From 84ecc5f798589a0592294b5b7e295d3df93a4236 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 8 Mar 2021 10:21:02 -0500 Subject: [PATCH 001/340] remove use of += in configure per issue #382 --- configure | 16 ++++++++-------- configure.ac | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/configure b/configure index 55102b042..b152d3abd 100755 --- a/configure +++ b/configure @@ -11997,14 +11997,14 @@ if test "$crypto_lib" = "none"; then $as_echo "none" >&6; } else if test "$crypto_lib" = "commoncrypto"; then - CFLAGS+=" -DSQLCIPHER_CRYPTO_CC" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_CC" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_CC" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_CC" { $as_echo "$as_me:${as_lineno-$LINENO}: result: commoncrypto" >&5 $as_echo "commoncrypto" >&6; } else if test "$crypto_lib" = "libtomcrypt"; then - CFLAGS+=" -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" { $as_echo "$as_me:${as_lineno-$LINENO}: result: libtomcrypt" >&5 $as_echo "libtomcrypt" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for register_cipher in -ltomcrypt" >&5 @@ -12056,8 +12056,8 @@ fi else if test "$crypto_lib" = "nss"; then - CFLAGS+=" -DSQLCIPHER_CRYPTO_NSS" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_NSS" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_NSS" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_NSS" { $as_echo "$as_me:${as_lineno-$LINENO}: result: nss3" >&5 $as_echo "nss3" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PK11_Decrypt in -lnss3" >&5 @@ -12108,8 +12108,8 @@ else fi else - CFLAGS+=" -DSQLCIPHER_CRYPTO_OPENSSL" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_OPENSSL" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_OPENSSL" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_OPENSSL" { $as_echo "$as_me:${as_lineno-$LINENO}: result: openssl" >&5 $as_echo "openssl" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for HMAC_Init_ex in -lcrypto" >&5 diff --git a/configure.ac b/configure.ac index ef212983a..b55c93516 100644 --- a/configure.ac +++ b/configure.ac @@ -203,26 +203,26 @@ if test "$crypto_lib" = "none"; then AC_MSG_RESULT([none]) else if test "$crypto_lib" = "commoncrypto"; then - CFLAGS+=" -DSQLCIPHER_CRYPTO_CC" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_CC" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_CC" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_CC" AC_MSG_RESULT([commoncrypto]) else if test "$crypto_lib" = "libtomcrypt"; then - CFLAGS+=" -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_LIBTOMCRYPT" AC_MSG_RESULT([libtomcrypt]) AC_CHECK_LIB([tomcrypt], [register_cipher], , AC_MSG_ERROR([Library crypto not found. Install libtomcrypt!"])) else if test "$crypto_lib" = "nss"; then - CFLAGS+=" -DSQLCIPHER_CRYPTO_NSS" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_NSS" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_NSS" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_NSS" AC_MSG_RESULT([nss3]) AC_CHECK_LIB([nss3], [PK11_Decrypt], , AC_MSG_ERROR([Library crypto not found. Install nss!"])) else - CFLAGS+=" -DSQLCIPHER_CRYPTO_OPENSSL" - BUILD_CFLAGS+=" -DSQLCIPHER_CRYPTO_OPENSSL" + CFLAGS="$CFLAGS -DSQLCIPHER_CRYPTO_OPENSSL" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLCIPHER_CRYPTO_OPENSSL" AC_MSG_RESULT([openssl]) AC_CHECK_LIB([crypto], [HMAC_Init_ex], , AC_MSG_ERROR([Library crypto not found. Install openssl!"])) From 20c087553300d3c92b9dd398f3c21dec30a2fa3c Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 16 Mar 2021 11:07:32 -0400 Subject: [PATCH 002/340] fix build when OMIT_MEMLOCK defined per #384 --- src/crypto_impl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index c7214ed54..0baff69d3 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -35,10 +35,10 @@ #include "crypto.h" #ifndef OMIT_MEMLOCK #if defined(__unix__) || defined(__APPLE__) || defined(_AIX) -#include -#include -#include -#include +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ #elif defined(_WIN32) #include #endif From a646b5ceddbd487f9a1ff49496ce264d59bcbda8 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 16 Mar 2021 11:29:10 -0400 Subject: [PATCH 003/340] restores usage note on sqlite3_rekey behavior for #301 and #383 --- src/sqlite.h.in | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/sqlite.h.in b/src/sqlite.h.in index c69a0e894..9b959e22d 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -5976,6 +5976,19 @@ int sqlite3_key_v2( ** The code to implement this API is not available in the public release ** of SQLite. */ +/* SQLCipher usage note: + + If the current database is plaintext SQLCipher will NOT encrypt it. + If the current database is encrypted and pNew==0 or nNew==0, SQLCipher + will NOT decrypt it. + + This routine will ONLY work on an already encrypted database in order + to change the key. + + Conversion from plaintext-to-encrypted or encrypted-to-plaintext should + use an ATTACHed database and the sqlcipher_export() convenience function + as per the SQLCipher Documentation. +*/ int sqlite3_rekey( sqlite3 *db, /* Database to be rekeyed */ const void *pKey, int nKey /* The new key */ From 3399ace24662a076353932b8c2b1f168ebf4bf42 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 2 Aug 2021 16:49:37 -0400 Subject: [PATCH 004/340] add new amalgamation_line_macros option missing from configure.ac --- configure | 25 ++++++++++++++++++++++++- configure.ac | 18 ++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/configure b/configure index b152d3abd..c6167ed83 100755 --- a/configure +++ b/configure @@ -634,6 +634,8 @@ BUILD_CFLAGS USE_GCOV OPT_FEATURE_FLAGS HAVE_ZLIB +AMALGAMATION_LINE_MACROS +amalgamation_line_macros USE_AMALGAMATION TARGET_DEBUG TARGET_HAVE_EDITLINE @@ -797,7 +799,8 @@ LIBS CPPFLAGS LT_SYS_LIBRARY_PATH CPP -TCLLIBDIR' +TCLLIBDIR +amalgamation_line_macros' # Initialize some variables set by options. @@ -1484,6 +1487,8 @@ Some influential environment variables: User-defined run-time library search path. CPP C preprocessor TCLLIBDIR Where to install tcl plugin + amalgamation_line_macros + Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -12873,6 +12878,24 @@ if test "${enable_amalgamation}" = "no" ; then fi +######### +# By default, amalgamation sqlite3.c will have #line directives. +# This is a build option not shown by ./configure --help +# To control it, use configure option: amalgamation_line_macros=? +# where ? is no to suppress #line directives or yes to create them. +AMALGAMATION_LINE_MACROS=--linemacros=1 + + +if test "${amalgamation_line_macros+set}" = set; then : + enableval=$amalgamation_line_macros; +fi +if test "${amalgamation_line_macros}" = "yes" ; then + AMALGAMATION_LINE_MACROS=--linemacros=1 +fi +if test "${amalgamation_line_macros}" = "no" ; then + AMALGAMATION_LINE_MACROS=--linemacros=0 +fi + ######### # Look for zlib. Only needed by extensions and by the sqlite3.exe shell for ac_header in zlib.h diff --git a/configure.ac b/configure.ac index b55c93516..31ca12310 100644 --- a/configure.ac +++ b/configure.ac @@ -624,6 +624,24 @@ if test "${enable_amalgamation}" = "no" ; then fi AC_SUBST(USE_AMALGAMATION) +######### +# By default, amalgamation sqlite3.c will have #line directives. +# This is a build option not shown by ./configure --help +# To control it, use configure option: amalgamation_line_macros=? +# where ? is no to suppress #line directives or yes to create them. +AMALGAMATION_LINE_MACROS=--linemacros=1 +AC_ARG_VAR(amalgamation_line_macros,) +AC_SUBST(AMALGAMATION_LINE_MACROS) +if test "${amalgamation_line_macros+set}" = set; then : + enableval=$amalgamation_line_macros; +fi +if test "${amalgamation_line_macros}" = "yes" ; then + AMALGAMATION_LINE_MACROS=--linemacros=1 +fi +if test "${amalgamation_line_macros}" = "no" ; then + AMALGAMATION_LINE_MACROS=--linemacros=0 +fi + ######### # Look for zlib. Only needed by extensions and by the sqlite3.exe shell AC_CHECK_HEADERS(zlib.h) From 5db049f916832d8b527145fd1de77211adce0d89 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 7 Sep 2021 13:53:53 -0400 Subject: [PATCH 005/340] alter realloc logic to allocate and lock new segment with memory security on --- src/crypto_impl.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 0baff69d3..cf06cf61d 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -95,8 +95,20 @@ static void sqlcipher_mem_free(void *p) { default_mem_methods.xFree(p); } static void *sqlcipher_mem_realloc(void *p, int n) { - return default_mem_methods.xRealloc(p, n); + void *new = NULL; + if(mem_security_on) { + new = sqlcipher_mem_malloc(n); + if(new == NULL) { + return NULL; + } + memcpy(new, p, n); + sqlcipher_mem_free(p); + return new; + } else { + return default_mem_methods.xRealloc(p, n); + } } + static int sqlcipher_mem_roundup(int n) { return default_mem_methods.xRoundup(n); } From 7b54c31180e837d1feb99e78d33fee65904a8c7b Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 8 Sep 2021 10:20:23 -0400 Subject: [PATCH 006/340] fix and expand sqlcipher_mem_realloc implementation --- src/crypto_impl.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index cf06cf61d..f20d1101c 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -96,14 +96,24 @@ static void sqlcipher_mem_free(void *p) { } static void *sqlcipher_mem_realloc(void *p, int n) { void *new = NULL; + int orig_sz = 0; if(mem_security_on) { - new = sqlcipher_mem_malloc(n); - if(new == NULL) { + orig_sz = sqlcipher_mem_size(p); + if (n==0) { + sqlcipher_mem_free(p); return NULL; + } else if (!p) { + return sqlcipher_mem_malloc(n); + } else if(n <= orig_sz) { + return p; + } else { + new = sqlcipher_mem_malloc(n); + if(new) { + memcpy(new, p, orig_sz); + sqlcipher_mem_free(p); + } + return new; } - memcpy(new, p, n); - sqlcipher_mem_free(p); - return new; } else { return default_mem_methods.xRealloc(p, n); } From 07ca482e4e675bc50cea6003b501c080a0c55e89 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 8 Sep 2021 10:26:36 -0400 Subject: [PATCH 007/340] disable memory security feature by default; once it is turned on it can't be turned off --- src/crypto_impl.c | 9 ++++++--- test/sqlcipher-core.test | 14 +++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index f20d1101c..e798b65db 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -51,7 +51,7 @@ static volatile int default_page_size = 4096; static volatile int default_plaintext_header_sz = 0; static volatile int default_hmac_algorithm = SQLCIPHER_HMAC_SHA512; static volatile int default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA512; -static volatile int mem_security_on = 1; +static volatile int mem_security_on = 0; static volatile int mem_security_initialized = 0; static volatile int mem_security_activated = 0; static volatile unsigned int sqlcipher_activate_count = 0; @@ -836,8 +836,11 @@ int sqlcipher_get_default_pagesize() { } void sqlcipher_set_mem_security(int on) { - mem_security_on = on; - mem_security_activated = 0; + /* memory security can only be enabled, not disabled */ + if(on) { + mem_security_on = on; + mem_security_activated = 0; + } } int sqlcipher_get_mem_security() { diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index cc3683397..185a0ff8b 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -730,20 +730,20 @@ db close file delete -force test.db # verify memory security behavior -# initially should report ON -# then disable, check that it is off -# turn it back on, then check. +# initially should report OFF +# then enable, check that it is ON +# try to turn if off, but verify that it +# can't be unset. do_test verify-memory-security { sqlite_orig db test.db execsql { - PRAGMA cipher_memory_security; - PRAGMA cipher_memory_security = OFF; PRAGMA cipher_memory_security; PRAGMA cipher_memory_security = ON; PRAGMA cipher_memory_security; - + PRAGMA cipher_memory_security = OFF; + PRAGMA cipher_memory_security; } -} {1 0 1} +} {0 1 1} db close file delete -force test.db From 3cfbe7934bacb04fbadb55e2a573bd0769cb3887 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 8 Sep 2021 13:03:59 -0400 Subject: [PATCH 008/340] format pointer for key material with trace logging enabled per #398 --- src/crypto.c | 12 ++++++------ src/crypto_impl.c | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 428b9a58c..5a181a885 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -82,7 +82,7 @@ static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ct static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("codec_set_pass_key: entered db=%p nDb=%d zKey=%s nKey=%d for_ctx=%d\n", db, nDb, (char *)zKey, nKey, for_ctx); + CODEC_TRACE("codec_set_pass_key: entered db=%p nDb=%d zKey=%p nKey=%d for_ctx=%d\n", db, nDb, zKey, nKey, for_ctx); if(pDb->pBt) { codec_ctx *ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); @@ -770,7 +770,7 @@ static void sqlite3FreeCodecArg(void *pCodecArg) { int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("sqlite3CodecAttach: entered db=%p, nDb=%d zKey=%s, nKey=%d\n", db, nDb, (char *)zKey, nKey); + CODEC_TRACE("sqlite3CodecAttach: entered db=%p, nDb=%d zKey=%p, nKey=%d\n", db, nDb, zKey, nKey); if(nKey && zKey && pDb->pBt) { @@ -858,12 +858,12 @@ void sqlite3_activate_see(const char* in) { } int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_key entered: db=%p pKey=%s nKey=%d\n", db, (char *)pKey, nKey); + CODEC_TRACE("sqlite3_key entered: db=%p pKey=%p nKey=%d\n", db, pKey, nKey); return sqlite3_key_v2(db, "main", pKey, nKey); } int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_key_v2: entered db=%p zDb=%s pKey=%s nKey=%d\n", db, zDb, (char *)pKey, nKey); + CODEC_TRACE("sqlite3_key_v2: entered db=%p zDb=%s pKey=%p nKey=%d\n", db, zDb, pKey, nKey); /* attach key if db and pKey are not null and nKey is > 0 */ if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); @@ -873,7 +873,7 @@ int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { } int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_rekey entered: db=%p pKey=%s nKey=%d\n", db, (char *)pKey, nKey); + CODEC_TRACE("sqlite3_rekey entered: db=%p pKey=%p nKey=%d\n", db, pKey, nKey); return sqlite3_rekey_v2(db, "main", pKey, nKey); } @@ -888,7 +888,7 @@ int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { ** 3. If there is a key present, re-encrypt the database with the new key */ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_rekey_v2: entered db=%p zDb=%s pKey=%s, nKey=%d\n", db, zDb, (char *)pKey, nKey); + CODEC_TRACE("sqlite3_rekey_v2: entered db=%p zDb=%s pKey=%p, nKey=%d\n", db, zDb, pKey, nKey); if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); struct Db *pDb = &db->aDb[db_index]; diff --git a/src/crypto_impl.c b/src/crypto_impl.c index e798b65db..93c44079a 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1117,7 +1117,7 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int */ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { int rc; - CODEC_TRACE("cipher_ctx_key_derive: entered c_ctx->pass=%s, c_ctx->pass_sz=%d \ + CODEC_TRACE("cipher_ctx_key_derive: entered c_ctx->pass=%p, c_ctx->pass_sz=%d \ ctx->kdf_salt=%p ctx->kdf_salt_sz=%d ctx->kdf_iter=%d \ ctx->hmac_kdf_salt=%p, ctx->fast_kdf_iter=%d ctx->key_sz=%d\n", c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, From a422e6d25664625c285e7450cae423270d6f543a Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Mon, 4 Oct 2021 13:41:03 -0700 Subject: [PATCH 009/340] Fix some formatting and grammar in README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 608cb8dc5..945f43e90 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ The SQLCipher team welcomes contributions to the core library. All contributions ## Compiling -Building SQLCipher is similar to compiling a regular version of SQLite from source a couple small exceptions: +Building SQLCipher is similar to compiling a regular version of SQLite from source, with a couple of small exceptions: - 1. You *must* define `SQLITE_HAS_CODEC` and either `SQLITE_TEMP_STORE=2` or SQLITE_TEMP_STORE=3` + 1. You *must* define `SQLITE_HAS_CODEC` and either `SQLITE_TEMP_STORE=2` or `SQLITE_TEMP_STORE=3` 2. You will need to link against a support cryptographic provider (OpenSSL, LibTomCrypt, CommonCrypto/Security.framework, or NSS) The following examples demonstrate linking against OpenSSL, which is a readily available provider on most Unix-like systems. @@ -43,17 +43,17 @@ Example 1. Static linking (replace /opt/local/lib with the path to libcrypto.a). example, `--enable-tempstore=yes` is setting `SQLITE_TEMP_STORE=2` for the build. ``` - $ ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ - LDFLAGS="/opt/local/lib/libcrypto.a" - $ make +$ ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ + LDFLAGS="/opt/local/lib/libcrypto.a" +$ make ``` Example 2. Dynamic linking ``` - $ ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ - LDFLAGS="-lcrypto" - $ make +$ ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ + LDFLAGS="-lcrypto" +$ make ``` ## Testing @@ -65,10 +65,10 @@ As a result, the SQLCipher package includes it's own independent tests that exer To run SQLCipher specific tests, configure as described here and run the following to execute the tests and receive a report of the results: ``` - $ ./configure --enable-tempstore=yes --enable-fts5 CFLAGS="-DSQLITE_HAS_CODEC -DSQLCIPHER_TEST" \ - LDFLAGS="-lcrypto" - $ make testfixture - $ ./testfixture test/sqlcipher.test +$ ./configure --enable-tempstore=yes --enable-fts5 CFLAGS="-DSQLITE_HAS_CODEC -DSQLCIPHER_TEST" \ + LDFLAGS="-lcrypto" +$ make testfixture +$ ./testfixture test/sqlcipher.test ``` ## Encrypting a database From 328a4c84379d87de81e14d8ddd1d99b77f0979d7 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 5 Oct 2021 16:25:47 -0400 Subject: [PATCH 010/340] Snapshot of upstream SQLite 3.36.0 --- Makefile.in | 20 +- Makefile.msc | 14 +- README.md | 24 +- VERSION | 2 +- autoconf/Makefile.msc | 5 +- autoconf/configure.ac | 48 +- autoconf/tea/win/nmakehlp.c | 183 +- configure | 214 +- configure.ac | 68 +- doc/lemon.html | 38 +- ext/expert/expert1.test | 85 +- ext/expert/sqlite3expert.c | 2 +- ext/fts3/fts3.c | 35 +- ext/fts3/fts3Int.h | 2 +- ext/fts3/fts3_aux.c | 1 + ext/fts3/fts3_expr.c | 5 + ext/fts3/fts3_snippet.c | 24 +- ext/fts3/fts3_test.c | 2 + ext/fts3/fts3_unicode.c | 1 + ext/fts3/fts3_write.c | 17 +- ext/fts5/fts5_buffer.c | 22 +- ext/fts5/fts5_config.c | 2 +- ext/fts5/fts5_expr.c | 11 +- ext/fts5/fts5_index.c | 567 ++-- ext/fts5/fts5_main.c | 19 +- ext/fts5/fts5_tcl.c | 4 + ext/fts5/fts5_test_tok.c | 2 +- ext/fts5/test/fts5corrupt3.test | 4629 ++++++++++++++++++++++++++++++- ext/fts5/test/fts5corrupt4.test | 2 + ext/fts5/test/fts5delete.test | 23 +- ext/fts5/test/fts5doclist.test | 21 + ext/fts5/test/fts5plan.test | 14 +- ext/fts5/test/fts5prefix2.test | 57 + ext/fts5/test/fts5trigram.test | 3 +- ext/misc/appendvfs.c | 333 ++- ext/misc/cksumvfs.c | 70 +- ext/misc/decimal.c | 9 +- ext/misc/ieee754.c | 8 + ext/misc/json1.c | 21 +- ext/misc/memvfs.c | 1 + ext/misc/regexp.c | 37 +- ext/misc/series.c | 16 +- ext/misc/shathree.c | 3 + ext/misc/vfslog.c | 1 + ext/misc/vfsstat.c | 1 + ext/misc/wholenumber.c | 2 +- ext/misc/zipfile.c | 67 +- ext/rbu/rbu.c | 9 +- ext/rbu/rbu1.test | 5 + ext/rbu/rbudiff.test | 8 + ext/rbu/sqlite3rbu.c | 46 +- ext/rtree/geopoly.c | 6 +- ext/rtree/rtree.c | 7 +- ext/rtree/rtree6.test | 24 +- ext/rtree/rtreeC.test | 88 +- ext/rtree/rtreefuzz001.test | 162 ++ ext/session/session2.test | 58 +- ext/session/sessionbig.test | 108 + ext/session/sessionmem.test | 57 + ext/session/sessionnoop.test | 187 ++ ext/session/sessionsize.test | 131 + ext/session/sessionwor.test | 25 +- ext/session/sqlite3session.c | 685 +++-- ext/session/sqlite3session.h | 81 +- ext/session/test_session.c | 45 +- main.mk | 25 +- manifest | 643 +++-- manifest.uuid | 2 +- src/alter.c | 611 +++- src/attach.c | 212 +- src/auth.c | 17 +- src/btree.c | 306 +- src/btree.h | 3 + src/btreeInt.h | 3 +- src/build.c | 359 ++- src/ctime.c | 66 +- src/date.c | 1 + src/delete.c | 40 +- src/expr.c | 271 +- src/fkey.c | 21 +- src/func.c | 260 +- src/global.c | 37 +- src/insert.c | 370 ++- src/loadext.c | 38 +- src/main.c | 73 +- src/malloc.c | 1 - src/memdb.c | 349 ++- src/memjournal.c | 41 +- src/os.c | 13 +- src/os.h | 6 + src/os_unix.c | 61 +- src/pager.c | 22 +- src/parse.y | 123 +- src/pcache1.c | 1 + src/pragma.c | 2 +- src/prepare.c | 165 +- src/printf.c | 39 +- src/random.c | 7 +- src/resolve.c | 276 +- src/select.c | 1200 +++++--- src/shell.c.in | 244 +- src/sqlite.h.in | 97 +- src/sqliteInt.h | 436 ++- src/tclsqlite.c | 4 +- src/test1.c | 84 +- src/test8.c | 6 + src/test_config.c | 21 +- src/tokenize.c | 89 +- src/treeview.c | 29 +- src/trigger.c | 290 +- src/update.c | 14 +- src/upsert.c | 232 +- src/util.c | 10 + src/vdbe.c | 415 +-- src/vdbe.h | 2 +- src/vdbeInt.h | 9 +- src/vdbeapi.c | 35 +- src/vdbeaux.c | 63 +- src/vdbeblob.c | 3 +- src/vdbemem.c | 20 +- src/vdbesort.c | 5 +- src/vdbetrace.c | 2 +- src/vtab.c | 6 +- src/wal.c | 5 +- src/walker.c | 45 +- src/where.c | 215 +- src/whereInt.h | 26 +- src/wherecode.c | 70 +- src/whereexpr.c | 192 +- src/window.c | 145 +- test/alter.test | 12 + test/alter4.test | 8 +- test/alterauth2.test | 20 + test/altercol.test | 60 +- test/altercorrupt.test | 175 ++ test/alterdropcol.test | 339 +++ test/alterdropcol2.test | 222 ++ test/alterlegacy.test | 4 +- test/altermalloc2.test | 21 + test/altermalloc3.test | 47 + test/alterqf.test | 111 + test/altertab.test | 258 +- test/altertab3.test | 2 +- test/analyze3.test | 30 +- test/analyze4.test | 2 +- test/analyze6.test | 24 +- test/analyze7.test | 32 +- test/analyze8.test | 22 +- test/analyze9.test | 30 +- test/analyzeC.test | 2 +- test/analyzeD.test | 8 +- test/analyzeE.test | 88 +- test/analyzeF.test | 4 +- test/analyzeG.test | 32 +- test/attach.test | 15 + test/attach4.test | 1 - test/auth.test | 16 +- test/autoindex1.test | 30 +- test/autoindex3.test | 8 +- test/autoindex5.test | 6 +- test/avfs.test | 395 +++ test/backup2.test | 5 +- test/bestindex1.test | 10 +- test/bestindex2.test | 30 +- test/bestindex3.test | 16 +- test/bestindex4.test | 8 +- test/bestindex7.test | 1 - test/between.test | 6 +- test/bigmmap.test | 4 +- test/busy2.test | 1 - test/columncount.test | 56 + test/corrupt4.test | 69 +- test/corruptL.test | 88 + test/corruptN.test | 228 ++ test/cost.test | 46 +- test/count.test | 8 +- test/coveridxscan.test | 4 +- test/crash5.test | 75 +- test/date2.test | 7 + test/dbfuzz2.c | 19 +- test/delete.test | 2 +- test/delete4.test | 12 +- test/distinct.test | 15 +- test/distinctagg.test | 157 +- test/e_createtable.test | 6 +- test/e_fkey.test | 22 +- test/e_select.test | 9 +- test/eqp.test | 324 ++- test/exprfault.test | 35 + test/external_reader.test | 74 + test/fts3aux1.test | 34 +- test/fts3corrupt4.test | 510 ++++ test/fts3corrupt6.test | 26 +- test/fts3join.test | 8 +- test/fts3query.test | 16 +- test/fts3snippet2.test | 1 - test/fts4rename.test | 2 +- test/fts4unicode.test | 18 + test/fts4upfrom.test | 1 - test/func.test | 7 + test/func7.test | 245 ++ test/fuzzcheck.c | 117 +- test/fuzzdata8.db | Bin 1616896 -> 2398208 bytes test/fuzzerfault.test | 19 + test/having.test | 13 + test/hook.test | 57 + test/in4.test | 107 +- test/in6.test | 19 + test/incrvacuum.test | 58 +- test/index6.test | 22 +- test/index7.test | 14 +- test/index8.test | 2 +- test/indexedby.test | 68 +- test/indexexpr1.test | 4 +- test/indexexpr2.test | 10 +- test/intpkey.test | 2 +- test/join2.test | 34 +- test/join5.test | 10 +- test/like.test | 29 +- test/like3.test | 24 +- test/limit.test | 23 + test/mallocK.test | 2 +- test/memdb1.test | 10 + test/memjournal.test | 48 + test/minmax.test | 15 +- test/minmax2.test | 15 +- test/misc2.test | 25 +- test/misc7.test | 6 +- test/misc8.test | 4 +- test/notnull2.test | 112 + test/notnullfault.test | 55 + test/nulls1.test | 10 +- test/optfuzz.c | 1 - test/orderby1.test | 2 +- test/pager1.test | 9 + test/permutations.test | 19 +- test/quote.test | 45 +- test/regexp1.test | 35 +- test/regexp2.test | 20 + test/releasetest.tcl | 4 +- test/releasetest_data.tcl | 32 +- test/returning1.test | 321 +++ test/rollback2.test | 4 +- test/rowvalue.test | 60 +- test/rowvalue4.test | 34 +- test/rowvalue9.test | 13 + test/scanstatus.test | 86 +- test/select1.test | 12 +- test/select4.test | 14 + test/select5.test | 11 +- test/select9.test | 6 +- test/selectA.test | 19 +- test/selectC.test | 6 +- test/selectD.test | 2 +- test/shell1.test | 4 +- test/shell4.test | 2 +- test/skipscan1.test | 4 +- test/skipscan2.test | 2 +- test/skipscan5.test | 24 +- test/skipscan6.test | 4 +- test/speedtest1.c | 132 +- test/sqllimits1.test | 4 +- test/startup.c | 628 +++++ test/tabfunc01.test | 44 + test/tester.tcl | 17 +- test/threadtest5.c | 339 +++ test/tkt-385a5b56b9.test | 8 +- test/tkt-78e04e52ea.test | 6 +- test/tkt-b75a9ca6b0.test | 4 +- test/tkt3442.test | 6 +- test/tpch01.test | 14 +- test/trace3.test | 79 + test/transitive1.test | 31 +- test/trigger2.test | 21 +- test/trigger9.test | 11 +- test/triggerE.test | 2 + test/triggerupfrom.test | 1 - test/unionall.test | 367 +++ test/unionall2.test | 58 + test/unionallfault.test | 36 + test/unordered.test | 28 +- test/upfrom1.test | 32 + test/upfrom2.test | 2 - test/upfrom3.test | 1 - test/upfromfault.test | 1 - test/upsert5.test | 411 +++ test/vacuum2.test | 2 +- test/vacuum6.test | 112 + test/view.test | 7 +- test/view2.test | 49 + test/vtab1.test | 1 + test/wal.test | 31 + test/walsetlk.test | 1 - test/walvfs.test | 9 +- test/where.test | 20 +- test/where2.test | 10 +- test/where3.test | 50 +- test/where7.test | 6 +- test/where9.test | 39 +- test/whereE.test | 8 +- test/whereF.test | 6 +- test/whereG.test | 79 +- test/whereI.test | 8 +- test/whereJ.test | 6 +- test/whereK.test | 10 +- test/whereL.test | 21 +- test/whereM.test | 112 + test/wherefault.test | 24 + test/wherelimit.test | 22 +- test/window1.test | 179 +- test/window8.tcl | 70 + test/window8.test | 71 + test/window9.test | 17 + test/windowB.test | 21 + test/windowfault.test | 22 +- test/windowpushd.test | 236 ++ test/with1.test | 64 +- test/with2.test | 196 ++ test/with3.test | 76 +- test/with6.test | 255 ++ test/without_rowid1.test | 13 + test/without_rowid6.test | 8 +- test/zipfile.test | 12 + tool/lemon.c | 14 +- tool/lempar.c | 101 +- tool/merge-test.tcl | 99 + tool/mkctimec.tcl | 160 +- tool/mkkeywordhash.c | 32 +- tool/mkopcodeh.tcl | 14 +- tool/mkshellc.tcl | 1 + tool/omittest.tcl | 2 + tool/showdb.c | 17 +- tool/sqldiff.c | 14 +- 333 files changed, 22890 insertions(+), 4623 deletions(-) create mode 100644 ext/fts5/test/fts5prefix2.test create mode 100644 ext/session/sessionbig.test create mode 100644 ext/session/sessionmem.test create mode 100644 ext/session/sessionnoop.test create mode 100644 ext/session/sessionsize.test create mode 100644 test/altercorrupt.test create mode 100644 test/alterdropcol.test create mode 100644 test/alterdropcol2.test create mode 100644 test/altermalloc3.test create mode 100644 test/alterqf.test create mode 100644 test/avfs.test create mode 100644 test/columncount.test create mode 100644 test/corruptN.test create mode 100644 test/exprfault.test create mode 100644 test/external_reader.test create mode 100644 test/func7.test create mode 100644 test/memjournal.test create mode 100644 test/notnull2.test create mode 100644 test/notnullfault.test create mode 100644 test/returning1.test create mode 100644 test/startup.c create mode 100644 test/threadtest5.c create mode 100644 test/unionall.test create mode 100644 test/unionall2.test create mode 100644 test/unionallfault.test create mode 100644 test/upsert5.test create mode 100644 test/vacuum6.test create mode 100644 test/view2.test create mode 100644 test/whereM.test create mode 100644 test/windowpushd.test create mode 100644 test/with6.test create mode 100644 tool/merge-test.tcl diff --git a/Makefile.in b/Makefile.in index 6ab49dbe9..2795ffa9e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -432,7 +432,7 @@ TESTSRC = \ $(TOP)/ext/fts3/fts3_term.c \ $(TOP)/ext/fts3/fts3_test.c \ $(TOP)/ext/session/test_session.c \ - $(TOP)/ext/rbu/test_rbu.c + $(TOP)/ext/rbu/test_rbu.c # Statically linked extensions # @@ -440,6 +440,7 @@ TESTSRC += \ $(TOP)/ext/expert/sqlite3expert.c \ $(TOP)/ext/expert/test_expert.c \ $(TOP)/ext/misc/amatch.c \ + $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/cksumvfs.c \ $(TOP)/ext/misc/closure.c \ @@ -520,7 +521,8 @@ TESTSRC2 = \ $(TOP)/ext/fts3/fts3_write.c \ $(TOP)/ext/async/sqlite3async.c \ $(TOP)/ext/session/sqlite3session.c \ - $(TOP)/ext/misc/stmt.c + $(TOP)/ext/misc/stmt.c \ + fts5.c # Header files used by all library source files. # @@ -614,12 +616,10 @@ SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC -SHELL_OPT += -DSQLITE_ENABLE_DESERIALIZE FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 -FUZZCHECK_OPT += -DSQLITE_ENABLE_DESERIALIZE FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4 FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS3_PARENTHESIS #FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS5 @@ -693,7 +693,6 @@ dbfuzz$(TEXE): $(TOP)/test/dbfuzz.c sqlite3.c sqlite3.h DBFUZZ2_OPTS = \ -DSQLITE_THREADSAFE=0 \ -DSQLITE_OMIT_LOAD_EXTENSION \ - -DSQLITE_ENABLE_DESERIALIZE \ -DSQLITE_DEBUG \ -DSQLITE_ENABLE_DBSTAT_VTAB \ -DSQLITE_ENABLE_BYTECODE_VTAB \ @@ -1087,6 +1086,7 @@ SHELL_SRC = \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ + $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ @@ -1233,7 +1233,6 @@ TESTFIXTURE_FLAGS += -DSQLITE_DEFAULT_PAGE_SIZE=1024 TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_STMTVTAB TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_DBPAGE_VTAB TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_BYTECODE_VTAB -TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_DESERIALIZE TESTFIXTURE_FLAGS += -DSQLITE_CKSUMVFS_STATIC TESTFIXTURE_SRC0 = $(TESTSRC2) libsqlite3.la @@ -1267,7 +1266,7 @@ fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuz ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db - valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA) + valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M $(FUZZDATA) valgrind ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db # The veryquick.test TCL tests. @@ -1378,6 +1377,9 @@ wordcount$(TEXE): $(TOP)/test/wordcount.c sqlite3.lo speedtest1$(TEXE): $(TOP)/test/speedtest1.c sqlite3.c $(LTLINK) $(ST_OPT) -o $@ $(TOP)/test/speedtest1.c sqlite3.c $(TLIBS) +startup$(TEXE): $(TOP)/test/startup.c sqlite3.c + $(CC) -Os -g -DSQLITE_THREADSAFE=0 -o $@ $(TOP)/test/startup.c sqlite3.c $(TLIBS) + KV_OPT += -DSQLITE_DIRECT_OVERFLOW_READ kvtest$(TEXE): $(TOP)/test/kvtest.c sqlite3.c @@ -1426,6 +1428,9 @@ threadtest3$(TEXE): sqlite3.lo $(THREADTEST3_SRC) threadtest: threadtest3$(TEXE) ./threadtest3$(TEXE) +threadtest5: sqlite3.c $(TOP)/test/threadtest5.c + $(LTLINK) $(TOP)/test/threadtest5.c sqlite3.c -o $@ $(TLIBS) + releasetest: $(TCLSH_CMD) $(TOP)/test/releasetest.tcl @@ -1479,6 +1484,7 @@ clean: rm -f sqldiff sqldiff.exe rm -f dbhash dbhash.exe rm -f fts5.* fts5parse.* + rm -f threadtest5 distclean: clean rm -f config.h config.log config.status libtool Makefile sqlite3.pc diff --git a/Makefile.msc b/Makefile.msc index 404e3b236..4f20c0c0c 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -367,7 +367,6 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DBSTAT_VTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 -OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DESERIALIZE=1 !ENDIF OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1 !ENDIF @@ -380,6 +379,9 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_SESSION=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_PREUPDATE_HOOK=1 !ENDIF +# Always enable math functions on Windows +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS + # Should the rbu extension be enabled? If so, add compilation options # to enable it. # @@ -1549,7 +1551,7 @@ TESTSRC = \ $(TOP)\ext\fts3\fts3_term.c \ $(TOP)\ext\fts3\fts3_test.c \ $(TOP)\ext\rbu\test_rbu.c \ - $(TOP)\ext\session\test_session.c + $(TOP)\ext\session\test_session.c # Statically linked extensions. # @@ -1557,6 +1559,7 @@ TESTEXT = \ $(TOP)\ext\expert\sqlite3expert.c \ $(TOP)\ext\expert\test_expert.c \ $(TOP)\ext\misc\amatch.c \ + $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\carray.c \ $(TOP)\ext\misc\cksumvfs.c \ $(TOP)\ext\misc\closure.c \ @@ -1581,7 +1584,8 @@ TESTEXT = \ $(TOP)\ext\misc\spellfix.c \ $(TOP)\ext\misc\totype.c \ $(TOP)\ext\misc\unionvtab.c \ - $(TOP)\ext\misc\wholenumber.c + $(TOP)\ext\misc\wholenumber.c \ + fts5.c # If use of zlib is enabled, add the "zipfile.c" source file. # @@ -1684,7 +1688,6 @@ FUZZDATA = \ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 -SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DESERIALIZE=1 !ENDIF # <> @@ -1693,7 +1696,6 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DESERIALIZE=1 MPTESTER_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 FUZZERSHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -DSQLITE_MAX_MEMORY=50000000 -DSQLITE_PRINTF_PRECISION_LIMIT=1000 -FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DESERIALIZE FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS4 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_RTREE FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY @@ -2209,6 +2211,7 @@ SHELL_SRC = \ $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\fileio.c \ $(TOP)\ext\misc\ieee754.c \ + $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\shathree.c \ $(TOP)\ext\misc\uint.c \ @@ -2387,7 +2390,6 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_JSON1=1 -TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_DESERIALIZE=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_CKSUMVFS_STATIC=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS) diff --git a/README.md b/README.md index c12b77c1b..b79fc67f1 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ a stand-alone program. To install, simply download or build the single executable file and put that file someplace on your $PATH.) Then run commands like this: - mkdir ~/sqlite + mkdir -p ~/sqlite ~/Fossils cd ~/sqlite - fossil clone https://www.sqlite.org/src sqlite.fossil - fossil open sqlite.fossil - + fossil clone https://www.sqlite.org/src ~/Fossils/sqlite.fossil + fossil open ~/Fossils/sqlite.fossil + After setting up a repository using the steps above, you can always update to the lastest version using: @@ -68,7 +68,7 @@ update to the lastest version using: Or type "fossil ui" to get a web-based user interface. -## Compiling +## Compiling for Unix-like systems First create a directory in which to place the build products. It is recommended, but not required, that the @@ -94,22 +94,22 @@ script does not work out for you, there is a generic makefile named can copy and edit to suit your needs. Comments on the generic makefile show what changes are needed. -## Using MSVC +## Using MSVC for Windows systems On Windows, all applicable build products can be compiled with MSVC. First open the command prompt window associated with the desired compiler version (e.g. "Developer Command Prompt for VS2013"). Next, use NMAKE with the provided "Makefile.msc" to build one of the supported targets. -For example: +For example, from the parent directory of the source subtree named "sqlite": mkdir bld cd bld - nmake /f Makefile.msc TOP=..\sqlite - nmake /f Makefile.msc sqlite3.c TOP=..\sqlite - nmake /f Makefile.msc sqlite3.dll TOP=..\sqlite - nmake /f Makefile.msc sqlite3.exe TOP=..\sqlite - nmake /f Makefile.msc test TOP=..\sqlite + nmake /f ..\sqlite\Makefile.msc TOP=..\sqlite + nmake /f ..\sqlite\Makefile.msc sqlite3.c TOP=..\sqlite + nmake /f ..\sqlite\Makefile.msc sqlite3.dll TOP=..\sqlite + nmake /f ..\sqlite\Makefile.msc sqlite3.exe TOP=..\sqlite + nmake /f ..\sqlite\Makefile.msc test TOP=..\sqlite There are several build options that can be set via the NMAKE command line. For example, to build for WinRT, simply add "FOR_WINRT=1" argument diff --git a/VERSION b/VERSION index 62d286466..0b477b458 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.34.1 +3.36.0 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 746162a00..40d0e8113 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -290,7 +290,6 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DBSTAT_VTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 -OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DESERIALIZE=1 !ENDIF OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1 !ENDIF @@ -303,6 +302,9 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_SESSION=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_PREUPDATE_HOOK=1 !ENDIF +# Always enable math functions on Windows +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS + # Should the rbu extension be enabled? If so, add compilation options # to enable it. # @@ -957,7 +959,6 @@ LIBRESOBJS = SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 -SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DESERIALIZE=1 !ENDIF diff --git a/autoconf/configure.ac b/autoconf/configure.ac index 167626d59..e050786bd 100644 --- a/autoconf/configure.ac +++ b/autoconf/configure.ac @@ -87,7 +87,9 @@ AC_SUBST(READLINE_LIBS) AC_ARG_ENABLE(threadsafe, [AS_HELP_STRING( [--enable-threadsafe], [build a thread-safe library [default=yes]])], [], [enable_threadsafe=yes]) -if test x"$enable_threadsafe" != "xno"; then +if test x"$enable_threadsafe" == "xno"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_THREADSAFE=0" +else BUILD_CFLAGS="$BUILD_CFLAGS -D_REENTRANT=1 -DSQLITE_THREADSAFE=1" AC_SEARCH_LIBS(pthread_create, pthread) AC_SEARCH_LIBS(pthread_mutexattr_init, pthread) @@ -109,14 +111,34 @@ AC_MSG_CHECKING([for whether to support dynamic extensions]) AC_MSG_RESULT($enable_dynamic_extensions) #----------------------------------------------------------------------- +#----------------------------------------------------------------------- +# --enable-math +# +AC_ARG_ENABLE(math, [AS_HELP_STRING( + [--enable-math], [SQL math functions [default=yes]])], + [], [enable_math=yes]) +AC_MSG_CHECKING([SQL math functions]) +if test x"$enable_math" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_MATH_FUNCTIONS" + AC_MSG_RESULT([enabled]) + AC_SEARCH_LIBS(ceil, m) +else + AC_MSG_RESULT([disabled]) +fi +#----------------------------------------------------------------------- + #----------------------------------------------------------------------- # --enable-fts4 # AC_ARG_ENABLE(fts4, [AS_HELP_STRING( [--enable-fts4], [include fts4 support [default=yes]])], [], [enable_fts4=yes]) +AC_MSG_CHECKING([FTS4 extension]) if test x"$enable_fts4" = "xyes"; then BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS4" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) fi #----------------------------------------------------------------------- @@ -126,8 +148,12 @@ fi AC_ARG_ENABLE(fts3, [AS_HELP_STRING( [--enable-fts3], [include fts3 support [default=no]])], [], []) +AC_MSG_CHECKING([FTS3 extension]) if test x"$enable_fts3" = "xyes" -a x"$enable_fts4" = "xno"; then BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS3" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) fi #----------------------------------------------------------------------- @@ -137,9 +163,13 @@ fi AC_ARG_ENABLE(fts5, [AS_HELP_STRING( [--enable-fts5], [include fts5 support [default=yes]])], [], [enable_fts5=yes]) +AC_MSG_CHECKING([FTS5 extension]) if test x"$enable_fts5" = "xyes"; then + AC_MSG_RESULT([enabled]) AC_SEARCH_LIBS(log, m) BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS5" +else + AC_MSG_RESULT([disabled]) fi #----------------------------------------------------------------------- @@ -149,8 +179,12 @@ fi AC_ARG_ENABLE(json1, [AS_HELP_STRING( [--enable-json1], [include json1 support [default=yes]])], [],[enable_json1=yes]) +AC_MSG_CHECKING([JSON functions]) if test x"$enable_json1" = "xyes"; then BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_JSON1" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) fi #----------------------------------------------------------------------- @@ -160,8 +194,12 @@ fi AC_ARG_ENABLE(rtree, [AS_HELP_STRING( [--enable-rtree], [include rtree support [default=yes]])], [], [enable_rtree=yes]) +AC_MSG_CHECKING([RTREE extension]) if test x"$enable_rtree" = "xyes"; then BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_GEOPOLY" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) fi #----------------------------------------------------------------------- @@ -171,8 +209,12 @@ fi AC_ARG_ENABLE(session, [AS_HELP_STRING( [--enable-session], [enable the session extension [default=no]])], [], []) +AC_MSG_CHECKING([Session extension]) if test x"$enable_session" = "xyes"; then BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) fi #----------------------------------------------------------------------- @@ -182,9 +224,13 @@ fi AC_ARG_ENABLE(debug, [AS_HELP_STRING( [--enable-debug], [build with debugging features enabled [default=no]])], [], []) +AC_MSG_CHECKING([Build type]) if test x"$enable_debug" = "xyes"; then BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_DEBUG -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE" CFLAGS="-g -O0" + AC_MSG_RESULT([debug]) +else + AC_MSG_RESULT([release]) fi #----------------------------------------------------------------------- diff --git a/autoconf/tea/win/nmakehlp.c b/autoconf/tea/win/nmakehlp.c index e00f1b499..7536edee3 100644 --- a/autoconf/tea/win/nmakehlp.c +++ b/autoconf/tea/win/nmakehlp.c @@ -14,13 +14,8 @@ #define _CRT_SECURE_NO_DEPRECATE #include -#define NO_SHLWAPI_GDI -#define NO_SHLWAPI_STREAM -#define NO_SHLWAPI_REG -#include #pragma comment (lib, "user32.lib") #pragma comment (lib, "kernel32.lib") -#pragma comment (lib, "shlwapi.lib") #include #include @@ -39,15 +34,15 @@ #endif - /* protos */ static int CheckForCompilerFeature(const char *option); -static int CheckForLinkerFeature(const char *option); +static int CheckForLinkerFeature(const char **options, int count); static int IsIn(const char *string, const char *substring); static int SubstituteFile(const char *substs, const char *filename); static int QualifyPath(const char *path); -static const char *GetVersionFromFile(const char *filename, const char *match); +static int LocateDependency(const char *keyfile); +static const char *GetVersionFromFile(const char *filename, const char *match, int numdots); static DWORD WINAPI ReadFromPipe(LPVOID args); /* globals */ @@ -74,6 +69,7 @@ main( char msg[300]; DWORD dwWritten; int chars; + const char *s; /* * Make sure children (cl.exe and link.exe) are kept quiet. @@ -102,16 +98,16 @@ main( } return CheckForCompilerFeature(argv[2]); case 'l': - if (argc != 3) { + if (argc < 3) { chars = snprintf(msg, sizeof(msg) - 1, - "usage: %s -l \n" + "usage: %s -l ? ...?\n" "Tests for whether link.exe supports an option\n" "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL); return 2; } - return CheckForLinkerFeature(argv[2]); + return CheckForLinkerFeature(&argv[2], argc-2); case 'f': if (argc == 2) { chars = snprintf(msg, sizeof(msg) - 1, @@ -153,8 +149,13 @@ main( &dwWritten, NULL); return 0; } - printf("%s\n", GetVersionFromFile(argv[2], argv[3])); - return 0; + s = GetVersionFromFile(argv[2], argv[3], *(argv[1]+2) - '0'); + if (s && *s) { + printf("%s\n", s); + return 0; + } else + return 1; /* Version not found. Return non-0 exit code */ + case 'Q': if (argc != 3) { chars = snprintf(msg, sizeof(msg) - 1, @@ -166,6 +167,18 @@ main( return 2; } return QualifyPath(argv[2]); + + case 'L': + if (argc != 3) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -L keypath\n" + "Emit the fully qualified path of directory containing keypath\n" + "exitcodes: 0 == success, 1 == not found, 2 == error\n", argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 2; + } + return LocateDependency(argv[2]); } } chars = snprintf(msg, sizeof(msg) - 1, @@ -313,7 +326,8 @@ CheckForCompilerFeature( static int CheckForLinkerFeature( - const char *option) + const char **options, + int count) { STARTUPINFO si; PROCESS_INFORMATION pi; @@ -322,7 +336,8 @@ CheckForLinkerFeature( char msg[300]; BOOL ok; HANDLE hProcess, h, pipeThreads[2]; - char cmdline[100]; + int i; + char cmdline[255]; hProcess = GetCurrentProcess(); @@ -368,7 +383,11 @@ CheckForLinkerFeature( * Append our option for testing. */ - lstrcat(cmdline, option); + for (i = 0; i < count; i++) { + lstrcat(cmdline, " \""); + lstrcat(cmdline, options[i]); + lstrcat(cmdline, "\""); + } ok = CreateProcess( NULL, /* Module name. */ @@ -433,7 +452,9 @@ CheckForLinkerFeature( return !(strstr(Out.buffer, "LNK1117") != NULL || strstr(Err.buffer, "LNK1117") != NULL || strstr(Out.buffer, "LNK4044") != NULL || - strstr(Err.buffer, "LNK4044") != NULL); + strstr(Err.buffer, "LNK4044") != NULL || + strstr(Out.buffer, "LNK4224") != NULL || + strstr(Err.buffer, "LNK4224") != NULL); } static DWORD WINAPI @@ -479,7 +500,8 @@ IsIn( static const char * GetVersionFromFile( const char *filename, - const char *match) + const char *match, + int numdots) { size_t cbBuffer = 100; static char szBuffer[100]; @@ -497,9 +519,10 @@ GetVersionFromFile( p = strstr(szBuffer, match); if (p != NULL) { /* - * Skip to first digit. + * Skip to first digit after the match. */ + p += strlen(match); while (*p && !isdigit(*p)) { ++p; } @@ -509,7 +532,8 @@ GetVersionFromFile( */ q = p; - while (*q && (isalnum(*q) || *q == '.')) { + while (*q && (strchr("0123456789.ab", *q)) && ((!strchr(".ab", *q) + && (!strchr("ab", q[-1])) || --numdots))) { ++q; } @@ -619,7 +643,7 @@ SubstituteFile( } /* debug: dump the list */ -#ifdef _DEBUG +#ifndef NDEBUG { int n = 0; list_item_t *p = NULL; @@ -628,11 +652,11 @@ SubstituteFile( } } #endif - + /* * Run the substitutions over each line of the input */ - + while (fgets(szBuffer, cbBuffer, fp) != NULL) { list_item_t *p = NULL; for (p = substPtr; p != NULL; p = p->nextPtr) { @@ -652,13 +676,24 @@ SubstituteFile( } printf(szBuffer); } - + list_free(&substPtr); } fclose(fp); return 0; } +BOOL FileExists(LPCTSTR szPath) +{ +#ifndef INVALID_FILE_ATTRIBUTES + #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif + DWORD pathAttr = GetFileAttributes(szPath); + return (pathAttr != INVALID_FILE_ATTRIBUTES && + !(pathAttr & FILE_ATTRIBUTE_DIRECTORY)); +} + + /* * QualifyPath -- * @@ -672,17 +707,103 @@ QualifyPath( const char *szPath) { char szCwd[MAX_PATH + 1]; - char szTmp[MAX_PATH + 1]; - char *p; - GetCurrentDirectory(MAX_PATH, szCwd); - while ((p = strchr(szPath, '/')) && *p) - *p = '\\'; - PathCombine(szTmp, szCwd, szPath); - PathCanonicalize(szCwd, szTmp); + + GetFullPathName(szPath, sizeof(szCwd)-1, szCwd, NULL); printf("%s\n", szCwd); return 0; } +/* + * Implements LocateDependency for a single directory. See that command + * for an explanation. + * Returns 0 if found after printing the directory. + * Returns 1 if not found but no errors. + * Returns 2 on any kind of error + * Basically, these are used as exit codes for the process. + */ +static int LocateDependencyHelper(const char *dir, const char *keypath) +{ + HANDLE hSearch; + char path[MAX_PATH+1]; + int dirlen, keylen, ret; + WIN32_FIND_DATA finfo; + + if (dir == NULL || keypath == NULL) + return 2; /* Have no real error reporting mechanism into nmake */ + dirlen = strlen(dir); + if ((dirlen + 3) > sizeof(path)) + return 2; + strncpy(path, dir, dirlen); + strncpy(path+dirlen, "\\*", 3); /* Including terminating \0 */ + keylen = strlen(keypath); + +#if 0 /* This function is not available in Visual C++ 6 */ + /* + * Use numerics 0 -> FindExInfoStandard, + * 1 -> FindExSearchLimitToDirectories, + * as these are not defined in Visual C++ 6 + */ + hSearch = FindFirstFileEx(path, 0, &finfo, 1, NULL, 0); +#else + hSearch = FindFirstFile(path, &finfo); +#endif + if (hSearch == INVALID_HANDLE_VALUE) + return 1; /* Not found */ + + /* Loop through all subdirs checking if the keypath is under there */ + ret = 1; /* Assume not found */ + do { + int sublen; + /* + * We need to check it is a directory despite the + * FindExSearchLimitToDirectories in the above call. See SDK docs + */ + if ((finfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + continue; + sublen = strlen(finfo.cFileName); + if ((dirlen+1+sublen+1+keylen+1) > sizeof(path)) + continue; /* Path does not fit, assume not matched */ + strncpy(path+dirlen+1, finfo.cFileName, sublen); + path[dirlen+1+sublen] = '\\'; + strncpy(path+dirlen+1+sublen+1, keypath, keylen+1); + if (FileExists(path)) { + /* Found a match, print to stdout */ + path[dirlen+1+sublen] = '\0'; + QualifyPath(path); + ret = 0; + break; + } + } while (FindNextFile(hSearch, &finfo)); + FindClose(hSearch); + return ret; +} + +/* + * LocateDependency -- + * + * Locates a dependency for a package. + * keypath - a relative path within the package directory + * that is used to confirm it is the correct directory. + * The search path for the package directory is currently only + * the parent and grandparent of the current working directory. + * If found, the command prints + * name_DIRPATH= + * and returns 0. If not found, does not print anything and returns 1. + */ +static int LocateDependency(const char *keypath) +{ + int i, ret; + static const char *paths[] = {"..", "..\\..", "..\\..\\.."}; + + for (i = 0; i < (sizeof(paths)/sizeof(paths[0])); ++i) { + ret = LocateDependencyHelper(paths[i], keypath); + if (ret == 0) + return ret; + } + return ret; +} + + /* * Local variables: * mode: c diff --git a/configure b/configure index ff6029b73..5df138630 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.69 for sqlite 3.34.1. +# Generated by GNU Autoconf 2.69 for sqlite 3.36.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.34.1' -PACKAGE_STRING='sqlite 3.34.1' +PACKAGE_VERSION='3.36.0' +PACKAGE_STRING='sqlite 3.36.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -903,9 +903,10 @@ with_readline_inc enable_debug enable_amalgamation enable_load_extension +enable_math +enable_all enable_memsys5 enable_memsys3 -enable_all enable_fts3 enable_fts4 enable_fts5 @@ -1466,7 +1467,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.34.1 to adapt to many kinds of systems. +\`configure' configures sqlite 3.36.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1531,7 +1532,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.34.1:";; + short | recursive ) echo "Configuration of sqlite 3.36.0:";; esac cat <<\_ACEOF @@ -1557,9 +1558,10 @@ Optional Features: separately --disable-load-extension Disable loading of external extensions + --disable-math Disable math functions + --enable-all Enable FTS4, FTS5, Geopoly, JSON, RTree, Sessions --enable-memsys5 Enable MEMSYS5 --enable-memsys3 Enable MEMSYS3 - --enable-all Enable FTS4, FTS5, Geopoly, JSON, RTree, Sessions --enable-fts3 Enable the FTS3 extension --enable-fts4 Enable the FTS4 extension --enable-fts5 Enable the FTS5 extension @@ -1658,7 +1660,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.34.1 +sqlite configure 3.36.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2077,7 +2079,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.34.1, which was +It was created by sqlite $as_me 3.36.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3935,13 +3937,13 @@ if ${lt_cv_nm_interface+:} false; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3938: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3940: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3941: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3943: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3944: output\"" >&5) + (eval echo "\"\$as_me:3946: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -5147,7 +5149,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5150 "configure"' > conftest.$ac_ext + echo '#line 5152 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6672,11 +6674,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:6675: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6677: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6679: \$? = $ac_status" >&5 + echo "$as_me:6681: \$? = $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. @@ -7011,11 +7013,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:7014: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7016: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7018: \$? = $ac_status" >&5 + echo "$as_me:7020: \$? = $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. @@ -7116,11 +7118,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:7119: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7121: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7123: \$? = $ac_status" >&5 + echo "$as_me:7125: \$? = $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 @@ -7171,11 +7173,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:7174: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7176: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7178: \$? = $ac_status" >&5 + echo "$as_me:7180: \$? = $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 @@ -9551,7 +9553,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9554 "configure" +#line 9556 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -9647,7 +9649,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9650 "configure" +#line 9652 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -10364,8 +10366,10 @@ fi if test "x${TCLLIBDIR+set}" != "xset" ; then TCLLIBDIR='$(libdir)' for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD}` ; do - TCLLIBDIR=$i - break + if test -d $i ; then + TCLLIBDIR=$i + break + fi done TCLLIBDIR="${TCLLIBDIR}/sqlite3" fi @@ -11247,10 +11251,16 @@ if test "${enable_debug+set}" = set; then : enableval=$enable_debug; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build type" >&5 +$as_echo_n "checking build type... " >&6; } if test "${enable_debug}" = "yes" ; then TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: debug" >&5 +$as_echo "debug" >&6; } else TARGET_DEBUG="-DNDEBUG" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: release" >&5 +$as_echo "release" >&6; } fi @@ -11411,6 +11421,91 @@ else OPT_FEATURE_FLAGS="-DSQLITE_OMIT_LOAD_EXTENSION=1" fi +########## +# Do we want to support math functions +# +# Check whether --enable-math was given. +if test "${enable_math+set}" = set; then : + enableval=$enable_math; +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support math functions" >&5 +$as_echo_n "checking whether to support math functions... " >&6; } +if test "$enable_math" = "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_MATH_FUNCTIONS" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing ceil" >&5 +$as_echo_n "checking for library containing ceil... " >&6; } +if ${ac_cv_search_ceil+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ceil (); +int +main () +{ +return ceil (); + ; + return 0; +} +_ACEOF +for ac_lib in '' m; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_ceil=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_ceil+:} false; then : + break +fi +done +if ${ac_cv_search_ceil+:} false; then : + +else + ac_cv_search_ceil=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_ceil" >&5 +$as_echo "$ac_cv_search_ceil" >&6; } +ac_res=$ac_cv_search_ceil +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + +fi + + +######## +# The --enable-all argument is short-hand to enable +# multiple extensions. +# Check whether --enable-all was given. +if test "${enable_all+set}" = set; then : + enableval=$enable_all; +fi + + ########## # Do we want to support memsys3 and/or memsys5 # @@ -11445,15 +11540,6 @@ else $as_echo "no" >&6; } fi -######## -# The --enable-extensions argument is short-hand to enable -# multiple extensions. -# Check whether --enable-all was given. -if test "${enable_all+set}" = set; then : - enableval=$enable_all; -fi - - ######### # See whether we should enable Full Text Search extensions # Check whether --enable-fts3 was given. @@ -11461,15 +11547,26 @@ if test "${enable_fts3+set}" = set; then : enableval=$enable_fts3; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support FTS3" >&5 +$as_echo_n "checking whether to support FTS3... " >&6; } if test "${enable_fts3}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_FTS3" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi # Check whether --enable-fts4 was given. if test "${enable_fts4+set}" = set; then : enableval=$enable_fts4; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support FTS4" >&5 +$as_echo_n "checking whether to support FTS4... " >&6; } if test "${enable_fts4}" = "yes" -o "${enable_all}" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_FTS4" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing log" >&5 $as_echo_n "checking for library containing log... " >&6; } @@ -11527,13 +11624,20 @@ if test "$ac_res" != no; then : fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi # Check whether --enable-fts5 was given. if test "${enable_fts5+set}" = set; then : enableval=$enable_fts5; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support FTS5" >&5 +$as_echo_n "checking whether to support FTS5... " >&6; } if test "${enable_fts5}" = "yes" -o "${enable_all}" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_FTS5" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing log" >&5 $as_echo_n "checking for library containing log... " >&6; } @@ -11591,6 +11695,9 @@ if test "$ac_res" != no; then : fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi ######### @@ -11600,8 +11707,15 @@ if test "${enable_json1+set}" = set; then : enableval=$enable_json1; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support JSON" >&5 +$as_echo_n "checking whether to support JSON... " >&6; } if test "${enable_json1}" = "yes" -o "${enable_all}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_JSON1" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi ######### @@ -11612,8 +11726,15 @@ if test "${enable_update_limit+set}" = set; then : enableval=$enable_update_limit; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support LIMIT on UPDATE and DELETE statements" >&5 +$as_echo_n "checking whether to support LIMIT on UPDATE and DELETE statements... " >&6; } if test "${enable_update_limit}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi ######### @@ -11625,9 +11746,16 @@ else enable_geopoly=no fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support GEOPOLY" >&5 +$as_echo_n "checking whether to support GEOPOLY... " >&6; } if test "${enable_geopoly}" = "yes" -o "${enable_all}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_GEOPOLY" enable_rtree=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi ######### @@ -11637,8 +11765,15 @@ if test "${enable_rtree+set}" = set; then : enableval=$enable_rtree; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support RTREE" >&5 +$as_echo_n "checking whether to support RTREE... " >&6; } if test "${enable_rtree}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_RTREE" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi ######### @@ -11648,9 +11783,16 @@ if test "${enable_session+set}" = set; then : enableval=$enable_session; fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support SESSION" >&5 +$as_echo_n "checking whether to support SESSION... " >&6; } if test "${enable_session}" = "yes" -o "${enable_all}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_SESSION" OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_PREUPDATE_HOOK" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi ######### @@ -12236,7 +12378,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.34.1, which was +This file was extended by sqlite $as_me 3.36.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12302,7 +12444,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.34.1 +sqlite config.status 3.36.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index bb8f4bd56..70664dd41 100644 --- a/configure.ac +++ b/configure.ac @@ -134,8 +134,10 @@ AC_ARG_VAR([TCLLIBDIR], [Where to install tcl plugin]) if test "x${TCLLIBDIR+set}" != "xset" ; then TCLLIBDIR='$(libdir)' for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD}` ; do - TCLLIBDIR=$i - break + if test -d $i ; then + TCLLIBDIR=$i + break + fi done TCLLIBDIR="${TCLLIBDIR}/sqlite3" fi @@ -553,10 +555,13 @@ AC_SEARCH_LIBS(fdatasync, [rt]) ######### # check for debug enabled AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug],[enable debugging & verbose explain])) +AC_MSG_CHECKING([build type]) if test "${enable_debug}" = "yes" ; then TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0" + AC_MSG_RESULT([debug]) else TARGET_DEBUG="-DNDEBUG" + AC_MSG_RESULT([release]) fi AC_SUBST(TARGET_DEBUG) @@ -586,6 +591,27 @@ else OPT_FEATURE_FLAGS="-DSQLITE_OMIT_LOAD_EXTENSION=1" fi +########## +# Do we want to support math functions +# +AC_ARG_ENABLE(math, +AC_HELP_STRING([--disable-math],[Disable math functions])) +AC_MSG_CHECKING([whether to support math functions]) +if test "$enable_math" = "no"; then + AC_MSG_RESULT([no]) +else + AC_MSG_RESULT([yes]) + OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_MATH_FUNCTIONS" + AC_SEARCH_LIBS(ceil, m) +fi + + +######## +# The --enable-all argument is short-hand to enable +# multiple extensions. +AC_ARG_ENABLE(all, AC_HELP_STRING([--enable-all], + [Enable FTS4, FTS5, Geopoly, JSON, RTree, Sessions])) + ########## # Do we want to support memsys3 and/or memsys5 # @@ -608,37 +634,47 @@ else AC_MSG_RESULT([no]) fi -######## -# The --enable-extensions argument is short-hand to enable -# multiple extensions. -AC_ARG_ENABLE(all, AC_HELP_STRING([--enable-all], - [Enable FTS4, FTS5, Geopoly, JSON, RTree, Sessions])) - ######### # See whether we should enable Full Text Search extensions AC_ARG_ENABLE(fts3, AC_HELP_STRING([--enable-fts3], [Enable the FTS3 extension])) +AC_MSG_CHECKING([whether to support FTS3]) if test "${enable_fts3}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_FTS3" + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) fi AC_ARG_ENABLE(fts4, AC_HELP_STRING([--enable-fts4], [Enable the FTS4 extension])) +AC_MSG_CHECKING([whether to support FTS4]) if test "${enable_fts4}" = "yes" -o "${enable_all}" = "yes" ; then + AC_MSG_RESULT([yes]) OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_FTS4" AC_SEARCH_LIBS([log],[m]) +else + AC_MSG_RESULT([no]) fi AC_ARG_ENABLE(fts5, AC_HELP_STRING([--enable-fts5], [Enable the FTS5 extension])) +AC_MSG_CHECKING([whether to support FTS5]) if test "${enable_fts5}" = "yes" -o "${enable_all}" = "yes" ; then + AC_MSG_RESULT([yes]) OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_FTS5" AC_SEARCH_LIBS([log],[m]) +else + AC_MSG_RESULT([no]) fi ######### # See whether we should enable JSON1 AC_ARG_ENABLE(json1, AC_HELP_STRING([--enable-json1],[Enable the JSON1 extension])) +AC_MSG_CHECKING([whether to support JSON]) if test "${enable_json1}" = "yes" -o "${enable_all}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_JSON1" + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) fi ######### @@ -646,8 +682,12 @@ fi # statements. AC_ARG_ENABLE(update-limit, AC_HELP_STRING([--enable-update-limit], [Enable the UPDATE/DELETE LIMIT clause])) +AC_MSG_CHECKING([whether to support LIMIT on UPDATE and DELETE statements]) if test "${enable_update_limit}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT" + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) fi ######### @@ -655,26 +695,38 @@ fi AC_ARG_ENABLE(geopoly, AC_HELP_STRING([--enable-geopoly], [Enable the GEOPOLY extension]), [enable_geopoly=yes],[enable_geopoly=no]) +AC_MSG_CHECKING([whether to support GEOPOLY]) if test "${enable_geopoly}" = "yes" -o "${enable_all}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_GEOPOLY" enable_rtree=yes + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) fi ######### # See whether we should enable RTREE AC_ARG_ENABLE(rtree, AC_HELP_STRING([--enable-rtree], [Enable the RTREE extension])) +AC_MSG_CHECKING([whether to support RTREE]) if test "${enable_rtree}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_RTREE" + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) fi ######### # See whether we should enable the SESSION extension AC_ARG_ENABLE(session, AC_HELP_STRING([--enable-session], [Enable the SESSION extension])) +AC_MSG_CHECKING([whether to support SESSION]) if test "${enable_session}" = "yes" -o "${enable_all}" = "yes" ; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_SESSION" OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_PREUPDATE_HOOK" + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) fi ######### diff --git a/doc/lemon.html b/doc/lemon.html index bd078c8ec..324b3f331 100644 --- a/doc/lemon.html +++ b/doc/lemon.html @@ -697,6 +697,7 @@

4.4 Special Directives

  • %stack_size
  • %start_symbol
  • %syntax_error +
  • %token
  • %token_class
  • %token_destructor
  • %token_prefix @@ -1077,16 +1078,39 @@

    4.4.18 The %start_symbol directive

    4.4.19 The %syntax_error directive

    -

    See Error Processing.

    +

    See Error Processing.

    + + +

    4.4.20 The %token directive

    + +

    Tokens are normally created automatically, the first time they are used. +Any identifier that begins with an upper-case letter is a token. + +

    Sometimes it is useful to declare tokens in advance, however. The +integer values assigned to each token determined by the order in which +the tokens are seen. So by declaring tokens in advance, it is possible to +cause some tokens to have low-numbered values, which might be desirable in +some grammers, or to have sequential values assigned to a sequence of +related tokens. For this reason, the %token directive is provided to +declare tokens in advance. The syntax is as follows: + +

    +%token TOKEN TOKEN... . +

    + +

    The %token directive is followed by zero or more token symbols and +terminated by a single ".". Each token named is created if it does not +already exist. Tokens are created in order. + -

    4.4.20 The %token_class directive

    +

    4.4.21 The %token_class directive

    Undocumented. Appears to be related to the MULTITERMINAL concept. Implementation.

    -

    4.4.21 The %token_destructor directive

    +

    4.4.22 The %token_destructor directive

    The %destructor directive assigns a destructor to a non-terminal symbol. (See the description of the @@ -1102,7 +1126,7 @@

    4.4.21 The %token_destructor directive

    destructors.

    -

    4.4.22 The %token_prefix directive

    +

    4.4.23 The %token_prefix directive

    Lemon generates #defines that assign small integer constants to each terminal symbol in the grammar. If desired, Lemon will @@ -1129,7 +1153,7 @@

    4.4.22 The %token_prefix directive

    -

    4.4.23 The %token_type and %type directives

    +

    4.4.24 The %token_type and %type directives

    These directives are used to specify the data types for values on the parser's stack associated with terminal and non-terminal @@ -1166,7 +1190,7 @@

    4.4.23 The %token_type and %type directives

    and able to pay that price, fine. You just need to know.

    -

    4.4.24 The %wildcard directive

    +

    4.4.25 The %wildcard directive

    The %wildcard directive is followed by a single token name and a period. This directive specifies that the identified token should @@ -1176,7 +1200,7 @@

    4.4.24 The %wildcard directive

    the wildcard token and some other token, the other token is always used. The wildcard token is only matched if there are no alternatives.

    - +

    5.0 Error Processing

    After extensive experimentation over several years, it has been diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index f49f1f5e5..3987d0c91 100644 --- a/ext/expert/expert1.test +++ b/ext/expert/expert1.test @@ -28,6 +28,7 @@ if {[info commands sqlite3_expert_new]==""} { return } + set CLI [test_binary_name sqlite3] set CMD [test_binary_name sqlite3_expert] @@ -102,7 +103,7 @@ do_setup_rec_test $tn.1 { CREATE TABLE t1(a, b, c) } { SELECT * FROM t1 } { (no new indexes) - SCAN TABLE t1 + SCAN t1 } do_setup_rec_test $tn.2 { @@ -111,7 +112,7 @@ do_setup_rec_test $tn.2 { SELECT * FROM t1 WHERE b>?; } { CREATE INDEX t1_idx_00000062 ON t1(b); - SEARCH TABLE t1 USING INDEX t1_idx_00000062 (b>?) + SEARCH t1 USING INDEX t1_idx_00000062 (b>?) } do_setup_rec_test $tn.3 { @@ -120,7 +121,7 @@ do_setup_rec_test $tn.3 { SELECT * FROM t1 WHERE b COLLATE nocase BETWEEN ? AND ? } { CREATE INDEX t1_idx_3e094c27 ON t1(b COLLATE NOCASE); - SEARCH TABLE t1 USING INDEX t1_idx_3e094c27 (b>? AND b? AND b? AND b? AND b? } { (no new indexes) - SEARCH TABLE example USING INDEX sqlite_autoindex_example_1 (A=? AND B>?) + SEARCH example USING INDEX sqlite_autoindex_example_1 (A=? AND B>?) } do_setup_rec_test $tn.17.5 { CREATE TABLE example (A INTEGER, B INTEGER, C INTEGER, PRIMARY KEY (A,B)); @@ -364,7 +365,30 @@ do_setup_rec_test $tn.17.5 { SELECT * FROM example WHERE a>? AND b=? } { CREATE INDEX example_idx_0000cb3f ON example(B, A); - SEARCH TABLE example USING INDEX example_idx_0000cb3f (B=? AND A>?) + SEARCH example USING INDEX example_idx_0000cb3f (B=? AND A>?) +} + +do_setup_rec_test $tn.18.0 { + CREATE TABLE SomeObject ( + a INTEGER PRIMARY KEY, + x TEXT GENERATED ALWAYS AS(HEX(a)) VIRTUAL + ); +} { + SELECT x FROM SomeObject; +} { + (no new indexes) + SCAN SomeObject +} +do_setup_rec_test $tn.18.1 { + CREATE TABLE SomeObject ( + a INTEGER PRIMARY KEY, + x TEXT GENERATED ALWAYS AS(HEX(a)) VIRTUAL + ); +} { + SELECT * FROM SomeObject WHERE x=?; +} { + CREATE INDEX SomeObject_idx_00000078 ON SomeObject(x); + SEARCH SomeObject USING COVERING INDEX SomeObject_idx_00000078 (x=?) } } @@ -430,5 +454,4 @@ do_execsql_test 5.3 { t2 t2_idx_0001295b {100 20 5} } - finish_test diff --git a/ext/expert/sqlite3expert.c b/ext/expert/sqlite3expert.c index 5c1342320..863c6a340 100644 --- a/ext/expert/sqlite3expert.c +++ b/ext/expert/sqlite3expert.c @@ -687,7 +687,7 @@ static int idxGetTableInfo( char *pCsr = 0; int nPk = 0; - rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_info=%Q", zTab); + rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_xinfo=%Q", zTab); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){ const char *zCol = (const char*)sqlite3_column_text(p1, 1); nByte += 1 + STRLEN(zCol); diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 79dc5c88c..9c5703d3f 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -326,7 +326,9 @@ int sqlite3Fts3Never(int b) { assert( !b ); return b; } ** assert() conditions in the fts3 code are activated - conditions that are ** only true if it is guaranteed that the fts3 database is not corrupt. */ +#ifdef SQLITE_DEBUG int sqlite3_fts3_may_be_corrupt = 1; +#endif /* ** Write a 64-bit variable-length integer to memory starting at p[0]. @@ -1897,7 +1899,7 @@ static int fts3ScanInteriorNode( char *zBuffer = 0; /* Buffer to load terms into */ i64 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 */ + u64 iChild; /* Block id of child node to descend to */ int nBuffer = 0; /* Total term size */ /* Skip over the 'height' varint that occurs at the start of every @@ -1913,8 +1915,8 @@ static int fts3ScanInteriorNode( ** 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); + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); if( zCsr>zEnd ){ return FTS_CORRUPT_VTAB; } @@ -1967,20 +1969,20 @@ static int fts3ScanInteriorNode( */ cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer)); if( piFirst && (cmp<0 || (cmp==0 && nBuffer>nTerm)) ){ - *piFirst = iChild; + *piFirst = (i64)iChild; piFirst = 0; } if( piLast && cmp<0 ){ - *piLast = iChild; + *piLast = (i64)iChild; piLast = 0; } iChild++; }; - if( piFirst ) *piFirst = iChild; - if( piLast ) *piLast = iChild; + if( piFirst ) *piFirst = (i64)iChild; + if( piLast ) *piLast = (i64)iChild; finish_scan: sqlite3_free(zBuffer); @@ -3586,14 +3588,20 @@ static int fts3SetHasStat(Fts3Table *p){ */ static int fts3BeginMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table*)pVtab; + int rc; UNUSED_PARAMETER(pVtab); assert( p->pSegments==0 ); assert( p->nPendingData==0 ); assert( p->inTransaction!=1 ); - TESTONLY( p->inTransaction = 1 ); - TESTONLY( p->mxSavepoint = -1; ); p->nLeafAdd = 0; - return fts3SetHasStat(p); + rc = fts3SetHasStat(p); +#ifdef SQLITE_DEBUG + if( rc==SQLITE_OK ){ + p->inTransaction = 1; + p->mxSavepoint = -1; + } +#endif + return rc; } /* @@ -5122,16 +5130,15 @@ static int fts3EvalStart(Fts3Cursor *pCsr){ #ifndef SQLITE_DISABLE_FTS4_DEFERRED if( rc==SQLITE_OK && nToken>1 && pTab->bFts4 ){ Fts3TokenAndCost *aTC; - Fts3Expr **apOr; aTC = (Fts3TokenAndCost *)sqlite3_malloc64( sizeof(Fts3TokenAndCost) * nToken + sizeof(Fts3Expr *) * nOr * 2 ); - apOr = (Fts3Expr **)&aTC[nToken]; if( !aTC ){ rc = SQLITE_NOMEM; }else{ + Fts3Expr **apOr = (Fts3Expr **)&aTC[nToken]; int ii; Fts3TokenAndCost *pTC = aTC; Fts3Expr **ppOr = apOr; @@ -5212,9 +5219,9 @@ static int fts3EvalNearTrim( ); if( res ){ nNew = (int)(pOut - pPhrase->doclist.pList) - 1; - if( nNew>=0 ){ + assert_fts3_nc( nNew<=pPhrase->doclist.nList && nNew>0 ); + if( nNew>=0 && nNew<=pPhrase->doclist.nList ){ assert( pPhrase->doclist.pList[nNew]=='\0' ); - assert( nNew<=pPhrase->doclist.nList && nNew>0 ); memset(&pPhrase->doclist.pList[nNew], 0, pPhrase->doclist.nList - nNew); pPhrase->doclist.nList = nNew; } diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index b3e0ec85f..8c2579166 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -134,7 +134,7 @@ SQLITE_EXTENSION_INIT3 ** is used for assert() conditions that are true only if it can be ** guranteed that the database is not corrupt. */ -#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +#ifdef SQLITE_DEBUG extern int sqlite3_fts3_may_be_corrupt; # define assert_fts3_nc(x) assert(sqlite3_fts3_may_be_corrupt || (x)) #else diff --git a/ext/fts3/fts3_aux.c b/ext/fts3/fts3_aux.c index 08edc00f4..b6e651853 100644 --- a/ext/fts3/fts3_aux.c +++ b/ext/fts3/fts3_aux.c @@ -406,6 +406,7 @@ static int fts3auxFilterMethod( sqlite3Fts3SegReaderFinish(&pCsr->csr); sqlite3_free((void *)pCsr->filter.zTerm); sqlite3_free(pCsr->aStat); + sqlite3_free(pCsr->zStop); memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr); pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; diff --git a/ext/fts3/fts3_expr.c b/ext/fts3/fts3_expr.c index e19137a03..7a69a935f 100644 --- a/ext/fts3/fts3_expr.c +++ b/ext/fts3/fts3_expr.c @@ -493,6 +493,11 @@ static int getNextNode( if( *zInput=='(' ){ int nConsumed = 0; pParse->nNest++; +#if !defined(SQLITE_MAX_EXPR_DEPTH) + if( pParse->nNest>1000 ) return SQLITE_ERROR; +#elif SQLITE_MAX_EXPR_DEPTH>0 + if( pParse->nNest>SQLITE_MAX_EXPR_DEPTH ) return SQLITE_ERROR; +#endif rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed); *pnConsumed = (int)(zInput - z) + 1 + nConsumed; return rc; diff --git a/ext/fts3/fts3_snippet.c b/ext/fts3/fts3_snippet.c index ebc771fd6..613d3e5f1 100644 --- a/ext/fts3/fts3_snippet.c +++ b/ext/fts3/fts3_snippet.c @@ -17,6 +17,10 @@ #include #include +#ifndef SQLITE_AMALGAMATION +typedef sqlite3_int64 i64; +#endif + /* ** Characters that may appear in the second argument to matchinfo(). */ @@ -67,9 +71,9 @@ struct SnippetIter { struct SnippetPhrase { int nToken; /* Number of tokens in phrase */ char *pList; /* Pointer to start of phrase position list */ - int iHead; /* Next value in position list */ + i64 iHead; /* Next value in position list */ char *pHead; /* Position list data following iHead */ - int iTail; /* Next value in trailing position list */ + i64 iTail; /* Next value in trailing position list */ char *pTail; /* Position list data following iTail */ }; @@ -234,7 +238,7 @@ void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){ ** After it returns, *piPos contains the value of the next element of the ** list and *pp is advanced to the following varint. */ -static void fts3GetDeltaPosition(char **pp, int *piPos){ +static void fts3GetDeltaPosition(char **pp, i64 *piPos){ int iVal; *pp += fts3GetVarint32(*pp, &iVal); *piPos += (iVal-2); @@ -343,10 +347,10 @@ static int fts3ExprPhraseCount(Fts3Expr *pExpr){ ** arguments so that it points to the first element with a value greater ** than or equal to parameter iNext. */ -static void fts3SnippetAdvance(char **ppIter, int *piIter, int iNext){ +static void fts3SnippetAdvance(char **ppIter, i64 *piIter, int iNext){ char *pIter = *ppIter; if( pIter ){ - int iIter = *piIter; + i64 iIter = *piIter; while( iIteraPhrase[i]; if( pPhrase->pTail ){ char *pCsr = pPhrase->pTail; - int iCsr = pPhrase->iTail; + i64 iCsr = pPhrase->iTail; while( iCsr<(iStart+pIter->nSnippet) && iCsr>=iStart ){ int j; @@ -475,7 +479,7 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr); assert( rc==SQLITE_OK || pCsr==0 ); if( pCsr ){ - int iFirst = 0; + i64 iFirst = 0; pPhrase->pList = pCsr; fts3GetDeltaPosition(&pCsr, &iFirst); if( iFirst<0 ){ @@ -1539,8 +1543,8 @@ typedef struct TermOffsetCtx TermOffsetCtx; struct TermOffset { char *pList; /* Position-list */ - int iPos; /* Position just read from pList */ - int iOff; /* Offset of this term from read positions */ + i64 iPos; /* Position just read from pList */ + i64 iOff; /* Offset of this term from read positions */ }; struct TermOffsetCtx { @@ -1559,7 +1563,7 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){ int nTerm; /* Number of tokens in phrase */ int iTerm; /* For looping through nTerm phrase terms */ char *pList; /* Pointer to position list for phrase */ - int iPos = 0; /* First position in position-list */ + i64 iPos = 0; /* First position in position-list */ int rc; UNUSED_PARAMETER(iPhrase); diff --git a/ext/fts3/fts3_test.c b/ext/fts3/fts3_test.c index fa0f00a6c..49a8476bf 100644 --- a/ext/fts3/fts3_test.c +++ b/ext/fts3/fts3_test.c @@ -585,6 +585,7 @@ static int SQLITE_TCLAPI fts3_may_be_corrupt( int objc, Tcl_Obj *CONST objv[] ){ +#ifdef SQLITE_DEBUG int bOld = sqlite3_fts3_may_be_corrupt; if( objc!=2 && objc!=1 ){ @@ -598,6 +599,7 @@ static int SQLITE_TCLAPI fts3_may_be_corrupt( } Tcl_SetObjResult(interp, Tcl_NewIntObj(bOld)); +#endif return TCL_OK; } diff --git a/ext/fts3/fts3_unicode.c b/ext/fts3/fts3_unicode.c index b06d99214..2fd4b39fb 100644 --- a/ext/fts3/fts3_unicode.c +++ b/ext/fts3/fts3_unicode.c @@ -285,6 +285,7 @@ static int unicodeOpen( pCsr->aInput = (const unsigned char *)aInput; if( aInput==0 ){ pCsr->nInput = 0; + pCsr->aInput = (const unsigned char*)""; }else if( nInput<0 ){ pCsr->nInput = (int)strlen(aInput); }else{ diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 092cad9ac..56c59ce3a 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -1807,7 +1807,7 @@ static int fts3SegReaderCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ if( rc==0 ){ rc = pRhs->iIdx - pLhs->iIdx; } - assert( rc!=0 ); + assert_fts3_nc( rc!=0 ); return rc; } @@ -2003,8 +2003,8 @@ static int fts3PrefixCompress( int nNext /* Size of buffer zNext in bytes */ ){ int n; - UNUSED_PARAMETER(nNext); - for(n=0; nkey, reader.term.n, &rc); if( rc==SQLITE_OK ){ - memcpy(pNode->key.a, reader.term.a, reader.term.n); + assert_fts3_nc( reader.term.n>0 || reader.aNode==0 ); + if( reader.term.n>0 ){ + memcpy(pNode->key.a, reader.term.a, reader.term.n); + } pNode->key.n = reader.term.n; if( i>0 ){ char *aBlock = 0; int nBlock = 0; pNode = &pWriter->aNodeWriter[i-1]; pNode->iBlock = reader.iChild; - rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock, 0); + rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0); blobGrowBuffer(&pNode->block, MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc - ); + ); if( rc==SQLITE_OK ){ memcpy(pNode->block.a, aBlock, nBlock); pNode->block.n = nBlock; diff --git a/ext/fts5/fts5_buffer.c b/ext/fts5/fts5_buffer.c index cb768ebb1..a8f3bee33 100644 --- a/ext/fts5/fts5_buffer.c +++ b/ext/fts5/fts5_buffer.c @@ -178,6 +178,7 @@ int sqlite3Fts5PoslistNext64( i64 iOff = *piOff; int iVal; fts5FastGetVarint32(a, i, iVal); + assert( iVal>=0 ); if( iVal<=1 ){ if( iVal==0 ){ *pi = i; @@ -191,9 +192,12 @@ int sqlite3Fts5PoslistNext64( *piOff = -1; return 1; } + *piOff = iOff + ((iVal-2) & 0x7FFFFFFF); + }else{ + *piOff = (iOff & (i64)0x7FFFFFFF<<32)+((iOff + (iVal-2)) & 0x7FFFFFFF); } - *piOff = iOff + ((iVal-2) & 0x7FFFFFFF); *pi = i; + assert( *piOff>=iOff ); return 0; } } @@ -232,14 +236,16 @@ void sqlite3Fts5PoslistSafeAppend( i64 *piPrev, i64 iPos ){ - static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; - if( (iPos & colmask) != (*piPrev & colmask) ){ - pBuf->p[pBuf->n++] = 1; - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); - *piPrev = (iPos & colmask); + if( iPos>=*piPrev ){ + static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; + if( (iPos & colmask) != (*piPrev & colmask) ){ + pBuf->p[pBuf->n++] = 1; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); + *piPrev = (iPos & colmask); + } + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); + *piPrev = iPos; } - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); - *piPrev = iPos; } int sqlite3Fts5PoslistWriterAppend( diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c index 6c02d2b35..32335c937 100644 --- a/ext/fts5/fts5_config.c +++ b/ext/fts5/fts5_config.c @@ -537,7 +537,7 @@ int sqlite3Fts5ConfigParse( nByte = nArg * (sizeof(char*) + sizeof(u8)); pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte); - pRet->abUnindexed = (u8*)&pRet->azCol[nArg]; + pRet->abUnindexed = pRet->azCol ? (u8*)&pRet->azCol[nArg] : 0; pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1); pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1); pRet->bColumnsize = 1; diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 392dde3ab..f3cec00be 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -1500,8 +1500,8 @@ int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ } /* If the iterator is not at a real match, skip forward until it is. */ - while( pRoot->bNomatch ){ - assert( pRoot->bEof==0 && rc==SQLITE_OK ); + while( pRoot->bNomatch && rc==SQLITE_OK ){ + assert( pRoot->bEof==0 ); rc = fts5ExprNodeNext(p, pRoot, 0, 0); } return rc; @@ -2412,6 +2412,7 @@ Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( return pRet; } +#ifdef SQLITE_TEST static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ sqlite3_int64 nByte = 0; Fts5ExprTerm *p; @@ -2778,12 +2779,14 @@ static void fts5ExprFold( sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); } } +#endif /* ifdef SQLITE_TEST */ /* ** This is called during initialization to register the fts5_expr() scalar ** UDF with the SQLite handle passed as the only argument. */ int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ +#ifdef SQLITE_TEST struct Fts5ExprFunc { const char *z; void (*x)(sqlite3_context*,int,sqlite3_value**); @@ -2801,6 +2804,10 @@ int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ struct Fts5ExprFunc *p = &aFunc[i]; rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0); } +#else + int rc = SQLITE_OK; + UNUSED_PARAM2(pGlobal,db); +#endif /* Avoid warnings indicating that sqlite3Fts5ParserTrace() and ** sqlite3Fts5ParserFallback() are unused */ diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index f83488e2f..7cc0d46ac 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -431,7 +431,7 @@ struct Fts5SegIter { int iLeafPgno; /* Current leaf page number */ Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ - int iLeafOffset; /* Byte offset within current leaf */ + i64 iLeafOffset; /* Byte offset within current leaf */ /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); @@ -1611,7 +1611,7 @@ static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ - int iOff = pIter->iLeafOffset; + i64 iOff = pIter->iLeafOffset; ASSERT_SZLEAF_OK(pIter->pLeaf); if( iOff>=pIter->pLeaf->szLeaf ){ @@ -1644,7 +1644,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ */ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ - int iOff = pIter->iLeafOffset; /* Offset to read at */ + i64 iOff = pIter->iLeafOffset; /* Offset to read at */ int nNew; /* Bytes of new data */ iOff += fts5GetVarint32(&a[iOff], nNew); @@ -2070,14 +2070,9 @@ static void fts5SegIterNext( }else{ /* The following could be done by calling fts5SegIterLoadNPos(). But ** this block is particularly performance critical, so equivalent - ** code is inlined. - ** - ** Later: Switched back to fts5SegIterLoadNPos() because it supports - ** detail=none mode. Not ideal. - */ + ** code is inlined. */ int nSz; - assert( p->rc==SQLITE_OK ); - assert( pIter->iLeafOffset<=pIter->pLeaf->nn ); + assert_nc( pIter->iLeafOffset<=pIter->pLeaf->nn ); fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); pIter->bDel = (nSz & 0x0001); pIter->nPos = nSz>>1; @@ -3069,7 +3064,7 @@ static void fts5ChunkIterate( int pgno = pSeg->iLeafPgno; int pgnoSave = 0; - /* This function does notmwork with detail=none databases. */ + /* This function does not work with detail=none databases. */ assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){ @@ -3082,6 +3077,9 @@ static void fts5ChunkIterate( fts5DataRelease(pData); if( nRem<=0 ){ break; + }else if( pSeg->pSeg==0 ){ + p->rc = FTS5_CORRUPT; + return; }else{ pgno++; pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); @@ -3133,66 +3131,72 @@ static void fts5SegiterPoslist( } /* -** IN/OUT parameter (*pa) points to a position list n bytes in size. If -** the position list contains entries for column iCol, then (*pa) is set -** to point to the sub-position-list for that column and the number of -** bytes in it returned. Or, if the argument position list does not -** contain any entries for column iCol, return 0. +** Parameter pPos points to a buffer containing a position list, size nPos. +** This function filters it according to pColset (which must be non-NULL) +** and sets pIter->base.pData/nData to point to the new position list. +** If memory is required for the new position list, use buffer pIter->poslist. +** Or, if the new position list is a contiguous subset of the input, set +** pIter->base.pData/nData to point directly to it. +** +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. If an OOM error is encountered, *pRc is set to SQLITE_NOMEM +** before returning. */ -static int fts5IndexExtractCol( - const u8 **pa, /* IN/OUT: Pointer to poslist */ - int n, /* IN: Size of poslist in bytes */ - int iCol /* Column to extract from poslist */ -){ - int iCurrent = 0; /* Anything before the first 0x01 is col 0 */ - const u8 *p = *pa; - const u8 *pEnd = &p[n]; /* One byte past end of position list */ - - while( iCol>iCurrent ){ - /* Advance pointer p until it points to pEnd or an 0x01 byte that is - ** not part of a varint. Note that it is not possible for a negative - ** or extremely large varint to occur within an uncorrupted position - ** list. So the last byte of each varint may be assumed to have a clear - ** 0x80 bit. */ - while( *p!=0x01 ){ - while( *p++ & 0x80 ); - if( p>=pEnd ) return 0; - } - *pa = p++; - iCurrent = *p++; - if( iCurrent & 0x80 ){ - p--; - p += fts5GetVarint32(p, iCurrent); - } - } - if( iCol!=iCurrent ) return 0; - - /* Advance pointer p until it points to pEnd or an 0x01 byte that is - ** not part of a varint */ - while( pnCol; i++){ - const u8 *pSub = pPos; - int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); - if( nSub ){ - fts5BufferAppendBlob(pRc, pBuf, nSub, pSub); + const u8 *p = pPos; + const u8 *aCopy = p; + const u8 *pEnd = &p[nPos]; /* One byte past end of position list */ + int i = 0; + int iCurrent = 0; + + if( pColset->nCol>1 && sqlite3Fts5BufferSize(pRc, &pIter->poslist, nPos) ){ + return; + } + + while( 1 ){ + while( pColset->aiCol[i]nCol ){ + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + return; + } + } + + /* Advance pointer p until it points to pEnd or an 0x01 byte that is + ** not part of a varint */ + while( paiCol[i]==iCurrent ){ + if( pColset->nCol==1 ){ + pIter->base.pData = aCopy; + pIter->base.nData = p-aCopy; + return; + } + fts5BufferSafeAppendBlob(&pIter->poslist, aCopy, p-aCopy); + } + if( p>=pEnd ){ + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + return; + } + aCopy = p++; + iCurrent = *p++; + if( iCurrent & 0x80 ){ + p--; + p += fts5GetVarint32(p, iCurrent); } } } + } /* @@ -3312,16 +3316,9 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ /* All data is stored on the current page. Populate the output ** variables to point into the body of the page object. */ const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset]; - if( pColset->nCol==1 ){ - pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]); - pIter->base.pData = a; - }else{ - int *pRc = &pIter->pIndex->rc; - fts5BufferZero(&pIter->poslist); - fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, &pIter->poslist); - pIter->base.pData = pIter->poslist.p; - pIter->base.nData = pIter->poslist.n; - } + int *pRc = &pIter->pIndex->rc; + fts5BufferZero(&pIter->poslist); + fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, pIter); }else{ /* The data is distributed over two or more pages. Copy it into the ** Fts5Iter.poslist buffer and then set the output pointer to point @@ -4543,14 +4540,14 @@ static void fts5FlushOneHash(Fts5Index *p){ fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); }else{ i64 iRowid = 0; - i64 iDelta = 0; + u64 iDelta = 0; int iOff = 0; /* The entire doclist will not fit on this leaf. The following ** loop iterates through the poslists that make up the current ** doclist. */ while( p->rc==SQLITE_OK && iOffaPoslist + pIter->nSize + pIter->nPoslist; - assert( pIter->aPoslist ); + assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) ); if( p>=pIter->aEof ){ pIter->aPoslist = 0; }else{ @@ -4824,6 +4821,9 @@ static void fts5DoclistIterNext(Fts5DoclistIter *pIter){ } pIter->aPoslist = p; + if( &pIter->aPoslist[pIter->nPoslist]>pIter->aEof ){ + pIter->aPoslist = 0; + } } } @@ -4832,9 +4832,11 @@ static void fts5DoclistIterInit( Fts5DoclistIter *pIter ){ memset(pIter, 0, sizeof(*pIter)); - pIter->aPoslist = pBuf->p; - pIter->aEof = &pBuf->p[pBuf->n]; - fts5DoclistIterNext(pIter); + if( pBuf->n>0 ){ + pIter->aPoslist = pBuf->p; + pIter->aEof = &pBuf->p[pBuf->n]; + fts5DoclistIterNext(pIter); + } } #if 0 @@ -4888,16 +4890,20 @@ static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){ static void fts5MergeRowidLists( Fts5Index *p, /* FTS5 backend object */ Fts5Buffer *p1, /* First list to merge */ - Fts5Buffer *p2 /* Second list to merge */ + int nBuf, /* Number of entries in apBuf[] */ + Fts5Buffer *aBuf /* Array of other lists to merge into p1 */ ){ int i1 = 0; int i2 = 0; i64 iRowid1 = 0; i64 iRowid2 = 0; i64 iOut = 0; - + Fts5Buffer *p2 = &aBuf[0]; Fts5Buffer out; + + (void)nBuf; memset(&out, 0, sizeof(out)); + assert( nBuf==1 ); sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n); if( p->rc ) return; @@ -4924,180 +4930,214 @@ static void fts5MergeRowidLists( fts5BufferFree(&out); } +typedef struct PrefixMerger PrefixMerger; +struct PrefixMerger { + Fts5DoclistIter iter; /* Doclist iterator */ + i64 iPos; /* For iterating through a position list */ + int iOff; + u8 *aPos; + PrefixMerger *pNext; /* Next in docid/poslist order */ +}; + +static void fts5PrefixMergerInsertByRowid( + PrefixMerger **ppHead, + PrefixMerger *p +){ + if( p->iter.aPoslist ){ + PrefixMerger **pp = ppHead; + while( *pp && p->iter.iRowid>(*pp)->iter.iRowid ){ + pp = &(*pp)->pNext; + } + p->pNext = *pp; + *pp = p; + } +} + +static void fts5PrefixMergerInsertByPosition( + PrefixMerger **ppHead, + PrefixMerger *p +){ + if( p->iPos>=0 ){ + PrefixMerger **pp = ppHead; + while( *pp && p->iPos>(*pp)->iPos ){ + pp = &(*pp)->pNext; + } + p->pNext = *pp; + *pp = p; + } +} + + /* -** Buffers p1 and p2 contain doclists. This function merges the content -** of the two doclists together and sets buffer p1 to the result before -** returning. -** -** If an error occurs, an error code is left in p->rc. If an error has -** already occurred, this function is a no-op. +** Array aBuf[] contains nBuf doclists. These are all merged in with the +** doclist in buffer p1. */ static void fts5MergePrefixLists( Fts5Index *p, /* FTS5 backend object */ Fts5Buffer *p1, /* First list to merge */ - Fts5Buffer *p2 /* Second list to merge */ + int nBuf, /* Number of buffers in array aBuf[] */ + Fts5Buffer *aBuf /* Other lists to merge in */ ){ - if( p2->n ){ - i64 iLastRowid = 0; - Fts5DoclistIter i1; - Fts5DoclistIter i2; - Fts5Buffer out = {0, 0, 0}; - Fts5Buffer tmp = {0, 0, 0}; - - /* The maximum size of the output is equal to the sum of the two - ** input sizes + 1 varint (9 bytes). The extra varint is because if the - ** first rowid in one input is a large negative number, and the first in - ** the other a non-negative number, the delta for the non-negative - ** number will be larger on disk than the literal integer value - ** was. - ** - ** Or, if the input position-lists are corrupt, then the output might - ** include up to 2 extra 10-byte positions created by interpreting -1 - ** (the value PoslistNext64() uses for EOF) as a position and appending - ** it to the output. This can happen at most once for each input - ** position-list, hence two 10 byte paddings. */ - if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n + 9+10+10) ) return; - fts5DoclistIterInit(p1, &i1); - fts5DoclistIterInit(p2, &i2); - - while( 1 ){ - if( i1.iRowidp) + (i2.aPoslist-p2->p)+9+10+10) ); - } - else if( i2.iRowid!=i1.iRowid ){ - /* Copy entry from i2 */ - fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); - fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.nPoslist+i2.nSize); - fts5DoclistIterNext(&i2); - if( i2.aPoslist==0 ) break; - assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) ); +#define fts5PrefixMergerNextPosition(p) \ + sqlite3Fts5PoslistNext64((p)->aPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos) +#define FTS5_MERGE_NLIST 16 + PrefixMerger aMerger[FTS5_MERGE_NLIST]; + PrefixMerger *pHead = 0; + int i; + int nOut = 0; + Fts5Buffer out = {0, 0, 0}; + Fts5Buffer tmp = {0, 0, 0}; + i64 iLastRowid = 0; + + /* Initialize a doclist-iterator for each input buffer. Arrange them in + ** a linked-list starting at pHead in ascending order of rowid. Avoid + ** linking any iterators already at EOF into the linked list at all. */ + assert( nBuf+1<=sizeof(aMerger)/sizeof(aMerger[0]) ); + memset(aMerger, 0, sizeof(PrefixMerger)*(nBuf+1)); + pHead = &aMerger[nBuf]; + fts5DoclistIterInit(p1, &pHead->iter); + for(i=0; in + 9 + 10*nBuf; + + /* The maximum size of the output is equal to the sum of the + ** input sizes + 1 varint (9 bytes). The extra varint is because if the + ** first rowid in one input is a large negative number, and the first in + ** the other a non-negative number, the delta for the non-negative + ** number will be larger on disk than the literal integer value + ** was. + ** + ** Or, if the input position-lists are corrupt, then the output might + ** include up to (nBuf+1) extra 10-byte positions created by interpreting -1 + ** (the value PoslistNext64() uses for EOF) as a position and appending + ** it to the output. This can happen at most once for each input + ** position-list, hence (nBuf+1) 10 byte paddings. */ + if( sqlite3Fts5BufferSize(&p->rc, &out, nOut) ) return; + + while( pHead ){ + fts5MergeAppendDocid(&out, iLastRowid, pHead->iter.iRowid); + + if( pHead->pNext && iLastRowid==pHead->pNext->iter.iRowid ){ + /* Merge data from two or more poslists */ + i64 iPrev = 0; + int nTmp = FTS5_DATA_ZERO_PADDING; + int nMerge = 0; + PrefixMerger *pSave = pHead; + PrefixMerger *pThis = 0; + int nTail = 0; + + pHead = 0; + while( pSave && pSave->iter.iRowid==iLastRowid ){ + PrefixMerger *pNext = pSave->pNext; + pSave->iOff = 0; + pSave->iPos = 0; + pSave->aPos = &pSave->iter.aPoslist[pSave->iter.nSize]; + fts5PrefixMergerNextPosition(pSave); + nTmp += pSave->iter.nPoslist + 10; + nMerge++; + fts5PrefixMergerInsertByPosition(&pHead, pSave); + pSave = pNext; } - else{ - /* Merge the two position lists. */ - i64 iPos1 = 0; - i64 iPos2 = 0; - int iOff1 = 0; - int iOff2 = 0; - u8 *a1 = &i1.aPoslist[i1.nSize]; - u8 *a2 = &i2.aPoslist[i2.nSize]; - int nCopy; - u8 *aCopy; - - i64 iPrev = 0; - Fts5PoslistWriter writer; - memset(&writer, 0, sizeof(writer)); - - /* See the earlier comment in this function for an explanation of why - ** corrupt input position lists might cause the output to consume - ** at most 20 bytes of unexpected space. */ - fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); - fts5BufferZero(&tmp); - sqlite3Fts5BufferSize(&p->rc, &tmp, - i1.nPoslist + i2.nPoslist + 10 + 10 + FTS5_DATA_ZERO_PADDING - ); - if( p->rc ) break; - sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); - sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); - assert_nc( iPos1>=0 && iPos2>=0 ); + if( pHead==0 || pHead->pNext==0 ){ + p->rc = FTS5_CORRUPT; + break; + } - if( iPos1=0 && iPos2>=0 ){ - while( 1 ){ - if( iPos1rc, &tmp, nTmp+nMerge*10) ){ + break; + } + fts5BufferZero(&tmp); + + pThis = pHead; + pHead = pThis->pNext; + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos); + fts5PrefixMergerNextPosition(pThis); + fts5PrefixMergerInsertByPosition(&pHead, pThis); + + while( pHead->pNext ){ + pThis = pHead; + if( pThis->iPos!=iPrev ){ + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos); } + fts5PrefixMergerNextPosition(pThis); + pHead = pThis->pNext; + fts5PrefixMergerInsertByPosition(&pHead, pThis); + } - if( iPos1>=0 ){ - if( iPos1!=iPrev ){ - sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1); - } - aCopy = &a1[iOff1]; - nCopy = i1.nPoslist - iOff1; - }else{ - assert_nc( iPos2>=0 && iPos2!=iPrev ); - sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2); - aCopy = &a2[iOff2]; - nCopy = i2.nPoslist - iOff2; - } - if( nCopy>0 ){ - fts5BufferSafeAppendBlob(&tmp, aCopy, nCopy); - } + if( pHead->iPos!=iPrev ){ + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pHead->iPos); + } + nTail = pHead->iter.nPoslist - pHead->iOff; - /* WRITEPOSLISTSIZE */ - assert_nc( tmp.n<=i1.nPoslist+i2.nPoslist ); - assert( tmp.n<=i1.nPoslist+i2.nPoslist+10+10 ); - if( tmp.n>i1.nPoslist+i2.nPoslist ){ - if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; - break; + /* WRITEPOSLISTSIZE */ + assert_nc( tmp.n+nTail<=nTmp ); + assert( tmp.n+nTail<=nTmp+nMerge*10 ); + if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){ + if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; + break; + } + fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2); + fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); + if( nTail>0 ){ + fts5BufferSafeAppendBlob(&out, &pHead->aPos[pHead->iOff], nTail); + } + + pHead = pSave; + for(i=0; iiter.aPoslist && pX->iter.iRowid==iLastRowid ){ + fts5DoclistIterNext(&pX->iter); + fts5PrefixMergerInsertByRowid(&pHead, pX); } - fts5BufferSafeAppendVarint(&out, tmp.n * 2); - fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); - fts5DoclistIterNext(&i1); - fts5DoclistIterNext(&i2); - assert_nc( out.n<=(p1->n+p2->n+9) ); - if( i1.aPoslist==0 || i2.aPoslist==0 ) break; - assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) ); } - } - if( i1.aPoslist ){ - fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid); - fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.aEof - i1.aPoslist); - } - else if( i2.aPoslist ){ - fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); - fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist); + }else{ + /* Copy poslist from pHead to output */ + PrefixMerger *pThis = pHead; + Fts5DoclistIter *pI = &pThis->iter; + fts5BufferSafeAppendBlob(&out, pI->aPoslist, pI->nPoslist+pI->nSize); + fts5DoclistIterNext(pI); + pHead = pThis->pNext; + fts5PrefixMergerInsertByRowid(&pHead, pThis); } - assert_nc( out.n<=(p1->n+p2->n+9) ); - - fts5BufferFree(p1); - fts5BufferFree(&tmp); - memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING); - *p1 = out; } + + fts5BufferFree(p1); + fts5BufferFree(&tmp); + memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING); + *p1 = out; } static void fts5SetupPrefixIter( Fts5Index *p, /* Index to read from */ int bDesc, /* True for "ORDER BY rowid DESC" */ - const u8 *pToken, /* Buffer containing prefix to match */ + int iIdx, /* Index to scan for data */ + u8 *pToken, /* Buffer containing prefix to match */ int nToken, /* Size of buffer pToken in bytes */ Fts5Colset *pColset, /* Restrict matches to these columns */ Fts5Iter **ppIter /* OUT: New iterator */ ){ Fts5Structure *pStruct; Fts5Buffer *aBuf; - const int nBuf = 32; + int nBuf = 32; + int nMerge = 1; - void (*xMerge)(Fts5Index*, Fts5Buffer*, Fts5Buffer*); + void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*); if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ xMerge = fts5MergeRowidLists; xAppend = fts5AppendRowid; }else{ + nMerge = FTS5_MERGE_NLIST-1; + nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */ xMerge = fts5MergePrefixLists; xAppend = fts5AppendPoslist; } @@ -5117,6 +5157,27 @@ static void fts5SetupPrefixIter( int bNewTerm = 1; memset(&doclist, 0, sizeof(doclist)); + if( iIdx!=0 ){ + int dummy = 0; + const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT; + pToken[0] = FTS5_MAIN_PREFIX; + fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1); + fts5IterSetOutputCb(&p->rc, p1); + for(; + fts5MultiIterEof(p, p1)==0; + fts5MultiIterNext2(p, p1, &dummy) + ){ + Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; + p1->xSetOutputs(p1, pSeg); + if( p1->base.nData ){ + xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist); + iLastRowid = p1->base.iRowid; + } + } + fts5MultiIterFree(p1); + } + + pToken[0] = FTS5_MAIN_PREFIX + iIdx; fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); fts5IterSetOutputCb(&p->rc, p1); for( /* no-op */ ; @@ -5137,13 +5198,21 @@ static void fts5SetupPrefixIter( if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ - assert( ibase.iRowid; } - for(i=0; irc==SQLITE_OK ){ - xMerge(p, &doclist, &aBuf[i]); + xMerge(p, &doclist, nMerge, &aBuf[i]); + } + for(iFree=i; iFreerc, &buf, nToken+1)==0 ){ int iIdx = 0; /* Index to search */ + int iPrefixIdx = 0; /* +1 prefix index */ if( nToken ) memcpy(&buf.p[1], pToken, nToken); /* Figure out which index to search and set iIdx accordingly. If this @@ -5433,7 +5507,9 @@ int sqlite3Fts5IndexQuery( if( flags & FTS5INDEX_QUERY_PREFIX ){ int nChar = fts5IndexCharlen(pToken, nToken); for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){ - if( pConfig->aPrefix[iIdx-1]==nChar ) break; + int nIdxChar = pConfig->aPrefix[iIdx-1]; + if( nIdxChar==nChar ) break; + if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx; } } @@ -5450,8 +5526,7 @@ int sqlite3Fts5IndexQuery( }else{ /* Scan multiple terms in the main index */ int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; - buf.p[0] = FTS5_MAIN_PREFIX; - fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pColset, &pRet); + fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); assert( p->rc!=SQLITE_OK || pRet->pColset==0 ); fts5IterSetOutputCb(&p->rc, pRet); if( p->rc==SQLITE_OK ){ @@ -5524,8 +5599,9 @@ int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){ int n; const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); + assert_nc( z || n<=1 ); *pn = n-1; - return &z[1]; + return (z ? &z[1] : 0); } /* @@ -6150,6 +6226,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ ** function only. */ +#ifdef SQLITE_TEST /* ** Decode a segment-data rowid from the %_data table. This function is ** the opposite of macro FTS5_SEGMENT_ROWID(). @@ -6172,7 +6249,9 @@ static void fts5DecodeRowid( *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */ fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno); @@ -6190,7 +6269,9 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ ); } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST static void fts5DebugStructure( int *pRc, /* IN/OUT: error code */ Fts5Buffer *pBuf, @@ -6212,7 +6293,9 @@ static void fts5DebugStructure( sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** This is part of the fts5_decode() debugging aid. ** @@ -6237,7 +6320,9 @@ static void fts5DecodeStructure( fts5DebugStructure(pRc, pBuf, p); fts5StructureRelease(p); } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** This is part of the fts5_decode() debugging aid. ** @@ -6260,7 +6345,9 @@ static void fts5DecodeAverages( zSpace = " "; } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** Buffer (a/n) is assumed to contain a list of serialized varints. Read ** each varint and append its string representation to buffer pBuf. Return @@ -6277,7 +6364,9 @@ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ } return iOff; } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** The start of buffer (a/n) contains the start of a doclist. The doclist ** may or may not finish within the buffer. This function appends a text @@ -6310,7 +6399,9 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ return iOff; } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** This function is part of the fts5_decode() debugging function. It is ** only ever used with detail=none tables. @@ -6351,7 +6442,9 @@ static void fts5DecodeRowidList( sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** The implementation of user-defined scalar function fts5_decode(). */ @@ -6560,7 +6653,9 @@ static void fts5DecodeFunction( } fts5BufferFree(&s); } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** The implementation of user-defined scalar function fts5_rowid(). */ @@ -6594,6 +6689,7 @@ static void fts5RowidFunction( } } } +#endif /* SQLITE_TEST */ /* ** This is called as part of registering the FTS5 module with database @@ -6604,6 +6700,7 @@ static void fts5RowidFunction( ** SQLite error code is returned instead. */ int sqlite3Fts5IndexInit(sqlite3 *db){ +#ifdef SQLITE_TEST int rc = sqlite3_create_function( db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 ); @@ -6621,6 +6718,10 @@ int sqlite3Fts5IndexInit(sqlite3 *db){ ); } return rc; +#else + return SQLITE_OK; + UNUSED_PARAM(db); +#endif } diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 788821e6b..3dea6cafc 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -22,7 +22,9 @@ ** assert() conditions in the fts5 code are activated - conditions that are ** only true if it is guaranteed that the fts5 database is not corrupt. */ +#ifdef SQLITE_DEBUG int sqlite3_fts5_may_be_corrupt = 1; +#endif typedef struct Fts5Auxdata Fts5Auxdata; @@ -1947,13 +1949,15 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ nInst++; if( nInst>=pCsr->nInstAlloc ){ - pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; + int nNewSize = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; aInst = (int*)sqlite3_realloc64( - pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3 + pCsr->aInst, nNewSize*sizeof(int)*3 ); if( aInst ){ pCsr->aInst = aInst; + pCsr->nInstAlloc = nNewSize; }else{ + nInst--; rc = SQLITE_NOMEM; break; } @@ -2177,7 +2181,8 @@ static int fts5ApiPhraseFirst( int n; int rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); if( rc==SQLITE_OK ){ - pIter->b = &pIter->a[n]; + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); *piCol = 0; *piOff = 0; fts5ApiPhraseNext(pCtx, pIter, piCol, piOff); @@ -2236,7 +2241,8 @@ static int fts5ApiPhraseFirstColumn( rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); } if( rc==SQLITE_OK ){ - pIter->b = &pIter->a[n]; + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); *piCol = 0; fts5ApiPhraseNextColumn(pCtx, pIter, piCol); } @@ -2244,7 +2250,8 @@ static int fts5ApiPhraseFirstColumn( int n; rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); if( rc==SQLITE_OK ){ - pIter->b = &pIter->a[n]; + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); if( n<=0 ){ *piCol = -1; }else if( pIter->a[0]==0x01 ){ @@ -2722,7 +2729,7 @@ int sqlite3Fts5GetTokenizer( *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]); }else{ rc = pMod->x.xCreate( - pMod->pUserData, &azArg[1], (nArg?nArg-1:0), &pConfig->pTok + pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->pTok ); pConfig->pTokApi = &pMod->x; if( rc!=SQLITE_OK ){ diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c index f33dcae03..80c600dbb 100644 --- a/ext/fts5/fts5_tcl.c +++ b/ext/fts5/fts5_tcl.c @@ -29,7 +29,9 @@ #include #include +#ifdef SQLITE_DEBUG extern int sqlite3_fts5_may_be_corrupt; +#endif extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3*); extern int sqlite3Fts5TestRegisterTok(sqlite3*, fts5_api*); @@ -1011,6 +1013,7 @@ static int SQLITE_TCLAPI f5tMayBeCorrupt( int objc, Tcl_Obj *CONST objv[] ){ +#ifdef SQLITE_DEBUG int bOld = sqlite3_fts5_may_be_corrupt; if( objc!=2 && objc!=1 ){ @@ -1024,6 +1027,7 @@ static int SQLITE_TCLAPI f5tMayBeCorrupt( } Tcl_SetObjResult(interp, Tcl_NewIntObj(bOld)); +#endif return TCL_OK; } diff --git a/ext/fts5/fts5_test_tok.c b/ext/fts5/fts5_test_tok.c index b5d4162fa..a5d839da6 100644 --- a/ext/fts5/fts5_test_tok.c +++ b/ext/fts5/fts5_test_tok.c @@ -211,7 +211,7 @@ static int fts5tokConnectMethod( rc = pApi->xFindTokenizer(pApi, zModule, &pTokCtx, &pTab->tok); if( rc==SQLITE_OK ){ - const char **azArg = (const char **)&azDequote[1]; + const char **azArg = (nDequote>1 ? (const char **)&azDequote[1] : 0); int nArg = nDequote>0 ? nDequote-1 : 0; rc = pTab->tok.xCreate(pTokCtx, azArg, nArg, &pTab->pTok); } diff --git a/ext/fts5/test/fts5corrupt3.test b/ext/fts5/test/fts5corrupt3.test index 1b7b0c9d8..5604bca76 100644 --- a/ext/fts5/test/fts5corrupt3.test +++ b/ext/fts5/test/fts5corrupt3.test @@ -8346,6 +8346,7 @@ do_catchsql_test 58.1 { #------------------------------------------------------------------------- do_test 59.0 { sqlite3 db {} + sqlite3_fts5_register_matchinfo db db deserialize [decode_hexdb { .open --hexdb | size 32768 pagesize 4096 filename crash-96b136358d01ec.db @@ -8544,11 +8545,12 @@ do_test 59.0 { do_catchsql_test 59.1 { SELECT (matchinfo(591,t1)) FROM t1 WHERE t1 MATCH 'e*eŸ' -} {1 {database disk image is malformed}} +} {0 {}} #------------------------------------------------------------------------- do_test 60.0 { sqlite3 db {} + sqlite3_fts5_register_matchinfo db db deserialize [decode_hexdb { .open --hexdb | size 32768 pagesize 4096 filename crash-c77b90b929dc92.db @@ -10507,6 +10509,4631 @@ do_catchsql_test 71.2 { INSERT INTO t1(t1) VALUES('integrity-check'); } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db +do_test 72.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 24576 pagesize 4096 filename crash-77b86d070d0ac6.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 06 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................ +| 96: 00 00 00 00 0d 00 00 00 06 0d e2 00 0f c4 0f 6a ...............j +| 112: 0e fc 0e 9d 0e 3d 0d e2 00 00 00 00 00 00 00 00 .....=.......... +| 3552: 00 00 59 06 06 17 21 21 01 7f 74 61 62 6c 65 74 ..Y...!!..tablet +| 3568: 74 74 5f 63 6f 6e 66 69 67 74 74 74 5f 63 6f 6e tt_configttt_con +| 3584: 66 69 67 06 43 52 45 41 54 45 20 54 41 42 4c 45 fig.CREATE TABLE +| 3600: 20 27 74 74 74 5f 63 6f 6e 66 69 67 27 28 6b 20 'ttt_config'(k +| 3616: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 20 PRIMARY KEY, v) +| 3632: 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5e 05 07 WITHOUT ROWID^.. +| 3648: 17 23 23 01 81 03 74 61 62 6c 65 74 74 74 5f 64 .##...tablettt_d +| 3664: 6f 63 73 69 7a 65 74 74 74 5f 64 6f 63 73 69 7a ocsizettt_docsiz +| 3680: 65 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 e.CREATE TABLE ' +| 3696: 74 74 74 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 ttt_docsize'(id +| 3712: 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 INTEGER PRIMARY +| 3728: 4b 45 59 2c 20 73 7a 20 42 4c 4f 42 29 5d 04 07 KEY, sz BLOB)].. +| 3744: 17 23 23 01 81 01 74 61 62 6c 65 74 74 74 5f 63 .##...tablettt_c +| 3760: 6f 6e 74 65 6e 74 74 74 74 5f 63 6f 6e 74 65 6e ontentttt_conten +| 3776: 74 04 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 t.CREATE TABLE ' +| 3792: 74 74 74 5f 63 6f 6e 74 65 6e 74 27 28 69 64 20 ttt_content'(id +| 3808: 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 INTEGER PRIMARY +| 3824: 4b 45 59 2c 20 63 30 2c 20 63 31 29 6c 03 07 17 KEY, c0, c1)l... +| 3840: 1b 1b 01 81 2f 74 61 62 6c 65 74 74 74 5f 69 64 ..../tablettt_id +| 3856: 78 74 74 74 5f 69 64 78 03 43 52 45 41 54 45 20 xttt_idx.CREATE +| 3872: 54 41 42 4c 45 20 27 74 74 74 5f 69 64 78 27 28 TABLE 'ttt_idx'( +| 3888: 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 70 67 6e segid, term, pgn +| 3904: 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 59 28 73 o, PRIMARY KEY(s +| 3920: 65 67 69 64 2c 20 74 65 72 6d 29 29 20 57 49 54 egid, term)) WIT +| 3936: 48 4f 55 54 20 52 4f 57 49 44 58 02 07 17 1d 1d HOUT ROWIDX..... +| 3952: 01 81 03 74 61 62 6c 65 74 74 74 5f 64 61 74 61 ...tablettt_data +| 3968: 74 74 74 5f 64 61 74 61 02 43 52 45 41 54 45 20 ttt_data.CREATE +| 3984: 54 41 42 4c 45 20 27 74 74 74 5f 64 61 74 61 27 TABLE 'ttt_data' +| 4000: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM +| 4016: 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 ARY KEY, block B +| 4032: 4c 4f 42 29 3a 01 06 17 13 13 08 5f 74 61 62 6c LOB):......_tabl +| 4048: 65 74 74 74 74 74 74 43 52 45 41 54 45 20 56 49 ettttttCREATE VI +| 4064: 52 54 55 41 4c 20 54 41 42 4c 45 20 74 74 74 20 RTUAL TABLE ttt +| 4080: 55 53 49 4e 47 20 66 74 73 35 28 61 2c 20 62 29 USING fts5(a, b) +| page 2 offset 4096 +| 0: 0d 0f 44 00 05 0e 81 00 0f e7 0e 81 0f af 0f 58 ..D............X +| 16: 0e 98 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3712: 00 15 0a 03 00 30 00 00 00 00 01 03 03 00 03 01 .....0.......... +| 3728: 01 01 02 01 01 03 01 01 81 24 8c 80 80 80 80 01 .........$...... +| 3744: 04 00 82 4c 00 00 00 9b 02 30 65 03 1a 02 05 05 ...L.....0e..... +| 3760: 07 05 01 01 04 03 03 08 03 03 01 2e 02 05 05 07 ................ +| 3776: 05 07 05 07 05 01 01 04 03 03 08 03 03 08 03 03 ................ +| 3792: 08 03 03 02 01 65 03 1e 03 05 05 04 05 05 01 01 .....e.......... +| 3808: 03 06 04 04 06 04 03 01 36 03 05 05 04 05 05 04 ........6....... +| 3824: 05 05 04 05 05 01 01 03 06 04 04 06 04 04 06 04 ................ +| 3840: 04 06 04 03 03 01 65 03 14 04 05 07 05 05 01 01 ......e......... +| 3856: 02 08 0a 01 20 04 05 07 05 07 05 07 05 05 01 01 .... ........... +| 3872: 02 08 0a 09 fa 04 01 65 03 02 0a 01 06 0a 1a 0a .......e........ +| 3888: 05 01 65 03 06 01 01 0a 01 0a 01 01 0a 0a 0a 04 ..e............. +| 3904: 2b 31 21 0b 0f ef 00 14 2a 00 00 00 00 01 02 02 +1!.....*....... +| 3920: 00 02 01 01 01 02 01 01 50 88 80 80 80 80 01 04 ........P....... +| 3936: 00 81 24 00 00 00 47 02 30 65 02 1a 02 05 05 07 ..$...G.0e...... +| 3952: 05 01 01 04 03 03 08 03 03 02 01 65 02 1e 03 05 ...........e.... +| 3968: 05 04 05 05 01 01 03 06 04 04 06 04 03 03 01 65 ...............e +| 3984: 02 14 04 05 07 05 05 01 01 02 08 0a 04 01 65 02 ..............e. +| 4000: 02 0a 05 01 65 02 06 01 01 0a 04 12 14 0f 06 31 ....e..........1 +| 4016: 84 80 80 80 80 01 03 00 68 00 00 00 2b 02 30 65 ........h...+.0e +| 4032: 01 10 02 05 05 01 01 04 03 9f 02 01 65 01 12 03 ............e... +| 4048: 05 05 01 01 03 06 04 03 03 01 65 01 0e 14 05 05 ..........e..... +| 4064: 01 01 02 08 04 0d 0e 06 01 03 00 12 04 4c 4c 00 .............LL. +| 4080: 00 00 11 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ +| page 3 offset 8192 +| 0: 0a 00 00 00 03 0f 00 00 00 00 00 00 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 06 04 01 0c ................ +| 4080: 01 03 02 06 04 01 0c 01 02 02 05 04 09 0c 01 02 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3600: 00 00 00 00 00 00 00 00 00 00 81 52 04 06 00 81 ...........R.... +| 3616: 5d 81 55 65 20 65 65 20 65 65 65 20 65 20 65 65 ].Ue ee eee e ee +| 3632: 20 65 65 65 20 65 20 65 65 20 65 65 65 65 20 65 eee e ee eeee e +| 3648: 65 20 65 65 65 20 65 20 65 65 20 65 65 65 20 65 e eee e ee eee e +| 3664: 20 65 65 20 65 65 55 65 20 65 65 20 65 65 65 20 ee eeUe ee eee +| 3680: 65 20 65 65 20 65 65 65 20 65 20 65 65 20 65 65 e ee eee e ee ee +| 3696: 65 65 20 65 65 20 65 65 65 20 65 20 65 65 20 65 ee ee eee e ee e +| 3712: 65 65 20 65 20 65 65 20 65 65 65 65 65 65 20 65 ee e ee eeeeee e +| 3728: 65 20 65 20 65 20 65 20 65 65 20 65 85 65 20 65 e e e e ee e.e e +| 3744: 65 20 65 65 65 65 65 20 65 65 20 65 20 65 20 65 e eeeee ee e e e +| 3760: 20 65 65 20 65 65 65 20 65 65 20 65 65 65 65 65 ee eee ee eeeee +| 3776: 20 65 65 20 65 20 65 20 65 20 65 65 20 65 65 65 ee e e e ee eee +| 3792: 20 65 65 20 65 65 65 65 65 20 65 65 20 65 20 65 ee eeeee ee e e +| 3808: 20 65 20 65 65 20 65 65 65 20 65 65 20 65 65 6a e ee eee ee eej +| 3824: 03 04 00 75 71 65 20 65 65 20 65 65 65 20 65 20 ...uqe ee eee e +| 3840: 65 65 20 65 65 65 20 65 20 65 65 20 65 65 65 65 ee eee e ee eeee +| 3856: 20 65 65 20 65 65 65 20 65 20 65 65 20 65 65 65 ee eee e ee eee +| 3872: 20 65 20 65 65 20 65 65 65 65 65 65 20 65 65 20 e ee eeeeee ee +| 3888: 65 20 65 20 65 20 65 64 20 65 65 65 20 65 65 20 e e e ed eee ee +| 3904: 65 65 65 65 65 20 65 65 20 65 20 65 20 65 10 65 eeeee ee e e e.e +| 3920: 65 20 65 65 65 10 65 65 20 65 65 6a 02 04 00 75 e eee.ee eej...u +| 3936: 71 65 20 65 65 20 65 65 65 20 65 20 65 65 20 65 qe ee eee e ee e +| 3952: 65 65 20 65 20 65 65 20 65 65 65 65 20 65 65 20 ee e ee eeee ee +| 3968: 65 65 65 20 65 20 65 65 20 65 65 65 20 65 20 65 eee e ee eee e e +| 3984: 65 20 65 65 65 66 65 65 20 65 65 20 65 20 65 20 e eeefee ee e e +| 4000: 65 88 65 65 20 65 65 65 30 65 65 20 65 65 65 65 e.ee eee0ee eeee +| 4016: 65 20 65 65 20 65 20 65 20 65 20 65 65 20 65 65 e ee e e e ee ee +| 4032: 65 20 65 65 20 65 65 37 01 04 00 41 3f 65 20 65 e ee ee7...A?e e +| 4048: 65 20 65 65 65 20 65 20 65 65 20 65 65 65 20 65 e eee e ee eee e +| 4064: 20 65 65 20 65 65 65 65 65 65 20 65 65 20 65 20 ee eeeeee ee e +| 4080: 65 20 65 20 65 65 20 65 65 65 20 65 65 20 65 65 e e ee eee ee ee +| page 5 offset 16384 +| 0: 0d 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4064: 00 00 00 00 05 04 03 00 10 21 21 05 03 03 00 10 .........!!..... +| 4080: 11 11 05 02 03 00 10 11 11 05 01 03 00 10 09 09 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| end crash-77b86d070d0ac6.db +}]} {} + +do_catchsql_test 72.1 { + INSERT INTO ttt(ttt) VALUES('integrity-check'); +} {1 {database disk image is malformed}} + +do_catchsql_test 72.1 { + SELECT 1 FROM ttt('e* NOT ee*'); +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +reset_db +do_test 73.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 24576 pagesize 4096 filename crash-b02ca2cc4d7dda.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 06 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................ +| 96: 00 00 00 00 0d 00 00 00 06 0d e2 00 0f c4 0f 6a ...............j +| 112: 0e fc 0e 9d 0e 3d 0d e2 00 00 00 00 00 00 00 00 .....=.......... +| 3552: 00 00 59 06 06 17 21 21 01 7f 74 61 62 6c 65 74 ..Y...!!..tablet +| 3568: 74 74 5f 63 6f 6e 66 69 67 74 74 74 5f 63 6f 6e tt_configttt_con +| 3584: 66 69 67 06 43 52 45 41 54 45 20 54 41 42 4c 45 fig.CREATE TABLE +| 3600: 20 27 74 74 74 5f 63 6f 6e 66 69 67 27 28 6b 20 'ttt_config'(k +| 3616: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 20 PRIMARY KEY, v) +| 3632: 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5e 05 07 WITHOUT ROWID^.. +| 3648: 17 23 23 01 81 03 74 61 62 6c 65 74 74 74 5f 64 .##...tablettt_d +| 3664: 6f 63 73 69 7a 65 74 74 74 5f 64 6f 63 73 69 7a ocsizettt_docsiz +| 3680: 65 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 e.CREATE TABLE ' +| 3696: 74 74 74 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 ttt_docsize'(id +| 3712: 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 INTEGER PRIMARY +| 3728: 4b 45 59 2c 20 73 7a 20 42 4c 4f 42 29 5d 04 07 KEY, sz BLOB)].. +| 3744: 17 23 23 01 81 01 74 61 62 6c 65 74 74 74 5f 63 .##...tablettt_c +| 3760: 6f 6e 74 65 6e 74 74 74 74 5f 63 6f 6e 74 65 6e ontentttt_conten +| 3776: 74 04 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 t.CREATE TABLE ' +| 3792: 74 74 74 5f 63 6f 6e 74 65 6e 74 27 28 69 64 20 ttt_content'(id +| 3808: 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 INTEGER PRIMARY +| 3824: 4b 45 59 2c 20 63 30 2c 20 63 31 29 6c 03 07 17 KEY, c0, c1)l... +| 3840: 1b 1b 01 81 2f 74 61 62 6c 65 74 74 74 5f 69 64 ..../tablettt_id +| 3856: 78 74 74 74 5f 69 64 78 03 43 52 45 41 54 45 20 xttt_idx.CREATE +| 3872: 54 41 42 4c 45 20 27 74 74 74 5f 69 64 78 27 28 TABLE 'ttt_idx'( +| 3888: 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 70 67 6e segid, term, pgn +| 3904: 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 59 28 73 o, PRIMARY KEY(s +| 3920: 65 67 69 64 2c 20 74 65 72 6d 29 29 20 57 49 54 egid, term)) WIT +| 3936: 48 4f 55 54 20 52 4f 57 49 44 58 02 07 17 1d 1d HOUT ROWIDX..... +| 3952: 01 81 03 74 61 62 6c 65 74 74 74 5f 64 61 74 61 ...tablettt_data +| 3968: 74 74 74 5f 64 61 74 61 02 43 52 45 41 54 45 20 ttt_data.CREATE +| 3984: 54 41 42 4c 45 20 27 74 74 74 5f 64 61 74 61 27 TABLE 'ttt_data' +| 4000: 28 69 65 20 49 4e 54 45 47 45 52 20 50 52 49 4d (ie INTEGER PRIM +| 4016: 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 ARY KEY, block B +| 4032: 4c 4f 42 29 3a 01 06 17 13 13 08 5f 74 61 62 6c LOB):......_tabl +| 4048: 65 74 74 74 74 74 74 43 52 45 41 54 45 20 56 49 ettttttCREATE VI +| 4064: 52 54 55 41 4c 20 54 41 42 4c 45 20 74 74 74 20 RTUAL TABLE ttt +| 4080: 55 53 49 4e 47 20 66 74 73 35 28 61 2c 20 62 29 USING fts5(a, b) +| page 2 offset 4096 +| 0: 0d 0f 44 00 05 0e 81 00 0f e7 0e 81 0f af 0f 58 ..D............X +| 16: 0e 98 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3712: 00 15 0a 03 00 30 00 00 00 00 01 03 03 00 03 01 .....0.......... +| 3728: 01 01 02 01 01 03 01 01 81 24 8c 80 80 80 80 01 .........$...... +| 3744: 04 00 82 4c 00 00 00 9b 02 30 65 03 1a 02 05 05 ...L.....0e..... +| 3760: 07 05 01 01 04 03 03 08 03 03 01 2e 02 05 05 07 ................ +| 3776: 05 07 05 07 05 01 01 04 03 03 08 03 03 08 03 03 ................ +| 3792: 08 03 03 02 01 65 03 1e 03 05 05 04 05 05 01 01 .....e.......... +| 3808: 03 06 04 04 06 04 03 01 36 03 05 05 04 05 05 04 ........6....... +| 3824: 05 04 f4 04 05 01 01 03 06 04 04 06 04 04 06 04 ................ +| 3840: 04 06 04 db 03 01 65 03 14 04 05 07 05 05 01 01 ......e......... +| 3856: 02 08 0a 01 20 04 05 07 05 07 05 07 05 05 01 01 .... ........... +| 3872: 02 08 0a 0a 0a 04 01 65 03 02 0a 01 06 0a 0a 0a .......e........ +| 3888: 05 01 65 03 06 01 01 0a 01 0a 01 01 0a 0a 0a 04 ..e............. +| 3904: 2b 31 21 0b 0f ef 00 14 2a 00 00 00 00 01 02 02 +1!.....*....... +| 3920: 00 02 01 01 01 02 01 01 50 88 80 80 80 80 01 04 ........P....... +| 3936: 00 81 24 00 00 00 47 02 30 65 02 1a 02 05 05 07 ..$...G.0e...... +| 3952: 05 01 01 04 03 03 08 03 03 02 01 65 02 1e 03 05 ...........e.... +| 3968: 05 04 05 05 01 01 03 06 09 14 06 04 03 03 01 65 ...............e +| 3984: 02 14 04 05 07 05 05 01 01 02 08 ed 04 01 65 02 ..............e. +| 4000: 02 0a 05 01 65 02 06 01 01 0a 04 12 14 0f 06 31 ....e..........1 +| 4016: 84 80 80 80 80 01 03 00 68 00 00 00 2b 02 30 65 ........h...+.0e +| 4032: 01 10 02 05 05 01 01 04 03 03 02 01 55 01 12 03 ............U... +| 4048: 05 05 01 01 03 06 05 03 03 01 65 01 0e 04 05 05 ..........e..... +| 4064: 01 01 02 08 04 0d 0e 06 01 03 00 12 04 4c 4c 00 .............LL. +| 4080: 00 00 11 24 00 00 00 00 01 0f c1 00 01 01 01 01 ...$............ +| page 3 offset 8192 +| 0: 0a 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 06 04 01 0c ................ +| 4080: 01 03 02 06 04 01 0c 01 02 02 05 04 09 0c 01 02 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3600: 00 00 00 00 00 00 00 00 00 00 81 52 04 06 00 81 ...........R.... +| 3616: 5d 81 55 65 20 65 65 20 65 65 65 20 65 20 65 65 ].Ue ee eee e ee +| 3632: 20 65 65 65 20 65 20 65 65 20 65 65 65 65 20 65 eee e ee eeee e +| 3648: 65 20 65 65 65 20 65 20 65 65 20 65 65 65 20 65 e eee e ee eee e +| 3664: 20 65 65 20 65 65 65 65 20 65 65 20 65 65 65 20 ee eeee ee eee +| 3680: 65 20 65 65 20 65 65 65 20 65 20 65 65 20 65 65 e ee eee e ee ee +| 3696: 65 65 20 65 65 20 65 65 65 20 65 20 65 65 20 65 ee ee eee e ee e +| 3712: 65 65 20 65 20 65 65 20 65 65 65 65 65 65 20 65 ee e ee eeeeee e +| 3728: 65 20 65 20 65 20 65 20 65 65 20 65 65 65 20 65 e e e e ee eee e +| 3744: 65 20 65 65 65 65 65 20 65 65 20 65 20 65 20 65 e eeeee ee e e e +| 3760: 20 65 65 20 65 62 d5 20 65 65 20 65 65 65 65 65 ee eb. ee eeeee +| 3776: 20 65 65 20 65 20 65 20 65 20 65 65 20 65 65 65 ee e e e ee eee +| 3792: 20 65 65 20 65 65 65 65 65 20 65 65 20 65 21 65 ee eeeee ee e!e +| 3808: 20 65 20 65 65 20 65 65 65 20 65 65 20 65 65 6a e ee eee ee eej +| 3824: 03 04 00 75 71 65 20 65 65 10 65 65 65 20 65 20 ...uqe ee.eee e +| 3840: 65 65 20 65 65 65 20 65 20 65 65 20 65 65 65 65 ee eee e ee eeee +| 3856: 20 65 65 20 65 65 65 20 65 20 65 65 20 65 65 65 ee eee e ee eee +| 3872: 20 65 20 65 65 20 65 65 65 65 65 65 20 65 65 20 e ee eeeeee ee +| 3888: 65 20 65 20 65 20 65 65 20 65 65 65 20 62 55 20 e e e ee eee bU +| 3904: 65 65 65 65 65 20 65 65 20 65 20 65 20 65 20 65 eeeee ee e e e e +| 3920: 65 20 65 65 65 20 55 65 20 65 65 6a 02 04 00 75 e eee Ue eej...u +| 3936: 71 65 20 65 65 20 65 65 65 20 65 10 65 65 20 65 qe ee eee e.ee e +| 3952: 65 65 20 65 20 65 65 20 65 65 65 65 20 65 65 20 ee e ee eeee ee +| 3968: 65 65 65 20 65 20 65 65 20 65 65 65 20 65 20 65 eee e ee eee e e +| 3984: 65 20 65 65 65 65 65 65 20 65 65 20 65 20 65 20 e eeeeee ee e e +| 4000: 65 20 65 65 20 65 65 65 20 65 65 20 65 65 65 65 e ee eee ee eeee +| 4016: 65 20 65 65 20 65 20 65 20 65 20 65 65 20 65 65 e ee e e e ee ee +| 4032: 65 20 65 65 21 65 65 37 0a 04 00 41 3f 65 20 65 e ee!ee7...A?e e +| 4048: 65 20 65 65 65 20 65 20 65 65 20 65 65 65 20 65 e eee e ee eee e +| 4064: 20 65 65 20 65 65 65 65 65 65 20 65 65 20 65 20 ee eeeeee ee e +| 4080: 65 20 65 20 65 65 20 65 65 65 20 65 65 20 65 65 e e ee eee ee ee +| page 5 offset 16384 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4064: 00 00 00 00 05 04 03 00 10 21 21 05 13 03 00 10 .........!!..... +| 4080: 11 11 05 02 03 00 10 11 11 05 01 03 00 10 09 09 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| end crash-b02ca2cc4d7dda.db +}]} {} + +do_catchsql_test 73.1 { + SELECT snippet(ttt,ttt, NOT 54 ), + * FROM ttt('e* NOT ee*e* NOT ee* NOT ee*e* NOT e*') ; +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +reset_db +do_test 74.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 106496 pagesize 4096 filename x.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 01 00 00 00 1a .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ +| 96: 00 2e 4f 78 0d 0f c7 00 07 0d 92 00 0f 8d 0f 36 ..Ox...........6 +| 112: 0e cb 0e 6b 0e 0e 0d b6 0d 92 0d 92 00 00 00 00 ...k............ +| 3472: 00 00 22 08 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet +| 3488: 32 74 32 08 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE +| 3504: 20 74 32 28 78 29 56 07 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta +| 3520: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c +| 3536: 6f 6e 66 69 67 07 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB +| 3552: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k +| 3568: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) +| 3584: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 06 WITHOUT ROWID[. +| 3600: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d +| 3616: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize +| 3632: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't +| 3648: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN +| 3664: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE +| 3680: 59 2c 20 73 7a 20 42 4c 4f 42 29 5e 05 07 17 21 Y, sz BLOB)^...! +| 3696: 21 01 81 07 74 61 62 6c 65 74 31 5f 63 6f 6e 74 !...tablet1_cont +| 3712: 65 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 05 43 52 entt1_content.CR +| 3728: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 EATE TABLE 't1_c +| 3744: 6f 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 ontent'(id INTEG +| 3760: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 3776: 63 30 2c 20 63 31 2c d6 63 32 29 69 04 07 17 19 c0, c1,.c2)i.... +| 3792: 19 01 81 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 ...-tablet1_idxt +| 3808: 31 5f 69 64 78 04 43 52 45 41 54 45 20 54 41 42 1_idx.CREATE TAB +| 3824: 4c 45 20 27 74 31 5f 69 64 78 27 28 73 65 67 69 LE 't1_idx'(segi +| 3840: 64 2c 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 d, term, pgno, P +| 3856: 52 49 4d 41 52 59 20 4b 45 59 28 73 65 67 69 64 RIMARY KEY(segid +| 3872: 2c 20 74 65 72 6d 29 29 20 57 49 54 48 4f 55 54 , term)) WITHOUT +| 3888: 20 52 4f 57 49 44 55 03 07 17 1b 1b 01 81 01 74 ROWIDU........t +| 3904: 61 62 6c 65 74 31 5f 64 61 74 61 74 31 5f 64 61 ablet1_datat1_da +| 3920: 74 61 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 ta.CREATE TABLE +| 3936: 27 74 31 5f 64 61 74 61 27 28 69 64 20 49 4e 54 't1_data'(id INT +| 3952: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY +| 3968: 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 38 02 06 , block BLOB)8.. +| 3984: 17 11 11 08 5f 74 61 62 6c 65 74 31 74 31 43 52 ...._tablet1t1CR +| 4000: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4016: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 LE t1 USING fts5 +| 4032: 28 61 2c 62 2c 63 29 00 00 00 00 00 00 00 00 00 (a,b,c)......... +| page 2 offset 4096 +| 0: 0d 00 00 00 24 0c 0a 00 0f d8 0f af 0f 86 0f 74 ....$..........t +| 16: 0f 61 0f 4e 0f 2f 0f 0f 0e ef 0e d7 0e be 0e a5 .a.N./.......... +| 32: 0e 8d 0e 74 0e 5b 0e 40 0e 24 0e 08 01 2f 0d d5 ...t.[.@.$.../.. +| 48: 0d bb 0d a0 0d 84 0d 68 0d 4f 0d 35 0d 1b 0c fb .......h.O.5.... +| 64: 0c da 0c b9 0c 99 0c 78 0c 57 0c 00 00 00 00 00 .......x.W...... +| 3072: 00 00 00 00 00 00 00 00 00 00 18 24 05 00 25 0f ...........$..%. +| 3088: 19 54 48 52 45 41 44 53 41 46 45 3d 30 58 42 49 .THREADSAFE=0XBI +| 3104: 4e 41 52 59 18 23 05 00 25 0f 19 54 48 52 45 41 NARY.#..%..THREA +| 3120: 44 53 41 46 45 3d 30 58 4e 4f 43 41 53 45 17 8f DSAFE=0XNOCASE.. +| 3136: 05 00 25 0f 17 54 48 52 45 41 44 53 41 46 45 3d ..%..THREADSAFE= +| 3152: 30 58 52 54 52 49 4d 1f 21 05 00 33 0f 19 4f 4d 0XRTRIM.!..3..OM +| 3168: 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 4f IT LOAD EXTENSIO +| 3184: 4e 58 42 49 4e 41 52 59 1f 20 05 00 33 0f 19 4f NXBINARY. ..3..O +| 3200: 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 MIT LOAD EXTENSI +| 3216: 4f 4e 58 4e 4f 43 41 53 45 1e 1f 05 00 33 0f 17 ONXNOCASE....3.. +| 3232: 4f 4d 59 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 OMYT LOAD EXTENS +| 3248: 49 4f 4e 58 52 54 52 49 4d 1f 1e 05 00 33 0f 19 IONXRTRIM....3.. +| 3264: 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 30 MAX MEMORY=50000 +| 3280: 30 30 30 57 42 49 4e 41 52 59 1f 1d 05 00 33 0f 000WBINARY....3. +| 3296: 19 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 .MAX MEMORY=5000 +| 3312: 30 30 30 30 58 4e 4f 43 41 53 45 1e 1c 05 00 33 0000XNOCASE....3 +| 3328: 0f 17 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 ..MAX MEMORY=500 +| 3344: 30 30 30 30 30 58 52 54 52 49 4d 18 1b 05 00 25 00000XRTRIM....% +| 3360: 0f 19 45 4e 41 42 4c 45 20 52 54 52 45 45 58 42 ..ENABLE RTREEXB +| 3376: 49 4e 41 52 59 18 1a 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3392: 4c 45 20 52 54 52 45 45 59 4e 4f 43 41 53 45 17 LE RTREEYNOCASE. +| 3408: 19 05 00 25 0f 17 45 4e 41 42 4c 45 20 52 54 52 ...%..ENABLE RTR +| 3424: 45 45 58 52 54 52 49 4d 1a 18 05 00 29 0f 19 45 EEXRTRIM....)..E +| 3440: 4e 41 42 4c 45 20 4d 45 4d 53 59 53 35 58 42 49 NABLE MEMSYS5XBI +| 3456: 4e 41 52 59 1a 17 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3472: 45 20 4d 45 4d 53 59 53 35 58 4e 4f 43 41 53 45 E MEMSYS5XNOCASE +| 3488: 19 16 05 00 29 0f 17 45 4e 41 42 4c 45 20 4d 45 ....)..ENABLE ME +| 3504: 4d 53 59 53 35 58 52 54 52 49 4d 18 15 05 00 25 MSYS5XRTRIM....% +| 3520: 0f 19 45 4e 41 42 4c 45 20 4a 53 4f 4e 31 58 42 ..ENABLE JSON1XB +| 3536: 49 4e 41 52 59 18 14 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3552: 4c 45 20 4a 53 4f 4e 31 58 4e 4f 43 41 53 45 17 LE JSON1XNOCASE. +| 3568: 13 05 00 25 0f 17 45 4e 41 42 4c 45 20 4a 53 4f ...%..ENABLE JSO +| 3584: 4e 31 58 52 54 52 49 4d 1a 12 05 00 29 0f 19 45 N1XRTRIM....)..E +| 3600: 4e 41 42 4c 45 20 47 45 4f 50 4f 4c 59 58 42 49 NABLE GEOPOLYXBI +| 3616: 4e 41 52 59 1a 11 05 00 29 0f 19 45 4f 41 42 4c NARY....)..EOABL +| 3632: 45 20 47 45 4f 50 4f 4c 59 58 4e 4f 43 51 53 45 E GEOPOLYXNOCQSE +| 3648: 19 10 05 00 29 0f 17 45 4e 41 42 4c 45 20 47 45 ....)..ENABLE GE +| 3664: 4f 50 4f 4c 59 58 52 54 52 49 4d 17 0f 05 00 23 OPOLYXRTRIM....# +| 3680: 0f 19 45 4e 41 42 4c 45 20 46 54 53 35 58 42 49 ..ENABLE FTS5XBI +| 3696: 4e 41 52 59 17 0e 05 00 23 0f 19 45 4e 41 42 4c NARY....#..ENABL +| 3712: 45 20 46 54 53 35 58 4e 4f 43 41 53 45 16 1d 05 E FTS5XNOCASE... +| 3728: 00 23 0f a4 45 4e 41 42 4c 45 20 46 54 53 35 58 .#..ENABLE FTS5X +| 3744: 52 54 52 49 4d 17 0c 05 00 23 0f 19 45 4e 41 42 RTRIM....#..ENAB +| 3760: 4c 45 20 46 55 53 34 58 42 49 4e 41 52 59 17 0b LE FUS4XBINARY.. +| 3776: 05 00 23 0f 19 45 4e 41 42 4c 45 20 46 54 53 34 ..#..ENABLE FTS4 +| 3792: 58 4e 4f 43 41 53 45 16 0a 05 00 23 0f 17 45 4e XNOCASE....#..EN +| 3808: 41 42 4c 45 20 46 54 53 34 58 52 54 52 49 4d 1e ABLE FTS4XRTRIM. +| 3824: 09 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3840: 54 41 54 20 56 54 41 42 58 42 49 4e 41 52 59 1e TAT VTABXBINARY. +| 3856: 08 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3872: 54 41 54 20 56 54 41 42 58 4e 4f 43 41 53 45 1d TAT VTABXNOCASE. +| 3888: 07 05 00 31 0f 17 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3904: 54 41 54 20 56 54 41 42 58 52 54 52 49 4d 11 06 TAT VTABXRTRIM.. +| 3920: 05 00 17 0f 19 44 45 42 55 47 58 42 49 4e 41 52 .....DEBUGXBINAR +| 3936: 59 11 05 05 00 17 0f 19 44 45 42 55 47 58 4e 4f Y.......DEBUGXNO +| 3952: 43 41 53 45 10 04 05 00 17 0f 17 44 45 42 55 47 CASE.......DEBUG +| 3968: 58 52 54 52 49 4d 27 03 05 00 43 0f 19 43 4f 4d XRTRIM'...C..COM +| 3984: 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e 30 20 PILER=gcc-5.4.0 +| 4000: 32 30 31 36 30 36 30 39 58 42 49 4e 41 52 59 27 20160609XBINARY' +| 4016: 02 05 00 43 0f 19 43 4f 4d 50 49 4c 45 52 3d 67 ...C..COMPILER=g +| 4032: 63 63 2d 35 2e 34 2e 30 20 32 30 31 36 30 36 30 cc-5.4.0 2016060 +| 4048: 39 58 4e 4f 43 41 53 45 26 01 05 00 43 0f 17 43 9XNOCASE&...C..C +| 4064: 4f 4d 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e OMPILER=gcc-5.4. +| 4080: 30 20 32 30 31 36 30 36 30 39 58 52 54 53 49 4d 0 20160609XRTSIM +| page 3 offset 8192 +| 0: 05 00 00 00 07 0f ba 00 00 00 00 1a 0f f6 0f ec ................ +| 16: 0f e2 0f d8 0f ce 0f c4 0f ba 00 00 00 00 00 00 ................ +| 3200: 00 00 00 00 00 00 00 00 00 00 08 01 03 00 16 2e ................ +| 3216: b1 7d 24 24 86 4a 84 80 80 80 80 01 04 00 8d 18 ..$$.J.......... +| 3232: 00 00 03 2b 02 30 30 01 02 06 01 02 06 01 02 06 ...+.00......... +| 3248: 1f 02 03 01 02 03 01 02 03 01 08 32 30 31 36 30 ...........20160 +| 3264: 36 30 39 01 02 07 01 02 07 01 02 07 01 01 34 00 609...........4. +| 3280: 02 05 01 02 05 01 02 05 01 01 35 01 02 04 01 02 ..........5..... +| 3296: 04 01 02 04 02 07 30 30 30 30 30 30 30 1c 02 3d ......0000000..= +| 3312: 01 02 04 01 02 04 01 06 62 69 6e 61 72 79 03 06 ........binary.. +| 3328: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3344: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3360: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3376: 03 06 01 02 02 03 06 01 02 02 01 08 63 6f 6d 70 ............comp +| 3392: 69 6c 65 72 01 02 02 01 02 02 01 02 02 01 06 64 iler...........d +| 3408: 62 73 74 61 74 07 02 03 01 02 03 01 02 03 02 04 bstat........... +| 3424: 65 62 75 67 04 02 02 01 02 02 01 02 02 01 07 65 ebug...........e +| 3440: 6e 61 62 6c 65 07 02 02 01 02 02 01 d3 02 01 02 nable........... +| 3456: 02 01 02 02 02 02 02 01 02 02 01 02 02 01 02 02 ................ +| 3472: 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 ................ +| 3488: 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 ................ +| 3504: 02 01 02 02 02 08 78 74 65 6e 73 69 6f 6e 1f 02 ......xtension.. +| 3520: 04 01 02 04 01 02 04 01 04 66 74 73 34 0a 02 03 .........fts4... +| 3536: 01 02 03 01 02 03 04 01 35 0d 02 03 01 02 03 01 ........5....... +| 3552: 02 03 01 03 67 63 63 01 02 54 01 02 03 01 02 03 ....gcc..T...... +| 3568: 02 06 65 6f 70 6f 6c 79 10 02 03 01 02 03 01 02 ..eopoly........ +| 3584: 03 01 05 6a 73 6f 6e 31 13 02 03 01 02 03 01 02 ...json1........ +| 3600: 03 01 04 6c 6f 61 64 1f 02 03 01 02 03 01 02 03 ...load......... +| 3616: 01 03 6d 61 78 1c 02 02 01 02 02 01 02 02 02 05 ..max........... +| 3632: 65 6d 6f 72 79 1c 02 03 01 02 03 01 02 03 04 04 emory........... +| 3648: 73 79 73 35 16 02 03 01 02 03 01 02 03 01 06 6e sys5...........n +| 3664: 6f 63 61 73 65 02 06 01 02 02 03 06 01 02 02 03 ocase........... +| 3680: 06 01 02 02 13 06 01 02 02 03 06 01 02 02 03 06 ................ +| 3696: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3712: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3728: 02 01 04 6f 6d 69 74 1f 02 02 01 02 02 01 02 02 ...omit......... +| 3744: 01 05 72 74 72 65 65 19 02 03 01 02 03 01 02 03 ..rtree......... +| 3760: 04 02 69 6d 01 06 01 02 02 03 06 01 02 02 03 06 ..im............ +| 3776: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3792: 02 01 f3 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3808: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 08 ................ +| 3824: 01 0a 74 68 72 65 61 64 73 61 66 65 22 02 02 01 ..threadsafe.... +| 3840: 02 02 01 02 02 01 04 76 74 61 62 07 02 04 01 02 .......vtab..... +| 3856: 04 01 02 04 01 01 78 01 06 01 01 02 01 06 01 01 ......x......... +| 3872: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 ................ +| 3888: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3904: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 3920: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 3936: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 3952: 02 01 06 01 01 02 01 06 01 01 02 ad 06 01 01 02 ................ +| 3968: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3984: 06 01 01 01 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 4000: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 4016: 01 02 01 06 01 01 02 01 06 01 00 00 00 19 88 80 ................ +| 4032: 80 80 80 06 00 00 00 18 88 80 80 80 80 05 00 00 ................ +| 4048: 00 17 88 80 80 80 80 04 00 00 00 16 88 80 80 80 ................ +| 4064: 80 03 00 00 00 15 88 80 80 80 80 02 00 00 00 14 ................ +| 4080: 88 80 80 80 80 01 00 00 00 13 84 80 80 80 80 01 ................ +| page 4 offset 12288 +| 0: 0a 00 00 00 08 0e bd 00 00 00 0e f9 0e ef 0e e5 ................ +| 16: 0e db 0e d1 0e c7 0e bd 00 00 00 00 00 00 00 00 ................ +| 3760: 00 00 00 00 00 00 00 00 00 00 00 00 00 09 04 01 ................ +| 3776: 12 01 02 30 f4 a3 0e 09 04 01 12 01 02 30 cf 8c ...0.........0.. +| 3792: 0c 09 04 01 12 01 02 30 7a 34 0a 09 04 01 12 01 .......0z4...... +| 3808: 02 30 72 64 08 09 04 01 12 01 02 30 6b 30 06 09 .0rd.......0k0.. +| 3824: 04 01 12 01 02 30 63 33 04 06 04 01 0c 01 02 02 .....0c3........ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 5 offset 16384 +| 0: 05 00 00 00 0a 0f ce 00 00 00 00 12 0f fb 0f f6 ................ +| 16: 0f f1 0f ec 0f e7 0f e2 0f dd 0f d8 0f d3 0f ce ................ +| 32: 0e 8d 0e 74 0e 5b 0e 40 0e 24 0e 08 01 2f 0d d5 ...t.[.@.$.../.. +| 48: 0d bb 0d a0 0d 84 0d 68 0d 4f 0d 35 0d 1b 0c fb .......h.O.5.... +| 64: 0c da 0c b9 0c 99 0c 78 0c 57 0c 00 00 00 00 00 .......x.W...... +| 3072: 00 00 00 00 00 00 00 00 00 00 18 24 05 00 25 0f ...........$..%. +| 3088: 19 54 48 52 45 41 44 53 41 46 45 3d 30 58 42 49 .THREADSAFE=0XBI +| 3104: 4e 41 52 59 18 23 05 00 25 0f 19 54 48 52 45 41 NARY.#..%..THREA +| 3120: 44 53 41 46 45 3d 30 58 4e 4f 43 41 53 45 17 8f DSAFE=0XNOCASE.. +| 3136: 05 00 25 0f 17 54 48 52 45 41 44 53 41 46 45 3d ..%..THREADSAFE= +| 3152: 30 58 52 54 52 49 4d 1f 21 05 00 33 0f 19 4f 4d 0XRTRIM.!..3..OM +| 3168: 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 4f IT LOAD EXTENSIO +| 3184: 4e 58 42 49 4e 41 52 59 1f 20 05 00 33 0f 19 4f NXBINARY. ..3..O +| 3200: 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 MIT LOAD EXTENSI +| 3216: 4f 4e 58 4e 4f 43 41 53 45 1e 1f 05 00 33 0f 17 ONXNOCASE....3.. +| 3232: 4f 4d 59 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 OMYT LOAD EXTENS +| 3248: 49 4f 4e 58 52 54 52 49 4d 1f 1e 05 00 33 0f 19 IONXRTRIM....3.. +| 3264: 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 30 MAX MEMORY=50000 +| 3280: 30 30 30 57 42 49 4e 41 52 59 1f 1d 05 00 33 0f 000WBINARY....3. +| 3296: 19 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 .MAX MEMORY=5000 +| 3312: 30 30 30 30 58 4e 4f 43 41 53 45 1e 1c 05 00 33 0000XNOCASE....3 +| 3328: 0f 17 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 ..MAX MEMORY=500 +| 3344: 30 30 30 30 30 58 52 54 52 49 4d 18 1b 05 00 25 00000XRTRIM....% +| 3360: 0f 19 45 4e 41 42 4c 45 20 52 54 52 45 45 58 42 ..ENABLE RTREEXB +| 3376: 49 4e 41 52 59 18 1a 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3392: 4c 45 20 52 54 52 45 45 59 4e 4f 43 41 53 45 17 LE RTREEYNOCASE. +| 3408: 19 05 00 25 0f 17 45 4e 41 42 4c 45 20 52 54 52 ...%..ENABLE RTR +| 3424: 45 45 58 52 54 52 49 4d 1a 18 05 00 29 0f 19 45 EEXRTRIM....)..E +| 3440: 4e 41 42 4c 45 20 4d 45 4d 53 59 53 35 58 42 49 NABLE MEMSYS5XBI +| 3456: 4e 41 52 59 1a 17 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3472: 45 20 4d 45 4d 53 59 53 35 58 4e 4f 43 41 53 45 E MEMSYS5XNOCASE +| 3488: 19 16 05 00 29 0f 17 45 4e 41 42 4c 45 20 4d 45 ....)..ENABLE ME +| 3504: 4d 53 59 53 35 58 52 54 52 49 4d 18 15 05 00 25 MSYS5XRTRIM....% +| 3520: 0f 19 45 4e 41 42 4c 45 20 4a 53 4f 4e 31 58 42 ..ENABLE JSON1XB +| 3536: 49 4e 41 52 59 18 14 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3552: 4c 45 20 4a 53 4f 4e 31 58 4e 4f 43 41 53 45 17 LE JSON1XNOCASE. +| 3568: 13 05 00 25 0f 17 45 4e 41 42 4c 45 20 4a 53 4f ...%..ENABLE JSO +| 3584: 4e 31 58 52 54 52 49 4d 1a 12 05 00 29 0f 19 45 N1XRTRIM....)..E +| 3600: 4e 41 42 4c 45 20 47 45 4f 50 4f 4c 59 58 42 49 NABLE GEOPOLYXBI +| 3616: 4e 41 52 59 1a 11 05 00 29 0f 19 45 4f 41 42 4c NARY....)..EOABL +| 3632: 45 20 47 45 4f 50 4f 4c 59 58 4e 4f 43 51 53 45 E GEOPOLYXNOCQSE +| 3648: 19 10 05 00 29 0f 17 45 4e 41 42 4c 45 20 47 45 ....)..ENABLE GE +| 3664: 4f 50 4f 4c 59 58 52 54 52 49 4d 17 0f 05 00 23 OPOLYXRTRIM....# +| 3680: 0f 19 45 4e 41 42 4c 45 20 46 54 53 35 58 42 49 ..ENABLE FTS5XBI +| 3696: 4e 41 52 59 17 0e 05 00 23 0f 19 45 4e 41 42 4c NARY....#..ENABL +| 3712: 45 20 46 54 53 35 58 4e 4f 43 41 53 45 16 1d 05 E FTS5XNOCASE... +| 3728: 00 23 0f a4 45 4e 41 42 4c 45 20 46 54 53 35 58 .#..ENABLE FTS5X +| 3744: 52 54 52 49 4d 17 0c 05 00 23 0f 19 45 4e 41 42 RTRIM....#..ENAB +| 3760: 4c 45 20 46 55 53 34 58 42 49 4e 41 52 59 17 0b LE FUS4XBINARY.. +| 3776: 05 00 23 0f 19 45 4e 41 42 4c 45 20 46 54 53 34 ..#..ENABLE FTS4 +| 3792: 58 4e 4f 43 41 53 45 16 0a 05 00 23 0f 17 45 4e XNOCASE....#..EN +| 3808: 41 42 4c 45 20 46 54 53 34 58 52 54 52 49 4d 1e ABLE FTS4XRTRIM. +| 3824: 09 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3840: 54 41 54 20 56 54 41 42 58 42 49 4e 41 52 59 1e TAT VTABXBINARY. +| 3856: 08 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3872: 54 41 54 20 56 54 41 42 58 4e 4f 43 41 53 45 1d TAT VTABXNOCASE. +| 3888: 07 05 00 31 0f 17 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3904: 54 41 54 20 56 54 41 42 58 52 54 52 49 4d 11 06 TAT VTABXRTRIM.. +| 3920: 05 00 17 0f 19 44 45 42 55 47 58 42 49 4e 41 52 .....DEBUGXBINAR +| 3936: 59 11 05 05 00 17 0f 19 44 45 42 55 47 58 4e 4f Y.......DEBUGXNO +| 3952: 43 41 53 45 10 04 05 00 17 0f 17 44 45 42 55 47 CASE.......DEBUG +| 3968: 58 52 54 52 49 4d 27 03 05 00 43 0f 19 43 4f 4d XRTRIM'...C..COM +| 3984: 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e 30 20 PILER=gcc-5.4.0 +| 4000: 32 30 31 36 30 36 30 39 58 42 49 4e 41 52 59 27 20160609XBINARY' +| 4016: 02 05 00 43 0f 19 43 4f 4d 50 49 4c 45 52 3d 67 ...C..COMPILER=g +| 4032: 63 63 2d 35 2e 34 2e 30 20 32 30 31 36 30 00 00 cc-5.4.0 20160.. +| 4048: 00 11 09 00 00 00 10 08 00 00 00 0f 07 00 00 00 ................ +| 4064: 0e 06 00 00 00 0d 05 00 00 00 0c 04 00 00 00 0b ................ +| 4080: 03 00 00 00 0a 02 00 00 00 09 01 00 00 00 02 00 ................ +| page 6 offset 20480 +| 0: 0d 0f b0 00 25 0e bc 03 0e d7 0e ce 0f f0 0e c5 ....%........... +| 16: 0f e7 0f de 0f d5 0f cc 0f c3 0e bc 0f b7 0f a8 ................ +| 32: 0f a0 0f 98 0f 90 0f 88 0f 80 0f 78 0f 70 0f 68 ...........x.p.h +| 48: 0f 60 0f 58 0f 50 0f 48 0f 40 0f 38 0f 30 0f 28 .`.X.P.H.@.8.0.( +| 64: 0f 20 0f 18 0f 10 0f 08 0f 00 0e f8 0e 00 00 00 . .............. +| 3760: 00 00 00 00 00 00 00 00 00 00 00 00 07 09 03 00 ................ +| 3776: 14 84 6b 00 00 07 03 03 00 14 84 5b 00 00 07 02 ..k........[.... +| 3792: 03 00 14 84 63 00 00 07 01 03 00 14 85 07 00 00 ....c........... +| 3808: 06 24 03 00 12 02 01 01 06 23 03 00 12 02 01 01 .$.......#...... +| 3824: 06 22 03 00 12 02 01 01 06 21 03 00 12 03 01 01 .........!...... +| 3840: 06 20 03 00 12 03 01 01 06 1f 03 00 12 03 01 01 . .............. +| 3856: 06 1e 03 00 12 03 01 01 06 1d 03 00 12 03 01 01 ................ +| 3872: 06 1c 03 00 12 03 01 01 06 1b 03 00 12 02 01 01 ................ +| 3888: 06 1a 03 00 12 02 01 01 06 19 03 00 12 02 01 01 ................ +| 3904: 06 18 03 00 12 02 01 01 06 17 03 00 12 02 01 01 ................ +| 3920: 06 16 03 00 12 02 01 01 06 15 03 00 12 02 01 01 ................ +| 3936: 06 14 03 00 12 02 01 01 06 13 03 00 12 02 01 01 ................ +| 3952: 06 12 03 00 12 02 01 01 06 11 03 00 12 02 01 01 ................ +| 3968: 06 00 03 00 12 02 01 01 06 0f 03 00 12 02 01 01 ................ +| 3984: 06 0e 03 00 12 02 01 01 06 0d 03 00 12 02 01 01 ................ +| 4000: 06 0c 03 00 12 02 01 01 06 0b 03 00 12 02 01 01 ................ +| 4016: 0f f8 00 07 12 02 01 07 0a 03 00 14 85 05 00 00 ................ +| 4032: 0f f8 00 07 08 03 00 14 84 73 00 00 07 07 03 00 .........s...... +| 4048: 14 85 0b 00 00 07 06 03 00 14 85 02 00 00 07 05 ................ +| 4064: 03 00 14 84 70 00 00 07 04 03 00 14 84 7e 00 00 ....p........~.. +| 4080: 06 de 03 00 12 06 01 01 00 00 00 08 12 06 01 01 ................ +| page 7 offset 24576 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| page 8 offset 28672 +| 0: 0d 00 00 00 03 0f d6 00 0f f4 0f e9 0f d6 00 01 ................ +| 4048: 00 00 00 00 00 00 11 03 02 2b 69 6e 74 65 67 72 .........+integr +| 4064: 69 74 79 2d 63 68 65 63 6b 09 02 02 1b 72 65 62 ity-check....reb +| 4080: 75 69 6c 64 0a 01 02 1d 6f 70 74 69 00 00 00 00 uild....opti.... +| page 9 offset 32768 +| 0: 0d 00 00 00 01 04 3f 00 04 3f 00 00 00 00 00 00 ......?..?...... +| 1072: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 97 ................ +| 1088: 3e 01 06 00 ae 7c 00 00 ee a4 ad af cf 26 bc c7 >....|.......&.. +| 1104: a8 a1 59 d7 43 c2 93 4f ad 82 19 2e 25 e1 97 f9 ..Y.C..O....%... +| 1120: 21 59 34 ce e8 b2 e2 3c 4b f2 b4 ee 6b 35 84 c3 !Y4.....@..r... +| 1792: 8d 30 50 e6 54 9d 62 0d 11 b0 80 ad 64 44 7f 82 .0P.T.b.....dD.. +| 1808: 9a 61 c7 d5 f1 6b 34 2b ec bf 1a ee 5f bf 10 fe .a...k4+...._... +| 1824: 0f 9e 42 3d 34 1e ff 00 82 db 52 e0 65 d1 d7 0d ..B=4.....R.e... +| 1840: 77 fb 84 5c ec c3 b7 72 cf 21 26 40 85 20 f4 78 w......r.!&@. .x +| 1856: 6a f2 f4 52 8a c8 cd d9 99 73 8f 65 f5 f5 a8 af j..R.....s.e.... +| 1872: 75 bd a0 c6 ff 1a bf 64 53 01 4e ed be 22 95 1a u......dS.N..... +| 1888: 57 06 21 1d f3 f3 97 4f c7 de 83 ab 80 40 c4 02 W.!....O.....@.. +| 1904: 12 65 0d d5 55 87 6f 33 a1 b9 45 86 56 aa df fc .e..U.o3..E.V... +| 1920: 24 e6 70 37 c3 e8 bc b6 b6 e1 02 29 dc ba 7e 16 $.p7.......)..~. +| 1936: e9 52 3b 9d e7 f8 d9 d1 5c b2 db 4c 18 29 56 80 .R;........L.)V. +| 1952: f0 f1 b4 07 34 30 2c 2f ee f6 a5 80 f7 a0 b6 7c ....40,/.......| +| 1968: 43 e7 6f e2 a9 87 21 a2 96 82 d7 17 55 4d 33 ff C.o...!.....UM3. +| 1984: 78 75 40 fe db de 63 c3 b5 45 c8 37 40 97 f8 68 xu@...c..E.7@..h +| 2000: b3 03 46 2d 50 1e 2b b0 90 23 09 47 f5 05 db 9c ..F-P.+..#.G.... +| 2016: 23 71 b1 12 e4 b2 ff 41 b4 1f e3 bb 3c 50 d2 ab #q.....A...... +| 2096: 24 2d b9 ba ec 3d e0 88 60 b2 9d 02 07 86 97 13 $-...=..`....... +| 2112: e9 c5 64 c8 0f 03 e1 1b 12 0b ff 39 e1 49 95 fa ..d........9.I.. +| 2128: 6f c3 63 4a 7c 86 38 66 95 b3 57 0b 95 96 c5 e5 o.cJ|.8f..W..... +| 2144: 55 3b 4b 58 d2 59 89 fa 70 40 b5 59 a1 b9 9e 46 U;KX.Y..p@.Y...F +| 2160: ec 3a 5a c8 6d fb ac da d0 62 f6 fb 8c 26 ff 3e .:Z.m....b...&.> +| 2176: fb 54 69 ca 27 4b b5 a3 d1 27 f4 f5 2e d4 26 5a .Ti.'K...'....&Z +| 2192: 0b 3e d6 f4 11 b9 ae fd 42 65 08 6f c1 83 51 1a .>......Be.o..Q. +| 2208: 98 bc ad a9 77 8c da ef 70 dd 9b 6e e6 89 a0 75 ....w...p..n...u +| 2224: 5b c5 14 70 9c 3c 8d 99 b5 83 59 1e 77 0f 16 5e [..p.<....Y.w..^ +| 2240: 38 44 b9 da 9c c0 f0 61 23 5a 67 0e 43 48 81 ff 8D.....a#Zg.CH.. +| 2256: 50 9b 2a 36 d1 18 7e bb d2 4e 83 45 07 f8 35 5f P.*6..~..N.E..5_ +| 2272: a4 8a af 12 ab 91 3e 4f 4f 40 32 3f 7e fc 06 6a ......>OO@2?~..j +| 2288: c4 de 5e aa 2a a5 21 ba 14 51 28 65 c9 9e ef 7c ..^.*.!..Q(e...| +| 2304: 6a 65 67 fc 11 23 1d 76 e3 be e3 10 7a 34 b8 ef jeg..#.v....z4.. +| 2320: 37 b8 ba 9c 1a 13 c7 e1 91 c9 06 ad 98 3c 4b ea 7................ +| 2576: 93 1b 7c 28 fb 53 bc b2 13 2d 8e 22 50 97 4c cf ..|(.S...-..P.L. +| 2592: 06 f1 ac 55 9d c8 ce cd 59 74 9c af 7b bd 6f 7c ...U....Yt....o| +| 2608: a5 b3 a4 87 6a 67 22 f1 82 5b 3e 9e 76 b0 2f d6 ....jg...[>.v./. +| 2624: f6 6d 7a c0 f5 8f 06 c2 5b 09 0f b5 df b9 d7 c2 .mz.....[....... +| 2640: be 51 e0 5e 5a 0a 79 62 3a 3c c8 a1 50 0d a3 36 .Q.^Z.yb:<..P..6 +| 2656: e1 7d 61 4a 1b af f0 42 20 dc 10 a9 13 3d 2b 46 ..aJ...B ....=+F +| 2672: 16 98 d1 24 a8 a1 1a c8 88 0d 63 23 2b 23 b3 8d ...$......c#+#.. +| 2688: 19 13 f9 4c 76 0b 48 40 bb 02 db 1a 54 e8 ea f4 ...Lv.H@....T... +| 2704: f4 57 25 0d 50 1d 31 af 74 00 d8 f4 22 d9 53 e8 .W%.P.1.t.....S. +| 2720: 35 ae 1c c6 82 18 06 4f b6 f3 e8 2c 15 1c 38 1c 5......O...,..8. +| 2736: 5c 47 24 f4 49 44 ef a6 cc de 85 0d 61 aa f6 4f .G$.ID......a..O +| 2752: 18 4b 23 7e ca dd 04 7a 7f 6c d0 70 59 05 0f 31 .K#~...z.l.pY..1 +| 2768: de 19 71 96 7a 1b 93 a2 20 18 d6 a6 f2 8d 28 1d ..q.z... .....(. +| 2784: c0 f3 a9 20 87 f7 dc 10 eb bf bc 1e cf 7a 54 a2 ... .........zT. +| 2800: 7f 96 0d 32 a9 30 30 5c 6d 31 3c 76 4d 65 1f d0 ...2.00.m1..=......USJ +| 2928: 53 80 b1 9e 23 83 49 1a e0 22 9f 5c f3 df 87 d8 S...#.I......... +| 2944: 6f 45 ff 9b 8a 69 80 95 f8 ca bc 5c 0c b1 46 04 oE...i........F. +| 2960: 7e d7 61 72 d8 bf a1 b6 bc b3 48 c7 c2 b3 9d 7c ~.ar......H....| +| 2976: de fb a4 0a 04 2f 97 99 eb ca 8c b5 39 fc 2b 45 ...../......9.+E +| 2992: 69 7f a9 70 c6 73 a1 22 71 b0 0d 53 0c f4 c7 68 i..p.s..q..S...h +| 3008: 85 ec 11 44 0b f9 b7 3b ff b7 91 1b fb bd bf e1 ...D...;........ +| 3024: 01 90 4c 74 35 f3 ac 5a 70 bd e1 4e bb fd a8 dc ..Lt5..Zp..N.... +| 3040: 4d 38 c4 68 a8 e1 8f d1 69 b8 7a 20 b6 5c ed 2b M8.h....i.z ...+ +| 3056: e8 20 dc 0f 7e 29 3e 5f 83 76 e2 d9 b1 c0 07 66 . ..~)>_.v.....f +| 3072: cd a2 d4 57 bc 27 ff 1d 16 7a 11 d6 3d df 04 89 ...W.'...z..=... +| 3088: 45 91 e5 37 62 51 5a e6 0f 0d b8 e9 1a 43 e4 c3 E..7bQZ......C.. +| 3104: 47 94 bf 16 fb 8c e2 f7 b8 c8 7f 7f 34 3b a3 fa G...........4;.. +| 3120: 4f 39 b3 89 ee 7a 1b 80 a6 04 46 24 7f 0c d2 48 O9...z....F$...H +| 3136: b4 cb a2 df 5c af 55 11 a5 c6 f6 de 6a a4 0f dc ......U.....j... +| 3152: ae 12 98 11 9e 95 e2 b1 a6 c2 67 bb 37 ea e8 8e ..........g.7... +| 3168: d1 6f 6c 7a 3a 13 5e 1c 31 92 7e 24 72 b0 f5 b6 .olz:.^.1.~$r... +| 3184: f5 db 3e b9 3b 2a 18 da 93 29 da 2c be 4a de c6 ..>.;*...).,.J.. +| 3200: 6c 55 a0 3e 47 25 76 5c 73 08 99 17 87 e9 30 0e lU.>G%v.s.....0. +| 3216: 91 a8 cd da 9a cd 90 8b 2d 5f 0e 88 7c a7 00 42 ........-_..|..B +| 3232: 84 bd 59 1b ce fa 76 27 33 78 c1 a4 0d 29 98 45 ..Y...v'3x...).E +| 3248: d1 7f b6 7d 56 f6 67 fe 78 ae 83 03 39 66 ce 5a ....V.g.x...9f.Z +| 3264: 62 a1 e3 c5 fd 29 53 06 6e cd ff 0e 5a 95 ca 91 b....)S.n...Z... +| 3280: 1b 24 0d 42 ec e2 24 8a 01 ff 12 0b bf b1 18 74 .$.B..$........t +| 3296: 77 2d f5 9e 0a 74 e6 d4 7d 1c c1 53 d9 f5 65 9c w-...t.....S..e. +| 3312: 40 6d 8f a9 f6 7b b0 96 37 71 c2 96 8c 90 f8 29 @m......7q.....) +| 3328: 07 10 c7 2d f5 1d 80 dc 96 b7 25 65 a6 a2 ff ba ...-......%e.... +| 3344: 5d 1e c1 0d ed b1 6a 83 20 6d 06 28 6d 54 8c 88 ].....j. m.(mT.. +| 3360: 08 02 3d cf f6 79 81 f1 36 3b f0 6e e6 80 39 43 ..=..y..6;.n..9C +| 3376: 64 d6 4a 24 8b 3d 21 41 a9 48 d2 36 65 2f 5a 71 d.J$.=!A.H.6e/Zq +| 3392: eb 6f 2b 47 78 2d 8c 28 91 60 25 3c 35 81 5b 1d .o+Gx-.(.`%<5.[. +| 3408: b7 36 34 71 4c 38 f2 29 7e f5 a8 45 71 95 78 19 .64qL8.)~..Eq.x. +| 3424: 00 e8 87 4d da 50 78 5e f7 dc aa 2d 15 92 49 d8 ...M.Px^...-..I. +| 3440: 4e af 77 30 bd ad 22 1b 6a 84 ff 78 6d 37 cf 1b N.w0....j..xm7.. +| 3456: 8a d9 81 dd 34 15 a7 3a c0 53 d6 ab 5a 38 ec 69 ....4..:.S..Z8.i +| 3472: 6a 88 64 8c a6 ce 50 12 45 a1 7f a2 aa 3a a8 cf j.d...P.E....:.. +| 3488: d6 a0 80 4e d6 7a b6 50 90 64 c0 52 30 51 04 6f ...N.z.P.d.R0Q.o +| 3504: 89 25 02 b3 54 0b fb 24 89 cf f7 98 bd 8e fc 9f .%..T..$........ +| 3520: 62 0c cd fd 57 fd ac 64 b9 2a 94 62 94 38 c6 01 b...W..d.*.b.8.. +| 3536: 0c f1 b9 75 f4 3c 5d 0e d4 1f 96 b3 74 3f 96 03 ...u.<].....t?.. +| 3552: 13 b6 76 32 07 e0 1f 82 d8 27 f3 e7 2e f4 60 d0 ..v2.....'....`. +| 3568: 56 a5 8f 04 37 bd 5c 17 1e 33 94 75 d8 30 59 0d V...7....3.u.0Y. +| 3584: e5 90 f5 09 ee 5c 01 88 14 ca 69 27 08 fa e7 3c ..........i'...< +| 3600: a2 69 df e2 be 35 44 96 b5 06 69 5c 01 3f 52 67 .i...5D...i..?Rg +| 3616: 18 d2 c9 64 a7 ba 0b 59 d8 b8 53 21 74 de 2b 21 ...d...Y..S!t.+! +| 3632: 8a 53 3d 97 14 92 77 ed 51 21 4b f0 2d 69 93 09 .S=...w.Q!K.-i.. +| 3648: 57 3e 92 9f 3e 20 6c 4d bf 8b fd 4f 75 4b 19 5d W>..> lM...OuK.] +| 3664: 48 ef 23 1e 53 11 ee 76 b7 04 08 5a c4 9a 1f 6c H.#.S..v...Z...l +| 3680: 24 cb 15 7f 0b f7 86 8e 60 a4 8d 3c 2a fe 14 13 $.......`..<*... +| 3696: 03 28 80 fa 6b d7 1b 02 a7 0d 9e 88 4d 1f b2 a4 .(..k.......M... +| 3712: 63 c7 65 56 14 df 51 7e d3 d4 3b e3 45 e1 7a 49 c.eV..Q~..;.E.zI +| 3728: 1e 71 40 fe b7 ae 65 10 b1 27 3a 02 31 21 47 11 .q@...e..':.1!G. +| 3744: d9 fc 9c 32 e5 c8 40 0d b6 4b 02 ed bc da 4c 98 ...2..@..K....L. +| 3760: 35 2c d5 9e 6f b3 42 c7 8e 0a c7 fa ae ff 36 5b 5,..o.B.......6[ +| 3776: 76 08 69 3e 3c cd 4d eb 6f 0c a0 f6 23 93 a6 bb v.i><.M.o...#... +| 3792: 2f ed 44 64 22 df e8 6b 21 68 5b 35 d6 8f 68 c5 /.Dd...k!h[5..h. +| 3808: 15 1f 46 fd 12 bc b5 b5 3e a7 e4 9b b2 83 f4 12 ..F.....>....... +| 3824: ea bb 50 84 f4 40 96 c4 64 30 d8 fe 74 5b f2 ba ..P..@..d0..t[.. +| 3840: 9a 64 23 67 4a 3d 7e 54 da 8f 39 18 df 31 88 23 .d#gJ=~T..9..1.# +| 3856: d6 80 5f e9 10 9f 37 22 6f 4a 21 13 20 13 fc 66 .._...7.oJ!. ..f +| 3872: fc 4b db a8 d9 aa 55 01 48 3e 8c ac bf 16 fc 62 .K....U.H>.....b +| 3888: 95 2c 44 1f 27 bf 7b 45 7d 28 55 14 1f ed 56 ed .,D.'..E.(U...V. +| 3904: 24 5b 11 ff be a0 7a 20 3b 3e 9c 2c e6 d6 b0 ef $[....z ;>.,.... +| 3920: b4 df 16 73 f2 d3 a9 90 2b 54 c7 7a fa 25 e7 ee ...s....+T.z.%.. +| 3936: da 99 8d d7 b5 7d 0f 72 c7 61 75 d1 d7 23 dd 41 .......r.au..#.A +| 3952: 1e 46 ee ef 41 86 00 9f 1c 47 36 75 95 f6 1d 89 .F..A....G6u.... +| 3968: 13 c0 75 f8 cc 5e 08 93 e4 de a8 ee d2 ce c2 32 ..u..^.........2 +| 3984: e4 16 b0 c8 82 c1 2a 74 ed 5c 5f c1 99 f7 07 a7 ......*t.._..... +| 4000: b3 50 21 87 a1 43 dc 17 4f 2d 47 e0 be 53 ad 17 .P!..C..O-G..S.. +| 4016: f9 09 67 d1 4f 1f 72 17 62 b7 03 fa cd de 3e a7 ..g.O.r.b.....>. +| 4032: 25 a9 e7 a0 e2 3d a3 6b 2b 34 3f 55 46 18 df ef %....=.k+4?UF... +| 4048: 16 0a ce c8 67 58 eb 64 eb 7e a3 5b 4e 85 49 64 ....gX.d.~.[N.Id +| 4064: d7 f9 ec 0d 4d b6 1d 49 bb 93 e9 79 3d e1 f9 ad ....M..I...y=... +| 4080: 6d c0 45 9c 46 26 21 41 10 dd 4a 12 fc 28 f4 cc m.E.F&!A..J..(.. +| page 10 offset 36864 +| 0: 0d 00 00 00 01 04 3f 00 04 3f 00 00 00 00 00 00 ......?..?...... +| 1072: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 97 ................ +| 1088: 3e 02 06 00 ae 7c 00 00 9a 84 85 31 87 79 cc e4 >....|.....1.y.. +| 1104: 12 98 6f 2c 46 b6 5d 4e d9 5c ba 21 2f cf 51 9d ..o,F.]N...!/.Q. +| 1120: 9f 8c 48 7d 5a 15 ae 1c 54 d7 f0 7f a2 e6 cc fd ..H.Z...T....... +| 1136: ad e6 3c d5 e5 98 5d f2 64 82 5b 9e 6c d6 05 8f ..<...].d.[.l... +| 1152: de fb 33 3a 37 b3 1d 58 04 47 13 cd d9 39 17 a9 ..3:7..X.G...9.. +| 1168: a7 14 f1 fe c8 3b 21 40 a8 22 f5 3b 64 74 14 dc .....;!@...;dt.. +| 1184: c5 9e 99 8b f3 d4 5f 1a e8 1d 93 98 9e 00 7d ee ......_......... +| 1200: 9f 0f 49 b5 af 12 7e 47 8e fe 5f 70 a1 9c 32 ae ..I...~G.._p..2. +| 1216: d1 23 33 d1 c0 65 b6 8e 5a 4a 03 2f 74 27 0f 3d .#3..e..ZJ./t'.= +| 1232: 3f 42 0d 03 5f 6c ef 2c 54 6a bd ec 88 eb cd 1f ?B.._l.,Tj...... +| 1248: dd 03 9c 06 18 8e a4 c1 a5 3c ec 9d 68 da d9 d3 .........<..h... +| 1264: d8 8f ba 16 95 fd 94 ed 19 2b 2f 5e 0e a5 fa de .........+/^.... +| 1280: dc c5 a0 a2 d4 31 0f dc f5 8b 4a 28 4f 6f 9f ff .....1....J(Oo.. +| 1296: 7f e6 d4 d0 08 7e 5c 76 9a 3a ea 0e ea b4 9d 45 .....~.v.:.....E +| 1312: 4b db 96 ed 27 ad a8 09 63 3c d0 1d d9 b1 dc 94 K...'...c<...... +| 1328: 6a 8e e7 6a 9b 6e d1 8b f7 c8 60 1c 85 e9 a2 3e j..j.n....`....> +| 1344: 0c b6 2f 40 b0 2e b6 53 8f 94 74 6b 39 13 fd c9 ../@...S..tk9... +| 1360: 44 77 64 7c 62 e0 51 2e 04 0f 6a 99 5e 68 6f 72 Dwd|b.Q...j.^hor +| 1376: 08 92 02 bf 36 78 0c 98 c3 3f 3d e3 ac a4 2d 3b ....6x...?=...-; +| 1392: d7 51 cf f9 25 72 59 36 b0 ea 51 a1 9c bf 13 f1 .Q..%rY6..Q..... +| 1408: dc f5 36 f0 d9 83 fe 0a ff b5 00 ab 5b 4e 0b 33 ..6.........[N.3 +| 1424: 0a a1 fa c1 02 c8 7a af 00 30 54 d3 6e a8 37 b4 ......z..0T.n.7. +| 1440: 02 88 16 41 95 86 1f 36 e0 98 43 d5 55 57 c6 5e ...A...6..C.UW.^ +| 1456: 0d 10 40 fd 3d 85 a1 5f 19 3f 18 87 11 d9 6a ca ..@.=.._.?....j. +| 1472: 8b 5c 14 cc 64 48 b9 42 71 61 30 ca 0d 2c a5 67 ....dH.Bqa0..,.g +| 1488: 6b 4f 5b 34 1b f3 7b a1 1f ed e4 3b ef 68 27 00 kO[4.......;.h'. +| 1504: f3 8a 27 ff de 2e 07 ab 03 b1 5b b9 c3 84 53 f1 ..'.......[...S. +| 1520: d0 c5 8c 65 50 4b 7a 35 06 0c d5 2a ce 4c b9 02 ...ePKz5...*.L.. +| 1536: 7f cf 2a d9 0d 2a 1c c1 5f 94 cb 5c 05 4d 3a 7a ..*..*.._....M:z +| 1552: aa c5 3e 4e 26 93 8a 4d 1d fc 75 23 9f b5 27 87 ..>N&..M..u#..'. +| 1568: fc f7 3d aa a6 3e 5d c3 55 63 e2 a8 2a 7b 2e 26 ..=..>].Uc..*..& +| 1584: e9 64 59 1f 2a e7 ff 7c d4 69 a6 34 bb d9 23 81 .dY.*..|.i.4..#. +| 1600: de 4b ba f3 91 cd 9a 8b 98 56 f0 b8 73 7b 83 f1 .K.......V..s... +| 1616: fc e9 5c 01 7e 74 66 d0 0c 01 79 c0 b2 49 0f 78 ....~tf...y..I.x +| 1632: 79 ef 20 96 ec cb 63 67 fe 43 a7 ea 5d af 68 12 y. ...cg.C..].h. +| 1648: 84 dc 0c 5c 31 6a 1d d6 a1 1e d4 25 a3 f0 7c 19 ....1j.....%..|. +| 1664: dc 84 4f 7b e4 5a e5 40 23 07 b3 5b 92 92 4b 3a ..O..Z.@#..[..K: +| 1680: 0e 11 9c b4 ba d4 d1 ff 22 af cd 7d e8 86 c3 0a ................ +| 1696: 14 eb 01 13 f8 99 56 8c c6 26 55 8d a7 cc cc 00 ......V..&U..... +| 1712: 4c 16 7a 07 de 6c 69 02 e2 a1 e6 e1 d5 ff 12 f5 L.z..li......... +| 1728: ee d7 8f 90 e1 78 99 09 de 27 b8 b3 e1 7a df 8a .....x...'...z.. +| 1744: 08 cb c0 67 ff d4 48 bd 0b 4c 65 56 5a ed 02 47 ...g..H..LeVZ..G +| 1760: 1c de b7 58 7c cf 68 a4 7f e6 db 74 26 55 9c 14 ...X|.h....t&U.. +| 1776: 76 5a bf f3 e1 79 23 5e 33 f8 65 13 bb 36 cc ed vZ...y#^3.e..6.. +| 1792: 9e 12 63 b2 c2 14 3f 6a 4d e3 a4 63 bf 30 0f cb ..c...?jM..c.0.. +| 1808: f2 f0 d1 81 a7 26 d7 c0 92 9f 06 79 bd a7 a0 8d .....&.....y.... +| 1824: 74 21 7b c6 46 49 5c fd 01 a6 92 56 3b eb e6 d5 t!..FI.....V;... +| 1840: b4 4d 76 2a e8 00 98 3b a3 67 a9 7d 02 25 96 3b .Mv*...;.g...%.; +| 1856: 45 19 a0 d4 b5 67 88 cf 48 eb 1a b6 0e f3 2d 62 E....g..H.....-b +| 1872: 3e 0c fa b3 e7 a4 f1 76 d4 d7 f5 19 6f b2 9e e8 >......v....o... +| 1888: e1 a1 b7 be 3a ff 69 db eb 75 1c 0c 91 7f 02 4f ....:.i..u.....O +| 1904: df 15 af 09 48 2c d0 86 bd b0 80 82 e7 7b 1d a6 ....H,.......... +| 1920: 38 f1 24 79 d0 8e 4c 07 9f ed 9f fb 90 a1 7e fd 8.$y..L.......~. +| 1936: 50 c6 fe d3 61 04 a1 03 34 30 bd 98 db 5c 18 e3 P...a...40...... +| 1952: 94 d9 ba 8f 64 f8 6b c1 21 3b 22 b7 3e 71 5a 66 ....d.k.!;..>qZf +| 1968: cf 9f 51 c4 a2 36 c8 ba c3 2a 95 2e 67 e4 87 3e ..Q..6...*..g..> +| 1984: 07 ae 66 ad ec af c6 17 62 11 be 5f 15 fc 61 53 ..f.....b.._..aS +| 2000: 76 eb f5 78 da 32 8c fa 4e d3 cc 27 19 dc 89 fc v..x.2..N..'.... +| 2016: 37 4f 74 61 f7 5e 97 0f fe fc af aa 12 ee ea f2 7Ota.^.......... +| 2032: 68 36 36 cd 7e 57 41 75 48 be cd 46 e3 cd 3a 99 h66.~WAuH..F..:. +| 2048: 31 33 9a 84 8a 83 a2 fc 85 85 3c bc cf 07 b4 6d 13........<....m +| 2064: 57 d2 c9 63 a2 9d 42 07 4e cd 65 2e 65 0a af 03 W..c..B.N.e.e... +| 2080: dc a9 98 57 b6 7f da 1d b7 3c e0 ef aa 18 eb 3f ...W.....<.....? +| 2096: 78 f1 34 e6 bf c7 28 34 11 a7 bc b3 34 79 f2 85 x.4...(4....4y.. +| 2112: ed 7d fe 38 a5 48 b3 b4 8a 0d 75 12 65 04 f8 88 ...8.H....u.e... +| 2128: 71 b9 d4 86 48 c7 ea 2f af 4d 50 b9 50 a7 17 f6 q...H../.MP.P... +| 2144: 1f a2 e1 b8 aa ed 6d 85 3a e2 91 be 94 c8 fc db ......m.:....... +| 2160: 93 50 0e 50 7c cf 52 f7 55 81 3e 1a 59 4d a8 36 .P.P|.R.U.>.YM.6 +| 2176: ee 07 f1 9e 26 4a 1e d3 0b 7d 52 e3 bc 7c 91 78 ....&J....R..|.x +| 2192: 02 48 7e b5 4c 32 1e a3 ba db 93 61 94 3b d7 22 .H~.L2.....a.;.. +| 2208: 7b cb 46 5b c0 a5 1e e6 5a ed c9 82 07 48 17 d6 ..F[....Z....H.. +| 2224: de 85 ca 47 e1 16 5c c8 dc 30 5e 27 65 60 a5 41 ...G.....0^'e`.A +| 2240: 46 0a 12 0e 97 63 3f 05 5f 62 83 e0 cb 72 b6 61 F....c?._b...r.a +| 2256: 6a 4c 1d 7b 09 28 75 54 1c cd 29 bc f8 71 34 56 jL...(uT..)..q4V +| 2272: d6 ac 77 34 44 0d 5f c3 0e f1 d7 b3 dc 0d 1f 85 ..w4D._......... +| 2288: e3 db 45 88 fd db ab bf a6 ff bc 08 f3 3c 00 f0 ..E..........<.. +| 2304: 9f 3b 07 36 cc 37 f0 6d 14 a8 6b 69 d6 e4 3a ab .;.6.7.m..ki..:. +| 2320: 4f 5a b5 ad 3a e5 e3 d6 1c e1 6b 15 97 69 b0 41 OZ..:.....k..i.A +| 2336: 91 09 50 02 6a 5c c1 9e fe e7 38 7e 19 8a 36 44 ..P.j.....8~..6D +| 2352: 51 04 8d 61 d9 a3 12 34 00 b2 b1 60 f3 35 eb 8a Q..a...4...`.5.. +| 2368: dc bb a1 67 48 b6 95 81 91 fe 20 dd 03 74 bb 3b ...gH..... ..t.; +| 2384: 8c 33 58 b2 d8 55 7e 04 ea 42 6c 02 e8 26 18 19 .3X..U~..Bl..&.. +| 2400: 23 15 21 a5 73 ca 08 7b dd db fb b2 12 df 6a 5a #.!.s.........jZ +| 2416: d7 ac e4 57 61 ac 3f 81 77 df f6 a3 97 5c 69 47 ...Wa.?.w.....iG +| 2432: 5a b7 75 23 6c 60 be 97 ee b5 5d a0 c3 60 15 4a Z.u#l`....]..`.J +| 2448: 79 eb 81 9f 2a 74 22 13 7e ca 4d 3b 19 13 62 58 y...*t..~.M;..bX +| 2464: 78 25 ca 21 c3 10 1e 96 34 82 37 6b 08 b5 9b f6 x%.!....4.7k.... +| 2480: 7b 87 97 cd bf 64 67 b4 10 f6 84 16 62 74 a5 b0 .....dg.....bt.. +| 2496: 1f d4 c7 1d 8c 2d b5 10 36 5b fb 06 80 fe 97 59 .....-..6[.....Y +| 2512: b6 5a 08 0f 1e ff 0f 5f bf 28 46 4b d8 84 6c ad .Z....._.(FK..l. +| 2528: 05 c0 25 89 a9 cd 6a be a3 59 84 f0 17 1c 37 8a ..%...j..Y....7. +| 2544: 8d 09 17 bf 7d fe 47 b4 d6 d6 56 1f d8 04 66 59 ......G...V...fY +| 2560: 2c c4 b4 91 a9 ff da 4a 9c b1 29 ff 92 db 6f 19 ,......J..)...o. +| 2576: ef eb 99 ba 6e 65 2f 6a 7f 1a cf ad a3 96 8c 1d ....ne/j........ +| 2592: 62 86 42 3e a3 64 fc e0 40 4c 7c 60 77 b5 42 68 b.B>.d..@L|`w.Bh +| 2608: 3f 09 37 68 02 75 2c 22 83 d5 04 17 eb a7 e2 71 ?.7h.u,........q +| 2624: 29 36 b7 1b c5 1f 11 ce 8d 91 5a 25 39 50 16 2b )6........Z%9P.+ +| 2640: 60 29 50 9f 17 55 b0 9b a5 92 92 f8 1b e3 9c a3 `)P..U.......... +| 2656: e2 a4 cd 90 1e 21 ac 30 ac 35 de 25 30 88 6c 2c .....!.0.5.%0.l, +| 2672: 79 ea b5 0d 58 a5 37 2b ac af 7d 1f af 32 ca 58 y...X.7+.....2.X +| 2688: 27 17 68 f2 a3 ca a6 cc b2 be 12 c1 a0 43 1e 7d '.h..........C.. +| 2704: 11 ec 8e 23 22 0a ca cd 70 d1 05 fd c0 68 92 e9 ...#....p....h.. +| 2720: 1c 55 85 48 10 37 7c 02 69 bf 3f 86 cf d6 40 38 .U.H.7|.i.?...@8 +| 2736: a3 9c 83 40 b8 4b 83 51 50 0d d2 b9 c3 32 09 f8 ...@.K.QP....2.. +| 2752: bc 7b 9b b8 d7 2e 4f a0 96 48 a7 5a 1b c9 71 fb ......O..H.Z..q. +| 2768: f4 2b ff 05 54 89 26 b9 6f 25 4a b9 e2 2b e8 86 .+..T.&.o%J..+.. +| 2784: 43 22 f6 20 28 a9 39 d9 09 a2 dc 60 14 09 d6 0d C.. (.9....`.... +| 2800: 61 7c 15 5a 8f 3f cc 00 12 f5 e0 45 fe 14 1a cc a|.Z.?.....E.... +| 2816: 98 c4 de 48 75 12 02 2b 79 a1 4a 33 a5 7c 3d cd ...Hu..+y.J3.|=. +| 2832: b0 5c dd 77 15 5f d9 24 b7 6b 62 80 cb 35 5c e6 ...w._.$.kb..5.. +| 2848: a6 57 2e e9 00 9e 20 a9 c6 f0 63 a2 0e eb 9d f3 .W.... ...c..... +| 2864: bb 2c 56 03 68 35 53 5a fb 4f 44 8e 0f a4 9c 9a .,V.h5SZ.OD..... +| 2880: 0d b7 2c a9 03 14 8c 51 23 21 fb fd 46 07 68 a7 ..,....Q#!..F.h. +| 2896: f3 09 25 e4 98 55 24 da 72 ee 50 00 95 04 7c 74 ..%..U$.r.P...|t +| 2912: d0 07 8b 92 f9 27 11 3e 41 b4 3e 6c aa 56 ed 54 .....'.>A.>l.V.T +| 2928: e3 40 4d 67 8b 7b 63 cd 62 37 ec e2 1b b4 f9 eb .@Mg..c.b7...... +| 2944: ca c7 6e 8a d3 7e f5 e9 e1 33 84 31 05 cb f8 e4 ..n..~...3.1.... +| 2960: 02 76 c2 2c b9 00 32 5f be b5 f1 c8 78 e8 cf 22 .v.,..2_....x... +| 2976: 65 d8 2b 43 2c 2e 5d fb 2e 58 92 d2 3e 9b 7e 67 e.+C,.]..X..>.~g +| 2992: a7 3b 59 18 84 5d 61 92 ec 57 97 b6 bd e3 7b f2 .;Y..]a..W...... +| 3008: ff 3b 9f 48 39 ad 10 81 51 85 7c 6b 5c b0 53 10 .;.H9...Q.|k..S. +| 3024: 60 9b 4e 0d 86 c0 31 b9 a1 95 05 f4 54 13 52 6a `.N...1.....T.Rj +| 3040: 96 50 1b e5 f5 54 98 63 8f 07 28 cf b4 ba 9c 3b .P...T.c..(....; +| 3056: dd 85 3c a6 15 a6 31 f6 aa 1c 3b 31 3e d9 6e c3 ..<...1...;1>.n. +| 3072: 09 74 b9 8e 59 a1 50 10 e0 f0 48 a9 f1 dd a1 ab .t..Y.P...H..... +| 3088: b6 fc 53 9f bb 38 69 78 07 50 8c 77 cb d1 89 e2 ..S..8ix.P.w.... +| 3104: cb b9 51 dc c7 2e 8a 56 47 14 67 80 eb 7a 16 f8 ..Q....VG.g..z.. +| 3120: 87 89 58 d1 55 58 c5 bd 62 24 4d ef 46 9f 68 94 ..X.UX..b$M.F.h. +| 3136: 61 9a 08 81 dd dd f1 51 c6 40 63 9b a1 3c 3e 5a a......Q.@c..<>Z +| 3152: 78 b2 10 7c a2 47 45 57 ef 98 85 82 7e 21 3b 77 x..|.GEW....~!;w +| 3168: 1e d2 fd 0d bf a5 41 6f f5 ee 4c ed 82 f3 15 ea ......Ao..L..... +| 3184: 2e 57 5e 78 7c 22 2e a4 a6 9c 3f 50 e9 4b 02 11 .W^x|.....?P.K.. +| 3200: a0 7c 5a 19 82 9b 34 13 39 32 07 f1 46 4c ad fa .|Z...4.92..FL.. +| 3216: a7 5a fa 22 5c 84 4d 8d 07 1f 17 1f 1a 49 5f fa .Z....M......I_. +| 3232: df 22 56 ac ce 27 4d 00 a9 b0 c0 77 d8 10 56 cb ..V..'M....w..V. +| 3248: 76 e0 c8 4a 8a d1 37 c1 cc b8 74 d3 e9 61 1b 0c v..J..7...t..a.. +| 3264: 81 7b be 3d e3 17 43 75 5c ba 6f 61 74 d4 a4 e9 ...=..Cu..oat... +| 3280: d7 0f 2f d2 bc 7d 0b 6e 32 ae fc 20 db 08 39 b5 ../....n2.. ..9. +| 3296: 8f 78 1f d6 4e 84 46 05 a3 a1 15 e8 9d 36 b1 96 .x..N.F......6.. +| 3312: 3e e3 12 1b 72 28 28 e8 a5 0f e1 16 a2 31 22 48 >...r((......1.H +| 3328: 4b 77 c6 e5 3f b8 f5 bb 29 13 e1 c1 da 79 81 72 Kw..?...)....y.r +| 3344: fa 12 ab 62 da 63 ac 95 2a 1b 4a a4 4f 96 95 c0 ...b.c..*.J.O... +| 3360: 30 38 3b af 2d c5 cd aa 56 ef e4 35 1d 97 9c 3f 08;.-...V..5...? +| 3376: 57 30 2a 7a 1a 0f 4d 9e be 82 a6 fc 5e a7 9d 0d W0*z..M.....^... +| 3392: a8 11 12 39 b5 e7 5f 8d 18 1d bb a0 2b 02 10 d3 ...9.._.....+... +| 3408: 08 3a 47 de a1 93 f3 7b b7 07 21 c2 62 22 f2 b3 .:G.......!.b... +| 3424: dd 67 9c 8c 87 59 7d 87 75 46 0c 85 64 f3 09 7f .g...Y..uF..d... +| 3440: e2 7f ee ca c8 d2 2e 96 14 50 30 89 86 f6 ed 07 .........P0..... +| 3456: ee 9d 78 0c 84 c8 80 ee 2f d4 59 22 12 92 e9 80 ..x...../.Y..... +| 3472: 04 f5 ed aa 7a f5 cd 53 d7 de 45 a5 c3 e1 4e 9d ....z..S..E...N. +| 3488: fb 78 98 4d cb 5b 0b bf 1b 6a bc 50 ba 45 e5 ff .x.M.[...j.P.E.. +| 3504: d7 d0 7b 39 3d 5b eb 92 46 7f bb 21 7f 81 8b da ...9=[..F..!.... +| 3520: e7 c1 e2 12 41 82 19 7a bd 78 de bc e0 51 cf a7 ....A..z.x...Q.. +| 3536: 0e 20 0b 74 cb c8 fe e9 26 64 6e c4 e1 cc 3f b8 . .t....&dn...?. +| 3552: 62 a1 9b 79 bd 9f 05 21 b1 d6 93 66 6f 38 e5 df b..y...!...fo8.. +| 3568: 56 0c 57 c3 3b 57 04 79 f7 f4 b5 20 16 65 18 dc V.W.;W.y... .e.. +| 3584: a8 88 ce 1c bd 7f 40 c8 05 46 17 d7 99 a7 8e 07 ......@..F...... +| 3600: b7 02 2f 8c a6 49 5a 1f ce 63 3a 66 31 c2 3b 78 ../..IZ..c:f1.;x +| 3616: 84 00 80 8e 81 43 07 3a 43 ef e4 df 33 4b 18 33 .....C.:C...3K.3 +| 3632: 45 27 cc 2e e8 cf e8 be 1a 90 97 ed 99 9b b2 6f E'.............o +| 3648: 65 ff 53 ad df f8 58 47 d9 5d 71 6a 38 e4 e8 17 e.S...XG.]qj8... +| 3664: e1 1d 4c 03 cc c1 33 18 96 3e 9c 11 98 55 8e 62 ..L...3..>...U.b +| 3680: 1a af a0 33 36 f7 0c 87 0a 0c f0 43 ff e4 71 19 ...36......C..q. +| 3696: 5a 38 f8 9d 9a 53 d4 48 ff e3 40 89 e2 18 d5 3c Z8...S.H..@....< +| 3712: fb 2c 67 2c b9 f3 e5 d7 5c 97 e9 8f fb 3e 3e a2 .,g,.........>>. +| 3728: 22 63 47 fb d0 65 ed 87 b0 93 e4 28 e5 85 87 68 .cG..e.....(...h +| 3744: fc 0b 64 4c 5b 3c 5f 9f 40 96 d7 34 5b cb d0 a9 ..dL[<_.@..4[... +| 3760: 63 f5 f7 80 f4 67 49 1d 20 0d 84 f9 39 85 28 8b c....gI. ...9.(. +| 3776: 78 52 fc 86 fb f8 33 cd d6 ef ef ca 32 51 98 2c xR....3.....2Q., +| 3792: 54 d4 2e b9 78 0c 2a 5e c0 e6 2c 58 83 60 85 ea T...x.*^..,X.`.. +| 3808: 0b 33 de 33 40 a4 05 80 0d 21 b5 0e 4f 87 a1 a2 .3.3@....!..O... +| 3824: 3a 20 d6 ee 63 7a 31 93 5c 64 f4 f4 57 43 8a 65 : ..cz1..d..WC.e +| 3840: 53 e0 6e 84 c0 ee f6 44 ce 46 ea 97 fc af ec 4b S.n....D.F.....K +| 3856: ff af da 33 b8 69 e5 08 bd 36 17 aa be 1b 99 5f ...3.i...6....._ +| 3872: 80 c2 8c 27 56 1c f4 99 fa c3 1c 54 01 b7 3e 96 ...'V......T..>. +| 3888: 67 b7 f0 31 f7 20 63 51 17 61 f1 b7 06 23 24 35 g..1. cQ.a...#$5 +| 3904: f3 84 c9 01 ec 59 ba 00 83 b8 6c f9 4b 94 01 53 .....Y....l.K..S +| 3920: d4 06 ef b7 a9 08 3c 29 7d ab 8a 88 12 e2 3f a6 ......<)......?. +| 3936: a1 27 be 2e 09 aa d4 15 bc 43 2a 10 ff 2a 2d 89 .'.......C*..*-. +| 3952: fd a0 73 e7 91 14 a3 e2 2c 00 e8 c8 a7 bf 7c c3 ..s.....,.....|. +| 3968: 4c a0 89 bb 70 da 99 1a 39 5f 81 77 af 17 8d af L...p...9_.w.... +| 3984: e8 b0 e0 27 fb f0 b2 39 45 4a 82 97 93 00 a5 54 ...'...9EJ.....T +| 4000: 44 07 b7 1d ce 52 5f bd cd 07 6f ba 6f 8d 6d d7 D....R_...o.o.m. +| 4016: 2b 4f f5 4b 7b 23 45 74 d9 d8 aa e0 15 14 f6 be +O.K.#Et........ +| 4032: 74 f2 b8 38 f9 ba 64 58 93 b2 b6 0c e7 62 10 81 t..8..dX.....b.. +| 4048: ad ce 6f 23 0e cd 1c 3c 1e 30 62 64 95 d6 48 55 ..o#...<.0bd..HU +| 4064: fc 63 67 73 85 40 16 2b ab 0b 96 c4 90 8e ea 04 .cgs.@.+........ +| 4080: 64 13 bb 60 63 d8 6c d3 73 ed a9 10 03 bf 20 04 d..`c.l.s..... . +| page 11 offset 40960 +| 0: 0d 00 00 00 01 04 3f 00 04 3f 00 00 00 00 00 00 ......?..?...... +| 1072: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 97 ................ +| 1088: 3e 03 06 00 ae 7c 00 00 68 95 7f 98 0f 98 e0 e6 >....|..h....... +| 1104: fd 92 02 0f 0f 29 aa bd bc 40 56 6c 5e fa 3f 54 .....)...@Vl^.?T +| 1120: 10 24 70 48 b1 c5 fe e5 0b 21 21 65 1e a4 f1 2a .$pH.....!!e...* +| 1136: fa 3e 9a a1 b8 67 92 ab cd 27 a2 0f f3 b1 77 9b .>...g...'....w. +| 1152: 97 e6 c0 f2 c4 db a4 5c d8 f1 01 bd 3c f4 fd 0f ............<... +| 1168: 00 ca 6d 75 11 fd 56 e2 12 96 e1 f7 18 99 f3 78 ..mu..V........x +| 1184: 9b b7 ef 8c 50 1f dc c5 fa f5 92 fb 91 70 cc d3 ....P........p.. +| 1200: ca 0a c6 9b 42 d4 2f cc ce 98 48 a6 ab cd d1 d8 ....B./...H..... +| 1216: 4c 8a 98 2f 2d dc 48 d9 84 52 8f 86 57 04 b8 14 L../-.H..R..W... +| 1232: ec 33 d4 5e 69 da c5 45 8e 2c 4f d6 fc a3 ee f1 .3.^i..E.,O..... +| 1248: 7c 47 c1 5c 8d 48 1b c2 be f1 ca b8 f4 91 d8 bd |G...H.......... +| 1264: d6 41 f6 40 ee 41 90 51 14 09 a8 c5 cf 38 16 21 .A.@.A.Q.....8.! +| 1280: b5 84 48 6c 8e eb e7 b0 62 69 dd 04 c4 4a 75 f6 ..Hl....bi...Ju. +| 1296: 6f 92 af 16 33 94 81 eb 26 00 9f dd d4 01 73 32 o...3...&.....s2 +| 1312: ec ee 9d 23 bf 82 8f e0 c8 eb 8f aa 4f 69 4b 68 ...#........OiKh +| 1328: a8 4e 3d 6e d8 2c 99 49 fb 96 d1 77 f6 a0 c3 97 .N=n.,.I...w.... +| 1344: 7e c6 7d ff 90 fc 12 97 e7 71 7b 20 ea dd ba 47 ~........q. ...G +| 1360: 3f bc bf 89 2e 26 82 91 71 97 6c 5e 2e ba 8d b9 ?....&..q.l^.... +| 1376: c6 ff 51 b3 17 7d e6 ca 99 75 53 73 99 9f 5a b8 ..Q......uSs..Z. +| 1392: 11 db 5a fb b3 73 55 bd 61 0c 2c e5 03 b2 d5 1c ..Z..sU.a.,..... +| 1408: ce ea ef b4 2e 2c dd d4 55 df cc 7b 13 c9 ba 87 .....,..U....... +| 1424: 25 38 2d d7 fc 7b 47 c1 da 56 42 fe 50 8e 12 a8 %8-...G..VB.P... +| 1440: fd 9d 90 e3 ae e1 f2 5d fa 1e c5 e2 d9 74 b9 55 .......].....t.U +| 1456: d0 5b 90 1d 31 f0 fb 36 d6 e5 df 91 72 4b 69 41 .[..1..6....rKiA +| 1472: 06 33 4e 77 cc 1c a7 d1 3e 49 7f 76 8d d3 73 88 .3Nw....>I.v..s. +| 1488: 27 53 13 95 87 9a 99 cc b2 54 46 34 7f 96 98 1e 'S.......TF4.... +| 1504: fb 60 06 85 93 37 0a 90 30 4b 8a ca 7f e8 a6 6a .`...7..0K.....j +| 1520: a1 94 da ac 1f 47 80 1a 81 8f dd 19 9d 70 88 73 .....G.......p.s +| 1536: b7 4c 22 f7 fe 8b c1 05 5a ea e0 20 5d 01 55 c4 .L......Z.. ].U. +| 1552: 7c 3d 99 f7 8a 1a 15 23 1a a6 5a 02 52 35 2f 14 |=.....#..Z.R5/. +| 1568: ea 01 d4 2b 97 3f 7c e3 4f 7b 58 ea 83 2f 1f 03 ...+.?|.O.X../.. +| 1584: c3 64 4f e8 81 f2 ec 19 92 5f c7 b1 27 5a 74 12 .dO......_..'Zt. +| 1600: fd f3 d1 48 0c eb fd 57 9d 28 1e 44 28 3d 69 aa ...H...W.(.D(=i. +| 1616: 94 66 23 55 11 3b 01 52 76 0b 3b e0 fe 67 e0 fe .f#U.;.Rv.;..g.. +| 1632: f1 e5 18 2b 19 ec 22 a0 33 94 e2 33 3f 59 fc 04 ...+....3..3?Y.. +| 1648: 15 c3 ee a0 f8 18 7d 07 39 04 04 e5 df ee 0c 38 ........9......8 +| 1664: 31 94 76 b0 ec 42 89 fe 3a d9 d2 1a 4d ee b0 67 1.v..B..:...M..g +| 1680: 10 f4 e6 d1 dd 96 9c d8 ec 10 f0 4b 8c 02 37 54 ...........K..7T +| 1696: 5b b8 e6 d6 d2 d3 ad cd cc 84 02 bd 1f bf 36 de [.............6. +| 1712: fe d3 8f 68 94 5d c7 19 18 d0 91 e5 be c2 e6 40 ...h.].........@ +| 1728: b6 21 ec 29 08 49 08 96 97 a1 14 3a 32 cb 0b 24 .!.).I.....:2..$ +| 1744: d4 8c 77 f9 f8 4b 78 8c cf de 90 27 b8 a1 5b dd ..w..Kx....'..[. +| 1760: 84 c5 88 21 39 fe 1e 88 c0 1c 9b d7 46 44 4f a7 ...!9.......FDO. +| 1776: 13 49 c0 49 d6 c4 c2 06 ab 24 9b 8d 80 83 32 32 .I.I.....$....22 +| 1792: b0 ff e5 47 db 08 c0 17 c5 98 67 12 fa 2f 86 39 ...G......g../.9 +| 1808: a5 f6 13 03 4d 25 af dc 2c 52 e2 5e f0 36 b3 a2 ....M%..,R.^.6.. +| 1824: 13 5b d4 47 14 5e 26 66 f5 a8 48 b3 1a 9e c5 03 .[.G.^&f..H..... +| 1840: 39 66 f3 34 fc c2 ff a8 0f a5 66 a7 7a 6b ad dc 9f.4......f.zk.. +| 1856: 77 e2 c7 fb 10 c6 fe a6 2a 64 fc e3 84 8c 12 0e w.......*d...... +| 1872: 5d 96 80 c8 d2 0d d9 e4 f1 bd 96 51 a0 ad a2 cb ]..........Q.... +| 1888: 99 d9 13 4b de c3 f6 ab 20 d8 ad 61 23 fd be 39 ...K.... ..a#..9 +| 1904: 97 0b 90 22 49 08 e2 38 3e 43 e6 91 9a ef e5 d7 ....I..8>C...... +| 1920: 9e d3 ff 82 51 99 6d 12 f5 74 0d 84 6e f8 ed 63 ....Q.m..t..n..c +| 1936: b8 ff 4d dd ea ae a3 0c 55 13 a1 03 4c 8b 3f 0e ..M.....U...L.?. +| 1952: 54 b7 02 3e 01 5a 77 ad 6f fc 92 bd b3 6f 3e 89 T..>.Zw.o....o>. +| 1968: 8e fe e1 2e 1c d4 56 c8 5f 4c 57 73 99 95 d6 be ......V._LWs.... +| 1984: f8 f5 17 22 5f 3f 13 5d 98 c9 b5 74 b2 17 7c c8 ...._?.]...t..|. +| 2000: dd d0 a8 d1 fa da 22 5a e8 34 f9 83 93 1b 7f e7 .......Z.4...... +| 2016: ba 48 ab e4 cd 3d 54 ec a2 9b 4b ca cf 84 0a d3 .H...=T...K..... +| 2032: 4d 8c bc 0d 73 1a 29 05 0c 60 0a 4a 6e 54 d3 0f M...s.)..`.JnT.. +| 2048: 84 00 df c0 f3 0b 73 2b 3b f0 60 68 91 ae cd 60 ......s+;.`h...` +| 2064: 59 0b ee b7 dc 7c eb b1 cc 70 f3 bb 6a 27 3b bb Y....|...p..j';. +| 2080: 20 41 3c 84 9d 3d 06 94 0c 53 eb 9c 31 e3 8a a0 A<..=...S..1... +| 2096: 1e c1 65 ef a6 78 92 ae 2e f9 64 49 58 b7 c0 23 ..e..x....dIX..# +| 2112: 2b 4c ab 93 2c 78 c2 86 32 09 d0 8e bf 34 b4 9e +L..,x..2....4.. +| 2128: 59 5c 6f 69 bb 85 5d a6 02 b2 01 85 89 23 40 7f Y.oi..]......#@. +| 2144: 23 3f c7 67 da 35 cf 2e d0 36 1e 71 fa 78 da c5 #?.g.5...6.q.x.. +| 2160: 41 db 9a 14 b1 48 d4 02 36 2f ed 6a 85 4a f4 f6 A....H..6/.j.J.. +| 2176: f4 3f 46 81 2d fa 92 47 21 16 14 84 f8 c9 18 86 .?F.-..G!....... +| 2192: 74 45 16 8d b3 cd 58 93 40 62 9b 24 6b af d3 44 tE....X.@b.$k..D +| 2208: 67 f9 0a 7f e5 25 01 b9 76 3f 8e 4d 82 b8 04 ae g....%..v?.M.... +| 2224: ef ed 12 c3 9f c6 a9 54 03 8a 78 0d f4 7e bf 7d .......T..x..~.. +| 2240: c1 f5 be 24 33 54 77 e3 7f c4 c9 fd 5c 79 6d 54 ...$3Tw......ymT +| 2256: 67 2f 83 a3 04 8b 16 09 71 ff 47 67 14 6a 84 71 g/......q.Gg.j.q +| 2272: 39 54 18 0b 7d 7e ac ef 62 0f 4b dd 9c d0 0d 20 9T...~..b.K.... +| 2288: 9e 62 98 79 11 53 de 73 a9 9d 44 5e f5 5a 23 62 .b.y.S.s..D^.Z#b +| 2304: 08 01 fb de 39 57 24 ac c6 b5 5f e2 6b 07 18 2e ....9W$..._.k... +| 2320: 27 24 42 96 d8 31 68 d3 0e bb 65 9c 01 f8 93 ba '$B..1h...e..... +| 2336: 4a 2a 60 3a b3 c2 9c 17 66 1d 34 4c 0f 90 38 5e J*`:....f.4L..8^ +| 2352: a4 9c 72 9c 16 d7 c4 98 33 ed c1 95 a2 d7 cc a1 ..r.....3....... +| 2368: 3b 36 28 1c 44 1c 8f c9 f7 bc eb ed 5a d3 2d 69 ;6(.D.......Z.-i +| 2384: 6f 86 9a 2f 15 91 aa 63 0d 19 f9 bb 07 6b 87 f5 o../...c.....k.. +| 2400: 0a 48 7e db b6 9a c3 97 01 d1 91 44 37 7c ce 5c .H~........D7|.. +| 2416: 63 43 c0 92 20 31 b4 5c 36 98 01 50 05 ec 1d ac cC.. 1..6..P.... +| 2432: 73 10 66 97 48 60 c2 2b 46 3e 2b fc 52 1d 42 87 s.f.H`.+F>+.R.B. +| 2448: d3 af 2c 42 7f c4 a2 0b 2b d6 09 64 0a f9 9d f5 ..,B....+..d.... +| 2464: 54 b8 1d 96 23 1f 22 0e 56 de dc 0a f8 97 55 41 T...#...V.....UA +| 2480: 56 ff 72 20 73 2b 50 fe 0d 79 c2 d3 61 73 22 da V.r s+P..y..as.. +| 2496: 68 8e 77 23 19 ca f9 7c a6 9c 91 27 ed eb f8 9c h.w#...|...'.... +| 2512: b6 bb 72 dc 3a 62 23 17 fb 04 98 bb ce 1a fb 1f ..r.:b#......... +| 2528: 17 ab aa 46 46 5a 8b 98 c6 7e 0e 18 74 8a 62 9e ...FFZ...~..t.b. +| 2544: 1f 7f f4 dd 8c 1d 68 8d 54 3d 0c 06 95 f9 9f 06 ......h.T=...... +| 2560: 3f 97 71 00 21 d5 e9 c0 44 7b 98 27 16 ba d2 fa ?.q.!...D..'.... +| 2576: c2 33 1d 4a 34 ec ae 4d a7 6c 68 77 9b f6 7b 2c .3.J4..M.lhw..., +| 2592: 6e d3 f6 1b b6 35 6f 47 69 be 5f 96 66 13 c3 8e n....5oGi._.f... +| 2608: 2f f1 4a 5e cb 26 00 73 33 a9 05 12 7b 6d 5b 96 /.J^.&.s3....m[. +| 2624: b7 3d 7e bc 62 aa c7 fe d2 fb 11 d5 7c a3 bf 90 .=~.b.......|... +| 2640: 09 fa ba 2b 2e 8d 65 c6 3f 21 41 fa 3d 71 f8 8d ...+..e.?!A.=q.. +| 2656: e5 77 34 b2 ea 4d 28 d1 48 fc 4c 1f 2e db 9c 58 .w4..M(.H.L....X +| 2672: b1 04 54 ce 4b 68 a3 7b b6 28 16 19 22 d7 fe d3 ..T.Kh...(...... +| 2688: f9 88 dd ca f6 26 43 88 28 bb 0f 62 3b d5 d4 cf .....&C.(..b;... +| 2704: af 90 27 ca 8e 40 62 f6 50 a8 2b 23 d4 3e 6d 32 ..'..@b.P.+#.>m2 +| 2720: e0 62 79 28 29 ab fe 77 21 ad 98 62 11 8a d2 90 .by()..w!..b.... +| 2736: 9a 83 73 c5 44 45 cb dd 71 7e 45 b7 79 d8 ab 3f ..s.DE..q~E.y..? +| 2752: ea 53 89 0b 8c 18 b3 95 1b 45 d5 dd 45 5b 79 b0 .S.......E..E[y. +| 2768: e8 c2 a9 58 77 cf c2 5b 43 a2 81 0e 43 9c c2 6d ...Xw..[C...C..m +| 2784: 5b a3 7a ef ed e1 24 56 54 a4 e1 ef 07 e7 9b 0e [.z...$VT....... +| 2800: 1f 32 78 3d a4 0f c2 52 ee 7d 4e 4d 80 4d ff 00 .2x=...R..NM.M.. +| 2816: 6c 8f da b7 ff aa fd a6 05 c1 31 e1 03 5c a4 e1 l.........1..... +| 2832: 92 39 d9 be 7a 16 9a c2 4b c7 01 eb ef 54 f8 2a .9..z...K....T.* +| 2848: 60 82 bb f6 4c 86 d5 8b 23 ce 88 52 a5 35 a7 ba `...L...#..R.5.. +| 2864: b6 31 e5 ec fe 30 f9 06 98 e7 bd eb 83 08 33 e5 .1...0........3. +| 2880: c5 a2 12 68 ca cb 97 77 db 60 94 4a 43 fb c1 04 ...h...w.`.JC... +| 2896: f1 8d 16 af 2e 8d bf 61 2a ff b5 4b 80 65 8a 07 .......a*..K.e.. +| 2912: 91 17 7f b7 ee c0 57 ce 40 82 c7 c8 57 89 62 61 ......W.@...W.ba +| 2928: b8 63 fe eb c2 97 30 ed 71 84 23 b3 48 e9 d9 ba .c....0.q.#.H... +| 2944: 71 c3 66 f5 6a 66 5a ee e6 bf 72 fe 80 a1 40 24 q.f.jfZ...r...@$ +| 2960: 75 0e 01 f9 1d 18 c3 fd 73 1c 21 92 5c 2b 07 a0 u.......s.!..+.. +| 2976: 83 80 5c 0f 20 fe e9 55 d6 f4 4c 96 d8 ab 7a 5c .... ..U..L...z. +| 2992: d6 d1 1e 44 60 ee 0a 30 7e 92 cf af a3 41 20 d5 ...D`..0~....A . +| 3008: 0b de fb 9c f4 81 76 5c d6 58 5d 86 1e 14 10 c5 ......v..X]..... +| 3024: 12 b5 f1 2d 73 09 0b ad 33 2c 49 5f 59 a7 08 80 ...-s...3,I_Y... +| 3040: 30 bf 50 61 b4 0b b2 3c 53 f3 de 2f e0 87 59 58 0.Pa...MC....u. +| 3200: 36 53 ad 19 cc d1 3c 57 45 64 fc e4 19 89 42 81 6S........... +| 3424: 65 16 ad c6 c4 a3 75 78 94 b2 ce b5 9c 50 df 02 e.....ux.....P.. +| 3440: f3 c2 91 11 5a 81 de eb 31 24 16 50 16 d8 5f c5 ....Z...1$.P.._. +| 3456: df 62 1b 24 37 bb cd 5d a8 a7 93 d0 6a 5e d8 55 .b.$7..]....j^.U +| 3472: 74 cf 69 a2 4a 05 07 2e 61 09 0e fa 1e 12 ab 62 t.i.J...a......b +| 3488: d9 ca ff ab 2b b0 3b 16 16 b0 95 15 27 26 89 46 ....+.;.....'&.F +| 3504: d3 67 fb 0f 33 00 18 44 c3 a1 92 de 6e 6d b0 67 .g..3..D....nm.g +| 3520: b0 65 97 fa b9 10 1d 39 15 10 95 f1 b3 cc a3 d4 .e.....9........ +| 3536: 28 06 9f b7 00 be a1 06 8f 61 34 66 92 fa 58 0b (........a4f..X. +| 3552: 4f 91 6d 93 31 32 cb a1 97 a5 18 b2 f1 bc fc 70 O.m.12.........p +| 3568: c0 86 62 24 4b 82 ce b3 71 30 53 ac 42 c7 32 2d ..b$K...q0S.B.2- +| 3584: 3a 0e 3f 8a 91 89 0b 46 55 f3 5d 73 b1 74 a2 9e :.?....FU.]s.t.. +| 3600: 97 6e 85 b6 f0 3e 56 6d 31 50 96 87 3d 18 cf 5f .n...>Vm1P..=.._ +| 3616: 74 57 1a 65 b4 d2 4e 96 1c 6e 3f 5b a9 a0 47 2d tW.e..N..n?[..G- +| 3632: 22 5b 16 ab 27 f4 86 36 f7 8e a1 f2 9c 69 7f 0c .[..'..6.....i.. +| 3648: 85 4d 3c 08 3b fa 96 ee 92 af f9 8d c2 ca a9 f1 .M<.;........... +| 3664: 2b fa 90 4b a3 fb 35 c4 c6 05 c3 0f ad 5e bb f6 +..K..5......^.. +| 3680: 55 66 73 bd db 98 2f 70 7d 25 3f 05 3d 24 c6 0d Ufs.../p.%?.=$.. +| 3696: 8a e2 e1 3e 19 59 51 25 a9 e8 4a 0a 68 69 05 c9 ...>.YQ%..J.hi.. +| 3712: ce 1e 5f 96 c4 a1 98 2a 7b b9 e9 99 ce 84 af 0b .._....*........ +| 3728: 82 61 5b 97 b1 8c 2e ea 81 98 44 39 79 e0 d5 9c .a[.......D9y... +| 3744: 6a b9 14 0f 36 98 ea c8 cd 6b 66 fa d1 23 cf cb j...6....kf..#.. +| 3760: c6 1b 25 6c 42 13 b2 67 f9 68 0a 62 ff 37 ab ca ..%lB..g.h.b.7.. +| 3776: 22 60 77 c1 30 e4 59 5f 17 b4 47 c3 ea c5 c4 2b .`w.0.Y_..G....+ +| 3792: 62 44 94 08 87 03 1a a5 10 ae b1 53 cd 2d 40 5a bD.........S.-@Z +| 3808: 80 ef dd 56 6b 88 92 d7 3c d0 7a a0 2f 63 22 02 ...Vk...<.z./c.. +| 3824: 61 f1 ab 36 f9 16 f0 14 ba 67 7d 9c 7f 90 cc d9 a..6.....g...... +| 3840: b1 ac d3 1d 73 1e e8 bf 88 6a 6b 15 32 b3 b5 9c ....s....jk.2... +| 3856: 4a ad bd 4f f8 f8 b8 b8 2e 48 0f e2 e9 d6 d9 72 J..O.....H.....r +| 3872: 22 9a 28 8d 24 e2 f0 33 23 27 ff 37 5e 36 55 37 ..(.$..3#'.7^6U7 +| 3888: e1 92 78 19 a4 9d ff b9 22 e5 47 79 b8 de b4 9f ..x.......Gy.... +| 3904: 4e f0 65 1d 89 5e 55 86 bf 25 ff 6f 7e 27 f2 c1 N.e..^U..%.o~'.. +| 3920: 9d 26 69 ab 5b 1a 16 1d a1 b7 0a f0 60 06 12 4b .&i.[.......`..K +| 3936: ce 6b 17 d3 e7 66 ef 6d 83 10 30 96 49 8b 8d 52 .k...f.m..0.I..R +| 3952: 98 65 b1 48 a8 0d 96 ae 15 cd 00 5b f5 3e 4c f6 .e.H.......[.>L. +| 3968: 16 b3 15 5b cf 3e ba 15 86 7e 9b 92 be cb 8a de ...[.>...~...... +| 3984: d0 de c8 0e ac cb 79 cc 8f ad f6 3a 40 33 9f 91 ......y....:@3.. +| 4000: 48 7c 3f d1 33 c1 d3 51 ba 44 47 0e 41 8d fa 2d H|?.3..Q.DG.A..- +| 4016: 55 99 7f 4d 44 4a 85 dc 6b b1 9f 8a 4c 55 70 b7 U..MDJ..k...LUp. +| 4032: 2d 14 4d 72 ea 76 c8 0c 9f 9c 99 0a c2 7e 95 94 -.Mr.v.......~.. +| 4048: 61 84 76 8c 20 1f a3 2d 5f 54 ab 5b 8c fc 04 0d a.v. ..-_T.[.... +| 4064: 25 33 9f d1 f3 f7 38 1d 64 4c c1 0d 38 0e 4d b8 %3....8.dL..8.M. +| 4080: 61 16 f8 ea 3d 01 e9 f7 7b 6d 10 ed 08 07 86 f3 a...=....m...... +| page 12 offset 45056 +| 0: 0d 00 00 00 01 04 3f 00 04 3f 00 00 00 00 00 00 ......?..?...... +| 1072: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 97 ................ +| 1088: 3e 04 06 00 ae 7c 00 00 72 d0 44 9f 95 5b ad 14 >....|..r.D..[.. +| 1104: 76 bf ee ef 64 b3 05 0c b8 3c fa 37 1b ab 5a f0 v...d....<.7..Z. +| 1120: ae 4c 14 79 98 2a 95 49 a0 1e 3e ff 44 91 47 ad .L.y.*.I..>.D.G. +| 1136: 40 a0 7b 5e 0c 5f 86 45 28 c7 d9 03 81 2b 26 13 @..^._.E(....+&. +| 1152: 0c 38 01 db 73 e2 42 53 3c 8c d1 6c fd 51 a6 5a .8..s.BS<..l.Q.Z +| 1168: fa 65 81 fc 71 89 ea 74 84 df da ea e0 d8 40 ce .e..q..t......@. +| 1184: 56 a4 cb 65 0b 6b 79 7c 81 a9 55 79 8c 3e 02 dd V..e.ky|..Uy.>.. +| 1200: 17 7f 47 b6 84 c0 c2 a3 5e 79 66 83 e4 f2 73 6a ..G.....^yf...sj +| 1216: 46 9f 20 24 f3 4e 2b 6e a9 2c 9b 51 39 96 42 82 F. $.N+n.,.Q9.B. +| 1232: ec c0 c9 78 ec ec 90 e3 57 af fb 1f 51 41 a3 37 ...x....W...QA.7 +| 1248: b7 68 5d 84 5e 6a 9e 17 13 e1 87 7d 93 f5 92 8e .h].^j.......... +| 1264: 7a 8d 3b d4 40 35 df 0e 8f 80 13 f3 54 9c 57 a8 z.;.@5......T.W. +| 1280: 86 88 69 32 7d a2 22 13 ab 14 db e6 8a 1c 84 ed ..i2............ +| 1296: 54 4e d3 0a ba 5f 9e 66 88 2a 64 d8 04 86 3c 5e TN..._.f.*d...<^ +| 1312: 41 b7 2d c2 c1 a0 73 61 d8 80 3b 9d 9d 1e 11 ab A.-...sa..;..... +| 1328: ae 20 63 ac 31 80 2e 66 d5 a7 c8 8e 0a 55 78 53 . c.1..f.....UxS +| 1344: 66 8d 5d 1e cb 04 13 d9 a8 d1 09 4b 31 3c e9 ba f.]........K1<.. +| 1360: 83 69 76 4c 12 e8 95 84 e1 fe c0 64 74 c5 74 8d .ivL.......dt.t. +| 1376: 1f 75 28 75 1f 51 23 b7 df ef 45 66 61 1b 18 2e .u(u.Q#...Efa... +| 1392: c8 56 69 8f fa 2a 08 41 21 2d 6a c7 79 46 8f 95 .Vi..*.A!-j.yF.. +| 1408: dc b1 b2 d7 39 b9 a6 41 af 06 3e 53 85 c2 db c3 ....9..A..>S.... +| 1424: 42 fb b7 4f e9 e0 11 c1 a6 9a 48 fb d6 c9 26 31 B..O......H...&1 +| 1440: 3b c4 7a e2 bd 53 d7 3e d7 56 60 8f 6a 80 60 19 ;.z..S.>.V`.j.`. +| 1456: db fd 5a 7a 8c c7 43 c7 29 57 8b 6f 31 ea 13 af ..Zz..C.)W.o1... +| 1472: de 2e 32 75 5f 63 b7 bb 45 07 9e da 69 54 a0 fd ..2u_c..E...iT.. +| 1488: 0e dc 0d f5 42 17 eb 64 60 0d d8 56 4b 61 6d 8c ....B..d`..VKam. +| 1504: 82 09 f1 80 a2 25 5d 8b e5 67 76 13 9d 46 4a 6f .....%]..gv..FJo +| 1520: 30 f6 eb e0 7b 0e 01 e1 a8 b2 38 1b 9b 21 48 ed 0.........8..!H. +| 1536: c8 7f 7d 6b 0c 25 ce a8 0a 78 a8 48 45 2d b5 e7 ...k.%...x.HE-.. +| 1552: fd ca b2 a1 2b 2a b9 72 61 76 76 0c ae 9c b3 17 ....+*.ravv..... +| 1568: fa 83 07 24 24 62 18 18 d6 12 2d ad 58 0b b3 5e ...$$b....-.X..^ +| 1584: 80 da 40 b5 d0 e7 aa 28 52 bc 7f 68 7a 1d 5a 1f ..@....(R..hz.Z. +| 1600: 79 65 10 9e 4d fe be 32 c7 0f aa 7d a5 0a 43 0f ye..M..2......C. +| 1616: 3d 17 0e 88 a3 7f 3f ff 04 bc 79 56 62 0e 9b a0 =.....?...yVb... +| 1632: 51 1f 49 04 ff 40 7a c2 51 e8 36 44 85 63 df a4 Q.I..@z.Q.6D.c.. +| 1648: d5 63 a4 eb a3 71 44 b4 02 51 a1 2b 00 73 69 4e .c...qD..Q.+.siN +| 1664: 07 76 42 84 bc 87 1a e6 15 13 f7 ac 7a e8 bf c0 .vB.........z... +| 1680: a4 8a 64 fe 9c 17 a7 20 cb 67 cd e6 da f1 95 26 ..d.... .g.....& +| 1696: b6 79 d4 93 e2 52 fd 6f 44 29 db 1e 34 02 46 a4 .y...R.oD)..4.F. +| 1712: f1 c5 f0 b5 c3 19 1d 0e 14 d4 ba 08 1c 01 bf 39 ...............9 +| 1728: 54 02 e2 f5 d3 cf fb d1 d6 76 65 91 ba 7b c8 22 T........ve..... +| 1744: 85 f8 fa c7 ae ae 21 85 b9 d2 09 5f f4 3b 27 c5 ......!...._.;'. +| 1760: 13 39 39 7e 79 ae 07 37 26 72 07 22 01 40 2c 10 .99~y..7&r...@,. +| 1776: 5b 2c 9b bf 30 92 2e f5 12 9f 19 f9 2d 69 2a c6 [,..0.......-i*. +| 1792: 9a 26 1b de 18 b8 05 65 a8 b4 b3 3e 2c 04 5b bc .&.....e...>,.[. +| 1808: 63 79 56 7a ba 24 ae 69 ee d6 d5 7c 3d 5f 2f 7b cyVz.$.i...|=_/. +| 1824: e9 e5 55 f2 88 58 72 49 89 3e d5 91 be ae ea 9e ..U..XrI.>...... +| 1840: 58 20 45 2c da c7 b8 f9 db 97 68 59 c7 f4 d3 4b X E,......hY...K +| 1856: 95 b6 c7 73 1c 9b 96 d2 1c 56 17 eb 18 a1 fa 17 ...s.....V...... +| 1872: 05 72 44 05 4f c3 2b 8c 89 d2 3c a2 25 d8 16 74 .rD.O.+...<.%..t +| 1888: 48 b7 78 36 7a 86 88 ff cc 47 ac bd 73 28 1f 3f H.x6z....G..s(.? +| 1904: 13 3d b9 d2 df 9a 93 43 a6 9f b9 fc 7c b2 d4 84 .=.....C....|... +| 1920: c4 7e d0 14 60 01 63 fb 09 de a3 2f 1c ae 2e 6b .~..`.c..../...k +| 1936: 9f 8d c8 f1 2d f0 c9 a5 1f 48 c1 7c 53 c5 63 8a ....-....H.|S.c. +| 1952: 9b 0b fd f3 7f 3a 63 eb 2c 4f df 3a 57 8c 20 3e .....:c.,O.:W. > +| 1968: 0b d1 00 0f ce e3 ab a8 77 31 63 4a aa 35 5f 3f ........w1cJ.5_? +| 1984: 77 ba d3 38 7b a3 53 94 e6 9d 34 ec 5a 28 09 6e w..8..S...4.Z(.n +| 2000: 5a 85 bc 72 18 bb 15 8a 20 01 f3 88 19 74 65 1b Z..r.... ....te. +| 2016: 51 bc 8a 4d 32 a6 00 80 fc 06 f8 aa 1c ee 0e 84 Q..M2........... +| 2032: b5 70 fe 09 02 78 d4 ae 3a bb 02 ed 5a 90 d2 a9 .p...x..:...Z... +| 2048: 3e 58 8a 08 2d 4c 79 7c f2 94 89 ac 04 59 d8 17 >X..-Ly|.....Y.. +| 2064: 09 e4 c3 e0 78 6b 77 58 64 4f 2f 77 15 16 c2 41 ....xkwXdO/w...A +| 2080: 96 61 8c 23 59 73 b9 56 c3 4e da 79 34 94 e5 31 .a.#Ys.V.N.y4..1 +| 2096: 9b a6 92 ca 23 64 e4 78 32 e2 9d 2e ad 18 11 b7 ....#d.x2....... +| 2112: 12 0d 1b 40 44 4f 92 cf 78 80 86 fb af c9 30 20 ...@DO..x.....0 +| 2128: 52 9b 22 8f f4 ca 65 e5 f2 e7 a3 c3 e7 00 ee a1 R.....e......... +| 2144: ca ab 3d 71 40 bd cf c3 2f 7b 59 e7 9d 18 6a 65 ..=q@.../.Y...je +| 2160: a1 c9 84 cf 0f f5 36 d5 22 06 d4 6a b6 ad 38 03 ......6....j..8. +| 2176: b4 ac 9e c4 7a b0 71 24 a9 01 f0 5c 5b e3 c9 bd ....z.q$....[... +| 2192: d6 e7 ba 71 fd 44 22 14 af f7 18 ef 29 1d f2 0f ...q.D......)... +| 2208: 5d e1 3a 71 3b 47 44 da 21 f6 73 8b 6a a1 ff 93 ].:q;GD.!.s.j... +| 2224: d5 eb 5d d0 61 23 41 53 99 7f 23 0f c0 a3 18 3c ..].a#AS..#....< +| 2240: bd 30 e0 bf 16 41 27 71 80 67 49 61 e9 64 71 e0 .0...A'q.gIa.dq. +| 2256: 9d 42 e6 48 9b 0e 40 82 f9 e2 a5 b4 51 37 7d 6b .B.H..@.....Q7.k +| 2272: 74 54 03 bb a2 93 ac 47 f8 a5 75 86 73 0f 12 47 tT.....G..u.s..G +| 2288: 27 65 16 16 a2 ab 99 08 14 0b 7e 1b 2c 7e 9d 20 'e........~.,~. +| 2304: 58 23 14 70 bd f5 7c 2b 74 ce d1 46 b3 29 03 8f X#.p..|+t..F.).. +| 2320: 76 f3 20 ca 9e a3 fc 1a 17 f2 8e 78 97 4e 0b 5e v. ........x.N.^ +| 2336: 61 2b e1 3d f1 14 ae 9a 1f e8 a8 3c 99 02 a5 c0 a+.=.......<.... +| 2352: a6 ee bd 47 a4 3c 6d db 6c 20 dd c4 60 09 17 e7 ...G.h$^0....z..... +| 2464: 89 df 3b c6 e1 09 1d 16 69 40 e9 d7 f7 41 d3 2b ..;.....i@...A.+ +| 2480: 52 6a 9a d9 65 e2 44 de 45 63 be 85 41 b8 d4 4a Rj..e.D.Ec..A..J +| 2496: 49 b9 b8 bb 4d a6 6e 60 46 13 6f 7f b1 57 9c 5b I...M.n`F.o..W.[ +| 2512: 78 69 87 3d 42 e5 c2 0a 46 9c 38 9d e2 44 80 fe xi.=B...F.8..D.. +| 2528: c8 81 f4 fe bb ef 61 46 88 c7 6f 1a f8 00 0b 63 ......aF..o....c +| 2544: d0 ac 8e 5e 88 8d 2d 56 15 79 d2 12 d9 94 ba 2b ...^..-V.y.....+ +| 2560: ae 71 63 be 62 08 75 b9 97 fc 1e 17 89 83 3b 30 .qc.b.u.......;0 +| 2576: 07 6e f0 db 44 17 c0 49 7f ed 0a 0a 43 13 6a 72 .n..D..I....C.jr +| 2592: 78 9c 96 52 2c 02 ac da a0 90 b5 66 34 b7 a2 17 x..R,......f4... +| 2608: e7 71 11 8d 8f 22 0e 40 90 9b 0f 99 f5 10 c0 64 .q.....@.......d +| 2624: ac e3 24 0d 49 1a f4 2f 0b 13 fc 94 a7 18 b1 5f ..$.I../......._ +| 2640: a6 64 49 0b c2 35 99 b6 05 81 bd a8 f8 88 56 83 .dI..5........V. +| 2656: 17 56 cc f8 91 db 5b 18 7c 42 46 3f 3d 9a 2d b6 .V....[.|BF?=.-. +| 2672: 63 8c 62 bf 78 53 a0 23 53 40 c0 32 06 f1 c8 3a c.b.xS.#S@.2...: +| 2688: f8 17 34 a7 29 1e ab 92 2d 2d 68 c5 83 e0 0a 2e ..4.)...--h..... +| 2704: 7b f8 9c b7 32 86 c0 1e f5 29 44 a6 24 e1 d7 66 ....2....)D.$..f +| 2720: 0d 48 2b 0e 49 f8 e4 52 4a 7d bd 1c c1 44 27 f0 .H+.I..RJ....D'. +| 2736: c9 db 87 93 13 62 82 ef ad 2c ea 8f d1 3d a4 a6 .....b...,...=.. +| 2752: b8 80 87 e2 0e 27 27 b3 3d 56 66 39 de e8 21 5f .....''.=Vf9..!_ +| 2768: 95 25 d9 68 f1 57 50 0e 15 54 0b a6 44 27 e8 d9 .%.h.WP..T..D'.. +| 2784: f2 dc 5e 79 f0 ec 2b a3 39 77 8f 3d 53 70 8a d3 ..^y..+.9w.=Sp.. +| 2800: e5 aa 14 cb 7b dc 31 72 f6 90 5e 8b 3a 8c f3 77 ......1r..^.:..w +| 2816: 4d 00 a3 1d 3a 63 47 c0 2a a2 32 98 6e 5c bc 21 M...:cG.*.2.n..! +| 2832: f0 6a 34 6c 89 a5 bc 04 f6 3b 8c 96 b0 eb 0d 70 .j4l.....;.....p +| 2848: 9f 18 d5 64 ec 2e df 19 7d 1d a4 61 48 e7 0b eb ...d.......aH... +| 2864: f3 94 16 f5 7f 4c 9e 0c 78 aa 4b 61 14 13 eb e2 .....L..x.Ka.... +| 2880: 72 14 56 27 41 70 8f 7f cb e6 a1 c3 37 c4 78 32 r.V'Ap......7.x2 +| 2896: 85 ea e5 af 96 46 4b c3 93 af 8f 26 0c ea 08 b6 .....FK....&.... +| 2912: b4 a6 a4 78 6d 82 51 ab fb e5 a9 e9 89 c3 a1 be ...xm.Q......... +| 2928: 3e b9 e3 8c 61 cf 42 1d de 25 45 9e f0 ff 8b 75 >...a.B..%E....u +| 2944: 63 9b 3d d6 92 c3 ad ca c5 4c 79 9d 72 37 fd 3a c.=......Ly.r7.: +| 2960: 21 f2 8d 34 37 b9 eb a0 86 d8 1a f2 9d aa 6a 53 !..47.........jS +| 2976: f6 c9 29 d0 39 2c 66 24 c6 8e 68 9c 70 cc 9e cc ..).9,f$..h.p... +| 2992: 25 a8 dd 5e d8 e6 87 1b dc 3a 26 20 e3 1c 3b ba %..^.....:& ..;. +| 3008: f8 c4 80 83 79 49 f1 2a f0 e8 70 31 e7 b2 a5 e3 ....yI.*..p1.... +| 3024: 9f 5b 49 25 2c 33 b6 ea 38 ab a8 0a 9a a2 0e fd .[I%,3..8....... +| 3040: 3a a8 9c 0f ab 93 45 a7 54 d2 18 2f 2e 94 34 cc :.....E.T../..4. +| 3056: fa 14 b6 8e 01 8d af 80 94 e7 80 31 dc 2c b9 1e ...........1.,.. +| 3072: dd 35 91 29 28 a0 29 65 bc e3 a2 52 b0 e5 56 5a .5.)(.)e...R..VZ +| 3088: 12 21 06 fd f3 04 77 31 6d f2 e0 66 0a a3 77 f3 .!....w1m..f..w. +| 3104: 5a 17 37 59 65 d9 32 68 82 2c 72 05 16 e9 9a 89 Z.7Ye.2h.,r..... +| 3120: 10 94 53 e9 be 63 2e 82 27 28 30 1d 2a bf ad 00 ..S..c..'(0.*... +| 3136: 49 9a a7 e3 50 d0 61 b4 bd 73 3e 99 a3 a7 69 7f I...P.a..s>...i. +| 3152: a8 4c 4d 12 7f c0 55 b7 de 0c 93 5e 27 cf 4a 53 .LM...U....^'.JS +| 3168: 78 08 ee da 22 37 3f 94 2f dd a6 b2 28 84 0f 62 x....7?./...(..b +| 3184: bc 7f be 3c af ad 84 82 70 b2 98 d5 9a d5 07 34 ...<....p......4 +| 3200: 41 6f 9c 0a 17 54 cf fc 43 1e 7c ca 48 6f 23 c5 Ao...T..C.|.Ho#. +| 3216: 50 7b b6 ec e5 3a 27 0f 65 29 97 ad 37 b1 9f e6 P....:'.e)..7... +| 3232: 6b d1 d4 41 ed 5e 0e 20 22 00 ca 41 58 d2 b9 73 k..A.^. ...AX..s +| 3248: 08 2d 01 83 db d1 57 a2 e3 d2 34 c6 73 fd 2e b2 .-....W...4.s... +| 3264: 9b f2 a4 a4 f1 1d 9f 7a cc d8 93 37 b3 95 2c 99 .......z...7..,. +| 3280: f5 92 0c 91 d5 e2 2e b2 bb 49 76 19 c9 58 16 28 .........Iv..X.( +| 3296: 1e 76 60 ba d9 a8 04 e9 91 5e 7f e6 b1 dc de 9f .v`......^...... +| 3312: 6f e6 52 f2 61 53 7b 09 f4 df c4 96 15 23 99 67 o.R.aS.......#.g +| 3328: 6b b8 e8 04 05 62 0d 5f 05 d1 8a 6c 49 e8 2b 71 k....b._...lI.+q +| 3344: 10 44 8e 93 13 bc 27 14 32 52 22 2d 11 cf cd 43 .D....'.2R.-...C +| 3360: 77 a1 60 21 70 44 60 cb 64 9e b4 c6 23 aa f2 0d w.`!pD`.d...#... +| 3376: c0 6d 50 0b d9 73 d0 eb 6d d5 03 fc 74 e1 fb 49 .mP..s..m...t..I +| 3392: a1 18 4f bb a2 2f 09 54 28 80 8c 04 86 3d e1 fd ..O../.T(....=.. +| 3408: ff 55 c9 7a a0 35 ef 64 b8 87 1b 10 8c fc 3d 27 .U.z.5.d......=' +| 3424: e3 6c 30 3c 05 4d b3 37 b8 29 ba a2 46 9b 76 5d .l0<.M.7.)..F.v] +| 3440: ff 1b 30 65 70 57 f2 89 c3 17 3f 21 5d 26 5f 58 ..0epW....?!]&_X +| 3456: b1 50 6e 75 c3 dc be a9 0e 43 3e 34 4e 40 0e a1 .Pnu.....C>4N@.. +| 3472: f4 69 6d b1 56 a7 0f 09 7e 25 74 08 83 bb 9d 67 .im.V...~%t....g +| 3488: a6 f6 68 87 e9 d9 ba 16 97 42 ca 66 29 e6 df cd ..h......B.f)... +| 3504: e8 e4 ef ef c9 1f f7 d8 9c 8a a3 24 c9 43 60 4d ...........$.C`M +| 3520: 9a 73 cb 73 9f b2 b1 fe 51 9d 55 9f 37 a0 d2 3c .s.s....Q.U.7..< +| 3536: 7e be e4 35 1e 8e 81 f5 67 29 29 ac 95 0a 99 28 ~..5....g))....( +| 3552: ec 4c f2 c6 03 6b 01 76 f5 87 77 58 51 51 40 d4 .L...k.v..wXQQ@. +| 3568: 81 c7 d7 7b 40 09 8f f5 c8 a7 49 00 c9 ee 99 c9 ....@.....I..... +| 3584: 6f 4a c5 6f e7 51 8e 4c 95 a3 de 6d 53 45 7b 97 oJ.o.Q.L...mSE.. +| 3600: 7b 7f 8b b4 db f7 52 97 08 2e 4f 36 41 24 79 bd ......R...O6A$y. +| 3616: 54 bb b8 2c 32 6c 1f 54 e6 82 47 27 53 7f 57 ea T..,2l.T..G'S.W. +| 3632: 4c 10 71 9b b2 0d 9a 80 fb d2 a6 ec 8d 6e 60 64 L.q..........n`d +| 3648: 1a 73 43 13 b9 cb b1 bd da 84 82 8d 17 ae 75 4d .sC...........uM +| 3664: 1a af 28 9d 01 6d c7 f7 25 8c b5 ef 1b 5e 33 86 ..(..m..%....^3. +| 3680: e7 ce 59 c0 5b 3d e2 46 ab 85 3b b2 c0 9e 7a 75 ..Y.[=.F..;...zu +| 3696: 26 15 cd 6e 6d c2 19 fc cb 3e 5c 5e 95 94 ec 29 &..nm....>.^...) +| 3712: 94 8e d4 e6 0a 38 8c ef fc eb cb 3a 06 97 20 f3 .....8.....:.. . +| 3728: 5f 53 b5 5f 40 33 9e 41 9c db a8 f3 7a d8 aa 0e _S._@3.A....z... +| 3744: a3 37 00 87 ff 13 78 1a 98 c2 b2 aa bf 2d 10 24 .7....x......-.$ +| 3760: 8e e5 9f 9f 3c 4f e4 90 e0 6a 90 44 32 9f ca 6a ....9=J.4.... +| 3888: 9c 65 c7 29 02 bf a6 97 61 3c 6f 46 48 0a e5 a6 .e.)....a7~. +| 3952: 43 f8 b7 ae 2e af d4 9d 67 8d fa c0 24 b2 1c 55 C.......g...$..U +| 3968: ea aa 33 67 22 d4 f3 e7 87 a5 9d fe a3 04 55 ae ..3g..........U. +| 3984: 56 87 2d 21 a9 02 c2 4d a3 88 a3 3f 48 18 83 a7 V.-!...M...?H... +| 4000: 01 b9 6d 60 d4 0f 79 79 8a 4b 66 c3 49 c7 51 0b ..m`..yy.Kf.I.Q. +| 4016: 15 73 df f2 53 eb 79 03 da 55 2f 5d 9f 8f 01 a7 .s..S.y..U/].... +| 4032: c7 5d cf 61 f0 c2 78 52 56 b8 45 7d f2 d8 56 ca .].a..xRV.E...V. +| 4048: 80 84 6e ee 2a 00 ba 00 74 46 0f 76 23 76 79 55 ..n.*...tF.v#vyU +| 4064: 4f 97 57 f2 63 07 11 1f a3 46 07 14 3b 50 43 c0 O.W.c....F..;PC. +| 4080: 72 b2 89 5a 44 15 dc c8 9b 2e f8 ad 69 80 3e d7 r..ZD.......i.>. +| page 13 offset 49152 +| 0: 0d 00 00 00 01 04 3f 00 04 3f 00 00 00 00 00 00 ......?..?...... +| 1072: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 97 ................ +| 1088: 3e 05 06 00 ae 7c 00 00 40 9e 86 c7 f4 d7 a0 78 >....|..@......x +| 1104: b9 63 d2 14 9c 0c 6a 77 07 99 fe 03 10 f7 fd 07 .c....jw........ +| 1120: ea be 72 a1 24 46 49 15 05 16 1f 1c 80 72 1b 98 ..r.$FI......r.. +| 1136: e5 f9 d0 65 50 cb 29 09 d4 2b 17 e2 73 7e 45 4f ...eP.)..+..s~EO +| 1152: 05 c8 b5 bb 1a 81 34 52 32 17 53 96 c8 0d 54 19 ......4R2.S...T. +| 1168: bd d8 c3 af 35 79 a8 75 ae 2d 5d c4 44 eb b2 1f ....5y.u.-].D... +| 1184: ab 9d f6 d8 42 7b 3b dd 10 f4 34 95 57 ff ec cc ....B.;...4.W... +| 1200: 63 3f d3 93 e5 16 6d b5 3d e3 23 7b 32 88 b7 ce c?....m.=.#.2... +| 1216: 7b 9a 8c 46 26 6f ed 2f a7 83 fb fd a7 b7 1b 48 ...F&o./.......H +| 1232: 88 40 20 a5 9b de 7b d9 32 e5 72 1c 85 8a a2 63 .@ .....2.r....c +| 1248: e2 8d 7b 25 a2 13 51 ec fd 0a ed 1b 76 8f 9d 62 ...%..Q.....v..b +| 1264: 2e 9e cf 0f df 58 30 83 e5 90 40 df d2 9f c9 65 .....X0...@....e +| 1280: 3f ae c6 4a 11 a3 16 5b 6f 24 74 95 5e 21 6a c8 ?..J...[o$t.^!j. +| 1296: da 7a dc 82 c2 ab a0 93 c8 dd a8 f1 1d 78 5e 9b .z...........x^. +| 1312: c8 17 c7 e0 4f 53 1d e1 5a 5f 5c bf 23 25 fb f3 ....OS..Z_..#%.. +| 1328: a0 d4 f5 f7 16 78 f8 00 2e 49 1f 7b ff 41 5c 11 .....x...I...A.. +| 1344: 83 9e c7 f4 f5 c7 85 d3 97 dd 67 5a 1e 47 d0 41 ..........gZ.G.A +| 1360: ae cb 4c 47 4f 5f fc 9f bf 0b 20 04 94 08 11 8b ..LGO_.... ..... +| 1376: a8 98 f2 5e 86 19 3b f1 0f 9d 49 c8 02 14 68 24 ...^..;...I...h$ +| 1392: 20 71 28 49 3c 60 5a 92 ea d3 2c cd 8a 8a d7 cb q(I<`Z...,..... +| 1408: fd 14 db 41 3c d0 24 89 46 6a c4 52 8d f0 c0 dd ...A<.$.Fj.R.... +| 1424: b4 a6 0f bd c2 5a fd d1 53 bb a9 82 88 64 4a 34 .....Z..S....dJ4 +| 1440: 0f 8f ed 49 49 81 03 29 c3 c3 01 00 6a 83 21 dd ...II..)....j.!. +| 1456: bc 98 29 fb 2b 5e 78 95 4a 57 4f a7 d3 ec 44 a8 ..).+^x.JWO...D. +| 1472: 97 6c 7f 39 11 6f aa a0 60 b1 71 43 a6 45 ea 9b .l.9.o..`.qC.E.. +| 1488: e1 6f 67 2b f9 e4 8a 55 60 5b 69 f3 54 bf 5c 78 .og+...U`[i.T..x +| 1504: 32 d4 25 22 bf 9b 7a a6 a8 a3 70 53 7d e8 50 1c 2.%...z...pS..P. +| 1520: cd 2a 4a 92 a7 f1 e4 84 39 8e 72 a4 cf 3a c8 b2 .*J.....9.r..:.. +| 1536: 3e f1 06 f2 83 80 0c 1c 10 91 7b 41 92 f5 df 0f >..........A.... +| 1552: 7a dc 04 78 3c b4 cd 96 b6 15 f6 7f 85 33 db 09 z..x<........3.. +| 1568: 92 de 83 ef fc 2c 75 43 49 0c 12 61 48 14 38 d3 .....,uCI..aH.8. +| 1584: b4 fb 43 33 74 3a 0a 5b c5 03 0a d8 f2 2c 1d cf ..C3t:.[.....,.. +| 1600: e9 a0 7f be 25 24 90 77 75 2f 15 a7 ff 62 83 ef ....%$.wu/...b.. +| 1616: 01 04 b1 7c c8 5d 03 5a 28 1d a3 24 e9 7f 32 73 ...|.].Z(..$..2s +| 1632: 19 59 8c 07 30 10 be 58 c2 48 f6 d1 c6 3c b3 09 .Y..0..X.H...<.. +| 1648: e0 bf 5c c3 47 81 c8 04 71 ab 55 cb a5 7c 7a a8 ....G...q.U..|z. +| 1664: 45 28 03 75 e9 82 51 e8 bd 41 d4 1e cc b1 fe 43 E(.u..Q..A.....C +| 1680: 4d 5c 66 25 6f fa da a6 fa ea d3 de 0a 2e 5a 3d M.f%o.........Z= +| 1696: 68 65 10 b6 ee 3c 0b f6 13 bf 3e 6e 57 f3 30 43 he...<....>nW.0C +| 1712: f6 f4 e0 34 e5 47 88 73 9d 9c 36 f0 90 98 0e 5a ...4.G.s..6....Z +| 1728: 3b ab 0c 7f 8b ab ea 9c 8c 5e 34 26 d7 53 bb cb ;........^4&.S.. +| 1744: 18 58 a7 6f bf 78 43 7b 47 a6 3b 09 f7 9f 5f 81 .X.o.xC.G.;..._. +| 1760: 87 c0 17 02 8f ad 78 32 8c dd 96 0e d8 b8 66 66 ......x2......ff +| 1776: 23 91 e3 e3 37 5c 63 f4 16 c1 58 4f b7 63 de 7c #...7.c...XO.c.| +| 1792: 36 c3 67 fc 32 a3 d8 84 20 fe e6 0f 2e 2d ee 11 6.g.2... ....-.. +| 1808: 3b 97 1a 05 47 ea a1 71 c4 11 08 b1 a9 52 b7 54 ;...G..q.....R.T +| 1824: bf 4c 06 dd f5 a3 ec c2 ce eb c3 82 e0 5f 5c c1 .L..........._.. +| 1840: 51 9c e9 d7 d1 45 a6 ea 62 dd 3b 25 79 0a 5e ed Q....E..b.;%y.^. +| 1856: 5a 9d 81 8e 7c a8 fd 60 31 88 80 f3 df de 60 23 Z...|..`1.....`# +| 1872: 4e 07 7a 02 dd 89 7d 2c bd ce fa 45 ff c6 05 f4 N.z....,...E.... +| 1888: ff 2d 18 f5 13 37 c8 d8 be e9 6d 81 da 8f 03 e4 .-...7....m..... +| 1904: 82 0a c3 3b 19 10 de 4a d8 12 37 37 ce 51 4d 41 ...;...J..77.QMA +| 1920: 0a ad 6a 1f c2 80 49 84 15 7d d4 f0 53 5d d1 b1 ..j...I.....S].. +| 1936: b3 c7 27 34 2f bd 05 af df 5c 5f 86 90 55 99 e5 ..'4/....._..U.. +| 1952: 4a 9f 86 34 30 b1 fe 0d 14 2c 1a 85 28 0e 09 5e J..40....,..(..^ +| 1968: d0 31 99 e7 8c ff 6e f9 e6 a5 cb fd 85 9b f7 31 .1....n........1 +| 1984: a0 07 f4 cc ca 5d c2 37 f9 a3 6e d2 5b 2e 04 32 .....].7..n.[..2 +| 2000: 19 03 7a ee fa 9c 5c 68 89 f7 94 b9 19 fe 05 d2 ..z....h........ +| 2016: 19 f8 3b fd cf dd 6f 23 3f c3 28 15 8a 8c 86 4b ..;...o#?.(....K +| 2032: 8a ff ed ca b7 6a 69 4a fc 66 94 a0 ea b7 fa f0 .....jiJ.f...... +| 2048: b1 3b 67 85 2b 82 e4 7f 3b 8d da 02 9e 27 fb 69 .;g.+...;....'.i +| 2064: 7b 20 c6 f6 fb c2 18 fa d5 6d 60 22 be 01 93 28 . .......m`....( +| 2080: 08 71 9f b2 b0 e6 79 94 91 b6 b8 b0 ff a7 3f d8 .q....y.......?. +| 2096: e3 eb b8 8f c7 02 5b f1 35 b7 15 db 01 cd c9 f6 ......[.5....... +| 2112: d9 83 36 2f d0 8b 43 66 e2 3a 14 34 70 1e 68 e3 ..6/..Cf.:.4p.h. +| 2128: e3 a7 fd 7f 51 cb ec 60 ca fb 13 fc 11 56 97 46 ....Q..`.....V.F +| 2144: 23 53 0a aa 8d 15 de d4 cd 2b 1b de 3f d5 57 9c #S.......+..?.W. +| 2160: e5 91 3b 15 98 3e 09 d9 4e d9 25 14 01 32 fc 23 ..;..>..N.%..2.# +| 2176: 38 60 b2 8f 00 f3 21 8b 20 be 8f 6e 02 9a 58 3c 8`....!. ..n..X< +| 2192: a7 a7 6f 3e 24 cc 1f 87 ab e6 12 0f bd 0a 8b 38 ..o>$..........8 +| 2208: 17 b2 ee 80 80 bb 50 df e0 cb 72 0a 98 07 40 bc ......P...r...@. +| 2224: 8d 77 86 27 e8 07 57 4b 50 84 4c b4 73 b4 32 3d .w.'..WKP.L.s.2= +| 2240: 89 61 e8 b8 dd bf 22 c8 06 07 e6 8f 6d 54 c7 e3 .a..........mT.. +| 2256: 12 f9 1f 61 cb e2 40 e1 14 34 9a 2b 16 a7 1f ec ...a..@..4.+.... +| 2272: 85 b8 76 8e 25 59 51 5a 19 82 d9 54 7f 3d 11 05 ..v.%YQZ...T.=.. +| 2288: a6 4c 43 28 9c 65 44 5e 57 fe 04 84 ae 7f 26 5b .LC(.eD^W.....&[ +| 2304: ed 26 e2 1c 07 1f fa f1 99 7b 69 f0 03 16 bf 7c .&........i....| +| 2320: 13 16 75 80 d1 14 17 fa c9 f4 3b e5 9a 06 c0 d4 ..u.......;..... +| 2336: c2 55 53 0a 78 32 86 3c 23 a4 f8 b6 5b d4 9a 51 .US.x2.<#...[..Q +| 2352: 7c c3 1b 56 b1 e7 cd 83 02 75 8c 5e e3 6b b3 d3 |..V.....u.^.k.. +| 2368: 03 23 21 75 3f 78 a7 0f 66 87 2e 85 37 6b bc 0e .#!u?x..f...7k.. +| 2384: 2f 27 cd da 3c 1e 1a 35 2b bc 2d 97 ec 55 df 22 /'..<..5+.-..U.. +| 2400: fd 19 04 fa be 12 2a 1f 4b ed e3 0d 87 f0 61 db ......*.K.....a. +| 2416: 06 1f 86 2f 9d 80 32 97 df 70 ff b4 78 df 95 7d .../..2..p..x... +| 2432: a3 db a5 d0 db a0 b9 ed 4f a7 63 14 bf 8b 99 fd ........O.c..... +| 2448: 6e 1b d0 02 07 93 e1 be 56 4e d5 c1 41 a6 f0 ea n.......VN..A... +| 2464: 37 aa 21 7c 51 2f 5b c4 a0 ae ba e8 a7 1d c6 a5 7.!|Q/[......... +| 2480: 1e 8b 3b 86 7b f2 ea 57 b7 2f 88 37 5e 7a 89 e6 ..;....W./.7^z.. +| 2496: 09 c0 e4 3b aa bf 78 41 7c 00 14 cc 37 f1 1a f6 ...;..xA|...7... +| 2512: a5 79 49 f0 5a e0 1e 5d ba 33 ef fe 89 6e 20 01 .yI.Z..].3...n . +| 2528: 96 70 7d 58 0a 98 e0 c4 3e 20 b2 46 83 b8 1f c9 .p.X....> .F.... +| 2544: fe 78 8a 41 a9 ca 3f 2c c1 d4 35 a3 39 85 4d a1 .x.A..?,..5.9.M. +| 2560: 23 5e c7 b5 79 75 00 e7 d5 fb 30 99 00 c4 d0 31 #^..yu....0....1 +| 2576: 80 68 f2 80 1b 5e c8 30 f6 36 e1 a1 b5 87 92 35 .h...^.0.6.....5 +| 2592: 6b 9c ac 39 39 3f 54 e9 4d b4 8b bd 09 4c 6f 9a k..99?T.M....Lo. +| 2608: 9a 6d 65 1a 7a 2f 98 0f 33 32 3a 4c bd d6 c3 d5 .me.z/..32:L.... +| 2624: 2b 3f 7f 0a d7 f7 f0 05 88 5e 36 30 4d a2 0a 65 +?.......^60M..e +| 2640: f6 a9 5b 2d 5e 3f 0e 13 c9 45 1d c9 28 19 6d ba ..[-^?...E..(.m. +| 2656: ff 9d af 10 17 b2 49 34 d8 a1 c8 ce d2 b7 99 3e ......I4.......> +| 2672: 7c c6 f5 d6 74 84 05 84 2a bb 2d 25 27 67 04 f0 |...t...*.-%'g.. +| 2688: b3 6d 0a a9 43 f6 c4 51 71 1d 48 8d b3 a4 47 ed .m..C..Qq.H...G. +| 2704: d6 14 c3 27 3e 6a e9 64 ef d7 af fb b0 eb ec 3c ...'>j.d.......< +| 2720: bb 92 77 32 5e 4f 20 6d f7 a4 94 ce d9 9b c5 78 ..w2^O m.......x +| 2736: 84 f0 c3 1a ca d8 e2 ce 76 0a 5e f8 73 9d 5a 82 ........v.^.s.Z. +| 2752: 1e f5 0a 43 41 77 b9 d1 17 ef 47 0f 04 7b 5c b9 ...CAw....G..... +| 2768: 3d 96 ec 7c b6 45 7d f9 fe 46 cb d2 50 a3 7f 99 =..|.E...F..P... +| 2784: 16 ef 8d 2d d5 55 30 d8 27 18 bb f9 fa 76 80 55 ...-.U0.'....v.U +| 2800: 13 1e f3 c2 dc 11 ef 8b 5d 67 d2 cd 70 74 b0 93 ........]g..pt.. +| 2816: f9 51 ec 35 24 cc 01 55 9f 92 a5 10 7f 66 07 e9 .Q.5$..U.....f.. +| 2832: f9 0a 32 06 db fd a6 ed 3e c6 b4 55 41 45 6c d3 ..2.....>..UAEl. +| 2848: da a9 57 98 b1 79 3e 3a 6a e3 c4 37 3c 81 ba 16 ..W..y>:j..7<... +| 2864: 3b 6a 3b 29 93 80 50 6d 94 05 dd 6b 37 10 f7 5c ;j;)..Pm...k7... +| 2880: 5a 23 ef 13 1b 49 a5 fb 7b e8 49 a8 2a f4 e2 f4 Z#...I....I.*... +| 2896: 0b a6 28 03 5b 3e ce c0 f6 b4 d3 c3 b5 ed 2f 87 ..(.[>......../. +| 2912: a3 66 13 93 45 50 d5 3d 59 e4 42 fa f7 00 d8 b8 .f..EP.=Y.B..... +| 2928: 7a 3a cc ce fc d2 8d a5 e9 1b 63 f8 21 6e 4f a3 z:........c.!nO. +| 2944: 0e 17 92 56 d6 2e c9 a4 b8 07 2f 25 9e 3b ff a7 ...V....../%.;.. +| 2960: 9d 31 56 43 85 d6 ea 91 ab 29 8c 1e bd 29 f2 9c .1VC.....)...).. +| 2976: 9c 48 81 a2 a9 da 80 c8 b6 8a 67 48 1d c0 02 4a .H........gH...J +| 2992: fa a1 e3 69 15 83 c5 ca 9d 96 43 06 ad 97 c3 5c ...i......C..... +| 3008: 09 e0 de b9 0d 4e f5 99 75 e1 c0 98 20 91 c3 5d .....N..u... ..] +| 3024: 93 81 0f bf 2b 96 96 c0 9d 30 d3 84 c0 9e 60 56 ....+....0....`V +| 3040: 08 0a 3d 48 be 6b 3d 32 c4 5b e8 87 02 88 f6 f3 ..=H.k=2.[...... +| 3056: 71 71 07 f8 66 42 8d df 4e aa 44 6f ec 3a b8 7e qq..fB..N.Do.:.~ +| 3072: 4d b3 83 74 b4 bf 34 d2 2c 95 3c 44 9b c1 cc 9a M..t..4.,.+......6..#. +| 3120: 1b f5 ce 42 b0 0c 11 d4 69 a4 bf fa 4b 30 34 b4 ...B....i...K04. +| 3136: 52 48 84 cc 31 08 03 a9 93 d9 e5 c8 44 cd cf fe RH..1.......D... +| 3152: e4 2e c5 3a c2 b2 d9 bc 15 4d 7f 7a 81 52 9c 53 ...:.....M.z.R.S +| 3168: 3e 1a 74 78 c8 38 4a 02 ce 6b 96 2d 9c 3b 74 41 >.tx.8J..k.-.;tA +| 3184: 50 06 d9 6f fa c8 98 05 f5 70 c3 7f ca 0a 78 f4 P..o.....p....x. +| 3200: 01 f1 a2 f5 46 8a 02 b8 c3 37 7e cc a0 87 b0 fc ....F....7~..... +| 3216: 32 e5 86 6d 95 22 67 fd 10 56 c4 78 52 c5 12 e5 2..m..g..V.xR... +| 3232: e9 f2 b2 fa f6 78 2e a3 82 a6 73 1b 9f 96 a7 e6 .....x....s..... +| 3248: 81 49 e9 17 f1 0a e6 99 21 ce ec ff be aa 43 e6 .I......!.....C. +| 3264: ea 6a ac 2b a4 b9 0b 48 6c 81 1a 26 bf 93 69 36 .j.+...Hl..&..i6 +| 3280: 53 1e 46 2a 43 af cc ef 96 4e 0b 32 38 93 10 d5 S.F*C....N.28... +| 3296: d8 6f 88 e7 f9 92 f8 37 28 51 55 98 fa 1a 89 80 .o.....7(QU..... +| 3312: 35 d5 d1 90 f1 91 8f 4a 5a 81 2f 79 a7 e4 80 3c 5......JZ./y...< +| 3328: a2 5e e5 b3 f8 d3 56 ec cd 02 73 0e af 8d d8 81 .^....V...s..... +| 3344: 26 7b 4d 96 f1 b3 75 7a de c3 b8 a6 b6 75 f2 5c &.M...uz.....u.. +| 3360: 97 08 a3 50 9b d8 cf 43 c0 90 f7 52 da 87 8d 12 ...P...C...R.... +| 3376: 1c d4 0a de 56 26 3d af d1 d5 03 ff d8 62 a1 c5 ....V&=......b.. +| 3392: f2 7c ea 1d 39 3f f0 e1 5f cb 3d db 1a 8a a1 b7 .|..9?.._.=..... +| 3408: 10 01 f1 c6 17 53 bc 83 eb 2a c4 cf 3a 04 b1 8a .....S...*..:... +| 3424: 6d 80 20 be 7b 72 6c c9 be 4e eb f4 cd c8 60 d2 m. ..rl..N....`. +| 3440: 1c 46 b1 8a e5 9b 66 22 4f 05 bd c6 37 34 67 2d .F....f.O...74g- +| 3456: b1 c5 bb 60 de 4b c4 3a 29 14 dc de 38 9c 0d 09 ...`.K.:)...8... +| 3472: bd 78 2f f6 e4 34 18 9b 51 41 a7 57 f9 f2 ad 0d .x/..4..QA.W.... +| 3488: 82 70 5f b4 fa 84 19 5c 72 29 8e 5f b2 8b fa 2a .p_.....r)._...* +| 3504: e1 d1 55 bf b8 45 c3 79 e0 04 62 10 1f 76 79 de ..U..E.y..b..vy. +| 3520: 84 93 a8 df 92 8f 41 0d 38 14 70 b3 dd 9d c6 36 ......A.8.p....6 +| 3536: f6 12 72 ee 23 e6 ba ed 97 1f cd 36 55 ac 32 98 ..r.#......6U.2. +| 3552: 78 12 55 23 d5 64 ad ed cf 32 d3 2d 7b 51 d7 7f x.U#.d...2.-.Q.. +| 3568: 3c 6f 0f fb c7 06 75 96 ef 64 ce c4 09 15 4f b8 ....|....c/.... +| 1104: f2 f2 37 ff aa 18 0f 69 94 cc 49 85 cd f7 bb 7b ..7....i..I..... +| 1120: e5 c0 09 d1 a2 78 f4 c0 1b 23 e7 d5 ac 2d 85 27 .....x...#...-.' +| 1136: c2 48 c2 80 ad 93 a0 10 44 98 5a 6e 9e f1 92 a9 .H......D.Zn.... +| 1152: 98 f9 0d 00 2a 3a 22 36 c5 21 63 fb 95 fb c7 7e ....*:.6.!c....~ +| 1168: 6c 07 7d d1 a5 f1 a9 8f f2 d2 e2 0f 3d 02 56 94 l...........=.V. +| 1184: ea 44 2a 17 ee 99 82 71 9c 81 c7 9c 8f cd 2e 4d .D*....q.......M +| 1200: 8d 0a 1e 71 18 3d 4d 9d a6 ae 35 36 48 08 24 e8 ...q.=M...56H.$. +| 1216: 46 78 8e 66 f2 8a f1 3d c3 72 ad 65 18 5f ed 2d Fx.f...=.r.e._.- +| 1232: 7e 92 18 9e 4c 23 7c f8 8d 83 20 43 50 63 db 75 ~...L#|... CPc.u +| 1248: 12 7e 52 0e 71 13 e4 b6 31 c3 5f 88 b7 d0 76 80 .~R.q...1._...v. +| 1264: d5 9f 7e dc 8c b4 be 11 da 2c e1 a9 28 5e c8 46 ..~......,..(^.F +| 1280: 3c 0d cd 7a 2f b9 9f 53 9c 11 01 d1 09 31 5f 36 <..z/..S.....1_6 +| 1296: bb a4 f3 a0 ad ba b7 c4 ce d1 78 4e ad e8 cd f9 ..........xN.... +| 1312: 92 ed 51 05 69 ab 12 a3 89 4c 6d f5 7a c4 c5 54 ..Q.i....Lm.z..T +| 1328: 1e 26 e7 f8 22 1c 42 cb 9e ac 7a 53 79 09 3c 04 .&....B...zSy.<. +| 1344: 14 1d 49 15 c7 13 00 84 3c 7f 15 2d 5b 62 da d8 ..I.....<..-[b.. +| 1360: 8f 88 53 7a 78 f7 c4 7b be 67 25 cd 97 1f bf 19 ..Szx....g%..... +| 1376: b4 8f 4a 10 63 89 2f cf 56 c1 63 79 92 cc b1 75 ..J.c./.V.cy...u +| 1392: 45 ee 6a 9d c5 de 04 e0 60 49 c5 0e 09 a0 65 46 E.j.....`I....eF +| 1408: 57 f3 6c 5f 0f 07 99 fa 2b 2c 2c 5b bd b3 9b 50 W.l_....+,,[...P +| 1424: 38 43 94 4b 7b d2 b0 78 3b 4c e8 55 1d d3 6b 6a 8C.K...x;L.U..kj +| 1440: f7 dc 27 ef b5 b1 3a 52 9f 5a e9 7d 87 bb 61 5b ..'...:R.Z....a[ +| 1456: 39 a3 eb 5e eb f1 80 78 85 d8 86 16 76 3c b3 b0 9..^...x....v<.. +| 1472: 44 72 d7 0e 91 95 5e 7d d5 93 0a 4d 60 46 ad a8 Dr....^....M`F.. +| 1488: b7 d5 30 e2 39 4c d8 0a d4 ea 5e 46 95 39 1a 1e ..0.9L....^F.9.. +| 1504: 9d 81 27 20 bc 01 1e f5 2d 4a 19 4a a5 2d 94 91 ..' ....-J.J.-.. +| 1520: c2 a3 31 65 aa 13 f4 92 ce b2 c1 61 25 13 7d 93 ..1e.......a%... +| 1536: f0 c9 51 e5 ce 7c 0e 2d e4 f2 d4 b5 09 73 fc 96 ..Q..|.-.....s.. +| 1552: e0 a8 a7 85 b6 10 0f 95 a5 83 c5 4b ca 33 9f 98 ...........K.3.. +| 1568: 38 d9 50 29 38 08 cc b9 89 40 0e d7 1d 92 cc 4a 8.P)8....@.....J +| 1584: 7f a2 53 dd 19 d3 13 63 04 29 04 18 c2 79 97 36 ..S....c.)...y.6 +| 1600: 85 e8 95 4a 2c 34 7c 39 78 a5 fe 8b bb 22 93 4c ...J,4|9x......L +| 1616: 69 b6 86 ca 0c 61 39 b3 85 c1 df ee 28 b0 3d 73 i....a9.....(.=s +| 1632: 2f 78 84 69 f4 55 ed 6c b8 98 61 0c f5 64 15 7e /x.i.U.l..a..d.~ +| 1648: 84 bd 4f de ae 6c 1b db c1 60 c9 0a b9 22 f0 7d ..O..l...`...... +| 1664: 03 23 0f e3 f5 77 3a 3b 4b 7f 33 d9 e6 7c ee 92 .#...w:;K.3..|.. +| 1680: 0c 95 f6 9d 98 53 e3 7b 92 3b 15 a2 de 84 e6 75 .....S...;.....u +| 1696: 64 e1 44 b5 9d 16 77 10 78 65 e0 72 66 a9 13 db d.D...w.xe.rf... +| 1712: 27 cf 9f 0d 33 5d ee 89 37 cf 2f b6 fc 28 b1 93 '...3]..7./..(.. +| 1728: c3 6d 0f 80 d8 5a 16 33 ea 06 d7 81 d3 45 59 94 .m...Z.3.....EY. +| 1744: 07 73 bf 47 f6 fd 6e 78 13 5a ec 30 62 a4 c4 12 .s.G..nx.Z.0b... +| 1760: 2a 56 8f 29 03 37 0c 98 80 8e e9 50 4f 3a 37 a6 *V.).7.....PO:7. +| 1776: f6 29 3a 6f 17 54 45 8e df 98 62 1e 23 d0 ec 20 .):o.TE...b.#.. +| 1792: 4b c8 f6 cb ce ef f5 8b f2 fd a9 a8 51 5c 0f d0 K...........Q... +| 1808: e4 4d 7c a1 a1 2a 88 f1 24 78 9e cb 7c 81 6b 0a .M|..*..$x..|.k. +| 1824: 51 73 1e bf 48 21 fb b3 7a 5c 48 0f 53 04 00 5f Qs..H!..z.H.S.._ +| 1840: 7e 66 7a 2e c0 30 9f bf 2f e8 95 b5 fe ea 9c f8 ~fz..0../....... +| 1856: 0e 6d 1f 54 81 da e9 bc 28 ea 42 de 5b e0 7f 93 .m.T....(.B.[... +| 1872: 79 6d 6d 8e f3 3c ba 13 67 3a 08 31 03 43 da eb ymm..<..g:.1.C.. +| 1888: 34 40 04 3e a4 21 44 92 96 75 2c 9f 8f 03 89 98 4@.>.!D..u,..... +| 1904: f0 27 dd 27 49 3d e3 9b 37 4e 66 ef 98 bd 1b ed .'.'I=..7Nf..... +| 1920: 3f cc a4 10 85 a4 39 90 cc a3 80 3d fd e5 44 4f ?.....9....=..DO +| 1936: 45 79 64 37 d2 70 ba 69 99 0f 4d 33 5a 68 bd 53 Eyd7.p.i..M3Zh.S +| 1952: 92 24 0d 3f f4 e2 4b 72 35 cb 52 ea 3d d4 85 ee .$.?..Kr5.R.=... +| 1968: e3 12 b3 87 3a 9d 70 b8 5c bb c7 c4 98 03 84 25 ....:.p........% +| 1984: 5d 39 ef 24 bc ab b3 da 13 7b 2c bc 17 b1 76 4c ]9.$......,...vL +| 2000: 5d 5a 1c 62 14 8d 5d 20 32 dc 16 64 79 7e 13 95 ]Z.b..] 2..dy~.. +| 2016: 27 f6 99 dd f2 32 c2 43 c6 77 b4 f4 11 88 6d 3f '....2.C.w....m? +| 2032: 94 0e cb db c8 c3 9e 40 b1 7d d4 16 e6 95 f4 d9 .......@........ +| 2048: 3d 9b 5d df d5 a3 39 06 c1 eb a1 30 cb 85 77 d6 =.]...9....0..w. +| 2064: 9b d9 12 7a 6a 82 9e 3a bf 5a ff 5c b8 6f 4a bb ...zj..:.Z...oJ. +| 2080: 02 36 94 1d 5a 0c 95 da 85 d1 9c 22 5a 36 fd 28 .6..Z.......Z6.( +| 2096: 51 cf 48 60 84 a1 ce ad a9 5f 4c 51 43 71 7f d5 Q.H`....._LQCq.. +| 2112: 1a e1 6f b0 01 a5 6b f1 57 91 84 78 b0 d5 06 7c ..o...k.W..x...| +| 2128: f4 96 3d 9b e4 6d 04 b7 26 a0 5b 61 53 39 ba 49 ..=..m..&.[aS9.I +| 2144: 2c ea d8 18 e5 aa b3 ef 3e 8e 5d 91 ba 93 15 2b ,.......>.]....+ +| 2160: 4d e6 5c e7 47 ee 7c 9e d5 ca f4 82 91 84 f4 33 M...G.|........3 +| 2176: 02 46 9d dc 34 8d f1 5b 1c 15 5d db 2f c9 d0 09 .F..4..[..]./... +| 2192: fb a4 cf d9 0d 30 9f 16 3b 5e 80 ff 26 ac 19 b7 .....0..;^..&... +| 2208: b2 56 1a bb 72 7f b9 2f e7 41 6a e3 fc e5 cc e0 .V..r../.Aj..... +| 2224: 27 2c 78 86 1c 11 ee f4 51 2d 22 20 73 3b ed d6 ',x.....Q-. s;.. +| 2240: 42 c2 de 23 de c6 7d 43 8f 16 de 66 57 64 94 32 B..#...C...fWd.2 +| 2256: 13 da a6 91 b7 d5 8f 3a d6 c4 a7 e0 93 ff f8 9c .......:........ +| 2272: e3 70 a9 e9 e9 7a 50 37 3c 38 d3 fd 68 ff 17 5f .p...zP7<8..h.._ +| 2288: 96 b7 b4 77 c5 ce 1d 37 49 35 91 6c 2e 37 ef ac ...w...7I5.l.7.. +| 2304: 18 ee 9f 7a e5 28 88 a6 33 39 05 f7 c8 1f 1a 15 ...z.(..39...... +| 2320: f6 ad 55 5e 24 ea ce 21 c8 e2 1e 63 bd 7a 19 60 ..U^$..!...c.z.` +| 2336: c0 aa e4 42 2f 0f 53 f4 2c 04 99 a4 9a 13 86 6f ...B/.S.,......o +| 2352: 24 7b 42 ad c9 a9 c9 df 82 90 23 0a b6 e8 2a e4 $.B.......#...*. +| 2368: 36 c5 04 1a e4 dd 87 06 ca 6e 04 7b be 37 b4 ef 6........n...7.. +| 2384: 05 1d 6a ab 36 c8 58 52 3d ad 26 21 45 58 f4 66 ..j.6.XR=.&!EX.f +| 2400: 05 a2 95 89 9c 31 1c 3c 14 9b 59 90 29 28 f9 9d .....1.<..Y.)(.. +| 2416: f9 96 74 ab d7 30 91 81 17 ba b8 08 51 38 75 77 ..t..0......Q8uw +| 2432: 06 ca 81 1d e3 9b 67 3e 0f d9 fe 98 69 e0 d8 f1 ......g>....i... +| 2448: 78 dc 96 0e 88 50 9c b5 c1 1d 25 61 87 70 bf a5 x....P....%a.p.. +| 2464: 93 2f f9 c7 7b ab fd 2c 98 10 ef 0f ce a4 bd fa ./.....,........ +| 2480: 52 7f ff 29 53 5f 9c b5 92 2e 8c f5 36 a0 7c 88 R..)S_......6.|. +| 2496: 84 18 80 59 66 d2 c6 58 55 c0 5d 07 9f 69 d2 72 ...Yf..XU.]..i.r +| 2512: 88 0d 1d 8f 08 7c d5 b7 9c 15 4c 6c c6 1d f4 76 .....|....Ll...v +| 2528: 0a 82 32 6c 72 4e 3e cc ff 8b 62 c5 37 4e 0c 0d ..2lrN>...b.7N.. +| 2544: 09 89 8a 95 1b 63 d3 85 c6 4a 41 7c 49 46 60 95 .....c...JA|IF`. +| 2560: eb 37 60 82 84 55 ea fe 22 97 16 58 ad 33 06 ae .7`..U.....X.3.. +| 2576: d0 f5 5d 06 b3 2e b9 0a 6d d0 14 c8 d5 0b 8a cf ..].....m....... +| 2592: 51 ff 64 db a8 81 e1 8b 39 1a ef 85 18 fd a2 48 Q.d.....9......H +| 2608: 4f 47 b3 1e 5a 57 21 f0 6b 10 f0 ec 0a 4a 35 56 OG..ZW!.k....J5V +| 2624: f5 79 c5 de 62 d1 4a c2 e5 bc 69 89 9d 9b b9 4b .y..b.J...i....K +| 2640: ba 21 99 5a 58 c6 23 ab 67 45 13 4a 1d 3e da 68 .!.ZX.#.gE.J.>.h +| 2656: cf a6 4b 20 58 b0 4e 2f 0d f2 42 83 9d 96 43 6e ..K X.N/..B...Cn +| 2672: df 45 61 c5 bd ca 86 b3 6a ad 26 a2 8d c7 c4 03 .Ea.....j.&..... +| 2688: 63 d8 b8 02 0a f6 96 f9 15 46 2c bd 04 68 aa 1b c........F,..h.. +| 2704: 00 46 7e a0 bb 47 fe 5f 2b 7b b4 59 54 14 e3 25 .F~..G._+..YT..% +| 2720: cd 10 8b ff 05 6c 6d ab 9c 90 cb d4 d8 0d bd 58 .....lm........X +| 2736: be bc d5 c0 67 bd 0f d3 9d 22 71 c4 65 e6 2f bc ....g.....q.e./. +| 2752: 5e 3a 54 39 7a ec ab b1 46 80 25 5e 33 19 de 3b ^:T9z...F.%^3..; +| 2768: 4a 84 28 04 80 f6 86 aa 32 75 8e 5b 51 66 b0 64 J.(.....2u.[Qf.d +| 2784: 29 94 f6 56 6d 1c b1 2e 2f 4c 55 3b e5 82 01 75 )..Vm.../LU;...u +| 2800: 3f 58 d3 a0 b9 0b 6b 25 24 94 9e a5 ce a4 1c db ?X....k%$....... +| 2816: 51 f9 bb 53 71 19 0a 5a e3 2e 48 e1 06 24 7a b9 Q..Sq..Z..H..$z. +| 2832: 88 fb dc 4c 2a 23 b9 b9 7e 26 a4 3f 7f c7 b5 f1 ...L*#..~&.?.... +| 2848: cd cb 82 17 34 89 70 b4 10 ad be b8 4b 0d aa 07 ....4.p.....K... +| 2864: 61 6d b3 6f 56 ac a4 b2 e4 2f be 28 e6 24 3a ab am.oV..../.(.$:. +| 2880: cc 1a 8a 7f 54 c7 a3 c8 49 50 91 76 f9 75 d8 3a ....T...IP.v.u.: +| 2896: 76 1b ec 5b 51 c2 5f 6f c9 c7 60 24 0a 61 f4 ed v..[Q._o..`$.a.. +| 2912: d3 30 23 e7 a0 83 c7 5e f4 b8 1b 39 65 43 2a 8a .0#....^...9eC*. +| 2928: 1d a8 64 4d c5 4a a6 ab e1 f4 66 90 f7 2b 1c d6 ..dM.J....f..+.. +| 2944: ba 76 96 43 2d a1 6b 26 ee f1 3b 8b 2f e4 78 7e .v.C-.k&..;./.x~ +| 2960: 9b bd fa 2e 80 ff ec 26 74 5b 56 11 92 88 51 8c .......&t[V...Q. +| 2976: 6d 42 4f f5 3c 53 8b 42 3e b6 57 37 fe ad 66 59 mBO..W7..fY +| 2992: 4a f4 0e a1 39 a2 32 c0 23 78 e0 e7 db f9 f9 44 J...9.2.#x.....D +| 3008: 34 12 00 a0 d2 1f 45 25 4f 4a 72 74 9e b2 d5 e4 4.....E%OJrt.... +| 3024: 25 4e d5 0f ba 1b 87 7f 23 f2 7f dd 96 6c 1c 44 %N......#....l.D +| 3040: 0d 11 9a 22 6a f8 83 bf 37 e6 c9 5d 2e d3 dc d3 ....j...7..].... +| 3056: aa 03 03 7e bf a5 cc a1 d8 55 6f 05 1a 70 5e 89 ...~.....Uo..p^. +| 3072: d5 8e a5 08 f4 fc 90 7c d2 8a 67 53 82 25 82 02 .......|..gS.%.. +| 3088: 98 e0 ad 57 bb d4 7e fc 18 8f 25 9d d7 11 57 ab ...W..~...%...W. +| 3104: cf de 4b a5 1b 90 6b 38 a3 ef 93 ed 44 d7 9d f1 ..K...k8....D... +| 3120: 54 b1 0b 9b 2d e9 e7 d3 a6 6c 78 fd 40 d9 ea 7f T...-....lx.@... +| 3136: ec 10 60 2f ed 4c df f4 ef fb 4a cd fd 1a ea 94 ..`/.L....J..... +| 3152: 7b ac 69 70 be d1 2c ca e3 88 a7 b4 7f 95 83 eb ..ip..,......... +| 3168: 2e 82 0e 3e 60 27 b9 0c 62 56 3d b2 c4 7d 97 2c ...>`'..bV=...., +| 3184: ac cd 0b 05 b3 41 ab 7c 2e eb 4e a7 3e 8f db 53 .....A.|..N.>..S +| 3200: 88 59 f3 d6 f4 bf 5e 80 2e 5b f2 08 f8 dc 68 28 .Y....^..[....h( +| 3216: a7 a3 e3 4e ef 8f 3f 4e a6 c0 4b 04 d9 59 3e 53 ...N..?N..K..Y>S +| 3232: 07 78 2c b2 58 a6 78 a4 0a 6b 9e 6b 8d f2 3e 70 .x,.X.x..k.k..>p +| 3248: 45 07 82 f9 f0 d4 0f f7 81 e9 ab 09 c6 7a 3a ff E............z:. +| 3264: 98 6c bf 1f 43 c3 d5 ba c1 ad fe 9c a6 5e d5 f7 .l..C........^.. +| 3280: 93 ec 20 dd 33 ad 35 0b 74 47 80 1e 5b 8b c3 7c .. .3.5.tG..[..| +| 3296: 5c 81 66 82 dd 18 70 19 b0 54 18 2f 90 c2 22 bf ..f...p..T./.... +| 3312: 75 e8 b6 8e b7 3e 7c 2d b4 e9 7c b4 08 4e e9 c3 u....>|-..|..N.. +| 3328: e1 80 88 63 f2 90 ee ae 3d 9e 55 15 62 7f ee df ...c....=.U.b... +| 3344: 57 69 03 da 76 e9 f3 6b 90 9a a7 b9 bb 4b b7 2b Wi..v..k.....K.+ +| 3360: d0 a0 3b 70 b9 96 70 17 c8 0c cb ae 6e 3e e1 33 ..;p..p.....n>.3 +| 3376: 02 98 53 d6 af c6 d8 55 15 7a ea f1 8f 36 6b d4 ..S....U.z...6k. +| 3392: f2 8c fd db a5 81 50 fe c8 a8 cb 7f e6 9a a9 9f ......P......... +| 3408: 34 82 6d a5 11 6b e6 79 58 a8 54 ed 55 cd 19 46 4.m..k.yX.T.U..F +| 3424: e9 7f 92 f8 0b 3e ab b5 8d d3 9f 33 5c e7 f6 1e .....>.....3.... +| 3440: 28 f7 5f 2f 38 36 25 ed 7f 36 93 19 f1 a7 9c e7 (._/86%..6...... +| 3456: ad 4e 11 33 17 93 82 cf b4 a7 93 36 97 d9 e0 3d .N.3.......6...= +| 3472: 7e 54 b9 96 37 85 16 db e8 29 6f b0 b1 83 16 63 ~T..7....)o....c +| 3488: 9b 19 30 85 c6 17 2c b7 bc f2 2f 87 07 56 97 b3 ..0...,.../..V.. +| 3504: c4 a6 47 d5 8b 7a 68 a4 d0 11 ce ff 6c 4c 28 93 ..G..zh.....lL(. +| 3520: c3 37 ef 1e 7a 51 65 a9 6b 8a a0 a4 b0 b9 ef 17 .7..zQe.k....... +| 3536: dc 44 99 34 7b 33 f4 b9 3f d3 20 36 54 5c 10 6d .D.4.3..?. 6T..m +| 3552: 18 25 33 6e fc fa a0 16 c7 b6 78 f8 4b 0c 5e 7e .%3n......x.K.^~ +| 3568: c3 cb 95 54 90 88 57 20 d9 17 9b 82 dc e8 7f c6 ...T..W ........ +| 3584: bd d8 dc 13 ca 66 06 64 8e 46 2e 48 13 eb 4d 07 .....f.d.F.H..M. +| 3600: 24 7f 8e 35 f3 bf fb 54 80 c7 6e 93 da ce 7f 2b $..5...T..n....+ +| 3616: 60 3e 53 82 33 38 fe 69 7b f6 fa 2f d8 40 bd c8 `>S.38.i.../.@.. +| 3632: 95 75 6b a2 e5 53 7f 5c f1 f3 23 07 8f 42 04 8f .uk..S....#..B.. +| 3648: 64 43 a2 25 c1 40 07 e9 be b6 3c d6 1b 86 7f 91 dC.%.@....<..... +| 3664: f4 73 0f 6b ca 6b cb 8f 7c a8 5b 60 ee 20 a6 e6 .s.k.k..|.[`. .. +| 3680: dc 96 6b 8f 8d 62 1e 42 e0 1b 85 49 85 34 b4 c9 ..k..b.B...I.4.. +| 3696: 85 06 ec c4 b7 b5 89 47 af fa db d1 5b 9f ac 6f .......G....[..o +| 3712: 8a 27 53 48 4d ad a1 30 6b 3e 45 b1 b8 b9 50 17 .'SHM..0k>E...P. +| 3728: 7b b7 29 a8 b3 d5 9a 42 a9 2a e4 01 79 34 c5 21 ..)....B.*..y4.! +| 3744: a7 8b 21 69 ab c4 9b ec eb 0d 9f 34 b5 ff 46 2d ..!i.......4..F- +| 3760: 68 55 9b 63 9a ac ac 80 4c 45 8d eb dd b2 26 4a hU.c....LE....&J +| 3776: 91 8f ee f7 50 6f 84 81 c3 79 2e 6d 56 1c ee 01 ....Po...y.mV... +| 3792: 20 2b e5 ca 3a 51 ba 40 b6 44 15 a4 4a 3d d0 33 +..:Q.@.D..J=.3 +| 3808: 7d 2d 3c ca e3 f8 e9 1d bc b2 1e 59 1e 57 10 2f .-<........Y.W./ +| 3824: 92 4b b7 50 41 98 6f 07 0b 38 ba 31 08 3e d4 34 .K.PA.o..8.1.>.4 +| 3840: 4f f4 99 32 2f e5 7f 8f 14 8e 48 11 88 ad 18 d6 O..2/.....H..... +| 3856: db d0 e5 cc 17 4c b6 f7 45 f9 1e 9a d9 86 5d 56 .....L..E.....]V +| 3872: fa 76 52 49 cd f1 76 ea 84 b1 55 03 54 e2 ff 91 .vRI..v...U.T... +| 3888: 23 fb 22 b0 8a b8 da 6e 7e 54 25 03 f3 fb eb 9a #......n~T%..... +| 3904: ce 64 cb 70 38 c9 aa 11 e8 36 45 16 8c 82 0e 18 .d.p8....6E..... +| 3920: f3 cf 81 4c 7c ef 53 6a 5c 43 85 e3 01 c9 d5 97 ...L|.Sj.C...... +| 3936: 8b 03 78 90 35 c9 7c 3a 62 3d 66 d9 ff 6c 1f 3f ..x.5.|:b=f..l.? +| 3952: 7f a1 85 ad ec 87 65 3b 48 3c 0b 0c 94 c0 05 85 ......e;H<...... +| 3968: f9 a4 4a 79 c1 dc 7b 9f 87 c0 4f c9 7b a7 ce 5c ..Jy......O..... +| 3984: cd 11 68 d5 79 d2 e7 b5 8c 13 af d6 e9 81 ef 42 ..h.y..........B +| 4000: 4c 11 2f c0 55 33 4c f4 44 c1 75 67 ae 43 95 68 L./.U3L.D.ug.C.h +| 4016: da 90 26 d5 42 75 b6 ef 99 22 ce b8 2d 52 5f 43 ..&.Bu......-R_C +| 4032: a4 f7 23 72 17 c6 66 36 84 e6 d3 a7 da a9 ec 82 ..#r..f6........ +| 4048: b2 fd 87 90 51 2e 78 71 3f 56 49 96 d2 da 6b 67 ....Q.xq?VI...kg +| 4064: 2a f8 b1 eb ae 5d d0 e7 59 31 ed ed 24 b6 2c 32 *....]..Y1..$.,2 +| 4080: 7a b7 97 c5 e0 f1 a4 05 d3 8e fa 0f 10 ee 19 4d z..............M +| page 15 offset 57344 +| 0: 0d 00 00 00 01 04 3f 00 04 3f 00 00 00 00 00 00 ......?..?...... +| 1072: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 97 ................ +| 1088: 3e 07 06 00 ae 7c 00 00 07 2c f5 e5 48 a9 82 86 >....|...,..H... +| 1104: 40 71 a9 80 b9 af 28 61 b5 17 5a cb c3 67 db aa @q....(a..Z..g.. +| 1120: fa 64 ec 63 b4 34 d5 37 ee 52 d8 d5 38 6a 25 d0 .d.c.4.7.R..8j%. +| 1136: 1f 05 80 72 bd c3 b9 1a d1 ea f7 f0 73 0e 97 29 ...r........s..) +| 1152: 41 19 09 d1 53 df c8 c5 02 ab a2 e8 76 b8 09 3d A...S.......v..= +| 1168: 8b 07 21 05 5f 80 3d 88 67 fd fe ab e6 4b 7f 97 ..!._.=.g....K.. +| 1184: a6 17 42 02 f5 ad f3 74 6b bb 6e 2a fd af 88 3b ..B....tk.n*...; +| 1200: d9 b2 3c 0d 33 a7 93 69 c0 97 c5 c4 c0 e9 29 d5 ..<.3..i......). +| 1216: c3 78 96 13 55 f3 41 e6 87 eb 7a 2f b9 e2 28 f3 .x..U.A...z/..(. +| 1232: e1 ed b8 c8 5d bd 80 80 a6 52 50 ff 0d db 2b f2 ....]....RP...+. +| 1248: 7e 9e 91 32 fd 08 46 ba db 2b 41 00 70 6f cf 80 ~..2..F..+A.po.. +| 1264: 46 1b 49 cf ab 6e 1d 14 21 f2 3f 55 0a 4a ed 19 F.I..n..!.?U.J.. +| 1280: 11 9b 9e c0 f2 c4 55 86 2c 05 4e a2 6f 8c b2 9c ......U.,.N.o... +| 1296: 11 fd 85 0c 5b d5 ee ef 9c f2 4b 1e 74 72 bd a4 ....[.....K.tr.. +| 1312: 29 8d 0d 92 67 97 b5 b5 03 b4 a7 68 15 50 c7 7e )...g......h.P.~ +| 1328: 91 22 a8 39 3e e2 18 bd 68 20 74 c1 e5 f2 8a e5 ...9>...h t..... +| 1344: 1e 72 0c 2a e3 1a b2 60 57 ad 2b ee 1f 59 57 ce .r.*...`W.+..YW. +| 1360: 6f b2 e0 15 96 73 f6 2e 5b 82 b7 ca 6b bf ed 78 o....s..[...k..x +| 1376: e5 af 82 82 73 16 6a ce cb 09 b9 5f b3 2d c0 0c ....s.j...._.-.. +| 1392: 1d 46 5f 7e be ee 0c b3 54 e7 ed e7 cc cf f9 9d .F_~....T....... +| 1408: 42 90 94 e2 e9 c2 76 2b 02 b9 a8 73 20 0a e3 6f B.....v+...s ..o +| 1424: 23 a0 c0 59 f2 af 9d c4 78 90 ba 8e 0c ef b4 d6 #..Y....x....... +| 1440: 37 03 5e f4 7f 54 a6 d7 87 bc 20 3f e1 7a 84 d9 7.^..T.... ?.z.. +| 1456: 1b 33 4a fc bd 1f c1 8b b3 db 8f 0c fa 49 7f d7 .3J..........I.. +| 1472: 9a d0 48 8e c6 3b 79 22 d6 0c fa 93 4d 43 f3 de ..H..;y.....MC.. +| 1488: bb 10 3e 81 d2 6b b4 9f 4c 91 05 d7 87 b5 35 98 ..>..k..L.....5. +| 1504: eb 84 9d 2c 0e 4e 2b de 1b 23 0d f5 bd aa 4a 9c ...,.N+..#....J. +| 1520: 6d 7e 48 23 18 f9 72 5b ef 9e da a9 ef 1b 29 08 m~H#..r[......). +| 1536: 03 34 e2 86 97 ff c9 2d c6 04 34 65 84 20 5b 7c .4.....-..4e. [| +| 1552: df e3 2c bc 73 04 22 70 f5 03 f2 8f fe fb 72 b5 ..,.s..p......r. +| 1568: a1 93 91 a4 3e a5 20 ac 21 19 8a 60 60 48 63 a0 ....>. .!..``Hc. +| 1584: 47 80 a8 ac 9f 68 a9 ab b7 0f 71 d5 44 1a fb 4a G....h....q.D..J +| 1600: 99 06 2b fc 96 ad ac 5e 37 37 84 6e 4e de 66 ba ..+....^77.nN.f. +| 1616: c6 10 fc e9 a0 26 cf 48 f6 f6 96 b7 69 35 30 06 .....&.H....i50. +| 1632: f2 7a db 70 53 4d f9 5b 9b 5f 51 b8 f6 af fb 1c .z.pSM.[._Q..... +| 1648: f5 2a d7 01 80 d2 0e b3 fe cc 99 ad 74 ad fa df .*..........t... +| 1664: 26 86 c7 29 b2 b3 36 98 b8 b7 2c de 9c 78 41 99 &..)..6...,..xA. +| 1680: ff 46 40 76 64 64 0c f5 46 d5 bd 72 a3 46 b0 b3 .F@vdd..F..r.F.. +| 1696: 75 45 03 d1 06 4b c9 ca a6 4f 90 f7 9b 60 4a 46 uE...K...O...`JF +| 1712: ed af a1 d5 68 30 db a7 5e ca 76 bf 66 64 e7 fa ....h0..^.v.fd.. +| 1728: 50 5c 31 5f 86 94 3a 5c f4 98 3d d5 52 80 26 1e P.1_..:...=.R.&. +| 1744: 55 07 14 d5 f5 2a 17 46 8b 6d 71 d2 da 4b e7 c8 U....*.F.mq..K.. +| 1760: 3d 8f 97 b7 97 51 c5 fb aa 4f 3f 3b 85 c1 aa e8 =....Q...O?;.... +| 1776: bf 3a d9 56 c0 d3 23 31 28 46 bd 45 2e 4b b5 e7 .:.V..#1(F.E.K.. +| 1792: f6 8e fa e2 28 65 6c 42 f2 68 8f 37 cb 4d e9 26 ....(elB.h.7.M.& +| 1808: f7 63 58 8c 99 09 71 7c 14 3a d7 92 c4 cf 14 51 .cX...q|.:.....Q +| 1824: af fe 6e 6b 50 e9 3a 70 0f ac 53 9d 57 01 4f 7a ..nkP.:p..S.W.Oz +| 1840: cc b2 c5 b9 2a 3e 02 6a d4 15 6f 2e d3 20 99 ed ....*>.j..o.. .. +| 1856: d7 1f 71 01 2f b4 e4 08 fe 8c 38 02 2c 14 e0 fd ..q./.....8.,... +| 1872: ec 3e 61 a8 b7 fa de f0 81 56 6a e2 d1 3d 2f de .>a......Vj..=/. +| 1888: f8 a3 75 5d 35 1b a4 a3 8e 6e 8c 9d b2 56 56 ba ..u]5....n...VV. +| 1904: 89 ab 42 86 70 59 5e 70 51 27 fc 87 b4 5c e5 89 ..B.pY^pQ'...... +| 1920: 20 b5 bd 40 6a 68 cb 1a 70 49 b5 2f c4 02 34 66 ..@jh..pI./..4f +| 1936: be cc 14 12 e0 f9 c7 4c 84 ff 37 a6 a5 ae 5c 36 .......L..7....6 +| 1952: 03 58 8e 44 17 e1 ca 30 3e 7f c9 54 94 f4 ff 86 .X.D...0>..T.... +| 1968: 57 8b 5e 2c aa 0b 28 30 ac 5a 1a 14 39 70 8a 84 W.^,..(0.Z..9p.. +| 1984: c2 4c 64 28 b1 87 6a 34 cf 50 b7 37 2e 96 ca ba .Ld(..j4.P.7.... +| 2000: c9 b1 37 d5 56 e7 39 b6 70 b1 16 2b 67 8a fb 0f ..7.V.9.p..+g... +| 2016: b3 ec 53 b4 27 ba 3e 60 16 96 86 b0 02 ba c9 d7 ..S.'.>`........ +| 2032: e1 d9 17 0f d4 12 aa 5d 65 53 29 47 04 47 2f 6f .......]eS)G.G/o +| 2048: 76 be c9 87 4b 41 27 63 28 f9 b0 48 a9 91 36 32 v...KA'c(..H..62 +| 2064: b7 e1 1f 84 52 c5 3f 81 98 6d de e0 31 27 eb 49 ....R.?..m..1'.I +| 2080: 0c f6 0c 00 5c db 30 f4 67 4e 90 f8 32 10 87 0f ......0.gN..2... +| 2096: 27 0c 19 71 b1 b8 e6 ea 40 e9 e0 fa 30 44 f0 d0 '..q....@...0D.. +| 2112: ae 89 05 c6 71 0d 18 36 f5 75 4d 6e b9 cf 60 2a ....q..6.uMn..`* +| 2128: e9 3e 54 d3 86 34 3b 69 87 47 64 9f 0d 99 d5 d7 .>T..4;i.Gd..... +| 2144: 42 bf c6 dd 24 51 32 5d e8 70 cf dc 67 55 58 bd B...$Q2].p..gUX. +| 2160: a0 93 c4 f2 05 73 91 bc 46 d1 4a ac 03 9d b1 ab .....s..F.J..... +| 2176: e9 6a de 28 8e d4 2a 75 e6 0b f5 ed a0 a6 45 e2 .j.(..*u......E. +| 2192: ea 86 1e a9 9d ad d9 8c 82 72 26 9e 7b ca a5 13 .........r&..... +| 2208: 3e ab 9f ed bf 81 9d 51 f0 f4 47 be 89 e1 0b a4 >......Q..G..... +| 2224: ec 5f 1c 5d b2 64 d0 7f 31 59 2e 68 2c 54 d2 69 ._.].d..1Y.h,T.i +| 2240: 91 34 56 e5 b9 83 0d 3a 00 9d e3 3d 78 6d 47 1d .4V....:...=xmG. +| 2256: 09 7d 67 de d8 a2 92 e6 b1 36 4d 89 c0 dd ce 76 ..g......6M....v +| 2272: df 73 aa 1d 88 38 c5 63 43 ac af 28 35 f8 48 5c .s...8.cC..(5.H. +| 2288: 9c 4d 50 ab f4 f3 c8 3c 6a 82 99 68 f9 25 7e 86 .MP....>..tJU..O() +| 2608: f8 00 70 ee 67 18 5e 7d 4d 1a fd b5 92 84 69 4e ..p.g.^.M.....iN +| 2624: c4 de 4d 45 90 3e 70 22 d3 b6 b0 74 b3 b0 21 86 ..ME.>p....t..!. +| 2640: 2d 10 d1 d3 2f d8 89 d5 b5 bd e0 92 66 a0 99 30 -.../.......f..0 +| 2656: 0f 6d de e8 db 9a 2d 6c 20 89 43 3c e0 61 32 67 .m....-l .C<.a2g +| 2672: ee f6 be 79 20 b4 06 11 63 b3 89 0f df 28 56 df ...y ...c....(V. +| 2688: 94 3c f2 31 b0 b8 5f 11 6c 6b 9b d3 43 cc f0 38 .<.1.._.lk..C..8 +| 2704: 6a 92 3d ee a6 92 95 9c 6c 02 94 31 7b ae 7a 0c j.=.....l..1..z. +| 2720: 0b 49 0f 82 54 ae c4 b8 58 4d 57 75 d6 e9 20 5a .I..T...XMWu.. Z +| 2736: bf 5a 2e ae 2d 18 4f 60 62 d9 1f 91 1d cd da f4 .Z..-.O`b....... +| 2752: 00 e2 d8 fd f0 20 d3 70 2b 4d 6f a1 80 a2 25 16 ..... .p+Mo...%. +| 2768: 7e 75 5e 7b 45 8d 72 62 11 ba 36 7e 4f 89 ba 4d ~u^.E.rb..6~O..M +| 2784: e2 0c e3 f4 0d da ac 14 89 36 e5 c8 4f 67 a5 33 .........6..Og.3 +| 2800: ff c2 91 ac 81 25 39 21 c4 79 5c 97 6d 97 45 3a .....%9!.y..m.E: +| 2816: 6e 00 60 1e c0 e4 ee f3 a6 98 16 43 ee 56 62 32 n.`........C.Vb2 +| 2832: 85 db 7a 83 af 26 a6 3f be 66 c6 5b ff 51 d7 f2 ..z..&.?.f.[.Q.. +| 2848: 8f 2e 13 c4 b2 67 72 ac c4 4d a3 3a 69 cb b5 bc .....gr..M.:i... +| 2864: 01 74 09 53 16 3a fa 7d 0e 24 8a c8 93 14 4d ca .t.S.:...$....M. +| 2880: 95 2f 30 dc d1 6c 42 d6 78 04 c8 91 4e 86 2a a0 ./0..lB.x...N.*. +| 2896: 0f 71 da 74 20 b0 a9 a5 a5 7e 96 44 e1 86 d1 af .q.t ....~.D.... +| 2912: bc 1d 24 19 11 54 3a e1 a6 db 38 f8 64 59 29 0f ..$..T:...8.dY). +| 2928: 3d 7b ce 0d c5 38 bd cd c2 85 13 f8 62 09 83 96 =....8......b... +| 2944: 20 2c 3d 81 6b 9d 48 24 4e d6 eb 88 7e f4 6f f4 ,=.k.H$N...~.o. +| 2960: fc 55 32 3d ab 74 bd f1 4f 75 66 f5 53 a5 a6 7f .U2=.t..Ouf.S... +| 2976: f5 89 da df 35 a7 44 0b 84 8d 85 f3 08 9b 63 14 ....5.D.......c. +| 2992: 8b ea 66 43 6f 07 a3 e5 33 eb fb 8b 11 40 10 7d ..fCo...3....@.. +| 3008: 11 cd 65 d7 38 4a 50 d7 02 73 ed a6 15 ac 82 77 ..e.8JP..s.....w +| 3024: 0d 8c f3 7a ea a2 36 32 11 52 71 d3 f4 24 13 5a ...z..62.Rq..$.Z +| 3040: 22 e6 75 14 28 7f b7 0c a8 71 16 20 3c 79 f5 ce ..u.(....q. ..L..... +| 3552: 7f e9 e1 a6 3b 68 b0 1e a0 81 5a 54 f7 3c a2 7a ....;h....ZT.<.z +| 3568: 24 ee 8e 43 a7 df 14 22 da b2 8c 80 6a 40 4c bf $..C........j@L. +| 3584: f8 5a 4e d1 34 af 19 3f dc 4f 93 29 83 d1 af 7a .ZN.4..?.O.)...z +| 3600: 33 fe 25 1a 1c d5 c1 e5 98 4e 30 4d 2b 2c 6e 55 3.%......N0M+,nU +| 3616: 8d d3 0f d5 f5 a3 a1 44 10 ba d6 2d 05 71 5d 4d .......D...-.q]M +| 3632: 15 e6 06 21 00 56 c1 45 21 63 f8 61 bf c5 75 79 ...!.V.E!c.a..uy +| 3648: f5 5f 83 53 ba cd a0 e6 12 45 3b 8b 33 b6 4d 29 ._.S.....E;.3.M) +| 3664: 41 c9 ad 09 a3 f1 a7 a6 6d 8d ec 16 68 b4 a3 fb A.......m...h... +| 3680: 01 1a c3 ee 78 17 d9 de cb 2e 42 d4 a3 56 8f f4 ....x.....B..V.. +| 3696: 3c 75 b1 92 d0 b5 75 10 69 cc c0 50 a4 bb d0 4c r. +| 3776: 8d e5 f5 f6 d4 49 8d b4 65 50 38 8b b3 3e 20 c1 .....I..eP8..> . +| 3792: 07 cd ad 7d e3 d1 8e 5f 6e 76 7b fe 01 ba 08 74 ......._nv.....t +| 3808: 16 a5 1b 5e 95 a2 ff 91 bb 9f 64 3f a4 e5 95 be ...^......d?.... +| 3824: d2 59 02 aa c5 06 66 9e fe 56 6e d1 1d 31 6f b3 .Y....f..Vn..1o. +| 3840: da 6d b4 3a 17 86 e0 e6 c1 37 21 68 6c 33 d3 58 .m.:.....7!hl3.X +| 3856: e8 67 fa 04 8d 59 ae e4 fb 1f 8c 48 17 7e bb 8c .g...Y.....H.~.. +| 3872: 17 61 b4 a7 e4 6b 87 c0 6a 8b 5f d1 76 16 5c f7 .a...k..j._.v... +| 3888: 81 b4 2d 66 ad ba 46 6f 95 bf a8 19 e8 82 05 7f ..-f..Fo........ +| 3904: 43 47 8b 63 b4 14 c6 2a de 90 8c 6d 04 3d 90 dc CG.c...*...m.=.. +| 3920: c4 6b d0 4c 33 9f 6c a7 38 6b fd b8 53 de 17 5e .k.L3.l.8k..S..^ +| 3936: ef e4 17 19 25 32 eb 12 86 03 3c 61 2e b4 06 ca ....%2........|..~v3.]... +| 1104: a8 31 d7 92 4a d2 68 29 8d e4 3c 76 6f d3 a0 f2 .1..J.h)..8...j..:. +| 1152: 2a 01 f9 21 05 ec 07 3f 6f 7d ba 17 5d ba 3a 04 *..!...?o...].:. +| 1168: a9 cc 88 35 4f 43 4c dc ab 1d 1d b0 24 1f a2 83 ...5OCL.....$... +| 1184: 69 4a 89 1e a8 a3 de bc 17 15 be 3d 07 ae 42 02 iJ.........=..B. +| 1200: f6 60 ff 36 08 cf 63 1f b5 be b1 1c 45 14 b0 56 .`.6..c.....E..V +| 1216: 0c 2e b9 49 49 69 47 17 da 41 4e 91 0b 5a bd 76 ...IIiG..AN..Z.v +| 1232: be 67 65 82 27 35 0a 23 a4 21 f4 ba 78 af 0a 7c .ge.'5.#.!..x..| +| 1248: 0e f0 ca 90 c6 32 d9 dc f1 dc 25 33 da 76 c0 f5 .....2....%3.v.. +| 1264: c2 4d ed 55 d7 f8 1f 44 82 37 c3 d3 90 12 23 52 .M.U...D.7....#R +| 1280: 8d 3c 3d f0 9e bf 7b 0e 49 8a 32 5d a1 64 53 26 .<=.....I.2].dS& +| 1296: cb be bc 26 dd bf f8 a6 ad 57 e5 35 68 c1 6c b9 ...&.....W.5h.l. +| 1312: 02 d7 0d 70 f4 d9 10 8f 75 35 8b 60 21 46 62 60 ...p....u5.`!Fb` +| 1328: e4 e7 70 35 0f a9 70 0d 50 da 5c c2 17 9b c6 3b ..p5..p.P......; +| 1344: 61 83 9f 3f 1d f4 28 dc c6 32 a6 12 ff 56 dd 0e a..?..(..2...V.. +| 1360: 57 08 0e 0d 5b f8 ea 32 33 20 a7 a1 a4 9b b3 77 W...[..23 .....w +| 1376: 4f db 85 06 dd 0b 52 b0 45 ff a4 e5 23 5e ea c0 O.....R.E...#^.. +| 1392: d6 54 d6 84 cd c4 fc 2e 95 ec 78 13 cf 9a fa e3 .T........x..... +| 1408: a1 5a b3 60 1a ab 1a 97 21 67 9c cd 44 6f ee a9 .Z.`....!g..Do.. +| 1424: bf 1f 07 52 d0 1a a4 ae 15 1e b5 01 0d ac 31 32 ...R..........12 +| 1440: 11 7f 67 b4 9c 20 78 28 8b 4a 79 32 44 c8 aa 1d ..g.. x(.Jy2D... +| 1456: 05 4b 94 3e e2 f0 d4 f2 16 3c 8e b7 67 13 98 47 .K.>.....<..g..G +| 1472: 2b 6a 1a 98 c9 82 7d a7 a7 7d ee 62 dd a8 29 58 +j.........b..)X +| 1488: 98 5b 2e 5b 62 1b 1b 21 37 4d eb e7 85 ac a3 8d .[.[b..!7M...... +| 1504: fe 69 ca 05 34 83 84 34 47 8d 5b b0 8d 71 f1 22 .i..4..4G.[..q.. +| 1520: 07 b5 a9 be 42 88 58 27 84 3e 37 1c 0f c7 1f 77 ....B.X'.>7....w +| 1536: 72 5e 2c fc 80 43 03 71 00 22 7f fe 5d fe ea a7 r^,..C.q....]... +| 1552: 33 3a c6 ae db e7 1d ba 8b 61 8c b9 b4 b2 ab b4 3:.......a...... +| 1568: 2f ec 9d b1 9c bb 13 7b 6e 3a b9 aa 43 b2 14 6f /.......n:..C..o +| 1584: f0 27 00 31 f6 5b 9c e9 96 40 3a 13 2f fc 6b ae .'.1.[...@:./.k. +| 1600: 0a 55 bf b3 cd 83 23 25 f1 15 e2 2e bc 7c 6b 29 .U....#%.....|k) +| 1616: 90 8f 85 ff 5b 5a 18 52 03 84 4d f9 f9 fa e1 a0 ....[Z.R..M..... +| 1632: e8 32 29 9c 5b 9e ed 39 b3 17 f8 ef 7f 55 b4 3f .2).[..9.....U.? +| 1648: c3 f3 66 f1 bc 15 1b 78 2c 9b ab cf e1 10 d0 46 ..f....x,......F +| 1664: 3d 21 2b eb 4f 8f 1d eb 8a 5e 87 23 72 50 04 ea =!+.O....^.#rP.. +| 1680: 8f 53 1b 3a 3f cf d5 92 5a 06 c1 18 25 33 41 84 .S.:?...Z...%3A. +| 1696: 77 56 bf a5 8a d1 ff 97 51 34 d3 24 88 12 dc 33 wV......Q4.$...3 +| 1712: 09 09 fc 68 b5 2a 4a cf 7c 73 d8 ff 93 29 40 1c ...h.*J.|s...)@. +| 1728: 8e ea 7d e3 7f 25 57 f5 bf 2b 19 80 4d fa 23 35 .....%W..+..M.#5 +| 1744: 18 8b ad af 46 85 3b 51 34 49 dd cb 39 d8 50 50 ....F.;Q4I..9.PP +| 1760: 7a a5 58 af 72 66 5d 21 c2 e2 03 1f ee bb d2 b4 z.X.rf]!........ +| 1776: 62 d2 f4 c0 fb 04 12 b2 35 0c 0f 0d b9 e2 a5 28 b.......5......( +| 1792: 87 5b 76 c7 39 4d 18 8d 3f 61 ab a0 84 4a 11 22 .[v.9M..?a...J.. +| 1808: e1 a9 69 55 2b 03 41 34 73 83 0d 0b ed da a6 d8 ..iU+.A4s....... +| 1824: f8 ff 9b d2 62 1b ca 1a 40 4f 0a 86 ad e7 92 af ....b...@O...... +| 1840: ce 69 19 8e 35 5d cd 50 4e 53 2d 90 46 90 ae 8a .i..5].PNS-.F... +| 1856: 43 ac 8b 30 7f 3b b3 05 78 63 b3 b7 0b 3d 2a 13 C..0.;..xc...=*. +| 1872: 55 83 ed a0 61 6b 12 30 5c 46 f3 1f 18 1f bd 89 U...ak.0.F...... +| 1888: af 86 9a 82 ed 89 35 0e 29 06 2c b8 97 c5 ef 46 ......5.).,....F +| 1904: 90 ce 6c 83 f8 7d 08 75 76 b4 07 6b 48 15 bb bd ..l....uv..kH... +| 1920: c2 fd 79 a8 7f 54 e4 d5 93 c2 17 09 3f bb 58 84 ..y..T......?.X. +| 1936: dd 78 7a 81 c4 13 70 e5 23 73 d4 60 25 0f 91 bc .xz...p.#s.`%... +| 1952: a9 8e 54 64 46 e1 8f 11 66 7c 1e 31 5b 9e 10 d9 ..TdF...f|.1[... +| 1968: 7e 09 e8 bc 2f 73 d4 f6 27 0c a0 62 5d ce 65 40 ~.../s..'..b].e@ +| 1984: f7 24 b0 bf f0 26 c0 17 1a dd d3 5d 16 35 22 10 .$...&.....].5.. +| 2000: 4d c4 cf 5b 22 2f 12 b2 5b f2 87 04 34 4d 5c 9d M..[./..[...4M.. +| 2016: 50 48 71 c7 8e bb 7d ce 89 f3 e4 4a c1 f2 92 4f PHq........J...O +| 2032: 90 4d 58 ac 27 60 a7 e9 31 41 ca b1 c0 38 ce 2d .MX.'`..1A...8.- +| 2048: df 40 e9 cd db 2f f3 28 09 ef 14 79 99 ef 66 1b .@.../.(...y..f. +| 2064: e6 c0 7f 4c cc 61 96 b6 f9 95 9d 1b 90 16 55 08 ...L.a........U. +| 2080: 71 ad 92 88 98 01 7f b9 8e 9c a2 f4 d4 24 33 a0 q............$3. +| 2096: 53 f2 01 df 47 3f 8d 15 a8 d1 70 a9 c2 70 f8 75 S...G?....p..p.u +| 2112: b4 ed b6 7d 82 95 63 5e 86 c1 3a 7c de b1 46 fd ......c^..:|..F. +| 2128: b8 30 42 36 83 35 e2 4a 78 c2 b5 b9 1e 57 f2 19 .0B6.5.Jx....W.. +| 2144: 91 6d ff 4f 87 97 55 98 a3 86 a2 54 1f 2f 0c 8e .m.O..U....T./.. +| 2160: 72 d6 a0 37 f2 bf 84 a3 a5 b3 87 12 a3 ef 0a 00 r..7............ +| 2176: 31 69 49 83 8c 6e 82 3a 84 62 a8 ce ee ce 91 61 1iI..n.:.b.....a +| 2192: b7 26 c1 fd bf 77 15 81 23 e6 d5 6f bb c4 ab eb .&...w..#..o.... +| 2208: 8b a7 fc b6 4e cb c1 61 ab 59 a2 dc ca ca f3 42 ....N..a.Y.....B +| 2224: 90 16 d8 d0 03 43 c1 e0 d9 25 50 b0 17 65 64 d0 .....C...%P..ed. +| 2240: 11 be 52 a8 e3 bd dd 7f 39 1e 0b 85 ae 92 c0 1c ..R.....9....... +| 2256: 0c 3b 06 2a bb be b6 c7 94 d6 83 6b b0 17 6d 24 .;.*.......k..m$ +| 2272: b1 b5 2d 22 5e 7f b9 db 47 65 e6 21 37 a2 2f d3 ..-.^...Ge.!7./. +| 2288: 03 8a 91 c0 5a de 52 27 dc 4b 9a 92 f1 fc f0 93 ....Z.R'.K...... +| 2304: d1 de 9e 04 73 13 c5 ec 25 36 54 15 94 92 b9 de ....s...%6T..... +| 2320: bd e2 39 b3 5c 88 8c e6 11 48 28 b8 0d 50 5a 6f ..9......H(..PZo +| 2336: d9 8a a6 17 4f a0 82 ff 94 70 28 10 0c 41 80 c4 ....O....p(..A.. +| 2352: d9 42 a9 fe d2 f7 1c 70 45 74 fa 65 d2 cf 49 00 .B.....pEt.e..I. +| 2368: b8 24 83 06 97 f5 1c 48 a7 dd 82 2f f0 77 f5 e6 .$.....H.../.w.. +| 2384: 5c 03 11 fa 65 46 57 90 c0 6f 1f 86 58 de 34 21 ....eFW..o..X.4! +| 2400: 5f 76 d7 1e 1a 16 6a e1 ad 26 ae 6a 32 53 30 8b _v....j..&.j2S0. +| 2416: db d9 05 93 22 87 58 e8 91 d8 26 80 85 f0 01 93 ......X...&..... +| 2432: 77 0e 88 91 bc bc ce e9 5e 6e e8 b8 aa 4e ad fa w.......^n...N.. +| 2448: a8 a2 5a 17 b8 88 56 f5 71 8a 70 fe 83 4f 5c 8c ..Z...V.q.p..O.. +| 2464: 07 1a 45 cf a9 89 05 c6 81 79 90 a5 d2 53 a4 3e ..E......y...S.> +| 2480: ac be 52 ae aa 9d 30 66 c5 b7 1f 7a c8 8e 6a 3b ..R...0f...z..j; +| 2496: 82 54 6a 62 aa 6e 4a c4 02 11 5b 69 12 6f 84 23 .Tjb.nJ...[i.o.# +| 2512: 17 f6 3d 81 1f 29 60 28 7c e2 95 b4 ae 39 e6 6b ..=..)`(|....9.k +| 2528: 5e c5 df 82 66 82 57 d4 84 cd 2b 1e f2 a0 31 82 ^...f.W...+...1. +| 2544: 8b 9f 0e 0c 72 76 6b 6c 5b cb 0c 5c 2a 77 22 df ....rvkl[...*w.. +| 2560: 1e 96 44 f9 4e 22 dd 22 ff fc 14 a2 cc 36 77 02 ..D.N........6w. +| 2576: 81 8f 22 1e 46 ea 11 e1 85 41 8a ee 69 64 e6 27 ....F....A..id.' +| 2592: 8b 46 0b 4a 47 35 f4 72 71 62 a1 0c 4d 55 be a0 .F.JG5.rqb..MU.. +| 2608: 1f ae ae 8b a6 2d de 54 04 24 05 98 06 43 04 57 .....-.T.$...C.W +| 2624: 09 8e ff 81 ff 9c 9a 18 02 bf c3 66 8f 65 fb e3 ...........f.e.. +| 2640: 66 0c 20 bb 50 f5 0c a8 a8 e9 6f 53 65 b5 3a e4 f. .P.....oSe.:. +| 2656: 48 d2 1c 86 e2 a6 35 c0 91 d4 72 b6 67 21 49 fb H.....5...r.g!I. +| 2672: 0c f9 91 b8 64 46 c2 75 28 df ac c0 bc 4f 61 d9 ....dF.u(....Oa. +| 2688: 92 06 6b 48 d8 29 ba 4f e6 40 a8 c8 35 8b 83 e6 ..kH.).O.@..5... +| 2704: 79 ec a1 d3 c1 73 82 64 42 13 3c 7b 73 3e 7a 14 y....s.dB.<.s>z. +| 2720: 2d db ac 00 76 00 81 ae fb c1 30 7b dc 57 39 f5 -...v.....0..W9. +| 2736: 27 6b 1a d5 ed c2 4b 21 12 3e 4d e0 8e 58 69 9b 'k....K!.>M..Xi. +| 2752: f5 cb 26 7a 62 e5 7b 3a fd 1a 8a e6 7d e6 f1 60 ..&zb..:.......` +| 2768: cc 4f 30 39 76 b7 3d 49 3f b7 59 a3 d0 a6 c8 8e .O09v.=I?.Y..... +| 2784: 3e a8 e1 a0 1b 7d 82 39 8b c6 a5 87 3e 2c da 16 >......9....>,.. +| 2800: f0 71 cc 5e 02 17 49 6d 48 24 8c 19 9e c4 d1 97 .q.^..ImH$...... +| 2816: a6 cc 28 28 c9 3e c2 e3 19 6a 05 f2 bd 4c ef 44 ..((.>...j...L.D +| 2832: df 84 da 8c 3f 41 16 2f 87 b8 88 bd 6d 8b 6e dc ....?A./....m.n. +| 2848: fa b0 44 08 ee ef 22 84 bf c0 5c a4 2f 2c 7e a2 ..D........./,~. +| 2864: 50 8b 84 cd 60 08 d8 53 4b 2d 4f 1e 3b 14 b4 62 P...`..SK-O.;..b +| 2880: 97 a1 66 49 2a 6f 3b 36 85 c2 42 58 98 5a db 3e ..fI*o;6..BX.Z.> +| 2896: 64 fd ad 32 f2 e0 f9 5a 13 dd e1 68 6c 35 34 a5 d..2...Z...hl54. +| 2912: 3c 9a b1 8a 51 78 49 5b c6 ef 57 7f 6e de fe 2d <...QxI[..W.n..- +| 2928: 0a 2c 1b 74 3f 19 bf 8e 6d 2f 0b 22 91 36 95 5d .,.t?...m/...6.] +| 2944: a3 65 b6 3b 3a e4 de 70 9e 29 f4 c6 0a 23 27 d6 .e.;:..p.)...#'. +| 2960: d1 4a f6 2e 2b a0 ee 8d cd fc 42 7a c8 1a a6 36 .J..+.....Bz...6 +| 2976: 17 aa f4 03 b5 cc e2 54 7a c4 fb 27 e5 90 62 20 .......Tz..'..b +| 2992: 03 77 4d fc 35 7c 59 87 01 49 86 ae 82 6b 8b a7 .wM.5|Y..I...k.. +| 3008: bc b0 b0 dd f2 ef f7 ef 0a 36 a6 25 f1 70 ba 89 .........6.%.p.. +| 3024: e8 00 f4 fc c0 98 73 f2 b0 9a a2 ed d5 1e 17 d1 ......s......... +| 3040: 79 82 18 84 b0 f8 2a 5d f2 a1 03 d6 45 b0 01 26 y.....*]....E..& +| 3056: 19 01 6d b3 0e 5f 55 6c 2b 21 72 33 84 a9 ab fb ..m.._Ul+!r3.... +| 3072: 64 4d bc f0 1d 16 ae aa 09 c1 29 60 e2 63 e1 d5 dM........)`.c.. +| 3088: 84 41 6e 5c 12 08 9a 04 dd 27 b8 fe 2f fb ca 83 .An......'../... +| 3104: 2a 7b eb 05 3b 77 fd 42 31 42 84 98 89 24 3c cc *...;w.B1B...$<. +| 3120: 47 f6 bc 13 37 d7 97 98 c4 61 24 50 0b 9e e5 53 G...7....a$P...S +| 3136: 46 05 49 5e a5 51 d5 48 26 fa 31 eb 0e 76 14 16 F.I^.Q.H&.1..v.. +| 3152: e9 60 f6 05 d5 bb 47 85 e2 da f6 5a 0a c0 14 38 .`....G....Z...8 +| 3168: bb 70 4d be eb d8 6d 10 61 d6 9b c6 c4 f4 56 7f .pM...m.a.....V. +| 3184: ff b8 fb 1f 92 a3 f5 74 78 7f c0 8d 9d f4 b1 a2 .......tx....... +| 3200: f4 0c 33 da 98 9e a2 61 4c c7 41 9c ea 0f 33 54 ..3....aL.A...3T +| 3216: 40 54 31 c3 04 fa d1 4b 67 80 e2 a7 6a 77 c6 ca @T1....Kg...jw.. +| 3232: 04 fc 71 ea fa 0f 92 8e d3 40 e8 0e 1c 48 a4 55 ..q......@...H.U +| 3248: 7f 74 67 ea c9 29 67 73 b9 b9 73 b1 00 1a d4 0b .tg..)gs..s..... +| 3264: 21 95 d6 1c b4 68 2b c5 e1 18 40 7e 8e 09 6f 28 !....h+...@~..o( +| 3280: 88 2d 6f 24 d3 73 7b 89 7a a6 aa df ad ae 7b 14 .-o$.s..z....... +| 3296: d9 f0 ff 20 ba fd bf a7 d7 04 6c 35 5c 76 4e f5 ... ......l5.vN. +| 3312: d3 5b a9 2b 8f 3a 11 fa 35 26 eb 78 45 da cb 00 .[.+.:..5&.xE... +| 3328: 78 97 b1 49 82 4e c1 4d b1 aa b7 80 75 fb 20 75 x..I.N.M....u. u +| 3344: cb 3f ba 05 95 33 cd e9 b3 bd b2 84 c4 4f df af .?...3.......O.. +| 3360: 77 c2 44 24 57 01 f9 9d c1 ef b6 ce 01 6f a6 5d w.D$W........o.] +| 3376: 3d 4b 12 e9 8f c2 a6 d5 1c 3b e4 05 83 48 aa 78 =K.......;...H.x +| 3392: 4b 3a 1c 1b ad 8e bc 49 c5 ee 91 68 28 8d 74 74 K:.....I...h(.tt +| 3408: a0 e1 20 ba 3d 62 97 0a 40 58 6b 1a c9 be 53 00 .. .=b..@Xk...S. +| 3424: 5d 9f e7 8e b6 05 7a d2 d1 89 ac fd 7f 9b ec 19 ].....z......... +| 3440: d0 95 35 e6 41 32 eb 68 85 eb 06 8c 53 f2 25 f3 ..5.A2.h....S.%. +| 3456: 19 d9 1c e7 a8 c2 c3 1c cd 78 50 4d 73 2d 5c 15 .........xPMs-.. +| 3472: 7c 3e b5 f0 e8 64 f5 72 a1 4a 9d f3 87 1d 12 0b |>...d.r.J...... +| 3488: d6 50 21 18 ca 10 f9 29 32 53 14 54 08 ef 51 6d .P!....)2S.T..Qm +| 3504: 6e 20 ae f1 3c b5 6b 8b 4c 4d 68 9f 73 99 2f d2 n ..<.k.LMh.s./. +| 3520: f2 ea ba bd 72 e4 d5 de 71 c2 15 a2 57 f7 69 c0 ....r...q...W.i. +| 3536: c4 6b cb c1 72 8e a8 fe f8 79 d2 6d 97 82 bf a1 .k..r....y.m.... +| 3552: 11 35 dc 30 36 e5 32 b5 81 f7 2b 58 06 c3 28 3a .5.06.2...+X..(: +| 3568: 43 c0 bd 16 a4 b8 f7 37 2b 1f 5f 8a 2a 09 21 4e C......7+._.*.!N +| 3584: d7 14 35 31 e6 36 8d 6f 2b 2a e2 63 1f 59 8d 62 ..51.6.o+*.c.Y.b +| 3600: 16 cd 16 d5 5a 20 b0 4b 9c 44 4d bb 0e 8a 01 6d ....Z .K.DM....m +| 3616: 6c 2e 09 48 6c 32 f5 96 45 0b df 3a a4 09 a4 1c l..Hl2..E..:.... +| 3632: 44 81 72 60 8a ed 10 29 c0 62 da ba 51 4f a0 7d D.r`...).b..QO.. +| 3648: e0 9b ed 31 6a 0e f8 b5 f4 69 b2 15 d4 01 ed c5 ...1j....i...... +| 3664: e2 09 df cc 97 13 70 57 48 1b bd 4a ad 0b ad 8a ......pWH..J.... +| 3680: 80 3d c1 c0 c7 f2 2d d9 a8 b7 3f b8 e5 aa 0f 5c .=....-...?..... +| 3696: e6 95 2a c6 80 83 69 ca 0e dd f9 7e 04 48 7d d3 ..*...i....~.H.. +| 3712: d5 29 fd 9d 7f 59 d7 1d 9b cd c3 06 e7 51 13 bc .)...Y.......Q.. +| 3728: 60 d5 c3 d5 a9 0b 78 ab 01 3e 34 06 4f 8a 47 4a `.....x..>4.O.GJ +| 3744: 0d a9 c1 b5 29 c4 26 c4 5d cb bd 80 f1 d4 eb b9 ....).&.]....... +| 3760: 6b 7d 2f c3 95 7c 2c e3 86 c9 5d 41 ee 76 89 9f k./..|,...]A.v.. +| 3776: 3a f3 e2 c8 6e f5 37 a7 68 01 06 26 47 9c 1b aa :...n.7.h..&G... +| 3792: 5a dc 55 89 af 53 89 9f f2 30 ee 04 90 cc 30 de Z.U..S...0....0. +| 3808: f4 2a c9 23 59 9d cf 65 7d 2c 30 7e ae 7d 08 43 .*.#Y..e.,0~...C +| 3824: 1f a9 b8 14 3b e8 3d 1e 3e 4b 46 93 18 4b 08 ee ....;.=.>KF..K.. +| 3840: 53 f4 07 0d 4e 69 87 a4 61 9f b0 75 a7 fc 64 34 S...Ni..a..u..d4 +| 3856: 86 aa a2 82 21 40 d7 7a 03 30 d0 f4 47 d2 4a ff ....!@.z.0..G.J. +| 3872: dd 27 02 69 99 d2 c9 61 d0 fa 46 dc 07 1d f7 0b .'.i...a..F..... +| 3888: 39 0b a3 2d 7f 9f 1b ce ca 79 69 62 bc 50 1a 07 9..-.....yib.P.. +| 3904: 2c 1b 38 37 9f 93 65 dc 45 3a 1e 2b ff 3f d0 bb ,.87..e.E:.+.?.. +| 3920: 05 f2 9b 36 b5 79 9c ca 4f 7d 8a 2f 6e 62 1d 20 ...6.y..O../nb. +| 3936: ab 6e 5f 75 19 67 e9 4a e1 c4 6a 2e 8a 06 b0 36 .n_u.g.J..j....6 +| 3952: 37 47 5f a1 27 93 d4 36 80 85 f9 29 94 db 83 13 7G_.'..6...).... +| 3968: f5 16 1e 0e 6b 6c b1 c1 f2 66 0a 8e 63 ea ca 6b ....kl...f..c..k +| 3984: f3 74 91 9e 73 62 d4 09 bd a9 64 54 e1 64 b0 e4 .t..sb....dT.d.. +| 4000: b6 47 e0 ff 92 09 88 39 e1 44 f8 50 20 b2 3c 6b .G.....9.D.P .....|..g.>@.E.y +| 1104: ad 46 a6 a7 42 e7 7c 05 62 a4 28 61 03 fc 1c e4 .F..B.|.b.(a.... +| 1120: 48 2e f1 ef d9 09 23 04 f4 d7 c2 ad 73 c4 d8 69 H.....#.....s..i +| 1136: cf 15 ef a5 1a ba db 63 7c d8 e9 4c 70 cf 08 d8 .......c|..Lp... +| 1152: 62 d3 ec 60 a0 93 06 fe 50 87 b5 22 48 37 b8 46 b..`....P...H7.F +| 1168: 30 26 ed 8d 2c f3 ed c1 51 62 e8 ce 13 45 b4 4a 0&..,...Qb...E.J +| 1184: 9a 3c 76 c4 fd a6 e2 de 26 04 93 69 30 3c 26 4d .1..3. .. +| 1248: 2f 8f 00 d5 47 ae 22 01 bf 83 40 da c4 74 43 56 /...G.....@..tCV +| 1264: 32 35 6e c9 3a 82 44 0e 86 32 5a 8e 46 5c 16 23 25n.:.D..2Z.F..# +| 1280: e5 dc 7e 38 26 fd ba d1 d3 d0 7e 4e 88 6e 2a 85 ..~8&.....~N.n*. +| 1296: d1 25 70 b4 fc 55 2b 5a 5b 4e 01 1b 20 21 d2 2b .%p..U+Z[N.. !.+ +| 1312: c1 5b e9 3e f1 fe 3f 65 18 07 a6 10 3a 88 e5 9b .[.>..?e....:... +| 1328: 4d 83 91 f2 1f b5 03 2a cb 57 21 e2 20 fc 35 99 M......*.W!. .5. +| 1344: b1 49 4a 0c b9 04 35 b9 b0 43 52 14 e7 2d 03 30 .IJ...5..CR..-.0 +| 1360: 73 cc 40 39 c3 d6 58 1b 90 85 df 21 6b e4 03 87 s.@9..X....!k... +| 1376: e1 de 4c 36 2c f3 c1 0b 75 92 a9 11 6e 9b de ac ..L6,...u...n... +| 1392: cb 26 b9 8d 48 47 f3 63 44 30 b0 e6 84 31 ec aa .&..HG.cD0...1.. +| 1408: f9 64 3b b2 12 b4 13 46 be 97 6f 36 37 3a ea 4b .d;....F..o67:.K +| 1424: ef 0d d6 04 44 21 e3 25 72 6c 70 d5 58 87 7d 16 ....D!.%rlp.X... +| 1440: 37 39 d3 0a 7b fe e2 81 d1 50 6a 62 6c a8 86 ca 79.......Pjbl... +| 1456: b9 f4 4e b5 04 c3 97 1b 4e 65 49 ee f4 cd 9c e4 ..N.....NeI..... +| 1472: 89 7d 7e c8 29 9b 3e 88 82 b7 68 c4 9b ea 75 48 ..~.).>...h...uH +| 1488: 1b 5b 93 0d 12 86 df 35 44 28 95 6c f2 c4 e5 3b .[.....5D(.l...; +| 1504: 89 a7 bf 11 ce 79 76 99 bb 0d 01 2e 58 e3 e1 0d .....yv.....X... +| 1520: 06 19 5e 28 bc 06 32 4e 76 bd e5 01 a0 20 5c 3f ..^(..2Nv.... .? +| 1536: 15 ad e8 4e 64 5a 67 a4 77 3f 23 c2 be 0d 96 b5 ...NdZg.w?#..... +| 1552: 28 e9 c3 60 ec 5d 09 73 e9 dd 40 a8 7d a0 ac 4f (..`.].s..@....O +| 1568: ef 82 ac 4d 25 19 a8 1b 22 71 c2 fa 97 c3 27 da ...M%....q....'. +| 1584: 44 14 f0 41 26 20 fd 57 4f 4e 9d f5 b3 d5 7c 72 D..A& .WON....|r +| 1600: f4 e5 56 0c 8e e8 c0 39 1c 3c 0e bb ce 8f 08 cb ..V....9.<...... +| 1616: 7f dd 0c fc 11 17 4b 41 03 b6 43 17 25 92 84 8b ......KA..C.%... +| 1632: f8 51 57 92 93 14 25 11 83 ce da bf 04 0d ed 32 .QW...%........2 +| 1648: 07 dc 6d b5 e1 c5 74 42 07 06 2a eb 2c a0 78 a7 ..m...tB..*.,.x. +| 1664: de 21 66 0b ea 49 fc e9 26 87 63 5c 8f 66 59 95 .!f..I..&.c..fY. +| 1680: c1 cd e9 00 79 05 0f 57 d0 d5 a9 1e 48 f6 7a 9d ....y..W....H.z. +| 1696: 80 6d 5c 74 55 3d fd 67 0e ff 74 26 70 bf c3 9b .m.tU=.g..t&p... +| 1712: 5f af 8b 43 69 e0 49 31 63 2b e4 9f 59 09 0e 5a _..Ci.I1c+..Y..Z +| 1728: 18 84 b3 f4 19 17 18 16 5b fd ed 78 5f 68 ec 2c ........[..x_h., +| 1744: d7 66 05 6e 47 eb 9b a1 52 90 0f 34 99 4f a0 fd .f.nG...R..4.O.. +| 1760: 21 f7 4d da f6 ae e4 57 51 63 59 e1 ee b8 32 6a !.M....WQcY...2j +| 1776: 03 ac c4 c4 69 73 10 32 d5 94 25 7e 6b d0 ec 62 ....is.2..%~k..b +| 1792: 0d 17 f7 16 24 b5 df 11 7b 29 01 03 d8 ff 1a 2f ....$....)...../ +| 1808: 7f 51 ab 9e 41 5b aa 0c a5 5d e9 69 ce 3b 85 4e .Q..A[...].i.;.N +| 1824: 7d ff 5a 37 b5 74 18 78 85 95 4d 44 8a d9 8c ea ..Z7.t.x..MD.... +| 1840: 2e 10 6e e6 cb a1 3d ef 57 38 f0 9b f0 8b 0b 68 ..n...=.W8.....h +| 1856: f9 f8 9f ac 58 da f3 9c 2b 2a 90 c7 4d 00 e4 4c ....X...+*..M..L +| 1872: 46 59 01 1c 0c 6b 08 6d 33 07 41 d0 5d 23 20 3b FY...k.m3.A.]# ; +| 1888: 00 e0 6b 32 2e 76 3a 7e 09 3e c9 66 90 4f d5 9b ..k2.v:~.>.f.O.. +| 1904: 8a bd b1 6d f9 33 d9 5a b3 bb a1 2a 45 31 0b c6 ...m.3.Z...*E1.. +| 1920: ce 44 34 5d 88 61 89 d1 cc 7c 14 86 a3 d7 6b 6b .D4].a...|....kk +| 1936: 22 d2 98 ab 62 7c 43 2e a8 92 12 de 43 d4 0a 9d ....b|C.....C... +| 1952: 94 f0 41 2d 14 86 69 fe 98 71 1a a2 2b db 5b ec ..A-..i..q..+.[. +| 1968: 23 00 e3 a3 03 1d 31 97 94 e4 85 1e 5f 53 44 66 #.....1....._SDf +| 1984: 0a 21 df ec ca 6a a6 a3 f4 c8 00 40 8e 0f 19 3f .!...j.....@...? +| 2000: 36 69 a3 7c de fc 73 16 d8 d3 f9 ff 03 ac 94 dd 6i.|..s......... +| 2016: 38 d5 d9 20 2e d8 97 2a 22 9d bb a5 cc 04 07 8f 8.. ...*........ +| 2032: be 18 b4 a8 ae e5 b4 45 27 7f e2 7e 3f 91 67 98 .......E'..~?.g. +| 2048: 86 3d 3c 05 3a 1e 71 bf 40 f8 a6 ca 48 7a ca c7 .=<.:.q.@...Hz.. +| 2064: b7 67 10 bd 99 79 53 32 70 b0 74 fc d9 93 60 c8 .g...yS2p.t...`. +| 2080: 3e 39 5c fa 3c 97 34 70 57 a3 0f f8 ab 4e 3f a6 >9..<.4pW....N?. +| 2096: a5 25 26 ea 0c 60 fe 32 f3 7a 68 d0 9f 50 a4 45 .%&..`.2.zh..P.E +| 2112: 13 2e f8 41 3f e9 d1 9f 78 4a d0 a7 d3 57 3c af ...A?...xJ...W<. +| 2128: a6 69 6f 2b 4e f0 86 b5 96 2d b2 de 06 5b 68 b2 .io+N....-...[h. +| 2144: 9a b9 de e7 7d 54 0a ff b1 26 17 ea bc a7 37 ff .....T...&....7. +| 2160: 46 7b f7 00 90 ec 71 92 cb 5a b7 c8 7f 45 60 ec F.....q..Z...E`. +| 2176: 9e 30 ca 80 69 4c 07 4d c8 c9 53 c4 77 3c 93 15 .0..iL.M..S.w<.. +| 2192: 17 63 15 c0 3a 1d a2 02 82 6d 4b ed 50 4c 5d 93 .c..:....mK.PL]. +| 2208: 2d ef bf 1e 9a ff 04 26 1a f6 7b da c9 21 7b 50 -......&.....!.P +| 2224: 4d 2f 53 c4 1e b4 dc 9f 5f 33 26 80 4a 8c ef 54 M/S....._3&.J..T +| 2240: fb 58 95 55 3c ec c0 a7 c5 78 a0 91 08 b4 d6 ce .X.U<....x...... +| 2256: d8 25 27 2b 37 e9 63 72 94 c3 89 5a 58 85 f4 95 .%'+7.cr...ZX... +| 2272: 09 fe db 6c 9c 71 54 af 1a 0c eb 2d e6 a9 db a0 ...l.qT....-.... +| 2288: 04 2c 29 70 12 b2 7e 66 ae 25 ce b4 b9 5c a2 12 .,)p..~f.%...... +| 2304: 1c 75 10 d5 54 f8 04 c5 8d be 38 29 64 f8 29 00 .u..T.....8)d.). +| 2320: 07 35 e4 e7 6e bc 64 db 39 d8 98 ee 72 28 a8 8b .5..n.d.9...r(.. +| 2336: 0f 1b 87 26 6e d4 73 1b ef b6 d4 db 05 12 1b c7 ...&n.s......... +| 2352: c6 1e 02 b1 ab bb e2 29 81 9a 9e b6 80 22 6e b4 .......)......n. +| 2368: 3c 30 ad e7 8b a9 60 7e de 01 a3 74 0b 76 0b d6 <0....`~...t.v.. +| 2384: e8 a0 91 65 ce bb 6a d7 96 cc fa 96 d0 c0 39 d0 ...e..j.......9. +| 2400: 54 80 1d 8d 79 17 11 86 c5 8f a7 7f 57 92 8d b2 T...y.......W... +| 2416: 41 5b 86 61 94 23 99 77 b0 5d 5d f9 29 86 36 c8 A[.a.#.w.]].).6. +| 2432: ea f8 2f b5 7d 42 66 fe c7 0d 19 17 e6 6f c5 79 ../..Bf......o.y +| 2448: e0 22 24 70 eb 79 23 ec 45 77 d3 44 8f 74 15 bc ..$p.y#.Ew.D.t.. +| 2464: cf 46 cc ff 5b 05 1e 03 7d ea c5 82 14 11 86 4b .F..[..........K +| 2480: cb 23 5b e0 1d 96 ca ea 62 d8 71 56 c4 1f d6 ed .#[.....b.qV.... +| 2496: 9e 8f bd b0 4a fe 53 87 c8 16 1a 31 cb 25 ba 18 ....J.S....1.%.. +| 2512: 66 c3 b2 a7 91 68 ff b9 2b bc c4 f5 50 36 79 e5 f....h..+...P6y. +| 2528: 33 c3 d4 98 e1 fd f9 3a c7 a1 ef 79 29 c1 bf 7e 3......:...y)..~ +| 2544: db 7b 66 3b 95 37 02 c7 ee ca 7f 9f 50 8f 47 5d ..f;.7......P.G] +| 2560: af 45 29 e3 f5 7e 07 93 73 a0 42 ef 05 a6 2b 74 .E)..~..s.B...+t +| 2576: 92 ab 8a 9a 19 be 2f 21 cf 6a 90 02 a3 1f f4 5c ....../!.j...... +| 2592: 72 b3 31 00 85 9b 20 d8 f3 c5 00 a7 15 30 56 6c r.1... ......0Vl +| 2608: 0f 7c 84 be 55 5b 8b aa df d6 bd 9e d9 55 54 93 .|..U[.......UT. +| 2624: 44 7d 9a 9b b8 38 46 b7 1e 1c 75 17 bb 29 05 5f D....8F...u..)._ +| 2640: da 39 7e fe 9e 40 e3 a5 7c a2 ba 5d 7e 9d 14 57 .9~..@..|..]~..W +| 2656: 19 10 2a d1 85 d3 06 0f d4 d8 ac f0 03 8a bd 61 ..*............a +| 2672: 26 89 4e c6 92 0a 4b 0b a2 3e 1d 5e 88 8c ce 33 &.N...K..>.^...3 +| 2688: 47 e7 4e 69 0c e3 09 ef 3a 9f af 19 ae 83 68 21 G.Ni....:.....h! +| 2704: 75 c3 54 7b 00 a5 f3 d2 32 41 69 ac fc 31 2e 0c u.T.....2Ai..1.. +| 2720: 91 dd 90 78 2c ab 3f 0c 37 a4 8c 69 a9 ae 41 8e ...x,.?.7..i..A. +| 2736: 6b b5 b5 ee bf df bc 1c 61 bb c9 51 37 a5 d5 96 k.......a..Q7... +| 2752: 0c 26 aa 97 67 72 73 cc c1 92 d3 46 cd 3e d8 ad .&..grs....F.>.. +| 2768: e6 8b 51 ee 41 ed 39 57 65 2e 5c 39 2b eb a7 72 ..Q.A.9We..9+..r +| 2784: b6 4c a8 87 72 a4 16 b4 d9 90 db e9 25 11 53 4e .L..r.......%.SN +| 2800: ef 24 1d b0 21 bc 97 52 38 53 49 c8 31 c1 c4 9f .$..!..R8SI.1... +| 2816: 2b 13 0c 10 5b 4a 0c 6e 07 c0 ec d4 77 f0 f0 38 +...[J.n....w..8 +| 2832: a6 88 d4 28 40 30 76 f4 ab 2e 94 b5 6d 47 79 84 ...(@0v.....mGy. +| 2848: f9 aa 28 32 66 c8 aa cf 17 18 3d 92 0e 66 4e fe ..(2f.....=..fN. +| 2864: 5d 80 51 29 df 97 de a4 c6 57 23 67 84 f4 32 86 ].Q).....W#g..2. +| 2880: 51 03 b6 67 29 54 74 87 da c0 41 e9 3a 3e 07 02 Q..g)Tt...A.:>.. +| 2896: d9 85 dc 55 e7 23 60 80 b0 01 48 cd 59 21 82 fe ...U.#`...H.Y!.. +| 2912: 14 65 1a 5d 9e 5e 2b 69 52 ee 64 01 4d 46 ac 94 .e.].^+iR.d.MF.. +| 2928: 60 04 d9 2c 41 e3 5b 35 e7 cc 75 06 7d ff 48 ae `..,A.[5..u...H. +| 2944: 13 e5 4f 54 f6 78 86 c5 c4 99 58 02 41 87 a9 82 ..OT.x....X.A... +| 2960: 34 95 75 b2 e5 5e 92 23 a1 7b 3c 7b 1d 94 dd 5f 4.u..^.#..<...._ +| 2976: f6 56 07 06 41 12 a0 56 7a 15 01 58 1f 9f 15 1a .V..A..Vz..X.... +| 2992: bd 2e c6 ea b8 29 ae 13 19 a6 40 b0 8d ec 3a cd .....)....@...:. +| 3008: ca 6b b4 d5 96 95 fe 8d 34 23 aa ab df c3 23 fa .k......4#....#. +| 3024: c4 02 eb 10 8c f2 e8 e0 5f d4 e9 4a ae f4 8d 60 ........_..J...` +| 3040: a9 1f 65 40 93 26 bf 1c 49 9d b6 8f e3 f1 c7 0b ..e@.&..I....... +| 3056: a7 bb d1 ae 56 60 5c 80 d8 e0 e4 f0 72 62 45 b7 ....V`......rbE. +| 3072: 31 33 66 a7 17 a4 34 67 f6 55 b1 09 eb 75 8f f0 13f...4g.U...u.. +| 3088: cd c1 86 e3 f5 3a e7 d8 08 da 4c a7 b4 45 8b ce .....:....L..E.. +| 3104: 9c 8d 48 41 b4 3e 6b 2d fb be c3 e7 bf 80 d3 29 ..HA.>k-.......) +| 3120: 9f 8c 5d 7e 48 76 dc 95 cc 30 bf 9f e1 e0 dc c1 ..]~Hv...0...... +| 3136: da 9c 73 18 3c ff 53 49 88 dc ef 90 20 5f 3f 75 ..s.<.SI.... _?u +| 3152: 85 02 0c d4 c3 f4 c1 ed f7 82 05 c4 e9 80 97 65 ...............e +| 3168: 09 2a ac 52 c5 77 6c f4 6b 35 30 c2 fd 38 48 ae .*.R.wl.k50..8H. +| 3184: b6 f6 38 0f 87 1a 66 54 91 3a 5b a7 48 5c 9d 36 ..8...fT.:[.H..6 +| 3200: e5 7f 60 4d 68 19 ff 8d 2e 6a 8a 99 37 72 11 f9 ..`Mh....j..7r.. +| 3216: 5b 29 77 3e 76 cd 57 6b 07 d0 cf d6 b4 cd d0 1e [)w>v.Wk........ +| 3232: dd 6e 1c a1 bd 7f bd 9d 57 0d 14 25 9e 4a 36 8c .n......W..%.J6. +| 3248: 9f 7b b4 f9 db 57 22 f1 0b 47 e8 2e 04 70 e2 d6 .....W...G...p.. +| 3264: 29 0e bb 52 33 80 6b 12 a5 20 97 3b 60 01 a2 8c )..R3.k.. .;`... +| 3280: 74 68 0f b1 b0 44 63 04 fe 69 2f 98 ee d9 e9 39 th...Dc..i/....9 +| 3296: af 0d 7a 79 00 ca d4 3e 96 e1 27 f8 27 e6 a7 d5 ..zy...>..'.'... +| 3312: 0c 7d 0c 13 de 31 bd 75 cf 41 04 1f 03 bb df 1d .....1.u.A...... +| 3328: c7 c0 3b a8 15 d8 15 4b b5 89 ff 4c 16 31 7a 62 ..;....K...L.1zb +| 3344: bb f8 d0 f2 43 fe 03 68 b0 35 7a 33 ae 4e a9 0b ....C..h.5z3.N.. +| 3360: 6a c8 35 d3 2c bf 6c 35 7b 1d 4c 9d 62 c1 96 01 j.5.,.l5..L.b... +| 3376: cf 7f 5f f0 20 ce 16 25 32 bc b2 bb 29 7a 6c f5 .._. ..%2...)zl. +| 3392: 36 17 6e 1b 93 4f 0f f0 de eb 67 a8 be 6a ad 7a 6.n..O....g..j.z +| 3408: e6 42 1f 63 d2 de 72 1b f6 01 08 e8 72 d6 85 81 .B.c..r.....r... +| 3424: 3d ec 80 70 f3 84 5c 54 2a 2d 66 f8 e9 ba d7 7c =..p...T*-f....| +| 3440: af df 2a 43 f4 6c 78 3e 5d e6 92 b4 9f 02 05 bb ..*C.lx>]....... +| 3456: 84 9e ed 44 71 74 21 c9 be 07 3b 98 b1 49 b7 81 ...Dqt!...;..I.. +| 3472: 6a 4a c4 08 28 1c 19 0f ae 36 c2 6c a2 55 af 09 jJ..(....6.l.U.. +| 3488: 47 da e4 a8 32 df 4f 68 83 b1 c8 89 a0 1a e1 72 G...2.Oh.......r +| 3504: b2 dd ab e9 fd 2f c8 79 d1 7e 69 16 e7 72 85 09 ...../.y.~i..r.. +| 3520: 95 77 18 1f 2c d1 51 31 68 88 8f 0a fb a1 4f 3b .w..,.Q1h.....O; +| 3536: 6d fb 24 cc 0a 77 34 cb 49 35 06 07 a6 cd 5e 27 m.$..w4.I5....^' +| 3552: d6 87 cc ae e1 00 cc 69 22 0e 4f 5a 59 cc 8f 30 .......i..OZY..0 +| 3568: c3 65 8f 8d bc 1a 86 8f 02 9b 0a 2c 5b b5 3d fa .e.........,[.=. +| 3584: c9 fd 1c 96 4c 59 e1 ac 43 17 e8 b1 18 57 c8 48 ....LY..C....W.H +| 3600: c7 df e9 d1 d3 11 d9 4b 44 33 d9 da 9d 86 7a cb .......KD3....z. +| 3616: de 17 ce 40 aa b8 2d b6 26 a9 68 e8 72 f3 cb e0 ...@..-.&.h.r... +| 3632: ae ec b1 1e 76 0b 5c 49 93 f5 f1 04 56 fa ac 33 ....v..I....V..3 +| 3648: 65 de d4 36 6b 3a 3c e1 a7 7b b6 66 e1 69 e7 86 e..6k:<....f.i.. +| 3664: c0 42 a2 71 e5 f1 35 3b 38 1e 52 a2 bb 72 39 de .B.q..5;8.R..r9. +| 3680: e2 78 23 3a 71 b3 0b 8d 94 e5 26 80 9d d3 67 76 .x#:q.....&...gv +| 3696: 14 aa 98 b4 f8 42 78 6c a1 8f 7a bd 5a 5f 99 1f .....Bxl..z.Z_.. +| 3712: 14 5a d4 02 bf c3 60 a7 c7 34 8f 81 4b 4a f6 c2 .Z....`..4..KJ.. +| 3728: 21 0e 6e 26 55 79 10 67 13 dc da ac 80 37 ad 95 !.n&Uy.g.....7.. +| 3744: 11 4a 54 b5 f9 da f6 66 26 99 51 98 1e 05 c0 5b .JT....f&.Q....[ +| 3760: 2d 23 b8 a2 5b de c3 f4 4f 2b 3a 22 10 1c 25 cc -#..[...O+:...%. +| 3776: 57 db 91 c4 6c df 33 a1 1f d6 a3 2e 56 de 54 d1 W...l.3.....V.T. +| 3792: 02 ac 2d ff 96 d6 db ec 80 f7 79 ea 81 30 68 d0 ..-.......y..0h. +| 3808: 90 b2 d1 cc 5e ac 50 df 5e 1b 0a 78 01 70 e8 21 ....^.P.^..x.p.! +| 3824: 35 08 64 46 9a 55 91 aa 9e 41 df 4f 5b 2b 3e 1c 5.dF.U...A.O[+>. +| 3840: bc f5 90 37 d7 35 ac 34 8e 70 94 28 46 eb c8 42 ...7.5.4.p.(F..B +| 3856: 10 df 40 b6 c0 76 57 6d 79 d6 f7 b6 cf ca 88 bc ..@..vWmy....... +| 3872: f9 f6 ce ad 69 d8 a6 a7 41 cb cb c6 bf 91 ef ff ....i...A....... +| 3888: 72 6f d2 b0 1e bf db 33 4b cd 1b 81 2d 65 de ff ro.....3K...-e.. +| 3904: 5c 4b 45 f0 57 c3 5e 1f 77 6d 14 b7 ea e4 8c 88 .KE.W.^.wm...... +| 3920: d0 b1 63 86 fc 68 26 e3 b2 7a 5f ca ec 09 00 4b ..c..h&..z_....K +| 3936: dc f0 d4 70 fd c1 10 36 a9 cc fc 0d fd 24 12 13 ...p...6.....$.. +| 3952: d9 bd f0 2f 87 99 00 d1 d4 7c c6 41 f3 59 2a db .../.....|.A.Y*. +| 3968: e1 7c 44 99 65 e7 4b 2f 42 a6 02 ad eb 7b db 14 .|D.e.K/B....... +| 3984: e4 c7 0d bf a7 f1 3d 69 01 cc c0 2e 82 06 fe 13 ......=i........ +| 4000: a3 f2 df 3f fe 65 d6 73 b3 d1 48 0c 00 5f 6c 4e ...?.e.s..H.._lN +| 4016: 1e ff f6 d2 69 08 a0 7a ed 50 16 a5 b1 56 1c f5 ....i..z.P...V.. +| 4032: 14 b0 b8 3a 38 19 a6 e7 ef 28 3d b9 9f 5e 38 39 ...:8....(=..^89 +| 4048: bc 41 c2 b1 fa 0b be 8b e9 9f 2f a0 be 8a eb 7f .A......../..... +| 4064: eb 55 5e fb 3a 36 82 02 fd 50 a6 b2 7d fb e7 5a .U^.:6...P.....Z +| 4080: a4 b2 d9 d9 8b 93 7a 2e ed c3 0c 13 68 22 cf f9 ......z.....h... +| page 18 offset 69632 +| 0: 0d 00 00 00 01 04 3f 00 04 3f 00 00 00 00 00 00 ......?..?...... +| 1072: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 97 ................ +| 1088: 3e 0a 06 00 ae 7c 00 00 18 91 e3 df f3 2f 7b 53 >....|......./.S +| 1104: 6d 25 6e 2a df e7 b0 18 33 2f 73 08 d7 a8 04 75 m%n*....3/s....u +| 1120: 49 89 b0 84 41 57 fb 8f 24 85 4e 24 c6 f1 14 03 I...AW..$.N$.... +| 1136: 9e 8e ef 9d f0 5c 54 69 11 78 d5 0f d6 97 22 1e ......Ti.x...... +| 1152: b5 17 73 66 0d 95 db ce c4 12 cb b0 ae a8 53 70 ..sf..........Sp +| 1168: c7 6b 02 f6 7d 56 49 eb 11 75 da b6 c2 ba a1 f2 .k...VI..u...... +| 1184: 19 8d c3 fc 6f 9d 3e e8 0e 1a 51 37 d7 ff b1 19 ....o.>...Q7.... +| 1200: 50 bd a1 74 26 2b 53 c6 1e 12 75 20 ed 43 51 0e P..t&+S...u .CQ. +| 1216: 28 f7 c4 a3 b5 8c ec de 2c 58 29 8c 74 81 d7 2d (.......,X).t..- +| 1232: a2 8d 0a d3 85 98 6b f2 3e 34 94 08 73 06 e7 06 ......k.>4..s... +| 1248: 04 e3 81 81 2a d8 5c 61 1b e9 7c ff 92 a8 c9 50 ....*..a..|....P +| 1264: 97 a0 8e f6 1f 4b 25 ae 8e fa 84 7a 6f a7 11 42 .....K%....zo..B +| 1280: c2 97 7a 93 42 39 ab 41 9f c1 7c 86 4a 66 06 cd ..z.B9.A..|.Jf.. +| 1296: e3 20 2c 3e ba f5 3c 32 fd a7 4a 73 50 74 a4 50 . ,>..<2..JsPt.P +| 1312: 89 28 57 71 bc 3d 31 77 89 ef cd cd ff b4 fb 2c .(Wq.=1w......., +| 1328: 5f a0 55 da b0 d9 18 d4 f7 a3 e4 f0 bf a8 86 b8 _.U............. +| 1344: 64 97 49 26 1b 4a b7 e7 6f 88 a9 a8 ab 54 1c 48 d.I&.J..o....T.H +| 1360: 0c 77 2c e3 7a 83 7d 05 c8 22 f7 b7 57 92 61 97 .w,.z.......W.a. +| 1376: 69 52 51 f7 8d 4c 2a b5 5e d5 55 f4 fc ae 35 bc iRQ..L*.^.U...5. +| 1392: fa 44 c5 19 ec 62 5e 98 12 bf a8 e1 53 32 76 4c .D...b^.....S2vL +| 1408: fb 4e 80 40 85 39 71 69 bd 55 90 9b 4c 46 b1 06 .N.@.9qi.U..LF.. +| 1424: 84 14 80 f3 2b ba 43 15 7e 12 44 4b 38 2e 01 a8 ....+.C.~.DK8... +| 1440: 2e 58 11 e1 dd 6b 24 9e 6a fb 21 14 f5 ae be 7a .X...k$.j.!....z +| 1456: 9b 26 0d a8 2f 29 8c 5a 63 8a cf 58 36 e1 76 fb .&../).Zc..X6.v. +| 1472: ca 95 7a 0c 74 0d d5 57 04 a4 65 5f 2a 0d 46 ee ..z.t..W..e_*.F. +| 1488: 0c 2e a6 a1 dd 04 b8 1d b7 72 a0 d5 ad cc 8e d3 .........r...... +| 1504: bb 04 bc 39 4d 22 0f 4e 6c b1 4d b4 08 3b 7f a5 ...9M..Nl.M..;.. +| 1520: f1 7d 18 c5 bf 43 86 b3 c1 3b 85 6f 30 84 1a 7b .....C...;.o0... +| 1536: 17 d0 91 d3 6d 99 cc ff ac 64 88 53 d3 ad 1e 5f ....m....d.S..._ +| 1552: ba 4c af 64 80 ae ca c7 27 56 7e 41 02 61 f1 d2 .L.d....'V~A.a.. +| 1568: e4 4b 99 7c e4 18 41 9c f7 b9 e8 5a 3f 6e a3 57 .K.|..A....Z?n.W +| 1584: ca 18 c5 8a a8 39 c6 fe 02 d0 9d 26 37 42 07 3d .....9.....&7B.= +| 1600: 38 4e fe 9a 3b 54 39 20 23 53 8a 84 2f 4a 06 06 8N..;T9 #S../J.. +| 1616: ed 56 dd d8 bf 56 ef ca a5 c0 a4 aa d5 88 41 42 .V...V........AB +| 1632: 8a c0 37 65 f3 c8 4c 87 a5 f3 3d 99 78 2b d7 4e ..7e..L...=.x+.N +| 1648: d7 6e 51 28 3f 5c 93 cb 56 08 91 39 8e 1d fb 26 .nQ(?...V..9...& +| 1664: 5d 80 7c 44 59 c4 d4 b3 5e 0c c1 3f 85 f8 d6 d0 ].|DY...^..?.... +| 1680: 25 0f a8 c4 40 a5 f7 63 ec 2b fc 78 e6 b4 7c 72 %...@..c.+.x..|r +| 1696: 87 f0 6d 2c 00 63 dc 29 4a e5 b5 6b 9e 73 33 b4 ..m,.c.)J..k.s3. +| 1712: 19 03 1a 5c de 8f 98 fa ce 4d e3 1a 62 6b 5b f5 .........M..bk[. +| 1728: 60 d6 4c 13 39 14 06 83 90 09 56 8b 71 3b b9 bc `.L.9.....V.q;.. +| 1744: e3 7e 5e ae f5 3e b7 aa bd 73 d6 f1 47 4a 84 60 .~^..>...s..GJ.` +| 1760: 20 d7 93 ce f0 f2 1a 63 b7 f0 7e 3b 4e 36 1c dd ......c..~;N6.. +| 1776: cc ef 50 7a a9 90 f7 48 05 fb 78 e8 72 71 df 3a ..Pz...H..x.rq.: +| 1792: 41 51 2c 4c 5d 0f cd 51 0e f3 5a a1 e6 81 15 b3 AQ,L]..Q..Z..... +| 1808: bb 48 5e 13 cf 46 4c f1 26 47 4b 51 87 d7 39 a6 .H^..FL.&GKQ..9. +| 1824: 38 c5 72 52 55 97 f6 81 bc 0f a1 95 72 b8 ec 6d 8.rRU.......r..m +| 1840: dd 6d 92 03 b5 0f d6 fd 7e 29 c9 55 50 57 71 2c .m......~).UPWq, +| 1856: 34 35 21 75 6a 4e e6 f0 6a 99 b7 51 b5 3e 0a 6c 45!ujN..j..Q.>.l +| 1872: 1b c0 ba cb 92 90 15 b8 35 b9 6b 78 f2 c6 03 48 ........5.kx...H +| 1888: 66 d6 2e 75 47 b8 eb d0 30 48 c9 4d 67 d1 c1 8a f..uG...0H.Mg... +| 1904: b2 9c a9 c0 3d a1 77 e3 35 e4 85 01 e7 dc 74 bc ....=.w.5.....t. +| 1920: 8d ff f6 f0 ad e1 35 63 75 6d ee 28 53 29 1c 9c ......5cum.(S).. +| 1936: 67 dd ea 7d 1f af 87 c3 2e 8d a4 23 d2 6b db 49 g..........#.k.I +| 1952: 1a 36 10 7b e1 6c 9a 1c a2 5f 17 fb 43 e5 da f5 .6...l..._..C... +| 1968: 2c 60 86 42 1d 28 a1 5f cc 73 b2 5d 69 2b fa 18 ,`.B.(._.s.]i+.. +| 1984: 85 a2 de 30 9a c7 08 42 b9 e3 b1 a1 32 1d 70 31 ...0...B....2.p1 +| 2000: b4 7a cf f4 57 5d 5e 45 53 1d 79 35 7b 4f 9a 2f .z..W]^ES.y5.O./ +| 2016: 80 11 76 23 60 dc 86 e7 f0 74 2d 46 51 40 01 10 ..v#`....t-FQ@.. +| 2032: 13 69 90 a9 fb cb 66 8e 3f e0 a1 4e 99 eb 61 1b .i....f.?..N..a. +| 2048: fe c7 5e b5 f5 0f a3 46 64 19 09 11 c0 83 c7 28 ..^....Fd......( +| 2064: 41 20 81 d3 f5 9c 21 2b ed 06 1a 4a 89 4d 6a e5 A ....!+...J.Mj. +| 2080: 2d 4f 95 d7 3b 95 8c 59 6f 3e 79 51 5f ef b8 2a -O..;..Yo>yQ_..* +| 2096: 43 ef 07 e2 d1 d1 13 38 67 54 88 e2 6f 03 fe 05 C......8gT..o... +| 2112: 10 0e e8 e9 4e 9a 75 92 ec 1f e7 56 61 3b 54 43 ....N.u....Va;TC +| 2128: 08 af e9 d5 56 bc 87 a3 25 6f e2 b8 01 62 1a 30 ....V...%o...b.0 +| 2144: ba 26 8a b1 9a 7e 44 a2 f5 d4 75 2e a0 d0 b8 71 .&...~D...u....q +| 2160: 61 61 92 ff 32 0b a8 94 a5 81 80 d0 3b b5 51 4a aa..2.......;.QJ +| 2176: 01 e2 8e 0a 38 90 19 f7 b4 38 b0 9c 32 e1 6a f0 ....8....8..2.j. +| 2192: f8 7b f0 58 0a d1 19 c0 20 d6 54 fe 28 ac 02 64 ...X.... .T.(..d +| 2208: c4 33 55 01 d2 bd 01 51 87 01 0c 66 bb 6e 1b 94 .3U....Q...f.n.. +| 2224: 9c 24 50 40 5b 2f 64 f9 b5 b6 6b 15 fd f8 e7 05 .$P@[/d...k..... +| 2240: 37 92 95 d3 b4 1e be b3 09 c0 74 f8 ca 03 10 89 7.........t..... +| 2256: 2d 4d f5 56 0f 6e 20 72 a7 5e 1d 9f fd d5 43 af -M.V.n r.^....C. +| 2272: 2e 7b f7 91 99 ed 47 ea c7 a4 76 82 ca 5f 94 20 ......G...v.._. +| 2288: 52 01 b4 21 cb 50 d5 f2 d6 cf 1d 11 0f b2 07 ee R..!.P.......... +| 2304: f2 cf 2b 52 7b 1d e0 be 16 a1 cf 06 52 1b 33 f5 ..+R........R.3. +| 2320: c2 8e ac f1 00 2b 82 cf b4 ae d5 f0 9b 4b 11 14 .....+.......K.. +| 2336: b5 c5 43 f4 08 9d 4a 59 ba a6 52 67 fe e1 bd 2d ..C...JY..Rg...- +| 2352: c2 40 c6 3e d1 8a d1 f5 a0 b4 d0 b1 92 3f 9e 1d .@.>.........?.. +| 2368: 18 5e b4 78 30 45 57 29 30 e1 5a bb ee 7b 1e 99 .^.x0EW)0.Z..... +| 2384: 0d 10 ea 14 7b 5e 36 32 8e e1 a7 e4 76 19 48 a8 .....^62....v.H. +| 2400: c6 19 74 b9 4e 31 81 24 fe 07 e3 86 ec c4 1d 05 ..t.N1.$........ +| 2416: 1f 5a 52 14 0c 08 3f d1 93 fe b1 46 27 87 58 21 .ZR...?....F'.X! +| 2432: 4b d5 ff bb 98 d8 f8 0c 96 66 02 9c 86 f9 0e 0b K........f...... +| 2448: f0 11 7a 99 39 52 38 84 22 04 58 07 f1 95 eb c7 ..z.9R8...X..... +| 2464: 44 2e a5 fe d4 68 a9 98 77 14 4f 8a 44 4c 7d c4 D....h..w.O.DL.. +| 2480: 49 8a d7 89 83 8e e6 1e d0 af b7 41 3d 1a 85 14 I..........A=... +| 2496: ec 3a e5 1b 2c c5 17 77 85 82 19 57 37 94 93 7e .:..,..w...W7..~ +| 2512: 52 16 a3 dd 0a fd 57 1a 57 32 11 4d 71 e3 4b 1b R.....W.W2.Mq.K. +| 2528: c5 02 d7 89 74 85 b0 3d 8d 7b 53 a2 d6 60 99 d4 ....t..=..S..`.. +| 2544: ce f0 1c 3d a3 aa db db c4 80 38 7a cb 12 7e 66 ...=......8z..~f +| 2560: 3f 69 af fa 57 49 35 05 94 33 df fe 91 8a 25 3d ?i..WI5..3....%= +| 2576: 9b 32 71 72 d2 bc bc 23 61 69 9c 68 a7 58 c0 f1 .2qr...#ai.h.X.. +| 2592: 0e 20 a9 d3 d2 a9 11 d7 ee 52 46 70 b7 aa 6b f3 . .......RFp..k. +| 2608: 4a 51 a7 a5 26 92 35 44 f9 cc 7b c7 ec db 5d b6 JQ..&.5D......]. +| 2624: 5c 88 d9 bd 14 df a0 14 35 09 2f c8 76 d4 4c 19 ........5./.v.L. +| 2640: 12 29 b9 dd 9b 21 ed b8 ee 1f f9 38 05 9e 93 aa .)...!.....8.... +| 2656: ab 82 15 69 88 81 f6 4f 1b 72 bb 84 cb 9c 33 ec ...i...O.r....3. +| 2672: 94 4d 44 42 8e 8f 12 91 1f 32 07 09 38 8b 44 be .MDB.....2..8.D. +| 2688: 9e 31 49 9e 76 04 d8 b7 69 ad f1 59 81 5f d7 a0 .1I.v...i..Y._.. +| 2704: 2f 34 94 27 b4 c1 e9 f0 18 a7 43 7e 1e fd 27 5b /4.'......C~..'[ +| 2720: d8 e9 c3 5d be 8f 91 f2 4a cd 33 5f 6c 76 f6 f1 ...]....J.3_lv.. +| 2736: 17 ae 80 87 e7 ec 22 ef 73 8e a7 3a 30 dd 27 3d ........s..:0.'= +| 2752: 6d 95 59 eb f3 7f 97 b7 b9 8d ff 86 ed dd 5d f4 m.Y...........]. +| 2768: 39 3c 6a 13 3d 7a 93 3d 37 ed 8c d6 98 0f 0b 7a 9.....p..$o..... +| 3040: e1 6a a9 0a d8 f6 89 09 51 98 1f 89 1b f1 34 87 .j......Q.....4. +| 3056: a5 b3 22 d0 65 53 bd ae 57 7a 8a 8f a8 a6 10 9e ....eS..Wz...... +| 3072: 72 7c 6e 37 8f 67 db d8 89 54 77 87 6d 63 6e 31 r|n7.g...Tw.mcn1 +| 3088: ef ae 41 51 22 cc 24 08 89 f6 dd 2c f9 cb a4 f8 ..AQ..$....,.... +| 3104: ea f9 42 33 01 fd cf b9 d6 73 aa b4 9d 45 31 eb ..B3.....s...E1. +| 3120: 42 ca df 3a d2 3c c9 41 28 fd e4 a2 2f cf bf ca B..:.<.A(.../... +| 3136: 63 94 a2 74 ee 6b 4c 62 bb 74 5f cc 39 68 b5 e9 c..t.kLb.t_.9h.. +| 3152: 68 47 90 45 85 f0 20 4e 3a fe 4a 4f ab f2 fe c7 hG.E.. N:.JO.... +| 3168: f5 23 56 6e 09 9e c6 3d 36 30 82 62 6f 5f 78 f6 .#Vn...=60.bo_x. +| 3184: f6 07 58 e8 fd 98 09 e5 a5 5b 65 27 43 e6 9e 3d ..X......[e'C..= +| 3200: 98 d5 db 1d 09 35 48 b0 cd 5e 53 a1 d6 b2 4f 85 .....5H..^S...O. +| 3216: e5 4d 80 18 8f 78 6e a0 0e 35 08 7d 1d 5d 3e ab .M...xn..5...]>. +| 3232: c3 5c dc ec c9 e0 22 1a 17 a3 80 40 3d e7 66 df ...........@=.f. +| 3248: b2 38 f4 59 6d 20 e8 83 93 53 8c ac 52 26 ec 60 .8.Ym ...S..R&.` +| 3264: f8 50 85 1c 97 9c ae a3 9f bd af 75 be 73 23 b5 .P.........u.s#. +| 3280: 7f fa 1f 7f 28 16 c7 7c 5f 0a 5b 32 1a c8 45 cf ....(..|_.[2..E. +| 3296: df 2e 8e d1 d9 59 76 d5 6c 8d b5 12 8a c5 77 32 .....Yv.l.....w2 +| 3312: d4 87 02 80 09 b6 43 34 76 09 f9 b4 74 66 ce ee ......C4v...tf.. +| 3328: 26 77 62 11 b0 23 92 5c 72 38 41 e9 70 4f b5 83 &wb..#..r8A.pO.. +| 3344: e4 35 7c b2 2a 38 49 12 48 18 1c 95 5c b1 18 1a .5|.*8I.H....... +| 3360: 51 cd 4b 7b 22 94 0e c7 da c1 30 97 d1 be 42 07 Q.K.......0...B. +| 3376: 94 01 a5 fd 2f 0d 2c 53 33 c0 91 c6 bc af 2c f2 ..../.,S3.....,. +| 3392: f2 07 6e 4a d2 22 3e 3c 18 3c ca 24 bf 42 78 7a ..nJ..><.<.$.Bxz +| 3408: 69 0e a9 12 e3 20 fa 8b ad 75 27 0c c3 82 84 8d i.... ...u'..... +| 3424: 46 af 3e 1e 89 27 4d 7e f7 21 96 b4 6c 17 7f 19 F.>..'M~.!..l... +| 3440: 8a 78 d7 bb 40 67 35 45 2d d4 97 6a 4c e9 4a 58 .x..@g5E-..jL.JX +| 3456: 22 a3 bb 31 d5 4f 26 40 cc fe c8 cd 1d a4 0d 67 ...1.O&@.......g +| 3472: 14 13 05 4d 0e 15 40 ea 7d 62 c6 80 08 b9 f6 b2 ...M..@..b...... +| 3488: 44 58 66 d5 ca b6 f4 20 b0 4a b8 37 64 3d b0 a7 DXf.... .J.7d=.. +| 3504: a7 87 70 26 b2 ea f9 cf 98 03 6e 63 5a fe c4 cd ..p&......ncZ... +| 3520: 80 cb ca f4 a6 02 11 39 4f 6c bf bf a4 8e 99 32 .......9Ol.....2 +| 3536: e3 47 51 3e 85 f6 84 6b 3d 9a fe 2f 96 18 49 2a .GQ>...k=../..I* +| 3552: dc a4 56 77 d1 3f 94 61 2b 58 e7 74 ee c9 16 7b ..Vw.?.a+X.t.... +| 3568: 6f 76 47 da b2 fb 89 75 80 78 05 69 c8 3e f0 97 ovG....u.x.i.>.. +| 3584: 1b 40 f1 48 43 7f cd 0b b1 c6 cf 59 73 4f 3f 33 .@.HC......YsO?3 +| 3600: 2e 32 d2 b6 69 fc 6b f2 20 0f 12 bf f0 98 2e e2 .2..i.k. ....... +| 3616: 4f ed 80 27 00 e2 b1 c2 ca cd 6b de e1 b4 af 9e O..'......k..... +| 3632: df 4f d2 da 6d 9f b4 5d 99 4a c4 59 d4 e1 98 05 .O..m..].J.Y.... +| 3648: 68 00 a5 72 3f 0e 35 29 59 81 8a f9 f2 c0 5a de h..r?.5)Y.....Z. +| 3664: 25 35 d5 60 03 f7 0f 20 3a bb a6 45 fc cc 2d e9 %5.`... :..E..-. +| 3680: fe 37 0b 6c f2 96 19 dc 39 62 6e f7 9c 17 37 8c .7.l....9bn...7. +| 3696: 8a dd 62 a2 a3 e6 b8 a0 cd 61 f5 27 3b 29 e2 f4 ..b......a.';).. +| 3712: 0a 06 21 c3 b9 8e 70 34 59 ca d9 a1 92 db a9 74 ..!...p4Y......t +| 3728: d1 7c a1 ef 64 c2 75 51 02 b3 6a 57 6d 12 f6 eb .|..d.uQ..jWm... +| 3744: 3b 41 ce a5 ca 69 62 3e c3 55 eb 1c 80 01 79 05 ;A...ib>.U....y. +| 3760: a4 51 7c 45 00 76 41 5e 57 1a 88 aa 15 c8 42 82 .Q|E.vA^W.....B. +| 3776: d5 83 74 97 93 b4 28 df 4d 82 7d 6f ef 6a d4 e4 ..t...(.M..o.j.. +| 3792: 03 e7 20 4a e8 84 54 0c 6e 5f 8e b0 d1 0d 67 aa .. J..T.n_....g. +| 3808: f9 20 21 ea c0 4e 5c e3 f8 65 d7 67 0a 7a e0 0b . !..N...e.g.z.. +| 3824: d8 1e 53 95 bf 24 39 12 d4 30 8c 2c b4 13 2e bd ..S..$9..0.,.... +| 3840: f8 81 d5 15 35 d7 3f f2 23 a4 2c 1b cd 29 e8 88 ....5.?.#.,..).. +| 3856: a8 f1 cc 3f 9b 72 d0 8e c0 cb 80 ca c9 68 1a ca ...?.r.......h.. +| 3872: 38 09 59 e5 a6 33 95 57 55 c9 dd c4 8a 8d 36 e5 8.Y..3.WU.....6. +| 3888: 0c 40 95 77 63 4b 82 53 98 d0 bd cd 57 b6 f7 2b .@.wcK.S....W..+ +| 3904: fe 1c 25 d4 95 3d 4c 63 b7 fb 94 37 3f 6d 96 28 ..%..=Lc...7?m.( +| 3920: 1d 13 76 d2 ab 9a 7e 8f f5 a2 e6 15 0b 1c 10 14 ..v...~......... +| 3936: c3 b5 c0 1c 52 af 2a 32 35 05 4a 0c f1 cb ed 5e ....R.*25.J....^ +| 3952: 42 6c 31 8b dc 78 4b 68 5a b7 0f e3 6c d6 da e6 Bl1..xKhZ...l... +| 3968: 59 f3 34 43 47 1c 2b f6 f4 eb 6e 12 4d 60 f4 d9 Y.4CG.+...n.M`.. +| 3984: 39 2f 84 a5 8f f0 03 e7 ce b6 10 d0 a9 f9 69 76 9/............iv +| 4000: 94 29 2e 60 db fc e7 5f e6 c5 5b b9 c5 a7 1f 95 .).`..._..[..... +| 4016: 0d 66 aa 55 7b f5 ec 67 c6 f7 26 52 c4 02 60 31 .f.U...g..&R..`1 +| 4032: 7d 5e de ba 06 2f 09 1d a1 db 61 83 c8 13 be 98 .^.../....a..... +| 4048: 14 9d c9 15 1e 33 04 de a5 53 ed 42 ab 45 b1 f3 .....3...S.B.E.. +| 4064: 09 a7 47 54 14 bc a0 88 80 58 94 0a 78 f2 31 3d ..GT.....X..x.1= +| 4080: bc 0d 85 bf da a0 7e d2 be e4 1c cc b4 45 a8 bd ......~......E.. +| page 19 offset 73728 +| 0: 0d 0f e6 00 03 0c 8a 00 0c 8a 0f ec 0c 94 00 00 ................ +| 3200: 00 00 00 00 00 00 00 00 00 00 08 01 03 00 16 2e ................ +| 3216: b1 7d 24 24 86 4a 84 80 80 80 80 01 04 00 8d 18 ..$$.J.......... +| 3232: 00 00 03 2b 02 30 30 01 02 06 01 02 06 01 02 06 ...+.00......... +| 3248: 1f 02 03 01 02 03 01 02 03 01 08 32 30 31 36 30 ...........20160 +| 3264: 36 30 39 01 02 07 01 02 07 01 02 07 01 01 34 00 609...........4. +| 3280: 02 05 01 02 05 01 02 05 01 01 35 01 02 04 01 02 ..........5..... +| 3296: 04 01 02 04 02 07 30 30 30 30 30 30 30 1c 02 3d ......0000000..= +| 3312: 01 02 04 01 02 04 01 06 62 69 6e 61 72 79 03 06 ........binary.. +| 3328: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3344: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3360: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3376: 03 06 01 02 02 03 06 01 02 02 01 08 63 6f 6d 70 ............comp +| 3392: 69 6c 65 72 01 02 02 01 02 02 01 02 02 01 06 64 iler...........d +| 3408: 62 73 74 61 74 07 02 03 01 02 03 01 02 03 02 04 bstat........... +| 3424: 65 62 75 67 04 02 02 01 02 02 01 02 02 01 07 65 ebug...........e +| 3440: 6e 61 62 6c 65 07 02 02 01 02 02 01 d3 02 01 02 nable........... +| 3456: 02 01 02 02 02 02 02 01 02 02 01 02 02 01 02 02 ................ +| 3472: 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 ................ +| 3488: 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 ................ +| 3504: 02 01 02 02 02 08 78 74 65 6e 73 69 6f 6e 1f 02 ......xtension.. +| 3520: 04 01 02 04 01 02 04 01 04 66 74 73 34 0a 02 03 .........fts4... +| 3536: 01 02 03 01 02 03 04 01 35 0d 02 03 01 02 03 01 ........5....... +| 3552: 02 03 01 03 67 63 63 01 02 54 01 02 03 01 02 03 ....gcc..T...... +| 3568: 02 06 65 6f 70 6f 6c 79 10 02 03 01 02 03 01 02 ..eopoly........ +| 3584: 03 01 05 6a 73 6f 6e 31 13 02 03 01 02 03 01 02 ...json1........ +| 3600: 03 01 04 6c 6f 61 64 1f 02 03 01 02 03 01 02 03 ...load......... +| 3616: 01 03 6d 61 78 1c 02 02 01 02 02 01 02 02 02 05 ..max........... +| 3632: 65 6d 6f 72 79 1c 02 03 01 02 03 01 02 03 04 04 emory........... +| 3648: 73 79 73 35 16 02 03 01 02 03 01 02 03 01 06 6e sys5...........n +| 3664: 6f 63 61 73 65 02 06 01 02 02 03 06 01 02 02 03 ocase........... +| 3680: 06 01 02 02 13 06 01 02 02 03 06 01 02 02 03 06 ................ +| 3696: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3712: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3728: 02 01 04 6f 6d 69 74 1f 02 02 01 02 02 01 02 02 ...omit......... +| 3744: 01 05 72 74 72 65 65 19 02 03 01 02 03 01 02 03 ..rtree......... +| 3760: 04 02 69 6d 01 06 01 02 02 03 06 01 02 02 03 06 ..im............ +| 3776: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3792: 02 01 f3 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3808: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 08 ................ +| 3824: 01 0a 74 68 72 65 61 64 73 61 66 65 22 02 02 01 ..threadsafe.... +| 3840: 02 02 01 02 02 01 04 76 74 61 62 07 02 04 01 02 .......vtab..... +| 3856: 04 01 02 04 01 01 78 01 06 01 01 02 01 06 01 01 ......x......... +| 3872: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 ................ +| 3888: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3904: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 3920: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 3936: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 3952: 02 01 06 01 01 02 01 06 01 01 02 ad 06 01 01 02 ................ +| 3968: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3984: 06 01 01 01 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 4000: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 4016: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 4032: 02 01 06 01 01 02 01 06 01 01 02 04 15 13 0c 0c ................ +| 4048: 12 44 13 11 0f 47 13 0e fc 0e 11 10 0f 0e 10 0f .D...G.......... +| 4064: 44 0f 10 40 15 0f 00 00 00 06 14 24 12 0a 03 00 D..@.......$.... +| 4080: 2a 00 00 00 00 01 02 08 00 02 01 01 01 02 01 07 *............... +| page 20 offset 77824 +| 0: 0d 00 00 00 01 00 22 00 00 22 00 00 00 00 00 00 ................ +| 32: 00 00 9f 56 88 80 80 80 80 01 04 00 bf 30 00 00 ...V.........0.. +| 48: 0e 9c 02 30 30 01 0c 74 81 44 06 81 0b 01 0a 81 ...00..t.D...... +| 64: 03 58 58 04 01 10 82 66 10 0f 0b 81 14 24 01 0c .XX....f.....$.. +| 80: 81 14 47 4a 20 6e 01 0e 71 81 41 05 82 0f 03 01 ..GJ n..q.A..... +| 96: 0c 53 54 4c 2c 70 7d 01 12 81 43 06 1e 37 42 37 .STL,p....C..7B7 +| 112: 77 3e 01 14 81 22 07 81 30 81 5d 03 05 0d 01 08 w>......0.]..... +| 128: 82 01 81 21 01 0e 81 45 54 51 81 0a 61 02 01 30 ...!...ETQ..a..0 +| 144: 01 04 82 6e 02 01 36 08 04 83 7e 02 01 38 02 04 ...n..6...~..8.. +| 160: 83 4f 02 01 62 06 04 81 11 03 01 36 08 04 81 5b .O..b......6...[ +| 176: 03 01 64 02 04 84 5d 02 01 63 05 04 81 02 02 01 ..d...]..c...... +| 192: 64 07 04 81 67 02 01 65 0a 04 83 0c 03 02 70 77 d...g..e......pw +| 208: 04 04 83 70 02 02 66 79 08 04 82 25 02 01 68 0a ...p..fy...%..h. +| 224: 04 81 34 02 01 6b 03 02 4d 03 04 84 32 02 01 70 ..4..k..M...2..p +| 240: 01 04 81 15 02 01 73 07 04 83 4d 02 02 37 02 01 ......s...M..7.. +| 256: 74 02 02 40 03 01 68 07 04 83 45 02 02 76 69 09 t..@..h...E..vi. +| 272: 04 82 66 03 01 6c 09 04 82 34 02 01 79 01 04 84 ..f..l...4..y... +| 288: 13 02 02 c2 ba 0a 04 81 68 03 01 bc 0a 04 83 0d ........h....... +| 304: 02 04 ca 80 69 6c 09 04 81 5a 02 02 d3 84 05 04 ....il...Z...... +| 320: 83 0a 01 01 31 01 16 18 1a 30 81 4c 31 12 54 81 ....1....0.L1.T. +| 336: 00 1d 01 0e 02 25 82 4b 1d 3f 76 01 0c 43 79 4b .....%.K.?v..CyK +| 352: 5b 0b 76 01 0e 35 1a 81 08 82 77 10 01 0c 81 24 [.v..5....w....$ +| 368: 18 07 74 6f 01 08 29 81 08 69 01 10 81 0e 0d 4a ..to..)..i.....J +| 384: 81 01 07 58 01 08 6b 51 76 7a 01 0c 20 81 18 6c ...X..kQvz.. ..l +| 400: 2c 18 01 08 83 7c 7b 0e 02 01 32 08 02 48 03 04 ,....|....2..H.. +| 416: f3 a1 97 a5 03 04 83 71 02 01 33 02 04 81 2f 03 .......q..3.../. +| 432: 01 66 09 04 83 15 02 03 61 ca b1 08 04 81 48 02 .f......a.....H. +| 448: 01 63 01 02 1c 02 03 65 c2 aa 06 02 5a 02 01 68 .c.....e....Z..h +| 464: 03 04 81 6e 02 01 69 0a 04 82 5a 03 01 69 08 04 ...n..i...Z..i.. +| 480: 81 65 02 01 6a 02 02 64 06 04 84 12 02 01 6c 01 .e..j..d......l. +| 496: 02 1f 02 01 6e 03 04 83 24 02 03 6f c2 b3 07 04 ....n...$..o.... +| 512: 84 52 02 03 72 c9 9b 04 04 82 6e 02 01 73 07 04 .R..r.....n..s.. +| 528: 82 10 02 01 77 0a 02 31 02 01 79 07 04 81 7f 02 ....w..1..y..... +| 544: 02 7a 62 09 04 83 49 02 02 c2 b9 02 04 83 01 03 .zb...I......... +| 560: 02 bd 75 09 04 83 45 02 02 c6 aa 02 04 83 08 02 ..u...E......... +| 576: 03 d7 92 6a 08 02 04 01 01 32 01 0c 81 7d 72 81 ...j.....2....r. +| 592: 4e 36 01 0e 1a 81 10 81 10 11 2e 01 08 78 49 82 N6...........xI. +| 608: 3b 01 0e 73 81 64 05 1b 81 69 01 1a 18 06 7e 2c ;..s.d...i....~, +| 624: 21 12 20 5a 28 69 06 1e 06 01 0c 81 48 05 31 81 !. Z(i......H.1. +| 640: 24 01 14 25 81 42 3e 81 16 81 37 13 13 01 0c 21 $..%.B>...7....! +| 656: 0d 0f 3e 82 03 01 08 6b 60 81 27 01 10 2d 81 41 ..>....k`.'..-.A +| 672: 06 69 81 08 30 02 01 32 03 04 81 01 02 01 33 08 .i..0..2......3. +| 688: 02 3c 02 01 35 0a 04 84 64 02 01 38 05 04 83 41 .<..5...d..8...A +| 704: 02 02 61 69 09 04 82 49 02 03 66 c8 ab 09 04 82 ..ai...I..f..... +| 720: 68 02 01 68 04 04 83 27 02 01 6c 04 04 84 18 03 h..h...'..l..... +| 736: 02 72 6e 06 04 82 30 02 01 6e 04 04 84 55 03 01 .rn...0..n...U.. +| 752: 6d 05 04 83 30 03 03 76 c2 bd 09 02 57 02 01 71 m...0..v....W..q +| 768: 02 04 84 22 03 04 84 11 03 05 72 f0 92 bc bc 0a ..........r..... +| 784: 04 82 40 02 01 72 04 04 83 5b 02 01 73 05 02 6f ..@..r...[..s..o +| 800: 03 04 83 6f 02 01 75 04 02 55 02 01 78 03 04 82 ...o..u..U..x... +| 816: 56 02 01 7a 06 04 85 01 03 02 25 02 02 c2 b2 07 V..z......%..... +| 832: 04 83 7a 03 03 b3 ce bc 03 04 84 2b 03 03 bc c2 ..z........+.... +| 848: b2 09 04 83 53 02 04 ce a2 38 6e 07 04 82 23 03 ....S....8n...#. +| 864: 01 bc 08 04 83 7f 02 03 d2 b7 69 0a 04 84 19 02 ..........i..... +| 880: 02 d6 84 09 04 81 0e 01 01 33 01 06 2a 83 69 01 .........3..*.i. +| 896: 18 0f 0e 25 42 81 71 81 21 17 07 03 0d 01 1a 15 ...%B.q.!....... +| 912: 13 42 03 81 40 45 21 60 4d 1a 04 0e 01 0a 83 18 .B..@E!`M....... +| 928: 81 11 0a 01 08 64 83 24 24 01 16 62 1d 0c 06 62 .....d.$$..b...b +| 944: 50 2c 73 17 15 4d 01 0e 1b 82 15 4d 31 3a 4f 01 P,s..M.....M1:O. +| 960: 0e 22 63 51 81 7f 81 22 01 0e 21 81 0a 3e 42 82 ..cQ......!..>B. +| 976: 08 01 12 05 82 0a 33 19 10 81 37 63 02 01 32 05 ......3...7c..2. +| 992: 04 82 3c 02 01 36 02 04 84 0f 02 01 38 06 04 84 ..<..6......8... +| 1008: 1f 02 01 39 06 04 82 06 02 01 61 08 04 81 00 03 ...9......a..... +| 1024: 03 33 6f 62 01 02 69 02 01 65 02 04 84 05 07 04 .3ob..i..e...... +| 1040: 84 0e 02 01 67 09 04 82 44 02 01 6a 07 02 55 02 ....g...D..j..U. +| 1056: 01 6b 02 04 84 04 07 04 84 44 02 01 6c 06 04 84 .k.......D..l... +| 1072: 0d 02 01 6e 06 04 84 10 03 01 77 03 02 46 02 01 ...n......w..F.. +| 1088: 74 08 04 83 33 03 01 77 03 04 81 5b 02 01 75 0a t...3..w...[..u. +| 1104: 04 81 75 02 01 78 01 04 83 48 03 02 c2 b2 02 04 ..u..x...H...... +| 1120: 81 77 02 04 c2 b3 c2 b3 09 02 1b 02 06 f2 98 9e .w.............. +| 1136: a2 61 6c 08 04 83 31 01 01 34 01 0e 81 1c 82 11 .al...1..4...... +| 1152: 4e 81 06 01 10 4a 12 64 39 18 81 24 72 01 04 81 N....J.d9..$r... +| 1168: 0b 01 10 81 08 81 50 48 2a 79 15 01 14 13 72 07 ......PH*y....r. +| 1184: 2f 42 81 21 50 81 02 01 10 6d 46 3e 81 0c 81 17 /B.!P....mF>.... +| 1200: 23 01 08 0a 5f 83 38 01 0a 57 81 3e 82 0f 01 10 #..._.8..W.>.... +| 1216: 81 07 81 7e 0c 81 14 1d 01 08 1f 82 42 4e 02 01 ...~........BN.. +| 1232: 30 01 04 81 36 04 04 81 39 03 02 c2 bd 02 04 81 0...6...9....... +| 1248: 1e 02 01 31 05 04 84 31 02 01 35 0a 04 81 2b 02 ...1...1..5...+. +| 1264: 06 37 c2 b9 eb a0 86 04 04 83 0d 02 02 61 6f 04 .7...........ao. +| 1280: 04 83 39 02 01 63 07 04 83 3c 03 01 67 0a 04 84 ..9..c...<..g... +| 1296: 6a 02 01 65 07 06 68 82 7d 02 03 66 c2 be 07 04 j..e..h....f.... +| 1312: 81 3d 02 01 67 08 02 58 01 04 83 16 02 01 6c 03 .=..g..X......l. +| 1328: 04 81 73 02 01 6d 08 04 81 42 02 01 6e 04 04 83 ..s..m...B..n... +| 1344: 75 02 03 6f 65 32 06 04 84 4f 02 01 70 05 04 81 u..oe2...O..p... +| 1360: 56 03 01 77 09 04 81 47 02 02 72 32 05 02 0b 02 V..w...G..r2.... +| 1376: 01 73 01 02 47 04 04 84 09 02 01 74 07 04 85 05 .s..G......t.... +| 1392: 02 01 75 01 02 5b 02 04 76 e5 b9 83 07 04 82 03 ..u..[..v....... +| 1408: 02 01 78 01 04 81 11 02 02 c6 bf 02 04 81 3b 02 ..x...........;. +| 1424: 02 ce bc 06 04 84 3a 02 03 e1 83 93 03 04 81 2b ......:........+ +| 1440: 01 01 35 01 0e 81 7b 61 23 71 55 0c 01 08 82 30 ..5....a#qU....0 +| 1456: 2a 7b 01 0c 81 45 81 20 72 38 01 0c 29 82 25 55 *....E. r8..).%U +| 1472: 4c 21 01 0c 81 53 38 25 3a 63 01 08 83 55 81 0f L!...S8%:c...U.. +| 1488: 01 0e 81 32 5b 81 1b 31 6f 01 16 1e 6d 17 0e 17 ...2[..1o...m... +| 1504: 1f 6b 0b 3e 3d 3a 01 12 32 81 4a 81 04 55 47 20 .k.>=:..2.J..UG +| 1520: 09 01 10 82 4b 81 0a 17 3e 05 2a 02 01 31 08 04 ....K...>.*..1.. +| 1536: 84 04 02 02 36 68 06 02 18 02 01 61 02 04 84 3d ....6h.....a...= +| 1552: 08 04 81 38 03 01 34 01 02 71 02 03 63 75 6d 0a ...8..4..q..cum. +| 1568: 04 81 3a 02 01 64 09 02 52 01 04 82 48 02 01 68 ..:..d..R...H..h +| 1584: 08 02 30 02 04 83 4e 02 01 6e 01 02 42 02 05 6f ..0...N..n..B..o +| 1600: 63 6c dc ab 08 02 11 03 04 67 69 c2 be 03 04 82 cl.......gi..... +| 1616: 29 02 01 71 07 04 82 12 02 01 79 06 04 84 1b 02 )..q......y..... +| 1632: 02 7a 33 09 04 83 4c 02 02 c2 b2 05 04 84 17 03 .z3...L......... +| 1648: 01 b9 09 02 35 04 02 6b 78 0a 04 81 31 03 01 bc ....5..kx...1... +| 1664: 0a 02 41 02 08 cb 8a f0 9c bb a1 67 68 02 04 81 ..A........gh... +| 1680: 75 01 01 36 01 10 55 77 30 36 21 31 69 60 01 0e u..6..Uw06!1i`.. +| 1696: 3b 0b 3e 4e 1c 2c 1b 01 16 44 32 54 07 2f 0e 81 ;.>N.,...D2T./.. +| 1712: 11 81 03 14 01 04 81 63 01 0e 81 16 82 5c 15 20 .......c....... +| 1728: 19 01 12 0d 1f 43 6e 39 06 18 81 54 01 0c 81 40 .....Cn9...T...@ +| 1744: 2c 81 0f 4d 01 10 16 82 5e 0e 08 0d 78 51 01 12 ,..M....^...xQ.. +| 1760: 1d 81 73 81 22 29 17 66 1a 01 0a 81 3f 81 3f 19 ..s..).f....?.?. +| 1776: 02 01 30 0a 04 83 47 03 01 6d 05 04 82 3e 02 01 ..0...G..m...>.. +| 1792: 32 07 04 81 5e 03 04 82 19 02 04 34 71 6c 38 01 2...^......4ql8. +| 1808: 04 83 6b 02 02 37 67 08 04 84 53 02 01 64 04 02 ..k..7g...S..d.. +| 1824: 7b 03 01 71 02 04 81 71 02 01 65 01 04 83 66 05 ...q...q..e...f. +| 1840: 04 84 5c 02 01 69 09 04 81 3a 02 01 6a 03 04 83 .....i...:..j... +| 1856: 35 02 01 6b 06 04 83 6d 01 04 83 5f 02 04 84 0f 5..k...m..._.... +| 1872: 02 01 73 03 04 83 2d 02 01 74 06 04 84 0e 02 04 ..s...-..t...... +| 1888: 82 02 02 01 75 05 04 83 73 03 01 37 03 04 84 32 ....u...s..7...2 +| 1904: 02 01 76 03 04 83 0f 02 01 77 08 04 82 33 03 01 ..v......w...3.. +| 1920: 6f 01 02 0b 02 01 78 02 02 36 02 02 c2 b3 03 04 o.....x..6...... +| 1936: 81 07 02 04 84 29 02 02 ca a9 05 04 83 19 02 03 .....).......... +| 1952: ce bc 79 08 04 84 4b 02 02 d0 b8 05 04 81 06 02 ..y...K......... +| 1968: 06 f1 b5 87 92 35 6b 05 04 82 34 01 01 37 01 14 .....5k...4..7.. +| 1984: 81 3d 4b 17 08 16 81 03 81 20 01 0e 42 81 27 32 .=K...... ..B.'2 +| 2000: 20 0c 7a 01 0a 4c 83 12 40 19 01 16 06 21 6f 81 .z..L..@....!o. +| 2016: 02 70 36 0d 49 2e 2d 01 14 81 12 19 19 60 06 4c .p6.I.-......`.L +| 2032: 44 17 4f 01 12 03 81 12 04 81 23 81 4b 0a 01 12 D.O.......#.K... +| 2048: 0b 48 50 22 10 7f 81 38 56 01 0e 27 38 81 21 82 .HP....8V..'8.!. +| 2064: 08 2a 01 08 81 6b 41 24 01 0c 81 7c 73 81 3e 05 .*...kA$...|s.>. +| 2080: 02 02 34 67 05 04 83 5d 02 01 37 05 04 81 2f 02 ..4g...]..7.../. +| 2096: 02 73 02 01 38 01 02 21 02 01 39 09 02 4a 02 01 .s..8..!..9..J.. +| 2112: 62 0a 02 70 03 02 71 7a 01 04 83 26 02 01 64 0a b..p..qz...&..d. +| 2128: 04 84 04 02 01 65 0a 02 78 02 01 68 02 04 82 26 .....e..x..h...& +| 2144: 02 02 69 35 06 04 82 02 02 01 6b 02 04 82 0a 03 ..i5......k..... +| 2160: 02 c2 bc 05 04 82 08 02 01 6d 08 02 54 02 01 6e .........m..T..n +| 2176: 06 04 82 32 02 03 6f 74 61 02 04 81 2a 02 05 71 ...2..ota...*..q +| 2192: f2 96 8c 90 01 04 83 5a 02 01 72 09 04 83 31 02 .......Z..r...1. +| 2208: 01 74 03 02 71 04 04 83 62 02 02 79 65 04 04 83 .t..q...b..ye... +| 2224: 26 02 02 c2 aa 05 04 82 19 03 01 b3 02 02 10 02 &............... +| 2240: 04 83 48 03 01 bd 01 04 84 10 02 02 cf ad 06 04 ..H............. +| 2256: 82 04 02 04 f0 97 97 98 08 04 83 1f 02 04 f3 a5 ................ +| 2272: b3 87 08 04 81 64 01 01 38 01 0a 16 82 49 81 30 .....d..8....I.0 +| 2288: 01 0e 81 19 27 34 53 82 19 01 12 20 1f 5c 5f 81 ....'4S.... .._. +| 2304: 50 81 17 04 01 10 0e 81 2e 2d 51 67 81 16 01 0c P........-Qg.... +| 2320: 81 62 09 81 79 10 01 08 63 04 81 1c 01 0a 81 2e .b..y...c....... +| 2336: 5d 81 0f 01 0a 0b 81 40 81 63 01 10 27 81 17 81 ]......@.c..'... +| 2352: 2b 48 6c 4c 01 10 81 23 4d 64 0a 7f 81 01 02 01 +HlL...#Md...... +| 2368: 31 03 02 6b 02 01 36 06 04 83 79 02 01 37 08 04 1..k..6...y..7.. +| 2384: 84 47 02 04 39 c2 bc 61 09 04 84 61 02 05 64 c2 .G..9..a...a..d. +| 2400: b9 da 9c 01 04 81 73 02 01 66 01 04 81 58 08 04 ......s..f...X.. +| 2416: 82 39 02 02 67 74 0a 04 81 5f 02 01 68 09 04 83 .9..gt..._..h... +| 2432: 2a 02 01 69 0a 04 83 6a 03 01 78 02 04 83 11 02 *..i...j..x..... +| 2448: 01 6a 05 04 83 26 02 06 0d 82 58 03 01 70 07 04 .j...&....X..p.. +| 2464: 83 2b 02 01 6b 07 08 83 58 81 12 02 01 6e 0a 02 .+..k...X....n.. +| 2480: 71 02 01 77 0a 04 83 04 02 03 c2 ba 31 06 04 84 q..w........1... +| 2496: 4e 03 01 bd 07 04 83 18 02 02 d3 b5 05 02 68 01 N.............h. +| 2512: 01 39 01 0a 53 81 03 81 24 01 0e 13 82 3d 81 1e .9..S...$....=.. +| 2528: 37 2d 01 0c 6a 15 09 13 82 58 01 08 83 0f 81 3f 7-..j....X.....? +| 2544: 01 10 4f 81 60 81 29 64 15 0c 01 0e 4b 0d 62 0d ..O.`.)d....K.b. +| 2560: 81 00 5e 01 0a 37 81 1b 59 56 01 0e 81 0c 6b 6d ..^..7..YV....km +| 2576: 81 67 1d 01 10 38 2f 63 38 0c 37 20 6b 01 10 7e .g...8/c8.7 k..~ +| 2592: 10 18 81 4a 81 64 23 02 01 32 02 04 83 2c 02 01 ...J.d#..2...,.. +| 2608: 39 04 04 81 10 01 04 82 35 02 04 62 6e c7 9c 0a 9.......5..bn... +| 2624: 04 84 2b 02 02 65 63 06 04 83 0a 03 01 6a 02 04 ..+..ec......j.. +| 2640: 84 4d 02 01 66 01 04 83 4d 02 04 81 0a 02 03 68 .M..f...M......h +| 2656: ce bc 0a 04 83 41 02 01 6c 06 02 54 02 01 6d 08 .....A..l..T..m. +| 2672: 04 81 14 02 02 6f 6c 0a 04 84 0a 02 01 70 02 04 .....ol......p.. +| 2688: 82 2b 05 04 81 49 02 05 71 69 c2 bd 75 0a 02 46 .+...I..qi..u..F +| 2704: 02 02 72 38 0a 04 82 26 02 01 74 04 04 81 0b 04 ..r8...&..t..... +| 2720: 04 84 6e 02 01 77 03 04 81 6a 01 04 82 6b 03 01 ..n..w...j...k.. +| 2736: 65 09 04 82 58 02 01 78 06 02 6e 02 02 c2 b3 08 e...X..x..n..... +| 2752: 06 76 81 10 03 01 b9 04 02 47 03 01 bc 0a 04 83 .v.......G...... +| 2768: 03 02 02 ce bc 02 06 83 3f 19 02 03 d9 be 7a 03 ........?.....z. +| 2784: 04 82 5d 01 01 61 01 1e 17 50 16 12 12 2d 32 81 ..]..a...P...-2. +| 2800: 1e 56 81 06 04 06 1b 01 16 43 5c 52 64 19 15 27 .V.......C.Rd..' +| 2816: 1d 29 0e 52 01 1c 1d 03 81 1c 13 6b 3d 64 14 32 .).R.......k=d.2 +| 2832: 16 29 09 0a 01 22 32 13 07 81 08 03 22 05 17 20 .)....2........ +| 2848: 07 0e 7a 15 71 22 22 01 1c 30 05 06 0b 22 40 11 ..z.q....0....@. +| 2864: 49 1c 0c 15 81 1f 27 01 0c 4a 14 1c 7a 36 66 01 I.....'..J..z6f. +| 2880: 1c 10 19 81 0a 81 1c 81 02 10 54 33 09 0c 07 01 ..........T3.... +| 2896: 1e 38 2e 33 3a 22 1e 2e 35 3a 0f 07 77 15 0b 2f .8.3:...5:..w../ +| 2912: 01 2a 08 5a 35 13 0b 07 05 04 17 42 32 14 04 09 .*.Z5......B2... +| 2928: 1d 0b 09 06 42 72 1c 01 1e 22 0a 15 2b 03 04 6c ....Br......+..l +| 2944: 05 81 3d 14 19 58 21 4a 02 01 32 08 04 83 64 03 ..=..X!J..2...d. +| 2960: 01 67 07 04 82 5c 02 02 34 66 03 04 83 6d 03 01 .g......4f...m.. +| 2976: 73 08 04 81 18 02 05 37 6a 77 c2 b9 0a 04 83 1d s......7jw...... +| 2992: 02 02 38 7a 0a 04 82 3a 02 01 39 05 02 5c 03 02 ..8z...:..9..... +| 3008: c2 b3 06 02 70 02 01 61 01 04 83 03 02 01 66 02 ....p..a......f. +| 3024: 04 81 5b 02 04 82 37 02 01 68 04 04 82 78 01 02 ..[...7..h...x.. +| 3040: 67 02 01 69 04 04 84 0e 06 04 82 41 02 01 6a 01 g..i.......A..j. +| 3056: 04 82 4c 02 02 4e 03 04 81 73 03 01 6c 02 04 81 ..L..N...s..l... +| 3072: 5f 02 01 6b 08 04 81 26 02 05 6d c2 b3 6f 76 06 _..k...&..m..ov. +| 3088: 04 82 7c 02 01 6e 08 02 1c 02 01 6f 02 04 83 24 ..|..n.....o...$ +| 3104: 07 04 83 77 02 01 70 04 04 82 7e 02 01 71 0a 04 ...w..p...~..q.. +| 3120: 81 19 02 06 72 f1 b6 bc b3 68 01 04 83 0e 02 01 ....r....h...... +| 3136: 73 03 04 82 17 01 08 81 70 81 65 03 04 39 c2 ba s.......p.e..9.. +| 3152: 69 06 04 81 65 03 01 76 02 04 81 26 02 01 74 01 i...e..v...&..t. +| 3168: 02 4c 02 01 75 01 04 84 67 02 02 77 69 0a 02 09 .L..u...g..wi... +| 3184: 02 04 78 d2 b9 73 04 04 83 42 02 03 7a c2 b3 08 ..x..s...B..z... +| 3200: 02 43 02 02 c2 aa 01 04 82 61 06 04 84 10 02 02 .C.......a...... +| 3216: c6 88 07 02 1f 02 04 c8 b8 dd bf 05 04 81 72 02 ..............r. +| 3232: 02 c9 ad 07 04 84 2f 02 02 ce bc 07 02 05 02 02 ....../......... +| 3248: cf 85 0a 04 84 36 02 02 d9 a3 02 04 81 72 02 04 .....6.......r.. +| 3264: f1 a9 82 9b 0a 04 82 72 01 01 62 01 1c 81 17 06 .......r..b..... +| 3280: 4a 46 28 2a 0a 4a 10 36 05 52 20 01 1c 1f 15 5f JF(*.J.6.R ...._ +| 3296: 17 12 2b 46 03 81 2b 0f 1b 1f 4f 01 26 6d 68 10 ..+F..+...O.&mh. +| 3312: 04 08 06 20 04 10 05 20 05 07 6d 2e 09 14 05 24 ... ... ..m....$ +| 3328: 01 18 21 2b 11 12 81 0f 3e 22 11 28 51 28 01 18 ..!+....>..(Q(.. +| 3344: 12 11 4e 36 81 5a 23 3b 1a 16 26 2a 01 20 34 79 ..N6.Z#;..&*. 4y +| 3360: 1e 32 14 05 27 17 0c 4a 49 46 09 03 0d 2f 01 0e .2..'..JIF.../.. +| 3376: 15 35 6f 3c 24 5e 2e 01 14 15 40 49 25 2c 0a 1d .5o<$^....@I%,.. +| 3392: 67 70 2e 01 1c 06 03 0b 0e 76 81 0e 15 81 25 0b gp.......v....%. +| 3408: 39 2d 1a 01 14 27 1e 81 01 27 81 54 76 14 3e 02 9-...'...'.Tv.>. +| 3424: 02 31 62 08 04 83 1d 02 01 33 0a 04 83 35 02 01 .1b......3...5.. +| 3440: 37 02 04 82 6b 02 01 39 0a 02 29 02 01 61 03 04 7...k..9..)..a.. +| 3456: 82 72 02 01 64 01 04 81 05 02 04 84 1f 02 01 65 .r..d..........e +| 3472: 01 04 81 69 02 01 66 04 04 82 50 05 04 82 10 02 ...i..f...P..... +| 3488: 01 69 01 02 0d 02 01 6b 0a 04 81 0a 02 01 6c 02 .i.....k......l. +| 3504: 04 81 79 04 04 84 6e 03 01 31 0a 04 84 66 02 01 ..y...n..1...f.. +| 3520: 6f 0a 04 83 48 02 01 73 01 02 3d 03 02 10 02 01 o...H..s..=..... +| 3536: 74 02 04 82 0e 02 01 75 06 04 84 74 03 01 32 01 t......u...t..2. +| 3552: 04 82 0d 02 01 76 06 04 83 3a 02 01 78 08 04 82 .....v...:..x... +| 3568: 73 03 01 6c 09 04 84 1c 03 01 78 02 04 82 08 03 s..l......x..... +| 3584: 02 7a 69 0a 04 83 73 02 01 79 03 04 82 42 02 01 .zi...s..y...B.. +| 3600: 7a 08 04 83 03 02 06 c2 b9 e3 b1 a1 32 0a 04 81 z...........2... +| 3616: 46 03 01 bc 04 04 83 36 03 01 be 04 04 84 3f 02 F......6......?. +| 3632: 02 c6 80 0a 04 84 00 02 03 d4 a3 76 07 04 84 33 ...........v...3 +| 3648: 02 04 da ba 71 6f 08 04 84 11 02 02 dd a8 08 02 ....qo.......... +| 3664: 51 02 06 eb 9e ac 7a 73 79 06 02 32 01 01 63 01 Q.....zsy..2..c. +| 3680: 18 04 39 12 3a 05 34 56 46 59 81 0b 43 01 22 2a ..9.:.4VFY..C..* +| 3696: 1d 1f 53 2b 60 22 12 0d 13 1c 2e 35 05 03 0f 54 ..S+`......5...T +| 3712: 01 14 81 1e 81 1f 17 03 24 2a 81 0b 01 24 34 20 ........$*...$4 +| 3728: 06 21 0a 30 05 08 03 62 0c 26 10 6c 3b 0d 44 3a .!.0...b.&.l;.D: +| 3744: 01 1a 15 0b 77 04 45 3f 34 37 3f 08 13 36 53 01 ....w.E?47?..6S. +| 3760: 1c 02 38 33 49 1e 2e 81 19 43 33 3e 24 15 08 01 ..83I....C3>$... +| 3776: 1a 09 81 55 40 43 09 21 27 4c 04 5b 07 07 07 07 ...U@C.!'L.[.... +| 3792: 07 07 07 07 07 08 08 07 0a 07 0a 06 07 08 07 07 ................ +| 3808: 08 07 0a 08 56 06 0a 07 07 09 06 08 07 07 07 0a ....V........... +| 3824: 06 07 09 09 07 06 07 08 08 08 08 08 5e 07 06 07 ............^... +| 3840: 07 08 09 07 07 08 07 07 08 0b 0b 07 0a 06 07 0a ................ +| 3856: 08 09 09 0a 07 09 08 65 07 07 07 07 07 08 0b 07 .......e........ +| 3872: 06 0b 07 07 06 07 07 07 07 08 09 0c 57 0b 08 07 ............W... +| 3888: 07 0c 08 07 07 08 09 0a 07 07 07 09 07 07 07 0a ................ +| 3904: 07 06 0a 07 08 08 09 5b 07 07 0b 06 09 0a 0a 06 .......[........ +| 3920: 0a 0a 07 07 08 08 06 08 06 0e 5f 07 07 0b 0a 08 .........._..... +| 3936: 06 07 0b 07 07 0f 07 0b 07 07 07 07 06 06 0c 08 ................ +| 3952: 09 08 0c 65 08 0a 06 06 06 08 07 06 07 08 07 08 ...e............ +| 3968: 06 07 09 0b 07 0a 08 08 0a 07 08 0a 0a 58 06 07 .............X.. +| 3984: 07 0a 0b 0b 08 07 07 07 0c 07 09 06 07 09 07 07 ................ +| 4000: 58 07 0b 0a 08 07 0b 09 06 07 08 0b 0a 08 0b 0b X............... +| 4016: 07 06 09 06 07 09 09 81 25 07 07 08 07 0b 08 06 ........%....... +| 4032: 07 07 0b 0a 0b 0e 07 07 0b 06 0b 07 07 0c 0d 0a ................ +| 4048: 07 06 07 07 0a 08 0c 07 0a 08 07 08 08 0a 81 17 ................ +| 4064: 08 07 07 06 07 0b 07 0b 06 07 0b 07 07 09 07 07 ................ +| 4080: 07 07 07 07 07 08 07 07 0c 07 07 08 09 0a 07 0b ................ +| page 21 offset 81920 +| 0: 0d 00 00 00 01 00 21 00 00 21 00 00 00 00 00 00 ......!..!...... +| 32: 00 9f 57 88 80 80 80 80 02 04 00 bf 32 00 08 0e ..W.........2... +| 48: 8c 3a 14 04 29 08 18 17 4a 44 3a 19 4e 5f 56 16 .:..)...JD:.N_V. +| 64: 08 30 25 01 1c 0e 13 32 1b 0b 41 03 2f 81 34 3c .0%....2..A./.4< +| 80: 14 09 21 01 14 81 01 06 10 30 1f 28 0f 51 5f 04 ..!......0.(.Q_. +| 96: 30 63 33 74 05 02 69 02 02 34 76 0a 04 83 63 02 0c3t..i..4v...c. +| 112: 04 36 e7 8e a1 03 04 84 03 02 04 61 77 c2 b9 05 .6.........aw... +| 128: 04 82 56 02 01 63 03 04 82 02 04 04 82 0a 02 02 ..V..c.......... +| 144: 64 30 09 02 40 02 01 67 02 06 61 83 38 02 04 82 d0..@..g..a.8... +| 160: 70 03 04 84 62 03 01 73 02 04 84 5f 02 01 68 01 p...b..s..._..h. +| 176: 04 81 76 03 01 32 07 04 84 3f 02 01 69 09 02 7e ..v..2...?..i..~ +| 192: 02 01 6a 01 04 81 57 02 01 6b 01 04 82 33 02 01 ..j...W..k...3.. +| 208: 6d 05 02 7b 02 01 6e 06 04 82 51 02 01 6f 06 02 m.....n...Q..o.. +| 224: 0e 03 01 65 01 04 81 3c 02 02 70 63 06 02 1e 02 ...e...<..pc.... +| 240: 01 71 02 04 84 3b 08 02 1a 02 01 72 09 06 36 81 .q...;.....r..6. +| 256: 38 02 01 75 02 04 83 3b 03 02 54 02 01 77 04 04 8..u...;..T..w.. +| 272: 83 5c 06 04 83 21 02 01 78 07 04 81 21 02 01 79 .....!..x...!..y +| 288: 06 02 3a 02 02 7a 31 02 04 84 2a 02 02 c2 b2 02 ..:..z1...*..... +| 304: 04 81 01 03 01 b3 07 04 82 5e 03 02 bd 7a 06 04 .........^...z.. +| 320: 82 08 02 02 c6 9e 0a 04 83 4d 02 02 ce bc 05 04 .........M...... +| 336: 84 70 02 02 d3 86 06 04 82 33 02 02 d4 a7 01 02 .p.......3...... +| 352: 7c 02 02 d8 b8 06 04 82 53 02 02 df a4 04 02 7c |.......S......| +| 368: 02 03 e0 b7 ae 04 04 84 5e 02 03 e6 91 9a 03 04 ........^....... +| 384: 81 18 02 04 f0 90 ac 8e 04 04 82 39 01 01 64 01 ...........9..d. +| 400: 16 23 2f 1a 6d 81 47 65 09 1b 42 25 01 10 0d 82 .#/.m.Ge..B%.... +| 416: 18 81 3c 50 06 34 01 10 60 31 5a 14 18 16 5f 18 ....J.Z.!. +| 464: 18 0a 0a 67 3d 81 37 40 68 0e 30 17 18 01 12 08 ...g=.7@h.0..... +| 480: 6a 54 81 0c 49 14 4f 21 01 16 26 82 0d 3c 0c 60 jT..I.O!..&..<.` +| 496: 19 27 51 05 07 01 16 24 20 07 1a 81 1c 1f 24 43 .'Q....$ .....$C +| 512: 81 5b 01 16 42 21 05 81 07 0d 36 5b 21 81 15 02 .[..B!....6[!... +| 528: 01 30 01 04 84 4d 02 01 32 04 04 84 38 02 01 34 .0...M..2...8..4 +| 544: 06 04 83 1e 02 04 84 3c 01 04 81 2c 02 01 62 08 .......<...,..b. +| 560: 04 82 51 03 01 61 05 04 84 3e 02 01 63 06 04 84 ..Q..a...>..c... +| 576: 24 03 04 83 40 02 01 64 01 08 81 18 83 2e 02 01 $...@..d........ +| 592: 65 03 04 82 46 02 01 66 09 04 84 33 02 01 67 03 e...F..f...3..g. +| 608: 04 81 55 02 07 68 c2 b9 62 71 61 30 02 02 48 02 ..U..h..bqa0..H. +| 624: 01 69 04 04 82 4b 03 01 78 03 04 81 3c 02 02 6a .i...K..x...<..j +| 640: 34 05 02 47 02 01 6b 01 04 82 29 03 01 38 0a 02 4..G..k...)..8.. +| 656: 49 02 01 6c 02 04 84 1a 01 04 84 58 07 04 82 2c I..l.......X..., +| 672: 02 01 6d 06 04 83 0b 02 04 84 0c 03 02 c2 bc 08 ..m............. +| 688: 04 83 17 02 01 6e 02 04 83 70 02 01 6f 04 04 81 .....n...p..o... +| 704: 56 03 01 61 03 02 5b 03 04 65 79 64 37 06 04 81 V..a..[..eyd7... +| 720: 38 03 03 ee a9 bf 08 02 45 02 01 71 04 04 81 75 8.......E..q...u +| 736: 03 01 74 09 04 83 66 02 01 72 06 02 4f 02 01 73 ..t...f..r..O..s +| 752: 01 04 81 26 07 02 2d 02 01 74 02 02 14 02 02 3c ...&..-..t.....< +| 768: 04 04 84 5d 02 02 77 64 02 02 31 02 01 78 02 04 ...]..wd..1..x.. +| 784: 84 59 03 04 84 65 02 01 79 02 02 58 04 04 81 49 .Y...e..y..X...I +| 800: 01 04 83 17 03 02 7f 02 02 c2 b3 04 02 05 03 01 ................ +| 816: b9 01 04 84 09 03 01 be 0a 04 82 59 02 02 ce bc ...........Y.... +| 832: 06 04 81 02 02 02 d7 9d 06 04 83 31 02 04 df 84 ...........1.... +| 848: da 8c 08 04 82 67 02 04 e1 b5 b6 6b 0a 04 81 7b .....g.....k.... +| 864: 02 03 e3 a1 92 03 04 83 66 02 04 f0 9b 93 a9 07 ........f....... +| 880: 04 85 0b 04 02 a8 81 06 04 82 3e 01 01 65 01 24 ..........>..e.$ +| 896: 43 34 18 15 08 09 51 19 5b 17 28 26 11 06 23 38 C4....Q.[.(&..#8 +| 912: 2b 2b 01 1e 1c 32 20 17 0f 2c 03 0c 1b 0c 6f 25 ++...2 ..,....o% +| 928: 73 16 40 01 1e 07 12 81 5a 44 19 06 03 24 5c 0e s.@.....ZD...$.. +| 944: 16 18 39 0d 01 26 0d 09 06 41 42 09 24 1d 25 0c ..9..&...AB.$.%. +| 960: 06 1d 63 14 26 18 7d 0a 1d 01 14 25 56 29 0a 81 ..c.&......%V).. +| 976: 1a 1b 37 5d 3d 01 16 1c 81 26 25 7f 41 52 46 08 ..7]=....&%.ARF. +| 992: 1b 16 01 12 81 0f 6b 7b 0f 2d 03 7f 07 01 18 19 ......k..-...... +| 1008: 28 81 01 50 36 41 16 81 22 17 03 01 1c 16 1a 81 (..P6A.......... +| 1024: 1f 10 17 41 4c 1c 0b 66 49 10 06 01 18 54 82 38 ...AL..fI....T.8 +| 1040: 13 2a 0b 14 4c 15 0f 36 0b 02 01 30 07 04 82 28 .*..L..6...0...( +| 1056: 02 01 31 09 04 81 2b 01 04 83 38 02 01 33 03 04 ..1...+...8..3.. +| 1072: 81 76 02 01 61 08 04 84 6b 03 05 7a ea 86 b3 6a .v..a...k..z...j +| 1088: 06 04 82 52 02 01 62 0a 04 83 19 02 01 63 02 04 ...R..b......c.. +| 1104: 82 4d 03 02 c2 be 04 04 82 29 02 01 64 05 04 81 .M.......)..d... +| 1120: 7a 03 04 81 72 02 02 66 61 04 02 41 03 01 77 06 z...r..fa..A..w. +| 1136: 02 3e 02 04 82 10 02 01 67 05 04 84 48 02 01 69 .>......g...H..i +| 1152: 01 04 83 12 02 02 6c 62 07 04 81 1d 02 01 6d 01 ......lb......m. +| 1168: 04 83 6e 02 01 6f 05 02 09 02 04 81 16 02 01 70 ..n..o.........p +| 1184: 05 06 07 82 72 03 01 38 07 04 84 46 02 01 73 02 ....r..8...F..s. +| 1200: 04 84 2d 05 04 81 58 03 04 81 4a 03 02 c2 bd 0a ..-...X...J..... +| 1216: 04 83 2d 02 01 74 02 04 84 55 05 04 85 02 02 01 ..-..t...U...... +| 1232: 75 02 04 82 62 02 01 76 01 04 84 31 02 01 77 09 u...b..v...1..w. +| 1248: 04 82 15 02 01 78 01 02 45 05 04 82 14 03 04 83 .....x..E....... +| 1264: 01 02 01 79 06 04 81 0c 02 02 7a 78 09 04 81 6d ...y......zx...m +| 1280: 02 04 c2 bc 61 72 04 04 83 20 04 01 6f 01 02 70 ....ar... ..o..p +| 1296: 02 02 c9 9e 01 04 82 02 02 02 ca 83 05 04 82 3f ...............? +| 1312: 03 01 9b 05 02 52 03 01 aa 07 04 83 65 02 02 cd .....R......e... +| 1328: bb 01 04 83 07 02 02 ce b2 06 02 5b 02 03 cf a7 ...........[.... +| 1344: 78 03 04 81 3b 02 03 ef a9 89 08 04 82 20 02 04 x...;........ .. +| 1360: f5 87 b0 93 02 04 84 18 01 01 66 01 1c 2b 3d 5b ..........f..+=[ +| 1376: 23 45 2d 41 17 0e 81 1d 0f 15 1f 01 16 05 81 20 #E-A........... +| 1392: 0d 27 81 0f 3e 28 2b 13 01 14 62 2c 44 26 17 04 .'..>(+...b,D&.. +| 1408: 23 50 6d 5b 01 1c 30 5b 81 27 07 29 38 15 4d 11 #Pm[..0[.'.)8.M. +| 1424: 2e 20 17 20 01 1a 19 65 4f 14 2e 55 0e 12 39 13 . . ...eO..U..9. +| 1440: 1d 70 12 01 20 1a 0d 2f 05 81 19 2b 42 05 37 4c .p.. ../...+B.7L +| 1456: 21 22 04 25 2b 01 14 45 3e 05 10 81 47 6c 81 11 !..%+..E>...Gl.. +| 1472: 11 01 1e 78 04 12 18 0a 05 10 14 6b 05 0a 04 82 ...x.......k.... +| 1488: 05 15 01 1e 05 23 4b 18 24 31 20 27 12 2e 81 0e .....#K.$1 '.... +| 1504: 31 18 18 01 10 55 7e 29 2a 06 19 65 38 02 01 30 1....U~)*..e8..0 +| 1520: 09 02 14 02 01 31 02 04 84 00 02 01 36 06 04 84 .....1......6... +| 1536: 7a 02 01 61 07 04 83 71 02 01 62 05 04 83 10 03 z..a...q..b..... +| 1552: 02 34 02 02 63 6f 07 04 83 27 02 01 64 07 04 81 .4..co...'..d... +| 1568: 0c 03 04 81 55 03 01 6f 03 02 7e 02 01 69 02 04 ....U..o..~..i.. +| 1584: 81 08 03 02 05 03 04 82 70 02 01 6a 05 02 41 03 ........p..j..A. +| 1600: 02 6f 30 04 02 5e 02 01 6b 02 04 82 13 02 01 6c .o0..^..k......l +| 1616: 02 04 83 2d 08 04 81 20 02 01 6e 09 04 82 69 02 ...-... ..n...i. +| 1632: 02 6f 38 02 04 83 73 02 01 71 0a 04 81 4f 02 01 .o8...s..q...O.. +| 1648: 74 09 04 83 2c 02 01 75 03 04 83 79 03 03 68 c2 t...,..u...y..h. +| 1664: b3 03 04 81 09 02 02 77 64 06 04 81 79 02 01 78 .......wd...y..x +| 1680: 03 04 83 41 03 02 19 02 01 79 02 04 82 1a 07 02 ...A.....y...... +| 1696: 72 03 01 6a 06 04 83 1a 02 01 7a 05 04 84 6d 01 r..j......z...m. +| 1712: 04 81 24 02 03 c2 aa 75 0a 04 84 72 03 01 b3 04 ..$....u...r.... +| 1728: 04 82 02 03 04 82 2c 03 01 ba 07 04 26 51 03 02 ......,.....&Q.. +| 1744: bd 65 07 04 81 1a 03 01 be 09 02 44 02 02 ca 97 .e.........D.... +| 1760: 02 04 84 30 02 03 d5 a7 6f 04 02 36 03 02 bd 72 ...0....o..6...r +| 1776: 07 04 81 03 02 05 f3 b2 a7 91 68 09 04 82 22 01 ..........h..... +| 1792: 01 67 01 16 81 41 81 1f 4c 0f 0d 0d 6f 3d 04 01 .g...A..L...o=.. +| 1808: 18 12 08 5e 05 16 4f 42 61 22 45 3c 27 01 20 08 ...^..OBa.E<'. . +| 1824: 13 27 14 17 1f 08 3e 0d 41 81 1c 3e 06 1c 1e 01 .'....>.A..>.... +| 1840: 16 0c 11 6b 26 54 05 14 03 81 6b 12 01 1a 36 41 ...k&T....k...6A +| 1856: 11 0b 0c 36 7c 08 0e 0c 52 4f 50 01 0c 81 0e 22 ...6|...ROP..... +| 1872: 3d 72 1f 01 22 07 0e 22 3c 4d 1c 09 03 2d 22 1e =r.........?.....u.. +| 2656: be 6b 05 04 83 0c 02 04 c3 b8 66 70 01 04 82 2f .k........fp.../ +| 2672: 02 02 ce bc 08 04 81 04 02 03 cf a7 6b 06 04 82 ............k... +| 2688: 4d 02 03 d9 84 72 03 02 13 02 02 da 90 06 04 84 M....r.......... +| 2704: 73 02 04 f0 ad 93 a0 06 02 09 01 01 69 01 20 37 s...........i. 7 +| 2720: 4d 55 11 49 26 3c 17 0d 4a 26 03 05 0c 20 43 01 MU.I&<..J&... C. +| 2736: 10 59 08 37 5a 56 72 81 05 01 1a 16 33 32 0a 03 .Y.7ZVr.....32.. +| 2752: 18 81 17 5f 59 27 37 08 01 24 0a 70 1f 07 40 3b ..._Y'7..$.p..@; +| 2768: 15 20 09 15 30 0f 17 06 36 81 08 17 01 1c 32 0a . ..0...6.....2. +| 2784: 05 1a 5f 1c 32 78 03 15 17 81 2b 1e 01 1a 04 03 .._.2x....+..... +| 2800: 2b 07 0c 38 43 6c 0f 81 77 0e 0e 01 1a 1c 3d 81 +..8Cl..w.....=. +| 2816: 0d 0d 17 1d 4c 63 44 2e 11 2b 01 18 2b 2d 49 72 ....LcD..+..+-Ir +| 2832: 1d 22 17 31 19 27 32 4c 01 1c 0b 67 27 20 81 1c ...1.'2L...g' .. +| 2848: 47 31 29 0d 0b 12 4c 07 01 18 35 81 0b 08 0e 5f G1)...L...5...._ +| 2864: 11 18 37 81 09 09 02 01 30 09 02 19 02 02 31 63 ..7.....0.....1c +| 2880: 09 02 7f 02 01 32 04 02 2c 05 04 83 6e 02 01 35 .....2..,...n..5 +| 2896: 09 04 83 7a 03 01 79 05 02 0f 02 02 36 73 05 04 ...z..y.....6s.. +| 2912: 83 3d 02 03 38 6a 70 07 04 84 39 02 01 61 07 04 .=..8jp...9..a.. +| 2928: 83 44 02 01 62 0a 04 84 37 02 01 64 01 04 85 00 .D..b...7..d.... +| 2944: 07 04 82 36 03 02 cf a7 01 04 82 5f 02 01 66 06 ...6......._..f. +| 2960: 04 82 35 03 03 71 70 6e 04 04 82 14 02 02 67 7a ..5..qpn......gz +| 2976: 02 04 82 00 02 01 68 01 02 14 02 01 69 05 02 48 ......h.....i..H +| 2992: 02 01 6a 01 04 83 7a 07 02 12 01 02 33 02 01 6d ..j...z.....3..m +| 3008: 04 04 83 76 03 01 68 08 04 82 63 02 01 6f 03 06 ...v..h...c..o.. +| 3024: 2c 81 52 06 04 81 4f 02 01 70 06 04 83 00 03 02 ,.R...O..p...... +| 3040: c2 be 06 04 83 37 02 01 72 09 04 82 78 03 03 71 .....7..r...x..q +| 3056: 61 6c 0a 02 3e 03 02 c2 be 05 04 84 67 02 01 73 al..>.......g..s +| 3072: 09 04 81 0d 02 01 74 04 02 58 02 01 75 08 04 81 ......t..X..u... +| 3088: 17 02 01 76 04 04 83 4b 06 04 84 70 02 01 7a 02 ...v...K...p..z. +| 3104: 04 83 7e 02 02 c2 aa 03 02 61 03 01 b2 08 04 84 ..~......a...... +| 3120: 13 03 04 b9 c2 b2 6a 01 02 19 03 02 bc 63 05 04 ......j......c.. +| 3136: 84 12 02 02 c7 86 09 04 84 11 02 02 ce bc 02 02 ................ +| 3152: 17 02 03 cf ab 6e 07 02 29 02 06 d5 b1 30 d1 bb .....n..)....0.. +| 3168: 6e 01 02 4e 02 03 eb 8b a7 08 04 81 6b 03 02 b5 n..N........k... +| 3184: bc 07 04 83 08 01 01 6a 01 14 81 07 79 0f 79 32 .......j....y.y2 +| 3200: 33 13 81 16 01 16 2c 0a 81 1b 24 2f 05 81 19 19 3.....,...$/.... +| 3216: 2d 01 16 81 4c 03 14 4f 81 08 2d 05 2f 21 01 18 -...L..O..-./!.. +| 3232: 27 1f 0e 81 16 0c 82 4b 04 12 06 0e 01 1e 26 05 '......K......&. +| 3248: 22 14 55 05 09 81 16 21 05 19 37 74 32 01 1e 37 ..U....!..7t2..7 +| 3264: 07 1e 03 10 81 2c 38 07 1a 2a 2b 81 0d 08 01 1e .....,8..*+..... +| 3280: 2b 19 31 3c 4c 03 1a 14 04 1c 06 76 04 48 62 01 +.1-......p$D..b +| 3760: 12 3b 50 15 34 03 16 01 0c 32 08 08 0a 0a 0b 07 .;P.4....2...... +| 3776: 10 07 07 07 06 07 07 06 07 06 07 07 0a 08 0a 0b ................ +| 3792: 07 06 08 08 07 08 08 08 08 07 08 07 09 09 0a 81 ................ +| 3808: 03 07 07 0f 07 07 0b 09 07 07 07 0c 07 07 07 07 ................ +| 3824: 06 0f 0b 08 07 07 06 0a 08 07 07 06 0a 0d 07 0b ................ +| 3840: 11 07 07 07 08 08 0a 0a 09 0a 08 81 1e 07 0b 07 ................ +| 3856: 07 0b 07 07 08 0b 07 0a 07 07 08 07 0a 08 07 0f ................ +| 3872: 08 0b 07 07 07 0e 07 08 0a 06 08 08 06 07 08 07 ................ +| 3888: 09 09 0a 81 15 06 07 07 07 0a 08 0b 06 0e 06 07 ................ +| 3904: 07 0b 07 08 07 07 07 09 08 0a 0a 07 0b 09 0b 07 ................ +| 3920: 08 06 08 08 08 0b 81 0d 08 08 07 07 07 0b 0b 0b ................ +| 3936: 08 07 07 07 0f 0b 06 07 07 07 08 0c 07 07 08 08 ................ +| 3952: 0e 09 0b 07 06 08 06 07 07 08 09 07 0a 0a 81 1f ................ +| 3968: 07 09 08 06 0b 0a 0d 07 0a 07 08 0a 09 07 08 07 ................ +| 3984: 07 09 06 0d 09 07 0a 08 08 0b 0b 06 08 0a 08 09 ................ +| 4000: 08 08 09 81 1c 06 07 0a 07 06 08 09 07 07 0b 08 ................ +| 4016: 07 09 08 06 06 0d 07 07 0c 07 08 07 08 08 07 06 ................ +| 4032: 07 0b 07 07 07 09 08 08 07 08 0b 09 08 81 16 09 ................ +| 4048: 07 07 07 07 08 07 09 07 07 07 07 07 07 07 0a 07 ................ +| 4064: 07 07 07 09 07 09 08 07 07 07 07 07 07 07 09 09 ................ +| 4080: 0a 07 06 07 0b 09 07 09 07 08 09 09 0c 0b 07 07 ................ +| page 22 offset 86016 +| 0: 0d 00 00 00 01 00 24 00 00 24 00 00 00 00 00 00 ......$..$...... +| 32: 00 00 00 00 9f 54 88 80 80 80 80 03 04 00 bf 2c .....T........., +| 48: 00 0a 0e 8b 61 42 11 82 12 4d 05 12 81 47 48 81 ....aB...M...GH. +| 64: 1c 3a 41 17 25 01 2a 42 21 1d 1f 07 44 65 2c 0f .:A.%.*B!...De,. +| 80: 18 20 18 08 03 19 04 10 19 0b 17 26 01 1a 14 1f . .........&.... +| 96: 11 1f 2b 10 82 08 26 2b 76 0c 27 01 22 4c 23 06 ..+...&+v.'..L#. +| 112: 81 10 2e 2e 03 37 4b 13 14 14 1b 15 24 0d 01 18 .....7K.....$... +| 128: 3a 0e 4b 15 7b 2a 4a 13 24 81 10 0a 01 18 11 16 :.K..*J.$....... +| 144: 28 1f 57 54 15 16 6b 50 2f 04 04 30 6b 30 34 05 (.WT..kP/..0k04. +| 160: 04 83 1c 02 01 31 04 02 39 02 02 22 01 04 82 48 .....1..9......H +| 176: 02 01 32 09 04 81 25 02 01 34 01 04 81 1a 02 01 ..2...%..4...... +| 192: 35 01 02 09 03 01 30 09 04 83 29 02 01 37 05 04 5.....0...)..7.. +| 208: 82 70 02 01 38 06 04 83 2f 02 01 39 03 04 83 29 .p..8.../..9...) +| 224: 02 01 61 09 02 67 02 01 62 02 04 82 57 02 01 63 ..a..g..b...W..c +| 240: 01 04 82 24 02 02 64 33 09 04 84 06 02 01 65 09 ...$..d3......e. +| 256: 04 84 46 02 01 66 03 04 84 15 01 04 84 69 04 04 ..F..f.......i.. +| 272: 84 36 02 01 67 06 04 84 7e 02 08 83 35 81 2c 02 .6..g...~...5.,. +| 288: 01 68 03 04 82 3a 05 08 81 2e 81 20 02 01 69 02 .h...:..... ..i. +| 304: 04 81 69 02 01 6a 06 02 46 01 08 82 1e 82 5b 02 ..i..j..F.....[. +| 320: 04 84 20 02 01 6b 09 04 81 2e 02 01 6c 08 04 84 .. ..k......l... +| 336: 56 03 01 62 0a 04 83 3f 02 02 6d 6a 01 04 82 15 V..b...?..mj.... +| 352: 02 04 6f 69 72 33 01 04 82 25 02 01 72 01 02 44 ..oir3...%..r..D +| 368: 03 01 35 06 04 81 3b 02 02 74 74 04 04 81 79 02 ..5...;..tt...y. +| 384: 01 78 01 04 81 5b 02 02 7a 02 01 79 04 02 19 02 .x...[..z..y.... +| 400: 03 7a 71 6b 03 04 83 37 02 02 c2 b3 01 02 31 04 .zqk...7......1. +| 416: 04 82 04 03 07 ba 6e f5 9a 8b 98 76 02 02 5b 02 ......n....v..[. +| 432: 02 cb 8f 06 04 84 28 02 02 ce bc 01 04 81 66 06 ......(.......f. +| 448: 04 81 1b 02 04 83 47 04 06 ce bc ce bf df bc 09 ......G......... +| 464: 04 82 4f 02 02 dd 9c 03 04 81 62 02 04 f2 86 b3 ..O.......b..... +| 480: bb 01 04 82 08 01 01 6c 01 12 81 34 81 00 0d 2a .......l...4...* +| 496: 81 48 13 01 1a 0e 14 4f 81 17 14 12 10 75 68 36 .H.....O.....uh6 +| 512: 0b 1d 01 16 12 22 23 51 1d 7c 25 08 20 4f 7a 01 ......#Q.|%. Oz. +| 528: 12 08 0b 81 7c 71 81 12 0b 0b 01 0e 4e 50 55 82 ....|q......NPU. +| 544: 1e 2e 23 01 1c 0f 10 24 07 33 06 81 0c 73 42 1d ..#....$.3...sB. +| 560: 81 04 16 01 16 5e 62 6a 06 32 81 03 2f 0f 08 4e .....^bj.2../..N +| 576: 01 06 81 2c 22 01 12 53 81 1e 6e 40 31 0b 1c 41 ...,...S..n@1..A +| 592: 01 18 79 14 11 17 13 81 0f 81 15 19 35 40 02 01 ..y.........5@.. +| 608: 30 01 04 81 0d 03 04 83 6b 02 01 33 07 04 84 66 0.......k..3...f +| 624: 03 04 83 08 02 01 35 08 04 83 43 01 04 83 50 02 ......5...C...P. +| 640: 01 36 09 02 3b 02 01 37 07 04 84 38 02 01 61 0a .6..;..7...8..a. +| 656: 04 83 17 03 04 e1 8d 93 69 01 04 81 0e 02 01 62 ........i......b +| 672: 03 04 84 16 04 04 83 0e 02 01 63 05 04 81 79 05 ..........c...y. +| 688: 04 84 5d 02 01 64 07 04 81 4a 03 02 6d 6e 08 02 ..]..d...J..mn.. +| 704: 09 02 01 65 06 04 84 3e 03 02 76 7a 02 02 76 02 ...e...>..vz..v. +| 720: 01 66 0a 02 47 03 01 79 09 04 81 21 02 02 67 6f .f..G..y...!..go +| 736: 05 02 38 02 02 68 77 03 04 82 27 02 01 69 02 02 ..8..hw...'..i.. +| 752: 6f 04 02 6f 02 01 6b 07 04 82 61 02 01 6c 06 08 o..o..k...a..l.. +| 768: 82 2e 81 5a 02 01 6d 01 04 84 25 03 04 83 31 02 ...Z..m...%...1. +| 784: 06 2f 82 2e 03 01 68 08 04 83 73 02 01 6e 09 04 ./....h...s..n.. +| 800: 84 5a 02 01 6f 05 04 82 39 02 01 70 04 04 82 19 .Z..o...9..p.... +| 816: 05 02 0f 02 01 71 07 04 83 39 03 02 63 71 06 04 .....q...9..cq.. +| 832: 81 5e 03 01 67 07 04 83 73 03 01 70 08 04 84 6a .^..g...s..p...j +| 848: 02 01 73 03 04 83 40 02 01 74 07 04 84 7c 03 06 ..s...@..t...|.. +| 864: 35 69 7a 70 c2 bd 01 04 83 1a 02 01 75 01 08 82 5izp........u... +| 880: 2e 81 13 05 04 82 69 03 01 70 03 04 84 50 02 01 ......i..p...P.. +| 896: 76 01 04 82 52 09 04 82 64 02 02 77 73 03 04 81 v...R...d..ws... +| 912: 27 02 01 78 08 02 1f 01 04 83 64 02 01 79 04 08 '..x......d..y.. +| 928: 81 49 81 44 05 04 84 01 02 03 c2 aa 76 02 04 82 .I.D........v... +| 944: 67 03 01 b9 02 02 4f 06 02 31 03 01 bd 05 04 82 g.....O..1...... +| 960: 3d 02 02 ce bc 01 02 2c 01 01 6d 01 12 0c 47 83 =......,..m...G. +| 976: 0a 07 52 16 41 05 01 18 50 05 74 25 21 81 16 16 ..R.A...P.t%!... +| 992: 05 24 16 6c 01 22 81 05 18 06 12 2a 59 09 1f 09 .$.l.......*Y... +| 1008: 3d 27 39 17 3a 1f 04 01 14 72 81 1a 24 81 38 20 ='9.:....r..$.8 +| 1024: 27 19 2b 01 18 81 2b 24 63 0b 11 5d 2d 0c 41 1a '.+...+$c..]-.A. +| 1040: 30 01 1e 15 04 3c 3a 15 0e 28 18 06 56 81 37 20 0....<:..(..V.7 +| 1056: 0d 6b 01 1e 63 3f 42 73 0a 26 0c 6f 0f 15 13 0b .k..c?Bs.&.o.... +| 1072: 04 25 33 01 16 24 52 16 3a 21 1b 63 26 31 20 34 .%3..$R.:!.c&1 4 +| 1088: 01 0e 1a 60 13 19 3d 82 1f 01 1e 5b 07 26 07 20 ...`..=....[.&. +| 1104: 03 5a 69 6c 27 0b 22 24 20 0f 02 01 31 01 04 82 .Zil'..$ ...1... +| 1120: 6f 02 01 32 03 04 82 41 01 04 81 43 02 01 33 09 o..2...A...C..3. +| 1136: 04 81 23 03 05 7a 68 c2 bd 73 06 04 81 3a 02 01 ..#..zh..s...:.. +| 1152: 35 07 04 83 6c 02 01 38 01 04 83 1c 02 02 62 6f 5...l..8......bo +| 1168: 06 04 83 16 02 01 63 07 02 5b 03 06 6e 31 cf af ......c..[..n1.. +| 1184: 61 71 0a 04 83 33 03 02 c2 be 03 04 83 2b 02 01 aq...3.......+.. +| 1200: 64 09 04 81 19 01 04 83 09 03 01 62 0a 04 82 56 d..........b...V +| 1216: 03 01 6a 03 04 84 4e 02 01 65 05 04 82 3a 02 04 ..j...N..e...:.. +| 1232: 82 53 02 01 66 09 04 82 7a 02 01 67 02 04 82 69 .S..f...z..g...i +| 1248: 08 04 81 35 02 01 68 09 04 83 2f 02 01 69 01 04 ...5..h.../..i.. +| 1264: 81 62 02 01 6a 0a 04 81 59 02 01 6b 09 04 81 5f .b..j...Y..k..._ +| 1280: 02 01 6c 08 04 84 0d 02 01 6d 05 04 84 32 02 01 ..l......m...2.. +| 1296: 6e 01 04 83 05 02 01 6f 07 04 82 6f 02 01 70 04 n......o...o..p. +| 1312: 04 83 60 03 04 82 0d 03 03 c2 b9 70 02 04 81 43 ..`........p...C +| 1328: 02 01 71 07 04 81 13 03 04 82 35 02 01 72 03 04 ..q.......5..r.. +| 1344: 84 51 02 02 73 65 04 04 84 14 02 01 74 01 04 83 .Q..se......t... +| 1360: 5f 02 01 75 03 02 0b 03 02 c2 be 08 04 82 3a 02 _..u..........:. +| 1376: 01 76 06 04 84 43 02 01 77 01 04 82 18 01 04 81 .v...C..w....... +| 1392: 32 02 01 78 08 04 81 47 02 01 7a 01 04 82 44 02 2..x...G..z...D. +| 1408: 02 c2 b3 05 04 83 12 03 04 83 14 04 01 37 04 04 .............7.. +| 1424: 83 6c 03 03 ba dd af 05 04 82 41 02 02 c3 9f 07 .l........A..... +| 1440: 04 82 34 02 03 c6 80 78 0a 04 83 14 02 02 ca 95 ..4....x........ +| 1456: 07 04 83 0c 02 03 ce b0 67 03 02 6e 03 01 bc 05 ........g..n.... +| 1472: 02 17 04 02 6c 02 03 e7 a4 94 05 04 82 51 01 01 ....l........Q.. +| 1488: 6e 01 18 56 26 2f 54 29 5a 25 11 28 24 81 0f 01 n..V&/T)Z%.($... +| 1504: 1c 06 39 06 13 59 09 07 81 39 16 0c 39 27 4a 01 ..9..Y...9..9'J. +| 1520: 14 2a 03 53 23 81 0d 7d 5b 06 03 01 18 1e 03 81 .*.S#...[....... +| 1536: 02 34 37 29 15 34 81 30 36 01 1a 81 25 19 07 21 .47).4.06...%..! +| 1552: 07 33 12 82 02 45 04 05 01 12 82 0f 42 55 1d 08 .3...E......BU.. +| 1568: 1a 44 3d 01 16 18 17 36 54 6e 1b 49 1f 30 09 1c .D=....6Tn.I.0.. +| 1584: 01 16 68 81 00 08 48 3a 13 50 3c 28 27 01 1c 28 ..h...H:.P<('..( +| 1600: 03 06 12 5b 06 30 38 04 65 54 1a 0b 4d 01 20 03 ...[.08.eT..M. . +| 1616: 09 3d 27 04 11 59 11 1a 0b 73 53 81 03 04 26 02 .='..Y...sS...&. +| 1632: 01 32 02 04 83 3e 02 01 36 01 02 73 09 04 81 14 .2...>..6..s.... +| 1648: 02 01 37 0a 04 83 30 02 01 62 08 04 84 4d 02 02 ..7...0..b...M.. +| 1664: 63 7a 0a 04 84 08 02 01 64 07 04 83 61 03 02 7a cz......d...a..z +| 1680: 67 09 02 58 02 02 65 69 09 02 4e 02 05 67 eb 9b g..X..ei..N..g.. +| 1696: a1 72 09 04 81 06 02 01 68 01 04 82 13 02 01 69 .r......h......i +| 1712: 08 04 84 39 01 04 82 45 02 01 6a 0a 04 83 72 02 ...9...E..j...r. +| 1728: 02 6b 70 07 04 81 25 02 01 6c 0a 02 5a 02 01 6d .kp...%..l..Z..m +| 1744: 03 08 82 58 81 11 01 04 84 2c 02 01 6e 07 02 74 ...X.....,..n..t +| 1760: 02 01 6f 05 04 82 7d 02 01 71 0a 02 7c 02 02 72 ..o......q..|..r +| 1776: 75 05 04 83 09 02 01 74 03 04 83 1d 02 01 75 07 u......t......u. +| 1792: 04 84 21 02 01 76 07 04 84 49 02 01 77 03 02 09 ..!..v...I..w... +| 1808: 02 04 81 01 02 01 78 06 04 81 0f 02 01 7a 04 04 ......x......z.. +| 1824: 81 3e 02 04 c2 aa 64 6f 05 04 83 11 03 02 bc 64 .>....do.......d +| 1840: 09 04 81 7b 02 05 c6 80 39 63 64 01 04 83 62 03 ........9cd...b. +| 1856: 01 92 09 04 82 42 02 02 ce bc 09 02 4d 02 02 cf .....B......M... +| 1872: 97 06 04 83 42 02 02 d1 8b 02 02 2d 02 05 dd b1 ....B......-.... +| 1888: ca b2 70 01 02 1b 02 03 e6 b5 96 09 04 81 50 02 ..p...........P. +| 1904: 04 e8 b8 aa 6e 08 04 82 1a 02 05 f0 90 b2 b2 6c ....n..........l +| 1920: 01 02 59 02 04 f1 8f 99 80 0a 04 83 25 01 01 6f ..Y.........%..o +| 1936: 01 26 05 6b 0f 30 12 1f 16 5d 19 08 20 08 33 33 .&.k.0...].. .33 +| 1952: 1e 12 31 31 06 01 16 04 63 33 67 24 2b 81 0a 5d ..11....c3g$+..] +| 1968: 2c 0b 01 14 18 0f 36 82 49 0c 1c 08 27 4b 01 1a ,.....6.I...'K.. +| 1984: 4b 24 3a 13 64 07 14 0b 43 08 68 2e 3c 01 26 1a K$:.d...C.h.<.&. +| 2000: 0b 06 58 10 3d 1b 0a 2f 3e 27 36 1b 1b 21 05 25 ..X.=../>'6..!.% +| 2016: 0d 41 01 12 81 16 4b 2e 7c 7d 32 1f 1e 01 1a 2e .A....K.|.2..... +| 2032: 20 62 72 11 22 05 20 0a 2c 24 60 5e 01 2a 0e 5e br... .,$`^.*.^ +| 2048: 13 22 2d 1c 0c 1f 0c 10 0c 47 05 4e 03 15 06 33 ..-......G.N...3 +| 2064: 1d 06 29 01 10 81 08 81 0b 81 48 53 11 01 20 14 ..).......HS.. . +| 2080: 25 81 17 10 08 08 47 2a 56 2b 2f 20 05 26 22 02 %.....G*V+/ .&.. +| 2096: 01 30 0a 02 5e 03 02 39 76 08 04 82 5c 02 01 31 .0..^..9v......1 +| 2112: 04 02 54 02 01 33 01 04 81 2d 02 02 36 37 09 02 ..T..3...-..67.. +| 2128: 45 03 01 61 04 04 84 16 02 01 37 0a 04 84 5e 02 E..a......7...^. +| 2144: 03 39 c2 b3 01 04 83 2c 02 01 61 08 04 82 4b 02 .9.....,..a...K. +| 2160: 01 62 08 04 83 08 03 02 65 30 07 04 84 0d 02 01 .b......e0...... +| 2176: 64 02 04 82 5e 02 04 81 07 02 05 65 70 6b 7a 35 d...^......epkz5 +| 2192: 02 02 4e 03 03 db 8a 69 01 04 83 0b 02 01 66 05 ..N....i......f. +| 2208: 04 84 34 03 01 68 04 04 84 51 02 01 67 05 02 53 ..4..h...Q..g..S +| 2224: 02 04 82 78 02 01 68 09 04 83 6f 02 01 69 03 04 ...x..h...o..i.. +| 2240: 81 42 02 01 6a 01 04 84 55 03 04 84 10 02 04 81 .B..j...U....... +| 2256: 56 03 02 72 74 06 04 83 20 02 02 6c 7a 01 04 83 V..rt... ..lz... +| 2272: 38 02 01 6e 01 02 15 02 01 6f 01 04 81 7c 01 02 8..n.....o...|.. +| 2288: 27 02 01 73 01 02 36 04 02 2e 03 03 65 ce bc 08 '..s..6.....e... +| 2304: 04 82 43 02 01 74 09 04 82 7f 02 01 75 07 04 82 ..C..t......u... +| 2320: 13 03 01 66 07 04 83 21 03 02 6a 69 07 04 83 7e ...f...!..ji...~ +| 2336: 03 01 6b 01 04 84 26 02 01 76 0a 04 82 7f 03 05 ..k...&..v...... +| 2352: 67 da b2 65 75 0a 04 84 13 03 06 c2 be c9 87 6b g..eu..........k +| 2368: 61 07 04 81 5b 02 02 77 64 01 02 3c 02 01 7a 06 a...[..wd..<..z. +| 2384: 04 81 21 01 04 81 29 02 04 84 4b 03 02 79 30 09 ..!...)...K..y0. +| 2400: 04 83 7d 03 02 ce bc 02 04 81 6a 02 02 c2 aa 05 ..........j..... +| 2416: 02 50 03 01 b2 02 04 81 12 05 02 3e 04 01 63 07 .P.........>..c. +| 2432: 04 84 04 03 03 b3 62 61 01 04 84 3e 03 02 ba 6f ......ba...>...o +| 2448: 02 04 84 51 02 02 c4 91 04 04 84 36 02 05 ce b9 ...Q.......6.... +| 2464: 65 c9 97 07 04 83 76 02 04 ef 82 ac 6d 09 02 5d e.....v.....m..] +| 2480: 02 05 f0 92 bd b3 6f 03 04 81 25 03 04 99 ba 8f ......o...%..... +| 2496: 64 02 04 81 1f 02 05 f3 8a bd b1 6d 09 04 81 28 d..........m...( +| 2512: 01 01 70 01 18 46 7c 1f 14 0a 1b 2e 1c 2d 14 6b ..p..F|......-.k +| 2528: 07 01 18 19 81 05 2e 28 40 11 2b 22 0b 07 18 01 .......(@.+..... +| 2544: 14 40 13 81 36 12 2d 7f 1f 1b 1a 01 12 82 76 1e .@..6.-.......v. +| 2560: 1d 12 81 03 07 12 01 10 5a 81 38 19 36 52 26 18 ........Z.8.6R&. +| 2576: 01 12 64 5d 40 25 05 57 33 31 11 01 1e 36 36 25 ..d]@%.W31...66% +| 2592: 1b 28 07 22 42 13 0f 07 1c 5a 73 51 01 24 07 2d .(..B....ZsQ.$.- +| 2608: 06 03 81 00 22 03 1d 19 18 26 2c 16 23 4f 76 08 .........&,.#Ov. +| 2624: 01 1c 11 1b 54 78 23 1a 81 13 75 04 0c 14 12 0c ....Tx#...u..... +| 2640: 01 12 24 0d 81 4d 11 7f 22 35 2e 02 01 30 02 04 ..$..M...5...0.. +| 2656: 83 5e 02 01 31 0a 04 81 47 03 05 e7 b2 a5 c3 9f .^..1...G....... +| 2672: 04 04 83 16 02 01 35 08 02 35 02 02 36 79 09 04 ......5..5..6y.. +| 2688: 82 24 02 01 37 01 04 81 30 03 02 6d 65 04 04 82 .$..7...0..me... +| 2704: 0e 02 02 38 63 06 02 41 03 02 c9 aa 06 04 84 5b ...8c..A.......[ +| 2720: 02 01 61 03 04 83 0b 03 04 84 4c 03 04 83 5f 03 ..a.......L..._. +| 2736: 01 6c 07 04 83 7f 02 01 63 04 04 84 7b 02 01 64 .l......c......d +| 2752: 04 04 83 5d 02 01 65 06 04 83 4c 03 01 74 08 04 ...]..e...L..t.. +| 2768: 82 0b 02 01 68 03 02 06 03 04 71 e7 8e bb 08 04 ....h.....q..... +| 2784: 81 43 03 01 74 07 04 82 30 02 03 69 ce bc 07 04 .C..t...0..i.... +| 2800: 81 3c 02 03 6a 62 6c 09 02 4b 02 01 6c 09 04 81 .<..jbl..K..l... +| 2816: 60 02 01 6d 05 04 82 6f 04 04 81 62 03 04 6c 74 `..m...o...b..lt +| 2832: c2 b9 07 04 85 08 03 02 c2 be 08 04 83 2b 02 02 .............+.. +| 2848: 6e 73 08 04 81 1f 03 01 75 04 04 83 72 02 01 6f ns......u...r..o +| 2864: 06 08 81 14 83 2f 03 02 ce bc 0a 04 83 67 03 03 ...../.......g.. +| 2880: cf 80 66 07 02 28 02 01 70 01 04 82 2a 03 01 7a ..f..(..p...*..z +| 2896: 08 04 81 0d 02 01 71 07 04 81 37 02 01 73 05 02 ......q...7..s.. +| 2912: 59 03 01 6d 07 02 7a 02 01 74 05 04 82 62 02 02 Y..m..z..t...b.. +| 2928: 75 6a 03 04 81 37 02 02 77 68 08 04 84 14 02 01 uj...7..wh...... +| 2944: 78 01 04 83 6f 02 01 79 01 04 82 66 06 04 81 36 x...o..y...f...6 +| 2960: 02 01 7a 0a 04 81 15 03 05 6f f0 99 8a a6 08 04 ..z......o...... +| 2976: 82 06 02 02 c2 b2 04 04 83 38 03 03 b3 dd 9d 05 .........8...... +| 2992: 04 83 6f 03 01 b9 06 04 83 66 03 01 ba 01 04 81 ..o......f...... +| 3008: 0c 07 04 83 10 04 01 69 06 04 81 39 03 01 bd 04 .......i...9.... +| 3024: 04 82 00 06 02 16 02 02 da 99 02 04 84 49 02 07 .............I.. +| 3040: dd 9b 6e e6 89 a0 75 01 04 81 6e 02 09 f0 92 ab ..n...u...n..... +| 3056: ab ec 8c 8d ca 81 01 04 81 46 01 01 71 01 1a 1a .........F..q... +| 3072: 08 41 65 0b 22 18 69 2f 81 0e 13 05 01 18 08 2d .Ae...i/.......- +| 3088: 07 04 81 70 23 18 20 1a 0c 74 01 16 1f 11 05 6b ...p#. ..t.....k +| 3104: 2f 18 47 13 16 30 4c 01 22 12 05 2d 39 05 07 61 /.G..0L....-9..a +| 3120: 0b 06 0a 56 3f 56 2e 10 0d 50 01 16 1f 1f 3c 2b ...V?V...P....<+ +| 3136: 32 0c 43 4b 81 18 5a 01 18 16 0d 0e 32 40 42 1c 2.CK..Z.....2@B. +| 3152: 4a 24 12 17 13 01 20 03 6e 0e 1c 0f 04 0b 3b 05 J$.... .n.....;. +| 3168: 52 4e 0e 23 26 50 20 01 12 59 09 72 4f 47 44 15 RN.#&P ..Y.rOGD. +| 3184: 41 28 01 16 5e 36 24 0e 19 81 15 07 81 26 16 01 A(..^6$......&.. +| 3200: 18 81 0e 0f 5e 81 01 04 04 1a 1a 44 50 02 02 31 ....^......DP..1 +| 3216: 68 09 04 83 76 02 01 32 07 04 81 70 02 02 33 7a h...v..2...p..3z +| 3232: 05 04 84 4a 02 01 34 08 04 81 02 03 01 69 08 04 ...J..4......i.. +| 3248: 81 0b 03 01 76 02 04 81 62 02 01 37 04 04 81 78 ....v...b..7...x +| 3264: 05 04 82 51 01 02 15 02 03 38 75 77 06 04 82 1b ...Q.....8uw.... +| 3280: 02 01 39 04 02 20 03 04 84 03 03 01 74 03 04 81 ..9.. ......t... +| 3296: 60 02 01 61 04 02 24 01 04 83 63 02 01 62 09 02 `..a..$...c..b.. +| 3312: 15 02 01 63 05 06 51 84 0e 03 03 c2 be 62 04 04 ...c..Q......b.. +| 3328: 82 3d 02 02 64 70 03 04 83 36 02 01 66 06 04 82 .=..dp...6..f... +| 3344: 66 02 02 6a 38 02 04 84 0a 02 02 6d 61 05 04 81 f..j8......ma... +| 3360: 30 03 01 6e 08 04 83 71 02 01 6e 01 02 60 02 01 0..n...q..n..`.. +| 3376: 70 02 04 82 43 02 01 71 05 06 82 48 49 02 01 73 p...C..q...HI..s +| 3392: 06 04 81 1f 02 01 74 09 04 81 70 02 01 75 05 04 ......t...p..u.. +| 3408: 83 44 02 01 76 05 04 84 08 04 04 82 1d 02 01 77 .D..v..........w +| 3424: 09 02 69 02 02 78 69 08 04 82 79 02 01 7a 05 04 ..i..xi...y..z.. +| 3440: 84 22 03 09 66 cf 9f 71 67 36 e2 b1 a5 02 04 81 ....f..qg6...... +| 3456: 21 02 03 c2 aa 6a 01 04 81 12 03 01 b3 03 02 35 !....j.........5 +| 3472: 02 04 84 1f 04 04 84 19 03 04 b9 d4 87 68 02 04 .............h.. +| 3488: 81 42 03 01 ba 06 04 84 44 04 02 64 67 03 04 84 .B......D..dg... +| 3504: 4b 03 01 bc 04 04 81 42 03 01 be 01 02 48 02 02 K......B.....H.. +| 3520: ce bc 0a 04 81 2e 02 02 cf a7 02 04 83 6e 02 04 .............n.. +| 3536: e7 a4 ba 67 07 04 83 59 01 01 72 01 20 41 1d 3a ...g...Y..r. A.: +| 3552: 0b 05 04 12 1f 3c 03 2f 81 05 81 2e 13 01 14 0c .....<./........ +| 3568: 81 3f 17 81 07 63 08 81 07 01 12 81 06 71 17 0b .?...c.......q.. +| 3584: 46 81 5a 13 01 18 02 81 06 0f 49 42 2d 39 2e 2c F.Z.......IB-9., +| 3600: 45 27 01 16 06 19 27 1d 3f 53 81 3a 45 0d 20 01 E'....'.?S.:E. . +| 3616: 14 1b 07 2a 76 37 36 09 82 4d 04 01 14 0c 30 2d ...*v76..M....0- +| 3632: 2d 51 38 31 49 49 74 01 16 29 17 0a 2f 72 1d 4a -Q81IIt..)../r.J +| 3648: 81 27 11 18 01 14 63 81 1c 5f 04 4d 38 19 18 0e .'....c.._.M8... +| 3664: 01 1c 56 2f 25 5f 07 05 03 28 24 5d 2b 4d 42 13 ..V/%_...($]+MB. +| 3680: 02 02 30 71 01 04 84 03 02 01 33 08 04 83 16 01 ..0q......3..... +| 3696: 04 83 3d 02 01 35 03 02 58 02 01 37 04 04 83 0c ..=..5..X..7.... +| 3712: 03 01 64 04 04 84 46 02 02 38 61 0a 04 83 66 03 ..d...F..8a...f. +| 3728: 02 73 69 09 04 82 5f 02 01 39 09 04 84 17 02 02 .si..._..9...... +| 3744: 61 6a 03 04 83 12 02 01 62 07 04 82 72 03 01 65 aj......b...r..e +| 3760: 09 04 83 14 04 01 68 07 04 82 24 6a 09 0d 07 07 ......h...$j.... +| 3776: 06 07 07 07 07 06 07 07 08 07 0f 0d 0d 07 10 07 ................ +| 3792: 07 07 08 0a 06 07 08 0a 06 09 0b 0c 08 10 0c 08 ................ +| 3808: 0a 79 0b 0b 0b 06 07 07 0a 0b 0b 07 07 07 07 06 .y.............. +| 3824: 07 07 08 09 07 09 10 07 07 07 0a 07 08 07 07 07 ................ +| 3840: 07 0c 0d 07 0b 08 0a 0d 09 09 07 07 81 12 07 0b ................ +| 3856: 07 0b 07 07 08 06 0c 08 0b 07 07 0b 07 0b 07 07 ................ +| 3872: 07 07 07 07 07 07 0b 09 0b 07 08 07 06 08 07 0b ................ +| 3888: 07 07 0c 07 09 08 09 08 08 09 09 81 11 07 0a 07 ................ +| 3904: 07 08 07 07 07 0b 07 0b 07 08 06 0d 06 07 06 08 ................ +| 3920: 07 07 07 0a 07 07 0a 08 0b 07 07 08 07 0a 09 0a ................ +| 3936: 0a 0a 81 22 06 08 06 07 07 07 07 09 07 07 08 0b ................ +| 3952: 0a 09 07 07 0a 07 07 0f 08 08 06 0a 09 09 07 07 ................ +| 3968: 07 08 07 07 0b 0c 07 0f 08 08 07 0a 07 09 08 08 ................ +| 3984: 0b 09 0b 0a 0b 81 0b 07 07 0b 06 08 07 08 07 08 ................ +| 4000: 0f 07 07 07 07 07 06 0a 07 09 08 07 0b 0a 08 08 ................ +| 4016: 07 09 08 08 07 07 07 06 06 07 08 08 07 0b 07 0b ................ +| 4032: 08 09 07 0b 07 0a 08 0d 0f 81 13 08 07 08 07 07 ................ +| 4048: 07 0e 09 0a 07 0a 06 08 09 08 07 08 08 07 06 07 ................ +| 4064: 08 07 07 07 0b 06 08 07 0f 09 0e 0a 07 08 07 06 ................ +| 4080: 08 08 0a 81 08 08 0b 06 07 07 08 08 07 08 07 07 ................ +| page 23 offset 90112 +| 0: 0d 00 00 00 01 00 22 00 00 22 00 00 00 00 00 00 ................ +| 32: 00 00 9f 56 88 80 80 80 80 04 04 00 bf 30 00 00 ...V.........0.. +| 48: 0e 8c 03 30 72 64 04 04 81 24 02 01 66 06 04 81 ...0rd...$..f... +| 64: 05 02 04 81 0f 03 01 70 0a 04 82 45 02 01 67 01 .......p...E..g. +| 80: 04 84 1a 02 04 81 03 07 04 82 13 02 01 68 05 04 .............h.. +| 96: 83 1d 02 01 6a 02 04 83 03 02 06 82 26 39 03 01 ....j.......&9.. +| 112: 67 01 04 81 0a 02 02 6c 70 09 02 48 03 03 c9 be g......lp..H.... +| 128: 6e 05 04 83 58 02 01 6e 07 04 83 10 03 02 30 6d n...X..n......0m +| 144: 07 04 84 20 02 03 6f d2 b1 09 04 84 43 02 01 70 ... ..o.....C..p +| 160: 07 02 24 01 02 7d 02 01 71 07 04 83 2f 03 04 81 ..$.....q.../... +| 176: 18 03 01 62 08 04 82 39 02 02 72 75 0a 04 81 24 ...b...9..ru...$ +| 192: 02 01 75 02 04 81 4e 05 04 83 3d 02 01 76 03 02 ..u...N...=..v.. +| 208: 64 03 02 6b 6c 08 04 82 2f 02 01 78 05 04 84 66 d..kl.../..x...f +| 224: 02 02 79 36 02 02 39 02 02 c2 b2 04 04 84 7c 04 ..y6..9.......|. +| 240: 02 dd ab 09 04 83 71 03 02 b3 31 09 04 82 33 03 ......q...1...3. +| 256: 01 bc 04 02 6e 03 01 bd 07 04 85 09 04 01 75 07 ....n.........u. +| 272: 02 0e 02 02 ce bc 07 02 6b 02 02 d1 9e 0a 04 84 ........k....... +| 288: 51 02 03 d5 b1 36 09 02 1e 02 04 e4 8d ac 38 01 Q....6........8. +| 304: 02 61 02 04 f0 96 85 81 09 04 83 5e 03 03 9a 87 .a.........^.... +| 320: 8d 05 04 83 51 01 01 73 01 30 0f 1c 12 09 29 0d ....Q..s.0....). +| 336: 07 2e 81 01 11 2a 22 1d 04 2e 0f 08 04 23 29 0b .....*.......#). +| 352: 3b 15 01 16 2f 2f 81 20 81 07 13 56 26 3b 25 01 ;...//. ...V&;%. +| 368: 28 49 03 0a 61 04 08 2e 03 22 10 38 34 0c 09 29 (I..a......84..) +| 384: 0c 0c 34 28 0b 01 1e 0f 3c 5b 13 0a 34 11 5a 73 ..4(....<[..4.Zs +| 400: 1e 22 03 1a 54 03 01 22 08 06 3c 41 06 2c 2b 16 ....T......^btG7 +| 1328: 20 3d 07 20 0e 01 1e 10 69 42 48 08 04 08 56 08 =. ....iBH...V. +| 1344: 66 21 10 07 1a 34 01 18 1f 28 31 41 81 06 36 19 f!...4...(1A..6. +| 1360: 53 0d 0f 0c 01 1c 16 0a 0e 04 52 17 22 48 7b 43 S.........R..H.C +| 1376: 3e 04 47 44 01 1e 25 4b 0b 30 2c 0a 0c 6b 71 15 >.GD..%K.0,..kq. +| 1392: 03 4c 19 0f 16 01 22 2b 13 28 1d 24 4c 0f 46 0f .L.....+.(.$L.F. +| 1408: 2f 0b 1c 03 0e 4a 4a 33 01 10 19 29 81 24 0a 82 /....JJ3...).$.. +| 1424: 0c 46 02 01 30 05 04 82 5d 02 01 32 07 04 83 1f .F..0...]..2.... +| 1440: 02 02 33 6c 06 04 84 6f 02 01 35 08 02 33 02 06 ..3l...o..5..3.. +| 1456: 37 6e 66 ef 98 bd 06 04 81 35 02 02 61 76 03 06 7nf......5..av.. +| 1472: 82 12 73 02 01 63 02 02 57 02 04 83 09 03 01 69 ..s..c..W......i +| 1488: 05 02 66 02 01 64 06 04 81 01 02 01 65 06 02 3b ..f..d......e..; +| 1504: 03 01 71 01 04 83 6c 02 01 66 01 04 84 7c 01 04 ..q...l..f...|.. +| 1520: 83 5c 03 03 69 c2 b2 08 04 84 74 03 03 73 c2 bd ....i.....t..s.. +| 1536: 03 04 84 0a 02 01 67 06 08 82 1d 82 56 04 04 81 ......g.....V... +| 1552: 33 02 01 68 09 02 51 02 01 69 0a 02 08 02 01 6a 3..h..Q..i.....j +| 1568: 06 02 6c 03 01 6e 0a 04 81 2c 02 01 6c 08 04 83 ..l..n...,..l... +| 1584: 15 02 01 6d 04 04 84 24 03 01 33 01 04 81 3a 03 ...m...$..3...:. +| 1600: 03 6e c2 b9 07 04 81 6b 02 01 6f 06 04 83 28 02 .n.....k..o...(. +| 1616: 03 70 77 71 0a 04 81 2a 02 01 71 0a 04 84 34 02 .pwq...*..q...4. +| 1632: 01 73 05 04 81 7f 03 02 6a 73 01 04 83 08 02 01 .s......js...... +| 1648: 74 02 04 81 60 01 04 83 5e 06 04 82 37 02 01 76 t...`...^...7..v +| 1664: 08 04 81 2d 02 01 77 01 04 82 12 01 02 46 02 03 ...-..w......F.. +| 1680: 78 73 66 04 02 37 03 02 7a 62 02 04 83 1a 02 01 xsf..7..zb...... +| 1696: 79 04 02 1a 03 04 84 2a 02 04 84 22 02 01 7a 05 y......*......z. +| 1712: 04 83 4d 02 02 c2 b2 09 04 83 04 03 01 b9 04 04 ..M............. +| 1728: 82 3e 03 02 bd 6c 07 04 84 06 03 02 be 73 0a 04 .>...l.......s.. +| 1744: 83 5b 02 06 c9 82 71 c6 9a 61 05 02 7a 03 04 89 .[....q..a..z... +| 1760: e3 a1 be 04 04 83 05 02 04 ca aa 33 67 04 04 84 ...........3g... +| 1776: 61 02 03 d6 83 6b 08 04 81 77 02 02 da b0 0a 02 a....k...w...... +| 1792: 32 03 01 b6 0a 02 13 02 03 e3 9a bb 04 04 84 42 2..............B +| 1808: 02 04 f0 b6 8e b7 06 04 83 5a 01 01 76 01 1a 7f .........Z..v... +| 1824: 38 16 7c 3b 26 23 08 05 47 1c 19 20 01 1c 28 45 8.|;&#..G.. ..(E +| 1840: 20 0a 81 0a 45 15 44 05 03 1d 26 44 01 10 0c 3e ...E.D...&D...> +| 1856: 26 3c 6d 82 43 04 01 1c 04 15 3a 56 62 39 16 03 &..xqg2.. +| 2192: 84 72 03 02 76 76 01 04 82 1f 02 03 79 75 6f 04 .r..vv......yuo. +| 2208: 04 84 77 03 04 f6 84 93 a8 05 04 83 6c 02 01 7a ..w.........l..z +| 2224: 02 02 7c 02 04 83 21 05 04 83 07 02 02 c2 aa 01 ..|...!......... +| 2240: 04 81 2f 03 01 bc 0a 04 81 65 02 02 c3 be 01 04 ../......e...... +| 2256: 82 04 02 02 df 94 07 04 82 5f 02 04 ea 80 84 6e ........._.....n +| 2272: 04 04 84 73 03 03 84 b1 75 06 04 84 56 02 03 ef ...s....u...V... +| 2288: 89 9e 07 04 83 7b 02 04 f0 92 ab 9a 0a 04 84 60 ...............` +| 2304: 01 01 77 01 18 49 1d 46 47 07 65 21 05 81 13 1a ..w..I.FG.e!.... +| 2320: 06 01 16 81 7e 5a 26 1b 12 05 0f 44 03 57 01 16 ....~Z&....D.W.. +| 2336: 14 4d 31 81 0d 2c 2a 07 03 81 2c 01 14 23 0a 2a .M1..,*...,..#.* +| 2352: 66 18 65 76 21 5a 5f 01 14 14 81 4c 11 10 24 81 f.ev!Z_....L..$. +| 2368: 49 48 28 01 16 7b 0a 4c 08 10 22 81 2d 03 69 38 IH(....L....-.i8 +| 2384: 01 0e 3c 6e 1f 81 6a 2a 16 01 1a 3b 81 25 0d 28 ....d......i +| 2592: 06 04 83 60 03 01 35 0a 04 82 3d 02 01 6b 09 04 ...`..5...=..k.. +| 2608: 83 34 03 01 70 05 04 81 6e 02 01 6c 09 04 83 28 .4..p...n..l...( +| 2624: 02 01 6d 04 04 82 6f 04 04 83 09 01 04 84 48 02 ..m...o.......H. +| 2640: 04 6e 79 68 31 03 04 83 4d 02 01 6f 03 02 2d 03 .nyh1...M..o..-. +| 2656: 01 6e 09 02 61 02 01 70 04 04 82 67 02 03 71 63 .n..a..p...g..qc +| 2672: 79 09 04 81 0b 03 02 c2 bc 0a 02 30 02 01 72 08 y..........0..r. +| 2688: 02 5e 02 01 73 08 04 81 07 02 01 75 05 02 6c 05 .^..s......u..l. +| 2704: 04 84 56 02 01 76 08 04 81 01 02 01 78 05 04 84 ..V..v......x... +| 2720: 61 02 04 84 6c 02 01 7a 0a 04 83 2e 02 02 c2 ba a...l..z........ +| 2736: 04 04 81 39 03 01 bc 01 04 83 23 02 02 c9 a8 03 ...9......#..... +| 2752: 04 83 38 02 04 ce bc 62 68 02 04 82 25 02 02 d4 ..8....bh...%... +| 2768: 85 08 04 82 2d 02 02 db 91 09 04 84 29 01 01 78 ....-.......)..x +| 2784: 01 0a 27 82 4d 5c 23 01 1c 11 62 38 15 17 67 05 ..'.M.#...b8..g. +| 2800: 3e 05 33 1b 29 1c 26 01 16 0d 81 33 0c 0c 09 61 >.3.).&....3...a +| 2816: 4e 1a 54 47 01 18 22 43 09 60 11 2a 08 1b 81 30 N.TG...C.`.*...0 +| 2832: 27 44 01 24 2d 06 1c 19 2a 5e 22 24 04 2a 5c 0b 'D.$-...*^.$.*.. +| 2848: 42 0d 0f 37 06 2d 01 1a 4c 28 2d 47 14 46 18 45 B..7.-..L(-G.F.E +| 2864: 0e 2c 04 81 1a 01 14 1d 33 75 70 62 32 75 26 2d .,......3upb2u&- +| 2880: 08 01 18 41 0b 0a 0c 1f 17 27 61 81 37 3b 21 01 ...A.....'a.7;!. +| 2896: 1e 39 12 0e 1b 17 17 08 4c 04 81 18 0a 81 12 1a .9......L....... +| 2912: 01 22 0d 11 30 32 1f 5d 31 08 1e 2e 23 3b 03 49 ....02.]1...#;.I +| 2928: 05 6d 03 02 01 30 01 04 81 47 04 02 22 03 02 65 .m...0...G.....e +| 2944: 77 0a 04 82 16 02 01 32 04 08 81 55 81 2e 01 06 w......2...U.... +| 2960: 57 3a 73 02 01 36 0a 02 4f 03 01 7a 04 04 81 27 W:s..6..O..z...' +| 2976: 02 01 61 05 04 82 21 02 01 62 01 02 7e 02 01 63 ..a...!..b..~..c +| 2992: 05 04 81 0c 03 02 c2 b3 08 04 81 24 02 01 65 05 ...........$..e. +| 3008: 04 84 5e 01 04 81 04 01 04 82 3e 01 04 83 46 02 ..^.......>...F. +| 3024: 01 67 02 04 84 09 02 01 68 01 04 83 04 02 01 69 .g......h......i +| 3040: 04 04 82 31 04 04 82 5a 02 01 6a 01 04 81 21 02 ...1...Z..j...!. +| 3056: 01 6b 08 04 83 5e 03 02 68 7a 0a 04 84 67 03 04 .k...^..hz...g.. +| 3072: 77 78 64 6f 04 04 81 4c 02 01 6d 04 04 83 03 03 wxdo...L..m..... +| 3088: 01 37 01 04 83 75 03 01 67 07 04 82 04 02 01 6e .7...u..g......n +| 3104: 06 02 2c 04 04 83 52 02 01 6f 05 04 81 14 02 03 ..,...R..o...... +| 3120: 70 6d 73 08 04 83 68 02 01 71 06 04 84 7c 02 01 pms...h..q...|.. +| 3136: 72 02 04 84 20 03 04 83 33 01 04 82 13 02 04 82 r... ...3....... +| 3152: 17 03 01 69 04 04 81 1c 03 01 76 04 04 84 71 02 ...i......v...q. +| 3168: 01 73 04 04 82 53 02 01 75 01 04 81 3b 05 04 82 .s...S..u...;... +| 3184: 2a 02 01 77 03 04 82 4f 02 01 79 02 02 60 02 01 *..w...O..y..`.. +| 3200: 7a 08 04 81 34 02 04 c2 aa 6b 61 04 04 82 7b 02 z...4....ka..... +| 3216: 02 c6 b4 0a 04 81 02 02 02 ca 83 03 02 5a 02 02 .............Z.. +| 3232: d4 ae 04 04 81 45 02 02 d7 bb 0a 04 83 78 02 02 .....E.......x.. +| 3248: dc 96 06 04 82 1f 02 02 df 95 05 04 82 11 02 03 ................ +| 3264: ea b7 a6 07 04 83 6e 02 04 f0 93 a0 b9 06 04 82 ......n......... +| 3280: 6c 02 05 f5 af 82 82 73 07 02 41 01 01 79 01 1e l......s..A..y.. +| 3296: 07 21 30 08 81 04 17 25 0a 0c 81 17 27 81 25 01 .!0....%....'.%. +| 3312: 1a 03 5d 21 81 16 07 3f 27 16 3f 14 08 18 01 12 ..]!...?'.?..... +| 3328: 68 4f 0e 25 34 3a 3d 81 16 01 12 09 81 0a 3c 72 hO.%4:=...............n..t... +| 3648: 17 02 03 c5 a7 33 0a 04 84 55 02 02 c7 9d 04 04 .....3...U...... +| 3664: 81 60 02 08 ca 81 30 68 f0 90 90 b2 09 04 84 2e .`....0h........ +| 3680: 03 01 b5 02 04 82 33 02 03 d1 9e 6c 02 04 81 1a ......3....l.... +| 3696: 02 02 d4 93 04 04 81 05 02 02 d8 ab 03 04 82 49 ...............I +| 3712: 03 02 b8 73 01 04 84 1d 02 03 e2 af 9d 07 02 4d ...s...........M +| 3728: 02 03 ec 8f ad 03 04 84 47 01 01 7a 01 22 1d 42 ........G..z...B +| 3744: 10 5f 1b 08 63 1e 07 0a 30 07 0b 26 31 61 07 01 ._..c...0..&1a.. +| 3760: 1c 0a 37 29 0a 68 40 38 0a 4a 04 08 0b 07 0f 07 ..7).h@8.J...... +| 3776: 0c 07 07 09 07 08 09 09 0b 07 08 0b 06 08 07 07 ................ +| 3792: 08 08 08 06 07 06 07 08 08 09 0a 09 81 31 06 07 .............1.. +| 3808: 0d 06 06 07 0a 07 08 0e 08 07 07 0b 06 07 09 07 ................ +| 3824: 0b 07 07 07 0c 07 07 07 07 08 09 08 07 0a 81 14 ................ +| 3840: 07 07 06 0b 08 06 07 09 07 07 0f 0e 06 0b 0b 0a ................ +| 3856: 0c 07 06 06 09 06 08 0b 09 07 08 0f 0b 07 0c 08 ................ +| 3872: 06 07 07 09 08 09 07 09 0a 81 20 07 07 08 06 0c .......... ..... +| 3888: 09 0a 06 07 06 07 0b 09 09 0d 06 06 06 07 07 07 ................ +| 3904: 07 09 07 09 07 07 08 0f 07 0a 08 08 0e 07 08 07 ................ +| 3920: 08 08 0b 0a 0a 09 07 06 09 0a 81 10 07 06 09 0a ................ +| 3936: 07 08 07 07 08 07 0d 0a 07 07 0a 07 08 07 13 0a ................ +| 3952: 08 08 08 08 07 09 0a 08 09 0a 0e 08 07 08 08 0a ................ +| 3968: 09 09 0a 81 08 07 08 08 0b 08 07 07 08 0b 07 0b ................ +| 3984: 0b 0a 07 07 07 07 07 07 07 07 07 07 0f 0a 06 06 ................ +| 4000: 07 09 07 06 07 0a 07 0b 07 08 07 08 0a 08 08 81 ................ +| 4016: 16 0a 08 0e 06 07 07 06 07 08 13 07 07 0b 07 07 ................ +| 4032: 08 0a 07 07 07 0a 07 09 07 13 07 07 07 0b 07 06 ................ +| 4048: 07 0a 08 07 08 08 08 08 09 0a 0a 81 06 07 0e 07 ................ +| 4064: 0b 06 0b 0b 0b 07 08 07 0f 07 06 08 07 09 0b 0a ................ +| 4080: 06 0e 07 08 09 07 09 08 0e 07 09 08 08 08 08 09 ................ +| page 24 offset 94208 +| 0: 0d 00 00 00 01 00 28 00 00 28 00 00 00 00 00 00 ......(..(...... +| 32: 00 00 00 00 00 00 00 00 9f 50 88 80 80 80 80 05 .........P...... +| 48: 04 00 bf 24 00 09 0e 5b 14 06 1c 0d 12 03 18 37 ...$...[.......7 +| 64: 1f 05 55 32 10 15 5a 81 07 4b 04 01 16 07 0e 5f ..U2..Z..K....._ +| 80: 0b 46 0b 22 81 41 24 23 01 28 2a 07 11 09 15 0b .F...A$#.(*..... +| 96: 0f 0c 09 0a 1e 05 1f 5e 07 19 1c 1e 33 3e 01 18 .......^....3>.. +| 112: 26 0c 1b 43 08 38 11 05 81 1b 5f 20 01 1e 06 1c &..C.8...._ .... +| 128: 36 27 33 20 53 4f 06 03 1a 2f 2c 40 6d 01 18 72 6'3 SO.../,@m..r +| 144: 81 0d 20 3a 23 05 34 19 23 2b 36 01 12 2c 4d 0c .. :#.4.#+6..,M. +| 160: 59 82 04 46 40 0f 01 1a 28 15 32 33 2e 52 0f 49 Y..F@...(.23.R.I +| 176: 04 09 81 33 26 03 30 7a 34 01 04 82 05 02 01 36 ...3&.0z4......6 +| 192: 06 04 81 5a 02 04 37 ce bc 74 09 04 81 17 02 01 ...Z..7..t...... +| 208: 38 01 04 83 79 03 03 dd 9a 73 02 04 84 12 02 01 8...y....s...... +| 224: 61 04 04 82 1c 02 01 62 01 04 83 4e 07 04 82 5b a......b...N...[ +| 240: 02 01 63 0a 02 4e 02 01 64 04 04 84 7d 02 01 65 ..c..N..d......e +| 256: 01 04 84 2a 02 01 67 01 04 81 75 02 04 68 d0 bf ...*..g...u..h.. +| 272: 70 09 04 81 4a 02 01 69 01 04 84 34 05 04 82 48 p...J..i...4...H +| 288: 02 01 6a 02 02 1d 04 04 81 54 02 01 6b 03 04 81 ..j......T..k... +| 304: 0d 02 01 6c 09 04 83 54 02 01 6e 06 02 0b 01 04 ...l...T..n..... +| 320: 84 1c 02 01 6f 0a 02 26 03 01 6a 08 04 82 26 03 ....o..&..j...&. +| 336: 05 73 75 c2 bd 61 03 02 38 02 02 70 37 06 04 81 .su..a..8..p7... +| 352: 7e 02 01 71 01 04 83 67 03 01 65 06 04 84 08 02 ~..q...g..e..... +| 368: 01 72 0a 04 82 1e 02 01 74 01 04 82 6c 02 02 5d .r......t...l..] +| 384: 04 04 84 17 03 04 82 77 02 01 75 04 08 82 22 82 .......w..u..... +| 400: 0b 02 02 76 6c 03 04 83 25 02 01 77 03 04 81 24 ...vl...%..w...$ +| 416: 03 04 82 42 02 01 78 06 04 82 4a 03 02 c2 b2 02 ...B..x...J..... +| 432: 04 83 21 02 01 79 09 04 83 43 02 01 7a 04 02 51 ..!..y...C..z..Q +| 448: 02 02 c2 aa 02 02 51 03 01 b3 09 04 81 2a 03 01 ......Q......*.. +| 464: b9 06 04 82 73 03 02 bd 73 04 02 4e 04 05 76 c2 ....s...s..N..v. +| 480: be 67 65 08 02 1d 04 03 78 de bc 02 04 83 6d 04 .ge.....x.....m. +| 496: 01 7a 09 04 84 1d 02 04 ca a2 36 32 07 04 83 2e .z........62.... +| 512: 02 02 d8 aa 04 04 84 31 02 02 df 8a 02 02 73 01 .......1......s. +| 528: 02 c2 aa 01 08 81 7f 82 01 01 06 56 83 70 02 06 ...........V.p.. +| 544: 74 82 6d 01 06 81 5d 45 01 04 82 7b 01 0c 81 46 t.m...]E.......F +| 560: 13 6a 82 12 01 0c 82 24 63 15 35 74 01 12 81 14 .j.....$c.5t.... +| 576: 81 24 1f 3d 7c 15 1c 01 0a 82 39 1a 81 6f 03 02 .$.=|.....9..o.. +| 592: 30 65 07 04 83 3a 03 01 63 03 04 81 7d 03 01 65 0e...:..c......e +| 608: 09 02 03 03 01 66 01 02 6c 04 02 66 7a 03 04 82 .....f..l..fz... +| 624: 1c 03 01 6b 0a 04 82 46 03 01 7a 08 04 84 2d 03 ...k...F..z...-. +| 640: 03 c2 bd 73 0a 04 81 10 05 02 c2 bc 03 02 03 04 ...s............ +| 656: 01 be 02 04 84 35 03 04 f0 9f ad ae 08 04 83 41 .....5.........A +| 672: 02 01 b2 01 0a 3e 77 21 82 5f 01 08 81 74 82 68 .....>w!._...t.h +| 688: 01 0e 39 81 0c 81 4b 1c 4e 01 0e 83 46 06 3b 1d ..9...K.N...F.;. +| 704: 0e 38 01 0e 81 50 15 81 53 35 57 01 08 82 7d 26 .8...P..S5W....& +| 720: 1c 01 04 2f 0e 01 08 66 5d 83 24 01 0e 43 81 10 .../...f].$..C.. +| 736: 24 82 77 04 01 0a 82 0a 81 61 1f 03 01 35 08 04 $.w......a...5.. +| 752: 81 11 03 01 61 09 04 82 0b 03 01 64 07 04 81 7e ....a......d...~ +| 768: 03 01 66 05 04 82 29 03 01 67 03 04 84 17 03 04 ..f...)..g...... +| 784: 69 34 d8 a1 05 04 82 42 03 01 73 04 04 84 3d 03 i4.....B..s...=. +| 800: 01 76 06 04 81 70 04 03 76 c2 ba 07 04 81 34 03 .v...p..v.....4. +| 816: 01 78 06 04 83 48 03 03 c2 b3 36 07 02 7f 03 02 .x...H....6..... +| 832: d4 85 04 04 81 2d 03 02 d9 bc 05 04 83 20 03 04 .....-....... .. +| 848: ee b5 9c 70 03 04 83 57 03 05 f6 80 80 bb 70 05 ...p...W......p. +| 864: 04 81 6a 02 01 b3 01 06 82 41 12 01 08 67 0d 82 ..j......A...g.. +| 880: 69 01 0e 81 51 22 5c 7d 81 00 01 10 68 05 2f 81 i...Q.......h./. +| 896: 4e 81 78 03 01 0a 74 81 58 81 72 01 0c 4e 72 07 N.x...t.X.r..Nr. +| 912: 79 81 7d 01 10 44 3b 58 66 82 08 0c 2f 01 06 0c y....D;Xf.../... +| 928: 65 36 01 04 81 02 01 08 5d 43 82 10 03 01 61 06 e6......]C....a. +| 944: 04 83 3c 03 01 63 07 04 82 32 03 01 68 03 04 82 ..<..c...2..h... +| 960: 76 03 03 6a 77 6d 0a 04 84 35 03 01 70 01 04 84 v..jwm...5..p... +| 976: 70 03 01 74 01 06 84 05 0a 06 02 47 04 04 6a 75 p..t.......G..ju +| 992: ce bc 07 04 82 4d 03 02 75 65 07 04 81 05 03 01 .....M..ue...... +| 1008: 77 01 04 81 59 04 03 6f db 85 08 02 3d 03 04 c2 w...Y..o....=... +| 1024: be c2 b9 01 04 81 04 03 07 c4 a7 67 d5 bb 7a 68 ...........g..zh +| 1040: 06 04 84 05 03 02 c7 a5 02 04 81 10 02 01 b9 01 ................ +| 1056: 0c 1e 81 43 0b 81 56 01 08 82 70 81 27 01 06 34 ...C..V...p.'..4 +| 1072: 83 38 01 0c 81 0f 1d 04 81 74 01 06 82 58 65 01 .8.......t...Xe. +| 1088: 10 27 55 7a 4b 81 00 2c 29 01 0e 04 1f 24 09 83 .'UzK..,)....$.. +| 1104: 44 04 01 08 65 2f 41 2b 01 0c 34 0c 81 17 83 0f D...e/A+..4..... +| 1120: 01 04 82 69 03 02 61 73 02 02 4d 03 01 65 01 04 ...i..as..M..e.. +| 1136: 81 2e 03 02 69 61 04 04 83 06 04 03 69 69 67 08 ....ia......iig. +| 1152: 02 1b 03 03 6b c2 ba 06 04 82 49 03 01 6d 04 04 ....k.....I..m.. +| 1168: 84 67 03 01 6f 02 04 82 4b 03 01 70 06 04 84 34 .g..o...K..p...4 +| 1184: 03 04 72 61 76 76 04 02 67 03 03 73 c2 be 07 04 ..ravv..g..s.... +| 1200: 82 17 03 01 76 03 04 81 56 03 01 78 02 04 84 24 ....v...V..x...$ +| 1216: 03 03 79 69 38 05 04 84 68 03 05 c2 aa 63 c2 b2 ..yi8...h....c.. +| 1232: 08 02 69 04 01 b9 06 04 82 75 04 01 ba 01 04 81 ..i......u...... +| 1248: 51 04 01 bc 0a 04 81 0f 03 02 c5 a7 0a 04 84 71 Q..............q +| 1264: 03 05 c9 99 ee 84 af 03 04 84 10 04 06 9f db a5 ................ +| 1280: 30 71 68 07 04 82 46 03 05 ca 8b 64 78 66 0a 04 0qh...F....dxf.. +| 1296: 84 01 03 02 dd 9b 0a 04 82 4e 03 02 de bd 08 04 .........N...... +| 1312: 82 03 03 07 eb b1 bd f2 84 82 8d 04 04 84 23 02 ..............#. +| 1328: 01 ba 01 0c 2d 81 55 09 82 17 01 04 07 64 01 0e ....-.U......d.. +| 1344: 33 81 73 0e 36 81 63 01 0e 2f 60 82 09 5b 5f 2c 3.s.6.c../`..[_, +| 1360: 01 08 82 6d 81 69 01 0c 81 2d 3c 35 81 0b 01 10 ...m.i...-<5.... +| 1376: 4f 81 08 03 82 4f 29 27 01 0c 0f 03 55 82 6d 10 O....O)'....U.m. +| 1392: 01 08 0d 82 16 1e 01 06 2c 82 56 03 01 33 05 04 ........,.V..3.. +| 1408: 82 25 03 01 36 07 04 82 73 03 01 63 0a 02 48 03 .%..6...s..c..H. +| 1424: 02 66 6f 07 04 84 60 03 01 67 03 04 84 27 03 01 .fo...`..g...'.. +| 1440: 6c 0a 02 63 03 01 6d 07 04 82 75 03 01 6f 08 04 l..c..m...u..o.. +| 1456: 82 4d 04 04 61 74 d4 a5 02 04 83 3c 03 02 d9 a8 .M..at.....<.... +| 1472: 04 04 83 4e 03 03 db 93 61 02 04 81 52 03 03 e1 ...N....a...R... +| 1488: bf a7 08 04 83 42 02 01 bc 01 06 81 6c 2f 01 06 .....B......l/.. +| 1504: 81 31 32 01 06 30 81 02 01 0c 81 02 81 73 04 67 .12..0.......s.g +| 1520: 01 06 81 6c 20 01 0e 57 6d 04 81 1f 81 24 01 04 ...l ..Wm....$.. +| 1536: 83 54 01 0c 70 49 81 59 81 11 01 0e 56 81 44 0d .T..pI.Y....V.D. +| 1552: 3d 81 23 01 0a 83 15 81 6b 06 03 02 39 6d 0a 02 =.#.....k...9m.. +| 1568: 59 03 03 62 c2 aa 03 04 82 2f 03 01 63 02 04 84 Y..b...../..c... +| 1584: 45 04 05 79 76 7a c2 ba 04 04 81 19 03 01 66 07 E..yvz........f. +| 1600: 04 81 74 03 01 69 08 04 83 59 03 02 6f 37 09 04 ..t..i...Y..o7.. +| 1616: 84 38 03 01 72 04 04 81 3f 03 01 73 07 02 69 03 .8..r...?..s..i. +| 1632: 03 79 76 62 04 02 76 03 02 c2 b2 06 04 84 48 04 .yvb..v.......H. +| 1648: 03 b3 34 79 02 04 81 3d 04 01 bc 08 04 82 19 03 ..4y...=........ +| 1664: 04 ce bc ce bc 01 04 84 4a 03 04 e7 a8 a1 79 01 ........J.....y. +| 1680: 02 03 02 01 bd 01 06 58 82 1d 01 0a 82 7b 81 02 .......X........ +| 1696: 57 01 04 0a 6b 01 08 81 5f 70 13 01 12 0e 38 65 W...k..._p....8e +| 1712: 11 34 52 4c 5c 75 01 06 82 56 3e 01 02 23 01 0a .4RL.u...V>..#.. +| 1728: 81 29 08 83 2f 01 0c 81 43 81 48 2f 03 01 08 83 .)../...C.H/.... +| 1744: 5a 76 3a 03 01 30 04 04 81 71 03 01 32 01 04 82 Zv:..0...q..2... +| 1760: 1d 03 01 36 02 04 84 34 03 01 61 09 04 82 41 03 ...6...4..a...A. +| 1776: 01 68 07 02 38 03 01 6a 08 04 84 15 03 01 6c 08 .h..8..j......l. +| 1792: 04 82 66 03 01 6d 08 04 82 69 03 01 6f 01 04 82 ..f..m...i..o... +| 1808: 40 02 04 84 2d 03 02 78 03 01 73 04 08 81 29 82 @...-..x..s...). +| 1824: 08 03 03 75 75 6b 06 04 84 21 03 01 78 05 04 83 ...uuk...!..x... +| 1840: 61 04 04 c2 be c2 bc 06 04 82 5c 03 01 79 01 04 a............y.. +| 1856: 83 46 03 02 c2 b3 06 02 40 02 01 be 01 08 81 0f .F......@....... +| 1872: 81 71 01 0e 81 25 60 81 39 1d 70 01 06 1b 83 53 .q...%`.9.p....S +| 1888: 01 06 83 37 52 01 10 6b 65 18 2a 81 4d 47 33 01 ...7R..ke.*.MG3. +| 1904: 06 82 79 07 01 02 46 01 08 03 13 81 64 01 0c 5a ..y...F.....d..Z +| 1920: 65 76 82 33 04 01 08 82 61 82 19 03 02 35 64 01 ev.3....a....5d. +| 1936: 04 84 17 03 01 37 06 04 82 10 03 01 62 08 02 5b .....7......b..[ +| 1952: 03 01 66 07 04 83 04 03 01 67 06 02 36 03 01 6a ..f......g..6..j +| 1968: 01 04 83 3e 08 04 83 59 03 01 71 01 04 82 47 03 ...>...Y..q...G. +| 1984: 01 72 08 06 81 73 32 03 01 75 09 04 82 35 03 02 .r...s2..u...5.. +| 2000: 77 76 07 04 83 68 03 01 78 05 02 72 03 02 c2 b3 wv...h..x..r.... +| 2016: 0a 04 81 7e 04 01 bd 05 04 84 35 03 02 cb 8a 03 ...~......5..... +| 2032: 04 84 46 03 02 ce bc 02 04 82 72 01 05 c3 9f ca ..F.......r..... +| 2048: 83 74 03 04 81 58 02 01 a6 08 04 84 26 02 04 82 .t...X......&... +| 2064: 1d 02 01 be 06 04 81 4f 03 04 83 1d 01 02 c4 a7 .......O........ +| 2080: 01 02 0e 05 04 81 7c 04 04 84 09 02 01 b3 01 04 ......|......... +| 2096: 81 43 09 04 82 70 03 02 67 72 07 04 83 06 02 01 .C...p..gr...... +| 2112: b8 06 04 83 09 03 04 78 6d 77 75 07 04 82 69 01 .......xmwu...i. +| 2128: 02 c5 80 06 04 83 40 02 01 82 06 04 82 6a 03 04 ......@......j.. +| 2144: 82 1a 02 01 89 07 08 81 39 81 6c 02 01 8b 06 04 ........9.l..... +| 2160: 81 1a 03 01 6a 02 02 26 02 01 93 04 04 83 49 03 ....j..&......I. +| 2176: 02 6e 70 03 02 0f 01 02 c6 83 01 04 82 5b 06 04 .np..........[.. +| 2192: 83 6a 02 01 85 02 04 82 0d 03 01 31 09 02 41 03 .j.........1..A. +| 2208: 01 6b 0a 04 84 0d 02 01 88 04 06 82 16 7f 02 01 .k.............. +| 2224: 95 06 04 81 50 02 04 84 18 01 04 84 3f 03 01 78 ....P.......?..x +| 2240: 06 04 84 11 02 01 99 05 04 83 38 01 04 81 4a 02 ..........8...J. +| 2256: 01 9a 04 04 81 16 02 02 9b 62 03 02 10 02 01 a3 .........b...... +| 2272: 04 04 82 0d 01 04 84 64 02 01 a5 05 06 81 3d 61 .......d......=a +| 2288: 03 02 79 69 05 04 82 23 02 01 a8 09 04 83 44 02 ..yi...#......D. +| 2304: 01 ab 03 04 81 13 02 02 ad 75 06 04 82 07 02 01 .........u...... +| 2320: b4 05 04 82 74 03 04 75 61 65 6c 05 04 82 68 02 ....t..uael...h. +| 2336: 01 b6 03 04 81 6b 02 01 ba 05 04 83 72 02 01 bd .....k......r... +| 2352: 06 04 84 15 02 02 be 74 02 04 84 57 03 01 79 07 .......t...W..y. +| 2368: 04 82 5d 02 02 bf 67 09 04 81 42 03 01 72 03 04 ..]...g...B..r.. +| 2384: 82 7a 03 01 77 0a 02 3c 01 02 c7 80 02 04 84 1d .z..w..<........ +| 2400: 03 01 31 04 04 83 1d 02 03 81 c9 ab 06 04 83 4d ..1............M +| 2416: 02 01 82 09 04 83 25 02 03 86 d3 97 05 02 34 02 ......%.......4. +| 2432: 02 89 78 02 04 83 19 02 01 8c 03 02 56 02 04 81 ..x.........V... +| 2448: 3b 02 04 82 16 02 01 9c 03 04 82 55 04 04 81 08 ;..........U.... +| 2464: 02 01 9d 07 04 81 1c 03 01 68 04 04 83 11 02 01 .........h...... +| 2480: 9f 05 04 81 0e 02 01 a1 01 04 84 79 08 04 82 27 ...........y...' +| 2496: 02 01 a3 04 04 81 5c 06 02 33 02 02 a5 76 0a 04 .........3...v.. +| 2512: 82 07 02 08 ad 7a c8 bf e0 a4 8a 64 04 04 81 03 .....z.....d.... +| 2528: 02 01 af 05 04 84 25 02 01 b3 03 02 5c 02 01 bb ......%......... +| 2544: 06 02 06 03 01 68 03 04 81 2c 03 01 71 04 04 81 .....h...,..q... +| 2560: 69 02 01 bd 03 06 81 7a 6f 02 04 84 2f 01 03 c8 i......zo.../... +| 2576: 9d 36 02 04 83 43 02 02 a1 70 01 04 82 4a 02 01 .6...C...p...J.. +| 2592: a5 02 04 83 45 02 01 ad 01 04 81 10 02 01 b1 02 ....E........... +| 2608: 04 84 4c 07 04 84 03 02 01 bc 08 04 81 3b 03 01 ..L..........;.. +| 2624: 31 03 04 83 45 02 01 bf 07 04 81 17 01 02 c9 80 1...E........... +| 2640: 01 02 3a 01 04 83 62 02 01 82 02 04 81 55 04 04 ..:...b......U.. +| 2656: 84 6d 02 02 50 02 01 8f 02 04 84 16 06 04 83 57 .m..P..........W +| 2672: 02 01 91 04 04 83 4f 02 01 93 0a 04 81 1d 03 01 ......O......... +| 2688: 69 05 04 83 37 03 01 79 08 04 82 21 02 01 96 04 i...7..y...!.... +| 2704: 04 82 13 04 02 6c 02 04 83 29 02 01 97 04 02 2d .....l...).....- +| 2720: 02 03 99 6d 74 05 04 81 73 02 01 9f 09 04 84 63 ...mt...s......c +| 2736: 02 01 a0 05 02 6a 02 02 76 02 01 a2 02 02 2e 02 .....j..v....... +| 2752: 01 a3 01 04 82 16 02 01 a5 04 04 81 31 06 04 83 ............1... +| 2768: 0f 02 01 a8 03 04 83 47 02 01 a9 06 06 82 0d 49 .......G.......I +| 2784: 02 01 af 0a 04 83 1c 02 02 b2 34 04 04 81 3c 02 ..........4...<. +| 2800: 04 b5 74 c2 b2 03 04 81 28 02 01 ba 09 04 83 62 ..t.....(......b +| 2816: 02 01 bb 07 04 84 73 02 01 bc 06 04 81 2a 02 01 ......s......*.. +| 2832: bd 04 04 81 68 02 01 be 09 04 83 67 03 01 73 08 ....h......g..s. +| 2848: 04 83 5f 01 02 ca 80 07 04 84 0b 03 01 77 02 04 .._..........w.. +| 2864: 82 59 02 01 81 06 04 82 1c 02 01 83 08 04 83 1b .Y.............. +| 2880: 01 04 81 71 02 01 86 07 04 81 79 02 01 88 08 02 ...q......y..... +| 2896: 62 01 04 81 0a 02 01 89 04 04 81 62 02 01 8a 03 b..........b.... +| 2912: 04 83 11 01 04 83 50 02 01 8c 06 04 84 2e 02 01 ......P......... +| 2928: 8e 03 04 82 3e 04 04 82 33 03 01 68 07 04 82 3b ....>...3..h...; +| 2944: 02 01 8f 04 04 82 63 03 01 73 08 02 7e 02 01 90 ......c..s..~... +| 2960: 08 02 20 02 01 91 01 04 83 52 02 01 94 06 04 83 .. ......R...... +| 2976: 36 02 02 95 7a 0a 02 51 02 04 99 75 73 73 03 02 6...z..Q...uss.. +| 2992: 36 02 01 9c 06 04 81 27 02 01 a5 07 04 81 7b 03 6......'........ +| 3008: 07 e0 a4 aa d5 b8 61 62 0a 02 77 02 02 a6 6f 07 ......ab..w...o. +| 3024: 04 81 07 03 03 ec b2 be 02 04 82 39 02 02 a7 33 ...........9...3 +| 3040: 08 02 61 02 01 a9 03 04 84 07 02 01 b6 0a 04 84 ..a............. +| 3056: 02 02 01 b7 05 04 81 4a 03 03 6a 69 6a 05 04 81 .......J..jij... +| 3072: 48 02 01 b8 09 04 83 0a 03 04 c4 91 d8 bd 03 02 H............... +| 3088: 1c 02 01 b9 09 02 4c 02 04 ba c9 b1 37 07 04 81 ......L.....7... +| 3104: 4e 02 02 bb 70 01 04 84 4c 02 01 bc 01 04 83 0c N...p...L....... +| 3120: 02 02 be 72 05 02 04 01 02 cb 80 0a 04 84 52 02 ...r..........R. +| 3136: 01 88 07 04 83 1d 02 01 8a 08 02 7c 02 01 a1 09 ...........|.... +| 3152: 04 81 1b 03 01 30 06 04 81 52 03 01 6a 01 04 82 .....0...R..j... +| 3168: 34 02 01 a2 01 04 83 30 02 03 a3 71 64 04 02 7e 4......0...qd..~ +| 3184: 02 01 a4 0a 04 83 34 02 01 ae 06 04 84 7f 03 01 ......4......... +| 3200: 6e 06 04 83 68 01 02 cd b1 02 04 82 55 03 01 6a n...h.......U..j +| 3216: 01 04 83 5d 02 01 b7 08 04 81 58 03 01 75 07 04 ...]......X..u.. +| 3232: 84 09 02 01 b8 01 04 82 0e 06 02 22 03 04 82 4f ...............O +| 3248: 03 02 dd af 05 04 84 3f 02 01 bc 01 04 84 3b 02 .......?......;. +| 3264: 01 bd 09 04 81 61 01 04 83 3c 02 03 bf 64 67 02 .....a...<...dg. +| 3280: 04 82 0c 01 05 ce 81 66 da 94 01 02 10 03 01 6b .......f.......k +| 3296: 0a 04 82 7e 02 01 8d 08 04 83 02 02 07 ad 69 f0 ...~..........i. +| 3312: 98 a6 a7 61 09 04 84 41 03 01 72 03 04 82 62 02 ...a...A..r...b. +| 3328: 01 ae 06 04 83 5d 02 04 81 44 03 01 37 06 04 81 .....]...D..7... +| 3344: 08 02 05 af 64 cf 8d 32 01 02 11 02 01 b1 05 04 ....d..2........ +| 3360: 84 52 03 01 61 08 04 81 68 03 01 68 08 04 83 5a .R..a...h..h...Z +| 3376: 02 01 b2 05 04 84 43 01 02 7e 03 04 83 23 02 04 ......C..~...#.. +| 3392: b3 71 30 73 03 04 83 76 02 01 b5 02 04 82 04 02 .q0s...v........ +| 3408: 01 b6 0a 04 84 6e 02 01 b7 03 04 81 36 02 01 b8 .....n......6... +| 3424: 01 04 84 60 02 02 3a 03 04 84 76 01 02 50 03 02 ...`..:...v..P.. +| 3440: 32 6a 09 04 81 0c 03 01 68 03 02 11 02 01 b9 04 2j......h....... +| 3456: 04 84 0f 02 04 bb d2 b5 62 08 04 81 10 02 01 bc ........b....... +| 3472: 01 0e 4d 81 25 3f 1b 81 54 01 0c 55 81 38 06 82 ..M.%?..T..U.8.. +| 3488: 1b 01 08 21 82 66 37 01 08 65 09 83 3c 02 0a 82 ...!.f7..e..<... +| 3504: 21 08 81 52 01 08 83 4e 81 29 01 0e 47 15 81 21 !..R...N.)..G..! +| 3520: 81 0f 65 01 10 12 20 2d 38 81 00 81 73 01 10 0e ..e... -8...s... +| 3536: 33 6c 2d 3e 81 4e 07 03 01 67 03 04 83 52 03 01 3l->.N...g...R.. +| 3552: 6b 08 04 83 72 03 02 6c 32 02 04 81 51 03 03 6d k...r..l2...Q..m +| 3568: 67 79 09 04 82 67 03 01 70 04 04 81 44 03 02 71 gy...g..p...D..q +| 3584: 6a 0a 04 81 6d 03 01 79 01 04 81 5e 03 02 c2 bd j...m..y...^.... +| 3600: 07 04 81 3a 04 01 be 08 02 18 03 02 ce bc 07 02 ...:............ +| 3616: 34 02 01 bd 03 02 28 03 01 67 04 04 82 09 03 02 4.....(..g...... +| 3632: 6e 6c 01 02 22 03 01 78 02 04 83 5f 02 01 bf 02 nl.....x..._.... +| 3648: 02 16 03 01 7a 06 04 82 05 01 02 cf 80 03 02 69 ....z..........i +| 3664: 03 01 6e 05 04 83 40 02 02 81 6c 06 04 84 5d 03 ..n...@...l...]. +| 3680: 02 ca ab 04 04 81 5d 02 01 84 03 04 81 2e 03 04 ......]......... +| 3696: 82 6e 02 01 85 06 04 82 40 02 01 88 04 02 62 02 .n......@.....b. +| 3712: 03 8a 75 6a 03 04 83 50 02 01 8b 05 04 82 60 81 ..uj...P......`. +| 3728: 01 08 07 0a 07 09 07 0b 06 07 07 07 0a 0b 0a 07 ................ +| 3744: 07 0a 06 07 0a 08 07 07 07 12 09 08 0b 07 08 07 ................ +| 3760: 06 07 07 07 07 0a 09 07 0a 08 07 3f 08 07 06 06 ...........?.... +| 3776: 08 07 07 09 07 07 0a 4b 07 07 07 07 07 0a 07 07 .......K........ +| 3792: 09 07 08 08 08 0a 0b 49 07 07 07 09 07 0b 0a 08 .......I........ +| 3808: 07 08 0a 0d 08 48 07 07 08 08 09 07 07 07 09 09 .....H.......... +| 3824: 07 07 09 0a 07 07 07 08 0b 0c 0b 08 08 0d 4c 07 ..............L. +| 3840: 07 06 08 07 06 07 07 0a 08 09 09 44 07 09 07 0b ...........D.... +| 3856: 07 07 08 07 06 08 08 09 07 0a 09 41 07 07 07 07 ...........A.... +| 3872: 06 07 07 07 0e 09 09 07 0a 07 07 42 08 07 06 07 ...........B.... +| 3888: 06 0b 07 08 07 08 06 08 07 08 08 0b 0b 0b 0f 0b ................ +| 3904: 08 07 0a 08 0b 09 07 06 07 07 0c 07 06 07 08 0f ................ +| 3920: 07 0b 07 07 0b 08 08 07 07 08 07 0a 07 07 07 08 ................ +| 3936: 07 08 07 06 08 07 09 07 08 08 0e 0b 07 07 07 0b ................ +| 3952: 0a 08 0e 07 06 06 07 07 0c 09 08 07 07 0b 07 07 ................ +| 3968: 07 0b 0e 0b 07 07 07 07 0e 06 09 07 09 06 07 0b ................ +| 3984: 07 08 07 08 0a 07 07 07 07 07 07 08 07 07 0b 07 ................ +| 4000: 0a 07 0b 07 0b 07 07 06 06 07 07 07 09 07 07 0c ................ +| 4016: 08 09 07 07 07 07 09 07 09 06 0a 08 07 07 08 07 ................ +| 4032: 06 07 07 07 07 08 07 07 07 08 07 07 07 0e 08 07 ................ +| 4048: 0b 09 0a 07 07 0d 07 0b 07 0a 07 07 07 0e 0a 07 ................ +| 4064: 07 07 11 08 06 07 0a 4a 07 07 08 09 07 08 07 08 .......J........ +| 4080: 06 07 06 07 07 07 06 07 07 07 08 08 0b 07 06 09 ................ +| page 25 offset 98304 +| 0: 0d 00 00 00 01 00 29 00 00 29 00 00 00 00 00 00 ......)..)...... +| 32: 00 00 00 00 00 00 00 00 00 9f 4f 88 80 80 80 80 ..........O..... +| 48: 06 04 00 bf 22 00 00 0e 17 04 30 cf 8c 70 03 02 ..........0..p.. +| 64: 0e 02 01 8d 05 04 82 5c 03 01 63 07 04 84 19 02 ..........c..... +| 80: 01 8e 09 02 66 02 01 93 06 04 83 30 02 01 99 06 ....f......0.... +| 96: 04 84 75 04 04 84 07 03 02 c6 a3 05 04 84 01 02 ..u............. +| 112: 01 9b 08 02 42 02 01 9d 07 02 30 03 02 0b 02 01 ....B.....0..... +| 128: 9f 06 04 81 06 03 02 da a9 07 02 66 02 01 a5 09 ...........f.... +| 144: 02 0c 02 01 ab 02 04 81 39 02 01 ad 01 02 6d 03 ........9.....m. +| 160: 04 82 62 02 01 b2 0a 04 83 36 02 03 b3 d6 88 07 ..b......6...... +| 176: 04 84 78 02 01 b8 05 04 84 02 05 04 81 5d 01 02 ..x..........].. +| 192: d0 b1 0a 02 5f 03 01 7a 03 02 76 02 02 b5 35 08 ...._..z..v...5. +| 208: 04 83 63 03 01 75 07 04 84 35 02 04 b8 71 61 61 ..c..u...5...qaa +| 224: 0a 04 81 6b 02 01 bb 08 04 84 4a 02 01 bd 0a 06 ...k......J..... +| 240: 6f 83 6d 01 02 d1 80 06 04 83 65 02 01 84 07 04 o.m.......e..... +| 256: 82 1b 02 01 85 09 04 82 3f 02 02 86 6f 08 04 82 ........?...o... +| 272: 5f 02 01 88 03 04 81 29 02 01 89 02 04 83 14 08 _......)........ +| 288: 04 84 6f 03 01 63 02 04 84 1c 02 01 8a 0a 06 82 ..o..c.......... +| 304: 14 2d 03 02 6c 69 04 04 83 57 02 01 8e 07 04 84 .-..li...W...... +| 320: 48 02 06 90 e1 91 8f 6a 7a 05 04 83 46 02 03 91 H......jz...F... +| 336: 64 37 03 04 82 01 02 01 93 0a 04 82 1f 02 01 98 d7.............. +| 352: 05 04 84 30 02 03 9b 63 66 05 04 81 55 02 05 9f ...0...cf...U... +| 368: 78 6a d1 87 09 04 81 4d 02 02 a3 78 06 02 07 02 xj.....M...x.... +| 384: 04 a5 e1 a9 8f 06 02 10 02 02 af 76 09 04 83 13 ...........v.... +| 400: 03 02 7a 33 07 04 84 1f 02 01 b1 05 04 84 0e 02 ..z3............ +| 416: 02 b7 71 0a 04 82 7b 02 02 bf 62 0a 04 83 6f 01 ..q.......b...o. +| 432: 04 d2 8b 67 73 06 04 83 2b 02 04 97 e3 86 b2 07 ...gs...+....... +| 448: 04 84 12 02 01 9f 02 04 81 05 03 02 24 02 01 a7 ............$... +| 464: 04 04 84 1f 02 01 a9 04 04 81 47 06 06 23 82 23 ..........G..#.# +| 480: 02 02 b1 78 06 02 43 02 01 b9 02 04 82 44 02 01 ...x..C......D.. +| 496: bd 02 04 83 3d 08 04 81 76 02 01 bf 0a 04 85 04 ....=...v....... +| 512: 01 02 d3 8e 06 04 85 02 02 02 8f 68 03 02 75 02 ...........h..u. +| 528: 01 91 08 02 28 02 01 93 05 02 16 02 01 9d 06 04 ....(........... +| 544: 82 5e 02 02 9f 33 06 04 83 78 02 03 a7 6c 78 06 .^...3...x...lx. +| 560: 04 83 33 03 09 da a9 ec 82 b2 e1 87 90 71 06 04 ..3..........q.. +| 576: 84 7b 02 01 ab 06 04 83 27 02 01 ad 03 02 72 02 ........'.....r. +| 592: 01 af 03 04 82 0c 02 01 b5 0a 04 81 7d 02 01 bb ................ +| 608: 0a 02 58 03 01 6a 03 04 81 70 01 02 d4 81 04 04 ..X..j...p...... +| 624: 84 0d 02 01 85 06 04 81 3d 02 02 8b 35 07 04 83 ........=...5... +| 640: 50 02 02 8d 77 03 02 79 02 01 95 0a 04 84 5c 02 P...w..y........ +| 656: 03 97 6a 6c 0a 04 83 7a 02 01 99 09 04 82 26 02 ..jl...z......&. +| 672: 02 9b 71 05 04 82 01 02 02 9d 67 04 04 84 5f 02 ..q.......g..._. +| 688: 01 a7 04 04 82 4a 01 02 d5 a3 0a 04 81 00 03 01 .....J.......... +| 704: 39 06 04 81 51 02 01 a5 06 02 5e 03 01 67 02 04 9...Q.....^..g.. +| 720: 81 0d 02 01 a9 08 04 84 1e 01 02 75 02 01 aa 04 ...........u.... +| 736: 04 81 0a 02 01 ab 03 04 83 21 02 01 ac 06 02 08 .........!...... +| 752: 02 01 ad 0a 02 57 02 02 b3 74 0a 04 84 40 02 03 .....W...t...@.. +| 768: b4 61 6e 08 04 83 1a 03 02 6d 76 02 04 81 0a 02 .an......mv..... +| 784: 01 ba 06 04 83 51 02 01 bb 03 04 82 61 03 01 67 .....Q......a..g +| 800: 08 04 83 28 02 01 bc 01 02 62 01 02 d6 80 01 04 ...(.....b...... +| 816: 84 53 02 02 82 7a 08 02 7f 02 01 83 06 02 50 02 .S...z........P. +| 832: 04 81 32 02 01 86 09 04 82 52 02 01 87 09 04 83 ..2......R...... +| 848: 7b 02 01 8c 03 04 83 19 01 03 d7 89 74 0a 04 82 ............t... +| 864: 37 02 01 92 07 04 81 23 02 01 93 0a 04 81 12 02 7......#........ +| 880: 01 9a 07 02 58 02 01 9e 03 04 81 19 02 01 a0 0a ....X........... +| 896: 04 82 5e 03 01 6c 01 02 0a 03 04 78 c2 b9 63 05 ..^..l.....x..c. +| 912: 02 02 02 01 a8 0a 02 07 02 01 ab 01 04 81 01 02 ................ +| 928: 03 af c3 b0 05 04 82 4e 02 04 b7 69 c2 be 04 04 .......N...i.... +| 944: 84 44 02 05 bd e4 98 a5 6c 03 04 83 51 01 03 d8 .D......l...Q... +| 960: 9c 38 08 04 84 73 02 02 9d 73 0a 04 83 0b 02 01 .8...s...s...... +| 976: aa 02 04 84 56 02 02 ab 7a 03 04 82 7f 02 01 ac ....V...z....... +| 992: 09 04 82 40 02 02 ad 61 03 04 81 14 03 03 c6 8c ...@...a........ +| 1008: 71 09 04 82 56 02 02 b7 69 0a 04 82 5c 02 03 b8 q...V...i....... +| 1024: 66 66 05 04 81 11 03 01 7a 05 04 82 7a 02 01 be ff......z...z... +| 1040: 05 04 81 2a 02 02 bf 76 0a 02 76 01 02 d9 81 01 ...*...v..v..... +| 1056: 04 83 76 02 01 83 02 02 3c 03 01 36 05 04 81 54 ..v.....<..6...T +| 1072: 02 01 85 09 04 82 73 02 01 86 06 04 84 53 02 03 ......s......S.. +| 1088: a7 6a 61 01 04 82 0f 02 01 a8 04 02 38 02 01 b1 .ja.........8... +| 1104: 01 04 83 21 03 03 dc 94 6a 02 02 2b 02 01 b2 07 ...!....j..+.... +| 1120: 02 1a 02 01 b6 04 04 82 17 02 01 ba 04 04 83 7b ................ +| 1136: 03 01 71 03 04 82 77 02 01 bd 09 04 84 4f 01 04 ..q...w......O.. +| 1152: 82 49 01 07 da 80 e8 b6 8a 67 68 05 04 83 04 02 .I.......gh..... +| 1168: 03 85 d1 9c 06 04 81 59 02 01 8f 05 04 81 2c 02 .......Y......,. +| 1184: 01 93 01 04 83 3d 02 04 9a ed 90 8b 01 04 83 44 .....=.........D +| 1200: 02 02 9c 73 09 04 83 21 02 01 a0 0a 04 85 03 02 ...s...!........ +| 1216: 01 a6 05 02 7e 03 04 81 19 02 02 a9 77 05 04 82 ....~.......w... +| 1232: 69 02 01 ac 03 02 4f 04 04 82 76 03 03 67 c2 bc i.....O...v..g.. +| 1248: 07 04 83 38 02 01 b7 03 04 82 5b 02 01 bf 09 02 ...8......[..... +| 1264: 6a 01 02 db 83 08 04 84 55 02 02 93 70 02 04 81 j.......U...p... +| 1280: 47 02 01 a5 05 04 82 12 02 01 a6 01 02 52 01 02 G............R.. +| 1296: dc 8e 01 02 6a 02 02 96 6b 06 04 84 29 02 01 9a ....j...k...)... +| 1312: 05 04 84 05 03 04 82 3e 02 01 9f 09 04 81 64 02 .......>......d. +| 1328: 03 a4 76 77 0a 04 84 0f 02 01 a6 06 04 83 52 02 ..vw..........R. +| 1344: 01 aa 01 04 83 70 02 01 ae 01 04 83 33 01 02 dd .....p......3... +| 1360: 8c 03 04 82 1f 02 02 90 78 09 04 82 4b 02 01 96 ........x...K... +| 1376: 05 04 81 10 03 01 6c 06 04 83 24 02 02 9c 6a 04 ......l...$...j. +| 1392: 04 82 1b 02 05 a0 79 c9 a8 75 01 02 34 02 01 a8 ......y..u..4... +| 1408: 05 02 2c 02 01 b2 06 04 84 3f 02 02 ba 67 03 02 ..,......?...g.. +| 1424: 2f 02 05 bf e0 a6 ad 77 08 02 2f 01 02 de 83 05 /......w../..... +| 1440: 02 65 02 01 84 06 04 81 00 02 01 85 01 04 82 60 .e.............` +| 1456: 01 04 81 57 02 01 8f 03 04 83 3b 02 01 90 03 02 ...W......;..... +| 1472: 7b 02 01 91 07 04 83 5d 02 03 9c 78 61 07 04 81 .......]...xa... +| 1488: 00 02 01 9e 08 04 82 00 02 02 9f 6f 04 04 83 51 ...........o...Q +| 1504: 02 01 a3 04 04 81 2f 02 02 a5 73 0a 04 84 7a 02 ....../...s...z. +| 1520: 02 b1 66 08 04 81 5a 02 02 b7 78 02 02 78 02 01 ..f...Z...x..x.. +| 1536: b9 05 04 83 08 02 01 ba 0a 04 84 76 02 01 bb 07 ...........v.... +| 1552: 02 5c 02 01 bc 08 02 13 02 01 bf 08 02 2a 01 03 .............*.. +| 1568: df 82 66 08 04 82 2c 02 01 87 01 04 83 0a 02 01 ..f...,......... +| 1584: 8d 05 04 84 0c 02 05 91 72 6b 69 61 03 02 45 02 ........rkia..E. +| 1600: 03 97 de a4 09 04 82 6b 02 02 98 62 06 04 81 18 .......k...b.... +| 1616: 02 01 a0 0a 04 82 4a 02 01 bf 05 02 39 01 06 e0 ......J.....9... +| 1632: a7 9d 31 76 63 05 04 83 00 02 02 af aa 02 04 81 ..1vc........... +| 1648: 2b 02 03 b7 ae 65 01 04 84 36 02 02 bb 98 0a 04 +....e...6...... +| 1664: 82 23 01 04 e1 80 88 63 06 04 83 5c 03 01 a2 04 .#.....c........ +| 1680: 02 5c 02 02 85 9b 05 04 81 3e 02 04 8b b3 db 8f .........>...... +| 1696: 07 02 56 02 04 91 b8 64 66 08 04 82 49 02 02 95 ..V....df...I... +| 1712: 9d 08 04 81 4e 03 01 a2 03 04 81 77 02 02 9a a0 ....N......w.... +| 1728: 01 04 82 39 02 03 9d 90 69 03 02 41 02 03 a6 9a ...9....i..A.... +| 1744: 68 04 02 4c 02 05 a7 9c c7 ad 6e 06 04 83 7b 02 h..L......n..... +| 1760: 03 a8 b2 38 04 02 5f 02 02 af 88 07 02 19 02 03 ...8.._......... +| 1776: bd 96 71 03 04 81 11 01 03 e2 83 80 05 02 5f 02 ..q..........._. +| 1792: 02 b1 a6 01 04 83 34 01 04 84 06 02 02 b3 9d 01 ......4......... +| 1808: 04 83 0f 02 03 ba 9a 64 01 04 84 4f 01 03 e3 81 .......d...O.... +| 1824: 81 0a 02 21 02 02 84 8c 03 04 81 10 02 02 90 a1 ...!............ +| 1840: 02 04 81 1b 02 02 9c a3 02 04 82 2e 02 02 a5 89 ................ +| 1856: 04 04 82 23 02 02 a6 98 07 04 83 00 02 09 ab a8 ...#............ +| 1872: 77 31 63 6a c2 aa 35 04 04 81 38 03 01 aa 01 04 w1cj..5...8..... +| 1888: 82 1e 02 02 ac a4 02 02 37 02 05 b9 8e 70 34 79 ........7....p4y +| 1904: 0a 04 84 30 02 02 bd bf 01 04 83 19 01 05 e4 80 ...0............ +| 1920: 83 79 69 04 04 83 15 03 01 bb 01 02 63 02 03 8a .yi.........c... +| 1936: 8d 36 0a 04 84 57 02 05 8c 88 d0 b1 63 09 04 84 .6...W......c... +| 1952: 49 02 02 90 8e 02 04 84 60 02 02 a1 98 03 04 84 I.......`....... +| 1968: 0f 02 02 b1 a2 08 04 83 30 01 04 e5 85 87 68 02 ........0.....h. +| 1984: 04 84 19 02 03 8a a8 39 0a 02 6e 02 03 8d be 38 .......9..n....8 +| 2000: 09 04 81 78 02 04 8f 85 68 30 05 04 84 5a 03 01 ...x....h0...Z.. +| 2016: a7 09 04 82 09 02 03 92 8e 7a 04 02 28 02 02 95 .........z..(... +| 2032: 89 01 02 38 03 01 be 07 04 84 4d 02 08 9e 9e f4 ...8......M..... +| 2048: 95 95 98 68 71 07 04 84 13 02 02 9f 9f 04 04 84 ...hq........... +| 2064: 35 02 02 a0 a2 02 02 24 03 05 b4 f0 90 b1 92 0a 5......$........ +| 2080: 04 82 15 02 03 a3 a1 64 07 04 84 22 02 02 a6 bb .......d........ +| 2096: 04 04 84 52 02 05 a8 af 75 c2 bd 01 04 81 25 02 ...R....u.....%. +| 2112: 02 aa b3 06 04 81 66 02 03 ae be 7a 0a 02 4d 02 ......f....z..M. +| 2128: 08 af 96 66 6b f3 93 af 8f 04 04 83 02 02 03 bd ...fk........... +| 2144: aa 6a 07 02 62 01 04 e6 80 83 69 08 04 84 19 02 .j..b.....i..... +| 2160: 02 81 bc 0a 04 81 25 02 04 86 aa 32 75 06 04 82 ......%....2u... +| 2176: 65 02 05 96 b7 69 35 30 07 02 78 02 03 9d 98 73 e....i50..x....s +| 2192: 06 02 7f 02 02 a3 97 02 04 81 7f 02 05 a5 80 e7 ................ +| 2208: a0 b6 01 04 81 37 03 01 87 08 04 82 61 02 02 b3 .....7......a... +| 2224: b2 03 04 83 1e 02 02 b8 a0 0a 04 84 2e 02 02 bc ................ +| 2240: af 0a 04 83 71 02 02 bf 91 09 04 84 42 01 03 e7 ....q.......B... +| 2256: 81 b4 07 04 84 5e 02 02 86 8e 01 04 84 2c 02 02 .....^.......,.. +| 2272: 8b a9 09 04 82 02 02 03 8d 9d 30 05 04 84 1a 02 ..........0..... +| 2288: 02 8e b6 08 04 83 60 02 02 91 99 0a 04 82 05 02 ......`......... +| 2304: 02 92 af 08 04 81 1c 02 02 94 b9 05 04 81 45 02 ..............E. +| 2320: 02 9c 8f 06 02 14 02 02 9e a2 07 04 83 78 02 02 .............x.. +| 2336: a0 83 06 04 83 08 02 02 a1 a8 01 02 78 02 02 ae ............x... +| 2352: ae 04 04 81 0e 02 02 b5 8c 06 04 84 6c 02 02 bf ............l... +| 2368: 80 09 04 83 1e 01 04 e8 83 93 73 0a 04 83 57 02 ..........s...W. +| 2384: 02 88 a8 0a 04 84 50 02 02 89 a0 09 04 83 70 02 ......P.......p. +| 2400: 02 8a ad 05 04 83 18 02 02 95 84 04 02 3b 03 01 .............;.. +| 2416: b5 06 04 81 26 02 08 a0 91 65 ce bb 6a d7 96 09 ....&....e..j... +| 2432: 04 82 05 02 02 a7 bf 02 04 84 47 02 02 b5 bb 05 ..........G..... +| 2448: 02 0a 02 04 bf 88 6a 6b 03 04 84 2a 01 04 e9 80 ......jk...*.... +| 2464: 97 65 09 04 83 26 02 02 9a 89 04 04 83 29 02 02 .e...&.......).. +| 2480: a4 b8 05 04 82 7f 02 05 ba 83 69 76 6c 04 02 3a ..........ivl..: +| 2496: 03 01 87 03 02 3c 02 02 be b6 06 04 84 25 01 06 .....<.......%.. +| 2512: ea 81 98 64 39 79 03 04 84 12 02 02 88 bc 09 04 ...d9y.......... +| 2528: 84 40 02 03 8c b5 39 01 04 83 11 02 02 91 ab 05 .@....9......... +| 2544: 04 83 01 02 03 9d 97 63 05 04 83 07 02 02 9e a3 .......c........ +| 2560: 04 04 82 04 02 02 ae a3 03 04 81 20 02 02 b2 a1 ........... .... +| 2576: 04 02 66 02 04 b4 9d 65 6b 02 02 29 02 03 ba bd ..f....ek..).... +| 2592: 72 08 04 83 75 02 03 bc a7 37 09 04 81 55 01 03 r...u....7...U.. +| 2608: eb 84 9d 07 02 60 02 06 8f aa 6f 69 6b 68 03 02 .....`....oikh.. +| 2624: 29 02 02 92 90 0a 04 81 30 02 04 99 ba 6e 65 02 ).......0....ne. +| 2640: 04 82 1e 02 02 b8 8f 05 04 81 52 02 02 bd 80 08 ..........R..... +| 2656: 04 84 23 02 02 be bc 08 02 2e 02 02 bf bc 01 04 ..#............. +| 2672: 82 6b 01 04 ec 85 b8 76 05 04 81 76 02 03 99 ad .k.....v...v.... +| 2688: 74 07 02 7e 02 02 a3 80 06 04 81 37 02 04 ac a9 t..~.......7.... +| 2704: 79 75 01 02 6f 02 02 b9 89 06 02 66 01 03 ed 8a yu..o......f.... +| 2720: 8a 05 02 3f 02 02 96 b6 05 02 63 01 04 ee 8d 91 ...?......c..... +| 2736: 7a 02 04 82 2a 02 02 92 af 03 04 84 06 02 03 98 z...*........... +| 2752: be 78 01 04 81 50 02 03 99 82 71 06 02 13 02 04 .x...P....q..... +| 2768: 9c 8d 68 61 09 04 83 1b 02 02 a4 bd 06 04 82 24 ..ha...........$ +| 2784: 02 02 ad a9 06 04 81 5d 02 02 b4 b9 09 04 81 75 .......].......u +| 2800: 02 02 bb 8d 05 04 84 39 01 03 ef af 90 03 04 82 .......9........ +| 2816: 3d 03 02 a3 61 03 04 83 02 02 02 b4 ae 0a 04 82 =...a........... +| 2832: 0e 02 02 b5 b1 06 02 47 02 02 b7 a9 02 04 84 42 .......G.......B +| 2848: 01 05 f0 90 8e bf 34 03 04 81 40 03 04 ad b0 33 ......4...@....3 +| 2864: 38 05 04 84 19 03 02 ae 89 07 04 81 68 03 03 af 8...........h... +| 2880: b7 61 0a 04 82 2f 02 03 91 81 a7 02 04 81 04 03 .a.../.......... +| 2896: 02 89 ac 08 04 83 62 03 02 97 a6 08 04 82 64 03 ......b.......d. +| 2912: 02 af bc 07 04 83 14 03 02 b1 b3 05 04 81 34 03 ..............4. +| 2928: 02 b7 ad 07 04 82 35 03 03 bb 9f 64 07 04 84 4c ......5....d...L +| 2944: 02 03 92 8d a5 05 04 82 7b 03 03 98 ab 62 09 04 .............b.. +| 2960: 81 2f 03 02 b7 99 05 04 82 43 02 04 93 85 98 6b ./.......C.....k +| 2976: 0a 02 1e 03 02 a9 90 01 04 84 62 03 05 b6 b0 74 ..........b....t +| 2992: c2 b3 07 04 82 55 02 03 95 8e a5 06 04 83 2a 03 .....U........*. +| 3008: 02 96 95 09 04 83 0c 03 02 97 8b 06 04 84 60 03 ..............`. +| 3024: 02 b5 bd 07 04 82 56 03 02 b7 9c 06 04 82 2d 02 ......V.......-. +| 3040: 04 96 a0 80 6e 01 04 83 7f 03 02 a5 ad 03 04 83 ....n........... +| 3056: 3c 03 02 ad ac 07 02 72 02 04 97 87 b5 35 07 02 <......r.....5.. +| 3072: 5f 04 01 bc 07 02 53 03 02 8f 90 02 02 70 02 06 _.....S......p.. +| 3088: 98 8f 88 73 7a 78 06 02 35 04 01 ba 02 02 23 03 ...szx..5.....#. +| 3104: 06 a2 92 ca 8a 36 6d 07 04 82 06 02 04 99 8b 93 .....6m......... +| 3120: 7a 09 04 84 6b 03 03 8c 82 72 07 04 81 7a 03 02 z...k....r...z.. +| 3136: 94 ba 04 04 82 3c 03 02 a1 92 0a 04 84 31 03 02 .....<.......1.. +| 3152: a8 b7 08 04 84 16 03 02 b1 ac 03 04 84 28 02 05 .............(.. +| 3168: 9a 99 8d d7 b5 01 04 84 65 03 03 9d 86 7a 09 04 ........e....z.. +| 3184: 84 07 03 03 ac 80 37 09 04 84 24 02 03 9b 87 93 ......7...$..... +| 3200: 04 04 82 60 03 02 91 8b 05 04 84 6c 03 02 a0 b9 ...`.......l.... +| 3216: 05 04 82 13 03 03 a5 81 70 06 04 83 6e 03 02 b6 ........p...n... +| 3232: 9a 03 04 82 00 02 03 9c 8a a3 04 04 83 7e 03 02 .............~.. +| 3248: 96 b7 01 04 83 5b 03 02 a8 88 02 04 83 7a 03 03 .....[.......z.. +| 3264: a9 98 77 02 04 81 38 03 02 b1 b2 04 02 46 03 03 ..w...8......F.. +| 3280: b6 bb 72 03 04 82 1a 03 02 be a9 04 04 83 73 02 ..r...........s. +| 3296: 04 9d 8a 9a 6a 01 02 32 03 02 96 9c 03 02 6f 03 ....j..2......o. +| 3312: 02 a6 b2 04 04 83 35 03 02 b4 a6 05 02 43 03 02 ......5......C.. +| 3328: bc 98 05 02 4a 02 03 9e 8f 98 0a 04 81 08 03 03 ....J........... +| 3344: 90 8c 6d 07 04 84 64 03 03 b4 9f 6e 03 04 84 35 ..m...d....n...5 +| 3360: 02 04 9f a1 a9 69 07 04 84 43 02 03 a7 85 b6 06 .....i...C...... +| 3376: 02 60 02 03 ad ba b7 06 02 2b 02 03 bc b6 b6 01 .`.......+...... +| 3392: 04 81 31 01 04 f1 92 a9 98 06 02 0c 03 03 b1 9d ..1............. +| 3408: 69 08 04 84 65 02 06 9e 91 a5 ea b0 98 07 04 82 i...e........... +| 3424: 43 02 03 a1 b7 be 02 04 81 13 02 03 b4 af 9e 0a C............... +| 3440: 04 84 1d 02 05 b5 92 84 69 6e 07 04 82 52 01 05 ........in...R.. +| 3456: f2 90 9a 83 73 03 04 82 45 02 03 91 ac 81 07 04 ....s...E....... +| 3472: 82 7a 03 02 be 94 02 04 81 46 02 03 94 89 ac 04 .z.......F...... +| 3488: 04 81 4a 02 04 95 b4 ae 39 08 04 82 2a 02 05 a0 ..J.....9...*... +| 3504: 90 b5 66 34 04 04 82 46 02 05 a6 91 b7 d5 bf 06 ..f4...F........ +| 3520: 04 81 7b 02 03 ab a0 93 05 02 2b 02 04 b2 8c 80 ..........+..... +| 3536: 6a 07 04 84 1a 03 02 9c a9 0a 04 81 36 03 02 aa j...........6... +| 3552: bf 04 04 84 34 01 05 f3 82 84 8d 66 0a 04 83 75 ....4......f...u +| 3568: 02 03 88 a7 b4 06 04 83 38 02 03 ab bf a6 02 04 ........8....... +| 3584: 81 65 02 05 b0 ae a8 73 70 0a 02 10 02 04 b8 a6 .e.....sp....... +| 3600: b6 75 05 04 83 4e 02 03 bd b2 84 08 04 83 50 01 .u...N........P. +| 3616: 04 f4 82 91 84 06 04 81 6a 02 03 8c b4 be 06 02 ........j....... +| 3632: 24 02 03 9b b2 83 01 04 84 4b 02 03 a0 87 b0 05 $........K...... +| 3648: 04 83 2f 03 03 ae ba 61 05 04 82 1b 04 08 07 07 ../....a........ +| 3664: 06 07 0b 08 06 09 07 07 06 07 0a 07 09 0b 07 06 ................ +| 3680: 08 07 0a 07 08 08 07 07 08 07 0b 07 08 08 07 0c ................ +| 3696: 09 07 07 09 0b 07 09 08 08 07 08 08 0a 0a 0a 07 ................ +| 3712: 0c 07 07 0b 07 08 07 06 06 07 08 09 0f 07 06 07 ................ +| 3728: 07 06 07 08 07 08 07 07 09 07 08 08 07 08 07 06 ................ +| 3744: 07 0a 07 07 06 06 08 09 08 07 07 07 06 08 07 0a ................ +| 3760: 07 07 07 09 07 07 06 07 07 06 09 06 07 09 0a 0b ................ +| 3776: 09 08 07 08 07 08 09 08 09 07 07 07 08 06 07 07 ................ +| 3792: 07 09 06 07 08 06 07 07 07 0b 0d 09 07 07 0a 08 ................ +| 3808: 07 0a 08 0a 09 07 06 08 08 07 06 07 08 0b 07 09 ................ +| 3824: 07 07 07 08 08 07 07 08 0a 06 07 07 0a 07 07 0b ................ +| 3840: 07 06 07 09 07 08 07 08 08 07 07 07 06 06 06 09 ................ +| 3856: 07 07 0a 09 08 07 06 0c 08 09 08 0a 06 08 09 0a ................ +| 3872: 08 07 08 08 08 0b 08 07 09 08 0c 08 09 08 08 08 ................ +| 3888: 08 08 08 0f 07 07 0b 08 0b 06 09 0b 08 08 08 0a ................ +| 3904: 08 09 0a 07 08 07 07 0e 08 07 0b 09 08 0b 08 08 ................ +| 3920: 0e 08 0a 08 0a 0a 08 08 0b 07 08 08 08 08 09 08 ................ +| 3936: 08 09 08 08 08 08 07 08 08 07 08 08 08 0a 08 08 ................ +| 3952: 08 07 07 0e 08 07 0a 0a 08 08 0a 06 08 0c 08 09 ................ +| 3968: 08 09 08 08 07 09 09 09 08 0b 08 0a 08 08 07 08 ................ +| 3984: 0a 08 08 09 07 08 07 0a 08 09 08 0a 08 08 08 08 ................ +| 4000: 09 08 08 07 08 0b 0a 08 09 09 08 08 08 08 08 09 ................ +| 4016: 09 09 08 09 08 0b 09 08 08 08 08 0a 08 07 09 06 ................ +| 4032: 07 0b 06 0c 0a 09 08 08 08 08 0b 09 09 09 08 08 ................ +| 4048: 09 08 09 08 08 09 07 09 08 09 07 08 07 07 09 09 ................ +| 4064: 09 0a 08 08 09 09 09 0c 09 09 0b 0b 09 08 09 0a ................ +| 4080: 0b 0b 08 0a 08 08 0b 09 09 0a 0a 09 0a 08 09 09 ................ +| page 26 offset 102400 +| 0: 0d 00 00 00 01 0e a7 00 0e a7 00 00 00 00 00 00 ................ +| 3744: 00 00 00 00 00 00 00 82 51 88 80 80 80 80 07 04 ........Q....... +| 3760: 00 85 26 00 00 01 2d 05 30 f4 a3 b5 8c 0a 02 1b ..&...-.0....... +| 3776: 02 03 b4 91 a9 02 04 82 1b 02 04 b7 b5 89 67 06 ..............g. +| 3792: 04 84 2f 01 04 f5 86 a9 bd 01 04 81 13 02 06 91 ../............. +| 3808: be ae ca 9e 78 04 04 81 1d 02 05 99 9b b2 6f 65 ....x.........oe +| 3824: 02 04 84 07 02 03 9e 99 8b 02 02 15 02 03 a1 ab ................ +| 3840: b6 02 04 83 0f 02 03 a3 96 8c 02 04 82 20 02 03 ............. .. +| 3856: ac a3 8d 08 02 55 02 03 b0 80 82 02 04 81 18 02 .....U.......... +| 3872: 03 b1 9c bb 08 02 67 02 03 b5 be 81 07 04 83 79 ......g........y +| 3888: 02 04 bf 81 9d 71 07 04 81 7c 01 04 f6 83 ab 80 .....q...|...... +| 3904: 01 04 81 2a 02 04 89 8c a8 61 01 04 82 30 02 03 ...*.....a...0.. +| 3920: 8e ae 81 07 04 84 05 02 04 8f bd b0 6a 09 04 82 ............j... +| 3936: 1e 02 04 92 95 9c 6c 07 04 82 64 03 02 b4 9f 09 ......l...d..... +| 3952: 04 83 65 02 04 9a a9 9f 34 06 04 83 70 02 03 a4 ..e.....4...p... +| 3968: ad af 01 02 02 02 03 b4 9a 90 07 04 83 5e 01 04 .............^.. +| 3984: f7 87 a5 9d 04 04 84 62 02 03 89 83 8e 0a 04 82 .......b........ +| 4000: 2e 02 03 98 bd 8e 01 04 84 06 02 03 99 a7 8e 02 ................ +| 4016: 04 83 7d 02 03 a0 a1 bb 01 04 81 4e 02 04 a8 86 ...........N.... +| 4032: b8 64 0a 02 34 02 03 b2 9a 9e 05 04 83 16 02 04 .d..4........... +| 4048: b4 a7 93 36 06 04 83 7d 03 02 ba 9c 02 04 83 07 ...6............ +| 4064: 04 09 09 0a 0a 0c 0b 08 09 09 08 09 08 09 0a 0a ................ +| 4080: 0a 09 0a 0a 08 0a 08 09 0a 09 09 09 09 09 09 0a ................ +| end x.db +}]} {} + +do_catchsql_test 74.1 { + SELECT rowid, quote(matchinfo(t1,'p�xyb... +| 2416: 17 01 05 05 34 74 61 62 6c 03 02 03 01 04 77 68 ....4tabl.....wh +| 2432: 65 72 03 02 06 09 1b 8c 80 80 80 80 0f 03 00 3c er.............< +| 2448: 00 00 00 16 05 34 66 74 73 34 03 02 02 01 04 6e .....4fts4.....n +| 2464: 75 6d 62 03 06 01 04 09 1b 8c 80 80 80 80 0e 03 umb............. +| 2480: 00 3c 00 00 00 16 04 33 74 68 65 03 06 01 01 04 .<.....3the..... +| 2496: 01 03 77 68 65 03 02 04 04 0a 1b 8c 80 80 80 80 ..whe........... +| 2512: 0d 03 00 3c 00 00 00 16 04 33 6e 75 6d 03 06 01 ...<.....3num... +| 2528: 01 05 01 03 74 61 62 03 02 03 04 0a 19 8c 80 80 ....tab......... +| 2544: 80 80 0c 03 00 38 00 00 00 14 03 32 77 68 03 02 .....8.....2wh.. +| 2560: 04 00 04 33 66 74 73 03 02 02 04 07 18 8c 80 80 ...3fts......... +| 2576: 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 03 02 .....6.....2ta.. +| 2592: 03 02 01 68 03 06 01 01 04 04 07 1b 8c 80 80 80 ...h............ +| 2608: 80 0a 03 00 3c 00 00 00 16 03 32 6e 75 03 06 01 ....<.....2nu... +| 2624: 01 05 01 02 6f 66 03 06 01 01 06 04 09 19 8c 80 ....of.......... +| 2640: 82 d0 80 09 03 00 38 00 00 00 14 03 32 66 74 03 ......8.....2ft. +| 2656: 02 02 01 02 69 73 03 06 01 01 03 04 07 18 8c 80 ....is.......... +| 2672: 80 80 80 08 03 00 36 00 00 00 13 02 31 74 03 08 ......6.....1t.. +| 2688: 03 01 01 04 01 01 77 03 02 04 04 09 1a 8c 80 80 ......w......... +| 2704: 80 80 07 03 00 3a 00 00 00 15 02 31 6e 03 08 01 .....:.....1n... +| 2720: 01 02 05 01 01 6f 03 06 01 01 06 04 09 18 8c 80 .....o.......... +| 2736: 81 80 80 06 03 00 36 00 00 00 13 04 02 31 66 03 ......6......1f. +| 2752: 02 02 01 01 69 03 06 01 01 03 05 06 1c 8c 80 80 ....i........... +| 2768: 80 80 05 03 00 3e 00 00 00 17 04 30 74 68 65 03 .....>.....0the. +| 2784: 06 01 01 04 01 05 77 68 65 72 65 03 02 04 0a 15 ......where..... +| 2800: 8c 80 80 80 80 04 03 00 30 00 00 00 11 01 01 06 ........0....... +| 2816: 06 30 74 61 62 6c 65 03 02 03 07 1c 8c 80 80 80 .0table......... +| 2832: 80 03 03 00 3e 00 00 00 17 07 30 6e 75 6d 62 65 ....>.....0numbe +| 2848: 72 03 06 01 01 05 01 02 6f 66 03 06 04 0d 13 8c r.......of...... +| 2864: 80 80 80 80 02 03 00 2c 00 00 00 0f fc 01 03 02 .......,........ +| 2880: 30 6e 03 06 01 00 f2 07 1b 8c 80 80 80 80 01 03 0n.............. +| 2896: 00 3c 00 00 00 16 08 30 66 74 73 34 61 75 78 03 .<.....0fts4aux. +| 2912: 02 02 01 02 69 73 03 06 04 0c 00 00 00 18 ea 00 ....is.......... +| 2928: 00 00 01 01 02 24 00 02 01 01 12 02 01 12 08 88 .....$.......... +| 2944: 80 80 80 80 12 03 00 12 10 00 00 05 02 1c 88 80 ................ +| 2960: 80 80 80 11 03 00 3e 00 00 00 17 05 34 72 6f 77 ......>.....4row +| 2976: 73 02 06 01 01 05 01 04 74 68 65 72 02 02 04 0b s.......ther.... +| 2992: 15 88 80 80 80 80 10 03 00 2f ff ff f0 11 02 01 ........./...... +| 3008: 01 07 05 34 62 65 74 77 02 02 04 08 1b 88 80 80 ...4betw........ +| 3024: 80 80 0f 03 00 3c 00 00 00 16 04 04 33 72 6f 77 .....<......3row +| 3040: 02 06 01 01 05 01 03 74 68 65 02 08 05 0a 1b 88 .......the...... +| 3056: 80 80 80 80 0d 03 00 3c 00 00 00 16 01 01 02 04 .......<........ +| 3072: 33 61 72 65 02 02 03 01 03 62 65 74 02 02 07 08 3are.....bet.... +| 3088: 1b 88 80 80 80 80 0d 03 00 3c 00 00 00 16 03 32 .........<.....2 +| 3104: 74 68 02 08 02 01 01 07 00 04 33 61 6e 64 02 06 th........3and.. +| 3120: 04 0a 1b 88 80 80 80 80 0c 03 00 3c 00 00 00 16 ...........<.... +| 3136: 03 32 69 6e 02 06 01 01 06 01 02 72 6f 02 06 01 .2in.......ro... +| 3152: 01 05 04 09 18 88 80 80 80 80 0b 03 00 36 00 00 .............6.. +| 3168: 00 13 02 03 32 61 72 02 02 03 01 02 62 65 02 02 ....2ar.....be.. +| 3184: 04 05 07 1b 88 80 80 80 80 0a 03 00 3c 00 00 00 ............<... +| 3200: 16 02 31 74 02 08 02 01 01 07 00 03 32 61 6e 02 ..1t........2an. +| 3216: 06 01 01 04 09 19 88 80 80 80 80 09 03 00 38 00 ..............8. +| 3232: 00 00 14 02 31 6e 02 06 01 01 03 01 01 72 02 06 ....1n.......r.. +| 3248: 01 01 05 04 08 17 88 80 80 80 80 08 03 00 34 00 ..............4. +| 3264: 01 00 12 02 31 62 02 02 04 01 01 69 02 06 01 01 ....1b.....i.... +| 3280: 06 04 06 19 88 80 80 80 80 07 03 00 38 00 00 00 ............8... +| 3296: 14 04 02 31 32 02 02 05 01 01 61 02 08 03 01 01 ...12.....a..... +| 3312: 02 05 06 1b 88 80 80 80 80 06 03 00 3c 00 00 00 ............<... +| 3328: 16 06 30 74 68 65 72 65 e7 02 02 00 02 31 31 02 ..0there.....11. +| 3344: 06 01 01 04 0a 15 88 80 80 80 80 05 03 00 30 00 ..............0. +| 3360: 00 00 11 01 01 05 04 30 74 68 65 02 06 01 01 07 .......0the..... +| 3376: 07 1c 88 80 80 80 80 04 03 00 3e 00 00 00 17 01 ..........>..... +| 3392: 01 06 02 30 6e 02 06 01 01 03 01 04 72 6f 77 73 ...0n.......rows +| 3408: 02 06 07 08 1b 88 80 80 80 80 03 03 00 3c 00 00 .............<.. +| 3424: 00 16 08 30 62 65 74 77 65 65 6e 02 02 04 01 02 ...0between..... +| 3440: 69 6e 02 06 04 0c 1a 88 80 80 80 80 02 03 00 3a in.............: +| 3456: 00 00 00 15 04 30 61 6e 64 02 06 01 01 02 02 02 .....0and....... +| 3472: 72 65 02 02 03 04 0a 17 88 80 80 80 80 01 03 00 re.............. +| 3488: 34 01 00 00 12 02 30 31 02 06 01 01 04 01 01 32 4.....01.......2 +| 3504: 02 02 05 04 08 08 84 80 80 80 80 12 03 00 16 00 ................ +| 3520: 00 00 05 04 1b 84 80 80 80 80 11 03 00 3c 00 00 .............<.. +| 3536: 00 16 05 34 74 61 62 6c 01 06 01 01 05 02 03 65 ...4tabl.......e +| 3552: 72 6d 01 02 04 0b 1b 84 80 80 80 80 10 03 00 3c rm.............< +| 3568: 00 00 00 16 05 34 65 61 63 68 01 02 02 01 04 70 .....4each.....p +| 3584: 72 65 73 01 02 05 04 09 1a 84 80 80 80 80 0f 03 res............. +| 3600: 00 3a 00 00 00 15 04 33 74 65 72 01 02 04 02 02 .:.....3ter..... +| 3616: 68 65 01 06 01 01 03 04 08 1b 84 80 80 80 80 0e he.............. +| 3632: 03 00 3c 00 00 00 16 04 33 70 72 65 01 02 05 01 ..<.....3pre.... +| 3648: 03 74 61 62 01 06 01 01 05 04 08 1a 84 80 80 80 .tab............ +| 3664: 80 0d 03 00 3a 00 00 00 15 04 33 66 6f 72 01 02 ....:.....3for.. +| 3680: 02 02 02 74 73 01 06 01 01 04 04 08 1b 84 80 80 ...ts........... +| 3696: 80 80 0c 03 00 3c 00 00 00 16 03 32 74 68 01 06 .....<.....2th.. +| 3712: 01 01 03 00 04 33 65 61 63 01 02 03 04 09 18 84 .....3eac....... +| 3728: 80 80 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 .......6.....2ta +| 3744: 01 06 01 01 05 02 01 65 01 02 04 04 09 19 84 80 .......e........ +| 3760: 80 80 80 0a 03 00 38 00 00 00 14 03 32 69 6e 01 ......8.....2in. +| 3776: 06 01 01 02 01 02 70 72 01 02 05 04 09 18 84 80 ......pr........ +| 3792: 80 80 80 09 03 00 36 00 00 00 13 03 32 66 6f 01 ......6.....2fo. +| 3808: 02 02 02 01 74 01 06 01 01 04 04 07 1b 84 80 80 ....t........... +| 3824: 80 80 08 03 00 3c 00 00 00 16 02 31 74 01 0a 04 .....<.....1t... +| 3840: 01 01 03 04 00 03 32 65 61 01 02 03 04 0a 17 84 ......2ea....... +| 3856: 80 80 80 80 07 03 00 34 00 00 00 12 02 31 69 01 .......4.....1i. +| 3872: 06 01 01 02 01 01 70 01 02 05 04 08 18 84 80 80 ......p......... +| 3888: 80 80 06 03 00 36 00 00 00 13 02 31 65 01 02 03 .....6.....1e... +| 3904: 01 01 66 01 08 02 01 01 04 04 06 1b 84 80 80 80 ..f............. +| 3920: 80 05 03 00 3c 00 00 00 16 05 30 74 65 72 6d 01 ....<.....0term. +| 3936: 02 04 02 02 68 65 01 06 01 01 03 04 09 14 84 80 ....he.......... +| 3952: 80 80 80 04 03 00 2e 00 00 00 10 06 30 74 61 62 ............0tab +| 3968: 6c 65 01 06 01 01 05 04 15 84 7f 80 80 80 03 03 le.............. +| 3984: 00 30 00 00 00 11 02 08 30 70 72 65 73 65 6e 74 .0......0present +| 4000: 01 02 05 05 1b 84 80 80 80 80 02 03 00 3c 00 00 .............<.. +| 4016: 00 16 04 30 66 74 73 01 06 01 01 04 01 02 69 6e ...0fts.......in +| 4032: 01 06 01 01 04 0a 1a 84 80 80 80 80 01 03 00 3a ...............: +| 4048: 00 00 00 15 05 30 65 61 63 68 01 02 03 01 03 66 .....0each.....f +| 4064: 6f 72 01 02 02 04 09 06 01 03 00 12 03 0b 0f 00 or.............. +| 4080: 00 08 8c 80 80 80 80 11 03 00 16 00 00 00 05 04 ................ +| page 3 offset 8192 +| 0: 0a 00 00 00 32 0e 4f 00 0f fa 0f f1 0f e9 0f e1 ....2.O......... +| 16: 0f d8 0f d1 0f c9 0f c1 0f b9 0f b1 0f a9 0f a0 ................ +| 32: 0f 98 0f 90 0f 87 0f 80 0f 78 0f 71 0f 68 0f 5f .........x.q.h._ +| 48: 0f 56 0f 4d 0f 41 0f 38 0f 2f 0f 26 0f 1d 0f 03 .V.M.A.8./.&.... +| 64: 0f 0a 0f 00 9e f7 0e ee 0e e6 0e dd 0e d6 0e cd ................ +| 80: 0e c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3648: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 ................ +| 3664: 04 01 10 01 03 34 74 20 07 04 01 0e 01 03 34 1e .....4t ......4. +| 3680: 09 04 01 12 01 03 33 74 68 1c 08 04 01 10 01 03 ......3th....... +| 3696: 33 6e 1a 08 04 01 10 01 03 32 77 18 08 04 01 10 3n.......2w..... +| 3712: 01 03 32 74 16 08 04 01 10 01 03 32 6e 14 07 04 ..2t.......2n... +| 3728: 01 0e 01 03 32 12 08 04 01 10 01 03 31 74 10 08 ....2.......1t.. +| 3744: 04 01 10 01 03 31 6e 0e 07 04 01 0e 01 03 31 0c .....1n.......1. +| 3760: 09 04 01 12 01 03 30 74 68 0a 08 04 01 10 01 03 ......0th....... +| 3776: 30 74 08 09 04 01 12 01 03 30 6e 75 06 08 04 01 0t.......0nu.... +| 3792: 10 01 03 30 6e 04 06 04 01 0c 01 03 02 08 04 01 ...0n........... +| 3808: 10 01 02 34 72 22 07 04 01 0e 01 02 34 20 08 04 ...4r.......4 .. +| 3824: 01 10 01 02 33 72 1e 09 04 01 12 01 02 33 61 72 ....3r.......3ar +| 3840: 1c 18 04 01 10 01 02 32 74 1a 08 04 01 10 01 02 .......2t....... +| 3856: 32 69 18 09 04 01 12 01 02 32 61 72 16 08 04 01 2i.......2ar.... +| 3872: 10 01 02 31 74 14 08 04 01 0f 01 02 31 6e 12 0a ...1t.......1n.. +| 3888: d4 01 10 01 02 31 62 10 08 04 01 10 01 02 31 32 .....1b.......12 +| 3904: 0e 0b 04 01 16 01 02 30 74 68 65 72 0c 08 04 01 .......0ther.... +| 3920: 10 01 02 30 74 0a 08 04 01 10 01 02 30 6e 08 08 ...0t.......0n.. +| 3936: 04 01 10 01 02 30 62 06 08 04 01 10 01 02 30 61 .....0b.......0a +| 3952: 04 06 04 01 0c 01 02 02 07 04 09 10 01 34 74 22 .............4t. +| 3968: 06 04 09 0e 01 34 20 08 04 09 12 01 33 74 65 1e .....4 .....3te. +| 3984: 07 04 09 10 01 33 70 1c 07 04 09 10 01 33 66 1a .....3p......3f. +| 4000: 08 04 09 12 01 32 74 68 18 07 04 09 10 01 32 74 .....2th......2t +| 4016: 16 07 04 09 10 01 32 69 14 07 04 09 10 01 32 66 ......2i......2f +| 4032: 12 07 04 09 10 01 31 74 10 07 04 09 10 01 31 69 ......1t......1i +| 4048: 0e 06 04 09 0e 01 31 0c 08 04 09 12 01 30 74 65 ......1......0te +| 4064: 0a 07 04 09 10 01 30 74 08 07 04 09 10 01 30 70 ......0t......0p +| 4080: 06 08 04 09 1e 61 30 66 74 04 05 04 09 0c 01 02 .....a0ft....... +| page 4 offset 12288 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 05 03 03 00 10 ................ +| 4080: 03 05 05 02 03 00 10 04 06 05 01 03 00 10 04 04 ................ +| page 5 offset 16384 +| 0: 0a 00 00 00 02 0f eb 00 0f eb 0f f4 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 08 03 15 01 70 ...............p +| 4080: 67 73 7a 18 0b 03 1b 01 76 65 72 73 69 6f 6e 04 gsz.....version. +| page 6 offset 20480 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4080: 00 00 03 03 02 01 da 03 02 02 01 02 00 00 00 00 ................ +| end crash-53216796d7c3d8.db +}]} {} + +do_catchsql_test 77.1 { + UPDATE t1 SET B =quote(zeroblob(200)) WHERE b MATCH 'threa*thrad*tlrad*the�d'; +} {1 {database disk image is malformed}} sqlite3_fts5_may_be_corrupt 0 finish_test diff --git a/ext/fts5/test/fts5corrupt4.test b/ext/fts5/test/fts5corrupt4.test index dc9ca661d..b31f4d96e 100644 --- a/ext/fts5/test/fts5corrupt4.test +++ b/ext/fts5/test/fts5corrupt4.test @@ -29,6 +29,8 @@ do_execsql_test 1.0 { INSERT INTO ttt SELECT a||a, b||b FROM ttt; } +expr srand(1) + proc mutate {blob i} { set o [expr {$i % [string length $blob]}] set a [string range $blob 0 $o-1] diff --git a/ext/fts5/test/fts5delete.test b/ext/fts5/test/fts5delete.test index 467e2e570..1214fec4f 100644 --- a/ext/fts5/test/fts5delete.test +++ b/ext/fts5/test/fts5delete.test @@ -91,6 +91,27 @@ do_catchsql_test 2.4 { SELECT rowid FROM test_idx WHERE test_idx MATCH 'two' ORDER BY rank; } {1 {database disk image is malformed}} - +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE tx USING fts5(a, b, c, d, content=); + INSERT INTO tx(rowid, a, c) VALUES(1, 'abc def', 'a b c'); + INSERT INTO tx(rowid, a, c) VALUES(5, 'a b c', 'a b d def'); +} +do_execsql_test 3.1 { + INSERT INTO tx(tx, rowid, a, b, c, d) + VALUES('delete', 5, 'a b c', NULL, 'a b d def', NULL); +} +do_execsql_test 3.2 { + INSERT INTO tx(tx) VALUES('integrity-check'); +} +do_execsql_test 3.3 { + INSERT INTO tx(tx, rowid, a, b, c, d) + VALUES('delete', 1, 'abc def', NULL, 'a b c', NULL); +} +do_execsql_test 3.4 { + INSERT INTO tx(tx) VALUES('integrity-check'); +} finish_test + diff --git a/ext/fts5/test/fts5doclist.test b/ext/fts5/test/fts5doclist.test index d8308fd0f..08b773f6f 100644 --- a/ext/fts5/test/fts5doclist.test +++ b/ext/fts5/test/fts5doclist.test @@ -42,5 +42,26 @@ do_execsql_test 1.2 { INSERT INTO ccc(ccc) VALUES('integrity-check'); } +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 2.1 { + CREATE VIRTUAL TABLE tx USING fts5(x); +} + +set doc [string repeat "abc " 5000] +do_execsql_test 2.2 { + BEGIN; + INSERT INTO tx(rowid, x) VALUES(-9000000000000000000, $doc); + INSERT INTO tx(rowid, x) VALUES(9000000000000000000, $doc); + COMMIT; +} + +do_execsql_test 2.3 { + SELECT rowid FROM tx('abc'); +} { + -9000000000000000000 + 9000000000000000000 +} finish_test diff --git a/ext/fts5/test/fts5plan.test b/ext/fts5/test/fts5plan.test index 7118427a2..6862acf17 100644 --- a/ext/fts5/test/fts5plan.test +++ b/ext/fts5/test/fts5plan.test @@ -30,23 +30,23 @@ do_eqp_test 1.1 { SELECT * FROM t1, f1 WHERE f1 MATCH t1.x } { QUERY PLAN - |--SCAN TABLE t1 - `--SCAN TABLE f1 VIRTUAL TABLE INDEX 0:M1 + |--SCAN t1 + `--SCAN f1 VIRTUAL TABLE INDEX 0:M1 } do_eqp_test 1.2 { SELECT * FROM t1, f1 WHERE f1 > t1.x } { QUERY PLAN - |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0: - `--SCAN TABLE t1 + |--SCAN f1 VIRTUAL TABLE INDEX 0: + `--SCAN t1 } do_eqp_test 1.3 { SELECT * FROM f1 WHERE f1 MATCH ? ORDER BY ff } { QUERY PLAN - |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0:M1 + |--SCAN f1 VIRTUAL TABLE INDEX 0:M1 `--USE TEMP B-TREE FOR ORDER BY } @@ -54,12 +54,12 @@ do_eqp_test 1.4 { SELECT * FROM f1 ORDER BY rank } { QUERY PLAN - |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0: + |--SCAN f1 VIRTUAL TABLE INDEX 0: `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 1.5 { SELECT * FROM f1 WHERE rank MATCH ? -} {SCAN TABLE f1 VIRTUAL TABLE INDEX 0:r} +} {SCAN f1 VIRTUAL TABLE INDEX 0:r} finish_test diff --git a/ext/fts5/test/fts5prefix2.test b/ext/fts5/test/fts5prefix2.test new file mode 100644 index 000000000..bf16e81a7 --- /dev/null +++ b/ext/fts5/test/fts5prefix2.test @@ -0,0 +1,57 @@ +# 2020 Dec 3 +# +# 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 focused on prefix indexes. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5prefix2 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +foreach p {3 2 1} { + reset_db + do_execsql_test 1.$p.0 " + CREATE VIRTUAL TABLE t1 USING fts5(xyz, prefix=$p); + " + do_execsql_test 1.$p.1 { + INSERT INTO t1 VALUES + ('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 f.'); + } + + do_execsql_test 1.$p.2 { + SELECT highlight(t1, 0, '[', ']') FROM t1('f*'); + } { + {May you [find] [forgiveness] [for] yourself and [forgive] others.} + {May you share [freely], never taking more than you give [f].} + } +} + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t2 USING fts5(one, prefix=3); + INSERT INTO t2 VALUES('top'); + INSERT INTO t2 VALUES('to'); + INSERT INTO t2 VALUES('tommy'); +} + +do_execsql_test 2.1 { + SELECT * FROM t2('to*'); +} {top to tommy} + + + +finish_test diff --git a/ext/fts5/test/fts5trigram.test b/ext/fts5/test/fts5trigram.test index 26eb7ba8d..fb66efed6 100644 --- a/ext/fts5/test/fts5trigram.test +++ b/ext/fts5/test/fts5trigram.test @@ -192,10 +192,9 @@ do_eqp_test 6.2 { } {VIRTUAL TABLE INDEX 0:G0} do_eqp_test 6.3 { SELECT * FROM ci1 WHERE x LIKE ? -} {{SCAN TABLE ci1 VIRTUAL TABLE INDEX 0:}} +} {{SCAN ci1 VIRTUAL TABLE INDEX 0:}} do_eqp_test 6.4 { SELECT * FROM ci1 WHERE x GLOB ? } {VIRTUAL TABLE INDEX 0:G0} finish_test - diff --git a/ext/misc/appendvfs.c b/ext/misc/appendvfs.c index 14260efb5..eb9ed01f6 100644 --- a/ext/misc/appendvfs.c +++ b/ext/misc/appendvfs.c @@ -14,24 +14,23 @@ ** appended onto the end of some other file, such as an executable. ** ** A special record must appear at the end of the file that identifies the -** file as an appended database and provides an offset to page 1. For -** best performance page 1 should be located at a disk page boundary, though -** that is not required. +** file as an appended database and provides the offset to the first page +** of the exposed content. (Or, it is the length of the content prefix.) +** For best performance page 1 should be located at a disk page boundary, +** though that is not required. ** ** When opening a database using this VFS, the connection might treat -** the file as an ordinary SQLite database, or it might treat is as a -** database appended onto some other file. Here are the rules: +** the file as an ordinary SQLite database, or it might treat it as a +** database appended onto some other file. The decision is made by +** applying the following rules in order: ** -** (1) When opening a new empty file, that file is treated as an ordinary -** database. +** (1) An empty file is an ordinary database. ** -** (2) When opening a file that begins with the standard SQLite prefix -** string "SQLite format 3", that file is treated as an ordinary -** database. +** (2) If the file ends with the appendvfs trailer string +** "Start-Of-SQLite3-NNNNNNNN" that file is an appended database. ** -** (3) When opening a file that ends with the appendvfs trailer string -** "Start-Of-SQLite3-NNNNNNNN" that file is treated as an appended -** database. +** (3) If the file begins with the standard SQLite prefix string +** "SQLite format 3", that file is an ordinary database. ** ** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is ** set, then a new database is appended to the already existing file. @@ -39,13 +38,13 @@ ** (5) Otherwise, SQLITE_CANTOPEN is returned. ** ** To avoid unnecessary complications with the PENDING_BYTE, the size of -** the file containing the database is limited to 1GB. This VFS will refuse -** to read or write past the 1GB mark. This restriction might be lifted in -** future versions. For now, if you need a large database, then keep the -** database in a separate file. +** the file containing the database is limited to 1GiB. (1073741824 bytes) +** This VFS will not read or write past the 1GiB mark. This restriction +** might be lifted in future versions. For now, if you need a larger +** database, then keep it in a separate file. ** -** If the file being opened is not an appended database, then this shim is -** a pass-through into the default underlying VFS. +** If the file being opened is a plain database (not an appended one), then +** this shim is a pass-through into the default underlying VFS. (rule 3) **/ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 @@ -58,17 +57,27 @@ SQLITE_EXTENSION_INIT1 ** 123456789 123456789 12345 ** ** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is -** the offset to page 1. +** the offset to page 1, and also the length of the prefix content. */ #define APND_MARK_PREFIX "Start-Of-SQLite3-" #define APND_MARK_PREFIX_SZ 17 -#define APND_MARK_SIZE 25 +#define APND_MARK_FOS_SZ 8 +#define APND_MARK_SIZE (APND_MARK_PREFIX_SZ+APND_MARK_FOS_SZ) /* ** Maximum size of the combined prefix + database + append-mark. This ** must be less than 0x40000000 to avoid locking issues on Windows. */ -#define APND_MAX_SIZE (65536*15259) +#define APND_MAX_SIZE (0x40000000) + +/* +** Try to align the database to an even multiple of APND_ROUNDUP bytes. +*/ +#ifndef APND_ROUNDUP +#define APND_ROUNDUP 4096 +#endif +#define APND_ALIGN_MASK ((sqlite3_int64)(APND_ROUNDUP-1)) +#define APND_START_ROUNDUP(fsz) (((fsz)+APND_ALIGN_MASK) & ~APND_ALIGN_MASK) /* ** Forward declaration of objects used by this utility @@ -82,11 +91,45 @@ typedef struct ApndFile ApndFile; #define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) #define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1)) -/* An open file */ +/* An open appendvfs file +** +** An instance of this structure describes the appended database file. +** A separate sqlite3_file object is always appended. The appended +** sqlite3_file object (which can be accessed using ORIGFILE()) describes +** the entire file, including the prefix, the database, and the +** append-mark. +** +** The structure of an AppendVFS database is like this: +** +** +-------------+---------+----------+-------------+ +** | prefix-file | padding | database | append-mark | +** +-------------+---------+----------+-------------+ +** ^ ^ +** | | +** iPgOne iMark +** +** +** "prefix file" - file onto which the database has been appended. +** "padding" - zero or more bytes inserted so that "database" +** starts on an APND_ROUNDUP boundary +** "database" - The SQLite database file +** "append-mark" - The 25-byte "Start-Of-SQLite3-NNNNNNNN" that indicates +** the offset from the start of prefix-file to the start +** of "database". +** +** The size of the database is iMark - iPgOne. +** +** The NNNNNNNN in the "Start-Of-SQLite3-NNNNNNNN" suffix is the value +** of iPgOne stored as a big-ending 64-bit integer. +** +** iMark will be the size of the underlying file minus 25 (APND_MARKSIZE). +** Or, iMark is -1 to indicate that it has not yet been written. +*/ struct ApndFile { - sqlite3_file base; /* IO methods */ - sqlite3_int64 iPgOne; /* File offset to page 1 */ - sqlite3_int64 iMark; /* Start of the append-mark */ + sqlite3_file base; /* Subclass. MUST BE FIRST! */ + sqlite3_int64 iPgOne; /* Offset to the start of the database */ + sqlite3_int64 iMark; /* Offset of the append mark. -1 if unwritten */ + /* Always followed by another sqlite3_file that describes the whole file */ }; /* @@ -178,8 +221,6 @@ static const sqlite3_io_methods apnd_io_methods = { apndUnfetch /* xUnfetch */ }; - - /* ** Close an apnd-file. */ @@ -197,22 +238,37 @@ static int apndRead( int iAmt, sqlite_int64 iOfst ){ - ApndFile *p = (ApndFile *)pFile; + ApndFile *paf = (ApndFile *)pFile; pFile = ORIGFILE(pFile); - return pFile->pMethods->xRead(pFile, zBuf, iAmt, iOfst+p->iPgOne); + return pFile->pMethods->xRead(pFile, zBuf, iAmt, paf->iPgOne+iOfst); } /* -** Add the append-mark onto the end of the file. +** Add the append-mark onto what should become the end of the file. +* If and only if this succeeds, internal ApndFile.iMark is updated. +* Parameter iWriteEnd is the appendvfs-relative offset of the new mark. */ -static int apndWriteMark(ApndFile *p, sqlite3_file *pFile){ - int i; +static int apndWriteMark( + ApndFile *paf, + sqlite3_file *pFile, + sqlite_int64 iWriteEnd +){ + sqlite_int64 iPgOne = paf->iPgOne; unsigned char a[APND_MARK_SIZE]; + int i = APND_MARK_FOS_SZ; + int rc; + assert(pFile == ORIGFILE(paf)); memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ); - for(i=0; i<8; i++){ - a[APND_MARK_PREFIX_SZ+i] = (p->iPgOne >> (56 - i*8)) & 0xff; + while( --i >= 0 ){ + a[APND_MARK_PREFIX_SZ+i] = (unsigned char)(iPgOne & 0xff); + iPgOne >>= 8; + } + iWriteEnd += paf->iPgOne; + if( SQLITE_OK==(rc = pFile->pMethods->xWrite + (pFile, a, APND_MARK_SIZE, iWriteEnd)) ){ + paf->iMark = iWriteEnd; } - return pFile->pMethods->xWrite(pFile, a, APND_MARK_SIZE, p->iMark); + return rc; } /* @@ -224,38 +280,28 @@ static int apndWrite( int iAmt, sqlite_int64 iOfst ){ - int rc; - ApndFile *p = (ApndFile *)pFile; + ApndFile *paf = (ApndFile *)pFile; + sqlite_int64 iWriteEnd = iOfst + iAmt; + if( iWriteEnd>=APND_MAX_SIZE ) return SQLITE_FULL; pFile = ORIGFILE(pFile); - if( iOfst+iAmt>=APND_MAX_SIZE ) return SQLITE_FULL; - rc = pFile->pMethods->xWrite(pFile, zBuf, iAmt, iOfst+p->iPgOne); - if( rc==SQLITE_OK && iOfst + iAmt + p->iPgOne > p->iMark ){ - sqlite3_int64 sz = 0; - rc = pFile->pMethods->xFileSize(pFile, &sz); - if( rc==SQLITE_OK ){ - p->iMark = sz - APND_MARK_SIZE; - if( iOfst + iAmt + p->iPgOne > p->iMark ){ - p->iMark = p->iPgOne + iOfst + iAmt; - rc = apndWriteMark(p, pFile); - } - } + /* If append-mark is absent or will be overwritten, write it. */ + if( paf->iMark < 0 || paf->iPgOne + iWriteEnd > paf->iMark ){ + int rc = apndWriteMark(paf, pFile, iWriteEnd); + if( SQLITE_OK!=rc ) return rc; } - return rc; + return pFile->pMethods->xWrite(pFile, zBuf, iAmt, paf->iPgOne+iOfst); } /* ** Truncate an apnd-file. */ static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){ - int rc; - ApndFile *p = (ApndFile *)pFile; + ApndFile *paf = (ApndFile *)pFile; pFile = ORIGFILE(pFile); - rc = pFile->pMethods->xTruncate(pFile, size+p->iPgOne+APND_MARK_SIZE); - if( rc==SQLITE_OK ){ - p->iMark = p->iPgOne+size; - rc = apndWriteMark(p, pFile); - } - return rc; + /* The append mark goes out first so truncate failure does not lose it. */ + if( SQLITE_OK!=apndWriteMark(paf, pFile, size) ) return SQLITE_IOERR; + /* Truncate underlying file just past append mark */ + return pFile->pMethods->xTruncate(pFile, paf->iMark+APND_MARK_SIZE); } /* @@ -268,16 +314,12 @@ static int apndSync(sqlite3_file *pFile, int flags){ /* ** Return the current file-size of an apnd-file. +** If the append mark is not yet there, the file-size is 0. */ static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ - ApndFile *p = (ApndFile *)pFile; - int rc; - pFile = ORIGFILE(p); - rc = pFile->pMethods->xFileSize(pFile, pSize); - if( rc==SQLITE_OK && p->iPgOne ){ - *pSize -= p->iPgOne + APND_MARK_SIZE; - } - return rc; + ApndFile *paf = (ApndFile *)pFile; + *pSize = ( paf->iMark >= 0 )? (paf->iMark - paf->iPgOne) : 0; + return SQLITE_OK; } /* @@ -308,12 +350,13 @@ static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){ ** File control method. For custom operations on an apnd-file. */ static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){ - ApndFile *p = (ApndFile *)pFile; + ApndFile *paf = (ApndFile *)pFile; int rc; pFile = ORIGFILE(pFile); + if( op==SQLITE_FCNTL_SIZE_HINT ) *(sqlite3_int64*)pArg += paf->iPgOne; rc = pFile->pMethods->xFileControl(pFile, op, pArg); if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ - *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", p->iPgOne, *(char**)pArg); + *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", paf->iPgOne,*(char**)pArg); } return rc; } @@ -372,6 +415,9 @@ static int apndFetch( void **pp ){ ApndFile *p = (ApndFile *)pFile; + if( p->iMark < 0 || iOfst+iAmt > p->iMark ){ + return SQLITE_IOERR; /* Cannot read what is not yet there. */ + } pFile = ORIGFILE(pFile); return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp); } @@ -383,95 +429,155 @@ static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, pPage); } -/* -** Check to see if the file is an ordinary SQLite database file. -*/ -static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){ - int rc; - char zHdr[16]; - static const char aSqliteHdr[] = "SQLite format 3"; - if( sz<512 ) return 0; - rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0); - if( rc ) return 0; - return memcmp(zHdr, aSqliteHdr, sizeof(zHdr))==0; -} - /* ** Try to read the append-mark off the end of a file. Return the -** start of the appended database if the append-mark is present. If -** there is no append-mark, return -1; +** start of the appended database if the append-mark is present. +** If there is no valid append-mark, return -1; +** +** An append-mark is only valid if the NNNNNNNN start-of-database offset +** indicates that the appended database contains at least one page. The +** start-of-database value must be a multiple of 512. */ static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){ int rc, i; sqlite3_int64 iMark; + int msbs = 8 * (APND_MARK_FOS_SZ-1); unsigned char a[APND_MARK_SIZE]; - if( sz<=APND_MARK_SIZE ) return -1; + if( APND_MARK_SIZE!=(sz & 0x1ff) ) return -1; rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE); if( rc ) return -1; if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1; - iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ]&0x7f))<<56; - for(i=1; i<8; i++){ - iMark += (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<(56-8*i); + iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ] & 0x7f)) << msbs; + for(i=1; i<8; i++){ + msbs -= 8; + iMark |= (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]< (sz - APND_MARK_SIZE - 512) ) return -1; + if( iMark & 0x1ff ) return -1; return iMark; } +static const char apvfsSqliteHdr[] = "SQLite format 3"; +/* +** Check to see if the file is an appendvfs SQLite database file. +** Return true iff it is such. Parameter sz is the file's size. +*/ +static int apndIsAppendvfsDatabase(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc; + char zHdr[16]; + sqlite3_int64 iMark = apndReadMark(sz, pFile); + if( iMark>=0 ){ + /* If file has the correct end-marker, the expected odd size, and the + ** SQLite DB type marker where the end-marker puts it, then it + ** is an appendvfs database. + */ + rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), iMark); + if( SQLITE_OK==rc + && memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0 + && (sz & 0x1ff) == APND_MARK_SIZE + && sz>=512+APND_MARK_SIZE + ){ + return 1; /* It's an appendvfs database */ + } + } + return 0; +} + +/* +** Check to see if the file is an ordinary SQLite database file. +** Return true iff so. Parameter sz is the file's size. +*/ +static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){ + char zHdr[16]; + if( apndIsAppendvfsDatabase(sz, pFile) /* rule 2 */ + || (sz & 0x1ff) != 0 + || SQLITE_OK!=pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0) + || memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))!=0 + ){ + return 0; + }else{ + return 1; + } +} + /* ** Open an apnd file handle. */ static int apndOpen( - sqlite3_vfs *pVfs, + sqlite3_vfs *pApndVfs, const char *zName, sqlite3_file *pFile, int flags, int *pOutFlags ){ - ApndFile *p; - sqlite3_file *pSubFile; - sqlite3_vfs *pSubVfs; + ApndFile *pApndFile = (ApndFile*)pFile; + sqlite3_file *pBaseFile = ORIGFILE(pFile); + sqlite3_vfs *pBaseVfs = ORIGVFS(pApndVfs); int rc; - sqlite3_int64 sz; - pSubVfs = ORIGVFS(pVfs); + sqlite3_int64 sz = 0; if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ - return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags); + /* The appendvfs is not to be used for transient or temporary databases. + ** Just use the base VFS open to initialize the given file object and + ** open the underlying file. (Appendvfs is then unused for this file.) + */ + return pBaseVfs->xOpen(pBaseVfs, zName, pFile, flags, pOutFlags); } - p = (ApndFile*)pFile; - memset(p, 0, sizeof(*p)); - pSubFile = ORIGFILE(pFile); + memset(pApndFile, 0, sizeof(ApndFile)); pFile->pMethods = &apnd_io_methods; - rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags); - if( rc ) goto apnd_open_done; - rc = pSubFile->pMethods->xFileSize(pSubFile, &sz); + pApndFile->iMark = -1; /* Append mark not yet written */ + + rc = pBaseVfs->xOpen(pBaseVfs, zName, pBaseFile, flags, pOutFlags); + if( rc==SQLITE_OK ){ + rc = pBaseFile->pMethods->xFileSize(pBaseFile, &sz); + if( rc ){ + pBaseFile->pMethods->xClose(pBaseFile); + } + } if( rc ){ - pSubFile->pMethods->xClose(pSubFile); - goto apnd_open_done; + pFile->pMethods = 0; + return rc; } - if( apndIsOrdinaryDatabaseFile(sz, pSubFile) ){ - memmove(pFile, pSubFile, pSubVfs->szOsFile); + if( apndIsOrdinaryDatabaseFile(sz, pBaseFile) ){ + /* The file being opened appears to be just an ordinary DB. Copy + ** the base dispatch-table so this instance mimics the base VFS. + */ + memmove(pApndFile, pBaseFile, pBaseVfs->szOsFile); return SQLITE_OK; } - p->iMark = 0; - p->iPgOne = apndReadMark(sz, pFile); - if( p->iPgOne>0 ){ + pApndFile->iPgOne = apndReadMark(sz, pFile); + if( pApndFile->iPgOne>=0 ){ + pApndFile->iMark = sz - APND_MARK_SIZE; /* Append mark found */ return SQLITE_OK; } if( (flags & SQLITE_OPEN_CREATE)==0 ){ - pSubFile->pMethods->xClose(pSubFile); + pBaseFile->pMethods->xClose(pBaseFile); rc = SQLITE_CANTOPEN; + pFile->pMethods = 0; + }else{ + /* Round newly added appendvfs location to #define'd page boundary. + ** Note that nothing has yet been written to the underlying file. + ** The append mark will be written along with first content write. + ** Until then, paf->iMark value indicates it is not yet written. + */ + pApndFile->iPgOne = APND_START_ROUNDUP(sz); } - p->iPgOne = (sz+0xfff) & ~(sqlite3_int64)0xfff; -apnd_open_done: - if( rc ) pFile->pMethods = 0; return rc; } /* -** All other VFS methods are pass-thrus. +** Delete an apnd file. +** For an appendvfs, this could mean delete the appendvfs portion, +** leaving the appendee as it was before it gained an appendvfs. +** For now, this code deletes the underlying file too. */ static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync); } + +/* +** All other VFS methods are pass-thrus. +*/ static int apndAccess( sqlite3_vfs *pVfs, const char *zPath, @@ -551,6 +657,7 @@ int sqlite3_appendvfs_init( (void)pzErrMsg; (void)db; pOrig = sqlite3_vfs_find(0); + if( pOrig==0 ) return SQLITE_ERROR; apnd_vfs.iVersion = pOrig->iVersion; apnd_vfs.pAppData = pOrig; apnd_vfs.szOsFile = pOrig->szOsFile + sizeof(ApndFile); diff --git a/ext/misc/cksumvfs.c b/ext/misc/cksumvfs.c index a0888c002..8c340889f 100644 --- a/ext/misc/cksumvfs.c +++ b/ext/misc/cksumvfs.c @@ -356,6 +356,41 @@ static void cksmVerifyFunc( sqlite3_result_int(context, memcmp(data+nByte-8,cksum,8)==0); } +#ifdef SQLITE_CKSUMVFS_INIT_FUNCNAME +/* +** SQL function: initialize_cksumvfs(SCHEMANAME) +** +** This SQL functions (whose name is actually determined at compile-time +** by the value of the SQLITE_CKSUMVFS_INIT_FUNCNAME macro) invokes: +** +** sqlite3_file_control(db, SCHEMANAME, SQLITE_FCNTL_RESERVE_BYTE, &n); +** +** In order to set the reserve bytes value to 8, so that cksumvfs will +** operation. This feature is provided (if and only if the +** SQLITE_CKSUMVFS_INIT_FUNCNAME compile-time option is set to a string +** which is the name of the SQL function) so as to provide the ability +** to invoke the file-control in programming languages that lack +** direct access to the sqlite3_file_control() interface (ex: Java). +** +** This interface is undocumented, apart from this comment. Usage +** example: +** +** 1. Compile with -DSQLITE_CKSUMVFS_INIT_FUNCNAME="ckvfs_init" +** 2. Run: "SELECT cksum_init('main'); VACUUM;" +*/ +static void cksmInitFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int nByte = 8; + const char *zSchemaName = (const char*)sqlite3_value_text(argv[0]); + sqlite3 *db = sqlite3_context_db_handle(context); + sqlite3_file_control(db, zSchemaName, SQLITE_FCNTL_RESERVE_BYTES, &nByte); + /* Return NULL */ +} +#endif /* SQLITE_CKSUMBFS_INIT_FUNCNAME */ + /* ** Close a cksm-file. */ @@ -544,6 +579,18 @@ static int cksmFileControl(sqlite3_file *pFile, int op, void *pArg){ }else if( op==SQLITE_FCNTL_CKPT_START || op==SQLITE_FCNTL_CKPT_DONE ){ p->inCkpt = op==SQLITE_FCNTL_CKPT_START; if( p->pPartner ) p->pPartner->inCkpt = p->inCkpt; + }else if( op==SQLITE_FCNTL_CKSM_FILE ){ + /* This VFS needs to obtain a pointer to the corresponding database + ** file handle from within xOpen() calls to open wal files. To do this, + ** it uses the sqlite3_database_file_object() API to obtain a pointer + ** to the file-handle used by SQLite to access the db file. This is + ** fine if cksmvfs happens to be the top-level VFS, but not if there + ** are one or more wrapper VFS. To handle this case, this file-control + ** is used to extract the cksmvfs file-handle from any wrapper file + ** handle. */ + sqlite3_file **ppFile = (sqlite3_file**)pArg; + *ppFile = (sqlite3_file*)p; + return SQLITE_OK; } rc = pFile->pMethods->xFileControl(pFile, op, pArg); if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ @@ -653,6 +700,8 @@ static int cksmOpen( if( rc ) goto cksm_open_done; if( flags & SQLITE_OPEN_WAL ){ sqlite3_file *pDb = sqlite3_database_file_object(zName); + rc = pDb->pMethods->xFileControl(pDb, SQLITE_FCNTL_CKSM_FILE, (void*)&pDb); + assert( rc==SQLITE_OK ); p->pPartner = (CksmFile*)pDb; assert( p->pPartner->pPartner==0 ); p->pPartner->pPartner = p; @@ -715,7 +764,17 @@ static int cksmGetLastError(sqlite3_vfs *pVfs, int a, char *b){ return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); } static int cksmCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ - return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); + sqlite3_vfs *pOrig = ORIGVFS(pVfs); + int rc; + assert( pOrig->iVersion>=2 ); + if( pOrig->xCurrentTimeInt64 ){ + rc = pOrig->xCurrentTimeInt64(pOrig, p); + }else{ + double r; + rc = pOrig->xCurrentTime(pOrig, &r); + *p = (sqlite3_int64)(r*86400000.0); + } + return rc; } static int cksmSetSystemCall( sqlite3_vfs *pVfs, @@ -746,6 +805,11 @@ static int cksmRegisterFunc( rc = sqlite3_create_function(db, "verify_checksum", 1, SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, 0, cksmVerifyFunc, 0, 0); +#ifdef SQLITE_CKSUMVFS_INIT_FUNCNAME + (void)sqlite3_create_function(db, SQLITE_CKSUMVFS_INIT_FUNCNAME, 1, + SQLITE_UTF8|SQLITE_DIRECTONLY, + 0, cksmInitFunc, 0, 0); +#endif return rc; } @@ -759,6 +823,7 @@ static int cksmRegisterVfs(void){ sqlite3_vfs *pOrig; if( sqlite3_vfs_find("cksmvfs")!=0 ) return SQLITE_OK; pOrig = sqlite3_vfs_find(0); + if( pOrig==0 ) return SQLITE_ERROR; cksm_vfs.iVersion = pOrig->iVersion; cksm_vfs.pAppData = pOrig; cksm_vfs.szOsFile = pOrig->szOsFile + sizeof(CksmFile); @@ -806,9 +871,6 @@ int sqlite3_cksumvfs_init( SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; /* not used */ rc = cksmRegisterFunc(db, 0, 0); - if( rc==SQLITE_OK ){ - - } if( rc==SQLITE_OK ){ rc = cksmRegisterVfs(); } diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index a8d68ac72..37c6c2f52 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -459,10 +459,11 @@ static void decimalSubFunc( Decimal *pA = decimal_new(context, argv[0], 0, 0); Decimal *pB = decimal_new(context, argv[1], 0, 0); UNUSED_PARAMETER(argc); - if( pB==0 ) return; - pB->sign = !pB->sign; - decimal_add(pA, pB); - decimal_result(context, pA); + if( pB ){ + pB->sign = !pB->sign; + decimal_add(pA, pB); + decimal_result(context, pA); + } decimal_free(pA); decimal_free(pB); } diff --git a/ext/misc/ieee754.c b/ext/misc/ieee754.c index 121eb43d6..6cdd79a4d 100644 --- a/ext/misc/ieee754.c +++ b/ext/misc/ieee754.c @@ -167,6 +167,14 @@ static void ieee754func( int isNeg = 0; m = sqlite3_value_int64(argv[0]); e = sqlite3_value_int64(argv[1]); + + /* Limit the range of e. Ticket 22dea1cfdb9151e4 2021-03-02 */ + if( e>10000 ){ + e = 10000; + }else if( e<-10000 ){ + e = -10000; + } + if( m<0 ){ isNeg = 1; m = -m; diff --git a/ext/misc/json1.c b/ext/misc/json1.c index 77641064d..077d02d91 100644 --- a/ext/misc/json1.c +++ b/ext/misc/json1.c @@ -299,7 +299,7 @@ static void jsonAppendSeparator(JsonString *p){ */ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ u32 i; - if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return; + if( zIn==0 || ((N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0) ) return; p->zBuf[p->nUsed++] = '"'; for(i=0; inUsed>1 ){ jsonAppendChar(pStr, ','); - pStr->pCtx = ctx; } + pStr->pCtx = ctx; jsonAppendValue(pStr, argv[0]); } } @@ -1959,11 +1959,7 @@ static void jsonGroupInverse( if( NEVER(!pStr) ) return; #endif z = pStr->zBuf; - for(i=1; (c = z[i])!=',' || inStr || nNest; i++){ - if( i>=pStr->nUsed ){ - pStr->nUsed = 1; - return; - } + for(i=1; inUsed && ((c = z[i])!=',' || inStr || nNest); i++){ if( c=='"' ){ inStr = !inStr; }else if( c=='\\' ){ @@ -1973,8 +1969,13 @@ static void jsonGroupInverse( if( c=='}' || c==']' ) nNest--; } } - pStr->nUsed -= i; - memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1); + if( inUsed ){ + pStr->nUsed -= i; + memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1); + z[pStr->nUsed] = 0; + }else{ + pStr->nUsed = 1; + } } #else # define jsonGroupInverse 0 @@ -2002,8 +2003,8 @@ static void jsonObjectStep( jsonAppendChar(pStr, '{'); }else if( pStr->nUsed>1 ){ jsonAppendChar(pStr, ','); - pStr->pCtx = ctx; } + pStr->pCtx = ctx; z = (const char*)sqlite3_value_text(argv[0]); n = (u32)sqlite3_value_bytes(argv[0]); jsonAppendString(pStr, z, n); diff --git a/ext/misc/memvfs.c b/ext/misc/memvfs.c index 27a61c35e..83fc9468e 100644 --- a/ext/misc/memvfs.c +++ b/ext/misc/memvfs.c @@ -559,6 +559,7 @@ int sqlite3_memvfs_init( int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); mem_vfs.pAppData = sqlite3_vfs_find(0); + if( mem_vfs.pAppData==0 ) return SQLITE_ERROR; mem_vfs.szOsFile = sizeof(MemFile); rc = sqlite3_vfs_register(&mem_vfs, 1); #ifdef MEMVFS_TEST diff --git a/ext/misc/regexp.c b/ext/misc/regexp.c index 03bbeb97d..99fb453ce 100644 --- a/ext/misc/regexp.c +++ b/ext/misc/regexp.c @@ -248,7 +248,7 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ break; } case RE_OP_ANY: { - re_add_state(pNext, x+1); + if( c!=0 ) re_add_state(pNext, x+1); break; } case RE_OP_WORD: { @@ -256,7 +256,7 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ break; } case RE_OP_NOTWORD: { - if( !re_word_char(c) ) re_add_state(pNext, x+1); + if( !re_word_char(c) && c!=0 ) re_add_state(pNext, x+1); break; } case RE_OP_DIGIT: { @@ -264,7 +264,7 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ break; } case RE_OP_NOTDIGIT: { - if( !re_digit_char(c) ) re_add_state(pNext, x+1); + if( !re_digit_char(c) && c!=0 ) re_add_state(pNext, x+1); break; } case RE_OP_SPACE: { @@ -272,7 +272,7 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ break; } case RE_OP_NOTSPACE: { - if( !re_space_char(c) ) re_add_state(pNext, x+1); + if( !re_space_char(c) && c!=0 ) re_add_state(pNext, x+1); break; } case RE_OP_BOUNDARY: { @@ -297,8 +297,11 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ rc = 1; goto re_match_end; } - case RE_OP_CC_INC: case RE_OP_CC_EXC: { + if( c==0 ) break; + /* fall-through */ + } + case RE_OP_CC_INC: { int j = 1; int n = pRe->aArg[x]; int hit = 0; @@ -319,7 +322,7 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ } if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit; if( hit ) re_add_state(pNext, x+n); - break; + break; } } } @@ -480,7 +483,7 @@ static const char *re_subcompile_string(ReCompiled *p){ iStart = p->nState; switch( c ){ case '|': - case '$': + case '$': case ')': { p->sIn.i--; return 0; @@ -496,7 +499,7 @@ static const char *re_subcompile_string(ReCompiled *p){ if( rePeek(p)=='*' ){ re_append(p, RE_OP_ANYSTAR, 0); p->sIn.i++; - }else{ + }else{ re_append(p, RE_OP_ANY, 0); } break; @@ -673,8 +676,8 @@ static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ ** regex engine over the string. Do not worry able trying to match ** unicode characters beyond plane 0 - those are very rare and this is ** just an optimization. */ - if( pRe->aOp[0]==RE_OP_ANYSTAR ){ - for(j=0, i=1; jzInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ + if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ + for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ unsigned x = pRe->aArg[i]; if( x<=127 ){ pRe->zInit[j++] = (unsigned char)x; @@ -705,8 +708,8 @@ static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ ** is implemented as regexp(B,A). */ static void re_sql_func( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ ReCompiled *pRe; /* Compiled regular expression */ @@ -715,11 +718,12 @@ static void re_sql_func( const char *zErr; /* Compile error message */ int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ + (void)argc; /* Unused */ pRe = sqlite3_get_auxdata(context, 0); if( pRe==0 ){ zPattern = (const char*)sqlite3_value_text(argv[0]); if( zPattern==0 ) return; - zErr = re_compile(&pRe, zPattern, 0); + zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); if( zErr ){ re_free(pRe); sqlite3_result_error(context, zErr, -1); @@ -754,7 +758,14 @@ int sqlite3_regexp_init( ){ int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused */ rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, 0, re_sql_func, 0, 0); + if( rc==SQLITE_OK ){ + /* The regexpi(PATTERN,STRING) function is a case-insensitive version + ** of regexp(PATTERN,STRING). */ + rc = sqlite3_create_function(db, "regexpi", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, + (void*)db, re_sql_func, 0, 0); + } return rc; } diff --git a/ext/misc/series.c b/ext/misc/series.c index 092383e57..a4e92ace8 100644 --- a/ext/misc/series.c +++ b/ext/misc/series.c @@ -247,7 +247,8 @@ static int seriesEof(sqlite3_vtab_cursor *cur){ ** 4: step=VALUE ** ** Also, if bit 8 is set, that means that the series should be output -** in descending order rather than in ascending order. +** in descending order rather than in ascending order. If bit 16 is +** set, then output must appear in ascending order. ** ** This routine should initialize the cursor and position it so that it ** is pointing at the first row, or pointing off the end of the table @@ -273,7 +274,12 @@ static int seriesFilter( } if( idxNum & 4 ){ pCur->iStep = sqlite3_value_int64(argv[i++]); - if( pCur->iStep<1 ) pCur->iStep = 1; + if( pCur->iStep==0 ){ + pCur->iStep = 1; + }else if( pCur->iStep<0 ){ + pCur->iStep = -pCur->iStep; + if( (idxNum & 16)==0 ) idxNum |= 8; + } }else{ pCur->iStep = 1; } @@ -367,7 +373,11 @@ static int seriesBestIndex( pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); pIdxInfo->estimatedRows = 1000; if( pIdxInfo->nOrderBy==1 ){ - if( pIdxInfo->aOrderBy[0].desc ) idxNum |= 8; + if( pIdxInfo->aOrderBy[0].desc ){ + idxNum |= 8; + }else{ + idxNum |= 16; + } pIdxInfo->orderByConsumed = 1; } }else{ diff --git a/ext/misc/shathree.c b/ext/misc/shathree.c index 56a5584f3..ef25cb56c 100644 --- a/ext/misc/shathree.c +++ b/ext/misc/shathree.c @@ -31,7 +31,10 @@ SQLITE_EXTENSION_INIT1 #include #include #include + +#ifndef SQLITE_AMALGAMATION typedef sqlite3_uint64 u64; +#endif /* SQLITE_AMALGAMATION */ /****************************************************************************** ** The Hash Engine diff --git a/ext/misc/vfslog.c b/ext/misc/vfslog.c index 24a46a921..cb5bc5513 100644 --- a/ext/misc/vfslog.c +++ b/ext/misc/vfslog.c @@ -754,6 +754,7 @@ static int vlogCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ */ int sqlite3_register_vfslog(const char *zArg){ vlog_vfs.pVfs = sqlite3_vfs_find(0); + if( vlog_vfs.pVfs==0 ) return SQLITE_ERROR; vlog_vfs.base.szOsFile = sizeof(VLogFile) + vlog_vfs.pVfs->szOsFile; return sqlite3_vfs_register(&vlog_vfs.base, 1); } diff --git a/ext/misc/vfsstat.c b/ext/misc/vfsstat.c index 186ed788f..83a7a3df7 100644 --- a/ext/misc/vfsstat.c +++ b/ext/misc/vfsstat.c @@ -806,6 +806,7 @@ int sqlite3_vfsstat_init( int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); vstat_vfs.pVfs = sqlite3_vfs_find(0); + if( vstat_vfs.pVfs==0 ) return SQLITE_ERROR; vstat_vfs.base.szOsFile = sizeof(VStatFile) + vstat_vfs.pVfs->szOsFile; rc = sqlite3_vfs_register(&vstat_vfs.base, 1); if( rc==SQLITE_OK ){ diff --git a/ext/misc/wholenumber.c b/ext/misc/wholenumber.c index 5643f9cf7..03d6e6902 100644 --- a/ext/misc/wholenumber.c +++ b/ext/misc/wholenumber.c @@ -220,7 +220,7 @@ static int wholenumberBestIndex( pIdxInfo->orderByConsumed = 1; } if( (idxNum & 12)==0 ){ - pIdxInfo->estimatedCost = (double)100000000; + pIdxInfo->estimatedCost = 1e99; }else if( (idxNum & 3)==0 ){ pIdxInfo->estimatedCost = (double)5; }else{ diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index f9fbcfc0b..b727f9f2b 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -36,10 +36,24 @@ SQLITE_EXTENSION_INIT1 #ifndef SQLITE_AMALGAMATION +#ifndef UINT32_TYPE +# ifdef HAVE_UINT32_T +# define UINT32_TYPE uint32_t +# else +# define UINT32_TYPE unsigned int +# endif +#endif +#ifndef UINT16_TYPE +# ifdef HAVE_UINT16_T +# define UINT16_TYPE uint16_t +# else +# define UINT16_TYPE unsigned short int +# endif +#endif typedef sqlite3_int64 i64; typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned long u32; +typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ +typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ #define MIN(a,b) ((a)<(b) ? (a) : (b)) #if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) @@ -706,34 +720,24 @@ static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){ ** https://msdn.microsoft.com/en-us/library/9kkf9tah.aspx */ static u32 zipfileMtime(ZipfileCDS *pCDS){ - int Y = (1980 + ((pCDS->mDate >> 9) & 0x7F)); - int M = ((pCDS->mDate >> 5) & 0x0F); - int D = (pCDS->mDate & 0x1F); - int B = -13; - - int sec = (pCDS->mTime & 0x1F)*2; - int min = (pCDS->mTime >> 5) & 0x3F; - int hr = (pCDS->mTime >> 11) & 0x1F; - i64 JD; - - /* JD = INT(365.25 * (Y+4716)) + INT(30.6001 * (M+1)) + D + B - 1524.5 */ - - /* Calculate the JD in seconds for noon on the day in question */ - if( M<3 ){ - Y = Y-1; - M = M+12; + int Y,M,D,X1,X2,A,B,sec,min,hr; + i64 JDsec; + Y = (1980 + ((pCDS->mDate >> 9) & 0x7F)); + M = ((pCDS->mDate >> 5) & 0x0F); + D = (pCDS->mDate & 0x1F); + sec = (pCDS->mTime & 0x1F)*2; + min = (pCDS->mTime >> 5) & 0x3F; + hr = (pCDS->mTime >> 11) & 0x1F; + if( M<=2 ){ + Y--; + M += 12; } - JD = (i64)(24*60*60) * ( - (int)(365.25 * (Y + 4716)) - + (int)(30.6001 * (M + 1)) - + D + B - 1524 - ); - - /* Correct the JD for the time within the day */ - JD += (hr-12) * 3600 + min * 60 + sec; - - /* Convert JD to unix timestamp (the JD epoch is 2440587.5) */ - return (u32)(JD - (i64)(24405875) * 24*60*6); + X1 = 36525*(Y+4716)/100; + X2 = 306001*(M+1)/10000; + A = Y/100; + B = 2 - A + (A/4); + JDsec = (i64)((X1 + X2 + D + B - 1524.5)*86400) + hr*3600 + min*60 + sec; + return (u32)(JDsec - (i64)24405875*(i64)8640); } /* @@ -1482,6 +1486,7 @@ static int zipfileBegin(sqlite3_vtab *pVtab){ static u32 zipfileTime(void){ sqlite3_vfs *pVfs = sqlite3_vfs_find(0); u32 ret; + if( pVfs==0 ) return 0; if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){ i64 ms; pVfs->xCurrentTimeInt64(pVfs, &ms); @@ -2170,6 +2175,10 @@ static int zipfileRegister(sqlite3 *db){ zipfileStep, zipfileFinal ); } + assert( sizeof(i64)==8 ); + assert( sizeof(u32)==4 ); + assert( sizeof(u16)==2 ); + assert( sizeof(u8)==1 ); return rc; } #else /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/rbu/rbu.c b/ext/rbu/rbu.c index 02822c4a4..7a24db214 100644 --- a/ext/rbu/rbu.c +++ b/ext/rbu/rbu.c @@ -56,7 +56,7 @@ void usage(const char *zArgv0){ void report_default_vfs(){ sqlite3_vfs *pVfs = sqlite3_vfs_find(0); - fprintf(stdout, "default vfs is \"%s\"\n", pVfs->zName); + fprintf(stdout, "default vfs is \"%s\"\n", pVfs ? pVfs->zName : "NULL"); } void report_rbu_vfs(sqlite3rbu *pRbu){ @@ -183,6 +183,13 @@ int main(int argc, char **argv){ break; } + if( nStatStep>0 ){ + sqlite3_int64 nUsed; + sqlite3_int64 nHighwater; + sqlite3_status64(SQLITE_STATUS_MEMORY_USED, &nUsed, &nHighwater, 0); + fprintf(stdout, "memory used=%lld highwater=%lld\n", nUsed, nHighwater); + } + sqlite3_free(zErrmsg); return (rc==SQLITE_OK || rc==SQLITE_DONE) ? 0 : 1; } diff --git a/ext/rbu/rbu1.test b/ext/rbu/rbu1.test index 7ec075b7e..9237dbcea 100644 --- a/ext/rbu/rbu1.test +++ b/ext/rbu/rbu1.test @@ -132,6 +132,11 @@ foreach {tn3 create_vfs destroy_vfs} { } { sqlite3rbu_destroy_vfs myrbu } + 3 { + sqlite3_register_cksumvfs + } { + sqlite3_unregister_cksumvfs + } } { eval $create_vfs diff --git a/ext/rbu/rbudiff.test b/ext/rbu/rbudiff.test index 965ce297e..5c2bf9bee 100644 --- a/ext/rbu/rbudiff.test +++ b/ext/rbu/rbudiff.test @@ -268,6 +268,14 @@ tablE t1 USING FTs5(c); DELETE FROM t1 WHERE rowid = 1; INSERT INTO t1 VALUES('a b c'); } + 4 { + creAte virTUal tablE t1 USING FTs5(c); + INSERT INTO t1 VALUES('a b c'); + INSERT INTO t1 VALUES('a b c'); + } { + DELETE FROM t1 WHERE rowid = 1; + INSERT INTO t1 VALUES('a b c'); + } } { forcedelete test.db test.db2 diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c index 12a0a4660..1a13b6c4a 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -1620,7 +1620,9 @@ char *rbuVacuumIndexStart( zSep = ""; for(iCol=0; iColnCol; iCol++){ const char *zQuoted = (const char*)sqlite3_column_text(pSel, iCol); - if( zQuoted[0]=='N' ){ + if( zQuoted==0 ){ + p->rc = SQLITE_NOMEM; + }else if( zQuoted[0]=='N' ){ bFailed = 1; break; } @@ -4828,22 +4830,24 @@ static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ #endif assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); - if( pRbu && (pRbu->eStage==RBU_STAGE_OAL || pRbu->eStage==RBU_STAGE_MOVE) ){ - /* Magic number 1 is the WAL_CKPT_LOCK lock. Preventing SQLite from - ** taking this lock also prevents any checkpoints from occurring. - ** todo: really, it's not clear why this might occur, as - ** wal_autocheckpoint ought to be turned off. */ + if( pRbu && ( + pRbu->eStage==RBU_STAGE_OAL + || pRbu->eStage==RBU_STAGE_MOVE + || pRbu->eStage==RBU_STAGE_DONE + )){ + /* Prevent SQLite from taking a shm-lock on the target file when it + ** is supplying heap memory to the upper layer in place of *-shm + ** segments. */ if( ofst==WAL_LOCK_CKPT && n==1 ) rc = SQLITE_BUSY; }else{ int bCapture = 0; if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){ bCapture = 1; } - if( bCapture==0 || 0==(flags & SQLITE_SHM_UNLOCK) ){ rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); if( bCapture && rc==SQLITE_OK ){ - pRbu->mLock |= (1 << ofst); + pRbu->mLock |= ((1<pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){ - /* This call is to open a *-wal file. Intead, open the *-oal. This - ** code ensures that the string passed to xOpen() is terminated by a - ** pair of '\0' bytes in case the VFS attempts to extract a URI - ** parameter from it. */ - const char *zBase = zName; - size_t nCopy; - char *zCopy; + /* This call is to open a *-wal file. Intead, open the *-oal. */ + size_t nOpen; if( rbuIsVacuum(pDb->pRbu) ){ - zBase = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); - zBase = sqlite3_filename_wal(zBase); - } - nCopy = strlen(zBase); - zCopy = sqlite3_malloc64(nCopy+2); - if( zCopy ){ - memcpy(zCopy, zBase, nCopy); - zCopy[nCopy-3] = 'o'; - zCopy[nCopy] = '\0'; - zCopy[nCopy+1] = '\0'; - zOpen = (const char*)(pFd->zDel = zCopy); - }else{ - rc = SQLITE_NOMEM; + zOpen = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); + zOpen = sqlite3_filename_wal(zOpen); } + nOpen = strlen(zOpen); + ((char*)zOpen)[nOpen-3] = 'o'; pFd->pRbu = pDb->pRbu; } pDb->pWalFd = pFd; diff --git a/ext/rtree/geopoly.c b/ext/rtree/geopoly.c index b1cff38ed..b7759bbb5 100644 --- a/ext/rtree/geopoly.c +++ b/ext/rtree/geopoly.c @@ -310,6 +310,10 @@ static GeoPoly *geopolyFuncParam( ){ const unsigned char *a = sqlite3_value_blob(pVal); int nVertex; + if( a==0 ){ + sqlite3_result_error_nomem(pCtx); + return 0; + } nVertex = (a[1]<<16) + (a[2]<<8) + a[3]; if( (a[0]==0 || a[0]==1) && (nVertex*2*sizeof(GeoCoord) + 4)==(unsigned int)nByte @@ -683,7 +687,7 @@ static GeoPoly *geopolyBBox( aCoord[2].f = mnY; aCoord[3].f = mxY; } - }else{ + }else if( aCoord ){ memset(aCoord, 0, sizeof(RtreeCoord)*4); } return pOut; diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index f5b57a5e2..2151d5af0 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -3890,11 +3890,16 @@ 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 + ){ sqlite3_result_error(ctx, "Invalid argument to rtreedepth()", -1); }else{ u8 *zBlob = (u8 *)sqlite3_value_blob(apArg[0]); - sqlite3_result_int(ctx, readInt16(zBlob)); + if( zBlob ){ + sqlite3_result_int(ctx, readInt16(zBlob)); + }else{ + sqlite3_result_error_nomem(ctx); + } } } diff --git a/ext/rtree/rtree6.test b/ext/rtree/rtree6.test index bc9ff8429..b6dfe992a 100644 --- a/ext/rtree/rtree6.test +++ b/ext/rtree/rtree6.test @@ -79,47 +79,47 @@ do_eqp_test rtree6.2.1 { SELECT * FROM t1,t2 WHERE k=+ii AND x1<10 } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0 - `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t1 VIRTUAL TABLE INDEX 2:C0 + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) } do_eqp_test rtree6.2.2 { SELECT * FROM t1,t2 WHERE k=ii AND x1<10 } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0 - `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t1 VIRTUAL TABLE INDEX 2:C0 + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) } do_eqp_test rtree6.2.3 { SELECT * FROM t1,t2 WHERE k=ii } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2: - `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t1 VIRTUAL TABLE INDEX 2: + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) } do_eqp_test rtree6.2.4.1 { SELECT * FROM t1,t2 WHERE v=+ii and x1<10 and x2>10 } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0E1 - `--SEARCH TABLE t2 USING AUTOMATIC COVERING INDEX (v=?) + |--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1 + `--SEARCH t2 USING AUTOMATIC COVERING INDEX (v=?) } do_eqp_test rtree6.2.4.2 { SELECT * FROM t1,t2 WHERE v=10 and x1<10 and x2>10 } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0E1 - `--SEARCH TABLE t2 USING AUTOMATIC PARTIAL COVERING INDEX (v=?) + |--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1 + `--SEARCH t2 USING AUTOMATIC PARTIAL COVERING INDEX (v=?) } do_eqp_test rtree6.2.5 { SELECT * FROM t1,t2 WHERE k=ii AND x1=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y } { QUERY PLAN - |--SCAN TABLE t - `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 + |--SCAN t + `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 } do_eqp_test 1.2 { @@ -39,8 +39,8 @@ do_eqp_test 1.2 { WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y } { QUERY PLAN - |--SCAN TABLE t - `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 + |--SCAN t + `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 } do_eqp_test 1.3 { @@ -48,16 +48,16 @@ do_eqp_test 1.3 { WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND ?<=max_y } { QUERY PLAN - |--SCAN TABLE t - `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 + |--SCAN t + `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 } do_eqp_test 1.5 { SELECT * FROM t, r_tree } { QUERY PLAN - |--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2: - `--SCAN TABLE t + |--SCAN r_tree VIRTUAL TABLE INDEX 2: + `--SCAN t } do_execsql_test 2.0 { @@ -87,8 +87,8 @@ do_eqp_test 2.1 { WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y } { QUERY PLAN - |--SCAN TABLE t - `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 + |--SCAN t + `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 } do_eqp_test 2.2 { @@ -96,8 +96,8 @@ do_eqp_test 2.2 { WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y } { QUERY PLAN - |--SCAN TABLE t - `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 + |--SCAN t + `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 } do_eqp_test 2.3 { @@ -105,16 +105,16 @@ do_eqp_test 2.3 { WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND ?<=max_y } { QUERY PLAN - |--SCAN TABLE t - `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 + |--SCAN t + `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0 } do_eqp_test 2.5 { SELECT * FROM t, r_tree } { QUERY PLAN - |--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2: - `--SCAN TABLE t + |--SCAN r_tree VIRTUAL TABLE INDEX 2: + `--SCAN t } #------------------------------------------------------------------------- @@ -128,24 +128,24 @@ do_execsql_test 3.1 { do_eqp_test 3.2.1 { SELECT * FROM t1 CROSS JOIN t2 } { QUERY PLAN - |--SCAN TABLE t1 - `--SCAN TABLE t2 + |--SCAN t1 + `--SCAN t2 } do_eqp_test 3.2.2 { SELECT * FROM t2 CROSS JOIN t1 } { QUERY PLAN - |--SCAN TABLE t2 - `--SCAN TABLE t1 + |--SCAN t2 + `--SCAN t1 } do_eqp_test 3.3.1 { SELECT * FROM t1 CROSS JOIN t3 } { QUERY PLAN - |--SCAN TABLE t1 - `--SCAN TABLE t3 VIRTUAL TABLE INDEX 2: + |--SCAN t1 + `--SCAN t3 VIRTUAL TABLE INDEX 2: } do_eqp_test 3.3.2 { SELECT * FROM t3 CROSS JOIN t1 } { QUERY PLAN - |--SCAN TABLE t3 VIRTUAL TABLE INDEX 2: - `--SCAN TABLE t1 + |--SCAN t3 VIRTUAL TABLE INDEX 2: + `--SCAN t1 } #-------------------------------------------------------------------- @@ -203,8 +203,8 @@ do_eqp_test 5.2 { SELECT * FROM t1, rt WHERE x==id; } { QUERY PLAN - |--SCAN TABLE t1 - `--SCAN TABLE rt VIRTUAL TABLE INDEX 1: + |--SCAN t1 + `--SCAN rt VIRTUAL TABLE INDEX 1: } # Now create enough ANALYZE data to tell SQLite that virtual table "rt" @@ -220,8 +220,8 @@ do_eqp_test 5.4 { SELECT * FROM t1, rt WHERE x==id; } { QUERY PLAN - |--SCAN TABLE rt VIRTUAL TABLE INDEX 2: - `--SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (x=?) + |--SCAN rt VIRTUAL TABLE INDEX 2: + `--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (x=?) } # Delete the ANALYZE data. "t1" should be the outer loop again. @@ -233,8 +233,8 @@ do_eqp_test 5.6 { SELECT * FROM t1, rt WHERE x==id; } { QUERY PLAN - |--SCAN TABLE t1 - `--SCAN TABLE rt VIRTUAL TABLE INDEX 1: + |--SCAN t1 + `--SCAN rt VIRTUAL TABLE INDEX 1: } # This time create and attach a database that contains ANALYZE data for @@ -258,8 +258,8 @@ do_eqp_test 5.8 { SELECT * FROM t1, rt WHERE x==id; } { QUERY PLAN - |--SCAN TABLE t1 - `--SCAN TABLE rt VIRTUAL TABLE INDEX 1: + |--SCAN t1 + `--SCAN rt VIRTUAL TABLE INDEX 1: } #-------------------------------------------------------------------- @@ -327,9 +327,9 @@ do_eqp_execsql_test 7.1 { WHERE (x1 BETWEEN xmin AND xmax); } { QUERY PLAN - |--SCAN TABLE xdir - |--SCAN TABLE ydir - `--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B2D3B0D1 + |--SCAN xdir + |--SCAN ydir + `--SCAN rt VIRTUAL TABLE INDEX 2:B2D3B0D1 } { 2 4 } @@ -340,9 +340,9 @@ do_eqp_execsql_test 7.2 { WHERE (x1 BETWEEN xmin AND xmax); } { QUERY PLAN - |--SCAN TABLE xdir - |--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1 - `--SCAN TABLE ydir + |--SCAN xdir + |--SCAN rt VIRTUAL TABLE INDEX 2:B0D1 + `--SCAN ydir } { 5 1 2 7 12 14 {} 5 2 2 7 8 12 10 @@ -355,9 +355,9 @@ do_eqp_execsql_test 7.3 { WHERE (x1 BETWEEN xmin AND xmax); } { QUERY PLAN - |--SCAN TABLE xdir - |--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1 - `--SCAN TABLE ydir + |--SCAN xdir + |--SCAN rt VIRTUAL TABLE INDEX 2:B0D1 + `--SCAN ydir } { 2 4 } @@ -368,9 +368,9 @@ do_eqp_execsql_test 7.4 { WHERE (x1 BETWEEN xmin AND xmax); } { QUERY PLAN - |--SCAN TABLE xdir - |--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1 - `--SCAN TABLE ydir + |--SCAN xdir + |--SCAN rt VIRTUAL TABLE INDEX 2:B0D1 + `--SCAN ydir } { 2 4 } diff --git a/ext/rtree/rtreefuzz001.test b/ext/rtree/rtreefuzz001.test index db9df205a..58fd179ab 100644 --- a/ext/rtree/rtreefuzz001.test +++ b/ext/rtree/rtreefuzz001.test @@ -1045,5 +1045,167 @@ do_test rtreefuzz001-500 { catchsql {UPDATE t1 SET ex= ex ISNULL} } {1 {database disk image is malformed}} +do_test rtreefuzz001-600 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 20480 pagesize 4096 filename crash-7b37d80f000235.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 05 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 10 06 00 00 00 04 ................ +| 96: 00 00 00 00 0d 00 00 00 05 0e 49 00 0f 99 0f 40 ..........I....@ +| 112: 0e da 0e 8f 0e 49 00 00 00 00 00 00 00 00 00 00 .....I.......... +| 3648: 00 00 00 00 00 00 00 00 00 44 05 06 17 15 15 08 .........D...... +| 3664: 6f 74 61 62 6c 65 67 65 6f 31 67 65 6f 31 43 52 otablegeo1geo1CR +| 3680: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 3696: 4c 45 20 67 65 6f 31 20 55 53 49 4e 47 20 67 65 LE geo1 USING ge +| 3712: 6f 70 6f 6c 79 28 74 79 70 65 2c 63 6c 72 29 49 opoly(type,clr)I +| 3728: 04 06 17 1f 1f 01 63 74 61 62 6c 65 71 75 65 72 ......ctablequer +| 3744: 79 70 6f 6c 79 71 75 65 72 79 70 6f 6c 79 05 43 ypolyquerypoly.C +| 3760: 52 45 41 54 45 20 54 41 42 4c 45 20 71 75 65 72 REATE TABLE quer +| 3776: 79 70 6f 6c 79 28 70 6f 6c 79 20 4a 53 4f 4e 2c ypoly(poly JSON, +| 3792: 20 63 6c 72 20 54 45 58 54 29 64 03 07 17 23 23 clr TEXT)d...## +| 3808: 01 81 0f 74 61 62 6c 65 67 65 6f 31 5f 70 61 72 ...tablegeo1_par +| 3824: 65 6e 74 67 65 6f 31 5f 70 61 72 65 6e 74 04 43 entgeo1_parent.C +| 3840: 52 45 41 54 45 20 54 41 42 4c 45 20 22 67 65 6f REATE TABLE .geo +| 3856: 31 5f 70 61 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 1_parent.(nodeno +| 3872: 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 INTEGER PRIMARY +| 3888: 20 4b 45 59 2c 70 61 72 65 6e 74 6e 6f 64 85 29 KEY,parentnod.) +| 3904: 57 02 06 17 1f 1f 01 7f 74 61 62 6c 65 67 65 6f W.......tablegeo +| 3920: 31 5f 6e 6f 64 65 67 65 6f 31 5f 6e 6f 64 65 03 1_nodegeo1_node. +| 3936: 43 52 45 41 54 45 20 54 41 42 4c 45 20 22 67 65 CREATE TABLE .ge +| 3952: 6f 31 5f 6e 6f 64 65 22 28 6e 6f 64 65 6e 6f 20 o1_node.(nodeno +| 3968: 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 INTEGER PRIMARY +| 3984: 4b 45 59 2c 64 61 74 61 29 65 01 07 17 21 21 01 KEY,data)e...!!. +| 4000: 81 15 74 61 62 6c 65 67 65 6f 31 5f 72 6f 77 69 ..tablegeo1_rowi +| 4016: 64 67 65 6f 31 5f 72 6f 77 69 64 02 43 52 45 41 dgeo1_rowid.CREA +| 4032: 54 45 20 54 41 42 4c 45 20 22 67 65 6f 31 5f 72 TE TABLE .geo1_r +| 4048: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE +| 4064: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY, +| 4080: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 2c 61 32 29 nodeno,a0,a1,a2) +| page 2 offset 4096 +| 0: 0d 00 00 00 0a 0d ab 00 0f c9 0f 88 0f 48 0f 00 .............H.. +| 3488: 00 00 00 00 00 00 00 00 00 00 00 45 82 0a 06 00 ...........E.... +| 3504: 09 74 1d 13 01 00 00 06 00 80 b5 43 00 80 ac 43 .t.........C...C +| 3520: 00 00 bd 43 8f 82 9f 43 71 fd c9 43 8f 02 a7 43 ...C...Cq..C...C +| 3536: 71 fd c8 43 e4 bd a8 43 64 bb bd 43 f4 3d a2 43 q..C...Cd..C.=.C +| 3552: 64 3b b7 43 00 80 ad 43 61 6e 67 6c 65 2d 33 30 d;.C...Cangle-30 +| 3568: 72 65 64 32 81 4e 06 00 09 44 23 17 01 00 00 03 red2.N...D#..... +| 3584: 00 40 3f 44 00 c0 20 44 00 c0 46 44 00 c0 20 44 .@?D.. D..FD.. D +| 3600: 00 00 43 44 00 40 28 44 74 72 69 61 6e 67 6c 65 ..CD.@(Dtriangle +| 3616: 2d 33 30 62 6c 61 63 6b 35 82 3e 06 00 09 54 1d -30black5.>...T. +| 3632: 13 01 00 00 04 00 40 54 44 00 80 1d 44 9a c9 5c ......@TD...D... +| 3648: 44 66 36 1b 44 33 13 5f 44 00 c0 23 44 9a 89 5b Df6.D3._D..#D..[ +| 3664: 44 a4 60 1d 44 61 72 72 6f 77 2d 35 30 72 65 64 D.`.Darrow-50red +| 3680: 36 74 06 00 09 54 1b 17 01 00 00 04 00 80 0d 44 6t...T.........D +| 3696: 00 00 f2 42 0a d7 04 44 00 00 ca 42 0a 77 05 44 ...B...D...B.w.D +| 3712: 0a 57 c1 42 00 20 0e 44 0a 57 e9 42 6c 69 6e 65 .W.B. .D.W.Bline +| 3728: 2d 34 30 67 72 65 65 6e 36 72 06 00 09 54 1b 17 -40green6r...T.. +| 3744: 01 00 00 04 00 00 7b 43 00 00 ea 42 29 5c 58 43 .......C...B).XC +| 3760: 00 00 c2 42 29 dc 5a 43 0a 57 b9 42 00 80 7d 43 ...B).ZC.W.B...C +| 3776: 0a 57 e1 42 6c 69 6e 65 2d 34 30 67 72 65 65 6e .W.Bline-40green +| 3792: 36 54 06 00 09 54 1b 17 01 00 00 04 00 00 a2 43 6T...T.........C +| 3808: 00 00 24 44 00 00 b6 43 00 00 24 44 00 00 b6 43 ..$D...C..$D...C +| 3824: 00 40 25 44 00 00 a2 43 00 40 25 44 6c 69 6e 65 .@%D...C.@%Dline +| 3840: 2d 34 30 62 6c 61 63 6b 3e 37 06 00 09 64 1d 15 -40black>7...d.. +| 3856: 01 00 00 05 00 80 f0 43 00 00 54 43 66 16 01 44 .......C..TCf..D +| 3872: 66 a6 30 43 cd ec 09 44 00 00 54 43 8f 0a 09 44 f.0C...D..TC...D +| 3888: a4 d0 73 43 66 16 01 44 9a 59 77 43 68 6f 75 73 ..sCf..D.YwChous +| 3904: 65 2d 37 30 62 6c 75 65 3e 35 06 00 09 64 1d 15 e-70blue>5...d.. +| 3920: 01 00 00 05 00 00 a2 43 00 00 5a 43 cd ac b3 43 .......C..ZC...C +| 3936: 66 a6 36 43 9a 59 c5 43 00 00 5a 43 1f 95 c3 43 f.6C.Y.C..ZC...C +| 3952: a4 d0 79 43 cd ac b3 43 9a 59 7d 43 68 6f 75 73 ..yC...C.Y.Chous +| 3968: 65 2d 37 30 62 6c 75 65 3f 2c 06 00 09 64 1d 17 e-70blue?,...d.. +| 3984: 01 00 00 05 00 00 f5 43 00 00 2f 43 00 00 07 44 .......C../C...D +| 4000: 00 00 2f 43 00 00 07 44 00 00 61 43 00 c0 00 44 ../C...D..aC...D +| 4016: 00 00 75 43 00 00 f5 43 00 00 61 43 68 6f 75 73 ..uC...C..aChous +| 4032: 65 2d 37 30 62 6c 61 63 6b 35 1f 06 10 09 54 19 e-70black5....T. +| 4048: 17 01 00 00 04 00 00 9b 43 00 00 67 43 0a 57 92 ........C..gC.W. +| 4064: 43 00 00 5d 43 0a 57 97 43 14 ae 4b 42 ff ff a0 C..]C.W.C..KB... +| 4080: 43 14 ae 55 43 62 6f 78 2d 32 30 67 72 65 65 6e C..UCbox-20green +| page 3 offset 8192 +| 0: 0d 00 00 00 01 0b 2d 00 0b 2e 00 00 00 00 00 00 ......-......... +| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 ...............P +| 2864: 01 04 00 93 24 00 00 00 0a 00 00 00 00 00 00 01 ....$........... +| 2880: 0a 43 b5 80 00 43 c9 fd 71 43 9f 82 8f 43 ad 80 .C...C..qC...C.. +| 2896: 00 00 00 00 00 00 00 00 72 43 58 5c 29 43 7d 80 ........rCX.)C.. +| 2912: 00 42 b9 57 0a 42 ea 00 00 00 00 00 00 00 00 00 .B.W.B.......... +| 2928: 35 43 a2 00 00 43 c5 59 9a 43 36 a6 66 43 7d 59 5C...C.Y.C6.fC.Y +| 2944: 9a 00 00 00 00 00 00 00 1f 43 92 57 0a 43 a0 00 .........C.W.C.. +| 2960: 00 43 4b ae 14 43 67 00 00 00 00 00 00 00 00 00 .CK..Cg......... +| 2976: 37 43 f0 80 00 44 09 ec cd 43 30 a6 66 43 77 59 7C...D...C0.fCwY +| 2992: 9a 00 00 00 00 00 00 00 2c 43 f5 00 00 44 07 00 ........,C...D.. +| 3008: 00 43 2f 00 00 43 75 00 00 00 00 00 00 00 00 00 .C/..Cu......... +| 3024: 74 44 04 d7 0a 44 0e 20 00 42 c1 57 0a 42 f2 00 tD...D. .B.W.B.. +| 3040: 00 00 00 00 00 00 00 00 ce 44 3f 40 00 44 46 c0 .........D?@.DF. +| 3056: 00 44 20 c0 00 44 28 40 00 00 00 00 00 00 00 00 .D ..D(@........ +| 3072: be 44 54 40 00 44 5f 13 33 44 1b 36 66 44 23 c0 .DT@.D_.3D.6fD#. +| 3088: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C.. +| 3104: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........ +| 3120: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@ +| 3136: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C.. +| 3152: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........ +| 3168: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@ +| 3184: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C.. +| 3200: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........ +| 3216: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@ +| 3232: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C.. +| 3248: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........ +| 3264: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@ +| 3280: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C.. +| 3296: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........ +| 3312: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@ +| 3328: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C.. +| 3344: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 01 .D$..D%@........ +| 3360: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| 3376: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV. +| 3392: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C.......... +| 3408: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| 3424: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV. +| 3440: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C.......... +| 3456: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| 3472: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV. +| 3488: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C.......... +| 3504: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| 3520: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV. +| 3536: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C.......... +| 3552: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| 3568: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV. +| 3584: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C.......... +| 3600: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| 3616: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV. +| 3632: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C.......... +| 3648: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| 3664: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV. +| 3680: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C.......... +| 3696: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C.. +| page 4 offset 12288 +| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................ +| page 5 offset 16384 +| 0: 0d 00 00 00 01 0f 8f 00 00 00 00 00 00 00 00 00 ................ +| 3968: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6f ...............o +| 3984: 01 04 81 57 19 5b 5b 33 30 30 2c 33 30 30 5d 2c ...W.[[300,300], +| 4000: 5b 34 30 30 2c 33 35 30 5d 2c 5b 35 30 30 2c 32 [400,350],[500,2 +| 4016: 35 30 5d 2c 5b 34 38 30 2c 35 30 30 5d 2c 5b 34 50],[480,500],[4 +| 4032: 30 30 2c 34 38 30 5d 2c 5c 33 30 30 2c 35 35 30 00,480],.300,550 +| 4048: 5d 2c 5b 32 38 30 2c 34 35 30 5d 2c 5b 33 32 30 ],[280,450],[320 +| 4064: 2c 34 30 30 5d 2c 5b 32 38 30 2c 33 35 30 5d 2c ,400],[280,350], +| 4080: 5b 33 30 30 2c 33 30 00 00 00 00 00 00 00 00 00 [300,30......... +| end crash-7b37d80f000235.db +}]} {} + +ifcapable geopoly { + +do_catchsql_test rtreefuzz001-601 { + SAVEPOINT one; + UPDATE geo1 SET clr=CASE WHEN rowid IN ( SELECT geo1.rowid FROM geo1, querypoly ) THEN 'e' ELSE 'blue' END; +} {1 {database disk image is malformed}} + +do_catchsql_test rtreefuzz001-602 { + SELECT geopoly_svg(_shape, printf('j',geo1.clr)) + FROM geo1, querypoly WHERE geopoly_overlap(_shape, poly); +} {1 {database disk image is malformed}} + +} ;# ifcapable geopoly finish_test diff --git a/ext/session/session2.test b/ext/session/session2.test index cd8c2869e..806687745 100644 --- a/ext/session/session2.test +++ b/ext/session/session2.test @@ -35,7 +35,7 @@ proc test_reset {} { test_reset do_execsql_test 1.0 { - CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE t1(a INT PRIMARY KEY, b); INSERT INTO t1 VALUES('i', 'one'); } do_iterator_test 1.1 t1 { @@ -184,7 +184,7 @@ set set_of_tests { test_reset do_common_sql { - CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE t1(a int PRIMARY KEY, b); CREATE TABLE t2(a, b INTEGER PRIMARY KEY); CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b)); CREATE TABLE t4(a, b, PRIMARY KEY(b, a)); @@ -206,17 +206,17 @@ sqlite3 db3 test.db3 do_test 3.0 { execsql { ATTACH 'test.db3' AS 'aux'; - CREATE TABLE t1(a, b PRIMARY KEY); + CREATE TABLE t1(a int, b PRIMARY KEY); CREATE TABLE t2(x, y, z); CREATE TABLE t3(a); - CREATE TABLE aux.t1(a PRIMARY KEY, b); + CREATE TABLE aux.t1(a int PRIMARY KEY, b); CREATE TABLE aux.t2(a, b INTEGER PRIMARY KEY); CREATE TABLE aux.t3(a, b, c, PRIMARY KEY(a, b)); CREATE TABLE aux.t4(a, b, PRIMARY KEY(b, a)); } execsql { - CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE t1(a int PRIMARY KEY, b); CREATE TABLE t2(a, b INTEGER PRIMARY KEY); CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b)); CREATE TABLE t4(a, b, PRIMARY KEY(b, a)); @@ -588,4 +588,52 @@ do_execsql_test 10.2 { } {0 0 1 1} S delete +#------------------------------------------------------------------------- +test_reset +do_common_sql { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d, e, f); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<32 + ) + INSERT INTO t1 SELECT NULL, 0, 0, 0, 0, 0 FROM s +} + +do_then_apply_sql { + UPDATE t1 SET f=f+1 WHERE a=1; + UPDATE t1 SET e=e+1 WHERE a=2; + UPDATE t1 SET e=e+1, f=f+1 WHERE a=3; + UPDATE t1 SET d=d+1 WHERE a=4; + UPDATE t1 SET d=d+1, f=f+1 WHERE a=5; + UPDATE t1 SET d=d+1, e=e+1 WHERE a=6; + UPDATE t1 SET d=d+1, e=e+1, f=f+1 WHERE a=7; + UPDATE t1 SET c=c+1 WHERE a=8; + UPDATE t1 SET c=c+1, f=f+1 WHERE a=9; + UPDATE t1 SET c=c+1, e=e+1 WHERE a=10; + UPDATE t1 SET c=c+1, e=e+1, f=f+1 WHERE a=11; + UPDATE t1 SET c=c+1, d=d+1 WHERE a=12; + UPDATE t1 SET c=c+1, d=d+1, f=f+1 WHERE a=13; + UPDATE t1 SET c=c+1, d=d+1, e=e+1 WHERE a=14; + UPDATE t1 SET c=c+1, d=d+1, e=e+1, f=f+1 WHERE a=15; + UPDATE t1 SET d=d+1 WHERE a=16; + UPDATE t1 SET d=d+1, f=f+1 WHERE a=17; + UPDATE t1 SET d=d+1, e=e+1 WHERE a=18; + UPDATE t1 SET d=d+1, e=e+1, f=f+1 WHERE a=19; + UPDATE t1 SET d=d+1, d=d+1 WHERE a=20; + UPDATE t1 SET d=d+1, d=d+1, f=f+1 WHERE a=21; + UPDATE t1 SET d=d+1, d=d+1, e=e+1 WHERE a=22; + UPDATE t1 SET d=d+1, d=d+1, e=e+1, f=f+1 WHERE a=23; + UPDATE t1 SET d=d+1, c=c+1 WHERE a=24; + UPDATE t1 SET d=d+1, c=c+1, f=f+1 WHERE a=25; + UPDATE t1 SET d=d+1, c=c+1, e=e+1 WHERE a=26; + UPDATE t1 SET d=d+1, c=c+1, e=e+1, f=f+1 WHERE a=27; + UPDATE t1 SET d=d+1, c=c+1, d=d+1 WHERE a=28; + UPDATE t1 SET d=d+1, c=c+1, d=d+1, f=f+1 WHERE a=29; + UPDATE t1 SET d=d+1, c=c+1, d=d+1, e=e+1 WHERE a=30; + UPDATE t1 SET d=d+1, c=c+1, d=d+1, e=e+1, f=f+1 WHERE a=31; +} + +do_test 11.0 { + compare_db db db2 +} {} + finish_test diff --git a/ext/session/sessionbig.test b/ext/session/sessionbig.test new file mode 100644 index 000000000..80ce00a0f --- /dev/null +++ b/ext/session/sessionbig.test @@ -0,0 +1,108 @@ +# 2014 August 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 regression tests for sessions SQLite extension. +# Specifically, this file contains tests for "patchset" changes. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +if {[permutation]=="session_strm" || [permutation]=="session_eec"} { + finish_test + return +} + +if {$::tcl_platform(pointerSize)<8} { + finish_test + return +} + +set testprefix sessionbig + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); +} +do_execsql_test -db db2 1.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); +} + +do_test 1.2 { + do_then_apply_sql { + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + } +} {} + +do_test 1.3 { + execsql { DELETE FROM t1 } + execsql2 { DELETE FROM t1 } +} {} + +do_test 1.4 { + set rc [catch { + do_then_apply_sql { + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); + } + } msg] + list $rc $msg +} {1 SQLITE_NOMEM} + + +finish_test + diff --git a/ext/session/sessionmem.test b/ext/session/sessionmem.test new file mode 100644 index 000000000..75b48623d --- /dev/null +++ b/ext/session/sessionmem.test @@ -0,0 +1,57 @@ +# 2020 December 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. +# +#*********************************************************************** +# This file implements regression tests for the SQLite sessions module +# Specifically, for the sqlite3session_memory_used() API. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionmem + +do_execsql_test 1.0 { + CREATE TABLE t1(i INTEGER PRIMARY KEY, x, y); + CREATE TABLE t2(i INTEGER, x, y, PRIMARY KEY(x, y)); +} + +do_test 1.1 { + sqlite3session S db main + S attach * +} {} + +foreach {tn sql eRes} { + 1 { INSERT INTO t1 VALUES(1, 2, 3) } 1 + 2 { UPDATE t1 SET x=5 } 0 + 3 { UPDATE t1 SET i=5 } 1 + 4 { DELETE FROM t1 } 0 + 5 { INSERT INTO t1 VALUES(1, 2, 3) } 0 + 6 { INSERT INTO t1 VALUES(5, 2, 3) } 0 + 7 { INSERT INTO t2 VALUES('a', 'b', 'c') } 1 + 8 { INSERT INTO t2 VALUES('d', 'e', 'f') } 1 + 9 { UPDATE t2 SET i='e' } 0 +} { + set mem1 [S memory_used] + do_test 1.2.$tn.(mu=$mem1) { + execsql $sql + set mem2 [S memory_used] + expr {$mem2 > $mem1} + } $eRes +} + +do_test 1.3 { + S delete +} {} + +finish_test diff --git a/ext/session/sessionnoop.test b/ext/session/sessionnoop.test new file mode 100644 index 000000000..16c60b7ab --- /dev/null +++ b/ext/session/sessionnoop.test @@ -0,0 +1,187 @@ +# 2021 Februar 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. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionnoop + +#------------------------------------------------------------------------- +# Test plan: +# +# 1.*: Test that concatenating changesets cannot produce a noop UPDATE. +# 2.*: Test that rebasing changesets cannot produce a noop UPDATE. +# 3.*: Test that sqlite3changeset_apply() ignores noop UPDATE changes. +# + +do_execsql_test 1.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c, d); + INSERT INTO t1 VALUES(1, 1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3, 3); +} + +proc do_concat_test {tn sql1 sql2 res} { + uplevel [list do_test $tn [subst -nocommands { + set C1 [changeset_from_sql {$sql1}] + set C2 [changeset_from_sql {$sql2}] + set C3 [sqlite3changeset_concat [set C1] [set C2]] + set got [list] + sqlite3session_foreach elem [set C3] { lappend got [set elem] } + set got + }] [list {*}$res]] +} + +do_concat_test 1.1 { + UPDATE t1 SET c=c+1; +} { + UPDATE t1 SET c=c-1; +} { +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); +} + +proc do_rebase_test {tn sql_local sql_remote conflict_res expected} { + proc xConflict {args} [list return $conflict_res] + + uplevel [list \ + do_test $tn [subst -nocommands { + execsql BEGIN + set c_remote [changeset_from_sql {$sql_remote}] + execsql ROLLBACK + + execsql BEGIN + set c_local [changeset_from_sql {$sql_local}] + set base [sqlite3changeset_apply_v2 db [set c_remote] xConflict] + execsql ROLLBACK + + sqlite3rebaser_create R + R config [set base] + set res [list] + sqlite3session_foreach elem [R rebase [set c_local]] { + lappend res [set elem] + } + R delete + set res + }] [list {*}$expected] + ] +} + +do_rebase_test 2.1 { + UPDATE t1 SET c=2 WHERE a=1; -- local +} { + UPDATE t1 SET c=3 WHERE a=1; -- remote +} OMIT { + {UPDATE t1 0 X.. {i 1 {} {} i 3} {{} {} {} {} i 2}} +} + +do_rebase_test 2.2 { + UPDATE t1 SET c=2 WHERE a=1; -- local +} { + UPDATE t1 SET c=3 WHERE a=1; -- remote +} REPLACE { +} + +do_rebase_test 2.3.1 { + UPDATE t1 SET c=4 WHERE a=1; -- local +} { + UPDATE t1 SET c=4 WHERE a=1 -- remote +} OMIT { + {UPDATE t1 0 X.. {i 1 {} {} i 4} {{} {} {} {} i 4}} +} + +do_rebase_test 2.3.2 { + UPDATE t1 SET c=5 WHERE a=1; -- local +} { + UPDATE t1 SET c=5 WHERE a=1 -- remote +} REPLACE { +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); + INSERT INTO t1 VALUES(4, 4, 4); +} + +# Arg $pkstr contains one character for each column in the table. An +# "X" for PK column, or a "." for a non-PK. +# +proc mk_tbl_header {name pkstr} { + set ret [binary format H2c 54 [string length $pkstr]] + foreach i [split $pkstr {}] { + if {$i=="X"} { + append ret [binary format H2 01] + } else { + if {$i!="."} {error "bad pkstr: $pkstr ($i)"} + append ret [binary format H2 00] + } + } + append ret $name + append ret [binary format H2 00] + set ret +} + +proc mk_update_change {args} { + set ret [binary format H2H2 17 00] + foreach a $args { + if {$a==""} { + append ret [binary format H2 00] + } else { + append ret [binary format H2W 01 $a] + } + } + set ret +} + +proc xConflict {args} { return "ABORT" } +do_test 3.1 { + set C [mk_tbl_header t1 X..] + append C [mk_update_change 1 {} 1 {} {} 500] + append C [mk_update_change 2 {} {} {} {} {}] + append C [mk_update_change 3 3 {} {} 600 {}] + append C [mk_update_change 4 {} {} {} {} {}] + + sqlite3changeset_apply_v2 db $C xConflict +} {} +do_execsql_test 3.2 { + SELECT * FROM t1 +} { + 1 1 500 + 2 2 2 + 3 600 3 + 4 4 4 +} + + + + + + +finish_test + diff --git a/ext/session/sessionsize.test b/ext/session/sessionsize.test new file mode 100644 index 000000000..04d05514d --- /dev/null +++ b/ext/session/sessionsize.test @@ -0,0 +1,131 @@ +# 2021 April 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 implements regression tests for SQLite library. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionsize + +proc do_changeset_size_test {tn sql} { + sqlite3session S db main + S attach * + db eval $sql + + set sz [S changeset_size] + set C [S changeset] + set szC [string length $C] + S delete + + do_test $tn "expr $sz" $szC +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 'abc', 'def'); + INSERT INTO t1 VALUES(2, 'ghi', 'jkl'); +} + +do_changeset_size_test 1.1 { + INSERT INTO t1 VALUES(3, 'hello', 'world'); +} + +do_changeset_size_test 1.2 { + DELETE FROM t1 WHERE a=2; +} + +do_changeset_size_test 1.3 { + DELETE FROM t1 WHERE a=3; + INSERT INTO t1 VALUES(3, 1, 2); +} + +do_changeset_size_test 1.4 { + UPDATE t1 SET c='hello world' WHERE a=3; +} + +#------------------------------------------------------------------------- + +do_execsql_test 2.0 { + CREATE TABlE t2(a, b, c, d, PRIMARY KEY(a, b)) WITHOUT ROWID; + CREATE TABlE t3(a, b, c, d PRIMARY KEY); +} + +do_changeset_size_test 2.1 { + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50 + ) + INSERT INTO t2 SELECT i, i+1, i+2, i+3 FROM s; + + UPDATE t2 SET c=randomblob(a) WHERE a>10 +} + +do_changeset_size_test 2.2 { + DELETE FROM t2 WHERE a=1; + INSERT INTO t2 VALUES(1, 4, 3, 4); +} + +do_changeset_size_test 2.2 { + UPDATE t2 SET b=4 WHERE a=2 +} + +do_changeset_size_test 2.3 { + INSERT INTO t2 VALUES('a', 'b', 'c', 'd'); + UPDATE t2 SET c='qwertyuiop' WHERE a='a'; +} + +do_changeset_size_test 2.4 { + DELETE FROM t2 WHERE a='a'; + INSERT INTO t2 VALUES('a', 'b', 'c', 'd'); +} + +do_changeset_size_test 2.5 { + UPDATE t2 SET a='aa', b='bb' WHERE (a, b) = ('a', 'b'); +} + +do_changeset_size_test 2.6 { + UPDATE t2 SET a='a', b='b' WHERE (a, b) = ('aa', 'bb'); +} + +do_changeset_size_test 2.7 { + INSERT INTO t3 DEFAULT VALUES; + INSERT INTO t3 VALUES(1,2,3,4); +} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 3.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); +} + +do_test 3.1 { + sqlite3session S db main + S object_config_size -1 +} 1 + +do_test 3.2.1 { S object_config_size 0 } 0 +do_test 3.2.2 { S object_config_size -1 } 0 +do_test 3.2.3 { S object_config_size 1 } 1 +do_test 3.2.4 { S object_config_size -1 } 1 + +do_test 3.3 { S attach t1 } {} +do_test 3.4 { S object_config_size 1 } {SQLITE_MISUSE} +do_test 3.4 { S object_config_size -1 } {1} + +S delete + +finish_test + diff --git a/ext/session/sessionwor.test b/ext/session/sessionwor.test index 0f0b429d7..7d9e5c6a8 100644 --- a/ext/session/sessionwor.test +++ b/ext/session/sessionwor.test @@ -69,7 +69,9 @@ foreach {tn wo} { } { reset_db - do_execsql_test 2.$tn.0 "CREATE TABLE t1(a INTEGER PRIMARY KEY, b) $wo ;" + do_execsql_test 2.$tn.0.1 "CREATE TABLE t1(a INTEGER PRIMARY KEY, b) $wo ;" + do_execsql_test 2.$tn.0.2 "CREATE TABLE t2(a INTEGER PRIMARY KEY, b) $wo ;" + do_execsql_test 2.$tn.0.3 "CREATE TABLE t3(a INTEGER PRIMARY KEY, b) $wo ;" do_iterator_test 1.1 t1 { INSERT INTO t1 VALUES(1, 'two'); @@ -94,6 +96,27 @@ foreach {tn wo} { } { {DELETE t1 0 X. {i 1 t four} {}} } + + do_execsql_test 2.$tn.5 { + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); + } + + do_iterator_test 2.$tn.6 t2 { + INSERT INTO t2 SELECT a, b FROM t1 + } { + {INSERT t2 0 X. {} {i 1 t one}} + {INSERT t2 0 X. {} {i 2 t two}} + {INSERT t2 0 X. {} {i 3 t three}} + } + do_iterator_test 2.$tn.7 t3 { + INSERT INTO t3 SELECT * FROM t1 + } { + {INSERT t3 0 X. {} {i 1 t one}} + {INSERT t3 0 X. {} {i 2 t two}} + {INSERT t3 0 X. {} {i 3 t three}} + } } finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index dae260471..c96363b60 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -42,12 +42,15 @@ struct SessionHook { struct sqlite3_session { sqlite3 *db; /* Database handle session is attached to */ char *zDb; /* Name of database session is attached to */ + int bEnableSize; /* True if changeset_size() enabled */ int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ int rc; /* Non-zero if an error has occurred */ void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); + i64 nMalloc; /* Number of bytes of data allocated */ + i64 nMaxChangesetSize; sqlite3_value *pZeroBlob; /* Value containing X'' */ sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ @@ -90,6 +93,7 @@ struct sqlite3_changeset_iter { SessionBuffer tblhdr; /* Buffer to hold apValue/zTab/abPK/ */ int bPatchset; /* True if this is a patchset */ int bInvert; /* True to invert changeset */ + int bSkipEmpty; /* Skip noop UPDATE changes */ int rc; /* Iterator error code */ sqlite3_stmt *pConflict; /* Points to conflicting row, if any */ char *zTab; /* Current table */ @@ -289,8 +293,9 @@ struct SessionTable { ** this structure stored in a SessionTable.aChange[] hash table. */ struct SessionChange { - int op; /* One of UPDATE, DELETE, INSERT */ - int bIndirect; /* True if this change is "indirect" */ + u8 op; /* One of UPDATE, DELETE, INSERT */ + u8 bIndirect; /* True if this change is "indirect" */ + int nMaxSize; /* Max size of eventual changeset record */ int nRecord; /* Number of bytes in buffer aRecord[] */ u8 *aRecord; /* Buffer containing old.* record */ SessionChange *pNext; /* For hash-table collisions */ @@ -431,6 +436,26 @@ static int sessionSerializeValue( return SQLITE_OK; } +/* +** Allocate and return a pointer to a buffer nByte bytes in size. If +** pSession is not NULL, increase the sqlite3_session.nMalloc variable +** by the number of bytes allocated. +*/ +static void *sessionMalloc64(sqlite3_session *pSession, i64 nByte){ + void *pRet = sqlite3_malloc64(nByte); + if( pSession ) pSession->nMalloc += sqlite3_msize(pRet); + return pRet; +} + +/* +** Free buffer pFree, which must have been allocated by an earlier +** call to sessionMalloc64(). If pSession is not NULL, decrease the +** sqlite3_session.nMalloc counter by the number of bytes freed. +*/ +static void sessionFree(sqlite3_session *pSession, void *pFree){ + if( pSession ) pSession->nMalloc -= sqlite3_msize(pFree); + sqlite3_free(pFree); +} /* ** This macro is used to calculate hash key values for data structures. In @@ -898,13 +923,19 @@ static int sessionPreupdateEqual( ** Growing the hash table in this case is a performance optimization only, ** it is not required for correct operation. */ -static int sessionGrowHash(int bPatchset, SessionTable *pTab){ +static int sessionGrowHash( + sqlite3_session *pSession, /* For memory accounting. May be NULL */ + int bPatchset, + SessionTable *pTab +){ if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){ int i; SessionChange **apNew; sqlite3_int64 nNew = 2*(sqlite3_int64)(pTab->nChange ? pTab->nChange : 128); - apNew = (SessionChange **)sqlite3_malloc64(sizeof(SessionChange *) * nNew); + apNew = (SessionChange**)sessionMalloc64( + pSession, sizeof(SessionChange*) * nNew + ); if( apNew==0 ){ if( pTab->nChange==0 ){ return SQLITE_ERROR; @@ -925,7 +956,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){ } } - sqlite3_free(pTab->apChange); + sessionFree(pSession, pTab->apChange); pTab->nChange = nNew; pTab->apChange = apNew; } @@ -959,6 +990,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){ ** be freed using sqlite3_free() by the caller */ static int sessionTableInfo( + sqlite3_session *pSession, /* For memory accounting. May be NULL */ sqlite3 *db, /* Database connection */ const char *zDb, /* Name of attached database (e.g. "main") */ const char *zThis, /* Table name */ @@ -1013,7 +1045,7 @@ static int sessionTableInfo( if( rc==SQLITE_OK ){ nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1); - pAlloc = sqlite3_malloc64(nByte); + pAlloc = sessionMalloc64(pSession, nByte); if( pAlloc==0 ){ rc = SQLITE_NOMEM; } @@ -1056,7 +1088,7 @@ static int sessionTableInfo( *pabPK = 0; *pnCol = 0; if( pzTab ) *pzTab = 0; - sqlite3_free(azCol); + sessionFree(pSession, azCol); } sqlite3_finalize(pStmt); return rc; @@ -1078,7 +1110,7 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ if( pTab->nCol==0 ){ u8 *abPK; assert( pTab->azCol==0 || pTab->abPK==0 ); - pSession->rc = sessionTableInfo(pSession->db, pSession->zDb, + pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK ); if( pSession->rc==SQLITE_OK ){ @@ -1092,6 +1124,12 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){ pTab->bStat1 = 1; } + + if( pSession->bEnableSize ){ + pSession->nMaxChangesetSize += ( + 1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1 + ); + } } } return (pSession->rc || pTab->abPK==0); @@ -1137,6 +1175,103 @@ static int sessionStat1Depth(void *pCtx){ return p->hook.xDepth(p->hook.pCtx); } +static int sessionUpdateMaxSize( + int op, + sqlite3_session *pSession, /* Session object pTab is attached to */ + SessionTable *pTab, /* Table that change applies to */ + SessionChange *pC /* Update pC->nMaxSize */ +){ + i64 nNew = 2; + if( pC->op==SQLITE_INSERT ){ + if( op!=SQLITE_DELETE ){ + int ii; + for(ii=0; iinCol; ii++){ + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, ii, &p); + sessionSerializeValue(0, p, &nNew); + } + } + }else if( op==SQLITE_DELETE ){ + nNew += pC->nRecord; + if( sqlite3_preupdate_blobwrite(pSession->db)>=0 ){ + nNew += pC->nRecord; + } + }else{ + int ii; + u8 *pCsr = pC->aRecord; + for(ii=0; iinCol; ii++){ + int bChanged = 1; + int nOld = 0; + int eType; + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, ii, &p); + if( p==0 ){ + return SQLITE_NOMEM; + } + + eType = *pCsr++; + switch( eType ){ + case SQLITE_NULL: + bChanged = sqlite3_value_type(p)!=SQLITE_NULL; + break; + + case SQLITE_FLOAT: + case SQLITE_INTEGER: { + if( eType==sqlite3_value_type(p) ){ + sqlite3_int64 iVal = sessionGetI64(pCsr); + if( eType==SQLITE_INTEGER ){ + bChanged = (iVal!=sqlite3_value_int64(p)); + }else{ + double dVal; + memcpy(&dVal, &iVal, 8); + bChanged = (dVal!=sqlite3_value_double(p)); + } + } + nOld = 8; + pCsr += 8; + break; + } + + default: { + int nByte; + nOld = sessionVarintGet(pCsr, &nByte); + pCsr += nOld; + nOld += nByte; + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + if( eType==sqlite3_value_type(p) + && nByte==sqlite3_value_bytes(p) + && (nByte==0 || 0==memcmp(pCsr, sqlite3_value_blob(p), nByte)) + ){ + bChanged = 0; + } + pCsr += nByte; + break; + } + } + + if( bChanged && pTab->abPK[ii] ){ + nNew = pC->nRecord + 2; + break; + } + + if( bChanged ){ + nNew += 1 + nOld; + sessionSerializeValue(0, p, &nNew); + }else if( pTab->abPK[ii] ){ + nNew += 2 + nOld; + }else{ + nNew += 2; + } + } + } + + if( nNew>pC->nMaxSize ){ + int nIncr = nNew - pC->nMaxSize; + pC->nMaxSize = nNew; + pSession->nMaxChangesetSize += nIncr; + } + return SQLITE_OK; +} /* ** This function is only called from with a pre-update-hook reporting a @@ -1169,7 +1304,7 @@ static void sessionPreupdateOneChange( } /* Grow the hash table if required */ - if( sessionGrowHash(0, pTab) ){ + if( sessionGrowHash(pSession, 0, pTab) ){ pSession->rc = SQLITE_NOMEM; return; } @@ -1210,7 +1345,6 @@ static void sessionPreupdateOneChange( /* Create a new change object containing all the old values (if ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK ** values (if this is an INSERT). */ - SessionChange *pChange; /* New change object */ sqlite3_int64 nByte; /* Number of bytes to allocate */ int i; /* Used to iterate through columns */ @@ -1236,13 +1370,13 @@ static void sessionPreupdateOneChange( } /* Allocate the change object */ - pChange = (SessionChange *)sqlite3_malloc64(nByte); - if( !pChange ){ + pC = (SessionChange *)sessionMalloc64(pSession, nByte); + if( !pC ){ rc = SQLITE_NOMEM; goto error_out; }else{ - memset(pChange, 0, sizeof(SessionChange)); - pChange->aRecord = (u8 *)&pChange[1]; + memset(pC, 0, sizeof(SessionChange)); + pC->aRecord = (u8 *)&pC[1]; } /* Populate the change object. None of the preupdate_old(), @@ -1257,17 +1391,17 @@ static void sessionPreupdateOneChange( }else if( pTab->abPK[i] ){ pSession->hook.xNew(pSession->hook.pCtx, i, &p); } - sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); + sessionSerializeValue(&pC->aRecord[nByte], p, &nByte); } /* Add the change to the hash-table */ if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){ - pChange->bIndirect = 1; + pC->bIndirect = 1; } - pChange->nRecord = nByte; - pChange->op = op; - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; + pC->nRecord = nByte; + pC->op = op; + pC->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pC; }else if( pC->bIndirect ){ /* If the existing change is considered "indirect", but this current @@ -1278,8 +1412,14 @@ static void sessionPreupdateOneChange( pC->bIndirect = 0; } } + + assert( rc==SQLITE_OK ); + if( pSession->bEnableSize ){ + rc = sessionUpdateMaxSize(op, pSession, pTab, pC); + } } + /* If an error has occurred, mark the session object as failed. */ error_out: if( pTab->bStat1 ){ @@ -1609,7 +1749,7 @@ int sqlite3session_diff( int nCol; /* Columns in zFrom.zTbl */ u8 *abPK; const char **azCol = 0; - rc = sessionTableInfo(db, zFrom, zTbl, &nCol, 0, &azCol, &abPK); + rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK); if( rc==SQLITE_OK ){ if( pTo->nCol!=nCol ){ bMismatch = 1; @@ -1707,7 +1847,7 @@ int sqlite3session_create( ** Free the list of table objects passed as the first argument. The contents ** of the changed-rows hash tables are also deleted. */ -static void sessionDeleteTable(SessionTable *pList){ +static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){ SessionTable *pNext; SessionTable *pTab; @@ -1719,12 +1859,12 @@ static void sessionDeleteTable(SessionTable *pList){ SessionChange *pNextChange; for(p=pTab->apChange[i]; p; p=pNextChange){ pNextChange = p->pNext; - sqlite3_free(p); + sessionFree(pSession, p); } } - sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */ - sqlite3_free(pTab->apChange); - sqlite3_free(pTab); + sessionFree(pSession, (char*)pTab->azCol); /* cast works around VC++ bug */ + sessionFree(pSession, pTab->apChange); + sessionFree(pSession, pTab); } } @@ -1752,9 +1892,11 @@ void sqlite3session_delete(sqlite3_session *pSession){ /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ - sessionDeleteTable(pSession->pTable); + sessionDeleteTable(pSession, pSession->pTable); - /* Free the session object itself. */ + /* Assert that all allocations have been freed and then free the + ** session object itself. */ + assert( pSession->nMalloc==0 ); sqlite3_free(pSession); } @@ -1801,7 +1943,8 @@ int sqlite3session_attach( if( !pTab ){ /* Allocate new SessionTable object. */ - pTab = (SessionTable *)sqlite3_malloc64(sizeof(SessionTable) + nName + 1); + int nByte = sizeof(SessionTable) + nName + 1; + pTab = (SessionTable*)sessionMalloc64(pSession, nByte); if( !pTab ){ rc = SQLITE_NOMEM; }else{ @@ -1831,13 +1974,29 @@ int sqlite3session_attach( ** If successful, return zero. Otherwise, if an OOM condition is encountered, ** set *pRc to SQLITE_NOMEM and return non-zero. */ -static int sessionBufferGrow(SessionBuffer *p, size_t nByte, int *pRc){ - if( *pRc==SQLITE_OK && (size_t)(p->nAlloc-p->nBuf)nBuf + nByte; + if( *pRc==SQLITE_OK && nReq>p->nAlloc ){ u8 *aNew; i64 nNew = p->nAlloc ? p->nAlloc : 128; + do { nNew = nNew*2; - }while( (size_t)(nNew-p->nBuf)SESSION_MAX_BUFFER_SZ ){ + nNew = SESSION_MAX_BUFFER_SZ; + if( nNewaBuf, nNew); if( 0==aNew ){ @@ -2398,7 +2557,7 @@ static int sessionGenerateChangeset( int nNoop; /* Size of buffer after writing tbl header */ /* Check the table schema is still Ok. */ - rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK); + rc = sessionTableInfo(0, db, pSession->zDb, zName, &nCol, 0,&azCol,&abPK); if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){ rc = SQLITE_SCHEMA; } @@ -2488,7 +2647,11 @@ int sqlite3session_changeset( int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ void **ppChangeset /* OUT: Buffer containing changeset */ ){ - return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset); + int rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset); + assert( rc || pnChangeset==0 + || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize + ); + return rc; } /* @@ -2573,6 +2736,46 @@ int sqlite3session_isempty(sqlite3_session *pSession){ return (ret==0); } +/* +** Return the amount of heap memory in use. +*/ +sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){ + return pSession->nMalloc; +} + +/* +** Configure the session object passed as the first argument. +*/ +int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){ + int rc = SQLITE_OK; + switch( op ){ + case SQLITE_SESSION_OBJCONFIG_SIZE: { + int iArg = *(int*)pArg; + if( iArg>=0 ){ + if( pSession->pTable ){ + rc = SQLITE_MISUSE; + }else{ + pSession->bEnableSize = (iArg!=0); + } + } + *(int*)pArg = pSession->bEnableSize; + break; + } + + default: + rc = SQLITE_MISUSE; + } + + return rc; +} + +/* +** Return the maximum size of sqlite3session_changeset() output. +*/ +sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession){ + return pSession->nMaxChangesetSize; +} + /* ** Do the work for either sqlite3changeset_start() or start_strm(). */ @@ -2582,7 +2785,8 @@ static int sessionChangesetStart( void *pIn, int nChangeset, /* Size of buffer pChangeset in bytes */ void *pChangeset, /* Pointer to buffer containing changeset */ - int bInvert /* True to invert changeset */ + int bInvert, /* True to invert changeset */ + int bSkipEmpty /* True to skip empty UPDATE changes */ ){ sqlite3_changeset_iter *pRet; /* Iterator to return */ int nByte; /* Number of bytes to allocate for iterator */ @@ -2603,6 +2807,7 @@ static int sessionChangesetStart( pRet->in.pIn = pIn; pRet->in.bEof = (xInput ? 0 : 1); pRet->bInvert = bInvert; + pRet->bSkipEmpty = bSkipEmpty; /* Populate the output variable and return success. */ *pp = pRet; @@ -2617,7 +2822,7 @@ int sqlite3changeset_start( int nChangeset, /* Size of buffer pChangeset in bytes */ void *pChangeset /* Pointer to buffer containing changeset */ ){ - return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0); + return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0, 0); } int sqlite3changeset_start_v2( sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ @@ -2626,7 +2831,7 @@ int sqlite3changeset_start_v2( int flags ){ int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); - return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert); + return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert, 0); } /* @@ -2637,7 +2842,7 @@ int sqlite3changeset_start_strm( int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ){ - return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0); + return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0, 0); } int sqlite3changeset_start_v2_strm( sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ @@ -2646,7 +2851,7 @@ int sqlite3changeset_start_v2_strm( int flags ){ int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); - return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert); + return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert, 0); } /* @@ -2772,11 +2977,14 @@ static int sessionReadRecord( SessionInput *pIn, /* Input data */ int nCol, /* Number of values in record */ u8 *abPK, /* Array of primary key flags, or NULL */ - sqlite3_value **apOut /* Write values to this array */ + sqlite3_value **apOut, /* Write values to this array */ + int *pbEmpty ){ int i; /* Used to iterate through columns */ int rc = SQLITE_OK; + assert( pbEmpty==0 || *pbEmpty==0 ); + if( pbEmpty ) *pbEmpty = 1; for(i=0; iaData[pIn->iNext++]; assert( apOut[i]==0 ); if( eType ){ + if( pbEmpty ) *pbEmpty = 0; apOut[i] = sqlite3ValueNew(0); if( !apOut[i] ) rc = SQLITE_NOMEM; } @@ -2967,31 +3176,27 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){ } /* -** Advance the changeset iterator to the next change. +** Advance the changeset iterator to the next change. The differences between +** this function and sessionChangesetNext() are that ** -** If both paRec and pnRec are NULL, then this function works like the public -** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the -** sqlite3changeset_new() and old() APIs may be used to query for values. -** -** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change -** record is written to *paRec before returning and the number of bytes in -** the record to *pnRec. +** * If pbEmpty is not NULL and the change is a no-op UPDATE (an UPDATE +** that modifies no columns), this function sets (*pbEmpty) to 1. ** -** Either way, this function returns SQLITE_ROW if the iterator is -** successfully advanced to the next change in the changeset, an SQLite -** error code if an error occurs, or SQLITE_DONE if there are no further -** changes in the changeset. +** * If the iterator is configured to skip no-op UPDATEs, +** sessionChangesetNext() does that. This function does not. */ -static int sessionChangesetNext( +static int sessionChangesetNextOne( sqlite3_changeset_iter *p, /* Changeset iterator */ u8 **paRec, /* If non-NULL, store record pointer here */ int *pnRec, /* If non-NULL, store size of record here */ - int *pbNew /* If non-NULL, true if new table */ + int *pbNew, /* If non-NULL, true if new table */ + int *pbEmpty ){ int i; u8 op; assert( (paRec==0 && pnRec==0) || (paRec && pnRec) ); + assert( pbEmpty==0 || *pbEmpty==0 ); /* If the iterator is in the error-state, return immediately. */ if( p->rc!=SQLITE_OK ) return p->rc; @@ -3064,13 +3269,13 @@ static int sessionChangesetNext( /* If this is an UPDATE or DELETE, read the old.* record. */ if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){ u8 *abPK = p->bPatchset ? p->abPK : 0; - p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld); + p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld, 0); if( p->rc!=SQLITE_OK ) return p->rc; } /* If this is an INSERT or UPDATE, read the new.* record. */ if( p->op!=SQLITE_DELETE ){ - p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew); + p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew, pbEmpty); if( p->rc!=SQLITE_OK ) return p->rc; } @@ -3097,6 +3302,37 @@ static int sessionChangesetNext( return SQLITE_ROW; } +/* +** Advance the changeset iterator to the next change. +** +** If both paRec and pnRec are NULL, then this function works like the public +** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the +** sqlite3changeset_new() and old() APIs may be used to query for values. +** +** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change +** record is written to *paRec before returning and the number of bytes in +** the record to *pnRec. +** +** Either way, this function returns SQLITE_ROW if the iterator is +** successfully advanced to the next change in the changeset, an SQLite +** error code if an error occurs, or SQLITE_DONE if there are no further +** changes in the changeset. +*/ +static int sessionChangesetNext( + sqlite3_changeset_iter *p, /* Changeset iterator */ + u8 **paRec, /* If non-NULL, store record pointer here */ + int *pnRec, /* If non-NULL, store size of record here */ + int *pbNew /* If non-NULL, true if new table */ +){ + int bEmpty; + int rc; + do { + bEmpty = 0; + rc = sessionChangesetNextOne(p, paRec, pnRec, pbNew, &bEmpty); + }while( rc==SQLITE_ROW && p->bSkipEmpty && bEmpty); + return rc; +} + /* ** Advance an iterator created by sqlite3changeset_start() to the next ** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE @@ -3369,9 +3605,9 @@ static int sessionChangesetInvert( /* Read the old.* and new.* records for the update change. */ pInput->iNext += 2; - rc = sessionReadRecord(pInput, nCol, 0, &apVal[0]); + rc = sessionReadRecord(pInput, nCol, 0, &apVal[0], 0); if( rc==SQLITE_OK ){ - rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol]); + rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol], 0); } /* Write the new old.* record. Consists of the PK columns from the @@ -3472,16 +3708,25 @@ int sqlite3changeset_invert_strm( return rc; } + +typedef struct SessionUpdate SessionUpdate; +struct SessionUpdate { + sqlite3_stmt *pStmt; + u32 *aMask; + SessionUpdate *pNext; +}; + typedef struct SessionApplyCtx SessionApplyCtx; struct SessionApplyCtx { sqlite3 *db; sqlite3_stmt *pDelete; /* DELETE statement */ - sqlite3_stmt *pUpdate; /* UPDATE statement */ sqlite3_stmt *pInsert; /* INSERT statement */ sqlite3_stmt *pSelect; /* SELECT statement */ int nCol; /* Size of azCol[] and abPK[] arrays */ const char **azCol; /* Array of column names */ u8 *abPK; /* Boolean array - true if column is in PK */ + u32 *aUpdateMask; /* Used by sessionUpdateFind */ + SessionUpdate *pUp; int bStat1; /* True if table is sqlite_stat1 */ int bDeferConstraints; /* True to defer constraints */ int bInvertConstraints; /* Invert when iterating constraints buffer */ @@ -3491,6 +3736,167 @@ struct SessionApplyCtx { u8 bRebase; /* True to collect rebase information */ }; +/* Number of prepared UPDATE statements to cache. */ +#define SESSION_UPDATE_CACHE_SZ 12 + +/* +** Find a prepared UPDATE statement suitable for the UPDATE step currently +** being visited by the iterator. The UPDATE is of the form: +** +** UPDATE tbl SET col = ?, col2 = ? WHERE pk1 IS ? AND pk2 IS ? +*/ +static int sessionUpdateFind( + sqlite3_changeset_iter *pIter, + SessionApplyCtx *p, + int bPatchset, + sqlite3_stmt **ppStmt +){ + int rc = SQLITE_OK; + SessionUpdate *pUp = 0; + int nCol = pIter->nCol; + int nU32 = (pIter->nCol+33)/32; + int ii; + + if( p->aUpdateMask==0 ){ + p->aUpdateMask = sqlite3_malloc(nU32*sizeof(u32)); + if( p->aUpdateMask==0 ){ + rc = SQLITE_NOMEM; + } + } + + if( rc==SQLITE_OK ){ + memset(p->aUpdateMask, 0, nU32*sizeof(u32)); + rc = SQLITE_CORRUPT; + for(ii=0; iinCol; ii++){ + if( sessionChangesetNew(pIter, ii) ){ + p->aUpdateMask[ii/32] |= (1<<(ii%32)); + rc = SQLITE_OK; + } + } + } + + if( rc==SQLITE_OK ){ + if( bPatchset ) p->aUpdateMask[nCol/32] |= (1<<(nCol%32)); + + if( p->pUp ){ + int nUp = 0; + SessionUpdate **pp = &p->pUp; + while( 1 ){ + nUp++; + if( 0==memcmp(p->aUpdateMask, (*pp)->aMask, nU32*sizeof(u32)) ){ + pUp = *pp; + *pp = pUp->pNext; + pUp->pNext = p->pUp; + p->pUp = pUp; + break; + } + + if( (*pp)->pNext ){ + pp = &(*pp)->pNext; + }else{ + if( nUp>=SESSION_UPDATE_CACHE_SZ ){ + sqlite3_finalize((*pp)->pStmt); + sqlite3_free(*pp); + *pp = 0; + } + break; + } + } + } + + if( pUp==0 ){ + int nByte = sizeof(SessionUpdate) * nU32*sizeof(u32); + int bStat1 = (sqlite3_stricmp(pIter->zTab, "sqlite_stat1")==0); + pUp = (SessionUpdate*)sqlite3_malloc(nByte); + if( pUp==0 ){ + rc = SQLITE_NOMEM; + }else{ + const char *zSep = ""; + SessionBuffer buf; + + memset(&buf, 0, sizeof(buf)); + pUp->aMask = (u32*)&pUp[1]; + memcpy(pUp->aMask, p->aUpdateMask, nU32*sizeof(u32)); + + sessionAppendStr(&buf, "UPDATE main.", &rc); + sessionAppendIdent(&buf, pIter->zTab, &rc); + sessionAppendStr(&buf, " SET ", &rc); + + /* Create the assignments part of the UPDATE */ + for(ii=0; iinCol; ii++){ + if( p->abPK[ii]==0 && sessionChangesetNew(pIter, ii) ){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[ii], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, ii*2+1, &rc); + zSep = ", "; + } + } + + /* Create the WHERE clause part of the UPDATE */ + zSep = ""; + sessionAppendStr(&buf, " WHERE ", &rc); + for(ii=0; iinCol; ii++){ + if( p->abPK[ii] || (bPatchset==0 && sessionChangesetOld(pIter, ii)) ){ + sessionAppendStr(&buf, zSep, &rc); + if( bStat1 && ii==1 ){ + assert( sqlite3_stricmp(p->azCol[ii], "idx")==0 ); + sessionAppendStr(&buf, + "idx IS CASE " + "WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL " + "ELSE ?4 END ", &rc + ); + }else{ + sessionAppendIdent(&buf, p->azCol[ii], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, ii*2+2, &rc); + } + zSep = " AND "; + } + } + + if( rc==SQLITE_OK ){ + char *zSql = (char*)buf.aBuf; + rc = sqlite3_prepare_v2(p->db, zSql, buf.nBuf, &pUp->pStmt, 0); + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(pUp); + pUp = 0; + }else{ + pUp->pNext = p->pUp; + p->pUp = pUp; + } + sqlite3_free(buf.aBuf); + } + } + } + + assert( (rc==SQLITE_OK)==(pUp!=0) ); + if( pUp ){ + *ppStmt = pUp->pStmt; + }else{ + *ppStmt = 0; + } + return rc; +} + +/* +** Free all cached UPDATE statements. +*/ +static void sessionUpdateFree(SessionApplyCtx *p){ + SessionUpdate *pUp; + SessionUpdate *pNext; + for(pUp=p->pUp; pUp; pUp=pNext){ + pNext = pUp->pNext; + sqlite3_finalize(pUp->pStmt); + sqlite3_free(pUp); + } + p->pUp = 0; + sqlite3_free(p->aUpdateMask); + p->aUpdateMask = 0; +} + /* ** Formulate a statement to DELETE a row from database db. Assuming a table ** structure like this: @@ -3560,103 +3966,6 @@ static int sessionDeleteRow( return rc; } -/* -** Formulate and prepare a statement to UPDATE a row from database db. -** Assuming a table structure like this: -** -** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); -** -** The UPDATE statement looks like this: -** -** UPDATE x SET -** a = CASE WHEN ?2 THEN ?3 ELSE a END, -** b = CASE WHEN ?5 THEN ?6 ELSE b END, -** c = CASE WHEN ?8 THEN ?9 ELSE c END, -** d = CASE WHEN ?11 THEN ?12 ELSE d END -** WHERE a = ?1 AND c = ?7 AND (?13 OR -** (?5==0 OR b IS ?4) AND (?11==0 OR d IS ?10) AND -** ) -** -** For each column in the table, there are three variables to bind: -** -** ?(i*3+1) The old.* value of the column, if any. -** ?(i*3+2) A boolean flag indicating that the value is being modified. -** ?(i*3+3) The new.* value of the column, if any. -** -** Also, a boolean flag that, if set to true, causes the statement to update -** a row even if the non-PK values do not match. This is required if the -** conflict-handler is invoked with CHANGESET_DATA and returns -** CHANGESET_REPLACE. This is variable "?(nCol*3+1)". -** -** If successful, SQLITE_OK is returned and SessionApplyCtx.pUpdate is left -** pointing to the prepared version of the SQL statement. -*/ -static int sessionUpdateRow( - sqlite3 *db, /* Database handle */ - const char *zTab, /* Table name */ - SessionApplyCtx *p /* Session changeset-apply context */ -){ - int rc = SQLITE_OK; - int i; - const char *zSep = ""; - SessionBuffer buf = {0, 0, 0}; - - /* Append "UPDATE tbl SET " */ - sessionAppendStr(&buf, "UPDATE main.", &rc); - sessionAppendIdent(&buf, zTab, &rc); - sessionAppendStr(&buf, " SET ", &rc); - - /* Append the assignments */ - for(i=0; inCol; i++){ - sessionAppendStr(&buf, zSep, &rc); - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " = CASE WHEN ?", &rc); - sessionAppendInteger(&buf, i*3+2, &rc); - sessionAppendStr(&buf, " THEN ?", &rc); - sessionAppendInteger(&buf, i*3+3, &rc); - sessionAppendStr(&buf, " ELSE ", &rc); - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " END", &rc); - zSep = ", "; - } - - /* Append the PK part of the WHERE clause */ - sessionAppendStr(&buf, " WHERE ", &rc); - for(i=0; inCol; i++){ - if( p->abPK[i] ){ - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " = ?", &rc); - sessionAppendInteger(&buf, i*3+1, &rc); - sessionAppendStr(&buf, " AND ", &rc); - } - } - - /* Append the non-PK part of the WHERE clause */ - sessionAppendStr(&buf, " (?", &rc); - sessionAppendInteger(&buf, p->nCol*3+1, &rc); - sessionAppendStr(&buf, " OR 1", &rc); - for(i=0; inCol; i++){ - if( !p->abPK[i] ){ - sessionAppendStr(&buf, " AND (?", &rc); - sessionAppendInteger(&buf, i*3+2, &rc); - sessionAppendStr(&buf, "=0 OR ", &rc); - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " IS ?", &rc); - sessionAppendInteger(&buf, i*3+1, &rc); - sessionAppendStr(&buf, ")", &rc); - } - } - sessionAppendStr(&buf, ")", &rc); - - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0); - } - sqlite3_free(buf.aBuf); - - return rc; -} - - /* ** Formulate and prepare an SQL statement to query table zTab by primary ** key. Assuming the following table structure: @@ -3737,17 +4046,6 @@ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ "?3)" ); } - if( rc==SQLITE_OK ){ - rc = sessionPrepare(db, &p->pUpdate, - "UPDATE main.sqlite_stat1 SET " - "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, " - "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, " - "stat = CASE WHEN ?8 THEN ?9 ELSE stat END " - "WHERE tbl=?1 AND idx IS " - "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END " - "AND (?10 OR ?8=0 OR stat IS ?7)" - ); - } if( rc==SQLITE_OK ){ rc = sessionPrepare(db, &p->pDelete, "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " @@ -4064,7 +4362,7 @@ static int sessionApplyOneOp( int nCol; int rc = SQLITE_OK; - assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect ); + assert( p->pDelete && p->pInsert && p->pSelect ); assert( p->azCol && p->abPK ); assert( !pbReplace || *pbReplace==0 ); @@ -4104,29 +4402,28 @@ static int sessionApplyOneOp( }else if( op==SQLITE_UPDATE ){ int i; + sqlite3_stmt *pUp = 0; + int bPatchset = (pbRetry==0 || pIter->bPatchset); + + rc = sessionUpdateFind(pIter, p, bPatchset, &pUp); /* Bind values to the UPDATE statement. */ for(i=0; rc==SQLITE_OK && ipUpdate, i*3+2, !!pNew); - if( pOld ){ - rc = sessionBindValue(p->pUpdate, i*3+1, pOld); + if( p->abPK[i] || (bPatchset==0 && pOld) ){ + rc = sessionBindValue(pUp, i*2+2, pOld); } if( rc==SQLITE_OK && pNew ){ - rc = sessionBindValue(p->pUpdate, i*3+3, pNew); + rc = sessionBindValue(pUp, i*2+1, pNew); } } - if( rc==SQLITE_OK ){ - sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0 || pIter->bPatchset); - } if( rc!=SQLITE_OK ) return rc; /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict, ** the result will be SQLITE_OK with 0 rows modified. */ - sqlite3_step(p->pUpdate); - rc = sqlite3_reset(p->pUpdate); + sqlite3_step(pUp); + rc = sqlite3_reset(pUp); if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ /* A NOTFOUND or DATA error. Search the table to see if it contains @@ -4258,7 +4555,7 @@ static int sessionRetryConstraints( memset(&pApply->constraints, 0, sizeof(SessionBuffer)); rc = sessionChangesetStart( - &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints + &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1 ); if( rc==SQLITE_OK ){ size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); @@ -4349,14 +4646,13 @@ static int sessionChangesetApply( ); if( rc!=SQLITE_OK ) break; + sessionUpdateFree(&sApply); sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_finalize(sApply.pDelete); - sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pInsert); sqlite3_finalize(sApply.pSelect); sApply.db = db; sApply.pDelete = 0; - sApply.pUpdate = 0; sApply.pInsert = 0; sApply.pSelect = 0; sApply.nCol = 0; @@ -4384,7 +4680,7 @@ static int sessionChangesetApply( int i; sqlite3changeset_pk(pIter, &abPK, 0); - rc = sessionTableInfo( + rc = sessionTableInfo(0, db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK ); if( rc!=SQLITE_OK ) break; @@ -4420,11 +4716,10 @@ static int sessionChangesetApply( } sApply.bStat1 = 1; }else{ - if((rc = sessionSelectRow(db, zTab, &sApply)) - || (rc = sessionUpdateRow(db, zTab, &sApply)) - || (rc = sessionDeleteRow(db, zTab, &sApply)) - || (rc = sessionInsertRow(db, zTab, &sApply)) - ){ + if( (rc = sessionSelectRow(db, zTab, &sApply)) + || (rc = sessionDeleteRow(db, zTab, &sApply)) + || (rc = sessionInsertRow(db, zTab, &sApply)) + ){ break; } sApply.bStat1 = 0; @@ -4483,9 +4778,9 @@ static int sessionChangesetApply( *pnRebase = sApply.rebase.nBuf; sApply.rebase.aBuf = 0; } + sessionUpdateFree(&sApply); sqlite3_finalize(sApply.pInsert); sqlite3_finalize(sApply.pDelete); - sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pSelect); sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_free((char*)sApply.constraints.aBuf); @@ -4516,8 +4811,8 @@ int sqlite3changeset_apply_v2( int flags ){ sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset,bInverse); + int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1); if( rc==SQLITE_OK ){ rc = sessionChangesetApply( db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags @@ -4575,7 +4870,7 @@ int sqlite3changeset_apply_v2_strm( ){ sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse); + int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1); if( rc==SQLITE_OK ){ rc = sessionChangesetApply( db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags @@ -4863,7 +5158,7 @@ static int sessionChangesetToHash( } } - if( sessionGrowHash(pIter->bPatchset, pTab) ){ + if( sessionGrowHash(0, pIter->bPatchset, pTab) ){ rc = SQLITE_NOMEM; break; } @@ -5049,7 +5344,7 @@ int sqlite3changegroup_output_strm( */ void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ if( pGrp ){ - sessionDeleteTable(pGrp->pList); + sessionDeleteTable(0, pGrp->pList); sqlite3_free(pGrp); } } @@ -5195,7 +5490,7 @@ static void sessionAppendPartialUpdate( int n1 = sessionSerialLen(a1); int n2 = sessionSerialLen(a2); if( pIter->abPK[i] || a2[0]==0 ){ - if( !pIter->abPK[i] ) bData = 1; + if( !pIter->abPK[i] && a1[0] ) bData = 1; memcpy(pOut, a1, n1); pOut += n1; }else if( a2[0]!=0xFF ){ @@ -5450,7 +5745,7 @@ int sqlite3rebaser_rebase_strm( */ void sqlite3rebaser_delete(sqlite3_rebaser *p){ if( p ){ - sessionDeleteTable(p->grp.pList); + sessionDeleteTable(0, p->grp.pList); sqlite3_free(p); } } diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 1ed3b550f..10d013390 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -79,6 +79,38 @@ int sqlite3session_create( */ void sqlite3session_delete(sqlite3_session *pSession); +/* +** CAPIREF: Conigure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid value for the second parameter is +** [SQLITE_SESSION_OBJCONFIG_SIZE]. +** +** Arguments for sqlite3session_object_config() +** +** The following values may passed as the the 4th parameter to +** sqlite3session_object_config(). +** +**

    SQLITE_SESSION_OBJCONFIG_SIZE
    +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 /* ** CAPI3REF: Enable Or Disable A Session Object @@ -323,6 +355,22 @@ int sqlite3session_changeset( void **ppChangeset /* OUT: Buffer containing changeset */ ); +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + /* ** CAPI3REF: Load The Difference Between Tables Into A Session ** METHOD: sqlite3_session @@ -440,6 +488,14 @@ int sqlite3session_patchset( */ int sqlite3session_isempty(sqlite3_session *pSession); +/* +** CAPI3REF: Query for the amount of heap memory used by a session object. +** +** This API returns the total amount of heap memory in bytes currently +** used by the session object passed as the only argument. +*/ +sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession); + /* ** CAPI3REF: Create An Iterator To Traverse A Changeset ** CONSTRUCTOR: sqlite3_changeset_iter @@ -542,18 +598,23 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter); ** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this ** is not the case, this function returns [SQLITE_MISUSE]. ** -** If argument pzTab is not NULL, then *pzTab is set to point to a -** nul-terminated utf-8 encoded string containing the name of the table -** affected by the current change. The buffer remains valid until either -** sqlite3changeset_next() is called on the iterator or until the -** conflict-handler function returns. If pnCol is not NULL, then *pnCol is -** set to the number of columns in the table affected by the change. If -** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change +** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three +** outputs are set through these pointers: +** +** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the type of change that the iterator currently points to; +** +** *pnCol is set to the number of columns in the table affected by the change; and +** +** *pzTab is set to point to a nul-terminated utf-8 encoded string containing +** the name of the table affected by the current change. The buffer remains +** valid until either sqlite3changeset_next() is called on the iterator +** or until the conflict-handler function returns. +** +** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change ** is an indirect change, or false (0) otherwise. See the documentation for ** [sqlite3session_indirect()] for a description of direct and indirect -** changes. Finally, if pOp is not NULL, then *pOp is set to one of -** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the -** type of change that the iterator currently points to. +** changes. ** ** If no error occurs, SQLITE_OK is returned. If an error does occur, an ** SQLite error code is returned. The values of the output variables may not diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 82f30979f..f2fc77eee 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -146,7 +146,10 @@ static int SQLITE_TCLAPI test_sql_exec_changeset( static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){ Tcl_Obj *pObj; int iVal = 0; - pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY); + Tcl_Obj *pName = Tcl_NewStringObj(zVar, -1); + Tcl_IncrRefCount(pName); + pObj = Tcl_ObjGetVar2(interp, pName, 0, TCL_GLOBAL_ONLY); + Tcl_DecrRefCount(pName); if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal); return iVal; } @@ -242,6 +245,9 @@ static int SQLITE_TCLAPI test_session_cmd( { "table_filter", 1, "SCRIPT", }, /* 6 */ { "patchset", 0, "", }, /* 7 */ { "diff", 2, "FROMDB TBL", }, /* 8 */ + { "memory_used", 0, "", }, /* 9 */ + { "changeset_size", 0, "", }, /* 10 */ + { "object_config_size", 1, "INTEGER", }, /* 11 */ { 0 } }; int iSub; @@ -347,6 +353,35 @@ static int SQLITE_TCLAPI test_session_cmd( } break; } + + case 9: { /* memory_used */ + sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc)); + break; + } + + case 10: { + sqlite3_int64 nSize = sqlite3session_changeset_size(pSession); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize)); + break; + } + case 11: { + int rc; + int iArg; + if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){ + return TCL_ERROR; + } + rc = sqlite3session_object_config( + pSession, SQLITE_SESSION_OBJCONFIG_SIZE, &iArg + ); + if( rc!=SQLITE_OK ){ + extern const char *sqlite3ErrName(int); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + }else{ + Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg)); + } + break; + } } return TCL_OK; @@ -372,6 +407,7 @@ static int SQLITE_TCLAPI test_sqlite3session( Tcl_CmdInfo info; int rc; /* sqlite3session_create() return code */ TestSession *p; /* New wrapper object */ + int iArg = -1; if( objc!=4 ){ Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME"); @@ -392,6 +428,13 @@ static int SQLITE_TCLAPI test_sqlite3session( return test_session_error(interp, rc, 0); } + /* Query the SQLITE_SESSION_OBJCONFIG_SIZE option to ensure that it + ** is clear by default. Then set it. */ + sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg); + assert( iArg==0 ); + iArg = 1; + sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg); + Tcl_CreateObjCommand( interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p, test_session_del diff --git a/main.mk b/main.mk index e47e85fdf..16d72ff92 100644 --- a/main.mk +++ b/main.mk @@ -360,6 +360,7 @@ TESTSRC = \ # TESTSRC += \ $(TOP)/ext/misc/amatch.c \ + $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/cksumvfs.c \ $(TOP)/ext/misc/closure.c \ @@ -438,7 +439,8 @@ TESTSRC2 = \ $(TOP)/ext/async/sqlite3async.c \ $(TOP)/ext/misc/stmt.c \ $(TOP)/ext/session/sqlite3session.c \ - $(TOP)/ext/session/test_session.c + $(TOP)/ext/session/test_session.c \ + fts5.c # Header files used by all library source files. # @@ -537,7 +539,6 @@ FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 -FUZZCHECK_OPT += -DSQLITE_ENABLE_DESERIALIZE FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4 FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY @@ -550,13 +551,13 @@ ST_OPT = -DSQLITE_THREADSAFE=0 # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. # -all: sqlite3.h libsqlite3.a sqlite3$(EXE) +all: sqlite3.h sqlite3ext.h libsqlite3.a sqlite3$(EXE) -libsqlite3.a: $(LIBOBJ) +libsqlite3.a: sqlite3.h $(LIBOBJ) $(AR) libsqlite3.a $(LIBOBJ) $(RANLIB) libsqlite3.a -sqlite3$(EXE): shell.c libsqlite3.a sqlite3.h +sqlite3$(EXE): sqlite3.h libsqlite3.a shell.c $(TCCX) $(READLINE_FLAGS) -o sqlite3$(EXE) $(SHELL_OPT) \ shell.c libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB) @@ -590,7 +591,6 @@ dbfuzz$(EXE): $(TOP)/test/dbfuzz.c sqlite3.c sqlite3.h DBFUZZ2_OPTS = \ -DSQLITE_THREADSAFE=0 \ -DSQLITE_OMIT_LOAD_EXTENSION \ - -DSQLITE_ENABLE_DESERIALIZE \ -DSQLITE_DEBUG \ -DSQLITE_ENABLE_DBSTAT_VTAB \ -DSQLITE_ENABLE_BYTECODE_VTAB \ @@ -747,6 +747,7 @@ SHELL_SRC = \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ + $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ $(TOP)/ext/misc/uint.c \ @@ -824,13 +825,13 @@ fts3_unicode2.o: $(TOP)/ext/fts3/fts3_unicode2.c $(HDR) $(EXTHDR) fts3_write.o: $(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR) $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_write.c -fts5.o: fts5.c +fts5.o: fts5.c sqlite3ext.h sqlite3.h $(TCCX) -DSQLITE_CORE -c fts5.c -json1.o: $(TOP)/ext/misc/json1.c +json1.o: $(TOP)/ext/misc/json1.c sqlite3ext.h sqlite3.h $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/misc/json1.c -stmt.o: $(TOP)/ext/misc/stmt.c +stmt.o: $(TOP)/ext/misc/stmt.c sqlite3ext.h sqlite3.h $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/misc/stmt.c rtree.o: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR) @@ -954,7 +955,7 @@ fuzztest: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz- ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db - valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA) + valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M $(FUZZDATA) valgrind ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db # The veryquick.test TCL tests. @@ -1079,6 +1080,9 @@ rbu$(EXE): $(TOP)/ext/rbu/rbu.c $(TOP)/ext/rbu/sqlite3rbu.c sqlite3.o loadfts: $(TOP)/tool/loadfts.c libsqlite3.a $(TCC) $(TOP)/tool/loadfts.c libsqlite3.a -o loadfts $(THREADLIB) +threadtest5: $(TOP)/test/threadtest5.c libsqlite3.a + $(TCC) $(TOP)/test/threadtest5.c libsqlite3.a -o threadtest5 $(THREADLIB) + # This target will fail if the SQLite amalgamation contains any exported # symbols that do not begin with "sqlite3_". It is run as part of the # releasetest.tcl script. @@ -1141,3 +1145,4 @@ clean: rm -f sqldiff sqldiff.exe rm -f fts5.* fts5parse.* rm -f lsm.h lsm1.c + rm -f threadtest5 diff --git a/manifest b/manifest index 37ade307d..a20358755 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.34.1 -D 2021-01-20T14:10:07.150 +C Version\s3.36.0 +D 2021-06-18T18:36:39.378 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 0e88f5d095213a9ccd45c5bbd871c8ead498f886dff4493471fbf48b1f867f9d +F Makefile.in ff5ac24ec9cb700135ee4453fa14c18ea371e94250d3673bda1f2cb56e1108ce F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 -F Makefile.msc dd10dbf63b2f8ac3e2f0542963a21bc69058976ac4355165f212a31c83d17f44 -F README.md 1514a365ffca3c138e00c5cc839906108a01011a6b082bad19b09781e3aa498a -F VERSION 9633e2edacc7963299a285b89cbeb832f73316c9ccc58f9a70701a5e8b16513c +F Makefile.msc 8285e1ea7264f32b08702866d69dd127c0f663bbc2002323d853c68c27c6b6e4 +F README.md 27fb76aa7eb57ed63a53bbba7292b6bf71f51125554f79f16b5d040edd1e6110 +F VERSION 8c392f6d70d82e513a0eac622bdf23c43df3e084cb630288beba8cef3012c812 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -15,10 +15,10 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc e0f1dafc48d000fd6ddfdb01815271528db55cbc7299ca888df5b93367f0d5a4 +F autoconf/Makefile.msc d146a08ebbdf7f881ba600a49cd8dce40c4c807addcdb4b9b6a507e4b40ce837 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 4f04b0819303aabaa35fff5f7b257fb0c1ef95f1 -F autoconf/configure.ac 3cd933b959fe514eebd1ca1717dfddbf2c9b825b6bc2c5f744deaf5d63af9288 +F autoconf/configure.ac a8ba2a9e61216f5093d44f3b7d2cb8fe1890d6b7dc330a02f802d8efaa1fdc79 F autoconf/tea/Makefile.in b438a7020446c8a8156e8d97c8914a04833da6fd F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 @@ -29,16 +29,16 @@ F autoconf/tea/pkgIndex.tcl.in 3ef61715cf1c7bdcff56947ffadb26bc991ca39d F autoconf/tea/tclconfig/install-sh bdd5e293591621ae60d9824d86a4b1c5f22c3d00 F autoconf/tea/tclconfig/tcl.m4 66ddf0a5d5e4b1d29bff472c0985fd7fa89d0fb5 F autoconf/tea/win/makefile.vc a5ff708245260c2794c6aaa0151efe5403d5896566eaf096747be0d9075284e4 -F autoconf/tea/win/nmakehlp.c 247538ad8e8c508f33c03ec1fbd67d3a07ef6291 +F autoconf/tea/win/nmakehlp.c 190bf8b40f6f90eeb6cbc1e5c13a19fc7ad870e479e0314c88174f71bf9e28a7 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 10bbf6fa13320dc9b6197aa9574bd7360f379f904a53ad632ddb310099cf148d x -F configure.ac 73545c21eebcef9398d85c982c7be260f07708256778221b541f83ae8c6f61eb +F configure c658869fc056a2460a2212fb2960e410cd24e3fb9c55640fbc78d15b48810936 x +F configure.ac 4e4b58b32f88c8da9914a2f2c3158f80e69907eccc019fcc7e3ba14ffd91c640 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd -F doc/lemon.html c5d8ba85ac1daef7be8c2d389899480eb62451ff5c09b0c28ff8157bb8770746 +F doc/lemon.html efc0cd2345d66905505d98f862e1c571512def0ceb5b016cb658fd4918eb76a3 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 @@ -50,8 +50,8 @@ F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test dba6e752cc701621771f925f3872b183fa688f7b4a9f4822631fc02bdbffc45a -F ext/expert/sqlite3expert.c 216e250acc54b8aaa9ff5c1e1b2256788abc3994dd1d757626102718122e6a91 +F ext/expert/expert1.test 6703fd74711daf8230240680b0a348d0720e28819b602701adfbd32457fdcddd +F ext/expert/sqlite3expert.c de51b187c629a4c4264d5de0b77862641e11426f7a963a92abf2d4077085fc8c F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e @@ -84,25 +84,25 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 4809e0b05af4519ad8bfa13d684f7ad635d1390a758299d2302f7e85c48ec160 +F ext/fts3/fts3.c 95f55e24550c01c2a325d09c9ea8fdff61e923a4675f8545b28bf3c470e57dfb F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h 045179f538c478ced266ca14327269cde8ad8d573c5be902230a5ebaa5636c59 -F ext/fts3/fts3_aux.c 96708c8b3a7d9b8ca1b68ea2b7e503e283f20e95f145becadedfad096dbd0f34 -F ext/fts3/fts3_expr.c f081e38da641724cd72c20e23b71db2bf4d0c9517c14637442f6910259f11a34 +F ext/fts3/fts3Int.h bde280294d56ff50ee29d03e5140f0b6953b44d1c969bb5831e8ae85e3e76715 +F ext/fts3/fts3_aux.c 1af58af8f2b00a49f4fb1c2602f8da2054ad60076f46c8ebf85c5410eccccb65 +F ext/fts3/fts3_expr.c 5853cd7a35a79d193614add9b4c461b2d56f465d90899ca4309f05d9d1536558 F ext/fts3/fts3_hash.c 8b6e31bfb0844c27dc6092c2620bdb1fca17ed613072db057d96952c6bdb48b7 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf F ext/fts3/fts3_icu.c 305ce7fb6036484085b5556a9c8e62acdc7763f0f4cdf5fd538212a9f3720116 F ext/fts3/fts3_porter.c 3565faf04b626cddf85f03825e86056a4562c009 -F ext/fts3/fts3_snippet.c 86e7e947a176f0f005720b3ca17631aca2fd2f9daa6729d4adbf2d16ab1b9613 +F ext/fts3/fts3_snippet.c 57e1965906f3ecfcb5251a14aea98ac2d2dd1868a123bcd5a569601719ab9ead F ext/fts3/fts3_term.c f45a1e7c6ef464abb1231245d123dae12266b69e05cc56e14045b76591ae92d1 -F ext/fts3/fts3_test.c 73b16e229e517c1b1f0fb8e1046182a4e5dbc8dbe6eea8a5d4353fcce7dbbf39 +F ext/fts3/fts3_test.c d8d7b2734f894e8a489987447658e374cdd3a3bc8575c401decf1911cb7c6454 F ext/fts3/fts3_tokenize_vtab.c 8d15b148e7d88a4280389a200b26e8d52abda4c4ec2e9a35e9d7a1fa50e5aa03 F ext/fts3/fts3_tokenizer.c 6d8fc150c48238955d5182bf661498db0dd473c8a2a80e00c16994a646fa96e7 F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004 -F ext/fts3/fts3_unicode.c 4b9af6151c29b35ed09574937083cece7c31e911f69615e168a39677569b684d +F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c 723ed1b11ed46ad1b3a23c0d69fa39e77986783a82d5711bf87a5ce29e0a3b52 +F ext/fts3/fts3_write.c 98edfd77aeb53afcb26d8de3ed0a87f16468ee05f84f8c1752e6e378c354cd7a F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 @@ -115,16 +115,16 @@ F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 F ext/fts5/fts5.h c132a9323f22a972c4c93a8d5a3d901113a6e612faf30ca8e695788438c5ca2a F ext/fts5/fts5Int.h 26c74dd5776f798436fbf604a0bf0e8de263b35b5060b05c15f9085845d9fda2 F ext/fts5/fts5_aux.c f558e1fb9f0f86a4f7489e258c162e1f947de5ff2709087fbb465fddb7092f98 -F ext/fts5/fts5_buffer.c 5a5fe0159752c0fb0a5a93c722e9db2662822709490769d482b76a6dc8aaca70 -F ext/fts5/fts5_config.c be54f44fca491e96c6923a4b9a736f2da2b13811600eb6e38d1bcc91c4ea2e61 -F ext/fts5/fts5_expr.c e527e3a7410393075598cec544e3831798a8c88b3e8878e2cfb7cb147113e925 +F ext/fts5/fts5_buffer.c 605f509f88c73e9ab41323cb6cf74932f936acf3f7d9c87bd0d3c599b4c90281 +F ext/fts5/fts5_config.c 8336d0ff6db0933f63cfec8ae0ab76e68393259cbccc0b46e1f79f7fa1842ff3 +F ext/fts5/fts5_expr.c 9462249a3bb82d0e49b163500f9d2197c2e4cd95bf440a9bbfc3906b22ea1e1b F ext/fts5/fts5_hash.c 1aa93c9b5f461afba66701ee226297dc78402b3bdde81e90a10de5fe3df14959 -F ext/fts5/fts5_index.c 9f152a596df3a2227ddd59adb82549c2b6858ab36bc1d26fd8933f43d3d63d36 -F ext/fts5/fts5_main.c b4e4931c7fcc9acfa0c3b8b5e5e80b5b424b8d9207aae3a22b674bd35ccf149d +F ext/fts5/fts5_index.c eb1864c6abacf08d959956183a55a4f9767af76be289f2bb519bb0f197b3fd72 +F ext/fts5/fts5_main.c 35ebbcae681a4a40027c47bc2e94d7e7c81e331dc406bb9b23c546454ee8f98a F ext/fts5/fts5_storage.c 58ba71e6cd3d43a5735815e7956ee167babb4d2cbfe206905174792af4d09d75 -F ext/fts5/fts5_tcl.c 39bcbae507f594aad778172fa914cad0f585bf92fd3b078c686e249282db0d95 +F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee -F ext/fts5/fts5_test_tok.c f96c6e193c466711d6d7828d5f190407fe7ab897062d371426dd3036f01258e7 +F ext/fts5/fts5_test_tok.c a2bed8edb25f6432e8cdb62aad5916935c19dba8dac2b8324950cfff397e25ff F ext/fts5/fts5_tokenize.c 5e251efb0f1af99a25ed50010ba6b1ad1250aca5921af1988fdcabe5ebc3cb43 F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80 @@ -160,13 +160,13 @@ F ext/fts5/test/fts5connect.test 08030168fc96fc278fa81f28654fb7e90566f33aff269c0 F ext/fts5/test/fts5content.test 213506436fb2c87567b8e31f6d43ab30aab99354cec74ed679f22aad0cdbf283 F ext/fts5/test/fts5corrupt.test 77ae6f41a7eba10620efb921cf7dbe218b0ef232b04519deb43581cb17a57ebe F ext/fts5/test/fts5corrupt2.test 7453752ba12ce91690c469a6449d412561cc604b1dec994e16ab132952e7805f -F ext/fts5/test/fts5corrupt3.test dd5435659d5363fc3b3ac32175e2fc793fdc17980b47e3d598695d867eaa77a2 -F ext/fts5/test/fts5corrupt4.test ea805c4d7c68b5f185b9db5d2060a7ae5875339738dd48203c92162f41e7ca91 -F ext/fts5/test/fts5delete.test 4a15fb03b6c7eac62ac807a3a32b7f0dc74f0d479c410e3e3568ae96b9469290 +F ext/fts5/test/fts5corrupt3.test 79467b7c69a9c45ee3260507c4648d6449323aaa3c1e05b360c9479f1aa7dcce +F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3 +F ext/fts5/test/fts5delete.test 619295b20dbc1d840b403ee07c878f52378849c3c02e44f2ee143b3e978a0aa7 F ext/fts5/test/fts5detail.test 31b240dbf6d44ac3507e2f8b65f29fdc12465ffd531212378c7ce1066766f54e F ext/fts5/test/fts5determin.test 1b77879b2ae818b5b71c859e534ee334dac088b7cf3ff3bf76a2c82b1c788d11 F ext/fts5/test/fts5dlidx.test b90852c55881b29dbac6380b274de27beae623ac4b6d567c6c8fb9cdc315a86e -F ext/fts5/test/fts5doclist.test e39a6001495f1dc68e20323586ac965787986c2bf6f515b9b0285627b089d9e6 +F ext/fts5/test/fts5doclist.test faa9e9cc3c0645fa6203667cb5f007c359447c6ee66753f71a58175c2497cacd F ext/fts5/test/fts5ea.test b01e3a18cdfabbff8104a96a5242a06a68a998a0 F ext/fts5/test/fts5eb.test 239bb2f02571f8cccfc7018d08f502df1cd8cc6a69b65ed1dde5f6a070e3f669 F ext/fts5/test/fts5fault1.test d28a65caee75db6897c3cf1358c5230d3bb2a3bf7fb31062c19c7e5382b3d2bd @@ -200,10 +200,11 @@ F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c27 F ext/fts5/test/fts5onepass.test f9b7d9b2c334900c6542a869760290e2ab5382af8fbd618834bf1fcc3e7b84da F ext/fts5/test/fts5optimize.test 36a752d24c818792032e4ff502936fc9cc5ef938721696396fdc79214b2717f1 F ext/fts5/test/fts5phrase.test 13e5d8e9083077b3d9c74315b3c92ec723cc6eb37c8155e0bfe1bba00559f07b -F ext/fts5/test/fts5plan.test 79d35b5e83bbdcba48d946a7f008df161f6b0ede1a966892d0aa6c8dd0b6e773 +F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd116514f79c49c5a F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15 F ext/fts5/test/fts5porter2.test 0d251a673f02fa13ca7f011654873b3add20745f7402f108600a23e52d8c7457 F ext/fts5/test/fts5prefix.test a0fa67b06650f2deaa7bf27745899d94e0fb547ad9ecbd08bfad98c04912c056 +F ext/fts5/test/fts5prefix2.test 3847ce46f70b82d61c6095103a9d7c53f2952c40a4704157bc079c04d9c8b18b F ext/fts5/test/fts5query.test ac363b17a442620bb0780e93c24f16a5f963dfe2f23dc85647b869efcfada728 F ext/fts5/test/fts5rank.test c9fd4a1e36b4fa92d572ec13d846469b97da249d1c2f7fd3ee7e017ce46f2416 F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde40a46fc5d785b451e7 @@ -218,7 +219,7 @@ F ext/fts5/test/fts5synonym2.test b54cce5c34ec08ed616f646635538ae82e34a0e28f947e F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 -F ext/fts5/test/fts5trigram.test e2ce256fd0ccd6707e740aa3596206aedb0d5834b100c0cb49e344dcd1d8463c +F ext/fts5/test/fts5trigram.test 5b4feb53a4d5aca70c841f6919c8719b5a9c805474727dda99285fccdd2e9cce F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602 F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9 F ext/fts5/test/fts5unicode2.test 9b3df486de05fb4bde4aa7ee8de2e6dae1df6eb90e3f2e242c9383b95d314e3e @@ -285,42 +286,42 @@ F ext/lsm1/tool/mklsm1c.tcl f31561bbee5349f0a554d1ad7236ac1991fc09176626f529f607 F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f238c240 F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb -F ext/misc/appendvfs.c 55121d311d408ba9c62c3cfa367408887638f02f9522dd9859891d0ee69a7eba +F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824 F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9 F ext/misc/carray.c b75a0f207391038bf1540d3372f482a95c3613511c7c474db51ede1196321c7c F ext/misc/carray.h de74ac70b2338f416723f7d538026e8ec0b7f1d388319f8f140c9a4d7677f02e -F ext/misc/cksumvfs.c c4e7ebeae5aa578df98f23bddb63ecbcbe913ee3c32b8b769525af100d752061 +F ext/misc/cksumvfs.c b42ef52eaaa510d54ec320c87bea149e934a3b06cd232be2093562bf669bd572 F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c85c243 F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9 F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 F ext/misc/csv.c 53b3338d4fa812eda51a2637df30233a4dae16b964ee5666e2051b9672ed8bb4 F ext/misc/dbdata.c e316fba936571584e55abd5b974a32a191727a6b746053a0c9d439bd2cf93940 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 -F ext/misc/decimal.c 3ddbf8162015be4d5ec2395dee4538f1e638bb517174bb148274b132df6e1d08 +F ext/misc/decimal.c 09f967dcf4a1ee35a76309829308ec278d3648168733f4a1147820e11ebefd12 F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe F ext/misc/fileio.c 9b69e25da3b51d4a1d905a464ccb96709792ad627a742ba09215bc0d1447e7bd F ext/misc/fossildelta.c 1240b2d3e52eab1d50c160c7fe1902a9bd210e052dc209200a750bbf885402d5 F ext/misc/fuzzer.c eae560134f66333e9e1ca4c8ffea75df42056e2ce8456734565dbe1c2a92bf3d -F ext/misc/ieee754.c 5c7ca326361c7368f95f5743972eade3b8b24f60359ed7cba4706668a5682896 -F ext/misc/json1.c f31e89171f932d1821c91f10d2cb4979fc0447030030a8bce70420cd43d074c0 +F ext/misc/ieee754.c cd6ab89f85fda8a020559b3f4d03001a8a62dd856beda5af3f558621d12be913 +F ext/misc/json1.c 76c5d9e0960fd15b4be79dacb76d872b4d0d983ce13e72ebfe9481d82cb9345d F ext/misc/memstat.c 3017a0832c645c0f8c773435620d663855f04690172316bd127270d1a7523d4d F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2d5537b -F ext/misc/memvfs.c ab36f49e02ebcdf85a1e08dc4d8599ea8f343e073ac9e0bca18a98b7e1ec9567 +F ext/misc/memvfs.c 7dffa8cc89c7f2d73da4bd4ccea1bcbd2bd283e3bb4cea398df7c372a197291b F ext/misc/mmapwarm.c 347caa99915fb254e8949ec131667b7fae99e2a9ce91bd468efb6dc372d9b7a9 F ext/misc/nextchar.c 7877914c2a80c2f181dd04c3dbef550dfb54c93495dc03da2403b5dd58f34edd F ext/misc/noop.c 81efe4cad9ec740e64388b14281cb983e6e2c223fed43eb77ab3e34946e0c1ab F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d09ea61f F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196 -F ext/misc/regexp.c 246244c714267f303df76acf73dcf110cf2eaf076896aaaba8db6d6d21a129db +F ext/misc/regexp.c 5853b0e5ed40c47f7ded2b0bf2ff73796f7cb21543089c5f07308e0032647b76 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 -F ext/misc/series.c fbb8e6be97b54d10d2f235e163fa2f53a8f4421c66ebd532a233fd1c69c3f522 +F ext/misc/series.c c6bd5d249e5199a1b55aeee4d0e6576ff3a68702fc475dbd64503a32903516c7 F ext/misc/sha1.c c8f2253c8792ffab9517695ea7d88c079f0395a5505eefef5c8198fe184ed5ac -F ext/misc/shathree.c 86bee126b89dc7f3a9edc6d31497588a0f1e78ab867025a766b67f52bcf1f656 +F ext/misc/shathree.c e984f31731de4cf302a0386be5fe664580f63d8204c47b9b41cc4b997745f9ec F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6 @@ -331,15 +332,15 @@ F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a03 F ext/misc/unionvtab.c 36237f0607ca954ac13a4a0e2d2ac40c33bc6e032a5f55f431713061ef1625f9 F ext/misc/urifuncs.c f71360d14fa9e7626b563f1f781c6148109462741c5235ac63ae0f8917b9c751 F ext/misc/uuid.c 5bb2264c1b64d163efa46509544fd7500cb8769cb7c16dd52052da8d961505cf -F ext/misc/vfslog.c 3b25c2f56ba60788db247287be6ab024b53c4afffd412b4876db563389be0d35 -F ext/misc/vfsstat.c 389ea13983d3af926504c314f06a83cc858d5adc24b40af74aaed1fece00c118 +F ext/misc/vfslog.c 3932ab932eeb2601dbc4447cb14d445aaa9fbe43b863ef5f014401c3420afd20 +F ext/misc/vfsstat.c 474d08efc697b8eba300082cb1eb74a5f0f3df31ed257db1cb07e72ab0e53dfb F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd -F ext/misc/wholenumber.c 520f34c3099e5b7d546f13708607dc2fa173c46b68952eecf0d19cd675fec85e -F ext/misc/zipfile.c e35e035bc2765b1ccdcb15f9815c2112843fcbc8f36aa071f0e5935df7072228 +F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546 +F ext/misc/zipfile.c b7261ef6dbc2d18924ff80c40fb5d56c9ccfee3f822a7d3d43b7c87af3d27218 F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64 -F ext/rbu/rbu.c 8681f6157db6adc82c34af24b14ea8a3be0146ad2a3b6c1d5da6cb8a5796c8ce -F ext/rbu/rbu1.test 221d9c18a5e600ac9ac6b1810d99d9f99163a7909ba61597876ab6e4d4beb3d6 +F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 +F ext/rbu/rbu1.test c62904bd9526dcdc3496a21199aaf14ae191bbadbf67f076bf16be6b3f2115c2 F ext/rbu/rbu10.test 0a201c32202143f23c81c0144503da339786fc20acb7a2fda11601b65659f314 F ext/rbu/rbu11.test 5c834cf491086b45e071eabf71f708febc143e86a384a92de69e0b1a4cace144 F ext/rbu/rbu12.test 29f8b2118f6c96fac3755bd6d2b55c2db24f878b1f11fbfbe294f3a230a3dcdc @@ -358,7 +359,7 @@ F ext/rbu/rbu_common.tcl 60d904133ff843fe72cc0514e9dd2486707181e6e0fbab20979da28 F ext/rbu/rbucollate.test cac528a9a46318cba42e61258bb42660bbbf4fdb9a8c863de5a54ad0c658d197 F ext/rbu/rbucrash.test 000981a1fe8a6e4d9a684232f6a129e66a3ef595f5ed74655e2f9c68ffa613b4 F ext/rbu/rbucrash2.test efa143cc94228eb0266d3f1abfbee60a5838a84cef7cc3fcb8c145b74d96fd41 -F ext/rbu/rbudiff.test 156957851136b63c143478518dc1bda6c832103cdbe8ac1d7cdd47edb3cbe0a3 +F ext/rbu/rbudiff.test abe895a8d479e4d33acb40e244e3d8e2cd25f55a18dfa8b9f83e13d00073f600 F ext/rbu/rbudor.test e3e8623926012f43eebe51fedf06a102df2640750d971596b052495f2536db20 F ext/rbu/rbuexpr.test 10d0420537c3bc7666e576d72adeffe7e86cfbb00dcc30aa9ce096c042415190 F ext/rbu/rbufault.test 2d7f567b79d558f6e093c58808cab4354f8a174e3802f69e7790a9689b3c09f8 @@ -378,7 +379,7 @@ F ext/rbu/rbuvacuum.test 55e101e90168c2b31df6c9638fe73dc7f7cc666b6142266d1563697 F ext/rbu/rbuvacuum2.test b8e5b51dc8b2c0153373d024c0936be3f66f9234acbd6d0baab0869d56b14e6b F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10 -F ext/rbu/sqlite3rbu.c 05c457c27e9340c944f34e850871a915a6b5ee1d823f7a0bb2b482ac6b1e1464 +F ext/rbu/sqlite3rbu.c badb52388467f58e67ef104c5276d1ac68b316a30f8ccb2f74eac733625ae236 F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812 F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 @@ -391,21 +392,21 @@ F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc782 F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c335096108c12c01bddbadcec F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/geopoly.c 1b1ddecb79877b12331d35de9f393b80bbb731e0bf6db6c572f23191987ea803 -F ext/rtree/rtree.c 60f6f1675134201974be4189dbd219494c13e1ed30e80bd92ae0dcc86df1b035 +F ext/rtree/geopoly.c 98d45533989e908bf65b43f36ff6eaad95a9ffe6f3b6b8658fbd47d45c58b10b +F ext/rtree/rtree.c f1ce6a86f7cbff634900653bec2dfeba732dd4450eec921c0ac3851d41f462b9 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test 00792b030a4e188ff1b22e8530e8aa0452bb5dd81c2b18cb004afc7dc63e040e F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d F ext/rtree/rtree3.test 4ee5d7df86040efe3d8d84f141f2962a7745452200a7cba1db06f86d97050499 F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2e55d3142 -F ext/rtree/rtree6.test 1252a0439da01d2f1f5cbbdeeb80455a2d68b9bae2a9787937b167a5e3957828 +F ext/rtree/rtree6.test 9ce3691c1aac43070a9f194f0ebf54372db346c5a82241fd11b525ed53ce9f3f F ext/rtree/rtree7.test c8fb2e555b128dd0f0bdb520c61380014f497f8a23c40f2e820acc9f9e4fdce5 F ext/rtree/rtree8.test 2d99006a1386663978c9e1df167554671e4f711c419175b39f332719deb1ce0e F ext/rtree/rtree9.test c646f12c8c1c68ef015c6c043d86a0c42488e2e68ed1bb1b0771a7ca246cbabf F ext/rtree/rtreeA.test ed2f1be9c06dde0b1ab93a95dd9e87eeaa02db2d30bcb4b9179b69ee3dc3319b F ext/rtree/rtreeB.test 4cec297f8e5c588654bbf3c6ed0903f10612be8a2878055dd25faf8c71758bc9 -F ext/rtree/rtreeC.test 6aa87eba4d9a3003b941a1ba77db259c5cabc3fd92fc5a6360f5369520eb9a4d +F ext/rtree/rtreeC.test c4bfa9a61c6788c03e4a9ce40ab2cfc6100982559effd9842d1b658e1d47aa5f F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc F ext/rtree/rtreeE.test e65d3fc625da1800b412fc8785817327d43ccfec5f5973912d8c9e471928caa9 F ext/rtree/rtreeF.test 81ffa7ef51c4e4618d497a57328c265bf576990c7070633b623b23cd450ed331 @@ -417,7 +418,7 @@ F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b5879 F ext/rtree/rtreecheck.test d67d5b3e9e45bfa8cd90734e8e9302144ac415b8e9176c6f02d4f92892ee8a35 F ext/rtree/rtreecirc.test aec664eb21ae943aeb344191407afff5d392d3ae9d12b9a112ced0d9c5de298e F ext/rtree/rtreeconnect.test 225ad3fcb483d36cbee423a25052a6bbae762c9576ae9268332360c68c170d3d -F ext/rtree/rtreefuzz001.test eef1ed593bb15886cd5d5367a2f2492f81e315848896cdf7afb6e21454978827 +F ext/rtree/rtreefuzz001.test 0fc793f67897c250c5fde96cefee455a5e2fb92f4feeabde5b85ea02040790ee F ext/rtree/sqlite3rtree.h 03c8db3261e435fbddcfc961471795cbf12b24e03001d0015b2636b0f3881373 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/util/randomshape.tcl 54ee03d0d4a1c621806f7f44d5b78d2db8fac26e0e8687c36c4bd0203b27dbff @@ -427,7 +428,7 @@ F ext/session/changeset.c 7a1e6a14c7e92d36ca177e92e88b5281acd709f3b726298dc34ec0 F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8fac9cd8b0b24aa F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a F ext/session/session1.test 0b2f88995832ea040ae8e83a1ad4afa99c00b85c779d213da73a95ea4113233e -F ext/session/session2.test 284de45abae4cc1082bc52012ee81521d5ac58e0 +F ext/session/session2.test 7f53d755d921e0baf815c4258348e0ed460dfd8a772351bca5ad3ccbb1dc786e F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479 F ext/session/session4.test 6778997065b44d99c51ff9cece047ff9244a32856b328735ae27ddef68979c40 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 @@ -445,23 +446,27 @@ F ext/session/sessionH.test b17afdbd3b8f17e9bab91e235acf167cf35485db2ab2df0ea889 F ext/session/session_common.tcl f613174665456b2d916ae8df3e5735092a1c1712f36f46840172e9a01e8cc53e F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3 F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec +F ext/session/sessionbig.test 890ade19e3f80f3d3a3e83821ff79c5e2af906a67ecb5450879f0015cadf101e F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test dd593f80b6b4786f7adfe83c5939620bc505559770cc181332da26f29cddd7bb F ext/session/sessioninvert.test 04075517a9497a80d39c495ba6b44f3982c7371129b89e2c52219819bc105a25 +F ext/session/sessionmem.test f2a735db84a3e9e19f571033b725b0b2daf847f3f28b1da55a0c1a4e74f1de09 +F ext/session/sessionnoop.test a9366a36a95ef85f8a3687856ebef46983df399541174cb1ede2ee53b8011bc7 F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2f05e82363b9142810 +F ext/session/sessionsize.test 6f644aff31c7f1e4871e9ff3542766e18da68fc7e587b83a347ea9820a002dd8 F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5 -F ext/session/sessionwor.test 67b5ab91d4f93ce65ff1f58240ac5ddf73f8670facc1ffa49cef56293d52818d -F ext/session/sqlite3session.c 2c76b8c3a5d6dab736686f8a48833b8bdac0871ecc6f447f9839d28bd4a63d6c -F ext/session/sqlite3session.h a2db5b72b938d12c727b4b4ec632254ca493670a9c0de597af3271a7f774fc57 -F ext/session/test_session.c 24286d958dc6f4ca4d7e710f09bc0fa9d50956a40dd99fd8223e7488024c71fe +F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc +F ext/session/sqlite3session.c af0cb1163e224b286a9cba3c1ad877d927821e6a0e317c29b289a16e5da378a8 +F ext/session/sqlite3session.h 0907de79bc13a2e3af30a6dc29acc60792a3eaf7d33d44cf52500d0f3c2b2171 +F ext/session/test_session.c f433f68a8a8c64b0f5bc74dc725078f12483301ad4ae8375205eef790274a787 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 57451ea5b3d5cd86e9c5324b10c9de184b12e8dcccc31c65d24fbcb55ccd9c53 +F main.mk c0c95f0aa2e3b731eb0c5a85869d4d6abef91647ec8e87a4013059bd1b910c0a F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -473,45 +478,45 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a -F src/alter.c b6de60d59419e34f5b48ff2b21fe0f9bb66fc714e5545a6ac790ac7a0c46548c +F src/alter.c 774a2a175ec747b55046ec62f5c1208d730ee22b10bdad5f182591fa247946a8 F src/analyze.c 01c6c6765cb4d40b473b71d85535093730770bb186f2f473abac25f07fcdee5c -F src/attach.c 0f497c15c4cfe3bdcb214f0dbdbbb6c5ed7e8a9308ac445c7959f5e5780437a9 -F src/auth.c a3d5bfdba83d25abed1013a8c7a5f204e2e29b0c25242a56bc02bb0c07bf1e06 +F src/attach.c a514e81758ba7b3a3a0501faf70af6cfc509de8810235db726cfc9f25165e929 +F src/auth.c 08954fdc4cc2da5264ba5b75cfd90b67a6fc7d1710a02ccf917c38eadec77853 F src/backup.c 3014889fa06e20e6adfa0d07b60097eec1f6e5b06671625f476a714d2356513d F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33 F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6 -F src/btree.c ee14224322b9e4172d01e691e2f289f6c630ae39b7906f84b72dc780b9e42a76 -F src/btree.h dcdff4037d75b3f032a5de0d922fcfaf35d48589417f634fa8627362709315f9 -F src/btreeInt.h ffd66480520d9d70222171b3a026d78b80833b5cea49c89867949f3e023d5f43 -F src/build.c f6449d4e85e998e14d3f537e8ea898dca2fcb83c277db3e60945af9b9177db81 +F src/btree.c 399e1ebcd6c4f9ad47f5457bfe3623441db287f0923433cf6539497791557be8 +F src/btree.h 096cc53baa58be22b02c896d1cf933c38cfc6d65f9253c1367ece8cc88a24de5 +F src/btreeInt.h 7bc15a24a02662409ebcd6aeaa1065522d14b7fda71573a2b0568b458f514ae0 +F src/build.c ea28944f65b04f771e7aa5d614d406faa1bde5fe4ce882e2ead73049f03ed568 F src/callback.c d0b853dd413255d2e337b34545e54d888ea02f20da5ad0e63585b389624c4a6c F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c e98518d2d3d4029a13c805e07313fb60c877be56db76e90dd5f3af73085d0ce6 -F src/date.c dace306a10d9b02ee553d454c8e1cf8d3c9b932e137738a6b15b90253a9bfc10 +F src/ctime.c 3052b6b05ed9ef547a3dd66b8e01bfa9d582e78752ad6ed327da84652641e038 +F src/date.c e0632f335952b32401482d099321bbf12716b29d6e72836b53ae49683ebae4bf F src/dbpage.c 8a01e865bf8bc6d7b1844b4314443a6436c07c3efe1d488ed89e81719047833a F src/dbstat.c 3aa79fc3aed7ce906e4ea6c10e85d657299e304f6049861fe300053ac57de36c -F src/delete.c 927cf8f900583e79aca8f1a321979e0a8f053babd9a690b44b38f79de2cc09fe -F src/expr.c 0d196ed5a2ebf96be7e8df88add4fabfad0dce16c0fed81a4b8f6a26e259797f +F src/delete.c 62451bba9fe641159e9c0b7d9d2bab1c48d0cff11e16de2d14000603d2af1fcf +F src/expr.c 30a2abf526531ce6bd45fbc85bfec0fc3f6e5a0fb490cd2350855f2fc34dd789 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c 83372403298e6a7dd989a47aaacdbaa5b4307b5199dbd56e07d4896066b3de72 -F src/func.c 574f7e5a67e4b7a7855cf3478037717c8f44686c0cd727e1d7f7773414165c03 -F src/global.c 943256ac44f333039d35a9830c18d075a81fa6b6bf2af05771494a9acfb9a40b +F src/fkey.c e9063648396c58778f77583a678342fe4a9bc82436bf23c5f9f444f2df0fdaa4 +F src/func.c 9eb67f0aaf1cf439c21d6fc8afe270973d6e8345af3f1ebda98ad42186d30f5b +F src/global.c 25ba4d58476f6be29bba9d9d14f7f146b78476d3a4d75ebb8c3b736328afe0f9 F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19 F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38 F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 7e081d33aab4a9d761c39dccf3c3872c35501565d2ed9db66301918d23bc7901 +F src/insert.c 2189e0e596010a0dc5405d9f14f78db1ee2fa71138c931f5b6ea96610b95bfc1 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 8c9c8cd2bd8eecdb06d9b6e89de7e9e65bae45cc8fc33609cc74023a5c296067 -F src/main.c 54d61d1e94f225636053c862c930d7dd7b29162b93d3f3ecb00439910ccba02a -F src/malloc.c c1af4ac5a463648cd2953fd4ac679b3ba9022ce5ec794a60806150ad69dfd33a +F src/loadext.c 12684b3f19cd103cea97cdf14d0758196d0c646e12a898d7245141a9abfde9a4 +F src/main.c 9c06f8dc6137aaa990692ce30db1c00c3e6a574c5a4756640625c19b507c65ec +F src/malloc.c cbc93cdd429c4594912017d92ab656e2579aca64dbd1c6888551275bed46f25b F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c b93b8762ab999a29ae7751532dadf0a1ac78040308a5fb1d17fcc365171d67eb F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944 -F src/memdb.c ab0632d42407e866d2b616bd19d4211ac0ad1b430f04c4e187d60005b8700b98 -F src/memjournal.c 90b2ca7e2f465d57c16b69d15a9f3e3294af61088eb4938f2f7664d5ac50f813 +F src/memdb.c 2f2e8efc6e531c59cf8255f0bf4ad81f2e88e0a394581244154c8cf5141757ce +F src/memjournal.c a85f0dc5c02a42453d0bc3819ecfb5666cb6433e5deefcd93ccbe05c9f088b83 F src/msvc.h 3a15918220367a8876be3fa4f2abe423a861491e84b864fb2b7426bf022a28f8 F src/mutex.c 5e3409715552348732e97b9194abe92fdfcd934cfb681df4ba0ab87ac6c18d25 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a @@ -519,44 +524,44 @@ F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 -F src/os.c 80e4cf3e5da06be03ca641661e331ce60eeeeabf0d7354dbb1c0e166d0eedbbe -F src/os.h 48388821692e87da174ea198bf96b1b2d9d83be5dfc908f673ee21fafbe0d432 +F src/os.c 59ed1f503347e8b5434c0ce7d7d0f02a3f24a72fea8b26d0bba2de8dfaef778b +F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 -F src/os_unix.c adbbcea4c63d3b400d405f60a5da4c01433753ec4a12e2dc695beb2bbd671fe9 +F src/os_unix.c feac74a0ba7652c405ba30f61d9e738be717d15899f2915a129ef160a3e5b26b F src/os_win.c 77d39873836f1831a9b0b91894fec45ab0e9ca8e067dc8c549e1d1eca1566fe9 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c c49952ac5e9cc536778eff528091d79d38b3e45cbeeed4695dc05e207dc6547d +F src/pager.c 95c255256b13827caf038c8f963d334784073f38ab6ef9d70371d9d04f3c43e0 F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f -F src/parse.y 9ce4dfb772608ed5bd3c32f33e943e021e3b06cfd2c01932d4280888fdd2ebed +F src/parse.y 8920f4444957d7827ca458029b2e41ffa32dd3b72917be0b52cae0aace3eadb5 F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177 F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 -F src/pcache1.c 6596e10baf3d8f84cc1585d226cf1ab26564a5f5caf85a15757a281ff977d51a -F src/pragma.c 6daaaecc26a4b09481d21722525b079ce756751a43a79cc1d8f122d686806193 +F src/pcache1.c 388304fd2d91c39591080b5e0f3c62cfba87db20370e7e0554062bfb29740e9f +F src/pragma.c eb42cb9bec189cf18cef5d8fcae56e13bb73ef2b019b198fb48740ced81bce95 F src/pragma.h 8dc78ab7e9ec6ce3ded8332810a2066f1ef6267e2e03cd7356ee00276125c6cf -F src/prepare.c 270170a239c0f66bd3c228f373afe24447c2614a6829ae22080babc64f241931 -F src/printf.c 30e92b638fac71dcd85cdea1d12ecfae354c9adee2c71e8e1ae4727cde7c91ed -F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 -F src/resolve.c 1948a92ca9eab776632816b97e57c61d933474a78aad4f4ef835c916a83dbb1c +F src/prepare.c 0d53d20532aada295c1690792a125adbd6435f5ce703ff0adf1b9b3605238b67 +F src/printf.c 78fabb49b9ac9a12dd1c89d744abdc9b67fd3205e62967e158f78b965a29ec4b +F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c +F src/resolve.c 35630effd4d16d2373caa41bae40a3d71f853f3ad0cb4f572f2ed4b8c350c1e9 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 847062694ac9a6add162d39182584b7ca53948ad821c52512a125cd845904e71 -F src/shell.c.in 55113760ae91a05c6ce4558714a1c8fc7a44bf266f735de6e71ea40f79e69830 -F src/sqlite.h.in 843471c794810d090b7bc1a31ff1d7c6fed5d029dc47ccc54605bd418df5d294 +F src/select.c 4fa607bab6bcc580f12dbaf9c800b2250a1e408f10321a1d3bcb1dd30c447e62 +F src/shell.c.in f3e91c697f33daae14923355dcadfc26bb0c1eabdb343e5508706893e017a0a6 +F src/sqlite.h.in 8549395aa13361d5dcea04e3a57d680af10f7ee0989c45ebfffe3a3bcb2cdc3d F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 61b38c073d5e1e96a3d45271b257aef27d0d13da2bea5347692ae579475cd95e -F src/sqliteInt.h c01115c8dd967f7d334a98ba37ac821eafb04144c8085a795daaf2185743d27a +F src/sqliteInt.h ab37a8dd95884fc25ef1eb7b6d53d6674905c67298affc2b5bf947f739096504 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 986b6391f02cd9b53c1d688be55899f6ffddeb8e8014cd83c1b73ff912579a71 -F src/test1.c 465b7a35b7e231bf2833e47c1371b5dfc99bd803ebe73783862399d479d696a5 +F src/tclsqlite.c 97645e4a15dde6a6ad6de8d81057ff9869413b866015a89e208fedacd709493e +F src/test1.c 2100f4c28bae21ce83a9a0c5ec6827efd0e15d11b93b569b614daa5654b3fcf6 F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d F src/test6.c ae73a3a42bbc982fb9e301b84d30bda65a307be48c6dff20aba1461e17a9b0ce F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010 -F src/test8.c 7fb971777c2c79c734bb52757191d68d4af659b8de9b4a071be3f527a9d19a02 +F src/test8.c 0c856d6ff6b0d2ff6696addc467a15ed17c6910f14475302cd5b3b4e54406161 F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 @@ -564,7 +569,7 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 -F src/test_config.c 5ea19bf0972a9d91728518b4d30e91477acce80496003ecbef3a7fb18d0bd081 +F src/test_config.c 9c8e12823c46082a01765addf43be9309889f4e9dfb5a512a6c974e1c4efb413 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f F src/test_demovfs.c 86142ba864d4297d54c5b2e972e74f3141ae4b30f05b3a95824184ed2d3d7f91 F src/test_devsym.c aff2255ea290d7718da08af30cdf18e470ff7325a5eff63e0057b1496ed66593 @@ -603,34 +608,34 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 4dc01b267593537e2a0d0efe9f80dabe24c5b6f7627bc6971c487fa6a1dacbbf -F src/treeview.c 4b92992176fb2caefbe06ba5bd06e0e0ebcde3d5564758da672631f17aa51cda -F src/trigger.c 515e79206d40d1d4149129318582e79a6e9db590a7b74e226fdb5b2a6c7e1b10 -F src/update.c 9f126204a6acb96bbe47391ae48e0fc579105d8e76a6d9c4fab3271367476580 -F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78 +F src/tokenize.c bae853ad129d1129c063de8630a3e99e306283bc40146f359b1bb91be2c08f1e +F src/treeview.c 21449a944d5abd3ac07b7eab76f7221e20c9480d06f01798f8dc66d9bc361769 +F src/trigger.c e0fd347b2571a2d956318cdc6d011ccca7ce862d10a0ca04188a37920ef5440c +F src/update.c 56fa0458b1ffc1042629f926443e8ed44203983df3ab2b0db2ba556e6ceed68c +F src/upsert.c df8f1727d62b5987c4fd302cd4d7c0c84ae57cd65683c5a34a740dfe24039235 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c c0c7977de7ef9b8cb10f6c85f2d0557889a658f817b0455909a49179ba4c8002 +F src/util.c 41c7a72da1df47864faa378a1c720b38adb288c6838cb6be5594511b6287a048 F src/vacuum.c 492422c1463c076473bae1858799c7a0a5fe87a133d1223239447c422cd26286 -F src/vdbe.c d24a43b6b1ed2dba893636a14f5e56001444ab3fd5465e3bca8ab01799840acd -F src/vdbe.h 83603854bfa5851af601fc0947671eb260f4363e62e960e8a994fb9bbcd2aaa1 -F src/vdbeInt.h 3ca5e9fd6e095a8b6cf6bc3587a46fc93499503b2fe48951e1034ba9e2ce2f6e -F src/vdbeapi.c c5e7cb2ab89a24d7f723e87b508f21bfb1359a04db5277d8a99fd1e015c12eb9 -F src/vdbeaux.c c76b7e96e189f5056d1de914d33d07bd03d3b88741f75375c8e18c9b11ffd379 -F src/vdbeblob.c 253ed82894924c362a7fa3079551d3554cd1cdace39aa833da77d3bc67e7c1b1 -F src/vdbemem.c 947f2a65910edb4014dc981d33e414a68c51f169f9df8c4c493a0ba840b6eb1f -F src/vdbesort.c f5b5e473a7cee44e47a94817b042fd7172cf3aa2c0a7928a8339d612bcfdec5a -F src/vdbetrace.c fa3bf238002f0bbbdfb66cc8afb0cea284ff9f148d6439bc1f6f2b4c3b7143f0 +F src/vdbe.c 6ece0ee94fa3a95601372efdc4ec6d068cd6cc58e28af8852f3407b90be4b7f8 +F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe +F src/vdbeInt.h 465fcb494db4ca6630fb9c19b2f3dfed597fbe885b0d4204193a5093b0dd7dc6 +F src/vdbeapi.c aa5aaf2c37676b83af5724c6cd8207a3064ed46a217fd180957f75ac84f7a2a5 +F src/vdbeaux.c 065a10392378109f08435bd50d03dff315e384cde2831d6b8dbaec05f33b10af +F src/vdbeblob.c c6b8db50b227f66fb404215732068df76485b5b433e5f9d4d9ac27410b218193 +F src/vdbemem.c 53881aa0a7845922a075b3f375695588618098871a7a4120af4c297b80fa3e64 +F src/vdbesort.c cd5130f683706c1a43e165a74187745fb3351cb56052cf9dc91de820634bbde2 +F src/vdbetrace.c 666c6fd9f1b62be6999e072a45b913e3c2c3518bc60dfd4d54fe304130acb724 F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c -F src/vtab.c 5f5fc793092f53bbdfde296c50f563fb7bda58cf48e9cf6a8bdfbc5abd409845 +F src/vtab.c b928405ccb66040fc6c3a11eaa93ddb02cbf20f9ab6860b301b222b9b50dc089 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 69e770e96fd56cc21608992bf2c6f1f3dc5cf2572d0495c6a643b06c3a679f14 +F src/wal.c c8ec20a1ca161d5635a4f19c2a4efec2e006e19a8a61f272bf6bce1c80ab7436 F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a -F src/walker.c 3df26a33dc4f54e8771600fb7fdebe1ece0896c2ad68c30ab40b017aa4395049 -F src/where.c ad738741bf45999188a3047f6277063e3f6843304d581e3ad5875419afdeb6b2 -F src/whereInt.h 59077fbd0b3d01bc8715e746c86a99ebf4c85bde8a57077ec04d2a23e59666ec -F src/wherecode.c a3a1aff30fe99a818d8e7c607980f033f40c68d890e03ed25838b9dbb7908bee -F src/whereexpr.c 3a463e156ea388083c501502229c2c7f4f5c6b5330ea59bdf40d6eb6e155a25f -F src/window.c edd6f5e25a1e8f2b6f5305b7f5f7da7bb35f07f0d432b255b1d4c2fcab4205aa +F src/walker.c 7342becedf3f8a26f9817f08436bdf8b56ad69af83705f6b9320a0ad3092c2ac +F src/where.c 4caf843825c6292440113258fefaa6f747fd570c6915628957e42e39529988f5 +F src/whereInt.h 9248161dd004f625ce5d3841ca9b99fed3fc8d61522cf76340fc5217dbe1375b +F src/wherecode.c 9f1f65d11437b25cd0a1497a170514c785f19ce6ad9d3e6fc73719cb5a49012f +F src/whereexpr.c 5a9c9f5d2dac4bcdcaae3035034b4667523f731df228e0bb1d4efc669efa9da5 +F src/window.c a6d624d83b2d5b3cfb82bb437a2fbae759c928d47dc9ad1338a9419269181bb2 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test eecb0dabee4b7765a8465439d5e99429279ffba23ca74a7eae270a452799f9e7 @@ -638,33 +643,38 @@ F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggnested.test 2f65ec8132e0ca896de550b9908094d49ad65a99116a9d79deeb6017604ad4f6 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 -F test/alter.test 25e109787dc5e631e117eb6e1c57f96a572bb51228db3b4f8b5f41d665e2ccaa +F test/alter.test f53d8a4ecd35f051c07e54a36beec5d0a30d30a9d98bc723f6cde6afbfb3c5ca F test/alter2.test a966ccfcddf9ce0a4e0e6ff1aca9e6e7948e0e242cd7e43fc091948521807687 F test/alter3.test e487958dec7932453e0b83baf21d6b1e71d5e7d9a55bc20eadfa62a51ddffc29 -F test/alter4.test dfd6086faf461b27ca2d2999848dcd207edf23352fc1592d0005c0844f3f08cf +F test/alter4.test 716caa071dd8a3c6d57225778d15d3c3cbf5e34b2e84ae44199aeb2bbf50a707 F test/alterauth.test 63442ba61ceb0c1eeb63aac1f4f5cebfa509d352276059d27106ae256bafc959 -F test/alterauth2.test c0a1ddf5b93d93cb0d15ba7acaf0c5c6fb515bbe861ede75b2d3fabad33b6499 -F test/altercol.test 1d6a6fe698b81e626baea4881f5717f9bc53d7d07f1cd23ee7ad1b931f117ddf -F test/alterlegacy.test 82022721ce0de29cedc9a7af63bc9fcc078b0ee000f8283b4b6ea9c3eab2f44b +F test/alterauth2.test 381b1ab603c9ef96314a3158528ea17f7964449385a28eeaf8191120b2e24a8d +F test/altercol.test b11fa1b131e80ab5b6ecfb3b725fb0419c14ca6efba5adb57aeabfc9baa0c8f3 +F test/altercorrupt.test 584d707a80e106952d6382790c8919bcf9f0db678ed3a1c09fd98b7f9d1d3a10 +F test/alterdropcol.test a653a3945f964d26845ec0cd0a8e74189f46de3119a984c5bc45457da392612e +F test/alterdropcol2.test 527fce683b200d620f560f666c44ae33e22728e990a10a48a543280dfd4b4d41 +F test/alterlegacy.test f38c6d06cda39e1f7b955bbce57f2e3ef5b7cb566d3d1234502093e228c15811 F test/altermalloc.test 167a47de41b5c638f5f5c6efb59784002b196fff70f98d9b4ed3cd74a3fb80c9 -F test/altermalloc2.test fa7b1c1139ea39b8dec407cf1feb032ca8e0076bd429574969b619175ad0174b -F test/altertab.test 6d7bbac2c4a6ef71b775094a3298fa3a92274d95034ee23157ffba92768e47e6 +F test/altermalloc2.test ca3ebc01670d9313953a2b7628d8cc00dc5ea9988f229b3cbbbe1cca506dae45 +F test/altermalloc3.test 059841a3de6b6780efd9f0b30bf1d9b4443c555f68d39975cbcac2583167b239 +F test/alterqf.test 67568ad152db8c1187b15633b801242cf960f1beafc51261a3d1725d910baeb2 +F test/altertab.test 466b9757630d68455080690b6e99c5ff11b199df4b3530e17f3df10a63aa05d3 F test/altertab2.test b0d62f323ca5dab42b0bc028c52e310ebdd13e655e8fac070fe622bad7852c2b -F test/altertab3.test 1db384eb85b4a30b0b332842f5c596b0dc0126f7c61959be3f85ae8b1c271d9a +F test/altertab3.test 2b82fa2236a3a91553d53ae5555d8e723c7eec174c41f1fa62ff497355398479 F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 547bb700f903107b38611b014ca645d6b5bb819f5210d7bf39c40802aafeb7d7 -F test/analyze3.test fca2a9de0017becfdcc201647f03b1cfd5ba0e7b5b5c852936e4ec62780cde49 -F test/analyze4.test cdf88f3f72b0f0643a1ff6c730fc5af1e42464d47478d9fbac84c333f72c014e +F test/analyze3.test 4440c4932247adb2b4e0c838f657c19dc7af4f56859255436dc4e855f39b9324 +F test/analyze4.test 293ec8ea21525f3435beaf9d6b1efb29e719415f03a0cd2a70589d59098e8464 F test/analyze5.test fa5131952303ac4146aba101b116b9c8cb89e2637531c334a6df7f7d19dddc0d -F test/analyze6.test 6c3f7df2996cb6872f355a6ac1eb6d5de00a5a9288214bad7ef25c97d9cc72dc -F test/analyze7.test 6ef0b12369f61ddeadc7d8a705c40e6b52cb29f63de3a4c56581b510b46b5783 -F test/analyze8.test 36ce54947710bd44e4f9484e1ad07e725ef01a9d7078b417c1bc884356febe4d -F test/analyze9.test 9fbf0e0101eef4f5dc149769aa14e10b76ee06e7c28598264b32173cd1999a54 -F test/analyzeC.test 489fe2ea3be3f17548e8dd895f1b41c9669b52de1b0861f5bffe6eec46eac710 -F test/analyzeD.test e50cd0b3e6063216cc0c88a1776e8645dc0bd65a6bb275769cbee33b7fd8d90c -F test/analyzeE.test 8684e8ac5722fb97c251887ad97e5d496a98af1d -F test/analyzeF.test 9e1a0537949eb5483642b1140a5c39e5b4025939024b935398471fa552f4dabb -F test/analyzeG.test a48c0f324dd14de9a40d52abe5ca2637f682b9a791d2523dd619f6efa14e345b +F test/analyze6.test 028f5bdfc9e5b5294768fa9a7185b8cd1d019aa7aab5b2f8ee42d7271d9a3b28 +F test/analyze7.test 079d17c495e396bdbd6cc6a083112788a6fbfb3b95c42e760e4270a53c9ead8f +F test/analyze8.test 29ef237d8a59b39cc31c3310134fefe96a690b195e3deed5ecb652839089f15c +F test/analyze9.test 162cd40d713310fbe7a63a5ecff5d3a013777d86809618838177d9b2af4dd09c +F test/analyzeC.test 1111830ad355d29a294a5dda654dd5f6a8622c6a223a4f7b7b3d091df7a7a42b +F test/analyzeD.test 485f621cfd2ef0a8f8ac79672586651bfa495bd899db50461bb4b558400ab3c1 +F test/analyzeE.test 69d130f9ba78c9853dcd5a18317e81f462a72d704cec0c4c30afb220213acd29 +F test/analyzeF.test 40b5cc3ad7b10e81020d7ca86f1417647ecfae7477cfd88acc5aa7ae1068f949 +F test/analyzeG.test 623be33038c49648872746c8dd8b23b5792c08fef173c55e82f1b12fca259852 F test/analyzer1.test 459fa02c445ddbf0101a3bad47b34290a35f2e49 F test/async.test 1d0e056ba1bb9729283a0f22718d3a25e82c277b F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b @@ -675,27 +685,28 @@ F test/atof1.test 10049623e77006691c4c2978c1dc8a3f75276377a53417811aa85bda7493f9 F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d061 F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c -F test/attach.test d42862c72fef3d54367d962d41dcfb5363442a4a1bd898c22ae950cea1aa0dd3 +F test/attach.test 54f8e49e88d0de48f6428267a678465863d2b8f72320612f35bd5c02e240bc2f F test/attach2.test 256bd240da1835fb8408dd59fb7ef71f8358c7a756c46662434d11d07ba3a0ce F test/attach3.test c59d92791070c59272e00183b7353eeb94915976 -F test/attach4.test aa05b1d8218b24eba5a7cccf4f224f514ba57ba705c9267f09d2bb63fed0eea1 +F test/attach4.test 00e754484859998d124d144de6d114d920f2ed6ca2f961e6a7f4183c714f885e F test/attachmalloc.test 12c4f028e570acf9e0a4b0b7fe6f536e21f3d5ebddcece423603d0569beaf438 -F test/auth.test 2154625c05bc79f0e0ea72cb2358395a8041243caa0fd7ce7617d50da4331794 +F test/auth.test 567d917e0baddb6d0026a251cff977a3ab2c805a3cef906ba8653aafe7ad7240 F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test db21405b95257c24d29273b6b31d0efc59e1d337e3d5804ba2d1fd4897b1ae49 F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec F test/autoinc.test 997d6f185f138229dc4251583a1d04816423dddc2fc034871a01aeb1d728cb39 -F test/autoindex1.test 96185415f5faacd5b8d7a7f505efddd5abb1f111d58338e9c0b1dc40b87cd3cc +F test/autoindex1.test fe27af92eaf884bd9c38f94be3e8afa04ec494e5eefb189902026181a6175f5e F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df -F test/autoindex3.test 2dd997d6590438b53e4f715f9278aa91c9299cf3f81246a0915269c35beb790e +F test/autoindex3.test 2d13958a5617e987624a428d7aed91bf51f322b49b476e3573fadec697ce6da5 F test/autoindex4.test 49d3cd791a9baa16fb461d7ea3de80d019a819cf -F test/autoindex5.test a5d72fe8c217cc0ea356dc6fa06a282a8a3fc53aa807709d79dba07a8f248102 +F test/autoindex5.test ee0aa95e3f44190347a29a672c3d4ecf042e3f33c5fa3dc29f0ab6be057bbded F test/autovacuum.test 0831cd34e14695d297187f7f6519265e3121c5b0a1720e548e86829e796129e9 F test/autovacuum_ioerr2.test 8a367b224183ad801e0e24dcb7d1501f45f244b4 +F test/avfs.test 0c3a38e03cccb0fc3127838462dc05dc3f4c1480d770c084b388304c25de3652 F test/avtrans.test b7dc25459ecbd86c6fa9c606ee3068f59d81e225118617dcf2bbb6ded2ade89e F test/backcompat.test 3e64cedda754c778ef6bbe417b6e7a295e662a4d F test/backup.test dd4a5ff756e3df3931dacb1791db0584d4bad989 -F test/backup2.test 1fd1ad8c5b3d2d5b9c0cce4143a4fc610d51ddc6ae16a7a122973d43e6b50bbd +F test/backup2.test 8facb54df1388419d34b362ab1f7e233310ff3a3af64e8ad5ec47ba3c2bbe5cf F test/backup4.test 8f6fd48e0dfde77b9a3bb26dc471ede3e101df32 F test/backup5.test ee5da6d7fe5082f5b9b0bbfa31d016f52412a2e4 F test/backup_ioerr.test 4c3c7147cee85b024ecf6e150e090c32fdbb5135 @@ -703,17 +714,17 @@ F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399 F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c -F test/bestindex1.test 705b57d7f51d53ee5fd043dd9666236e1fc18f4d59abf51da0ea5ea1b4804947 -F test/bestindex2.test 9a0ccd320b6525eec3a706aae6cdab7e1b7b5abca75027e39f39f755e76e5928 -F test/bestindex3.test 7622e792ff2da16d262d3cea6ad914591ac4806d57ed128e6c940b7920b47b84 -F test/bestindex4.test 038e3d0789332f3f1d61474f9bbc9c6d08c6bd1783a978f31f38ad82688de601 +F test/bestindex1.test 2e92e046148b3f8a2be62af9c0f307413547a33677af0df159c0fcc76e5134a2 +F test/bestindex2.test 60266e2854055788459cbfd86cef575601eabe74a2c61faba72601739fea4398 +F test/bestindex3.test e061a6ed0f519beee037ba7e7a4c37f80c8a7e4a303e2559ed1f760e4b0235eb +F test/bestindex4.test 82250e7dcc6d5b15244edc9d6554b1760583af1b8548c2a255a1c4f28e744c0e F test/bestindex5.test 67c1166131bb59f9e47c00118f7d432ca5491e6cae6ca3f87ca9db20103a78f9 F test/bestindex6.test d856a9bb63d927493575823eed44053bc36251e241aa364e54d0f2a2d302e1d4 -F test/bestindex7.test f36ada201973d3022cf7afffffe08de9e58341996020e7a2df9a1d2f2be20132 -F test/between.test 68137a6e941c221417c15b6fe2d55f27bb1b6ab48bdf9e2aa51efdd85bc53802 +F test/bestindex7.test a11348824aed0de2bb9030f092636929000cd72882bdf919adacc3792f67ccbd +F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc -F test/bigmmap.test e906cf9729d7c362270ffe9fa6471f4c39c28ba83f0f77fb795e1b4d38a22ba0 +F test/bigmmap.test b820c234daa56d24bc3bf006e3ac7aa9d9623c8ac656a38f59063b444a2d65d1 F test/bigrow.test f0aeb7573dcb8caaafea76454be3ade29b7fc747 F test/bigsort.test 8299fa9298f4f1e02fc7d2712e8b77d6cd60e5a2 F test/bind.test 1e136709b306f7ed3192d349c2930d89df6ab621654ad6f1a72381d3fe76f483 @@ -732,7 +743,7 @@ F test/btree01.test e08b3613540145b353f20c81cb18ead54ff12e0f F test/btree02.test 7555a5440453d900410160a52554fe6478af4faf53098f7235f1f443d5a1d6cc F test/btreefault.test c2bcb542685eea44621275cfedbd8a13f65201e3 F test/busy.test 510dc6daaad18bcbbc085bcc6217d6dc418def5e73f72ce1475eea0cb7834727 -F test/busy2.test 0a1633fef7987fc8ea25ed5838f7eef2f08fc6d2a3dcc77d23e6402e53946a7a +F test/busy2.test dbfb61b3265e7a962d3bcd32cd542bbe3d7801edbda6438d35af5aa707cae981 F test/cache.test 13bc046b26210471ca6f2889aceb1ea52dc717de F test/cacheflush.test af25bb1509df04c1da10e38d8f322d66eceedf61 F test/cachespill.test 895997f84a25b323b166aecb69baab2d6380ea98f9e0bcc688c4493c535cfab9 @@ -764,6 +775,7 @@ F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6 F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151ecac0b95 F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1 F test/colname.test 87ad5458bb8709312dac0d6755fd30e8e4ca83298d0a9ef6e5c24277a3c3390e +F test/columncount.test d86fb6307261186370698962790ad2088ed419e4a4e823512b502f17d443b1b7 F test/conflict.test ac0667090f66130ac77d5fb764655558ca6600dd6d88f670ca9123b61c448337 F test/conflict2.test 5557909ce683b1073982f5d1b61dfb1d41e369533bfdaf003180c5bc87282dd1 F test/conflict3.test 81865d9599609aca394fb3b9cd5f561d4729ea5b176bece3644f6ecb540f88ac @@ -771,7 +783,7 @@ F test/contrib01.test 2a1cbc0f2f48955d7d073f725765da6fbceda6b4 F test/corrupt.test d7cb0300e4a297147b6a05e92a1684bc8973635c3bcaa3d66e983c9cbdbf47a3 F test/corrupt2.test bb50042cf9a1f1023d73af325d47eb02a6bb11e3c52f8812644b220c5d4bca35 F test/corrupt3.test 2520432b1fbf99994841e69804a3c59fb828183f4d09b85a1631bc7adca17e31 -F test/corrupt4.test 8d1d86b850fcc43e417450454f2044e52d55778a +F test/corrupt4.test b5ae41607e8d17d9c1f3e94fdb572ce061ed3beeebdb46fb3a348181b8c8a097 F test/corrupt5.test 387be3250795e2a86e6234745558b80efb248a357d0cd8e53bce75c7463f545d F test/corrupt6.test fc6a891716139665dae0073b6945e3670bf92568 F test/corrupt7.test b036f94bda4b0b23a2919bf717046ce9ecca4543 @@ -788,17 +800,18 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test a17bbf54fdde78d43cf3cc34b0057719fd4a173a3d824285b67dc5257c064c7b F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test 22589f503602cc5984e80f27f46c4de2134f24f1515ba2440513c377cb692258 +F test/corruptL.test df132ba9ffd6fa15038380b4154998b9904ab8f1ea78400d7da53c920cb3b13d F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 -F test/cost.test 1d156ce9858780a966c062694687afe0343a0ed12d081d071fb57027e726bafc -F test/count.test e0699a15712bc2a4679d60e408921c2cce7f6365a30340e790c98e0f334a9c77 +F test/corruptN.test c2a96ff81386027f7d7e95858783aa36f82ba1532106969575e3c8f90903a5bb +F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 +F test/count.test 5364003488249957750a5f15ee42ca1cd7b100b1131c2dc71fff266a1250bf55 F test/countofview.test e17d6e6688cf74f22783c9ec6e788c0790ee4fbbaee713affd00b1ac0bb39b86 -F test/coveridxscan.test 5ec98719a2e2914e8908dc75f7247d9b54a26df04625f846ac7900d5483f7296 +F test/coveridxscan.test f35c7208dedc4f98e471c569df64c0f95a49f6e072d8dc7c8f99bdee2697de1b F test/crash.test fb9dc4a02dcba30d4aa5c2c226f98b220b2b959f F test/crash2.test 5b14d4eb58b880e231361d3b609b216acda86651 F test/crash3.test 8f5de9d32ab9ab95475a9efe7f47a940aa889418 F test/crash4.test fe2821baf37168dc59dd733dcf7dba2a401487bc -F test/crash5.test f14ff37eddc41991be4eb63568f86caa306fd9962a0ae3750db8836777bb7aae +F test/crash5.test 4aa55e7ac3c4bc511873e457aa65d2827d52da9b51e061511899dadcfe22b1e8 F test/crash6.test 4c56f1e40d0291e1110790a99807aa875b1647ba F test/crash7.test 1a194c4900a255258cf94b7fcbfd29536db572df F test/crash8.test 64366e459c28dd62edfb7ad87253a409c7533b92d16fcc479a6a8131bdcc3100 @@ -812,45 +825,45 @@ F test/cursorhint.test 0175e4404181ace3ceca8b114eb0a98eae600d565aa4e2705abbe6614 F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8 F test/date.test 9b73bbeb1b82d9c1f44dec5cf563bf7da58d2373 -F test/date2.test 520a1708e4e14c682cf514560f3e9f2e5affa9d66560fbc2a6941ae291260daf +F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1 F test/dbdata.test 042f49acff3438f940eeba5868d3af080ae64ddf26ae78f80c92bec3ca7d8603 F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e F test/dbfuzz001.test 55e1a3504f8dea84155e09912fe3b1c3ad77e0b1a938ec42ca03b8e51b321e30 F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee -F test/dbfuzz2.c 40cc4600947f30600f0ab365a2714ec76a899c9adb2c0ccd63ba583b2f71390e +F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23 F test/dbpage.test 650234ba683b9d82b899c6c51439819787e7609f17a0cc40e0080a7b6443bc38 F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test fcf403fd5585f47342234e153c4a4338cd737b8e0884ac66fc484df47dbcf1a7 F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50 -F test/delete.test 31832b0c45ecb51a54348c68db173be462985901e6ed7f403d6d7a8f70ab4ef0 +F test/delete.test 438c14fdf2f2a520b63f892ea645896acf8832e2a2308578593d8e05274314b1 F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab -F test/delete4.test 6aa279f459f4aa792cc251435c3809415c1ecaf9f27dce91675e26f05b503db3 +F test/delete4.test 51fafebe9503a40796d1aae1565c60524cada720e50eecac01b7fd0419d9ea0b F test/delete_db.test 096d828493c7907f9ea11a7098ea6a0f73edba89406487d5d6cc2228dc4ab8b0 F test/descidx1.test edc8adee58d491b06c7157c50364eaf1c3605c9c19f8093cb1ea2b6184f3ac13 F test/descidx2.test a0ba347037ff3b811f4c6ceca5fd0f9d5d72e74e59f2d9de346a9d2f6ad78298 F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a1999b926 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e -F test/distinct.test e7d0cf371944dd0cbedff86420744e2f1ea2b528156451c97eb6ff41a99b9236 +F test/distinct.test 3e4210ef9cd1985aeec44939ad912c4621fbea9bb4a9c565696cebfe184b2ec5 F test/distinct2.test cd1d15a4a2abf579298f7161e821ed50c0119136fe0424db85c52cf0adc230d1 -F test/distinctagg.test 1a6ef9c87a58669438fc771450d7a72577417376 +F test/distinctagg.test d76ef2e91fe810630c176d6bd0a58c14d5851c3125f0a1d977db87ba76359639 F test/e_blobbytes.test 439a945953b35cb6948a552edaec4dc31fd70a05 F test/e_blobclose.test 4b3c8c60c2171164d472059c73e9f3c1844bb66d F test/e_blobopen.test e95e1d40f995056f6f322cd5e1a1b83a27e1a145 F test/e_blobwrite.test f87ff598b67af5b3ec002a8d83e804dc8d23808e88cf0080c176612fc9ffce14 F test/e_changes.test fd66105385153dbf21fdb35eb8ef6c3e1eade579 -F test/e_createtable.test 7107f5deda2324ea12dbf105044dc9265195401e9dbb3eead6a2c1a3da4bb5b7 +F test/e_createtable.test 7997c0106c181243e0ac7db7ba8b9ae7233d0bfb0188605650322a7a02ea326e F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7 F test/e_expr.test 6ba7a51ece7b3e7fc145f14f924eed25ebb5a24e7b8596c78f3838d372cf0385 -F test/e_fkey.test 0b458b85f192cdb9e9933d5891848bb19bcc44d3f49faf111a375f2844a164d3 +F test/e_fkey.test 351c7b989e5aefcc339ef5fc78dc4738442bd247a392cd67d81c2881000c369e F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e F test/e_reindex.test 2b0e29344497d9a8a999453a003cb476b6b1d2eef2d6c120f83c2d3a429f3164 F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 -F test/e_select.test f9474205669a7736ef725b29cc7ae9e8601919a3d0ffc0ab30745a028f2a4b61 +F test/e_select.test c5425a423da06d0494119db8361ebfc6de302929f7546ca596d56224137e0360 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f F test/e_totalchanges.test b12ee5809d3e63aeb83238dd501a7bca7fd72c10 F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 @@ -865,7 +878,7 @@ F test/enc.test 9a7be5479da985381d740b15f432800f65e2c87029ee57a318f42cb2eb43763a F test/enc2.test 848bf05f15b011719f478dddb7b5e9aea35e39e457493cba4c4eef75d849a5ec F test/enc3.test 6807f7a7740a00361ca8d0ccd66bc60c8dc5f2b6 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test 84879b63e3110552bf8ce648a3507dc3ceb72109ecec83c2aef0db37a27f6382 +F test/eqp.test bfe979eb1f4b8ab7a3bd7db6d16c2e6c6be0e5a3aada2227716f3fd3a9d76b69 F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/eval.test a64c9105d6ff163df7cf09d6ac29cdad5922078c F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650bf0747 @@ -874,7 +887,9 @@ F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac F test/expr.test 26cd01e8485bc48c8aa6a1add598e9ce1e706b4eb4f3f554e0b0223022e8c2cf F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 +F test/exprfault.test 497cc0b8fe6a677f49b55cb485e040f709ec2834b84f25912fe9c2dfeeda33db F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9 +F test/external_reader.test c7d34694f1b25c32d866f56ac80c1e29edddc42b4ef90cad589263ffac2cde0c F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 F test/fallocate.test 37a62e396a68eeede8f8d2ecf23573a80faceb630788d314d0a073d862616717 F test/filectrl.test 6e871c2d35dead1d9a88e176e8d2ca094fec6bb3 @@ -948,7 +963,7 @@ F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18 F test/fts3ao.test 266989148fec6d9f1bb6c5382f7aa3dcea0e9cd444576e28dd2b9287ac7dd220 F test/fts3atoken.test dc2078ce464914efe3a8dfc545dd034a0fc14f2ab425c240471d5a5f1c721400 F test/fts3auto.test 649aa4c198d7acc5cd6355e19ee073d051c40d9e88a43fc3d88af46bdf3e99d5 -F test/fts3aux1.test 7a170e172afdbceb67f5baa05941fd4fbf56af42f61daa3d140f4b4bf4cb68f6 +F test/fts3aux1.test 1880eaa75c586cd10f53080479a2b819b3915ae7ce55c4e0ba8f1fe05ac0a6a7 F test/fts3aux2.test 2459e7fa3e22734aed237d1e2ae192f5541c4d8b218956ad2d90754977bf907f F test/fts3b.test c15c4a9d04e210d0be67e54ce6a87b927168fbf9c1e3faec8c1a732c366fd491 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 @@ -957,9 +972,9 @@ F test/fts3conf.test c84bbaec81281c1788aa545ac6e78a6bd6cde2bdbbce2da261690e3659f F test/fts3corrupt.test 79a32ffdcd5254e2f7fa121d9656e61949ad049c3c6554229911b7ceac37c9c6 F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0 F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cdb764b491f -F test/fts3corrupt4.test e4662d37f02248301d8b58778eac380663e09a17a38dd5d6bb5ea4c927b9a575 +F test/fts3corrupt4.test 1b3333822577b0888c95de8490a1a6152c47cb33a763fe62c54825202c31812f F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5 -F test/fts3corrupt6.test b6c55218b704b0cef224b284c756f9c55d0afd0b3c3837618bffeaa8c31e0d8e +F test/fts3corrupt6.test 657b4b8e5791d8d4adc93c90588fb25f1c7346544dd877c6c298a0746749146d F test/fts3cov.test 7eacdbefd756cfa4dc2241974e3db2834e9b372ca215880e00032222f32194cf F test/fts3d.test 2bd8c97bcb9975f2334147173b4872505b6a41359a4f9068960a36afe07a679f F test/fts3defer.test f4c20e4c7153d20a98ee49ee5f3faef624fefc9a067f8d8d629db380c4d9f1de @@ -976,7 +991,7 @@ F test/fts3fault.test 798e45af84be7978ca33d5bdc94246eb44724db24174b5d8e9b1ac46c5 F test/fts3fault2.test 6a17a11d8034b1c4eca9f3091649273d56c49ff049e2173df8060f94341e9da0 F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641 F test/fts3fuzz001.test e3c7b0ce9b04cc02281dcc96812a277f02df03cd7dc082055d87e11eb18aaf56 -F test/fts3join.test 949b4f5ae3ae9cc2423cb865d711e32476bdb205ab2be923fdf48246e4a44166 +F test/fts3join.test b285c919559af5b093c51abb2c07ce7ec0156dbc9573f444b78dabd9f3040db2 F test/fts3malloc.test b0e4c133b8d61d4f6d112d8110f8320e9e453ef6 F test/fts3matchinfo.test aa66cc50615578b30f6df9984819ae5b702511cf8a94251ec7c594096a703a4a F test/fts3matchinfo2.test 00144e841704b8debfcdf6097969cd9f2a1cf759e2203cda42583648f2e6bf58 @@ -985,12 +1000,12 @@ F test/fts3near.test 7e3354d46f155a822b59c0e957fd2a70c1d7e905 F test/fts3offsets.test b85fd382abdc78ebce721d8117bd552dfb75094c F test/fts3prefix.test fa794eaab0bdae466494947b0b153d7844478ab2 F test/fts3prefix2.test e1f0a822ca661dced7f12ce392e14eaf65609dce -F test/fts3query.test ca033ff2ebcc22c69d89032fb0bc1850997d31e7e60ecd26440796ba1654e78f +F test/fts3query.test 45806a302921b245a9dba5d85c9d51fb98b3f137eea6e6bf6eae4883e041ab72 F test/fts3rank.test cd99bc83a3c923c8d52afd90d86979cf05fc41849f892faeac3988055ef37b99 F test/fts3rnd.test 1320d8826a845e38a96e769562bf83d7a92a15d0 F test/fts3shared.test 57e26a801f21027b7530da77db54286a6fe4997e F test/fts3snippet.test 0887196d67cffbe365edde535b95ecc642a532ce8551ccd9a73aab5999c3ffae -F test/fts3snippet2.test 2dabb5889eda4c9980aad325e688b470781f97ce7c0fca0db125616fae0a2cdd +F test/fts3snippet2.test e79afeb1f673713f96d7fc5655726081975399d11e659d15553207be43301dc4 F test/fts3sort.test ed34c716a11cc2009a35210e84ad5f9c102362ca F test/fts3tok1.test a663f4cac22a9505400bc22aacb818d7055240409c28729669ea7d4cc2120d15 F test/fts3tok_err.test 52273cd193b9036282f7bacb43da78c6be87418d @@ -1014,17 +1029,18 @@ F test/fts4noti.test 5553d7bb2e20bf4a06b23e849352efc022ce6309 F test/fts4onepass.test d69ddc4ee3415e40b0c5d1d0408488a87614d4f63ba9c44f3e52db541d6b7cc7 F test/fts4opt.test 0fd0cc84000743ff2a883b9b84b4a5be07249f0ba790c8848a757164cdd46b2a F test/fts4record.test a48508f69a84c9287c8019d3a1ae712f5730d8335ffaf8e2101e691d078950bb -F test/fts4rename.test 15fd9985c2bce6dea20da2245b22029ec89bd4710ed317c4c53abbe3cfd0c880 +F test/fts4rename.test 2e0565ffd92b2c51f1a757df0b8f2ca30537197106fec09e943919801d173692 F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f757380429 -F test/fts4unicode.test ceca76422abc251818cb25dabe33d3c3970da5f7c90e1540f190824e6b3a7c95 -F test/fts4upfrom.test 8df5acb6e10ad73f393d1add082b042ab1db72567888847d098152121e507b34 +F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 +F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test f673822636fb8ed618dd2b80230d16e495d19c8f2e2e7d6c22e93e2b3de097ad +F test/func.test 77f6ea02c97d9ea64074461d347276a75df22d2cf51045a40f90857569e985f0 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 2bb0f31ab7baaed690b962a88544d7be6b34fa389364bc36a44e441ed3e3f1e6 F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 F test/func5.test 863e6d1bd0013d09c17236f8a13ea34008dd857d87d85a13a673960e4c25d82a F test/func6.test 90e42b64c4f9fb6f04f44cb8a1da586c8542502e926b19c76504fe74ff2a9b7c +F test/func7.test b9e2a1a30a8562b00841b4a21a5d2d81754fa3ab99275fd71fd5279287b44b1c F test/fuzz-oss1.test e58330d01cbbd8215ee636b17a03fe220b37dbfa F test/fuzz.test 96083052bf5765e4518c1ba686ce2bab785670d1 F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1 @@ -1032,7 +1048,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 5f81f2cc65f13068620245f2e2c6059657d3b26be476df379ae2da539f17676d +F test/fuzzcheck.c 37f09efcd982048694d8b06fa3ef817df17f2a82e90af85cb3fb8eddd0f57ec4 F test/fuzzdata1.db d36e88741b4f23bcbaaf55b006290669d03c6c891cf13c7b3a53bc1b097b693f F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1040,17 +1056,17 @@ F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e4 F test/fuzzdata5.db e35f64af17ec48926481cfaf3b3855e436bd40d1cfe2d59a9474cb4b748a52a5 F test/fuzzdata6.db 92a80e4afc172c24f662a10a612d188fb272de4a9bd19e017927c95f737de6d7 F test/fuzzdata7.db 0166b56fd7a6b9636a1d60ef0a060f86ddaecf99400a666bb6e5bbd7199ad1f2 -F test/fuzzdata8.db f8451a1fd38efbea8c1a7cdf5d02259c4702446a9fabf566becd306b64a50236 +F test/fuzzdata8.db 9e0123ce3df47d1f159f7b71d60b96bd8b89800a40ab081c5402167d7239ead7 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 -F test/fuzzerfault.test 8792cd77fd5bce765b05d0c8e01b9edcf8af8536 +F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c F test/gencol1.test b05e6c5edb9b10d48efb634ed07342441bddc89d225043e17095c36e567521a0 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 -F test/having.test ea5cb01cdf3d90fd1b516ef36b1fbde518dbbd61c50141f5eb830d8101844040 +F test/having.test a89236dd8d55aa50c4805f82ac9daf64d477a44d712d8209c118978d0ca21ec9 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711 -F test/hook.test e97382e68e4379838e888756d653afd159f5f14780315ff97b70360d3d8485bc +F test/hook.test 55b6d605d06dadbb04416eae8ad79889aea3521d119c87f04353c25f9c1450a4 F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 F test/icu.test 716a6b89fbabe5cc63e0cd4c260befb08fd7b9d761f04d43669233292f0753b1 F test/ieee754.test b0945d12be7d255f3dfa18e2511b17ca37e0edd2b803231c52d05b86c04ab26e @@ -1058,9 +1074,9 @@ F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 F test/in.test 688ed2011d922d83141a45af431601738674a4c0bdde34b6351f688b82a169b3 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 -F test/in4.test 65460600d48933adba4283c6ebd089aae173d16136ab9d01f74c89089090c5a5 +F test/in4.test addf1416fdbbcf2f082ab7974e4813f38b4759b95699a683a31c37a3fffe7b57 F test/in5.test b32ce7f4a93f44c5dee94af16886d922cc16ebe33c8e1765c73d4049d0f4b40f -F test/in6.test 8562d0945195cab3cc4ab3794e9118e72cb44c43f785c2b04d48a9d06ca6b4ec +F test/in6.test f5f40d6816a8bb7c784424b58a10ac38efb76ab29127a2c17399e0cbeeda0e4b F test/incrblob.test c9b96afc292aeff43d6687bcb09b0280aa599822 F test/incrblob2.test a494c9e848560039a23974b9119cfc2cf3ad3bd15cc2694ee6367ae537ef8f1f F test/incrblob3.test d8d036fde015d4a159cd3cbae9d29003b37227a4 @@ -1068,7 +1084,7 @@ F test/incrblob4.test 21a52a6843a56cdcce968c6a86b72a7066d0e6ba F test/incrblob_err.test 89372a28f1d98254f03fed705f9efcd34ef61a674df16d2dbb4726944a2de5e9 F test/incrblobfault.test 74dd8ac108304cea0b4a0df6df63a1567e558758 F test/incrcorrupt.test 6c567fbf870aa9e91866fe52ce6f200cd548939a -F test/incrvacuum.test 2aaee202b1f230e55779f70d155f6ba67bbdff8481d650214d256ab0f97d4a2b +F test/incrvacuum.test 3fa6145f5e71f603554fd7b8ec3da4290b1341029682313285cb5f9e1893d6ba F test/incrvacuum2.test 7d26cfda66c7e55898d196de54ac4ec7d86a4e3d F test/incrvacuum3.test 75256fb1377e7c39ef2de62bfc42bbff67be295a F test/incrvacuum_ioerr.test 6ae2f783424e47a0033304808fe27789cf93e635 @@ -1077,13 +1093,13 @@ F test/index2.test f835d5e13ca163bd78c4459ca15fd2e4ed487407 F test/index3.test 51685f39345462b84fcf77eb8537af847fdf438cc96b05c45d6aaca4e473ade0 F test/index4.test ab92e736d5946840236cd61ac3191f91a7856bf6 F test/index5.test 8621491915800ec274609e42e02a97d67e9b13e7 -F test/index6.test f172653b35b20233e59200e8b92a76db61bf7285437bf777b93b306ba26a47e7 -F test/index7.test b8a0ba2110fd517bb48c4e76d26d60f1ab2ed9e257b18d71f820d7e71e9f8570 -F test/index8.test bc2e3db70e8e62459aaa1bd7e4a9b39664f8f9d7 +F test/index6.test 5a7ab531c692ff3b3d139ef8ea6709fab40f9c6862ed418b4976752a5481da3a +F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6eb3 +F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3 F test/index9.test 0aa3e509dddf81f93380396e40e9bb386904c1054924ba8fa9bcdfe85a8e7721 -F test/indexedby.test f54aac21c06948872010a956fd02de5178c362c7785a9887cf0b8616be17883b -F test/indexexpr1.test 284e119999d132cc8bf37735a928c9859b28e8e295d02b7a6a4f93977c7f9ba5 -F test/indexexpr2.test dba11dbb0a58fcba4cd694f46b4004976123b81b0501f525d43c9be59f0207b1 +F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 +F test/indexexpr1.test 7e0e7a33acb4d9b3524398e6ce90cc05c26603fabbaf3062083a036c8874bc12 +F test/indexexpr2.test 2c7abe3c48f8aaa5a448615ab4d13df3662185d28419c00999670834a3f0b484 F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 F test/insert.test 4e3f0de67aac3c5be1f4aaedbcea11638f1b5cdc9a3115be14d19aa9db7623c6 @@ -1097,7 +1113,7 @@ F test/instrfault.test 95e28efade652e6d51ae11b377088fe523a581a07ec428009e152a4dd F test/intarray.test bb976b0b3df0ebb6a2eddfb61768280440e672beba5460ed49679ea984ccf440 F test/interrupt.test 16ea879ec728cb76414c148c5f24afd5d1f91054 F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d -F test/intpkey.test ac71107a49a06492b69b82aafaf225400598d3c8 +F test/intpkey.test aee694afed1a65c86c4e69ad030224b3fc268113d00685234d40079fca16bad3 F test/intreal.test 2a87e85a5949bd55e41ef04c58f5800587c5380bdbc559ff1c79b614b0e6a533 F test/io.test f95bca1783b01ea7761671560d023360d2dfa4cc F test/ioerr.test 470fcc78e9cd352d162baf782fe301ea807d764241f58a48fc58109c2dfcdb6b @@ -1108,10 +1124,10 @@ F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b F test/istrue.test 9619a2d77580f676048aaff7a16a0bcfea2b96c6c660dfaded2e53c873418899 F test/join.test 25da4f53523a4aa17c893134b47fba6aa4799bb33350517b157785878290e238 -F test/join2.test 21fc30e54ab35ed66bf51b89cec18729205497f5cc43c83bc042f96a73721593 +F test/join2.test 9bdc615841b91c97a16d68bad9508aea11fa0c6b34e5689847bcc4dac70e4990 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 -F test/join5.test 3a96dc62f0b45402d7207e22d1993fe0c2fce1c57644a11439891dd62b990eb7 +F test/join5.test f418fccdfefa41f1659663463aa517431ddcf3e30ccbb80e64173b7d615a03f4 F test/join6.test f809c025fa253f9e150c0e9afd4cef8813257bceeb6f46e04041228c9403cc2c F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 @@ -1129,10 +1145,10 @@ F test/kvtest.c feb4358fb022da8ebd098c45811f2f6507688bb6c43aa72b3e840df19026317b F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 F test/lemon-test01.y 58b764610fd934e189ffbb0bbfa33d171b9cb06019b55bdc04d090d6767e11d7 -F test/like.test 47b81d5de2ff19d996d49a65d50ec9754246aacbe0e950b48d186d9d8171eaf0 +F test/like.test 5fe0bc37f307aef0a453ce2de4632bdfc0759448f0421c39f6d53caefe905fac F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da -F test/like3.test 03d1bdf848483b78d2cfd1db283d75c4ec2e37c8b8eccc006813f3978d78fbbd -F test/limit.test 0c99a27a87b14c646a9d583c7c89fd06c352663e +F test/like3.test a76e5938fadbe6d32807284c796bafd869974a961057bc5fc5a28e06de98745c +F test/limit.test 350f5d03c29e7dff9a2cde016f84f8d368d40bcd02fa2b2a52fa10c4bf3cbfaf F test/limit2.test 9409b033284642a859fafc95f29a5a6a557bd57c1f0d7c3f554bd64ed69df77e F test/loadext.test faa4f6eed07a5aac35d57fdd7bc07f8fc82464cfd327567c10cf0ba3c86cde04 F test/loadext2.test 0408380b57adca04004247179837a18e866a74f7 @@ -1166,7 +1182,7 @@ F test/mallocG.test 0ff91b65c50bdaba680fb75d87fe4ad35bb7934f F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb F test/mallocI.test 6c23a71df077fa5d387be90e7e669c5b368ca38a F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e -F test/mallocK.test 1f4b5efbf61715ab79b20b38739ff4b3d110ceb53f54e5db6da1f01c083707ab +F test/mallocK.test 25897506da0098cea09b302ff432b0fb6d8002773c1e0fc9732aa8b444bfd455 F test/mallocL.test fb311ff80afddf3b1a75e52289081f4754d901dc F test/mallocM.test 78bbe9d3da84a5c679123cdb40d7b2010b18fc46e13897e4f253c6ba6fbff134 F test/malloc_common.tcl aac62499b76be719fac31e7a3e54a7fd53272e7f @@ -1174,22 +1190,23 @@ F test/malloctraceviewer.tcl b7a54595270c1d201abf1c3f3d461f27eaf24cdef623ad08a0f F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7 -F test/memdb1.test 58d92c2bf622cc5f0f41461e1b35cf64f3f787199544c2c1dada37d88753f9d4 +F test/memdb1.test 7b76c3262d63c46dd6b408d18f5721071776f2df4ffeb11e668824e427127594 +F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 F test/memsubsys1.test 9e7555a22173b8f1c96c281ce289b338fcba2abe8b157f8798ca195bbf1d347e F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08 -F test/minmax.test 6751e87b409fe11b02e70a306d846fa544e25a41 -F test/minmax2.test 1edf66901ddfab26ae1e04165e8da834c8d3284e2b20aefb26b80ef217962eab +F test/minmax.test 0015e5cd5e7af48bb3364f26d9f3a9cdbea2a442d4774281c39e2229591b7351 +F test/minmax2.test cf9311babb6f0518d04e42fd6a42c619531c4309a9dd790a2c4e9b3bc595e0de F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 F test/minmax4.test 272ca395257f05937dc96441c9dde4bc9fbf116a8d4fa02baeb0d13d50e36c87 F test/misc1.test 7ce84b25df9872e7d7878613a96815d2ba5bc974ac4e15a50118dde8f3917599 -F test/misc2.test 00d7de54eda90e237fc9a38b9e5ccc769ebf6d4d +F test/misc2.test 71e746af479119386ac2ed7ab7d81d99970e75b49ffd3e8efffee100b4b5f350 F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d F test/misc4.test 10cd6addb2fa9093df4751a1b92b50440175dd5468a6ec84d0386e78f087db0e F test/misc5.test c4aeaa0fa28faa08f2485309c38db4719e6cd1364215d5687a5b96d340a3fa58 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 -F test/misc7.test 7b4c88c1d5ea8c8b9d537d212c08a0343d345fdd5c789598692c1c0e60fbda69 -F test/misc8.test 8782708f4c8a459591c3e8fe1215bd2048bffb4024b3df249e9b9ed407dc61ed +F test/misc7.test d912f3d45c2989191b797504a220ca225d6be80b21acad22ba0d35f4a9ee4579 +F test/misc8.test 4db9f8be59834cea08c87e9658014080efa02678ef54a088f84fa5647e81fee0 F test/misuse.test 9e7f78402005e833af71dcab32d048003869eca5abcaccc985d4f8dc1d86bcc7 F test/mjournal.test 28a08d5cb5fb5b5702a46e19176e45e964e0800d1f894677169e79f34030e152 F test/mmap1.test fb04e0c10492455007624ade884ca0c8852ff3e4e11d95408f9709ca2ef7f626 @@ -1212,16 +1229,18 @@ F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 F test/notify3.test 10ff25cde502e72a92053a2f215d64bece4ef934 F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18 +F test/notnull2.test 131365171d973c57fe694fcc46c1e645f6e24ceb43b230ad5011a0ea2182f215 +F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5 F test/null.test b7ff206a1c60fe01aa2abd33ef9ea83c93727d993ca8a613de86e925c9f2bc6f -F test/nulls1.test 82c5bc33148405f21205865abf13c786084438d573a4ac4e87e11b6091cde526 +F test/nulls1.test 7a5e4346ee4285034100b4cd20e6784f16a9d6c927e44ecdf10034086bbee9c9 F test/numcast.test 5d126f7f581432e86a90d1e35cac625164aec4a1 F test/numindex1.test 20a5450d4b056e48cd5db30e659f13347a099823 F test/offset1.test f06b83657bcf26f9ce805e67450e189e282143b2 F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394 F test/optfuzz-db01.c 9f2fa80b8f84ebbf1f2e8b13421a4e0477fe300f6686fbd76cac1d2db66e0fdc F test/optfuzz-db01.txt 21f6bdeadc701cf11528276e2a55c70bfcb846ba42df327f979bd9e7b6ce7041 -F test/optfuzz.c 50e330304eb1992e15ddd11f3daaad9bcc0d9aaad09cb2bcc77f9515df2e88b1 -F test/orderby1.test 6bf0ce45cbfb1cf4779dd418ac5e8cf66abfa04de2c1d2edf1e0e85f1520d8f3 +F test/optfuzz.c 690430a0bf0ad047d5a168bf52b05b2ee97aedaad8c14337e9eb5050faa64994 +F test/orderby1.test a4bba04b9c60a21e53486fbc173a596b29641a3b3a57a0f26a1cbef1360358e9 F test/orderby2.test bc11009f7cd99d96b1b11e57b199b00633eb5b04 F test/orderby3.test 8619d06a3debdcd80a27c0fdea5c40b468854b99 F test/orderby4.test 4d39bfbaaa3ae64d026ca2ff166353d2edca4ba4 @@ -1235,7 +1254,7 @@ F test/oserror.test 1fc9746b83d778e70d115049747ba19c7fba154afce7cc165b09feb6ca6a F test/ossfuzz.c 9636dad2092a05a32110df0ca06713038dd0c43dd89a77dabe4b8b0d71096715 F test/ossshell.c f125c5bd16e537a2549aa579b328dd1c59905e7ab1338dfc210e755bb7b69f17 F test/ovfl.test 199c482696defceacee8c8e0e0ef36da62726b2f -F test/pager1.test 293c7ad1f19a07e548179b97534c6588249d4e40058b378ea26e75cc84d936f5 +F test/pager1.test a4e438c344663ad7f0bf6e880cacae7531bdf7d960db15a3db4751273ecee06d F test/pager2.test 67b8f40ae98112bcdba1f2b2d03ea83266418c71 F test/pager3.test 4e9a83d6ca0838d7c602c9eb93d1357562d9059c1e02ffb138a8271020838370 F test/pager4.test a122e9e6925d5b23b31e3dfef8c6a44bbf19590e @@ -1248,7 +1267,7 @@ F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035c F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 661a4325a5717957a77836910ee164ba26594a502d7a3df0e1ae7b9cba829c5d +F test/permutations.test 63da39a4234eed2ccd10bf7872de58e24d53a50d11014dc8a8ab9f252368e880 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f F test/pragma.test 50b91bedea9324d3ab48e793f908ee7d2c7dcf84bfa2281e792838be59641ec8 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f @@ -1267,35 +1286,36 @@ F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 F test/quota-glob.test 32901e9eed6705d68ca3faee2a06b73b57cb3c26 F test/quota.test bfb269ce81ea52f593f9648316cd5013d766dd2a F test/quota2.test 7dc12e08b11cbc4c16c9ba2aa2e040ea8d8ab4b8 -F test/quote.test 3f9238ab0e1db70dea89af9afa5859dbd759b0ce0a63da67d547553e2c316475 +F test/quote.test b8ddaba6b81dcf63bb31243219e28a2f96e04396adc50108cc7e5593019c3eb5 F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459 F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/recover.test ccb8c2623902a92ebb76770edd075cb4f75a4760bb7afde38026572c6e79070d -F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8 -F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c +F test/regexp1.test 0c3ff80f66b0eff80e623eb5db7a3dad512095c573d78ac23009785f6d8f51ce +F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest.tcl fb76d8fcc95ac29d6356cd9e52b726ab9e43a24082897618dfbcb7c2b0049153 x -F test/releasetest_data.tcl b9cb30360759b80d92d4ea86b84ebfd8035b97f9078a482deb3cf9d0b2442655 +F test/releasetest.tcl 6f803ef0b896f8f3f4c26eb072c0399963a5987a509a64d45f5dfbc1ebae2951 x +F test/releasetest_data.tcl f88ed29aa18366ed3956ace36c96ec6868ef5b9ee04cc05d32f4d81031e19e28 F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb +F test/returning1.test 52b4ed60f104a4ce85b38417e5319b2078b2b908c71a768d539cee3c0ce8bdc9 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa -F test/rollback2.test bc868d57899dc6972e2b4483faae0e03365a0556941474eec487ae21d8d38bb6 +F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a F test/round1.test 768018b04522ca420b1aba8a24bd76091d269f3bce3902af3ec6ebcee41ab21e F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test bfbd7b97d9267660be3c8f28507c4ed7f205196b8877c0db42df347c2e8845e3 -F test/rowvalue.test 8964f95b253d3b5cc8dc1cfd0cdb7529bce3ecc6b6259e23c5f829f80f4d51cd +F test/rowvalue.test 37effea4dd83555ea969a9461dfcffb25e6731a5db7c388e232410999c100853 F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256 -F test/rowvalue4.test 02e35f7762371c2f57ebd856aa056eac56cb27ef7715a0bb31eac1895a745356 +F test/rowvalue4.test 441e7e366ac6d939a3a95a574031c56ec2a854077a91d66eee5ff1d86cb5be58 F test/rowvalue5.test c81c7d8cf36711ab37675ad7376084ae2a359cb6 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 F test/rowvalue7.test c1cbdbf407029db01f87764097c6ac02a1c5a37efd2776eff32a9cdfdf6f2dba F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 -F test/rowvalue9.test d8dd2c6ecac432dadaa79e41dc2434f007be1b6b +F test/rowvalue9.test cb5380df82dca9db463081e26952c1e097b34fc2c2ac87453970c048d97df687 F test/rowvaluefault.test 7cd9ccc6c2fbdd881672984087aad0491bb75504 F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 @@ -1307,7 +1327,7 @@ F test/savepoint5.test 0735db177e0ebbaedc39812c8d065075d563c4fd F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7 F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 -F test/scanstatus.test 874e35011779b07725a47dbf1dd6282b0ca04af7e028fb0b534ee544b571be42 +F test/scanstatus.test 9a0ed37ab6d57b50567282788fffdf832d9b16739ecc41bff9d77a8d767cf317 F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 1bc1008e1f8cb5654b248c55f27249366eb7ed38 @@ -1317,19 +1337,19 @@ F test/schema6.test e4bd1f23d368695eb9e7b51ef6e02ca0642ea2ab4a52579959826b5e7dce F test/schemafault.test 1936bceca55ac82c5efbcc9fc91a1933e45c8d1e1d106b9a7e56c972a5a2a51e F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 -F test/select1.test 0ed936740777f52858b6607f39ffac4b2b63b8fc7edf3ab2ebad3c3553ceecee +F test/select1.test 3d23f66bf9ba77570acfe2ca5f1540ece17037cc64ab1a00efec9758ac29c268 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 F test/select3.test c49fbb758903f3718e2de5aa4655eda4838131cbea24a86db908f8b6889aa68c -F test/select4.test e8a2502e3623f3058871030599a48abb35789d2244d5b380ecf3696873fdd4a4 -F test/select5.test df9ec0d218cedceb4fe7b63262025b547b50a55e59148c6f40b60ca25f1d4546 +F test/select4.test f0684d3da3bccacbe2a1ebadf6fb49d9df6f53acb4c6ebc228a88d0d6054cc7b +F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f390ae F test/select6.test 319d45e414cdd321bf17cfacedaf19e3935ad64dac357c53f1492338c6e9b801 F test/select7.test f659f231489349e8c5734e610803d7654207318f F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d -F test/select9.test aebc2bb0c3bc44606125033cbcaac2c8d1f33a95 -F test/selectA.test 68de52409e45a3313d00b8461b48bef4fb729faf36ade9067a994eae55cc86f4 +F test/select9.test f7586b207ce2304ab80dc93d3146469a28fd4403621dd3a82d06644563d3c812 +F test/selectA.test 985df11dfc58dedd6f8124dea32754566346b93e294f26291b5c96d10c8cc889 F test/selectB.test 954e4e49cf1f896d61794e440669e03a27ceea25 -F test/selectC.test e25243f8ca503e06f252eb0218976d07cfeceac3 -F test/selectD.test fc20452847a01775710090383cfb4423275d2f745fed61f34fbf37573ac0d214 +F test/selectC.test fec14c9015ed4ec941508bbc144f30b42e40ac34a4bb33001450369865dd0b75 +F test/selectD.test 6d1909b49970bf92f45ce657505befcef5fc7cbc13544e18103a316d32189bfb F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3 F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840 @@ -1349,10 +1369,10 @@ F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 0ba53c72545de142e1b76fa793ee33293134aa02abb9b50b35398670481ea661 +F test/shell1.test 56a7358a2a05e850e9e4aa24629db9c8975e8038dbe8debd2d95be22a5f03612 F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b F test/shell3.test ac8c2b744014c3e9a0e26bfd829ab65f00923dc1a91ffd044863e9423cc91494 -F test/shell4.test 1c6aef11daaa2d6830acaba3ac9cbec93fbc1c3d5530743a637f39b3987d08ce +F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f48be F test/shell5.test 84a30b55722a95a5b72989e691c469a999ca7591e7aa00b7fabc783ea5c9a6fe F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f @@ -1362,11 +1382,11 @@ F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 F test/shrink.test 1b4330b1fd9e818c04726d45cb28db73087535ce F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329 -F test/skipscan1.test ed524bc86f27646b3a297f45d6557b55db338977b6838f8064b196b35848b31b -F test/skipscan2.test 3eb703ce794f139e7b83567911046298bcde29606116727f9b700ce34f559d2d +F test/skipscan1.test 1a9972e1dc15ca3887f306d3cd9a29679afb382eca0f3539f3b746f3c2ccaf68 +F test/skipscan2.test c588cb7ed947db724d300f2a0dc537dd2ad292b0f812641d8481bc0b95dd3f49 F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 -F test/skipscan5.test 980875ad45d8aacfe7bdec7c288343520cd1d14e84befdcfe50c785f9e6c8709 -F test/skipscan6.test 0b4cd1b4ac9f84d91454df513c99a4932fa07e8f27b8049bea605068b3e34ac7 +F test/skipscan5.test 0672103fd2c8f96bd114133f356192b35ece45c794fe3677e1d9e5e3104a608e +F test/skipscan6.test bddbb35dd335e2d21b7791a61e3b2e1f3255dc307ce80aa6fe19cc298e6feb13 F test/snapshot.test a504f2e7009f512ef66c719f0ea1c55a556bdaf1e1312c80a04d46fc1a3e9632 F test/snapshot2.test 8d6ff5dd9cc503f6e12d408a30409c3f9c653507b24408d9cd7195931c89bc54 F test/snapshot3.test 8744313270c55f6e18574283553d3c5c5fe4c5970585663613a0e75c151e599b @@ -1390,14 +1410,15 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest1.c 849dbcb0bded9f966e43f28e8ce824b2915cb5dd0031a2e85996f7e3de36c2b1 +F test/speedtest1.c 5e5b805f24cc939656058f6a498f5a2160f9142e4815c54faf758ec798d4cdad F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb F test/sqldiff1.test 28cd737cf1b0078b1ec1bbf425e674c47785835e -F test/sqllimits1.test 264f4b0f941800ba139d25e33ee919c5d95fea06dfbe8ac291d6811a30984ca5 +F test/sqllimits1.test 3f9030e5d35375ad3b912b4908094aa806335c8e9d804b8ffff70c5e9c664ab2 F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a +F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805 F test/stat.test 15a3106eddedfc882f64bc09f237b4169be4b92dd57c93031b8ff8b13af3e7c5 F test/statfault.test f525a7bf633e50afd027700e9a486090684b1ac1 F test/stmt.test 54ed2cc0764bf3e48a058331813c3dbd19fc1d0827c3d8369914a5d8f564ec75 @@ -1419,7 +1440,7 @@ F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d433309 F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039 F test/syscall.test a39d9a36f852ae6e4800f861bc2f2e83f68bbc2112d9399931ecfadeabd2d69d F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 -F test/tabfunc01.test 5ca6d004157a3e886a55a9387b960cc0db41acd88753eb597ff409ec6cfb1be0 +F test/tabfunc01.test acb5be558868c65d8cf3495539fff23093d77139eb1e4e8c4580568099f98645 F test/table.test eb3463b7add9f16a5bb836badf118cf391b809d09fdccd1f79684600d07ec132 F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 @@ -1431,7 +1452,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test d2940417496e2b9548e01d09990763fbe88c316504033256d51493e1f1a5ce6a F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 754521f0db534d51ab504b2d14fe0bdca1f1c15de731ceb8ee5bfd78372a2a5f +F test/tester.tcl 19d2a19a6dd55a2b4e2b943963959a05a2c088495dd5f5274b04e0494ce86d66 F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1444,6 +1465,7 @@ F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b F test/threadtest2.c a70a8e94bef23339d34226eb9521015ef99f4df8 F test/threadtest3.c e63013af10cf236c7610eb06d33bde08c861806dc64be811940ff4d9ddd34a4f F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925 +F test/threadtest5.c 9b4d782c58d8915d7e955ff8051f3d03628bda0d33b82971ea8c0f2f2808c421 F test/time-wordcount.sh 8e0b0f8109367827ad5d58f5cc849705731e4b90 F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c F test/tkt-18458b1a.test 6a62cb1ee50fa3c620da59e3a6f531eb38fceaf7e2166203816b724524e6f1d6 @@ -1453,7 +1475,7 @@ F test/tkt-2d1a5c67d.test be1326f3061caec85085f4c9ee4490561ca037c0 F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28 F test/tkt-31338dca7e.test 6fb8807851964da0d24e942f2e19c7c705b9fb58 F test/tkt-313723c356.test 4b306ad45c736cedf2f5221f6155b92143244b6d -F test/tkt-385a5b56b9.test 5204a7cba0e28c99df0acbf95af5e1af4d32965a7a14de6eccebf949607618b1 +F test/tkt-385a5b56b9.test 81d7c8ef2d2a378323ba2190c1f02f91022fdff0d8d0b84c04954d02c85ef374 F test/tkt-38cb5df375.test f3cc8671f1eb604d4ae9cf886ed4366bec656678 F test/tkt-3998683a16.test 6d1d04d551ed1704eb3396ca87bb9ccc8c5c1eb7 F test/tkt-3a77c9714e.test 90e3e8455ee945a4076d4c44062b8845708af24a880355328fe7008f2047c9f0 @@ -1468,7 +1490,7 @@ F test/tkt-5e10420e8d.test 904d1687b3c06d43e5b3555bbcf6802e7c0ffd84 F test/tkt-5ee23731f.test 9db6e1d7209dc0794948b260d6f82b2b1de83a9f F test/tkt-6bfb98dfc0.test 24780633627b5cfc0635a5500c2389ebfb563336 F test/tkt-752e1646fc.test ea78d88d14fe9866bdd991c634483334639e13bf -F test/tkt-78e04e52ea.test cb44d0f5e7940223be740a39913a1b9b9b30d7e4a17ed3349141f893bae1b8f2 +F test/tkt-78e04e52ea.test b731f2ab7d1c2482ac5152097da02ef4805a45147ba9498d3cd9da27072f34d1 F test/tkt-7a31705a7e6.test 9e9c057b6a9497c8f7ba7b16871029414ccf6550e7345d9085d6d71c9a56bb6f F test/tkt-7bbfb7d442.test 7b2cd79c7a17ae6750e75ec1a7846712a69c9d18 F test/tkt-80ba201079.test 105a721e6aad0ae3c5946d7615d1e4d03f6145b8 @@ -1487,7 +1509,7 @@ F test/tkt-a8a0d2996a.test 002e1cde8fc30c39611b52cf981c88200b858765748556822da72 F test/tkt-b1d3a2e531.test 8f7576e41ca179289ee1a8fee28386fd8e4b0550 F test/tkt-b351d95f9.test d14a503c414c5c58fdde3e80f9a3cfef986498c0 F test/tkt-b72787b1.test a95e8cdad0b98af1853ac7f0afd4ab27b77bf5f3 -F test/tkt-b75a9ca6b0.test ade89229d853a67a21bbd5e6e1e787a8f9d21f19908d1b7fca6bf3d4d5aa0767 +F test/tkt-b75a9ca6b0.test dc6a853c242f7d0326564ae32e9e5eb462b5e8d2bc5b01ea3b18fd24f8e5894b F test/tkt-ba7cbfaedc.test b4c0deccc12aeb55cfdb57935b16b5d67c5a9877 F test/tkt-bd484a090c.test 60460bf946f79a79712b71f202eda501ca99b898 F test/tkt-bdc6bbbb38.test fc38bb09bdd440e3513a1f5f98fc60a075182d7d @@ -1553,7 +1575,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 a1fc47c669e651d16494de3ff349bcb53281456f2ca02c8bc14220b6044bbfe8 +F test/tkt3442.test 6287173de5bb2d43693b1f822426018a209f9df49ce2f454808bac1771852330 F test/tkt3457.test 5651e2cbb94645b677ec663160b9e192b87b7d365aecdfb24e19f749575a6fc2 F test/tkt3461.test 228ea328a5a21e8663f80ee3d212a6ad92549a19 F test/tkt3493.test 1686cbde85f8721fc1bdc0ee72f2ef2f63139218 @@ -1589,31 +1611,31 @@ F test/tkt3992.test f3e7d548ac26f763b47bc0f750da3d03c81071da F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd F test/tkt4018.test 18dbc6617f7a4b90e938d1bd6d26ad18daafaf08 F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7 -F test/tpch01.test 7c4eb8cdd79c568f46d344b3e789c9fdb8a766d112871352704861f3fca32a2a +F test/tpch01.test 4479008f85f6f8f25f7ab2cb305d665752b4727fa28a8df3d8e0ad46520c62ff F test/trace.test a659a9862957f4789e37a92b3bf6d2caf5c86b02cdeefc41e850ae53acf6992a F test/trace2.test f5cb67ad3bc09e0c58e8cca78dfd0b5639259983 -F test/trace3.test 1dff966888773ff1bfea01c080caf15417892b3f998408fe920c4791f7337144 +F test/trace3.test ae2004df24b585fed9046cc0bae4601762bc6fc4aa321d475f1350bba5047f31 F test/trans.test 45f6f9ab6f66a7b5744f1caac06b558f95da62501916906cf55586a896f9f439 F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76 F test/trans3.test 91a100e5412b488e22a655fe423a14c26403ab94 -F test/transitive1.test 293300f46916569f08875cdb2fe2134be2c27677 +F test/transitive1.test 06bcfeeb2ed719abf6ae582f9f65a6b07642dd1363fa648ae9a74a35e83a825c F test/trigger1.test d30cd09ae8ac365a088f09daba583cc5c0b8fc7d4e1d70809d0b4be3bf6ae2ab -F test/trigger2.test d15da46f7012832faf3e0c536b47024409d5fb1722d2bb77e29c06d96d704bb1 +F test/trigger2.test 6e35bd7321c49e63d540aee980eb95dec63e1d1caca175224101045bcc80871f F test/trigger3.test aa640bb2bbb03edd5ff69c055117ea088f121945 F test/trigger4.test 74700b76ebf3947b2f7a92405141eb2cf2a5d359 F test/trigger5.test 619391a3e9fc194081d22cefd830d811e7badf83 F test/trigger6.test 0e411654f122552da6590f0b4e6f781048a4a9b9 F test/trigger7.test e7ce54bfda67a88d778aea42544e151c465547a7e617127b6914c2221a6d53c1 F test/trigger8.test 30cb0530bd7c4728055420e3f739aa00412eafa4 -F test/trigger9.test 2226ec795a33b0460ab5cf8891e9054cc7edef41 +F test/trigger9.test fd49aff8b724ae1a6238989ecf84320d88c0e8f8d9142a891b93f826dfe37b59 F test/triggerA.test 837be862d8721f903dba3f3ceff05b32e0bee5214cf6ea3da5fadf12d3650e9d F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe F test/triggerC.test 29f5a28d0fe39e6e2c01f6e1f53f08c0955170ae10a63ad023e33cb0a1682a51 F test/triggerD.test 8e7f3921a92a5797d472732108109e44575fa650 -F test/triggerE.test ede2e4bce4ba802337bd69d39447fa04a938e06d84a8bfc53c76850fc36ed86d +F test/triggerE.test 612969cb57a4ef792059ad6d01af0117e1ae862c283753ffcc9a6428642b22ee F test/triggerF.test 5d76f0a8c428ff87a4d5ed52da06f6096a2c787a1e21b846111dfac4123de3ad F test/triggerG.test 2b816093c91ba73c733cfa8aedcc210ad819d72a98b1da30768a3c56505233e9 -F test/triggerupfrom.test d25961fa70a99b6736193da7b49a36d8c1d28d56188f0be6406d4366315cd6e4 +F test/triggerupfrom.test d1f9e56090408115c522bee626cc33a2f3370f627a5e341d832589d72e3aa271 F test/trustschema1.test 4e970aef0bfe0cee139703cc7209d0e0f07725d999b180ba50770f49edef1494 F test/tt3_checkpoint.c 9e75cf7c1c364f52e1c47fd0f14c4340a9db0fe1 F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a @@ -1624,23 +1646,27 @@ F test/tt3_vacuum.c 1753f45917699c9c1f66b64c717a717c9379f776 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a +F test/unionall.test fee6e6adebe5446938475432ae8b042835a286c7e2efdc1b186d2182dfb93ffb +F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1 +F test/unionallfault.test 652bfbb630e6c43135965dc1e8f0a9a791da83aec885d626a632fe1909c56f73 F test/unionvtab.test e1704ab1b4c1bb3ffc9da4681f8e85a0b909fd80b937984fc94b27415ac8e5a4 F test/unionvtabfault.test e8759f3d14fb938ce9657e2342db34aeac0fb9bc1692b0d1ebb0069630151d06 F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264 F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2 F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97 -F test/unordered.test ffeea7747d5ba962a8009a20b7e53d68cbae05b063604c68702c5998eb50c981 +F test/unordered.test 0edaf3411d300693bca595897c5201421c6c5ec787990a1dfe2f7f60ae93f1e2 F test/update.test e906ca7cb1dc6f52af1ea243e08f727edfa79f924c2691f2f9e72481f847310d F test/update2.test 67455bc61fcbcf96923c45b3bc4f87bc72be7d67575ad35f134906148c7b06d3 F test/upfrom1.tcl 8859d9d437f03b44174c4524a7a734a391fd4526fcff65be08285dafc9dc9041 -F test/upfrom1.test d18f69f7c691bc791e7f31bf0e354eeff04cf2f44edc32d6b1928bad71697073 -F test/upfrom2.test 6ebd3be8c3fac984e89a177d823686f04605b512fc167392bce6d8ba2ba63325 -F test/upfrom3.test 7dab379d128e8dd7beb2055b295fb113c7ba93e8c2038f5ddb7a4a10f0ebb348 -F test/upfromfault.test 70ecf8eb85559727a487283f69374e3ae39879e994d8a2437c49d7c05ecb70c9 +F test/upfrom1.test 8cb06689e99cd707d884faa16da0e8eb26ff658bb01c47ddf72fadade666e6e1 +F test/upfrom2.test f92e47bfc35e9410d3e8716ee626384e89ad026c55fd6148508ca9d707521673 +F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0 +F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6 F test/upsert1.test 88f9e258c6a0eeeb85937b08831e8daad440ba41f125af48439e9d33f266fb18 F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c5a09 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 +F test/upsert5.test fff0dcfce73c649204543088d8e5bde01172676063ec9b8f8fc7f195abc386fe F test/upsertfault.test f21ca47740841fdb4d61acfa7b17646d773e67724fe8c185b71c018db8a94b35 F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 @@ -1648,15 +1674,17 @@ F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae F test/vacuum-into.test 48f4cec354fb6f27c98ef58d2fe49a11b71ff131af0cd9140efacc9858b9f670 F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d -F test/vacuum2.test aa048abee196c16c9ba308465494009057b79f9b +F test/vacuum2.test 9fd45ce6ce29f5614c249e03938d3567c06a9e772d4f155949f8eafe2d8af520 F test/vacuum3.test 77ecdd54592b45a0bcb133339f99f1ae0ae94d0d F test/vacuum4.test 7ea76b769fffeb41f925303b04cbcf5a5bbeabe55e4c60ae754ff24eeeb7c010 F test/vacuum5.test 263b144d537e92ad8e9ca8a73cc6e1583f41cfd0dda9432b87f7806174a2f48c +F test/vacuum6.test d3173a54edc81d13d99e4cf4972232b3cbb52f1d56ed48c3a939ef4e751c1ee8 F test/vacuummem.test 7b42abb3208bd82dd23a7536588396f295a314f2 F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 -F test/view.test fd48eddb32a35a98c3de70062ebac66ebf4a2bbfc75a1cc1109159ef8bfc47a9 -F test/vtab1.test c5d9e90ed02bcacd776dcbb7360199d290f7f53c26b484ddece543060c54319f +F test/view.test ea88361d5e9bc8eabf9f573185a16aea73a885be9b6c6a95ae84908913416a80 +F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 +F test/vtab1.test 99c0c13b5336ca7f87f137459de144b2f396bb8563fbd602e46bfaa425e3d8cc F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e F test/vtab4.test 8e73ed268f3d596bc3590f45fc948fb40f28e9c3 @@ -1678,7 +1706,7 @@ F test/vtab_alter.test 736e66fb5ec7b4fee58229aa3ada2f27ec58bc58c00edae4836890c37 F test/vtab_err.test dcc8b7b9cb67522b3fe7a272c73856829dae4ab7fdb30399aea1b6981bda2b65 F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad F test/vtabdrop.test 65d4cf6722972e5499bdaf0c0d70ee3b8133944a4e4bc31862563f32a7edca12 -F test/wal.test 16180bc4becda176428ad02eaea437b4b8f5ae099314de443a4e12b2dcc007a2 +F test/wal.test b7cc6984709f54afbf8441747ced1f646af120bf0c1b1d847bfa39306fbea089 F test/wal2.test 31f6e2c404b9f2cdf9ca19b105a1742fdc19653c2c936da39e3658c617524046 F test/wal3.test 2a93004bc0fb2b5c29888964024695bade278ab2 F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c @@ -1709,43 +1737,44 @@ F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db8 F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20 F test/walro2.test 0e79dd15cbdb4f482c01ea248373669c732414a726b357d04846a816afafb768 F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 -F test/walsetlk.test 11f7fe792fdce54cf09874dab824e0627f2eedecfb9f7983e325606ec5184e0c +F test/walsetlk.test 3185bebc90557e0d611442c8d64f7a0cb7b06f8e156eea37a4a7358f722715be F test/walshared.test 0befc811dcf0b287efae21612304d15576e35417 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747 -F test/walvfs.test a2913001a83b19c1d20220e556cee14d87d47ecb6949b5e0a2e9e2590abecf1e +F test/walvfs.test bccb3e0d235ef85e276f491d34db32c9ada1ea67be8d9f10aabe7b30319ec656 F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec F test/wapptest.tcl 899594e25684861d5b0c0880fb012364def50ef8097041b8ddf74be5ba7fa270 x -F test/where.test e713c0c64e3e6b062235e39a2f7e5508c517df16b63d69fd786e26bc7330b1c6 -F test/where2.test 478d2170637b9211f593120648858593bf2445a1 -F test/where3.test 2341a294e17193a6b1699ea7f192124a5286ca6acfcc3f4b06d16c931fbcda2c +F test/where.test 4f7a3939e5190845ef42142031a1819070f65e065e6a840a09adc54dccfef01f +F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6 +F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b -F test/where7.test 75722434c486ac9e74718caa6cce234f45ba34c0b6c0f9555b29eb8bb5f6ade1 +F test/where7.test ab41d53ce8f2a6919ea3d5b13cd1153c1375a8e3ddaa129b81781f9033981383 F test/where8.test 461ca40265ed996a6305da99bb024b0e41602bb586acf544c08f95922358e49f -F test/where9.test 8e3e0ff42cc17156f52361a1c012281550d0d632912fec92d1d6df74db7a8e6d +F test/where9.test 1ffb75edc50a8faa6e7bd77f8221d783febb00b44b0bdb32fb48cec6e38eca95 F test/whereA.test 9d1077b117f1b68d5f739d94f36956c36cf995eb87bb19b77b2e81af020edd20 F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5 F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6 F test/whereD.test c1c335e914e28b122e000e9310f02d2be83e1c9dbca2e29f46bd732703944d1b -F test/whereE.test b3a055eef928c992b0a33198a7b8dc10eea5ad2f -F test/whereF.test 3d9412b1199d3e2bed34fcb76b4c48d0bf4df95d27e3f8dd27b6f8b4716d0d89 -F test/whereG.test 77127a4d214bb21da3b5c695cf4106cd9e418c60d9c24471ba55d3772067cd35 +F test/whereE.test 0ac7e61c6225354a980666996539da154991b4325af943a25a9079079c82fb03 +F test/whereF.test 926b65519608e3f2aa28720822b9154fb5c7b13519dd78194f434a511ab3dac5 +F test/whereG.test b2a479f425f7d0a432df7e842e8484560908ef703fe0fd407888ff85e7097238 F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 -F test/whereI.test a2874062140ed4aba9ffae76e6190a3df6fc73d1373fdfa8fd632945082a5364 -F test/whereJ.test 88287550f6ee604422403b053455b1ad894eeaa5c35d348532dfa1439286cb9a -F test/whereK.test f8e3cf26a8513ecc7f514f54df9f0572c046c42b -F test/whereL.test e05cedc9389c6f09ad55bd5999a3fddccebec90672fb989433c145dcdaf26996 -F test/wherefault.test 1374c3aa198388925246475f84ad4cd5f9528864 +F test/whereI.test c4bb7e2ca56d49bd8ab5c7bd085b8b83e353922b46904d68aefb3c7468643581 +F test/whereJ.test fc05e374cc9f2dc204148d6c06822c380ad388895fe97a6d335b94a26a08aecf +F test/whereK.test 0270ab7f04ba5436fb9156d31d642a1c82727f4c4bfe5ba90d435c78cf44684a +F test/whereL.test 50171e3ec00b4c8ad5ec773119a35d9e9642cec45154b44c366d628326479f4d +F test/whereM.test 0dbc9998783458ddcf3cc078ca7c2951d8b2677d472ecf0028f449ed327c0250 +F test/wherefault.test 6cf2a9c5712952d463d3f45ebee7f6caf400984df51a195d884cfb7eb0e837a7 F test/wherelfault.test 9012e4ef5259058b771606616bd007af5d154e64cc25fa9fd4170f6411db44e3 -F test/wherelimit.test 592081800806d297dd7449b1030c863d2883d6d42901837ccd2e5a9bd962edb0 +F test/wherelimit.test afb46397c6d7e964e6e294ba3569864a0c570fe3807afc634236c2b752372f31 F test/wherelimit2.test 657a3f24aadee62d058c5091ea682dc4af4b95ffe32f137155be49799a58e721 F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2aeee74 F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972 F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test e52b81fff0c3cb122a1240f336688eb81bea2967a99c4ddb78969adec7aadc2a +F test/window1.test 49eadb28b0bae0f916518c9983b1fb7450c198915664f323b2bfc480cc98c431 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -1756,32 +1785,34 @@ F test/window5.test d328dd18221217c49c144181975eea17339eaeaf0e9aa558cee3afb84652 F test/window6.test f8d674254b23289cc17c84d79dec7eda7caa1dfb7836c43122cfdf3640d1df32 F test/window7.tcl 6a1210f05d40ec89c22960213a22cd3f98d4e2f2eb20646c83c8c30d4d76108f F test/window7.test 1d31276961ae7801edc72173edaf7593e3cbc79c06d1f1f09e20d8418af403cd -F test/window8.tcl f2711aa3571e4e6b0dad98db8d95fd6cb8d9db0c92bbdf535f153b07606a1ce2 -F test/window8.test c4331b27a6f66d69fa8f8bab10cc731db1a81d293ae108a68f7c3487fa94e65b -F test/window9.test 4d8c875b73febdbac9b8f2b52ec132b98f48261cdafd6b08db62bc6d8ff913fc +F test/window8.tcl 5e02e41d9d9a80f597063aed1a381eb19d1d0ef677a4f0df352c5365cf23f79c +F test/window8.test 4ab16817414af0c904abe2ebdf88eb6c2b00058b84f9748c6174ff11fc45f1ed +F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae4836 F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be -F test/windowB.test 7a983ea1cc1cf72be7f378e4b32f6cb2d73014c5cd8b25aaee825164cd4269e5 +F test/windowB.test 6e601f8178ba8ba28b2f19e74fe613815084bb4a8d2ad942defc7d42e191e521 F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b -F test/windowfault.test 72375ae71031eabf96bc88d0af128c8628a091ddc99b5a394e848b3df5fc17ad -F test/with1.test 780be387f01e290e768bdfd1827280f9e37ba37223eb4736aba386864fac5a94 -F test/with2.test e0030e2f0267a910d6c0e4f46f2dfe941c1cc0d4f659ba69b3597728e7e8f1ab -F test/with3.test a261f0ea225c4af0ce6447f1157bb603959b2a665f14a03951c2883d2ef1c0f0 +F test/windowfault.test 21919e601f20b976ea2a73aa401220c89ed0e8d203c4f69476ea55bce3726496 +F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df +F test/with1.test 7bc5abfe4c80c0cef8a90f5a66d60b9982e8ccd7350c8eb70611323a3b8e07ba +F test/with2.test bbf82609bbacc0a7a01d822022aed7b2fa702436dd3d0ecf942023564d2bba13 +F test/with3.test ad32d13ad50661e6fa305f62a0717649c348792e7b658bf2644976227a9e0373 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8 +F test/with6.test 661d7e416bef6c0a2556b2c9f0c8178a5b15932bed65246abed99723a8d4e7c0 F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64 -F test/without_rowid1.test e4034c0849ccc2e8bb749c69f15bd69bb9fcf8fe77e8d17ce02369604242fe83 +F test/without_rowid1.test 6abc5d497f634520944dac0a89a6c240a48d2ee0f8353356a750eb70dc1db41a F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99 F test/without_rowid3.test 39ab0dd773eaa62e59b17093f875327630f54c4145458f6d2b053d68d4b2f67b F test/without_rowid4.test 4e08bcbaee0399f35d58b5581881e7a6243d458a F test/without_rowid5.test 89b1c587bd92a0590e440da33e7666bf4891572a -F test/without_rowid6.test 8463b20098e9f75a501a9f17dfb42fffc79068eac0b2775fe56ef2281d2df45e +F test/without_rowid6.test efbd7add62c59bf5ca97bf8da674e734e6a70ef979234e816166824b4d258f68 F test/without_rowid7.test d7c59a93d726b55812d620f8f284e01904a5b85f9ee9eea8f2f68571a5e8c40e F test/wordcount.c d721a4b6fae93e6e33449700bce1686bc23257c27425bc3ef1599dc912adec66 F test/writecrash.test f1da7f7adfe8d7f09ea79b42e5ca6dcc41102f27f8e334ad71539501ddd910cc F test/zeroblob.test 07a5b11ab591d1f26c626945fb7f228f68b993533b2ada77273edf6ee29db174 F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc -F test/zipfile.test 429cb81c518487fa1b644b6b04b6e9af704a4fa767bd1a110204c5f03b2e8616 +F test/zipfile.test 0d8758d8c0d63f16644f959689f78969d223789d998964276554039f067b4548 F test/zipfile2.test 9903388a602a3834189857a985106ff95c3bba6a3969e0134127df991889db5d F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb44 @@ -1805,22 +1836,23 @@ F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5 F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f -F tool/lemon.c 70eedc31614a58fe31a71025c17ebd1502a6ce9cfef0ed5e33acb0b5b737b291 -F tool/lempar.c 0e1d5eeb9736108d3dba782a9dd56f4e7cb69006b6ee181308b7ebfb15313a12 +F tool/lemon.c a5acddd3eec6a388872aae6efc7563336348a9c45e5563642f77e8e3a50e859d +F tool/lempar.c 1d3d075da18681c67ecc66c1f171e7094e18cd2cfba6a8a1bd4f3f639d6656e1 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 +F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl dd183b73ae1c28249669741c250525f0407e579a70482371668fd5f130d9feb3 -F tool/mkkeywordhash.c 11a3f3af8e787d0c5ca459ed66fe80fd09e661876506e7b978ec08c19477bdc2 +F tool/mkctimec.tcl 06b0d503ee0e6c2d4abe83563b43d4925a12e31ec9fb3249ce39661f53fbd1ce +F tool/mkkeywordhash.c 08b6e4d7a482a7f37a9a0032e7ba968e26624a027b6b2e9ba589be6f5e3d8c2c F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a895ab33 F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c -F tool/mkopcodeh.tcl 352a4319c0ad869eb26442bf7c3b015aa15594c21f1cce5a6420dbe999367c21 +F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl ae5585ae76ca26e4d6ccd5ea9cdebaf5efefb318bf989497a0e846cd711d9ab1 -F tool/mkshellc.tcl 70a9978e363b0f3280ca9ce1c46d72563ff479c1930a12a7375e3881b7325712 +F tool/mkshellc.tcl 5fe7e518112b262e25726f248c0f33dd153192867453984b6af0a76a88e97cb2 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f @@ -1829,14 +1861,14 @@ F tool/mksqlite3h.tcl 1f5e4a1dbbbc43c83cc6e74fe32c6c620502240b66c7c0f33a51378e78 F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845 -F tool/omittest.tcl 6616fbf384f0f630113eab27d41d4530435dd94e2883307759988b45f0604a3b +F tool/omittest.tcl 3d222272b1d840b4e3d67bff0cb743ce3f633faddadb3702b2056b726775db8f F tool/opcodesum.tcl 740ed206ba8c5040018988129abbf3089a0ccf4a F tool/pagesig.c ff0ca355fd3c2398e933da5e22439bbff89b803b F tool/replace.tcl 60f91e8dd06ab81f74d213ecbd9c9945f32ac048 F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076 -F tool/showdb.c 49e810f5c414c792b5bf38cd5557ca9639713ebfef32aaff32faf7cb7ccce513 +F tool/showdb.c 7cc12c6deeddfe40ba5d948b408730696d8365988da05fcb6b6a90ea4965e2b4 F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818 F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564 F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809 @@ -1851,7 +1883,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/split-sqlite3c.tcl 3efcd4240b738f6bb2b5af0aea7e1e0ef9bc1c61654f645076cec883030b710c -F tool/sqldiff.c 4151108387cb56c08906e5ac8d6a353dcfe8fc6014eeded0f0910e2230ea0f5b +F tool/sqldiff.c 21226ef092ec1e543b237c5d3d2d32d344a60f2437eadfea04f65b348fbd00e4 F tool/sqlite3_analyzer.c.in 7eeaae8b0d7577662acaabbb11107af0659d1b41bc1dfdd4d91422de27127968 F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 @@ -1886,11 +1918,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P a1a7a5f238fcfdcd56faba063e381258c9e2944d9b02b31d402a4207a607fc1c -R 58ed0e2a70acf908b3c6a7c54bbafc77 -T +bgcolor * #d0c0ff +P 7bd55eee1ac63cf6d5699ce85bc5a29bf51afdf7a80bce44937fa833947a40f4 +R 6b8a0a7cf847af9edb1fb9387491b821 T +sym-release * -T +sym-version-3.34.1 * +T +sym-version-3.36.0 * U drh -Z 11054c73a336ca8fa32ae8cddcafd46b +Z 05ee2bbc3eef4148ab2800c954a168c4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4bea651fb..ef5b50a93 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -10e20c0b43500cfb9bbc0eaa061c57514f715d87238f4d835880cd846b9ebd1f +5c9a6c06871cb9fe42814af9c039eb6da5427a6ec28f187af7ebfb62eafa66e5 diff --git a/src/alter.c b/src/alter.c index f4098863d..f998b893c 100644 --- a/src/alter.c +++ b/src/alter.c @@ -29,8 +29,9 @@ ** Or, if zName is not a system table, zero is returned. */ static int isAlterableTable(Parse *pParse, Table *pTab){ - if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) + if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) #ifndef SQLITE_OMIT_VIRTUALTABLE + || (pTab->tabFlags & TF_Eponymous)!=0 || ( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(pParse->db) ) @@ -49,15 +50,22 @@ static int isAlterableTable(Parse *pParse, Table *pTab){ ** statement to ensure that the operation has not rendered any schema ** objects unusable. */ -static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){ +static void renameTestSchema( + Parse *pParse, /* Parse context */ + const char *zDb, /* Name of db to verify schema of */ + int bTemp, /* True if this is the temp db */ + const char *zWhen, /* "when" part of error message */ + int bNoDQS /* Do not allow DQS in the schema */ +){ + pParse->colNamesSet = 1; sqlite3NestedParse(pParse, "SELECT 1 " "FROM \"%w\"." DFLT_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" - " AND sqlite_rename_test(%Q, sql, type, name, %d)=NULL ", + " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %d)=NULL ", zDb, - zDb, bTemp + zDb, bTemp, zWhen, bNoDQS ); if( bTemp==0 ){ @@ -66,8 +74,32 @@ static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){ "FROM temp." DFLT_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" - " AND sqlite_rename_test(%Q, sql, type, name, 1)=NULL ", - zDb + " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %d)=NULL ", + zDb, zWhen, bNoDQS + ); + } +} + +/* +** Generate VM code to replace any double-quoted strings (but not double-quoted +** identifiers) within the "sql" column of the sqlite_schema table in +** database zDb with their single-quoted equivalents. If argument bTemp is +** not true, similarly update all SQL statements in the sqlite_schema table +** of the temp db. +*/ +static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." DFLT_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix(%Q, sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb + ); + if( bTemp==0 ){ + sqlite3NestedParse(pParse, + "UPDATE temp." DFLT_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix('temp', sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" ); } } @@ -76,12 +108,12 @@ static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){ ** Generate code to reload the schema for database iDb. And, if iDb!=1, for ** the temp database as well. */ -static void renameReloadSchema(Parse *pParse, int iDb){ +static void renameReloadSchema(Parse *pParse, int iDb, u16 p5){ Vdbe *v = pParse->pVdbe; if( v ){ sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0); - if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0); + sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0, p5); + if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0, p5); } } @@ -230,7 +262,7 @@ void sqlite3AlterRenameTable( "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), " "tbl_name = " "CASE WHEN tbl_name=%Q COLLATE nocase AND " - " sqlite_rename_test(%Q, sql, type, name, 1) " + " sqlite_rename_test(%Q, sql, type, name, 1, 'after rename', 0) " "THEN %Q ELSE tbl_name END " "WHERE type IN ('view', 'trigger')" , zDb, zTabName, zName, zTabName, zDb, zName); @@ -249,8 +281,8 @@ void sqlite3AlterRenameTable( } #endif - renameReloadSchema(pParse, iDb); - renameTestSchema(pParse, zDb, iDb==1); + renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iDb==1, "after rename", 0); exit_rename_table: sqlite3SrcListDelete(db, pSrc); @@ -381,11 +413,14 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ *zEnd-- = '\0'; } db->mDbFlags |= DBFLAG_PreferBuiltin; + /* substr() operations on characters, but addColOffset is in bytes. So we + ** have to use printf() to translate between these units: */ sqlite3NestedParse(pParse, "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " - "sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d) " + "sql = printf('%%.%ds, ',sql) || %Q" + " || substr(sql,1+length(printf('%%.%ds',sql))) " "WHERE type = 'table' AND name = %Q", - zDb, pNew->addColOffset, zCol, pNew->addColOffset+1, + zDb, pNew->addColOffset, zCol, pNew->addColOffset, zTab ); sqlite3DbFree(db, zCol); @@ -409,7 +444,7 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ } /* Reload the table definition */ - renameReloadSchema(pParse, iDb); + renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); } /* @@ -509,7 +544,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ ** Or, if pTab is not a view or virtual table, zero is returned. */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) -static int isRealTable(Parse *pParse, Table *pTab){ +static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ const char *zType = 0; #ifndef SQLITE_OMIT_VIEW if( pTab->pSelect ){ @@ -522,15 +557,16 @@ static int isRealTable(Parse *pParse, Table *pTab){ } #endif if( zType ){ - sqlite3ErrorMsg( - pParse, "cannot rename columns of %s \"%s\"", zType, pTab->zName + sqlite3ErrorMsg(pParse, "cannot %s %s \"%s\"", + (bDrop ? "drop column from" : "rename columns of"), + zType, pTab->zName ); return 1; } return 0; } #else /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */ -# define isRealTable(x,y) (0) +# define isRealTable(x,y,z) (0) #endif /* @@ -559,7 +595,7 @@ void sqlite3AlterRenameColumn( /* Cannot alter a system table */ if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column; - if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column; + if( SQLITE_OK!=isRealTable(pParse, pTab, 0) ) goto exit_rename_column; /* Which schema holds the table to be altered */ iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); @@ -585,6 +621,10 @@ void sqlite3AlterRenameColumn( goto exit_rename_column; } + /* Ensure the schema contains no double-quoted strings */ + renameTestSchema(pParse, zDb, iSchema==1, "", 0); + renameFixQuotes(pParse, zDb, iSchema==1); + /* Do the rename operation using a recursive UPDATE statement that ** uses the sqlite_rename_column() SQL function to compute the new ** CREATE statement text for the sqlite_schema table. @@ -613,8 +653,8 @@ void sqlite3AlterRenameColumn( ); /* Drop and reload the database schema. */ - renameReloadSchema(pParse, iSchema); - renameTestSchema(pParse, zDb, iSchema==1); + renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1); exit_rename_column: sqlite3SrcListDelete(db, pSrc); @@ -760,15 +800,30 @@ static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ static void renameWalkWith(Walker *pWalker, Select *pSelect){ With *pWith = pSelect->pWith; if( pWith ){ + Parse *pParse = pWalker->pParse; int i; + With *pCopy = 0; + assert( pWith->nCte>0 ); + if( (pWith->a[0].pSelect->selFlags & SF_Expanded)==0 ){ + /* Push a copy of the With object onto the with-stack. We use a copy + ** here as the original will be expanded and resolved (flags SF_Expanded + ** and SF_Resolved) below. And the parser code that uses the with-stack + ** fails if the Select objects on it have already been expanded and + ** resolved. */ + pCopy = sqlite3WithDup(pParse->db, pWith); + pCopy = sqlite3WithPush(pParse, pCopy, 1); + } for(i=0; inCte; i++){ Select *p = pWith->a[i].pSelect; NameContext sNC; memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pWalker->pParse; - sqlite3SelectPrep(sNC.pParse, p, &sNC); + sNC.pParse = pParse; + if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC); sqlite3WalkSelect(pWalker, p); - sqlite3RenameExprlistUnmap(pWalker->pParse, pWith->a[i].pCols); + sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols); + } + if( pCopy && pParse->pWith==pCopy ){ + pParse->pWith = pCopy->pOuter; } } } @@ -795,7 +850,11 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; int i; if( pParse->nErr ) return WRC_Abort; - if( NEVER(p->selFlags & SF_View) ) return WRC_Prune; + if( p->selFlags & (SF_View|SF_CopyCte) ){ + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + return WRC_Prune; + } if( ALWAYS(p->pEList) ){ ExprList *pList = p->pEList; for(i=0; inExpr; i++){ @@ -866,23 +925,35 @@ static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ /* ** Search the Parse object passed as the first argument for a RenameToken -** object associated with parse tree element pPtr. If found, remove it -** from the Parse object and add it to the list maintained by the -** RenameCtx object passed as the second argument. +** object associated with parse tree element pPtr. If found, return a pointer +** to it. Otherwise, return NULL. +** +** If the second argument passed to this function is not NULL and a matching +** RenameToken object is found, remove it from the Parse object and add it to +** the list maintained by the RenameCtx object. */ -static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){ +static RenameToken *renameTokenFind( + Parse *pParse, + struct RenameCtx *pCtx, + void *pPtr +){ RenameToken **pp; - assert( pPtr!=0 ); + if( NEVER(pPtr==0) ){ + return 0; + } for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){ if( (*pp)->p==pPtr ){ RenameToken *pToken = *pp; - *pp = pToken->pNext; - pToken->pNext = pCtx->pList; - pCtx->pList = pToken; - pCtx->nList++; - break; + if( pCtx ){ + *pp = pToken->pNext; + pToken->pNext = pCtx->pList; + pCtx->pList = pToken; + pCtx->nList++; + } + return pToken; } } + return 0; } /* @@ -891,7 +962,11 @@ static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){ ** descend into sub-select statements. */ static int renameColumnSelectCb(Walker *pWalker, Select *p){ - if( p->selFlags & SF_View ) return WRC_Prune; + if( p->selFlags & (SF_View|SF_CopyCte) ){ + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + return WRC_Prune; + } renameWalkWith(pWalker, p); return WRC_Continue; } @@ -953,7 +1028,7 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ */ static void renameColumnParseError( sqlite3_context *pCtx, - int bPost, + const char *zWhen, sqlite3_value *pType, sqlite3_value *pObject, Parse *pParse @@ -962,8 +1037,8 @@ static void renameColumnParseError( const char *zN = (const char*)sqlite3_value_text(pObject); char *zErr; - zErr = sqlite3_mprintf("error in %s %s%s: %s", - zT, zN, (bPost ? " after rename" : ""), + zErr = sqlite3_mprintf("error in %s %s%s%s: %s", + zT, zN, (zWhen[0] ? " " : ""), zWhen, pParse->zErrMsg ); sqlite3_result_error(pCtx, zErr, -1); @@ -1042,7 +1117,7 @@ static int renameParseSql( p->eParseMode = PARSE_MODE_RENAME; p->db = db; p->nQueryLoop = 1; - rc = sqlite3RunParser(p, zSql, &zErr); + rc = zSql ? sqlite3RunParser(p, zSql, &zErr) : SQLITE_NOMEM; assert( p->zErrMsg==0 ); assert( rc!=SQLITE_OK || zErr==0 ); p->zErrMsg = zErr; @@ -1085,51 +1160,76 @@ static int renameEditSql( const char *zNew, /* New token text */ int bQuote /* True to always quote token */ ){ - int nNew = sqlite3Strlen30(zNew); - int nSql = sqlite3Strlen30(zSql); + i64 nNew = sqlite3Strlen30(zNew); + i64 nSql = sqlite3Strlen30(zSql); sqlite3 *db = sqlite3_context_db_handle(pCtx); int rc = SQLITE_OK; - char *zQuot; + char *zQuot = 0; char *zOut; - int nQuot; - - /* Set zQuot to point to a buffer containing a quoted copy of the - ** identifier zNew. If the corresponding identifier in the original - ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to - ** point to zQuot so that all substitutions are made using the - ** quoted version of the new column name. */ - zQuot = sqlite3MPrintf(db, "\"%w\"", zNew); - if( zQuot==0 ){ - return SQLITE_NOMEM; + i64 nQuot = 0; + char *zBuf1 = 0; + char *zBuf2 = 0; + + if( zNew ){ + /* Set zQuot to point to a buffer containing a quoted copy of the + ** identifier zNew. If the corresponding identifier in the original + ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to + ** point to zQuot so that all substitutions are made using the + ** quoted version of the new column name. */ + zQuot = sqlite3MPrintf(db, "\"%w\" ", zNew); + if( zQuot==0 ){ + return SQLITE_NOMEM; + }else{ + nQuot = sqlite3Strlen30(zQuot)-1; + } + + assert( nQuot>=nNew ); + zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1); }else{ - nQuot = sqlite3Strlen30(zQuot); - } - if( bQuote ){ - zNew = zQuot; - nNew = nQuot; + zOut = (char*)sqlite3DbMallocZero(db, (nSql*2+1) * 3); + if( zOut ){ + zBuf1 = &zOut[nSql*2+1]; + zBuf2 = &zOut[nSql*4+2]; + } } /* At this point pRename->pList contains a list of RenameToken objects ** corresponding to all tokens in the input SQL that must be replaced - ** with the new column name. All that remains is to construct and - ** return the edited SQL string. */ - assert( nQuot>=nNew ); - zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1); + ** with the new column name, or with single-quoted versions of themselves. + ** All that remains is to construct and return the edited SQL string. */ if( zOut ){ int nOut = nSql; memcpy(zOut, zSql, nSql); while( pRename->pList ){ int iOff; /* Offset of token to replace in zOut */ - RenameToken *pBest = renameColumnTokenNext(pRename); - u32 nReplace; const char *zReplace; - if( sqlite3IsIdChar(*pBest->t.z) ){ - nReplace = nNew; - zReplace = zNew; + RenameToken *pBest = renameColumnTokenNext(pRename); + + if( zNew ){ + if( bQuote==0 && sqlite3IsIdChar(*pBest->t.z) ){ + nReplace = nNew; + zReplace = zNew; + }else{ + nReplace = nQuot; + zReplace = zQuot; + if( pBest->t.z[pBest->t.n]=='"' ) nReplace++; + } }else{ - nReplace = nQuot; - zReplace = zQuot; + /* Dequote the double-quoted token. Then requote it again, this time + ** using single quotes. If the character immediately following the + ** original token within the input SQL was a single quote ('), then + ** add another space after the new, single-quoted version of the + ** token. This is so that (SELECT "string"'alias') maps to + ** (SELECT 'string' 'alias'), and not (SELECT 'string''alias'). */ + memcpy(zBuf1, pBest->t.z, pBest->t.n); + zBuf1[pBest->t.n] = 0; + sqlite3Dequote(zBuf1); + sqlite3_snprintf(nSql*2, zBuf2, "%Q%s", zBuf1, + pBest->t.z[pBest->t.n]=='\'' ? " " : "" + ); + zReplace = zBuf2; + nReplace = sqlite3Strlen30(zReplace); } iOff = pBest->t.z - zSql; @@ -1195,7 +1295,7 @@ static int renameResolveTrigger(Parse *pParse){ if( pSrc ){ int i; for(i=0; inSrc && rc==SQLITE_OK; i++){ - struct SrcList_item *p = &pSrc->a[i]; + SrcItem *p = &pSrc->a[i]; p->iCursor = pParse->nTab++; if( p->pSelect ){ sqlite3SelectPrep(pParse, p->pSelect, 0); @@ -1221,9 +1321,8 @@ static int renameResolveTrigger(Parse *pParse){ rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList); } assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) ); - if( pStep->pUpsert ){ + if( pStep->pUpsert && rc==SQLITE_OK ){ Upsert *pUpsert = pStep->pUpsert; - assert( rc==SQLITE_OK ); pUpsert->pUpsertSrc = pSrc; sNC.uNC.pUpsert = pUpsert; sNC.ncFlags = NC_UUpsert; @@ -1397,9 +1496,11 @@ static void renameColumnFunc( assert( sParse.pNewTable->pSelect==0 ); sCtx.pTab = sParse.pNewTable; if( bFKOnly==0 ){ - renameTokenFind( - &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName - ); + if( iColnCol ){ + renameTokenFind( + &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName + ); + } if( sCtx.iCol<0 ){ renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey); } @@ -1410,12 +1511,12 @@ static void renameColumnFunc( for(pIdx=sParse.pNewIndex; pIdx; pIdx=pIdx->pNext){ sqlite3WalkExprList(&sWalker, pIdx->aColExpr); } - } #ifndef SQLITE_OMIT_GENERATED_COLUMNS - for(i=0; inCol; i++){ - sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt); - } + for(i=0; inCol; i++){ + sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt); + } #endif + } for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){ for(i=0; inCol; i++){ @@ -1469,7 +1570,7 @@ static void renameColumnFunc( renameColumnFunc_done: if( rc!=SQLITE_OK ){ if( sParse.zErrMsg ){ - renameColumnParseError(context, 0, argv[1], argv[2], &sParse); + renameColumnParseError(context, "", argv[1], argv[2], &sParse); }else{ sqlite3_result_error_code(context, rc); } @@ -1501,13 +1602,17 @@ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ int i; RenameCtx *p = pWalker->u.pRename; SrcList *pSrc = pSelect->pSrc; - if( pSelect->selFlags & SF_View ) return WRC_Prune; - if( pSrc==0 ){ + if( pSelect->selFlags & (SF_View|SF_CopyCte) ){ + testcase( pSelect->selFlags & SF_View ); + testcase( pSelect->selFlags & SF_CopyCte ); + return WRC_Prune; + } + if( NEVER(pSrc==0) ){ assert( pWalker->pParse->db->mallocFailed ); return WRC_Abort; } for(i=0; inSrc; i++){ - struct SrcList_item *pItem = &pSrc->a[i]; + SrcItem *pItem = &pSrc->a[i]; if( pItem->pTab==p->pTab ){ renameTokenFind(pWalker->pParse, p, pItem->zName); } @@ -1658,7 +1763,7 @@ static void renameTableFunc( } if( rc!=SQLITE_OK ){ if( sParse.zErrMsg ){ - renameColumnParseError(context, 0, argv[1], argv[2], &sParse); + renameColumnParseError(context, "", argv[1], argv[2], &sParse); }else{ sqlite3_result_error_code(context, rc); } @@ -1675,6 +1780,119 @@ static void renameTableFunc( return; } +static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){ + renameTokenFind(pWalker->pParse, pWalker->u.pRename, (void*)pExpr); + } + return WRC_Continue; +} + +/* +** The implementation of an SQL scalar function that rewrites DDL statements +** so that any string literals that use double-quotes are modified so that +** they use single quotes. +** +** Two arguments must be passed: +** +** 0: Database name ("main", "temp" etc.). +** 1: SQL statement to edit. +** +** The returned value is the modified SQL statement. For example, given +** the database schema: +** +** CREATE TABLE t1(a, b, c); +** +** SELECT sqlite_rename_quotefix('main', +** 'CREATE VIEW v1 AS SELECT "a", "string" FROM t1' +** ); +** +** returns the string: +** +** CREATE VIEW v1 AS SELECT "a", 'string' FROM t1 +*/ +static void renameQuotefixFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + char const *zDb = (const char*)sqlite3_value_text(argv[0]); + char const *zInput = (const char*)sqlite3_value_text(argv[1]); + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + sqlite3BtreeEnterAll(db); + + UNUSED_PARAMETER(NotUsed); + if( zDb && zInput ){ + int rc; + Parse sParse; + rc = renameParseSql(&sParse, zDb, db, zInput, 0); + + if( rc==SQLITE_OK ){ + RenameCtx sCtx; + Walker sWalker; + + /* Walker to find tokens that need to be replaced. */ + memset(&sCtx, 0, sizeof(RenameCtx)); + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = &sParse; + sWalker.xExprCallback = renameQuotefixExprCb; + sWalker.xSelectCallback = renameColumnSelectCb; + sWalker.u.pRename = &sCtx; + + if( sParse.pNewTable ){ + Select *pSelect = sParse.pNewTable->pSelect; + if( pSelect ){ + pSelect->selFlags &= ~SF_View; + sParse.rc = SQLITE_OK; + sqlite3SelectPrep(&sParse, pSelect, 0); + rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); + if( rc==SQLITE_OK ){ + sqlite3WalkSelect(&sWalker, pSelect); + } + }else{ + int i; + sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + for(i=0; inCol; i++){ + sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt); + } +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + } + }else if( sParse.pNewIndex ){ + sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); + sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + }else{ +#ifndef SQLITE_OMIT_TRIGGER + rc = renameResolveTrigger(&sParse); + if( rc==SQLITE_OK ){ + renameWalkTrigger(&sWalker, sParse.pNewTrigger); + } +#endif /* SQLITE_OMIT_TRIGGER */ + } + + if( rc==SQLITE_OK ){ + rc = renameEditSql(context, &sCtx, zInput, 0, 0); + } + renameTokenFree(db, sCtx.pList); + } + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(context, rc); + } + renameParseCleanup(&sParse); + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + + sqlite3BtreeLeaveAll(db); +} + /* ** An SQL user function that checks that there are no parse or symbol ** resolution problems in a CREATE TRIGGER|TABLE|VIEW|INDEX statement. @@ -1687,6 +1905,8 @@ static void renameTableFunc( ** 2: Object type ("view", "table", "trigger" or "index"). ** 3: Object name. ** 4: True if object is from temp schema. +** 5: "when" part of error message. +** 6: True to disable the DQS quirk when parsing SQL. ** ** Unless it finds an error, this function normally returns NULL. However, it ** returns integer value 1 if: @@ -1704,6 +1924,8 @@ static void renameTableTest( char const *zInput = (const char*)sqlite3_value_text(argv[1]); int bTemp = sqlite3_value_int(argv[4]); int isLegacy = (db->flags & SQLITE_LegacyAlter); + char const *zWhen = (const char*)sqlite3_value_text(argv[5]); + int bNoDQS = sqlite3_value_int(argv[6]); #ifndef SQLITE_OMIT_AUTHORIZATION sqlite3_xauth xAuth = db->xAuth; @@ -1711,10 +1933,14 @@ static void renameTableTest( #endif UNUSED_PARAMETER(NotUsed); + if( zDb && zInput ){ int rc; Parse sParse; + int flags = db->flags; + if( bNoDQS ) db->flags &= ~(SQLITE_DqsDML|SQLITE_DqsDDL); rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); + db->flags |= (flags & (SQLITE_DqsDML|SQLITE_DqsDDL)); if( rc==SQLITE_OK ){ if( isLegacy==0 && sParse.pNewTable && sParse.pNewTable->pSelect ){ NameContext sNC; @@ -1736,8 +1962,8 @@ static void renameTableTest( } } - if( rc!=SQLITE_OK ){ - renameColumnParseError(context, 1, argv[2], argv[3], &sParse); + if( rc!=SQLITE_OK && zWhen ){ + renameColumnParseError(context, zWhen, argv[2], argv[3],&sParse); } renameParseCleanup(&sParse); } @@ -1747,14 +1973,219 @@ static void renameTableTest( #endif } +/* +** The implementation of internal UDF sqlite_drop_column(). +** +** Arguments: +** +** argv[0]: An integer - the index of the schema containing the table +** argv[1]: CREATE TABLE statement to modify. +** argv[2]: An integer - the index of the column to remove. +** +** The value returned is a string containing the CREATE TABLE statement +** with column argv[2] removed. +*/ +static void dropColumnFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + int iSchema = sqlite3_value_int(argv[0]); + const char *zSql = (const char*)sqlite3_value_text(argv[1]); + int iCol = sqlite3_value_int(argv[2]); + const char *zDb = db->aDb[iSchema].zDbSName; + int rc; + Parse sParse; + RenameToken *pCol; + Table *pTab; + const char *zEnd; + char *zNew = 0; + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + UNUSED_PARAMETER(NotUsed); + rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1); + if( rc!=SQLITE_OK ) goto drop_column_done; + pTab = sParse.pNewTable; + if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){ + /* This can happen if the sqlite_schema table is corrupt */ + rc = SQLITE_CORRUPT_BKPT; + goto drop_column_done; + } + + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zName); + if( iColnCol-1 ){ + RenameToken *pEnd; + pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zName); + zEnd = (const char*)pEnd->t.z; + }else{ + zEnd = (const char*)&zSql[pTab->addColOffset]; + while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; + } + + zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd); + sqlite3_result_text(context, zNew, -1, SQLITE_TRANSIENT); + sqlite3_free(zNew); + +drop_column_done: + renameParseCleanup(&sParse); +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(context, rc); + } +} + +/* +** This function is called by the parser upon parsing an +** +** ALTER TABLE pSrc DROP COLUMN pName +** +** statement. Argument pSrc contains the possibly qualified name of the +** table being edited, and token pName the name of the column to drop. +*/ +void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){ + sqlite3 *db = pParse->db; /* Database handle */ + Table *pTab; /* Table to modify */ + int iDb; /* Index of db containing pTab in aDb[] */ + const char *zDb; /* Database containing pTab ("main" etc.) */ + char *zCol = 0; /* Name of column to drop */ + int iCol; /* Index of column zCol in pTab->aCol[] */ + + /* Look up the table being altered. */ + assert( pParse->pNewTable==0 ); + assert( sqlite3BtreeHoldsAllMutexes(db) ); + if( NEVER(db->mallocFailed) ) goto exit_drop_column; + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( !pTab ) goto exit_drop_column; + + /* Make sure this is not an attempt to ALTER a view, virtual table or + ** system table. */ + if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_drop_column; + if( SQLITE_OK!=isRealTable(pParse, pTab, 1) ) goto exit_drop_column; + + /* Find the index of the column being dropped. */ + zCol = sqlite3NameFromToken(db, pName); + if( zCol==0 ){ + assert( db->mallocFailed ); + goto exit_drop_column; + } + iCol = sqlite3ColumnIndex(pTab, zCol); + if( iCol<0 ){ + sqlite3ErrorMsg(pParse, "no such column: \"%s\"", zCol); + goto exit_drop_column; + } + + /* Do not allow the user to drop a PRIMARY KEY column or a column + ** constrained by a UNIQUE constraint. */ + if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){ + sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"", + (pTab->aCol[iCol].colFlags&COLFLAG_PRIMKEY) ? "PRIMARY KEY" : "UNIQUE", + zCol + ); + goto exit_drop_column; + } + + /* Do not allow the number of columns to go to zero */ + if( pTab->nCol<=1 ){ + sqlite3ErrorMsg(pParse, "cannot drop column \"%s\": no other columns exist",zCol); + goto exit_drop_column; + } + + /* Edit the sqlite_schema table */ + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=0 ); + zDb = db->aDb[iDb].zDbSName; + renameTestSchema(pParse, zDb, iDb==1, "", 0); + renameFixQuotes(pParse, zDb, iDb==1); + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "sql = sqlite_drop_column(%d, sql, %d) " + "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)" + , zDb, iDb, iCol, pTab->zName + ); + + /* Drop and reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDrop); + renameTestSchema(pParse, zDb, iDb==1, "after drop column", 1); + + /* Edit rows of table on disk */ + if( pParse->nErr==0 && (pTab->aCol[iCol].colFlags & COLFLAG_VIRTUAL)==0 ){ + int i; + int addr; + int reg; + int regRec; + Index *pPk = 0; + int nField = 0; /* Number of non-virtual columns after drop */ + int iCur; + Vdbe *v = sqlite3GetVdbe(pParse); + iCur = pParse->nTab++; + sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); + addr = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + reg = ++pParse->nMem; + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, reg); + pParse->nMem += pTab->nCol; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + pParse->nMem += pPk->nColumn; + for(i=0; inKeyCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, iCur, i, reg+i+1); + } + nField = pPk->nKeyCol; + } + regRec = ++pParse->nMem; + for(i=0; inCol; i++){ + if( i!=iCol && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ + int regOut; + if( pPk ){ + int iPos = sqlite3TableColumnToIndex(pPk, i); + int iColPos = sqlite3TableColumnToIndex(pPk, iCol); + if( iPosnKeyCol ) continue; + regOut = reg+1+iPos-(iPos>iColPos); + }else{ + regOut = reg+1+nField; + } + if( i==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regOut); + }else{ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); + } + nField++; + } + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg+1, nField, regRec); + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iCur, regRec, reg+1, pPk->nKeyCol); + }else{ + sqlite3VdbeAddOp3(v, OP_Insert, iCur, regRec, reg); + } + sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); + + sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr); + } + +exit_drop_column: + sqlite3DbFree(db, zCol); + sqlite3SrcListDelete(db, pSrc); +} + /* ** Register built-in functions used to help implement ALTER TABLE */ void sqlite3AlterFunctions(void){ static FuncDef aAlterTableFuncs[] = { - INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc), - INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc), - INTERNAL_FUNCTION(sqlite_rename_test, 5, renameTableTest), + INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc), + INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc), + INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), + INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), + INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), }; sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); } diff --git a/src/attach.c b/src/attach.c index 3b5c57f0c..2fc84e5b8 100644 --- a/src/attach.c +++ b/src/attach.c @@ -95,7 +95,7 @@ static void attachFunc( if( zFile==0 ) zFile = ""; if( zName==0 ) zName = ""; -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE # define REOPEN_AS_MEMDB(db) (db->init.reopenMemdb) #else # define REOPEN_AS_MEMDB(db) (0) @@ -433,6 +433,65 @@ void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){ } #endif /* SQLITE_OMIT_ATTACH */ +/* +** Expression callback used by sqlite3FixAAAA() routines. +*/ +static int fixExprCb(Walker *p, Expr *pExpr){ + DbFixer *pFix = p->u.pFix; + if( !pFix->bTemp ) ExprSetProperty(pExpr, EP_FromDDL); + if( pExpr->op==TK_VARIABLE ){ + if( pFix->pParse->db->init.busy ){ + pExpr->op = TK_NULL; + }else{ + sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType); + return WRC_Abort; + } + } + return WRC_Continue; +} + +/* +** Select callback used by sqlite3FixAAAA() routines. +*/ +static int fixSelectCb(Walker *p, Select *pSelect){ + DbFixer *pFix = p->u.pFix; + int i; + SrcItem *pItem; + sqlite3 *db = pFix->pParse->db; + int iDb = sqlite3FindDbName(db, pFix->zDb); + SrcList *pList = pSelect->pSrc; + + if( NEVER(pList==0) ) return WRC_Continue; + for(i=0, pItem=pList->a; inSrc; i++, pItem++){ + if( pFix->bTemp==0 ){ + if( pItem->zDatabase ){ + if( iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){ + sqlite3ErrorMsg(pFix->pParse, + "%s %T cannot reference objects in database %s", + pFix->zType, pFix->pName, pItem->zDatabase); + return WRC_Abort; + } + sqlite3DbFree(db, pItem->zDatabase); + pItem->zDatabase = 0; + pItem->fg.notCte = 1; + } + pItem->pSchema = pFix->pSchema; + pItem->fg.fromDDL = 1; + } +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) + if( sqlite3WalkExpr(&pFix->w, pList->a[i].pOn) ) return WRC_Abort; +#endif + } + if( pSelect->pWith ){ + for(i=0; ipWith->nCte; i++){ + if( sqlite3WalkSelect(p, pSelect->pWith->a[i].pSelect) ){ + return WRC_Abort; + } + } + } + return WRC_Continue; +} + /* ** Initialize a DbFixer structure. This routine must be called prior ** to passing the structure to one of the sqliteFixAAAA() routines below. @@ -444,9 +503,7 @@ void sqlite3FixInit( const char *zType, /* "view", "trigger", or "index" */ const Token *pName /* Name of the view, trigger, or index */ ){ - sqlite3 *db; - - db = pParse->db; + sqlite3 *db = pParse->db; assert( db->nDb>iDb ); pFix->pParse = pParse; pFix->zDb = db->aDb[iDb].zDbSName; @@ -454,6 +511,13 @@ void sqlite3FixInit( pFix->zType = zType; pFix->pName = pName; pFix->bTemp = (iDb==1); + pFix->w.pParse = pParse; + pFix->w.xExprCallback = fixExprCb; + pFix->w.xSelectCallback = fixSelectCb; + pFix->w.xSelectCallback2 = sqlite3WalkWinDefnDummyCallback; + pFix->w.walkerDepth = 0; + pFix->w.eCode = 0; + pFix->w.u.pFix = pFix; } /* @@ -474,115 +538,27 @@ int sqlite3FixSrcList( DbFixer *pFix, /* Context of the fixation */ SrcList *pList /* The Source list to check and modify */ ){ - int i; - struct SrcList_item *pItem; - sqlite3 *db = pFix->pParse->db; - int iDb = sqlite3FindDbName(db, pFix->zDb); - - if( NEVER(pList==0) ) return 0; - - for(i=0, pItem=pList->a; inSrc; i++, pItem++){ - if( pFix->bTemp==0 ){ - if( pItem->zDatabase && iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){ - sqlite3ErrorMsg(pFix->pParse, - "%s %T cannot reference objects in database %s", - pFix->zType, pFix->pName, pItem->zDatabase); - return 1; - } - sqlite3DbFree(db, pItem->zDatabase); - pItem->zDatabase = 0; - pItem->pSchema = pFix->pSchema; - pItem->fg.fromDDL = 1; - } -#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) - if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1; - if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1; -#endif - if( pItem->fg.isTabFunc && sqlite3FixExprList(pFix, pItem->u1.pFuncArg) ){ - return 1; - } + int res = 0; + if( pList ){ + Select s; + memset(&s, 0, sizeof(s)); + s.pSrc = pList; + res = sqlite3WalkSelect(&pFix->w, &s); } - return 0; + return res; } #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) int sqlite3FixSelect( DbFixer *pFix, /* Context of the fixation */ Select *pSelect /* The SELECT statement to be fixed to one database */ ){ - while( pSelect ){ - if( sqlite3FixExprList(pFix, pSelect->pEList) ){ - return 1; - } - if( sqlite3FixSrcList(pFix, pSelect->pSrc) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pSelect->pWhere) ){ - return 1; - } - if( sqlite3FixExprList(pFix, pSelect->pGroupBy) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pSelect->pHaving) ){ - return 1; - } - if( sqlite3FixExprList(pFix, pSelect->pOrderBy) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pSelect->pLimit) ){ - return 1; - } - if( pSelect->pWith ){ - int i; - for(i=0; ipWith->nCte; i++){ - if( sqlite3FixSelect(pFix, pSelect->pWith->a[i].pSelect) ){ - return 1; - } - } - } - pSelect = pSelect->pPrior; - } - return 0; + return sqlite3WalkSelect(&pFix->w, pSelect); } int sqlite3FixExpr( DbFixer *pFix, /* Context of the fixation */ Expr *pExpr /* The expression to be fixed to one database */ ){ - while( pExpr ){ - if( !pFix->bTemp ) ExprSetProperty(pExpr, EP_FromDDL); - if( pExpr->op==TK_VARIABLE ){ - if( pFix->pParse->db->init.busy ){ - pExpr->op = TK_NULL; - }else{ - sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType); - return 1; - } - } - if( ExprHasProperty(pExpr, EP_TokenOnly|EP_Leaf) ) break; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - if( sqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1; - }else{ - if( sqlite3FixExprList(pFix, pExpr->x.pList) ) return 1; - } - if( sqlite3FixExpr(pFix, pExpr->pRight) ){ - return 1; - } - pExpr = pExpr->pLeft; - } - return 0; -} -int sqlite3FixExprList( - DbFixer *pFix, /* Context of the fixation */ - ExprList *pList /* The expression to be fixed to one database */ -){ - int i; - struct ExprList_item *pItem; - if( pList==0 ) return 0; - for(i=0, pItem=pList->a; inExpr; i++, pItem++){ - if( sqlite3FixExpr(pFix, pItem->pExpr) ){ - return 1; - } - } - return 0; + return sqlite3WalkExpr(&pFix->w, pExpr); } #endif @@ -592,32 +568,30 @@ int sqlite3FixTriggerStep( TriggerStep *pStep /* The trigger step be fixed to one database */ ){ while( pStep ){ - if( sqlite3FixSelect(pFix, pStep->pSelect) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pStep->pWhere) ){ - return 1; - } - if( sqlite3FixExprList(pFix, pStep->pExprList) ){ - return 1; - } - if( pStep->pFrom && sqlite3FixSrcList(pFix, pStep->pFrom) ){ + if( sqlite3WalkSelect(&pFix->w, pStep->pSelect) + || sqlite3WalkExpr(&pFix->w, pStep->pWhere) + || sqlite3WalkExprList(&pFix->w, pStep->pExprList) + || sqlite3FixSrcList(pFix, pStep->pFrom) + ){ return 1; } #ifndef SQLITE_OMIT_UPSERT - if( pStep->pUpsert ){ - Upsert *pUp = pStep->pUpsert; - if( sqlite3FixExprList(pFix, pUp->pUpsertTarget) - || sqlite3FixExpr(pFix, pUp->pUpsertTargetWhere) - || sqlite3FixExprList(pFix, pUp->pUpsertSet) - || sqlite3FixExpr(pFix, pUp->pUpsertWhere) - ){ - return 1; + { + Upsert *pUp; + for(pUp=pStep->pUpsert; pUp; pUp=pUp->pNextUpsert){ + if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere) + || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere) + ){ + return 1; + } } } #endif pStep = pStep->pNext; } + return 0; } #endif diff --git a/src/auth.c b/src/auth.c index 40673d5ea..33420f583 100644 --- a/src/auth.c +++ b/src/auth.c @@ -143,7 +143,6 @@ void sqlite3AuthRead( Schema *pSchema, /* The schema of the expression */ SrcList *pTabList /* All table that pExpr might refer to */ ){ - sqlite3 *db = pParse->db; Table *pTab = 0; /* The table being read */ const char *zCol; /* Name of the column of the table */ int iSrc; /* Index in pTabList->a[] of table being read */ @@ -151,8 +150,8 @@ void sqlite3AuthRead( int iCol; /* Index of column in table */ assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER ); - assert( !IN_RENAME_OBJECT || db->xAuth==0 ); - if( db->xAuth==0 ) return; + assert( !IN_RENAME_OBJECT ); + assert( pParse->db->xAuth!=0 ); iDb = sqlite3SchemaToIndex(pParse->db, pSchema); if( iDb<0 ){ /* An attempt to read a column out of a subquery or other @@ -164,7 +163,7 @@ void sqlite3AuthRead( pTab = pParse->pTriggerTab; }else{ assert( pTabList ); - for(iSrc=0; ALWAYS(iSrcnSrc); iSrc++){ + for(iSrc=0; iSrcnSrc; iSrc++){ if( pExpr->iTable==pTabList->a[iSrc].iCursor ){ pTab = pTabList->a[iSrc].pTab; break; @@ -172,7 +171,7 @@ void sqlite3AuthRead( } } iCol = pExpr->iColumn; - if( NEVER(pTab==0) ) return; + if( pTab==0 ) return; if( iCol>=0 ){ assert( iColnCol ); @@ -183,7 +182,7 @@ void sqlite3AuthRead( }else{ zCol = "ROWID"; } - assert( iDb>=0 && iDbnDb ); + assert( iDb>=0 && iDbdb->nDb ); if( SQLITE_IGNORE==sqlite3AuthReadCol(pParse, pTab->zName, zCol, iDb) ){ pExpr->op = TK_NULL; } @@ -209,11 +208,7 @@ int sqlite3AuthCheck( ** or if the parser is being invoked from within sqlite3_declare_vtab. */ assert( !IN_RENAME_OBJECT || db->xAuth==0 ); - if( db->init.busy || IN_SPECIAL_PARSE ){ - return SQLITE_OK; - } - - if( db->xAuth==0 ){ + if( db->xAuth==0 || db->init.busy || IN_SPECIAL_PARSE ){ return SQLITE_OK; } diff --git a/src/btree.c b/src/btree.c index 4ac283101..d1e84d0a6 100644 --- a/src/btree.c +++ b/src/btree.c @@ -547,7 +547,7 @@ static void invalidateIncrblobCursors( int isClearTable /* True if all rows are being deleted */ ){ BtCursor *p; - if( pBtree->hasIncrblobCur==0 ) return; + assert( pBtree->hasIncrblobCur ); assert( sqlite3BtreeHoldsMutex(pBtree) ); pBtree->hasIncrblobCur = 0; for(p=pBtree->pBt->pCursor; p; p=p->pNext){ @@ -1143,6 +1143,24 @@ static SQLITE_NOINLINE void btreeParseCellAdjustSizeForOverflow( pInfo->nSize = (u16)(&pInfo->pPayload[pInfo->nLocal] - pCell) + 4; } +/* +** Given a record with nPayload bytes of payload stored within btree +** page pPage, return the number of bytes of payload stored locally. +*/ +static int btreePayloadToLocal(MemPage *pPage, i64 nPayload){ + int maxLocal; /* Maximum amount of payload held locally */ + maxLocal = pPage->maxLocal; + if( nPayload<=maxLocal ){ + return nPayload; + }else{ + int minLocal; /* Minimum amount of payload held locally */ + int surplus; /* Overflow payload available for local storage */ + minLocal = pPage->minLocal; + surplus = minLocal + (nPayload - minLocal)%(pPage->pBt->usableSize-4); + return ( surplus <= maxLocal ) ? surplus : minLocal; + } +} + /* ** The following routines are implementations of the MemPage.xParseCell() ** method. @@ -1430,6 +1448,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ unsigned char *src; /* Source of content */ int iCellFirst; /* First allowable cell index */ int iCellLast; /* Last possible cell index */ + int iCellStart; /* First cell offset in input */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt!=0 ); @@ -1471,7 +1490,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ if( iFree2+sz2 > usableSize ) return SQLITE_CORRUPT_PAGE(pPage); memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz)); sz += sz2; - }else if( NEVER(iFree+sz>usableSize) ){ + }else if( iFree+sz>usableSize ){ return SQLITE_CORRUPT_PAGE(pPage); } @@ -1490,6 +1509,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ cbrk = usableSize; iCellLast = usableSize - 4; + iCellStart = get2byte(&data[hdr+5]); for(i=0; iiCellLast ){ + if( pciCellLast ){ return SQLITE_CORRUPT_PAGE(pPage); } - assert( pc>=iCellFirst && pc<=iCellLast ); + assert( pc>=iCellStart && pc<=iCellLast ); size = pPage->xCellSize(pPage, &src[pc]); cbrk -= size; - if( cbrkusableSize ){ + if( cbrkusableSize ){ return SQLITE_CORRUPT_PAGE(pPage); } - assert( cbrk+size<=usableSize && cbrk>=iCellFirst ); + assert( cbrk+size<=usableSize && cbrk>=iCellStart ); testcase( cbrk+size==usableSize ); testcase( pc+size==usableSize ); put2byte(pAddr, cbrk); if( temp==0 ){ - int x; if( cbrk==pc ) continue; temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - x = get2byte(&data[hdr+5]); - memcpy(&temp[x], &data[x], (cbrk+size) - x); + memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart); src = temp; } memcpy(&data[cbrk], &src[pc], size); @@ -2719,19 +2737,23 @@ static void freeTempSpace(BtShared *pBt){ */ int sqlite3BtreeClose(Btree *p){ BtShared *pBt = p->pBt; - BtCursor *pCur; /* Close all cursors opened via this handle. */ assert( sqlite3_mutex_held(p->db->mutex) ); sqlite3BtreeEnter(p); - pCur = pBt->pCursor; - while( pCur ){ - BtCursor *pTmp = pCur; - pCur = pCur->pNext; - if( pTmp->pBtree==p ){ - sqlite3BtreeCloseCursor(pTmp); + + /* Verify that no other cursors have this Btree open */ +#ifdef SQLITE_DEBUG + { + BtCursor *pCur = pBt->pCursor; + while( pCur ){ + BtCursor *pTmp = pCur; + pCur = pCur->pNext; + assert( pTmp->pBtree!=p ); + } } +#endif /* Rollback any active transaction and free the handle structure. ** The call to sqlite3BtreeRollback() drops any table-locks held by @@ -2883,6 +2905,7 @@ int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve, int iFix){ ((pageSize-1)&pageSize)==0 ){ assert( (pageSize & 7)==0 ); assert( !pBt->pCursor ); + if( nReserve>32 && pageSize==512 ) pageSize = 1024; pBt->pageSize = (u32)pageSize; freeTempSpace(pBt); } @@ -4112,7 +4135,7 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ sqlite3BtreeLeave(p); return rc; } - p->iDataVersion--; /* Compensate for pPager->iDataVersion++; */ + p->iBDataVersion--; /* Compensate for pPager->iDataVersion++; */ pBt->inTransaction = TRANS_READ; btreeClearHasContent(pBt); } @@ -4522,7 +4545,14 @@ int sqlite3BtreeCloseCursor(BtCursor *pCur){ unlockBtreeIfUnused(pBt); sqlite3_free(pCur->aOverflow); sqlite3_free(pCur->pKey); - sqlite3BtreeLeave(pBtree); + if( (pBt->openFlags & BTREE_SINGLE) && pBt->pCursor==0 ){ + /* Since the BtShared is not sharable, there is no need to + ** worry about the missing sqlite3BtreeLeave() call here. */ + assert( pBtree->sharable==0 ); + sqlite3BtreeClose(pBtree); + }else{ + sqlite3BtreeLeave(pBtree); + } pCur->pBtree = 0; } return SQLITE_OK; @@ -5364,7 +5394,9 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ for(ii=0; iiiPage; ii++){ assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); } - assert( pCur->ix==pCur->pPage->nCell-1 ); + assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB ); + testcase( pCur->ix!=pCur->pPage->nCell-1 ); + /* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */ assert( pCur->pPage->leaf ); #endif *pRes = 0; @@ -6131,7 +6163,7 @@ static int allocateBtreePage( iPage = get4byte(&aData[8+closest*4]); testcase( iPage==mxPage ); - if( iPage>mxPage ){ + if( iPage>mxPage || iPage<2 ){ rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; } @@ -6387,10 +6419,9 @@ static void freePage(MemPage *pPage, int *pRC){ } /* -** Free any overflow pages associated with the given Cell. Store -** size information about the cell in pInfo. +** Free the overflow pages associated with the given Cell. */ -static int clearCell( +static SQLITE_NOINLINE int clearCellOverflow( MemPage *pPage, /* The page that contains the Cell */ unsigned char *pCell, /* First byte of the Cell */ CellInfo *pInfo /* Size information about the cell */ @@ -6402,10 +6433,7 @@ static int clearCell( u32 ovflPageSize; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - pPage->xParseCell(pPage, pCell, pInfo); - if( pInfo->nLocal==pInfo->nPayload ){ - return SQLITE_OK; /* No overflow pages. Return without doing anything */ - } + assert( pInfo->nLocal!=pInfo->nPayload ); testcase( pCell + pInfo->nSize == pPage->aDataEnd ); testcase( pCell + (pInfo->nSize-1) == pPage->aDataEnd ); if( pCell + pInfo->nSize > pPage->aDataEnd ){ @@ -6461,6 +6489,21 @@ static int clearCell( return SQLITE_OK; } +/* Call xParseCell to compute the size of a cell. If the cell contains +** overflow, then invoke cellClearOverflow to clear out that overflow. +** STore the result code (SQLITE_OK or some error code) in rc. +** +** Implemented as macro to force inlining for performance. +*/ +#define BTREE_CLEAR_CELL(rc, pPage, pCell, sInfo) \ + pPage->xParseCell(pPage, pCell, &sInfo); \ + if( sInfo.nLocal!=sInfo.nPayload ){ \ + rc = clearCellOverflow(pPage, pCell, &sInfo); \ + }else{ \ + rc = SQLITE_OK; \ + } + + /* ** Create the byte sequence used to represent a cell on page pPage ** and write that byte sequence into pCell[]. Overflow pages are @@ -6983,7 +7026,7 @@ static int rebuildPage( u8 *pCell = pCArray->apCell[i]; u16 sz = pCArray->szCell[i]; assert( sz>0 ); - if( SQLITE_WITHIN(pCell,aData,pEnd) ){ + if( SQLITE_WITHIN(pCell,aData+j,pEnd) ){ if( ((uptr)(pCell+sz))>(uptr)pEnd ) return SQLITE_CORRUPT_BKPT; pCell = &pTmp[pCell - aData]; }else if( (uptr)(pCell+sz)>(uptr)pSrcEnd @@ -6996,9 +7039,8 @@ static int rebuildPage( put2byte(pCellptr, (pData - aData)); pCellptr += 2; if( pData < pCellptr ) return SQLITE_CORRUPT_BKPT; - memcpy(pData, pCell, sz); + memmove(pData, pCell, sz); assert( sz==pPg->xCellSize(pPg, pCell) || CORRUPT_DB ); - testcase( sz!=pPg->xCellSize(pPg,pCell) ) i++; if( i>=iEnd ) break; if( pCArray->ixNx[k]<=i ){ @@ -7137,7 +7179,9 @@ static int pageFreeArray( } pFree = pCell; szFree = sz; - if( pFree+sz>pEnd ) return 0; + if( pFree+sz>pEnd ){ + return 0; + } }else{ pFree = pCell; szFree += sz; @@ -7618,7 +7662,9 @@ static int balance_nonroot( } pgno = get4byte(pRight); while( 1 ){ - rc = getAndInitPage(pBt, pgno, &apOld[i], 0, 0); + if( rc==SQLITE_OK ){ + rc = getAndInitPage(pBt, pgno, &apOld[i], 0, 0); + } if( rc ){ memset(apOld, 0, (i+1)*sizeof(MemPage*)); goto balance_cleanup; @@ -7657,12 +7703,10 @@ static int balance_nonroot( if( pBt->btsFlags & BTS_FAST_SECURE ){ int iOff; + /* If the following if() condition is not true, the db is corrupted. + ** The call to dropCell() below will detect this. */ iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); - if( (iOff+szNew[i])>(int)pBt->usableSize ){ - rc = SQLITE_CORRUPT_BKPT; - memset(apOld, 0, (i+1)*sizeof(MemPage*)); - goto balance_cleanup; - }else{ + if( (iOff+szNew[i])<=(int)pBt->usableSize ){ memcpy(&aOvflSpace[iOff], apDiv[i], szNew[i]); apDiv[i] = &aOvflSpace[apDiv[i]-pParent->aData]; } @@ -7790,7 +7834,7 @@ static int balance_nonroot( b.szCell[b.nCell] = b.szCell[b.nCell] - leafCorrection; if( !pOld->leaf ){ assert( leafCorrection==0 ); - assert( pOld->hdrOffset==0 ); + assert( pOld->hdrOffset==0 || CORRUPT_DB ); /* The right pointer of the child page pOld becomes the left ** pointer of the divider cell */ memcpy(b.apCell[b.nCell], &pOld->aData[8], 4); @@ -7956,6 +8000,9 @@ static int balance_nonroot( apOld[i] = 0; rc = sqlite3PagerWrite(pNew->pDbPage); nNew++; + if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) ){ + rc = SQLITE_CORRUPT_BKPT; + } if( rc ) goto balance_cleanup; }else{ assert( i>0 ); @@ -7992,7 +8039,7 @@ static int balance_nonroot( aPgOrder[i] = aPgno[i] = apNew[i]->pgno; aPgFlags[i] = apNew[i]->pDbPage->flags; for(j=0; jmaxLocal+23 ); assert( iOvflSpace <= (int)pBt->pageSize ); + for(k=0; b.ixNx[k]<=i && ALWAYS(kpgno, &rc); if( rc!=SQLITE_OK ) goto balance_cleanup; assert( sqlite3PagerIswriteable(pParent->pDbPage) ); @@ -8660,7 +8714,8 @@ int sqlite3BtreeInsert( unsigned char *oldCell; unsigned char *newCell = 0; - assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND))==flags ); + assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags ); + assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 ); if( pCur->eState==CURSOR_FAULT ){ assert( pCur->skipNext!=SQLITE_OK ); @@ -8678,7 +8733,7 @@ int sqlite3BtreeInsert( ** keys with no associated data. If the cursor was opened expecting an ** intkey table, the caller should be inserting integer keys with a ** blob of associated data. */ - assert( (pX->pKey==0)==(pCur->pKeyInfo==0) ); + assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) ); /* Save the positions of any other cursors open on this table. ** @@ -8694,13 +8749,23 @@ int sqlite3BtreeInsert( if( pCur->curFlags & BTCF_Multiple ){ rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur); if( rc ) return rc; + if( loc && pCur->iPage<0 ){ + /* This can only happen if the schema is corrupt such that there is more + ** than one table or index with the same root page as used by the cursor. + ** Which can only happen if the SQLITE_NoSchemaError flag was set when + ** the schema was loaded. This cannot be asserted though, as a user might + ** set the flag, load the schema, and then unset the flag. */ + return SQLITE_CORRUPT_BKPT; + } } if( pCur->pKeyInfo==0 ){ assert( pX->pKey==0 ); /* If this is an insert into a table b-tree, invalidate any incrblob ** cursors open on the row being replaced */ - invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); + } /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing ** to a row with the same key as the new entry being inserted. @@ -8781,17 +8846,16 @@ int sqlite3BtreeInsert( return btreeOverwriteCell(pCur, &x2); } } - } assert( pCur->eState==CURSOR_VALID || (pCur->eState==CURSOR_INVALID && loc) || CORRUPT_DB ); pPage = pCur->pPage; - assert( pPage->intKey || pX->nKey>=0 ); + assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) ); assert( pPage->leaf || !pPage->intKey ); if( pPage->nFree<0 ){ - if( pCur->eState>CURSOR_INVALID ){ + if( NEVER(pCur->eState>CURSOR_INVALID) ){ rc = SQLITE_CORRUPT_BKPT; }else{ rc = btreeComputeFreeSpace(pPage); @@ -8805,7 +8869,21 @@ int sqlite3BtreeInsert( assert( pPage->isInit ); newCell = pBt->pTmpSpace; assert( newCell!=0 ); - rc = fillInCell(pPage, newCell, pX, &szNew); + if( flags & BTREE_PREFORMAT ){ + rc = SQLITE_OK; + szNew = pBt->nPreformatSize; + if( szNew<4 ) szNew = 4; + if( ISAUTOVACUUM && szNew>pPage->maxLocal ){ + CellInfo info; + pPage->xParseCell(pPage, newCell, &info); + if( info.nPayload!=info.nLocal ){ + Pgno ovfl = get4byte(&newCell[szNew-4]); + ptrmapPut(pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, &rc); + } + } + }else{ + rc = fillInCell(pPage, newCell, pX, &szNew); + } if( rc ) goto end_insert; assert( szNew==pPage->xCellSize(pPage, newCell) ); assert( szNew <= MX_CELL_SIZE(pBt) ); @@ -8821,7 +8899,7 @@ int sqlite3BtreeInsert( if( !pPage->leaf ){ memcpy(newCell, oldCell, 4); } - rc = clearCell(pPage, oldCell, &info); + BTREE_CLEAR_CELL(rc, pPage, oldCell, info); testcase( pCur->curFlags & BTCF_ValidOvfl ); invalidateOverflowCache(pCur); if( info.nSize==szNew && info.nLocal==info.nPayload @@ -8912,6 +8990,114 @@ int sqlite3BtreeInsert( return rc; } +/* +** This function is used as part of copying the current row from cursor +** pSrc into cursor pDest. If the cursors are open on intkey tables, then +** parameter iKey is used as the rowid value when the record is copied +** into pDest. Otherwise, the record is copied verbatim. +** +** This function does not actually write the new value to cursor pDest. +** Instead, it creates and populates any required overflow pages and +** writes the data for the new cell into the BtShared.pTmpSpace buffer +** for the destination database. The size of the cell, in bytes, is left +** in BtShared.nPreformatSize. The caller completes the insertion by +** calling sqlite3BtreeInsert() with the BTREE_PREFORMAT flag specified. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ + int rc = SQLITE_OK; + BtShared *pBt = pDest->pBt; + u8 *aOut = pBt->pTmpSpace; /* Pointer to next output buffer */ + const u8 *aIn; /* Pointer to next input buffer */ + u32 nIn; /* Size of input buffer aIn[] */ + u32 nRem; /* Bytes of data still to copy */ + + getCellInfo(pSrc); + aOut += putVarint32(aOut, pSrc->info.nPayload); + if( pDest->pKeyInfo==0 ) aOut += putVarint(aOut, iKey); + nIn = pSrc->info.nLocal; + aIn = pSrc->info.pPayload; + if( aIn+nIn>pSrc->pPage->aDataEnd ){ + return SQLITE_CORRUPT_BKPT; + } + nRem = pSrc->info.nPayload; + if( nIn==nRem && nInpPage->maxLocal ){ + memcpy(aOut, aIn, nIn); + pBt->nPreformatSize = nIn + (aOut - pBt->pTmpSpace); + }else{ + Pager *pSrcPager = pSrc->pBt->pPager; + u8 *pPgnoOut = 0; + Pgno ovflIn = 0; + DbPage *pPageIn = 0; + MemPage *pPageOut = 0; + u32 nOut; /* Size of output buffer aOut[] */ + + nOut = btreePayloadToLocal(pDest->pPage, pSrc->info.nPayload); + pBt->nPreformatSize = nOut + (aOut - pBt->pTmpSpace); + if( nOutinfo.nPayload ){ + pPgnoOut = &aOut[nOut]; + pBt->nPreformatSize += 4; + } + + if( nRem>nIn ){ + if( aIn+nIn+4>pSrc->pPage->aDataEnd ){ + return SQLITE_CORRUPT_BKPT; + } + ovflIn = get4byte(&pSrc->info.pPayload[nIn]); + } + + do { + nRem -= nOut; + do{ + assert( nOut>0 ); + if( nIn>0 ){ + int nCopy = MIN(nOut, nIn); + memcpy(aOut, aIn, nCopy); + nOut -= nCopy; + nIn -= nCopy; + aOut += nCopy; + aIn += nCopy; + } + if( nOut>0 ){ + sqlite3PagerUnref(pPageIn); + pPageIn = 0; + rc = sqlite3PagerGet(pSrcPager, ovflIn, &pPageIn, PAGER_GET_READONLY); + if( rc==SQLITE_OK ){ + aIn = (const u8*)sqlite3PagerGetData(pPageIn); + ovflIn = get4byte(aIn); + aIn += 4; + nIn = pSrc->pBt->usableSize - 4; + } + } + }while( rc==SQLITE_OK && nOut>0 ); + + if( rc==SQLITE_OK && nRem>0 ){ + Pgno pgnoNew; + MemPage *pNew = 0; + rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); + put4byte(pPgnoOut, pgnoNew); + if( ISAUTOVACUUM && pPageOut ){ + ptrmapPut(pBt, pgnoNew, PTRMAP_OVERFLOW2, pPageOut->pgno, &rc); + } + releasePage(pPageOut); + pPageOut = pNew; + if( pPageOut ){ + pPgnoOut = pPageOut->aData; + put4byte(pPgnoOut, 0); + aOut = &pPgnoOut[4]; + nOut = MIN(pBt->usableSize - 4, nRem); + } + } + }while( nRem>0 && rc==SQLITE_OK ); + + releasePage(pPageOut); + sqlite3PagerUnref(pPageIn); + } + + return rc; +} + /* ** Delete the entry that the cursor is pointing to. ** @@ -8950,9 +9136,10 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 ); if( pCur->eState==CURSOR_REQUIRESEEK ){ rc = btreeRestoreCursorPosition(pCur); - if( rc ) return rc; + assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID ); + if( rc || pCur->eState!=CURSOR_VALID ) return rc; } - assert( pCur->eState==CURSOR_VALID ); + assert( CORRUPT_DB || pCur->eState==CURSOR_VALID ); iCellDepth = pCur->iPage; iCellIdx = pCur->ix; @@ -9005,7 +9192,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ /* If this is a delete operation to remove a row from a table b-tree, ** invalidate any incrblob cursors open on the row being deleted. */ - if( pCur->pKeyInfo==0 ){ + if( pCur->pKeyInfo==0 && p->hasIncrblobCur ){ invalidateIncrblobCursors(p, pCur->pgnoRoot, pCur->info.nKey, 0); } @@ -9014,7 +9201,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** itself from within the page. */ rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ) return rc; - rc = clearCell(pPage, pCell, &info); + BTREE_CLEAR_CELL(rc, pPage, pCell, info); dropCell(pPage, iCellIdx, info.nSize, &rc); if( rc ) return rc; @@ -9301,14 +9488,14 @@ static int clearDatabasePage( rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange); if( rc ) goto cleardatabasepage_out; } - rc = clearCell(pPage, pCell, &info); + BTREE_CLEAR_CELL(rc, pPage, pCell, info); if( rc ) goto cleardatabasepage_out; } if( !pPage->leaf ){ rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange); if( rc ) goto cleardatabasepage_out; - }else if( pnChange ){ - assert( pPage->intKey || CORRUPT_DB ); + } + if( pnChange ){ testcase( !pPage->intKey ); *pnChange += pPage->nCell; } @@ -9333,9 +9520,8 @@ static int clearDatabasePage( ** read cursors on the table. Open write cursors are moved to the ** root of the table. ** -** If pnChange is not NULL, then table iTable must be an intkey table. The -** integer value pointed to by pnChange is incremented by the number of -** entries in the table. +** If pnChange is not NULL, then the integer value pointed to by pnChange +** is incremented by the number of entries in the table. */ int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ int rc; @@ -9349,7 +9535,9 @@ int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ /* Invalidate all incrblob cursors open on table iTable (assuming iTable ** is the root of a table b-tree - if it is not, the following call is ** a no-op). */ - invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); + } rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange); } sqlite3BtreeLeave(p); @@ -9509,7 +9697,7 @@ void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){ assert( idx>=0 && idx<=15 ); if( idx==BTREE_DATA_VERSION ){ - *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion; + *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iBDataVersion; }else{ *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]); } diff --git a/src/btree.h b/src/btree.h index 7a9ed2e3c..b7afecc42 100644 --- a/src/btree.h +++ b/src/btree.h @@ -262,6 +262,7 @@ int sqlite3BtreeDelete(BtCursor*, u8 flags); #define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */ #define BTREE_AUXDELETE 0x04 /* not the primary delete operation */ #define BTREE_APPEND 0x08 /* Insert is likely an append */ +#define BTREE_PREFORMAT 0x80 /* Inserted data is a preformated cell */ /* An instance of the BtreePayload object describes the content of a single ** entry in either an index or table btree. @@ -361,6 +362,8 @@ void sqlite3BtreeCursorList(Btree*); int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); #endif +int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64); + /* ** If we are not using shared cache, then there is no need to ** use mutexes to access the BtShared structures. So make the diff --git a/src/btreeInt.h b/src/btreeInt.h index c09699fbb..37c07fe93 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -350,7 +350,7 @@ struct Btree { u8 hasIncrblobCur; /* True if there are one or more Incrblob cursors */ int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */ int nBackup; /* Number of backup operations reading this btree */ - u32 iDataVersion; /* Combines with pBt->pPager->iDataVersion */ + u32 iBDataVersion; /* Combines with pBt->pPager->iDataVersion */ Btree *pNext; /* List of other sharable Btrees from the same db */ Btree *pPrev; /* Back pointer of the same list */ #ifdef SQLITE_DEBUG @@ -455,6 +455,7 @@ struct BtShared { Btree *pWriter; /* Btree with currently open write transaction */ #endif u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */ + int nPreformatSize; /* Size of last cell written by TransferRow() */ }; /* diff --git a/src/build.c b/src/build.c index 9779e9373..5bf5680d4 100644 --- a/src/build.c +++ b/src/build.c @@ -46,7 +46,7 @@ struct TableLock { ** code to make the lock occur is generated by a later call to ** codeTableLocks() which occurs during sqlite3FinishCoding(). */ -void sqlite3TableLock( +static SQLITE_NOINLINE void lockTable( Parse *pParse, /* Parsing context */ int iDb, /* Index of the database containing the table to lock */ Pgno iTab, /* Root page number of the table to be locked */ @@ -59,8 +59,6 @@ void sqlite3TableLock( TableLock *p; assert( iDb>=0 ); - if( iDb==1 ) return; - if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; pToplevel = sqlite3ParseToplevel(pParse); for(i=0; inTableLock; i++){ p = &pToplevel->aTableLock[i]; @@ -84,6 +82,17 @@ void sqlite3TableLock( sqlite3OomFault(pToplevel->db); } } +void sqlite3TableLock( + Parse *pParse, /* Parsing context */ + int iDb, /* Index of the database containing the table to lock */ + Pgno iTab, /* Root page number of the table to be locked */ + u8 isWriteLock, /* True for a write lock */ + const char *zName /* Name of the table to be locked */ +){ + if( iDb==1 ) return; + if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; + lockTable(pParse, iDb, iTab, isWriteLock, zName); +} /* ** Code an OP_TableLock instruction for each table locked by the @@ -143,10 +152,36 @@ void sqlite3FinishCoding(Parse *pParse){ /* Begin by generating some termination code at the end of the ** vdbe program */ - v = sqlite3GetVdbe(pParse); + v = pParse->pVdbe; + if( v==0 ){ + if( db->init.busy ){ + pParse->rc = SQLITE_DONE; + return; + } + v = sqlite3GetVdbe(pParse); + if( v==0 ) pParse->rc = SQLITE_ERROR; + } assert( !pParse->isMultiWrite || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort)); if( v ){ + if( pParse->bReturning ){ + Returning *pReturning = pParse->u1.pReturning; + int addrRewind; + int i; + int reg; + + addrRewind = + sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); + VdbeCoverage(v); + reg = pReturning->iRetReg; + for(i=0; inRetCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i); + sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrRewind); + } sqlite3VdbeAddOp0(v, OP_Halt); #if SQLITE_USER_AUTHENTICATION @@ -224,12 +259,16 @@ void sqlite3FinishCoding(Parse *pParse){ } } + if( pParse->bReturning ){ + Returning *pRet = pParse->u1.pReturning; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + } + /* Finally, jump back to the beginning of the executable code. */ sqlite3VdbeGoto(v, 1); } } - /* Get the VDBE program ready for execution */ if( v && pParse->nErr==0 && !db->mallocFailed ){ @@ -408,7 +447,7 @@ Table *sqlite3LocateTable( /* If zName is the not the name of a table in the schema created using ** CREATE, then check to see if it is the name of an virtual table that ** can be an eponymous virtual table. */ - if( pParse->disableVtab==0 ){ + if( pParse->disableVtab==0 && db->init.busy==0 ){ Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ pMod = sqlite3PragmaVtabRegister(db, zName); @@ -431,6 +470,8 @@ Table *sqlite3LocateTable( }else{ sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName); } + }else{ + assert( HasRowid(p) || p->iPKey<0 ); } return p; @@ -448,7 +489,7 @@ Table *sqlite3LocateTable( Table *sqlite3LocateTableItem( Parse *pParse, u32 flags, - struct SrcList_item *p + SrcItem *p ){ const char *zDb; assert( p->pSchema==0 || p->zDatabase==0 ); @@ -847,7 +888,7 @@ int sqlite3TwoPartName( return -1; } }else{ - assert( db->init.iDb==0 || db->init.busy || IN_RENAME_OBJECT + assert( db->init.iDb==0 || db->init.busy || IN_SPECIAL_PARSE || (db->mDbFlags & DBFLAG_Vacuum)!=0); iDb = db->init.iDb; *pUnqual = pName1; @@ -1016,6 +1057,23 @@ i16 sqlite3TableColumnToStorage(Table *pTab, i16 iCol){ } #endif +/* +** Insert a single OP_JournalMode query opcode in order to force the +** prepared statement to return false for sqlite3_stmt_readonly(). This +** is used by CREATE TABLE IF NOT EXISTS and similar if the table already +** exists, so that the prepared statement for CREATE TABLE IF NOT EXISTS +** will return false for sqlite3_stmt_readonly() even if that statement +** is a read-only no-op. +*/ +static void sqlite3ForceNotReadOnly(Parse *pParse){ + int iReg = ++pParse->nMem; + Vdbe *v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp3(v, OP_JournalMode, 0, iReg, PAGER_JOURNALMODE_QUERY); + sqlite3VdbeUsesBtree(v, 0); + } +} + /* ** Begin constructing a new table representation in memory. This is ** the first of several action routines that get called in response @@ -1115,6 +1173,7 @@ void sqlite3StartTable( }else{ assert( !db->init.busy || CORRUPT_DB ); sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); } goto begin_table_error; } @@ -1143,17 +1202,6 @@ void sqlite3StartTable( assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; - /* If this is the magic sqlite_sequence table used by autoincrement, - ** then record a pointer to this table in the main database structure - ** so that INSERT can find the table easily. - */ -#ifndef SQLITE_OMIT_AUTOINCREMENT - if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){ - assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - pTable->pSchema->pSeqTab = pTable; - } -#endif - /* Begin generating the code that will insert the table record into ** the schema table. Note in particular that we must go ahead ** and allocate the record number for the table entry now. Before any @@ -1206,7 +1254,8 @@ void sqlite3StartTable( }else #endif { - pParse->addrCrTab = + assert( !pParse->bReturning ); + pParse->u1.addrCrTab = sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, reg2, BTREE_INTKEY); } sqlite3OpenSchemaTable(pParse, iDb); @@ -1233,12 +1282,86 @@ void sqlite3StartTable( void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ if( sqlite3_strnicmp(pCol->zName, "__hidden__", 10)==0 ){ pCol->colFlags |= COLFLAG_HIDDEN; + if( pTab ) pTab->tabFlags |= TF_HasHidden; }else if( pTab && pCol!=pTab->aCol && (pCol[-1].colFlags & COLFLAG_HIDDEN) ){ pTab->tabFlags |= TF_OOOHidden; } } #endif +/* +** Name of the special TEMP trigger used to implement RETURNING. The +** name begins with "sqlite_" so that it is guaranteed not to collide +** with any application-generated triggers. +*/ +#define RETURNING_TRIGGER_NAME "sqlite_returning" + +/* +** Clean up the data structures associated with the RETURNING clause. +*/ +static void sqlite3DeleteReturning(sqlite3 *db, Returning *pRet){ + Hash *pHash; + pHash = &(db->aDb[1].pSchema->trigHash); + sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, 0); + sqlite3ExprListDelete(db, pRet->pReturnEL); + sqlite3DbFree(db, pRet); +} + +/* +** Add the RETURNING clause to the parse currently underway. +** +** This routine creates a special TEMP trigger that will fire for each row +** of the DML statement. That TEMP trigger contains a single SELECT +** statement with a result set that is the argument of the RETURNING clause. +** The trigger has the Trigger.bReturning flag and an opcode of +** TK_RETURNING instead of TK_SELECT, so that the trigger code generator +** knows to handle it specially. The TEMP trigger is automatically +** removed at the end of the parse. +** +** When this routine is called, we do not yet know if the RETURNING clause +** is attached to a DELETE, INSERT, or UPDATE, so construct it as a +** RETURNING trigger instead. It will then be converted into the appropriate +** type on the first call to sqlite3TriggersExist(). +*/ +void sqlite3AddReturning(Parse *pParse, ExprList *pList){ + Returning *pRet; + Hash *pHash; + sqlite3 *db = pParse->db; + if( pParse->pNewTrigger ){ + sqlite3ErrorMsg(pParse, "cannot use RETURNING in a trigger"); + }else{ + assert( pParse->bReturning==0 ); + } + pParse->bReturning = 1; + pRet = sqlite3DbMallocZero(db, sizeof(*pRet)); + if( pRet==0 ){ + sqlite3ExprListDelete(db, pList); + return; + } + pParse->u1.pReturning = pRet; + pRet->pParse = pParse; + pRet->pReturnEL = pList; + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3DeleteReturning, pRet); + testcase( pParse->earlyCleanup ); + if( db->mallocFailed ) return; + pRet->retTrig.zName = RETURNING_TRIGGER_NAME; + pRet->retTrig.op = TK_RETURNING; + pRet->retTrig.tr_tm = TRIGGER_AFTER; + pRet->retTrig.bReturning = 1; + pRet->retTrig.pSchema = db->aDb[1].pSchema; + pRet->retTrig.pTabSchema = db->aDb[1].pSchema; + pRet->retTrig.step_list = &pRet->retTStep; + pRet->retTStep.op = TK_RETURNING; + pRet->retTStep.pTrig = &pRet->retTrig; + pRet->retTStep.pExprList = pList; + pHash = &(db->aDb[1].pSchema->trigHash); + assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 || pParse->nErr ); + if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig) + ==&pRet->retTrig ){ + sqlite3OomFault(db); + } +} /* ** Add a new column to the table currently being constructed. @@ -1255,6 +1378,8 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ char *zType; Column *pCol; sqlite3 *db = pParse->db; + u8 hName; + if( (p = pParse->pNewTable)==0 ) return; if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName); @@ -1266,8 +1391,9 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ memcpy(z, pName->z, pName->n); z[pName->n] = 0; sqlite3Dequote(z); + hName = sqlite3StrIHash(z); for(i=0; inCol; i++){ - if( sqlite3_stricmp(z, p->aCol[i].zName)==0 ){ + if( p->aCol[i].hName==hName && sqlite3StrICmp(z, p->aCol[i].zName)==0 ){ sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); sqlite3DbFree(db, z); return; @@ -1285,7 +1411,7 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ pCol = &p->aCol[p->nCol]; memset(pCol, 0, sizeof(p->aCol[0])); pCol->zName = z; - pCol->hName = sqlite3StrIHash(z); + pCol->hName = hName; sqlite3ColumnPropertiesFromName(p, pCol); if( pType->n==0 ){ @@ -2068,9 +2194,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ /* Convert the P3 operand of the OP_CreateBtree opcode from BTREE_INTKEY ** into BTREE_BLOBKEY. */ - if( pParse->addrCrTab ){ + assert( !pParse->bReturning ); + if( pParse->u1.addrCrTab ){ assert( v ); - sqlite3VdbeChangeP3(v, pParse->addrCrTab, BTREE_BLOBKEY); + sqlite3VdbeChangeP3(v, pParse->u1.addrCrTab, BTREE_BLOBKEY); } /* Locate the PRIMARY KEY index. Or, if this table was originally @@ -2082,7 +2209,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0)); - if( pList==0 ) return; + if( pList==0 ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey); } @@ -2091,7 +2221,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ pTab->iPKey = -1; sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0, SQLITE_IDXTYPE_PRIMARYKEY); - if( db->mallocFailed || pParse->nErr ) return; + if( db->mallocFailed || pParse->nErr ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } pPk = sqlite3PrimaryKeyIndex(pTab); assert( pPk->nKeyCol==1 ); }else{ @@ -2295,7 +2428,6 @@ void sqlite3EndTable( if( pEnd==0 && pSelect==0 ){ return; } - assert( !db->mallocFailed ); p = pParse->pNewTable; if( p==0 ) return; @@ -2520,7 +2652,7 @@ void sqlite3EndTable( /* Check to see if we need to create an sqlite_sequence table for ** keeping track of autoincrement keys. */ - if( (p->tabFlags & TF_Autoincrement)!=0 ){ + if( (p->tabFlags & TF_Autoincrement)!=0 && !IN_SPECIAL_PARSE ){ Db *pDb = &db->aDb[iDb]; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( pDb->pSchema->pSeqTab==0 ){ @@ -2534,7 +2666,7 @@ void sqlite3EndTable( /* Reparse everything to update our internal data structures */ sqlite3VdbeAddParseSchemaOp(v, iDb, - sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName)); + sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName),0); } /* Add the table to the in-memory representation of the database. @@ -2543,6 +2675,7 @@ void sqlite3EndTable( Table *pOld; Schema *pSchema = p->pSchema; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( HasRowid(p) || p->iPKey<0 ); pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p); if( pOld ){ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ @@ -2552,19 +2685,27 @@ void sqlite3EndTable( pParse->pNewTable = 0; db->mDbFlags |= DBFLAG_SchemaChange; -#ifndef SQLITE_OMIT_ALTERTABLE - if( !p->pSelect ){ - const char *zName = (const char *)pParse->sNameToken.z; - int nName; - assert( !pSelect && pCons && pEnd ); - if( pCons->z==0 ){ - pCons = pEnd; - } - nName = (int)((const char *)pCons->z - zName); - p->addColOffset = 13 + sqlite3Utf8CharLen(zName, nName); + /* If this is the magic sqlite_sequence table used by autoincrement, + ** then record a pointer to this table in the main database structure + ** so that INSERT can find the table easily. */ + assert( !pParse->nested ); +#ifndef SQLITE_OMIT_AUTOINCREMENT + if( strcmp(p->zName, "sqlite_sequence")==0 ){ + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + p->pSchema->pSeqTab = p; } #endif } + +#ifndef SQLITE_OMIT_ALTERTABLE + if( !pSelect && !p->pSelect ){ + assert( pCons && pEnd ); + if( pCons->z==0 ){ + pCons = pEnd; + } + p->addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z); + } +#endif } #ifndef SQLITE_OMIT_VIEW @@ -2597,6 +2738,16 @@ void sqlite3CreateView( sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr); p = pParse->pNewTable; if( p==0 || pParse->nErr ) goto create_view_fail; + + /* Legacy versions of SQLite allowed the use of the magic "rowid" column + ** on a view, even though views do not have rowids. The following flag + ** setting fixes this problem. But the fix can be disabled by compiling + ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that + ** depend upon the old buggy behavior. */ +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW + p->tabFlags |= TF_NoVisibleRowid; +#endif + sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); @@ -2755,6 +2906,7 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ assert( pTable->aCol==0 ); pTable->nCol = pSelTab->nCol; pTable->aCol = pSelTab->aCol; + pTable->tabFlags |= (pSelTab->tabFlags & COLFLAG_NOINSERT); pSelTab->nCol = 0; pSelTab->aCol = 0; assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) ); @@ -3072,7 +3224,10 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){ if( noErr ) db->suppressErr--; if( pTab==0 ){ - if( noErr ) sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + if( noErr ){ + sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + sqlite3ForceNotReadOnly(pParse); + } goto exit_drop_table; } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); @@ -3642,6 +3797,7 @@ void sqlite3CreateIndex( }else{ assert( !db->init.busy ); sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); } goto exit_create_index; } @@ -4022,7 +4178,7 @@ void sqlite3CreateIndex( sqlite3RefillIndex(pParse, pIndex, iMem); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddParseSchemaOp(v, iDb, - sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName)); + sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName), 0); sqlite3VdbeAddOp2(v, OP_Expire, 0, 1); } @@ -4043,7 +4199,11 @@ void sqlite3CreateIndex( /* Clean up before exiting */ exit_create_index: if( pIndex ) sqlite3FreeIndex(db, pIndex); - if( pTab ){ /* Ensure all REPLACE indexes are at the end of the list */ + if( pTab ){ + /* Ensure all REPLACE indexes on pTab are at the end of the pIndex list. + ** The list was already ordered when this routine was entered, so at this + ** point at most a single index (the newly added index) will be out of + ** order. So we have to reorder at most one index. */ Index **ppFrom = &pTab->pIndex; Index *pThis; for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){ @@ -4057,6 +4217,16 @@ void sqlite3CreateIndex( } break; } +#ifdef SQLITE_DEBUG + /* Verify that all REPLACE indexes really are now at the end + ** of the index list. In other words, no other index type ever + ** comes after a REPLACE index on the list. */ + for(pThis = pTab->pIndex; pThis; pThis=pThis->pNext){ + assert( pThis->onError!=OE_Replace + || pThis->pNext==0 + || pThis->pNext->onError==OE_Replace ); + } +#endif } sqlite3ExprDelete(db, pPIWhere); sqlite3ExprListDelete(db, pList); @@ -4108,7 +4278,7 @@ void sqlite3DefaultRowEst(Index *pIdx){ if( x<99 ){ pIdx->pTable->nRowLogEst = x = 99; } - if( pIdx->pPartIdxWhere!=0 ) x -= 10; assert( 10==sqlite3LogEst(2) ); + if( pIdx->pPartIdxWhere!=0 ){ x -= 10; assert( 10==sqlite3LogEst(2) ); } a[0] = x; /* Estimate that a[1] is 10, a[2] is 9, a[3] is 8, a[4] is 7, a[5] is @@ -4143,9 +4313,10 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase); if( pIndex==0 ){ if( !ifExists ){ - sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0); + sqlite3ErrorMsg(pParse, "no such index: %S", pName->a); }else{ sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + sqlite3ForceNotReadOnly(pParse); } pParse->checkSchema = 1; goto exit_drop_index; @@ -4165,7 +4336,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ goto exit_drop_index; } - if( !OMIT_TEMPDB && iDb ) code = SQLITE_DROP_TEMP_INDEX; + if( !OMIT_TEMPDB && iDb==1 ) code = SQLITE_DROP_TEMP_INDEX; if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){ goto exit_drop_index; } @@ -4415,7 +4586,7 @@ SrcList *sqlite3SrcListAppend( Token *pTable, /* Table to append */ Token *pDatabase /* Database of the table */ ){ - struct SrcList_item *pItem; + SrcItem *pItem; sqlite3 *db; assert( pDatabase==0 || pTable!=0 ); /* Cannot have C without B */ assert( pParse!=0 ); @@ -4456,9 +4627,9 @@ SrcList *sqlite3SrcListAppend( */ void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){ int i; - struct SrcList_item *pItem; - assert(pList || pParse->db->mallocFailed ); - if( pList ){ + SrcItem *pItem; + assert( pList || pParse->db->mallocFailed ); + if( ALWAYS(pList) ){ for(i=0, pItem=pList->a; inSrc; i++, pItem++){ if( pItem->iCursor>=0 ) continue; pItem->iCursor = pParse->nTab++; @@ -4474,7 +4645,7 @@ void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){ */ void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ int i; - struct SrcList_item *pItem; + SrcItem *pItem; if( pList==0 ) return; for(pItem=pList->a, i=0; inSrc; i++, pItem++){ if( pItem->zDatabase ) sqlite3DbFreeNN(db, pItem->zDatabase); @@ -4516,7 +4687,7 @@ SrcList *sqlite3SrcListAppendFromTerm( Expr *pOn, /* The ON clause of a join */ IdList *pUsing /* The USING clause of a join */ ){ - struct SrcList_item *pItem; + SrcItem *pItem; sqlite3 *db = pParse->db; if( !p && (pOn || pUsing) ){ sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", @@ -4560,7 +4731,7 @@ SrcList *sqlite3SrcListAppendFromTerm( void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ assert( pIndexedBy!=0 ); if( p && pIndexedBy->n>0 ){ - struct SrcList_item *pItem; + SrcItem *pItem; assert( p->nSrc>0 ); pItem = &p->a[p->nSrc-1]; assert( pItem->fg.notIndexed==0 ); @@ -4590,7 +4761,7 @@ SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ sqlite3SrcListDelete(pParse->db, p2); }else{ p1 = pNew; - memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(struct SrcList_item)); + memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem)); sqlite3DbFree(pParse->db, p2); } } @@ -4603,7 +4774,7 @@ SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ */ void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){ if( p ){ - struct SrcList_item *pItem = &p->a[p->nSrc-1]; + SrcItem *pItem = &p->a[p->nSrc-1]; assert( pItem->fg.notIndexed==0 ); assert( pItem->fg.isIndexedBy==0 ); assert( pItem->fg.isTabFunc==0 ); @@ -4758,7 +4929,7 @@ int sqlite3OpenTempDatabase(Parse *pParse){ static void sqlite3CodeVerifySchemaAtToplevel(Parse *pToplevel, int iDb){ assert( iDb>=0 && iDbdb->nDb ); assert( pToplevel->db->aDb[iDb].pBt!=0 || iDb==1 ); - assert( iDbdb, iDb, 0) ); if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){ DbMaskSet(pToplevel->cookieMask, iDb); @@ -5100,24 +5271,76 @@ KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ } #ifndef SQLITE_OMIT_CTE +/* +** Create a new CTE object +*/ +Cte *sqlite3CteNew( + Parse *pParse, /* Parsing context */ + Token *pName, /* Name of the common-table */ + ExprList *pArglist, /* Optional column name list for the table */ + Select *pQuery, /* Query used to initialize the table */ + u8 eM10d /* The MATERIALIZED flag */ +){ + Cte *pNew; + sqlite3 *db = pParse->db; + + pNew = sqlite3DbMallocZero(db, sizeof(*pNew)); + assert( pNew!=0 || db->mallocFailed ); + + if( db->mallocFailed ){ + sqlite3ExprListDelete(db, pArglist); + sqlite3SelectDelete(db, pQuery); + }else{ + pNew->pSelect = pQuery; + pNew->pCols = pArglist; + pNew->zName = sqlite3NameFromToken(pParse->db, pName); + pNew->eM10d = eM10d; + } + return pNew; +} + +/* +** Clear information from a Cte object, but do not deallocate storage +** for the object itself. +*/ +static void cteClear(sqlite3 *db, Cte *pCte){ + assert( pCte!=0 ); + sqlite3ExprListDelete(db, pCte->pCols); + sqlite3SelectDelete(db, pCte->pSelect); + sqlite3DbFree(db, pCte->zName); +} + +/* +** Free the contents of the CTE object passed as the second argument. +*/ +void sqlite3CteDelete(sqlite3 *db, Cte *pCte){ + assert( pCte!=0 ); + cteClear(db, pCte); + sqlite3DbFree(db, pCte); +} + /* ** This routine is invoked once per CTE by the parser while parsing a -** WITH clause. +** WITH clause. The CTE described by teh third argument is added to +** the WITH clause of the second argument. If the second argument is +** NULL, then a new WITH argument is created. */ With *sqlite3WithAdd( Parse *pParse, /* Parsing context */ With *pWith, /* Existing WITH clause, or NULL */ - Token *pName, /* Name of the common-table */ - ExprList *pArglist, /* Optional column name list for the table */ - Select *pQuery /* Query used to initialize the table */ + Cte *pCte /* CTE to add to the WITH clause */ ){ sqlite3 *db = pParse->db; With *pNew; char *zName; + if( pCte==0 ){ + return pWith; + } + /* Check that the CTE name is unique within this WITH clause. If ** not, store an error in the Parse structure. */ - zName = sqlite3NameFromToken(pParse->db, pName); + zName = pCte->zName; if( zName && pWith ){ int i; for(i=0; inCte; i++){ @@ -5136,16 +5359,11 @@ With *sqlite3WithAdd( assert( (pNew!=0 && zName!=0) || db->mallocFailed ); if( db->mallocFailed ){ - sqlite3ExprListDelete(db, pArglist); - sqlite3SelectDelete(db, pQuery); - sqlite3DbFree(db, zName); + sqlite3CteDelete(db, pCte); pNew = pWith; }else{ - pNew->a[pNew->nCte].pSelect = pQuery; - pNew->a[pNew->nCte].pCols = pArglist; - pNew->a[pNew->nCte].zName = zName; - pNew->a[pNew->nCte].zCteErr = 0; - pNew->nCte++; + pNew->a[pNew->nCte++] = *pCte; + sqlite3DbFree(db, pCte); } return pNew; @@ -5158,10 +5376,7 @@ void sqlite3WithDelete(sqlite3 *db, With *pWith){ if( pWith ){ int i; for(i=0; inCte; i++){ - struct Cte *pCte = &pWith->a[i]; - sqlite3ExprListDelete(db, pCte->pCols); - sqlite3SelectDelete(db, pCte->pSelect); - sqlite3DbFree(db, pCte->zName); + cteClear(db, &pWith->a[i]); } sqlite3DbFree(db, pWith); } diff --git a/src/ctime.c b/src/ctime.c index e3e1e79a9..fa69216c6 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -58,8 +58,10 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_64BIT_STATS "64BIT_STATS", #endif -#if SQLITE_ALLOW_COVERING_INDEX_SCAN - "ALLOW_COVERING_INDEX_SCAN", +#ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN +# if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 + "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), +# endif #endif #if SQLITE_ALLOW_URI_AUTHORITY "ALLOW_URI_AUTHORITY", @@ -121,8 +123,10 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_DEFAULT_LOOKASIDE "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE), #endif -#if SQLITE_DEFAULT_MEMSTATUS - "DEFAULT_MEMSTATUS", +#ifdef SQLITE_DEFAULT_MEMSTATUS +# if SQLITE_DEFAULT_MEMSTATUS != 1 + "DEFAULT_MEMSTATUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_MEMSTATUS), +# endif #endif #ifdef SQLITE_DEFAULT_MMAP_SIZE "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), @@ -196,7 +200,7 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_ENABLE_BYTECODE_VTAB "ENABLE_BYTECODE_VTAB", #endif -#if SQLITE_ENABLE_CEROD +#ifdef SQLITE_ENABLE_CEROD "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), #endif #if SQLITE_ENABLE_COLUMN_METADATA @@ -211,17 +215,17 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_ENABLE_CURSOR_HINTS "ENABLE_CURSOR_HINTS", #endif +#if SQLITE_ENABLE_DBPAGE_VTAB + "ENABLE_DBPAGE_VTAB", +#endif #if SQLITE_ENABLE_DBSTAT_VTAB "ENABLE_DBSTAT_VTAB", #endif #if SQLITE_ENABLE_EXPENSIVE_ASSERT "ENABLE_EXPENSIVE_ASSERT", #endif -#if SQLITE_ENABLE_FTS1 - "ENABLE_FTS1", -#endif -#if SQLITE_ENABLE_FTS2 - "ENABLE_FTS2", +#if SQLITE_ENABLE_EXPLAIN_COMMENTS + "ENABLE_EXPLAIN_COMMENTS", #endif #if SQLITE_ENABLE_FTS3 "ENABLE_FTS3", @@ -259,6 +263,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_ENABLE_LOCKING_STYLE "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), #endif +#if SQLITE_ENABLE_MATH_FUNCTIONS + "ENABLE_MATH_FUNCTIONS", +#endif #if SQLITE_ENABLE_MEMORY_MANAGEMENT "ENABLE_MEMORY_MANAGEMENT", #endif @@ -277,6 +284,9 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_ENABLE_NULL_TRIM "ENABLE_NULL_TRIM", #endif +#if SQLITE_ENABLE_OFFSET_SQL_FUNC + "ENABLE_OFFSET_SQL_FUNC", +#endif #if SQLITE_ENABLE_OVERSIZE_CELL_CHECK "ENABLE_OVERSIZE_CELL_CHECK", #endif @@ -307,7 +317,7 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_ENABLE_SQLLOG "ENABLE_SQLLOG", #endif -#if defined(SQLITE_ENABLE_STAT4) +#if SQLITE_ENABLE_STAT4 "ENABLE_STAT4", #endif #if SQLITE_ENABLE_STMTVTAB @@ -361,8 +371,10 @@ static const char * const sqlite3azCompileOpt[] = { #if HAVE_ISNAN || SQLITE_HAVE_ISNAN "HAVE_ISNAN", #endif -#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX - "HOMEGROWN_RECURSIVE_MUTEX", +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX +# if SQLITE_HOMEGROWN_RECURSIVE_MUTEX != 1 + "HOMEGROWN_RECURSIVE_MUTEX=" CTIMEOPT_VAL(SQLITE_HOMEGROWN_RECURSIVE_MUTEX), +# endif #endif #if SQLITE_IGNORE_AFP_LOCK_ERRORS "IGNORE_AFP_LOCK_ERRORS", @@ -460,9 +472,6 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_MUTEX_NOOP "MUTEX_NOOP", #endif -#if SQLITE_MUTEX_NREF - "MUTEX_NREF", -#endif #if SQLITE_MUTEX_OMIT "MUTEX_OMIT", #endif @@ -532,7 +541,7 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_OMIT_CTE "OMIT_CTE", #endif -#if SQLITE_OMIT_DATETIME_FUNCS +#if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT) "OMIT_DATETIME_FUNCS", #endif #if SQLITE_OMIT_DECLTYPE @@ -541,6 +550,9 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_OMIT_DEPRECATED "OMIT_DEPRECATED", #endif +#if SQLITE_OMIT_DESERIALIZE + "OMIT_DESERIALIZE", +#endif #if SQLITE_OMIT_DISKIO "OMIT_DISKIO", #endif @@ -568,6 +580,9 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_OMIT_INTEGRITY_CHECK "OMIT_INTEGRITY_CHECK", #endif +#if SQLITE_OMIT_INTROSPECTION_PRAGMAS + "OMIT_INTROSPECTION_PRAGMAS", +#endif #if SQLITE_OMIT_LIKE_OPTIMIZATION "OMIT_LIKE_OPTIMIZATION", #endif @@ -631,8 +646,10 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_OMIT_TEST_CONTROL "OMIT_TEST_CONTROL", #endif -#if SQLITE_OMIT_TRACE - "OMIT_TRACE", +#ifdef SQLITE_OMIT_TRACE +# if SQLITE_OMIT_TRACE != 1 + "OMIT_TRACE=" CTIMEOPT_VAL(SQLITE_OMIT_TRACE), +# endif #endif #if SQLITE_OMIT_TRIGGER "OMIT_TRIGGER", @@ -667,8 +684,10 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_PERFORMANCE_TRACE "PERFORMANCE_TRACE", #endif -#if SQLITE_POWERSAFE_OVERWRITE - "POWERSAFE_OVERWRITE", +#ifdef SQLITE_POWERSAFE_OVERWRITE +# if SQLITE_POWERSAFE_OVERWRITE != 1 + "POWERSAFE_OVERWRITE=" CTIMEOPT_VAL(SQLITE_POWERSAFE_OVERWRITE), +# endif #endif #if SQLITE_PREFER_PROXY_LOCKING "PREFER_PROXY_LOCKING", @@ -703,7 +722,10 @@ static const char * const sqlite3azCompileOpt[] = { #if SQLITE_SUBSTR_COMPATIBILITY "SUBSTR_COMPATIBILITY", #endif -#if SQLITE_SYSTEM_MALLOC +#if (!defined(SQLITE_WIN32_MALLOC) \ + && !defined(SQLITE_ZERO_MALLOC) \ + && !defined(SQLITE_MEMDEBUG) \ + ) || defined(SQLITE_SYSTEM_MALLOC) "SYSTEM_MALLOC", #endif #if SQLITE_TCL diff --git a/src/date.c b/src/date.c index 1db26b1c6..f88f544e3 100644 --- a/src/date.c +++ b/src/date.c @@ -881,6 +881,7 @@ static int isDate( int eType; memset(p, 0, sizeof(*p)); if( argc==0 ){ + if( !sqlite3NotPureFunc(context) ) return 1; return setDateTimeToCurrent(context, p); } if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT diff --git a/src/delete.c b/src/delete.c index 064ae7325..dd074bb35 100644 --- a/src/delete.c +++ b/src/delete.c @@ -29,7 +29,7 @@ ** */ Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ - struct SrcList_item *pItem = pSrc->a; + SrcItem *pItem = pSrc->a; Table *pTab; assert( pItem && pSrc->nSrc>=1 ); pTab = sqlite3LocateTableItem(pParse, 0, pItem); @@ -37,9 +37,9 @@ Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ pItem->pTab = pTab; if( pTab ){ pTab->nTabRef++; - } - if( sqlite3IndexedByLookup(pParse, pItem) ){ - pTab = 0; + if( pItem->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pItem) ){ + pTab = 0; + } } return pTab; } @@ -207,9 +207,15 @@ Expr *sqlite3LimitWhere( /* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree ** and the SELECT subtree. */ pSrc->a[0].pTab = 0; - pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0); + pSelectSrc = sqlite3SrcListDup(db, pSrc, 0); pSrc->a[0].pTab = pTab; - pSrc->a[0].pIBIndex = 0; + if( pSrc->a[0].fg.isIndexedBy ){ + pSrc->a[0].u2.pIBIndex = 0; + pSrc->a[0].fg.isIndexedBy = 0; + sqlite3DbFree(db, pSrc->a[0].u1.zIndexedBy); + }else if( pSrc->a[0].fg.isCte ){ + pSrc->a[0].u2.pCteUse->nUse++; + } /* generate the SELECT expression tree. */ pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, @@ -387,6 +393,7 @@ void sqlite3DeleteFrom( if( (db->flags & SQLITE_CountRows)!=0 && !pParse->nested && !pParse->pTriggerTab + && !pParse->bReturning ){ memCnt = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt); @@ -421,6 +428,9 @@ void sqlite3DeleteFrom( for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->pSchema==pTab->pSchema ); sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + sqlite3VdbeChangeP3(v, -1, memCnt ? memCnt : -1); + } } }else #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ @@ -608,7 +618,7 @@ void sqlite3DeleteFrom( ** invoke the callback function. */ if( memCnt ){ - sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1); + sqlite3VdbeAddOp2(v, OP_ChngCntRow, memCnt, 1); sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC); } @@ -932,13 +942,15 @@ int sqlite3GenerateIndexKey( continue; } sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j); - /* If the column affinity is REAL but the number is an integer, then it - ** might be stored in the table as an integer (using a compact - ** representation) then converted to REAL by an OP_RealAffinity opcode. - ** But we are getting ready to store this value back into an index, where - ** it should be converted by to INTEGER again. So omit the OP_RealAffinity - ** opcode if it is present */ - sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + if( pIdx->aiColumn[j]>=0 ){ + /* If the column affinity is REAL but the number is an integer, then it + ** might be stored in the table as an integer (using a compact + ** representation) then converted to REAL by an OP_RealAffinity opcode. + ** But we are getting ready to store this value back into an index, where + ** it should be converted by to INTEGER again. So omit the + ** OP_RealAffinity opcode if it is present */ + sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + } } if( regOut ){ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); diff --git a/src/expr.c b/src/expr.c index 685f04175..b751f51a6 100644 --- a/src/expr.c +++ b/src/expr.c @@ -52,6 +52,10 @@ char sqlite3ExprAffinity(const Expr *pExpr){ assert( pExpr!=0 ); } op = pExpr->op; + if( op==TK_REGISTER ) op = pExpr->op2; + if( (op==TK_COLUMN || op==TK_AGG_COLUMN) && pExpr->y.pTab ){ + return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + } if( op==TK_SELECT ){ assert( pExpr->flags&EP_xIsSelect ); assert( pExpr->x.pSelect!=0 ); @@ -59,16 +63,12 @@ char sqlite3ExprAffinity(const Expr *pExpr){ assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); } - if( op==TK_REGISTER ) op = pExpr->op2; #ifndef SQLITE_OMIT_CAST if( op==TK_CAST ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); return sqlite3AffinityType(pExpr->u.zToken, 0); } #endif - if( (op==TK_AGG_COLUMN || op==TK_COLUMN) && pExpr->y.pTab ){ - return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); - } if( op==TK_SELECT_COLUMN ){ assert( pExpr->pLeft->flags&EP_xIsSelect ); return sqlite3ExprAffinity( @@ -445,7 +445,7 @@ int sqlite3ExprVectorSize(Expr *pExpr){ ** been positioned. */ Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ - assert( iop==TK_ERROR ); if( sqlite3ExprIsVector(pVector) ){ assert( pVector->op2==0 || pVector->op==TK_REGISTER ); if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){ @@ -561,7 +561,7 @@ static int exprVectorRegister( int *pRegFree /* OUT: Temp register to free */ ){ u8 op = pVector->op; - assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT ); + assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT || op==TK_ERROR ); if( op==TK_REGISTER ){ *ppExpr = sqlite3VectorFieldSubexpr(pVector, iField); return pVector->iTable+iField; @@ -570,8 +570,11 @@ static int exprVectorRegister( *ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr; return regSelect+iField; } - *ppExpr = pVector->x.pList->a[iField].pExpr; - return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); + if( op==TK_VECTOR ){ + *ppExpr = pVector->x.pList->a[iField].pExpr; + return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); + } + return 0; } /* @@ -600,6 +603,7 @@ static void codeVectorCompare( int regLeft = 0; int regRight = 0; u8 opx = op; + int addrCmp = 0; int addrDone = sqlite3VdbeMakeLabel(pParse); int isCommuted = ExprHasProperty(pExpr,EP_Commuted); @@ -619,21 +623,24 @@ static void codeVectorCompare( assert( p5==0 || pExpr->op!=op ); assert( p5==SQLITE_NULLEQ || pExpr->op==op ); - p5 |= SQLITE_STOREP2; - if( opx==TK_LE ) opx = TK_LT; - if( opx==TK_GE ) opx = TK_GT; + if( op==TK_LE ) opx = TK_LT; + if( op==TK_GE ) opx = TK_GT; + if( op==TK_NE ) opx = TK_EQ; regLeft = exprCodeSubselect(pParse, pLeft); regRight = exprCodeSubselect(pParse, pRight); + sqlite3VdbeAddOp2(v, OP_Integer, 1, dest); for(i=0; 1 /*Loop exits by "break"*/; i++){ int regFree1 = 0, regFree2 = 0; - Expr *pL, *pR; + Expr *pL = 0, *pR = 0; int r1, r2; assert( i>=0 && i0 @@ -947,8 +960,8 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ }else if( (ExprAlwaysFalse(pLeft) || ExprAlwaysFalse(pRight)) && !IN_RENAME_OBJECT ){ - sqlite3ExprDelete(db, pLeft); - sqlite3ExprDelete(db, pRight); + sqlite3ExprDeferredDelete(pParse, pLeft); + sqlite3ExprDeferredDelete(pParse, pRight); return sqlite3Expr(db, TK_INTEGER, "0"); }else{ return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); @@ -1145,6 +1158,22 @@ void sqlite3ExprDelete(sqlite3 *db, Expr *p){ if( p ) sqlite3ExprDeleteNN(db, p); } + +/* +** Arrange to cause pExpr to be deleted when the pParse is deleted. +** This is similar to sqlite3ExprDelete() except that the delete is +** deferred untilthe pParse is deleted. +** +** The pExpr might be deleted immediately on an OOM error. +** +** The deferred delete is (currently) implemented by adding the +** pExpr to the pParse->pConstExpr list with a register number of 0. +*/ +void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ + pParse->pConstExpr = + sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr); +} + /* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the ** expression. */ @@ -1287,6 +1316,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ if( pzBuffer ){ zAlloc = *pzBuffer; staticFlag = EP_Static; + assert( zAlloc!=0 ); }else{ zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags)); staticFlag = 0; @@ -1365,7 +1395,8 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ if( pNew->op==TK_SELECT_COLUMN ){ pNew->pLeft = p->pLeft; assert( p->iColumn==0 || p->pRight==0 ); - assert( p->pRight==0 || p->pRight==p->pLeft ); + assert( p->pRight==0 || p->pRight==p->pLeft + || ExprHasProperty(p->pLeft, EP_Subquery) ); }else{ pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); } @@ -1382,7 +1413,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ ** and the db->mallocFailed flag set. */ #ifndef SQLITE_OMIT_CTE -static With *withDup(sqlite3 *db, With *p){ +With *sqlite3WithDup(sqlite3 *db, With *p){ With *pRet = 0; if( p ){ sqlite3_int64 nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1); @@ -1400,7 +1431,7 @@ static With *withDup(sqlite3 *db, With *p){ return pRet; } #else -# define withDup(x,y) 0 +# define sqlite3WithDup(x,y) 0 #endif #ifndef SQLITE_OMIT_WINDOWFUNC @@ -1467,6 +1498,7 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ pNew = sqlite3DbMallocRawNN(db, sqlite3DbMallocSize(db, p)); if( pNew==0 ) return 0; pNew->nExpr = p->nExpr; + pNew->nAlloc = p->nAlloc; pItem = pNew->a; pOldItem = p->a; for(i=0; inExpr; i++, pItem++, pOldItem++){ @@ -1479,7 +1511,8 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ ){ assert( pNewExpr->iColumn==0 || i>0 ); if( pNewExpr->iColumn==0 ){ - assert( pOldExpr->pLeft==pOldExpr->pRight ); + assert( pOldExpr->pLeft==pOldExpr->pRight + || ExprHasProperty(pOldExpr->pLeft, EP_Subquery) ); pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight; }else{ assert( i>0 ); @@ -1519,8 +1552,8 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ if( pNew==0 ) return 0; pNew->nSrc = pNew->nAlloc = p->nSrc; for(i=0; inSrc; i++){ - struct SrcList_item *pNewItem = &pNew->a[i]; - struct SrcList_item *pOldItem = &p->a[i]; + SrcItem *pNewItem = &pNew->a[i]; + SrcItem *pOldItem = &p->a[i]; Table *pTab; pNewItem->pSchema = pOldItem->pSchema; pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase); @@ -1533,7 +1566,10 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ if( pNewItem->fg.isIndexedBy ){ pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy); } - pNewItem->pIBIndex = pOldItem->pIBIndex; + pNewItem->u2 = pOldItem->u2; + if( pNewItem->fg.isCte ){ + pNewItem->u2.pCteUse->nUse++; + } if( pNewItem->fg.isTabFunc ){ pNewItem->u1.pFuncArg = sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); @@ -1599,13 +1635,21 @@ Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ pNew->addrOpenEphm[0] = -1; pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = p->nSelectRow; - pNew->pWith = withDup(db, p->pWith); + pNew->pWith = sqlite3WithDup(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC pNew->pWin = 0; pNew->pWinDefn = sqlite3WindowListDup(db, p->pWinDefn); if( p->pWin && db->mallocFailed==0 ) gatherSelectWindows(pNew); #endif pNew->selId = p->selId; + if( db->mallocFailed ){ + /* Any prior OOM might have left the Select object incomplete. + ** Delete the whole thing rather than allow an incomplete Select + ** to be used by the code generator. */ + pNew->pNext = 0; + sqlite3SelectDelete(db, pNew); + break; + } *pp = pNew; pp = &pNew->pPrior; pNext = pNew; @@ -1636,41 +1680,64 @@ Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ ** NULL is returned. If non-NULL is returned, then it is guaranteed ** that the new entry was successfully appended. */ +static const struct ExprList_item zeroItem = {0}; +SQLITE_NOINLINE ExprList *sqlite3ExprListAppendNew( + sqlite3 *db, /* Database handle. Used for memory allocation */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pList; + + pList = sqlite3DbMallocRawNN(db, sizeof(ExprList)+sizeof(pList->a[0])*4 ); + if( pList==0 ){ + sqlite3ExprDelete(db, pExpr); + return 0; + } + pList->nAlloc = 4; + pList->nExpr = 1; + pItem = &pList->a[0]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} +SQLITE_NOINLINE ExprList *sqlite3ExprListAppendGrow( + sqlite3 *db, /* Database handle. Used for memory allocation */ + ExprList *pList, /* List to which to append. Might be NULL */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pNew; + pList->nAlloc *= 2; + pNew = sqlite3DbRealloc(db, pList, + sizeof(*pList)+(pList->nAlloc-1)*sizeof(pList->a[0])); + if( pNew==0 ){ + sqlite3ExprListDelete(db, pList); + sqlite3ExprDelete(db, pExpr); + return 0; + }else{ + pList = pNew; + } + pItem = &pList->a[pList->nExpr++]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} ExprList *sqlite3ExprListAppend( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to append. Might be NULL */ Expr *pExpr /* Expression to be appended. Might be NULL */ ){ struct ExprList_item *pItem; - sqlite3 *db = pParse->db; - assert( db!=0 ); if( pList==0 ){ - pList = sqlite3DbMallocRawNN(db, sizeof(ExprList) ); - if( pList==0 ){ - goto no_mem; - } - pList->nExpr = 0; - }else if( (pList->nExpr & (pList->nExpr-1))==0 ){ - ExprList *pNew; - pNew = sqlite3DbRealloc(db, pList, - sizeof(*pList)+(2*(sqlite3_int64)pList->nExpr-1)*sizeof(pList->a[0])); - if( pNew==0 ){ - goto no_mem; - } - pList = pNew; + return sqlite3ExprListAppendNew(pParse->db,pExpr); + } + if( pList->nAllocnExpr+1 ){ + return sqlite3ExprListAppendGrow(pParse->db,pList,pExpr); } pItem = &pList->a[pList->nExpr++]; - assert( offsetof(struct ExprList_item,zEName)==sizeof(pItem->pExpr) ); - assert( offsetof(struct ExprList_item,pExpr)==0 ); - memset(&pItem->zEName,0,sizeof(*pItem)-offsetof(struct ExprList_item,zEName)); + *pItem = zeroItem; pItem->pExpr = pExpr; return pList; - -no_mem: - /* Avoid leaking memory if malloc has failed. */ - sqlite3ExprDelete(db, pExpr); - sqlite3ExprListDelete(db, pList); - return 0; } /* @@ -2283,8 +2350,10 @@ int sqlite3ExprIsInteger(Expr *p, int *pValue){ */ int sqlite3ExprCanBeNull(const Expr *p){ u8 op; + assert( p!=0 ); while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ p = p->pLeft; + assert( p!=0 ); } op = p->op; if( op==TK_REGISTER ) op = p->op2; @@ -2571,7 +2640,7 @@ int sqlite3FindInIndex( /* Code an OP_Transaction and OP_TableLock for . */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - assert( iDb>=0 && iDb=0 && iDbtnum, 0, pTab->zName); @@ -2916,19 +2985,23 @@ void sqlite3CodeRhsOfIN( /* If the LHS and RHS of the IN operator do not match, that ** error will have been caught long before we reach this point. */ if( ALWAYS(pEList->nExpr==nVal) ){ + Select *pCopy; SelectDest dest; int i; + int rc; sqlite3SelectDestInit(&dest, SRT_Set, iTab); dest.zAffSdst = exprINAffinity(pParse, pExpr); pSelect->iLimit = 0; testcase( pSelect->selFlags & SF_Distinct ); testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ - if( sqlite3Select(pParse, pSelect, &dest) ){ - sqlite3DbFree(pParse->db, dest.zAffSdst); + pCopy = sqlite3SelectDup(pParse->db, pSelect, 0); + rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest); + sqlite3SelectDelete(pParse->db, pCopy); + sqlite3DbFree(pParse->db, dest.zAffSdst); + if( rc ){ sqlite3KeyInfoUnref(pKeyInfo); return; - } - sqlite3DbFree(pParse->db, dest.zAffSdst); + } assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); @@ -3027,12 +3100,30 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ Vdbe *v = pParse->pVdbe; assert( v!=0 ); + if( pParse->nErr ) return 0; testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); assert( ExprHasProperty(pExpr, EP_xIsSelect) ); pSel = pExpr->x.pSelect; + /* If this routine has already been coded, then invoke it as a + ** subroutine. */ + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); + sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr); + return pExpr->iTable; + } + + /* Begin coding the subroutine */ + ExprSetProperty(pExpr, EP_Subrtn); + pExpr->y.sub.regReturn = ++pParse->nMem; + pExpr->y.sub.iAddr = + sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; + VdbeComment((v, "return address")); + + /* The evaluation of the EXISTS/SELECT must be repeated every time it ** is encountered if any of the following is true: ** @@ -3044,22 +3135,6 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ ** save the results, and reuse the same result on subsequent invocations. */ if( !ExprHasProperty(pExpr, EP_VarSelect) ){ - /* If this routine has already been coded, then invoke it as a - ** subroutine. */ - if( ExprHasProperty(pExpr, EP_Subrtn) ){ - ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); - sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, - pExpr->y.sub.iAddr); - return pExpr->iTable; - } - - /* Begin coding the subroutine */ - ExprSetProperty(pExpr, EP_Subrtn); - pExpr->y.sub.regReturn = ++pParse->nMem; - pExpr->y.sub.iAddr = - sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; - VdbeComment((v, "return address")); - addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } @@ -3108,19 +3183,22 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ } pSel->iLimit = 0; if( sqlite3Select(pParse, pSel, &dest) ){ + if( pParse->nErr ){ + pExpr->op2 = pExpr->op; + pExpr->op = TK_ERROR; + } return 0; } pExpr->iTable = rReg = dest.iSDParm; ExprSetVVAProperty(pExpr, EP_NoReduce); if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); - - /* Subroutine return */ - sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); - sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); - sqlite3ClearTempRegCache(pParse); } + /* Subroutine return */ + sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); + sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); + sqlite3ClearTempRegCache(pParse); return rReg; } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -3134,7 +3212,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ */ int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ int nVector = sqlite3ExprVectorSize(pIn->pLeft); - if( (pIn->flags & EP_xIsSelect) ){ + if( (pIn->flags & EP_xIsSelect)!=0 && !pParse->db->mallocFailed ){ if( nVector!=pIn->x.pSelect->pEList->nExpr ){ sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); return 1; @@ -3325,6 +3403,7 @@ static void sqlite3ExprCodeIN( if( pParse->nErr ) goto sqlite3ExprCodeIN_finished; for(i=0; ipLeft, i); + if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error; if( sqlite3ExprCanBeNull(p) ){ sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); VdbeCoverage(v); @@ -3950,7 +4029,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** Expr node to be passed into this function, it will be handled ** sanely and not crash. But keep the assert() to bring the problem ** to the attention of the developers. */ - assert( op==TK_NULL ); + assert( op==TK_NULL || op==TK_ERROR || pParse->db->mallocFailed ); sqlite3VdbeAddOp2(v, OP_Null, 0, target); return target; } @@ -4016,8 +4095,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ }else{ r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - codeCompare(pParse, pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2 | p5, + sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg); + codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2, + sqlite3VdbeCurrentAddr(v)+2, p5, ExprHasProperty(pExpr,EP_Commuted)); assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); @@ -4025,6 +4105,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + if( p5==SQLITE_NULLEQ ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg); + }else{ + sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2); + } testcase( regFree1==0 ); testcase( regFree2==0 ); } @@ -4287,7 +4372,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( pExpr->pLeft->iTable==0 ){ pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft); } - assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT ); + assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT + || pExpr->pLeft->op==TK_ERROR ); if( pExpr->iTable!=0 && pExpr->iTable!=(n = sqlite3ExprVectorSize(pExpr->pLeft)) ){ @@ -5767,8 +5853,7 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ pExpr = sqlite3ExprDup(db, pExpr, 0); if( pExpr ){ pAggInfo->aCol[iAgg].pCExpr = pExpr; - pParse->pConstExpr = - sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr); + sqlite3ExprDeferredDelete(pParse, pExpr); } } }else{ @@ -5777,8 +5862,7 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ pExpr = sqlite3ExprDup(db, pExpr, 0); if( pExpr ){ pAggInfo->aFunc[iAgg].pFExpr = pExpr; - pParse->pConstExpr = - sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr); + sqlite3ExprDeferredDelete(pParse, pExpr); } } } @@ -5850,7 +5934,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ /* Check to see if the column is in one of the tables in the FROM ** clause of the aggregate query */ if( ALWAYS(pSrcList!=0) ){ - struct SrcList_item *pItem = pSrcList->a; + SrcItem *pItem = pSrcList->a; for(i=0; inSrc; i++, pItem++){ struct AggInfo_col *pCol; assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); @@ -5921,6 +6005,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ */ struct AggInfo_func *pItem = pAggInfo->aFunc; for(i=0; inFunc; i++, pItem++){ + if( pItem->pFExpr==pExpr ) break; if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){ break; } diff --git a/src/fkey.c b/src/fkey.c index 959e994d1..9f622f40c 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -1024,7 +1024,7 @@ void sqlite3FkCheck( ** child table as a SrcList for sqlite3WhereBegin() */ pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); if( pSrc ){ - struct SrcList_item *pItem = pSrc->a; + SrcItem *pItem = pSrc->a; pItem->pTab = pFKey->pFrom; pItem->zName = pFKey->pFrom->zName; pItem->pTab->nTabRef++; @@ -1112,7 +1112,9 @@ u32 sqlite3FkOldmask( ** ** For an UPDATE, this function returns 2 if: ** -** * There are any FKs for which pTab is the child and the parent table, or +** * There are any FKs for which pTab is the child and the parent table +** and any FK processing at all is required (even of a different FK), or +** ** * the UPDATE modifies one or more parent keys for which the action is ** not "NO ACTION" (i.e. is CASCADE, SET DEFAULT or SET NULL). ** @@ -1124,13 +1126,14 @@ int sqlite3FkRequired( int *aChange, /* Non-NULL for UPDATE operations */ int chngRowid /* True for UPDATE that affects rowid */ ){ - int eRet = 0; + int eRet = 1; /* Value to return if bHaveFK is true */ + int bHaveFK = 0; /* If FK processing is required */ if( pParse->db->flags&SQLITE_ForeignKeys ){ if( !aChange ){ /* A DELETE operation. Foreign key processing is required if the ** table in question is either the child or parent table for any ** foreign key constraint. */ - eRet = (sqlite3FkReferences(pTab) || pTab->pFKey); + bHaveFK = (sqlite3FkReferences(pTab) || pTab->pFKey); }else{ /* This is an UPDATE. Foreign key processing is only required if the ** operation modifies one or more child or parent key columns. */ @@ -1138,9 +1141,9 @@ int sqlite3FkRequired( /* Check if any child key columns are being modified. */ for(p=pTab->pFKey; p; p=p->pNextFrom){ - if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) return 2; if( fkChildIsModified(pTab, p, aChange, chngRowid) ){ - eRet = 1; + if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) eRet = 2; + bHaveFK = 1; } } @@ -1148,12 +1151,12 @@ int sqlite3FkRequired( for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ if( fkParentIsModified(pTab, p, aChange, chngRowid) ){ if( p->aAction[1]!=OE_None ) return 2; - eRet = 1; + bHaveFK = 1; } } } } - return eRet; + return bHaveFK ? eRet : 0; } /* @@ -1352,7 +1355,7 @@ static Trigger *fkActionTrigger( switch( action ){ case OE_Restrict: - pStep->op = TK_SELECT; + pStep->op = TK_SELECT; break; case OE_Cascade: if( !pChanges ){ diff --git a/src/func.c b/src/func.c index 5d00c94a9..dc19ec722 100644 --- a/src/func.c +++ b/src/func.c @@ -694,7 +694,8 @@ static int patternCompare( /* Skip over multiple "*" characters in the pattern. If there ** are also "?" characters, skip those as well, but consume a ** single character of the input string for each "?" skipped */ - while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){ + while( (c=Utf8Read(zPattern)) == matchAll + || (c == matchOne && matchOne!=0) ){ if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){ return SQLITE_NOWILDCARDMATCH; } @@ -1315,10 +1316,10 @@ static void trimFunc( ){ const unsigned char *zIn; /* Input string */ const unsigned char *zCharSet; /* Set of characters to trim */ - int nIn; /* Number of bytes in input */ + unsigned int nIn; /* Number of bytes in input */ int flags; /* 1: trimleft 2: trimright 3: trim */ int i; /* Loop counter */ - unsigned char *aLen = 0; /* Length of each character in zCharSet */ + unsigned int *aLen = 0; /* Length of each character in zCharSet */ unsigned char **azChar = 0; /* Individual characters in zCharSet */ int nChar; /* Number of characters in zCharSet */ @@ -1327,13 +1328,13 @@ static void trimFunc( } zIn = sqlite3_value_text(argv[0]); if( zIn==0 ) return; - nIn = sqlite3_value_bytes(argv[0]); + nIn = (unsigned)sqlite3_value_bytes(argv[0]); assert( zIn==sqlite3_value_text(argv[0]) ); if( argc==1 ){ - static const unsigned char lenOne[] = { 1 }; + static const unsigned lenOne[] = { 1 }; static unsigned char * const azOne[] = { (u8*)" " }; nChar = 1; - aLen = (u8*)lenOne; + aLen = (unsigned*)lenOne; azChar = (unsigned char **)azOne; zCharSet = 0; }else if( (zCharSet = sqlite3_value_text(argv[1]))==0 ){ @@ -1344,15 +1345,16 @@ static void trimFunc( SQLITE_SKIP_UTF8(z); } if( nChar>0 ){ - azChar = contextMalloc(context, ((i64)nChar)*(sizeof(char*)+1)); + azChar = contextMalloc(context, + ((i64)nChar)*(sizeof(char*)+sizeof(unsigned))); if( azChar==0 ){ return; } - aLen = (unsigned char*)&azChar[nChar]; + aLen = (unsigned*)&azChar[nChar]; for(z=zCharSet, nChar=0; *z; nChar++){ azChar[nChar] = (unsigned char *)z; SQLITE_SKIP_UTF8(z); - aLen[nChar] = (u8)(z - azChar[nChar]); + aLen[nChar] = (unsigned)(z - azChar[nChar]); } } } @@ -1360,7 +1362,7 @@ static void trimFunc( flags = SQLITE_PTR_TO_INT(sqlite3_user_data(context)); if( flags & 1 ){ while( nIn>0 ){ - int len = 0; + unsigned int len = 0; for(i=0; i0 ){ - int len = 0; + unsigned int len = 0; for(i=0; iop!=TK_FUNCTION || !pExpr->x.pList ){ + assert( pExpr!=0 ); + assert( pExpr->op==TK_FUNCTION ); + if( !pExpr->x.pList ){ return 0; } assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); @@ -1904,6 +1908,201 @@ int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ return 1; } +/* Mathematical Constants */ +#ifndef M_PI +# define M_PI 3.141592653589793238462643383279502884 +#endif +#ifndef M_LN10 +# define M_LN10 2.302585092994045684017991454684364208 +#endif +#ifndef M_LN2 +# define M_LN2 0.693147180559945309417232121458176568 +#endif + + +/* Extra math functions that require linking with -lm +*/ +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS +/* +** Implementation SQL functions: +** +** ceil(X) +** ceiling(X) +** floor(X) +** +** The sqlite3_user_data() pointer is a pointer to the libm implementation +** of the underlying C function. +*/ +static void ceilingFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: { + sqlite3_result_int64(context, sqlite3_value_int64(argv[0])); + break; + } + case SQLITE_FLOAT: { + double (*x)(double) = (double(*)(double))sqlite3_user_data(context); + sqlite3_result_double(context, x(sqlite3_value_double(argv[0]))); + break; + } + default: { + break; + } + } +} + +/* +** On some systems, ceil() and floor() are intrinsic function. You are +** unable to take a pointer to these functions. Hence, we here wrap them +** in our own actual functions. +*/ +static double xCeil(double x){ return ceil(x); } +static double xFloor(double x){ return floor(x); } + +/* +** Implementation of SQL functions: +** +** ln(X) - natural logarithm +** log(X) - log X base 10 +** log10(X) - log X base 10 +** log(B,X) - log X base B +*/ +static void logFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + double x, b, ans; + assert( argc==1 || argc==2 ); + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: + x = sqlite3_value_double(argv[0]); + if( x<=0.0 ) return; + break; + default: + return; + } + if( argc==2 ){ + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: + b = log(x); + if( b<=0.0 ) return; + x = sqlite3_value_double(argv[1]); + if( x<=0.0 ) return; + break; + default: + return; + } + ans = log(x)/b; + }else{ + ans = log(x); + switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){ + case 1: + /* Convert from natural logarithm to log base 10 */ + ans *= 1.0/M_LN10; + break; + case 2: + /* Convert from natural logarithm to log base 2 */ + ans *= 1.0/M_LN2; + break; + default: + break; + } + } + sqlite3_result_double(context, ans); +} + +/* +** Functions to converts degrees to radians and radians to degrees. +*/ +static double degToRad(double x){ return x*(M_PI/180.0); } +static double radToDeg(double x){ return x*(180.0/M_PI); } + +/* +** Implementation of 1-argument SQL math functions: +** +** exp(X) - Compute e to the X-th power +*/ +static void math1Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0; + double v0, ans; + double (*x)(double); + assert( argc==1 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + v0 = sqlite3_value_double(argv[0]); + x = (double(*)(double))sqlite3_user_data(context); + ans = x(v0); + sqlite3_result_double(context, ans); +} + +/* +** Implementation of 2-argument SQL math functions: +** +** power(X,Y) - Compute X to the Y-th power +*/ +static void math2Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0, type1; + double v0, v1, ans; + double (*x)(double,double); + assert( argc==2 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + type1 = sqlite3_value_numeric_type(argv[1]); + if( type1!=SQLITE_INTEGER && type1!=SQLITE_FLOAT ) return; + v0 = sqlite3_value_double(argv[0]); + v1 = sqlite3_value_double(argv[1]); + x = (double(*)(double,double))sqlite3_user_data(context); + ans = x(v0, v1); + sqlite3_result_double(context, ans); +} + +/* +** Implementation of 0-argument pi() function. +*/ +static void piFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==0 ); + sqlite3_result_double(context, M_PI); +} + +#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ + +/* +** Implementation of sign(X) function. +*/ +static void signFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0; + double x; + UNUSED_PARAMETER(argc); + assert( argc==1 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + x = sqlite3_value_double(argv[0]); + sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); +} + /* ** All of the FuncDef structures in the aBuiltinFunc[] array above ** to the global function hash table. This occurs at start-time (as @@ -2022,6 +2221,43 @@ void sqlite3RegisterBuiltinFunctions(void){ #endif FUNCTION(coalesce, 1, 0, 0, 0 ), FUNCTION(coalesce, 0, 0, 0, 0 ), +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + MFUNCTION(ceil, 1, xCeil, ceilingFunc ), + MFUNCTION(ceiling, 1, xCeil, ceilingFunc ), + MFUNCTION(floor, 1, xFloor, ceilingFunc ), +#if SQLITE_HAVE_C99_MATH_FUNCS + MFUNCTION(trunc, 1, trunc, ceilingFunc ), +#endif + FUNCTION(ln, 1, 0, 0, logFunc ), + FUNCTION(log, 1, 1, 0, logFunc ), + FUNCTION(log10, 1, 1, 0, logFunc ), + FUNCTION(log2, 1, 2, 0, logFunc ), + FUNCTION(log, 2, 0, 0, logFunc ), + MFUNCTION(exp, 1, exp, math1Func ), + MFUNCTION(pow, 2, pow, math2Func ), + MFUNCTION(power, 2, pow, math2Func ), + MFUNCTION(mod, 2, fmod, math2Func ), + MFUNCTION(acos, 1, acos, math1Func ), + MFUNCTION(asin, 1, asin, math1Func ), + MFUNCTION(atan, 1, atan, math1Func ), + MFUNCTION(atan2, 2, atan2, math2Func ), + MFUNCTION(cos, 1, cos, math1Func ), + MFUNCTION(sin, 1, sin, math1Func ), + MFUNCTION(tan, 1, tan, math1Func ), + MFUNCTION(cosh, 1, cosh, math1Func ), + MFUNCTION(sinh, 1, sinh, math1Func ), + MFUNCTION(tanh, 1, tanh, math1Func ), +#if SQLITE_HAVE_C99_MATH_FUNCS + MFUNCTION(acosh, 1, acosh, math1Func ), + MFUNCTION(asinh, 1, asinh, math1Func ), + MFUNCTION(atanh, 1, atanh, math1Func ), +#endif + MFUNCTION(sqrt, 1, sqrt, math1Func ), + MFUNCTION(radians, 1, degToRad, math1Func ), + MFUNCTION(degrees, 1, radToDeg, math1Func ), + FUNCTION(pi, 0, 0, 0, piFunc ), +#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ + FUNCTION(sign, 1, 0, 0, signFunc ), INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ), INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ), }; diff --git a/src/global.c b/src/global.c index 8ef5a8b2d..4648c26d9 100644 --- a/src/global.c +++ b/src/global.c @@ -37,7 +37,7 @@ const unsigned char sqlite3UpperToLower[] = { 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215, 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233, 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251, - 252,253,254,255 + 252,253,254,255, #endif #ifdef SQLITE_EBCDIC 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 0x */ @@ -57,7 +57,35 @@ const unsigned char sqlite3UpperToLower[] = { 224,225,162,163,164,165,166,167,168,169,234,235,236,237,238,239, /* Ex */ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, /* Fx */ #endif +/* All of the upper-to-lower conversion data is above. The following +** 18 integers are completely unrelated. They are appended to the +** sqlite3UpperToLower[] array to avoid UBSAN warnings. Here's what is +** going on: +** +** The SQL comparison operators (<>, =, >, <=, <, and >=) are implemented +** by invoking sqlite3MemCompare(A,B) which compares values A and B and +** returns negative, zero, or positive if A is less then, equal to, or +** greater than B, respectively. Then the true false results is found by +** consulting sqlite3aLTb[opcode], sqlite3aEQb[opcode], or +** sqlite3aGTb[opcode] depending on whether the result of compare(A,B) +** is negative, zero, or positive, where opcode is the specific opcode. +** The only works because the comparison opcodes are consecutive and in +** this order: NE EQ GT LE LT GE. Various assert()s throughout the code +** ensure that is the case. +** +** These elements must be appended to another array. Otherwise the +** index (here shown as [256-OP_Ne]) would be out-of-bounds and thus +** be undefined behavior. That's goofy, but the C-standards people thought +** it was a good idea, so here we are. +*/ +/* NE EQ GT LE LT GE */ + 1, 0, 0, 1, 1, 0, /* aLTb[]: Use when compare(A,B) less than zero */ + 0, 1, 0, 1, 0, 1, /* aEQb[]: Use when compare(A,B) equals zero */ + 1, 0, 1, 0, 0, 1 /* aGTb[]: Use when compare(A,B) greater than zero*/ }; +const unsigned char *sqlite3aLTb = &sqlite3UpperToLower[256-OP_Ne]; +const unsigned char *sqlite3aEQb = &sqlite3UpperToLower[256+6-OP_Ne]; +const unsigned char *sqlite3aGTb = &sqlite3UpperToLower[256+12-OP_Ne]; /* ** The following 256 byte lookup table is used to support SQLites built-in @@ -251,7 +279,7 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* xVdbeBranch */ 0, /* pVbeBranchArg */ #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE SQLITE_MEMDB_DEFAULT_MAXSIZE, /* mxMemdbSize */ #endif #ifndef SQLITE_UNTESTABLE @@ -301,9 +329,10 @@ int sqlite3PendingByte = 0x40000000; #endif /* -** Flags for select tracing and the ".selecttrace" macro of the CLI +** Tracing flags set by SQLITE_TESTCTRL_TRACEFLAGS. */ -u32 sqlite3_unsupported_selecttrace = 0; +u32 sqlite3SelectTrace = 0; +u32 sqlite3WhereTrace = 0; #include "opcodes.h" /* diff --git a/src/insert.c b/src/insert.c index 393cd528f..1ed0b4315 100644 --- a/src/insert.c +++ b/src/insert.c @@ -358,7 +358,7 @@ static int autoIncBegin( ** Ticket d8dc2b3a58cd5dc2918a1d4acb 2018-05-23 */ if( pSeqTab==0 || !HasRowid(pSeqTab) - || IsVirtual(pSeqTab) + || NEVER(IsVirtual(pSeqTab)) || pSeqTab->nCol!=2 ){ pParse->nErr++; @@ -370,7 +370,9 @@ static int autoIncBegin( while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; } if( pInfo==0 ){ pInfo = sqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo)); - if( pInfo==0 ) return 0; + sqlite3ParserAddCleanup(pToplevel, sqlite3DbFree, pInfo); + testcase( pParse->earlyCleanup ); + if( pParse->db->mallocFailed ) return 0; pInfo->pNext = pToplevel->pAinc; pToplevel->pAinc = pInfo; pInfo->pTab = pTab; @@ -815,7 +817,7 @@ void sqlite3Insert( bIdListInOrder = 0; }else{ sqlite3ErrorMsg(pParse, "table %S has no column named %s", - pTabList, 0, pColumn->a[i].zName); + pTabList->a, pColumn->a[i].zName); pParse->checkSchema = 1; goto insert_cleanup; } @@ -928,19 +930,24 @@ void sqlite3Insert( } } #endif - } - /* Make sure the number of columns in the source data matches the number - ** of columns to be inserted into the table. - */ - for(i=0; inCol; i++){ - if( pTab->aCol[i].colFlags & COLFLAG_NOINSERT ) nHidden++; - } - if( pColumn==0 && nColumn && nColumn!=(pTab->nCol-nHidden) ){ - sqlite3ErrorMsg(pParse, - "table %S has %d columns but %d values were supplied", - pTabList, 0, pTab->nCol-nHidden, nColumn); - goto insert_cleanup; + /* Make sure the number of columns in the source data matches the number + ** of columns to be inserted into the table. + */ + assert( TF_HasHidden==COLFLAG_HIDDEN ); + assert( TF_HasGenerated==COLFLAG_GENERATED ); + assert( COLFLAG_NOINSERT==(COLFLAG_GENERATED|COLFLAG_HIDDEN) ); + if( (pTab->tabFlags & (TF_HasGenerated|TF_HasHidden))!=0 ){ + for(i=0; inCol; i++){ + if( pTab->aCol[i].colFlags & COLFLAG_NOINSERT ) nHidden++; + } + } + if( nColumn!=(pTab->nCol-nHidden) ){ + sqlite3ErrorMsg(pParse, + "table %S has %d columns but %d values were supplied", + pTabList->a, pTab->nCol-nHidden, nColumn); + goto insert_cleanup; + } } if( pColumn!=0 && nColumn!=pColumn->nId ){ sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId); @@ -952,6 +959,7 @@ void sqlite3Insert( if( (db->flags & SQLITE_CountRows)!=0 && !pParse->nested && !pParse->pTriggerTab + && !pParse->bReturning ){ regRowCount = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); @@ -975,6 +983,7 @@ void sqlite3Insert( } #ifndef SQLITE_OMIT_UPSERT if( pUpsert ){ + Upsert *pNx; if( IsVirtual(pTab) ){ sqlite3ErrorMsg(pParse, "UPSERT not implemented for virtual table \"%s\"", pTab->zName); @@ -988,13 +997,19 @@ void sqlite3Insert( goto insert_cleanup; } pTabList->a[0].iCursor = iDataCur; - pUpsert->pUpsertSrc = pTabList; - pUpsert->regData = regData; - pUpsert->iDataCur = iDataCur; - pUpsert->iIdxCur = iIdxCur; - if( pUpsert->pUpsertTarget ){ - sqlite3UpsertAnalyzeTarget(pParse, pTabList, pUpsert); - } + pNx = pUpsert; + do{ + pNx->pUpsertSrc = pTabList; + pNx->regData = regData; + pNx->iDataCur = iDataCur; + pNx->iIdxCur = iIdxCur; + if( pNx->pUpsertTarget ){ + if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){ + goto insert_cleanup; + } + } + pNx = pNx->pNextUpsert; + }while( pNx!=0 ); } #endif @@ -1135,11 +1150,6 @@ void sqlite3Insert( sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v); } - /* Cannot have triggers on a virtual table. If it were possible, - ** this block would have to account for hidden column. - */ - assert( !IsVirtual(pTab) ); - /* Copy the new data already generated. */ assert( pTab->nNVCol>0 ); sqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1); @@ -1238,7 +1248,7 @@ void sqlite3Insert( }else #endif { - int isReplace; /* Set to true if constraints may cause a replace */ + int isReplace = 0;/* Set to true if constraints may cause a replace */ int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert @@ -1258,6 +1268,13 @@ void sqlite3Insert( regIns, aRegIdx, 0, appendFlag, bUseSeek ); } +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + }else if( pParse->bReturning ){ + /* If there is a RETURNING clause, populate the rowid register with + ** constant value -1, in case one or more of the returned expressions + ** refer to the "rowid" of the view. */ + sqlite3VdbeAddOp2(v, OP_Integer, -1, regRowid); +#endif } /* Update the count of rows that are inserted @@ -1294,7 +1311,9 @@ void sqlite3Insert( sqlite3VdbeJumpHere(v, addrInsTop); } +#ifndef SQLITE_OMIT_XFER_OPT insert_end: +#endif /* SQLITE_OMIT_XFER_OPT */ /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. @@ -1309,7 +1328,7 @@ void sqlite3Insert( ** invoke the callback function. */ if( regRowCount ){ - sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1); + sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1); sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC); } @@ -1399,6 +1418,70 @@ int sqlite3ExprReferencesUpdatedColumn( return w.eCode!=0; } +/* +** The sqlite3GenerateConstraintChecks() routine usually wants to visit +** the indexes of a table in the order provided in the Table->pIndex list. +** However, sometimes (rarely - when there is an upsert) it wants to visit +** the indexes in a different order. The following data structures accomplish +** this. +** +** The IndexIterator object is used to walk through all of the indexes +** of a table in either Index.pNext order, or in some other order established +** by an array of IndexListTerm objects. +*/ +typedef struct IndexListTerm IndexListTerm; +typedef struct IndexIterator IndexIterator; +struct IndexIterator { + int eType; /* 0 for Index.pNext list. 1 for an array of IndexListTerm */ + int i; /* Index of the current item from the list */ + union { + struct { /* Use this object for eType==0: A Index.pNext list */ + Index *pIdx; /* The current Index */ + } lx; + struct { /* Use this object for eType==1; Array of IndexListTerm */ + int nIdx; /* Size of the array */ + IndexListTerm *aIdx; /* Array of IndexListTerms */ + } ax; + } u; +}; + +/* When IndexIterator.eType==1, then each index is an array of instances +** of the following object +*/ +struct IndexListTerm { + Index *p; /* The index */ + int ix; /* Which entry in the original Table.pIndex list is this index*/ +}; + +/* Return the first index on the list */ +static Index *indexIteratorFirst(IndexIterator *pIter, int *pIx){ + assert( pIter->i==0 ); + if( pIter->eType ){ + *pIx = pIter->u.ax.aIdx[0].ix; + return pIter->u.ax.aIdx[0].p; + }else{ + *pIx = 0; + return pIter->u.lx.pIdx; + } +} + +/* Return the next index from the list. Return NULL when out of indexes */ +static Index *indexIteratorNext(IndexIterator *pIter, int *pIx){ + if( pIter->eType ){ + int i = ++pIter->i; + if( i>=pIter->u.ax.nIdx ){ + *pIx = i; + return 0; + } + *pIx = pIter->u.ax.aIdx[i].ix; + return pIter->u.ax.aIdx[i].p; + }else{ + ++(*pIx); + pIter->u.lx.pIdx = pIter->u.lx.pIdx->pNext; + return pIter->u.lx.pIdx; + } +} + /* ** Generate code to do constraint checks prior to an INSERT or an UPDATE ** on table pTab. @@ -1507,7 +1590,7 @@ void sqlite3GenerateConstraintChecks( ){ Vdbe *v; /* VDBE under constrution */ Index *pIdx; /* Pointer to one of the indices */ - Index *pPk = 0; /* The PRIMARY KEY index */ + Index *pPk = 0; /* The PRIMARY KEY index for WITHOUT ROWID tables */ sqlite3 *db; /* Database connection */ int i; /* loop counter */ int ix; /* Index loop counter */ @@ -1515,11 +1598,11 @@ void sqlite3GenerateConstraintChecks( int onError; /* Conflict resolution strategy */ int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */ int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */ - Index *pUpIdx = 0; /* Index to which to apply the upsert */ - u8 isUpdate; /* True if this is an UPDATE operation */ + Upsert *pUpsertClause = 0; /* The specific ON CONFLICT clause for pIdx */ + u8 isUpdate; /* True if this is an UPDATE operation */ u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */ - int upsertBypass = 0; /* Address of Goto to bypass upsert subroutine */ - int upsertJump = 0; /* Address of Goto that jumps into upsert subroutine */ + int upsertIpkReturn = 0; /* Address of Goto at end of IPK uniqueness check */ + int upsertIpkDelay = 0; /* Address of Goto to bypass initial IPK check */ int ipkTop = 0; /* Top of the IPK uniqueness check */ int ipkBottom = 0; /* OP_Goto at the end of the IPK uniqueness check */ /* Variables associated with retesting uniqueness constraints after @@ -1529,6 +1612,7 @@ void sqlite3GenerateConstraintChecks( int lblRecheckOk = 0; /* Each recheck jumps to this label if it passes */ Trigger *pTrigger; /* List of DELETE triggers on the table pTab */ int nReplaceTrig = 0; /* Number of replace triggers coded */ + IndexIterator sIdxIter; /* Index iterator */ isUpdate = regOldData!=0; db = pParse->db; @@ -1726,19 +1810,63 @@ void sqlite3GenerateConstraintChecks( ** list of indexes attached to a table puts all OE_Replace indexes last ** in the list. See sqlite3CreateIndex() for where that happens. */ - + sIdxIter.eType = 0; + sIdxIter.i = 0; + sIdxIter.u.ax.aIdx = 0; /* Silence harmless compiler warning */ + sIdxIter.u.lx.pIdx = pTab->pIndex; if( pUpsert ){ if( pUpsert->pUpsertTarget==0 ){ - /* An ON CONFLICT DO NOTHING clause, without a constraint-target. - ** Make all unique constraint resolution be OE_Ignore */ - assert( pUpsert->pUpsertSet==0 ); - overrideError = OE_Ignore; - pUpsert = 0; - }else if( (pUpIdx = pUpsert->pUpsertIdx)!=0 ){ - /* If the constraint-target uniqueness check must be run first. - ** Jump to that uniqueness check now */ - upsertJump = sqlite3VdbeAddOp0(v, OP_Goto); - VdbeComment((v, "UPSERT constraint goes first")); + /* There is just on ON CONFLICT clause and it has no constraint-target */ + assert( pUpsert->pNextUpsert==0 ); + if( pUpsert->isDoUpdate==0 ){ + /* A single ON CONFLICT DO NOTHING clause, without a constraint-target. + ** Make all unique constraint resolution be OE_Ignore */ + overrideError = OE_Ignore; + pUpsert = 0; + }else{ + /* A single ON CONFLICT DO UPDATE. Make all resolutions OE_Update */ + overrideError = OE_Update; + } + }else if( pTab->pIndex!=0 ){ + /* Otherwise, we'll need to run the IndexListTerm array version of the + ** iterator to ensure that all of the ON CONFLICT conditions are + ** checked first and in order. */ + int nIdx, jj; + u64 nByte; + Upsert *pTerm; + u8 *bUsed; + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ + assert( aRegIdx[nIdx]>0 ); + } + sIdxIter.eType = 1; + sIdxIter.u.ax.nIdx = nIdx; + nByte = (sizeof(IndexListTerm)+1)*nIdx + nIdx; + sIdxIter.u.ax.aIdx = sqlite3DbMallocZero(db, nByte); + if( sIdxIter.u.ax.aIdx==0 ) return; /* OOM */ + bUsed = (u8*)&sIdxIter.u.ax.aIdx[nIdx]; + pUpsert->pToFree = sIdxIter.u.ax.aIdx; + for(i=0, pTerm=pUpsert; pTerm; pTerm=pTerm->pNextUpsert){ + if( pTerm->pUpsertTarget==0 ) break; + if( pTerm->pUpsertIdx==0 ) continue; /* Skip ON CONFLICT for the IPK */ + jj = 0; + pIdx = pTab->pIndex; + while( ALWAYS(pIdx!=0) && pIdx!=pTerm->pUpsertIdx ){ + pIdx = pIdx->pNext; + jj++; + } + if( bUsed[jj] ) continue; /* Duplicate ON CONFLICT clause ignored */ + bUsed[jj] = 1; + sIdxIter.u.ax.aIdx[i].p = pIdx; + sIdxIter.u.ax.aIdx[i].ix = jj; + i++; + } + for(jj=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, jj++){ + if( bUsed[jj] ) continue; + sIdxIter.u.ax.aIdx[i].p = pIdx; + sIdxIter.u.ax.aIdx[i].ix = jj; + i++; + } + assert( i==nIdx ); } } @@ -1801,11 +1929,20 @@ void sqlite3GenerateConstraintChecks( } /* figure out whether or not upsert applies in this case */ - if( pUpsert && pUpsert->pUpsertIdx==0 ){ - if( pUpsert->pUpsertSet==0 ){ - onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ - }else{ - onError = OE_Update; /* DO UPDATE */ + if( pUpsert ){ + pUpsertClause = sqlite3UpsertOfIndex(pUpsert,0); + if( pUpsertClause!=0 ){ + if( pUpsertClause->isDoUpdate==0 ){ + onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ + }else{ + onError = OE_Update; /* DO UPDATE */ + } + } + if( pUpsertClause!=pUpsert ){ + /* The first ON CONFLICT clause has a conflict target other than + ** the IPK. We have to jump ahead to that first ON CONFLICT clause + ** and then come back here and deal with the IPK afterwards */ + upsertIpkDelay = sqlite3VdbeAddOp0(v, OP_Goto); } } @@ -1815,7 +1952,7 @@ void sqlite3GenerateConstraintChecks( ** the UNIQUE constraints have run. */ if( onError==OE_Replace /* IPK rule is REPLACE */ - && onError!=overrideError /* Rules for other contraints are different */ + && onError!=overrideError /* Rules for other constraints are different */ && pTab->pIndex /* There exist other constraints */ ){ ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1; @@ -1912,7 +2049,9 @@ void sqlite3GenerateConstraintChecks( } } sqlite3VdbeResolveLabel(v, addrRowidOk); - if( ipkTop ){ + if( pUpsert && pUpsertClause!=pUpsert ){ + upsertIpkReturn = sqlite3VdbeAddOp0(v, OP_Goto); + }else if( ipkTop ){ ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto); sqlite3VdbeJumpHere(v, ipkTop-1); } @@ -1925,7 +2064,10 @@ void sqlite3GenerateConstraintChecks( ** This loop also handles the case of the PRIMARY KEY index for a ** WITHOUT ROWID table. */ - for(ix=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, ix++){ + for(pIdx = indexIteratorFirst(&sIdxIter, &ix); + pIdx; + pIdx = indexIteratorNext(&sIdxIter, &ix) + ){ int regIdx; /* Range of registers hold conent for pIdx */ int regR; /* Range of registers holding conflicting PK */ int iThisCur; /* Cursor for this UNIQUE index */ @@ -1933,15 +2075,14 @@ void sqlite3GenerateConstraintChecks( int addrConflictCk; /* First opcode in the conflict check logic */ if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */ - if( pUpIdx==pIdx ){ - addrUniqueOk = upsertJump+1; - upsertBypass = sqlite3VdbeGoto(v, 0); - VdbeComment((v, "Skip upsert subroutine")); - sqlite3VdbeJumpHere(v, upsertJump); - }else{ - addrUniqueOk = sqlite3VdbeMakeLabel(pParse); + if( pUpsert ){ + pUpsertClause = sqlite3UpsertOfIndex(pUpsert, pIdx); + if( upsertIpkDelay && pUpsertClause==pUpsert ){ + sqlite3VdbeJumpHere(v, upsertIpkDelay); + } } - if( bAffinityDone==0 && (pUpIdx==0 || pUpIdx==pIdx) ){ + addrUniqueOk = sqlite3VdbeMakeLabel(pParse); + if( bAffinityDone==0 ){ sqlite3TableAffinity(v, pTab, regNewData+1); bAffinityDone = 1; } @@ -2012,8 +2153,8 @@ void sqlite3GenerateConstraintChecks( } /* Figure out if the upsert clause applies to this index */ - if( pUpIdx==pIdx ){ - if( pUpsert->pUpsertSet==0 ){ + if( pUpsertClause ){ + if( pUpsertClause->isDoUpdate==0 ){ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ }else{ onError = OE_Update; /* DO UPDATE */ @@ -2051,7 +2192,7 @@ void sqlite3GenerateConstraintChecks( regIdx, pIdx->nKeyCol); VdbeCoverage(v); /* Generate code to handle collisions */ - regR = (pIdx==pPk) ? regIdx : sqlite3GetTempRange(pParse, nPkField); + regR = pIdx==pPk ? regIdx : sqlite3GetTempRange(pParse, nPkField); if( isUpdate || onError==OE_Replace ){ if( HasRowid(pTab) ){ sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR); @@ -2203,13 +2344,16 @@ void sqlite3GenerateConstraintChecks( break; } } - if( pUpIdx==pIdx ){ - sqlite3VdbeGoto(v, upsertJump+1); - sqlite3VdbeJumpHere(v, upsertBypass); - }else{ - sqlite3VdbeResolveLabel(v, addrUniqueOk); - } + sqlite3VdbeResolveLabel(v, addrUniqueOk); if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); + if( pUpsertClause + && upsertIpkReturn + && sqlite3UpsertNextIsIPK(pUpsertClause) + ){ + sqlite3VdbeGoto(v, upsertIpkDelay+1); + sqlite3VdbeJumpHere(v, upsertIpkReturn); + upsertIpkReturn = 0; + } } /* If the IPK constraint is a REPLACE, run it last */ @@ -2275,6 +2419,32 @@ void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){ } #endif +/* +** Table pTab is a WITHOUT ROWID table that is being written to. The cursor +** number is iCur, and register regData contains the new record for the +** PK index. This function adds code to invoke the pre-update hook, +** if one is registered. +*/ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +static void codeWithoutRowidPreupdate( + Parse *pParse, /* Parse context */ + Table *pTab, /* Table being updated */ + int iCur, /* Cursor number for table */ + int regData /* Data containing new record */ +){ + Vdbe *v = pParse->pVdbe; + int r = sqlite3GetTempReg(pParse); + assert( !HasRowid(pTab) ); + assert( 0==(pParse->db->mDbFlags & DBFLAG_Vacuum) || CORRUPT_DB ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, r); + sqlite3VdbeAddOp4(v, OP_Insert, iCur, regData, r, (char*)pTab, P4_TABLE); + sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP); + sqlite3ReleaseTempReg(pParse, r); +} +#else +# define codeWithoutRowidPreupdate(a,b,c,d) +#endif + /* ** This routine generates code to finish the INSERT or UPDATE operation ** that was started by a prior call to sqlite3GenerateConstraintChecks. @@ -2323,17 +2493,9 @@ void sqlite3CompleteInsertion( assert( pParse->nested==0 ); pik_flags |= OPFLAG_NCHANGE; pik_flags |= (update_flags & OPFLAG_SAVEPOSITION); -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK if( update_flags==0 ){ - int r = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_Integer, 0, r); - sqlite3VdbeAddOp4(v, OP_Insert, - iIdxCur+i, aRegIdx[i], r, (char*)pTab, P4_TABLE - ); - sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP); - sqlite3ReleaseTempReg(pParse, r); + codeWithoutRowidPreupdate(pParse, pTab, iIdxCur+i, aRegIdx[i]); } -#endif } sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i], aRegIdx[i]+1, @@ -2531,7 +2693,7 @@ static int xferOptimization( ExprList *pEList; /* The result set of the SELECT */ Table *pSrc; /* The table in the FROM clause of SELECT */ Index *pSrcIdx, *pDestIdx; /* Source and destination indices */ - struct SrcList_item *pItem; /* An element of pSelect->pSrc */ + SrcItem *pItem; /* An element of pSelect->pSrc */ int i; /* Loop counter */ int iDbSrc; /* The database of pSrc */ int iSrc, iDest; /* Cursors from source and destination */ @@ -2748,6 +2910,7 @@ static int xferOptimization( iDest = pParse->nTab++; regAutoinc = autoIncBegin(pParse, iDbDest, pDest); regData = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Null, 0, regData); regRowid = sqlite3GetTempReg(pParse); sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite); assert( HasRowid(pDest) || destHasUniqueIdx ); @@ -2783,11 +2946,13 @@ static int xferOptimization( emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); if( pDest->iPKey>=0 ){ addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); - sqlite3VdbeVerifyAbortable(v, onError); - addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); - VdbeCoverage(v); - sqlite3RowidConstraint(pParse, onError, pDest); - sqlite3VdbeJumpHere(v, addr2); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeVerifyAbortable(v, onError); + addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); + VdbeCoverage(v); + sqlite3RowidConstraint(pParse, onError, pDest); + sqlite3VdbeJumpHere(v, addr2); + } autoIncStep(pParse, regAutoinc, regRowid); }else if( pDest->pIndex==0 && !(db->mDbFlags & DBFLAG_VacuumInto) ){ addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid); @@ -2795,16 +2960,28 @@ static int xferOptimization( addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); assert( (pDest->tabFlags & TF_Autoincrement)==0 ); } + if( db->mDbFlags & DBFLAG_Vacuum ){ sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest); - insFlags = OPFLAG_APPEND|OPFLAG_USESEEKRESULT; + insFlags = OPFLAG_APPEND|OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT; }else{ - insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND; + insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND|OPFLAG_PREFORMAT; + } +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + insFlags &= ~OPFLAG_PREFORMAT; + }else +#endif + { + sqlite3VdbeAddOp3(v, OP_RowCell, iDest, iSrc, regRowid); + } + sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeChangeP4(v, -1, (char*)pDest, P4_TABLE); } - sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); - sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid, - (char*)pDest, P4_TABLE); sqlite3VdbeChangeP5(v, insFlags); + sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); @@ -2846,13 +3023,22 @@ static int xferOptimization( if( sqlite3_stricmp(sqlite3StrBINARY, zColl) ) break; } if( i==pSrcIdx->nColumn ){ - idxInsFlags = OPFLAG_USESEEKRESULT; + idxInsFlags = OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT; sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest); + sqlite3VdbeAddOp2(v, OP_RowCell, iDest, iSrc); } }else if( !HasRowid(pSrc) && pDestIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){ idxInsFlags |= OPFLAG_NCHANGE; } - sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + if( idxInsFlags!=(OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT) ){ + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 + && !HasRowid(pDest) + && IsPrimaryKeyIndex(pDestIdx) + ){ + codeWithoutRowidPreupdate(pParse, pDest, iDest, regData); + } + } sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData); sqlite3VdbeChangeP5(v, idxInsFlags|OPFLAG_APPEND); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v); diff --git a/src/loadext.c b/src/loadext.c index 60cffdaed..aeea837c9 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -515,7 +515,7 @@ static int sqlite3LoadExtension( const char *zEntry; char *zAltEntry = 0; void **aHandle; - u64 nMsg = 300 + sqlite3Strlen30(zFile); + u64 nMsg = strlen(zFile); int ii; int rc; @@ -549,6 +549,12 @@ static int sqlite3LoadExtension( zEntry = zProc ? zProc : "sqlite3_extension_init"; + /* tag-20210611-1. Some dlopen() implementations will segfault if given + ** an oversize filename. Most filesystems have a pathname limit of 4K, + ** so limit the extension filename length to about twice that. + ** https://sqlite.org/forum/forumpost/08a0d6d9bf */ + if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found; + handle = sqlite3OsDlOpen(pVfs, zFile); #if SQLITE_OS_UNIX || SQLITE_OS_WIN for(ii=0; iiaExtension[db->nExtension++] = handle; return SQLITE_OK; + +extension_not_found: + if( pzErrMsg ){ + nMsg += 300; + *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg); + if( zErrmsg ){ + assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */ + sqlite3_snprintf((int)nMsg, zErrmsg, + "unable to open shared library [%.*s]", SQLITE_MAX_PATHLEN, zFile); + sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); + } + } + return SQLITE_ERROR; } int sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ diff --git a/src/main.c b/src/main.c index cc1464f13..a04a6bc0f 100644 --- a/src/main.c +++ b/src/main.c @@ -305,7 +305,7 @@ int sqlite3_initialize(void){ sqlite3GlobalConfig.isPCacheInit = 1; rc = sqlite3OsInit(); } -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE if( rc==SQLITE_OK ){ rc = sqlite3MemdbInit(); } @@ -720,12 +720,12 @@ int sqlite3_config(int op, ...){ } #endif /* SQLITE_ENABLE_SORTER_REFERENCES */ -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE case SQLITE_CONFIG_MEMDB_MAXSIZE: { sqlite3GlobalConfig.mxMemdbSize = va_arg(ap, sqlite3_int64); break; } -#endif /* SQLITE_ENABLE_DESERIALIZE */ +#endif /* SQLITE_OMIT_DESERIALIZE */ default: { rc = SQLITE_ERROR; @@ -1274,7 +1274,7 @@ int sqlite3_txn_state(sqlite3 *db, const char *zSchema){ /* ** Two variations on the public interface for closing a database ** connection. The sqlite3_close() version returns SQLITE_BUSY and -** leaves the connection option if there are unfinalized prepared +** leaves the connection open if there are unfinalized prepared ** statements or unfinished sqlite3_backups. The sqlite3_close_v2() ** version forces the connection to become a zombie if there are ** unclosed resources, and arranges for deallocation when the last @@ -1884,6 +1884,10 @@ int sqlite3CreateFunc( }else{ sqlite3ExpirePreparedStatements(db, 0); } + }else if( xSFunc==0 && xFinal==0 ){ + /* Trying to delete a function that does not exist. This is a no-op. + ** https://sqlite.org/forum/forumpost/726219164b */ + return SQLITE_OK; } p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1); @@ -2374,7 +2378,7 @@ int sqlite3_wal_checkpoint_v2( return SQLITE_OK; #else int rc; /* Return code */ - int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */ + int iDb; /* Schema to checkpoint */ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; @@ -2397,6 +2401,8 @@ int sqlite3_wal_checkpoint_v2( sqlite3_mutex_enter(db->mutex); if( zDb && zDb[0] ){ iDb = sqlite3FindDbName(db, zDb); + }else{ + iDb = SQLITE_MAX_DB; /* This means process all schemas */ } if( iDb<0 ){ rc = SQLITE_ERROR; @@ -2445,7 +2451,7 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ ** associated with the specific b-tree being checkpointed is taken by ** this function while the checkpoint is running. ** -** If iDb is passed SQLITE_MAX_ATTACHED, then all attached databases are +** If iDb is passed SQLITE_MAX_DB then all attached databases are ** checkpointed. If an error is encountered it is returned immediately - ** no attempt is made to checkpoint any remaining databases. ** @@ -2460,9 +2466,11 @@ int sqlite3Checkpoint(sqlite3 *db, int iDb, int eMode, int *pnLog, int *pnCkpt){ assert( sqlite3_mutex_held(db->mutex) ); assert( !pnLog || *pnLog==-1 ); assert( !pnCkpt || *pnCkpt==-1 ); + testcase( iDb==SQLITE_MAX_ATTACHED ); /* See forum post a006d86f72 */ + testcase( iDb==SQLITE_MAX_DB ); for(i=0; inDb && rc==SQLITE_OK; i++){ - if( i==iDb || iDb==SQLITE_MAX_ATTACHED ){ + if( i==iDb || iDb==SQLITE_MAX_DB ){ rc = sqlite3BtreeCheckpoint(db->aDb[i].pBt, eMode, pnLog, pnCkpt); pnLog = 0; pnCkpt = 0; @@ -4080,7 +4088,7 @@ int sqlite3_test_control(int op, ...){ */ case SQLITE_TESTCTRL_OPTIMIZATIONS: { sqlite3 *db = va_arg(ap, sqlite3*); - db->dbOptFlags = (u16)(va_arg(ap, int) & 0xffff); + db->dbOptFlags = va_arg(ap, u32); break; } @@ -4255,7 +4263,56 @@ int sqlite3_test_control(int op, ...){ break; } + /* sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, op, ptr) + ** + ** "ptr" is a pointer to a u32. + ** + ** op==0 Store the current sqlite3SelectTrace in *ptr + ** op==1 Set sqlite3SelectTrace to the value *ptr + ** op==3 Store the current sqlite3WhereTrace in *ptr + ** op==3 Set sqlite3WhereTrace to the value *ptr + */ + case SQLITE_TESTCTRL_TRACEFLAGS: { + int opTrace = va_arg(ap, int); + u32 *ptr = va_arg(ap, u32*); + switch( opTrace ){ + case 0: *ptr = sqlite3SelectTrace; break; + case 1: sqlite3SelectTrace = *ptr; break; + case 2: *ptr = sqlite3WhereTrace; break; + case 3: sqlite3WhereTrace = *ptr; break; + } + break; + } +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) + /* sqlite3_test_control(SQLITE_TESTCTRL_TUNE, id, *piValue) + ** + ** If "id" is an integer between 1 and SQLITE_NTUNE then set the value + ** of the id-th tuning parameter to *piValue. If "id" is between -1 + ** and -SQLITE_NTUNE, then write the current value of the (-id)-th + ** tuning parameter into *piValue. + ** + ** Tuning parameters are for use during transient development builds, + ** to help find the best values for constants in the query planner. + ** Access tuning parameters using the Tuning(ID) macro. Set the + ** parameters in the CLI using ".testctrl tune ID VALUE". + ** + ** Transient use only. Tuning parameters should not be used in + ** checked-in code. + */ + case SQLITE_TESTCTRL_TUNE: { + int id = va_arg(ap, int); + int *piValue = va_arg(ap, int*); + if( id>0 && id<=SQLITE_NTUNE ){ + Tuning(id) = *piValue; + }else if( id<0 && id>=-SQLITE_NTUNE ){ + *piValue = Tuning(-id); + }else{ + rc = SQLITE_NOTFOUND; + } + break; + } +#endif } va_end(ap); #endif /* SQLITE_UNTESTABLE */ diff --git a/src/malloc.c b/src/malloc.c index a0f7a739c..b8a88f128 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -161,7 +161,6 @@ int sqlite3MallocInit(void){ if( sqlite3GlobalConfig.m.xMalloc==0 ){ sqlite3MemSetDefault(); } - memset(&mem0, 0, sizeof(mem0)); mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); if( sqlite3GlobalConfig.pPage==0 || sqlite3GlobalConfig.szPage<512 || sqlite3GlobalConfig.nPage<=0 ){ diff --git a/src/memdb.c b/src/memdb.c index a48980aa3..4e5751f93 100644 --- a/src/memdb.c +++ b/src/memdb.c @@ -17,31 +17,88 @@ ** sqlite3_deserialize(). */ #include "sqliteInt.h" -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE /* ** Forward declaration of objects used by this utility */ typedef struct sqlite3_vfs MemVfs; typedef struct MemFile MemFile; +typedef struct MemStore MemStore; /* Access to a lower-level VFS that (might) implement dynamic loading, ** access to randomness, etc. */ #define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) -/* An open file */ -struct MemFile { - sqlite3_file base; /* IO methods */ +/* Storage for a memdb file. +** +** An memdb object can be shared or separate. Shared memdb objects can be +** used by more than one database connection. Mutexes are used by shared +** memdb objects to coordinate access. Separate memdb objects are only +** connected to a single database connection and do not require additional +** mutexes. +** +** Shared memdb objects have .zFName!=0 and .pMutex!=0. They are created +** using "file:/name?vfs=memdb". The first character of the name must be +** "/" or else the object will be a separate memdb object. All shared +** memdb objects are stored in memdb_g.apMemStore[] in an arbitrary order. +** +** Separate memdb objects are created using a name that does not begin +** with "/" or using sqlite3_deserialize(). +** +** Access rules for shared MemStore objects: +** +** * .zFName is initialized when the object is created and afterwards +** is unchanged until the object is destroyed. So it can be accessed +** at any time as long as we know the object is not being destroyed, +** which means while either the SQLITE_MUTEX_STATIC_VFS1 or +** .pMutex is held or the object is not part of memdb_g.apMemStore[]. +** +** * Can .pMutex can only be changed while holding the +** SQLITE_MUTEX_STATIC_VFS1 mutex or while the object is not part +** of memdb_g.apMemStore[]. +** +** * Other fields can only be changed while holding the .pMutex mutex +** or when the .nRef is less than zero and the object is not part of +** memdb_g.apMemStore[]. +** +** * The .aData pointer has the added requirement that it can can only +** be changed (for resizing) when nMmap is zero. +** +*/ +struct MemStore { sqlite3_int64 sz; /* Size of the file */ sqlite3_int64 szAlloc; /* Space allocated to aData */ sqlite3_int64 szMax; /* Maximum allowed size of the file */ unsigned char *aData; /* content of the file */ + sqlite3_mutex *pMutex; /* Used by shared stores only */ int nMmap; /* Number of memory mapped pages */ unsigned mFlags; /* Flags */ + int nRdLock; /* Number of readers */ + int nWrLock; /* Number of writers. (Always 0 or 1) */ + int nRef; /* Number of users of this MemStore */ + char *zFName; /* The filename for shared stores */ +}; + +/* An open file */ +struct MemFile { + sqlite3_file base; /* IO methods */ + MemStore *pStore; /* The storage */ int eLock; /* Most recent lock against this file */ }; +/* +** File-scope variables for holding the memdb files that are accessible +** to multiple database connections in separate threads. +** +** Must hold SQLITE_MUTEX_STATIC_VFS1 to access any part of this object. +*/ +static struct MemFS { + int nMemStore; /* Number of shared MemStore objects */ + MemStore **apMemStore; /* Array of all shared MemStore objects */ +} memdb_g; + /* ** Methods for MemFile */ @@ -95,7 +152,10 @@ static sqlite3_vfs memdb_vfs = { memdbSleep, /* xSleep */ 0, /* memdbCurrentTime, */ /* xCurrentTime */ memdbGetLastError, /* xGetLastError */ - memdbCurrentTimeInt64 /* xCurrentTimeInt64 */ + memdbCurrentTimeInt64, /* xCurrentTimeInt64 */ + 0, /* xSetSystemCall */ + 0, /* xGetSystemCall */ + 0, /* xNextSystemCall */ }; static const sqlite3_io_methods memdb_io_methods = { @@ -120,19 +180,67 @@ static const sqlite3_io_methods memdb_io_methods = { memdbUnfetch /* xUnfetch */ }; +/* +** Enter/leave the mutex on a MemStore +*/ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 +static void memdbEnter(MemStore *p){ + UNUSED_PARAMETER(p); +} +static void memdbLeave(MemStore *p){ + UNUSED_PARAMETER(p); +} +#else +static void memdbEnter(MemStore *p){ + sqlite3_mutex_enter(p->pMutex); +} +static void memdbLeave(MemStore *p){ + sqlite3_mutex_leave(p->pMutex); +} +#endif + /* ** Close an memdb-file. -** -** The pData pointer is owned by the application, so there is nothing -** to free. Unless the SQLITE_DESERIALIZE_FREEONCLOSE flag is set, -** in which case we own the pData pointer and need to free it. +** Free the underlying MemStore object when its refcount drops to zero +** or less. */ static int memdbClose(sqlite3_file *pFile){ - MemFile *p = (MemFile *)pFile; - if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ){ - sqlite3_free(p->aData); + MemStore *p = ((MemFile*)pFile)->pStore; + if( p->zFName ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; ALWAYS(inRef==1 ){ + memdb_g.apMemStore[i] = memdb_g.apMemStore[--memdb_g.nMemStore]; + if( memdb_g.nMemStore==0 ){ + sqlite3_free(memdb_g.apMemStore); + memdb_g.apMemStore = 0; + } + } + break; + } + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + memdbEnter(p); + } + p->nRef--; + if( p->nRef<=0 ){ + if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ){ + sqlite3_free(p->aData); + } + memdbLeave(p); + sqlite3_mutex_free(p->pMutex); + sqlite3_free(p); + }else{ + memdbLeave(p); } return SQLITE_OK; } @@ -146,20 +254,23 @@ static int memdbRead( int iAmt, sqlite_int64 iOfst ){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); if( iOfst+iAmt>p->sz ){ memset(zBuf, 0, iAmt); if( iOfstsz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst); + memdbLeave(p); return SQLITE_IOERR_SHORT_READ; } memcpy(zBuf, p->aData+iOfst, iAmt); + memdbLeave(p); return SQLITE_OK; } /* ** Try to enlarge the memory allocation to hold at least sz bytes */ -static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){ +static int memdbEnlarge(MemStore *p, sqlite3_int64 newSz){ unsigned char *pNew; if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || p->nMmap>0 ){ return SQLITE_FULL; @@ -170,7 +281,7 @@ static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){ newSz *= 2; if( newSz>p->szMax ) newSz = p->szMax; pNew = sqlite3Realloc(p->aData, newSz); - if( pNew==0 ) return SQLITE_NOMEM; + if( pNew==0 ) return SQLITE_IOERR_NOMEM; p->aData = pNew; p->szAlloc = newSz; return SQLITE_OK; @@ -185,19 +296,27 @@ static int memdbWrite( int iAmt, sqlite_int64 iOfst ){ - MemFile *p = (MemFile *)pFile; - if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ) return SQLITE_READONLY; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ){ + /* Can't happen: memdbLock() will return SQLITE_READONLY before + ** reaching this point */ + memdbLeave(p); + return SQLITE_IOERR_WRITE; + } if( iOfst+iAmt>p->sz ){ int rc; if( iOfst+iAmt>p->szAlloc && (rc = memdbEnlarge(p, iOfst+iAmt))!=SQLITE_OK ){ + memdbLeave(p); return rc; } if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz); p->sz = iOfst+iAmt; } memcpy(p->aData+iOfst, z, iAmt); + memdbLeave(p); return SQLITE_OK; } @@ -209,16 +328,24 @@ static int memdbWrite( ** the size of a file, never to increase the size. */ static int memdbTruncate(sqlite3_file *pFile, sqlite_int64 size){ - MemFile *p = (MemFile *)pFile; - if( NEVER(size>p->sz) ) return SQLITE_FULL; - p->sz = size; - return SQLITE_OK; + MemStore *p = ((MemFile*)pFile)->pStore; + int rc = SQLITE_OK; + memdbEnter(p); + if( NEVER(size>p->sz) ){ + rc = SQLITE_FULL; + }else{ + p->sz = size; + } + memdbLeave(p); + return rc; } /* ** Sync an memdb-file. */ static int memdbSync(sqlite3_file *pFile, int flags){ + UNUSED_PARAMETER(pFile); + UNUSED_PARAMETER(flags); return SQLITE_OK; } @@ -226,8 +353,10 @@ static int memdbSync(sqlite3_file *pFile, int flags){ ** Return the current file-size of an memdb-file. */ static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); *pSize = p->sz; + memdbLeave(p); return SQLITE_OK; } @@ -235,19 +364,48 @@ static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ ** Lock an memdb-file. */ static int memdbLock(sqlite3_file *pFile, int eLock){ - MemFile *p = (MemFile *)pFile; - if( eLock>SQLITE_LOCK_SHARED - && (p->mFlags & SQLITE_DESERIALIZE_READONLY)!=0 - ){ - return SQLITE_READONLY; + MemFile *pThis = (MemFile*)pFile; + MemStore *p = pThis->pStore; + int rc = SQLITE_OK; + if( eLock==pThis->eLock ) return SQLITE_OK; + memdbEnter(p); + if( eLock>SQLITE_LOCK_SHARED ){ + if( p->mFlags & SQLITE_DESERIALIZE_READONLY ){ + rc = SQLITE_READONLY; + }else if( pThis->eLock<=SQLITE_LOCK_SHARED ){ + if( p->nWrLock ){ + rc = SQLITE_BUSY; + }else{ + p->nWrLock = 1; + } + } + }else if( eLock==SQLITE_LOCK_SHARED ){ + if( pThis->eLock > SQLITE_LOCK_SHARED ){ + assert( p->nWrLock==1 ); + p->nWrLock = 0; + }else if( p->nWrLock ){ + rc = SQLITE_BUSY; + }else{ + p->nRdLock++; + } + }else{ + assert( eLock==SQLITE_LOCK_NONE ); + if( pThis->eLock>SQLITE_LOCK_SHARED ){ + assert( p->nWrLock==1 ); + p->nWrLock = 0; + } + assert( p->nRdLock>0 ); + p->nRdLock--; } - p->eLock = eLock; - return SQLITE_OK; + if( rc==SQLITE_OK ) pThis->eLock = eLock; + memdbLeave(p); + return rc; } -#if 0 /* Never used because memdbAccess() always returns false */ +#if 0 /* -** Check if another file-handle holds a RESERVED lock on an memdb-file. +** This interface is only used for crash recovery, which does not +** occur on an in-memory database. */ static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){ *pResOut = 0; @@ -255,12 +413,14 @@ static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){ } #endif + /* ** File control method. For custom operations on an memdb-file. */ static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; int rc = SQLITE_NOTFOUND; + memdbEnter(p); if( op==SQLITE_FCNTL_VFSNAME ){ *(char**)pArg = sqlite3_mprintf("memdb(%p,%lld)", p->aData, p->sz); rc = SQLITE_OK; @@ -278,6 +438,7 @@ static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){ *(sqlite3_int64*)pArg = iLimit; rc = SQLITE_OK; } + memdbLeave(p); return rc; } @@ -294,6 +455,7 @@ static int memdbSectorSize(sqlite3_file *pFile){ ** Return the device characteristic flags supported by an memdb-file. */ static int memdbDeviceCharacteristics(sqlite3_file *pFile){ + UNUSED_PARAMETER(pFile); return SQLITE_IOCAP_ATOMIC | SQLITE_IOCAP_POWERSAFE_OVERWRITE | SQLITE_IOCAP_SAFE_APPEND | @@ -307,20 +469,26 @@ static int memdbFetch( int iAmt, void **pp ){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); if( iOfst+iAmt>p->sz ){ *pp = 0; }else{ p->nMmap++; *pp = (void*)(p->aData + iOfst); } + memdbLeave(p); return SQLITE_OK; } /* Release a memory-mapped page */ static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + UNUSED_PARAMETER(iOfst); + UNUSED_PARAMETER(pPage); + memdbEnter(p); p->nMmap--; + memdbLeave(p); return SQLITE_OK; } @@ -330,20 +498,79 @@ static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ static int memdbOpen( sqlite3_vfs *pVfs, const char *zName, - sqlite3_file *pFile, + sqlite3_file *pFd, int flags, int *pOutFlags ){ - MemFile *p = (MemFile*)pFile; + MemFile *pFile = (MemFile*)pFd; + MemStore *p = 0; + int szName; if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ - return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFile, flags, pOutFlags); + return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFd, flags, pOutFlags); } - memset(p, 0, sizeof(*p)); - p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE; + memset(pFile, 0, sizeof(*p)); + szName = sqlite3Strlen30(zName); + if( szName>1 && zName[0]=='/' ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; izFName,zName)==0 ){ + p = memdb_g.apMemStore[i]; + break; + } + } + if( p==0 ){ + MemStore **apNew; + p = sqlite3Malloc( sizeof(*p) + szName + 3 ); + if( p==0 ){ + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew = sqlite3Realloc(memdb_g.apMemStore, + sizeof(apNew[0])*(memdb_g.nMemStore+1) ); + if( apNew==0 ){ + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew[memdb_g.nMemStore++] = p; + memdb_g.apMemStore = apNew; + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE|SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; + p->zFName = (char*)&p[1]; + memcpy(p->zFName, zName, szName+1); + p->pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( p->pMutex==0 ){ + memdb_g.nMemStore--; + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + p->nRef = 1; + memdbEnter(p); + }else{ + memdbEnter(p); + p->nRef++; + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + p = sqlite3Malloc( sizeof(*p) ); + if( p==0 ){ + return SQLITE_NOMEM; + } + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; + } + pFile->pStore = p; assert( pOutFlags!=0 ); /* True because flags==SQLITE_OPEN_MAIN_DB */ *pOutFlags = flags | SQLITE_OPEN_MEMORY; - pFile->pMethods = &memdb_io_methods; - p->szMax = sqlite3GlobalConfig.mxMemdbSize; + pFd->pMethods = &memdb_io_methods; + memdbLeave(p); return SQLITE_OK; } @@ -371,6 +598,9 @@ static int memdbAccess( int flags, int *pResOut ){ + UNUSED_PARAMETER(pVfs); + UNUSED_PARAMETER(zPath); + UNUSED_PARAMETER(flags); *pResOut = 0; return SQLITE_OK; } @@ -386,6 +616,7 @@ static int memdbFullPathname( int nOut, char *zOut ){ + UNUSED_PARAMETER(pVfs); sqlite3_snprintf(nOut, zOut, "%s", zPath); return SQLITE_OK; } @@ -458,9 +689,14 @@ static int memdbCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ */ static MemFile *memdbFromDbSchema(sqlite3 *db, const char *zSchema){ MemFile *p = 0; + MemStore *pStore; int rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p); if( rc ) return 0; if( p->base.pMethods!=&memdb_io_methods ) return 0; + pStore = p->pStore; + memdbEnter(pStore); + if( pStore->zFName!=0 ) p = 0; + memdbLeave(pStore); return p; } @@ -496,12 +732,14 @@ unsigned char *sqlite3_serialize( if( piSize ) *piSize = -1; if( iDb<0 ) return 0; if( p ){ - if( piSize ) *piSize = p->sz; + MemStore *pStore = p->pStore; + assert( pStore->pMutex==0 ); + if( piSize ) *piSize = pStore->sz; if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ - pOut = p->aData; + pOut = pStore->aData; }else{ - pOut = sqlite3_malloc64( p->sz ); - if( pOut ) memcpy(pOut, p->aData, p->sz); + pOut = sqlite3_malloc64( pStore->sz ); + if( pOut ) memcpy(pOut, pStore->aData, pStore->sz); } return pOut; } @@ -595,15 +833,16 @@ int sqlite3_deserialize( if( p==0 ){ rc = SQLITE_ERROR; }else{ - p->aData = pData; + MemStore *pStore = p->pStore; + pStore->aData = pData; pData = 0; - p->sz = szDb; - p->szAlloc = szBuf; - p->szMax = szBuf; - if( p->szMaxszMax = sqlite3GlobalConfig.mxMemdbSize; + pStore->sz = szDb; + pStore->szAlloc = szBuf; + pStore->szMax = szBuf; + if( pStore->szMaxszMax = sqlite3GlobalConfig.mxMemdbSize; } - p->mFlags = mFlags; + pStore->mFlags = mFlags; rc = SQLITE_OK; } @@ -622,7 +861,9 @@ int sqlite3_deserialize( */ int sqlite3MemdbInit(void){ sqlite3_vfs *pLower = sqlite3_vfs_find(0); - int sz = pLower->szOsFile; + unsigned int sz; + if( NEVER(pLower==0) ) return SQLITE_ERROR; + sz = pLower->szOsFile; memdb_vfs.pAppData = pLower; /* The following conditional can only be true when compiled for ** Windows x86 and SQLITE_MAX_MMAP_SIZE=0. We always leave @@ -632,4 +873,4 @@ int sqlite3MemdbInit(void){ memdb_vfs.szOsFile = sz; return sqlite3_vfs_register(&memdb_vfs, 0); } -#endif /* SQLITE_ENABLE_DESERIALIZE */ +#endif /* SQLITE_OMIT_DESERIALIZE */ diff --git a/src/memjournal.c b/src/memjournal.c index 4811f2d8d..598d5cc02 100644 --- a/src/memjournal.c +++ b/src/memjournal.c @@ -70,7 +70,6 @@ struct MemJournal { int nChunkSize; /* In-memory chunk-size */ int nSpill; /* Bytes of data before flushing */ - int nSize; /* Bytes of data currently in memory */ FileChunk *pFirst; /* Head of in-memory chunk-list */ FilePoint endpoint; /* Pointer to the end of the file */ FilePoint readpoint; /* Pointer to the end of the last xRead() */ @@ -131,14 +130,13 @@ static int memjrnlRead( /* ** Free the list of FileChunk structures headed at MemJournal.pFirst. */ -static void memjrnlFreeChunks(MemJournal *p){ +static void memjrnlFreeChunks(FileChunk *pFirst){ FileChunk *pIter; FileChunk *pNext; - for(pIter=p->pFirst; pIter; pIter=pNext){ + for(pIter=pFirst; pIter; pIter=pNext){ pNext = pIter->pNext; sqlite3_free(pIter); } - p->pFirst = 0; } /* @@ -165,7 +163,7 @@ static int memjrnlCreateFile(MemJournal *p){ } if( rc==SQLITE_OK ){ /* No error has occurred. Free the in-memory buffers. */ - memjrnlFreeChunks(©); + memjrnlFreeChunks(copy.pFirst); } } if( rc!=SQLITE_OK ){ @@ -248,7 +246,6 @@ static int memjrnlWrite( nWrite -= iSpace; p->endpoint.iOffset += iSpace; } - p->nSize = iAmt + iOfst; } } @@ -256,19 +253,29 @@ static int memjrnlWrite( } /* -** Truncate the file. -** -** If the journal file is already on disk, truncate it there. Or, if it -** is still in main memory but is being truncated to zero bytes in size, -** ignore +** Truncate the in-memory file. */ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ MemJournal *p = (MemJournal *)pJfd; - if( ALWAYS(size==0) ){ - memjrnlFreeChunks(p); - p->nSize = 0; - p->endpoint.pChunk = 0; - p->endpoint.iOffset = 0; + assert( p->endpoint.pChunk==0 || p->endpoint.pChunk->pNext==0 ); + if( sizeendpoint.iOffset ){ + FileChunk *pIter = 0; + if( size==0 ){ + memjrnlFreeChunks(p->pFirst); + p->pFirst = 0; + }else{ + i64 iOff = p->nChunkSize; + for(pIter=p->pFirst; ALWAYS(pIter) && iOff<=size; pIter=pIter->pNext){ + iOff += p->nChunkSize; + } + if( ALWAYS(pIter) ){ + memjrnlFreeChunks(pIter->pNext); + pIter->pNext = 0; + } + } + + p->endpoint.pChunk = pIter; + p->endpoint.iOffset = size; p->readpoint.pChunk = 0; p->readpoint.iOffset = 0; } @@ -280,7 +287,7 @@ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ */ static int memjrnlClose(sqlite3_file *pJfd){ MemJournal *p = (MemJournal *)pJfd; - memjrnlFreeChunks(p); + memjrnlFreeChunks(p->pFirst); return SQLITE_OK; } diff --git a/src/os.c b/src/os.c index a1a276f43..f1798ff96 100644 --- a/src/os.c +++ b/src/os.c @@ -129,6 +129,8 @@ int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ #ifdef SQLITE_TEST if( op!=SQLITE_FCNTL_COMMIT_PHASETWO && op!=SQLITE_FCNTL_LOCK_TIMEOUT + && op!=SQLITE_FCNTL_CKPT_DONE + && op!=SQLITE_FCNTL_CKPT_START ){ /* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite ** is using a regular VFS, it is called after the corresponding @@ -139,7 +141,12 @@ int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ ** The core must call OsFileControl() though, not OsFileControlHint(), ** as if a custom VFS (e.g. zipvfs) returns an error here, it probably ** means the commit really has failed and an error should be returned - ** to the user. */ + ** to the user. + ** + ** The CKPT_DONE and CKPT_START file-controls are write-only signals + ** to the cksumvfs. Their return code is meaningless and is ignored + ** by the SQLite core, so there is no point in simulating OOMs for them. + */ DO_OS_MALLOC_TEST(id); } #endif @@ -222,7 +229,7 @@ int sqlite3OsOpen( int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ DO_OS_MALLOC_TEST(0); assert( dirSync==0 || dirSync==1 ); - return pVfs->xDelete(pVfs, zPath, dirSync); + return pVfs->xDelete!=0 ? pVfs->xDelete(pVfs, zPath, dirSync) : SQLITE_OK; } int sqlite3OsAccess( sqlite3_vfs *pVfs, @@ -245,6 +252,8 @@ int sqlite3OsFullPathname( } #ifndef SQLITE_OMIT_LOAD_EXTENSION void *sqlite3OsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + assert( zPath!=0 ); + assert( strlen(zPath)<=SQLITE_MAX_PATHLEN ); /* tag-20210611-1 */ return pVfs->xDlOpen(pVfs, zPath); } void sqlite3OsDlError(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ diff --git a/src/os.h b/src/os.h index f841d6bab..1a8eff329 100644 --- a/src/os.h +++ b/src/os.h @@ -33,6 +33,12 @@ # define SET_FULLSYNC(x,y) #endif +/* Maximum pathname length. Note: FILENAME_MAX defined by stdio.h +*/ +#ifndef SQLITE_MAX_PATHLEN +# define SQLITE_MAX_PATHLEN FILENAME_MAX +#endif + /* ** The default size of a disk sector */ diff --git a/src/os_unix.c b/src/os_unix.c index a688ed270..d45fdabab 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -3951,6 +3951,7 @@ static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){ /* Forward declaration */ static int unixGetTempname(int nBuf, char *zBuf); +static int unixFcntlExternalReader(unixFile*, int*); /* ** Information and control of an open file handle. @@ -4067,6 +4068,10 @@ 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_EXTERNAL_READER: { + return unixFcntlExternalReader((unixFile*)id, (int*)pArg); + } } return SQLITE_NOTFOUND; } @@ -4312,6 +4317,40 @@ struct unixShm { #define UNIX_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ #define UNIX_SHM_DMS (UNIX_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ +/* +** Use F_GETLK to check whether or not there are any readers with open +** wal-mode transactions in other processes on database file pFile. If +** no error occurs, return SQLITE_OK and set (*piOut) to 1 if there are +** such transactions, or 0 otherwise. If an error occurs, return an +** SQLite error code. The final value of *piOut is undefined in this +** case. +*/ +static int unixFcntlExternalReader(unixFile *pFile, int *piOut){ + int rc = SQLITE_OK; + *piOut = 0; + if( pFile->pShm){ + unixShmNode *pShmNode = pFile->pShm->pShmNode; + struct flock f; + + memset(&f, 0, sizeof(f)); + f.l_type = F_WRLCK; + f.l_whence = SEEK_SET; + f.l_start = UNIX_SHM_BASE + 3; + f.l_len = SQLITE_SHM_NLOCK - 3; + + sqlite3_mutex_enter(pShmNode->pShmMutex); + if( osFcntl(pShmNode->hShm, F_GETLK, &f)<0 ){ + rc = SQLITE_IOERR_LOCK; + }else{ + *piOut = (f.l_type!=F_UNLCK); + } + sqlite3_mutex_leave(pShmNode->pShmMutex); + } + + return rc; +} + + /* ** Apply posix advisory locks for all bytes from ofst through ofst+n-1. ** @@ -6361,7 +6400,8 @@ static int unixBackupDir(const char *z, int *pJ){ int j = *pJ; int i; if( j<=0 ) return 0; - for(i=j-1; ALWAYS(i>0) && z[i-1]!='/'; i--){} + for(i=j-1; i>0 && z[i-1]!='/'; i--){} + if( i==0 ) return 0; if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0; *pJ = i-1; return 1; @@ -8027,6 +8067,25 @@ int sqlite3_os_init(void){ sqlite3_vfs_register(&aVfs[i], i==0); } unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); + +#ifndef SQLITE_OMIT_WAL + /* Validate lock assumptions */ + assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */ + assert( UNIX_SHM_BASE==120 ); /* Start of locking area */ + /* Locks: + ** WRITE UNIX_SHM_BASE 120 + ** CKPT UNIX_SHM_BASE+1 121 + ** RECOVER UNIX_SHM_BASE+2 122 + ** READ-0 UNIX_SHM_BASE+3 123 + ** READ-1 UNIX_SHM_BASE+4 124 + ** READ-2 UNIX_SHM_BASE+5 125 + ** READ-3 UNIX_SHM_BASE+6 126 + ** READ-4 UNIX_SHM_BASE+7 127 + ** DMS UNIX_SHM_BASE+8 128 + */ + assert( UNIX_SHM_DMS==128 ); /* Byte offset of the deadman-switch */ +#endif + return SQLITE_OK; } diff --git a/src/pager.c b/src/pager.c index a5510e7eb..12a78cde8 100644 --- a/src/pager.c +++ b/src/pager.c @@ -435,6 +435,7 @@ struct PagerSavepoint { Bitvec *pInSavepoint; /* Set of pages in this savepoint */ Pgno nOrig; /* Original number of pages in file */ Pgno iSubRec; /* Index of first record in sub-journal */ + int bTruncateOnRelease; /* If stmt journal may be truncated on RELEASE */ #ifndef SQLITE_OMIT_WAL u32 aWalData[WAL_SAVEPOINT_NDATA]; /* WAL savepoint context */ #endif @@ -1070,6 +1071,9 @@ static int subjRequiresPage(PgHdr *pPg){ for(i=0; inSavepoint; i++){ p = &pPager->aSavepoint[i]; if( p->nOrig>=pgno && 0==sqlite3BitvecTestNotNull(p->pInSavepoint, pgno) ){ + for(i=i+1; inSavepoint; i++){ + pPager->aSavepoint[i].bTruncateOnRelease = 0; + } return 1; } } @@ -3932,7 +3936,8 @@ static void assertTruncateConstraint(Pager *pPager){ ** then continue writing to the database. */ void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){ - assert( pPager->dbSize>=nPage ); + assert( pPager->dbSize>=nPage || CORRUPT_DB ); + testcase( pPager->dbSizeeState>=PAGER_WRITER_CACHEMOD ); pPager->dbSize = nPage; @@ -4660,7 +4665,7 @@ int sqlite3PagerOpen( int rc = SQLITE_OK; /* Return code */ int tempFile = 0; /* True for temp files (incl. in-memory files) */ int memDb = 0; /* True if this is an in-memory file */ -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE int memJM = 0; /* Memory journal mode */ #else # define memJM 0 @@ -4864,7 +4869,7 @@ int sqlite3PagerOpen( int fout = 0; /* VFS flags returned by xOpen() */ rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); assert( !memDb ); -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE memJM = (fout&SQLITE_OPEN_MEMORY)!=0; #endif readOnly = (fout&SQLITE_OPEN_READONLY)!=0; @@ -5832,7 +5837,7 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){ assert( pPager->eState>=PAGER_READER && pPager->eStatesubjInMemory = (u8)subjInMemory; - if( ALWAYS(pPager->eState==PAGER_READER) ){ + if( pPager->eState==PAGER_READER ){ assert( pPager->pInJournal==0 ); if( pagerUseWal(pPager) ){ @@ -6848,6 +6853,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){ } aNew[ii].iSubRec = pPager->nSubRec; aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize); + aNew[ii].bTruncateOnRelease = 1; if( !aNew[ii].pInSavepoint ){ return SQLITE_NOMEM_BKPT; } @@ -6929,13 +6935,15 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ /* If this is a release of the outermost savepoint, truncate ** the sub-journal to zero bytes in size. */ if( op==SAVEPOINT_RELEASE ){ - if( nNew==0 && isOpen(pPager->sjfd) ){ + PagerSavepoint *pRel = &pPager->aSavepoint[nNew]; + if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){ /* Only truncate if it is an in-memory sub-journal. */ if( sqlite3JournalIsInMemory(pPager->sjfd) ){ - rc = sqlite3OsTruncate(pPager->sjfd, 0); + i64 sz = (pPager->pageSize+4)*pRel->iSubRec; + rc = sqlite3OsTruncate(pPager->sjfd, sz); assert( rc==SQLITE_OK ); } - pPager->nSubRec = 0; + pPager->nSubRec = pRel->iSubRec; } } /* Else this is a rollback operation, playback the specified savepoint. diff --git a/src/parse.y b/src/parse.y index d3ec2b3da..359b02bc4 100644 --- a/src/parse.y +++ b/src/parse.y @@ -193,7 +193,7 @@ ifnotexists(A) ::= . {A = 0;} ifnotexists(A) ::= IF NOT EXISTS. {A = 1;} %type temp {int} %ifndef SQLITE_OMIT_TEMPDB -temp(A) ::= TEMP. {A = 1;} +temp(A) ::= TEMP. {A = pParse->db->init.busy==0;} %endif SQLITE_OMIT_TEMPDB temp(A) ::= . {A = 0;} create_table_args ::= LP columnlist conslist_opt(X) RP(E) table_options(F). { @@ -250,6 +250,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} %ifndef SQLITE_OMIT_GENERATED_COLUMNS GENERATED ALWAYS %endif + MATERIALIZED REINDEX RENAME CTIME_KW IF . %wildcard ANY. @@ -496,11 +497,21 @@ cmd ::= select(X). { static void parserDoubleLinkSelect(Parse *pParse, Select *p){ assert( p!=0 ); if( p->pPrior ){ - Select *pNext = 0, *pLoop; - int mxSelect, cnt = 0; - for(pLoop=p; pLoop; pNext=pLoop, pLoop=pLoop->pPrior, cnt++){ + Select *pNext = 0, *pLoop = p; + int mxSelect, cnt = 1; + while(1){ pLoop->pNext = pNext; pLoop->selFlags |= SF_Compound; + pNext = pLoop; + pLoop = pLoop->pPrior; + if( pLoop==0 ) break; + cnt++; + if( pLoop->pOrderBy || pLoop->pLimit ){ + sqlite3ErrorMsg(pParse,"%s clause should come after %s not before", + pLoop->pOrderBy!=0 ? "ORDER BY" : "LIMIT", + sqlite3SelectOpName(pNext->op)); + break; + } } if( (p->selFlags & SF_MultiValue)==0 && (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 && @@ -510,29 +521,25 @@ cmd ::= select(X). { } } } -} -%ifndef SQLITE_OMIT_CTE -select(A) ::= WITH wqlist(W) selectnowith(X). { - Select *p = X; - if( p ){ - p->pWith = W; - parserDoubleLinkSelect(pParse, p); - }else{ - sqlite3WithDelete(pParse->db, W); - } - A = p; -} -select(A) ::= WITH RECURSIVE wqlist(W) selectnowith(X). { - Select *p = X; - if( p ){ - p->pWith = W; - parserDoubleLinkSelect(pParse, p); - }else{ - sqlite3WithDelete(pParse->db, W); + /* Attach a With object describing the WITH clause to a Select + ** object describing the query for which the WITH clause is a prefix. + */ + static Select *attachWithToSelect(Parse *pParse, Select *pSelect, With *pWith){ + if( pSelect ){ + pSelect->pWith = pWith; + parserDoubleLinkSelect(pParse, pSelect); + }else{ + sqlite3WithDelete(pParse->db, pWith); + } + return pSelect; } - A = p; } + +%ifndef SQLITE_OMIT_CTE +select(A) ::= WITH wqlist(W) selectnowith(X). {A = attachWithToSelect(pParse,X,W);} +select(A) ::= WITH RECURSIVE wqlist(W) selectnowith(X). + {A = attachWithToSelect(pParse,X,W);} %endif /* SQLITE_OMIT_CTE */ select(A) ::= selectnowith(X). { Select *p = X; @@ -699,8 +706,8 @@ seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) }else if( F->nSrc==1 ){ A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,0,N,U); if( A ){ - struct SrcList_item *pNew = &A->a[A->nSrc-1]; - struct SrcList_item *pOld = F->a; + SrcItem *pNew = &A->a[A->nSrc-1]; + SrcItem *pOld = F->a; pNew->zName = pOld->zName; pNew->zDatabase = pOld->zDatabase; pNew->pSelect = pOld->pSelect; @@ -868,7 +875,7 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y). /////////////////////////// The DELETE statement ///////////////////////////// // %if SQLITE_ENABLE_UPDATE_DELETE_LIMIT || SQLITE_UDL_CAPABLE_PARSER -cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W) +cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W) orderby_opt(O) limit_opt(L). { sqlite3SrcListIndexedBy(pParse, X, &I); #ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT @@ -881,7 +888,7 @@ cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W) sqlite3DeleteFrom(pParse,X,W,O,L); } %else -cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W). { +cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W). { sqlite3SrcListIndexedBy(pParse, X, &I); sqlite3DeleteFrom(pParse,X,W,0,0); } @@ -889,15 +896,23 @@ cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W). { %type where_opt {Expr*} %destructor where_opt {sqlite3ExprDelete(pParse->db, $$);} +%type where_opt_ret {Expr*} +%destructor where_opt_ret {sqlite3ExprDelete(pParse->db, $$);} where_opt(A) ::= . {A = 0;} where_opt(A) ::= WHERE expr(X). {A = X;} +where_opt_ret(A) ::= . {A = 0;} +where_opt_ret(A) ::= WHERE expr(X). {A = X;} +where_opt_ret(A) ::= RETURNING selcollist(X). + {sqlite3AddReturning(pParse,X); A = 0;} +where_opt_ret(A) ::= WHERE expr(X) RETURNING selcollist(Y). + {sqlite3AddReturning(pParse,Y); A = X;} ////////////////////////// The UPDATE command //////////////////////////////// // %if SQLITE_ENABLE_UPDATE_DELETE_LIMIT || SQLITE_UDL_CAPABLE_PARSER cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F) - where_opt(W) orderby_opt(O) limit_opt(L). { + where_opt_ret(W) orderby_opt(O) limit_opt(L). { sqlite3SrcListIndexedBy(pParse, X, &I); X = sqlite3SrcListAppendList(pParse, X, F); sqlite3ExprListCheckLength(pParse,Y,"set list"); @@ -912,7 +927,7 @@ cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F) } %else cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F) - where_opt(W). { + where_opt_ret(W). { sqlite3SrcListIndexedBy(pParse, X, &I); sqlite3ExprListCheckLength(pParse,Y,"set list"); X = sqlite3SrcListAppendList(pParse, X, F); @@ -946,7 +961,7 @@ cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) select(S) upsert(U). { sqlite3Insert(pParse, X, S, F, R, U); } -cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES. +cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES returning. { sqlite3Insert(pParse, X, 0, F, R, 0); } @@ -959,13 +974,19 @@ cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES. // avoid unreachable code. //%destructor upsert {sqlite3UpsertDelete(pParse->db,$$);} upsert(A) ::= . { A = 0; } +upsert(A) ::= RETURNING selcollist(X). { A = 0; sqlite3AddReturning(pParse,X); } upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) - DO UPDATE SET setlist(Z) where_opt(W). - { A = sqlite3UpsertNew(pParse->db,T,TW,Z,W);} -upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING. - { A = sqlite3UpsertNew(pParse->db,T,TW,0,0); } -upsert(A) ::= ON CONFLICT DO NOTHING. - { A = sqlite3UpsertNew(pParse->db,0,0,0,0); } + DO UPDATE SET setlist(Z) where_opt(W) upsert(N). + { A = sqlite3UpsertNew(pParse->db,T,TW,Z,W,N);} +upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING upsert(N). + { A = sqlite3UpsertNew(pParse->db,T,TW,0,0,N); } +upsert(A) ::= ON CONFLICT DO NOTHING returning. + { A = sqlite3UpsertNew(pParse->db,0,0,0,0,0); } +upsert(A) ::= ON CONFLICT DO UPDATE SET setlist(Z) where_opt(W) returning. + { A = sqlite3UpsertNew(pParse->db,0,0,Z,W,0);} + +returning ::= RETURNING selcollist(X). {sqlite3AddReturning(pParse,X);} +returning ::= . %type insert_cmd {int} insert_cmd(A) ::= INSERT orconf(R). {A = R;} @@ -1607,6 +1628,10 @@ cmd ::= ALTER TABLE add_column_fullname Y.n = (int)(pParse->sLastToken.z-Y.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &Y); } +cmd ::= ALTER TABLE fullname(X) DROP kwcolumn_opt nm(Y). { + sqlite3AlterDropColumn(pParse, X, &Y); +} + add_column_fullname ::= fullname(X). { disableLookaside(pParse); sqlite3AlterBeginAddColumn(pParse, X); @@ -1644,17 +1669,26 @@ anylist ::= anylist ANY. //////////////////////// COMMON TABLE EXPRESSIONS //////////////////////////// %type wqlist {With*} %destructor wqlist {sqlite3WithDelete(pParse->db, $$);} +%type wqitem {Cte*} +// %destructor wqitem {sqlite3CteDelete(pParse->db, $$);} // not reachable with ::= . %ifndef SQLITE_OMIT_CTE with ::= WITH wqlist(W). { sqlite3WithPush(pParse, W, 1); } with ::= WITH RECURSIVE wqlist(W). { sqlite3WithPush(pParse, W, 1); } -wqlist(A) ::= nm(X) eidlist_opt(Y) AS LP select(Z) RP. { - A = sqlite3WithAdd(pParse, 0, &X, Y, Z); /*A-overwrites-X*/ +%type wqas {u8} +wqas(A) ::= AS. {A = M10d_Any;} +wqas(A) ::= AS MATERIALIZED. {A = M10d_Yes;} +wqas(A) ::= AS NOT MATERIALIZED. {A = M10d_No;} +wqitem(A) ::= nm(X) eidlist_opt(Y) wqas(M) LP select(Z) RP. { + A = sqlite3CteNew(pParse, &X, Y, Z, M); /*A-overwrites-X*/ +} +wqlist(A) ::= wqitem(X). { + A = sqlite3WithAdd(pParse, 0, X); /*A-overwrites-X*/ } -wqlist(A) ::= wqlist(A) COMMA nm(X) eidlist_opt(Y) AS LP select(Z) RP. { - A = sqlite3WithAdd(pParse, A, &X, Y, Z); +wqlist(A) ::= wqlist(A) COMMA wqitem(X). { + A = sqlite3WithAdd(pParse, A, X); } %endif SQLITE_OMIT_CTE @@ -1767,7 +1801,11 @@ frame_exclude(A) ::= GROUP|TIES(X). {A = @X; /*A-overwrites-X*/} window_clause(A) ::= WINDOW windowdefn_list(B). { A = B; } filter_over(A) ::= filter_clause(F) over_clause(O). { - O->pFilter = F; + if( O ){ + O->pFilter = F; + }else{ + sqlite3ExprDelete(pParse->db, F); + } A = O; } filter_over(A) ::= over_clause(O). { @@ -1817,6 +1855,7 @@ filter_clause(A) ::= FILTER LP WHERE expr(X) RP. { A = X; } IF_NULL_ROW /* the if-null-row operator */ ASTERISK /* The "*" in count(*) and similar */ SPAN /* The span operator */ + ERROR /* An expression containing an error */ . /* There must be no more than 255 tokens defined above. If this grammar ** is extended with new rules and tokens, they must either be so few in diff --git a/src/pcache1.c b/src/pcache1.c index ed762ebf7..3eae6b63c 100644 --- a/src/pcache1.c +++ b/src/pcache1.c @@ -461,6 +461,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){ p->page.pExtra = &p[1]; p->isBulkLocal = 0; p->isAnchor = 0; + p->pLruPrev = 0; /* Initializing this saves a valgrind error */ } (*pCache->pnPurgeable)++; return p; diff --git a/src/pragma.c b/src/pragma.c index 7be0f7f25..84f29c2fd 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1969,7 +1969,7 @@ void sqlite3Pragma( ** Checkpoint the database. */ case PragTyp_WAL_CHECKPOINT: { - int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED); + int iBt = (pId2->z?iDb:SQLITE_MAX_DB); int eMode = SQLITE_CHECKPOINT_PASSIVE; if( zRight ){ if( sqlite3StrICmp(zRight, "full")==0 ){ diff --git a/src/prepare.c b/src/prepare.c index 13fd1d33b..6dcb3842e 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -21,7 +21,7 @@ */ static void corruptSchema( InitData *pData, /* Initialization context */ - const char *zObj, /* Object being parsed at the point of error */ + char **azObj, /* Type and name of object being parsed */ const char *zExtra /* Error information */ ){ sqlite3 *db = pData->db; @@ -29,14 +29,18 @@ static void corruptSchema( pData->rc = SQLITE_NOMEM_BKPT; }else if( pData->pzErrMsg[0]!=0 ){ /* A error message has already been generated. Do not overwrite it */ - }else if( pData->mInitFlags & INITFLAG_AlterTable ){ - *pData->pzErrMsg = sqlite3DbStrDup(db, zExtra); + }else if( pData->mInitFlags & (INITFLAG_AlterRename|INITFLAG_AlterDrop) ){ + *pData->pzErrMsg = sqlite3MPrintf(db, + "error in %s %s after %s: %s", azObj[0], azObj[1], + (pData->mInitFlags & INITFLAG_AlterRename) ? "rename" : "drop column", + zExtra + ); pData->rc = SQLITE_ERROR; }else if( db->flags & SQLITE_WriteSchema ){ pData->rc = SQLITE_CORRUPT_BKPT; }else{ char *z; - if( zObj==0 ) zObj = "?"; + const char *zObj = azObj[1] ? azObj[1] : "?"; z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj); if( zExtra && zExtra[0] ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra); *pData->pzErrMsg = z; @@ -92,21 +96,28 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ UNUSED_PARAMETER2(NotUsed, argc); assert( sqlite3_mutex_held(db->mutex) ); db->mDbFlags |= DBFLAG_EncodingFixed; + if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ pData->nInitRow++; if( db->mallocFailed ){ - corruptSchema(pData, argv[1], 0); + corruptSchema(pData, argv, 0); return 1; } assert( iDb>=0 && iDbnDb ); - if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ if( argv[3]==0 ){ - corruptSchema(pData, argv[1], 0); - }else if( sqlite3_strnicmp(argv[4],"create ",7)==0 ){ + corruptSchema(pData, argv, 0); + }else if( argv[4] + && 'c'==sqlite3UpperToLower[(unsigned char)argv[4][0]] + && 'r'==sqlite3UpperToLower[(unsigned char)argv[4][1]] ){ /* Call the parser to process a CREATE TABLE, INDEX or VIEW. ** But because db->init.busy is set to 1, no VDBE code is generated ** or executed. All the parser does is build the internal data ** structures that describe the table, index, or view. + ** + ** No other valid SQL statement, other than the variable CREATE statements, + ** can begin with the letters "C" and "R". Thus, it is not possible run + ** any other kind of statement while parsing the schema, even a corrupt + ** schema. */ int rc; u8 saved_iDb = db->init.iDb; @@ -119,7 +130,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ || (db->init.newTnum>pData->mxPage && pData->mxPage>0) ){ if( sqlite3Config.bExtraSchemaChecks ){ - corruptSchema(pData, argv[1], "invalid rootpage"); + corruptSchema(pData, argv, "invalid rootpage"); } } db->init.orphanTrigger = 0; @@ -138,13 +149,13 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ if( rc==SQLITE_NOMEM ){ sqlite3OomFault(db); }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){ - corruptSchema(pData, argv[1], sqlite3_errmsg(db)); + corruptSchema(pData, argv, sqlite3_errmsg(db)); } } } sqlite3_finalize(pStmt); }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){ - corruptSchema(pData, argv[1], 0); + corruptSchema(pData, argv, 0); }else{ /* If the SQL column is blank it means this is an index that ** was created to be the PRIMARY KEY or to fulfill a UNIQUE @@ -155,7 +166,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ Index *pIndex; pIndex = sqlite3FindIndex(db, argv[1], db->aDb[iDb].zDbSName); if( pIndex==0 ){ - corruptSchema(pData, argv[1], "orphan index"); + corruptSchema(pData, argv, "orphan index"); }else if( sqlite3GetUInt32(argv[3],&pIndex->tnum)==0 || pIndex->tnum<2 @@ -163,7 +174,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ || sqlite3IndexHasDuplicateRootPage(pIndex) ){ if( sqlite3Config.bExtraSchemaChecks ){ - corruptSchema(pData, argv[1], "invalid rootpage"); + corruptSchema(pData, argv, "invalid rootpage"); } } } @@ -366,18 +377,22 @@ int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){ } #endif } + assert( pDb == &(db->aDb[iDb]) ); if( db->mallocFailed ){ rc = SQLITE_NOMEM_BKPT; sqlite3ResetAllSchemasOfConnection(db); - } + pDb = &db->aDb[iDb]; + }else if( rc==SQLITE_OK || (db->flags&SQLITE_NoSchemaError)){ - /* Black magic: If the SQLITE_NoSchemaError flag is set, then consider - ** the schema loaded, even if errors occurred. In this situation the - ** current sqlite3_prepare() operation will fail, but the following one - ** will attempt to compile the supplied statement against whatever subset - ** of the schema was loaded before the error occurred. The primary - ** purpose of this is to allow access to the sqlite_schema table - ** even when its contents have been corrupted. + /* Hack: If the SQLITE_NoSchemaError flag is set, then consider + ** the schema loaded, even if errors (other than OOM) occurred. In + ** this situation the current sqlite3_prepare() operation will fail, + ** but the following one will attempt to compile the supplied statement + ** against whatever subset of the schema was loaded before the error + ** occurred. + ** + ** The primary purpose of this is to allow access to the sqlite_schema + ** table even when its contents have been corrupted. */ DbSetProperty(db, iDb, DB_SchemaLoaded); rc = SQLITE_OK; @@ -487,6 +502,7 @@ static void schemaIsValid(Parse *pParse){ rc = sqlite3BtreeBeginTrans(pBt, 0, 0); if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ sqlite3OomFault(db); + pParse->rc = SQLITE_NOMEM; } if( rc!=SQLITE_OK ) return; openedTransaction = 1; @@ -543,28 +559,21 @@ int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){ return i; } -/* -** Deallocate a single AggInfo object -*/ -static void agginfoFree(sqlite3 *db, AggInfo *p){ - sqlite3DbFree(db, p->aCol); - sqlite3DbFree(db, p->aFunc); - sqlite3DbFree(db, p); -} - /* ** Free all memory allocations in the pParse object */ void sqlite3ParserReset(Parse *pParse){ sqlite3 *db = pParse->db; - AggInfo *pThis = pParse->pAggList; - while( pThis ){ - AggInfo *pNext = pThis->pNext; - agginfoFree(db, pThis); - pThis = pNext; + while( pParse->pCleanup ){ + ParseCleanup *pCleanup = pParse->pCleanup; + pParse->pCleanup = pCleanup->pNext; + pCleanup->xCleanup(db, pCleanup->pPtr); + sqlite3DbFreeNN(db, pCleanup); } sqlite3DbFree(db, pParse->aLabel); - sqlite3ExprListDelete(db, pParse->pConstExpr); + if( pParse->pConstExpr ){ + sqlite3ExprListDelete(db, pParse->pConstExpr); + } if( db ){ assert( db->lookaside.bDisable >= pParse->disableLookaside ); db->lookaside.bDisable -= pParse->disableLookaside; @@ -573,6 +582,55 @@ void sqlite3ParserReset(Parse *pParse){ pParse->disableLookaside = 0; } +/* +** Add a new cleanup operation to a Parser. The cleanup should happen when +** the parser object is destroyed. But, beware: the cleanup might happen +** immediately. +** +** Use this mechanism for uncommon cleanups. There is a higher setup +** cost for this mechansim (an extra malloc), so it should not be used +** for common cleanups that happen on most calls. But for less +** common cleanups, we save a single NULL-pointer comparison in +** sqlite3ParserReset(), which reduces the total CPU cycle count. +** +** If a memory allocation error occurs, then the cleanup happens immediately. +** When either SQLITE_DEBUG or SQLITE_COVERAGE_TEST are defined, the +** pParse->earlyCleanup flag is set in that case. Calling code show verify +** that test cases exist for which this happens, to guard against possible +** use-after-free errors following an OOM. The preferred way to do this is +** to immediately follow the call to this routine with: +** +** testcase( pParse->earlyCleanup ); +** +** This routine returns a copy of its pPtr input (the third parameter) +** except if an early cleanup occurs, in which case it returns NULL. So +** another way to check for early cleanup is to check the return value. +** Or, stop using the pPtr parameter with this call and use only its +** return value thereafter. Something like this: +** +** pObj = sqlite3ParserAddCleanup(pParse, destructor, pObj); +*/ +void *sqlite3ParserAddCleanup( + Parse *pParse, /* Destroy when this Parser finishes */ + void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */ + void *pPtr /* Pointer to object to be cleaned up */ +){ + ParseCleanup *pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup)); + if( pCleanup ){ + pCleanup->pNext = pParse->pCleanup; + pParse->pCleanup = pCleanup; + pCleanup->pPtr = pPtr; + pCleanup->xCleanup = xCleanup; + }else{ + xCleanup(pParse->db, pPtr); + pPtr = 0; +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) + pParse->earlyCleanup = 1; +#endif + } + return pPtr; +} + /* ** Compile the UTF-8 encoded SQL statement zSql into a statement handle. */ @@ -671,12 +729,6 @@ static int sqlite3Prepare( } assert( 0==sParse.nQueryLoop ); - if( sParse.rc==SQLITE_DONE ){ - sParse.rc = SQLITE_OK; - } - if( sParse.checkSchema ){ - schemaIsValid(&sParse); - } if( pzTail ){ *pzTail = sParse.zTail; } @@ -686,21 +738,30 @@ static int sqlite3Prepare( } if( db->mallocFailed ){ sParse.rc = SQLITE_NOMEM_BKPT; + sParse.checkSchema = 0; } - rc = sParse.rc; - if( rc!=SQLITE_OK ){ - if( sParse.pVdbe ) sqlite3VdbeFinalize(sParse.pVdbe); - assert(!(*ppStmt)); + if( sParse.rc!=SQLITE_OK && sParse.rc!=SQLITE_DONE ){ + if( sParse.checkSchema ){ + schemaIsValid(&sParse); + } + if( sParse.pVdbe ){ + sqlite3VdbeFinalize(sParse.pVdbe); + } + assert( 0==(*ppStmt) ); + rc = sParse.rc; + if( zErrMsg ){ + sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg); + sqlite3DbFree(db, zErrMsg); + }else{ + sqlite3Error(db, rc); + } }else{ + assert( zErrMsg==0 ); *ppStmt = (sqlite3_stmt*)sParse.pVdbe; + rc = SQLITE_OK; + sqlite3ErrorClear(db); } - if( zErrMsg ){ - sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg); - sqlite3DbFree(db, zErrMsg); - }else{ - sqlite3Error(db, rc); - } /* Delete any TriggerPrg structures allocated while parsing this statement. */ while( sParse.pTriggerPrg ){ diff --git a/src/printf.c b/src/printf.c index f78d3bbb1..f663e1b1e 100644 --- a/src/printf.c +++ b/src/printf.c @@ -29,7 +29,7 @@ #define etSQLESCAPE2 10 /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ #define etTOKEN 11 /* a pointer to a Token structure */ -#define etSRCLIST 12 /* a pointer to a SrcList */ +#define etSRCITEM 12 /* a pointer to a SrcItem */ #define etPOINTER 13 /* The %p conversion */ #define etSQLESCAPE3 14 /* %w -> Strings with '\"' doubled */ #define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ @@ -95,10 +95,16 @@ static const et_info fmtinfo[] = { /* All the rest are undocumented and are for internal use only */ { 'T', 0, 0, etTOKEN, 0, 0 }, - { 'S', 0, 0, etSRCLIST, 0, 0 }, + { 'S', 0, 0, etSRCITEM, 0, 0 }, { 'r', 10, 1, etORDINAL, 0, 0 }, }; +/* Notes: +** +** %S Takes a pointer to SrcItem. Shows name or database.name +** %!S Like %S but prefer the zName over the zAlias +*/ + /* Floating point constants used for rounding */ static const double arRound[] = { 5.0e-01, 5.0e-02, 5.0e-03, 5.0e-04, 5.0e-05, @@ -853,21 +859,24 @@ void sqlite3_str_vappendf( length = width = 0; break; } - case etSRCLIST: { - SrcList *pSrc; - int k; - struct SrcList_item *pItem; + case etSRCITEM: { + SrcItem *pItem; if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; - pSrc = va_arg(ap, SrcList*); - k = va_arg(ap, int); - pItem = &pSrc->a[k]; + pItem = va_arg(ap, SrcItem*); assert( bArgList==0 ); - assert( k>=0 && knSrc ); - if( pItem->zDatabase ){ - sqlite3_str_appendall(pAccum, pItem->zDatabase); - sqlite3_str_append(pAccum, ".", 1); + if( pItem->zAlias && !flag_altform2 ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else if( pItem->zName ){ + if( pItem->zDatabase ){ + sqlite3_str_appendall(pAccum, pItem->zDatabase); + sqlite3_str_append(pAccum, ".", 1); + } + sqlite3_str_appendall(pAccum, pItem->zName); + }else if( pItem->zAlias ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else if( ALWAYS(pItem->pSelect) ){ + sqlite3_str_appendf(pAccum, "SUBQUERY %u", pItem->pSelect->selId); } - sqlite3_str_appendall(pAccum, pItem->zName); length = width = 0; break; } @@ -921,7 +930,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ }else{ char *zOld = isMalloced(p) ? p->zText : 0; i64 szNew = p->nChar; - szNew += N + 1; + szNew += (sqlite3_int64)N + 1; if( szNew+p->nChar<=p->mxAlloc ){ /* Force exponential buffer size growth as long as it does not overflow, ** to avoid having to call this routine too often */ diff --git a/src/random.c b/src/random.c index d4ae77c43..87f9e2cec 100644 --- a/src/random.c +++ b/src/random.c @@ -76,11 +76,16 @@ void sqlite3_randomness(int N, void *pBuf){ ** number generator) not as an encryption device. */ if( !wsdPrng.isInit ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); int i; char k[256]; wsdPrng.j = 0; wsdPrng.i = 0; - sqlite3OsRandomness(sqlite3_vfs_find(0), 256, k); + if( NEVER(pVfs==0) ){ + memset(k, 0, sizeof(k)); + }else{ + sqlite3OsRandomness(pVfs, 256, k); + } for(i=0; i<256; i++){ wsdPrng.s[i] = (u8)i; } diff --git a/src/resolve.c b/src/resolve.c index b55bdc418..93e10b6e4 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -70,7 +70,6 @@ static void resolveAlias( ExprList *pEList, /* A result set */ int iCol, /* A column in the result set. 0..pEList->nExpr-1 */ Expr *pExpr, /* Transform this into an alias to the result set */ - const char *zType, /* "GROUP" or "ORDER" or "" */ int nSubquery /* Number of subqueries that the label is moving */ ){ Expr *pOrig; /* The iCol-th column of the result set */ @@ -82,8 +81,11 @@ static void resolveAlias( assert( pOrig!=0 ); db = pParse->db; pDup = sqlite3ExprDup(db, pOrig, 0); - if( pDup!=0 ){ - if( zType[0]!='G' ) incrAggFunctionDepth(pDup, nSubquery); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + pDup = 0; + }else{ + incrAggFunctionDepth(pDup, nSubquery); if( pExpr->op==TK_COLLATE ){ pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken); } @@ -104,15 +106,12 @@ static void resolveAlias( pExpr->flags |= EP_MemToken; } if( ExprHasProperty(pExpr, EP_WinFunc) ){ - if( pExpr->y.pWin!=0 ){ + if( ALWAYS(pExpr->y.pWin!=0) ){ pExpr->y.pWin->pOwner = pExpr; - }else{ - assert( db->mallocFailed ); } } sqlite3DbFree(db, pDup); } - ExprSetProperty(pExpr, EP_Alias); } @@ -247,8 +246,8 @@ static int lookupName( int cntTab = 0; /* Number of matching table names */ int nSubquery = 0; /* How many levels of subquery */ sqlite3 *db = pParse->db; /* The database connection */ - struct SrcList_item *pItem; /* Use for looping over pSrcList items */ - struct SrcList_item *pMatch = 0; /* The matching pSrcList item */ + SrcItem *pItem; /* Use for looping over pSrcList items */ + SrcItem *pMatch = 0; /* The matching pSrcList item */ NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */ @@ -369,25 +368,33 @@ static int lookupName( #if !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT) /* If we have not already resolved the name, then maybe ** it is a new.* or old.* trigger argument reference. Or - ** maybe it is an excluded.* from an upsert. + ** maybe it is an excluded.* from an upsert. Or maybe it is + ** a reference in the RETURNING clause to a table being modified. */ - if( zDb==0 && zTab!=0 && cntTab==0 ){ + if( cnt==0 && zDb==0 ){ pTab = 0; #ifndef SQLITE_OMIT_TRIGGER if( pParse->pTriggerTab!=0 ){ int op = pParse->eTriggerOp; assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); - if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){ + if( pParse->bReturning ){ + if( (pNC->ncFlags & NC_UBaseReg)!=0 + && (zTab==0 || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0) + ){ + pExpr->iTable = op!=TK_DELETE; + pTab = pParse->pTriggerTab; + } + }else if( op!=TK_DELETE && zTab && sqlite3StrICmp("new",zTab) == 0 ){ pExpr->iTable = 1; pTab = pParse->pTriggerTab; - }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){ + }else if( op!=TK_INSERT && zTab && sqlite3StrICmp("old",zTab)==0 ){ pExpr->iTable = 0; pTab = pParse->pTriggerTab; } } #endif /* SQLITE_OMIT_TRIGGER */ #ifndef SQLITE_OMIT_UPSERT - if( (pNC->ncFlags & NC_UUpsert)!=0 ){ + if( (pNC->ncFlags & NC_UUpsert)!=0 && zTab!=0 ){ Upsert *pUpsert = pNC->uNC.pUpsert; if( pUpsert && sqlite3StrICmp("excluded",zTab)==0 ){ pTab = pUpsert->pUpsertSrc->a[0].pTab; @@ -415,6 +422,7 @@ static int lookupName( } if( iColnCol ){ cnt++; + pMatch = 0; #ifndef SQLITE_OMIT_UPSERT if( pExpr->iTable==EXCLUDED_TABLE_NUMBER ){ testcase( iCol==(-1) ); @@ -426,27 +434,32 @@ static int lookupName( pExpr->iTable = pNC->uNC.pUpsert->regData + sqlite3TableColumnToStorage(pTab, iCol); eNewExprOp = TK_REGISTER; - ExprSetProperty(pExpr, EP_Alias); } }else #endif /* SQLITE_OMIT_UPSERT */ { -#ifndef SQLITE_OMIT_TRIGGER - if( iCol<0 ){ - pExpr->affExpr = SQLITE_AFF_INTEGER; - }else if( pExpr->iTable==0 ){ - testcase( iCol==31 ); - testcase( iCol==32 ); - pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<y.pTab = pTab; - pExpr->iColumn = (i16)iCol; - eNewExprOp = TK_TRIGGER; + if( pParse->bReturning ){ + eNewExprOp = TK_REGISTER; + pExpr->iTable = pNC->uNC.iBaseReg + (pTab->nCol+1)*pExpr->iTable + + sqlite3TableColumnToStorage(pTab, iCol) + 1; + }else{ + pExpr->iColumn = (i16)iCol; + eNewExprOp = TK_TRIGGER; +#ifndef SQLITE_OMIT_TRIGGER + if( iCol<0 ){ + pExpr->affExpr = SQLITE_AFF_INTEGER; + }else if( pExpr->iTable==0 ){ + testcase( iCol==31 ); + testcase( iCol==32 ); + pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<ncFlags & NC_UEList)!=0 - && cnt==0 + if( cnt==0 + && (pNC->ncFlags & NC_UEList)!=0 && zTab==0 ){ pEList = pNC->uNC.pEList; @@ -516,7 +529,7 @@ static int lookupName( sqlite3ErrorMsg(pParse, "row value misused"); return WRC_Abort; } - resolveAlias(pParse, pEList, j, pExpr, "", nSubquery); + resolveAlias(pParse, pEList, j, pExpr, nSubquery); cnt = 1; pMatch = 0; assert( zTab==0 && zDb==0 ); @@ -595,7 +608,7 @@ static int lookupName( sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol); } pParse->checkSchema = 1; - pTopNC->nErr++; + pTopNC->nNcErr++; } /* If a column from a table in pSrcList is referenced, then record @@ -618,18 +631,24 @@ static int lookupName( /* Clean up and return */ - sqlite3ExprDelete(db, pExpr->pLeft); - pExpr->pLeft = 0; - sqlite3ExprDelete(db, pExpr->pRight); - pExpr->pRight = 0; + if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + } pExpr->op = eNewExprOp; ExprSetProperty(pExpr, EP_Leaf); lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); - if( !ExprHasProperty(pExpr, EP_Alias) ){ +#ifndef SQLITE_OMIT_AUTHORIZATION + if( pParse->db->xAuth + && (pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER) + ){ sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList); } +#endif /* Increment the nRef value on all name contexts from TopNC up to ** the point where the name matched. */ for(;;){ @@ -651,7 +670,7 @@ static int lookupName( Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){ Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0); if( p ){ - struct SrcList_item *pItem = &pSrc->a[iSrc]; + SrcItem *pItem = &pSrc->a[iSrc]; Table *pTab = p->y.pTab = pItem->pTab; p->iTable = pItem->iCursor; if( p->y.pTab->iPKey==iCol ){ @@ -763,7 +782,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ */ case TK_ROW: { SrcList *pSrcList = pNC->pSrcList; - struct SrcList_item *pItem; + SrcItem *pItem; assert( pSrcList && pSrcList->nSrc>=1 ); pItem = pSrcList->a; pExpr->op = TK_COLUMN; @@ -774,6 +793,47 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ break; } + /* An optimization: Attempt to convert + ** + ** "expr IS NOT NULL" --> "TRUE" + ** "expr IS NULL" --> "FALSE" + ** + ** if we can prove that "expr" is never NULL. Call this the + ** "NOT NULL strength reduction optimization". + ** + ** If this optimization occurs, also restore the NameContext ref-counts + ** to the state they where in before the "column" LHS expression was + ** resolved. This prevents "column" from being counted as having been + ** referenced, which might prevent a SELECT from being erroneously + ** marked as correlated. + */ + case TK_NOTNULL: + case TK_ISNULL: { + int anRef[8]; + NameContext *p; + int i; + for(i=0, p=pNC; p && ipNext, i++){ + anRef[i] = p->nRef; + } + sqlite3WalkExpr(pWalker, pExpr->pLeft); + if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ + if( pExpr->op==TK_NOTNULL ){ + pExpr->u.zToken = "true"; + ExprSetProperty(pExpr, EP_IsTrue); + }else{ + pExpr->u.zToken = "false"; + ExprSetProperty(pExpr, EP_IsFalse); + } + pExpr->op = TK_TRUEFALSE; + for(i=0, p=pNC; p && ipNext, i++){ + p->nRef = anRef[i]; + } + sqlite3ExprDelete(pParse->db, pExpr->pLeft); + pExpr->pLeft = 0; + } + return WRC_Prune; + } + /* A column name: ID ** Or table name and column name: ID.ID ** Or a database, table and column: ID.ID.ID @@ -855,7 +915,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ sqlite3ErrorMsg(pParse, "second argument to likelihood() must be a " "constant between 0.0 and 1.0"); - pNC->nErr++; + pNC->nNcErr++; } }else{ /* EVIDENCE-OF: R-61304-29449 The unlikely(X) function is @@ -877,7 +937,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ if( auth==SQLITE_DENY ){ sqlite3ErrorMsg(pParse, "not authorized to use function: %s", pDef->zName); - pNC->nErr++; + pNC->nNcErr++; } pExpr->op = TK_NULL; return WRC_Prune; @@ -933,7 +993,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ sqlite3ErrorMsg(pParse, "%.*s() may not be used as a window function", nId, zId ); - pNC->nErr++; + pNC->nNcErr++; }else if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) || (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pWin) @@ -946,13 +1006,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ zType = "aggregate"; } sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()",zType,nId,zId); - pNC->nErr++; + pNC->nNcErr++; is_agg = 0; } #else if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){ sqlite3ErrorMsg(pParse,"misuse of aggregate function %.*s()",nId,zId); - pNC->nErr++; + pNC->nNcErr++; is_agg = 0; } #endif @@ -962,11 +1022,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #endif ){ sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId); - pNC->nErr++; + pNC->nNcErr++; }else if( wrong_num_args ){ sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()", nId, zId); - pNC->nErr++; + pNC->nNcErr++; } #ifndef SQLITE_OMIT_WINDOWFUNC else if( is_agg==0 && ExprHasProperty(pExpr, EP_WinFunc) ){ @@ -974,7 +1034,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ "FILTER may not be used with non-aggregate %.*s()", nId, zId ); - pNC->nErr++; + pNC->nNcErr++; } #endif if( is_agg ){ @@ -1001,6 +1061,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pWin==pExpr->y.pWin ); if( IN_RENAME_OBJECT==0 ){ sqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef); + if( pParse->db->mallocFailed ) break; } sqlite3WalkExprList(pWalker, pWin->pPartition); sqlite3WalkExprList(pWalker, pWin->pOrderBy); @@ -1075,7 +1136,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( !ExprHasProperty(pExpr, EP_Reduced) ); /* Handle special cases of "x IS TRUE", "x IS FALSE", "x IS NOT TRUE", ** and "x IS NOT FALSE". */ - if( pRight && (pRight->op==TK_ID || pRight->op==TK_TRUEFALSE) ){ + if( ALWAYS(pRight) && (pRight->op==TK_ID || pRight->op==TK_TRUEFALSE) ){ int rc = resolveExprStep(pWalker, pRight); if( rc==WRC_Abort ) return WRC_Abort; if( pRight->op==TK_TRUEFALSE ){ @@ -1197,11 +1258,11 @@ static int resolveOrderByTermToExprList( nc.pParse = pParse; nc.pSrcList = pSelect->pSrc; nc.uNC.pEList = pEList; - nc.ncFlags = NC_AllowAgg|NC_UEList; - nc.nErr = 0; + nc.ncFlags = NC_AllowAgg|NC_UEList|NC_NoSelect; + nc.nNcErr = 0; db = pParse->db; savedSuppErr = db->suppressErr; - if( IN_RENAME_OBJECT==0 ) db->suppressErr = 1; + db->suppressErr = 1; rc = sqlite3ResolveExprNames(&nc, pE); db->suppressErr = savedSuppErr; if( rc ) return 0; @@ -1300,29 +1361,24 @@ static int resolveCompoundOrderBy( ** Once the comparisons are finished, the duplicate expression ** is deleted. ** - ** Or, if this is running as part of an ALTER TABLE operation, - ** resolve the symbols in the actual expression, not a duplicate. - ** And, if one of the comparisons is successful, leave the expression - ** as is instead of transforming it to an integer as in the usual - ** case. This allows the code in alter.c to modify column - ** refererences within the ORDER BY expression as required. */ - if( IN_RENAME_OBJECT ){ - pDup = pE; - }else{ - pDup = sqlite3ExprDup(db, pE, 0); - } + ** If this is running as part of an ALTER TABLE operation and + ** the symbols resolve successfully, also resolve the symbols in the + ** actual expression. This allows the code in alter.c to modify + ** column references within the ORDER BY expression as required. */ + pDup = sqlite3ExprDup(db, pE, 0); if( !db->mallocFailed ){ assert(pDup); iCol = resolveOrderByTermToExprList(pParse, pSelect, pDup); + if( IN_RENAME_OBJECT && iCol>0 ){ + resolveOrderByTermToExprList(pParse, pSelect, pE); + } } - if( !IN_RENAME_OBJECT ){ - sqlite3ExprDelete(db, pDup); - } + sqlite3ExprDelete(db, pDup); } } if( iCol>0 ){ /* Convert the ORDER BY term into an integer column number iCol, - ** taking care to preserve the COLLATE clause if it exists */ + ** taking care to preserve the COLLATE clause if it exists. */ if( !IN_RENAME_OBJECT ){ Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); if( pNew==0 ) return 1; @@ -1391,8 +1447,7 @@ int sqlite3ResolveOrderGroupBy( resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr); return 1; } - resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr, - zType,0); + resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr,0); } } return 0; @@ -1458,7 +1513,7 @@ static int resolveOrderGroupBy( Parse *pParse; /* Parsing context */ int nResult; /* Number of terms in the result set */ - if( pOrderBy==0 ) return 0; + assert( pOrderBy!=0 ); nResult = pSelect->pEList->nExpr; pParse = pNC->pParse; for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ @@ -1548,8 +1603,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ while( p ){ assert( (p->selFlags & SF_Expanded)!=0 ); assert( (p->selFlags & SF_Resolved)==0 ); + assert( db->suppressErr==0 ); /* SF_Resolved not set if errors suppressed */ p->selFlags |= SF_Resolved; + /* Resolve the expressions in the LIMIT and OFFSET clauses. These ** are not allowed to refer to any names, so pass an empty NameContext. */ @@ -1577,27 +1634,26 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ /* Recursively resolve names in all subqueries */ for(i=0; ipSrc->nSrc; i++){ - struct SrcList_item *pItem = &p->pSrc->a[i]; + SrcItem *pItem = &p->pSrc->a[i]; if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){ - NameContext *pNC; /* Used to iterate name contexts */ - int nRef = 0; /* Refcount for pOuterNC and outer contexts */ + int nRef = pOuterNC ? pOuterNC->nRef : 0; const char *zSavedContext = pParse->zAuthContext; - /* Count the total number of references to pOuterNC and all of its - ** parent contexts. After resolving references to expressions in - ** pItem->pSelect, check if this value has changed. If so, then - ** SELECT statement pItem->pSelect must be correlated. Set the - ** pItem->fg.isCorrelated flag if this is the case. */ - for(pNC=pOuterNC; pNC; pNC=pNC->pNext) nRef += pNC->nRef; - if( pItem->zName ) pParse->zAuthContext = pItem->zName; sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC); pParse->zAuthContext = zSavedContext; if( pParse->nErr || db->mallocFailed ) return WRC_Abort; - for(pNC=pOuterNC; pNC; pNC=pNC->pNext) nRef -= pNC->nRef; - assert( pItem->fg.isCorrelated==0 && nRef<=0 ); - pItem->fg.isCorrelated = (nRef!=0); + /* If the number of references to the outer context changed when + ** expressions in the sub-select were resolved, the sub-select + ** is correlated. It is not required to check the refcount on any + ** but the innermost outer context object, as lookupName() increments + ** the refcount on all contexts between the current one and the + ** context containing the column when it resolves a name. */ + if( pOuterNC ){ + assert( pItem->fg.isCorrelated==0 && pOuterNC->nRef>=nRef ); + pItem->fg.isCorrelated = (pOuterNC->nRef>nRef); + } } } @@ -1624,13 +1680,6 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ sNC.ncFlags &= ~NC_AllowAgg; } - /* If a HAVING clause is present, then there must be a GROUP BY clause. - */ - if( p->pHaving && !pGroupBy ){ - sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING"); - return WRC_Abort; - } - /* Add the output column list to the name-context before parsing the ** other expressions in the SELECT statement. This is so that ** expressions in the WHERE clause (etc.) can refer to expressions by @@ -1639,15 +1688,21 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** Minor point: If this is the case, then the expression will be ** re-evaluated for each reference to it. */ - assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert))==0 ); + assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert|NC_UBaseReg))==0 ); sNC.uNC.pEList = p->pEList; sNC.ncFlags |= NC_UEList; - if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + if( p->pHaving ){ + if( !pGroupBy ){ + sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING"); + return WRC_Abort; + } + if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + } if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; /* Resolve names in table-valued-function arguments */ for(i=0; ipSrc->nSrc; i++){ - struct SrcList_item *pItem = &p->pSrc->a[i]; + SrcItem *pItem = &p->pSrc->a[i]; if( pItem->fg.isTabFunc && sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg) ){ @@ -1655,6 +1710,19 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( IN_RENAME_OBJECT ){ + Window *pWin; + for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){ + if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy) + || sqlite3ResolveExprListNames(&sNC, pWin->pPartition) + ){ + return WRC_Abort; + } + } + } +#endif + /* The ORDER BY and GROUP BY clauses may not refer to terms in ** outer queries */ @@ -1682,7 +1750,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** is not detected until much later, and so we need to go ahead and ** resolve those symbols on the incorrect ORDER BY for consistency. */ - if( isCompound<=nCompound /* Defer right-most ORDER BY of a compound */ + if( p->pOrderBy!=0 + && isCompound<=nCompound /* Defer right-most ORDER BY of a compound */ && resolveOrderGroupBy(&sNC, p, p->pOrderBy, "ORDER") ){ return WRC_Abort; @@ -1710,19 +1779,6 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } -#ifndef SQLITE_OMIT_WINDOWFUNC - if( IN_RENAME_OBJECT ){ - Window *pWin; - for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){ - if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy) - || sqlite3ResolveExprListNames(&sNC, pWin->pPartition) - ){ - return WRC_Abort; - } - } - } -#endif - /* If this is part of a compound SELECT, check that it has the right ** number of expressions in the select list. */ if( p->pNext && p->pEList->nExpr!=p->pNext->pEList->nExpr ){ @@ -1806,7 +1862,7 @@ int sqlite3ResolveExprNames( pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); w.pParse = pNC->pParse; w.xExprCallback = resolveExprStep; - w.xSelectCallback = resolveSelectStep; + w.xSelectCallback = (pNC->ncFlags & NC_NoSelect) ? 0 : resolveSelectStep; w.xSelectCallback2 = 0; w.u.pNC = pNC; #if SQLITE_MAX_EXPR_DEPTH>0 @@ -1825,7 +1881,7 @@ int sqlite3ResolveExprNames( testcase( pNC->ncFlags & NC_HasWin ); ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); pNC->ncFlags |= savedHasAgg; - return pNC->nErr>0 || w.pParse->nErr>0; + return pNC->nNcErr>0 || w.pParse->nErr>0; } /* @@ -1870,7 +1926,7 @@ int sqlite3ResolveExprListNames( savedHasAgg |= pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); } - if( pNC->nErr>0 || w.pParse->nErr>0 ) return WRC_Abort; + if( w.pParse->nErr>0 ) return WRC_Abort; } pNC->ncFlags |= savedHasAgg; return WRC_Continue; diff --git a/src/select.c b/src/select.c index b4f272679..7d8fd5939 100644 --- a/src/select.c +++ b/src/select.c @@ -85,12 +85,16 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ sqlite3ExprDelete(db, p->pHaving); sqlite3ExprListDelete(db, p->pOrderBy); sqlite3ExprDelete(db, p->pLimit); + if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){ sqlite3WindowListDelete(db, p->pWinDefn); } + while( p->pWin ){ + assert( p->pWin->ppThis==&p->pWin ); + sqlite3WindowUnlinkFromSelect(p->pWin); + } #endif - if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); if( bFree ) sqlite3DbFreeNN(db, p); p = pPrior; bFree = 1; @@ -262,7 +266,7 @@ int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ ** Return the index of a column in a table. Return -1 if the column ** is not contained in the table. */ -static int columnIndex(Table *pTab, const char *zCol){ +int sqlite3ColumnIndex(Table *pTab, const char *zCol){ int i; u8 h = sqlite3StrIHash(zCol); Column *pCol; @@ -294,7 +298,7 @@ static int tableAndColumnIndex( assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */ for(i=0; ia[i].pTab, zCol); + iCol = sqlite3ColumnIndex(pSrc->a[i].pTab, zCol); if( iCol>=0 && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0) ){ @@ -347,7 +351,7 @@ static void addWhereTerm( ExprSetProperty(pEq, EP_FromJoin); assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(pEq, EP_NoReduce); - pEq->iRightJoinTable = (i16)pE2->iTable; + pEq->iRightJoinTable = pE2->iTable; } *ppWhere = sqlite3ExprAnd(pParse, *ppWhere, pEq); } @@ -383,7 +387,7 @@ void sqlite3SetJoinExpr(Expr *p, int iTable){ ExprSetProperty(p, EP_FromJoin); assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(p, EP_NoReduce); - p->iRightJoinTable = (i16)iTable; + p->iRightJoinTable = iTable; if( p->op==TK_FUNCTION && p->x.pList ){ int i; for(i=0; ix.pList->nExpr; i++){ @@ -407,6 +411,9 @@ static void unsetJoinExpr(Expr *p, int iTable){ && (iTable<0 || p->iRightJoinTable==iTable) ){ ExprClearProperty(p, EP_FromJoin); } + if( p->op==TK_COLUMN && p->iTable==iTable ){ + ExprClearProperty(p, EP_CanBeNull); + } if( p->op==TK_FUNCTION && p->x.pList ){ int i; for(i=0; ix.pList->nExpr; i++){ @@ -435,8 +442,8 @@ static void unsetJoinExpr(Expr *p, int iTable){ static int sqliteProcessJoin(Parse *pParse, Select *p){ SrcList *pSrc; /* All tables in the FROM clause */ int i, j; /* Loop counters */ - struct SrcList_item *pLeft; /* Left table being joined */ - struct SrcList_item *pRight; /* Right table being joined */ + SrcItem *pLeft; /* Left table being joined */ + SrcItem *pRight; /* Right table being joined */ pSrc = p->pSrc; pLeft = &pSrc->a[0]; @@ -504,7 +511,7 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){ int iRightCol; /* Column number of matching column on the right */ zName = pList->a[j].zName; - iRightCol = columnIndex(pRightTab, zName); + iRightCol = sqlite3ColumnIndex(pRightTab, zName); if( iRightCol<0 || !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 0) ){ @@ -734,31 +741,155 @@ static void codeOffset( } /* -** Add code that will check to make sure the N registers starting at iMem -** form a distinct entry. iTab is a sorting index that holds previously -** seen combinations of the N values. A new entry is made in iTab -** if the current N values are new. -** -** A jump to addrRepeat is made and the N+1 values are popped from the -** stack if the top N elements are not distinct. +** Add code that will check to make sure the array of registers starting at +** iMem form a distinct entry. This is used by both "SELECT DISTINCT ..." and +** distinct aggregates ("SELECT count(DISTINCT ) ..."). Three strategies +** are available. Which is used depends on the value of parameter eTnctType, +** as follows: +** +** WHERE_DISTINCT_UNORDERED/WHERE_DISTINCT_NOOP: +** Build an ephemeral table that contains all entries seen before and +** skip entries which have been seen before. +** +** Parameter iTab is the cursor number of an ephemeral table that must +** be opened before the VM code generated by this routine is executed. +** The ephemeral cursor table is queried for a record identical to the +** record formed by the current array of registers. If one is found, +** jump to VM address addrRepeat. Otherwise, insert a new record into +** the ephemeral cursor and proceed. +** +** The returned value in this case is a copy of parameter iTab. +** +** WHERE_DISTINCT_ORDERED: +** In this case rows are being delivered sorted order. The ephermal +** table is not required. Instead, the current set of values +** is compared against previous row. If they match, the new row +** is not distinct and control jumps to VM address addrRepeat. Otherwise, +** the VM program proceeds with processing the new row. +** +** The returned value in this case is the register number of the first +** in an array of registers used to store the previous result row so that +** it can be compared to the next. The caller must ensure that this +** register is initialized to NULL. (The fixDistinctOpenEph() routine +** will take care of this initialization.) +** +** WHERE_DISTINCT_UNIQUE: +** In this case it has already been determined that the rows are distinct. +** No special action is required. The return value is zero. +** +** Parameter pEList is the list of expressions used to generated the +** contents of each row. It is used by this routine to determine (a) +** how many elements there are in the array of registers and (b) the +** collation sequences that should be used for the comparisons if +** eTnctType is WHERE_DISTINCT_ORDERED. */ -static void codeDistinct( +static int codeDistinct( Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ int iTab, /* A sorting index used to test for distinctness */ int addrRepeat, /* Jump to here if not distinct */ - int N, /* Number of elements */ - int iMem /* First element */ + ExprList *pEList, /* Expression for each element */ + int regElem /* First element */ ){ - Vdbe *v; - int r1; + int iRet = 0; + int nResultCol = pEList->nExpr; + Vdbe *v = pParse->pVdbe; - v = pParse->pVdbe; - r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N); - sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); - sqlite3ReleaseTempReg(pParse, r1); + switch( eTnctType ){ + case WHERE_DISTINCT_ORDERED: { + int i; + int iJump; /* Jump destination */ + int regPrev; /* Previous row content */ + + /* Allocate space for the previous row */ + iRet = regPrev = pParse->nMem+1; + pParse->nMem += nResultCol; + + iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; + for(i=0; ia[i].pExpr); + if( idb->mallocFailed ); + sqlite3VdbeAddOp3(v, OP_Copy, regElem, regPrev, nResultCol-1); + break; + } + + case WHERE_DISTINCT_UNIQUE: { + /* nothing to do */ + break; + } + + default: { + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, regElem, nResultCol); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regElem, nResultCol, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, regElem, nResultCol); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3ReleaseTempReg(pParse, r1); + iRet = iTab; + break; + } + } + + return iRet; +} + +/* +** This routine runs after codeDistinct(). It makes necessary +** adjustments to the OP_OpenEphemeral opcode that the codeDistinct() +** routine made use of. This processing must be done separately since +** sometimes codeDistinct is called before the OP_OpenEphemeral is actually +** laid down. +** +** WHERE_DISTINCT_NOOP: +** WHERE_DISTINCT_UNORDERED: +** +** No adjustments necessary. This function is a no-op. +** +** WHERE_DISTINCT_UNIQUE: +** +** The ephemeral table is not needed. So change the +** OP_OpenEphemeral opcode into an OP_Noop. +** +** WHERE_DISTINCT_ORDERED: +** +** The ephemeral table is not needed. But we do need register +** iVal to be initialized to NULL. So change the OP_OpenEphemeral +** into an OP_Null on the iVal register. +*/ +static void fixDistinctOpenEph( + Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ + int iVal, /* Value returned by codeDistinct() */ + int iOpenEphAddr /* Address of OP_OpenEphemeral instruction for iTab */ +){ + if( eTnctType==WHERE_DISTINCT_UNIQUE || eTnctType==WHERE_DISTINCT_ORDERED ){ + Vdbe *v = pParse->pVdbe; + sqlite3VdbeChangeToNoop(v, iOpenEphAddr); + if( sqlite3VdbeGetOp(v, iOpenEphAddr+1)->opcode==OP_Explain ){ + sqlite3VdbeChangeToNoop(v, iOpenEphAddr+1); + } + if( eTnctType==WHERE_DISTINCT_ORDERED ){ + /* Change the OP_OpenEphemeral to an OP_Null that sets the MEM_Cleared + ** bit on the first register of the previous value. This will cause the + ** OP_Ne added in codeDistinct() to always fail on the first iteration of + ** the loop even if the first row is all NULLs. */ + VdbeOp *pOp = sqlite3VdbeGetOp(v, iOpenEphAddr); + pOp->opcode = OP_Null; + pOp->p1 = 1; + pOp->p2 = iVal; + } + } } #ifdef SQLITE_ENABLE_SORTER_REFERENCES @@ -1006,59 +1137,11 @@ static void selectInnerLoop( ** part of the result. */ if( hasDistinct ){ - switch( pDistinct->eTnctType ){ - case WHERE_DISTINCT_ORDERED: { - VdbeOp *pOp; /* No longer required OpenEphemeral instr. */ - int iJump; /* Jump destination */ - int regPrev; /* Previous row content */ - - /* Allocate space for the previous row */ - regPrev = pParse->nMem+1; - pParse->nMem += nResultCol; - - /* Change the OP_OpenEphemeral coded earlier to an OP_Null - ** sets the MEM_Cleared bit on the first register of the - ** previous value. This will cause the OP_Ne below to always - ** fail on the first iteration of the loop even if the first - ** row is all NULLs. - */ - sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct); - pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct); - pOp->opcode = OP_Null; - pOp->p1 = 1; - pOp->p2 = regPrev; - pOp = 0; /* Ensure pOp is not used after sqlite3VdbeAddOp() */ - - iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; - for(i=0; ipEList->a[i].pExpr); - if( idb->mallocFailed ); - sqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nResultCol-1); - break; - } - - case WHERE_DISTINCT_UNIQUE: { - sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct); - break; - } - - default: { - assert( pDistinct->eTnctType==WHERE_DISTINCT_UNORDERED ); - codeDistinct(pParse, pDistinct->tabTnct, iContinue, nResultCol, - regResult); - break; - } - } + int eType = pDistinct->eTnctType; + int iTab = pDistinct->tabTnct; + assert( nResultCol==p->pEList->nExpr ); + iTab = codeDistinct(pParse, eType, iTab, iContinue, p->pEList, regResult); + fixDistinctOpenEph(pParse, eType, iTab, pDistinct->addrTnct); if( pSort==0 ){ codeOffset(v, p->iOffset, iContinue); } @@ -1383,7 +1466,7 @@ KeyInfo *sqlite3KeyInfoFromExprList( /* ** Name of the connection operator, used for error messages. */ -static const char *selectOpName(int id){ +const char *sqlite3SelectOpName(int id){ char *z; switch( id ){ case TK_ALL: z = "UNION ALL"; break; @@ -1724,7 +1807,13 @@ static const char *columnTypeImpl( ** of the SELECT statement. Return the declaration type and origin ** data for the result-set column of the sub-select. */ - if( iCol>=0 && iColpEList->nExpr ){ + if( iColpEList->nExpr +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + && iCol>=0 +#else + && ALWAYS(iCol>=0) +#endif + ){ /* If iCol is less than zero, then the expression requests the ** rowid of the sub-select or view. This expression is legal (see ** test case misc2.2.2) - it always evaluates to NULL. @@ -1866,7 +1955,7 @@ static void generateColumnTypes( ** then the result column name with the table name ** prefix, ex: TABLE.COLUMN. Otherwise use zSpan. */ -static void generateColumnNames( +void sqlite3GenerateColumnNames( Parse *pParse, /* Parser context */ Select *pSelect /* Generate column names for this SELECT statement */ ){ @@ -1956,7 +2045,7 @@ static void generateColumnNames( ** and will break if those assumptions changes. Hence, use extreme caution ** when modifying this routine to avoid breaking legacy. ** -** See Also: generateColumnNames() +** See Also: sqlite3GenerateColumnNames() */ int sqlite3ColumnsFromExprList( Parse *pParse, /* Parsing context */ @@ -1979,7 +2068,7 @@ int sqlite3ColumnsFromExprList( nCol = pEList->nExpr; aCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol); testcase( aCol==0 ); - if( nCol>32767 ) nCol = 32767; + if( NEVER(nCol>32767) ) nCol = 32767; }else{ nCol = 0; aCol = 0; @@ -2086,6 +2175,7 @@ void sqlite3SelectAddColumnTypeAndCollation( for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; int n, m; + pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; zType = columnType(&sNC, p, 0, 0, 0); /* pCol->szEst = ... // Column size est for SELECT tables never used */ @@ -2601,12 +2691,8 @@ static int multiSelect( db = pParse->db; pPrior = p->pPrior; dest = *pDest; - if( pPrior->pOrderBy || pPrior->pLimit ){ - sqlite3ErrorMsg(pParse,"%s clause should come after %s not before", - pPrior->pOrderBy!=0 ? "ORDER BY" : "LIMIT", selectOpName(p->op)); - rc = 1; - goto multi_select_end; - } + assert( pPrior->pOrderBy==0 ); + assert( pPrior->pLimit==0 ); v = sqlite3GetVdbe(pParse); assert( v!=0 ); /* The VDBE already created by calling function */ @@ -2657,13 +2743,14 @@ static int multiSelect( switch( p->op ){ case TK_ALL: { int addr = 0; - int nLimit; + int nLimit = 0; /* Initialize to suppress harmless compiler warning */ assert( !pPrior->pLimit ); pPrior->iLimit = p->iLimit; pPrior->iOffset = p->iOffset; pPrior->pLimit = p->pLimit; + SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL left...\n")); rc = sqlite3Select(pParse, pPrior, &dest); - p->pLimit = 0; + pPrior->pLimit = 0; if( rc ){ goto multi_select_end; } @@ -2679,13 +2766,14 @@ static int multiSelect( } } ExplainQueryPlan((pParse, 1, "UNION ALL")); + SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL right...\n")); rc = sqlite3Select(pParse, p, &dest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; p->pPrior = pPrior; p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); - if( pPrior->pLimit - && sqlite3ExprIsInteger(pPrior->pLimit->pLeft, &nLimit) + if( p->pLimit + && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit) && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) ){ p->nSelectRow = sqlite3LogEst((u64)nLimit); @@ -2731,6 +2819,7 @@ static int multiSelect( */ assert( !pPrior->pOrderBy ); sqlite3SelectDestInit(&uniondest, priorOp, unionTab); + SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); rc = sqlite3Select(pParse, pPrior, &uniondest); if( rc ){ goto multi_select_end; @@ -2749,7 +2838,8 @@ static int multiSelect( p->pLimit = 0; uniondest.eDest = op; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", - selectOpName(p->op))); + sqlite3SelectOpName(p->op))); + SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); rc = sqlite3Select(pParse, p, &uniondest); testcase( rc!=SQLITE_OK ); assert( p->pOrderBy==0 ); @@ -2810,6 +2900,7 @@ static int multiSelect( /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); + SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT left...\n")); rc = sqlite3Select(pParse, pPrior, &intersectdest); if( rc ){ goto multi_select_end; @@ -2825,7 +2916,8 @@ static int multiSelect( p->pLimit = 0; intersectdest.iSDParm = tab2; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", - selectOpName(p->op))); + sqlite3SelectOpName(p->op))); + SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT right...\n")); rc = sqlite3Select(pParse, p, &intersectdest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; @@ -2934,7 +3026,8 @@ void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms"); }else{ sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" - " do not have the same number of result columns", selectOpName(p->op)); + " do not have the same number of result columns", + sqlite3SelectOpName(p->op)); } } @@ -3031,10 +3124,8 @@ static int generateOutputSubroutine( ** if it is the RHS of a row-value IN operator. */ case SRT_Mem: { - if( pParse->nErr==0 ){ - testcase( pIn->nSdst>1 ); - sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, pIn->nSdst); - } + testcase( pIn->nSdst>1 ); + sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, pIn->nSdst); /* The LIMIT clause will jump out of the loop for us */ break; } @@ -3326,7 +3417,7 @@ static int multiSelectOrderBy( sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA); sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB); - ExplainQueryPlan((pParse, 1, "MERGE (%s)", selectOpName(p->op))); + ExplainQueryPlan((pParse, 1, "MERGE (%s)", sqlite3SelectOpName(p->op))); /* Generate a coroutine to evaluate the SELECT statement to the ** left of the compound operator - the "A" select. @@ -3460,6 +3551,9 @@ static int multiSelectOrderBy( p->pPrior = pPrior; pPrior->pNext = p; + sqlite3ExprListDelete(db, pPrior->pOrderBy); + pPrior->pOrderBy = 0; + /*** TBD: Insert subroutine calls to close cursors on incomplete **** subqueries ****/ ExplainQueryPlanPop(pParse); @@ -3514,9 +3608,12 @@ static Expr *substExpr( && pExpr->iTable==pSubst->iTable && !ExprHasProperty(pExpr, EP_FixedCol) ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW if( pExpr->iColumn<0 ){ pExpr->op = TK_NULL; - }else{ + }else +#endif + { Expr *pNew; Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr; Expr ifNullRow; @@ -3536,10 +3633,14 @@ static Expr *substExpr( } testcase( ExprHasProperty(pCopy, EP_Subquery) ); pNew = sqlite3ExprDup(db, pCopy, 0); - if( pNew && pSubst->isLeftJoin ){ + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pNew); + return pExpr; + } + if( pSubst->isLeftJoin ){ ExprSetProperty(pNew, EP_CanBeNull); } - if( pNew && ExprHasProperty(pExpr,EP_FromJoin) ){ + if( ExprHasProperty(pExpr,EP_FromJoin) ){ sqlite3SetJoinExpr(pNew, pExpr->iRightJoinTable); } sqlite3ExprDelete(db, pExpr); @@ -3547,15 +3648,13 @@ static Expr *substExpr( /* Ensure that the expression now has an implicit collation sequence, ** just as it did when it was a column of a view or sub-query. */ - if( pExpr ){ - if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){ - CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr); - pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, - (pColl ? pColl->zName : "BINARY") - ); - } - ExprClearProperty(pExpr, EP_Collate); + if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){ + CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr); + pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, + (pColl ? pColl->zName : "BINARY") + ); } + ExprClearProperty(pExpr, EP_Collate); } } }else{ @@ -3596,7 +3695,7 @@ static void substSelect( int doPrior /* Do substitutes on p->pPrior too */ ){ SrcList *pSrc; - struct SrcList_item *pItem; + SrcItem *pItem; int i; if( !p ) return; do{ @@ -3626,7 +3725,7 @@ static void substSelect( ** pSrcItem->colUsed mask. */ static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){ - struct SrcList_item *pItem; + SrcItem *pItem; if( pExpr->op!=TK_COLUMN ) return WRC_Continue; pItem = pWalker->u.pSrcItem; if( pItem->iCursor!=pExpr->iTable ) return WRC_Continue; @@ -3636,7 +3735,7 @@ static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){ } static void recomputeColumnsUsed( Select *pSelect, /* The complete SELECT statement */ - struct SrcList_item *pSrcItem /* Which FROM clause item to recompute */ + SrcItem *pSrcItem /* Which FROM clause item to recompute */ ){ Walker w; if( NEVER(pSrcItem->pTab==0) ) return; @@ -3649,6 +3748,92 @@ static void recomputeColumnsUsed( } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +/* +** Assign new cursor numbers to each of the items in pSrc. For each +** new cursor number assigned, set an entry in the aCsrMap[] array +** to map the old cursor number to the new: +** +** aCsrMap[iOld] = iNew; +** +** The array is guaranteed by the caller to be large enough for all +** existing cursor numbers in pSrc. +** +** If pSrc contains any sub-selects, call this routine recursively +** on the FROM clause of each such sub-select, with iExcept set to -1. +*/ +static void srclistRenumberCursors( + Parse *pParse, /* Parse context */ + int *aCsrMap, /* Array to store cursor mappings in */ + SrcList *pSrc, /* FROM clause to renumber */ + int iExcept /* FROM clause item to skip */ +){ + int i; + SrcItem *pItem; + for(i=0, pItem=pSrc->a; inSrc; i++, pItem++){ + if( i!=iExcept ){ + Select *p; + if( !pItem->fg.isRecursive || aCsrMap[pItem->iCursor]==0 ){ + aCsrMap[pItem->iCursor] = pParse->nTab++; + } + pItem->iCursor = aCsrMap[pItem->iCursor]; + for(p=pItem->pSelect; p; p=p->pPrior){ + srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1); + } + } + } +} + +/* +** Expression walker callback used by renumberCursors() to update +** Expr objects to match newly assigned cursor numbers. +*/ +static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ + int *aCsrMap = pWalker->u.aiCol; + int op = pExpr->op; + if( (op==TK_COLUMN || op==TK_IF_NULL_ROW) && aCsrMap[pExpr->iTable] ){ + pExpr->iTable = aCsrMap[pExpr->iTable]; + } + if( ExprHasProperty(pExpr, EP_FromJoin) && aCsrMap[pExpr->iRightJoinTable] ){ + pExpr->iRightJoinTable = aCsrMap[pExpr->iRightJoinTable]; + } + return WRC_Continue; +} + +/* +** Assign a new cursor number to each cursor in the FROM clause (Select.pSrc) +** of the SELECT statement passed as the second argument, and to each +** cursor in the FROM clause of any FROM clause sub-selects, recursively. +** Except, do not assign a new cursor number to the iExcept'th element in +** the FROM clause of (*p). Update all expressions and other references +** to refer to the new cursor numbers. +** +** Argument aCsrMap is an array that may be used for temporary working +** space. Two guarantees are made by the caller: +** +** * the array is larger than the largest cursor number used within the +** select statement passed as an argument, and +** +** * the array entries for all cursor numbers that do *not* appear in +** FROM clauses of the select statement as described above are +** initialized to zero. +*/ +static void renumberCursors( + Parse *pParse, /* Parse context */ + Select *p, /* Select to renumber cursors within */ + int iExcept, /* FROM clause item to skip */ + int *aCsrMap /* Working space */ +){ + Walker w; + srclistRenumberCursors(pParse, aCsrMap, p->pSrc, iExcept); + memset(&w, 0, sizeof(w)); + w.u.aiCol = aCsrMap; + w.xExprCallback = renumberCursorsCb; + w.xSelectCallback = sqlite3SelectWalkNoop; + sqlite3WalkSelect(&w, p); +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** This routine attempts to flatten subqueries as a performance optimization. @@ -3743,9 +3928,9 @@ static void recomputeColumnsUsed( ** (17c) every term within the subquery compound must have a FROM clause ** (17d) the outer query may not be ** (17d1) aggregate, or -** (17d2) DISTINCT, or -** (17d3) a join. -** (17e) the subquery may not contain window functions +** (17d2) DISTINCT +** (17e) the subquery may not contain window functions, and +** (17f) the subquery must not be the RHS of a LEFT JOIN. ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, @@ -3761,8 +3946,8 @@ static void recomputeColumnsUsed( ** syntax error and return a detailed message. ** ** (18) If the sub-query is a compound select, then all terms of the -** ORDER BY clause of the parent must be simple references to -** columns of the sub-query. +** ORDER BY clause of the parent must be copies of a term returned +** by the parent query. ** ** (19) If the subquery uses LIMIT then the outer query may not ** have a WHERE clause. @@ -3778,9 +3963,8 @@ static void recomputeColumnsUsed( ** ** (22) The subquery may not be a recursive CTE. ** -** (**) Subsumed into restriction (17d3). Was: If the outer query is -** a recursive CTE, then the sub-query may not be a compound query. -** This restriction is because transforming the +** (23) If the outer query is a recursive CTE, then the sub-query may not be +** a compound query. This restriction is because transforming the ** parent to a compound query confuses the code that handles ** recursive queries in multiSelect(). ** @@ -3822,9 +4006,10 @@ static int flattenSubquery( int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ - struct SrcList_item *pSubitem; /* The subquery */ + SrcItem *pSubitem; /* The subquery */ sqlite3 *db = pParse->db; Walker w; /* Walker to persist agginfo data */ + int *aCsrMap = 0; /* Check to see if flattening is permitted. Return 0 if not. */ @@ -3920,13 +4105,14 @@ static int flattenSubquery( if( pSub->pOrderBy ){ return 0; /* Restriction (20) */ } - if( isAgg || (p->selFlags & SF_Distinct)!=0 || pSrc->nSrc!=1 ){ - return 0; /* (17d1), (17d2), or (17d3) */ + if( isAgg || (p->selFlags & SF_Distinct)!=0 || isLeftJoin>0 ){ + return 0; /* (17d1), (17d2), or (17f) */ } for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){ testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct ); testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Aggregate ); assert( pSub->pSrc!=0 ); + assert( (pSub->selFlags & SF_Recursive)==0 ); assert( pSub->pEList->nExpr==pSub1->pEList->nExpr ); if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0 /* (17b) */ || (pSub1->pPrior && pSub1->op!=TK_ALL) /* (17a) */ @@ -3947,15 +4133,15 @@ static int flattenSubquery( if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0; } } - } - /* Ex-restriction (23): - ** The only way that the recursive part of a CTE can contain a compound - ** subquery is for the subquery to be one term of a join. But if the - ** subquery is a join, then the flattening has already been stopped by - ** restriction (17d3) - */ - assert( (p->selFlags & SF_Recursive)==0 || pSub->pPrior==0 ); + /* Restriction (23) */ + if( (p->selFlags & SF_Recursive) ) return 0; + + if( pSrc->nSrc>1 ){ + if( pParse->nSelect>500 ) return 0; + aCsrMap = sqlite3DbMallocZero(db, pParse->nTab*sizeof(int)); + } + } /***** If we reach this point, flattening is permitted. *****/ SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n", @@ -3967,6 +4153,17 @@ static int flattenSubquery( testcase( i==SQLITE_DENY ); pParse->zAuthContext = zSavedAuthContext; + /* Delete the transient structures associated with thesubquery */ + pSub1 = pSubitem->pSelect; + sqlite3DbFree(db, pSubitem->zDatabase); + sqlite3DbFree(db, pSubitem->zName); + sqlite3DbFree(db, pSubitem->zAlias); + pSubitem->zDatabase = 0; + pSubitem->zName = 0; + pSubitem->zAlias = 0; + pSubitem->pSelect = 0; + assert( pSubitem->pOn==0 ); + /* If the sub-query is a compound SELECT statement, then (by restrictions ** 17 and 18 above) it must be a UNION ALL and the parent query must ** be of the form: @@ -4005,18 +4202,23 @@ static int flattenSubquery( ExprList *pOrderBy = p->pOrderBy; Expr *pLimit = p->pLimit; Select *pPrior = p->pPrior; + Table *pItemTab = pSubitem->pTab; + pSubitem->pTab = 0; p->pOrderBy = 0; - p->pSrc = 0; p->pPrior = 0; p->pLimit = 0; pNew = sqlite3SelectDup(db, p, 0); p->pLimit = pLimit; p->pOrderBy = pOrderBy; - p->pSrc = pSrc; p->op = TK_ALL; + pSubitem->pTab = pItemTab; if( pNew==0 ){ p->pPrior = pPrior; }else{ + pNew->selId = ++pParse->nSelect; + if( aCsrMap && ALWAYS(db->mallocFailed==0) ){ + renumberCursors(pParse, pNew, iFrom, aCsrMap); + } pNew->pPrior = pPrior; if( pPrior ) pPrior->pNext = pNew; pNew->pNext = p; @@ -4024,24 +4226,13 @@ static int flattenSubquery( SELECTTRACE(2,pParse,p,("compound-subquery flattener" " creates %u as peer\n",pNew->selId)); } - if( db->mallocFailed ) return 1; + assert( pSubitem->pSelect==0 ); + } + sqlite3DbFree(db, aCsrMap); + if( db->mallocFailed ){ + pSubitem->pSelect = pSub1; + return 1; } - - /* Begin flattening the iFrom-th entry of the FROM clause - ** in the outer query. - */ - pSub = pSub1 = pSubitem->pSelect; - - /* Delete the transient table structure associated with the - ** subquery - */ - sqlite3DbFree(db, pSubitem->zDatabase); - sqlite3DbFree(db, pSubitem->zName); - sqlite3DbFree(db, pSubitem->zAlias); - pSubitem->zDatabase = 0; - pSubitem->zName = 0; - pSubitem->zAlias = 0; - pSubitem->pSelect = 0; /* Defer deleting the Table object associated with the ** subquery until code generation is @@ -4054,8 +4245,10 @@ static int flattenSubquery( Table *pTabToDel = pSubitem->pTab; if( pTabToDel->nTabRef==1 ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - pTabToDel->pNextZombie = pToplevel->pZombieTab; - pToplevel->pZombieTab = pTabToDel; + sqlite3ParserAddCleanup(pToplevel, + (void(*)(sqlite3*,void*))sqlite3DeleteTable, + pTabToDel); + testcase( pToplevel->earlyCleanup ); }else{ pTabToDel->nTabRef--; } @@ -4075,6 +4268,7 @@ static int flattenSubquery( ** those references with expressions that resolve to the subquery FROM ** elements we are now copying in. */ + pSub = pSub1; for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ int nSubSrc; u8 jointype = 0; @@ -4083,16 +4277,10 @@ static int flattenSubquery( nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ pSrc = pParent->pSrc; /* FROM clause of the outer query */ - if( pSrc ){ - assert( pParent==p ); /* First time through the loop */ - jointype = pSubitem->fg.jointype; - }else{ - assert( pParent!=p ); /* 2nd and subsequent times through the loop */ - pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); - if( pSrc==0 ) break; - pParent->pSrc = pSrc; + if( pParent==p ){ + jointype = pSubitem->fg.jointype; /* First time through the loop */ } - + /* The subquery uses a single slot of the FROM clause of the outer ** query. If the subquery has more than one element in its FROM clause, ** then expand the outer query to make space for it to hold all elements @@ -4210,7 +4398,7 @@ static int flattenSubquery( sqlite3SelectDelete(db, pSub1); #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x100 ){ + if( sqlite3SelectTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p,("After flattening:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -4227,8 +4415,10 @@ static int flattenSubquery( typedef struct WhereConst WhereConst; struct WhereConst { Parse *pParse; /* Parsing context */ + u8 *pOomFault; /* Pointer to pParse->db->mallocFailed */ int nConst; /* Number for COLUMN=CONSTANT terms */ int nChng; /* Number of times a constant is propagated */ + int bHasAffBlob; /* At least one column in apExpr[] as affinity BLOB */ Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */ }; @@ -4267,6 +4457,9 @@ static void constInsert( return; /* Already present. Return without doing anything. */ } } + if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ + pConst->bHasAffBlob = 1; + } pConst->nConst++; pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr, @@ -4287,7 +4480,7 @@ static void constInsert( */ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ Expr *pRight, *pLeft; - if( pExpr==0 ) return; + if( NEVER(pExpr==0) ) return; if( ExprHasProperty(pExpr, EP_FromJoin) ) return; if( pExpr->op==TK_AND ){ findConstInWhere(pConst, pExpr->pRight); @@ -4308,37 +4501,83 @@ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ } /* -** This is a Walker expression callback. pExpr is a candidate expression -** to be replaced by a value. If pExpr is equivalent to one of the -** columns named in pWalker->u.pConst, then overwrite it with its -** corresponding value. +** This is a helper function for Walker callback propagateConstantExprRewrite(). +** +** Argument pExpr is a candidate expression to be replaced by a value. If +** pExpr is equivalent to one of the columns named in pWalker->u.pConst, +** then overwrite it with the corresponding value. Except, do not do so +** if argument bIgnoreAffBlob is non-zero and the affinity of pExpr +** is SQLITE_AFF_BLOB. */ -static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ +static int propagateConstantExprRewriteOne( + WhereConst *pConst, + Expr *pExpr, + int bIgnoreAffBlob +){ int i; - WhereConst *pConst; + if( pConst->pOomFault[0] ) return WRC_Prune; if( pExpr->op!=TK_COLUMN ) return WRC_Continue; if( ExprHasProperty(pExpr, EP_FixedCol|EP_FromJoin) ){ testcase( ExprHasProperty(pExpr, EP_FixedCol) ); testcase( ExprHasProperty(pExpr, EP_FromJoin) ); return WRC_Continue; } - pConst = pWalker->u.pConst; for(i=0; inConst; i++){ Expr *pColumn = pConst->apExpr[i*2]; if( pColumn==pExpr ) continue; if( pColumn->iTable!=pExpr->iTable ) continue; if( pColumn->iColumn!=pExpr->iColumn ) continue; + if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ + break; + } /* A match is found. Add the EP_FixedCol property */ pConst->nChng++; ExprClearProperty(pExpr, EP_Leaf); ExprSetProperty(pExpr, EP_FixedCol); assert( pExpr->pLeft==0 ); pExpr->pLeft = sqlite3ExprDup(pConst->pParse->db, pConst->apExpr[i*2+1], 0); + if( pConst->pParse->db->mallocFailed ) return WRC_Prune; break; } return WRC_Prune; } +/* +** This is a Walker expression callback. pExpr is a node from the WHERE +** clause of a SELECT statement. This function examines pExpr to see if +** any substitutions based on the contents of pWalker->u.pConst should +** be made to pExpr or its immediate children. +** +** A substitution is made if: +** +** + pExpr is a column with an affinity other than BLOB that matches +** one of the columns in pWalker->u.pConst, or +** +** + pExpr is a binary comparison operator (=, <=, >=, <, >) that +** uses an affinity other than TEXT and one of its immediate +** children is a column that matches one of the columns in +** pWalker->u.pConst. +*/ +static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ + WhereConst *pConst = pWalker->u.pConst; + assert( TK_GT==TK_EQ+1 ); + assert( TK_LE==TK_EQ+2 ); + assert( TK_LT==TK_EQ+3 ); + assert( TK_GE==TK_EQ+4 ); + if( pConst->bHasAffBlob ){ + if( (pExpr->op>=TK_EQ && pExpr->op<=TK_GE) + || pExpr->op==TK_IS + ){ + propagateConstantExprRewriteOne(pConst, pExpr->pLeft, 0); + if( pConst->pOomFault[0] ) return WRC_Prune; + if( sqlite3ExprAffinity(pExpr->pLeft)!=SQLITE_AFF_TEXT ){ + propagateConstantExprRewriteOne(pConst, pExpr->pRight, 0); + } + } + } + return propagateConstantExprRewriteOne(pConst, pExpr, pConst->bHasAffBlob); +} + /* ** The WHERE-clause constant propagation optimization. ** @@ -4374,6 +4613,21 @@ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ ** routines know to generate the constant "123" instead of looking up the ** column value. Also, to avoid collation problems, this optimization is ** only attempted if the "a=123" term uses the default BINARY collation. +** +** 2021-05-25 forum post 6a06202608: Another troublesome case is... +** +** CREATE TABLE t1(x); +** INSERT INTO t1 VALUES(10.0); +** SELECT 1 FROM t1 WHERE x=10 AND x LIKE 10; +** +** The query should return no rows, because the t1.x value is '10.0' not '10' +** and '10.0' is not LIKE '10'. But if we are not careful, the first WHERE +** term "x=10" will cause the second WHERE term to become "10 LIKE 10", +** resulting in a false positive. To avoid this, constant propagation for +** columns with BLOB affinity is only allowed if the constant is used with +** operators ==, <=, <, >=, >, or IS in a way that will cause the correct +** type conversions to occur. See logic associated with the bHasAffBlob flag +** for details. */ static int propagateConstants( Parse *pParse, /* The parsing context */ @@ -4383,10 +4637,12 @@ static int propagateConstants( Walker w; int nChng = 0; x.pParse = pParse; + x.pOomFault = &pParse->db->mallocFailed; do{ x.nConst = 0; x.nChng = 0; x.apExpr = 0; + x.bHasAffBlob = 0; findConstInWhere(&x, p->pWhere); if( x.nConst ){ memset(&w, 0, sizeof(w)); @@ -4404,6 +4660,35 @@ static int propagateConstants( return nChng; } +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +# if !defined(SQLITE_OMIT_WINDOWFUNC) +/* +** This function is called to determine whether or not it is safe to +** push WHERE clause expression pExpr down to FROM clause sub-query +** pSubq, which contains at least one window function. Return 1 +** if it is safe and the expression should be pushed down, or 0 +** otherwise. +** +** It is only safe to push the expression down if it consists only +** of constants and copies of expressions that appear in the PARTITION +** BY clause of all window function used by the sub-query. It is safe +** to filter out entire partitions, but not rows within partitions, as +** this may change the results of the window functions. +** +** At the time this function is called it is guaranteed that +** +** * the sub-query uses only one distinct window frame, and +** * that the window frame has a PARTITION BY clase. +*/ +static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ + assert( pSubq->pWin->pPartition ); + assert( (pSubq->selFlags & SF_MultiPart)==0 ); + assert( pSubq->pPrior==0 ); + return sqlite3ExprIsConstantOrGroupBy(pParse, pExpr, pSubq->pWin->pPartition); +} +# endif /* SQLITE_OMIT_WINDOWFUNC */ +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** Make copies of relevant WHERE clause terms of the outer query into @@ -4451,9 +4736,24 @@ static int propagateConstants( ** But if the (b2=2) term were to be pushed down into the bb subquery, ** then the (1,1,NULL) row would be suppressed. ** -** (6) The inner query features one or more window-functions (since -** changes to the WHERE clause of the inner query could change the -** window over which window functions are calculated). +** (6) Window functions make things tricky as changes to the WHERE clause +** of the inner query could change the window over which window +** functions are calculated. Therefore, do not attempt the optimization +** if: +** +** (6a) The inner query uses multiple incompatible window partitions. +** +** (6b) The inner query is a compound and uses window-functions. +** +** (6c) The WHERE clause does not consist entirely of constants and +** copies of expressions found in the PARTITION BY clause of +** all window-functions used by the sub-query. It is safe to +** filter out entire partitions, as this does not change the +** window over which any window-function is calculated. +** +** (7) The inner query is a Common Table Expression (CTE) that should +** be materialized. (This restriction is implemented in the calling +** routine.) ** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. @@ -4467,13 +4767,17 @@ static int pushDownWhereTerms( ){ Expr *pNew; int nChng = 0; - Select *pSel; if( pWhere==0 ) return 0; - if( pSubq->selFlags & SF_Recursive ) return 0; /* restriction (2) */ + if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0; #ifndef SQLITE_OMIT_WINDOWFUNC - for(pSel=pSubq; pSel; pSel=pSel->pPrior){ - if( pSel->pWin ) return 0; /* restriction (6) */ + if( pSubq->pPrior ){ + Select *pSel; + for(pSel=pSubq; pSel; pSel=pSel->pPrior){ + if( pSel->pWin ) return 0; /* restriction (6b) */ + } + }else{ + if( pSubq->pWin && pSubq->pWin->pPartition==0 ) return 0; } #endif @@ -4509,6 +4813,7 @@ static int pushDownWhereTerms( } if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){ nChng++; + pSubq->selFlags |= SF_PushDown; while( pSubq ){ SubstContext x; pNew = sqlite3ExprDup(pParse->db, pWhere, 0); @@ -4519,6 +4824,14 @@ static int pushDownWhereTerms( x.isLeftJoin = 0; x.pEList = pSubq->pEList; pNew = substExpr(&x, pNew); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){ + /* Restriction 6c has prevented push-down in this case */ + sqlite3ExprDelete(pParse->db, pNew); + nChng--; + break; + } +#endif if( pSubq->selFlags & SF_Aggregate ){ pSubq->pHaving = sqlite3ExprAnd(pParse, pSubq->pHaving, pNew); }else{ @@ -4557,7 +4870,11 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ assert( *ppMinMax==0 ); assert( pFunc->op==TK_AGG_FUNCTION ); assert( !IsWindowFunc(pFunc) ); - if( pEList==0 || pEList->nExpr!=1 || ExprHasProperty(pFunc, EP_WinFunc) ){ + if( pEList==0 + || pEList->nExpr!=1 + || ExprHasProperty(pFunc, EP_WinFunc) + || OptimizationDisabled(db, SQLITE_MinMaxOpt) + ){ return eRet; } zFunc = pFunc->u.zToken; @@ -4620,24 +4937,26 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ ** SQLITE_ERROR and leave an error in pParse. Otherwise, populate ** pFrom->pIndex and return SQLITE_OK. */ -int sqlite3IndexedByLookup(Parse *pParse, struct SrcList_item *pFrom){ - if( pFrom->pTab && pFrom->fg.isIndexedBy ){ - Table *pTab = pFrom->pTab; - char *zIndexedBy = pFrom->u1.zIndexedBy; - Index *pIdx; - for(pIdx=pTab->pIndex; - pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy); - pIdx=pIdx->pNext - ); - if( !pIdx ){ - sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0); - pParse->checkSchema = 1; - return SQLITE_ERROR; - } - pFrom->pIBIndex = pIdx; +int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ + Table *pTab = pFrom->pTab; + char *zIndexedBy = pFrom->u1.zIndexedBy; + Index *pIdx; + assert( pTab!=0 ); + assert( pFrom->fg.isIndexedBy!=0 ); + + for(pIdx=pTab->pIndex; + pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy); + pIdx=pIdx->pNext + ); + if( !pIdx ){ + sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0); + pParse->checkSchema = 1; + return SQLITE_ERROR; } + pFrom->u2.pIBIndex = pIdx; return SQLITE_OK; } + /* ** Detect compound SELECT statements that use an ORDER BY clause with ** an alternative collating sequence. @@ -4724,7 +5043,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ ** arguments. If it does, leave an error message in pParse and return ** non-zero, since pFrom is not allowed to be a table-valued function. */ -static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){ +static int cannotBeFunction(Parse *pParse, SrcItem *pFrom){ if( pFrom->fg.isTabFunc ){ sqlite3ErrorMsg(pParse, "'%s' is not a function", pFrom->zName); return 1; @@ -4745,21 +5064,22 @@ static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){ */ static struct Cte *searchWith( With *pWith, /* Current innermost WITH clause */ - struct SrcList_item *pItem, /* FROM clause element to resolve */ + SrcItem *pItem, /* FROM clause element to resolve */ With **ppContext /* OUT: WITH clause return value belongs to */ ){ - const char *zName; - if( pItem->zDatabase==0 && (zName = pItem->zName)!=0 ){ - With *p; - for(p=pWith; p; p=p->pOuter){ - int i; - for(i=0; inCte; i++){ - if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){ - *ppContext = p; - return &p->a[i]; - } + const char *zName = pItem->zName; + With *p; + assert( pItem->zDatabase==0 ); + assert( zName!=0 ); + for(p=pWith; p; p=p->pOuter){ + int i; + for(i=0; inCte; i++){ + if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){ + *ppContext = p; + return &p->a[i]; } } + if( p->bView ) break; } return 0; } @@ -4769,52 +5089,83 @@ static struct Cte *searchWith( ** ** This routine pushes the WITH clause passed as the second argument ** onto the top of the stack. If argument bFree is true, then this -** WITH clause will never be popped from the stack. In this case it -** should be freed along with the Parse object. In other cases, when +** WITH clause will never be popped from the stack but should instead +** be freed along with the Parse object. In other cases, when ** bFree==0, the With object will be freed along with the SELECT ** statement with which it is associated. +** +** This routine returns a copy of pWith. Or, if bFree is true and +** the pWith object is destroyed immediately due to an OOM condition, +** then this routine return NULL. +** +** If bFree is true, do not continue to use the pWith pointer after +** calling this routine, Instead, use only the return value. */ -void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ - assert( bFree==0 || (pParse->pWith==0 && pParse->pWithToFree==0) ); +With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ if( pWith ){ - assert( pParse->pWith!=pWith ); - pWith->pOuter = pParse->pWith; - pParse->pWith = pWith; - if( bFree ) pParse->pWithToFree = pWith; + if( bFree ){ + pWith = (With*)sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3WithDelete, + pWith); + if( pWith==0 ) return 0; + } + if( pParse->nErr==0 ){ + assert( pParse->pWith!=pWith ); + pWith->pOuter = pParse->pWith; + pParse->pWith = pWith; + } } + return pWith; } /* ** This function checks if argument pFrom refers to a CTE declared by -** a WITH clause on the stack currently maintained by the parser. And, -** if currently processing a CTE expression, if it is a recursive -** reference to the current CTE. -** -** If pFrom falls into either of the two categories above, pFrom->pTab -** and other fields are populated accordingly. The caller should check -** (pFrom->pTab!=0) to determine whether or not a successful match -** was found. -** -** Whether or not a match is found, SQLITE_OK is returned if no error -** occurs. If an error does occur, an error message is stored in the -** parser and some error code other than SQLITE_OK returned. +** a WITH clause on the stack currently maintained by the parser (on the +** pParse->pWith linked list). And if currently processing a CTE +** CTE expression, through routine checks to see if the reference is +** a recursive reference to the CTE. +** +** If pFrom matches a CTE according to either of these two above, pFrom->pTab +** and other fields are populated accordingly. +** +** Return 0 if no match is found. +** Return 1 if a match is found. +** Return 2 if an error condition is detected. */ -static int withExpand( - Walker *pWalker, - struct SrcList_item *pFrom +static int resolveFromTermToCte( + Parse *pParse, /* The parsing context */ + Walker *pWalker, /* Current tree walker */ + SrcItem *pFrom /* The FROM clause term to check */ ){ - Parse *pParse = pWalker->pParse; - sqlite3 *db = pParse->db; - struct Cte *pCte; /* Matched CTE (or NULL if no match) */ - With *pWith; /* WITH clause that pCte belongs to */ + Cte *pCte; /* Matched CTE (or NULL if no match) */ + With *pWith; /* The matching WITH */ assert( pFrom->pTab==0 ); + if( pParse->pWith==0 ){ + /* There are no WITH clauses in the stack. No match is possible */ + return 0; + } if( pParse->nErr ){ - return SQLITE_ERROR; + /* Prior errors might have left pParse->pWith in a goofy state, so + ** go no further. */ + return 0; + } + if( pFrom->zDatabase!=0 ){ + /* The FROM term contains a schema qualifier (ex: main.t1) and so + ** it cannot possibly be a CTE reference. */ + return 0; + } + if( pFrom->fg.notCte ){ + /* The FROM term is specifically excluded from matching a CTE. + ** (1) It is part of a trigger that used to have zDatabase but had + ** zDatabase removed by sqlite3FixTriggerStep(). + ** (2) This is the first term in the FROM clause of an UPDATE. + */ + return 0; } - pCte = searchWith(pParse->pWith, pFrom, &pWith); if( pCte ){ + sqlite3 *db = pParse->db; Table *pTab; ExprList *pEList; Select *pSel; @@ -4823,6 +5174,7 @@ static int withExpand( int bMayRecursive; /* True if compound joined by UNION [ALL] */ With *pSavedWith; /* Initial value of pParse->pWith */ int iRecTab = -1; /* Cursor for recursive table */ + CteUse *pCteUse; /* If pCte->zCteErr is non-NULL at this point, then this is an illegal ** recursive reference to CTE pCte. Leave an error in pParse and return @@ -4830,21 +5182,40 @@ static int withExpand( ** In this case, proceed. */ if( pCte->zCteErr ){ sqlite3ErrorMsg(pParse, pCte->zCteErr, pCte->zName); - return SQLITE_ERROR; + return 2; } - if( cannotBeFunction(pParse, pFrom) ) return SQLITE_ERROR; + if( cannotBeFunction(pParse, pFrom) ) return 2; assert( pFrom->pTab==0 ); - pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); - if( pTab==0 ) return WRC_Abort; + pTab = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTab==0 ) return 2; + pCteUse = pCte->pUse; + if( pCteUse==0 ){ + pCte->pUse = pCteUse = sqlite3DbMallocZero(db, sizeof(pCteUse[0])); + if( pCteUse==0 + || sqlite3ParserAddCleanup(pParse,sqlite3DbFree,pCteUse)==0 + ){ + sqlite3DbFree(db, pTab); + return 2; + } + pCteUse->eM10d = pCte->eM10d; + } + pFrom->pTab = pTab; pTab->nTabRef = 1; pTab->zName = sqlite3DbStrDup(db, pCte->zName); pTab->iPKey = -1; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0); - if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; + if( db->mallocFailed ) return 2; + pFrom->pSelect->selFlags |= SF_CopyCte; assert( pFrom->pSelect ); + pFrom->fg.isCte = 1; + pFrom->u2.pCteUse = pCteUse; + pCteUse->nUse++; + if( pCteUse->nUse>=2 && pCteUse->eM10d==M10d_Any ){ + pCteUse->eM10d = M10d_Yes; + } /* Check if this is a recursive CTE. */ pRecTerm = pSel = pFrom->pSelect; @@ -4854,7 +5225,7 @@ static int withExpand( SrcList *pSrc = pRecTerm->pSrc; assert( pRecTerm->pPrior!=0 ); for(i=0; inSrc; i++){ - struct SrcList_item *pItem = &pSrc->a[i]; + SrcItem *pItem = &pSrc->a[i]; if( pItem->zDatabase==0 && pItem->zName!=0 && 0==sqlite3StrICmp(pItem->zName, pCte->zName) @@ -4866,7 +5237,7 @@ static int withExpand( sqlite3ErrorMsg(pParse, "multiple references to recursive table: %s", pCte->zName ); - return SQLITE_ERROR; + return 2; } pRecTerm->selFlags |= SF_Recursive; if( iRecTab<0 ) iRecTab = pParse->nTab++; @@ -4881,16 +5252,24 @@ static int withExpand( pSavedWith = pParse->pWith; pParse->pWith = pWith; if( pSel->selFlags & SF_Recursive ){ + int rc; assert( pRecTerm!=0 ); assert( (pRecTerm->selFlags & SF_Recursive)==0 ); assert( pRecTerm->pNext!=0 ); assert( (pRecTerm->pNext->selFlags & SF_Recursive)!=0 ); assert( pRecTerm->pWith==0 ); pRecTerm->pWith = pSel->pWith; - sqlite3WalkSelect(pWalker, pRecTerm); + rc = sqlite3WalkSelect(pWalker, pRecTerm); pRecTerm->pWith = 0; + if( rc ){ + pParse->pWith = pSavedWith; + return 2; + } }else{ - sqlite3WalkSelect(pWalker, pSel); + if( sqlite3WalkSelect(pWalker, pSel) ){ + pParse->pWith = pSavedWith; + return 2; + } } pParse->pWith = pWith; @@ -4902,7 +5281,7 @@ static int withExpand( pCte->zName, pEList->nExpr, pCte->pCols->nExpr ); pParse->pWith = pSavedWith; - return SQLITE_ERROR; + return 2; } pEList = pCte->pCols; } @@ -4918,9 +5297,9 @@ static int withExpand( } pCte->zCteErr = 0; pParse->pWith = pSavedWith; + return 1; /* Success */ } - - return SQLITE_OK; + return 0; /* No match */ } #endif @@ -4933,7 +5312,7 @@ static int withExpand( ** sqlite3SelectExpand() when walking a SELECT tree to resolve table ** names and other FROM clause elements. */ -static void selectPopWith(Walker *pWalker, Select *p){ +void sqlite3SelectPopWith(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; if( OK_IF_ALWAYS_TRUE(pParse->pWith) && p->pPrior==0 ){ With *pWith = findRightmost(p)->pWith; @@ -4943,8 +5322,6 @@ static void selectPopWith(Walker *pWalker, Select *p){ } } } -#else -#define selectPopWith 0 #endif /* @@ -4954,7 +5331,7 @@ static void selectPopWith(Walker *pWalker, Select *p){ ** SQLITE_OK is returned. Otherwise, if an OOM error is encountered, ** SQLITE_NOMEM. */ -int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFrom){ +int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ Select *pSel = pFrom->pSelect; Table *pTab; @@ -4971,7 +5348,13 @@ int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFrom){ sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); pTab->iPKey = -1; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); - pTab->tabFlags |= TF_Ephemeral; +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW + /* The usual case - do not allow ROWID on a subquery */ + pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; +#else + pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */ +#endif + return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; } @@ -5002,10 +5385,10 @@ int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFrom){ */ static int selectExpander(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; - int i, j, k; + int i, j, k, rc; SrcList *pTabList; ExprList *pEList; - struct SrcList_item *pFrom; + SrcItem *pFrom; sqlite3 *db = pParse->db; Expr *pE, *pRight, *pExpr; u16 selFlags = p->selFlags; @@ -5025,6 +5408,15 @@ static int selectExpander(Walker *pWalker, Select *p){ } pTabList = p->pSrc; pEList = p->pEList; + if( pParse->pWith && (p->selFlags & SF_View) ){ + if( p->pWith==0 ){ + p->pWith = (With*)sqlite3DbMallocZero(db, sizeof(With)); + if( p->pWith==0 ){ + return WRC_Abort; + } + } + p->pWith->bView = 1; + } sqlite3WithPush(pParse, p->pWith, 0); /* Make sure cursor numbers have been assigned to all entries in @@ -5041,10 +5433,6 @@ static int selectExpander(Walker *pWalker, Select *p){ assert( pFrom->fg.isRecursive==0 || pFrom->pTab!=0 ); if( pFrom->pTab ) continue; assert( pFrom->fg.isRecursive==0 ); -#ifndef SQLITE_OMIT_CTE - if( withExpand(pWalker, pFrom) ) return WRC_Abort; - if( pFrom->pTab ) {} else -#endif if( pFrom->zName==0 ){ #ifndef SQLITE_OMIT_SUBQUERY Select *pSel = pFrom->pSelect; @@ -5053,6 +5441,12 @@ static int selectExpander(Walker *pWalker, Select *p){ assert( pFrom->pTab==0 ); if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort; if( sqlite3ExpandSubquery(pParse, pFrom) ) return WRC_Abort; +#endif +#ifndef SQLITE_OMIT_CTE + }else if( (rc = resolveFromTermToCte(pParse, pWalker, pFrom))!=0 ){ + if( rc>1 ) return WRC_Abort; + pTab = pFrom->pTab; + assert( pTab!=0 ); #endif }else{ /* An ordinary table or view name in the FROM clause */ @@ -5075,11 +5469,15 @@ static int selectExpander(Walker *pWalker, Select *p){ u8 eCodeOrig = pWalker->eCode; if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; assert( pFrom->pSelect==0 ); - if( pTab->pSelect && (db->flags & SQLITE_EnableView)==0 ){ + if( pTab->pSelect + && (db->flags & SQLITE_EnableView)==0 + && pTab->pSchema!=db->aDb[1].pSchema + ){ sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited", pTab->zName); } #ifndef SQLITE_OMIT_VIRTUALTABLE + assert( SQLITE_VTABRISK_Normal==1 && SQLITE_VTABRISK_High==2 ); if( IsVirtual(pTab) && pFrom->fg.fromDDL && ALWAYS(pTab->pVTable!=0) @@ -5101,7 +5499,7 @@ static int selectExpander(Walker *pWalker, Select *p){ } /* Locate the index named by the INDEXED BY clause, if any. */ - if( sqlite3IndexedByLookup(pParse, pFrom) ){ + if( pFrom->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pFrom) ){ return WRC_Abort; } } @@ -5320,7 +5718,7 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ sqlite3WalkSelect(&w, pSelect); } w.xSelectCallback = selectExpander; - w.xSelectCallback2 = selectPopWith; + w.xSelectCallback2 = sqlite3SelectPopWith; w.eCode = 0; sqlite3WalkSelect(&w, pSelect); } @@ -5344,7 +5742,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ Parse *pParse; int i; SrcList *pTabList; - struct SrcList_item *pFrom; + SrcItem *pFrom; assert( p->selFlags & SF_Resolved ); if( p->selFlags & SF_HasTypeInfo ) return; @@ -5453,8 +5851,10 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ pFunc->iDistinct = -1; }else{ KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pE->x.pList,0,0); - sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0, - (char*)pKeyInfo, P4_KEYINFO); + pFunc->iDistAddr = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + pFunc->iDistinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO); + ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(DISTINCT)", + pFunc->pFunc->zName)); } } } @@ -5486,7 +5886,12 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ ** registers if register regAcc contains 0. The caller will take care ** of setting and clearing regAcc. */ -static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ +static void updateAccumulator( + Parse *pParse, + int regAcc, + AggInfo *pAggInfo, + int eDistinctType +){ Vdbe *v = pParse->pVdbe; int i; int regHit = 0; @@ -5532,13 +5937,12 @@ static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ nArg = 0; regAgg = 0; } - if( pF->iDistinct>=0 ){ + if( pF->iDistinct>=0 && pList ){ if( addrNext==0 ){ addrNext = sqlite3VdbeMakeLabel(pParse); } - testcase( nArg==0 ); /* Error condition */ - testcase( nArg>1 ); /* Also an error */ - codeDistinct(pParse, pF->iDistinct, addrNext, 1, regAgg); + pF->iDistinct = codeDistinct(pParse, eDistinctType, + pF->iDistinct, addrNext, pList, regAgg); } if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ CollSeq *pColl = 0; @@ -5590,7 +5994,7 @@ static void explainSimpleCount( ){ if( pParse->explain==2 ){ int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx))); - sqlite3VdbeExplain(pParse, 0, "SCAN TABLE %s%s%s", + sqlite3VdbeExplain(pParse, 0, "SCAN %s%s%s", pTab->zName, bCover ? " USING COVERING INDEX " : "", bCover ? pIdx->zName : "" @@ -5656,7 +6060,7 @@ static void havingToWhere(Parse *pParse, Select *p){ sWalker.u.pSelect = p; sqlite3WalkExpr(&sWalker, p->pHaving); #if SELECTTRACE_ENABLED - if( sWalker.eCode && (sqlite3_unsupported_selecttrace & 0x100)!=0 ){ + if( sWalker.eCode && (sqlite3SelectTrace & 0x100)!=0 ){ SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -5668,11 +6072,13 @@ static void havingToWhere(Parse *pParse, Select *p){ ** If it is, then return the SrcList_item for the prior view. If it is not, ** then return 0. */ -static struct SrcList_item *isSelfJoinView( +static SrcItem *isSelfJoinView( SrcList *pTabList, /* Search for self-joins in this FROM clause */ - struct SrcList_item *pThis /* Search for prior reference to this subquery */ + SrcItem *pThis /* Search for prior reference to this subquery */ ){ - struct SrcList_item *pItem; + SrcItem *pItem; + assert( pThis->pSelect!=0 ); + if( pThis->pSelect->selFlags & SF_PushDown ) return 0; for(pItem = pTabList->a; pItempSelect==0 ) continue; @@ -5688,9 +6094,7 @@ static struct SrcList_item *isSelfJoinView( ** names in the same FROM clause. */ continue; } - if( sqlite3ExprCompare(0, pThis->pSelect->pWhere, pS1->pWhere, -1) - || sqlite3ExprCompare(0, pThis->pSelect->pHaving, pS1->pHaving, -1) - ){ + if( pItem->pSelect->selFlags & SF_PushDown ){ /* The view was modified by some other optimization such as ** pushDownWhereTerms() */ continue; @@ -5700,6 +6104,15 @@ static struct SrcList_item *isSelfJoinView( return 0; } +/* +** Deallocate a single AggInfo object +*/ +static void agginfoFree(sqlite3 *db, AggInfo *p){ + sqlite3DbFree(db, p->aCol); + sqlite3DbFree(db, p->aFunc); + sqlite3DbFreeNN(db, p); +} + #ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION /* ** Attempt to transform a query of the form @@ -5778,7 +6191,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ p->selFlags &= ~SF_Aggregate; #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x400 ){ + if( sqlite3SelectTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -5831,7 +6244,7 @@ int sqlite3Select( if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; #if SELECTTRACE_ENABLED SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain)); - if( sqlite3_unsupported_selecttrace & 0x100 ){ + if( sqlite3SelectTrace & 0x100 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -5845,8 +6258,19 @@ int sqlite3Select( pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ - sqlite3ExprListDelete(db, p->pOrderBy); - p->pOrderBy = 0; + if( p->pOrderBy ){ +#if SELECTTRACE_ENABLED + SELECTTRACE(1,pParse,p, ("dropping superfluous ORDER BY:\n")); + if( sqlite3SelectTrace & 0x100 ){ + sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); + } +#endif + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprListDelete, + p->pOrderBy); + testcase( pParse->earlyCleanup ); + p->pOrderBy = 0; + } p->selFlags &= ~SF_Distinct; p->selFlags |= SF_NoopOrderBy; } @@ -5856,7 +6280,7 @@ int sqlite3Select( } assert( p->pEList!=0 ); #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x104 ){ + if( sqlite3SelectTrace & 0x104 ){ SELECTTRACE(0x104,pParse,p, ("after name resolution:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -5867,9 +6291,9 @@ int sqlite3Select( ** In this case, it is an error if the target object (pSrc->a[0]) name ** or alias is duplicated within FROM clause (pSrc->a[1..n]). */ if( p->selFlags & SF_UpdateFrom ){ - struct SrcList_item *p0 = &p->pSrc->a[0]; + SrcItem *p0 = &p->pSrc->a[0]; for(i=1; ipSrc->nSrc; i++){ - struct SrcList_item *p1 = &p->pSrc->a[i]; + SrcItem *p1 = &p->pSrc->a[i]; if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){ sqlite3ErrorMsg(pParse, "target object/alias may not appear in FROM clause: %s", @@ -5881,17 +6305,16 @@ int sqlite3Select( } if( pDest->eDest==SRT_Output ){ - generateColumnNames(pParse, p); + sqlite3GenerateColumnNames(pParse, p); } #ifndef SQLITE_OMIT_WINDOWFUNC - rc = sqlite3WindowRewrite(pParse, p); - if( rc ){ + if( sqlite3WindowRewrite(pParse, p) ){ assert( db->mallocFailed || pParse->nErr>0 ); goto select_end; } #if SELECTTRACE_ENABLED - if( p->pWin && (sqlite3_unsupported_selecttrace & 0x108)!=0 ){ + if( p->pWin && (sqlite3SelectTrace & 0x108)!=0 ){ SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -5907,7 +6330,7 @@ int sqlite3Select( */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) for(i=0; !p->pPrior && inSrc; i++){ - struct SrcList_item *pItem = &pTabList->a[i]; + SrcItem *pItem = &pTabList->a[i]; Select *pSub = pItem->pSelect; Table *pTab = pItem->pTab; @@ -5998,7 +6421,7 @@ int sqlite3Select( rc = multiSelect(pParse, p, pDest); #if SELECTTRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end compound-select processing\n")); - if( (sqlite3_unsupported_selecttrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -6012,12 +6435,13 @@ int sqlite3Select( ** as the equivalent optimization will be handled by query planner in ** sqlite3WhereBegin(). */ - if( pTabList->nSrc>1 + if( p->pWhere!=0 + && p->pWhere->op==TK_AND && OptimizationEnabled(db, SQLITE_PropagateConst) && propagateConstants(pParse, p) ){ #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x100 ){ + if( sqlite3SelectTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p,("After constant propagation:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6041,7 +6465,8 @@ int sqlite3Select( ** (2) Generate code for all sub-queries */ for(i=0; inSrc; i++){ - struct SrcList_item *pItem = &pTabList->a[i]; + SrcItem *pItem = &pTabList->a[i]; + SrcItem *pPrior; SelectDest dest; Select *pSub; #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) @@ -6074,19 +6499,8 @@ int sqlite3Select( pSub = pItem->pSelect; if( pSub==0 ) continue; - /* The code for a subquery should only be generated once, though it is - ** technically harmless for it to be generated multiple times. The - ** following assert() will detect if something changes to cause - ** the same subquery to be coded multiple times, as a signal to the - ** developers to try to optimize the situation. - ** - ** Update 2019-07-24: - ** See ticket https://sqlite.org/src/tktview/c52b09c7f38903b1311cec40. - ** The dbsqlfuzz fuzzer found a case where the same subquery gets - ** coded twice. So this assert() now becomes a testcase(). It should - ** be very rare, though. - */ - testcase( pItem->addrFillSub!=0 ); + /* The code for a subquery should only be generated once. */ + assert( pItem->addrFillSub==0 ); /* Increment Parse.nHeight by the height of the largest expression ** tree referred to by this, the parent select. The child select @@ -6101,16 +6515,18 @@ int sqlite3Select( ** inside the subquery. This can help the subquery to run more efficiently. */ if( OptimizationEnabled(db, SQLITE_PushDown) + && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor, (pItem->fg.jointype & JT_OUTER)!=0) ){ #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x100 ){ + if( sqlite3SelectTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p, ("After WHERE-clause push-down into subquery %d:\n", pSub->selId)); sqlite3TreeViewSelect(0, p, 0); } #endif + assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 ); }else{ SELECTTRACE(0x100,pParse,p,("Push-down not possible\n")); } @@ -6120,16 +6536,18 @@ int sqlite3Select( /* Generate code to implement the subquery ** - ** The subquery is implemented as a co-routine if the subquery is - ** guaranteed to be the outer loop (so that it does not need to be - ** computed more than once) + ** The subquery is implemented as a co-routine if: + ** (1) the subquery is guaranteed to be the outer loop (so that + ** it does not need to be computed more than once), and + ** (2) the subquery is not a CTE that should be materialized ** - ** TODO: Are there other reasons beside (1) to use a co-routine + ** TODO: Are there other reasons beside (1) and (2) to use a co-routine ** implementation? */ if( i==0 && (pTabList->nSrc==1 || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */ + && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */ ){ /* Implement a co-routine that will return a single row of the result ** set on each invocation. @@ -6138,10 +6556,10 @@ int sqlite3Select( pItem->regReturn = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop); - VdbeComment((v, "%s", pItem->pTab->zName)); + VdbeComment((v, "%!S", pItem)); pItem->addrFillSub = addrTop; sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn); - ExplainQueryPlan((pParse, 1, "CO-ROUTINE %u", pSub->selId)); + ExplainQueryPlan((pParse, 1, "CO-ROUTINE %!S", pItem)); sqlite3Select(pParse, pSub, &dest); pItem->pTab->nRowLogEst = pSub->nSelectRow; pItem->fg.viaCoroutine = 1; @@ -6149,18 +6567,33 @@ int sqlite3Select( sqlite3VdbeEndCoroutine(v, pItem->regReturn); sqlite3VdbeJumpHere(v, addrTop-1); sqlite3ClearTempRegCache(pParse); + }else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){ + /* This is a CTE for which materialization code has already been + ** generated. Invoke the subroutine to compute the materialization, + ** the make the pItem->iCursor be a copy of the ephemerial table that + ** holds the result of the materialization. */ + CteUse *pCteUse = pItem->u2.pCteUse; + sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); + if( pItem->iCursor!=pCteUse->iCur ){ + sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur); + } + pSub->nSelectRow = pCteUse->nRowEst; + }else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){ + /* This view has already been materialized by a prior entry in + ** this same FROM clause. Reuse it. */ + if( pPrior->addrFillSub ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub); + } + sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); + pSub->nSelectRow = pPrior->pSelect->nSelectRow; }else{ - /* Generate a subroutine that will fill an ephemeral table with - ** the content of this subquery. pItem->addrFillSub will point - ** to the address of the generated subroutine. pItem->regReturn - ** is a register allocated to hold the subroutine return address - */ + /* Materialize the view. If the view is not correlated, generate a + ** subroutine to do the materialization so that subsequent uses of + ** the same view can reuse the materialization. */ int topAddr; int onceAddr = 0; int retAddr; - struct SrcList_item *pPrior; - testcase( pItem->addrFillSub==0 ); /* Ticket c52b09c7f38903b1311 */ pItem->regReturn = ++pParse->nMem; topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn); pItem->addrFillSub = topAddr+1; @@ -6169,26 +6602,26 @@ int sqlite3Select( ** a trigger, then we only need to compute the value of the subquery ** once. */ onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); - VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName)); + VdbeComment((v, "materialize %!S", pItem)); }else{ - VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName)); - } - pPrior = isSelfJoinView(pTabList, pItem); - if( pPrior ){ - sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); - assert( pPrior->pSelect!=0 ); - pSub->nSelectRow = pPrior->pSelect->nSelectRow; - }else{ - sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); - ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId)); - sqlite3Select(pParse, pSub, &dest); + VdbeNoopComment((v, "materialize %!S", pItem)); } + sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); + ExplainQueryPlan((pParse, 1, "MATERIALIZE %!S", pItem)); + sqlite3Select(pParse, pSub, &dest); pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn); - VdbeComment((v, "end %s", pItem->pTab->zName)); + VdbeComment((v, "end %!S", pItem)); sqlite3VdbeChangeP1(v, topAddr, retAddr); sqlite3ClearTempRegCache(pParse); + if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){ + CteUse *pCteUse = pItem->u2.pCteUse; + pCteUse->addrM9e = pItem->addrFillSub; + pCteUse->regRtn = pItem->regReturn; + pCteUse->iCur = pItem->iCursor; + pCteUse->nRowEst = pSub->nSelectRow; + } } if( db->mallocFailed ) goto select_end; pParse->nHeight -= sqlite3SelectExprHeight(p); @@ -6205,7 +6638,7 @@ int sqlite3Select( sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x400 ){ + if( sqlite3SelectTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6241,7 +6674,7 @@ int sqlite3Select( assert( sDistinct.isTnct ); #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x400 ){ + if( sqlite3SelectTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6333,6 +6766,7 @@ int sqlite3Select( sSort.pOrderBy = 0; } } + SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); /* If sorting index that was created by a prior OP_OpenEphemeral ** instruction ended up not being needed, then change the OP_OpenEphemeral @@ -6371,6 +6805,7 @@ int sqlite3Select( /* End the database scan loop. */ + SELECTTRACE(1,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); } }else{ @@ -6441,11 +6876,14 @@ int sqlite3Select( ** SELECT statement. */ pAggInfo = sqlite3DbMallocZero(db, sizeof(*pAggInfo) ); - if( pAggInfo==0 ){ + if( pAggInfo ){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))agginfoFree, pAggInfo); + testcase( pParse->earlyCleanup ); + } + if( db->mallocFailed ){ goto select_end; } - pAggInfo->pNext = pParse->pAggList; - pParse->pAggList = pAggInfo; pAggInfo->selId = p->selId; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; @@ -6489,10 +6927,14 @@ int sqlite3Select( pAggInfo->mxReg = pParse->nMem; if( db->mallocFailed ) goto select_end; #if SELECTTRACE_ENABLED - if( sqlite3_unsupported_selecttrace & 0x400 ){ + if( sqlite3SelectTrace & 0x400 ){ int ii; SELECTTRACE(0x400,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); sqlite3TreeViewSelect(0, p, 0); + if( minMaxFlag ){ + sqlite3DebugPrintf("MIN/MAX Optimization (0x%02x) adds:\n", minMaxFlag); + sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY"); + } for(ii=0; iinColumn; ii++){ sqlite3DebugPrintf("agg-column[%d] iMem=%d\n", ii, pAggInfo->aCol[ii].iMem); @@ -6520,6 +6962,20 @@ int sqlite3Select( int addrSortingIdx; /* The OP_OpenEphemeral for the sorting index */ int addrReset; /* Subroutine for resetting the accumulator */ int regReset; /* Return address register for reset subroutine */ + ExprList *pDistinct = 0; + u16 distFlag = 0; + int eDist = WHERE_DISTINCT_NOOP; + + if( pAggInfo->nFunc==1 + && pAggInfo->aFunc[0].iDistinct>=0 + && pAggInfo->aFunc[0].pFExpr->x.pList + ){ + Expr *pExpr = pAggInfo->aFunc[0].pFExpr->x.pList->a[0].pExpr; + pExpr = sqlite3ExprDup(db, pExpr, 0); + pDistinct = sqlite3ExprListDup(db, pGroupBy, 0); + pDistinct = sqlite3ExprListAppend(pParse, pDistinct, pExpr); + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; + } /* If there is a GROUP BY clause we might need a sorting index to ** implement it. Allocate that sorting index now. If it turns out @@ -6556,10 +7012,15 @@ int sqlite3Select( */ sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); SELECTTRACE(1,pParse,p,("WhereBegin\n")); - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0, - WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0 + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, + WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 ); - if( pWInfo==0 ) goto select_end; + if( pWInfo==0 ){ + sqlite3ExprListDelete(db, pDistinct); + goto select_end; + } + eDist = sqlite3WhereIsDistinct(pWInfo); + SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){ /* The optimizer is able to deliver rows in group by order so ** we do not have to sort. The OP_OpenEphemeral table will be @@ -6608,6 +7069,7 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3ReleaseTempRange(pParse, regBase, nCol); + SELECTTRACE(1,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++; sortOut = sqlite3GetTempReg(pParse); @@ -6675,19 +7137,21 @@ int sqlite3Select( ** the current row */ sqlite3VdbeJumpHere(v, addr1); - updateAccumulator(pParse, iUseFlag, pAggInfo); + updateAccumulator(pParse, iUseFlag, pAggInfo, eDist); sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag); VdbeComment((v, "indicate data in accumulator")); /* End of the loop */ if( groupBySort ){ - sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx, addrTopOfLoop); + sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop); VdbeCoverage(v); }else{ + SELECTTRACE(1,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); sqlite3VdbeChangeToNoop(v, addrSortingIdx); } + sqlite3ExprListDelete(db, pDistinct); /* Output the final row of result */ @@ -6730,7 +7194,11 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag); VdbeComment((v, "indicate accumulator empty")); sqlite3VdbeAddOp1(v, OP_Return, regReset); - + + if( eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = &pAggInfo->aFunc[0]; + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } } /* endif pGroupBy. Begin aggregate queries without GROUP BY: */ else { Table *pTab; @@ -6794,7 +7262,9 @@ int sqlite3Select( explainSimpleCount(pParse, pTab, pBest); }else{ int regAcc = 0; /* "populate accumulators" flag */ - int addrSkip; + ExprList *pDistinct = 0; + u16 distFlag = 0; + int eDist; /* If there are accumulator registers but no min() or max() functions ** without FILTER clauses, allocate register regAcc. Register regAcc @@ -6818,6 +7288,9 @@ int sqlite3Select( regAcc = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc); } + }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){ + pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; } /* This case runs if the aggregate has no GROUP BY clause. The @@ -6837,16 +7310,23 @@ int sqlite3Select( SELECTTRACE(1,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy, - 0, minMaxFlag, 0); + pDistinct, minMaxFlag|distFlag, 0); if( pWInfo==0 ){ goto select_end; } - updateAccumulator(pParse, regAcc, pAggInfo); + SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); + eDist = sqlite3WhereIsDistinct(pWInfo); + updateAccumulator(pParse, regAcc, pAggInfo, eDist); + if( eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = &pAggInfo->aFunc[0]; + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } + if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc); - addrSkip = sqlite3WhereOrderByLimitOptLabel(pWInfo); - if( addrSkip!=sqlite3WhereContinueLabel(pWInfo) ){ - sqlite3VdbeGoto(v, addrSkip); + if( minMaxFlag ){ + sqlite3WhereMinMaxOptEarlyOut(v, pWInfo); } + SELECTTRACE(1,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); finalizeAggFunctions(pParse, pAggInfo); } @@ -6886,20 +7366,20 @@ int sqlite3Select( ** successful coding of the SELECT. */ select_end: + assert( db->mallocFailed==0 || db->mallocFailed==1 ); + pParse->nErr += db->mallocFailed; sqlite3ExprListDelete(db, pMinMaxOrderBy); #ifdef SQLITE_DEBUG if( pAggInfo && !db->mallocFailed ){ for(i=0; inColumn; i++){ Expr *pExpr = pAggInfo->aCol[i].pCExpr; - assert( pExpr!=0 || db->mallocFailed ); - if( pExpr==0 ) continue; + assert( pExpr!=0 ); assert( pExpr->pAggInfo==pAggInfo ); assert( pExpr->iAgg==i ); } for(i=0; inFunc; i++){ Expr *pExpr = pAggInfo->aFunc[i].pFExpr; - assert( pExpr!=0 || db->mallocFailed ); - if( pExpr==0 ) continue; + assert( pExpr!=0 ); assert( pExpr->pAggInfo==pAggInfo ); assert( pExpr->iAgg==i ); } @@ -6908,7 +7388,7 @@ int sqlite3Select( #if SELECTTRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end processing\n")); - if( (sqlite3_unsupported_selecttrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif diff --git a/src/shell.c.in b/src/shell.c.in index 2d98d23c2..0f8de61a1 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -226,6 +226,7 @@ static sqlite3_int64 timeOfDay(void){ static sqlite3_vfs *clockVfs = 0; sqlite3_int64 t; if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); + if( clockVfs==0 ) return 0; /* Never actually happens */ if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ clockVfs->xCurrentTimeInt64(clockVfs, &t); }else{ @@ -1027,6 +1028,7 @@ INCLUDE ../ext/misc/uint.c INCLUDE ../ext/misc/decimal.c INCLUDE ../ext/misc/ieee754.c INCLUDE ../ext/misc/series.c +INCLUDE ../ext/misc/regexp.c #ifdef SQLITE_HAVE_ZLIB INCLUDE ../ext/misc/zipfile.c INCLUDE ../ext/misc/sqlar.c @@ -1085,12 +1087,12 @@ struct ShellState { u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ u8 autoEQPtest; /* autoEQP is in test mode */ u8 autoEQPtrace; /* autoEQP is in trace mode */ - u8 statsOn; /* True to display memory stats before each finalize */ u8 scanstatsOn; /* True to display scan stats before each finalize */ u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */ u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ u8 nEqpLevel; /* Depth of the EQP output graph */ u8 eTraceType; /* SHELL_TRACE_* value for type of trace */ + unsigned statsOn; /* True to display memory stats before each finalize */ unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */ int outCount; /* Revert to stdout when reaching zero */ int cnt; /* Number of records displayed so far */ @@ -2027,6 +2029,7 @@ static int shell_callback( if( azArg==0 ) break; for(i=0; iw ){ w = strlenChar(azArg[i]); } @@ -2591,7 +2594,7 @@ static int display_stats( if( pArg==0 || pArg->out==0 ) return 0; out = pArg->out; - if( pArg->pStmt && (pArg->statsOn & 2) ){ + if( pArg->pStmt && pArg->statsOn==2 ){ int nCol, i, x; sqlite3_stmt *pStmt = pArg->pStmt; char z[100]; @@ -2615,6 +2618,14 @@ static int display_stats( } } + if( pArg->statsOn==3 ){ + if( pArg->pStmt ){ + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); + raw_printf(pArg->out, "VM-steps: %d\n", iCur); + } + return 0; + } + displayStatLine(pArg, "Memory Used:", "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset); displayStatLine(pArg, "Number of Outstanding Allocations:", @@ -2881,31 +2892,18 @@ static void explain_data_delete(ShellState *p){ /* ** Disable and restore .wheretrace and .selecttrace settings. */ -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) -extern unsigned int sqlite3_unsupported_selecttrace; -static int savedSelectTrace; -#endif -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) -extern int sqlite3WhereTrace; -static int savedWhereTrace; -#endif +static unsigned int savedSelectTrace; +static unsigned int savedWhereTrace; static void disable_debug_trace_modes(void){ -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) - savedSelectTrace = sqlite3_unsupported_selecttrace; - sqlite3_unsupported_selecttrace = 0; -#endif -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) - savedWhereTrace = sqlite3WhereTrace; - sqlite3WhereTrace = 0; -#endif + unsigned int zero = 0; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &savedSelectTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &zero); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 2, &savedWhereTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &zero); } static void restore_debug_trace_modes(void){ -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) - sqlite3_unsupported_selecttrace = savedSelectTrace; -#endif -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) - sqlite3WhereTrace = savedWhereTrace; -#endif + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &savedSelectTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace); } /* Create the TEMP table used to store parameter bindings */ @@ -2919,7 +2917,7 @@ static void bind_table_init(ShellState *p){ sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n" " key TEXT PRIMARY KEY,\n" - " value ANY\n" + " value\n" ") WITHOUT ROWID;", 0, 0, 0); sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0); @@ -3067,6 +3065,7 @@ static void exec_prepared_stmt_columnar( if( rc!=SQLITE_ROW ) return; nColumn = sqlite3_column_count(pStmt); nAlloc = nColumn*4; + if( nAlloc<=0 ) nAlloc = 1; azData = sqlite3_malloc64( nAlloc*sizeof(char*) ); if( azData==0 ) shell_out_of_memory(); for(i=0; ip->actualWidth[j] ) p->actualWidth[j] = n; } if( seenInterrupt ) goto columnar_end; + if( nColumn==0 ) goto columnar_end; switch( p->cMode ){ case MODE_Column: { colSep = " "; @@ -3878,7 +3878,7 @@ static const char *(azHelp[]) = { " .ar -tf ARCHIVE # List members of ARCHIVE", " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", " See also:", - " http://sqlite.org/cli.html#sqlar_archive_support", + " http://sqlite.org/cli.html#sqlite_archive_support", #endif #ifndef SQLITE_OMIT_AUTHORIZATION ".auth ON|OFF Show authorizer callbacks", @@ -3895,13 +3895,13 @@ static const char *(azHelp[]) = { ".databases List names and files of attached databases", ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", ".dbinfo ?DB? Show status information about the database", - ".dump ?TABLE? Render database content as SQL", + ".dump ?OBJECTS? Render database content as SQL", " Options:", " --data-only Output only INSERT statements", " --newlines Allow unescaped newline characters in output", " --nosys Omit system tables (ex: \"sqlite_stat1\")", " --preserve-rowids Include ROWID values in the output", - " TABLE is a LIKE pattern for the tables to dump", + " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump", " Additional LIKE patterns can be given in subsequent arguments", ".echo on|off Turn command echo on or off", ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN", @@ -3980,8 +3980,8 @@ static const char *(azHelp[]) = { ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", " Options:", " --append Use appendvfs to append database to the end of FILE", -#ifdef SQLITE_ENABLE_DESERIALIZE - " --deserialize Load into memory useing sqlite3_deserialize()", +#ifndef SQLITE_OMIT_DESERIALIZE + " --deserialize Load into memory using sqlite3_deserialize()", " --hexdb Load the output of \"dbtotxt\" as an in-memory db", " --maxsize N Maximum size for --hexdb or --deserialized database", #endif @@ -4060,7 +4060,11 @@ static const char *(azHelp[]) = { ".shell CMD ARGS... Run CMD ARGS... in a system shell", #endif ".show Show the current values for various settings", - ".stats ?on|off? Show stats or turn stats on or off", + ".stats ?ARG? Show stats or turn stats on or off", + " off Turn off automatic stat display", + " on Turn on automatic stat display", + " stmt Show statement stats", + " vmstep Show the virtual machine step count only", #ifndef SQLITE_NOHAVE_SYSTEM ".system CMD ARGS... Run CMD ARGS... in a system shell", #endif @@ -4298,7 +4302,7 @@ int deduceDatabaseType(const char *zName, int dfltZip){ return rc; } -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE /* ** Reconstruct an in-memory database using the output from the "dbtotxt" ** program. Read content from the file in p->zDbFilename. If p->zDbFilename @@ -4387,7 +4391,7 @@ readHexDb_error: utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine); return 0; } -#endif /* SQLITE_ENABLE_DESERIALIZE */ +#endif /* SQLITE_OMIT_DESERIALIZE */ /* ** Scalar function "shell_int32". The first argument to this function @@ -4613,6 +4617,7 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_completion_init(p->db, 0, 0); sqlite3_uint_init(p->db, 0, 0); sqlite3_decimal_init(p->db, 0, 0); + sqlite3_regexp_init(p->db, 0, 0); sqlite3_ieee_init(p->db, 0, 0); sqlite3_series_init(p->db, 0, 0); #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) @@ -4648,7 +4653,7 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_free(zSql); } -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE else if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){ int rc; @@ -5777,7 +5782,7 @@ static int lintFkeyIndexes( " || fkey_collate_clause(" " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" ", " - " 'SEARCH TABLE ' || s.name || ' USING COVERING INDEX*('" + " 'SEARCH ' || s.name || ' USING COVERING INDEX*('" " || group_concat('*=?', ' AND ') || ')'" ", " " s.name || '(' || group_concat(f.[from], ', ') || ')'" @@ -5797,7 +5802,7 @@ static int lintFkeyIndexes( "GROUP BY s.name, f.id " "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" ; - const char *zGlobIPK = "SEARCH TABLE * USING INTEGER PRIMARY KEY (rowid=?)"; + const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)"; for(i=2; inErr = 0; if( zLike==0 ) zLike = sqlite3_mprintf("true"); zSql = sqlite3_mprintf( - "SELECT name, type, sql FROM sqlite_schema " + "SELECT name, type, sql FROM sqlite_schema AS o " "WHERE (%s) AND type=='table'" " AND sql NOT NULL" " ORDER BY tbl_name='sqlite_sequence', rowid", @@ -7772,7 +7793,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(zSql); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ zSql = sqlite3_mprintf( - "SELECT sql FROM sqlite_schema " + "SELECT sql FROM sqlite_schema AS o " "WHERE (%s) AND sql NOT NULL" " AND type IN ('index','trigger','view')", zLike @@ -7876,16 +7897,17 @@ static int do_meta_command(char *zLine, ShellState *p){ int ctrlCode; /* Integer code for that option */ const char *zUsage; /* Usage notes */ } aCtrl[] = { - { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, - /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ - { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, - { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, - /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ - { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, + { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, + { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, + /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ + { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" }, + { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, + { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ }; int filectrl = -1; int iCtrl = -1; @@ -7972,6 +7994,7 @@ static int do_meta_command(char *zLine, ShellState *p){ isOk = 1; break; } + case SQLITE_FCNTL_DATA_VERSION: case SQLITE_FCNTL_HAS_MOVED: { int x; if( nArg!=2 ) break; @@ -8017,7 +8040,6 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){ ShellState data; - char *zErrMsg = 0; int doStats = 0; memcpy(&data, p, sizeof(data)); data.showHeader = 0; @@ -8039,7 +8061,7 @@ static int do_meta_command(char *zLine, ShellState *p){ " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " "ORDER BY rowid", - callback, &data, &zErrMsg + callback, &data, 0 ); if( rc==SQLITE_OK ){ sqlite3_stmt *pStmt; @@ -8055,12 +8077,12 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ raw_printf(p->out, "ANALYZE sqlite_schema;\n"); sqlite3_exec(p->db, "SELECT 'ANALYZE sqlite_schema'", - callback, &data, &zErrMsg); + callback, &data, 0); data.cMode = data.mode = MODE_Insert; data.zDestTable = "sqlite_stat1"; - shell_exec(&data, "SELECT * FROM sqlite_stat1", &zErrMsg); + shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); data.zDestTable = "sqlite_stat4"; - shell_exec(&data, "SELECT * FROM sqlite_stat4", &zErrMsg); + shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); raw_printf(p->out, "ANALYZE sqlite_schema;\n"); } }else @@ -8680,9 +8702,9 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif /* SQLITE_DEBUG */ if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ - char *zNewFilename; /* Name of the database file to open */ - int iName = 1; /* Index in azArg[] of the filename */ - int newFlag = 0; /* True to delete file before opening */ + char *zNewFilename = 0; /* Name of the database file to open */ + int iName = 1; /* Index in azArg[] of the filename */ + int newFlag = 0; /* True to delete file before opening */ /* Close the existing database */ session_close_all(p); close_db(p->db); @@ -8694,7 +8716,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->openFlags = 0; p->szMax = 0; /* Check for command-line arguments */ - for(iName=1; iNameopenMode = SHELL_OPEN_READONLY; }else if( optionMatch(z, "nofollow") ){ p->openFlags |= SQLITE_OPEN_NOFOLLOW; -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE }else if( optionMatch(z, "deserialize") ){ p->openMode = SHELL_OPEN_DESERIALIZE; }else if( optionMatch(z, "hexdb") ){ p->openMode = SHELL_OPEN_HEXDB; }else if( optionMatch(z, "maxsize") && iName+1szMax = integerValue(azArg[++iName]); -#endif /* SQLITE_ENABLE_DESERIALIZE */ +#endif /* SQLITE_OMIT_DESERIALIZE */ }else if( z[0]=='-' ){ utf8_printf(stderr, "unknown option: %s\n", z); rc = 1; goto meta_command_exit; + }else if( zNewFilename ){ + utf8_printf(stderr, "extra argument: \"%s\"\n", z); + rc = 1; + goto meta_command_exit; + }else{ + zNewFilename = sqlite3_mprintf("%s", z); } } /* If a filename is specified, try to open it first */ - zNewFilename = nArg>iName ? sqlite3_mprintf("%s", azArg[iName]) : 0; if( zNewFilename || p->openMode==SHELL_OPEN_HEXDB ){ if( newFlag ) shellDeleteFile(zNewFilename); p->zDbFilename = zNewFilename; @@ -8746,7 +8773,7 @@ static int do_meta_command(char *zLine, ShellState *p){ && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0)) || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0) ){ - const char *zFile = 0; + char *zFile = 0; int bTxtMode = 0; int i; int eMode = 0; @@ -8776,17 +8803,22 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; goto meta_command_exit; } - }else if( zFile==0 ){ - zFile = z; + }else if( zFile==0 && eMode!='e' && eMode!='x' ){ + zFile = sqlite3_mprintf("%s", z); + if( zFile[0]=='|' ){ + while( i+1out,"ERROR: extra parameter: \"%s\". Usage:\n", azArg[i]); showHelp(p->out, azArg[0]); rc = 1; + sqlite3_free(zFile); goto meta_command_exit; } } - if( zFile==0 ) zFile = "stdout"; + if( zFile==0 ) zFile = sqlite3_mprintf("stdout"); if( bOnce ){ p->outCount = 2; }else{ @@ -8809,7 +8841,8 @@ static int do_meta_command(char *zLine, ShellState *p){ newTempFile(p, "txt"); bTxtMode = 1; } - zFile = p->zTempFile; + sqlite3_free(zFile); + zFile = sqlite3_mprintf("%s", p->zTempFile); } #endif /* SQLITE_NOHAVE_SYSTEM */ if( zFile[0]=='|' ){ @@ -8841,6 +8874,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } } + sqlite3_free(zFile); }else if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){ @@ -9024,6 +9058,11 @@ static int do_meta_command(char *zLine, ShellState *p){ goto meta_command_exit; } if( azArg[1][0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + rc = 1; + p->out = stdout; +#else p->in = popen(azArg[1]+1, "r"); if( p->in==0 ){ utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); @@ -9032,6 +9071,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = process_input(p); pclose(p->in); } +#endif }else if( notNormalFile(azArg[1]) || (p->in = fopen(azArg[1], "rb"))==0 ){ utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; @@ -9246,11 +9286,10 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ - sqlite3_unsupported_selecttrace = nArg>=2 ? (int)integerValue(azArg[1]) : 0xffff; + unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); }else -#endif #if defined(SQLITE_ENABLE_SESSION) if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){ @@ -9731,6 +9770,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='s' && strncmp(azArg[0], "show", n)==0 ){ static const char *azBool[] = { "off", "on", "trigger", "full"}; + const char *zOut; int i; if( nArg!=1 ){ raw_printf(stderr, "Usage: .show\n"); @@ -9755,7 +9795,13 @@ static int do_meta_command(char *zLine, ShellState *p){ utf8_printf(p->out,"%12.12s: ", "rowseparator"); output_c_string(p->out, p->rowSeparator); raw_printf(p->out, "\n"); - utf8_printf(p->out, "%12.12s: %s\n","stats", azBool[p->statsOn!=0]); + switch( p->statsOn ){ + case 0: zOut = "off"; break; + default: zOut = "on"; break; + case 2: zOut = "stmt"; break; + case 3: zOut = "vmstep"; break; + } + utf8_printf(p->out, "%12.12s: %s\n","stats", zOut); utf8_printf(p->out, "%12.12s: ", "width"); for (i=0;inWidth;i++) { raw_printf(p->out, "%d ", p->colWidth[i]); @@ -9767,11 +9813,17 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ if( nArg==2 ){ - p->statsOn = (u8)booleanValue(azArg[1]); + if( strcmp(azArg[1],"stmt")==0 ){ + p->statsOn = 2; + }else if( strcmp(azArg[1],"vmstep")==0 ){ + p->statsOn = 3; + }else{ + p->statsOn = (u8)booleanValue(azArg[1]); + } }else if( nArg==1 ){ display_stats(p->db, p, 0); }else{ - raw_printf(stderr, "Usage: .stats ?on|off?\n"); + raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n"); rc = 1; } }else @@ -9923,6 +9975,7 @@ static int do_meta_command(char *zLine, ShellState *p){ { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, "" }, { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, "SEED ?db?" }, { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, "" }, + { "tune", SQLITE_TESTCTRL_TUNE, "ID VALUE" }, }; int testctrl = -1; int iCtrl = -1; @@ -9976,7 +10029,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* sqlite3_test_control(int, db, int) */ case SQLITE_TESTCTRL_OPTIMIZATIONS: if( nArg==3 ){ - int opt = (int)strtol(azArg[2], 0, 0); + unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0); rc2 = sqlite3_test_control(testctrl, p->db, opt); isOk = 3; } @@ -10067,11 +10120,40 @@ static int do_meta_command(char *zLine, ShellState *p){ } #ifdef YYCOVERAGE - case SQLITE_TESTCTRL_PARSER_COVERAGE: + case SQLITE_TESTCTRL_PARSER_COVERAGE: { if( nArg==2 ){ sqlite3_test_control(testctrl, p->out); isOk = 3; } + break; + } +#endif +#ifdef SQLITE_DEBUG + case SQLITE_TESTCTRL_TUNE: { + if( nArg==4 ){ + int id = (int)integerValue(azArg[2]); + int val = (int)integerValue(azArg[3]); + sqlite3_test_control(testctrl, id, &val); + isOk = 3; + }else if( nArg==3 ){ + int id = (int)integerValue(azArg[2]); + sqlite3_test_control(testctrl, -id, &rc2); + isOk = 1; + }else if( nArg==2 ){ + int id = 1; + while(1){ + int val = 0; + rc2 = sqlite3_test_control(testctrl, -id, &val); + if( rc2!=SQLITE_OK ) break; + if( id>1 ) utf8_printf(p->out, " "); + utf8_printf(p->out, "%d: %d", id, val); + id++; + } + if( id>1 ) utf8_printf(p->out, "\n"); + isOk = 3; + } + break; + } #endif } } @@ -10305,11 +10387,10 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){ - sqlite3WhereTrace = nArg>=2 ? booleanValue(azArg[1]) : 0xff; + unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); }else -#endif if( c=='w' && strncmp(azArg[0], "width", n)==0 ){ int j; @@ -10670,7 +10751,7 @@ static const char zOptions[] = " -column set output mode to 'column'\n" " -cmd COMMAND run \"COMMAND\" before reading stdin\n" " -csv set output mode to 'csv'\n" -#if defined(SQLITE_ENABLE_DESERIALIZE) +#if !defined(SQLITE_OMIT_DESERIALIZE) " -deserialize open the database using sqlite3_deserialize()\n" #endif " -echo print commands before execution\n" @@ -10687,7 +10768,7 @@ static const char zOptions[] = " -list set output mode to 'list'\n" " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" " -markdown set output mode to 'markdown'\n" -#if defined(SQLITE_ENABLE_DESERIALIZE) +#if !defined(SQLITE_OMIT_DESERIALIZE) " -maxsize N maximum size for a --deserialize database\n" #endif " -memtrace trace all memory allocations and deallocations\n" @@ -10798,7 +10879,8 @@ static char *cmdline_option_value(int argc, char **argv, int i){ } #ifndef SQLITE_SHELL_IS_UTF8 -# if (defined(_WIN32) || defined(WIN32)) && defined(_MSC_VER) +# if (defined(_WIN32) || defined(WIN32)) \ + && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) # define SQLITE_SHELL_IS_UTF8 (0) # else # define SQLITE_SHELL_IS_UTF8 (1) @@ -11016,7 +11098,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif }else if( strcmp(z,"-append")==0 ){ data.openMode = SHELL_OPEN_APPENDVFS; -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE }else if( strcmp(z,"-deserialize")==0 ){ data.openMode = SHELL_OPEN_DESERIALIZE; }else if( strcmp(z,"-maxsize")==0 && i+1 +** +**
  • [[SQLITE_FCNTL_EXTERNAL_READER]] +** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect +** whether or not there is a database client in another process with a wal-mode +** transaction open on the database or not. It is only available on unix.The +** (void*) argument passed with this file-control should be a pointer to a +** value of type (int). The integer value is set to 1 if the database is a wal +** mode database and there exists at least one client in another process that +** currently has an SQL transaction open on the database. It is set to 0 if +** the database is not a wal-mode db, or if there is no such connection in any +** other process. This opcode cannot be used to detect transactions opened +** by clients within the current process, only within other processes. +** +** +**
  • [[SQLITE_FCNTL_CKSM_FILE]] +** Used by the cksmvfs VFS module only. +** */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 @@ -1167,6 +1184,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_CKPT_DONE 37 #define SQLITE_FCNTL_RESERVE_BYTES 38 #define SQLITE_FCNTL_CKPT_START 39 +#define SQLITE_FCNTL_EXTERNAL_READER 40 +#define SQLITE_FCNTL_CKSM_FILE 41 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -2115,7 +2134,13 @@ struct sqlite3_mem_methods { ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in -** which case the trigger setting is not reported back. +** which case the trigger setting is not reported back. +** +**

    Originally this option disabled all triggers. ^(However, since +** SQLite version 3.35.0, TEMP triggers are still allowed even if +** this option is off. So, in other words, this option now only disables +** triggers in the main database schema or in the schemas of ATTACH-ed +** databases.)^ ** ** [[SQLITE_DBCONFIG_ENABLE_VIEW]] **

    SQLITE_DBCONFIG_ENABLE_VIEW
    @@ -2126,7 +2151,13 @@ struct sqlite3_mem_methods { ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether views are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in -** which case the view setting is not reported back. +** which case the view setting is not reported back. +** +**

    Originally this option disabled all views. ^(However, since +** SQLite version 3.35.0, TEMP views are still allowed even if +** this option is off. So, in other words, this option now only disables +** views in the main database schema or in the schemas of ATTACH-ed +** databases.)^ ** ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] **

    SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
    @@ -3499,6 +3530,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** that uses dot-files in place of posix advisory locking. **
  • file:data.db?mode=readonly ** An error. "readonly" is not a valid option for the "mode" parameter. +** Use "ro" instead: "file:data.db?mode=ro". **
    ** ** ^URI hexadecimal escape sequences (%HH) are supported within the path and @@ -4166,6 +4198,15 @@ const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and ** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so ** sqlite3_stmt_readonly() returns false for those commands. +** +** ^This routine returns false if there is any possibility that the +** statement might change the database file. ^A false return does +** not guarantee that the statement will change the database file. +** ^For example, an UPDATE statement might have a WHERE clause that +** makes it a no-op, but the sqlite3_stmt_readonly() result would still +** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a +** read-only no-op if the table already exists, but +** sqlite3_stmt_readonly() still returns false for such a statement. */ int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); @@ -4335,18 +4376,22 @@ typedef struct sqlite3_context sqlite3_context; ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** -** ^The fifth argument to the BLOB and string binding interfaces -** is a destructor used to dispose of the BLOB or -** string after SQLite has finished with it. ^The destructor is called -** to dispose of the BLOB or string even if the call to the bind API fails, -** except the destructor is not called if the third parameter is a NULL -** pointer or the fourth parameter is negative. -** ^If the fifth argument is -** the special value [SQLITE_STATIC], then SQLite assumes that the -** information is in static, unmanaged space and does not need to be freed. -** ^If the fifth argument has the value [SQLITE_TRANSIENT], then -** SQLite makes its own private copy of the data immediately, before -** the sqlite3_bind_*() routine returns. +** ^The fifth argument to the BLOB and string binding interfaces controls +** or indicates the lifetime of the object referenced by the third parameter. +** These three options exist: +** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished +** with it may be passed. ^It is called to dispose of the BLOB or string even +** if the call to the bind API fails, except the destructor is not called if +** the third parameter is a NULL pointer or the fourth parameter is negative. +** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that +** the application remains responsible for disposing of the object. ^In this +** case, the object and the provided pointer to it must remain valid until +** either the prepared statement is finalized or the same SQL parameter is +** bound to something else, whichever occurs sooner. +** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the +** object is to be copied prior to the return from sqlite3_bind_*(). ^The +** object and pointer to it must remain valid until then. ^SQLite will then +** manage the lifetime of its private copy. ** ** ^The sixth argument to sqlite3_bind_text64() must be one of ** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] @@ -5088,7 +5133,6 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, ** index expressions, or the WHERE clause of partial indexes. ** -** ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for ** all application-defined SQL functions that do not need to be ** used inside of triggers, view, CHECK constraints, or other elements of @@ -5098,7 +5142,6 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** a database file to include invocations of the function with parameters ** chosen by the attacker, which the application will then execute when ** the database file is opened and read. -** ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ @@ -7765,7 +7808,9 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_PRNG_SEED 28 #define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 #define SQLITE_TESTCTRL_SEEK_COUNT 30 -#define SQLITE_TESTCTRL_LAST 30 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_TRACEFLAGS 31 +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LAST 32 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking @@ -9517,6 +9562,15 @@ int sqlite3_db_cacheflush(sqlite3*); ** triggers; or 2 for changes resulting from triggers called by top-level ** triggers; and so forth. ** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE. This is because the +** in this case the new values are not available. In this case, when a +** callback made with op==SQLITE_DELETE is actuall a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** ** See also: [sqlite3_update_hook()] */ #if defined(SQLITE_ENABLE_PREUPDATE_HOOK) @@ -9537,6 +9591,7 @@ int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); int sqlite3_preupdate_count(sqlite3 *); int sqlite3_preupdate_depth(sqlite3 *); int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +int sqlite3_preupdate_blobwrite(sqlite3 *); #endif /* @@ -9775,8 +9830,8 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory ** allocation error occurs. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ unsigned char *sqlite3_serialize( sqlite3 *db, /* The database connection */ @@ -9827,8 +9882,8 @@ unsigned char *sqlite3_serialize( ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ int sqlite3_deserialize( sqlite3 *db, /* The database connection */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 467062277..91af412bb 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -119,6 +119,18 @@ # define MSVC_VERSION 0 #endif +/* +** Some C99 functions in "math.h" are only present for MSVC when its version +** is associated with Visual Studio 2013 or higher. +*/ +#ifndef SQLITE_HAVE_C99_MATH_FUNCS +# if MSVC_VERSION==0 || MSVC_VERSION>=1800 +# define SQLITE_HAVE_C99_MATH_FUNCS (1) +# else +# define SQLITE_HAVE_C99_MATH_FUNCS (0) +# endif +#endif + /* Needed for various definitions... */ #if defined(__GNUC__) && !defined(_GNU_SOURCE) # define _GNU_SOURCE @@ -202,7 +214,8 @@ #ifndef __has_extension # define __has_extension(x) 0 /* compatibility with non-clang compilers */ #endif -#if GCC_VERSION>=4007000 || __has_extension(c_atomic) +#if GCC_VERSION>=4007000 || \ + (__has_extension(c_atomic) && __has_extension(c_atomic_store_n)) # define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED) # define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED) #else @@ -984,15 +997,14 @@ typedef INT16_TYPE LogEst; ** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not ** the Select query generator tracing logic is turned on. */ -#if defined(SQLITE_ENABLE_SELECTTRACE) -# define SELECTTRACE_ENABLED 1 -#else -# define SELECTTRACE_ENABLED 0 +#if !defined(SQLITE_AMALGAMATION) +extern u32 sqlite3SelectTrace; #endif -#if defined(SQLITE_ENABLE_SELECTTRACE) +#if defined(SQLITE_DEBUG) \ + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE)) # define SELECTTRACE_ENABLED 1 # define SELECTTRACE(K,P,S,X) \ - if(sqlite3_unsupported_selecttrace&(K)) \ + if(sqlite3SelectTrace&(K)) \ sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ sqlite3DebugPrintf X #else @@ -1000,6 +1012,19 @@ typedef INT16_TYPE LogEst; # define SELECTTRACE_ENABLED 0 #endif +/* +** Macros for "wheretrace" +*/ +extern u32 sqlite3WhereTrace; +#if defined(SQLITE_DEBUG) \ + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE)) +# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X +# define WHERETRACE_ENABLED 1 +#else +# define WHERETRACE(K,X) +#endif + + /* ** An instance of the following structure is used to store the busy-handler ** callback for a given sqlite handle. @@ -1111,7 +1136,10 @@ typedef struct AutoincInfo AutoincInfo; typedef struct Bitvec Bitvec; typedef struct CollSeq CollSeq; typedef struct Column Column; +typedef struct Cte Cte; +typedef struct CteUse CteUse; typedef struct Db Db; +typedef struct DbFixer DbFixer; typedef struct Schema Schema; typedef struct Expr Expr; typedef struct ExprList ExprList; @@ -1129,14 +1157,17 @@ typedef struct LookasideSlot LookasideSlot; typedef struct Module Module; typedef struct NameContext NameContext; typedef struct Parse Parse; +typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; typedef struct PrintfArguments PrintfArguments; typedef struct RenameToken RenameToken; +typedef struct Returning Returning; typedef struct RowSet RowSet; typedef struct Savepoint Savepoint; typedef struct Select Select; typedef struct SQLiteThread SQLiteThread; typedef struct SelectDest SelectDest; +typedef struct SrcItem SrcItem; typedef struct SrcList SrcList; typedef struct sqlite3_str StrAccum; /* Internal alias for sqlite3_str */ typedef struct Table Table; @@ -1450,6 +1481,11 @@ void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); #endif /* SQLITE_OMIT_DEPRECATED */ #define SQLITE_TRACE_NONLEGACY_MASK 0x0f /* Normal flags */ +/* +** Maximum number of sqlite3.aDb[] entries. This is the number of attached +** databases plus 2 for "main" and "temp". +*/ +#define SQLITE_MAX_DB (SQLITE_MAX_ATTACHED+2) /* ** Each database connection is an instance of the following structure. @@ -1470,7 +1506,7 @@ struct sqlite3 { int errCode; /* Most recent error code (SQLITE_*) */ int errMask; /* & result codes with this before returning */ int iSysErrno; /* Errno value from last system error */ - u16 dbOptFlags; /* Flags to enable/disable optimizations */ + u32 dbOptFlags; /* Flags to enable/disable optimizations */ u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ @@ -1677,24 +1713,26 @@ struct sqlite3 { ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to ** selectively disable various optimizations. */ -#define SQLITE_QueryFlattener 0x0001 /* Query flattening */ -#define SQLITE_WindowFunc 0x0002 /* Use xInverse for window functions */ -#define SQLITE_GroupByOrder 0x0004 /* GROUPBY cover of ORDERBY */ -#define SQLITE_FactorOutConst 0x0008 /* Constant factoring */ -#define SQLITE_DistinctOpt 0x0010 /* DISTINCT using indexes */ -#define SQLITE_CoverIdxScan 0x0020 /* Covering index scans */ -#define SQLITE_OrderByIdxJoin 0x0040 /* ORDER BY of joins via index */ -#define SQLITE_Transitive 0x0080 /* Transitive constraints */ -#define SQLITE_OmitNoopJoin 0x0100 /* Omit unused tables in joins */ -#define SQLITE_CountOfView 0x0200 /* The count-of-view optimization */ -#define SQLITE_CursorHints 0x0400 /* Add OP_CursorHint opcodes */ -#define SQLITE_Stat4 0x0800 /* Use STAT4 data */ - /* TH3 expects the Stat4 ^^^^^^ value to be 0x0800. Don't change it */ -#define SQLITE_PushDown 0x1000 /* The push-down optimization */ -#define SQLITE_SimplifyJoin 0x2000 /* Convert LEFT JOIN to JOIN */ -#define SQLITE_SkipScan 0x4000 /* Skip-scans */ -#define SQLITE_PropagateConst 0x8000 /* The constant propagation opt */ -#define SQLITE_AllOpts 0xffff /* All optimizations */ +#define SQLITE_QueryFlattener 0x00000001 /* Query flattening */ +#define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */ +#define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */ +#define SQLITE_FactorOutConst 0x00000008 /* Constant factoring */ +#define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */ +#define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */ +#define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */ +#define SQLITE_Transitive 0x00000080 /* Transitive constraints */ +#define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */ +#define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */ +#define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */ +#define SQLITE_Stat4 0x00000800 /* Use STAT4 data */ + /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */ +#define SQLITE_PushDown 0x00001000 /* The push-down optimization */ +#define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */ +#define SQLITE_SkipScan 0x00004000 /* Skip-scans */ +#define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */ +#define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */ +#define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */ +#define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* ** Macros for testing whether or not optimizations are enabled or disabled. @@ -1850,6 +1888,9 @@ struct FuncDestructor { ** a single query. The iArg is ignored. The user-data is always set ** to a NULL pointer. The bNC parameter is not used. ** +** MFUNCTION(zName, nArg, xPtr, xFunc) +** For math-library functions. xPtr is an arbitrary pointer. +** ** PURE_DATE(zName, nArg, iArg, bNC, xFunc) ** Used for "pure" date/time functions, this macro is like DFUNCTION ** except that it does set the SQLITE_FUNC_CONSTANT flags. iArg is @@ -1885,6 +1926,9 @@ struct FuncDestructor { #define SFUNCTION(zName, nArg, iArg, bNC, xFunc) \ {nArg, SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define MFUNCTION(zName, nArg, xPtr, xFunc) \ + {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ {nArg, SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } @@ -1979,7 +2023,12 @@ struct Column { u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ }; -/* Allowed values for Column.colFlags: +/* Allowed values for Column.colFlags. +** +** Constraints: +** TF_HasVirtual == COLFLAG_VIRTUAL +** TF_HasStored == COLFLAG_STORED +** TF_HasHidden == COLFLAG_HIDDEN */ #define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */ #define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */ @@ -2055,9 +2104,7 @@ struct CollSeq { ** operator is NULL. It is added to certain comparison operators to ** prove that the operands are always NOT NULL. */ -#define SQLITE_KEEPNULL 0x08 /* Used by vector == or <> */ #define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */ -#define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */ #define SQLITE_NULLEQ 0x80 /* NULL=NULL */ #define SQLITE_NOTNULL 0x90 /* Assert that operands are never NULL */ @@ -2155,7 +2202,6 @@ struct Table { #endif Trigger *pTrigger; /* List of triggers stored in pSchema */ Schema *pSchema; /* Schema that contains this table */ - Table *pNextZombie; /* Next on the Parse.pZombieTab list */ }; /* @@ -2169,11 +2215,12 @@ struct Table { ** ** Constraints: ** -** TF_HasVirtual == COLFLAG_Virtual -** TF_HasStored == COLFLAG_Stored +** TF_HasVirtual == COLFLAG_VIRTUAL +** TF_HasStored == COLFLAG_STORED +** TF_HasHidden == COLFLAG_HIDDEN */ #define TF_Readonly 0x0001 /* Read-only system table */ -#define TF_Ephemeral 0x0002 /* An ephemeral table */ +#define TF_HasHidden 0x0002 /* Has one or more hidden columns */ #define TF_HasPrimaryKey 0x0004 /* Table has a primary key */ #define TF_Autoincrement 0x0008 /* Integer primary key is autoincrement */ #define TF_HasStat1 0x0010 /* nRowLogEst set from sqlite_stat1 */ @@ -2188,6 +2235,8 @@ struct Table { #define TF_HasNotNull 0x0800 /* Contains NOT NULL constraints */ #define TF_Shadow 0x1000 /* True for a shadow table */ #define TF_HasStat4 0x2000 /* STAT4 info available for this table */ +#define TF_Ephemeral 0x4000 /* An ephemeral table */ +#define TF_Eponymous 0x8000 /* An eponymous virtual table */ /* ** Test to see whether or not a table is a virtual table. This is @@ -2284,16 +2333,22 @@ struct FKey { ** is returned. REPLACE means that preexisting database rows that caused ** a UNIQUE constraint violation are removed so that the new insert or ** update can proceed. Processing continues and no error is reported. +** UPDATE applies to insert operations only and means that the insert +** is omitted and the DO UPDATE clause of an upsert is run instead. ** -** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. +** RESTRICT, SETNULL, SETDFLT, and CASCADE actions apply only to foreign keys. ** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the ** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign -** key is set to NULL. CASCADE means that a DELETE or UPDATE of the +** key is set to NULL. SETDFLT means that the foreign key is set +** to its default value. CASCADE means that a DELETE or UPDATE of the ** referenced table row is propagated into the row that holds the ** foreign key. ** +** The OE_Default value is a place holder that means to use whatever +** conflict resolution algorthm is required from context. +** ** The following symbolic values are used to record which type -** of action to take. +** of conflict resolution action to take. */ #define OE_None 0 /* There is no constraint to check */ #define OE_Rollback 1 /* Fail the operation and rollback the transaction */ @@ -2547,10 +2602,10 @@ struct AggInfo { FuncDef *pFunc; /* The aggregate function implementation */ int iMem; /* Memory location that acts as accumulator */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ + int iDistAddr; /* Address of OP_OpenEphemeral */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ u32 selId; /* Select to which this AggInfo belongs */ - AggInfo *pNext; /* Next in list of them all */ }; /* @@ -2679,7 +2734,7 @@ struct Expr { ** TK_VARIABLE: variable number (always >= 1). ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ - i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */ + int iRightJoinTable; /* If EP_FromJoin, the right table of the join */ AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ union { Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL @@ -2721,7 +2776,7 @@ struct Expr { #define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ -#define EP_Alias 0x400000 /* Is an alias for a result set column */ + /* 0x400000 // Available */ #define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ #define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */ #define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */ @@ -2820,6 +2875,7 @@ struct Expr { */ struct ExprList { int nExpr; /* Number of expressions on the list */ + int nAlloc; /* Number of a[] slots allocated */ struct ExprList_item { /* For each expression in the list */ Expr *pExpr; /* The parse tree for this expression */ char *zEName; /* Token associated with this expression */ @@ -2869,6 +2925,46 @@ struct IdList { int nId; /* Number of identifiers on the list */ }; +/* +** The SrcItem object represents a single term in the FROM clause of a query. +** The SrcList object is mostly an array of SrcItems. +*/ +struct SrcItem { + Schema *pSchema; /* Schema to which this item is fixed */ + char *zDatabase; /* Name of database holding this table */ + char *zName; /* Name of the table */ + char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */ + Table *pTab; /* An SQL table corresponding to zName */ + Select *pSelect; /* A SELECT statement used in place of a table name */ + int addrFillSub; /* Address of subroutine to manifest a subquery */ + int regReturn; /* Register holding return address of addrFillSub */ + int regResult; /* Registers holding results of a co-routine */ + struct { + u8 jointype; /* Type of join between this table and the previous */ + unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */ + unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */ + unsigned isTabFunc :1; /* True if table-valued-function syntax */ + unsigned isCorrelated :1; /* True if sub-query is correlated */ + unsigned viaCoroutine :1; /* Implemented as a co-routine */ + unsigned isRecursive :1; /* True for recursive reference in WITH */ + unsigned fromDDL :1; /* Comes from sqlite_schema */ + unsigned isCte :1; /* This is a CTE */ + unsigned notCte :1; /* This item may not match a CTE */ + } fg; + 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 */ + Bitmask colUsed; /* Bit N (1<" clause */ + ExprList *pFuncArg; /* Arguments to table-valued-function */ + } u1; + union { + Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ + CteUse *pCteUse; /* CTE Usage info info fg.isCte is true */ + } u2; +}; + /* ** The following structure describes the FROM clause of a SELECT statement. ** Each table or subquery in the FROM clause is a separate element of @@ -2891,36 +2987,7 @@ struct IdList { struct SrcList { int nSrc; /* Number of tables or subqueries in the FROM clause */ u32 nAlloc; /* Number of entries allocated in a[] below */ - struct SrcList_item { - Schema *pSchema; /* Schema to which this item is fixed */ - char *zDatabase; /* Name of database holding this table */ - char *zName; /* Name of the table */ - char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */ - Table *pTab; /* An SQL table corresponding to zName */ - Select *pSelect; /* A SELECT statement used in place of a table name */ - int addrFillSub; /* Address of subroutine to manifest a subquery */ - int regReturn; /* Register holding return address of addrFillSub */ - int regResult; /* Registers holding results of a co-routine */ - struct { - u8 jointype; /* Type of join between this table and the previous */ - unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */ - unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */ - unsigned isTabFunc :1; /* True if table-valued-function syntax */ - unsigned isCorrelated :1; /* True if sub-query is correlated */ - unsigned viaCoroutine :1; /* Implemented as a co-routine */ - unsigned isRecursive :1; /* True for recursive reference in WITH */ - unsigned fromDDL :1; /* Comes from sqlite_schema */ - } fg; - 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 */ - Bitmask colUsed; /* Bit N (1<" clause */ - ExprList *pFuncArg; /* Arguments to table-valued-function */ - } u1; - Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ - } a[1]; /* One entry for each identifier on the list */ + SrcItem a[1]; /* One entry for each identifier on the list */ }; /* @@ -2954,7 +3021,7 @@ struct SrcList { #define WHERE_DISTINCTBY 0x0080 /* pOrderby is really a DISTINCT clause */ #define WHERE_WANT_DISTINCT 0x0100 /* All output needs to be distinct */ #define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */ - /* 0x0400 not currently used */ +#define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */ #define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ /* 0x1000 not currently used */ /* 0x2000 not currently used */ @@ -2996,10 +3063,11 @@ struct NameContext { ExprList *pEList; /* Optional list of result-set columns */ AggInfo *pAggInfo; /* Information about aggregates at this level */ Upsert *pUpsert; /* ON CONFLICT clause information from an upsert */ + int iBaseReg; /* For TK_REGISTER when parsing RETURNING */ } uNC; NameContext *pNext; /* Next outer name context. NULL for outermost */ int nRef; /* Number of names resolved by this context */ - int nErr; /* Number of errors encountered while resolving names */ + int nNcErr; /* Number of errors encountered while resolving names */ int ncFlags; /* Zero or more NC_* flags defined below */ Select *pWinSelect; /* SELECT statement for any window functions */ }; @@ -3024,6 +3092,7 @@ struct NameContext { #define NC_UEList 0x00080 /* True if uNC.pEList is used */ #define NC_UAggInfo 0x00100 /* True if uNC.pAggInfo is used */ #define NC_UUpsert 0x00200 /* True if uNC.pUpsert is used */ +#define NC_UBaseReg 0x00400 /* True if uNC.iBaseReg is used */ #define NC_MinMaxAgg 0x01000 /* min/max aggregates seen. See note above */ #define NC_Complex 0x02000 /* True if a function or subquery seen */ #define NC_AllowWin 0x04000 /* Window functions are allowed here */ @@ -3031,6 +3100,7 @@ struct NameContext { #define NC_IsDDL 0x10000 /* Resolving names in a CREATE statement */ #define NC_InAggFunc 0x20000 /* True if analyzing arguments to an agg func */ #define NC_FromDDL 0x40000 /* SQL text comes from sqlite_schema */ +#define NC_NoSelect 0x80000 /* Do not descend into sub-selects */ /* ** An instance of the following object describes a single ON CONFLICT @@ -3047,15 +3117,21 @@ struct NameContext { ** WHERE clause is omitted. */ struct Upsert { - ExprList *pUpsertTarget; /* Optional description of conflicting index */ + ExprList *pUpsertTarget; /* Optional description of conflict target */ Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */ ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */ Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */ - /* The fields above comprise the parse tree for the upsert clause. - ** The fields below are used to transfer information from the INSERT - ** processing down into the UPDATE processing while generating code. - ** Upsert owns the memory allocated above, but not the memory below. */ - Index *pUpsertIdx; /* Constraint that pUpsertTarget identifies */ + Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */ + u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */ + /* Above this point is the parse tree for the ON CONFLICT clauses. + ** The next group of fields stores intermediate data. */ + void *pToFree; /* Free memory when deleting the Upsert object */ + /* All fields above are owned by the Upsert object and must be freed + ** when the Upsert is destroyed. The fields below are used to transfer + ** information from the INSERT processing down into the UPDATE processing + ** while generating code. The fields below are owned by the INSERT + ** statement and will be freed by INSERT processing. */ + Index *pUpsertIdx; /* UNIQUE constraint specified by pUpsertTarget */ SrcList *pUpsertSrc; /* Table to be updated */ int regData; /* First register holding array of VALUES */ int iDataCur; /* Index of the data cursor */ @@ -3135,6 +3211,9 @@ struct Select { #define SF_View 0x0200000 /* SELECT statement is a view */ #define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ #define SF_UpdateFrom 0x0800000 /* Statement is an UPDATE...FROM */ +#define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ +#define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ +#define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ /* ** The results of a SELECT can be distributed in several ways, as defined @@ -3305,6 +3384,17 @@ struct TriggerPrg { # define DbMaskNonZero(M) (M)!=0 #endif +/* +** An instance of the ParseCleanup object specifies an operation that +** should be performed after parsing to deallocation resources obtained +** during the parse and which are no longer needed. +*/ +struct ParseCleanup { + ParseCleanup *pNext; /* Next cleanup task */ + void *pPtr; /* Pointer to object to deallocate */ + void (*xCleanup)(sqlite3*,void*); /* Deallocation routine */ +}; + /* ** An SQL parser context. A copy of this structure is passed through ** the parser and down into all the parser action routine in order to @@ -3336,6 +3426,9 @@ struct Parse { u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 disableVtab; /* Disable all virtual tables for this parse */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) + u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ +#endif int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ @@ -3363,12 +3456,15 @@ struct Parse { Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ Parse *pParentParse; /* Parent parser if this parser is nested */ - AggInfo *pAggList; /* List of all AggInfo objects */ - int addrCrTab; /* Address of OP_CreateBtree opcode on CREATE TABLE */ + union { + int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */ + Returning *pReturning; /* The RETURNING clause */ + } u1; u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u32 oldmask; /* Mask of old.* columns referenced */ u32 newmask; /* Mask of new.* columns referenced */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ + u8 bReturning; /* Coding a RETURNING trigger */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ u8 disableTriggers; /* True to disable triggers */ @@ -3414,10 +3510,9 @@ struct Parse { Token sArg; /* Complete text of a module argument */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ #endif - Table *pZombieTab; /* List of Table objects to delete after code gen */ TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ With *pWith; /* Current WITH clause, or NULL */ - With *pWithToFree; /* Free this WITH object at the end of the parse */ + ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */ #ifndef SQLITE_OMIT_ALTERTABLE RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */ #endif @@ -3497,6 +3592,7 @@ struct AuthContext { #define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */ #define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ #define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */ +#define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */ /* * Each trigger present in the database schema is stored as an instance of @@ -3518,6 +3614,7 @@ struct Trigger { char *table; /* The table or view to which the trigger applies */ u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */ u8 tr_tm; /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ + u8 bReturning; /* This trigger implements a RETURNING clause */ Expr *pWhen; /* The WHEN clause of the expression (may be NULL) */ IdList *pColumns; /* If this is an UPDATE OF trigger, the is stored here */ @@ -3576,14 +3673,15 @@ struct Trigger { * */ struct TriggerStep { - u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */ + u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT, + ** or TK_RETURNING */ u8 orconf; /* OE_Rollback etc. */ Trigger *pTrig; /* The trigger that this step is a part of */ Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ SrcList *pFrom; /* FROM clause for UPDATE statement (if any) */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ - ExprList *pExprList; /* SET clause for UPDATE */ + ExprList *pExprList; /* SET clause for UPDATE, or RETURNING clause */ IdList *pIdList; /* Column names for INSERT */ Upsert *pUpsert; /* Upsert clauses on an INSERT */ char *zSpan; /* Original SQL text of this command */ @@ -3592,18 +3690,16 @@ struct TriggerStep { }; /* -** The following structure contains information used by the sqliteFix... -** routines as they walk the parse tree to make database references -** explicit. +** Information about a RETURNING clause */ -typedef struct DbFixer DbFixer; -struct DbFixer { - Parse *pParse; /* The parsing context. Error messages written here */ - Schema *pSchema; /* Fix items to this schema */ - u8 bTemp; /* True for TEMP schema entries */ - const char *zDb; /* Make sure all objects are contained in this database */ - const char *zType; /* Type of the container - used for error messages */ - const Token *pName; /* Name of the container - used for error messages */ +struct Returning { + Parse *pParse; /* The parse that includes the RETURNING clause */ + ExprList *pReturnEL; /* List of expressions to return */ + Trigger retTrig; /* The transient trigger that implements RETURNING */ + TriggerStep retTStep; /* The trigger step */ + int iRetCur; /* Transient table holding RETURNING results */ + int nRetCol; /* Number of in pReturnEL after expansion */ + int iRetReg; /* Register array for holding a row of RETURNING */ }; /* @@ -3643,7 +3739,24 @@ typedef struct { /* ** Allowed values for mInitFlags */ -#define INITFLAG_AlterTable 0x0001 /* This is a reparse after ALTER TABLE */ +#define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ +#define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ + +/* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled +** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning +** parameters are for temporary use during development, to help find +** optimial values for parameters in the query planner. The should not +** be used on trunk check-ins. They are a temporary mechanism available +** for transient development builds only. +** +** Tuning parameters are numbered starting with 1. +*/ +#define SQLITE_NTUNE 6 /* Should be zero for all trunk check-ins */ +#ifdef SQLITE_DEBUG +# define Tuning(X) (sqlite3Config.aTune[(X)-1]) +#else +# define Tuning(X) 0 +#endif /* ** Structure containing global configuration data for the SQLite library. @@ -3699,7 +3812,7 @@ struct Sqlite3Config { void (*xVdbeBranch)(void*,unsigned iSrcLine,u8 eThis,u8 eMx); /* Callback */ void *pVdbeBranchArg; /* 1st argument */ #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE sqlite3_int64 mxMemdbSize; /* Default max memdb size */ #endif #ifndef SQLITE_UNTESTABLE @@ -3709,6 +3822,10 @@ struct Sqlite3Config { int iOnceResetThreshold; /* When to reset OP_Once counters */ u32 szSorterRef; /* Min size in bytes to use sorter-refs */ unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */ + /* vvvv--- must be last ---vvv */ +#ifdef SQLITE_DEBUG + sqlite3_int64 aTune[SQLITE_NTUNE]; /* Tuning parameters */ +#endif }; /* @@ -3755,10 +3872,26 @@ struct Walker { struct WhereConst *pConst; /* WHERE clause constants */ struct RenameCtx *pRename; /* RENAME COLUMN context */ struct Table *pTab; /* Table of generated column */ - struct SrcList_item *pSrcItem; /* A single FROM clause item */ + SrcItem *pSrcItem; /* A single FROM clause item */ + DbFixer *pFix; } u; }; +/* +** The following structure contains information used by the sqliteFix... +** routines as they walk the parse tree to make database references +** explicit. +*/ +struct DbFixer { + Parse *pParse; /* The parsing context. Error messages written here */ + Walker w; /* Walker object */ + Schema *pSchema; /* Fix items to this schema */ + u8 bTemp; /* True for TEMP schema entries */ + const char *zDb; /* Make sure all objects are contained in this database */ + const char *zType; /* Type of the container - used for error messages */ + const Token *pName; /* Name of the container - used for error messages */ +}; + /* Forward declarations */ int sqlite3WalkExpr(Walker*, Expr*); int sqlite3WalkExprList(Walker*, ExprList*); @@ -3770,11 +3903,18 @@ int sqlite3SelectWalkNoop(Walker*, Select*); int sqlite3SelectWalkFail(Walker*, Select*); int sqlite3WalkerDepthIncrease(Walker*,Select*); void sqlite3WalkerDepthDecrease(Walker*,Select*); +void sqlite3WalkWinDefnDummyCallback(Walker*,Select*); #ifdef SQLITE_DEBUG void sqlite3SelectWalkAssert2(Walker*, Select*); #endif +#ifndef SQLITE_OMIT_CTE +void sqlite3SelectPopWith(Walker*, Select*); +#else +# define sqlite3SelectPopWith 0 +#endif + /* ** Return code from the parse-tree walking primitives and their ** callbacks. @@ -3784,20 +3924,56 @@ void sqlite3SelectWalkAssert2(Walker*, Select*); #define WRC_Abort 2 /* Abandon the tree walk */ /* -** An instance of this structure represents a set of one or more CTEs -** (common table expressions) created by a single WITH clause. +** A single common table expression +*/ +struct Cte { + char *zName; /* Name of this CTE */ + ExprList *pCols; /* List of explicit column names, or NULL */ + Select *pSelect; /* The definition of this CTE */ + const char *zCteErr; /* Error message for circular references */ + CteUse *pUse; /* Usage information for this CTE */ + u8 eM10d; /* The MATERIALIZED flag */ +}; + +/* +** Allowed values for the materialized flag (eM10d): +*/ +#define M10d_Yes 0 /* AS MATERIALIZED */ +#define M10d_Any 1 /* Not specified. Query planner's choice */ +#define M10d_No 2 /* AS NOT MATERIALIZED */ + +/* +** An instance of the With object represents a WITH clause containing +** one or more CTEs (common table expressions). */ struct With { - int nCte; /* Number of CTEs in the WITH clause */ - With *pOuter; /* Containing WITH clause, or NULL */ - struct Cte { /* For each CTE in the WITH clause.... */ - char *zName; /* Name of this CTE */ - ExprList *pCols; /* List of explicit column names, or NULL */ - Select *pSelect; /* The definition of this CTE */ - const char *zCteErr; /* Error message for circular references */ - } a[1]; + int nCte; /* Number of CTEs in the WITH clause */ + int bView; /* Belongs to the outermost Select of a view */ + With *pOuter; /* Containing WITH clause, or NULL */ + Cte a[1]; /* For each CTE in the WITH clause.... */ }; +/* +** The Cte object is not guaranteed to persist for the entire duration +** of code generation. (The query flattener or other parser tree +** edits might delete it.) The following object records information +** about each Common Table Expression that must be preserved for the +** duration of the parse. +** +** The CteUse objects are freed using sqlite3ParserAddCleanup() rather +** than sqlite3SelectDelete(), which is what enables them to persist +** until the end of code generation. +*/ +struct CteUse { + int nUse; /* Number of users of this CTE */ + int addrM9e; /* Start of subroutine to compute materialization */ + int regRtn; /* Return address register for addrM9e subroutine */ + int iCur; /* Ephemeral table holding the materialization */ + LogEst nRowEst; /* Estimated number of rows in the table */ + u8 eM10d; /* The MATERIALIZED flag */ +}; + + #ifdef SQLITE_DEBUG /* ** An instance of the TreeView object is used for printing the content of @@ -3875,7 +4051,6 @@ int sqlite3WindowCompare(Parse*, Window*, Window*, int); void sqlite3WindowCodeInit(Parse*, Select*); void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int); int sqlite3WindowRewrite(Parse*, Select*); -int sqlite3ExpandSubquery(Parse*, struct SrcList_item*); void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*); Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p); Window *sqlite3WindowListDup(sqlite3 *db, Window *p); @@ -4144,6 +4319,7 @@ Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*, int); void sqlite3ExprFunctionUsable(Parse*,Expr*,FuncDef*); void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); void sqlite3ExprDelete(sqlite3*, Expr*); +void sqlite3ExprDeferredDelete(Parse*, Expr*); void sqlite3ExprUnmapAndDelete(Parse*, Expr*); ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); @@ -4165,6 +4341,7 @@ void sqlite3ResetOneSchema(sqlite3*,int); void sqlite3CollapseDatabaseArray(sqlite3*); void sqlite3CommitInternalChanges(sqlite3*); void sqlite3DeleteColumnNames(sqlite3*,Table*); +void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); void sqlite3SelectAddColumnTypeAndCollation(Parse*,Table*,Select*,char); Table *sqlite3ResultSetOfSelect(Parse*,Select*,char); @@ -4192,6 +4369,7 @@ void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*); void sqlite3AddCollateType(Parse*, Token*); void sqlite3AddGenerated(Parse*,Expr*,Token*); void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); +void sqlite3AddReturning(Parse*,ExprList*); int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); #define sqlite3CodecQueryParameters(A,B,C) 0 @@ -4257,7 +4435,7 @@ SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, Token*, Select*, Expr*, IdList*); void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *); void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*); -int sqlite3IndexedByLookup(Parse *, struct SrcList_item *); +int sqlite3IndexedByLookup(Parse *, SrcItem *); void sqlite3SrcListShiftJoinType(SrcList*); void sqlite3SrcListAssignCursors(Parse*, SrcList*); void sqlite3IdListDelete(sqlite3*, IdList*); @@ -4285,6 +4463,7 @@ LogEst sqlite3WhereOutputRowCount(WhereInfo*); int sqlite3WhereIsDistinct(WhereInfo*); int sqlite3WhereIsOrdered(WhereInfo*); int sqlite3WhereOrderByLimitOptLabel(WhereInfo*); +void sqlite3WhereMinMaxOptEarlyOut(Vdbe*,WhereInfo*); int sqlite3WhereIsSorted(WhereInfo*); int sqlite3WhereContinueLabel(WhereInfo*); int sqlite3WhereBreakLabel(WhereInfo*); @@ -4318,7 +4497,7 @@ Table *sqlite3FindTable(sqlite3*,const char*, const char*); #define LOCATE_VIEW 0x01 #define LOCATE_NOERR 0x02 Table *sqlite3LocateTable(Parse*,u32 flags,const char*, const char*); -Table *sqlite3LocateTableItem(Parse*,u32 flags,struct SrcList_item *); +Table *sqlite3LocateTableItem(Parse*,u32 flags,SrcItem *); Index *sqlite3FindIndex(sqlite3*,const char*, const char*); void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*); void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); @@ -4398,6 +4577,7 @@ void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); int sqlite3SafetyCheckOk(sqlite3*); int sqlite3SafetyCheckSickOrOk(sqlite3*); void sqlite3ChangeCookie(Parse*, int); +With *sqlite3WithDup(sqlite3 *db, With *p); #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); @@ -4446,6 +4626,7 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); #endif int sqlite3JoinType(Parse*, Token*, Token*, Token*); +int sqlite3ColumnIndex(Table *pTab, const char *zCol); void sqlite3SetJoinExpr(Expr*,int); void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); void sqlite3DeferForeignKey(Parse*, int); @@ -4468,7 +4649,6 @@ void 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 sqlite3RealSameAsInt(double,sqlite3_int64); void sqlite3Int64ToText(i64,char*); @@ -4531,6 +4711,7 @@ int sqlite3Atoi64(const char*, i64*, int, u8); int sqlite3DecOrHexToI64(const char*, i64*); void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); void sqlite3Error(sqlite3*,int); +void sqlite3ErrorClear(sqlite3*); void sqlite3SystemError(sqlite3*,int); void *sqlite3HexToBlob(sqlite3*, const char *z, int n); u8 sqlite3HexToInt(int h); @@ -4540,7 +4721,7 @@ int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); const char *sqlite3ErrName(int); #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE int sqlite3MemdbInit(void); #endif @@ -4591,10 +4772,12 @@ void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); extern const unsigned char sqlite3OpcodeProperty[]; extern const char sqlite3StrBINARY[]; extern const unsigned char sqlite3UpperToLower[]; +extern const unsigned char *sqlite3aLTb; +extern const unsigned char *sqlite3aEQb; +extern const unsigned char *sqlite3aGTb; extern const unsigned char sqlite3CtypeMap[]; extern SQLITE_WSD struct Sqlite3Config sqlite3Config; extern FuncDefHash sqlite3BuiltinFunctions; -extern u32 sqlite3_unsupported_selecttrace; #ifndef SQLITE_OMIT_WSD extern int sqlite3PendingByte; #endif @@ -4613,6 +4796,7 @@ void sqlite3ExpirePreparedStatements(sqlite3*, int); void sqlite3CodeRhsOfIN(Parse*, Expr*, int); int sqlite3CodeSubselect(Parse*, Expr*); void sqlite3SelectPrep(Parse*, Select*, NameContext*); +int sqlite3ExpandSubquery(Parse*, SrcItem*); void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p); int sqlite3MatchEName( const struct ExprList_item*, @@ -4630,6 +4814,7 @@ int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*); void sqlite3ColumnDefault(Vdbe *, Table *, int, int); void sqlite3AlterFinishAddColumn(Parse *, Token *); void sqlite3AlterBeginAddColumn(Parse *, SrcList *); +void sqlite3AlterDropColumn(Parse*, SrcList*, Token*); void *sqlite3RenameTokenMap(Parse*, void*, Token*); void sqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom); void sqlite3RenameExprUnmap(Parse*, Expr*); @@ -4653,6 +4838,7 @@ void sqlite3KeyInfoUnref(KeyInfo*); KeyInfo *sqlite3KeyInfoRef(KeyInfo*); KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*); KeyInfo *sqlite3KeyInfoFromExprList(Parse*, ExprList*, int, int); +const char *sqlite3SelectOpName(int); int sqlite3HasExplicitNulls(Parse*, ExprList*); #ifdef SQLITE_DEBUG @@ -4783,6 +4969,7 @@ sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); void sqlite3ParserReset(Parse*); +void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*); #ifdef SQLITE_ENABLE_NORMALIZE char *sqlite3Normalize(Vdbe*, const char*); #endif @@ -4797,23 +4984,32 @@ const char *sqlite3JournalModename(int); int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); #endif #ifndef SQLITE_OMIT_CTE - With *sqlite3WithAdd(Parse*,With*,Token*,ExprList*,Select*); + Cte *sqlite3CteNew(Parse*,Token*,ExprList*,Select*,u8); + void sqlite3CteDelete(sqlite3*,Cte*); + With *sqlite3WithAdd(Parse*,With*,Cte*); void sqlite3WithDelete(sqlite3*,With*); - void sqlite3WithPush(Parse*, With*, u8); + With *sqlite3WithPush(Parse*, With*, u8); #else -#define sqlite3WithPush(x,y,z) -#define sqlite3WithDelete(x,y) +# define sqlite3CteNew(P,T,E,S) ((void*)0) +# define sqlite3CteDelete(D,C) +# define sqlite3CteWithAdd(P,W,C) ((void*)0) +# define sqlite3WithDelete(x,y) +# define sqlite3WithPush(x,y,z) #endif #ifndef SQLITE_OMIT_UPSERT - Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*); + Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); void sqlite3UpsertDelete(sqlite3*,Upsert*); Upsert *sqlite3UpsertDup(sqlite3*,Upsert*); int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*); void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int); + Upsert *sqlite3UpsertOfIndex(Upsert*,Index*); + int sqlite3UpsertNextIsIPK(Upsert*); #else -#define sqlite3UpsertNew(v,w,x,y,z) ((Upsert*)0) +#define sqlite3UpsertNew(u,v,w,x,y,z) ((Upsert*)0) #define sqlite3UpsertDelete(x,y) -#define sqlite3UpsertDup(x,y) ((Upsert*)0) +#define sqlite3UpsertDup(x,y) ((Upsert*)0) +#define sqlite3UpsertOfIndex(x,y) ((Upsert*)0) +#define sqlite3UpsertNextIsIPK(x) 0 #endif diff --git a/src/tclsqlite.c b/src/tclsqlite.c index d80c25251..2206bca32 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -2600,7 +2600,7 @@ static int SQLITE_TCLAPI DbObjCmd( ** Reopen DATABASE (default "main") using the content in $VALUE */ case DB_DESERIALIZE: { -#ifndef SQLITE_ENABLE_DESERIALIZE +#ifdef SQLITE_OMIT_DESERIALIZE Tcl_AppendResult(interp, "MEMDB not available in this build", (char*)0); rc = TCL_ERROR; @@ -3167,7 +3167,7 @@ static int SQLITE_TCLAPI DbObjCmd( ** Return a serialization of a database. */ case DB_SERIALIZE: { -#ifndef SQLITE_ENABLE_DESERIALIZE +#ifdef SQLITE_OMIT_DESERIALIZE Tcl_AppendResult(interp, "MEMDB not available in this build", (char*)0); rc = TCL_ERROR; diff --git a/src/test1.c b/src/test1.c index 49461ea5f..7cb56d3be 100644 --- a/src/test1.c +++ b/src/test1.c @@ -3825,9 +3825,11 @@ static int SQLITE_TCLAPI test_bind_text( ){ sqlite3_stmt *pStmt; int idx; + int trueLength = 0; int bytes; char *value; int rc; + char *toFree = 0; if( objc!=5 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", @@ -3837,13 +3839,23 @@ static int SQLITE_TCLAPI test_bind_text( if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR; - value = (char*)Tcl_GetByteArrayFromObj(objv[3], &bytes); + value = (char*)Tcl_GetByteArrayFromObj(objv[3], &trueLength); if( Tcl_GetIntFromObj(interp, objv[4], &bytes) ) return TCL_ERROR; - + if( bytes<0 ){ + toFree = malloc( trueLength + 1 ); + if( toFree==0 ){ + Tcl_AppendResult(interp, "out of memory", (void*)0); + return TCL_ERROR; + } + memcpy(toFree, value, trueLength); + toFree[trueLength] = 0; + value = toFree; + } rc = sqlite3_bind_text(pStmt, idx, value, bytes, SQLITE_TRANSIENT); + free(toFree); if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR; if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); + Tcl_AppendResult(interp, sqlite3ErrName(rc), (void*)0); return TCL_ERROR; } @@ -3869,7 +3881,9 @@ static int SQLITE_TCLAPI test_bind_text16( int idx; int bytes; char *value; + char *toFree = 0; int rc; + int trueLength = 0; void (*xDel)(void*) = (objc==6?SQLITE_STATIC:SQLITE_TRANSIENT); Tcl_Obj *oStmt = objv[objc-4]; @@ -3885,10 +3899,20 @@ static int SQLITE_TCLAPI test_bind_text16( if( getStmtPointer(interp, Tcl_GetString(oStmt), &pStmt) ) return TCL_ERROR; if( Tcl_GetIntFromObj(interp, oN, &idx) ) return TCL_ERROR; - value = (char*)Tcl_GetByteArrayFromObj(oString, 0); + value = (char*)Tcl_GetByteArrayFromObj(oString, &trueLength); if( Tcl_GetIntFromObj(interp, oBytes, &bytes) ) return TCL_ERROR; - + if( bytes<0 && xDel==SQLITE_TRANSIENT ){ + toFree = malloc( trueLength + 3 ); + if( toFree==0 ){ + Tcl_AppendResult(interp, "out of memory", (void*)0); + return TCL_ERROR; + } + memcpy(toFree, value, trueLength); + memset(toFree+trueLength, 0, 3); + value = toFree; + } rc = sqlite3_bind_text16(pStmt, idx, (void *)value, bytes, xDel); + free(toFree); if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR; if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); @@ -6490,6 +6514,42 @@ static int SQLITE_TCLAPI file_control_tempfilename( return TCL_OK; } +/* +** tclcmd: file_control_external_reader DB ?AUXDB? +** +** Return a string that is a temporary filename +*/ +static int SQLITE_TCLAPI file_control_external_reader( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + const char *zName = "main"; + int iRes = 0; + int rc = SQLITE_OK; + + if( objc!=2 && objc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], 0), " DB ?AUXDB?", 0); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ + return TCL_ERROR; + } + if( objc==3 ){ + zName = Tcl_GetString(objv[2]); + } + rc = sqlite3_file_control(db, zName, SQLITE_FCNTL_EXTERNAL_READER, &iRes); + if( rc!=SQLITE_OK ){ + Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(iRes)); + return TCL_OK; +} + /* ** tclcmd: sqlite3_vfs_list @@ -6648,7 +6708,7 @@ static int SQLITE_TCLAPI prng_seed( Tcl_WrongNumArgs(interp, 1, objv, "SEED ?DB?"); return TCL_ERROR; } - if( Tcl_GetIntFromObj(interp,objv[0],&i) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp,objv[1],&i) ) return TCL_ERROR; if( objc==3 && getDbPointer(interp, Tcl_GetString(objv[2]), &db) ){ return TCL_ERROR; } @@ -7494,6 +7554,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( Tcl_Obj *CONST objv[] ){ extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*); + extern int sqlite3_appendvfs_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_carray_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_csv_init(sqlite3*,char**,const sqlite3_api_routines*); @@ -7523,6 +7584,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*); } aExtension[] = { { "amatch", sqlite3_amatch_init }, + { "appendvfs", sqlite3_appendvfs_init }, { "carray", sqlite3_carray_init }, { "closure", sqlite3_closure_init }, { "csv", sqlite3_csv_init }, @@ -7571,7 +7633,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( }else{ rc = SQLITE_OK; } - if( rc!=SQLITE_OK || zErrMsg ){ + if( (rc!=SQLITE_OK && rc!=SQLITE_OK_LOAD_PERMANENTLY) || zErrMsg ){ Tcl_AppendResult(interp, "initialization of ", zName, " failed: ", zErrMsg, (char*)0); sqlite3_free(zErrMsg); @@ -8426,6 +8488,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "file_control_vfsname", file_control_vfsname, 0 }, { "file_control_reservebytes", file_control_reservebytes, 0 }, { "file_control_tempfilename", file_control_tempfilename, 0 }, + { "file_control_external_reader", file_control_external_reader, 0 }, { "sqlite3_vfs_list", vfs_list, 0 }, { "sqlite3_create_function_v2", test_create_function_v2, 0 }, @@ -8512,7 +8575,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ extern LONG volatile sqlite3_os_type; #endif #ifdef SQLITE_DEBUG - extern int sqlite3WhereTrace; + extern u32 sqlite3WhereTrace; extern int sqlite3OSTrace; extern int sqlite3WalTrace; #endif @@ -8520,9 +8583,6 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ #ifdef SQLITE_ENABLE_FTS3 extern int sqlite3_fts3_enable_parentheses; #endif -#endif -#if defined(SQLITE_ENABLE_SELECTTRACE) - extern u32 sqlite3_unsupported_selecttrace; #endif for(i=0; idb, "function_that_does_not_exist_0982ma98", + SQLITE_ANY, 1, 0, 0, 0, 0); sqlite3_free(p); } @@ -1376,6 +1380,7 @@ static int SQLITE_TCLAPI register_echo_module( /* Virtual table module "echo" */ pMod = sqlite3_malloc(sizeof(EchoModule)); pMod->interp = interp; + pMod->db = db; rc = sqlite3_create_module_v2( db, "echo", &echoModule, (void*)pMod, moduleDestroy ); @@ -1384,6 +1389,7 @@ static int SQLITE_TCLAPI register_echo_module( if( rc==SQLITE_OK ){ pMod = sqlite3_malloc(sizeof(EchoModule)); pMod->interp = interp; + pMod->db = db; rc = sqlite3_create_module_v2(db, "echo_v2", &echoModuleV2, (void*)pMod, moduleDestroy ); diff --git a/src/test_config.c b/src/test_config.c index 362e92aa9..86e4df38f 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -65,6 +65,13 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options","casesensitivelike","0",TCL_GLOBAL_ONLY); #endif +#ifdef CONFIG_SLOWDOWN_FACTOR + Tcl_SetVar2(interp, "sqlite_options","configslower", + STRINGVALUE(CONFIG_SLOWDOWN_FACTOR),TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options","configslower","1.0",TCL_GLOBAL_ONLY); +#endif + #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT Tcl_SetVar2(interp, "sqlite_options", "curdir", "1", TCL_GLOBAL_ONLY); #else @@ -148,12 +155,18 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "hiddencolumns", "0", TCL_GLOBAL_ONLY); #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE Tcl_SetVar2(interp, "sqlite_options", "deserialize", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "deserialize", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + Tcl_SetVar2(interp, "sqlite_options", "mathlib", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "mathlib", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_ENABLE_MEMSYS3 Tcl_SetVar2(interp, "sqlite_options", "mem3", "1", TCL_GLOBAL_ONLY); #else @@ -220,6 +233,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "atomicwrite", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ENABLE_GEOPOLY + Tcl_SetVar2(interp, "sqlite_options", "geopoly", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "geopoly", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_ENABLE_JSON1 Tcl_SetVar2(interp, "sqlite_options", "json1", "1", TCL_GLOBAL_ONLY); #else diff --git a/src/tokenize.c b/src/tokenize.c index 70f0c6367..5d250e6f6 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -27,8 +27,8 @@ ** all of them need to be used within the switch. */ #define CC_X 0 /* The letter 'x', or start of BLOB literal */ -#define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */ -#define CC_ID 2 /* unicode characters usable in IDs */ +#define CC_KYWD0 1 /* First letter of a keyword */ +#define CC_KYWD 2 /* Alphabetics or '_'. Usable in a keyword */ #define CC_DIGIT 3 /* Digits */ #define CC_DOLLAR 4 /* '$' */ #define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */ @@ -53,47 +53,49 @@ #define CC_AND 24 /* '&' */ #define CC_TILDA 25 /* '~' */ #define CC_DOT 26 /* '.' */ -#define CC_ILLEGAL 27 /* Illegal character */ -#define CC_NUL 28 /* 0x00 */ +#define CC_ID 27 /* unicode characters usable in IDs */ +#define CC_ILLEGAL 28 /* Illegal character */ +#define CC_NUL 29 /* 0x00 */ +#define CC_BOM 30 /* First byte of UTF8 BOM: 0xEF 0xBB 0xBF */ static const unsigned char aiClass[] = { #ifdef SQLITE_ASCII /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ -/* 0x */ 28, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 27, 7, 7, 27, 27, -/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* 0x */ 29, 28, 28, 28, 28, 28, 28, 28, 28, 7, 7, 28, 7, 7, 28, 28, +/* 1x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, /* 2x */ 7, 15, 8, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16, /* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6, /* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 9, 27, 27, 27, 1, +/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 9, 28, 28, 28, 2, /* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27, -/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 +/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 28, 10, 28, 25, 28, +/* 8x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* 9x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ax */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Cx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Dx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ex */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 30, +/* Fx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27 #endif #ifdef SQLITE_EBCDIC /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ -/* 0x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 7, 7, 27, 27, -/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 2x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 3x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 12, 17, 20, 10, -/* 5x */ 24, 27, 27, 27, 27, 27, 27, 27, 27, 27, 15, 4, 21, 18, 19, 27, -/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 6, -/* 7x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 5, 5, 5, 8, 14, 8, -/* 8x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* 9x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* Ax */ 27, 25, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, -/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 9, 27, 27, 27, 27, 27, -/* Cx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* Dx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* Ex */ 27, 27, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, -/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 27, 27, 27, 27, 27, 27, +/* 0x */ 29, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 7, 7, 28, 28, +/* 1x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 2x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 3x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 4x */ 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 26, 12, 17, 20, 10, +/* 5x */ 24, 28, 28, 28, 28, 28, 28, 28, 28, 28, 15, 4, 21, 18, 19, 28, +/* 6x */ 11, 16, 28, 28, 28, 28, 28, 28, 28, 28, 28, 23, 22, 2, 13, 6, +/* 7x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 8, 5, 5, 5, 8, 14, 8, +/* 8x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* 9x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Ax */ 28, 25, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28, +/* Bx */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 9, 28, 28, 28, 28, 28, +/* Cx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Dx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Ex */ 28, 28, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28, +/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 28, 28, 28, 28, 28, 28, #endif }; @@ -499,7 +501,7 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ if( n==0 ) *tokenType = TK_ILLEGAL; return i; } - case CC_KYWD: { + case CC_KYWD0: { for(i=1; aiClass[z[i]]<=CC_KYWD; i++){} if( IdChar(z[i]) ){ /* This token started out using characters that can appear in keywords, @@ -529,10 +531,19 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ ** SQL keywords start with the letter 'x'. Fall through */ /* no break */ deliberate_fall_through } + case CC_KYWD: case CC_ID: { i = 1; break; } + case CC_BOM: { + if( z[1]==0xbb && z[2]==0xbf ){ + *tokenType = TK_SPACE; + return 3; + } + i = 1; + break; + } case CC_NUL: { *tokenType = TK_ILLEGAL; return 0; @@ -711,19 +722,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ if( !IN_RENAME_OBJECT ){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); } - - if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree); sqlite3DbFree(db, pParse->pVList); - while( pParse->pAinc ){ - AutoincInfo *p = pParse->pAinc; - pParse->pAinc = p->pNext; - sqlite3DbFreeNN(db, p); - } - while( pParse->pZombieTab ){ - Table *p = pParse->pZombieTab; - pParse->pZombieTab = p->pNextZombie; - sqlite3DeleteTable(db, p); - } db->pParse = pParse->pParentParse; pParse->pParentParse = 0; assert( nErr==0 || pParse->rc!=SQLITE_OK ); diff --git a/src/treeview.c b/src/treeview.c index 187f1a07d..c737a1b98 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -111,7 +111,10 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){ } sqlite3_str_appendf(&x, ")"); } - sqlite3_str_appendf(&x, " AS"); + if( pCte->pUse ){ + sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse, + pCte->pUse->nUse); + } sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, inCte-1); sqlite3TreeViewSelect(pView, pCte->pSelect, 0); @@ -127,29 +130,25 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ int i; for(i=0; inSrc; i++){ - const struct SrcList_item *pItem = &pSrc->a[i]; + const SrcItem *pItem = &pSrc->a[i]; StrAccum x; char zLine[100]; sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); - sqlite3_str_appendf(&x, "{%d:*}", pItem->iCursor); - if( pItem->zDatabase ){ - sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName); - }else if( pItem->zName ){ - sqlite3_str_appendf(&x, " %s", pItem->zName); - } + x.printfFlags |= SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem); if( pItem->pTab ){ sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx", pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed); } - if( pItem->zAlias ){ - sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias); - } if( pItem->fg.jointype & JT_LEFT ){ sqlite3_str_appendf(&x, " LEFT-JOIN"); } if( pItem->fg.fromDDL ){ sqlite3_str_appendf(&x, " DDL"); } + if( pItem->fg.isCte ){ + sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse); + } sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, inSrc-1); if( pItem->pSelect ){ @@ -707,6 +706,14 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } + case TK_ERROR: { + Expr tmp; + sqlite3TreeViewLine(pView, "ERROR"); + tmp = *pExpr; + tmp.op = pExpr->op2; + sqlite3TreeViewExpr(pView, &tmp, 0); + break; + } default: { sqlite3TreeViewLine(pView, "op=%d", pExpr->op); break; diff --git a/src/trigger.c b/src/trigger.c index dd4ed8c2e..90e6ef4a7 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -48,28 +48,51 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){ ** pTab as well as the triggers lised in pTab->pTrigger. */ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){ - Schema * const pTmpSchema = pParse->db->aDb[1].pSchema; - Trigger *pList = 0; /* List of triggers to return */ + Schema *pTmpSchema; /* Schema of the pTab table */ + Trigger *pList; /* List of triggers to return */ + HashElem *p; /* Loop variable for TEMP triggers */ if( pParse->disableTriggers ){ return 0; } - - if( pTmpSchema!=pTab->pSchema ){ - HashElem *p; - assert( sqlite3SchemaMutexHeld(pParse->db, 0, pTmpSchema) ); - for(p=sqliteHashFirst(&pTmpSchema->trigHash); p; p=sqliteHashNext(p)){ - Trigger *pTrig = (Trigger *)sqliteHashData(p); - if( pTrig->pTabSchema==pTab->pSchema - && 0==sqlite3StrICmp(pTrig->table, pTab->zName) - ){ - pTrig->pNext = (pList ? pList : pTab->pTrigger); - pList = pTrig; - } + pTmpSchema = pParse->db->aDb[1].pSchema; + p = sqliteHashFirst(&pTmpSchema->trigHash); + pList = pTab->pTrigger; + while( p ){ + Trigger *pTrig = (Trigger *)sqliteHashData(p); + if( pTrig->pTabSchema==pTab->pSchema + && pTrig->table + && 0==sqlite3StrICmp(pTrig->table, pTab->zName) + && pTrig->pTabSchema!=pTmpSchema + ){ + pTrig->pNext = pList; + pList = pTrig; + }else if( pTrig->op==TK_RETURNING +#ifndef SQLITE_OMIT_VIRTUALTABLE + && pParse->db->pVtabCtx==0 +#endif + ){ + assert( pParse->bReturning ); + assert( &(pParse->u1.pReturning->retTrig) == pTrig ); + pTrig->table = pTab->zName; + pTrig->pTabSchema = pTab->pSchema; + pTrig->pNext = pList; + pList = pTrig; + } + p = sqliteHashNext(p); + } +#if 0 + if( pList ){ + Trigger *pX; + printf("Triggers for %s:", pTab->zName); + for(pX=pList; pX; pX=pX->pNext){ + printf(" %s", pX->zName); } + printf("\n"); + fflush(stdout); } - - return (pList ? pList : pTab->pTrigger); +#endif + return pList; } /* @@ -198,12 +221,12 @@ void sqlite3BeginTrigger( */ if( pTab->pSelect && tr_tm!=TK_INSTEAD ){ sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S", - (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0); + (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a); goto trigger_orphan_error; } if( !pTab->pSelect && tr_tm==TK_INSTEAD ){ sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF" - " trigger on table: %S", pTableName, 0); + " trigger on table: %S", pTableName->a); goto trigger_orphan_error; } @@ -345,7 +368,7 @@ void sqlite3FinishTrigger( sqlite3DbFree(db, z); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddParseSchemaOp(v, iDb, - sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName)); + sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName), 0); } if( db->init.busy ){ @@ -558,7 +581,7 @@ TriggerStep *sqlite3TriggerDeleteStep( ** Recursively delete a Trigger structure */ void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){ - if( pTrigger==0 ) return; + if( pTrigger==0 || pTrigger->bReturning ) return; sqlite3DeleteTriggerStep(db, pTrigger->step_list); sqlite3DbFree(db, pTrigger->zName); sqlite3DbFree(db, pTrigger->table); @@ -600,7 +623,7 @@ void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){ } if( !pTrigger ){ if( !noErr ){ - sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0); + sqlite3ErrorMsg(pParse, "no such trigger: %S", pName->a); }else{ sqlite3CodeVerifyNamedSchema(pParse, zDb); } @@ -723,15 +746,53 @@ Trigger *sqlite3TriggersExist( Trigger *pList = 0; Trigger *p; - if( (pParse->db->flags & SQLITE_EnableTrigger)!=0 ){ - pList = sqlite3TriggerList(pParse, pTab); - } - assert( pList==0 || IsVirtual(pTab)==0 ); - for(p=pList; p; p=p->pNext){ - if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){ - mask |= p->tr_tm; + pList = sqlite3TriggerList(pParse, pTab); + assert( pList==0 || IsVirtual(pTab)==0 + || (pList->bReturning && pList->pNext==0) ); + if( pList!=0 ){ + p = pList; + if( (pParse->db->flags & SQLITE_EnableTrigger)==0 + && pTab->pTrigger!=0 + ){ + /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that + ** only TEMP triggers are allowed. Truncate the pList so that it + ** includes only TEMP triggers */ + if( pList==pTab->pTrigger ){ + pList = 0; + goto exit_triggers_exist; + } + while( ALWAYS(p->pNext) && p->pNext!=pTab->pTrigger ) p = p->pNext; + p->pNext = 0; + p = pList; } + do{ + if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){ + mask |= p->tr_tm; + }else if( p->op==TK_RETURNING ){ + /* The first time a RETURNING trigger is seen, the "op" value tells + ** us what time of trigger it should be. */ + assert( sqlite3IsToplevel(pParse) ); + p->op = op; + if( IsVirtual(pTab) ){ + if( op!=TK_INSERT ){ + sqlite3ErrorMsg(pParse, + "%s RETURNING is not available on virtual tables", + op==TK_DELETE ? "DELETE" : "UPDATE"); + } + p->tr_tm = TRIGGER_BEFORE; + }else{ + p->tr_tm = TRIGGER_AFTER; + } + mask |= p->tr_tm; + }else if( p->bReturning && p->op==TK_INSERT && op==TK_UPDATE + && sqlite3IsToplevel(pParse) ){ + /* Also fire a RETURNING trigger for an UPSERT */ + mask |= p->tr_tm; + } + p = p->pNext; + }while( p ); } +exit_triggers_exist: if( pMask ){ *pMask = mask; } @@ -774,6 +835,137 @@ SrcList *sqlite3TriggerStepSrc( return pSrc; } +/* +** Return true if the pExpr term from the RETURNING clause argument +** list is of the form "*". Raise an error if the terms if of the +** form "table.*". +*/ +static int isAsteriskTerm( + Parse *pParse, /* Parsing context */ + Expr *pTerm /* A term in the RETURNING clause */ +){ + assert( pTerm!=0 ); + if( pTerm->op==TK_ASTERISK ) return 1; + if( pTerm->op!=TK_DOT ) return 0; + assert( pTerm->pRight!=0 ); + assert( pTerm->pLeft!=0 ); + if( pTerm->pRight->op!=TK_ASTERISK ) return 0; + sqlite3ErrorMsg(pParse, "RETURNING may not use \"TABLE.*\" wildcards"); + return 1; +} + +/* The input list pList is the list of result set terms from a RETURNING +** clause. The table that we are returning from is pTab. +** +** This routine makes a copy of the pList, and at the same time expands +** any "*" wildcards to be the complete set of columns from pTab. +*/ +static ExprList *sqlite3ExpandReturning( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* The arguments to RETURNING */ + Table *pTab /* The table being updated */ +){ + ExprList *pNew = 0; + sqlite3 *db = pParse->db; + int i; + + for(i=0; inExpr; i++){ + Expr *pOldExpr = pList->a[i].pExpr; + if( NEVER(pOldExpr==0) ) continue; + if( isAsteriskTerm(pParse, pOldExpr) ){ + int jj; + for(jj=0; jjnCol; jj++){ + Expr *pNewExpr; + if( IsHiddenColumn(pTab->aCol+jj) ) continue; + pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zName); + pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); + if( !db->mallocFailed ){ + struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; + pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zName); + pItem->eEName = ENAME_NAME; + } + } + }else{ + Expr *pNewExpr = sqlite3ExprDup(db, pOldExpr, 0); + pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); + if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){ + struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; + pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName); + pItem->eEName = pList->a[i].eEName; + } + } + } + return pNew; +} + +/* +** Generate code for the RETURNING trigger. Unlike other triggers +** that invoke a subprogram in the bytecode, the code for RETURNING +** is generated in-line. +*/ +static void codeReturningTrigger( + Parse *pParse, /* Parse context */ + Trigger *pTrigger, /* The trigger step that defines the RETURNING */ + Table *pTab, /* The table to code triggers from */ + int regIn /* The first in an array of registers */ +){ + Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; + ExprList *pNew; + Returning *pReturning; + Select sSelect; + SrcList sFrom; + + assert( v!=0 ); + assert( pParse->bReturning ); + pReturning = pParse->u1.pReturning; + assert( pTrigger == &(pReturning->retTrig) ); + memset(&sSelect, 0, sizeof(sSelect)); + memset(&sFrom, 0, sizeof(sFrom)); + sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); + sSelect.pSrc = &sFrom; + sFrom.nSrc = 1; + sFrom.a[0].pTab = pTab; + sqlite3SelectPrep(pParse, &sSelect, 0); + if( db->mallocFailed==0 && pParse->nErr==0 ){ + sqlite3GenerateColumnNames(pParse, &sSelect); + } + sqlite3ExprListDelete(db, sSelect.pEList); + pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab); + if( pNew ){ + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + if( pReturning->nRetCol==0 ){ + pReturning->nRetCol = pNew->nExpr; + pReturning->iRetCur = pParse->nTab++; + } + sNC.pParse = pParse; + sNC.uNC.iBaseReg = regIn; + sNC.ncFlags = NC_UBaseReg; + pParse->eTriggerOp = pTrigger->op; + pParse->pTriggerTab = pTab; + if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK ){ + int i; + int nCol = pNew->nExpr; + int reg = pParse->nMem+1; + pParse->nMem += nCol+2; + pReturning->iRetReg = reg; + for(i=0; ia[i].pExpr; + sqlite3ExprCodeFactorable(pParse, pCol, reg+i); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i); + sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1); + sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1); + } + sqlite3ExprListDelete(db, pNew); + pParse->eTriggerOp = 0; + pParse->pTriggerTab = 0; + } +} + + + /* ** Generate VDBE code for the statements inside the body of a single ** trigger. @@ -823,6 +1015,7 @@ static int codeTriggerProgram( sqlite3ExprDup(db, pStep->pWhere, 0), pParse->eOrconf, 0, 0, 0 ); + sqlite3VdbeAddOp0(v, OP_ResetCount); break; } case TK_INSERT: { @@ -833,6 +1026,7 @@ static int codeTriggerProgram( pParse->eOrconf, sqlite3UpsertDup(db, pStep->pUpsert) ); + sqlite3VdbeAddOp0(v, OP_ResetCount); break; } case TK_DELETE: { @@ -840,6 +1034,7 @@ static int codeTriggerProgram( sqlite3TriggerStepSrc(pParse, pStep), sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0 ); + sqlite3VdbeAddOp0(v, OP_ResetCount); break; } default: assert( pStep->op==TK_SELECT ); { @@ -851,9 +1046,6 @@ static int codeTriggerProgram( break; } } - if( pStep->op!=TK_SELECT ){ - sqlite3VdbeAddOp0(v, OP_ResetCount); - } } return 0; @@ -969,8 +1161,8 @@ static TriggerPrg *codeRowTrigger( ** OP_Halt inserted at the end of the program. */ if( pTrigger->pWhen ){ pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0); - if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) - && db->mallocFailed==0 + if( db->mallocFailed==0 + && SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) ){ iEndTrigger = sqlite3VdbeMakeLabel(pSubParse); sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); @@ -1000,7 +1192,6 @@ static TriggerPrg *codeRowTrigger( sqlite3VdbeDelete(v); } - assert( !pSubParse->pAinc && !pSubParse->pZombieTab ); assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg ); sqlite3ParserReset(pSubParse); sqlite3StackFree(db, pSubParse); @@ -1102,7 +1293,7 @@ void sqlite3CodeRowTriggerDirect( ** ... ... ** reg+N OLD.* value of right-most column of pTab ** reg+N+1 NEW.rowid -** reg+N+2 OLD.* value of left-most column of pTab +** reg+N+2 NEW.* value of left-most column of pTab ** ... ... ** reg+N+N+1 NEW.* value of right-most column of pTab ** @@ -1147,12 +1338,20 @@ void sqlite3CodeRowTrigger( assert( p->pSchema==p->pTabSchema || p->pSchema==pParse->db->aDb[1].pSchema ); - /* Determine whether we should code this trigger */ - if( p->op==op + /* Determine whether we should code this trigger. One of two choices: + ** 1. The trigger is an exact match to the current DML statement + ** 2. This is a RETURNING trigger for INSERT but we are currently + ** doing the UPDATE part of an UPSERT. + */ + if( (p->op==op || (p->bReturning && p->op==TK_INSERT && op==TK_UPDATE)) && p->tr_tm==tr_tm && checkColumnOverlap(p->pColumns, pChanges) ){ - sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump); + if( !p->bReturning ){ + sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump); + }else if( sqlite3IsToplevel(pParse) ){ + codeReturningTrigger(pParse, p, pTab, reg); + } } } } @@ -1197,13 +1396,18 @@ u32 sqlite3TriggerColmask( assert( isNew==1 || isNew==0 ); for(p=pTrigger; p; p=p->pNext){ - if( p->op==op && (tr_tm&p->tr_tm) + if( p->op==op + && (tr_tm&p->tr_tm) && checkColumnOverlap(p->pColumns,pChanges) ){ - TriggerPrg *pPrg; - pPrg = getRowTrigger(pParse, p, pTab, orconf); - if( pPrg ){ - mask |= pPrg->aColmask[isNew]; + if( p->bReturning ){ + mask = 0xffffffff; + }else{ + TriggerPrg *pPrg; + pPrg = getRowTrigger(pParse, p, pTab, orconf); + if( pPrg ){ + mask |= pPrg->aColmask[isNew]; + } } } } diff --git a/src/update.c b/src/update.c index f8cb2afed..132232724 100644 --- a/src/update.c +++ b/src/update.c @@ -220,6 +220,7 @@ static void updateFromSelect( assert( pTabList->nSrc>1 ); if( pSrc ){ + pSrc->a[0].fg.notCte = 1; pSrc->a[0].iCursor = -1; pSrc->a[0].pTab->nTabRef--; pSrc->a[0].pTab = 0; @@ -249,7 +250,8 @@ static void updateFromSelect( } #endif } - if( ALWAYS(pChanges) ){ + assert( pChanges!=0 || pParse->db->mallocFailed ); + if( pChanges ){ for(i=0; inExpr; i++){ pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) @@ -643,6 +645,7 @@ void sqlite3Update( if( (db->flags&SQLITE_CountRows)!=0 && !pParse->pTriggerTab && !pParse->nested + && !pParse->bReturning && pUpsert==0 ){ regRowCount = ++pParse->nMem; @@ -798,7 +801,12 @@ void sqlite3Update( /* Top of the update loop */ if( eOnePass!=ONEPASS_OFF ){ - if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){ + if( aiCurOnePass[0]!=iDataCur + && aiCurOnePass[1]!=iDataCur +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + && !isView +#endif + ){ assert( pPk ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey); VdbeCoverage(v); @@ -1106,7 +1114,7 @@ void sqlite3Update( ** that information. */ if( regRowCount ){ - sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1); + sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1); sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC); } diff --git a/src/upsert.c b/src/upsert.c index 9a33f75d0..982dc7dbc 100644 --- a/src/upsert.c +++ b/src/upsert.c @@ -18,15 +18,22 @@ /* ** Free a list of Upsert objects */ -void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){ - if( p ){ +static void SQLITE_NOINLINE upsertDelete(sqlite3 *db, Upsert *p){ + do{ + Upsert *pNext = p->pNextUpsert; sqlite3ExprListDelete(db, p->pUpsertTarget); sqlite3ExprDelete(db, p->pUpsertTargetWhere); sqlite3ExprListDelete(db, p->pUpsertSet); sqlite3ExprDelete(db, p->pUpsertWhere); + sqlite3DbFree(db, p->pToFree); sqlite3DbFree(db, p); - } + p = pNext; + }while( p ); } +void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){ + if( p ) upsertDelete(db, p); +} + /* ** Duplicate an Upsert object. @@ -37,7 +44,8 @@ Upsert *sqlite3UpsertDup(sqlite3 *db, Upsert *p){ sqlite3ExprListDup(db, p->pUpsertTarget, 0), sqlite3ExprDup(db, p->pUpsertTargetWhere, 0), sqlite3ExprListDup(db, p->pUpsertSet, 0), - sqlite3ExprDup(db, p->pUpsertWhere, 0) + sqlite3ExprDup(db, p->pUpsertWhere, 0), + sqlite3UpsertDup(db, p->pNextUpsert) ); } @@ -49,22 +57,25 @@ Upsert *sqlite3UpsertNew( ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */ Expr *pTargetWhere, /* Optional WHERE clause on the target */ ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */ - Expr *pWhere /* WHERE clause for the ON CONFLICT UPDATE */ + Expr *pWhere, /* WHERE clause for the ON CONFLICT UPDATE */ + Upsert *pNext /* Next ON CONFLICT clause in the list */ ){ Upsert *pNew; - pNew = sqlite3DbMallocRaw(db, sizeof(Upsert)); + pNew = sqlite3DbMallocZero(db, sizeof(Upsert)); if( pNew==0 ){ sqlite3ExprListDelete(db, pTarget); sqlite3ExprDelete(db, pTargetWhere); sqlite3ExprListDelete(db, pSet); sqlite3ExprDelete(db, pWhere); + sqlite3UpsertDelete(db, pNext); return 0; }else{ pNew->pUpsertTarget = pTarget; pNew->pUpsertTargetWhere = pTargetWhere; pNew->pUpsertSet = pSet; pNew->pUpsertWhere = pWhere; - pNew->pUpsertIdx = 0; + pNew->isDoUpdate = pSet!=0; + pNew->pNextUpsert = pNext; } return pNew; } @@ -89,6 +100,7 @@ int sqlite3UpsertAnalyzeTarget( Expr *pTerm; /* One term of the conflict-target clause */ NameContext sNC; /* Context for resolving symbolic names */ Expr sCol[2]; /* Index column converted into an Expr */ + int nClause = 0; /* Counter of ON CONFLICT clauses */ assert( pTabList->nSrc==1 ); assert( pTabList->a[0].pTab!=0 ); @@ -102,87 +114,131 @@ int sqlite3UpsertAnalyzeTarget( memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; - rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); - if( rc ) return rc; - rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); - if( rc ) return rc; - - /* Check to see if the conflict target matches the rowid. */ - pTab = pTabList->a[0].pTab; - pTarget = pUpsert->pUpsertTarget; - iCursor = pTabList->a[0].iCursor; - if( HasRowid(pTab) - && pTarget->nExpr==1 - && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN - && pTerm->iColumn==XN_ROWID - ){ - /* The conflict-target is the rowid of the primary table */ - assert( pUpsert->pUpsertIdx==0 ); - return SQLITE_OK; - } - - /* Initialize sCol[0..1] to be an expression parse tree for a - ** single column of an index. The sCol[0] node will be the TK_COLLATE - ** operator and sCol[1] will be the TK_COLUMN operator. Code below - ** will populate the specific collation and column number values - ** prior to comparing against the conflict-target expression. - */ - memset(sCol, 0, sizeof(sCol)); - sCol[0].op = TK_COLLATE; - sCol[0].pLeft = &sCol[1]; - sCol[1].op = TK_COLUMN; - sCol[1].iTable = pTabList->a[0].iCursor; - - /* Check for matches against other indexes */ - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - int ii, jj, nn; - if( !IsUniqueIndex(pIdx) ) continue; - if( pTarget->nExpr!=pIdx->nKeyCol ) continue; - if( pIdx->pPartIdxWhere ){ - if( pUpsert->pUpsertTargetWhere==0 ) continue; - if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere, - pIdx->pPartIdxWhere, iCursor)!=0 ){ - continue; - } + for(; pUpsert && pUpsert->pUpsertTarget; + pUpsert=pUpsert->pNextUpsert, nClause++){ + rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); + if( rc ) return rc; + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); + if( rc ) return rc; + + /* Check to see if the conflict target matches the rowid. */ + pTab = pTabList->a[0].pTab; + pTarget = pUpsert->pUpsertTarget; + iCursor = pTabList->a[0].iCursor; + if( HasRowid(pTab) + && pTarget->nExpr==1 + && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN + && pTerm->iColumn==XN_ROWID + ){ + /* The conflict-target is the rowid of the primary table */ + assert( pUpsert->pUpsertIdx==0 ); + continue; } - nn = pIdx->nKeyCol; - for(ii=0; iiazColl[ii]; - if( pIdx->aiColumn[ii]==XN_EXPR ){ - assert( pIdx->aColExpr!=0 ); - assert( pIdx->aColExpr->nExpr>ii ); - pExpr = pIdx->aColExpr->a[ii].pExpr; - if( pExpr->op!=TK_COLLATE ){ - sCol[0].pLeft = pExpr; - pExpr = &sCol[0]; + + /* Initialize sCol[0..1] to be an expression parse tree for a + ** single column of an index. The sCol[0] node will be the TK_COLLATE + ** operator and sCol[1] will be the TK_COLUMN operator. Code below + ** will populate the specific collation and column number values + ** prior to comparing against the conflict-target expression. + */ + memset(sCol, 0, sizeof(sCol)); + sCol[0].op = TK_COLLATE; + sCol[0].pLeft = &sCol[1]; + sCol[1].op = TK_COLUMN; + sCol[1].iTable = pTabList->a[0].iCursor; + + /* Check for matches against other indexes */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int ii, jj, nn; + if( !IsUniqueIndex(pIdx) ) continue; + if( pTarget->nExpr!=pIdx->nKeyCol ) continue; + if( pIdx->pPartIdxWhere ){ + if( pUpsert->pUpsertTargetWhere==0 ) continue; + if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere, + pIdx->pPartIdxWhere, iCursor)!=0 ){ + continue; } - }else{ - sCol[0].pLeft = &sCol[1]; - sCol[1].iColumn = pIdx->aiColumn[ii]; - pExpr = &sCol[0]; } - for(jj=0; jja[jj].pExpr, pExpr,iCursor)<2 ){ - break; /* Column ii of the index matches column jj of target */ + nn = pIdx->nKeyCol; + for(ii=0; iiazColl[ii]; + if( pIdx->aiColumn[ii]==XN_EXPR ){ + assert( pIdx->aColExpr!=0 ); + assert( pIdx->aColExpr->nExpr>ii ); + pExpr = pIdx->aColExpr->a[ii].pExpr; + if( pExpr->op!=TK_COLLATE ){ + sCol[0].pLeft = pExpr; + pExpr = &sCol[0]; + } + }else{ + sCol[0].pLeft = &sCol[1]; + sCol[1].iColumn = pIdx->aiColumn[ii]; + pExpr = &sCol[0]; + } + for(jj=0; jja[jj].pExpr,pExpr,iCursor)<2 ){ + break; /* Column ii of the index matches column jj of target */ + } + } + if( jj>=nn ){ + /* The target contains no match for column jj of the index */ + break; } } - if( jj>=nn ){ - /* The target contains no match for column jj of the index */ - break; + if( iipUpsertIdx = pIdx; + break; } - if( iipUpsertIdx==0 ){ + char zWhich[16]; + if( nClause==0 && pUpsert->pNextUpsert==0 ){ + zWhich[0] = 0; + }else{ + sqlite3_snprintf(sizeof(zWhich),zWhich,"%r ", nClause+1); + } + sqlite3ErrorMsg(pParse, "%sON CONFLICT clause does not match any " + "PRIMARY KEY or UNIQUE constraint", zWhich); + return SQLITE_ERROR; } - pUpsert->pUpsertIdx = pIdx; - return SQLITE_OK; } - sqlite3ErrorMsg(pParse, "ON CONFLICT clause does not match any " - "PRIMARY KEY or UNIQUE constraint"); - return SQLITE_ERROR; + return SQLITE_OK; +} + +/* +** Return true if pUpsert is the last ON CONFLICT clause with a +** conflict target, or if pUpsert is followed by another ON CONFLICT +** clause that targets the INTEGER PRIMARY KEY. +*/ +int sqlite3UpsertNextIsIPK(Upsert *pUpsert){ + Upsert *pNext; + if( NEVER(pUpsert==0) ) return 0; + pNext = pUpsert->pNextUpsert; + if( pNext==0 ) return 1; + if( pNext->pUpsertTarget==0 ) return 1; + if( pNext->pUpsertIdx==0 ) return 1; + return 0; +} + +/* +** Given the list of ON CONFLICT clauses described by pUpsert, and +** a particular index pIdx, return a pointer to the particular ON CONFLICT +** clause that applies to the index. Or, if the index is not subject to +** any ON CONFLICT clause, return NULL. +*/ +Upsert *sqlite3UpsertOfIndex(Upsert *pUpsert, Index *pIdx){ + while( + pUpsert + && pUpsert->pUpsertTarget!=0 + && pUpsert->pUpsertIdx!=pIdx + ){ + pUpsert = pUpsert->pNextUpsert; + } + return pUpsert; } /* @@ -206,11 +262,13 @@ void sqlite3UpsertDoUpdate( SrcList *pSrc; /* FROM clause for the UPDATE */ int iDataCur; int i; + Upsert *pTop = pUpsert; assert( v!=0 ); assert( pUpsert!=0 ); - VdbeNoopComment((v, "Begin DO UPDATE of UPSERT")); iDataCur = pUpsert->iDataCur; + pUpsert = sqlite3UpsertOfIndex(pTop, pIdx); + VdbeNoopComment((v, "Begin DO UPDATE of UPSERT")); if( pIdx && iCur!=iDataCur ){ if( HasRowid(pTab) ){ int regRowid = sqlite3GetTempReg(pParse); @@ -240,19 +298,17 @@ void sqlite3UpsertDoUpdate( sqlite3VdbeJumpHere(v, i); } } - /* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So - ** we have to make a copy before passing it down into sqlite3Update() */ - pSrc = sqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0); + /* pUpsert does not own pTop->pUpsertSrc - the outer INSERT statement does. + ** So we have to make a copy before passing it down into sqlite3Update() */ + pSrc = sqlite3SrcListDup(db, pTop->pUpsertSrc, 0); /* excluded.* columns of type REAL need to be converted to a hard real */ for(i=0; inCol; i++){ if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ - sqlite3VdbeAddOp1(v, OP_RealAffinity, pUpsert->regData+i); + sqlite3VdbeAddOp1(v, OP_RealAffinity, pTop->regData+i); } } - sqlite3Update(pParse, pSrc, pUpsert->pUpsertSet, - pUpsert->pUpsertWhere, OE_Abort, 0, 0, pUpsert); - pUpsert->pUpsertSet = 0; /* Will have been deleted by sqlite3Update() */ - pUpsert->pUpsertWhere = 0; /* Will have been deleted by sqlite3Update() */ + sqlite3Update(pParse, pSrc, sqlite3ExprListDup(db,pUpsert->pUpsertSet,0), + sqlite3ExprDup(db,pUpsert->pUpsertWhere,0), OE_Abort, 0, 0, pUpsert); VdbeNoopComment((v, "End DO UPDATE of UPSERT")); } diff --git a/src/util.c b/src/util.c index fb86d7d11..fc0c2042b 100644 --- a/src/util.c +++ b/src/util.c @@ -114,6 +114,16 @@ void sqlite3Error(sqlite3 *db, int err_code){ if( err_code || db->pErr ) sqlite3ErrorFinish(db, err_code); } +/* +** The equivalent of sqlite3Error(db, SQLITE_OK). Clear the error state +** and error message. +*/ +void sqlite3ErrorClear(sqlite3 *db){ + assert( db!=0 ); + db->errCode = SQLITE_OK; + if( db->pErr ) sqlite3ValueSetNull(db->pErr); +} + /* ** Load the sqlite3.iSysErrno field if that is an appropriate thing ** to do based on the SQLite error code in rc. diff --git a/src/vdbe.c b/src/vdbe.c index 1507fb318..4442f7d9f 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -272,26 +272,39 @@ static VdbeCursor *allocateCursor( assert( iCur>=0 && iCurnCursor ); if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/ - /* Before calling sqlite3VdbeFreeCursor(), ensure the isEphemeral flag - ** is clear. Otherwise, if this is an ephemeral cursor created by - ** OP_OpenDup, the cursor will not be closed and will still be part - ** of a BtShared.pCursor list. */ - if( p->apCsr[iCur]->pBtx==0 ) p->apCsr[iCur]->isEphemeral = 0; sqlite3VdbeFreeCursor(p, p->apCsr[iCur]); p->apCsr[iCur] = 0; } - if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){ - p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z; - memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); - pCx->eCurType = eCurType; - pCx->iDb = iDb; - pCx->nField = nField; - pCx->aOffset = &pCx->aType[nField]; - if( eCurType==CURTYPE_BTREE ){ - pCx->uc.pCursor = (BtCursor*) - &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; - sqlite3BtreeCursorZero(pCx->uc.pCursor); + + /* There used to be a call to sqlite3VdbeMemClearAndResize() to make sure + ** the pMem used to hold space for the cursor has enough storage available + ** in pMem->zMalloc. But for the special case of the aMem[] entries used + ** to hold cursors, it is faster to in-line the logic. */ + assert( pMem->flags==MEM_Undefined ); + assert( (pMem->flags & MEM_Dyn)==0 ); + assert( pMem->szMalloc==0 || pMem->z==pMem->zMalloc ); + if( pMem->szMallocszMalloc>0 ){ + sqlite3DbFreeNN(pMem->db, pMem->zMalloc); + } + pMem->z = pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, nByte); + if( pMem->zMalloc==0 ){ + pMem->szMalloc = 0; + return 0; } + pMem->szMalloc = nByte; + } + + p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->zMalloc; + memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); + pCx->eCurType = eCurType; + pCx->iDb = iDb; + pCx->nField = nField; + pCx->aOffset = &pCx->aType[nField]; + if( eCurType==CURTYPE_BTREE ){ + pCx->uc.pCursor = (BtCursor*) + &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; + sqlite3BtreeCursorZero(pCx->uc.pCursor); } return pCx; } @@ -438,7 +451,10 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ sqlite3_int64 ix; assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ); assert( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ); - ExpandBlob(pMem); + if( ExpandBlob(pMem) ){ + pMem->u.i = 0; + return MEM_Int; + } rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); if( rc<=0 ){ if( rc==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){ @@ -576,6 +592,11 @@ static void registerTrace(int iReg, Mem *p){ printf("\n"); sqlite3VdbeCheckMemInvariants(p); } +/**/ void sqlite3PrintMem(Mem *pMem){ + memTracePrint(pMem); + printf("\n"); + fflush(stdout); +} #endif #ifdef SQLITE_DEBUG @@ -685,7 +706,7 @@ int sqlite3VdbeExec( #endif /*** INSERT STACK UNION HERE ***/ - assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ + assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ sqlite3VdbeEnter(p); #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( db->xProgress ){ @@ -1445,6 +1466,26 @@ case OP_IntCopy: { /* out2 */ break; } +/* Opcode: ChngCntRow P1 P2 * * * +** Synopsis: output=r[P1] +** +** Output value in register P1 as the chance count for a DML statement, +** due to the "PRAGMA count_changes=ON" setting. Or, if there was a +** foreign key error in the statement, trigger the error now. +** +** This opcode is a variant of OP_ResultRow that checks the foreign key +** immediate constraint count and throws an error if the count is +** non-zero. The P2 opcode must be 1. +*/ +case OP_ChngCntRow: { + assert( pOp->p2==1 ); + if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){ + goto abort_due_to_error; + } + /* Fall through to the next case, OP_ResultRow */ + /* no break */ deliberate_fall_through +} + /* Opcode: ResultRow P1 P2 * * * ** Synopsis: output=r[P1@P2] ** @@ -1458,37 +1499,9 @@ case OP_ResultRow: { Mem *pMem; int i; assert( p->nResColumn==pOp->p2 ); - assert( pOp->p1>0 ); + assert( pOp->p1>0 || CORRUPT_DB ); assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); - /* If this statement has violated immediate foreign key constraints, do - ** not return the number of rows modified. And do not RELEASE the statement - ** transaction. It needs to be rolled back. */ - if( SQLITE_OK!=(rc = sqlite3VdbeCheckFk(p, 0)) ){ - assert( db->flags&SQLITE_CountRows ); - assert( p->usesStmtJournal ); - goto abort_due_to_error; - } - - /* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then - ** DML statements invoke this opcode to return the number of rows - ** modified to the user. This is the only way that a VM that - ** opens a statement transaction may invoke this opcode. - ** - ** In case this is such a statement, close any statement transaction - ** opened by this VM before returning control to the user. This is to - ** ensure that statement-transactions are always nested, not overlapping. - ** If the open statement-transaction is not closed here, then the user - ** may step another VM that opens its own statement transaction. This - ** may lead to overlapping statement transactions. - ** - ** The statement transaction is never a top-level transaction. Hence - ** the RELEASE call below can never fail. - */ - assert( p->iStatement==0 || db->flags&SQLITE_CountRows ); - rc = sqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE); - assert( rc==SQLITE_OK ); - /* Invalidate all ephemeral cursor row caches */ p->cacheCtr = (p->cacheCtr + 2)|1; @@ -1928,8 +1941,7 @@ case OP_Cast: { /* in1 */ ** Synopsis: IF r[P3]==r[P1] ** ** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then -** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5, then -** store the result of comparison in register P2. +** jump to address P2. ** ** 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 @@ -1955,9 +1967,8 @@ case OP_Cast: { /* in1 */ ** If neither operand is NULL the result is the same as it would be if ** the SQLITE_NULLEQ flag were omitted from P5. ** -** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the -** content of r[P2] is only changed if the new value is NULL or 0 (false). -** In other words, a prior r[P2] value will not be overwritten by 1 (true). +** This opcode saves the result of comparison for use by the new +** OP_Jump opcode. */ /* Opcode: Ne P1 P2 P3 P4 P5 ** Synopsis: IF r[P3]!=r[P1] @@ -1965,17 +1976,12 @@ case OP_Cast: { /* in1 */ ** This works just like the Eq opcode except that the jump is taken if ** the operands in registers P1 and P3 are not equal. See the Eq opcode for ** additional information. -** -** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the -** content of r[P2] is only changed if the new value is NULL or 1 (true). -** In other words, a prior r[P2] value will not be overwritten by 0 (false). */ /* Opcode: Lt P1 P2 P3 P4 P5 ** Synopsis: IF r[P3]p3]; flags1 = pIn1->flags; flags3 = pIn3->flags; + if( (flags1 & flags3 & MEM_Int)!=0 ){ + assert( (pOp->p5 & SQLITE_AFF_MASK)!=SQLITE_AFF_TEXT || CORRUPT_DB ); + /* Common case of comparison of two integers */ + if( pIn3->u.i > pIn1->u.i ){ + iCompare = +1; + if( sqlite3aGTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + }else if( pIn3->u.i < pIn1->u.i ){ + iCompare = -1; + if( sqlite3aLTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + }else{ + iCompare = 0; + if( sqlite3aEQb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + } + VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + break; + } if( (flags1 | flags3)&MEM_Null ){ /* One or both operands are NULL */ if( pOp->p5 & SQLITE_NULLEQ ){ @@ -2057,22 +2091,16 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** then the result is always NULL. ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. */ - if( pOp->p5 & SQLITE_STOREP2 ){ - pOut = &aMem[pOp->p2]; - iCompare = 1; /* Operands are not equal */ - memAboutToChange(p, pOut); - MemSetTypeFlag(pOut, MEM_Null); - REGISTER_TRACE(pOp->p2, pOut); - }else{ - VdbeBranchTaken(2,3); - if( pOp->p5 & SQLITE_JUMPIFNULL ){ - goto jump_to_p2; - } + iCompare = 1; /* Operands are not equal */ + VdbeBranchTaken(2,3); + if( pOp->p5 & SQLITE_JUMPIFNULL ){ + goto jump_to_p2; } break; } }else{ - /* Neither operand is NULL. Do a comparison. */ + /* Neither operand is NULL and we couldn't do the special high-speed + ** integer comparison case. So do a general-case comparison. */ affinity = pOp->p5 & SQLITE_AFF_MASK; if( affinity>=SQLITE_AFF_NUMERIC ){ if( (flags1 | flags3)&MEM_Str ){ @@ -2085,14 +2113,6 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ applyNumericAffinity(pIn3,0); } } - /* Handle the common case of integer comparison here, as an - ** optimization, to avoid a call to sqlite3MemCompare() */ - if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){ - if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; } - if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; } - res = 0; - goto compare_op; - } }else if( affinity==SQLITE_AFF_TEXT ){ if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn1->flags & MEM_Int ); @@ -2115,7 +2135,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); } -compare_op: + /* At this point, res is negative, zero, or positive if reg[P1] is ** less than, equal to, or greater than reg[P3], respectively. Compute ** the answer to this operator in res2, depending on what the comparison @@ -2124,16 +2144,14 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** order: NE, EQ, GT, LE, LT, GE */ assert( OP_Eq==OP_Ne+1 ); assert( OP_Gt==OP_Ne+2 ); assert( OP_Le==OP_Ne+3 ); assert( OP_Lt==OP_Ne+4 ); assert( OP_Ge==OP_Ne+5 ); - if( res<0 ){ /* ne, eq, gt, le, lt, ge */ - static const unsigned char aLTb[] = { 1, 0, 0, 1, 1, 0 }; - res2 = aLTb[pOp->opcode - OP_Ne]; + if( res<0 ){ + res2 = sqlite3aLTb[pOp->opcode]; }else if( res==0 ){ - static const unsigned char aEQb[] = { 0, 1, 0, 1, 0, 1 }; - res2 = aEQb[pOp->opcode - OP_Ne]; + res2 = sqlite3aEQb[pOp->opcode]; }else{ - static const unsigned char aGTb[] = { 1, 0, 1, 0, 0, 1 }; - res2 = aGTb[pOp->opcode - OP_Ne]; + res2 = sqlite3aGTb[pOp->opcode]; } + iCompare = res; /* Undo any changes made by applyAffinity() to the input registers. */ assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) ); @@ -2141,67 +2159,39 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); pIn1->flags = flags1; - if( pOp->p5 & SQLITE_STOREP2 ){ - pOut = &aMem[pOp->p2]; - iCompare = res; - if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){ - /* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1 - ** and prevents OP_Ne from overwriting NULL with 0. This flag - ** is only used in contexts where either: - ** (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0) - ** (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1) - ** Therefore it is not necessary to check the content of r[P2] for - ** NULL. */ - assert( pOp->opcode==OP_Ne || pOp->opcode==OP_Eq ); - assert( res2==0 || res2==1 ); - testcase( res2==0 && pOp->opcode==OP_Eq ); - testcase( res2==1 && pOp->opcode==OP_Eq ); - testcase( res2==0 && pOp->opcode==OP_Ne ); - testcase( res2==1 && pOp->opcode==OP_Ne ); - if( (pOp->opcode==OP_Eq)==res2 ) break; - } - memAboutToChange(p, pOut); - MemSetTypeFlag(pOut, MEM_Int); - pOut->u.i = res2; - REGISTER_TRACE(pOp->p2, pOut); - }else{ - VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); - if( res2 ){ - goto jump_to_p2; - } + VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + if( res2 ){ + goto jump_to_p2; } break; } -/* Opcode: ElseNotEq * P2 * * * +/* Opcode: ElseEq * P2 * * * ** ** This opcode must follow an OP_Lt or OP_Gt comparison operator. There ** can be zero or more OP_ReleaseReg opcodes intervening, but no other ** opcodes are allowed to occur between this instruction and the previous -** OP_Lt or OP_Gt. Furthermore, the prior OP_Lt or OP_Gt must have the -** SQLITE_STOREP2 bit set in the P5 field. +** OP_Lt or OP_Gt. ** ** If result of an OP_Eq comparison on the same two operands as the -** prior OP_Lt or OP_Gt would have been NULL or false (0), then then -** jump to P2. If the result of an OP_Eq comparison on the two previous -** operands would have been true (1), then fall through. +** prior OP_Lt or OP_Gt would have been true, then jump to P2. +** If the result of an OP_Eq comparison on the two previous +** operands would have been false or NULL, then fall through. */ -case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */ +case OP_ElseEq: { /* same as TK_ESCAPE, jump */ #ifdef SQLITE_DEBUG /* Verify the preconditions of this opcode - that it follows an OP_Lt or - ** OP_Gt with the SQLITE_STOREP2 flag set, with zero or more intervening - ** OP_ReleaseReg opcodes */ + ** OP_Gt with zero or more intervening OP_ReleaseReg opcodes */ int iAddr; for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){ if( aOp[iAddr].opcode==OP_ReleaseReg ) continue; assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt ); - assert( aOp[iAddr].p5 & SQLITE_STOREP2 ); break; } #endif /* SQLITE_DEBUG */ - VdbeBranchTaken(iCompare!=0, 2); - if( iCompare!=0 ) goto jump_to_p2; + VdbeBranchTaken(iCompare==0, 2); + if( iCompare==0 ) goto jump_to_p2; break; } @@ -2512,6 +2502,24 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ break; } +/* Opcode: ZeroOrNull P1 P2 P3 * * +** Synopsis: r[P2] = 0 OR NULL +** +** If all both registers P1 and P3 are NOT NULL, then store a zero in +** register P2. If either registers P1 or P3 are NULL then put +** a NULL in register P2. +*/ +case OP_ZeroOrNull: { /* in1, in2, out2, in3 */ + if( (aMem[pOp->p1].flags & MEM_Null)!=0 + || (aMem[pOp->p3].flags & MEM_Null)!=0 + ){ + sqlite3VdbeMemSetNull(aMem + pOp->p2); + }else{ + sqlite3VdbeMemSetInt64(aMem + pOp->p2, 0); + } + break; +} + /* Opcode: NotNull P1 P2 * * * ** Synopsis: if r[P1]!=NULL goto P2 ** @@ -3881,7 +3889,7 @@ case OP_OpenDup: { pOrig = p->apCsr[pOp->p2]; assert( pOrig ); - assert( pOrig->pBtx!=0 ); /* Only ephemeral cursors can be duplicated */ + assert( pOrig->isEphemeral ); /* Only ephemeral cursors can be duplicated */ pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; @@ -3891,7 +3899,10 @@ case OP_OpenDup: { pCx->isTable = pOrig->isTable; pCx->pgnoRoot = pOrig->pgnoRoot; pCx->isOrdered = pOrig->isOrdered; - rc = sqlite3BtreeCursor(pOrig->pBtx, pCx->pgnoRoot, BTREE_WRCSR, + pCx->pBtx = pOrig->pBtx; + pCx->hasBeenDuped = 1; + pOrig->hasBeenDuped = 1; + rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, pCx->pKeyInfo, pCx->uc.pCursor); /* The sqlite3BtreeCursor() routine can only fail for the first cursor ** opened for a database. Since there is already an open cursor when this @@ -3957,9 +3968,10 @@ case OP_OpenEphemeral: { aMem[pOp->p3].z = ""; } pCx = p->apCsr[pOp->p1]; - if( pCx && pCx->pBtx ){ - /* If the ephermeral table is already open, erase all existing content - ** so that the table is empty again, rather than creating a new table. */ + if( pCx && !pCx->hasBeenDuped ){ + /* If the ephermeral table is already open and has no duplicates from + ** OP_OpenDup, then erase all existing content so that the table is + ** empty again, rather than creating a new table. */ assert( pCx->isEphemeral ); pCx->seqCount = 0; pCx->cacheStatus = CACHE_STALE; @@ -3973,33 +3985,36 @@ case OP_OpenEphemeral: { vfsFlags); if( rc==SQLITE_OK ){ rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1, 0); - } - if( rc==SQLITE_OK ){ - /* If a transient index is required, create it by calling - ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before - ** opening it. If a transient table is required, just use the - ** automatically created table with root-page 1 (an BLOB_INTKEY table). - */ - if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ - assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot, - BTREE_BLOBKEY | pOp->p5); - if( rc==SQLITE_OK ){ - assert( pCx->pgnoRoot==SCHEMA_ROOT+1 ); - assert( pKeyInfo->db==db ); - assert( pKeyInfo->enc==ENC(db) ); - rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, - pKeyInfo, pCx->uc.pCursor); + if( rc==SQLITE_OK ){ + /* If a transient index is required, create it by calling + ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before + ** opening it. If a transient table is required, just use the + ** automatically created table with root-page 1 (an BLOB_INTKEY table). + */ + if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ + assert( pOp->p4type==P4_KEYINFO ); + rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot, + BTREE_BLOBKEY | pOp->p5); + if( rc==SQLITE_OK ){ + assert( pCx->pgnoRoot==SCHEMA_ROOT+1 ); + assert( pKeyInfo->db==db ); + assert( pKeyInfo->enc==ENC(db) ); + rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, + pKeyInfo, pCx->uc.pCursor); + } + pCx->isTable = 0; + }else{ + pCx->pgnoRoot = SCHEMA_ROOT; + rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR, + 0, pCx->uc.pCursor); + pCx->isTable = 1; } - pCx->isTable = 0; - }else{ - pCx->pgnoRoot = SCHEMA_ROOT; - rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR, - 0, pCx->uc.pCursor); - pCx->isTable = 1; + } + pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); + if( rc ){ + sqlite3BtreeClose(pCx->pBtx); } } - pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); } if( rc ) goto abort_due_to_error; pCx->nullRow = 1; @@ -4433,13 +4448,13 @@ case OP_SeekGT: { /* jump, in3, group */ ** ** There are three possible outcomes from this opcode:
      ** -**
    1. If after This.P1 steps, the cursor is still point to a place that -** is earlier in the btree than the target row, -** then fall through into the subsquence OP_SeekGE opcode. +**
    2. If after This.P1 steps, the cursor is still pointing to a place that +** is earlier in the btree than the target row, then fall through +** into the subsquence OP_SeekGE opcode. ** **
    3. If the cursor is successfully moved to the target row by 0 or more ** sqlite3BtreeNext() calls, then jump to This.P2, which will land just -** past the OP_IdxGT opcode that follows the OP_SeekGE. +** past the OP_IdxGT or OP_IdxGE opcode that follows the OP_SeekGE. ** **
    4. If the cursor ends up past the target row (indicating the the target ** row does not exist in the btree) then jump to SeekOP.P2. @@ -4456,7 +4471,8 @@ case OP_SeekScan: { /* pOp->p2 points to the first instruction past the OP_IdxGT that ** follows the OP_SeekGE. */ assert( pOp->p2>=(int)(pOp-aOp)+2 ); - assert( aOp[pOp->p2-1].opcode==OP_IdxGT ); + assert( aOp[pOp->p2-1].opcode==OP_IdxGT || aOp[pOp->p2-1].opcode==OP_IdxGE ); + testcase( aOp[pOp->p2-1].opcode==OP_IdxGE ); assert( pOp[1].p1==aOp[pOp->p2-1].p1 ); assert( pOp[1].p2==aOp[pOp->p2-1].p2 ); assert( pOp[1].p3==aOp[pOp->p2-1].p3 ); @@ -4560,8 +4576,18 @@ case OP_SeekHit: { assert( pC!=0 ); assert( pOp->p3>=pOp->p2 ); if( pC->seekHitp2 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p2); + } +#endif pC->seekHit = pOp->p2; }else if( pC->seekHit>pOp->p3 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p3); + } +#endif pC->seekHit = pOp->p3; } break; @@ -4676,6 +4702,11 @@ case OP_IfNoHope: { /* jump, in3 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit is %d\n", pC->seekHit); + } +#endif if( pC->seekHit>=pOp->p4.i ) break; /* Fall through into OP_NotFound */ /* no break */ deliberate_fall_through @@ -4909,8 +4940,10 @@ case OP_NewRowid: { /* out2 */ VdbeCursor *pC; /* Cursor of table to get the new rowid */ int res; /* Result of an sqlite3BtreeLast() */ int cnt; /* Counter to limit the number of searches */ +#ifndef SQLITE_OMIT_AUTOINCREMENT Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */ VdbeFrame *pFrame; /* Root frame of VDBE */ +#endif v = 0; res = 0; @@ -5103,7 +5136,7 @@ case OP_Insert: { /* Invoke the pre-update hook, if any */ if( pTab ){ if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){ - sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2); + sqlite3VdbePreUpdateHook(p,pC,SQLITE_INSERT,zDb,pTab,x.nKey,pOp->p2,-1); } if( db->xUpdateCallback==0 || pTab->aCol==0 ){ /* Prevent post-update hook from running in cases when it should not */ @@ -5126,7 +5159,8 @@ case OP_Insert: { } x.pKey = 0; rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, - (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), seekResult + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + seekResult ); pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; @@ -5143,6 +5177,33 @@ case OP_Insert: { break; } +/* Opcode: RowCell P1 P2 P3 * * +** +** P1 and P2 are both open cursors. Both must be opened on the same type +** of table - intkey or index. This opcode is used as part of copying +** the current row from P2 into P1. If the cursors are opened on intkey +** tables, register P3 contains the rowid to use with the new record in +** P1. If they are opened on index tables, P3 is not used. +** +** This opcode must be followed by either an Insert or InsertIdx opcode +** with the OPFLAG_PREFORMAT flag set to complete the insert operation. +*/ +case OP_RowCell: { + VdbeCursor *pDest; /* Cursor to write to */ + VdbeCursor *pSrc; /* Cursor to read from */ + i64 iKey; /* Rowid value to insert with */ + assert( pOp[1].opcode==OP_Insert || pOp[1].opcode==OP_IdxInsert ); + assert( pOp[1].opcode==OP_Insert || pOp->p3==0 ); + assert( pOp[1].opcode==OP_IdxInsert || pOp->p3>0 ); + assert( pOp[1].p5 & OPFLAG_PREFORMAT ); + pDest = p->apCsr[pOp->p1]; + pSrc = p->apCsr[pOp->p2]; + iKey = pOp->p3 ? aMem[pOp->p3].u.i : 0; + rc = sqlite3BtreeTransferRow(pDest->uc.pCursor, pSrc->uc.pCursor, iKey); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + break; +}; + /* Opcode: Delete P1 P2 P3 P4 P5 ** ** Delete the record at which the P1 cursor is currently pointing. @@ -5235,7 +5296,7 @@ case OP_Delete: { sqlite3VdbePreUpdateHook(p, pC, (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, zDb, pTab, pC->movetoTarget, - pOp->p3 + pOp->p3, -1 ); } if( opflags & OPFLAG_ISNOOP ) break; @@ -5798,7 +5859,7 @@ case OP_IdxInsert: { /* in2 */ assert( pC!=0 ); assert( !isSorter(pC) ); pIn2 = &aMem[pOp->p2]; - assert( pIn2->flags & MEM_Blob ); + assert( (pIn2->flags & MEM_Blob) || (pOp->p5 & OPFLAG_PREFORMAT) ); if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->isTable==0 ); @@ -5809,7 +5870,7 @@ case OP_IdxInsert: { /* in2 */ x.aMem = aMem + pOp->p3; x.nMem = (u16)pOp->p4.i; rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, - (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) ); assert( pC->deferredMoveto==0 ); @@ -5882,7 +5943,7 @@ case OP_IdxDelete: { rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); if( rc ) goto abort_due_to_error; }else if( pOp->p5 ){ - rc = SQLITE_CORRUPT_INDEX; + rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); goto abort_due_to_error; } assert( pC->deferredMoveto==0 ); @@ -5961,6 +6022,8 @@ case OP_IdxRowid: { /* out2 */ pTabCur->deferredMoveto = 1; assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 ); pTabCur->aAltMap = pOp->p4.ai; + assert( !pC->isEphemeral ); + assert( !pTabCur->isEphemeral ); pTabCur->pAltCursor = pC; }else{ pOut = out2Prerelease(p, pOp); @@ -6179,11 +6242,10 @@ case OP_Destroy: { /* out2 */ ** P2==1 then the table to be clear is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** -** If the P3 value is non-zero, then the table referred to must be an -** intkey table (an SQL table, not an index). In this case the row change -** count is incremented by the number of rows in the table being cleared. -** If P3 is greater than zero, then the value stored in register P3 is -** also incremented by the number of rows in the table being cleared. +** If the P3 value is non-zero, then the row change count is incremented +** by the number of rows in the table being cleared. If P3 is greater +** than zero, then the value stored in register P3 is also incremented +** by the number of rows in the table being cleared. ** ** See also: Destroy */ @@ -6194,9 +6256,7 @@ case OP_Clear: { nChange = 0; assert( p->readOnly==0 ); assert( DbMaskTest(p->btreeMask, pOp->p2) ); - rc = sqlite3BtreeClearTable( - db->aDb[pOp->p2].pBt, (u32)pOp->p1, (pOp->p3 ? &nChange : 0) - ); + rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, (u32)pOp->p1, &nChange); if( pOp->p3 ){ p->nChange += nChange; if( pOp->p3>0 ){ @@ -6302,13 +6362,15 @@ case OP_ParseSchema: { iDb = pOp->p1; assert( iDb>=0 && iDbnDb ); - assert( DbHasProperty(db, iDb, DB_SchemaLoaded) ); + assert( DbHasProperty(db, iDb, DB_SchemaLoaded) + || db->mallocFailed + || (CORRUPT_DB && (db->flags & SQLITE_NoSchemaError)!=0) ); #ifndef SQLITE_OMIT_ALTERTABLE if( pOp->p4.z==0 ){ sqlite3SchemaClear(db->aDb[iDb].pSchema); db->mDbFlags &= ~DBFLAG_SchemaKnownOk; - rc = sqlite3InitOne(db, iDb, &p->zErrMsg, INITFLAG_AlterTable); + rc = sqlite3InitOne(db, iDb, &p->zErrMsg, pOp->p5); db->mDbFlags |= DBFLAG_SchemaChange; p->expired = 0; }else @@ -7184,6 +7246,7 @@ case OP_JournalMode: { /* out2 */ pPager = sqlite3BtreePager(pBt); eOld = sqlite3PagerGetJournalMode(pPager); if( eNew==PAGER_JOURNALMODE_QUERY ) eNew = eOld; + assert( sqlite3BtreeHoldsMutex(pBt) ); if( !sqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld; #ifndef SQLITE_OMIT_WAL diff --git a/src/vdbe.h b/src/vdbe.h index 17f11fdd7..3257ff68a 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -223,7 +223,7 @@ VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); #else # define sqlite3ExplainBreakpoint(A,B) /*no-op*/ #endif -void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); +void sqlite3VdbeAddParseSchemaOp(Vdbe*, int, char*, u16); void sqlite3VdbeChangeOpcode(Vdbe*, int addr, u8); void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1); void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 7d22cb809..b6e028880 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -86,6 +86,7 @@ struct VdbeCursor { Bool isEphemeral:1; /* True for an ephemeral table */ Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ + Bool hasBeenDuped:1; /* This cursor was source or target of OP_OpenDup */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ Btree *pBtx; /* Separate file holding temporary table */ i64 seqCount; /* Sequence counter */ @@ -381,7 +382,7 @@ struct Vdbe { Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ Parse *pParse; /* Parsing context used to create this Vdbe */ ynVar nVar; /* Number of entries in aVar[] */ - u32 magic; /* Magic number for sanity checking */ + u32 iVdbeMagic; /* Magic number defining state of the SQL statement */ int nMem; /* Number of memory locations currently allocated */ int nCursor; /* Number of slots in apCsr[] */ u32 cacheCtr; /* VdbeCursor row cache generation counter */ @@ -471,6 +472,7 @@ struct PreUpdate { UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */ UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ int iNewReg; /* Register for new.* values */ + int iBlobWrite; /* Value returned by preupdate_blobwrite() */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ Mem *aNew; /* Array of new.* values */ @@ -514,7 +516,7 @@ int sqlite3VdbeMemCopy(Mem*, const Mem*); void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); void sqlite3VdbeMemMove(Mem*, Mem*); int sqlite3VdbeMemNulTerminate(Mem*); -int sqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*)); +int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*)); void sqlite3VdbeMemSetInt64(Mem*, i64); #ifdef SQLITE_OMIT_FLOATING_POINT # define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 @@ -559,7 +561,8 @@ void sqlite3VdbeFrameMemDel(void*); /* Destructor on Mem */ void sqlite3VdbeFrameDelete(VdbeFrame*); /* Actually deletes the Frame */ int sqlite3VdbeFrameRestore(VdbeFrame *); #ifdef SQLITE_ENABLE_PREUPDATE_HOOK -void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int); +void sqlite3VdbePreUpdateHook( + Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int); #endif int sqlite3VdbeTransferError(Vdbe *p); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index a9cbf92fc..5de7c64c2 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -392,7 +392,7 @@ static int invokeValueDestructor( }else{ xDel((void*)p); } - if( pCtx ) sqlite3_result_error_toobig(pCtx); + sqlite3_result_error_toobig(pCtx); return SQLITE_TOOBIG; } void sqlite3_result_blob( @@ -617,7 +617,7 @@ static int sqlite3Step(Vdbe *p){ int rc; assert(p); - if( p->magic!=VDBE_MAGIC_RUN ){ + if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){ /* We used to require that sqlite3_reset() be called before retrying ** sqlite3_step() after any error or after SQLITE_DONE. But beginning ** with version 3.7.0, we changed this so that sqlite3_reset() would @@ -1333,7 +1333,7 @@ static int vdbeUnbind(Vdbe *p, int i){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(p->db->mutex); - if( p->magic!=VDBE_MAGIC_RUN || p->pc>=0 ){ + if( p->iVdbeMagic!=VDBE_MAGIC_RUN || p->pc>=0 ){ sqlite3Error(p->db, SQLITE_MISUSE); sqlite3_mutex_leave(p->db->mutex); sqlite3_log(SQLITE_MISUSE, @@ -1374,7 +1374,7 @@ static int bindText( sqlite3_stmt *pStmt, /* The statement to bind against */ int i, /* Index of the parameter to bind */ const void *zData, /* Pointer to the data to be bound */ - int nData, /* Number of bytes of data to be bound */ + i64 nData, /* Number of bytes of data to be bound */ void (*xDel)(void*), /* Destructor for the data */ u8 encoding /* Encoding for the data */ ){ @@ -1426,11 +1426,7 @@ int sqlite3_bind_blob64( void (*xDel)(void*) ){ assert( xDel!=SQLITE_DYNAMIC ); - if( nData>0x7fffffff ){ - return invokeValueDestructor(zData, xDel, 0); - }else{ - return bindText(pStmt, i, zData, (int)nData, xDel, 0); - } + return bindText(pStmt, i, zData, nData, xDel, 0); } int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ int rc; @@ -1500,12 +1496,8 @@ int sqlite3_bind_text64( unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); - if( nData>0x7fffffff ){ - return invokeValueDestructor(zData, xDel, 0); - }else{ - if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; - return bindText(pStmt, i, zData, (int)nData, xDel, enc); - } + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + return bindText(pStmt, i, zData, nData, xDel, enc); } #ifndef SQLITE_OMIT_UTF16 int sqlite3_bind_text16( @@ -1687,7 +1679,7 @@ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ */ int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; - return v!=0 && v->magic==VDBE_MAGIC_RUN && v->pc>=0; + return v!=0 && v->iVdbeMagic==VDBE_MAGIC_RUN && v->pc>=0; } /* @@ -1905,6 +1897,17 @@ int sqlite3_preupdate_depth(sqlite3 *db){ } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is designed to be called from within a pre-update callback +** only. +*/ +int sqlite3_preupdate_blobwrite(sqlite3 *db){ + PreUpdate *p = db->pPreUpdate; + return (p ? p->iBlobWrite : -1); +} +#endif + #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* ** This function is called from within a pre-update callback to retrieve diff --git a/src/vdbeaux.c b/src/vdbeaux.c index dfd259d71..d22a6a559 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -35,7 +35,7 @@ Vdbe *sqlite3VdbeCreate(Parse *pParse){ p->pNext = db->pVdbe; p->pPrev = 0; db->pVdbe = p; - p->magic = VDBE_MAGIC_INIT; + p->iVdbeMagic = VDBE_MAGIC_INIT; p->pParse = pParse; pParse->pVdbe = p; assert( pParse->aLabel==0 ); @@ -236,7 +236,7 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ VdbeOp *pOp; i = p->nOp; - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); assert( op>=0 && op<0xff ); if( p->nOpAlloc<=i ){ return growOp3(p, op, p1, p2, p3); @@ -471,9 +471,10 @@ void sqlite3VdbeExplainPop(Parse *pParse){ ** The zWhere string must have been obtained from sqlite3_malloc(). ** This routine will take ownership of the allocated memory. */ -void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){ +void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere, u16 p5){ int j; sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC); + sqlite3VdbeChangeP5(p, p5); for(j=0; jdb->nDb; j++) sqlite3VdbeUsesBtree(p, j); sqlite3MayAbort(p->pParse); } @@ -565,7 +566,7 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){ void sqlite3VdbeResolveLabel(Vdbe *v, int x){ Parse *p = v->pParse; int j = ADDR(x); - assert( v->magic==VDBE_MAGIC_INIT ); + assert( v->iVdbeMagic==VDBE_MAGIC_INIT ); assert( j<-p->nLabel ); assert( j>=0 ); #ifdef SQLITE_DEBUG @@ -890,7 +891,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ ** Return the address of the next instruction to be inserted. */ int sqlite3VdbeCurrentAddr(Vdbe *p){ - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); return p->nOp; } @@ -975,7 +976,7 @@ VdbeOp *sqlite3VdbeAddOpList( int i; VdbeOp *pOut, *pFirst; assert( nOp>0 ); - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){ return 0; } @@ -1299,7 +1300,7 @@ void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){ sqlite3 *db; assert( p!=0 ); db = p->db; - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); assert( p->aOp!=0 || db->mallocFailed ); if( db->mallocFailed ){ if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4); @@ -1428,7 +1429,7 @@ VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ /* C89 specifies that the constant "dummy" will be initialized to all ** zeros, which is correct. MSVC generates a warning, nevertheless. */ static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */ - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); if( addr<0 ){ addr = p->nOp - 1; } @@ -1486,11 +1487,7 @@ char *sqlite3VdbeDisplayComment( char c; zSynopsis = zOpName += nOpName + 1; if( strncmp(zSynopsis,"IF ",3)==0 ){ - if( pOp->p5 & SQLITE_STOREP2 ){ - sqlite3_snprintf(sizeof(zAlt), zAlt, "r[P2] = (%s)", zSynopsis+3); - }else{ - sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); - } + sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); zSynopsis = zAlt; } for(ii=0; (c = zSynopsis[ii])!=0; ii++){ @@ -2113,7 +2110,7 @@ int sqlite3VdbeList( Op *pOp; /* Current opcode */ assert( p->explain ); - assert( p->magic==VDBE_MAGIC_RUN ); + assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); /* Even though this opcode does not use dynamic strings for @@ -2293,14 +2290,14 @@ void sqlite3VdbeRewind(Vdbe *p){ int i; #endif assert( p!=0 ); - assert( p->magic==VDBE_MAGIC_INIT || p->magic==VDBE_MAGIC_RESET ); + assert( p->iVdbeMagic==VDBE_MAGIC_INIT || p->iVdbeMagic==VDBE_MAGIC_RESET ); /* There should be at least one opcode. */ assert( p->nOp>0 ); /* Set the magic to VDBE_MAGIC_RUN sooner rather than later. */ - p->magic = VDBE_MAGIC_RUN; + p->iVdbeMagic = VDBE_MAGIC_RUN; #ifdef SQLITE_DEBUG for(i=0; inMem; i++){ @@ -2356,8 +2353,10 @@ void sqlite3VdbeMakeReady( assert( p!=0 ); assert( p->nOp>0 ); assert( pParse!=0 ); - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); assert( pParse==p->pParse ); + p->pVList = pParse->pVList; + pParse->pVList = 0; db = p->db; assert( db->mallocFailed==0 ); nVar = pParse->nVar; @@ -2442,8 +2441,6 @@ void sqlite3VdbeMakeReady( } } - p->pVList = pParse->pVList; - pParse->pVList = 0; if( db->mallocFailed ){ p->nVar = 0; p->nCursor = 0; @@ -2471,20 +2468,15 @@ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ return; } assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE ); + assert( pCx->pBtx==0 || pCx->isEphemeral ); switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); break; } case CURTYPE_BTREE: { - if( pCx->isEphemeral ){ - if( pCx->pBtx ) sqlite3BtreeClose(pCx->pBtx); - /* The pCx->pCursor will be close automatically, if it exists, by - ** the call above. */ - }else{ - assert( pCx->uc.pCursor!=0 ); - sqlite3BtreeCloseCursor(pCx->uc.pCursor); - } + assert( pCx->uc.pCursor!=0 ); + sqlite3BtreeCloseCursor(pCx->uc.pCursor); break; } #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -3041,7 +3033,7 @@ int sqlite3VdbeHalt(Vdbe *p){ ** one, or the complete transaction if there is no statement transaction. */ - if( p->magic!=VDBE_MAGIC_RUN ){ + if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){ return SQLITE_OK; } if( db->mallocFailed ){ @@ -3199,7 +3191,7 @@ int sqlite3VdbeHalt(Vdbe *p){ assert( db->nVdbeRead>=db->nVdbeWrite ); assert( db->nVdbeWrite>=0 ); } - p->magic = VDBE_MAGIC_HALT; + p->iVdbeMagic = VDBE_MAGIC_HALT; checkActiveVdbeCnt(db); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; @@ -3372,7 +3364,7 @@ int sqlite3VdbeReset(Vdbe *p){ } } #endif - p->magic = VDBE_MAGIC_RESET; + p->iVdbeMagic = VDBE_MAGIC_RESET; return p->rc & db->errMask; } @@ -3382,7 +3374,7 @@ int sqlite3VdbeReset(Vdbe *p){ */ int sqlite3VdbeFinalize(Vdbe *p){ int rc = SQLITE_OK; - if( p->magic==VDBE_MAGIC_RUN || p->magic==VDBE_MAGIC_HALT ){ + if( p->iVdbeMagic==VDBE_MAGIC_RUN || p->iVdbeMagic==VDBE_MAGIC_HALT ){ rc = sqlite3VdbeReset(p); assert( (rc & p->db->errMask)==rc ); } @@ -3443,7 +3435,7 @@ void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } - if( p->magic!=VDBE_MAGIC_INIT ){ + if( p->iVdbeMagic!=VDBE_MAGIC_INIT ){ releaseMemArray(p->aVar, p->nVar); sqlite3DbFree(db, p->pVList); sqlite3DbFree(db, p->pFree); @@ -3491,7 +3483,7 @@ void sqlite3VdbeDelete(Vdbe *p){ if( p->pNext ){ p->pNext->pPrev = p->pPrev; } - p->magic = VDBE_MAGIC_DEAD; + p->iVdbeMagic = VDBE_MAGIC_DEAD; p->db = 0; sqlite3DbFreeNN(db, p); } @@ -3568,6 +3560,7 @@ int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){ assert( p->eCurType==CURTYPE_BTREE || p->eCurType==CURTYPE_PSEUDO ); if( p->deferredMoveto ){ u32 iMap; + assert( !p->isEphemeral ); if( p->aAltMap && (iMap = p->aAltMap[1+*piCol])>0 && !p->nullRow ){ *pp = p->pAltCursor; *piCol = iMap - 1; @@ -5196,7 +5189,8 @@ void sqlite3VdbePreUpdateHook( const char *zDb, /* Database name */ Table *pTab, /* Modified table */ i64 iKey1, /* Initial key value */ - int iReg /* Register for new.* record */ + int iReg, /* Register for new.* record */ + int iBlobWrite ){ sqlite3 *db = v->db; i64 iKey2; @@ -5232,6 +5226,7 @@ void sqlite3VdbePreUpdateHook( preupdate.iKey1 = iKey1; preupdate.iKey2 = iKey2; preupdate.pTab = pTab; + preupdate.iBlobWrite = iBlobWrite; db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); diff --git a/src/vdbeblob.c b/src/vdbeblob.c index d34e3cf9f..a4e79bf89 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -420,7 +420,7 @@ static int blobReadWrite( sqlite3_int64 iKey; iKey = sqlite3BtreeIntegerKey(p->pCsr); sqlite3VdbePreUpdateHook( - v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1 + v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol ); } #endif @@ -491,6 +491,7 @@ int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ rc = SQLITE_ABORT; }else{ char *zErr; + ((Vdbe*)p->pStmt)->rc = SQLITE_OK; rc = blobSeekToRow(p, iRow, &zErr); if( rc!=SQLITE_OK ){ sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); diff --git a/src/vdbemem.c b/src/vdbemem.c index 384eacc01..dc177161b 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -75,7 +75,9 @@ int sqlite3VdbeCheckMemInvariants(Mem *p){ /* The szMalloc field holds the correct memory allocation size */ assert( p->szMalloc==0 - || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc) ); + || (p->flags==MEM_Undefined + && p->szMalloc<=sqlite3DbMallocSize(p->db,p->zMalloc)) + || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc)); /* If p holds a string or blob, the Mem.z must point to exactly ** one of the following: @@ -239,7 +241,9 @@ SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPreserve){ testcase( bPreserve && pMem->z==0 ); assert( pMem->szMalloc==0 - || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) ); + || (pMem->flags==MEM_Undefined + && pMem->szMalloc<=sqlite3DbMallocSize(pMem->db,pMem->zMalloc)) + || pMem->szMalloc==sqlite3DbMallocSize(pMem->db,pMem->zMalloc)); if( pMem->szMalloc>0 && bPreserve && pMem->z==pMem->zMalloc ){ if( pMem->db ){ pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); @@ -1068,11 +1072,11 @@ void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){ int sqlite3VdbeMemSetStr( Mem *pMem, /* Memory cell to set to string value */ const char *z, /* String pointer */ - int n, /* Bytes in string, or negative */ + i64 n, /* Bytes in string, or negative */ u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - int nByte = n; /* New value for pMem->n */ + i64 nByte = n; /* New value for pMem->n */ int iLimit; /* Maximum allowed string or blob size */ u16 flags = 0; /* New value for pMem->flags */ @@ -1094,7 +1098,7 @@ int sqlite3VdbeMemSetStr( if( nByte<0 ){ assert( enc!=0 ); if( enc==SQLITE_UTF8 ){ - nByte = 0x7fffffff & (int)strlen(z); + nByte = strlen(z); }else{ for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){} } @@ -1106,7 +1110,7 @@ int sqlite3VdbeMemSetStr( ** management (one of MEM_Dyn or MEM_Static). */ if( xDel==SQLITE_TRANSIENT ){ - u32 nAlloc = nByte; + i64 nAlloc = nByte; if( flags&MEM_Term ){ nAlloc += (enc==SQLITE_UTF8?1:2); } @@ -1132,7 +1136,7 @@ int sqlite3VdbeMemSetStr( } } - pMem->n = nByte; + pMem->n = (int)(nByte & 0x7fffffff); pMem->flags = flags; if( enc ){ pMem->enc = enc; @@ -1152,7 +1156,7 @@ int sqlite3VdbeMemSetStr( #endif if( nByte>iLimit ){ - return SQLITE_TOOBIG; + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); } return SQLITE_OK; diff --git a/src/vdbesort.c b/src/vdbesort.c index f61b8b732..397935aa8 100644 --- a/src/vdbesort.c +++ b/src/vdbesort.c @@ -1073,8 +1073,9 @@ static void vdbeSorterWorkDebug(SortSubtask *pTask, const char *zEvent){ fprintf(stderr, "%lld:%d %s\n", t, iTask, zEvent); } static void vdbeSorterRewindDebug(const char *zEvent){ - i64 t; - sqlite3OsCurrentTimeInt64(sqlite3_vfs_find(0), &t); + i64 t = 0; + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + if( ALWAYS(pVfs) ) sqlite3OsCurrentTimeInt64(pVfs, &t); fprintf(stderr, "%lld:X %s\n", t, zEvent); } static void vdbeSorterPopulateDebug( diff --git a/src/vdbetrace.c b/src/vdbetrace.c index 32c66af22..1095e7f58 100644 --- a/src/vdbetrace.c +++ b/src/vdbetrace.c @@ -125,7 +125,7 @@ char *sqlite3VdbeExpandSql( assert( idx>0 ); } zRawSql += nToken; - nextIndex = idx + 1; + nextIndex = MAX(idx + 1, nextIndex); assert( idx>0 && idx<=p->nVar ); pVar = &p->aVar[idx-1]; if( pVar->flags & MEM_Null ){ diff --git a/src/vtab.c b/src/vtab.c index b2c01f2fa..c9dcadae4 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -489,7 +489,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ sqlite3VdbeAddOp0(v, OP_Expire); zWhere = sqlite3MPrintf(db, "name=%Q AND sql=%Q", pTab->zName, zStmt); - sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere); + sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere, 0); sqlite3DbFree(db, zStmt); iReg = ++pParse->nMem; @@ -660,6 +660,7 @@ static int vtabCallConstructor( zType[i-1] = '\0'; } pTab->aCol[iCol].colFlags |= COLFLAG_HIDDEN; + pTab->tabFlags |= TF_HasHidden; oooHidden = TF_OOOHidden; }else{ pTab->tabFlags |= oooHidden; @@ -828,7 +829,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ Table *pNew = sParse.pNewTable; Index *pIdx; pTab->aCol = pNew->aCol; - pTab->nCol = pNew->nCol; + pTab->nNVCol = pTab->nCol = pNew->nCol; pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid); pNew->nCol = 0; pNew->aCol = 0; @@ -1220,6 +1221,7 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ pTab->pSchema = db->aDb[0].pSchema; assert( pTab->nModuleArg==0 ); pTab->iPKey = -1; + pTab->tabFlags |= TF_Eponymous; addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); addModuleArgument(pParse, pTab, 0); addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); diff --git a/src/wal.c b/src/wal.c index ac0a03e93..c3f84dd2e 100644 --- a/src/wal.c +++ b/src/wal.c @@ -999,7 +999,6 @@ static void walCleanupHash(Wal *pWal){ int iLimit = 0; /* Zero values greater than this */ int nByte; /* Number of bytes to zero in aPgno[] */ int i; /* Used to iterate through aHash[] */ - int rc; /* Return code form walHashGet() */ assert( pWal->writeLock ); testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE-1 ); @@ -1014,8 +1013,8 @@ static void walCleanupHash(Wal *pWal){ */ assert( pWal->nWiData>walFramePage(pWal->hdr.mxFrame) ); assert( pWal->apWiData[walFramePage(pWal->hdr.mxFrame)] ); - rc = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc); - if( NEVER(rc) ) return; /* Defense-in-depth, in case (1) above is wrong */ + i = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc); + if( NEVER(i) ) return; /* Defense-in-depth, in case (1) above is wrong */ /* Zero all hash-table entries that correspond to frame numbers greater ** than pWal->hdr.mxFrame. diff --git a/src/walker.c b/src/walker.c index 7649036f5..8c0308473 100644 --- a/src/walker.c +++ b/src/walker.c @@ -22,7 +22,7 @@ ** Walk all expressions linked into the list of Window objects passed ** as the second argument. */ -static int walkWindowList(Walker *pWalker, Window *pList){ +static int walkWindowList(Walker *pWalker, Window *pList, int bOneOnly){ Window *pWin; for(pWin=pList; pWin; pWin=pWin->pNextWin){ int rc; @@ -32,15 +32,11 @@ static int walkWindowList(Walker *pWalker, Window *pList){ if( rc ) return WRC_Abort; rc = sqlite3WalkExpr(pWalker, pWin->pFilter); if( rc ) return WRC_Abort; - - /* The next two are purely for calls to sqlite3RenameExprUnmap() - ** within sqlite3WindowOffsetExpr(). Because of constraints imposed - ** by sqlite3WindowOffsetExpr(), they can never fail. The results do - ** not matter anyhow. */ rc = sqlite3WalkExpr(pWalker, pWin->pStart); - if( NEVER(rc) ) return WRC_Abort; + if( rc ) return WRC_Abort; rc = sqlite3WalkExpr(pWalker, pWin->pEnd); - if( NEVER(rc) ) return WRC_Abort; + if( rc ) return WRC_Abort; + if( bOneOnly ) break; } return WRC_Continue; } @@ -88,7 +84,7 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ } #ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(pExpr, EP_WinFunc) ){ - if( walkWindowList(pWalker, pExpr->y.pWin) ) return WRC_Abort; + if( walkWindowList(pWalker, pExpr->y.pWin, 1) ) return WRC_Abort; } #endif } @@ -116,6 +112,16 @@ int sqlite3WalkExprList(Walker *pWalker, ExprList *p){ return WRC_Continue; } +/* +** This is a no-op callback for Walker->xSelectCallback2. If this +** callback is set, then the Select->pWinDefn list is traversed. +*/ +void sqlite3WalkWinDefnDummyCallback(Walker *pWalker, Select *p){ + UNUSED_PARAMETER(pWalker); + UNUSED_PARAMETER(p); + /* No-op */ +} + /* ** Walk all expressions associated with SELECT statement p. Do ** not invoke the SELECT callback on p, but do (of course) invoke @@ -129,13 +135,18 @@ int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){ if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort; if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort; if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort; -#if !defined(SQLITE_OMIT_WINDOWFUNC) && !defined(SQLITE_OMIT_ALTERTABLE) - { - Parse *pParse = pWalker->pParse; - if( pParse && IN_RENAME_OBJECT ){ +#if !defined(SQLITE_OMIT_WINDOWFUNC) + if( p->pWinDefn ){ + Parse *pParse; + if( pWalker->xSelectCallback2==sqlite3WalkWinDefnDummyCallback + || ((pParse = pWalker->pParse)!=0 && IN_RENAME_OBJECT) +#ifndef SQLITE_OMIT_CTE + || pWalker->xSelectCallback2==sqlite3SelectPopWith +#endif + ){ /* The following may return WRC_Abort if there are unresolvable ** symbols (e.g. a table that does not exist) in a window definition. */ - int rc = walkWindowList(pWalker, p->pWinDefn); + int rc = walkWindowList(pWalker, p->pWinDefn, 0); return rc; } } @@ -153,10 +164,10 @@ int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){ int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ SrcList *pSrc; int i; - struct SrcList_item *pItem; + SrcItem *pItem; pSrc = p->pSrc; - if( pSrc ){ + if( ALWAYS(pSrc) ){ for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){ return WRC_Abort; @@ -169,7 +180,7 @@ int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ } } return WRC_Continue; -} +} /* ** Call sqlite3WalkExpr() for every expression in Select statement p. diff --git a/src/where.c b/src/where.c index 2b97114e6..5315520db 100644 --- a/src/where.c +++ b/src/where.c @@ -37,12 +37,6 @@ struct HiddenIndexInfo { /* Forward declaration of methods */ static int whereLoopResize(sqlite3*, WhereLoop*, int); -/* Test variable that can be set to enable WHERE tracing */ -#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) -/***/ int sqlite3WhereTrace = 0; -#endif - - /* ** Return the estimated number of output rows from a WHERE clause */ @@ -105,6 +99,32 @@ int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){ return pInner->addrNxt; } +/* +** While generating code for the min/max optimization, after handling +** the aggregate-step call to min() or max(), check to see if any +** additional looping is required. If the output order is such that +** we are certain that the correct answer has already been found, then +** code an OP_Goto to by pass subsequent processing. +** +** Any extra OP_Goto that is coded here is an optimization. The +** correct answer should be obtained regardless. This OP_Goto just +** makes the answer appear faster. +*/ +void sqlite3WhereMinMaxOptEarlyOut(Vdbe *v, WhereInfo *pWInfo){ + WhereLevel *pInner; + int i; + if( !pWInfo->bOrderedInnerLoop ) return; + if( pWInfo->nOBSat==0 ) return; + for(i=pWInfo->nLevel-1; i>=0; i--){ + pInner = &pWInfo->a[i]; + if( (pInner->pWLoop->wsFlags & WHERE_COLUMN_IN)!=0 ){ + sqlite3VdbeGoto(v, pInner->addrNxt); + return; + } + } + sqlite3VdbeGoto(v, pWInfo->iBreak); +} + /* ** Return the VDBE address or label to jump to in order to continue ** immediately with the next row of a WHERE clause. @@ -241,7 +261,9 @@ static void createMask(WhereMaskSet *pMaskSet, int iCursor){ */ static Expr *whereRightSubexprIsColumn(Expr *p){ p = sqlite3ExprSkipCollateAndLikely(p->pRight); - if( ALWAYS(p!=0) && p->op==TK_COLUMN ) return p; + if( ALWAYS(p!=0) && p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return p; + } return 0; } @@ -316,6 +338,18 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ } pScan->pWC = pWC; pScan->k = k+1; +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x20000 ){ + int ii; + sqlite3DebugPrintf("SCAN-TERM %p: nEquiv=%d", + pTerm, pScan->nEquiv); + for(ii=0; iinEquiv; ii++){ + sqlite3DebugPrintf(" {%d:%d}", + pScan->aiCur[ii], pScan->aiColumn[ii]); + } + sqlite3DebugPrintf("\n"); + } +#endif return pTerm; } } @@ -472,7 +506,7 @@ static int findIndexCol( for(i=0; inExpr; i++){ Expr *p = sqlite3ExprSkipCollateAndLikely(pList->a[i].pExpr); if( ALWAYS(p!=0) - && p->op==TK_COLUMN + && (p->op==TK_COLUMN || p->op==TK_AGG_COLUMN) && p->iColumn==pIdx->aiColumn[iCol] && p->iTable==iBase ){ @@ -537,7 +571,8 @@ static int isDistinctRedundant( for(i=0; inExpr; i++){ Expr *p = sqlite3ExprSkipCollateAndLikely(pDistinct->a[i].pExpr); if( NEVER(p==0) ) continue; - if( p->op==TK_COLUMN && p->iTable==iBase && p->iColumn<0 ) return 1; + if( p->op!=TK_COLUMN && p->op!=TK_AGG_COLUMN ) continue; + if( p->iTable==iBase && p->iColumn<0 ) return 1; } /* Loop through all indices on the table, checking each to see if it makes @@ -555,6 +590,7 @@ static int isDistinctRedundant( */ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ if( !IsUniqueIndex(pIdx) ) continue; + if( pIdx->pPartIdxWhere ) continue; for(i=0; inKeyCol; i++){ if( 0==sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){ if( findIndexCol(pParse, pDistinct, iBase, pIdx, i)<0 ) break; @@ -609,14 +645,14 @@ static void translateColumnToCopy( pOp->p2 = pOp->p3; pOp->p3 = 0; }else if( pOp->opcode==OP_Rowid ){ - if( iAutoidxCur ){ - pOp->opcode = OP_Sequence; - pOp->p1 = iAutoidxCur; - }else{ + pOp->opcode = OP_Sequence; + pOp->p1 = iAutoidxCur; +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( iAutoidxCur==0 ){ pOp->opcode = OP_Null; - pOp->p1 = 0; pOp->p3 = 0; } +#endif } } } @@ -674,7 +710,7 @@ static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ */ static int termCanDriveIndex( WhereTerm *pTerm, /* WHERE clause term to check */ - struct SrcList_item *pSrc, /* Table we are trying to access */ + SrcItem *pSrc, /* Table we are trying to access */ Bitmask notReady /* Tables in outer loops of the join */ ){ char aff; @@ -708,7 +744,7 @@ static int termCanDriveIndex( static void constructAutomaticIndex( Parse *pParse, /* The parsing context */ WhereClause *pWC, /* The WHERE clause */ - struct SrcList_item *pSrc, /* The FROM clause term to get the next index */ + SrcItem *pSrc, /* The FROM clause term to get the next index */ Bitmask notReady, /* Mask of cursors that are not available */ WhereLevel *pLevel /* Write new index here */ ){ @@ -732,7 +768,7 @@ static void constructAutomaticIndex( u8 sentWarning = 0; /* True if a warnning has been issued */ Expr *pPartial = 0; /* Partial Index Expression */ int iContinue = 0; /* Jump here to skip excluded rows */ - struct SrcList_item *pTabItem; /* FROM clause term being indexed */ + SrcItem *pTabItem; /* FROM clause term being indexed */ int addrCounter = 0; /* Address where integer counter is initialized */ int regBase; /* Array of registers where record is assembled */ @@ -781,7 +817,7 @@ static void constructAutomaticIndex( } } } - assert( nKeyCol>0 ); + assert( nKeyCol>0 || pParse->db->mallocFailed ); pLoop->u.btree.nEq = pLoop->nLTerm = nKeyCol; pLoop->wsFlags = WHERE_COLUMN_EQ | WHERE_IDX_ONLY | WHERE_INDEXED | WHERE_AUTO_INDEX; @@ -916,7 +952,7 @@ static sqlite3_index_info *allocateIndexInfo( Parse *pParse, /* The parsing context */ WhereClause *pWC, /* The WHERE clause being analyzed */ Bitmask mUnusable, /* Ignore terms with these prereqs */ - struct SrcList_item *pSrc, /* The FROM clause term that is the vtab */ + SrcItem *pSrc, /* The FROM clause term that is the vtab */ ExprList *pOrderBy, /* The ORDER BY clause */ u16 *pmNoOmit /* Mask of terms not to omit */ ){ @@ -1814,7 +1850,7 @@ void sqlite3WhereClausePrint(WhereClause *pWC){ void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){ WhereInfo *pWInfo = pWC->pWInfo; int nb = 1+(pWInfo->pTabList->nSrc+3)/4; - struct SrcList_item *pItem = pWInfo->pTabList->a + p->iTab; + SrcItem *pItem = pWInfo->pTabList->a + p->iTab; Table *pTab = pItem->pTab; Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1; sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId, @@ -1918,7 +1954,7 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ whereLoopClearUnion(db, pTo); if( whereLoopResize(db, pTo, pFrom->nLTerm) ){ - memset(&pTo->u, 0, sizeof(pTo->u)); + memset(pTo, 0, WHERE_LOOP_XFER_SZ); return SQLITE_NOMEM_BKPT; } memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ); @@ -1961,6 +1997,17 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ sqlite3DbFreeNN(db, pWInfo); } +/* Undo all Expr node modifications +*/ +static void whereUndoExprMods(WhereInfo *pWInfo){ + while( pWInfo->pExprMods ){ + WhereExprMod *p = pWInfo->pExprMods; + pWInfo->pExprMods = p->pNext; + memcpy(p->pExpr, &p->orig, sizeof(p->orig)); + sqlite3DbFree(pWInfo->pParse->db, p); + } +} + /* ** Return TRUE if all of the following are true: ** @@ -2425,7 +2472,7 @@ static int whereRangeVectorLen( */ static int whereLoopAddBtreeIndex( WhereLoopBuilder *pBuilder, /* The WhereLoop factory */ - struct SrcList_item *pSrc, /* FROM clause term being analyzed */ + SrcItem *pSrc, /* FROM clause term being analyzed */ Index *pProbe, /* An index on pSrc */ LogEst nInMul /* log(Number of iterations due to IN) */ ){ @@ -2466,6 +2513,8 @@ static int whereLoopAddBtreeIndex( if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); assert( pNew->u.btree.nEqnColumn ); + assert( pNew->u.btree.nEqnKeyCol + || pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY ); saved_nEq = pNew->u.btree.nEq; saved_nBtm = pNew->u.btree.nBtm; @@ -2548,7 +2597,7 @@ static int whereLoopAddBtreeIndex( nIn = sqlite3LogEst(pExpr->x.pList->nExpr); } if( pProbe->hasStat1 && rLogSize>=10 ){ - LogEst M, logK, safetyMargin; + LogEst M, logK, x; /* Let: ** N = the total number of rows in the table ** K = the number of entries on the RHS of the IN operator @@ -2571,16 +2620,25 @@ static int whereLoopAddBtreeIndex( */ M = pProbe->aiRowLogEst[saved_nEq]; logK = estLog(nIn); - safetyMargin = 10; /* TUNING: extra weight for indexed IN */ - if( M + logK + safetyMargin < nIn + rLogSize ){ + /* TUNING v----- 10 to bias toward indexed IN */ + x = M + logK + 10 - (nIn + rLogSize); + if( x>=0 ){ WHERETRACE(0x40, - ("Scan preferred over IN operator on column %d of \"%s\" (%d<%d)\n", - saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize)); + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d) " + "prefers indexed lookup\n", + saved_nEq, M, logK, nIn, rLogSize, x)); + }else if( nInMul<2 && OptimizationEnabled(db, SQLITE_SeekScan) ){ + WHERETRACE(0x40, + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers skip-scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); pNew->wsFlags |= WHERE_IN_SEEKSCAN; }else{ WHERETRACE(0x40, - ("IN operator preferred on column %d of \"%s\" (%d>=%d)\n", - saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize)); + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers normal scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); + continue; } } pNew->wsFlags |= WHERE_COLUMN_IN; @@ -2599,6 +2657,7 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags |= WHERE_UNQ_WANTED; } } + if( scan.iEquiv>1 ) pNew->wsFlags |= WHERE_TRANSCONS; }else if( eOp & WO_ISNULL ){ pNew->wsFlags |= WHERE_COLUMN_NULL; }else if( eOp & (WO_GT|WO_GE) ){ @@ -2611,7 +2670,7 @@ static int whereLoopAddBtreeIndex( pBtm = pTerm; pTop = 0; if( pTerm->wtFlags & TERM_LIKEOPT ){ - /* Range contraints that come from the LIKE optimization are + /* Range constraints that come from the LIKE optimization are ** always used in pairs. */ pTop = &pTerm[1]; assert( (pTop-(pTerm->pWC->a))pWC->nTerm ); @@ -2660,7 +2719,7 @@ static int whereLoopAddBtreeIndex( tRowcnt nOut = 0; if( nInMul==0 && pProbe->nSample - && pNew->u.btree.nEq<=pProbe->nSampleCol + && ALWAYS(pNew->u.btree.nEq<=pProbe->nSampleCol) && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect)) && OptimizationEnabled(db, SQLITE_Stat4) ){ @@ -2742,6 +2801,8 @@ static int whereLoopAddBtreeIndex( if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 && pNew->u.btree.nEqnColumn + && (pNew->u.btree.nEqnKeyCol || + pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY) ){ whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); } @@ -2862,7 +2923,8 @@ static int whereUsablePartialIndex( pExpr = pTerm->pExpr; if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab) && (isLeft==0 || ExprHasProperty(pExpr, EP_FromJoin)) - && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) + && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) + && (pTerm->wtFlags & TERM_VNULL)==0 ){ return 1; } @@ -2916,7 +2978,7 @@ static int whereLoopAddBtree( LogEst aiRowEstPk[2]; /* The aiRowLogEst[] value for the sPk index */ i16 aiColumnPk = -1; /* The aColumn[] value for the sPk index */ SrcList *pTabList; /* The FROM clause */ - struct SrcList_item *pSrc; /* The FROM clause btree term to add */ + SrcItem *pSrc; /* The FROM clause btree term to add */ WhereLoop *pNew; /* Template WhereLoop object */ int rc = SQLITE_OK; /* Return code */ int iSortIdx = 1; /* Index number */ @@ -2934,9 +2996,9 @@ static int whereLoopAddBtree( pWC = pBuilder->pWC; assert( !IsVirtual(pSrc->pTab) ); - if( pSrc->pIBIndex ){ + if( pSrc->fg.isIndexedBy ){ /* An INDEXED BY clause specifies a particular index to use */ - pProbe = pSrc->pIBIndex; + pProbe = pSrc->u2.pIBIndex; }else if( !HasRowid(pTab) ){ pProbe = pTab->pIndex; }else{ @@ -2972,7 +3034,7 @@ static int whereLoopAddBtree( if( !pBuilder->pOrSet /* Not part of an OR optimization */ && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 - && pSrc->pIBIndex==0 /* Has no INDEXED BY clause */ + && !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */ && !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */ && HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */ && !pSrc->fg.isCorrelated /* Not a correlated subquery */ @@ -3022,7 +3084,7 @@ static int whereLoopAddBtree( /* Loop over all indices. If there was an INDEXED BY clause, then only ** consider index pProbe. */ for(; rc==SQLITE_OK && pProbe; - pProbe=(pSrc->pIBIndex ? 0 : pProbe->pNext), iSortIdx++ + pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++ ){ int isLeft = (pSrc->fg.jointype & JT_OUTER)!=0; if( pProbe->pPartIdxWhere!=0 @@ -3197,7 +3259,7 @@ static int whereLoopAddVirtualOne( int rc = SQLITE_OK; WhereLoop *pNew = pBuilder->pNew; Parse *pParse = pBuilder->pWInfo->pParse; - struct SrcList_item *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab]; + SrcItem *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab]; int nConstraint = pIdxInfo->nConstraint; assert( (mUsable & mPrereq)==mPrereq ); @@ -3389,7 +3451,7 @@ static int whereLoopAddVirtual( WhereInfo *pWInfo; /* WHERE analysis context */ Parse *pParse; /* The parsing context */ WhereClause *pWC; /* The WHERE clause */ - struct SrcList_item *pSrc; /* The FROM clause term to search */ + SrcItem *pSrc; /* The FROM clause term to search */ sqlite3_index_info *p; /* Object to pass to xBestIndex() */ int nConstraint; /* Number of constraints in p */ int bIn; /* True if plan uses IN(...) operator */ @@ -3517,7 +3579,7 @@ static int whereLoopAddOr( WhereClause tempWC; WhereLoopBuilder sSubBuild; WhereOrSet sSum, sCur; - struct SrcList_item *pItem; + SrcItem *pItem; pWC = pBuilder->pWC; pWCEnd = pWC->a + pWC->nTerm; @@ -3573,7 +3635,9 @@ static int whereLoopAddOr( if( rc==SQLITE_OK ){ rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable); } - assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0 ); + assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0 + || rc==SQLITE_NOMEM ); + testcase( rc==SQLITE_NOMEM && sCur.n>0 ); testcase( rc==SQLITE_DONE ); if( sCur.n==0 ){ sSum.n = 0; @@ -3633,8 +3697,8 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ Bitmask mPrior = 0; int iTab; SrcList *pTabList = pWInfo->pTabList; - struct SrcList_item *pItem; - struct SrcList_item *pEnd = &pTabList->a[pWInfo->nLevel]; + SrcItem *pItem; + SrcItem *pEnd = &pTabList->a[pWInfo->nLevel]; sqlite3 *db = pWInfo->pParse->db; int rc = SQLITE_OK; WhereLoop *pNew; @@ -3657,7 +3721,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ } #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pItem->pTab) ){ - struct SrcList_item *p; + SrcItem *p; for(p=&pItem[1]; pfg.jointype & (JT_LEFT|JT_CROSS)) ){ mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor); @@ -3802,7 +3866,7 @@ static i8 wherePathSatisfiesOrderBy( if( MASKBIT(i) & obSat ) continue; pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr); if( NEVER(pOBExpr==0) ) continue; - if( pOBExpr->op!=TK_COLUMN ) continue; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; pTerm = sqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn, ~ready, eqOpMask, 0); @@ -3842,6 +3906,10 @@ static i8 wherePathSatisfiesOrderBy( assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) ); assert( pIndex->aiColumn[nColumn-1]==XN_ROWID || !HasRowid(pIndex->pTable)); + /* All relevant terms of the index must also be non-NULL in order + ** for isOrderDistinct to be true. So the isOrderDistint value + ** computed here might be a false positive. Corrections will be + ** made at tag-20210426-1 below */ isOrderDistinct = IsUniqueIndex(pIndex) && (pLoop->wsFlags & WHERE_SKIPSCAN)==0; } @@ -3909,15 +3977,19 @@ static i8 wherePathSatisfiesOrderBy( } /* An unconstrained column that might be NULL means that this - ** WhereLoop is not well-ordered + ** WhereLoop is not well-ordered. tag-20210426-1 */ - if( isOrderDistinct - && iColumn>=0 - && j>=pLoop->u.btree.nEq - && pIndex->pTable->aCol[iColumn].notNull==0 - ){ - isOrderDistinct = 0; - } + if( isOrderDistinct ){ + if( iColumn>=0 + && j>=pLoop->u.btree.nEq + && pIndex->pTable->aCol[iColumn].notNull==0 + ){ + isOrderDistinct = 0; + } + if( iColumn==XN_EXPR ){ + isOrderDistinct = 0; + } + } /* Find the ORDER BY term that corresponds to the j-th column ** of the index and mark that ORDER BY term off @@ -3931,7 +4003,7 @@ static i8 wherePathSatisfiesOrderBy( if( NEVER(pOBExpr==0) ) continue; if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0; if( iColumn>=XN_ROWID ){ - if( pOBExpr->op!=TK_COLUMN ) continue; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; if( pOBExpr->iColumn!=iColumn ) continue; }else{ @@ -4100,7 +4172,7 @@ static LogEst whereSortingCost( }else if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT) ){ /* TUNING: In the sort for a DISTINCT operator, assume that the DISTINCT ** reduces the number of output rows by a factor of 2 */ - if( nRow>10 ) nRow -= 10; assert( 10==sqlite3LogEst(2) ); + if( nRow>10 ){ nRow -= 10; assert( 10==sqlite3LogEst(2) ); } } rSortCost += estLog(nRow); return rSortCost; @@ -4512,7 +4584,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ */ static int whereShortCut(WhereLoopBuilder *pBuilder){ WhereInfo *pWInfo; - struct SrcList_item *pItem; + SrcItem *pItem; WhereClause *pWC; WhereTerm *pTerm; WhereLoop *pLoop; @@ -4971,7 +5043,7 @@ WhereInfo *sqlite3WhereBegin( if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ pWInfo->revMask = ALLBITS; } - if( pParse->nErr || NEVER(db->mallocFailed) ){ + if( pParse->nErr || db->mallocFailed ){ goto whereBeginError; } #ifdef WHERETRACE_ENABLED @@ -5032,7 +5104,8 @@ WhereInfo *sqlite3WhereBegin( */ notReady = ~(Bitmask)0; if( pWInfo->nLevel>=2 - && pResultSet!=0 /* guarantees condition (1) above */ + && pResultSet!=0 /* these two combine to guarantee */ + && 0==(wctrlFlags & WHERE_AGG_DISTINCT) /* condition (1) above */ && OptimizationEnabled(db, SQLITE_OmitNoopJoin) ){ int i; @@ -5042,7 +5115,7 @@ WhereInfo *sqlite3WhereBegin( } for(i=pWInfo->nLevel-1; i>=1; i--){ WhereTerm *pTerm, *pEnd; - struct SrcList_item *pItem; + SrcItem *pItem; pLoop = pWInfo->a[i].pWLoop; pItem = &pWInfo->pTabList->a[pLoop->iTab]; if( (pItem->fg.jointype & JT_LEFT)==0 ) continue; @@ -5132,7 +5205,7 @@ WhereInfo *sqlite3WhereBegin( for(ii=0, pLevel=pWInfo->a; iia[pLevel->iFrom]; pTab = pTabItem->pTab; @@ -5291,6 +5364,8 @@ WhereInfo *sqlite3WhereBegin( /* Jump here if malloc fails */ whereBeginError: if( pWInfo ){ + testcase( pWInfo->pExprMods!=0 ); + whereUndoExprMods(pWInfo); pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); } @@ -5387,6 +5462,8 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ int j; sqlite3VdbeResolveLabel(v, pLevel->addrNxt); for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ + assert( sqlite3VdbeGetOp(v, pIn->addrInTop+1)->opcode==OP_IsNull + || pParse->db->mallocFailed ); sqlite3VdbeJumpHere(v, pIn->addrInTop+1); if( pIn->eEndLoopOp!=OP_Noop ){ if( pIn->nPrefix ){ @@ -5411,6 +5488,11 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeCurrentAddr(v)+2, pIn->iBase, pIn->nPrefix); VdbeCoverage(v); + /* Retarget the OP_IsNull against the left operand of IN so + ** it jumps past the OP_IfNoHope. This is because the + ** OP_IsNull also bypasses the OP_Affinity opcode that is + ** required by OP_IfNoHope. */ + sqlite3VdbeJumpHere(v, pIn->addrInTop+1); } } sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); @@ -5469,7 +5551,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ int k, last; VdbeOp *pOp, *pLastOp; Index *pIdx = 0; - struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom]; + SrcItem *pTabItem = &pTabList->a[pLevel->iFrom]; Table *pTab = pTabItem->pTab; assert( pTab!=0 ); pLoop = pLevel->pWLoop; @@ -5545,7 +5627,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ #endif pOp = sqlite3VdbeGetOp(v, k); pLastOp = pOp + (last - k); - assert( pOpp1!=pLevel->iTabCur ){ /* no-op */ @@ -5590,16 +5672,9 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } } - /* Undo all Expr node modifications */ - while( pWInfo->pExprMods ){ - WhereExprMod *p = pWInfo->pExprMods; - pWInfo->pExprMods = p->pNext; - memcpy(p->pExpr, &p->orig, sizeof(p->orig)); - sqlite3DbFree(db, p); - } - /* Final cleanup */ + if( pWInfo->pExprMods ) whereUndoExprMods(pWInfo); pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); return; diff --git a/src/whereInt.h b/src/whereInt.h index 6c969af9c..c5dd83cc8 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -17,19 +17,6 @@ #ifndef SQLITE_WHEREINT_H #define SQLITE_WHEREINT_H -/* -** Trace output macros -*/ -#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) -/***/ extern int sqlite3WhereTrace; -#endif -#if defined(SQLITE_DEBUG) \ - && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE)) -# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X -# define WHERETRACE_ENABLED 1 -#else -# define WHERETRACE(K,X) -#endif /* Forward references */ @@ -283,11 +270,7 @@ struct WhereTerm { #define TERM_ORINFO 0x0010 /* Need to free the WhereTerm.u.pOrInfo object */ #define TERM_ANDINFO 0x0020 /* Need to free the WhereTerm.u.pAndInfo obj */ #define TERM_OR_OK 0x0040 /* Used during OR-clause processing */ -#ifdef SQLITE_ENABLE_STAT4 -# define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */ -#else -# define TERM_VNULL 0x0000 /* Disabled if not using stat4 */ -#endif +#define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */ #define TERM_LIKEOPT 0x0100 /* Virtual terms from the LIKE optimization */ #define TERM_LIKECOND 0x0200 /* Conditionally this LIKE operator term */ #define TERM_LIKE 0x0400 /* The original LIKE operator */ @@ -310,8 +293,8 @@ struct WhereScan { const char *zCollName; /* Required collating sequence, if not NULL */ Expr *pIdxExpr; /* Search for this index expression */ char idxaff; /* Must match this affinity, if zCollName!=NULL */ - unsigned char nEquiv; /* Number of entries in aEquiv[] */ - unsigned char iEquiv; /* Next unused slot in aEquiv[] */ + unsigned char nEquiv; /* Number of entries in aiCur[] and aiColumn[] */ + unsigned char iEquiv; /* Next unused slot in aiCur[] and aiColumn[] */ u32 opMask; /* Acceptable operators */ int k; /* Resume scanning at this->pWC->a[this->k] */ int aiCur[11]; /* Cursors in the equivalence class */ @@ -557,7 +540,7 @@ Bitmask sqlite3WhereExprUsage(WhereMaskSet*, Expr*); Bitmask sqlite3WhereExprUsageNN(WhereMaskSet*, Expr*); Bitmask sqlite3WhereExprListUsage(WhereMaskSet*, ExprList*); void sqlite3WhereExprAnalyze(SrcList*, WhereClause*); -void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereClause*); +void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); @@ -620,5 +603,6 @@ void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereClause*); #define WHERE_IN_EARLYOUT 0x00040000 /* Perhaps quit IN loops early */ #define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */ #define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */ +#define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */ #endif /* !defined(SQLITE_WHEREINT_H) */ diff --git a/src/wherecode.c b/src/wherecode.c index 4afe0ac9c..84fa1a141 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -129,7 +129,7 @@ int sqlite3WhereExplainOneScan( if( sqlite3ParseToplevel(pParse)->explain==2 ) #endif { - struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom]; + SrcItem *pItem = &pTabList->a[pLevel->iFrom]; Vdbe *v = pParse->pVdbe; /* VM being constructed */ sqlite3 *db = pParse->db; /* Database handle */ int isSearch; /* True for a SEARCH. False for SCAN. */ @@ -148,16 +148,8 @@ int sqlite3WhereExplainOneScan( || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); - sqlite3_str_appendall(&str, isSearch ? "SEARCH" : "SCAN"); - if( pItem->pSelect ){ - sqlite3_str_appendf(&str, " SUBQUERY %u", pItem->pSelect->selId); - }else{ - sqlite3_str_appendf(&str, " TABLE %s", pItem->zName); - } - - if( pItem->zAlias ){ - sqlite3_str_appendf(&str, " AS %s", pItem->zAlias); - } + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "%s %S", isSearch ? "SEARCH" : "SCAN", pItem); if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ const char *zFmt = 0; Index *pIdx; @@ -305,6 +297,12 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ }else{ pTerm->wtFlags |= TERM_CODED; } +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x20000 ){ + sqlite3DebugPrintf("DISABLE-"); + sqlite3WhereTermPrint(pTerm, (int)(pTerm - (pTerm->pWC->a))); + } +#endif if( pTerm->iParent<0 ) break; pTerm = &pTerm->pWC->a[pTerm->iParent]; assert( pTerm!=0 ); @@ -622,7 +620,22 @@ static int codeEqualityTerm( sqlite3DbFree(pParse->db, aiMap); #endif } - disableTerm(pLevel, pTerm); + + /* As an optimization, try to disable the WHERE clause term that is + ** driving the index as it will always be true. The correct answer is + ** obtained regardless, but we might get the answer with fewer CPU cycles + ** by omitting the term. + ** + ** But do not disable the term unless we are certain that the term is + ** not a transitive constraint. For an example of where that does not + ** work, see https://sqlite.org/forum/forumpost/eb8613976a (2021-05-04) + */ + if( (pLevel->pWLoop->wsFlags & WHERE_TRANSCONS)==0 + || (pTerm->eOperator & WO_EQUIV)==0 + ){ + disableTerm(pLevel, pTerm); + } + return iReg; } @@ -708,6 +721,7 @@ static int codeAllEqualityTerms( if( nSkip ){ int iIdxCur = pLevel->iIdxCur; + sqlite3VdbeAddOp3(v, OP_Null, 0, regBase, regBase+nSkip-1); sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); @@ -742,7 +756,7 @@ static int codeAllEqualityTerms( sqlite3ReleaseTempReg(pParse, regBase); regBase = r1; }else{ - sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j); + sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j); } } if( pTerm->eOperator & WO_IN ){ @@ -759,7 +773,7 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk); VdbeCoverage(v); } - if( zAff ){ + if( pParse->db->mallocFailed==0 && pParse->nErr==0 ){ if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){ zAff[j] = SQLITE_AFF_BLOB; } @@ -922,7 +936,7 @@ static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){ ** Insert an OP_CursorHint instruction if it is appropriate to do so. */ static void codeCursorHint( - struct SrcList_item *pTabItem, /* FROM clause item */ + SrcItem *pTabItem, /* FROM clause item */ WhereInfo *pWInfo, /* The where clause */ WhereLevel *pLevel, /* Which loop to provide hints for */ WhereTerm *pEndRange /* Hint this end-of-scan boundary term if not NULL */ @@ -1108,7 +1122,7 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ } } }else{ - assert( nReg==1 ); + assert( nReg==1 || pParse->nErr ); sqlite3ExprCode(pParse, p, iReg); } } @@ -1297,7 +1311,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( WhereClause *pWC; /* Decomposition of the entire WHERE clause */ WhereTerm *pTerm; /* A WHERE clause term */ sqlite3 *db; /* Database connection */ - struct SrcList_item *pTabItem; /* FROM clause term being coded */ + SrcItem *pTabItem; /* FROM clause term being coded */ int addrBrk; /* Jump here to break out of the loop */ int addrHalt; /* addrBrk for the outermost loop */ int addrCont; /* Jump here to continue with next cycle */ @@ -1735,14 +1749,18 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** a forward order scan on a descending index, interchange the ** start and end terms (pRangeStart and pRangeEnd). */ - if( (nEqnKeyCol && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) - || (bRev && pIdx->nKeyCol==nEq) - ){ + if( (nEqnColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); SWAP(u8, bSeekPastNull, bStopAtNull); SWAP(u8, nBtm, nTop); } + if( iLevel>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)!=0 ){ + /* In case OP_SeekScan is used, ensure that the index cursor does not + ** point to a valid row for the first iteration of this loop. */ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } + /* Generate code to evaluate all constraint terms using == or IN ** and store the values of those terms in an array of registers ** starting at regBase. @@ -2079,7 +2097,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( */ if( pWInfo->nLevel>1 ){ int nNotReady; /* The number of notReady tables */ - struct SrcList_item *origSrc; /* Original list of tables */ + SrcItem *origSrc; /* Original list of tables */ nNotReady = pWInfo->nLevel - iLevel - 1; pOrTab = sqlite3StackAllocRaw(db, sizeof(*pOrTab)+ nNotReady*sizeof(pOrTab->a[0])); @@ -2152,7 +2170,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( /* The extra 0x10000 bit on the opcode is masked off and does not ** become part of the new Expr.op. However, it does make the ** op==TK_AND comparison inside of sqlite3PExpr() false, and this - ** prevents sqlite3PExpr() from implementing AND short-circuit + ** prevents sqlite3PExpr() from applying the AND short-circuit ** optimization, which we do not want here. */ pAndExpr = sqlite3PExpr(pParse, TK_AND|0x10000, 0, pAndExpr); } @@ -2168,10 +2186,16 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){ WhereInfo *pSubWInfo; /* Info for single OR-term scan */ Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */ + Expr *pDelete; /* Local copy of OR clause term */ int jmp1 = 0; /* Address of jump operation */ testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0 && !ExprHasProperty(pOrExpr, EP_FromJoin) ); /* See TH3 vtab25.400 and ticket 614b25314c766238 */ + pDelete = pOrExpr = sqlite3ExprDup(db, pOrExpr, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDelete); + continue; + } if( pAndExpr ){ pAndExpr->pLeft = pOrExpr; pOrExpr = pAndExpr; @@ -2286,6 +2310,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3WhereEnd(pSubWInfo); ExplainQueryPlanPop(pParse); } + sqlite3ExprDelete(db, pDelete); } } ExplainQueryPlanPop(pParse); @@ -2450,6 +2475,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( sEAlt = *pAlt->pExpr; sEAlt.pLeft = pE->pLeft; sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL); + pAlt->wtFlags |= TERM_CODED; } /* For a LEFT OUTER JOIN, generate code that will record the fact that diff --git a/src/whereexpr.c b/src/whereexpr.c index a77eb3610..31f2ea438 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -511,6 +511,7 @@ static void whereCombineDisjuncts( int op; /* Operator for the combined expression */ int idxNew; /* Index in pWC of the next virtual term */ + if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return; if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp @@ -871,7 +872,7 @@ static void exprAnalyzeOrTerm( idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); exprAnalyze(pSrc, pWC, idxNew); - /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where used again */ + /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where reused */ markTermAsChild(pWC, idxNew, idxTerm); }else{ sqlite3ExprListDelete(db, pList); @@ -995,6 +996,7 @@ static int exprMightBeIndexed( assert( op<=TK_GE ); if( pExpr->op==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ pExpr = pExpr->x.pList->a[0].pExpr; + } if( pExpr->op==TK_COLUMN ){ @@ -1007,6 +1009,7 @@ static int exprMightBeIndexed( return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr); } + /* ** The input to this routine is an WhereTerm structure with only the ** "pExpr" field filled in. The job of this routine is to analyze the @@ -1105,6 +1108,7 @@ static void exprAnalyze( if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; if( pRight && exprMightBeIndexed(pSrc, pTerm->prereqRight, aiCurCol, pRight, op) + && !ExprHasProperty(pRight, EP_FixedCol) ){ WhereTerm *pNew; Expr *pDup; @@ -1140,6 +1144,12 @@ static void exprAnalyze( pNew->prereqRight = prereqLeft | extraRight; pNew->prereqAll = prereqAll; pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask; + }else if( op==TK_ISNULL && 0==sqlite3ExprCanBeNull(pLeft) ){ + pExpr->op = TK_TRUEFALSE; + pExpr->u.zToken = "false"; + ExprSetProperty(pExpr, EP_IsFalse); + pTerm->prereqAll = 0; + pTerm->eOperator = 0; } } @@ -1191,6 +1201,42 @@ static void exprAnalyze( pTerm = &pWC->a[idxTerm]; } #endif /* SQLITE_OMIT_OR_OPTIMIZATION */ + /* The form "x IS NOT NULL" can sometimes be evaluated more efficiently + ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a + ** virtual term of that form. + ** + ** The virtual term must be tagged with TERM_VNULL. + */ + else if( pExpr->op==TK_NOTNULL ){ + if( pExpr->pLeft->op==TK_COLUMN + && pExpr->pLeft->iColumn>=0 + && !ExprHasProperty(pExpr, EP_FromJoin) + ){ + Expr *pNewExpr; + Expr *pLeft = pExpr->pLeft; + int idxNew; + WhereTerm *pNewTerm; + + pNewExpr = sqlite3PExpr(pParse, TK_GT, + sqlite3ExprDup(db, pLeft, 0), + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); + + idxNew = whereClauseInsert(pWC, pNewExpr, + TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); + if( idxNew ){ + pNewTerm = &pWC->a[idxNew]; + pNewTerm->prereqRight = 0; + pNewTerm->leftCursor = pLeft->iTable; + pNewTerm->u.x.leftColumn = pLeft->iColumn; + pNewTerm->eOperator = WO_GT; + markTermAsChild(pWC, idxNew, idxTerm); + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_COPIED; + pNewTerm->prereqAll = pTerm->prereqAll; + } + } + } + #ifndef SQLITE_OMIT_LIKE_OPTIMIZATION /* Add constraints to reduce the search space on a LIKE or GLOB @@ -1206,7 +1252,8 @@ static void exprAnalyze( ** bound is made all lowercase so that the bounds also work when comparing ** BLOBs. */ - if( pWC->op==TK_AND + else if( pExpr->op==TK_FUNCTION + && pWC->op==TK_AND && isLikeOrGlob(pParse, pExpr, &pStr1, &isComplete, &noCase) ){ Expr *pLeft; /* LHS of LIKE/GLOB operator */ @@ -1276,52 +1323,6 @@ static void exprAnalyze( } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ -#ifndef SQLITE_OMIT_VIRTUALTABLE - /* Add a WO_AUX auxiliary term to the constraint set if the - ** current expression is of the form "column OP expr" where OP - ** is an operator that gets passed into virtual tables but which is - ** not normally optimized for ordinary tables. In other words, OP - ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL. - ** This information is used by the xBestIndex methods of - ** virtual tables. The native query optimizer does not attempt - ** to do anything with MATCH functions. - */ - if( pWC->op==TK_AND ){ - Expr *pRight = 0, *pLeft = 0; - int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight); - while( res-- > 0 ){ - int idxNew; - WhereTerm *pNewTerm; - Bitmask prereqColumn, prereqExpr; - - prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight); - prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft); - if( (prereqExpr & prereqColumn)==0 ){ - Expr *pNewExpr; - pNewExpr = sqlite3PExpr(pParse, TK_MATCH, - 0, sqlite3ExprDup(db, pRight, 0)); - if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ - ExprSetProperty(pNewExpr, EP_FromJoin); - pNewExpr->iRightJoinTable = pExpr->iRightJoinTable; - } - idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); - testcase( idxNew==0 ); - pNewTerm = &pWC->a[idxNew]; - pNewTerm->prereqRight = prereqExpr; - pNewTerm->leftCursor = pLeft->iTable; - pNewTerm->u.x.leftColumn = pLeft->iColumn; - pNewTerm->eOperator = WO_AUX; - pNewTerm->eMatchOp = eOp2; - markTermAsChild(pWC, idxNew, idxTerm); - pTerm = &pWC->a[idxTerm]; - pTerm->wtFlags |= TERM_COPIED; - pNewTerm->prereqAll = pTerm->prereqAll; - } - SWAP(Expr*, pLeft, pRight); - } - } -#endif /* SQLITE_OMIT_VIRTUALTABLE */ - /* If there is a vector == or IS term - e.g. "(a, b) == (?, ?)" - create ** new terms for each component comparison - "a = ?" and "b = ?". The ** new terms completely replace the original vector comparison, which is @@ -1329,12 +1330,12 @@ static void exprAnalyze( ** ** This is only required if at least one side of the comparison operation ** is not a sub-select. */ - if( pWC->op==TK_AND - && (pExpr->op==TK_EQ || pExpr->op==TK_IS) - && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 - && sqlite3ExprVectorSize(pExpr->pRight)==nLeft - && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 - || (pExpr->pRight->flags & EP_xIsSelect)==0) + if( (pExpr->op==TK_EQ || pExpr->op==TK_IS) + && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 + && sqlite3ExprVectorSize(pExpr->pRight)==nLeft + && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 + || (pExpr->pRight->flags & EP_xIsSelect)==0) + && pWC->op==TK_AND ){ int i; for(i=0; iop==TK_AND && pExpr->op==TK_IN && pTerm->u.x.iField==0 + else if( pExpr->op==TK_IN + && pTerm->u.x.iField==0 && pExpr->pLeft->op==TK_VECTOR && pExpr->x.pSelect->pPrior==0 #ifndef SQLITE_OMIT_WINDOWFUNC && pExpr->x.pSelect->pWin==0 #endif + && pWC->op==TK_AND ){ int i; for(i=0; ipLeft); i++){ @@ -1379,44 +1382,51 @@ static void exprAnalyze( } } -#ifdef SQLITE_ENABLE_STAT4 - /* When sqlite_stat4 histogram data is available an operator of the - ** form "x IS NOT NULL" can sometimes be evaluated more efficiently - ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a - ** virtual term of that form. - ** - ** Note that the virtual term must be tagged with TERM_VNULL. +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Add a WO_AUX auxiliary term to the constraint set if the + ** current expression is of the form "column OP expr" where OP + ** is an operator that gets passed into virtual tables but which is + ** not normally optimized for ordinary tables. In other words, OP + ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL. + ** This information is used by the xBestIndex methods of + ** virtual tables. The native query optimizer does not attempt + ** to do anything with MATCH functions. */ - if( pExpr->op==TK_NOTNULL - && pExpr->pLeft->op==TK_COLUMN - && pExpr->pLeft->iColumn>=0 - && !ExprHasProperty(pExpr, EP_FromJoin) - && OptimizationEnabled(db, SQLITE_Stat4) - ){ - Expr *pNewExpr; - Expr *pLeft = pExpr->pLeft; - int idxNew; - WhereTerm *pNewTerm; - - pNewExpr = sqlite3PExpr(pParse, TK_GT, - sqlite3ExprDup(db, pLeft, 0), - sqlite3ExprAlloc(db, TK_NULL, 0, 0)); - - idxNew = whereClauseInsert(pWC, pNewExpr, - TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); - if( idxNew ){ - pNewTerm = &pWC->a[idxNew]; - pNewTerm->prereqRight = 0; - pNewTerm->leftCursor = pLeft->iTable; - pNewTerm->u.x.leftColumn = pLeft->iColumn; - pNewTerm->eOperator = WO_GT; - markTermAsChild(pWC, idxNew, idxTerm); - pTerm = &pWC->a[idxTerm]; - pTerm->wtFlags |= TERM_COPIED; - pNewTerm->prereqAll = pTerm->prereqAll; + else if( pWC->op==TK_AND ){ + Expr *pRight = 0, *pLeft = 0; + int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight); + while( res-- > 0 ){ + int idxNew; + WhereTerm *pNewTerm; + Bitmask prereqColumn, prereqExpr; + + prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight); + prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft); + if( (prereqExpr & prereqColumn)==0 ){ + Expr *pNewExpr; + pNewExpr = sqlite3PExpr(pParse, TK_MATCH, + 0, sqlite3ExprDup(db, pRight, 0)); + if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ + ExprSetProperty(pNewExpr, EP_FromJoin); + pNewExpr->iRightJoinTable = pExpr->iRightJoinTable; + } + idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); + testcase( idxNew==0 ); + pNewTerm = &pWC->a[idxNew]; + pNewTerm->prereqRight = prereqExpr; + pNewTerm->leftCursor = pLeft->iTable; + pNewTerm->u.x.leftColumn = pLeft->iColumn; + pNewTerm->eOperator = WO_AUX; + pNewTerm->eMatchOp = eOp2; + markTermAsChild(pWC, idxNew, idxTerm); + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_COPIED; + pNewTerm->prereqAll = pTerm->prereqAll; + } + SWAP(Expr*, pLeft, pRight); } } -#endif /* SQLITE_ENABLE_STAT4 */ +#endif /* SQLITE_OMIT_VIRTUALTABLE */ /* Prevent ON clause terms of a LEFT JOIN from being used to drive ** an index for tables to the left of the join. @@ -1576,7 +1586,7 @@ void sqlite3WhereExprAnalyze( */ void sqlite3WhereTabFuncArgs( Parse *pParse, /* Parsing context */ - struct SrcList_item *pItem, /* The FROM clause term to process */ + SrcItem *pItem, /* The FROM clause term to process */ WhereClause *pWC /* Xfer function arguments to here */ ){ Table *pTab; diff --git a/src/window.c b/src/window.c index 88ff7d314..2afa7c12c 100644 --- a/src/window.c +++ b/src/window.c @@ -788,6 +788,7 @@ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ case TK_AGG_FUNCTION: case TK_COLUMN: { int iCol = -1; + if( pParse->db->mallocFailed ) return WRC_Abort; if( p->pSub ){ int i; for(i=0; ipSub->nExpr; i++){ @@ -897,9 +898,14 @@ static ExprList *exprListAppendList( int i; int nInit = pList ? pList->nExpr : 0; for(i=0; inExpr; i++){ - Expr *pDup = sqlite3ExprDup(pParse->db, pAppend->a[i].pExpr, 0); + sqlite3 *db = pParse->db; + Expr *pDup = sqlite3ExprDup(db, pAppend->a[i].pExpr, 0); assert( pDup==0 || !ExprHasProperty(pDup, EP_MemToken) ); - if( bIntToNull && pDup ){ + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + break; + } + if( bIntToNull ){ int iDummy; Expr *pSub; for(pSub=pDup; ExprHasProperty(pSub, EP_Skip); pSub=pSub->pLeft){ @@ -935,6 +941,14 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } +static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_AGG_FUNCTION && pExpr->pAggInfo==0 ){ + sqlite3ErrorMsg(pWalker->pParse, + "misuse of aggregate: %s()", pExpr->u.zToken); + } + return WRC_Continue; +} + /* ** If the SELECT statement passed as the second argument does not invoke ** any SQL window functions, this function is a no-op. Otherwise, it @@ -944,7 +958,7 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ */ int sqlite3WindowRewrite(Parse *pParse, Select *p){ int rc = SQLITE_OK; - if( p->pWin && p->pPrior==0 && (p->selFlags & SF_WinRewrite)==0 ){ + if( p->pWin && p->pPrior==0 && ALWAYS((p->selFlags & SF_WinRewrite)==0) ){ Vdbe *v = sqlite3GetVdbe(pParse); sqlite3 *db = pParse->db; Select *pSub = 0; /* The subquery */ @@ -968,6 +982,11 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ } sqlite3AggInfoPersistWalkerInit(&w, pParse); sqlite3WalkSelect(&w, p); + if( (p->selFlags & SF_Aggregate)==0 ){ + w.xExprCallback = disallowAggregatesInOrderByCb; + w.xSelectCallback = 0; + sqlite3WalkExprList(&w, p->pOrderBy); + } p->pSrc = 0; p->pWhere = 0; @@ -1304,15 +1323,19 @@ void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){ ** SELECT, or (b) the windows already linked use a compatible window frame. */ void sqlite3WindowLink(Select *pSel, Window *pWin){ - if( pSel!=0 - && (0==pSel->pWin || 0==sqlite3WindowCompare(0, pSel->pWin, pWin, 0)) - ){ - pWin->pNextWin = pSel->pWin; - if( pSel->pWin ){ - pSel->pWin->ppThis = &pWin->pNextWin; + if( pSel ){ + if( 0==pSel->pWin || 0==sqlite3WindowCompare(0, pSel->pWin, pWin, 0) ){ + pWin->pNextWin = pSel->pWin; + if( pSel->pWin ){ + pSel->pWin->ppThis = &pWin->pNextWin; + } + pSel->pWin = pWin; + pWin->ppThis = &pSel->pWin; + }else{ + if( sqlite3ExprListCompare(pWin->pPartition, pSel->pWin->pPartition,-1) ){ + pSel->selFlags |= SF_MultiPart; + } } - pSel->pWin = pWin; - pWin->ppThis = &pSel->pWin; } } @@ -1465,6 +1488,7 @@ static void windowCheckValue(Parse *pParse, int reg, int eCond){ VdbeCoverageIf(v, eCond==2); } sqlite3VdbeAddOp3(v, aOp[eCond], regZero, sqlite3VdbeCurrentAddr(v)+2, reg); + sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC); VdbeCoverageNeverNullIf(v, eCond==0); /* NULL case captured by */ VdbeCoverageNeverNullIf(v, eCond==1); /* the OP_MustBeInt */ VdbeCoverageNeverNullIf(v, eCond==2); @@ -1559,6 +1583,7 @@ struct WindowCodeArg { int regGosub; /* Register used with OP_Gosub(addrGosub) */ int regArg; /* First in array of accumulator registers */ int eDelete; /* See above */ + int regRowid; WindowCsrAndReg start; WindowCsrAndReg current; @@ -1675,15 +1700,15 @@ static void windowAggStep( } if( pWin->bExprArgs ){ - int iStart = sqlite3VdbeCurrentAddr(v); - VdbeOp *pOp, *pEnd; + int iOp = sqlite3VdbeCurrentAddr(v); + int iEnd; nArg = pWin->pOwner->x.pList->nExpr; regArg = sqlite3GetTempRange(pParse, nArg); sqlite3ExprCodeExprList(pParse, pWin->pOwner->x.pList, regArg, 0, 0); - pEnd = sqlite3VdbeGetOp(v, -1); - for(pOp=sqlite3VdbeGetOp(v, iStart); pOp<=pEnd; pOp++){ + for(iEnd=sqlite3VdbeCurrentAddr(v); iOpopcode==OP_Column && pOp->p1==pWin->iEphCsr ){ pOp->p1 = csr; } @@ -2042,7 +2067,7 @@ static void windowIfNewPeer( ** if( csr1.peerVal - regVal <= csr2.peerVal ) goto lbl; ** ** A special type of arithmetic is used such that if csr1.peerVal is not -** a numeric type (real or integer), then the result of the addition addition +** a numeric type (real or integer), then the result of the addition ** or subtraction is a a copy of csr1.peerVal. */ static void windowCodeRangeTest( @@ -2061,6 +2086,12 @@ static void windowCodeRangeTest( int regString = ++pParse->nMem; /* Reg. for constant value '' */ int arith = OP_Add; /* OP_Add or OP_Subtract */ int addrGe; /* Jump destination */ + int addrDone = sqlite3VdbeMakeLabel(pParse); /* Address past OP_Ge */ + CollSeq *pColl; + + /* Read the peer-value from each cursor into a register */ + windowReadPeerValues(p, csr1, reg1); + windowReadPeerValues(p, csr2, reg2); assert( op==OP_Ge || op==OP_Gt || op==OP_Le ); assert( pOrderBy && pOrderBy->nExpr==1 ); @@ -2073,34 +2104,11 @@ static void windowCodeRangeTest( arith = OP_Subtract; } - /* Read the peer-value from each cursor into a register */ - windowReadPeerValues(p, csr1, reg1); - windowReadPeerValues(p, csr2, reg2); - VdbeModuleComment((v, "CodeRangeTest: if( R%d %s R%d %s R%d ) goto lbl", reg1, (arith==OP_Add ? "+" : "-"), regVal, ((op==OP_Ge) ? ">=" : (op==OP_Le) ? "<=" : (op==OP_Gt) ? ">" : "<"), reg2 )); - /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1). - ** This block adds (or subtracts for DESC) the numeric value in regVal - ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob), - ** then leave reg1 as it is. In pseudo-code, this is implemented as: - ** - ** if( reg1>='' ) goto addrGe; - ** reg1 = reg1 +/- regVal - ** addrGe: - ** - ** Since all strings and blobs are greater-than-or-equal-to an empty string, - ** the add/subtract is skipped for these, as required. If reg1 is a NULL, - ** then the arithmetic is performed, but since adding or subtracting from - ** NULL is always NULL anyway, this case is handled as required too. */ - sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC); - addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1); - VdbeCoverage(v); - sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1); - sqlite3VdbeJumpHere(v, addrGe); - /* If the BIGNULL flag is set for the ORDER BY, then it is required to ** consider NULL values to be larger than all other values, instead of ** the usual smaller. The VDBE opcodes OP_Ge and so on do not handle this @@ -2137,21 +2145,46 @@ static void windowCodeRangeTest( break; default: assert( op==OP_Lt ); /* no-op */ break; } - sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrDone); /* This block runs if reg1 is not NULL, but reg2 is. */ sqlite3VdbeJumpHere(v, addr); sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); VdbeCoverage(v); if( op==OP_Gt || op==OP_Ge ){ - sqlite3VdbeChangeP2(v, -1, sqlite3VdbeCurrentAddr(v)+1); + sqlite3VdbeChangeP2(v, -1, addrDone); } } + /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1). + ** This block adds (or subtracts for DESC) the numeric value in regVal + ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob), + ** then leave reg1 as it is. In pseudo-code, this is implemented as: + ** + ** if( reg1>='' ) goto addrGe; + ** reg1 = reg1 +/- regVal + ** addrGe: + ** + ** Since all strings and blobs are greater-than-or-equal-to an empty string, + ** the add/subtract is skipped for these, as required. If reg1 is a NULL, + ** then the arithmetic is performed, but since adding or subtracting from + ** NULL is always NULL anyway, this case is handled as required too. */ + sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC); + addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1); + VdbeCoverage(v); + if( (op==OP_Ge && arith==OP_Add) || (op==OP_Le && arith==OP_Subtract) ){ + sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1); + sqlite3VdbeJumpHere(v, addrGe); + /* Compare registers reg2 and reg1, taking the jump if required. Note that ** control skips over this test if the BIGNULL flag is set and either ** reg1 or reg2 contain a NULL value. */ sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v); + pColl = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[0].pExpr); + sqlite3VdbeAppendP4(v, (void*)pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); + sqlite3VdbeResolveLabel(v, addrDone); assert( op==OP_Ge || op==OP_Gt || op==OP_Lt || op==OP_Le ); testcase(op==OP_Ge); VdbeCoverageIf(v, op==OP_Ge); @@ -2227,16 +2260,24 @@ static int windowCodeOp( /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the ** start cursor does not advance past the end cursor within the - ** temporary table. It otherwise might, if (a>b). */ + ** temporary table. It otherwise might, if (a>b). Also ensure that, + ** if the input cursor is still finding new rows, that the end + ** cursor does not go past it to EOF. */ if( pMWin->eStart==pMWin->eEnd && regCountdown - && pMWin->eFrmType==TK_RANGE && op==WINDOW_AGGINVERSE + && pMWin->eFrmType==TK_RANGE ){ int regRowid1 = sqlite3GetTempReg(pParse); int regRowid2 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1); - sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2); - sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1); - VdbeCoverage(v); + if( op==WINDOW_AGGINVERSE ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1); + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2); + sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1); + VdbeCoverage(v); + }else if( p->regRowid ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid1); + sqlite3VdbeAddOp3(v, OP_Ge, p->regRowid, lblDone, regRowid1); + VdbeCoverageNeverNull(v); + } sqlite3ReleaseTempReg(pParse, regRowid1); sqlite3ReleaseTempReg(pParse, regRowid2); assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ); @@ -2733,7 +2774,6 @@ void sqlite3WindowCodeStep( int addrEmpty; /* Address of OP_Rewind in flush: */ int regNew; /* Array of registers holding new input row */ int regRecord; /* regNew array in record form */ - int regRowid; /* Rowid for regRecord in eph table */ int regNewPeer = 0; /* Peer values for new row (part of regNew) */ int regPeer = 0; /* Peer values for current row */ int regFlushPart = 0; /* Register for "Gosub flush_partition" */ @@ -2805,7 +2845,7 @@ void sqlite3WindowCodeStep( regNew = pParse->nMem+1; pParse->nMem += nInput; regRecord = ++pParse->nMem; - regRowid = ++pParse->nMem; + s.regRowid = ++pParse->nMem; /* If the window frame contains an " PRECEDING" or " FOLLOWING" ** clause, allocate registers to store the results of evaluating each @@ -2861,9 +2901,9 @@ void sqlite3WindowCodeStep( } /* Insert the new row into the ephemeral table */ - sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, regRowid); - addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, s.regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, s.regRowid); + addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, s.regRowid); VdbeCoverageNeverNull(v); /* This block is run for the first row of each partition */ @@ -2981,6 +3021,7 @@ void sqlite3WindowCodeStep( sqlite3VdbeJumpHere(v, addrGosubFlush); } + s.regRowid = 0; addrEmpty = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite); VdbeCoverage(v); if( pMWin->eEnd==TK_PRECEDING ){ diff --git a/test/alter.test b/test/alter.test index 43d2a6d5a..634e318e3 100644 --- a/test/alter.test +++ b/test/alter.test @@ -903,4 +903,16 @@ ifcapable rtree { } {1 1.0 2.0 3 1.5 3.5} } +# 2021-03-08 dbsqlfuzz 3f0a7245b69cd08617d7d7781ebaedb0fe765a93 +reset_db +do_catchsql_test alter-18.1 { + CREATE TABLE t1(a,b,c); + CREATE TABLE log(a INTEGER PRIMARY KEY,b,c); + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + INSERT INTO logx(a,b,c) VALUES(new.a,new.b,new.c) + ON CONFLICT(a) DO UPDATE SET c=excluded.c, b=new.b; + END; + ALTER TABLE log RENAME COLUMN a TO x; +} {1 {error in trigger tr1: no such table: main.logx}} + finish_test diff --git a/test/alter4.test b/test/alter4.test index 3aca7df33..c63ba6b07 100644 --- a/test/alter4.test +++ b/test/alter4.test @@ -318,16 +318,16 @@ ifcapable trigger&&tempdb { END; INSERT INTO t1 VALUES(1, 2); - SELECT * FROM log; + SELECT * FROM log ORDER BY trig, a, b; } - } {b 1 2 a 1 2} + } {a 1 2 b 1 2} do_test alter4-6.2 { execsql { ALTER TABLE t1 ADD COLUMN c DEFAULT 'c'; INSERT INTO t1(a, b) VALUES(3, 4); - SELECT * FROM log; + SELECT * FROM log ORDER BY trig, a, b; } - } {b 1 2 a 1 2 b 3 4 a 3 4} + } {a 1 2 a 3 4 b 1 2 b 3 4} } # Ticket #1183 - Make sure adding columns to large tables does not cause diff --git a/test/alterauth2.test b/test/alterauth2.test index bd589cda1..27ded1c3e 100644 --- a/test/alterauth2.test +++ b/test/alterauth2.test @@ -82,6 +82,7 @@ do_auth_test 1.2 { {SQLITE_ALTER_TABLE main t2 {} {}} {SQLITE_FUNCTION {} like {} {}} {SQLITE_FUNCTION {} sqlite_rename_column {} {}} + {SQLITE_FUNCTION {} sqlite_rename_quotefix {} {}} {SQLITE_FUNCTION {} sqlite_rename_test {} {}} {SQLITE_READ sqlite_master name main {}} {SQLITE_READ sqlite_master sql main {}} @@ -95,4 +96,23 @@ do_auth_test 1.2 { {SQLITE_UPDATE sqlite_temp_master sql temp {}} } +do_auth_test 1.3 { + ALTER TABLE t2 DROP COLUMN c; +} { + {SQLITE_FUNCTION {} like {} {}} + {SQLITE_FUNCTION {} sqlite_drop_column {} {}} + {SQLITE_FUNCTION {} sqlite_rename_quotefix {} {}} + {SQLITE_FUNCTION {} sqlite_rename_test {} {}} + {SQLITE_READ sqlite_master name main {}} + {SQLITE_READ sqlite_master sql main {}} + {SQLITE_READ sqlite_master tbl_name main {}} + {SQLITE_READ sqlite_master type main {}} + {SQLITE_READ sqlite_temp_master name temp {}} + {SQLITE_READ sqlite_temp_master sql temp {}} + {SQLITE_READ sqlite_temp_master type temp {}} + {SQLITE_SELECT {} {} {} {}} + {SQLITE_UPDATE sqlite_master sql main {}} + {SQLITE_UPDATE sqlite_temp_master sql temp {}} +} + finish_test diff --git a/test/altercol.test b/test/altercol.test index 1479b3a7d..d0c5c40ea 100644 --- a/test/altercol.test +++ b/test/altercol.test @@ -13,8 +13,6 @@ # file format change that may be used in the future to implement # "ALTER TABLE ... RENAME COLUMN ... TO". # -# $Id: alter4.test,v 1.1 2009/02/02 18:03:22 drh Exp $ -# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -569,7 +567,7 @@ do_execsql_test 13.1.6 { do_catchsql_test 13.1.7 { ALTER TABLE x1 RENAME COLUMN t TO ttt; -} {1 {database disk image is malformed}} +} {1 {error in index x1i: }} do_execsql_test 13.1.8 { DELETE FROM sqlite_master WHERE name = 'x1i'; @@ -804,6 +802,62 @@ do_execsql_test 20.110 { SELECT sql FROM sqlite_master WHERE name='t1'; } {{CREATE TABLE t1(xx UNIQUE,yy UNIQUE,zz UNIQUE,UNIQUE(xx),PRIMARY KEY(yy),UNIQUE(zz))}} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 21.0 { + CREATE TABLE t1(a, b, c NOT NULL); + CREATE TRIGGER tr1 AFTER INSERT ON t1 WHEN new.c IS NOT NULL BEGIN + SELECT c NOT NULL FROM t1; + END; +} + +do_execsql_test 21.1 { + ALTER TABLE t1 RENAME c TO d; +} + +do_execsql_test 21.2 { + SELECT sql FROM sqlite_schema WHERE name IS 'tr1' +} {{CREATE TRIGGER tr1 AFTER INSERT ON t1 WHEN new.d IS NOT NULL BEGIN + SELECT d NOT NULL FROM t1; + END} +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 22.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, othername, extra AS (c + 1)); + ALTER TABLE t1 RENAME a to othername; + SELECT sql FROM sqlite_schema; +} { + {CREATE TABLE t1(othername, b)} + {CREATE TABLE t2(c, othername, extra AS (c + 1))} +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 22.0 { + CREATE TABLE t1(a, b); + CREATE INDEX x1 on t1("c"=b); + INSERT INTO t1 VALUES('a', 'a'); + INSERT INTO t1 VALUES('b', 'b'); + INSERT INTO t1 VALUES('c', 'c'); + ALTER TABLE t1 RENAME COLUMN a TO "c"; + PRAGMA integrity_check; +} {ok} + +reset_db +do_execsql_test 23.0 { + CREATE TABLE t1('a'"b",c); + CREATE INDEX i1 ON t1('a'); + INSERT INTO t1 VALUES(1,2), (3,4); + ALTER TABLE t1 RENAME COLUMN a TO x; + PRAGMA integrity_check; + SELECT sql FROM sqlite_schema WHERE name='t1'; + +} {ok {CREATE TABLE t1("x" "b",c)}} finish_test diff --git a/test/altercorrupt.test b/test/altercorrupt.test new file mode 100644 index 000000000..5c50fa4a4 --- /dev/null +++ b/test/altercorrupt.test @@ -0,0 +1,175 @@ +# 2019-01-11 +# +# 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 altercorrupt + +database_may_be_corrupt + +#-------------------------------------------------------------------------- +reset_db +do_test 1.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 24576 pagesize 4096 filename crash-685346d89b5e5f.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 06 .....@ ........ +| 32: 00 00 63 00 00 05 f0 00 00 00 00 04 10 00 00 04 ..c............. +| 48: 00 00 00 00 00 00 0f f0 00 00 00 01 00 00 00 00 ................ +| 64: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 96: 00 00 00 00 0d 0f f8 00 05 0e cf 00 0f 79 0f d3 .............y.. +| 112: 0f 2e 0e f3 0e cf 00 00 00 00 00 00 00 00 00 00 ................ +| 3776: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 22 ................ +| 3792: 05 06 17 11 11 01 31 74 61 62 6c 65 74 34 74 34 ......1tablet4t4 +| 3808: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 74 34 .CREATE TABLE t4 +| 3824: 28 7a 29 39 04 06 17 11 11 01 5f 74 61 62 6c 65 (z)9......_table +| 3840: 74 33 74 33 05 43 52 45 41 54 45 20 54 41 42 4c t3t3.CREATE TABL +| 3856: 45 20 74 33 28 78 20 49 4e 54 45 47 45 52 20 50 E t3(x INTEGER P +| 3872: 52 49 4d 41 52 59 20 4b 45 59 2c 20 79 29 49 03 RIMARY KEY, y)I. +| 3888: 06 17 11 11 01 7f 74 61 62 6c 65 74 32 74 32 04 ......tablet2t2. +| 3904: 43 52 45 41 54 45 20 54 41 42 4c 45 20 74 32 28 CREATE TABLE t2( +| 3920: 61 2c 62 2c 63 20 50 52 49 4d 41 52 59 20 4b 45 a,b,c PRIMARY KE +| 3936: 59 2c 20 64 2c 20 65 2c 20 66 29 20 57 49 54 48 Y, d, e, f) WITH +| 3952: 4f 55 54 20 52 4f 57 49 44 58 03 07 17 11 11 01 OUT ROWIDX...... +| 3968: 81 1b 74 61 62 6c 65 74 31 74 31 02 43 52 45 41 ..tablet1t1.CREA +| 3984: 54 45 20 54 41 42 4c 45 20 74 31 28 61 2c 62 2c TE TABLE t1(a,b, +| 4000: 63 20 41 53 20 28 2d 62 29 20 56 49 52 54 55 41 c AS (-b) VIRTUA +| 4016: 4c 2c 64 20 43 48 45 43 4b 28 64 3e 35 29 2c 65 L,d CHECK(d>5),e +| 4032: 20 55 4e 49 51 55 45 2c 20 66 20 41 53 20 28 2b UNIQUE, f AS (+ +| 4048: 62 29 29 23 02 06 17 37 11 01 00 69 6e 64 65 78 b))#...7...index +| 4064: 73 71 6c 69 74 65 5f 61 75 74 6f 69 6e 64 65 78 sqlite_autoindex +| 4080: 5f 74 31 5f 31 74 31 03 00 00 00 08 00 00 00 00 _t1_1t1......... +| page 2 offset 4096 +| 0: 0d 00 00 00 0a 0f 93 00 0f f6 0f eb 0f e0 0f d5 ................ +| 16: 0f ca 0f 8f 0f b4 0f a9 0f 9e 0f 93 00 00 00 00 ................ +| 3984: 00 00 00 09 0a 05 01 01 01 01 0a 64 6e 14 09 09 ...........dn... +| 4000: 05 01 01 01 01 09 5a 6d 12 09 08 05 01 01 01 01 ......Zm........ +| 4016: 08 50 6c 10 09 07 05 01 01 01 01 07 46 6b 0e 09 .Pl.........Fk.. +| 4032: 06 05 01 01 01 01 06 3c 6a 0c 09 05 05 01 01 01 .......5),e +| 4032: 20 55 4e 49 51 55 45 2c 20 66 20 41 53 20 28 2b UNIQUE, f AS (+ +| 4048: 62 29 29 23 02 06 17 37 11 01 00 69 6e 64 65 78 b))#...7...index +| 4064: 73 71 6c 69 74 65 5f 61 75 74 6f 69 6e 64 65 78 sqlite_autoindex +| 4080: 5f 74 31 5f 31 84 31 03 01 00 00 08 00 00 00 00 _t1_1.1......... +| page 2 offset 4096 +| 0: 0d 00 00 00 0a 0f 93 00 0f f6 0f eb 0f e0 0f d5 ................ +| 16: 0f ca 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3984: 00 00 00 09 0a 05 01 01 01 01 0a 64 6e 14 09 09 ...........dn... +| 4000: 05 01 01 01 01 09 5a 6d 12 09 08 05 01 00 f1 01 ......Zm........ +| 4016: 08 50 6c 10 09 07 05 01 01 01 01 07 46 6b 0e 09 .Pl.........Fk.. +| 4032: 06 05 01 00 f1 01 06 3c 6a 0c 09 05 05 01 01 01 .......10)); + CREATE TABLE t13(a, b, c CHECK(c>10)); +} +do_catchsql_test 3.1 { + ALTER TABLE t12 DROP COLUMN c; +} {1 {error in table t12 after drop column: no such column: c}} + +do_catchsql_test 3.2 { + ALTER TABLE t13 DROP COLUMN c; +} {0 {}} + +#------------------------------------------------------------------------- +# Test that generated columns can be dropped. And that other columns from +# tables that contain generated columns can be dropped. +# +foreach {tn wo vs} { + 1 "" "" + 2 "" VIRTUAL + 3 "" STORED + 4 "WITHOUT ROWID" STORED + 5 "WITHOUT ROWID" VIRTUAL +} { + reset_db + + do_execsql_test 4.$tn.0 " + CREATE TABLE 'my table'(a, b PRIMARY KEY, c AS (a+b) $vs, d) $wo + " + do_execsql_test 4.$tn.1 { + INSERT INTO "my table"(a, b, d) VALUES(1, 2, 'hello'); + INSERT INTO "my table"(a, b, d) VALUES(3, 4, 'world'); + + SELECT * FROM "my table" + } { + 1 2 3 hello + 3 4 7 world + } + + do_execsql_test 4.$tn.2 { + ALTER TABLE "my table" DROP COLUMN c; + } + do_execsql_test 4.$tn.3 { + SELECT * FROM "my table" + } { + 1 2 hello + 3 4 world + } + + do_execsql_test 4.$tn.4 " + CREATE TABLE x1(a, b, c PRIMARY KEY, d AS (b+c) $vs, e) $wo + " + do_execsql_test 4.$tn.5 { + INSERT INTO x1(a, b, c, e) VALUES(1, 2, 3, 4); + INSERT INTO x1(a, b, c, e) VALUES(5, 6, 7, 8); + INSERT INTO x1(a, b, c, e) VALUES(9, 10, 11, 12); + SELECT * FROM x1; + } { + 1 2 3 5 4 + 5 6 7 13 8 + 9 10 11 21 12 + } + + do_execsql_test 4.$tn.6 { + ALTER TABLE x1 DROP COLUMN a + } + do_execsql_test 4.$tn.7 { + SELECT * FROM x1 + } { + 2 3 5 4 + 6 7 13 8 + 10 11 21 12 + } + do_execsql_test 4.$tn.8 { + ALTER TABLE x1 DROP COLUMN e + } + do_execsql_test 4.$tn.9 { + SELECT * FROM x1 + } { + 2 3 5 + 6 7 13 + 10 11 21 + } +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 5.0 { + CREATE TABLE p1(a PRIMARY KEY, b UNIQUE); + CREATE TABLE c1(x, y, z REFERENCES p1(c)); + CREATE TABLE c2(x, y, z, w REFERENCES p1(b)); +} +do_execsql_test 5.1 { + ALTER TABLE c1 DROP COLUMN z; + ALTER TABLE c2 DROP COLUMN z; + SELECT sql FROM sqlite_schema WHERE name IN ('c1', 'c2'); +} { + {CREATE TABLE c1(x, y)} + {CREATE TABLE c2(x, y, w REFERENCES p1(b))} +} + +do_execsql_test 5.2.1 { + CREATE VIEW v1 AS SELECT d, e FROM p1 +} +do_catchsql_test 5.2.2 { + ALTER TABLE c1 DROP COLUMN x +} {1 {error in view v1: no such column: d}} +do_execsql_test 5.3.1 { + DROP VIEW v1; + CREATE VIEW v1 AS SELECT x, y FROM c1; +} +do_catchsql_test 5.3.2 { + ALTER TABLE c1 DROP COLUMN x +} {1 {error in view v1 after drop column: no such column: x}} + +do_execsql_test 5.4.1 { + CREATE TRIGGER tr AFTER INSERT ON c1 BEGIN + INSERT INTO p1 VALUES(new.y, new.xyz); + END; +} +do_catchsql_test 5.4.2 { + ALTER TABLE c1 DROP COLUMN y +} {1 {error in trigger tr: no such column: new.xyz}} +do_execsql_test 5.5.1 { + DROP TRIGGER tr; + CREATE TRIGGER tr AFTER INSERT ON c1 BEGIN + INSERT INTO p1 VALUES(new.y, new.z); + END; +} +do_catchsql_test 5.5.2 { + ALTER TABLE c1 DROP COLUMN y +} {1 {error in trigger tr: no such column: new.z}} + +# 2021-03-06 dbsqlfuzz crash-419aa525df93db6e463772c686ac6da27b46da9e +reset_db +do_catchsql_test 6.0 { + CREATE TABLE t1(a,b,c); + CREATE TABLE t2(x,y,z); + PRAGMA writable_schema=ON; + UPDATE sqlite_schema SET sql='CREATE INDEX t1b ON t1(b)' WHERE name='t2'; + PRAGMA writable_schema=OFF; + ALTER TABLE t2 DROP COLUMN z; +} {1 {database disk image is malformed}} +reset_db +do_catchsql_test 6.1 { + CREATE TABLE t1(a,b,c); + CREATE TABLE t2(x,y,z); + PRAGMA writable_schema=ON; + UPDATE sqlite_schema SET sql='CREATE VIEW t2(x,y,z) AS SELECT b,a,c FROM t1' + WHERE name='t2'; + PRAGMA writable_schema=OFF; + ALTER TABLE t2 DROP COLUMN z; +} {1 {database disk image is malformed}} + +# 2021-04-06 dbsqlfuzz crash-331c5c29bb76257b198f1318eef3288f9624c8ce +reset_db +do_execsql_test 7.0 { + CREATE TABLE t1(a, b, c, PRIMARY KEY(a COLLATE nocase, a)) WITHOUT ROWID; + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); +} +do_execsql_test 7.1 { + ALTER TABLE t1 DROP COLUMN c; +} +do_execsql_test 7.2 { + SELECT sql FROM sqlite_schema; +} {{CREATE TABLE t1(a, b, PRIMARY KEY(a COLLATE nocase, a)) WITHOUT ROWID}} +do_execsql_test 7.3 { + SELECT * FROM t1; +} {1 2 4 5} + +reset_db +do_execsql_test 8.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + PRAGMA writable_schema = 1; + UPDATE sqlite_schema + SET sql = 'CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b)' +} +db close +sqlite3 db test.db +do_execsql_test 8.1 { + ALTER TABLE t1 DROP COLUMN b; +} +do_execsql_test 8.2 { + SELECT sql FROM sqlite_schema; +} {{CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT)}} + +#------------------------------------------------------------------------- + +foreach {tn wo} { + 1 {} + 2 {WITHOUT ROWID} +} { + reset_db + do_execsql_test 9.$tn.0 " + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) $wo; + " + do_execsql_test 9.$tn.1 { + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50000 + ) + INSERT INTO t1(a, b, c) SELECT i, 123, 456 FROM s; + } + do_execsql_test 9.$tn.2 { + ALTER TABLE t1 DROP COLUMN b; + } + + do_execsql_test 9.$tn.3 { + SELECT count(*), c FROM t1 GROUP BY c; + } {50000 456} +} + + + +finish_test diff --git a/test/alterdropcol2.test b/test/alterdropcol2.test new file mode 100644 index 000000000..d60e7db44 --- /dev/null +++ b/test/alterdropcol2.test @@ -0,0 +1,222 @@ +# 2021 February 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 +set testprefix alterdropcol2 + +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + +# EVIDENCE-OF: R-58318-35349 The DROP COLUMN syntax is used to remove an +# existing column from a table. +do_execsql_test 1.0 { + CREATE TABLE t1(c, b, a, PRIMARY KEY(b, a)) WITHOUT ROWID; + INSERT INTO t1 VALUES(1, 2, 3), (4, 5, 6); +} +do_execsql_test 1.1 { + ALTER TABLE t1 DROP c; +} + +# EVIDENCE-OF: The DROP COLUMN command removes the named column from the table, +# and also rewrites the entire table to purge the data associated with that +# column. +do_execsql_test 1.2.1 { + SELECT * FROM t1; +} {2 3 5 6} + +do_execsql_test 1.2.2 { + SELECT sql FROM sqlite_schema; +} { + {CREATE TABLE t1(b, a, PRIMARY KEY(b, a)) WITHOUT ROWID} +} + +proc do_atdc_error_test {tn schema atdc error} { + reset_db + execsql $schema + uplevel [list do_catchsql_test $tn $atdc [list 1 [string trim $error]]] +} + +#------------------------------------------------------------------------- +# Test cases 2.* attempt to verify the following: +# +# EVIDENCE-OF: R-24098-10282 The DROP COLUMN command only works if the column +# is not referenced by any other parts of the schema and is not a PRIMARY KEY +# and does not have a UNIQUE constraint. +# + +# EVIDENCE-OF: R-52436-31752 The column is a PRIMARY KEY or part of one. +# +do_atdc_error_test 2.1.1 { + CREATE TABLE x1(a PRIMARY KEY, b, c); +} { + ALTER TABLE x1 DROP COLUMN a +} { + cannot drop PRIMARY KEY column: "a" +} +do_atdc_error_test 2.1.2 { + CREATE TABLE x1(a,b,c,d,e, PRIMARY KEY(b,c,d)); +} { + ALTER TABLE x1 DROP COLUMN c +} { + cannot drop PRIMARY KEY column: "c" +} + +# EVIDENCE-OF: R-43412-16016 The column has a UNIQUE constraint. +# +do_atdc_error_test 2.2.1 { + CREATE TABLE x1(a PRIMARY KEY, b, c UNIQUE); +} { + ALTER TABLE x1 DROP COLUMN c +} { + cannot drop UNIQUE column: "c" +} +do_atdc_error_test 2.2.2 { + CREATE TABLE x1(a PRIMARY KEY, b, c, UNIQUE(b, c)); +} { + ALTER TABLE x1 DROP COLUMN c +} { + error in table x1 after drop column: no such column: c +} + +# EVIDENCE-OF: R-46731-08965 The column is indexed. +# +do_atdc_error_test 2.3.1 { + CREATE TABLE 'one two'('x y', 'z 1', 'a b'); + CREATE INDEX idx ON 'one two'('z 1'); +} { + ALTER TABLE 'one two' DROP COLUMN 'z 1' +} { + error in index idx after drop column: no such column: z 1 +} +do_atdc_error_test 2.3.2 { + CREATE TABLE x1(a, b, c); + CREATE INDEX idx ON x1(a); +} { + ALTER TABLE x1 DROP COLUMN a; +} { + error in index idx after drop column: no such column: a +} + +# EVIDENCE-OF: R-46731-08965 The column is indexed. +# +do_atdc_error_test 2.4.1 { + CREATE TABLE x1234(a, b, c PRIMARY KEY) WITHOUT ROWID; + CREATE INDEX i1 ON x1234(b) WHERE ((a+5) % 10)==0; +} { + ALTER TABLE x1234 DROP a +} { + error in index i1 after drop column: no such column: a +} + +# EVIDENCE-OF: R-47838-03249 The column is named in a table or column +# CHECK constraint not associated with the column being dropped. +# +do_atdc_error_test 2.5.1 { + CREATE TABLE x1234(a, b, c PRIMARY KEY, CHECK(((a+5)%10)!=0)) WITHOUT ROWID; +} { + ALTER TABLE x1234 DROP a +} { + error in table x1234 after drop column: no such column: a +} + +# EVIDENCE-OF: R-55640-01652 The column is used in a foreign key constraint. +# +do_atdc_error_test 2.6.1 { + CREATE TABLE p1(x, y UNIQUE); + CREATE TABLE c1(u, v, FOREIGN KEY (v) REFERENCES p1(y)) +} { + ALTER TABLE c1 DROP v +} { + error in table c1 after drop column: unknown column "v" in foreign key definition +} + +# EVIDENCE-OF: R-20795-39479 The column is used in the expression of a +# generated column. +do_atdc_error_test 2.7.1 { + CREATE TABLE c1(u, v, w AS (u+v)); +} { + ALTER TABLE c1 DROP v +} { + error in table c1 after drop column: no such column: v +} +do_atdc_error_test 2.7.2 { + CREATE TABLE c1(u, v, w AS (u+v) STORED); +} { + ALTER TABLE c1 DROP u +} { + error in table c1 after drop column: no such column: u +} + +# EVIDENCE-OF: R-01515-49025 The column appears in a trigger or view. +# +do_atdc_error_test 2.8.1 { + CREATE TABLE log(l); + CREATE TABLE c1(u, v, w); + CREATE TRIGGER tr1 AFTER INSERT ON c1 BEGIN + INSERT INTO log VALUES(new.w); + END; +} { + ALTER TABLE c1 DROP w +} { + error in trigger tr1 after drop column: no such column: new.w +} +do_atdc_error_test 2.8.2 { + CREATE TABLE c1(u, v, w); + CREATE VIEW v1 AS SELECT u, v, w FROM c1; +} { + ALTER TABLE c1 DROP w +} { + error in view v1 after drop column: no such column: w +} +do_atdc_error_test 2.8.3 { + CREATE TABLE c1(u, v, w); + CREATE VIEW v1 AS SELECT * FROM c1 WHERE w IS NOT NULL; +} { + ALTER TABLE c1 DROP w +} { + error in view v1 after drop column: no such column: w +} + +#------------------------------------------------------------------------- +# Verify that a column that is part of a CHECK constraint may be dropped +# if the CHECK constraint was specified as part of the column definition. +# + +# STALE-EVIDENCE: R-60924-11170 However, the column being deleted can be used in a +# column CHECK constraint because the column CHECK constraint is dropped +# together with the column itself. +do_execsql_test 3.0 { + CREATE TABLE yyy(q, w, e CHECK (e > 0), r); + INSERT INTO yyy VALUES(1,1,1,1), (2,2,2,2); + + CREATE TABLE zzz(q, w, e, r, CHECK (e > 0)); + INSERT INTO zzz VALUES(1,1,1,1), (2,2,2,2); +} +do_catchsql_test 3.1.1 { + INSERT INTO yyy VALUES(0,0,0,0); +} {1 {CHECK constraint failed: e > 0}} +do_catchsql_test 3.1.2 { + INSERT INTO yyy VALUES(0,0,0,0); +} {1 {CHECK constraint failed: e > 0}} + +do_execsql_test 3.2.1 { + ALTER TABLE yyy DROP e; +} +do_catchsql_test 3.2.2 { + ALTER TABLE zzz DROP e; +} {1 {error in table zzz after drop column: no such column: e}} + +finish_test diff --git a/test/alterlegacy.test b/test/alterlegacy.test index 47d8bcfba..dfa171646 100644 --- a/test/alterlegacy.test +++ b/test/alterlegacy.test @@ -40,7 +40,7 @@ do_execsql_test 1.1 { # slightly different - it rejects the change and rolls back the transaction. do_catchsql_test 1.2 { ALTER TABLE t1 RENAME TO t1new; -} {1 {no such column: t1.a}} +} {1 {error in table t1new after rename: no such column: t1.a}} do_execsql_test 1.3 { CREATE TABLE t3(c, d); @@ -59,7 +59,7 @@ do_execsql_test 1.4 { do_catchsql_test 1.3 { ALTER TABLE t2 RENAME TO t2new; -} {1 {no such column: t2.b}} +} {1 {error in index t2expr after rename: no such column: t2.b}} do_execsql_test 1.4 { SELECT sql FROM sqlite_master } { diff --git a/test/altermalloc2.test b/test/altermalloc2.test index 610b3f6bf..3d22ad945 100644 --- a/test/altermalloc2.test +++ b/test/altermalloc2.test @@ -98,4 +98,25 @@ do_faultsim_test 4 -faults oom-* -prep { faultsim_test_result {0 {}} } +reset_db +do_execsql_test 5.0 { + CREATE TABLE rr(a, b); + CREATE VIEW vv AS SELECT * FROM ( + WITH abc(d, e) AS (SELECT * FROM rr) + SELECT * FROM abc + ); +} {} + +faultsim_save_and_close +do_faultsim_test 5 -faults oom-* -prep { + faultsim_restore_and_reopen + execsql { SELECT * FROM sqlite_master } +} -body { + execsql { + ALTER TABLE rr RENAME TO c; + } +} -test { + faultsim_test_result {0 {}} +} + finish_test diff --git a/test/altermalloc3.test b/test/altermalloc3.test new file mode 100644 index 000000000..5e4e75149 --- /dev/null +++ b/test/altermalloc3.test @@ -0,0 +1,47 @@ +# 2021 February 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. +# +#************************************************************************* +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix altermalloc3 + +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE x1( + one, two, three, PRIMARY KEY(one), + CHECK (three!="xyz"), CHECK (two!="one") + ) WITHOUT ROWID; + CREATE INDEX x1i ON x1(one+"two"+"four") WHERE "five"; + CREATE TEMP TRIGGER AFTER INSERT ON x1 BEGIN + UPDATE x1 SET two=new.three || "new" WHERE one=new.one||""; + END; + CREATE TABLE t1(a, b, c, d, PRIMARY KEY(d, b)) WITHOUT ROWID; + INSERT INTO t1 VALUES(1, 2, 3, 4); +} +faultsim_save_and_close + +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen +} -body { + execsql { ALTER TABLE t1 DROP COLUMN c } +} -test { + faultsim_test_result {0 {}} +} + + +finish_test diff --git a/test/alterqf.test b/test/alterqf.test new file mode 100644 index 000000000..ce00f3b59 --- /dev/null +++ b/test/alterqf.test @@ -0,0 +1,111 @@ +# 2021 March 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 regression tests for SQLite library. This +# script focuses on testing internal function sqlite_rename_quotefix(). +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix alterqf + +sqlite3_test_control SQLITE_TESTCTRL_INTERNAL_FUNCTIONS db + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c); +} + +foreach {tn before after} { + 1 {CREATE VIEW v1 AS SELECT "a", "b", "notacolumn!", "c" FROM t1} + {CREATE VIEW v1 AS SELECT "a", "b", 'notacolumn!', "c" FROM t1} + + 2 {CREATE VIEW v1 AS SELECT "a", "b", "not'a'column!", "c" FROM t1} + {CREATE VIEW v1 AS SELECT "a", "b", 'not''a''column!', "c" FROM t1} + + 3 {CREATE VIEW v1 AS SELECT "a", "b", "not""a""column!", "c" FROM t1} + {CREATE VIEW v1 AS SELECT "a", "b", 'not"a"column!', "c" FROM t1} + + 4 {CREATE VIEW v1 AS SELECT "val", count("b") FROM t1 GROUP BY "abc"} + {CREATE VIEW v1 AS SELECT 'val', count("b") FROM t1 GROUP BY 'abc'} + + 5 {CREATE TABLE xyz(a CHECK (a!="str"), b AS (a||"str"))} + {CREATE TABLE xyz(a CHECK (a!='str'), b AS (a||'str'))} + + 6 {CREATE INDEX i1 ON t1(a || "str", "b", "val")} + {CREATE INDEX i1 ON t1(a || 'str', "b", 'val')} + + 7 {CREATE TRIGGER tr AFTER INSERT ON t1 BEGIN SELECT "abcd"; END} + {CREATE TRIGGER tr AFTER INSERT ON t1 BEGIN SELECT 'abcd'; END} + + 8 {CREATE VIEW v1 AS SELECT "string"'alias' FROM t1} + {CREATE VIEW v1 AS SELECT 'string' 'alias' FROM t1} + + 9 {CREATE INDEX i1 ON t1(a) WHERE "b"="bb"} + {CREATE INDEX i1 ON t1(a) WHERE "b"='bb'} + + 10 {CREATE TABLE t2(abc, xyz CHECK (xyz != "123"))} + {CREATE TABLE t2(abc, xyz CHECK (xyz != '123'))} + + 11 { + CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN + SELECT max("str", new."a") FROM t1 + WHERE group_concat("b", ",") OVER (ORDER BY c||"str"); + UPDATE t1 SET c= b + "str"; + DELETE FROM t1 WHERE EXISTS ( + SELECT 1 FROM t1 AS o WHERE o."a" = "o.a" AND t1.b IN("t1.b") + ); + END; + } { + CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN + SELECT max('str', new."a") FROM t1 + WHERE group_concat("b", ',') OVER (ORDER BY c||'str'); + UPDATE t1 SET c= b + 'str'; + DELETE FROM t1 WHERE EXISTS ( + SELECT 1 FROM t1 AS o WHERE o."a" = 'o.a' AND t1.b IN('t1.b') + ); + END; + } + +} { + do_execsql_test 1.$tn { + SELECT sqlite_rename_quotefix('main', $before) + } [list $after] +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE x1( + one, two, three, PRIMARY KEY(one), + CHECK (three!="xyz"), CHECK (two!="one") + ) WITHOUT ROWID; + CREATE INDEX x1i ON x1(one+"two"+"four") WHERE "five"; + CREATE TEMP TRIGGER AFTER INSERT ON x1 BEGIN + UPDATE x1 SET two=new.three || "new" WHERE one=new.one||""; + END; +} + +do_execsql_test 2.1 { + ALTER TABLE x1 RENAME two TO 'four'; + SELECT sql FROM sqlite_schema; + SELECT sql FROM sqlite_temp_schema; +} {{CREATE TABLE x1( + one, "four", three, PRIMARY KEY(one), + CHECK (three!='xyz'), CHECK ("four"!="one") + ) WITHOUT ROWID} + {CREATE INDEX x1i ON x1(one+"four"+'four') WHERE 'five'} + {CREATE TRIGGER AFTER INSERT ON x1 BEGIN + UPDATE x1 SET "four"=new.three || 'new' WHERE one=new.one||''; + END} +} + + +finish_test diff --git a/test/altertab.test b/test/altertab.test index 4e6d98c93..079a382d8 100644 --- a/test/altertab.test +++ b/test/altertab.test @@ -711,5 +711,261 @@ do_execsql_test 23.4 { {CREATE VIEW v1 AS SELECT * FROM "idx2" WHERE y='abc'} } -finish_test +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 24.1.0 { + CREATE TABLE t1(a, b); + CREATE TRIGGER AFTER INSERT ON t1 BEGIN + INSERT INTO nosuchtable VALUES(new.a) ON CONFLICT(a) DO NOTHING; + END; +} +do_catchsql_test 24.1.1 { + ALTER TABLE t1 RENAME TO t2; +} {1 {error in trigger AFTER: no such table: main.nosuchtable}} + +reset_db +do_execsql_test 24.2.0 { + CREATE TABLE t1(a, b); + CREATE TRIGGER AFTER INSERT ON t1 BEGIN + INSERT INTO v1 VALUES(new.a) ON CONFLICT(a) DO NOTHING; + END; + CREATE VIEW v1 AS SELECT * FROM nosuchtable; +} +do_catchsql_test 24.2.1 { + ALTER TABLE t1 RENAME TO t2; +} {1 {error in trigger AFTER: no such table: main.nosuchtable}} + +#-------------------------------------------------------------------------- +# +reset_db +do_execsql_test 25.1 { + CREATE TABLE xx(x); + CREATE VIEW v3(b) AS WITH b AS (SELECT b FROM (SELECT * FROM t2)) VALUES(1); +} + +ifcapable json1&&vtab { + do_catchsql_test 25.2 { + ALTER TABLE json_each RENAME TO t4; + } {1 {table json_each may not be altered}} +} + +# 2021-05-01 dbsqlfuzz bc17a306a09329bba0ecc61547077f6178bcf321 +# Remove a NEVER() inserted on 2019-12-09 that is reachable after all. +# +reset_db +do_execsql_test 26.1 { + CREATE TABLE t1(k,v); + CREATE TABLE t2_a(k,v); + CREATE VIEW t2 AS SELECT * FROM t2_a; + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + UPDATE t1 + SET (k,v)=((WITH cte1(a) AS (SELECT 1 FROM t2) SELECT t2.k FROM t2, cte1),1); + END; + ALTER TABLE t1 RENAME TO t1x; + INSERT INTO t2_a VALUES(2,3); + INSERT INTO t1x VALUES(98,99); + SELECT * FROM t1x; +} {2 1} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 27.1 { + + create table t_sa ( + c_muyat INTEGER NOT NULL, + c_d4u TEXT + ); + + create table t2 ( abc ); + + CREATE TRIGGER trig AFTER DELETE ON t_sa + BEGIN + DELETE FROM t_sa WHERE ( + SELECT 123 FROM t2 + WINDOW oamat7fzf AS ( PARTITION BY t_sa.c_d4u ) + ); + END; +} + +do_execsql_test 27.2 { + alter table t_sa rename column c_muyat to c_dg; +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 29.1 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES('a', 'b', 'c'); + + CREATE VIEW v0 AS + WITH p AS ( SELECT 1 FROM t1 ), + g AS ( SELECT 1 FROM p, t1 ) + SELECT 1 FROM g; +} + +do_execsql_test 29.2 { + SELECT * FROM v0 +} 1 + +do_execsql_test 29.2 { + ALTER TABLE t1 RENAME TO t2 +} +do_execsql_test 29.3 { + SELECT sql FROM sqlite_schema WHERE name='v0' +} {{CREATE VIEW v0 AS + WITH p AS ( SELECT 1 FROM "t2" ), + g AS ( SELECT 1 FROM p, "t2" ) + SELECT 1 FROM g}} + +do_execsql_test 29.4 { + CREATE VIEW v2 AS + WITH p AS ( SELECT 1 FROM t2 ), + g AS ( SELECT 1 FROM ( + WITH i AS (SELECT 1 FROM p, t2) + SELECT * FROM i + ) + ) + SELECT 1 FROM g; +} + +do_execsql_test 29.4 { + SELECT * FROM v2; +} 1 + +do_execsql_test 29.5 { + ALTER TABLE t2 RENAME TO t3; +} + +do_execsql_test 29.5 { + SELECT sql FROM sqlite_schema WHERE name='v2' +} {{CREATE VIEW v2 AS + WITH p AS ( SELECT 1 FROM "t3" ), + g AS ( SELECT 1 FROM ( + WITH i AS (SELECT 1 FROM p, "t3") + SELECT * FROM i + ) + ) + SELECT 1 FROM g}} + + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 28.1 { + CREATE TABLE t1(a); + CREATE TABLE t2(b,c); + CREATE TABLE t4(b,c); + INSERT INTO t2 VALUES(1,2),(1,3),(2,5); + INSERT INTO t4 VALUES(1,2),(1,3),(2,5); + + CREATE VIEW v3 AS + WITH RECURSIVE t3(x,y,z) AS ( + SELECT b,c,NULL FROM t4 + UNION + SELECT x,y,NULL FROM t3, t2 + ) + SELECT * FROM t3 AS xyz; +} + +do_execsql_test 28.2 { + SELECT * FROM v3 +} { + 1 2 {} 1 3 {} 2 5 {} +} + +do_execsql_test 28.3 { + ALTER TABLE t1 RENAME a TO a2; -- fails in v3 +} + +do_execsql_test 28.4 { + ALTER TABLE t2 RENAME TO t5; +} + +do_execsql_test 28.5 { + SELECT sql FROM sqlite_schema WHERE name='v3' +} {{CREATE VIEW v3 AS + WITH RECURSIVE t3(x,y,z) AS ( + SELECT b,c,NULL FROM t4 + UNION + SELECT x,y,NULL FROM t3, "t5" + ) + SELECT * FROM t3 AS xyz}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 30.0 { + CREATE TABLE t1(a,b,c,d,e,f); + CREATE TABLE t2(a,b,c); + CREATE INDEX t1abc ON t1(a,b,c+d+e); + CREATE VIEW v1(x,y) AS + SELECT t1.b,t2.b FROM t1,t2 WHERE t1.a=t2.a + GROUP BY 1 HAVING t2.c NOT NULL LIMIT 10; + CREATE TRIGGER r1 AFTER INSERT ON t1 WHEN 'no' NOT NULL BEGIN + INSERT INTO t2(a,a,b,c) VALUES(new.b,new.a,new.c-7); + WITH c1(x) AS ( + VALUES(0) + UNION ALL + SELECT current_time+x FROM c1 WHERE x + UNION ALL + SELECT 1+x FROM c1 WHERE x<1 + ), c2(x) AS (VALUES(0),(1)) + SELECT * FROM c1 AS x1, c2 AS x2, ( + SELECT x+1 FROM c1 WHERE x IS NOT TRUE + UNION ALL + SELECT 1+x FROM c1 WHERE 1200 AND x<300 -} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x? AND x0 AND x<1100 -} {SCAN TABLE t1} +} {SCAN t1} # 2017-06-26: Verify that the SQLITE_DBCONFIG_ENABLE_QPSG setting disables # the use of bound parameters by STAT4 @@ -137,27 +137,27 @@ unset -nocomplain l unset -nocomplain u do_eqp_test analyze3-1.1.3.100 { SELECT sum(y) FROM t1 WHERE x>$l AND x<$u -} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x? AND x$l AND x<$u -} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x? AND x$l AND x<$u -} {SCAN TABLE t1} +} {SCAN t1} db cache flush sqlite3_db_config db ENABLE_QPSG 1 do_eqp_test analyze3-1.1.3.103 { SELECT sum(y) FROM t1 WHERE x>$l AND x<$u -} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x? AND x$l AND x<$u -} {SCAN TABLE t1} +} {SCAN t1} do_test analyze3-1.1.4 { sf_execsql { SELECT sum(y) FROM t1 WHERE x>200 AND x<300 } @@ -207,10 +207,10 @@ do_execsql_test analyze3-2.1.x { } {200 990} do_eqp_test analyze3-1.2.2 { SELECT sum(y) FROM t2 WHERE x>1 AND x<2 -} {SEARCH TABLE t2 USING INDEX i2 (x>? AND x? AND x0 AND x<99 -} {SCAN TABLE t2} +} {SCAN t2} do_test analyze3-1.2.4 { sf_execsql { SELECT sum(y) FROM t2 WHERE x>12 AND x<20 } @@ -259,10 +259,10 @@ do_execsql_test analyze3-1.3.x { } {99 1000} do_eqp_test analyze3-1.3.2 { SELECT sum(y) FROM t3 WHERE x>200 AND x<300 -} {SEARCH TABLE t3 USING INDEX i3 (x>? AND x? AND x0 AND x<1100 -} {SCAN TABLE t3} +} {SCAN t3} do_test analyze3-1.3.4 { sf_execsql { SELECT sum(y) FROM t3 WHERE x>200 AND x<300 } @@ -314,10 +314,10 @@ do_test analyze3-2.1 { } {} do_eqp_test analyze3-2.2 { SELECT count(a) FROM t1 WHERE b LIKE 'a%' -} {SEARCH TABLE t1 USING INDEX i1 (b>? AND b? AND b 'w' AND c = 13; -} {SEARCH TABLE t1 USING INDEX i2 (c=?)} +} {SEARCH t1 USING INDEX i2 (c=?)} #----------------------------------------------------------------------------- # 2015-04-20. diff --git a/test/analyze4.test b/test/analyze4.test index 9fc98aa8f..e3b0d23f6 100644 --- a/test/analyze4.test +++ b/test/analyze4.test @@ -38,7 +38,7 @@ do_test analyze4-1.0 { # Should choose the t1a index since it is more specific than t1b. db eval {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=5 AND b IS NULL} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} # Verify that the t1b index shows that it does not narrow down the # search any at all. diff --git a/test/analyze6.test b/test/analyze6.test index b6353f266..807fec132 100644 --- a/test/analyze6.test +++ b/test/analyze6.test @@ -61,7 +61,7 @@ do_test analyze6-1.0 { # do_test analyze6-1.1 { eqp {SELECT count(*) FROM ev, cat WHERE x=y} -} {/*SCAN TABLE cat USING COVERING INDEX catx*SEARCH TABLE ev USING COVERING INDEX evy (y=?)*/} +} {/*SCAN cat USING COVERING INDEX catx*SEARCH ev USING COVERING INDEX evy (y=?)*/} # The same plan is chosen regardless of the order of the tables in the # FROM clause. @@ -70,8 +70,8 @@ do_eqp_test analyze6-1.2 { SELECT count(*) FROM cat, ev WHERE x=y } { QUERY PLAN - |--SCAN TABLE cat USING COVERING INDEX catx - `--SEARCH TABLE ev USING COVERING INDEX evy (y=?) + |--SCAN cat USING COVERING INDEX catx + `--SEARCH ev USING COVERING INDEX evy (y=?) } @@ -86,26 +86,26 @@ do_test analyze6-2.1 { ANALYZE; } eqp {SELECT * FROM t201 WHERE z=5} -} {/*SEARCH TABLE t201 USING INDEX t201z (z=?)*/} +} {/*SEARCH t201 USING INDEX t201z (z=?)*/} do_test analyze6-2.2 { eqp {SELECT * FROM t201 WHERE y=5} -} {/*SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/} +} {/*SEARCH t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/} do_test analyze6-2.3 { eqp {SELECT * FROM t201 WHERE x=5} -} {/*SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)*/} +} {/*SEARCH t201 USING INTEGER PRIMARY KEY (rowid=?)*/} do_test analyze6-2.4 { execsql { INSERT INTO t201 VALUES(1,2,3),(2,3,4),(3,4,5); ANALYZE t201; } eqp {SELECT * FROM t201 WHERE z=5} -} {/*SEARCH TABLE t201 USING INDEX t201z (z=?)*/} +} {/*SEARCH t201 USING INDEX t201z (z=?)*/} do_test analyze6-2.5 { eqp {SELECT * FROM t201 WHERE y=5} -} {/*SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/} +} {/*SEARCH t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/} do_test analyze6-2.6 { eqp {SELECT * FROM t201 WHERE x=5} -} {/*SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)*/} +} {/*SEARCH t201 USING INTEGER PRIMARY KEY (rowid=?)*/} do_test analyze6-2.7 { execsql { INSERT INTO t201 VALUES(4,5,7); @@ -115,12 +115,12 @@ do_test analyze6-2.7 { ANALYZE t201; } eqp {SELECT * FROM t201 WHERE z=5} -} {/*SEARCH TABLE t201 USING INDEX t201z (z=?)*/} +} {/*SEARCH t201 USING INDEX t201z (z=?)*/} do_test analyze6-2.8 { eqp {SELECT * FROM t201 WHERE y=5} -} {/*SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/} +} {/*SEARCH t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/} do_test analyze6-2.9 { eqp {SELECT * FROM t201 WHERE x=5} -} {/*SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)*/} +} {/*SEARCH t201 USING INTEGER PRIMARY KEY (rowid=?)*/} finish_test diff --git a/test/analyze7.test b/test/analyze7.test index b3dba0256..53a80f641 100644 --- a/test/analyze7.test +++ b/test/analyze7.test @@ -37,13 +37,13 @@ do_test analyze7-1.0 { WHERE value BETWEEN 1 AND 256; EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123; } -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} do_test analyze7-1.1 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;} -} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/} +} {/*SEARCH t1 USING INDEX t1b (b=?)*/} do_test analyze7-1.2 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} -} {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/} +} {/*SEARCH t1 USING INDEX t1cd (c=?)*/} # Run an analyze on one of the three indices. Verify that this # effects the row-count estimate on the one query that uses that @@ -53,20 +53,20 @@ do_test analyze7-2.0 { execsql {ANALYZE t1a;} db cache flush execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123;} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} do_test analyze7-2.1 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;} -} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/} +} {/*SEARCH t1 USING INDEX t1b (b=?)*/} do_test analyze7-2.2 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} -} {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/} +} {/*SEARCH t1 USING INDEX t1cd (c=?)*/} # Verify that since the query planner now things that t1a is more # selective than t1b, it prefers to use t1a. # do_test analyze7-2.3 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND b=123} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} # Run an analysis on another of the three indices. Verify that this # new analysis works and does not disrupt the previous analysis. @@ -75,40 +75,40 @@ do_test analyze7-3.0 { execsql {ANALYZE t1cd;} db cache flush; execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123;} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} do_test analyze7-3.1 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;} -} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/} +} {/*SEARCH t1 USING INDEX t1b (b=?)*/} do_test analyze7-3.2.1 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=?;} -} {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/} +} {/*SEARCH t1 USING INDEX t1cd (c=?)*/} ifcapable stat4 { # If ENABLE_STAT4 is defined, SQLite comes up with a different estimated # row count for (c=2) than it does for (c=?). do_test analyze7-3.2.2 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} - } {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/} + } {/*SEARCH t1 USING INDEX t1cd (c=?)*/} } else { # If ENABLE_STAT4 is not defined, the expected row count for (c=2) is the # same as that for (c=?). do_test analyze7-3.2.3 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} - } {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/} + } {/*SEARCH t1 USING INDEX t1cd (c=?)*/} } do_test analyze7-3.3 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND b=123} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} ifcapable {!stat4} { do_test analyze7-3.4 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND b=123} - } {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/} + } {/*SEARCH t1 USING INDEX t1b (b=?)*/} do_test analyze7-3.5 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND c=123} - } {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} + } {/*SEARCH t1 USING INDEX t1a (a=?)*/} } do_test analyze7-3.6 { execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND d=123 AND b=123} -} {/*SEARCH TABLE t1 USING INDEX t1cd (c=? AND d=?)*/} +} {/*SEARCH t1 USING INDEX t1cd (c=? AND d=?)*/} finish_test diff --git a/test/analyze8.test b/test/analyze8.test index 7c61652e5..69605fd6f 100644 --- a/test/analyze8.test +++ b/test/analyze8.test @@ -61,25 +61,25 @@ do_test 1.0 { # do_test 1.1 { eqp {SELECT * FROM t1 WHERE a=100 AND b=55} -} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/} +} {/*SEARCH t1 USING INDEX t1b (b=?)*/} do_test 1.2 { eqp {SELECT * FROM t1 WHERE a=99 AND b=55} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} do_test 1.3 { eqp {SELECT * FROM t1 WHERE a=101 AND b=55} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} do_test 1.4 { eqp {SELECT * FROM t1 WHERE a=100 AND b=56} -} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/} +} {/*SEARCH t1 USING INDEX t1b (b=?)*/} do_test 1.5 { eqp {SELECT * FROM t1 WHERE a=99 AND b=56} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} do_test 1.6 { eqp {SELECT * FROM t1 WHERE a=101 AND b=56} -} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/} +} {/*SEARCH t1 USING INDEX t1a (a=?)*/} do_test 2.1 { eqp {SELECT * FROM t1 WHERE a=100 AND b BETWEEN 50 AND 54} -} {/*SEARCH TABLE t1 USING INDEX t1b (b>? AND b? AND b? AND b? AND b? AND c? AND c? AND c? AND c? AND b? AND b=? term. Better than # (a<20) but not as good as (a<10). do_eqp_test 25.4.1 { SELECT * FROM t6 WHERE a < 10 AND (b BETWEEN ? AND 60) - } {SEARCH TABLE t6 USING INDEX aa (a? AND b? AND b25 AND z=?; -} {SEARCH TABLE t1 USING INDEX i1 (x=? AND y>?)} +} {SEARCH t1 USING INDEX i1 (x=? AND y>?)} finish_test diff --git a/test/analyzeC.test b/test/analyzeC.test index 2a0a89781..2f43d57a1 100644 --- a/test/analyzeC.test +++ b/test/analyzeC.test @@ -50,7 +50,7 @@ do_execsql_test 1.2 { do_execsql_test 1.3 { EXPLAIN QUERY PLAN SELECT c FROM t1 ORDER BY a; -} {/.*SCAN TABLE t1 USING INDEX t1a.*/} +} {/.*SCAN t1 USING INDEX t1a.*/} do_execsql_test 1.3x { EXPLAIN QUERY PLAN SELECT c FROM t1 ORDER BY a; diff --git a/test/analyzeD.test b/test/analyzeD.test index 9c7ed7586..7a51785a1 100644 --- a/test/analyzeD.test +++ b/test/analyzeD.test @@ -63,7 +63,7 @@ do_test 1.1 { # do_eqp_test 1.2 { SELECT * FROM t1 WHERE a=3001 AND c=150; -} {SEARCH TABLE t1 USING INDEX t1_c (c=?)} +} {SEARCH t1 USING INDEX t1_c (c=?)} do_test 1.3 { execsql { DELETE FROM sqlite_stat1 } @@ -78,7 +78,7 @@ do_test 1.3 { # do_eqp_test 1.4 { SELECT * FROM t1 WHERE a=3001 AND c=150; -} {SEARCH TABLE t1 USING INDEX t1_ab (a=?)} +} {SEARCH t1 USING INDEX t1_ab (a=?)} do_test 1.5 { execsql { @@ -89,7 +89,7 @@ do_test 1.5 { do_eqp_test 1.6 { SELECT * FROM t1 WHERE a=13 AND c=150; -} {SEARCH TABLE t1 USING INDEX t1_c (c=?)} +} {SEARCH t1 USING INDEX t1_c (c=?)} do_test 1.7 { execsql { DELETE FROM sqlite_stat1 } @@ -102,6 +102,6 @@ do_test 1.7 { # gets this right, even without stat1 data. do_eqp_test 1.8 { SELECT * FROM t1 WHERE a=13 AND c=150; -} {SEARCH TABLE t1 USING INDEX t1_c (c=?)} +} {SEARCH t1 USING INDEX t1_c (c=?)} finish_test diff --git a/test/analyzeE.test b/test/analyzeE.test index 66db1e122..733b79367 100644 --- a/test/analyzeE.test +++ b/test/analyzeE.test @@ -36,47 +36,47 @@ do_execsql_test analyzeE-1.0 { do_execsql_test analyzeE-1.1 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 500 AND 2500; -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-1.2 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 2900 AND 3000; -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.3 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1700 AND 1750; -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.4 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1 AND 500 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.5 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 3000 AND 3000000 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.6 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<500 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.7 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>2500 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.8 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1900 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.9 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1100 -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-1.10 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1100 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-1.11 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1900 -} {/SCAN TABLE t1/} +} {/SCAN t1/} # Verify that everything works the same on a DESCENDING index. # @@ -88,47 +88,47 @@ do_execsql_test analyzeE-2.0 { do_execsql_test analyzeE-2.1 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 500 AND 2500; -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-2.2 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 2900 AND 3000; -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.3 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1700 AND 1750; -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.4 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1 AND 500 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.5 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 3000 AND 3000000 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.6 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<500 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.7 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>2500 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.8 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1900 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.9 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1100 -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-2.10 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1100 -} {/SEARCH TABLE t1 USING INDEX t1a/} +} {/SEARCH t1 USING INDEX t1a/} do_execsql_test analyzeE-2.11 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1900 -} {/SCAN TABLE t1/} +} {/SCAN t1/} # Now do a range query on the second term of an ASCENDING index # where the first term is constrained by equality. @@ -145,47 +145,47 @@ do_execsql_test analyzeE-3.0 { do_execsql_test analyzeE-3.1 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 500 AND 2500 AND c=123; -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-3.2 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 2900 AND 3000 AND c=123; -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.3 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1700 AND 1750 AND c=123; -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.4 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1 AND 500 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.5 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 3000 AND 3000000 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.6 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<500 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.7 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>2500 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.8 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1900 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.9 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1100 AND c=123 -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-3.10 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1100 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-3.11 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1900 AND c=123 -} {/SCAN TABLE t1/} +} {/SCAN t1/} # Repeat the 3.x tests using a DESCENDING index # @@ -197,46 +197,46 @@ do_execsql_test analyzeE-4.0 { do_execsql_test analyzeE-4.1 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 500 AND 2500 AND c=123; -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-4.2 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 2900 AND 3000 AND c=123; -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.3 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1700 AND 1750 AND c=123; -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.4 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 1 AND 500 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.5 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a BETWEEN 3000 AND 3000000 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.6 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<500 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.7 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>2500 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.8 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1900 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.9 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a>1100 AND c=123 -} {/SCAN TABLE t1/} +} {/SCAN t1/} do_execsql_test analyzeE-4.10 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1100 AND c=123 -} {/SEARCH TABLE t1 USING INDEX t1ca/} +} {/SEARCH t1 USING INDEX t1ca/} do_execsql_test analyzeE-4.11 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a<1900 AND c=123 -} {/SCAN TABLE t1/} +} {/SCAN t1/} finish_test diff --git a/test/analyzeF.test b/test/analyzeF.test index 143cecdf5..00107993e 100644 --- a/test/analyzeF.test +++ b/test/analyzeF.test @@ -62,7 +62,7 @@ foreach {tn where idx} { 11 "x = nullif('19', 0) AND y = nullif('4', 0)" {t1y (y=?)} 12 "x = nullif('4', 0) AND y = nullif('19', 0)" {t1y (y=?)} } { - set res "SEARCH TABLE t1 USING INDEX $idx" + set res "SEARCH t1 USING INDEX $idx" do_eqp_test 1.$tn "SELECT * FROM t1 WHERE $where" $res } @@ -92,7 +92,7 @@ foreach {tn where idx} { 3 "x = nondet4() AND y = nondet19()" {t1y (y=?)} 4 "x = nondet19() AND y = nondet4()" {t1y (y=?)} } { - set res "SEARCH TABLE t1 USING INDEX $idx" + set res "SEARCH t1 USING INDEX $idx" do_eqp_test 3.$tn "SELECT * FROM t1 WHERE $where" $res } diff --git a/test/analyzeG.test b/test/analyzeG.test index eb1853b1d..ca65bc3b9 100644 --- a/test/analyzeG.test +++ b/test/analyzeG.test @@ -20,16 +20,6 @@ ifcapable !stat4 { } set testprefix analyzeG -proc do_scan_order_test {tn sql expect} { - uplevel [list do_test $tn [subst -nocommands { - set res "" - db eval "explain query plan $sql" { - lappend res [set detail] - } - set res - }] [list {*}$expect]] -} - #------------------------------------------------------------------------- # Test cases 1.* seek to verify that even if an index is not used, its # stat4 data may be used by the planner to estimate the number of @@ -54,15 +44,17 @@ do_execsql_test 1.0 { # know this, so it has no preference as to which order the tables are # scanned in. In practice this means that tables are scanned in the order # they are specified in in the FROM clause. -do_scan_order_test 1.1.1 { +do_eqp_test 1.1.1 { SELECT * FROM t1, t2 WHERE a=44 AND b=44; } { - {SCAN TABLE t1} {SCAN TABLE t2} + } -do_scan_order_test 1.1.2 { +do_eqp_test 1.1.2 { SELECT * FROM t2, t1 WHERE a=44 AND b=44 } { - {SCAN TABLE t2} {SCAN TABLE t1} + QUERY PLAN + |--SCAN t2 + `--SCAN t1 } do_execsql_test 1.2 { @@ -73,15 +65,19 @@ do_execsql_test 1.2 { # Now, with the ANALYZE data, the planner knows that (b=44) matches a # large number of rows. So it elects to scan table "t1" first, regardless # of the order in which the tables are specified in the FROM clause. -do_scan_order_test 1.3.1 { +do_eqp_test 1.3.1 { SELECT * FROM t1, t2 WHERE a=44 AND b=44; } { - {SCAN TABLE t1} {SCAN TABLE t2} + QUERY PLAN + |--SCAN t1 + `--SCAN t2 } -do_scan_order_test 1.3.2 { +do_eqp_test 1.3.2 { SELECT * FROM t2, t1 WHERE a=44 AND b=44 } { - {SCAN TABLE t1} {SCAN TABLE t2} + QUERY PLAN + |--SCAN t1 + `--SCAN t2 } diff --git a/test/attach.test b/test/attach.test index 1e1d426a4..557201d65 100644 --- a/test/attach.test +++ b/test/attach.test @@ -910,4 +910,19 @@ do_execsql_test attach-12.1 { PRAGMA integrity_check; } {ok} +# 2021-03-10 Forum post https://sqlite.org/forum/forumpost/a006d86f72 +# +reset_db +do_test attach-13.1 { + sqlite3 db :memory: + db eval {CREATE TABLE base(x);} + for {set i 0} {$i<$SQLITE_MAX_ATTACHED} {incr i} { + db eval "ATTACH ':memory:' AS a$i" + } + set m "a[expr {$SQLITE_MAX_ATTACHED-1}]" + db eval "CREATE TABLE $m.t1(a INTEGER PRIMARY KEY, b);" + db eval "CREATE TABLE $m.t2(a INTEGER PRIMARY KEY, b);" + db eval {SELECT a FROM t1 WHERE b IN (SELECT a FROM t2);} +} {} + finish_test diff --git a/test/attach4.test b/test/attach4.test index 87911814a..9d54e7df5 100644 --- a/test/attach4.test +++ b/test/attach4.test @@ -135,4 +135,3 @@ do_execsql_test 2.2 { } finish_test - diff --git a/test/auth.test b/test/auth.test index cc48fdb9a..d8f23a15e 100644 --- a/test/auth.test +++ b/test/auth.test @@ -1391,9 +1391,23 @@ do_test auth-1.205 { } catchsql {DROP INDEX i2} } {1 {not authorized}} -do_test auth-1.206 { +do_test auth-1.205a { set ::authargs } {i2 t2 main {}} +db eval { + ATTACH ':memory:' as di205; + CREATE TABLE di205.t1(x); + CREATE INDEX di205.t1x ON t1(x); +} +do_catchsql_test auth-1.205b { + DROP INDEX di205.t1x; +} {1 {not authorized}} +db eval { + DETACH di205; +} +do_test auth-1.206 { + set ::authargs +} {t1x t1 di205 {}} do_test auth-1.207 { execsql {SELECT name FROM sqlite_master} } {t2 i2} diff --git a/test/autoindex1.test b/test/autoindex1.test index b08f8cfeb..6b437f186 100644 --- a/test/autoindex1.test +++ b/test/autoindex1.test @@ -183,18 +183,18 @@ do_eqp_test autoindex1-500.1 { WHERE t501.a IN (SELECT x FROM t502 WHERE y=?); } { QUERY PLAN - |--SEARCH TABLE t501 USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH t501 USING INTEGER PRIMARY KEY (rowid=?) `--LIST SUBQUERY xxxxxx - `--SCAN TABLE t502 + `--SCAN t502 } do_eqp_test autoindex1-501 { SELECT b FROM t501 WHERE t501.a IN (SELECT x FROM t502 WHERE y=t501.b); } { QUERY PLAN - |--SCAN TABLE t501 + |--SCAN t501 `--CORRELATED LIST SUBQUERY xxxxxx - `--SEARCH TABLE t502 USING AUTOMATIC COVERING INDEX (y=?) + `--SEARCH t502 USING AUTOMATIC COVERING INDEX (y=?) } do_eqp_test autoindex1-502 { SELECT b FROM t501 @@ -202,9 +202,9 @@ do_eqp_test autoindex1-502 { AND t501.a IN (SELECT x FROM t502 WHERE y=t501.b); } { QUERY PLAN - |--SEARCH TABLE t501 USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH t501 USING INTEGER PRIMARY KEY (rowid=?) `--CORRELATED LIST SUBQUERY xxxxxx - `--SCAN TABLE t502 + `--SCAN t502 } # The following code checks a performance regression reported on the @@ -277,13 +277,13 @@ do_eqp_test autoindex1-600a { ORDER BY x.registering_flock; } { QUERY PLAN - |--MATERIALIZE xxxxxx - | |--SCAN TABLE sheep AS s - | |--SEARCH TABLE flock_owner AS prev USING INDEX sqlite_autoindex_flock_owner_1 (flock_no=? AND owner_change_date? AND owner_change_date? AND owner_change_date?) -# 0|1|0|SEARCH TABLE u USING COVERING INDEX uab (ANY(a) AND b=?) +# 0|0|1|SEARCH v USING INDEX ve (e>?) +# 0|1|0|SEARCH u USING COVERING INDEX uab (ANY(a) AND b=?) # # on the basis that the real index "uab" must be better than the automatic # index. This is not right - a skip-scan is not necessarily better than an @@ -85,8 +85,8 @@ do_eqp_test 220 { select count(*) from u, v where u.b = v.b and v.e > 34; } { QUERY PLAN - |--SEARCH TABLE v USING INDEX ve (e>?) - `--SEARCH TABLE u USING AUTOMATIC COVERING INDEX (b=?) + |--SEARCH v USING INDEX ve (e>?) + `--SEARCH u USING AUTOMATIC COVERING INDEX (b=?) } diff --git a/test/autoindex5.test b/test/autoindex5.test index 94e220328..36695d7f7 100644 --- a/test/autoindex5.test +++ b/test/autoindex5.test @@ -102,7 +102,7 @@ do_eqp_test autoindex5-1.1 { AND ( sp.release = 'sid' OR sp.release = 'stretch' OR sp.release = 'jessie' OR sp.release = 'wheezy' OR sp.release = 'squeeze' ) ORDER BY sp.name, st.bug_name, sp.release, sp.subrelease; -} {SEARCH SUBQUERY * USING AUTOMATIC COVERING INDEX (bug_name=?)} +} {SEARCH debian_cve USING AUTOMATIC COVERING INDEX (bug_name=?)} #------------------------------------------------------------------------- # Test that ticket [8a2adec1] has been fixed. @@ -123,7 +123,7 @@ do_execsql_test 2.1 { SELECT sum(z) FROM vvv WHERE x='aaa' ) FROM one; } {8.0} -do_execsql_test 2.2 { +do_catchsql_test 2.2 { DROP TABLE t1; CREATE TABLE t1(aaa); INSERT INTO t1(aaa) VALUES(9); @@ -136,7 +136,7 @@ do_execsql_test 2.2 { ) WHERE bbb = 1 ) ); -} {9} +} {1 {no such column: rowid}} # Ticket https://www.sqlite.org/src/info/787fa716be3a7f65 # Segfault due to multiple uses of the same subquery where the diff --git a/test/avfs.test b/test/avfs.test new file mode 100644 index 000000000..2ebd608ba --- /dev/null +++ b/test/avfs.test @@ -0,0 +1,395 @@ +# 2021-03-06 +# +# 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 for the appendvfs extension. +# +# Tests performed: +# avfs-1.0. Test that an appendvfs DB can be added to an empty (ZLF) file. +# avfs-1.1. Test that the DB can be read with correct content upon reopen. +# avfs-1.2. Test that an appendvfs DB can be added to a simple text file. +# avfs-1.3. Test that the DB can be read with correct content upon reopen. +# avfs-1.4. Test that appended DB is aligned to default page boundary. +# avfs-2.1. Test that the simple text file retains its initial text. +# avfs-3.1. Test that the appendvfs can grow and shrink, remaining intact. +# avfs-3.2. Test that appendvfs is intact after grow/shrink/close/reopen. +# avfs-3.3. Test that appendvfs can grow by many pages and be written. +# avfs-3.4. Test that grown appendvfs can be reopened and appear intact. +# avfs-3.5. Test that much grown appendvfs can shrink and reopen intact. +# avfs-4.1. Test shell's ability to append to a non-appendvfs file. +# avfs-4.2. Test shell's ability to append to empty or nonexistent file. +# avfs-4.3. Test shell's ability to reopen and alter an appendvfs file. +# avfs-5.1. Test appendvfs refusal to open too-tiny DB appended onto ZLF. +# avfs-5.2. Test appendvfs refusal to open too-tiny DB appended on other. +# ... +# (more to come) + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix avfs + +# Do not attempt this test if SQLITE_OMIT_VIRTUALTABLE is defined. +# +ifcapable !vtab { + finish_test + return +} + +set CLI [test_find_cli] +db close +# forcedelete test.db + +load_static_extension db appendvfs + +set ::fa avfs.adb +set ::fza avfs.sdb +forcedelete $::fa $::fza +set ::result {} + +proc shellDoesAr {} { + set shdo "sh_app1.sql" + forcedelete $shdo + set fd [open $shdo w] + puts $fd ".help\n.q" + close $fd + set res [catchcmd "-batch -cmd \".read $shdo\""] + return [regexp {^.archive} [lindex $res 1]] +} + +set ::vf "&vfs=apndvfs" + +# Return file offset of appendvfs portion of a file, or {} if none such. +proc fosAvfs {fname} { + if {[file size $fname] < 25} { + return {} + } + if {[catch {set fd [open $fname rb]}]} { + return {} + } + seek $fd -25 end + set am [read $fd 17] + set ao [read $fd 8] + close $fd + if {$am ne "Start-Of-SQLite3-"} { + return {} + } + binary scan $ao "W" rvo + return $rvo +} + +do_test 1.0 { + set results {} + set out [open $::fza wb] + close $out + sqlite3 adb "file:$::fza?mode=rwc$::vf" -uri 1 + adb eval { + PRAGMA page_size=1024; + PRAGMA cache_size=10; + CREATE TABLE t1(a TEXT); + INSERT INTO t1 VALUES ('dog'),('cat'); + SELECT group_concat(a) as pets FROM (SELECT a FROM t1 ORDER BY a); + } { lappend results $pets } + adb close + lappend results [fosAvfs $fza] + set ::result [join $results " | "] +} {cat,dog | 0} + +do_test 1.1 { + set results {} + sqlite3 adb "file:$::fza?mode=rw$::vf" -uri 1 + adb eval { + SELECT group_concat(a) as pets FROM (SELECT a FROM t1 ORDER BY a DESC); + } { lappend results $pets } + adb close + set ::result [join $results " | "] +} {dog,cat} + +do_test 1.2 { + set results {} + set out [open $::fa wb] + set ::tlo { "Just some text," "and more text," "ending at 3 lines." } + puts $out [join $::tlo "\n"] + close $out + set adbSz [file size $::fa] + sqlite3 adb "file:$::fa?mode=rwc$::vf" -uri 1 + adb eval { + PRAGMA auto_vacuum = 0; + PRAGMA page_size=512; + PRAGMA cache_size=0; + CREATE TABLE t1(a TEXT); + INSERT INTO t1 VALUES ('dog'),('cat'),('pig'); + SELECT group_concat(a) as pets FROM (SELECT a FROM t1 ORDER BY a); + } { lappend results $pets } + adb close + set adaSz [file size $::fa] + lappend results "Bytes before/after $adbSz/$adaSz" + set ::result [join $results " | "] +} {cat,dog,pig | Bytes before/after 50/5145} + +do_test 1.3 { + set results {} + sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1 + adb eval { + SELECT group_concat(a) as pets FROM (SELECT a FROM t1 ORDER BY a DESC); + } { lappend results $pets } + adb close + set ::result [join $results " | "] +} {pig,dog,cat} + +do_test 1.4 { + set ::result [fosAvfs $fa] +} {4096} + +do_test 2.1 { + set in [open $::fa r] + set tli {} + for {set i [llength $::tlo]} {$i > 0} {incr i -1} { + lappend tli [gets $in] + } + close $in + if { [join $tli ":"] ne [join $::tlo ":"] } { + set ::result "Appendee changed." + } else { + set ::result "Appendee intact." + } +} {Appendee intact.} + +# Set of repeatable random integers for a couple tests. +set ::nrint 50000 +proc rint {v} { + return [::tcl::mathfunc::int [expr $v * 100000]] +} +array set ::randints [list 0 [rint [::tcl::mathfunc::srand 0]]] +for {set i 1} {$i < $::nrint} {incr i} { + set ::randints($i) [rint [::tcl::mathfunc::rand]] +} + +do_test 3.1 { + set results {} + sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1 + adb eval { + DROP TABLE t1; + PRAGMA cache_size=10; + CREATE TABLE ri (i INTEGER); + BEGIN; + } + for {set i 0} {$i < $::nrint} {incr i} { + set r $::randints($i) + set s $::randints([incr i]) + set t $::randints([incr i]) + set u $::randints([incr i]) + set v $::randints([incr i]) + adb eval { + INSERT INTO ri VALUES ($r),($s),($t),($u),($v) + } + } + adb eval { + COMMIT; + SELECT integrity_check as ic FROM pragma_integrity_check(); + } { lappend results $ic } + set adbSz [file size $::fa] + set qr {} + adb eval { + SELECT count(*) as ic FROM ri; + DELETE FROM ri WHERE (i % 50) <> 25; + SELECT integrity_check as ic FROM pragma_integrity_check(); + VACUUM; + SELECT integrity_check as ic FROM pragma_integrity_check(); + SELECT count(*) as ic FROM ri; + } { lappend qr $ic } + adb close + set adaSz [file size $::fa] + set adba [expr ($adbSz + 0.1)/$adaSz] + # lappend results $adba + set results [concat $results [lrange $qr 0 2]] + lappend results [expr {$adba > 10.0}] + set ::result [join $results " | "] +} "ok | $::nrint | ok | ok | 1" + +do_test 3.2 { + set results {} + sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1 + adb eval { + SELECT integrity_check as ic FROM pragma_integrity_check(); + } { lappend results $ic } + adb close + set ::result [join $results " | "] +} {ok} + +# avfs-3.3. Test that appendvfs can grow by many pages and be written. +do_test 3.3 { + set results {} + sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1 + set npages 300 + adb eval { BEGIN } + while {$npages > 0} { + adb eval { INSERT INTO ri VALUES (randomblob(1500)) } + incr npages -1 + } + adb eval { COMMIT } + adb eval { + SELECT integrity_check as ic FROM pragma_integrity_check(); + } { lappend results $ic } + adb close + set adaSzr [expr [file size $::fa] / 300.0 / 1500 ] + set okSzr [expr $adaSzr > 1.0 && $adaSzr < 1.3 ] + lappend results $okSzr + set ::result [join $results " | "] +} {ok | 1} + +# avfs-3.4. Test that grown appendvfs can be reopened and appear intact. +do_test 3.4 { + set results {} + sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1 + adb eval { + SELECT integrity_check as ic FROM pragma_integrity_check(); + } { lappend results $ic } + adb close + set ::result $ic +} {ok} + +# avfs-3.5. Test that much grown appendvfs can shrink and reopen intact. +do_test 3.5 { + set results {} + set adbsz [file size $::fa] + sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1 + adb eval { + DELETE FROM ri WHERE rowid % 8 <> 0; + SELECT integrity_check as ic FROM pragma_integrity_check(); + VACUUM; + SELECT integrity_check as ic FROM pragma_integrity_check(); + } { lappend results $ic } + adb close + set adasz [file size $::fa] + lappend results [expr {$adbsz/$adasz > 5}] + sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1 + adb eval { + SELECT integrity_check as ic FROM pragma_integrity_check(); + } { lappend results $ic } + adb close + set ::result [join $results " | "] +} {ok | ok | 1 | ok} + +set ::cliDoesAr [shellDoesAr] + +do_test 4.1 { + set shdo "sh_app1.sql" + set shod "sh_app1.adb" + forcedelete $shdo $shod + set ofd [open $shdo w] + if {$::cliDoesAr} { + puts $ofd ".ar -c" + } else { + puts $ofd "pragma page_size=512;" + puts $ofd "create table sqlar (a);" + } + puts $ofd ".tables" + puts $ofd ".q" + close $ofd + set ofd [open $shod wb] + puts $ofd "Some text." + close $ofd + set res [catchcmd "-append -batch -init $shdo $shod" ""] + lappend res [fosAvfs $shod] + forcedelete $shdo $shod + set ::result [join $res " | "] +} {0 | sqlar | 4096} + +do_test 4.2 { + set shdo "sh_app1.sql" + set shod "sh_app1.adb" + forcedelete $shdo $shod + set ofd [open $shdo w] + if {$::cliDoesAr} { + puts $ofd ".ar -c" + } else { + puts $ofd "pragma page_size=512;" + puts $ofd "create table sqlar (a);" + } + puts $ofd ".tables" + puts $ofd ".q" + close $ofd + set ofd [open $shod wb] + close $ofd + set res [catchcmd "-append -batch -init $shdo $shod" ""] + lappend res [fosAvfs $shod] + forcedelete $shdo ; # Leave $shod for next test. + set ::result [join $res " | "] +} {0 | sqlar | 0} + +do_test 4.3 { + set shdo "sh_app1.sql" + set shod "sh_app1.adb" ; # Same as test 4.2, reusing ADB. + forcedelete $shdo + set ofd [open $shdo w] + if {$::cliDoesAr} { + puts $ofd ".ar -u $shdo" + puts $ofd "select count(*) from sqlar where name = '$shdo';" + } else { + puts $ofd "insert into sqlar values (1);" + puts $ofd "select count(*) from sqlar;" + } + puts $ofd ".q" + close $ofd + set res [catchcmd "-append -batch -init $shdo $shod" ""] + sqlite3 adb "file:$shod?mode=rw$::vf" -uri 1 + adb eval { + SELECT count(*) as n FROM sqlar + } { lappend res $n } + adb close + forcedelete $shdo $shod; + set ::result [join $res " | "] +} {0 | 1 | 1} + +do_test 5.1 { + set fake "faketiny.sdb" + forcedelete $fake + set ofd [open $fake wb] + puts -nonewline $ofd "SQLite format 3" + puts -nonewline $ofd [binary format "c" 0] + puts -nonewline $ofd "Start-Of-SQLite3-" + puts -nonewline $ofd [binary format "W" 0] + close $ofd + if {[catch {sqlite3 adb "file:$fake?mode=rw$::vf" -uri 1}]} { + set res "Open failed." + } else { + adb close + set res "Opened when should not." + } + forcedelete $fake + set ::result $res +} {Open failed.} + +do_test 5.2 { + set fake "faketiny.sdb" + forcedelete $fake + set ofd [open $fake wb] + set fakeAppendee "Dog ate my homework.\n" + puts -nonewline $ofd $fakeAppendee + puts -nonewline $ofd "SQLite format 3" + puts -nonewline $ofd [binary format "c" 0] + puts -nonewline $ofd "Start-Of-SQLite3-" + puts -nonewline $ofd [binary format "W" [string length $fakeAppendee]] + close $ofd + if {[catch {sqlite3 adb "file:$fake?mode=rw$::vf" -uri 1}]} { + set res "Open failed." + } else { + adb close + set res "Opened when should not." + } + forcedelete $fake + set ::result $res +} {Open failed.} + +forcedelete $::fa $::fza + +unset -nocomplain ::fa ::fza ::tlo ::result ::randints ::nrint ::cliDoesAr + +finish_test diff --git a/test/backup2.test b/test/backup2.test index b58c6a3cd..1822e2dbf 100644 --- a/test/backup2.test +++ b/test/backup2.test @@ -144,7 +144,7 @@ do_test backup2-9 { if {$tcl_platform(platform)=="windows"} { set msg {cannot open source database: unable to open database file} } elseif {[string match *BSD $tcl_platform(os)]} { - set msg {restore failed: file is not a database} + set msg {} } else { set msg {cannot open source database: disk I/O error} } @@ -152,7 +152,8 @@ do_test backup2-10 { forcedelete bu3.db file mkdir bu3.db set rc [catch {db restore temp bu3.db} res] - lappend rc $res + if {[string match *BSD $tcl_platform(os)]} { set res "" } + list $rc $res } [list 1 $msg] # Try to restore from something that is not a database file. diff --git a/test/bestindex1.test b/test/bestindex1.test index b97af1114..0d6946994 100644 --- a/test/bestindex1.test +++ b/test/bestindex1.test @@ -51,11 +51,11 @@ do_execsql_test 1.0 { do_eqp_test 1.1 { SELECT * FROM x1 WHERE a = 'abc' -} {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!} +} {SCAN x1 VIRTUAL TABLE INDEX 555:eq!} do_eqp_test 1.2 { SELECT * FROM x1 WHERE a IN ('abc', 'def'); -} {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!} +} {SCAN x1 VIRTUAL TABLE INDEX 555:eq!} #------------------------------------------------------------------------- # @@ -141,17 +141,17 @@ foreach {tn mode} { set plan(use) { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%' + |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%' `--USE TEMP B-TREE FOR ORDER BY } set plan(omit) { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%' + |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%' `--USE TEMP B-TREE FOR ORDER BY } set plan(use2) { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x + |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x `--USE TEMP B-TREE FOR ORDER BY } diff --git a/test/bestindex2.test b/test/bestindex2.test index c17665f6f..81ed9ebf6 100644 --- a/test/bestindex2.test +++ b/test/bestindex2.test @@ -89,40 +89,40 @@ do_execsql_test 1.0 { do_eqp_test 1.1 { SELECT * FROM t1 WHERE a='abc' -} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)} +} {SCAN t1 VIRTUAL TABLE INDEX 0:indexed(a=?)} do_eqp_test 1.2 { SELECT * FROM t1 WHERE a='abc' AND b='def' -} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=? AND b=?)} +} {SCAN t1 VIRTUAL TABLE INDEX 0:indexed(a=? AND b=?)} do_eqp_test 1.3 { SELECT * FROM t1 WHERE a='abc' AND a='def' -} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)} +} {SCAN t1 VIRTUAL TABLE INDEX 0:indexed(a=?)} do_eqp_test 1.4 { SELECT * FROM t1,t2 WHERE c=a } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0: - `--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?) + |--SCAN t1 VIRTUAL TABLE INDEX 0: + `--SCAN t2 VIRTUAL TABLE INDEX 0:indexed(c=?) } do_eqp_test 1.5 { SELECT * FROM t1, t2 CROSS JOIN t3 WHERE t2.c = +t1.b AND t3.e=t2.d } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0: - |--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?) - `--SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?) + |--SCAN t1 VIRTUAL TABLE INDEX 0: + |--SCAN t2 VIRTUAL TABLE INDEX 0:indexed(c=?) + `--SCAN t3 VIRTUAL TABLE INDEX 0:indexed(e=?) } do_eqp_test 1.6 { SELECT * FROM t1, t2, t3 WHERE t2.c = +t1.b AND t3.e = t2.d } { QUERY PLAN - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0: - |--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?) - `--SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?) + |--SCAN t1 VIRTUAL TABLE INDEX 0: + |--SCAN t2 VIRTUAL TABLE INDEX 0:indexed(c=?) + `--SCAN t3 VIRTUAL TABLE INDEX 0:indexed(e=?) } do_execsql_test 1.7.1 { @@ -133,10 +133,10 @@ do_eqp_test 1.7.2 { WHERE t1.a = t2.c AND t1.b = t3.e } { QUERY PLAN - |--SCAN TABLE x1 - |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0: - |--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?) - `--SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?) + |--SCAN x1 + |--SCAN t1 VIRTUAL TABLE INDEX 0: + |--SCAN t2 VIRTUAL TABLE INDEX 0:indexed(c=?) + `--SCAN t3 VIRTUAL TABLE INDEX 0:indexed(e=?) } finish_test diff --git a/test/bestindex3.test b/test/bestindex3.test index 80038e285..1ee3975f8 100644 --- a/test/bestindex3.test +++ b/test/bestindex3.test @@ -79,11 +79,11 @@ do_execsql_test 1.0 { do_eqp_test 1.1 { SELECT * FROM t1 WHERE a LIKE 'abc'; -} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ?} +} {SCAN t1 VIRTUAL TABLE INDEX 0:a LIKE ?} do_eqp_test 1.2 { SELECT * FROM t1 WHERE a = 'abc'; -} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ?} +} {SCAN t1 VIRTUAL TABLE INDEX 0:a EQ ?} do_eqp_test 1.3 { SELECT * FROM t1 WHERE a = 'abc' OR b = 'def'; @@ -91,9 +91,9 @@ do_eqp_test 1.3 { QUERY PLAN `--MULTI-INDEX OR |--INDEX 1 - | `--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ? + | `--SCAN t1 VIRTUAL TABLE INDEX 0:a EQ ? `--INDEX 2 - `--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ? + `--SCAN t1 VIRTUAL TABLE INDEX 0:b EQ ? } do_eqp_test 1.4 { @@ -102,9 +102,9 @@ do_eqp_test 1.4 { QUERY PLAN `--MULTI-INDEX OR |--INDEX 1 - | `--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ? + | `--SCAN t1 VIRTUAL TABLE INDEX 0:a LIKE ? `--INDEX 2 - `--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ? + `--SCAN t1 VIRTUAL TABLE INDEX 0:b EQ ? } do_execsql_test 1.5 { @@ -155,9 +155,9 @@ ifcapable !icu { QUERY PLAN `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t2 USING INDEX t2x (x>? AND x? AND x? AND b? AND b? AND x? AND x? AND b? AND b? AND b? AND b=argc ){ + fprintf(stderr, + "--lookaside requires two arguments: slot-size num-slots\n"); + exit(1); + } + sz = atoi(argv[++i]); + nSlot = atoi(argv[++i]); + sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, nSlot); + continue; + } #ifndef _WIN32 if( strcmp(z,"max-stack")==0 || strcmp(z,"max-data")==0 diff --git a/test/delete.test b/test/delete.test index a448e52dd..c15ac53c6 100644 --- a/test/delete.test +++ b/test/delete.test @@ -100,7 +100,7 @@ do_test delete-5.2.1 { } {200} do_test delete-5.2.2 { execsql {DELETE FROM table1} -} {200} +} {202} do_test delete-5.2.3 { execsql {BEGIN TRANSACTION} for {set i 1} {$i<=200} {incr i} { diff --git a/test/delete4.test b/test/delete4.test index f64778a16..8d6a1b8c7 100644 --- a/test/delete4.test +++ b/test/delete4.test @@ -59,14 +59,22 @@ do_execsql_test 2.3 { #------------------------------------------------------------------------- # reset_db -do_execsql_test 3.1 { +do_execsql_test 3.0.1 { CREATE TABLE t1(a, b, PRIMARY KEY(a, b)) WITHOUT ROWID; INSERT INTO t1 VALUES(1, 2); INSERT INTO t1 VALUES(2, 4); INSERT INTO t1 VALUES(1, 5); DELETE FROM t1 WHERE a=1; + SELECT printf('(%d)',changes()); SELECT * FROM t1; -} {2 4} +} {(2) 2 4} +do_execsql_test 3.0.2 { + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) + INSERT INTO t1(a,b) SELECT x, x+1 FROM c; + SELECT printf('(%d)',changes()); + DELETE FROM t1; + SELECT printf('(%d)',changes()); +} {(100) (101)} #------------------------------------------------------------------------- # DELETE statement that uses the OR optimization diff --git a/test/distinct.test b/test/distinct.test index f6f3c6cdc..a3cb1afc8 100644 --- a/test/distinct.test +++ b/test/distinct.test @@ -30,12 +30,11 @@ proc is_distinct_noop {sql} { set program1 [list] set program2 [list] db eval "EXPLAIN $sql1" { - if {$opcode != "Noop"} { lappend program1 $opcode } + if {$opcode != "Noop" && $opcode != "Explain"} { lappend program1 $opcode } } db eval "EXPLAIN $sql2" { - if {$opcode != "Noop"} { lappend program2 $opcode } + if {$opcode != "Noop" && $opcode != "Explain"} { lappend program2 $opcode } } - return [expr {$program1==$program2}] } @@ -128,7 +127,6 @@ foreach {tn noop sql} { 21 0 "SELECT DISTINCT c2 FROM t3" 22 0 "SELECT DISTINCT * FROM (SELECT 1, 2, 3 UNION SELECT 4, 5, 6)" - 23 1 "SELECT DISTINCT rowid FROM (SELECT 1, 2, 3 UNION SELECT 4, 5, 6)" 24 0 "SELECT DISTINCT rowid/2 FROM t1" 25 1 "SELECT DISTINCT rowid/2, rowid FROM t1" @@ -294,4 +292,13 @@ do_execsql_test 7.1 { WHERE (t1.a=t3.a) AND (SELECT count(*) FROM t2 AS y WHERE t4.x!='abc')=t1.a } {2 2 2} +# 2021-04-06 forum post https://sqlite.org/forum/forumpost/66954e9ece +reset_db +do_execsql_test 8.0 { + CREATE TABLE person ( pid INT) ; + CREATE UNIQUE INDEX idx ON person ( pid ) WHERE pid == 1; + INSERT INTO person VALUES (1), (10), (10); + SELECT DISTINCT pid FROM person where pid = 10; +} {10} + finish_test diff --git a/test/distinctagg.test b/test/distinctagg.test index 9b5dc2167..a34312ef9 100644 --- a/test/distinctagg.test +++ b/test/distinctagg.test @@ -16,6 +16,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix distinctagg do_test distinctagg-1.1 { execsql { @@ -31,7 +32,7 @@ do_test distinctagg-1.1 { } {1 2 3 3} do_test distinctagg-1.2 { execsql { - SELECT b, count(distinct c) FROM t1 GROUP BY b ORDER BY b + SELECT b, count(distinct c) FROM t1 GROUP BY b } } {2 1 3 2} do_test distinctagg-1.3 { @@ -59,4 +60,158 @@ do_test distinctagg-2.2 { } } {1 {DISTINCT aggregates must have exactly one argument}} +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(a, b, c); + CREATE TABLE t2(d, e, f); + + INSERT INTO t1 VALUES (1, 1, 1); + INSERT INTO t1 VALUES (2, 2, 2); + INSERT INTO t1 VALUES (3, 3, 3); + INSERT INTO t1 VALUES (4, 1, 4); + INSERT INTO t1 VALUES (5, 2, 1); + INSERT INTO t1 VALUES (5, 3, 2); + INSERT INTO t1 VALUES (4, 1, 3); + INSERT INTO t1 VALUES (3, 2, 4); + INSERT INTO t1 VALUES (2, 3, 1); + INSERT INTO t1 VALUES (1, 1, 2); + + INSERT INTO t2 VALUES('a', 'a', 'a'); + INSERT INTO t2 VALUES('b', 'b', 'b'); + INSERT INTO t2 VALUES('c', 'c', 'c'); + + CREATE INDEX t1a ON t1(a); + CREATE INDEX t1bc ON t1(b, c); +} + +foreach {tn use_eph sql res} { + 1 0 "SELECT count(DISTINCT a) FROM t1" 5 + 2 0 "SELECT count(DISTINCT b) FROM t1" 3 + 3 1 "SELECT count(DISTINCT c) FROM t1" 4 + 4 0 "SELECT count(DISTINCT c) FROM t1 WHERE b=3" 3 + 5 0 "SELECT count(DISTINCT rowid) FROM t1" 10 + 6 0 "SELECT count(DISTINCT a) FROM t1, t2" 5 + 7 0 "SELECT count(DISTINCT a) FROM t2, t1" 5 + 8 1 "SELECT count(DISTINCT a+b) FROM t1, t2, t2, t2" 6 + 9 0 "SELECT count(DISTINCT c) FROM t1 WHERE c=2" 1 + 10 1 "SELECT count(DISTINCT t1.rowid) FROM t1, t2" 10 +} { + do_test 3.$tn.1 { + set prg [db eval "EXPLAIN $sql"] + set idx [lsearch $prg OpenEphemeral] + expr {$idx>=0} + } $use_eph + + do_execsql_test 3.$tn.2 $sql $res +} + +do_execsql_test 3.10 { + SELECT a, count(DISTINCT b) FROM t1 GROUP BY a; +} { + 1 1 2 2 3 2 4 1 5 2 +} + +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(a, b, c); + CREATE INDEX t1a ON t1(a); + CREATE INDEX t1bc ON t1(b, c); + + INSERT INTO t1 VALUES(1, 'A', 1); + INSERT INTO t1 VALUES(1, 'A', 1); + INSERT INTO t1 VALUES(2, 'A', 2); + INSERT INTO t1 VALUES(2, 'A', 2); + INSERT INTO t1 VALUES(1, 'B', 1); + INSERT INTO t1 VALUES(2, 'B', 2); + INSERT INTO t1 VALUES(3, 'B', 3); + INSERT INTO t1 VALUES(NULL, 'B', NULL); + INSERT INTO t1 VALUES(NULL, 'C', NULL); + INSERT INTO t1 VALUES('d', 'D', 'd'); + + CREATE TABLE t2(d, e, f); + CREATE INDEX t2def ON t2(d, e, f); + + INSERT INTO t2 VALUES(1, 1, 'a'); + INSERT INTO t2 VALUES(1, 1, 'a'); + INSERT INTO t2 VALUES(1, 2, 'a'); + INSERT INTO t2 VALUES(1, 2, 'a'); + INSERT INTO t2 VALUES(1, 2, 'b'); + INSERT INTO t2 VALUES(1, 3, 'b'); + INSERT INTO t2 VALUES(1, 3, 'a'); + INSERT INTO t2 VALUES(1, 3, 'b'); + INSERT INTO t2 VALUES(2, 3, 'x'); + INSERT INTO t2 VALUES(2, 3, 'y'); + INSERT INTO t2 VALUES(2, 3, 'z'); + + CREATE TABLE t3(x, y, z); + INSERT INTO t3 VALUES(1,1,1); + INSERT INTO t3 VALUES(2,2,2); +} + +foreach {tn use_eph sql res} { + 1 0 "SELECT count(DISTINCT c) FROM t1 GROUP BY b" {2 3 0 1} + 2 1 "SELECT count(DISTINCT a) FROM t1 GROUP BY b" {2 3 0 1} + 3 1 "SELECT count(DISTINCT a) FROM t1 GROUP BY b+c" {0 1 1 1 1} + + 4 0 "SELECT count(DISTINCT f) FROM t2 GROUP BY d, e" {1 2 2 3} + 5 1 "SELECT count(DISTINCT f) FROM t2 GROUP BY d" {2 3} + 6 0 "SELECT count(DISTINCT f) FROM t2 WHERE d IS 1 GROUP BY e" {1 2 2} +} { + do_test 4.$tn.1 { + set prg [db eval "EXPLAIN $sql"] + set idx [lsearch $prg OpenEphemeral] + expr {$idx>=0} + } $use_eph + + do_execsql_test 4.$tn.2 $sql $res +} + + +set t3root [db one {SELECT rootpage FROM sqlite_schema WHERE name='t3'}] +foreach {tn use_t3 sql res} { + 1 1 "SELECT count(*) FROM t3" 2 + 2 0 "SELECT count(*) FROM t1" 10 + 2 1 "SELECT count(DISTINCT a) FROM t1, t3" 4 + 3 1 "SELECT count(DISTINCT a) FROM t1 LEFT JOIN t3" 4 + 4 1 "SELECT count(DISTINCT a) FROM t1 LEFT JOIN t3 WHERE t3.x=1" 4 + 5 1 "SELECT count(DISTINCT a) FROM t1 LEFT JOIN t3 WHERE t3.x=0" 0 + 6 1 "SELECT count(DISTINCT a) FROM t1 LEFT JOIN t3 ON (t3.x=0)" 4 + 7 1 "SELECT count(DISTINCT x) FROM t1 LEFT JOIN t3" 2 + 8 1 "SELECT count(DISTINCT x) FROM t1 LEFT JOIN t3 WHERE t3.x=1" 1 + 9 1 "SELECT count(DISTINCT x) FROM t1 LEFT JOIN t3 WHERE t3.x=0" 0 + 10 1 "SELECT count(DISTINCT x) FROM t1 LEFT JOIN t3 ON (t3.x=0)" 0 + +} { + do_test 5.$tn.1 { + set bUse 0 + db eval "EXPLAIN $sql" a { + if {$a(opcode)=="OpenRead" && $a(p2)==$t3root} {set bUse 1} + } + set bUse + } $use_t3 + + do_execsql_test 5.$tn.2 $sql $res +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, d); + INSERT INTO t1 VALUES(123,456); + INSERT INTO t2 VALUES(123,456); +} +do_execsql_test 6.1 { + SELECT count(DISTINCT c) FROM t1 LEFT JOIN t2; +} {1} + +do_execsql_test 7.0 { + CREATE TABLE v1 ( v2 UNIQUE, v3 AS( TYPEOF ( NULL ) ) UNIQUE ); + SELECT COUNT ( DISTINCT TRUE ) FROM v1 GROUP BY likelihood ( v3 , 0.100000 ); +} + + finish_test + diff --git a/test/e_createtable.test b/test/e_createtable.test index e83e5cca1..c2cd1bc2c 100644 --- a/test/e_createtable.test +++ b/test/e_createtable.test @@ -1393,13 +1393,13 @@ do_execsql_test 4.10.0 { } do_createtable_tests 4.10 { 1 "EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b = 5" - {/*SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (b=?)*/} + {/*SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (b=?)*/} 2 "EXPLAIN QUERY PLAN SELECT * FROM t2 ORDER BY b, c" - {/*SCAN TABLE t2 USING INDEX sqlite_autoindex_t2_1*/} + {/*SCAN t2 USING INDEX sqlite_autoindex_t2_1*/} 3 "EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE b=10 AND c>10" - {/*SEARCH TABLE t2 USING INDEX sqlite_autoindex_t2_1 (b=? AND c>?)*/} + {/*SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (b=? AND c>?)*/} } # EVIDENCE-OF: R-45493-35653 A CHECK constraint may be attached to a diff --git a/test/e_fkey.test b/test/e_fkey.test index c5ac5fde3..e5eb0d49c 100644 --- a/test/e_fkey.test +++ b/test/e_fkey.test @@ -992,15 +992,15 @@ do_detail_test e_fkey-25.2 { EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1; EXPLAIN QUERY PLAN SELECT rowid FROM track WHERE trackartist = ?; } { - {SCAN TABLE artist} - {SCAN TABLE track} + {SCAN artist} + {SCAN track} } do_detail_test e_fkey-25.3 { PRAGMA foreign_keys = ON; EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1; } { - {SCAN TABLE artist} - {SCAN TABLE track} + {SCAN artist} + {SCAN track} } do_test e_fkey-25.4 { execsql { @@ -1117,15 +1117,15 @@ do_test e_fkey-27.2 { do_detail_test e_fkey-27.3 { EXPLAIN QUERY PLAN UPDATE artist SET artistid = ?, artistname = ? } { - {SCAN TABLE artist} - {SEARCH TABLE track USING COVERING INDEX trackindex (trackartist=?)} - {SEARCH TABLE track USING COVERING INDEX trackindex (trackartist=?)} + {SCAN artist} + {SEARCH track USING COVERING INDEX trackindex (trackartist=?)} + {SEARCH track USING COVERING INDEX trackindex (trackartist=?)} } do_detail_test e_fkey-27.4 { EXPLAIN QUERY PLAN DELETE FROM artist } { - {SCAN TABLE artist} - {SEARCH TABLE track USING COVERING INDEX trackindex (trackartist=?)} + {SCAN artist} + {SEARCH track USING COVERING INDEX trackindex (trackartist=?)} } ########################################################################### @@ -2055,9 +2055,9 @@ do_test e_fkey-44.5 { #------------------------------------------------------------------------- # Test SET DEFAULT actions. # -# EVIDENCE-OF: R-43054-54832 The "SET DEFAULT" actions are similar to +# EVIDENCE-OF: R-55814-22637 The "SET DEFAULT" actions are similar to # "SET NULL", except that each of the child key columns is set to -# contain the columns default value instead of NULL. +# contain the column's default value instead of NULL. # drop_all_tables do_test e_fkey-45.1 { diff --git a/test/e_select.test b/test/e_select.test index c2fdc9ad9..a4e74d30e 100644 --- a/test/e_select.test +++ b/test/e_select.test @@ -1004,12 +1004,13 @@ do_execsql_test e_select-4.9.0 { INSERT INTO b3 VALUES('dEF', 'dEF'); } {} -# EVIDENCE-OF: R-07284-35990 If the SELECT statement is an aggregate +# EVIDENCE-OF: R-40855-36147 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 get assigned to the same group. +# dataset according to the processing rules stated below for ORDER BY +# expressions. 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 get assigned to the same group. # # These tests also show that the following is not untrue: # diff --git a/test/eqp.test b/test/eqp.test index f931f8f34..19d6ea91a 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -46,111 +46,131 @@ do_eqp_test 1.2 { QUERY PLAN |--MULTI-INDEX OR | |--INDEX 1 - | | `--SEARCH TABLE t1 USING INDEX i1 (a=?) + | | `--SEARCH t1 USING INDEX i1 (a=?) | `--INDEX 2 - | `--SEARCH TABLE t1 USING INDEX i2 (b=?) - `--SCAN TABLE t2 + | `--SEARCH t1 USING INDEX i2 (b=?) + `--SCAN t2 } do_eqp_test 1.3 { SELECT * FROM t2 CROSS JOIN t1 WHERE t1.a=1 OR t1.b=2; } { QUERY PLAN - |--SCAN TABLE t2 + |--SCAN t2 `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t1 USING INDEX i1 (a=?) + | `--SEARCH t1 USING INDEX i1 (a=?) `--INDEX 2 - `--SEARCH TABLE t1 USING INDEX i2 (b=?) + `--SEARCH t1 USING INDEX i2 (b=?) } do_eqp_test 1.3 { SELECT a FROM t1 ORDER BY a } { QUERY PLAN - `--SCAN TABLE t1 USING COVERING INDEX i1 + `--SCAN t1 USING COVERING INDEX i1 } do_eqp_test 1.4 { SELECT a FROM t1 ORDER BY +a } { QUERY PLAN - |--SCAN TABLE t1 USING COVERING INDEX i1 + |--SCAN t1 USING COVERING INDEX i1 `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 1.5 { SELECT a FROM t1 WHERE a=4 } { QUERY PLAN - `--SEARCH TABLE t1 USING COVERING INDEX i1 (a=?) + `--SEARCH t1 USING COVERING INDEX i1 (a=?) } do_eqp_test 1.6 { SELECT DISTINCT count(*) FROM t3 GROUP BY a; } { QUERY PLAN - |--SCAN TABLE t3 + |--SCAN t3 |--USE TEMP B-TREE FOR GROUP BY `--USE TEMP B-TREE FOR DISTINCT } -do_eqp_test 1.7 { +do_eqp_test 1.7.1 { SELECT * FROM t3 JOIN (SELECT 1) } { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE SUBQUERY xxxxxx | `--SCAN CONSTANT ROW |--SCAN SUBQUERY xxxxxx - `--SCAN TABLE t3 + `--SCAN t3 } +do_eqp_test 1.7.2 { + SELECT * FROM t3 JOIN (SELECT 1) AS v1 +} { + QUERY PLAN + |--MATERIALIZE v1 + | `--SCAN CONSTANT ROW + |--SCAN v1 + `--SCAN t3 +} +do_eqp_test 1.7.3 { + SELECT * FROM t3 AS xx JOIN (SELECT 1) AS yy +} { + QUERY PLAN + |--MATERIALIZE yy + | `--SCAN CONSTANT ROW + |--SCAN yy + `--SCAN xx +} + + do_eqp_test 1.8 { SELECT * FROM t3 JOIN (SELECT 1 UNION SELECT 2) } { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE SUBQUERY xxxxxx | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--UNION USING TEMP B-TREE | `--SCAN CONSTANT ROW |--SCAN SUBQUERY xxxxxx - `--SCAN TABLE t3 + `--SCAN t3 } do_eqp_test 1.9 { - SELECT * FROM t3 JOIN (SELECT 1 EXCEPT SELECT a FROM t3 LIMIT 17) + SELECT * FROM t3 JOIN (SELECT 1 EXCEPT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--EXCEPT USING TEMP B-TREE - | `--SCAN TABLE t3 - |--SCAN SUBQUERY xxxxxx - `--SCAN TABLE t3 + | `--SCAN t3 + |--SCAN abc + `--SCAN t3 } do_eqp_test 1.10 { - SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17) + SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--INTERSECT USING TEMP B-TREE - | `--SCAN TABLE t3 - |--SCAN SUBQUERY xxxxxx - `--SCAN TABLE t3 + | `--SCAN t3 + |--SCAN abc + `--SCAN t3 } do_eqp_test 1.11 { - SELECT * FROM t3 JOIN (SELECT 1 UNION ALL SELECT a FROM t3 LIMIT 17) + SELECT * FROM t3 JOIN (SELECT 1 UNION ALL SELECT a FROM t3 LIMIT 17) abc } { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--UNION ALL - | `--SCAN TABLE t3 - |--SCAN SUBQUERY xxxxxx - `--SCAN TABLE t3 + | `--SCAN t3 + |--SCAN abc + `--SCAN t3 } #------------------------------------------------------------------------- @@ -166,57 +186,57 @@ do_execsql_test 2.1 { det 2.2.1 "SELECT DISTINCT min(x), max(x) FROM t1 GROUP BY x ORDER BY 1" { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 |--USE TEMP B-TREE FOR GROUP BY |--USE TEMP B-TREE FOR DISTINCT `--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" { QUERY PLAN - |--SCAN TABLE t2 USING COVERING INDEX t2i1 + |--SCAN t2 USING COVERING INDEX t2i1 |--USE TEMP B-TREE FOR DISTINCT `--USE TEMP B-TREE FOR ORDER BY } det 2.2.3 "SELECT DISTINCT * FROM t1" { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--USE TEMP B-TREE FOR DISTINCT } det 2.2.4 "SELECT DISTINCT * FROM t1, t2" { QUERY PLAN - |--SCAN TABLE t1 - |--SCAN TABLE t2 + |--SCAN t1 + |--SCAN t2 `--USE TEMP B-TREE FOR DISTINCT } det 2.2.5 "SELECT DISTINCT * FROM t1, t2 ORDER BY t1.x" { QUERY PLAN - |--SCAN TABLE t1 - |--SCAN TABLE t2 + |--SCAN t1 + |--SCAN t2 |--USE TEMP B-TREE FOR DISTINCT `--USE TEMP B-TREE FOR ORDER BY } det 2.2.6 "SELECT DISTINCT t2.x FROM t1, t2 ORDER BY t2.x" { QUERY PLAN - |--SCAN TABLE t2 USING COVERING INDEX t2i1 - `--SCAN TABLE t1 + |--SCAN t2 USING COVERING INDEX t2i1 + `--SCAN t1 } det 2.3.1 "SELECT max(x) FROM t2" { QUERY PLAN - `--SEARCH TABLE t2 USING COVERING INDEX t2i1 + `--SEARCH t2 USING COVERING INDEX t2i1 } det 2.3.2 "SELECT min(x) FROM t2" { QUERY PLAN - `--SEARCH TABLE t2 USING COVERING INDEX t2i1 + `--SEARCH t2 USING COVERING INDEX t2i1 } det 2.3.3 "SELECT min(x), max(x) FROM t2" { QUERY PLAN - `--SCAN TABLE t2 USING COVERING INDEX t2i1 + `--SCAN t2 USING COVERING INDEX t2i1 } det 2.4.1 "SELECT * FROM t1 WHERE rowid=?" { QUERY PLAN - `--SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?) + `--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?) } @@ -228,42 +248,42 @@ do_eqp_test 3.1.1 { SELECT (SELECT x FROM t1 AS sub) FROM t1; } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--SCALAR SUBQUERY xxxxxx - `--SCAN TABLE t1 AS sub + `--SCAN sub } do_eqp_test 3.1.2 { SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub); } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--SCALAR SUBQUERY xxxxxx - `--SCAN TABLE t1 AS sub + `--SCAN sub } do_eqp_test 3.1.3 { SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub ORDER BY y); } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--SCALAR SUBQUERY xxxxxx - |--SCAN TABLE t1 AS sub + |--SCAN sub `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 3.1.4 { SELECT * FROM t1 WHERE (SELECT x FROM t2 ORDER BY x); } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--SCALAR SUBQUERY xxxxxx - `--SCAN TABLE t2 USING COVERING INDEX t2i1 + `--SCAN t2 USING COVERING INDEX t2i1 } det 3.2.1 { SELECT * FROM (SELECT * FROM t1 ORDER BY x LIMIT 10) ORDER BY y LIMIT 5 } { QUERY PLAN - |--CO-ROUTINE xxxxxx - | |--SCAN TABLE t1 + |--CO-ROUTINE SUBQUERY xxxxxx + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY |--SCAN SUBQUERY xxxxxx `--USE TEMP B-TREE FOR ORDER BY @@ -275,13 +295,13 @@ det 3.2.2 { ORDER BY x2.y LIMIT 5 } { QUERY PLAN - |--MATERIALIZE xxxxxx - | |--SCAN TABLE t1 + |--MATERIALIZE x1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY - |--MATERIALIZE xxxxxx - | `--SCAN TABLE t2 USING INDEX t2i1 - |--SCAN SUBQUERY xxxxxx AS x1 - |--SCAN SUBQUERY xxxxxx AS x2 + |--MATERIALIZE x2 + | `--SCAN t2 USING INDEX t2i1 + |--SCAN x1 + |--SCAN x2 `--USE TEMP B-TREE FOR ORDER BY } @@ -289,25 +309,25 @@ det 3.3.1 { SELECT * FROM t1 WHERE y IN (SELECT y FROM t2) } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--LIST SUBQUERY xxxxxx - `--SCAN TABLE t2 + `--SCAN t2 } det 3.3.2 { SELECT * FROM t1 WHERE y IN (SELECT y FROM t2 WHERE t1.x!=t2.x) } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--CORRELATED LIST SUBQUERY xxxxxx - `--SCAN TABLE t2 + `--SCAN t2 } det 3.3.3 { SELECT * FROM t1 WHERE EXISTS (SELECT y FROM t2 WHERE t1.x!=t2.x) } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--CORRELATED SCALAR SUBQUERY xxxxxx - `--SCAN TABLE t2 + `--SCAN t2 } #------------------------------------------------------------------------- @@ -319,9 +339,9 @@ do_eqp_test 4.1.1 { QUERY PLAN `--COMPOUND QUERY |--LEFT-MOST SUBQUERY - | `--SCAN TABLE t1 + | `--SCAN t1 `--UNION ALL - `--SCAN TABLE t2 + `--SCAN t2 } do_eqp_test 4.1.2 { SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 2 @@ -329,10 +349,10 @@ do_eqp_test 4.1.2 { QUERY PLAN `--MERGE (UNION ALL) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - |--SCAN TABLE t2 + |--SCAN t2 `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 4.1.3 { @@ -341,10 +361,10 @@ do_eqp_test 4.1.3 { QUERY PLAN `--MERGE (UNION) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - |--SCAN TABLE t2 + |--SCAN t2 `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 4.1.4 { @@ -353,10 +373,10 @@ do_eqp_test 4.1.4 { QUERY PLAN `--MERGE (INTERSECT) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - |--SCAN TABLE t2 + |--SCAN t2 `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 4.1.5 { @@ -365,10 +385,10 @@ do_eqp_test 4.1.5 { QUERY PLAN `--MERGE (EXCEPT) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - |--SCAN TABLE t2 + |--SCAN t2 `--USE TEMP B-TREE FOR ORDER BY } @@ -378,10 +398,10 @@ do_eqp_test 4.2.2 { QUERY PLAN `--MERGE (UNION ALL) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - `--SCAN TABLE t2 USING INDEX t2i1 + `--SCAN t2 USING INDEX t2i1 } do_eqp_test 4.2.3 { SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 1 @@ -389,10 +409,10 @@ do_eqp_test 4.2.3 { QUERY PLAN `--MERGE (UNION) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - |--SCAN TABLE t2 USING INDEX t2i1 + |--SCAN t2 USING INDEX t2i1 `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY } do_eqp_test 4.2.4 { @@ -401,10 +421,10 @@ do_eqp_test 4.2.4 { QUERY PLAN `--MERGE (INTERSECT) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - |--SCAN TABLE t2 USING INDEX t2i1 + |--SCAN t2 USING INDEX t2i1 `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY } do_eqp_test 4.2.5 { @@ -413,10 +433,10 @@ do_eqp_test 4.2.5 { QUERY PLAN `--MERGE (EXCEPT) |--LEFT - | |--SCAN TABLE t1 + | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT - |--SCAN TABLE t2 USING INDEX t2i1 + |--SCAN t2 USING INDEX t2i1 `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY } @@ -426,9 +446,9 @@ do_eqp_test 4.3.1 { QUERY PLAN `--COMPOUND QUERY |--LEFT-MOST SUBQUERY - | `--SCAN TABLE t1 + | `--SCAN t1 `--UNION USING TEMP B-TREE - `--SCAN TABLE t2 USING COVERING INDEX t2i1 + `--SCAN t2 USING COVERING INDEX t2i1 } do_eqp_test 4.3.2 { @@ -437,11 +457,11 @@ do_eqp_test 4.3.2 { QUERY PLAN `--COMPOUND QUERY |--LEFT-MOST SUBQUERY - | `--SCAN TABLE t1 + | `--SCAN t1 |--UNION USING TEMP B-TREE - | `--SCAN TABLE t2 USING COVERING INDEX t2i1 + | `--SCAN t2 USING COVERING INDEX t2i1 `--UNION USING TEMP B-TREE - `--SCAN TABLE t1 + `--SCAN t1 } do_eqp_test 4.3.3 { SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 ORDER BY 1 @@ -451,12 +471,12 @@ do_eqp_test 4.3.3 { |--LEFT | `--MERGE (UNION) | |--LEFT - | | |--SCAN TABLE t1 + | | |--SCAN t1 | | `--USE TEMP B-TREE FOR ORDER BY | `--RIGHT - | `--SCAN TABLE t2 USING COVERING INDEX t2i1 + | `--SCAN t2 USING COVERING INDEX t2i1 `--RIGHT - |--SCAN TABLE t1 + |--SCAN t1 `--USE TEMP B-TREE FOR ORDER BY } @@ -469,145 +489,145 @@ drop_all_tables # XVIDENCE-OF: R-47779-47605 sqlite> EXPLAIN QUERY PLAN SELECT a, b # FROM t1 WHERE a=1; -# 0|0|0|SCAN TABLE t1 +# 0|0|0|SCAN t1 # do_execsql_test 5.1.0 { CREATE TABLE t1(a INT, b INT, ex TEXT) } det 5.1.1 "SELECT a, b FROM t1 WHERE a=1" { - 0 0 0 {SCAN TABLE t1} + 0 0 0 {SCAN t1} } # XVIDENCE-OF: R-55852-17599 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 +# 0|0|0|SEARCH t1 USING INDEX i1 # 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=?)} + 0 0 0 {SEARCH t1 USING INDEX i1 (a=?)} } # XVIDENCE-OF: R-21179-11011 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=?) +# 0|0|0|SEARCH t1 USING COVERING INDEX i2 (a=?) # 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=?)} + 0 0 0 {SEARCH t1 USING COVERING INDEX i2 (a=?)} } # XVIDENCE-OF: R-09991-48941 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>?) -# 0|1|1|SCAN TABLE t2 +# 0|0|0|SEARCH t1 USING COVERING INDEX i2 (a=? AND b>?) +# 0|1|1|SCAN t2 # do_execsql_test 5.4.0 {CREATE TABLE t2(c INT, d INT, ex TEXT)} det 5.4.1 "SELECT t1.a, t2.c FROM t1, t2 WHERE t1.a=1 AND t1.b>2" { - 0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?)} - 0 1 1 {SCAN TABLE t2} + 0 0 0 {SEARCH t1 USING COVERING INDEX i2 (a=? AND b>?)} + 0 1 1 {SCAN t2} } # XVIDENCE-OF: R-33626-61085 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>?) -# 0|1|0|SCAN TABLE t2 +# 0|0|1|SEARCH t1 USING COVERING INDEX i2 (a=? AND b>?) +# 0|1|0|SCAN t2 # det 5.5 "SELECT t1.a, t2.c FROM t2, t1 WHERE t1.a=1 AND t1.b>2" { - 0 0 1 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?)} - 0 1 0 {SCAN TABLE t2} + 0 0 1 {SEARCH t1 USING COVERING INDEX i2 (a=? AND b>?)} + 0 1 0 {SCAN t2} } # XVIDENCE-OF: R-04002-25654 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=?) -# 0|0|0|SEARCH TABLE t1 USING INDEX i3 (b=?) +# 0|0|0|SEARCH t1 USING COVERING INDEX i2 (a=?) +# 0|0|0|SEARCH t1 USING INDEX i3 (b=?) # do_execsql_test 5.5.0 {CREATE INDEX i3 ON t1(b)} det 5.6.1 "SELECT a, b FROM t1 WHERE a=1 OR b=2" { - 0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)} - 0 0 0 {SEARCH TABLE t1 USING INDEX i3 (b=?)} + 0 0 0 {SEARCH t1 USING COVERING INDEX i2 (a=?)} + 0 0 0 {SEARCH t1 USING INDEX i3 (b=?)} } # XVIDENCE-OF: R-24577-38891 sqlite> EXPLAIN QUERY PLAN # SELECT c, d FROM t2 ORDER BY c; -# 0|0|0|SCAN TABLE t2 +# 0|0|0|SCAN t2 # 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} + 0 0 0 {SCAN t2} 0 0 0 {USE TEMP B-TREE FOR ORDER BY} } # XVIDENCE-OF: R-58157-12355 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 +# 0|0|0|SCAN t2 USING INDEX i4 # 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} + 0 0 0 {SCAN t2 USING INDEX i4} } # XVIDENCE-OF: R-13931-10421 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 +# 0|0|0|SCAN t2 # 0|0|0|EXECUTE SCALAR SUBQUERY 1 -# 1|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=?) +# 1|0|0|SEARCH t1 USING COVERING INDEX i2 (a=?) # 0|0|0|EXECUTE CORRELATED SCALAR SUBQUERY 2 -# 2|0|0|SEARCH TABLE t1 USING INDEX i3 (b=?) +# 2|0|0|SEARCH t1 USING INDEX i3 (b=?) # 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 USING COVERING INDEX i4} + 0 0 0 {SCAN t2 USING COVERING INDEX i4} 0 0 0 {EXECUTE SCALAR SUBQUERY 1} - 1 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)} + 1 0 0 {SEARCH t1 USING COVERING INDEX i2 (a=?)} 0 0 0 {EXECUTE CORRELATED SCALAR SUBQUERY 2} - 2 0 0 {SEARCH TABLE t1 USING INDEX i3 (b=?)} + 2 0 0 {SEARCH t1 USING INDEX i3 (b=?)} } # XVIDENCE-OF: R-50892-45943 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 +# 1|0|0|SCAN t1 USING COVERING INDEX i2 # 0|0|0|SCAN SUBQUERY 1 # 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} + 1 0 0 {SCAN t1 USING COVERING INDEX i2} 0 0 0 {SCAN SUBQUERY 1} 0 0 0 {USE TEMP B-TREE FOR GROUP BY} } # XVIDENCE-OF: R-46219-33846 sqlite> EXPLAIN QUERY PLAN # SELECT * FROM (SELECT * FROM t2 WHERE c=1), t1; -# 0|0|0|SEARCH TABLE t2 USING INDEX i4 (c=?) -# 0|1|1|SCAN TABLE t1 +# 0|0|0|SEARCH t2 USING INDEX i4 (c=?) +# 0|1|1|SCAN t1 # det 5.11 "SELECT a, b FROM (SELECT * FROM t2 WHERE c=1), t1" { - 0 0 0 {SEARCH TABLE t2 USING INDEX i4 (c=?)} - 0 1 1 {SCAN TABLE t1 USING COVERING INDEX i2} + 0 0 0 {SEARCH t2 USING INDEX i4 (c=?)} + 0 1 1 {SCAN t1 USING COVERING INDEX i2} } # XVIDENCE-OF: R-37879-39987 sqlite> EXPLAIN QUERY PLAN # SELECT a FROM t1 UNION SELECT c FROM t2; -# 1|0|0|SCAN TABLE t1 -# 2|0|0|SCAN TABLE t2 +# 1|0|0|SCAN t1 +# 2|0|0|SCAN t2 # 0|0|0|COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION) # det 5.12 "SELECT a,b FROM t1 UNION SELECT c, 99 FROM t2" { - 1 0 0 {SCAN TABLE t1 USING COVERING INDEX i2} - 2 0 0 {SCAN TABLE t2 USING COVERING INDEX i4} + 1 0 0 {SCAN t1 USING COVERING INDEX i2} + 2 0 0 {SCAN t2 USING COVERING INDEX i4} 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION)} } # XVIDENCE-OF: R-44864-63011 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 -# 2|0|0|SCAN TABLE t2 2|0|0|USE TEMP B-TREE FOR ORDER BY +# 1|0|0|SCAN t1 USING COVERING INDEX i2 +# 2|0|0|SCAN t2 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 i1} - 2 0 0 {SCAN TABLE t2} + 1 0 0 {SCAN t1 USING COVERING INDEX i1} + 2 0 0 {SCAN t2} 2 0 0 {USE TEMP B-TREE FOR ORDER BY} 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)} } @@ -647,8 +667,8 @@ if {![nonzero_reserved_bytes]} { do_peqp_test 6.1 { SELECT a, b FROM t1 EXCEPT SELECT d, 99 FROM t2 ORDER BY 1 } [string trimleft { -1 0 0 SCAN TABLE t1 USING COVERING INDEX i2 -2 0 0 SCAN TABLE t2 +1 0 0 SCAN t1 USING COVERING INDEX i2 +2 0 0 SCAN t2 2 0 0 USE TEMP B-TREE FOR ORDER BY 0 0 0 COMPOUND SUBQUERIES 1 AND 2 (EXCEPT) }] @@ -669,12 +689,12 @@ do_execsql_test 7.0 { det 7.1 "SELECT count(*) FROM t1" { QUERY PLAN - `--SCAN TABLE t1 + `--SCAN t1 } det 7.2 "SELECT count(*) FROM t2" { QUERY PLAN - `--SCAN TABLE t2 USING COVERING INDEX i1 + `--SCAN t2 USING COVERING INDEX i1 } do_execsql_test 7.3 { @@ -693,12 +713,12 @@ sqlite3 db test.db det 7.4 "SELECT count(*) FROM t1" { QUERY PLAN - `--SCAN TABLE t1 + `--SCAN t1 } det 7.5 "SELECT count(*) FROM t2" { QUERY PLAN - `--SCAN TABLE t2 USING COVERING INDEX i1 + `--SCAN t2 USING COVERING INDEX i1 } #------------------------------------------------------------------------- @@ -714,37 +734,37 @@ do_execsql_test 8.0 { det 8.1.1 "SELECT * FROM t2" { QUERY PLAN - `--SCAN TABLE t2 + `--SCAN t2 } det 8.1.2 "SELECT * FROM t2 WHERE rowid=?" { QUERY PLAN - `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) } det 8.1.3 "SELECT count(*) FROM t2" { QUERY PLAN - `--SCAN TABLE t2 + `--SCAN t2 } det 8.2.1 "SELECT * FROM t1" { QUERY PLAN - `--SCAN TABLE t1 + `--SCAN t1 } det 8.2.2 "SELECT * FROM t1 WHERE b=?" { QUERY PLAN - `--SEARCH TABLE t1 USING PRIMARY KEY (b=?) + `--SEARCH t1 USING PRIMARY KEY (b=?) } det 8.2.3 "SELECT * FROM t1 WHERE b=? AND c=?" { QUERY PLAN - `--SEARCH TABLE t1 USING PRIMARY KEY (b=? AND c=?) + `--SEARCH t1 USING PRIMARY KEY (b=? AND c=?) } det 8.2.4 "SELECT count(*) FROM t1" { QUERY PLAN - `--SCAN TABLE t1 + `--SCAN t1 } # 2018-08-16: While working on Fossil I discovered that EXPLAIN QUERY PLAN @@ -814,16 +834,16 @@ do_eqp_test 9.1 { ORDER BY 1; } { QUERY PLAN - |--MATERIALIZE xxxxxx - | |--SCAN TABLE forumpost AS x USING INDEX forumthread + |--MATERIALIZE thread + | |--SCAN x USING INDEX forumthread | |--USING ROWID SEARCH ON TABLE private FOR IN-OPERATOR | |--CORRELATED SCALAR SUBQUERY xxxxxx - | | |--SEARCH TABLE forumpost USING COVERING INDEX forumthread (froot=?) + | | |--SEARCH forumpost USING COVERING INDEX forumthread (froot=?) | | `--USING ROWID SEARCH ON TABLE private FOR IN-OPERATOR | `--USE TEMP B-TREE FOR ORDER BY - |--SCAN SUBQUERY xxxxxx - |--SEARCH TABLE blob USING INTEGER PRIMARY KEY (rowid=?) - |--SEARCH TABLE event USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN thread + |--SEARCH blob USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH event USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR ORDER BY } diff --git a/test/exprfault.test b/test/exprfault.test new file mode 100644 index 000000000..9e5977e1d --- /dev/null +++ b/test/exprfault.test @@ -0,0 +1,35 @@ +# 2021 April 17 +# +# 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. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix exprfault + +do_execsql_test 1.0 { + CREATE TABLE t1(a); + CREATE TABLE t2(d); +} +faultsim_save_and_close + +do_faultsim_test 1.1 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + SELECT a = ( SELECT d FROM (SELECT d FROM t2) ) FROM t1 + } +} -test { + faultsim_test_result {0 {}} +} + + +finish_test diff --git a/test/external_reader.test b/test/external_reader.test new file mode 100644 index 000000000..5d293981c --- /dev/null +++ b/test/external_reader.test @@ -0,0 +1,74 @@ +# 2021 April 2 +# +# 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 external_reader + +ifcapable !wal { + finish_test + return +} +if {$::tcl_platform(platform)!="unix"} { + finish_test + return +} + +do_multiclient_test tn { + + set bExternal 1 + if {[info commands db3]!=""} { set bExternal 0 } + + do_test 1.$tn.0 { + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } + } {wal} + + do_test 1.$tn.1 { + sql2 { SELECT * FROM t1 } + } {1 2} + + do_test 1.$tn.2 { + code1 { + file_control_external_reader db + } + } {0} + + do_test 1.$tn.3 { + sql2 { + BEGIN; + SELECT * FROM t1; + } + } {1 2} + + do_test 1.$tn.4 { + code1 { + file_control_external_reader db + } + } $bExternal + + do_test 1.$tn.5 { + sql2 { COMMIT } + } {} + + do_test 1.$tn.6 { + code1 { file_control_external_reader db } + } 0 + +} + + +finish_test diff --git a/test/fts3aux1.test b/test/fts3aux1.test index c7e1ac1a5..1524d6d30 100644 --- a/test/fts3aux1.test +++ b/test/fts3aux1.test @@ -105,10 +105,10 @@ db func rec rec # do_execsql_test 2.1.1.1 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term='braid' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 1:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 1:*/} do_execsql_test 2.1.1.2 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term='braid' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 0:*/} # Now show that using "term='braid'" means the virtual table returns # only 1 row to SQLite, but "+term='braid'" means all 19 are returned. @@ -154,24 +154,24 @@ do_execsql_test 2.1.5 { SELECT * FROM terms WHERE term=NULL } {} do_execsql_test 2.2.1.1 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term>'brain' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 2:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 2:*/} do_execsql_test 2.2.1.2 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term>'brain' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 0:*/} do_execsql_test 2.2.1.3 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term<'brain' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 4:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 4:*/} do_execsql_test 2.2.1.4 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term<'brain' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 0:*/} do_execsql_test 2.2.1.5 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term BETWEEN 'brags' AND 'brain' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 6:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 6:*/} do_execsql_test 2.2.1.6 { EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term BETWEEN 'brags' AND 'brain' -} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/} +} {/*SCAN terms VIRTUAL TABLE INDEX 0:*/} do_test 2.2.2.1 { set cnt 0 @@ -335,7 +335,7 @@ foreach {tn sort orderby} { 9 1 "ORDER BY occurrences DESC" } { - set res {SCAN TABLE terms VIRTUAL TABLE INDEX 0:} + set res {SCAN terms VIRTUAL TABLE INDEX 0:} if {$sort} { append res {*USE TEMP B-TREE FOR ORDER BY} } set res "/*$res*/" @@ -413,8 +413,8 @@ do_plansql_test 4.2 { SELECT y FROM x2, terms WHERE y = term AND col = '*' } { QUERY PLAN - |--SCAN TABLE x2 - `--SCAN TABLE terms VIRTUAL TABLE INDEX 1: + |--SCAN x2 + `--SCAN terms VIRTUAL TABLE INDEX 1: } { a b c d e f g h i j k l } @@ -423,8 +423,8 @@ do_plansql_test 4.3 { SELECT y FROM terms, x2 WHERE y = term AND col = '*' } { QUERY PLAN - |--SCAN TABLE x2 - `--SCAN TABLE terms VIRTUAL TABLE INDEX 1: + |--SCAN x2 + `--SCAN terms VIRTUAL TABLE INDEX 1: } { a b c d e f g h i j k l } @@ -433,8 +433,8 @@ do_plansql_test 4.4 { SELECT y FROM x3, terms WHERE y = term AND col = '*' } { QUERY PLAN - |--SCAN TABLE terms VIRTUAL TABLE INDEX 0: - `--SEARCH TABLE x3 USING COVERING INDEX i1 (y=?) + |--SCAN terms VIRTUAL TABLE INDEX 0: + `--SEARCH x3 USING COVERING INDEX i1 (y=?) } { a b c d e f g h i j k l } @@ -443,8 +443,8 @@ do_plansql_test 4.5 { SELECT y FROM terms, x3 WHERE y = term AND occurrences>1 AND col = '*' } { QUERY PLAN - |--SCAN TABLE terms VIRTUAL TABLE INDEX 0: - `--SEARCH TABLE x3 USING COVERING INDEX i1 (y=?) + |--SCAN terms VIRTUAL TABLE INDEX 0: + `--SEARCH x3 USING COVERING INDEX i1 (y=?) } { a k l } diff --git a/test/fts3corrupt4.test b/test/fts3corrupt4.test index bddcc9f5d..044fd4e64 100644 --- a/test/fts3corrupt4.test +++ b/test/fts3corrupt4.test @@ -6298,4 +6298,514 @@ do_catchsql_test 47.3 { SELECT matchinfo(t1) FROM t1 WHERE t1 MATCH '"json1 enable"'; } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +# +reset_db +do_test 48.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 20480 pagesize 4096 filename sql038051.txt.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 00 .....@ ........ +| 96: 00 00 00 00 0d 0e fc 00 05 0e 13 00 0f ca 0f 6c ...............l +| 112: 0f 04 0e 13 0e c9 00 00 00 00 00 00 00 00 00 00 ................ +| 3600: 00 00 00 81 33 04 07 17 1f 1f 01 82 35 74 61 62 ....3.......5tab +| 3616: 6c 65 78 31 5f 73 65 67 64 69 72 78 31 5f 73 65 lex1_segdirx1_se +| 3632: 67 64 69 72 04 43 52 45 41 54 45 20 54 41 42 4c gdir.CREATE TABL +| 3648: 45 20 27 78 31 5f 73 65 67 64 69 72 27 28 6c 65 E 'x1_segdir'(le +| 3664: 76 65 6c 20 49 4e 54 45 47 45 52 2c 69 64 78 20 vel INTEGER,idx +| 3680: 49 4e 54 45 47 45 52 2c 73 74 61 72 74 5f 62 6c INTEGER,start_bl +| 3696: 6f 63 6b 20 49 4e 54 45 47 45 52 2c 6c 65 61 76 ock INTEGER,leav +| 3712: 65 73 5f 65 6e 64 5f 62 6c 6f 63 6b 20 49 4e 54 es_end_block INT +| 3728: 45 47 45 52 2c 65 6e 64 5f 62 6c 6f 63 6b 20 49 EGER,end_block I +| 3744: 4e 54 45 47 45 52 2c 72 6f 6f 74 20 42 4c 4f 42 NTEGER,root BLOB +| 3760: 2c 50 52 49 4d 41 52 59 20 4b 45 59 28 6c 65 76 ,PRIMARY KEY(lev +| 3776: 65 6c 2c 20 69 64 78 29 29 31 05 06 17 45 1f 01 el, idx))1...E.. +| 3792: 00 69 6e 64 65 78 73 71 6c 69 74 65 5f 61 75 74 .indexsqlite_aut +| 3808: 6f 69 6e 64 65 78 5f 78 31 5f 73 65 67 64 69 72 oindex_x1_segdir +| 3824: 5f 31 78 31 5f 73 65 67 64 69 72 05 00 00 00 08 _1x1_segdir..... +| 3840: 00 00 00 00 66 03 07 17 23 23 01 81 13 74 61 62 ....f...##...tab +| 3856: 6c 65 78 31 5f 73 65 67 6d 65 6e 74 73 78 31 5f lex1_segmentsx1_ +| 3872: 73 65 67 6d 65 6e 74 73 03 43 52 45 41 54 45 20 segments.CREATE +| 3888: 54 41 42 4c 45 20 27 78 31 5f 73 65 67 6d 65 6e TABLE 'x1_segmen +| 3904: 74 73 27 28 62 6c 6f 63 6b 69 64 20 49 4e 54 45 ts'(blockid INTE +| 3920: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY, +| 3936: 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 5c 02 07 17 block BLOB).... +| 3952: 21 21 01 81 03 74 61 62 6c 65 78 31 5f 63 6f 6e !!...tablex1_con +| 3968: 74 65 6e 74 78 31 5f 63 6f 6e 74 65 6e 74 02 43 tentx1_content.C +| 3984: 52 45 41 54 45 20 54 41 42 4c 45 20 27 78 31 5f REATE TABLE 'x1_ +| 4000: 63 6f 6e 74 65 6e 74 27 28 64 6f 63 69 64 20 49 content'(docid I +| 4016: 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b NTEGER PRIMARY K +| 4032: 45 59 2c 20 27 63 30 78 27 29 34 01 06 17 11 11 EY, 'c0x')4..... +| 4048: 08 57 74 61 62 6c 65 78 31 78 31 43 52 45 41 54 .Wtablex1x1CREAT +| 4064: 45 20 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 E VIRTUAL TABLE +| 4080: 78 31 20 55 53 49 4e 47 20 66 74 73 33 28 78 29 x1 USING fts3(x) +| page 2 offset 4096 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3920: 00 00 00 2e 04 03 00 63 62 72 61 69 6e 73 74 65 .......cbrainste +| 3936: 6d 20 62 72 61 69 6e 73 74 65 6d 73 20 62 72 61 m brainstems bra +| 3952: 69 6e 73 74 6f 72 6d 20 62 72 61 69 6e 73 74 6f instorm brainsto +| 3968: 72 6d 73 2b 03 03 00 5d 62 72 61 69 6e 20 62 72 rms+...]brain br +| 3984: 61 69 6e 63 68 69 6c 64 20 62 72 61 69 6e 65 64 ainchild brained +| 4000: 20 62 72 61 69 6e 69 6e 67 20 62 72 61 69 6e 73 braining brains +| 4016: 26 02 03 00 53 62 72 61 67 73 20 62 72 61 69 64 &...Sbrags braid +| 4032: 20 62 72 61 69 64 65 64 20 62 72 61 69 64 69 6e braided braidin +| 4048: 67 20 62 72 61 69 64 73 26 01 03 00 53 62 72 61 g braids&...Sbra +| 4064: 65 73 20 62 72 61 67 20 62 72 61 67 67 65 64 20 es brag bragged +| 4080: 62 72 61 c3 67 65 72 20 62 72 61 67 67 69 6e 67 bra.ger bragging +| page 3 offset 8192 +| 0: 0d 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 04 0f 20 00 0f c8 0f 90 0f 54 0f 20 ...... ......T. +| 3872: 32 04 07 08 01 08 08 15 58 03 30 20 33 38 00 09 2.......X.0 38.. +| 3888: 62 72 61 69 6e 73 74 65 6d 03 04 02 00 09 01 73 brainstem......s +| 3904: 03 04 03 00 07 03 6f 72 6d 03 04 04 00 0a 01 73 ......orm......s +| 3920: 03 04 05 00 3a 03 07 08 01 08 08 15 68 02 30 20 ....:.......h.0 +| 3936: 34 36 00 05 62 72 61 69 6e 03 03 02 00 05 05 63 46..brain......c +| 3952: 68 69 6c 64 03 03 03 00 05 02 65 64 03 03 04 00 hild......ed.... +| 3968: 05 03 69 6e 67 03 03 05 00 05 01 73 03 03 06 00 ..ing......s.... +| 3984: 36 02 07 08 09 08 08 15 62 30 20 34 33 00 05 62 6.......b0 43..b +| 4000: 72 61 67 73 03 02 02 00 03 02 69 64 03 02 03 00 rags......id.... +| 4016: 05 02 65 64 03 02 04 00 05 03 69 6e 67 03 02 05 ..ed......ing... +| 4032: 00 05 01 73 03 02 06 00 36 01 07 08 08 08 08 15 ...s....6....... +| 4048: 62 30 20 34 33 00 05 62 72 61 65 73 03 01 02 00 b0 43..braes.... +| 4064: 03 01 68 03 01 03 00 04 03 67 65 74 03 01 04 00 ..h......get.... +| 4080: 06 01 72 03 01 05 00 05 03 69 6e 67 03 01 06 00 ..r......ing.... +| page 5 offset 16384 +| 0: 0a 00 00 00 04 0f e7 00 0f fb 0f f5 0f ee 0f e7 ................ +| 4064: 00 00 00 00 00 00 00 06 04 08 01 01 03 04 06 04 ................ +| 4080: 08 01 01 02 03 05 04 08 09 01 02 04 04 08 08 09 ................ +| end sql038051.txt.db +}]} {} + +do_catchsql_test 48.1 { + INSERT INTO x1(x1) VALUES('nodesize=24'),('merge=3,4'); + INSERT INTO x1(x1) VALUES( 'merge=3,4' ),('merge=3,4'); +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +# +reset_db +do_test 49.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 28672 pagesize 4096 filename crash-58821b8eae6883.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................ +| 96: 00 00 00 00 0d 0e ef 00 07 0d 4d 00 0f bd 0f 5f ..........M...._ +| 112: 0e f7 0e 06 0e bc 0d a4 0d 4d 00 00 00 00 00 00 .........M...... +| 3392: 00 00 00 00 00 00 00 00 00 00 00 00 00 55 07 07 .............U.. +| 3408: 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 73 74 ......tablet1_st +| 3424: 61 74 74 31 5f 73 74 61 74 07 43 52 45 41 54 45 att1_stat.CREATE +| 3440: 20 54 41 42 4c 45 20 27 74 31 5f 73 74 61 74 27 TABLE 't1_stat' +| 3456: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM +| 3472: 41 52 59 20 4b 45 59 2c 20 76 61 6c 75 65 20 42 ARY KEY, value B +| 3488: 4c 4f 42 29 60 06 07 17 21 21 01 81 0b 74 61 62 LOB)`...!!...tab +| 3504: 6c 65 74 31 5f 64 6f 63 73 69 7a 65 74 31 5f 64 let1_docsizet1_d +| 3520: 6f 63 73 69 7a 65 06 43 52 45 41 54 45 20 54 41 ocsize.CREATE TA +| 3536: 42 4c 45 20 27 74 31 5f 64 6f 63 73 69 7a 65 27 BLE 't1_docsize' +| 3552: 28 64 6f 63 69 64 20 49 4e 54 45 47 45 52 20 50 (docid INTEGER P +| 3568: 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 69 7a 65 RIMARY KEY, size +| 3584: 20 42 4c 4f 42 29 81 33 04 07 17 1f 1f 01 82 35 BLOB).3.......5 +| 3600: 74 61 62 6c 65 74 31 5f 73 65 67 64 69 72 74 31 tablet1_segdirt1 +| 3616: 5f 73 65 67 64 69 72 04 43 52 45 41 54 45 20 54 _segdir.CREATE T +| 3632: 41 42 4c 45 20 27 74 31 5f 73 65 67 64 69 72 27 ABLE 't1_segdir' +| 3648: 28 6c 65 76 65 6c 20 49 4e 54 45 47 45 52 2c 69 (level INTEGER,i +| 3664: 64 78 20 49 4e 54 45 47 45 52 2c 73 74 61 72 74 dx INTEGER,start +| 3680: 5f 62 6c 6f 63 6b 20 49 4e 54 45 47 45 52 2c 6c _block INTEGER,l +| 3696: 65 61 76 65 73 5f 65 6e 64 5f 62 6c 6f 63 6b 20 eaves_end_block +| 3712: 49 4e 54 45 47 45 52 2c 65 6e 64 5f 62 6c 6f 63 INTEGER,end_bloc +| 3728: 6b 20 49 4e 54 45 47 45 52 2c 72 6f 6f 74 20 42 k INTEGER,root B +| 3744: 4c 4f 42 2c 50 52 49 4d 41 52 59 20 4b 45 59 28 LOB,PRIMARY KEY( +| 3760: 6c 65 76 65 6c 2c 20 69 64 78 29 29 31 05 06 17 level, idx))1... +| 3776: 45 1f 01 00 69 6e 64 65 78 73 71 6c 69 74 65 5f E...indexsqlite_ +| 3792: 61 75 74 6f 69 6e 64 65 78 5f 74 31 5f 73 65 67 autoindex_t1_seg +| 3808: 64 69 72 5f 31 74 31 5f 73 65 67 64 69 72 05 00 dir_1t1_segdir.. +| 3824: 00 00 08 00 00 00 00 66 03 07 17 23 23 01 81 13 .......f...##... +| 3840: 74 61 62 6c 65 74 31 5f 73 65 67 6d 65 6e 74 73 tablet1_segments +| 3856: 74 31 5f 73 65 67 6d 65 6e 74 73 03 43 52 45 41 t1_segments.CREA +| 3872: 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 65 67 TE TABLE 't1_seg +| 3888: 6d 65 6e 74 73 27 28 62 6c 6f 63 6b 69 64 20 49 ments'(blockid I +| 3904: 4e 54 45 47 45 52 20 f9 52 49 4d 41 52 59 20 4b NTEGER .RIMARY K +| 3920: 45 59 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 5c EY, block BLOB). +| 3936: 02 07 17 21 21 01 81 03 74 61 62 6c 65 74 31 5f ...!!...tablet1_ +| 3952: 63 6f 6e 74 65 6e 74 74 31 5f 63 6f 6e 74 65 6e contentt1_conten +| 3968: 74 02 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 t.CREATE TABLE ' +| 3984: 74 31 5f 63 6f 6e 74 65 6e 74 27 28 64 6f 63 69 t1_content'(doci +| 4000: 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 d INTEGER PRIMAR +| 4016: 59 20 4b 45 59 2c 20 27 63 30 61 27 29 41 01 06 Y KEY, 'c0a')A.. +| 4032: 17 11 11 08 71 74 61 62 6c 65 74 31 74 31 43 52 ....qtablet1t1CR +| 4048: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4064: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 34 LE t1 USING fts4 +| 4080: 28 61 2c 70 72 65 66 69 78 3d 27 32 2c 32 27 29 (a,prefix='2,2') +| page 2 offset 4096 +| 0: 0d 00 00 00 08 0e 1f 00 0f c4 0f 7c 0f 34 0f 07 ...........|.4.. +| 16: 0e c3 0e 97 0e 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 42 ...............B +| 3616: 08 04 00 81 09 73 75 6e 74 20 69 6e 20 63 75 6c .....sunt in cul +| 3632: 70 61 20 71 75 69 20 6f 66 66 69 63 69 61 20 64 pa qui officia d +| 3648: 65 73 65 72 75 6e 74 20 6d 6f 6c 6c 69 74 20 61 eserunt mollit a +| 3664: 6e 69 6d 20 69 64 20 65 73 74 20 6c 61 62 6f 72 nim id est labor +| 3680: 75 6d 2e 32 07 03 00 6b 45 78 63 65 70 74 65 75 um.2...kExcepteu +| 3696: 72 20 73 69 6e 74 20 6f 63 63 61 65 63 61 74 20 r sint occaecat +| 3712: 63 75 70 69 64 61 74 61 74 20 6e 6f 6e 20 70 72 cupidatat non pr +| 3728: 6f 69 64 65 6e 74 2c 2a 06 03 00 5b 63 69 6c 6c oident,*...[cill +| 3744: 75 6d 20 64 6f 6c 6f 72 65 20 65 75 20 66 75 67 um dolore eu fug +| 3760: 69 61 74 20 6e 75 6c 6c 61 20 70 61 72 69 61 74 iat nulla pariat +| 3776: 75 72 2e 42 05 04 00 81 09 44 75 69 73 20 61 75 ur.B.....Duis au +| 3792: 74 65 20 69 72 75 72 65 20 64 6f 6c 6f 72 20 69 te irure dolor i +| 3808: 6e 20 72 65 70 72 65 68 65 6e 64 65 72 69 74 20 n reprehenderit +| 3824: 69 6e 20 76 6f 6c 75 70 74 61 74 65 20 76 65 6c in voluptate vel +| 3840: 69 74 20 65 73 73 65 2b 04 03 00 5d 6e 69 73 69 it esse+...]nisi +| 3856: 20 75 74 20 61 6c 69 71 75 69 70 20 65 78 20 65 ut aliquip ex e +| 3872: 61 20 63 6f 6d 6d 6f 64 6f 20 63 6f 6e 73 65 71 a commodo conseq +| 3888: 75 61 74 2e 46 03 04 00 81 11 55 74 20 65 6e 69 uat.F.....Ut eni +| 3904: 6d 20 61 64 20 6d 69 6e 69 6d 20 76 65 6e 69 61 m ad minim venia +| 3920: 6d 2c 20 71 75 69 73 20 6e 6f 73 74 72 75 64 20 m, quis nostrud +| 3936: 65 78 65 72 63 69 74 61 74 69 6f 6e 20 75 6c 6c exercitation ull +| 3952: 61 6d 63 6f 20 6c 61 62 6f 72 69 73 46 02 04 00 amco laborisF... +| 3968: 81 11 73 65 64 20 64 6f 20 65 69 75 73 6d 6f 64 ..sed do eiusmod +| 3984: 20 74 65 6d 70 6f 72 20 69 6e 63 69 64 69 64 75 tempor incididu +| 4000: 6e 74 20 75 74 20 6c 61 62 6f 72 65 20 65 74 20 nt ut labore et +| 4016: 64 6f 6c 6f 72 65 20 6d 61 67 6e 61 20 61 6c 69 dolore magna ali +| 4032: 71 75 61 2e 3a 01 03 00 7b 4c 6f 72 65 6d 20 69 qua.:....Lorem i +| 4048: 70 73 75 6d 20 64 6f 6c 6f 72 20 73 69 74 20 61 psum dolor sit a +| 4064: 6d 65 74 2c 20 63 6f 6e 73 65 63 74 65 74 75 72 met, consectetur +| 4080: 20 61 64 69 70 69 73 63 69 6e 67 20 65 00 01 00 adipiscing e... +| page 4 offset 12288 +| 0: 0d 00 00 00 03 0a a6 00 0d 57 0c 4a 0a a6 00 00 .........W.J.... +| 2720: 00 00 00 00 00 00 83 21 03 08 02 08 08 08 17 86 .......!........ +| 2736: 30 08 00 30 20 34 30 32 00 02 61 64 06 01 08 00 0..0 402..ad.... +| 2752: 02 04 00 01 01 6c 06 02 0c 00 02 04 00 01 01 6d .....l.........m +| 2768: 03 01 06 00 01 01 6e 03 08 09 00 01 01 75 03 05 ......n......u.. +| 2784: 03 00 00 02 63 69 03 06 02 00 01 01 6f 07 01 07 ....ci......o... +| 2800: 00 03 07 03 00 01 01 75 06 07 05 00 01 04 00 00 .......u........ +| 2816: 02 64 65 03 08 07 00 01 01 6f 0d 01 04 00 01 03 .de......o...... +| 2832: 09 00 03 05 00 01 03 00 01 01 75 03 05 02 00 00 ..........u..... +| 2848: 02 65 61 03 04 06 00 01 01 69 03 02 04 00 01 01 .ea......i...... +| 2864: 6c 03 01 09 00 01 01 6e 03 03 03 00 01 01 73 06 l......n......s. +| 2880: 05 0b 00 03 0b 00 01 01 74 03 02 09 00 01 01 75 ........t......u +| 2896: 03 06 04 00 01 01 78 09 03 09 00 01 05 00 03 02 ......x......... +| 2912: 00 00 02 66 75 03 06 05 00 00 02 69 64 03 08 0a ...fu......id... +| 2928: 00 01 01 6e 0a 02 06 00 03 06 04 00 03 03 00 01 ...n............ +| 2944: 01 70 03 01 03 00 01 01 72 03 05 04 00 00 02 6c .p......r......l +| 2960: 61 09 02 08 00 01 0b 00 05 0c 00 01 01 6f 03 01 a............o.. +| 2976: 02 00 00 02 6d 61 03 02 0b 00 01 01 69 03 03 05 ....ma......i... +| 2992: 00 01 01 6f 03 08 08 00 00 02 6e 69 03 04 02 00 ...o......ni.... +| 3008: 01 01 6f 06 03 08 00 04 06 00 01 01 75 03 06 06 ..o.........u... +| 3024: 00 00 02 6f 63 03 07 04 00 01 01 66 03 08 06 00 ...oc......f.... +| 3040: 00 02 70 61 03 06 07 00 01 01 72 03 07 07 00 00 ..pa......r..... +| 3056: 02 71 75 06 03 07 00 05 05 00 00 02 72 65 03 05 .qu.........re.. +| 3072: 07 00 00 02 73 65 03 02 02 00 01 01 69 06 01 05 ....se......i... +| 3088: 00 06 03 00 01 01 75 03 08 02 00 00 02 74 65 03 ......u......te. +| 3104: 02 05 00 00 02 75 6c 03 03 0a 00 01 01 74 09 02 .....ul......t.. +| 3120: 07 00 01 02 00 01 03 00 00 02 76 65 06 03 06 00 ..........ve.... +| 3136: 02 0a 00 01 01 6f 03 05 09 00 82 0a 02 08 02 08 .....o.......... +| 3152: 08 08 17 84 02 04 00 30 20 32 35 31 00 01 61 13 .......0 251..a. +| 3168: 01 06 04 00 01 0c 00 01 04 00 01 04 00 01 03 00 ................ +| 3184: 03 09 00 00 01 63 10 01 07 00 03 07 03 00 02 02 .....c.......... +| 3200: 00 01 05 00 01 04 00 00 01 64 11 01 04 00 01 03 .........d...... +| 3216: 09 00 03 02 05 00 01 03 00 02 07 00 00 01 65 1b ..............e. +| 3232: 01 09 00 01 04 07 00 01 03 08 00 01 05 03 00 01 ................ +| 3248: 0b 00 01 04 00 01 02 00 01 0b 00 00 01 66 03 06 .............f.. +| 3264: 05 00 00 01 69 0f 01 03 00 01 06 00 03 04 04 04 ....i........... +| 3280: 00 03 03 09 00 00 01 6c 0c 01 02 00 01 08 00 01 .......l........ +| 3296: 0b 00 05 0c 00 00 01 6d 09 02 0b 00 01 05 00 05 .......m........ +| 3312: 08 00 00 01 6e 0c 03 08 00 01 02 00 02 06 00 01 ....n........... +| 3328: 06 00 00 01 6f 06 07 04 00 01 06 00 00 01 70 06 ....o.........p. +| 3344: 06 07 00 01 07 00 00 01 71 06 03 07 00 05 05 00 ........q....... +| 3360: 00 01 72 03 05 07 00 00 01 73 0c 01 05 00 01 02 ..r......s...... +| 3376: 00 05 03 00 01 02 00 00 01 74 03 02 05 00 00 01 .........t...... +| 3392: 75 0a 02 07 00 01 02 0a 00 01 03 00 00 01 76 07 u.............v. +| 3408: 03 06 00 02 09 03 00 85 26 01 08 08 08 08 08 17 ........&....... +| 3424: 8a 3e 30 20 36 36 35 00 02 61 65 03 03 04 00 02 .>0 665..ae..... +| 3440: 08 69 70 69 73 63 69 6e 67 03 01 08 00 01 05 6c .ipiscing......l +| 3456: 69 71 75 61 03 02 0c 00 05 02 69 70 03 04 04 00 iqua......ip.... +| 3472: 01 03 6d 65 74 03 01 06 00 01 03 6e 69 6d 03 08 ..met......nim.. +| 3488: 09 00 01 03 75 74 65 03 05 03 00 00 06 63 69 6c ....ute......cil +| 3504: 6c 75 6d 03 06 02 00 01 06 6f 6d 6d 6f 64 6f 03 lum......ommodo. +| 3520: 04 07 00 02 09 6e 73 65 63 74 65 74 b5 72 03 01 .....nsectet.r.. +| 3536: 07 00 05 04 71 75 61 74 03 04 08 00 01 04 75 6c ....quat......ul +| 3552: 70 61 03 08 04 00 02 07 70 69 64 61 74 61 74 03 pa......pidatat. +| 3568: 07 05 00 00 08 64 65 73 65 72 75 6e 74 03 08 07 .....deserunt... +| 3584: 00 01 01 6f 03 02 03 00 02 03 6c 6f 72 06 01 04 ...o......lor... +| 3600: 00 04 05 00 05 01 65 06 02 0a 00 04 03 00 01 03 ......e......... +| 3616: 75 69 73 03 05 02 00 00 02 65 61 03 04 06 00 01 uis......ea..... +| 3632: 06 69 75 73 6d 6f 64 03 02 04 00 01 03 6c 69 74 .iusmod......lit +| 3648: 03 01 09 00 01 03 6e 69 6d 03 03 03 00 01 03 73 ......nim......s +| 3664: 73 65 03 05 0b 00 02 01 74 03 08 0b 00 01 01 74 se......t......t +| 3680: 03 02 09 00 01 01 75 03 06 04 00 01 01 78 03 04 ......u......x.. +| 3696: 05 00 02 07 63 65 70 74 65 75 72 03 07 02 00 02 ....cepteur..... +| 3712: 0a 65 72 63 69 74 61 74 69 6f 6e 03 03 09 00 00 .ercitation..... +| 3728: 06 66 75 67 69 61 74 03 06 05 00 00 02 69 64 03 .fugiat......id. +| 3744: 08 0a 00 01 01 6e 07 05 06 04 00 03 03 00 02 08 .....n.......... +| 3760: 63 69 64 69 64 75 6e 74 03 02 06 00 01 04 70 73 cididunt......ps +| 3776: 75 6d 03 01 03 00 01 04 72 75 72 65 03 05 04 00 um......rure.... +| 3792: 00 06 6c 61 62 6f 72 65 03 02 08 00 05 02 69 73 ..labore......is +| 3808: 03 03 0b 00 05 02 75 6d 03 08 0c 00 01 04 6f 72 ......um......or +| 3824: 65 6d 03 01 02 00 00 05 6d 61 67 6e 61 03 02 0b em......magna... +| 3840: 00 01 04 69 6e 69 6d 03 03 05 00 01 05 6f 6c 6c ...inim......oll +| 3856: 69 74 03 08 08 00 00 04 6e 69 73 69 03 04 02 00 it......nisi.... +| 3872: 01 02 6f 6e 03 07 06 00 02 05 73 74 72 75 64 03 ..on......strud. +| 3888: 03 08 00 01 04 75 6c 6c 61 03 06 06 00 00 08 6f .....ulla......o +| 3904: 63 63 61 65 63 61 74 03 07 04 00 01 06 66 66 69 ccaecat......ffi +| 3920: 63 69 61 03 08 06 00 00 08 70 61 72 69 61 74 75 cia......pariatu +| 3936: 72 03 06 07 00 01 07 72 6f 69 64 65 6e 74 03 07 r......roident.. +| 3952: 07 00 00 03 71 75 69 03 08 05 00 03 01 73 03 03 ....qui......s.. +| 3968: 07 00 00 0d 72 65 70 72 65 68 65 6e 64 65 72 69 ....reprehenderi +| 3984: 74 03 05 07 00 00 03 73 65 64 03 02 02 00 01 03 t......sed...... +| 4000: 69 6e 74 03 07 03 00 02 01 74 03 01 05 00 01 03 int......t...... +| 4016: 75 6e 74 03 08 02 00 00 06 74 65 6d 70 6f 72 03 unt......tempor. +| 4032: 02 05 00 00 07 75 6c 6c 61 6d 63 6f 03 03 0a 00 .....ullamco.... +| 4048: 01 01 74 09 02 07 00 01 02 00 01 03 00 00 05 76 ..t............v +| 4064: 65 6c 69 74 03 05 0a 00 02 04 6e 69 61 6d 03 03 elit......niam.. +| 4080: 06 00 01 08 6f 6c 75 70 74 61 74 65 03 05 09 00 ....oluptate.... +| page 5 offset 16384 +| 0: 0a 00 00 00 03 0f eb 00 0f fb 0f f3 0f eb 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 07 04 02 08 01 ................ +| 4080: 08 00 03 07 04 02 08 01 04 00 02 04 04 08 08 09 ................ +| page 6 offset 20480 +| 0: 0d 00 00 00 08 0f d0 00 0f fa 0f f4 0f ee 0f e8 ................ +| 16: 0f e2 0f dc 0f d6 0f d0 00 00 00 00 00 00 00 00 ................ +| 4048: 04 08 03 00 0e 0b 04 07 03 00 0e 06 04 06 03 00 ................ +| 4064: 0e 06 04 05 03 00 0e 0a 04 04 03 00 0e 07 04 03 ................ +| 4080: 03 00 0e 0a 04 02 03 00 0e 0b 04 01 03 00 0e 08 ................ +| page 7 offset 24576 +| 0: 0d 00 00 00 01 0f f7 00 0f f7 00 00 00 00 01 00 ................ +| 4080: 00 00 00 00 00 00 00 07 00 03 00 14 08 45 b5 03 .............E.. +| end crash-58821b8eae6883.db +}]} {} + +do_catchsql_test 49.1 { + SAVEPOINT one; + DELETE FROM t1 WHERE t1 MATCH 'c*'; + SELECT matchinfo(t1,'pcx') IS NULL FROM t1 WHERE t1 MATCH 'f*e*'; +} {0 0} + +#------------------------------------------------------------------------- +# +reset_db +do_test 50.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 28672 pagesize 4096 filename crash-14ab65782c9c45.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........ +| 32: 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 00 ................ +| 96: 00 00 00 00 0d 0e b1 00 06 0d a4 00 0f 8d 0f 21 ...............! +| 112: 0e b9 0d c8 0e 7e 0d a4 00 00 00 00 00 00 00 00 .....~.......... +| 3488: 00 00 00 00 22 07 06 17 11 11 01 31 74 61 62 6c ...........1tabl +| 3504: 65 74 32 74 32 07 43 52 45 41 54 45 20 54 41 42 et2t2.CREATE TAB +| 3520: 4c 45 20 74 32 28 78 29 81 33 05 07 17 1f 1f 01 LE t2(x).3...... +| 3536: 82 35 74 61 62 6c 65 74 31 5f 73 65 67 64 69 72 .5tablet1_segdir +| 3552: 74 31 5f 73 65 67 64 69 72 05 43 52 45 41 54 45 t1_segdir.CREATE +| 3568: 20 54 41 42 4c 45 20 27 74 31 5f 73 65 67 64 69 TABLE 't1_segdi +| 3584: 72 27 28 6c 65 76 65 6c 20 49 4e 54 45 47 45 52 r'(level INTEGER +| 3600: 2c 69 64 78 20 49 4e 54 45 47 45 52 2c 73 74 61 ,idx INTEGER,sta +| 3616: 72 74 5f 62 6c 6f 63 6b 20 49 4e 54 45 47 45 52 rt_block INTEGER +| 3632: 2c 6c 65 61 76 65 73 5f 65 6e 64 5f 62 6c 6f 63 ,leaves_end_bloc +| 3648: 6b 20 49 4e 54 45 47 45 52 2c 65 6e 64 5f 62 6c k INTEGER,end_bl +| 3664: 6f 63 6b 20 49 4e 54 45 47 45 52 2c 72 6f 6f 74 ock INTEGER,root +| 3680: 20 42 4c 4f 42 2c 50 52 49 4d 41 52 59 20 4b 45 BLOB,PRIMARY KE +| 3696: 59 28 6c 65 76 65 6c 2c 20 69 64 78 29 29 31 06 Y(level, idx))1. +| 3712: 06 17 45 1f 01 00 69 6e 64 65 78 73 71 6c 69 74 ..E...indexsqlit +| 3728: 65 5f 61 75 74 6f 69 6e 64 65 78 5f 74 31 5f 73 e_autoindex_t1_s +| 3744: 65 67 64 69 72 5f 31 74 31 5f 73 65 68 64 69 72 egdir_1t1_sehdir +| 3760: 06 0f c7 00 08 00 00 00 00 66 04 07 17 23 23 01 .........f...##. +| 3776: 81 13 74 61 62 6c 65 74 31 5f 73 65 67 6d 65 6e ..tablet1_segmen +| 3792: 74 73 74 31 5f 73 65 67 6d 65 6e 74 73 04 43 52 tst1_segments.CR +| 3808: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 EATE TABLE 't1_s +| 3824: 65 67 6d 65 6e 74 73 27 28 62 6c 6f 63 6b 69 64 egments'(blockid +| 3840: 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 INTEGER PRIMARY +| 3856: 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 KEY, block BLOB +| 3872: 29 6a 03 07 17 21 21 01 81 1f 74 61 62 6c 65 74 )j...!!...tablet +| 3888: 31 5f 63 6f 6e 74 65 6e 74 74 31 5f 63 6f 6e 74 1_contentt1_cont +| 3904: 65 6e 74 03 43 52 45 41 54 45 20 54 41 42 4c 45 ent.CREATE TABLE +| 3920: 20 27 74 31 5f 63 6f 6e 74 65 6e 74 27 28 64 6f 't1_content'(do +| 3936: 63 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d cid INTEGER PRIM +| 3952: 41 52 59 20 4b 45 59 2c 20 27 63 30 61 27 2c 20 ARY KEY, 'c0a', +| 3968: 27 62 31 62 27 2c 20 27 63 32 63 27 29 38 02 06 'b1b', 'c2c')8.. +| 3984: 17 11 11 08 5f 74 61 62 6c 65 74 31 74 31 43 52 ...._tablet1t1CR +| 4000: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4016: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 33 LE t1 USING fts3 +| 4032: 28 61 2c 62 2c 63 29 00 00 00 00 00 00 00 00 00 (a,b,c)......... +| page 3 offset 8192 +| 0: 0d 00 00 00 25 0b 48 00 0f d8 0f af 0f 86 0f 74 ....%.H........t +| 16: 0f 61 0f 4e 0f 2f 0f 0f 0e ef 0e d7 0e be 0e a5 .a.N./.......... +| 32: 0e 8d 0e 74 0e 5b 0e 40 0e 24 0e 08 0d ef 0d d5 ...t.[.@.$...... +| 48: 0d bb 0d a0 0d 84 0d 00 00 00 00 00 00 00 00 00 ................ +| 2880: 00 00 00 00 00 00 00 00 81 f9 25 06 00 82 7f 00 ..........%..... +| 2896: 00 43 4f 4d 50 49 4c 45 52 3d 67 63 63 2d 35 2e .COMPILER=gcc-5. +| 2912: 34 2e 30 20 32 30 31 36 30 36 30 39 20 44 45 42 4.0 20160609 DEB +| 2928: 55 47 20 45 4e 41 42 4c 45 20 44 42 53 54 41 54 UG ENABLE DBSTAT +| 2944: 20 56 54 41 42 20 45 4e 41 42 4c 45 20 46 54 53 VTAB ENABLE FTS +| 2960: 34 20 45 4e 41 42 4c 45 20 46 54 53 35 20 45 4e 4 ENABLE FTS5 EN +| 2976: 41 42 4c 45 20 47 45 4f 50 4f 4c 59 20 45 4e 41 ABLE GEOPOLY ENA +| 2992: 42 4c 45 20 4a 53 4f 4e 31 20 45 4e 41 42 4c 45 BLE JSON1 ENABLE +| 3008: 20 4d 45 4d 53 59 53 35 20 45 4e 41 42 4c 45 20 MEMSYS5 ENABLE +| 3024: 52 54 52 45 45 20 4d 41 58 20 4d 45 4d 4f 52 59 RTREE MAX MEMORY +| 3040: 3d 37 a0 30 30 30 30 f2 30 20 4f 4d 49 54 20 4c =7.0000.0 OMIT L +| 3056: 4f 41 44 20 45 58 54 45 4e 53 49 4f 4e 20 54 48 OAD EXTENSION TH +| 3072: 52 45 41 44 53 41 46 45 3d 30 18 24 05 00 25 0f READSAFE=0.$..%. +| 3088: 19 54 48 52 45 41 44 53 41 46 45 3d 30 58 42 49 .THREADSAFE=0XBI +| 3104: 4e 41 52 59 18 23 05 00 25 0f 19 54 48 52 45 41 NARY.#..%..THREA +| 3120: 44 53 41 46 45 3d 30 58 4e 4f 43 41 53 45 17 22 DSAFE=0XNOCASE.. +| 3136: 05 00 25 0f 17 54 48 52 45 41 54 53 41 46 45 3d ..%..THREATSAFE= +| 3152: 30 58 52 54 52 49 4d 1f 21 05 00 33 0f 19 4f 4d 0XRTRIM.!..3..OM +| 3168: 49 54 20 5c 4f 41 44 20 45 58 54 45 4e 53 49 4f IT .OAD EXTENSIO +| 3184: 4e 58 42 49 4e 41 52 59 1f 20 05 00 33 0f 19 4f NXBINARY. ..3..O +| 3200: 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 MIT LOAD EXTENSI +| 3216: 4f 4e 58 4e 4f 43 41 53 46 1e 1f 05 00 33 0f 17 ONXNOCASF....3.. +| 3232: 4f 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 OMIT LOAD EXTENS +| 3248: 49 4f 4e 58 52 44 52 49 4d 1f 1e 05 00 33 0f 19 IONXRDRIM....3.. +| 3264: 4d 41 68 20 4d 45 4d 4f 52 59 3d 35 30 30 30 30 MAh MEMORY=50000 +| 3280: 30 30 30 58 42 49 4e 41 52 59 1f 1d 05 00 33 0f 000XBINARY....3. +| 3296: 19 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 .MAX MEMORY=5000 +| 3312: 30 30 30 30 58 4e 4f 43 41 53 45 1e 1c 05 00 33 0000XNOCASE....3 +| 3328: 0f 16 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 ..MAX MEMORY=500 +| 3344: 30 30 30 30 30 58 52 54 52 49 4d 18 1b 05 00 25 00000XRTRIM....% +| 3360: 0f 19 45 4e 41 42 4c 45 20 52 54 52 45 45 58 42 ..ENABLE RTREEXB +| 3376: 49 4e 41 52 59 18 1a 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3392: 4c 45 20 52 54 52 45 45 58 4e 4f 43 41 53 45 17 LE RTREEXNOCASE. +| 3408: 19 05 00 25 0f 17 45 4e 41 42 4c 45 20 52 54 52 ...%..ENABLE RTR +| 3424: 45 45 58 52 54 52 49 4d 1a 18 05 00 29 0f 19 45 EEXRTRIM....)..E +| 3440: 4e 41 42 4c 45 20 4d 45 43 53 59 53 35 58 42 49 NABLE MECSYS5XBI +| 3456: 4e 41 52 59 1a 17 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3472: 45 20 4d 45 4d 53 59 53 35 58 4e 4f 43 41 53 45 E MEMSYS5XNOCASE +| 3488: 19 16 05 00 29 0f 17 45 4e 41 42 4c 45 20 4d 45 ....)..ENABLE ME +| 3504: 4d 53 59 53 35 58 52 54 52 49 4d 18 15 05 00 25 MSYS5XRTRIM....% +| 3520: 0f 19 45 4e 41 42 4c 45 20 4a 53 4f 4e 31 58 42 ..ENABLE JSON1XB +| 3536: 49 4e 41 52 59 18 14 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3552: 4c 45 20 4a 53 4f 4e 31 58 4e 4f 43 41 53 45 17 LE JSON1XNOCASE. +| 3568: 13 05 00 25 0f 17 45 4e 41 42 4c 45 20 4a 53 4f ...%..ENABLE JSO +| 3584: 4e 31 58 52 54 52 49 4d 1a 12 05 00 29 0f 19 45 N1XRTRIM....)..E +| 3600: 4e 41 42 4c 45 20 47 45 4f 50 4f 4c 59 58 42 49 NABLE GEOPOLYXBI +| 3616: 4e 41 52 59 1a 11 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3632: 45 20 47 45 4f 50 4f 4c 59 58 4e 4f 43 41 53 45 E GEOPOLYXNOCASE +| 3648: 19 10 05 00 29 0f 17 45 4e 41 42 4c 45 20 47 45 ....)..ENABLE GE +| 3664: 4f 50 4f 4c 59 58 52 54 52 49 4d 17 0f 05 00 23 OPOLYXRTRIM....# +| 3680: 0f 19 45 4e 41 42 4c 45 20 46 54 53 35 58 42 49 ..ENABLE FTS5XBI +| 3696: 4e 41 52 59 17 0e 05 00 23 0f 19 45 4e 41 42 4c NARY....#..ENABL +| 3712: 45 20 46 54 53 35 58 4e 4f 43 41 53 45 16 0d 05 E FTS5XNOCASE... +| 3728: 00 23 0f 17 45 4e 41 42 4c 45 20 46 54 53 35 58 .#..ENABLE FTS5X +| 3744: 52 54 52 49 4d 17 0c 05 00 23 0f 19 45 4e 41 42 RTRIM....#..ENAB +| 3760: 4c 45 20 46 54 53 34 58 42 49 4e 41 52 59 17 0b LE FTS4XBINARY.. +| 3776: 05 00 23 0f 19 45 4e 41 42 4c 45 20 46 54 53 34 ..#..ENABLE FTS4 +| 3792: 58 4e 4f 43 41 53 45 16 0a 05 00 23 0f 17 45 4e XNOCASE....#..EN +| 3808: 41 42 4c 46 20 46 54 53 34 58 52 54 52 49 4d 1e ABLF FTS4XRTRIM. +| 3824: 09 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3840: 54 41 54 20 56 54 41 42 58 42 49 4e 41 52 59 1e TAT VTABXBINARY. +| 3856: 08 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3872: 55 41 54 20 56 54 41 42 58 4e 4f 43 41 53 45 1d UAT VTABXNOCASE. +| 3888: 07 05 00 31 0f 17 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3904: 54 41 54 20 56 54 41 42 58 52 54 52 49 4d 11 06 TAT VTABXRTRIM.. +| 3920: 05 00 17 0f 19 44 45 42 55 47 58 42 49 4e 41 52 .....DEBUGXBINAR +| 3936: 59 11 05 05 00 17 0f 19 44 45 42 55 47 58 4e 4f Y.......DEBUGXNO +| 3952: 43 41 53 45 10 04 05 00 17 0f 17 44 45 42 55 47 CASE.......DEBUG +| 3968: 58 52 54 52 49 4d 27 03 05 00 43 0f 19 43 4f 4d XRTRIM'...C..COM +| 3984: 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e 30 20 PILER=gcc-5.4.0 +| 4000: 32 30 31 36 30 36 30 39 58 42 49 4e 41 52 59 27 20160609XBINARY' +| 4016: 02 05 00 43 0f 19 43 4f 4d 50 49 4c 45 52 3d 67 ...C..COMPILER=g +| 4032: 63 63 2d 35 2e 34 2e 30 20 32 30 31 36 30 36 30 cc-5.4.0 2016060 +| 4048: 39 58 4e 4f 43 41 53 45 00 00 00 00 00 00 00 00 9XNOCASE........ +| page 5 offset 16384 +| 0: 0d 00 00 00 02 0b a0 00 0c ad 0b a0 00 00 00 00 ................ +| 2976: 82 0a 02 08 08 09 08 08 17 84 06 30 20 32 35 33 ...........0 253 +| 2992: 00 01 30 04 25 06 1b 00 00 08 32 30 31 36 30 36 ..0.%.....201606 +| 3008: 30 39 03 25 07 00 00 01 34 03 25 05 00 00 01 35 09.%....4.%....5 +| 3024: 03 25 04 00 01 07 30 30 30 30 30 30 30 03 25 1a .%....0000000.%. +| 3040: 00 00 08 63 6f 6d 70 69 6c 65 72 03 25 02 00 00 ...compiler.%... +| 3056: 06 64 62 73 74 61 74 03 25 0a 00 01 04 65 62 75 .dbstat.%....ebu +| 3072: 67 03 25 08 00 00 06 65 6e 61 62 6c 65 09 25 09 g.%....enable.%. +| 3088: 05 04 04 04 04 04 00 01 08 78 74 65 6e 73 69 6f .........xtensio +| 3104: 6e 03 25 1d 00 00 04 66 74 73 34 03 25 0d 00 03 n.%....fts4.%... +| 3120: 01 35 03 25 0f 00 00 03 67 63 63 03 25 03 00 01 .5.%....gcc.%... +| 3136: 06 65 6f 70 6f 6c 79 03 25 11 00 00 05 6a 73 6f .eopoly.%....jso +| 3152: 6e 31 03 25 13 00 00 04 6c 6f 61 64 03 25 1c 00 n1.%....load.%.. +| 3168: 00 03 6d 61 78 03 25 18 00 01 05 65 6d 6f 72 79 ..max.%....emory +| 3184: 03 25 19 00 03 04 73 79 73 35 03 25 15 00 00 04 .%....sys5.%.... +| 3200: 6f 6d 69 74 03 25 1b 00 00 05 72 74 72 65 65 03 omit.%....rtree. +| 3216: 25 17 00 00 0a 74 68 72 65 61 64 73 61 66 65 03 %....threadsafe. +| 3232: 25 1e 00 00 04 76 74 61 62 03 25 0b 00 86 50 01 %....vtab.%...P. +| 3248: 08 08 08 08 08 17 8d 12 30 20 38 33 35 00 01 30 ........0 835..0 +| 3264: 12 01 06 00 01 06 00 01 06 00 1f 03 00 01 03 00 ................ +| 3280: 01 03 00 00 08 32 30 31 36 30 36 30 39 09 01 07 .....20160609... +| 3296: 00 01 07 00 01 07 00 00 01 34 09 01 05 00 01 05 .........4...... +| 3312: 00 01 05 00 00 01 35 09 01 04 00 01 04 00 01 04 ......5......... +| 3328: 00 01 07 30 30 30 30 30 30 30 09 1c 04 00 01 04 ...0000000...... +| 3344: 00 01 04 00 00 06 62 69 6e 61 72 79 3c 03 01 02 ......binary<... +| 3360: 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 ................ +| 3376: 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 00 ................ +| 3392: 03 01 02 02 00 03 01 02 02 00 03 01 02 02 00 03 ................ +| 3408: 01 02 02 00 03 01 02 02 00 00 08 63 6f 38 70 69 ...........co8pi +| 3424: 6c 65 72 09 01 02 00 01 02 00 01 02 00 00 06 64 ler............d +| 3440: 52 73 74 61 74 09 07 03 00 01 03 00 01 03 00 01 Rstat........... +| 3456: 04 65 62 75 67 09 04 02 00 01 02 00 01 02 00 00 .ebug........... +| 3472: 06 65 6e 61 62 6c 65 3f 07 02 00 01 02 00 01 01 .enable?........ +| 3488: ff f1 02 00 01 02 00 01 02 00 01 02 00 01 02 00 ................ +| 3504: 01 02 00 01 03 00 01 01 00 02 02 00 01 02 00 01 ................ +| 3520: 02 00 01 02 00 01 12 00 01 02 00 01 02 00 01 02 ................ +| 3536: 00 01 02 00 01 02 00 01 08 78 74 65 6e 73 69 6f .........xtensio +| 3552: 6e 09 1f 04 00 01 04 00 01 04 00 00 04 66 74 73 n............fts +| 3568: 34 09 0a 03 00 01 03 00 01 04 00 03 01 35 09 0d 4............5.. +| 3584: 03 00 01 03 00 01 03 00 00 03 67 63 63 09 01 03 ..........gcc... +| 3600: 00 01 03 0b 01 13 00 01 06 65 6f 70 6f 6c 79 09 .........eopoly. +| 3616: 10 03 00 01 03 00 01 03 00 00 05 6a 83 6f 6e 31 ...........j.on1 +| 3632: 09 13 03 00 01 03 00 01 03 00 00 04 6c 6f 61 64 ............load +| 3648: 09 1f 03 00 01 03 00 01 03 00 00 03 6d 61 78 09 ............max. +| 3664: 1c 02 00 01 02 00 01 02 00 01 05 65 6d 6f 72 79 ...........emory +| 3680: 09 1c 03 00 01 03 00 01 03 00 03 04 73 79 73 35 ............sys5 +| 3696: 09 16 03 00 01 03 00 01 03 00 00 06 6e 6f 63 61 ............noca +| 3712: 73 65 3c 02 01 02 02 00 03 01 02 02 00 03 01 02 se<............. +| 3728: 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 ................ +| 3744: 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 00 ................ +| 3760: 03 01 02 02 00 03 01 02 02 00 03 01 02 02 00 00 ................ +| 3776: 04 6f 6d 69 74 09 1f 02 00 01 02 00 01 02 00 00 .omit........... +| 3792: 05 72 74 72 65 65 09 19 03 00 01 03 00 01 03 00 .rtree.......... +| 3808: 03 02 69 6d 3c 01 01 02 02 00 03 01 02 02 00 03 ..im<........... +| 3824: 01 02 02 00 03 01 02 02 00 03 01 02 02 00 03 01 ................ +| 3840: 02 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 ................ +| 3856: 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 ................ +| 3872: 00 00 0a 74 68 72 65 61 64 73 61 66 65 09 22 02 ...threadsafe... +| 3888: 00 01 02 00 01 02 00 00 04 76 74 61 62 09 07 04 .........vtab... +| 3904: 00 01 04 00 01 04 00 00 01 78 b4 01 01 01 01 02 .........x...... +| 3920: 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 ................ +| 3936: 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 01 ................ +| 3952: 01 01 02 00 01 01 01 02 00 01 01 01 02 00 01 01 ................ +| 3968: 01 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 ................ +| 3984: 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 ................ +| 4000: 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 ................ +| 4016: 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 01 ................ +| 4032: 01 01 02 00 01 01 01 02 00 01 01 01 02 00 01 01 ................ +| 4048: 01 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 ................ +| 4064: 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 ................ +| 4080: 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 02 0f f5 00 0f fb 0f f5 00 00 00 00 ................ +| 4080: 00 00 00 00 00 05 04 08 09 01 02 04 04 08 08 09 ................ +| page 7 offset 24576 +| 0: 0d 00 00 00 05 0f b8 00 0f f4 0f 00 00 00 00 00 ................ +| 4016: 00 00 00 00 00 00 00 00 0d 05 02 23 61 75 74 6f ...........#auto +| 4032: 6d 65 72 67 65 3d 35 0d 04 02 23 6d 65 72 67 65 merge=5...#merge +| 4048: 3d 31 30 30 2c 38 11 03 02 2b 69 6e 74 65 67 72 =100,8...+integr +| 4064: 69 74 79 2d 63 68 65 63 6b 09 02 02 00 00 00 00 ity-check....... +| end crash-14ab65782c9c45.db +}]} {} + +do_execsql_test 50.1 { + SELECT NULL FROM t1 WHERE t1 MATCH '"^enable"' +} { + {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} +} + + + + finish_test diff --git a/test/fts3corrupt6.test b/test/fts3corrupt6.test index 9e22bdf68..fde2fba2e 100644 --- a/test/fts3corrupt6.test +++ b/test/fts3corrupt6.test @@ -11,8 +11,6 @@ # This file implements regression tests for SQLite library. The # focus of this script is testing the FTS3 module. # -# $Id: fts3aa.test,v 1.1 2007/08/20 17:38:42 shess Exp $ -# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -54,7 +52,27 @@ do_execsql_test 1.3 { SELECT 42+matchinfo(t1,'yxyyxy') FROM t1 WHERE t1 MATCH x'2b0a312b0a312a312a2a0b5d0a0b0b0a312a0a0b0b0a312a0b310a392a0b0a27312a2a0b5d0a312a0b310a31315d0b310a312a316d2a0b313b15bceaa50a312a0b0a27312a2a0b5d0a312a0b310a312b0b2a310a312a0b2a0b2a0b2e5d0a0bff313336e34a2a312a0b0a3c310b0a0b4b4b0b4b2a4bec40322b2a0b310a0a312a0a0a0a0a0a0a0a0a0b310a312a2a2a0b5d0a0b0b0a312a0b310a312a0b0a4e4541530b310a5df5ced70a0a0a0a0a4f520a0a0a0a0a0a0a312a0b0a4e4541520b310a5d616161610a0a0a0a4f520a0a0a0a0a0a312b0a312a312a0a0a0a0a0a0a004a0b0a310b220a0b0a310a4a22310a0b0a7e6fe0e0e030e0e0e0e0e01176e02000e0e0e0e0e01131320226310a0b0a310a4a22310a0b0a310a766f8b8b4ee0e0300ae0090909090909090909090909090909090909090909090909090909090909090947aaaa540b09090909090909090909090909090909090909090909090909090909090909fae0e0f2f22164e0e0f273e07fefefef7d6dfafafafa6d6d6d6d'; } {42} -set sqlite_fts3_enable_parentheses $saved_sqlite_fts3_enable_parentheses -finish_test +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t0 USING fts3(a); + INSERT INTO t0_segdir VALUES(0,0,0,0,'0 42',X'000131030782000103323334050100fff200010461616161050101020200000462626262050101030200'); +} +do_execsql_test 2.1 { + SELECT count(*) FROM t0 WHERE t0 MATCH '(1 NEAR 1) AND (aaaa OR 1)'; +} 1 +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE main.Table0 USING fts3(); + INSERT INTO Table0 VALUES (1), (printf('%8.1280000X') ), (1), (printf('%8.1280000X') ), (1) ; + INSERT INTO Table0 VALUES (0), (printf('%8.1280000X%8.1280000X') ), (1), (printf('%1280000.1280000X%#1280000.1280000E%8.1280000X') ), (1) ; + INSERT INTO Table0 VALUES (1) ; + UPDATE Table0_segdir SET start_block = 1; + INSERT INTO Table0 VALUES (1) ; + INSERT INTO Table0(Table0) VALUES('merge=6,8'); +} +set sqlite_fts3_enable_parentheses $saved_sqlite_fts3_enable_parentheses +finish_test diff --git a/test/fts3join.test b/test/fts3join.test index f3b9b3639..9a7994193 100644 --- a/test/fts3join.test +++ b/test/fts3join.test @@ -97,10 +97,10 @@ do_eqp_test 4.2 { WHERE t4.y = ?; } { QUERY PLAN - |--MATERIALIZE xxxxxx - | `--SCAN TABLE ft4 VIRTUAL TABLE INDEX 3: - |--SCAN TABLE t4 - `--SEARCH SUBQUERY xxxxxx AS rr USING AUTOMATIC COVERING INDEX (docid=?) + |--MATERIALIZE rr + | `--SCAN ft4 VIRTUAL TABLE INDEX 3: + |--SCAN t4 + `--SEARCH rr USING AUTOMATIC COVERING INDEX (docid=?) } finish_test diff --git a/test/fts3query.test b/test/fts3query.test index ca3ee6695..70ee00529 100644 --- a/test/fts3query.test +++ b/test/fts3query.test @@ -119,29 +119,29 @@ do_eqp_test fts3query-4.2 { SELECT t1.number FROM t1, ft WHERE t1.number=ft.rowid ORDER BY t1.date } { QUERY PLAN - |--SCAN TABLE t1 USING COVERING INDEX i1 - `--SCAN TABLE ft VIRTUAL TABLE INDEX 1: + |--SCAN t1 USING COVERING INDEX i1 + `--SCAN ft VIRTUAL TABLE INDEX 1: } do_eqp_test fts3query-4.3 { SELECT t1.number FROM ft, t1 WHERE t1.number=ft.rowid ORDER BY t1.date } { QUERY PLAN - |--SCAN TABLE t1 USING COVERING INDEX i1 - `--SCAN TABLE ft VIRTUAL TABLE INDEX 1: + |--SCAN t1 USING COVERING INDEX i1 + `--SCAN ft VIRTUAL TABLE INDEX 1: } do_eqp_test fts3query-4.4 { SELECT t1.number FROM t1, bt WHERE t1.number=bt.rowid ORDER BY t1.date } { QUERY PLAN - |--SCAN TABLE t1 USING COVERING INDEX i1 - `--SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t1 USING COVERING INDEX i1 + `--SEARCH bt USING INTEGER PRIMARY KEY (rowid=?) } do_eqp_test fts3query-4.5 { SELECT t1.number FROM bt, t1 WHERE t1.number=bt.rowid ORDER BY t1.date } { QUERY PLAN - |--SCAN TABLE t1 USING COVERING INDEX i1 - `--SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t1 USING COVERING INDEX i1 + `--SEARCH bt USING INTEGER PRIMARY KEY (rowid=?) } diff --git a/test/fts3snippet2.test b/test/fts3snippet2.test index fc3994189..607b01e0f 100644 --- a/test/fts3snippet2.test +++ b/test/fts3snippet2.test @@ -57,4 +57,3 @@ do_execsql_test 2.2 { set sqlite_fts3_enable_parentheses 0 finish_test - diff --git a/test/fts4rename.test b/test/fts4rename.test index c647d6928..5571ea7b1 100644 --- a/test/fts4rename.test +++ b/test/fts4rename.test @@ -29,7 +29,7 @@ do_execsql_test 1.0 { do_catchsql_test 1.1 { ALTER TABLE t1_content RENAME c0a TO docid; -} {1 {duplicate column name: docid}} +} {1 {error in table t1_content after rename: duplicate column name: docid}} do_catchsql_test 1.2 { UPDATE t1 SET Col0 = 1 ; diff --git a/test/fts4unicode.test b/test/fts4unicode.test index f4d1a3348..facf2bf9c 100644 --- a/test/fts4unicode.test +++ b/test/fts4unicode.test @@ -567,4 +567,22 @@ do_execsql_test 11.1 { berlin@street sydney.road } +# Test for embedded nul characters in fts4 unicode index. +# +do_execsql_test 12.0 { + CREATE VIRTUAL TABLE t12 USING fts4(tokenize=unicode61); + INSERT INTO t12 VALUES('abc' || char(0) || 'def'); + SELECT hex(CAST(content AS blob)) FROM t12; +} {61626300646566} +do_execsql_test 12.1 { + INSERT INTO t12(t12) VALUES('integrity-check'); +} {} +do_execsql_test 12.2 { + CREATE VIRTUAL TABLE t12aux USING fts4aux(t12); + SELECT * FROM t12aux; +} {abc * 1 1 abc 0 1 1} +do_execsql_test 12.3 { + SELECT hex(CAST(content AS blob)) FROM t12 WHERE t12 MATCH 'abc' +} {61626300646566} + finish_test diff --git a/test/fts4upfrom.test b/test/fts4upfrom.test index b1b43a037..889b64ad6 100644 --- a/test/fts4upfrom.test +++ b/test/fts4upfrom.test @@ -137,4 +137,3 @@ foreach {tn create_table} { } finish_test - diff --git a/test/func.test b/test/func.test index 4b235bedb..38c003773 100644 --- a/test/func.test +++ b/test/func.test @@ -1111,6 +1111,13 @@ do_test func-22.22 { execsql {SELECT typeof(trim('hello',NULL));} } {null} +# 2021-06-15 - infinite loop due to unsigned character counter +# overflow, reported by Zimuzo Ezeozue +# +do_execsql_test func-22.23 { + SELECT trim('xyzzy',x'c0808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080'); +} {xyzzy} + # This is to test the deprecated sqlite3_aggregate_count() API. # ifcapable deprecated { diff --git a/test/func7.test b/test/func7.test new file mode 100644 index 000000000..c8ae2931e --- /dev/null +++ b/test/func7.test @@ -0,0 +1,245 @@ +# 2020-12-07 +# +# 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. +# +#************************************************************************* +# +# Test cases for SQL functions based off the standard math library +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !mathlib { + finish_test + return +} + +do_execsql_test func7-100 { + SELECT ceil(99.9), ceiling(-99.01), floor(17), floor(-17.99); +} {100.0 -99.0 17 -18.0} +do_execsql_test func7-110 { + SELECT quote(ceil(NULL)), ceil('-99.99'); +} {NULL -99.0} +do_execsql_test func7-200 { + SELECT round(ln(5),2), log(100.0), log(100), log(2,'256'); +} {1.61 2.0 2.0 8.0} +do_execsql_test func7-210 { + SELECT ln(-5), log(-5,100.0); +} {{} {}} + +# Test cases derived from PostgreSQL documentation +# +do_execsql_test func7-pg-100 { + SELECT abs(-17.4) +} {17.4} +do_execsql_test func7-pg-110 { + SELECT ceil(42.2) +} {43.0} +do_execsql_test func7-pg-120 { + SELECT ceil(-42.2) +} {-42.0} +do_execsql_test func7-pg-130 { + SELECT round(exp(1.0),7) +} {2.7182818} +do_execsql_test func7-pg-140 { + SELECT floor(42.8) +} {42.0} +do_execsql_test func7-pg-150 { + SELECT floor(-42.8) +} {-43.0} +do_execsql_test func7-pg-160 { + SELECT round(ln(2.0),7) +} {0.6931472} +do_execsql_test func7-pg-170 { + SELECT log(100.0) +} {2.0} +do_execsql_test func7-pg-180 { + SELECT log10(1000.0) +} {3.0} +do_execsql_test func7-pg-190 { + SELECT log(2.0, 64.0) +} {6.0} +do_execsql_test func7-pg-200 { + SELECT mod(9,4); +} {1.0} +do_execsql_test func7-pg-210 { + SELECT round(pi(),7); +} {3.1415927} +do_execsql_test func7-pg-220 { + SELECT power(9,3); +} {729.0} +do_execsql_test func7-pg-230 { + SELECT round(radians(45.0),7); +} {0.7853982} +do_execsql_test func7-pg-240 { + SELECT round(42.4); +} {42.0} +do_execsql_test func7-pg-250 { + SELECT round(42.4382,2); +} {42.44} +do_execsql_test func7-pg-260 { + SELECT sign(-8.4); +} {-1} +do_execsql_test func7-pg-270 { + SELECT round( sqrt(2), 7); +} {1.4142136} +do_execsql_test func7-pg-280 { + SELECT trunc(42.8), trunc(-42.8); +} {42.0 -42.0} +do_execsql_test func7-pg-300 { + SELECT acos(1); +} {0.0} +do_execsql_test func7-pg-301 { + SELECT degrees(acos(0.5)); +} {60.0} +do_execsql_test func7-pg-310 { + SELECT round( asin(1), 7); +} {1.5707963} +do_execsql_test func7-pg-311 { + SELECT degrees( asin(0.5) ); +} {30.0} +do_execsql_test func7-pg-320 { + SELECT round( atan(1), 7); +} {0.7853982} +do_execsql_test func7-pg-321 { + SELECT degrees( atan(1) ); +} {45.0} +do_execsql_test func7-pg-330 { + SELECT round( atan2(1,0), 7); +} {1.5707963} +do_execsql_test func7-pg-331 { + SELECT degrees( atan2(1,0) ); +} {90.0} +do_execsql_test func7-pg-400 { + SELECT cos(0); +} {1.0} +do_execsql_test func7-pg-401 { + SELECT cos( radians(60.0) ); +} {0.5} +do_execsql_test func7-pg-400 { + SELECT cos(0); +} {1.0} +do_execsql_test func7-pg-410 { + SELECT round( sin(1), 7); +} {0.841471} +do_execsql_test func7-pg-411 { + SELECT sin( radians(30) ); +} {0.5} +do_execsql_test func7-pg-420 { + SELECT round( tan(1), 7); +} {1.5574077} +do_execsql_test func7-pg-421 { + SELECT tan( radians(45) ); +} {1.0} +do_execsql_test func7-pg-500 { + SELECT round( sinh(1), 7); +} {1.1752012} +do_execsql_test func7-pg-510 { + SELECT round( cosh(0), 7); +} {1.0} +do_execsql_test func7-pg-520 { + SELECT round( tanh(1), 7); +} {0.7615942} +do_execsql_test func7-pg-530 { + SELECT round( asinh(1), 7); +} {0.8813736} +do_execsql_test func7-pg-540 { + SELECT round( acosh(1), 7); +} {0.0} +do_execsql_test func7-pg-550 { + SELECT round( atanh(0.5), 7); +} {0.5493061} + +# Test cases derived from MySQL documentation +# +do_execsql_test func7-mysql-100 { + SELECT acos(1); +} {0.0} +do_execsql_test func7-mysql-110 { + SELECT acos(1.0001); +} {{}} +do_execsql_test func7-mysql-120 { + SELECT round( acos(0.0), 7); +} {1.5707963} +do_execsql_test func7-mysql-130 { + SELECT round( asin(0.2), 7); +} {0.2013579} +do_execsql_test func7-mysql-140 { + SELECT asin('foo'); +} {{}} ;# Note: MySQL returns 0 here, not NULL. + # SQLite deliberately returns NULL. + # SQLServer and Oracle throw an error. +do_execsql_test func7-mysql-150 { + SELECT round( atan(2), 7), round( atan(-2), 7); +} {1.1071487 -1.1071487} +do_execsql_test func7-mysql-160 { + SELECT round( atan2(-2,2), 7), round( atan2(pi(),0), 7); +} {-0.7853982 1.5707963} +do_execsql_test func7-mysql-170 { + SELECT ceiling(1.23), ceiling(-1.23); +} {2.0 -1.0} +do_execsql_test func7-mysql-180 { + SELECT cos(pi()); +} {-1.0} +do_execsql_test func7-mysql-190 { + SELECT degrees(pi()), degrees(pi()/2); +} {180.0 90.0} +do_execsql_test func7-mysql-190 { + SELECT round( exp(2), 7), round( exp(-2), 7), exp(0); +} {7.3890561 0.1353353 1.0} +do_execsql_test func7-mysql-200 { + SELECT floor(1.23), floor(-1.23); +} {1.0 -2.0} +do_execsql_test func7-mysql-210 { + SELECT round(ln(2),7), quote(ln(-2)); +} {0.6931472 NULL} +#do_execsql_test func7-mysql-220 { +# SELECT round(log(2),7), log(-2); +#} {0.6931472 NULL} +# log() means natural logarithm in MySQL +do_execsql_test func7-mysql-230 { + SELECT log(2,65536), log(10,100), quote(log(1,100)), quote(log(0,100)); +} {16.0 2.0 NULL NULL} +do_execsql_test func7-mysql-240 { + SELECT log2(65536), quote(log2(-100)), quote(log2(0)); +} {16.0 NULL NULL} +do_execsql_test func7-mysql-250 { + SELECT round(log10(2),7), log10(100), quote(log10(-100)); +} {0.30103 2.0 NULL} +do_execsql_test func7-mysql-260 { + SELECT mod(234,10), 253%7, mod(29,9), 29%9; +} {4.0 1 2.0 2} +do_execsql_test func7-mysql-270 { + SELECT mod(34.5,3); +} {1.5} +do_execsql_test func7-mysql-280 { + SELECT pow(2,2), pow(2,-2); +} {4.0 0.25} +do_execsql_test func7-mysql-281 { + SELECT power(2,2), power(2,-2); +} {4.0 0.25} +do_execsql_test func7-mysql-290 { + SELECT round(radians(90),7); +} {1.5707963} +do_execsql_test func7-mysql-300 { + SELECT sign(-32), sign(0), sign(234); +} {-1 0 1} +do_execsql_test func7-mysql-310 { + SELECT sin(pi()) BETWEEN -1.0e-15 AND 1.0e-15; +} {1} +do_execsql_test func7-mysql-320 { + SELECT sqrt(4), round(sqrt(20),7), quote(sqrt(-16)); +} {2.0 4.472136 NULL} +do_execsql_test func7-mysql-330 { + SELECT tan(pi()) BETWEEN -1.0e-15 AND 1.0e-15; +} {1} +do_execsql_test func7-mysql-331 { + SELECT round(tan(pi()+1),7); +} {1.5574077} + + +finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index cf49956b4..3020086a2 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -63,7 +63,7 @@ ** of the global variable g.zTextName[] will identify the specific XSQL and ** DB values that were running when the crash occurred. ** -** DBSQLFUZZ: +** DBSQLFUZZ: (Added 2020-02-25) ** ** The dbsqlfuzz fuzzer includes both a database file and SQL to run against ** that database in its input. This utility can now process dbsqlfuzz @@ -108,10 +108,10 @@ typedef unsigned char uint8_t; */ typedef struct VFile VFile; struct VFile { - char *zFilename; /* Filename. NULL for delete-on-close. From malloc() */ - int sz; /* Size of the file in bytes */ - int nRef; /* Number of references to this file */ - unsigned char *a; /* Content of the file. From malloc() */ + char *zFilename; /* Filename. NULL for delete-on-close. From malloc() */ + int sz; /* Size of the file in bytes */ + int nRef; /* Number of references to this file */ + unsigned char *a; /* Content of the file. From malloc() */ }; typedef struct VHandle VHandle; struct VHandle { @@ -219,7 +219,7 @@ static int progressHandler(void *pVdbeLimitFlag){ #endif /* -** Reallocate memory. Show and error and quit if unable. +** Reallocate memory. Show an error and quit if unable. */ static void *safe_realloc(void *pOld, int szNew){ void *pNew = realloc(pOld, szNew<=0 ? 1 : szNew); @@ -455,7 +455,12 @@ static void blobListFree(Blob *p){ } } -/* Return the current wall-clock time */ +/* Return the current wall-clock time +** +** The number of milliseconds since the julian epoch. +** 1907-01-01 00:00:00 -> 210866716800000 +** 2021-01-01 00:00:00 -> 212476176000000 +*/ static sqlite3_int64 timeOfDay(void){ static sqlite3_vfs *clockVfs = 0; sqlite3_int64 t; @@ -612,8 +617,8 @@ static int isOffset( /* ** Decode the text starting at zIn into a binary database file. -** The maximum length of zIn is nIn bytes. Compute the binary database -** file contain in space obtained from sqlite3_malloc(). +** The maximum length of zIn is nIn bytes. Store the binary database +** file in space obtained from sqlite3_malloc(). ** ** Return the number of bytes of zIn consumed. Or return -1 if there ** is an error. One potential error is that the recipe specifies a @@ -713,6 +718,7 @@ static int progress_handler(void *pClientData) { sqlite3_int64 iNow = timeOfDay(); int rc = iNow>=p->iCutoffTime; sqlite3_int64 iDiff = iNow - p->iLastCb; + /* printf("time-remaining: %lld\n", p->iCutoffTime - iNow); */ if( iDiff > p->mxInterval ) p->mxInterval = iDiff; p->nCb++; if( rc==0 && p->mxCb>0 && p->mxCb<=p->nCb ) rc = 1; @@ -729,7 +735,7 @@ static int progress_handler(void *pClientData) { ** "PRAGMA parser_trace" since they can dramatically increase the ** amount of output without actually testing anything useful. ** -** Also block ATTACH and DETACH +** Also block ATTACH if attaching a file from the filesystem. */ static int block_troublesome_sql( void *Notused, @@ -744,18 +750,21 @@ static int block_troublesome_sql( (void)zArg3; (void)zArg4; if( eCode==SQLITE_PRAGMA ){ - if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0 - || sqlite3_stricmp("parser_trace", zArg1)==0 - || sqlite3_stricmp("temp_store_directory", zArg1)==0 - ){ - return SQLITE_DENY; - } - if( sqlite3_stricmp("oom",zArg1)==0 && zArg2!=0 && zArg2[0]!=0 ){ + if( eVerbosity==0 ){ + if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0 + || sqlite3_stricmp("parser_trace", zArg1)==0 + || sqlite3_stricmp("temp_store_directory", zArg1)==0 + ){ + return SQLITE_DENY; + } + }else if( sqlite3_stricmp("oom",zArg1)==0 + && zArg2!=0 && zArg2[0]!=0 ){ oomCounter = atoi(zArg2); } - }else if( (eCode==SQLITE_ATTACH || eCode==SQLITE_DETACH) - && zArg1 && zArg1[0] ){ - return SQLITE_DENY; + }else if( eCode==SQLITE_ATTACH ){ + if( zArg1==0 || (zArg1[0]!=0 && strcmp(zArg1,":memory:")!=0) ){ + return SQLITE_DENY; + } } return SQLITE_OK; } @@ -835,7 +844,7 @@ static int runDbSql(sqlite3 *db, const char *zSql){ } /* Invoke this routine to run a single test case */ -int runCombinedDbSqlInput(const uint8_t *aData, size_t nByte){ +int runCombinedDbSqlInput(const uint8_t *aData, size_t nByte, int iTimeout){ int rc; /* SQLite API return value */ int iSql; /* Index in aData[] of start of SQL */ unsigned char *aDb = 0; /* Decoded database content */ @@ -882,7 +891,7 @@ int runCombinedDbSqlInput(const uint8_t *aData, size_t nByte){ ** elapsed since the start of the test. */ cx.iLastCb = timeOfDay(); - cx.iCutoffTime = cx.iLastCb + giTimeout; /* Now + giTimeout seconds */ + cx.iCutoffTime = cx.iLastCb + (iTimeout=argc-1 ) fatalError("missing arguments on %s", argv[i]); onlySqlid = integerValue(argv[++i]); @@ -1757,10 +1779,25 @@ int main(int argc, char **argv){ rc = sqlite3_exec(db, "BEGIN", 0, 0, 0); if( rc ) fatalError("cannot start a transaction"); for(i=iFirstInsArg; i0 && zLine[kk-1]<=' ' ) kk--; + sqlite3_bind_text(pStmt, 1, zLine, kk, SQLITE_STATIC); + if( verboseFlag ) printf("loading %.*s\n", (int)kk, zLine); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc ) fatalError("insert failed for %s", zLine); + } + }else{ + sqlite3_bind_text(pStmt, 1, argv[i], -1, SQLITE_STATIC); + if( verboseFlag ) printf("loading %s\n", argv[i]); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc ) fatalError("insert failed for %s", argv[i]); + } } sqlite3_finalize(pStmt); rc = sqlite3_exec(db, "COMMIT", 0, 0, 0); @@ -1894,6 +1931,7 @@ int main(int argc, char **argv){ */ if( !verboseFlag && !quietFlag && !bSpinner ) printf("%s:", zDbName); for(pSql=g.pFirstSql; pSql; pSql=pSql->pNext){ + tmStart = timeOfDay(); if( isDbSql(pSql->a, pSql->sz) ){ sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d",pSql->id); if( bSpinner ){ @@ -1917,9 +1955,13 @@ int main(int argc, char **argv){ if( nSkip>0 ){ nSkip--; }else{ - runCombinedDbSqlInput(pSql->a, pSql->sz); + runCombinedDbSqlInput(pSql->a, pSql->sz, iTimeout); } nTest++; + if( bTimer ){ + sqlite3_int64 tmEnd = timeOfDay(); + printf("%lld %s\n", tmEnd - tmStart, g.zTestName); + } g.zTestName[0] = 0; disableOom(); continue; @@ -1972,7 +2014,9 @@ int main(int argc, char **argv){ sqlite3_limit(db, SQLITE_LIMIT_LENGTH, 100000000); sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH, 50); if( cellSzCkFlag ) runSql(db, "PRAGMA cell_size_check=ON", runFlags); - setAlarm(iTimeout); + setAlarm((iTimeout+999)/1000); + /* Enable test functions */ + sqlite3_test_control(SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, db); #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( sqlFuzz || vdbeLimitFlag ){ sqlite3_progress_handler(db, 100000, progressHandler, @@ -1998,6 +2042,10 @@ int main(int argc, char **argv){ } reformatVfs(); nTest++; + if( bTimer ){ + sqlite3_int64 tmEnd = timeOfDay(); + printf("%lld %s\n", tmEnd - tmStart, g.zTestName); + } g.zTestName[0] = 0; /* Simulate an error if the TEST_FAILURE environment variable is "5". @@ -2018,7 +2066,8 @@ int main(int argc, char **argv){ } } if( bSpinner ){ - printf("\n"); + int nTotal = g.nDb*g.nSql; + printf("\r%s: %d/%d \n", zDbName, nTotal, nTotal); }else if( !quietFlag && !verboseFlag ){ printf(" 100%% - %d tests\n", g.nDb*g.nSql); } diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db index 39191fc8b1f03935f6b661eab5af1e2e2fba7285..86217e4b421375154ba0316867b7d9aa9514c191 100644 GIT binary patch delta 269924 zcmeFa31C#!*#^s)0|(Zl_1PNL8_X{lTWa`fN4-0rCvJ{6F{) z;%37>T2+{HVNCVq9MhC3DwX;~-%_N%?yE=oN?#q)7yD46`h~uQNPp0`0O@0W^N`-( zSA+ESzPU(m>6?x8#=codclDiv^s2rpq+jiuf%J;L=}0f{3nGp6RU+Ne7eKnX&x5qR zPgKQJ>I?g(;$l-@1=0PNd6E@4q|Uxq4{pWtvhM&*0A~}2^lJ9Op@^A~1{Tq>d za|4n)n~{7YiiDbwd_98XDUpHU4qnnE)o-<&1nE^)klD}>izjheho;gepQX+zh@!&K0vGbE?}za zpN8aHfT-#|09194AIV)lB)55y+#({m$%SMOV5`~S zQ?<1eNws zUsgSTNwv_?(N-n8rkj@6EUuZ;AXEwSmenm0!lF>OY;MgmVfOh#h^n8=9Yrs`Y?(+0 z-ZGA&_Z_;De24Nvulz!fyPQhj&KX08F3=c?gxb0WVPS3ZnXZm)ElmPh8ZAY-RSRmC z)s)lH7YwCz@!iG>dj5d1fEs^k$)!0@=8ljGx7nvHiI#12wby%ub>jnNLUg-uk!y#0 zJ-xTxT1b2UY`BnqcGy@%^KY~k)1jZ4N7J6Wb!Iy99i4%ulh)0~&R8q7g?b_zTiTmD z9Kzy-=hg^x5oxzr%BqFsj%`G_-5E!BikR-2<(VA;T)P zY0w`NJg^0Eu|uyL#STmA@2JY$$Z^}o*6&{J<1b7}83 z&ba3sV;0TIGmfPL+qf}Q5-^(Sf_~iF(T#CmKQ+IEX5F5xr)&F-n{Rl>m`B2Ia*Co0^=fWMH^P>+(Mtyhlr@AoPAR zWcl{uf|AqV37TePBbP<*{lyYKCB}T6o+a(X;ZdWHBW;dN85q|K+1>&0Q^T#f7JB{` zor$V$(%B^Vn?m7kM`#l2Rng{~bdFDi3fXxnv>QMJJ3Y~!Zy1aL@872%L9_mrua}^& znJh#=Y5m3#rw6=#S8GE**3;~^{gUJeX&Y?al$eqYoR(WjCSZRSMwUx zSB!MDROjfAj9JM&Xwzuo8exo9qfy;1vitV_CBlO|m;2y&t;WdxQpGo^7jVDqx70t! zP1~i?YgBu+&@4=I!AcuyWWLziu_47Uuvg%9JNvGok_WN`O;1mco!)yOYkZbmEy^!1r|SRC9zi>2fp5-zKQ~BE zk1-c%g6{1us`eK6=*v|W<#O}!9MM}{TfO-F)ivO&E$yjZG1e9VbF)8WS6VL$GG%v# z+AkDn@7Ih1EqNwSPj~IlDxmuPS!JWq76%BQb=khoKG1P6zYx9r8o$`&^5LJCrBweq zUr5{U;Ib(9-mF5p@eVFG3*ePMqKP`+h$4} zuo$NL*Nisu{neNs^I=S6>@;oY>e$lR80lz_gnArdr`=vI%v-p)0X)?qQ@bNH!ChWe zC6-Id4jXZ>I*j}vqwx}VFnH~EL%}K2hg5wqUSc6%yeREp1!<$b8AZvl(L=> zdrhGjNw0JYA;A%<65ZfJ;c{oZ+Gfj2HCxz0eOt{oO}NT#X9!ZdVgO-M#q`=eO>WU$ zS?O|nd{KXMB;p1*e9eA$Fwo?VHb?yakmz+cSBBl5CU2-R6clkY=&5XqHUZj|&0hL$ zbKWSXYN%-Dxh5}NzD-+fiijRTZYbsE8f=q$#6g;lL+O-E9Tn}NwrIJNVWNYMKcX#g zRxcO2qM_FEVV@H+UM?qbYR-tP=wz2rT{~9@%?MKIKe>?<`X@Jvo_a8UJguLm%iq(V z?<9RxSH=l6DXOc=SH^KtO7eth@B2DC&1&RwB8qn07(yfFtXQ!mHD1HEr?V@xp)J(d z6AHIR8+#?CJEMh4_cEJJOYbT!5ESB;bdh9!j~Ei_Y9-Yf6N|CXt&ZLXJ!h9yZ5->X z@O%Bk;|}8=DLybV$p_NeKq%7Dy)l#e^Q18k3mEU7Xm3wBJ+;tOLOU1aYG~)8f(fJ2 zdC_!}T~bq2nDG8ybxGVZdb6IwP&4;0q{G=9H0sxk28np8KN;}=hpc;a294@FmQ;Zn zn)+9xZRIB`kcpL3Q6n9nqAQ?}^Ye>xYuzrp)85e@wS#~}+PpD)R7?TR%7rzngdU%x zx10^r%Id`{YL=TEcDG9mPH~A-TpmGmPjk8OAL2Yc!z+m1XRlD{{5;w%hBg^j20@_*eyEzzv2KlrMaqfJYRS3wwLVHai_^(-qP0 zC+mymOVhLI8B+Z8oM%P-TqbWovde24ge3PUiQmmzxN1QBDx^P=#7$@{2_ksj3jx8I=O2EE6TLjF}c}r$dhN0ah(4)z4kH zykTMO9Exqs&Y_9RFwJ~>vVMeXIB7n>$%h}$(pWk-{+vcG)n{eA$`$Rj*EMqr&6t{# zyEKh!4j$!CHpub!{8k$AB&LQ;9T}BmLmBGGFnmbTLL38p=#2CcKQsdmBsbFx28;dX zL23Y=^vX{T|m84V+f3xTcdc2VsQ-v}# zp|Be|j|tBP!_DDf+6M}iJIWfu6eD+&T%M;n;uY~qz#oh>yO2FdQ4&2SH-s{)+S44( zc+?9%=8mPQbtsjoanhqrgC3O?K2uTybTzq|eiv}Ly`o2qVmOpfK+^u8MIaYSm*{pe z```%Ze@*U6<&BiWY4UhtO!1UePIo|l$Bl+D2)mgIipC7UO@`CpYdi^DU?VmJ83rQ< zdclw(4Ztx;DGUK4(jaUa0I{2a9wmDv5T6Rd;LY;bWkA^Ejiey#@=MCED~v&5j|M%> z;aD1ggBVhgDZCPdMFwls98N*hBx~P8K`biB09`#pOO_y<0bnsA7n)M(V@Zg93Y}h3 z{G-931jYdLZFwpX4OYs46d7QLfer&?2K@n9#ghPiC?rM30MZ~S15%XX3sN;@(kf^` zYD6THG-H^845n2etvvE6(oCDSWYFp-nYE-(q(o1Q(X51Er-hwMf${*P6oeSKAtMNi zV_?Q~Od>{UI_6R)Xwp;+G?t%Q(@?aO;iiT{TT44r{r)d2 zBeBn18A;Bl3r6;_i* zb&ZD^NRx`JY1P7p1@at^%_i)-dJq+j=V240RR?|Br(QJg6x7q*hk!2(Z4 z^{~MY?n8{(q0X)o%*K-5g;VyR#~T(-)r*+YjloU-q8b%TjrFOO0bo!Ps|O7~;}8Yp?X3B4{Cz!s%d5!G>(>Nx22CH+@MXK7Sd zSJA#_bcOUsL1&3M1~JZZpmUOBUFA-PePgJzb35kh4sV6KTv%4quwq&5!rJ*T(1E%` zp^#Gu!_qVi1CDfbwAz({$7EYBC%pjJFecINeR^vlvgM}-il0QC9(^IbE9i1!5;~u> zQ3G{1g_$W)n%!fTo-9)aJ*p^|dNQ~LgJ$>cffh$P=CK|uGo|`GiNt`hFy`}_QgR;V z)=a~+=omVr6DxP@u=j4i2;=w}3~o8IU3~GQ3tCj{CWGoe`*D?qRT))&z90{)mU&=| zq1}6Qqse?c$4Wb&$j!=A?2X-#jnOu&%hSR2+CI8ry*9|toEac-y|#%y4r$A%?18Ki z^vvZt_vCWfFCaXl3btDKD~L|4z{~gSq44&L>_W}*Io0(wLRg@_kXGPk(0gI+D9%Ao zgtVXvfGl z;`WGkdeIaVTC;dLj7*SKLQU=5a$&{{dMcu=4&WkX?dcIKV4jsk6?`%vuRIlPRk+z} zl984DWGf*(p6T?5v4+WnRc{kLWil9J!iw63c%yppVn9Rk?hq#{?i!Kg8eQ)U5$3dr zTQTe8KOX4jMaxY9>5sAC@y75cC=Ski3ChzHbeQZqW0h3|mx-)5uI0M$j z9Xn3iAf*$GjR+~?5PE%b$7AUnduTt;qye95FtltmQe{AaRXm9Lfo-K~*ii@1HD=tc!AUzB^)v!bAq+he?U+gI3?7V)V$!w_HA?&~ku|S~;ZUlL@>MbKhN6&x-Oa{6=TJ=>^SlIJDu1-&7 z*&5XY3zv4>D;lX-4;zFTil-4+ zMR>XiXiB^PloiYsCx?V3)eUnN2=<B9ln*l zbTFP%Q0Qb9^lg(oOvZw_GiNw~Zr!gN8y^w1W z*e~`T{)(>TFKJcWgjIL8UW?4m76(f^wk5ASwnb5%l&ADfdKEpJ&wiNj13hDWu6D)Z z#dx=Sb61b_h|<$!hw)b)0!X^{2%eCKvPY!O@o?hX)33A8uNYeboTienUpJFZ+^@56 z+vurVbfq-vCah1b-UE-fcmKp~FkrLX2p$Z5ALh{hxi zz4sek@tCl{I=Ng}jOAGfIH%B*c7qPzuM_C`n}KS*dvp`%&@(v}YCM*wrN30!wsRY( zd#3H1dbs}yzzo{_fpsnIILEeisw5s1Q30touNvyWbW^posxG@aY=8<{TfGFH5Ab84 zL-*?(2J~?bdZwb#%~%y}yhS&O+fMuU>qg-Z#%mTe?9VY%$^Gy|ynT-@mo=-5?){Dq zi>G^ZIda>+y3LYB|EjWejoctO2QSb=M`tPt9sjenj;4HI&DCscX&30vGi^?7WS|FY z-?zfYNp7-4q7BOy&Yur|fG!dL6;+wUt@;8wc0ZgmFW8Uad1H^Rik{q$hOZ|y#(a}5 zi;mr*E1|CO#sXyqvK#KRn1Nhjy=`rdM)gEhn#F)Ybq5`<&=-uIJm3^E3{2r_Qc9hB zQ1H>04AzQm!b~^_OgA+&R5Qsktu5Nt(Y1Y=#Gyi4dRc23lTmuW*7v4VgXJo^83bId zP+2Jo%xP5cc_iJbR`3Ud@*PjmBWrSQzt0O>A&mD-pJROJ%PE{SQOEr{J-zi`1`9)B zQ!!>mPWytI73ZEiPUt*;ZfnJink991YTly@WT#C$Ceh~qGK`uoW=^mMrdnFA0I z(H^0xqZPl(>@@XKgOytP;bS`~XwK5DtY+>9lCOZ6;VaPVN$Kj5A;O30%3!nf-dD^8 z1t_n_$$O< z&`0-gHJ3QOGpn}CZW-HMGt!mRl>7)aH@CF6^lW#8=-^*5rTI2%DDzmkrO_f3bfk+r z$(J}n`wxP=U%g##p0TC9wdKNSE8eYgLnUM0AIgnzReELRrc_qT#tv}(NR_mZZKHKJ zTE}F`V+=9dP)GZ+bz`z6jRQhZQsLLWqb*}omkG4{AKF#4_a9md{pKBQE&X;STuHpY z)y~1H_{3jpf6DN?z~Er+m(D379rhT@DK<4fm*z88bn7kfJAV9tZj`1+>=mf`dE@Ar zidgQ6wspe02k=jgO41op%=9+dUe~4G)G)ylE9HzQ`br zTS-OlXkTV%Y^OiIqn)2Z-Wqc4Fj#Yx>7~S-(l;wir~PjkN7LbEZnO#XAWwH3wCm>F zBHFd0cwFgBkg925yM!KoKz*2zs%! zj-@7#C*Xsc;}U^vDHcFt%(#MH-=Hm~AFnO2h}$okd{LEOB711xGiGAus383CPSMlp zIYECT-9FtW(56!TEG|mlDb=qos1oeeFks1l%GsV02R%DZUqNfi^x1`(Tu#OSdLtL! z$h#~W^XIZrAeUTU%Jb9uUs`e~@{Qb52J$TJrw|=PbBrw{ynt;I|F04Rbo_yw!nk>a zp>`Wgd2nEkGU`*1H93pMRFEMcz*a8r*_mz{Mr@>*lF#XJ(Z0)aMr3;`DueDyueUNf z#YMZnnlpl8yj4%XyiIRj_-RDTbkksoLWRH99vdVdc5c&-$lj1-m1Rw3gCs^#nx|G8 zIoQp?G z4p(Fs8=%@_1Da|Q9lpC54wy-X7dth>n=V=GO=Vl!TRPh3cZE7PHuiLdA{R!xrY&SL zEZPLqb5pdd%&~B;Qz%R3ZE0^~J0IqTdZJ~{)ZLcuWl(*$GAGT{-B{POmhQUFo|Z_c zHFdLVT4-yirB!}V*ubh~>nNt=!b{nlY1mgaZC*=j6jg-k!keOzo-)i1QdOOC17hgO z=^6n2X}R1AdG461C36T&LoA+WFeb6#2Icfk@>Tj+qqZvzs-Bh{`Lk_O>CZ0md6I@%vUK{Zqor+^U0ca7R`#Cmil~^m6<~(l+8OyvtXEa zX7H+ExP;88SVlA0$Kn_^hg3%*(eCbO6ZlW^{v233dou1U$BsTS1vb(x)+4HH>PFQZEFWa2g+ZJYL|30wKT_Cq*{{X3lDIxSIk7@_lMzqGNB}! z|EXIaOZNDaHUHGDAHq0?!XtJv>PxnZjnwA|pj7`)3ZYYXQDMaCL{mMSa5@Q(X^(ws zNP-VIKRGVO7!f3q@pN4shQSYo=-B}@_QjK2MlWO229unxau~_+X}fvSUdCFZpA%=v z=;koJlm$_YDOZZPo_pqq%Zwfa-0V|uONRev!7fkNsbM78sX>>ie|&0UJZnOX4J#E+ z*Ue!hVmtW26tsV^QanT?LdP0Vnj2$G=Yj<_+SS<=g~Y4wR#f?+_njw|Mnj5l3PV|c zX1&ScV*~Y`j71Jp`tV$L_!>@KK@5|IWLpNFbPCw*Gg#}GJ&iq>Hg-ebJF6~8Y$ld2 zIWVpHOlugVYcf@Ha0Spm27!NQABG(uriToxF%GWb)D^@e*N~L49bwhV^pN%;4)^hmQ(v>w|`dytzc6=f{x-Xc#m!TL~Q8}@QH zZ10q`K3ukj+Ba-L@D8Tt;j*(88`T>iUFOK;DYrL7p$Ued;mOyrX-(1YNLLGHz#Z+% zBTX&c5xm?cYm6wWDcTurZ<3TvR*y78k}HXHv`IaYueL_Jx><~Yid0cbU(y&f3Q%rJ zT2)NZdvUv^3B7J)aAgK@3tt`!WZJksrXeA-V*;)`j82635cIPzz4xws{hjg{LFzp46J4O zBVpC&5A0!J9sA6HJ*>nThUhc-Ta3jWO0l0LRxtYgVvr)1r1{U73v@7jU}`3f4#x#z z8I!9oo(sfeHZTlkd}fGoQe}Z5M{JNt$i$A%A30)YAR`8`42GPa8OMl8vTiuc_+k(v zCQGi9%7@PvF}y--kdaVU@=nV(J~PBf(*Gw?9?l3czz19>*B@d-6^KtIPZYHVO3Imu zlNeLAKB@BX86XFne-5ua3~vVj?R}=mk%a%}u4tTuYsAh%v-~^|gUvrad#&=!5hErq zMxBjT>8m`+yy}x6hQj~80A&MwKuk?LQk*09Sxq}WA#ym4CIK(le5oI*%;0Q zb#kr|gGWx<9*Hkp6OBz9G$Bo4#utMaF$MF}P5c9+JcW4%0uRjerR0r!BV3-%Am z0_79}_^%j((^mUg15Ro3lI)9}ZmFf2GygMcDdbN^Q>9sVW;v{XXVA)FxUbwcPh)p< zbJh*tjh)sRSfD6tR&3V7*@Qy zLUMI*hYF*b)E?Zjt&PoP^0vy9A4I3rH(yyrnbIDqAok=m)-TB=V&Itp>_|Sgb=r#5 zb3^UqUa8#VvuI?RgGGj)ftc~&)-qFk-{ivB>77P^=ID27rGHSFSj$azRPn|FZA&&f-3tT7h-t6Q8SJCPYD>O1;9tbv65!-GC;-&L<;-!GYLDejWDZ?phH>?@ z(W&J ztdDQnWbvKU&C`7fCr{?qSpo;yTv~rywvOJtBQMW-MqZq^<_&V+JY(0*u|r}!4e!L@jUZ^aUCbb51pld=sbxyc|hC=}ul zr`zEpgb(Kk%9T-dIH$mBXU^)%)*YZ7nI6eD)!o0f0i|baw6TX}Rdvs$L4eR`wa(z_<(pLAdZlFoOeL3oh z>;~F7HK%}++&M>B6z}yxzVIyYi5BtN6c_5D&hS(kG|OJ=>T(l-=52K3H`ZVCAt6HI zTh{UP`rFpAwDY(%hxWf|9YaUnw&v06zp*aYc;TSC=Pm0bc7Ku$n|}}@1WYd#UU3=D zD^LZf`c3N`iyg6QCrO)=wuZXc)SPx5w@#q-Z(Fsr@wnAa*Su}5OB_3H71Xr!w!E4& z4|zJ!P+V;FOECv31Foc>a2kDSB?oqFm$1#s;J95_b?a)vKJwH)AZ2G7fdL;AE$o`{zwP& zvWwYa3zN@+Khn{Jx%dni;2E;&MulTrV+wSwOK zS@tMrk|zzJ`*LB$au#9+9Mj{Fxhfs|jdkg#=AnD8Hfr_Uc$K<^8{cmkZR7MeyUiNa zLoM|2i$CLn1sXlwyg$cH@%=fY=-2}}I-2`hPAMI| z*=pmlYk~!CE>P4x?BTDOo19};1tqBxmX4pHOHLR^@pa1t+IGU&oOt}jYzv3INqzSe zk7qcvdwcr~_|*G=UMXxDbDUvB;5EA%t6Qv0`BnpmlJ+SOt`muBB%T}=E7rjb0t_|U-{ za+lCEMQRsyZm~A=5VCaCtnt#g$m=x2?~qP6emmXMgQDNzU^U$dMI_jUB=DSQzd+Oks* ze5tfuC4DD3l;e!ik-##xRywEWWMHB996CSUf6!{q_637(Pe3_FXZ^)iXC}ZH1{A67 zP{m)%vB>~=Vx~n;SN{&%ol%M}ZOmUS&`fyHsnh6cRon?xEoNx5^_Q3A|#P?MjOVR<6W^^CaUDx>IF{v@fKwX{Vd6)>`_f zJF?Z>IoG+0wS^kh?ogc53-Rf$rJC7a=`85q<=UF1^QrjjI3JD*Dd%)oFDurlZlcm? zZfX3nbF9k{)l~WfTwX+}ls+cqmi&jsg=EqC5!_P#nAZ53*CDW_%N=z`pzPtC0qNsM zoLL{-RK2Zv>>DkzCx&|u%Qgfr2LAL;DzrxqIGPd4b; z2~;&#+KV)*UCX6jKX%SM?Ht@x#c!E4ioX4DmN728S*{P7)*ztnx_IBBLL+|Sf24U` zd~bEpNUuNW5BMwnEHJMUmK32{5!bajUVmOuyze@@32`??(;9CivM#$<)W=#q07wu}pkY*Z3{n3c3REV{w72jBBs2=KS%^Jq7V4drX{&8jID< z?)W9wTk}k75J7jH=rXNA;p@cs)Ry8gD5)9oKyk~o20f|_`q$x52Co=Eq*iyl^!h>p zL0H+bB`ka>8o}C$N`3NS47je0TUM9mHiys-_K!su^~YzeE{p|y(l7)9UKEFrVJO11 zCKU3nYp!f=HmwPZu60U}118fN0DfHx<`|p^LmR}lWlf8i)-*-j>(BrkXCMX;o>>}S z6wDfxc_)PM>S8mp^4;=?q8RBD0w%=v6vy1n-Y80Sd6{5K=e&W;Fcp|INX*54o*mj6 z3oL{?Qd}5EIuBym?74E}o~i(#&wLiywBIo6Z~E)j>U6iM)Y;r^pq}eE!=b4*t*URQ zOk|IpQ>?~`6$hJg1R7mzFtH=h7RIC2{(V&^I4zFuQ!R?`+faP2dmsuM#t6iNh}1aH zBkD)w*Cg=(vR)Ppo26p7i47*uWu2GBGWNSg(Ff3qz9!1P!!|A-P-PGMF!ElIrWfbX zRNrBn;*fCBkN4aFk{`K<{_H|{Ft-Z<>rHYQm7+JuN*R%Ze2hL~S)*DnEuLT)3Hn-c zhpi;W-UQ+$OJWa5rQt8*1X8^ynVm3VLJOrEKHQK>Lf=@Uq-*7#55cIt`0Yb=h*9D4 z0w;a!8Cqyn2UO7ernc!E7ST;K7KNAA0cN+%i|^qujB0`s6Qn`o!OT15XClVvhW z5ynx6C;s==k{GUJU?2c(`Uk^}8VB$i1cIQ+00#@590ff!0VGWzq6k7eM*xa&GrkD4 zj1bZy;*8^v6+8=st_+z#d3Y#*3v`=t0}&y*MFe~XdyFE%2ZZd3BBu%LGlF`;cs&G| z1*7;PzY_TjHYPtF@Wl^4GBUo`tBFOV(AJ`e14CH109gro?3fl8BSWAL@?55jOys#D zQZ#F1;>4Hai+G8Zjyx9lnB_$?@))EeCMlvd%WU$n5YbYV>`kVLoh!Y~A{?^^A{lS9 zi->d>4a>thH1fM9)>ei~$Yf!US!O7iDdkChVJFl`dBOC&n1_u`M2c&T7foVidaj4T zDUccOjGEIBOY}++tVINVce^1PqaFalhf`eK;M`5kk#IEV6QkiMVw(HiQTgBk*6OBlm3_G0rT)M{^D8oS1K*an#E|9wKamj zUg<<15e%pqvRpy|Fyv@TxG5Z8Vo~fJpQ|aB%#;)au=q%&*9W>_LJlz#B7rdDZqN-N zkWd@@fR3WF0Ag3ZN_HhSfu@>}69IRJ0wE&KzKAA?VO#@K#1NAu7$c}ejG*uZ!6E*8 z3~Uf30?v>&t|+=5MMprDt_TK5q9Jr2q=bO(pdk!{G=33uok>A(8L&W3x~@=6G67a(C=cQt zBcf0n)GhfJ2$Hc!p!+~pa|}!a*w5mur*5MJ7Stb9BR`1n^2#;XN06OP0dP#eoa z)1iB$RVC3jau@+aIUFtrfQ0gg91vSzKsMv07^7aq7aQ&^c9h`~5OFn#$T!l2!v7x zHxF==R6odow5(W)GY=~RBu3Bp;Sdaxr2|@iDjA^6>>zHLa0DwB8TvpBf!jnuJyGa? zXC%5qnC}c0-IC-66F*DIjbTWW+_Ax;JJNg-(LFq$WM@ufFiM5!&s1_N@{eHwV`9U@ zO%dH`U<*`YB7>o1gg;brdqgHZh6S7SQX14S{BgGwxD{9wN?$V_WM&y;1WuXLhb#&J1a8FQl^6Ft@=7p+>>j=b6ZR^M5 zUaPfeT<;77p6Uvg6(M3fUTMI#KQJ-6mBpQ^C6sU!@S_v(RV-Dp<+(Ezm2s|a^ zJ22-d0ZD@*3pYBW3d?drR!$-1{U+O}MKtAbRlIt9vBguKTyc?2atNWv0)FEt&Aj>n zM~93vcn7a6o+Q>*%EO4rwni)lJD69SVpzB+cs%V6qnQKncJxk@oJM?XBlV ztI7~(rga+PrbnlD_jGk!hz+vQCgtapZ7oeb8>fk7(n52(wQ|skNi%T|>K4)CBTch& zBx9%IJAy_|!E(&bzgP)<2kziUF(S0 zGUy(km+1~AgRc*^+%Z!YW3v+fr4e`7ikY1Hj~Va(fEf?WJtCb&$4p+6CShRVHz=@pa3(!NE04AgfXUC{vR{mf6RE$EHkVCOxgbTnek3J85l4sFpdAy znDCfUVbC<@bQ6KIFese08DU@|z$RfC69MpulTHLqJr6jQ36ISKaMB_(;X!L@_>URy zKW03TJ+nhF-RS>-84r#4JWY74pUjL03i`L0@G!_Y=@^}4rg-lVyC1SYUlSf<@`ILf zzz0@jGz4QaygleYW;|vN4gAN92PB3btt`NuQgVM{Gv0uP{F$2Ym<5k5V>L(qJtjOR zxnuuE6CShR!HhWE>KpjLN>KjER^a}BVa8*dRnFLWH-Y(Et(q@;&0z<)!-SWeqh88`ueqxZS;_&5?Xs2^o%)@8odhinD5FA?4WgQnPG1i*iGKv3x7e z&rQBB3Jz?T9Jt1d0K<8R|1ZV<&uGDC(g4;#`f@CP)Hx?_aQX^HUzB+O&}zhOG9 zI;rVYu^cm)sFvX*r~1X!b8wQBY)B4Or3|xYVi(mbcG38y7mlzgcrv{GGxnL-Kb0zy zo4tF&*mCZ$hlOc^I0**Qfdi&-a8sm=osZ)v>rqF_D!RlD7*g3b;vS(Hm+T6=u}*1E zJ-7p>13mPLrG)AqgsA-VKUtazX#J zWk2MMtAF4h0Tua zcUKP2we@SRM;Yz$qqmIjcii|Y&vnF)-eimKy?uS$eDBi!pWN!_xz6~}JEz7^?9s=c zywlMi-n)Q3%?D5I>?TEW5w|~mr_Zl=~HLAbS@uzjA{eQn-r{*ra?toK+ zEvQ7HnOTnh^A3EAGxS`35q4W#u^mmD&_DIT4O!Y!ch!;Q9YV8`;&A#MQljy)J1_PaV2)jV)Ym8IweB?E? z;j)`1Pt>R$T$6n0%lg=^9azHJN0#%=V*F39Po_UwOcM-f;SPLv(&5XE)8m~#KezvD z$F}oaA055XSkdqP_0cTuONrwe^C+IoG4*kV2VW*}v8IR)6laa3V-IK9=tO&79$oq& zZ>Ek5a~t}f{Gf*CzCzWNxhwlu|LvPRcLi0?$bGK=$iF;1cV*(lFYL}zc(R}?H!=%KC zLN1TjUWF!IL!1Ao!&cf!oGwve<9?y%b|vZs&ZE`@eBJ<^SZyn$Yhrvc39so!Cw@}K zm2y8)g!#$8)1Ob*PD+SZ6zj3($w_kp1!tI!EH837nauHzsmrJhqFm zWvPjIgZAanzFOX|lTM!x(t8t)I8CgUpF(%pxsih(jURlWgdTWNH%?|eGgaJ2s+ri) z;?~E-DH_#18`wxxCSsGgot)vic()VW=)%;!1)MtAOy={j|L%>+Tn@drgP#EKV|6ho za8?G*Yw?ZK?pHWp0=J%#DEEehAaE0T^yuQF*6W(H8lbboR@4Jk*Cgx7yCUDs9(fM=TDEjh5PSnBX+5bshP|6+OfD0((!Opu-GQT(^K^Mf^O+V=pF>yEl+zCBEZ9f zF{rr%D)o5oK)DpFC&lA zyYg_#=*yguc2457^wpAY$M;3FuhT9kCo*GB5x8Z!DSvRURbmYe-ePT&H_lTamlgv*WXC!8w$Ei(P zUmEbk06ureBTd|c8m(WW`bM7I-FG*#4&D7oE4NRh3E)2M85Sd2T%K&_Sj~bM)4$+-U9hP~p>b>?gX7iPqOR(sTcn z*t|g9&QWXxU!CaxAFf`_Jwuyo%-%%hJKTTs+>fYqbM92S;7)ih%rdFl=-4KkmM;8u zZeHS*KX8+H-Lr1&1MpH`JkOJuae{k>=YCAbIc{WP><8RNgBCpQjj8fT-~J&tneD;) z*@3`08r8Ax%)8G$%+1uOey5lFf@jyOwLiquKcS_Cd9OVDD>b;tPid(wZ*}4Vjrwk$ zd!9Bg2PbzK)GzSd5!(Gr^$6;`Lak4X&sM*|b4Q8WlLt_l)jRau3#^cL5`VC%tvvU0 zR?2yawWHPd^87D+m2`MHj%+jkRijNf1@%jO){8i8+3R)tJU$=oQ|H+ezVYgx@|u@C zl_D)&n>#15$f15r&%HwX7OLN;*oS-`6F-|0ouWFf=6~spq8+)r;s^d}qyt@fd5H(S z>J>cyYQ#sSS?a>X$V&BZdG58uK3yKiCA3xQDSB-+@T5cTddFP#E?)aGy8cGO;?8@Q zqq=AF_9o(s)SVo6j6(lnonFj3ef)+{UP)qHy&4+Jn{@Pn+;bCk4eEz@?kzf6rJk0U zd7gT_L0h9yU3)PrI!JrhtEXt*JFtE(9bK=!So8Y>p*d6^Qt#0G&qIZ?6GuYoscP-( zsQ5RD_cZ1#N$#G|x^d%eI^a@&EAhZabcB0{mR9D@ODt+tKgM&vrGrA=&k`?os;BVW z?;d$EZ>J(*4{T8{*KvPHSRS^n=Mwcg^LIEV$KU7vNPTs=^9Q7MV*gHc1JC`5I6d#$ z^Ud7C`0BsSOzkbFrj}m1UG32R8EyXy?S3|AYGUfI)j#2x!2RDu=$GaaE>ZGZ^{>?I z#$PG)y7?RQ{0Ev*)M+-4PP}}ddNR-bjgGp^cPH+DS-qU6^t6NMQgQ!G99)}kmx}v^RNTL)bXNY|sp5phYd=?ymLC0(jxWaq;#x9S(xIPPRM^A3i6ejXM z0xGDXM;v@t<9j&BKZ~~H(%69JO}af>TS>E?)oT)ETKBtlLro^GInP+iy9x^qH3=uK$m8lH_#hijhXI?THFQfMK|n*@QP)>P-P+Q{;hgC z7p7V@?@&jYNi=Ee=)fK7EIQ%Sd|mDJP>APmrxPV8qavXB85@QrRDZp>i+)w9vC*z) z%ntg!mWQUM;)G+GGP94329d;{FdD#qc}6Eh9OhHD$g9xkTRYb`#C zRcVZzi=()k`$0wQN6*<$uoU+*ncOM$ti^#z_Z$s~&xps9A^2pMQEy zGlx1$HA_;+X=U|}rhVU3FFGx9h9u-1LuwQMwUZ;{uHTrSVVF2Og^53sG0~#6tfKwh zmZh}!D;f9b#(ih`hxPEJuu_JbuLJpn@23;1+~ebc7I7stbouG&$(--}7y> z-<@AbJJ(spW=S!-F zqU=}>%8d8p5gG^%h6guHiy{lYLVWEkbc< z^-JNTY6Y)PRQy-r(F$)>1pScNLGV&T4!tVq3Svo*TM_J}0VZz1ue>VBxTI<}$faY$ z8#msk6ek9in}|-)z?^AWn2MUk%WDMep9uJIfV7|0Cp3Yl3grqHRhUv(n##iF&q9~c z#=DAhCBA8Vd{Xpw_O;WZj{H*k!QbVYMYHN@&X%kN;5MNYtiYt5JQ_$aKh=3Gm}5GkGmRvG_hizp;-L@>)(?r%_$CT~as` zbBg$N^(o_|CgaFgXJ7=vbx=B)AG6%@xcg|DFl zu+khWdvI+mLlKUVZ0=Z?pVr)o^_*{8mQS(xOXF$0EKEDp(nUE+wTbG^aou-sE8 z!244uCoZp`fPP$&U8o6%g{c&JiL=pj2Xsar+>Bv@SHjA3F&2;TMIoYvGw?^&dPMGvNI&h9OqVqcJp_e<_a4)4zoUX`$ z)g6S`sjjVFeE#Yhp$#W3RYb&8A+qDXg9Wd1xLwRd&Au+Pl(Jz+>N5x5E-q15c~TMj zvgzFmO}S(~o@3qP;t_(WT&14Ml_Lbx7*4-?+cJ&n&TfG0LnFd<%w7t6jS*K+Q~#yb z(V!5ql2h?VM4Ak)fg zD`Er<>u_ecrj)Ihx{QXBf#J_e8-CjDHCbubisEsaDfWt0lvq(*GDlH@q#<@pQc%CO zV}nHg%W(ElZNn(64+2u*v=iF>u$?Mz0`iwWZW%XHW*y;~RrV&kDYYn#*PPVXpev#^ z`*Vx{sVfr&L|N&XIWyq!I-QlyAPV*+OAdFVTA+v{G({|*Y6nM92s2fA8awD6%v~~A zfxI9ZZ`mXiNLy5v)d)C0Jle`mt&iHN^yA`@wt+rD8Y}T78xZ=aJ8^?&2vEyT^mDCFf4{_N_=S+ ze<=jtG#YtKcO>!ixqJn#rqj{I{3D6S7w`*kHG>Y{%b!Dg_wbg)iMf22n%=nCcs)IE zF28Kg*ZC^CZ4ZK@AwzRdJulfNX3{yYL57T6&3_MbUCXZ!aqUm5`BnJ6^H*F&;-8m7 za?{ubTM>=e$%}kWBBKDK`qu6}^dEG*ajaV_Rr9{NUuZ2}3u(p6C1I$q$K zsdF8_oUK||v82IWz)xZd!8x@47-v%lT=ZNYzYLu}vDQ{bk6*ywrH%w>^BmquS2yxK zH2Ih=mu|S6AA@&Y>v^}@3#-I2T>;&8Oji!$93P}zU*cPAku6FnFoBS#*34?=TBf9F?8rO-((k&Rs9mOjt#myZM>aIS*A$ zAoCXfLR#~Fu0WxS_%gLU#Bx^}lHqZww2i-#cDM28Sf&JmzJS|b83?$sp)V*_(&j7q zw+u`jLpz`~eU-maQXml3jrV9gnDy-W3ctwE8rtAs7v%aH|EwOgA_c=fzKFMumKdf_ zVZc7G%pS!`X%`q-uHxUN&W-#v`8(44+}#su>m>0izLREM%y*b&oaI$3RNS|?8LAb) z+WGt|-c>YfCI6<0JNHMX246`J?BH1lqtbsC7tjYg_)!42`4ZfM(f2%C1)YBh?=oX5fKjf(cMcluf(3U+ z4yc`EcearFEAJgV>HrzZ~V;_G=jbTz-36*E6kdJlgWml&~!e;bC>4t^FLYvM(gwSYD+ z<-Lh--iY9AiM?C-^&IV5%Fm&Sy?mVIFQoVG;;&3x-N|o7>5FLdE&R1qcO}1>cD==A zCGxiL?_)K2F$t47SaNsp4R~q^U9y(HC2{*sK7y;-RQtNr?D-AS484vlH}jLyYD`>k z7e7_%Rej?4^?W5q2kQ96G;0&zmH5Zkc>^@zrL=D!Uzo7%;;~Z0Eu+4B`AcZ;Rs8w{ zpk2a@q1@}8FR8tJaAS>qC&KjN>$1MBI? zo~+Wugy;AJChZ!maQj$02rElrEf;>YaW(b5#9yN0R-?v9;@Mld0xn^Fh5tPo*F@9z zab<~#uko)zw`-=*_4>t$`d{-8qSy^|;L)r_iQ0Gghj6u#4ty`GgD&_eYkJ~=KLAE1 zuEhnN#_xlf?!X#TJzdbxU6y$L1i##%U8_<35^G*)1Xg&aCl3CdU!pGKaAF77iYV3C z3GESCNIYM2z=P0gSpQ^qp)M|c0kNe6)aTAas7Mb?TMN`hK>pIAtOELeG^a$md05EW zpz$N>8ZEstkI}~A4^4#@6z2E%oBh!Mn#5A-dt6h>ULKvZa?c{QmN)q#bo5s2RE62; z@3|vr$&2|#pt9FWC zv9xU}p>tOj%|5681ptX!5_jm zj7S+xE}CLAxRxNo5u)-TxJO#BFj!a+urGN_irI%;7Kcdsu-vo|cHP_CBOALq+B>#% z(+N&jq^&uxVOcfRJ!%Mnx~dOnaY2Qv_V? zBkSyeP`c7YDy?=$kEfizGdX*dRdF~=uc}Av73t;|vxV^+q8*(bt=k*Bw{CDWc5e*f zC>oQH{3t3MwNJ?z5t-0EWaq0BZ;KU?)~@p8JM4#~2yWgI#X+zr8OOiiArq52Q0$V` zb>O_))=+1%N`XCDu0*>1@&6Q8#H8TinMXTpm1YVkq_+wxoYJc(jsR+EY2Q$_qG4WT z>aHCFQC%Lj3+%S&pIwv6Nj@*#UyS?GmfV#KYgP$R7L`J`g^F5%MeU8SPDL*4?7&pH zY92dNV(G^0;-Y0W%NyXMnR=S(jedfqW_%1PY3&>YYOUH9YE64N_2`i8@sF7@=kX!)YNB4zO-PW7~nYKWgTRGqAmWYM$4XwA9HijR>M1 z3P+lv%~Q5)-PXJPBG~qo#`bLNiiVoHr_=L#lYK-0o+57V;7n!+%-le2VWE4aBRtgH zL>2cVZXzcCBRSo^tP3=%?=vqhNr5(0v2blHSYH$KeseV=ieGae>}p2zxMqaNiAI}T zQRo*@IO($|nwp{rjp?pjSBWrjuFCk*6Qj%$9|wkX-c`Czqx#W&F#c(uzSwbt#KSNG zqZ0FQtQxxJtF>VCyVq#{ANIZkKC1Fwd(O<6b0(90AsIpl6ShepgxMD%K*WeBgxwus zRz#Kr2pZhRsftRig0Z|-tAbkX;sP!lTQ{h!RqKLXY_+yRwc5{adTVQY+pFL6f6p>Y z!Yb;$_jgCb%$zyrJ@0wf|N1;D*xb0;&bIx_KE@oy4iGnwX7Bgd<_HnC^AhtUpxYWd zjvW?io!h&W&-V(L=;;9u(FqcsJvHO)Fms-D)=X>vgl0r~Yj@ z!6KZmbZo4fDJZiAahF-RfOTxDyGQwsD4uQ+`q;b=ga?&fHgPtE<<9#X#Ql9gu5-d~ zQT7zu(O=i4e63V$kgZEZ(;uU>zO$mzvaw425b<|rYcFnhvC?F#gks1;DB#Hx&B7&Y z&tF7D0X2w-0s>*oV`hX0IYopAr5U1{?xsi+nJG5QdS&bw@l{z^s{B{0Xtr1{LU~_O zI=){yBwL>58Dw*cD^6tB`@~-1a<=AC#4NqnAie~T-IS`GY=gyel@X@!bKwfMXHMl* z_V05mo0Sz|@g|FKC97On5mDMFi+_@>Q3N)n6=8z-FE||$o2WXnJ7{!)+%iS1lFo7Ta|XoE3wWYZ4uc)BUf) z-}Nl|v@}c^kr3au2%FglNinIcSS@~4P(EHRTFt^1MOcfV38i$E_;*R@XC41Gk5!za z^;NE(-l#OaUa>@A2qZd*ZEml6Q@PA;J)i5$Uu7MlCCJ<**6%4FUM!YdgstqA1(g$x z*q`%Qx7G0v<;9FRTec=d)2~LbJ!e||O8ZRfx6D+%8}RqF{&{~L!CrpLJV8Mm%|jO9 zW;V|!PEp*y6rbcZxP{HT#|#zdxyo^LuP2p%-z$F1HS@2trm40!jhl=y@A|qp+Gk|-fLmo?x^DjA7-yCcf7+cTT@nYcSf|Z_n$V~cl}$mS*^=)@9D<9ALIAlf!ip` zE9X!zIj^l4s!R)5ALbAID9hZ5kvd2RUM?VP6?MQGQDVg!@jd35YJ1t}xGNET4oBhB?PUWa)%a&60kkY!oqHay};*T-;?BeHfome-TtS3Ewi^~39$+= z1wgjfP{Dx^&bo&c!8`j_C0jFF7%IhEv4sSiJ=3Q1}dH2VW}Y({D}J22pVCtpSAvV{`UG#GYukR__$j zZf@KKUzFI1nF%m^lByB1|b{_fUh zJZy`d=MCz|;dDl)4*nC_#p(1k@+S3LLCVSZzFd5<)r#W`d3)5$)cN6z8&V;FfSSgVcaI-qoqu> zAY&o(Q;mVPj{*XEA^JP(dbnnITK}e%0&}rH8Ss0-ufWUGzk`AWq$lykSh+Zn0d7Jo zFoGe}1)5e$e4|W%TJ&i3Rq?dx8?C*x;_;;%nJzdGAe5Hk>v+{mVHXWMU+Ko*0C^uZnB$02ABDb?H$=Y-Kt_>B(}x>P;S?hjFu zW!ph*z@uqDtDRbsy*u01%kJ&6mFG0aja{~Lr}8Hs^Z|iH19lwa0li)f(2&=T`GxpF zYI!^tBD;Qv82xqi|Anfyk53Ol55%Cf67DH6ElS}w1U>{h*LS(AW+>ehdBze zgTR}hX;024RErM*kbGiL(Lp~Z;u4G*J~-kz|6-sR{XL-XwQ-=ej6J{afzIvAZ5kgt zW2P{EICUGiva~kP`cm(6cKb}BwF_hJ@EYQ9W`mu5Fxys}9{A`vPoDeWDXbOR3<1WG z_JoIc^4tf<6wp^h!2Jd=)@i}A(sCGJHS=w|7_!+$ia002{x;t>>U7A0xne3XUl7g3 z32}LN4`fLm?s8%W0#ozid?+T%Fm??v9=TbjOsZ>H+q*WIbmnu#3NJ0{nB~+kNCO&b z205K-M)zh>EGD$NrfPx`IF}4LW_*-wi&PBps8I!)oD5irDESg79;X%2RtOX2p+T_5 zYrFHD`Rx3VJZ6Dz|5qb*W9_c>u=(o;Ri{9*J>DH%nOql7Hpg1sEflPypPt^A%M09T6@>7PeS1Iv{C?&@jMfqvBGM_+5zk1hFI7R@6EzywP~6okx01Z z;*0xSm<@gxNxrd_-3ZoFongxp?R~CTFzJtbQ{Y!|rxKxX8?m26;>lPv>BEj51zS`s z3O{C)d?5Z*81Z_^ls^&*r;_nh#Krc%gOIYD7ud$Jr`t*@*|xg6+L373Wz@1->-V+B zTVW36lDHP61=~eceLcnLp4aBmgHI65rCouHi`$AuG3SeNVhk{c>g!m`Z6MUf-KuP z+1wZ98g^c_qZ~mGMIzh0z~*OFPnOkJPNb-3C+Qi1R&-vZg}r!td0iRrKd6HXoRpNz z_7vF~5F4it7h~uyTMx6c)?U$gH^Sl2QuN_m^%DhQ^F#Qc9qNs0hw#CR;fjvJrc=fnBVH1lYKo@j6JooyL&BtGCnw0 zs&_@;pn6Y)gyS|mG<+F?2XWC!-3h+9v^sm1BXS(xScH2csE~g`^!N<*%i3sSd33dU zKkf1C#?!4^v$mI)sy`*()#t_>T8-FLgpL!8%g}5$z-Q=0to6P&bddAy644S_xEwZs zb~92)Z|Sk!#r}Aay@Kt&gsiAN_915FpIuhFfNauP>+EZl^XFPO$)jm}Wpf?KKHy6( z1_nGfSxCPWQHIE=rGJtF92Q_MNs|yYNRIW!e8{kX;6l*{aYP^R$OCLf$V&Ie{UnJPOEQraF z@`I;NbsR$>04xMauJj)UcoL(MOX{SIU|Y-m6>=?(VLuD_!OMGtR6P_flQd!oMM~((OoCghZtmi3lO1I1;&3C9j~09^3%eaglOh?bMnV z6oeoo6h|spI`?6zBna-jtivoRz)PZKq_7~O8U+i^9J-Z@8X5kG_)Al`GH+{E_1Z7yxdyD>^sC2O@r)-7C%L-I}Tl&+fuZDU;yT{mi|bP{?*c{ zu{~+go2=#QLV!WN+34NWRi&Z$i{#J(AgmF!5TrPWW>?ogj5oiZL-wD z>VP>zT3iI=u-w+1#HNNT=s&GcI)%aw$)-VJ)kIzvz&Eg}s5_^ZbN_++Rkd88Ozmtk zSYs~oir%!B;Atn50$--f-}>pUs{Hu`<@ zu3piOYA@g%<6Of+F+3TBt4*qm9&Kl@EEMV*jf&?N07=&g35v~IE>`z%8wnpP#HPvY zH!}ohS{L)9?JKeg35G=)y4mto_yy^OcpjeZ2;mkrLM4o^d6)8&b06O0nLH)xoH8Za z?3{rAv?kJ9$EO#UU*mx{(Drn?SC)pOg2T<})dKn)T=(@nPok0MqaeC1x_TLePY9B#UG*eNGF>qW051TAYKg;*@mO5!Y-(&8b+V}S|-o)s8iQhev| zVv@lCgQ2h6UwFhVz8i)QOl5?Ya*>W(;IOGdobqCyNoOq+1Fq7RMUwgg*x6`zNO1v? zmKnodV>gUcBukRic(^tye;^V_^Q}tXjqJ2dV!_aD5Lj2U-ypE&MaE*?%$|oW5~jEW z{&TAaIXoi$heyn*EB^+4(%8wpFhJ=yKk&Wn-Xz~@a>8;CaB~mc98CaAAZWngyrL*z zb$|CJu;N9y>5#fZ>k{@xR0gg1CaGu)HDAtFPqoih4RX$F`1`dY_E>>X^qXc@`H~et zM7`GcEW(?L*oUJ8&C+XqnH^VdgZps3)yhs^Z~cyP{Uz333Fs^pS6GLzxmR0D*wU-5 z#ftk1Ymv=*foQsa!(ane`N_4`2^Q;q+!RXg!%`h!j;z182=6H3jdZVf74ar||Ggr9 zo!-rrZm$Lak8U=Y#! ze<4y3-@{hOV$ae8eyE5q(80ec;$C|HyCVJ!@8*w2vZkL~y-Mhp))xSm((im3 z5&v&kfw=ciMchYc{-+|oP4E9w#NX5VznS~j*3nAyAFZFr;s(+5Z-RuIRxnwW-5*+? zHWz)IdA6d@YPx(gVTw#;KmN$-x4w<`_=I_`x1v4%VSU#knN8sF0lbi1awyJ!TYqao zu>B*Vgjj#G`Kv<->R>;*>{LQHXh!c0+866D!QO!uS@p{FP*iIS4oy%X5f5g&^XRJ?<_Ista^L^O#% z{D!440e4Np4wm5rXuJ}LUrqS&16bt12SenW;Qhs>jk|+m6Eu@>d%&K%7-6hnbt9QO zmWum9gN+?MLREAFktO1VYXs|@-^jSsU}J2RPgrX8-4!c$2A;e|r2)eB9dR2W$D zPDKsL(E?I=B9}`BL6@k#u>6Ar6cNw(8Db651jqb1ooG?{=>d>nXJ4Q>gj*dncKqxF z=qT+$sx!AeyF=h8CEn}oi*uG{ze2VGfd#K~;V*P))~Z04L=gLn=wHsRN-iNX{lk5P zrHYQmxD@QeI3r;%gn~)XLSv-kQBN$8v}4$W<53@UHp*_J?u`dRh&_!&aW47ssNtaM zAn7+2!~lx;{Bd6(oJb`@$cIb#V`1<~qESKZiBlsn49c7rTAuhbu*~A|!9K@}^HJxf z^^Pd)-9Q|t+NlTV*1lAJ_YUk`@Ed4n=Jak(=fWzXjv2&Es{;Y5g3psq>V1o5QF|9t zj`tVlCGTB?SX0TQS!lhBJQ#YZ-HV89emZ{lfT`4MqerAJN3{#Pt?;|YmnKI9H5nfb z=tuN``l2q*dqubxZ$J;j$&=H=_G}kp4dIml12}p(zk}fvR697$dzf>-Q#0rFuwCuq zWPTU(!GKX-(8CB>M(3+N3@}E1=a?~F?P}W(oWI0-%g@E+$eV;(4@Y9gROeJusH7c{ z90jN5e0Z=;#6k}L|R@#VOP<2pljx?y)iP*|RiQ`QsG!WZ`#=XBq_L=*K0*083U@XryZJk~7qX>^Ll1-T-+A*rFHZ zatlw!d9tjji2E9#sI{=`-vV^d%g@+kc2m5ziMik4q?AZtcUYV&oNVe(E1KBrcS;aZ z@023tNVIpwH=HukzvwKN6Yi4_zA4vO`;c|dQ>I3-Gyfr1+xuF%3l33IY|X!_Mm04< zGZ_9|A#w$x4(wz|Uidp5W z5J)#~s0%9HEz%Y<+p$8L%dTG_tzi@AN&r(enM+vZIh9E2bV?7(Tf>soYB|+pIaR5g zTs+hKkh8%e$lI$gMuO0IP0qXsCert2N==CTH5cKpoK2l0?O{J2CIyu8DbfeP=R8i@ z$ac(^uBYNF*v@6r95(kHK;~^rO0CLQTBY@Bc)E}9xv_9f5Mq;+TSXJ z3=cO>x%_ykTe7A_)1D6FR^t#lWUjl_cByFk-Voy`uRx7iGFRhQhq5Ou%@LFZv!w?h zst+}8tUalMC!H@?%=IxMvb>ZU@)FCg(2P^8Ix$*gk*Q^8BbPz zw^(X2+nzI-FBa}JJqLmQG4YzNjD@|tRQj3S+<49G3<6mDM`W*HFE>cjsVRnEZpsMC zWie@EkrXkRXP6?mdV+X`J!4g-tdvU3QU(A68@r85Pf=c9Ej6Q^nppW6)=kQFJyM@6 zy=tsLgVo)w#;0-4)Y|SBKxox{;l#Q^ zmeZ_DJi(@Jm1@nsUbeGaaI)X*vX#J%kBQy_3GHt}AJ|I@&>1aIAvb3&LQYaEQa7g9@J2z|%*ig6E0#N1=je zHE^(385)}|x_|a6cvb^X9}5-yh}7?V?K{^PGeGi>kiB4_f#s;w?|l1;p?#;b>NnPb z78qF$qJrbs0u>yJ$hdZRvX{g_QE~I1ykfs&%!6E63rI1z-!qkq&LJ8Psqn@ZI=?nV+ z+T~07!^p?*AYUN`Um=hV-Kj_<=#9ZN91bV221H_z?h_CT;6O+O6Uk7>7x%?Uu{VG{ zIS6B)r@7AqWIy(s%k33aUOxxmT$NnAEZOdF#W!vSu)qw!w%jHUXN@PyGU1*=_aAj2 z-JcLQ#u3&Aj0n2+@6547a67=iHdffPxl1e9mYMc?D{rR$j?s-#sQU<;%o-Qk5pbJl zV94I2)rr&e-;VEGcv>d{##{tRy9m*+FiE)9_w*#!@_$^$WdsC0A>ZH-VY3pT^gzFq zvk?|Vi+=o$RMa-O;URG#%L7?WD!A2zd#nZX29tS%Y6$3eHjK61ye@<6#YcFwRaUjD z;{j7nat0mD{q%*qo04!hg%97|gdNbOre6NbWcnAhxPp_R4PY< zrbw0n8uU0-gNAlXL2KNVoTE9z=d2&~hS|95q~Q)-6Xr=Y_5eZxD2oP}rK@GRwSU?l zYgp^tI(wKq3lFU6BCs3!Emv=O{IIUn#`?F9svY7*5QvuG=+Q&2Tl%jLImS}leJt%xZGr-DBUF@lQY%Is)wa5mQpi3GbR(oN%?(2 z)>9?du}w3DlH#0<9-iG%N^u849x(xj5!?QV6c~#zTLdf^(E`-LcJ9`t@La%#@pvpC z_uMXpE%s1^eeHJ1-4=$2#2ckls4%)0CJ~-B9wH@1~QD!7Q zHGW4L0pPbCH%bj`?gUw4OTQyYE<56ak$mQjBKQx*k_9ANte$a&aJ4x%f@1828>P?; z$|6f3&Wrq66j{apOHtevf>$7Fpa@F#g?rDvI47+Gu}j)82q0TRx8Q#J8f)p03!KMy zo(SK#Q!|RrInjy5efwQf4ZHLfso1iiwQnnv>e$V$0rb#vq?~MH8A*o5o zl%2k4(QJaU%C)OHqwQD}W9=Ac$9K+{*`?v3Xh|Sh@MX>VLh)ny23XYD1>=colVz{W zFLJPoP*FL1@dBZ;OwDk7#AG>L7;Sn4L*pLXRq0GEIAB12LIFTe{Sa1@2f~uA9QqGN zQu~dgGebnvH)k6^mytcCHpbzY$YK@?W46b_i@lJNSmmtp5d`x%Vr!~QWZQ!>u*TiR zR)kiMw1>jzarnT`@&KFB?o8|6d=K0%DV}Ma@+^jvgQNx;7hLY5&d@9f$Aqml%auCm z)3UWr&LVUZS8O3&Yc?NUCyPaMPP(QcQv>$AcB46q*mGx80C@d2sR%M3BB^|cKZqC( zZTngHwm8WBHxT2rupD2cAb1MU%qSfhjONLJJIg-g5j|rKSY>l4Y1L+D+b^mDZhseX z+-e<>M`D}JAA$IzMxGaN-g@#*G4hj$!d5*$JE#xT#pzj;Dt)+&yhr)?gFU@d4X+0k zVU<0Ho%iti`{;XO(5kSj(^Zbc?TMUb&Tam~Z}J1Q_*ppx>*M+epEGYfrv^k%VHqyJkm@&^brK`%0^?1kRK1?!fZRNQtT{ZOV!(4fChUH<5 zcOFJOvx{9uGwMi}@gp4Wd8=jaaMxB-_E`?N!=*Mq2Gjb0&-veDTGQe=^Oy~$U5~=0 z3Z{`&z^{cAABPTAo&k%~CN=8P`9rB*S@MMBGppLG38-0DLE5^+wsC#N0n8k(qG{3= z&hsim_eg&LIf?L&Nq7eZz9Gn0)Mkc?rmIzF78lFtM0WjBu~fP88R-%sZ9X2ZOQ<*q zV1#7Lt%V*>2!^9@D2o5!$l&lWkjyDDGk^l0OaR@&mKF*$s@cGZbHn}WpR6SM^+ikxon z{oqfH9GMb;i6<*rb)4EccMfb+&hFJoUFk6FY|U)bsSX!=@KneB!?}fKAriy&i?a(U zoVL#5(i&F&PD#;#Yy+Zlm;j99CCkbkY5OuTfAuVhcdw2|dy&J+*(PSrS_l~aW;IKr zIXcGM0y|Vo>!DBej)|(L+LuAJoqp(|$5D6-J;NC9I3F$r0?{BfvQz7@rN>L70GBMu z!w-t<$Srr(WN!o)#hh8Qj_;U$3TgSvSbAsGQ1+4uVXmnaOS99q$&ncb&gg)lnhj^suSmn0 zI7Gg^1jDcM)`mdI=c3P)+8b9CY;jf3mWh-fFk^8>DK+Txb8 z&=0=2@r6DeqIFUY@Ev4%%U#@H3NT)LjZ&io`Qk>PI)_-@24CEeYF)Ux<*fA71OR$Q zU?x_2$|oe#45fTa;-_Lr!HvNv z97Pg91O^|3^QS^cP{5vuKb#cAm4O7Z18^eQ&pK#=g^>GN@fSp~O|zC-+1t-bA%Ook zJSRfkwR7N65IC4R~&Cr)s&-#;&X zt4^Iz`Gy|Q-?7V93$tu6*l;pD`c_k zwxyX}f0iS{UOpR1mzOQEi0u70q@jxQ73sH9+B~aJexNRd{4j9K{4B(WG~!sbSAqO6 zKzu;84pV$M4~VKBM1J662UQG{DaXV%D3_qNdefd6 zBlfDSXwk`y1P7Nt*vzdkfOg+nR#irXoW?q@N*?N#_^wMAWeCni+rjnw3@(LW+;zNO zQuo=(8v|txOpVzm7Kk#l^x7U;-rzg);9Gk;px>~7aY;i`B@MRIWotF$idORgkpdKg z;~{zc+GQKXt{)GUxTnS;KzYll4(CC{3_o?i;B_!MW^iFbE%ckL2?m>3(Xk9j_ zEWw{uJwe@`(RN*mbE&mo0BOMx_M^)sFMv27iCRB$czlxq55Y5%k&_JY#Z!QyGNx|> z+Z?x!VrRvW`geEKTF=Vk){8(Py(MmS*e76^aH6;vQ{vX~6x%z8{rYmONaT(jt&Y$v zKUk`Foo`8R_lo3d;93K51=4bi?fgipXP;zBtn8CpON#R4xCzwqUtvqf+0GE1z_np} zehS8tsmtso4EQ~+LrFoy4=M$*Cnr@O&nhNXmGpP6s319LBum@{{%=Soqwy#Z?V2tC zHpJOWt*3n!%{5yROplpNk3s0!X}iBA(+C1`9t=E{mH$~9P3HZ|&q1D1cW*3$l&6Qy znp`xWM!rc8rpX?jd~>kX_ps%g+=wZ9wn4PFoSkS8`G$~SKZSgQg+5=vIgoqhpn237Dv-@DV=zd8r9(ol~EeLY3jD578AdJDxs3 zBf-P2dR}sk&T+@(I>;6})hJ=tqG)0q+cLMPW{A^Ryw<_tv5vEtYXmh9bqlpMK$cxJ zy9+rEM8B#*ov{zzk!smL+l0zWJqAH%L02zz<|=zB+#{dYx>GEy`iW4S)K3V+*0x!; z_bSnG*k_I?jeEuzw$SL-P|<|O11|aPFp9us_q{TI)l^UJ)522VWIK0A!^%jZy3pf1 z83qKb>4u6}T`?6d3OxY(bdzv8Y*t-@?JEtL5nvkAG$gDhV~N7*&u}S1iJB-R*_uB| zqp)>HpOyaXGJIS#TgdzB0cz7?%$tNd3=q>C@F#+7`Eyc)-B&ALRZtOtP=HM23leA3WH=?|HQ^Gw}sG8F7q`^wd9+6db` z_Uq+#FMIJDaY5CEM7J`)exLb>*d{<$lVBHQ;;-WOAwQ~anPm`y80)L3T0E`9nUHl>J7GUMH1$c1gVi-dSQrAP< zggZ(H0b+x=x!l8=iyG6GCaxF1<9R$OKR$%i6X|OZUxR7=LOK%05u}Fa+`vzVqd1Ik zbvjI6J;ynsmd5Q2{1i$q$gQ-tb72UCbIq9_6Nh5BRrLiXxn!cayW5nkR91-am_ z*5id0Mf;De(um%L(!=impvcjzPc@nzhn{Wv*+)FvK*<~;F?GQW;aWZtYNJt9GzGMP zR1{5&e-QIqjn%MqFYKO4fPZla2_=Z{=h$V#n#BrVbGb?M@+}G-C?A}L; zZ5RtB>`8}E&w3uSiEP+Ml06N8g9s7irO>_rp;TKgf;)kqko3|(IK^8y9t6?=+81%X zo&?PM-gx$v;8+YEB;g|4olskF7X8sn#W8Zj#_tOP3KQ@K9NmNrOVrG11b^{-&o~bZ zB2SFv z>^(trxgX)3NPo?(Dl)ZB1`<{Nv)~yCIEh{a-H*~>lm~nOJsuvws6T=*C>RaC0uRS@ z1~D%{lzVQyL7fRgFyCG+i`5)t#Z+v7(kScL7Do_1xsul+qPY{$&`OJeuJAp?pAKS z%OTk=A=}X{rc1D#b&FTjXGSR>ye&1D&6$y`>|N=7yZN-MHe{M$K;pnas_sA;fs62W zy3+B2G*wWhza~8elhGM$*j_0N>UPkwauVS$pXF%JT8k7|iz?(@zlTfBZZS;-9h~WY z(^TBoW4rpC%vjO1g(pd>x1c1+#hc19V_>Rf(J4su!s2=g>-dwj(QZEb+SUwIsGAn^ zWmmy3e?g|AM$6VQnS4m*$3q-&@}N{Eqtoh4>rZrsY;-+!_3-w;|_dYc1pVtlYQYC%yVV=SbLx?1c(qoX@Zdzk=GV| zZ8+Q;wjT6`B2fq^gyL5-uW)|)?fspoYoOZJy zU+vDcAgqr_eJ8QaMZoLDY#2>`jOdq_WST`2n{LP^rzrP)ES-j#vgC%aqd5?#T=~Kj z+5gRgi!P4P1kDv-()i9L82tn;5b+)J&|-`xJ^>`~sKl8h#67qwk^~id0J-Lvi8Z04 z6l>VM9~9LeiL!E(_i3SBDX`TqOJc1_sC}rip#;J1sSH@;zdX+v#lw{g1$m=6O_PQa zp|iMYfOH2+B6qIo4mS!K&ZA*4VA;qir$e+s%*tZRBlKCrk5^$+eI$_qgE)>G5ds|f z^H40|{X!nj=R}~)Utp;sW83FTKr%r(3Dz%FI70jdV%iBa&WqoSTW7KSCK(hJ37bF>gR zTS$H*P-Fb70$-B2bruzoEr8VhY+&zLAZBO)_JF(2%0+Mr=Z4zTZ(5MOX3&X}L8p&} zw!)E5hMuzA0OV97?(U98NsI1s`a>EHGvJv46NS7aZ*qbs>{ovc_%(K6PbjQfzkx^% z^h7UR2EU)vb+Oip4SCH7#a#7upJ+1wz;q(ys^e@|T$pJ^5{Cw&$D@mOY?lDri zQYp!Q5#&wo3=Hcx=4>GOr*%R`2K_DxQ|NNQ2H_i`m6HizCt#+KDiI^_S-=E?*)~63 z8NiG|MC=2x+o0UA78!&SM7J@PZCfvkoNgn+C$gG|fbA=eztka_RydO4XG|2bM3T!z z&KvT7{2-*D7G{uqvHT#!C4>c*7<960c){XsthO36inhLUC`_!s>Z%(7JQkueRIK3& z6&P6^f3(xEVe@T2XCMe!$tupQ8`*!}dF8rtrD}RAwLumqwvTL1E4-3)vGeyL;B5OI$=+IS!yhIMdRY~^OKb6*z0YJ0(92VzcVzn zZqTO#>-wIz%IjJ2YJe1dJ1W+(H=dT7;fZ?DWO@-x($luBqcRgj)4ivt`#E20md>%+ zz**gWMQt7X(Q2ujJy|J_{NGB<1Q`HvKD6soy0Hq!df3EF1P=Qd5;I}RP2z7koA;ZN zX127;ex|a~CSN8i(myOMg6*pvnKse%9XY@2CMxKzX;A;q-C85}TPGsyRa08fxz);h zHL}B!Hm@S1O%@d_Oq5Jfn3@3aiP@Qgq~ZZ6Rv;J(M^pxSNF^kts8R_@DMZ+qE2khH zRFzZgnkYkpif(6u;WZwlO$-0R&Xk6sNwXyd2@!~76{xN#p6sRB7a^HL8V9l>*wpeX zoL^r%D(r{ZD7S)?Ew6tkF^b$=5VJ@Es@2Ryy7?UvgTK`oVD3ao9fxZ!FeCAFfR=)& zZnoy!lDaXvRY-q7;Hg8?$h;ZvRC8@8hEBIgA^Ru$L;gMb^kf~qbp4$iZcaaZVBB40 zAae6ayllaSIgGO$OAde3dBrhl|T^?*Tx|(inZSa4j}`ZZ7!0-(HUVMMid|{ z5WGv)V(kbH798<JjkZ zz5_^d`<+ECY+|CMj4frAhl6S|irsf=t!x8oyc0-3E$oStt9hDt z8C!B!QQ1&U($&OVRnm462~+=?dI!7iZ}ENprYqZYbn}Enlt6wPQ@?`KB>jI)>V=ZOV9H z{+ofcNBMYVc%!^uu$#O48#8XvbhlQcGL2V52(ziQFEM5SXgys_Oo#Q+UuG! zUL+3lb~>H4G}M(Vk2N`l3(AtQ^6f&0xd$h$Q!&!0reujoB3u;ph5j*Nk` zaCk95!wE#x$WHWz*qJkhs0%`WnD#L=h_2Ooe85a5QITNGIOIAqet~`l+6*GJ;DQ$` z_uFdW&D!~1?Mdv1?^g_Ab7ui9AbNC>KyL&^&;O)yXZOVX#-y#pBtum9-0i zV@muVq+i%M$%e*uf;F7*LyU-Fo1ehWV!hZ4HCPNBNsUr5O)y*vi#BYeOYJnz@!2Ma*ZB>6{Pd@ zxwAUp1)sO5bN(4leC}eNxl+*V>}4H4wbimWzgsB}i6Rn~Z$NQGDIm@RD;S~`AaG7) zJKh2U#n1j);oi-A-^R2p(bkaDA1jJ#s@144Nl`b^dIL=CUesnGP9M-bU`|HNA zrSH~R*}41cT*gr?_SZShY~F3~dbDv+F;<7{Z4a+YWaj`$;gK`97SctFKNDv4v&M41Fx3mCd65cIvc3pqPe074HY2~`|yr}SeY zw%8kq`+~`&-xmU=0qTMIO72`b8BGG_4e-k#IV%KzXxIzeJ{&ZO#c>GOffR6X0HvR% z1SS|4X#=9G5NVLW0)jj%@E9UEi+uD6UutnG9QRfXoZ)v|80MoX#l*9BPJ%9d>UFym;{d#u3;Ep#8ukNA&NA z&A{Fm^hf;|D6w!Nkc4d<$oxhz06*o??W}TvT-Ong#y#O+C=n#EFKjUYnv80-!4QdI zC&g$cVLBX3BCF6Sg=A`;P}sl4U(*ryL7st?JD!TBfXn2@&PmkEXnsU>1Y;nxjrrn` z2LfS$jidRgEA2d918ho^smIoQXsu3ze=HeE0GZz(ftv+46@R|V7 zDS>&D9T6Cf*6xiF$!=+yC+G^#gq$TnR9czKDAO9Rmng!WJkL}!~^kM1j9xz z$qnup-jiybFf^0iF!;@((_QDo1}O-u;Csa~4>3MW+cuVXq8ou|h3mLzMK*{Kl4i4= z6r!m^$y@j(f4E|01MaByi#9dwd~RYCVBYed(&SN$MRJD_N+Ljbz@6nkEml^oG<8|E z)4J*o1JjQPPZDS>R6%cMy_L}g+C`pF{vw0p_$5sjB`LF)b{?-k7WDu^jrKmZ;?(=O zosXm1FWIdm0TW9sfNh5V0@DjH*7Eiy>|5GJab$JrqcVh1N%_9oPlf9adJRh@f2hKJ z0WGUvRItoo3DSPiXSzPoiGxD>DR*l~)82rQrT>(((qmSmS@mDEZ7@XJpq=(X?2(%N z39W^Bk8#Gs@1Rj}wi*mPI+}utg-agNY;iJ(vJZG5XZ6+sR@GrZRqp&`&Al#&@u8#jwz4 z4}hyC3eTi*g2#{NOdyjPYlLc?11WNxjC0y|C~Xl$TdChMTOpw(F&>QHq4~xE!DoC& zL=|D zvAo7Fga0t;jDi!4OSB?r88GDni<5kdl?}C~zD3VN?L7u#Wd& z;eP498XtRg0bE}Q1}%pIeAfLnW7!`c!B2<>EyV#(S!o@60zX!u;dA(ug3uq$Am;Tf zs|M-exmKCIG)t}oOBtwexjllER&uw2auOi6wEr98u9jH}Arf`LX_$as(lyo95{qmU z>u&^NG^7a(f5@}6$l@$WYx-G;@X^lU-Vv;Fv~==>2s}AOGSI(;feZmJ@riN9MwsNd zbDOX5s0CDF?9TWzCJqh~$rB2KsXJw-(Vs-1DoKz@FrbifjH>2Ux0>n;(j}aNR>moj zP|OJ9lhg=K0!d^~%#mvsl1FC7tU1R!4QNA{CSiD1!}{Vt`!Wy_8wW!~9OvT(z}zBS z1!#SpJkU@Akjd4xe@)aThMw0&$lX zL1T{vShTvwvZmR!^&JNjYmb3cOM-l%REwRHTX0mRK-=*TAk~JkK6CK{)Ab-yE!elR zQf-=~+CYv}3+?cTq*|D23SWS=J8Th`wgaQNkClv@Q=l&5(xfw9IU+9d*N?r7i(XL0 zz=0xes6fO;{yGFI<8`z$t~*c0MR5lZaiKUtY{?OE(?=)cLUTlm2EEYzE*V_TJ&2e~ z8@Mj!LLDIKHju2A6~5IYQyFm4jjMQN>`WBdVBt0691-l^#qE zCfLSH4@VY`=E=gCZs|`Y3#0A{$m_Z|P684cXjO(RjTHt+FP*2updODAZ8SKboFg}k zbP~V~u|ri)hjm^%Q$r{9vo*gescr#usTN>D{#+yQgd7sErgwGBI^)bv?!aS@t*)wX z(CMP#IM6QZO|I%$vaYv#ZIXa=tJwpWRt#lNEwfd#rI%DRux%SFszm_DIN3Q@R`|-s zFL428sMUk4J~z_j_pB&sW5e3&%D&oISHk|fQyNkeZ|>s`Y27#5cPXG+JN{D`%ceOc zkxeVNm$Uu{N-GO*H2czuDqo)~hV&=6z2LI<`w-{`Q#JBkB7q2S#QouTC=`u`;&8e| zyk7XP!r_REMX#)wT(3P0OlapOm#phqzH()|bIyq;LT$+PgZ7s`G@Q*TvrA;2H+9FF z$vx->Uiij}dpQ{y#g(%&UbfytG_otrbz{S=bLY>T-2rdNDV=9DH;?S=8;OXbttXh> zwtK>vFzBn+t44TO^Q+m8M{Q-z$JWwbjCB)RA@mpU9tm%j3}M}GmPnMj2L7!hGOt5q zI+S@GjX@@cV{KlCk@>(@b$w+wq#@Fq$w?beAb08rZ0%&qCQExd$JBl>OS@_wp{w$2 z?I3|Ykgc5?8FX7aT{x(v9jdb?-@;OwwX~}iA>%1Mx*d|r$+5tL*7WEs@U)!JXm+!{ z23tx0Hoq0Y+2)Jzmr*?hd9~Nf_Y{ph_)`=-L_Epe1qXZx@(|~~$nVj4n9l{{#OLK% z_)N8|2y*09#fEEHp3otD2YY^Bi|}#3H0=QHg99pBhWF)|C|U=1AAqrOprRH0KlefI zV0Rym`v4R1ONI-fP`~;#>J?%q3|qVILeTLsbp6T?botNIg^)ICG`I^PSGUSntw14i z3|t71R}--74(>uI(5(hntzgnYrtr}^7}AIKFy!f1$6CEg!w;={7`%gf81nQheG~q) z4hCJlN^9Ql!_}{jwR)9?P4f^QhJ!d5pzD812LtQ{2l6nGo7pmIyQ<{xuFn3)k^r2hADc4Y8Fv~J#ICq$~dYA zWf})~;FGTnB&P%v)0^z;)dGQyGjNHn-}zd5eXSIqg5ynoFVORdCK^lw<@W8xSQLntyXkOIklhabB}1P;G7R z+U2X-SF|8-43*SjO;)s1gtGB9iW79WANrj0M5u?yGd_yYS@j8dy#`#Y-?khV>zE;r zn@o=*7VAOzHg_f>njX)|xT;tn4+R&BV;JW(`adFu7<%A6zp0Q))7)v829QM1zhz%QDYx}gWF=%XGn6^f$8Z@>q zOj{#mEzLnH3bZk<=)hH_qk;N3czdjU17Hf<&sWuzUcYlz=ZOn7*vtS|wMdC*U#}|< zeeD3Z$SZl>nw86Yle)94xvzCYOQn(g=u$3JE_8J}*_u@ONNcbSkx|6XP*Y@RCT#!B zK-nBQzo@1TQq_3y8)%uM9AGGV@x?Lr;cuk}MsbZTP7krVyLUrR66y?PdODl4prLHn zTT)wH3n$5dGNWhNy=xLqHN6W(E%$EcWySSK!G-}IC%gS@=g!(;W9T~q2082vFN6RH zk)cTD9C!isES-;#ZefHVPC{~B(vSFHE$NP5>@UR3L2Zyshor|i66z2b$Ac4rNvmbU z)v_%w*xh3(;tyGHDeq!PL#Q;j;D z0`FlxkQUBQE`dd1*)pImOeV|)eZ5f09sq=tho~^v4cVUI6%NiC+T?8udIR|vM0Ge@ zUOTqqwaQWXSi?1|*RNcunPY%?tnq<1%rBb4c258Z!z&xhYpP*_As7nH6k&eOWPZ+I zi#T80G(OYLUYswVO*RNVk&9Tx9!Z)`kv=@K6}a`34w^y=363DT)#F_ZtW7l?l(GiH zMDdyr{zBh;ZUgwGcqqmI4Nf|W&Y+aaIH4u^1MI$;!Z=cTH{tIq2z{d5SQL*$QHaJ_ zNF&Osq;TNfy4VzfQ0FUwCPjbffwk-zgA+c4Y0~P9W^$pD&34txD(mBK6c8T zj)ozq0J4SfqUusyG2St8?{J)Ic6*p~r(=YTM!$aD%rug2_%l&j5JKpXn}T1p$D)Kh zq*EYO$c|!_VG_Yh$p zPbeS8&Un3~k==M#(L9kS*w1~vBye)Cr!5ZqU0Xs8+Qlc(1YrZqK~kZx5tSZ@gRV@; zk_2FgUB@$7r?h*#?97XcTFn6Zd}L8=6Z0)YLglzh#|U=o_bMIi#ZPQQnUX3mXSaol zhPCCc8@(QtVyEIpT7qP#0C-xdOF-aJdI=mZ7;TeH#f{qmL3b-hc@kFH#wY_(sod^L;CvozEu zj9YPh2Mv_qe?QToldzcc$T?z@xnz?SY`A4P{e|+#HZNeC(^WQ3IZ4`sN}%$&^PI9P z(p3#pJ{=h%@n;Mbn%VaIOKYaLw;0ok2+yaq69cuj9}WZ=`oAHnQQ`e}AEIB-@FcEh zPIf%?(+yF;vnU80Bucvy6#)-X*Z(wmp~H-|ea)AdDw-Z#VdyYU?5D3FVot*TiS%vu z-gZX|<-%|IMt5e4X!srU1G5qgD;)#sp4vMc2$~yXo862EGYAtXfq*vnJGN&MvS{n%m*Y2o+|GVC+U8=v z8iPHk-B?m2py4cSx&li3Uzq)=pAk(JqyZhlCaEvkuCBvKuT z%yuA=tq}sG2jKJoC;*fU;fs(A3P}+;-NK7!`O&ciGB_Ih8Dg@_1e{z9~8%@|wzkS4f5Ho=-Q@35L zj;>0o7L}g0(PhA}>y5@%CYLNI<*y-}%!K?EY7QK6=B95?8J6=+>d1H17EA7gbePs4{1?9i|tQ+SdZKyn6Q9 z&LRhU|8ZbYsF+AZ*HXUWLhPNYOi3aF+t~(EJ;W(nS(8xIXj`6e`lht^xmnL&i^j2z z|F8||P+O&sH;ZOQS_RoKxwefO5dLeI%Dkp+$mgN0qAQnx4I|mEX@wq_eGIcbC{1VI ztG735ZvVsXhYq%5hcw0zkcU)Xe6h=A&l;4dPg$l+sw>xOw=TJ6J>UwHtm)12&N^2T zz$SWnU4XX_M)@SR{i@oU`aYM_g^-Ajg$p~TpQJ%1E^q=n#tY;RaB{gnDylAaxA{HE zK&u~ctL&Bqwx)5(zWB=ZiDaS;&yX)==YhK*Hi4A+@wLw0waKJ&fkBnaAz9vJYqph- zWbQ_(zOGLPs5PAXYon`IIN7#MMUJ94g~LyuWCy0yxh2CKx~A{dEQ8~rfnd&lRT0C; z&NG|0D&l1RfhpoNxueKBO*B0}k9L={7$B=`%6&8CdFHhFMqG3=u!jP6KlEd`fsjAz zPk@yG@6h>T(2)&%Aw9d3>?g3J5bToA53CDVcYIzXPWg~9LP?c&jZ2g?PrN83HWfh@ zaXf$!H`v&bvJS)z50Ht|>Ko9e!1;{*i+%)aqmQFdss9GC3Ma7as274h8X=e=ul_Gj z7o|K6<$S{HmO96fT6H2osJ_ zCb&5%mP8B`@ZX3>8lc>PfG^JP%+t_l#ZGX@W8|_T{E|N?(opCd8Z-oi3tX*#=S_t> zXy5HQYa#|8u#DL6M%l{Q-fl?((M>^^j%#6bJr)Vg!pk9qIezd!#@y7=Q|4;?Q^-~z z2&Lh9Iy6RfE?<;Ng`8H&mXO)eN1Vr?ifrW|g8)WS`In3m^%dF*pfY4`I5 z;DX8RVg6x*8?Z{WWNvV))PQ}Sfe})?ffi09@<6 zR{*Z>z&Z)%U+kafbg#a@Eqn5MqN$-j;Lnv3Hg|B1Svuj@|rE;%sU*{vT+go+n73GbW2ko;S1{mq%<0SCBzXUn+sGOY`OVLoP8UWnXUtYBh5eKs!!liVa4{KqG2`DXsbV|+ zP*PnDgD9;xS?oGOrUI9f85il+^Wxzgpibw6#uDzsX7KeJO_>hS^iAs*Qu7j0CkW#H zupVH>fCoT=KQv`gjTKu_`itCHG4_cQ`3u`vX9GF#_oIL% z_kVlq=exI7Lkc#TRLW?!ojiPi03VsZi453XKl}ZZ?@^jh0QT8OXiGiFzB=;I`|7dX zIS*8squLF3&62I6Y(5iyKJ%HXr*F$#tL^&LnG;0Q?NQoT;zZ)L4X(fG3S564K{DTl z1KZh_Emfn3!qs<>?U1$2a1^tYTOQW$d{kto&u|Q3_h6f3=e=1{tmcOv;XZgww-IyD z$F|Rto7ld$&82BVa?~(!4y-kS(>AL$K@6Z5d~;k}1Z4_5br1_;NDht?<|9DjA_q~a z@-(Wnrmq~GN`>Xq?Ewte5vf#p8dbVLrGh-jU%D`KSSpovjq0_8kOa7P09C<{iPlI4 z2c%zmbM!9+7ad#yJ5v1%b)kAtm3Tc=Y8xCuuOR#wzvO9X=M1Kz1rMU4VaKNR3#uHn zMy|F2R7%x{p*RvMI21`$?L^zL{jXHj^j8eENwId(~QV2@{lQi{?Jv-0r`vWQ|IR)nsSDT4CvKgquosO5fdHb0^~ z`99qW4Rr!@mRG~Y;j5C_-WTK~6|+lu@o1qru-y$(h4RuH@?*TcpHMJ{FtDE7C;ylqcv5+J6Atjfvh_OoWh&?? zw&xdBpvFThC};(zpv)1fzr;`9&0euoPo!orqnS=AS*i5gC@W#y~#4cJ+~hkE>wMa$(ndcb#4#4~K?X@w8v$MGNW&Q;L4G($GY7tkX-%c7fv z#fo#Od^>NO=agyBR_)e?|F8ZayUgb2S*EINtkUyD)n@aqt#Z9!)ViEX_%VBG1;#7t zzs$lm-z0ms-Xfn!KfF-T!wPyB^Ftr|r|tTOY!QW*xKzM)-zVc1wKf3A_k6{%1ig5f z*~)q@lda0nJ_hf%@G{%;HOIFexzBd0McB)lifwQ9@7`rqelOaFS%g>Env8M1P6QT9NU9h=+e)g6S7>4;Qpeb@02Drp7^Jn}7NDcRaFJ`> z(2V6O7z*d`ft~rpjzzN;YS(}bZQ-K%UF515H-Kv4rz1do?_A+*hGWh!4uQ8i$#yQ1 zJIiuw2*dT>AC`}1OTaJ0c^=sJ7RjR~ayu6uh8wy9fsksa@`Sn74o0q^TeEEiePpR( zO?S(6tmAcgw0V+!Ygn3VW6sRUqUrulhN*p@IzAZ2$8B(*mS5-CZH$r_dw!^`h9>;a z@b^o$eM7}K)-xYnuy3=YQR#8mHmi%J)pVN4Jf7KWY;&!1MAP;Nt6XI}f!$eS8)@6W zS;};YrfuwlUzbc{r9*74qBpO;C^H-2<{ZWR1eO_MJFe(A+ook^iKac=hQdYN*ZVs& zrzpFJ*!BuFzrg)}#YZ3g69e)Te7M`291V5`YOvsUZ(|~k82F?T;A9x)=^5sClIl^v^ zteYOx+i+}!-8@0JK+a8bU&J8<-8>-|bK;F0K{v-!;ZbeVs_|ZxbK#cAb+`@WY?L&f z#L9+zu%Ql8hblJ+-KgZc7&?j(RyKGK&{dimL))o+}{oqb}g-8-J+w=d~ zdk^p^j%;msx_dO!VMY-o5j7$ypfo{Y0D(j_St1x?5Mm^a$VeoUEoN}`lB`9?5d)4m z0S=TS!S*_=6F7Sv0Ot%C@7=Y%YyP)-qC~L0_Wka6|L2n)mAb1do~k-kb>dDrl}a;k zjyf`4u+j15i80M=?|eZKJ*9ML0%Rii3ynNqC{tZ|5D_hc6?6x! zKVSHOMx8InmJAPUPSxPAHY1DXP70mY?x2G9<#Qi$EGI5(S={hvuX7l z_))mAzmlKT`pOWxzi^%vI0?}l8M1dx=s+7&M1`*JnlQ-LI%Au=1}+$zD^ogA*%*^4 zMwfr4-MdWPn|=)demAh%z$(nR8b3rFHAH8awC}AOK^xSHZBj>-c>+X3pOt%x{g=^{ zld;`scZQr3p54zgqfI^~2}hW*hkVK`dZKNbKnBP9N-+?uCzA?kkV3` zcM^DJceAO>H60SbvO3soDxl#lCQIyMFvYl3m>^mVqJ{g^>xV$ggUy{ zB~Rz)nvd}JDa);qai4f;teVdX3+0zZ;WKu?A@vp5!MSml3N*d5HKk>Q*i5J9!}rgM zn8bWKeovf^raq@l4RL9#dq4SzBz!@h1+l$be8!j9^udYU1$NwTj2GzmQ(`=AT^yT5 zy9OA~qg{jK@xqtXprn?z42WyTCJvVGlZ3D6P<3o4mUf=}u_Sy=hYnlY)7-UjO3N72 zc~p^UGO@-{aeM^%wHMrI?ie4<*RBBqr-Vqs%h?>%9Vo_EUmz9p>n%aq;96B*e?rn|8ThFS?q7Ad< zu5s8DGdX!NS6|}G>Co-wj99ofin@c)fmskmtfk?zlAdA^o>EhLCg`JFnj~P@%!=)m+yR|;njJ3rZ;ed z95UTDR~`)+QOU4tpl_nla8%*nYPrnlY9-zI?pQ_T?d*;mbu>=gUD@=wh+Nz8_|E^xp{be zb}oVga}Yc>8^J@f5bT_ZV8;vux6~uJ$%}yM5Ui_3aG3|en&}8uR3m7ZhG6zo1fD4f zCQn8%=>i1hlMob5Ku|CaK`zEu=!M}GdX7QRlrkD&{73{UMq4mqunj+BtPLlJA@~?0 zZFs*N!3$*wp2W}^9>bU#9_WMMF7Bb^P7J8wc22^+6)bea78imWV13qbT>*j?jICi4 z2G#JV-UzPDLU1w0(y+1zf)(8XF2hCX2o~88EW|h(W@8u)(>f!V+!4Wf7(>Hg44$F9 z9fC3pouMy=%;3aW8MF-Iigpr7AK42#nIw0v0Usc7K-^|me^hFBBqE5q9(=& zr-UDblfpN`XTnk8UEvMk72$c|Dd7o$3A=^6g{Iqtn}zFyW?`Lhx!@C43QL3rVU|!U zR0)%WN@1ihL>M5r1*ecJWC-a(C!xI%Cn!>>R4m!0-cnB~P3j;eNmj`$3F1%U-^F9% z7ve|a`{G;TA@K$AFXBG&5%B@>Uhxib8<5Usaf5hehBVY*@?+K+5Br-igvRIn7(?Sj zehi&)haY2S-0sJ?8C(2Vlg4I0R-^G!e>OtDKNI0KgP~j>c`+2v;A0y#%}%;gq{2tL}RSKJ@DGkeyl_32Y({QU-~aU7NT_0k3p5b z@neLfPy85G>2H3FtMs}b!zsPs2Yp|9%8&IX?e}9?r6>FtSLq?Yi0}cwfbbqa23NY( zcQL|kJ}f`!CLh+Hbe#_iP}<~MhVV~5EJ4ZdTa0j-Zvobyw8Yndm)XAg2)#b6L&@XA zLXce`J+WRg=(Q5$} zdDL1!MIOEuP%+=X7Bk+wb1f#ixoRzDy6MrifQ@O_TFiCR-D?3G)6HuE6;sPvz{B(> zKcH*6!Vhd@y2KB7m{$4$57SaV;9*+e2Ruwo^ZZNkZ-yUWG1dA37E`r<5yC0{g$O73 z7a$zvZ$voQ4>*}h{eTmG#1p=82#^2HT?6?4KiM{Z->&gbejE&lV^ME!j2Z*CodpGK z%RG67py2$+je+Bwn9?9S#JV~f*&wHh^$vbi#Q%D_xh;kQqwWK#TprHrSkCsQgZ z_xqH5sydmHppkQ60+Tp<|n8Y1S;t9Mv z&`?g)-83Mzss!fa!qTRH9Be$%ZT)YC%Z1T({Q+>X4I4LhV#RpA!kT@b=HT9wqENxf z6qWA&E~Nt$gAUAtcjF?bxwBA1S>S5m1rQ{K8dnj@`8RD`D$hx1 zEhfP+nspiYR0VXX%-U7s8cbZg0J>mw@iMv8=q{>L$v4H?nciI{ZwIOJ;^p!UbYv(2=t$Fmq3b~V5S4eB_!b|c>xsTBU8aYi^DZ|j9LHwE4t&~$i;JJOJ zd?7tE4FtY>?zZN!p{wL7LBd6wcK6Ua1g?s>7*{8>H|f{0AW!Q+z0g?=D@0_{#_MC- z)5eL(-7nw@piv4Sx_$(_21M-obDiw*gK}mBzHoaag93uQx-rTeH)LDfE?BaISOpvj z6&Ay^KfgTE2Qy-l>913h5{52c(R)Q{k$ywPMXq0em`6ApVh`T;BZOIgIbU9yasI*# zIvi{2NE_iDH!Y*6GB_Au^=Xn zo^}G7P5FtjG_1uGJBFLH2~Ly9WaL;pj9=H%$%v=^IGFNQ?;IZe1_=>-EYvZz@Wf#tCz})ae0xXqGGUPfV_*` zXGENhp!obJc=FOPYOD+(#Pe{9myBFY87LVZ1ZnRypx8eIKhpE3((PB{z{K{y9{qL4P6MK2`QH+9&V~MBGq~a0(d84M<_C>H+VU`XMH_(dK#NUO zDF1uK{J#d{rRw$%&WY`seh;<_RKoZrf+E8;{wDOOBgMvi>}@)7>yXfv;}?b1 zx`b;z9jis-v)CX?)RihF6Rn;v|0T#JsOAiU{P?yE0@^*y(y@76s!V^H0~bQz5!`t? zmD(0&8|44OG2#~(26g`@7zR`^A}&WK_V)s@9|xmgo9i1u4FEH7){ObpbG*x;Rka8v z!f&=`{BNN7SHqeJr}D>#sQaP+RL+?cTW88izn{Ku?W-1WTBIOufQ_B7t*d_WH6Gt2 zQ0Iqn>imK*QT|gPFDYivOgYUcOeWi0`7@KGuAAe#fLZ6sCq$DZK4bo}AY06l##boRaQcJk_zobh~ZYQLS;kW z8UC2z#4{4*gsH7!uE7;fmrE&5yWsP1!00`#9^XDVz@hZZa)n%fui7C7cbDI3nqfN zJ-4z3Fu`SeBp=sB)UA~1Wkczd&l^mXUN+=Ul?KzAvQ6e!qj4)9mF*=Me zQ9?0IE){P85~fNh&V^?H2{R=Uqr(UjB@&~<2oog|Q>=0rVWvc)bQswqFv6_iFv3I$ zrs3)qI*>3^BH8ea$%UyBiPB-jWz>SuhP4h3BTSS)3>wQP4L}e|fuk&MPk}cwA+Wyr zpkbbb62W+ z-VF|w19B+PlLrDT@lgL3H5EpSp=*JoeI2y)YcW;06LZj6pd|zMjWrOM1O21kg9Pv@ zR_{gv?%f;)4d5&sfB23Aw|+O^YW{FBTMwIj^_Zjeh4@~G?=YECuX4wuE+hbss|V)c zCj0BLNb6yv9|2NeZq;MNxrdQ-i;-u5B=Qo~2LLz{AMOK?MpPG?xaxYS1|U0N?_dLg+9DsKIstSRiP!rO03|S3 zBwp`A2(?A^0Ye>11XP7*EHpk497{z$MEU?9^MQDM76Td3O*!3>HRUV;rU+%=hm+8AkrP-W z^nyc3_}LX)RD4Q-5E-73POwM9iJ*ucegUuZgTF_(u)TMOY{euPLYygvSBqgLxMMtCpK) z4V9|eLEsO)z-Css69BgpD=IPk4R&P0&oGk}e#W4Mp0Qy>ttlY%@CP_>;m?81u2$cT z11q;CYEgNj))aQV$eMy}7}{dC_DS|&;6jMb0r>GX;x3@_I#WuQHsWeQriaduy2wsP ztV&JChPiWVJTOthJt$>x@m&ykZMq*Ls)$uO_OZ?N&Y!V(W~MD*(<&>&Giz=jah4ho zkn#i#r_=52K;c{H+GYsZu4gYy-g&`zG&S|O!R(+X(N677vR^m;<3E)%TFeNwtd8z3yDs*AMi zx#A!UU3s-`JiU!yyxNg-iT24kGda5^q)_$txJ3l}>A-iU z_U)l+198*ZWg$XHmd@WyxF{i=Y=V-;4Gt#L^S7I{+(k>~g~(gca^757Ctxcb`ZQr` z$~gfH?GyYor+<-3O-lfxEnBs2t-(EzgDv)t0U(Ek+3o0An7aLRJq6i1(#pikTMJFrj=xuXXXDvAa9iB7%?jfNll^6PJnSX|V+$VJ%Fc6xLSp%LOHjii6>k3Za`&L?bJNF0FLHV?>8an=4>{ zEvx|^!|iUUbmd`-jgDMnYS+AVw?g}-8?AJ`+1!;HACu*#uw`d#0=V4jdkcCqg=aqp2 zBGPz}1|BP5q7SD@xYzL(bKw(CK~5phC74~W$Z9j&vj&<>F;gKgyO9z;GnG-rJ92j} zJkz12zi*1saIZiK@5ogwpmv&9$;0(OsGGMm;)>8Mo>WHkw(P z@)`T;RyhUV)0y#32&0Qq_vS+%Ce!g3%TGuqsT-Qri(>yK zuVhV+%WIJ{gZ0}jKP}RLj!FW3dr)4%#y2dH9AfZpL|E2 z!`)IRME7RiY9Wq8BI~+2zG+6o15?7uu7Dhk)DwT%K<(s>T)G><) z;4PqUFE=L=^iAc6zA`~yc@RfqxvcM_=DP%X^eS^M-FAaKg%8AExx%uPM~!ztTE$h% zj@=|Dh|OzUMv}Kekh(G5q|)rIa#w3{v{n<_yj6DVIrHh|ZSoYMfMsu&^8{+yE+^2e zTjb*Af=7*X>n-wBhOyNID!T?!@564BJBaQgs<};G$*V13@82eO7YPG?3oYu5tPkHQ zzbO{h(TN@4W8JkwcJgF)_Wllex)9GX0Jr0Ya{*VKbTu#VwjjW@q4%{nuMn;hP-@jLW4rjYZ?*ld|DWI-9<@0i)G>6)nN`zHIZpI1Jn!+3y|8r+? z^M*DM1npa5Q+2La6R4cQt@Bq>y}8r(Fq%FaQ5Ql#L4s~=#Brb7(c29!+`u8jhK~u= zO7Hw7&Pso?DLtse-q_SW;iiVRieTf=lVGJ>o9W3zr|;q7BN zB`LZ+`_#F zS80A6ev%`GmXBm0%XG`~*iPGvJx3w`s6Y-x#8rqQmY*qG9amAE)=Tr)TqxMehI z?4b;yeTS7es+b{|sq7BL(GstijF1Jq5O}M?OvyeElvo2!O@OamI&HYQVOLD^{=+FI zeP!UFccnavZP=*XEpmb|Kc5ye`(ouO(FFRex7for6(u5$;g+gn|9S`qx3_TxYW+iRaaYb?D{W<9vI}CA&|8HS^#7FN?4*;^o-n_HV(Cz z>G`=@2ln#b4U9a2L>`6-osA=@_ z-xCTHDEvUsW5x0e+FcH#n_=&sBl5FV>%w)Sy`>2`?fcEL)sGuH+6K_*not4OeN(zpMC^^^ zgb@BKf(L-~`nWP0v^TZ`Id6ob^z>)qWZIdQWTp#`n$oEM7%81nuUBRYGg{s<-4Aw= zbb%$boUeyj(eQE}X1;%@uf}NjB%n9lYJ$`8J<98RK0{bZ>QDA6tjB|MaG2Y;SKh%! zz9hqHhctkte<;@owC+K_}R54n)i)R{4@gK`2N?_HhQwr(Ou;g}hsy;@gBXg8a^wDNh;yG7| zh*J)hMe9?0c5T-cCp)O#1R) zIn}$OaNx-DOXAY$6;VyrC;dmy$cfw(ZVVm%MD7@)Z(QV?VCwWoPjH$s0}J8^O#4v3 ztCjx0Z$8Hcw@j?-<#1yhHK!-+w8qoEgw$k8ON;MGeQq>&(Wf{E>@Zp|RD}(kN92K2 z@VRmi9W*O(IAO^L1d7j77SXiB@~xtD9=*Izb`TDx6KLZI!csQfAB>u@^cRP$-W zwaNxs_kwahH9RCI(~(%E6C3`C%uE5DK%Bj$Kbgum?B2i2m0%Q(M7ptTL85ZCK)Wtc zu49=$%74Sry@Jv&SBhERf5|r}F*A&Yhl?4q#R_a%f|4Zg8phynJRNvZ&S!@&QHljN zTTzahVrF7jnixsD6?H(;TRNW>n3XOpy`6FtC-X_PJ4NXQR=;wkz&6^IZm?(a5!C78 zXDdIEoTlhg^#V%xF)@RVy=keSjkhOP(#HF-+^64{JeAdVQEmj#CX;X?r2|!GC>O9L zHYGzAX0w|zvHBEg%H1a4ETiE~9e*%@W|H3MiD=5y=J-E7oDcM@VbuRS zIW-|lQVep6kW^%qM@>F~UYKL)!S`*AUU33nP5)AkYl*XT)@QM)#)cyl%)eGBNqLK* zdKldPq2(4j#oD-2s9E)pyyMn+gD)_;PMyXn9mxH9vNdW0EqhI#N7sKU52qDRDzV}+ z8*O4(jTer?efGtduuXGJ#CBFFxMu9{n^Z;*e3smW-l|k~5_JHT)s>ijqQd^xa zuNm-nZZF7z0qa8CN)9bgY}fJ$G*NFHr!+|Y`rGN$n;=@HjmO+u<)+8R<52ftoF%Dc zZ)~E~&M6n7;gmsm+HeZn%Sri>bl-fV;k#kPrwG48&bhUR4@RDz8{K-`GWfW0&5*gx z@p~smHkj?0;JB4eQhe;vsmf|hn)#H_M@ef*QO)d?ua#7h4Xjc2V_L$3Kv$)Ve%`Oh zv~G^lnYJucrm&~#lmW=pNQYGAxu`|Pu*kkct_Ac^ozjz)U82||+AtCa%nr(>dm0s( zvXU0D_=lCBq4l#=LCVFnpjz30mMJ?#)^DY9g*m3dXn1f6?}t-gRuJ!yma=WvDBQ@- zQsp2W{7K1TyA;KUz4CG;GbUz%(QvpozdYCSd-L+mt9*?{!`+lHIlhdq&K2P`^bZaW zsFO%50!Dl))++Zr(D?ce=o3G!tkn;LE7`VbsEIe|CVfh;qHQ(G7Zm@UTtYw%9a#VC zl&1l{)nt!V`mp{tDo+Cv7lXc_a6-Zw+I>PxWyiA>3z|Dr*-kc_TF;J5RSxRq`)J#6 zrI4@uJ@n!~um^uxuk@w*?qD12e_4*FonI?sA%-qAD5vOf4|SS^=}p@{#GyESmhxU; zBRt?x>gc#rjpr!61vkb`W0Ve3*36kS{4057#7qJ0dRETiw+18qEaJG2V|5q$DHa!% z(B&YWrahqOcZzK3hQ(eQ+aITug*}u3T$le)T58w)jO^mFMN0;BtMC-t#xAi9F1D|p zxXe4N)HbQ4anNwwEco1%40l6wN%+8UH)iKe#_Q93i&^eNO0Hm%F1sP#w}?&OqihjW zY3;SMeG8!>I++jFLOvK=$?6hn`dq1EWs{Wx{oK<_pz3Fo$6+o>qhpWZSQ5IchY$xmF-swZ{MZpSAomffgwr)3_S&59V613#}vOPT|o=p!mhVlvVFlZUHjBif!7YG>Cj!4dxl@sME8`XQFg9`9|u#9o!?}#!ZJGkr^G<7dizle7_qH)HQr zK#BTX;gqO9vjvKhDMnUdCz^FkxlWWe)6ovaji&0go;pGIsmHLSrFz8oO7s3P`lD6xu!Vol180qhiQ(?jG3OsM*Uvl z%7eCZ#G;djQ>#55c>Cf$F>JcHc-XeeSk$ttuR7#{ic#gDn^aC5GH$XB&tq(~&n%>v zc-5f&FIbAp$w$w^t^_2LO}f#I4re8V(4B%hH)9Dli=2va!$+5+#F0ZL>&h3nIE@-U za)_ZLnK{7? z+4lFWnfm33o{`kfV!u*qQmvh}ng9j5(jZiV0zC|yZ6_b>);)^bVJ1+_C!Z)Qnzz1w zJ#G1?ayy?$E%>{E$1udyVW@AUZPiLO0XG#!hKpgi6m!7aj~EN*Jz{LovcnZieIR{p zTEvTA$|vlW>!*Y7c^94~JrAxXJ`-7Ks3%f?kk!(Cx|Z7zGJi&_Lq z-3LbAtef$73mteGa~O%Mq&=ysk=q;p$RLa}d;|>emVD=U-^E75At2>>^2ClnVMT%K z2k6UO9^|&oU45%jLpjF-b9w0{ILd9um)q&U@mAs;S{Qkhsp{;P1|w8YJYUdJvCZd(k`e8%AHj>P4Rm zRU~qh*x}cRkD1%$ayY@R3^WTF!4&60DBv1`6Qcij6-B*s|LJhUfe&yES)#gNX&i-e zPwTBJ(t!;FOGPzKfB?L1y{^J&5a>m~S}p#=nPeS`a(imsg|#kM5%Sqt=D=I)X4(EFCkO3kH6b0UE zJ^Z~Gd|SMG3X5y)cn1i;`UpgL-7Y?Xn%w9phbDkrRLtRtUkEyK0e%inbg@;95UZ*Q zjI7tgeV(9$g%~*AgjzJg;jXJK^xz#;)}RIsi((8MfA^vZTqU4Yg-yX4RVa&{rIQ5( zd@Ar79Kp`j04S%wV*q()`7{n>2t>50k~bkRkX}d^6!G!LlEGBwi;Vw3bO{q)_vnSP zP@=O484B!t96gWx16YQH6LTaLk%fOwX386lqJpgBIou{@&FBvUB=T%;Gt-jm>3E&~7uoemQCSZw$ zGU!d<3rBCl8CuX(4^$J?gwV~Rr3KIPnjNzM_HUY7#5)F}rjSd~0KRV6D@KRhKy4*}jt5M~0Q%&G27e&_32aeV3pdQpVkAIDM~%A> za)U01*Fy`wk4YMX27)h%9~Fh-uH{B0PRz3+jB;@ue-9QG{AWjHZiik5xT5@rI}!3= zZK0>dl-@&18Vd&vV9M(CK_n0YbAS@S7AUj$^fDd5)m&l^WkDQK?*>pR1n_v_#f4Pq zR+2jNrRM^E@!-Fgw;1jpuo3aTU{9NCYS&Z?%mQ4i7vtiNc~P7LvqJx0qK_kv27<2|UV&aN{ueg2u0ij{xpTD6z{@*x z`T#^w#W^!HouN&qXV4MK**kH1hBG&bHx|%}ZW702`p?;Ow9XKnUOjV@u!47-JyYvE zO-QIWpJz~iOGnOK1E7DV;XbW`G&?Y(3pf@1!vCTg>=OKlf#reuU0g@os!U1ns&TF~ zA&`Xsw<=RTz0h#N&_k;Xr>74Ol0K*OOuvvLRB-tJY3XqQitcAPVdx<`L(>@m6H3Sb z2bvX~;}XB;Yp8kI1ANqBn~Wkr2dg#huai@?-2FVBZD=dCP-X{3vloPaBM)8 zkw_=sUiG+0lk|joGo#amnpqI-P;G{{p|N4^^6Et}o>sl62{&$d%x+u=|6ui*8M8BT z@EwlB=Pra%Ggar^^N1W9d=xHR&NBu@>_WvfT;yFi*VBli^pb2m2^UTXdk<`w?+yFg z;zi;951S%$7nbTWe@(iPkAS{-#C4TTZ$u#Wx3@uWLYAsKOUdwN1RRNF)W9`3p2rLh zQ6O|{X_V)VH#dzN0&>BiA>2qOhzSLmH8nXN$hYx+1ib!+dK&=R9}TmdAkbPVgmZZW z-91cD$uQ)+k)|c3f|CU|zI1Gk#R_96;dCoFuk@E&O!ITC9VYc$)OXS1g$;AOxl3o& zEuPuO-t7+=ds_2}oZ1KQ1Mq^>l^~&{an{_qrDMY)B(}CvFHsIc`K*AjNNp+TKHNbl zgbizQGJ9v0@~IW%pPxRL{nIz7{g)W-AM@pu$Cu9%1%%)x}oBRey zVO4UP#RQs`rQT?g?rmP|Yub~o{tQayUC4eP`G%^~sWCP6*$AH&^vbc9Dii3Te08)a z?PLq;l??*hHyspZn%`0Ra*s==uk5Bun_9>z!7T!N#jQF)M82PL?SiT-Ox@-HJtyIa^!$6S`ptnWniSwua;MweKgMU{KCbb)p*wG5*#CRO(xadN3)j-t z1KMnQc#7pbHl|8_5V;oHyZ0EDNtGia+f{%ZtqZ74qHsnV2TQt>VH2ge;*Hy``x>a3_ zraq0jULs$OXrs|nVs=tRtqAqtpxG2q_TN+2=^TKU$v!q_3w2tg-oQ%lRsVvDUZI4O z>M^RA6r<6aXVukojHwB1&By9uJ;y;xm=Kdj2al`GfUKI#UVKn3LW>Tu-IXz8MSA5^ z^)3Aal|80DDN3)h<3};xyu2>7aY9UCxV$TMvhiyqoK#PRtGkfRJb>A$N`}4$X}&n$ z&=*9vT;rcI{0bZJyqYU2(p#Gv{j$*jGTj9+eJOr5#Ejv^Tci7)SDm0&9e!S|0x?p0 zWB+Ww86t|lNjhZcxfj$Tlk_6Kyh;83s=g@{(~AJ{8A+-ihB;jfmTsB zo{~CvzOv&^4f$M<=s>D1wA8nw!kes!z!x{wt>nkhS)+*mpx>!F{Y@Q9*F;eaos=QJ^S95)f9U2WvCZn3fhdt z#id?a@Qj>$b`}Ts2fF7)Y(O6AoyZZIYs`k6e%5mVcaZ70kT=LnTyPxn=k-tv_Cop` zvE2T1J(qk3Z4J6BP&dK#L8qk$Xyzb~fXwT3(TRiBG>Z#l-(ru0>ma#kU!ggT);`d# zQ*%7<3dlxO-Xpaj-sJ{92$Tg1d~rSfe11}4^MHLCy~{Wg1E;+O8C-fO9cfO zI;*_y0L30eY!DG4XkS^F?0b~ zATQ1V+&3rGf04?I^mhCYR0`G0P?4&m24zyb$CyqVUx(B|*+d9TtB?mXr8hy29y0d; z8Z;~eV)}TlgQR^ezSKal9?F#U9^Gh*UDf?gLu;L5!GP!|`T{bNs|eI49Xm#1-G!c_ zI>;WoL3WD7mS6yCIY|!+ygWLf_Xx;MRU=7^u$!-T~5u z2yB2rEhmD(a5eq_6Wuu(HyOrN8K7$b;uoft1F3?01xKh*SOp=Yk(8+wN8+7B)5`RKtq0iW_{T?tn~MC){qcHFaF z4VukaHoTc$B&dfITqlxNfzrvTy5V1FUIYiR2)@BvfAau#fuV%$gQ#yD=uOZXD+mrL zni2Jtmkm|i;IM+3(dHZA8XR0;pgKMo!i_1gL;pB9#?aSmlMzHym!p^uG&Db&il9Fd zl@U}(ry3Y=D804G2?~91*fBm(X-CG;xZ5%LG4}e<2Z+E>j^>8|uNxN2qb2$IGoTfq zeMA3{p9=U7AI88_Fn<6$;frqBKiav*Vn-`)_Vr=*aQVPdLu}4|u&1Mok-|gu#L;88 z4aZiwqrWzdvDKX8Go*LU`L*tMeAS!Xgp-IZXxXK8uiCb{_1Ct|YW=ky#CY;o9B`La@52gL1K}RVgc4y|n@RVrQ$b%R@ziUEiwg);0=mSfgxI z^Er$A{f+bd8vGB_Ty_N%}<=xL|Ha!H7}kL^%YU-1>E{=|t+m~`{>W_&a~%4PI5Hp% z9Bkl^ru9zO6yEV7Tiw}rI?flxLHvwcUG!#k?yW3(Cu<6z@79^y?isDWw%sCHe{H)% zwEo(5D{wTm+6&ITm9^b}!+VpyHMMPY>#uDa9s1f7*!kPGI`ld4wQZ}w{6R3C2XEyU2SJUNXW8y%wxtU}cqVPv+zzBye>qRvOu0n~m8e6H@Q zh;gJBLl%fn2Tu7%35T7!aT6T(F`@J?>;N)I5h78$;3amRXL0S!S@Y`~GNDyK+rEeP zP}O5<2AAMF!V(@*KM)~1sCE!)nEj|~)Co#Q+0ltHX(EgNR()TO_>gUT1{!FDh@|9z z7GG9o#%;^)&&XL^5YQ=P3r?s@#Fp=2$|BAPeJN14Hqy=Ct9_+q%QKkogxaJNuylf0 z6R-SZR^AQ*j?D6^dLamKRtS7`3Ge6aXOVmH`v`eHBVhrQYFh2< zpPd1W^Ym7KPETkMwgn(R1$FHUPIc4iXCv9%kJT1LeaQ~(R`d0Cr7<-4K0Vu4G~k$e zfCijIz)5)gOM_0h`?_`H7_;b)W43>)U!i&5J~q}5$+{oaZ}9Rd+Iozo?@$k;@KMGD zwtk^%(F;FL>G!IzMXYJP9vY%O0fEc!$o{3Zn4eJ2)RPqeO+4tJ6T_hC!u5q@Habz; zjharNroU6%D)j?;(Pg!;Z`*12$)Z1jZRw&-ht%Bn`2J6_C&xjv>&!JQ6xglZwK$W> zpJ+7fkizw3u|C<_)6i%52a5O!q6(CLzqLEd&e7fm=>A0q^W@IVR-k=|w|~=5TVWG= zt6R%rBV1adD*BVywEo&wwD3pxPB74+lUi>&-jHf$X@2dfu;*3n0)ZL^Y0aWhpl>eK z{Oq#9+A0Z>hBRtDghFrb0=?qAx3%LUbzP^ug9&4lXw)_8tL*vpTCqUupOHq;n2p+0 z_P`iztRNbVEd7W!UZ63}+Wk~@Ed|gb;L6u1)1hq|V-=0sbb+SM(8jP?HQIGZr%?5L?QQm6oz@G5zyszA zt(y{UD_x#K>GAB*Te0-m^<~;>y=e(y&~IXZdwcXck=E~(T(s^_>Imk)LR*6-C(-&R zp_?*$gw`U2!R`hi+UbyIfc%3<)}ATjw0{Wfr_Guf1tn9?r`jcK-TB%g#9fCtlQE?L za+Iv#OYNZ0vK< zSKgD#Ka(JYdY5n^%^9z5jx>;roP+w70=xp`DLw zjqHQP7N}bk*0ZB~!O+m`K>3i3L*W@zehgX#y9CS6F!S4Z0HbVYhc;VY7r09C{%L9! zZ$c)OpOkj9ifNX6f}TxWs4e&eeGcmM`t7AsIJLj~F}*s_JHUKiTY($|H=6c1En zLTajEz3ARh$h|4nmcDpVluhTh?!-VIE1})1piNLYA_+PypC-z5%4dOw!4^wrX})(U zoytwjrDs;A!2kPfRo3dfbG?hbx#e@AmAm_alw>_NHD}=R#e?c6+wu$C=i8>Q?w39J z!h(JoS$(PEDO08eULieM{WBdo4tV-Xcr>4J}4LYkq8`z*1(0Jm(!{n^<2 z@iKp^(eOhIZA-AMpsmlOn5Z~ej%8ivsek243dZjE%OT&x(sE|?X%`99_^1TI=njI7 z=HH?ESbkjiXRqB0}dJCPpR9ivIUk6$*rmk13Gil!w(uK6)4ecL%-sVyK zKA4MmVyN~k9bBhgK(D=}<+fa@OvcFSnZLhJq_Sy}x1XfaP)K=IE? zE9uxq%}nxP?Vq&fImt#pZ_=KJyM%O74|5LVQd)VfI*=!~(-YUKE9vl`HB_=$tEVm3 z!RCq5qAeC^$0yn+JdJ~`{}h_$EnC##Z1m?^xsI=#Z2eZ42hfov0a^Z;J@&O`6V6V@ zrxTf`p+=kr`_Z;(VrOc6P0s7IYFQ3sI#7p8m+P3)GpDYK|8dPy&&{#Pitav=bJS>8 zeO&UaSx|$s_qM~i%V{noqho4j&G#%^j(H8ole)*rRjYnuPJ-rMCRfRp*{L_7Ur6JR zn!3`&H%uuE!DOMQvlz1V@OI#I!BOUrK|=;}RuPv1SRVCGNBhL5&`t+V$d3%Ob``U| zbkJr>gOZoQFm8zMGn3NCDGqw^Ti6q7SQ?+&ODFz?ylF18XFJ=PmDgQsfZve3mo9WB zcA=tU!01EB?ntspc?Fqt^)b!ewza)}Rco`FcV)GwD`PDQ^!YI@HGN_b*PO|=^FWMQ zQFCK_JIe8EllW4JXH5q+sPh<$sncL>4r_Q_YsOni>*>x9Os^x_edaj~2>@dseabRS z*$RQ~Aa!zh#y-L4shu1jX$M7P-h=<}VJlBdqF34H*w~S&HJuLU*<0zIe(6TPr z7D8M0Q@+Kb?a8s=Bx@YQ4(%5vqJ~QKS!Q!sKE$D7EG0ZGY^I6{D()GNYRlQb3oS9I zw1N_z6G~XW63a6vw~{5iC}avOr_}N$K8~Y(PiwE?posFvc+`IE;KRa9f%39&DA_W| zvdSp>yHe-#ERU+j343IJ7o*|p_6&ogK)3dJDE$0mI5vEfaWr~8k=eIfvILep+VTcU zKA#fiTTZdNcL-et{dB_ijI)eFJ15bmpEMlsCs=U6H$ox$BFk+o-)6x9XXP=>M+k{@ zK>o?JNwOrdl&O}lknBQAxY+Uy^{r81eIwJdo;~cbj6kX>&GtFCh}Bs>HH*e7lq}7m zjXM&n*~@b+F65gQKDn)D6Bk(SMV9KwX^r8u_MkwJP|0L_F49{!ol2+hvs_R( z$Ale;jqp7iI>l`y*VUGnMWdIcKdc=R*cTY#w0pi$X$%Wi7?FKH=#n`y~s&)s0@XM~i-wFcoL!ECq| zlD@~#)+dcQZ(A#nL1u0Cm;OS{HG-F-}@8a*At zht&&snt4?IlQx$%-eb82U7t?}mRSBCJt^GxTb{=NG|;Xoyr?R*3qAj!P|IGfQXL{q zWAGqeVX|b?nlj6?yu3zsV6~-Gh?+3+qn000!U8JSESP@94? zjs7K0^bnnG33fJ^p&`cde;+6M*YchnARq>R&&7Lg&3bN2GCwEp`41t|x2DlI+5b@9 z^O=bBLHq)tm)6vJomj7v?Ehz|^*XV>sSVev+O zkn*8~En<%(H#Et9yCf0>yZ-Ol3^dsBguW=3pRm)u*DWdMmPz_OtXY@|1!`$4gA~Lz zVa{xmE{wyu7CiNTB7+1axO2-O@q)uLNO*1~f&|xs5St3};B*-z&YJBSWX=AMux0sf z1N*M6M5BH|@F=3r1dh-jf%~BWqyMUP@RsnKH?D)?LbpW}X*h!04ZfkWFG`TvKw zpBt_{gWS=Xa6iZSO}`+F6y(tA>j=}2|3DTga1aNFR+pAU+%L%^wZ*StOc)ThIqPSg z-&qg(WGi`@aPBj*LtB4syDJ1Sbg0HATM&afY(deewkW-IacBA3mSNjE(>eAYzQwfp z+y>L@HbsYx8avR&iB{RLdkEt)t-#TG(+Tw~)Uno0Y1{s$)}K!UbsKPn3jHlro>pcw z+c}54Xk)%ge=({Da_b9qjuel{y7M6|6SYK`gHrCI;#uC|gqEfE&?({e@y~!~)h3 zpt)RF&us>9-F5!XV?ru1O;B|$#3sqbV6jp1kA>WW&gr>=8>y3LfP^iVuB~@*sad|g zI(e=_#HonH$&P2oC$4o<%oD(z0Fa@s7SJlWt4ZISU0{8nVmI0e`A=S&s}{{dBT*zb zb^x;q-l7^^Iym@Ni>lF6i1%{c9+X@Xl@8L7kVb|R1t{;;@Ve2%GgOYcE6^Riavexq z;5k_N+3Qvv-s0Ak z{AqWIsa;7uCkBM(!fz1IhIm>hP77O%zh%t$wB=`WnQ_J0pke3G0C2h5xbDpd-UqmR zbxDccQ3q$iZk2`dk1TBG}Pi zqo4}AmDiY(I`9%;bOaEAL!U@HitRM!D!zGhd2S4Du|AnlRdhSr43b@+7#KVkM7z<1 zx)?>w<*Y}7sRJ_Q`?eII{#IFWl0gC^0ozWODRQ=gf9xXey|tS;UE<> zMH5_gSferee58cgaW++l6y|pgcq;>h&cllwg%Eg$p#~T`;0@MiBSsX45?namasg;A zXwMx>pmbP1X;VDp`1R6Zofy*liGiU+@hDSYjVc}R!%4u#AH}#{e4>Yo)_rk>W%!*~ zeu0uzdU2kd1IsRsTC|0?ARy`=Y(XHI3NsbR!ENdkaq|lLf7qi-N>lVlbP#%BmF9Fh z^08>`+p1!e2s{w!Q!%)t218W0>?)s4!BXkaXC|AC7wSNvd^`{S+D*{{U~ z4E*!+x3?ai>mZrV5kMh~GeD*s^BNXW^cZIm96&*uTHgeqGvElO=Ol(edN+6Ejr5#B z3`GoJ>0&4}2hs;N4jg!3zQ2mktY8`^4AT^I`F^gz42=tnLP@|l4VNDe+=>f$2Hq@= z1h~x}cRlyP#orO~cPI`7s^uwMMQ8}17dSG(V+!8Th``AehLv=gCCGcj;w<1?H3CTk zeYOWP2HKA|6kE#=>}VJ~S^@9z^hm6C89QH~8+vE;R?zNROMCjFyR6Z{29wE}$@!?6 zx)<%NRkXmF3bO=x0IadaZ}griY=(5|@$_8jeY8&tl+ zMY!@{WW&*>qF$kzTIHTo%9X_+EFI8L@|4o16^UJ%!p%B&;yGY5hceE|YSS(lkhI(S z_vhuGyJe?03~JG~(^@tsx-MwrVnbQ=M{JH~*o%7WTl4?hjBrp2Y?c4xn^Ezv-HcKq zn^6R>LcfE`_X@U>wn+OoZA7ur8&OeF%La31M(eHZziIzT4(~tVF7fS!P9)3S(txJh z_9}WSqV(?ZNlIwvaWq%HX_G@658thm$EetS>Tr0mx8AgaJ5Nz~)BdeHPjb}G6WZTk z#)LZh^wm$pV`MiXSy(=mgt{u~C*Va=Gc^VDW+K>F7!=DT7Ny%;CFRZjrMD zrYfL#%bR9*_MgLF0-qrs6=+yg&Vq1GZjUpVZBBo?6EFFCUSM@y07%$t5OamS4Nb)(bdy9FFE8 zWK2W9usp6BFKeFbhZx}{J&Kdt&=Zs&$ zDhsqKfq70?wt;PaB|GTWwh3%Ux^r!Ifs|#*zpuBC?LW=KW{V+yHDQxUS>u!<0ycI^aUoN$F(&cgr9W1w|DoD4fprpu!3)XTtUCGO|UPq^`^b9!lrRMlIz^;23D!aUgq z@k1f|frQRo1-UuXpJO!KJ~2E&^VwUm)(k;4{`rv={@zBz$>rfsX@R-hGC-@R{a&k; z_J3iqal*%+@VA*6jaE@MU1AXT3zq<`EfeJXJNxr6`%A+mz?Z^s>rM!g8Oh(zpKCN+ zqFW26jV;y=y6gy%)w-KcnQO`SmAMlgzYHS0`Ki{|FnzCMCkm~95!mxxtZq#E>)8pn z^(lcpn{Mrk*Bj_WskMyC{%KL!m>yOxM13QjD7QYvp3Stn&AiMlv~i_6l}&e9x8v*0 z6!)EFA$!@OL003~cxwV#wn5GxqV|U%AGqB*n`hZdasOpGPJJg?|DsQ6hAHhrZQICx zhqV{WF0-z~63;gp-m8stlA)9PfeUUz0k_ibV$ATI94(RMjKZ+e$KGvAX z^2b?UgILDx^oZNa*kco{!;s<*dZg6)I(z#9Yc}5Q)E5cEBH1pmC#tM_piy8q8s5=s ztY&Z5S-YXKyLQ+43tDDae>NL$LA`g^(uuEONM_3Q*4qU3*ICvQwE7;n)8Jm>=UP9J z%*K1`a8;|N72B=T=-@=_ZM6Cp>kPJNfpvo@8SjHrq{-H9bnWd{P$7C+lUT)K>-`Wi z+Qq&doZ4Anw$;`TQRHsg^jGUa_WBxY9-4YTZF=0AOB+77TBzw#>l-ZbQtNS~dVqWv zTc@zl2ZS&lP^4+|d&ahRBbAn$hhT}H!)^Eg;* z>F|NrG#W768bg`aTFbdK&f~ia{qP+2VZ#{`4CD#++@I-pVD{-#TQ31FA7kO-Ycg4H zGZ}Xx38S07Fz3^*Ps|p2`wMe2d+b^(56rK^b02NN_Vx7a0@#)5%# z=OdDhTnFVoBaf|FTV1~rmjzO=X?Z`PEP~U#zM*Mtqc6Cp#<3}-GsaGErcDj5sC4YG zF)k1A?{T0334?rRz-!R!5UM}50xIFSvN@a)-NWJf1F7>|RZp+njfME9ug6$vryK)u zAEkek)Z6T?lMbw#co%1_w+^PuZ?vuDgUrXTdW^Eqx6S%qgUkTV?S1`i^C@?k?azEE z-t8(Vf=#gyU~LSrSHCP#ax zI1qg-Q$s;$!6s&A4F#u{+CxZDnDR7Yz6A~SOV?`TY5{0*ucHCk9H5e`t|nf$m{8poFZ1yu$uSX?JyzvNFt2rj z5^wFgHI#Z#MjFfu6jQW6;~=jYLNur+-;9T1m**+8a7`mGG%9GT-RdmsYn{AC^bv~80V7cqek5(vatF08>pZfPWht3D0 zl@>R0@^0U1BeAH44mCk!Ny)ZkKCFI2Q#x&0T)?jh5;uj_Y_r`gYO4{$YBWSs1D~RO zD{K$Ymp`{XBo^hc`){+2SsQcGA;q-vxNfR{he3%tROJqZrmM?1gwDMOD^m9kEM{znO(J)V!x102P}7hykO85|v!K@71Xkk` z5EFfKlQfAs?y)8I8C9GfSUZ{)A)RpTu(Y9RU>AB=8cb{6LD!AkDogb19Y{a0`_9<- zl(c2zFPuDa{Hj&wtshxZ>>PYy*`N|<^|?zMo%5Gu%}k@;-WdzsFjI7zGn}A*MCxB& z!(?sDrTCULHmzN-!nbnO(xwG9UVpykmBj&c>Ik~$J+KqL@@si0ROYCtDVQ5W3Hj_w zcp5pGoQE-;S{{|auvcKpBFBA}WI9hq77~2n;43py>tJ}AM-{t0#c2aVN~68E(wJXN zFTF0r(Jyw`V)_(k@&rDCTyg$I(CZ|3fh92}y(xD@u=z>7LF$Y6oEqejsH@AUr9unNf2Be>J7P zsZOTGTO%z7Iebk`~sr4({M!v@G?@DaTrzu-(*seeKtATBK`l!z*h18H( zO1RGEiLF7(U~t=n|F~)Db+*d{FTH-9Z8b0GalD@A>5myM`4IZk3v@6?mp~P-K+*aA zV%t`Rc{~=!U(|F723_K5e0nJ~`QFE;eGCG9xyWbLRR&RifGu}Q`AGcrH(ds6d@&|j zq)YCzT_oyXX77wk$k4X2FGD4Jg<7Apy~VnB*#@AHSIPS~T^=iX#P%#cy+(%)+EVDq zOBhPomtd;wiI)1Zsy((_p#c7dwZ9m%M5O%=olK2ii%6W7B;84)pSL|B>VFGbESrbA zRwAj=ren4{+2=3VVo>56bf{HHVFUNu4&u|Blo~JR(Sq6LZB%t9qIOf}m@lRBcWl3; zm{)8nGrw(nURyDKN87HnA!zZpwl{FuTXf(9+fnw}TQ&)w-e&GMZ2Ls&oCI#J9e;;n z)7@V|0>;C(?R0dtNu~>p<^eX1#cnZB8ffZw==fn9f`Q(*ZLyfz3K3~Riy1oAuEkfp zjx!EXsYBYmxl@c~CC}N$AduD~x!F0N+IB%lf0rtMXB)<{KC?~1j=zX)_{!F*GwQo> z-3Qd(S2~-Od}*5`kgM6YpDLcRO%F9gBYm?!rkF0&NtcWI!{knvR@$>zZsmgJm*NDFs&#L7`Te4GT+pP_RG+$QCw^(e8l{4uhKN~vv43peojtJ zPHjHW6i~rKuth6!p3vSe%!Sl-n^hlE%NxXx^?>I1zAiZv#cEbB%>y3tnTGt~{_nuN z57IQ4Nz~h|{TGAO%Lf<=+?xfQ;;e_W1yQW{l&_I@1xcg0>reB%;!OW$r9LWY0j`o;LPz1wEX6 zJFyPbjMEV?2jfeCr&+WAtne)ZPjmV@$Pq9a?zz&KG!+x()03Suw0NBdXL3Vv(8%qV zRAFE6vWh)9xkLT`=+I$vvKbGFz*i*QduOYhoET6=dxr>vO3uTzfx_5{emx}=y;_5i zS2}RFF=411$)G%V(^tI0_Dc)kuoD}qg=7ep3 z>}T004Gum^IG1xM?p7nYM_ePNmLs6NWg{X?U%p!DOPl_pco5AkPN4lTIWXPZNl=4&#J{@otGL)?M`|(Pnkqd+>AfhDE3Kegxv&5y;4t35d zojkK_dU|?K8xGCmDGK4O=E_DYJ=|rqM{Awx)?5OV+6g8HEoi!`s%h~UB%b0C(a^O9 z(n0J54E_Mdz5NxK5)rLz3MioD-5!^f8cA{O_TL>0lIgh_8HE`|;RPvLL1^vZHC*=} zNpHuC7J5-oVv>+A5&Bat4-73T(uPU3Ut&WAjH$_YNP;(|#9Pui0N?zQxiuQXlnLyx(5+;$y0rHaZnqOO`tu$Tls-MV%Ma zd%#M8iPGh(1<4_d2|nZ`@L_X^GpzIQ2e>yZ5J-#z+#4WF9KghJG+N%F9^4x;0p+;q z&}9fFZJsEYs;pt?HtjnsdLTADrLgdNxftmsD{_mh;h?twzNC&yR|pQIg8~W%OC4fe zbCHh(f(Xf#0P*0*eE^D71480o+CW7aJnRX34-``9IEso8L688EA_%DqxD0~W@Z{t1 zay-;$y{Lx7h9-q1r03}o8&-hhAcZs`#DPdB7D67sv>~Ab8(IjVextNo^cF$9s8fj` z4LNTGc!b(lm}9B?GP{8~O-L;H&NBGVIG%==;HKgO*6bmtCxfW;VO?So=b($oDe z@Pqd1eoY_19gt)u&x2+0C|-Z@&~Di_{LDX0Kbq1CLtEJaYufjDjv=S_9Mpa_vIMWU zo1*`x?EFiV9i(%B<^VBR2ZU5z9lY27e6=TBb!ZOL9k`SN`D`6jrvEcNsN^JC+iB}T znhrNW1J$2g@8JyL6X!b;#i`|VV5`aTgH)j8iGEF?w}KR=;{-K^(^7s02bG_YI&(tB zhpRWkzC*nU(@^~S&73UzVA@n-)YH>hF$(Ry&61FOY88STYm!c^Jv3J}Hi6ni(;ln= zq3pMpEcS2^L6Zl`m&Z+=?OeUM-ou5nzn#_q;Rc&tP$%%G$~b`@5jrDe^6I57*q~OP zKwl{7(H9~NqR^arGc}x8cX)6{{4WH3F^H`E@&B5~E~q>>{+*QvF5Uj)m513sSb2c# zg~JWB{VhD*2kW!?U^&M)eUB**L;5IC-V8#^!E9|4M>$) zLSK3=E5=3><~x$OP&{^rDW0yca@go^cbKGIC01#J(37l>!+Inn|ke$lOrE_{KRR~ zu|f1dGk?&%S)gxw?qX}=qybrZ?(%iR$Bi9cJJLB}uR58KoF<(Fqz4yE2~NrYd`-05$Ed7oQS2XNOza5LU3 z*39jkrtSZCUt~$sFFn6*>E^e=c2=^`8cjD$H>Wi_gVIy;?*fq*l7ppLejko=Oz@4S zdo*&A&h*l(bs1x_)&)GofC57BmTH)AycrXcfI;sr(vE7f+6`wC--^TD!>% zS8ayVOQSzE$E)GfWY&sVE7G))8}A>laL0!y!~(?|r;baH2>e4%nCZ^(rIV+ZAz65N z>69|3&&M5>aDQu>VaKQa9Q3ExgjgN7zf|34$)?W^8y)uQ2v0h%6HVPM^-1+aDm!6* zS?3C0O~VSfi=H<3)6h(@+(RFJLK8TpX!*^O!|!tMde4%m=Y0|}w1dMraR0y%4-D<< z-^C2x@-ehLeOKI4^KEO|M(LwoToq{r>34oG{L8-_e}_3F+`WU{NTf|9pPQ9W%O=wN zLu?}7N3GF9v5>_Mh7W}7;l-^RJqD77NDE0p-uJeUAf?Cmu#k|tUhETT9Wj>Xf`(VK zZu~o1Nq(SVq4@-c1xPCJzUsd|VNaalj&Sn0zEjz%{%d;T|J20u->ME9xRdp`=E`dB zWk&SX^*FZp1owaXZk6~4sKS~iJXXtjrY+*%?b`a!RfXkn84p#MOZZr-$h5`&vlL&k z$xVVS!Qsc5REZW7W&PDI>A6-xsh7YD&#?jotiT|eW|Z=S%$j}-V1#0P{8Aebz)|{d zDAlf;LBJlAQ?o7}AdY)%*65HV$vD*#oAv=yC1*G@g9uC1xsk6mO*|DUS; z->Ygn{%M-FQ4oI`CH|DDBcmsZ9nPd8^DpPLjn+rKmiM2lb=QH&;xs*xJz6&wxv#LK z=m0nA0ftyk<(B(_3g`c+n!|@E)=5(++380xdL;k7=htH`% zJK=W4I$AW#q{_X#RhqqzJJ!A%mV*(j_a~n_HI7L0&;glq3d3MTyBM6_H z-Qv!5dWw4-Jh>UUevC%YhS%G(TEXIIkI>SEL|}9taXh9um@0)VYH)DHqu?U$aYgY|E6FlHLq6t(8I6Cq}hTz_L|j( z4!s_89&NfUPNtSC3`tbJ5Om#L%cGSoX_hSd<17GW+uw@;;t)Ti_P3Px@1;`!MU7_6 zsPO}5LbTwLrv@<>D;u@HJ%JY?jDDb6_#D7YBZ7@1kK&sHxSWvDE&$O@o1X>_t_W{g zle?xmWP_{L46^}aCX(@LEnnYI>qC|Tj@E|pt*0~6wIe~3+X8-Cu>T5pl0FU%=T^NC zNGP`_A?#pIf7O!`Hp8h64S#9|Jx|R*-^@Wk-+iygWSxFf?}{!jojmtPWzJ>Z#r5Rn zX4X%#gp?k0aNMBu^16zWv#KU$SCpQ&YSlT*r#mmqEui)ai=+S2hLx*U_|n%5b=LUb z`a#NqFfmG)c%ND^S}@-{u&qQN^{@s(oN~7i{~~bD_c3Jse+}*# zqnW=>1Kfj!Sj5Yu!Cxb=M<=l7{F~yA{-@ylJ~3ude+K@df$kZO)DCf_|974s|~!(T)nuqY0+8k0aWrl z!bt&Twdj77HiA;$vJ9tl<~#a@Je-g9R|cP+qjD?--IIt_h%#8HwNCG((F>80f-@~~ zM`a~qA@ozIr9t1qjxW}i3iS9x#w7aj5@deayHKA)G0XIw{9>dQU!k8J?<_B!F>_k! zWItVp=E>uzWPWZx-N=#o)V@&fWJkRESV8dfIrYBFpS*e@048o+hBOP_M*T2;zttVx zgS8y<*U;@tapTg(`f@5+s$axG)+v2X;l<>>%b0L`3LQvENT%m6vLxELtMdGnMIiUe z$sM3|V$ruc@w9zwnkQ|}ZYf5J^q6UpUt7+N*PF$tB~fBx)RGQ0X@U91q_#2os0Ra4 z_IJICq)>MZHu?d*--|lrjF4G!Aa4(sWitSw{wNR#H_j~-Mu8bH1D|0sOKnpYqLbA4 zc|v@WC!r1De2c)Q8e;C8*jB2K`b{rrbSmwyHyY3V4(;g-`2v3sKKe|mP-octhh{b6 zWs78G0f6?0(z0NDaZVWjXace;GGr)>e-xyQ{!icL3Uw6&@7%*^+QU@(KRUGkKsvN= zZqC-rV>16S1~f~E0gYNeRWiR9`&q%M*f%i?qW7xbB$x$AUDH@_#_ChiboJVVeL7|%kyXPgD!&(3*L-m?(RGl-ozX&P$F zb4r6a6EQA0qS?W|&B+NzI*%9W&B=+pA3%A8I5`*dQ(V1CIZ-@MB-}&F39a_zoSdLd z(|)+$5|^EXmy=33$-&Xf_0iTP+!v%lmo!ukeK^nzUI#FAg0HCZ31b?yPDto82&Now zhL_`)(xwEFN$EYvA#O>^r#Id+CYh{$m=qnm3>-wRSL~J%$Os903O5r?<>BT5lNypH zmZt-344Bijalko^nTygRus_uq)s%TQ&~nZFW-3t=);@git_&d(C;Qc)IShw}4^ z3Vg#MfhjE;Hjy{CniSgiN}Q1{dctU#SUzO5%85X&-r^YlyL!7cRG&35vK=mVUwvfzW zjnVPAO{jJwxs8;^N*ffad>tWM|0AG|pt6R(ab^jqPn+{IT*zT9chI{x3;q6elJ1Q1 zOn+aoHi%0~O{{TAiNUJ!>IU{OO8*0bE+l)n{DP$X;erpTlOiSG z84!GsIwvOt?Ir!NuRL8!>K^IG-vx+6>%NBs#AT4o{L&tV>NrUGlTcL0#vsrpD3F%W zp{LA_{$L~tnfr6{GQtCs5tTfDp^)JT$c&nXRrO8jwTo9aEv~O=a>DFBexjBpDc!$F zYRmMff}!vv^eokywAFLR0v));~EFgnxT#kbJc!AIZ$z zbwyrpjt8u-P=N})weFe%AJQiv=|gU4!hKB(oEUz#x-|a2px=?Kzt(_{QQs1e9 z&zhh&Wo?wO7T%Nw!FGT}&&u*4EL45{>AoJ`{f z=`ttXu;-s{?u>tA!~QQ-WBy6Ib54Ek)`W+*#yH?)ddq2V&Lee}6KO2@wI@xC0U-^s z9Y#tH0vV*v_(7ZV@3%4MTlKJwd2nAoFCtq8Y_0i6pw~b zMGZ28B%6(mpxHbn%O>D0oc%4nBMb{fG^x-U_f09PN7TgDOa1A#bq%B*v44ZM< z=7rbittX7>a8FE$iuxjI3fvQ=riYAeW%{U3>Db#b6DV1gV}T!*%B+UC&1>7n>7(vk z&56z@33b%@sy&u_F4S;Q3oY+|qV_w|aQ%NLYI$ZfddmwAw^Q05M*B^*;VG_oTCJk* zN`M6+?!YUays`2$Hp?a5r4tPa=TAZM`i#V4}9+JD|72+QKsP!&s5KA8^ zy@|3CS=DB_6V!RvNC)ZSu~M^WNMf#QrCfm(mPyYUMME+jij^;6S4@`H;rtZZRw19w zCRIobpPY#9l$xni&?mEfFUf9!IfrkW6p-yB?%v zFUyQAo-181iiQD9f37qdbk!5UeAh;M=NLwS3)EafolnJsd#F~LWHb!B&)#-6OZG{3 z2}Z-9dt=+iv;B2a7C!XH(+p;ZJUU5W_Qeudoee{1Z-YFVjy@-U%JP;;FXB)d?R-hD zXDb?|O{j4?+xfDb3`V@$!F;~|C8ZBJ*UD+^vQ^T0oI8{%E0jFeFkdzbZ1h^`2|WLB z(9_ACI%@8#JD0nIGgxJUe2z* zb5diksWFYQ+jEulI`>o?3P_zPHc{s~ zxs&bKB>h1vwh#>IVghr$&I$VV>xE)=(59avY#A=U%&#dTAyWrV^IcM$#n^U^KI-?) z)V^K5jY{Szk5J?1(mkNGG>q7j-Zl}v-ZGtkSq<(<19y0iWINsRRFS4WD^FrupOR`( z+hVpePc{ow@sZp`yW7O=tm--GR(vsvb{5HZQ_3v4adV6O73=?!^Z^c!CbeD7rtV^S zEjwNe-a$Ubr)keq!rCj8N`d4w`53uk^dnhGx|}btUB8je(Tj#Ltn|V76!hIIw!|%O zTT_@+E^xaUON|zXv+OE4K`ZNQV6Y^j=c7Szx&I3x zi?xiGF+dlz$#deo1$90+8){*?L59h?>O#$FOxt!zx0CXQ%|yE;jFB5Yl@wtl-B^z; z^sY~(LH)Qg*Ry^0_#vpp)x+H*sO?kf+~y$s)elZh=N5V};06pDpu;Y6uGI|XuwaxU zC7W{w|6kxP;wB0l$SLq2@!(Av5B5bcH~6~>3iF-VwDFbu~q9 zH?qqiF=inYy1JsGd`~q}y%iSLx{-6b8h)_anj-vl)#lgw3JSo{Sd-@5Tp6E8CFMqA zqQ)GaR>yIkT7WpxCj=T1X8Bu8u^E1M1Q@`}#)Zr~0g5)z^PWhT+dfuQsAaMlAefyN zQ_{r7$&;t&O`MxsI@UQNy9r_DwI%b5ixPEAtrETz%)gr3Hc21#1yG{fE%1q5iq|=;b%JOY`7&i;m`m?r zqE4a%9&rOXlXOLF)3Z{eNSmgsDm8v1ZK9)%5=d;L43p_VzIZL$a~4DrZ7Ma#QTI>c z*D|bCSlEs>`A?ccdyV?R+Ir5gp0|n&^J6UTP)@1s@zbdB3qYCgxkhTI^M55<+3}Ah zBZSUmebh+$k4spMswlUz?qJ9&L4;#J9xPL|#h`P}!41z0ezg;$X;uY~2bp4V&| zxeekm_EEyPETL^++2CG=kSD?n9Vg~W6W>D$xrQOuhAvfy) ze5XTGl$%+})$yXB$pnH-s3PaNQae9+CTnk(8bop~kfPc6adMkh#4OgnnIA35kUxVr zEJmi8Pny9syTW25eZ9VbW6=zmrRp`oLYsaAhKlvC3v=m}6oKbkG0dU8Pr`DBAqTR^b6`l*HWKTmb0GNLah)BF zheuIWnq@Y1{9PzD`nMu9C0j1ww!(q5|5ZWHOKtS$D1q+`Xt9Tljo>e2MeKD@uK)(jI8HiuFNQ5R8%H*OR1J;eQi@w&yFd)o_YbPQViY) z2DFvyqdp|{_r?;of4lrHQd9(H&?^26Kf;Z0~kyw2n4?Ej`BX zUCX>PWC+*bB>7N#G9UBKky8at4I!u@U(2p(L4F%!O>_&b zSra{vS~{ak^fkW4OR04}2Ps zUmYE54cyC}v6PqT0H1QSjZ)8xcJb>w&EBP}eA-lU*G0QdS=zmFOrI@}A>MY++wpqZ znrn@vdv1`D1G$B6iH_+*-8X<19Sg%i9k;x{^s+L5gFda4eWR33N|6#nS7ph+?wGhF zn%e&k0bX(ggmuX)ik+rq%Xf-~CDh(-Pi9?urPDv`7#8kiO*LK0QnyP7b=3W+*q`_E zGM0LWG+nsu8wtXqQHlwTwu#z6s%NQpNk(00j2+?EG*DxG{M{i9eUOeHYz%3OdrS)S z*C>6|%qU&EuD^JG)J#m1GV`yNw@ruiTgYdLh4W$?S{{79_G zzYT2T1(JbRaTT@xQK)Cm<5G98hb`A0_5y1CM5rR==kXWysQQs}Oj{IIlR83#hB-*y zz#xc3Y!1JW`%}w!b3W~#BihON7onQpa1HHUAdhBa$H}{~J6Xqe{zaN3vXZ|_JGJ{= zNXNdE_S440f<%o^OZBXxQoa|Pto7{J*ODyq{#eg8&XQL`PH$kx1eqUelD1Ii)lz@0 zOBkkoj#k!1?3jUn-?d&!rxv}CL*4gE&uQxxL7Hm|N;5lVk>7;!`=B(7P6>{z>gCs$C(2I)F>SF1eBs5e}Z zU&Gwx(v8|4!olwUv0RQ8_%T)Hh{Ziy2CBbYx~8M+3k&Ofk*`)gn`l?Ph)hmVhM$n| zhUlUFM`Rc6-=`*0r&9-I{u9XBsufZ#%f4SeB9Og9J}MfnWZSNa-{w~**#6D32Y37_ zZEKHDWGUCl@8LPFV*9s9E`gQo!M2JaQAD>vY@3OkEAnEaVdG=d+h(w zft~vjmu11H>N>#_a3Xj^2UT9q_bvC!$gO3#mQTZ*>G!Y6*U+#3g56#A!!q(x88-8o z7|-tht2|vt^~2?BMZ*?yn}j%4^)@US?BU1d2?o({9hJ1k{|b3i7gOP_smodK2wb zMK^8uLcW;leoaSqvPZ9e%{PKr_x!<#Yj=7E9N3Gmt<`5nE7it)nse~v;Of0!V=aQyl}q?N?;`JL{cJWbL3s%e zGDjcvr`7yH=CJ5K3CBcAc~yT2e}>!fx`%li6P`tJ>FOWYJx0SMt-ZUb^=D!RbEPXU zqfvK~bE0DNaz5J>tKKQlp5;Q&7HmRTT8d-G@7AjBbjcs%FQ6D1)AyBP1H94^ZHF}U*Gt2|FJRx1xp&X0m z%hm7L^`**U3@}F9{wSlb$0@w8cahtPRBg!<6lB^kJVeKC&?m5@$;x4TdYFnwVj|{N zDEm#(dchhc-Ybq3)(X^qBz`%f-G8FrxV~*ZNpls<^Ix~0)pjmzn5#T#H9WyqwawE< z9ckc(@3D0CkCXvc!z1@*wfzX$%DlX~6G)t=d}TB|fVw`a$zSSnDrIcpxyp^W^JC;5 ztbD_CUgaCqcsIF+Do7nys~~lpVGp@86&Mf~Dcpd#m)ve;5}UeIxeuQnCwHE*j$PEK zTmljDBwNs;JT0*2*D3|Dygfx-Ep{wgEy{cN@);_K)ARw*7eJ!HTkq5|i+eJ15S)wj4E(gdiB7i@ANZ+} z^kq%%^eqm%b5uYp3xt(k;Q1ENg2x8e21wPld$=<4zzkWG&&%X2WohxVGG?czduJ?K zF>Ksf7cTH_7(6+1^!bR8S#B{qV{ikmnUx_3b(og@l|w&lCL}0N_rV2nu)&yW4pgF= z_S_j8R}46E7$y&h$Z&Ere_(5dJ1Z=RIb`1%3HuJaGj^gq$p5jnzGl&ihWdt8D`{_% zGE^>x(IGtz%q!4f;c=n^E0u&ks{!tWB_fmnlYj5zP?f%P_iB@cR#?m_{eu>x@OuSO zlm2?Aoo2mhcb$;ZamPSe>rQ_b z2cm>QQ4c^N{Dr>D)wY0cnWHSW8lKxcx$QhSmiklcTxA0FoeoH8Feug^6+?^WDmHDr zD|~o{{kSQ)MX6?)t@azRzHUlTxc^ksCNkvWvptavKz9~%TjkKs%SO*=Dv)!Dt*-9`MfpHrawKR4rX^e ztjvN-U_Wa0TRL`5%rw^3smv7FosTKAv0T4Ftyd}KY}y{B3%7ZbTH6&5%YRaN0iTf3 z=P&w!tfbgnCa|T?DqGN2Z?V>KcF3foZTb#&{fk&HMZ-I!{se1PMT;6I(hF1J5ryRI z5L}dct2mn8K8~bjn{E}cVn!Jbk`w6}=-@YEl3L3XoNz%L{7Z-GRLABD`$J-0E|or| zenEHn)OSR89UZt;>`zDY^n3$(Ow?V#i|VFLPb%BlMOUbEw2jCi8nr}yoJzjX=hNuQlzv?9hI-6jp&zl985K57JF4SyhS3HDaxg1l7KbZr+Uh1U$#XD6I$%k_0IshLo9c77iK4BAAomp+o1Y175ABP^`jsNc4i@;V zGJ9sN`jlV^tezuBk#KihAG+^Gb-HeLlt5~%dHj9OB(q?C)X;_<(ck3IfXreI%k6&@ zXn!9^49Vlw96n#)#_KS14^W@SM0rmf%#Ms#Rbk7cAnbJ=RQJ=ttMr=d=l68r6MZQw znW$a_ljjkA;{z@B#|60{Fp6?q`6%cQbl@1WvUc4nM)M(~u&3TqMq)Jok<=OR#trD! zw1p4p*!c;Uvn$_M)|#xks6kQU7BMYqPDv^q0PqHCdvCfJg=bLO)> zW7=wA^Jz7Oo+y#qeLlYToVFT$)CYdT?LBi9GoO9W(vBlah0*W<&iI7;s8YCYV5Ej| zm{y(3?YsQ#z|amywc+_Es{C4QV9(FB>)`1zLDi~?QQzR>@(5K5_E*HbI<~FYzCh&p zR-S>g!EhAM&MEfcd$-nkgMn>sR{4~2kKk7tM@>_Q(EhKKuSHib?Hy&-^w49>-KF1- zv{d8l1F-}7laMUBbLr45^%@?BDr~35|V)sEPV{&tV+2tt0H`2fSJ}m>Wkpq z&}LLYa2`PD`;Q=rMPTw2Vjge@NUkT(om*6pQ|RIIf?7+Aj%F_L1I8W@MualS`|+E- z)rz!?bq&6zAo@-l)xh5?!3qTX zk#;r+i5*{_pWPf-15dj{PS*LLnBmNx=q9c|ROq%G77O{z|D1XM7Jmy?5GMBF z8^jl|>HE33o`3ZPc}LkR*o?m_JFxuzm3hU4TZE21e+2>}R=tW%J+9mb1>{TS+^=3L zu!CiGkHGe~t7XE2dR2ta`S0B72hhezR?K5ql^+!}Q`T2Z@T&J=G25;@4hgbJe>uPZ z*KGOc%0^hgpHa11^w-Q4$Jz3~;V5&ypiaUXdt4d7&;5oiKdv3+bs^N{4>)R!V#`Gp zCA`#69mgi<)uFruNXg|!b)PlUo#pZr`7-l8)IQPLk9yKgmHIhkdNAkx0?2X1A5EM- zV|s98=7Wh>+nAW(tmWZDl)59@VaW7kx%|g1!83tZyV)`%;f!ADu*pE{t}hac}Sv4zbS6&nZL@5|$*~8m{G0OZW%>*VrE&8fbQy0u|S;ObuP> z?zwEqD0dDn%hs;d5cONyV)V4)*T{y;b8nqg9Jq=QmZ0TA)$w&3Y0%E5@fVq0+DgPR zNL@4Wy>e1lQ4NM(D&79#;zQyt`VRnf4{>-L(H_Vj{_JDxdS#aZtr< zV;60DL;q<=ce|bMjHV!2rd~J41QiFT#nOu<_O)z7g?gz@H1=VwSEC9 z>_Iwd&xeYaZ$K_%SFTd^Cb8{&*1ld{hFK7g!kn~ofRanfF=ZgVl?`0y{zvUrw&y1b znq+^sz3-L>#3Y8D?=&IQ0o*KTOr`d3#KF{H0=g^I9}N9*o4_#g9?_=50BYCi8mRdr zF`93+E0|o}VC?iZf$Mh5$ zIuqER?oeY<+u_tY(|idjSE|dXJ4>l#w)KzpPa(c#NaOGhV;WvA+Gf}NjNQIR zorl^Wr>I4l%diUl9G^V2*QVS~`yLiEs8Of0@Zk;|qcMlIb}OGl8jdn!(vKQ!e;saO zBik9LPSvLLUbgA5B4ar?g7s;%WWSPEJCFAERU9n-RrMp2Xe?mI#;I8Xzwb+2Y8eZu z{R1(VJ=)hkR%Azi4Kc@$7O`WK)%(RBV=lp%n@&fZ_8a)uXVHO7rI3yMgZdPzHGBzMRTcmr+i<{duEt6bc>Bj_y?z45SP6bJ?3)x3aWh z_TQn9NlblEaR@9v)85x4G%|Ot{YA|CGTgJAEjOz-hy(-EX3801-zOTUQtMvj8n$Jm zeJSoyLCY2Oy^xLG-x@R6TVw1)P;w>n?iHH_c5#`#88w*34yd|Bk-DEWMW3KAv>`T~ zyp#1q;7C=k6ZjpI*gI3~i_M1RNUEI|k`s4R$!z;$M&lIJaTXnWL;)&xjvc62<7{d* z8JZdPv>Uj|#yE#cZx{1vQ=KBQ303xoamrlUI}^E4oTR^w?XR`N@o4-JrA`wwnR}7_ z8M9#pI%00Pe!JIU1|0{GctvazR|>?3gfd9?Q;dpwO?ZQn)Z z>+N&c@U`{?lVK$iYifqFGImvqeYvjEcrFU@o-hSvSjGfTAt4>86z!n5q&+=(GQS@(klR+3MMQzrU4I~YXt*O zFuQ^WK}*35%z?a-IYq8Q+MNdj@j+-uWCgM)l74NbU!W>9t7t_(q&BYCP ztMXK__`{i?LTOW7XkOkdE*FRXdoW;g-GO@Y)Lqm*!;vsfI|)?@4qsjkT!uWa+*qe| z7?PYKz6ehgoIYA(@Nz++0v?s3j>`P_X084_0IIHt=dX67jd7XF>qZ_fjaIIj`>y!G zA{?p>xCg)&QtM|oLGsCK;U!^zMhZSk&9n#@awPeG!m zYHi;;G^(M>X6(R5hkM{??~T}YEF+^CshvvCo>JLfCMDkf)kZmk;(h)z^xh@mKEoO?gW^1ZdO47CT31X z4Va8G(q_&Ku%?9Y-Zksj=l4X2)3GW?KOK;Js+i z^l)j`l3eYGD_z5rM&a}x412)E2WKpvllw#iZ@}H$6pWn5H(uTV$W%q%G~6c=6leey zN=A*A{F+?D;)m0w@i8WPrcSWU;9oeahYsUd+Ho+BOe{x(t_=m4^XU9Q{&pbF(f3*l z!OiRh8WDC;&0MiC;+w*J+PfqsiH??Aq7iRel*3tO7ED8bVgH}t|GyIaXIAKQ znP2jc3VnK^&e4xX&2rfOy+r-#OZIOG_8F&;?3f{bAKQP8bcf6b`!1G10~hiBEYk@{ zkr_eke_~Pp>YqVRtBFvo^%AvM%xZZ+nn8GIE{y*u2{|kfI|aeCjAy7^Twm9a&czRK zG1VdQo#V>zxTy3`*8SMo!bhMUu^yq(f41(0naY42_0!fI@-TqDH_b2<2!3g64oX{c zM%!XZJ{)7ILN@_G z3g%NwXi8NR(&^@h)dUT%|1T5GeL|v{3uQ5CMO4(Gs1?8m)tRp|v;pDKCDZczjiV7) zq)s;_#08}>6lKiDMO&lWR_UW2p+ip^O9H3-cL?ln*(c8886f}vHc&Xi3>4`6TD%r( zK8S^f*)N>#pYi=)L4fk^jchNmD=Z7t|^BP>pu zIfYwfPGE8RmR@=awbXBL8dd{45yI4A@Ph5;JB>{#c_%V6`7aGKHKC5YenEqeW~b_9 zYT}#Ux@x@gV5tH}B1}#{*zm-6(zqQSfS1ZOD6VQ-!=DS5sf1ppsmR)q>CJeXIbfCY ztE?Rh&QJ11W=e9UdtrEqP~0@#jhl+NJqp%>fL({%BygS*vEGd|gWNx|@Z=7X zf0aZ!wS(l8;wT4pk~1%k;3B9CI!OLo#8GoV9KrDcdwq{TgfEwVpGa_*1P_3RNAF-s zJ*98t`-!ND0k6nu$*AuZM46%W-fvd1@?fOi-iOE~KGN60MMYh8z|V3L(SteGCp14mf$F3N#UW&k}16?9&Z{l{QH$i&vmot-#kIXds(G#&O!a|SqiX-UR z-&hNV27rnhkGOw<_OI`dSD~0BenH^$ab+_~$4+oI`Ia^Ix<~J&7nvJl7oWtv0f7`? zN`c296dHE27STKhAu)me^ZQ6ph-S+dE{_r}zfY~$AegUR+_qXD^^4h>)d1wLp&$SF zz*B2KTt(6H?49P;@%wEQ;mFgxm((;-i2Wg^d{>VPJwjr9%aor3|L#9|uNWUHzX!U2 zeMvqEvm9|}3cg^o2As#ax$ayym9|F5S#q)ES%OysYroXa`COi{SrL(IS6TOmCMQCv ze{7$|@gt3--e^C9*ap!|o62MEp|3tMtrhb;E6W#G+F5TJBk>iXPSZf) z{sq|m0FXR9SRysXaaLrZk4b$Fi6X zHy`qE7IVUEOuFz@!s|?{n>!44$bh;$455pGBwG&pgKSY$_^)yY1ZFB&Av8XSN#`p3(Lxj=QsSX6{y4lv(?B{{>=u?EEcE4$F;9$CXyjDK2 z*Re?u@>uKRre=hnh@y%1Ut#}%4~8bTSMM0DqvKmlN#ry;t|I3x_E`SSD%xvyR8jgb zq6?|>1~kFkM91xvVs)(Nz3*aIeQL@C9`t?tOb#-I=qw`dc_1Nke}z6pG^CuRk6e&_z>(x3P$l_M%crX#>N9!WQ%-^$8H;Ft;biYL8P-UfV2LJvA z8E$yaBo|A5N}`GB(){OXGM1gK4u1p;Yq^+pP&F zOQ@&q61L>pGtKuDw5`=gy|Xf6V5MserF>xB&6L;e9|I;F7**HsQ3bQJaV@1@5tG8Q zcG+LnZMg;k;yAXBgy-$ouq{{EJIsa)^-+&!N0j;_4v}7eQ{T3A`lww6p|2Q5^8ElE zGW!r-_!_)6unn&PV5i-cVJu_$nkqO(@q1jvTA$@#H9n%mv&0|U>oiCv*g8rbVD$Jp z!{K6Mx7v3bL}M#EaJ4BLW3=1ek2wX$eHx^*jUBj#AKf%5W+`jD*5uWWUQ7qJm?rWW zFqBk&jdvK5a zkU_rzkBCr?=n_6xd!8_nKjEc#{e_{Yc&wcw9lypD!yoQSw)Y(zrTu+k{=%>> z4;T1aeDj0*?Thq=W_{G2vQVp0_c!)rtMRI>32hg_rk@x3qUUBGQ`I;2Ogj1-yJR%} z7{y=B?Z2aXwJpcMZzQ+Fv5zH-j&}uy${AX%o5Q9s8)u6*I(NBZeBigXX;^ zR9G@75kIuwigG%5IS9c2*v>J}*YZmrV~-!Tm*CT8awj^{Df^h|LQ2_TpUJ-Z%)Z)W zycn(cLZ67*?-Q*?x+D8yu;T2m34NnIu+i9#I$g)y+wJ>wR23Jk(2XCNI605;dUD@^ z710>ysA8As9T(x2pySAN{DR$WcFY92_a>5Fw?D<6Qyhi(?q)J*vdl-ixT=cEQ3vbUPAI>z~ukE{mHMys3gB=N8Gt&dGJ`!h_$=D&Mrme^;PUE<62?V!!RNHy+^)YEp+vXZA|5$L%rDw?Yvm8k9sp~ETbJ?V%}+vn+2EgHk@)d03t})o(DS_prbw{)9@8lyL|b0pM&=t zM}c zE49pU427+dTNZ-YJAQtyc7B0|yvuRBtuA-2mXSDw>hk~y*?0s&%^^4{_wGw+$Z`hs zVR%t6c;^*DJ1PjGE(_i6YD!&iOQ)j-tBI7$?FO3M8f`%UGKW|K^~e9B9FA>-l2@aB z2L2OAH4rAx0RzxtEpUXMydpe9YqTqs!!Eh9=|*)UYiq^Ry7TkM{ZULT?USJzqLGXm z7@;4IhdsHQx0D}qo6DQ3uf?sk6^ahEZg1Ev&gfNwzwL6g0;pfTSr)AHQk^3~yW?Ot zf3i>m@jIe{fMN`w9C_snawv7O-qGQFcQAF`W|iroFKu!CPuVP_di6;Z_q>(IJwv`8 z+GQ+`!TuOHNNirb&|BTF#l$VtcKJ2)`Sw!-By!^^|A7tz3dO+opAXtxxAqm^lLrq2 zF$wDc4r>%3IM=_^F2esnF?>f|mxtT&oAqzzfyUTYSpx_|=)y2TaQrPV2`{vK*!=;d ziujsC4g+7KO|+8u-@xYGUu|e!+EqN^jK9!4%n$w@hafx|BPdk$mc=~rABGS{00)cz zA>bfa;QKTKiuc1S#VrEi07ZYIw$lYKu7L(l)u5B>o8g0|?hGVg3kO#s;aF_*(9?og zY6#0<^Q3dtG8j(?u%Bl9t>saIr&~Dm$M;s)ni6ACTt;d)8s=jN!sF46S16yFJXK!$O$eVSQ_9^ z3tPy73s}I`*|UK0Gr4oB=SM?EYik--F1~8{e?2t%wHVpI!fCtwh^343lxRzrb>6pE zwO-ZoFJL{ZB45BVIY3%V`iR{bU(O@<(M`Y?X}chD-XOuwffbJOzOdL0dDw8nWU(1B z`YD_gV8f+mwHe^&h{dKTe4=NZ5JY_f40Jjcif_TIBj-N+Tql_23BYE5`%0`qXJ8#n ztBI_Ezm6xUeOLto2yVUzq7BKHYgHo`nKRfvwUO0>Y4U_klyjmc8Vyrtr12z>Y0H0* z)EmGIy8i~m_74ADOdN?*Ep}?ig*xT=!qSHpd}c{3>k%+N^qJC5*iApQv=fY=UM<9J zMkg9Xc_(TiD6k#~{(^iz9!!lAbD~r}TcWmGFE;6;Ky$VfoVtVS9B&9#Qo$KLq^`-O8bAZHg4rRFmU1-?`8TKj^o(W+{g5Dv^V!7Uxec;Q!~DN zkgI>1&J-|o`&U07fau!B#S2G$9(Eo`1Ls35iO++unVKS3ZGl$do?ZYwnTPCX(Lm4%3kwRUgnXn^%E_-o`M|h?6(5>>?_;iN+Izh%MbE#XZC{ERQ07aYFb&qB zeP{~ubPf+(lWRpm{`5y)z9kDD4nIn*)uw*bcw8`Af?r#~nPWnV1yE&)HPzJqktudE z-#;OqDIYF6-gcfGfOp(zJ1trbn>B{Z^s44ZoGYSg=>xhsE>AI^G+Buh7?p`9(+^yzPA* zW_UHC{j~no+9kIZ{zQHgj>E>KlG0HB z;D(CpXQ*Xf0g@bI7}Vq-=M~=91cWn6>Oikb^+AW^0qyJH!;$R zd?_Z-O1LDbUcKgjX4~*!55wHSdpMyP z<4!XfM07KsIiX(W{aJ&auFdy(u%HFBK5l&BmYX^s&%(+Vze28l<`vGzP|Ghw`U(Fp z>IQ6ap(p4lEI=S4_SIZLcPOKf4hU)bpP2 zuY<>V>T}Ei?OGmRr@e<&CZ{m)0k?%+f!|sSzRSsJ);?&Fh$uBs37iB4Q~Tu?@HDT1 z3#>Uja4~pwq4wT?anb){@67|Fs?Pu6%uF&f8M4ovJ7FI}APWIzCOdnCs34Gp#iEEX zNhT15B!qyV$UtJ1wzdUP9$PI!wYIiaL9N%iAhc@T1#4YetDvp=?V?a?Yg?D!=Q;P@ znYpusfVS^HZy+}_=bq&`=RD`x&pF!X=9bDfAOqy!- z*mn1GIxr5`Ur#O08Xm$8DeQ{p0j^}Zy`_2?2nIIIq9{m*ua%cJfWfGVC$_xuUST{j z;doZ!w@Q4%A$9l+;~28Dr!gO;8Ik#_)H6 za9lN`jYz;>{?3@AeplxiX<7yR31g^z7l7eAs%5a$mns&is8)qdw}z68Nh&C62F50} zCABc?W>r95B;$}byi)(ELx#&{wSSzdr>S$mt3%D(2G)pKfh>X_yQ;C(z{vHKP3Du3 z)97c<*hWU98QN2SpwZznng0pC4GAM$48CEDAn;A9t8EL`ut|(3mobk2;!`VsN`Qfm zr2%2i!^Uy&Y?gHC=+V5wS>?EqK71q>Hh@l>S{3DF6-Jm1kv%CzJr$D>IXD%;gPT|B z9=r+~^=9^Hak!TrvnSk@_0T~1Kf(%&$X5)d^4O&?NLsMBHMJ+$jkKF>l4p5GXGgHV zt4;WZLMmM0%*%>$-+78lc#G!{-q@KPI?WxOz|b$qsF9PN zv-A(bRolgze2VKPTwZq8gv<39PWG*%tujv?e>f{#M^F2jAkOuj;=2^#nGSD>wzKK{ z+4Wyh9rEgNZ9NsmbsVvCeq&%Y>S*afR~LGURa>2-xM(KFe)4ZU;y>~IZ2o*MKhD8* z7dpT_6%%H|B!??!8Yj#TtOO((j z>$9h(994Z!1Z@xTwB8`?8)F^L>JN_Dwmz(su5!L?q0K+V9>fu=oxt;rNtw38`3nmj`Vi3m zbT3Xi9DKbqmtX&i_I<}$tav|m948E9d5Qg5&(Qwwp#w8+b!L;-A&pfw z-R8W;La9Ged-*J_JzJWhwEf8WT?=i$#5t5k-su$X^5^KTOPn*5OYU^;w$SQr&Ox-R zLmlJimD2qT;r4DE_}c!sGdHuwSKS7J*v18>r# z8MNXGr$gyE(Q375HA8c=@YzbgS4#ilyxU}hQPbO2wTzAyO6AJ* zuR9N$!uU9deAhaKME8br{~G>E(=XE9dWJ5E*T$@HVHP6aPR>H#kQ#uKvVKqYHrQ`GchQ zs5V0?rm_qPW#wt{v}lcv-9Z0Q?xq)Hl1MLAL?XM2ZE*5SpG zT*;wanIrXCXxa^ugAP?mf2J5u6P!g2j&y-s% zjaLGN(k&J%ZXQHnIIaDWvy?&ni&9a{Fs%4Gqhpygm=kfj@HdAql`fgOka2VIeKAkoH;Vqe`ia zyfu=bRVQd|g)~Z8TqFGiMD(9bmd=%OD1VAHjGI15xz*AzrE!Y1AEny?*~<1A(ndsURbuop1)03uKSZ1%)opf4%6YS- zpMjQ7Ly4?&)v^COWLFv+8A{J$DHEqbOQZSqKXKv|P-e~|$z=|<;qEPrIgMB>Ws`G>G>XCc7>k-t zmVz{&w}inue;)FGZ>2QEikJWL`zSXgjiNVNfdriwNjm{uvjnQXLK4wu|E8TQK}j!M zA>EF#--Iy*@$*sF1yV6L{~x8Im8bLJt(c`_OC;Rpwp@~J-e3z}enJPfN+UQK{OB4f zRk>u9wA(_rti&h}UoE*E$nz;{K%j{GmPo^uudJ5tz$9&!?Bv;v!IQfrac<#fv~w*6 zI_FkNR@QV$y8z=2(pZ}IUCflXN1f--Y3*LAjwbw2%2TfCkv0KkZ^C5dTq!Yiu%##! z8yV0;b_`9<6=-4TW`TS*lTxvn-`uwqtHK+STwvrjGgCr8!dBHTAbE(=vsJnlwO)v_ zJxx+R6<#IfaMcz%&#S*RdLMwo8TlI+X$o0Qa2j5~!rI6$MdSKKgD; zgH&DVYoW$@81NBW(L~p^5^l5g`st)370`|@fgv_49o`@nD;MpMZo}+vKnEV!iv~ts zFIF&aTiJPoRH|Hjz4U!tU%O5kpN*reP`$kzKVCm(`1ozoB4x*oQWseK_rYX#>{3gn z(}8X*tcIJV13>=Ur96`NiV?Rtc*JXz)qADuEtHMcX~iv=T<>jSuwfFp>w8X|gu4s$ z`0CrF%R$n1f$3gylQarlh9RUu~J1Ks8G_Ztu*pLRI}m>O ztxQed0Md4WJ}KZYN0H>FlivihgS7J&TFS4XqyKg~mHg|Sf5OYbbo@H!C zbay%{$$JxcH+aOONE%9SSTLht1h~g{uk%(U4Wk3QogM{j;0=5mu0TwIQQS5{Nxf5i z7)jeZkbCNW=i`9TgS$N&f6kXIR$GBCTFhdB;(ojz zO*__OI3QEtdMH~Vix9QcxYlW9>4Cu-qoh758XK!XUf>}ts@6JoM0BiJfr#*##bGNk zhz82oSDmk;e5sQ9x+p(RNj)Y$lqsoy5Ff_V+W+IW-$UD0+k`NGR|@{(#DzDuiOSe( zoxhE+Yd)J50jzB}UoM#($vn;mwR*P`xA}bR#BDw{zcThlt_2cgq1gtL=k*YnjiO_| z*;YXZZgNgmrl(53#84voh|zbLQBaIxzJD^?ss#U4z<*yxE7cUpmBuI~Inrw=UjuRl zNwSzZg;S}Mq{GO9+mYSUNV;i?RKmQJM}HEThDa0XP_C54{3Me(>3v9_tfYD*p6V&I ztpKzYZ1Yu%-8NO@ln`<%lxfA%W5_WL=W>E}3@MY|!?)?QVjhFx1sG=A44R)SRVkAy zq$lyMj&|lr6KJzbDo_^HfRCDOXDO*uL_4_JcoJY^o~HO_NQ}+1sIFY9r5X8H)aPvm zo`PlZTB}!5&lP3Prn<=zt`nat;W}{}t{UDd6)E6Jui)FcaNvkcv*ExIALh`gCJB;v z3xKV-NO~Tb&Z7e@V65OHZvlPg!fwwd9>F;HNVcU)>R!>vMey24 zz}efRmuyy3%BLwQpCWAULui#%eGX`qyE~0q<=r@qnYqw%rD>HDcarO>VmU8>6*zm7 z6LnS5FKA&Fge=xdaG5gl1eB&O&SOv-&*IX}N>h)h^5@PH8o$vgSX7JBw2>2a&9R(5 z-|FO3pKYy5(`J!fQy&EcL8WP{NPHZGc*<2mTEu0x8PE0w10oTzJ$LRx1L z(zZ&Ocmi~l#_n*A#1H2{sG=wr-{AK7OOCh$9Jf zn`JoLI;H6Wya}s@v(jO;Wd+mLqclA%YSUCh(fE35I^-0dJDubTLA?<){h>q7 zdZ8w&G(OK!t}3cP353(3whcWc)XAjbC!rRi19tt*VPxQfk6)9ak5>xol1@k*uXn5g#;YQ0;O zra$m^T~8E96jGYrLt;clB(M;^KGC*SX@Y)eVY`IDEP>(Ml%^A$2+R^4>Q$Ql&flN? z1VN97Zh~zDyo1FQTc6VOvB-I&n)529=@b4AON3DF#I$|&XH62)m~#_2x0vLssp+3! z&3Eo~E{Z5~4P50lN|T+l>)M-;J+~`O8Jwu=YODy^zNR$gaH6iEl}p~5R$OWmB7XBN zrKu2abS;f}4}teM`Q5jbreZFot7-xhb|_6{oTzJQtOeM1DotKa)OEA51g_*}VeUGF zMbMsHX{xfbLh6jDW@7wzDNQw8%%GLka)s9`O;h-dQ6VERFF`zRP?~1&YhC>sB6yg? zS6z((MDg89(=2|kYhW|EPEwlA5=ORs<%E|P`; z3{A^oJA>_eO4ACiz@T8&=J*g3?Q0PzfygV{9;K<3a~YK_0_PN?atpWxzt;7u3a)uC zxCJK~l&Z=6W*>M2zcHv%VqU%v9>H(+YYJ3lE(R8{t{Q5h?5$uJoWr0^3H-PX3`6pn ztbuao_>{XDBGOG)N2<8&%gzYzeg?bpBeri6~BB+yutWhDX~S;4ECspg4(z^kZ-qe#1JG;N=8fE+G2} z*bBeWwI#3B!5Y<_U@4ret4d-Ve+q8Gi3Tl6jN@HkCHzL$jj9Lvf?|cUiUhVIt+Z5V z7I!ti4%?z&%=RI?O}i%r+n*X1dCr;JaF(Yi(A-jrqb-YyH&WBDErqmWlY2yJ=lYIT zI()0cY2`3n9E9-MO4B`#uU>2E8w9!H#&n}x5wTX!%5r#HD+Q0a7#%x6k5uq6^XEE_ z5FNyA2EEJOQYu_j*wUD8F9>lEpz+i*e{le}xYW%R0P-F!&#d~h>lV*lRP3)DR}R-k zv*+x&4RwofKIW{3#b?c}_u#mRrp5JTj#kf1^rtMy8@}q#sb7F&NoUWiTX2Ery!s0~ zb&D6lppT~J)z4pqJ!-ftreX1-kY_=|S7y(etizy1BVs4-gdSf&8?*oh%`lql)~;^v z2`)G0i0lm%r?Tp&j$xyjI2g1=JM_YEH0V?-CIzR1QsXbJb8%!uK3VRucBJthIo#>9 z8)uQ_?8(Ta=g-c_pwJWcOu8rCo=5xNPA{N0U(K)$(#}v-72}U>Bgl6YqVMa)R=1@2 zhPnbPgUi>oUml!V3D@q)VrxNG_&C_^p6>QFH0oVkCwA!KRJ&a}tfH8jercU;DxsY- zQ%ltSdY!{?<-%|G*c>DJKhJ^=&x7%;(__t{w9I@b?w7aQORM3)it|Nv2Ue~LEMM2M zDu_QDg5B%dJ3BnIZ4YAnKJCr0WvZU6%f|VNHHM2-~tb!YRN;Yq%-_3E%p@-YD z9a3@BJwVzyxg~U0TXsI}sZJkC*mGA;%g%Gyt=c6){Azl8dt0$@oNubXEUt8PN^^Th zpnDUax61AxONu|mTw-cT@k3NEbo3tE*q4=-^3}s)f8I2@`wCoh@}ELSro{^j4*WY~ z&tZQ)|M*2Lf%+LQ@r_TJ!BMJ+%?QA+%11p{BaW>K@naROa6eaZxBz^twa@^e{s!K+ zLr}ROC%wMLkBv5nFTx&-N`y+_@MJXxO^q(9s4Nd~uoyzU$^#V~Glo#C3IvpJPzg36 z+Xr8L*Oh6@LV~-=BO9$_?wSe|M;dan7>9C1Ru-jx= zgIxau7j>{DxQrU#%os`DKWA8}!*0vpHRHIAmYjplkTuQriMU*KdWF3Zf3|fvM$n2U z#$i|N(MPn8Z9jey>bGC$L_bqf?-L*HSDt=Rs=|#1Pe|XQy+$;O`e~K%rQqbBHqP3?LU#bhOVHnf7dZA=K!ypxL$vwEgYOL#+ zs7gT7#Fad&1gc!OU|aM-Sb`7bx^Q)d?K!R(Sb9(zhv{)SlpQX)3m2)cb?v2q+qKVZ zdtOP+cd;?$1!{U<@-TD-49J55kR$3?;rlVscLd_5*p8}WgMR*>0OTzU>We%+EoLPr z&-EQKfWM}?k6qKr^Doy}@*Hwi#x(yPSM(CiZE`J9F7mo=2LQiOjt`SZLO7_BXE7Ka z1{*tiQ*19oJeG%>Za5@2Dc!ZM9q8CAlzX%5Nm_9TJ^_qkx`6go1`Kz^*kza@Q0_Ho zE>bHbm$9xJ`TK8qGzxfvz6PBq&n1TKcVs%pwTe9BT*qj?%T=g!&2wFc7G9^i@va~Z zpXhp90hsqA>G#U0gHkmv8hcfGT+Hwr+~9a6r;Z2p_gAR9h#}wai*9&5GU^QT3MCx&dN@A$hF=mIA?JZ99^9XcD%{3S57KPrX(7#>BKzOOI*hvsLtscO2r{k^Nq$afvG(A=pmp>n>LXeIm~gImabcvX!2pBVLz#iFSYaR1Eh zy13mufiAxh?fylXQ6^exQd{{e&G?zir&M0=dJd@ZzEby|>rsJ$c|0aUOa6e{#8q35 zxIR@zeb03q6Lf-;WM#$}`H)%JdQuuI>OQIDu5f)IN@w!8{0%y$3mSyyC$6{5whtMe zF$~W%ffV22EPoe+LnFtzj;lzKkNajIsY%$vq)`W@4HHGdFOOKb7$8ZV?dwOUW&Eoo`n5 zJ}ccW68_EbxFa}{#z6ju6T2C^vy>}~<;|kqPiWs$($5JrC!ZIQz>DqNku2qvzqv|q zfl$6{mI7Lk2RJ^ZyT)UE9(u`jFL!e{{rR5&^2k42UfcmcNzPXud)YM&`96ciM5^V^ zqzhbmPymHn^NQtG#ud@K1!?w_TAv@1zpDWnx1np?N!Hr!CWd(wcjR{ruHCFOy(Zm_ zN=>eLT&dlnU>u*omSNGWC#7N@b$e=fG=GnC2NL&foXKoYqmxfbqI)?eYIM4C2#AmE z&v6~}BePZQO#+khaVL|Vavue) zMQ7kGu@7PxC-ERXZD#O4X23I)x(e5M0uMWIUOrY@I3hrdHdBv_S+zU_C*TD)4J1A_W{3<=BmYNOOpc;e4U^dhcQltg2D_G{W!a4H;ED_~B9DD|&(qhnlA>0$w^^r8*s#T9EL>=Eym4}9K*&Y7-@lESV}{6 zBy*^rAk22Vh9>`UtHmE z4v!{LIt@sGZAN5soE{M~Vw;iNMQBvN*fug2+YHF&#}20t2T5_{8H;NeGU~KHmZC*4 z+DNib7Xvl&T^kY<4wtnjLqVpNaU{-&f`V^`^MQj!XkmOtY~qW;K)4h3nzPFN72)Ds z8gpf^e(01G2pq<|C?>jg8@Bu~$^~MUchoq>@=$FY1sO@i`gFXC!9YRFhN%-PEH!$* zQ{tZy{jj5`e#qkOG%MWr{vgpr}EB$(mNg7 z1hR>(fe{l})E>49-vE%UjLBV+jH0fJD0ESGMedzuO07y(hIuu1MY0HpwKp8}C<&T@ zljcy@7DqJsiRbvJ~>0r~vuH9iX!c*EGwF{FgA)cv&sEy$%#4{F6bS4l>yi}}8Ftr*| z@iK@Oe|4ORV3mecQQSnISOd-s1j&G7ST`fW!=X2#>HvRgLW0ua#zW;K`dhGuN;i(pno6(m|sm(b~9 zMfj3ROd~u4=mxffiUY3v{pZ)RhjZhK)lv6k4x4kD#Ugme+nY5VPo3fzyeo+kUU{v7 z5o6nG5ZYTC|7F!hmGxB$RGL88g?5O$Hvu`9snS=RnjcC;?BFmbgw*ZD5Y|E^^HsHI zN<<<-+9xVxB+wQz_bj?Y4xpDhL*f{)@Q5d0evudG%gKZ7GlJIn@g6Zf9>CTAmop{C~6Xq~3vP26(EV9Uqsa%a4Ws8z9 z;q*HTaceFzfsy$Wv>#m#3B!m^%&`1B`LEn6ju+4jugDrHoT1@tVU@^N6buu*Eac8vN&(oMDaV$eDrF00Di^>0~7fzU$CCRQ5QXF_Fcwd;Ak?#{TPgBBD ztW`7ljVooGrm)>K6^B8r{d%T*<$xz9!Z-}p&e;Akv8!Y|YGQU4M_3XN$K=B7z}FIv zo6*-&%u<5#6Q_YC!1796;1K!yBZdwLDEcJP(RZ!G8(`KxMJY^iR4L3+JU6RAn1Fp0 zxad0(#wS@+Vn9nFm!0NhMY3x&1%R z7XAt8&~TWJao|C%6XN+q$Q==2Rj!^SD{>;Hopq4P(H!Y)v7W~8EQL&*(@~L8zR4d>a#wJB6 z&;#{_d4n)DN9@oz@^Gay6H-sI@{Xn0NrXdk1{6V61^Y>IogCylNh?mH+lH;>LE5?3 zJQU{1N;Y8(z&N0tZ}k;wr-au7R4xtIJuNO77^3F-qbt0GRU5%q(72%N`&& z1PA8Z>)$M?`FB*iWVDq=qnW}ixjcd0BBI_W6KM6p4_gFu+lZ#_i*z0{)klD1YZ8!= zC`%I7+Gr@C@cc!?8Mab{9e)TzkC-Z>l>Srr^UA~iwu=lkb4&?}^9*{BI z)M7qkV>rCW(2pX5codqQ62;f;K)$#MYz$B8`$JvP2!qoJH55x)f(x)QK#PV* zf?|2(`JO2q2N>H66CjDcGLu;?BknVOIrN8C5epw?0 ze^JtC7=wc?Y~M|WRz_k41{%X9^+7e}tEvnWt1!h#LInqC3^!0sm3T+6taM;57p$x? z0^SjckT$|CMV_P)F)ZrDvPXRs;>7V7q6G(EKa4TPg%`ya6FBB{<5q+giHMj4IW;R- zBqPgU0m>-IhYTh(QhoB0Vy(eXS`D`W7l_iZDPr@JimuiQ4jnv@$Rp64Ys7L4N5OL>MGA&FUx|<^qYiUl>>Pd|^7J zFJZl+cw|D^Q-|sGFUqSlze*LSqA5YND1UJRjv7Ix2+$Bv7rWM#8q0q@}_P#6ww8&CzOFWM~}Qi1#b#=&5EsGXr%(SDs3&2IgZ6 z+dh+IPi&xOU!It=gr#^z{{IGQMg`U1TA9>94Gnf62I{aELF6~uNsTKc+Cc56Z4aih z7vU78k{PIt^5z#ZNKZ&an-VN4ETv*Sgva0z*N8~0EmSv!CgQ7MQ)pOGWRj74K z9DQygNb2ZsZb}o)jXpJ1xV<4JswdG{$4df&E*WCp68rojjlz4`;;HXo{ zZ&*O3ky(mj2|Ai5bhjqsJVh!(7)B}4G|`UHgIu9NC7+cJ2UrcevTA}j5#NuSTB@sX zp-P~|TN!M|t%?CJ;=R1(0eGyCt);aMzruTmB3ufG^}}yfgG0;NrzL)%%GUT)S+Ewz z?E7k(E1Dy=28d&|Rglyw7BJ_+n!*}w1&|z81|ud89%QW$jQ07`{t{*JF`!&Lth(siszl>JRNi>SF`XQbr`uO!)um+1E!tzMpq0S z>!%gej8?F8d3r%?u^BA+XmRD(Fo(bKNRNpC{V@b{J{~8YG!XndZ4m zo{y1-?EyOW@VGc)_6UOf8`_WL29ac{7qj;e0rwnF24kF90F2U1$5mKWkx`9k#tU8qVhsW(Pyogi#x$WY z`=U%K%t;L^10dm=D$KTu(8A5BZXX&MQ%vgvU=de2sdZyt0dKTwVF37v0n(@>)`hOF z18l}6&Bu*T;SqL3ZL>3GV$#ag)(3VN&MtC;TbBoMh#{DDrLU5s@i98QbTz=(Dx#wh ztiGW**E?>5(N_e3Qb!YmhxihK$uQQUOEI;aK_%ELP=jN+Zn)l@cOjNz88|fV`BJSm zIsl2R(y`!)nKZ(uF8GKohc%kqk}#&KlqX5wP?k(pIOLYggw(-=lO<*}!-L5*OlO7( z9L2G~3WYBgSS=_*ECpka%P`AWd-1gZAu%J|btVCr5{xDt4`vN*!~mWGCP%XBqHE)Y z7a71w5o4GtONq;y4WuX`qUeJe(#x|*%CZ>l;1Xymbh2nP4G%0#4E+x*+k{lYmUJfB zP9btl#gG_B);O^2Ku=l=&O*LDZ&*Oz7aX1@Hh)LixRXy)!f}lXJ=e^99I5A=6vk4) z9+LtUO&B#XQ*R;W}7$5r(|W5JCNcSI0}h_QZP9noChQI&|nz**nA2YVoB=BpIW z%f|5j*Vy{P{_ug+rXsCDX^vX<$p_TI>nDEM3xy})Ao>|$PHWk#3pN@Pw0)N<7c(N~ zI3srUkDP-F@`v%Nhk4=5Kw>=BXozvu$B@m)_$4N}ab(jQp_&er358d;>VZ6?$wuQU z!P9mXCaB~TWHT}gU}Qcry=)u4AbDl6bHO`glMy?WXku|qh&pOQpdieZ`t0;mK6(f- zWVp-=XPAPww&HR`=Ho1fMRwIC=y@!#YFi2CO9#h@dU^$g`s-L|qp$@hi1+0TpQGnY zToJ!T`~Bf?4A#NpmsdPcGAKqY%wAOP0&k7ByM>wwNAIhC`M zLrt(^CI%A5F4EZ_4$590C2H#<5aaAjVC&N7Aa2m&w09B2dCIs&ti7Z$i^68&uW9yG zI})OsDw6e!5)*lK8{=d9*^D0*A1mm7RMl8*iI$=f-NHtGE2Z}iay2Iy;ezB@x*)AS$E}rM1mM}IwATWp6PI`MgqHznll`#!I&Hu zvN&Mxy1wyC_(}Ahg~8BAFc#tTj>h|G2kKqtXzT=M<7k@+HMutmCla89pV5ssIKnWf zLmnHKp>57rnL^SqSytMkF)l0tCP6tI9KJoMQF+H1#Vptdv zo%@tHW;D=+DY{-|)HzG>w!;`4GoqNIB4pUM$%BvEuWN8)WcUZ@8N>z)Hn_KzGw*MP zLoCeuBf<*D(m)&mPzDohtW`BqK;$2tuz)ay>NZ;_Jvq+7#K$*GiYXme7-M6!+$5QI zTx`{wlevVIe1I&Z7xamD?3{)T7R((c6vo|Tt14V~*yzfXH8I%`K%mdU=sEzK7}`el zu(`51CC2EArR4KR?3*m}L>XS$fQG{qjMgOvO2xBybDhEP%F`Rk9B10&%2m5-E9_J( zqKQof{gxPz-IX~I^Db^6VtCc*mp;;%KSn}>p*4xAPgn?rk(Gt$UNir+6qo!Rt1`9 zR#kP8tAtRqsz!mCz^p39+7rbTxK!Av8n;b8nNd}rajZsIY{GI=&Ek0j@ZE;{0A|L#AVmloD z^c=Z36y6kvnk#t&Ty1mETUCo2xT~@C4JqvT^H!Fl6ug2}MsHsbY021)D!mFcisd%E zBA^B^{htC1BKnN_nU~Z2<AFa03`+mUJ@sQM@isVb37ctK2b=c zf3b8sLbfAR+o$fi1r_G=S9GKsNJ;K`gbL$yh>qI)5)ri@MWzx^;CPZ@l*f{Z`cc!C zP^^nk1Tifxj%$vFsVq(v0Lb zsd&$$r89*D^1Xp3tK01o*BlC*`U4d+@BPP~Kye6R8Q%)%rW5mLAmg)TIm|s9;BdTw-9CrFb zGRIdNL-%Rr8?x@x1u2m^W;Wr9-* z$zc`RC?zMS$;nA_oJI}I>wjZNa+1vAiGiT>1JEf1WaH=<3>7PQRFjC6x>RW($;^=0 z5ZIqQ9VZ~eP!XY$Op{axWq2*L*lEhhyij;62gv_vZUu>=(3)3Ls2WkiT=cMp#r&|098G3v?uUcY!xm+hea^&u z!T2gRSTaM?jl~6gF8(B*f4q&|`k|tdIQh3N&iY`$e%-7bB_>CAKs$O{$CA@EVK9N# zw0Bi`t_Q{%iEmO92BWhc3KK)2Y1A64Yo_Xv)Q`7>FOYxuXiHy$vn_qb!!1QVzn6}l zBTJNZJaaHTGA$R^zQ3JbN?VV+C(+L1?n3gu>wcEMX42hnxrft{ciq|a#yjqXwBwk& zlka?+OrIZfPcm0bq-}4z$I^;--D$MyxZ7{!{eiavYGT}HLX-4p52WA4F}cV$jKT^>qz;L7H$@4Cm(-`;Xh zLOr?1Q71qtqi-K~pTpIT2?cN+b;;zj>d%=yf3l-~J}$og3NFHS6s+&S{mbWc2f9`* z@97S-TpH}2G`piUxG~szcIWEWV0S_B>{(@=0{z|kjy1R+yk}ORCswzjvmiRuWO0WCIoM z|7o?5Xw@fcL5D1H1E4?QxzKa+R3ut(XVyZJ1~g0$LDP$%`l(t-i1@RyLb}%kdO%<6 zR|nSwJHiqns_SX*5xOV$vZWJiqNB$kq=g{7;nlkJswk;`ThqQKxM))si|}{}`pa3m zRtMTU7J&lKRI6dJ56LTo5CH>N!`k=O;JTLXcIdaA9ePUpx)zjOqe_Y>(i-dvcC-p1 zvpp1?w7R|HQuPaixJGn9t$RbTo5fMB9u52`YK6MA#AtyRsqL-kCrhC^RIA%tf*tF$ zP6@bMRs~ol4F$Uw278Px=+qAtb$535XoK3&rsZ0{F0e8zh}%uUi$C zh1=I@gH+$q(!HrGB6qj1I}0^x_JDP(J12!!ceaSBL*5HEh4)03leU@wIi^)5@SX^PkGCp-IeJcODS4uZuhvxqvsY))u#h|v zUeB23iSPk@Mf-_Y(*Fn(Lg3;CcK<)dgir(({+E~#rmIoHVV#h~{#V!lI&cVBsfw;Jrsh2rmX!h5F(^& zhNmevd|3z)(zIv|2@=Z0Rg(1BU8rlZr9CQlzDbou|w)++{z5t{t-rRx;~#fVVE za&+c;MIxhmGK^5|i(Njp0DIaySpNx|&^;pHWkX=~`k-cwDnnSx@*da;%RC}ke2x!W zQR|AXDqOz0%u}t2G*R(3TtN7vLYgrephOk-RCv1l0zxBK{z3p6tI_}r0lcITCXmI2 zufD5%`MTiRf-`|8fS?Bseb5HEIW$28<&=5&g*MXKTk%aBy6}8O4N`#+7ZIS8c&H#t zHhaEuZvBFK)GeCxOcXH}B@WFAQm|opTd2Ts_JW3a9z6=FpeqGGqJNbI6ACn#L{psI z!?;l3;YP!GB8Z`9+YreHYp2H$%1|L{)pNFu6P0Q1BB;Vq;a5IG&J6{QUHisKp#)J| z#$pA)aP(bwK^b5A&|Em;sx%%i69N)lV+c`V#wz_^w92>|%loO3^~L3JgQ`Oq_o9NU z(&kDXbA8?%Dr&HAHt_^ajh^G#vkDw_^JjU&!EDAc(5DJz5{{r>ol3Qe{&IN6CaIxZ zt05$y=;^NJa6KasS{9E1aDki}>`5EeUqr|9p_-X>3+p{TUPJRe*!<*Kgbx>aYAU^I zQn{}}BzbClp8C1ST3cOPUR~qExB0XB=_OW6Oy-0SZTl;cS zQ+_fzoF`XV9h9#_ZFBBlCI8-J?#or)Y?ntPp@ByE+|{&VsqCOFOXaLoUt3!nt{jp* zCLbMGDxXKkd~UDV-$wfsxyIB?f!E|^=3otNdrBTmzx2B=rhjaaN7L|5+0ETpO-E12 zl{DxXJmx+l&p<8Td`4bJE0)U{)bP5Ti-f@I@(>=3MjBb^UTVQE18N%W9!c{u-DAxa z6}0VUd6XG<&mVnA9#3zsl^alI^>y;@^k%>&eyF1d&HeIU^6*qY63DbiE~3tA_Z*si zu6w8xA+f#+f7>!_y=txTdHuc$90_|6B~I*-hmmird!D(rg>sL{Gihs%djuW0N-n1n z=iA2ts+1IJyg@E7`-8M%uY9|$l^b+;=}05`yYHwxfqHhMm!`w=h%^y^OV4kRt8yJn z{pI4i4V<^<^#!r@ATZfol*&2iK#9A9UOPw5D6Yk+aN~OXo|z4E=hiK%_jJISv@TfO zT!N5R$AbFV^JmpB@pJ?)qvJPV&fObKnIlD{t7mcJEY9EKLnP-SPs`XI--MRQ9Q{gn zznwnJ+%TUGbee_^7m>EXK+CGhj+qPUxx}Knv*y-&Rw9(lPjv^ghFVp2K?m-27dm~O z$&R`8$R)T&k8iT0gr>ZkH-wJmq>dQVGTG6%pzfS`b)FW~7+k&%!CR9Z#giSA9hn#= zQ_3GxQvSFrci8W2du)AqsVNUr*L~LMjFOYAUUixRm&iBm{)YT9?XPp^nf$ckukxSm znECnmT}X9*mLH-cch#9oXYV{@^G`&z5qqO zLb*@Mqv+6+@&us7@hjzV?j{BhMIj zhq)O!|AWf*k8$UtJ3Idk1jQ;I#E7+oIU#)NBJ-!pr@ zr+9pAxerHJ`|0E#Ww(9yLeKogbLW=O@juFA%_Y-l+sX`Y=44$WID=Y-XZXAxy;`+C z`Ybzrj{TOxJ}EWj;Jh$JsiSJyo=cBzm+O>AZ_l4$q9f;-?6mx^@=azOWE!|%{)&zS zJMADaQ=gQZdFIc@?^4?Kg#41Jn)b~w71Gvw)=|TfU53&&!hB%u(|Ck7Fs*7aXBQd*@*~()IwohGe^2&3@q8zvZEH_c{d6 z-<^$^cUzG=Zx~Y0c=t$jiMHdcRY3ysP_2VS^dz9_XCx#?sN zsLr*I%2Q2MwCxkjzK@PQDtnmTO+PAMgRga;f=F%MC;KJb4Ok7rf@4_9mm$a>CpffJ zDM$9nV@wvu`0*5&nLjkIWjz|dxVszL-trze?Mpm#;BmV%7fXBAf`&$*4bv2_XEHt2 z=NMX2KB30zuPQIE4301NdrQiSL9cK`nIH6L7DCr`TF_EX+ZI_}`I=O<$gfEl#euSp zk`mAP^y7QdOK8Q+{9&0b>!TW{H5a(^CEPsZHP+YT6PdFNnN{L7OpE2$XiXQK+Je+f z1>vp`r81&RQQ=4F6X@+v(>yLuv9AQ(oIQVG{Q`vC&tKHwx!gzl{)nl%+bn-1@T@dFC`={q&T35D(u1_T1{xdogMhB!fl8!kdGT+tnqW4iAeZ&0@j%yk$Nx zqD^@c73@!U(#(T~^-4;3*?hUh0^_`3;La6ETXOMSf4&Mo+^6Pj{fBwR27L>dt0J z#DZLtZ=Q2_X(Q-jN{XP;?TB3v;Ut08%QpmC)`N%`2JS$1hP?Y=Rvt~cCoh9`9!zu5 zTO;#bC9Pl%+TeF~u9@n`)M;M>9f8%G*0rx&zPf!)d(UM0%Lw}>xybLCb>(^AE9`7+ zEN_}zSX4A|)`ZnXH22S$<7nqddzrMcsI8)nRGB7SY-#?Bh&gfegW)wZ}7kBMp4Ar?PkxrZEbm z=#g(&M`RZTT2k9L1dFgaw6XdH#>ouQY}%4y z`m1Tny}6CuCi{;j_Q|O!M_R%ZbW8>o?uwsW*5^u1d1YNVc_wvzUCyC{+vT$eg!Nf7 zF60|^k37*-K~49_emZoXDU}H+U$7FOyWf*XaDjKgL~?&7@1wt5A?GL!7s}}-b9FTx zzZV>hnJa<0PNL_Yl>3;1HdEtM^4ZGGTjW*~d7qI}nV5Demu;2bH1Ycus{5Bb#U8*N zWccw>?(^~_CQxtn%Ja$J*_A zrY*Gh`{|RlICjVC&XxY+=CUGS(Al%+E~;PPDOMS7abS$UWa?C3Ntt71cjx-9gGL_0!(`69s3aa?n3)mn!Yks-qqzvx2hy(S6$(V zR`p|fMdg^vGLOH!0)Tj-2?M2DJm<`9ILlKMXl^Opxao4BqzBpbcW2cv`bs@CoTAm8 zmjxFWi4sNYyRcP2Jc=U4#zDN_Z!M$;_gjaA;=n!4;b0mRJA9`xBsmdMxw`$*;Ob4Z z?YVTB-oDk5OJ%>dxTy42#|U#NQ2hNtZFJYI4w-IxBRzvg-kUyX_w@8ZWVy$Bu1X$$ zygN60SI%I2<@ye_s^Y*@zh505Eund;zeH$s;(Pn5&d%22mZ|%EwhSMXjX4uBI za5&p?#Kd*146Xy|iQ%?>l?YPql{tfD*iSZSL~+A}jZ=N&LBckc(2ft>#b$e(()3qI zqe{+|^4}nQucSA>E3acp@FSbKFJBq8U4G2uu(#iw*5^)5c_2GNrW8<}Z<(z2AQtQ; zRQEI4NnLgB8DRo6#-xDoY(Slt(&0`Bbdc)1=m!<xzK_#-&-Gk`c zPs;n0U)?BAwwUc5)L7{*Q@l6JFXCG#d2Wyw2sxUS;7e)$C$f)ncFS2x#y85+`Q`^yZ7GQVOge7`k21+0NjYabMYL~2V+}$M&B$v)BEc1wD!%wGo^1j zI`diIR6IZFn~djw`X=G|ufAG5|JhfA=U@BysIx!y@kzRG^zl6@zwPtl`C{LAJRj>T z!}CC2DV{&=E5`HAz9KwBKj|BbKR@UjgXhhCqw%D^QFvb0=fU$EeIxO_x^Fn1+xmv# zd0F33JiGe_qbK=?5$E0LlUoM_=`*QG{-Itw`V!Nj=3(vdz z2I0A^58AEu!BAg1{_O9w;rWw3%$D_*J`0|^`%HLV+lxuGZtuMk&#QVj<9S8z6?k6O zy9v*=y&Lgd(|Z}7D|A+1or34Py*&4qvv?j zFY{KgwZvaty-c;6;P{ATelPQ1PG@@%TU6-KKG+NcCGII0SeSdg*T_@p(eIDhe4L#MTQN6^CCtQ;rINzSnRShvkWMF~} zd^22AcX6+l9nsRGqSXTc!TNMivv>V2nI8#Qh|F>k0YuFy&cpZiZRnM!7(NF+M}c zzB}m97P*QVX2Yf#pq&l!MCFa|$XiX}F`K88kWTXcUVhZv7N9y<+?l%VWcN%F9eP++ zl#9l=+hLW+ftgjerO%)f^Pwp`dqlp(*!89A3O;gFzD^|tJaKR^y$EYOEa~@=_j~yx zOrG7oJQt2Asrx}XzOa?NAQd{ENEn>G1!87NS?z?{s#Q=@NClEN@iN%#xX6$y9Qu44Tb|^`L#<(mqdW$}c0{ zw>!X1?cc)h^$cDmsHWXLnsUA+Ur0M{ga!g1MxLUiyHnBar{yxrALZUaxBOi8DhEfo zuM&fM1C6>_ep~^5%|WI;@*>*$A30ZnAHo$Gs~UPJ$6dwaxm!7zgXyEKliXL*`>?Gj znV-q$TCIJfQd6Fv7)FdabYx$CHf0|`hUr7xj{vd$EYD?3e4J5Z7kP z9__eNPE&@YyWazLe^>DpxIJk5W_gLSIn#YL(rzNpE%HUmiX1oDt$m~6rCb?qN=Sd- z`$1}70i?eOYQ{kDD73E|(dr)Mg~9Ie<}f&=$`41lm%=%E3o}WNQs{BNfrYV`cK#m9 zU00#|I(*xQLu_D|dOp{^R{1(sgw1Tfm3G!)!Rkh_F(x*KWh*Ov?wbJbZFDpwPh{Iy zw*tlV-*$7~7}_`0-EOtte$$G+LM3OK`+Y$4eL(a>C3S}TGmFFiBj&8Q7Q{z8Uz2Aj zb7#5lM?*iRra4v@d0VZUlsffVa{MKyZnrkFU)V zCumh6t=U__>D;Qgxr)zq#9MDgpvGH{H`VZt@bS(y;)-J&pM~XCRf|^v3?^=)LP5T^ zy1E*D;}?E!4SIqXXh|F_#zl}>{XzS!eAh~w1Ap3D0byG$Ag;z=pMyo2)=It#r5u2U z!322HjH4+81)woQf4WLws1PzSvl`Oa)662$fRtwq( zu&vFGrR8`oWBJQa47IBDR#xDgEv^=x7^J8m0-%t9h`TKkE2?ln7*1iX1mIEqZo$1T z=q9?&6BP8LNlaEti(_d8p3Bf99yFeOF_>t=&y&Nw)F(jeCU=zc)@lPhf!pD^^+6ug zCy}RBPd1EYC9Tc3%Cx_5khPTC10a<(@Ji|4M=kU{%)0BB%wD)?A?>>&&#C#6TBZg{ z=+1E&Lq};`9m{NgKgE2S4A4@xsbne~Q5%`t>jFVo2vajfwKiQ%L2E+h+BjACdp2=c z5sP&LtE5J2n_Hb)%n(GiT^11m&sos0xY2Xg1&IGRx993Xdm8PX^yQiH=P30iArD#99*0#9m(Z{V0cp!bl?Jhd>LkQJy=VsB+9f(Ld z^!K#sEHT`3)5yM()Rg;Yat&2xT(ozxWRQn8ueO&^&S9$+m=Xm30UK-DfQ3MQEXg)B zgjBc$g8pUx7T_*ZBiy*`^_92ouC)%O-Um|$(~3fCer|1f5XhyzXSz{Ib$^oQG~&{w zQ;|FeXjd()YUL4HhZDrZXBt;ml>4h$zrG8<_tNKoPkWkvy~mb2ETB4riUkU6ENZE3 zgNb~hNBI42Xv%TX!a3P4YWx(V@cU2F?5S-%>&lgXf0TB$Y1fB`7t;kFr@h1M^*&|# zztRqyY49~^x%9rnI*2@ppsn-8T+`M|SME4Gf{oHIK1j=L$h2w;>7KsN-sc^@?GIF3Wo^wZah z=v(i)8h?D4xkcr1@rn610G{s+kZdD>`80sPSuK2JMaZTV|@d#jYysaA^_ zMs~NQqy(DO=V@;GhSj>5i~gLBS*@!~x6y2yRY{+H&isZ2^(7@!X=}DsQbyUWkMe4; zKS;+M)*1BnJNDsbe+k`kWm+)}{WvX?9CJcCzY!>clkE!Wjdu}>6oVan`d(_qnP7&w1On)B)X2y| zK#npDT1pB9a3Y_AWBDF4Wwkc1>w!m0MYYK)!W3K%;7$y(=Fo|c)114eAIjc!@za^g zb$Qkffm{c7nNC6rS>b*M64#^3g?rq$nv`e1>aH-;?^e2t%=SaH)|(?y*XLO~=tNgm z25tRuRw`xP>i!#bc`SEOPz7GLh z|IAiX7j1F>7_!?Vn1UzBXEN6@(7!b)BYUw-%=TX?`-WJSn|3w+1Iv1tMdChGp{*yW z?rT}E#lTB|_e~0X!@bXJe~R`MSe*3!rS3dB^3$v-4ES0Lb&a)@GFCoK2Y#J3j#m5_ zir`f{+*{D4XB5-Fv(rtKb9UBtrE9%=tx2(x`;%0&{khxwvf!X-$XY^MK82fWMxT2J z?fn(T>nANJkHP&BKz*LpF39rGj$sx^{nun|-L?Op>9oH)`#QS#8u#~k*vHV3JF@O$ zp#N^RzhE5aTM?O~fq99T!6V9{^;x@2G_}*+!(cB~0POeC&Qaxsoo*k7`Jbj?O4D8L zO6UvY;V@B$nRdP6u2uehpF0&A>$B+TONuEir@=%sUd;56_pz++Dwz+uKSDjfp`$+= zR758_va=I)i_onx%Cbk?KfrLmtem{soozC9A_`M@lfAiY&`+TYYuf1!5ZlgNW8fi2d z$uG&W<#FPN>^PB<$c{!2TP9W<=SdzUj`M18jU!o7;v`Pu;1CB4j-$fz2$0~yzw82= z@Hnt+L(;Nk&h`KsHVbE$ZMNjJOMy~0fkN5RElJCE=_~AaN27dHx6WSJ?iE z)<3I<6t1DDS}_(#j0y}WM@ppSh63?Dob=PCM!)O}g+^z;I9c&Z((5L1x6ZIc|2DT9 z;A1l<-nTN@#Ot1z&T52@!v9AzUKX4-88D#-dmjQUSUU02MJJvq2^xa|p!m@lOC2<% z7%YYA9*pVfJcOIywb*&)pG+-Y+Wt+kfY$$;XkOvkwKyERdGpY^Il=lhbyoV*pFEq}+;ds|7vT z7iqG!rgqV6PRK3hlYE^;Ewq4y@clJf& zJ3N-rkrU7>IfM#*+e*65Av!VEp|x4faK*&K;YbE%7|($!t| z3VQ7q=E^qkT^_ekJH6!kB&Hms|q8GjAbQv%f^`0T4WC9l$dXXy~D{Pnjmty8nhe>$T1K zydx9HvAkm*>J-xoOjl6g+vbV%@#g&e!br|_z}`m!7JDdjZL$aA6}Qjh!>ixphfrio zWCMb6;Q7+#BRSc60(A1POcR7bR>5v2ei+V(A5;ZOT4BP7%Fn4lsk)E?Wtv}QO)s8B z%$X9g1V4w0WhS^O@t4}5xy*PeH$^>6WY$A3Rbsi}J+lR~;ab;Bs(ITSru_x^g_Pe= z?4(z20>#n3$s!iV;#;s9S$!K;57ypL_4mXo%yqA9WT?_?b4Hp?uNH8XG^2>Kx;y<{ z4eID})iv}s+}IiD>hyK7(dMd4y|Lh3!)6zK*}-M^V}MQItE5O!&aY5FOpd zmH$CUjSYPn>o|ea)9K>f5G-z{$WlZ3=>8Kt-R2M+Liduf&!3{P11bBgt0g(gmt{vw z(16iF2kMLMS3D&CZi=No4;}{5<<4I5>Xy|4m%ExZvS8wt#_H%Qp2L6dVgv?Y&Vcxw(o)PA&ga-GCj{8Kh4O%|pgrQ`J*F8zLbym47%6vlEAErvikA==g zg~~!q%r9ecOS2Z&pJD_rU~c6rHn;47Vt-&S2G;sV@~w^w+g|mNhh@J{e`2zavywIK zX<1CGp~DySWkjSrnFjgK1ir*WJDr2c{_7=SiGiNP^mapa=)1DhC%x z*CFZep1WG&c~P(5uhV~~-=CqYW{UR?CR;I6*@?63==o(*o5jrCu`@XfQ=)C@V->V; zxugiEIjOM`Yb5igf1NLf(`56_RP#%!9Ik5EV2=|N z#>UtfFQA_lhCS|^( zoqesK+RSppM5r-JBR{v+yT<0`W3KZI7vqQ1ACVT=2H8U2?h`%!#;C8SsS%V7Xae;# z)k&JZ0k+u?QmEC1y%tk{$+p&BUBPP5k3vBOq4MJF^4b1ImY}aWL8m2dV#WBILNlbD zu#HqR$!Zr_rLcVHI9X0LhGw1$fzU69i_<`M#9A7jD!Jqg9QF&Mn{Dh1B74nVk5{9G{`Xc zD3uQB(ISCB>cml80IwQ_<7HGDQiFa{DI9!89vXku2$g3d0bUHOm|76F;05ayL?>p& zvJH9FJ5-G`C=%>W0%M#ASvnilWR0xH;Y@D=so16Z65bpVWl9PFMBo5q6M)TWMTx49 zu4-;*!elyq45qOqEc;WDCeCn#&5`QykjXq>U_DtJpq!pFU`F6Zd@BIGDqK0%hv{0x zN$>XwlNmY|^D^8`WETDFqk_YLX&<#cDon4Ac#yHG4PCQn{#s`w=Nee~EQnjw~yf>j7G)lQKdgDp*%Os3w0&xPTi9pgotD#={dhAC#-QZ-(O4y@+v^mK$T z<7df3&1Boa*9hvoDMAO=a1JZtd2-KpvY5Ftdi_zMS%@&fCg`Q(LOE4z6-0IeZ6&Fr zz*QRC5|+cMY-6cx*>r3U?&`2l0FFN2Q=wWVxRFk^L z6Nst4+lI4P3$ljwq^qha2$~2ab8|or`RO-(LRG$NU`O29;oP)sD`tlMkC@7fP@URr z*@Kyri&~=vrS!u`ipuq#D1H4+o4qQWQHW`HU5o$WbPzo50vPHOAK96l&FfxJ?WsQI zr4=#U#=V@W5YN9J<`MHIqF{5I@bvM4%*JCX4%-=T>hZ=BA#V&D$l)wYCtkWjav%oE zwn>i42|SoWAs?vh0At%5oA3)-8_!*4m$01>+X{+(W=CL@eJ~WNhCr1U;A3-On0*-x z!XTfYN;5Ao<}6)+8S+GcSz%^$VCrEXaLkf2-z+)4VnsBu6HIIxk;lc5*&lf@VRoQ2D*a8yAYRmNJ8F`vwQghH|}2CW3!;IRpI$InbUYZ7`p zVk$xamteND>XUIF-fY#;UkfH@OA2J<96ld5QJGnGLzsulVJTo$8WbFL#dJBB0FCP9BW57Z%@TJ7gw9lIfzTm$}#xmmLW$ez9? zm@5Bo+~s+_DT{%Je@_ssN_`sH@Ee~|1_WOc0~`Mz1YZdV?#O~*$g3Cx_+o)DdHRHc z;W$tzV?N-2xeOwf)m`l%SHe_T{QLWQGEa35gPT$~USW0EYYjSLmAI>@>n253tqQKnlH| z2egZUaxR3lr7XgWz|G$hcDf)LnHqCt(5qu~4%DH5ss(i4YOXj1TEl73I{hN>Z5(`z zWD+d6M1rYP$6&p*uQwZFE;A3m?D2X0%mY_)dC|+UJI%(7n*0V|2FxkvzHiwGcS9Sm zd%BXj0vL4ZpC|d~Yu_stY4ETqpN{;q#YFoL=JUf~tNxT&X1E!^<4=fXhBY&twwHfw zEHli!D(S*6j-$RXEJJTaOkkdYWb%MvVCoSmgvi_%rgOE*lrKqdnF^VM(7=Us`dhfu zTM)9kca8b8;@P z`$iegb+W}*ZUQIbO;G=c=s?n!)>$2@eyv8Ec4(-52M~R{XCpEwxNk>t4p`}4)|Hj3 zu8~%8g=P>)=E8W-X18lVc9HGPd^`R6DO0iX?mi>*#sfiu-l&vf_Za`NQVXOwFYdgV zY3u0k-*-4h&%KmC`p)rEf_lnEkKJKhK@Wb#xG+z<6m>zCd)q8Nn$Tg+H^BQxhwd=8 zQQxgb#^;-hXy{hsMk;=>u-c;fBUMIAdG}W16CAzwidaHDuZvZ5(;Q0$w}xh)g*K`0 z1BZ#OU0hViid;fN_Zodvf4lKjWCyL!*Kap|Rk`a9W4oUG8;WK@%Qf2q$Nw#iVX`VWvVho$vMs0hntyDi!d<-?+He#KKKV2i%Ry|he zIC~fhhwqG7zpRIV@AN)#61DHOzKUgBPTnm#X!s#ey9XbXtRfTsGA{>lH(6KU>t~YI z7JV>AhX#rlS1>g%3>V5O?&%43!4(A;3G<6!J(c?-d3M_Q73&l{(_s!G2=jrNKn1Bk z2c?-i( za`c36&@OgNaCDGyobJK!&a}^LFbr=W-F(JeLqliGh3uu4y;N7Gx;*aL9UK}ORGWMd z%d$*!GEIScN}+ZN*0XWDZgkNLXUt6gIfV|LF_+I(-D`uIAEzFP)FH(38)W%qg$FK8 zA+XGeJ!jFe#+6c#TS}^s*(hZ2!l{_ItbJ+Q-1)djcu2LhTC@4_Z4df;sdDIMsasD& zJ0w3HxrfhJ=I#Nl!XDZV<~|GSXI7$I&g%I!wxp`=#&Vv`KWf1RAGGhiC#_Zb}d`8B~M^rJ_6`xrEp4 yT}a0sm+q$5=1KWtlpo>t79E)YN8RMMvW$-F;P<#6Jwgu|DJ0;^CjQ+`>*xZdh4zA)_SgWH_pr{yPb3P*?XUp zQSdU$xHuxKiqj$phPuLE{|08)Yb0BQ23@lXN97rGMm7jW2tN)>%)4m&a5qK$r`g*R)eOCSErnohEtz11){UTB>q=18x(EXQL+eEFzSfc8_gV*n6yPae^l_M(}-&@En2lqX?`UP9P_h!0Mp{mJA^arTvyf*9xH=QClBk)1 zBrQB9K?@H^&cYoMv+!LKflm_&oNh(neUhs1UULG)B-sVRA(E`Hj|3}hC%FpiNua{Z zBts#CL@3OtMdDq8o6 z@k~-Uix>DI7QfG#7|q0mobQ|8Fq4Uy?b*I6_1m+ToBCx@5;ny_d2OZEzV1!;ip;pT zr7W3*rh+(%cNUuFI1}rbSI7oQSm!X;ub(sLw=w%hJle<x;#^kCBner)bC@w42HRK=lsQ<8tn3XG9hbG0Ryr*2`iSMCM0njb0)G3SVblNX}(uj9kYmL>7tC2KHDn*kwUm z-soR=ILMNEcG zJ|^Rooh(aa(NMmRMPYOSyDYNWP+rJ_ArDz9sKqP=Pw!`qMOFvOkFXf1+|7(|{0N~t z;vmZxSzTCG%w|Hy9##wLmaw@HyNB7a{xSB3B0D7Emn#3lQ$^GJ>;Ypna!;^&l2Z}@ zhCgsbWB(KEEv7VBI9rv3rL(AX4UGAaJ#Ud+lCZuL#GPOR@u&05%w>JVG(_p5&Yeb( z{i`t;t6pJU71<*Ri@U=$s}zSDZ?HJV+<4|I7Ry;&?kzT2_DaI4W`WOe_W>dI<}Ef* z#Qe6lK@3WUT907J9rlMT2TQ^$2~UgFfr3A*3RI@b7D(1?_1`GE&$!ZZecd2Q*gNQH zvBkz_S#y1okPpt=zBst^fEe<cNAB0Vl98j{|+ZT^pc?XdtLegMQKOCs?H$@U*QmW?^mU(z1 zk+sFj(Y`DqVS}e0mV|JRNe-pqH1-K@=19KC*uJ1wh$LJC|BIdgZYDSr$zxfoq9{I5 zVO3#LJw8wBv`VRhmG!tol=Z2tGh|I5T_`&))nhqlR0Rje@iz?Y8OWB!1gzGL+eFxN zMXCltE%P35Y{x)+V&^<@^04aV|vF83rHdO_hOcQmNi{p<0)3B1rKMd~c}#}kXw zB+&$weYp&Wrt)Z|?@OT(lCUNq-#}LexK-5{3~i?I50pMR{Gz^ zh-?&G+hGlZ7I(}(Y_^|&DDu%wala%&TEk z31^C2TM~A4_%|knE|kM@*$HkEO=C;MXqs;_Oiw;9SjA0n`6C`BrxJ4GAbYU75gNxB zBOrN*>cCG<@i-%!0Ob$VOi<6;$6@6;?iSe#a5vt$5SL!$b44~0?lyJK#JN}aQjtx9 z@_x<`D6X(~f}X9M^)b7Gml&lwlJLd9^G}y|^?UX#jJnM)bD3-bVKPeJ5i{5nxc95N z8MjsPAtIZK_x@0w481?`JDg3!d(4@@u-UKtL(ZndJ=y8Q(1+Zw$n_*)Tkj`=n<a$QqE?SbPCe$4fGdKjU%WrC_m+Xqew{bfegK&?0?} zuw_;81CcGrDZLkp)x*UxiX0;eS7V;Ad{b?)uD*INEojm}5-O(r{jGL@)t7{Cj890R zvZ-7q+e*lUlr7#%_<23CrpOjT%69KS92zTb71<(4pFuN5Y`l0@WQ!r&Aca8MKGBF# zO~tDsTLP@6k3`i{oNHvu^j2=+ckRU>k-ez5(jNzR7T*xXmlB#n=?sq_)DH|Y*6Su- z5G~6a#>U6TH%w@hkO27;y|pkQS^QX(Ry1lF2PqkzcKA#m@sN?Ng8XUXj}SghvO`Hr z-*`+LBIa;$b^K^Z+u_NA0te|>0rxp@cdFP|6thMqfWL|8!O&6SZINYT{$wv>D32BE z8|7G1lu1vBpOhwMiSkM!ehoU`_ugfYmF_S>2KNHyOc&D_TMNU-c!~6xI_bGEe1bQO z7HN^zR)F$PJYBJ8hPYp3>!5tB*c@9g5~CD3UJ_Q%(K~1cc`L;F(zb%sMo_*&oFnBG z3~va@E5*6eEBifhShiA(;c^aj|0>>FKb8p9go7#7;A-dQ`NEz(u zh|jDO4~gtGC@k=Pf}d^>>x*nt(R03xKWFxWe6hEIjilJz(;g4dqR}2#6o@lKwguwm(3`Z0q@dmQizu?K5a;l&#+VFG z6^6SHi6wT{7$=<&BShuRW$pwrqv}4TT84T1O-A_QBhfFvK?J=8*?rwH7^6z3$siPj z9XLEUD2!p4SNfdmFLpxyCf_iqh?QEynRg9UAT7^V6)&C>>xyg_l$ZFn;x0+*Ekf!6 zQ7XD3(lopW*j-;5PB6xXtNOfO+&UmZ7%%46{%{VD3HRI(Q;58?qE+X|F&K_P>;Hy(ln5m@P6n_b z9RClFgi9ACsTolL>2G@jYls4qvLpkB*@(lCJlG)VgdL_|DLSR#pNUV?BssvRx1q4u zkbuqVNhb-NckuCODU?CtA7%+7;-m`Bg?FKRbkIiJG)@}MAV!mZfvn>}RlxDH{0%;C zA~|H%938EsyHvEKXpJN@xrHPwpA}FCY`0b_rVl5GCEga0+9@&!`>m69QdcJ-rJ>jt z2W*huq^A!drLpM6kUZ%&J$(eN|A>ghT3e*c^mGc?WqBH0^+zhGZI>1r6)ubv*jQmC zZ6KaCX3uCP39nBfTkw(c@&1`DCE@lodT4~N7fOS;LHK~kJ_B*Z(ntHwsPRTt4o)tA zyS6gQ1$jxl5A>{Nvg620(k3nnpEQEx<~$Bc>dJ{wU5c{fqASvT(I}i99y^@o-@;Zj zJ!HkmGBisy72>%HsR1E)4$5yyMR@pz)R~^nL+N9w4dk_zomg2RP3F+^Tk9IweN*a{ z+e~f+ueFukKp&)S-$_z#ACqk2t!RHDM)6kq-Xex8m*Nv?5#2i;3YmP_Na$CbsMiMC zFnO#=xUgqjYntr`5QXia{<9_iA z1E~LllnqC(NGc?LL95HKdPHLbzbPHn`tpoG!O!5*Pf}e-wTm_=j**jKw_S|HJgYo{ z=(-Hqze_E!prxEl%Ggv+*Ol=K`g_S+7>2Z!Z_*T0g_^8T z7a^z84Ew#*4jOcmbM)_DKY&Bdjp&u zPlh1U}0rVI+cmAXNqPo9jwHIo(6->)IRyG*;XmNHpK!cEW~OYalNig)0+5&g4llzEf;ZbZt zqxi-NaZRA$sy!KoUgeqi#Rj>zB6pC4&(Z@(kbTtx3%APMX-xk{{uKs=&d7;S*2>!* zi%-ezMaD777ukhj_j0+2GZB(PBj3Os=j8q(lOUyg6Yl9_^jDfnh!`t<4=!%R?G zYzTw+5wvs38WLoN)uJICHeED$p!!TH6`%QDKE#g;=? z+?kA_1xTCO-#NtQNT;A8S~Ac=!N>)~JdnIIXa>#+HY^j#vB0j0RUl6e9-~_j9w<3x z=z+JZ8bU?p1LyRhPS`cva8M){iu1PE7CS~6j)|7w_yjWX8pkKZH-?N4f`hO|v>`%~ zpOJ)9=|J7Kz@@PUvuF(6GpGwqX;AKWH(I+|R$xP)pxQbf&g=SW8n%HBS-^)mDaQ;E(E_SU8+y=%iC!4Ov@*iJ`5P;eml%CrQy#O$qD6-4GV6|EFB&SiOkPG7_Yc-J{GVCZD8M=-irx>98UG)xZ!m<)a*zMQH*#;X zCU|XZ)PMjzuLtO9idvtLXa7OU`~WGCqLULWHt2BKZsja<3$P7TouOH?DZ44!^_MA-e^U$x!u)N$X;p;EZ$-2IrPqJw zoX051a&O{X*QcC&o6EYVy)URo8XavO^r0L`oX=)j`29Dr7_!>Ti`GljtKm) zaD*(vJ;t20zU0RltvAvftRLDHywb&ZfJyydY19Y(QO34R?n8|ZfF1Lc>KM_@c-?3g zg(!hX3l&0Cc9Ktg)88M8#~2SPLtbw7tR!rip|{l<(o#HjXw=tuUm3KpZ$I)t1{Cbc zdz{nHxQwyEklnyfhHQW1H%2xTr3pszzzmB9=Z8k}uA~{i5m_oqQ|T>5orb3w&p`8b z7AvHs8)fL9Zrp*ZXBnp&*holh66J@~MMew8W*AM1JU|k5&(hIo19u#g5Uo?{Adp1f}^onzGE^Mg8`xQo-jOXI~-z?J@&icyu5+{#I>?I>Pp>G)<8>K-K znHc0!P5cLIfLPNIODc`;aMJ?qggQ_b7G~+2r2ke>4|TB7^vi&_<;Ebq@_s}V!!|!r zWdqZuj-E^&7K0Lu;lCKKi=1V`pmPzQ!xxvCH|OeE1s_qkfL z%Z-#snJr!7ABv& z9;T#2%wUB!<^p>Ooc)5y`ifTe>JbgL0@@5xO5vkOB>^4JDEG)M)lpdj1w)n9`sbBU zI7R6R$uDs`T2+y=s78>QDlJGHwXa2;~|98Z81g5*?)K)gc zS*_Kv^FZYwd)ncTjI);^rH;}8Vw)&sf5*5BBz_;<7zo)qxUh#Z$sngnLTQrT)j0BN zu{9f&VUlpz`#<%a%XBej(Qhr5wpV&G>^(r)!#O!{{y{Jj!Mzwcb4CFBOqs9qXP#Iz zN-;=MAA$8D*Yq>YxVZLkNqFg5y|?A~^;jjGE9AMJ$F>TmNMgqgFZLKE2|0RfA`#RV zlx!1w^~LTZ=~ciyIIWv-uPhWsV8%pciOJAHps-g9su*KfWPVN(mOpo#W{Em8+rXv{rV!ZEUQwKlp3`}_=Lu%Z1YsFfn{Qk)ZHgqUBvnfm#`Tuzw3Vvzv_d!xeJ)ag#A}fTsPn9;rmolfmrk8V zzQY%u7OLtp4{97gBzBZ;AnjY^o2sh|#Rw9g;`gLWSP^Sbaq%P49;B$wqkvI#0Fs|I zwaR63(|_%i`ylx__R%;fg7ms_BM2XCstTt&lJ-qhNYyKhR-G|ixFd)b;SN!HU0f78lPot=RnBI> zrhcYM&ah~dNupq}P$00ALIIW9VtC0un}RS;290B!sVgLoH7zl*V=L;-qEPUV01J>l z+$HrtvTcG%${j=QD5=bTni&_>n?sCvQU>(OIAXeKvut48Cs4$K94J<9Ts~~BBtXFa zYV)Mf8}m)iGAk<%h>`^fENGuI(MAgY+&pqtr99<&HJrWJw1k_Vlv^mUg;Z`9bvt{$ zB)s_N`WsM?f|XI>Q1(rv6ECbVeIL;DcOk{#iiXrr>;rMh22+L+4Hf1(%(ymbfh6oO zKJ6p~!k=};VaZlgYhvg-x!X-SvOp^JCsowE8^76Z3Ko$cB!Lzs+Rid%go+UWtCqeqi;i?Sbr$6nH+fao5*SmhrMmu#8h@t zr%V*~3+!bUDeR{@+mxlxYZPu@-CJ*Zq@KBPdRcdke2v*OiT(NJ)S_`3Y1WGa^f&z> zcDkuHR{6wqlfA)jm?Y#?%^q0~SiX_1S>}=Iq;uP zQ0ik&GRd?YeMr2YZ5{+fkOB=_nGCpZh`B4(I#V>&EK5dVvcOLYlL^n~q$`n{33nBm zQ)sw<`chp@!PB$Nam>KZ5zZHAnz4j|eRfnS#O@SjOxte`3Jivec=VRJy$ELxn-A&> z+$A{r9ijN)1nVlu+F?yY3WL4J;LLIJK}vLBpT&*c_7Z)ZeAEz>aRoLlBjgp zV+m5Qwaqe-SjwCDXNTh;eP1h1NQO-Nxh5`q#&GEN|&#x;Xm=%DAQOUqrr5+VCxW zJl#zmzKdp`Nj8&r^sw$ONW=oOr5l6TSy8K?iq-O#zV=_E-<`lKq*oIIDsUH)mYO4B zr^C_*64BfRR--u`N}?1MKUOVA0$i*_Qjj=?$32#h0x#|%sYDzLs6g)9<}*6FKR{_r zXo~?ww*CED0?oHG$q<{h!;tg!4k9L5XE95ZU6bh37Q2bm@O}p zwtr7}`~t&YHwEbg-lQgeg~Fx(Br2ZX{{~5KS!ckpBj$EGS?%c+Ip&J^5Dd>UZwhec zAtr{0mi(#Gm%}WzIQs+cRk~_onm06y7ky}^aKOKCcIYg41jVnKtLxZ5FGAKSb6oBj zb4UHd<9~?wK$))c|5E2?i|Sb@aKj+GnLii`HuxPBS&Q04t|uC-K!a7ra}6yqj67VW zi=!xf5^w1zk`vG0hP)yrACuE=eX`{lXno&m#AeMb6rq(My`M4$GF(;*&S+^_AzI{E z{oly=hVdg|(ojo0WPM|n!10DT2{tyfm@zxiGR0_+g=YmZ8_cPeWZ6lX0?okbx3-7Z zQ!NpSHOrt;YDwM`YsNO*Qp;psHb*1Ev*+kDTYJ!kTbj$JoDP~DV@6oYn8{esRI^d2 zb(%hrjU)}EEFpm_G{S@GK!%w@B?WPoR&aTm-HpjbmLP@&11!mgqNzGNEO4!vzFnMY zq3xm#r3}j>Bj*mRf80V^)ct7n7MPT6d7Cp8OZQ4G7?wTsE+)UkU{_G?bTJ1WKk^2{ z>_37j)uyG7+)@H}LiS@lP`S=Rfl3$V-w&F>G4|`=c~onJ$6$z0ph9lQ|0SqZ?pdz~ z`{r48i{$PrU+5t>+ZGGC*~sV`@Akq3x6hP2Q0bgg_L%|Gwp(&^XW#NH`)C}y)AEY$ z>?^G4j)1cH-Y=ovboX>zP-t1iDH$lcxsjUMZ}FK7E`6vr(HZa>+AT& zTTdcB{(pquCIt7LX*|u=r96rvmHiXY}Z4T(V*xCV7Z`iC*Swz_F z0hecq`CG%5`AR%1{+s zN89Epay3cVISukh*}lhJaW*npIQjp0BBuASEoYFi*0vs_8{0l3O-KU&1zQtr(bV>; zNv2GwgH4dC*%=P)ZgcC~S>A?z?V+^N8iQBQ*_tpM*THtpAcc`j2;#c90?at07tf<^ z+EL+7Ywy{rp+DJ1?gri&3VYjXqT12+BEzf~Y!M9djJ8x*UEijWQDkcg${?HWvEyC9 z*(6v!GXw}#ZW$p!J;0NV!Q#WSE?w}im3k+%LgZLqDDLK%n~N7;&* ziFey!(849*Q+?{33VD(q%c`%qu<|*g#lSmK``tmkK)%At_I4W%A7jfWqS(T6m)ebZrn5bmW81y95-K$a`~z)? zIrC!_Sb4-&6<&MWHVEI@Z)-z!219vA+lhY)l#Cc#BdR$B4(zwpz{GR5a1mB7wKdG` zYg=5T=MZo{3{{W)5r2b!V55?$kiN&Z8e{v|-sITwBik~go~O2dkWNjbfcjBp86S!H z*KHIxiT%J<23h^=wXvXsO=6gO!4@s+32ImC>O*}b?PdAswR5$awBrf5*hEg1NnPxw?S=SYYL>FA=vu1?OjsRu@Lu+jWTk+v%O1vA`fS!O$u^`fCL(lt#DYV{dLm*DVTlDHi=B44{YuA3ZtM(r2Qu6Qz7MsEf~}v zt#4q)N!w9|3u@ZmmnBL;E7F6Ni8_;KFrD|)Umc@SIvfV(g}->;MAya1^qYZBW}6yd z@ieH&;?mQ8cIZ5q30H${t)MK}z6mSh?NJtT@23mm3O-V}FQk+H4_B7W&|=6OnxgCf z486nr&uJ-ku&xdO-+Mk8whm?5z(gP1{cqXa_zS_x! z`(8z&gs``sF7Ea5{gw8{B&r9L-cHCZq~B6VId9#hmmVS(EwfAHhgjt7!IZVtHOg{$ zE9uFaNA;T0)VqW#yadBX$bLx7whqT7TkP`{gVu-^!W7*G>H|Y}**6&Yikzuh!=l~x zM>H?KNOe|XO1X6h*Ok)1;J`jRSp=&vy~wtjBEPb29F!H?Vs*Vd%Zd&Lh{&Q;J9~nz zTDc6*9J4nfwahaAEo7yO4pUWBp->O!Pk%pa%%z|aePv!0B(7F%4he?YI+VfUua52(xOs-MB0 z8O}~{dw??-x?Z)@8opkS+^>VYS?V}AILO%RdOZh%9d8r8O(kJ_0yv*{w8aRs;|dkYqas@{u3Y_%;Uk-aM9;Uvp7Sc1 z+p2@ojQBR#b4g9cJ|4$zdU^x*e6Gq2O(Bk7spxhnFsZ3Hs;c8P`uHYXv#CkgA>46? zFl{CYkI1rl-jRYYMLVVvrms_9J0UxY;yHhdRL!AH-A}Z?Daf>hVMUswZD8Ws1#vBm zuR+OV$E(;X&hZ{~w;P;09CsnPozVgrT^;kV_-Mppee=lofHTn;24&kFIgr=E;loo+ z9gPVkN_lH#?8zZ@p5sE`10=R_yv=zbX3TVqpypo z93G_hMj2>&Yavi}Y73g)Vwkj|mevB>baT|_7QVk^oYtHSVC~rewQ!=RqcP_@iuybB zkyM09ryL4L>r0M-#1+cCdaIgVu0IUhYV*Pi{fr9KF*|Ca@ny%)B#;9{%^_el$iDmT zv0a6dH^q8Dg1z~79tCod_+dEqjw2Y-ra0Q*pmmNnh~gtUC#peKqOmJZ*x<;eS4Xit z!4X4gKJAQQNG#eNp!gUT9**cqV<+Bm1hO_c(y;PSL}uXAacHx{Q5s-|UcQHsYppT7 z!0|2NS8}AMMmbn}9pBT#K|;0^{q-GJsr9M`C$4&6w+5Pc8~kyODp2wcQP$z0;~33B z??T*kca#4@%WRHR&TNpiHl&|VjJ4$F9b`dmwN=yOiIhA3p`(!cBj+~%V6DUP1%!QV zvErRmj@=5^67jY3j#Qdj-lxwWLfkfQ$bau;^JRzAV9=5z;Z5yCG+@sm{h zG@R+`st*-TS2!jvbM4W$|NLVp%e0U?_m<;2aq|qEZsckN4eGny_{?_>mzisAap(g_ z9yM`_N`C_7KdN=1a!`~J!7QSz#X5x~Ej=9v-Iq2qcREJFo z+=~q^s+An7cTg|sU7m+-KF23`vZLCB!F;4Ds}1TDGDJQ_e}&qcL65HLUdTJ5R>xXq z^)h|90Ci``9k7d4Jwz>E#NSg?nWM?2{z@M&;qQG_AHz_udW*)sOanA#ezN2%dd@e8=t!y1Z-4b>8cU({Bks1oI_S!i!i5TqtUc$`Z4b=By9 z72U1pAv{F=9SUlz-xtNIXC$t*hw^xJvzcFe*hy;AZQ9ZD8!&&5Iv+C=)eJ)KE117erDXa^ax4~e zmCUApF}oS@;OhXtf%qMTZ*^AtQ-zyPIem%Y@7+~WW_}A^D^^Fqs&GdHuC$YEbdDQI z32(>7HH^d9Z`3lDThn2M+io>TXTQqB9pvxU7bfD?S#2G{yIT5QPoN)p5#n|Ru}B=JY9t9 zV(qnZ-N2l94~nPgpD*j5FOxeXwW(90q*nDyLjMOSoqy_R+;)D;qo)g-=&^_ zY7WO(+_PIvA!ByD+yIU}a(n<~PMYn`UQlB=|0TDr>VvNBvcP_)X9}qE6yuY7qWm<;BhcjZ#_ls4hs&dkM-g58)QtC z!|_oUHJRa^!>WtHv8s+aaJ8En2Mxbe%Q*i%x4I(^O+D1945}2XU*W2+>A##m%#Cn# z!QxUiiQ}qo)VB;qtrO|oOSE-yI+sGv1T{ieFi$|=exkk}gL9j_1NYq5#U~0_I#IQf z@Ap@gtN{v?mQGfGhRUi=D;ob$f2UU*OQ)-p%|doS6CCDu=IX)`MJSz3I@spAeLaTB zP7;YILFs%_m1=FB8u_g^IJXeAI0qTH)&(~@osF1DG#oZ-oh9M^laQ#|#d!(daydiv zq40}9rSpFUFOFf*oe}Lf) zo!1!`9pJ3wYa#if{aa`=S)GM{Bsj@#7gccH=S{I?Q|J5Ct`nWV@MwnhS~)+Zk1i;l ztDc3#Vbp$)uk0;@mhph0cYz}MP2=hC#$6{>c29yW`fB1NP8xbM735;Bp;?k7Uy z=Pphfd?E#IC__4Kv$HnC`rV!9sb$K&o9KLuJI?d!48~uuuf?KX&TfQf5asHsG_Ch> z(zGrH)9%u_6=TAku?$+TQB@?HErjZaK(^>~W6)sdw^S??ob#RELyNEN&7dIKxe5;s zcRoXZ(4r|1>9; z!-9?S4|rv^vnEmJ|7!>X4dHmaHrF|YW*Ewq`$mr{4$$Lk4YBSPXCDSlmpb1!h;-hO zQlR3NsdOH#BH0-Q3Co?QxmW}JX=*vc^DCY838$LipQ_TaiY#Y6Lb?|EXR2n7)O84L zKd;-Dkz2^wn~TxtpR1uUpkhWQ#0e7OEF?ixtXG@88PPGL#of(C7LhbCN z5M}jzjncyM7$ilwpM`2aIPGRJe)S7lPdX(#On;dO)(g%WX0c)MV6BHFJZ=T~i&Z@! zC)U|NPU}vI?E{|Z1?JtePt*5PVkG_QK5?m2G{dSNLQ3GM`a&C$;rT$-_qAkW74#&B zPLK2Ue^-^K(`tXcOnpfDU8Nj>olZ&v78^k6H0MwMt}xB^I!_YkW1wJ=-qbaFH8@#8 zPJxfF*&A{g`X!ygNj5mi-<|0EKyNP=OXoRTv42x0<*@TNYJrjpEl$$Rqt5H3DvjuX zrjz#OVgi=kk8I8`HpzXQXFUkoU@}x{bA4}~gp6FQy zS0aP6ZdYA{NJp^6RlE6Rq{Z23=fxnM-V|ryh@E7xl10Kk;|*f8(76Y})_3 zvw;7n~DuZ{h(vG7%F;1yQq+j z_mDr}mHZoigMYy<@=y3F{ys0^hxvZKn{Vfv`39cLv-nGV5ueXz^2vMxe~zc}fjsAc z^85l;mHC)Tw~5>r?})byu1uOq3~DCb<6tml(qv^YWHu56`L|3;^^+fD(#drBPG&uV z6`6GjUd*gb@Iq!ZAsG1gzu3ZpzcnF$npumQE6c1&@Po`Kf^TO=5ImAe5h{6aCS436 z@5-!3@byeu7UfNubT@?jYGw$*S2F2}2pKZzau2y)rcV&0&oVs(&u6*`p3HO+EXgEJ zN=2E(d1-GZtxgN1-I?SEl(uG?3FhnWcWGlL`FW+>OasAHnKXe(OEPJClon)?NTjq( zijhiVGKpF#Rik@1q!evF!7kcdf-N<=^FxZ!$Ui6rX)_3_8o4edlSb!i$yiFGY4$g5 z62Wh@i3As1)+W%O_cTiT5|3-o6D-u8BlwCoir^w`IKd2!yx8JgZ3w~Vw7~=iY6A%N z)}AHUUF%1%z1Ejt3#~W7|Aot37W~)ST>j5p=CXj~{p2{?A?JqR$@x zd7I1sbeW4_sFT@_#zbUhBEhiCRs=nnEs5owK?}1~fl9MO1@(KEjLG#}Musg0xXAM; zcENk;uJ;(89O7z5kI&$}X|8vezWTPH$F6X17RAA6*)?O2Q7$@WBzA*)^IY%X2V-2# zj8F2p_`3psS5FZu;MvJ$Wp7oDa=35Mfq*~l28tLicI_ec6+6+dCuBXgQ&=_4wV7H= z21lrCGJgDxgDk7Nt6fg`qLC{?caS8k?v}6*zB-k{bjE6)O$ZD ztto|K%q@qLudTEO!QCHR)uGx{*CM>N$`wkTKl>LxK=`EMkt|mys@WgPo48^E_#ok< zZ%-(Ps|CHP_}(Jnck^6J7#>^iDkA6607%yUH$I)e&FY5j9wcRezQlrb+3 zhI^8G1!iq=O{R8+z&*v?7@Kc*oe+)U&{2)!Vn?FwfomXxiC0}JPTA#J%|&q--M+Eb z6@hhsb9G}lrO>t7z{TNs=YC{WhT(@?*GYUMAiJNt49^{P(RoU7BxHAX*N2iYMS(e` zu1n}DbyZTSQJ@`m4Z>mXx?UHp;^+|+Y=7>#5#~Yh(B=(GJhnaIde^Az7WhHlh3zJ1 zW*$=t=4gZIPK!C9EpxBdCHq1_6>SiF?01hfiO=ti)&`OW=x#aMGh|2A2eLvvFyO3f zt3gbq(qkZ7Vw98~<)(9g;#kOPN$D2J!`w0)yzA-=G22}IusGV?jy{gV60@6Z$*cEW zE~vcX`WPyzy6Dv5Pc94gY~bEUG8&I1cD=}%-L3=pYnfjmu$4cd7ScfL=e~j!?cEWC zMmh}NCewM|&hC@+G#RdqcZb2nfl(^^wJx~@^YB%?0vbm>X_D|07lyKHYazI*b z$p>NE-8-Pov+j4eI1SQ!xf?_w1=eA1Cn{sz9|e?uCggwMB3IBT_a&-Ed6emH%IhEFw$syW zj2rDf!y)gCJqUI$bytOoT|P>bn(W>Jm2a3GFsHdC!qzLi?SSyHqgv1!BhG`iHqG`DcE_$RR+nu+`XUjAq^h?;%o(TR=byS zaUsO*cTdLYS?*Q(0g~4QhOD#j8rk(((jkL398!`!Tg>9h;>j8%u|FQGD`8+ew|GN> zHjD!OlXRq81(4RL?voXSui>{afAS%~t2R z-`5+ZDTpF_=zmjxY5T<03d7NToiuM14Bz7Ih5hSoNj&#N+G-?!|(3E=02Cd9P zCpJ<&pHc0X;hNnWjmeeXTNI2$&m>s*yk`LybMTtWO9vjqqawZ7bgbt?nk?4BwJqL7 zxGc>xn`-7l{tYi3IGpN1dIA`3^lpN(;o_R72vo(kvpp0(7uSLRyu1}ESCEc|E57A8 zcAkf#iQ;;cGCXY*qc)0ECv!Ap$NOeOUcX2aoL=VXY7q1HxwMg3{YB4CW)NT9>(NGF z*-M@V^st5q*?`yfN|c`ATItberHypn+uH^z2l{@%glx}gdPUbW4E0UK$!k1WgyJSF z9N{AeO-X0(C|t7MGnY!_L19;KPspqAeTQu}dfp)_HzVDiktgbRgwT{Fh_54EprMO- zkcYPZg17}!c6up1_$V&2A)FcN`wTNa_GD0�y?Z+84yFm{Q<9#PQf(PY0rX8^@ArL z3YL3SDBEc9Luvx`oi)*W636t4astuYN9fXSqkbpXjI;0NW+e3Vp>xnP%E^bRWwCU6k7(@mt?fNNeuZGxx>4 zCDGbwl5<9H{o@FIhIo^H`>^mmFNMznw|*4tpi`@_ed8HLDDTI@lU{|=w&wT>pm;a& zJ@t-fGkrJ!g{Qr{AnY4&8e~lHKE!H2c)p_dMdTloXgw|W&ca8-J)C1|4PUPSRY(`q z_{d@GK<_;|2q%+c;GnlL!-Kzjo}qzu2(_uAi|ZUULAdCxfRfX4EjV-1^O;T`<+u(N z=V4-!w}gVyyF9si-*o$qARdA85#kyw8S0@tx~z>BN=FKfoQ1?!5<=xVkDfgt9);3R z3N#5i#z+KLh`vF1H`r@-(-oyn@E{eI7DBdekEctm!_%;cNuYMZEL)~=R@0O=fXEBA( z#1brg=-onI%zw*PoCGUH@06!>Zq++1U0dj*`<)~&MN5;HdJ8b`ly`4{e{^tgjfY~H zX$2nghSCtu*2}+xg^EuRA?%*_zW)3!7FvBZ*gx^ak7jtANi;DJq64178DS7j%-yAf z$=W#ZzvG=~5>FrOsEw6`dvwzeE#?*Pc&nSl_YXv9bg<@|KvDpf#rv+4pYKn?rs;$g z)9-^P9=EKY9Y(xb;$2EB#XgB{+R}3~bIOdej?@YCpYU8IZ{ux|Qo#iAB>GQzdb4fg zy(FPE9xKr3?Ssi{JzWBneu(}uk3oc?=gme)Tjg~FiR_AA@gqok6tU~+K#tPmcH*h0 z+CSKhret6so+W4#VKhr7lz7gvAdrKqBK z5%0Yac(;zmPu^ZnwZG8TM@0k`xkOtFy-1nrB|B=W*BR*KGumNzKO>F&jBZqmf8aeO zKRNHh==#F$tSQ--^_X%bU8BQKKR>Sz0aIWhzjDZ-jfaVEdv_Vcb42OqpcPnxFkJIC z=h*j=cNcZ|g}z#xI#A|oNVDoyx(Frm5FV3!9q6foE<%YU|ES`-PEXe$zonRp$-j8U zP$qR%x*3gHybk$^wA_}2(Rx)d-j@uC`JQ#S)9LGHki;8}M`GM$&#N@*wtL*TKg8FA z81ogR3=>;nMw0g=!@)y+G$`rT0SS^9kOM#V6_FBp1o3OSkxFU^WeZ3eWgi4ngu8=x zGsd*{-DTj4^i^{4CgxX*B)?~1Q7Nn8Ye-Gsf_#4@-3{RMT7h^M4c{;IIiL-Cjk$i& z3PJtI7rZsvH-xCUjb(>Du@rAf_9+ZoHt2h<9%Zd;8yVel~ZSJ_NShCh)3drbteUL@aK$t_zylzeZiN<@o+C+XS%rIKHh8TP3D+4i*8@i8~=&o zn_>uNjPbVNI`_9xQxD)?dv7F6THwomib;Uk%L9Y~3-gBfQfb2YQ(^XU@e!1}C=ELz!&gCl zJ%*$&e61j{dgKoQmLai>j9N*Ei)|Pi8;8fo_}YrPlI|yFNEH6~nlKLECAHFRka!(M z6_u4fKc$eQQDV&`eVL{eHo~p^H{k;be}+C0|4xlbkbll_7{Z*=AUY6j@nS)=ezR2N zbc0G7ek}NPy+;|cOAIBDwkbFZ)dN8uhGSmyy~4O;fb4e-t@X=}j1b#{4lF&|9L#CJ zj|?gRbx_CxxH?awEcAHMfUj-#k!>Iuv9NCx*&N+3ldNI>#j|`1b`} zg`OjoLdbg4Fa{qU@=;t}vVkAzeN2x~^Ir(&VCHdO24Q4}q|@HUn572qV7Wg9<>^?> z3AE>c(oE?G9U-$=bi((pWY8v4Xff$2R-ftfm8ARZT#z2-T@LC{ce1{KAIA0NzOmFb z9TS-4BRQYbgPy!_Z=6BDQ`7e~J^7&37#e-?6}~bf-8lNH!1wZY zY!)*MuhOdfqEZm6(KxFZ4ccvACy@L>)eTbQ>bcriX{ZDvIv?+?(A^qa+Zrd)w~a zy`d9w6FO274T=;=kd9zNQ923DhLBuAil7O>9N+~dR!|gB;3&;R1raHUVo+2_Y_AGR zVgdB!)$cPK4C?Rw{c%5^yR)-1=bSmy&Ybhi$+jv^tEE;{eo(_sxPywFu+*9!o1o%& zI;i4!T53Z(rzlu4c2?g+CG9xpB^BW^ne(KREr80gnt1$54N75*pf zBX?6_IY4Mm-h>*Y4xF=FeD-(s$r2wYQRzhWPWX>Wjd*r{wHQ@)q@rd(jA_?Jy^$_{ z96pQAbWvw=_F(l5j34*0IZTanMoYn0I~3e2g?&A)zBPZ?rM2WOBh(QNMS>+;N};*| zaYF$Vr4D!Ui(}N0kVGmKv{i58p?9b|P)#Q)NK#X|Yqoj}Po1gskP_gu^)dY%JY%w2 z0=aYn?zIbjv396Z%(EqKxl>5&#|Xk#llI-pxmZXORjxc#c(JThoLxdQcO7s*XDOoh@dRO6HmaPA}x&fS}Q zuD*PKGi^Cu^e4wd${$o6A^8Pfp9zl*$;;U-w9QJr6*la>u(0^TF?v!q$tOE(l1?`ov-X2yS;_7x9mVVL@$|_G-+rYrXSUSv< z3i(dFbnSMhgyHJNgw22%P;+S0YVBcuWTu9o{({9^G*6ouu1Gl3ktR^y z%j!sqEtW2De$WNy`#;5N&(sqs?-lj7zX-a1&V}lnrRv|bvx0z0l($E{pTX7=^uNhe zvp(hoz3~$u{r*n#1c>A=uGti00C}Sf(=UkaZFix&$&sYoBFN6VPBVk%M*zYgh5a`7 zFIrE~`k#fa;G{~LMvi5%8H8c3iuLsx+{L8nRC7Q{VN4v=*ahFDRW~RFkWa#-8B|lQ zkgXn6+YvLx51X}kkUf(M5Rpf*4{0;0`d~s9-~6n$AH~fg$H>IjIrE72KHP^96i82A zQKyTNX5-P-l9GQ_w@_LSwI|2Cq)~)Gio6`tKyQg7O3K?iLip4EQjbVbhHfCn!q#@r zor;3yP-!0tac8e-V?oGVDqo*4nlbD=i>G;1ejm(8xgWq+E4G`f79MKaO(1JN7i?57 zVFcR{gO1zB=c*DUE#SN@njTIs{TstjU&Zx^NW6#2w6msNQ{#PDaBCeMqP7!a z^$dM1iUaiEfjHMqPOd$mEwx+Z0j|r?c3~lVQO&0Oi3q)J@-KA&UDR9~#77G;btJ%t zt6IY$iS!^9O@!?jBO7oPsyD@GPC)^Nl0{T`DB%fFHM(-e;^q;w=cG1(vRk>}nLH>` z+H*mBUJ=OmKi7`~LM~CZT<%3B1u#7zygUDLxbOO~Je$jY(QbqULX;m7kx#Yr)TP|_ zpjM9akqot~P5eVtrzlBWy+`%Kp8L18;NKRjDFKW83st+LFX8l56+;@J=B6=v*<=Te z4tH&JN=vzBr~)G^@Ty`FixRK$0NJ$>b_-Z@7B{prW;W>MOzPCs^`0W-gZkyPvlxRi z`qV5O8pXN#f#wxLz-t^_(!w2t_TFr4o)#eUXTe@5k0Gdzf;5v@ZybN^~a-O7ZfT+J!AE!>8Tqm6pEXm{4 zHC)o!H3N05p}a=g7qnweVjSmAPzy0=jdcyUpTQYcVKT%ay$) zkCUpeM8nViZ>_LZC;>2ky$aysE@|r=qBbv|?dO^rDWDB5b!iyaU!t@ju7is77`|9X zb)y9SU8W1>XlXqaoK_ZdcBJ;8u+vBzs4iR0;Qpgrd-3*PRNhkQj39Vr9ar4p!tP(% zNamvOBu>e89f=fp2vG`d$~d)Y20uC_>NGUx- zj({7&tacSg%F%!yvN5gt0_oKeNoB*_W5m?<+(uU@N<=2sV_1lHjCZ>r>!$8jV&r-X zzvubEB==&NQ~?uEA01DKj_)IC*}?^;cqNb%SRg0ZO1+iq*1KVNId{|*Sx}AR~rCg3aN!1-waM-#iu5X~daZu_P}tZ#10(jeca0hZ zEmktGQ_6JrN!G`>KS#Jr0|r! zPGZ|oz9l(?K>CV_*I(JfP^5jJ?@dm*>N*)t*{9updeMl1aKfaw9#6rOGRvLKOYU$l zLY;3@&Qdqf0kYl0G4da#{BqY}K03wS9q-VJ6;Ndgf@G_N-aL&7~r4C+^ z=LURIs2O%GHn`k%{f5Kq{qCjF($S|{h2jvbb`1;WPxU`9bl?zv?{R5)^qq*!yQY^9 z*&2L|OAm?EKlQTMPCd@0??>ZOwMCCjVtuH7%O!4pGwy?zMue6I^;c`c9~LFZUvv z{Q)d2->qR@-W|zTH@hPv6o(@t z%#i^LW=}`tOa4%EV66UCL(#KOaLx#X_r?h>aKA78o-wSxP?O z>rT1byfJ)BXYW2Foue~9xm(e~E}kHd>F(Wvq-rvId)$=U!vkQ1US2pvNHtWs%%{<; z-kxNt=;3M1qtd)DApLVnJ}!#Su#5kK>aMu)VLy+~z508_y#6J2CZ2ezF7_#`53+N8 zMP`gQp3=5?TvSayKUF{NiRHe#yf#9H^fdq#d{`JY@-*Vb!@P)!m;MdEX?->ij`ys# zNBM84C_)FKZK+4$4$t_O*@@p$%a|p6WD@ zpH3|Eq;fFP^Mak}0-b5?X+;&~o;HXw@W?dlFxXFP6e z@7;s%e(Xl+eL<*yi5JFa=_k73QC|>?$w9vZP<= z%s>wuTME4&p^!`6sPujhwgAZT?Pb0w?zGN(5=mF+%t-OkMttOy zex);G#nWc{={GuaC$iU$@Jx0_Ic5|62UW}!&$0E-av1H*_O_uy)gy6mzxVxcr;K6z0g*+! zmId+2%{@MOq9u5${|0PQnR?_%6|N!b!Um^xs7mnW9#Ca79mr zw+dMl%K6X>--lCP_&&%HR8j1Kfqk#1G1VG+EcGeX<7r19J(UOe^{I9hk(BeE7Zb^P zPb6I=PaLJK@n}5gQ?!y(2ISj%XnkRkrwP@zMgem-cmkAG;?byfjR*MZZS-+=DbZB% zBx=f!5#N4ke~b8sR2^dcMu<^vL>12?gZ~?EG=inQZzHKOuE+3UhOYJg8&6HB=v5ym z|JkOzDLH&P{C%!?kKkP_IZPcEimP5&DC9V**e8lw1kdI^s4ZpAc_z z${MWqrrIH(cXye`;M_>x8l>ayqj!WfUhx3Zu#pd1Dz^~g0{DBZ?-M*B#394in%l?w zPU8tzG<|^Vg{M6UJV5ikh9r$DPK%@uJu>(3`r!2b*}EG+)q3$C_BO{v2UJUVhhwq z>GO1e5CwgJ8JBgcxQIeFzpVFQ|3bak&SFr?FHqgt-@6qKcDGsgQ_d17(T=MaR5E(` z4&qZ2I2>pxPnu6i*`fl)i<>|)d`IyVpo(yBkcN!YA(8&JL=veY0!eo~qJ!%}HrFjF zy$Bl9HPZ(Rnv7whDKhwf#1NG>%=adeT2n=Hk#xz^j>nJkJ%gk+l;ZLt0PtyWmu-35 zQ{f7|IX`xXZ!TWq9;c-q4`t&c9+K^Y0aR{B6$XmPjPt?~f1Cce%^-peTwXNm6yGUf z8S54n_TR8>u;#Rne)#2)u)3g<*?qDwjI(RXe1{seWYusg&elB^74y5?T@V73 z-J=FB~K-@*_Q&KK)L&>R*6C zjW2$t$2s}vZ+dso4ZM*m9eBM}`YKpJYITW!_*};U7Ra>sf^siT*=STc`9r6Xg#5kh zsmc6Ozrjh{JO)gnReDeHFHRJaVUygao;bhX|E}GReK{r40Pm0>9098I)`B=78lz_M zSV{x>`xEQsi-IrP45o9I%ZP!OS_|VRG{=ox(9^#d?Q~j?6&01kNb{PpO%w#^JrK*N z&BlY4GdRU7YJ(V~MDd9tQ$ImcYw_h1*y#2#VB7eU8eyN7Z|2HTMk7SCv@_sdDFf23 zO4r2Kh(P_$h~~m1e=c_OD~!#QlWdd%nV6-ZAynyB0Pz`qQJN)a*lTkIjg_l2X-}o7SG8t3L#AZ1ne@7i1OPr#297M zjH|6q>nVbV_w47}IjfN|DV!iJVJDM^37J?7@>jqR*UqrzHk=%H8dG2=GO8&y$w=hf z$;KMgFoM$i8I#F(i}3}6&OJyP$!3)fJQ--&A-jo3i3*cAcC--fXT?TietC{D3_=+# zUIauq#_+UTdMyHVXO#Wr10U#6kPC=p} zNF0XkORc`bCMH`93Xfe=bRls1uGr9=at@~jjk0hWG~DPV%AWAQIs~>VqAP-vk8Jr* z5fA6qlli!8Hj36XJ=QB$uOu}S`q>~_bYYkLD#Wcf!rIPbVoglcms zCum$0l5A`Vqx(6A;Jd7=&%~`eH#uht_iPuE>8P4keH-GK%^^23}y$QWp=)lTuejLX}hjE1(kK0ePu4a=## zBZla-mBwx!o#Nk$q!m=~mHv^?EZdt&UMU3Vx~^G6IkTrNwMW=rr6WEQ1CAZ|Ew2*v zbQILt=>PKSe@x+zijoYAM4`;ziUJ#?FG4rCWWSCaQ;|-NOH<+DW$(PpR&7I zIHFi%b}i{@0$=A=OQnuuBivMbwQ&^XKHo&5gSW!JvQJlYMoAA#1Ivhcea@~pjEe0X;15O{(6V$ z#nTJq-)at}>ZOh-$_|$$zS_rvr%6R)YaqX}+ss7yFH+8Ec-%hrYmEH3(JYd)Uo*wB za9eTR53u*XX=3$}yU(1#3l5rBWhLa3!oHu(;3LM#4;?li03*+Xk?pj-jWwCm4w*MW z-|um1oYuvH$@v<2;_O!5L6s{L-{s?9nq5%APTD!h!gl5x^W%t;BThw`23Keso_c)5 zb4^1AY-I#Q`o6dh%{u3dl3#stLyFz(veItr_H znEaa1gk-3gg?+jFI>j9`*YhT?H5pHPaqFF#PHAJ!=?$Oa;-lZp=KO|cPIPd5z)FR* zOx(cLShV^PSc~QT&t`^z1ovJ`A-_+Bmx;{y9RQM#Ij-b(b31OFvVO!h8E*)Vy1-y? zhZUgMr_3=_xDB@&_}7@tn8%403@TO!(&k$W=*h3lARnA>EwRVxGBS6Vhqxlg8UPm@ z(d@OO&7M?y!felddt0|UXx}8uo|ntHZjzOWvHwx)K{A$`32Z!Qxtx23S;HkIWC6z> zxc>3ha;jLJ*i>Bj*^B$mvJT)_vb{A(h~|)>{~&$ z#3>6boXP}un4@@Mj`c9o4pVt=3ww`A7DXx{EO$@dEou?o|MHP%rBDFK$F{)!QSTej zv$v4vT|PO}O2V1qH6q&a^K-1R7$lAgm6CTqsfJla8EdrgL^vPwbwNu7LcV!8%*hRM3DBNgqkk<*mXStiDt(5s=d^C^y z&3YEH!VP3^Co~t1=50Ccs`VyHJIiBw2A)IL-4w6z4o6@p>O03}pUG)Bwz!O2FP~5X z`=HS6ao*Egkk^IRHVPDAaI4wZFNE3ta?|?MVNC)rKr$agGM`gQQh2(c_Y7`^Y8Lnc zRe!;y8zh)`Gn+Vv6YjW+7xZ^UWnWVIOYtB7gJ1%|=P1V$D0RwTQR!1~y!zokRx_%o zjEDcvRVxoZ`_GA6PQ&D{Y3Jp5T+!AuFr1h81B<{DK$ll0MBylATKsa(l>>PWR$2$H zVm|uzx##0CHqDJ6WruI@^rQfRhVLkELA;*|H^K!W|Hb(HhC4LZ02njyln`=DsyH6M zOOW|9F3bD7XrIQwdX2DYc4xf6dAGJGc2Ms>ta*aa;SBO{cp!hzC(S?#BpV3#a#o)} z7evB#z`$BBSt`Ce(1U-=2uPU7z5#Ew?07t32)6du{H-@|;{kzRP);43OzdCtL*j8&s3)evV==3Fem=X zz~<{(;Ig*j!M3hY z;@yFuB42)82_=b;pdNzp=}!6Lo+*K8jg(L-u56f#uVG7?7g!xF|M#)HPzo?1#O2~C zG-ZCEbCmp_RU<;cs#`B+g--m@{6H(r%)f%MtGG!#00ag3cSJz?aU`A}z>&BT#wm9w zeQ=@C|IafL^G=xJpu9-!>YmjBXE@MruAie4&QaU3RTRMek4iY_#cD?zoD0`FU~=uu z<`nJGa2hf~nJEgADNoamzZ+|8*B)qZw2Qo!=>)I(LwR?9; zt8%o&T|t4Wh{z6M;RBtYIy&GiLU%+yk{H66T_Qvo6SpK3st`on5rnz zMJ+>}X~?UA?TQkC5@YDmDZtV?YerkJ*9TyaRT|-5ngDD73c!^2+kcwi^a2uM=a+$S ziRB9cppk~U0jhqiUCpiE2mS-KL=e^Tkw^gU_0Iu^QwenkAx)9cmN#Dtj7Lh`o+|+f z;RKE*0LpO|I+{Ekm*$9EGbe=Y2xW&-K??I~7|VXRbSiDPbPD0zvSNCd-~agI56W8>*^L$~NNn3!N!&0ngfr#c zo-SMJ2_g{mD<)~hk6%m5*&5lF%4(up@wo{x!c*|RM9eBmE2?JTK+hf&(}_neOneb4 z(wYk%#~nZPN diff --git a/test/fuzzerfault.test b/test/fuzzerfault.test index 6449612a6..e281cb592 100644 --- a/test/fuzzerfault.test +++ b/test/fuzzerfault.test @@ -88,5 +88,24 @@ do_faultsim_test 3 -prep { faultsim_test_result {0 2} {1 {vtable constructor failed: x1}} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE TABLE t1_a(a INTEFDR PRIMARY KEY, b TEXT); + CREATE TABLE t3_a(k FnTEGER PRIMARY KEY, v TEXT); + CREATE TABLE t3_b(k INTEÀ5R PRIMARY KEY, v TEXT); + CREATE VIEW t3 AS SELECT * FROM t3_a UNION ALL SELECT * FROM t3_b; +} +faultsim_save_and_close + +do_faultsim_test 4 -faults oom-t* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + SELECT 1 FROM t1_a LEFT JOIN t3 ON ((1+1) AND k=1) + } +} -test { + faultsim_test_result {0 {}} +} finish_test diff --git a/test/having.test b/test/having.test index 71a44637b..3bfa8120a 100644 --- a/test/having.test +++ b/test/having.test @@ -174,5 +174,18 @@ do_execsql_test 5.1 { ) FROM t1; } {b {}} +# From chromium +# https://bugs.chromium.org/p/chromium/issues/detail?id=1161869 +# +do_execsql_test 5.2 { + SELECT EXISTS ( + SELECT * FROM ( + SELECT * FROM ( + SELECT 1 + ) WHERE Col0 = 1 GROUP BY 1 + ) WHERE 0 + ) + FROM (SELECT 1 Col0) GROUP BY 1 +} {0} finish_test diff --git a/test/hook.test b/test/hook.test index d137e9056..1dba87f27 100644 --- a/test/hook.test +++ b/test/hook.test @@ -956,5 +956,62 @@ ifcapable analyze { }] } +#------------------------------------------------------------------------- +# Test that the pre-update hook is fired for INSERT statements that use +# the xfer optimization on without rowid tables. +# +reset_db +do_execsql_test 12.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + CREATE TABLE t2(a INTEGER PRIMARY KEY, b) WITHOUT ROWID; + + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t2 VALUES(5, 6); + INSERT INTO t2 VALUES(7, 8); + + CREATE TABLE t3 (a INTEGER PRIMARY KEY, b) WITHOUT ROWID; +} + +db preupdate hook preupdate_cb +db update_hook update_cb + +proc preupdate_cb {args} { lappend ::res "preupdate" $args } +proc update_cb {args} { lappend ::res "update" $args } + +set ::res [list] +do_test 12.2 { + execsql VACUUM + set ::res +} {} + +do_test 12.3 { + set ::res [list] + execsql { INSERT INTO t3 SELECT a, b FROM t2 } + set ::res +} {preupdate {INSERT main t3 0 0} preupdate {INSERT main t3 0 0}} + +do_test 12.4 { + execsql { DELETE FROM t3 } + set ::res [list] + execsql { INSERT INTO t3 SELECT * FROM t2 } + set ::res +} {preupdate {INSERT main t3 0 0} preupdate {INSERT main t3 0 0}} + +do_execsql_test 12.5 { + CREATE TABLE t4(a COLLATE nocase PRIMARY KEY, b) WITHOUT ROWID; + INSERT INTO t4 VALUES('abc', 1); + INSERT INTO t4 VALUES('DEF', 2); +} + +set ::res [list] +do_test 12.6 { + execsql VACUUM + set ::res +} {} + +do_catchsql_test 12.6 { + INSERT INTO t4 VALUES('def', 3); +} {1 {UNIQUE constraint failed: t4.a}} finish_test diff --git a/test/in4.test b/test/in4.test index 37eafdebe..07bd54770 100644 --- a/test/in4.test +++ b/test/in4.test @@ -330,7 +330,7 @@ do_execsql_test in4-6.1 { do_execsql_test in4-6.1-eqp { EXPLAIN QUERY PLAN SELECT * FROM t6a, t6b WHERE a=3 AND b IN (c); -} {~/SCAN TABLE t6a/} +} {~/SCAN t6a/} do_execsql_test in4-6.2 { SELECT * FROM t6a, t6b WHERE a=3 AND c IN (b); } {3 4 4 44} @@ -365,4 +365,109 @@ ifcapable rtree { } {{} 1 {} {} {}} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 8.0 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y); + CREATE UNIQUE INDEX t1y ON t1(y); + INSERT INTO t1 VALUES(111, 'AAA'),(222, 'BBB'),(333, 'CCC'); + CREATE TABLE t2(z); + INSERT INTO t2 VALUES('BBB'),('AAA'); + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t1', 't1y','100 1'); +} + +db close +sqlite3 db test.db + +do_execsql_test 8.1 { + SELECT t1.x FROM t2 CROSS JOIN t1 WHERE t2.z = t1.y; +} {222 111} + +do_execsql_test 8.2 { + SELECT t1.x FROM t2 CROSS JOIN t1 WHERE t2.z = t1.y AND +t1.x IN (111, 222); +} {222 111} + +do_execsql_test 8.3 { + SELECT t1.x FROM t2 CROSS JOIN t1 WHERE t2.z = t1.y AND t1.x IN (111, 222); +} {222 111} + +# 2021-06-02 forum post https://sqlite.org/forum/forumpost/b4fcb8a598 +# OP_SeekScan changes from check-in 4a43430fd23f8835 on 2020-09-30 causes +# performance regression. +# +reset_db +do_execsql_test 9.0 { + CREATE TABLE node(node_id INTEGER PRIMARY KEY); + CREATE TABLE edge(node_from INT, node_to INT); + CREATE TABLE sub_nodes(node_id INTEGER PRIMARY KEY); + CREATE INDEX edge_from_to ON edge(node_from,node_to); + CREATE INDEX edge_to_from ON edge(node_to,node_from); + ANALYZE; + DELETE FROM sqlite_stat1; + INSERT INTO sqlite_stat1 VALUES + ('sub_nodes',NULL,'1000000'), + ('edge','edge_to_from','20000000 2 2'), + ('edge','edge_from_to','20000000 2 2'), + ('node',NULL,'10000000'); + ANALYZE sqlite_schema; +} {} +do_eqp_test 9.1 { +SELECT count(*) FROM edge + WHERE node_from IN sub_nodes AND node_to IN sub_nodes; +} { + QUERY PLAN + |--SEARCH edge USING COVERING INDEX edge_to_from (node_to=?) + |--USING ROWID SEARCH ON TABLE sub_nodes FOR IN-OPERATOR + `--USING ROWID SEARCH ON TABLE sub_nodes FOR IN-OPERATOR +} +# ^^^^^ the key to the above is that the index should only use a single +# term (node_to=?), not two terms (node_to=? AND node_from=). + +# dbsqlfuzz case +# +reset_db +do_execsql_test 10.0 { + CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,b,c)) WITHOUT ROWID; + INSERT INTO t1(a,b,c,d) VALUES + (0,-2,2,3), + (0,2,3,4), + (0,5,8,10), + (1,7,11,13); + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t1','t1','10 3 2 1'); + ANALYZE sqlite_schema; + PRAGMA reverse_unordered_selects(1); + SELECT d FROM t1 WHERE 0=a AND b IN (-17,-4,-3,1,5,25,7798); +} {10} + +# 2021-06-13 dbsqlfuzz e41762333a4d6e90a49e628f488d0873b2dba4c5 +# The opcode that preceeds OP_SeekScan is usually OP_IdxGT, but can +# sometimes be OP_IdxGE +# +reset_db +do_execsql_test 11.0 { + CREATE TABLE t1(a TEXT, b INT, c INT, d INT); + INSERT INTO t1 VALUES('abc',123,4,5); + INSERT INTO t1 VALUES('xyz',1,'abcdefxyz',99); + CREATE INDEX t1abc ON t1(b,b,c); + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t1','t1abc','10000 5 00 2003 10'); + ANALYZE sqlite_schema; +} {} +do_execsql_test 11.1 { + SELECT * FROM t1 + WHERE b IN (345, (SELECT 1 FROM t1 + WHERE b IN (345 NOT GLOB 510) + AND c GLOB 'abc*xyz')) + AND c BETWEEN 'abc' AND 'xyz'; +} {xyz 1 abcdefxyz 99} +do_execsql_test 11.2 { + EXPLAIN SELECT * FROM t1 + WHERE b IN (345, (SELECT 1 FROM t1 + WHERE b IN (345 NOT GLOB 510) + AND c GLOB 'abc*xyz')) + AND c BETWEEN 'abc' AND 'xyz'; +} {/ SeekScan /} + finish_test diff --git a/test/in6.test b/test/in6.test index 3719a6cbf..f477f60ef 100644 --- a/test/in6.test +++ b/test/in6.test @@ -97,4 +97,23 @@ do_execsql_test in6-3.110 { SELECT quote(v5) FROM v0 LEFT JOIN v3 ON v4 = NULL AND v5 IN(0); } {NULL} +# 2021-04-29 forum https://sqlite.org/forum/forumpost/6a3ec138e9 +# An early OP_IsNull bypass might skip over the OP_Affinity and +# cause the OP_IfNoHope to jump on a false-positive, resulting in +# incomplete output. +# +reset_db +do_execsql_test in6-3.120 { + CREATE TABLE t1(a TEXT, b TEXT); + INSERT INTO t1 VALUES(null,10),(0,10),(10,10); + CREATE INDEX t1ab ON t1(a,b); + SELECT quote(a), quote(b), '|' FROM t1 WHERE b in (SELECT a FROM t1) AND a=0; +} {'0' '10' |} +do_execsql_test in6-3.130 { + CREATE TABLE t2(x TEXT); + INSERT INTO t2(x) VALUES(NULL),(0),(10); + SELECT quote(x), quote(a), quote(b), 'x' + FROM t2 LEFT JOIN t1 ON a=x AND b in (null,0,10); +} {NULL NULL NULL x '0' '0' '10' x '10' '10' '10' x} + finish_test diff --git a/test/incrvacuum.test b/test/incrvacuum.test index d2b505efe..d06005c1f 100644 --- a/test/incrvacuum.test +++ b/test/incrvacuum.test @@ -832,5 +832,61 @@ ifcapable mmap { set res } {1 2 3 4} } - + +# 2021-04-05 dbsqlfuzz cced0668cfd4da4eb2382cb9dd26c17c64aaff76 +# +# This is an incremental vacuum database that has one free page that +# needs to be filled. After removing the last page from the end of +# the database file to fill the free page slot, the last page that +# is left is the tail of an overflow chain. +# +# But the size of the database file is shorter than the actual data +# so that after incremental vacuum runs, the file is actually too +# small to hold the last page of the overflow chain. +# +# At one point this caused an assertion fault in +# sqlite3PagerTruncateImage(). +# +do_test incrvacuum-17.0 { + sqlite3 db {} + database_may_be_corrupt + db deserialize [decode_hexdb { +| size 20480 pagesize 4096 filename x2.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 05 00 00 00 07 .....@ ........ +| 32: 00 00 00 04 00 00 00 01 00 00 00 03 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 00 ................ +| 64: 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................ +| 96: 00 2e 53 60 0d 0f dc 00 01 0f b8 00 0f b8 0f b8 ..S`............ +| 4016: 00 00 00 00 00 00 00 00 22 02 06 17 11 11 01 31 ...............1 +| 4032: 74 61 62 6c 65 74 32 74 32 03 43 52 45 41 54 45 tablet2t2.CREATE +| 4048: 20 54 41 42 4c 45 20 74 32 28 79 29 00 00 00 24 TABLE t2(y)...$ +| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 03 43 52 ...1tablet1t1.CR +| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x) +| page 2 offset 4096 +| 0: 01 00 00 00 00 02 00 00 00 00 03 00 00 00 03 04 ................ +| 16: 00 00 00 05 03 00 00 00 03 00 00 00 00 00 00 00 ................ +| page 3 offset 8192 +| 0: 0d 00 00 00 02 05 47 00 08 dd 05 47 00 00 00 00 ......G....G.... +| 1344: 00 00 00 00 00 00 00 a7 0b 02 03 ce 1c 00 00 00 ................ +| 2256: 00 00 00 00 00 00 00 00 00 00 00 00 07 ce 14 01 ................ +| 2272: 04 81 9c 2c 00 00 00 00 00 00 00 00 00 00 00 00 ...,............ +| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................ +| page 4 offset 12288 +| 0: 00 00 00 00 00 00 00 00 08 dd 05 47 00 00 00 00 ...........G.... +| 1344: 00 00 00 00 00 00 00 a7 0b 02 03 ce 1c 00 00 00 ................ +| 2256: 00 00 00 00 00 00 00 00 00 00 00 00 07 ce 14 01 ................ +| 2272: 04 81 9c 2c 00 00 00 00 00 00 00 00 00 00 00 00 ...,............ +| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................ +| page 5 offset 16384 +| 0: 00 00 00 06 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| end x2.db +}]} {} +do_catchsql_test incrvacuum-17.1 { + PRAGMA writable_schema=ON; + PRAGMA incremental_vacuum(10); +} {0 {}} + finish_test diff --git a/test/index6.test b/test/index6.test index d38569258..7eed6a47e 100644 --- a/test/index6.test +++ b/test/index6.test @@ -158,7 +158,7 @@ do_test index6-2.2 { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a=5; } -} {/.* TABLE t2 USING INDEX t2a1 .*/} +} {/(SEARCH|SCAN) t2 USING INDEX t2a1 /} ifcapable stat4 { execsql ANALYZE do_test index6-2.3stat4 { @@ -166,21 +166,21 @@ ifcapable stat4 { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a IS NOT NULL; } - } {/.* TABLE t2 USING INDEX t2a1 .*/} + } {/(SEARCH|SCAN) t2 USING INDEX t2a1 /} } else { do_test index6-2.3stat4 { execsql { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a IS NOT NULL AND a>0; } - } {/.* TABLE t2 USING INDEX t2a1 .*/} + } {/(SEARCH|SCANE) t2 USING INDEX t2a1 /} } do_test index6-2.4 { execsql { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a IS NULL; } -} {~/.*INDEX t2a1.*/} +} {~/INDEX t2a1/} do_execsql_test index6-2.101 { DROP INDEX t2a1; @@ -319,8 +319,8 @@ do_eqp_test index6-8.1 { SELECT * FROM t8a LEFT JOIN t8b ON (x = 'value' AND y = a) } { QUERY PLAN - |--SCAN TABLE t8a - `--SEARCH TABLE t8b USING INDEX i8c (y=?) + |--SCAN t8a + `--SEARCH t8b USING INDEX i8c (y=?) } do_execsql_test index6-8.2 { @@ -498,4 +498,14 @@ do_execsql_test index6-17.3 { SELECT COUNT(*) FROM t0 WHERE t0.c0 GLOB t0.c0; } {1} +# 2021-05-29 +# Forum https://sqlite.org/forum/forumpost/d813704d7c +reset_db +do_execsql_test index6-18.1 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(10,10); + CREATE UNIQUE INDEX t1b ON t1(b) WHERE a>NULL; + SELECT * FROM t1 WHERE a IS NOT NULL; +} {10 10} + finish_test diff --git a/test/index7.test b/test/index7.test index 084e8c3c7..eeb36bab5 100644 --- a/test/index7.test +++ b/test/index7.test @@ -113,7 +113,7 @@ do_execsql_test index7-1.7 { do_execsql_test index7-1.7eqp { EXPLAIN QUERY PLAN SELECT b FROM t1 WHERE a NOT LIKE 'abc%' AND a=7 ORDER BY +b; -} {/SEARCH TABLE t1 USING COVERING INDEX bad1 /} +} {/SEARCH t1 USING COVERING INDEX bad1 /} do_execsql_test index7-1.8 { DELETE FROM t1 WHERE c>=101; DROP INDEX IF EXISTS bad1; @@ -202,28 +202,28 @@ do_test index7-2.2 { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a=5; } -} {/.* TABLE t2 USING COVERING INDEX t2a1 .*/} +} {/(SCAN|SEARCH) t2 USING COVERING INDEX t2a1 /} ifcapable stat4 { do_test index7-2.3stat4 { execsql { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a IS NOT NULL; } - } {/.* TABLE t2 USING COVERING INDEX t2a1 .*/} + } {/(SCAN|SEARCH) t2 USING COVERING INDEX t2a1 /} } else { do_test index7-2.3stat4 { execsql { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a IS NOT NULL AND a>0; } - } {/.* TABLE t2 USING COVERING INDEX t2a1 .*/} + } {/(SCAN|SEARCH) t2 USING COVERING INDEX t2a1 /} } do_test index7-2.4 { execsql { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a IS NULL; } -} {~/.*INDEX t2a1.*/} +} {~/INDEX t2a1/} do_execsql_test index7-2.101 { DROP INDEX t2a1; @@ -321,7 +321,7 @@ do_execsql_test index7-6.3 { } do_eqp_test index7-6.4 { SELECT * FROM v4 WHERE d='xyz' AND c='def' -} {SEARCH TABLE t4 USING INDEX i4 (c=?)} +} {SEARCH t4 USING INDEX i4 (c=?)} do_catchsql_test index7-6.5 { CREATE INDEX t5a ON t5(a) WHERE a=#1; @@ -349,7 +349,7 @@ do_execsql_test index7-8.1 { INSERT INTO t1(x) VALUES(1),(2); ANALYZE; EXPLAIN QUERY PLAN SELECT 1 FROM t1 WHERE y=5; -} {/SEARCH TABLE t1 USING COVERING INDEX t1y/} +} {/SEARCH t1 USING COVERING INDEX t1y/} finish_test diff --git a/test/index8.test b/test/index8.test index bb5822852..41ccf4bbc 100644 --- a/test/index8.test +++ b/test/index8.test @@ -41,7 +41,7 @@ do_execsql_test 1.0 { do_execsql_test 1.0eqp { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=4 ORDER BY a, b LIMIT 2; -} {/SCAN TABLE t1 USING INDEX t1abc/} +} {/SCAN t1 USING INDEX t1abc/} # If we change the index so that it no longer covers the WHERE clause, # then we should (correctly) revert to using a table scan. diff --git a/test/indexedby.test b/test/indexedby.test index 18f7bb8fa..6a371112b 100644 --- a/test/indexedby.test +++ b/test/indexedby.test @@ -42,16 +42,16 @@ proc EQP {sql} { # do_eqp_test indexedby-1.2 { select * from t1 WHERE a = 10; -} {SEARCH TABLE t1 USING INDEX i1 (a=?)} +} {SEARCH t1 USING INDEX i1 (a=?)} do_eqp_test indexedby-1.3 { select * from t1 ; -} {SCAN TABLE t1} +} {SCAN t1} do_eqp_test indexedby-1.4 { select * from t1, t2 WHERE c = 10; } { QUERY PLAN - |--SEARCH TABLE t2 USING INDEX i3 (c=?) - `--SCAN TABLE t1 + |--SEARCH t2 USING INDEX i3 (c=?) + `--SCAN t1 } # Parser tests. Test that an INDEXED BY or NOT INDEX clause can be @@ -90,9 +90,9 @@ do_test indexedby-2.4 { catchsql { SELECT * FROM t1 INDEXED BY i3 WHERE a = 'one' AND b = 'two'} } {1 {no such index: i3}} -# EVIDENCE-OF: R-62112-42456 If the query optimizer is unable to use the -# index specified by the INDEX BY clause, then the query will fail with -# an error. +# EVIDENCE-OF: R-05301-32681 If the query optimizer is unable to use the +# index specified by the INDEXED BY clause, then the query will fail +# with an error. do_test indexedby-2.4.1 { catchsql { SELECT b FROM t1 INDEXED BY i1 WHERE b = 'two' } } {0 {}} @@ -118,21 +118,21 @@ do_test indexedby-2.7 { # do_eqp_test indexedby-3.1 { SELECT * FROM t1 WHERE a = 'one' AND b = 'two' -} {/SEARCH TABLE t1 USING INDEX/} +} {/SEARCH t1 USING INDEX/} do_eqp_test indexedby-3.1.1 { SELECT * FROM t1 NOT INDEXED WHERE a = 'one' AND b = 'two' -} {SCAN TABLE t1} +} {SCAN t1} do_eqp_test indexedby-3.1.2 { SELECT * FROM t1 NOT INDEXED WHERE rowid=1 -} {/SEARCH TABLE t1 USING INTEGER PRIMARY KEY .rowid=/} +} {/SEARCH t1 USING INTEGER PRIMARY KEY .rowid=/} do_eqp_test indexedby-3.2 { SELECT * FROM t1 INDEXED BY i1 WHERE a = 'one' AND b = 'two' -} {SEARCH TABLE t1 USING INDEX i1 (a=?)} +} {SEARCH t1 USING INDEX i1 (a=?)} do_eqp_test indexedby-3.3 { SELECT * FROM t1 INDEXED BY i2 WHERE a = 'one' AND b = 'two' -} {SEARCH TABLE t1 USING INDEX i2 (b=?)} +} {SEARCH t1 USING INDEX i2 (b=?)} do_test indexedby-3.4 { catchsql { SELECT * FROM t1 INDEXED BY i2 WHERE a = 'one' } } {0 {}} @@ -148,10 +148,10 @@ do_test indexedby-3.7 { do_eqp_test indexedby-3.8 { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 ORDER BY e -} {SCAN TABLE t3 USING INDEX sqlite_autoindex_t3_1} +} {SCAN t3 USING INDEX sqlite_autoindex_t3_1} do_eqp_test indexedby-3.9 { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 WHERE e = 10 -} {SEARCH TABLE t3 USING INDEX sqlite_autoindex_t3_1 (e=?)} +} {SEARCH t3 USING INDEX sqlite_autoindex_t3_1 (e=?)} do_test indexedby-3.10 { catchsql { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 WHERE f = 10 } } {0 {}} @@ -165,15 +165,15 @@ do_eqp_test indexedby-4.1 { SELECT * FROM t1, t2 WHERE a = c } { QUERY PLAN - |--SCAN TABLE t1 - `--SEARCH TABLE t2 USING INDEX i3 (c=?) + |--SCAN t1 + `--SEARCH t2 USING INDEX i3 (c=?) } do_eqp_test indexedby-4.2 { SELECT * FROM t1 INDEXED BY i1, t2 WHERE a = c } { QUERY PLAN - |--SCAN TABLE t1 USING INDEX i1 - `--SEARCH TABLE t2 USING INDEX i3 (c=?) + |--SCAN t1 USING INDEX i1 + `--SEARCH t2 USING INDEX i3 (c=?) } do_test indexedby-4.3 { catchsql { @@ -193,10 +193,10 @@ do_test indexedby-4.4 { 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 -} {/*SEARCH TABLE t1 USING INDEX i1 (a>?)*/} +} {/*SEARCH t1 USING INDEX i1 (a>?)*/} do_execsql_test indexedby-5.2 { EXPLAIN QUERY PLAN SELECT * FROM v2 WHERE b = 10 -} {/*SEARCH TABLE t1 USING INDEX i1 (a>?)*/} +} {/*SEARCH t1 USING INDEX i1 (a>?)*/} do_test indexedby-5.3 { execsql { DROP INDEX i1 } catchsql { SELECT * FROM v2 } @@ -217,10 +217,10 @@ do_test indexedby-5.5 { # do_eqp_test indexedby-6.1 { SELECT * FROM t1 WHERE b = 10 ORDER BY rowid -} {SEARCH TABLE t1 USING INDEX i2 (b=?)} +} {SEARCH t1 USING INDEX i2 (b=?)} do_eqp_test indexedby-6.2 { SELECT * FROM t1 NOT INDEXED WHERE b = 10 ORDER BY rowid -} {SCAN TABLE t1} +} {SCAN t1} # EVIDENCE-OF: R-40297-14464 The INDEXED BY phrase forces the SQLite # query planner to use a particular named index on a DELETE, SELECT, or @@ -230,19 +230,19 @@ do_eqp_test indexedby-6.2 { # do_eqp_test indexedby-7.1 { DELETE FROM t1 WHERE a = 5 -} {SEARCH TABLE t1 USING INDEX i1 (a=?)} +} {SEARCH t1 USING INDEX i1 (a=?)} do_eqp_test indexedby-7.2 { DELETE FROM t1 NOT INDEXED WHERE a = 5 -} {SCAN TABLE t1} +} {SCAN t1} do_eqp_test indexedby-7.3 { DELETE FROM t1 INDEXED BY i1 WHERE a = 5 -} {SEARCH TABLE t1 USING INDEX i1 (a=?)} +} {SEARCH t1 USING INDEX i1 (a=?)} do_eqp_test indexedby-7.4 { DELETE FROM t1 INDEXED BY i1 WHERE a = 5 AND b = 10 -} {SEARCH TABLE t1 USING INDEX i1 (a=?)} +} {SEARCH t1 USING INDEX i1 (a=?)} do_eqp_test indexedby-7.5 { DELETE FROM t1 INDEXED BY i2 WHERE a = 5 AND b = 10 -} {SEARCH TABLE t1 USING INDEX i2 (b=?)} +} {SEARCH t1 USING INDEX i2 (b=?)} do_test indexedby-7.6 { catchsql { DELETE FROM t1 INDEXED BY i2 WHERE a = 5} } {0 {}} @@ -251,19 +251,19 @@ do_test indexedby-7.6 { # do_eqp_test indexedby-8.1 { UPDATE t1 SET rowid=rowid+1 WHERE a = 5 -} {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)} +} {SEARCH t1 USING COVERING INDEX i1 (a=?)} do_eqp_test indexedby-8.2 { UPDATE t1 NOT INDEXED SET rowid=rowid+1 WHERE a = 5 -} {SCAN TABLE t1} +} {SCAN t1} do_eqp_test indexedby-8.3 { UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5 -} {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)} +} {SEARCH t1 USING COVERING INDEX i1 (a=?)} do_eqp_test indexedby-8.4 { UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5 AND b = 10 -} {SEARCH TABLE t1 USING INDEX i1 (a=?)} +} {SEARCH t1 USING INDEX i1 (a=?)} do_eqp_test indexedby-8.5 { UPDATE t1 INDEXED BY i2 SET rowid=rowid+1 WHERE a = 5 AND b = 10 -} {SEARCH TABLE t1 USING INDEX i2 (b=?)} +} {SEARCH t1 USING INDEX i2 (b=?)} do_test indexedby-8.6 { catchsql { UPDATE t1 INDEXED BY i2 SET rowid=rowid+1 WHERE a = 5} } {0 {}} @@ -338,7 +338,7 @@ do_execsql_test 11.4 { } {1 1 3} do_eqp_test 11.5 { SELECT a,b,rowid FROM x1 INDEXED BY x1i WHERE a=1 AND b=1 AND rowid='3.0'; -} {SEARCH TABLE x1 USING COVERING INDEX x1i (a=? AND b=? AND rowid=?)} +} {SEARCH x1 USING COVERING INDEX x1i (a=? AND b=? AND rowid=?)} do_execsql_test 11.6 { CREATE TABLE x2(c INTEGER PRIMARY KEY, a, b TEXT); @@ -359,7 +359,7 @@ do_execsql_test 11.9 { } {1 1 3} do_eqp_test 11.10 { SELECT a,b,c FROM x2 INDEXED BY x2i WHERE a=1 AND b=1 AND c='3.0'; -} {SEARCH TABLE x2 USING COVERING INDEX x2i (a=? AND b=? AND rowid=?)} +} {SEARCH x2 USING COVERING INDEX x2i (a=? AND b=? AND rowid=?)} #------------------------------------------------------------------------- # Check INDEXED BY works (throws an exception) with partial indexes that diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 19c257350..a31b87695 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -97,14 +97,14 @@ do_execsql_test indexexpr1-170 { do_execsql_test indexexpr1-170eqp { EXPLAIN QUERY PLAN SELECT length(a) FROM t1 ORDER BY length(a); -} {/SCAN TABLE t1 USING INDEX t1alen/} +} {/SCAN t1 USING INDEX t1alen/} do_execsql_test indexexpr1-171 { SELECT length(a) FROM t1 ORDER BY length(a) DESC; } {52 38 29 27 25 20} do_execsql_test indexexpr1-171eqp { EXPLAIN QUERY PLAN SELECT length(a) FROM t1 ORDER BY length(a) DESC; -} {/SCAN TABLE t1 USING INDEX t1alen/} +} {/SCAN t1 USING INDEX t1alen/} do_execsql_test indexexpr1-200 { DROP TABLE t1; diff --git a/test/indexexpr2.test b/test/indexexpr2.test index 35caef3c2..8c1171e03 100644 --- a/test/indexexpr2.test +++ b/test/indexexpr2.test @@ -93,7 +93,7 @@ ifcapable json1 { ORDER BY json_extract(x, '$.b') COLLATE nocase; } [string map {"\n " \n} { QUERY PLAN - |--SCAN TABLE t2 + |--SCAN t2 `--USE TEMP B-TREE FOR GROUP BY }] @@ -108,7 +108,7 @@ ifcapable json1 { ORDER BY json_extract(x, '$.b') COLLATE nocase; } [string map {"\n " \n} { QUERY PLAN - |--SEARCH TABLE t3 USING INDEX i3 (=?) + |--SEARCH t3 USING INDEX i3 (=?) `--USE TEMP B-TREE FOR GROUP BY }] } @@ -154,7 +154,7 @@ do_execsql_test 3.4.5 { do_execsql_test 3.4.5eqp { EXPLAIN QUERY PLAN SELECT * FROM t4 ORDER BY Substr(a,-2) COLLATE nocase; -} {/SCAN TABLE t4 USING INDEX i4/} +} {/SCAN t4 USING INDEX i4/} do_execsql_test 3.4.6 { SELECT * FROM t4 ORDER BY Substr(a,-2) COLLATE binary; } {.ABC1 1 .ABC3 3 .abc2 2 .abc4 4} @@ -265,7 +265,7 @@ do_execsql_test 6.1.2 { } {1 123 2 123 3 123abc 4 123.0} do_eqp_test 6.1.3 { SELECT a, b FROM x1 WHERE CAST(b AS INTEGER) = 123; -} {SEARCH TABLE x1 USING INDEX x1i (=?)} +} {SEARCH x1 USING INDEX x1i (=?)} do_execsql_test 6.2.1 { SELECT a, b FROM x1 WHERE CAST(b AS TEXT) = 123; @@ -276,7 +276,7 @@ do_execsql_test 6.2.2 { } {1 123 2 123} do_eqp_test 6.2.3 { SELECT a, b FROM x1 WHERE CAST(b AS TEXT) = 123; -} {SEARCH TABLE x1 USING INDEX x1i2 (=?)} +} {SEARCH x1 USING INDEX x1i2 (=?)} do_execsql_test 7.0 { CREATE TABLE IF NOT EXISTS t0(c0); diff --git a/test/intpkey.test b/test/intpkey.test index a56cab4c6..d6b8833a2 100644 --- a/test/intpkey.test +++ b/test/intpkey.test @@ -129,7 +129,7 @@ do_test intpkey-1.12.2 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a==4; } -} {/SEARCH TABLE t1 /} +} {/SEARCH t1 /} # Try to insert a non-integer value into the primary key field. This # should result in a data type mismatch. diff --git a/test/join2.test b/test/join2.test index f004fda07..170000ca2 100644 --- a/test/join2.test +++ b/test/join2.test @@ -113,16 +113,16 @@ do_eqp_test 3.1 { SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_1 USING (k3); } { QUERY PLAN - |--SCAN TABLE t1 - `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t1 + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) } do_eqp_test 3.2 { SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_2 USING (k3); } { QUERY PLAN - |--SCAN TABLE t1 - `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t1 + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) } #------------------------------------------------------------------------- @@ -161,16 +161,16 @@ do_eqp_test 4.1.5 { SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2); } { QUERY PLAN - |--SCAN TABLE c1 - |--SEARCH TABLE c2 USING INTEGER PRIMARY KEY (rowid=?) - `--SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN c1 + |--SEARCH c2 USING INTEGER PRIMARY KEY (rowid=?) + `--SEARCH c3 USING INTEGER PRIMARY KEY (rowid=?) } do_eqp_test 4.1.6 { SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); } { QUERY PLAN - |--SCAN TABLE c1 - `--SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN c1 + `--SEARCH c3 USING INTEGER PRIMARY KEY (rowid=?) } do_execsql_test 4.2.0 { @@ -208,16 +208,16 @@ do_eqp_test 4.2.5 { SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2); } { QUERY PLAN - |--SCAN TABLE c1 - |--SEARCH TABLE c2 USING INDEX sqlite_autoindex_c2_1 (k=?) - `--SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?) + |--SCAN c1 + |--SEARCH c2 USING INDEX sqlite_autoindex_c2_1 (k=?) + `--SEARCH c3 USING INDEX sqlite_autoindex_c3_1 (k=?) } do_eqp_test 4.2.6 { SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); } { QUERY PLAN - |--SCAN TABLE c1 - `--SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?) + |--SCAN c1 + `--SEARCH c3 USING INDEX sqlite_autoindex_c3_1 (k=?) } # 2017-11-23 (Thanksgiving day) @@ -251,11 +251,11 @@ do_execsql_test 5.0 { } do_eqp_test 5.1 { SELECT s1.a FROM s1 left join s2 using (a); -} {SCAN TABLE s1} +} {SCAN s1} do_eqp_test 5.2 { SELECT s1.a FROM s1 left join s3 using (a); -} {SCAN TABLE s1} +} {SCAN s1} do_execsql_test 6.0 { CREATE TABLE u1(a INTEGER PRIMARY KEY, b, c); @@ -264,7 +264,7 @@ do_execsql_test 6.0 { } do_eqp_test 6.1 { SELECT u2.* FROM u2 LEFT JOIN u1 ON( u1.a=u2.a AND u1.b=u2.b AND u1.c=u2.c ); -} {SCAN TABLE u2} +} {SCAN u2} db close sqlite3 db :memory: diff --git a/test/join5.test b/test/join5.test index 6724419e6..e2ff2f6c2 100644 --- a/test/join5.test +++ b/test/join5.test @@ -277,12 +277,12 @@ do_eqp_test 7.2 { ); } { QUERY PLAN - |--SCAN TABLE t1 + |--SCAN t1 `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t2 USING INDEX t2xy (x=? AND y=?) + | `--SEARCH t2 USING INDEX t2xy (x=? AND y=?) `--INDEX 2 - `--SEARCH TABLE t2 USING INDEX t2xy (x=? AND y=?) + `--SEARCH t2 USING INDEX t2xy (x=? AND y=?) } do_execsql_test 7.3 { @@ -302,8 +302,8 @@ do_eqp_test 7.4 { SELECT * FROM t3 LEFT JOIN t4 ON (t4.x = t3.x) WHERE (t4.y = ? OR t4.z = ?); } { QUERY PLAN - |--SCAN TABLE t3 - `--SEARCH TABLE t4 USING INDEX t4xz (x=?) + |--SCAN t3 + `--SEARCH t4 USING INDEX t4xz (x=?) } reset_db diff --git a/test/like.test b/test/like.test index c29ebb267..e8662dc6c 100644 --- a/test/like.test +++ b/test/like.test @@ -168,13 +168,13 @@ proc queryplan {sql} { set eqp [execsql "EXPLAIN QUERY PLAN $sql"] # puts eqp=$eqp foreach {a b c x} $eqp { - if {[regexp { TABLE (\w+ AS )?(\w+) USING COVERING INDEX (\w+)\y} \ - $x all as tab idx]} { + if {[regexp {(SCAN|SEARCH) (\w+ AS )?(\w+) USING COVERING INDEX (\w+)\y} \ + $x all ss as tab idx]} { lappend data {} $idx - } elseif {[regexp { TABLE (\w+ AS )?(\w+) USING.* INDEX (\w+)\y} \ - $x all as tab idx]} { + } elseif {[regexp {(SCAN|SEARCH) (\w+ AS )?(\w+) USING.* INDEX (\w+)\y} \ + $x all ss as tab idx]} { lappend data $tab $idx - } elseif {[regexp { TABLE (\w+ AS )?(\w+)\y} $x all as tab]} { + } elseif {[regexp {(SCAN|SEARCH) (\w+ AS )?(\w+)\y} $x all ss as tab]} { lappend data $tab * } } @@ -726,7 +726,7 @@ ifcapable like_opt&&!icu { set res [sqlite3_exec_hex db { EXPLAIN QUERY PLAN SELECT x FROM t2 WHERE x LIKE '%ff%25' }] - regexp {SCAN TABLE t2} $res + regexp {SCAN t2} $res } {1} } do_test like-9.5.1 { @@ -1036,16 +1036,18 @@ do_test like-14.1 { set x [lindex [time { db one {SELECT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz'GLOB'*a*a*a*a*a*a*a*a*y'} }] 0] - puts -nonewline " ($x ms - want less than 1000) " - expr {$x<1000} + set tlimit [expr {1000 * $::sqlite_options(configslower)}] + puts -nonewline " ($x ms - want less than $tlimit) " + expr {$x<$tlimit} } {1} ifcapable !icu { do_test like-14.2 { set x [lindex [time { db one {SELECT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz'LIKE'%a%a%a%a%a%a%a%a%y'} }] 0] - puts -nonewline " ($x ms - want less than 1000) " - expr {$x<1000} + set tlimit [expr {1000 * $::sqlite_options(configslower)}] + puts -nonewline " ($x ms - want less than $tlimit) " + expr {$x<$tlimit} } {1} } @@ -1131,4 +1133,11 @@ do_execsql_test 17.1 { SELECT id FROM t1 WHERE x LIKE 'abc__' ESCAPE '_'; } {2} +# 2021-02-15 ticket c0aeea67d58ae0fd +# +do_execsql_test 17.1 { + SELECT 'x' LIKE '%' ESCAPE '_'; +} {1} + + finish_test diff --git a/test/like3.test b/test/like3.test index 3bfe30c31..a93e113d6 100644 --- a/test/like3.test +++ b/test/like3.test @@ -125,7 +125,7 @@ do_eqp_test like3-5.101 { SELECT x FROM t5a WHERE x LIKE '/%'; } { QUERY PLAN - `--SCAN TABLE t5a + `--SCAN t5a } do_execsql_test like3-5.110 { SELECT x FROM t5a WHERE x LIKE '/a%'; @@ -135,7 +135,7 @@ do_eqp_test like3-5.111 { SELECT x FROM t5a WHERE x LIKE '/a%'; } { QUERY PLAN - `--SEARCH TABLE t5a USING COVERING INDEX sqlite_autoindex_t5a_1 (x>? AND x? AND x? AND x? AND x? AND path? AND path? AND path? AND path? AND path? AND path? AND path? AND path? AND path? AND path? AND path? AND path? AND b? AND b$nstep} { + error \"got $vmstep, expected less than $nstep\" + }" + } + + # set name "$tn.vmstep=$vmstep,expect=$nstep" + set name "$tn.1" + uplevel [list do_test $name $body {}] +} + +do_vmstep_test 1.1.1 { + SELECT * FROM t1 LEFT JOIN t2 WHERE a=c AND d IS NULL; +} 100 {} +do_vmstep_test 1.1.2 { + SELECT * FROM t1 LEFT JOIN t2 WHERE a=c AND c IS NULL; +} +1000 {} + +do_vmstep_test 1.2.1 { + SELECT * FROM ( SELECT * FROM t2 ) WHERE d IS NULL +} 100 {} +do_vmstep_test 1.2.2 { + SELECT * FROM ( SELECT * FROM t2 ) WHERE c IS NULL +} +1000 {} + +do_vmstep_test 1.3.1 { + SELECT * FROM t2 WHERE d IS NULL +} 100 {} +do_vmstep_test 1.3.2 { + SELECT * FROM t2 WHERE c IS NULL +} +1000 {} + +do_vmstep_test 1.4.1 { + SELECT (d IS NOT NULL) FROM t2 WHERE 0==( d IS NOT NULL ) +} 100 {} +do_vmstep_test 1.4.2 { + SELECT * FROM t2 WHERE 0==( c IS NOT NULL ) +} +1000 {} + +do_vmstep_test 1.5.1 { + SELECT count(*) FROM t2 WHERE EXISTS( + SELECT t2.d IS NULL FROM t1 WHERE t1.a=450 + ) +} 10000 {1000} +do_vmstep_test 1.5.2 { + SELECT count(*) FROM t2 WHERE EXISTS( + SELECT t2.c IS NULL FROM t1 WHERE t1.a=450 + ) +} +100000 {1000} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE T1(a INTEGER PRIMARY KEY, b); + CREATE TABLE T3(k, v); +} + +do_execsql_test 2.1 { + SELECT * FROM (SELECT a, b FROM t1) LEFT JOIN t3 ON a IS NULL; +} + + + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t0(c0 PRIMARY KEY); + INSERT INTO t0(c0) VALUES (0); +} +do_execsql_test 3.1 { + SELECT * FROM t0 WHERE ((c0 NOT NULL) AND 1) OR (c0 == NULL); +} {0} + +finish_test diff --git a/test/notnullfault.test b/test/notnullfault.test new file mode 100644 index 000000000..0db8e4adb --- /dev/null +++ b/test/notnullfault.test @@ -0,0 +1,55 @@ +# 2021 February 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. The +# focus of this file is testing optimizations associated with "IS NULL" +# and "IS NOT NULL" operators on columns with NOT NULL constraints. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix notnullfault + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, d NOT NULL); +} +faultsim_save_and_close + +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen +} -body { + execsql { + SELECT * FROM t2 WHERE d NOT NULL + } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a, b, c); + CREATE TABLE t2(a, b, c, PRIMARY KEY(a, b, c)) WITHOUT ROWID; +} +faultsim_save_and_close + +do_faultsim_test 2.1 -faults oom-t* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + SELECT dense_rank() OVER win FROM t2 + WINDOW win AS (ORDER BY c IS NULL) + } +} -test { + faultsim_test_result {0 {}} +} + +finish_test diff --git a/test/nulls1.test b/test/nulls1.test index b794f35b0..c44d0af25 100644 --- a/test/nulls1.test +++ b/test/nulls1.test @@ -184,7 +184,7 @@ do_eqp_test 5.3 { SELECT * FROM t4 WHERE a IN (1, 2, 3) ORDER BY a, b NULLS LAST } { QUERY PLAN - `--SEARCH TABLE t4 USING INDEX t4ab (a=?) + `--SEARCH t4 USING INDEX t4ab (a=?) } do_execsql_test 5.4 { @@ -198,7 +198,7 @@ do_eqp_test 5.5 { SELECT * FROM t4 WHERE a IN (1, 2, 3) ORDER BY a DESC, b DESC NULLS FIRST } { QUERY PLAN - `--SEARCH TABLE t4 USING INDEX t4ab (a=?) + `--SEARCH t4 USING INDEX t4ab (a=?) } #------------------------------------------------------------------------- @@ -224,7 +224,7 @@ do_eqp_test 6.1.2 { SELECT a,b FROM t5 WHERE a=1 ORDER BY b NULLS LAST, c; } { QUERY PLAN - `--SEARCH TABLE t5 USING COVERING INDEX t5ab (a=?) + `--SEARCH t5 USING COVERING INDEX t5ab (a=?) } do_execsql_test 6.2.1 { SELECT a,b FROM t5 WHERE a=1 ORDER BY b DESC NULLS FIRST, c DESC @@ -233,7 +233,7 @@ do_eqp_test 6.2.2 { SELECT a,b FROM t5 WHERE a=1 ORDER BY b DESC NULLS FIRST, c DESC } { QUERY PLAN - `--SEARCH TABLE t5 USING COVERING INDEX t5ab (a=?) + `--SEARCH t5 USING COVERING INDEX t5ab (a=?) } #------------------------------------------------------------------------- @@ -294,7 +294,7 @@ do_execsql_test 9.3 { do_eqp_test 9.4 { SELECT c1, c2, ifnull(c3, 'NULL') FROM v0 WHERE c2=10 ORDER BY c1, c3 NULLS LAST -} {SEARCH TABLE v0 USING COVERING INDEX v3 (ANY(c1) AND c2=?)} +} {SEARCH v0 USING COVERING INDEX v3 (ANY(c1) AND c2=?)} # 2020-03-01 ticket e12a0ae526bb51c7 diff --git a/test/optfuzz.c b/test/optfuzz.c index 1992acac3..2acb0ce35 100644 --- a/test/optfuzz.c +++ b/test/optfuzz.c @@ -26,7 +26,6 @@ */ #define SQLITE_THREADSAFE 0 #define SQLITE_OMIT_LOAD_EXTENSION 1 -#define SQLITE_ENABLE_DESERIALIZE 1 #include "sqlite3.c" /* Content of the read-only test database */ diff --git a/test/orderby1.test b/test/orderby1.test index 5152ffaf8..9b5df4c75 100644 --- a/test/orderby1.test +++ b/test/orderby1.test @@ -516,7 +516,7 @@ do_eqp_test 8.1 { SELECT * FROM t1 ORDER BY a, b; } { QUERY PLAN - |--SCAN TABLE t1 USING INDEX i1 + |--SCAN t1 USING INDEX i1 `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY } diff --git a/test/pager1.test b/test/pager1.test index 5c3f7a8ea..a1e06d15c 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -1764,6 +1764,12 @@ tv delete #------------------------------------------------------------------------- # Test specal "PRAGMA journal_mode=OFF" test cases. # +# Do not run these tests for SQLITE_ENABLE_ZIPVFS builds. Such builds +# cause the pager to enter the error state if a statement transaction +# cannot be rolled back due to a prior "PRAGMA journal_mode=OFF". Which +# causes these tests to fail. +# +if {[info commands zip_register]==""} { faultsim_delete_and_reopen do_execsql_test pager1-14.1.1 { PRAGMA journal_mode = OFF; @@ -1788,8 +1794,11 @@ do_catchsql_test pager1-14.1.4 { } {1 {UNIQUE constraint failed: t1.rowid}} do_execsql_test pager1-14.1.5 { COMMIT; +} +do_execsql_test pager1-14.1.6 { SELECT * FROM t1; } {1 2 2 2} +} #------------------------------------------------------------------------- # Test opening and closing the pager sub-system with different values diff --git a/test/permutations.test b/test/permutations.test index 4ea6cd280..b652f8571 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -131,6 +131,7 @@ set allquicktests [test_set $alltests -exclude { fts3defer.test fts4langid.test fts3sort.test fts5unicode.test rtree4.test + sessionbig.test }] if {[info exists ::env(QUICKTEST_INCLUDE)]} { set allquicktests [concat $allquicktests $::env(QUICKTEST_INCLUDE)] @@ -989,7 +990,7 @@ test_suite "journaltest" -description { pager1.test syscall.test tkt3457.test *malloc* mmap* multiplex* nolock* pager2.test *fault* rowal* snapshot* superlock* symlink.test delete_db.test shmlock.test chunksize.test - busy2.test + busy2.test avfs.test external_reader.test }] if {[info commands register_demovfs] != ""} { @@ -1062,7 +1063,8 @@ test_suite "no_optimization" -description { 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 + select7.test select8.test selectA.test selectC.test \ + -exclude windowpushd.test ] -dbconfig { optimization_control $::dbhandle all 0 } @@ -1137,11 +1139,20 @@ proc run_tests {name args} { set ::G(perm:presql) $options(-presql) foreach file [lsort $options(-files)] { - uplevel $options(-initialize) if {[file tail $file] == $file} { set file [file join $::testdir $file] } + + if {[info exists ::env(SQLITE_TEST_PATTERN_LIST)]} { + set ok 0 + foreach p $::env(SQLITE_TEST_PATTERN_LIST) { + set p [string map {% *} $p] + if {[string match $p [file tail $file]]} {set ok 1 ; break} + } + if {!$ok} continue + } + + uplevel $options(-initialize) slave_test_file $file uplevel $options(-shutdown) - unset -nocomplain ::G(perm:sqlite3_args) } diff --git a/test/quote.test b/test/quote.test index 553e96a0b..212885c05 100644 --- a/test/quote.test +++ b/test/quote.test @@ -110,7 +110,7 @@ do_execsql_test 2.2 { PRAGMA writable_schema = 1; CREATE TABLE xyz(a, b, c CHECK (c!="null") ); CREATE INDEX i2 ON t1(x, y, z||"abc"); - CREATE INDEX i3 ON t1("w"); + CREATE INDEX i3 ON t1("w"||""); CREATE INDEX i4 ON t1(x) WHERE z="w"; } @@ -135,10 +135,49 @@ do_execsql_test 2.5 { {CREATE TABLE t1(x, y, z)} {CREATE TABLE xyz(a, b, c CHECK (c!="null") )} {CREATE INDEX i2 ON t1(x, y, z||"abc")} - {CREATE INDEX i3 ON t1("w")} + {CREATE INDEX i3 ON t1("w"||"")} {CREATE INDEX i4 ON t1(x) WHERE z="w"} } - +# 2021-03-13 +# ticket 1c24a659e6d7f3a1 +reset_db +do_catchsql_test 3.0 { + CREATE TABLE t1(a,b); + CREATE INDEX x1 on t1("b"); + ALTER TABLE t1 DROP COLUMN b; +} {1 {error in index x1 after drop column: no such column: b}} +do_catchsql_test 3.1 { + DROP TABLE t1; + CREATE TABLE t1(a,"b"); + CREATE INDEX x1 on t1("b"); + ALTER TABLE t1 DROP COLUMN b; +} {1 {error in index x1 after drop column: no such column: b}} +do_catchsql_test 3.2 { + DROP TABLE t1; + CREATE TABLE t1(a,'b'); + CREATE INDEX x1 on t1("b"); + ALTER TABLE t1 DROP COLUMN b; +} {1 {error in index x1 after drop column: no such column: b}} +do_catchsql_test 3.3 { + DROP TABLE t1; + CREATE TABLE t1(a,"b"); + CREATE INDEX x1 on t1('b'); + ALTER TABLE t1 DROP COLUMN b; +} {1 {error in index x1 after drop column: no such column: b}} +do_catchsql_test 3.4 { + DROP TABLE t1; + CREATE TABLE t1(a, b, c); + CREATE INDEX x1 ON t1("a"||"b"); + INSERT INTO t1 VALUES(1,2,3),(1,4,5); + ALTER TABLE t1 DROP COLUMN b; +} {1 {error in index x1 after drop column: no such column: b}} +do_catchsql_test 3.5 { + DROP TABLE t1; + CREATE TABLE t1(a, b, c); + CREATE INDEX x1 ON t1("a"||"x"); + INSERT INTO t1 VALUES(1,2,3),(1,4,5); + ALTER TABLE t1 DROP COLUMN b; +} {0 {}} finish_test diff --git a/test/regexp1.test b/test/regexp1.test index 0e63cd98c..1eb56c672 100644 --- a/test/regexp1.test +++ b/test/regexp1.test @@ -28,18 +28,49 @@ do_test regexp1-1.1 { } } {1 3} +do_execsql_test regexp1-1.1.2 { + SELECT regexpi('abc','ABC'); +} {1} +do_execsql_test regexp1-1.1.3 { + SELECT regexpi('ABC','ABC'); +} {1} +do_execsql_test regexp1-1.1.4 { + SELECT regexpi('ABC','abc'); +} {1} +do_execsql_test regexp1-1.1.5 { + SELECT regexpi('ABC.','ABC'); +} {0} + do_execsql_test regexp1-1.2 { SELECT x FROM t1 WHERE y REGEXP 'by|in' ORDER BY x; } {1 2 3 4} -do_execsql_test regexp1-1.3 { +do_execsql_test regexp1-1.3.1 { SELECT x FROM t1 WHERE y REGEXP 'by|Christ' ORDER BY x; } {1 2 4} +do_execsql_test regexp1-1.3.2 { + SELECT x FROM t1 WHERE regexp('by|christ',y) ORDER BY x; +} {1 2} +do_execsql_test regexp1-1.3.3 { + SELECT x FROM t1 WHERE regexpi('by|christ',y) ORDER BY x; +} {1 2 4} +do_execsql_test regexp1-1.3.4 { + SELECT x FROM t1 WHERE regexpi('BY|CHRIST',y) ORDER BY x; +} {1 2 4} do_execsql_test regexp1-1.4 { SELECT x FROM t1 WHERE y REGEXP 'shal+ al+' ORDER BY x; } {4} -do_execsql_test regexp1-1.5 { +do_execsql_test regexp1-1.5.1 { SELECT x FROM t1 WHERE y REGEXP 'shall x*y*z*all' ORDER BY x; } {4} +do_execsql_test regexp1-1.5.2 { + SELECT x FROM t1 WHERE regexp('shall x*y*z*all',y) ORDER BY x; +} {4} +do_execsql_test regexp1-1.5.3 { + SELECT x FROM t1 WHERE regexp('SHALL x*y*z*all',y) ORDER BY x; +} {} +do_execsql_test regexp1-1.5.4 { + SELECT x FROM t1 WHERE regexpi('SHALL x*y*z*all',y) ORDER BY x; +} {4} do_execsql_test regexp1-1.6 { SELECT x FROM t1 WHERE y REGEXP 'shallx?y? ?z?all' ORDER BY x; } {4} diff --git a/test/regexp2.test b/test/regexp2.test index e3922042c..3e1da9f23 100644 --- a/test/regexp2.test +++ b/test/regexp2.test @@ -120,5 +120,25 @@ do_execsql_test 3.0 { SELECT * FROM t6; } {eab dea} +# 2021-06-04 Forum https://sqlite.org/forum/forumpost/9104f0d9e7 +# +do_execsql_test 4.1 {SELECT 'abc' REGEXP '\W'} {0} +do_execsql_test 4.2 {SELECT 'a c' REGEXP '\W'} {1} +do_execsql_test 4.3 {SELECT ' ' REGEXP '\W'} {1} +do_execsql_test 4.4 {SELECT 'abc' REGEXP '\w'} {1} +do_execsql_test 4.5 {SELECT 'a c' REGEXP '\w'} {1} +do_execsql_test 4.6 {SELECT ' ' REGEXP '\w'} {0} +do_execsql_test 4.7 {SELECT 'abc' REGEXP '\D'} {1} +do_execsql_test 4.8 {SELECT 'abc' REGEXP '[^a-z]'} {0} +do_execsql_test 4.9 {SELECT 'a c' REGEXP '[^a-z]'} {1} +do_execsql_test 4.10 {SELECT ' ' REGEXP '[^a-z]'} {1} +do_execsql_test 4.11 {SELECT 'abc' REGEXP '[a-z]'} {1} +do_execsql_test 4.12 {SELECT 'a c' REGEXP '[a-z]'} {1} +do_execsql_test 4.13 {SELECT ' ' REGEXP '[a-z]'} {0} +do_execsql_test 4.14 {SELECT 'abc' REGEXP '[^a-z]{2}'} {0} +do_execsql_test 4.15 {SELECT 'a c' REGEXP '[^a-z]{2}'} {0} +do_execsql_test 4.16 {SELECT ' ' REGEXP '[^a-z]{2}'} {1} +do_execsql_test 4.17 {SELECT 'abc' REGEXP '\W{1,1}'} {0} +do_execsql_test 4.18 {SELECT 'abc' REGEXP '\W{1}'} {0} finish_test diff --git a/test/releasetest.tcl b/test/releasetest.tcl index 37f00eb4c..da6113a19 100755 --- a/test/releasetest.tcl +++ b/test/releasetest.tcl @@ -50,11 +50,11 @@ array set ::Configs [strip_comments { -O2 --disable-amalgamation --disable-shared --enable-session - -DSQLITE_ENABLE_DESERIALIZE } "Sanitize" { CC=clang -fsanitize=undefined -DSQLITE_ENABLE_STAT4 + -DCONFIG_SLOWDOWN_FACTOR=5.0 --enable-session } "Stdcall" { @@ -179,7 +179,6 @@ array set ::Configs [strip_comments { -DSQLITE_OMIT_TRACE=1 -DSQLITE_TEMP_STORE=3 -DSQLITE_THREADSAFE=2 - -DSQLITE_ENABLE_DESERIALIZE=1 --enable-json1 --enable-fts5 --enable-session } "Locking-Style" { @@ -254,6 +253,7 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DCONFIG_SLOWDOWN_FACTOR=8.0 --enable-json1 } diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl index 9692cf47f..00be20228 100644 --- a/test/releasetest_data.tcl +++ b/test/releasetest_data.tcl @@ -51,11 +51,11 @@ array set ::Configs [strip_comments { -O2 --disable-amalgamation --disable-shared --enable-session - -DSQLITE_ENABLE_DESERIALIZE } "Sanitize" { CC=clang -fsanitize=address,undefined -DSQLITE_ENABLE_STAT4 + -DCONFIG_SLOWDOWN_FACTOR=5.0 --enable-debug --enable-all } @@ -187,7 +187,6 @@ array set ::Configs [strip_comments { -DSQLITE_OMIT_TRACE=1 -DSQLITE_TEMP_STORE=3 -DSQLITE_THREADSAFE=2 - -DSQLITE_ENABLE_DESERIALIZE=1 --enable-json1 --enable-fts5 --enable-session } "Locking-Style" { @@ -258,6 +257,7 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DCONFIG_SLOWDOWN_FACTOR=8.0 --enable-json1 } @@ -283,13 +283,13 @@ array set ::Configs [strip_comments { FuzzFail2 {-O0} }] if {$tcl_platform(os)=="Darwin"} { - lappend Configs(Apple -DSQLITE_ENABLE_LOCKING_STYLE=1 + lappend Configs(Apple) -DSQLITE_ENABLE_LOCKING_STYLE=1 } array set ::Platforms [strip_comments { Linux-x86_64 { "Check-Symbols*" checksymbols - "Fast-One" "fuzztest test" + "Fast-One" "QUICKTEST_INCLUDE=rbu.test fuzztest test" "Debug-One" "mptest test" "Debug-Two" "test" "Have-Not" test @@ -302,7 +302,7 @@ array set ::Platforms [strip_comments { "No-lookaside" test "Devkit" test "Apple" test - "Sanitize" {QUICKTEST_OMIT=crash*,shell*,sqldiff*,sessionB.test test} + "Sanitize" test "Device-One" fulltest "Default" "threadtest fulltest" "Valgrind*" valgrindtest @@ -355,6 +355,14 @@ array set ::Platforms [strip_comments { } }] +#-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- +# End of configuration section. +#-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- + # Configuration verification: Check that each entry in the list of configs # specified for each platforms exists. # @@ -586,9 +594,15 @@ proc main_tests {args} { puts "$config \"$target\"" if {$bNodebug==0 && $bNosynthetic==0} { set iHas [string first SQLITE_DEBUG $::Configs($config)] - set dtarget test - if {$target=="tcltest"} { - set dtarget tcltest + set dtarget [list] + set iQTI [lsearch -glob $target QUICKTEST_*] + if {$iQTI>=0} { + lappend dtarget [lindex $target $iQTI] + } + if {[lsearch $target tcltest]>=0} { + lappend dtarget tcltest + } else { + lappend dtarget test } if {$iHas>=0} { puts "$config-ndebug \"$dtarget\"" @@ -613,5 +627,3 @@ if {[string match ${cmd}* configurations] && $n==0} { } else { usage } - - diff --git a/test/returning1.test b/test/returning1.test new file mode 100644 index 000000000..dc4655231 --- /dev/null +++ b/test/returning1.test @@ -0,0 +1,321 @@ +# 2021-01-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 the new RETURNING clause +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix returning1 + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c DEFAULT 'pax'); + INSERT INTO t1(b) VALUES(10),('happy'),(NULL) RETURNING a,b,c; +} {1 10 pax 2 happy pax 3 {} pax} +do_execsql_test 1.1 { + SELECT * FROM t1; +} {1 10 pax 2 happy pax 3 {} pax} +do_execsql_test 1.2 { + INSERT INTO t1(b,c) VALUES(5,99) RETURNING b,c,a,rowid; +} {5 99 4 4} +do_execsql_test 1.3 { + SELECT * FROM t1; +} {1 10 pax 2 happy pax 3 {} pax 4 5 99} +do_execsql_test 1.4 { + INSERT INTO t1 DEFAULT VALUES RETURNING *; +} {5 {} pax} +do_execsql_test 1.5 { + SELECT * FROM t1; +} {1 10 pax 2 happy pax 3 {} pax 4 5 99 5 {} pax} +do_execsql_test 1.6 { + CREATE TABLE t2(x,y,z); + INSERT INTO t2 VALUES(11,12,13),(21,'b','c'),(31,'b-value',4.75); +} +do_execsql_test 1.7 { + INSERT INTO t1 SELECT * FROM t2 RETURNING *; +} {11 12 13 21 b c 31 b-value 4.75} +do_execsql_test 1.8 { + SELECT *, '|' FROM t1; +} {1 10 pax | 2 happy pax | 3 {} pax | 4 5 99 | 5 {} pax | 11 12 13 | 21 b c | 31 b-value 4.75 |} + +do_execsql_test 2.1 { + UPDATE t1 SET c='bellum' WHERE c='pax' RETURNING rowid, b, '|'; +} {1 10 | 2 happy | 3 {} | 5 {} |} +do_execsql_test 2.2 { + SELECT *, '|' FROM t1; +} {1 10 bellum | 2 happy bellum | 3 {} bellum | 4 5 99 | 5 {} bellum | 11 12 13 | 21 b c | 31 b-value 4.75 |} + +do_execsql_test 3.1 { + DELETE FROM t1 WHERE c='bellum' RETURNING rowid, *, '|'; +} {1 1 10 bellum | 2 2 happy bellum | 3 3 {} bellum | 5 5 {} bellum |} +do_execsql_test 3.2 { + SELECT *, '|' FROM t1; +} {4 5 99 | 11 12 13 | 21 b c | 31 b-value 4.75 |} + +do_execsql_test 4.1 { + CREATE TABLE t4(a INT, b INT DEFAULT 1234, c INT DEFAULT -16); + CREATE UNIQUE INDEX t4a ON t4(a); + INSERT INTO t4(a,b,c) VALUES(1,2,3); +} {} +do_execsql_test 4.2 { + INSERT INTO t4(a,b,c) VALUES(1,22,33) + ON CONFLICT(a) DO UPDATE SET b=44 + RETURNING *; +} {1 44 3} +do_execsql_test 4.3 { + SELECT * FROM t4; +} {1 44 3} +do_execsql_test 4.4 { + DELETE FROM t4; + INSERT INTO t4 VALUES(1,2,3),(4,5,6),(7,8,9); +} {} +do_execsql_test 4.5 { + INSERT INTO t4(a,b,c) VALUES(2,3,4),(4,5,6),(5,6,7) + ON CONFLICT(a) DO UPDATE SET b=100 + RETURNING *, '|'; +} {2 3 4 | 4 100 6 | 5 6 7 |} + +#------------------------------------------------------------------------- +# Test RETURNING on a table with virtual columns. +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(xyz); + CREATE TABLE t2(a as (1+1), b); +} + +do_execsql_test 5.1 { + UPDATE t2 SET b='123' WHERE b='abc' RETURNING (SELECT b FROM t1); +} {} + +do_execsql_test 5.2 { + INSERT INTO t2(b) VALUES('abc'); +} + +do_execsql_test 5.3 { + UPDATE t2 SET b='123' WHERE b='abc' RETURNING (SELECT b FROM t1); +} {{}} + +do_execsql_test 5.4 { + INSERT INTO t2(b) VALUES('abc'); + INSERT INTO t1(xyz) VALUES(1); + UPDATE t2 SET b='123' WHERE b='abc' RETURNING b; +} {123} + +do_execsql_test 5.5 { + INSERT INTO t2(b) VALUES('abc'); + UPDATE t2 SET b='123' WHERE b='abc' RETURNING (SELECT b FROM t1); +} {123} + +# Ticket 132994c8b1063bfb +reset_db +do_catchsql_test 6.0 { + CREATE TABLE t1(id INTEGER PRIMARY KEY); + CREATE TABLE t2(x INT, y INT); + INSERT INTO t1 VALUES(1),(2),(4),(9); + INSERT INTO t2 VALUES(3,7), (4,25), (5,99); + UPDATE t1 SET id=id+y FROM t2 WHERE t1.id=t2.x RETURNING t2.*; +} {1 {RETURNING may not use "TABLE.*" wildcards}} +do_catchsql_test 6.1 { + UPDATE t1 SET id=id+y FROM t2 WHERE t1.id=t2.x RETURNING *, '|'; + SELECT * FROM t1 ORDER BY id; +} {0 {29 | 1 2 9 29}} + +# Forum https://sqlite.org/forum/forumpost/85aef8bc01 +# Do not silently ignore nonsense table names in the RETURNING clause. +# Raise an error. +# +reset_db +do_execsql_test 7.1 { + CREATE TABLE t1(a INT, b INT); + CREATE TABLE t2(x INT, y INT); + INSERT INTO t1(a,b) VALUES(1,2); + INSERT INTO t2(x,y) VALUES(1,30); +} {} +do_catchsql_test 7.2 { + UPDATE t1 SET b=b+1 RETURNING new.b; +} {1 {no such column: new.b}} +do_catchsql_test 7.3 { + UPDATE t1 SET b=b+1 RETURNING old.b; +} {1 {no such column: old.b}} +do_catchsql_test 7.4 { + UPDATE t1 SET b=b+1 RETURNING another.b; +} {1 {no such column: another.b}} +do_catchsql_test 7.5 { + UPDATE t1 SET b=b+y FROM t2 WHERE t2.x=t1.a RETURNING t2.x; +} {1 {no such column: t2.x}} +do_catchsql_test 7.6 { + UPDATE t1 SET b=b+y FROM t2 WHERE t2.x=t1.a RETURNING t1.b; +} {0 32} + +# This is goofy: The RETURNING clause does not honor the alias +# for the table being modified. This might change in the future. +# +do_catchsql_test 7.7 { + UPDATE t1 AS alias SET b=123 RETURNING alias.b; +} {1 {no such column: alias.b}} +do_catchsql_test 7.8 { + UPDATE t1 AS alias SET b=alias.b+1000 RETURNING t1.b; +} {0 1032} + +# Forum: https://sqlite.org/forum/info/34c81d83c9177f46 +reset_db +do_execsql_test 8.1 { + CREATE TABLE t1(a); + CREATE TABLE t2(b,c); + INSERT INTO t1 VALUES(1); + INSERT INTO t2 VALUES(3,40); +} {} +do_catchsql_test 8.2 { + INSERT INTO t1 VALUES(3) RETURNING a, (SELECT c FROM t2 WHERE new.a=t2.b) AS x; +} {1 {no such column: new.a}} +do_catchsql_test 8.3 { + INSERT INTO t1 VALUES(3) RETURNING a, (SELECT c FROM t2 WHERE old.a=t2.b) AS x; +} {1 {no such column: old.a}} +do_catchsql_test 8.4 { + INSERT INTO t1 VALUES(3) RETURNING a, (SELECT c FROM t2 WHERE t1.a=t2.b) AS x; +} {0 {3 40}} + +ifcapable vtab { +# dbsqlfuzz finds/crash-486f791cbe2dc45839310073e71367a1d8ad22dd +do_catchsql_test 9.1 { + UPDATE pragma_encoding SET encoding='UTF-8' RETURNING a, b, *; +} {1 {table pragma_encoding may not be modified}} +} ;# ifcapable vtab + +# dbsqlfuzz crash-0081f863d7b2002045ac2361879fc80dfebb98f1 +reset_db +do_execsql_test 10.1 { + CREATE TABLE t1_a(a, b); + CREATE VIEW t1 AS SELECT a, b FROM t1_a; + + INSERT INTO t1_a VALUES('x', 'y'); + INSERT INTO t1_a VALUES('x', 'y'); + INSERT INTO t1_a VALUES('x', 'y'); + + CREATE TABLE log(op, r, a, b); +} +do_execsql_test 10.2 { + CREATE TRIGGER tr1 INSTEAD OF INSERT ON t1 BEGIN + INSERT INTO log VALUES('insert', new.rowid, new.a, new.b); + END; + CREATE TRIGGER tr2 INSTEAD OF UPDATE ON t1 BEGIN + INSERT INTO log VALUES('update', new.rowid, new.a, new.b); + END; +} + +do_catchsql_test 10.3 { + INSERT INTO t1(a, b) VALUES(1234, 5678) RETURNING rowid; +} {1 {no such column: rowid}} + +do_catchsql_test 10.3 { + UPDATE t1 SET a='z' WHERE b='y' RETURNING rowid; +} {1 {no such column: rowid}} + +do_execsql_test 10.4 { + SELECT * FROM log; +} {} + +# 2021-04-27 dbsqlfuzz 78b9400770ef8cc7d9427dfba26f4fcf46ea7dc2 +# Returning clauses on TEMP tables with triggers. +# +reset_db +do_execsql_test 11.1 { + CREATE TEMP TABLE t1(a,b); + CREATE TEMP TABLE t2(c,d); + CREATE TEMP TABLE t3(e,f); + CREATE TEMP TABLE log(op,x,y); + CREATE TEMP TRIGGER t1r1 AFTER INSERT ON t1 BEGIN + INSERT INTO log(op,x,y) VALUES('I1',new.a,new.b); + END; + CREATE TEMP TRIGGER t1r2 BEFORE DELETE ON t1 BEGIN + INSERT INTO log(op,x,y) VALUES('D1',old.a,old.b); + END; + CREATE TEMP TRIGGER t2r3 AFTER UPDATE ON t1 BEGIN + INSERT INTO log(op,x,y) VALUES('U1',new.a,new.b); + END; + CREATE TEMP TRIGGER t2r1 BEFORE INSERT ON t2 BEGIN + INSERT INTO log(op,x,y) VALUES('I2',new.c,new.d); + END; + CREATE TEMP TRIGGER t3r1 AFTER DELETE ON t3 BEGIN + INSERT INTO log(op,x,y) VALUES('D3',old.e,old.f); + END; + CREATE TEMP TRIGGER t3r2 BEFORE UPDATE ON t3 BEGIN + INSERT INTO log(op,x,y) VALUES('U3',new.e,new.f); + END; + INSERT INTO t1(a,b) VALUES(1,2),('happy','glad') RETURNING a, b, '|'; +} {1 2 | happy glad |} +do_execsql_test 11.2 { + UPDATE t1 SET b=9 WHERE a=1 RETURNING a, b, 'x'; +} {1 9 x} +do_execsql_test 11.3 { + DELETE FROM t1 WHERE a<>'xray' RETURNING a, b, '@'; +} {1 9 @ happy glad @} +do_execsql_test 11.4 { + SELECT * FROM log; + DELETE FROM log; +} {I1 1 2 I1 happy glad U1 1 9 D1 1 9 D1 happy glad} +do_execsql_test 11.5 { + INSERT INTO t2 VALUES('bravo','charlie') RETURNING d, c, 'z'; +} {charlie bravo z} +do_execsql_test 11.6 { + SELECT * FROM log; + DELETE FROM log; +} {I2 bravo charlie} +do_execsql_test 11.7 { + INSERT INTO t3(e) VALUES(1),(2),(3) RETURNING 'I', e; + UPDATE t3 SET f=e+100 RETURNING 'U', e, f; + DELETE FROM t3 WHERE f>100 RETURNING 'D', e, f; +} {I 1 I 2 I 3 U 1 101 U 2 102 U 3 103 D 1 101 D 2 102 D 3 103} +do_execsql_test 11.6 { + SELECT * FROM log; + DELETE FROM log; +} {U3 1 101 U3 2 102 U3 3 103 D3 1 101 D3 2 102 D3 3 103} + +reset_db +do_execsql_test 11.11 { + CREATE TEMP TABLE t1(a,b); + CREATE TRIGGER r1 BEFORE INSERT ON t1 BEGIN SELECT 1; END; + DELETE FROM t1 RETURNING *; + DROP TRIGGER r1; + INSERT INTO t1 VALUES(5,30); +} {} +do_execsql_test 11.12 { + SELECT * FROM t1; +} {5 30} + +# RETURNING column names are dequoted. +# https://sqlite.org/forum/forumpost/033daf0b32 +# +reset_db +do_test 12.1 { + db eval {CREATE TABLE t1(x INT, y INT)} + unset -nocomplain cname + db eval {INSERT INTO t1(x) VALUES(1) RETURNING "x";} cname {} + lsort [array names cname] +} {* x} +do_test 12.2 { + unset -nocomplain cname + db eval {INSERT INTO t1(x) VALUES(2) RETURNING [x];} cname {} + lsort [array names cname] +} {* x} +do_test 12.3 { + unset -nocomplain cname + db eval {INSERT INTO t1(x) VALUES(3) RETURNING x AS [xyz];} cname {} + lsort [array names cname] +} {* xyz} +do_test 12.4 { + unset -nocomplain cname + db eval {INSERT INTO t1(x,y) VALUES(4,5) RETURNING "x"+"y";} cname {} + lsort [array names cname] +} {{"x"+"y"} *} + +finish_test diff --git a/test/rollback2.test b/test/rollback2.test index 70aecc337..21ebdeaca 100644 --- a/test/rollback2.test +++ b/test/rollback2.test @@ -101,7 +101,7 @@ do_rollback_test 2.2 -setup { # do_eqp_test 3.1 { SELECT i FROM t1 WHERE (i%2)==0 ORDER BY h DESC; -} {SCAN TABLE t1 USING INDEX i1} +} {SCAN t1 USING INDEX i1} do_rollback_test 3.2 -setup { BEGIN; DELETE FROM t1 WHERE (i%2)==1; @@ -131,7 +131,7 @@ do_execsql_test 4.1 { UPDATE t1 SET h = $leader || h; } do_eqp_test 4.2 { SELECT i FROM t1 WHERE (i%2)==0 ORDER BY h ASC; -} {SCAN TABLE t1 USING INDEX i1} +} {SCAN t1 USING INDEX i1} do_rollback_test 4.3 -setup { BEGIN; DELETE FROM t1 WHERE (i%2)==1; diff --git a/test/rowvalue.test b/test/rowvalue.test index e3b66a109..c92d34482 100644 --- a/test/rowvalue.test +++ b/test/rowvalue.test @@ -175,19 +175,19 @@ do_execsql_test 7.0 { foreach {tn sql res eqp} { 1 "SELECT * FROM xy WHERE (i, j) IS (2, 2)" {2 2 2} - "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid=?)" + "SEARCH xy USING INTEGER PRIMARY KEY (rowid=?)" 2 "SELECT * FROM xy WHERE (k, j) < (2, 3)" {1 1 1 2 2 2} - "SCAN TABLE xy" + "SCAN xy" 3 "SELECT * FROM xy WHERE (i, j) < (2, 3)" {1 1 1 2 2 2} - "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid (2, 1)" {2 2 2 3 3 3 4 4 4} - "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)" + "SEARCH xy USING INTEGER PRIMARY KEY (rowid>?)" 5 "SELECT * FROM xy WHERE (i, j) > ('2', 1)" {2 2 2 3 3 3 4 4 4} - "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)" + "SEARCH xy USING INTEGER PRIMARY KEY (rowid>?)" } { do_eqp_test 7.$tn.1 $sql $eqp @@ -644,4 +644,54 @@ do_catchsql_test 27.10 { INSERT INTO t0(c0) VALUES(0) ON CONFLICT(c0) DO UPDATE SET c0 = 3; } {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}} +# 2021-02-03 +# https://bugs.chromium.org/p/chromium/issues/detail?id=1173511 +# Faulty assert() statement. +# +reset_db +do_catchsql_test 28.10 { + CREATE TABLE t0(c0 PRIMARY KEY, c1); + CREATE TRIGGER trigger0 BEFORE DELETE ON t0 BEGIN + SELECT (SELECT c0,c1 FROM t0) FROM t0; + END ; + DELETE FROM t0; +} {1 {sub-select returns 2 columns - expected 1}} + +# 2021-03-19 +# dbsqlfuzz find of a NEVER(). +do_catchsql_test 29.1 { + SELECT (SELECT 1 WHERE ((SELECT 1 WHERE (2,(2,0)) IS (2,(20))),(2,0)) IS (2,(20))) WHERE (2,(2,0)) IS (2 IN(SELECT 1 WHERE (2,(2,2,0)) IS (2,(20))),(20)); +} {1 {row value misused}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 30.0 { + CREATE TABLE t1(x, y, z); + CREATE TABLE t2(a, b); + + INSERT INTO t1 VALUES(1000, 2000, 3000); + INSERT INTO t2 VALUES(NULL, NULL); +} + +do_execsql_test 30.1 { + UPDATE t2 SET (a,b)=( + SELECT max( t1.x ) OVER( PARTITION BY sum( (SELECT t1.y) ) ), 2 + ) + FROM t1; +} {} + +do_execsql_test 30.2 { + SELECT * FROM t2 +} {1000 2} + +reset_db +do_execsql_test 30.3 { + CREATE TABLE t1(x INT PRIMARY KEY, y, z); + CREATE TABLE t2(a,b,c,d,e,PRIMARY KEY(a,b))WITHOUT ROWID; + + UPDATE t2 SET (d,d,a)=(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t1 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t1 UNION SELECT x ORDER BY 1) ORDER BY 1) ORDERa)|9 AS blob, 2, 3) FROM t1 WHERE x'd' AND c=2 } \ - {SEARCH TABLE c1 USING INDEX c1cd (c=?)} + {SEARCH c1 USING INDEX c1cd (c=?)} do_eqp_test 3.1.3 { SELECT * FROM c1 WHERE a=1 AND b>'l' AND c=2 } \ - {SEARCH TABLE c1 USING INDEX c1ab (a=? AND b>?)} + {SEARCH c1 USING INDEX c1ab (a=? AND b>?)} do_eqp_test 3.2.1 { SELECT * FROM c1 WHERE a=1 AND c>1 } \ - {SEARCH TABLE c1 USING INDEX c1cd (c>?)} + {SEARCH c1 USING INDEX c1cd (c>?)} do_eqp_test 3.2.2 { SELECT * FROM c1 WHERE a=1 AND c>0 } \ - {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + {SEARCH c1 USING INDEX c1ab (a=?)} do_eqp_test 3.2.3 { SELECT * FROM c1 WHERE a=1 AND c>=1 } \ - {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + {SEARCH c1 USING INDEX c1ab (a=?)} do_eqp_test 3.2.4 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'c') } \ - {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + {SEARCH c1 USING INDEX c1ab (a=?)} do_eqp_test 3.2.5 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'o') } \ - {SEARCH TABLE c1 USING INDEX c1cd ((c,d)>(?,?))} + {SEARCH c1 USING INDEX c1cd ((c,d)>(?,?))} do_eqp_test 3.2.6 { SELECT * FROM c1 WHERE a=1 AND (c, +b)>(1, 'c') } \ - {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + {SEARCH c1 USING INDEX c1ab (a=?)} } @@ -234,11 +234,11 @@ do_eqp_test 5.1 { (c) IN (SELECT y FROM d1) } { QUERY PLAN - |--SEARCH TABLE d2 USING INDEX d2ab (a=? AND b=?) + |--SEARCH d2 USING INDEX d2ab (a=? AND b=?) |--LIST SUBQUERY xxxxxx - | `--SCAN TABLE d1 + | `--SCAN d1 `--LIST SUBQUERY xxxxxx - `--SCAN TABLE d1 + `--SCAN d1 } do_execsql_test 6.0 { @@ -249,23 +249,23 @@ do_execsql_test 6.0 { do_eqp_test 6.1 { SELECT * FROM e1 WHERE (a, b) > (?, ?) -} {SEARCH TABLE e1 USING INDEX e1ab ((a,b)>(?,?))} +} {SEARCH e1 USING INDEX e1ab ((a,b)>(?,?))} do_eqp_test 6.2 { SELECT * FROM e1 WHERE (a, b) < (?, ?) -} {SEARCH TABLE e1 USING INDEX e1ab ((a,b)<(?,?))} +} {SEARCH e1 USING INDEX e1ab ((a,b)<(?,?))} do_eqp_test 6.3 { SELECT * FROM e1 WHERE c = ? AND (d, e) > (?, ?) -} {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?))} +} {SEARCH e1 USING INDEX e1cde (c=? AND (d,e)>(?,?))} do_eqp_test 6.4 { SELECT * FROM e1 WHERE c = ? AND (d, e) < (?, ?) -} {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)<(?,?))} +} {SEARCH e1 USING INDEX e1cde (c=? AND (d,e)<(?,?))} do_eqp_test 6.5 { SELECT * FROM e1 WHERE (d, e) BETWEEN (?, ?) AND (?, ?) AND c = ? -} {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?) AND (d,e)<(?,?))} +} {SEARCH e1 USING INDEX e1cde (c=? AND (d,e)>(?,?) AND (d,e)<(?,?))} #------------------------------------------------------------------------- diff --git a/test/rowvalue9.test b/test/rowvalue9.test index 0fa652f97..611aa3fbb 100644 --- a/test/rowvalue9.test +++ b/test/rowvalue9.test @@ -297,5 +297,18 @@ do_execsql_test 7.3 { ); } { 1 4 1 5 } +#------------------------------------------------------------------------- +# +do_execsql_test 8.1 { + CREATE TABLE t1(a ,b FLOAT); + CREATE INDEX t1x1 ON t1(a,b,a,a,a,a,a,a,a,a,a,b); +} + +do_catchsql_test 8.2 { + SELECT a FROM t1 NATURAL JOIN t1 WHERE (a,b)> (SELECT 2 IN (SELECT 2,2), 2); +} {1 {sub-select returns 2 columns - expected 1}} + finish_test + + diff --git a/test/scanstatus.test b/test/scanstatus.test index 1b92ef3fe..46249f665 100644 --- a/test/scanstatus.test +++ b/test/scanstatus.test @@ -45,8 +45,8 @@ proc do_scanstatus_test {tn res} { do_execsql_test 1.1 { SELECT count(*) FROM t1, t2; } 6 do_scanstatus_test 1.2 { - nLoop 1 nVisit 2 nEst 1048576.0 zName t1 zExplain {SCAN TABLE t1} - nLoop 2 nVisit 6 nEst 1048576.0 zName t2 zExplain {SCAN TABLE t2} + nLoop 1 nVisit 2 nEst 1048576.0 zName t1 zExplain {SCAN t1} + nLoop 2 nVisit 6 nEst 1048576.0 zName t2 zExplain {SCAN t2} } do_execsql_test 1.3 { @@ -54,8 +54,8 @@ do_execsql_test 1.3 { SELECT count(*) FROM t1, t2; } 6 do_scanstatus_test 1.4 { - nLoop 1 nVisit 2 nEst 2.0 zName t1 zExplain {SCAN TABLE t1} - nLoop 2 nVisit 6 nEst 3.0 zName t2 zExplain {SCAN TABLE t2} + nLoop 1 nVisit 2 nEst 2.0 zName t1 zExplain {SCAN t1} + nLoop 2 nVisit 6 nEst 3.0 zName t2 zExplain {SCAN t2} } do_execsql_test 1.5 { ANALYZE } @@ -64,8 +64,8 @@ do_execsql_test 1.6 { } 4 do_scanstatus_test 1.7 { nLoop 1 nVisit 2 nEst 2.0 zName t2 zExplain - {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid>?)} - nLoop 2 nVisit 4 nEst 2.0 zName t1 zExplain {SCAN TABLE t1} + {SEARCH t2 USING INTEGER PRIMARY KEY (rowid>?)} + nLoop 2 nVisit 4 nEst 2.0 zName t1 zExplain {SCAN t1} } do_execsql_test 1.8 { @@ -74,8 +74,8 @@ do_execsql_test 1.8 { do_scanstatus_test 1.9 { nLoop 2 nVisit 4 nEst 2.0 zName t2 zExplain - {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid>?)} - nLoop 4 nVisit 8 nEst 2.0 zName t1 zExplain {SCAN TABLE t1} + {SEARCH t2 USING INTEGER PRIMARY KEY (rowid>?)} + nLoop 4 nVisit 8 nEst 2.0 zName t1 zExplain {SCAN t1} } do_test 1.9 { @@ -84,8 +84,8 @@ do_test 1.9 { do_scanstatus_test 1.10 { nLoop 0 nVisit 0 nEst 2.0 zName t2 zExplain - {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid>?)} - nLoop 0 nVisit 0 nEst 2.0 zName t1 zExplain {SCAN TABLE t1} + {SEARCH t2 USING INTEGER PRIMARY KEY (rowid>?)} + nLoop 0 nVisit 0 nEst 2.0 zName t1 zExplain {SCAN t1} } #------------------------------------------------------------------------- @@ -105,7 +105,7 @@ do_execsql_test 2.1 { do_scanstatus_test 2.2 { nLoop 1 nVisit 1 nEst 1.0 zName x1 - zExplain {SEARCH TABLE x1 USING INTEGER PRIMARY KEY (rowid=?)} + zExplain {SEARCH x1 USING INTEGER PRIMARY KEY (rowid=?)} } do_execsql_test 2.3.1 { @@ -113,7 +113,7 @@ do_execsql_test 2.3.1 { } {2 two} do_scanstatus_test 2.3.2 { nLoop 1 nVisit 1 nEst 10.0 zName x1j - zExplain {SEARCH TABLE x1 USING COVERING INDEX x1j (j=?)} + zExplain {SEARCH x1 USING COVERING INDEX x1j (j=?)} } do_execsql_test 2.4.1 { @@ -121,7 +121,7 @@ do_execsql_test 2.4.1 { } {4 four 1 one 3 three} do_scanstatus_test 2.4.2 { nLoop 1 nVisit 3 nEst 262144.0 zName x1j - zExplain {SEARCH TABLE x1 USING COVERING INDEX x1j (j?)} + zExplain {SEARCH x1 USING COVERING INDEX x1j (j>?)} } do_execsql_test 2.6.1 { @@ -137,7 +137,7 @@ do_execsql_test 2.6.1 { } {3 three 2 two} do_scanstatus_test 2.6.2 { nLoop 1 nVisit 2 nEst 16384.0 zName x1j - zExplain {SEARCH TABLE x1 USING COVERING INDEX x1j (j>? AND j? AND j? AND j? AND j? AND a? AND a? AND b? AND b? AND b? AND b? AND a? AND a5 GROUP BY x1.d) AS x2 ON t41.b=x2.d; -} {/*SEARCH SUBQUERY * AS x2 USING AUTOMATIC*/} +} {/SEARCH x2 USING AUTOMATIC/} finish_test diff --git a/test/shell1.test b/test/shell1.test index a62b0d1a8..330276120 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -642,7 +642,7 @@ do_test shell1-3.23.2 { # .stats ON|OFF Turn stats on or off #do_test shell1-3.23b.1 { # catchcmd "test.db" ".stats" -#} {1 {Usage: .stats on|off}} +#} {1 {Usage: .stats on|off|stmt|vmstep}} do_test shell1-3.23b.2 { catchcmd "test.db" ".stats ON" } {0 {}} @@ -652,7 +652,7 @@ do_test shell1-3.23b.3 { do_test shell1-3.23b.4 { # too many arguments catchcmd "test.db" ".stats OFF BAD" -} {1 {Usage: .stats ?on|off?}} +} {1 {Usage: .stats ?on|off|stmt|vmstep?}} # Ticket 7be932dfa60a8a6b3b26bcf7623ec46e0a403ddb 2018-06-07 # Adverse interaction between .stats and .eqp diff --git a/test/shell4.test b/test/shell4.test index 386e51f6c..9e3c58fbe 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -66,7 +66,7 @@ do_test shell4-1.3.3 { do_test shell4-1.3.4 { # too many arguments catchcmd "test.db" ".stats OFF BAD" -} {1 {Usage: .stats ?on|off?}} +} {1 {Usage: .stats ?on|off|stmt|vmstep?}} # NB. whitespace is important do_test shell4-1.4.1 { diff --git a/test/skipscan1.test b/test/skipscan1.test index 367cd2a10..94062fb17 100644 --- a/test/skipscan1.test +++ b/test/skipscan1.test @@ -341,7 +341,7 @@ optimization_control db skip-scan 0 do_execsql_test skipscan1-9.3 { EXPLAIN QUERY PLAN SELECT * FROM t9a WHERE b IN (SELECT x FROM t9b WHERE y!=5); -} {/{SCAN TABLE t9a}/} +} {/{SCAN t9a}/} optimization_control db skip-scan 1 do_execsql_test skipscan1-2.1 { @@ -394,7 +394,7 @@ do_eqp_test skipscan1-3.2 { FROM t1 WHERE t1.c3 = 1; } { QUERY PLAN - |--SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (ANY(c4) AND c3=?) + |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (ANY(c4) AND c3=?) `--USE TEMP B-TREE FOR DISTINCT } diff --git a/test/skipscan2.test b/test/skipscan2.test index 6fa04d54c..47b2b3fff 100644 --- a/test/skipscan2.test +++ b/test/skipscan2.test @@ -199,7 +199,7 @@ do_test skipscan2-3.2 { } {} do_eqp_test skipscan2-3.3eqp { SELECT * FROM t3 WHERE b=42; -} {SEARCH TABLE t3 USING PRIMARY KEY (ANY(a) AND b=?)} +} {SEARCH t3 USING PRIMARY KEY (ANY(a) AND b=?)} diff --git a/test/skipscan5.test b/test/skipscan5.test index c128e614c..026356e6c 100644 --- a/test/skipscan5.test +++ b/test/skipscan5.test @@ -41,20 +41,20 @@ do_test 1.2 { foreach {tn q res} { 1 "b = 5" {/*ANY(a) AND b=?*/} 2 "b > 12 AND b < 16" {/*ANY(a) AND b>? AND b 2 AND b < 16" {/*SCAN TABLE t1*/} + 3 "b > 2 AND b < 16" {/*SCAN t1*/} 4 "b > 18 AND b < 25" {/*ANY(a) AND b>? AND b 16" {/*ANY(a) AND b>?*/} - 6 "b > 5" {/*SCAN TABLE t1*/} - 7 "b < 15" {/*SCAN TABLE t1*/} + 6 "b > 5" {/*SCAN t1*/} + 7 "b < 15" {/*SCAN t1*/} 8 "b < 5" {/*ANY(a) AND b b" {/*ANY(a) AND b '12' AND b < '16'" {/*ANY(a) AND b>? AND b '2' AND b < '16'" {/*SCAN TABLE t1*/} + 12 "b > '2' AND b < '16'" {/*SCAN t1*/} 13 "b > '18' AND b < '25'" {/*ANY(a) AND b>? AND b '16'" {/*ANY(a) AND b>?*/} - 15 "b > '5'" {/*SCAN TABLE t1*/} - 16 "b < '15'" {/*SCAN TABLE t1*/} + 15 "b > '5'" {/*SCAN t1*/} + 16 "b < '15'" {/*SCAN t1*/} 17 "b < '5'" {/*ANY(a) AND b b" {/*ANY(a) AND b? AND c 'q' } {/*ANY(a) AND ANY(b) AND c>?*/} - 4 { c > 'e' } {/*SCAN TABLE t2*/} - 5 { c < 'q' } {/*SCAN TABLE t2*/} + 4 { c > 'e' } {/*SCAN t2*/} + 5 { c < 'q' } {/*SCAN t2*/} 6 { c < 'b' } {/*ANY(a) AND ANY(b) AND c? AND b X'5555'" {/*ANY(a) AND b>?*/} 5 "b > 'zzz'" {/*ANY(a) AND b>?*/} - 6 "b < 'zzz'" {/*SCAN TABLE t3*/} + 6 "b < 'zzz'" {/*SCAN t3*/} } { set sql "EXPLAIN QUERY PLAN SELECT * FROM t3 WHERE $q" do_execsql_test 3.3.$tn $sql $res diff --git a/test/skipscan6.test b/test/skipscan6.test index a53be1d95..4f592bc0e 100644 --- a/test/skipscan6.test +++ b/test/skipscan6.test @@ -179,13 +179,13 @@ do_execsql_test 3.0 { # do_eqp_test 3.1 { SELECT * FROM t3 WHERE a = ? AND c = ? -} {SEARCH TABLE t3 USING INDEX t3_a (a=?)} +} {SEARCH t3 USING INDEX t3_a (a=?)} # The same query on table t2. This should use index "t2_a", for the # same reason. At one point though, it was mistakenly using a skip-scan. # do_eqp_test 3.2 { SELECT * FROM t2 WHERE a = ? AND c = ? -} {SEARCH TABLE t2 USING INDEX t2_a (a=?)} +} {SEARCH t2 USING INDEX t2_a (a=?)} finish_test diff --git a/test/speedtest1.c b/test/speedtest1.c index 3d7ac4c8e..9e8c39608 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -707,9 +707,9 @@ void testset_main(void){ maxb = roundup_allones(sz); speedtest1_begin_test(100, "%d INSERTs into table with no index", n); speedtest1_exec("BEGIN"); - speedtest1_exec("CREATE%s TABLE t1(a INTEGER %s, b INTEGER %s, c TEXT %s);", + speedtest1_exec("CREATE%s TABLE z1(a INTEGER %s, b INTEGER %s, c TEXT %s);", isTemp(9), g.zNN, g.zNN, g.zNN); - speedtest1_prepare("INSERT INTO t1 VALUES(?1,?2,?3); -- %d times", n); + speedtest1_prepare("INSERT INTO z1 VALUES(?1,?2,?3); -- %d times", n); for(i=1; i<=n; i++){ x1 = swizzle(i,maxb); speedtest1_numbername(x1, zNum, sizeof(zNum)); @@ -726,9 +726,9 @@ void testset_main(void){ speedtest1_begin_test(110, "%d ordered INSERTS with one index/PK", n); speedtest1_exec("BEGIN"); speedtest1_exec( - "CREATE%s TABLE t2(a INTEGER %s %s, b INTEGER %s, c TEXT %s) %s", + "CREATE%s TABLE z2(a INTEGER %s %s, b INTEGER %s, c TEXT %s) %s", isTemp(5), g.zNN, g.zPK, g.zNN, g.zNN, g.zWR); - speedtest1_prepare("INSERT INTO t2 VALUES(?1,?2,?3); -- %d times", n); + speedtest1_prepare("INSERT INTO z2 VALUES(?1,?2,?3); -- %d times", n); for(i=1; i<=n; i++){ x1 = swizzle(i,maxb); speedtest1_numbername(x1, zNum, sizeof(zNum)); @@ -768,7 +768,7 @@ void testset_main(void){ speedtest1_begin_test(130, "%d SELECTS, numeric BETWEEN, unindexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)), group_concat(c) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(c) FROM z1\n" " WHERE b BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ @@ -788,7 +788,7 @@ void testset_main(void){ speedtest1_begin_test(140, "%d SELECTS, LIKE, unindexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)), group_concat(c) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(c) FROM z1\n" " WHERE c LIKE ?1; -- %d times", n ); for(i=1; i<=n; i++){ @@ -810,7 +810,7 @@ void testset_main(void){ speedtest1_begin_test(142, "%d SELECTS w/ORDER BY, unindexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT a, b, c FROM t1 WHERE c LIKE ?1\n" + "SELECT a, b, c FROM z1 WHERE c LIKE ?1\n" " ORDER BY a; -- %d times", n ); for(i=1; i<=n; i++){ @@ -831,7 +831,7 @@ void testset_main(void){ speedtest1_begin_test(145, "%d SELECTS w/ORDER BY and LIMIT, unindexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT a, b, c FROM t1 WHERE c LIKE ?1\n" + "SELECT a, b, c FROM z1 WHERE c LIKE ?1\n" " ORDER BY a LIMIT 10; -- %d times", n ); for(i=1; i<=n; i++){ @@ -851,10 +851,10 @@ void testset_main(void){ speedtest1_begin_test(150, "CREATE INDEX five times"); speedtest1_exec("BEGIN;"); - speedtest1_exec("CREATE UNIQUE INDEX t1b ON t1(b);"); - speedtest1_exec("CREATE INDEX t1c ON t1(c);"); - speedtest1_exec("CREATE UNIQUE INDEX t2b ON t2(b);"); - speedtest1_exec("CREATE INDEX t2c ON t2(c DESC);"); + speedtest1_exec("CREATE UNIQUE INDEX t1b ON z1(b);"); + speedtest1_exec("CREATE INDEX t1c ON z1(c);"); + speedtest1_exec("CREATE UNIQUE INDEX t2b ON z2(b);"); + speedtest1_exec("CREATE INDEX t2c ON z2(c DESC);"); speedtest1_exec("CREATE INDEX t3bc ON t3(b,c);"); speedtest1_exec("COMMIT;"); speedtest1_end_test(); @@ -864,7 +864,7 @@ void testset_main(void){ speedtest1_begin_test(160, "%d SELECTS, numeric BETWEEN, indexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM z1\n" " WHERE b BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ @@ -884,7 +884,7 @@ void testset_main(void){ speedtest1_begin_test(161, "%d SELECTS, numeric BETWEEN, PK", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM t2\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM z2\n" " WHERE a BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ @@ -904,7 +904,7 @@ void testset_main(void){ speedtest1_begin_test(170, "%d SELECTS, text BETWEEN, indexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM z1\n" " WHERE c BETWEEN ?1 AND (?1||'~'); -- %d times", n ); for(i=1; i<=n; i++){ @@ -930,14 +930,14 @@ void testset_main(void){ isTemp(1), g.zNN, g.zPK, g.zNN, g.zNN, g.zWR); speedtest1_exec("CREATE INDEX t4b ON t4(b)"); speedtest1_exec("CREATE INDEX t4c ON t4(c)"); - speedtest1_exec("INSERT INTO t4 SELECT * FROM t1"); + speedtest1_exec("INSERT INTO t4 SELECT * FROM z1"); speedtest1_exec("COMMIT"); speedtest1_end_test(); n = sz; speedtest1_begin_test(190, "DELETE and REFILL one table", n); - speedtest1_exec("DELETE FROM t2;"); - speedtest1_exec("INSERT INTO t2 SELECT * FROM t1;"); + speedtest1_exec("DELETE FROM z2;"); + speedtest1_exec("INSERT INTO z2 SELECT * FROM z1;"); speedtest1_end_test(); @@ -947,8 +947,8 @@ void testset_main(void){ speedtest1_begin_test(210, "ALTER TABLE ADD COLUMN, and query"); - speedtest1_exec("ALTER TABLE t2 ADD COLUMN d DEFAULT 123"); - speedtest1_exec("SELECT sum(d) FROM t2"); + speedtest1_exec("ALTER TABLE z2 ADD COLUMN d DEFAULT 123"); + speedtest1_exec("SELECT sum(d) FROM z2"); speedtest1_end_test(); @@ -956,7 +956,7 @@ void testset_main(void){ speedtest1_begin_test(230, "%d UPDATES, numeric BETWEEN, indexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "UPDATE t2 SET d=b*2 WHERE b BETWEEN ?1 AND ?2; -- %d times", n + "UPDATE z2 SET d=b*2 WHERE b BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ x1 = speedtest1_random()%maxb; @@ -973,7 +973,7 @@ void testset_main(void){ speedtest1_begin_test(240, "%d UPDATES of individual rows", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "UPDATE t2 SET d=b*3 WHERE a=?1; -- %d times", n + "UPDATE z2 SET d=b*3 WHERE a=?1; -- %d times", n ); for(i=1; i<=n; i++){ x1 = speedtest1_random()%sz + 1; @@ -984,12 +984,12 @@ void testset_main(void){ speedtest1_end_test(); speedtest1_begin_test(250, "One big UPDATE of the whole %d-row table", sz); - speedtest1_exec("UPDATE t2 SET d=b*4"); + speedtest1_exec("UPDATE z2 SET d=b*4"); speedtest1_end_test(); speedtest1_begin_test(260, "Query added column after filling"); - speedtest1_exec("SELECT sum(d) FROM t2"); + speedtest1_exec("SELECT sum(d) FROM z2"); speedtest1_end_test(); @@ -998,7 +998,7 @@ void testset_main(void){ speedtest1_begin_test(270, "%d DELETEs, numeric BETWEEN, indexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "DELETE FROM t2 WHERE b BETWEEN ?1 AND ?2; -- %d times", n + "DELETE FROM z2 WHERE b BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ x1 = speedtest1_random()%maxb + 1; @@ -1027,16 +1027,16 @@ void testset_main(void){ speedtest1_begin_test(290, "Refill two %d-row tables using REPLACE", sz); - speedtest1_exec("REPLACE INTO t2(a,b,c) SELECT a,b,c FROM t1"); - speedtest1_exec("REPLACE INTO t3(a,b,c) SELECT a,b,c FROM t1"); + speedtest1_exec("REPLACE INTO z2(a,b,c) SELECT a,b,c FROM z1"); + speedtest1_exec("REPLACE INTO t3(a,b,c) SELECT a,b,c FROM z1"); speedtest1_end_test(); speedtest1_begin_test(300, "Refill a %d-row table using (b&1)==(a&1)", sz); - speedtest1_exec("DELETE FROM t2;"); - speedtest1_exec("INSERT INTO t2(a,b,c)\n" - " SELECT a,b,c FROM t1 WHERE (b&1)==(a&1);"); - speedtest1_exec("INSERT INTO t2(a,b,c)\n" - " SELECT a,b,c FROM t1 WHERE (b&1)<>(a&1);"); + speedtest1_exec("DELETE FROM z2;"); + speedtest1_exec("INSERT INTO z2(a,b,c)\n" + " SELECT a,b,c FROM z1 WHERE (b&1)==(a&1);"); + speedtest1_exec("INSERT INTO z2(a,b,c)\n" + " SELECT a,b,c FROM z1 WHERE (b&1)<>(a&1);"); speedtest1_end_test(); @@ -1044,11 +1044,11 @@ void testset_main(void){ speedtest1_begin_test(310, "%d four-ways joins", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT t1.c FROM t1, t2, t3, t4\n" + "SELECT z1.c FROM z1, z2, t3, t4\n" " WHERE t4.a BETWEEN ?1 AND ?2\n" " AND t3.a=t4.b\n" - " AND t2.a=t3.b\n" - " AND t1.c=t2.c" + " AND z2.a=t3.b\n" + " AND z1.c=z2.c" ); for(i=1; i<=n; i++){ x1 = speedtest1_random()%sz + 1; @@ -1063,8 +1063,8 @@ void testset_main(void){ speedtest1_begin_test(320, "subquery in result set", n); speedtest1_prepare( "SELECT sum(a), max(c),\n" - " avg((SELECT a FROM t2 WHERE 5+t2.b=t1.b) AND rowid=?1 AND x1<=?2"); + speedtest1_prepare("SELECT count(*) FROM z1 WHERE x0>=?1 AND x1<=?2"); iStep = mxCoord/n; for(i=0; i=?1 AND y0<=?2"); + speedtest1_prepare("SELECT count(*) FROM z1 WHERE y1>=?1 AND y0<=?2"); iStep = mxCoord/n; for(i=0; i +#include +#include +#include +#include +#include "sqlite3.h" + +static const char zHelp[] = + "Usage: %s COMMAND\n" + "Commands:\n" + " init Initialized the startup.db database file\n" + " run Run the startup performance test\n" + "Options:\n" + " --dbname NAME Set the name of the test database file\n" + " --heap SZ MIN Memory allocator uses SZ bytes & min allocation MIN\n" + " --stats Show statistics at the end\n" +/* TBD + " --journal M Set the journal_mode to M\n" + " --lookaside N SZ Configure lookaside for N slots of SZ bytes each\n" + " --mmap SZ MMAP the first SZ bytes of the database file\n" + " --multithread Set multithreaded mode\n" + " --nomemstat Disable memory statistics\n" + " --pagesize N Set the page size to N\n" + " --pcache N SZ Configure N pages of pagecache each of size SZ bytes\n" + " --serialized Set serialized threading mode\n" + " --singlethread Set single-threaded mode - disables all mutexing\n" + " --utf16be Set text encoding to UTF-16BE\n" + " --utf16le Set text encoding to UTF-16LE\n" + " --utf8 Set text encoding to UTF-8\n" +*/ +; + +static void usage(const char *argv0){ + printf(zHelp, argv0); + exit(1); +} + +/* +** The test schema is derived from the Fossil repository for SQLite itself. +** The schema covers the repository, the local checkout database, and +** the global configuration database. +*/ +static const char zTestSchema[] = + "CREATE TABLE repo_blob(\n" + " rid INTEGER PRIMARY KEY,\n" + " rcvid INTEGER,\n" + " size INTEGER,\n" + " uuid TEXT UNIQUE NOT NULL,\n" + " content BLOB,\n" + " CHECK( length(uuid)>=40 AND rid>0 )\n" + ");\n" + "CREATE TABLE repo_delta(\n" + " rid INTEGER PRIMARY KEY,\n" + " srcid INTEGER NOT NULL REFERENCES blob\n" + ");\n" + "CREATE TABLE repo_rcvfrom(\n" + " rcvid INTEGER PRIMARY KEY,\n" + " uid INTEGER REFERENCES user,\n" + " mtime DATETIME,\n" + " nonce TEXT UNIQUE,\n" + " ipaddr TEXT\n" + ");\n" + "CREATE TABLE repo_private(rid INTEGER PRIMARY KEY);\n" + "CREATE TABLE repo_accesslog(\n" + " uname TEXT,\n" + " ipaddr TEXT,\n" + " success BOOLEAN,\n" + " mtime TIMESTAMP);\n" + "CREATE TABLE repo_user(\n" + " uid INTEGER PRIMARY KEY,\n" + " login TEXT UNIQUE,\n" + " pw TEXT,\n" + " cap TEXT,\n" + " cookie TEXT,\n" + " ipaddr TEXT,\n" + " cexpire DATETIME,\n" + " info TEXT,\n" + " mtime DATE,\n" + " photo BLOB\n" + ");\n" + "CREATE TABLE repo_reportfmt(\n" + " rn INTEGER PRIMARY KEY,\n" + " owner TEXT,\n" + " title TEXT UNIQUE,\n" + " mtime INTEGER,\n" + " cols TEXT,\n" + " sqlcode TEXT\n" + ");\n" + "CREATE TABLE repo_sqlite_stat2(tbl,idx,sampleno,sample);\n" + "CREATE TABLE repo_sqlite_stat1(tbl,idx,stat);\n" + "CREATE TABLE repo_sqlite_stat3(tbl,idx,neq,nlt,ndlt,sample);\n" + "CREATE TABLE repo_config(\n" + " name TEXT PRIMARY KEY NOT NULL,\n" + " value CLOB, mtime INTEGER,\n" + " CHECK( typeof(name)='text' AND length(name)>=1 )\n" + ") WITHOUT ROWID;\n" + "CREATE TABLE repo_shun(uuid PRIMARY KEY,\n" + " mtime INTEGER,\n" + " scom TEXT) WITHOUT ROWID;\n" + "CREATE TABLE repo_concealed(\n" + " hash TEXT PRIMARY KEY,\n" + " content TEXT\n" + ", mtime INTEGER) WITHOUT ROWID;\n" + "CREATE TABLE repo_admin_log(\n" + " id INTEGER PRIMARY KEY,\n" + " time INTEGER, -- Seconds since 1970\n" + " page TEXT, -- path of page\n" + " who TEXT, -- User who made the change\n" + " what TEXT -- What changed\n" + ");\n" + "CREATE TABLE repo_unversioned(\n" + " name TEXT PRIMARY KEY,\n" + " rcvid INTEGER,\n" + " mtime DATETIME,\n" + " hash TEXT,\n" + " sz INTEGER,\n" + " encoding INT,\n" + " content BLOB\n" + ") WITHOUT ROWID;\n" + "CREATE TABLE repo_subscriber(\n" + " subscriberId INTEGER PRIMARY KEY,\n" + " subscriberCode BLOB DEFAULT (randomblob(32)) UNIQUE,\n" + " semail TEXT UNIQUE COLLATE nocase,\n" + " suname TEXT,\n" + " sverified BOOLEAN DEFAULT true,\n" + " sdonotcall BOOLEAN,\n" + " sdigest BOOLEAN,\n" + " ssub TEXT,\n" + " sctime INTDATE,\n" + " mtime INTDATE,\n" + " smip TEXT\n" + ");\n" + "CREATE TABLE repo_pending_alert(\n" + " eventid TEXT PRIMARY KEY,\n" + " sentSep BOOLEAN DEFAULT false,\n" + " sentDigest BOOLEAN DEFAULT false\n" + ", sentMod BOOLEAN DEFAULT false) WITHOUT ROWID;\n" + "CREATE INDEX repo_delta_i1 ON repo_delta(srcid);\n" + "CREATE INDEX repo_blob_rcvid ON repo_blob(rcvid);\n" + "CREATE INDEX repo_subscriberUname\n" + " ON repo_subscriber(suname) WHERE suname IS NOT NULL;\n" + "CREATE VIEW repo_artifact(rid,rcvid,size,atype,srcid,hash,content) AS\n" + " SELECT blob.rid,rcvid,size,1,srcid,uuid,content\n" + " FROM repo_blob LEFT JOIN repo_delta ON (blob.rid=delta.rid);\n" + "CREATE TABLE repo_filename(\n" + " fnid INTEGER PRIMARY KEY,\n" + " name TEXT UNIQUE\n" + ");\n" + "CREATE TABLE repo_mlink(\n" + " mid INTEGER,\n" + " fid INTEGER,\n" + " pmid INTEGER,\n" + " pid INTEGER,\n" + " fnid INTEGER REFERENCES filename,\n" + " pfnid INTEGER,\n" + " mperm INTEGER,\n" + " isaux BOOLEAN DEFAULT 0\n" + ");\n" + "CREATE INDEX repo_mlink_i1 ON repo_mlink(mid);\n" + "CREATE INDEX repo_mlink_i2 ON repo_mlink(fnid);\n" + "CREATE INDEX repo_mlink_i3 ON repo_mlink(fid);\n" + "CREATE INDEX repo_mlink_i4 ON repo_mlink(pid);\n" + "CREATE TABLE repo_plink(\n" + " pid INTEGER REFERENCES blob,\n" + " cid INTEGER REFERENCES blob,\n" + " isprim BOOLEAN,\n" + " mtime DATETIME,\n" + " baseid INTEGER REFERENCES blob,\n" + " UNIQUE(pid, cid)\n" + ");\n" + "CREATE INDEX repo_plink_i2 ON repo_plink(cid,pid);\n" + "CREATE TABLE repo_leaf(rid INTEGER PRIMARY KEY);\n" + "CREATE TABLE repo_event(\n" + " type TEXT,\n" + " mtime DATETIME,\n" + " objid INTEGER PRIMARY KEY,\n" + " tagid INTEGER,\n" + " uid INTEGER REFERENCES user,\n" + " bgcolor TEXT,\n" + " euser TEXT,\n" + " user TEXT,\n" + " ecomment TEXT,\n" + " comment TEXT,\n" + " brief TEXT,\n" + " omtime DATETIME\n" + ");\n" + "CREATE INDEX repo_event_i1 ON repo_event(mtime);\n" + "CREATE TABLE repo_phantom(\n" + " rid INTEGER PRIMARY KEY\n" + ");\n" + "CREATE TABLE repo_orphan(\n" + " rid INTEGER PRIMARY KEY,\n" + " baseline INTEGER\n" + ");\n" + "CREATE INDEX repo_orphan_baseline ON repo_orphan(baseline);\n" + "CREATE TABLE repo_unclustered(\n" + " rid INTEGER PRIMARY KEY\n" + ");\n" + "CREATE TABLE repo_unsent(\n" + " rid INTEGER PRIMARY KEY\n" + ");\n" + "CREATE TABLE repo_tag(\n" + " tagid INTEGER PRIMARY KEY,\n" + " tagname TEXT UNIQUE\n" + ");\n" + "CREATE TABLE repo_tagxref(\n" + " tagid INTEGER REFERENCES tag,\n" + " tagtype INTEGER,\n" + " srcid INTEGER REFERENCES blob,\n" + " origid INTEGER REFERENCES blob,\n" + " value TEXT,\n" + " mtime TIMESTAMP,\n" + " rid INTEGER REFERENCE blob,\n" + " UNIQUE(rid, tagid)\n" + ");\n" + "CREATE INDEX repo_tagxref_i1 ON repo_tagxref(tagid, mtime);\n" + "CREATE TABLE repo_backlink(\n" + " target TEXT,\n" + " srctype INT,\n" + " srcid INT,\n" + " mtime TIMESTAMP,\n" + " UNIQUE(target, srctype, srcid)\n" + ");\n" + "CREATE INDEX repo_backlink_src ON repo_backlink(srcid, srctype);\n" + "CREATE TABLE repo_attachment(\n" + " attachid INTEGER PRIMARY KEY,\n" + " isLatest BOOLEAN DEFAULT 0,\n" + " mtime TIMESTAMP,\n" + " src TEXT,\n" + " target TEXT,\n" + " filename TEXT,\n" + " comment TEXT,\n" + " user TEXT\n" + ");\n" + "CREATE INDEX repo_attachment_idx1\n" + " ON repo_attachment(target, filename, mtime);\n" + "CREATE INDEX repo_attachment_idx2 ON repo_attachment(src);\n" + "CREATE TABLE repo_cherrypick(\n" + " parentid INT,\n" + " childid INT,\n" + " isExclude BOOLEAN DEFAULT false,\n" + " PRIMARY KEY(parentid, childid)\n" + ") WITHOUT ROWID;\n" + "CREATE INDEX repo_cherrypick_cid ON repo_cherrypick(childid);\n" + "CREATE TABLE repo_ticket(\n" + " -- Do not change any column that begins with tkt_\n" + " tkt_id INTEGER PRIMARY KEY,\n" + " tkt_uuid TEXT UNIQUE,\n" + " tkt_mtime DATE,\n" + " tkt_ctime DATE,\n" + " -- Add as many fields as required below this line\n" + " type TEXT,\n" + " status TEXT,\n" + " subsystem TEXT,\n" + " priority TEXT,\n" + " severity TEXT,\n" + " foundin TEXT,\n" + " private_contact TEXT,\n" + " resolution TEXT,\n" + " title TEXT,\n" + " comment TEXT\n" + ");\n" + "CREATE TABLE repo_ticketchng(\n" + " -- Do not change any column that begins with tkt_\n" + " tkt_id INTEGER REFERENCES ticket,\n" + " tkt_rid INTEGER REFERENCES blob,\n" + " tkt_mtime DATE,\n" + " -- Add as many fields as required below this line\n" + " login TEXT,\n" + " username TEXT,\n" + " mimetype TEXT,\n" + " icomment TEXT\n" + ");\n" + "CREATE INDEX repo_ticketchng_idx1 ON repo_ticketchng(tkt_id, tkt_mtime);\n" + "CREATE TRIGGER repo_alert_trigger1\n" + "AFTER INSERT ON repo_event BEGIN\n" + " INSERT INTO repo_pending_alert(eventid)\n" + " SELECT printf('%.1c%d',new.type,new.objid) WHERE true\n" + " ON CONFLICT(eventId) DO NOTHING;\n" + "END;\n" + "CREATE TABLE repo_vcache(\n" + " vid INTEGER, -- check-in ID\n" + " fname TEXT, -- filename\n" + " rid INTEGER, -- artifact ID\n" + " PRIMARY KEY(vid,fname)\n" + ") WITHOUT ROWID;\n" + "CREATE TABLE localdb_vvar(\n" + " name TEXT PRIMARY KEY NOT NULL,\n" + " value CLOB,\n" + " CHECK( typeof(name)='text' AND length(name)>=1 )\n" + ");\n" + "CREATE TABLE localdb_vfile(\n" + " id INTEGER PRIMARY KEY,\n" + " vid INTEGER REFERENCES blob,\n" + " chnged INT DEFAULT 0,\n" + " deleted BOOLEAN DEFAULT 0,\n" + " isexe BOOLEAN,\n" + " islink BOOLEAN,\n" + " rid INTEGER,\n" + " mrid INTEGER,\n" + " mtime INTEGER,\n" + " pathname TEXT,\n" + " origname TEXT, mhash,\n" + " UNIQUE(pathname,vid)\n" + ");\n" + "CREATE TABLE localdb_sqlite_stat1(tbl,idx,stat);\n" + "CREATE TABLE localdb_vcache(\n" + " vid INTEGER, -- check-in ID\n" + " fname TEXT, -- filename\n" + " rid INTEGER, -- artifact ID\n" + " PRIMARY KEY(vid,fname)\n" + ") WITHOUT ROWID;\n" + "CREATE TABLE localdb_stash(\n" + " stashid INTEGER PRIMARY KEY,\n" + " vid INTEGER,\n" + " hash TEXT,\n" + " comment TEXT,\n" + " ctime TIMESTAMP\n" + ");\n" + "CREATE TABLE localdb_stashfile(\n" + " stashid INTEGER REFERENCES stash,\n" + " isAdded BOOLEAN,\n" + " isRemoved BOOLEAN,\n" + " isExec BOOLEAN,\n" + " isLink BOOLEAN,\n" + " rid INTEGER,\n" + " hash TEXT,\n" + " origname TEXT,\n" + " newname TEXT,\n" + " delta BLOB,\n" + " PRIMARY KEY(newname, stashid)\n" + ");\n" + "CREATE TABLE localdb_vmerge(\n" + " id INTEGER REFERENCES vfile,\n" + " merge INTEGER,\n" + " mhash TEXT\n" + ");\n" + "CREATE UNIQUE INDEX localdb_vmergex1 ON localdb_vmerge(id,mhash);\n" + "CREATE TRIGGER localdb_vmerge_ck1 AFTER INSERT ON localdb_vmerge\n" + "WHEN new.mhash IS NULL BEGIN\n" + " SELECT raise(FAIL,\n" + " 'trying to update a newer checkout with an older version of Fossil');\n" + "END;\n" + "CREATE TABLE configdb_global_config(\n" + " name TEXT PRIMARY KEY,\n" + " value TEXT\n" + ");\n" + "CREATE TABLE configdb_sqlite_stat1(tbl,idx,stat);\n" +; + +#ifdef __linux__ +#include +#include + +/* +** Attempt to display I/O stats on Linux using /proc/PID/io +*/ +static void displayLinuxIoStats(FILE *out){ + FILE *in; + char z[200]; + sqlite3_snprintf(sizeof(z), z, "/proc/%d/io", getpid()); + in = fopen(z, "rb"); + if( in==0 ) return; + while( fgets(z, sizeof(z), in)!=0 ){ + static const struct { + const char *zPattern; + const char *zDesc; + } aTrans[] = { + { "rchar: ", "Bytes received by read():" }, + { "wchar: ", "Bytes sent to write():" }, + { "syscr: ", "Read() system calls:" }, + { "syscw: ", "Write() system calls:" }, + { "read_bytes: ", "Bytes rcvd from storage:" }, + { "write_bytes: ", "Bytes sent to storage:" }, + { "cancelled_write_bytes: ", "Cancelled write bytes:" }, + }; + int i; + for(i=0; i='0' && c<='9' ) return c - '0'; + if( c>='a' && c<='f' ) return c - 'a' + 10; + if( c>='A' && c<='F' ) return c - 'A' + 10; + return -1; +} + +/* +** Interpret zArg as an integer value, possibly with suffixes. +*/ +static int integerValue(const char *zArg){ + sqlite3_int64 v = 0; + static const struct { char *zSuffix; int iMult; } aMult[] = { + { "KiB", 1024 }, + { "MiB", 1024*1024 }, + { "GiB", 1024*1024*1024 }, + { "KB", 1000 }, + { "MB", 1000000 }, + { "GB", 1000000000 }, + { "K", 1000 }, + { "M", 1000000 }, + { "G", 1000000000 }, + }; + int i; + int isNeg = 0; + if( zArg[0]=='-' ){ + isNeg = 1; + zArg++; + }else if( zArg[0]=='+' ){ + zArg++; + } + if( zArg[0]=='0' && zArg[1]=='x' ){ + int x; + zArg += 2; + while( (x = hexDigitValue(zArg[0]))>=0 ){ + v = (v<<4) + x; + zArg++; + } + }else{ + while( isdigit(zArg[0]) ){ + v = v*10 + zArg[0] - '0'; + zArg++; + } + } + for(i=0; i0x7fffffff ){ + printf("ERROR: parameter too large - max 2147483648\n"); + exit(1); + } + return (int)(isNeg? -v : v); +} + + +int main(int argc, char **argv){ + const char *zCmd = 0; + int i; + int bAutovac = 0; + int showStats = 0; + const char *zDbName = "./startup.db"; + int nHeap = 0; + int mnHeap = 0; + + for(i=1; i=argc-2 ){ + printf("ERROR: missing arguments on %s\n", argv[i]); + exit(1); + } + nHeap = integerValue(argv[i+1]); + mnHeap = integerValue(argv[i+2]); + i += 2; + }else + if( strcmp(z,"-stats")==0 ){ + showStats = 1; + }else + { + printf("ERROR: unknown option \"%s\"\n", argv[i]); + usage(argv[0]); + } + } + if( zCmd==0 ){ + printf("ERROR: no COMMAND specified\n"); + usage(argv[0]); + } + if( strcmp(zCmd, "run")==0 ){ + sqlite3 *db; + int rc; + char *zErr = 0; + void *pHeap = 0; + if( nHeap>0 ){ + pHeap = malloc( nHeap ); + if( pHeap==0 ){ + printf("ERROR: cannot allocate %d-byte heap\n", nHeap); + exit(1); + } + rc = sqlite3_config(SQLITE_CONFIG_HEAP, pHeap, nHeap, mnHeap); + if( rc ){ + printf("ERROR: heap configuration failed: %d\n", rc); + exit(1); + } + } + rc = sqlite3_open(zDbName, &db); + if( rc ){ + printf("SQLite error: %s\n", sqlite3_errmsg(db)); + }else{ + sqlite3_exec(db, "PRAGMA synchronous", 0, 0, &zErr); + } + if( zErr ){ + printf("ERROR: %s\n", zErr); + sqlite3_free(zErr); + } + if( showStats ){ + int iCur, iHi; + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHi, 0); + printf("-- Lookaside Slots Used: %d (max %d)\n", iCur,iHi); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHi, 0); + printf("-- Successful lookasides: %d\n", iHi); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur,&iHi,0); + printf("-- Lookaside size faults: %d\n", iHi); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur,&iHi,0); + printf("-- Lookaside OOM faults: %d\n", iHi); + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHi, 0); + printf("-- Pager Heap Usage: %d bytes\n", iCur); + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHi, 1); + printf("-- Page cache hits: %d\n", iCur); + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHi, 1); + printf("-- Page cache misses: %d\n", iCur); + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHi, 1); + printf("-- Page cache writes: %d\n", iCur); + sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHi, 0); + printf("-- Schema Heap Usage: %d bytes\n", iCur); + sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHi, 0); + printf("-- Statement Heap Usage: %d bytes\n", iCur); + } + sqlite3_close(db); + free(pHeap); + /* Global memory usage statistics printed after the database connection + ** has closed. Memory usage should be zero at this point. */ + if( showStats ){ + int iCur, iHi; + sqlite3_status(SQLITE_STATUS_MEMORY_USED, &iCur, &iHi, 0); + printf("-- Memory Used (bytes): %d (max %d)\n", iCur,iHi); + sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &iCur, &iHi, 0); + printf("-- Outstanding Allocations: %d (max %d)\n", iCur,iHi); + sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &iCur, &iHi, 0); + printf("-- Pcache Overflow Bytes: %d (max %d)\n", iCur,iHi); + sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &iCur, &iHi, 0); + printf("-- Largest Allocation: %d bytes\n",iHi); + sqlite3_status(SQLITE_STATUS_PAGECACHE_SIZE, &iCur, &iHi, 0); + printf("-- Largest Pcache Allocation: %d bytes\n",iHi); +#ifdef __linux__ + displayLinuxIoStats(stdout); +#endif + } + return 0; + } + if( strcmp(zCmd, "init")==0 ){ + sqlite3 *db; + char *zAux; + char *zErr = 0; + int rc; + unlink(zDbName); + zAux = sqlite3_mprintf("%s-journal", zDbName); + unlink(zAux); + sqlite3_free(zAux); + zAux = sqlite3_mprintf("%s-wal", zDbName); + unlink(zAux); + sqlite3_free(zAux); + rc = sqlite3_open(zDbName, &db); + if( rc ){ + printf("SQLite error: %s\n", sqlite3_errmsg(db)); + }else{ + sqlite3_exec(db, "BEGIN", 0, 0, 0); + sqlite3_exec(db, zTestSchema, 0, 0, &zErr); + sqlite3_exec(db, "COMMIT", 0, 0, 0); + } + if( zErr ){ + printf("ERROR: %s\n", zErr); + sqlite3_free(zErr); + } + sqlite3_close(db); + return 0; + + } +} diff --git a/test/tabfunc01.test b/test/tabfunc01.test index 04bc6f42a..797267b33 100644 --- a/test/tabfunc01.test +++ b/test/tabfunc01.test @@ -227,6 +227,50 @@ do_test tabfunc01-751 { } } {5.0 x5 | 7.0 x7 | 13.0 x13 | 17.0 x17 | 23.0 x23 |} +ifcapable altertable { + do_test tabfunc01-800 { + catchsql { + ALTER TABLE generate_series ADD COLUMN col2; + } + } {1 {virtual tables may not be altered}} + do_test tabfunc01-810 { + catchsql { + ALTER TABLE generate_series RENAME TO flubber; + } + } {1 {table generate_series may not be altered}} + do_test tabfunc01-820 { + catchsql { + ALTER TABLE generate_series RENAME start TO flubber; + } + } {1 {table generate_series may not be altered}} + do_test tabfunc01-830 { + catchsql { + ALTER TABLE generate_series DROP COLUMN start; + } + } {1 {table generate_series may not be altered}} + do_test tabfunc01-900 { + catchsql { + ALTER TABLE pragma_compile_options ADD COLUMN col2; + } + } {1 {virtual tables may not be altered}} + do_test tabfunc01-910 { + catchsql { + ALTER TABLE pragma_compile_options RENAME TO flubber; + } + } {1 {table pragma_compile_options may not be altered}} + do_test tabfunc01-920 { + catchsql { + ALTER TABLE pragma_compile_options RENAME start TO flubber; + } + } {1 {table pragma_compile_options may not be altered}} + do_test tabfunc01-930 { + catchsql { + ALTER TABLE pragma_compile_options DROP COLUMN start; + } + } {1 {table pragma_compile_options may not be altered}} +} + + # Free up memory allocations intarray_addr int64array_addr diff --git a/test/tester.tcl b/test/tester.tcl index 3b91ff46c..304808b6b 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -174,8 +174,14 @@ proc get_pwd {} { # case of the result to what Tcl considers canonical, which would # defeat the purpose of this procedure. # + if {[info exists ::env(ComSpec)]} { + set comSpec $::env(ComSpec) + } else { + # NOTE: Hard-code the typical default value. + set comSpec {C:\Windows\system32\cmd.exe} + } return [string map [list \\ /] \ - [string trim [exec -- $::env(ComSpec) /c echo %CD%]]] + [string trim [exec -- $comSpec /c echo %CD%]]] } else { return [pwd] } @@ -902,8 +908,8 @@ proc catchcmdex {db {cmd ""}} { proc filepath_normalize {p} { # test cases should be written to assume "unix"-like file paths if {$::tcl_platform(platform)!="unix"} { - # lreverse*2 as a hack to remove any unneeded {} after the string map - lreverse [lreverse [string map {\\ /} [regsub -nocase -all {[a-z]:[/\\]+} $p {/}]]] + string map [list \\ / \{/ / .db\} .db] \ + [regsub -nocase -all {[a-z]:[/\\]+} $p {/}] } { set p } @@ -1689,9 +1695,12 @@ proc crashsql {args} { set cfile [string map {\\ \\\\} [file nativename [file join [get_pwd] $crashfile]]] set f [open crash.tcl w] + puts $f "sqlite3_initialize ; sqlite3_shutdown" + puts $f "catch { install_malloc_faultsim 1 }" puts $f "sqlite3_crash_enable 1 $dfltvfs" puts $f "sqlite3_crashparams $blocksize $dc $crashdelay $cfile" puts $f "sqlite3_test_control_pending_byte $::sqlite_pending_byte" + puts $f "autoinstall_test_functions" # This block sets the cache size of the main database to 10 # pages. This is done in case the build is configured to omit @@ -1719,7 +1728,7 @@ proc crashsql {args} { } close $f set r [catch { - exec [info nameofexec] crash.tcl >@stdout + exec [info nameofexec] crash.tcl >@stdout 2>@stdout } msg] # Windows/ActiveState TCL returns a slightly different diff --git a/test/threadtest5.c b/test/threadtest5.c new file mode 100644 index 000000000..6e6610ff6 --- /dev/null +++ b/test/threadtest5.c @@ -0,0 +1,339 @@ +/* +** 2021-05-12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** Testing threading behavior when multiple database connections in separate +** threads of the same process are all talking to the same database file. +** +** For best results, ensure that SQLite is compiled with HAVE_USLEEP=1 +** +** Only works on unix platforms. +** +** Usage: +** +** ./threadtest5 ?DATABASE? +** +** If DATABASE is omitted, it defaults to using file:/mem?vfs=memdb. +*/ +#include "sqlite3.h" +#include +#include +#include +#include +#include +#include + +/* Name of the in-memory database */ +static char *zDbName = 0; + +/* True for debugging */ +static int eVerbose = 0; + +/* If rc is not SQLITE_OK, then print an error message and stop +** the test. +*/ +static void error_out(int rc, const char *zCtx, int lineno){ + if( rc!=SQLITE_OK ){ + fprintf(stderr, "error %d at %d in \"%s\"\n", rc, lineno, zCtx); + exit(-1); + } +} + +#if 0 +/* Return the number of milliseconds since the Julian epoch (-4714-11-24). +*/ +static sqlite3_int64 gettime(void){ + sqlite3_int64 tm; + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + pVfs->xCurrentTimeInt64(pVfs, &tm); + return tm; +} +#endif + +/* Run the SQL in the second argument. +*/ +static int exec( + sqlite3 *db, + const char *zId, + int lineno, + const char *zFormat, + ... +){ + int rc; + va_list ap; + char *zSql; + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + if( eVerbose){ + printf("%s:%d: [%s]\n", zId, lineno, zSql); + fflush(stdout); + } + rc = sqlite3_exec(db, zSql, 0, 0, 0); + if( rc && eVerbose ){ + printf("%s:%d: return-code %d\n", zId, lineno, rc); + fflush(stdout); + } + sqlite3_free(zSql); + return rc; +} + +/* Generate a perpared statement from the input SQL +*/ +static sqlite3_stmt *prepare( + sqlite3 *db, + const char *zId, + int lineno, + const char *zFormat, + ... +){ + int rc; + va_list ap; + char *zSql; + sqlite3_stmt *pStmt = 0; + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + if( eVerbose){ + printf("%s:%d: [%s]\n", zId, lineno, zSql); + fflush(stdout); + } + + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc ){ + printf("%s:%d: ERROR - %s\n", zId, lineno, sqlite3_errmsg(db)); + exit(-1); + } + sqlite3_free(zSql); + return pStmt; +} + +/* +** Wait for table zTable to exist in the schema. +*/ +static void waitOnTable(sqlite3 *db, const char *zWorker, const char *zTable){ + while(1){ + int eFound = 0; + sqlite3_stmt *q = prepare(db, zWorker, __LINE__, + "SELECT 1 FROM sqlite_schema WHERE name=%Q", zTable); + if( sqlite3_step(q)==SQLITE_ROW && sqlite3_column_int(q,0)!=0 ){ + eFound = 1; + } + sqlite3_finalize(q); + if( eFound ) return; + sqlite3_sleep(1); + } +} + +/* +** Return true if x is a prime number +*/ +static int isPrime(int x){ + int i; + if( x<2 ) return 1; + for(i=2; i*i<=x; i++){ + if( (x%i)==0 ) return 0; + } + return 1; +} + +/* Each worker thread runs an instance of the following */ +static void *worker(void *pArg){ + int rc; + const char *zName = (const char*)pArg; + sqlite3 *db = 0; + + if( eVerbose ){ + printf("%s: startup\n", zName); + fflush(stdout); + } + + rc = sqlite3_open(zDbName, &db); + error_out(rc, "sqlite3_open", __LINE__); + sqlite3_busy_timeout(db, 2000); + + while( 1 ){ + sqlite3_stmt *q1; + int tid = -1; + q1 = prepare(db, zName, __LINE__, + "UPDATE task SET doneby=%Q" + " WHERE tid=(SELECT tid FROM task WHERE doneby IS NULL LIMIT 1)" + "RETURNING tid", zName + ); + if( sqlite3_step(q1)==SQLITE_ROW ){ + tid = sqlite3_column_int(q1,0); + } + sqlite3_finalize(q1); + if( tid<0 ) break; + if( eVerbose ){ + printf("%s: starting task %d\n", zName, tid); + fflush(stdout); + } + if( tid==1 ){ + exec(db, zName, __LINE__, + "CREATE TABLE IF NOT EXISTS p1(x INTEGER PRIMARY KEY);" + ); + }else if( tid>=2 && tid<=51 ){ + int a, b, i; + waitOnTable(db, zName, "p1"); + a = (tid-2)*200 + 1; + b = a+200; + for(i=a; i=53 && tid<=62 ){ + int a, b, i; + waitOnTable(db, zName, "p2"); + a = (tid-53)*10 + 2; + b = a+9; + for(i=a; i<=b; i++){ + exec(db, zName, __LINE__, + "DELETE FROM p2 WHERE x>%d AND (x %% %d)==0", i, i); + } + } + if( eVerbose ){ + printf("%s: completed task %d\n", zName, tid); + fflush(stdout); + } + sqlite3_sleep(1); + } + + sqlite3_close(db); + + if( eVerbose ){ + printf("%s: exit\n", zName); + fflush(stdout); + } + return 0; +} + +/* Print a usage comment and die */ +static void usage(const char *argv0){ + printf("Usage: %s [options]\n", argv0); + printf( + " -num-workers N Run N worker threads\n" + " -v Debugging output\n" + ); + exit(1); +} + +/* Maximum number of threads */ +#define MX_WORKER 100 + +/* +** Main routine +*/ +int main(int argc, char **argv){ + int i; + int nWorker = 4; + int rc; + sqlite3 *db = 0; + sqlite3_stmt *q; + pthread_t aWorker[MX_WORKER]; + char aWorkerName[MX_WORKER][8]; + + for(i=1; iMX_WORKER ){ + printf("number of threads must be between 1 and %d\n", MX_WORKER); + exit(1); + } + continue; + } + printf("unknown option: %s\n", argv[i]); + usage(argv[0]); + } + if( zDbName==0 ) zDbName = "file:/mem?vfs=memdb"; + + sqlite3_config(SQLITE_CONFIG_URI, (int)1); + rc = sqlite3_open(zDbName, &db); + error_out(rc, "sqlite3_open", __LINE__); + + rc = exec(db, "SETUP", __LINE__, + "DROP TABLE IF EXISTS task;\n" + "DROP TABLE IF EXISTS p1;\n" + "DROP TABLE IF EXISTS p2;\n" + "DROP TABLE IF EXISTS verify;\n" + "CREATE TABLE IF NOT EXISTS task(\n" + " tid INTEGER PRIMARY KEY,\n" + " doneby TEXT\n" + ");\n" + "WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100)" + "INSERT INTO task(tid) SELECT x FROM c;\n" + ); + error_out(rc, "sqlite3_exec", __LINE__); + + for(i=0; i? AND O_ORDERDATE? AND O_ORDERDATE ?3 AND scope = ?4 AND langid = ? + GROUP BY term, langid + HAVING (1.0 - ((distance / 100.0) / CAST( length(?1) - 1 AS REAL ))) >= ? + } -1 TAIL] + sqlite3_bind_parameter_count $::STMT +} {6} + +do_test 12.1.1 { + sqlite3_bind_text $STMT 1 "A" 1 + sqlite3_bind_text $STMT 2 "B" 1 + sqlite3_bind_text $STMT 3 "C" 1 + sqlite3_bind_text $STMT 4 "D" 1 + sqlite3_bind_text $STMT 5 "E" 1 + sqlite3_bind_text $STMT 6 "F" 1 + sqlite3_expanded_sql $STMT +} { + SELECT + substr(word,1,length('A')-1) AS term, + distance, + langid, + score + FROM + nameFtsFuzzySearchTable + WHERE + word MATCH ('A') AND abs('A') = abs(term) + AND top = 'B' AND distance > 'C' AND scope = 'D' AND langid = 'E' + GROUP BY term, langid + HAVING (1.0 - ((distance / 100.0) / CAST( length('A') - 1 AS REAL ))) >= 'F' + } + +do_test 12.1.2 { + sqlite3_finalize $STMT +} {SQLITE_OK} + + finish_test diff --git a/test/transitive1.test b/test/transitive1.test index 97dc5a71a..875533849 100644 --- a/test/transitive1.test +++ b/test/transitive1.test @@ -344,13 +344,40 @@ do_execsql_test transitive1-560 { do_execsql_test transitive1-560eqp { EXPLAIN QUERY PLAN SELECT * FROM c1 WHERE x=y AND y=z AND z='abc'; -} {/SCAN TABLE c1/} +} {/SCAN c1/} do_execsql_test transitive1-570 { SELECT * FROM c1 WHERE x=y AND z=y AND z='abc'; } {} do_execsql_test transitive1-570eqp { EXPLAIN QUERY PLAN SELECT * FROM c1 WHERE x=y AND z=y AND z='abc'; -} {/SEARCH TABLE c1 USING INDEX c1x/} +} {/SEARCH c1 USING INDEX c1x/} + +# 2021-05-04 forum https://sqlite.org/forum/forumpost/eb8613976a +reset_db +do_execsql_test transitive1-600 { + CREATE TABLE t0(a0 INT, b1 INT); + CREATE INDEX t0b1 ON t0(b1); + CREATE TABLE t1(w,x,y,z3 INT); + INSERT INTO t0(a0, b1) VALUES (0,1); + INSERT INTO t1(w,x,y,z3) VALUES (7,8,9,1); +} {} +do_execsql_test transitive1-610 { + SELECT ALL * FROM t0,t1 WHERE b1=z3 AND a0=z3; +} {} +do_execsql_test transitive1-620 { + SELECT ALL * FROM t0,t1 WHERE likely(b1=z3) AND a0=z3; +} {} +do_execsql_test transitive1-630 { + DROP TABLE t0; + DROP TABLE t1; + CREATE TABLE t0(c0 INT, c1 INT UNIQUE); + CREATE TABLE t1(c0 INT); + INSERT INTO t0(c0, c1) VALUES (0, 1); + INSERT INTO t1(c0) VALUES (1); + SELECT ALL * FROM t1 NATURAL JOIN t0 WHERE (t1.c0=t0.c1); + SELECT ALL * FROM t1 NATURAL JOIN t0 WHERE (likely(t1.c0=t0.c1)); + SELECT ALL * FROM t1,t0 WHERE (likely(t1.c0=t0.c1) AND t1.c0=t0.c0); +} {} finish_test diff --git a/test/trigger2.test b/test/trigger2.test index 1be72131f..6e007e969 100644 --- a/test/trigger2.test +++ b/test/trigger2.test @@ -49,6 +49,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix trigger2 ifcapable {!trigger} { finish_test return @@ -769,6 +770,24 @@ do_execsql_test trigger2-10.1 { } ;# ifcapable view -integrity_check trigger2-999 +#------------------------------------------------------------------------- +reset_db +do_execsql_test 11.1 { + CREATE TABLE t1(a INT PRIMARY KEY, b, c REAL, d, e); + CREATE TABLE t2(a INT, b, c REAL, d, e, PRIMARY KEY(a,b)) WITHOUT ROWID; + CREATE UNIQUE INDEX t2c ON t2(c); + CREATE UNIQUE INDEX t2d ON t2(d); + CREATE UNIQUE INDEX t2e ON t2(e); +} + +do_catchsql_test 11.2 { + CREATE TRIGGER r1 BEFORE INSERT ON t1 BEGIN + INSERT INTO t2(a,b,c,d,e) VALUES(91,NULL,93,94,?1) + ON CONFLICT(b,a) DO NOTHING + ON CONFLICT DO UPDATE SET b=?1; + END; +} {1 {trigger cannot use variables}} + finish_test + diff --git a/test/trigger9.test b/test/trigger9.test index 326fa63d4..6e31d1af9 100644 --- a/test/trigger9.test +++ b/test/trigger9.test @@ -242,15 +242,12 @@ do_execsql_test 4.1 { END; } -do_execsql_test 4.2 { +do_catchsql_test 4.2 { DELETE FROM v1 WHERE rowid=1; -} {} +} {1 {no such column: rowid}} -do_execsql_test 4.3 { +do_catchsql_test 4.3 { UPDATE v1 SET a=b WHERE rowid=2; -} {} - - - +} {1 {no such column: rowid}} finish_test diff --git a/test/triggerE.test b/test/triggerE.test index 9db703532..de4b06858 100644 --- a/test/triggerE.test +++ b/test/triggerE.test @@ -58,6 +58,8 @@ foreach {tn defn} { 8 { BEFORE UPDATE ON t1 BEGIN UPDATE t2 SET c = ?; END; } 9 { BEFORE UPDATE ON t1 BEGIN UPDATE t2 SET c = 1 WHERE d = ?; END; } 10 { AFTER INSERT ON t1 BEGIN SELECT * FROM pragma_stats(?); END; } + 11 { BEFORE INSERT ON t1 BEGIN + INSERT INTO t1 SELECT max(b) OVER(ORDER BY $1) FROM t1; END } } { catchsql {drop trigger tr1} do_catchsql_test 1.1.$tn "CREATE TRIGGER tr1 $defn" [list 1 $errmsg] diff --git a/test/triggerupfrom.test b/test/triggerupfrom.test index 9bfacb843..a731045c4 100644 --- a/test/triggerupfrom.test +++ b/test/triggerupfrom.test @@ -171,4 +171,3 @@ do_execsql_test 4.3 { finish_test - diff --git a/test/unionall.test b/test/unionall.test new file mode 100644 index 000000000..7554a3b30 --- /dev/null +++ b/test/unionall.test @@ -0,0 +1,367 @@ +# 2020-12-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 regression tests for SQLite library. The +# focus of this file is flattening UNION ALL sub-queries. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix unionall + +do_execsql_test 1.0 { + CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT); + CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT); + CREATE TABLE t1_c(e INTEGER PRIMARY KEY, f TEXT); + + INSERT INTO t1_a VALUES(1, 'one'), (4, 'four'); + INSERT INTO t1_b VALUES(2, 'two'), (5, 'five'); + INSERT INTO t1_c VALUES(3, 'three'), (6, 'six'); + + CREATE VIEW t1 AS + SELECT a, b FROM t1_a UNION ALL + SELECT c, d FROM t1_b UNION ALL + SELECT e, f FROM t1_c; + + CREATE TABLE i1(x); + INSERT INTO i1 VALUES(2), (5), (6), (1); +} + +do_execsql_test 1.1 { + SELECT a, b FROM ( + SELECT a, b FROM t1_a UNION ALL + SELECT c, d FROM t1_b UNION ALL + SELECT e, f FROM t1_c + ) ORDER BY a +} { + 1 one 2 two 3 three 4 four 5 five 6 six +} + +do_execsql_test 1.2 { + SELECT a, b FROM t1 ORDER BY a +} { + 1 one 2 two 3 three 4 four 5 five 6 six +} + +do_execsql_test 1.3 { + SELECT a, b FROM i1, t1 WHERE a=x ORDER BY a +} {1 one 2 two 5 five 6 six} + + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.1.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(1, 'ONE'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(2, 'TWO'); + INSERT INTO t1 VALUES(3, 'three'); + INSERT INTO t1 VALUES(3, 'THREE'); +} + +do_execsql_test 2.1.1 { + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<3 + ) + SELECT * FROM ( + SELECT 0 AS i UNION ALL SELECT i FROM s UNION ALL SELECT 0 + ), t1 WHERE x=i; +} { + 1 1 one 1 1 ONE 2 2 two 2 2 TWO 3 3 three 3 3 THREE +} + +do_catchsql_test 2.1.2 { + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<3 UNION ALL SELECT 4 + ) + SELECT * FROM s, t1 WHERE x=i; +} {1 {circular reference: s}} + +do_execsql_test 2.2.0 { + CREATE TABLE t2_a(k INTEGER PRIMARY KEY, v TEXT); + CREATE TABLE t2_b(k INTEGER PRIMARY KEY, v TEXT); + + CREATE VIEW t2 AS + SELECT * FROM t2_a + UNION ALL + SELECT * FROM t2_b; + + CREATE TRIGGER t2_insert INSTEAD OF INSERT ON t2 BEGIN + INSERT INTO t2_a SELECT new.k, new.v WHERE (new.k%2)==0; + INSERT INTO t2_b SELECT new.k, new.v WHERE (new.k%2)==1; + END; + + INSERT INTO t2 VALUES(5, 'v'), (4, 'iv'), (3, 'iii'), (2, 'ii'); +} + +do_execsql_test 2.2.1 { + SELECT * FROM t1, t2 WHERE x=k; +} { + 2 two 2 ii 2 TWO 2 ii 3 three 3 iii 3 THREE 3 iii +} + +do_execsql_test 2.2.2 { + SELECT * FROM t1 LEFT JOIN t2 ON (x=k); +} { + 1 one {} {} + 1 ONE {} {} + 2 two 2 ii 2 TWO 2 ii 3 three 3 iii 3 THREE 3 iii +} + +do_execsql_test 2.2.3 { + SELECT x1.*, x2.* FROM t2 AS x1, t2 AS x2 WHERE x1.k=x2.k+1 +} { + 4 iv 3 iii + 3 iii 2 ii + 5 v 4 iv +} + +do_execsql_test 2.2.4 { + SELECT * FROM t1, t2 WHERE x=k ORDER BY y; +} { + 3 THREE 3 iii + 2 TWO 2 ii + 3 three 3 iii + 2 two 2 ii +} +do_execsql_test 2.2.5 { + SELECT * FROM t1, t2 WHERE x=k ORDER BY y||''; +} { + 3 THREE 3 iii + 2 TWO 2 ii + 3 three 3 iii + 2 two 2 ii +} +do_execsql_test 2.2.6 { + SELECT * FROM t1, t2 WHERE x=k ORDER BY v +} { + 2 two 2 ii + 2 TWO 2 ii + 3 three 3 iii + 3 THREE 3 iii +} +do_execsql_test 2.2.7 { + SELECT * FROM t1, t2 WHERE x=k ORDER BY v||'' +} { + 2 two 2 ii + 2 TWO 2 ii + 3 three 3 iii + 3 THREE 3 iii +} +do_execsql_test 2.2.8 { + SELECT * FROM t1, t2 WHERE x=k ORDER BY k,v||'' +} { + 2 two 2 ii + 2 TWO 2 ii + 3 three 3 iii + 3 THREE 3 iii +} +do_execsql_test 2.2.9a { + SELECT * FROM t1, t2 ORDER BY +k +} { + 1 one 2 ii 1 ONE 2 ii 2 two 2 ii + 2 TWO 2 ii 3 three 2 ii 3 THREE 2 ii + + 1 one 3 iii 1 ONE 3 iii 2 two 3 iii + 2 TWO 3 iii 3 three 3 iii 3 THREE 3 iii + + 1 one 4 iv 1 ONE 4 iv 2 two 4 iv + 2 TWO 4 iv 3 three 4 iv 3 THREE 4 iv + + 1 one 5 v 1 ONE 5 v 2 two 5 v + 2 TWO 5 v 3 three 5 v 3 THREE 5 v +} + +do_execsql_test 2.2.9b { + SELECT * FROM t1, t2 ORDER BY k +} { + 1 one 2 ii 1 ONE 2 ii 2 two 2 ii + 2 TWO 2 ii 3 three 2 ii 3 THREE 2 ii + + 1 one 3 iii 1 ONE 3 iii 2 two 3 iii + 2 TWO 3 iii 3 three 3 iii 3 THREE 3 iii + + 1 one 4 iv 1 ONE 4 iv 2 two 4 iv + 2 TWO 4 iv 3 three 4 iv 3 THREE 4 iv + + 1 one 5 v 1 ONE 5 v 2 two 5 v + 2 TWO 5 v 3 three 5 v 3 THREE 5 v +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(c INTEGER PRIMARY KEY, d TEXT); + INSERT INTO t1 VALUES(1,2); + CREATE TABLE t3_a(k INTEGER PRIMARY KEY, v TEXT); + INSERT INTO t3_a VALUES(2,'ii'); + CREATE TABLE t3_b(k INTEGER PRIMARY KEY, v TEXT); + CREATE VIEW t3 AS + SELECT * FROM t3_a + UNION ALL + SELECT * FROM t3_b; +} {} + +do_execsql_test 3.1 { + SELECT * FROM t1, t3 ORDER BY k; +} {1 2 2 ii} + +reset_db +do_execsql_test 4.0 { + + CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1_a VALUES(123, 't1_a'); + CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT); + + CREATE VIEW t1 AS + SELECT a, b FROM t1_a + UNION ALL + SELECT c, d FROM t1_b; + + CREATE TABLE t3_a(k INTEGER PRIMARY KEY, v TEXT); + INSERT INTO t3_a VALUES(456, 't3_a'); + CREATE TABLE t3_b(k INTEGER PRIMARY KEY, v TEXT); + + CREATE VIEW t3 AS + SELECT * FROM t3_a + UNION ALL + SELECT * FROM t3_b; +} + +do_execsql_test 4.1 { + SELECT * FROM t1, t3 ORDER BY k; +} {123 t1_a 456 t3_a} + +do_execsql_test 4.2 { + SELECT * FROM (SELECT * FROM t1, t3) ORDER BY k; +} {123 t1_a 456 t3_a} + +do_execsql_test 4.3 { + SELECT * FROM (SELECT * FROM t1, t3), ( + SELECT max(a) OVER () FROM t1 + UNION ALL + SELECT min(a) OVER () FROM t1 + ) + ORDER BY k; +} { + 123 t1_a 456 t3_a 123 + 123 t1_a 456 t3_a 123 +} + +do_execsql_test 4.3 { + SELECT * FROM (SELECT * FROM t1, t3), ( + SELECT group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a), + group_concat(a) OVER (ORDER BY a) + FROM t1 + ) + ORDER BY k; +} { + 123 t1_a 456 t3_a 123 123 123 123 123 123 123 123 123 +} + +do_execsql_test 4.3 { + SELECT * FROM (SELECT * FROM t1, t3) AS o, ( + SELECT * FROM t1 LEFT JOIN t3 ON a=k + ); +} { + 123 t1_a 456 t3_a 123 t1_a {} {} +} + +# 2020-12-30: dbsqlfuzz find +reset_db +do_execsql_test 5.1 { + CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1_a VALUES(1,'one'); + INSERT INTO t1_a VALUES(0,NULL); + CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT); + INSERT INTO t1_b VALUES(2,'two'); + INSERT INTO t1_b VALUES(5,'five'); + CREATE TABLE t1_c(e INTEGER PRIMARY KEY, f TEXT); + INSERT INTO t1_c VALUES(3,'three'); + INSERT INTO t1_c VALUES(6,'six'); + CREATE TABLE t2(k,v); + INSERT INTO t2 VALUES(5,'v'); + INSERT INTO t2 VALUES(4,'iv'); + INSERT INTO t2 VALUES(3,'iii'); + INSERT INTO t2 VALUES(2,'ii'); + CREATE TABLE t3_a(k INTEGER PRIMARY KEY, v TEXT); + INSERT INTO t3_a VALUES(2,'ii'); + INSERT INTO t3_a VALUES(4,'iv'); + CREATE TABLE t3_b(k INTEG5R PRIMARY KEY, v TEXT); + INSERT INTO t3_b VALUES(NULL,'iii'); + INSERT INTO t3_b VALUES(NULL,'v'); + CREATE VIEW t1 AS + SELECT a, b FROM t1_a UNION ALL + SELECT c, d FROM t1_b UNION ALL + SELECT e, f FROM t1_c; + CREATE VIEW t3 AS + SELECT * FROM t3_a + UNION ALL + SELECT * FROM t3_b; + CREATE TRIGGER t3_insert INSTEAD OF INSERT ON t3 BEGIN + INSERT INTO t3_a SELECT new.k, new.v WHERE (new.k%2)==0; + INSERT INTO t3_b SELECT new.k, new.v WHERE (new.k%2)==1; + END; +} {} +do_execsql_test 5.10 { + SELECT *, '+' FROM t1 LEFT JOIN t2 ON (a NOT IN(SELECT v FROM t1, t3 WHERE a=k)=NOT EXISTS(SELECT 1 FROM t1 LEFT JOIN t3 ON (a=k))); +} {0 {} {} {} + 1 one {} {} + 2 two {} {} + 5 five {} {} + 3 three {} {} + 6 six {} {} +} +do_execsql_test 5.20 { + SELECT *, '+' FROM t1 LEFT JOIN t3 ON (a NOT IN(SELECT v FROM t1 LEFT JOIN t2 ON (a=k))=k); +} {0 {} {} {} + 1 one {} {} + 2 two {} {} + 5 five {} {} + 3 three {} {} + 6 six {} {} +} + +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a,b); + INSERT INTO t1 VALUES(1,2); + CREATE TABLE t2(a,b); + INSERT INTO t2 VALUES(3,4); + + CREATE TABLE t3(a,b); + INSERT INTO t3 VALUES(5,6); + CREATE TABLE t4(a,b); + INSERT INTO t4 VALUES(7,8); + + CREATE TABLE t5(a,b); + INSERT INTO t5 VALUES(9,10); +} + +do_execsql_test 6.1 { + WITH x(c) AS ( + SELECT 1000 FROM t1 UNION ALL SELECT 800 FROM t2 + ), + y(d) AS ( + SELECT 100 FROM t3 UNION ALL SELECT 400 FROM t4 + ) + SELECT * FROM t5, x, y; +} { + 9 10 1000 100 9 10 1000 400 + 9 10 800 100 9 10 800 400 +} + +# 2021-04-26 dbsqlfuzz 88ed5c66789fced139d148aed823cba7c0926dd7 +reset_db +do_execsql_test 7.1 { + WITH c1(x) AS (VALUES(0) UNION ALL SELECT 100+x FROM c1 WHERE x<100 UNION ALL SELECT 1+x FROM c1 WHERE x<1) + SELECT x, y, '|' + FROM c1 AS x1, (SELECT x+1 AS y FROM c1 WHERE x<1 UNION ALL SELECT 1+x FROM c1 WHERE 1 100" - {SEARCH TABLE t1 USING INDEX i1 (a>?)} - {SCAN TABLE t1} + {SEARCH t1 USING INDEX i1 (a>?)} + {SCAN t1} 3 "SELECT * FROM t1 WHERE a = ? ORDER BY rowid" - {SEARCH TABLE t1 USING INDEX i1 (a=?)} - {SEARCH TABLE t1 USING INDEX i1 (a=?)*USE TEMP B-TREE FOR ORDER BY} + {SEARCH t1 USING INDEX i1 (a=?)} + {SEARCH t1 USING INDEX i1 (a=?)*USE TEMP B-TREE FOR ORDER BY} 4 "SELECT max(a) FROM t1" - {SEARCH TABLE t1 USING COVERING INDEX i1} - {SEARCH TABLE t1} + {SEARCH t1 USING COVERING INDEX i1} + {SEARCH t1} 5 "SELECT group_concat(b) FROM t1 GROUP BY a" - {SCAN TABLE t1 USING INDEX i1} - {SCAN TABLE t1*USE TEMP B-TREE FOR GROUP BY} + {SCAN t1 USING INDEX i1} + {SCAN t1*USE TEMP B-TREE FOR GROUP BY} 6 "SELECT * FROM t1 WHERE a = ?" - {SEARCH TABLE t1 USING INDEX i1 (a=?)} - {SEARCH TABLE t1 USING INDEX i1 (a=?)} + {SEARCH t1 USING INDEX i1 (a=?)} + {SEARCH t1 USING INDEX i1 (a=?)} 7 "SELECT count(*) FROM t1" - {SCAN TABLE t1 USING COVERING INDEX i1} - {SCAN TABLE t1} + {SCAN t1 USING COVERING INDEX i1} + {SCAN t1} } { do_eqp_test 1.$idxmode.$tn $sql $r($idxmode) } diff --git a/test/upfrom1.test b/test/upfrom1.test index 7996f9770..204f3512e 100644 --- a/test/upfrom1.test +++ b/test/upfrom1.test @@ -175,4 +175,36 @@ do_execsql_test 3.1 { SELECT * FROM t0, t1; } {} +# Problem described by forum post https://sqlite.org/forum/forumpost/a274248080 +# +reset_db +do_execsql_test 4.1 { + CREATE TABLE t1(x INT); INSERT INTO t1 VALUES(1); + CREATE TABLE t2(y INT); INSERT INTO t2 VALUES(2); + WITH t1 AS (SELECT y+100 AS x FROM t2) + UPDATE t1 SET x=(SELECT x FROM t1); + SELECT x, y FROM t1, t2; +} {102 2} +do_execsql_test 4.2 { + WITH t1 AS (SELECT y+100 AS x FROM t2) + UPDATE t1 SET x=x+y FROM t2; + SELECT x, y FROM t1, t2; +} {104 2} + +# 2021-05-20 +# Forum https://sqlite.org/forum/forumpost/339f487de5 by Yu Liang +# A bad assert() +# +reset_db +do_execsql_test 5.1 { + CREATE TABLE t1(a); + INSERT INTO t1(a) VALUES(5); + CREATE VIEW t2 AS SELECT a FROM t1 UNION ALL SELECT a FROM t1; + CREATE TABLE t3(b,c); + INSERT INTO t3(b,c) VALUES(1,2); + UPDATE t3 SET (c,b) = (SELECT 3,4) FROM t1, t2; + SELECT * FROM t3; +} {4 3} + + finish_test diff --git a/test/upfrom2.test b/test/upfrom2.test index f903c1f4f..81c847a60 100644 --- a/test/upfrom2.test +++ b/test/upfrom2.test @@ -367,5 +367,3 @@ foreach {tn update nm} { finish_test - - diff --git a/test/upfrom3.test b/test/upfrom3.test index d30b3fa28..9936716b5 100644 --- a/test/upfrom3.test +++ b/test/upfrom3.test @@ -259,4 +259,3 @@ foreach {tn wo} { } finish_test - diff --git a/test/upfromfault.test b/test/upfromfault.test index fcb595608..e876c071c 100644 --- a/test/upfromfault.test +++ b/test/upfromfault.test @@ -137,4 +137,3 @@ do_faultsim_test 2.3 -prep { finish_test - diff --git a/test/upsert5.test b/test/upsert5.test new file mode 100644 index 000000000..3161abf15 --- /dev/null +++ b/test/upsert5.test @@ -0,0 +1,411 @@ +# 2020-12-11 +# +# 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. +# +#*********************************************************************** +# +# Test cases for generalized UPSERT + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix upsert5 + +foreach {tn sql} { + 1 { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c UNIQUE, d UNIQUE, e UNIQUE) } + 2 { CREATE TABLE t1(a INT PRIMARY KEY, b, c UNIQUE, d UNIQUE, e UNIQUE) } + 3 { CREATE TABLE t1(a INT PRIMARY KEY, b, c UNIQUE, d UNIQUE, e UNIQUE) WITHOUT ROWID} + 4 { CREATE TABLE t1(e UNIQUE, d UNIQUE, c UNIQUE, a INTEGER PRIMARY KEY, b) } + 5 { CREATE TABLE t1(e UNIQUE, d UNIQUE, c UNIQUE, a INT PRIMARY KEY, b) } + 6 { CREATE TABLE t1(e UNIQUE, d UNIQUE, c UNIQUE, a INT PRIMARY KEY, b) WITHOUT ROWID} +} { + reset_db + execsql $sql + + do_execsql_test 1.$tn.100 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,3,4,5) + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 a 3 4 5} + do_execsql_test 1.$tn.101 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,4,5) + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 c 3 4 5} + do_execsql_test 1.$tn.102 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,4,5) + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 d 3 4 5} + do_execsql_test 1.$tn.103 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 e 3 4 5} + do_execsql_test 1.$tn.200 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 a 3 4 5} + do_execsql_test 1.$tn.201 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,3,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 c 3 4 5} + do_execsql_test 1.$tn.202 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,3,4,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 c 3 4 5} + do_execsql_test 1.$tn.203 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 a 3 4 5} + do_execsql_test 1.$tn.204 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 a 3 4 5} + do_execsql_test 1.$tn.210 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 a 3 4 5} + do_execsql_test 1.$tn.211 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 d 3 4 5} + do_execsql_test 1.$tn.212 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 a 3 4 5} + do_execsql_test 1.$tn.213 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(a) DO UPDATE SET b='a' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 e 3 4 5} + do_execsql_test 1.$tn.214 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e' + ON CONFLICT(a) DO UPDATE SET b='a'; + SELECT a,b,c,d,e FROM t1; + } {1 e 3 4 5} + do_execsql_test 1.$tn.215 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e' + ON CONFLICT(a) DO UPDATE SET b='a'; + SELECT a,b,c,d,e FROM t1; + } {1 e 3 4 5} + do_execsql_test 1.$tn.216 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(e) DO UPDATE SET b='e' + ON CONFLICT(a) DO UPDATE SET b='a'; + SELECT a,b,c,d,e FROM t1; + } {1 a 3 4 5} + + do_execsql_test 1.$tn.300 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(a) DO UPDATE SET b='a1' + ON CONFLICT(a) DO UPDATE SET b='a2' + ON CONFLICT(a) DO UPDATE SET b='a3' + ON CONFLICT(a) DO UPDATE SET b='a4' + ON CONFLICT(a) DO UPDATE SET b='a5' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 a1 3 4 5} + do_execsql_test 1.$tn.301 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT(a) DO UPDATE SET b='a1' + ON CONFLICT(a) DO UPDATE SET b='a2' + ON CONFLICT(a) DO UPDATE SET b='a3' + ON CONFLICT(a) DO UPDATE SET b='a4' + ON CONFLICT(a) DO UPDATE SET b='a5' + ON CONFLICT(e) DO UPDATE SET b='e'; + SELECT a,b,c,d,e FROM t1; + } {1 e 3 4 5} + + do_execsql_test 1.$tn.400 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.401 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.402 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.403 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 c 3 4 5} + do_execsql_test 1.$tn.404 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,4,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 c 3 4 5} + do_execsql_test 1.$tn.405 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 d 3 4 5} + + do_execsql_test 1.$tn.410 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.411 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.412 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,4,95) + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.413 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95) + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + + do_execsql_test 1.$tn.420 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO NOTHING + ON CONFLICT(d) DO NOTHING + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.421 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT(c) DO NOTHING + ON CONFLICT(d) DO NOTHING + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 x 3 4 5} + do_execsql_test 1.$tn.422 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,4,95) + ON CONFLICT(c) DO NOTHING + ON CONFLICT(d) DO NOTHING + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 2 3 4 5} + do_execsql_test 1.$tn.423 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95) + ON CONFLICT(c) DO NOTHING + ON CONFLICT(d) DO NOTHING + ON CONFLICT DO UPDATE set b='x'; + SELECT a,b,c,d,e FROM t1; + } {1 2 3 4 5} + + do_execsql_test 1.$tn.500 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO NOTHING; + SELECT a,b,c,d,e FROM t1; + } {1 2 3 4 5} + do_execsql_test 1.$tn.501 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO NOTHING; + SELECT a,b,c,d,e FROM t1; + } {1 2 3 4 5} + do_execsql_test 1.$tn.502 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO NOTHING; + SELECT a,b,c,d,e FROM t1; + } {1 2 3 4 5} + do_execsql_test 1.$tn.503 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO NOTHING; + SELECT a,b,c,d,e FROM t1; + } {1 c 3 4 5} + do_execsql_test 1.$tn.504 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,4,95) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO NOTHING; + SELECT a,b,c,d,e FROM t1; + } {1 c 3 4 5} + do_execsql_test 1.$tn.505 { + DELETE FROM t1; + INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5); + INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,5) + ON CONFLICT(c) DO UPDATE SET b='c' + ON CONFLICT(d) DO UPDATE SET b='d' + ON CONFLICT DO NOTHING; + SELECT a,b,c,d,e FROM t1; + } {1 d 3 4 5} + +} + +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE t2(a, b, c REAL, d, e, PRIMARY KEY(a,b)) WITHOUT ROWID; + CREATE UNIQUE INDEX t2c ON t2(c); +} + +do_catchsql_test 2.1 { + INSERT INTO t2(a,b,c,e,d) VALUES(1,2,3,4,5) + ON CONFLICT(c) DO UPDATE SET b='' + ON CONFLICT((SELECT t2 FROM nosuchtable)) DO NOTHING; + +} {1 {no such table: nosuchtable}} + +finish_test diff --git a/test/vacuum2.test b/test/vacuum2.test index 0350c8ec4..89fdb506c 100644 --- a/test/vacuum2.test +++ b/test/vacuum2.test @@ -56,7 +56,7 @@ do_test vacuum2-2.1 { } hexio_get_int [hexio_read test.db 24 4] } [expr {[hexio_get_int [hexio_read test.db 24 4]]+3}] -do_test vacuum2-2.1 { +do_test vacuum2-2.2 { execsql { VACUUM } diff --git a/test/vacuum6.test b/test/vacuum6.test new file mode 100644 index 000000000..e9a1542f2 --- /dev/null +++ b/test/vacuum6.test @@ -0,0 +1,112 @@ +# 2016-08-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. +# +#*********************************************************************** +# +# This file implements a test for VACUUM on attached databases. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix vacuum6 + +# If the VACUUM statement is disabled in the current build, skip all +# the tests in this file. +# +ifcapable !vacuum { + finish_test + return +} + + +do_execsql_test 1.0 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y); + INSERT INTO t1 VALUES(1, 1); +} {} + +do_execsql_test 1.1 { + VACUUM +} + +reset_db +do_execsql_test 1.2 { + CREATE TABLE t1(x,b); + CREATE INDEX x1 ON t1(x); + CREATE INDEX x2 ON t1(x); + CREATE INDEX x3 ON t1(x); + INSERT INTO t1 SELECT 2,''; + VACUUM; +} + +#------------------------------------------------------------------------- +# +reset_db +foreach {tn sz} {1 400 2 4000 3 9999} { + reset_db + do_execsql_test 2.$tn.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO t1 SELECT i, randomblob($sz) FROM s; + } + + do_execsql_test 2.$tn.2 { + vacuum; + } + + do_execsql_test 2.$tn.3 { + PRAGMA integrity_check; + } {ok} +} + +reset_db +do_execsql_test 3.0 { + PRAGMA page_size = 1024; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y); + INSERT INTO t1 VALUES(2, randomblob(1200)); +} {} +do_execsql_test 3.1 { + PRAGMA page_size = 512; + VACUUM; +} +do_execsql_test 3.2 { + PRAGMA integrity_check +} {ok} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 4.0 { + CREATE TABLE tx(a, b); + CREATE INDEX i1 ON tx(b); + WITH s(i) AS ( + SELECT 8000 UNION ALL SELECT i+1 FROM s WHERE i<10000 + ) + INSERT INTO tx SELECT i, randomblob(i) FROM s; + + SELECT sum(length(b)) FROM tx; +} {18009000} +foreach {tn pgsz av} { + 1 2048 0 + 2 1024 1 + 3 65536 0 + 4 8192 1 + 5 512 0 + 6 4096 1 +} { + do_execsql_test 4.1.$tn.1 " + PRAGMA page_size = $pgsz; + PRAGMA auto_vacuum = $av; + " + do_execsql_test 4.1.$tn.2 VACUUM + integrity_check 4.1.$tn.3 +} + +finish_test diff --git a/test/view.test b/test/view.test index 66109c498..b30d4162d 100644 --- a/test/view.test +++ b/test/view.test @@ -44,12 +44,17 @@ do_test view-1.1.100 { SELECT * FROM v1 ORDER BY a; } } {1 {access to view "v1" prohibited}} +do_execsql_test view-1.1.101 { + CREATE TEMP VIEW v1temp AS SELECT a, b FROM t1; + SELECT * FROM v1temp ORDER BY a; +} {1 2 4 5 7 8} do_test view-1.1.110 { db config enable_view on catchsql { SELECT * FROM v1 ORDER BY a; + SELECT * FROM v1temp ORDER BY a; } -} {0 {1 2 4 5 7 8}} +} {0 {1 2 4 5 7 8 1 2 4 5 7 8}} do_test view-1.2 { catchsql { ROLLBACK; diff --git a/test/view2.test b/test/view2.test new file mode 100644 index 000000000..e5df1437f --- /dev/null +++ b/test/view2.test @@ -0,0 +1,49 @@ +# 2021 May 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. The +# focus of this file is testing VIEW statements. +# +# $Id: view.test,v 1.39 2008/12/14 14:45:21 danielk1977 Exp $ +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Omit this entire file if the library is not configured with views enabled. +ifcapable !view { + finish_test + return +} +set testprefix view2 + +do_execsql_test 1.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + CREATE VIEW v1 AS SELECT * FROM ( + WITH x1 AS (SELECT y, x FROM t1) + SELECT * FROM x1 + ); +} + +do_execsql_test 1.1 { + SELECT * FROM v1 +} {2 1} + +do_execsql_test 1.2 { + CREATE VIEW v3 AS SELECT * FROM main.t1; + WITH t1(a, b) AS ( SELECT 3, 4 ) SELECT * FROM v3; +} {1 2} + +breakpoint +do_execsql_test 1.3 { + CREATE VIEW v2 AS SELECT * FROM t1; + WITH t1(a, b) AS ( SELECT 3, 4 ) SELECT * FROM v2; +} {1 2} + +finish_test diff --git a/test/vtab1.test b/test/vtab1.test index e8891a632..3d2e23336 100644 --- a/test/vtab1.test +++ b/test/vtab1.test @@ -983,6 +983,7 @@ do_test vtab1.10-5 { proc match_func {args} {return ""} do_test vtab1.10-6 { set echo_module "" + sqlite_delete_function db match db function match match_func execsql { SELECT * FROM e WHERE match('pattern', rowid, 'pattern2'); diff --git a/test/wal.test b/test/wal.test index acc278008..234668240 100644 --- a/test/wal.test +++ b/test/wal.test @@ -1515,5 +1515,36 @@ foreach mode {OFF MEMORY PERSIST DELETE TRUNCATE WAL} { db close } +# 2021-03-10 forum post https://sqlite.org/forum/forumpost/a006d86f72 +# +file delete test.db +sqlite3 db test.db +db eval {PRAGMA journal_mode=WAL} +for {set i 0} {$i<$SQLITE_MAX_ATTACHED} {incr i} { + do_test wal-26.1.$i { + file delete attached-$i.db + db eval "ATTACH 'attached-$i.db' AS a$i;" + db eval "PRAGMA a$i.journal_mode=WAL;" + db eval "CREATE TABLE a$i.t$i (x);" + db eval "INSERT INTO t$i VALUES(zeroblob(10000));" + db eval "DELETE FROM t$i;" + db eval "INSERT INTO t$i VALUES(randomblob(10000));" + expr {[file size attached-$i.db-wal]>10000} + } {1} +} +for {set i [expr {$SQLITE_MAX_ATTACHED-1}]} {$i>=0} {incr i -1} { + do_test wal-26.2.$i { + db eval "PRAGMA a$i.wal_checkpoint(TRUNCATE);" + file size attached-$i.db-wal + } {0} + for {set j 0} {$j<$i} {incr j} { + do_test wal-26.2.$i.$j { + expr {[file size attached-$j.db-wal]>10000} + } {1} + } +} +db close + + test_restore_config_pagecache finish_test diff --git a/test/walsetlk.test b/test/walsetlk.test index 66513ad2d..8c0a9659f 100644 --- a/test/walsetlk.test +++ b/test/walsetlk.test @@ -195,4 +195,3 @@ do_test 3.1 { } {1 {database is locked}} finish_test - diff --git a/test/walvfs.test b/test/walvfs.test index f21b65e7e..d6c58656f 100644 --- a/test/walvfs.test +++ b/test/walvfs.test @@ -146,16 +146,23 @@ proc xWrite {method file args} { incr ::cnt -1 if {$::cnt==0} { sqlite3_memdebug_fail 1 -repeat 0 - catchsql { SELECT 'a big long string!' } + # For this test to pass, the following statement must call malloc() at + # least once. Even if the lookaside is enabled. + set ::xwrite_stmt_res [catchsql { SELECT hex(randomblob(4000)) }] sqlite3_interrupt db } } return SQLITE_OK } +set ::xwrite_stmt_res "" do_catchsql_test 3.2 { PRAGMA wal_checkpoint } {1 {out of memory}} +do_test 3.2.2 { + set ::xwrite_stmt_res +} {1 {out of memory}} +unset ::xwrite_stmt_res #------------------------------------------------------------------------- # diff --git a/test/where.test b/test/where.test index fa3bcd836..298248a6e 100644 --- a/test/where.test +++ b/test/where.test @@ -70,10 +70,10 @@ do_test where-1.1.1b { } {3 121 10 3} do_eqp_test where-1.1.2 { SELECT x, y, w FROM t1 WHERE w=10 -} {*SEARCH TABLE t1 USING INDEX i1w (w=?)*} +} {*SEARCH t1 USING INDEX i1w (w=?)*} do_eqp_test where-1.1.2b { SELECT x, y, w FROM t1 WHERE w IS 10 -} {*SEARCH TABLE t1 USING INDEX i1w (w=?)*} +} {*SEARCH t1 USING INDEX i1w (w=?)*} do_test where-1.1.3 { db status step } {0} @@ -85,13 +85,13 @@ do_test where-1.1.5 { } {99} do_eqp_test where-1.1.6 { SELECT x, y, w FROM t1 WHERE +w=10 -} {*SCAN TABLE t1*} +} {*SCAN t1*} do_test where-1.1.7 { count {SELECT x, y, w AS abc FROM t1 WHERE abc=10} } {3 121 10 3} do_eqp_test where-1.1.8 { SELECT x, y, w AS abc FROM t1 WHERE abc=10 -} {*SEARCH TABLE t1 USING INDEX i1w (w=?)*} +} {*SEARCH t1 USING INDEX i1w (w=?)*} do_test where-1.1.9 { db status step } {0} @@ -118,22 +118,22 @@ do_test where-1.4.1b { } {11 3 144 3} do_eqp_test where-1.4.2 { SELECT w, x, y FROM t1 WHERE 11=w AND x>2 -} {*SEARCH TABLE t1 USING INDEX i1w (w=?)*} +} {*SEARCH t1 USING INDEX i1w (w=?)*} do_eqp_test where-1.4.2b { SELECT w, x, y FROM t1 WHERE 11 IS w AND x>2 -} {*SEARCH TABLE t1 USING INDEX i1w (w=?)*} +} {*SEARCH t1 USING INDEX i1w (w=?)*} do_test where-1.4.3 { count {SELECT w AS a, x AS b, y FROM t1 WHERE 11=a AND b>2} } {11 3 144 3} do_eqp_test where-1.4.4 { SELECT w AS a, x AS b, y FROM t1 WHERE 11=a AND b>2 -} {*SEARCH TABLE t1 USING INDEX i1w (w=?)*} +} {*SEARCH t1 USING INDEX i1w (w=?)*} do_test where-1.5 { count {SELECT x, y FROM t1 WHERE y<200 AND w=11 AND x>2} } {3 144 3} do_eqp_test where-1.5.2 { SELECT x, y FROM t1 WHERE y<200 AND w=11 AND x>2 -} {*SEARCH TABLE t1 USING INDEX i1w (w=?)*} +} {*SEARCH t1 USING INDEX i1w (w=?)*} do_test where-1.6 { count {SELECT x, y FROM t1 WHERE y<200 AND x>2 AND w=11} } {3 144 3} @@ -145,10 +145,10 @@ do_test where-1.8 { } {3 144 3} do_eqp_test where-1.8.2 { SELECT x, y FROM t1 WHERE w>10 AND y=144 AND x=3 -} {*SEARCH TABLE t1 USING INDEX i1xy (x=? AND y=?)*} +} {*SEARCH t1 USING INDEX i1xy (x=? AND y=?)*} do_eqp_test where-1.8.3 { SELECT x, y FROM t1 WHERE y=144 AND x=3 -} {*SEARCH TABLE t1 USING COVERING INDEX i1xy (x=? AND y=?)*} +} {*SEARCH t1 USING COVERING INDEX i1xy (x=? AND y=?)*} do_test where-1.9 { count {SELECT x, y FROM t1 WHERE y=144 AND w>10 AND x=3} } {3 144 3} diff --git a/test/where2.test b/test/where2.test index 0a0533506..7a7e9b92e 100644 --- a/test/where2.test +++ b/test/where2.test @@ -76,10 +76,12 @@ proc queryplan {sql} { set eqp [execsql "EXPLAIN QUERY PLAN $sql"] # puts eqp=$eqp foreach {a b c x} $eqp { - if {[regexp { TABLE (\w+ AS )?(\w+) USING.* INDEX (\w+)\y} \ - $x all as tab idx]} { + if {[regexp {SCAN CONSTANT} $x]} { + # noop + } elseif {[regexp {(SCAN|SEARCH) (\w+ AS )?(\w+) USING.* INDEX (\w+)\y} \ + $x all ss as tab idx]} { lappend data $tab $idx - } elseif {[regexp { TABLE (\w+ AS )?(\w+)\y} $x all as tab]} { + } elseif {[regexp {(SCAN|SEARCH) (\w+ AS )?(\w+)\y} $x all ss as tab]} { lappend data $tab * } } @@ -753,7 +755,7 @@ do_execsql_test where2-12.1 { SELECT a.x, b.x FROM t12 AS a JOIN t12 AS b ON a.y=b.x WHERE (b.x=$abc OR b.y=$abc); -} {/.*SEARCH TABLE t12 AS b .*SEARCH TABLE t12 AS b .*/} +} {/SEARCH b .*SEARCH b /} } # Verify that all necessary OP_OpenRead opcodes occur in the OR optimization. diff --git a/test/where3.test b/test/where3.test index 4d4ea3ee6..20af995cf 100644 --- a/test/where3.test +++ b/test/where3.test @@ -111,10 +111,12 @@ proc queryplan {sql} { set eqp [execsql "EXPLAIN QUERY PLAN $sql"] # puts eqp=$eqp foreach {a b c x} $eqp { - if {[regexp { TABLE (\w+ AS )?(\w+) USING.* INDEX (\w+)\y} \ - $x all as tab idx]} { + if {[regexp {SCAN CONSTANT} $x]} { + # noop + } elseif {[regexp {(SCAN|SEARCH) (\w+ AS )?(\w+) USING.* INDEX (\w+)\y} \ + $x all ss as tab idx]} { lappend data $tab $idx - } elseif {[regexp { TABLE (\w+ AS )?(\w+)\y} $x all as tab]} { + } elseif {[regexp {(SCAN|SEARCH) (\w+ AS )?(\w+)\y} $x all ss as tab]} { lappend data $tab * } } @@ -240,15 +242,15 @@ do_eqp_test where3-3.0a { SELECT * FROM t302, t301 WHERE t302.x=5 AND t301.a=t302.y; } { QUERY PLAN - |--SCAN TABLE t302 - `--SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t302 + `--SEARCH t301 USING INTEGER PRIMARY KEY (rowid=?) } do_eqp_test where3-3.1 { SELECT * FROM t301, t302 WHERE t302.x=5 AND t301.a=t302.y; } { QUERY PLAN - |--SCAN TABLE t302 - `--SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?) + |--SCAN t302 + `--SEARCH t301 USING INTEGER PRIMARY KEY (rowid=?) } do_execsql_test where3-3.2 { SELECT * FROM t301 WHERE c=3 AND a IS NULL; @@ -269,25 +271,25 @@ do_execsql_test where3-4.0 { EXPLAIN QUERY PLAN SELECT * FROM t400, t401, t402 WHERE t402.z GLOB 'abc*'; } { - 0 0 2 {SCAN TABLE t402} - 0 1 0 {SCAN TABLE t400} - 0 2 1 {SCAN TABLE t401} + 0 0 2 {SCAN t402} + 0 1 0 {SCAN t400} + 0 2 1 {SCAN t401} } 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} - 0 1 0 {SCAN TABLE t400} - 0 2 2 {SCAN TABLE t402} + 0 0 1 {SCAN t401} + 0 1 0 {SCAN t400} + 0 2 2 {SCAN t402} } 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} - 0 1 1 {SCAN TABLE t401} - 0 2 2 {SCAN TABLE t402} + 0 0 0 {SCAN t400} + 0 1 1 {SCAN t401} + 0 2 2 {SCAN t402} } } ;# endif @@ -321,8 +323,8 @@ do_eqp_test where3-5.0a { ORDER BY bbb.title COLLATE NOCASE ASC; } { QUERY PLAN - |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) - |--SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH aaa USING INDEX aaa_333 (fk=?) + |--SEARCH bbb USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test where3-5.1 { @@ -334,8 +336,8 @@ do_eqp_test where3-5.1 { ORDER BY bbb.title COLLATE NOCASE ASC; } { QUERY PLAN - |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) - |--SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH aaa USING INDEX aaa_333 (fk=?) + |--SEARCH bbb USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test where3-5.2 { @@ -347,8 +349,8 @@ do_eqp_test where3-5.2 { ORDER BY bbb.title COLLATE NOCASE ASC; } { QUERY PLAN - |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) - |--SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH aaa USING INDEX aaa_333 (fk=?) + |--SEARCH bbb USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test where3-5.3 { @@ -360,8 +362,8 @@ do_eqp_test where3-5.3 { ORDER BY bbb.title COLLATE NOCASE ASC; } { QUERY PLAN - |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) - |--SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH aaa USING INDEX aaa_333 (fk=?) + |--SEARCH bbb USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR ORDER BY } diff --git a/test/where7.test b/test/where7.test index 81111a9de..830530634 100644 --- a/test/where7.test +++ b/test/where7.test @@ -23354,10 +23354,10 @@ do_eqp_test where7-3.2 { QUERY PLAN |--MULTI-INDEX OR | |--INDEX 1 - | | `--SEARCH TABLE t301 USING COVERING INDEX t301_c4 (c4=?) + | | `--SEARCH t301 USING COVERING INDEX t301_c4 (c4=?) | `--INDEX 2 - | `--SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?) - |--SEARCH TABLE t302 USING INDEX t302_c8_c3 (c8=? AND c3>?) + | `--SEARCH t301 USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH t302 USING INDEX t302_c8_c3 (c8=? AND c3>?) `--USE TEMP B-TREE FOR ORDER BY } diff --git a/test/where9.test b/test/where9.test index 0f770dfee..429708f1f 100644 --- a/test/where9.test +++ b/test/where9.test @@ -362,12 +362,12 @@ ifcapable explain { WHERE t1.a=80 AND ((t1.c=t2.c AND t1.d=t2.d) OR t1.f=t2.f) } [string map {"\n " \n} { QUERY PLAN - |--SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?) `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t2 USING INDEX t2d (d=?) + | `--SEARCH t2 USING INDEX t2d (d=?) `--INDEX 3 - `--SEARCH TABLE t2 USING COVERING INDEX t2f (f=?) + `--SEARCH t2 USING COVERING INDEX t2f (f=?) }] do_eqp_test where9-3.2 { SELECT coalesce(t2.a,9999) @@ -375,12 +375,12 @@ ifcapable explain { WHERE t1.a=80 } [string map {"\n " \n} { QUERY PLAN - |--SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?) `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t2 USING INDEX t2d (d=?) + | `--SEARCH t2 USING INDEX t2d (d=?) `--INDEX 2 - `--SEARCH TABLE t2 USING COVERING INDEX t2f (f=?) + `--SEARCH t2 USING COVERING INDEX t2f (f=?) }] } @@ -461,23 +461,23 @@ do_eqp_test where9-5.1 { QUERY PLAN `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t1 USING INDEX t1c (c=?) + | `--SEARCH t1 USING INDEX t1c (c=?) `--INDEX 2 - `--SEARCH TABLE t1 USING INDEX t1d (d=?) + `--SEARCH t1 USING INDEX t1d (d=?) } # In contrast, b=1000 is preferred over any OR-clause. # do_eqp_test where9-5.2 { SELECT a FROM t1 WHERE b=1000 AND (c=31031 OR d IS NULL) -} {SEARCH TABLE t1 USING INDEX t1b (b=?)} +} {SEARCH t1 USING INDEX t1b (b=?)} # Likewise, inequalities in an AND are preferred over inequalities in # an OR. # do_eqp_test where9-5.3 { SELECT a FROM t1 WHERE b>1000 AND (c>=31031 OR d IS NULL) -} {SEARCH TABLE t1 USING INDEX t1b (b>?)} +} {SEARCH t1 USING INDEX t1b (b>?)} ############################################################################ # Make sure OR-clauses work correctly on UPDATE and DELETE statements. @@ -982,6 +982,23 @@ do_test where9-10.2 { } } {1 {} 1} - +# dbsqlfuzz 9df1d53c24c4c96af0dae15ee764897af415ac76 +# The MULTI-INDEX OR processing evaluates the same WHERE-clause sub-expression +# twice. But if that sub-expression contains a UNION ALL SELECT statement +# subject to query flattening, the sub-expression might be transformed in a +# way that it can only be code-generated once. An assert() will fail on +# the second attempt to generate code from the same sub-expression. +# The solution is to make a copy of sub-expressions used by MULTI-INDEX OR +# +reset_db +do_execsql_test where9-11.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + CREATE TABLE t2_a(k INTEGER PRIMARY KEY, v TEXT); + CREATE TABLE t2_b(k INTEGER PRIMARY KEY, v TEXT); + CREATE VIEW t2 AS SELECT * FROM t2_a UNION ALL SELECT * FROM t2_b; + SELECT 1 FROM t1 JOIN t1 USING(a) + WHERE (a=1) + OR (a=2 AND (SELECT 4 FROM t2,(SELECT 5 FROM t1 ORDER BY a) WHERE a)); +} {} finish_test diff --git a/test/whereE.test b/test/whereE.test index a6b8f481b..31086b249 100644 --- a/test/whereE.test +++ b/test/whereE.test @@ -47,16 +47,16 @@ do_execsql_test 1.1 { CREATE UNIQUE INDEX t2zx ON t2(z,x); EXPLAIN QUERY PLAN SELECT x FROM t1, t2 WHERE a=z AND c=x; -} {/.*SCAN TABLE t1.*SEARCH TABLE t2.*/} +} {/.*SCAN t1.*SEARCH t2.*/} do_execsql_test 1.2 { EXPLAIN QUERY PLAN SELECT x FROM t2, t1 WHERE a=z AND c=x; -} {/.*SCAN TABLE t1.*SEARCH TABLE t2.*/} +} {/.*SCAN t1.*SEARCH t2.*/} do_execsql_test 1.3 { ANALYZE; EXPLAIN QUERY PLAN SELECT x FROM t1, t2 WHERE a=z AND c=x; -} {/.*SCAN TABLE t1.*SEARCH TABLE t2.*/} +} {/.*SCAN t1.*SEARCH t2.*/} do_execsql_test 1.4 { EXPLAIN QUERY PLAN SELECT x FROM t2, t1 WHERE a=z AND c=x; -} {/.*SCAN TABLE t1.*SEARCH TABLE t2.*/} +} {/.*SCAN t1.*SEARCH t2.*/} finish_test diff --git a/test/whereF.test b/test/whereF.test index 121cc3cf2..ac7fd807b 100644 --- a/test/whereF.test +++ b/test/whereF.test @@ -63,7 +63,7 @@ foreach {tn sql} { } { do_test 1.$tn { db eval "EXPLAIN QUERY PLAN $sql" - } {/.*SCAN TABLE t2\y.*SEARCH TABLE t1\y.*/} + } {/.*SCAN t2\y.*SEARCH t1\y.*/} } do_execsql_test 2.0 { @@ -84,7 +84,7 @@ foreach {tn sql} { } { do_test 2.$tn { db eval "EXPLAIN QUERY PLAN $sql" - } {/.*SCAN TABLE t2\y.*SEARCH TABLE t1\y.*/} + } {/.*SCAN t2\y.*SEARCH t1\y.*/} } do_execsql_test 3.0 { @@ -109,7 +109,7 @@ foreach {tn sql} { } { do_test 3.$tn { db eval "EXPLAIN QUERY PLAN $sql" - } {/.*SCAN TABLE t2\y.*SEARCH TABLE t1\y.*/} + } {/.*SCAN t2\y.*SEARCH t1\y.*/} } do_execsql_test 4.0 { diff --git a/test/whereG.test b/test/whereG.test index abff2270b..6ca363ed8 100644 --- a/test/whereG.test +++ b/test/whereG.test @@ -156,16 +156,16 @@ do_execsql_test whereG-3.0 { } {} do_eqp_test whereG-3.1 { SELECT * FROM a, b WHERE b1=a1 AND a2=5; -} {/.*SCAN TABLE a.*SEARCH TABLE b USING INDEX .*b_1 .b1=..*/} +} {/.*SCAN a.*SEARCH b USING INDEX .*b_1 .b1=..*/} do_eqp_test whereG-3.2 { SELECT * FROM a, b WHERE a1=b1 AND a2=5; -} {/.*SCAN TABLE a.*SEARCH TABLE b USING INDEX .*b_1 .b1=..*/} +} {/.*SCAN a.*SEARCH b USING INDEX .*b_1 .b1=..*/} do_eqp_test whereG-3.3 { SELECT * FROM a, b WHERE a2=5 AND b1=a1; -} {/.*SCAN TABLE a.*SEARCH TABLE b USING INDEX .*b_1 .b1=..*/} +} {/.*SCAN a.*SEARCH b USING INDEX .*b_1 .b1=..*/} do_eqp_test whereG-3.4 { SELECT * FROM a, b WHERE a2=5 AND a1=b1; -} {/.*SCAN TABLE a.*SEARCH TABLE b USING INDEX .*b_1 .b1=..*/} +} {/.*SCAN a.*SEARCH b USING INDEX .*b_1 .b1=..*/} # Ticket [1e64dd782a126f48d78c43a664844a41d0e6334e]: # Incorrect result in a nested GROUP BY/DISTINCT due to the use of an OP_SCopy @@ -195,13 +195,13 @@ do_execsql_test 5.1 { } do_eqp_test 5.1.2 { SELECT * FROM t1 WHERE a>? -} {SEARCH TABLE t1 USING INDEX i1 (a>?)} +} {SEARCH t1 USING INDEX i1 (a>?)} do_eqp_test 5.1.3 { SELECT * FROM t1 WHERE likelihood(a>?, 0.9) -} {SCAN TABLE t1} +} {SCAN t1} do_eqp_test 5.1.4 { SELECT * FROM t1 WHERE likely(a>?) -} {SCAN TABLE t1} +} {SCAN t1} do_test 5.2 { for {set i 0} {$i < 100} {incr i} { @@ -212,29 +212,29 @@ do_test 5.2 { } {} do_eqp_test 5.2.2 { SELECT * FROM t1 WHERE likelihood(b>?, 0.01) -} {SEARCH TABLE t1 USING INDEX i1 (ANY(a) AND b>?)} +} {SEARCH t1 USING INDEX i1 (ANY(a) AND b>?)} do_eqp_test 5.2.3 { SELECT * FROM t1 WHERE likelihood(b>?, 0.9) -} {SCAN TABLE t1} +} {SCAN t1} do_eqp_test 5.2.4 { SELECT * FROM t1 WHERE likely(b>?) -} {SCAN TABLE t1} +} {SCAN t1} ifcapable stat4 { do_eqp_test 5.3.1.stat4 { SELECT * FROM t1 WHERE a=? - } {SCAN TABLE t1} + } {SCAN t1} } else { do_eqp_test 5.3.1 { SELECT * FROM t1 WHERE a=? - } {SEARCH TABLE t1 USING INDEX i1} + } {SEARCH t1 USING INDEX i1} } do_eqp_test 5.3.2 { SELECT * FROM t1 WHERE likelihood(a=?, 0.9) -} {SCAN TABLE t1} +} {SCAN t1} do_eqp_test 5.3.3 { SELECT * FROM t1 WHERE likely(a=?) -} {SCAN TABLE t1} +} {SCAN t1} # 2015-06-18 # Ticket [https://www.sqlite.org/see/tktview/472f0742a1868fb58862bc588ed70] @@ -334,4 +334,55 @@ do_execsql_test 10.1 { SELECT g = g FROM f GROUP BY h; } {1} +reset_db +do_execsql_test 11.0 { + CREATE TABLE t1(x PRIMARY KEY, y); + INSERT INTO t1 VALUES('AAA', 'BBB'); + + CREATE TABLE t2(z); + INSERT INTO t2 VALUES('t2'); + + CREATE TABLE t3(x PRIMARY KEY, y); + INSERT INTO t3 VALUES('AAA', 'AAA'); +} + +do_execsql_test 11.1.1 { + SELECT * FROM t1 JOIN t2 ON unlikely(x=y) AND y='AAA' +} +do_execsql_test 11.1.2 { + SELECT * FROM t1 JOIN t2 ON likely(x=y) AND y='AAA' +} +do_execsql_test 11.1.3 { + SELECT * FROM t1 JOIN t2 ON x=y AND y='AAA' +} + +do_execsql_test 11.2.1 { + SELECT * FROM t3 JOIN t2 ON unlikely(x=y) AND y='AAA' +} {AAA AAA t2} +do_execsql_test 11.2.2 { + SELECT * FROM t3 JOIN t2 ON likely(x=y) AND y='AAA' +} {AAA AAA t2} +do_execsql_test 11.2.3 { + SELECT * FROM t3 JOIN t2 ON x=y AND y='AAA' +} {AAA AAA t2} + +# 2021-06-14 forum https://sqlite.org/forum/forumpost/3b940c437a +# Affinity problem when a likely() function is used as a column in +# an index. +# +reset_db +do_execsql_test 12.0 { + CREATE TABLE t1(a REAL); + INSERT INTO t1(a) VALUES(123); + CREATE INDEX t1x1 ON t1(likely(a)); + SELECT typeof(likely(a)) FROM t1 NOT INDEXED; + SELECT typeof(likely(a)) FROM t1 INDEXED BY t1x1; +} {real real} +do_execsql_test 12.1 { + CREATE INDEX t1x2 ON t1(abs(a)); + SELECT typeof(abs(a)) FROM t1 NOT INDEXED; + SELECT typeof(abs(a)) FROM t1 INDEXED BY t1x2; +} {real real} + + finish_test diff --git a/test/whereI.test b/test/whereI.test index 7bb4ba39d..38e014884 100644 --- a/test/whereI.test +++ b/test/whereI.test @@ -32,9 +32,9 @@ do_eqp_test 1.1 { QUERY PLAN `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t1 USING INDEX i1 (b=?) + | `--SEARCH t1 USING INDEX i1 (b=?) `--INDEX 2 - `--SEARCH TABLE t1 USING INDEX i2 (c=?) + `--SEARCH t1 USING INDEX i2 (c=?) } do_execsql_test 1.2 { @@ -64,9 +64,9 @@ do_eqp_test 2.1 { QUERY PLAN `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH TABLE t2 USING INDEX i3 (b=?) + | `--SEARCH t2 USING INDEX i3 (b=?) `--INDEX 2 - `--SEARCH TABLE t2 USING INDEX i4 (c=?) + `--SEARCH t2 USING INDEX i4 (c=?) } do_execsql_test 2.2 { diff --git a/test/whereJ.test b/test/whereJ.test index af6ffafb3..c31ab8c37 100644 --- a/test/whereJ.test +++ b/test/whereJ.test @@ -402,7 +402,7 @@ do_eqp_test 3.4 { a = 4 AND b BETWEEN 20 AND 80 -- Matches 80 rows AND c BETWEEN 150 AND 160 -- Matches 10 rows -} {SEARCH TABLE t1 USING INDEX idx_c (c>? AND c? AND c? AND b? AND b9 OR b=9 ORDER BY +a; -} {/SEARCH TABLE t1 USING INDEX t1bc/} +} {/SEARCH t1 USING INDEX t1bc/} do_execsql_test 1.2 { SELECT a FROM t1 WHERE b>8 OR (b=8 AND c>7) ORDER BY +a; @@ -41,7 +41,7 @@ do_execsql_test 1.2 { do_execsql_test 1.2eqp { EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>8 OR (b=8 AND c>7) ORDER BY +a; -} {/SEARCH TABLE t1 USING INDEX t1bc/} +} {/SEARCH t1 USING INDEX t1bc/} do_execsql_test 1.3 { SELECT a FROM t1 WHERE (b=8 AND c>7) OR b>8 ORDER BY +a; @@ -49,7 +49,7 @@ do_execsql_test 1.3 { do_execsql_test 1.3eqp { EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE (b=8 AND c>7) OR b>8 ORDER BY +a; -} {/SEARCH TABLE t1 USING INDEX t1bc/} +} {/SEARCH t1 USING INDEX t1bc/} do_execsql_test 1.4 { SELECT a FROM t1 WHERE (b=8 AND c>7) OR 87) OR 87) OR (b>8 AND c NOT IN (4,5,6)) @@ -67,6 +67,6 @@ do_execsql_test 1.5eqp { EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE (b=8 AND c>7) OR (b>8 AND c NOT IN (4,5,6)) ORDER BY +a; -} {/SEARCH TABLE t1 USING INDEX t1bc/} +} {/SEARCH t1 USING INDEX t1bc/} finish_test diff --git a/test/whereL.test b/test/whereL.test index fbb424e91..b6f86fc2d 100644 --- a/test/whereL.test +++ b/test/whereL.test @@ -26,14 +26,13 @@ do_eqp_test 110 { SELECT * FROM t1, v4 WHERE t1.a=?1 AND v4.a=t1.a; } { QUERY PLAN - |--MATERIALIZE xxxxxx - | `--COMPOUND QUERY - | |--LEFT-MOST SUBQUERY - | | `--SEARCH TABLE t2 USING INDEX sqlite_autoindex_t2_1 (a=?) - | `--UNION ALL - | `--SEARCH TABLE t3 USING INDEX sqlite_autoindex_t3_1 (a=?) - |--SCAN SUBQUERY xxxxxx - `--SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + | `--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) + `--UNION ALL + |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + `--SEARCH t3 USING INDEX sqlite_autoindex_t3_1 (a=?) } # The scan of the t1 table goes first since that enables the ORDER BY @@ -46,9 +45,9 @@ do_eqp_test 120 { ORDER BY t1.a; } { QUERY PLAN - |--SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (a=?) - |--SEARCH TABLE t2 USING INDEX sqlite_autoindex_t2_1 (a=?) - `--SCAN TABLE t3 + |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + |--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) + `--SCAN t3 } # Constant propagation in the face of collating sequences: diff --git a/test/whereM.test b/test/whereM.test new file mode 100644 index 000000000..83436893e --- /dev/null +++ b/test/whereM.test @@ -0,0 +1,112 @@ +# 2021 May 27 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# Tests focused on the "constant propagation" that occurs within the +# WHERE clause of a SELECT statemente. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix whereM + + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b INTEGER, c TEXT, d REAL, e BLOB); + INSERT INTO t1 VALUES(10.0, 10.0, 10.0, 10.0, 10.0); + SELECT * FROM t1; +} { + 10.0 10 10.0 10.0 10.0 +} + +do_execsql_test 1.1.1 { + SELECT a=10, a = '10.0', a LIKE '10.0' FROM t1; +} {1 0 1} +do_execsql_test 1.1.2 { + SELECT count(*) FROM t1 WHERE a=10 AND a = '10.0' +} {0} +do_execsql_test 1.1.3 { + SELECT count(*) FROM t1 WHERE a=10 AND a LIKE '10.0' +} {1} +do_execsql_test 1.1.4 { + SELECT count(*) FROM t1 WHERE a='10.0' AND a LIKE '10.0' +} {0} + +do_execsql_test 1.2.1 { + SELECT b=10, b = '10.0', b LIKE '10.0', b LIKE '10' FROM t1; +} {1 1 0 1} +do_execsql_test 1.2.2 { + SELECT count(*) FROM t1 WHERE b=10 AND b = '10.0' +} {1} +do_execsql_test 1.2.3 { + SELECT count(*) FROM t1 WHERE b=10 AND b LIKE '10.0' +} {0} +do_execsql_test 1.2.4 { + SELECT count(*) FROM t1 WHERE b='10.0' AND b LIKE '10.0' +} {0} +do_execsql_test 1.2.3 { + SELECT count(*) FROM t1 WHERE b=10 AND b LIKE '10' +} {1} +do_execsql_test 1.2.4 { + SELECT count(*) FROM t1 WHERE b='10.0' AND b LIKE '10' +} {1} + +do_execsql_test 1.3.1 { + SELECT c=10, c = 10.0, c = '10.0', c LIKE '10.0' FROM t1; +} {0 1 1 1} +do_execsql_test 1.3.2 { + SELECT count(*) FROM t1 WHERE c=10 AND c = '10.0' +} {0} +do_execsql_test 1.3.3 { + SELECT count(*) FROM t1 WHERE c=10 AND c LIKE '10.0' +} {0} +do_execsql_test 1.3.4 { + SELECT count(*) FROM t1 WHERE c='10.0' AND c LIKE '10.0' +} {1} +do_execsql_test 1.3.5 { + SELECT count(*) FROM t1 WHERE c=10.0 AND c = '10.0' +} {1} +do_execsql_test 1.3.6 { + SELECT count(*) FROM t1 WHERE c=10.0 AND c LIKE '10.0' +} {1} + +do_execsql_test 1.4.1 { + SELECT d=10, d = 10.0, d = '10.0', d LIKE '10.0', d LIKE '10' FROM t1; +} {1 1 1 1 0} +do_execsql_test 1.4.2 { + SELECT count(*) FROM t1 WHERE d=10 AND d = '10.0' +} {1} +do_execsql_test 1.4.3 { + SELECT count(*) FROM t1 WHERE d=10 AND d LIKE '10.0' +} {1} +do_execsql_test 1.4.4 { + SELECT count(*) FROM t1 WHERE d='10.0' AND d LIKE '10.0' +} {1} +do_execsql_test 1.4.5 { + SELECT count(*) FROM t1 WHERE d='10' AND d LIKE '10.0' +} {1} + +do_execsql_test 1.5.1 { + SELECT e=10, e = '10.0', e LIKE '10.0', e LIKE '10' FROM t1; +} {1 0 1 0} +do_execsql_test 1.5.2 { + SELECT count(*) FROM t1 WHERE e=10 AND e = '10.0' +} {0} +do_execsql_test 1.5.3 { + SELECT count(*) FROM t1 WHERE e=10 AND e LIKE '10.0' +} {1} +do_execsql_test 1.5.4 { + SELECT count(*) FROM t1 WHERE e='10.0' AND e LIKE '10.0' +} {0} +do_execsql_test 1.5.5 { + SELECT count(*) FROM t1 WHERE e=10.0 AND e LIKE '10.0' +} {1} + +finish_test diff --git a/test/wherefault.test b/test/wherefault.test index 60330a92d..7f07ad5f5 100644 --- a/test/wherefault.test +++ b/test/wherefault.test @@ -56,4 +56,28 @@ do_malloc_test 2 -tclprep { SELECT count(*) FROM t1 WHERE a BETWEEN 5 AND 995 OR b BETWEEN 5 AND 900000; } +reset_db +do_execsql_test 3.0 { + PRAGMA writable_schema = 1; + BEGIN TRANSACTION; + CREATE TABLE t1( + a INT AS (c*11), + b TEXT AS (substr(d,1,3)) STORED, + c INTEGEB PRIMARI KEY, d TEXT + ); + CREATE INDEX t1a ON t1(a); + COMMIT; +} +faultsim_save_and_close + +do_faultsim_test 3.1 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + SELECT * FROM (SELECT a FROM t1 NATURAL JOIN t1 WHERE a IN (SELECT b FROM t1 ORDER BY b)) WHERE (SELECT a FROM t1 NATURAL JOIN (SELECT * FROM (SELECT a FROM t1 NATURAL JOIN t1 WHERE a IN (SELECT CASE b WHEN 82 THEN 207 WHEN 869 THEN 406 WHEN 85 THEN 83 WHEN 705 THEN 698 ELSE 1992229051 END%5 FROM t1 ORDER BY b)) WHERE (SELECT a FROM t1 NATURAL JOIN (SELECT b FROM t1 ORDER BY b) WHERE a IN (SELECT b FROM t1 ORDER BY b))) WHERE a ); + } +} -test { + faultsim_test_result {0 {}} +} + finish_test diff --git a/test/wherelimit.test b/test/wherelimit.test index 8db7a0cc2..f6efab668 100644 --- a/test/wherelimit.test +++ b/test/wherelimit.test @@ -94,21 +94,31 @@ ifcapable {update_delete_limit} { execsql {DELETE FROM t1 ORDER BY x LIMIT 5} execsql {SELECT count(*) FROM t1} } {15} + create_test_data 4 + do_test wherelimit-1.3b { + # limit 5 + execsql {DELETE FROM t1 RETURNING x, y, '|' ORDER BY x, y LIMIT 5} + } {1 1 | 1 2 | 1 3 | 1 4 | 2 1 |} + do_test wherelimit-1.3c { + execsql {SELECT count(*) FROM t1} + } {11} do_test wherelimit-1.4 { # limit 5, offset 2 - execsql {DELETE FROM t1 ORDER BY x LIMIT 5 OFFSET 2} + execsql {DELETE FROM t1 RETURNING x, y, '|' ORDER BY x LIMIT 5 OFFSET 2} + } {2 4 | 3 1 | 3 2 | 3 3 | 3 4 |} + do_test wherelimit-1.4cnt { execsql {SELECT count(*) FROM t1} - } {10} + } {6} do_test wherelimit-1.5 { # limit 5, offset -2 execsql {DELETE FROM t1 ORDER BY x LIMIT 5 OFFSET -2} execsql {SELECT count(*) FROM t1} - } {5} + } {1} do_test wherelimit-1.6 { # limit -5 (no limit), offset 2 execsql {DELETE FROM t1 ORDER BY x LIMIT 2, -5} execsql {SELECT count(*) FROM t1} - } {2} + } {1} do_test wherelimit-1.7 { # limit 5, offset -2 (no offset) execsql {DELETE FROM t1 ORDER BY x LIMIT -2, 5} @@ -227,7 +237,9 @@ ifcapable {update_delete_limit} { } {11} create_test_data 6 do_test wherelimit-3.2 { - execsql {UPDATE t1 SET y=1 WHERE x=1 LIMIT 5} + execsql {UPDATE t1 SET y=1 WHERE x=1 RETURNING x, y, '|' LIMIT 5} + } {1 1 | 1 1 | 1 1 | 1 1 | 1 1 |} + do_test wherelimit-3.2cnt { execsql {SELECT count(*) FROM t1 WHERE y=1} } {10} do_test wherelimit-3.3 { diff --git a/test/window1.test b/test/window1.test index dbaf4388f..886bf468f 100644 --- a/test/window1.test +++ b/test/window1.test @@ -374,6 +374,38 @@ do_execsql_test 9.3 { SELECT *, min(z) OVER (ORDER BY x) FROM aaa ORDER BY 1; } {1 g g g 2 i i g 3 l l g 4 g l g 5 a l g 6 m m g} +do_catchsql_test 9.4 { + -- 2021-04-17 dbsqlfuzz d9cf66100064952b66951845dfab41de1c124611 + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a,b,c,d); + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(x,y); + CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN + INSERT INTO t2(x,y) + SELECT a, max(d) OVER w1 FROM t1 + WINDOW w1 AS (PARTITION BY EXISTS(SELECT 1 FROM t1 WHERE c=?1) ); + END; +} {1 {trigger cannot use variables}} + +do_catchsql_test 9.4.2 { + CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(a,b) + SELECT a, max(d) OVER w1 FROM t1 + WINDOW w1 AS ( + ORDER BY a ROWS BETWEEN ? PRECEDING AND UNBOUNDED FOLLOWING + ); + END; +} {1 {trigger cannot use variables}} +do_catchsql_test 9.4.3 { + CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(a,b) + SELECT a, max(d) OVER w1 FROM t1 + WINDOW w1 AS ( + ORDER BY a ROWS BETWEEN UNBOUNDED PRECEDING AND ? FOLLOWING + ); + END; +} {1 {trigger cannot use variables}} + #------------------------------------------------------------------------- # do_execsql_test 10.0 { @@ -1883,11 +1915,59 @@ do_execsql_test 60.1 { # the Parse object is destroyed. # reset_db -do_execsql_test 61.1 { +do_catchsql_test 61.1 { CREATE TABLE t1(a); INSERT INTO t1 VALUES(5),(NULL),('seventeen'); SELECT (SELECT max(x)OVER(ORDER BY x) % min(x)OVER(ORDER BY CASE x WHEN 889 THEN x WHEN x THEN x END)) FROM (SELECT (SELECT sum(CAST(a IN(SELECT (SELECT max(x)OVER(ORDER BY CASE x WHEN 889 THEN 299 WHEN 863 THEN 863 END)) FROM (SELECT (SELECT sum(CAST((SELECT (SELECT max(x)OVER(ORDER BY x) / min(x)OVER(ORDER BY CASE x WHEN 889 THEN 299 WHEN -true THEN 863 END)) FROM (SELECT (SELECT sum(CAST(a IN(SELECT (SELECT max(x) & sum ( a )OVER(ORDER BY CASE x WHEN -8 THEN 299 WHEN 863 THEN 863 END)) FROM (SELECT (SELECT sum(CAST(a AS )) FROM t1) AS x FROM t1)) AS t1 )) FROM t1) AS x FROM t1)) AS x )) FROM t1) AS x FROM t1)) AS real)) FROM t1) AS x FROM t1); -} {{} {} {}} +} {0 {{} {} {}}} + +foreach tn {1 2} { + if {$tn==2} { optimization_control db query-flattener 0 } + do_catchsql_test 61.2.$tn { + SELECT + (SELECT max(x)OVER(ORDER BY x) / min(x) OVER() ) + FROM ( + SELECT (SELECT sum(a) FROM t1 ) AS x FROM t1 + ) + + } {0 {1.0 1.0 1.0}} +} + +reset_db +optimization_control db all 0 +do_execsql_test 61.3.0 { + CREATE TABLE t1(a); + CREATE TABLE t2(y); +} + +do_execsql_test 61.3.1 { + SELECT ( + SELECT count(a) OVER ( ORDER BY (SELECT sum(y) FROM t2) ) + + total(a) OVER() + ) + FROM t1 +} {} +do_execsql_test 61.4.2 { + SELECT ( + SELECT count(a) OVER ( ORDER BY sum(a) ) + + total(a) OVER() + ) + FROM t1 +} {0.0} + +do_catchsql_test 61.4.3 { + SELECT + sum(a) OVER ( ORDER BY a ) + FROM t1 + ORDER BY (SELECT sum(a) FROM t2) +} {1 {misuse of aggregate: sum()}} +do_execsql_test 61.4.4 { + SELECT + sum(a) OVER ( ORDER BY a ) + FROM t1 + ORDER BY (SELECT sum(y) FROM t2) +} + #------------------------------------------------------------------------- reset_db @@ -2001,4 +2081,99 @@ do_execsql_test 65.4 { FROM t1; } {{}} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 66.1 { + CREATE TABLE t1(a INTEGER); + INSERT INTO t1 VALUES(3578824042033200656); + INSERT INTO t1 VALUES(3029012920382354029); +} + +foreach {tn spec} { + 1 "ORDER BY a RANGE BETWEEN 0.3 PRECEDING AND 10 FOLLOWING" + 2 "ORDER BY a RANGE BETWEEN 0.3 PRECEDING AND 0.1 PRECEDING" + 3 "ORDER BY a RANGE BETWEEN 0.3 FOLLOWING AND 10 FOLLOWING" + 4 "ORDER BY a DESC RANGE BETWEEN 0.3 PRECEDING AND 10 FOLLOWING" + 5 "ORDER BY a NULLS LAST RANGE BETWEEN 0.3 PRECEDING AND 10 FOLLOWING" + 6 "ORDER BY a RANGE BETWEEN 1.0 PRECEDING AND 2.0 PRECEDING" +} { + do_execsql_test 66.2.$tn " + SELECT total(a) OVER ( $spec ) FROM t1 ORDER BY a + " { + 3.02901292038235e+18 3.5788240420332e+18 + } +} + + +do_execsql_test 66.3 { + CREATE TABLE t2(a INTEGER); + INSERT INTO t2 VALUES(45); + INSERT INTO t2 VALUES(30); +} + +foreach {tn spec res} { + 1 "ORDER BY a RANGE BETWEEN 0.3 PRECEDING AND 10 FOLLOWING" {30.0 45.0} + 2 "ORDER BY a RANGE BETWEEN 0.3 PRECEDING AND 0.1 PRECEDING" {0.0 0.0} + 3 "ORDER BY a RANGE BETWEEN 0.3 FOLLOWING AND 10 FOLLOWING" {0.0 0.0} + 4 "ORDER BY a DESC RANGE BETWEEN 0.3 PRECEDING AND 10 FOLLOWING" {30.0 45.0} + 5 "ORDER BY a NULLS LAST RANGE BETWEEN 0.3 PRECEDING AND 10 FOLLOWING" {30.0 45.0} + 6 "ORDER BY a RANGE BETWEEN 1.0 PRECEDING AND 2.0 PRECEDING" {0.0 0.0} +} { + do_execsql_test 66.2.$tn " + SELECT total(a) OVER ( $spec ) FROM t2 ORDER BY a + " $res +} + + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 67.0 { + CREATE TABLE t1(a, b, c); + CREATE TABLE t2(a, b, c); +} + +do_catchsql_test 67.1 { + SELECT a,c,b FROM t1 INTERSECT SELECT a,b,c FROM t1 ORDER BY ( + SELECT nth_value(a,2) OVER w1 + WINDOW w1 AS ( ORDER BY ((SELECT 1 FROM v1)) ) + ) +} {1 {no such table: v1}} + +do_catchsql_test 67.2 { + SELECT a,c,b FROM t1 INTERSECT SELECT a,b,c FROM t1 ORDER BY ( + SELECT nth_value(a,2) OVER w1 + WINDOW w1 AS ( ORDER BY ((SELECT 1 FROM t2)) ) + ) +} {1 {1st ORDER BY term does not match any column in the result set}} + +# 2021-05-07 +# Do not allow aggregate functions in the ORDER BY clause even if +# there are window functions in the result set. +# Forum: /forumpost/540fdfef77 +# +reset_db +do_catchsql_test 68.0 { + CREATE TABLE t1(a,b); + INSERT INTO t1(a,b) VALUES(0,0),(1,1),(2,4),(3,9),(4,99); + SELECT rowid, a, b, sum(a)OVER() FROM t1 ORDER BY count(b); +} {1 {misuse of aggregate: count()}} + +# 2021-05-22 +# Forum https://sqlite.org/forum/forumpost/7e484e225c +# +reset_db +do_catchsql_test 69.0 { + CREATE TABLE t1(a,b); + CREATE INDEX t1ba ON t1(b,a); + SELECT * FROM t1 WHERE b = (SELECT b FROM t1 ORDER BY lead(b) OVER () AND sum(a)); +} {1 {misuse of aggregate: sum()}} +do_catchsql_test 69.1 { + SELECT * FROM t1 WHERE b >= (SELECT b FROM t1 ORDER BY lead(b) OVER () AND sum(a)); +} {1 {misuse of aggregate: sum()}} +do_catchsql_test 69.2 { + SELECT * FROM t1 WHERE b <= (SELECT b FROM t1 ORDER BY lead(b) OVER () AND sum(a)); +} {1 {misuse of aggregate: sum()}} + + finish_test diff --git a/test/window8.tcl b/test/window8.tcl index a8333f190..69ad0ad26 100644 --- a/test/window8.tcl +++ b/test/window8.tcl @@ -418,6 +418,76 @@ execsql_test 7.$tn.9 " " } +========== + +execsql_test 8.0 { + DROP TABLE IF EXISTS tx; + CREATE TABLE tx(a INTEGER PRIMARY KEY); + INSERT INTO tx VALUES(1), (2), (3), (4), (5), (6); + + DROP TABLE IF EXISTS map; + CREATE TABLE map(v INTEGER PRIMARY KEY, t TEXT); + INSERT INTO map VALUES + (1, 'odd'), (2, 'even'), (3, 'odd'), + (4, 'even'), (5, 'odd'), (6, 'even'); +} + +execsql_test 8.1 { + SELECT sum(a) OVER ( + PARTITION BY ( + SELECT t FROM map WHERE v=a + ) ORDER BY a + ) FROM tx; +} + +execsql_test 8.2 { + SELECT sum(a) OVER win FROM tx + WINDOW win AS ( + PARTITION BY ( + SELECT t FROM map WHERE v=a + ) ORDER BY a + ); +} + +execsql_test 8.3 { + WITH map2 AS ( + SELECT * FROM map + ) + SELECT sum(a) OVER ( + PARTITION BY ( + SELECT t FROM map2 WHERE v=a + ) ORDER BY a + ) FROM tx; +} + +execsql_test 8.4 { + WITH map2 AS ( + SELECT * FROM map + ) + SELECT sum(a) OVER win FROM tx + WINDOW win AS ( + PARTITION BY ( + SELECT t FROM map2 WHERE v=a + ) ORDER BY a + ); +} + +========== + +execsql_test 9.1 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(a INTEGER); + CREATE TABLE t2(y INTEGER); +} + +execsql_test 9.2 { + SELECT ( + SELECT max(a) OVER ( ORDER BY (SELECT sum(a) FROM t1) ) + + min(a) OVER() + ) + FROM t1 +} finish_test diff --git a/test/window8.test b/test/window8.test index ed720ebd4..0c5d39bad 100644 --- a/test/window8.test +++ b/test/window8.test @@ -6469,4 +6469,75 @@ do_execsql_test 7.4.9 { ); } {4 4 4 {} {} {}} +#========================================================================== + +do_execsql_test 8.0 { + DROP TABLE IF EXISTS tx; + CREATE TABLE tx(a INTEGER PRIMARY KEY); + INSERT INTO tx VALUES(1), (2), (3), (4), (5), (6); + + DROP TABLE IF EXISTS map; + CREATE TABLE map(v INTEGER PRIMARY KEY, t TEXT); + INSERT INTO map VALUES + (1, 'odd'), (2, 'even'), (3, 'odd'), + (4, 'even'), (5, 'odd'), (6, 'even'); +} {} + +do_execsql_test 8.1 { + SELECT sum(a) OVER ( + PARTITION BY ( + SELECT t FROM map WHERE v=a + ) ORDER BY a + ) FROM tx; +} {2 6 12 1 4 9} + +do_execsql_test 8.2 { + SELECT sum(a) OVER win FROM tx + WINDOW win AS ( + PARTITION BY ( + SELECT t FROM map WHERE v=a + ) ORDER BY a + ); +} {2 6 12 1 4 9} + +do_execsql_test 8.3 { + WITH map2 AS ( + SELECT * FROM map + ) + SELECT sum(a) OVER ( + PARTITION BY ( + SELECT t FROM map2 WHERE v=a + ) ORDER BY a + ) FROM tx; +} {2 6 12 1 4 9} + +do_execsql_test 8.4 { + WITH map2 AS ( + SELECT * FROM map + ) + SELECT sum(a) OVER win FROM tx + WINDOW win AS ( + PARTITION BY ( + SELECT t FROM map2 WHERE v=a + ) ORDER BY a + ); +} {2 6 12 1 4 9} + +#========================================================================== + +do_execsql_test 9.1 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(a INTEGER); + CREATE TABLE t2(y INTEGER); +} {} + +do_execsql_test 9.2 { + SELECT ( + SELECT max(a) OVER ( ORDER BY (SELECT sum(a) FROM t1) ) + + min(a) OVER() + ) + FROM t1 +} {} + finish_test diff --git a/test/window9.test b/test/window9.test index c342a4d79..4b8e4fa58 100644 --- a/test/window9.test +++ b/test/window9.test @@ -265,4 +265,21 @@ do_execsql_test 8.4 { FROM v1; } {0.0 0.0} +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 9.0 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES(NULL,'bb',356); + INSERT INTO t1 VALUES('CB','aa',158); + INSERT INTO t1 VALUES('BB','aa',399); + INSERT INTO t1 VALUES('FF','bb',938); +} + +do_catchsql_test 9.1 { + SELECT sum(c) OVER ( + ORDER BY c RANGE BETWEEN 0 PRECEDING AND '-700' PRECEDING + ) + FROM t1 +} {1 {frame ending offset must be a non-negative number}} + finish_test diff --git a/test/windowB.test b/test/windowB.test index 321c48140..30380aee2 100644 --- a/test/windowB.test +++ b/test/windowB.test @@ -336,4 +336,25 @@ do_execsql_test 7.4 { ) FROM t1; } {{} 45 {} 45 7 {} 7 {} 8 {} 8 {} abc 1001 abc 1001 xyz 3333} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 8.0 { + BEGIN TRANSACTION; + CREATE TABLE t1(a, c); + INSERT INTO t1 VALUES('aa', 111); + INSERT INTO t1 VALUES('BB', 660); + INSERT INTO t1 VALUES('CC', 938); + INSERT INTO t1 VALUES('dd', 979); + COMMIT; + + CREATE INDEX i1 ON t1(a COLLATE nocase); +} + +do_execsql_test 8.1 { + SELECT sum(c) OVER + (ORDER BY a COLLATE nocase RANGE BETWEEN 10.0 PRECEDING AND 5.0 PRECEDING) + FROM t1; +} {111 660 938 979} + + finish_test diff --git a/test/windowfault.test b/test/windowfault.test index aea234021..b5d743311 100644 --- a/test/windowfault.test +++ b/test/windowfault.test @@ -246,6 +246,7 @@ do_faultsim_test 10 -faults oom* -prep { faultsim_test_result {0 {}} } +#------------------------------------------------------------------------- reset_db do_execsql_test 11.0 { DROP TABLE IF EXISTS t0; @@ -253,7 +254,7 @@ do_execsql_test 11.0 { INSERT INTO t0 VALUES(0); } {} -do_faultsim_test 11 -faults oom* -prep { +do_faultsim_test 11.1 -faults oom* -prep { } -body { execsql { SELECT * FROM t0 WHERE @@ -263,7 +264,7 @@ do_faultsim_test 11 -faults oom* -prep { faultsim_test_result {0 {}} } -do_faultsim_test 11 -faults oom* -prep { +do_faultsim_test 11.2 -faults oom* -prep { } -body { execsql { VALUES(false),(current_date collate binary) @@ -274,4 +275,21 @@ do_faultsim_test 11 -faults oom* -prep { faultsim_test_result {0 {}} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 12.0 { + CREATE TABLE t1(a, b, c); +} {} +do_faultsim_test 12 -faults oom* -prep { +} -body { + execsql { + WITH v(a, b, row_number) AS ( + SELECT a, b, row_number() OVER (PARTITION BY a COLLATE nocase ORDER BY b) FROM t1 + ) + SELECT * FROM v WHERE a=2 + } +} -test { + faultsim_test_result {0 {}} +} + finish_test diff --git a/test/windowpushd.test b/test/windowpushd.test new file mode 100644 index 000000000..f2e04d72f --- /dev/null +++ b/test/windowpushd.test @@ -0,0 +1,236 @@ +# 2021 February 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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the push-down optimization when +# WHERE constraints are pushed down into a sub-query that uses +# window functions. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix windowpushd + +do_execsql_test 1.0 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, grp_id); + CREATE INDEX i1 ON t1(grp_id); + CREATE VIEW lll AS SELECT + row_number() OVER (PARTITION BY grp_id), + grp_id, id + FROM t1 +} + +do_execsql_test 1.1 { + INSERT INTO t1 VALUES + (1, 2), (2, 3), (3, 3), (4, 1), (5, 1), + (6, 1), (7, 1), (8, 1), (9, 3), (10, 3), + (11, 2), (12, 3), (13, 3), (14, 2), (15, 1), + (16, 2), (17, 1), (18, 2), (19, 3), (20, 2) +} + +do_execsql_test 1.2 { + SELECT * FROM lll +} { + 1 1 4 2 1 5 3 1 6 4 1 7 5 1 8 6 1 15 7 1 17 + 1 2 1 2 2 11 3 2 14 4 2 16 5 2 18 6 2 20 + 1 3 2 2 3 3 3 3 9 4 3 10 5 3 12 6 3 13 7 3 19 +} + +do_execsql_test 1.3 { + SELECT * FROM lll WHERE grp_id=2 +} { + 1 2 1 2 2 11 3 2 14 4 2 16 5 2 18 6 2 20 +} + +do_eqp_test 1.4 { + SELECT * FROM lll WHERE grp_id=2 +} {SEARCH t1 USING COVERING INDEX i1 (grp_id=?)} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a, b, c, d); + INSERT INTO t1 VALUES('A', 'C', 1, 0.1); + INSERT INTO t1 VALUES('A', 'D', 2, 0.2); + INSERT INTO t1 VALUES('A', 'E', 3, 0.3); + INSERT INTO t1 VALUES('A', 'C', 4, 0.4); + INSERT INTO t1 VALUES('B', 'D', 5, 0.5); + INSERT INTO t1 VALUES('B', 'E', 6, 0.6); + INSERT INTO t1 VALUES('B', 'C', 7, 0.7); + INSERT INTO t1 VALUES('B', 'D', 8, 0.8); + INSERT INTO t1 VALUES('C', 'E', 9, 0.9); + INSERT INTO t1 VALUES('C', 'C', 10, 1.0); + INSERT INTO t1 VALUES('C', 'D', 11, 1.1); + INSERT INTO t1 VALUES('C', 'E', 12, 1.2); + + CREATE INDEX i1 ON t1(a); + CREATE INDEX i2 ON t1(b); + + CREATE VIEW v1 AS SELECT a, c, max(c) OVER (PARTITION BY a) FROM t1; + + CREATE VIEW v2 AS SELECT a, c, + max(c) OVER (PARTITION BY a), + row_number() OVER () + FROM t1; + + CREATE VIEW v3 AS SELECT b, d, + max(d) OVER (PARTITION BY b), + row_number() OVER (PARTITION BY b) + FROM t1; + + CREATE TABLE t2(x, y, z); + INSERT INTO t2 VALUES('W', 3, 1); + INSERT INTO t2 VALUES('W', 2, 2); + INSERT INTO t2 VALUES('X', 1, 4); + INSERT INTO t2 VALUES('X', 5, 7); + INSERT INTO t2 VALUES('Y', 1, 9); + INSERT INTO t2 VALUES('Y', 4, 2); + INSERT INTO t2 VALUES('Z', 3, 3); + INSERT INTO t2 VALUES('Z', 3, 4); +} + +foreach tn {0 1} { + optimization_control db push-down $tn + + do_execsql_test 2.$tn.1.1 { + SELECT * FROM v1; + } { + A 1 4 A 2 4 A 3 4 A 4 4 + B 5 8 B 6 8 B 7 8 B 8 8 + C 9 12 C 10 12 C 11 12 C 12 12 + } + + do_execsql_test 2.$tn.1.2 { + SELECT * FROM v1 WHERE a IN ('A', 'B'); + } { + A 1 4 A 2 4 A 3 4 A 4 4 + B 5 8 B 6 8 B 7 8 B 8 8 + } + + do_execsql_test 2.$tn.1.3 { + SELECT * FROM v1 WHERE a IS 'C' + } { + C 9 12 C 10 12 C 11 12 C 12 12 + } + + if {$tn==1} { + do_eqp_test 2.$tn.1.4 { + SELECT * FROM v1 WHERE a IN ('A', 'B'); + } {USING INDEX i1 (a=?)} + + do_eqp_test 2.$tn.1.5 { + SELECT * FROM v1 WHERE a = 'c' COLLATE nocase + } {USING INDEX i1} + } + + do_execsql_test 2.$tn.2.1 { + SELECT * FROM v2; + } { + A 1 4 1 A 2 4 2 A 3 4 3 A 4 4 4 + B 5 8 5 B 6 8 6 B 7 8 7 B 8 8 8 + C 9 12 9 C 10 12 10 C 11 12 11 C 12 12 12 + } + + do_execsql_test 2.$tn.2.2 { + SELECT * FROM v2 WHERE a = 'C'; + } { + C 9 12 9 C 10 12 10 C 11 12 11 C 12 12 12 + } + + do_execsql_test 2.$tn.3.1 { SELECT * FROM v3; } { + C 0.1 1.0 1 C 0.4 1.0 2 C 0.7 1.0 3 C 1.0 1.0 4 + D 0.2 1.1 1 D 0.5 1.1 2 D 0.8 1.1 3 D 1.1 1.1 4 + E 0.3 1.2 1 E 0.6 1.2 2 E 0.9 1.2 3 E 1.2 1.2 4 + } + + do_execsql_test 2.$tn.3.2 { SELECT * FROM v3 WHERE b<'E' } { + C 0.1 1.0 1 C 0.4 1.0 2 C 0.7 1.0 3 C 1.0 1.0 4 + D 0.2 1.1 1 D 0.5 1.1 2 D 0.8 1.1 3 D 1.1 1.1 4 + } + + if {$tn==1} { + do_eqp_test 2.$tn.3.3 { + SELECT * FROM v3 WHERE b='E' + } {SEARCH t1 USING INDEX i2 (b=?)} + do_eqp_test 2.$tn.3.4 { + SELECT * FROM v3 WHERE b>'C' + } {SEARCH t1 USING INDEX i2 (b>?)} + } + + do_execsql_test 2.$tn.3.5 { SELECT * FROM v3 WHERE d<0.55; } { + C 0.1 1.0 1 C 0.4 1.0 2 + D 0.2 1.1 1 D 0.5 1.1 2 + E 0.3 1.2 1 + } + if {$tn==1} { + do_eqp_test 2.$tn.3.6 { + SELECT * FROM v3 WHERE d<0.55 + } {SCAN t1 USING INDEX i2} + } + + do_execsql_test 2.$tn.4.1 { + SELECT * FROM ( + SELECT x, sum(y) AS s, max(z) AS m + FROM t2 GROUP BY x + ) + } { + W 5 2 + X 6 7 + Y 5 9 + Z 6 4 + } + + do_execsql_test 2.$tn.4.1 { + SELECT * FROM ( + SELECT x, sum(y) AS s, max(z) AS m, + max( max(z) ) OVER (PARTITION BY sum(y) + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) + FROM t2 GROUP BY x + ) + } { + W 5 2 9 + Y 5 9 9 + X 6 7 7 + Z 6 4 7 + } + + do_execsql_test 2.$tn.4.2 { + SELECT * FROM ( + SELECT x, sum(y) AS s, max(z) AS m, + max( max(z) ) OVER (PARTITION BY sum(y) + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) + FROM t2 GROUP BY x + ) WHERE s=6 + } { + X 6 7 7 + Z 6 4 7 + } + + do_execsql_test 2.$tn.4.3 { + SELECT * FROM ( + SELECT x, sum(y) AS s, max(z) AS m, + max( max(z) ) OVER (PARTITION BY sum(y) + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) + FROM t2 GROUP BY x + ) WHERE s<6 + } { + W 5 2 9 + Y 5 9 9 + } + +} + + + + +finish_test diff --git a/test/with1.test b/test/with1.test index 74997c1db..5765399d1 100644 --- a/test/with1.test +++ b/test/with1.test @@ -1025,10 +1025,10 @@ do_eqp_test 19.1b { SELECT * FROM t1; } { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE x1 | `--SCAN CONSTANT ROW - |--SCAN SUBQUERY xxxxxx - `--SCAN SUBQUERY xxxxxx + |--SCAN x1 + `--SCAN x1 } # 2017-10-28. @@ -1149,20 +1149,26 @@ do_execsql_test 24.2 { # had to continue to recognize "true" and "false" as identifiers for # backwards compatibility. # -reset_db -do_execsql_test 25.1 { - CREATE TABLE dual(dummy); - INSERT INTO dual(dummy) VALUES('X'); - WITH cte1 AS ( - SELECT TRUE, ( - WITH cte2 AS (SELECT avg(DISTINCT TRUE) FROM dual) - SELECT 2571 FROM cte2 - ) AS subquery1 - FROM dual - GROUP BY 1 - ) - SELECT (SELECT 1324 FROM cte1) FROM cte1; -} {1324} +foreach {id dual} { + 1 {CREATE TABLE dual AS SELECT 'X' AS dummy} + 2 {CREATE TEMP TABLE dual AS SELECT 'X' AS dummy} + 3 {CREATE VIEW dual(dummy) AS VALUES('X')} + 4 {CREATE TEMP VIEW dual(dummy) AS VALUES('X')} +} { + reset_db + db eval $dual + do_execsql_test 25.$id { + WITH cte1 AS ( + SELECT TRUE, ( + WITH cte2 AS (SELECT avg(DISTINCT TRUE) FROM dual) + SELECT 2571 FROM cte2 + ) AS subquery1 + FROM dual + GROUP BY 1 + ) + SELECT (SELECT 1324 FROM cte1) FROM cte1; + } {1324} +} do_catchsql_test 26.0 { WITH i(x) AS ( @@ -1207,4 +1213,28 @@ do_execsql_test 26.3 { SELECT * FROM cte ORDER BY +label, +step; } {a 1 a 2 a 3 b 1 b 2 b 3} +# 2021-05-20 +# forum post https://sqlite.org/forum/forumpost/8590e3f6dc +# +reset_db +do_execsql_test 27.1 { + CREATE TABLE t1(k); + CREATE TABLE log(k, cte_map, main_map); + CREATE TABLE map(k, v); + INSERT INTO map VALUES(1, 'main1'), (2, 'main2'); + + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + INSERT INTO log + WITH map(k,v) AS (VALUES(1,'cte1'),(2,'cte2')) + SELECT + new.k, + (SELECT v FROM map WHERE k=new.k), + (SELECT v FROM main.map WHERE k=new.k); + END; + + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + SELECT k, cte_map, main_map, '|' FROM log ORDER BY k; +} {1 cte1 main1 | 2 cte2 main2 |} + finish_test diff --git a/test/with2.test b/test/with2.test index 004ec94b9..02f808ea2 100644 --- a/test/with2.test +++ b/test/with2.test @@ -414,5 +414,201 @@ do_execsql_test 8.3 { SELECT * FROM q; } {1 2 3 4 5} +# 2021-03-18 +# Ticket bb8a9fd4a9b7fce5 +reset_db +do_execsql_test 9.1 { + WITH xyz(a) AS ( + WITH abc AS ( SELECT 1234 ) SELECT * FROM abc + ) + SELECT * FROM xyz AS one, xyz AS two, ( + SELECT * FROM xyz UNION ALL SELECT * FROM xyz + ); +} {1234 1234 1234 1234 1234 1234} +ifcapable vtab { +load_static_extension db series +do_execsql_test 9.2 { + WITH + cst(rsx, rsy) AS ( + SELECT 100, 100 + ), + cst2(minx, maxx, stepx, miny, maxy, stepy, qualitativex, qualitativey) AS ( + SELECT NULL, NULL, NULL, NULL, NULL, NULL, 0, 0 + ), + ds0(m, n, x, y, x2, y2, title, size, mark, label, markmode) AS ( + SELECT 1, 2, 3, 4, 5, 6, 7 , 8, 9, 10, 11 + ), + ds(m, n, x, y, x2, y2, title, size, mark, label, markmode) AS ( + SELECT m, n, x, + y, x2, + y2, + title, size, mark, label, markmode + FROM ds0 + WINDOW w AS (PARTITION BY m, x ORDER BY n) + ), + d(m, n, x, y, x2, y2, labelx,labely,title,size,mark,label,markmode) AS ( + SELECT m, n, x, y, x2, y2, x, y, title, size, mark, label, markmode + FROM ds, cst2 + ), + ylabels(y, label) AS ( + SELECT y, MIN(labely) FROM d GROUP BY y + ), + yaxis(maxy, miny, stepy , minstepy) AS ( + WITH + xt0(minx, maxx) AS ( + SELECT coalesce(miny, min(min(y2), + min(y))), coalesce(maxy, max(max(y2), + max(y))) + qualitativey + FROM d, cst2 + ), + xt1(mx, mn) AS (SELECT maxx, minx FROM xt0), + xt2(mx, mn, step) AS (SELECT mx, mn, (mx-mn) FROM xt1), + + xt3(mx, mn, ms) AS ( + SELECT mx, mn, first_value(rs) OVER (order by x desc) AS ms + FROM (SELECT mx, mn, step, f,(mx-mn) as rng, + 1.0*step/f as rs, 1.0*(mx-mn)/(step/f) AS x + FROM xt2, (SELECT 1 AS f UNION ALL SELECT 2 + UNION ALL SELECT 4 + UNION ALL SELECT 5)) AS src + WHERE x < 10 limit 1), + xt4(minstepy) AS ( + SELECT MIN(abs(y2-y)) FROM d WHERE y2 != y + ) + SELECT (mx/ms)*ms, (mn/ms)*ms, coalesce(stepy, ms), + coalesce(minstepy, ms, stepy) FROM xt3, cst2,xt4 + ), + distinct_mark_n_m(mark, ze, zem, title) AS ( + SELECT DISTINCT mark, n AS ze, m AS zem, title FROM ds0 + ), + facet0(m, mi, title, radial) AS ( + SELECT md, row_number() OVER () - 1, title, 'radial' + IN (SELECT mark FROM distinct_mark_n_m WHERE zem = md) + FROM (SELECT DISTINCT zem AS md, title AS title + FROM distinct_mark_n_m ORDER BY 2, 1) + ), + facet(m, mi, xorigin, yorigin, title, radial) AS ( + SELECT m, mi, + rsx * 1.2 * IFNULL(CASE WHEN ( + 0 + ) > 0 THEN mi / ( + 0 + ) ELSE mi % ( + 2 + ) END, mi), + rsy * 1.2 * IFNULL(CASE WHEN ( + 2 + ) > 0 THEN mi / ( + 2 + ) ELSE mi / ( + 0 + ) END, 0), + title, radial FROM facet0, cst + ), + radygrid(m, mi, tty, wty, ttx, ttx2, xorigin, yorigin) AS ( + SELECT m, mi, rsy / 2 / ((maxy-miny)/stepy) * (value-1) AS tty, + coalesce(NULL, miny + stepy * (value-1)) AS wty, + xorigin, xorigin+rsx, xorigin + rsx / 2, + yorigin + rsy / 2 + FROM generate_series(1), yaxis, cst, + facet LEFT JOIN ylabels ON ylabels.y = (miny + (value-1) * stepy) + WHERE radial AND stop = 1+1.0*(maxy-miny)/stepy + ), + ypos(m, mi, pcx, pcy, radial) AS ( + SELECT m, mi, xorigin, yorigin + CASE + WHEN 0 BETWEEN miny AND maxy THEN + rsy - (0 - miny) * rsy / (maxy-miny) + WHEN 0 >= maxy THEN 0 + ELSE rsy + END, radial FROM yaxis, cst, facet WHERE NOT radial + UNION ALL + SELECT m, mi, xorigin + rsx / 2, yorigin + (CASE + WHEN 0 BETWEEN miny AND maxy THEN + rsy - (0 - miny) * rsy / 2 / (maxy-miny) + WHEN 0 >= maxy THEN 0 + ELSE rsy + END ) / 2, radial FROM yaxis, cst, facet WHERE radial + ) + SELECT * FROM radygrid , ypos; +} {} +} ;# end ifcapable vtab + +# 2021-03-19 +# dbsqlfuzz 01b8355086998f0a452cb31208e80b9d29ca739a +# +# Correlated CTEs should not be materialized. +# +reset_db +do_execsql_test 10.1 { + SELECT 1 AS c WHERE ( + SELECT ( + WITH t1(a) AS (VALUES( c )) + SELECT ( SELECT t1a.a FROM t1 AS t1a, t1 AS t1x ) + FROM t1 AS xyz GROUP BY 1 + ) + ) +} {1} + +# 2021-05-21 +# Forum post https://sqlite.org/forum/forumpost/aa4a7a3980 +# +reset_db +do_execsql_test 11.1 { + CREATE TABLE t1(a); + CREATE VIEW v2(c) AS + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM v2 + ) SELECT a + ) SELECT * from t1; + ALTER TABLE t1 RENAME COLUMN a TO b; + SELECT sql FROM sqlite_schema WHERE name='t1'; +} {{CREATE TABLE t1(b)}} +do_catchsql_test 11.2 { + INSERT INTO t1 VALUES(55); + SELECT * FROM v2; +} {0 55} +do_catchsql_test 11.3 { + DROP VIEW v2; + CREATE VIEW v2(c) AS + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM v2 + ) SELECT a + ) SELECT * from t1, x; + SELECT * FROM v2; +} {1 {no such column: a}} +do_catchsql_test 11.4 { + DROP VIEW v2; + CREATE VIEW v2(c) AS + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM v2 + ) SELECT * + ) SELECT * from t1, x; + SELECT * FROM v2; +} {1 {no tables specified}} +do_catchsql_test 11.5 { + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM no_such_table + ) SELECT a + ) SELECT * from t1; +} {0 55} + +# 2021-05-23 dbsqlfuzz 6b7a144674e215f06ddfeb9042c873d9ee956ac0 */ +reset_db +do_execsql_test 12.1 { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(1),('hello'),(4.25),(NULL),(x'3c626c6f623e'); + CREATE VIEW v2(c) AS WITH x AS (WITH y AS (WITH z AS(SELECT * FROM t1) SELECT * FROM v2) SELECT a) SELECT * from t1; + CREATE VIEW v3(c) AS WITH x AS (WITH y AS (WITH z AS(SELECT * FROM v2) SELECT * FROM v3) SELECT a) SELECT * from t1; + ALTER TABLE t1 RENAME TO t1x; + SELECT quote(c) FROM v3; +} {1 'hello' 4.25 NULL X'3C626C6F623E'} finish_test diff --git a/test/with3.test b/test/with3.test index a574263b8..8e8eba89f 100644 --- a/test/with3.test +++ b/test/with3.test @@ -89,13 +89,13 @@ ifcapable analyze { SELECT * FROM cnt, y1 WHERE i=a } [string map {"\n " \n} { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE cnt | |--SETUP | | `--SCAN CONSTANT ROW | `--RECURSIVE STEP - | `--SCAN TABLE cnt - |--SCAN SUBQUERY xxxxxx - `--SEARCH TABLE y1 USING INDEX y1a (a=?) + | `--SCAN cnt + |--SCAN cnt + `--SEARCH y1 USING INDEX y1a (a=?) }] do_eqp_test 3.1.3 { @@ -103,13 +103,13 @@ ifcapable analyze { SELECT * FROM cnt, y1 WHERE i=a } [string map {"\n " \n} { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE cnt | |--SETUP | | `--SCAN CONSTANT ROW | `--RECURSIVE STEP - | `--SCAN TABLE cnt - |--SCAN TABLE y1 - `--SEARCH SUBQUERY xxxxxx USING AUTOMATIC COVERING INDEX (i=?) + | `--SCAN cnt + |--SCAN y1 + `--SEARCH cnt USING AUTOMATIC COVERING INDEX (i=?) }] } @@ -125,17 +125,17 @@ do_eqp_test 3.2.2 { WHERE c.id=w2.pk AND c.id=w1.pk; } { QUERY PLAN - |--MATERIALIZE xxxxxx + |--MATERIALIZE c | |--SETUP | | |--SCAN CONSTANT ROW | | `--SCALAR SUBQUERY xxxxxx - | | `--SCAN TABLE w2 + | | `--SCAN w2 | `--RECURSIVE STEP - | |--SCAN TABLE w1 - | `--SCAN TABLE c - |--SCAN SUBQUERY xxxxxx - |--SEARCH TABLE w2 USING INTEGER PRIMARY KEY (rowid=?) - `--SEARCH TABLE w1 USING INTEGER PRIMARY KEY (rowid=?) + | |--SCAN w1 + | `--SCAN c + |--SCAN c + |--SEARCH w2 USING INTEGER PRIMARY KEY (rowid=?) + `--SEARCH w1 USING INTEGER PRIMARY KEY (rowid=?) } do_execsql_test 4.0 { @@ -198,4 +198,50 @@ do_execsql_test 4.2 { GROUP BY 1; } {elvis} +# 2021-02-13 +# Avoid manifesting the same CTE multiple times. +# +do_eqp_test 5.1 { + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<1) + SELECT x1.x||x2.x||x3.x||x4.x FROM c AS x1, c AS x2, c AS x3, c AS x4 + ORDER BY 1; +} { + QUERY PLAN + |--MATERIALIZE c + | |--SETUP + | | `--SCAN CONSTANT ROW + | `--RECURSIVE STEP + | `--SCAN c + |--SCAN x1 + |--SCAN x2 + |--SCAN x3 + |--SCAN x4 + `--USE TEMP B-TREE FOR ORDER BY +} +do_execsql_test 5.2 { + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<1) + SELECT x1.x||x2.x||x3.x||x4.x FROM c AS x1, c AS x2, c AS x3, c AS x4 + ORDER BY 1; +} {0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111} + +#------------------------------------------------------------------------- +# At one point this would incorrectly report "circular reference: cte1" +# +do_catchsql_test 6.0 { + with + cte1(x, y) AS ( select 1, 2, 3 ), + cte2(z) as ( select 1 from cte1 ) + select * from cte2, cte1; +} {1 {table cte1 has 3 values for 2 columns}} + +do_catchsql_test 6.1 { + with + cte1(x, y) AS ( select 1, 2, 3 ), + cte2(z) as ( select 1 from cte1 UNION ALL SELECT z+1 FROM cte2 WHERE z<5) + select * from cte2, cte1; +} {1 {table cte1 has 3 values for 2 columns}} + + + + finish_test diff --git a/test/with6.test b/test/with6.test new file mode 100644 index 000000000..333147d8b --- /dev/null +++ b/test/with6.test @@ -0,0 +1,255 @@ +# 2021-02-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 implements regression tests for SQLite library. The +# focus of this file is the MATERIALIZED hint to common table expressions +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix with6 + +ifcapable {!cte} { + finish_test + return +} + +do_execsql_test 100 { + WITH c(x) AS (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x FROM c c1, c c2, c c3; +} {000 001 010 011 100 101 110 111} +do_eqp_test 101 { + WITH c(x) AS (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x FROM c c1, c c2, c c3; +} { + QUERY PLAN + |--MATERIALIZE c + | `--SCAN 2 CONSTANT ROWS + |--SCAN c1 + |--SCAN c2 + `--SCAN c3 +} + +do_execsql_test 110 { + WITH c(x) AS MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x FROM c c1, c c2, c c3; +} {000 001 010 011 100 101 110 111} +do_eqp_test 111 { + WITH c(x) AS MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x FROM c c1, c c2, c c3; +} { + QUERY PLAN + |--MATERIALIZE c + | `--SCAN 2 CONSTANT ROWS + |--SCAN c1 + |--SCAN c2 + `--SCAN c3 +} + +# Even though the CTE is not materialized, the self-join optimization +# kicks in and does the materialization for us. +# +do_execsql_test 120 { + WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x FROM c c1, c c2, c c3; +} {000 001 010 011 100 101 110 111} +do_eqp_test 121 { + WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x FROM c c1, c c2, c c3; +} { + QUERY PLAN + |--MATERIALIZE c + | `--SCAN 2 CONSTANT ROWS + |--SCAN c1 + |--SCAN c2 + `--SCAN c3 +} + +do_execsql_test 130 { + WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x + FROM (SELECT x FROM c LIMIT 5) AS c1, + (SELECT x FROM c LIMIT 5) AS c2, + (SELECT x FROM c LIMIT 5) AS c3; +} {000 001 010 011 100 101 110 111} +do_eqp_test 131 { + WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x + FROM (SELECT x FROM c LIMIT 5) AS c1, + (SELECT x FROM c LIMIT 5) AS c2, + (SELECT x FROM c LIMIT 5) AS c3; +} { + QUERY PLAN + |--MATERIALIZE c1 + | |--CO-ROUTINE c + | | `--SCAN 2 CONSTANT ROWS + | `--SCAN c + |--MATERIALIZE c2 + | |--CO-ROUTINE c + | | `--SCAN 2 CONSTANT ROWS + | `--SCAN c + |--MATERIALIZE c3 + | |--CO-ROUTINE c + | | `--SCAN 2 CONSTANT ROWS + | `--SCAN c + |--SCAN c1 + |--SCAN c2 + `--SCAN c3 +} + +# The (SELECT x FROM c LIMIT N) subqueries get materialized once each. +# Show multiple materializations are shown. But there is only one +# materialization for c, shown by the "SCAN 2 CONSTANT ROWS" line. +# +do_execsql_test 140 { + WITH c(x) AS MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x + FROM (SELECT x FROM c LIMIT 5) AS c1, + (SELECT x FROM c LIMIT 6) AS c2, + (SELECT x FROM c LIMIT 7) AS c3; +} {000 001 010 011 100 101 110 111} +do_eqp_test 141 { + WITH c(x) AS MATERIALIZED (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x + FROM (SELECT x FROM c LIMIT 5) AS c1, + (SELECT x FROM c LIMIT 6) AS c2, + (SELECT x FROM c LIMIT 7) AS c3; +} { + QUERY PLAN + |--MATERIALIZE c1 + | |--MATERIALIZE c + | | `--SCAN 2 CONSTANT ROWS + | `--SCAN c + |--MATERIALIZE c2 + | `--SCAN c + |--MATERIALIZE c3 + | `--SCAN c + |--SCAN c1 + |--SCAN c2 + `--SCAN c3 +} + +do_execsql_test 150 { + WITH c(x) AS (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x + FROM (SELECT x FROM c LIMIT 5) AS c1, + (SELECT x FROM c LIMIT 6) AS c2, + (SELECT x FROM c LIMIT 7) AS c3; +} {000 001 010 011 100 101 110 111} +do_eqp_test 151 { + WITH c(x) AS (VALUES(0),(1)) + SELECT c1.x||c2.x||c3.x + FROM (SELECT x FROM c LIMIT 5) AS c1, + (SELECT x FROM c LIMIT 6) AS c2, + (SELECT x FROM c LIMIT 7) AS c3; +} { + QUERY PLAN + |--MATERIALIZE c1 + | |--MATERIALIZE c + | | `--SCAN 2 CONSTANT ROWS + | `--SCAN c + |--MATERIALIZE c2 + | `--SCAN c + |--MATERIALIZE c3 + | `--SCAN c + |--SCAN c1 + |--SCAN c2 + `--SCAN c3 +} + +do_execsql_test 160 { + WITH c(x) AS (VALUES(0),(1)) + SELECT c2.x + 100*(SELECT sum(x+1) FROM c WHERE c.x<=c2.x) + FROM c AS c2 WHERE c2.x<10; +} {100 301} +do_eqp_test 161 { + WITH c(x) AS (VALUES(0),(1)) + SELECT c2.x + 100*(SELECT sum(x+1) FROM c WHERE c.x<=c2.x) + FROM c AS c2 WHERE c2.x<10; +} { + QUERY PLAN + |--MATERIALIZE c + | `--SCAN 2 CONSTANT ROWS + |--SCAN c2 + `--CORRELATED SCALAR SUBQUERY xxxxxx + `--SCAN c +} + +do_execsql_test 170 { + WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) + SELECT c2.x + 100*(SELECT sum(x+1) FROM c WHERE c.x<=c2.x) + FROM c AS c2 WHERE c2.x<10; +} {100 301} +do_eqp_test 171 { + WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) + SELECT c2.x + 100*(SELECT sum(x+1) FROM c WHERE c.x<=c2.x) + FROM c AS c2 WHERE c2.x<10; +} { + QUERY PLAN + |--CO-ROUTINE c + | `--SCAN 2 CONSTANT ROWS + |--SCAN c2 + `--CORRELATED SCALAR SUBQUERY xxxxxx + |--CO-ROUTINE c + | `--SCAN 2 CONSTANT ROWS + `--SCAN c +} + + +do_execsql_test 200 { + CREATE TABLE t1(x); + INSERT INTO t1(x) VALUES(4); + CREATE VIEW t2(y) AS + WITH c(z) AS (VALUES(4),(5),(6)) + SELECT c1.z+c2.z*100+t1.x*10000 + FROM t1, + (SELECT z FROM c LIMIT 5) AS c1, + (SELECT z FROM c LIMIT 5) AS c2; + SELECT y FROM t2 ORDER BY y; +} {40404 40405 40406 40504 40505 40506 40604 40605 40606} +do_execsql_test 210 { + DROP VIEW t2; + CREATE VIEW t2(y) AS + WITH c(z) AS NOT MATERIALIZED (VALUES(4),(5),(6)) + SELECT c1.z+c2.z*100+t1.x*10000 + FROM t1, + (SELECT z FROM c LIMIT 5) AS c1, + (SELECT z FROM c LIMIT 5) AS c2; + SELECT y FROM t2 ORDER BY y; +} {40404 40405 40406 40504 40505 40506 40604 40605 40606} +do_eqp_test 211 { + SELECT y FROM t2 ORDER BY y; +} { + QUERY PLAN + |--MATERIALIZE c1 + | |--MATERIALIZE c + | | `--SCAN 3 CONSTANT ROWS + | `--SCAN c + |--MATERIALIZE c2 + | `--SCAN c + |--SCAN c1 + |--SCAN c2 + |--SCAN t1 + `--USE TEMP B-TREE FOR ORDER BY +} +do_execsql_test 220 { + DROP VIEW t2; + CREATE VIEW t2(y) AS + WITH c(z) AS MATERIALIZED (VALUES(4),(5),(6)) + SELECT c1.z+c2.z*100+t1.x*10000 + FROM t1, + (SELECT z FROM c LIMIT 5) AS c1, + (SELECT z FROM c LIMIT 5) AS c2; + SELECT y FROM t2 ORDER BY y; +} {40404 40405 40406 40504 40505 40506 40604 40605 40606} + + + +finish_test diff --git a/test/without_rowid1.test b/test/without_rowid1.test index 02a089610..e4e69eb72 100644 --- a/test/without_rowid1.test +++ b/test/without_rowid1.test @@ -452,5 +452,18 @@ do_execsql_test 13.10 { PRAGMA integrity_check; SELECT * FROM t0, t1; } {ok abc xyz abc xyz} + +# 2021-05-13 https://sqlite.org/forum/forumpost/6c8960f545 +reset_db +do_execsql_test 14.1 { + CREATE TABLE t1(a INT PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t1(a) VALUES(10); + ALTER TABLE t1 ADD COLUMN b INT; + SELECT * FROM t1 WHERE a=20 OR (a=10 AND b=10); +} {} +do_execsql_test 14.2 { + CREATE TABLE dual AS SELECT 'X' AS dummy; + EXPLAIN QUERY PLAN SELECT * FROM dual, t1 WHERE a=10 AND b=10; +} {~/b=/} finish_test diff --git a/test/without_rowid6.test b/test/without_rowid6.test index 567acfaed..06fc7435b 100644 --- a/test/without_rowid6.test +++ b/test/without_rowid6.test @@ -64,7 +64,7 @@ do_execsql_test_if_vtab without_rowid6-201 { do_execsql_test without_rowid6-210 { EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>3 ORDER BY b; -} {/SEARCH TABLE t1 USING PRIMARY KEY .b>../} +} {/SEARCH t1 USING PRIMARY KEY .b>../} do_execsql_test without_rowid6-220 { PRAGMA index_list(t1); } {/sqlite_autoindex_t1_2 1 pk/} @@ -83,7 +83,7 @@ do_execsql_test without_rowid6-300 { do_execsql_test without_rowid6-310 { EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>3 ORDER BY b; -} {/SEARCH TABLE t1 USING PRIMARY KEY .b>../} +} {/SEARCH t1 USING PRIMARY KEY .b>../} do_execsql_test without_rowid6-320 { PRAGMA index_list(t1); } {/sqlite_autoindex_t1_2 1 pk/} @@ -101,7 +101,7 @@ do_execsql_test without_rowid6-400 { do_execsql_test without_rowid6-410 { EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>3 ORDER BY b; -} {/SEARCH TABLE t1 USING PRIMARY KEY .b>../} +} {/SEARCH t1 USING PRIMARY KEY .b>../} do_execsql_test without_rowid6-420 { PRAGMA index_list(t1); } {/sqlite_autoindex_t1_2 1 pk/} @@ -121,7 +121,7 @@ do_execsql_test_if_vtab without_rowid6-501 { do_execsql_test without_rowid6-510 { EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>3 ORDER BY b; -} {/SEARCH TABLE t1 USING PRIMARY KEY .b>../} +} {/SEARCH t1 USING PRIMARY KEY .b>../} do_execsql_test without_rowid6-520 { PRAGMA index_list(t1); } {/sqlite_autoindex_t1_1 1 pk/} diff --git a/test/zipfile.test b/test/zipfile.test index 79298a050..8749f17c3 100644 --- a/test/zipfile.test +++ b/test/zipfile.test @@ -843,4 +843,16 @@ do_catchsql_test 16.20 { REPLACE INTO zipfile VALUES(null,null,null,null,null,123,null); } {1 {zipfile: missing filename}} +# 2021-04-22 forum https://sqlite.org/forum/forumpost/d82289d69f +do_execsql_test 17.1 { + WITH vlist(x) AS ( + VALUES(9223372036854775807), + (-9223372036854775808), + (9223372036854775806), + (-9223372036854775807) + ) + SELECT DISTINCT typeof(zipfile(0,0,x,0)) FROM vlist; +} {blob} + + finish_test diff --git a/tool/lemon.c b/tool/lemon.c index 54c8946a0..75fc7aa2f 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -401,7 +401,7 @@ struct lemon { struct symbol *errsym; /* The error symbol */ struct symbol *wildcard; /* Token that matches anything */ char *name; /* Name of the generated parser */ - char *arg; /* Declaration of the 3th argument to parser */ + char *arg; /* Declaration of the 3rd argument to parser */ char *ctx; /* Declaration of 2nd argument to constructor */ char *tokentype; /* Type of terminal symbols in the parser stack */ char *vartype; /* The default type of non-terminal symbols */ @@ -1027,7 +1027,7 @@ PRIVATE void buildshifts(struct lemon *lemp, struct state *stp) struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */ struct state *newstp; /* A pointer to a successor state */ - /* Each configuration becomes complete after it contibutes to a successor + /* Each configuration becomes complete after it contributes to a successor ** state. Initially, all configurations are incomplete */ for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE; @@ -1887,7 +1887,7 @@ static char *merge( ** ** Return Value: ** A pointer to the head of a sorted list containing the elements -** orginally in list. +** originally in list. ** ** Side effects: ** The "next" pointers for elements in list are changed. @@ -2708,7 +2708,7 @@ static void parseonetoken(struct pstate *psp) ** in order to control their assigned integer number. The number for ** each token is assigned when it is first seen. So by including ** - ** %token ONE TWO THREE + ** %token ONE TWO THREE. ** ** early in the grammar file, that assigns small consecutive values ** to each of the tokens ONE TWO and THREE. @@ -3513,7 +3513,7 @@ void ReportOutput(struct lemon *lemp) } /* Search for the file "name" which is in the same directory as -** the exacutable */ +** the executable */ PRIVATE char *pathsearch(char *argv0, char *name, int modemask) { const char *pathlist; @@ -3868,7 +3868,7 @@ PRIVATE int translate_code(struct lemon *lemp, struct rule *rp){ lhsdirect = 1; }else if( rp->rhsalias[0]==0 ){ /* The left-most RHS symbol has no value. LHS direct is ok. But - ** we have to call the distructor on the RHS symbol first. */ + ** we have to call the destructor on the RHS symbol first. */ lhsdirect = 1; if( has_destructor(rp->rhs[0],lemp) ){ append_str(0,0,0,0); @@ -4849,7 +4849,7 @@ void ReportTable( ** yyRuleInfoNRhs[]. ** ** Note: This code depends on the fact that rules are number - ** sequentually beginning with 0. + ** sequentially beginning with 0. */ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){ fprintf(out," %4d, /* (%d) ", rp->lhs->index, i); diff --git a/tool/lempar.c b/tool/lempar.c index 71a51cf44..35c3768bb 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -718,55 +718,6 @@ static YYACTIONTYPE yy_reduce( (void)yyLookahead; (void)yyLookaheadToken; yymsp = yypParser->yytos; - assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); -#ifndef NDEBUG - if( yyTraceFILE ){ - yysize = yyRuleInfoNRhs[yyruleno]; - if( yysize ){ - fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", - yyTracePrompt, - yyruleno, yyRuleName[yyruleno], - yyrulenoyytos - yypParser->yystack)>yypParser->yyhwm ){ - yypParser->yyhwm++; - assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack)); - } -#endif -#if YYSTACKDEPTH>0 - if( yypParser->yytos>=yypParser->yystackEnd ){ - yyStackOverflow(yypParser); - /* The call to yyStackOverflow() above pops the stack until it is - ** empty, causing the main parser loop to exit. So the return value - ** is never used and does not matter. */ - return 0; - } -#else - if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ - if( yyGrowStack(yypParser) ){ - yyStackOverflow(yypParser); - /* The call to yyStackOverflow() above pops the stack until it is - ** empty, causing the main parser loop to exit. So the return value - ** is never used and does not matter. */ - return 0; - } - yymsp = yypParser->yytos; - } -#endif - } switch( yyruleno ){ /* Beginning here are the reduction cases. A typical example @@ -925,12 +876,56 @@ void Parse( } #endif - do{ + while(1){ /* Exit by "break" */ + assert( yypParser->yytos>=yypParser->yystack ); assert( yyact==yypParser->yytos->stateno ); yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); if( yyact >= YY_MIN_REDUCE ){ - yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor, - yyminor ParseCTX_PARAM); + unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */ + assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); +#ifndef NDEBUG + if( yyTraceFILE ){ + int yysize = yyRuleInfoNRhs[yyruleno]; + if( yysize ){ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", + yyTracePrompt, + yyruleno, yyRuleName[yyruleno], + yyrulenoyytos[yysize].stateno); + }else{ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n", + yyTracePrompt, yyruleno, yyRuleName[yyruleno], + yyrulenoyytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == + (int)(yypParser->yytos - yypParser->yystack)); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>=yypParser->yystackEnd ){ + yyStackOverflow(yypParser); + break; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + break; + } + } +#endif + } + yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor ParseCTX_PARAM); }else if( yyact <= YY_MAX_SHIFTREDUCE ){ yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor); #ifndef YYNOERRORRECOVERY @@ -1043,7 +1038,7 @@ void Parse( break; #endif } - }while( yypParser->yytos>yypParser->yystack ); + } #ifndef NDEBUG if( yyTraceFILE ){ yyStackEntry *i; diff --git a/tool/merge-test.tcl b/tool/merge-test.tcl new file mode 100644 index 000000000..2010d6765 --- /dev/null +++ b/tool/merge-test.tcl @@ -0,0 +1,99 @@ +#!/usr/bin/tcl +# +# Run this script to test to see that the latest trunk changes can be +# merged into LTS branches without breaking anything. +# +# To Use: +# +# * Copy this script into a directory above the sqlite checkout +# * Run "fossil update trunk" and "fossil revert" +# * Run "tclsh ../merge-test.tcl" (in other words run this script) +# +# Operation: +# +# This script changes to each LTS branch to be tested, merges the latest +# trunk changes into the branch (without committing them) and then +# runs "make test". Any errors are stored in local files. +# +# Limitations: +# +# Some LTS branches are not synced directly from trunk but rather from +# other LTS branches. These other branches cannot be tested because +# there is no good way to generate the intermediate merges. +# +############################################################################### + +# Run a shell command contained in arguments. Put the return code in +# global variable ::res and the output string in global variable ::result +# +proc safeexec {args} { + global res result + set res [catch "exec $args" result] +} + +# Run the shell command contained in arguments. Print an error and exit +# if anything goes wrong. +# +proc mustbeok {args} { + global res result + set res [catch "exec $args" result] + if {$res} { + puts "FAILED: $args" + puts $result + exit 1 + } +} + +# Write $content into a file named $filename. The file is overwritten if it +# already exist. The file is create if it does not already exist. +# +proc writefile {filename content} { + set fd [open $filename wb] + puts $fd $content + close $fd +} + +# Run the merge-test +# +foreach {branch configopts} { + begin-concurrent {--enable-json1} + begin-concurrent-pnu {--enable-json1} + wal2 {--enable-all} + reuse-schema {--enable-all} +} { + puts $branch + set errorfile ${branch}-error.txt + mustbeok fossil revert + mustbeok fossil up $branch + safeexec fossil merge trunk + if {$res} { + puts " merge failed - see $errorfile" + writefile $errorfile $result + } else { + puts " merge ok" + safeexec ./configure --enable-debug {*}$configopts + if {$res} { + puts " configure failed - see $errorfile" + writefile $errorfile $result + } else { + puts " configure ok" + safeexec make fuzzcheck sqlite3 testfixture + if {$res} { + puts " build failed - see $errorfile" + writefile $errorfile $result + } else { + puts " build ok" + safeexec make test + if {$res} { + puts " test failed - see $errorfile" + writefile $errorfile $result + } else { + puts " test ok" + } + } + } + } +} +mustbeok fossil revert +mustbeok fossil up trunk +puts "reset back to trunk" diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl index 499d5a563..2f15ac9fb 100644 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -4,16 +4,28 @@ # # const char **azCompileOpt[] # -# declaration used in src/ctime.c, run this script. +# definition used in src/ctime.c, run this script from +# the checkout root. It alters src/ctime.c in-place. # -# All Boolean compile time options. +# All Boolean compile time options which default to something +# other than 0 or empty. The default is paired with the PP +# symbol so that a differing define can be detected. # -set boolean_options { +set boolean_defnnz_options { + {SQLITE_HOMEGROWN_RECURSIVE_MUTEX 1} + {SQLITE_POWERSAFE_OVERWRITE 1} + {SQLITE_DEFAULT_MEMSTATUS 1} + {SQLITE_OMIT_TRACE 1} + {SQLITE_ALLOW_COVERING_INDEX_SCAN 1} +} + +# All Boolean compile time options which default to 0 or empty. +# +set boolean_defnil_options { SQLITE_32BIT_ROWID SQLITE_4_BYTE_ALIGNED_MALLOC SQLITE_64BIT_STATS - SQLITE_ALLOW_COVERING_INDEX_SCAN SQLITE_ALLOW_URI_AUTHORITY SQLITE_BUG_COMPATIBLE_20160819 SQLITE_CASE_SENSITIVE_LIKE @@ -25,7 +37,6 @@ set boolean_options { SQLITE_DEFAULT_CKPTFULLFSYNC SQLITE_DEFAULT_FOREIGN_KEYS SQLITE_DEFAULT_LOCKING_MODE - SQLITE_DEFAULT_MEMSTATUS SQLITE_DEFAULT_RECURSIVE_TRIGGERS SQLITE_DEFAULT_SYNCHRONOUS SQLITE_DEFAULT_WAL_SYNCHRONOUS @@ -40,40 +51,49 @@ set boolean_options { SQLITE_ENABLE_8_3_NAMES SQLITE_ENABLE_API_ARMOR SQLITE_ENABLE_ATOMIC_WRITE - SQLITE_ENABLE_CEROD + SQLITE_ENABLE_BATCH_ATOMIC_WRITE + SQLITE_ENABLE_BYTECODE_VTAB SQLITE_ENABLE_COLUMN_METADATA SQLITE_ENABLE_COLUMN_USED_MASK SQLITE_ENABLE_COSTMULT SQLITE_ENABLE_CURSOR_HINTS + SQLITE_ENABLE_DBPAGE_VTAB SQLITE_ENABLE_DBSTAT_VTAB SQLITE_ENABLE_EXPENSIVE_ASSERT - SQLITE_ENABLE_FTS1 - SQLITE_ENABLE_FTS2 + SQLITE_ENABLE_EXPLAIN_COMMENTS SQLITE_ENABLE_FTS3 SQLITE_ENABLE_FTS3_PARENTHESIS SQLITE_ENABLE_FTS3_TOKENIZER SQLITE_ENABLE_FTS4 SQLITE_ENABLE_FTS5 + SQLITE_ENABLE_GEOPOLY SQLITE_ENABLE_HIDDEN_COLUMNS SQLITE_ENABLE_ICU SQLITE_ENABLE_IOTRACE SQLITE_ENABLE_JSON1 SQLITE_ENABLE_LOAD_EXTENSION SQLITE_ENABLE_LOCKING_STYLE + SQLITE_ENABLE_MATH_FUNCTIONS SQLITE_ENABLE_MEMORY_MANAGEMENT SQLITE_ENABLE_MEMSYS3 SQLITE_ENABLE_MEMSYS5 SQLITE_ENABLE_MULTIPLEX + SQLITE_ENABLE_NORMALIZE SQLITE_ENABLE_NULL_TRIM + SQLITE_ENABLE_OFFSET_SQL_FUNC SQLITE_ENABLE_OVERSIZE_CELL_CHECK SQLITE_ENABLE_PREUPDATE_HOOK + SQLITE_ENABLE_QPSG SQLITE_ENABLE_RBU SQLITE_ENABLE_RTREE SQLITE_ENABLE_SELECTTRACE SQLITE_ENABLE_SESSION SQLITE_ENABLE_SNAPSHOT + SQLITE_ENABLE_SORTER_REFERENCES SQLITE_ENABLE_SQLLOG + SQLITE_ENABLE_STAT4 SQLITE_ENABLE_STMT_SCANSTATUS + SQLITE_ENABLE_STMTVTAB SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION SQLITE_ENABLE_UNLOCK_NOTIFY SQLITE_ENABLE_UPDATE_DELETE_LIMIT @@ -85,8 +105,6 @@ set boolean_options { SQLITE_EXTRA_IFNULLROW SQLITE_FTS5_ENABLE_TEST_MI SQLITE_FTS5_NO_WITHOUT_ROWID - SQLITE_HAS_CODEC - SQLITE_HOMEGROWN_RECURSIVE_MUTEX SQLITE_IGNORE_AFP_LOCK_ERRORS SQLITE_IGNORE_FLOCK_LOCK_ERRORS SQLITE_INLINE_MEMCPY @@ -98,7 +116,6 @@ set boolean_options { SQLITE_MIXED_ENDIAN_64BIT_FLOAT SQLITE_MMAP_READWRITE SQLITE_MUTEX_NOOP - SQLITE_MUTEX_NREF SQLITE_MUTEX_OMIT SQLITE_MUTEX_PTHREADS SQLITE_MUTEX_W32 @@ -116,16 +133,15 @@ set boolean_options { SQLITE_OMIT_AUTOVACUUM SQLITE_OMIT_BETWEEN_OPTIMIZATION SQLITE_OMIT_BLOB_LITERAL - SQLITE_OMIT_BTREECOUNT SQLITE_OMIT_CAST SQLITE_OMIT_CHECK SQLITE_OMIT_COMPLETE SQLITE_OMIT_COMPOUND_SELECT SQLITE_OMIT_CONFLICT_CLAUSE SQLITE_OMIT_CTE - SQLITE_OMIT_DATETIME_FUNCS SQLITE_OMIT_DECLTYPE SQLITE_OMIT_DEPRECATED + SQLITE_OMIT_DESERIALIZE SQLITE_OMIT_DISKIO SQLITE_OMIT_EXPLAIN SQLITE_OMIT_FLAG_PRAGMAS @@ -135,6 +151,7 @@ set boolean_options { SQLITE_OMIT_HEX_INTEGER SQLITE_OMIT_INCRBLOB SQLITE_OMIT_INTEGRITY_CHECK + SQLITE_OMIT_INTROSPECTION_PRAGMAS SQLITE_OMIT_LIKE_OPTIMIZATION SQLITE_OMIT_LOAD_EXTENSION SQLITE_OMIT_LOCALTIME @@ -156,7 +173,6 @@ set boolean_options { SQLITE_OMIT_TCL_VARIABLE SQLITE_OMIT_TEMPDB SQLITE_OMIT_TEST_CONTROL - SQLITE_OMIT_TRACE SQLITE_OMIT_TRIGGER SQLITE_OMIT_TRUNCATE_OPTIMIZATION SQLITE_OMIT_UTF16 @@ -168,7 +184,6 @@ set boolean_options { SQLITE_OMIT_XFER_OPT SQLITE_PCACHE_SEPARATE_HEADER SQLITE_PERFORMANCE_TRACE - SQLITE_POWERSAFE_OVERWRITE SQLITE_PREFER_PROXY_LOCKING SQLITE_PROXY_DEBUG SQLITE_REVERSE_UNORDERED_SELECTS @@ -177,7 +192,6 @@ set boolean_options { SQLITE_SMALL_STACK SQLITE_SOUNDEX SQLITE_SUBSTR_COMPATIBILITY - SQLITE_SYSTEM_MALLOC SQLITE_TCL SQLITE_TEST SQLITE_UNLINK_AFTER_CLOSE @@ -191,7 +205,15 @@ set boolean_options { SQLITE_ZERO_MALLOC } -# All compile time options for which the assigned value is other than boolean. +# All compile time options for which the assigned value is other than boolean +# and is a comma-separated scalar pair. +# +set value2_options { + SQLITE_DEFAULT_LOOKASIDE +} + +# All compile time options for which the assigned value is other than boolean +# and is a single scalar. # set value_options { SQLITE_BITMASK_TYPE @@ -200,7 +222,6 @@ set value_options { SQLITE_DEFAULT_FILE_PERMISSIONS SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT SQLITE_DEFAULT_LOCKING_MODE - SQLITE_DEFAULT_LOOKASIDE SQLITE_DEFAULT_MMAP_SIZE SQLITE_DEFAULT_PAGE_SIZE SQLITE_DEFAULT_PCACHE_INITSZ @@ -212,6 +233,7 @@ set value_options { SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_WORKER_THREADS SQLITE_ENABLE_8_3_NAMES + SQLITE_ENABLE_CEROD SQLITE_ENABLE_LOCKING_STYLE SQLITE_EXTRA_INIT SQLITE_EXTRA_SHUTDOWN @@ -245,13 +267,6 @@ set value_options { # Options that require custom code. # -set options(ENABLE_STAT3) { -#if defined(SQLITE_ENABLE_STAT4) - "ENABLE_STAT4", -#elif defined(SQLITE_ENABLE_STAT3) - "ENABLE_STAT3", -#endif -} set options(COMPILER) { #if defined(__clang__) && defined(__clang_major__) "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "." @@ -268,13 +283,26 @@ set options(HAVE_ISNAN) { "HAVE_ISNAN", #endif } +set options(OMIT_DATETIME_FUNCS) { +#if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT) + "OMIT_DATETIME_FUNCS", +#endif +} +set options(SYSTEM_MALLOC) "\ +#if (!defined(SQLITE_WIN32_MALLOC) \\ + && !defined(SQLITE_ZERO_MALLOC) \\ + && !defined(SQLITE_MEMDEBUG) \\ + ) || defined(SQLITE_SYSTEM_MALLOC) + \"SYSTEM_MALLOC\", +#endif +" set options(THREADSAFE) { #if defined(SQLITE_THREADSAFE) "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), #elif defined(THREADSAFE) "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE), #else - "THREADSAFE=1" + "THREADSAFE=1", #endif } @@ -286,7 +314,20 @@ proc trim_name {in} { return $ret } -foreach b $boolean_options { +foreach name_defval $boolean_defnnz_options { + set b [lindex $name_defval 0] + set defval [lindex $name_defval 1] + set name [trim_name $b] + set options($name) [subst { +#ifdef $b +# if $b != $defval + "$name=" CTIMEOPT_VAL($b), +# endif +#endif +}] +} + +foreach b $boolean_defnil_options { set name [trim_name $b] set options($name) [subst { #if $b @@ -303,9 +344,70 @@ foreach v $value_options { #endif }] } + +foreach v $value2_options { + set name [trim_name $v] + set options($name) [subst { +#ifdef $v + "$name=" CTIMEOPT_VAL2($v), +#endif +}] +} -foreach o [lsort [array names options]] { - puts [string trim $options($o)] +# Split a string on a regex, return all parts in order. +# Any elements with an even index may be empty. +# Elements with odd indices will match the regex. +proc split_on_re {re str {nrepps 1}} { + set chunks {} + set cix 0 + set resm [regexp -all -inline -indices $re $str] + if {[llength $resm]==0} { + return $str + } + set rix 0 + while {$rix < [llength $resm]} { + set mre [lindex $resm $rix] + incr rix $nrepps + set mbx [lindex $mre 0] + set mex [lindex $mre 1] + lappend chunks [string range $str $cix [expr $mbx - 1]] + lappend chunks [string range $str $mbx $mex] + set cix [expr $mex + 1] + } + lappend chunks [string range $str $cix end] + return $chunks } +set ctime_c "src/ctime.c" +if {[catch {set cfd [open $ctime_c r]}]!=0} { + puts stderr "File '$ctime_c' unreadable. Run this script from checkout root." + exit 1; +} + +set ctfc [read $cfd] +close $cfd + +set re {/\*\s+\*+\s*((BEGIN)|(END)) CODE GENERATED BY (\S+)\s+\*/\s+} +set renpp 5 + +set ctfcChunks [split_on_re $re $ctfc $renpp] +if {[llength $ctfcChunks] != 5} { + puts stderr "File '$ctime_c' has too few generated code markers." + exit 1; +} + +if {[catch {set cfd [open $ctime_c w]}]!=0} { + puts stderr "File '$ctime_c' unwritable." + exit 1; +} + +puts -nonewline $cfd [lindex $ctfcChunks 0] +puts -nonewline $cfd [lindex $ctfcChunks 1] +foreach o [lsort [array names options]] { + puts $cfd [string trim $options($o)] +} +puts -nonewline $cfd [lindex $ctfcChunks 3] +puts -nonewline $cfd [lindex $ctfcChunks 4] + +close $cfd diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c index 83ec179ba..bbb0ccf29 100644 --- a/tool/mkkeywordhash.c +++ b/tool/mkkeywordhash.c @@ -155,10 +155,16 @@ struct Keyword { # define WINDOWFUNC 0x00100000 #endif #ifdef SQLITE_OMIT_GENERATED_COLUMNS -# define GENCOL 0 +# define GENCOL 0 #else -# define GENCOL 0x00200000 +# define GENCOL 0x00200000 #endif +#ifdef SQLITE_OMIT_RETURNING +# define RETURNING 0 +#else +# define RETURNING 0x00400000 +#endif + /* ** These are the keywords @@ -223,7 +229,7 @@ static Keyword aKeywordTable[] = { { "FOREIGN", "TK_FOREIGN", FKEY, 1 }, { "FROM", "TK_FROM", ALWAYS, 10 }, { "FULL", "TK_JOIN_KW", ALWAYS, 3 }, - { "GENERATED", "TK_GENERATED", GENCOL, 1 }, + { "GENERATED", "TK_GENERATED", ALWAYS, 1 }, { "GLOB", "TK_LIKE_KW", ALWAYS, 3 }, { "GROUP", "TK_GROUP", ALWAYS, 5 }, { "GROUPS", "TK_GROUPS", WINDOWFUNC, 2 }, @@ -249,6 +255,7 @@ static Keyword aKeywordTable[] = { { "LIKE", "TK_LIKE_KW", ALWAYS, 5 }, { "LIMIT", "TK_LIMIT", ALWAYS, 3 }, { "MATCH", "TK_MATCH", ALWAYS, 2 }, + { "MATERIALIZED", "TK_MATERIALIZED", CTE, 12 }, { "NATURAL", "TK_JOIN_KW", ALWAYS, 3 }, { "NO", "TK_NO", FKEY|WINDOWFUNC, 2 }, { "NOT", "TK_NOT", ALWAYS, 10 }, @@ -280,6 +287,7 @@ static Keyword aKeywordTable[] = { { "RENAME", "TK_RENAME", ALTER, 1 }, { "REPLACE", "TK_REPLACE", CONFLICT, 10 }, { "RESTRICT", "TK_RESTRICT", FKEY, 1 }, + { "RETURNING", "TK_RETURNING", RETURNING, 10 }, { "RIGHT", "TK_JOIN_KW", ALWAYS, 0 }, { "ROLLBACK", "TK_ROLLBACK", ALWAYS, 1 }, { "ROW", "TK_ROW", TRIGGER, 1 }, @@ -381,6 +389,14 @@ static void reorder(int *pFrom){ reorder(&aKeywordTable[i].iNext); } +/* Parameter to the hash function +*/ +#define HASH_OP ^ +#define HASH_CC '^' +#define HASH_C0 4 +#define HASH_C1 3 +#define HASH_C2 1 + /* ** This routine does the work. The generated code is printed on standard ** output. @@ -411,8 +427,9 @@ int main(int argc, char **argv){ assert( p->lenzOrigName) ); memcpy(p->zOrigName, p->zName, p->len+1); totalLen += p->len; - p->hash = (charMap(p->zName[0])*4) ^ - (charMap(p->zName[p->len-1])*3) ^ (p->len*1); + p->hash = (charMap(p->zName[0])*HASH_C0) HASH_OP + (charMap(p->zName[p->len-1])*HASH_C1) HASH_OP + (p->len*HASH_C2); p->id = i+1; } @@ -648,8 +665,9 @@ int main(int argc, char **argv){ printf(" int i, j;\n"); printf(" const char *zKW;\n"); printf(" if( n>=2 ){\n"); - printf(" i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n) %% %d;\n", - bestSize); + printf(" i = ((charMap(z[0])*%d) %c", HASH_C0, HASH_CC); + printf(" (charMap(z[n-1])*%d) %c", HASH_C1, HASH_CC); + printf(" n*%d) %% %d;\n", HASH_C2, bestSize); printf(" for(i=((int)aKWHash[i])-1; i>=0; i=((int)aKWNext[i])-1){\n"); printf(" if( aKWLen[i]!=n ) continue;\n"); printf(" zKW = &zKWText[aKWOffset[i]];\n"); diff --git a/tool/mkopcodeh.tcl b/tool/mkopcodeh.tcl index 57017364a..d4f3c4f13 100644 --- a/tool/mkopcodeh.tcl +++ b/tool/mkopcodeh.tcl @@ -145,7 +145,9 @@ foreach name {OP_Noop OP_Explain OP_Abortable} { incr nOp } -# The following are the opcodes that are processed by resolveP2Values() +# The following are the opcodes that receive special processing in the +# resolveP2Values() routine. Update this list whenever new cases are +# added to the pOp->opcode switch within resolveP2Values(). # set rp2v_ops { OP_Transaction @@ -157,13 +159,11 @@ set rp2v_ops { OP_VUpdate OP_VFilter OP_Next - OP_NextIfOpen OP_SorterNext OP_Prev - OP_PrevIfOpen } -# Assign small values to opcodes that are processed by resolveP2Values() +# Assign the smallest values to opcodes that are processed by resolveP2Values() # to make code generation for the switch() statement smaller and faster. # set cnt -1 @@ -177,6 +177,7 @@ for {set i 0} {$i<$nOp} {incr i} { set def($cnt) $name } } +set mxCase1 $cnt # Assign the next group of values to JUMP opcodes # @@ -207,8 +208,9 @@ for {set i 0} {$i<$nOp} {incr i} { for {set g 0} {$g<$nGroup} {incr g} { set gLen [llength $groups($g)] set ok 0; set start -1 + set seek $cnt while {!$ok} { - set seek $cnt; incr seek + incr seek while {[info exists used($seek)]} {incr seek} set ok 1; set start $seek for {set j 0} {$j<$gLen} {incr j} { @@ -311,7 +313,7 @@ for {set i 0} {$i<=$max} {incr i} { } puts "\175" puts "" -puts "/* The sqlite3P2Values() routine is able to run faster if it knows" +puts "/* The resolve3P2Values() routine is able to run faster if it knows" puts "** the value of the largest JUMP opcode. The smaller the maximum" puts "** JUMP opcode the better, so the mkopcodeh.tcl script that" puts "** generated this include file strives to group all JUMP opcodes" diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 4f0e17f83..46c2a528f 100644 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -11,6 +11,7 @@ # set topdir [file dir [file dir [file normal $argv0]]] set out stdout +fconfigure stdout -translation binary puts $out {/* DO NOT EDIT! ** This file is automatically generated by the script in the canonical ** SQLite source tree at tool/mkshellc.tcl. That script combines source diff --git a/tool/omittest.tcl b/tool/omittest.tcl index 120fc2afd..9f24d6d3d 100644 --- a/tool/omittest.tcl +++ b/tool/omittest.tcl @@ -206,6 +206,7 @@ proc main {argv} { SQLITE_OMIT_DATETIME_FUNCS \ SQLITE_OMIT_DECLTYPE \ SQLITE_OMIT_DEPRECATED \ + SQLITE_OMIT_DESERIALIZE \ SQLITE_OMIT_DISKIO \ SQLITE_OMIT_EXPLAIN \ SQLITE_OMIT_FLAG_PRAGMAS \ @@ -254,6 +255,7 @@ proc main {argv} { ] set ::ENABLE_SYMBOLS [list \ + SQLITE_ALLOW_ROWID_IN_VIEW \ SQLITE_DISABLE_DIRSYNC \ SQLITE_DISABLE_LFS \ SQLITE_ENABLE_ATOMIC_WRITE \ diff --git a/tool/showdb.c b/tool/showdb.c index 09c4d9de4..3c91967ed 100644 --- a/tool/showdb.c +++ b/tool/showdb.c @@ -753,7 +753,7 @@ static void decode_trunk_page( n = decodeInt32(&a[4]); for(i=0; i= g.pagesize ){ + printf("ERROR: page %d too many cells (%d)\n", pgno, nCell); + break; + } + ofst = a[cellidx]*256 + a[cellidx+1]; + if( ofst=g.pagesize ){ + printf("ERROR: page %d cell %d out of bounds\n", pgno, i); + continue; + } child = decodeInt32(a+ofst); page_usage_btree(child, pgno, i, zName); } @@ -1032,6 +1041,8 @@ static void page_usage_report(const char *zPrg, const char *zDbName){ for(i=1; i<=g.mxPage; i++){ if( zPageUse[i]==0 ) page_usage_btree(i, -1, 0, 0); printf("%5u: %s\n", i, zPageUse[i] ? zPageUse[i] : "???"); + } + for(i=1; i<=g.mxPage; i++){ sqlite3_free(zPageUse[i]); } sqlite3_free(zPageUse); diff --git a/tool/sqldiff.c b/tool/sqldiff.c index 123d5b49b..9844cbadf 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -1712,20 +1712,28 @@ static void changeset_one_table(const char *zTab, FILE *out){ strFree(&sql); } +/* +** Return true if the ascii character passed as the only argument is a +** whitespace character. Otherwise return false. +*/ +static int is_whitespace(char x){ + return (x==' ' || x=='\t' || x=='\n' || x=='\r'); +} + /* ** Extract the next SQL keyword or quoted string from buffer zIn and copy it ** (or a prefix of it if it will not fit) into buffer zBuf, size nBuf bytes. ** Return a pointer to the character within zIn immediately following ** the token or quoted string just extracted. */ -const char *gobble_token(const char *zIn, char *zBuf, int nBuf){ +static const char *gobble_token(const char *zIn, char *zBuf, int nBuf){ const char *p = zIn; char *pOut = zBuf; char *pEnd = &pOut[nBuf-1]; char q = 0; /* quote character, if any */ if( p==0 ) return 0; - while( *p==' ' ) p++; + while( is_whitespace(*p) ) p++; switch( *p ){ case '"': q = '"'; break; case '\'': q = '\''; break; @@ -1744,7 +1752,7 @@ const char *gobble_token(const char *zIn, char *zBuf, int nBuf){ p++; } }else{ - while( *p && *p!=' ' && *p!='(' ){ + while( *p && !is_whitespace(*p) && *p!='(' ){ if( pOut Date: Wed, 6 Oct 2021 16:26:26 -0400 Subject: [PATCH 011/340] update version to 4.5.0 --- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index b648eb188..1b2e3dc97 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -59,7 +59,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.4.3 +#define CIPHER_VERSION_NUMBER 4.5.0 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index 57c346c2b..f6eb7de1e 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.4.3 community}} +} {{4.5.0 community}} db close file delete -force test.db From e883653170572848a4436b91ebe8a0e984f61c58 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 8 Oct 2021 12:53:36 -0400 Subject: [PATCH 012/340] switch to flexible cipher_test pragma for controlling test failures --- src/crypto.c | 28 ++++++++++++---------------- src/crypto.h | 6 ++++++ test/sqlcipher-codecerror.test | 16 ++++++++-------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 5a181a885..d9c888feb 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -40,8 +40,7 @@ #endif #ifdef SQLCIPHER_TEST -static int cipher_fail_next_encrypt = 0; -static int cipher_fail_next_decrypt = 0; +static int cipher_test_flags = 0; #endif /* Generate code to return a string value */ @@ -117,20 +116,17 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else #endif #ifdef SQLCIPHER_TEST - if( sqlite3StrICmp(zLeft,"cipher_fail_next_encrypt")==0 ){ + if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ if( zRight ) { - cipher_fail_next_encrypt = sqlite3GetBoolean(zRight,1); - } else { - char *fail = sqlite3_mprintf("%d", cipher_fail_next_encrypt); - codec_vdbe_return_string(pParse, "cipher_fail_next_encrypt", fail, P4_DYNAMIC); - } - }else - if( sqlite3StrICmp(zLeft,"cipher_fail_next_decrypt")==0 ){ - if( zRight ) { - cipher_fail_next_decrypt = sqlite3GetBoolean(zRight,1); + if(sqlite3StrICmp(zRight, "fail_next_encrypt")) { + cipher_test_flags ^= TEST_FAIL_NEXT_ENCRYPT; + } else + if(sqlite3StrICmp(zRight, "fail_next_decrypt")) { + cipher_test_flags ^= TEST_FAIL_NEXT_DECRYPT; + } } else { - char *fail = sqlite3_mprintf("%d", cipher_fail_next_decrypt); - codec_vdbe_return_string(pParse, "cipher_fail_next_decrypt", fail, P4_DYNAMIC); + char *flags = sqlite3_mprintf("%d", cipher_test_flags); + codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); } }else #endif @@ -718,7 +714,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if(cipher_fail_next_decrypt) rc = SQLITE_ERROR; + if((cipher_test_flags & TEST_FAIL_NEXT_ENCRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); @@ -743,7 +739,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { } rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if(cipher_fail_next_encrypt) rc = SQLITE_ERROR; + if((cipher_test_flags & TEST_FAIL_NEXT_DECRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); diff --git a/src/crypto.h b/src/crypto.h index 1b2e3dc97..49e7080d4 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -191,6 +191,12 @@ static int cipher_isHex(const unsigned char *hex, int sz){ return 1; } +/* possible flags for simulating specific test conditions */ +#ifdef SQLCIPHER_TEST +#define TEST_FAIL_NEXT_ENCRYPT (1ul << 0) /* 1 */ +#define TEST_FAIL_NEXT_DECRYPT (1ul << 1) /* 2 */ +#endif + /* extensions defined in crypto_impl.c */ /* the default implementation of SQLCipher uses a cipher_ctx to keep track of read / write state separately. The following diff --git a/test/sqlcipher-codecerror.test b/test/sqlcipher-codecerror.test index f481aaf27..927e8d198 100644 --- a/test/sqlcipher-codecerror.test +++ b/test/sqlcipher-codecerror.test @@ -66,7 +66,7 @@ do_test codec-error-journal-delete { catchsql { PRAGMA key = 'testkey'; - PRAGMA cipher_fail_next_encrypt = 1; + PRAGMA cipher_test = fail_next_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -74,7 +74,7 @@ do_test codec-error-journal-delete { sqlite_orig db test.db execsql { - PRAGMA cipher_fail_next_encrypt = 0; + PRAGMA cipher_test = fail_next_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -92,7 +92,7 @@ do_test codec-error-journal-wal { catchsql { PRAGMA key = 'testkey'; - PRAGMA cipher_fail_next_encrypt = 1; + PRAGMA cipher_test = fail_next_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -100,7 +100,7 @@ do_test codec-error-journal-wal { sqlite_orig db test.db execsql { - PRAGMA cipher_fail_next_encrypt = 0; + PRAGMA cipher_test = fail_next_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -120,7 +120,7 @@ do_test codec-error-journal-wal-transaction { PRAGMA key = 'testkey'; BEGIN; UPDATE t1 SET b = 'success' WHERE a = 1; - PRAGMA cipher_fail_next_encrypt = 1; + PRAGMA cipher_test = fail_next_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; COMMIT; } @@ -129,7 +129,7 @@ do_test codec-error-journal-wal-transaction { sqlite_orig db test.db execsql { - PRAGMA cipher_fail_next_encrypt = 0; + PRAGMA cipher_test = fail_next_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -149,7 +149,7 @@ do_test codec-error-journal-wal-read { catchsql { PRAGMA key = 'testkey'; SELECT count(*) FROM sqlite_schema; - PRAGMA cipher_fail_next_decrypt = 1; + PRAGMA cipher_test = fail_next_decrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -157,7 +157,7 @@ do_test codec-error-journal-wal-read { sqlite_orig db test.db execsql { - PRAGMA cipher_fail_next_decrypt = 0; + PRAGMA cipher_test = fail_next_decrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; From 99d36b40c80099723108f2860bac266abb80437e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 8 Oct 2021 13:02:16 -0400 Subject: [PATCH 013/340] relocate test flag management to crypto_impl.c --- src/crypto.c | 15 +++++---------- src/crypto.h | 2 ++ src/crypto_impl.c | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index d9c888feb..4581939e8 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -39,11 +39,6 @@ #include "sqlcipher_ext.h" #endif -#ifdef SQLCIPHER_TEST -static int cipher_test_flags = 0; -#endif - -/* Generate code to return a string value */ static void codec_vdbe_return_string(Parse *pParse, const char *zLabel, const char *value, int value_type){ Vdbe *v = sqlite3GetVdbe(pParse); sqlite3VdbeSetNumCols(v, 1); @@ -119,13 +114,13 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ if( zRight ) { if(sqlite3StrICmp(zRight, "fail_next_encrypt")) { - cipher_test_flags ^= TEST_FAIL_NEXT_ENCRYPT; + sqlcipher_set_test_flags(sqlcipher_get_test_flags() ^ TEST_FAIL_NEXT_ENCRYPT); } else if(sqlite3StrICmp(zRight, "fail_next_decrypt")) { - cipher_test_flags ^= TEST_FAIL_NEXT_DECRYPT; + sqlcipher_set_test_flags(sqlcipher_get_test_flags() ^ TEST_FAIL_NEXT_DECRYPT); } } else { - char *flags = sqlite3_mprintf("%d", cipher_test_flags); + char *flags = sqlite3_mprintf("%d", sqlcipher_get_test_flags()); codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); } }else @@ -714,7 +709,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((cipher_test_flags & TEST_FAIL_NEXT_ENCRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_NEXT_ENCRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); @@ -739,7 +734,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { } rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((cipher_test_flags & TEST_FAIL_NEXT_DECRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_NEXT_DECRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); diff --git a/src/crypto.h b/src/crypto.h index 49e7080d4..f0aee2db9 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -195,6 +195,8 @@ static int cipher_isHex(const unsigned char *hex, int sz){ #ifdef SQLCIPHER_TEST #define TEST_FAIL_NEXT_ENCRYPT (1ul << 0) /* 1 */ #define TEST_FAIL_NEXT_DECRYPT (1ul << 1) /* 2 */ +int sqlcipher_get_test_flags(void); +void sqlcipher_set_test_flags(int); #endif /* extensions defined in crypto_impl.c */ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 93c44079a..78fe69b31 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -44,6 +44,12 @@ #endif #endif +#ifdef SQLCIPHER_TEST +static volatile int cipher_test_flags = 0; +#endif + +/* Generate code to return a string value */ + static volatile unsigned int default_flags = DEFAULT_CIPHER_FLAGS; static volatile unsigned char hmac_salt_mask = HMAC_SALT_MASK; static volatile int default_kdf_iter = PBKDF2_ITER; @@ -396,6 +402,16 @@ char* sqlcipher_version() { return version; } +#ifdef SQLCIPHER_TEST +int sqlcipher_get_test_flags() { + return cipher_test_flags; +} + +void sqlcipher_set_test_flags(int flags) { + cipher_test_flags = flags; +} +#endif + /** * Initialize new cipher_ctx struct. This function will allocate memory * for the cipher context and for the key From 78796fd0e2b5d8170fc6954f01f7284f6866b06e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 8 Oct 2021 16:50:57 -0400 Subject: [PATCH 014/340] set error state if cipher_migrate fails --- src/crypto.c | 49 ++++++++++++++----- src/crypto.h | 9 ++-- src/crypto_impl.c | 30 +++++++----- test/sqlcipher-codecerror.test | 16 +++--- test/sqlcipher-compatibility.test | 81 ++++++++++++++++++++++++++----- test/sqlcipher-core.test | 50 +++++++++++++++++++ 6 files changed, 186 insertions(+), 49 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 4581939e8..45cbdc898 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -111,18 +111,39 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else #endif #ifdef SQLCIPHER_TEST - if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ + if( sqlite3StrICmp(zLeft,"cipher_test_on")==0 ){ if( zRight ) { - if(sqlite3StrICmp(zRight, "fail_next_encrypt")) { - sqlcipher_set_test_flags(sqlcipher_get_test_flags() ^ TEST_FAIL_NEXT_ENCRYPT); + unsigned int flags = sqlcipher_get_test_flags(); + if(sqlite3StrICmp(zRight, "fail_encrypt")==0) { + flags |= TEST_FAIL_ENCRYPT; } else - if(sqlite3StrICmp(zRight, "fail_next_decrypt")) { - sqlcipher_set_test_flags(sqlcipher_get_test_flags() ^ TEST_FAIL_NEXT_DECRYPT); - } - } else { - char *flags = sqlite3_mprintf("%d", sqlcipher_get_test_flags()); - codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); + if(sqlite3StrICmp(zRight, "fail_decrypt")==0) { + flags |= TEST_FAIL_DECRYPT; + } else + if(sqlite3StrICmp(zRight, "fail_migrate")==0) { + flags |= TEST_FAIL_MIGRATE; + } + sqlcipher_set_test_flags(flags); + } + } else + if( sqlite3StrICmp(zLeft,"cipher_test_off")==0 ){ + if( zRight ) { + unsigned int flags = sqlcipher_get_test_flags(); + if(sqlite3StrICmp(zRight, "fail_encrypt")==0) { + flags &= ~TEST_FAIL_ENCRYPT; + } else + if(sqlite3StrICmp(zRight, "fail_decrypt")==0) { + flags &= ~TEST_FAIL_DECRYPT; + } else + if(sqlite3StrICmp(zRight, "fail_migrate")==0) { + flags &= ~TEST_FAIL_MIGRATE; + } + sqlcipher_set_test_flags(flags); } + } else + if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ + char *flags = sqlite3_mprintf("%i", sqlcipher_get_test_flags()); + codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); }else #endif if( sqlite3StrICmp(zLeft, "cipher_fips_status")== 0 && !zRight ){ @@ -157,8 +178,12 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else if( sqlite3StrICmp(zLeft, "cipher_migrate")==0 && !zRight ){ if(ctx){ - char *migrate_status = sqlite3_mprintf("%d", sqlcipher_codec_ctx_migrate(ctx)); + int status = sqlcipher_codec_ctx_migrate(ctx); + char *migrate_status = sqlite3_mprintf("%d", status); codec_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); + if(status != SQLITE_OK) { + sqlcipher_codec_ctx_set_error(ctx, status); + } } } else if( sqlite3StrICmp(zLeft, "cipher_provider")==0 && !zRight ){ @@ -709,7 +734,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_NEXT_ENCRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); @@ -734,7 +759,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { } rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_NEXT_DECRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); diff --git a/src/crypto.h b/src/crypto.h index f0aee2db9..3aaf23878 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -193,10 +193,11 @@ static int cipher_isHex(const unsigned char *hex, int sz){ /* possible flags for simulating specific test conditions */ #ifdef SQLCIPHER_TEST -#define TEST_FAIL_NEXT_ENCRYPT (1ul << 0) /* 1 */ -#define TEST_FAIL_NEXT_DECRYPT (1ul << 1) /* 2 */ -int sqlcipher_get_test_flags(void); -void sqlcipher_set_test_flags(int); +#define TEST_FAIL_ENCRYPT 0x01 +#define TEST_FAIL_DECRYPT 0x02 +#define TEST_FAIL_MIGRATE 0x04 +unsigned int sqlcipher_get_test_flags(void); +void sqlcipher_set_test_flags(unsigned int); #endif /* extensions defined in crypto_impl.c */ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 78fe69b31..d1f89b6c5 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -45,7 +45,13 @@ #endif #ifdef SQLCIPHER_TEST -static volatile int cipher_test_flags = 0; +static volatile unsigned int cipher_test_flags = 0; +unsigned int sqlcipher_get_test_flags() { + return cipher_test_flags; +} +void sqlcipher_set_test_flags(unsigned int flags) { + cipher_test_flags = flags; +} #endif /* Generate code to return a string value */ @@ -402,16 +408,6 @@ char* sqlcipher_version() { return version; } -#ifdef SQLCIPHER_TEST -int sqlcipher_get_test_flags() { - return cipher_test_flags; -} - -void sqlcipher_set_test_flags(int flags) { - cipher_test_flags = flags; -} -#endif - /** * Initialize new cipher_ctx struct. This function will allocate memory * for the cipher context and for the key @@ -1380,7 +1376,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { pass = sqlcipher_malloc(pass_sz+1); memset(pass, 0, pass_sz+1); memcpy(pass, ctx->read_ctx->pass, pass_sz); - + /* Version 4 - current, no upgrade required, so exit immediately */ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version, &journal_mode); if(rc == SQLITE_OK){ @@ -1398,6 +1394,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { if(pragma_compat) sqlcipher_free(pragma_compat, sqlite3Strlen30(pragma_compat)); pragma_compat = NULL; } + /* if we exit the loop normally we failed to determine the version, this is an error */ CODEC_TRACE("Upgrade format not determined\n"); goto handle_error; @@ -1445,6 +1442,14 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { goto handle_error; } +#ifdef SQLCIPHER_TEST + if((sqlcipher_get_test_flags() & TEST_FAIL_MIGRATE) > 0) { + rc = SQLITE_ERROR; + CODEC_TRACE("simulated migrate failure, error code %d\n", rc); + goto handle_error; + } +#endif + rc = sqlite3_exec(db, set_user_version, NULL, NULL, NULL); if(rc != SQLITE_OK){ CODEC_TRACE("set user version failed, error code %d\n", rc); @@ -1537,7 +1542,6 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { handle_error: CODEC_TRACE("An error occurred attempting to migrate the database - last error %d\n", rc); - rc = SQLITE_ERROR; cleanup: if(pass) sqlcipher_free(pass, pass_sz); diff --git a/test/sqlcipher-codecerror.test b/test/sqlcipher-codecerror.test index 927e8d198..12cafbd79 100644 --- a/test/sqlcipher-codecerror.test +++ b/test/sqlcipher-codecerror.test @@ -66,7 +66,7 @@ do_test codec-error-journal-delete { catchsql { PRAGMA key = 'testkey'; - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_on = fail_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -74,7 +74,7 @@ do_test codec-error-journal-delete { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_off = fail_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -92,7 +92,7 @@ do_test codec-error-journal-wal { catchsql { PRAGMA key = 'testkey'; - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_on = fail_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -100,7 +100,7 @@ do_test codec-error-journal-wal { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_off = fail_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -120,7 +120,7 @@ do_test codec-error-journal-wal-transaction { PRAGMA key = 'testkey'; BEGIN; UPDATE t1 SET b = 'success' WHERE a = 1; - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_on = fail_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; COMMIT; } @@ -129,7 +129,7 @@ do_test codec-error-journal-wal-transaction { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_off = fail_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -149,7 +149,7 @@ do_test codec-error-journal-wal-read { catchsql { PRAGMA key = 'testkey'; SELECT count(*) FROM sqlite_schema; - PRAGMA cipher_test = fail_next_decrypt; + PRAGMA cipher_test_on = fail_decrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -157,7 +157,7 @@ do_test codec-error-journal-wal-read { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_decrypt; + PRAGMA cipher_test_off = fail_decrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; diff --git a/test/sqlcipher-compatibility.test b/test/sqlcipher-compatibility.test index 416062d58..965ef0a0f 100644 --- a/test/sqlcipher-compatibility.test +++ b/test/sqlcipher-compatibility.test @@ -360,7 +360,8 @@ file delete -force test.db # open a 1.1.8 database without hmac, then copy the data do_test attach-and-copy-1.1.8 { - sqlite_orig db $sampleDir/sqlcipher-1.1.8-testkey.db + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db test.db + sqlite_orig db test.db execsql { PRAGMA key = 'testkey'; @@ -368,14 +369,14 @@ do_test attach-and-copy-1.1.8 { PRAGMA kdf_iter = 4000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1; - ATTACH DATABASE 'test.db' AS db2 KEY 'testkey-hmac'; + ATTACH DATABASE 'test-new.db' AS db2 KEY 'testkey-hmac'; CREATE TABLE db2.t1(a,b); INSERT INTO db2.t1 SELECT * FROM main.t1; DETACH DATABASE db2; } db close - sqlite_orig db test.db + sqlite_orig db test-new.db execsql { PRAGMA key = 'testkey-hmac'; SELECT count(*) FROM t1; @@ -384,7 +385,7 @@ do_test attach-and-copy-1.1.8 { } {ok 75709 1 1 one one 1 2 one two 1 2} db close file delete -force test.db - +file delete -force test-new.db # open a standard database, then attach a new # database with completely different options. @@ -681,7 +682,7 @@ file delete -force test-vacuum.db # setting as the original do_test default-hmac-kdf-attach { file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db test.db - file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db; + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db sqlite_orig db test.db execsql { PRAGMA cipher_default_use_hmac = OFF; @@ -707,7 +708,7 @@ file delete -force sqlcipher-1.1.8-testkey.db # fail because the hmac setting for the # attached database is not compatible do_test attach-1.1.8-database-from-2.0-fails { - file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db; + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db sqlite_orig db test.db catchsql { PRAGMA key = 'testkey'; @@ -725,7 +726,7 @@ file delete -force sqlcipher-1.1.8-testkey.db # succeed now that hmac is off by default # before the attach do_test change-default-hmac-kdf-attach { - file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db; + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db sqlite_orig db test.db execsql { PRAGMA key = 'testkey'; @@ -1087,19 +1088,22 @@ file delete -force test.db test.db-migrated test.db-journal do_test migrate-3-0-database-to-current-format { file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db sqlite_orig db test.db - execsql { + set rc {} + + lappend rc [execsql { PRAGMA key = 'testkey'; PRAGMA cipher_migrate; - } + SELECT count(*) FROM sqlite_schema; + }] db close sqlite_orig db test.db - execsql { + lappend rc [execsql { PRAGMA key = 'testkey'; SELECT count(*) FROM sqlite_schema; PRAGMA journal_mode; - } -} {ok 1 delete} + }] +} {{ok 0 1} {ok 1 delete}} db close file delete -force test.db @@ -1133,6 +1137,58 @@ do_test migrate-wal-database-to-current { db close file delete -force test.db +# test original database is left untouched after +# a failed migration e.g. due to low disk space +do_test migrate-failure { + file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db + sqlite_orig db test.db + + set rc {} + + lappend rc [execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_migrate; + }] + db close + + sqlite_orig db test.db + lappend rc [execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_compatibility = 3; + SELECT count(*) FROM sqlite_schema; + }] +} {{ok 1} {ok 1}} +db close +file delete -force test.db test.db-migrated + +# leave database is a readable state after a +# a failed migration +do_test migrate-failure-readable { + file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db + sqlite_orig db test.db + + set rc {} + lappend rc [execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_migrate; + }] + + lappend rc [catchsql { + SELECT count(*) FROM sqlite_schema; + }] + db close + + sqlite_orig db test.db + lappend rc [execsql { + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_test; + }] +} {{ok 1} {1 {SQL logic error}} 0} +db close +file delete -force test.db test.db-migrated do_test key-database-by-name { sqlite_orig db test.db @@ -1259,6 +1315,7 @@ do_test can-migrate-with-raw-hex-key { PRAGMA key = "x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'"; PRAGMA cipher_migrate; } + db close sqlite_orig db test.db execsql { diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index 185a0ff8b..72d452827 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -801,4 +801,54 @@ db close dba close file delete -force test.db +do_test test_flags_fail_encrypt { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_encrypt; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_encrypt; + PRAGMA cipher_test; + } +} {0 1 0} +db close + +do_test test_flags_fail_decrypt { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_decrypt; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_decrypt; + PRAGMA cipher_test; + } +} {0 2 0} +db close + +do_test test_flags_fail_migrate { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_test; + } +} {0 4 0} +db close + +do_test test_flags_combo { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_encrypt; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_encrypt; + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_test; + } +} {0 5 0} +db close + finish_test From b36c3dfa558c9a15bc813c280c8e018895713233 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 8 Oct 2021 17:16:33 -0400 Subject: [PATCH 015/340] cleanup temporary migration file on cipher_migrate error --- src/crypto_impl.c | 9 +++++---- test/sqlcipher-compatibility.test | 12 +++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index d1f89b6c5..e4a4393fa 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1526,10 +1526,6 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { CODEC_TRACE("DETACH DATABASE called %d\n", rc); if(rc != SQLITE_OK) goto cleanup; - rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0); - CODEC_TRACE("deleted migration database: %d\n", rc); - if( rc!=SQLITE_OK ) goto handle_error; - sqlite3ResetAllSchemasOfConnection(db); CODEC_TRACE("reset all schemas\n"); @@ -1544,6 +1540,11 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { CODEC_TRACE("An error occurred attempting to migrate the database - last error %d\n", rc); cleanup: + if(migrated_db_filename) { + int del_rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0); + CODEC_TRACE("deleted migration database: %d\n", del_rc); + } + if(pass) sqlcipher_free(pass, pass_sz); if(attach_command) sqlcipher_free(attach_command, sqlite3Strlen30(attach_command)); if(migrated_db_filename) sqlcipher_free(migrated_db_filename, sqlite3Strlen30(migrated_db_filename)); diff --git a/test/sqlcipher-compatibility.test b/test/sqlcipher-compatibility.test index 965ef0a0f..db7241df0 100644 --- a/test/sqlcipher-compatibility.test +++ b/test/sqlcipher-compatibility.test @@ -1065,7 +1065,7 @@ do_test migrate-1.1.8-database-to-current-format { } } {ok 1} db close -file delete -force test.db test.db-migrated test.db-journal +file delete -force test.db do_test migrate-2-0-le-database-to-current-format { file copy -force $sampleDir/sqlcipher-2.0-le-testkey.db test.db @@ -1083,7 +1083,7 @@ do_test migrate-2-0-le-database-to-current-format { } } {ok 1} db close -file delete -force test.db test.db-migrated test.db-journal +file delete -force test.db do_test migrate-3-0-database-to-current-format { file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db @@ -1151,6 +1151,8 @@ do_test migrate-failure { PRAGMA cipher_migrate; }] db close + + lappend rc [file exists test.db-migrated] sqlite_orig db test.db lappend rc [execsql { @@ -1159,9 +1161,9 @@ do_test migrate-failure { PRAGMA cipher_compatibility = 3; SELECT count(*) FROM sqlite_schema; }] -} {{ok 1} {ok 1}} +} {{ok 1} 0 {ok 1}} db close -file delete -force test.db test.db-migrated +file delete -force test.db # leave database is a readable state after a # a failed migration @@ -1188,7 +1190,7 @@ do_test migrate-failure-readable { }] } {{ok 1} {1 {SQL logic error}} 0} db close -file delete -force test.db test.db-migrated +file delete -force test.db do_test key-database-by-name { sqlite_orig db test.db From d018a5f2e63052b9623aabab8380e42157c260c1 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 11 Oct 2021 11:12:33 -0400 Subject: [PATCH 016/340] url filename key tests --- test/sqlcipher-core.test | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index 72d452827..da28d56b1 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -851,4 +851,52 @@ do_test test_flags_combo { } {0 5 0} db close +# configure URI filename support +# create a new encrypted database with the key via parameter +# close database +# open normally providing key via pragma verify +# correct key works +sqlite3_shutdown +sqlite3_config_uri 1 +do_test uri-key { + sqlite_orig db file:test.db?a=a&key=testkey&c=c + + execsql { + CREATE TABLE t1(a,b); + INSERT INTO t1 VALUES(1,2); + } + + db close + sqlite_orig db test.db + + catchsql { + PRAGMA key = 'testkey'; + SELECT count(*) FROM t1; + } + + db close + sqlite_orig db test.db + + execsql { + PRAGMA key = 'testkey'; + SELECT count(*) FROM t1; + } + +} {ok 1} +db close + +# verify wrong key fails +do_test uri-key-2 { + sqlite_orig db test.db + catchsql { + PRAGMA key = 'test'; + SELECT count(*) FROM t1; + } +} {1 {file is not a database}} +db close +file delete -force test.db +sqlite3_shutdown +sqlite3_config_uri 0 + finish_test + From 50ed23fe6aa537a7a7f7413b560ec5115c3b8f8b Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 18 Oct 2021 14:19:18 -0400 Subject: [PATCH 017/340] update tag used for SQLCipher pod --- SQLCipher.podspec.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 6ec97fc32..f1c018edc 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.4.3" + "tag": "v4.5.0" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.4.3", + "version": "4.5.0", "subspecs": [ { "compiler_flags": [ From f7e9e9052397a0313dde44175a7840e36c9abf56 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 18 Oct 2021 15:01:59 -0400 Subject: [PATCH 018/340] update CHANGELOG for 4.5.0 release --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb755310e..37db27bb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. +## [4.5.0] - (October 2021 - [4.5.0 changes]) +- Updates baseline to upstream SQLite 3.36.0 +- Changes the enhanced memory security feature to be DISABLED by default; once enabled by PRAGMA cipher_memory_security = ON, it can't be turned off for the lifetime of the process +- Changes PRAGMA cipher_migrate to permanently enter an error state if a migration fails +- Fixes memory locking/unlocking issue with realloc implementation on hardened runtimes when memory security is enabled +- Fixes cipher_migrate to cleanup the temporary database if a migration fails +- Removes logging of non-string pointers when compiling with trace level logging + ## [4.4.3] - (February 2021 - [4.4.3 changes]) - Updates baseline to ustream SQLite 3.34.1 - Fixes sqlcipher_export handling of NULL parameters @@ -184,7 +192,9 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 -[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.4.3...prerelease +[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.0...prerelease +[4.5.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.0 +[4.5.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.4.3...v4.5.0 [4.4.3]: https://github.com/sqlcipher/sqlcipher/tree/v4.4.3 [4.4.3 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.4.2...v4.4.3 [4.4.2]: https://github.com/sqlcipher/sqlcipher/tree/v4.4.2 From e4406223d0057640623f39d789c3dd9ec4cef979 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 1 Nov 2021 11:17:35 -0400 Subject: [PATCH 019/340] allow the pager to remain usable following an error occuring on a read operation --- src/crypto.c | 6 ++- test/sqlcipher-integrity.test | 97 +++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 45cbdc898..a0bc3d2a2 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -736,9 +736,9 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0) rc = SQLITE_ERROR; #endif - if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ + if(rc != SQLITE_OK) { /* clear results of failed cipher operation */ + /* this will be considered a temporary error condition. the pager is still usable */ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); - sqlcipher_codec_ctx_set_error(ctx, rc); } memcpy(pData, buffer, page_sz); /* copy buffer data back to pData and return */ return pData; @@ -762,6 +762,8 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ + /* failure to encrypt a page is considered a permanent error and will render the pager unusable + in order to prevent corrupted pages from being written to the main databased when using WAL */ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; diff --git a/test/sqlcipher-integrity.test b/test/sqlcipher-integrity.test index 639f9495d..61196c961 100644 --- a/test/sqlcipher-integrity.test +++ b/test/sqlcipher-integrity.test @@ -38,6 +38,8 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/sqlcipher.tcl +set old_pending_byte [sqlite3_test_control_pending_byte 0x40000000] + # 1. create a database and insert a bunch of data, close the database # 2. seek to the middle of the first database page and write some junk # 3. Open the database and verify that the database is no longer readable @@ -341,4 +343,99 @@ do_test integrity-check-plaintext-header { } {{} 1 {{HMAC verification failed for page 1} {HMAC verification failed for page 2}}} file delete -force test.db +# verify that the default page size for the +# tests is 1024 (via makefile for testfixure. If +# the default pagesize is different it will breat +# the following tests +do_test default-page-size { + sqlite_orig db test.db + execsql { + CREATE TABLE t1(a,b); + PRAGMA page_size; + } +} {1024} + +# zero out the 65th page, verify the behavior of +# pragma integrity check on a plaintext database +do_test integrity-check-one-page-plaintext { + sqlite_orig db $sampleDir/sqlcipher-4.0-testkey.db + set rc {} + + execsql { + PRAGMA key = 'testkey'; + ATTACH DATABASE 'plain-corrupt.db' AS plain KEY ''; + SELECT sqlcipher_export('plain'); + DETACH DATABASE plain; + } + + db close + + set txt "" + for {set i 0} {$i < 2048} {incr i} { + append txt "0" + } + + hexio_write plain-corrupt.db 65536 $txt + + sqlite_orig db plain-corrupt.db + + set DB [sqlite3_connection_pointer db] + set VM [sqlite3_prepare $DB {PRAGMA integrity_check;} -1 TAIL] + while {[sqlite3_step $VM] == "SQLITE_ROW"} { + lappend rc [sqlite3_column_text $VM 0] + } + sqlite3_finalize $VM + + lappend rc [catchsql { + PRAGMA integrity_check; + }] +} {{*** in database main *** +Page 65: btreeInitPage() returns error code 11} {1 {database disk image is malformed}}} +db close +file delete -force plain-corrupt.db + +# introduce a corruption in the 17th page then verify that +# both integrity checks only reports one corrupt page +# (not that all subsequent pages corrupt). Behavior of +# PRAGMA integrity check should be consistent with the plaintext database +# save that the page number will be different because page size for +# sqlcipher is 4096 under the testfixture instead of 1024 +do_test version-4-integrity-check-one-page { + file copy -force $sampleDir/sqlcipher-4.0-testkey.db test.db + + set txt "" + for {set i 0} {$i < 8192} {incr i} { + append txt "0" + } + + hexio_write test.db 65536 $txt + + sqlite_orig db test.db + set rc {} + lappend rc [ + execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_integrity_check; + } + ] + + set DB [sqlite3_connection_pointer db] + set VM [sqlite3_prepare $DB {PRAGMA integrity_check;} -1 TAIL] + while {[sqlite3_step $VM] == "SQLITE_ROW"} { + lappend rc [sqlite3_column_text $VM 0] + } + sqlite3_finalize $VM + + lappend rc [ + catchsql { + PRAGMA integrity_check; + } + ] +} {{ok {HMAC verification failed for page 17}} {*** in database main *** +Page 17: btreeInitPage() returns error code 11} {1 {database disk image is malformed}}} +db close +file delete -force test.db + +sqlite3_test_control_pending_byte $old_pending_byte + finish_test From 337f85d8fedd7837f0170ac98120d7500dfbd957 Mon Sep 17 00:00:00 2001 From: Fedor Indutnyy Date: Mon, 8 Nov 2021 21:31:05 +0100 Subject: [PATCH 020/340] Make sure munlock is called for ctx->buffer --- src/crypto_impl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index e4a4393fa..ee7e0f5d8 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -980,7 +980,7 @@ void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { CODEC_TRACE("codec_ctx_free: entered iCtx=%p\n", iCtx); sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz); sqlcipher_free(ctx->hmac_kdf_salt, ctx->kdf_salt_sz); - sqlcipher_free(ctx->buffer, 0); + sqlcipher_free(ctx->buffer, ctx->page_sz); ctx->provider->ctx_free(&ctx->provider_ctx); sqlcipher_free(ctx->provider, sizeof(sqlcipher_provider)); From e705e0a24ea26c6e8f92583d1d885c186f1cfded Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 15 Nov 2021 17:12:44 -0500 Subject: [PATCH 021/340] add facility for testing random codec failures --- src/crypto.c | 19 +++++++++++++++++-- src/crypto.h | 3 +++ src/crypto_impl.c | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index a0bc3d2a2..fef032b4e 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -145,6 +145,15 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef char *flags = sqlite3_mprintf("%i", sqlcipher_get_test_flags()); codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); }else + if( sqlite3StrICmp(zLeft,"cipher_test_rand")==0 ){ + if( zRight ) { + int rand = atoi(zRight); + sqlcipher_set_test_rand(rand); + } else { + char *rand = sqlite3_mprintf("%d", sqlcipher_get_test_rand()); + codec_vdbe_return_string(pParse, "cipher_test_rand", rand, P4_DYNAMIC); + } + } else #endif if( sqlite3StrICmp(zLeft, "cipher_fips_status")== 0 && !zRight ){ if(ctx) { @@ -734,7 +743,10 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { + rc = SQLITE_ERROR; + fprintf(stderr, "simulating encryption failure\n"); + } #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation */ /* this will be considered a temporary error condition. the pager is still usable */ @@ -759,7 +771,10 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { } rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { + fprintf(stderr, "simulating decryption failure\n"); + rc = SQLITE_ERROR; + } #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ /* failure to encrypt a page is considered a permanent error and will render the pager unusable diff --git a/src/crypto.h b/src/crypto.h index 3aaf23878..e4626a414 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -198,6 +198,9 @@ static int cipher_isHex(const unsigned char *hex, int sz){ #define TEST_FAIL_MIGRATE 0x04 unsigned int sqlcipher_get_test_flags(void); void sqlcipher_set_test_flags(unsigned int); +int sqlcipher_get_test_rand(void); +void sqlcipher_set_test_rand(int); +int sqlcipher_get_test_fail(void); #endif /* extensions defined in crypto_impl.c */ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index ee7e0f5d8..57b2b8465 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -52,6 +52,23 @@ unsigned int sqlcipher_get_test_flags() { void sqlcipher_set_test_flags(unsigned int flags) { cipher_test_flags = flags; } + +static volatile int cipher_test_rand = 0; +int sqlcipher_get_test_rand() { + return cipher_test_rand; +} +void sqlcipher_set_test_rand(int rand) { + cipher_test_rand = rand; +} +int sqlcipher_get_test_fail() { + int x; + + /* if cipher_test_rand is not set to a non-zero value always fail (return true) */ + if (cipher_test_rand == 0) return 1; + + sqlite3_randomness(sizeof(x), &x); + return ((x % cipher_test_rand) == 0); +} #endif /* Generate code to return a string value */ From 437bb47e9f64ae53f404eca1fbc62cfef92317e0 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 22 Nov 2021 17:15:53 -0500 Subject: [PATCH 022/340] fix encrypt / decrypt failure test flag checks --- src/crypto.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index fef032b4e..22acb2cc7 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -743,9 +743,8 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { + if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; - fprintf(stderr, "simulating encryption failure\n"); } #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation */ @@ -771,8 +770,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { } rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { - fprintf(stderr, "simulating decryption failure\n"); + if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; } #endif From 1876fe670e676b673d5fd7be667d5af88c2729d0 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 22 Nov 2021 17:17:32 -0500 Subject: [PATCH 023/340] Revert "allow the pager to remain usable following an error occuring on a read operation" This reverts commit e4406223d0057640623f39d789c3dd9ec4cef979. --- src/crypto.c | 6 +-- test/sqlcipher-integrity.test | 97 ----------------------------------- 2 files changed, 2 insertions(+), 101 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 22acb2cc7..2f951b7fd 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -747,9 +747,9 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = SQLITE_ERROR; } #endif - if(rc != SQLITE_OK) { /* clear results of failed cipher operation */ - /* this will be considered a temporary error condition. the pager is still usable */ + if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); + sqlcipher_codec_ctx_set_error(ctx, rc); } memcpy(pData, buffer, page_sz); /* copy buffer data back to pData and return */ return pData; @@ -775,8 +775,6 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { } #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ - /* failure to encrypt a page is considered a permanent error and will render the pager unusable - in order to prevent corrupted pages from being written to the main databased when using WAL */ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; diff --git a/test/sqlcipher-integrity.test b/test/sqlcipher-integrity.test index 61196c961..639f9495d 100644 --- a/test/sqlcipher-integrity.test +++ b/test/sqlcipher-integrity.test @@ -38,8 +38,6 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/sqlcipher.tcl -set old_pending_byte [sqlite3_test_control_pending_byte 0x40000000] - # 1. create a database and insert a bunch of data, close the database # 2. seek to the middle of the first database page and write some junk # 3. Open the database and verify that the database is no longer readable @@ -343,99 +341,4 @@ do_test integrity-check-plaintext-header { } {{} 1 {{HMAC verification failed for page 1} {HMAC verification failed for page 2}}} file delete -force test.db -# verify that the default page size for the -# tests is 1024 (via makefile for testfixure. If -# the default pagesize is different it will breat -# the following tests -do_test default-page-size { - sqlite_orig db test.db - execsql { - CREATE TABLE t1(a,b); - PRAGMA page_size; - } -} {1024} - -# zero out the 65th page, verify the behavior of -# pragma integrity check on a plaintext database -do_test integrity-check-one-page-plaintext { - sqlite_orig db $sampleDir/sqlcipher-4.0-testkey.db - set rc {} - - execsql { - PRAGMA key = 'testkey'; - ATTACH DATABASE 'plain-corrupt.db' AS plain KEY ''; - SELECT sqlcipher_export('plain'); - DETACH DATABASE plain; - } - - db close - - set txt "" - for {set i 0} {$i < 2048} {incr i} { - append txt "0" - } - - hexio_write plain-corrupt.db 65536 $txt - - sqlite_orig db plain-corrupt.db - - set DB [sqlite3_connection_pointer db] - set VM [sqlite3_prepare $DB {PRAGMA integrity_check;} -1 TAIL] - while {[sqlite3_step $VM] == "SQLITE_ROW"} { - lappend rc [sqlite3_column_text $VM 0] - } - sqlite3_finalize $VM - - lappend rc [catchsql { - PRAGMA integrity_check; - }] -} {{*** in database main *** -Page 65: btreeInitPage() returns error code 11} {1 {database disk image is malformed}}} -db close -file delete -force plain-corrupt.db - -# introduce a corruption in the 17th page then verify that -# both integrity checks only reports one corrupt page -# (not that all subsequent pages corrupt). Behavior of -# PRAGMA integrity check should be consistent with the plaintext database -# save that the page number will be different because page size for -# sqlcipher is 4096 under the testfixture instead of 1024 -do_test version-4-integrity-check-one-page { - file copy -force $sampleDir/sqlcipher-4.0-testkey.db test.db - - set txt "" - for {set i 0} {$i < 8192} {incr i} { - append txt "0" - } - - hexio_write test.db 65536 $txt - - sqlite_orig db test.db - set rc {} - lappend rc [ - execsql { - PRAGMA key = 'testkey'; - PRAGMA cipher_integrity_check; - } - ] - - set DB [sqlite3_connection_pointer db] - set VM [sqlite3_prepare $DB {PRAGMA integrity_check;} -1 TAIL] - while {[sqlite3_step $VM] == "SQLITE_ROW"} { - lappend rc [sqlite3_column_text $VM 0] - } - sqlite3_finalize $VM - - lappend rc [ - catchsql { - PRAGMA integrity_check; - } - ] -} {{ok {HMAC verification failed for page 17}} {*** in database main *** -Page 17: btreeInitPage() returns error code 11} {1 {database disk image is malformed}}} -db close -file delete -force test.db - -sqlite3_test_control_pending_byte $old_pending_byte - finish_test From 2ce01668d6e1aa16a1fc4cbb05851c8bcb59e865 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 22 Nov 2021 17:21:08 -0500 Subject: [PATCH 024/340] clarifying comments about codec error handling --- src/crypto.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 2f951b7fd..683fb89ac 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -747,7 +747,9 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = SQLITE_ERROR; } #endif - if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ + if(rc != SQLITE_OK) { + /* failure to decrypt a page is considered a permanent error and will render the pager unusable + in order to prevent inconsistent data being loaded into page cache */ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); } @@ -774,7 +776,9 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = SQLITE_ERROR; } #endif - if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ + if(rc != SQLITE_OK) { + /* failure to encrypt a page is considered a permanent error and will render the pager unusable + in order to prevent corrupted pages from being written to the main databased when using WAL */ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; From eecfc0e47f0741689e3f837365d706b448ad761f Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 22 Nov 2021 17:23:23 -0500 Subject: [PATCH 025/340] log message indicating simulated failure in codec --- src/crypto.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 683fb89ac..bebe3242b 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -745,6 +745,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; + fprintf(stderr, "simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { @@ -774,6 +775,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; + fprintf(stderr, "simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { From fbb0f4358dec3a73c57032010212bc6782528fc6 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 10 Dec 2021 12:33:38 -0500 Subject: [PATCH 026/340] fix cipher_integrity_check on databases > 2gb --- src/crypto_impl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 57b2b8465..801a6b94b 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1329,7 +1329,7 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *col hmac_out = sqlcipher_malloc(ctx->hmac_sz); for(page = 1; page <= file_sz / ctx->page_sz; page++) { - int offset = (page - 1) * ctx->page_sz; + i64 offset = (page - 1) * ctx->page_sz; int payload_sz = ctx->page_sz - ctx->reserve_sz + ctx->iv_sz; int read_sz = ctx->page_sz; From 39a36688cf7f7c95f73e9ae6ea4c501faecc8f2b Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 14 Dec 2021 14:54:47 -0500 Subject: [PATCH 027/340] replace sqlite3_profile with sqlite3_trace_v2 --- src/crypto_impl.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 801a6b94b..25726dff4 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1598,16 +1598,17 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz return SQLITE_ERROR; } -#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_DEPRECATED) -static void sqlcipher_profile_callback(void *file, const char *sql, sqlite3_uint64 run_time){ - FILE *f = (FILE*)file; - double elapsed = run_time/1000000.0; - if(f) fprintf(f, "Elapsed time:%.3f ms - %s\n", elapsed, sql); +#if !defined(SQLITE_OMIT_TRACE) +static int sqlcipher_profile_callback(unsigned int trace, void *file, void *stmt, void *run_time){ + FILE *f = (FILE*) file; + double elapsed = (*((sqlite3_uint64*)run_time))/1000000.0; + if(f) fprintf(f, "Elapsed time:%.3f ms - %s\n", elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); + return SQLITE_OK; } #endif int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ -#if defined(SQLITE_OMIT_TRACE) || defined(SQLITE_OMIT_DEPRECATED) +#if defined(SQLITE_OMIT_TRACE) return SQLITE_ERROR; #else FILE *f; @@ -1624,7 +1625,7 @@ int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ if((f = fopen(destination, "a")) == 0) return SQLITE_ERROR; #endif } - sqlite3_profile(db, sqlcipher_profile_callback, f); + sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, sqlcipher_profile_callback, f); return SQLITE_OK; #endif } From ce6381b2195335db846110ab0facc0dcba39b848 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 15 Dec 2021 11:50:59 -0500 Subject: [PATCH 028/340] cipher_trace pragma to allow logging of sqlcipher trace information --- src/crypto.c | 4 ++ src/crypto.h | 93 +++++++++++++++++++++++++---------------------- src/crypto_impl.c | 48 ++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 43 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index bebe3242b..03ecb430f 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -680,6 +680,10 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef if(ctx) { sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check"); } + } else + if( sqlite3StrICmp(zLeft, "cipher_trace")== 0 && zRight ){ + char *profile_status = sqlite3_mprintf("%d", sqlcipher_set_trace(zRight)); + codec_vdbe_return_string(pParse, "cipher_trace", profile_status, P4_DYNAMIC); }else { return 0; } diff --git a/src/crypto.h b/src/crypto.h index e4626a414..ed6e3a1d8 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -39,12 +39,17 @@ #include "btreeInt.h" #include "pager.h" +#ifdef __ANDROID__ +#include +#endif + /* extensions defined in pager.c */ void *sqlite3PagerGetCodec(Pager*); void sqlite3PagerSetCodec(Pager*, void *(*)(void*,void*,Pgno,int), void (*)(void*,int,int), void (*)(void*), void *); int sqlite3pager_is_mj_pgno(Pager*, Pgno); void sqlite3pager_error(Pager*, int); void sqlite3pager_reset(Pager *pPager); +/* end extensions defined in pager.c */ #if !defined (SQLCIPHER_CRYPTO_CC) \ && !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \ @@ -111,49 +116,6 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_MAX_KEY_SZ 64 #endif -#ifdef __ANDROID__ -#include -#endif - -#ifdef CODEC_DEBUG -#ifdef __ANDROID__ -#define CODEC_TRACE(...) {__android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", __VA_ARGS__);} -#else -#define CODEC_TRACE(...) {fprintf(stderr, __VA_ARGS__);fflush(stderr);} -#endif -#else -#define CODEC_TRACE(...) -#endif - -#ifdef CODEC_DEBUG_MUTEX -#define CODEC_TRACE_MUTEX(...) CODEC_TRACE(__VA_ARGS__) -#else -#define CODEC_TRACE_MUTEX(...) -#endif - -#ifdef CODEC_DEBUG_MEMORY -#define CODEC_TRACE_MEMORY(...) CODEC_TRACE(__VA_ARGS__) -#else -#define CODEC_TRACE_MEMORY(...) -#endif - -#ifdef CODEC_DEBUG_PAGEDATA -#define CODEC_HEXDUMP(DESC,BUFFER,LEN) \ - { \ - int __pctr; \ - printf(DESC); \ - for(__pctr=0; __pctr < LEN; __pctr++) { \ - if(__pctr % 16 == 0) printf("\n%05x: ",__pctr); \ - printf("%02x ",((unsigned char*) BUFFER)[__pctr]); \ - } \ - printf("\n"); \ - fflush(stdout); \ - } -#else -#define CODEC_HEXDUMP(DESC,BUFFER,LEN) -#endif - -/* end extensions defined in pager.c */ /* ** Simple shared routines for converting hex char strings to binary data @@ -341,6 +303,51 @@ int sqlcipher_find_db_index(sqlite3 *db, const char *zDb); int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); +int sqlcipher_set_trace(const char *destination); +void sqlcipher_trace(const char *message, ...); + +#ifdef CODEC_DEBUG +#ifdef __ANDROID__ +#define CODEC_TRACE(...) {__android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", __VA_ARGS__);} +#else +#define CODEC_TRACE(...) {fprintf(stderr, __VA_ARGS__);fflush(stderr);} +#endif +#else +#ifdef SQLCIPHER_OMIT_TRACE +#define CODEC_TRACE(...) +#else +#define CODEC_TRACE(...) {sqlcipher_trace(__VA_ARGS__);}; +#endif +#endif + +#ifdef CODEC_DEBUG_MUTEX +#define CODEC_TRACE_MUTEX(...) CODEC_TRACE(__VA_ARGS__) +#else +#define CODEC_TRACE_MUTEX(...) +#endif + +#ifdef CODEC_DEBUG_MEMORY +#define CODEC_TRACE_MEMORY(...) CODEC_TRACE(__VA_ARGS__) +#else +#define CODEC_TRACE_MEMORY(...) +#endif + +#ifdef CODEC_DEBUG_PAGEDATA +#define CODEC_HEXDUMP(DESC,BUFFER,LEN) \ + { \ + int __pctr; \ + printf(DESC); \ + for(__pctr=0; __pctr < LEN; __pctr++) { \ + if(__pctr % 16 == 0) printf("\n%05x: ",__pctr); \ + printf("%02x ",((unsigned char*) BUFFER)[__pctr]); \ + } \ + printf("\n"); \ + fflush(stdout); \ + } +#else +#define CODEC_HEXDUMP(DESC,BUFFER,LEN) +#endif + #endif #endif /* END SQLCIPHER */ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 25726dff4..195e4b207 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -88,6 +88,8 @@ static volatile sqlite3_mem_methods default_mem_methods; static sqlcipher_provider *default_provider = NULL; static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; +static volatile FILE* sqlcipher_trace_file = NULL; +static volatile int sqlcipher_trace_logcat = 0; sqlite3_mutex* sqlcipher_mutex(int mutex) { if(mutex < 0 || mutex >= SQLCIPHER_MUTEX_COUNT) return NULL; @@ -1638,5 +1640,51 @@ const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) { return ctx->provider->get_provider_version(ctx->provider_ctx); } +#ifndef SQLCIPHER_OMIT_TRACE +void sqlcipher_trace(const char *message, ...) { + va_list params; + va_start(params, message); + if(sqlcipher_trace_file != NULL){ + vfprintf((FILE*)sqlcipher_trace_file, message, params); + } +#ifdef __ANDROID__ + if(sqlcipher_trace_logcat) { + __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); + } +#endif + va_end(params); +} +#endif + +int sqlcipher_set_trace(const char *destination){ +#ifdef SQLCIPHER_OMIT_TRACE + return SQLITE_ERROR; +#else + /* close open trace file if it is not stdout or stderr, then + reset trace settings */ + if(sqlcipher_trace_file != NULL && sqlcipher_trace_file != stdout && sqlcipher_trace_file != stderr) { + fclose((FILE*)sqlcipher_trace_file); + } + sqlcipher_trace_file = NULL; + sqlcipher_trace_logcat = 0; + + if(sqlite3StrICmp(destination, "logcat") == 0){ + sqlcipher_trace_logcat = 1; + } else if(sqlite3StrICmp(destination, "stdout") == 0){ + sqlcipher_trace_file = stdout; + }else if(sqlite3StrICmp(destination, "stderr") == 0){ + sqlcipher_trace_file = stderr; + }else if(sqlite3StrICmp(destination, "off") != 0){ +#if !defined(SQLCIPHER_PROFILE_USE_FOPEN) && (defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT)) + if(fopen_s(&sqlcipher_trace_file, destination, "a") != 0) return SQLITE_ERROR; +#else + if((sqlcipher_trace_file = fopen(destination, "a")) == 0) return SQLITE_ERROR; +#endif + } + sqlcipher_trace("sqlcipher_set_trace: set trace to %s\n", destination); + return SQLITE_OK; +#endif +} + #endif /* END SQLCIPHER */ From 74cabf63250a3e1b17453277ae4a4c36e79d2698 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 15 Dec 2021 12:07:38 -0500 Subject: [PATCH 029/340] expand trace logging on error conditions in codec --- src/crypto.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/crypto.c b/src/crypto.c index 03ecb430f..411bf8d13 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -191,6 +191,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef char *migrate_status = sqlite3_mprintf("%d", status); codec_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); if(status != SQLITE_OK) { + CODEC_TRACE("sqlcipher_codec_pragma: error occurred during cipher_migrate: %d\n", status); sqlcipher_codec_ctx_set_error(ctx, status); } } @@ -723,6 +724,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { /* call to derive keys if not present yet */ if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) { + CODEC_TRACE("sqlite3Codec: error occurred during key derivation: %d\n", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -731,6 +733,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { PRAGMA. We can't set the error state on the pager at that point because the pager may not be open yet. However, this is a fatal error state, so abort the codec */ if(plaintext_header_sz < 0) { + CODEC_TRACE("sqlite3Codec: error invalid plaintext_header_sz: %d\n", plaintext_header_sz); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); return NULL; } @@ -755,6 +758,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { if(rc != SQLITE_OK) { /* failure to decrypt a page is considered a permanent error and will render the pager unusable in order to prevent inconsistent data being loaded into page cache */ + CODEC_TRACE("sqlite3Codec: error decrypting page data: %d\n", rc); sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); } @@ -770,6 +774,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { void *kdf_salt = NULL; /* retrieve the kdf salt */ if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &kdf_salt)) != SQLITE_OK) { + CODEC_TRACE("sqlite3Codec: error retrieving salt: %d\n", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -785,6 +790,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { if(rc != SQLITE_OK) { /* failure to encrypt a page is considered a permanent error and will render the pager unusable in order to prevent corrupted pages from being written to the main databased when using WAL */ + CODEC_TRACE("sqlite3Codec: error encrypting page data: %d\n", rc); sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; @@ -793,6 +799,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { break; default: + CODEC_TRACE("sqlite3Codec: error unsupported codec mode %d\n", mode); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); /* unsupported mode, set error */ return pData; break; From d4811ffd6927aca6ca3cbac85e3fd35a7348eace Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 Dec 2021 12:42:05 -0500 Subject: [PATCH 030/340] add trace log filter and tag feature via PRAGMA cipher_trace_filter --- src/crypto.c | 13 ++++++++++++- src/crypto.h | 19 +++++++++++++++++-- src/crypto_impl.c | 13 +++++++++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 411bf8d13..5fc91f9ef 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -142,7 +142,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } else if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ - char *flags = sqlite3_mprintf("%i", sqlcipher_get_test_flags()); + char *flags = sqlite3_mprintf("%u", sqlcipher_get_test_flags()); codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); }else if( sqlite3StrICmp(zLeft,"cipher_test_rand")==0 ){ @@ -682,6 +682,17 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check"); } } else + if( sqlite3StrICmp(zLeft, "cipher_trace_filter")==0 && zRight){ + unsigned int filter = 0; + printf("%s\n",zRight); + if(sqlite3_strlike("%CORE%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_CORE; + if(sqlite3_strlike("%MEMORY%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_MEMORY; + if(sqlite3_strlike("%MUTEX%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_MUTEX; + if(sqlite3_strlike("%PROVIDER%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_PROVIDER; + if(sqlite3_strlike("%ALL%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_ALL; + sqlcipher_set_trace_filter(filter); + codec_vdbe_return_string(pParse, "cipher_trace_filter", sqlite3_mprintf("%u", filter), P4_DYNAMIC); + } else if( sqlite3StrICmp(zLeft, "cipher_trace")== 0 && zRight ){ char *profile_status = sqlite3_mprintf("%d", sqlcipher_set_trace(zRight)); codec_vdbe_return_string(pParse, "cipher_trace", profile_status, P4_DYNAMIC); diff --git a/src/crypto.h b/src/crypto.h index ed6e3a1d8..4a94ce597 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -304,7 +304,14 @@ int sqlcipher_find_db_index(sqlite3 *db, const char *zDb); int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); int sqlcipher_set_trace(const char *destination); -void sqlcipher_trace(const char *message, ...); +int sqlcipher_set_trace_filter(unsigned int filter); +void sqlcipher_trace(unsigned int tag, const char *message, ...); + +#define SQLCIPHER_TRACE_CORE 0x01 +#define SQLCIPHER_TRACE_PROVIDER 0x02 +#define SQLCIPHER_TRACE_MEMORY 0x04 +#define SQLCIPHER_TRACE_MUTEX 0x08 +#define SQLCIPHER_TRACE_ALL 0xffffffff #ifdef CODEC_DEBUG #ifdef __ANDROID__ @@ -316,20 +323,28 @@ void sqlcipher_trace(const char *message, ...); #ifdef SQLCIPHER_OMIT_TRACE #define CODEC_TRACE(...) #else -#define CODEC_TRACE(...) {sqlcipher_trace(__VA_ARGS__);}; +#define CODEC_TRACE(...) {sqlcipher_trace(SQLCIPHER_TRACE_CORE, __VA_ARGS__);}; #endif #endif #ifdef CODEC_DEBUG_MUTEX #define CODEC_TRACE_MUTEX(...) CODEC_TRACE(__VA_ARGS__) #else +#ifdef SQLCIPHER_OMIT_TRACE #define CODEC_TRACE_MUTEX(...) +#else +#define CODEC_TRACE_MUTEX(...) {sqlcipher_trace(SQLCIPHER_TRACE_MUTEX, __VA_ARGS__);}; +#endif #endif #ifdef CODEC_DEBUG_MEMORY #define CODEC_TRACE_MEMORY(...) CODEC_TRACE(__VA_ARGS__) #else +#ifdef SQLCIPHER_OMIT_TRACE #define CODEC_TRACE_MEMORY(...) +#else +#define CODEC_TRACE_MEMORY(...) {sqlcipher_trace(SQLCIPHER_TRACE_MEMORY, __VA_ARGS__);}; +#endif #endif #ifdef CODEC_DEBUG_PAGEDATA diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 195e4b207..f130f4033 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -90,6 +90,7 @@ static sqlcipher_provider *default_provider = NULL; static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; static volatile FILE* sqlcipher_trace_file = NULL; static volatile int sqlcipher_trace_logcat = 0; +static volatile int sqlcipher_trace_filter = 0; sqlite3_mutex* sqlcipher_mutex(int mutex) { if(mutex < 0 || mutex >= SQLCIPHER_MUTEX_COUNT) return NULL; @@ -1641,8 +1642,12 @@ const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) { } #ifndef SQLCIPHER_OMIT_TRACE -void sqlcipher_trace(const char *message, ...) { +void sqlcipher_trace(unsigned int tag, const char *message, ...) { va_list params; + if((sqlcipher_trace_filter & tag) == 0 || (sqlcipher_trace_logcat == 0 && sqlcipher_trace_file == NULL)) { + /* no log target or tag not in included filters */ + return; + } va_start(params, message); if(sqlcipher_trace_file != NULL){ vfprintf((FILE*)sqlcipher_trace_file, message, params); @@ -1656,6 +1661,10 @@ void sqlcipher_trace(const char *message, ...) { } #endif +int sqlcipher_set_trace_filter(unsigned int filter) { + sqlcipher_trace_filter = filter; +} + int sqlcipher_set_trace(const char *destination){ #ifdef SQLCIPHER_OMIT_TRACE return SQLITE_ERROR; @@ -1681,7 +1690,7 @@ int sqlcipher_set_trace(const char *destination){ if((sqlcipher_trace_file = fopen(destination, "a")) == 0) return SQLITE_ERROR; #endif } - sqlcipher_trace("sqlcipher_set_trace: set trace to %s\n", destination); + sqlcipher_trace(SQLCIPHER_TRACE_CORE, "sqlcipher_set_trace: set trace to %s\n", destination); return SQLITE_OK; #endif } From 32b2109f57fd8297e4b219f5a0be0350e18fb658 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 Dec 2021 12:48:25 -0500 Subject: [PATCH 031/340] replace use of sqlite3StrICmp with public sqlite3_stricmp API --- src/crypto.c | 128 +++++++++++++++++++++++----------------------- src/crypto_impl.c | 14 ++--- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 5fc91f9ef..902730576 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -97,11 +97,11 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef CODEC_TRACE("sqlcipher_codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p\n", db, iDb, pParse, zLeft, zRight, ctx); #ifdef SQLCIPHER_EXT - if( sqlite3StrICmp(zLeft, "cipher_license")==0 && zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_license")==0 && zRight ){ char *license_result = sqlite3_mprintf("%d", sqlcipher_license_key(zRight)); codec_vdbe_return_string(pParse, "cipher_license", license_result, P4_DYNAMIC); } else - if( sqlite3StrICmp(zLeft, "cipher_license")==0 && !zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_license")==0 && !zRight ){ if(ctx) { char *license_result = sqlite3_mprintf("%d", ctx ? sqlcipher_license_key_status(ctx->provider) @@ -111,41 +111,41 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else #endif #ifdef SQLCIPHER_TEST - if( sqlite3StrICmp(zLeft,"cipher_test_on")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_test_on")==0 ){ if( zRight ) { unsigned int flags = sqlcipher_get_test_flags(); - if(sqlite3StrICmp(zRight, "fail_encrypt")==0) { + if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { flags |= TEST_FAIL_ENCRYPT; } else - if(sqlite3StrICmp(zRight, "fail_decrypt")==0) { + if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { flags |= TEST_FAIL_DECRYPT; } else - if(sqlite3StrICmp(zRight, "fail_migrate")==0) { + if(sqlite3_stricmp(zRight, "fail_migrate")==0) { flags |= TEST_FAIL_MIGRATE; } sqlcipher_set_test_flags(flags); } } else - if( sqlite3StrICmp(zLeft,"cipher_test_off")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_test_off")==0 ){ if( zRight ) { unsigned int flags = sqlcipher_get_test_flags(); - if(sqlite3StrICmp(zRight, "fail_encrypt")==0) { + if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { flags &= ~TEST_FAIL_ENCRYPT; } else - if(sqlite3StrICmp(zRight, "fail_decrypt")==0) { + if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { flags &= ~TEST_FAIL_DECRYPT; } else - if(sqlite3StrICmp(zRight, "fail_migrate")==0) { + if(sqlite3_stricmp(zRight, "fail_migrate")==0) { flags &= ~TEST_FAIL_MIGRATE; } sqlcipher_set_test_flags(flags); } } else - if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_test")==0 ){ char *flags = sqlite3_mprintf("%u", sqlcipher_get_test_flags()); codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); }else - if( sqlite3StrICmp(zLeft,"cipher_test_rand")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_test_rand")==0 ){ if( zRight ) { int rand = atoi(zRight); sqlcipher_set_test_rand(rand); @@ -155,13 +155,13 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } else #endif - if( sqlite3StrICmp(zLeft, "cipher_fips_status")== 0 && !zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_fips_status")== 0 && !zRight ){ if(ctx) { char *fips_mode_status = sqlite3_mprintf("%d", sqlcipher_codec_fips_status(ctx)); codec_vdbe_return_string(pParse, "cipher_fips_status", fips_mode_status, P4_DYNAMIC); } } else - if( sqlite3StrICmp(zLeft, "cipher_store_pass")==0 && zRight ) { + if( sqlite3_stricmp(zLeft, "cipher_store_pass")==0 && zRight ) { if(ctx) { char *deprecation = "PRAGMA cipher_store_pass is deprecated, please remove from use"; sqlcipher_codec_set_store_pass(ctx, sqlite3GetBoolean(zRight, 1)); @@ -169,23 +169,23 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlite3_log(SQLITE_WARNING, deprecation); } } else - if( sqlite3StrICmp(zLeft, "cipher_store_pass")==0 && !zRight ) { + if( sqlite3_stricmp(zLeft, "cipher_store_pass")==0 && !zRight ) { if(ctx){ char *store_pass_value = sqlite3_mprintf("%d", sqlcipher_codec_get_store_pass(ctx)); codec_vdbe_return_string(pParse, "cipher_store_pass", store_pass_value, P4_DYNAMIC); } } - if( sqlite3StrICmp(zLeft, "cipher_profile")== 0 && zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_profile")== 0 && zRight ){ char *profile_status = sqlite3_mprintf("%d", sqlcipher_cipher_profile(db, zRight)); codec_vdbe_return_string(pParse, "cipher_profile", profile_status, P4_DYNAMIC); } else - if( sqlite3StrICmp(zLeft, "cipher_add_random")==0 && zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_add_random")==0 && zRight ){ if(ctx) { char *add_random_status = sqlite3_mprintf("%d", sqlcipher_codec_add_random(ctx, zRight, sqlite3Strlen30(zRight))); codec_vdbe_return_string(pParse, "cipher_add_random", add_random_status, P4_DYNAMIC); } } else - if( sqlite3StrICmp(zLeft, "cipher_migrate")==0 && !zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_migrate")==0 && !zRight ){ if(ctx){ int status = sqlcipher_codec_ctx_migrate(ctx); char *migrate_status = sqlite3_mprintf("%d", status); @@ -196,20 +196,20 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } } else - if( sqlite3StrICmp(zLeft, "cipher_provider")==0 && !zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_provider")==0 && !zRight ){ if(ctx) { codec_vdbe_return_string(pParse, "cipher_provider", sqlcipher_codec_get_cipher_provider(ctx), P4_TRANSIENT); } } else - if( sqlite3StrICmp(zLeft, "cipher_provider_version")==0 && !zRight){ + if( sqlite3_stricmp(zLeft, "cipher_provider_version")==0 && !zRight){ if(ctx) { codec_vdbe_return_string(pParse, "cipher_provider_version", sqlcipher_codec_get_provider_version(ctx), P4_TRANSIENT); } } else - if( sqlite3StrICmp(zLeft, "cipher_version")==0 && !zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_version")==0 && !zRight ){ codec_vdbe_return_string(pParse, "cipher_version", sqlcipher_version(), P4_DYNAMIC); }else - if( sqlite3StrICmp(zLeft, "cipher")==0 ){ + if( sqlite3_stricmp(zLeft, "cipher")==0 ){ if(ctx) { if( zRight ) { const char* message = "PRAGMA cipher is no longer supported."; @@ -220,12 +220,12 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft, "rekey_cipher")==0 && zRight ){ + if( sqlite3_stricmp(zLeft, "rekey_cipher")==0 && zRight ){ const char* message = "PRAGMA rekey_cipher is no longer supported."; codec_vdbe_return_string(pParse, "rekey_cipher", message, P4_TRANSIENT); sqlite3_log(SQLITE_WARNING, message); }else - if( sqlite3StrICmp(zLeft,"cipher_default_kdf_iter")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_kdf_iter")==0 ){ if( zRight ) { sqlcipher_set_default_kdf_iter(atoi(zRight)); /* change default KDF iterations */ } else { @@ -233,7 +233,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef codec_vdbe_return_string(pParse, "cipher_default_kdf_iter", kdf_iter, P4_DYNAMIC); } }else - if( sqlite3StrICmp(zLeft, "kdf_iter")==0 ){ + if( sqlite3_stricmp(zLeft, "kdf_iter")==0 ){ if(ctx) { if( zRight ) { sqlcipher_codec_ctx_set_kdf_iter(ctx, atoi(zRight)); /* change of RW PBKDF2 iteration */ @@ -243,7 +243,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft, "fast_kdf_iter")==0){ + if( sqlite3_stricmp(zLeft, "fast_kdf_iter")==0){ if(ctx) { if( zRight ) { char *deprecation = "PRAGMA fast_kdf_iter is deprecated, please remove from use"; @@ -256,12 +256,12 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft, "rekey_kdf_iter")==0 && zRight ){ + if( sqlite3_stricmp(zLeft, "rekey_kdf_iter")==0 && zRight ){ const char* message = "PRAGMA rekey_kdf_iter is no longer supported."; codec_vdbe_return_string(pParse, "rekey_kdf_iter", message, P4_TRANSIENT); sqlite3_log(SQLITE_WARNING, message); }else - if( sqlite3StrICmp(zLeft,"cipher_page_size")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_page_size")==0 ){ if(ctx) { if( zRight ) { int size = atoi(zRight); @@ -275,7 +275,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_default_page_size")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_page_size")==0 ){ if( zRight ) { sqlcipher_set_default_pagesize(atoi(zRight)); } else { @@ -283,7 +283,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef codec_vdbe_return_string(pParse, "cipher_default_page_size", default_page_size, P4_DYNAMIC); } }else - if( sqlite3StrICmp(zLeft,"cipher_default_use_hmac")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_use_hmac")==0 ){ if( zRight ) { sqlcipher_set_default_use_hmac(sqlite3GetBoolean(zRight,1)); } else { @@ -291,7 +291,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef codec_vdbe_return_string(pParse, "cipher_default_use_hmac", default_use_hmac, P4_DYNAMIC); } }else - if( sqlite3StrICmp(zLeft,"cipher_use_hmac")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_use_hmac")==0 ){ if(ctx) { if( zRight ) { rc = sqlcipher_codec_ctx_set_use_hmac(ctx, sqlite3GetBoolean(zRight,1)); @@ -305,18 +305,18 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_hmac_pgno")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_hmac_pgno")==0 ){ if(ctx) { if(zRight) { char *deprecation = "PRAGMA cipher_hmac_pgno is deprecated, please remove from use"; /* clear both pgno endian flags */ - if(sqlite3StrICmp(zRight, "le") == 0) { + if(sqlite3_stricmp(zRight, "le") == 0) { sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO); sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_LE_PGNO); - } else if(sqlite3StrICmp(zRight, "be") == 0) { + } else if(sqlite3_stricmp(zRight, "be") == 0) { sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO); sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_BE_PGNO); - } else if(sqlite3StrICmp(zRight, "native") == 0) { + } else if(sqlite3_stricmp(zRight, "native") == 0) { sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO); sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO); } @@ -334,7 +334,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_hmac_salt_mask")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_hmac_salt_mask")==0 ){ if(ctx) { if(zRight) { char *deprecation = "PRAGMA cipher_hmac_salt_mask is deprecated, please remove from use"; @@ -352,7 +352,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_plaintext_header_size")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_plaintext_header_size")==0 ){ if(ctx) { if( zRight ) { int size = atoi(zRight); @@ -365,7 +365,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_default_plaintext_header_size")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_plaintext_header_size")==0 ){ if( zRight ) { sqlcipher_set_default_plaintext_header_size(atoi(zRight)); } else { @@ -373,7 +373,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef codec_vdbe_return_string(pParse, "cipher_default_plaintext_header_size", size, P4_DYNAMIC); } }else - if( sqlite3StrICmp(zLeft,"cipher_salt")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_salt")==0 ){ if(ctx) { if(zRight) { if (sqlite3StrNICmp(zRight ,"x'", 2) == 0 && sqlite3Strlen30(zRight) == (FILE_HEADER_SZ*2)+3) { @@ -396,15 +396,15 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_hmac_algorithm")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_hmac_algorithm")==0 ){ if(ctx) { if(zRight) { rc = SQLITE_ERROR; - if(sqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { + if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA256); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA512); } if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); @@ -422,14 +422,14 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_default_hmac_algorithm")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_hmac_algorithm")==0 ){ if(zRight) { rc = SQLITE_ERROR; - if(sqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { + if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA256); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA512); } } else { @@ -443,15 +443,15 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_kdf_algorithm")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_kdf_algorithm")==0 ){ if(ctx) { if(zRight) { rc = SQLITE_ERROR; - if(sqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { + if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA256); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA512); } if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); @@ -467,14 +467,14 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_default_kdf_algorithm")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_kdf_algorithm")==0 ){ if(zRight) { rc = SQLITE_ERROR; - if(sqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { + if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA256); - } else if(sqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA512); } } else { @@ -488,7 +488,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_compatibility")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_compatibility")==0 ){ if(ctx) { if(zRight) { int version = atoi(zRight); @@ -552,7 +552,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_default_compatibility")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_compatibility")==0 ){ if(zRight) { int version = atoi(zRight); switch(version) { @@ -590,7 +590,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } } }else - if( sqlite3StrICmp(zLeft,"cipher_memory_security")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_memory_security")==0 ){ if( zRight ) { sqlcipher_set_mem_security(sqlite3GetBoolean(zRight,1)); } else { @@ -598,7 +598,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef codec_vdbe_return_string(pParse, "cipher_memory_security", on, P4_DYNAMIC); } }else - if( sqlite3StrICmp(zLeft,"cipher_settings")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_settings")==0 ){ if(ctx) { int algorithm; char *pragma; @@ -639,7 +639,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } }else - if( sqlite3StrICmp(zLeft,"cipher_default_settings")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_default_settings")==0 ){ int algorithm; char *pragma; @@ -677,12 +677,12 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); }else - if( sqlite3StrICmp(zLeft,"cipher_integrity_check")==0 ){ + if( sqlite3_stricmp(zLeft,"cipher_integrity_check")==0 ){ if(ctx) { sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check"); } } else - if( sqlite3StrICmp(zLeft, "cipher_trace_filter")==0 && zRight){ + if( sqlite3_stricmp(zLeft, "cipher_trace_filter")==0 && zRight){ unsigned int filter = 0; printf("%s\n",zRight); if(sqlite3_strlike("%CORE%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_CORE; @@ -693,7 +693,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlcipher_set_trace_filter(filter); codec_vdbe_return_string(pParse, "cipher_trace_filter", sqlite3_mprintf("%u", filter), P4_DYNAMIC); } else - if( sqlite3StrICmp(zLeft, "cipher_trace")== 0 && zRight ){ + if( sqlite3_stricmp(zLeft, "cipher_trace")== 0 && zRight ){ char *profile_status = sqlite3_mprintf("%d", sqlcipher_set_trace(zRight)); codec_vdbe_return_string(pParse, "cipher_trace", profile_status, P4_DYNAMIC); }else { @@ -1154,7 +1154,7 @@ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **ar /* if the name of the target is not main, but the index returned is zero there is a mismatch and we should not proceed */ targetDb_idx = sqlcipher_find_db_index(db, targetDb); - if(targetDb_idx == 0 && targetDb != NULL && sqlite3StrICmp("main", targetDb) != 0) { + if(targetDb_idx == 0 && targetDb != NULL && sqlite3_stricmp("main", targetDb) != 0) { rc = SQLITE_ERROR; pzErrMsg = sqlite3_mprintf("unknown database %s", targetDb); goto end_of_export; diff --git a/src/crypto_impl.c b/src/crypto_impl.c index f130f4033..ebe9d1dab 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1615,11 +1615,11 @@ int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ return SQLITE_ERROR; #else FILE *f; - if(sqlite3StrICmp(destination, "stdout") == 0){ + if(sqlite3_stricmp(destination, "stdout") == 0){ f = stdout; - }else if(sqlite3StrICmp(destination, "stderr") == 0){ + }else if(sqlite3_stricmp(destination, "stderr") == 0){ f = stderr; - }else if(sqlite3StrICmp(destination, "off") == 0){ + }else if(sqlite3_stricmp(destination, "off") == 0){ f = 0; }else{ #if !defined(SQLCIPHER_PROFILE_USE_FOPEN) && (defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT)) @@ -1677,13 +1677,13 @@ int sqlcipher_set_trace(const char *destination){ sqlcipher_trace_file = NULL; sqlcipher_trace_logcat = 0; - if(sqlite3StrICmp(destination, "logcat") == 0){ + if(sqlite3_stricmp(destination, "logcat") == 0){ sqlcipher_trace_logcat = 1; - } else if(sqlite3StrICmp(destination, "stdout") == 0){ + } else if(sqlite3_stricmp(destination, "stdout") == 0){ sqlcipher_trace_file = stdout; - }else if(sqlite3StrICmp(destination, "stderr") == 0){ + }else if(sqlite3_stricmp(destination, "stderr") == 0){ sqlcipher_trace_file = stderr; - }else if(sqlite3StrICmp(destination, "off") != 0){ + }else if(sqlite3_stricmp(destination, "off") != 0){ #if !defined(SQLCIPHER_PROFILE_USE_FOPEN) && (defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT)) if(fopen_s(&sqlcipher_trace_file, destination, "a") != 0) return SQLITE_ERROR; #else From a1e5f72897abf8226bbe012b45b7a755ec224885 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 Dec 2021 13:23:08 -0500 Subject: [PATCH 032/340] prefix sqlcipher trace with local timestamp --- src/crypto.c | 1 - src/crypto_impl.c | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 902730576..8523ef452 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -684,7 +684,6 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else if( sqlite3_stricmp(zLeft, "cipher_trace_filter")==0 && zRight){ unsigned int filter = 0; - printf("%s\n",zRight); if(sqlite3_strlike("%CORE%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_CORE; if(sqlite3_strlike("%MEMORY%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_MEMORY; if(sqlite3_strlike("%MUTEX%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_MUTEX; diff --git a/src/crypto_impl.c b/src/crypto_impl.c index ebe9d1dab..bb1a2839d 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -33,14 +33,20 @@ #include "sqlcipher.h" #include "crypto.h" +#include + +#if defined(_WIN32) || defined(SQLITE_OS_WINRT) +#include /* amalgamator: dontcache */ +#else +#include /* amalgamator: dontcache */ +#endif + #ifndef OMIT_MEMLOCK #if defined(__unix__) || defined(__APPLE__) || defined(_AIX) #include /* amalgamator: dontcache */ #include /* amalgamator: dontcache */ #include /* amalgamator: dontcache */ #include /* amalgamator: dontcache */ -#elif defined(_WIN32) -#include #endif #endif @@ -1650,6 +1656,15 @@ void sqlcipher_trace(unsigned int tag, const char *message, ...) { } va_start(params, message); if(sqlcipher_trace_file != NULL){ + char buffer[20]; + struct tm tt; + struct timeval tv; + int ms; + gettimeofday(&tv, NULL); + ms = tv.tv_usec/1000.0; + localtime_r(&tv.tv_sec, &tt); + strftime(buffer, 20, "%Y-%m-%d %H:%M:%S", &tt); + fprintf((FILE*)sqlcipher_trace_file, "%s.%03d: ", buffer, ms); vfprintf((FILE*)sqlcipher_trace_file, message, params); } #ifdef __ANDROID__ From 47bb328afe4033ade734cfd854b66911e055fdbb Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 Dec 2021 13:38:24 -0500 Subject: [PATCH 033/340] trace logging will automatically append newline --- src/crypto.c | 92 ++++++++--------- src/crypto.h | 2 +- src/crypto_impl.c | 211 +++++++++++++++++++-------------------- src/crypto_libtomcrypt.c | 32 +++--- src/crypto_nss.c | 16 +-- src/crypto_openssl.c | 32 +++--- 6 files changed, 192 insertions(+), 193 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 8523ef452..33cebd0f2 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -53,11 +53,11 @@ static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ct page_sz = sqlcipher_codec_ctx_get_pagesize(ctx); reserve_sz = sqlcipher_codec_ctx_get_reservesize(ctx); - CODEC_TRACE("codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d\n", page_sz, reserve_sz); + CODEC_TRACE("codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d", page_sz, reserve_sz); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entering database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entered database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entered database mutex %p", db->mutex); db->nextPagesize = page_sz; /* before forcing the page size we need to unset the BTS_PAGESIZE_FIXED flag, else @@ -65,18 +65,18 @@ static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ct pDb->pBt->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; rc = sqlite3BtreeSetPageSize(pDb->pBt, page_sz, reserve_sz, 0); - CODEC_TRACE("codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d\n", rc); + CODEC_TRACE("codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d", rc); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: leaving database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: left database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: left database mutex %p", db->mutex); return rc; } static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("codec_set_pass_key: entered db=%p nDb=%d zKey=%p nKey=%d for_ctx=%d\n", db, nDb, zKey, nKey, for_ctx); + CODEC_TRACE("codec_set_pass_key: entered db=%p nDb=%d zKey=%p nKey=%d for_ctx=%d", db, nDb, zKey, nKey, for_ctx); if(pDb->pBt) { codec_ctx *ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); @@ -94,7 +94,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); } - CODEC_TRACE("sqlcipher_codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p\n", db, iDb, pParse, zLeft, zRight, ctx); + CODEC_TRACE("sqlcipher_codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); #ifdef SQLCIPHER_EXT if( sqlite3_stricmp(zLeft, "cipher_license")==0 && zRight ){ @@ -191,7 +191,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef char *migrate_status = sqlite3_mprintf("%d", status); codec_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); if(status != SQLITE_OK) { - CODEC_TRACE("sqlcipher_codec_pragma: error occurred during cipher_migrate: %d\n", status); + CODEC_TRACE("sqlcipher_codec_pragma: error occurred during cipher_migrate: %d", status); sqlcipher_codec_ctx_set_error(ctx, status); } } @@ -726,7 +726,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { int plaintext_header_sz = sqlcipher_codec_ctx_get_plaintext_header_size(ctx); int cctx = CIPHER_READ_CTX; - CODEC_TRACE("sqlite3Codec: entered pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); + CODEC_TRACE("sqlite3Codec: entered pgno=%d, mode=%d, page_sz=%d", pgno, mode, page_sz); #ifdef SQLCIPHER_EXT if(sqlcipher_license_check(ctx) != SQLITE_OK) return NULL; @@ -734,7 +734,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { /* call to derive keys if not present yet */ if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) { - CODEC_TRACE("sqlite3Codec: error occurred during key derivation: %d\n", rc); + CODEC_TRACE("sqlite3Codec: error occurred during key derivation: %d", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -743,7 +743,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { PRAGMA. We can't set the error state on the pager at that point because the pager may not be open yet. However, this is a fatal error state, so abort the codec */ if(plaintext_header_sz < 0) { - CODEC_TRACE("sqlite3Codec: error invalid plaintext_header_sz: %d\n", plaintext_header_sz); + CODEC_TRACE("sqlite3Codec: error invalid plaintext_header_sz: %d", plaintext_header_sz); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); return NULL; } @@ -752,7 +752,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { offset = plaintext_header_sz ? plaintext_header_sz : FILE_HEADER_SZ; - CODEC_TRACE("sqlite3Codec: switch mode=%d offset=%d\n", mode, offset); + CODEC_TRACE("sqlite3Codec: switch mode=%d offset=%d", mode, offset); switch(mode) { case CODEC_READ_OP: /* decrypt */ if(pgno == 1) /* copy initial part of file header or SQLite magic to buffer */ @@ -768,7 +768,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { if(rc != SQLITE_OK) { /* failure to decrypt a page is considered a permanent error and will render the pager unusable in order to prevent inconsistent data being loaded into page cache */ - CODEC_TRACE("sqlite3Codec: error decrypting page data: %d\n", rc); + CODEC_TRACE("sqlite3Codec: error decrypting page data: %d", rc); sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); } @@ -784,7 +784,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { void *kdf_salt = NULL; /* retrieve the kdf salt */ if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &kdf_salt)) != SQLITE_OK) { - CODEC_TRACE("sqlite3Codec: error retrieving salt: %d\n", rc); + CODEC_TRACE("sqlite3Codec: error retrieving salt: %d", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -800,7 +800,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { if(rc != SQLITE_OK) { /* failure to encrypt a page is considered a permanent error and will render the pager unusable in order to prevent corrupted pages from being written to the main databased when using WAL */ - CODEC_TRACE("sqlite3Codec: error encrypting page data: %d\n", rc); + CODEC_TRACE("sqlite3Codec: error encrypting page data: %d", rc); sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; @@ -809,7 +809,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { break; default: - CODEC_TRACE("sqlite3Codec: error unsupported codec mode %d\n", mode); + CODEC_TRACE("sqlite3Codec: error unsupported codec mode %d", mode); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); /* unsupported mode, set error */ return pData; break; @@ -826,7 +826,7 @@ static void sqlite3FreeCodecArg(void *pCodecArg) { int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("sqlite3CodecAttach: entered db=%p, nDb=%d zKey=%p, nKey=%d\n", db, nDb, zKey, nKey); + CODEC_TRACE("sqlite3CodecAttach: entered db=%p, nDb=%d zKey=%p, nKey=%d", db, nDb, zKey, nKey); if(nKey && zKey && pDb->pBt) { @@ -838,12 +838,12 @@ int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { /* check if the sqlite3_file is open, and if not force handle to NULL */ if((fd = sqlite3PagerFile(pPager))->pMethods == 0) fd = NULL; - CODEC_TRACE("sqlite3CodecAttach: calling sqlcipher_activate()\n"); + CODEC_TRACE("sqlite3CodecAttach: calling sqlcipher_activate()"); sqlcipher_activate(); /* perform internal initialization for sqlcipher */ - CODEC_TRACE_MUTEX("sqlite3CodecAttach: entering database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3CodecAttach: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - CODEC_TRACE_MUTEX("sqlite3CodecAttach: entered database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3CodecAttach: entered database mutex %p", db->mutex); #ifdef SQLCIPHER_EXT if((rc = sqlite3_set_authorizer(db, sqlcipher_license_authorizer, db)) != SQLITE_OK) { @@ -853,44 +853,44 @@ int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { #endif /* point the internal codec argument against the contet to be prepared */ - CODEC_TRACE("sqlite3CodecAttach: calling sqlcipher_codec_ctx_init()\n"); + CODEC_TRACE("sqlite3CodecAttach: calling sqlcipher_codec_ctx_init()"); rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, zKey, nKey); if(rc != SQLITE_OK) { /* initialization failed, do not attach potentially corrupted context */ - CODEC_TRACE("sqlite3CodecAttach: context initialization failed with rc=%d\n", rc); + CODEC_TRACE("sqlite3CodecAttach: context initialization failed with rc=%d", rc); /* force an error at the pager level, such that even the upstream caller ignores the return code the pager will be in an error state and will process no further operations */ sqlite3pager_error(pPager, rc); pDb->pBt->pBt->db->errCode = rc; - CODEC_TRACE_MUTEX("sqlite3CodecAttach: leaving database mutex %p (early return on rc=%d)\n", db->mutex, rc); + CODEC_TRACE_MUTEX("sqlite3CodecAttach: leaving database mutex %p (early return on rc=%d)", db->mutex, rc); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("sqlite3CodecAttach: left database mutex %p (early return on rc=%d)\n", db->mutex, rc); + CODEC_TRACE_MUTEX("sqlite3CodecAttach: left database mutex %p (early return on rc=%d)", db->mutex, rc); return rc; } - CODEC_TRACE("sqlite3CodecAttach: calling sqlite3PagerSetCodec()\n"); + CODEC_TRACE("sqlite3CodecAttach: calling sqlite3PagerSetCodec()"); sqlite3PagerSetCodec(sqlite3BtreePager(pDb->pBt), sqlite3Codec, NULL, sqlite3FreeCodecArg, (void *) ctx); - CODEC_TRACE("sqlite3CodecAttach: calling codec_set_btree_to_codec_pagesize()\n"); + CODEC_TRACE("sqlite3CodecAttach: calling codec_set_btree_to_codec_pagesize()"); codec_set_btree_to_codec_pagesize(db, pDb, ctx); /* force secure delete. This has the benefit of wiping internal data when deleted and also ensures that all pages are written to disk (i.e. not skipped by sqlite3PagerDontWrite optimizations) */ - CODEC_TRACE("sqlite3CodecAttach: calling sqlite3BtreeSecureDelete()\n"); + CODEC_TRACE("sqlite3CodecAttach: calling sqlite3BtreeSecureDelete()"); sqlite3BtreeSecureDelete(pDb->pBt, 1); /* if fd is null, then this is an in-memory database and we dont' want to overwrite the AutoVacuum settings if not null, then set to the default */ if(fd != NULL) { - CODEC_TRACE("sqlite3CodecAttach: calling sqlite3BtreeSetAutoVacuum()\n"); + CODEC_TRACE("sqlite3CodecAttach: calling sqlite3BtreeSetAutoVacuum()"); sqlite3BtreeSetAutoVacuum(pDb->pBt, SQLITE_DEFAULT_AUTOVACUUM); } - CODEC_TRACE_MUTEX("sqlite3CodecAttach: leaving database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3CodecAttach: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("sqlite3CodecAttach: left database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3CodecAttach: left database mutex %p", db->mutex); } return SQLITE_OK; } @@ -914,12 +914,12 @@ void sqlite3_activate_see(const char* in) { } int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_key entered: db=%p pKey=%p nKey=%d\n", db, pKey, nKey); + CODEC_TRACE("sqlite3_key entered: db=%p pKey=%p nKey=%d", db, pKey, nKey); return sqlite3_key_v2(db, "main", pKey, nKey); } int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_key_v2: entered db=%p zDb=%s pKey=%p nKey=%d\n", db, zDb, pKey, nKey); + CODEC_TRACE("sqlite3_key_v2: entered db=%p zDb=%s pKey=%p nKey=%d", db, zDb, pKey, nKey); /* attach key if db and pKey are not null and nKey is > 0 */ if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); @@ -929,7 +929,7 @@ int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { } int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_rekey entered: db=%p pKey=%p nKey=%d\n", db, pKey, nKey); + CODEC_TRACE("sqlite3_rekey entered: db=%p pKey=%p nKey=%d", db, pKey, nKey); return sqlite3_rekey_v2(db, "main", pKey, nKey); } @@ -944,11 +944,11 @@ int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { ** 3. If there is a key present, re-encrypt the database with the new key */ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_rekey_v2: entered db=%p zDb=%s pKey=%p, nKey=%d\n", db, zDb, pKey, nKey); + CODEC_TRACE("sqlite3_rekey_v2: entered db=%p zDb=%s pKey=%p, nKey=%d", db, zDb, pKey, nKey); if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); struct Db *pDb = &db->aDb[db_index]; - CODEC_TRACE("sqlite3_rekey_v2: database pDb=%p db_index:%d\n", pDb, db_index); + CODEC_TRACE("sqlite3_rekey_v2: database pDb=%p db_index:%d", pDb, db_index); if(pDb->pBt) { codec_ctx *ctx; int rc, page_count; @@ -960,13 +960,13 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(ctx == NULL) { /* there was no codec attached to this database, so this should do nothing! */ - CODEC_TRACE("sqlite3_rekey_v2: no codec attached to db, exiting\n"); + CODEC_TRACE("sqlite3_rekey_v2: no codec attached to db, exiting"); return SQLITE_OK; } - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: entering database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3_rekey_v2: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: entered database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3_rekey_v2: entered database mutex %p", db->mutex); codec_set_pass_key(db, db_index, pKey, nKey, CIPHER_WRITE_CTX); @@ -986,27 +986,27 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(rc == SQLITE_OK) { sqlite3PagerUnref(page); } else { - CODEC_TRACE("sqlite3_rekey_v2: error %d occurred writing page %d\n", rc, pgno); + CODEC_TRACE("sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); } } else { - CODEC_TRACE("sqlite3_rekey_v2: error %d occurred getting page %d\n", rc, pgno); + CODEC_TRACE("sqlite3_rekey_v2: error %d occurred getting page %d", rc, pgno); } } } /* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */ if(rc == SQLITE_OK) { - CODEC_TRACE("sqlite3_rekey_v2: committing\n"); + CODEC_TRACE("sqlite3_rekey_v2: committing"); rc = sqlite3BtreeCommit(pDb->pBt); sqlcipher_codec_key_copy(ctx, CIPHER_WRITE_CTX); } else { - CODEC_TRACE("sqlite3_rekey_v2: rollback\n"); + CODEC_TRACE("sqlite3_rekey_v2: rollback"); sqlite3BtreeRollback(pDb->pBt, SQLITE_ABORT_ROLLBACK, 0); } - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: leaving database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3_rekey_v2: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: left database mutex %p\n", db->mutex); + CODEC_TRACE_MUTEX("sqlite3_rekey_v2: left database mutex %p", db->mutex); } return SQLITE_OK; } @@ -1015,7 +1015,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { void sqlite3CodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("sqlite3CodecGetKey: entered db=%p, nDb=%d\n", db, nDb); + CODEC_TRACE("sqlite3CodecGetKey: entered db=%p, nDb=%d", db, nDb); if( pDb->pBt ) { codec_ctx *ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); diff --git a/src/crypto.h b/src/crypto.h index 4a94ce597..862ac229a 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -317,7 +317,7 @@ void sqlcipher_trace(unsigned int tag, const char *message, ...); #ifdef __ANDROID__ #define CODEC_TRACE(...) {__android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", __VA_ARGS__);} #else -#define CODEC_TRACE(...) {fprintf(stderr, __VA_ARGS__);fflush(stderr);} +#define CODEC_TRACE(...) {fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); fflush(stderr);} #endif #else #ifdef SQLCIPHER_OMIT_TRACE diff --git a/src/crypto_impl.c b/src/crypto_impl.c index bb1a2839d..4ac8adacd 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -112,7 +112,7 @@ static void sqlcipher_mem_shutdown(void *pAppData) { static void *sqlcipher_mem_malloc(int n) { void *ptr = default_mem_methods.xMalloc(n); if(mem_security_on) { - CODEC_TRACE_MEMORY("sqlcipher_mem_malloc: calling sqlcipher_mlock(%p,%d)\n", ptr, n); + CODEC_TRACE_MEMORY("sqlcipher_mem_malloc: calling sqlcipher_mlock(%p,%d)", ptr, n); sqlcipher_mlock(ptr, n); if(!mem_security_activated) mem_security_activated = 1; } @@ -125,7 +125,7 @@ static void sqlcipher_mem_free(void *p) { int sz; if(mem_security_on) { sz = sqlcipher_mem_size(p); - CODEC_TRACE_MEMORY("sqlcipher_mem_free: calling sqlcipher_memset(%p,0,%d) and sqlcipher_munlock(%p, %d) \n", p, sz, p, sz); + CODEC_TRACE_MEMORY("sqlcipher_mem_free: calling sqlcipher_memset(%p,0,%d) and sqlcipher_munlock(%p, %d)", p, sz, p, sz); sqlcipher_memset(p, 0, sz); sqlcipher_munlock(p, sz); if(!mem_security_activated) mem_security_activated = 1; @@ -182,9 +182,9 @@ void sqlcipher_init_memmethods() { } int sqlcipher_register_provider(sqlcipher_provider *p) { - CODEC_TRACE_MUTEX("sqlcipher_register_provider: entering SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_register_provider: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_register_provider: entered SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_register_provider: entered SQLCIPHER_MUTEX_PROVIDER"); if(default_provider != NULL && default_provider != p) { /* only free the current registerd provider if it has been initialized @@ -193,9 +193,9 @@ int sqlcipher_register_provider(sqlcipher_provider *p) { sqlcipher_free(default_provider, sizeof(sqlcipher_provider)); } default_provider = p; - CODEC_TRACE_MUTEX("sqlcipher_register_provider: leaving SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_register_provider: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_register_provider: left SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_register_provider: left SQLCIPHER_MUTEX_PROVIDER"); return SQLITE_OK; } @@ -208,9 +208,9 @@ sqlcipher_provider* sqlcipher_get_provider() { } void sqlcipher_activate() { - CODEC_TRACE_MUTEX("sqlcipher_activate: entering static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_activate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_activate: entered static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_activate: entered static master mutex"); /* allocate new mutexes */ if(sqlcipher_activate_count == 0) { @@ -240,42 +240,41 @@ void sqlcipher_activate() { #else #error "NO DEFAULT SQLCIPHER CRYPTO PROVIDER DEFINED" #endif - CODEC_TRACE("sqlcipher_activate: calling sqlcipher_register_provider(%p)\n", p); + CODEC_TRACE("sqlcipher_activate: calling sqlcipher_register_provider(%p)", p); #ifdef SQLCIPHER_EXT sqlcipher_ext_provider_setup(p); #endif sqlcipher_register_provider(p); - CODEC_TRACE("sqlcipher_activate: called sqlcipher_register_provider(%p)\n",p); + CODEC_TRACE("sqlcipher_activate: called sqlcipher_register_provider(%p)",p); } sqlcipher_activate_count++; /* increment activation count */ - CODEC_TRACE_MUTEX("sqlcipher_activate: leaving static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_activate: leaving static master mutex"); sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_activate: left static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_activate: left static master mutex"); } void sqlcipher_deactivate() { - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered static master mutex"); sqlcipher_activate_count--; /* if no connections are using sqlcipher, cleanup globals */ if(sqlcipher_activate_count < 1) { - - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered SQLCIPHER_MUTEX_PROVIDER"); if(default_provider != NULL) { sqlcipher_free(default_provider, sizeof(sqlcipher_provider)); default_provider = NULL; } - CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER"); #ifdef SQLCIPHER_EXT sqlcipher_ext_provider_destroy(); @@ -291,9 +290,9 @@ void sqlcipher_deactivate() { sqlcipher_activate_count = 0; /* reset activation count */ } - CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving static master mutex"); sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: left static master mutex\n"); + CODEC_TRACE_MUTEX("sqlcipher_deactivate: left static master mutex"); } /* constant time memset using volitile to avoid having the memset @@ -306,7 +305,7 @@ void* sqlcipher_memset(void *v, unsigned char value, u64 len) { if (v == NULL) return v; - CODEC_TRACE_MEMORY("sqlcipher_memset: setting %p[0-%llu]=%d)\n", a, len, value); + CODEC_TRACE_MEMORY("sqlcipher_memset: setting %p[0-%llu]=%d)", a, len, value); for(i = 0; i < len; i++) { a[i] = value; } @@ -350,18 +349,18 @@ void sqlcipher_mlock(void *ptr, u64 sz) { if(ptr == NULL || sz == 0) return; - CODEC_TRACE_MEMORY("sqlcipher_mem_lock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu\n", ptr - offset, sz + offset, pagesize); + CODEC_TRACE_MEMORY("sqlcipher_mem_lock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); rc = mlock(ptr - offset, sz + offset); if(rc!=0) { - CODEC_TRACE_MEMORY("sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d\n", ptr - offset, sz + offset, rc, errno); + CODEC_TRACE_MEMORY("sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) int rc; - CODEC_TRACE("sqlcipher_mem_lock: calling VirtualLock(%p,%d)\n", ptr, sz); + CODEC_TRACE("sqlcipher_mem_lock: calling VirtualLock(%p,%d)", ptr, sz); rc = VirtualLock(ptr, sz); if(rc==0) { - CODEC_TRACE("sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d\n", ptr, sz, rc, GetLastError()); + CODEC_TRACE("sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -377,18 +376,18 @@ void sqlcipher_munlock(void *ptr, u64 sz) { if(ptr == NULL || sz == 0) return; - CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: calling munlock(%p,%lu)\n", ptr - offset, sz + offset); + CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); rc = munlock(ptr - offset, sz + offset); if(rc!=0) { - CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d\n", ptr - offset, sz + offset, rc, errno); + CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) int rc; - CODEC_TRACE("sqlcipher_mem_lock: calling VirtualUnlock(%p,%d)\n", ptr, sz); + CODEC_TRACE("sqlcipher_mem_lock: calling VirtualUnlock(%p,%d)", ptr, sz); rc = VirtualUnlock(ptr, sz); if(!rc) { - CODEC_TRACE("sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d\n", ptr, sz, rc, GetLastError()); + CODEC_TRACE("sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -404,7 +403,7 @@ void sqlcipher_munlock(void *ptr, u64 sz) { * memory segment so it can be paged */ void sqlcipher_free(void *ptr, u64 sz) { - CODEC_TRACE_MEMORY("sqlcipher_free: calling sqlcipher_memset(%p,0,%llu)\n", ptr, sz); + CODEC_TRACE_MEMORY("sqlcipher_free: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); sqlcipher_memset(ptr, 0, sz); sqlcipher_munlock(ptr, sz); sqlite3_free(ptr); @@ -417,9 +416,9 @@ void sqlcipher_free(void *ptr, u64 sz) { */ void* sqlcipher_malloc(u64 sz) { void *ptr; - CODEC_TRACE_MEMORY("sqlcipher_malloc: calling sqlite3Malloc(%llu)\n", sz); + CODEC_TRACE_MEMORY("sqlcipher_malloc: calling sqlite3Malloc(%llu)", sz); ptr = sqlite3Malloc(sz); - CODEC_TRACE_MEMORY("sqlcipher_malloc: calling sqlcipher_memset(%p,0,%llu)\n", ptr, sz); + CODEC_TRACE_MEMORY("sqlcipher_malloc: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); sqlcipher_memset(ptr, 0, sz); sqlcipher_mlock(ptr, sz); return ptr; @@ -443,15 +442,15 @@ char* sqlcipher_version() { */ static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) { cipher_ctx *c_ctx; - CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating context\n"); + CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating context"); *iCtx = (cipher_ctx *) sqlcipher_malloc(sizeof(cipher_ctx)); c_ctx = *iCtx; if(c_ctx == NULL) return SQLITE_NOMEM; - CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating key\n"); + CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating key"); c_ctx->key = (unsigned char *) sqlcipher_malloc(ctx->key_sz); - CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating hmac_key\n"); + CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating hmac_key"); c_ctx->hmac_key = (unsigned char *) sqlcipher_malloc(ctx->key_sz); if(c_ctx->key == NULL) return SQLITE_NOMEM; @@ -465,7 +464,7 @@ static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) { */ static void sqlcipher_cipher_ctx_free(codec_ctx* ctx, cipher_ctx **iCtx) { cipher_ctx *c_ctx = *iCtx; - CODEC_TRACE("cipher_ctx_free: entered iCtx=%p\n", iCtx); + CODEC_TRACE("cipher_ctx_free: entered iCtx=%p", iCtx); sqlcipher_free(c_ctx->key, ctx->key_sz); sqlcipher_free(c_ctx->hmac_key, ctx->key_sz); sqlcipher_free(c_ctx->pass, c_ctx->pass_sz); @@ -486,7 +485,7 @@ static int sqlcipher_codec_ctx_reserve_setup(codec_ctx *ctx) { reserve = ((reserve % ctx->block_sz) == 0) ? reserve : ((reserve / ctx->block_sz) + 1) * ctx->block_sz; - CODEC_TRACE("sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d\n", + CODEC_TRACE("sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d", base_reserve, ctx->block_sz, ctx->hmac_sz, reserve); ctx->reserve_sz = reserve; @@ -516,8 +515,7 @@ static int sqlcipher_cipher_ctx_cmp(cipher_ctx *c1, cipher_ctx *c2) { c1->pass=%p c2->pass=%p \ c1->pass=%s c2->pass=%s \ sqlcipher_memcmp=%d \ - are_equal=%d \ - \n", + are_equal=%d", c1, c2, c1->pass_sz, c2->pass_sz, c1->pass, c2->pass, @@ -545,7 +543,7 @@ static int sqlcipher_cipher_ctx_copy(codec_ctx *ctx, cipher_ctx *target, cipher_ void *key = target->key; void *hmac_key = target->hmac_key; - CODEC_TRACE("sqlcipher_cipher_ctx_copy: entered target=%p, source=%p\n", target, source); + CODEC_TRACE("sqlcipher_cipher_ctx_copy: entered target=%p, source=%p", target, source); sqlcipher_free(target->pass, target->pass_sz); sqlcipher_free(target->keyspec, ctx->keyspec_sz); memcpy(target, source, sizeof(cipher_ctx)); @@ -790,7 +788,7 @@ int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag) { } void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) { - CODEC_TRACE("sqlcipher_codec_ctx_set_error: ctx=%p, error=%d\n", ctx, error); + CODEC_TRACE("sqlcipher_codec_ctx_set_error: ctx=%p, error=%d", ctx, error); sqlite3pager_error(ctx->pBt->pBt->pPager, error); ctx->pBt->pBt->db->errCode = error; } @@ -811,9 +809,9 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { } /* read salt from header, if present, otherwise generate a new random salt */ - CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: obtaining salt\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: obtaining salt"); if(fd == NULL || fd->pMethods == 0 || sqlite3OsRead(fd, ctx->kdf_salt, ctx->kdf_salt_sz, 0) != SQLITE_OK) { - CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random"); if(ctx->provider->random(ctx->provider_ctx, ctx->kdf_salt, ctx->kdf_salt_sz) != SQLITE_OK) return SQLITE_ERROR; } ctx->need_kdf_salt = 0; @@ -845,7 +843,7 @@ void sqlcipher_codec_get_keyspec(codec_ctx *ctx, void **zKey, int *nKey) { int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { if(!((size != 0) && ((size & (size - 1)) == 0)) || size < 512 || size > 65536) { - CODEC_TRACE(("cipher_page_size not a power of 2 and between 512 and 65536 inclusive\n")); + CODEC_TRACE(("cipher_page_size not a power of 2 and between 512 and 65536 inclusive")); return SQLITE_ERROR; } /* attempt to free the existing page buffer */ @@ -890,7 +888,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi int rc; codec_ctx *ctx; - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating context\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: allocating context"); *iCtx = sqlcipher_malloc(sizeof(codec_ctx)); ctx = *iCtx; @@ -903,7 +901,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi directly off the database file. This is the salt for the key derivation function. If we get a short read allocate a new random salt value */ - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating kdf_salt\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: allocating kdf_salt"); ctx->kdf_salt_sz = FILE_HEADER_SZ; ctx->kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz); if(ctx->kdf_salt == NULL) return SQLITE_NOMEM; @@ -911,7 +909,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi /* allocate space for separate hmac salt data. We want the HMAC derivation salt to be different than the encryption key derivation salt */ - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating hmac_kdf_salt\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: allocating hmac_kdf_salt"); ctx->hmac_kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz); if(ctx->hmac_kdf_salt == NULL) return SQLITE_NOMEM; @@ -922,22 +920,22 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi ctx->need_kdf_salt = 1; /* setup the crypto provider */ - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating provider\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: allocating provider"); ctx->provider = (sqlcipher_provider *) sqlcipher_malloc(sizeof(sqlcipher_provider)); if(ctx->provider == NULL) return SQLITE_NOMEM; /* make a copy of the provider to be used for the duration of the context */ - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entering SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entered SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entered SQLCIPHER_MUTEX_PROVIDER"); memcpy(ctx->provider, default_provider, sizeof(sqlcipher_provider)); - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: leaving SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: left SQLCIPHER_MUTEX_PROVIDER\n"); + CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: left SQLCIPHER_MUTEX_PROVIDER"); - CODEC_TRACE("sqlcipher_codec_ctx_init: calling provider ctx_init\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: calling provider ctx_init"); if((rc = ctx->provider->ctx_init(&ctx->provider_ctx)) != SQLITE_OK) return rc; ctx->key_sz = ctx->provider->get_key_sz(ctx->provider_ctx); @@ -954,44 +952,44 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi in encrypted and thus sqlite can't effectively determine the pagesize. this causes an issue in cases where bytes 16 & 17 of the page header are a power of 2 as reported by John Lehman */ - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_pagesize with %d\n", default_page_size); + CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_pagesize with %d", default_page_size); if((rc = sqlcipher_codec_ctx_set_pagesize(ctx, default_page_size)) != SQLITE_OK) return rc; /* establish settings for the KDF iterations and fast (HMAC) KDF iterations */ - CODEC_TRACE("sqlcipher_codec_ctx_init: setting default_kdf_iter\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: setting default_kdf_iter"); if((rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, default_kdf_iter)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: setting fast_kdf_iter\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: setting fast_kdf_iter"); if((rc = sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, FAST_PBKDF2_ITER)) != SQLITE_OK) return rc; /* set the default HMAC and KDF algorithms which will determine the reserve size */ - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_hmac_algorithm with %d\n", default_hmac_algorithm); + CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_hmac_algorithm with %d", default_hmac_algorithm); if((rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, default_hmac_algorithm)) != SQLITE_OK) return rc; /* Note that use_hmac is a special case that requires recalculation of page size so we call set_use_hmac to perform setup */ - CODEC_TRACE("sqlcipher_codec_ctx_init: setting use_hmac\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: setting use_hmac"); if((rc = sqlcipher_codec_ctx_set_use_hmac(ctx, default_flags & CIPHER_FLAG_HMAC)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_kdf_algorithm with %d\n", default_kdf_algorithm); + CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_kdf_algorithm with %d", default_kdf_algorithm); if((rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, default_kdf_algorithm)) != SQLITE_OK) return rc; /* setup the default plaintext header size */ - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_plaintext_header_size with %d\n", default_plaintext_header_sz); + CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_plaintext_header_size with %d", default_plaintext_header_sz); if((rc = sqlcipher_codec_ctx_set_plaintext_header_size(ctx, default_plaintext_header_sz)) != SQLITE_OK) return rc; /* initialize the read and write sub-contexts. this must happen after key_sz is established */ - CODEC_TRACE("sqlcipher_codec_ctx_init: initializing read_ctx\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: initializing read_ctx"); if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->read_ctx)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: initializing write_ctx\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: initializing write_ctx"); if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->write_ctx)) != SQLITE_OK) return rc; /* set the key material on one of the sub cipher contexts and sync them up */ - CODEC_TRACE("sqlcipher_codec_ctx_init: setting pass key\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: setting pass key"); if((rc = sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, 0)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: copying write_ctx to read_ctx\n"); + CODEC_TRACE("sqlcipher_codec_ctx_init: copying write_ctx to read_ctx"); if((rc = sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx)) != SQLITE_OK) return rc; return SQLITE_OK; @@ -1003,7 +1001,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi */ void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { codec_ctx *ctx = *iCtx; - CODEC_TRACE("codec_ctx_free: entered iCtx=%p\n", iCtx); + CODEC_TRACE("codec_ctx_free: entered iCtx=%p", iCtx); sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz); sqlcipher_free(ctx->hmac_kdf_salt, ctx->kdf_salt_sz); sqlcipher_free(ctx->buffer, ctx->page_sz); @@ -1076,12 +1074,12 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int hmac_out = out + size + ctx->iv_sz; out_start = out; /* note the original position of the output buffer pointer, as out will be rewritten during encryption */ - CODEC_TRACE("codec_cipher:entered pgno=%d, mode=%d, size=%d\n", pgno, mode, size); + CODEC_TRACE("codec_cipher:entered pgno=%d, mode=%d, size=%d", pgno, mode, size); CODEC_HEXDUMP("codec_cipher: input page data", in, page_sz); /* the key size should never be zero. If it is, error out. */ if(ctx->key_sz == 0) { - CODEC_TRACE("codec_cipher: error possible context corruption, key_sz is zero for pgno=%d\n", pgno); + CODEC_TRACE("codec_cipher: error possible context corruption, key_sz is zero for pgno=%d", pgno); goto error; } @@ -1094,38 +1092,38 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT) && !ctx->skip_read_hmac) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, in, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - CODEC_TRACE("codec_cipher: hmac operation on decrypt failed for pgno=%d\n", pgno); + CODEC_TRACE("codec_cipher: hmac operation on decrypt failed for pgno=%d", pgno); goto error; } - CODEC_TRACE("codec_cipher: comparing hmac on in=%p out=%p hmac_sz=%d\n", hmac_in, hmac_out, ctx->hmac_sz); + CODEC_TRACE("codec_cipher: comparing hmac on in=%p out=%p hmac_sz=%d", hmac_in, hmac_out, ctx->hmac_sz); if(sqlcipher_memcmp(hmac_in, hmac_out, ctx->hmac_sz) != 0) { /* the hmac check failed */ if(sqlcipher_ismemset(in, 0, page_sz) == 0) { /* first check if the entire contents of the page is zeros. If so, this page resulted from a short read (i.e. sqlite attempted to pull a page after the end of the file. these short read failures must be ignored for autovaccum mode to work so wipe the output buffer and return SQLITE_OK to skip the decryption step. */ - CODEC_TRACE("codec_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK\n", pgno); + CODEC_TRACE("codec_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK", pgno); sqlcipher_memset(out, 0, page_sz); return SQLITE_OK; } else { /* if the page memory is not all zeros, it means the there was data and a hmac on the page. since the check failed, the page was either tampered with or corrupted. wipe the output buffer, and return SQLITE_ERROR to the caller */ - CODEC_TRACE("codec_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR\n", pgno); + CODEC_TRACE("codec_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR", pgno); goto error; } } } if(ctx->provider->cipher(ctx->provider_ctx, mode, c_ctx->key, ctx->key_sz, iv_out, in, size, out) != SQLITE_OK) { - CODEC_TRACE("codec_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR\n", mode, pgno); + CODEC_TRACE("codec_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR", mode, pgno); goto error; }; if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, out_start, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - CODEC_TRACE("codec_cipher: hmac operation on encrypt failed for pgno=%d\n", pgno); + CODEC_TRACE("codec_cipher: hmac operation on encrypt failed for pgno=%d", pgno); goto error; }; } @@ -1157,7 +1155,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { int rc; CODEC_TRACE("cipher_ctx_key_derive: entered c_ctx->pass=%p, c_ctx->pass_sz=%d \ ctx->kdf_salt=%p ctx->kdf_salt_sz=%d ctx->kdf_iter=%d \ - ctx->hmac_kdf_salt=%p, ctx->fast_kdf_iter=%d ctx->key_sz=%d\n", + ctx->hmac_kdf_salt=%p, ctx->fast_kdf_iter=%d ctx->key_sz=%d", c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, ctx->hmac_kdf_salt, ctx->fast_kdf_iter, ctx->key_sz); @@ -1172,15 +1170,15 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if (c_ctx->pass_sz == ((ctx->key_sz * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, ctx->key_sz * 2)) { int n = c_ctx->pass_sz - 3; /* adjust for leading x' and tailing ' */ const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex\n"); + CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, n, c_ctx->key); } else if (c_ctx->pass_sz == (((ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, (ctx->key_sz + ctx->kdf_salt_sz) * 2)) { const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex\n"); + CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, (ctx->key_sz * 2), c_ctx->key); cipher_hex2bin(z + (ctx->key_sz * 2), (ctx->kdf_salt_sz * 2), ctx->kdf_salt); } else { - CODEC_TRACE("cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations\n", ctx->kdf_iter); + CODEC_TRACE("cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, ctx->key_sz, c_ctx->key) != SQLITE_OK) return SQLITE_ERROR; @@ -1205,7 +1203,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { ctx->hmac_kdf_salt[i] ^= hmac_salt_mask; } - CODEC_TRACE("cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations\n", + CODEC_TRACE("cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations", ctx->fast_kdf_iter); @@ -1355,7 +1353,7 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *col sqlcipher_memset(ctx->buffer, 0, ctx->page_sz); sqlcipher_memset(hmac_out, 0, ctx->hmac_sz); if(sqlite3OsRead(fd, ctx->buffer, read_sz, offset) != SQLITE_OK) { - result = sqlite3_mprintf("error reading %d bytes from file page %d at offset %d\n", read_sz, page, offset); + result = sqlite3_mprintf("error reading %d bytes from file page %d at offset %d", read_sz, page, offset); sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, result, P4_DYNAMIC); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); } else if(sqlcipher_page_hmac(ctx, ctx->read_ctx, page, ctx->buffer, payload_sz, hmac_out) != SQLITE_OK) { @@ -1406,7 +1404,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* Version 4 - current, no upgrade required, so exit immediately */ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version, &journal_mode); if(rc == SQLITE_OK){ - CODEC_TRACE("No upgrade required - exiting\n"); + CODEC_TRACE("No upgrade required - exiting"); goto cleanup; } @@ -1414,7 +1412,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { pragma_compat = sqlite3_mprintf("PRAGMA cipher_compatibility = %d;", i); rc = sqlcipher_check_connection(db_filename, pass, pass_sz, pragma_compat, &user_version, &journal_mode); if(rc == SQLITE_OK) { - CODEC_TRACE("Version %d format found\n", i); + CODEC_TRACE("Version %d format found", i); goto migrate; } if(pragma_compat) sqlcipher_free(pragma_compat, sqlite3Strlen30(pragma_compat)); @@ -1422,7 +1420,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { } /* if we exit the loop normally we failed to determine the version, this is an error */ - CODEC_TRACE("Upgrade format not determined\n"); + CODEC_TRACE("Upgrade format not determined"); goto handle_error; migrate: @@ -1439,46 +1437,46 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { rc = sqlite3_exec(db, pragma_compat, NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("set compatibility mode failed, error code %d\n", rc); + CODEC_TRACE("set compatibility mode failed, error code %d", rc); goto handle_error; } /* force journal mode to DELETE, we will set it back later if different */ rc = sqlite3_exec(db, "PRAGMA journal_mode = delete;", NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("force journal mode DELETE failed, error code %d\n", rc); + CODEC_TRACE("force journal mode DELETE failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, attach_command, NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("attach failed, error code %d\n", rc); + CODEC_TRACE("attach failed, error code %d", rc); goto handle_error; } rc = sqlite3_key_v2(db, "migrate", pass, pass_sz); if(rc != SQLITE_OK){ - CODEC_TRACE("keying attached database failed, error code %d\n", rc); + CODEC_TRACE("keying attached database failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, "SELECT sqlcipher_export('migrate');", NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("sqlcipher_export failed, error code %d\n", rc); + CODEC_TRACE("sqlcipher_export failed, error code %d", rc); goto handle_error; } #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_MIGRATE) > 0) { rc = SQLITE_ERROR; - CODEC_TRACE("simulated migrate failure, error code %d\n", rc); + CODEC_TRACE("simulated migrate failure, error code %d", rc); goto handle_error; } #endif rc = sqlite3_exec(db, set_user_version, NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("set user version failed, error code %d\n", rc); + CODEC_TRACE("set user version failed, error code %d", rc); goto handle_error; } @@ -1499,7 +1497,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* unset the BTS_PAGESIZE_FIXED flag to avoid SQLITE_READONLY */ pDest->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; rc = sqlite3BtreeSetPageSize(pDest, default_page_size, nRes, 0); - CODEC_TRACE("set btree page size to %d res %d rc %d\n", default_page_size, nRes, rc); + CODEC_TRACE("set btree page size to %d res %d rc %d", default_page_size, nRes, rc); if( rc!=SQLITE_OK ) goto handle_error; sqlite3CodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz); @@ -1512,7 +1510,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { sqlite3OsClose(destfile); #if defined(_WIN32) || defined(SQLITE_OS_WINRT) - CODEC_TRACE("performing windows MoveFileExA\n"); + CODEC_TRACE("performing windows MoveFileExA"); w_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) db_filename, -1, NULL, 0); w_db_filename = sqlcipher_malloc(w_db_filename_sz * sizeof(wchar_t)); @@ -1525,50 +1523,50 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { if(!MoveFileExW(w_migrated_db_filename, w_db_filename, MOVEFILE_REPLACE_EXISTING)) { CODEC_TRACE("move error"); rc = SQLITE_ERROR; - CODEC_TRACE("error occurred while renaming %d\n", rc); + CODEC_TRACE("error occurred while renaming %d", rc); goto handle_error; } #else - CODEC_TRACE("performing POSIX rename\n"); + CODEC_TRACE("performing POSIX rename"); if ((rc = rename(migrated_db_filename, db_filename)) != 0) { - CODEC_TRACE("error occurred while renaming %d\n", rc); + CODEC_TRACE("error occurred while renaming %d", rc); goto handle_error; } #endif - CODEC_TRACE("renamed migration database %s to main database %s: %d\n", migrated_db_filename, db_filename, rc); + CODEC_TRACE("renamed migration database %s to main database %s: %d", migrated_db_filename, db_filename, rc); rc = sqlite3OsOpen(db->pVfs, migrated_db_filename, srcfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - CODEC_TRACE("reopened migration database: %d\n", rc); + CODEC_TRACE("reopened migration database: %d", rc); if( rc!=SQLITE_OK ) goto handle_error; rc = sqlite3OsOpen(db->pVfs, db_filename, destfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - CODEC_TRACE("reopened main database: %d\n", rc); + CODEC_TRACE("reopened main database: %d", rc); if( rc!=SQLITE_OK ) goto handle_error; sqlite3pager_reset(pDest->pBt->pPager); - CODEC_TRACE("reset pager\n"); + CODEC_TRACE("reset pager"); rc = sqlite3_exec(db, "DETACH DATABASE migrate;", NULL, NULL, NULL); - CODEC_TRACE("DETACH DATABASE called %d\n", rc); + CODEC_TRACE("DETACH DATABASE called %d", rc); if(rc != SQLITE_OK) goto cleanup; sqlite3ResetAllSchemasOfConnection(db); - CODEC_TRACE("reset all schemas\n"); + CODEC_TRACE("reset all schemas"); set_journal_mode = sqlite3_mprintf("PRAGMA journal_mode = %s;", journal_mode); rc = sqlite3_exec(db, set_journal_mode, NULL, NULL, NULL); - CODEC_TRACE("%s: %d\n", set_journal_mode, rc); + CODEC_TRACE("%s: %d", set_journal_mode, rc); if( rc!=SQLITE_OK ) goto handle_error; goto cleanup; handle_error: - CODEC_TRACE("An error occurred attempting to migrate the database - last error %d\n", rc); + CODEC_TRACE("An error occurred attempting to migrate the database - last error %d", rc); cleanup: if(migrated_db_filename) { int del_rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0); - CODEC_TRACE("deleted migration database: %d\n", del_rc); + CODEC_TRACE("deleted migration database: %d", del_rc); } if(pass) sqlcipher_free(pass, pass_sz); @@ -1596,7 +1594,7 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz int buffer_sz = n / 2; unsigned char *random; const unsigned char *z = (const unsigned char *)zRight + 2; /* adjust lead offset of x' */ - CODEC_TRACE("sqlcipher_codec_add_random: using raw random blob from hex\n"); + CODEC_TRACE("sqlcipher_codec_add_random: using raw random blob from hex"); random = sqlcipher_malloc(buffer_sz); memset(random, 0, buffer_sz); cipher_hex2bin(z, n, random); @@ -1666,6 +1664,7 @@ void sqlcipher_trace(unsigned int tag, const char *message, ...) { strftime(buffer, 20, "%Y-%m-%d %H:%M:%S", &tt); fprintf((FILE*)sqlcipher_trace_file, "%s.%03d: ", buffer, ms); vfprintf((FILE*)sqlcipher_trace_file, message, params); + fprintf((FILE*)sqlcipher_trace_file, "\n"); } #ifdef __ANDROID__ if(sqlcipher_trace_logcat) { @@ -1705,7 +1704,7 @@ int sqlcipher_set_trace(const char *destination){ if((sqlcipher_trace_file = fopen(destination, "a")) == 0) return SQLITE_ERROR; #endif } - sqlcipher_trace(SQLCIPHER_TRACE_CORE, "sqlcipher_set_trace: set trace to %s\n", destination); + sqlcipher_trace(SQLCIPHER_TRACE_CORE, "sqlcipher_set_trace: set trace to %s", destination); return SQLITE_OK; #endif } diff --git a/src/crypto_libtomcrypt.c b/src/crypto_libtomcrypt.c index dba94abbb..87dd59b6e 100644 --- a/src/crypto_libtomcrypt.c +++ b/src/crypto_libtomcrypt.c @@ -48,9 +48,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { int block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ; const unsigned char * data = (const unsigned char *)buffer; - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); while(data_to_read > 0){ rc = fortuna_add_entropy(data, block_sz, &prng); @@ -64,9 +64,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { } fortuna_ready(&prng); - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); return rc; } @@ -74,9 +74,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { static int sqlcipher_ltc_activate(void *ctx) { unsigned char random_buffer[FORTUNA_MAX_SZ]; - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ); if(ltc_init == 0) { @@ -102,17 +102,17 @@ static int sqlcipher_ltc_activate(void *ctx) { } sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ); - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } static int sqlcipher_ltc_deactivate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); ltc_ref_count--; if(ltc_ref_count == 0){ @@ -120,9 +120,9 @@ static int sqlcipher_ltc_deactivate(void *ctx) { sqlcipher_memset((void *)&prng, 0, sizeof(prng)); } - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -136,15 +136,15 @@ static const char* sqlcipher_ltc_get_provider_version(void *ctx) { } static int sqlcipher_ltc_random(void *ctx, void *buffer, int length) { - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); fortuna_read(buffer, length, &prng); - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); return SQLITE_OK; } diff --git a/src/crypto_nss.c b/src/crypto_nss.c index ad29fc729..68cd90b5f 100644 --- a/src/crypto_nss.c +++ b/src/crypto_nss.c @@ -44,25 +44,25 @@ int sqlcipher_nss_setup(sqlcipher_provider *p); static int sqlcipher_nss_activate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); if (nss_init_context == NULL) { nss_init_context = NSS_InitContext("", "", "", "", NULL, NSS_INIT_READONLY | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT); } nss_init_count++; - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } static int sqlcipher_nss_deactivate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); nss_init_count--; if (nss_init_count == 0 && nss_init_context != NULL) { @@ -70,9 +70,9 @@ static int sqlcipher_nss_deactivate(void *ctx) { nss_init_context = NULL; } - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index 482dbfd68..a67a09138 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -67,15 +67,15 @@ static void HMAC_CTX_free(HMAC_CTX *ctx) static int sqlcipher_openssl_add_random(void *ctx, void *buffer, int length) { #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif RAND_add(buffer, length, 0); #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif return SQLITE_OK; } @@ -94,9 +94,9 @@ static int sqlcipher_openssl_activate(void *ctx) { but only if it hasn't been initalized outside of SQLCipher by this program e.g. on startup */ - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); #ifdef SQLCIPHER_FIPS if(!FIPS_mode()){ @@ -115,9 +115,9 @@ static int sqlcipher_openssl_activate(void *ctx) { #endif openssl_init_count++; - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -125,15 +125,15 @@ static int sqlcipher_openssl_activate(void *ctx) { freeing the EVP structures on the final deactivation to ensure that OpenSSL memory is cleaned up */ static int sqlcipher_openssl_deactivate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); openssl_init_count--; - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -155,15 +155,15 @@ static int sqlcipher_openssl_random (void *ctx, void *buffer, int length) { but a more proper solution is that applications setup platform-appropriate thread saftey in openssl externally */ #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif rc = RAND_bytes((unsigned char *)buffer, length); #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND\n"); + CODEC_TRACE_MUTEX("sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif return (rc == 1) ? SQLITE_OK : SQLITE_ERROR; } From 051b6d2631d08d2d8b2b18cf064e68026c865e42 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 Dec 2021 13:55:19 -0500 Subject: [PATCH 034/340] add logcat option to PRAGMA cipher_profile --- src/crypto_impl.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 4ac8adacd..f280cea13 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1608,8 +1608,14 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz #if !defined(SQLITE_OMIT_TRACE) static int sqlcipher_profile_callback(unsigned int trace, void *file, void *stmt, void *run_time){ FILE *f = (FILE*) file; + char *fmt = "Elapsed time:%.3f ms - %s\n"; double elapsed = (*((sqlite3_uint64*)run_time))/1000000.0; - if(f) fprintf(f, "Elapsed time:%.3f ms - %s\n", elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); +#ifdef __ANDROID__ + if(f == NULL) { + __android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", fmt, elapsed, sqlite3_sql((sqlite3_stmt*)stmt); + } +#endif + if(f) fprintf(f, fmt, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); return SQLITE_OK; } #endif @@ -1618,21 +1624,25 @@ int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ #if defined(SQLITE_OMIT_TRACE) return SQLITE_ERROR; #else - FILE *f; - if(sqlite3_stricmp(destination, "stdout") == 0){ - f = stdout; - }else if(sqlite3_stricmp(destination, "stderr") == 0){ - f = stderr; - }else if(sqlite3_stricmp(destination, "off") == 0){ - f = 0; - }else{ + FILE *f = NULL; + if(sqlite3_stricmp(destination, "off") == 0){ + sqlite3_trace_v2(db, 0, NULL, NULL); /* disable tracing */ + } else { + if(sqlite3_stricmp(destination, "stdout") == 0){ + f = stdout; + }else if(sqlite3_stricmp(destination, "stderr") == 0){ + f = stderr; + }else if(sqlite3_stricmp(destination, "logcat") == 0){ + f = NULL; /* file pointer will be NULL indicating logcat on android */ + }else{ #if !defined(SQLCIPHER_PROFILE_USE_FOPEN) && (defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT)) - if(fopen_s(&f, destination, "a") != 0) return SQLITE_ERROR; + if(fopen_s(&f, destination, "a") != 0) return SQLITE_ERROR; #else - if((f = fopen(destination, "a")) == 0) return SQLITE_ERROR; + if((f = fopen(destination, "a")) == 0) return SQLITE_ERROR; #endif + } + sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, sqlcipher_profile_callback, f); } - sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, sqlcipher_profile_callback, f); return SQLITE_OK; #endif } From 1e9c5815bf0aa324770863562dd122a904ef6a98 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 4 Jan 2022 14:45:46 -0500 Subject: [PATCH 035/340] redesigned logging implementation with standard levels --- src/crypto.c | 121 +++++++++-------- src/crypto.h | 53 ++------ src/crypto_impl.c | 276 ++++++++++++++++++++------------------- src/crypto_libtomcrypt.c | 32 ++--- src/crypto_nss.c | 16 +-- src/crypto_openssl.c | 32 ++--- 6 files changed, 253 insertions(+), 277 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 33cebd0f2..fefff47d5 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -53,11 +53,11 @@ static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ct page_sz = sqlcipher_codec_ctx_get_pagesize(ctx); reserve_sz = sqlcipher_codec_ctx_get_reservesize(ctx); - CODEC_TRACE("codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d", page_sz, reserve_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d", page_sz, reserve_sz); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entering database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entered database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: entered database mutex %p", db->mutex); db->nextPagesize = page_sz; /* before forcing the page size we need to unset the BTS_PAGESIZE_FIXED flag, else @@ -65,18 +65,18 @@ static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ct pDb->pBt->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; rc = sqlite3BtreeSetPageSize(pDb->pBt, page_sz, reserve_sz, 0); - CODEC_TRACE("codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d", rc); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: leaving database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: left database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: left database mutex %p", db->mutex); return rc; } static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("codec_set_pass_key: entered db=%p nDb=%d zKey=%p nKey=%d for_ctx=%d", db, nDb, zKey, nKey, for_ctx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_set_pass_key: entered db=%p nDb=%d zKey=%p nKey=%d for_ctx=%d", db, nDb, zKey, nKey, for_ctx); if(pDb->pBt) { codec_ctx *ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); @@ -94,7 +94,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); } - CODEC_TRACE("sqlcipher_codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); #ifdef SQLCIPHER_EXT if( sqlite3_stricmp(zLeft, "cipher_license")==0 && zRight ){ @@ -191,7 +191,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef char *migrate_status = sqlite3_mprintf("%d", status); codec_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); if(status != SQLITE_OK) { - CODEC_TRACE("sqlcipher_codec_pragma: error occurred during cipher_migrate: %d", status); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_pragma: error occurred during cipher_migrate: %d", status); sqlcipher_codec_ctx_set_error(ctx, status); } } @@ -682,19 +682,19 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check"); } } else - if( sqlite3_stricmp(zLeft, "cipher_trace_filter")==0 && zRight){ - unsigned int filter = 0; - if(sqlite3_strlike("%CORE%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_CORE; - if(sqlite3_strlike("%MEMORY%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_MEMORY; - if(sqlite3_strlike("%MUTEX%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_MUTEX; - if(sqlite3_strlike("%PROVIDER%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_PROVIDER; - if(sqlite3_strlike("%ALL%", zRight, '\'')==0) filter |= SQLCIPHER_TRACE_ALL; - sqlcipher_set_trace_filter(filter); - codec_vdbe_return_string(pParse, "cipher_trace_filter", sqlite3_mprintf("%u", filter), P4_DYNAMIC); + if( sqlite3_stricmp(zLeft, "cipher_log_level")==0 && zRight){ + unsigned int level = SQLCIPHER_LOG_NONE; + if(sqlite3_stricmp(zRight, "ERROR")==0) level = SQLCIPHER_LOG_ERROR; + else if(sqlite3_stricmp(zRight, "WARN" )==0) level = SQLCIPHER_LOG_WARN; + else if(sqlite3_stricmp(zRight, "INFO" )==0) level = SQLCIPHER_LOG_INFO; + else if(sqlite3_stricmp(zRight, "DEBUG")==0) level = SQLCIPHER_LOG_DEBUG; + else if(sqlite3_stricmp(zRight, "TRACE")==0) level = SQLCIPHER_LOG_TRACE; + sqlcipher_set_log_level(level); + codec_vdbe_return_string(pParse, "cipher_log_level", sqlite3_mprintf("%u", level), P4_DYNAMIC); } else - if( sqlite3_stricmp(zLeft, "cipher_trace")== 0 && zRight ){ - char *profile_status = sqlite3_mprintf("%d", sqlcipher_set_trace(zRight)); - codec_vdbe_return_string(pParse, "cipher_trace", profile_status, P4_DYNAMIC); + if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ + char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); + codec_vdbe_return_string(pParse, "cipher_log", status, P4_DYNAMIC); }else { return 0; } @@ -726,7 +726,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { int plaintext_header_sz = sqlcipher_codec_ctx_get_plaintext_header_size(ctx); int cctx = CIPHER_READ_CTX; - CODEC_TRACE("sqlite3Codec: entered pgno=%d, mode=%d, page_sz=%d", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3Codec: entered pgno=%d, mode=%d, page_sz=%d", pgno, mode, page_sz); #ifdef SQLCIPHER_EXT if(sqlcipher_license_check(ctx) != SQLITE_OK) return NULL; @@ -734,7 +734,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { /* call to derive keys if not present yet */ if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) { - CODEC_TRACE("sqlite3Codec: error occurred during key derivation: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error occurred during key derivation: %d", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -743,7 +743,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { PRAGMA. We can't set the error state on the pager at that point because the pager may not be open yet. However, this is a fatal error state, so abort the codec */ if(plaintext_header_sz < 0) { - CODEC_TRACE("sqlite3Codec: error invalid plaintext_header_sz: %d", plaintext_header_sz); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error invalid plaintext_header_sz: %d", plaintext_header_sz); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); return NULL; } @@ -752,7 +752,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { offset = plaintext_header_sz ? plaintext_header_sz : FILE_HEADER_SZ; - CODEC_TRACE("sqlite3Codec: switch mode=%d offset=%d", mode, offset); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3Codec: switch mode=%d offset=%d", mode, offset); switch(mode) { case CODEC_READ_OP: /* decrypt */ if(pgno == 1) /* copy initial part of file header or SQLite magic to buffer */ @@ -762,13 +762,13 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; - fprintf(stderr, "simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { /* failure to decrypt a page is considered a permanent error and will render the pager unusable in order to prevent inconsistent data being loaded into page cache */ - CODEC_TRACE("sqlite3Codec: error decrypting page data: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error decrypting page %d data: %d", pgno, rc); sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); } @@ -784,7 +784,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { void *kdf_salt = NULL; /* retrieve the kdf salt */ if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &kdf_salt)) != SQLITE_OK) { - CODEC_TRACE("sqlite3Codec: error retrieving salt: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error retrieving salt: %d", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -794,13 +794,13 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; - fprintf(stderr, "simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { /* failure to encrypt a page is considered a permanent error and will render the pager unusable in order to prevent corrupted pages from being written to the main databased when using WAL */ - CODEC_TRACE("sqlite3Codec: error encrypting page data: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error encrypting page %d data: %d", pgno, rc); sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; @@ -809,7 +809,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { break; default: - CODEC_TRACE("sqlite3Codec: error unsupported codec mode %d", mode); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error unsupported codec mode %d", mode); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); /* unsupported mode, set error */ return pData; break; @@ -826,8 +826,7 @@ static void sqlite3FreeCodecArg(void *pCodecArg) { int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("sqlite3CodecAttach: entered db=%p, nDb=%d zKey=%p, nKey=%d", db, nDb, zKey, nKey); - + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecAttach: entered db=%p, nDb=%d zKey=%p, nKey=%d", db, nDb, zKey, nKey); if(nKey && zKey && pDb->pBt) { int rc; @@ -838,12 +837,12 @@ int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { /* check if the sqlite3_file is open, and if not force handle to NULL */ if((fd = sqlite3PagerFile(pPager))->pMethods == 0) fd = NULL; - CODEC_TRACE("sqlite3CodecAttach: calling sqlcipher_activate()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecAttach: calling sqlcipher_activate()"); sqlcipher_activate(); /* perform internal initialization for sqlcipher */ - CODEC_TRACE_MUTEX("sqlite3CodecAttach: entering database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3CodecAttach: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - CODEC_TRACE_MUTEX("sqlite3CodecAttach: entered database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3CodecAttach: entered database mutex %p", db->mutex); #ifdef SQLCIPHER_EXT if((rc = sqlite3_set_authorizer(db, sqlcipher_license_authorizer, db)) != SQLITE_OK) { @@ -853,44 +852,44 @@ int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { #endif /* point the internal codec argument against the contet to be prepared */ - CODEC_TRACE("sqlite3CodecAttach: calling sqlcipher_codec_ctx_init()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecAttach: calling sqlcipher_codec_ctx_init()"); rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, zKey, nKey); if(rc != SQLITE_OK) { /* initialization failed, do not attach potentially corrupted context */ - CODEC_TRACE("sqlite3CodecAttach: context initialization failed with rc=%d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3CodecAttach: context initialization failed forcing error state with rc=%d", rc); /* force an error at the pager level, such that even the upstream caller ignores the return code the pager will be in an error state and will process no further operations */ sqlite3pager_error(pPager, rc); pDb->pBt->pBt->db->errCode = rc; - CODEC_TRACE_MUTEX("sqlite3CodecAttach: leaving database mutex %p (early return on rc=%d)", db->mutex, rc); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3CodecAttach: leaving database mutex %p (early return on rc=%d)", db->mutex, rc); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("sqlite3CodecAttach: left database mutex %p (early return on rc=%d)", db->mutex, rc); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3CodecAttach: left database mutex %p (early return on rc=%d)", db->mutex, rc); return rc; } - CODEC_TRACE("sqlite3CodecAttach: calling sqlite3PagerSetCodec()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecAttach: calling sqlite3PagerSetCodec()"); sqlite3PagerSetCodec(sqlite3BtreePager(pDb->pBt), sqlite3Codec, NULL, sqlite3FreeCodecArg, (void *) ctx); - CODEC_TRACE("sqlite3CodecAttach: calling codec_set_btree_to_codec_pagesize()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecAttach: calling codec_set_btree_to_codec_pagesize()"); codec_set_btree_to_codec_pagesize(db, pDb, ctx); /* force secure delete. This has the benefit of wiping internal data when deleted and also ensures that all pages are written to disk (i.e. not skipped by sqlite3PagerDontWrite optimizations) */ - CODEC_TRACE("sqlite3CodecAttach: calling sqlite3BtreeSecureDelete()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecAttach: calling sqlite3BtreeSecureDelete()"); sqlite3BtreeSecureDelete(pDb->pBt, 1); /* if fd is null, then this is an in-memory database and we dont' want to overwrite the AutoVacuum settings if not null, then set to the default */ if(fd != NULL) { - CODEC_TRACE("sqlite3CodecAttach: calling sqlite3BtreeSetAutoVacuum()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecAttach: calling sqlite3BtreeSetAutoVacuum()"); sqlite3BtreeSetAutoVacuum(pDb->pBt, SQLITE_DEFAULT_AUTOVACUUM); } - CODEC_TRACE_MUTEX("sqlite3CodecAttach: leaving database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3CodecAttach: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("sqlite3CodecAttach: left database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3CodecAttach: left database mutex %p", db->mutex); } return SQLITE_OK; } @@ -914,12 +913,12 @@ void sqlite3_activate_see(const char* in) { } int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_key entered: db=%p pKey=%p nKey=%d", db, pKey, nKey); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_key entered: db=%p pKey=%p nKey=%d", db, pKey, nKey); return sqlite3_key_v2(db, "main", pKey, nKey); } int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_key_v2: entered db=%p zDb=%s pKey=%p nKey=%d", db, zDb, pKey, nKey); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_key_v2: entered db=%p zDb=%s pKey=%p nKey=%d", db, zDb, pKey, nKey); /* attach key if db and pKey are not null and nKey is > 0 */ if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); @@ -929,7 +928,7 @@ int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { } int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_rekey entered: db=%p pKey=%p nKey=%d", db, pKey, nKey); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey entered: db=%p pKey=%p nKey=%d", db, pKey, nKey); return sqlite3_rekey_v2(db, "main", pKey, nKey); } @@ -944,11 +943,11 @@ int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { ** 3. If there is a key present, re-encrypt the database with the new key */ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - CODEC_TRACE("sqlite3_rekey_v2: entered db=%p zDb=%s pKey=%p, nKey=%d", db, zDb, pKey, nKey); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: entered db=%p zDb=%s pKey=%p, nKey=%d", db, zDb, pKey, nKey); if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); struct Db *pDb = &db->aDb[db_index]; - CODEC_TRACE("sqlite3_rekey_v2: database pDb=%p db_index:%d", pDb, db_index); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: database pDb=%p db_index:%d", pDb, db_index); if(pDb->pBt) { codec_ctx *ctx; int rc, page_count; @@ -960,13 +959,13 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(ctx == NULL) { /* there was no codec attached to this database, so this should do nothing! */ - CODEC_TRACE("sqlite3_rekey_v2: no codec attached to db, exiting"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no codec attached to db, exiting"); return SQLITE_OK; } - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: entering database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: entered database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: entered database mutex %p", db->mutex); codec_set_pass_key(db, db_index, pKey, nKey, CIPHER_WRITE_CTX); @@ -986,27 +985,27 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(rc == SQLITE_OK) { sqlite3PagerUnref(page); } else { - CODEC_TRACE("sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); } } else { - CODEC_TRACE("sqlite3_rekey_v2: error %d occurred getting page %d", rc, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: error %d occurred getting page %d", rc, pgno); } } } /* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */ if(rc == SQLITE_OK) { - CODEC_TRACE("sqlite3_rekey_v2: committing"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: committing"); rc = sqlite3BtreeCommit(pDb->pBt); sqlcipher_codec_key_copy(ctx, CIPHER_WRITE_CTX); } else { - CODEC_TRACE("sqlite3_rekey_v2: rollback"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: rollback"); sqlite3BtreeRollback(pDb->pBt, SQLITE_ABORT_ROLLBACK, 0); } - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: leaving database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - CODEC_TRACE_MUTEX("sqlite3_rekey_v2: left database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: left database mutex %p", db->mutex); } return SQLITE_OK; } @@ -1015,7 +1014,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { void sqlite3CodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) { struct Db *pDb = &db->aDb[nDb]; - CODEC_TRACE("sqlite3CodecGetKey: entered db=%p, nDb=%d", db, nDb); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3CodecGetKey: entered db=%p, nDb=%d", db, nDb); if( pDb->pBt ) { codec_ctx *ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); diff --git a/src/crypto.h b/src/crypto.h index 862ac229a..15d947bf3 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -303,49 +303,16 @@ int sqlcipher_find_db_index(sqlite3 *db, const char *zDb); int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); -int sqlcipher_set_trace(const char *destination); -int sqlcipher_set_trace_filter(unsigned int filter); -void sqlcipher_trace(unsigned int tag, const char *message, ...); - -#define SQLCIPHER_TRACE_CORE 0x01 -#define SQLCIPHER_TRACE_PROVIDER 0x02 -#define SQLCIPHER_TRACE_MEMORY 0x04 -#define SQLCIPHER_TRACE_MUTEX 0x08 -#define SQLCIPHER_TRACE_ALL 0xffffffff - -#ifdef CODEC_DEBUG -#ifdef __ANDROID__ -#define CODEC_TRACE(...) {__android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", __VA_ARGS__);} -#else -#define CODEC_TRACE(...) {fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); fflush(stderr);} -#endif -#else -#ifdef SQLCIPHER_OMIT_TRACE -#define CODEC_TRACE(...) -#else -#define CODEC_TRACE(...) {sqlcipher_trace(SQLCIPHER_TRACE_CORE, __VA_ARGS__);}; -#endif -#endif - -#ifdef CODEC_DEBUG_MUTEX -#define CODEC_TRACE_MUTEX(...) CODEC_TRACE(__VA_ARGS__) -#else -#ifdef SQLCIPHER_OMIT_TRACE -#define CODEC_TRACE_MUTEX(...) -#else -#define CODEC_TRACE_MUTEX(...) {sqlcipher_trace(SQLCIPHER_TRACE_MUTEX, __VA_ARGS__);}; -#endif -#endif - -#ifdef CODEC_DEBUG_MEMORY -#define CODEC_TRACE_MEMORY(...) CODEC_TRACE(__VA_ARGS__) -#else -#ifdef SQLCIPHER_OMIT_TRACE -#define CODEC_TRACE_MEMORY(...) -#else -#define CODEC_TRACE_MEMORY(...) {sqlcipher_trace(SQLCIPHER_TRACE_MEMORY, __VA_ARGS__);}; -#endif -#endif +int sqlcipher_set_log(const char *destination); +int sqlcipher_set_log_level(unsigned int level); +void sqlcipher_log(unsigned int tag, const char *message, ...); + +#define SQLCIPHER_LOG_NONE 0x00 +#define SQLCIPHER_LOG_ERROR 0x01 +#define SQLCIPHER_LOG_WARN 0x02 +#define SQLCIPHER_LOG_INFO 0x04 +#define SQLCIPHER_LOG_DEBUG 0x08 +#define SQLCIPHER_LOG_TRACE 0xffffffff #ifdef CODEC_DEBUG_PAGEDATA #define CODEC_HEXDUMP(DESC,BUFFER,LEN) \ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index f280cea13..f68192531 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -94,9 +94,9 @@ static volatile sqlite3_mem_methods default_mem_methods; static sqlcipher_provider *default_provider = NULL; static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; -static volatile FILE* sqlcipher_trace_file = NULL; -static volatile int sqlcipher_trace_logcat = 0; -static volatile int sqlcipher_trace_filter = 0; +static volatile FILE* sqlcipher_log_file = NULL; +static volatile int sqlcipher_log_logcat = 0; +static volatile int sqlcipher_log_level = SQLCIPHER_LOG_NONE; sqlite3_mutex* sqlcipher_mutex(int mutex) { if(mutex < 0 || mutex >= SQLCIPHER_MUTEX_COUNT) return NULL; @@ -112,7 +112,7 @@ static void sqlcipher_mem_shutdown(void *pAppData) { static void *sqlcipher_mem_malloc(int n) { void *ptr = default_mem_methods.xMalloc(n); if(mem_security_on) { - CODEC_TRACE_MEMORY("sqlcipher_mem_malloc: calling sqlcipher_mlock(%p,%d)", ptr, n); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_malloc: calling sqlcipher_mlock(%p,%d)", ptr, n); sqlcipher_mlock(ptr, n); if(!mem_security_activated) mem_security_activated = 1; } @@ -125,7 +125,7 @@ static void sqlcipher_mem_free(void *p) { int sz; if(mem_security_on) { sz = sqlcipher_mem_size(p); - CODEC_TRACE_MEMORY("sqlcipher_mem_free: calling sqlcipher_memset(%p,0,%d) and sqlcipher_munlock(%p, %d)", p, sz, p, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_free: calling sqlcipher_memset(%p,0,%d) and sqlcipher_munlock(%p, %d)", p, sz, p, sz); sqlcipher_memset(p, 0, sz); sqlcipher_munlock(p, sz); if(!mem_security_activated) mem_security_activated = 1; @@ -182,9 +182,9 @@ void sqlcipher_init_memmethods() { } int sqlcipher_register_provider(sqlcipher_provider *p) { - CODEC_TRACE_MUTEX("sqlcipher_register_provider: entering SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_register_provider: entered SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: entered SQLCIPHER_MUTEX_PROVIDER"); if(default_provider != NULL && default_provider != p) { /* only free the current registerd provider if it has been initialized @@ -193,9 +193,9 @@ int sqlcipher_register_provider(sqlcipher_provider *p) { sqlcipher_free(default_provider, sizeof(sqlcipher_provider)); } default_provider = p; - CODEC_TRACE_MUTEX("sqlcipher_register_provider: leaving SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_register_provider: left SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: left SQLCIPHER_MUTEX_PROVIDER"); return SQLITE_OK; } @@ -208,9 +208,9 @@ sqlcipher_provider* sqlcipher_get_provider() { } void sqlcipher_activate() { - CODEC_TRACE_MUTEX("sqlcipher_activate: entering static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_activate: entered static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: entered static master mutex"); /* allocate new mutexes */ if(sqlcipher_activate_count == 0) { @@ -240,41 +240,41 @@ void sqlcipher_activate() { #else #error "NO DEFAULT SQLCIPHER CRYPTO PROVIDER DEFINED" #endif - CODEC_TRACE("sqlcipher_activate: calling sqlcipher_register_provider(%p)", p); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_activate: calling sqlcipher_register_provider(%p)", p); #ifdef SQLCIPHER_EXT sqlcipher_ext_provider_setup(p); #endif sqlcipher_register_provider(p); - CODEC_TRACE("sqlcipher_activate: called sqlcipher_register_provider(%p)",p); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_activate: called sqlcipher_register_provider(%p)",p); } sqlcipher_activate_count++; /* increment activation count */ - CODEC_TRACE_MUTEX("sqlcipher_activate: leaving static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: leaving static master mutex"); sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_activate: left static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: left static master mutex"); } void sqlcipher_deactivate() { - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entered static master mutex"); sqlcipher_activate_count--; /* if no connections are using sqlcipher, cleanup globals */ if(sqlcipher_activate_count < 1) { - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entered SQLCIPHER_MUTEX_PROVIDER"); if(default_provider != NULL) { sqlcipher_free(default_provider, sizeof(sqlcipher_provider)); default_provider = NULL; } - CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER"); #ifdef SQLCIPHER_EXT sqlcipher_ext_provider_destroy(); @@ -290,9 +290,9 @@ void sqlcipher_deactivate() { sqlcipher_activate_count = 0; /* reset activation count */ } - CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: leaving static master mutex"); sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - CODEC_TRACE_MUTEX("sqlcipher_deactivate: left static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: left static master mutex"); } /* constant time memset using volitile to avoid having the memset @@ -305,7 +305,7 @@ void* sqlcipher_memset(void *v, unsigned char value, u64 len) { if (v == NULL) return v; - CODEC_TRACE_MEMORY("sqlcipher_memset: setting %p[0-%llu]=%d)", a, len, value); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_memset: setting %p[0-%llu]=%d)", a, len, value); for(i = 0; i < len; i++) { a[i] = value; } @@ -349,18 +349,18 @@ void sqlcipher_mlock(void *ptr, u64 sz) { if(ptr == NULL || sz == 0) return; - CODEC_TRACE_MEMORY("sqlcipher_mem_lock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_lock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); rc = mlock(ptr - offset, sz + offset); if(rc!=0) { - CODEC_TRACE_MEMORY("sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) int rc; - CODEC_TRACE("sqlcipher_mem_lock: calling VirtualLock(%p,%d)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_mem_lock: calling VirtualLock(%p,%d)", ptr, sz); rc = VirtualLock(ptr, sz); if(rc==0) { - CODEC_TRACE("sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -376,18 +376,18 @@ void sqlcipher_munlock(void *ptr, u64 sz) { if(ptr == NULL || sz == 0) return; - CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_unlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); rc = munlock(ptr - offset, sz + offset); if(rc!=0) { - CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) int rc; - CODEC_TRACE("sqlcipher_mem_lock: calling VirtualUnlock(%p,%d)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_mem_lock: calling VirtualUnlock(%p,%d)", ptr, sz); rc = VirtualUnlock(ptr, sz); if(!rc) { - CODEC_TRACE("sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -403,7 +403,7 @@ void sqlcipher_munlock(void *ptr, u64 sz) { * memory segment so it can be paged */ void sqlcipher_free(void *ptr, u64 sz) { - CODEC_TRACE_MEMORY("sqlcipher_free: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_free: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); sqlcipher_memset(ptr, 0, sz); sqlcipher_munlock(ptr, sz); sqlite3_free(ptr); @@ -416,9 +416,9 @@ void sqlcipher_free(void *ptr, u64 sz) { */ void* sqlcipher_malloc(u64 sz) { void *ptr; - CODEC_TRACE_MEMORY("sqlcipher_malloc: calling sqlite3Malloc(%llu)", sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_malloc: calling sqlite3Malloc(%llu)", sz); ptr = sqlite3Malloc(sz); - CODEC_TRACE_MEMORY("sqlcipher_malloc: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_malloc: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); sqlcipher_memset(ptr, 0, sz); sqlcipher_mlock(ptr, sz); return ptr; @@ -442,15 +442,15 @@ char* sqlcipher_version() { */ static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) { cipher_ctx *c_ctx; - CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating context"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_init: allocating context"); *iCtx = (cipher_ctx *) sqlcipher_malloc(sizeof(cipher_ctx)); c_ctx = *iCtx; if(c_ctx == NULL) return SQLITE_NOMEM; - CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating key"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_init: allocating key"); c_ctx->key = (unsigned char *) sqlcipher_malloc(ctx->key_sz); - CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating hmac_key"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_init: allocating hmac_key"); c_ctx->hmac_key = (unsigned char *) sqlcipher_malloc(ctx->key_sz); if(c_ctx->key == NULL) return SQLITE_NOMEM; @@ -464,7 +464,7 @@ static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) { */ static void sqlcipher_cipher_ctx_free(codec_ctx* ctx, cipher_ctx **iCtx) { cipher_ctx *c_ctx = *iCtx; - CODEC_TRACE("cipher_ctx_free: entered iCtx=%p", iCtx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_free: entered iCtx=%p", iCtx); sqlcipher_free(c_ctx->key, ctx->key_sz); sqlcipher_free(c_ctx->hmac_key, ctx->key_sz); sqlcipher_free(c_ctx->pass, c_ctx->pass_sz); @@ -485,7 +485,7 @@ static int sqlcipher_codec_ctx_reserve_setup(codec_ctx *ctx) { reserve = ((reserve % ctx->block_sz) == 0) ? reserve : ((reserve / ctx->block_sz) + 1) * ctx->block_sz; - CODEC_TRACE("sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d", + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d", base_reserve, ctx->block_sz, ctx->hmac_sz, reserve); ctx->reserve_sz = reserve; @@ -509,7 +509,7 @@ static int sqlcipher_cipher_ctx_cmp(cipher_ctx *c1, cipher_ctx *c2) { c1->pass_sz) )); - CODEC_TRACE("sqlcipher_cipher_ctx_cmp: entered \ + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_cmp: entered \ c1=%p c2=%p \ c1->pass_sz=%d c2->pass_sz=%d \ c1->pass=%p c2->pass=%p \ @@ -543,7 +543,7 @@ static int sqlcipher_cipher_ctx_copy(codec_ctx *ctx, cipher_ctx *target, cipher_ void *key = target->key; void *hmac_key = target->hmac_key; - CODEC_TRACE("sqlcipher_cipher_ctx_copy: entered target=%p, source=%p", target, source); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_copy: entered target=%p, source=%p", target, source); sqlcipher_free(target->pass, target->pass_sz); sqlcipher_free(target->keyspec, ctx->keyspec_sz); memcpy(target, source, sizeof(cipher_ctx)); @@ -788,7 +788,7 @@ int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag) { } void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) { - CODEC_TRACE("sqlcipher_codec_ctx_set_error: ctx=%p, error=%d", ctx, error); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_error: ctx=%p, error=%d", ctx, error); sqlite3pager_error(ctx->pBt->pBt->pPager, error); ctx->pBt->pBt->db->errCode = error; } @@ -809,9 +809,9 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { } /* read salt from header, if present, otherwise generate a new random salt */ - CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: obtaining salt"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init_kdf_salt: obtaining salt"); if(fd == NULL || fd->pMethods == 0 || sqlite3OsRead(fd, ctx->kdf_salt, ctx->kdf_salt_sz, 0) != SQLITE_OK) { - CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random"); if(ctx->provider->random(ctx->provider_ctx, ctx->kdf_salt, ctx->kdf_salt_sz) != SQLITE_OK) return SQLITE_ERROR; } ctx->need_kdf_salt = 0; @@ -843,7 +843,7 @@ void sqlcipher_codec_get_keyspec(codec_ctx *ctx, void **zKey, int *nKey) { int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { if(!((size != 0) && ((size & (size - 1)) == 0)) || size < 512 || size > 65536) { - CODEC_TRACE(("cipher_page_size not a power of 2 and between 512 and 65536 inclusive")); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_page_size not a power of 2 and between 512 and 65536 inclusive"); return SQLITE_ERROR; } /* attempt to free the existing page buffer */ @@ -888,7 +888,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi int rc; codec_ctx *ctx; - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating context"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating context"); *iCtx = sqlcipher_malloc(sizeof(codec_ctx)); ctx = *iCtx; @@ -901,7 +901,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi directly off the database file. This is the salt for the key derivation function. If we get a short read allocate a new random salt value */ - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating kdf_salt"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating kdf_salt"); ctx->kdf_salt_sz = FILE_HEADER_SZ; ctx->kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz); if(ctx->kdf_salt == NULL) return SQLITE_NOMEM; @@ -909,7 +909,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi /* allocate space for separate hmac salt data. We want the HMAC derivation salt to be different than the encryption key derivation salt */ - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating hmac_kdf_salt"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating hmac_kdf_salt"); ctx->hmac_kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz); if(ctx->hmac_kdf_salt == NULL) return SQLITE_NOMEM; @@ -920,22 +920,22 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi ctx->need_kdf_salt = 1; /* setup the crypto provider */ - CODEC_TRACE("sqlcipher_codec_ctx_init: allocating provider"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating provider"); ctx->provider = (sqlcipher_provider *) sqlcipher_malloc(sizeof(sqlcipher_provider)); if(ctx->provider == NULL) return SQLITE_NOMEM; /* make a copy of the provider to be used for the duration of the context */ - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entering SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entered SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: entered SQLCIPHER_MUTEX_PROVIDER"); memcpy(ctx->provider, default_provider, sizeof(sqlcipher_provider)); - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: leaving SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: left SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: left SQLCIPHER_MUTEX_PROVIDER"); - CODEC_TRACE("sqlcipher_codec_ctx_init: calling provider ctx_init"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: calling provider ctx_init"); if((rc = ctx->provider->ctx_init(&ctx->provider_ctx)) != SQLITE_OK) return rc; ctx->key_sz = ctx->provider->get_key_sz(ctx->provider_ctx); @@ -952,44 +952,44 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi in encrypted and thus sqlite can't effectively determine the pagesize. this causes an issue in cases where bytes 16 & 17 of the page header are a power of 2 as reported by John Lehman */ - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_pagesize with %d", default_page_size); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_pagesize with %d", default_page_size); if((rc = sqlcipher_codec_ctx_set_pagesize(ctx, default_page_size)) != SQLITE_OK) return rc; /* establish settings for the KDF iterations and fast (HMAC) KDF iterations */ - CODEC_TRACE("sqlcipher_codec_ctx_init: setting default_kdf_iter"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: setting default_kdf_iter"); if((rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, default_kdf_iter)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: setting fast_kdf_iter"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: setting fast_kdf_iter"); if((rc = sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, FAST_PBKDF2_ITER)) != SQLITE_OK) return rc; /* set the default HMAC and KDF algorithms which will determine the reserve size */ - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_hmac_algorithm with %d", default_hmac_algorithm); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_hmac_algorithm with %d", default_hmac_algorithm); if((rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, default_hmac_algorithm)) != SQLITE_OK) return rc; /* Note that use_hmac is a special case that requires recalculation of page size so we call set_use_hmac to perform setup */ - CODEC_TRACE("sqlcipher_codec_ctx_init: setting use_hmac"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: setting use_hmac"); if((rc = sqlcipher_codec_ctx_set_use_hmac(ctx, default_flags & CIPHER_FLAG_HMAC)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_kdf_algorithm with %d", default_kdf_algorithm); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_kdf_algorithm with %d", default_kdf_algorithm); if((rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, default_kdf_algorithm)) != SQLITE_OK) return rc; /* setup the default plaintext header size */ - CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_plaintext_header_size with %d", default_plaintext_header_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_plaintext_header_size with %d", default_plaintext_header_sz); if((rc = sqlcipher_codec_ctx_set_plaintext_header_size(ctx, default_plaintext_header_sz)) != SQLITE_OK) return rc; /* initialize the read and write sub-contexts. this must happen after key_sz is established */ - CODEC_TRACE("sqlcipher_codec_ctx_init: initializing read_ctx"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: initializing read_ctx"); if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->read_ctx)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: initializing write_ctx"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: initializing write_ctx"); if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->write_ctx)) != SQLITE_OK) return rc; /* set the key material on one of the sub cipher contexts and sync them up */ - CODEC_TRACE("sqlcipher_codec_ctx_init: setting pass key"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: setting pass key"); if((rc = sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, 0)) != SQLITE_OK) return rc; - CODEC_TRACE("sqlcipher_codec_ctx_init: copying write_ctx to read_ctx"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: copying write_ctx to read_ctx"); if((rc = sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx)) != SQLITE_OK) return rc; return SQLITE_OK; @@ -1001,7 +1001,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi */ void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { codec_ctx *ctx = *iCtx; - CODEC_TRACE("codec_ctx_free: entered iCtx=%p", iCtx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_ctx_free: entered iCtx=%p", iCtx); sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz); sqlcipher_free(ctx->hmac_kdf_salt, ctx->kdf_salt_sz); sqlcipher_free(ctx->buffer, ctx->page_sz); @@ -1074,12 +1074,12 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int hmac_out = out + size + ctx->iv_sz; out_start = out; /* note the original position of the output buffer pointer, as out will be rewritten during encryption */ - CODEC_TRACE("codec_cipher:entered pgno=%d, mode=%d, size=%d", pgno, mode, size); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_cipher:entered pgno=%d, mode=%d, size=%d", pgno, mode, size); CODEC_HEXDUMP("codec_cipher: input page data", in, page_sz); /* the key size should never be zero. If it is, error out. */ if(ctx->key_sz == 0) { - CODEC_TRACE("codec_cipher: error possible context corruption, key_sz is zero for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: error possible context corruption, key_sz is zero for pgno=%d", pgno); goto error; } @@ -1092,38 +1092,38 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT) && !ctx->skip_read_hmac) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, in, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - CODEC_TRACE("codec_cipher: hmac operation on decrypt failed for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: hmac operation on decrypt failed for pgno=%d", pgno); goto error; } - CODEC_TRACE("codec_cipher: comparing hmac on in=%p out=%p hmac_sz=%d", hmac_in, hmac_out, ctx->hmac_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_cipher: comparing hmac on in=%p out=%p hmac_sz=%d", hmac_in, hmac_out, ctx->hmac_sz); if(sqlcipher_memcmp(hmac_in, hmac_out, ctx->hmac_sz) != 0) { /* the hmac check failed */ if(sqlcipher_ismemset(in, 0, page_sz) == 0) { /* first check if the entire contents of the page is zeros. If so, this page resulted from a short read (i.e. sqlite attempted to pull a page after the end of the file. these short read failures must be ignored for autovaccum mode to work so wipe the output buffer and return SQLITE_OK to skip the decryption step. */ - CODEC_TRACE("codec_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK", pgno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "codec_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK", pgno); sqlcipher_memset(out, 0, page_sz); return SQLITE_OK; } else { /* if the page memory is not all zeros, it means the there was data and a hmac on the page. since the check failed, the page was either tampered with or corrupted. wipe the output buffer, and return SQLITE_ERROR to the caller */ - CODEC_TRACE("codec_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR", pgno); goto error; } } } if(ctx->provider->cipher(ctx->provider_ctx, mode, c_ctx->key, ctx->key_sz, iv_out, in, size, out) != SQLITE_OK) { - CODEC_TRACE("codec_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR", mode, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR", mode, pgno); goto error; }; if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, out_start, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - CODEC_TRACE("codec_cipher: hmac operation on encrypt failed for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: hmac operation on encrypt failed for pgno=%d", pgno); goto error; }; } @@ -1153,7 +1153,7 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int */ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { int rc; - CODEC_TRACE("cipher_ctx_key_derive: entered c_ctx->pass=%p, c_ctx->pass_sz=%d \ + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: entered c_ctx->pass=%p, c_ctx->pass_sz=%d \ ctx->kdf_salt=%p ctx->kdf_salt_sz=%d ctx->kdf_iter=%d \ ctx->hmac_kdf_salt=%p, ctx->fast_kdf_iter=%d ctx->key_sz=%d", c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, @@ -1170,15 +1170,15 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if (c_ctx->pass_sz == ((ctx->key_sz * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, ctx->key_sz * 2)) { int n = c_ctx->pass_sz - 3; /* adjust for leading x' and tailing ' */ const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, n, c_ctx->key); } else if (c_ctx->pass_sz == (((ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, (ctx->key_sz + ctx->kdf_salt_sz) * 2)) { const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, (ctx->key_sz * 2), c_ctx->key); cipher_hex2bin(z + (ctx->key_sz * 2), (ctx->kdf_salt_sz * 2), ctx->kdf_salt); } else { - CODEC_TRACE("cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, ctx->key_sz, c_ctx->key) != SQLITE_OK) return SQLITE_ERROR; @@ -1203,7 +1203,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { ctx->hmac_kdf_salt[i] ^= hmac_salt_mask; } - CODEC_TRACE("cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations", + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations", ctx->fast_kdf_iter); @@ -1404,7 +1404,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* Version 4 - current, no upgrade required, so exit immediately */ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version, &journal_mode); if(rc == SQLITE_OK){ - CODEC_TRACE("No upgrade required - exiting"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "No upgrade required - exiting"); goto cleanup; } @@ -1412,7 +1412,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { pragma_compat = sqlite3_mprintf("PRAGMA cipher_compatibility = %d;", i); rc = sqlcipher_check_connection(db_filename, pass, pass_sz, pragma_compat, &user_version, &journal_mode); if(rc == SQLITE_OK) { - CODEC_TRACE("Version %d format found", i); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "Version %d format found", i); goto migrate; } if(pragma_compat) sqlcipher_free(pragma_compat, sqlite3Strlen30(pragma_compat)); @@ -1420,7 +1420,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { } /* if we exit the loop normally we failed to determine the version, this is an error */ - CODEC_TRACE("Upgrade format not determined"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "Upgrade format not determined"); goto handle_error; migrate: @@ -1437,55 +1437,55 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { rc = sqlite3_exec(db, pragma_compat, NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("set compatibility mode failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "set compatibility mode failed, error code %d", rc); goto handle_error; } /* force journal mode to DELETE, we will set it back later if different */ rc = sqlite3_exec(db, "PRAGMA journal_mode = delete;", NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("force journal mode DELETE failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "force journal mode DELETE failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, attach_command, NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("attach failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "attach failed, error code %d", rc); goto handle_error; } rc = sqlite3_key_v2(db, "migrate", pass, pass_sz); if(rc != SQLITE_OK){ - CODEC_TRACE("keying attached database failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "keying attached database failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, "SELECT sqlcipher_export('migrate');", NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("sqlcipher_export failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_export failed, error code %d", rc); goto handle_error; } #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_MIGRATE) > 0) { rc = SQLITE_ERROR; - CODEC_TRACE("simulated migrate failure, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "simulated migrate failure, error code %d", rc); goto handle_error; } #endif rc = sqlite3_exec(db, set_user_version, NULL, NULL, NULL); if(rc != SQLITE_OK){ - CODEC_TRACE("set user version failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "set user version failed, error code %d", rc); goto handle_error; } if( !db->autoCommit ){ - CODEC_TRACE("cannot migrate from within a transaction"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "cannot migrate from within a transaction"); goto handle_error; } if( db->nVdbeActive>1 ){ - CODEC_TRACE("cannot migrate - SQL statements in progress"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "cannot migrate - SQL statements in progress"); goto handle_error; } @@ -1497,7 +1497,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* unset the BTS_PAGESIZE_FIXED flag to avoid SQLITE_READONLY */ pDest->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; rc = sqlite3BtreeSetPageSize(pDest, default_page_size, nRes, 0); - CODEC_TRACE("set btree page size to %d res %d rc %d", default_page_size, nRes, rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "set btree page size to %d res %d rc %d", default_page_size, nRes, rc); if( rc!=SQLITE_OK ) goto handle_error; sqlite3CodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz); @@ -1510,7 +1510,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { sqlite3OsClose(destfile); #if defined(_WIN32) || defined(SQLITE_OS_WINRT) - CODEC_TRACE("performing windows MoveFileExA"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "performing windows MoveFileExA"); w_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) db_filename, -1, NULL, 0); w_db_filename = sqlcipher_malloc(w_db_filename_sz * sizeof(wchar_t)); @@ -1521,52 +1521,51 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { w_migrated_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) migrated_db_filename, -1, (const LPWSTR) w_migrated_db_filename, w_migrated_db_filename_sz); if(!MoveFileExW(w_migrated_db_filename, w_db_filename, MOVEFILE_REPLACE_EXISTING)) { - CODEC_TRACE("move error"); rc = SQLITE_ERROR; - CODEC_TRACE("error occurred while renaming %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "error occurred while renaming %d", rc); goto handle_error; } #else - CODEC_TRACE("performing POSIX rename"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "performing POSIX rename"); if ((rc = rename(migrated_db_filename, db_filename)) != 0) { - CODEC_TRACE("error occurred while renaming %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "error occurred while renaming %d", rc); goto handle_error; } -#endif - CODEC_TRACE("renamed migration database %s to main database %s: %d", migrated_db_filename, db_filename, rc); +#endif + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "renamed migration database %s to main database %s: %d", migrated_db_filename, db_filename, rc); rc = sqlite3OsOpen(db->pVfs, migrated_db_filename, srcfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - CODEC_TRACE("reopened migration database: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reopened migration database: %d", rc); if( rc!=SQLITE_OK ) goto handle_error; rc = sqlite3OsOpen(db->pVfs, db_filename, destfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - CODEC_TRACE("reopened main database: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reopened main database: %d", rc); if( rc!=SQLITE_OK ) goto handle_error; sqlite3pager_reset(pDest->pBt->pPager); - CODEC_TRACE("reset pager"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reset pager"); rc = sqlite3_exec(db, "DETACH DATABASE migrate;", NULL, NULL, NULL); - CODEC_TRACE("DETACH DATABASE called %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "DETACH DATABASE called %d", rc); if(rc != SQLITE_OK) goto cleanup; sqlite3ResetAllSchemasOfConnection(db); - CODEC_TRACE("reset all schemas"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reset all schemas"); set_journal_mode = sqlite3_mprintf("PRAGMA journal_mode = %s;", journal_mode); rc = sqlite3_exec(db, set_journal_mode, NULL, NULL, NULL); - CODEC_TRACE("%s: %d", set_journal_mode, rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "%s: %d", set_journal_mode, rc); if( rc!=SQLITE_OK ) goto handle_error; goto cleanup; handle_error: - CODEC_TRACE("An error occurred attempting to migrate the database - last error %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "An error occurred attempting to migrate the database - last error %d", rc); cleanup: if(migrated_db_filename) { int del_rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0); - CODEC_TRACE("deleted migration database: %d", del_rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "deleted migration database: %d", del_rc); } if(pass) sqlcipher_free(pass, pass_sz); @@ -1594,7 +1593,7 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz int buffer_sz = n / 2; unsigned char *random; const unsigned char *z = (const unsigned char *)zRight + 2; /* adjust lead offset of x' */ - CODEC_TRACE("sqlcipher_codec_add_random: using raw random blob from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_add_random: using raw random blob from hex"); random = sqlcipher_malloc(buffer_sz); memset(random, 0, buffer_sz); cipher_hex2bin(z, n, random); @@ -1655,15 +1654,25 @@ const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) { return ctx->provider->get_provider_version(ctx->provider_ctx); } -#ifndef SQLCIPHER_OMIT_TRACE -void sqlcipher_trace(unsigned int tag, const char *message, ...) { +#ifndef SQLCIPHER_OMIT_LOG +void sqlcipher_log(unsigned int level, const char *message, ...) { va_list params; - if((sqlcipher_trace_filter & tag) == 0 || (sqlcipher_trace_logcat == 0 && sqlcipher_trace_file == NULL)) { + va_start(params, message); + +#ifdef CODEC_DEBUG +#ifdef __ANDROID__ + __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); +#else + vfprintf(stderr, message, params); + fprintf(stderr, "\n"); +#endif +#endif + + if(level > sqlcipher_log_level || (sqlcipher_log_logcat == 0 && sqlcipher_log_file == NULL)) { /* no log target or tag not in included filters */ - return; + goto end; } - va_start(params, message); - if(sqlcipher_trace_file != NULL){ + if(sqlcipher_log_file != NULL){ char buffer[20]; struct tm tt; struct timeval tv; @@ -1672,49 +1681,50 @@ void sqlcipher_trace(unsigned int tag, const char *message, ...) { ms = tv.tv_usec/1000.0; localtime_r(&tv.tv_sec, &tt); strftime(buffer, 20, "%Y-%m-%d %H:%M:%S", &tt); - fprintf((FILE*)sqlcipher_trace_file, "%s.%03d: ", buffer, ms); - vfprintf((FILE*)sqlcipher_trace_file, message, params); - fprintf((FILE*)sqlcipher_trace_file, "\n"); + fprintf((FILE*)sqlcipher_log_file, "%s.%03d: ", buffer, ms); + vfprintf((FILE*)sqlcipher_log_file, message, params); + fprintf((FILE*)sqlcipher_log_file, "\n"); } #ifdef __ANDROID__ - if(sqlcipher_trace_logcat) { + if(sqlcipher_log_logcat) { __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); } #endif +end: va_end(params); } #endif -int sqlcipher_set_trace_filter(unsigned int filter) { - sqlcipher_trace_filter = filter; +int sqlcipher_set_log_level(unsigned int level) { + sqlcipher_log_level = level; } -int sqlcipher_set_trace(const char *destination){ -#ifdef SQLCIPHER_OMIT_TRACE +int sqlcipher_set_log(const char *destination){ +#ifdef SQLCIPHER_OMIT_LOG return SQLITE_ERROR; #else /* close open trace file if it is not stdout or stderr, then reset trace settings */ - if(sqlcipher_trace_file != NULL && sqlcipher_trace_file != stdout && sqlcipher_trace_file != stderr) { - fclose((FILE*)sqlcipher_trace_file); + if(sqlcipher_log_file != NULL && sqlcipher_log_file != stdout && sqlcipher_log_file != stderr) { + fclose((FILE*)sqlcipher_log_file); } - sqlcipher_trace_file = NULL; - sqlcipher_trace_logcat = 0; + sqlcipher_log_file = NULL; + sqlcipher_log_logcat = 0; if(sqlite3_stricmp(destination, "logcat") == 0){ - sqlcipher_trace_logcat = 1; + sqlcipher_log_logcat = 1; } else if(sqlite3_stricmp(destination, "stdout") == 0){ - sqlcipher_trace_file = stdout; + sqlcipher_log_file = stdout; }else if(sqlite3_stricmp(destination, "stderr") == 0){ - sqlcipher_trace_file = stderr; + sqlcipher_log_file = stderr; }else if(sqlite3_stricmp(destination, "off") != 0){ #if !defined(SQLCIPHER_PROFILE_USE_FOPEN) && (defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT)) - if(fopen_s(&sqlcipher_trace_file, destination, "a") != 0) return SQLITE_ERROR; + if(fopen_s(&sqlcipher_log_file, destination, "a") != 0) return SQLITE_ERROR; #else - if((sqlcipher_trace_file = fopen(destination, "a")) == 0) return SQLITE_ERROR; + if((sqlcipher_log_file = fopen(destination, "a")) == 0) return SQLITE_ERROR; #endif } - sqlcipher_trace(SQLCIPHER_TRACE_CORE, "sqlcipher_set_trace: set trace to %s", destination); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_set_log: set log to %s", destination); return SQLITE_OK; #endif } diff --git a/src/crypto_libtomcrypt.c b/src/crypto_libtomcrypt.c index 87dd59b6e..03a068979 100644 --- a/src/crypto_libtomcrypt.c +++ b/src/crypto_libtomcrypt.c @@ -48,9 +48,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { int block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ; const unsigned char * data = (const unsigned char *)buffer; - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); while(data_to_read > 0){ rc = fortuna_add_entropy(data, block_sz, &prng); @@ -64,9 +64,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { } fortuna_ready(&prng); - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); return rc; } @@ -74,9 +74,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { static int sqlcipher_ltc_activate(void *ctx) { unsigned char random_buffer[FORTUNA_MAX_SZ]; - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ); if(ltc_init == 0) { @@ -102,17 +102,17 @@ static int sqlcipher_ltc_activate(void *ctx) { } sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ); - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } static int sqlcipher_ltc_deactivate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); ltc_ref_count--; if(ltc_ref_count == 0){ @@ -120,9 +120,9 @@ static int sqlcipher_ltc_deactivate(void *ctx) { sqlcipher_memset((void *)&prng, 0, sizeof(prng)); } - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -136,15 +136,15 @@ static const char* sqlcipher_ltc_get_provider_version(void *ctx) { } static int sqlcipher_ltc_random(void *ctx, void *buffer, int length) { - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); fortuna_read(buffer, length, &prng); - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); return SQLITE_OK; } diff --git a/src/crypto_nss.c b/src/crypto_nss.c index 68cd90b5f..5de302ca7 100644 --- a/src/crypto_nss.c +++ b/src/crypto_nss.c @@ -44,25 +44,25 @@ int sqlcipher_nss_setup(sqlcipher_provider *p); static int sqlcipher_nss_activate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); if (nss_init_context == NULL) { nss_init_context = NSS_InitContext("", "", "", "", NULL, NSS_INIT_READONLY | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT); } nss_init_count++; - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } static int sqlcipher_nss_deactivate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); nss_init_count--; if (nss_init_count == 0 && nss_init_context != NULL) { @@ -70,9 +70,9 @@ static int sqlcipher_nss_deactivate(void *ctx) { nss_init_context = NULL; } - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index a67a09138..40953514a 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -67,15 +67,15 @@ static void HMAC_CTX_free(HMAC_CTX *ctx) static int sqlcipher_openssl_add_random(void *ctx, void *buffer, int length) { #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif RAND_add(buffer, length, 0); #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif return SQLITE_OK; } @@ -94,9 +94,9 @@ static int sqlcipher_openssl_activate(void *ctx) { but only if it hasn't been initalized outside of SQLCipher by this program e.g. on startup */ - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); #ifdef SQLCIPHER_FIPS if(!FIPS_mode()){ @@ -115,9 +115,9 @@ static int sqlcipher_openssl_activate(void *ctx) { #endif openssl_init_count++; - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -125,15 +125,15 @@ static int sqlcipher_openssl_activate(void *ctx) { freeing the EVP structures on the final deactivation to ensure that OpenSSL memory is cleaned up */ static int sqlcipher_openssl_deactivate(void *ctx) { - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); openssl_init_count--; - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -155,15 +155,15 @@ static int sqlcipher_openssl_random (void *ctx, void *buffer, int length) { but a more proper solution is that applications setup platform-appropriate thread saftey in openssl externally */ #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif rc = RAND_bytes((unsigned char *)buffer, length); #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - CODEC_TRACE_MUTEX("sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif return (rc == 1) ? SQLITE_OK : SQLITE_ERROR; } From 58b3acc730eec3d6c65ff23913e2df345f36e6e6 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 4 Jan 2022 15:01:53 -0500 Subject: [PATCH 036/340] use EVP_MAC API with OpenSSL 3 --- src/crypto_openssl.c | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index 40953514a..df759cda4 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -169,8 +169,9 @@ static int sqlcipher_openssl_random (void *ctx, void *buffer, int length) { } static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out) { - unsigned int outlen; int rc = SQLITE_OK; +#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x30000000L) + unsigned int outlen; HMAC_CTX* hctx = NULL; if(in == NULL) goto error; @@ -197,12 +198,50 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ if(!HMAC_Update(hctx, in2, in2_sz)) goto error; } if(!HMAC_Final(hctx, out, &outlen)) goto error; - +#else + size_t outlen; + EVP_MAC *mac = NULL; + EVP_MAC_CTX *hctx = NULL; + OSSL_PARAM sha1[] = { { "digest", OSSL_PARAM_UTF8_STRING, "sha1", 4, 0 }, OSSL_PARAM_END }; + OSSL_PARAM sha256[] = { { "digest", OSSL_PARAM_UTF8_STRING, "sha256", 6, 0 }, OSSL_PARAM_END }; + OSSL_PARAM sha512[] = { { "digest", OSSL_PARAM_UTF8_STRING, "sha512", 6, 0 }, OSSL_PARAM_END }; + + if(in == NULL) goto error; + + mac = EVP_MAC_fetch(NULL, "HMAC", NULL); + hctx = EVP_MAC_CTX_new(mac); + if(hctx == NULL) goto error; + + switch(algorithm) { + case SQLCIPHER_HMAC_SHA1: + if(!EVP_MAC_init(hctx, hmac_key, key_sz, sha1)) goto error; + break; + case SQLCIPHER_HMAC_SHA256: + if(!EVP_MAC_init(hctx, hmac_key, key_sz, sha256)) goto error; + break; + case SQLCIPHER_HMAC_SHA512: + if(!EVP_MAC_init(hctx, hmac_key, key_sz, sha512)) goto error; + break; + default: + goto error; + } + + if(!EVP_MAC_update(hctx, in, in_sz)) goto error; + if(in2 != NULL) { + if(!EVP_MAC_update(hctx, in2, in2_sz)) goto error; + } + if(!EVP_MAC_final(hctx, NULL, &outlen, 0)) goto error; + if(!EVP_MAC_final(hctx, out, &outlen, outlen)) goto error; +#endif goto cleanup; error: rc = SQLITE_ERROR; cleanup: +#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x30000000L) if(hctx) HMAC_CTX_free(hctx); +#else + if(hctx) EVP_MAC_CTX_free(hctx); +#endif return rc; } From 1f3e3782adffc557ca49f620b299a727023b18cb Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 5 Jan 2022 17:10:00 -0500 Subject: [PATCH 037/340] significantly expand logging of error conditions --- src/crypto.c | 10 ++- src/crypto_impl.c | 64 ++++++++----- src/crypto_openssl.c | 207 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 217 insertions(+), 64 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index fefff47d5..de923b043 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -80,8 +80,14 @@ static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, if(pDb->pBt) { codec_ctx *ctx = (codec_ctx*) sqlite3PagerGetCodec(pDb->pBt->pBt->pPager); - if(ctx) return sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, for_ctx); + if(ctx) { + return sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, for_ctx); + } else { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_set_pass_key: error ocurred fetching codec from pager on db %d", nDb); + return SQLITE_ERROR; + } } + sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_set_pass_key: no btree present on db %d", nDb); return SQLITE_ERROR; } @@ -924,6 +930,7 @@ int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { int db_index = sqlcipher_find_db_index(db, zDb); return sqlite3CodecAttach(db, db_index, pKey, nKey); } + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_key_v2: no key provided"); return SQLITE_ERROR; } @@ -1009,6 +1016,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { } return SQLITE_OK; } + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no key provided"); return SQLITE_ERROR; } diff --git a/src/crypto_impl.c b/src/crypto_impl.c index f68192531..759a6203c 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -511,15 +511,9 @@ static int sqlcipher_cipher_ctx_cmp(cipher_ctx *c1, cipher_ctx *c2) { sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_cmp: entered \ c1=%p c2=%p \ - c1->pass_sz=%d c2->pass_sz=%d \ - c1->pass=%p c2->pass=%p \ - c1->pass=%s c2->pass=%s \ - sqlcipher_memcmp=%d \ + sqlcipher_memcmp(c1->pass, c2_pass)=%d \ are_equal=%d", c1, c2, - c1->pass_sz, c2->pass_sz, - c1->pass, c2->pass, - c1->pass, c2->pass, (c1->pass == NULL || c2->pass == NULL) ? -1 : sqlcipher_memcmp( (const unsigned char*)c1->pass, @@ -724,6 +718,7 @@ int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size) { return SQLITE_OK; } ctx->plaintext_header_sz = -1; + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_plaintext_header_size: attempt to set invalid plantext_header_size %d", size); return SQLITE_ERROR; } @@ -812,7 +807,10 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init_kdf_salt: obtaining salt"); if(fd == NULL || fd->pMethods == 0 || sqlite3OsRead(fd, ctx->kdf_salt, ctx->kdf_salt_sz, 0) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random"); - if(ctx->provider->random(ctx->provider_ctx, ctx->kdf_salt, ctx->kdf_salt_sz) != SQLITE_OK) return SQLITE_ERROR; + if(ctx->provider->random(ctx->provider_ctx, ctx->kdf_salt, ctx->kdf_salt_sz) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init_kdf_salt: error retrieving random bytes from provider"); + return SQLITE_ERROR; + } } ctx->need_kdf_salt = 0; return SQLITE_OK; @@ -824,6 +822,7 @@ int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int si ctx->need_kdf_salt = 0; return SQLITE_OK; } + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_kdf_salt: attempt to set salt of incorrect size %d", size); return SQLITE_ERROR; } @@ -1074,12 +1073,12 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int hmac_out = out + size + ctx->iv_sz; out_start = out; /* note the original position of the output buffer pointer, as out will be rewritten during encryption */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_cipher:entered pgno=%d, mode=%d, size=%d", pgno, mode, size); - CODEC_HEXDUMP("codec_cipher: input page data", in, page_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_page_cipher: entered pgno=%d, mode=%d, size=%d", pgno, mode, size); + CODEC_HEXDUMP("sqlcipher_page_cipher: input page data", in, page_sz); /* the key size should never be zero. If it is, error out. */ if(ctx->key_sz == 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: error possible context corruption, key_sz is zero for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: error possible context corruption, key_sz is zero for pgno=%d", pgno); goto error; } @@ -1092,43 +1091,43 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT) && !ctx->skip_read_hmac) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, in, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: hmac operation on decrypt failed for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac operation on decrypt failed for pgno=%d", pgno); goto error; } - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_cipher: comparing hmac on in=%p out=%p hmac_sz=%d", hmac_in, hmac_out, ctx->hmac_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_page_cipher: comparing hmac on in=%p out=%p hmac_sz=%d", hmac_in, hmac_out, ctx->hmac_sz); if(sqlcipher_memcmp(hmac_in, hmac_out, ctx->hmac_sz) != 0) { /* the hmac check failed */ if(sqlcipher_ismemset(in, 0, page_sz) == 0) { /* first check if the entire contents of the page is zeros. If so, this page resulted from a short read (i.e. sqlite attempted to pull a page after the end of the file. these short read failures must be ignored for autovaccum mode to work so wipe the output buffer and return SQLITE_OK to skip the decryption step. */ - sqlcipher_log(SQLCIPHER_LOG_INFO, "codec_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK", pgno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_page_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK", pgno); sqlcipher_memset(out, 0, page_sz); return SQLITE_OK; } else { /* if the page memory is not all zeros, it means the there was data and a hmac on the page. since the check failed, the page was either tampered with or corrupted. wipe the output buffer, and return SQLITE_ERROR to the caller */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR", pgno); goto error; } } } if(ctx->provider->cipher(ctx->provider_ctx, mode, c_ctx->key, ctx->key_sz, iv_out, in, size, out) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR", mode, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR", mode, pgno); goto error; }; if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, out_start, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_cipher: hmac operation on encrypt failed for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac operation on encrypt failed for pgno=%d", pgno); goto error; }; } - CODEC_HEXDUMP("codec_cipher: output page data", out_start, page_sz); + CODEC_HEXDUMP("sqlcipher_page_cipher: output page data", out_start, page_sz); return SQLITE_OK; error: @@ -1181,7 +1180,10 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, - ctx->key_sz, c_ctx->key) != SQLITE_OK) return SQLITE_ERROR; + ctx->key_sz, c_ctx->key) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_ctx_key_derive: error occurred from provider kdf generating encryption key"); + return SQLITE_ERROR; + } } /* set the context "keyspec" containing the hex-formatted key and salt to be used when attaching databases */ @@ -1209,27 +1211,40 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->key, ctx->key_sz, ctx->hmac_kdf_salt, ctx->kdf_salt_sz, ctx->fast_kdf_iter, - ctx->key_sz, c_ctx->hmac_key) != SQLITE_OK) return SQLITE_ERROR; + ctx->key_sz, c_ctx->hmac_key) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_ctx_key_derive: error occurred from provider kdf generating HMAC key"); + return SQLITE_ERROR; + } } c_ctx->derive_key = 0; return SQLITE_OK; - }; + } + sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_ctx_key_derive: key material is not present on the context for key derivation"); return SQLITE_ERROR; } int sqlcipher_codec_key_derive(codec_ctx *ctx) { /* derive key on first use if necessary */ if(ctx->read_ctx->derive_key) { - if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->read_ctx) != SQLITE_OK) return SQLITE_ERROR; + if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->read_ctx) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_key_derive: error occurred deriving read_ctx key"); + return SQLITE_ERROR; + } } if(ctx->write_ctx->derive_key) { if(sqlcipher_cipher_ctx_cmp(ctx->write_ctx, ctx->read_ctx) == 0) { /* the relevant parameters are the same, just copy read key */ - if(sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx) != SQLITE_OK) return SQLITE_ERROR; + if(sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_key_derive: error occurred copying read_ctx to write_ctx"); + return SQLITE_ERROR; + } } else { - if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->write_ctx) != SQLITE_OK) return SQLITE_ERROR; + if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->write_ctx) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_key_derive: error occurred deriving write_ctx key"); + return SQLITE_ERROR; + } } } @@ -1601,6 +1616,7 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz sqlcipher_free(random, buffer_sz); return rc; } + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_add_random: attemt to add random with invalid format"); return SQLITE_ERROR; } diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index df759cda4..d8f223619 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -42,6 +42,13 @@ static unsigned int openssl_init_count = 0; +static void sqlcipher_openssl_log_errors() { + unsigned long err = 0; + while((err = ERR_get_error()) != 0) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_log_errors: ERR_get_error() returned %lx: %s", err, ERR_error_string(err, NULL)); + } +} + #if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) static HMAC_CTX *HMAC_CTX_new(void) { @@ -82,7 +89,6 @@ static int sqlcipher_openssl_add_random(void *ctx, void *buffer, int length) { #define OPENSSL_CIPHER EVP_aes_256_cbc() - /* activate and initialize sqlcipher. Most importantly, this will automatically intialize OpenSSL's EVP system if it hasn't already be externally. Note that this function may be called multiple times as new codecs are intiialized. @@ -93,23 +99,21 @@ static int sqlcipher_openssl_activate(void *ctx) { /* initialize openssl and increment the internal init counter but only if it hasn't been initalized outside of SQLCipher by this program e.g. on startup */ + int rc = 0; sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); +#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) + ERR_load_crypto_strings(); +#endif + #ifdef SQLCIPHER_FIPS if(!FIPS_mode()){ - if(!FIPS_mode_set(1)){ - unsigned long err = 0; - ERR_load_crypto_strings(); -#ifdef __ANDROID__ - while((err = ERR_get_error()) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "sqlcipher","error: %lx. %s.", err, ERR_error_string(err, NULL)); - } -#else - ERR_print_errors_fp(stderr); -#endif + if(!(rc = FIPS_mode_set(1))){ + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_activate: FIPS_mode_set() returned %d", rc); + sqlcipher_openssl_log_errors(); } } #endif @@ -165,11 +169,16 @@ static int sqlcipher_openssl_random (void *ctx, void *buffer, int length) { sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif - return (rc == 1) ? SQLITE_OK : SQLITE_ERROR; + if(!rc) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_random: RAND_bytes() returned %d", rc); + sqlcipher_openssl_log_errors(); + return SQLITE_ERROR; + } + return SQLITE_OK; } static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out) { - int rc = SQLITE_OK; + int rc = 0; #if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x30000000L) unsigned int outlen; HMAC_CTX* hctx = NULL; @@ -177,27 +186,59 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ if(in == NULL) goto error; hctx = HMAC_CTX_new(); - if(hctx == NULL) goto error; + if(hctx == NULL) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_CTX_new() failed"); + sqlcipher_openssl_log_errors(); + goto error; + } switch(algorithm) { case SQLCIPHER_HMAC_SHA1: - if(!HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha1(), NULL)) goto error; + if(!(rc = HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha1(), NULL))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha1() returned %d", key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; case SQLCIPHER_HMAC_SHA256: - if(!HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha256(), NULL)) goto error; + if(!(rc = HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha256(), NULL))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha256() returned %d", key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; case SQLCIPHER_HMAC_SHA512: - if(!HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha512(), NULL)) goto error; + if(!(rc = HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha512(), NULL))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha512() returned %d", key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; default: + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: invalid algorithm %d", algorithm); goto error; } - if(!HMAC_Update(hctx, in, in_sz)) goto error; + if(!(rc = HMAC_Update(hctx, in, in_sz))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Update() on 1st input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); + sqlcipher_openssl_log_errors(); + goto error; + } + if(in2 != NULL) { - if(!HMAC_Update(hctx, in2, in2_sz)) goto error; + if(!(rc = HMAC_Update(hctx, in2, in2_sz))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Update() on 2nd input buffer of %d bytes using algorithm %d returned %d", in2_sz, algorithm, rc); + sqlcipher_openssl_log_errors(); + goto error; + } } - if(!HMAC_Final(hctx, out, &outlen)) goto error; + + if(!(rc = HMAC_Final(hctx, out, &outlen))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Final() using algorithm %d returned %d", algorithm, rc); + sqlcipher_openssl_log_errors(); + goto error; + } + #else size_t outlen; EVP_MAC *mac = NULL; @@ -209,30 +250,74 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ if(in == NULL) goto error; mac = EVP_MAC_fetch(NULL, "HMAC", NULL); + if(mac == NULL) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_fetch for HMAC failed"); + sqlcipher_openssl_log_errors(); + goto error; + } + hctx = EVP_MAC_CTX_new(mac); - if(hctx == NULL) goto error; + if(hctx == NULL) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_CTX_new() failed"); + sqlcipher_openssl_log_errors(); + goto error; + } switch(algorithm) { case SQLCIPHER_HMAC_SHA1: - if(!EVP_MAC_init(hctx, hmac_key, key_sz, sha1)) goto error; + if(!(rc = EVP_MAC_init(hctx, hmac_key, key_sz, sha1))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha1 returned %d", key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; case SQLCIPHER_HMAC_SHA256: - if(!EVP_MAC_init(hctx, hmac_key, key_sz, sha256)) goto error; + if(!(rc = EVP_MAC_init(hctx, hmac_key, key_sz, sha256))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha256 returned %d", key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; case SQLCIPHER_HMAC_SHA512: - if(!EVP_MAC_init(hctx, hmac_key, key_sz, sha512)) goto error; + if(!(rc = EVP_MAC_init(hctx, hmac_key, key_sz, sha512))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha512 returned %d", key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; default: + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: invalid algorithm %d", algorithm); goto error; } - if(!EVP_MAC_update(hctx, in, in_sz)) goto error; + if(!(rc = EVP_MAC_update(hctx, in, in_sz))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_update() on 1st input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); + sqlcipher_openssl_log_errors(); + goto error; + } + if(in2 != NULL) { - if(!EVP_MAC_update(hctx, in2, in2_sz)) goto error; + if(!(rc = EVP_MAC_update(hctx, in2, in2_sz))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_update() on 2nd input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); + sqlcipher_openssl_log_errors(); + goto error; + } + } + + if(!(rc = EVP_MAC_final(hctx, NULL, &outlen, 0))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: 1st EVP_MAC_final() for output length calculation using algorithm %d returned %d", algorithm, rc); + sqlcipher_openssl_log_errors(); + goto error; + } + + if(!(rc = EVP_MAC_final(hctx, out, &outlen, outlen))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: 2nd EVP_MAC_final() using algorithm %d returned %d", algorithm, rc); + sqlcipher_openssl_log_errors(); + goto error; } - if(!EVP_MAC_final(hctx, NULL, &outlen, 0)) goto error; - if(!EVP_MAC_final(hctx, out, &outlen, outlen)) goto error; + #endif + rc = SQLITE_OK; goto cleanup; error: rc = SQLITE_ERROR; @@ -246,22 +331,35 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ } static int sqlcipher_openssl_kdf(void *ctx, int algorithm, const unsigned char *pass, int pass_sz, unsigned char* salt, int salt_sz, int workfactor, int key_sz, unsigned char *key) { - int rc = SQLITE_OK; + int rc = 0; switch(algorithm) { case SQLCIPHER_HMAC_SHA1: - if(!PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha1(), key_sz, key)) goto error; + if(!(rc = PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha1(), key_sz, key))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha1() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; case SQLCIPHER_HMAC_SHA256: - if(!PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha256(), key_sz, key)) goto error; + if(!(rc = PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha256(), key_sz, key))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha256() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; case SQLCIPHER_HMAC_SHA512: - if(!PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha512(), key_sz, key)) goto error; + if(!(rc = PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha512(), key_sz, key))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha512() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); + sqlcipher_openssl_log_errors(); + goto error; + } break; default: return SQLITE_ERROR; } + rc = SQLITE_OK; goto cleanup; error: rc = SQLITE_ERROR; @@ -270,19 +368,50 @@ static int sqlcipher_openssl_kdf(void *ctx, int algorithm, const unsigned char * } static int sqlcipher_openssl_cipher(void *ctx, int mode, unsigned char *key, int key_sz, unsigned char *iv, unsigned char *in, int in_sz, unsigned char *out) { - int tmp_csz, csz, rc = SQLITE_OK; + int tmp_csz, csz, rc = 0; EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new(); - if(ectx == NULL) goto error; - if(!EVP_CipherInit_ex(ectx, OPENSSL_CIPHER, NULL, NULL, NULL, mode)) goto error; - if(!EVP_CIPHER_CTX_set_padding(ectx, 0)) goto error; /* no padding */ - if(!EVP_CipherInit_ex(ectx, NULL, NULL, key, iv, mode)) goto error; - if(!EVP_CipherUpdate(ectx, out, &tmp_csz, in, in_sz)) goto error; + if(ectx == NULL) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CIPHER_CTX_new failed"); + sqlcipher_openssl_log_errors(); + goto error; + } + + if(!(rc = EVP_CipherInit_ex(ectx, OPENSSL_CIPHER, NULL, NULL, NULL, mode))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherInit_ex for mode %d returned %d", mode, rc); + sqlcipher_openssl_log_errors(); + goto error; + } + + if(!(rc = EVP_CIPHER_CTX_set_padding(ectx, 0))) { /* no padding */ + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CIPHER_CTX_set_padding 0 returned %d", rc); + sqlcipher_openssl_log_errors(); + goto error; + } + + if(!(rc = EVP_CipherInit_ex(ectx, NULL, NULL, key, iv, mode))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherInit_ex for mode %d returned %d", mode, rc); + sqlcipher_openssl_log_errors(); + goto error; + } + + if(!(rc = EVP_CipherUpdate(ectx, out, &tmp_csz, in, in_sz))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherUpdate returned %d", rc); + sqlcipher_openssl_log_errors(); + goto error; + } + csz = tmp_csz; out += tmp_csz; - if(!EVP_CipherFinal_ex(ectx, out, &tmp_csz)) goto error; + if(!(rc = EVP_CipherFinal_ex(ectx, out, &tmp_csz))) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherFinal_ex returned %d", rc); + sqlcipher_openssl_log_errors(); + goto error; + } + csz += tmp_csz; assert(in_sz == csz); + rc = SQLITE_OK; goto cleanup; error: rc = SQLITE_ERROR; From 90401f041e4d351e3e9cae0ddcb1cfffcd71ae7e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 7 Jan 2022 12:16:23 -0500 Subject: [PATCH 038/340] Snapshot of upstream SQLite 3.37.2 --- Makefile.in | 13 +- Makefile.msc | 42 +- VERSION | 2 +- autoconf/tea/win/nmakehlp.c | 44 +- configure | 58 +- configure.ac | 11 + ext/expert/expert1.test | 4 +- ext/expert/sqlite3expert.c | 99 +- ext/fts3/fts3.c | 16 +- ext/fts3/fts3Int.h | 22 +- ext/fts3/fts3_aux.c | 6 +- ext/fts3/fts3_expr.c | 10 +- ext/fts3/fts3_snippet.c | 23 +- ext/fts3/fts3_write.c | 28 +- ext/fts5/fts5Int.h | 21 +- ext/fts5/fts5_buffer.c | 7 +- ext/fts5/fts5_config.c | 7 +- ext/fts5/fts5_expr.c | 27 +- ext/fts5/fts5_hash.c | 2 +- ext/fts5/fts5_index.c | 151 ++- ext/fts5/fts5_main.c | 3 +- ext/fts5/fts5_storage.c | 11 +- ext/fts5/fts5_vocab.c | 12 +- ext/fts5/test/fts5af.test | 8 +- ext/fts5/test/fts5corrupt3.test | 231 +++++ ext/fts5/test/fts5corrupt5.test | 798 ++++++++++++++++ ext/fts5/test/fts5corrupt6.test | 54 ++ ext/fts5/test/fts5detail.test | 4 + ext/fts5/test/fts5misc.test | 29 + ext/fts5/test/fts5vocab2.test | 49 + ext/fts5/tool/mkfts5c.tcl | 3 +- ext/misc/carray.c | 32 +- ext/misc/carray.h | 29 +- ext/misc/fileio.c | 29 + ext/misc/ieee754.c | 6 +- ext/misc/json1.c | 111 ++- ext/misc/regexp.c | 4 +- ext/misc/series.c | 20 +- ext/misc/zipfile.c | 8 +- ext/rbu/rbu10.test | 2 +- ext/rbu/rbuexlock.test | 207 ++++ ext/rbu/rbuvacuum2.test | 6 +- ext/rbu/sqlite3rbu.c | 106 ++- ext/repair/checkfreelist.c | 15 +- ext/rtree/geopoly.c | 9 +- ext/rtree/rtree.c | 148 +-- ext/rtree/rtree1.test | 5 +- ext/rtree/rtree9.test | 2 +- ext/rtree/rtreeA.test | 12 +- ext/rtree/rtreedoc.test | 1583 +++++++++++++++++++++++++++++++ ext/rtree/rtreedoc2.test | 346 +++++++ ext/rtree/rtreedoc3.test | 292 ++++++ ext/rtree/test_rtreedoc.c | 348 +++++++ ext/session/session1.test | 20 + ext/session/session6.test | 1 + ext/session/sessionat.test | 7 +- ext/session/sqlite3session.c | 58 +- main.mk | 7 +- manifest | 513 +++++----- manifest.uuid | 2 +- src/alter.c | 206 ++-- src/analyze.c | 59 +- src/attach.c | 7 +- src/auth.c | 4 +- src/bitvec.c | 2 +- src/btree.c | 550 ++++++----- src/btree.h | 10 +- src/btreeInt.h | 1 - src/build.c | 498 +++++++--- src/callback.c | 2 + src/ctime.c | 356 +++---- src/date.c | 191 ++-- src/dbstat.c | 71 +- src/delete.c | 13 +- src/expr.c | 595 ++++++++---- src/fkey.c | 69 +- src/func.c | 194 ++-- src/global.c | 54 ++ src/hash.h | 2 +- src/insert.c | 201 ++-- src/loadext.c | 5 + src/main.c | 157 ++- src/malloc.c | 12 +- src/mem2.c | 6 +- src/memdb.c | 26 +- src/memjournal.c | 2 +- src/os.c | 4 + src/os_unix.c | 51 +- src/pager.c | 25 +- src/parse.y | 63 +- src/pcache.c | 5 +- src/pcache1.c | 12 +- src/pragma.c | 170 +++- src/pragma.h | 132 +-- src/prepare.c | 15 +- src/printf.c | 30 +- src/resolve.c | 96 +- src/select.c | 287 ++++-- src/shell.c.in | 802 +++++++++++----- src/sqlite.h.in | 161 +++- src/sqlite3ext.h | 12 + src/sqliteInt.h | 498 ++++++---- src/tclsqlite.c | 142 +-- src/test1.c | 124 ++- src/test_config.c | 2 +- src/test_multiplex.c | 87 +- src/test_tclsh.c | 2 + src/tokenize.c | 6 +- src/treeview.c | 47 +- src/trigger.c | 26 +- src/update.c | 29 +- src/upsert.c | 2 +- src/util.c | 71 +- src/vacuum.c | 8 +- src/vdbe.c | 213 ++++- src/vdbeInt.h | 11 +- src/vdbeapi.c | 25 +- src/vdbeaux.c | 40 +- src/vdbeblob.c | 14 +- src/vdbemem.c | 45 +- src/vdbesort.c | 3 +- src/vdbetrace.c | 4 +- src/vtab.c | 131 +-- src/wal.c | 164 +++- src/walker.c | 2 +- src/where.c | 110 ++- src/whereInt.h | 2 +- src/wherecode.c | 69 +- src/whereexpr.c | 65 +- src/window.c | 50 +- test/aggnested.test | 42 + test/alter.test | 20 + test/alter3.test | 49 +- test/altercorrupt.test | 6 + test/alterfault.test | 41 + test/altermalloc3.test | 31 + test/alterqf.test | 7 + test/altertab.test | 13 + test/altertab3.test | 38 + test/analyze4.test | 6 + test/atof1.test | 5 + test/auth3.test | 24 +- test/autoindex5.test | 6 +- test/autovacuum.test | 3 +- test/autovacuum2.test | 87 ++ test/bestindex1.test | 3 + test/capi2.test | 12 +- test/carray01.test | 32 + test/changes.test | 90 ++ test/columncount.test | 6 + test/corruptL.test | 9 +- test/corruptN.test | 50 +- test/count.test | 11 + test/delete.test | 2 +- test/e_blobbytes.test | 5 + test/e_blobclose.test | 5 + test/e_blobopen.test | 5 + test/e_blobwrite.test | 5 + test/e_changes.test | 14 +- test/e_createtable.test | 15 +- test/e_expr.test | 10 +- test/e_fkey.test | 12 +- test/e_totalchanges.test | 45 +- test/e_uri.test | 1 + test/fkey1.test | 49 + test/fts3corrupt4.test | 421 ++++++++ test/fts3f.test | 57 ++ test/fts3offsets.test | 12 + test/func.test | 3 + test/func3.test | 8 + test/fuzzcheck.c | 234 ++++- test/fuzzdata8.db | Bin 2398208 -> 3019776 bytes test/gencol1.test | 29 + test/hook.test | 129 +-- test/in4.test | 47 + test/incrblob3.test | 5 + test/incrblobfault.test | 5 + test/indexexpr1.test | 52 +- test/io.test | 2 + test/ioerr.test | 1 + test/istrue.test | 30 +- test/json104.test | 3 + test/memdb1.test | 54 ++ test/memjournal2.test | 62 ++ test/minmax.test | 12 +- test/misc1.test | 9 +- test/mmap1.test | 2 +- test/multiplex.test | 17 + test/notnull2.test | 20 + test/pager1.test | 34 +- test/permutations.test | 13 +- test/pragma.test | 70 +- test/quote.test | 78 +- test/releasetest.tcl | 1100 --------------------- test/releasetest_data.tcl | 181 ++-- test/returning1.test | 15 + test/returningfault.test | 36 + test/rowid.test | 30 + test/rowvalueA.test | 77 ++ test/rowvaluefault.test | 18 + test/schema3.test | 6 + test/shell1.test | 24 +- test/shell2.test | 12 +- test/shell3.test | 41 +- test/shell5.test | 15 + test/shell8.test | 25 +- test/shrink.test | 2 +- test/skipscan2.test | 1 - test/sorterref.test | 6 + test/speedtest1.c | 19 +- test/sqldiff1.test | 9 + test/sqllimits1.test | 37 +- test/stat.test | 12 +- test/statfault.test | 10 + test/strict1.test | 138 +++ test/strict2.test | 158 +++ test/tabfunc01.test | 10 +- test/tclsqlite.test | 38 +- test/tester.tcl | 66 +- test/threadtest3.c | 97 +- test/tkt-2d1a5c67d.test | 2 +- test/tkt-8454a207b9.test | 6 + test/tkt-f67b41381a.test | 5 + test/transitive1.test | 46 + test/tt3_checkpoint.c | 4 +- test/tt3_vacuum.c | 2 +- test/update.test | 22 +- test/upfrom2.test | 16 + test/upsert1.test | 14 + test/vacuum-into.test | 33 + test/vacuum3.test | 12 +- test/view.test | 5 + test/vtab1.test | 32 + test/vtabA.test | 2 +- test/vtabK.test | 83 ++ test/where.test | 23 + test/whereE.test | 6 + test/window1.test | 42 +- test/windowB.test | 8 + test/windowC.test | 85 ++ test/windowfault.test | 39 + test/with2.test | 122 +-- test/without_rowid1.test | 42 +- test/without_rowid5.test | 85 ++ test/zeroblob.test | 50 +- test/zeroblobfault.test | 28 + tool/lemon.c | 61 +- tool/lempar.c | 15 +- tool/logest.c | 3 + tool/mkctimec.tcl | 4 +- tool/mkkeywordhash.c | 3 +- tool/mkopcodec.tcl | 3 +- tool/mkpragmatab.tcl | 10 +- tool/mkshellc.tcl | 6 +- tool/mksqlite3c.tcl | 82 +- tool/replace.tcl | 2 +- tool/showdb.c | 6 +- tool/showwal.c | 19 +- tool/spaceanal.tcl | 4 +- tool/speed-check.sh | 3 + tool/sqldiff.c | 104 +- tool/sqlite3_analyzer.c.in | 2 +- 262 files changed, 13744 insertions(+), 4621 deletions(-) create mode 100644 ext/fts5/test/fts5corrupt5.test create mode 100644 ext/fts5/test/fts5corrupt6.test create mode 100644 ext/rbu/rbuexlock.test create mode 100644 ext/rtree/rtreedoc.test create mode 100644 ext/rtree/rtreedoc2.test create mode 100644 ext/rtree/rtreedoc3.test create mode 100644 ext/rtree/test_rtreedoc.c create mode 100644 test/alterfault.test create mode 100644 test/autovacuum2.test create mode 100644 test/changes.test create mode 100644 test/fts3f.test create mode 100644 test/memjournal2.test delete mode 100755 test/releasetest.tcl create mode 100644 test/returningfault.test create mode 100644 test/rowvalueA.test create mode 100644 test/strict1.test create mode 100644 test/strict2.test create mode 100644 test/vtabK.test create mode 100644 test/windowC.test create mode 100644 test/zeroblobfault.test diff --git a/Makefile.in b/Makefile.in index 2795ffa9e..b3ea4628b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -165,6 +165,7 @@ LTINSTALL = $(LIBTOOL) --mode=install $(INSTALL) ############################################################################### USE_AMALGAMATION = @USE_AMALGAMATION@ +AMALGAMATION_LINE_MACROS = @AMALGAMATION_LINE_MACROS@ # Object files for the SQLite library (non-amalgamation). # @@ -467,7 +468,8 @@ TESTSRC += \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ $(TOP)/ext/misc/zipfile.c \ - $(TOP)/ext/userauth/userauth.c + $(TOP)/ext/userauth/userauth.c \ + $(TOP)/ext/rtree/test_rtreedoc.c # Source code to the library files needed by the test fixture # @@ -622,7 +624,7 @@ FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4 FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS3_PARENTHESIS -#FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS5 +FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS5 FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB @@ -756,7 +758,7 @@ mptest: mptester$(TEXE) touch .target_source sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl - $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . @@ -1249,7 +1251,10 @@ coretestprogs: $(TESTPROGS) testprogs: coretestprogs srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE) # A very detailed test running most or all test cases -fulltest: $(TESTPROGS) fuzztest +fulltest: alltest fuzztest + +# Run most or all tcl test cases +alltest: $(TESTPROGS) ./testfixture$(TEXE) $(TOP)/test/all.test $(TESTOPTS) # Really really long testing diff --git a/Makefile.msc b/Makefile.msc index 4f20c0c0c..3cbf560d7 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -235,8 +235,9 @@ DEBUG = 0 !ENDIF # <> -# Disable use of the --linemacros argument to the mksqlite3c.tcl tool, which -# is used to build the amalgamation. +# By default, use --linemacros=1 argument to the mksqlite3c.tcl tool, which +# is used to build the amalgamation. This can be turned off to ease debug +# of the amalgamation away from the source tree. # !IFNDEF NO_LINEMACROS NO_LINEMACROS = 0 @@ -788,9 +789,9 @@ MKSQLITE3C_TOOL = $(TOP)\tool\mksqlite3c.tcl !IFNDEF MKSQLITE3C_ARGS !IF $(DEBUG)>1 && $(NO_LINEMACROS)==0 -MKSQLITE3C_ARGS = --linemacros +MKSQLITE3C_ARGS = --linemacros=1 !ELSE -MKSQLITE3C_ARGS = +MKSQLITE3C_ARGS = --linemacros=0 !ENDIF !IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 MKSQLITE3C_ARGS = $(MKSQLITE3C_ARGS) --useapicall @@ -1585,6 +1586,7 @@ TESTEXT = \ $(TOP)\ext\misc\totype.c \ $(TOP)\ext\misc\unionvtab.c \ $(TOP)\ext\misc\wholenumber.c \ + $(TOP)\ext\rtree\test_rtreedoc.c \ fts5.c # If use of zlib is enabled, add the "zipfile.c" source file. @@ -1697,6 +1699,7 @@ MPTESTER_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 FUZZERSHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -DSQLITE_MAX_MEMORY=50000000 -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS4 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS5 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_RTREE FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBSTAT_VTAB @@ -2208,11 +2211,11 @@ SHELL_SRC = \ $(TOP)\src\shell.c.in \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\completion.c \ - $(TOP)\ext\misc\decimal.c \ + $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\fileio.c \ - $(TOP)\ext\misc\ieee754.c \ - $(TOP)\ext\misc\regexp.c \ - $(TOP)\ext\misc\series.c \ + $(TOP)\ext\misc\ieee754.c \ + $(TOP)\ext\misc\regexp.c \ + $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\shathree.c \ $(TOP)\ext\misc\uint.c \ $(TOP)\ext\expert\sqlite3expert.c \ @@ -2435,7 +2438,9 @@ coretestprogs: $(TESTPROGS) testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe -fulltest: $(TESTPROGS) fuzztest +fulltest: alltest fuzztest + +alltest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\all.test $(TESTOPTS) @@ -2463,7 +2468,11 @@ quicktest: testfixture.exe sourcetest # This is the common case. Run many tests that do not take too long, # including fuzzcheck, sqlite3_analyzer, and sqldiff tests. # -test: $(TESTPROGS) sourcetest fuzztest +test: $(TESTPROGS) sourcetest fuzztest tcltest + +# The veryquick.test TCL tests. +# +tcltest: testfixture.exe @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\veryquick.test $(TESTOPTS) @@ -2585,6 +2594,17 @@ rbu.exe: $(TOP)\ext\rbu\rbu.c $(TOP)\ext\rbu\sqlite3rbu.c $(SQLITE3C) $(SQLITE3H $(LTLINK) $(NO_WARN) -DSQLITE_ENABLE_RBU \ $(TOP)\ext\rbu\rbu.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) +THREADTEST3_SRC = \ + $(TOP)\test\threadtest3.c \ + $(TOP)\test\tt3_checkpoint.c \ + $(TOP)\test\tt3_index.c \ + $(TOP)\test\tt3_vacuum.c \ + $(TOP)\test\tt3_stress.c \ + $(TOP)\test\tt3_lookaside1.c + +threadtest3.exe: $(THREADTEST3_SRC) $(TOP)\src\test_multiplex.c $(SQLITE3C) $(SQLITE3H) + $(LTLINK) $(NO_WARN) $(TOP)\test\threadtest3.c $(TOP)\src\test_multiplex.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) + LSMDIR=$(TOP)\ext\lsm1 !INCLUDE $(LSMDIR)\Makefile.msc @@ -2622,7 +2642,7 @@ clean: del /Q sqlite-*-output.vsix 2>NUL del /Q fuzzershell.exe fuzzcheck.exe sqldiff.exe dbhash.exe 2>NUL del /Q sqltclsh.* 2>NUL - del /Q dbfuzz.exe sessionfuzz.exe 2>NUL + del /Q dbfuzz.exe sessionfuzz.exe threadtest3.exe 2>NUL del /Q kvtest.exe ossshell.exe scrub.exe 2>NUL del /Q showshm.exe sqlite3_checker.* sqlite3_expert.exe 2>NUL del /Q fts5.* fts5parse.* 2>NUL diff --git a/VERSION b/VERSION index 0b477b458..8587f0520 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.36.0 +3.37.2 diff --git a/autoconf/tea/win/nmakehlp.c b/autoconf/tea/win/nmakehlp.c index 7536edee3..2dc33cc65 100644 --- a/autoconf/tea/win/nmakehlp.c +++ b/autoconf/tea/win/nmakehlp.c @@ -14,8 +14,10 @@ #define _CRT_SECURE_NO_DEPRECATE #include +#ifdef _MSC_VER #pragma comment (lib, "user32.lib") #pragma comment (lib, "kernel32.lib") +#endif #include #include @@ -37,7 +39,7 @@ /* protos */ static int CheckForCompilerFeature(const char *option); -static int CheckForLinkerFeature(const char **options, int count); +static int CheckForLinkerFeature(char **options, int count); static int IsIn(const char *string, const char *substring); static int SubstituteFile(const char *substs, const char *filename); static int QualifyPath(const char *path); @@ -54,8 +56,8 @@ typedef struct { char buffer[STATICBUFFERSIZE]; } pipeinfo; -pipeinfo Out = {INVALID_HANDLE_VALUE, '\0'}; -pipeinfo Err = {INVALID_HANDLE_VALUE, '\0'}; +pipeinfo Out = {INVALID_HANDLE_VALUE, ""}; +pipeinfo Err = {INVALID_HANDLE_VALUE, ""}; /* * exitcodes: 0 == no, 1 == yes, 2 == error @@ -273,7 +275,7 @@ CheckForCompilerFeature( "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| - FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars], + FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPSTR)&msg[chars], (300-chars), 0); WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err,NULL); return 2; @@ -326,7 +328,7 @@ CheckForCompilerFeature( static int CheckForLinkerFeature( - const char **options, + char **options, int count) { STARTUPINFO si; @@ -407,7 +409,7 @@ CheckForLinkerFeature( "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| - FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars], + FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPSTR)&msg[chars], (300-chars), 0); WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err,NULL); return 2; @@ -503,7 +505,6 @@ GetVersionFromFile( const char *match, int numdots) { - size_t cbBuffer = 100; static char szBuffer[100]; char *szResult = NULL; FILE *fp = fopen(filename, "rt"); @@ -513,7 +514,7 @@ GetVersionFromFile( * Read data until we see our match string. */ - while (fgets(szBuffer, cbBuffer, fp) != NULL) { + while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) { LPSTR p, q; p = strstr(szBuffer, match); @@ -523,7 +524,7 @@ GetVersionFromFile( */ p += strlen(match); - while (*p && !isdigit(*p)) { + while (*p && !isdigit((unsigned char)*p)) { ++p; } @@ -532,14 +533,13 @@ GetVersionFromFile( */ q = p; - while (*q && (strchr("0123456789.ab", *q)) && ((!strchr(".ab", *q) - && (!strchr("ab", q[-1])) || --numdots))) { + while (*q && (strchr("0123456789.ab", *q)) && (((!strchr(".ab", *q) + && !strchr("ab", q[-1])) || --numdots))) { ++q; } - memcpy(szBuffer, p, q - p); - szBuffer[q-p] = 0; - szResult = szBuffer; + *q = 0; + szResult = p; break; } } @@ -562,7 +562,7 @@ typedef struct list_item_t { static list_item_t * list_insert(list_item_t **listPtrPtr, const char *key, const char *value) { - list_item_t *itemPtr = malloc(sizeof(list_item_t)); + list_item_t *itemPtr = (list_item_t *)malloc(sizeof(list_item_t)); if (itemPtr) { itemPtr->key = strdup(key); itemPtr->value = strdup(value); @@ -611,9 +611,7 @@ SubstituteFile( const char *substitutions, const char *filename) { - size_t cbBuffer = 1024; static char szBuffer[1024], szCopy[1024]; - char *szResult = NULL; list_item_t *substPtr = NULL; FILE *fp, *sp; @@ -626,7 +624,7 @@ SubstituteFile( sp = fopen(substitutions, "rt"); if (sp != NULL) { - while (fgets(szBuffer, cbBuffer, sp) != NULL) { + while (fgets(szBuffer, sizeof(szBuffer), sp) != NULL) { unsigned char *ks, *ke, *vs, *ve; ks = (unsigned char*)szBuffer; while (ks && *ks && isspace(*ks)) ++ks; @@ -657,7 +655,7 @@ SubstituteFile( * Run the substitutions over each line of the input */ - while (fgets(szBuffer, cbBuffer, fp) != NULL) { + while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) { list_item_t *p = NULL; for (p = substPtr; p != NULL; p = p->nextPtr) { char *m = strstr(szBuffer, p->key); @@ -674,7 +672,7 @@ SubstituteFile( memcpy(szBuffer, szCopy, sizeof(szCopy)); } } - printf(szBuffer); + printf("%s", szBuffer); } list_free(&substPtr); @@ -725,7 +723,8 @@ static int LocateDependencyHelper(const char *dir, const char *keypath) { HANDLE hSearch; char path[MAX_PATH+1]; - int dirlen, keylen, ret; + size_t dirlen; + int keylen, ret; WIN32_FIND_DATA finfo; if (dir == NULL || keypath == NULL) @@ -792,7 +791,8 @@ static int LocateDependencyHelper(const char *dir, const char *keypath) */ static int LocateDependency(const char *keypath) { - int i, ret; + size_t i; + int ret; static const char *paths[] = {"..", "..\\..", "..\\..\\.."}; for (i = 0; i < (sizeof(paths)/sizeof(paths[0])); ++i) { diff --git a/configure b/configure index 5df138630..55852f3ea 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.69 for sqlite 3.36.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.37.2. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.36.0' -PACKAGE_STRING='sqlite 3.36.0' +PACKAGE_VERSION='3.37.2' +PACKAGE_STRING='sqlite 3.37.2' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -770,6 +770,7 @@ ac_includes_default="\ ac_subst_vars='LTLIBOBJS LIBOBJS BUILD_CFLAGS +AMALGAMATION_LINE_MACROS USE_GCOV OPT_FEATURE_FLAGS HAVE_ZLIB @@ -1467,7 +1468,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.36.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.37.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1532,7 +1533,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.36.0:";; + short | recursive ) echo "Configuration of sqlite 3.37.2:";; esac cat <<\_ACEOF @@ -1660,7 +1661,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.36.0 +sqlite configure 3.37.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2079,7 +2080,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.36.0, which was +It was created by sqlite $as_me 3.37.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3937,13 +3938,13 @@ if ${lt_cv_nm_interface+:} false; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3940: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3941: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3943: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3944: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3946: output\"" >&5) + (eval echo "\"\$as_me:3947: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -5149,7 +5150,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5152 "configure"' > conftest.$ac_ext + echo '#line 5153 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6674,11 +6675,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:6677: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6678: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6681: \$? = $ac_status" >&5 + echo "$as_me:6682: \$? = $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. @@ -7013,11 +7014,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:7016: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7017: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7020: \$? = $ac_status" >&5 + echo "$as_me:7021: \$? = $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. @@ -7118,11 +7119,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:7121: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7122: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7125: \$? = $ac_status" >&5 + echo "$as_me:7126: \$? = $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 @@ -7173,11 +7174,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:7176: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7177: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7180: \$? = $ac_status" >&5 + echo "$as_me:7181: \$? = $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 @@ -9553,7 +9554,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9556 "configure" +#line 9557 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -9649,7 +9650,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9652 "configure" +#line 9653 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -11860,6 +11861,17 @@ else fi +######### +# Enable/disabled amalagamation line macros +######## +AMALGAMATION_LINE_MACROS=--linemacros=0 +if test "${amalgamation_line_macros}" = "yes" ; then + AMALGAMATION_LINE_MACROS=--linemacros=1 +fi +if test "${amalgamation_line_macros}" = "no" ; then + AMALGAMATION_LINE_MACROS=--linemacros=0 +fi + ######### # Output the config header @@ -12378,7 +12390,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.36.0, which was +This file was extended by sqlite $as_me 3.37.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12444,7 +12456,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.36.0 +sqlite config.status 3.37.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 70664dd41..32fe0d229 100644 --- a/configure.ac +++ b/configure.ac @@ -791,6 +791,17 @@ else fi AC_SUBST(USE_GCOV) +######### +# Enable/disabled amalagamation line macros +######## +AMALGAMATION_LINE_MACROS=--linemacros=0 +if test "${amalgamation_line_macros}" = "yes" ; then + AMALGAMATION_LINE_MACROS=--linemacros=1 +fi +if test "${amalgamation_line_macros}" = "no" ; then + AMALGAMATION_LINE_MACROS=--linemacros=0 +fi +AC_SUBST(AMALGAMATION_LINE_MACROS) ######### # Output the config header diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index 3987d0c91..73541122d 100644 --- a/ext/expert/expert1.test +++ b/ext/expert/expert1.test @@ -196,7 +196,7 @@ do_setup_rec_test $tn.9.1 { } { SELECT * FROM "t t" WHERE a=? } { - CREATE INDEX 't t_idx_00000061' ON 't t'(a); + CREATE INDEX "t t_idx_00000061" ON "t t"(a); SEARCH t t USING INDEX t t_idx_00000061 (a=?) } @@ -205,7 +205,7 @@ do_setup_rec_test $tn.9.2 { } { SELECT * FROM "t t" WHERE b BETWEEN ? AND ? } { - CREATE INDEX 't t_idx_00000062' ON 't t'(b); + CREATE INDEX "t t_idx_00000062" ON "t t"(b); SEARCH t t USING INDEX t t_idx_00000062 (b>? AND b #include +#if !defined(SQLITE_AMALGAMATION) +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif +#endif /* !defined(SQLITE_AMALGAMATION) */ + + #ifndef SQLITE_OMIT_VIRTUALTABLE typedef sqlite3_int64 i64; @@ -690,11 +707,13 @@ static int idxGetTableInfo( rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_xinfo=%Q", zTab); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){ const char *zCol = (const char*)sqlite3_column_text(p1, 1); + const char *zColSeq = 0; nByte += 1 + STRLEN(zCol); rc = sqlite3_table_column_metadata( - db, "main", zTab, zCol, 0, &zCol, 0, 0, 0 + db, "main", zTab, zCol, 0, &zColSeq, 0, 0, 0 ); - nByte += 1 + STRLEN(zCol); + if( zColSeq==0 ) zColSeq = "binary"; + nByte += 1 + STRLEN(zColSeq); nCol++; nPk += (sqlite3_column_int(p1, 5)>0); } @@ -714,6 +733,7 @@ static int idxGetTableInfo( nCol = 0; while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){ const char *zCol = (const char*)sqlite3_column_text(p1, 1); + const char *zColSeq = 0; int nCopy = STRLEN(zCol) + 1; pNew->aCol[nCol].zName = pCsr; pNew->aCol[nCol].iPk = (sqlite3_column_int(p1, 5)==1 && nPk==1); @@ -721,12 +741,13 @@ static int idxGetTableInfo( pCsr += nCopy; rc = sqlite3_table_column_metadata( - db, "main", zTab, zCol, 0, &zCol, 0, 0, 0 + db, "main", zTab, zCol, 0, &zColSeq, 0, 0, 0 ); if( rc==SQLITE_OK ){ - nCopy = STRLEN(zCol) + 1; + if( zColSeq==0 ) zColSeq = "binary"; + nCopy = STRLEN(zColSeq) + 1; pNew->aCol[nCol].zColl = pCsr; - memcpy(pCsr, zCol, nCopy); + memcpy(pCsr, zColSeq, nCopy); pCsr += nCopy; } @@ -737,9 +758,9 @@ static int idxGetTableInfo( if( rc!=SQLITE_OK ){ sqlite3_free(pNew); pNew = 0; - }else{ + }else if( ALWAYS(pNew!=0) ){ pNew->zName = pCsr; - memcpy(pNew->zName, zTab, nTab+1); + if( ALWAYS(pNew->zName!=0) ) memcpy(pNew->zName, zTab, nTab+1); } *ppOut = pNew; @@ -910,6 +931,19 @@ static int idxFindCompatible( return 0; } +/* Callback for sqlite3_exec() with query with leading count(*) column. + * The first argument is expected to be an int*, referent to be incremented + * if that leading column is not exactly '0'. + */ +static int countNonzeros(void* pCount, int nc, + char* azResults[], char* azColumns[]){ + (void)azColumns; /* Suppress unused parameter warning */ + if( nc>0 && (azResults[0][0]!='0' || azResults[0][1]!=0) ){ + *((int *)pCount) += 1; + } + return 0; +} + static int idxCreateFromCons( sqlite3expert *p, IdxScan *pScan, @@ -936,17 +970,40 @@ static int idxCreateFromCons( if( rc==SQLITE_OK ){ /* Hash the list of columns to come up with a name for the index */ const char *zTable = pScan->pTab->zName; - char *zName; /* Index name */ - int i; - for(i=0; zCols[i]; i++){ - h += ((h<<3) + zCols[i]); - } - zName = sqlite3_mprintf("%s_idx_%08x", zTable, h); - if( zName==0 ){ + int quoteTable = idxIdentifierRequiresQuotes(zTable); + char *zName = 0; /* Index name */ + int collisions = 0; + do{ + int i; + char *zFind; + for(i=0; zCols[i]; i++){ + h += ((h<<3) + zCols[i]); + } + sqlite3_free(zName); + zName = sqlite3_mprintf("%s_idx_%08x", zTable, h); + if( zName==0 ) break; + /* Is is unique among table, view and index names? */ + zFmt = "SELECT count(*) FROM sqlite_schema WHERE name=%Q" + " AND type in ('index','table','view')"; + zFind = sqlite3_mprintf(zFmt, zName); + i = 0; + rc = sqlite3_exec(dbm, zFind, countNonzeros, &i, 0); + assert(rc==SQLITE_OK); + sqlite3_free(zFind); + if( i==0 ){ + collisions = 0; + break; + } + ++collisions; + }while( collisions<50 && zName!=0 ); + if( collisions ){ + /* This return means "Gave up trying to find a unique index name." */ + rc = SQLITE_BUSY_TIMEOUT; + }else if( zName==0 ){ rc = SQLITE_NOMEM; }else{ - if( idxIdentifierRequiresQuotes(zTable) ){ - zFmt = "CREATE INDEX '%q' ON %Q(%s)"; + if( quoteTable ){ + zFmt = "CREATE INDEX \"%w\" ON \"%w\"(%s)"; }else{ zFmt = "CREATE INDEX %s ON %s(%s)"; } @@ -955,7 +1012,11 @@ static int idxCreateFromCons( rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(dbm, zIdx, 0, 0, p->pzErrmsg); - idxHashAdd(&rc, &p->hIdx, zName, zIdx); + if( rc!=SQLITE_OK ){ + rc = SQLITE_BUSY_TIMEOUT; + }else{ + idxHashAdd(&rc, &p->hIdx, zName, zIdx); + } } sqlite3_free(zName); sqlite3_free(zIdx); @@ -1879,6 +1940,10 @@ int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr){ /* Create candidate indexes within the in-memory database file */ if( rc==SQLITE_OK ){ rc = idxCreateCandidates(p); + }else if ( rc==SQLITE_BUSY_TIMEOUT ){ + if( pzErr ) + *pzErr = sqlite3_mprintf("Cannot find a unique index name to propose."); + return rc; } /* Generate the stat1 data */ diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 9c5703d3f..074123d65 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -313,13 +313,6 @@ static int fts3EvalStart(Fts3Cursor *pCsr); static int fts3TermSegReaderCursor( Fts3Cursor *, const char *, int, int, Fts3MultiSegReader **); -#ifndef SQLITE_AMALGAMATION -# if defined(SQLITE_DEBUG) -int sqlite3Fts3Always(int b) { assert( b ); return b; } -int sqlite3Fts3Never(int b) { assert( !b ); return b; } -# endif -#endif - /* ** This variable is set to false when running tests for which the on disk ** structures should not be corrupt. Otherwise, true. If it is false, extra @@ -4480,7 +4473,7 @@ void sqlite3Fts3DoclistPrev( assert( nDoclist>0 ); assert( *pbEof==0 ); - assert( p || *piDocid==0 ); + assert_fts3_nc( p || *piDocid==0 ); assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) ); if( p==0 ){ @@ -5344,8 +5337,8 @@ static void fts3EvalNextRow( Fts3Expr *pRight = pExpr->pRight; sqlite3_int64 iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid); - assert( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); - assert( pRight->bStart || pLeft->iDocid==pRight->iDocid ); + assert_fts3_nc( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); + assert_fts3_nc( pRight->bStart || pLeft->iDocid==pRight->iDocid ); if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ fts3EvalNextRow(pCsr, pLeft, pRc); @@ -5983,6 +5976,9 @@ int sqlite3Fts3EvalPhrasePoslist( if( bEofSave==0 && pNear->iDocid==iDocid ) break; } assert( rc!=SQLITE_OK || pPhrase->bIncr==0 ); + if( rc==SQLITE_OK && pNear->bEof!=bEofSave ){ + rc = FTS_CORRUPT_VTAB; + } } if( bTreeEof ){ while( rc==SQLITE_OK && !pNear->bEof ){ diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index 8c2579166..3a62ccc7a 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -151,17 +151,18 @@ extern int sqlite3_fts3_may_be_corrupt; ** Macros indicating that conditional expressions are always true or ** false. */ -#ifdef SQLITE_COVERAGE_TEST -# define ALWAYS(x) (1) -# define NEVER(X) (0) -#elif defined(SQLITE_DEBUG) -# define ALWAYS(x) sqlite3Fts3Always((x)!=0) -# define NEVER(x) sqlite3Fts3Never((x)!=0) -int sqlite3Fts3Always(int b); -int sqlite3Fts3Never(int b); +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) #else -# define ALWAYS(x) (x) -# define NEVER(x) (x) +# define ALWAYS(X) (X) +# define NEVER(X) (X) #endif /* @@ -620,6 +621,7 @@ void sqlite3Fts3ExprFree(Fts3Expr *); int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash*); int sqlite3Fts3InitTerm(sqlite3 *db); #endif +void *sqlite3Fts3MallocZero(i64 nByte); int sqlite3Fts3OpenTokenizer(sqlite3_tokenizer *, int, const char *, int, sqlite3_tokenizer_cursor ** diff --git a/ext/fts3/fts3_aux.c b/ext/fts3/fts3_aux.c index b6e651853..d3b194c94 100644 --- a/ext/fts3/fts3_aux.c +++ b/ext/fts3/fts3_aux.c @@ -297,6 +297,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ if( fts3auxGrowStatArray(pCsr, 2) ) return SQLITE_NOMEM; memset(pCsr->aStat, 0, sizeof(struct Fts3auxColstats) * pCsr->nStat); iCol = 0; + rc = SQLITE_OK; while( iaStat[iCol+1].nDoc++; eState = 2; @@ -348,7 +353,6 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ } pCsr->iCol = 0; - rc = SQLITE_OK; }else{ pCsr->isEof = 1; } diff --git a/ext/fts3/fts3_expr.c b/ext/fts3/fts3_expr.c index 7a69a935f..ea8167c59 100644 --- a/ext/fts3/fts3_expr.c +++ b/ext/fts3/fts3_expr.c @@ -122,7 +122,7 @@ static int fts3isspace(char c){ ** zero the memory before returning a pointer to it. If unsuccessful, ** return NULL. */ -static void *fts3MallocZero(sqlite3_int64 nByte){ +void *sqlite3Fts3MallocZero(sqlite3_int64 nByte){ void *pRet = sqlite3_malloc64(nByte); if( pRet ) memset(pRet, 0, nByte); return pRet; @@ -203,7 +203,7 @@ static int getNextToken( rc = pModule->xNext(pCursor, &zToken, &nToken, &iStart, &iEnd, &iPosition); if( rc==SQLITE_OK ){ nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken; - pRet = (Fts3Expr *)fts3MallocZero(nByte); + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(nByte); if( !pRet ){ rc = SQLITE_NOMEM; }else{ @@ -458,7 +458,7 @@ static int getNextNode( if( fts3isspace(cNext) || cNext=='"' || cNext=='(' || cNext==')' || cNext==0 ){ - pRet = (Fts3Expr *)fts3MallocZero(sizeof(Fts3Expr)); + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pRet ){ return SQLITE_NOMEM; } @@ -637,7 +637,7 @@ static int fts3ExprParse( && p->eType==FTSQUERY_PHRASE && pParse->isNot ){ /* Create an implicit NOT operator. */ - Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr)); + Fts3Expr *pNot = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pNot ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; @@ -671,7 +671,7 @@ static int fts3ExprParse( /* Insert an implicit AND operator. */ Fts3Expr *pAnd; assert( pRet && pPrev ); - pAnd = fts3MallocZero(sizeof(Fts3Expr)); + pAnd = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pAnd ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; diff --git a/ext/fts3/fts3_snippet.c b/ext/fts3/fts3_snippet.c index 613d3e5f1..c5192c4eb 100644 --- a/ext/fts3/fts3_snippet.c +++ b/ext/fts3/fts3_snippet.c @@ -138,9 +138,8 @@ static MatchinfoBuffer *fts3MIBufferNew(size_t nElem, const char *zMatchinfo){ + sizeof(MatchinfoBuffer); sqlite3_int64 nStr = strlen(zMatchinfo); - pRet = sqlite3_malloc64(nByte + nStr+1); + pRet = sqlite3Fts3MallocZero(nByte + nStr+1); if( pRet ){ - memset(pRet, 0, nByte); pRet->aMatchinfo[0] = (u8*)(&pRet->aMatchinfo[1]) - (u8*)pRet; pRet->aMatchinfo[1+nElem] = pRet->aMatchinfo[0] + sizeof(u32)*((int)nElem+1); @@ -544,11 +543,10 @@ static int fts3BestSnippet( ** the required space using malloc(). */ nByte = sizeof(SnippetPhrase) * nList; - sIter.aPhrase = (SnippetPhrase *)sqlite3_malloc64(nByte); + sIter.aPhrase = (SnippetPhrase *)sqlite3Fts3MallocZero(nByte); if( !sIter.aPhrase ){ return SQLITE_NOMEM; } - memset(sIter.aPhrase, 0, nByte); /* Initialize the contents of the SnippetIter object. Then iterate through ** the set of phrases in the expression to populate the aPhrase[] array. @@ -1112,10 +1110,12 @@ static int fts3MatchinfoLcsCb( ** position list for the next column. */ static int fts3LcsIteratorAdvance(LcsIterator *pIter){ - char *pRead = pIter->pRead; + char *pRead; sqlite3_int64 iRead; int rc = 0; + if( NEVER(pIter==0) ) return 1; + pRead = pIter->pRead; pRead += sqlite3Fts3GetVarint(pRead, &iRead); if( iRead==0 || iRead==1 ){ pRead = 0; @@ -1149,9 +1149,8 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ /* Allocate and populate the array of LcsIterator objects. The array ** contains one element for each matchable phrase in the query. **/ - aIter = sqlite3_malloc64(sizeof(LcsIterator) * pCsr->nPhrase); + aIter = sqlite3Fts3MallocZero(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++){ @@ -1612,7 +1611,7 @@ void sqlite3Fts3Offsets( if( rc!=SQLITE_OK ) goto offsets_out; /* Allocate the array of TermOffset iterators. */ - sCtx.aTerm = (TermOffset *)sqlite3_malloc64(sizeof(TermOffset)*nToken); + sCtx.aTerm = (TermOffset *)sqlite3Fts3MallocZero(sizeof(TermOffset)*nToken); if( 0==sCtx.aTerm ){ rc = SQLITE_NOMEM; goto offsets_out; @@ -1633,13 +1632,13 @@ void sqlite3Fts3Offsets( const char *zDoc; int nDoc; - /* Initialize the contents of sCtx.aTerm[] for column iCol. There is - ** no way that this operation can fail, so the return code from - ** fts3ExprIterate() can be discarded. + /* Initialize the contents of sCtx.aTerm[] for column iCol. This + ** operation may fail if the database contains corrupt records. */ sCtx.iCol = iCol; sCtx.iTerm = 0; - (void)fts3ExprIterate(pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx); + rc = fts3ExprIterate(pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx); + if( rc!=SQLITE_OK ) goto offsets_out; /* Retreive the text stored in column iCol. If an SQL NULL is stored ** in column iCol, jump immediately to the next iteration of the loop. diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 56c59ce3a..201e5813c 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -1335,8 +1335,18 @@ static int fts3SegReaderNext( char *aCopy; PendingList *pList = (PendingList *)fts3HashData(pElem); int nCopy = pList->nData+1; - pReader->zTerm = (char *)fts3HashKey(pElem); - pReader->nTerm = fts3HashKeysize(pElem); + + int nTerm = fts3HashKeysize(pElem); + if( (nTerm+1)>pReader->nTermAlloc ){ + sqlite3_free(pReader->zTerm); + pReader->zTerm = (char*)sqlite3_malloc((nTerm+1)*2); + if( !pReader->zTerm ) return SQLITE_NOMEM; + pReader->nTermAlloc = (nTerm+1)*2; + } + memcpy(pReader->zTerm, fts3HashKey(pElem), nTerm); + pReader->zTerm[nTerm] = '\0'; + pReader->nTerm = nTerm; + aCopy = (char*)sqlite3_malloc(nCopy); if( !aCopy ) return SQLITE_NOMEM; memcpy(aCopy, pList->aData, nCopy); @@ -1589,9 +1599,7 @@ int sqlite3Fts3MsrOvfl( */ void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){ if( pReader ){ - if( !fts3SegReaderIsPending(pReader) ){ - sqlite3_free(pReader->zTerm); - } + sqlite3_free(pReader->zTerm); if( !fts3SegReaderIsRootOnly(pReader) ){ sqlite3_free(pReader->aNode); } @@ -3783,7 +3791,7 @@ static int nodeReaderNext(NodeReader *p){ return FTS_CORRUPT_VTAB; } blobGrowBuffer(&p->term, nPrefix+nSuffix, &rc); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(p->term.a!=0) ){ memcpy(&p->term.a[nPrefix], &p->aNode[p->iOff], nSuffix); p->term.n = nPrefix+nSuffix; p->iOff += nSuffix; @@ -4177,7 +4185,11 @@ static int fts3TermCmp( int nCmp = MIN(nLhs, nRhs); int res; - res = (nCmp ? memcmp(zLhs, zRhs, nCmp) : 0); + if( nCmp && ALWAYS(zLhs) && ALWAYS(zRhs) ){ + res = memcmp(zLhs, zRhs, nCmp); + }else{ + res = 0; + } if( res==0 ) res = nLhs - nRhs; return res; @@ -4821,7 +4833,7 @@ static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){ if( aHint ){ blobGrowBuffer(pHint, nHint, &rc); if( rc==SQLITE_OK ){ - memcpy(pHint->a, aHint, nHint); + if( ALWAYS(pHint->a!=0) ) memcpy(pHint->a, aHint, nHint); pHint->n = nHint; } } diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index c639f9b57..754f28c67 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -35,8 +35,20 @@ typedef sqlite3_uint64 u64; #endif #define testcase(x) -#define ALWAYS(x) 1 -#define NEVER(x) 0 + +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif #define MIN(x,y) (((x) < (y)) ? (x) : (y)) #define MAX(x,y) (((x) > (y)) ? (x) : (y)) @@ -96,7 +108,7 @@ extern int sqlite3_fts5_may_be_corrupt; ** A version of memcmp() that does not cause asan errors if one of the pointer ** parameters is NULL and the number of bytes to compare is zero. */ -#define fts5Memcmp(s1, s2, n) ((n)==0 ? 0 : memcmp((s1), (s2), (n))) +#define fts5Memcmp(s1, s2, n) ((n)<=0 ? 0 : memcmp((s1), (s2), (n))) /* Mark a function parameter as unused, to suppress nuisance compiler ** warnings. */ @@ -435,6 +447,9 @@ void sqlite3Fts5IndexCloseReader(Fts5Index*); */ const char *sqlite3Fts5IterTerm(Fts5IndexIter*, int*); int sqlite3Fts5IterNextScan(Fts5IndexIter*); +void *sqlite3Fts5StructureRef(Fts5Index*); +void sqlite3Fts5StructureRelease(void*); +int sqlite3Fts5StructureTest(Fts5Index*, void*); /* diff --git a/ext/fts5/fts5_buffer.c b/ext/fts5/fts5_buffer.c index a8f3bee33..b9614e129 100644 --- a/ext/fts5/fts5_buffer.c +++ b/ext/fts5/fts5_buffer.c @@ -66,7 +66,6 @@ void sqlite3Fts5BufferAppendBlob( u32 nData, const u8 *pData ){ - assert_nc( *pRc || nData>=0 ); if( nData ){ if( fts5BufferGrow(pRc, pBuf, nData) ) return; memcpy(&pBuf->p[pBuf->n], pData, nData); @@ -176,9 +175,8 @@ int sqlite3Fts5PoslistNext64( return 1; }else{ i64 iOff = *piOff; - int iVal; + u32 iVal; fts5FastGetVarint32(a, i, iVal); - assert( iVal>=0 ); if( iVal<=1 ){ if( iVal==0 ){ *pi = i; @@ -186,6 +184,7 @@ int sqlite3Fts5PoslistNext64( } fts5FastGetVarint32(a, i, iVal); iOff = ((i64)iVal) << 32; + assert( iOff>=0 ); fts5FastGetVarint32(a, i, iVal); if( iVal<2 ){ /* This is a corrupt record. So stop parsing it here. */ @@ -197,7 +196,7 @@ int sqlite3Fts5PoslistNext64( *piOff = (iOff & (i64)0x7FFFFFFF<<32)+((iOff + (iVal-2)) & 0x7FFFFFFF); } *pi = i; - assert( *piOff>=iOff ); + assert_nc( *piOff>=iOff ); return 0; } } diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c index 32335c937..ab1a846b1 100644 --- a/ext/fts5/fts5_config.c +++ b/ext/fts5/fts5_config.c @@ -562,6 +562,7 @@ int sqlite3Fts5ConfigParse( z = fts5ConfigSkipWhitespace(z); if( z && *z=='=' ){ bOption = 1; + assert( zOne!=0 ); z++; if( bMustBeCol ) z = 0; } @@ -578,7 +579,11 @@ int sqlite3Fts5ConfigParse( rc = SQLITE_ERROR; }else{ if( bOption ){ - rc = fts5ConfigParseSpecial(pGlobal, pRet, zOne, zTwo?zTwo:"", pzErr); + rc = fts5ConfigParseSpecial(pGlobal, pRet, + ALWAYS(zOne)?zOne:"", + zTwo?zTwo:"", + pzErr + ); }else{ rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr); zOne = 0; diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index f3cec00be..d9c1dd0fd 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -135,6 +135,7 @@ void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){ va_list ap; va_start(ap, zFmt); if( pParse->rc==SQLITE_OK ){ + assert( pParse->zErr==0 ); pParse->zErr = sqlite3_vmprintf(zFmt, ap); pParse->rc = SQLITE_ERROR; } @@ -433,6 +434,7 @@ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ int bRetValid = 0; Fts5ExprTerm *p; + assert( pTerm ); assert( pTerm->pSynonym ); assert( bDesc==0 || bDesc==1 ); for(p=pTerm; p; p=p->pSynonym){ @@ -1873,7 +1875,7 @@ int sqlite3Fts5ExprClonePhrase( sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){ /* All the allocations succeeded. Put the expression object together. */ pNew->pIndex = pExpr->pIndex; pNew->pConfig = pExpr->pConfig; @@ -2144,9 +2146,8 @@ void sqlite3Fts5ParseSetColset( ){ Fts5Colset *pFree = pColset; if( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){ - pParse->rc = SQLITE_ERROR; - pParse->zErr = sqlite3_mprintf( - "fts5: column queries are not supported (detail=none)" + sqlite3Fts5ParseError(pParse, + "fts5: column queries are not supported (detail=none)" ); }else{ fts5ParseSetColset(pParse, pExpr, pColset, &pFree); @@ -2320,13 +2321,10 @@ Fts5ExprNode *sqlite3Fts5ParseNode( || pPhrase->nTerm>1 || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst) ){ - assert( pParse->rc==SQLITE_OK ); - pParse->rc = SQLITE_ERROR; - assert( pParse->zErr==0 ); - pParse->zErr = sqlite3_mprintf( + sqlite3Fts5ParseError(pParse, "fts5: %s queries are not supported (detail!=full)", pNear->nPhrase==1 ? "phrase": "NEAR" - ); + ); sqlite3_free(pRet); pRet = 0; } @@ -2858,6 +2856,15 @@ struct Fts5PoslistPopulator { int bMiss; }; +/* +** Clear the position lists associated with all phrases in the expression +** passed as the first argument. Argument bLive is true if the expression +** might be pointing to a real entry, otherwise it has just been reset. +** +** At present this function is only used for detail=col and detail=none +** fts5 tables. This implies that all phrases must be at most 1 token +** in size, as phrase matches are not supported without detail=full. +*/ Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){ Fts5PoslistPopulator *pRet; pRet = sqlite3_malloc64(sizeof(Fts5PoslistPopulator)*pExpr->nPhrase); @@ -2867,7 +2874,7 @@ Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){ for(i=0; inPhrase; i++){ Fts5Buffer *pBuf = &pExpr->apExprPhrase[i]->poslist; Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; - assert( pExpr->apExprPhrase[i]->nTerm==1 ); + assert( pExpr->apExprPhrase[i]->nTerm<=1 ); if( bLive && (pBuf->n==0 || pNode->iRowid!=pExpr->pRoot->iRowid || pNode->bEof) ){ diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c index 0f4651a83..bc9244fc0 100644 --- a/ext/fts5/fts5_hash.c +++ b/ext/fts5/fts5_hash.c @@ -356,7 +356,7 @@ int sqlite3Fts5HashWrite( p->bContent = 1; }else{ /* Append a new column value, if necessary */ - assert( iCol>=p->iCol ); + assert_nc( iCol>=p->iCol ); if( iCol!=p->iCol ){ if( pHash->eDetail==FTS5_DETAIL_FULL ){ pPtr[p->nData++] = 0x01; diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 7cc0d46ac..fe253984b 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -600,8 +600,11 @@ static int fts5BufferCompareBlob( ** res = *pLeft - *pRight */ static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){ - int nCmp = MIN(pLeft->n, pRight->n); - int res = fts5Memcmp(pLeft->p, pRight->p, nCmp); + int nCmp, res; + nCmp = MIN(pLeft->n, pRight->n); + assert( nCmp<=0 || pLeft->p!=0 ); + assert( nCmp<=0 || pRight->p!=0 ); + res = fts5Memcmp(pLeft->p, pRight->p, nCmp); return (res==0 ? (pLeft->n - pRight->n) : res); } @@ -697,6 +700,7 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ return pRet; } + /* ** Release a reference to data record returned by an earlier call to ** fts5DataRead(). @@ -821,6 +825,58 @@ static void fts5StructureRef(Fts5Structure *pStruct){ pStruct->nRef++; } +void *sqlite3Fts5StructureRef(Fts5Index *p){ + fts5StructureRef(p->pStruct); + return (void*)p->pStruct; +} +void sqlite3Fts5StructureRelease(void *p){ + if( p ){ + fts5StructureRelease((Fts5Structure*)p); + } +} +int sqlite3Fts5StructureTest(Fts5Index *p, void *pStruct){ + if( p->pStruct!=(Fts5Structure*)pStruct ){ + return SQLITE_ABORT; + } + return SQLITE_OK; +} + +/* +** Ensure that structure object (*pp) is writable. +** +** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. If +** an error occurs, (*pRc) is set to an SQLite error code before returning. +*/ +static void fts5StructureMakeWritable(int *pRc, Fts5Structure **pp){ + Fts5Structure *p = *pp; + if( *pRc==SQLITE_OK && p->nRef>1 ){ + i64 nByte = sizeof(Fts5Structure)+(p->nLevel-1)*sizeof(Fts5StructureLevel); + Fts5Structure *pNew; + pNew = (Fts5Structure*)sqlite3Fts5MallocZero(pRc, nByte); + if( pNew ){ + int i; + memcpy(pNew, p, nByte); + for(i=0; inLevel; i++) pNew->aLevel[i].aSeg = 0; + for(i=0; inLevel; i++){ + Fts5StructureLevel *pLvl = &pNew->aLevel[i]; + nByte = sizeof(Fts5StructureSegment) * pNew->aLevel[i].nSeg; + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(pRc, nByte); + if( pLvl->aSeg==0 ){ + for(i=0; inLevel; i++){ + sqlite3_free(pNew->aLevel[i].aSeg); + } + sqlite3_free(pNew); + return; + } + memcpy(pLvl->aSeg, p->aLevel[i].aSeg, nByte); + } + p->nRef--; + pNew->nRef = 1; + } + *pp = pNew; + } +} + /* ** Deserialize and return the structure record currently stored in serialized ** form within buffer pData/nData. @@ -922,9 +978,11 @@ static int fts5StructureDecode( } /* -** +** Add a level to the Fts5Structure.aLevel[] array of structure object +** (*ppStruct). */ static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){ + fts5StructureMakeWritable(pRc, ppStruct); if( *pRc==SQLITE_OK ){ Fts5Structure *pStruct = *ppStruct; int nLevel = pStruct->nLevel; @@ -1718,6 +1776,7 @@ static void fts5SegIterInit( if( p->rc==SQLITE_OK ){ pIter->iLeafOffset = 4; + assert( pIter->pLeaf!=0 ); assert_nc( pIter->pLeaf->nn>4 ); assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 ); pIter->iPgidxOff = pIter->pLeaf->szLeaf+1; @@ -1820,8 +1879,12 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ int iRowidOff; iRowidOff = fts5LeafFirstRowidOff(pNew); if( iRowidOff ){ - pIter->pLeaf = pNew; - pIter->iLeafOffset = iRowidOff; + if( iRowidOff>=pNew->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else{ + pIter->pLeaf = pNew; + pIter->iLeafOffset = iRowidOff; + } } } @@ -2101,7 +2164,7 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ if( pDlidx ){ int iSegid = pIter->pSeg->iSegid; pgnoLast = fts5DlidxIterPgno(pDlidx); - pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); + pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); }else{ Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */ @@ -2128,7 +2191,7 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ ** forward to find the page containing the last rowid. */ for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){ i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno); - Fts5Data *pNew = fts5DataRead(p, iAbs); + Fts5Data *pNew = fts5LeafRead(p, iAbs); if( pNew ){ int iRowid, bTermless; iRowid = fts5LeafFirstRowidOff(pNew); @@ -2159,6 +2222,10 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ pIter->pLeaf = pLast; pIter->iLeafPgno = pgnoLast; iOff = fts5LeafFirstRowidOff(pLast); + if( iOff>pLast->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + } iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid); pIter->iLeafOffset = iOff; @@ -2167,7 +2234,6 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ }else{ pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast); } - } fts5SegIterReverseInitPage(p, pIter); @@ -2219,21 +2285,20 @@ static void fts5LeafSeek( Fts5SegIter *pIter, /* Iterator to seek */ const u8 *pTerm, int nTerm /* Term to search for */ ){ - int iOff; + u32 iOff; const u8 *a = pIter->pLeaf->p; - int szLeaf = pIter->pLeaf->szLeaf; - int n = pIter->pLeaf->nn; + u32 n = (u32)pIter->pLeaf->nn; u32 nMatch = 0; u32 nKeep = 0; u32 nNew = 0; u32 iTermOff; - int iPgidx; /* Current offset in pgidx */ + u32 iPgidx; /* Current offset in pgidx */ int bEndOfPage = 0; assert( p->rc==SQLITE_OK ); - iPgidx = szLeaf; + iPgidx = (u32)pIter->pLeaf->szLeaf; iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff); iOff = iTermOff; if( iOff>n ){ @@ -2299,15 +2364,15 @@ static void fts5LeafSeek( if( pIter->pLeaf==0 ) return; a = pIter->pLeaf->p; if( fts5LeafIsTermless(pIter->pLeaf)==0 ){ - iPgidx = pIter->pLeaf->szLeaf; + iPgidx = (u32)pIter->pLeaf->szLeaf; iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff); - if( iOff<4 || iOff>=pIter->pLeaf->szLeaf ){ + if( iOff<4 || (i64)iOff>=pIter->pLeaf->szLeaf ){ p->rc = FTS5_CORRUPT; return; }else{ nKeep = 0; iTermOff = iOff; - n = pIter->pLeaf->nn; + n = (u32)pIter->pLeaf->nn; iOff += fts5GetVarint32(&a[iOff], nNew); break; } @@ -2675,7 +2740,7 @@ static void fts5SegIterGotoPage( fts5SegIterNextPage(p, pIter); assert( p->rc!=SQLITE_OK || pIter->iLeafPgno==iLeafPgno ); - if( p->rc==SQLITE_OK ){ + if( p->rc==SQLITE_OK && ALWAYS(pIter->pLeaf!=0) ){ int iOff; u8 *a = pIter->pLeaf->p; int n = pIter->pLeaf->szLeaf; @@ -3107,7 +3172,11 @@ static void fts5SegiterPoslist( Fts5Colset *pColset, Fts5Buffer *pBuf ){ + assert( pBuf!=0 ); + assert( pSeg!=0 ); if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+FTS5_DATA_ZERO_PADDING) ){ + assert( pBuf->p!=0 ); + assert( pBuf->nSpace >= pBuf->n+pSeg->nPos+FTS5_DATA_ZERO_PADDING ); memset(&pBuf->p[pBuf->n+pSeg->nPos], 0, FTS5_DATA_ZERO_PADDING); if( pColset==0 ){ fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); @@ -3331,6 +3400,7 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ } static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ + assert( pIter!=0 || (*pRc)!=SQLITE_OK ); if( *pRc==SQLITE_OK ){ Fts5Config *pConfig = pIter->pIndex->pConfig; if( pConfig->eDetail==FTS5_DETAIL_NONE ){ @@ -3402,7 +3472,10 @@ static void fts5MultiIterNew( } } *ppOut = pNew = fts5MultiIterAlloc(p, nSeg); - if( pNew==0 ) return; + if( pNew==0 ){ + assert( p->rc!=SQLITE_OK ); + goto fts5MultiIterNew_post_check; + } pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC)); pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY)); pNew->pColset = pColset; @@ -3466,6 +3539,10 @@ static void fts5MultiIterNew( fts5MultiIterFree(pNew); *ppOut = 0; } + +fts5MultiIterNew_post_check: + assert( (*ppOut)!=0 || p->rc!=SQLITE_OK ); + return; } /* @@ -3513,7 +3590,8 @@ static void fts5MultiIterNew2( ** False otherwise. */ static int fts5MultiIterEof(Fts5Index *p, Fts5Iter *pIter){ - assert( p->rc + assert( pIter!=0 || p->rc!=SQLITE_OK ); + assert( p->rc!=SQLITE_OK || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->base.bEof ); return (p->rc || pIter->base.bEof); @@ -4317,6 +4395,7 @@ static void fts5IndexMergeLevel( ** and last leaf page number at the same time. */ fts5WriteFinish(p, &writer, &pSeg->pgnoLast); + assert( pIter!=0 || p->rc!=SQLITE_OK ); if( fts5MultiIterEof(p, pIter) ){ int i; @@ -4417,7 +4496,7 @@ static void fts5IndexAutomerge( Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ int nLeaf /* Number of output leaves just written */ ){ - if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 ){ + if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 && ALWAYS((*ppStruct)!=0) ){ Fts5Structure *pStruct = *ppStruct; u64 nWrite; /* Initial value of write-counter */ int nWork; /* Number of work-quanta to perform */ @@ -5527,11 +5606,15 @@ int sqlite3Fts5IndexQuery( /* Scan multiple terms in the main index */ int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); - assert( p->rc!=SQLITE_OK || pRet->pColset==0 ); - fts5IterSetOutputCb(&p->rc, pRet); - if( p->rc==SQLITE_OK ){ - Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; - if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + if( pRet==0 ){ + assert( p->rc!=SQLITE_OK ); + }else{ + assert( pRet->pColset==0 ); + fts5IterSetOutputCb(&p->rc, pRet); + if( p->rc==SQLITE_OK ){ + Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; + if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + } } } @@ -5779,7 +5862,7 @@ static int fts5QueryCksum( Fts5IndexIter *pIter = 0; int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); - while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIter) ){ + while( rc==SQLITE_OK && ALWAYS(pIter!=0) && 0==sqlite3Fts5IterEof(pIter) ){ i64 rowid = pIter->iRowid; if( eDetail==FTS5_DETAIL_NONE ){ @@ -6144,6 +6227,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */ Fts5Iter *pIter; /* Used to iterate through entire index */ Fts5Structure *pStruct; /* Index structure */ + int iLvl, iSeg; #ifdef SQLITE_DEBUG /* Used by extra internal tests only run if NDEBUG is not defined */ @@ -6154,15 +6238,16 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ /* Load the FTS index structure */ pStruct = fts5StructureRead(p); + if( pStruct==0 ){ + assert( p->rc!=SQLITE_OK ); + return fts5IndexReturn(p); + } /* Check that the internal nodes of each segment match the leaves */ - if( pStruct ){ - int iLvl, iSeg; - for(iLvl=0; iLvlnLevel; iLvl++){ - for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ - Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; - fts5IndexIntegrityCheckSegment(p, pSeg); - } + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + fts5IndexIntegrityCheckSegment(p, pSeg); } } diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 3dea6cafc..fd3a9066b 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -1374,7 +1374,8 @@ static int fts5FilterMethod( pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->p.base.zErrMsg ); if( rc==SQLITE_OK ){ - if( pCsr->ePlan==FTS5_PLAN_ROWID ){ + if( pRowidEq!=0 ){ + assert( pCsr->ePlan==FTS5_PLAN_ROWID ); sqlite3_bind_value(pCsr->pStmt, 1, pRowidEq); }else{ sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid); diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index c5052b047..02b98d9e4 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -417,12 +417,16 @@ static int fts5StorageDeleteFromIndex( if( pConfig->abUnindexed[iCol-1]==0 ){ const char *zText; int nText; + assert( pSeek==0 || apVal==0 ); + assert( pSeek!=0 || apVal!=0 ); if( pSeek ){ zText = (const char*)sqlite3_column_text(pSeek, iCol); nText = sqlite3_column_bytes(pSeek, iCol); - }else{ + }else if( ALWAYS(apVal) ){ zText = (const char*)sqlite3_value_text(apVal[iCol-1]); nText = sqlite3_value_bytes(apVal[iCol-1]); + }else{ + continue; } ctx.szCol = 0; rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, @@ -1058,8 +1062,9 @@ int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ assert( p->pConfig->bColumnsize ); rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); - if( rc==SQLITE_OK ){ + if( pLookup ){ int bCorrupt = 1; + assert( rc==SQLITE_OK ); sqlite3_bind_int64(pLookup, 1, iRowid); if( SQLITE_ROW==sqlite3_step(pLookup) ){ const u8 *aBlob = sqlite3_column_blob(pLookup, 0); @@ -1072,6 +1077,8 @@ int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ if( bCorrupt && rc==SQLITE_OK ){ rc = FTS5_CORRUPT; } + }else{ + assert( rc!=SQLITE_OK ); } return rc; diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c index 48aa6939c..18774c4e4 100644 --- a/ext/fts5/fts5_vocab.c +++ b/ext/fts5/fts5_vocab.c @@ -60,6 +60,7 @@ struct Fts5VocabCursor { int bEof; /* True if this cursor is at EOF */ Fts5IndexIter *pIter; /* Term/rowid iterator object */ + void *pStruct; /* From sqlite3Fts5StructureRef() */ int nLeTerm; /* Size of zLeTerm in bytes */ char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */ @@ -373,7 +374,7 @@ static int fts5VocabOpenMethod( } if( rc==SQLITE_OK ){ - int nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor); + i64 nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor); pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte); } @@ -393,6 +394,8 @@ static int fts5VocabOpenMethod( static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){ pCsr->rowid = 0; sqlite3Fts5IterClose(pCsr->pIter); + sqlite3Fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; pCsr->pIter = 0; sqlite3_free(pCsr->zLeTerm); pCsr->nLeTerm = -1; @@ -470,9 +473,11 @@ static int fts5VocabInstanceNext(Fts5VocabCursor *pCsr){ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab; - int rc = SQLITE_OK; int nCol = pCsr->pFts5->pConfig->nCol; + int rc; + rc = sqlite3Fts5StructureTest(pCsr->pFts5->pIndex, pCsr->pStruct); + if( rc!=SQLITE_OK ) return rc; pCsr->rowid++; if( pTab->eType==FTS5_VOCAB_INSTANCE ){ @@ -646,6 +651,9 @@ static int fts5VocabFilterMethod( if( rc==SQLITE_OK ){ Fts5Index *pIndex = pCsr->pFts5->pIndex; rc = sqlite3Fts5IndexQuery(pIndex, zTerm, nTerm, f, 0, &pCsr->pIter); + if( rc==SQLITE_OK ){ + pCsr->pStruct = sqlite3Fts5StructureRef(pIndex); + } } if( rc==SQLITE_OK && eType==FTS5_VOCAB_INSTANCE ){ rc = fts5VocabInstanceNewTerm(pCsr); diff --git a/ext/fts5/test/fts5af.test b/ext/fts5/test/fts5af.test index 86c8f753f..a3ff330ef 100644 --- a/ext/fts5/test/fts5af.test +++ b/ext/fts5/test/fts5af.test @@ -165,7 +165,7 @@ do_execsql_test 4.0 { } do_execsql_test 5.0 { - CREATE VIRTUAL TABLE p1 USING fts5(a, b); + CREATE VIRTUAL TABLE p1 USING fts5(a, b, detail=%DETAIL%); INSERT INTO p1 VALUES( 'x a a a a a a a a a a', 'a a a a a a a a a a a a a a a a a a a x' @@ -184,6 +184,12 @@ do_execsql_test 5.3 { do_execsql_test 5.4 { SELECT snippet(p1, 0, '[', NULL, '...', 6) FROM p1('x'); } {{[x a a a a a...}} +do_execsql_test 5.5 { + SELECT snippet(p1, 0, '[', NULL, '...', 6) FROM p1('x OR ""'); +} {{[x a a a a a...}} +do_execsql_test 5.6 { + SELECT snippet(p1, 0, '[', NULL, '...', 6) FROM p1('x OR ' || x'DB'); +} {{[x a a a a a...}} } ;# foreach_detail_mode diff --git a/ext/fts5/test/fts5corrupt3.test b/ext/fts5/test/fts5corrupt3.test index 5604bca76..adfaa6d85 100644 --- a/ext/fts5/test/fts5corrupt3.test +++ b/ext/fts5/test/fts5corrupt3.test @@ -15135,6 +15135,237 @@ do_catchsql_test 77.1 { UPDATE t1 SET B =quote(zeroblob(200)) WHERE b MATCH 'threa*thrad*tlrad*the�d'; } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 78.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a,b,c); + DELETE FROM t1_data; + INSERT INTO t1_data VALUES(1,X'245a2424'); + INSERT INTO t1_data VALUES(10,X'000000000101010001010101'); + INSERT INTO t1_data VALUES(137438953473,X'0000032b0230300102060102060102061f0203010203010203010832303136303630390102070102070102070101340102050102050102050101350102040102040102040207303030303030301c0204010204010204010662696e6172790306010202030601020203060102020306010202030601020203060102020306010202030601020203060102020306010202030601020203060102020108636f6d70696c657201200102020201020201066462737ccccccccccccccccccccccccccccccccccccccccccccbccccccccccccccccccccccccccccccccccccccccccccccca2cccccccccc461740702030102030102030204656275670402020102020102020106656e61626c6507020201020201020201020201020201020201020201020201020201020201020201020201020201020201020201020201020201020523d6763632d352e342e30203230313630363039584e4f4341534526010500430f17434f4d50494c45523d6763632d352e342e3020323031363036303958525452494d0d000000240ee00004a810000fe80fe00fd80fd00fc80fc00fb80fb00fa80fa00f980f900f880f800f780f700f680f60945736502060102020306010202030601020203060102020306010202030601020203060102020306010202030601020203060102020306010202030601020201046f6d59741f0202010202010202010572747265651945030102030102030402696d010601020203060102020306010202030601120203060102020306010202030601020203060102020306010202030601020203060102020306010202010a7468726561647361666522020201020201020201047674616207020401020401020401407801060cb102010601010201060101020106010102010601010201060101020106010102010601010201060e0102010601010201060101020106010102010601010201060101020106010102010601010201060101020106010102010601010201060101021106010102010601010201060101020106010102010601010201060101020106010102010601010201060101020106010102010601011201060101020106010102010601010201060101020106010102041513020c124413110f47130f0c0e11100f0e100f440f1040150f'); +} + +do_execsql_test 78.1 { + CREATE VIRTUAL TABLE t3 USING fts5vocab('t1','col'); +} + +do_execsql_test 78.2 { + SELECT count(rowid) FROM t3 WHERE term>='nsocse'; +} 2 + +#------------------------------------------------------------------------- +reset_db +do_test 79.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 28672 pagesize 4096 filename sql053282.txt.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........ +| 32: 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 00 ................ +| 96: 00 00 00 00 0d 0f c7 00 06 0d b6 00 0f 8d 0f 36 ...............6 +| 112: 0e cb 0e 6b 0e 0e 0d b6 0d b5 00 00 00 00 00 00 ...k............ +| 3504: 00 00 00 00 00 00 56 07 06 17 1f 1f 01 7d 74 61 ......V.......ta +| 3520: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c +| 3536: 6f 6e 66 69 67 07 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB +| 3552: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k +| 3568: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) +| 3584: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 06 WITHOUT ROWID[. +| 3600: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d +| 3616: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize +| 3632: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't +| 3648: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN +| 3664: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE +| 3680: 59 2c 20 73 7a 20 42 4c 4f 42 29 5e 05 07 17 21 Y, sz BLOB)^...! +| 3696: 21 01 81 07 74 61 62 6c 65 74 31 5f 63 6f 6e 74 !...tablet1_cont +| 3712: 65 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 05 43 52 entt1_content.CR +| 3728: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 EATE TABLE 't1_c +| 3744: 6f 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 ontent'(id INTEG +| 3760: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 3776: 63 30 2c 20 63 31 2c 20 63 32 29 69 04 07 17 19 c0, c1, c2)i.... +| 3792: 19 01 81 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 ...-tablet1_idxt +| 3808: 31 5f 69 64 78 04 43 52 45 41 54 45 20 54 41 42 1_idx.CREATE TAB +| 3824: 4c 45 20 27 74 31 5f 69 64 78 27 28 73 65 67 69 LE 't1_idx'(segi +| 3840: 64 2c 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 d, term, pgno, P +| 3856: 52 49 4d 41 52 59 20 4b 45 59 28 73 65 67 69 64 RIMARY KEY(segid +| 3872: 2c 20 74 65 72 6d 29 29 20 57 49 54 48 4f 55 54 , term)) WITHOUT +| 3888: 20 52 4f 57 49 44 55 03 07 17 1b 1b 01 81 01 74 ROWIDU........t +| 3904: 61 62 6c 65 74 31 5f 64 61 74 61 74 31 5f 64 61 ablet1_datat1_da +| 3920: 74 61 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 ta.CREATE TABLE +| 3936: 27 74 31 5f 64 61 74 61 27 28 69 64 20 49 4e 54 't1_data'(id INT +| 3952: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY +| 3968: 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 38 02 06 , block BLOB)8.. +| 3984: 17 11 11 08 5f 74 61 62 6c 65 74 31 74 31 43 52 ...._tablet1t1CR +| 4000: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4016: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 LE t1 USING fts5 +| 4032: 28 61 2c 62 2c 63 29 00 00 00 00 00 00 00 00 00 (a,b,c)......... +| page 3 offset 8192 +| 0: 0d 00 00 00 03 0c 94 00 0f e6 0f ef 0c 94 00 01 ................ +| 3216: 00 00 00 00 86 4a 84 80 80 80 80 01 04 00 8d 18 .....J.......... +| 3232: 00 00 03 2b 02 30 30 01 02 06 01 02 06 01 02 06 ...+.00......... +| 3248: 1f 02 03 01 02 03 01 02 03 01 08 32 30 31 36 30 ...........20160 +| 3264: 36 30 39 01 02 07 01 02 07 01 02 07 01 01 34 01 609...........4. +| 3280: 02 05 01 02 05 01 02 05 01 01 35 01 02 04 01 02 ..........5..... +| 3296: 04 01 02 04 02 07 30 30 30 30 30 30 30 1c 02 04 ......0000000... +| 3312: 01 02 04 01 02 04 01 06 62 69 6e 61 72 79 03 06 ........binary.. +| 3328: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3344: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3360: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3376: 03 06 01 02 02 03 06 01 02 02 01 08 63 6f 6d 70 ............comp +| 3392: 69 6c 65 72 01 02 02 01 02 02 01 02 02 01 06 64 iler...........d +| 3408: 62 73 74 61 74 07 02 03 01 02 03 01 02 03 02 04 bstat........... +| 3424: 65 62 75 67 04 02 02 01 02 02 01 02 02 01 06 65 ebug...........e +| 3440: 6e 61 62 6c 65 07 02 02 01 02 02 01 02 02 01 02 nable........... +| 3456: 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 ................ +| 3472: 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 ................ +| 3488: 02 02 01 02 02 01 02 02 01 02 12 01 02 02 01 02 ................ +| 3504: 02 01 02 02 02 08 78 74 65 6e 73 69 6f 6e 1f 02 ......xtension.. +| 3520: 04 01 02 04 01 02 04 01 04 66 74 73 34 0a 02 03 .........fts4... +| 3536: 01 02 03 01 02 03 04 01 35 0d 02 03 01 02 03 01 ........5....... +| 3552: 02 03 01 03 67 63 63 01 02 03 01 02 03 01 02 03 ....gcc......... +| 3568: 02 06 65 6f 70 6f 6c 79 10 02 03 01 02 03 01 02 ..eopoly........ +| 3584: 03 01 05 6a 73 6f 6e 31 13 02 03 01 02 03 01 02 ...json1........ +| 3600: 03 01 04 6c 6f 61 64 1f 02 03 01 02 03 01 02 03 ...load......... +| 3616: 01 03 6d 61 78 1c 02 02 01 02 02 01 02 02 02 05 ..max........... +| 3632: 65 6d 6f 72 79 1c 02 03 01 02 03 01 02 03 04 04 emory........... +| 3648: 73 79 73 35 16 02 03 01 02 03 01 02 03 01 06 6e sys5...........n +| 3664: 6f 63 61 73 65 02 06 01 02 02 03 06 01 02 02 03 ocase........... +| 3680: 06 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 ................ +| 3696: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3712: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3728: 02 01 04 6f 6d 69 74 1f 02 02 01 02 02 01 02 02 ...omit......... +| 3744: 01 05 72 74 72 65 65 19 02 03 01 02 03 01 02 03 ..rtree......... +| 3760: 04 02 69 6d 01 06 01 02 02 03 06 01 02 02 03 06 ..im............ +| 3776: 01 02 02 03 06 01 01 02 03 06 01 02 02 03 06 01 ................ +| 3792: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3808: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3824: 01 0a 74 68 72 65 61 64 73 61 66 65 22 02 02 01 ..threadsafe.... +| 3840: 02 02 01 02 02 01 04 76 74 61 62 07 02 04 01 02 .......vtab..... +| 3856: 04 01 02 04 01 01 78 01 05 f1 01 02 01 06 01 01 ......x......... +| 3872: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 ................ +| 3888: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3904: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 3920: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 3936: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 3952: 01 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 ................ +| 3968: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3984: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 4000: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 4016: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 4032: 02 01 06 01 01 02 01 06 01 01 02 04 15 13 0c 0c ................ +| 4048: 12 44 13 11 0f 47 13 0f 0c 0e 11 10 0f 0e 10 0f .D...G.......... +| 4064: 44 0f 10 40 15 0f 07 01 03 00 14 24 5a 24 24 0f D..@.......$Z$$. +| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ +| page 4 offset 12288 +| 0: 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 5 offset 16384 +| 0: 0d 00 00 00 24 0c 0a 00 0f d8 0f af 0f 86 0f 74 ....$..........t +| 16: 0f 61 0f 4e 0f 2f 0f 0f 0e ef 0e d7 0e be 0e a5 .a.N./.......... +| 32: 0e 8d 0e 74 0e 5b 0e 40 0e 24 0e 08 0d ef 0d d5 ...t.[.@.$...... +| 48: 0d bb 0d a0 0d 84 0d 68 0d 4f 0d 00 00 00 00 00 .......h.O...... +| 3072: 00 00 00 00 00 00 00 00 00 00 18 24 05 00 25 0f ...........$..%. +| 3088: 19 54 48 52 45 41 44 53 41 46 45 3d 30 58 42 49 .THREADSAFE=0XBI +| 3104: 4e 41 52 59 18 23 05 00 25 0f 19 54 48 52 45 41 NARY.#..%..THREA +| 3120: 44 53 41 46 45 3d 30 58 4e 4f 43 41 53 45 17 22 DSAFE=0XNOCASE.. +| 3136: 05 00 25 0f 17 54 48 52 45 41 44 53 41 46 45 3d ..%..THREADSAFE= +| 3152: 30 58 52 54 52 49 4d 1f 21 05 00 33 0f 19 4f 4d 0XRTRIM.!..3..OM +| 3168: 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 4f IT LOAD EXTENSIO +| 3184: 4e 58 42 49 4e 41 52 59 1f 20 05 00 33 0f 19 4f NXBINARY. ..3..O +| 3200: 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 MIT LOAD EXTENSI +| 3216: 4f 4e 58 4e 4f 43 41 53 45 1e 1f 05 00 33 0f 17 ONXNOCASE....3.. +| 3232: 4f 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 OMIT LOAD EXTENS +| 3248: 49 4f 4e 58 52 54 52 49 4d 1f 1e 05 00 33 0f 19 IONXRTRIM....3.. +| 3264: 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 30 MAX MEMORY=50000 +| 3280: 30 30 30 58 42 49 4e 41 52 59 1f 1d 05 00 33 0f 000XBINARY....3. +| 3296: 19 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 .MAX MEMORY=5000 +| 3312: 30 30 30 30 58 4e 4f 43 41 53 45 1e 1c 05 00 33 0000XNOCASE....3 +| 3328: 0f 17 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 ..MAX MEMORY=500 +| 3344: 30 30 30 30 30 58 52 54 52 49 4d 18 1b 05 00 2c 00000XRTRIM...., +| 3360: 0f 19 45 4e 41 42 4c 45 20 52 54 52 45 45 58 42 ..ENABLE RTREEXB +| 3376: 49 4e 41 52 59 18 1a 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3392: 4c 45 20 52 54 52 45 45 58 4e 4f 43 41 53 45 17 LE RTREEXNOCASE. +| 3408: 19 05 00 25 0f 17 45 4e 41 42 4c 45 20 52 54 52 ...%..ENABLE RTR +| 3424: 45 45 58 52 54 52 49 4d 1a 18 05 00 29 0f 19 45 EEXRTRIM....)..E +| 3440: 4e 41 42 4c 45 20 4d 45 4d 5a 69 53 35 58 42 49 NABLE MEMZiS5XBI +| 3456: 4e 41 52 59 1a 17 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3472: 45 20 4d 45 4d 53 59 53 35 58 4e 4f 43 41 53 45 E MEMSYS5XNOCASE +| 3488: 19 16 05 00 29 0f 17 45 4e 41 42 4c 45 20 4d 45 ....)..ENABLE ME +| 3504: 4d 53 59 53 35 58 52 54 52 49 4d 18 15 05 00 25 MSYS5XRTRIM....% +| 3520: 0f 19 45 4e 41 42 4c 45 20 4a 53 4f 4e 31 58 42 ..ENABLE JSON1XB +| 3536: 49 4e 41 52 59 18 14 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3552: 4c 45 20 4a 53 4f 4e 31 58 4e 4f 43 41 53 45 17 LE JSON1XNOCASE. +| 3568: 13 05 00 25 0f 17 45 4e 41 42 4c 45 20 4a 53 4f ...%..ENABLE JSO +| 3584: 4e 31 58 52 54 52 49 4d 1a 12 05 00 29 0f 19 45 N1XRTRIM....)..E +| 3600: 4e 41 42 4c 45 20 47 45 4f 50 4f 4c 59 58 42 49 NABLE GEOPOLYXBI +| 3616: 4e 41 52 59 1a 11 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3632: 45 20 47 45 4f 50 4f 4c 59 58 4e 4f 43 41 53 45 E GEOPOLYXNOCASE +| 3648: 19 10 05 00 29 0f 17 45 4e 41 42 4c 45 20 47 45 ....)..ENABLE GE +| 3664: 4f 50 4f 4c 59 58 52 54 52 49 4d 17 0f 05 00 23 OPOLYXRTRIM....# +| 3680: 0f 19 45 4e 41 42 4c 45 20 46 54 53 35 58 42 49 ..ENABLE FTS5XBI +| 3696: 4e 41 52 59 17 0e 05 00 23 0f 19 45 4e 41 42 4c NARY....#..ENABL +| 3712: 45 20 46 54 53 35 58 4e 4f 43 41 53 45 16 0d 05 E FTS5XNOCASE... +| 3728: 00 23 0f 17 45 4e 41 42 4c 45 20 46 54 53 3f d8 .#..ENABLE FTS?. +| 3744: 52 54 52 49 4d 17 0c 05 00 23 0f 19 45 4e 41 42 RTRIM....#..ENAB +| 3760: 4c 45 20 46 54 53 34 58 42 49 4e 41 52 59 17 0b LE FTS4XBINARY.. +| 3776: 05 00 23 0f 19 45 4e 41 42 4c 45 20 46 54 53 34 ..#..ENABLE FTS4 +| 3792: 58 4e 4f 43 41 53 45 16 0a 05 00 23 0f 17 45 4e XNOCASE....#..EN +| 3808: 41 42 4c 45 20 46 54 53 34 58 52 54 52 49 4d 1e ABLE FTS4XRTRIM. +| 3824: 09 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3840: 55 41 54 20 56 54 41 42 58 42 49 4e 41 52 59 1e UAT VTABXBINARY. +| 3856: 08 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3872: 54 41 54 20 56 54 41 42 58 4e 4f 43 41 53 45 1d TAT VTABXNOCASE. +| 3888: 07 05 00 31 0f 17 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3904: 54 41 54 20 56 54 41 42 58 52 54 52 49 4d 11 06 TAT VTABXRTRIM.. +| 3920: 05 00 17 0f 19 44 45 42 55 47 58 42 49 4e 41 52 .....DEBUGXBINAR +| 3936: 59 11 05 05 00 17 0f 19 44 45 42 55 47 58 4e 4f Y.......DEBUGXNO +| 3952: 43 41 53 45 10 04 05 00 17 0f 17 44 45 42 55 46 CASE.......DEBUF +| 3968: e8 52 54 52 49 4d 27 03 05 00 43 0f 19 43 4f 4d .RTRIM'...C..COM +| 3984: 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e 30 20 PILER=gcc-5.4.0 +| 4000: 32 30 31 36 30 36 30 39 58 42 49 4e 41 52 59 27 20160609XBINARY' +| 4016: 02 05 00 43 0f 19 43 4f 4d 50 49 4c 45 52 3d 67 ...C..COMPILER=g +| 4032: 63 63 2d 35 2e 34 2e 30 20 32 30 31 36 30 36 30 cc-5.4.0 2016060 +| 4048: 39 58 4e 4f 43 41 53 45 26 01 05 00 43 0f 17 43 9XNOCASE&...C..C +| 4064: 4f 4d 50 49 4c 45 52 3d 67 62 63 2d 35 2e 34 2e OMPILER=gbc-5.4. +| 4080: 30 20 32 30 31 36 30 36 30 39 58 52 54 52 49 4d 0 20160609XRTRIM +| page 6 offset 20480 +| 0: 0d 00 00 00 24 0e e0 00 0f f8 0f f0 0f e8 0f e0 ....$........... +| 16: 0f d8 0f d0 0f c8 0f c0 0f b8 0f bb 1f a8 0f a0 ................ +| 32: 0f 98 0f 90 0f 88 0f 80 0f 78 0f 70 0f 68 0f 60 .........x.p.h.` +| 48: 0f 58 0f 50 0f 48 0f 40 0f 38 0f 30 00 00 00 00 .X.P.H.@.8.0.... +| 3808: 06 24 03 00 12 02 01 01 06 23 03 00 12 02 01 01 .$.......#...... +| 3824: 06 22 03 00 12 02 01 01 06 21 03 00 12 03 01 01 .........!...... +| 3840: 06 20 03 00 12 03 01 01 06 1f 03 00 12 03 01 01 . .............. +| 3856: 06 1e 03 00 12 03 01 01 06 1d 03 00 12 03 01 01 ................ +| 3872: 06 1c 03 00 12 03 01 01 06 1b 03 00 12 02 01 01 ................ +| 3888: 06 1a 03 00 12 02 01 01 06 19 03 00 12 02 01 01 ................ +| 3904: 06 18 03 00 12 02 01 01 06 17 03 00 12 02 01 01 ................ +| 3920: 06 16 03 00 12 02 01 01 06 15 03 00 12 02 01 01 ................ +| 3936: 06 14 03 00 12 02 01 01 06 13 03 00 12 02 01 01 ................ +| 3952: 06 12 03 00 12 02 01 01 06 11 03 00 12 02 01 01 ................ +| 3968: 06 10 03 00 12 02 01 01 06 0f 03 00 12 02 01 01 ................ +| 3984: 06 0e 03 00 12 02 01 01 06 0d 03 00 12 02 01 01 ................ +| 4000: 06 0c 03 00 12 02 01 01 06 0b 03 00 12 02 01 01 ................ +| 4016: 06 0a 03 00 12 02 01 01 06 09 03 00 12 03 01 01 ................ +| 4032: 06 08 03 00 12 03 01 01 06 07 03 00 12 03 01 01 ................ +| 4048: 06 06 03 00 12 01 01 01 06 05 03 00 12 01 01 01 ................ +| 4064: 06 04 03 00 12 01 01 01 06 03 03 00 12 06 01 01 ................ +| 4080: 06 0a 83 00 12 06 01 01 06 01 03 00 12 06 01 01 ................ +| page 7 offset 24576 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| end sql053282.txt.db +}]} {} + +do_execsql_test 79.1 { + CREATE VIRTUAL TABLE t2 USING fts5vocab('t1','row'); +} + +do_catchsql_test 79.2 { + INSERT INTO t1(t1) SELECT 'merge' FROM t2; +} {1 {query aborted}} + sqlite3_fts5_may_be_corrupt 0 finish_test diff --git a/ext/fts5/test/fts5corrupt5.test b/ext/fts5/test/fts5corrupt5.test new file mode 100644 index 000000000..16682b132 --- /dev/null +++ b/ext/fts5/test/fts5corrupt5.test @@ -0,0 +1,798 @@ +# 2015 Apr 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 tests that FTS5 handles corrupt databases (i.e. internal +# inconsistencies in the backing tables) correctly. In this case +# "correctly" means without crashing. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5corrupt3 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} +sqlite3_fts5_may_be_corrupt 1 +database_may_be_corrupt + +#------------------------------------------------------------------------- +# dbsqlfuzz crash-0f47112aa7520cf08c6a835a88fdff8c2a32a188 +# +reset_db +do_test 1.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 24576 pagesize 4096 filename crash-0f47112aa7520c.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 00 .....@ ........ +| 96: 00 00 00 00 0d 00 00 00 06 0e 0f 00 0f aa 0f 53 ...............S +| 112: 0e e8 0e 8b 0e 33 0e 0f 00 00 00 00 00 00 00 00 .....3.......... +| 3584: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 22 ................ +| 3600: 06 06 17 11 11 01 31 74 61 62 6c 65 62 62 62 62 ......1tablebbbb +| 3616: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 62 62 .CREATE TABLE bb +| 3632: 28 61 29 56 05 06 17 1f 1f 01 7d 74 61 62 6c 65 (a)V.......table +| 3648: 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 t1_configt1_conf +| 3664: 69 67 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 ig.CREATE TABLE +| 3680: 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 't1_config'(k PR +| 3696: 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 20 57 49 IMARY KEY, v) WI +| 3712: 54 48 4f 55 54 20 52 4f 57 49 44 5b 04 07 17 21 THOUT ROWID[...! +| 3728: 21 01 81 01 74 61 62 6c 65 74 31 5f 64 6f 63 73 !...tablet1_docs +| 3744: 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 04 43 52 izet1_docsize.CR +| 3760: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 EATE TABLE 't1_d +| 3776: 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 54 45 47 ocsize'(id INTEG +| 3792: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 3808: 73 7a 20 42 4c 4f 42 29 69 03 07 17 19 19 01 81 sz BLOB)i....... +| 3824: 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 -tablet1_idxt1_i +| 3840: 64 78 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 dx.CREATE TABLE +| 3856: 27 74 31 5f 69 64 78 27 28 73 65 67 69 64 2c 20 't1_idx'(segid, +| 3872: 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d term, pgno, PRIM +| 3888: 41 52 59 20 4b 45 59 28 73 65 67 69 64 2c 20 74 ARY KEY(segid, t +| 3904: 65 72 6d 29 29 20 57 49 54 48 4f 55 54 20 52 4f erm)) WITHOUT RO +| 3920: 57 49 44 55 02 07 17 1b 1b 01 81 01 74 61 62 6c WIDU........tabl +| 3936: 65 74 31 5f 64 61 74 61 74 31 5f 64 61 74 61 02 et1_datat1_data. +| 3952: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 3968: 5f 64 61 74 61 27 28 69 64 20 49 4e 54 45 47 45 _data'(id INTEGE +| 3984: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 R PRIMARY KEY, b +| 4000: 6c 6f 63 6b 20 42 4c 4f 42 29 54 01 07 17 11 11 lock BLOB)T..... +| 4016: 08 81 15 74 61 62 6c 65 74 31 74 31 43 52 45 41 ...tablet1t1CREA +| 4032: 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 4c 45 TE VIRTUAL TABLE +| 4048: 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 28 61 t1 USING fts5(a +| 4064: 2c 62 2c 70 72 65 66 69 78 3d 22 31 2c 32 2c 33 ,b,prefix=.1,2,3 +| 4080: 2c 34 22 2c 20 63 6f 6e 74 65 6e 74 3d 22 22 29 ,4., content=..) +| page 2 offset 4096 +| 0: 0d 0b 6a 00 37 09 4c 02 0f e7 09 4c 0f c6 0f a4 ..j.7.L....L.... +| 16: 0f 88 0f 6d 0f 4b 0f 2c 0f 0e 0e ec 0e cd 0e ad ...m.K.,........ +| 32: 0e 8e 0e 6c 0e 4b 0e 29 0e 08 0d e6 0d c4 0d b5 ...l.K.)........ +| 48: 0d 97 0d 76 0d 54 0d 31 0d 15 0c f3 0c d3 0c b5 ...v.T.1........ +| 64: 0c 95 0c 73 0c 54 0c 32 0c 10 0b ee 0b cc 0b b0 ...s.T.2........ +| 80: 0b 8d 0b 7e 0b 48 0b 2e 0b 0b 0a ef 00 00 00 00 ...~.H.......... +| 2368: 00 00 00 00 00 00 00 00 00 00 00 00 15 0a 03 00 ................ +| 2384: 30 00 00 00 01 01 03 35 00 03 01 11 12 02 01 12 0......5........ +| 2400: 03 01 11 1c 8c 80 80 80 80 10 03 00 3e 00 00 00 ............>... +| 2416: 17 01 05 05 34 74 61 62 6c 03 02 03 01 04 77 68 ....4tabl.....wh +| 2432: 65 72 03 02 06 09 1b 8c 80 80 80 80 0f 03 00 3c er.............< +| 2448: 00 00 00 16 05 34 66 74 73 34 03 02 02 01 04 6e .....4fts4.....n +| 2464: 75 6d 62 03 06 01 04 09 1b 8c 80 80 80 80 0e 03 umb............. +| 2480: 00 3c 00 00 00 16 04 33 74 68 65 03 06 01 01 04 .<.....3the..... +| 2496: 01 03 77 68 65 03 02 04 04 0a 1b 8c 80 80 80 80 ..whe........... +| 2512: 0d 03 00 3c 00 00 00 16 04 33 6e 75 6d 03 06 01 ...<.....3num... +| 2528: 01 05 01 03 74 61 62 03 02 03 04 0a 19 8c 80 80 ....tab......... +| 2544: 80 80 0c 03 00 38 00 00 00 14 03 32 77 68 03 02 .....8.....2wh.. +| 2560: 04 00 04 33 66 74 73 03 02 02 04 07 18 8c 80 80 ...3fts......... +| 2576: 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 03 02 .....6.....2ta.. +| 2592: 03 02 01 68 03 06 01 01 04 04 07 1b 8c 80 80 80 ...h............ +| 2608: 80 0a 03 00 3c 00 00 00 16 03 32 6e 75 03 06 01 ....<.....2nu... +| 2624: 01 05 01 02 6f 66 03 06 01 01 06 04 09 19 8c 80 ....of.......... +| 2640: 80 80 80 09 03 00 38 00 00 00 14 03 32 66 74 03 ......8.....2ft. +| 2656: 02 02 01 02 69 73 03 06 01 01 03 04 07 18 8c 80 ....is.......... +| 2672: 80 80 80 08 03 00 36 00 00 00 13 02 31 74 03 08 ......6.....1t.. +| 2688: 03 01 01 04 01 01 77 03 02 04 04 09 1a 8c 80 80 ......w......... +| 2704: 80 80 07 03 00 3a 00 00 00 15 02 31 6e 03 08 01 .....:.....1n... +| 2720: 01 02 05 01 00 6f 03 06 01 01 06 04 09 18 8c 80 .....o.......... +| 2736: 80 80 80 06 03 00 36 00 00 00 03 04 02 31 66 03 ......6......1f. +| 2752: 02 02 01 01 69 03 06 01 01 03 04 f6 1c 8c 80 80 ....i........... +| 2768: 80 80 05 03 00 3e 00 00 00 17 04 30 74 68 65 03 .....>.....0the. +| 2784: f6 01 01 04 01 05 77 68 65 72 65 03 02 04 0a 15 ......where..... +| 2800: 8c 80 80 80 80 04 03 00 30 00 00 00 11 01 01 06 ........0....... +| 2816: 06 30 74 61 62 6c 65 0f 42 03 07 1c 8c 81 80 80 .0table.B....... +| 2832: 80 03 03 00 3e 00 00 00 17 07 30 6e 75 6d 62 65 ....>.....0numbe +| 2848: 72 03 06 01 01 05 01 02 6f 66 03 06 04 0d 13 8c r.......of...... +| 2864: 80 80 80 80 02 03 00 2c 00 00 00 0f 01 01 03 02 .......,........ +| 2880: 30 6e 03 06 01 01 02 07 1b 8c 80 80 80 80 01 03 0n.............. +| 2896: 00 3c 00 00 00 16 08 30 66 74 73 34 61 75 78 03 .<.....0fts4aux. +| 2912: 02 02 01 02 69 73 03 06 04 0c 00 00 00 14 2a 00 ....is........*. +| 2928: 00 00 01 01 02 24 00 02 01 01 12 02 01 12 08 88 .....$.......... +| 2944: 80 80 80 80 12 03 00 16 00 00 00 05 02 1c 88 80 ................ +| 2960: 80 80 80 11 03 00 3e 00 00 00 17 05 34 72 6f 77 ......>.....4row +| 2976: 73 02 06 01 01 05 01 04 74 68 65 72 02 02 04 0b s.......ther.... +| 2992: 15 88 80 80 80 80 10 03 00 30 00 00 00 11 02 01 .........0...... +| 3008: 01 07 05 34 62 65 74 77 02 02 04 08 1b 88 80 80 ...4betw........ +| 3024: 80 80 0f 03 00 3c 00 00 00 16 04 04 33 72 6f 77 .....<......3row +| 3040: 02 06 01 01 05 01 03 74 68 65 02 08 05 0a 1b 88 .......the...... +| 3056: 80 80 80 80 0e 03 00 3c 00 00 00 16 01 01 02 04 .......<........ +| 3072: 33 61 72 65 02 02 03 01 03 62 65 74 02 02 07 08 3are.....bet.... +| 3088: 1b 88 80 80 80 80 0d 03 00 3c 00 00 00 16 13 32 .........<.....2 +| 3104: 74 68 02 08 02 01 01 07 00 04 33 61 6e 64 02 06 th........3and.. +| 3120: 04 0a 1b 88 80 80 80 80 0c 03 00 3c 00 00 00 16 ...........<.... +| 3136: 03 32 69 6e 02 06 01 01 06 01 02 72 6f 02 06 01 .2in.......ro... +| 3152: 01 05 04 09 18 88 80 80 80 80 0b 03 00 36 00 00 .............6.. +| 3168: 00 13 02 03 32 61 72 02 02 03 01 02 62 65 02 02 ....2ar.....be.. +| 3184: 04 05 07 1b 88 80 80 80 80 0a 03 00 3c 00 9e 00 ............<... +| 3200: 16 02 31 74 02 08 02 01 01 07 00 03 32 61 6e 02 ..1t........2an. +| 3216: 06 01 01 04 09 19 88 80 80 80 80 09 03 00 38 00 ..............8. +| 3232: 00 00 14 02 31 6e 02 06 01 01 03 01 01 72 02 06 ....1n.......r.. +| 3248: 01 01 05 04 08 17 88 80 80 80 80 08 03 00 34 00 ..............4. +| 3264: 00 00 12 02 31 62 02 02 04 01 01 69 02 06 01 01 ....1b.....i.... +| 3280: 06 04 06 19 88 80 80 80 80 07 03 00 38 00 00 00 ............8... +| 3296: 14 04 02 31 32 02 02 05 01 01 61 02 08 03 01 01 ...12.....a..... +| 3312: 02 05 06 1b 88 80 80 80 80 06 03 00 3c 00 00 00 ............<... +| 3328: 16 06 30 74 68 65 72 65 02 02 02 00 02 31 31 02 ..0there.....11. +| 3344: 06 01 01 04 0a 15 88 80 80 80 80 05 03 00 30 00 ..............0. +| 3360: 00 00 11 01 01 05 04 30 74 68 65 02 06 01 01 07 .......0the..... +| 3376: 07 1c 88 80 80 80 80 04 03 00 3e 00 00 00 17 01 ..........>..... +| 3392: 01 06 02 30 6e 02 06 01 01 03 01 04 72 6f 77 73 ...0n.......rows +| 3408: 02 06 07 08 1b 88 80 80 80 80 03 03 00 3c 00 00 .............<.. +| 3424: 00 16 08 30 62 65 74 77 65 65 6e 02 02 04 01 02 ...0between..... +| 3440: 69 6e 02 06 04 0c 1a 88 80 80 80 80 02 03 00 3a in.............: +| 3456: 00 00 00 15 04 30 61 6e 64 02 06 01 01 02 02 02 .....0and....... +| 3472: 72 65 02 02 03 04 0a 17 88 80 80 80 80 01 03 00 re.............. +| 3488: 34 00 00 00 12 02 30 31 02 06 01 01 04 01 01 32 4.....01.......2 +| 3504: 02 02 05 04 08 08 84 80 80 80 80 12 03 00 16 00 ................ +| 3520: 00 00 05 04 1b 84 80 80 80 80 11 03 00 3c 00 00 .............<.. +| 3536: 00 16 05 34 74 61 62 6c 01 06 01 01 05 02 03 65 ...4tabl.......e +| 3552: 72 6d 01 02 04 0b 1b 84 80 80 80 80 10 03 00 3c rm.............< +| 3568: 00 00 00 16 05 34 65 61 63 68 01 02 03 01 04 70 .....4each.....p +| 3584: 72 65 73 01 02 05 04 08 1a 84 80 80 80 80 0f 03 res............. +| 3600: 00 3a 00 00 00 15 04 33 74 65 72 01 02 04 02 02 .:.....3ter..... +| 3616: 68 65 01 06 01 01 03 04 08 1b 84 80 80 80 80 0e he.............. +| 3632: 03 00 3c 00 00 00 16 04 33 80 72 65 01 02 05 01 ..<.....3.re.... +| 3648: 03 74 61 62 01 06 01 01 05 04 08 1a 84 80 80 80 .tab............ +| 3664: 80 0d 03 00 3a 00 00 00 15 04 33 66 6f 72 01 02 ....:.....3for.. +| 3680: 02 02 02 74 73 01 06 01 01 04 04 08 1b 84 80 80 ...ts........... +| 3696: 80 80 0c 03 00 3c 00 00 00 17 03 32 74 68 01 06 .....<.....2th.. +| 3712: 01 01 03 00 04 33 65 61 63 01 02 03 04 09 18 84 .....3eac....... +| 3728: 80 80 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 .......6.....2ta +| 3744: 01 06 01 01 05 02 01 65 01 02 04 04 09 19 84 80 .......e........ +| 3760: 80 80 80 0a 03 00 38 00 00 00 14 03 32 69 6e 01 ......8.....2in. +| 3776: 06 01 01 02 01 02 70 72 01 02 05 04 09 18 84 80 ......pr........ +| 3792: 80 80 80 09 03 00 36 00 00 00 13 03 32 66 6f 01 ......6.....2fo. +| 3808: 02 02 02 01 74 01 06 01 01 04 04 07 1b 84 80 80 ....t........... +| 3824: 80 80 08 03 00 3c 00 00 00 16 02 31 74 01 0a 04 .....<.....1t... +| 3840: 01 01 03 04 00 03 32 65 61 01 02 03 04 0a 17 84 ......2ea....... +| 3856: 80 80 80 80 07 03 00 34 00 00 00 12 02 31 69 01 .......4.....1i. +| 3872: 06 01 01 02 01 01 70 01 02 05 04 08 18 84 80 80 ......p......... +| 3888: 80 80 06 03 00 36 00 00 00 13 02 31 65 01 02 03 .....6.....1e... +| 3904: 01 01 66 01 08 02 01 01 04 04 06 1b 84 80 80 80 ..f............. +| 3920: 80 05 03 00 3c 00 00 00 16 05 30 74 65 72 6d 01 ....<.....0term. +| 3936: 02 04 02 02 68 65 01 06 01 01 03 04 09 14 84 80 ....he.......... +| 3952: 80 80 80 04 03 00 2e 00 00 00 10 06 30 64 61 62 ............0dab +| 3968: 6c 65 01 06 01 01 05 04 15 84 80 80 80 80 03 03 le.............. +| 3984: 00 30 00 00 00 11 02 08 30 70 72 65 73 65 6e 74 .0......0present +| 4000: 01 02 05 05 1b 84 80 80 80 80 02 03 00 3c 00 00 .............<.. +| 4016: 00 16 04 30 66 74 73 01 06 01 01 04 01 02 69 6e ...0fts.......in +| 4032: 01 06 01 01 04 0a 1a 84 80 80 80 80 01 03 00 3a ...............: +| 4048: 00 00 00 15 05 30 65 61 63 68 01 02 03 01 13 66 .....0each.....f +| 4064: 6f 72 01 02 02 04 09 06 01 03 00 12 03 0b 0f 00 or.............. +| 4080: 00 08 8c 80 80 80 80 11 03 00 16 00 00 00 05 04 ................ +| page 3 offset 8192 +| 0: 0a 00 00 00 32 0e 4f 00 0f fa 0f f1 0f e9 0f e1 ....2.O......... +| 16: 0f d8 0f d1 0f c9 0f c1 0f b9 0f b1 0f a9 0f a0 ................ +| 32: 0f 98 0f 90 0f 87 0f 80 0f 78 0f 71 0f 68 0f 5f .........x.q.h._ +| 48: 0f 56 0f 4d 0f 41 0f 38 0f 2f 0f 26 0f 1d 0f 13 .V.M.A.8./.&.... +| 64: 0f 0a 0f 01 0e f7 0e ee 0e e6 0e dd 0e d6 0e cd ................ +| 80: 0e c3 0e ba 0e 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3648: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 ................ +| 3664: 04 01 10 01 03 34 74 20 07 04 01 0e 01 03 34 1e .....4t ......4. +| 3680: 09 04 01 12 01 03 33 74 68 1c 08 04 01 10 01 03 ......3th....... +| 3696: 33 6e 1a 08 04 01 10 01 03 32 77 18 08 04 01 10 3n.......2w..... +| 3712: 01 03 32 74 16 08 04 01 10 01 03 32 6e 14 07 04 ..2t.......2n... +| 3728: 01 0e 01 03 32 12 08 04 01 10 01 03 31 74 10 08 ....2.......1t.. +| 3744: 04 01 10 01 03 31 6e 0e 07 04 01 0e 01 03 31 0c .....1n.......1. +| 3760: 09 04 01 12 01 03 30 74 68 0a 08 04 01 10 01 03 ......0th....... +| 3776: 30 74 08 09 04 01 12 01 03 30 6e 75 06 08 04 01 0t.......0nu.... +| 3792: 10 01 03 30 6e 04 06 04 01 0c 01 03 02 08 04 01 ...0n........... +| 3808: 10 01 02 34 72 22 07 04 01 0e 01 02 34 20 08 04 ...4r.......4 .. +| 3824: 01 10 01 02 33 72 1e 09 04 01 12 01 02 33 61 72 ....3r.......3ar +| 3840: 1c 08 04 01 10 01 02 32 74 1a 08 04 01 10 01 02 .......2t....... +| 3856: 32 69 18 09 04 01 12 01 02 32 61 72 16 08 04 01 2i.......2ar.... +| 3872: 10 01 02 31 74 14 08 04 01 10 01 02 31 6e 12 08 ...1t.......1n.. +| 3888: 04 01 10 01 02 31 62 10 08 04 01 10 01 02 31 32 .....1b.......12 +| 3904: 0e 0b 04 01 16 01 02 30 74 68 65 72 0c 08 04 01 .......0ther.... +| 3920: 10 01 02 30 74 0a 08 04 01 10 01 02 30 6e 08 08 ...0t.......0n.. +| 3936: 14 01 10 01 02 30 62 06 08 04 01 10 01 02 30 61 .....0b.......0a +| 3952: 04 06 04 01 0c 01 02 02 07 04 09 10 01 34 74 22 .............4t. +| 3968: 06 04 09 0e 01 34 20 08 04 09 12 01 33 74 65 1e .....4 .....3te. +| 3984: 07 04 09 10 01 33 70 1c 07 04 09 10 01 33 66 1a .....3p......3f. +| 4000: 08 04 09 12 01 32 74 68 18 07 04 09 10 01 32 74 .....2th......2t +| 4016: 16 01 64 09 10 01 32 69 14 07 04 09 10 01 32 66 ..d...2i......2f +| 4032: 12 07 04 09 10 01 31 74 10 07 04 09 10 01 31 69 ......1t......1i +| 4048: 0e 06 04 09 0e 01 31 0c 08 04 09 12 01 30 74 65 ......1......0te +| 4064: 0a 06 04 09 10 01 30 74 08 07 04 09 10 01 30 70 ......0t......0p +| 4080: 06 08 04 09 12 00 00 00 00 00 00 00 00 00 00 00 ................ +| page 4 offset 12288 +| 4064: 00 00 00 00 00 00 00 00 00 00 00 05 03 03 00 10 ................ +| 4080: 03 05 05 02 03 00 10 04 06 05 01 03 00 10 04 04 ................ +| page 5 offset 16384 +| 0: 0a 00 00 00 02 0f eb 00 0f eb 0f f4 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 08 03 15 01 70 ...............p +| 4080: 67 73 7a 18 0b 03 1b 01 76 65 72 73 69 6f 6e 04 gsz.....version. +| page 6 offset 20480 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4080: 00 00 03 03 02 01 03 03 02 02 01 00 00 00 00 00 ................ +| end crash-0f47112aa7520c.db + }] +} {} + +do_catchsql_test 1.1 { + SELECT * FROM t1('R*') WHERE (a,b)<=(current_date,0) ORDER BY rowid DESC; +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +# +reset_db +do_test 2.0 { + sqlite3 db {} + db deserialize [decode_hexdb { + +.open --hexdb +| size 24576 pagesize 4096 filename sql047467.txt.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 00 .....@ ........ +| 96: 00 00 00 00 0d 00 00 00 06 0e 0f 00 0f aa 0f 53 ...............S +| 112: 0e e8 0e 8b 0e 33 0e 0f 01 00 00 00 00 00 00 00 .....3.......... +| 3584: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 22 ................ +| 3600: 06 06 17 11 11 01 31 74 61 62 6c 65 62 62 62 62 ......1tablebbbb +| 3616: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 62 62 .CREATE TABLE bb +| 3632: 28 61 29 56 05 06 17 1f 1f 01 7d 74 61 62 6c 65 (a)V.......table +| 3648: 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 t1_configt1_conf +| 3664: 69 67 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 ig.CREATE TABLE +| 3680: 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 't1_config'(k PR +| 3696: 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 20 57 49 IMARY KEY, v) WI +| 3712: 54 48 4f 55 54 20 52 4f 57 49 44 5b 04 07 17 21 THOUT ROWID[...! +| 3728: 21 01 81 01 74 61 62 6c 65 74 31 5f 64 6f 63 73 !...tablet1_docs +| 3744: 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 04 43 52 izet1_docsize.CR +| 3760: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 EATE TABLE 't1_d +| 3776: 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 54 45 47 ocsize'(id INTEG +| 3792: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 3808: 73 7a 20 42 4c 4f 42 29 69 03 07 17 19 19 01 81 sz BLOB)i....... +| 3824: 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 -tablet1_idxt1_i +| 3840: 64 78 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 dx.CREATE TABLE +| 3856: 27 74 31 5f 69 64 78 27 28 73 65 67 69 64 2c 20 't1_idx'(segid, +| 3872: 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d term, pgno, PRIM +| 3888: 41 52 59 20 4b 45 59 28 73 65 67 69 64 2c 20 74 ARY KEY(segid, t +| 3904: 65 72 6d 29 29 20 57 49 54 48 4f 55 54 20 52 4f erm)) WITHOUT RO +| 3920: 57 49 44 55 02 07 17 1b 1b 01 81 01 74 61 62 6c WIDU........tabl +| 3936: 65 74 31 5f 64 61 74 61 74 31 5f 64 61 74 61 02 et1_datat1_data. +| 3952: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 3968: 5f 64 61 74 61 27 28 69 64 20 49 4e 54 45 47 45 _data'(id INTEGE +| 3984: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 R PRIMARY KEY, b +| 4000: 6c 6f 63 6b 20 42 4c 4f 42 29 54 01 07 17 11 11 lock BLOB)T..... +| 4016: 08 81 15 74 61 62 6c 65 74 31 74 31 43 52 45 41 ...tablet1t1CREA +| 4032: 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 4c 45 TE VIRTUAL TABLE +| 4048: 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 28 61 t1 USING fts5(a +| 4064: 2c 62 2c 70 72 65 66 69 78 3d 22 31 2c 32 2c 33 ,b,prefix=.1,2,3 +| 4080: 2c 34 22 2c 20 63 6f 6e 74 65 6e 74 3d 22 22 29 ,4., content=..) +| page 2 offset 4096 +| 0: 0d 0b 6a 00 37 09 4c 02 0f e7 09 4c 0f c6 0f a4 ..j.7.L....L.... +| 16: 0f 88 0f 6d 0f 4b 0f 2c 0f 0e 0e ec 0e cd 0e ad ...m.K.,........ +| 32: 0e 8e 0e 6c 0e 4b 0e 29 0e 08 0d e6 0d c4 0d b5 ...l.K.)........ +| 48: 0d 97 0d 76 0d 54 0d 31 0d 15 0c f3 0c d3 0c b5 ...v.T.1........ +| 64: 0c 95 0c 73 0c 54 0c 32 0c 10 0b ee 0b cc 0b b0 ...s.T.2........ +| 80: 0b 8d 0b 7e 0b 48 0b 2e 0b 0b 0a ef 00 00 00 00 ...~.H.......... +| 2368: 00 00 00 00 00 00 00 00 00 00 00 00 15 0a 03 00 ................ +| 2384: 30 00 00 00 01 01 03 35 00 03 01 11 12 02 01 12 0......5........ +| 2400: 03 01 11 1c 8c 80 80 80 80 10 03 00 3e 00 00 00 ............>... +| 2416: 17 01 05 05 34 74 61 62 6c 03 02 03 01 04 77 68 ....4tabl.....wh +| 2432: 65 72 03 02 06 09 1b 8c 80 80 80 80 0f 03 00 3c er.............< +| 2448: 00 00 00 16 05 34 66 74 73 34 03 02 02 01 04 6e .....4fts4.....n +| 2464: 75 6d 62 03 06 01 04 09 1b 8c 80 80 80 80 0e 03 umb............. +| 2480: 00 3b ff f0 00 16 04 33 74 68 65 03 06 01 01 04 .;.....3the..... +| 2496: 01 03 77 68 65 03 02 04 04 0a 1b 8c 80 80 80 80 ..whe........... +| 2512: 0d 03 00 3c 00 00 00 16 04 33 6e 75 6d 03 06 01 ...<.....3num... +| 2528: 01 05 01 03 74 61 62 03 02 03 04 0a 19 8c 80 80 ....tab......... +| 2544: 80 80 0c 03 00 38 00 00 00 14 03 32 77 68 03 02 .....8.....2wh.. +| 2560: 04 00 04 33 66 74 73 03 02 02 04 07 18 8c 80 80 ...3fts......... +| 2576: 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 03 02 .....6.....2ta.. +| 2592: 03 02 01 68 03 06 01 01 04 04 07 1b 8c 80 80 80 ...h............ +| 2608: 80 0a 03 00 3c 00 00 00 16 03 32 6e 75 03 06 01 ....<.....2nu... +| 2624: 01 05 01 02 6f 66 03 06 01 01 06 04 09 19 8c 80 ....of.......... +| 2640: 80 80 80 09 03 00 38 00 00 00 14 03 32 66 74 03 ......8.....2ft. +| 2656: 02 02 01 02 69 73 03 06 01 01 03 04 07 18 8c 80 ....is.......... +| 2672: 80 80 80 08 03 00 36 00 00 00 13 02 31 74 03 08 ......6.....1t.. +| 2688: 03 01 01 04 01 01 77 03 02 04 04 09 1a 8c 80 80 ......w......... +| 2704: 80 80 07 03 00 3a 00 00 00 15 02 31 6e 03 08 01 .....:.....1n... +| 2720: 01 02 05 01 00 6f 03 06 01 01 06 14 09 18 8c 80 .....o.......... +| 2736: 80 80 80 06 03 00 36 00 00 00 03 04 02 31 66 03 ......6......1f. +| 2752: 02 02 01 01 69 03 06 01 01 03 04 f6 1c 8c 80 80 ....i........... +| 2768: 80 80 05 03 00 3e 00 00 00 17 04 30 74 68 65 03 .....>.....0the. +| 2784: f6 01 01 04 01 05 77 68 65 72 65 03 02 04 0a 15 ......where..... +| 2800: 8c 80 80 80 80 04 03 00 30 00 00 00 11 01 01 06 ........0....... +| 2816: 06 30 74 61 62 6c 65 0f 42 03 07 1c 8c 81 80 80 .0table.B....... +| 2832: 80 03 03 00 3e 00 00 00 17 07 30 6e 75 6d 62 65 ....>.....0numbe +| 2848: 72 03 06 01 01 05 01 02 6f 66 03 06 04 0d 13 8c r.......of...... +| 2864: 80 80 80 80 02 03 00 2c 00 00 00 0f 01 01 03 02 .......,........ +| 2880: 30 6e 03 06 01 01 02 07 1b 8c 80 80 80 80 01 03 0n.............. +| 2896: 00 3c 00 00 00 16 08 30 66 74 73 34 61 75 78 03 .<.....0fts4aux. +| 2912: 02 02 01 02 69 73 03 06 04 0c 00 00 00 14 2a 00 ....is........*. +| 2928: 00 00 01 01 02 24 00 02 01 01 12 02 01 12 08 88 .....$.......... +| 2944: 80 80 80 80 12 03 00 16 00 00 00 05 02 1c 88 80 ................ +| 2960: 80 80 80 11 03 00 3e 00 00 00 17 05 34 72 6f 77 ......>.....4row +| 2976: 73 02 06 01 01 05 01 04 74 68 65 72 02 02 04 0b s.......ther.... +| 2992: 15 88 80 80 80 80 10 03 00 30 00 00 00 11 02 01 .........0...... +| 3008: 01 07 05 34 62 65 74 77 02 02 04 08 1b 88 80 80 ...4betw........ +| 3024: 80 80 0f 03 00 3c 00 00 00 16 04 04 33 72 6f 77 .....<......3row +| 3040: 02 06 01 01 05 01 03 74 68 65 02 08 05 0a 1b 88 .......the...... +| 3056: 80 80 80 80 0e 03 00 3c 00 00 00 16 01 01 02 04 .......<........ +| 3072: 33 61 72 65 02 02 03 01 03 62 65 74 02 02 07 08 3are.....bet.... +| 3088: 1b 88 80 80 80 80 0d 03 00 3c 00 00 00 16 13 32 .........<.....2 +| 3104: 74 68 02 08 02 01 01 07 00 04 33 61 6e 64 02 06 th........3and.. +| 3120: 04 0a 1b 88 80 80 80 80 0c 03 00 3c 00 00 00 16 ...........<.... +| 3136: 03 32 69 6e 02 06 01 01 06 01 02 72 6f 02 06 01 .2in.......ro... +| 3152: 01 05 04 09 18 88 80 80 80 80 0b 03 00 36 00 00 .............6.. +| 3168: 00 13 02 03 32 61 72 02 02 03 01 02 62 65 02 02 ....2ar.....be.. +| 3184: 04 05 07 1b 88 80 80 80 80 0a 03 00 3c 00 94 50 ............<..P +| 3200: 16 02 31 74 02 08 02 01 01 07 00 03 32 61 6e 02 ..1t........2an. +| 3216: 06 01 01 04 09 19 88 80 80 80 80 09 03 00 38 00 ..............8. +| 3232: 00 00 14 02 31 6e 02 06 01 01 03 01 01 72 02 06 ....1n.......r.. +| 3248: 01 01 05 04 08 17 88 80 80 80 80 08 03 00 34 00 ..............4. +| 3264: 00 00 12 02 31 62 02 02 04 01 01 69 02 06 01 01 ....1b.....i.... +| 3280: 06 04 06 19 88 80 80 80 80 07 03 00 38 00 00 00 ............8... +| 3296: 14 04 02 31 32 02 02 05 01 01 61 02 08 03 01 01 ...12.....a..... +| 3312: 02 05 06 1b 88 80 80 80 80 06 03 00 3c 00 00 00 ............<... +| 3328: 16 06 30 74 68 65 72 65 02 02 02 00 02 31 31 02 ..0there.....11. +| 3344: 06 01 01 04 0a 15 88 80 80 80 80 05 03 00 30 00 ..............0. +| 3360: 00 00 11 01 01 05 04 30 74 68 65 02 06 01 01 07 .......0the..... +| 3376: 07 1c 88 80 80 80 80 04 03 00 3e 00 00 00 17 01 ..........>..... +| 3392: 01 06 02 30 6e 02 06 01 01 03 01 04 72 6f 77 73 ...0n.......rows +| 3408: 02 06 07 08 1b 88 80 80 80 80 03 03 00 3c 00 00 .............<.. +| 3424: 00 16 08 30 62 65 74 77 65 65 6e 02 02 04 01 02 ...0between..... +| 3440: 69 6e 02 06 04 0c 1a 88 80 80 80 80 02 03 00 3a in.............: +| 3456: 00 00 00 15 04 30 61 6e 64 02 06 01 01 02 02 02 .....0and....... +| 3472: 72 65 02 02 03 04 0a 17 88 80 80 80 80 01 03 00 re.............. +| 3488: 34 00 00 00 12 02 30 31 02 06 01 01 04 01 01 32 4.....01.......2 +| 3504: 02 02 05 04 08 08 84 80 80 80 80 12 03 00 16 00 ................ +| 3520: 00 00 05 04 1b 84 80 80 80 80 11 03 00 3c 00 00 .............<.. +| 3536: 00 16 05 34 74 61 62 6c 01 06 01 01 05 02 03 65 ...4tabl.......e +| 3552: 72 6d 01 02 04 0b 1b 84 80 80 80 80 10 03 00 3c rm.............< +| 3568: 00 00 00 16 05 34 65 61 63 68 01 02 03 01 04 70 .....4each.....p +| 3584: 72 65 73 01 02 05 04 08 1a 84 80 80 80 80 0f 03 res............. +| 3600: 00 3a 00 00 00 15 04 33 74 65 72 01 02 04 02 02 .:.....3ter..... +| 3616: 68 65 01 06 01 01 03 04 08 1b 84 80 80 80 80 0e he.............. +| 3632: 03 00 3c 00 00 00 16 04 33 80 72 65 01 02 05 01 ..<.....3.re.... +| 3648: 03 74 61 62 01 06 01 01 05 04 08 1a 84 80 80 80 .tab............ +| 3664: 80 0d 03 00 3a 00 00 00 15 04 33 66 6f 72 01 02 ....:.....3for.. +| 3680: 02 02 02 74 73 01 06 01 01 04 04 08 1b 84 80 80 ...ts........... +| 3696: 80 80 0c 03 00 3c 00 00 00 17 03 32 74 68 01 06 .....<.....2th.. +| 3712: 01 01 03 00 04 33 65 61 63 01 02 03 04 09 18 84 .....3eac....... +| 3728: 80 80 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 .......6.....2ta +| 3744: 01 06 01 01 05 02 01 65 01 02 04 04 09 19 84 80 .......e........ +| 3760: 80 80 80 0a 03 00 38 00 00 00 14 03 32 69 6e 01 ......8.....2in. +| 3776: 06 01 01 02 01 02 70 72 01 02 05 04 09 18 84 80 ......pr........ +| 3792: 80 80 80 09 03 00 36 00 00 00 13 03 32 66 6f 01 ......6.....2fo. +| 3808: 02 02 02 01 74 01 06 01 01 04 04 07 1b 84 80 80 ....t........... +| 3824: 80 80 08 03 00 3c 00 00 00 16 02 31 74 01 0a 04 .....<.....1t... +| 3840: 01 01 03 04 00 03 32 65 61 01 02 03 04 0a 17 84 ......2ea....... +| 3856: 80 80 80 80 07 03 00 34 00 00 00 12 02 31 69 01 .......4.....1i. +| 3872: 06 01 01 02 01 01 70 01 02 05 04 08 18 84 80 80 ......p......... +| 3888: 80 80 06 03 00 36 00 00 00 13 02 31 65 01 02 03 .....6.....1e... +| 3904: 01 01 66 01 08 02 01 01 04 04 06 1b 84 80 80 80 ..f............. +| 3920: 80 05 03 00 3c 00 00 00 16 05 30 74 65 72 6d 01 ....<.....0term. +| 3936: 02 04 02 02 68 65 01 06 01 01 03 04 09 14 84 80 ....he.......... +| 3952: 80 80 80 04 03 00 2e 00 00 00 10 06 30 64 61 62 ............0dab +| 3968: 6c 65 01 06 01 01 05 04 15 84 80 80 80 80 03 03 le.............. +| 3984: 00 30 00 00 00 11 02 08 30 70 72 65 73 65 6e 74 .0......0present +| 4000: 01 02 05 05 1b 84 80 80 80 80 02 03 00 3c 00 00 .............<.. +| 4016: 00 16 04 30 66 74 73 01 06 01 01 04 01 02 69 6e ...0fts.......in +| 4032: 01 06 01 01 04 0a 1a 84 80 80 80 80 01 03 00 3a ...............: +| 4048: 00 00 00 15 05 30 65 61 63 68 01 02 03 01 13 66 .....0each.....f +| 4064: 6f 72 01 02 02 04 09 06 01 03 00 12 03 0b 0f 00 or.............. +| 4080: 00 08 8c 80 80 80 80 11 03 00 16 00 00 00 05 04 ................ +| page 3 offset 8192 +| 0: 0a 00 00 00 32 0e 4f 00 0f fa 0f f1 0f e9 0f e1 ....2.O......... +| 16: 0f d8 0f d1 0f c9 0f c1 0f b9 0f b1 0f a9 0f a0 ................ +| 32: 0f 98 0f 90 0f 87 0f 80 0f 78 0f 71 0f 68 0f 5f .........x.q.h._ +| 48: 0f 56 0f 4d 0f 41 0f 38 0f 2f 0f 26 0f 1d 0f 13 .V.M.A.8./.&.... +| 64: 0f 0a 0f 01 0e f7 0e ee 0e e6 0e dd 0e d6 0e cd ................ +| 80: 0e c3 0e ba 0e 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3648: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 ................ +| 3664: 04 01 10 01 03 34 74 20 07 04 01 0e 01 03 34 1e .....4t ......4. +| 3680: 09 04 01 12 01 03 33 74 68 1c 08 04 01 10 01 03 ......3th....... +| 3696: 33 6e 1a 08 04 01 10 01 03 32 77 18 08 04 01 10 3n.......2w..... +| 3712: 01 03 32 74 16 08 04 01 10 01 03 32 6e 14 07 04 ..2t.......2n... +| 3728: 01 0e 01 03 32 12 08 04 01 10 01 03 31 74 10 08 ....2.......1t.. +| 3744: 04 01 10 01 03 31 6e 0e 07 04 01 0e 01 03 31 0c .....1n.......1. +| 3760: 09 04 01 12 01 03 30 74 68 0a 08 04 01 10 01 03 ......0th....... +| 3776: 30 74 08 09 04 01 12 01 03 30 6e 75 06 08 04 01 0t.......0nu.... +| 3792: 10 01 03 30 6e 04 06 04 01 0c 01 03 02 08 04 01 ...0n........... +| 3808: 10 01 02 34 72 22 07 04 01 0e 01 02 34 20 08 04 ...4r.......4 .. +| 3824: 01 10 01 02 33 72 1e 09 04 01 12 01 02 33 61 72 ....3r.......3ar +| 3840: 1c 08 04 01 10 01 02 32 74 1a 08 04 01 10 01 02 .......2t....... +| 3856: 32 69 18 09 04 01 12 01 02 32 61 72 16 08 04 01 2i.......2ar.... +| 3872: 10 01 02 31 74 14 08 04 01 10 01 02 31 6e 12 08 ...1t.......1n.. +| 3888: 04 01 10 01 02 31 62 10 08 04 01 10 01 02 31 32 .....1b.......12 +| 3904: 0e 0b 04 01 16 01 02 30 74 68 65 72 0c 08 04 01 .......0ther.... +| 3920: 10 01 02 30 74 0a 08 04 01 10 01 02 30 6e 08 08 ...0t.......0n.. +| 3936: 14 01 10 01 02 30 62 06 08 04 01 10 01 02 30 61 .....0b.......0a +| 3952: 04 06 04 01 0c 01 02 02 07 04 09 10 01 34 74 22 .............4t. +| 3968: 06 04 09 0e 01 34 20 08 04 09 12 01 33 74 65 1e .....4 .....3te. +| 3984: 07 04 09 10 01 33 70 1c 07 04 09 10 01 33 66 1a .....3p......3f. +| 4000: 08 04 09 12 01 32 74 68 18 07 04 09 10 01 32 74 .....2th......2t +| 4016: 16 01 64 09 10 01 32 69 14 07 04 09 10 01 32 66 ..d...2i......2f +| 4032: 12 07 04 09 10 01 31 74 10 07 04 09 10 01 31 69 ......1t......1i +| 4048: 0e 06 04 09 0e 01 31 0c 08 04 09 12 01 30 74 65 ......1......0te +| 4064: 0a 06 04 09 10 01 30 74 08 07 04 09 10 01 30 70 ......0t......0p +| 4080: 06 08 04 09 12 00 00 00 00 00 00 00 00 00 00 00 ................ +| page 4 offset 12288 +| 4064: 00 00 00 00 00 00 00 00 00 00 00 05 03 03 00 10 ................ +| 4080: 03 05 05 02 03 00 10 04 06 05 01 03 00 10 04 04 ................ +| page 5 offset 16384 +| 0: 0a 00 00 00 02 0f eb 00 0f eb 0f f4 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 08 03 15 01 70 ...............p +| 4080: 67 73 7a 18 0b 03 1b 01 76 65 72 73 69 6f 6e 04 gsz.....version. +| page 6 offset 20480 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4080: 00 00 03 03 02 01 03 03 02 02 01 00 00 00 00 00 ................ +| end sql047467.txt.db +}]} {} + +do_catchsql_test 2.1 { +SELECT * FROM t1('R*R*R*R*') WHERE (a,b)<=(current_date,0) ORDER BY rowid DESC; +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +reset_db +do_test 3.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 32768 pagesize 4096 filename crash-c69fcaceff1e50.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 08 .....@ ........ +| 32: 00 00 00 02 00 00 00 01 00 00 00 09 00 00 00 04 ................ +| 96: 00 00 00 00 0d 0f c7 00 07 0d 92 00 0f 8d 0f 36 ...............6 +| 112: 0e cb 0e 6b 0e 0e 0d b6 0d 92 0d 92 00 00 00 00 ...k............ +| 3472: 00 00 22 08 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet +| 3488: 32 74 32 08 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE +| 3504: 20 74 32 28 78 29 56 07 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta +| 3520: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c +| 3536: 6f 6e 66 69 67 07 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB +| 3552: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k +| 3568: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) +| 3584: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 06 WITHOUT ROWID[. +| 3600: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d +| 3616: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize +| 3632: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't +| 3648: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN +| 3664: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE +| 3680: 59 2c 20 73 7a 20 42 4c 4f 42 29 5e 05 07 17 21 Y, sz BLOB)^...! +| 3696: 21 01 81 07 74 61 62 6c 65 74 31 5f 63 6f 6e 74 !...tablet1_cont +| 3712: 65 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 05 43 52 entt1_content.CR +| 3728: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 EATE TABLE 't1_c +| 3744: 6f 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 ontent'(id INTEG +| 3760: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 3776: 63 30 2c 20 63 31 2c 20 63 32 29 69 04 07 17 19 c0, c1, c2)i.... +| 3792: 19 01 81 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 ...-tablet1_idxt +| 3808: 31 5f 69 64 78 04 43 52 45 41 54 45 20 54 41 42 1_idx.CREATE TAB +| 3824: 4c 45 20 27 74 31 5f 69 64 78 27 28 73 65 67 69 LE 't1_idx'(segi +| 3840: 64 2c 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 d, term, pgno, P +| 3856: 52 49 4d 41 52 59 20 4b 45 59 28 73 65 67 69 64 RIMARY KEY(segid +| 3872: 2c 20 74 65 72 6d 29 29 20 57 49 54 48 4f 55 54 , term)) WITHOUT +| 3888: 20 52 4f 57 49 44 55 03 07 17 1b 1b 01 81 01 74 ROWIDU........t +| 3904: 61 62 6c 65 74 31 5f 64 61 74 61 74 31 5f 64 61 ablet1_datat1_da +| 3920: 74 61 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 ta.CREATE TABLE +| 3936: 27 74 31 5f 64 61 74 61 27 28 69 64 20 49 4e 54 't1_data'(id INT +| 3952: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY +| 3968: 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 38 02 06 , block BLOB)8.. +| 3984: 17 11 11 08 5f 74 61 62 6c 65 74 31 74 31 43 52 ...._tablet1t1CR +| 4000: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4016: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 LE t1 USING fts5 +| 4032: 28 61 2c 62 2c 63 29 00 00 00 00 00 00 00 00 00 (a,b,c)......... +| page 3 offset 8192 +| 0: 0d 00 00 00 03 0c 94 00 0f e6 0f ef 0c 94 00 00 ................ +| 3216: 00 00 00 00 86 4a 84 80 80 80 80 01 04 00 8d 18 .....J.......... +| 3232: 00 00 01 bb 02 30 30 01 02 06 01 02 06 01 02 06 .....00......... +| 3248: 1f 02 03 01 02 03 01 02 03 01 08 32 30 31 36 30 ...........20160 +| 3264: 36 30 39 01 02 07 01 02 07 01 02 07 01 01 34 01 609...........4. +| 3280: 02 05 01 02 05 01 02 05 01 01 35 01 02 04 01 02 ..........5..... +| 3296: 04 01 02 04 02 07 30 30 30 30 30 30 30 1c 02 04 ......0000000... +| 3312: 01 02 04 01 02 04 01 06 62 69 6e 61 72 79 03 06 ........binary.. +| 3328: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3344: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3360: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3376: 03 06 01 02 02 03 06 01 02 02 01 08 63 6f 6d 70 ............comp +| 3392: 69 6c 65 72 01 02 02 01 02 02 01 02 02 01 06 64 iler...........d +| 3408: 62 73 74 61 74 07 02 03 01 02 03 01 02 03 02 04 bstat........... +| 3424: 65 62 75 67 04 02 02 01 02 02 01 02 02 01 06 65 ebug...........e +| 3440: 6b b1 62 6c 65 07 02 02 01 02 02 01 02 02 01 02 k.ble........... +| 3456: 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 ................ +| 3472: 01 02 02 01 02 02 05 02 02 01 02 02 01 02 02 01 ................ +| 3488: 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 ................ +| 3504: 02 01 02 02 02 08 78 74 65 6e 73 69 6f 6e 1f 02 ......xtension.. +| 3520: 04 01 02 04 01 02 04 01 04 66 74 73 34 1a 02 03 .........fts4... +| 3536: 01 02 03 01 02 03 04 01 35 0d 02 03 01 02 03 01 ........5....... +| 3552: 02 03 01 03 67 63 63 01 02 03 01 02 03 01 02 03 ....gcc......... +| 3568: 02 06 65 6f 70 6f 6c 79 10 02 03 01 02 03 01 02 ..eopoly........ +| 3584: 03 01 05 6a 73 6f 6e 31 13 02 03 01 02 03 01 02 ...json1........ +| 3600: 03 01 04 6c 6f 61 64 1f 02 03 01 02 03 01 02 03 ...load......... +| 3616: 01 03 6d 61 78 1c 02 02 01 02 02 01 02 02 02 05 ..max........... +| 3632: 65 6d 6f 72 79 1c 02 03 01 02 03 01 02 03 04 04 emory........... +| 3648: 73 79 73 35 16 02 03 01 02 03 01 02 03 01 06 6e sys5...........n +| 3664: 6f 63 61 73 65 02 06 01 02 02 03 06 01 02 02 03 ocase........... +| 3680: 06 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 ................ +| 3696: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3712: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3728: 02 01 04 6f 6d 69 74 1f 02 02 01 02 02 01 02 01 ...omit......... +| 3744: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 ................ +| 3760: 58 81 96 4d 01 06 01 02 02 03 06 01 02 02 03 06 X..M............ +| 3776: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3792: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3808: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3824: 01 0a 74 68 72 65 61 64 73 61 66 65 22 02 02 01 ..threadsafe.... +| 3840: 02 02 01 02 02 01 04 76 74 61 62 07 02 04 01 02 .......vtab..... +| 3856: 04 01 02 04 01 01 78 01 06 01 01 02 01 06 01 01 ......x......... +| 3872: 02 01 06 01 c6 02 01 06 01 01 02 01 06 01 01 02 ................ +| 3888: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3904: 06 01 01 02 00 f6 01 01 02 01 06 01 01 02 01 06 ................ +| 3920: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 3936: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 3952: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 ................ +| 3968: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3984: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 4000: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 4016: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 4032: 02 01 06 01 01 02 01 06 01 01 02 04 15 13 0c 0c ................ +| 4048: 12 44 13 11 0f 47 13 0f 0c 0e 11 10 0f 0e 10 0f .D...G.......... +| 4064: 44 0f 10 40 15 0f 07 01 03 00 14 24 5a 24 24 0f D..@.......$Z$$. +| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ +| page 4 offset 12288 +| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 7 offset 24576 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| end crash-c69fcaceff1e50.db +}]} {} + +do_catchsql_test 3.1 { + UPDATE t1 SET b=quote(zeroblob(200)) WHERE a MATCH 'thra*T'; +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +reset_db +do_test 4.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 24576 pagesize 4096 filename crash-ef6738247b1344.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 10 00 06 40 00 00 06 .....@ ....@... +| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 00 ................ +| 96: 00 00 00 00 0d 00 00 00 06 0e 0f 00 0f aa 0f 53 ...............S +| 112: 0e e8 0e 8b 0e 33 0e 0f 00 00 00 00 00 00 00 00 .....3.......... +| 3584: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 22 ................ +| 3600: 06 06 17 11 11 01 31 74 61 62 6c 65 62 62 62 62 ......1tablebbbb +| 3616: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 62 62 .CREATE TABLE bb +| 3632: 28 61 29 56 05 06 17 1f 1f 01 7d 74 61 62 6c 65 (a)V.......table +| 3648: 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 t1_configt1_conf +| 3664: 69 67 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 ig.CREATE TABLE +| 3680: 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 't1_config'(k PR +| 3696: 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 20 57 49 IMARY KEY, v) WI +| 3712: 54 48 4f 55 54 20 52 4f 57 49 44 5b 04 07 17 21 THOUT ROWID[...! +| 3728: 21 01 81 01 74 61 62 6c 65 74 31 5f 64 6f 63 73 !...tablet1_docs +| 3744: 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 04 43 52 izet1_docsize.CR +| 3760: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 EATE TABLE 't1_d +| 3776: 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 54 45 47 ocsize'(id INTEG +| 3792: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 3808: 73 7a 20 42 4c 4f 42 29 69 03 07 17 19 19 01 81 sz BLOB)i....... +| 3824: 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 -tablet1_idxt1_i +| 3840: 64 78 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 dx.CREATE TABLE +| 3856: 27 74 31 5f 69 64 78 27 28 73 65 67 69 64 2c 20 't1_idx'(segid, +| 3872: 74 65 72 6d 2c 20 6f 67 6e 6f 2c 20 50 52 49 4d term, ogno, PRIM +| 3888: 41 52 59 20 4b 45 59 28 73 65 67 69 64 2c 20 74 ARY KEY(segid, t +| 3904: 65 72 6d 29 29 20 57 49 54 48 4f 55 54 20 52 4f erm)) WITHOUT RO +| 3920: 57 49 44 55 35 07 17 1b 1b 01 81 01 74 61 62 6c WIDU5.......tabl +| 3936: 65 74 31 5f 64 61 74 61 74 31 5f 64 61 74 61 02 et1_datat1_data. +| 3952: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 3968: 5f 64 61 74 61 27 28 69 64 20 49 4e 54 45 47 45 _data'(id INTEGE +| 3984: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 R PRIMARY KEY, b +| 4000: 6c 6f 63 6b 20 42 4c 4f 42 29 54 01 07 17 11 11 lock BLOB)T..... +| 4016: 08 81 15 74 61 62 6c 65 74 31 74 31 43 52 45 41 ...tablet1t1CREA +| 4032: 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 4c 45 TE VIRTUAL TABLE +| 4048: 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 28 61 t1 USING fts5(a +| 4064: 2c 62 2c 70 72 65 66 69 78 3d 22 31 2c 32 2c 33 ,b,prefix=.1,2,3 +| 4080: 2c 34 22 2c 20 63 6f 6e 74 65 6e 74 3d 22 22 29 ,4., content=..) +| page 2 offset 4096 +| 0: 0d 0b 6a 00 37 09 4c 02 0f e7 09 4c 0f c6 0f a4 ..j.7.L....L.... +| 16: 0f 88 0f 6d 0f 4b 0f 2c 0f 0e 0e ec 0e cd 0e ad ...m.K.,........ +| 32: 0e 8e 0e 6c 0e 4b 0e 29 0e 08 0d e6 0d c4 0d b5 ...l.K.)........ +| 48: 0d 97 0d 76 0d 54 0d 31 0d 15 0c f3 0c d3 0c b5 ...v.T.1........ +| 64: 0c 95 0c 73 0c 54 0c 32 0c 10 0b ee 0b cc 0b b0 ...s.T.2........ +| 80: 0b 8d 0b 7e 0b 48 0b 2e 0b 0b 0a ef 0a cc 0a 00 ...~.H.......... +| 2368: 00 00 00 00 00 00 00 00 00 00 00 00 15 0a 03 00 ................ +| 2384: 30 00 00 00 01 01 03 35 00 03 01 01 12 02 01 12 0......5........ +| 2400: 03 01 11 1c 8c 80 80 80 80 10 02 9c 3e 00 00 00 ............>... +| 2416: 17 01 05 05 34 74 61 62 6c 03 02 03 01 04 77 68 ....4tabl.....wh +| 2432: 65 72 03 02 06 09 1b 8c 80 80 80 80 0f 03 00 3c er.............< +| 2448: 00 00 00 16 05 34 66 74 73 34 03 02 02 01 04 6e .....4fts4.....n +| 2464: 75 6d 62 03 06 01 04 09 1b 8c 80 80 80 80 0e 03 umb............. +| 2480: 00 3c 00 00 00 16 04 33 74 68 65 03 06 01 01 04 .<.....3the..... +| 2496: 01 03 77 68 65 03 02 04 04 0a 1b 8c 80 80 80 80 ..whe........... +| 2512: 0d 03 00 3c 00 00 00 16 04 33 6e 75 6d 03 06 01 ...<.....3num... +| 2528: 01 05 01 03 74 61 62 03 02 03 04 0a 19 8c 80 80 ....tab......... +| 2544: 80 80 0c 03 00 38 00 00 00 14 03 32 77 68 03 02 .....8.....2wh.. +| 2560: 04 00 04 33 66 74 73 03 12 02 04 07 18 8c 80 80 ...3fts......... +| 2576: 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 03 02 .....6.....2ta.. +| 2592: 03 02 01 68 03 06 01 01 04 04 07 1b 8c 80 80 80 ...h............ +| 2608: 80 0a 03 00 3c 00 00 00 16 03 32 6e 75 03 06 01 ....<.....2nu... +| 2624: 01 05 01 02 6f 66 03 06 01 01 06 04 09 19 8c 80 ....of.......... +| 2640: 82 d0 80 09 03 00 38 00 00 00 14 03 32 66 74 03 ......8.....2ft. +| 2656: 02 02 01 02 69 73 03 06 01 01 03 04 07 18 8c 80 ....is.......... +| 2672: 80 80 80 08 03 00 36 00 00 00 13 02 31 74 03 08 ......6.....1t.. +| 2688: 03 01 01 04 01 01 77 03 02 04 04 09 1a 8c 80 80 ......w......... +| 2704: 80 80 07 03 00 3a 00 00 00 15 02 31 6e 03 08 01 .....:.....1n... +| 2720: 01 02 05 01 01 6f 03 06 01 01 06 04 09 18 8c 80 .....o.......... +| 2736: 81 80 80 06 03 00 36 00 00 00 13 04 12 31 66 03 ......6......1f. +| 2752: 02 02 01 01 69 03 06 01 01 03 05 06 1c 8c 80 80 ....i........... +| 2768: 80 80 05 03 00 3e 00 00 00 17 04 30 74 68 65 03 .....>.....0the. +| 2784: 06 01 01 04 01 05 77 68 65 72 65 03 02 04 0a 15 ......where..... +| 2800: 8c 80 65 80 80 04 03 00 30 00 00 00 11 01 01 06 ..e.....0....... +| 2816: 06 30 74 61 62 6c 65 03 02 03 07 1c 8c 80 80 80 .0table......... +| 2832: 80 03 03 00 3e 00 00 00 17 07 30 6e 75 6d 62 65 ....>.....0numbe +| 2848: 72 03 06 01 01 05 01 02 6f 66 03 06 04 0d 13 8c r.......of...... +| 2864: 80 80 80 80 02 03 00 2c 00 00 00 0f fc 01 03 02 .......,........ +| 2880: 30 6e 03 06 01 00 f2 07 1b 8c 80 80 80 80 01 03 0n.............. +| 2896: 00 3c 00 00 00 16 08 30 66 74 73 34 61 75 78 03 .<.....0fts4aux. +| 2912: 03 02 01 02 69 73 03 06 04 0c 00 00 00 18 ea 00 ....is.......... +| 2928: 00 00 01 01 02 24 00 02 01 01 12 02 01 12 08 88 .....$.......... +| 2944: 80 80 80 80 12 03 00 12 10 00 00 05 02 1c 88 80 ................ +| 2960: 80 80 80 11 03 00 3e 00 00 00 17 05 34 72 6f 77 ......>.....4row +| 2976: 73 02 06 01 01 05 01 04 74 68 65 72 02 02 04 0b s.......ther.... +| 2992: 15 88 80 80 80 80 10 03 00 2f ff ff f0 11 02 01 ........./...... +| 3008: 01 07 05 34 62 65 74 77 02 02 04 08 1b 88 80 80 ...4betw........ +| 3024: 80 80 0f cf 00 3c 00 00 00 16 04 04 33 72 6f 77 .....<......3row +| 3040: 02 06 01 01 05 01 03 74 68 65 02 08 05 0a 1b 88 .......the...... +| 3056: 80 80 80 80 0d 03 00 3c 00 00 00 16 00 01 02 04 .......<........ +| 3072: 33 61 72 65 02 02 03 01 03 62 65 74 02 02 07 08 3are.....bet.... +| 3088: 1b 88 80 80 80 80 0d 03 00 3c 00 00 00 16 03 32 .........<.....2 +| 3104: 74 68 02 08 02 01 01 07 00 04 33 61 6e 64 02 06 th........3and.. +| 3120: 04 0a 1b 88 80 80 80 80 0c 03 00 3c 00 00 00 16 ...........<.... +| 3136: 03 32 69 6e 02 06 01 01 06 01 02 72 6f 02 06 01 .2in.......ro... +| 3152: 01 05 04 09 18 88 80 80 80 80 0b 03 00 36 00 00 .............6.. +| 3168: 00 13 02 03 32 61 72 02 02 03 01 02 62 65 02 02 ....2ar.....be.. +| 3184: 04 05 07 1b 88 80 80 80 80 0a 03 00 3c 00 00 00 ............<... +| 3200: 16 12 31 74 02 08 02 01 01 07 00 03 32 61 6e 02 ..1t........2an. +| 3216: 06 01 01 04 09 19 88 80 80 80 80 09 03 00 38 00 ..............8. +| 3232: 00 00 14 02 31 6e 02 06 01 01 03 01 01 72 02 06 ....1n.......r.. +| 3248: 01 01 05 04 08 17 78 80 80 80 80 08 03 00 34 10 ......x.......4. +| 3264: 01 00 12 02 31 62 02 02 04 01 01 69 02 06 01 01 ....1b.....i.... +| 3280: 06 04 06 19 88 80 80 80 80 07 03 00 38 00 00 00 ............8... +| 3296: 14 04 02 31 32 02 02 05 01 01 61 02 08 03 01 01 ...12.....a..... +| 3312: 02 05 06 1b 88 80 80 80 80 06 03 00 3c 00 00 00 ............<... +| 3328: 16 06 30 74 68 65 72 65 e7 02 02 00 02 31 31 02 ..0there.....11. +| 3344: 06 01 01 04 0a 15 88 80 80 80 80 05 03 00 30 00 ..............0. +| 3360: 00 00 11 01 01 05 e5 30 74 68 65 02 06 01 01 07 .......0the..... +| 3376: 07 1c 88 80 80 80 80 04 03 00 3e 00 00 00 17 01 ..........>..... +| 3392: 01 06 02 30 6e 02 06 01 01 03 01 04 72 6f 77 73 ...0n.......rows +| 3408: 02 06 07 08 1b 88 80 80 80 80 03 03 00 3c 00 00 .............<.. +| 3424: 00 16 08 30 62 65 74 77 65 65 6e 02 02 04 01 02 ...0between..... +| 3440: 69 6e 02 06 04 0c 1a 88 80 80 80 80 02 03 00 3a in.............: +| 3456: 00 00 00 15 04 30 61 6e 64 02 06 01 01 02 02 02 .....0and....... +| 3472: 72 65 02 02 03 04 0a 17 88 80 80 80 80 01 03 00 re.............. +| 3488: 34 01 00 00 12 02 30 31 02 06 01 01 04 01 01 32 4.....01.......2 +| 3504: 02 02 05 04 08 08 84 80 80 80 80 12 03 00 16 00 ................ +| 3520: 00 00 05 04 1b 84 80 80 80 80 11 03 00 3c 00 00 .............<.. +| 3536: 00 16 05 34 74 61 62 6c 01 06 01 01 05 02 03 65 ...4tabl.......e +| 3552: 72 6d 01 02 04 0b 1b 84 80 80 80 80 10 03 00 3c rm.............< +| 3568: 00 00 00 16 05 34 65 61 63 68 01 02 02 01 04 70 .....4each.....p +| 3584: 72 65 73 01 02 05 04 09 1a 84 80 80 80 80 0f 03 res............. +| 3600: 00 3a 00 00 00 15 04 33 74 65 72 01 02 04 02 02 .:.....3ter..... +| 3616: 68 65 01 06 01 01 03 04 08 1b 84 80 80 80 80 0e he.............. +| 3632: 03 00 3c 00 00 00 16 04 33 70 72 65 01 02 05 01 ..<.....3pre.... +| 3648: 03 74 61 62 01 06 01 01 05 04 08 1a 84 80 80 80 .tab............ +| 3664: 80 0d 03 0d 1a 00 00 00 15 04 33 66 6e 72 01 02 ..........3fnr.. +| 3680: 02 02 02 74 73 01 06 01 01 04 04 08 1b 84 80 80 ...ts........... +| 3696: 80 80 0c 03 00 3c 00 00 00 16 03 32 74 68 01 06 .....<.....2th.. +| 3712: 01 01 03 00 04 33 65 61 63 01 02 03 04 09 18 84 .....3eac....... +| 3728: 80 80 80 80 0b 03 00 36 00 00 00 13 03 32 74 61 .......6.....2ta +| 3744: 01 06 01 01 05 02 01 65 01 02 04 04 09 19 84 80 .......e........ +| 3760: 80 80 80 0a 03 00 38 00 00 00 14 03 30 c9 6e 01 ......8.....0.n. +| 3776: 06 01 01 02 01 02 70 72 01 02 05 04 09 18 84 80 ......pr........ +| 3792: 80 80 80 09 03 00 36 00 00 00 13 03 32 66 6f 01 ......6.....2fo. +| 3808: 02 0b e2 01 74 01 06 01 01 04 04 07 1b 84 80 80 ....t........... +| 3824: 86 f0 08 03 00 3c 00 00 00 16 02 31 74 01 0a 04 .....<.....1t... +| 3840: 01 01 03 04 00 03 32 65 61 01 02 03 04 0a 17 84 ......2ea....... +| 3856: 80 80 80 80 07 03 00 34 00 00 00 12 02 31 69 01 .......4.....1i. +| 3872: 06 01 01 02 01 01 70 01 02 05 04 08 18 84 80 80 ......p......... +| 3888: 80 80 06 03 00 36 00 00 00 13 02 31 65 01 02 03 .....6.....1e... +| 3904: 01 01 66 01 08 02 01 01 04 04 06 1b 84 80 80 80 ..f............. +| 3920: 80 05 03 00 3c 00 00 00 16 05 30 74 65 72 6d 01 ....<.....0term. +| 3936: 02 04 02 02 68 65 01 06 01 01 03 04 09 14 84 80 ....he.......... +| 3952: 80 80 80 04 03 00 2e 00 00 00 10 06 30 74 61 62 ............0tab +| 3968: 6c 65 01 06 01 01 05 04 15 84 7f 80 80 80 03 03 le.............. +| 3984: 00 30 00 00 00 11 02 08 30 70 72 65 73 65 6e 74 .0......0present +| 4000: 01 02 05 05 1b 84 80 22 80 80 02 03 00 3c 00 00 .............<.. +| 4016: 00 16 04 30 66 74 73 01 06 01 01 04 01 02 69 6e ...0fts.......in +| 4032: 01 06 01 01 04 0a 1a 84 80 80 80 80 01 03 00 3a ...............: +| 4048: 00 00 00 15 05 30 65 61 63 68 01 02 03 01 03 66 .....0each.....f +| 4064: 6f 72 01 02 02 04 09 06 01 03 00 12 03 0b 0f 00 or.............. +| 4080: 00 08 8c 80 80 80 80 11 03 00 16 00 00 00 05 04 ................ +| page 3 offset 8192 +| 0: 0a 00 00 00 32 0e 4f 00 0f fa 0f f1 0f e9 0f e1 ....2.O......... +| 16: 0f d8 0f d1 0f c9 0f c1 0f b9 0f b1 0f a9 0f a0 ................ +| 32: 0f 98 0f 90 0f 87 0f 80 0f 78 0f 71 0f 68 0f 5f .........x.q.h._ +| 48: 0f 56 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 .V.............. +| 3392: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 ................ +| 3408: 04 01 10 01 03 34 74 20 07 04 01 0e 01 03 34 1e .....4t ......4. +| 3424: 09 04 01 12 01 03 33 74 68 1c 08 04 01 10 01 03 ......3th....... +| 3440: 34 6e 1a 08 04 01 10 01 03 32 67 18 08 04 01 10 4n.......2g..... +| 3456: 01 03 32 74 16 08 04 01 10 01 03 32 6e 14 07 04 ..2t.......2n... +| 3472: 01 0e 01 03 32 12 08 04 01 10 01 03 31 74 10 08 ....2.......1t.. +| 3488: 04 01 10 01 03 31 6e 0e 07 04 01 0e 01 03 31 0c .....1n.......1. +| 3504: 09 04 01 12 01 03 30 74 68 0a 08 04 01 10 01 03 ......0th....... +| 3520: 30 74 08 09 04 01 12 01 03 30 6e 75 06 08 04 01 0t.......0nu.... +| 3536: 10 01 03 30 6e 04 06 04 01 0c 01 03 02 08 04 01 ...0n........... +| 3552: 10 01 02 34 72 22 07 04 01 0e 01 02 34 20 08 04 ...4r.......4 .. +| 3568: 01 10 01 02 33 72 1e 09 04 01 12 01 02 33 61 72 ....3r.......3ar +| 3584: 1c 18 04 01 10 01 02 32 74 1a 08 04 01 10 01 02 .......2t....... +| 3600: 32 69 18 09 04 01 12 01 02 32 61 72 16 08 04 01 2i.......2ar.... +| 3616: 10 01 02 31 74 2a 08 04 01 0f 01 02 31 6e 12 0a ...1t*......1n.. +| 3632: d4 01 10 01 02 31 62 10 08 04 01 10 01 02 31 32 .....1b.......12 +| 3648: 0e 0b 04 01 16 01 02 30 74 68 65 72 0c 08 04 01 .......0ther.... +| 3664: 10 01 02 30 74 0a 08 04 01 10 01 02 30 6e 08 08 ...0t.......0n.. +| 3680: 04 01 10 01 02 30 62 06 08 04 01 10 01 02 30 61 .....0b.......0a +| 3696: 04 06 03 f1 0c 01 02 02 07 04 09 10 01 34 74 22 .............4t. +| 3712: 06 04 09 0e 01 34 20 08 04 09 12 01 33 74 65 1e .....4 .....3te. +| 3728: 07 04 09 10 01 33 70 1c 07 04 09 10 01 33 66 1a .....3p......3f. +| 3744: 08 04 09 12 01 32 74 68 18 07 04 09 10 01 32 74 .....2th......2t +| 3760: 16 07 04 09 10 01 32 69 14 07 04 09 10 01 32 66 ......2i......2f +| 3776: 12 07 04 09 10 01 31 74 10 07 04 09 10 01 31 69 ......1t......1i +| 3792: 0e 06 04 09 0e 01 31 0c 08 04 09 12 01 30 74 65 ......1......0te +| 3808: 0a 07 04 09 10 01 30 74 08 07 04 09 10 01 30 70 ......0t......0p +| 3824: 06 08 04 09 1e 61 30 66 74 04 05 00 00 00 00 00 .....a0ft....... +| page 4 offset 12288 +| 4064: 00 00 00 00 00 00 00 00 00 00 00 05 03 03 00 10 ................ +| 4080: 03 05 05 02 03 00 10 04 06 05 01 03 00 10 04 04 ................ +| page 5 offset 16384 +| 0: 0a 00 00 00 02 0f eb 00 0f eb 0f f4 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 08 03 15 01 70 ...............p +| 4080: 67 73 7a 18 0b 03 1b 01 76 65 72 73 69 6f 6e 04 gsz.....version. +| end crash-ef6738247b1344.db +}]} {} + + +do_catchsql_test 4.1 { + BEGIN; + REPLACE INTO t1(rowid,b,a,rowid) VALUES(200,1,2,3); +} {1 {database disk image is malformed}} + +do_catchsql_test 4.2 { + INSERT INTO t1(t1) VALUES('delete-all'); +} {1 {database disk image is malformed}} +do_catchsql_test 4.3 { + REPLACE INTO t1(rowid,b,rowid,a) VALUES(200,1,2,3); +} {1 {database disk image is malformed}} +do_catchsql_test 4.4 { + REPLACE INTO t1(rowid,b,a,rowid) VALUES(0,1,2,3); +} {1 {database disk image is malformed}} +do_catchsql_test 4.5 { + REPLACE INTO t1(rowid,a,b,rowid) VALUES(200,1,2,3); +} {1 {database disk image is malformed}} + +sqlite3_fts5_may_be_corrupt 0 +finish_test + diff --git a/ext/fts5/test/fts5corrupt6.test b/ext/fts5/test/fts5corrupt6.test new file mode 100644 index 000000000..6403d3a40 --- /dev/null +++ b/ext/fts5/test/fts5corrupt6.test @@ -0,0 +1,54 @@ +# 2015 Apr 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 tests that FTS5 handles corrupt databases (i.e. internal +# inconsistencies in the backing tables) correctly. In this case +# "correctly" means without crashing. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5corrupt6 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} +sqlite3_fts5_may_be_corrupt 1 +database_may_be_corrupt + +proc editblock {block} { + binary format Sa* 20000 [string range $block 2 end] +} +db func editblock editblock + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(abc, def); + WITH a(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM a WHERE i<1000 + ) + INSERT INTO ft SELECT + 'abc abc abc abc abc abc abc abc abc abc', + 'def def def def def def def def def def' + FROM a; + UPDATE ft_data SET block = editblock(block) WHERE id=( + SELECT id FROM ft_data ORDER BY id LIMIT 1 OFFSET 5 + ); +} + +do_catchsql_test 1.1 { + SELECT rowid FROM ft('def') ORDER BY rowid DESC LIMIT 1 OFFSET 9999; +} {1 {database disk image is malformed}} + + +sqlite3_fts5_may_be_corrupt 0 +finish_test + diff --git a/ext/fts5/test/fts5detail.test b/ext/fts5/test/fts5detail.test index cf4b718ba..267ce6187 100644 --- a/ext/fts5/test/fts5detail.test +++ b/ext/fts5/test/fts5detail.test @@ -212,6 +212,10 @@ do_catchsql_test 4.1 { SELECT * FROM t4('a:a') } {1 {fts5: column queries are not supported (detail=none)}} +do_catchsql_test 4.2 { + SELECT * FROM t4('a:a &') +} {1 {fts5: syntax error near "&"}} + #------------------------------------------------------------------------- # Test that for the same content detail=none uses less space than # detail=col, and that detail=col uses less space than detail=full diff --git a/ext/fts5/test/fts5misc.test b/ext/fts5/test/fts5misc.test index 9abc92b23..e354d20e2 100644 --- a/ext/fts5/test/fts5misc.test +++ b/ext/fts5/test/fts5misc.test @@ -323,5 +323,34 @@ do_execsql_test 12.3 { SELECT * FROM t2 JOIN ft USING (ft) } {3 4 b b} +#------------------------------------------------------------------------- +# Forum post https://sqlite.org/forum/forumpost/21127c1160 +# +reset_db +sqlite3_db_config db DEFENSIVE 1 + +do_execsql_test 13.0 { + CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT); + CREATE VIRTUAL TABLE b USING fts5(name); + CREATE TRIGGER a_trigger AFTER INSERT ON a BEGIN + INSERT INTO b (name) VALUES ('foo'); + END; +} + +do_test 13.1 { + set ::STMT [ + sqlite3_prepare db "INSERT INTO a VALUES (1, 'foo') RETURNING id;" -1 dummy + ] + sqlite3_step $::STMT +} {SQLITE_ROW} + +do_test 13.2 { + sqlite3_finalize $::STMT +} {SQLITE_OK} + +do_test 13.3 { + sqlite3_errmsg db +} {not an error} + finish_test diff --git a/ext/fts5/test/fts5vocab2.test b/ext/fts5/test/fts5vocab2.test index 45d657146..6f7aad329 100644 --- a/ext/fts5/test/fts5vocab2.test +++ b/ext/fts5/test/fts5vocab2.test @@ -234,4 +234,53 @@ ifcapable fts3 { } {1 {no such fts5 table: main.nosuchtable}} } +#------------------------------------------------------------------------- +# Check that the fts5 table cannot be written while there are vocab +# cursors open. +reset_db +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a); + CREATE VIRTUAL TABLE v1 USING fts5vocab(t1, instance); + INSERT INTO t1 VALUES('one'), ('two'), ('three'), ('four'); +} + +do_test 5.1 { + list [catch { + db eval { SELECT * FROM v1 } { + db eval {INSERT INTO t1 VALUES('five')} + } + } msg] $msg +} {1 {query aborted}} + +do_execsql_test 5.2 { + SELECT * FROM t1 +} {one two three four five} + +#------------------------------------------------------------------------- +# Check that the fts5 table cannot be written while there are vocab +# cursors open. +reset_db +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a); + CREATE VIRTUAL TABLE v1 USING fts5vocab(t1, instance); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<10000 + ) + INSERT INTO t1 SELECT + 'State Emergency Service (SES), Rural Fire Service (RFS) and Volunteers' + FROM s; +} + +do_catchsql_test 5.1 { + INSERT INTO t1 SELECT rowid FROM v1 +} {1 {query aborted}} + +do_catchsql_test 5.2 { + DELETE FROM t1 WHERE rowid>100; + INSERT INTO t1 SELECT randomblob(3000) FROM v1 +} {1 {query aborted}} + + finish_test + + diff --git a/ext/fts5/tool/mkfts5c.tcl b/ext/fts5/tool/mkfts5c.tcl index 797811d46..b1a55fa4a 100644 --- a/ext/fts5/tool/mkfts5c.tcl +++ b/ext/fts5/tool/mkfts5c.tcl @@ -60,7 +60,8 @@ proc fts5_source_id {zDir} { set L [split [readfile [file join $top manifest]]] set date [lindex $L [expr [lsearch -exact $L D]+1]] - set date [string range $date 0 [string last . $date]-1] + set idx [expr {[string last . $date]-1}] + set date [string range $date 0 $idx] set date [string map {T { }} $date] return "fts5: $date $uuid" diff --git a/ext/misc/carray.c b/ext/misc/carray.c index b8cda21bc..b6eb0453a 100644 --- a/ext/misc/carray.c +++ b/ext/misc/carray.c @@ -56,14 +56,24 @@ SQLITE_EXTENSION_INIT1 #include #include - + /* Allowed values for the mFlags parameter to sqlite3_carray_bind(). ** Must exactly match the definitions in carray.h. */ -#define CARRAY_INT32 0 /* Data is 32-bit signed integers */ -#define CARRAY_INT64 1 /* Data is 64-bit signed integers */ -#define CARRAY_DOUBLE 2 /* Data is doubles */ -#define CARRAY_TEXT 3 /* Data is char* */ +#ifndef CARRAY_INT32 +# define CARRAY_INT32 0 /* Data is 32-bit signed integers */ +# define CARRAY_INT64 1 /* Data is 64-bit signed integers */ +# define CARRAY_DOUBLE 2 /* Data is doubles */ +# define CARRAY_TEXT 3 /* Data is char* */ +#endif + +#ifndef SQLITE_API +# ifdef _WIN32 +# define SQLITE_API __declspec(dllexport) +# else +# define SQLITE_API +# endif +#endif #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -400,10 +410,7 @@ static void carrayBindDel(void *pPtr){ ** Invoke this interface in order to bind to the single-argument ** version of CARRAY(). */ -#ifdef _WIN32 -__declspec(dllexport) -#endif -int sqlite3_carray_bind( +SQLITE_API int sqlite3_carray_bind( sqlite3_stmt *pStmt, int idx, void *aData, @@ -457,7 +464,7 @@ int sqlite3_carray_bind( z += n+1; } }else{ - memcpy(pNew->aData, aData, sz*nData); + memcpy(pNew->aData, aData, sz); } pNew->xDel = sqlite3_free; }else{ @@ -498,10 +505,7 @@ static void inttoptrFunc( #endif /* SQLITE_OMIT_VIRTUALTABLE */ -#ifdef _WIN32 -__declspec(dllexport) -#endif -int sqlite3_carray_init( +SQLITE_API int sqlite3_carray_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi diff --git a/ext/misc/carray.h b/ext/misc/carray.h index e490bf24c..63df0066b 100644 --- a/ext/misc/carray.h +++ b/ext/misc/carray.h @@ -1,12 +1,32 @@ /* +** 2020-11-17 +** +** 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. +** +************************************************************************* +** ** Interface definitions for the CARRAY table-valued function ** extension. */ +#ifndef _CARRAY_H +#define _CARRAY_H + +#include "sqlite3.h" /* Required for error code definitions */ + +#ifdef __cplusplus +extern "C" { +#endif + /* Use this interface to bind an array to the single-argument version ** of CARRAY(). */ -int sqlite3_carray_bind( +SQLITE_API int sqlite3_carray_bind( sqlite3_stmt *pStmt, /* Statement to be bound */ int i, /* Parameter index */ void *aData, /* Pointer to array data */ @@ -21,3 +41,10 @@ int sqlite3_carray_bind( #define CARRAY_INT64 1 /* Data is 64-bit signed integers */ #define CARRAY_DOUBLE 2 /* Data is doubles */ #define CARRAY_TEXT 3 /* Data is char* */ + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* ifndef _CARRAY_H */ + diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c index d977d41dd..c9988f607 100644 --- a/ext/misc/fileio.c +++ b/ext/misc/fileio.c @@ -72,6 +72,11 @@ ** $path is a relative path, then $path is interpreted relative to $dir. ** And the paths returned in the "name" column of the table are also ** relative to directory $dir. +** +** Notes on building this extension for Windows: +** Unless linked statically with the SQLite library, a preprocessor +** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone +** DLL form of this extension for WIN32. See its use below for details. */ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 @@ -225,6 +230,22 @@ static sqlite3_uint64 fileTimeToUnixTime( return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; } + +#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32)) +# /* To allow a standalone DLL, use this next replacement function: */ +# undef sqlite3_win32_utf8_to_unicode +# define sqlite3_win32_utf8_to_unicode utf8_to_utf16 +# +LPWSTR utf8_to_utf16(const char *z){ + int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0); + LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR)); + if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) ) + return rv; + sqlite3_free(rv); + return 0; +} +#endif + /* ** This function attempts to normalize the time values found in the stat() ** buffer to UTC. This is necessary on Win32, where the runtime library @@ -998,3 +1019,11 @@ int sqlite3_fileio_init( } return rc; } + +#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32)) +/* To allow a standalone DLL, make test_windirent.c use the same + * redefined SQLite API calls as the above extension code does. + * Just pull in this .c to accomplish this. As a beneficial side + * effect, this extension becomes a single translation unit. */ +# include "test_windirent.c" +#endif diff --git a/ext/misc/ieee754.c b/ext/misc/ieee754.c index 6cdd79a4d..66d946f3d 100644 --- a/ext/misc/ieee754.c +++ b/ext/misc/ieee754.c @@ -194,7 +194,11 @@ static void ieee754func( e += 1075; if( e<=0 ){ /* Subnormal */ - m >>= 1-e; + if( 1-e >= 64 ){ + m = 0; + }else{ + m >>= 1-e; + } e = 0; }else if( e>0x7ff ){ e = 0x7ff; diff --git a/ext/misc/json1.c b/ext/misc/json1.c index 077d02d91..7fcd7342a 100644 --- a/ext/misc/json1.c +++ b/ext/misc/json1.c @@ -96,7 +96,34 @@ static const char jsonIsSpace[] = { typedef unsigned int u32; typedef unsigned short int u16; typedef unsigned char u8; +# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +# endif +# if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +# elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +# else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +# endif +# define testcase(X) #endif +#if !defined(SQLITE_DEBUG) && !defined(SQLITE_COVERAGE_TEST) +# define VVA(X) +#else +# define VVA(X) X +#endif + +/* +** Some of the testcase() macros in this file are problematic for gcov +** in that they generate false-miss errors randomly. This is a gcov problem, +** not a problem in this case. But to work around it, we disable the +** problematic test cases for production builds. +*/ +#define json_testcase(X) /* Objects */ typedef struct JsonString JsonString; @@ -154,13 +181,14 @@ static const char * const jsonType[] = { struct JsonNode { u8 eType; /* One of the JSON_ type values */ u8 jnFlags; /* JNODE flags */ + u8 eU; /* Which union element to use */ u32 n; /* Bytes of content, or number of sub-nodes */ union { - const char *zJContent; /* Content for INT, REAL, and STRING */ - u32 iAppend; /* More terms for ARRAY and OBJECT */ - u32 iKey; /* Key for ARRAY objects in json_tree() */ - u32 iReplace; /* Replacement content for JNODE_REPLACE */ - JsonNode *pPatch; /* Node chain of patch for JNODE_PATCH */ + const char *zJContent; /* 1: Content for INT, REAL, and STRING */ + u32 iAppend; /* 2: More terms for ARRAY and OBJECT */ + u32 iKey; /* 3: Key for ARRAY objects in json_tree() */ + u32 iReplace; /* 4: Replacement content for JNODE_REPLACE */ + JsonNode *pPatch; /* 5: Node chain of patch for JNODE_PATCH */ } u; }; @@ -438,11 +466,14 @@ static void jsonRenderNode( JsonString *pOut, /* Write JSON here */ sqlite3_value **aReplace /* Replacement values */ ){ + assert( pNode!=0 ); if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){ - if( pNode->jnFlags & JNODE_REPLACE ){ + if( (pNode->jnFlags & JNODE_REPLACE)!=0 && ALWAYS(aReplace!=0) ){ + assert( pNode->eU==4 ); jsonAppendValue(pOut, aReplace[pNode->u.iReplace]); return; } + assert( pNode->eU==5 ); pNode = pNode->u.pPatch; } switch( pNode->eType ){ @@ -461,6 +492,7 @@ static void jsonRenderNode( } case JSON_STRING: { if( pNode->jnFlags & JNODE_RAW ){ + assert( pNode->eU==1 ); jsonAppendString(pOut, pNode->u.zJContent, pNode->n); break; } @@ -468,6 +500,7 @@ static void jsonRenderNode( } case JSON_REAL: case JSON_INT: { + assert( pNode->eU==1 ); jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); break; } @@ -483,6 +516,7 @@ static void jsonRenderNode( j += jsonNodeSize(&pNode[j]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + assert( pNode->eU==2 ); pNode = &pNode[pNode->u.iAppend]; j = 1; } @@ -503,6 +537,7 @@ static void jsonRenderNode( j += 1 + jsonNodeSize(&pNode[j+1]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + assert( pNode->eU==2 ); pNode = &pNode[pNode->u.iAppend]; j = 1; } @@ -582,7 +617,9 @@ static void jsonReturn( } case JSON_INT: { sqlite3_int64 i = 0; - const char *z = pNode->u.zJContent; + const char *z; + assert( pNode->eU==1 ); + z = pNode->u.zJContent; if( z[0]=='-' ){ z++; } while( z[0]>='0' && z[0]<='9' ){ unsigned v = *(z++) - '0'; @@ -605,14 +642,17 @@ static void jsonReturn( sqlite3_result_int64(pCtx, i); int_done: break; - int_as_real: i=0; /* no break */ deliberate_fall_through + int_as_real: ; /* no break */ deliberate_fall_through } case JSON_REAL: { double r; #ifdef SQLITE_AMALGAMATION - const char *z = pNode->u.zJContent; + const char *z; + assert( pNode->eU==1 ); + z = pNode->u.zJContent; sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); #else + assert( pNode->eU==1 ); r = strtod(pNode->u.zJContent, 0); #endif sqlite3_result_double(pCtx, r); @@ -623,6 +663,7 @@ static void jsonReturn( ** json_insert() and json_replace() and those routines do not ** call jsonReturn() */ if( pNode->jnFlags & JNODE_RAW ){ + assert( pNode->eU==1 ); sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n, SQLITE_TRANSIENT); }else @@ -630,15 +671,18 @@ static void jsonReturn( assert( (pNode->jnFlags & JNODE_RAW)==0 ); if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ /* JSON formatted without any backslash-escapes */ + assert( pNode->eU==1 ); sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2, SQLITE_TRANSIENT); }else{ /* Translate JSON formatted string into raw text */ u32 i; u32 n = pNode->n; - const char *z = pNode->u.zJContent; + const char *z; char *zOut; u32 j; + assert( pNode->eU==1 ); + z = pNode->u.zJContent; zOut = sqlite3_malloc( n+1 ); if( zOut==0 ){ sqlite3_result_error_nomem(pCtx); @@ -759,12 +803,13 @@ static int jsonParseAddNode( const char *zContent /* Content */ ){ JsonNode *p; - if( pParse->nNode>=pParse->nAlloc ){ + if( pParse->aNode==0 || pParse->nNode>=pParse->nAlloc ){ return jsonParseAddNodeExpand(pParse, eType, n, zContent); } p = &pParse->aNode[pParse->nNode]; p->eType = (u8)eType; p->jnFlags = 0; + VVA( p->eU = zContent ? 1 : 0 ); p->n = n; p->u.zJContent = zContent; return pParse->nNode++; @@ -832,6 +877,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ /* Parse array */ iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); if( iThis<0 ) return -1; + memset(&pParse->aNode[iThis].u, 0, sizeof(pParse->aNode[iThis].u)); for(j=i+1;;j++){ while( safe_isspace(z[j]) ){ j++; } if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; @@ -1096,6 +1142,7 @@ static JsonParse *jsonParseCached( ** a match. */ static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){ + assert( pNode->eU==1 ); if( pNode->jnFlags & JNODE_RAW ){ if( pNode->n!=nKey ) return 0; return strncmp(pNode->u.zJContent, zKey, nKey)==0; @@ -1161,6 +1208,7 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + assert( pRoot->eU==2 ); iRoot += pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; @@ -1175,8 +1223,10 @@ static JsonNode *jsonLookupStep( if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; + assert( pRoot->eU==0 ); pRoot->u.iAppend = iStart - iRoot; pRoot->jnFlags |= JNODE_APPEND; + VVA( pRoot->eU = 2 ); pParse->aNode[iLabel].jnFlags |= JNODE_RAW; } return pNode; @@ -1199,6 +1249,7 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pBase[j]); } if( (pBase->jnFlags & JNODE_APPEND)==0 ) break; + assert( pBase->eU==2 ); iBase += pBase->u.iAppend; pBase = &pParse->aNode[iBase]; j = 1; @@ -1232,6 +1283,7 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + assert( pRoot->eU==2 ); iRoot += pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; @@ -1247,8 +1299,10 @@ static JsonNode *jsonLookupStep( if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; + assert( pRoot->eU==0 ); pRoot->u.iAppend = iStart - iRoot; pRoot->jnFlags |= JNODE_APPEND; + VVA( pRoot->eU = 2 ); } return pNode; } @@ -1402,9 +1456,13 @@ static void jsonParseFunc( } jsonPrintf(100, &s,"node %3u: %7s n=%-4d up=%-4d", i, zType, x.aNode[i].n, x.aUp[i]); + assert( x.aNode[i].eU==0 || x.aNode[i].eU==1 ); if( x.aNode[i].u.zJContent!=0 ){ + assert( x.aNode[i].eU==1 ); jsonAppendRaw(&s, " ", 1); jsonAppendRaw(&s, x.aNode[i].u.zJContent, x.aNode[i].n); + }else{ + assert( x.aNode[i].eU==0 ); } jsonAppendRaw(&s, "\n", 1); } @@ -1587,6 +1645,7 @@ static JsonNode *jsonMergePatch( const char *zKey; assert( pPatch[i].eType==JSON_STRING ); assert( pPatch[i].jnFlags & JNODE_LABEL ); + assert( pPatch[i].eU==1 ); nKey = pPatch[i].n; zKey = pPatch[i].u.zJContent; assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); @@ -1603,6 +1662,12 @@ static JsonNode *jsonMergePatch( if( pNew==0 ) return 0; pTarget = &pParse->aNode[iTarget]; if( pNew!=&pTarget[j+1] ){ + assert( pTarget[j+1].eU==0 + || pTarget[j+1].eU==1 + || pTarget[j+1].eU==2 ); + testcase( pTarget[j+1].eU==1 ); + testcase( pTarget[j+1].eU==2 ); + VVA( pTarget[j+1].eU = 5 ); pTarget[j+1].u.pPatch = pNew; pTarget[j+1].jnFlags |= JNODE_PATCH; } @@ -1618,9 +1683,14 @@ static JsonNode *jsonMergePatch( if( pParse->oom ) return 0; jsonRemoveAllNulls(pPatch); pTarget = &pParse->aNode[iTarget]; + assert( pParse->aNode[iRoot].eU==0 || pParse->aNode[iRoot].eU==2 ); + testcase( pParse->aNode[iRoot].eU==2 ); pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; + VVA( pParse->aNode[iRoot].eU = 2 ); pParse->aNode[iRoot].u.iAppend = iStart - iRoot; iRoot = iStart; + assert( pParse->aNode[iPatch].eU==0 ); + VVA( pParse->aNode[iPatch].eU = 5 ); pParse->aNode[iPatch].jnFlags |= JNODE_PATCH; pParse->aNode[iPatch].u.pPatch = &pPatch[i+1]; } @@ -1762,11 +1832,15 @@ static void jsonReplaceFunc( pNode = jsonLookup(&x, zPath, 0, ctx); if( x.nErr ) goto replace_err; if( pNode ){ + assert( pNode->eU==0 || pNode->eU==1 || pNode->eU==4 ); + json_testcase( pNode->eU!=0 && pNode->eU!=1 ); pNode->jnFlags |= (u8)JNODE_REPLACE; + VVA( pNode->eU = 4 ); pNode->u.iReplace = i + 1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ + assert( x.aNode[0].eU==4 ); sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); }else{ jsonReturnJson(x.aNode, ctx, argv); @@ -1816,11 +1890,15 @@ static void jsonSetFunc( }else if( x.nErr ){ goto jsonSetDone; }else if( pNode && (bApnd || bIsSet) ){ + json_testcase( pNode->eU!=0 && pNode->eU!=1 && pNode->eU!=4 ); + assert( pNode->eU!=3 || pNode->eU!=5 ); + VVA( pNode->eU = 4 ); pNode->jnFlags |= (u8)JNODE_REPLACE; pNode->u.iReplace = i + 1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ + assert( x.aNode[0].eU==4 ); sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); }else{ jsonReturnJson(x.aNode, ctx, argv); @@ -2171,6 +2249,9 @@ static int jsonEachNext(sqlite3_vtab_cursor *cur){ JsonNode *pUp = &p->sParse.aNode[iUp]; p->eType = pUp->eType; if( pUp->eType==JSON_ARRAY ){ + assert( pUp->eU==0 || pUp->eU==3 ); + json_testcase( pUp->eU==3 ); + VVA( pUp->eU = 3 ); if( iUp==p->i-1 ){ pUp->u.iKey = 0; }else{ @@ -2217,12 +2298,15 @@ static void jsonEachComputePath( pNode = &p->sParse.aNode[i]; pUp = &p->sParse.aNode[iUp]; if( pUp->eType==JSON_ARRAY ){ + assert( pUp->eU==3 || (pUp->eU==0 && pUp->u.iKey==0) ); + testcase( pUp->eU==0 ); jsonPrintf(30, pStr, "[%d]", pUp->u.iKey); }else{ assert( pUp->eType==JSON_OBJECT ); if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--; assert( pNode->eType==JSON_STRING ); assert( pNode->jnFlags & JNODE_LABEL ); + assert( pNode->eU==1 ); jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1); } } @@ -2244,6 +2328,7 @@ static int jsonEachColumn( u32 iKey; if( p->bRecursive ){ if( p->iRowid==0 ) break; + assert( p->sParse.aNode[p->sParse.aUp[p->i]].eU==3 ); iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey; }else{ iKey = p->iRowid; @@ -2293,6 +2378,7 @@ static int jsonEachColumn( if( p->eType==JSON_ARRAY ){ jsonPrintf(30, &x, "[%d]", p->iRowid); }else if( p->eType==JSON_OBJECT ){ + assert( pThis->eU==1 ); jsonPrintf(pThis->n, &x, ".%.*s", pThis->n-2, pThis->u.zJContent+1); } } @@ -2360,6 +2446,7 @@ static int jsonEachBestIndex( if( pConstraint->iColumn < JEACH_JSON ) continue; iCol = pConstraint->iColumn - JEACH_JSON; assert( iCol==0 || iCol==1 ); + testcase( iCol==0 ); iMask = 1 << iCol; if( pConstraint->usable==0 ){ unusableMask |= iMask; @@ -2457,6 +2544,8 @@ static int jsonEachFilter( p->iBegin = p->i = (int)(pNode - p->sParse.aNode); p->eType = pNode->eType; if( p->eType>=JSON_ARRAY ){ + assert( pNode->eU==0 ); + VVA( pNode->eU = 3 ); pNode->u.iKey = 0; p->iEnd = p->i + pNode->n + 1; if( p->bRecursive ){ diff --git a/ext/misc/regexp.c b/ext/misc/regexp.c index 99fb453ce..f282e777f 100644 --- a/ext/misc/regexp.c +++ b/ext/misc/regexp.c @@ -299,9 +299,9 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ } case RE_OP_CC_EXC: { if( c==0 ) break; - /* fall-through */ + /* fall-through */ goto re_op_cc_inc; } - case RE_OP_CC_INC: { + case RE_OP_CC_INC: re_op_cc_inc: { int j = 1; int n = pRe->aArg[x]; int hit = 0; diff --git a/ext/misc/series.c b/ext/misc/series.c index a4e92ace8..e8d8c10ae 100644 --- a/ext/misc/series.c +++ b/ext/misc/series.c @@ -323,11 +323,12 @@ static int seriesFilter( ** (8) output in descending order */ static int seriesBestIndex( - sqlite3_vtab *tabUnused, + sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo ){ int i, j; /* Loop over constraints */ int idxNum = 0; /* The query plan bitmask */ + int bStartSeen = 0; /* EQ constraint seen on the START column */ int unusableMask = 0; /* Mask of unusable constraints */ int nArg = 0; /* Number of arguments that seriesFilter() expects */ int aIdx[3]; /* Constraints on start, stop, and step */ @@ -337,7 +338,7 @@ static int seriesBestIndex( ** are the last three columns in the virtual table. */ assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); - (void)tabUnused; + aIdx[0] = aIdx[1] = aIdx[2] = -1; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ @@ -347,6 +348,7 @@ static int seriesBestIndex( iCol = pConstraint->iColumn - SERIES_COLUMN_START; assert( iCol>=0 && iCol<=2 ); iMask = 1 << iCol; + if( iCol==0 ) bStartSeen = 1; if( pConstraint->usable==0 ){ unusableMask |= iMask; continue; @@ -361,6 +363,18 @@ static int seriesBestIndex( pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; } } + /* The current generate_column() implementation requires at least one + ** argument (the START value). Legacy versions assumed START=0 if the + ** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES + ** to obtain the legacy behavior */ +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + if( !bStartSeen ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf( + "first argument to \"generate_series()\" missing or unusable"); + return SQLITE_ERROR; + } +#endif if( (unusableMask & ~idxNum)!=0 ){ /* The start, stop, and step columns are inputs. Therefore if there ** are unusable constraints on any of start, stop, or step then @@ -434,7 +448,7 @@ int sqlite3_series_init( int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); #ifndef SQLITE_OMIT_VIRTUALTABLE - if( sqlite3_libversion_number()<3008012 ){ + if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){ *pzErrMsg = sqlite3_mprintf( "generate_series() requires SQLite 3.8.12 or later"); return SQLITE_ERROR; diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index b727f9f2b..b7ceb9396 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -57,6 +57,9 @@ typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ #define MIN(a,b) ((a)<(b) ? (a) : (b)) #if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) # define ALWAYS(X) (1) # define NEVER(X) (0) #elif !defined(NDEBUG) @@ -561,6 +564,7 @@ static u16 zipfileGetU16(const u8 *aBuf){ ** Read and return a 32-bit little-endian unsigned integer from buffer aBuf. */ static u32 zipfileGetU32(const u8 *aBuf){ + if( aBuf==0 ) return 0; return ((u32)(aBuf[3]) << 24) + ((u32)(aBuf[2]) << 16) + ((u32)(aBuf[1]) << 8) @@ -863,7 +867,7 @@ static int zipfileGetEntry( aRead = (u8*)&aBlob[pNew->cds.iOffset]; } - rc = zipfileReadLFH(aRead, &lfh); + if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh); if( rc==SQLITE_OK ){ pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ; pNew->iDataOff += lfh.nFile + lfh.nExtra; @@ -1139,13 +1143,13 @@ static int zipfileReadEOCD( int nRead; /* Bytes to read from file */ int rc = SQLITE_OK; + memset(pEOCD, 0, sizeof(ZipfileEOCD)); if( aBlob==0 ){ i64 iOff; /* Offset to read from */ i64 szFile; /* Total size of file in bytes */ fseek(pFile, 0, SEEK_END); szFile = (i64)ftell(pFile); if( szFile==0 ){ - memset(pEOCD, 0, sizeof(ZipfileEOCD)); return SQLITE_OK; } nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE)); diff --git a/ext/rbu/rbu10.test b/ext/rbu/rbu10.test index a7c63cf6d..aa4db8a29 100644 --- a/ext/rbu/rbu10.test +++ b/ext/rbu/rbu10.test @@ -50,7 +50,7 @@ do_test 1.1 { # ifcapable fts3 { do_execsql_test 2.0 { - CREATE VIRTUAL TABLE ft USING fts4(a, b, languageid='langid'); + create virtual TABLE ft USING fts4(a, b, languageid='langid'); } do_test 2.1 { apply_rbu { diff --git a/ext/rbu/rbuexlock.test b/ext/rbu/rbuexlock.test new file mode 100644 index 000000000..eddcdc115 --- /dev/null +++ b/ext/rbu/rbuexlock.test @@ -0,0 +1,207 @@ +# 2021 November 06 +# +# 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] rbu_common.tcl] +set ::testprefix rbuexlock + +db close +sqlite3_shutdown +sqlite3_config_uri 1 + +# Create a simple RBU database. That expects to write to a table: +# +# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); +# +proc create_rbu {filename} { + forcedelete $filename + sqlite3 rbu1 $filename + rbu1 eval { + CREATE TABLE data_t1(a, b, c, rbu_control); + INSERT INTO data_t1 VALUES(10, random(), random(), 0); + INSERT INTO data_t1 VALUES(20, random(), random(), 0); + INSERT INTO data_t1 VALUES(30, random(), random(), 0); + INSERT INTO data_t1 VALUES(40, random(), random(), 0); + INSERT INTO data_t1 VALUES(50, random(), random(), 0); + INSERT INTO data_t1 VALUES(60, random(), random(), 0); + INSERT INTO data_t1 VALUES(70, random(), random(), 0); + INSERT INTO data_t1 VALUES(80, random(), random(), 0); + } + rbu1 close + return $filename +} + +reset_db + +do_execsql_test 1.0 { + CREATE TABLE t1(a PRIMARY KEY, b INT, c INT); + CREATE INDEX t1b ON t1(b); + CREATE INDEX t1c ON t1(c); + INSERT INTO t1 VALUES(1, 2, 3); +} +create_rbu rbu1.db + +do_test 1.1.0 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db + rbu step +} SQLITE_OK +do_catchsql_test 1.1.1 { SELECT * FROM t1 } {0 {1 2 3}} + +do_test 1.2.0 { + for {set ii 0} {$ii < 10} {incr ii} { + rbu step + } + rbu step +} SQLITE_OK +do_catchsql_test 1.2.1 { SELECT * FROM t1 } {0 {1 2 3}} +do_test 1.2.2 { + db eval {PRAGMA journal_mode} +} {delete} + +do_test 1.3.0 { + while {[file exists test.db-wal]==0} { + rbu step + } +} {} +do_catchsql_test 1.3.1 { SELECT * FROM t1 } {1 {database is locked}} +do_test 1.3.2 { + db eval {PRAGMA journal_mode} +} {delete} + + +do_test 1.4.0 { + rbu step +} SQLITE_OK +do_catchsql_test 1.4.1 { SELECT * FROM t1 } {1 {database is locked}} +do_test 1.4.2 { + db eval {PRAGMA journal_mode} +} {delete} + + +rbu close + +do_test 1.5.0 { + file exists test.db-wal +} {1} +do_test 1.5.1 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db + file exists test.db-wal +} 1 +do_catchsql_test 1.5.2 { SELECT * FROM t1 } {1 {database is locked}} +do_test 1.5.2 { + db eval {PRAGMA journal_mode} +} {delete} + + +do_test 1.6.0 { + rbu step +} SQLITE_OK +do_catchsql_test 1.6.1 { SELECT * FROM t1 } {1 {database is locked}} +do_test 1.6.2 { + db eval {PRAGMA journal_mode} +} {delete} + +do_test 1.7.0 { + while {[rbu step]=="SQLITE_OK"} {} + rbu close +} SQLITE_DONE +do_catchsql_test 1.7.2 { SELECT count(*) FROM t1 } {0 9} +do_test 1.7.2 { + db eval {PRAGMA journal_mode} +} {delete} + +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a PRIMARY KEY, b INT, c INT); + CREATE INDEX t1b ON t1(b); + CREATE INDEX t1c ON t1(c); + INSERT INTO t1 VALUES(1, 2, 3); +} +create_rbu rbu1.db + +do_test 2.1.0 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=0 rbu1.db + rbu step +} SQLITE_OK +do_catchsql_test 2.1.1 { SELECT * FROM t1 } {0 {1 2 3}} + +do_test 2.2.0 { + for {set ii 0} {$ii < 10} {incr ii} { + rbu step + } + rbu step +} SQLITE_OK +do_catchsql_test 2.2.1 { SELECT * FROM t1 } {0 {1 2 3}} + +do_test 2.3.0 { + while {[file exists test.db-wal]==0} { + rbu step + } +} {} +do_test 2.3.1 { + llength [db eval {SELECT * FROM t1}] +} {27} +do_test 2.3.2 { + db eval {PRAGMA journal_mode} +} {wal} + +do_test 2.4.0 { + rbu step +} SQLITE_OK +do_test 2.4.1 { + llength [db eval {SELECT * FROM t1}] +} {27} +do_test 2.4.2 { + db eval {PRAGMA journal_mode} +} {wal} + +rbu close + +do_test 2.5.0 { + db eval {PRAGMA journal_mode} +} {wal} +do_execsql_test 2.5.1 { + DELETE FROM t1; +} {} + +create_rbu rbu1.db +do_test 3.1.0 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=0 rbu1.db + rbu step +} SQLITE_ERROR + +do_test 3.1.1 { + set rc [catch {rbu close} msg] + lappend rc $msg +} {1 {SQLITE_ERROR - cannot update wal mode database}} +db eval {PRAGMA journal_mode=DELETE} + +create_rbu rbu1.db +do_test 3.2.0 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=0 rbu1.db + rbu step +} SQLITE_OK + +do_test 3.3.1 { + set rc [catch {rbu close} msg] + lappend rc $msg +} {0 SQLITE_OK} + +db close +create_rbu rbu1.db +do_test 3.4.0 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=0 rbu1.db + rbu step +} SQLITE_OK +rbu close + + +finish_test diff --git a/ext/rbu/rbuvacuum2.test b/ext/rbu/rbuvacuum2.test index 6d6dfde9b..c8fba6a22 100644 --- a/ext/rbu/rbuvacuum2.test +++ b/ext/rbu/rbuvacuum2.test @@ -198,7 +198,11 @@ if {$::tcl_platform(platform)=="unix"} { } {SQLITE_OK} do_test 5.$tn.2 { file exists test.db-vacuum } 1 - do_test 5.$tn.3 { file attributes test.db-vacuum -permissions} $perm + # The result pattern might be 00xxx or 0oxxx depending on which + # version of TCL is being used. So make perm2 into a regexp that + # will match either + regsub {^00} $perm {0.} perm2 + do_test 5.$tn.3 { file attributes test.db-vacuum -permissions} /$perm2/ rbu close } } diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c index 1a13b6c4a..202712cca 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -110,6 +110,13 @@ # define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} #endif +/* +** Name of the URI option that causes RBU to take an exclusive lock as +** part of the incremental checkpoint operation. +*/ +#define RBU_EXCLUSIVE_CHECKPOINT "rbu_exclusive_checkpoint" + + /* ** The rbu_state table is used to save the state of a partially applied ** update so that it can be resumed later. The table consists of integer @@ -1194,7 +1201,9 @@ static void rbuTableType( assert( p->rc==SQLITE_OK ); p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[0], &p->zErrmsg, sqlite3_mprintf( - "SELECT (sql LIKE 'create virtual%%'), rootpage" + "SELECT " + " (sql COLLATE nocase BETWEEN 'CREATE VIRTUAL' AND 'CREATE VIRTUAM')," + " rootpage" " FROM sqlite_schema" " WHERE name=%Q", zTab )); @@ -2727,7 +2736,7 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ break; case RBU_STATE_OALSZ: - pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); + pRet->iOalSz = sqlite3_column_int64(pStmt, 1); break; case RBU_STATE_PHASEONESTEP: @@ -2754,13 +2763,19 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ /* ** Open the database handle and attach the RBU database as "rbu". If an ** error occurs, leave an error code and message in the RBU handle. +** +** If argument dbMain is not NULL, then it is a database handle already +** open on the target database. Use this handle instead of opening a new +** one. */ -static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ +static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){ assert( p->rc || (p->dbMain==0 && p->dbRbu==0) ); assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 ); + assert( dbMain==0 || rbuIsVacuum(p)==0 ); /* Open the RBU database */ p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1); + p->dbMain = dbMain; if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); @@ -3126,15 +3141,31 @@ static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){ /* -** Take an EXCLUSIVE lock on the database file. +** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if +** successful, or an SQLite error code otherwise. */ -static void rbuLockDatabase(sqlite3rbu *p){ - sqlite3_file *pReal = p->pTargetFd->pReal; - assert( p->rc==SQLITE_OK ); - p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_SHARED); - if( p->rc==SQLITE_OK ){ - p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_EXCLUSIVE); +static int rbuLockDatabase(sqlite3 *db){ + int rc = SQLITE_OK; + sqlite3_file *fd = 0; + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + + if( fd->pMethods ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); + if( rc==SQLITE_OK ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE); + } } + return rc; +} + +/* +** Return true if the database handle passed as the only argument +** was opened with the rbu_exclusive_checkpoint=1 URI parameter +** specified. Or false otherwise. +*/ +static int rbuExclusiveCheckpoint(sqlite3 *db){ + const char *zUri = sqlite3_db_filename(db, 0); + return sqlite3_uri_boolean(zUri, RBU_EXCLUSIVE_CHECKPOINT, 0); } #if defined(_WIN32_WCE) @@ -3192,18 +3223,24 @@ static void rbuMoveOalFile(sqlite3rbu *p){ ** In order to ensure that there are no database readers, an EXCLUSIVE ** lock is obtained here before the *-oal is moved to *-wal. */ - rbuLockDatabase(p); - if( p->rc==SQLITE_OK ){ - rbuFileSuffix3(zBase, zWal); - rbuFileSuffix3(zBase, zOal); + sqlite3 *dbMain = 0; + rbuFileSuffix3(zBase, zWal); + rbuFileSuffix3(zBase, zOal); + + /* Re-open the databases. */ + rbuObjIterFinalize(&p->objiter); + sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); + p->dbMain = 0; + p->dbRbu = 0; - /* Re-open the databases. */ - rbuObjIterFinalize(&p->objiter); - sqlite3_close(p->dbRbu); - sqlite3_close(p->dbMain); - p->dbMain = 0; - p->dbRbu = 0; + dbMain = rbuOpenDbhandle(p, p->zTarget, 1); + if( dbMain ){ + assert( p->rc==SQLITE_OK ); + p->rc = rbuLockDatabase(dbMain); + } + if( p->rc==SQLITE_OK ){ #if defined(_WIN32_WCE) { LPWSTR zWideOal; @@ -3230,11 +3267,19 @@ static void rbuMoveOalFile(sqlite3rbu *p){ #else p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK; #endif + } - if( p->rc==SQLITE_OK ){ - rbuOpenDatabase(p, 0); - rbuSetupCheckpoint(p, 0); - } + if( p->rc!=SQLITE_OK + || rbuIsVacuum(p) + || rbuExclusiveCheckpoint(dbMain)==0 + ){ + sqlite3_close(dbMain); + dbMain = 0; + } + + if( p->rc==SQLITE_OK ){ + rbuOpenDatabase(p, dbMain, 0); + rbuSetupCheckpoint(p, 0); } } @@ -3985,9 +4030,9 @@ static sqlite3rbu *openRbuHandle( ** If this is the case, it will have been checkpointed and deleted ** when the handle was closed and a second attempt to open the ** database may succeed. */ - rbuOpenDatabase(p, &bRetry); + rbuOpenDatabase(p, 0, &bRetry); if( bRetry ){ - rbuOpenDatabase(p, 0); + rbuOpenDatabase(p, 0, 0); } } @@ -4082,6 +4127,14 @@ static sqlite3rbu *openRbuHandle( }else if( p->eStage==RBU_STAGE_MOVE ){ /* no-op */ }else if( p->eStage==RBU_STAGE_CKPT ){ + if( !rbuIsVacuum(p) && rbuExclusiveCheckpoint(p->dbMain) ){ + /* If the rbu_exclusive_checkpoint=1 URI parameter was specified + ** and an incremental checkpoint is being resumed, attempt an + ** exclusive lock on the db file. If this fails, so be it. */ + p->eStage = RBU_STAGE_DONE; + rbuLockDatabase(p->dbMain); + p->eStage = RBU_STAGE_CKPT; + } rbuSetupCheckpoint(p, pState); }else if( p->eStage==RBU_STAGE_DONE ){ p->rc = SQLITE_DONE; @@ -4119,7 +4172,6 @@ sqlite3rbu *sqlite3rbu_open( const char *zState ){ if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); } - /* TODO: Check that zTarget and zRbu are non-NULL */ return openRbuHandle(zTarget, zRbu, zState); } diff --git a/ext/repair/checkfreelist.c b/ext/repair/checkfreelist.c index 990be4afa..d1d3d5407 100644 --- a/ext/repair/checkfreelist.c +++ b/ext/repair/checkfreelist.c @@ -44,8 +44,19 @@ SQLITE_EXTENSION_INIT1 # include # include # include -# define ALWAYS(X) 1 -# define NEVER(X) 0 +# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +# endif +# if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +# elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +# else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +# endif typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; diff --git a/ext/rtree/geopoly.c b/ext/rtree/geopoly.c index b7759bbb5..98ea718f5 100644 --- a/ext/rtree/geopoly.c +++ b/ext/rtree/geopoly.c @@ -305,13 +305,14 @@ static GeoPoly *geopolyFuncParam( ){ GeoPoly *p = 0; int nByte; + testcase( pCtx==0 ); if( sqlite3_value_type(pVal)==SQLITE_BLOB && (nByte = sqlite3_value_bytes(pVal))>=(4+6*sizeof(GeoCoord)) ){ const unsigned char *a = sqlite3_value_blob(pVal); int nVertex; if( a==0 ){ - sqlite3_result_error_nomem(pCtx); + if( pCtx ) sqlite3_result_error_nomem(pCtx); return 0; } nVertex = (a[1]<<16) + (a[2]<<8) + a[3]; @@ -1138,11 +1139,11 @@ static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){ }else{ /* Remove a segment */ if( pActive==pThisEvent->pSeg ){ - pActive = pActive->pNext; + pActive = ALWAYS(pActive) ? pActive->pNext : 0; }else{ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){ if( pSeg->pNext==pThisEvent->pSeg ){ - pSeg->pNext = pSeg->pNext->pNext; + pSeg->pNext = ALWAYS(pSeg->pNext) ? pSeg->pNext->pNext : 0; break; } } @@ -1386,6 +1387,7 @@ static int geopolyFilter( RtreeCoord bbox[4]; RtreeConstraint *p; assert( argc==1 ); + assert( argv[0]!=0 ); geopolyBBox(0, argv[0], bbox, &rc); if( rc ){ goto geopoly_filter_end; @@ -1613,6 +1615,7 @@ static int geopolyUpdate( || !sqlite3_value_nochange(aData[2]) /* UPDATE _shape */ || oldRowid!=newRowid) /* Rowid change */ ){ + assert( aData[2]!=0 ); geopolyBBox(0, aData[2], cell.aCoord, &rc); if( rc ){ if( rc==SQLITE_ERROR ){ diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 2151d5af0..49053a2bc 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -64,7 +64,11 @@ #endif int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */ -#ifndef SQLITE_AMALGAMATION +/* +** If building separately, we will need some setup that is normally +** found in sqliteInt.h +*/ +#if !defined(SQLITE_AMALGAMATION) #include "sqlite3rtree.h" typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; @@ -77,7 +81,20 @@ typedef unsigned int u32; #if defined(NDEBUG) && defined(SQLITE_DEBUG) # undef NDEBUG #endif +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 #endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif +#endif /* !defined(SQLITE_AMALGAMATION) */ #include #include @@ -135,7 +152,9 @@ struct Rtree { u8 nBytesPerCell; /* Bytes consumed per cell */ u8 inWrTrans; /* True if inside write transaction */ u8 nAux; /* # of auxiliary columns in %_rowid */ +#ifdef SQLITE_ENABLE_GEOPOLY u8 nAuxNotNull; /* Number of initial not-null aux columns */ +#endif #ifdef SQLITE_DEBUG u8 bCorrupt; /* Shadow table corruption detected */ #endif @@ -417,7 +436,12 @@ struct RtreeMatchArg { ** it is not, make it a no-op. */ #ifndef SQLITE_AMALGAMATION -# define testcase(X) +# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) + unsigned int sqlite3RtreeTestcase = 0; +# define testcase(X) if( X ){ sqlite3RtreeTestcase += __LINE__; } +# else +# define testcase(X) +# endif #endif /* @@ -666,18 +690,6 @@ static void nodeBlobReset(Rtree *pRtree){ } } -/* -** Check to see if pNode is the same as pParent or any of the parents -** of pParent. -*/ -static int nodeInParentChain(const RtreeNode *pNode, const RtreeNode *pParent){ - do{ - if( pNode==pParent ) return 1; - pParent = pParent->pParent; - }while( pParent ); - return 0; -} - /* ** Obtain a reference to an r-tree node. */ @@ -694,14 +706,7 @@ static int nodeAcquire( ** increase its reference count and return it. */ if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){ - if( pParent && !pNode->pParent ){ - if( nodeInParentChain(pNode, pParent) ){ - RTREE_IS_CORRUPT(pRtree); - return SQLITE_CORRUPT_VTAB; - } - pParent->nRef++; - pNode->pParent = pParent; - }else if( pParent && pNode->pParent && pParent!=pNode->pParent ){ + if( pParent && pParent!=pNode->pParent ){ RTREE_IS_CORRUPT(pRtree); return SQLITE_CORRUPT_VTAB; } @@ -759,7 +764,7 @@ static int nodeAcquire( ** 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 && rc==SQLITE_OK && iNode==1 ){ + if( rc==SQLITE_OK && pNode && iNode==1 ){ pRtree->iDepth = readInt16(pNode->zData); if( pRtree->iDepth>RTREE_MAX_DEPTH ){ rc = SQLITE_CORRUPT_VTAB; @@ -1282,20 +1287,29 @@ static void rtreeNonleafConstraint( switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ case RTREE_FALSE: break; /* Never satisfied */ + case RTREE_EQ: + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the lower bound of the coordinate pair */ + if( p->u.rValue>=val ){ + pCellData += 4; + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the upper bound of the coordinate pair */ + if( p->u.rValue<=val ) return; + } + break; case RTREE_LE: case RTREE_LT: - case RTREE_EQ: RTREE_DECODE_COORD(eInt, pCellData, val); /* val now holds the lower bound of the coordinate pair */ if( p->u.rValue>=val ) return; - if( p->op!=RTREE_EQ ) break; /* RTREE_LE and RTREE_LT end here */ - /* Fall through for the RTREE_EQ case */ + break; - default: /* RTREE_GT or RTREE_GE, or fallthrough of RTREE_EQ */ + default: pCellData += 4; RTREE_DECODE_COORD(eInt, pCellData, val); /* val now holds the upper bound of the coordinate pair */ if( p->u.rValue<=val ) return; + break; } *peWithin = NOT_WITHIN; } @@ -1365,11 +1379,12 @@ static int nodeRowidIndex( */ static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){ RtreeNode *pParent = pNode->pParent; - if( pParent ){ + if( ALWAYS(pParent) ){ return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex); + }else{ + *piIndex = -1; + return SQLITE_OK; } - *piIndex = -1; - return SQLITE_OK; } /* @@ -1492,7 +1507,8 @@ static RtreeSearchPoint *rtreeSearchPointNew( pNew = rtreeEnqueue(pCur, rScore, iLevel); if( pNew==0 ) return 0; ii = (int)(pNew - pCur->aPoint) + 1; - if( iiaNode[ii]==0 ); pCur->aNode[ii] = pCur->aNode[0]; }else{ @@ -1553,7 +1569,7 @@ static void rtreeSearchPointPop(RtreeCursor *p){ if( p->bPoint ){ p->anQueue[p->sPoint.iLevel]--; p->bPoint = 0; - }else if( p->nPoint ){ + }else if( ALWAYS(p->nPoint) ){ p->anQueue[p->aPoint[0].iLevel]--; n = --p->nPoint; p->aPoint[0] = p->aPoint[n]; @@ -1694,7 +1710,7 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); int rc = SQLITE_OK; RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); - if( rc==SQLITE_OK && p ){ + if( rc==SQLITE_OK && ALWAYS(p) ){ *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); } return rc; @@ -1712,7 +1728,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); if( rc ) return rc; - if( p==0 ) return SQLITE_OK; + if( NEVER(p==0) ) return SQLITE_OK; if( i==0 ){ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else if( i<=pRtree->nDim2 ){ @@ -1911,8 +1927,11 @@ static int rtreeFilter( } if( rc==SQLITE_OK ){ RtreeSearchPoint *pNew; + assert( pCsr->bPoint==0 ); /* Due to the resetCursor() call above */ pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1)); - if( pNew==0 ) return SQLITE_NOMEM; + if( NEVER(pNew==0) ){ /* Because pCsr->bPoint was FALSE */ + return SQLITE_NOMEM; + } pNew->id = 1; pNew->iCell = 0; pNew->eWithin = PARTLY_WITHIN; @@ -1989,7 +2008,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; if( bMatch==0 && p->usable - && p->iColumn==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ + && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ /* We have an equality constraint on the rowid. Use strategy 1. */ int jj; @@ -2195,7 +2214,7 @@ static int ChooseLeaf( int nCell = NCELL(pNode); RtreeCell cell; - RtreeNode *pChild; + RtreeNode *pChild = 0; RtreeCell *aCell = 0; @@ -2242,12 +2261,19 @@ static int AdjustTree( ){ RtreeNode *p = pNode; int cnt = 0; + int rc; while( p->pParent ){ RtreeNode *pParent = p->pParent; RtreeCell cell; int iCell; - if( (++cnt)>1000 || nodeParentIndex(pRtree, p, &iCell) ){ + cnt++; + if( NEVER(cnt>100) ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + rc = nodeParentIndex(pRtree, p, &iCell); + if( NEVER(rc!=SQLITE_OK) ){ RTREE_IS_CORRUPT(pRtree); return SQLITE_CORRUPT_VTAB; } @@ -2536,12 +2562,17 @@ static int updateMapping( xSetMapping = ((iHeight==0)?rowidWrite:parentWrite); if( iHeight>0 ){ RtreeNode *pChild = nodeHashLookup(pRtree, iRowid); + RtreeNode *p; + for(p=pNode; p; p=p->pParent){ + if( p==pChild ) return SQLITE_CORRUPT_VTAB; + } if( pChild ){ nodeRelease(pRtree, pChild->pParent); nodeReference(pNode); pChild->pParent = pNode; } } + if( NEVER(pNode==0) ) return SQLITE_ERROR; return xSetMapping(pRtree, iRowid, pNode->iNode); } @@ -2631,11 +2662,12 @@ static int SplitNode( RtreeNode *pParent = pLeft->pParent; int iCell; rc = nodeParentIndex(pRtree, pLeft, &iCell); - if( rc==SQLITE_OK ){ + if( ALWAYS(rc==SQLITE_OK) ){ nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell); rc = AdjustTree(pRtree, pParent, &leftbbox); + assert( rc==SQLITE_OK ); } - if( rc!=SQLITE_OK ){ + if( NEVER(rc!=SQLITE_OK) ){ goto splitnode_out; } } @@ -2710,7 +2742,7 @@ static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){ */ iNode = sqlite3_column_int64(pRtree->pReadParent, 0); for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent); - if( !pTest ){ + if( pTest==0 ){ rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent); } } @@ -2741,6 +2773,7 @@ static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ pParent = pNode->pParent; pNode->pParent = 0; rc = deleteCell(pRtree, pParent, iCell, iHeight+1); + testcase( rc!=SQLITE_OK ); } rc2 = nodeRelease(pRtree, pParent); if( rc==SQLITE_OK ){ @@ -2963,7 +2996,7 @@ static int rtreeInsertCell( } }else{ rc = AdjustTree(pRtree, pNode, pCell); - if( rc==SQLITE_OK ){ + if( ALWAYS(rc==SQLITE_OK) ){ if( iHeight==0 ){ rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode); }else{ @@ -3069,7 +3102,7 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ int rc2; RtreeNode *pChild = 0; i64 iChild = nodeGetRowid(pRtree, pRoot, 0); - rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); + rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); /* tag-20210916a */ if( rc==SQLITE_OK ){ rc = removeNode(pRtree, pChild, pRtree->iDepth-1); } @@ -3404,7 +3437,7 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ char *zSql; sqlite3_stmt *p; int rc; - i64 nRow = 0; + i64 nRow = RTREE_MIN_ROWEST; rc = sqlite3_table_column_metadata( db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0 @@ -3421,20 +3454,10 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ if( rc==SQLITE_OK ){ if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0); rc = sqlite3_finalize(p); - }else if( rc!=SQLITE_NOMEM ){ - rc = SQLITE_OK; - } - - if( rc==SQLITE_OK ){ - if( nRow==0 ){ - pRtree->nRowEst = RTREE_DEFAULT_ROWEST; - }else{ - pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); - } } sqlite3_free(zSql); } - + pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); return rc; } @@ -3584,9 +3607,12 @@ static int rtreeSqlInit( sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix); for(ii=0; iinAux; ii++){ if( ii ) sqlite3_str_append(p, ",", 1); +#ifdef SQLITE_ENABLE_GEOPOLY if( iinAuxNotNull ){ sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii); - }else{ + }else +#endif + { sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2); } } @@ -3851,6 +3877,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ tree.nDim2 = tree.nDim*2; tree.nBytesPerCell = 8 + 8 * tree.nDim; node.zData = (u8 *)sqlite3_value_blob(apArg[1]); + if( node.zData==0 ) return; nData = sqlite3_value_bytes(apArg[1]); if( nData<4 ) return; if( nDataxGeom = 0; pGeomCtx->xQueryFunc = xQueryFunc; pGeomCtx->xDestructor = xDestructor; diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index c37a2a77d..0423bf557 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -57,9 +57,12 @@ ifcapable !rtree { do_test rtree-1.1.1 { execsql { CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2, y1, y2) } } {} -do_test rtree-1.1.2 { +do_test rtree-1.1.2a { execsql { SELECT name FROM sqlite_master ORDER BY name } } {t1 t1_node t1_parent t1_rowid} +do_execsql_test rtree-1.1.2b { + SELECT name FROM pragma_table_list WHERE type='shadow' ORDER BY name; +} {t1_node t1_parent t1_rowid} do_test rtree-1.1.3 { execsql { DROP TABLE t1; diff --git a/ext/rtree/rtree9.test b/ext/rtree/rtree9.test index a7cd344d7..f39a82e58 100644 --- a/ext/rtree/rtree9.test +++ b/ext/rtree/rtree9.test @@ -36,7 +36,7 @@ do_execsql_test rtree9-1.4 { DELETE FROM rt; } {} - +unset -nocomplain x for {set i 0} {$i < 1000} {incr i} { set x [expr $i%10] set y [expr ($i/10)%10] diff --git a/ext/rtree/rtreeA.test b/ext/rtree/rtreeA.test index 8344863df..921ba0b51 100644 --- a/ext/rtree/rtreeA.test +++ b/ext/rtree/rtreeA.test @@ -145,7 +145,7 @@ 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" + 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" } @@ -160,7 +160,7 @@ 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" + 2 "SELECT * FROM t1 WHERE +rowid=5" 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" } @@ -171,7 +171,7 @@ do_execsql_test rtreeA-3.1.0.3 { 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" + 2 "SELECT * FROM t1 WHERE +rowid=5" 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" } @@ -183,7 +183,7 @@ do_test rtreeA-3.3.0 { } {65535} do_corruption_tests rtreeA-3.3 { 1 "SELECT * FROM t1" - 2 "SELECT * FROM t1 WHERE rowid=5" + 2 "SELECT * FROM t1 WHERE +rowid=5" 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" } @@ -203,7 +203,7 @@ do_test rtreeA-4.1.0 { } {4000} do_corruption_tests rtreeA-4.1 { 1 "SELECT * FROM t1" - 2 "SELECT * FROM t1 WHERE rowid=5" + 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" } @@ -216,7 +216,7 @@ 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" + 1 "DELETE FROM t1 WHERE +rowid = 5" 2 "DELETE FROM t1" } diff --git a/ext/rtree/rtreedoc.test b/ext/rtree/rtreedoc.test new file mode 100644 index 000000000..b64faa2e9 --- /dev/null +++ b/ext/rtree/rtreedoc.test @@ -0,0 +1,1583 @@ +# 2021 September 13 +# +# 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 focus of this file is testing the r-tree extension. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] rtree_util.tcl] +source $testdir/tester.tcl +set testprefix rtreedoc + +ifcapable !rtree { + finish_test + return +} + +# This command returns the number of columns in table $tbl within the +# database opened by database handle $db +proc column_count {db tbl} { + set nCol 0 + $db eval "PRAGMA table_info = $tbl" { incr nCol } + return $nCol +} + +proc column_name_list {db tbl} { + set lCol [list] + $db eval "PRAGMA table_info = $tbl" { + lappend lCol $name + } + return $lCol +} +unset -nocomplain res + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 3 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-1 + +# EVIDENCE-OF: R-15060-13876 A 1-dimensional R*Tree thus has 3 columns. +do_execsql_test 1.1.1 { CREATE VIRTUAL TABLE rt1 USING rtree(id, x1,x2) } +do_test 1.1.2 { column_count db rt1 } 3 + +# EVIDENCE-OF: R-19353-19546 A 2-dimensional R*Tree has 5 columns. +do_execsql_test 1.2.1 { CREATE VIRTUAL TABLE rt2 USING rtree(id,x1,x2, y1,y2) } +do_test 1.2.2 { column_count db rt2 } 5 + +# EVIDENCE-OF: R-13615-19528 A 3-dimensional R*Tree has 7 columns. +do_execsql_test 1.3.1 { + CREATE VIRTUAL TABLE rt3 USING rtree(id, x1,x2, y1,y2, z1,z2) +} +do_test 1.3.2 { column_count db rt3 } 7 + +# EVIDENCE-OF: R-53479-41922 A 4-dimensional R*Tree has 9 columns. +do_execsql_test 1.4.1 { + CREATE VIRTUAL TABLE rt4 USING rtree(id, x1,x2, y1,y2, z1,z2, v1,v2) +} +do_test 1.4.2 { column_count db rt4 } 9 + +# EVIDENCE-OF: R-13981-28768 And a 5-dimensional R*Tree has 11 columns. +do_execsql_test 1.5.1 { + CREATE VIRTUAL TABLE rt5 USING rtree(id, x1,x2, y1,y2, z1,z2, v1,v2, w1,w2) +} +do_test 1.5.2 { column_count db rt5 } 11 + + +# Attempt to create r-tree tables with 6 and 7 dimensions. +# +# EVIDENCE-OF: R-61533-25862 The SQLite R*Tree implementation does not +# support R*Trees wider than 5 dimensions. +do_catchsql_test 2.1.1 { + CREATE VIRTUAL TABLE rt6 USING rtree( + id, x1,x2, y1,y2, z1,z2, v1,v2, w1,w2, a1,a2 + ) +} {1 {Too many columns for an rtree table}} +do_catchsql_test 2.1.2 { + CREATE VIRTUAL TABLE rt6 USING rtree( + id, x1,x2, y1,y2, z1,z2, v1,v2, w1,w2, a1,a2, b1, b2 + ) +} {1 {Too many columns for an rtree table}} + +# Attempt to create r-tree tables with no columns, a single column, or +# an even number of columns. This and the tests above establish that: +# +# EVIDENCE-OF: R-16717-50504 Each R*Tree index is a virtual table with +# an odd number of columns between 3 and 11. +foreach {tn cols err} { + 1 "" "Too few columns for an rtree table" + 2 "x" "Too few columns for an rtree table" + 3 "x,y" "Too few columns for an rtree table" + 4 "a,b,c,d" "Wrong number of columns for an rtree table" + 5 "a,b,c,d,e,f" "Wrong number of columns for an rtree table" + 6 "a,b,c,d,e,f,g,h" "Wrong number of columns for an rtree table" + 7 "a,b,c,d,e,f,g,h,i,j" "Wrong number of columns for an rtree table" + 8 "a,b,c,d,e,f,g,h,i,j,k,l" "Too many columns for an rtree table" +} { + do_catchsql_test 3.$tn " + CREATE VIRTUAL TABLE xyz USING rtree($cols) + " [list 1 $err] +} + +# EVIDENCE-OF: R-17874-21123 The first column of an SQLite R*Tree is +# similar to an integer primary key column of a normal SQLite table. +# +# EVIDENCE-OF: R-46619-65417 The first column is always a 64-bit signed +# integer primary key. +# +# EVIDENCE-OF: R-46866-24036 It may only store a 64-bit signed integer +# value. +# +# EVIDENCE-OF: R-00250-64843 If an attempt is made to insert any other +# non-integer value into this column, the r-tree module silently +# converts it to an integer before writing it into the database. +# +do_execsql_test 4.0 { CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2) } +foreach {tn val res} { + 1 10 10 + 2 10.6 10 + 3 10.99 10 + 4 '123' 123 + 5 X'313233' 123 + 6 -10 -10 + 7 9223372036854775807 9223372036854775807 + 8 -9223372036854775808 -9223372036854775808 + 9 '9223372036854775807' 9223372036854775807 + 10 '-9223372036854775808' -9223372036854775808 + 11 'hello+world' 0 +} { + do_execsql_test 4.$tn.1 " + DELETE FROM rt; + INSERT INTO rt VALUES($val, 10, 20); + " + do_execsql_test 4.$tn.2 { + SELECT typeof(id), id FROM rt + } [list integer $res] +} + +# EVIDENCE-OF: R-15544-29079 Inserting a NULL value into this column +# causes SQLite to automatically generate a new unique primary key +# value. +do_execsql_test 5.1 { + DELETE FROM rt; + INSERT INTO rt VALUES(100, 1, 2); + INSERT INTO rt VALUES(NULL, 1, 2); +} +do_execsql_test 5.2 { SELECT id FROM rt } {100 101} +do_execsql_test 5.3 { + INSERT INTO rt VALUES(9223372036854775807, 1, 2); + INSERT INTO rt VALUES(NULL, 1, 2); +} +do_execsql_test 5.4 { + SELECT count(*) FROM rt; +} 4 +do_execsql_test 5.5 { + SELECT id IN(100, 101, 9223372036854775807) FROM rt ORDER BY 1; +} {0 1 1 1} + + +# EVIDENCE-OF: R-64317-38978 The other columns are pairs, one pair per +# dimension, containing the minimum and maximum values for that +# dimension, respectively. +# +# Show this by observing that attempts to insert rows with max>min fail. +# +do_execsql_test 6.1 { + CREATE VIRTUAL TABLE rtF USING rtree(id, x1,x2, y1,y2); + CREATE VIRTUAL TABLE rtI USING rtree_i32(id, x1,x2, y1,y2, z1,z2); +} +foreach {tn x1 x2 y1 y2 ok} { + 1 10.3 20.1 30.9 40.2 1 + 2 10.3 20.1 40.2 30.9 0 + 3 10.3 30.9 20.1 40.2 1 + 4 20.1 10.3 30.9 40.2 0 +} { + do_test 6.2.$tn { + catch { db eval { INSERT INTO rtF VALUES(NULL, $x1, $x2, $y1, $y2) } } + } [expr $ok==0] +} +foreach {tn x1 x2 y1 y2 z1 z2 ok} { + 1 10 20 30 40 50 60 1 + 2 10 20 30 40 60 50 0 + 3 10 20 30 50 40 60 1 + 4 10 20 40 30 50 60 0 + 5 10 30 20 40 50 60 1 + 6 20 10 30 40 50 60 0 +} { + do_test 6.3.$tn { + catch { db eval { INSERT INTO rtI VALUES(NULL,$x1,$x2,$y1,$y2,$z1,$z2) } } + } [expr $ok==0] +} + +# EVIDENCE-OF: R-08054-15429 The min/max-value pair columns are stored +# as 32-bit floating point values for "rtree" virtual tables or as +# 32-bit signed integers in "rtree_i32" virtual tables. +# +# Show this by showing that large values are rounded in ways consistent +# with those two 32-bit types. +do_execsql_test 7.1 { + DELETE FROM rtI; + INSERT INTO rtI VALUES( + 0, -2000000000, 2000000000, -5000000000, 5000000000, + -1000000000000, 10000000000000 + ); + SELECT * FROM rtI; +} { + 0 -2000000000 2000000000 -705032704 705032704 727379968 1316134912 +} +do_execsql_test 7.2 { + DELETE FROM rtF; + INSERT INTO rtF VALUES( + 0, -2000000000, 2000000000, + -1000000000000, 10000000000000 + ); + SELECT * FROM rtF; +} { + 0 -2000000000.0 2000000000.0 -1000000126976.0 10000000876544.0 +} + +# EVIDENCE-OF: R-47371-54529 Unlike regular SQLite tables which can +# store data in a variety of datatypes and formats, the R*Tree rigidly +# enforce these storage types. +# +# EVIDENCE-OF: R-39153-14977 If any other type of value is inserted into +# such a column, the r-tree module silently converts it to the required +# type before writing the new record to the database. +do_execsql_test 8.1 { + DELETE FROM rtI; + INSERT INTO rtI VALUES( + 1, 'hello world', X'616263', NULL, 44.5, 1000, 9999.9999 + ); + SELECT * FROM rtI; +} { + 1 0 0 0 44 1000 9999 +} + +do_execsql_test 8.2 { + SELECT + typeof(x1), typeof(x2), typeof(y1), typeof(y2), typeof(z1), typeof(z2) + FROM rtI +} {integer integer integer integer integer integer} + +do_execsql_test 8.3 { + DELETE FROM rtF; + INSERT INTO rtF VALUES( + 1, 'hello world', X'616263', NULL, 44 + ); + SELECT * FROM rtF; +} { + 1 0.0 0.0 0.0 44.0 +} +do_execsql_test 8.4 { + SELECT + typeof(x1), typeof(x2), typeof(y1), typeof(y2) + FROM rtF +} {real real real real} + + + + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 3.1 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-2 +reset_db + +foreach {tn name clist} { + 1 t1 "id x1 x2" + 2 t2 "id x1 x2 y1 y2 z1 z2" +} { +# EVIDENCE-OF: R-15142-18077 A new R*Tree index is created as follows: +# CREATE VIRTUAL TABLE USING rtree(); + do_execsql_test 1.$tn.1 " + CREATE VIRTUAL TABLE $name USING rtree([join $clist ,]) + " + +# EVIDENCE-OF: R-51698-09302 The is the name your +# application chooses for the R*Tree index and is a +# comma separated list of between 3 and 11 columns. + do_test 1.$tn.2 { column_name_list db $name } [list {*}$clist] + +# EVIDENCE-OF: R-50130-53472 The virtual table creates +# three shadow tables to actually store its content. + do_execsql_test 1.$tn.3 { + SELECT count(*) FROM sqlite_schema + } [expr 1+3] + +# EVIDENCE-OF: R-45256-35998 The names of these shadow tables are: +# _node _rowid _parent + do_execsql_test 1.$tn.4 { + SELECT name FROM sqlite_schema WHERE rootpage>0 ORDER BY 1 + } [list ${name}_node ${name}_parent ${name}_rowid] + + do_execsql_test 1.$tn.5 "DROP TABLE $name" +} + +# EVIDENCE-OF: R-11241-54478 As an example, consider creating a +# two-dimensional R*Tree index for use in spatial queries: CREATE +# VIRTUAL TABLE demo_index USING rtree( id, -- Integer primary key minX, +# maxX, -- Minimum and maximum X coordinate minY, maxY -- Minimum and +# maximum Y coordinate ); +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate + ); + INSERT INTO demo_index VALUES(1,2,3,4,5); + INSERT INTO demo_index VALUES(6,7,8,9,10); +} + +# EVIDENCE-OF: R-02287-33529 The shadow tables are ordinary SQLite data +# tables. +# +# Ordinary tables. With ordinary sqlite_schema entries. +do_execsql_test 2.1 { + SELECT type, name, sql FROM sqlite_schema WHERE sql NOT LIKE '%virtual%' +} { + table demo_index_rowid + {CREATE TABLE "demo_index_rowid"(rowid INTEGER PRIMARY KEY,nodeno)} + table demo_index_node + {CREATE TABLE "demo_index_node"(nodeno INTEGER PRIMARY KEY,data)} + table demo_index_parent + {CREATE TABLE "demo_index_parent"(nodeno INTEGER PRIMARY KEY,parentnode)} +} + +# EVIDENCE-OF: R-10863-13089 You can query them directly if you like, +# though this unlikely to reveal anything particularly useful. +# +# Querying: +do_execsql_test 2.2 { + SELECT count(*) FROM demo_index_node; + SELECT count(*) FROM demo_index_rowid; + SELECT count(*) FROM demo_index_parent; +} {1 2 0} + +# EVIDENCE-OF: R-05650-46070 And you can UPDATE, DELETE, INSERT or even +# DROP the shadow tables, though doing so will corrupt your R*Tree +# index. +do_execsql_test 2.3 { + DELETE FROM demo_index_rowid; + INSERT INTO demo_index_parent VALUES(2, 3); + UPDATE demo_index_node SET data = 'hello world' +} +do_catchsql_test 2.4 { + SELECT * FROM demo_index WHERE minX>10 AND maxX<30 +} {1 {database disk image is malformed}} +do_execsql_test 2.5 { + DROP TABLE demo_index_rowid +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 3.1.1 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-3 +reset_db + +# EVIDENCE-OF: R-44253-50720 In the argments to "rtree" in the CREATE +# VIRTUAL TABLE statement, the names of the columns are taken from the +# first token of each argument. All subsequent tokens within each +# argument are silently ignored. +# +foreach {tn cols lCol} { + 1 {(id TEXT, x1 TEXT, x2 TEXT, y1 TEXT, y2 TEXT)} {id x1 x2 y1 y2} + 2 {(id TEXT, x1 UNIQUE, x2 TEXT, y1 NOT NULL, y2 TEXT)} {id x1 x2 y1 y2} + 3 {(id, x1 DEFAULT 4, x2 TEXT, y1 NOT NULL, y2 TEXT)} {id x1 x2 y1 y2} +} { + do_execsql_test 1.$tn.1 " CREATE VIRTUAL TABLE abc USING rtree $cols " + do_test 1.$tn.2 { column_name_list db abc } $lCol + +# EVIDENCE-OF: R-52032-06717 This means, for example, that if you try to +# give a column a type affinity or add a constraint such as UNIQUE or +# NOT NULL or DEFAULT to a column, those extra tokens are accepted as +# valid, but they do not change the behavior of the rtree. + + # Show there are no UNIQUE constraints + do_execsql_test 1.$tn.3 { + INSERT INTO abc VALUES(1, 10.0, 20.0, 10.0, 20.0); + INSERT INTO abc VALUES(2, 10.0, 20.0, 10.0, 20.0); + } + + # Show the default values have not been modified + do_execsql_test 1.$tn.4 { + INSERT INTO abc DEFAULT VALUES; + SELECT * FROM abc WHERE rowid NOT IN (1,2) + } {3 0.0 0.0 0.0 0.0} + + # Show that there are no NOT NULL constraints + do_execsql_test 1.$tn.5 { + INSERT INTO abc VALUES(NULL, NULL, NULL, NULL, NULL); + SELECT * FROM abc WHERE rowid NOT IN (1,2,3) + } {4 0.0 0.0 0.0 0.0} + +# EVIDENCE-OF: R-06893-30579 In an RTREE virtual table, the first column +# always has a type affinity of INTEGER and all other data columns have +# a type affinity of REAL. + do_execsql_test 1.$tn.5 { + INSERT INTO abc VALUES('5', '5', '5', '5', '5'); + SELECT * FROM abc WHERE rowid NOT IN (1,2,3,4) + } {5 5.0 5.0 5.0 5.0} + do_execsql_test 1.$tn.6 { + SELECT type FROM pragma_table_info('abc') ORDER BY cid + } {INT REAL REAL REAL REAL} + + do_execsql_test 1.$tn.7 " CREATE VIRTUAL TABLE abc2 USING rtree_i32 $cols " + +# EVIDENCE-OF: R-06224-52418 In an RTREE_I32 virtual table, all columns +# have type affinity of INTEGER. + do_execsql_test 1.$tn.8 { + INSERT INTO abc2 VALUES('6.0', '6.0', '6.0', '6.0', '6.0'); + SELECT * FROM abc2 + } {6 6 6 6 6} + do_execsql_test 1.$tn.9 { + SELECT type FROM pragma_table_info('abc2') ORDER BY cid + } {INT INT INT INT INT} + + + do_execsql_test 1.$tn.10 { + DROP TABLE abc; + DROP TABLE abc2; + } +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 3.2 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-4 +reset_db + +# EVIDENCE-OF: R-36195-31555 The usual INSERT, UPDATE, and DELETE +# commands work on an R*Tree index just like on regular tables. +# +# Create a regular table and an rtree table. Perform INSERT, UPDATE and +# DELETE operations, then observe that the contents of the two tables +# are identical. +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2); + CREATE TABLE t1(id INTEGER PRIMARY KEY, x1 REAL, x2 REAL); +} +foreach {tn sql} { + 1 "INSERT INTO %TBL% VALUES(5, 11,12)" + 2 "INSERT INTO %TBL% VALUES(11, -11,14.5)" + 3 "UPDATE %TBL% SET x1=-99 WHERE id=11" + 4 "DELETE FROM %TBL% WHERE x2=14.5" + 5 "DELETE FROM %TBL%" +} { + set sql1 [string map {%TBL% rt} $sql] + set sql2 [string map {%TBL% t1} $sql] + do_execsql_test 1.$tn.0 $sql1 + do_execsql_test 1.$tn.1 $sql2 + + set data1 [execsql {SELECT * FROM rt ORDER BY 1}] + set data2 [execsql {SELECT * FROM t1 ORDER BY 1}] + + set res [expr {$data1==$data2}] + do_test 1.$tn.2 {set res} 1 +} + +# EVIDENCE-OF: R-56987-45305 +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate + ); + + INSERT INTO demo_index VALUES + (28215, -80.781227, -80.604706, 35.208813, 35.297367), + (28216, -80.957283, -80.840599, 35.235920, 35.367825), + (28217, -80.960869, -80.869431, 35.133682, 35.208233), + (28226, -80.878983, -80.778275, 35.060287, 35.154446), + (28227, -80.745544, -80.555382, 35.130215, 35.236916), + (28244, -80.844208, -80.841988, 35.223728, 35.225471), + (28262, -80.809074, -80.682938, 35.276207, 35.377747), + (28269, -80.851471, -80.735718, 35.272560, 35.407925), + (28270, -80.794983, -80.728966, 35.059872, 35.161823), + (28273, -80.994766, -80.875259, 35.074734, 35.172836), + (28277, -80.876793, -80.767586, 35.001709, 35.101063), + (28278, -81.058029, -80.956375, 35.044701, 35.223812), + (28280, -80.844208, -80.841972, 35.225468, 35.227203), + (28282, -80.846382, -80.844193, 35.223972, 35.225655); +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 3.3 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-5 + +do_execsql_test 1.0 { + INSERT INTO demo_index + SELECT NULL, minX, maxX, minY+0.2, maxY+0.2 FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX+0.2, maxX+0.2, minY, maxY FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX, maxX, minY+0.4, maxY+0.4 FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX+0.4, maxX+0.4, minY, maxY FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX, maxX, minY+0.8, maxY+0.8 FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX+0.8, maxX+0.8, minY, maxY FROM demo_index; + + SELECT count(*) FROM demo_index; +} {896} + +proc do_vmstep_test {tn sql expr} { + execsql $sql + set step [db status vmstep] + do_test $tn.$step "expr {[subst $expr]}" 1 +} + +# EVIDENCE-OF: R-45880-07724 Any valid query will work against an R*Tree +# index. +do_execsql_test 1.1.0 { + CREATE TABLE demo_tbl AS SELECT * FROM demo_index; +} +foreach {tn sql} { + 1 {SELECT * FROM %TBL% ORDER BY 1} + 2 {SELECT max(minX) FROM %TBL% ORDER BY 1} + 3 {SELECT max(minX) FROM %TBL% GROUP BY round(minY) ORDER BY 1} +} { + set sql1 [string map {%TBL% demo_index} $sql] + set sql2 [string map {%TBL% demo_tbl} $sql] + + do_execsql_test 1.1.$tn $sql1 [execsql $sql2] +} + +# EVIDENCE-OF: R-60814-18273 The R*Tree implementation just makes some +# kinds of queries especially efficient. +# +# The second query is more efficient than the first. +do_vmstep_test 1.2.1 {SELECT * FROM demo_index WHERE +rowid=28269} {$step>2000} +do_vmstep_test 1.2.2 {SELECT * FROM demo_index WHERE rowid=28269} {$step<100} + +# EVIDENCE-OF: R-37800-50174 Queries against the primary key are +# efficient: SELECT * FROM demo_index WHERE id=28269; +do_vmstep_test 2.2 { SELECT * FROM demo_index WHERE id=28269 } {$step < 100} + +# EVIDENCE-OF: R-35847-18866 The big reason for using an R*Tree is so +# that you can efficiently do range queries against the coordinate +# ranges. +# +# EVIDENCE-OF: R-49927-54202 +do_vmstep_test 2.3 { + SELECT id FROM demo_index + WHERE minX<=-80.77470 AND maxX>=-80.77470 + AND minY<=35.37785 AND maxY>=35.37785; +} {$step < 100} + +# EVIDENCE-OF: R-12823-37176 The query above will quickly locate all +# zipcodes that contain the SQLite main office in their bounding box, +# even if the R*Tree contains many entries. +# +do_execsql_test 2.4 { + SELECT id FROM demo_index + WHERE minX<=-80.77470 AND maxX>=-80.77470 + AND minY<=35.37785 AND maxY>=35.37785; +} { + 28322 28269 +} + +# EVIDENCE-OF: R-07351-00257 For example, to find all zipcode bounding +# boxes that overlap with the 28269 zipcode: SELECT A.id FROM demo_index +# AS A, demo_index AS B WHERE A.maxX>=B.minX AND A.minX<=B.maxX +# AND A.maxY>=B.minY AND A.minY<=B.maxY AND B.id=28269; +# +# Also check that it is efficient +# +# EVIDENCE-OF: R-39094-01937 This second query will find both 28269 +# entry (since every bounding box overlaps with itself) and also other +# zipcode that is close enough to 28269 that their bounding boxes +# overlap. +# +# 28269 is there in the result. +# +do_vmstep_test 2.5.1 { + SELECT A.id FROM demo_index AS A, demo_index AS B + WHERE A.maxX>=B.minX AND A.minX<=B.maxX + AND A.maxY>=B.minY AND A.minY<=B.maxY + AND B.id=28269 +} {$step < 100} +do_execsql_test 2.5.2 { + SELECT A.id FROM demo_index AS A, demo_index AS B + WHERE A.maxX>=B.minX AND A.minX<=B.maxX + AND A.maxY>=B.minY AND A.minY<=B.maxY + AND B.id=28269; +} { + 28293 28216 28322 28286 28269 + 28215 28336 28262 28291 28320 + 28313 28298 28287 +} + +# EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all +# coordinates in an R*Tree index to be constrained in order for the +# index search to be efficient. +# +# EVIDENCE-OF: R-22490-27246 One might, for example, want to query all +# objects that overlap with the 35th parallel: SELECT id FROM demo_index +# WHERE maxY>=35.0 AND minY<=35.0; +do_vmstep_test 2.6.1 { + SELECT id FROM demo_index + WHERE maxY>=35.0 AND minY<=35.0; +} {$step < 100} +do_execsql_test 2.6.2 { + SELECT id FROM demo_index + WHERE maxY>=35.0 AND minY<=35.0; +} {} + + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 3.4 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-6 +reset_db + +# EVIDENCE-OF: R-08327-00674 By default, coordinates are stored in an +# R*Tree using 32-bit floating point values. +# +# EVIDENCE-OF: R-22000-53613 The default virtual table ("rtree") stores +# coordinates as single-precision (4-byte) floating point numbers. +# +# Show this by showing that rounding is consistent with 32-bit float +# rounding. +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE rt USING rtree(id, a,b); +} +do_execsql_test 1.1 { + INSERT INTO rt VALUES(14, -1000000000000, 1000000000000); + SELECT * FROM rt; +} {14 -1000000126976.0 1000000126976.0} + +# EVIDENCE-OF: R-39127-51288 When a coordinate cannot be exactly +# represented by a 32-bit floating point number, the lower-bound +# coordinates are rounded down and the upper-bound coordinates are +# rounded up. +foreach {tn val} { + 1 100000000000 + 2 200000000000 + 3 300000000000 + 4 400000000000 + + 5 -100000000000 + 6 -200000000000 + 7 -300000000000 + 8 -400000000000 +} { + set val [expr $val] + do_execsql_test 2.$tn.0 {DELETE FROM rt} + do_execsql_test 2.$tn.1 {INSERT INTO rt VALUES(23, $val, $val)} + do_execsql_test 2.$tn.2 { + SELECT $val>=a, $val<=b, a!=b FROM rt + } {1 1 1} +} + +do_execsql_test 3.0 { + DROP TABLE rt; + CREATE VIRTUAL TABLE rt USING rtree(id, x1,x2, y1,y2); +} + +# EVIDENCE-OF: R-45870-62834 Thus, bounding boxes might be slightly +# larger than specified, but will never be any smaller. +foreach {tn x1 x2 y1 y2} { + 1 100000000000 200000000000 300000000000 400000000000 +} { + set val [expr $val] + do_execsql_test 3.$tn.0 {DELETE FROM rt} + do_execsql_test 3.$tn.1 {INSERT INTO rt VALUES(23, $x1, $x2, $y1, $y2)} + do_execsql_test 3.$tn.2 { + SELECT (x2-x1)*(y2-y1) >= ($x2-$x1)*($y2-$y1) FROM rt + } {1} +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 3.5 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-7 +reset_db + +# EVIDENCE-OF: R-55979-39402 It is the nature of the Guttman R-Tree +# algorithm that any write might radically restructure the tree, and in +# the process change the scan order of the nodes. +# +# In the test below, the INSERT marked "THIS INSERT!!" does not affect +# the results of queries with an ORDER BY, but does affect the results +# of one without an ORDER BY. Therefore the INSERT changed the scan +# order. +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE rt USING rtree(id, minX, maxX); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<51 + ) + INSERT INTO rt SELECT NULL, i%10, (i%10)+5 FROM s +} +do_execsql_test 1.1 { SELECT count(*) FROM rt_node } 1 +do_test 1.2 { + set res1 [db eval {SELECT * FROM rt WHERE maxX < 30}] + set res1o [db eval {SELECT * FROM rt WHERE maxX < 30 ORDER BY +id}] + + db eval { INSERT INTO rt VALUES(NULL, 50, 50) } ;# THIS INSERT!! + + set res2 [db eval {SELECT * FROM rt WHERE maxX < 30}] + set res2o [db eval {SELECT * FROM rt WHERE maxX < 30 ORDER BY +id}] + list [expr {$res1==$res2}] [expr {$res1o==$res2o}] +} {0 1} + +do_execsql_test 1.3 { SELECT count(*) FROM rt_node } 3 + +# EVIDENCE-OF: R-00683-48865 For this reason, it is not generally +# possible to modify the R-Tree in the middle of a query of the R-Tree. +# Attempts to do so will fail with a SQLITE_LOCKED "database table is +# locked" error. +# +# SQLITE_LOCKED==6 +# +do_test 1.4 { + set nCnt 3 + db eval { SELECT * FROM rt WHERE minX>0 AND maxX<12 } { + incr nCnt -1 + if {$nCnt==0} { + set rc [catch {db eval { + INSERT INTO rt VALUES(NULL, 51, 51); + }} msg] + set errorcode [db errorcode] + break + } + } + + list $errorcode $rc $msg +} {6 1 {database table is locked}} + +# EVIDENCE-OF: R-19740-29710 So, for example, suppose an application +# runs one query against an R-Tree like this: SELECT id FROM demo_index +# WHERE maxY>=35.0 AND minY<=35.0; Then for each "id" value +# returned, suppose the application creates an UPDATE statement like the +# following and binds the "id" value returned against the "?1" +# parameter: UPDATE demo_index SET maxY=maxY+0.5 WHERE id=?1; +# +# EVIDENCE-OF: R-52919-32711 Then the UPDATE might fail with an +# SQLITE_LOCKED error. +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate + ); + INSERT INTO demo_index VALUES + (28215, -80.781227, -80.604706, 35.208813, 35.297367), + (28216, -80.957283, -80.840599, 35.235920, 35.367825), + (28217, -80.960869, -80.869431, 35.133682, 35.208233), + (28226, -80.878983, -80.778275, 35.060287, 35.154446); +} +do_test 2.1 { + db eval { SELECT id FROM demo_index WHERE maxY>=35.0 AND minY<=35.0 } { + set rc [catch { + db eval { UPDATE demo_index SET maxY=maxY+0.5 WHERE id=$id } + } msg] + set errorcode [db errorcode] + break + } + list $errorcode $rc $msg +} {6 1 {database table is locked}} + +# EVIDENCE-OF: R-32604-49843 Ordinary tables in SQLite are able to read +# and write at the same time. +# +do_execsql_test 3.0 { + CREATE TABLE x1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO x1 VALUES(1, 1, 1); + INSERT INTO x1 VALUES(2, 2, 2); + INSERT INTO x1 VALUES(3, 3, 3); + INSERT INTO x1 VALUES(4, 4, 4); +} +do_test 3.1 { + unset -nocomplain res + set res [list] + db eval { SELECT * FROM x1 } { + lappend res $a $b $c + switch -- $a { + 1 { + db eval { INSERT INTO x1 VALUES(5, 5, 5) } + } + 2 { + db eval { UPDATE x1 SET c=20 WHERE a=2 } + } + 3 { + db eval { DELETE FROM x1 WHERE c IN (3,4) } + } + } + } + set res +} {1 1 1 2 2 2 3 3 3 5 5 5} +do_execsql_test 3.2 { + SELECT * FROM x1 +} {1 1 1 2 2 20 5 5 5} + +# EVIDENCE-OF: R-06177-00576 And R-Tree can appear to read and write at +# the same time in some circumstances, if it can figure out how to +# reliably run the query to completion before starting the update. +# +# In 8.2, it can, it 8.1, it cannot. +do_test 8.1 { + db eval { SELECT * FROM rt } { + set rc [catch { db eval { INSERT INTO rt VALUES(53,53,53) } } msg] + break; + } + list $rc $msg +} {1 {database table is locked}} +do_test 8.2 { + db eval { SELECT * FROM rt ORDER BY +id } { + set rc [catch { db eval { INSERT INTO rt VALUES(53,53,53) } } msg] + break + } + list $rc $msg +} {0 {}} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 4 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-8 +reset_db + +# EVIDENCE-OF: R-21062-30088 For the example above, one might create an +# auxiliary table as follows: CREATE TABLE demo_data( id INTEGER PRIMARY +# KEY, -- primary key objname TEXT, -- name of the object objtype TEXT, +# -- object type boundary BLOB -- detailed boundary of object ); +# +# One might. +# +do_execsql_test 1.0 { + CREATE TABLE demo_data( + id INTEGER PRIMARY KEY, -- primary key + objname TEXT, -- name of the object + objtype TEXT, -- object type + boundary BLOB -- detailed boundary of object + ); +} + +do_execsql_test 1.1 { + CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate + ); + + INSERT INTO demo_index VALUES + (28215, -80.781227, -80.604706, 35.208813, 35.297367), + (28216, -80.957283, -80.840599, 35.235920, 35.367825), + (28217, -80.960869, -80.869431, 35.133682, 35.208233), + (28226, -80.878983, -80.778275, 35.060287, 35.154446), + (28227, -80.745544, -80.555382, 35.130215, 35.236916), + (28244, -80.844208, -80.841988, 35.223728, 35.225471), + (28262, -80.809074, -80.682938, 35.276207, 35.377747), + (28269, -80.851471, -80.735718, 35.272560, 35.407925), + (28270, -80.794983, -80.728966, 35.059872, 35.161823), + (28273, -80.994766, -80.875259, 35.074734, 35.172836), + (28277, -80.876793, -80.767586, 35.001709, 35.101063), + (28278, -81.058029, -80.956375, 35.044701, 35.223812), + (28280, -80.844208, -80.841972, 35.225468, 35.227203), + (28282, -80.846382, -80.844193, 35.223972, 35.225655); + + INSERT INTO demo_index + SELECT NULL, minX, maxX, minY+0.2, maxY+0.2 FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX+0.2, maxX+0.2, minY, maxY FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX, maxX, minY+0.4, maxY+0.4 FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX+0.4, maxX+0.4, minY, maxY FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX, maxX, minY+0.8, maxY+0.8 FROM demo_index; + INSERT INTO demo_index + SELECT NULL, minX+0.8, maxX+0.8, minY, maxY FROM demo_index; + + INSERT INTO demo_data(id) SELECT id FROM demo_index; + + SELECT count(*) FROM demo_index; +} {896} + +set ::contained_in 0 +proc contained_in {args} {incr ::contained_in ; return 0} +db func contained_in contained_in + +# EVIDENCE-OF: R-32671-43888 Then an efficient way to find the specific +# ZIP code for the main SQLite office would be to run a query like this: +# SELECT objname FROM demo_data, demo_index WHERE +# demo_data.id=demo_index.id AND contained_in(demo_data.boundary, +# 35.37785, -80.77470) AND minX<=-80.77470 AND maxX>=-80.77470 AND +# minY<=35.37785 AND maxY>=35.37785; +do_vmstep_test 1.2 { + SELECT objname FROM demo_data, demo_index + WHERE demo_data.id=demo_index.id + AND contained_in(demo_data.boundary, 35.37785, -80.77470) + AND minX<=-80.77470 AND maxX>=-80.77470 + AND minY<=35.37785 AND maxY>=35.37785; +} {$step<100} +set ::contained_in1 $::contained_in + +# EVIDENCE-OF: R-32761-23915 One would get the same answer without the +# use of the R*Tree index using the following simpler query: SELECT +# objname FROM demo_data WHERE contained_in(demo_data.boundary, +# 35.37785, -80.77470); +set ::contained_in 0 +do_vmstep_test 1.3 { + SELECT objname FROM demo_data + WHERE contained_in(demo_data.boundary, 35.37785, -80.77470); +} {$step>3200} + +# EVIDENCE-OF: R-40261-32799 The problem with this latter query is that +# it must apply the contained_in() function to all entries in the +# demo_data table. +# +# 896 of them, IIRC. +do_test 1.4 { + set ::contained_in +} 896 + +# EVIDENCE-OF: R-24212-52761 The use of the R*Tree in the penultimate +# query reduces the number of calls to contained_in() function to a +# small subset of the entire table. +# +# 2 is a small subset of 896. +# +# EVIDENCE-OF: R-39057-63901 The R*Tree index did not find the exact +# answer itself, it merely limited the search space. +# +# contained_in() filtered out those 2 rows. +do_test 1.5 { + set ::contained_in1 +} {2} + + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 4.1 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-9 +reset_db + +# EVIDENCE-OF: R-46566-43213 Beginning with SQLite version 3.24.0 +# (2018-06-04), r-tree tables can have auxiliary columns that store +# arbitrary data. Auxiliary columns can be used in place of secondary +# tables such as "demo_data". +# +# EVIDENCE-OF: R-41287-48160 Auxiliary columns are marked with a "+" +# symbol before the column name. +# +# This interface cannot conveniently be used to prove anything about +# versions of SQLite prior to 3.24.0. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE rta USING rtree( + id, u1,u2, v1,v2, +aux + ); + + INSERT INTO rta(aux) VALUES(NULL); + INSERT INTO rta(aux) VALUES(45); + INSERT INTO rta(aux) VALUES(22.3); + INSERT INTO rta(aux) VALUES('hello'); + INSERT INTO rta(aux) VALUES(X'ABCD'); + + SELECT typeof(aux), quote(aux) FROM rta; +} { + null NULL + integer 45 + real 22.3 + text 'hello' + blob X'ABCD' +} + +# EVIDENCE-OF: R-30514-26093 Auxiliary columns must come after all of +# the coordinate boundary columns. +foreach {tn cols} { + 1 "id x1,x2, +extra, y1,y2" + 2 "extra, +id x1,x2, y1,y2" + 3 "id, x1,+x2, extra, y1,y2" +} { + do_catchsql_test 2.$tn " + CREATE VIRTUAL TABLE rrr USING rtree($cols) + " {1 {Auxiliary rtree columns must be last}} +} +do_catchsql_test 3.0 { + CREATE VIRTUAL TABLE rrr USING rtree(+id, extra, x1, x2); +} {1 {near "+": syntax error}} + +# EVIDENCE-OF: R-01280-03635 An RTREE table can have no more than 100 +# columns total. In other words, the count of columns including the +# integer primary key column, the coordinate boundary columns, and all +# auxiliary columns must be 100 or less. +do_catchsql_test 3.1 { + CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2, + +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09, + +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19, + +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29, + +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39, + +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49, + +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59, + +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69, + +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79, + +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89, + +c90, +c91, +c92, +c93, +c94, +c95, +c96 + ); +} {0 {}} +do_catchsql_test 3.2 { + DROP TABLE r1; + CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2, + +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09, + +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19, + +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29, + +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39, + +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49, + +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59, + +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69, + +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79, + +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89, + +c90, +c91, +c92, +c93, +c94, +c95, +c96, +c97 + ); +} {1 {Too many columns for an rtree table}} +do_catchsql_test 3.3 { + CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2, v1,v2, + +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09, + +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19, + +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29, + +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39, + +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49, + +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59, + +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69, + +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79, + +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89, + +c90, +c91, +c92, +c93, +c94, + ); +} {0 {}} +do_catchsql_test 3.4 { + DROP TABLE r1; + CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2, v1,v2, + +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09, + +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19, + +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29, + +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39, + +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49, + +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59, + +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69, + +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79, + +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89, + +c90, +c91, +c92, +c93, +c94, +c95, + ); +} {1 {Too many columns for an rtree table}} + +# EVIDENCE-OF: R-05552-15084 +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE demo_index2 USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY, -- Minimum and maximum Y coordinate + +objname TEXT, -- name of the object + +objtype TEXT, -- object type + +boundary BLOB -- detailed boundary of object + ); +} +do_execsql_test 4.1 { + CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate + ); + CREATE TABLE demo_data( + id INTEGER PRIMARY KEY, -- primary key + objname TEXT, -- name of the object + objtype TEXT, -- object type + boundary BLOB -- detailed boundary of object + ); + + INSERT INTO demo_index2(id) VALUES(1); + INSERT INTO demo_index(id) VALUES(1); + INSERT INTO demo_data(id) VALUES(1); +} +do_test 4.2 { + catch { array unset R } + db eval {SELECT * FROM demo_index2} R { set r1 [array names R] } + catch { array unset R } + db eval {SELECT * FROM demo_index NATURAL JOIN demo_data } R { + set r2 [array names R] + } + expr {$r1==$r2} +} {1} + +# EVIDENCE-OF: R-26099-32169 SELECT objname FROM demo_index2 WHERE +# contained_in(boundary, 35.37785, -80.77470) AND minX<=-80.77470 AND +# maxX>=-80.77470 AND minY<=35.37785 AND maxY>=35.37785; +do_execsql_test 4.3.1 { + DELETE FROM demo_index2; + INSERT INTO demo_index2(id,minX,maxX,minY,maxY) VALUES + (28215, -80.781227, -80.604706, 35.208813, 35.297367), + (28216, -80.957283, -80.840599, 35.235920, 35.367825), + (28217, -80.960869, -80.869431, 35.133682, 35.208233), + (28226, -80.878983, -80.778275, 35.060287, 35.154446), + (28227, -80.745544, -80.555382, 35.130215, 35.236916), + (28244, -80.844208, -80.841988, 35.223728, 35.225471), + (28262, -80.809074, -80.682938, 35.276207, 35.377747), + (28269, -80.851471, -80.735718, 35.272560, 35.407925), + (28270, -80.794983, -80.728966, 35.059872, 35.161823), + (28273, -80.994766, -80.875259, 35.074734, 35.172836), + (28277, -80.876793, -80.767586, 35.001709, 35.101063), + (28278, -81.058029, -80.956375, 35.044701, 35.223812), + (28280, -80.844208, -80.841972, 35.225468, 35.227203), + (28282, -80.846382, -80.844193, 35.223972, 35.225655); +} +set ::contained_in 0 +proc contained_in {args} { + incr ::contained_in + return 0 +} +db func contained_in contained_in +do_execsql_test 4.3.2 { + SELECT objname FROM demo_index2 + WHERE contained_in(boundary, 35.37785, -80.77470) + AND minX<=-80.77470 AND maxX>=-80.77470 + AND minY<=35.37785 AND maxY>=35.37785; +} +do_test 4.3.3 { + # Function invoked only once because r-tree filtering happened first. + set ::contained_in +} 1 +set ::contained_in 0 +do_execsql_test 4.3.4 { + SELECT objname FROM demo_index2 + WHERE contained_in(boundary, 35.37785, -80.77470) +} +do_test 4.3.3 { + # Function invoked 14 times because no r-tree filtering. Inefficient. + set ::contained_in +} 14 + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 4.1.1 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-9 +reset_db + +# EVIDENCE-OF: R-24021-02490 For auxiliary columns, only the name of the +# column matters. The type affinity is ignored. +# +# EVIDENCE-OF: R-39906-44154 Constraints such as NOT NULL, UNIQUE, +# REFERENCES, or CHECK are also ignored. +do_execsql_test 1.0 { PRAGMA foreign_keys = on } +foreach {tn auxcol nm} { + 1 "+extra INTEGER" extra + 2 "+extra TEXT" extra + 3 "+extra BLOB" extra + 4 "+extra REAL" extra + + 5 "+col NOT NULL" col + 6 "+col CHECK (col IS NOT NULL)" col + 7 "+col REFERENCES tbl(x)" col +} { + do_execsql_test 1.$tn.1 " + CREATE VIRTUAL TABLE rt USING rtree_i32(k, a,b, $auxcol) + " + + # Check that the aux column has no affinity. Or NOT NULL constraint. + # And that the aux column is the child key of an FK constraint. + # + do_execsql_test 1.$tn.2 " + INSERT INTO rt($nm) VALUES(NULL), (45), (-123.2), ('456'), (X'ABCD'); + SELECT typeof($nm), quote($nm) FROM rt; + " { + null NULL + integer 45 + real -123.2 + text '456' + blob X'ABCD' + } + + # Check that there is no UNIQUE constraint either. + # + do_execsql_test 1.$tn.3 " + INSERT INTO rt($nm) VALUES('xyz'), ('xyz'), ('xyz'); + " + + do_execsql_test 1.$tn.2 { + DROP TABLE rt + } +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 5 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-10 + +# EVIDENCE-OF: R-21011-43790 If integer coordinates are desired, declare +# the table using "rtree_i32" instead: CREATE VIRTUAL TABLE intrtree +# USING rtree_i32(id,x0,x1,y0,y1,z0,z1); +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE intrtree USING rtree_i32(id,x0,x1,y0,y1,z0,z1); + INSERT INTO intrtree DEFAULT VALUES; + SELECT typeof(x0) FROM intrtree; +} {integer} + +# EVIDENCE-OF: R-09193-49806 An rtree_i32 stores coordinates as 32-bit +# signed integers. +# +# Show that coordinates are cast in a way consistent with casting to +# a signed 32-bit integer. +do_execsql_test 1.1 { + DELETE FROM intrtree; + INSERT INTO intrtree VALUES(333, + 1<<44, (1<<44)+1, + 10000000000, 10000000001, + -10000000001, -10000000000 + ); + SELECT * FROM intrtree; +} { + 333 0 1 1410065408 1410065409 -1410065409 -1410065408 +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 7.1 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-11 +reset_db + +# This command assumes that the argument is a node blob for a 2 dimensional +# i32 r-tree table. It decodes and returns a list of cells from the node +# as a list. Each cell is itself a list of the following form: +# +# {$rowid $minX $maxX $minY $maxY} +# +# For internal (non-leaf) nodes, the rowid is replaced by the child node +# number. +# +proc rnode {aData} { + set nDim 2 + + set nData [string length $aData] + set nBytePerCell [expr (8 + 2*$nDim*4)] + binary scan [string range $aData 2 3] S nCell + + set res [list] + for {set i 0} {$i < $nCell} {incr i} { + set iOff [expr $i*$nBytePerCell+4] + set cell [string range $aData $iOff [expr $iOff+$nBytePerCell-1]] + binary scan $cell WIIII rowid x1 x2 y1 y2 + lappend res [list $rowid $x1 $x2 $y1 $y2] + } + + return $res +} + +# aData must be a node blob. This command returns true if the node contains +# rowid $rowid, or false otherwise. +# +proc rnode_contains {aData rowid} { + set L [rnode $aData] + foreach cell $L { + set r [lindex $cell 0] + if {$r==$rowid} { return 1 } + } + return 0 +} + +proc rnode_replace_cell {aData iCell cell} { + set aCell [binary format WIIII {*}$cell] + set nDim 2 + set nBytePerCell [expr (8 + 2*$nDim*4)] + set iOff [expr $iCell*$nBytePerCell+4] + + set aNew [binary format a*a*a* \ + [string range $aData 0 $iOff-1] \ + $aCell \ + [string range $aData $iOff+$nBytePerCell end] \ + ] + return $aNew +} + +db function rnode rnode +db function rnode_contains rnode_contains +db function rnode_replace_cell rnode_replace_cell + +foreach {tn nm} { + 1 x1 + 2 asdfghjkl + 3 hello_world +} { + do_execsql_test 1.$tn.1 " + CREATE VIRTUAL TABLE $nm USING rtree(a,b,c,d,e); + " + + # EVIDENCE-OF: R-33789-46762 The content of an R*Tree index is actually + # stored in three ordinary SQLite tables with names derived from the + # name of the R*Tree. + # + # EVIDENCE-OF: R-39849-06566 This is their schema: CREATE TABLE + # %_node(nodeno INTEGER PRIMARY KEY, data) CREATE TABLE %_parent(nodeno + # INTEGER PRIMARY KEY, parentnode) CREATE TABLE %_rowid(rowid INTEGER + # PRIMARY KEY, nodeno) + # + # EVIDENCE-OF: R-07489-10051 The "%" in the name of each shadow table is + # replaced by the name of the R*Tree virtual table. So, if the name of + # the R*Tree table is "xyz" then the three shadow tables would be + # "xyz_node", "xyz_parent", and "xyz_rowid". + do_execsql_test 1.$tn.2 { + SELECT sql FROM sqlite_schema WHERE name!=$nm ORDER BY 1 + } [string map [list % $nm] " + {CREATE TABLE \"%_node\"(nodeno INTEGER PRIMARY KEY,data)} + {CREATE TABLE \"%_parent\"(nodeno INTEGER PRIMARY KEY,parentnode)} + {CREATE TABLE \"%_rowid\"(rowid INTEGER PRIMARY KEY,nodeno)} + "] + + do_execsql_test 1.$tn "DROP TABLE $nm" +} + + +# EVIDENCE-OF: R-51070-59303 There is one entry in the %_node table for +# each R*Tree node. +# +# The following creates a 6 node r-tree structure. +# +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE r1 USING rtree_i32(i, x1,x2, y1,y2); + WITH t(i) AS ( + VALUES(1) UNION SELECT i+1 FROM t WHERE i<110 + ) + INSERT INTO r1 SELECT i, (i%10), (i%10)+2, (i%6), (i%7)+6 FROM t; +} +do_execsql_test 2.1 { + SELECT count(*) FROM r1_node; +} 6 + +# EVIDENCE-OF: R-27261-09153 All nodes other than the root have an entry +# in the %_parent shadow table that identifies the parent node. +# +# In this case nodes 2-6 are the children of node 1. +# +do_execsql_test 2.3 { + SELECT nodeno, parentnode FROM r1_parent +} {2 1 3 1 4 1 5 1 6 1} + +# EVIDENCE-OF: R-02358-35037 The %_rowid shadow table maps entry rowids +# to the node that contains that entry. +# +do_execsql_test 2.4 { + SELECT 'failed' FROM r1_rowid WHERE 0==rnode_contains( + (SELECT data FROM r1_node WHERE nodeno=r1_rowid.nodeno), rowid + ) +} +do_test 2.5 { + db eval { SELECT nodeno, data FROM r1_node WHERE nodeno!=1 } { + set L [rnode $data] + foreach cell $L { + set rowid [lindex $cell 0] + set rowid_nodeno 0 + db eval {SELECT nodeno AS rowid_nodeno FROM r1_rowid WHERE rowid=$rowid} { + break + } + if {$rowid_nodeno!=$nodeno} { error "data mismatch!" } + } + } +} {} + +# EVIDENCE-OF: R-65201-22208 Extra columns appended to the %_rowid table +# hold the content of auxiliary columns. +# +# EVIDENCE-OF: R-44161-28345 The names of these extra %_rowid columns +# are probably not the same as the actual auxiliary column names. +# +# In this case, the auxiliary columns are named "e1" and "e2". The +# extra %_rowid columns are named "a0" and "a1". +# +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE rtaux USING rtree(id, x1,x2, y1,y2, +e1, +e2); + SELECT sql FROM sqlite_schema WHERE name='rtaux_rowid'; +} { + {CREATE TABLE "rtaux_rowid"(rowid INTEGER PRIMARY KEY,nodeno,a0,a1)} +} +do_execsql_test 3.1 { + INSERT INTO rtaux(e1, e2) VALUES('hello', 'world'), (123, 456); +} +do_execsql_test 3.2 { + SELECT a0, a1 FROM rtaux_rowid; +} { + hello world 123 456 +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 7.2 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc-12 +reset_db +forcedelete test.db2 + +db function rnode rnode +db function rnode_contains rnode_contains +db function rnode_replace_cell rnode_replace_cell + +# EVIDENCE-OF: R-13571-45795 The scalar SQL function rtreecheck(R) or +# rtreecheck(S,R) runs an integrity check on the rtree table named R +# contained within database S. +# +# EVIDENCE-OF: R-36011-59963 The function returns a human-language +# description of any problems found, or the string 'ok' if everything is +# ok. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE rt1 USING rtree(id, a, b); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<200 + ) + INSERT INTO rt1 SELECT i, i, i FROM s; + + ATTACH 'test.db2' AS 'aux'; + CREATE VIRTUAL TABLE aux.rt1 USING rtree(id, a, b); + INSERT INTO aux.rt1 SELECT * FROM rt1; +} + +do_execsql_test 1.1.1 { SELECT rtreecheck('rt1'); } {ok} +do_execsql_test 1.1.2 { SELECT rtreecheck('main', 'rt1'); } {ok} +do_execsql_test 1.1.3 { SELECT rtreecheck('aux', 'rt1'); } {ok} +do_catchsql_test 1.1.4 { + SELECT rtreecheck('nosuchdb', 'rt1'); +} {1 {SQL logic error}} + +# Corrupt the table in database 'main': +do_execsql_test 1.2.1 { UPDATE rt1_node SET nodeno=21 WHERE nodeno=3; } +do_execsql_test 1.2.1 { SELECT rtreecheck('rt1')=='ok'; } {0} +do_execsql_test 1.2.2 { SELECT rtreecheck('main', 'rt1')=='ok'; } {0} +do_execsql_test 1.2.3 { SELECT rtreecheck('aux', 'rt1')=='ok'; } {1} +do_execsql_test 1.2.4 { UPDATE rt1_node SET nodeno=3 WHERE nodeno=21; } + +# Corrupt the table in database 'aux': +do_execsql_test 1.2.1 { UPDATE aux.rt1_node SET nodeno=21 WHERE nodeno=3; } +do_execsql_test 1.2.1 { SELECT rtreecheck('rt1')=='ok'; } {1} +do_execsql_test 1.2.2 { SELECT rtreecheck('main', 'rt1')=='ok'; } {1} +do_execsql_test 1.2.3 { SELECT rtreecheck('aux', 'rt1')=='ok'; } {0} +do_execsql_test 1.2.4 { UPDATE rt1_node SET nodeno=3 WHERE nodeno=21; } + +# EVIDENCE-OF: R-45759-33459 Example: To verify that an R*Tree named +# "demo_index" is well-formed and internally consistent, run: SELECT +# rtreecheck('demo_index'); +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE demo_index USING rtree(id, x1,x2, y1,y2); + INSERT INTO demo_index SELECT id, a, b, a, b FROM rt1; +} +do_execsql_test 2.1 { SELECT rtreecheck('demo_index') } {ok} +do_execsql_test 2.2 { + UPDATE demo_index_rowid SET nodeno=44 WHERE rowid=44; + SELECT rtreecheck('demo_index'); +} {{Found (44 -> 44) in %_rowid table, expected (44 -> 4)}} + + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE rt2 USING rtree_i32(id, a, b, c, d); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<200 + ) + INSERT INTO rt2 SELECT i, i, i+2, i, i+2 FROM s; +} + +# EVIDENCE-OF: R-02555-31045 for each dimension, (coord1 <= coord2). +# +execsql BEGIN +do_test 3.1 { + set cell [ + lindex [execsql {SELECT rnode(data) FROM rt2_node WHERE nodeno=3}] 0 3 + ] + set cell [list [lindex $cell 0] \ + [lindex $cell 2] [lindex $cell 1] \ + [lindex $cell 3] [lindex $cell 4] \ + ] + execsql { + UPDATE rt2_node SET data=rnode_replace_cell(data, 3, $cell) WHERE nodeno=3 + } + execsql { SELECT rtreecheck('rt2') } +} {{Dimension 0 of cell 3 on node 3 is corrupt}} +execsql ROLLBACK + +# EVIDENCE-OF: R-13844-15873 unless the cell is on the root node, that +# the cell is bounded by the parent cell on the parent node. +# +execsql BEGIN +do_test 3.2 { + set cell [ + lindex [execsql {SELECT rnode(data) FROM rt2_node WHERE nodeno=3}] 0 3 + ] + lset cell 3 450 + lset cell 4 451 + execsql { + UPDATE rt2_node SET data=rnode_replace_cell(data, 3, $cell) WHERE nodeno=3 + } + execsql { SELECT rtreecheck('rt2') } +} {{Dimension 1 of cell 3 on node 3 is corrupt relative to parent}} +execsql ROLLBACK + +# EVIDENCE-OF: R-02505-03621 for leaf nodes, that there is an entry in +# the %_rowid table corresponding to the cell's rowid value that points +# to the correct node. +# +execsql BEGIN +do_test 3.3 { + execsql { + UPDATE rt2_rowid SET rowid=452 WHERE rowid=100 + } + execsql { SELECT rtreecheck('rt2') } +} {{Mapping (100 -> 6) missing from %_rowid table}} +execsql ROLLBACK + +# EVIDENCE-OF: R-50927-02218 for cells on non-leaf nodes, that there is +# an entry in the %_parent table mapping from the cell's child node to +# the node that it resides on. +# +execsql BEGIN +do_test 3.4.1 { + execsql { + UPDATE rt2_parent SET parentnode=123 WHERE nodeno=3 + } + execsql { SELECT rtreecheck('rt2') } +} {{Found (3 -> 123) in %_parent table, expected (3 -> 1)}} +execsql ROLLBACK +execsql BEGIN +do_test 3.4.2 { + execsql { + UPDATE rt2_parent SET nodeno=123 WHERE nodeno=3 + } + execsql { SELECT rtreecheck('rt2') } +} {{Mapping (3 -> 1) missing from %_parent table}} +execsql ROLLBACK + +# EVIDENCE-OF: R-23235-09153 That there are the same number of entries +# in the %_rowid table as there are leaf cells in the r-tree structure, +# and that there is a leaf cell that corresponds to each entry in the +# %_rowid table. +execsql BEGIN +do_test 3.5 { + execsql { INSERT INTO rt2_rowid VALUES(1000, 1000) } + execsql { SELECT rtreecheck('rt2') } +} {{Wrong number of entries in %_rowid table - expected 200, actual 201}} +execsql ROLLBACK + +# EVIDENCE-OF: R-62800-43436 That there are the same number of entries +# in the %_parent table as there are non-leaf cells in the r-tree +# structure, and that there is a non-leaf cell that corresponds to each +# entry in the %_parent table. +execsql BEGIN +do_test 3.6 { + execsql { INSERT INTO rt2_parent VALUES(1000, 1000) } + execsql { SELECT rtreecheck('rt2') } +} {{Wrong number of entries in %_parent table - expected 9, actual 10}} +execsql ROLLBACK + + + +finish_test diff --git a/ext/rtree/rtreedoc2.test b/ext/rtree/rtreedoc2.test new file mode 100644 index 000000000..ca0c6b31b --- /dev/null +++ b/ext/rtree/rtreedoc2.test @@ -0,0 +1,346 @@ +# 2021 September 13 +# +# 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 focus of this file is testing the r-tree extension. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] rtree_util.tcl] +source $testdir/tester.tcl +set testprefix rtreedoc2 + +ifcapable !rtree { + finish_test + return +} + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 6 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc2-1 + +# EVIDENCE-OF: R-35254-48865 A call to one of the above APIs creates a +# new SQL function named by the second parameter (zQueryFunc or zGeom). +# +# [register_circle_geom db] registers new geometry callback "Qcircle" +# and legacy implementation "circle". Test that these do actually appear. +# +do_execsql_test 1.1.0 { + SELECT * FROM pragma_function_list WHERE name IN('circle', 'qcircle'); +} { +} +do_test 1.1 { + register_circle_geom db +} {SQLITE_OK} +do_execsql_test 1.1.2 { + SELECT * FROM pragma_function_list WHERE name = 'circle' AND enc='utf8'; +} { + circle 0 s utf8 -1 0 +} +do_execsql_test 1.1.3 { + SELECT * FROM pragma_function_list WHERE name = 'qcircle' AND enc='utf8'; +} { + qcircle 0 s utf8 -1 0 +} + +do_execsql_test 1.2.0 { SELECT circle(1, 2, 3); } {{}} +do_execsql_test 1.2.1 { SELECT qcircle(1, 2, 3); } {{}} + +# EVIDENCE-OF: R-61427-46983 +do_execsql_test 1.3.0 { + CREATE VIRTUAL TABLE demo_index USING rtree(id, x1,x2, y1,y2); + INSERT INTO demo_index VALUES(10, 45,45, 24,24); + INSERT INTO demo_index VALUES(20, 50,50, 28,28); + INSERT INTO demo_index VALUES(30, 43,43, 22,22); +} +do_execsql_test 1.3.1 { + SELECT id FROM demo_index WHERE id MATCH circle(45.3, 22.9, 5.0) +} {10 30} + +# EVIDENCE-OF: R-16907-50223 The SQL syntax for custom queries is the +# same regardless of which interface, sqlite3_rtree_geometry_callback() +# or sqlite3_rtree_query_callback(), is used to register the SQL +# function. +do_execsql_test 1.3.2 { + SELECT id FROM demo_index WHERE id MATCH qcircle(45.3, 22.9, 5.0, 1) +} {10 30} + + +# EVIDENCE-OF: R-59634-51678 When that SQL function appears on the +# right-hand side of the MATCH operator and the left-hand side of the +# MATCH operator is any column in the R*Tree virtual table, then the +# callback defined by the third argument (xQueryFunc or xGeom) is +# invoked to determine if a particular object or subtree overlaps the +# desired region. +proc box_geom {args} { + lappend ::box_geom [concat [lindex $args 0] [lrange $args 2 end-1]] + return "" +} +register_box_geom db box_geom +set box_geom [list] +do_execsql_test 1.3.2 { + SELECT id FROM demo_index WHERE id MATCH box(43,46, 21,25); +} {10 30} +do_test 1.3.3 { + set ::box_geom +} [list {*}{ + {box {43.0 46.0 21.0 25.0} {45.0 45.0 24.0 24.0}} + {box {43.0 46.0 21.0 25.0} {50.0 50.0 28.0 28.0}} + {box {43.0 46.0 21.0 25.0} {43.0 43.0 22.0 22.0}} +}] + +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Section 6 of documentation. +#------------------------------------------------------------------------- +#------------------------------------------------------------------------- +set testprefix rtreedoc2-2 + +# EVIDENCE-OF: R-02424-24769 The second argument is the number of +# coordinates in each r-tree entry, and is always the same for any given +# R*Tree. +# +# EVIDENCE-OF: R-40260-16838 The number of coordinates is 2 for a +# 1-dimensional R*Tree, 4 for a 2-dimensional R*Tree, 6 for a +# 3-dimensional R*Tree, and so forth. +# +# The second argument refered to above is the length of the list passed +# as the 3rd parameter to the Tcl script. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE rt1 USING rtree(id, x1,x2); + CREATE VIRTUAL TABLE rt2 USING rtree(id, x1,x2, y1,y2); + CREATE VIRTUAL TABLE rt3 USING rtree(id, x1,x2, y1,y2, z1,z2); + + INSERT INTO rt1 DEFAULT VALUES; + INSERT INTO rt2 DEFAULT VALUES; + INSERT INTO rt3 DEFAULT VALUES; +} +foreach {tn tbl nCoord} { + 1 rt1 2 + 2 rt2 4 + 3 rt3 6 +} { + set ::box_geom [list] + do_catchsql_test 1.$tn.1 " + SELECT id FROM $tbl WHERE id MATCH box(); + " {1 {SQL logic error}} + + do_test 1.$tn.2 { + llength [lindex $::box_geom 0 2] + } $nCoord +} + +# EVIDENCE-OF: R-28051-48608 If xGeom returns anything other than +# SQLITE_OK, then the r-tree query will abort with an error. +proc box_geom {args} { + error "an error!" +} +do_catchsql_test 2.0 { + SELECT * FROM rt2 WHERE id MATCH box(22,23, 24,25); +} {1 {SQL logic error}} + +do_execsql_test 3.0 { + INSERT INTO rt1 VALUES(10, 10, 10); + INSERT INTO rt1 VALUES(11, 11, 11); + INSERT INTO rt1 VALUES(12, 12, 12); + INSERT INTO rt1 VALUES(13, 13, 13); + INSERT INTO rt1 VALUES(14, 14, 14); +} + +# EVIDENCE-OF: R-53759-57366 The exact same sqlite3_rtree_geometry +# structure is used for every callback for same MATCH operator in the +# same query. +proc box_geom {args} { + lappend ::ptr_list [lindex $args 4] + return 0 +} +set ::ptr_list [list] +do_execsql_test 3.1 { + SELECT * FROM rt1 WHERE id MATCH box(1,1); +} +do_test 3.2 { + set val [lindex $::ptr_list 0] + foreach p $::ptr_list { + if {$p!=$val} {error "pointer mismatch"} + } +} {} + +# EVIDENCE-OF: R-60247-35692 The contents of the sqlite3_rtree_geometry +# structure are initialized by SQLite but are not subsequently modified. +proc box_geom {args} { + lappend ::box_geom [concat [lindex $args 0] [lrange $args 2 end-1]] + if {[llength $::box_geom]==3} { + return "zero" + } + return "" +} +set ::box_geom [list] +do_catchsql_test 3.2 { + SELECT * FROM rt1 WHERE id MATCH box(1,1); +} {1 {SQL logic error}} +do_test 3.3 { + set ::box_geom +} [list {*}{ + {box {1.0 1.0} {0.0 0.0}} + {box {1.0 1.0} {10.0 10.0}} + {box {1.0 1.0} {11.0 11.0}} + {box 0.0 {12.0 12.0}} +}] + +# EVIDENCE-OF: R-31246-29731 The pContext member of the +# sqlite3_rtree_geometry structure is always set to a copy of the +# pContext argument passed to sqlite3_rtree_geometry_callback() when the +# callback is registered. +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE r1 USING rtree(id, minX,maxX, minY,maxY); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<120 + ) + INSERT INTO r1 SELECT i,i,i+1, 200,201 FROM s; +} +set ctx [register_box_geom db box_geom] +set ::box_geom [list] +proc box_geom {args} { + lappend ::box_geom [lindex $args 1] + return "" +} +do_execsql_test 4.1 { + SELECT count(*) FROM r1 WHERE id MATCH box(0,150,199,201) +} 120 +do_test 4.2 { + foreach g $::box_geom { + if {$g!=$ctx} {error "pointer mismatch"} + } +} {} + +# EVIDENCE-OF: R-09904-19077 The aParam[] array (size nParam) contains +# the parameter values passed to the SQL function on the right-hand side +# of the MATCH operator. +proc box_geom {args} { + set ::box_geom [lindex $args 2] +} +foreach {tn q vals} { + 1 "SELECT count(*) FROM r1 WHERE id MATCH box(1,2,3)" {1.0 2.0 3.0} + 2 "SELECT count(*) FROM r1 WHERE id MATCH box(10001)" {10001.0} + 3 "SELECT count(*) FROM r1 WHERE id MATCH box(-10001)" {-10001.0} +} { + do_catchsql_test 5.$tn.1 $q {1 {SQL logic error}} + do_test 5.$tn.2 { set ::box_geom } $vals +} + +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE myrtree USING rtree(id, x1,x2); + INSERT INTO myrtree VALUES(1, 1, 1); + INSERT INTO myrtree VALUES(2, 2, 2); + INSERT INTO myrtree VALUES(3, 3, 3); +} + +# EVIDENCE-OF: R-44448-00687 The pUser and xDelUser members of the +# sqlite3_rtree_geometry structure are initially set to NULL. +set ::box_geom_calls 0 +proc box_geom {args} { + incr ::box_geom_calls + return user_is_zero +} +do_execsql_test 5.1.1 { + SELECT * FROM myrtree WHERE id MATCH box(4, 5); +} +do_test 5.1.2 { set ::box_geom_calls } 3 + + +# EVIDENCE-OF: R-55837-00155 The pUser variable may be set by the +# callback implementation to any arbitrary value that may be useful to +# subsequent invocations of the callback within the same query (for +# example, a pointer to a complicated data structure used to test for +# region intersection). +# +# EVIDENCE-OF: R-34745-08839 If the xDelUser variable is set to a +# non-NULL value, then after the query has finished running SQLite +# automatically invokes it with the value of the pUser variable as the +# only argument. +# +set ::box_geom_calls 0 +proc box_geom {args} { + incr ::box_geom_calls + switch -- $::box_geom_calls { + 1 { + return user_is_zero + } + 2 { + return [list user box_geom_finalizer] + } + } + return "" +} +proc box_geom_finalizer {} { + set ::box_geom_finalizer "::box_geom_calls is $::box_geom_calls" +} +do_execsql_test 5.1.1 { + SELECT * FROM myrtree WHERE id MATCH box(4, 5); +} +do_test 5.1.2 { set ::box_geom_calls } 3 +do_test 5.1.3 { + set ::box_geom_finalizer +} {::box_geom_calls is 3} + + +# EVIDENCE-OF: R-28176-28813 The xGeom callback always does a +# depth-first search of the r-tree. +# +# For a breadth first search, final test case would return "B L" only. +# +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE xyz USING rtree(x, x1,x2, y1,y2); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<15 + ) + INSERT INTO xyz SELECT NULL, one.i,one.i+1, two.i,two.i+1 FROM s one, s two; +} +do_execsql_test 6.1 { + SELECT count(*) FROM xyz_node +} {10} +proc box_geom {args} { + set coords [lindex $args 3] + set area [expr { + ([lindex $coords 1]-[lindex $coords 0]) * + ([lindex $coords 3]-[lindex $coords 2]) + }] + if {$area==1} { + lappend ::box_geom_calls L + } else { + lappend ::box_geom_calls B + } +} +set ::box_geom_calls [list] +do_execsql_test 6.2 { + SELECT count(*) FROM xyz WHERE x MATCH box(0,20,0,20) +} 225 +do_test 6.3 { + set prev "" + set box_calls [list] + foreach c $::box_geom_calls { + if {$c!=$prev} { + lappend ::box_calls $c + set prev $c + } + } + set ::box_calls +} {B L B L B L B L B L B L B L B L B L} + + +finish_test + diff --git a/ext/rtree/rtreedoc3.test b/ext/rtree/rtreedoc3.test new file mode 100644 index 000000000..0403409fa --- /dev/null +++ b/ext/rtree/rtreedoc3.test @@ -0,0 +1,292 @@ +# 2021 September 13 +# +# 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 focus of this file is testing the r-tree extension. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] rtree_util.tcl] +source $testdir/tester.tcl +set testprefix rtreedoc3 + +ifcapable !rtree { + finish_test + return +} + + +# This command assumes that the argument is a node blob for a 2 dimensional +# i32 r-tree table. It decodes and returns a list of cells from the node +# as a list. Each cell is itself a list of the following form: +# +# {$rowid $minX $maxX $minY $maxY} +# +# For internal (non-leaf) nodes, the rowid is replaced by the child node +# number. +# +proc rnode_cells {aData} { + set nDim 2 + + set nData [string length $aData] + set nBytePerCell [expr (8 + 2*$nDim*4)] + binary scan [string range $aData 2 3] S nCell + + set res [list] + for {set i 0} {$i < $nCell} {incr i} { + set iOff [expr $i*$nBytePerCell+4] + set cell [string range $aData $iOff [expr $iOff+$nBytePerCell-1]] + binary scan $cell WIIII rowid x1 x2 y1 y2 + lappend res [list $rowid $x1 $x2 $y1 $y2] + } + + return $res +} + +# Interpret the first two bytes of the blob passed as the only parameter +# as a 16-bit big-endian integer and return the value. If this blob is +# the root node of an r-tree, this value is the height of the tree. +# +proc rnode_height {aData} { + binary scan [string range $aData 0 1] S nHeight + return $nHeight +} + +# Return a blob containing node iNode of r-tree "rt". +# +proc rt_node_get {iNode} { + db one { SELECT data FROM rt_node WHERE nodeno=$iNode } +} + + +#-------------------------------------------------------------- +# API: +# +# pq_init +# Initialize a new test. +# +# pq_test_callback +# Invoked each time the xQueryCallback function is called. This Tcl +# command checks that the arguments that SQLite passed to xQueryCallback +# are as expected. +# +# pq_test_row +# Invoked each time a row is returned. Checks that the row returned +# was predicted by the documentation. +# +# DATA STRUCTURE: +# The priority queue is stored as a Tcl list. The order of elements in +# the list is unimportant - it is just used as a set here. Each element +# in the priority queue is itself a list. The first element is the +# priority value for the entry (a real). Following this is a list of +# key-value pairs that make up the entries fields. +# +proc pq_init {} { + global Q + set Q(pri_queue) [list] + + set nHeight [rnode_height [rt_node_get 1]] + set nCell [llength [rnode_cells [rt_node_get 1]]] + + # EVIDENCE-OF: R-54708-13595 An R*Tree query is initialized by making + # the root node the only entry in a priority queue sorted by rScore. + lappend Q(pri_queue) [list 0.0 [list \ + iLevel [expr $nHeight+1] \ + iChild 1 \ + iCurrent 0 \ + ]] +} + +proc pq_extract {} { + global Q + if {[llength $Q(pri_queue)]==0} { + error "priority queue is empty!" + } + + # Find the priority queue entry with the lowest score. + # + # EVIDENCE-OF: R-47257-47871 Smaller scores are processed first. + set iBest 0 + set rBestScore [lindex $Q(pri_queue) 0 0] + for {set ii 1} {$ii < [llength $Q(pri_queue)]} {incr ii} { + set rScore [expr [lindex $Q(pri_queue) $ii 0]] + if {$rScore<$rBestScore} { + set rBestScore $rScore + set iBest $ii + } + } + + # Extract the entry with the lowest score from the queue and return it. + # + # EVIDENCE-OF: R-60002-49798 The query proceeds by extracting the entry + # from the priority queue that has the lowest score. + set ret [lindex $Q(pri_queue) $iBest] + set Q(pri_queue) [lreplace $Q(pri_queue) $iBest $iBest] + + return $ret +} + +proc pq_new_entry {rScore iLevel cell} { + global Q + + set rowid_name "iChild" + if {$iLevel==0} { set rowid_name "iRowid" } + + set kv [list] + lappend kv aCoord [lrange $cell 1 end] + lappend kv iLevel $iLevel + + if {$iLevel==0} { + lappend kv iRowid [lindex $cell 0] + } else { + lappend kv iChild [lindex $cell 0] + lappend kv iCurrent 0 + } + + lappend Q(pri_queue) [list $rScore $kv] +} + +proc pq_test_callback {L res} { + #pq_debug "pq_test_callback $L -> $res" + global Q + + array set G $L ;# "Got" - as in stuff passed to xQuery + + # EVIDENCE-OF: R-65127-42665 If the extracted priority queue entry is a + # node (a subtree), then the next child of that node is passed to the + # xQueryFunc callback. + # + # If it had been a leaf, the row should have been returned, instead of + # xQueryCallback being called on a child - as is happening here. + foreach {rParentScore parent} [pq_extract] {} + array set P $parent ;# "Parent" - as in parent of expected cell + if {$P(iLevel)==0} { error "query callback mismatch (1)" } + set child_node [rnode_cells [rt_node_get $P(iChild)]] + set expected_cell [lindex $child_node $P(iCurrent)] + set expected_coords [lrange $expected_cell 1 end] + if {[llength $expected_coords] != [llength $G(aCoord)]} { + puts [array get P] + puts "E: $expected_coords G: $G(aCoord)" + error "coordinate mismatch in query callback (1)" + } + foreach a [lrange $expected_cell 1 end] b $G(aCoord) { + if {$a!=$b} { error "coordinate mismatch in query callback (2)" } + } + + # Check level is as expected + # + if {$G(iLevel) != $P(iLevel)-1} { + error "iLevel mismatch in query callback (1)" + } + + # Unless the callback returned NOT_WITHIN, add the entry to the priority + # queue. + # + # EVIDENCE-OF: R-28754-35153 Those subelements for which the xQueryFunc + # callback sets eWithin to PARTLY_WITHIN or FULLY_WITHIN are added to + # the priority queue using the score supplied by the callback. + # + # EVIDENCE-OF: R-08681-45277 Subelements that return NOT_WITHIN are + # discarded. + set r [lindex $res 0] + set rScore [lindex $res 1] + if {$r!="fully" && $r!="partly" && $r!="not"} { + error "unknown result: $r - expected \"fully\", \"partly\" or \"not\"" + } + if {$r!="not"} { + pq_new_entry $rScore [expr $P(iLevel)-1] $expected_cell + } + + # EVIDENCE-OF: R-07194-63805 If the node has more children then it is + # returned to the priority queue. Otherwise it is discarded. + incr P(iCurrent) + if {$P(iCurrent)<[llength $child_node]} { + lappend Q(pri_queue) [list $rParentScore [array get P]] + } +} + +proc pq_test_result {id x1 x2 y1 y2} { + #pq_debug "pq_test_result $id $x1 $x2 $y1 $y2" + foreach {rScore next} [pq_extract] {} + + # The extracted entry must be a leaf (otherwise, xQueryCallback would + # have been called on the extracted entries children instead of just + # returning the data). + # + # EVIDENCE-OF: R-13214-54017 If that entry is a leaf (meaning that it is + # an actual R*Tree entry and not a subtree) then that entry is returned + # as one row of the query result. + array set N $next + if {$N(iLevel)!=0} { error "result row mismatch (1)" } + + if {$x1!=[lindex $N(aCoord) 0] || $x2!=[lindex $N(aCoord) 1] + || $y1!=[lindex $N(aCoord) 2] || $y2!=[lindex $N(aCoord) 3] + } { + if {$N(iLevel)!=0} { error "result row mismatch (2)" } + } + + if {$id!=$N(iRowid)} { error "result row mismatch (3)" } +} + +proc pq_done {} { + global Q + # EVIDENCE-OF: R-57438-45968 The query runs until the priority queue is + # empty. + if {[llength $Q(pri_queue)]>0} { + error "priority queue is not empty!" + } +} + +proc pq_debug {caption} { + global Q + + puts "**** $caption ****" + set i 0 + foreach q [lsort -real -index 0 $Q(pri_queue)] { + puts "PQ $i: $q" + incr i + } +} + +#-------------------------------------------------------------- + +proc box_query {a} { + set res [list fully [expr rand()]] + pq_test_callback $a $res + return $res +} + +register_box_query db box_query + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE rt USING rtree_i32(id, x1,x2, y1,y2); + WITH s(i) AS ( + SELECT 0 UNION ALL SELECT i+1 FROM s WHERE i<64 + ) + INSERT INTO rt SELECT NULL, a.i, a.i+1, b.i, b.i+1 FROM s a, s b; +} + +proc box_query {a} { + set res [list fully [expr rand()]] + pq_test_callback $a $res + return $res +} + +pq_init +db eval { SELECT id, x1,x2, y1,y2 FROM rt WHERE id MATCH qbox() } { + pq_test_result $id $x1 $x2 $y1 $y2 +} +pq_done + +finish_test + + diff --git a/ext/rtree/test_rtreedoc.c b/ext/rtree/test_rtreedoc.c new file mode 100644 index 000000000..119be0e0a --- /dev/null +++ b/ext/rtree/test_rtreedoc.c @@ -0,0 +1,348 @@ +/* +** 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. +** +************************************************************************* +** Code for testing all sorts of SQLite interfaces. This code +** is not included in the SQLite library. +*/ + +#include "sqlite3.h" +#if defined(INCLUDE_SQLITE_TCL_H) +# include "sqlite_tcl.h" +#else +# include "tcl.h" +#endif + +/* Solely for the UNUSED_PARAMETER() macro. */ +#include "sqliteInt.h" + +#ifdef SQLITE_ENABLE_RTREE + +typedef struct BoxGeomCtx BoxGeomCtx; +struct BoxGeomCtx { + Tcl_Interp *interp; + Tcl_Obj *pScript; +}; + +typedef struct BoxQueryCtx BoxQueryCtx; +struct BoxQueryCtx { + Tcl_Interp *interp; + Tcl_Obj *pScript; +}; + +static void testDelUser(void *pCtx){ + BoxGeomCtx *p = (BoxGeomCtx*)pCtx; + Tcl_EvalObjEx(p->interp, p->pScript, 0); + Tcl_DecrRefCount(p->pScript); + sqlite3_free(p); +} + +static int invokeTclGeomCb( + const char *zName, + sqlite3_rtree_geometry *p, + int nCoord, + sqlite3_rtree_dbl *aCoord +){ + int rc = SQLITE_OK; + if( p->pContext ){ + char aPtr[64]; + BoxGeomCtx *pCtx = (BoxGeomCtx*)p->pContext; + Tcl_Interp *interp = pCtx->interp; + Tcl_Obj *pScript = 0; + Tcl_Obj *pParam = 0; + Tcl_Obj *pCoord = 0; + int ii; + Tcl_Obj *pRes; + + + pScript = Tcl_DuplicateObj(pCtx->pScript); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(zName,-1)); + + sqlite3_snprintf(sizeof(aPtr)-1, aPtr, "%p", (void*)p->pContext); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(aPtr,-1)); + + pParam = Tcl_NewObj(); + for(ii=0; iinParam; ii++){ + Tcl_ListObjAppendElement( + interp, pParam, Tcl_NewDoubleObj(p->aParam[ii]) + ); + } + Tcl_ListObjAppendElement(interp, pScript, pParam); + + pCoord = Tcl_NewObj(); + for(ii=0; ii0 ){ + const char *zCmd = Tcl_GetString(aObj[0]); + if( 0==sqlite3_stricmp(zCmd, "zero") ){ + p->aParam[0] = 0.0; + p->nParam = 1; + } + else if( 0==sqlite3_stricmp(zCmd, "user") ){ + if( p->pUser || p->xDelUser ){ + rc = SQLITE_ERROR; + }else{ + BoxGeomCtx *pBGCtx = sqlite3_malloc(sizeof(BoxGeomCtx)); + if( pBGCtx==0 ){ + rc = SQLITE_NOMEM; + }else{ + pBGCtx->interp = interp; + pBGCtx->pScript = Tcl_DuplicateObj(pRes); + Tcl_IncrRefCount(pBGCtx->pScript); + Tcl_ListObjReplace(interp, pBGCtx->pScript, 0, 1, 0, 0); + p->pUser = (void*)pBGCtx; + p->xDelUser = testDelUser; + } + } + } + else if( 0==sqlite3_stricmp(zCmd, "user_is_zero") ){ + if( p->pUser || p->xDelUser ) rc = SQLITE_ERROR; + } + } + } + } + return rc; +} + +/* +# EVIDENCE-OF: R-00693-36727 The legacy xGeom callback is invoked with +# four arguments. + +# EVIDENCE-OF: R-50437-53270 The first argument is a pointer to an +# sqlite3_rtree_geometry structure which provides information about how +# the SQL function was invoked. + +# EVIDENCE-OF: R-00090-24248 The third argument, aCoord[], is an array +# of nCoord coordinates that defines a bounding box to be tested. + +# EVIDENCE-OF: R-28207-40885 The last argument is a pointer into which +# the callback result should be written. + +*/ +static int box_geom( + sqlite3_rtree_geometry *p, /* R-50437-53270 */ + int nCoord, /* R-02424-24769 */ + sqlite3_rtree_dbl *aCoord, /* R-00090-24248 */ + int *pRes /* R-28207-40885 */ +){ + int ii; + + if( p->nParam!=nCoord ){ + invokeTclGeomCb("box", p, nCoord, aCoord); + return SQLITE_ERROR; + } + if( invokeTclGeomCb("box", p, nCoord, aCoord) ) return SQLITE_ERROR; + + for(ii=0; iip->aParam[ii+1] || aCoord[ii+1]aParam[ii] ){ + /* R-28207-40885 */ + *pRes = 0; + return SQLITE_OK; + } + } + + /* R-28207-40885 */ + *pRes = 1; + + return SQLITE_OK; +} + +static int SQLITE_TCLAPI register_box_geom( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**); + extern const char *sqlite3ErrName(int); + sqlite3 *db; + BoxGeomCtx *pCtx; + char aPtr[64]; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB SCRIPT"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + + pCtx = (BoxGeomCtx*)ckalloc(sizeof(BoxGeomCtx*)); + pCtx->interp = interp; + pCtx->pScript = Tcl_DuplicateObj(objv[2]); + Tcl_IncrRefCount(pCtx->pScript); + + sqlite3_rtree_geometry_callback(db, "box", box_geom, (void*)pCtx); + + sqlite3_snprintf(64, aPtr, "%p", (void*)pCtx); + Tcl_SetObjResult(interp, Tcl_NewStringObj(aPtr, -1)); + return TCL_OK; +} + +static int box_query(sqlite3_rtree_query_info *pInfo){ + const char *azParentWithin[] = {"not", "partly", "fully", 0}; + BoxQueryCtx *pCtx = (BoxQueryCtx*)pInfo->pContext; + Tcl_Interp *interp = pCtx->interp; + Tcl_Obj *pEval; + Tcl_Obj *pArg; + Tcl_Obj *pTmp = 0; + int rc; + int ii; + + pEval = Tcl_DuplicateObj(pCtx->pScript); + Tcl_IncrRefCount(pEval); + pArg = Tcl_NewObj(); + Tcl_IncrRefCount(pArg); + + /* aParam[] */ + pTmp = Tcl_NewObj(); + Tcl_IncrRefCount(pTmp); + for(ii=0; iinParam; ii++){ + Tcl_Obj *p = Tcl_NewDoubleObj(pInfo->aParam[ii]); + Tcl_ListObjAppendElement(interp, pTmp, p); + } + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("aParam", -1)); + Tcl_ListObjAppendElement(interp, pArg, pTmp); + Tcl_DecrRefCount(pTmp); + + /* aCoord[] */ + pTmp = Tcl_NewObj(); + Tcl_IncrRefCount(pTmp); + for(ii=0; iinCoord; ii++){ + Tcl_Obj *p = Tcl_NewDoubleObj(pInfo->aCoord[ii]); + Tcl_ListObjAppendElement(interp, pTmp, p); + } + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("aCoord", -1)); + Tcl_ListObjAppendElement(interp, pArg, pTmp); + Tcl_DecrRefCount(pTmp); + + /* anQueue[] */ + pTmp = Tcl_NewObj(); + Tcl_IncrRefCount(pTmp); + for(ii=0; ii<=pInfo->mxLevel; ii++){ + Tcl_Obj *p = Tcl_NewIntObj((int)pInfo->anQueue[ii]); + Tcl_ListObjAppendElement(interp, pTmp, p); + } + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("anQueue", -1)); + Tcl_ListObjAppendElement(interp, pArg, pTmp); + Tcl_DecrRefCount(pTmp); + + /* iLevel */ + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("iLevel", -1)); + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewIntObj(pInfo->iLevel)); + + /* mxLevel */ + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("mxLevel", -1)); + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewIntObj(pInfo->mxLevel)); + + /* iRowid */ + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("iRowid", -1)); + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewWideIntObj(pInfo->iRowid)); + + /* rParentScore */ + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("rParentScore", -1)); + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewDoubleObj(pInfo->rParentScore)); + + /* eParentWithin */ + assert( pInfo->eParentWithin==0 + || pInfo->eParentWithin==1 + || pInfo->eParentWithin==2 + ); + Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("eParentWithin", -1)); + Tcl_ListObjAppendElement(interp, pArg, + Tcl_NewStringObj(azParentWithin[pInfo->eParentWithin], -1) + ); + + Tcl_ListObjAppendElement(interp, pEval, pArg); + rc = Tcl_EvalObjEx(interp, pEval, 0) ? SQLITE_ERROR : SQLITE_OK; + + if( rc==SQLITE_OK ){ + double rScore = 0.0; + int nObj = 0; + int eP = 0; + Tcl_Obj **aObj = 0; + Tcl_Obj *pRes = Tcl_GetObjResult(interp); + + if( Tcl_ListObjGetElements(interp, pRes, &nObj, &aObj) + || nObj!=2 + || Tcl_GetDoubleFromObj(interp, aObj[1], &rScore) + || Tcl_GetIndexFromObj(interp, aObj[0], azParentWithin, "value", 0, &eP) + ){ + rc = SQLITE_ERROR; + }else{ + pInfo->rScore = rScore; + pInfo->eParentWithin = eP; + } + } + + Tcl_DecrRefCount(pArg); + Tcl_DecrRefCount(pEval); + return rc; +} + +static void box_query_destroy(void *p){ + BoxQueryCtx *pCtx = (BoxQueryCtx*)p; + Tcl_DecrRefCount(pCtx->pScript); + ckfree((char*)pCtx); +} + +static int SQLITE_TCLAPI register_box_query( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**); + extern const char *sqlite3ErrName(int); + sqlite3 *db; + BoxQueryCtx *pCtx; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB SCRIPT"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + + pCtx = (BoxQueryCtx*)ckalloc(sizeof(BoxQueryCtx*)); + pCtx->interp = interp; + pCtx->pScript = Tcl_DuplicateObj(objv[2]); + Tcl_IncrRefCount(pCtx->pScript); + + sqlite3_rtree_query_callback( + db, "qbox", box_query, (void*)pCtx, box_query_destroy + ); + + Tcl_ResetResult(interp); + return TCL_OK; +} +#endif /* SQLITE_ENABLE_RTREE */ + + +int Sqlitetestrtreedoc_Init(Tcl_Interp *interp){ +#ifdef SQLITE_ENABLE_RTREE + Tcl_CreateObjCommand(interp, "register_box_geom", register_box_geom, 0, 0); + Tcl_CreateObjCommand(interp, "register_box_query", register_box_query, 0, 0); +#endif /* SQLITE_ENABLE_RTREE */ + return TCL_OK; +} diff --git a/ext/session/session1.test b/ext/session/session1.test index 0eb850a79..bcd7b03d5 100644 --- a/ext/session/session1.test +++ b/ext/session/session1.test @@ -152,6 +152,26 @@ do_changeset_test $tn.2.4.2 S {} do_changeset_invert_test $tn.2.4.3 S {} do_test $tn.2.4.4 { S delete } {} +do_execsql_test $tn.2.5.0 { + SELECT * FROM t1 ORDER BY x +} { + 2 Surin + 10 Sukhothai + 20 Thapae +} + +do_test $tn.2.5.1 { + sqlite3session S db main + S attach t1 + execsql { DELETE FROM t1 } +} {} +do_changeset_test $tn.2.5.2 S { + {DELETE t1 0 X. {i 10 t Sukhothai} {}} + {DELETE t1 0 X. {i 2 t Surin} {}} + {DELETE t1 0 X. {i 20 t Thapae} {}} +} +do_test $tn.2.5.3 { S delete } {} + #------------------------------------------------------------------------- # Test the application of simple changesets. These tests also test that # the conflict callback is invoked correctly. For these tests, the diff --git a/ext/session/session6.test b/ext/session/session6.test index 8a1f172cd..22fa93cb3 100644 --- a/ext/session/session6.test +++ b/ext/session/session6.test @@ -19,6 +19,7 @@ if {![info exists testdir]} { source [file join [file dirname [info script]] session_common.tcl] source $testdir/tester.tcl ifcapable !session {finish_test; return} +ifcapable !incrblob {finish_test; return} set testprefix session6 diff --git a/ext/session/sessionat.test b/ext/session/sessionat.test index 4a3f59a44..8141d9232 100644 --- a/ext/session/sessionat.test +++ b/ext/session/sessionat.test @@ -20,9 +20,14 @@ if {![info exists testdir]} { source [file join [file dirname [info script]] session_common.tcl] source $testdir/tester.tcl ifcapable !session {finish_test; return} - set testprefix sessionat +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + db close sqlite3_shutdown test_sqlite3_log log diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index c96363b60..a892804b4 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -420,7 +420,7 @@ static int sessionSerializeValue( if( aBuf ){ sessionVarintPut(&aBuf[1], n); - if( n ) memcpy(&aBuf[nVarint + 1], z, n); + if( n>0 ) memcpy(&aBuf[nVarint + 1], z, n); } nByte = 1 + nVarint + n; @@ -1025,16 +1025,32 @@ static int sessionTableInfo( }else if( rc==SQLITE_ERROR ){ zPragma = sqlite3_mprintf(""); }else{ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; return rc; } }else{ zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); } - if( !zPragma ) return SQLITE_NOMEM; + if( !zPragma ){ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + return SQLITE_NOMEM; + } rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); sqlite3_free(zPragma); - if( rc!=SQLITE_OK ) return rc; + if( rc!=SQLITE_OK ){ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + return rc; + } nByte = nThis + 1; while( SQLITE_ROW==sqlite3_step(pStmt) ){ @@ -1452,7 +1468,11 @@ static int sessionFindTable( ){ rc = sqlite3session_attach(pSession, zName); if( rc==SQLITE_OK ){ - for(pRet=pSession->pTable; pRet->pNext; pRet=pRet->pNext); + pRet = pSession->pTable; + while( ALWAYS(pRet) && pRet->pNext ){ + pRet = pRet->pNext; + } + assert( pRet!=0 ); assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ); } } @@ -2225,6 +2245,7 @@ static int sessionAppendUpdate( int i; /* Used to iterate through columns */ u8 *pCsr = p->aRecord; /* Used to iterate through old.* values */ + assert( abPK!=0 ); sessionAppendByte(pBuf, SQLITE_UPDATE, &rc); sessionAppendByte(pBuf, p->bIndirect, &rc); for(i=0; ipTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ if( pTab->nEntry ){ const char *zName = pTab->zName; - int nCol; /* Number of columns in table */ - u8 *abPK; /* Primary key array */ + int nCol = 0; /* Number of columns in table */ + u8 *abPK = 0; /* Primary key array */ const char **azCol = 0; /* Table columns */ int i; /* Used to iterate through hash buckets */ sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */ @@ -2587,6 +2610,7 @@ static int sessionGenerateChangeset( sessionAppendCol(&buf, pSel, iCol, &rc); } }else{ + assert( abPK!=0 ); /* Because sessionSelectStmt() returned ok */ rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK); } }else if( p->op!=SQLITE_INSERT ){ @@ -2647,7 +2671,10 @@ int sqlite3session_changeset( int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ void **ppChangeset /* OUT: Buffer containing changeset */ ){ - int rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset); + int rc; + + if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE; + rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset); assert( rc || pnChangeset==0 || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize ); @@ -2662,6 +2689,7 @@ int sqlite3session_changeset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ + if( xOutput==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0); } @@ -2673,6 +2701,7 @@ int sqlite3session_patchset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ + if( xOutput==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0); } @@ -2688,6 +2717,7 @@ int sqlite3session_patchset( int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */ void **ppPatchset /* OUT: Buffer containing changeset */ ){ + if( pnPatchset==0 || ppPatchset==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset); } @@ -3651,11 +3681,11 @@ static int sessionChangesetInvert( } assert( rc==SQLITE_OK ); - if( pnInverted ){ + if( pnInverted && ALWAYS(ppInverted) ){ *pnInverted = sOut.nBuf; *ppInverted = sOut.aBuf; sOut.aBuf = 0; - }else if( sOut.nBuf>0 ){ + }else if( sOut.nBuf>0 && ALWAYS(xOutput!=0) ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); } @@ -4111,7 +4141,7 @@ static int sessionBindRow( for(i=0; rc==SQLITE_OK && i0 ) rc = xOutput(pOut, buf.aBuf, buf.nBuf); - }else{ + }else if( ppOut ){ *ppOut = buf.aBuf; - *pnOut = buf.nBuf; + if( pnOut ) *pnOut = buf.nBuf; buf.aBuf = 0; } } @@ -5656,7 +5686,7 @@ static int sessionRebase( if( sOut.nBuf>0 ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); } - }else{ + }else if( ppOut ){ *ppOut = (void*)sOut.aBuf; *pnOut = sOut.nBuf; sOut.aBuf = 0; diff --git a/main.mk b/main.mk index 16d72ff92..f1a4660a8 100644 --- a/main.mk +++ b/main.mk @@ -1,3 +1,4 @@ + ############################################################################### # The following macros should be defined before this script is # invoked: @@ -386,7 +387,8 @@ TESTSRC += \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/fts5/fts5_tcl.c \ $(TOP)/ext/fts5/fts5_test_mi.c \ - $(TOP)/ext/fts5/fts5_test_tok.c + $(TOP)/ext/fts5/fts5_test_tok.c \ + $(TOP)/ext/rtree/test_rtreedoc.c #TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c @@ -663,7 +665,7 @@ sqlite3ext.h: target_source cp tsrc/sqlite3ext.h . sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl - tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros + tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros=1 echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c @@ -748,6 +750,7 @@ SHELL_SRC = \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ $(TOP)/ext/misc/uint.c \ diff --git a/manifest b/manifest index a20358755..8adddf8d8 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.36.0 -D 2021-06-18T18:36:39.378 +C Version\s3.37.2 +D 2022-01-06T13:25:41.295 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in ff5ac24ec9cb700135ee4453fa14c18ea371e94250d3673bda1f2cb56e1108ce +F Makefile.in 0e91c42a1dd13a569b1fa4f4dfb7d3632f3164a1c05c71341533d67db5b641dd F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 -F Makefile.msc 8285e1ea7264f32b08702866d69dd127c0f663bbc2002323d853c68c27c6b6e4 +F Makefile.msc 88f05063ee36a5fb77d69b877ef824d0743b5325c95bfbf74d6ff17bd1c9fb1f F README.md 27fb76aa7eb57ed63a53bbba7292b6bf71f51125554f79f16b5d040edd1e6110 -F VERSION 8c392f6d70d82e513a0eac622bdf23c43df3e084cb630288beba8cef3012c812 +F VERSION b8218d35896c0f381cf64a499abbab64eeb86b40116a7d35c5a39c6677e8bb89 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -29,13 +29,13 @@ F autoconf/tea/pkgIndex.tcl.in 3ef61715cf1c7bdcff56947ffadb26bc991ca39d F autoconf/tea/tclconfig/install-sh bdd5e293591621ae60d9824d86a4b1c5f22c3d00 F autoconf/tea/tclconfig/tcl.m4 66ddf0a5d5e4b1d29bff472c0985fd7fa89d0fb5 F autoconf/tea/win/makefile.vc a5ff708245260c2794c6aaa0151efe5403d5896566eaf096747be0d9075284e4 -F autoconf/tea/win/nmakehlp.c 190bf8b40f6f90eeb6cbc1e5c13a19fc7ad870e479e0314c88174f71bf9e28a7 +F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea00342d1a1a7eaa19cb F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure c658869fc056a2460a2212fb2960e410cd24e3fb9c55640fbc78d15b48810936 x -F configure.ac 4e4b58b32f88c8da9914a2f2c3158f80e69907eccc019fcc7e3ba14ffd91c640 +F configure f8a03394ccec572e76a570aa858ccd24e0d54fd76c995b7220a4dfda8b899d27 x +F configure.ac c8ba54bac7e73e000acdfef5e394fe21a3876aa09d0f5c07131bf5ac5a525299 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/lemon.html efc0cd2345d66905505d98f862e1c571512def0ceb5b016cb658fd4918eb76a3 @@ -50,8 +50,8 @@ F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test 6703fd74711daf8230240680b0a348d0720e28819b602701adfbd32457fdcddd -F ext/expert/sqlite3expert.c de51b187c629a4c4264d5de0b77862641e11426f7a963a92abf2d4077085fc8c +F ext/expert/expert1.test 3c642a4e7bbb14f21ddab595436fb465a4733f47a0fe5b2855e1d5ff900ef08e +F ext/expert/sqlite3expert.c 921a00823a826150cbb9a3341285a8edec7533480b71281e77737f19559b4b14 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e @@ -84,16 +84,16 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 95f55e24550c01c2a325d09c9ea8fdff61e923a4675f8545b28bf3c470e57dfb +F ext/fts3/fts3.c 6bfdc941372ee431e3d362986674a8708eb0e700b28c3d35d46d953a9351155f F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h bde280294d56ff50ee29d03e5140f0b6953b44d1c969bb5831e8ae85e3e76715 -F ext/fts3/fts3_aux.c 1af58af8f2b00a49f4fb1c2602f8da2054ad60076f46c8ebf85c5410eccccb65 -F ext/fts3/fts3_expr.c 5853cd7a35a79d193614add9b4c461b2d56f465d90899ca4309f05d9d1536558 +F ext/fts3/fts3Int.h cff59b8b13dafe9d59924a5d710f771ed8b121a55cccbc99b6e2a723fcde14dc +F ext/fts3/fts3_aux.c f0dc9bd98582615b7750218899bd0c729879b6bbf94d1be57ca1833ff49afc6f +F ext/fts3/fts3_expr.c 903bfb9433109fffb10e910d7066c49cbf8eeae316adc93f0499c4da7dfc932a F ext/fts3/fts3_hash.c 8b6e31bfb0844c27dc6092c2620bdb1fca17ed613072db057d96952c6bdb48b7 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf F ext/fts3/fts3_icu.c 305ce7fb6036484085b5556a9c8e62acdc7763f0f4cdf5fd538212a9f3720116 F ext/fts3/fts3_porter.c 3565faf04b626cddf85f03825e86056a4562c009 -F ext/fts3/fts3_snippet.c 57e1965906f3ecfcb5251a14aea98ac2d2dd1868a123bcd5a569601719ab9ead +F ext/fts3/fts3_snippet.c f9a8149173553113f3c495a503843e30028b5dc3723d0ca798c5ad6142e130e6 F ext/fts3/fts3_term.c f45a1e7c6ef464abb1231245d123dae12266b69e05cc56e14045b76591ae92d1 F ext/fts3/fts3_test.c d8d7b2734f894e8a489987447658e374cdd3a3bc8575c401decf1911cb7c6454 F ext/fts3/fts3_tokenize_vtab.c 8d15b148e7d88a4280389a200b26e8d52abda4c4ec2e9a35e9d7a1fa50e5aa03 @@ -102,7 +102,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c 98edfd77aeb53afcb26d8de3ed0a87f16468ee05f84f8c1752e6e378c354cd7a +F ext/fts3/fts3_write.c 3109c1a232da86474e196cc7db754445a354409f141e08cb11c846cdb17bdf31 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 @@ -113,22 +113,22 @@ F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6d F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 F ext/fts5/fts5.h c132a9323f22a972c4c93a8d5a3d901113a6e612faf30ca8e695788438c5ca2a -F ext/fts5/fts5Int.h 26c74dd5776f798436fbf604a0bf0e8de263b35b5060b05c15f9085845d9fda2 +F ext/fts5/fts5Int.h 36fd4a05e6e6307e3bac359a589d5f090b903afe0e7ae15db84f0ff90c79676a F ext/fts5/fts5_aux.c f558e1fb9f0f86a4f7489e258c162e1f947de5ff2709087fbb465fddb7092f98 -F ext/fts5/fts5_buffer.c 605f509f88c73e9ab41323cb6cf74932f936acf3f7d9c87bd0d3c599b4c90281 -F ext/fts5/fts5_config.c 8336d0ff6db0933f63cfec8ae0ab76e68393259cbccc0b46e1f79f7fa1842ff3 -F ext/fts5/fts5_expr.c 9462249a3bb82d0e49b163500f9d2197c2e4cd95bf440a9bbfc3906b22ea1e1b -F ext/fts5/fts5_hash.c 1aa93c9b5f461afba66701ee226297dc78402b3bdde81e90a10de5fe3df14959 -F ext/fts5/fts5_index.c eb1864c6abacf08d959956183a55a4f9767af76be289f2bb519bb0f197b3fd72 -F ext/fts5/fts5_main.c 35ebbcae681a4a40027c47bc2e94d7e7c81e331dc406bb9b23c546454ee8f98a -F ext/fts5/fts5_storage.c 58ba71e6cd3d43a5735815e7956ee167babb4d2cbfe206905174792af4d09d75 +F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 +F ext/fts5/fts5_config.c 501e7d3566bc92766b0e11c0109a7c5a6146bc41144195459af5422f6c2078aa +F ext/fts5/fts5_expr.c fcd0770d53028c2b53a15d0f53bf6d0e01b1bf3dd97630b9fedf0801f03aa3ec +F ext/fts5/fts5_hash.c d4fb70940359f2120ccd1de7ffe64cc3efe65de9e8995b822cd536ff64c96982 +F ext/fts5/fts5_index.c b1b2e5d4a9e3b54c740d8354cc47e3fa879f54c2176de55e0b882dab45ab7b07 +F ext/fts5/fts5_main.c 7c6092a53e6802962fa07b0fad3e61cb077b6c98b74b727d8d44ac2cf63bd914 +F ext/fts5/fts5_storage.c 76c6085239eb44424004c022e9da17a5ecd5aaec859fba90ad47d3b08f4c8082 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee F ext/fts5/fts5_test_tok.c a2bed8edb25f6432e8cdb62aad5916935c19dba8dac2b8324950cfff397e25ff F ext/fts5/fts5_tokenize.c 5e251efb0f1af99a25ed50010ba6b1ad1250aca5921af1988fdcabe5ebc3cb43 F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80 -F ext/fts5/fts5_vocab.c 7a071833064dc8bca236c3c323e56aac36f583aa2c46ce916d52e31ce87462c9 +F ext/fts5/fts5_vocab.c 12138e84616b56218532e3e8feb1d3e0e7ae845e33408dbe911df520424dc9d6 F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba F ext/fts5/test/fts5_common.tcl b01c584144b5064f30e6c648145a2dd6bc440841 @@ -137,7 +137,7 @@ F ext/fts5/test/fts5ab.test 9205c839332c908aaad2b01ab8670ece8b161e8f2ec8a9fabf18 F ext/fts5/test/fts5ac.test a7aa7e1fefc6e1918aa4d3111d5c44a09177168e962c5fd2cca9620de8a7ed6d F ext/fts5/test/fts5ad.test e8cf959dfcd57c8e46d6f5f25665686f3b6627130a9a981371dafdf6482790de F ext/fts5/test/fts5ae.test 1142d16d9cc193894dc13cc8f9c7a8a21411ac61b5567a878514df6f9f0d7bb7 -F ext/fts5/test/fts5af.test 724247405b13f8f06cc6ce464dc4f152dc5dd4e86b12c2099685d8f19747bf7b +F ext/fts5/test/fts5af.test bea75184c0e63631b552c20ebe4a631699f357e00a2059c92538f7aeece8291e F ext/fts5/test/fts5ag.test 7816f25a0707578f08145ab539fc0ca025f8951e788b28a6a18a06b2099469dd F ext/fts5/test/fts5ah.test 27b5a33bfd0363ca8a4dc659e6e2a5df3dea1c3c5b04bc51ca6aeb1277bd9b21 F ext/fts5/test/fts5ai.test bc97e4758cc93e06bf851d61c98fdf4e8b8f8315ee28a84fb15f916360856414 @@ -160,10 +160,12 @@ F ext/fts5/test/fts5connect.test 08030168fc96fc278fa81f28654fb7e90566f33aff269c0 F ext/fts5/test/fts5content.test 213506436fb2c87567b8e31f6d43ab30aab99354cec74ed679f22aad0cdbf283 F ext/fts5/test/fts5corrupt.test 77ae6f41a7eba10620efb921cf7dbe218b0ef232b04519deb43581cb17a57ebe F ext/fts5/test/fts5corrupt2.test 7453752ba12ce91690c469a6449d412561cc604b1dec994e16ab132952e7805f -F ext/fts5/test/fts5corrupt3.test 79467b7c69a9c45ee3260507c4648d6449323aaa3c1e05b360c9479f1aa7dcce +F ext/fts5/test/fts5corrupt3.test 0e473620582a53ac61f468f364db8a151c1e18d2a879b16439d172c12c4c9828 F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3 +F ext/fts5/test/fts5corrupt5.test 550d0884c14424f9acad051a741f1dd99ec9342277d938e91ff3daf9123d1209 +F ext/fts5/test/fts5corrupt6.test bf8eeae07825b088b9665d9d8e4accbd8dc9bf3cb85b6c64cf6c9e18ccc420a4 F ext/fts5/test/fts5delete.test 619295b20dbc1d840b403ee07c878f52378849c3c02e44f2ee143b3e978a0aa7 -F ext/fts5/test/fts5detail.test 31b240dbf6d44ac3507e2f8b65f29fdc12465ffd531212378c7ce1066766f54e +F ext/fts5/test/fts5detail.test 54015e9c43ec4ba542cfb93268abdf280e0300f350efd08ee411284b03595cc4 F ext/fts5/test/fts5determin.test 1b77879b2ae818b5b71c859e534ee334dac088b7cf3ff3bf76a2c82b1c788d11 F ext/fts5/test/fts5dlidx.test b90852c55881b29dbac6380b274de27beae623ac4b6d567c6c8fb9cdc315a86e F ext/fts5/test/fts5doclist.test faa9e9cc3c0645fa6203667cb5f007c359447c6ee66753f71a58175c2497cacd @@ -193,7 +195,7 @@ F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc27826807405 F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2 -F ext/fts5/test/fts5misc.test 088ac5f0f5de1ad45b0f83197ab5263bcae8130156cdc901bff2375ff2b8af86 +F ext/fts5/test/fts5misc.test 4d7d20372242cc618688de029b87d897d09fa1640302c254abee63cf3ffa2c10 F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd @@ -229,11 +231,11 @@ F ext/fts5/test/fts5unindexed.test 9021af86a0fb9fc616f7a69a996db0116e7936d0db638 F ext/fts5/test/fts5update.test b8affd796e45c94a4d19ad5c26606ea06065a0f162a9562d9f005b5a80ccf0bc F ext/fts5/test/fts5version.test c8f2cc105f0abf0224965f93e584633dee3e06c91478bc67e468f7cfdf97fd6a F ext/fts5/test/fts5vocab.test 7ed80d9af1ddaaa1637da05e406327b5aac250848bc604c1c1cc667908b87760 -F ext/fts5/test/fts5vocab2.test e0fdc3a3095f6eda68ac9bf9a443ff929a124d46f00af19933604085712e9d47 +F ext/fts5/test/fts5vocab2.test 681980e92e031c9f3fe8d9c149189e876c108da2fb0fb3a25bd8a9b94bff8f68 F ext/fts5/tool/fts5speed.tcl b0056f91a55b2d1a3684ec05729de92b042e2f85 F ext/fts5/tool/fts5txt2db.tcl c0d43c8590656f8240e622b00957b3a0facc49482411a9fdc2870b45c0c82f9f F ext/fts5/tool/loadfts5.tcl 95b03429ee6b138645703c6ca192c3ac96eaf093 -F ext/fts5/tool/mkfts5c.tcl d1c2a9ab8e0ec690a52316f33dd9b1d379942f45 +F ext/fts5/tool/mkfts5c.tcl 3eba8e9bee4221ed165f3304b51b2a74a705f4ec5df3d044573a2be539534af8 F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 1c48ffaf7f255bd73d00a35f68f6de357c2a6594f16cb00506a151be23694706 F ext/icu/icu.c 91c021c7e3e8bbba286960810fa303295c622e323567b2e6def4ce58e4466e60 @@ -289,8 +291,8 @@ F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a2 F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824 F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9 -F ext/misc/carray.c b75a0f207391038bf1540d3372f482a95c3613511c7c474db51ede1196321c7c -F ext/misc/carray.h de74ac70b2338f416723f7d538026e8ec0b7f1d388319f8f140c9a4d7677f02e +F ext/misc/carray.c b752f46411e4e47e34dce6f0c88bc8e51bb821ba9e49bfcd882506451c928f69 +F ext/misc/carray.h d2b1b12486d531367c37832d3d0dad34eea4bdd83ed839d445521ef01f0bc4e3 F ext/misc/cksumvfs.c b42ef52eaaa510d54ec320c87bea149e934a3b06cd232be2093562bf669bd572 F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c85c243 F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9 @@ -301,11 +303,11 @@ F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f82 F ext/misc/decimal.c 09f967dcf4a1ee35a76309829308ec278d3648168733f4a1147820e11ebefd12 F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe -F ext/misc/fileio.c 9b69e25da3b51d4a1d905a464ccb96709792ad627a742ba09215bc0d1447e7bd +F ext/misc/fileio.c 57fefd0efc535e62bb8b07fa146875171481da81a759bbfbe2fc91bab90058e0 F ext/misc/fossildelta.c 1240b2d3e52eab1d50c160c7fe1902a9bd210e052dc209200a750bbf885402d5 F ext/misc/fuzzer.c eae560134f66333e9e1ca4c8ffea75df42056e2ce8456734565dbe1c2a92bf3d -F ext/misc/ieee754.c cd6ab89f85fda8a020559b3f4d03001a8a62dd856beda5af3f558621d12be913 -F ext/misc/json1.c 76c5d9e0960fd15b4be79dacb76d872b4d0d983ce13e72ebfe9481d82cb9345d +F ext/misc/ieee754.c 91a5594071143a4ab79c638fe9f059af1db09932faf2e704c3e29216a7d4f511 +F ext/misc/json1.c 89a988f06dcb3da0d0af9fdb2b09892452ad12dfd8f432600ee6437a6dcac310 F ext/misc/memstat.c 3017a0832c645c0f8c773435620d663855f04690172316bd127270d1a7523d4d F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2d5537b F ext/misc/memvfs.c 7dffa8cc89c7f2d73da4bd4ccea1bcbd2bd283e3bb4cea398df7c372a197291b @@ -315,11 +317,11 @@ F ext/misc/noop.c 81efe4cad9ec740e64388b14281cb983e6e2c223fed43eb77ab3e34946e0c1 F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d09ea61f F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196 -F ext/misc/regexp.c 5853b0e5ed40c47f7ded2b0bf2ff73796f7cb21543089c5f07308e0032647b76 +F ext/misc/regexp.c 8cd0d2d904bf7014ba28beab8c1d502b5154e04a8c738b079d88e4ecca1b3981 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 -F ext/misc/series.c c6bd5d249e5199a1b55aeee4d0e6576ff3a68702fc475dbd64503a32903516c7 +F ext/misc/series.c f9896e76b029e3c6553c520552555e803e26e7dfe1890d5866243cf072d938d0 F ext/misc/sha1.c c8f2253c8792ffab9517695ea7d88c079f0395a5505eefef5c8198fe184ed5ac F ext/misc/shathree.c e984f31731de4cf302a0386be5fe664580f63d8204c47b9b41cc4b997745f9ec F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 @@ -337,11 +339,11 @@ F ext/misc/vfsstat.c 474d08efc697b8eba300082cb1eb74a5f0f3df31ed257db1cb07e72ab0e F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546 -F ext/misc/zipfile.c b7261ef6dbc2d18924ff80c40fb5d56c9ccfee3f822a7d3d43b7c87af3d27218 +F ext/misc/zipfile.c 5dcbbdae13ba45db5d3843b6f32a8f99df7ab0349a704857a3000618f9ea9ecb F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test c62904bd9526dcdc3496a21199aaf14ae191bbadbf67f076bf16be6b3f2115c2 -F ext/rbu/rbu10.test 0a201c32202143f23c81c0144503da339786fc20acb7a2fda11601b65659f314 +F ext/rbu/rbu10.test 06d2bc934a03a0978e750cc9c95b419d9b0bcbec1fc77128e33e377c3a73240b F ext/rbu/rbu11.test 5c834cf491086b45e071eabf71f708febc143e86a384a92de69e0b1a4cace144 F ext/rbu/rbu12.test 29f8b2118f6c96fac3755bd6d2b55c2db24f878b1f11fbfbe294f3a230a3dcdc F ext/rbu/rbu13.test 1285298e3360ec74511764841b3c174dcfe21da2f618c22febf1a20abd0365c2 @@ -361,6 +363,7 @@ F ext/rbu/rbucrash.test 000981a1fe8a6e4d9a684232f6a129e66a3ef595f5ed74655e2f9c68 F ext/rbu/rbucrash2.test efa143cc94228eb0266d3f1abfbee60a5838a84cef7cc3fcb8c145b74d96fd41 F ext/rbu/rbudiff.test abe895a8d479e4d33acb40e244e3d8e2cd25f55a18dfa8b9f83e13d00073f600 F ext/rbu/rbudor.test e3e8623926012f43eebe51fedf06a102df2640750d971596b052495f2536db20 +F ext/rbu/rbuexlock.test 4634a5526d02bf80b0c563f95774bd5af5783e3219ddeb30e413753c9a65510c F ext/rbu/rbuexpr.test 10d0420537c3bc7666e576d72adeffe7e86cfbb00dcc30aa9ce096c042415190 F ext/rbu/rbufault.test 2d7f567b79d558f6e093c58808cab4354f8a174e3802f69e7790a9689b3c09f8 F ext/rbu/rbufault2.test c81327a3ac2c385b9b954db3644d4e0df93eeebfc3de9f1f29975a1e73fd3d0c @@ -376,14 +379,14 @@ F ext/rbu/rbusave.test f4190a1a86fccf84f723af5c93813365ae33feda35845ba107b59683d F ext/rbu/rbusplit.test b37e7b40b38760881dc9c854bd40b4744c6b6cd74990754eca3bda0f407051e8 F ext/rbu/rbutemplimit.test 05ceefa90a2e26a99f40dd48282ed63a00df5e59c1f2bfd479c143e201a1b0ba F ext/rbu/rbuvacuum.test 55e101e90168c2b31df6c9638fe73dc7f7cc666b6142266d1563697d79f73534 -F ext/rbu/rbuvacuum2.test b8e5b51dc8b2c0153373d024c0936be3f66f9234acbd6d0baab0869d56b14e6b +F ext/rbu/rbuvacuum2.test 886add83fd74bcb02e6dd016ae5b585367bd58c5d0694c9d9ca7bdb1d1f578c2 F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10 -F ext/rbu/sqlite3rbu.c badb52388467f58e67ef104c5276d1ac68b316a30f8ccb2f74eac733625ae236 +F ext/rbu/sqlite3rbu.c 3658f1c6603955c7426952b74a6896337b1f672d326cd565e5af20e18d5744f0 F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812 F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 -F ext/repair/checkfreelist.c 0dbae18c1b552f58d64f8969e4fb1e7f11930c60a8c2a9a8d50b7f15bdfd54bd +F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890 F ext/repair/sqlite3_checker.c.in 4a5a3af3f450fe503e5a2985e98516dc2a6b9ad247449e284c1cf140fc91720f F ext/repair/sqlite3_checker.tcl a9a2caa9660567257c177a91124d8c0dccdfa341e25c51e6da7f1fd9e601eafa @@ -392,10 +395,10 @@ F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc782 F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c335096108c12c01bddbadcec F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/geopoly.c 98d45533989e908bf65b43f36ff6eaad95a9ffe6f3b6b8658fbd47d45c58b10b -F ext/rtree/rtree.c f1ce6a86f7cbff634900653bec2dfeba732dd4450eec921c0ac3851d41f462b9 +F ext/rtree/geopoly.c a7021cb524621573ccda213a35b0339371849dd4acc4909f689786ee1f964b7f +F ext/rtree/rtree.c d7b4b8b81d8d54376a7f81de5be85ec58b37c11604bcf42984a8418b34158d93 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 -F ext/rtree/rtree1.test 00792b030a4e188ff1b22e8530e8aa0452bb5dd81c2b18cb004afc7dc63e040e +F ext/rtree/rtree1.test 35c3bc0def71317b7601ee0d1149e7df2cd8fc4f13ec89a64761ac3f46ca123f F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d F ext/rtree/rtree3.test 4ee5d7df86040efe3d8d84f141f2962a7745452200a7cba1db06f86d97050499 F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b @@ -403,8 +406,8 @@ F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2 F ext/rtree/rtree6.test 9ce3691c1aac43070a9f194f0ebf54372db346c5a82241fd11b525ed53ce9f3f F ext/rtree/rtree7.test c8fb2e555b128dd0f0bdb520c61380014f497f8a23c40f2e820acc9f9e4fdce5 F ext/rtree/rtree8.test 2d99006a1386663978c9e1df167554671e4f711c419175b39f332719deb1ce0e -F ext/rtree/rtree9.test c646f12c8c1c68ef015c6c043d86a0c42488e2e68ed1bb1b0771a7ca246cbabf -F ext/rtree/rtreeA.test ed2f1be9c06dde0b1ab93a95dd9e87eeaa02db2d30bcb4b9179b69ee3dc3319b +F ext/rtree/rtree9.test fd3c9384ef8aabbc127b3878764070398f136eebc551cd20484b570f2cc1956a +F ext/rtree/rtreeA.test c0d8e91e25052d5f3fbda17632ca843b82ca13c4181fb6000a0d63bd2d7e70ce F ext/rtree/rtreeB.test 4cec297f8e5c588654bbf3c6ed0903f10612be8a2878055dd25faf8c71758bc9 F ext/rtree/rtreeC.test c4bfa9a61c6788c03e4a9ce40ab2cfc6100982559effd9842d1b658e1d47aa5f F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc @@ -418,8 +421,12 @@ F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b5879 F ext/rtree/rtreecheck.test d67d5b3e9e45bfa8cd90734e8e9302144ac415b8e9176c6f02d4f92892ee8a35 F ext/rtree/rtreecirc.test aec664eb21ae943aeb344191407afff5d392d3ae9d12b9a112ced0d9c5de298e F ext/rtree/rtreeconnect.test 225ad3fcb483d36cbee423a25052a6bbae762c9576ae9268332360c68c170d3d +F ext/rtree/rtreedoc.test 27a5703cb1200f6f69051de68da546cef3dfdcf59be73afadfc50b9f9c9960d9 +F ext/rtree/rtreedoc2.test 194ebb7d561452dcdc10bf03f44e30c082c2f0c14efeb07f5e02c7daf8284d93 +F ext/rtree/rtreedoc3.test 555a878c4d79c4e37fa439a1c3b02ee65d3ebaf75d9e8d96a9c55d66db3efbf8 F ext/rtree/rtreefuzz001.test 0fc793f67897c250c5fde96cefee455a5e2fb92f4feeabde5b85ea02040790ee F ext/rtree/sqlite3rtree.h 03c8db3261e435fbddcfc961471795cbf12b24e03001d0015b2636b0f3881373 +F ext/rtree/test_rtreedoc.c 5ad4029d6804eb9efafcac1598a9e0582f6119e48f818854f5b4db1788ca8bd4 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/util/randomshape.tcl 54ee03d0d4a1c621806f7f44d5b78d2db8fac26e0e8687c36c4bd0203b27dbff F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024 @@ -427,12 +434,12 @@ F ext/rtree/visual01.txt e9c2564083bcd30ec51b07f881bffbf0e12b50a3f6fced0c222c5c1 F ext/session/changeset.c 7a1e6a14c7e92d36ca177e92e88b5281acd709f3b726298dc34ec0fb58869cb5 F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8fac9cd8b0b24aa F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a -F ext/session/session1.test 0b2f88995832ea040ae8e83a1ad4afa99c00b85c779d213da73a95ea4113233e +F ext/session/session1.test e94f764fbfb672147c0ef7026b195988133b371dc8cf9e52423eba6cad69717e F ext/session/session2.test 7f53d755d921e0baf815c4258348e0ed460dfd8a772351bca5ad3ccbb1dc786e F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479 F ext/session/session4.test 6778997065b44d99c51ff9cece047ff9244a32856b328735ae27ddef68979c40 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 -F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26 +F ext/session/session6.test 35279f2ec45448cd2e24a61688219dc6cf7871757716063acf4a8b5455e1e926 F ext/session/session8.test 8e194b3f655d861ca36de5d4de53f702751bab3b F ext/session/session9.test 5409d90d8141881d08285ed1c2c0d8d10fb92069 F ext/session/sessionA.test 1feeab0b8e03527f08f2f1defb442da25480138f @@ -445,7 +452,7 @@ F ext/session/sessionG.test 3828b944cd1285f4379340fd36f8b64c464fc84df6ff3ccbc955 F ext/session/sessionH.test b17afdbd3b8f17e9bab91e235acf167cf35485db2ab2df0ea8893fbb914741a4 F ext/session/session_common.tcl f613174665456b2d916ae8df3e5735092a1c1712f36f46840172e9a01e8cc53e F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3 -F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec +F ext/session/sessionat.test 52993535f1230a42f70886643574ba7ae60ef854f8add9c8e3fcc3eb5c564bd2 F ext/session/sessionbig.test 890ade19e3f80f3d3a3e83821ff79c5e2af906a67ecb5450879f0015cadf101e F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 @@ -457,7 +464,7 @@ F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2 F ext/session/sessionsize.test 6f644aff31c7f1e4871e9ff3542766e18da68fc7e587b83a347ea9820a002dd8 F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5 F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c af0cb1163e224b286a9cba3c1ad877d927821e6a0e317c29b289a16e5da378a8 +F ext/session/sqlite3session.c 1d019c5caf51936ef24c761db63552b06e0e0d951c8740bba9639b17fa0cb107 F ext/session/sqlite3session.h 0907de79bc13a2e3af30a6dc29acc60792a3eaf7d33d44cf52500d0f3c2b2171 F ext/session/test_session.c f433f68a8a8c64b0f5bc74dc725078f12483301ad4ae8375205eef790274a787 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 @@ -466,7 +473,7 @@ F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk c0c95f0aa2e3b731eb0c5a85869d4d6abef91647ec8e87a4013059bd1b910c0a +F main.mk 002e77acdfeb08d1d8f4d360b01e130aa243fb5701728e81fac9085794f27155 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -478,45 +485,45 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a -F src/alter.c 774a2a175ec747b55046ec62f5c1208d730ee22b10bdad5f182591fa247946a8 -F src/analyze.c 01c6c6765cb4d40b473b71d85535093730770bb186f2f473abac25f07fcdee5c -F src/attach.c a514e81758ba7b3a3a0501faf70af6cfc509de8810235db726cfc9f25165e929 -F src/auth.c 08954fdc4cc2da5264ba5b75cfd90b67a6fc7d1710a02ccf917c38eadec77853 +F src/alter.c 23743384e59f9d36df870ce41adfdf7934fd0adb619d7fa6fd1aac77c28a7533 +F src/analyze.c 7518b99e07c5494111fe3bd867f28f804b6c5c1ad0703ec3d116de9bab3fa516 +F src/attach.c e3f9d9a2a4a844750f3f348f37afb244535f21382cbfcd840152cb21cb41cfaf +F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf F src/backup.c 3014889fa06e20e6adfa0d07b60097eec1f6e5b06671625f476a714d2356513d -F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33 +F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6 -F src/btree.c 399e1ebcd6c4f9ad47f5457bfe3623441db287f0923433cf6539497791557be8 -F src/btree.h 096cc53baa58be22b02c896d1cf933c38cfc6d65f9253c1367ece8cc88a24de5 -F src/btreeInt.h 7bc15a24a02662409ebcd6aeaa1065522d14b7fda71573a2b0568b458f514ae0 -F src/build.c ea28944f65b04f771e7aa5d614d406faa1bde5fe4ce882e2ead73049f03ed568 -F src/callback.c d0b853dd413255d2e337b34545e54d888ea02f20da5ad0e63585b389624c4a6c +F src/btree.c 012772a0cbd1c8ee7ac34f5a9ff9861de989725cb26e7a07afbbb2d43deeccd9 +F src/btree.h 74d64b8f28cfa4a894d14d4ed64fa432cd697b98b61708d4351482ae15913e22 +F src/btreeInt.h ee9348c4cb9077243b049edc93a82c1f32ca48baeabf2140d41362b9f9139ff7 +F src/build.c c46bd4f5a69f398410c4472f7c1c4291fb8078d2c9758a2dad5916edd1d30ecc +F src/callback.c 106b585da1edd57d75fa579d823a5218e0bf37f191dbf7417eeb4a8a9a267dbc F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c 3052b6b05ed9ef547a3dd66b8e01bfa9d582e78752ad6ed327da84652641e038 -F src/date.c e0632f335952b32401482d099321bbf12716b29d6e72836b53ae49683ebae4bf +F src/ctime.c 8159d5f706551861c18ec6c8f6bdf105e15ea00367f05d9ab65d31a1077facc1 +F src/date.c fa928630fecf1d436cdc7a7a5c950c781709023ca782c21b7a43cc7361a9451e F src/dbpage.c 8a01e865bf8bc6d7b1844b4314443a6436c07c3efe1d488ed89e81719047833a -F src/dbstat.c 3aa79fc3aed7ce906e4ea6c10e85d657299e304f6049861fe300053ac57de36c -F src/delete.c 62451bba9fe641159e9c0b7d9d2bab1c48d0cff11e16de2d14000603d2af1fcf -F src/expr.c 30a2abf526531ce6bd45fbc85bfec0fc3f6e5a0fb490cd2350855f2fc34dd789 +F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d +F src/delete.c 0c151975fa99560767d7747f9b60543d0093d9f8b89f13d2d6058e9c83ad19e7 +F src/expr.c 827179c78d2ca7cc318392811de8151c60eacf7ce804b13e61bb7ef38f954846 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c e9063648396c58778f77583a678342fe4a9bc82436bf23c5f9f444f2df0fdaa4 -F src/func.c 9eb67f0aaf1cf439c21d6fc8afe270973d6e8345af3f1ebda98ad42186d30f5b -F src/global.c 25ba4d58476f6be29bba9d9d14f7f146b78476d3a4d75ebb8c3b736328afe0f9 +F src/fkey.c 5b73f7a7c00f06017531a5bd258cbc2c7a294e55a7f84a729fe27aa525242560 +F src/func.c 1cfb09d7ffca81238eccefdb0293e1f5b7cfebbd1816dfad5ec6024742a7496b +F src/global.c 1f56aead86e8a18c4415638f5e6c4d0a0550427f4b3f5d065ba5164cc09c22e8 F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19 -F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38 +F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 2189e0e596010a0dc5405d9f14f78db1ee2fa71138c931f5b6ea96610b95bfc1 +F src/insert.c 04c9b133be6152b4f229aab8e38230b3bf6d2e7e76455a893bff5f70e566237b F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 12684b3f19cd103cea97cdf14d0758196d0c646e12a898d7245141a9abfde9a4 -F src/main.c 9c06f8dc6137aaa990692ce30db1c00c3e6a574c5a4756640625c19b507c65ec -F src/malloc.c cbc93cdd429c4594912017d92ab656e2579aca64dbd1c6888551275bed46f25b +F src/loadext.c e1dcff1c916bf6834e150b492eddda5d9792453182d2ad64294d2266b6e93c4c +F src/main.c 1ea70751e6005ab6a9f784730fa0919efaa6639440a287deb73cb711e5aae57a +F src/malloc.c ef796bcc0e81d845d59a469f1cf235056caf9024172fd524e32136e65593647b F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de -F src/mem2.c b93b8762ab999a29ae7751532dadf0a1ac78040308a5fb1d17fcc365171d67eb +F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944 -F src/memdb.c 2f2e8efc6e531c59cf8255f0bf4ad81f2e88e0a394581244154c8cf5141757ce -F src/memjournal.c a85f0dc5c02a42453d0bc3819ecfb5666cb6433e5deefcd93ccbe05c9f088b83 +F src/memdb.c c2dc88f97c410eb68a24468344b65526685e18354ddfd15906750c1eaf9dc2dd +F src/memjournal.c ff4336a98b05ede2adee7595f22d6f7d1cdc6bf0f0a5c3d77b0acdf017b2e8b2 F src/msvc.h 3a15918220367a8876be3fa4f2abe423a861491e84b864fb2b7426bf022a28f8 F src/mutex.c 5e3409715552348732e97b9194abe92fdfcd934cfb681df4ba0ab87ac6c18d25 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a @@ -524,37 +531,37 @@ F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 -F src/os.c 59ed1f503347e8b5434c0ce7d7d0f02a3f24a72fea8b26d0bba2de8dfaef778b +F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 -F src/os_unix.c feac74a0ba7652c405ba30f61d9e738be717d15899f2915a129ef160a3e5b26b +F src/os_unix.c f5ad51cfd024116db8531feab9efd831c2621436dca1464e4ff1e8af9bf3252e F src/os_win.c 77d39873836f1831a9b0b91894fec45ab0e9ca8e067dc8c549e1d1eca1566fe9 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 95c255256b13827caf038c8f963d334784073f38ab6ef9d70371d9d04f3c43e0 +F src/pager.c 5b79a1c09bc19628a763c822ef96326832090703a6608c64dc5fc166ab8dda79 F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f -F src/parse.y 8920f4444957d7827ca458029b2e41ffa32dd3b72917be0b52cae0aace3eadb5 -F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177 +F src/parse.y 0bd7971a7489bbf6c3726f1b50da6e508bdff8fa493e9cc3f5a96b12cbb2361e +F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 -F src/pcache1.c 388304fd2d91c39591080b5e0f3c62cfba87db20370e7e0554062bfb29740e9f -F src/pragma.c eb42cb9bec189cf18cef5d8fcae56e13bb73ef2b019b198fb48740ced81bce95 -F src/pragma.h 8dc78ab7e9ec6ce3ded8332810a2066f1ef6267e2e03cd7356ee00276125c6cf -F src/prepare.c 0d53d20532aada295c1690792a125adbd6435f5ce703ff0adf1b9b3605238b67 -F src/printf.c 78fabb49b9ac9a12dd1c89d744abdc9b67fd3205e62967e158f78b965a29ec4b +F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65 +F src/pragma.c c536665ce8431c8b1efbf7e0a5c01852f49f7bf28f1954f8118b2d28e4a3797f +F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad +F src/prepare.c dab0c2995a33ee2c458354cb7dd13b2b33362425c52301e41d833add7660e7ca +F src/printf.c 5901672228f305f7d493cbc4e7d76a61a5caecdbc1cd06b1f9ec42ea4265cf8d F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c -F src/resolve.c 35630effd4d16d2373caa41bae40a3d71f853f3ad0cb4f572f2ed4b8c350c1e9 +F src/resolve.c 4a1db4aadd802683db40ca2dbbb268187bd195f10cbdb7206dbd8ac988795571 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 4fa607bab6bcc580f12dbaf9c800b2250a1e408f10321a1d3bcb1dd30c447e62 -F src/shell.c.in f3e91c697f33daae14923355dcadfc26bb0c1eabdb343e5508706893e017a0a6 -F src/sqlite.h.in 8549395aa13361d5dcea04e3a57d680af10f7ee0989c45ebfffe3a3bcb2cdc3d +F src/select.c a7a3d9f54eb24821ec5f67f2e5589b68a5d42d46fc5849d7376886777d93a85a +F src/shell.c.in 1458b700144c8326fda2514aaddeda49d6f01f1d1ccf7b9b696c53a3535a119c +F src/sqlite.h.in 5cd209ac7dc4180f0e19292846f40440b8488015849ca0110c70b906b57d68f0 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h 61b38c073d5e1e96a3d45271b257aef27d0d13da2bea5347692ae579475cd95e -F src/sqliteInt.h ab37a8dd95884fc25ef1eb7b6d53d6674905c67298affc2b5bf947f739096504 +F src/sqlite3ext.h 8ff2fd2c166150b2e48639f5e506fb44e29f1a3f65031710b9e89d1c126ac839 +F src/sqliteInt.h 4bf21edf5c330299d1b7399d604da1787001725dbb1c675fe0989ceb1ee8043f F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 97645e4a15dde6a6ad6de8d81057ff9869413b866015a89e208fedacd709493e -F src/test1.c 2100f4c28bae21ce83a9a0c5ec6827efd0e15d11b93b569b614daa5654b3fcf6 +F src/tclsqlite.c 48f291e1a7e672a7204884d4c164a8ed3a522ff087c361ada2991f5d54e987f6 +F src/test1.c d6c945a8bb211ed72ea515f0b9743caeaf38c66da6418a7b1dcf3764e1368bbb F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159 @@ -569,7 +576,7 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 -F src/test_config.c 9c8e12823c46082a01765addf43be9309889f4e9dfb5a512a6c974e1c4efb413 +F src/test_config.c 284c29912736f68b0a583a920bf63fd8f9125dffb8a75cb0676e58502b2f7908 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f F src/test_demovfs.c 86142ba864d4297d54c5b2e972e74f3141ae4b30f05b3a95824184ed2d3d7f91 F src/test_devsym.c aff2255ea290d7718da08af30cdf18e470ff7325a5eff63e0057b1496ed66593 @@ -583,7 +590,7 @@ F src/test_journal.c a0b9709b2f12b1ec819eea8a1176f283bca6d688a6d4a502bd6fd79786f F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd F src/test_malloc.c 21121ea85b49ec0bdb69995847cef9036ef9beca3ce63bbb776e4ea2ecc44b97 F src/test_md5.c 7268e1e8c399d4a5e181b64ac20e1e6f3bc4dd9fc87abac02db145a3d951fa8c -F src/test_multiplex.c 2ccf35551c094e353af20b0cdfac053a37bf3e96e10e7cf9f4abd1d279890a78 +F src/test_multiplex.c 1b23782212a01349fac382913ef82b8de4ae8a4cb46556602c2ee733edabbbc9 F src/test_multiplex.h 5436d03f2d0501d04f3ed50a75819e190495b635 F src/test_mutex.c abf486e91bd65e2448027d4bb505e7cce6ba110e1afb9bd348d1996961cadf0d F src/test_onefile.c f31e52e891c5fef6709b9fcef54ce660648a34172423a9cbdf4cbce3ba0049f4 @@ -597,7 +604,7 @@ F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939 -F src/test_tclsh.c eeafce33ad2136d57e5dec10f1e9a4347447eb72ffd504a1c7b9c6bfe2e71578 +F src/test_tclsh.c c4065ced25126e25c40122c5ff62dc89902ea617d72cdd27765151cdd7fcc477 F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc F src/test_thread.c 269ea9e1fa5828dba550eb26f619aa18aedbc29fd92f8a5f6b93521fbb74a61c F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43 @@ -608,63 +615,64 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c bae853ad129d1129c063de8630a3e99e306283bc40146f359b1bb91be2c08f1e -F src/treeview.c 21449a944d5abd3ac07b7eab76f7221e20c9480d06f01798f8dc66d9bc361769 -F src/trigger.c e0fd347b2571a2d956318cdc6d011ccca7ce862d10a0ca04188a37920ef5440c -F src/update.c 56fa0458b1ffc1042629f926443e8ed44203983df3ab2b0db2ba556e6ceed68c -F src/upsert.c df8f1727d62b5987c4fd302cd4d7c0c84ae57cd65683c5a34a740dfe24039235 +F src/tokenize.c 865911afa00fed589cd03b25c140ca88544842aaef7b81f7d41ed769a7a54120 +F src/treeview.c 9dfdb7ff7f6645d0a6458dbdf4ffac041c071c4533a6db8bb6e502b979ac67bc +F src/trigger.c 043d66ecb25a223c614681c8ab758f1aaf6e507c901d3a4668113afab1cc2dc7 +F src/update.c 69c4c10bc6873a80c0a77cb578f9fc60ee90003d03f9530bc3370fa24615772d +F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c 41c7a72da1df47864faa378a1c720b38adb288c6838cb6be5594511b6287a048 -F src/vacuum.c 492422c1463c076473bae1858799c7a0a5fe87a133d1223239447c422cd26286 -F src/vdbe.c 6ece0ee94fa3a95601372efdc4ec6d068cd6cc58e28af8852f3407b90be4b7f8 +F src/util.c 30df8356e231dad33be10bb27897655002668343280004ba28c734489414a167 +F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 +F src/vdbe.c 84cc51edc36f773a97433c0a1388833557806f56562b6a2cb9fefeadc5e236b0 F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe -F src/vdbeInt.h 465fcb494db4ca6630fb9c19b2f3dfed597fbe885b0d4204193a5093b0dd7dc6 -F src/vdbeapi.c aa5aaf2c37676b83af5724c6cd8207a3064ed46a217fd180957f75ac84f7a2a5 -F src/vdbeaux.c 065a10392378109f08435bd50d03dff315e384cde2831d6b8dbaec05f33b10af -F src/vdbeblob.c c6b8db50b227f66fb404215732068df76485b5b433e5f9d4d9ac27410b218193 -F src/vdbemem.c 53881aa0a7845922a075b3f375695588618098871a7a4120af4c297b80fa3e64 -F src/vdbesort.c cd5130f683706c1a43e165a74187745fb3351cb56052cf9dc91de820634bbde2 -F src/vdbetrace.c 666c6fd9f1b62be6999e072a45b913e3c2c3518bc60dfd4d54fe304130acb724 +F src/vdbeInt.h 31fbabdc1ed61d9695337dfe5269ea94e1cf615c17f5cafeaa1bb01066820bab +F src/vdbeapi.c 22c79072ae7d8a01e9bcae8ba16e918d60d202eaa9553b5fda38f99f7464d99a +F src/vdbeaux.c 9e16b5cb02f9e95df008369880332b6551feea6a3495b44c20da96c381508042 +F src/vdbeblob.c 29c4118f7ee615cdee829e8401f6ead1b96b95d545b4de0042f6de39c962c652 +F src/vdbemem.c a3d91dc9bb9ef725db77e4e9de7e1acef43192c9f8406c307665d503e3c2837c +F src/vdbesort.c 513b481c8bab4a6578c92194a60cf3bc3b48736e4a53f8d2d7918121c5b594e7 +F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c -F src/vtab.c b928405ccb66040fc6c3a11eaa93ddb02cbf20f9ab6860b301b222b9b50dc089 +F src/vtab.c 9d5c3f49d3a6959b6eef287bb8fa773563102a80a835c3314c57144412709e78 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c c8ec20a1ca161d5635a4f19c2a4efec2e006e19a8a61f272bf6bce1c80ab7436 +F src/wal.c ed0398a7adf02c31e34aada42cc86c58f413a7afe5f741a5d373ad087abde028 F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a -F src/walker.c 7342becedf3f8a26f9817f08436bdf8b56ad69af83705f6b9320a0ad3092c2ac -F src/where.c 4caf843825c6292440113258fefaa6f747fd570c6915628957e42e39529988f5 -F src/whereInt.h 9248161dd004f625ce5d3841ca9b99fed3fc8d61522cf76340fc5217dbe1375b -F src/wherecode.c 9f1f65d11437b25cd0a1497a170514c785f19ce6ad9d3e6fc73719cb5a49012f -F src/whereexpr.c 5a9c9f5d2dac4bcdcaae3035034b4667523f731df228e0bb1d4efc669efa9da5 -F src/window.c a6d624d83b2d5b3cfb82bb437a2fbae759c928d47dc9ad1338a9419269181bb2 +F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b +F src/where.c de0d4ff409c7b62a8803f9f267cc2c7fedddbc00de9ab7b5382c507383c18665 +F src/whereInt.h 83877a75a1bce056ea44aff02f1dfa958ad1d6038c213ddadb8652003b45151d +F src/wherecode.c 1f5b62f46d284c8886945eb7438415bc27e23e87bb60b9ee468fa6bd31268f33 +F src/whereexpr.c 17bdbf4f5b490e70a18635498f0b910a558f953a9bf80af7f19cbde6e60e6825 +F src/window.c 5d3b397b0c026d0ff5890244ac41359e524c01ae31e78782e1ff418c3e271a9e F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test eecb0dabee4b7765a8465439d5e99429279ffba23ca74a7eae270a452799f9e7 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 -F test/aggnested.test 2f65ec8132e0ca896de550b9908094d49ad65a99116a9d79deeb6017604ad4f6 +F test/aggnested.test cc47afa5e11e0d6771a85a4993fa6ff721480ddb53ea538ec3fdbafb720bd505 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 -F test/alter.test f53d8a4ecd35f051c07e54a36beec5d0a30d30a9d98bc723f6cde6afbfb3c5ca +F test/alter.test 313073774ab5c3f2ef1d3f0d03757c9d3a81284ae7e1b4a6ca34db088f886896 F test/alter2.test a966ccfcddf9ce0a4e0e6ff1aca9e6e7948e0e242cd7e43fc091948521807687 -F test/alter3.test e487958dec7932453e0b83baf21d6b1e71d5e7d9a55bc20eadfa62a51ddffc29 +F test/alter3.test ffc4ab29ce78a3517a66afd69b2730667e3471622509c283b2bd4c46f680fba3 F test/alter4.test 716caa071dd8a3c6d57225778d15d3c3cbf5e34b2e84ae44199aeb2bbf50a707 F test/alterauth.test 63442ba61ceb0c1eeb63aac1f4f5cebfa509d352276059d27106ae256bafc959 F test/alterauth2.test 381b1ab603c9ef96314a3158528ea17f7964449385a28eeaf8191120b2e24a8d F test/altercol.test b11fa1b131e80ab5b6ecfb3b725fb0419c14ca6efba5adb57aeabfc9baa0c8f3 -F test/altercorrupt.test 584d707a80e106952d6382790c8919bcf9f0db678ed3a1c09fd98b7f9d1d3a10 +F test/altercorrupt.test 2e1d705342cf9d7de884518ddbb053fd52d7e60d2b8869b7b63b2fda68435c12 F test/alterdropcol.test a653a3945f964d26845ec0cd0a8e74189f46de3119a984c5bc45457da392612e F test/alterdropcol2.test 527fce683b200d620f560f666c44ae33e22728e990a10a48a543280dfd4b4d41 +F test/alterfault.test 289067108947bedca27534edd4ff251bcd298cf84402d7b24eaa3749305418c6 F test/alterlegacy.test f38c6d06cda39e1f7b955bbce57f2e3ef5b7cb566d3d1234502093e228c15811 F test/altermalloc.test 167a47de41b5c638f5f5c6efb59784002b196fff70f98d9b4ed3cd74a3fb80c9 F test/altermalloc2.test ca3ebc01670d9313953a2b7628d8cc00dc5ea9988f229b3cbbbe1cca506dae45 -F test/altermalloc3.test 059841a3de6b6780efd9f0b30bf1d9b4443c555f68d39975cbcac2583167b239 -F test/alterqf.test 67568ad152db8c1187b15633b801242cf960f1beafc51261a3d1725d910baeb2 -F test/altertab.test 466b9757630d68455080690b6e99c5ff11b199df4b3530e17f3df10a63aa05d3 +F test/altermalloc3.test 4660ac6240a8c82ba3947b927612dcc7c05a8eec3fe3c9f38e047ca69a789a33 +F test/alterqf.test 6b2482a957692606b23567ebd2cf80eb773e3c826086f5f151eee9c5a962623d +F test/altertab.test 7273b8506eab46342be016af78028df49f3bd99037412f997a8f1011b37a6912 F test/altertab2.test b0d62f323ca5dab42b0bc028c52e310ebdd13e655e8fac070fe622bad7852c2b -F test/altertab3.test 2b82fa2236a3a91553d53ae5555d8e723c7eec174c41f1fa62ff497355398479 +F test/altertab3.test 5929f522fd6fd708396ad9f317d4af9ff1a93e460df85bb1d54d4499eeb94960 F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 547bb700f903107b38611b014ca645d6b5bb819f5210d7bf39c40802aafeb7d7 F test/analyze3.test 4440c4932247adb2b4e0c838f657c19dc7af4f56859255436dc4e855f39b9324 -F test/analyze4.test 293ec8ea21525f3435beaf9d6b1efb29e719415f03a0cd2a70589d59098e8464 +F test/analyze4.test 68bd069f3ac7ac1e652ddd9f04f57d5606ddb4208450f5297005db7aa0dd707d F test/analyze5.test fa5131952303ac4146aba101b116b9c8cb89e2637531c334a6df7f7d19dddc0d F test/analyze6.test 028f5bdfc9e5b5294768fa9a7185b8cd1d019aa7aab5b2f8ee42d7271d9a3b28 F test/analyze7.test 079d17c495e396bdbd6cc6a083112788a6fbfb3b95c42e760e4270a53c9ead8f @@ -681,7 +689,7 @@ F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7 F test/async4.test 1787e3952128aa10238bf39945126de7ca23685a F test/async5.test 383ab533fdb9f7ad228cc99ee66e1acb34cc0dc0 -F test/atof1.test 10049623e77006691c4c2978c1dc8a3f75276377a53417811aa85bda7493f963 +F test/atof1.test 77e8517df10cb39f2e4e65dbefe1e81c8d8e65f29f68b09265ca6e534c68227e F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d061 F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c @@ -692,15 +700,16 @@ F test/attach4.test 00e754484859998d124d144de6d114d920f2ed6ca2f961e6a7f4183c714f F test/attachmalloc.test 12c4f028e570acf9e0a4b0b7fe6f536e21f3d5ebddcece423603d0569beaf438 F test/auth.test 567d917e0baddb6d0026a251cff977a3ab2c805a3cef906ba8653aafe7ad7240 F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 -F test/auth3.test db21405b95257c24d29273b6b31d0efc59e1d337e3d5804ba2d1fd4897b1ae49 +F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec F test/autoinc.test 997d6f185f138229dc4251583a1d04816423dddc2fc034871a01aeb1d728cb39 F test/autoindex1.test fe27af92eaf884bd9c38f94be3e8afa04ec494e5eefb189902026181a6175f5e F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df F test/autoindex3.test 2d13958a5617e987624a428d7aed91bf51f322b49b476e3573fadec697ce6da5 F test/autoindex4.test 49d3cd791a9baa16fb461d7ea3de80d019a819cf -F test/autoindex5.test ee0aa95e3f44190347a29a672c3d4ecf042e3f33c5fa3dc29f0ab6be057bbded -F test/autovacuum.test 0831cd34e14695d297187f7f6519265e3121c5b0a1720e548e86829e796129e9 +F test/autoindex5.test 2ee94f033b87ca0160e08d81034c507aff8e230df2627f0304fa309b2fee19a3 +F test/autovacuum.test 00671369bbf96c6a49989a9425f5b78b94075d6a4b031e5e00000c2c32f365df +F test/autovacuum2.test 76f7eb4fe6a6bf6d33a196a7141dba98886d2fb53a268d7feca285d5da4759d7 F test/autovacuum_ioerr2.test 8a367b224183ad801e0e24dcb7d1501f45f244b4 F test/avfs.test 0c3a38e03cccb0fc3127838462dc05dc3f4c1480d770c084b388304c25de3652 F test/avtrans.test b7dc25459ecbd86c6fa9c606ee3068f59d81e225118617dcf2bbb6ded2ade89e @@ -714,7 +723,7 @@ F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399 F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c -F test/bestindex1.test 2e92e046148b3f8a2be62af9c0f307413547a33677af0df159c0fcc76e5134a2 +F test/bestindex1.test 7cc626f1f4a7483bb6b38487d467db4477083be5cd93958aeda5d5127640dc81 F test/bestindex2.test 60266e2854055788459cbfd86cef575601eabe74a2c61faba72601739fea4398 F test/bestindex3.test e061a6ed0f519beee037ba7e7a4c37f80c8a7e4a303e2559ed1f760e4b0235eb F test/bestindex4.test 82250e7dcc6d5b15244edc9d6554b1760583af1b8548c2a255a1c4f28e744c0e @@ -747,15 +756,16 @@ F test/busy2.test dbfb61b3265e7a962d3bcd32cd542bbe3d7801edbda6438d35af5aa707cae9 F test/cache.test 13bc046b26210471ca6f2889aceb1ea52dc717de F test/cacheflush.test af25bb1509df04c1da10e38d8f322d66eceedf61 F test/cachespill.test 895997f84a25b323b166aecb69baab2d6380ea98f9e0bcc688c4493c535cfab9 -F test/capi2.test 34a1a9a96d543a2ec2c209696b11b164444f57253b1f2cba1c2e53fadede6c7b +F test/capi2.test 4ee545824adc3eb33bf57ef89f77440b28188ec3da72e5425ff0fcdba32e8d5a F test/capi3.test 3910a73c38ac76d69778dd9eb481ab7cd6ed59117fc047b4f6056a5c72529de1 F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4 F test/capi3c.test 54e2dc0c8fd7c34ad1590d1be6864397da2438c95a9f5aee2f8fbc60c112e44b F test/capi3d.test aba917805573a03deed961a21f07a5a84505ad0a616f7e3fc1508844a15bccc4 F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe -F test/carray01.test 3f2658bbddd75a013735a296ae2178ff441aca3f00ba623cfbae00b732ede792 +F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212a2634 F test/cast.test 336fa21989b5170ebcaf90c24266be22dd97b3e23d1fad5ecf6ad4efb04c4423 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef +F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b F test/check.test 4a2a91ed67eee84a6be16057c48d5198b6fb24849cd6da6cd855981de3fbb416 F test/checkfault.test da6cb3d50247169efcb20bdf57863a3ccfa1d27d9e55cd324f0680096970f014 F test/chunksize.test 427d87791743486cbf0c3b8c625002f3255cb3a89c6eba655a98923b1387b760 @@ -775,7 +785,7 @@ F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6 F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151ecac0b95 F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1 F test/colname.test 87ad5458bb8709312dac0d6755fd30e8e4ca83298d0a9ef6e5c24277a3c3390e -F test/columncount.test d86fb6307261186370698962790ad2088ed419e4a4e823512b502f17d443b1b7 +F test/columncount.test 6fe99c2f35738b0129357a1cf3fa483f76140f4cd8a89014c88c33c876d2638f F test/conflict.test ac0667090f66130ac77d5fb764655558ca6600dd6d88f670ca9123b61c448337 F test/conflict2.test 5557909ce683b1073982f5d1b61dfb1d41e369533bfdaf003180c5bc87282dd1 F test/conflict3.test 81865d9599609aca394fb3b9cd5f561d4729ea5b176bece3644f6ecb540f88ac @@ -800,11 +810,11 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test a17bbf54fdde78d43cf3cc34b0057719fd4a173a3d824285b67dc5257c064c7b F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test df132ba9ffd6fa15038380b4154998b9904ab8f1ea78400d7da53c920cb3b13d +F test/corruptL.test 7d3440831ca24ba64305583c4d4506d417d3f89f5775c0b7cc8102db078f8ff5 F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 -F test/corruptN.test c2a96ff81386027f7d7e95858783aa36f82ba1532106969575e3c8f90903a5bb +F test/corruptN.test 60b5a62944b4f0029ba07edaa5fd8e670539d6b0a8d99db26c068d435675cbfe F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 -F test/count.test 5364003488249957750a5f15ee42ca1cd7b100b1131c2dc71fff266a1250bf55 +F test/count.test 013d64569c15563c59472d17ec4ddc681034aaee65e8b01880bc88315fd4c673 F test/countofview.test e17d6e6688cf74f22783c9ec6e788c0790ee4fbbaee713affd00b1ac0bb39b86 F test/coveridxscan.test f35c7208dedc4f98e471c569df64c0f95a49f6e072d8dc7c8f99bdee2697de1b F test/crash.test fb9dc4a02dcba30d4aa5c2c226f98b220b2b959f @@ -836,7 +846,7 @@ F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test fcf403fd5585f47342234e153c4a4338cd737b8e0884ac66fc484df47dbcf1a7 F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50 -F test/delete.test 438c14fdf2f2a520b63f892ea645896acf8832e2a2308578593d8e05274314b1 +F test/delete.test 31832b0c45ecb51a54348c68db173be462985901e6ed7f403d6d7a8f70ab4ef0 F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab F test/delete4.test 51fafebe9503a40796d1aae1565c60524cada720e50eecac01b7fd0419d9ea0b @@ -848,26 +858,26 @@ F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e F test/distinct.test 3e4210ef9cd1985aeec44939ad912c4621fbea9bb4a9c565696cebfe184b2ec5 F test/distinct2.test cd1d15a4a2abf579298f7161e821ed50c0119136fe0424db85c52cf0adc230d1 F test/distinctagg.test d76ef2e91fe810630c176d6bd0a58c14d5851c3125f0a1d977db87ba76359639 -F test/e_blobbytes.test 439a945953b35cb6948a552edaec4dc31fd70a05 -F test/e_blobclose.test 4b3c8c60c2171164d472059c73e9f3c1844bb66d -F test/e_blobopen.test e95e1d40f995056f6f322cd5e1a1b83a27e1a145 -F test/e_blobwrite.test f87ff598b67af5b3ec002a8d83e804dc8d23808e88cf0080c176612fc9ffce14 -F test/e_changes.test fd66105385153dbf21fdb35eb8ef6c3e1eade579 -F test/e_createtable.test 7997c0106c181243e0ac7db7ba8b9ae7233d0bfb0188605650322a7a02ea326e +F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 +F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075 +F test/e_blobopen.test 29f6055ee453b8e679fe9570c4d3acfedbef821622c5dad16875148c5952ef50 +F test/e_blobwrite.test 3075ff539827576d9a34cbb5a2ac75eb65fb49cd5aadc27686b0719fbf99c156 +F test/e_changes.test 0f8c3e6aab7335cb772d5a3ea34ca4c82f98d0eb896e2eb3add971c16984b405 +F test/e_createtable.test 04c50b7fe41c12ed9cd88fbbc09b4900bcfc66f98ad198874fc993a2771f3913 F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7 -F test/e_expr.test 6ba7a51ece7b3e7fc145f14f924eed25ebb5a24e7b8596c78f3838d372cf0385 -F test/e_fkey.test 351c7b989e5aefcc339ef5fc78dc4738442bd247a392cd67d81c2881000c369e +F test/e_expr.test e164550b9f8fd9c130283d1eae692dff9e2ba67f4dbd35f7325021f5d4b8851c +F test/e_fkey.test ee321dbca3c53204da46cb40b8ec80192e995035f9ea26576ddcd842b1f0877f F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e F test/e_reindex.test 2b0e29344497d9a8a999453a003cb476b6b1d2eef2d6c120f83c2d3a429f3164 F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 F test/e_select.test c5425a423da06d0494119db8361ebfc6de302929f7546ca596d56224137e0360 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f -F test/e_totalchanges.test b12ee5809d3e63aeb83238dd501a7bca7fd72c10 +F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f3b30e9b41e4 F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 -F test/e_uri.test 47eeb2960e74613f0f8722b2f13aef08fde69daa16e5380ac93df84dac8b1f72 +F test/e_uri.test a3d30c7d6d7700150055b6660ee42da7f05dee2c150ce08bf6a9ededc26468cc F test/e_vacuum.test 0d8832a2ce92350db0d0cff47b419465fd9772562e1f77ff7d9478c07a4980d2 F test/e_wal.test ae9a593207a77d711443ee69ffe081fda9243625 F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8 @@ -898,7 +908,7 @@ F test/filter1.test 6c483ecf7886c8843a8612c021aa23f33c581f584151f251842b3a3592c9 F test/filter2.tcl 44e525497ce07382915f01bd29ffd0fa49dab3adb87253b5e5103ba8f93393e8 F test/filter2.test 485cf95d1f6d6ceee5632201ca52a71868599836f430cdee42e5f7f14666e30a F test/filterfault.test c08fb491d698e8df6c122c98f7db1c65ffcfcad2c1ab0e07fa8a5be1b34eaa8b -F test/fkey1.test d11dbb8a93ead9b5c46ae5d02da016d61245d47662fb2d844c99214f6163f768 +F test/fkey1.test 55663090ab6735319a52647057b9f19f8ec8c6c7d7da25170b71a75e3e5bdeb7 F test/fkey2.test 1063d65e5923c054cfb8f0555a92a3ae0fa8c067275a33ee1715bd856cdb304c F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49 F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d @@ -972,7 +982,7 @@ F test/fts3conf.test c84bbaec81281c1788aa545ac6e78a6bd6cde2bdbbce2da261690e3659f F test/fts3corrupt.test 79a32ffdcd5254e2f7fa121d9656e61949ad049c3c6554229911b7ceac37c9c6 F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0 F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cdb764b491f -F test/fts3corrupt4.test 1b3333822577b0888c95de8490a1a6152c47cb33a763fe62c54825202c31812f +F test/fts3corrupt4.test 799ff994b964fed7201be6b6b62c7ff2ef7bb3da6c02b9eaf0d96a5a4d9b6ca3 F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5 F test/fts3corrupt6.test 657b4b8e5791d8d4adc93c90588fb25f1c7346544dd877c6c298a0746749146d F test/fts3cov.test 7eacdbefd756cfa4dc2241974e3db2834e9b372ca215880e00032222f32194cf @@ -987,6 +997,7 @@ F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a F test/fts3expr3.test c4d4a7d6327418428c96e0a3a1137c251b8dfbf8 F test/fts3expr4.test f5b2832549f01b1f7f73389fa21d4b875499bc95bf7c8b36271844888c6a0938 F test/fts3expr5.test a5b9a053becbdb8e973fbf4d6d3abaabeb42d511d1848bd57931f3e0a1cf983e +F test/fts3f.test 8c438d5e1cab526b0021988fb1dc70cf3597b006a33ffd6c955ee89929077fe3 F test/fts3fault.test 798e45af84be7978ca33d5bdc94246eb44724db24174b5d8e9b1ac46c57fb08d F test/fts3fault2.test 6a17a11d8034b1c4eca9f3091649273d56c49ff049e2173df8060f94341e9da0 F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641 @@ -997,7 +1008,7 @@ F test/fts3matchinfo.test aa66cc50615578b30f6df9984819ae5b702511cf8a94251ec7c594 F test/fts3matchinfo2.test 00144e841704b8debfcdf6097969cd9f2a1cf759e2203cda42583648f2e6bf58 F test/fts3misc.test 9ec15e7c0b5831a6353bd4c46bf3acdf1360eda5d9f396f667db4d05bcf92ecf F test/fts3near.test 7e3354d46f155a822b59c0e957fd2a70c1d7e905 -F test/fts3offsets.test b85fd382abdc78ebce721d8117bd552dfb75094c +F test/fts3offsets.test eb114b16e4d2495133bc2710d9c05ccd5ac90319718248fdf2dd379cdaa85358 F test/fts3prefix.test fa794eaab0bdae466494947b0b153d7844478ab2 F test/fts3prefix2.test e1f0a822ca661dced7f12ce392e14eaf65609dce F test/fts3query.test 45806a302921b245a9dba5d85c9d51fb98b3f137eea6e6bf6eae4883e041ab72 @@ -1034,9 +1045,9 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test 77f6ea02c97d9ea64074461d347276a75df22d2cf51045a40f90857569e985f0 +F test/func.test 3a65ddb6c1998f71aa86492501a6be87904197e62bfb5b70b2493552b558abd1 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f -F test/func3.test 2bb0f31ab7baaed690b962a88544d7be6b34fa389364bc36a44e441ed3e3f1e6 +F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 F test/func5.test 863e6d1bd0013d09c17236f8a13ea34008dd857d87d85a13a673960e4c25d82a F test/func6.test 90e42b64c4f9fb6f04f44cb8a1da586c8542502e926b19c76504fe74ff2a9b7c @@ -1048,7 +1059,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 37f09efcd982048694d8b06fa3ef817df17f2a82e90af85cb3fb8eddd0f57ec4 +F test/fuzzcheck.c bdb852815048a0beebbe5768bca61e75295324a811a8d7216f97b96501befd1e F test/fuzzdata1.db d36e88741b4f23bcbaaf55b006290669d03c6c891cf13c7b3a53bc1b097b693f F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1056,17 +1067,17 @@ F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e4 F test/fuzzdata5.db e35f64af17ec48926481cfaf3b3855e436bd40d1cfe2d59a9474cb4b748a52a5 F test/fuzzdata6.db 92a80e4afc172c24f662a10a612d188fb272de4a9bd19e017927c95f737de6d7 F test/fuzzdata7.db 0166b56fd7a6b9636a1d60ef0a060f86ddaecf99400a666bb6e5bbd7199ad1f2 -F test/fuzzdata8.db 9e0123ce3df47d1f159f7b71d60b96bd8b89800a40ab081c5402167d7239ead7 +F test/fuzzdata8.db ef4d280ee69d6da0ebda7f81c0c66839fa577a97b29cc5790a564fde88be7183 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c -F test/gencol1.test b05e6c5edb9b10d48efb634ed07342441bddc89d225043e17095c36e567521a0 +F test/gencol1.test cc0dbb0ee116e5602e18ea7d47f2a0f76b26e09a823b7c36ef254370c2b0f3c1 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 F test/having.test a89236dd8d55aa50c4805f82ac9daf64d477a44d712d8209c118978d0ca21ec9 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711 -F test/hook.test 55b6d605d06dadbb04416eae8ad79889aea3521d119c87f04353c25f9c1450a4 +F test/hook.test bb9e03b93fa4bc3bf0f164e455ddaee3603dee0501cb61119baed557c8f936be F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 F test/icu.test 716a6b89fbabe5cc63e0cd4c260befb08fd7b9d761f04d43669233292f0753b1 F test/ieee754.test b0945d12be7d255f3dfa18e2511b17ca37e0edd2b803231c52d05b86c04ab26e @@ -1074,15 +1085,15 @@ F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 F test/in.test 688ed2011d922d83141a45af431601738674a4c0bdde34b6351f688b82a169b3 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 -F test/in4.test addf1416fdbbcf2f082ab7974e4813f38b4759b95699a683a31c37a3fffe7b57 +F test/in4.test fdd1d8134da8376985c2edba6035a2de1f6c731524d2ffa651419e8fe2cd1c5a F test/in5.test b32ce7f4a93f44c5dee94af16886d922cc16ebe33c8e1765c73d4049d0f4b40f F test/in6.test f5f40d6816a8bb7c784424b58a10ac38efb76ab29127a2c17399e0cbeeda0e4b F test/incrblob.test c9b96afc292aeff43d6687bcb09b0280aa599822 F test/incrblob2.test a494c9e848560039a23974b9119cfc2cf3ad3bd15cc2694ee6367ae537ef8f1f -F test/incrblob3.test d8d036fde015d4a159cd3cbae9d29003b37227a4 +F test/incrblob3.test d47be78a46da142dd60b93772db6e03936a8a36a3b6129dff8d11923dfdc6d63 F test/incrblob4.test 21a52a6843a56cdcce968c6a86b72a7066d0e6ba F test/incrblob_err.test 89372a28f1d98254f03fed705f9efcd34ef61a674df16d2dbb4726944a2de5e9 -F test/incrblobfault.test 74dd8ac108304cea0b4a0df6df63a1567e558758 +F test/incrblobfault.test de274b1e329169c2c3438f9528994807ea8201ebf38ae9f157d34bf3ec0cc549 F test/incrcorrupt.test 6c567fbf870aa9e91866fe52ce6f200cd548939a F test/incrvacuum.test 3fa6145f5e71f603554fd7b8ec3da4290b1341029682313285cb5f9e1893d6ba F test/incrvacuum2.test 7d26cfda66c7e55898d196de54ac4ec7d86a4e3d @@ -1098,7 +1109,7 @@ F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6 F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3 F test/index9.test 0aa3e509dddf81f93380396e40e9bb386904c1054924ba8fa9bcdfe85a8e7721 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 -F test/indexexpr1.test 7e0e7a33acb4d9b3524398e6ce90cc05c26603fabbaf3062083a036c8874bc12 +F test/indexexpr1.test 8f7241410e351679010f14cd7cd30357622d04a784508ff54ba5ce99f64a2228 F test/indexexpr2.test 2c7abe3c48f8aaa5a448615ab4d13df3662185d28419c00999670834a3f0b484 F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 @@ -1115,14 +1126,14 @@ F test/interrupt.test 16ea879ec728cb76414c148c5f24afd5d1f91054 F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d F test/intpkey.test aee694afed1a65c86c4e69ad030224b3fc268113d00685234d40079fca16bad3 F test/intreal.test 2a87e85a5949bd55e41ef04c58f5800587c5380bdbc559ff1c79b614b0e6a533 -F test/io.test f95bca1783b01ea7761671560d023360d2dfa4cc -F test/ioerr.test 470fcc78e9cd352d162baf782fe301ea807d764241f58a48fc58109c2dfcdb6b +F test/io.test f138f3fe696d1ed8c51dfea5b325910d319a1b29e1d25ea57231a02092f02cca +F test/ioerr.test 530d05801ff1b6327018b2e140da34a74effa2773a844ddb8dc79c32e9567318 F test/ioerr2.test 2593563599e2cc6b6b4fcf5878b177bdd5d8df26 F test/ioerr3.test d3cec5e1a11ad6d27527d0d38573fbff14c71bdd F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b -F test/istrue.test 9619a2d77580f676048aaff7a16a0bcfea2b96c6c660dfaded2e53c873418899 +F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9 F test/join.test 25da4f53523a4aa17c893134b47fba6aa4799bb33350517b157785878290e238 F test/join2.test 9bdc615841b91c97a16d68bad9508aea11fa0c6b34e5689847bcc4dac70e4990 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 @@ -1138,7 +1149,7 @@ F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa F test/json101.test bb71538005f2d9e18620bdd3b76839a93ca0be61903eb8d751a64e78cf99b8fb F test/json102.test eeb54efa221e50b74a2d6fb9259963b48d7414dca3ce2fdfdeed45cb28487bc1 F test/json103.test aff6b7a4c17d5a20b487a7bc1a274bfdc63b829413bdfb83bedac42ec7f67e3b -F test/json104.test 317f4ec4b2d87afbba4d2460cf5be297aea76f2285eb618d276dbcd40a50950f +F test/json104.test 2cb7ff2cca2c8214d3e5260eeb9ce45faec0926f68b3e40c1aaa6ca247284144 F test/json105.test 45f7d6a9a54c85f8a9589b68d3e7a1f42d02f2359911a8cdbad1f9988f571173 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff F test/kvtest.c feb4358fb022da8ebd098c45811f2f6507688bb6c43aa72b3e840df19026317b @@ -1190,16 +1201,17 @@ F test/malloctraceviewer.tcl b7a54595270c1d201abf1c3f3d461f27eaf24cdef623ad08a0f F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7 -F test/memdb1.test 7b76c3262d63c46dd6b408d18f5721071776f2df4ffeb11e668824e427127594 +F test/memdb1.test 2c4e9cc10d21c6bf4e217d72b7f6b8ba9b2605971bb2c5e6df76018e189f98f5 F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c +F test/memjournal2.test 89a4e0d1084170a281efa4d54c2677599f986f44227f98f7dfae282802737b65 F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 F test/memsubsys1.test 9e7555a22173b8f1c96c281ce289b338fcba2abe8b157f8798ca195bbf1d347e F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08 -F test/minmax.test 0015e5cd5e7af48bb3364f26d9f3a9cdbea2a442d4774281c39e2229591b7351 +F test/minmax.test fe638b55d77d2375531a8f549b338eafcd9adfbd2f72df37ed77d9b26ca0a71a F test/minmax2.test cf9311babb6f0518d04e42fd6a42c619531c4309a9dd790a2c4e9b3bc595e0de F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 F test/minmax4.test 272ca395257f05937dc96441c9dde4bc9fbf116a8d4fa02baeb0d13d50e36c87 -F test/misc1.test 7ce84b25df9872e7d7878613a96815d2ba5bc974ac4e15a50118dde8f3917599 +F test/misc1.test e3fa5732080cc9a2b77bd5dd4ebb55bd6785b02565f8806092686b83ac58d600 F test/misc2.test 71e746af479119386ac2ed7ab7d81d99970e75b49ffd3e8efffee100b4b5f350 F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d F test/misc4.test 10cd6addb2fa9093df4751a1b92b50440175dd5468a6ec84d0386e78f087db0e @@ -1209,13 +1221,13 @@ F test/misc7.test d912f3d45c2989191b797504a220ca225d6be80b21acad22ba0d35f4a9ee45 F test/misc8.test 4db9f8be59834cea08c87e9658014080efa02678ef54a088f84fa5647e81fee0 F test/misuse.test 9e7f78402005e833af71dcab32d048003869eca5abcaccc985d4f8dc1d86bcc7 F test/mjournal.test 28a08d5cb5fb5b5702a46e19176e45e964e0800d1f894677169e79f34030e152 -F test/mmap1.test fb04e0c10492455007624ade884ca0c8852ff3e4e11d95408f9709ca2ef7f626 +F test/mmap1.test 5c1f768828094b0dd94e55ae7f10489a1ded74772682be2c4c78679d0acaf7ef F test/mmap2.test 9d6dd9ddb4ad2379f29cc78f38ce1e63ed418022 F test/mmap3.test b3c297e78e6a8520aafcc1a8f140535594c9086e F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 -F test/multiplex.test dc0d67b66f84b484a83cb8bbdf3f0a7f49562ccd +F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test d228f59eac91839a977eac19f21d053f03e4d101 F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4 @@ -1229,7 +1241,7 @@ F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 F test/notify3.test 10ff25cde502e72a92053a2f215d64bece4ef934 F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18 -F test/notnull2.test 131365171d973c57fe694fcc46c1e645f6e24ceb43b230ad5011a0ea2182f215 +F test/notnull2.test 8e1aa4f311df37f9d2cd4f5563eea955b8028c692151404e9cc896420a01a3dc F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5 F test/null.test b7ff206a1c60fe01aa2abd33ef9ea83c93727d993ca8a613de86e925c9f2bc6f F test/nulls1.test 7a5e4346ee4285034100b4cd20e6784f16a9d6c927e44ecdf10034086bbee9c9 @@ -1254,7 +1266,7 @@ F test/oserror.test 1fc9746b83d778e70d115049747ba19c7fba154afce7cc165b09feb6ca6a F test/ossfuzz.c 9636dad2092a05a32110df0ca06713038dd0c43dd89a77dabe4b8b0d71096715 F test/ossshell.c f125c5bd16e537a2549aa579b328dd1c59905e7ab1338dfc210e755bb7b69f17 F test/ovfl.test 199c482696defceacee8c8e0e0ef36da62726b2f -F test/pager1.test a4e438c344663ad7f0bf6e880cacae7531bdf7d960db15a3db4751273ecee06d +F test/pager1.test 8cb45ccbdb3ba423fc8158701c8f010a1d104336b8f14ef14bbfbadf14bad700 F test/pager2.test 67b8f40ae98112bcdba1f2b2d03ea83266418c71 F test/pager3.test 4e9a83d6ca0838d7c602c9eb93d1357562d9059c1e02ffb138a8271020838370 F test/pager4.test a122e9e6925d5b23b31e3dfef8c6a44bbf19590e @@ -1267,9 +1279,9 @@ F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035c F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 63da39a4234eed2ccd10bf7872de58e24d53a50d11014dc8a8ab9f252368e880 +F test/permutations.test 68b6dcd2667acdc643140d502c0b6c503abe444495cf5d16aa3a4f0391604020 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f -F test/pragma.test 50b91bedea9324d3ab48e793f908ee7d2c7dcf84bfa2281e792838be59641ec8 +F test/pragma.test cae534c12a033a5c319ccc94f50b32811acdef9f67bf19a82ff42697caccd69f F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31 F test/pragma4.test ca5e4dfc46adfe490f75d73734f70349d95a199e6510973899e502eef2c8b1f8 @@ -1286,7 +1298,7 @@ F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 F test/quota-glob.test 32901e9eed6705d68ca3faee2a06b73b57cb3c26 F test/quota.test bfb269ce81ea52f593f9648316cd5013d766dd2a F test/quota2.test 7dc12e08b11cbc4c16c9ba2aa2e040ea8d8ab4b8 -F test/quote.test b8ddaba6b81dcf63bb31243219e28a2f96e04396adc50108cc7e5593019c3eb5 +F test/quote.test f33f95990e4032d1227b98c0ef314c0a077d162f3f2e61b3039ed69e6f8adbbf F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459 F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 @@ -1295,18 +1307,18 @@ F test/recover.test ccb8c2623902a92ebb76770edd075cb4f75a4760bb7afde38026572c6e79 F test/regexp1.test 0c3ff80f66b0eff80e623eb5db7a3dad512095c573d78ac23009785f6d8f51ce F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest.tcl 6f803ef0b896f8f3f4c26eb072c0399963a5987a509a64d45f5dfbc1ebae2951 x -F test/releasetest_data.tcl f88ed29aa18366ed3956ace36c96ec6868ef5b9ee04cc05d32f4d81031e19e28 +F test/releasetest_data.tcl 7cea6c852ae6bb3a9ff1a2b910e4dd13c16a05f74443984dfd52159b0b01ea55 F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test 52b4ed60f104a4ce85b38417e5319b2078b2b908c71a768d539cee3c0ce8bdc9 +F test/returning1.test f96c7245f6ac16038e802760cd90b93479369939a8a7a44e2329ee5aed28239c +F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a F test/round1.test 768018b04522ca420b1aba8a24bd76091d269f3bce3902af3ec6ebcee41ab21e F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 -F test/rowid.test bfbd7b97d9267660be3c8f28507c4ed7f205196b8877c0db42df347c2e8845e3 +F test/rowid.test e29025be95baf6b32f0d5edef59a7633028325896a98f1caa8019559ca910350 F test/rowvalue.test 37effea4dd83555ea969a9461dfcffb25e6731a5db7c388e232410999c100853 F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256 @@ -1316,7 +1328,8 @@ F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 F test/rowvalue7.test c1cbdbf407029db01f87764097c6ac02a1c5a37efd2776eff32a9cdfdf6f2dba F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 F test/rowvalue9.test cb5380df82dca9db463081e26952c1e097b34fc2c2ac87453970c048d97df687 -F test/rowvaluefault.test 7cd9ccc6c2fbdd881672984087aad0491bb75504 +F test/rowvalueA.test 51f79b6098c193f838168752c9640f4eae6c63346bf64b5bed4f4e22fe2c71d0 +F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972d511c54fff F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 @@ -1330,7 +1343,7 @@ F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test 9a0ed37ab6d57b50567282788fffdf832d9b16739ecc41bff9d77a8d767cf317 F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 -F test/schema3.test 1bc1008e1f8cb5654b248c55f27249366eb7ed38 +F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce F test/schema4.test 3b26c9fa916abb6dadf894137adcf41b7796f7b9 F test/schema5.test 29699b4421f183c8f0e88bd28ce7d75d13ea653e F test/schema6.test e4bd1f23d368695eb9e7b51ef6e02ca0642ea2ab4a52579959826b5e7dce1f9b @@ -1369,21 +1382,21 @@ F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 56a7358a2a05e850e9e4aa24629db9c8975e8038dbe8debd2d95be22a5f03612 -F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b -F test/shell3.test ac8c2b744014c3e9a0e26bfd829ab65f00923dc1a91ffd044863e9423cc91494 +F test/shell1.test c354008b27c904f0166c2138abd7382013ea070b41114114ecbdfb32c726a807 +F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c +F test/shell3.test cb4b835a901742c9719437a89171172ecc4a8823ad97349af8e4e841e6f82566 F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f48be -F test/shell5.test 84a30b55722a95a5b72989e691c469a999ca7591e7aa00b7fabc783ea5c9a6fe +F test/shell5.test 6e4aa0e531dcb8dcf74b7920a2a7442c6712d4dff8422bbc81f768f9dee8a0e3 F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f -F test/shell8.test 96be02ea0c21f05b24c1883d7b711a1fa8525a68ab7b636aacf6057876941013 +F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 -F test/shrink.test 1b4330b1fd9e818c04726d45cb28db73087535ce +F test/shrink.test 9521e5e0d74c0b6192794f3de3a3e5e3190d465527ae365d96763ef753c7229c F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329 F test/skipscan1.test 1a9972e1dc15ca3887f306d3cd9a29679afb382eca0f3539f3b746f3c2ccaf68 -F test/skipscan2.test c588cb7ed947db724d300f2a0dc537dd2ad292b0f812641d8481bc0b95dd3f49 +F test/skipscan2.test b032ed3e0ba5caa4df6c43ef22c31566aac67783bc031869155989a7ccdb5bd5 F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 0672103fd2c8f96bd114133f356192b35ece45c794fe3677e1d9e5e3104a608e F test/skipscan6.test bddbb35dd335e2d21b7791a61e3b2e1f3255dc307ce80aa6fe19cc298e6feb13 @@ -1400,7 +1413,7 @@ F test/sort2.test cc23b7c19d684657559e8a55b02f7fcee03851d0 F test/sort3.test 1480ed7c4c157682542224e05e3b75faf4a149e5 F test/sort4.test 5c34d9623a4ae5921d956dfa2b70e77ed0fc6e5c F test/sort5.test 6b43ae0e2169b5ceed441844492e55ba7f1ae0790528395ddf7888ab3094525d -F test/sorterref.test a13ed207a0eea3c7898f308f979bfb518f68c598ec737d2c494dfd3deaa83506 +F test/sorterref.test 9a606c86a4c682db5eeaaefa0565b52102778db53e48ca7101cd4f9ebcc0ad94 F test/sortfault.test d4ccf606a0c77498e2beb542764fd9394acb4d66 F test/speed1.test f2974a91d79f58507ada01864c0e323093065452 F test/speed1p.explain d841e650a04728b39e6740296b852dccdca9b2cb @@ -1410,19 +1423,21 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest1.c 5e5b805f24cc939656058f6a498f5a2160f9142e4815c54faf758ec798d4cdad +F test/speedtest1.c 61f8a72bbcc80edb0b95e957f108feb4013ff3d08721cc87ae1865fd4d20652d F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb -F test/sqldiff1.test 28cd737cf1b0078b1ec1bbf425e674c47785835e -F test/sqllimits1.test 3f9030e5d35375ad3b912b4908094aa806335c8e9d804b8ffff70c5e9c664ab2 +F test/sqldiff1.test 182058e09c7082de5c6a470ff9c291337bbeb650052c2cc68fbb3d7e25861d91 +F test/sqllimits1.test f46f405d754e702227dcdf5b73f94fffcd48c676e845afa37329e4ce2e32ccbf F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805 -F test/stat.test 15a3106eddedfc882f64bc09f237b4169be4b92dd57c93031b8ff8b13af3e7c5 -F test/statfault.test f525a7bf633e50afd027700e9a486090684b1ac1 +F test/stat.test 123212a20ceb496893d5254a5f6c76442ce549fdc08d1702d8288a2bbaac8408 +F test/statfault.test 55f86055f9cd7b2d962a621b8a04215c1cebd4eaaecde92d279442327fe648a0 F test/stmt.test 54ed2cc0764bf3e48a058331813c3dbd19fc1d0827c3d8369914a5d8f564ec75 F test/stmtvtab1.test 6873dfb24f8e79cbb5b799b95c2e4349060eb7a3b811982749a84b359468e2d5 +F test/strict1.test a3ec495471f24c1a6e1a1664bd23e24ccdb27ae93b1a763ee1942ec955b68e71 +F test/strict2.test b22c7a98b5000aef937f1990776497f0e979b1a23bc4f63e2d53b00e59b20070 F test/subjournal.test 8d4e2572c0ee9a15549f0d8e40863161295107e52f07a3e8012a2e1fdd093c49 F test/subquery.test d7268d193dd33d5505df965399d3a594e76ae13f F test/subquery2.test 90cf944b9de8204569cf656028391e4af1ccc8c0cc02d4ef38ee3be8de1ffb12 @@ -1440,11 +1455,11 @@ F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d433309 F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039 F test/syscall.test a39d9a36f852ae6e4800f861bc2f2e83f68bbc2112d9399931ecfadeabd2d69d F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 -F test/tabfunc01.test acb5be558868c65d8cf3495539fff23093d77139eb1e4e8c4580568099f98645 +F test/tabfunc01.test d6821e7042e5653104dac0c63d75eff24a2415ab1889fc68b5db7fde59464c59 F test/table.test eb3463b7add9f16a5bb836badf118cf391b809d09fdccd1f79684600d07ec132 F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 -F test/tclsqlite.test 79a473f5797e317c08f2c4f8192edb3eea6a67329b1087453328b66a7cb31070 +F test/tclsqlite.test 97cda6e4843e9f3e06c56f656d9b77ee0178fe1ee33fb09a6eeae8f125757ac1 F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08 F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900 @@ -1452,7 +1467,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test d2940417496e2b9548e01d09990763fbe88c316504033256d51493e1f1a5ce6a F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 19d2a19a6dd55a2b4e2b943963959a05a2c088495dd5f5274b04e0494ce86d66 +F test/tester.tcl f577d040078dd4fb56ea1ccaa71f6bfd0ebf0bd0ac373304db4269b5474f9cb7 F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1463,7 +1478,7 @@ F test/thread2.test f35d2106452b77523b3a2b7d1dcde2e5ee8f9e46 F test/thread_common.tcl 334639cadcb9f912bf82aa73f49efd5282e6cadd F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b F test/threadtest2.c a70a8e94bef23339d34226eb9521015ef99f4df8 -F test/threadtest3.c e63013af10cf236c7610eb06d33bde08c861806dc64be811940ff4d9ddd34a4f +F test/threadtest3.c 655bff6c0895ec03f014126aa65e808fac9aae8c5a7a7da58a510cbe8b43b781 F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925 F test/threadtest5.c 9b4d782c58d8915d7e955ff8051f3d03628bda0d33b82971ea8c0f2f2808c421 F test/time-wordcount.sh 8e0b0f8109367827ad5d58f5cc849705731e4b90 @@ -1471,7 +1486,7 @@ F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c F test/tkt-18458b1a.test 6a62cb1ee50fa3c620da59e3a6f531eb38fceaf7e2166203816b724524e6f1d6 F test/tkt-26ff0c2d1e.test c15bec890c4d226c0da2f35ff30f9e84c169cfef90e73a8cb5cec11d723dfa96 F test/tkt-2a5629202f.test 0521bd25658428baa26665aa53ffed9367d33af2 -F test/tkt-2d1a5c67d.test be1326f3061caec85085f4c9ee4490561ca037c0 +F test/tkt-2d1a5c67d.test f143872a2102c62e777be3486b38ac2744c18ece31585ed3d0afcb573ca3b4f5 F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28 F test/tkt-31338dca7e.test 6fb8807851964da0d24e942f2e19c7c705b9fb58 F test/tkt-313723c356.test 4b306ad45c736cedf2f5221f6155b92143244b6d @@ -1495,7 +1510,7 @@ F test/tkt-7a31705a7e6.test 9e9c057b6a9497c8f7ba7b16871029414ccf6550e7345d9085d6 F test/tkt-7bbfb7d442.test 7b2cd79c7a17ae6750e75ec1a7846712a69c9d18 F test/tkt-80ba201079.test 105a721e6aad0ae3c5946d7615d1e4d03f6145b8 F test/tkt-80e031a00f.test 9ee36348b761bf7c14261e002b75a4c0d5a04d4c -F test/tkt-8454a207b9.test c583a9f814a82a2b5ba95207f55001c9f0cd816c +F test/tkt-8454a207b9.test aff2e76143cfa443ddce6f7d85968a2e9b57a3deb0b881b730120740555f9e2f F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed F test/tkt-91e2e8ba6f.test 08c4f94ae07696b05c9b822da0b4e5337a2f54c5 @@ -1520,7 +1535,7 @@ F test/tkt-d11f09d36e.test d999b548fef885d1d1afa49a0e8544ecf436869d F test/tkt-d635236375.test 9d37e988b47d87505bc9445be0ca447002df5d09 F test/tkt-d82e3f3721.test bcc0dfba658d15bab30fd4a9320c9e35d214ce30 F test/tkt-f3e5abed55.test d5a0126118142d13e27f6ce9f4c47096e9321c00 -F test/tkt-f67b41381a.test a23bc124c981662db712167bacd0ed8ad11abac9 +F test/tkt-f67b41381a.test 9120eab5e949969a29087e01bf57ac6a52b6c06c16be41091a74c2a863ffc660 F test/tkt-f777251dc7a.test d1a8fc3eefb7a9e64d19ff24d5c8c94c34a632fb F test/tkt-f7b4edec.test d998a08ff2b18b7f62edce8e3044317c45efe6c7 F test/tkt-f973c7ac31.test 28ef85c7f015477916795246d8286aeda39d4ead @@ -1618,7 +1633,7 @@ F test/trace3.test ae2004df24b585fed9046cc0bae4601762bc6fc4aa321d475f1350bba5047 F test/trans.test 45f6f9ab6f66a7b5744f1caac06b558f95da62501916906cf55586a896f9f439 F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76 F test/trans3.test 91a100e5412b488e22a655fe423a14c26403ab94 -F test/transitive1.test 06bcfeeb2ed719abf6ae582f9f65a6b07642dd1363fa648ae9a74a35e83a825c +F test/transitive1.test f8ee983600b33d167da1885657f064aec404e1c0d0bc8765fdf163f4c749237a F test/trigger1.test d30cd09ae8ac365a088f09daba583cc5c0b8fc7d4e1d70809d0b4be3bf6ae2ab F test/trigger2.test 6e35bd7321c49e63d540aee980eb95dec63e1d1caca175224101045bcc80871f F test/trigger3.test aa640bb2bbb03edd5ff69c055117ea088f121945 @@ -1637,12 +1652,12 @@ F test/triggerF.test 5d76f0a8c428ff87a4d5ed52da06f6096a2c787a1e21b846111dfac4123 F test/triggerG.test 2b816093c91ba73c733cfa8aedcc210ad819d72a98b1da30768a3c56505233e9 F test/triggerupfrom.test d1f9e56090408115c522bee626cc33a2f3370f627a5e341d832589d72e3aa271 F test/trustschema1.test 4e970aef0bfe0cee139703cc7209d0e0f07725d999b180ba50770f49edef1494 -F test/tt3_checkpoint.c 9e75cf7c1c364f52e1c47fd0f14c4340a9db0fe1 +F test/tt3_checkpoint.c ac7ca661d739280c89d9c253897df64a59a49369bd1247207ac0f655b622579d F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9 F test/tt3_shared.c b37d22defc944a2ac4c91c927fd06c1d48cd51e2ce9d004fe868625bd2399f93 F test/tt3_stress.c f9a769ca8b026ecc76ee93ca8c9700a5619f8e51c581107c4053ba6ac97f616f -F test/tt3_vacuum.c 1753f45917699c9c1f66b64c717a717c9379f776 +F test/tt3_vacuum.c 71b254cde1fc49d6c8c44efd54f4668f3e57d7b3a8f4601ade069f75a999ba39 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a @@ -1655,14 +1670,14 @@ F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264 F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2 F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97 F test/unordered.test 0edaf3411d300693bca595897c5201421c6c5ec787990a1dfe2f7f60ae93f1e2 -F test/update.test e906ca7cb1dc6f52af1ea243e08f727edfa79f924c2691f2f9e72481f847310d +F test/update.test ef3ebbafeb4be5c96db831f40796e2e77ee846da5ee8b61cfedb1ff1b9e0cc23 F test/update2.test 67455bc61fcbcf96923c45b3bc4f87bc72be7d67575ad35f134906148c7b06d3 F test/upfrom1.tcl 8859d9d437f03b44174c4524a7a734a391fd4526fcff65be08285dafc9dc9041 F test/upfrom1.test 8cb06689e99cd707d884faa16da0e8eb26ff658bb01c47ddf72fadade666e6e1 -F test/upfrom2.test f92e47bfc35e9410d3e8716ee626384e89ad026c55fd6148508ca9d707521673 +F test/upfrom2.test 88d39cb755db5789541e645d4e2764abc697a56958f28a3f8451a0e9342bbd6b F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0 F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6 -F test/upsert1.test 88f9e258c6a0eeeb85937b08831e8daad440ba41f125af48439e9d33f266fb18 +F test/upsert1.test b0ae2f58680c5205b4bc1cdeed3c3d444057c506f6c44494fa3eac60731d68a2 F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c5a09 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 @@ -1672,19 +1687,19 @@ F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae -F test/vacuum-into.test 48f4cec354fb6f27c98ef58d2fe49a11b71ff131af0cd9140efacc9858b9f670 +F test/vacuum-into.test f0b8c091df5305728b6973e9cce4166c861955b650dd3c599cb045d7160d3971 F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d F test/vacuum2.test 9fd45ce6ce29f5614c249e03938d3567c06a9e772d4f155949f8eafe2d8af520 -F test/vacuum3.test 77ecdd54592b45a0bcb133339f99f1ae0ae94d0d +F test/vacuum3.test d9d9a04ee58c485b94694fd4f68cffaba49c32234fdefe1ac1a622c5e17d4ce3 F test/vacuum4.test 7ea76b769fffeb41f925303b04cbcf5a5bbeabe55e4c60ae754ff24eeeb7c010 F test/vacuum5.test 263b144d537e92ad8e9ca8a73cc6e1583f41cfd0dda9432b87f7806174a2f48c F test/vacuum6.test d3173a54edc81d13d99e4cf4972232b3cbb52f1d56ed48c3a939ef4e751c1ee8 F test/vacuummem.test 7b42abb3208bd82dd23a7536588396f295a314f2 F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 -F test/view.test ea88361d5e9bc8eabf9f573185a16aea73a885be9b6c6a95ae84908913416a80 +F test/view.test d654fbadae82f936c2a820bbc892592085467548ff59e88acef201416e9fe48a F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 -F test/vtab1.test 99c0c13b5336ca7f87f137459de144b2f396bb8563fbd602e46bfaa425e3d8cc +F test/vtab1.test 09a72330d0f31eda2ffaa828b06a6b917fb86250ee72de0301570af725774c07 F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e F test/vtab4.test 8e73ed268f3d596bc3590f45fc948fb40f28e9c3 @@ -1693,7 +1708,7 @@ F test/vtab6.test 8e789f526e6594cf7ae933d1adee0caa87dc9f78 F test/vtab7.test 70c6f4a1d6177144a8236e4172d5fba92e683440374664ad1f04851fbb335d3c F test/vtab8.test e19fa4a538fcd1bb66c22825fa8f71618fb13583 F test/vtab9.test ea58d2b95d61955f87226381716b2d0b1d4e4f9b -F test/vtabA.test 1317f06a03597eee29f40a49b6c21e1aaba4285f +F test/vtabA.test 325a77e7f0f80aa78ab388875c0ad6fb853acc6ac54d85514650b0ae15da24ff F test/vtabB.test 04df5dc531b9f44d9ca65b9c1b79f12b5922a796 F test/vtabC.test 4528f459a13136f982e75614d120aef165f17292 F test/vtabD.test 05b3f1d77117271671089e48719524b676842e96 @@ -1702,6 +1717,7 @@ F test/vtabF.test 1918844c7c902f6a16c8dacf1ec8f84886d6e78b F test/vtabH.test 2efb5a24b0bb50796b21eca23032cfb77abfa4b0c03938e38ce5897abac404ca F test/vtabI.test 751b07636700dbdea328e4265b6077ccd6811a3f F test/vtabJ.test a6aef49d558af90fae10565b29501f82a95781cb4f797f2d13e2d19f9b6bc77b +F test/vtabK.test 13293177528fada1235c0112db0d187d754af1355c5a39371abd365104e3afbf F test/vtab_alter.test 736e66fb5ec7b4fee58229aa3ada2f27ec58bc58c00edae4836890c3784c6783 F test/vtab_err.test dcc8b7b9cb67522b3fe7a272c73856829dae4ab7fdb30399aea1b6981bda2b65 F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad @@ -1744,7 +1760,7 @@ F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2a F test/walvfs.test bccb3e0d235ef85e276f491d34db32c9ada1ea67be8d9f10aabe7b30319ec656 F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec F test/wapptest.tcl 899594e25684861d5b0c0880fb012364def50ef8097041b8ddf74be5ba7fa270 x -F test/where.test 4f7a3939e5190845ef42142031a1819070f65e065e6a840a09adc54dccfef01f +F test/where.test f114842c1851d257a26770f2ad55119b084001c0e1b8c214f886f45152d37cd8 F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6 F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 @@ -1757,7 +1773,7 @@ F test/whereA.test 9d1077b117f1b68d5f739d94f36956c36cf995eb87bb19b77b2e81af020ed F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5 F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6 F test/whereD.test c1c335e914e28b122e000e9310f02d2be83e1c9dbca2e29f46bd732703944d1b -F test/whereE.test 0ac7e61c6225354a980666996539da154991b4325af943a25a9079079c82fb03 +F test/whereE.test 7a727b5d5b6bc8fa4cef5206e90cc0363e55ca7f0566f6fbad0206e43170f59e F test/whereF.test 926b65519608e3f2aa28720822b9154fb5c7b13519dd78194f434a511ab3dac5 F test/whereG.test b2a479f425f7d0a432df7e842e8484560908ef703fe0fd407888ff85e7097238 F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 @@ -1774,7 +1790,7 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2 F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972 F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test 49eadb28b0bae0f916518c9983b1fb7450c198915664f323b2bfc480cc98c431 +F test/window1.test 18aadea36d7a9fab1474b1d80002d82304a342e0471793841c66afde14a381b8 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -1789,28 +1805,30 @@ F test/window8.tcl 5e02e41d9d9a80f597063aed1a381eb19d1d0ef677a4f0df352c5365cf23f F test/window8.test 4ab16817414af0c904abe2ebdf88eb6c2b00058b84f9748c6174ff11fc45f1ed F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae4836 F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be -F test/windowB.test 6e601f8178ba8ba28b2f19e74fe613815084bb4a8d2ad942defc7d42e191e521 +F test/windowB.test b67bda5645f3226790e1a360c4225241840b84adb5aa2e69bfb0b27eef3b84d9 +F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2 F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b -F test/windowfault.test 21919e601f20b976ea2a73aa401220c89ed0e8d203c4f69476ea55bce3726496 +F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df F test/with1.test 7bc5abfe4c80c0cef8a90f5a66d60b9982e8ccd7350c8eb70611323a3b8e07ba -F test/with2.test bbf82609bbacc0a7a01d822022aed7b2fa702436dd3d0ecf942023564d2bba13 +F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df F test/with3.test ad32d13ad50661e6fa305f62a0717649c348792e7b658bf2644976227a9e0373 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8 F test/with6.test 661d7e416bef6c0a2556b2c9f0c8178a5b15932bed65246abed99723a8d4e7c0 F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64 -F test/without_rowid1.test 6abc5d497f634520944dac0a89a6c240a48d2ee0f8353356a750eb70dc1db41a +F test/without_rowid1.test 78fd9b437f4cdb46f76e6a510d545334e4f58e3e4ce37aaf19384eda5b27de8c F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99 F test/without_rowid3.test 39ab0dd773eaa62e59b17093f875327630f54c4145458f6d2b053d68d4b2f67b F test/without_rowid4.test 4e08bcbaee0399f35d58b5581881e7a6243d458a -F test/without_rowid5.test 89b1c587bd92a0590e440da33e7666bf4891572a +F test/without_rowid5.test f14298eb5ac8013894b75141c3f4f5f325a6ad0eded55516eef72c49e60a67cb F test/without_rowid6.test efbd7add62c59bf5ca97bf8da674e734e6a70ef979234e816166824b4d258f68 F test/without_rowid7.test d7c59a93d726b55812d620f8f284e01904a5b85f9ee9eea8f2f68571a5e8c40e F test/wordcount.c d721a4b6fae93e6e33449700bce1686bc23257c27425bc3ef1599dc912adec66 F test/writecrash.test f1da7f7adfe8d7f09ea79b42e5ca6dcc41102f27f8e334ad71539501ddd910cc -F test/zeroblob.test 07a5b11ab591d1f26c626945fb7f228f68b993533b2ada77273edf6ee29db174 +F test/zeroblob.test 7b74cefc7b281dfa2b07cd237987fbe94b4a2037a7771e9e83f2d5f608b1d99e +F test/zeroblobfault.test 861d8191a0d944dfebb3cb4d2c5b4e46a5a119eaec5a63dd996c2389f8063441 F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc F test/zipfile.test 0d8758d8c0d63f16644f959689f78969d223789d998964276554039f067b4548 F test/zipfile2.test 9903388a602a3834189857a985106ff95c3bba6a3969e0134127df991889db5d @@ -1836,27 +1854,27 @@ F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5 F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f -F tool/lemon.c a5acddd3eec6a388872aae6efc7563336348a9c45e5563642f77e8e3a50e859d -F tool/lempar.c 1d3d075da18681c67ecc66c1f171e7094e18cd2cfba6a8a1bd4f3f639d6656e1 +F tool/lemon.c 258881835bd5bccd0c74fb110fe54244ff18e8e7ef3d949cbdab7187f02132bb +F tool/lempar.c 57478ea48420da05faa873c6d1616321caa5464644588c97fbe8e0ea04450748 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 -F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca +F tool/logest.c 06446d42942a7128ab4963eb438eb3f528e07095d5d1080f9105a6f337238d33 F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl 06b0d503ee0e6c2d4abe83563b43d4925a12e31ec9fb3249ce39661f53fbd1ce -F tool/mkkeywordhash.c 08b6e4d7a482a7f37a9a0032e7ba968e26624a027b6b2e9ba589be6f5e3d8c2c +F tool/mkctimec.tcl 5ef1891ed3d0e8143ff39bad7c01ed60c2817a2fb2d9a09487f7ccad2df621e4 +F tool/mkkeywordhash.c 35bfc41adacc4aa6ef6fca7fd0c63e0ec0534b78daf4d0cfdebe398216bbffc3 F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a895ab33 -F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c +F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa -F tool/mkpragmatab.tcl ae5585ae76ca26e4d6ccd5ea9cdebaf5efefb318bf989497a0e846cd711d9ab1 -F tool/mkshellc.tcl 5fe7e518112b262e25726f248c0f33dd153192867453984b6af0a76a88e97cb2 +F tool/mkpragmatab.tcl de206c64b6e9ac8cd5e3cbd0ffe456f07d5710605ef8385d677e60ce3335ea12 +F tool/mkshellc.tcl df5d249617f9cc94d5c48eb0401673eb3f31f383ecbc54e8a13ca3dd97e89450 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f -F tool/mksqlite3c.tcl 00c4c0382eb000153182da2bee2547ca05554eeeed10177efe5894c54e4e6d4d +F tool/mksqlite3c.tcl bf9b40811aba68f73f2a8848fad9b1fb09fd54ab5b77e5227f18eea87ab60d92 F tool/mksqlite3h.tcl 1f5e4a1dbbbc43c83cc6e74fe32c6c620502240b66c7c0f33a51378e78fc4edf F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 @@ -1864,27 +1882,27 @@ F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c611528584 F tool/omittest.tcl 3d222272b1d840b4e3d67bff0cb743ce3f633faddadb3702b2056b726775db8f F tool/opcodesum.tcl 740ed206ba8c5040018988129abbf3089a0ccf4a F tool/pagesig.c ff0ca355fd3c2398e933da5e22439bbff89b803b -F tool/replace.tcl 60f91e8dd06ab81f74d213ecbd9c9945f32ac048 +F tool/replace.tcl 937c931ad560688e85bdd6258bdc754371bb1e2732e1fb28ef441e44c9228fce F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076 -F tool/showdb.c 7cc12c6deeddfe40ba5d948b408730696d8365988da05fcb6b6a90ea4965e2b4 +F tool/showdb.c 72239e95e1d05a2941c6a1d86b4d857812be4dadd71f24e381db572f350fc172 F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818 F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564 F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809 F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c2a1 -F tool/showwal.c ad9d768f96ca6199ad3a8c9562d679680bd032dd01204ea3e5ea6fb931d81847 +F tool/showwal.c 0253c187ae16fdae9cde89e63e1dfcd3bb35e5416d066415f99e2f8cac6ab03d F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe -F tool/spaceanal.tcl a95036b36622e25cffd65a55b22d6af53dfbbff0de02d45dd0059bb3c9978609 -F tool/speed-check.sh 8ba7c7c0dba37e664679974f5954f2282275271a5b92f890756e282df0bfc458 +F tool/spaceanal.tcl 1b5be34c6223cb1af06da2a10fb77863eb869b1962d055820b0a11cf2336ab45 +F tool/speed-check.sh ff74a68bb95a0341275f4d3c9a7d8a3800bd278aceecf1913295a1f0175bc3e6 F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/split-sqlite3c.tcl 3efcd4240b738f6bb2b5af0aea7e1e0ef9bc1c61654f645076cec883030b710c -F tool/sqldiff.c 21226ef092ec1e543b237c5d3d2d32d344a60f2437eadfea04f65b348fbd00e4 -F tool/sqlite3_analyzer.c.in 7eeaae8b0d7577662acaabbb11107af0659d1b41bc1dfdd4d91422de27127968 +F tool/sqldiff.c 9c639de9fa541a947ea9776435c84adf00f43945e262556e15219ef09f0d912c +F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f @@ -1918,10 +1936,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7bd55eee1ac63cf6d5699ce85bc5a29bf51afdf7a80bce44937fa833947a40f4 -R 6b8a0a7cf847af9edb1fb9387491b821 +P e1871201e73c1d970f0434d3c7190da2c1c49ded3a359640d959ec0c0450b8ed +R 6d4acf62ee33d6425f8616c1e2a2b241 +T +bgcolor * #d0c0ff T +sym-release * -T +sym-version-3.36.0 * +T +sym-version-3.37.2 * U drh -Z 05ee2bbc3eef4148ab2800c954a168c4 +Z bc8f65914f35757c2569ece42ec5d728 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ef5b50a93..8927d8b43 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5c9a6c06871cb9fe42814af9c039eb6da5427a6ec28f187af7ebfb62eafa66e5 +872ba256cbf61d9290b571c0e6d82a20c224ca3ad82971edc46b29818d5d17a0 diff --git a/src/alter.c b/src/alter.c index f998b893c..bb43edbed 100644 --- a/src/alter.c +++ b/src/alter.c @@ -60,7 +60,7 @@ static void renameTestSchema( pParse->colNamesSet = 1; sqlite3NestedParse(pParse, "SELECT 1 " - "FROM \"%w\"." DFLT_SCHEMA_TABLE " " + "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %d)=NULL ", @@ -71,7 +71,7 @@ static void renameTestSchema( if( bTemp==0 ){ sqlite3NestedParse(pParse, "SELECT 1 " - "FROM temp." DFLT_SCHEMA_TABLE " " + "FROM temp." LEGACY_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %d)=NULL ", @@ -89,14 +89,14 @@ static void renameTestSchema( */ static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){ sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET sql = sqlite_rename_quotefix(%Q, sql)" "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb ); if( bTemp==0 ){ sqlite3NestedParse(pParse, - "UPDATE temp." DFLT_SCHEMA_TABLE + "UPDATE temp." LEGACY_SCHEMA_TABLE " SET sql = sqlite_rename_quotefix('temp', sql)" "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" @@ -135,9 +135,7 @@ void sqlite3AlterRenameTable( const char *zTabName; /* Original name of the table */ Vdbe *v; VTable *pVTab = 0; /* Non-zero if this is a v-tab with an xRename() */ - u32 savedDbFlags; /* Saved value of db->mDbFlags */ - savedDbFlags = db->mDbFlags; if( NEVER(db->mallocFailed) ) goto exit_rename_table; assert( pSrc->nSrc==1 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); @@ -146,7 +144,6 @@ void sqlite3AlterRenameTable( if( !pTab ) goto exit_rename_table; iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; - db->mDbFlags |= DBFLAG_PreferBuiltin; /* Get a NULL terminated version of the new table name. */ zName = sqlite3NameFromToken(db, pName); @@ -175,7 +172,7 @@ void sqlite3AlterRenameTable( } #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName); goto exit_rename_table; } @@ -217,7 +214,7 @@ void sqlite3AlterRenameTable( /* Rewrite all CREATE TABLE, INDEX, TRIGGER or VIEW statements in ** the schema to use the new table name. */ sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, %d) " "WHERE (type!='index' OR tbl_name=%Q COLLATE nocase)" "AND name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" @@ -227,7 +224,7 @@ void sqlite3AlterRenameTable( /* Update the tbl_name and name columns of the sqlite_schema table ** as required. */ sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE " SET " + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET " "tbl_name = %Q, " "name = CASE " "WHEN type='table' THEN %Q " @@ -287,7 +284,6 @@ void sqlite3AlterRenameTable( exit_rename_table: sqlite3SrcListDelete(db, pSrc); sqlite3DbFree(db, zName); - db->mDbFlags = savedDbFlags; } /* @@ -337,7 +333,7 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ zDb = db->aDb[iDb].zDbSName; zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ pCol = &pNew->aCol[pNew->nCol-1]; - pDflt = pCol->pDflt; + pDflt = sqlite3ColumnExpr(pNew, pCol); pTab = sqlite3FindTable(db, zTab, zDb); assert( pTab ); @@ -371,7 +367,8 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ if( pDflt && pDflt->pLeft->op==TK_NULL ){ pDflt = 0; } - if( (db->flags&SQLITE_ForeignKeys) && pNew->pFKey && pDflt ){ + assert( IsOrdinaryTable(pNew) ); + if( (db->flags&SQLITE_ForeignKeys) && pNew->u.tab.pFKey && pDflt ){ sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, "Cannot add a REFERENCES column with non-NULL default value"); } @@ -408,31 +405,30 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ zCol = sqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n); if( zCol ){ char *zEnd = &zCol[pColDef->n-1]; - u32 savedDbFlags = db->mDbFlags; while( zEnd>zCol && (*zEnd==';' || sqlite3Isspace(*zEnd)) ){ *zEnd-- = '\0'; } - db->mDbFlags |= DBFLAG_PreferBuiltin; /* substr() operations on characters, but addColOffset is in bytes. So we ** have to use printf() to translate between these units: */ + assert( IsOrdinaryTable(pTab) ); + assert( IsOrdinaryTable(pNew) ); sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = printf('%%.%ds, ',sql) || %Q" " || substr(sql,1+length(printf('%%.%ds',sql))) " "WHERE type = 'table' AND name = %Q", - zDb, pNew->addColOffset, zCol, pNew->addColOffset, + zDb, pNew->u.tab.addColOffset, zCol, pNew->u.tab.addColOffset, zTab ); sqlite3DbFree(db, zCol); - db->mDbFlags = savedDbFlags; } - /* Make sure the schema version is at least 3. But do not upgrade - ** from less than 3 to 4, as that will corrupt any preexisting DESC - ** index. - */ v = sqlite3GetVdbe(pParse); if( v ){ + /* Make sure the schema version is at least 3. But do not upgrade + ** from less than 3 to 4, as that will corrupt any preexisting DESC + ** index. + */ r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT); sqlite3VdbeUsesBtree(v, iDb); @@ -441,10 +437,25 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, 3); sqlite3ReleaseTempReg(pParse, r1); - } - /* Reload the table definition */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); + /* Reload the table definition */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterAdd); + + /* Verify that constraints are still satisfied */ + if( pNew->pCheck!=0 + || (pCol->notNull && (pCol->colFlags & COLFLAG_GENERATED)!=0) + ){ + sqlite3NestedParse(pParse, + "SELECT CASE WHEN quick_check GLOB 'CHECK*'" + " THEN raise(ABORT,'CHECK constraint failed')" + " ELSE raise(ABORT,'NOT NULL constraint failed')" + " END" + " FROM pragma_quick_check(\"%w\",\"%w\")" + " WHERE quick_check GLOB 'CHECK*' OR quick_check GLOB 'NULL*'", + zTab, zDb + ); + } + } } /* @@ -485,7 +496,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ #endif /* Make sure this is not an attempt to ALTER a view. */ - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "Cannot add a column to a view"); goto exit_begin_add_column; } @@ -494,7 +505,8 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ } sqlite3MayAbort(pParse); - assert( pTab->addColOffset>0 ); + assert( IsOrdinaryTable(pTab) ); + assert( pTab->u.tab.addColOffset>0 ); iDb = sqlite3SchemaToIndex(db, pTab->pSchema); /* Put a copy of the Table struct in Parse.pNewTable for the @@ -521,13 +533,13 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol); for(i=0; inCol; i++){ Column *pCol = &pNew->aCol[i]; - pCol->zName = sqlite3DbStrDup(db, pCol->zName); - pCol->hName = sqlite3StrIHash(pCol->zName); - pCol->zColl = 0; - pCol->pDflt = 0; + pCol->zCnName = sqlite3DbStrDup(db, pCol->zCnName); + pCol->hName = sqlite3StrIHash(pCol->zCnName); } + assert( IsOrdinaryTable(pNew) ); + pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pTab->u.tab.pDfltList, 0); pNew->pSchema = db->aDb[iDb].pSchema; - pNew->addColOffset = pTab->addColOffset; + pNew->u.tab.addColOffset = pTab->u.tab.addColOffset; pNew->nTabRef = 1; exit_begin_add_column: @@ -547,7 +559,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ const char *zType = 0; #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ zType = "view"; } #endif @@ -614,7 +626,7 @@ void sqlite3AlterRenameColumn( zOld = sqlite3NameFromToken(db, pOld); if( !zOld ) goto exit_rename_column; for(iCol=0; iColnCol; iCol++){ - if( 0==sqlite3StrICmp(pTab->aCol[iCol].zName, zOld) ) break; + if( 0==sqlite3StrICmp(pTab->aCol[iCol].zCnName, zOld) ) break; } if( iCol==pTab->nCol ){ sqlite3ErrorMsg(pParse, "no such column: \"%s\"", zOld); @@ -635,18 +647,17 @@ void sqlite3AlterRenameColumn( assert( pNew->n>0 ); bQuote = sqlite3Isquote(pNew->z[0]); sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' " - " AND (type != 'index' OR tbl_name = %Q)" - " AND sql NOT LIKE 'create virtual%%'", + " AND (type != 'index' OR tbl_name = %Q)", zDb, zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1, pTab->zName ); sqlite3NestedParse(pParse, - "UPDATE temp." DFLT_SCHEMA_TABLE " SET " + "UPDATE temp." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, 1) " "WHERE type IN ('trigger', 'view')", zDb, pTab->zName, iCol, zNew, bQuote @@ -681,7 +692,7 @@ void sqlite3AlterRenameColumn( ** the parse tree. */ struct RenameToken { - void *p; /* Parse tree element created by token t */ + const void *p; /* Parse tree element created by token t */ Token t; /* The token that created parse tree element p */ RenameToken *pNext; /* Next is a list of all RenameToken objects */ }; @@ -723,9 +734,9 @@ struct RenameCtx { ** Technically, as x no longer points into a valid object or to the byte ** following a valid object, it may not be used in comparison operations. */ -static void renameTokenCheckAll(Parse *pParse, void *pPtr){ +static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ if( pParse->nErr==0 && pParse->db->mallocFailed==0 ){ - RenameToken *p; + const RenameToken *p; u8 i = 0; for(p=pParse->pRename; p; p=p->pNext){ if( p->p ){ @@ -751,7 +762,11 @@ static void renameTokenCheckAll(Parse *pParse, void *pPtr){ ** with tail recursion in tokenExpr() routine, for a small performance ** improvement. */ -void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pToken){ +const void *sqlite3RenameTokenMap( + Parse *pParse, + const void *pPtr, + const Token *pToken +){ RenameToken *pNew; assert( pPtr || pParse->db->mallocFailed ); renameTokenCheckAll(pParse, pPtr); @@ -773,7 +788,7 @@ void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pToken){ ** with parse tree element pFrom. This function remaps the associated token ** to parse tree element pTo. */ -void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFrom){ +void sqlite3RenameTokenRemap(Parse *pParse, const void *pTo, const void *pFrom){ RenameToken *p; renameTokenCheckAll(pParse, pTo); for(p=pParse->pRename; p; p=p->pNext){ @@ -789,7 +804,10 @@ void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFrom){ */ static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ Parse *pParse = pWalker->pParse; - sqlite3RenameTokenRemap(pParse, 0, (void*)pExpr); + sqlite3RenameTokenRemap(pParse, 0, (const void*)pExpr); + if( ExprUseYTab(pExpr) ){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)&pExpr->y.pTab); + } return WRC_Continue; } @@ -819,6 +837,7 @@ static void renameWalkWith(Walker *pWalker, Select *pSelect){ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC); + if( sNC.pParse->db->mallocFailed ) return; sqlite3WalkSelect(pWalker, p); sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols); } @@ -833,12 +852,12 @@ static void renameWalkWith(Walker *pWalker, Select *pSelect){ */ static void unmapColumnIdlistNames( Parse *pParse, - IdList *pIdList + const IdList *pIdList ){ if( pIdList ){ int ii; for(ii=0; iinId; ii++){ - sqlite3RenameTokenRemap(pParse, 0, (void*)pIdList->a[ii].zName); + sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName); } } } @@ -850,9 +869,9 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; int i; if( pParse->nErr ) return WRC_Abort; + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); if( p->selFlags & (SF_View|SF_CopyCte) ){ - testcase( p->selFlags & SF_View ); - testcase( p->selFlags & SF_CopyCte ); return WRC_Prune; } if( ALWAYS(p->pEList) ){ @@ -867,7 +886,7 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){ SrcList *pSrc = p->pSrc; for(i=0; inSrc; i++){ sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName); - if( sqlite3WalkExpr(pWalker, pSrc->a[i].pOn) ) return WRC_Abort; + sqlite3WalkExpr(pWalker, pSrc->a[i].pOn); unmapColumnIdlistNames(pParse, pSrc->a[i].pUsing); } } @@ -935,7 +954,7 @@ static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ static RenameToken *renameTokenFind( Parse *pParse, struct RenameCtx *pCtx, - void *pPtr + const void *pPtr ){ RenameToken **pp; if( NEVER(pPtr==0) ){ @@ -988,7 +1007,8 @@ static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ ){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); }else if( pExpr->op==TK_COLUMN - && pExpr->iColumn==p->iCol + && pExpr->iColumn==p->iCol + && ALWAYS(ExprUseYTab(pExpr)) && p->pTab==pExpr->y.pTab ){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); @@ -1054,18 +1074,18 @@ static void renameColumnParseError( static void renameColumnElistNames( Parse *pParse, RenameCtx *pCtx, - ExprList *pEList, + const ExprList *pEList, const char *zOld ){ if( pEList ){ int i; for(i=0; inExpr; i++){ - char *zName = pEList->a[i].zEName; + const char *zName = pEList->a[i].zEName; if( ALWAYS(pEList->a[i].eEName==ENAME_NAME) && ALWAYS(zName!=0) && 0==sqlite3_stricmp(zName, zOld) ){ - renameTokenFind(pParse, pCtx, (void*)zName); + renameTokenFind(pParse, pCtx, (const void*)zName); } } } @@ -1079,15 +1099,15 @@ static void renameColumnElistNames( static void renameColumnIdlistNames( Parse *pParse, RenameCtx *pCtx, - IdList *pIdList, + const IdList *pIdList, const char *zOld ){ if( pIdList ){ int i; for(i=0; inId; i++){ - char *zName = pIdList->a[i].zName; + const char *zName = pIdList->a[i].zName; if( 0==sqlite3_stricmp(zName, zOld) ){ - renameTokenFind(pParse, pCtx, (void*)zName); + renameTokenFind(pParse, pCtx, (const void*)zName); } } } @@ -1313,6 +1333,9 @@ static int renameResolveTrigger(Parse *pParse){ } } } + if( rc==SQLITE_OK && db->mallocFailed ){ + rc = SQLITE_NOMEM; + } sNC.pSrcList = pSrc; if( rc==SQLITE_OK && pStep->pWhere ){ rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); @@ -1460,7 +1483,7 @@ static void renameColumnFunc( sqlite3BtreeLeaveAll(db); return; } - zOld = pTab->aCol[iCol].zName; + zOld = pTab->aCol[iCol].zCnName; memset(&sCtx, 0, sizeof(sCtx)); sCtx.iCol = ((iCol==pTab->iPKey) ? -1 : iCol); @@ -1479,8 +1502,8 @@ static void renameColumnFunc( sCtx.pTab = pTab; if( rc!=SQLITE_OK ) goto renameColumnFunc_done; if( sParse.pNewTable ){ - Select *pSelect = sParse.pNewTable->pSelect; - if( pSelect ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; pSelect->selFlags &= ~SF_View; sParse.rc = SQLITE_OK; sqlite3SelectPrep(&sParse, pSelect, 0); @@ -1489,16 +1512,15 @@ static void renameColumnFunc( sqlite3WalkSelect(&sWalker, pSelect); } if( rc!=SQLITE_OK ) goto renameColumnFunc_done; - }else{ + }else if( IsOrdinaryTable(sParse.pNewTable) ){ /* A regular table */ int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName); FKey *pFKey; - assert( sParse.pNewTable->pSelect==0 ); sCtx.pTab = sParse.pNewTable; if( bFKOnly==0 ){ if( iColnCol ){ renameTokenFind( - &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName + &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zCnName ); } if( sCtx.iCol<0 ){ @@ -1513,12 +1535,15 @@ static void renameColumnFunc( } #ifndef SQLITE_OMIT_GENERATED_COLUMNS for(i=0; inCol; i++){ - sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt); + Expr *pExpr = sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i]); + sqlite3WalkExpr(&sWalker, pExpr); } #endif } - for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(sParse.pNewTable) ); + for(pFKey=sParse.pNewTable->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ for(i=0; inCol; i++){ if( bFKOnly==0 && pFKey->aCol[i].iFrom==iCol ){ renameTokenFind(&sParse, &sCtx, (void*)&pFKey->aCol[i]); @@ -1589,7 +1614,10 @@ static void renameColumnFunc( */ static int renameTableExprCb(Walker *pWalker, Expr *pExpr){ RenameCtx *p = pWalker->u.pRename; - if( pExpr->op==TK_COLUMN && p->pTab==pExpr->y.pTab ){ + if( pExpr->op==TK_COLUMN + && ALWAYS(ExprUseYTab(pExpr)) + && p->pTab==pExpr->y.pTab + ){ renameTokenFind(pWalker->pParse, p, (void*)&pExpr->y.pTab); } return WRC_Continue; @@ -1684,28 +1712,31 @@ static void renameTableFunc( if( sParse.pNewTable ){ Table *pTab = sParse.pNewTable; - if( pTab->pSelect ){ + if( IsView(pTab) ){ if( isLegacy==0 ){ - Select *pSelect = pTab->pSelect; + Select *pSelect = pTab->u.view.pSelect; NameContext sNC; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = &sParse; assert( pSelect->selFlags & SF_View ); pSelect->selFlags &= ~SF_View; - sqlite3SelectPrep(&sParse, pTab->pSelect, &sNC); + sqlite3SelectPrep(&sParse, pTab->u.view.pSelect, &sNC); if( sParse.nErr ){ rc = sParse.rc; }else{ - sqlite3WalkSelect(&sWalker, pTab->pSelect); + sqlite3WalkSelect(&sWalker, pTab->u.view.pSelect); } } }else{ /* Modify any FK definitions to point to the new table. */ #ifndef SQLITE_OMIT_FOREIGN_KEY - if( isLegacy==0 || (db->flags & SQLITE_ForeignKeys) ){ + if( (isLegacy==0 || (db->flags & SQLITE_ForeignKeys)) + && !IsVirtual(pTab) + ){ FKey *pFKey; - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ if( sqlite3_stricmp(pFKey->zTo, zOld)==0 ){ renameTokenFind(&sParse, &sCtx, (void*)pFKey->zTo); } @@ -1782,7 +1813,7 @@ static void renameTableFunc( static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){ - renameTokenFind(pWalker->pParse, pWalker->u.pRename, (void*)pExpr); + renameTokenFind(pWalker->pParse, pWalker->u.pRename, (const void*)pExpr); } return WRC_Continue; } @@ -1845,8 +1876,8 @@ static void renameQuotefixFunc( sWalker.u.pRename = &sCtx; if( sParse.pNewTable ){ - Select *pSelect = sParse.pNewTable->pSelect; - if( pSelect ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; pSelect->selFlags &= ~SF_View; sParse.rc = SQLITE_OK; sqlite3SelectPrep(&sParse, pSelect, 0); @@ -1859,7 +1890,9 @@ static void renameQuotefixFunc( sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); #ifndef SQLITE_OMIT_GENERATED_COLUMNS for(i=0; inCol; i++){ - sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt); + sqlite3WalkExpr(&sWalker, + sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i])); } #endif /* SQLITE_OMIT_GENERATED_COLUMNS */ } @@ -1942,11 +1975,11 @@ static void renameTableTest( rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); db->flags |= (flags & (SQLITE_DqsDML|SQLITE_DqsDDL)); if( rc==SQLITE_OK ){ - if( isLegacy==0 && sParse.pNewTable && sParse.pNewTable->pSelect ){ + if( isLegacy==0 && sParse.pNewTable && IsView(sParse.pNewTable) ){ NameContext sNC; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = &sParse; - sqlite3SelectPrep(&sParse, sParse.pNewTable->pSelect, &sNC); + sqlite3SelectPrep(&sParse, sParse.pNewTable->u.view.pSelect, &sNC); if( sParse.nErr ) rc = sParse.rc; } @@ -2017,13 +2050,14 @@ static void dropColumnFunc( goto drop_column_done; } - pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zName); + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); if( iColnCol-1 ){ RenameToken *pEnd; - pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zName); + pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); zEnd = (const char*)pEnd->t.z; }else{ - zEnd = (const char*)&zSql[pTab->addColOffset]; + assert( IsOrdinaryTable(pTab) ); + zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; } @@ -2049,7 +2083,7 @@ static void dropColumnFunc( ** statement. Argument pSrc contains the possibly qualified name of the ** table being edited, and token pName the name of the column to drop. */ -void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){ +void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ sqlite3 *db = pParse->db; /* Database handle */ Table *pTab; /* Table to modify */ int iDb; /* Index of db containing pTab in aDb[] */ @@ -2104,7 +2138,7 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){ renameTestSchema(pParse, zDb, iDb==1, "", 0); renameFixQuotes(pParse, zDb, iDb==1); sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_drop_column(%d, sql, %d) " "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)" , zDb, iDb, iCol, pTab->zName @@ -2159,6 +2193,12 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){ nField++; } } + if( nField==0 ){ + /* dbsqlfuzz 5f09e7bcc78b4954d06bf9f2400d7715f48d1fef */ + pParse->nMem++; + sqlite3VdbeAddOp2(v, OP_Null, 0, reg+1); + nField = 1; + } sqlite3VdbeAddOp3(v, OP_MakeRecord, reg+1, nField, regRec); if( pPk ){ sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iCur, regRec, reg+1, pPk->nKeyCol); diff --git a/src/analyze.c b/src/analyze.c index dc77220a5..5bbd34f9a 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -433,7 +433,6 @@ static void statInit( + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample); } #endif - db = sqlite3_context_db_handle(context); p = sqlite3DbMallocZero(db, n); if( p==0 ){ sqlite3_result_error_nomem(context); @@ -852,28 +851,19 @@ static void statGet( ** ** I = (K+D-1)/D */ - char *z; - int i; - - char *zRet = sqlite3MallocZero( (p->nKeyCol+1)*25 ); - if( zRet==0 ){ - sqlite3_result_error_nomem(context); - return; - } + sqlite3_str sStat; /* Text of the constructed "stat" line */ + int i; /* Loop counter */ - sqlite3_snprintf(24, zRet, "%llu", + sqlite3StrAccumInit(&sStat, 0, 0, 0, (p->nKeyCol+1)*100); + sqlite3_str_appendf(&sStat, "%llu", p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow); - z = zRet + sqlite3Strlen30(zRet); for(i=0; inKeyCol; i++){ u64 nDistinct = p->current.anDLt[i] + 1; u64 iVal = (p->nRow + nDistinct - 1) / nDistinct; - sqlite3_snprintf(24, z, " %llu", iVal); - z += sqlite3Strlen30(z); + sqlite3_str_appendf(&sStat, " %llu", iVal); assert( p->current.anEq[i] ); } - assert( z[0]=='\0' && z>zRet ); - - sqlite3_result_text(context, zRet, -1, sqlite3_free); + sqlite3ResultStrAccum(context, &sStat); } #ifdef SQLITE_ENABLE_STAT4 else if( eCall==STAT_GET_ROWID ){ @@ -892,6 +882,8 @@ static void statGet( } }else{ tRowcnt *aCnt = 0; + sqlite3_str sStat; + int i; assert( p->iGetnSample ); switch( eCall ){ @@ -903,23 +895,12 @@ static void statGet( break; } } - - { - char *zRet = sqlite3MallocZero(p->nCol * 25); - if( zRet==0 ){ - sqlite3_result_error_nomem(context); - }else{ - int i; - char *z = zRet; - for(i=0; inCol; i++){ - sqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]); - z += sqlite3Strlen30(z); - } - assert( z[0]=='\0' && z>zRet ); - z[-1] = '\0'; - sqlite3_result_text(context, zRet, -1, sqlite3_free); - } + sqlite3StrAccumInit(&sStat, 0, 0, 0, p->nCol*100); + for(i=0; inCol; i++){ + sqlite3_str_appendf(&sStat, "%llu ", (u64)aCnt[i]); } + if( sStat.nChar ) sStat.nChar--; + sqlite3ResultStrAccum(context, &sStat); } #endif /* SQLITE_ENABLE_STAT4 */ #ifndef SQLITE_DEBUG @@ -968,7 +949,7 @@ static void analyzeVdbeCommentIndexWithColumnName( }else if( i==XN_EXPR ){ VdbeComment((v,"%s.expr(%d)",pIdx->zName, k)); }else{ - VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zName)); + VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zCnName)); } } #else @@ -1015,7 +996,7 @@ static void analyzeOneTable( if( v==0 || NEVER(pTab==0) ){ return; } - if( pTab->tnum==0 ){ + if( !IsOrdinaryTable(pTab) ){ /* Do not gather statistics on views or virtual tables */ return; } @@ -1840,9 +1821,12 @@ static int loadStatTbl( */ static int loadStat4(sqlite3 *db, const char *zDb){ int rc = SQLITE_OK; /* Result codes from subroutines */ + const Table *pStat4; assert( db->lookaside.bDisable ); - if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){ + if( (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0 + && IsOrdinaryTable(pStat4) + ){ rc = loadStatTbl(db, "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx", "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4", @@ -1879,6 +1863,7 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ char *zSql; int rc = SQLITE_OK; Schema *pSchema = db->aDb[iDb].pSchema; + const Table *pStat1; assert( iDb>=0 && iDbnDb ); assert( db->aDb[iDb].pBt!=0 ); @@ -1901,7 +1886,9 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Load new statistics out of the sqlite_stat1 table */ sInfo.db = db; sInfo.zDatabase = db->aDb[iDb].zDbSName; - if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){ + if( (pStat1 = sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)) + && IsOrdinaryTable(pStat1) + ){ zSql = sqlite3MPrintf(db, "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); if( zSql==0 ){ diff --git a/src/attach.c b/src/attach.c index 2fc84e5b8..2123278c4 100644 --- a/src/attach.c +++ b/src/attach.c @@ -346,9 +346,9 @@ static void codeAttach( sName.pParse = pParse; if( - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pFilename)) || - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pDbname)) || - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pKey)) + SQLITE_OK!=resolveAttachExpr(&sName, pFilename) || + SQLITE_OK!=resolveAttachExpr(&sName, pDbname) || + SQLITE_OK!=resolveAttachExpr(&sName, pKey) ){ goto attach_end; } @@ -357,6 +357,7 @@ static void codeAttach( if( pAuthArg ){ char *zAuthArg; if( pAuthArg->op==TK_STRING ){ + assert( !ExprHasProperty(pAuthArg, EP_IntValue) ); zAuthArg = pAuthArg->u.zToken; }else{ zAuthArg = 0; diff --git a/src/auth.c b/src/auth.c index 33420f583..3a4f73a23 100644 --- a/src/auth.c +++ b/src/auth.c @@ -175,10 +175,10 @@ void sqlite3AuthRead( if( iCol>=0 ){ assert( iColnCol ); - zCol = pTab->aCol[iCol].zName; + zCol = pTab->aCol[iCol].zCnName; }else if( pTab->iPKey>=0 ){ assert( pTab->iPKeynCol ); - zCol = pTab->aCol[pTab->iPKey].zName; + zCol = pTab->aCol[pTab->iPKey].zCnName; }else{ zCol = "ROWID"; } diff --git a/src/bitvec.c b/src/bitvec.c index bd4a09429..9393428d9 100644 --- a/src/bitvec.c +++ b/src/bitvec.c @@ -353,7 +353,7 @@ int sqlite3BitvecBuiltinTest(int sz, int *aOp){ sqlite3BitvecClear(0, 1, pTmpSpace); /* Run the program */ - pc = 0; + pc = i = 0; while( (op = aOp[pc])!=0 ){ switch( op ){ case 1: diff --git a/src/btree.c b/src/btree.c index d1e84d0a6..fe2d1b103 100644 --- a/src/btree.c +++ b/src/btree.c @@ -827,15 +827,13 @@ static int btreeMoveto( sqlite3VdbeRecordUnpack(pKeyInfo, (int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 || pIdxKey->nField>pKeyInfo->nAllField ){ rc = SQLITE_CORRUPT_BKPT; - goto moveto_done; + }else{ + rc = sqlite3BtreeIndexMoveto(pCur, pIdxKey, pRes); } + sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); }else{ pIdxKey = 0; - } - rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes); -moveto_done: - if( pIdxKey ){ - sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); + rc = sqlite3BtreeTableMoveto(pCur, nKey, bias, pRes); } return rc; } @@ -1248,7 +1246,7 @@ static void btreeParseCellPtr( pInfo->nPayload = nPayload; pInfo->pPayload = pIter; testcase( nPayload==pPage->maxLocal ); - testcase( nPayload==pPage->maxLocal+1 ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); if( nPayload<=pPage->maxLocal ){ /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. @@ -1285,7 +1283,7 @@ static void btreeParseCellPtrIndex( pInfo->nPayload = nPayload; pInfo->pPayload = pIter; testcase( nPayload==pPage->maxLocal ); - testcase( nPayload==pPage->maxLocal+1 ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); if( nPayload<=pPage->maxLocal ){ /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. @@ -1348,7 +1346,7 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ while( (*pIter++)&0x80 && pItermaxLocal ); - testcase( nSize==pPage->maxLocal+1 ); + testcase( nSize==(u32)pPage->maxLocal+1 ); if( nSize<=pPage->maxLocal ){ nSize += (u32)(pIter - pCell); if( nSize<4 ) nSize = 4; @@ -1356,7 +1354,7 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ int minLocal = pPage->minLocal; nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); testcase( nSize==pPage->maxLocal ); - testcase( nSize==pPage->maxLocal+1 ); + testcase( nSize==(u32)pPage->maxLocal+1 ); if( nSize>pPage->maxLocal ){ nSize = minLocal; } @@ -1490,7 +1488,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ if( iFree2+sz2 > usableSize ) return SQLITE_CORRUPT_PAGE(pPage); memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz)); sz += sz2; - }else if( iFree+sz>usableSize ){ + }else if( NEVER(iFree+sz>usableSize) ){ return SQLITE_CORRUPT_PAGE(pPage); } @@ -1684,7 +1682,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ int g2; assert( pSpace+nByte<=data+pPage->pBt->usableSize ); *pIdx = g2 = (int)(pSpace-data); - if( NEVER(g2<=gap) ){ + if( g2<=gap ){ return SQLITE_CORRUPT_PAGE(pPage); }else{ return SQLITE_OK; @@ -3097,7 +3095,6 @@ static int lockBtree(BtShared *pBt){ MemPage *pPage1; /* Page 1 of the database file */ u32 nPage; /* Number of pages in the database */ u32 nPageFile = 0; /* Number of pages in the database file */ - u32 nPageHeader; /* Number of pages in the database according to hdr */ assert( sqlite3_mutex_held(pBt->mutex) ); assert( pBt->pPage1==0 ); @@ -3109,7 +3106,7 @@ static int lockBtree(BtShared *pBt){ /* Do some checking to help insure the file we opened really is ** a valid database file. */ - nPage = nPageHeader = get4byte(28+(u8*)pPage1->aData); + nPage = get4byte(28+(u8*)pPage1->aData); sqlite3PagerPagecount(pBt->pPager, (int*)&nPageFile); if( nPage==0 || memcmp(24+(u8*)pPage1->aData, 92+(u8*)pPage1->aData,4)!=0 ){ nPage = nPageFile; @@ -3144,7 +3141,7 @@ static int lockBtree(BtShared *pBt){ goto page1_init_failed; } - /* If the write version is set to 2, this database should be accessed + /* If the read version is set to 2, this database should be accessed ** in WAL mode. If the log is not already open, open it now. Then ** return SQLITE_OK and return without populating BtShared.pPage1. ** The caller detects this and calls this function again. This is @@ -3942,16 +3939,18 @@ int sqlite3BtreeIncrVacuum(Btree *p){ /* ** This routine is called prior to sqlite3PagerCommit when a transaction ** is committed for an auto-vacuum database. -** -** If SQLITE_OK is returned, then *pnTrunc is set to the number of pages -** the database file should be truncated to during the commit process. -** i.e. the database has been reorganized so that only the first *pnTrunc -** pages are in use. */ -static int autoVacuumCommit(BtShared *pBt){ +static int autoVacuumCommit(Btree *p){ int rc = SQLITE_OK; - Pager *pPager = pBt->pPager; - VVA_ONLY( int nRef = sqlite3PagerRefcount(pPager); ) + Pager *pPager; + BtShared *pBt; + sqlite3 *db; + VVA_ONLY( int nRef ); + + assert( p!=0 ); + pBt = p->pBt; + pPager = pBt->pPager; + VVA_ONLY( nRef = sqlite3PagerRefcount(pPager); ) assert( sqlite3_mutex_held(pBt->mutex) ); invalidateAllOverflowCache(pBt); @@ -3959,6 +3958,7 @@ static int autoVacuumCommit(BtShared *pBt){ if( !pBt->incrVacuum ){ Pgno nFin; /* Number of pages in database after autovacuuming */ Pgno nFree; /* Number of pages on the freelist initially */ + Pgno nVac; /* Number of pages to vacuum */ Pgno iFree; /* The next page to be freed */ Pgno nOrig; /* Database size before freeing */ @@ -3972,18 +3972,42 @@ static int autoVacuumCommit(BtShared *pBt){ } nFree = get4byte(&pBt->pPage1->aData[36]); - nFin = finalDbSize(pBt, nOrig, nFree); + db = p->db; + if( db->xAutovacPages ){ + int iDb; + for(iDb=0; ALWAYS(iDbnDb); iDb++){ + if( db->aDb[iDb].pBt==p ) break; + } + nVac = db->xAutovacPages( + db->pAutovacPagesArg, + db->aDb[iDb].zDbSName, + nOrig, + nFree, + pBt->pageSize + ); + if( nVac>nFree ){ + nVac = nFree; + } + if( nVac==0 ){ + return SQLITE_OK; + } + }else{ + nVac = nFree; + } + nFin = finalDbSize(pBt, nOrig, nVac); if( nFin>nOrig ) return SQLITE_CORRUPT_BKPT; if( nFinnFin && rc==SQLITE_OK; iFree--){ - rc = incrVacuumStep(pBt, nFin, iFree, 1); + rc = incrVacuumStep(pBt, nFin, iFree, nVac==nFree); } if( (rc==SQLITE_DONE || rc==SQLITE_OK) && nFree>0 ){ rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); - put4byte(&pBt->pPage1->aData[32], 0); - put4byte(&pBt->pPage1->aData[36], 0); + if( nVac==nFree ){ + put4byte(&pBt->pPage1->aData[32], 0); + put4byte(&pBt->pPage1->aData[36], 0); + } put4byte(&pBt->pPage1->aData[28], nFin); pBt->bDoTruncate = 1; pBt->nPage = nFin; @@ -4034,7 +4058,7 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){ sqlite3BtreeEnter(p); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - rc = autoVacuumCommit(pBt); + rc = autoVacuumCommit(p); if( rc!=SQLITE_OK ){ sqlite3BtreeLeave(p); return rc; @@ -4221,7 +4245,7 @@ static void btreeSetNPage(BtShared *pBt, MemPage *pPage1){ int nPage = get4byte(&pPage1->aData[28]); testcase( nPage==0 ); if( nPage==0 ) sqlite3PagerPagecount(pBt->pPager, &nPage); - testcase( pBt->nPage!=nPage ); + testcase( pBt->nPage!=(u32)nPage ); pBt->nPage = nPage; } @@ -4839,7 +4863,9 @@ static int accessPayload( assert( pPage ); assert( eOp==0 || eOp==1 ); assert( pCur->eState==CURSOR_VALID ); - assert( pCur->ixnCell ); + if( pCur->ix>=pPage->nCell ){ + return SQLITE_CORRUPT_PAGE(pPage); + } assert( cursorHoldsMutex(pCur) ); getCellInfo(pCur); @@ -5026,7 +5052,6 @@ int sqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>=0 && pCur->pPage ); - assert( pCur->ixpPage->nCell ); return accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0); } @@ -5088,7 +5113,7 @@ static const void *fetchPayload( assert( pCur->eState==CURSOR_VALID ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( cursorOwnsBtShared(pCur) ); - assert( pCur->ixpPage->nCell ); + assert( pCur->ixpPage->nCell || CORRUPT_DB ); assert( pCur->info.nSize>0 ); assert( pCur->info.pPayload>pCur->pPage->aData || CORRUPT_DB ); assert( pCur->info.pPayloadpPage->aDataEnd ||CORRUPT_DB); @@ -5421,12 +5446,8 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ return rc; } -/* Move the cursor so that it points to an entry near the key -** specified by pIdxKey or intKey. Return a success code. -** -** For INTKEY tables, the intKey parameter is used. pIdxKey -** must be NULL. For index tables, pIdxKey is used and intKey -** is ignored. +/* Move the cursor so that it points to an entry in a table (a.k.a INTKEY) +** table near the key intKey. Return a success code. ** ** If an exact match is not found, then the cursor is always ** left pointing at a leaf page which would hold the entry if it @@ -5439,39 +5460,32 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ ** *pRes is as follows: ** ** *pRes<0 The cursor is left pointing at an entry that -** is smaller than intKey/pIdxKey or if the table is empty +** is smaller than intKey or if the table is empty ** and the cursor is therefore left point to nothing. ** ** *pRes==0 The cursor is left pointing at an entry that -** exactly matches intKey/pIdxKey. +** exactly matches intKey. ** ** *pRes>0 The cursor is left pointing at an entry that -** is larger than intKey/pIdxKey. -** -** For index tables, the pIdxKey->eqSeen field is set to 1 if there -** exists an entry in the table that exactly matches pIdxKey. +** is larger than intKey. */ -int sqlite3BtreeMovetoUnpacked( +int sqlite3BtreeTableMoveto( BtCursor *pCur, /* The cursor to be moved */ - UnpackedRecord *pIdxKey, /* Unpacked index key */ i64 intKey, /* The table key */ int biasRight, /* If true, bias the search to the high end */ int *pRes /* Write search results here */ ){ int rc; - RecordCompare xRecordCompare; assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( pRes ); - assert( (pIdxKey==0)==(pCur->pKeyInfo==0) ); - assert( pCur->eState!=CURSOR_VALID || (pIdxKey==0)==(pCur->curIntKey!=0) ); + assert( pCur->pKeyInfo==0 ); + assert( pCur->eState!=CURSOR_VALID || pCur->curIntKey!=0 ); /* If the cursor is already positioned at the point we are trying ** to move to, then just return without doing any work */ - if( pIdxKey==0 - && pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 - ){ + if( pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 ){ if( pCur->info.nKey==intKey ){ *pRes = 0; return SQLITE_OK; @@ -5493,9 +5507,7 @@ int sqlite3BtreeMovetoUnpacked( if( pCur->info.nKey==intKey ){ return SQLITE_OK; } - }else if( rc==SQLITE_DONE ){ - rc = SQLITE_OK; - }else{ + }else if( rc!=SQLITE_DONE ){ return rc; } } @@ -5506,17 +5518,6 @@ int sqlite3BtreeMovetoUnpacked( pCur->pBtree->nSeek++; /* Performance measurement during testing */ #endif - if( pIdxKey ){ - xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); - pIdxKey->errCode = 0; - assert( pIdxKey->default_rc==1 - || pIdxKey->default_rc==0 - || pIdxKey->default_rc==-1 - ); - }else{ - xRecordCompare = 0; /* All keys are integers */ - } - rc = moveToRoot(pCur); if( rc ){ if( rc==SQLITE_EMPTY ){ @@ -5531,7 +5532,8 @@ int sqlite3BtreeMovetoUnpacked( assert( pCur->eState==CURSOR_VALID ); assert( pCur->pPage->nCell > 0 ); assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey ); - assert( pCur->curIntKey || pIdxKey ); + assert( pCur->curIntKey ); + for(;;){ int lwr, upr, idx, c; Pgno chldPg; @@ -5545,133 +5547,238 @@ int sqlite3BtreeMovetoUnpacked( ** be the right kind (index or table) of b-tree page. Otherwise ** a moveToChild() or moveToRoot() call would have detected corruption. */ assert( pPage->nCell>0 ); - assert( pPage->intKey==(pIdxKey==0) ); + assert( pPage->intKey ); lwr = 0; upr = pPage->nCell-1; assert( biasRight==0 || biasRight==1 ); idx = upr>>(1-biasRight); /* idx = biasRight ? upr : (lwr+upr)/2; */ pCur->ix = (u16)idx; - if( xRecordCompare==0 ){ - for(;;){ - i64 nCellKey; - pCell = findCellPastPtr(pPage, idx); - if( pPage->intKeyLeaf ){ - while( 0x80 <= *(pCell++) ){ - if( pCell>=pPage->aDataEnd ){ - return SQLITE_CORRUPT_PAGE(pPage); - } + for(;;){ + i64 nCellKey; + pCell = findCellPastPtr(pPage, idx); + if( pPage->intKeyLeaf ){ + while( 0x80 <= *(pCell++) ){ + if( pCell>=pPage->aDataEnd ){ + return SQLITE_CORRUPT_PAGE(pPage); } } - getVarint(pCell, (u64*)&nCellKey); - if( nCellKeyupr ){ c = -1; break; } - }else if( nCellKey>intKey ){ - upr = idx-1; - if( lwr>upr ){ c = +1; break; } + } + getVarint(pCell, (u64*)&nCellKey); + if( nCellKeyupr ){ c = -1; break; } + }else if( nCellKey>intKey ){ + upr = idx-1; + if( lwr>upr ){ c = +1; break; } + }else{ + assert( nCellKey==intKey ); + pCur->ix = (u16)idx; + if( !pPage->leaf ){ + lwr = idx; + goto moveto_table_next_layer; }else{ - assert( nCellKey==intKey ); - pCur->ix = (u16)idx; - if( !pPage->leaf ){ - lwr = idx; - goto moveto_next_layer; - }else{ - pCur->curFlags |= BTCF_ValidNKey; - pCur->info.nKey = nCellKey; - pCur->info.nSize = 0; - *pRes = 0; - return SQLITE_OK; - } + pCur->curFlags |= BTCF_ValidNKey; + pCur->info.nKey = nCellKey; + pCur->info.nSize = 0; + *pRes = 0; + return SQLITE_OK; } - assert( lwr+upr>=0 ); - idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ } + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ + } + assert( lwr==upr+1 || !pPage->leaf ); + assert( pPage->isInit ); + if( pPage->leaf ){ + assert( pCur->ixpPage->nCell ); + pCur->ix = (u16)idx; + *pRes = c; + rc = SQLITE_OK; + goto moveto_table_finish; + } +moveto_table_next_layer: + if( lwr>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); }else{ - for(;;){ - int nCell; /* Size of the pCell cell in bytes */ - pCell = findCellPastPtr(pPage, idx); - - /* The maximum supported page-size is 65536 bytes. This means that - ** the maximum number of record bytes stored on an index B-Tree - ** page is less than 16384 bytes and may be stored as a 2-byte - ** varint. This information is used to attempt to avoid parsing - ** the entire cell by checking for the cases where the record is - ** stored entirely within the b-tree page by inspecting the first - ** 2 bytes of the cell. - */ - nCell = pCell[0]; - if( nCell<=pPage->max1bytePayload ){ - /* This branch runs if the record-size field of the cell is a - ** single byte varint and the record fits entirely on the main - ** b-tree page. */ - testcase( pCell+nCell+1==pPage->aDataEnd ); - c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); - }else if( !(pCell[1] & 0x80) - && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal - ){ - /* The record-size field is a 2 byte varint and the record - ** fits entirely on the main b-tree page. */ - testcase( pCell+nCell+2==pPage->aDataEnd ); - c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); - }else{ - /* The record flows over onto one or more overflow pages. In - ** this case the whole cell needs to be parsed, a buffer allocated - ** and accessPayload() used to retrieve the record into the - ** buffer before VdbeRecordCompare() can be called. - ** - ** If the record is corrupt, the xRecordCompare routine may read - ** up to two varints past the end of the buffer. An extra 18 - ** bytes of padding is allocated at the end of the buffer in - ** case this happens. */ - void *pCellKey; - u8 * const pCellBody = pCell - pPage->childPtrSize; - const int nOverrun = 18; /* Size of the overrun padding */ - pPage->xParseCell(pPage, pCellBody, &pCur->info); - nCell = (int)pCur->info.nKey; - testcase( nCell<0 ); /* True if key size is 2^32 or more */ - testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */ - testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ - testcase( nCell==2 ); /* Minimum legal index key size */ - if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){ - rc = SQLITE_CORRUPT_PAGE(pPage); - goto moveto_finish; - } - pCellKey = sqlite3Malloc( nCell+nOverrun ); - if( pCellKey==0 ){ - rc = SQLITE_NOMEM_BKPT; - goto moveto_finish; - } - pCur->ix = (u16)idx; - rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); - memset(((u8*)pCellKey)+nCell,0,nOverrun); /* Fix uninit warnings */ - pCur->curFlags &= ~BTCF_ValidOvfl; - if( rc ){ - sqlite3_free(pCellKey); - goto moveto_finish; - } - c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); - sqlite3_free(pCellKey); + chldPg = get4byte(findCell(pPage, lwr)); + } + pCur->ix = (u16)lwr; + rc = moveToChild(pCur, chldPg); + if( rc ) break; + } +moveto_table_finish: + pCur->info.nSize = 0; + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); + return rc; +} + +/* Move the cursor so that it points to an entry in an index table +** near the key pIdxKey. Return a success code. +** +** If an exact match is not found, then the cursor is always +** left pointing at a leaf page which would hold the entry if it +** were present. The cursor might point to an entry that comes +** before or after the key. +** +** An integer is written into *pRes which is the result of +** comparing the key with the entry to which the cursor is +** pointing. The meaning of the integer written into +** *pRes is as follows: +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than pIdxKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches pIdxKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than pIdxKey. +** +** The pIdxKey->eqSeen field is set to 1 if there +** exists an entry in the table that exactly matches pIdxKey. +*/ +int sqlite3BtreeIndexMoveto( + BtCursor *pCur, /* The cursor to be moved */ + UnpackedRecord *pIdxKey, /* Unpacked index key */ + int *pRes /* Write search results here */ +){ + int rc; + RecordCompare xRecordCompare; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + assert( pRes ); + assert( pCur->pKeyInfo!=0 ); + +#ifdef SQLITE_DEBUG + pCur->pBtree->nSeek++; /* Performance measurement during testing */ +#endif + + xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); + pIdxKey->errCode = 0; + assert( pIdxKey->default_rc==1 + || pIdxKey->default_rc==0 + || pIdxKey->default_rc==-1 + ); + + rc = moveToRoot(pCur); + if( rc ){ + if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + *pRes = -1; + return SQLITE_OK; + } + return rc; + } + assert( pCur->pPage ); + assert( pCur->pPage->isInit ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->pPage->nCell > 0 ); + assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey ); + assert( pCur->curIntKey || pIdxKey ); + for(;;){ + int lwr, upr, idx, c; + Pgno chldPg; + MemPage *pPage = pCur->pPage; + u8 *pCell; /* Pointer to current cell in pPage */ + + /* pPage->nCell must be greater than zero. If this is the root-page + ** the cursor would have been INVALID above and this for(;;) loop + ** not run. If this is not the root-page, then the moveToChild() routine + ** would have already detected db corruption. Similarly, pPage must + ** be the right kind (index or table) of b-tree page. Otherwise + ** a moveToChild() or moveToRoot() call would have detected corruption. */ + assert( pPage->nCell>0 ); + assert( pPage->intKey==(pIdxKey==0) ); + lwr = 0; + upr = pPage->nCell-1; + idx = upr>>1; /* idx = (lwr+upr)/2; */ + pCur->ix = (u16)idx; + for(;;){ + int nCell; /* Size of the pCell cell in bytes */ + pCell = findCellPastPtr(pPage, idx); + + /* The maximum supported page-size is 65536 bytes. This means that + ** the maximum number of record bytes stored on an index B-Tree + ** page is less than 16384 bytes and may be stored as a 2-byte + ** varint. This information is used to attempt to avoid parsing + ** the entire cell by checking for the cases where the record is + ** stored entirely within the b-tree page by inspecting the first + ** 2 bytes of the cell. + */ + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + testcase( pCell+nCell+1==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + testcase( pCell+nCell+2==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* The record flows over onto one or more overflow pages. In + ** this case the whole cell needs to be parsed, a buffer allocated + ** and accessPayload() used to retrieve the record into the + ** buffer before VdbeRecordCompare() can be called. + ** + ** If the record is corrupt, the xRecordCompare routine may read + ** up to two varints past the end of the buffer. An extra 18 + ** bytes of padding is allocated at the end of the buffer in + ** case this happens. */ + void *pCellKey; + u8 * const pCellBody = pCell - pPage->childPtrSize; + const int nOverrun = 18; /* Size of the overrun padding */ + pPage->xParseCell(pPage, pCellBody, &pCur->info); + nCell = (int)pCur->info.nKey; + testcase( nCell<0 ); /* True if key size is 2^32 or more */ + testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */ + testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ + testcase( nCell==2 ); /* Minimum legal index key size */ + if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){ + rc = SQLITE_CORRUPT_PAGE(pPage); + goto moveto_index_finish; } - assert( - (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) - && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) - ); - if( c<0 ){ - lwr = idx+1; - }else if( c>0 ){ - upr = idx-1; - }else{ - assert( c==0 ); - *pRes = 0; - rc = SQLITE_OK; - pCur->ix = (u16)idx; - if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT; - goto moveto_finish; + pCellKey = sqlite3Malloc( nCell+nOverrun ); + if( pCellKey==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto moveto_index_finish; } - if( lwr>upr ) break; - assert( lwr+upr>=0 ); - idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ + pCur->ix = (u16)idx; + rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); + memset(((u8*)pCellKey)+nCell,0,nOverrun); /* Fix uninit warnings */ + pCur->curFlags &= ~BTCF_ValidOvfl; + if( rc ){ + sqlite3_free(pCellKey); + goto moveto_index_finish; + } + c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); + sqlite3_free(pCellKey); + } + assert( + (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) + && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) + ); + if( c<0 ){ + lwr = idx+1; + }else if( c>0 ){ + upr = idx-1; + }else{ + assert( c==0 ); + *pRes = 0; + rc = SQLITE_OK; + pCur->ix = (u16)idx; + if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT; + goto moveto_index_finish; } + if( lwr>upr ) break; + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ } assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); assert( pPage->isInit ); @@ -5680,9 +5787,8 @@ int sqlite3BtreeMovetoUnpacked( pCur->ix = (u16)idx; *pRes = c; rc = SQLITE_OK; - goto moveto_finish; + goto moveto_index_finish; } -moveto_next_layer: if( lwr>=pPage->nCell ){ chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); }else{ @@ -5692,7 +5798,7 @@ int sqlite3BtreeMovetoUnpacked( rc = moveToChild(pCur, chldPg); if( rc ) break; } -moveto_finish: +moveto_index_finish: pCur->info.nSize = 0; assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); return rc; @@ -5793,16 +5899,6 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ return SQLITE_CORRUPT_BKPT; } - /* If the database file is corrupt, it is possible for the value of idx - ** to be invalid here. This can only occur if a second cursor modifies - ** the page while cursor pCur is holding a reference to it. Which can - ** only happen if the database is corrupt in such a way as to link the - ** page into more than one b-tree structure. - ** - ** Update 2019-12-23: appears to long longer be possible after the - ** addition of anotherValidCursor() condition on balance_deeper(). */ - harmless( idx>pPage->nCell ); - if( idx>=pPage->nCell ){ if( !pPage->leaf ){ rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); @@ -6290,7 +6386,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ assert( CORRUPT_DB || iPage>1 ); assert( !pMemPage || pMemPage->pgno==iPage ); - if( iPage<2 || iPage>pBt->nPage ){ + if( NEVER(iPage<2) || iPage>pBt->nPage ){ return SQLITE_CORRUPT_BKPT; } if( pMemPage ){ @@ -6714,16 +6810,18 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */ if( *pRC ) return; - assert( idx>=0 && idxnCell ); + assert( idx>=0 ); + assert( idxnCell ); assert( CORRUPT_DB || sz==cellSize(pPage, idx) ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( pPage->nFree>=0 ); data = pPage->aData; ptr = &pPage->aCellIdx[2*idx]; + assert( pPage->pBt->usableSize > (int)(ptr-data) ); pc = get2byte(ptr); hdr = pPage->hdrOffset; - testcase( pc==get2byte(&data[hdr+5]) ); + testcase( pc==(u32)get2byte(&data[hdr+5]) ); testcase( pc+sz==pPage->pBt->usableSize ); if( pc+sz > pPage->pBt->usableSize ){ *pRC = SQLITE_CORRUPT_BKPT; @@ -7015,7 +7113,7 @@ static int rebuildPage( assert( i(u32)usableSize) ){ j = 0; } + if( j>(u32)usableSize ){ j = 0; } memcpy(&pTmp[j], &aData[j], usableSize - j); for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kpPg->aDataEnd ) goto editpage_fail; /* Add cells to the start of the page */ if( iNewpBt; assert( sqlite3_mutex_held(pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); @@ -7676,6 +7774,7 @@ static int balance_nonroot( goto balance_cleanup; } } + nMaxCells += apOld[i]->nCell + ArraySize(pParent->apOvfl); if( (i--)==0 ) break; if( pParent->nOverflow && i+nxDiv==pParent->aiOvfl[0] ){ @@ -7717,7 +7816,6 @@ static int balance_nonroot( /* Make nMaxCells a multiple of 4 in order to preserve 8-byte ** alignment */ - nMaxCells = nOld*(MX_CELL(pBt) + ArraySize(pParent->apOvfl)); nMaxCells = (nMaxCells + 3)&~3; /* @@ -8000,7 +8098,9 @@ static int balance_nonroot( apOld[i] = 0; rc = sqlite3PagerWrite(pNew->pDbPage); nNew++; - if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) ){ + if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) + && rc==SQLITE_OK + ){ rc = SQLITE_CORRUPT_BKPT; } if( rc ) goto balance_cleanup; @@ -8420,7 +8520,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ ** Return SQLITE_CORRUPT if any cursor other than pCur is currently valid ** on the same B-tree as pCur. ** -** This can if a database is corrupt with two or more SQL tables +** This can occur if a database is corrupt with two or more SQL tables ** pointing to the same b-tree. If an insert occurs on one SQL table ** and causes a BEFORE TRIGGER to do a secondary insert on the other SQL ** table linked to the same b-tree. If the secondary insert causes a @@ -8649,7 +8749,7 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ do{ rc = btreeGetPage(pBt, ovflPgno, &pPage, 0); if( rc ) return rc; - if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 ){ + if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){ rc = SQLITE_CORRUPT_BKPT; }else{ if( iOffset+ovflPageSize<(u32)nTotal ){ @@ -8798,7 +8898,8 @@ int sqlite3BtreeInsert( ** to an adjacent cell. Move the cursor so that it is pointing either ** to the cell to be overwritten or an adjacent cell. */ - rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, flags!=0, &loc); + rc = sqlite3BtreeTableMoveto(pCur, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); if( rc ) return rc; } }else{ @@ -8821,13 +8922,11 @@ int sqlite3BtreeInsert( r.aMem = pX->aMem; r.nField = pX->nMem; r.default_rc = 0; - r.errCode = 0; - r.r1 = 0; - r.r2 = 0; r.eqSeen = 0; - rc = sqlite3BtreeMovetoUnpacked(pCur, &r, 0, flags!=0, &loc); + rc = sqlite3BtreeIndexMoveto(pCur, &r, &loc); }else{ - rc = btreeMoveto(pCur, pX->pKey, pX->nKey, flags!=0, &loc); + rc = btreeMoveto(pCur, pX->pKey, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); } if( rc ) return rc; } @@ -8890,7 +8989,10 @@ int sqlite3BtreeInsert( idx = pCur->ix; if( loc==0 ){ CellInfo info; - assert( idxnCell ); + assert( idx>=0 ); + if( idx>=pPage->nCell ){ + return SQLITE_CORRUPT_BKPT; + } rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ){ goto end_insert; @@ -9072,7 +9174,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ } }while( rc==SQLITE_OK && nOut>0 ); - if( rc==SQLITE_OK && nRem>0 ){ + if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ Pgno pgnoNew; MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); @@ -9145,7 +9247,12 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ iCellIdx = pCur->ix; pPage = pCur->pPage; pCell = findCell(pPage, iCellIdx); - if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ) return SQLITE_CORRUPT; + if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ){ + return SQLITE_CORRUPT_BKPT; + } + if( pPage->nCell<=iCellIdx ){ + return SQLITE_CORRUPT_BKPT; + } /* If the bPreserve flag is set to true, then the cursor position must ** be preserved following this delete operation. If the current delete @@ -9461,7 +9568,7 @@ static int clearDatabasePage( BtShared *pBt, /* The BTree that contains the table */ Pgno pgno, /* Page number to clear */ int freePageFlag, /* Deallocate page if true */ - int *pnChange /* Add number of Cells freed to this counter */ + i64 *pnChange /* Add number of Cells freed to this counter */ ){ MemPage *pPage; int rc; @@ -9476,11 +9583,12 @@ static int clearDatabasePage( } rc = getAndInitPage(pBt, pgno, &pPage, 0, 0); if( rc ) return rc; - if( pPage->bBusy ){ + if( (pBt->openFlags & BTREE_SINGLE)==0 + && sqlite3PagerPageRefcount(pPage->pDbPage)!=1 + ){ rc = SQLITE_CORRUPT_BKPT; goto cleardatabasepage_out; } - pPage->bBusy = 1; hdr = pPage->hdrOffset; for(i=0; inCell; i++){ pCell = findCell(pPage, i); @@ -9494,6 +9602,7 @@ static int clearDatabasePage( if( !pPage->leaf ){ rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange); if( rc ) goto cleardatabasepage_out; + if( pPage->intKey ) pnChange = 0; } if( pnChange ){ testcase( !pPage->intKey ); @@ -9506,7 +9615,6 @@ static int clearDatabasePage( } cleardatabasepage_out: - pPage->bBusy = 0; releasePage(pPage); return rc; } @@ -9523,7 +9631,7 @@ static int clearDatabasePage( ** If pnChange is not NULL, then the integer value pointed to by pnChange ** is incremented by the number of entries in the table. */ -int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ +int sqlite3BtreeClearTable(Btree *p, int iTable, i64 *pnChange){ int rc; BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); @@ -9585,10 +9693,10 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ return SQLITE_CORRUPT_BKPT; } - rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); - if( rc ) return rc; rc = sqlite3BtreeClearTable(p, iTable, 0); - if( rc ){ + if( rc ) return rc; + rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); + if( NEVER(rc) ){ releasePage(pPage); return rc; } diff --git a/src/btree.h b/src/btree.h index b7afecc42..f80ba4a97 100644 --- a/src/btree.h +++ b/src/btree.h @@ -123,7 +123,7 @@ int sqlite3BtreeIncrVacuum(Btree *); #define BTREE_BLOBKEY 2 /* Table has keys only - no data */ int sqlite3BtreeDropTable(Btree*, int, int*); -int sqlite3BtreeClearTable(Btree*, int, int*); +int sqlite3BtreeClearTable(Btree*, int, i64*); int sqlite3BtreeClearTableOfCursor(BtCursor*); int sqlite3BtreeTripAllCursors(Btree*, int, int); @@ -247,13 +247,17 @@ void sqlite3BtreeCursorHint(BtCursor*, int, ...); #endif int sqlite3BtreeCloseCursor(BtCursor*); -int sqlite3BtreeMovetoUnpacked( +int sqlite3BtreeTableMoveto( BtCursor*, - UnpackedRecord *pUnKey, i64 intKey, int bias, int *pRes ); +int sqlite3BtreeIndexMoveto( + BtCursor*, + UnpackedRecord *pUnKey, + int *pRes +); int sqlite3BtreeCursorHasMoved(BtCursor*); int sqlite3BtreeCursorRestore(BtCursor*, int*); int sqlite3BtreeDelete(BtCursor*, u8 flags); diff --git a/src/btreeInt.h b/src/btreeInt.h index 37c07fe93..1076fd8f2 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -272,7 +272,6 @@ typedef struct CellInfo CellInfo; */ struct MemPage { u8 isInit; /* True if previously initialized. MUST BE FIRST! */ - u8 bBusy; /* Prevent endless loops on corrupt database files */ u8 intKey; /* True if table b-trees. False for index b-trees */ u8 intKeyLeaf; /* True if the leaf of an intKey table */ Pgno pgno; /* Page number for this page */ diff --git a/src/build.c b/src/build.c index 5bf5680d4..d53ff3b67 100644 --- a/src/build.c +++ b/src/build.c @@ -170,17 +170,21 @@ void sqlite3FinishCoding(Parse *pParse){ int i; int reg; - addrRewind = - sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); - VdbeCoverage(v); - reg = pReturning->iRetReg; - for(i=0; inRetCol; i++){ - sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i); + if( pReturning->nRetCol==0 ){ + assert( CORRUPT_DB ); + }else{ + addrRewind = + sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); + VdbeCoverage(v); + reg = pReturning->iRetReg; + for(i=0; inRetCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i); + sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrRewind); } - sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i); - sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1); - VdbeCoverage(v); - sqlite3VdbeJumpHere(v, addrRewind); } sqlite3VdbeAddOp0(v, OP_Halt); @@ -261,7 +265,11 @@ void sqlite3FinishCoding(Parse *pParse){ if( pParse->bReturning ){ Returning *pRet = pParse->u1.pReturning; - sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + if( pRet->nRetCol==0 ){ + assert( CORRUPT_DB ); + }else{ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + } } /* Finally, jump back to the beginning of the executable code. */ @@ -285,20 +293,22 @@ void sqlite3FinishCoding(Parse *pParse){ /* ** Run the parser and code generator recursively in order to generate ** code for the SQL statement given onto the end of the pParse context -** currently under construction. When the parser is run recursively -** this way, the final OP_Halt is not appended and other initialization -** and finalization steps are omitted because those are handling by the -** outermost parser. +** currently under construction. Notes: +** +** * The final OP_Halt is not appended and other initialization +** and finalization steps are omitted because those are handling by the +** outermost parser. ** -** Not everything is nestable. This facility is designed to permit -** INSERT, UPDATE, and DELETE operations against the schema table. Use -** care if you decide to try to use this routine for some other purposes. +** * Built-in SQL functions always take precedence over application-defined +** SQL functions. In other words, it is not possible to override a +** built-in function. */ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ va_list ap; char *zSql; char *zErrMsg = 0; sqlite3 *db = pParse->db; + u32 savedDbFlags = db->mDbFlags; char saveBuf[PARSE_TAIL_SZ]; if( pParse->nErr ) return; @@ -317,7 +327,9 @@ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ pParse->nested++; memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); + db->mDbFlags |= DBFLAG_PreferBuiltin; sqlite3RunParser(pParse, zSql, &zErrMsg); + db->mDbFlags = savedDbFlags; sqlite3DbFree(db, zErrMsg); sqlite3DbFree(db, zSql); memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); @@ -375,17 +387,17 @@ Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ if( i==1 ){ - if( sqlite3StrICmp(zName+7, &ALT_TEMP_SCHEMA_TABLE[7])==0 - || sqlite3StrICmp(zName+7, &ALT_SCHEMA_TABLE[7])==0 - || sqlite3StrICmp(zName+7, &DFLT_SCHEMA_TABLE[7])==0 + if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 ){ p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, - DFLT_TEMP_SCHEMA_TABLE); + LEGACY_TEMP_SCHEMA_TABLE); } }else{ - if( sqlite3StrICmp(zName+7, &ALT_SCHEMA_TABLE[7])==0 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, - DFLT_SCHEMA_TABLE); + LEGACY_SCHEMA_TABLE); } } } @@ -403,11 +415,11 @@ Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ if( p ) break; } if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ - if( sqlite3StrICmp(zName+7, &ALT_SCHEMA_TABLE[7])==0 ){ - p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, DFLT_SCHEMA_TABLE); - }else if( sqlite3StrICmp(zName+7, &ALT_TEMP_SCHEMA_TABLE[7])==0 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, LEGACY_SCHEMA_TABLE); + }else if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, - DFLT_TEMP_SCHEMA_TABLE); + LEGACY_TEMP_SCHEMA_TABLE); } } } @@ -453,6 +465,7 @@ Table *sqlite3LocateTable( pMod = sqlite3PragmaVtabRegister(db, zName); } if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ + testcase( pMod->pEpoTab==0 ); return pMod->pEpoTab; } } @@ -502,6 +515,22 @@ Table *sqlite3LocateTableItem( return sqlite3LocateTable(pParse, flags, p->zName, zDb); } +/* +** Return the preferred table name for system tables. Translate legacy +** names into the new preferred names, as appropriate. +*/ +const char *sqlite3PreferredTableName(const char *zName){ + if( sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_SCHEMA_TABLE; + } + if( sqlite3StrICmp(zName+7, &LEGACY_TEMP_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_TEMP_SCHEMA_TABLE; + } + } + return zName; +} + /* ** Locate the in-memory structure that describes ** a particular index given the name of that index @@ -666,6 +695,84 @@ void sqlite3CommitInternalChanges(sqlite3 *db){ db->mDbFlags &= ~DBFLAG_SchemaChange; } +/* +** Set the expression associated with a column. This is usually +** the DEFAULT value, but might also be the expression that computes +** the value for a generated column. +*/ +void sqlite3ColumnSetExpr( + Parse *pParse, /* Parsing context */ + Table *pTab, /* The table containing the column */ + Column *pCol, /* The column to receive the new DEFAULT expression */ + Expr *pExpr /* The new default expression */ +){ + ExprList *pList; + assert( IsOrdinaryTable(pTab) ); + pList = pTab->u.tab.pDfltList; + if( pCol->iDflt==0 + || NEVER(pList==0) + || NEVER(pList->nExpriDflt) + ){ + pCol->iDflt = pList==0 ? 1 : pList->nExpr+1; + pTab->u.tab.pDfltList = sqlite3ExprListAppend(pParse, pList, pExpr); + }else{ + sqlite3ExprDelete(pParse->db, pList->a[pCol->iDflt-1].pExpr); + pList->a[pCol->iDflt-1].pExpr = pExpr; + } +} + +/* +** Return the expression associated with a column. The expression might be +** the DEFAULT clause or the AS clause of a generated column. +** Return NULL if the column has no associated expression. +*/ +Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){ + if( pCol->iDflt==0 ) return 0; + if( NEVER(!IsOrdinaryTable(pTab)) ) return 0; + if( NEVER(pTab->u.tab.pDfltList==0) ) return 0; + if( NEVER(pTab->u.tab.pDfltList->nExpriDflt) ) return 0; + return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; +} + +/* +** Set the collating sequence name for a column. +*/ +void sqlite3ColumnSetColl( + sqlite3 *db, + Column *pCol, + const char *zColl +){ + i64 nColl; + i64 n; + char *zNew; + assert( zColl!=0 ); + n = sqlite3Strlen30(pCol->zCnName) + 1; + if( pCol->colFlags & COLFLAG_HASTYPE ){ + n += sqlite3Strlen30(pCol->zCnName+n) + 1; + } + nColl = sqlite3Strlen30(zColl) + 1; + zNew = sqlite3DbRealloc(db, pCol->zCnName, nColl+n); + if( zNew ){ + pCol->zCnName = zNew; + memcpy(pCol->zCnName + n, zColl, nColl); + pCol->colFlags |= COLFLAG_HASCOLL; + } +} + +/* +** Return the collating squence name for a column +*/ +const char *sqlite3ColumnColl(Column *pCol){ + const char *z; + if( (pCol->colFlags & COLFLAG_HASCOLL)==0 ) return 0; + z = pCol->zCnName; + while( *z ){ z++; } + if( pCol->colFlags & COLFLAG_HASTYPE ){ + do{ z++; }while( *z ); + } + return z+1; +} + /* ** Delete memory allocated for the column names of a table or view (the ** Table.aCol[] array). @@ -676,12 +783,20 @@ void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ assert( pTable!=0 ); if( (pCol = pTable->aCol)!=0 ){ for(i=0; inCol; i++, pCol++){ - assert( pCol->zName==0 || pCol->hName==sqlite3StrIHash(pCol->zName) ); - sqlite3DbFree(db, pCol->zName); - sqlite3ExprDelete(db, pCol->pDflt); - sqlite3DbFree(db, pCol->zColl); + assert( pCol->zCnName==0 || pCol->hName==sqlite3StrIHash(pCol->zCnName) ); + sqlite3DbFree(db, pCol->zCnName); } sqlite3DbFree(db, pTable->aCol); + if( IsOrdinaryTable(pTable) ){ + sqlite3ExprListDelete(db, pTable->u.tab.pDfltList); + } + if( db==0 || db->pnBytesFreed==0 ){ + pTable->aCol = 0; + pTable->nCol = 0; + if( IsOrdinaryTable(pTable) ){ + pTable->u.tab.pDfltList = 0; + } + } } } @@ -733,19 +848,25 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ sqlite3FreeIndex(db, pIndex); } - /* Delete any foreign keys attached to this table. */ - sqlite3FkDelete(db, pTable); + if( IsOrdinaryTable(pTable) ){ + sqlite3FkDelete(db, pTable); + } +#ifndef SQLITE_OMIT_VIRTUAL_TABLE + else if( IsVirtual(pTable) ){ + sqlite3VtabClear(db, pTable); + } +#endif + else{ + assert( IsView(pTable) ); + sqlite3SelectDelete(db, pTable->u.view.pSelect); + } /* Delete the Table structure itself. */ sqlite3DeleteColumnNames(db, pTable); sqlite3DbFree(db, pTable->zName); sqlite3DbFree(db, pTable->zColAff); - sqlite3SelectDelete(db, pTable->pSelect); sqlite3ExprListDelete(db, pTable->pCheck); -#ifndef SQLITE_OMIT_VIRTUALTABLE - sqlite3VtabClear(db, pTable); -#endif sqlite3DbFree(db, pTable); /* Verify that no lookaside memory was used by schema tables */ @@ -791,10 +912,10 @@ void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char *zTabName){ ** are not \000 terminated and are not persistent. The returned string ** is \000 terminated and is persistent. */ -char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ +char *sqlite3NameFromToken(sqlite3 *db, const Token *pName){ char *zName; if( pName ){ - zName = sqlite3DbStrNDup(db, (char*)pName->z, pName->n); + zName = sqlite3DbStrNDup(db, (const char*)pName->z, pName->n); sqlite3Dequote(zName); }else{ zName = 0; @@ -808,7 +929,7 @@ char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ */ void sqlite3OpenSchemaTable(Parse *p, int iDb){ Vdbe *v = sqlite3GetVdbe(p); - sqlite3TableLock(p, iDb, SCHEMA_ROOT, 1, DFLT_SCHEMA_TABLE); + sqlite3TableLock(p, iDb, SCHEMA_ROOT, 1, LEGACY_SCHEMA_TABLE); sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, SCHEMA_ROOT, iDb, 5); if( p->nTab==0 ){ p->nTab = 1; @@ -1271,6 +1392,7 @@ void sqlite3StartTable( /* If an error occurs, we jump here */ begin_table_error: + pParse->checkSchema = 1; sqlite3DbFree(db, zName); return; } @@ -1280,7 +1402,7 @@ void sqlite3StartTable( */ #if SQLITE_ENABLE_HIDDEN_COLUMNS void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ - if( sqlite3_strnicmp(pCol->zName, "__hidden__", 10)==0 ){ + if( sqlite3_strnicmp(pCol->zCnName, "__hidden__", 10)==0 ){ pCol->colFlags |= COLFLAG_HIDDEN; if( pTab ) pTab->tabFlags |= TF_HasHidden; }else if( pTab && pCol!=pTab->aCol && (pCol[-1].colFlags & COLFLAG_HIDDEN) ){ @@ -1371,7 +1493,7 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){ ** first to get things going. Then this routine is called for each ** column. */ -void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ +void sqlite3AddColumn(Parse *pParse, Token sName, Token sType){ Table *p; int i; char *z; @@ -1379,55 +1501,96 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ Column *pCol; sqlite3 *db = pParse->db; u8 hName; + Column *aNew; + u8 eType = COLTYPE_CUSTOM; + u8 szEst = 1; + char affinity = SQLITE_AFF_BLOB; if( (p = pParse->pNewTable)==0 ) return; if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName); return; } - z = sqlite3DbMallocRaw(db, pName->n + pType->n + 2); + if( !IN_RENAME_OBJECT ) sqlite3DequoteToken(&sName); + + /* Because keywords GENERATE ALWAYS can be converted into indentifiers + ** by the parser, we can sometimes end up with a typename that ends + ** with "generated always". Check for this case and omit the surplus + ** text. */ + if( sType.n>=16 + && sqlite3_strnicmp(sType.z+(sType.n-6),"always",6)==0 + ){ + sType.n -= 6; + while( ALWAYS(sType.n>0) && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + if( sType.n>=9 + && sqlite3_strnicmp(sType.z+(sType.n-9),"generated",9)==0 + ){ + sType.n -= 9; + while( sType.n>0 && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + } + } + + /* Check for standard typenames. For standard typenames we will + ** set the Column.eType field rather than storing the typename after + ** the column name, in order to save space. */ + if( sType.n>=3 ){ + sqlite3DequoteToken(&sType); + for(i=0; i0) ); if( z==0 ) return; - if( IN_RENAME_OBJECT ) sqlite3RenameTokenMap(pParse, (void*)z, pName); - memcpy(z, pName->z, pName->n); - z[pName->n] = 0; + if( IN_RENAME_OBJECT ) sqlite3RenameTokenMap(pParse, (void*)z, &sName); + memcpy(z, sName.z, sName.n); + z[sName.n] = 0; sqlite3Dequote(z); hName = sqlite3StrIHash(z); for(i=0; inCol; i++){ - if( p->aCol[i].hName==hName && sqlite3StrICmp(z, p->aCol[i].zName)==0 ){ + if( p->aCol[i].hName==hName && sqlite3StrICmp(z, p->aCol[i].zCnName)==0 ){ sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); sqlite3DbFree(db, z); return; } } - if( (p->nCol & 0x7)==0 ){ - Column *aNew; - aNew = sqlite3DbRealloc(db,p->aCol,(p->nCol+8)*sizeof(p->aCol[0])); - if( aNew==0 ){ - sqlite3DbFree(db, z); - return; - } - p->aCol = aNew; + aNew = sqlite3DbRealloc(db,p->aCol,((i64)p->nCol+1)*sizeof(p->aCol[0])); + if( aNew==0 ){ + sqlite3DbFree(db, z); + return; } + p->aCol = aNew; pCol = &p->aCol[p->nCol]; memset(pCol, 0, sizeof(p->aCol[0])); - pCol->zName = z; + pCol->zCnName = z; pCol->hName = hName; sqlite3ColumnPropertiesFromName(p, pCol); - if( pType->n==0 ){ + if( sType.n==0 ){ /* If there is no type specified, columns have the default affinity ** 'BLOB' with a default size of 4 bytes. */ - pCol->affinity = SQLITE_AFF_BLOB; - pCol->szEst = 1; + pCol->affinity = affinity; + pCol->eCType = eType; + pCol->szEst = szEst; #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( 4>=sqlite3GlobalConfig.szSorterRef ){ - pCol->colFlags |= COLFLAG_SORTERREF; + if( affinity==SQLITE_AFF_BLOB ){ + if( 4>=sqlite3GlobalConfig.szSorterRef ){ + pCol->colFlags |= COLFLAG_SORTERREF; + } } #endif }else{ zType = z + sqlite3Strlen30(z) + 1; - memcpy(zType, pType->z, pType->n); - zType[pType->n] = 0; + memcpy(zType, sType.z, sType.n); + zType[sType.n] = 0; sqlite3Dequote(zType); pCol->affinity = sqlite3AffinityType(zType, pCol); pCol->colFlags |= COLFLAG_HASTYPE; @@ -1582,7 +1745,7 @@ void sqlite3AddDefaultValue( pCol = &(p->aCol[p->nCol-1]); if( !sqlite3ExprIsConstantOrFunction(pExpr, isInit) ){ sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant", - pCol->zName); + pCol->zCnName); #ifndef SQLITE_OMIT_GENERATED_COLUMNS }else if( pCol->colFlags & COLFLAG_GENERATED ){ testcase( pCol->colFlags & COLFLAG_VIRTUAL ); @@ -1593,15 +1756,15 @@ void sqlite3AddDefaultValue( /* A copy of pExpr is used instead of the original, as pExpr contains ** tokens that point to volatile memory. */ - Expr x; - sqlite3ExprDelete(db, pCol->pDflt); + Expr x, *pDfltExpr; memset(&x, 0, sizeof(x)); x.op = TK_SPAN; x.u.zToken = sqlite3DbSpanDup(db, zStart, zEnd); x.pLeft = pExpr; x.flags = EP_Skip; - pCol->pDflt = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); + pDfltExpr = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); sqlite3DbFree(db, x.u.zToken); + sqlite3ColumnSetExpr(pParse, p, pCol, pDfltExpr); } } if( IN_RENAME_OBJECT ){ @@ -1697,9 +1860,11 @@ void sqlite3AddPrimaryKey( assert( pCExpr!=0 ); sqlite3StringToId(pCExpr); if( pCExpr->op==TK_ID ){ - const char *zCName = pCExpr->u.zToken; + const char *zCName; + assert( !ExprHasProperty(pCExpr, EP_IntValue) ); + zCName = pCExpr->u.zToken; for(iCol=0; iColnCol; iCol++){ - if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){ + if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zCnName)==0 ){ pCol = &pTab->aCol[iCol]; makeColumnPartOfPrimaryKey(pParse, pCol); break; @@ -1710,7 +1875,7 @@ void sqlite3AddPrimaryKey( } if( nTerm==1 && pCol - && sqlite3StrICmp(sqlite3ColumnType(pCol,""), "INTEGER")==0 + && pCol->eCType==COLTYPE_INTEGER && sortOrder!=SQLITE_SO_DESC ){ if( IN_RENAME_OBJECT && pList ){ @@ -1790,8 +1955,7 @@ void sqlite3AddCollateType(Parse *pParse, Token *pToken){ if( sqlite3LocateCollSeq(pParse, zColl) ){ Index *pIdx; - sqlite3DbFree(db, p->aCol[i].zColl); - p->aCol[i].zColl = zColl; + sqlite3ColumnSetColl(db, &p->aCol[i], zColl); /* If the column is declared as " PRIMARY KEY COLLATE ", ** then an index may have been created on this column before the @@ -1800,12 +1964,11 @@ void sqlite3AddCollateType(Parse *pParse, Token *pToken){ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->nKeyCol==1 ); if( pIdx->aiColumn[0]==i ){ - pIdx->azColl[0] = p->aCol[i].zColl; + pIdx->azColl[0] = sqlite3ColumnColl(&p->aCol[i]); } } - }else{ - sqlite3DbFree(db, zColl); } + sqlite3DbFree(db, zColl); } /* Change the most recently parsed column to be a GENERATED ALWAYS AS @@ -1825,7 +1988,7 @@ void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType){ sqlite3ErrorMsg(pParse, "virtual tables cannot use computed columns"); goto generated_done; } - if( pCol->pDflt ) goto generated_error; + if( pCol->iDflt>0 ) goto generated_error; if( pType ){ if( pType->n==7 && sqlite3StrNICmp("virtual",pType->z,7)==0 ){ /* no-op */ @@ -1843,13 +2006,13 @@ void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType){ if( pCol->colFlags & COLFLAG_PRIMKEY ){ makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */ } - pCol->pDflt = pExpr; + sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); pExpr = 0; goto generated_done; generated_error: sqlite3ErrorMsg(pParse, "error in generated column \"%s\"", - pCol->zName); + pCol->zCnName); generated_done: sqlite3ExprDelete(pParse->db, pExpr); #else @@ -1951,7 +2114,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){ Column *pCol; n = 0; for(pCol = p->aCol, i=0; inCol; i++, pCol++){ - n += identLength(pCol->zName) + 5; + n += identLength(pCol->zCnName) + 5; } n += identLength(p->zName); if( n<50 ){ @@ -1987,7 +2150,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){ sqlite3_snprintf(n-k, &zStmt[k], zSep); k += sqlite3Strlen30(&zStmt[k]); zSep = zSep2; - identPut(zStmt, &k, pCol->zName); + identPut(zStmt, &k, pCol->zCnName); assert( pCol->affinity-SQLITE_AFF_BLOB >= 0 ); assert( pCol->affinity-SQLITE_AFF_BLOB < ArraySize(azType) ); testcase( pCol->affinity==SQLITE_AFF_BLOB ); @@ -2071,7 +2234,6 @@ static void estimateIndexWidth(Index *pIdx){ */ static int hasColumn(const i16 *aiCol, int nCol, int x){ while( nCol-- > 0 ){ - assert( aiCol[0]>=0 ); if( x==*(aiCol++) ){ return 1; } @@ -2184,7 +2346,9 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ */ if( !db->init.imposterTable ){ for(i=0; inCol; i++){ - if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 ){ + if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 + && (pTab->aCol[i].notNull==OE_None) + ){ pTab->aCol[i].notNull = OE_Abort; } } @@ -2206,7 +2370,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ if( pTab->iPKey>=0 ){ ExprList *pList; Token ipkToken; - sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName); + sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zCnName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0)); if( pList==0 ){ @@ -2336,7 +2500,7 @@ int sqlite3IsShadowTableOf(sqlite3 *db, Table *pTab, const char *zName){ nName = sqlite3Strlen30(pTab->zName); if( sqlite3_strnicmp(zName, pTab->zName, nName)!=0 ) return 0; if( zName[nName]!='_' ) return 0; - pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->azModuleArg[0]); + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); if( pMod==0 ) return 0; if( pMod->pModule->iVersion<3 ) return 0; if( pMod->pModule->xShadowName==0 ) return 0; @@ -2344,6 +2508,41 @@ int sqlite3IsShadowTableOf(sqlite3 *db, Table *pTab, const char *zName){ } #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Table pTab is a virtual table. If it the virtual table implementation +** exists and has an xShadowName method, then loop over all other ordinary +** tables within the same schema looking for shadow tables of pTab, and mark +** any shadow tables seen using the TF_Shadow flag. +*/ +void sqlite3MarkAllShadowTablesOf(sqlite3 *db, Table *pTab){ + int nName; /* Length of pTab->zName */ + Module *pMod; /* Module for the virtual table */ + HashElem *k; /* For looping through the symbol table */ + + assert( IsVirtual(pTab) ); + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); + if( pMod==0 ) return; + if( NEVER(pMod->pModule==0) ) return; + if( pMod->pModule->iVersion<3 ) return; + if( pMod->pModule->xShadowName==0 ) return; + assert( pTab->zName!=0 ); + nName = sqlite3Strlen30(pTab->zName); + for(k=sqliteHashFirst(&pTab->pSchema->tblHash); k; k=sqliteHashNext(k)){ + Table *pOther = sqliteHashData(k); + assert( pOther->zName!=0 ); + if( !IsOrdinaryTable(pOther) ) continue; + if( pOther->tabFlags & TF_Shadow ) continue; + if( sqlite3StrNICmp(pOther->zName, pTab->zName, nName)==0 + && pOther->zName[nName]=='_' + && pMod->pModule->xShadowName(pOther->zName+nName+1) + ){ + pOther->tabFlags |= TF_Shadow; + } + } +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Return true if zName is a shadow table name in the current database @@ -2417,7 +2616,7 @@ void sqlite3EndTable( Parse *pParse, /* Parse context */ Token *pCons, /* The ',' token after the last column defn. */ Token *pEnd, /* The ')' before options in the CREATE TABLE */ - u8 tabOpts, /* Extra table options. Usually 0. */ + u32 tabOpts, /* Extra table options. Usually 0. */ Select *pSelect /* Select from a "CREATE ... AS SELECT" */ ){ Table *p; /* The new table */ @@ -2445,7 +2644,7 @@ void sqlite3EndTable( ** table itself. So mark it read-only. */ if( db->init.busy ){ - if( pSelect ){ + if( pSelect || (!IsOrdinaryTable(p) && db->init.newTnum) ){ sqlite3ErrorMsg(pParse, ""); return; } @@ -2453,6 +2652,44 @@ void sqlite3EndTable( if( p->tnum==1 ) p->tabFlags |= TF_Readonly; } + /* Special processing for tables that include the STRICT keyword: + ** + ** * Do not allow custom column datatypes. Every column must have + ** a datatype that is one of INT, INTEGER, REAL, TEXT, or BLOB. + ** + ** * If a PRIMARY KEY is defined, other than the INTEGER PRIMARY KEY, + ** then all columns of the PRIMARY KEY must have a NOT NULL + ** constraint. + */ + if( tabOpts & TF_Strict ){ + int ii; + p->tabFlags |= TF_Strict; + for(ii=0; iinCol; ii++){ + Column *pCol = &p->aCol[ii]; + if( pCol->eCType==COLTYPE_CUSTOM ){ + if( pCol->colFlags & COLFLAG_HASTYPE ){ + sqlite3ErrorMsg(pParse, + "unknown datatype for %s.%s: \"%s\"", + p->zName, pCol->zCnName, sqlite3ColumnType(pCol, "") + ); + }else{ + sqlite3ErrorMsg(pParse, "missing datatype for %s.%s", + p->zName, pCol->zCnName); + } + return; + }else if( pCol->eCType==COLTYPE_ANY ){ + pCol->affinity = SQLITE_AFF_BLOB; + } + if( (pCol->colFlags & COLFLAG_PRIMKEY)!=0 + && p->iPKey!=ii + && pCol->notNull == OE_None + ){ + pCol->notNull = OE_Abort; + p->tabFlags |= TF_HasNotNull; + } + } + } + assert( (p->tabFlags & TF_HasPrimaryKey)==0 || p->iPKey>=0 || sqlite3PrimaryKeyIndex(p)!=0 ); assert( (p->tabFlags & TF_HasPrimaryKey)!=0 @@ -2497,7 +2734,7 @@ void sqlite3EndTable( for(ii=0; iinCol; ii++){ u32 colFlags = p->aCol[ii].colFlags; if( (colFlags & COLFLAG_GENERATED)!=0 ){ - Expr *pX = p->aCol[ii].pDflt; + Expr *pX = sqlite3ColumnExpr(p, &p->aCol[ii]); testcase( colFlags & COLFLAG_VIRTUAL ); testcase( colFlags & COLFLAG_STORED ); if( sqlite3ResolveSelfReference(pParse, p, NC_GenCol, pX, 0) ){ @@ -2507,8 +2744,8 @@ void sqlite3EndTable( ** tree that have been allocated from lookaside memory, which is ** illegal in a schema and will lead to errors or heap corruption ** when the database connection closes. */ - sqlite3ExprDelete(db, pX); - p->aCol[ii].pDflt = sqlite3ExprAlloc(db, TK_NULL, 0, 0); + sqlite3ColumnSetExpr(pParse, p, &p->aCol[ii], + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); } }else{ nNG++; @@ -2548,7 +2785,7 @@ void sqlite3EndTable( /* ** Initialize zType for the new view or table. */ - if( p->pSelect==0 ){ + if( IsOrdinaryTable(p) ){ /* A regular table */ zType = "table"; zType2 = "TABLE"; @@ -2634,7 +2871,7 @@ void sqlite3EndTable( ** the information we've collected. */ sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q" " WHERE rowid=#%d", db->aDb[iDb].zDbSName, @@ -2698,12 +2935,12 @@ void sqlite3EndTable( } #ifndef SQLITE_OMIT_ALTERTABLE - if( !pSelect && !p->pSelect ){ + if( !pSelect && IsOrdinaryTable(p) ){ assert( pCons && pEnd ); if( pCons->z==0 ){ pCons = pEnd; } - p->addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z); + p->u.tab.addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z); } #endif } @@ -2760,12 +2997,13 @@ void sqlite3CreateView( */ pSelect->selFlags |= SF_View; if( IN_RENAME_OBJECT ){ - p->pSelect = pSelect; + p->u.view.pSelect = pSelect; pSelect = 0; }else{ - p->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + p->u.view.pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); } p->pCheck = sqlite3ExprListDup(db, pCNames, EXPRDUP_REDUCE); + p->eTabType = TABTYP_VIEW; if( db->mallocFailed ) goto create_view_fail; /* Locate the end of the CREATE VIEW statement. Make sEnd point to @@ -2819,13 +3057,12 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ assert( pTable ); #ifndef SQLITE_OMIT_VIRTUALTABLE - db->nSchemaLock++; - rc = sqlite3VtabCallConnect(pParse, pTable); - db->nSchemaLock--; - if( rc ){ - return 1; + if( IsVirtual(pTable) ){ + db->nSchemaLock++; + rc = sqlite3VtabCallConnect(pParse, pTable); + db->nSchemaLock--; + return rc; } - if( IsVirtual(pTable) ) return 0; #endif #ifndef SQLITE_OMIT_VIEW @@ -2862,8 +3099,8 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ ** to be permanent. So the computation is done on a copy of the SELECT ** statement that defines the view. */ - assert( pTable->pSelect ); - pSel = sqlite3SelectDup(db, pTable->pSelect, 0); + assert( IsView(pTable) ); + pSel = sqlite3SelectDup(db, pTable->u.view.pSelect, 0); if( pSel ){ u8 eParseMode = pParse->eParseMode; pParse->eParseMode = PARSE_MODE_NORMAL; @@ -2922,8 +3159,6 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ pTable->pSchema->schemaFlags |= DB_UnresetViews; if( db->mallocFailed ){ sqlite3DeleteColumnNames(db, pTable); - pTable->aCol = 0; - pTable->nCol = 0; } #endif /* SQLITE_OMIT_VIEW */ return nErr; @@ -2940,10 +3175,8 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){ if( !DbHasProperty(db, idx, DB_UnresetViews) ) return; for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3DeleteColumnNames(db, pTab); - pTab->aCol = 0; - pTab->nCol = 0; } } DbClearProperty(db, idx, DB_UnresetViews); @@ -3017,7 +3250,7 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ ** token for additional information. */ sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET rootpage=%d WHERE #%d AND rootpage=#%d", pParse->db->aDb[iDb].zDbSName, iTable, r1, r1); #endif @@ -3152,7 +3385,7 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ ** database. */ sqlite3NestedParse(pParse, - "DELETE FROM %Q." DFLT_SCHEMA_TABLE + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE tbl_name=%Q and type!='trigger'", pDb->zDbSName, pTab->zName); if( !isView && !IsVirtual(pTab) ){ @@ -3180,6 +3413,7 @@ int sqlite3ReadOnlyShadowTables(sqlite3 *db){ if( (db->flags & SQLITE_Defensive)!=0 && db->pVtabCtx==0 && db->nVdbeExec==0 + && !sqlite3VtabInSync(db) ){ return 1; } @@ -3199,6 +3433,9 @@ static int tableMayNotBeDropped(sqlite3 *db, Table *pTab){ if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){ return 1; } + if( pTab->tabFlags & TF_Eponymous ){ + return 1; + } return 0; } @@ -3283,11 +3520,11 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){ /* Ensure DROP TABLE is not used on a view, and DROP VIEW is not used ** on a table. */ - if( isView && pTab->pSelect==0 ){ + if( isView && !IsView(pTab) ){ sqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName); goto exit_drop_table; } - if( !isView && pTab->pSelect ){ + if( !isView && IsView(pTab) ){ sqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName); goto exit_drop_table; } @@ -3338,7 +3575,7 @@ void sqlite3CreateForeignKey( FKey *pFKey = 0; FKey *pNextTo; Table *p = pParse->pNewTable; - int nByte; + i64 nByte; int i; int nCol; char *z; @@ -3351,7 +3588,7 @@ void sqlite3CreateForeignKey( if( pToCol && pToCol->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "foreign key on %s" " should reference only one column of table %T", - p->aCol[iCol].zName, pTo); + p->aCol[iCol].zCnName, pTo); goto fk_end; } nCol = 1; @@ -3374,7 +3611,8 @@ void sqlite3CreateForeignKey( goto fk_end; } pFKey->pFrom = p; - pFKey->pNextFrom = p->pFKey; + assert( IsOrdinaryTable(p) ); + pFKey->pNextFrom = p->u.tab.pFKey; z = (char*)&pFKey->aCol[nCol]; pFKey->zTo = z; if( IN_RENAME_OBJECT ){ @@ -3391,7 +3629,7 @@ void sqlite3CreateForeignKey( for(i=0; inCol; j++){ - if( sqlite3StrICmp(p->aCol[j].zName, pFromCol->a[i].zEName)==0 ){ + if( sqlite3StrICmp(p->aCol[j].zCnName, pFromCol->a[i].zEName)==0 ){ pFKey->aCol[i].iFrom = j; break; } @@ -3439,7 +3677,8 @@ void sqlite3CreateForeignKey( /* Link the foreign key to the table as the last step. */ - p->pFKey = pFKey; + assert( IsOrdinaryTable(p) ); + p->u.tab.pFKey = pFKey; pFKey = 0; fk_end: @@ -3460,7 +3699,9 @@ void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){ #ifndef SQLITE_OMIT_FOREIGN_KEY Table *pTab; FKey *pFKey; - if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return; + if( (pTab = pParse->pNewTable)==0 ) return; + if( NEVER(!IsOrdinaryTable(pTab)) ) return; + if( (pFKey = pTab->u.tab.pFKey)==0 ) return; assert( isDeferred==0 || isDeferred==1 ); /* EV: R-30323-21917 */ pFKey->isDeferred = (u8)isDeferred; #endif @@ -3752,7 +3993,7 @@ void sqlite3CreateIndex( goto exit_create_index; } #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "views may not be indexed"); goto exit_create_index; } @@ -3843,7 +4084,7 @@ void sqlite3CreateIndex( Token prevCol; Column *pCol = &pTab->aCol[pTab->nCol-1]; pCol->colFlags |= COLFLAG_UNIQUE; - sqlite3TokenInit(&prevCol, pCol->zName); + sqlite3TokenInit(&prevCol, pCol->zCnName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &prevCol, 0)); if( pList==0 ) goto exit_create_index; @@ -3861,6 +4102,7 @@ void sqlite3CreateIndex( Expr *pExpr = pList->a[i].pExpr; assert( pExpr!=0 ); if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken)); } } @@ -3956,6 +4198,7 @@ void sqlite3CreateIndex( zColl = 0; if( pListItem->pExpr->op==TK_COLLATE ){ int nColl; + assert( !ExprHasProperty(pListItem->pExpr, EP_IntValue) ); zColl = pListItem->pExpr->u.zToken; nColl = sqlite3Strlen30(zColl) + 1; assert( nExtra>=nColl ); @@ -3964,7 +4207,7 @@ void sqlite3CreateIndex( zExtra += nColl; nExtra -= nColl; }else if( j>=0 ){ - zColl = pTab->aCol[j].zColl; + zColl = sqlite3ColumnColl(&pTab->aCol[j]); } if( !zColl ) zColl = sqlite3StrBINARY; if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){ @@ -4162,7 +4405,7 @@ void sqlite3CreateIndex( /* Add an entry in sqlite_schema for this index */ sqlite3NestedParse(pParse, - "INSERT INTO %Q." DFLT_SCHEMA_TABLE " VALUES('index',%Q,%Q,#%d,%Q);", + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE " VALUES('index',%Q,%Q,#%d,%Q);", db->aDb[iDb].zDbSName, pIndex->zName, pTab->zName, @@ -4204,7 +4447,7 @@ void sqlite3CreateIndex( ** The list was already ordered when this routine was entered, so at this ** point at most a single index (the newly added index) will be out of ** order. So we have to reorder at most one index. */ - Index **ppFrom = &pTab->pIndex; + Index **ppFrom; Index *pThis; for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){ Index *pNext; @@ -4348,7 +4591,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ if( v ){ sqlite3BeginWriteOperation(pParse, 1, iDb); sqlite3NestedParse(pParse, - "DELETE FROM %Q." DFLT_SCHEMA_TABLE " WHERE name=%Q AND type='index'", + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='index'", db->aDb[iDb].zDbSName, pIndex->zName ); sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName); @@ -4744,6 +4987,7 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ }else{ pItem->u1.zIndexedBy = sqlite3NameFromToken(pParse->db, pIndexedBy); pItem->fg.isIndexedBy = 1; + assert( pItem->fg.isCte==0 ); /* No collision on union u2 */ } } } @@ -5056,7 +5300,7 @@ void sqlite3UniqueConstraint( for(j=0; jnKeyCol; j++){ char *zCol; assert( pIdx->aiColumn[j]>=0 ); - zCol = pTab->aCol[pIdx->aiColumn[j]].zName; + zCol = pTab->aCol[pIdx->aiColumn[j]].zCnName; if( j ) sqlite3_str_append(&errMsg, ", ", 2); sqlite3_str_appendall(&errMsg, pTab->zName); sqlite3_str_append(&errMsg, ".", 1); @@ -5083,7 +5327,7 @@ void sqlite3RowidConstraint( int rc; if( pTab->iPKey>=0 ){ zMsg = sqlite3MPrintf(pParse->db, "%s.%s", pTab->zName, - pTab->aCol[pTab->iPKey].zName); + pTab->aCol[pTab->iPKey].zCnName); rc = SQLITE_CONSTRAINT_PRIMARYKEY; }else{ zMsg = sqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName); diff --git a/src/callback.c b/src/callback.c index 421cef281..936c374ab 100644 --- a/src/callback.c +++ b/src/callback.c @@ -337,6 +337,7 @@ FuncDef *sqlite3FunctionSearch( ){ FuncDef *p; for(p=sqlite3BuiltinFunctions.a[h]; p; p=p->u.pHash){ + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); if( sqlite3StrICmp(p->zName, zFunc)==0 ){ return p; } @@ -358,6 +359,7 @@ void sqlite3InsertBuiltinFuncs( int nName = sqlite3Strlen30(zName); int h = SQLITE_FUNC_HASH(zName[0], nName); assert( zName[0]>='a' && zName[0]<='z' ); + assert( aDef[i].funcFlags & SQLITE_FUNC_BUILTIN ); pOther = sqlite3FunctionSearch(h, zName); if( pOther ){ assert( pOther!=&aDef[i] && pOther->pNext!=&aDef[i] ); diff --git a/src/ctime.c b/src/ctime.c index fa69216c6..de68ea7f5 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -13,7 +13,6 @@ ** This file implements routines used to report what compile-time options ** SQLite was built with. */ - #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS /* IMP: R-16824-07538 */ /* @@ -35,6 +34,7 @@ ** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE="100,100") */ #define CTIMEOPT_VAL2_(opt1,opt2) #opt1 "," #opt2 #define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt) +#include "sqliteInt.h" /* ** An array of names of all compile-time options. This array should @@ -49,13 +49,13 @@ static const char * const sqlite3azCompileOpt[] = { /* ** BEGIN CODE GENERATED BY tool/mkctime.tcl */ -#if SQLITE_32BIT_ROWID +#ifdef SQLITE_32BIT_ROWID "32BIT_ROWID", #endif -#if SQLITE_4_BYTE_ALIGNED_MALLOC +#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC "4_BYTE_ALIGNED_MALLOC", #endif -#if SQLITE_64BIT_STATS +#ifdef SQLITE_64BIT_STATS "64BIT_STATS", #endif #ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN @@ -63,19 +63,22 @@ static const char * const sqlite3azCompileOpt[] = { "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), # endif #endif -#if SQLITE_ALLOW_URI_AUTHORITY +#ifdef SQLITE_ALLOW_URI_AUTHORITY "ALLOW_URI_AUTHORITY", #endif +#ifdef SQLITE_ATOMIC_INTRINSICS + "ATOMIC_INTRINSICS=" CTIMEOPT_VAL(SQLITE_ATOMIC_INTRINSICS), +#endif #ifdef SQLITE_BITMASK_TYPE "BITMASK_TYPE=" CTIMEOPT_VAL(SQLITE_BITMASK_TYPE), #endif -#if SQLITE_BUG_COMPATIBLE_20160819 +#ifdef SQLITE_BUG_COMPATIBLE_20160819 "BUG_COMPATIBLE_20160819", #endif -#if SQLITE_CASE_SENSITIVE_LIKE +#ifdef SQLITE_CASE_SENSITIVE_LIKE "CASE_SENSITIVE_LIKE", #endif -#if SQLITE_CHECK_PAGES +#ifdef SQLITE_CHECK_PAGES "CHECK_PAGES", #endif #if defined(__clang__) && defined(__clang_major__) @@ -87,22 +90,22 @@ static const char * const sqlite3azCompileOpt[] = { #elif defined(__GNUC__) && defined(__VERSION__) "COMPILER=gcc-" __VERSION__, #endif -#if SQLITE_COVERAGE_TEST +#ifdef SQLITE_COVERAGE_TEST "COVERAGE_TEST", #endif -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG "DEBUG", #endif -#if SQLITE_DEFAULT_AUTOMATIC_INDEX +#ifdef SQLITE_DEFAULT_AUTOMATIC_INDEX "DEFAULT_AUTOMATIC_INDEX", #endif -#if SQLITE_DEFAULT_AUTOVACUUM +#ifdef SQLITE_DEFAULT_AUTOVACUUM "DEFAULT_AUTOVACUUM", #endif #ifdef SQLITE_DEFAULT_CACHE_SIZE "DEFAULT_CACHE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_CACHE_SIZE), #endif -#if SQLITE_DEFAULT_CKPTFULLFSYNC +#ifdef SQLITE_DEFAULT_CKPTFULLFSYNC "DEFAULT_CKPTFULLFSYNC", #endif #ifdef SQLITE_DEFAULT_FILE_FORMAT @@ -111,7 +114,7 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_DEFAULT_FILE_PERMISSIONS "DEFAULT_FILE_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_PERMISSIONS), #endif -#if SQLITE_DEFAULT_FOREIGN_KEYS +#ifdef SQLITE_DEFAULT_FOREIGN_KEYS "DEFAULT_FOREIGN_KEYS", #endif #ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT @@ -140,7 +143,7 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS "DEFAULT_PROXYDIR_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_PROXYDIR_PERMISSIONS), #endif -#if SQLITE_DEFAULT_RECURSIVE_TRIGGERS +#ifdef SQLITE_DEFAULT_RECURSIVE_TRIGGERS "DEFAULT_RECURSIVE_TRIGGERS", #endif #ifdef SQLITE_DEFAULT_ROWEST @@ -161,196 +164,196 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_DEFAULT_WORKER_THREADS "DEFAULT_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WORKER_THREADS), #endif -#if SQLITE_DIRECT_OVERFLOW_READ +#ifdef SQLITE_DIRECT_OVERFLOW_READ "DIRECT_OVERFLOW_READ", #endif -#if SQLITE_DISABLE_DIRSYNC +#ifdef SQLITE_DISABLE_DIRSYNC "DISABLE_DIRSYNC", #endif -#if SQLITE_DISABLE_FTS3_UNICODE +#ifdef SQLITE_DISABLE_FTS3_UNICODE "DISABLE_FTS3_UNICODE", #endif -#if SQLITE_DISABLE_FTS4_DEFERRED +#ifdef SQLITE_DISABLE_FTS4_DEFERRED "DISABLE_FTS4_DEFERRED", #endif -#if SQLITE_DISABLE_INTRINSIC +#ifdef SQLITE_DISABLE_INTRINSIC "DISABLE_INTRINSIC", #endif -#if SQLITE_DISABLE_LFS +#ifdef SQLITE_DISABLE_LFS "DISABLE_LFS", #endif -#if SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS +#ifdef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS "DISABLE_PAGECACHE_OVERFLOW_STATS", #endif -#if SQLITE_DISABLE_SKIPAHEAD_DISTINCT +#ifdef SQLITE_DISABLE_SKIPAHEAD_DISTINCT "DISABLE_SKIPAHEAD_DISTINCT", #endif #ifdef SQLITE_ENABLE_8_3_NAMES "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), #endif -#if SQLITE_ENABLE_API_ARMOR +#ifdef SQLITE_ENABLE_API_ARMOR "ENABLE_API_ARMOR", #endif -#if SQLITE_ENABLE_ATOMIC_WRITE +#ifdef SQLITE_ENABLE_ATOMIC_WRITE "ENABLE_ATOMIC_WRITE", #endif -#if SQLITE_ENABLE_BATCH_ATOMIC_WRITE +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE "ENABLE_BATCH_ATOMIC_WRITE", #endif -#if SQLITE_ENABLE_BYTECODE_VTAB +#ifdef SQLITE_ENABLE_BYTECODE_VTAB "ENABLE_BYTECODE_VTAB", #endif #ifdef SQLITE_ENABLE_CEROD "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), #endif -#if SQLITE_ENABLE_COLUMN_METADATA +#ifdef SQLITE_ENABLE_COLUMN_METADATA "ENABLE_COLUMN_METADATA", #endif -#if SQLITE_ENABLE_COLUMN_USED_MASK +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK "ENABLE_COLUMN_USED_MASK", #endif -#if SQLITE_ENABLE_COSTMULT +#ifdef SQLITE_ENABLE_COSTMULT "ENABLE_COSTMULT", #endif -#if SQLITE_ENABLE_CURSOR_HINTS +#ifdef SQLITE_ENABLE_CURSOR_HINTS "ENABLE_CURSOR_HINTS", #endif -#if SQLITE_ENABLE_DBPAGE_VTAB +#ifdef SQLITE_ENABLE_DBPAGE_VTAB "ENABLE_DBPAGE_VTAB", #endif -#if SQLITE_ENABLE_DBSTAT_VTAB +#ifdef SQLITE_ENABLE_DBSTAT_VTAB "ENABLE_DBSTAT_VTAB", #endif -#if SQLITE_ENABLE_EXPENSIVE_ASSERT +#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT "ENABLE_EXPENSIVE_ASSERT", #endif -#if SQLITE_ENABLE_EXPLAIN_COMMENTS +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS "ENABLE_EXPLAIN_COMMENTS", #endif -#if SQLITE_ENABLE_FTS3 +#ifdef SQLITE_ENABLE_FTS3 "ENABLE_FTS3", #endif -#if SQLITE_ENABLE_FTS3_PARENTHESIS +#ifdef SQLITE_ENABLE_FTS3_PARENTHESIS "ENABLE_FTS3_PARENTHESIS", #endif -#if SQLITE_ENABLE_FTS3_TOKENIZER +#ifdef SQLITE_ENABLE_FTS3_TOKENIZER "ENABLE_FTS3_TOKENIZER", #endif -#if SQLITE_ENABLE_FTS4 +#ifdef SQLITE_ENABLE_FTS4 "ENABLE_FTS4", #endif -#if SQLITE_ENABLE_FTS5 +#ifdef SQLITE_ENABLE_FTS5 "ENABLE_FTS5", #endif -#if SQLITE_ENABLE_GEOPOLY +#ifdef SQLITE_ENABLE_GEOPOLY "ENABLE_GEOPOLY", #endif -#if SQLITE_ENABLE_HIDDEN_COLUMNS +#ifdef SQLITE_ENABLE_HIDDEN_COLUMNS "ENABLE_HIDDEN_COLUMNS", #endif -#if SQLITE_ENABLE_ICU +#ifdef SQLITE_ENABLE_ICU "ENABLE_ICU", #endif -#if SQLITE_ENABLE_IOTRACE +#ifdef SQLITE_ENABLE_IOTRACE "ENABLE_IOTRACE", #endif -#if SQLITE_ENABLE_JSON1 +#ifdef SQLITE_ENABLE_JSON1 "ENABLE_JSON1", #endif -#if SQLITE_ENABLE_LOAD_EXTENSION +#ifdef SQLITE_ENABLE_LOAD_EXTENSION "ENABLE_LOAD_EXTENSION", #endif #ifdef SQLITE_ENABLE_LOCKING_STYLE "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), #endif -#if SQLITE_ENABLE_MATH_FUNCTIONS +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS "ENABLE_MATH_FUNCTIONS", #endif -#if SQLITE_ENABLE_MEMORY_MANAGEMENT +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT "ENABLE_MEMORY_MANAGEMENT", #endif -#if SQLITE_ENABLE_MEMSYS3 +#ifdef SQLITE_ENABLE_MEMSYS3 "ENABLE_MEMSYS3", #endif -#if SQLITE_ENABLE_MEMSYS5 +#ifdef SQLITE_ENABLE_MEMSYS5 "ENABLE_MEMSYS5", #endif -#if SQLITE_ENABLE_MULTIPLEX +#ifdef SQLITE_ENABLE_MULTIPLEX "ENABLE_MULTIPLEX", #endif -#if SQLITE_ENABLE_NORMALIZE +#ifdef SQLITE_ENABLE_NORMALIZE "ENABLE_NORMALIZE", #endif -#if SQLITE_ENABLE_NULL_TRIM +#ifdef SQLITE_ENABLE_NULL_TRIM "ENABLE_NULL_TRIM", #endif -#if SQLITE_ENABLE_OFFSET_SQL_FUNC +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC "ENABLE_OFFSET_SQL_FUNC", #endif -#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK +#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK "ENABLE_OVERSIZE_CELL_CHECK", #endif -#if SQLITE_ENABLE_PREUPDATE_HOOK +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK "ENABLE_PREUPDATE_HOOK", #endif -#if SQLITE_ENABLE_QPSG +#ifdef SQLITE_ENABLE_QPSG "ENABLE_QPSG", #endif -#if SQLITE_ENABLE_RBU +#ifdef SQLITE_ENABLE_RBU "ENABLE_RBU", #endif -#if SQLITE_ENABLE_RTREE +#ifdef SQLITE_ENABLE_RTREE "ENABLE_RTREE", #endif -#if SQLITE_ENABLE_SELECTTRACE +#ifdef SQLITE_ENABLE_SELECTTRACE "ENABLE_SELECTTRACE", #endif -#if SQLITE_ENABLE_SESSION +#ifdef SQLITE_ENABLE_SESSION "ENABLE_SESSION", #endif -#if SQLITE_ENABLE_SNAPSHOT +#ifdef SQLITE_ENABLE_SNAPSHOT "ENABLE_SNAPSHOT", #endif -#if SQLITE_ENABLE_SORTER_REFERENCES +#ifdef SQLITE_ENABLE_SORTER_REFERENCES "ENABLE_SORTER_REFERENCES", #endif -#if SQLITE_ENABLE_SQLLOG +#ifdef SQLITE_ENABLE_SQLLOG "ENABLE_SQLLOG", #endif -#if SQLITE_ENABLE_STAT4 +#ifdef SQLITE_ENABLE_STAT4 "ENABLE_STAT4", #endif -#if SQLITE_ENABLE_STMTVTAB +#ifdef SQLITE_ENABLE_STMTVTAB "ENABLE_STMTVTAB", #endif -#if SQLITE_ENABLE_STMT_SCANSTATUS +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS "ENABLE_STMT_SCANSTATUS", #endif -#if SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION "ENABLE_UNKNOWN_SQL_FUNCTION", #endif -#if SQLITE_ENABLE_UNLOCK_NOTIFY +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY "ENABLE_UNLOCK_NOTIFY", #endif -#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT "ENABLE_UPDATE_DELETE_LIMIT", #endif -#if SQLITE_ENABLE_URI_00_ERROR +#ifdef SQLITE_ENABLE_URI_00_ERROR "ENABLE_URI_00_ERROR", #endif -#if SQLITE_ENABLE_VFSTRACE +#ifdef SQLITE_ENABLE_VFSTRACE "ENABLE_VFSTRACE", #endif -#if SQLITE_ENABLE_WHERETRACE +#ifdef SQLITE_ENABLE_WHERETRACE "ENABLE_WHERETRACE", #endif -#if SQLITE_ENABLE_ZIPVFS +#ifdef SQLITE_ENABLE_ZIPVFS "ENABLE_ZIPVFS", #endif -#if SQLITE_EXPLAIN_ESTIMATED_ROWS +#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS "EXPLAIN_ESTIMATED_ROWS", #endif -#if SQLITE_EXTRA_IFNULLROW +#ifdef SQLITE_EXTRA_IFNULLROW "EXTRA_IFNULLROW", #endif #ifdef SQLITE_EXTRA_INIT @@ -362,10 +365,10 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_FTS3_MAX_EXPR_DEPTH "FTS3_MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_FTS3_MAX_EXPR_DEPTH), #endif -#if SQLITE_FTS5_ENABLE_TEST_MI +#ifdef SQLITE_FTS5_ENABLE_TEST_MI "FTS5_ENABLE_TEST_MI", #endif -#if SQLITE_FTS5_NO_WITHOUT_ROWID +#ifdef SQLITE_FTS5_NO_WITHOUT_ROWID "FTS5_NO_WITHOUT_ROWID", #endif #if HAVE_ISNAN || SQLITE_HAVE_ISNAN @@ -376,28 +379,28 @@ static const char * const sqlite3azCompileOpt[] = { "HOMEGROWN_RECURSIVE_MUTEX=" CTIMEOPT_VAL(SQLITE_HOMEGROWN_RECURSIVE_MUTEX), # endif #endif -#if SQLITE_IGNORE_AFP_LOCK_ERRORS +#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS "IGNORE_AFP_LOCK_ERRORS", #endif -#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS "IGNORE_FLOCK_LOCK_ERRORS", #endif -#if SQLITE_INLINE_MEMCPY +#ifdef SQLITE_INLINE_MEMCPY "INLINE_MEMCPY", #endif -#if SQLITE_INT64_TYPE +#ifdef SQLITE_INT64_TYPE "INT64_TYPE", #endif #ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), #endif -#if SQLITE_LIKE_DOESNT_MATCH_BLOBS +#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS "LIKE_DOESNT_MATCH_BLOBS", #endif -#if SQLITE_LOCK_TRACE +#ifdef SQLITE_LOCK_TRACE "LOCK_TRACE", #endif -#if SQLITE_LOG_CACHE_SPILL +#ifdef SQLITE_LOG_CACHE_SPILL "LOG_CACHE_SPILL", #endif #ifdef SQLITE_MALLOC_SOFT_LIMIT @@ -460,190 +463,187 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_MAX_WORKER_THREADS "MAX_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_MAX_WORKER_THREADS), #endif -#if SQLITE_MEMDEBUG +#ifdef SQLITE_MEMDEBUG "MEMDEBUG", #endif -#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT "MIXED_ENDIAN_64BIT_FLOAT", #endif -#if SQLITE_MMAP_READWRITE +#ifdef SQLITE_MMAP_READWRITE "MMAP_READWRITE", #endif -#if SQLITE_MUTEX_NOOP +#ifdef SQLITE_MUTEX_NOOP "MUTEX_NOOP", #endif -#if SQLITE_MUTEX_OMIT +#ifdef SQLITE_MUTEX_OMIT "MUTEX_OMIT", #endif -#if SQLITE_MUTEX_PTHREADS +#ifdef SQLITE_MUTEX_PTHREADS "MUTEX_PTHREADS", #endif -#if SQLITE_MUTEX_W32 +#ifdef SQLITE_MUTEX_W32 "MUTEX_W32", #endif -#if SQLITE_NEED_ERR_NAME +#ifdef SQLITE_NEED_ERR_NAME "NEED_ERR_NAME", #endif -#if SQLITE_NOINLINE - "NOINLINE", -#endif -#if SQLITE_NO_SYNC +#ifdef SQLITE_NO_SYNC "NO_SYNC", #endif -#if SQLITE_OMIT_ALTERTABLE +#ifdef SQLITE_OMIT_ALTERTABLE "OMIT_ALTERTABLE", #endif -#if SQLITE_OMIT_ANALYZE +#ifdef SQLITE_OMIT_ANALYZE "OMIT_ANALYZE", #endif -#if SQLITE_OMIT_ATTACH +#ifdef SQLITE_OMIT_ATTACH "OMIT_ATTACH", #endif -#if SQLITE_OMIT_AUTHORIZATION +#ifdef SQLITE_OMIT_AUTHORIZATION "OMIT_AUTHORIZATION", #endif -#if SQLITE_OMIT_AUTOINCREMENT +#ifdef SQLITE_OMIT_AUTOINCREMENT "OMIT_AUTOINCREMENT", #endif -#if SQLITE_OMIT_AUTOINIT +#ifdef SQLITE_OMIT_AUTOINIT "OMIT_AUTOINIT", #endif -#if SQLITE_OMIT_AUTOMATIC_INDEX +#ifdef SQLITE_OMIT_AUTOMATIC_INDEX "OMIT_AUTOMATIC_INDEX", #endif -#if SQLITE_OMIT_AUTORESET +#ifdef SQLITE_OMIT_AUTORESET "OMIT_AUTORESET", #endif -#if SQLITE_OMIT_AUTOVACUUM +#ifdef SQLITE_OMIT_AUTOVACUUM "OMIT_AUTOVACUUM", #endif -#if SQLITE_OMIT_BETWEEN_OPTIMIZATION +#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION "OMIT_BETWEEN_OPTIMIZATION", #endif -#if SQLITE_OMIT_BLOB_LITERAL +#ifdef SQLITE_OMIT_BLOB_LITERAL "OMIT_BLOB_LITERAL", #endif -#if SQLITE_OMIT_CAST +#ifdef SQLITE_OMIT_CAST "OMIT_CAST", #endif -#if SQLITE_OMIT_CHECK +#ifdef SQLITE_OMIT_CHECK "OMIT_CHECK", #endif -#if SQLITE_OMIT_COMPLETE +#ifdef SQLITE_OMIT_COMPLETE "OMIT_COMPLETE", #endif -#if SQLITE_OMIT_COMPOUND_SELECT +#ifdef SQLITE_OMIT_COMPOUND_SELECT "OMIT_COMPOUND_SELECT", #endif -#if SQLITE_OMIT_CONFLICT_CLAUSE +#ifdef SQLITE_OMIT_CONFLICT_CLAUSE "OMIT_CONFLICT_CLAUSE", #endif -#if SQLITE_OMIT_CTE +#ifdef SQLITE_OMIT_CTE "OMIT_CTE", #endif #if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT) "OMIT_DATETIME_FUNCS", #endif -#if SQLITE_OMIT_DECLTYPE +#ifdef SQLITE_OMIT_DECLTYPE "OMIT_DECLTYPE", #endif -#if SQLITE_OMIT_DEPRECATED +#ifdef SQLITE_OMIT_DEPRECATED "OMIT_DEPRECATED", #endif -#if SQLITE_OMIT_DESERIALIZE +#ifdef SQLITE_OMIT_DESERIALIZE "OMIT_DESERIALIZE", #endif -#if SQLITE_OMIT_DISKIO +#ifdef SQLITE_OMIT_DISKIO "OMIT_DISKIO", #endif -#if SQLITE_OMIT_EXPLAIN +#ifdef SQLITE_OMIT_EXPLAIN "OMIT_EXPLAIN", #endif -#if SQLITE_OMIT_FLAG_PRAGMAS +#ifdef SQLITE_OMIT_FLAG_PRAGMAS "OMIT_FLAG_PRAGMAS", #endif -#if SQLITE_OMIT_FLOATING_POINT +#ifdef SQLITE_OMIT_FLOATING_POINT "OMIT_FLOATING_POINT", #endif -#if SQLITE_OMIT_FOREIGN_KEY +#ifdef SQLITE_OMIT_FOREIGN_KEY "OMIT_FOREIGN_KEY", #endif -#if SQLITE_OMIT_GET_TABLE +#ifdef SQLITE_OMIT_GET_TABLE "OMIT_GET_TABLE", #endif -#if SQLITE_OMIT_HEX_INTEGER +#ifdef SQLITE_OMIT_HEX_INTEGER "OMIT_HEX_INTEGER", #endif -#if SQLITE_OMIT_INCRBLOB +#ifdef SQLITE_OMIT_INCRBLOB "OMIT_INCRBLOB", #endif -#if SQLITE_OMIT_INTEGRITY_CHECK +#ifdef SQLITE_OMIT_INTEGRITY_CHECK "OMIT_INTEGRITY_CHECK", #endif -#if SQLITE_OMIT_INTROSPECTION_PRAGMAS +#ifdef SQLITE_OMIT_INTROSPECTION_PRAGMAS "OMIT_INTROSPECTION_PRAGMAS", #endif -#if SQLITE_OMIT_LIKE_OPTIMIZATION +#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION "OMIT_LIKE_OPTIMIZATION", #endif -#if SQLITE_OMIT_LOAD_EXTENSION +#ifdef SQLITE_OMIT_LOAD_EXTENSION "OMIT_LOAD_EXTENSION", #endif -#if SQLITE_OMIT_LOCALTIME +#ifdef SQLITE_OMIT_LOCALTIME "OMIT_LOCALTIME", #endif -#if SQLITE_OMIT_LOOKASIDE +#ifdef SQLITE_OMIT_LOOKASIDE "OMIT_LOOKASIDE", #endif -#if SQLITE_OMIT_MEMORYDB +#ifdef SQLITE_OMIT_MEMORYDB "OMIT_MEMORYDB", #endif -#if SQLITE_OMIT_OR_OPTIMIZATION +#ifdef SQLITE_OMIT_OR_OPTIMIZATION "OMIT_OR_OPTIMIZATION", #endif -#if SQLITE_OMIT_PAGER_PRAGMAS +#ifdef SQLITE_OMIT_PAGER_PRAGMAS "OMIT_PAGER_PRAGMAS", #endif -#if SQLITE_OMIT_PARSER_TRACE +#ifdef SQLITE_OMIT_PARSER_TRACE "OMIT_PARSER_TRACE", #endif -#if SQLITE_OMIT_POPEN +#ifdef SQLITE_OMIT_POPEN "OMIT_POPEN", #endif -#if SQLITE_OMIT_PRAGMA +#ifdef SQLITE_OMIT_PRAGMA "OMIT_PRAGMA", #endif -#if SQLITE_OMIT_PROGRESS_CALLBACK +#ifdef SQLITE_OMIT_PROGRESS_CALLBACK "OMIT_PROGRESS_CALLBACK", #endif -#if SQLITE_OMIT_QUICKBALANCE +#ifdef SQLITE_OMIT_QUICKBALANCE "OMIT_QUICKBALANCE", #endif -#if SQLITE_OMIT_REINDEX +#ifdef SQLITE_OMIT_REINDEX "OMIT_REINDEX", #endif -#if SQLITE_OMIT_SCHEMA_PRAGMAS +#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS "OMIT_SCHEMA_PRAGMAS", #endif -#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS +#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS "OMIT_SCHEMA_VERSION_PRAGMAS", #endif -#if SQLITE_OMIT_SHARED_CACHE +#ifdef SQLITE_OMIT_SHARED_CACHE "OMIT_SHARED_CACHE", #endif -#if SQLITE_OMIT_SHUTDOWN_DIRECTORIES +#ifdef SQLITE_OMIT_SHUTDOWN_DIRECTORIES "OMIT_SHUTDOWN_DIRECTORIES", #endif -#if SQLITE_OMIT_SUBQUERY +#ifdef SQLITE_OMIT_SUBQUERY "OMIT_SUBQUERY", #endif -#if SQLITE_OMIT_TCL_VARIABLE +#ifdef SQLITE_OMIT_TCL_VARIABLE "OMIT_TCL_VARIABLE", #endif -#if SQLITE_OMIT_TEMPDB +#ifdef SQLITE_OMIT_TEMPDB "OMIT_TEMPDB", #endif -#if SQLITE_OMIT_TEST_CONTROL +#ifdef SQLITE_OMIT_TEST_CONTROL "OMIT_TEST_CONTROL", #endif #ifdef SQLITE_OMIT_TRACE @@ -651,37 +651,37 @@ static const char * const sqlite3azCompileOpt[] = { "OMIT_TRACE=" CTIMEOPT_VAL(SQLITE_OMIT_TRACE), # endif #endif -#if SQLITE_OMIT_TRIGGER +#ifdef SQLITE_OMIT_TRIGGER "OMIT_TRIGGER", #endif -#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION +#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION "OMIT_TRUNCATE_OPTIMIZATION", #endif -#if SQLITE_OMIT_UTF16 +#ifdef SQLITE_OMIT_UTF16 "OMIT_UTF16", #endif -#if SQLITE_OMIT_VACUUM +#ifdef SQLITE_OMIT_VACUUM "OMIT_VACUUM", #endif -#if SQLITE_OMIT_VIEW +#ifdef SQLITE_OMIT_VIEW "OMIT_VIEW", #endif -#if SQLITE_OMIT_VIRTUALTABLE +#ifdef SQLITE_OMIT_VIRTUALTABLE "OMIT_VIRTUALTABLE", #endif -#if SQLITE_OMIT_WAL +#ifdef SQLITE_OMIT_WAL "OMIT_WAL", #endif -#if SQLITE_OMIT_WSD +#ifdef SQLITE_OMIT_WSD "OMIT_WSD", #endif -#if SQLITE_OMIT_XFER_OPT +#ifdef SQLITE_OMIT_XFER_OPT "OMIT_XFER_OPT", #endif -#if SQLITE_PCACHE_SEPARATE_HEADER +#ifdef SQLITE_PCACHE_SEPARATE_HEADER "PCACHE_SEPARATE_HEADER", #endif -#if SQLITE_PERFORMANCE_TRACE +#ifdef SQLITE_PERFORMANCE_TRACE "PERFORMANCE_TRACE", #endif #ifdef SQLITE_POWERSAFE_OVERWRITE @@ -689,28 +689,28 @@ static const char * const sqlite3azCompileOpt[] = { "POWERSAFE_OVERWRITE=" CTIMEOPT_VAL(SQLITE_POWERSAFE_OVERWRITE), # endif #endif -#if SQLITE_PREFER_PROXY_LOCKING +#ifdef SQLITE_PREFER_PROXY_LOCKING "PREFER_PROXY_LOCKING", #endif -#if SQLITE_PROXY_DEBUG +#ifdef SQLITE_PROXY_DEBUG "PROXY_DEBUG", #endif -#if SQLITE_REVERSE_UNORDERED_SELECTS +#ifdef SQLITE_REVERSE_UNORDERED_SELECTS "REVERSE_UNORDERED_SELECTS", #endif -#if SQLITE_RTREE_INT_ONLY +#ifdef SQLITE_RTREE_INT_ONLY "RTREE_INT_ONLY", #endif -#if SQLITE_SECURE_DELETE +#ifdef SQLITE_SECURE_DELETE "SECURE_DELETE", #endif -#if SQLITE_SMALL_STACK +#ifdef SQLITE_SMALL_STACK "SMALL_STACK", #endif #ifdef SQLITE_SORTER_PMASZ "SORTER_PMASZ=" CTIMEOPT_VAL(SQLITE_SORTER_PMASZ), #endif -#if SQLITE_SOUNDEX +#ifdef SQLITE_SOUNDEX "SOUNDEX", #endif #ifdef SQLITE_STAT4_SAMPLES @@ -719,7 +719,7 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_STMTJRNL_SPILL "STMTJRNL_SPILL=" CTIMEOPT_VAL(SQLITE_STMTJRNL_SPILL), #endif -#if SQLITE_SUBSTR_COMPATIBILITY +#ifdef SQLITE_SUBSTR_COMPATIBILITY "SUBSTR_COMPATIBILITY", #endif #if (!defined(SQLITE_WIN32_MALLOC) \ @@ -728,13 +728,13 @@ static const char * const sqlite3azCompileOpt[] = { ) || defined(SQLITE_SYSTEM_MALLOC) "SYSTEM_MALLOC", #endif -#if SQLITE_TCL +#ifdef SQLITE_TCL "TCL", #endif #ifdef SQLITE_TEMP_STORE "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), #endif -#if SQLITE_TEST +#ifdef SQLITE_TEST "TEST", #endif #if defined(SQLITE_THREADSAFE) @@ -744,31 +744,31 @@ static const char * const sqlite3azCompileOpt[] = { #else "THREADSAFE=1", #endif -#if SQLITE_UNLINK_AFTER_CLOSE +#ifdef SQLITE_UNLINK_AFTER_CLOSE "UNLINK_AFTER_CLOSE", #endif -#if SQLITE_UNTESTABLE +#ifdef SQLITE_UNTESTABLE "UNTESTABLE", #endif -#if SQLITE_USER_AUTHENTICATION +#ifdef SQLITE_USER_AUTHENTICATION "USER_AUTHENTICATION", #endif -#if SQLITE_USE_ALLOCA +#ifdef SQLITE_USE_ALLOCA "USE_ALLOCA", #endif -#if SQLITE_USE_FCNTL_TRACE +#ifdef SQLITE_USE_FCNTL_TRACE "USE_FCNTL_TRACE", #endif -#if SQLITE_USE_URI +#ifdef SQLITE_USE_URI "USE_URI", #endif -#if SQLITE_VDBE_COVERAGE +#ifdef SQLITE_VDBE_COVERAGE "VDBE_COVERAGE", #endif -#if SQLITE_WIN32_MALLOC +#ifdef SQLITE_WIN32_MALLOC "WIN32_MALLOC", #endif -#if SQLITE_ZERO_MALLOC +#ifdef SQLITE_ZERO_MALLOC "ZERO_MALLOC", #endif /* diff --git a/src/date.c b/src/date.c index f88f544e3..20a0a5d17 100644 --- a/src/date.c +++ b/src/date.c @@ -1009,131 +1009,100 @@ static void strftimeFunc( sqlite3_value **argv ){ DateTime x; - u64 n; size_t i,j; - char *z; sqlite3 *db; const char *zFmt; - char zBuf[100]; + sqlite3_str sRes; + + if( argc==0 ) return; zFmt = (const char*)sqlite3_value_text(argv[0]); if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return; db = sqlite3_context_db_handle(context); - for(i=0, n=1; zFmt[i]; i++, n++){ - if( zFmt[i]=='%' ){ - switch( zFmt[i+1] ){ - case 'd': - case 'H': - case 'm': - case 'M': - case 'S': - case 'W': - n++; - /* fall thru */ - case 'w': - case '%': - break; - case 'f': - n += 8; - break; - case 'j': - n += 3; - break; - case 'Y': - n += 8; - break; - case 's': - case 'J': - n += 50; - break; - default: - return; /* ERROR. return a NULL */ - } - i++; - } - } - testcase( n==sizeof(zBuf)-1 ); - testcase( n==sizeof(zBuf) ); - testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); - testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ); - if( n(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - sqlite3_result_error_toobig(context); - return; - }else{ - z = sqlite3DbMallocRawNN(db, (int)n); - if( z==0 ){ - sqlite3_result_error_nomem(context); - return; - } - } + sqlite3StrAccumInit(&sRes, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + computeJD(&x); computeYMD_HMS(&x); for(i=j=0; zFmt[i]; i++){ - if( zFmt[i]!='%' ){ - z[j++] = zFmt[i]; - }else{ - i++; - switch( zFmt[i] ){ - case 'd': sqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break; - case 'f': { - double s = x.s; - if( s>59.999 ) s = 59.999; - sqlite3_snprintf(7, &z[j],"%06.3f", s); - j += sqlite3Strlen30(&z[j]); - break; - } - case 'H': sqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break; - case 'W': /* Fall thru */ - case 'j': { - int nDay; /* Number of days since 1st day of year */ - DateTime y = x; - y.validJD = 0; - y.M = 1; - y.D = 1; - computeJD(&y); - nDay = (int)((x.iJD-y.iJD+43200000)/86400000); - if( zFmt[i]=='W' ){ - int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ - wd = (int)(((x.iJD+43200000)/86400000)%7); - sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7); - j += 2; - }else{ - sqlite3_snprintf(4, &z[j],"%03d",nDay+1); - j += 3; - } - break; - } - case 'J': { - sqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0); - j+=sqlite3Strlen30(&z[j]); - break; - } - case 'm': sqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break; - case 'M': sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break; - case 's': { - i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); - sqlite3Int64ToText(iS, &z[j]); - j += sqlite3Strlen30(&z[j]); - break; - } - case 'S': sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break; - case 'w': { - z[j++] = (char)(((x.iJD+129600000)/86400000) % 7) + '0'; - break; - } - case 'Y': { - sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=sqlite3Strlen30(&z[j]); - break; + if( zFmt[i]!='%' ) continue; + if( j59.999 ) s = 59.999; + sqlite3_str_appendf(&sRes, "%06.3f", s); + break; + } + case 'H': { + sqlite3_str_appendf(&sRes, "%02d", x.h); + break; + } + case 'W': /* Fall thru */ + case 'j': { + int nDay; /* Number of days since 1st day of year */ + DateTime y = x; + y.validJD = 0; + y.M = 1; + y.D = 1; + computeJD(&y); + nDay = (int)((x.iJD-y.iJD+43200000)/86400000); + if( zFmt[i]=='W' ){ + int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ + wd = (int)(((x.iJD+43200000)/86400000)%7); + sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7); + }else{ + sqlite3_str_appendf(&sRes,"%03d",nDay+1); } - default: z[j++] = '%'; break; + break; + } + case 'J': { + sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0); + break; + } + case 'm': { + sqlite3_str_appendf(&sRes,"%02d",x.M); + break; + } + case 'M': { + sqlite3_str_appendf(&sRes,"%02d",x.m); + break; + } + case 's': { + i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); + sqlite3_str_appendf(&sRes,"%lld",iS); + break; + } + case 'S': { + sqlite3_str_appendf(&sRes,"%02d",(int)x.s); + break; + } + case 'w': { + sqlite3_str_appendchar(&sRes, 1, + (char)(((x.iJD+129600000)/86400000) % 7) + '0'); + break; + } + case 'Y': { + sqlite3_str_appendf(&sRes,"%04d",x.Y); + break; + } + case '%': { + sqlite3_str_appendchar(&sRes, 1, '%'); + break; + } + default: { + sqlite3_str_reset(&sRes); + return; } } } - z[j] = 0; - sqlite3_result_text(context, z, -1, - z==zBuf ? SQLITE_TRANSIENT : SQLITE_DYNAMIC); + if( jaPg; statClearCells(p); - sqlite3PagerUnref(p->pPg); sqlite3_free(p->zPath); memset(p, 0, sizeof(StatPage)); + p->aPg = aPg; } static void statResetCsr(StatCursor *pCsr){ int i; - sqlite3_reset(pCsr->pStmt); + /* In some circumstances, specifically if an OOM has occurred, the call + ** to sqlite3_reset() may cause the pager to be reset (emptied). It is + ** important that statClearPage() is called to free any page refs before + ** this happens. dbsqlfuzz 9ed3e4e3816219d3509d711636c38542bf3f40b1. */ for(i=0; iaPage); i++){ statClearPage(&pCsr->aPage[i]); + sqlite3_free(pCsr->aPage[i].aPg); + pCsr->aPage[i].aPg = 0; } + sqlite3_reset(pCsr->pStmt); pCsr->iPage = 0; sqlite3_free(pCsr->zPath); pCsr->zPath = 0; @@ -382,7 +397,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ int isLeaf; int szPage; - u8 *aData = sqlite3PagerGetData(p->pPg); + u8 *aData = p->aPg; u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0]; p->flags = aHdr[0]; @@ -453,7 +468,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ if( nPayload>(u32)nLocal ){ int j; int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4); - if( iOff+nLocal>nUsable || nPayload>0x7fffffff ){ + if( iOff+nLocal+4>nUsable || nPayload>0x7fffffff ){ goto statPageIsCorrupt; } pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4); @@ -512,6 +527,38 @@ static void statSizeAndOffset(StatCursor *pCsr){ } } +/* +** Load a copy of the page data for page iPg into the buffer belonging +** to page object pPg. Allocate the buffer if necessary. Return SQLITE_OK +** if successful, or an SQLite error code otherwise. +*/ +static int statGetPage( + Btree *pBt, /* Load page from this b-tree */ + u32 iPg, /* Page number to load */ + StatPage *pPg /* Load page into this object */ +){ + int pgsz = sqlite3BtreeGetPageSize(pBt); + DbPage *pDbPage = 0; + int rc; + + if( pPg->aPg==0 ){ + pPg->aPg = (u8*)sqlite3_malloc(pgsz + DBSTAT_PAGE_PADDING_BYTES); + if( pPg->aPg==0 ){ + return SQLITE_NOMEM_BKPT; + } + memset(&pPg->aPg[pgsz], 0, DBSTAT_PAGE_PADDING_BYTES); + } + + rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPg, &pDbPage, 0); + if( rc==SQLITE_OK ){ + const u8 *a = sqlite3PagerGetData(pDbPage); + memcpy(pPg->aPg, a, pgsz); + sqlite3PagerUnref(pDbPage); + } + + return rc; +} + /* ** Move a DBSTAT cursor to the next entry. Normally, the next ** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0), @@ -530,7 +577,7 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ pCsr->zPath = 0; statNextRestart: - if( pCsr->aPage[0].pPg==0 ){ + if( pCsr->iPage<0 ){ /* Start measuring space on the next btree */ statResetCounts(pCsr); rc = sqlite3_step(pCsr->pStmt); @@ -542,7 +589,7 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ pCsr->isEof = 1; return sqlite3_reset(pCsr->pStmt); } - rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0); + rc = statGetPage(pBt, iRoot, &pCsr->aPage[0]); pCsr->aPage[0].iPgno = iRoot; pCsr->aPage[0].iCell = 0; if( !pCsr->isAgg ){ @@ -593,9 +640,8 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ if( !p->iRightChildPg || p->iCell>p->nCell ){ statClearPage(p); - if( pCsr->iPage>0 ){ - pCsr->iPage--; - }else if( pCsr->isAgg ){ + pCsr->iPage--; + if( pCsr->isAgg && pCsr->iPage<0 ){ /* label-statNext-done: When computing aggregate space usage over ** an entire btree, this is the exit point from this function */ return SQLITE_OK; @@ -614,7 +660,7 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ }else{ p[1].iPgno = p->aCell[p->iCell].iChildPg; } - rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0); + rc = statGetPage(pBt, p[1].iPgno, &p[1]); pCsr->nPage++; p[1].iCell = 0; if( !pCsr->isAgg ){ @@ -744,6 +790,7 @@ static int statFilter( } if( rc==SQLITE_OK ){ + pCsr->iPage = -1; rc = statNext(pCursor); } return rc; diff --git a/src/delete.c b/src/delete.c index dd074bb35..e2b283ea4 100644 --- a/src/delete.c +++ b/src/delete.c @@ -84,7 +84,7 @@ int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){ return 1; } #ifndef SQLITE_OMIT_VIEW - if( !viewOk && pTab->pSelect ){ + if( !viewOk && IsView(pTab) ){ sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); return 1; } @@ -188,13 +188,13 @@ Expr *sqlite3LimitWhere( }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); if( pPk->nKeyCol==1 ){ - const char *zName = pTab->aCol[pPk->aiColumn[0]].zName; + const char *zName = pTab->aCol[pPk->aiColumn[0]].zCnName; pLhs = sqlite3Expr(db, TK_ID, zName); pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, zName)); }else{ int i; for(i=0; inKeyCol; i++){ - Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName); + Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); pEList = sqlite3ExprListAppend(pParse, pEList, p); } pLhs = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); @@ -210,6 +210,7 @@ Expr *sqlite3LimitWhere( pSelectSrc = sqlite3SrcListDup(db, pSrc, 0); pSrc->a[0].pTab = pTab; if( pSrc->a[0].fg.isIndexedBy ){ + assert( pSrc->a[0].fg.isCte==0 ); pSrc->a[0].u2.pIBIndex = 0; pSrc->a[0].fg.isIndexedBy = 0; sqlite3DbFree(db, pSrc->a[0].u1.zIndexedBy); @@ -301,7 +302,7 @@ void sqlite3DeleteFrom( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); - isView = pTab->pSelect!=0; + isView = IsView(pTab); #else # define pTrigger 0 # define isView 0 @@ -551,7 +552,7 @@ void sqlite3DeleteFrom( if( eOnePass!=ONEPASS_OFF ){ assert( nKey==nPk ); /* OP_Found will use an unpacked key */ if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){ - assert( pPk!=0 || pTab->pSelect!=0 ); + assert( pPk!=0 || IsView(pTab) ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey); VdbeCoverage(v); } @@ -785,7 +786,7 @@ void sqlite3GenerateRowDelete( ** the update-hook is not invoked for rows removed by REPLACE, but the ** pre-update-hook is. */ - if( pTab->pSelect==0 ){ + if( !IsView(pTab) ){ u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); diff --git a/src/expr.c b/src/expr.c index b751f51a6..a51d37a7b 100644 --- a/src/expr.c +++ b/src/expr.c @@ -21,9 +21,9 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree); /* ** Return the affinity character for a single column of a table. */ -char sqlite3TableColumnAffinity(Table *pTab, int iCol){ - assert( iColnCol ); - return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER; +char sqlite3TableColumnAffinity(const Table *pTab, int iCol){ + if( iCol<0 || NEVER(iCol>=pTab->nCol) ) return SQLITE_AFF_INTEGER; + return pTab->aCol[iCol].affinity; } /* @@ -53,11 +53,14 @@ char sqlite3ExprAffinity(const Expr *pExpr){ } op = pExpr->op; if( op==TK_REGISTER ) op = pExpr->op2; - if( (op==TK_COLUMN || op==TK_AGG_COLUMN) && pExpr->y.pTab ){ - return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + if( op==TK_COLUMN || op==TK_AGG_COLUMN ){ + assert( ExprUseYTab(pExpr) ); + if( pExpr->y.pTab ){ + return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + } } if( op==TK_SELECT ){ - assert( pExpr->flags&EP_xIsSelect ); + assert( ExprUseXSelect(pExpr) ); assert( pExpr->x.pSelect!=0 ); assert( pExpr->x.pSelect->pEList!=0 ); assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); @@ -70,12 +73,15 @@ char sqlite3ExprAffinity(const Expr *pExpr){ } #endif if( op==TK_SELECT_COLUMN ){ - assert( pExpr->pLeft->flags&EP_xIsSelect ); + assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); + assert( pExpr->iColumn < pExpr->iTable ); + assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); return sqlite3ExprAffinity( pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr ); } if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); } return pExpr->affExpr; @@ -90,7 +96,7 @@ char sqlite3ExprAffinity(const Expr *pExpr){ ** and the pExpr parameter is returned unchanged. */ Expr *sqlite3ExprAddCollateToken( - Parse *pParse, /* Parsing context */ + const Parse *pParse, /* Parsing context */ Expr *pExpr, /* Add the "COLLATE" clause to this expression */ const Token *pCollName, /* Name of collating sequence */ int dequote /* True to dequote pCollName */ @@ -105,7 +111,11 @@ Expr *sqlite3ExprAddCollateToken( } return pExpr; } -Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){ +Expr *sqlite3ExprAddCollateString( + const Parse *pParse, /* Parsing context */ + Expr *pExpr, /* Add the "COLLATE" clause to this expression */ + const char *zC /* The collating sequence name */ +){ Token s; assert( zC!=0 ); sqlite3TokenInit(&s, (char*)zC); @@ -131,7 +141,7 @@ Expr *sqlite3ExprSkipCollate(Expr *pExpr){ Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){ while( pExpr && ExprHasProperty(pExpr, EP_Skip|EP_Unlikely) ){ if( ExprHasProperty(pExpr, EP_Unlikely) ){ - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); assert( pExpr->x.pList->nExpr>0 ); assert( pExpr->op==TK_FUNCTION ); pExpr = pExpr->x.pList->a[0].pExpr; @@ -164,27 +174,30 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ while( p ){ int op = p->op; if( op==TK_REGISTER ) op = p->op2; - if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER) - && p->y.pTab!=0 - ){ - /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally - ** a TK_COLUMN but was previously evaluated and cached in a register */ - int j = p->iColumn; - if( j>=0 ){ - const char *zColl = p->y.pTab->aCol[j].zColl; - pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); + if( op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER ){ + assert( ExprUseYTab(p) ); + if( p->y.pTab!=0 ){ + /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally + ** a TK_COLUMN but was previously evaluated and cached in a register */ + int j = p->iColumn; + if( j>=0 ){ + const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]); + pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); + } + break; } - break; } if( op==TK_CAST || op==TK_UPLUS ){ p = p->pLeft; continue; } if( op==TK_VECTOR ){ + assert( ExprUseXList(p) ); p = p->x.pList->a[0].pExpr; continue; } if( op==TK_COLLATE ){ + assert( !ExprHasProperty(p, EP_IntValue) ); pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken); break; } @@ -194,11 +207,9 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ }else{ Expr *pNext = p->pRight; /* The Expr.x union is never used at the same time as Expr.pRight */ + assert( ExprUseXList(p) ); assert( p->x.pList==0 || p->pRight==0 ); - if( p->x.pList!=0 - && !db->mallocFailed - && ALWAYS(!ExprHasProperty(p, EP_xIsSelect)) - ){ + if( p->x.pList!=0 && !db->mallocFailed ){ int i; for(i=0; ALWAYS(ix.pList->nExpr); i++){ if( ExprHasProperty(p->x.pList->a[i].pExpr, EP_Collate) ){ @@ -281,7 +292,7 @@ static char comparisonAffinity(const Expr *pExpr){ aff = sqlite3ExprAffinity(pExpr->pLeft); if( pExpr->pRight ){ aff = sqlite3CompareAffinity(pExpr->pRight, aff); - }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + }else if( ExprUseXSelect(pExpr) ){ aff = sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff); }else if( aff==0 ){ aff = SQLITE_AFF_BLOB; @@ -407,7 +418,7 @@ static int codeCompare( ** But a TK_SELECT might be either a vector or a scalar. It is only ** considered a vector if it has two or more result columns. */ -int sqlite3ExprIsVector(Expr *pExpr){ +int sqlite3ExprIsVector(const Expr *pExpr){ return sqlite3ExprVectorSize(pExpr)>1; } @@ -417,12 +428,14 @@ int sqlite3ExprIsVector(Expr *pExpr){ ** is a sub-select, return the number of columns in the sub-select. For ** any other type of expression, return 1. */ -int sqlite3ExprVectorSize(Expr *pExpr){ +int sqlite3ExprVectorSize(const Expr *pExpr){ u8 op = pExpr->op; if( op==TK_REGISTER ) op = pExpr->op2; if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); return pExpr->x.pList->nExpr; }else if( op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); return pExpr->x.pSelect->pEList->nExpr; }else{ return 1; @@ -449,8 +462,10 @@ Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ if( sqlite3ExprIsVector(pVector) ){ assert( pVector->op2==0 || pVector->op==TK_REGISTER ); if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); return pVector->x.pSelect->pEList->a[i].pExpr; }else{ + assert( ExprUseXList(pVector) ); return pVector->x.pList->a[i].pExpr; } } @@ -481,11 +496,12 @@ Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ Expr *sqlite3ExprForVectorField( Parse *pParse, /* Parsing context */ Expr *pVector, /* The vector. List of expressions or a sub-SELECT */ - int iField /* Which column of the vector to return */ + int iField, /* Which column of the vector to return */ + int nField /* Total number of columns in the vector */ ){ Expr *pRet; if( pVector->op==TK_SELECT ){ - assert( pVector->flags & EP_xIsSelect ); + assert( ExprUseXSelect(pVector) ); /* The TK_SELECT_COLUMN Expr node: ** ** pLeft: pVector containing TK_SELECT. Not deleted. @@ -504,14 +520,23 @@ Expr *sqlite3ExprForVectorField( */ pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0); if( pRet ){ + pRet->iTable = nField; pRet->iColumn = iField; pRet->pLeft = pVector; } - assert( pRet==0 || pRet->iTable==0 ); }else{ - if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr; + if( pVector->op==TK_VECTOR ){ + Expr **ppVector; + assert( ExprUseXList(pVector) ); + ppVector = &pVector->x.pList->a[iField].pExpr; + pVector = *ppVector; + if( IN_RENAME_OBJECT ){ + /* This must be a vector UPDATE inside a trigger */ + *ppVector = 0; + return pVector; + } + } pRet = sqlite3ExprDup(pParse->db, pVector, 0); - sqlite3RenameTokenRemap(pParse, pRet, pVector); } return pRet; } @@ -567,10 +592,12 @@ static int exprVectorRegister( return pVector->iTable+iField; } if( op==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); *ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr; return regSelect+iField; } if( op==TK_VECTOR ){ + assert( ExprUseXList(pVector) ); *ppExpr = pVector->x.pList->a[iField].pExpr; return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); } @@ -704,14 +731,14 @@ int sqlite3ExprCheckHeight(Parse *pParse, int nHeight){ ** to by pnHeight, the second parameter, then set *pnHeight to that ** value. */ -static void heightOfExpr(Expr *p, int *pnHeight){ +static void heightOfExpr(const Expr *p, int *pnHeight){ if( p ){ if( p->nHeight>*pnHeight ){ *pnHeight = p->nHeight; } } } -static void heightOfExprList(ExprList *p, int *pnHeight){ +static void heightOfExprList(const ExprList *p, int *pnHeight){ if( p ){ int i; for(i=0; inExpr; i++){ @@ -719,8 +746,8 @@ static void heightOfExprList(ExprList *p, int *pnHeight){ } } } -static void heightOfSelect(Select *pSelect, int *pnHeight){ - Select *p; +static void heightOfSelect(const Select *pSelect, int *pnHeight){ + const Select *p; for(p=pSelect; p; p=p->pPrior){ heightOfExpr(p->pWhere, pnHeight); heightOfExpr(p->pHaving, pnHeight); @@ -745,7 +772,7 @@ static void exprSetHeight(Expr *p){ int nHeight = 0; heightOfExpr(p->pLeft, &nHeight); heightOfExpr(p->pRight, &nHeight); - if( ExprHasProperty(p, EP_xIsSelect) ){ + if( ExprUseXSelect(p) ){ heightOfSelect(p->x.pSelect, &nHeight); }else if( p->x.pList ){ heightOfExprList(p->x.pList, &nHeight); @@ -772,7 +799,7 @@ void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ ** Return the maximum height of any expression tree referenced ** by the select statement passed as an argument. */ -int sqlite3SelectExprHeight(Select *p){ +int sqlite3SelectExprHeight(const Select *p){ int nHeight = 0; heightOfSelect(p, &nHeight); return nHeight; @@ -784,7 +811,7 @@ int sqlite3SelectExprHeight(Select *p){ */ void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ if( pParse->nErr ) return; - if( p && p->x.pList && !ExprHasProperty(p, EP_xIsSelect) ){ + if( p && ExprUseXList(p) && p->x.pList ){ p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList); } } @@ -942,6 +969,63 @@ void sqlite3PExprAddSelect(Parse *pParse, Expr *pExpr, Select *pSelect){ } } +/* +** Expression list pEList is a list of vector values. This function +** converts the contents of pEList to a VALUES(...) Select statement +** returning 1 row for each element of the list. For example, the +** expression list: +** +** ( (1,2), (3,4) (5,6) ) +** +** is translated to the equivalent of: +** +** VALUES(1,2), (3,4), (5,6) +** +** Each of the vector values in pEList must contain exactly nElem terms. +** If a list element that is not a vector or does not contain nElem terms, +** an error message is left in pParse. +** +** This is used as part of processing IN(...) expressions with a list +** of vectors on the RHS. e.g. "... IN ((1,2), (3,4), (5,6))". +*/ +Select *sqlite3ExprListToValues(Parse *pParse, int nElem, ExprList *pEList){ + int ii; + Select *pRet = 0; + assert( nElem>1 ); + for(ii=0; iinExpr; ii++){ + Select *pSel; + Expr *pExpr = pEList->a[ii].pExpr; + int nExprElem; + if( pExpr->op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); + nExprElem = pExpr->x.pList->nExpr; + }else{ + nExprElem = 1; + } + if( nExprElem!=nElem ){ + sqlite3ErrorMsg(pParse, "IN(...) element has %d term%s - expected %d", + nExprElem, nExprElem>1?"s":"", nElem + ); + break; + } + assert( ExprUseXList(pExpr) ); + pSel = sqlite3SelectNew(pParse, pExpr->x.pList, 0, 0, 0, 0, 0, SF_Values,0); + pExpr->x.pList = 0; + if( pSel ){ + if( pRet ){ + pSel->op = TK_ALL; + pSel->pPrior = pRet; + } + pRet = pSel; + } + } + + if( pRet && pRet->pPrior ){ + pRet->selFlags |= SF_MultiValue; + } + sqlite3ExprListDelete(pParse->db, pEList); + return pRet; +} /* ** Join two expressions using an AND operator. If either expression is @@ -975,7 +1059,7 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ Expr *sqlite3ExprFunction( Parse *pParse, /* Parsing context */ ExprList *pList, /* Argument list */ - Token *pToken, /* Name of the function */ + const Token *pToken, /* Name of the function */ int eDistinct /* SF_Distinct or SF_ALL or 0 */ ){ Expr *pNew; @@ -986,12 +1070,15 @@ Expr *sqlite3ExprFunction( sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */ return 0; } - if( pList && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ + if( pList + && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] + && !pParse->nested + ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", pToken); } pNew->x.pList = pList; ExprSetProperty(pNew, EP_HasFunc); - assert( !ExprHasProperty(pNew, EP_xIsSelect) ); + assert( ExprUseXList(pNew) ); sqlite3ExprSetHeightAndFlags(pParse, pNew); if( eDistinct==SF_Distinct ) ExprSetProperty(pNew, EP_Distinct); return pNew; @@ -1010,8 +1097,8 @@ Expr *sqlite3ExprFunction( */ void sqlite3ExprFunctionUsable( Parse *pParse, /* Parsing and code generating context */ - Expr *pExpr, /* The function invocation */ - FuncDef *pDef /* The function being invoked */ + const Expr *pExpr, /* The function invocation */ + const FuncDef *pDef /* The function being invoked */ ){ assert( !IN_RENAME_OBJECT ); assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 ); @@ -1117,27 +1204,26 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){ */ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); - /* Sanity check: Assert that the IntValue is non-negative if it exists */ - assert( !ExprHasProperty(p, EP_IntValue) || p->u.iValue>=0 ); - - assert( !ExprHasProperty(p, EP_WinFunc) || p->y.pWin!=0 || db->mallocFailed ); - assert( p->op!=TK_FUNCTION || ExprHasProperty(p, EP_TokenOnly|EP_Reduced) - || p->y.pWin==0 || ExprHasProperty(p, EP_WinFunc) ); + assert( !ExprUseUValue(p) || p->u.iValue>=0 ); + assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); + assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); + assert( p->op!=TK_FUNCTION || !ExprUseYSub(p) ); #ifdef SQLITE_DEBUG if( ExprHasProperty(p, EP_Leaf) && !ExprHasProperty(p, EP_TokenOnly) ){ assert( p->pLeft==0 ); assert( p->pRight==0 ); - assert( p->x.pSelect==0 ); + assert( !ExprUseXSelect(p) || p->x.pSelect==0 ); + assert( !ExprUseXList(p) || p->x.pList==0 ); } #endif if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ - assert( p->x.pList==0 || p->pRight==0 ); + assert( (ExprUseXList(p) && p->x.pList==0) || p->pRight==0 ); if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); if( p->pRight ){ assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3ExprDeleteNN(db, p->pRight); - }else if( ExprHasProperty(p, EP_xIsSelect) ){ + }else if( ExprUseXSelect(p) ){ assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3SelectDelete(db, p->x.pSelect); }else{ @@ -1149,7 +1235,10 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ #endif } } - if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); + if( ExprHasProperty(p, EP_MemToken) ){ + assert( !ExprHasProperty(p, EP_IntValue) ); + sqlite3DbFree(db, p->u.zToken); + } if( !ExprHasProperty(p, EP_Static) ){ sqlite3DbFreeNN(db, p); } @@ -1191,7 +1280,7 @@ void sqlite3ExprUnmapAndDelete(Parse *pParse, Expr *p){ ** passed as the first argument. This is always one of EXPR_FULLSIZE, ** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE. */ -static int exprStructSize(Expr *p){ +static int exprStructSize(const Expr *p){ if( ExprHasProperty(p, EP_TokenOnly) ) return EXPR_TOKENONLYSIZE; if( ExprHasProperty(p, EP_Reduced) ) return EXPR_REDUCEDSIZE; return EXPR_FULLSIZE; @@ -1231,7 +1320,7 @@ static int exprStructSize(Expr *p){ ** of dupedExprStructSize() contain multiple assert() statements that attempt ** to enforce this constraint. */ -static int dupedExprStructSize(Expr *p, int flags){ +static int dupedExprStructSize(const Expr *p, int flags){ int nSize; assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); @@ -1262,7 +1351,7 @@ static int dupedExprStructSize(Expr *p, int flags){ ** of the Expr structure and a copy of the Expr.u.zToken string (if that ** string is defined.) */ -static int dupedExprNodeSize(Expr *p, int flags){ +static int dupedExprNodeSize(const Expr *p, int flags){ int nByte = dupedExprStructSize(p, flags) & 0xfff; if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ nByte += sqlite3Strlen30NN(p->u.zToken)+1; @@ -1283,7 +1372,7 @@ static int dupedExprNodeSize(Expr *p, int flags){ ** and Expr.pRight variables (but not for any structures pointed to or ** descended from the Expr.x.pList or Expr.x.pSelect variables). */ -static int dupedExprSize(Expr *p, int flags){ +static int dupedExprSize(const Expr *p, int flags){ int nByte = 0; if( p ){ nByte = dupedExprNodeSize(p, flags); @@ -1302,7 +1391,7 @@ static int dupedExprSize(Expr *p, int flags){ ** if any. Before returning, *pzBuffer is set to the first byte past the ** portion of the buffer copied into by this function. */ -static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ +static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ Expr *pNew; /* Value to return */ u8 *zAlloc; /* Memory space from which to build Expr object */ u32 staticFlag; /* EP_Static if space not obtained from malloc */ @@ -1365,7 +1454,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){ /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ - if( ExprHasProperty(p, EP_xIsSelect) ){ + if( ExprUseXSelect(p) ){ pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags); }else{ pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags); @@ -1394,7 +1483,6 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ if( pNew->op==TK_SELECT_COLUMN ){ pNew->pLeft = p->pLeft; - assert( p->iColumn==0 || p->pRight==0 ); assert( p->pRight==0 || p->pRight==p->pLeft || ExprHasProperty(p->pLeft, EP_Subquery) ); }else{ @@ -1484,15 +1572,17 @@ static void gatherSelectWindows(Select *p){ ** truncated version of the usual Expr structure that will be stored as ** part of the in-memory representation of the database schema. */ -Expr *sqlite3ExprDup(sqlite3 *db, Expr *p, int flags){ +Expr *sqlite3ExprDup(sqlite3 *db, const Expr *p, int flags){ assert( flags==0 || flags==EXPRDUP_REDUCE ); return p ? exprDup(db, p, flags, 0) : 0; } -ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ +ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){ ExprList *pNew; - struct ExprList_item *pItem, *pOldItem; + struct ExprList_item *pItem; + const struct ExprList_item *pOldItem; int i; - Expr *pPriorSelectCol = 0; + Expr *pPriorSelectColOld = 0; + Expr *pPriorSelectColNew = 0; assert( db!=0 ); if( p==0 ) return 0; pNew = sqlite3DbMallocRawNN(db, sqlite3DbMallocSize(db, p)); @@ -1509,17 +1599,17 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ && pOldExpr->op==TK_SELECT_COLUMN && (pNewExpr = pItem->pExpr)!=0 ){ - assert( pNewExpr->iColumn==0 || i>0 ); - if( pNewExpr->iColumn==0 ){ - assert( pOldExpr->pLeft==pOldExpr->pRight - || ExprHasProperty(pOldExpr->pLeft, EP_Subquery) ); - pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight; + if( pNewExpr->pRight ){ + pPriorSelectColOld = pOldExpr->pRight; + pPriorSelectColNew = pNewExpr->pRight; + pNewExpr->pLeft = pNewExpr->pRight; }else{ - assert( i>0 ); - assert( pItem[-1].pExpr!=0 ); - assert( pNewExpr->iColumn==pItem[-1].pExpr->iColumn+1 ); - assert( pPriorSelectCol==pItem[-1].pExpr->pLeft ); - pNewExpr->pLeft = pPriorSelectCol; + if( pOldExpr->pLeft!=pPriorSelectColOld ){ + pPriorSelectColOld = pOldExpr->pLeft; + pPriorSelectColNew = sqlite3ExprDup(db, pPriorSelectColOld, flags); + pNewExpr->pRight = pPriorSelectColNew; + } + pNewExpr->pLeft = pPriorSelectColNew; } } pItem->zEName = sqlite3DbStrDup(db, pOldItem->zEName); @@ -1541,7 +1631,7 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \ || !defined(SQLITE_OMIT_SUBQUERY) -SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ +SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){ SrcList *pNew; int i; int nByte; @@ -1553,7 +1643,7 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ pNew->nSrc = pNew->nAlloc = p->nSrc; for(i=0; inSrc; i++){ SrcItem *pNewItem = &pNew->a[i]; - SrcItem *pOldItem = &p->a[i]; + const SrcItem *pOldItem = &p->a[i]; Table *pTab; pNewItem->pSchema = pOldItem->pSchema; pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase); @@ -1585,7 +1675,7 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ } return pNew; } -IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ +IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){ IdList *pNew; int i; assert( db!=0 ); @@ -1609,11 +1699,11 @@ IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ } return pNew; } -Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ +Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){ Select *pRet = 0; Select *pNext = 0; Select **pp = &pRet; - Select *p; + const Select *p; assert( db!=0 ); for(p=pDup; p; p=p->pPrior){ @@ -1658,7 +1748,7 @@ Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ return pRet; } #else -Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ +Select *sqlite3SelectDup(sqlite3 *db, const Select *p, int flags){ assert( p==0 ); return 0; } @@ -1778,11 +1868,9 @@ ExprList *sqlite3ExprListAppendVector( } for(i=0; inId; i++){ - Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i); + Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i, pColumns->nId); assert( pSubExpr!=0 || db->mallocFailed ); - assert( pSubExpr==0 || pSubExpr->iTable==0 ); if( pSubExpr==0 ) continue; - pSubExpr->iTable = pColumns->nId; pList = sqlite3ExprListAppend(pParse, pList, pSubExpr); if( pList ){ assert( pList->nExpr==iFirst+i+1 ); @@ -1856,7 +1944,7 @@ void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int eNulls){ void sqlite3ExprListSetName( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to add the span. */ - Token *pName, /* Name to be added */ + const Token *pName, /* Name to be added */ int dequote /* True to cause the name to be dequoted */ ){ assert( pList!=0 || pParse->db->mallocFailed!=0 ); @@ -1874,7 +1962,7 @@ void sqlite3ExprListSetName( ** to the token-map. */ sqlite3Dequote(pItem->zEName); if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, (void*)pItem->zEName, pName); + sqlite3RenameTokenMap(pParse, (const void*)pItem->zEName, pName); } } } @@ -1993,7 +2081,7 @@ u32 sqlite3IsTrueOrFalse(const char *zIn){ int sqlite3ExprIdToTrueFalse(Expr *pExpr){ u32 v; assert( pExpr->op==TK_ID || pExpr->op==TK_STRING ); - if( !ExprHasProperty(pExpr, EP_Quoted) + if( !ExprHasProperty(pExpr, EP_Quoted|EP_IntValue) && (v = sqlite3IsTrueOrFalse(pExpr->u.zToken))!=0 ){ pExpr->op = TK_TRUEFALSE; @@ -2010,6 +2098,7 @@ int sqlite3ExprIdToTrueFalse(Expr *pExpr){ int sqlite3ExprTruthValue(const Expr *pExpr){ pExpr = sqlite3ExprSkipCollate((Expr*)pExpr); assert( pExpr->op==TK_TRUEFALSE ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 || sqlite3StrICmp(pExpr->u.zToken,"false")==0 ); return pExpr->u.zToken[4]==0; @@ -2214,7 +2303,7 @@ static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){ } /* Check if pExpr is a sub-select. If so, consider it variable. */ - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ pWalker->eCode = 0; return WRC_Abort; } @@ -2302,7 +2391,7 @@ int sqlite3ExprContainsSubquery(Expr *p){ ** in *pValue. If the expression is not an integer or if it is too big ** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged. */ -int sqlite3ExprIsInteger(Expr *p, int *pValue){ +int sqlite3ExprIsInteger(const Expr *p, int *pValue){ int rc = 0; if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */ @@ -2321,9 +2410,9 @@ int sqlite3ExprIsInteger(Expr *p, int *pValue){ break; } case TK_UMINUS: { - int v; + int v = 0; if( sqlite3ExprIsInteger(p->pLeft, &v) ){ - assert( v!=(-2147483647-1) ); + assert( ((unsigned int)v)!=0x80000000 ); *pValue = -v; rc = 1; } @@ -2364,10 +2453,11 @@ int sqlite3ExprCanBeNull(const Expr *p){ case TK_BLOB: return 0; case TK_COLUMN: + assert( ExprUseYTab(p) ); return ExprHasProperty(p, EP_CanBeNull) || p->y.pTab==0 || /* Reference to column of index on expression */ (p->iColumn>=0 - && ALWAYS(p->y.pTab->aCol!=0) /* Defense against OOM problems */ + && p->y.pTab->aCol!=0 /* Possible due to prior error */ && p->y.pTab->aCol[p->iColumn].notNull==0); default: return 1; @@ -2435,13 +2525,13 @@ int sqlite3IsRowid(const char *z){ ** table, then return NULL. */ #ifndef SQLITE_OMIT_SUBQUERY -static Select *isCandidateForInOpt(Expr *pX){ +static Select *isCandidateForInOpt(const Expr *pX){ Select *p; SrcList *pSrc; ExprList *pEList; Table *pTab; int i; - if( !ExprHasProperty(pX, EP_xIsSelect) ) return 0; /* Not a subquery */ + if( !ExprUseXSelect(pX) ) return 0; /* Not a subquery */ if( ExprHasProperty(pX, EP_VarSelect) ) return 0; /* Correlated subq */ p = pX->x.pSelect; if( p->pPrior ) return 0; /* Not a compound SELECT */ @@ -2459,7 +2549,7 @@ static Select *isCandidateForInOpt(Expr *pX){ if( pSrc->a[0].pSelect ) return 0; /* FROM is not a subquery or view */ pTab = pSrc->a[0].pTab; assert( pTab!=0 ); - assert( pTab->pSelect==0 ); /* FROM clause is not a view */ + assert( !IsView(pTab) ); /* FROM clause is not a view */ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ pEList = p->pEList; assert( pEList!=0 ); @@ -2612,7 +2702,7 @@ int sqlite3FindInIndex( ** or not NULL is actually possible (it may not be, for example, due ** to NOT NULL constraints in the schema). If no NULL values are possible, ** set prRhsHasNull to 0 before continuing. */ - if( prRhsHasNull && (pX->flags & EP_xIsSelect) ){ + if( prRhsHasNull && ExprUseXSelect(pX) ){ int i; ExprList *pEList = pX->x.pSelect->pEList; for(i=0; inExpr; i++){ @@ -2713,7 +2803,8 @@ int sqlite3FindInIndex( CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); int j; - assert( pReq!=0 || pRhs->iColumn==XN_ROWID || pParse->nErr ); + assert( pReq!=0 || pRhs->iColumn==XN_ROWID + || pParse->nErr || db->mallocFailed ); for(j=0; jaiColumn[j]!=pRhs->iColumn ) continue; assert( pIdx->azColl[j] ); @@ -2768,7 +2859,7 @@ int sqlite3FindInIndex( */ if( eType==0 && (inFlags & IN_INDEX_NOOP_OK) - && !ExprHasProperty(pX, EP_xIsSelect) + && ExprUseXList(pX) && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) ){ eType = IN_INDEX_NOOP; @@ -2813,10 +2904,10 @@ int sqlite3FindInIndex( ** It is the responsibility of the caller to ensure that the returned ** string is eventually freed using sqlite3DbFree(). */ -static char *exprINAffinity(Parse *pParse, Expr *pExpr){ +static char *exprINAffinity(Parse *pParse, const Expr *pExpr){ Expr *pLeft = pExpr->pLeft; int nVal = sqlite3ExprVectorSize(pLeft); - Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0; + Select *pSelect = ExprUseXSelect(pExpr) ? pExpr->x.pSelect : 0; char *zRet; assert( pExpr->op==TK_IN ); @@ -2866,7 +2957,7 @@ void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){ */ void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ #ifndef SQLITE_OMIT_SUBQUERY - if( pExpr->flags & EP_xIsSelect ){ + if( ExprUseXSelect(pExpr) ){ sqlite3SubselectError(pParse, pExpr->x.pSelect->pEList->nExpr, 1); }else #endif @@ -2930,10 +3021,11 @@ void sqlite3CodeRhsOfIN( */ if( ExprHasProperty(pExpr, EP_Subrtn) ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ ExplainQueryPlan((pParse, 0, "REUSE LIST SUBQUERY %d", pExpr->x.pSelect->selId)); } + assert( ExprUseYSub(pExpr) ); sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); sqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable); @@ -2942,6 +3034,7 @@ void sqlite3CodeRhsOfIN( } /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); ExprSetProperty(pExpr, EP_Subrtn); assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); pExpr->y.sub.regReturn = ++pParse->nMem; @@ -2962,7 +3055,7 @@ void sqlite3CodeRhsOfIN( pExpr->iTable = iTab; addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, nVal); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ VdbeComment((v, "Result of SELECT %u", pExpr->x.pSelect->selId)); }else{ VdbeComment((v, "RHS of IN operator")); @@ -2970,7 +3063,7 @@ void sqlite3CodeRhsOfIN( #endif pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ /* Case 1: expr IN (SELECT ...) ** ** Generate code to write the results of the select into the temporary @@ -3068,6 +3161,7 @@ void sqlite3CodeRhsOfIN( if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); /* Subroutine return */ + assert( ExprUseYSub(pExpr) ); sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); sqlite3ClearTempRegCache(pParse); @@ -3104,19 +3198,22 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXSelect(pExpr) ); pSel = pExpr->x.pSelect; /* If this routine has already been coded, then invoke it as a ** subroutine. */ if( ExprHasProperty(pExpr, EP_Subrtn) ){ ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); + assert( ExprUseYSub(pExpr) ); sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); return pExpr->iTable; } /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); + assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ); ExprSetProperty(pExpr, EP_Subrtn); pExpr->y.sub.regReturn = ++pParse->nMem; pExpr->y.sub.iAddr = @@ -3196,6 +3293,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ } /* Subroutine return */ + assert( ExprUseYSub(pExpr) ); sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); sqlite3ClearTempRegCache(pParse); @@ -3212,7 +3310,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ */ int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ int nVector = sqlite3ExprVectorSize(pIn->pLeft); - if( (pIn->flags & EP_xIsSelect)!=0 && !pParse->db->mallocFailed ){ + if( ExprUseXSelect(pIn) && !pParse->db->mallocFailed ){ if( nVector!=pIn->x.pSelect->pEList->nExpr ){ sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); return 1; @@ -3346,13 +3444,15 @@ static void sqlite3ExprCodeIN( ** This is step (1) in the in-operator.md optimized algorithm. */ if( eType==IN_INDEX_NOOP ){ - ExprList *pList = pExpr->x.pList; - CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); + ExprList *pList; + CollSeq *pColl; int labelOk = sqlite3VdbeMakeLabel(pParse); int r2, regToFree; int regCkNull = 0; int ii; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; + pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); if( destIfNull!=destIfFalse ){ regCkNull = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull); @@ -3589,9 +3689,10 @@ void sqlite3ExprCodeLoadIndexColumn( ** and store the result in register regOut */ void sqlite3ExprCodeGeneratedColumn( - Parse *pParse, - Column *pCol, - int regOut + Parse *pParse, /* Parsing context */ + Table *pTab, /* Table containing the generated column */ + Column *pCol, /* The generated column */ + int regOut /* Put the result in this register */ ){ int iAddr; Vdbe *v = pParse->pVdbe; @@ -3602,7 +3703,7 @@ void sqlite3ExprCodeGeneratedColumn( }else{ iAddr = 0; } - sqlite3ExprCodeCopy(pParse, pCol->pDflt, regOut); + sqlite3ExprCodeCopy(pParse, sqlite3ColumnExpr(pTab,pCol), regOut); if( pCol->affinity>=SQLITE_AFF_TEXT ){ sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1); } @@ -3638,12 +3739,13 @@ void sqlite3ExprCodeGetColumnOfTable( }else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){ Parse *pParse = sqlite3VdbeParser(v); if( pCol->colFlags & COLFLAG_BUSY ){ - sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pCol->zName); + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", + pCol->zCnName); }else{ int savedSelfTab = pParse->iSelfTab; pCol->colFlags |= COLFLAG_BUSY; pParse->iSelfTab = iTabCur+1; - sqlite3ExprCodeGeneratedColumn(pParse, pCol, regOut); + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, regOut); pParse->iSelfTab = savedSelfTab; pCol->colFlags &= ~COLFLAG_BUSY; } @@ -3736,6 +3838,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ int i; iResult = pParse->nMem+1; pParse->nMem += nResult; + assert( ExprUseXList(p) ); for(i=0; ix.pList->a[i].pExpr, i+iResult); } @@ -3810,6 +3913,7 @@ static int exprCodeInlineFunction( ** Test-only SQL functions that are only usable if enabled ** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS */ +#if !defined(SQLITE_UNTESTABLE) case INLINEFUNC_expr_compare: { /* Compare two expressions using sqlite3ExprCompare() */ assert( nFarg==2 ); @@ -3843,7 +3947,6 @@ static int exprCodeInlineFunction( break; } -#ifdef SQLITE_DEBUG case INLINEFUNC_affinity: { /* The AFFINITY() function evaluates to a string that describes ** the type affinity of the argument. This is used for testing of @@ -3857,7 +3960,7 @@ static int exprCodeInlineFunction( (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]); break; } -#endif +#endif /* !defined(SQLITE_UNTESTABLE) */ } return target; } @@ -3911,7 +4014,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( pCol->iColumn<0 ){ VdbeComment((v,"%s.rowid",pTab->zName)); }else{ - VdbeComment((v,"%s.%s",pTab->zName,pTab->aCol[pCol->iColumn].zName)); + VdbeComment((v,"%s.%s", + pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, target); } @@ -3933,6 +4037,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ */ int aff; iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target); + assert( ExprUseYTab(pExpr) ); if( pExpr->y.pTab ){ aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); }else{ @@ -3956,9 +4061,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** immediately prior to the first column. */ Column *pCol; - Table *pTab = pExpr->y.pTab; + Table *pTab; int iSrc; int iCol = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; assert( pTab!=0 ); assert( iCol>=XN_ROWID ); assert( iColnCol ); @@ -3972,12 +4079,12 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( pCol->colFlags & COLFLAG_GENERATED ){ if( pCol->colFlags & COLFLAG_BUSY ){ sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", - pCol->zName); + pCol->zCnName); return 0; } pCol->colFlags |= COLFLAG_BUSY; if( pCol->colFlags & COLFLAG_NOTAVAIL ){ - sqlite3ExprCodeGeneratedColumn(pParse, pCol, iSrc); + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, iSrc); } pCol->colFlags &= ~(COLFLAG_BUSY|COLFLAG_NOTAVAIL); return iSrc; @@ -3996,6 +4103,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ iTab = pParse->iSelfTab - 1; } } + assert( ExprUseYTab(pExpr) ); iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab, pExpr->iColumn, iTab, target, pExpr->op2); @@ -4073,6 +4181,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); inReg = target; } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3VdbeAddOp2(v, OP_Cast, target, sqlite3AffinityType(pExpr->u.zToken, 0)); return inReg; @@ -4240,8 +4349,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** multiple times if we know they always give the same result */ return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); } - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); assert( !ExprHasProperty(pExpr, EP_TokenOnly) ); + assert( ExprUseXList(pExpr) ); pFarg = pExpr->x.pList; nFarg = pFarg ? pFarg->nExpr : 0; assert( !ExprHasProperty(pExpr, EP_IntValue) ); @@ -4330,7 +4439,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } #ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC - if( pDef->funcFlags & SQLITE_FUNC_OFFSET ){ + if( (pDef->funcFlags & SQLITE_FUNC_OFFSET)!=0 && ALWAYS(pFarg!=0) ){ Expr *pArg = pFarg->a[0].pExpr; if( pArg->op==TK_COLUMN ){ sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); @@ -4360,7 +4469,10 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ testcase( op==TK_SELECT ); if( pParse->db->mallocFailed ){ return 0; - }else if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){ + }else if( op==TK_SELECT + && ALWAYS( ExprUseXSelect(pExpr) ) + && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 + ){ sqlite3SubselectError(pParse, nCol, 1); }else{ return sqlite3CodeSubselect(pParse, pExpr); @@ -4372,11 +4484,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( pExpr->pLeft->iTable==0 ){ pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft); } - assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT - || pExpr->pLeft->op==TK_ERROR ); - if( pExpr->iTable!=0 - && pExpr->iTable!=(n = sqlite3ExprVectorSize(pExpr->pLeft)) - ){ + assert( pExpr->pLeft->op==TK_SELECT || pExpr->pLeft->op==TK_ERROR ); + n = sqlite3ExprVectorSize(pExpr->pLeft); + if( pExpr->iTable!=n ){ sqlite3ErrorMsg(pParse, "%d columns assigned %d values", pExpr->iTable, n); } @@ -4444,9 +4554,14 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** p1==1 -> old.a p1==4 -> new.a ** p1==2 -> old.b p1==5 -> new.b */ - Table *pTab = pExpr->y.pTab; - int iCol = pExpr->iColumn; - int p1 = pExpr->iTable * (pTab->nCol+1) + 1 + Table *pTab; + int iCol; + int p1; + + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; + iCol = pExpr->iColumn; + p1 = pExpr->iTable * (pTab->nCol+1) + 1 + sqlite3TableColumnToStorage(pTab, iCol); assert( pExpr->iTable==0 || pExpr->iTable==1 ); @@ -4457,7 +4572,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeAddOp2(v, OP_Param, p1, target); VdbeComment((v, "r[%d]=%s.%s", target, (pExpr->iTable ? "new" : "old"), - (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[iCol].zName) + (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[iCol].zCnName) )); #ifndef SQLITE_OMIT_FLOATING_POINT @@ -4534,7 +4649,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ Expr *pDel = 0; sqlite3 *db = pParse->db; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) && pExpr->x.pList ); + assert( ExprUseXList(pExpr) && pExpr->x.pList!=0 ); assert(pExpr->x.pList->nExpr > 0); pEList = pExpr->x.pList; aListelem = pEList->a; @@ -4731,7 +4846,7 @@ void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); if( inReg!=target ){ u8 op; - if( ExprHasProperty(pExpr,EP_Subquery) ){ + if( ALWAYS(pExpr) && ExprHasProperty(pExpr,EP_Subquery) ){ op = OP_Copy; }else{ op = OP_SCopy; @@ -4879,7 +4994,7 @@ static void exprCodeBetween( memset(&compRight, 0, sizeof(Expr)); memset(&exprAnd, 0, sizeof(Expr)); - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); pDel = sqlite3ExprDup(db, pExpr->pLeft, 0); if( db->mallocFailed==0 ){ exprAnd.op = TK_AND; @@ -5269,7 +5384,11 @@ void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,int jumpIfNull){ ** Otherwise, if the values are not the same or if pExpr is not a simple ** SQL value, zero is returned. */ -static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ +static int exprCompareVariable( + const Parse *pParse, + const Expr *pVar, + const Expr *pExpr +){ int res = 0; int iVar; sqlite3_value *pL, *pR = 0; @@ -5321,7 +5440,12 @@ static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ ** Argument pParse should normally be NULL. If it is not NULL and pA or ** pB causes a return value of 2. */ -int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){ +int sqlite3ExprCompare( + const Parse *pParse, + const Expr *pA, + const Expr *pB, + int iTab +){ u32 combinedFlags; if( pA==0 || pB==0 ){ return pB==pA ? 0 : 2; @@ -5345,7 +5469,9 @@ int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){ } return 2; } - if( pA->op!=TK_COLUMN && pA->op!=TK_AGG_COLUMN && pA->u.zToken ){ + assert( !ExprHasProperty(pA, EP_IntValue) ); + assert( !ExprHasProperty(pB, EP_IntValue) ); + if( pA->u.zToken ){ if( pA->op==TK_FUNCTION || pA->op==TK_AGG_FUNCTION ){ if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; #ifndef SQLITE_OMIT_WINDOWFUNC @@ -5363,7 +5489,12 @@ int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){ return 0; }else if( pA->op==TK_COLLATE ){ if( sqlite3_stricmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; - }else if( ALWAYS(pB->u.zToken!=0) && strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ + }else + if( pB->u.zToken!=0 + && pA->op!=TK_COLUMN + && pA->op!=TK_AGG_COLUMN + && strcmp(pA->u.zToken,pB->u.zToken)!=0 + ){ return 2; } } @@ -5405,7 +5536,7 @@ int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){ ** Two NULL pointers are considered to be the same. But a NULL pointer ** always differs from a non-NULL pointer. */ -int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ +int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){ int i; if( pA==0 && pB==0 ) return 0; if( pA==0 || pB==0 ) return 1; @@ -5424,7 +5555,7 @@ int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ ** Like sqlite3ExprCompare() except COLLATE operators at the top-level ** are ignored. */ -int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ +int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){ return sqlite3ExprCompare(0, sqlite3ExprSkipCollateAndLikely(pA), sqlite3ExprSkipCollateAndLikely(pB), @@ -5438,9 +5569,9 @@ int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ ** non-NULL if pNN is not NULL */ static int exprImpliesNotNull( - Parse *pParse, /* Parsing context */ - Expr *p, /* The expression to be checked */ - Expr *pNN, /* The expression that is NOT NULL */ + const Parse *pParse,/* Parsing context */ + const Expr *p, /* The expression to be checked */ + const Expr *pNN, /* The expression that is NOT NULL */ int iTab, /* Table being evaluated */ int seenNot /* Return true only if p can be any non-NULL value */ ){ @@ -5452,12 +5583,13 @@ static int exprImpliesNotNull( switch( p->op ){ case TK_IN: { if( seenNot && ExprHasProperty(p, EP_xIsSelect) ) return 0; - assert( ExprHasProperty(p,EP_xIsSelect) - || (p->x.pList!=0 && p->x.pList->nExpr>0) ); + assert( ExprUseXSelect(p) || (p->x.pList!=0 && p->x.pList->nExpr>0) ); return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); } case TK_BETWEEN: { - ExprList *pList = p->x.pList; + ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; assert( pList!=0 ); assert( pList->nExpr==2 ); if( seenNot ) return 0; @@ -5533,7 +5665,12 @@ static int exprImpliesNotNull( ** improvement. Returning false might cause a performance reduction, but ** it will always give the correct answer and is hence always safe. */ -int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){ +int sqlite3ExprImpliesExpr( + const Parse *pParse, + const Expr *pE1, + const Expr *pE2, + int iTab +){ if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){ return 1; } @@ -5629,10 +5766,14 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_GE ); /* The y.pTab=0 assignment in wherecode.c always happens after the ** impliesNotNullRow() test */ - if( (pLeft->op==TK_COLUMN && ALWAYS(pLeft->y.pTab!=0) - && IsVirtual(pLeft->y.pTab)) - || (pRight->op==TK_COLUMN && ALWAYS(pRight->y.pTab!=0) - && IsVirtual(pRight->y.pTab)) + assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); + assert( pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); + if( (pLeft->op==TK_COLUMN + && pLeft->y.pTab!=0 + && IsVirtual(pLeft->y.pTab)) + || (pRight->op==TK_COLUMN + && pRight->y.pTab!=0 + && IsVirtual(pRight->y.pTab)) ){ return WRC_Prune; } @@ -5741,88 +5882,125 @@ int sqlite3ExprCoveredByIndex( } -/* -** An instance of the following structure is used by the tree walker -** to count references to table columns in the arguments of an -** aggregate function, in order to implement the -** sqlite3FunctionThisSrc() routine. -*/ -struct SrcCount { - SrcList *pSrc; /* One particular FROM clause in a nested query */ - int iSrcInner; /* Smallest cursor number in this context */ - int nThis; /* Number of references to columns in pSrcList */ - int nOther; /* Number of references to columns in other FROM clauses */ +/* Structure used to pass information throught the Walker in order to +** implement sqlite3ReferencesSrcList(). +*/ +struct RefSrcList { + sqlite3 *db; /* Database connection used for sqlite3DbRealloc() */ + SrcList *pRef; /* Looking for references to these tables */ + i64 nExclude; /* Number of tables to exclude from the search */ + int *aiExclude; /* Cursor IDs for tables to exclude from the search */ }; /* -** xSelect callback for sqlite3FunctionUsesThisSrc(). If this is the first -** SELECT with a FROM clause encountered during this iteration, set -** SrcCount.iSrcInner to the cursor number of the leftmost object in -** the FROM cause. +** Walker SELECT callbacks for sqlite3ReferencesSrcList(). +** +** When entering a new subquery on the pExpr argument, add all FROM clause +** entries for that subquery to the exclude list. +** +** When leaving the subquery, remove those entries from the exclude list. */ -static int selectSrcCount(Walker *pWalker, Select *pSel){ - struct SrcCount *p = pWalker->u.pSrcCount; - if( p->iSrcInner==0x7FFFFFFF && ALWAYS(pSel->pSrc) && pSel->pSrc->nSrc ){ - pWalker->u.pSrcCount->iSrcInner = pSel->pSrc->a[0].iCursor; +static int selectRefEnter(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + i64 i, j; + int *piNew; + if( pSrc->nSrc==0 ) return WRC_Continue; + j = p->nExclude; + p->nExclude += pSrc->nSrc; + piNew = sqlite3DbRealloc(p->db, p->aiExclude, p->nExclude*sizeof(int)); + if( piNew==0 ){ + p->nExclude = 0; + return WRC_Abort; + }else{ + p->aiExclude = piNew; + } + for(i=0; inSrc; i++, j++){ + p->aiExclude[j] = pSrc->a[i].iCursor; } return WRC_Continue; } +static void selectRefLeave(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + if( p->nExclude ){ + assert( p->nExclude>=pSrc->nSrc ); + p->nExclude -= pSrc->nSrc; + } +} -/* -** Count the number of references to columns. +/* This is the Walker EXPR callback for sqlite3ReferencesSrcList(). +** +** Set the 0x01 bit of pWalker->eCode if there is a reference to any +** of the tables shown in RefSrcList.pRef. +** +** Set the 0x02 bit of pWalker->eCode if there is a reference to a +** table is in neither RefSrcList.pRef nor RefSrcList.aiExclude. */ -static int exprSrcCount(Walker *pWalker, Expr *pExpr){ - /* There was once a NEVER() on the second term on the grounds that - ** sqlite3FunctionUsesThisSrc() was always called before - ** sqlite3ExprAnalyzeAggregates() and so the TK_COLUMNs have not yet - ** been converted into TK_AGG_COLUMN. But this is no longer true due - ** to window functions - sqlite3WindowRewrite() may now indirectly call - ** FunctionUsesThisSrc() when creating a new sub-select. */ - if( pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN ){ +static int exprRefToSrcList(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN + || pExpr->op==TK_AGG_COLUMN + ){ int i; - struct SrcCount *p = pWalker->u.pSrcCount; - SrcList *pSrc = p->pSrc; + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = p->pRef; int nSrc = pSrc ? pSrc->nSrc : 0; for(i=0; iiTable==pSrc->a[i].iCursor ) break; + if( pExpr->iTable==pSrc->a[i].iCursor ){ + pWalker->eCode |= 1; + return WRC_Continue; + } } - if( inThis++; - }else if( pExpr->iTableiSrcInner ){ - /* In a well-formed parse tree (no name resolution errors), - ** TK_COLUMN nodes with smaller Expr.iTable values are in an - ** outer context. Those are the only ones to count as "other" */ - p->nOther++; + for(i=0; inExclude && p->aiExclude[i]!=pExpr->iTable; i++){} + if( i>=p->nExclude ){ + pWalker->eCode |= 2; } } return WRC_Continue; } /* -** Determine if any of the arguments to the pExpr Function reference -** pSrcList. Return true if they do. Also return true if the function -** has no arguments or has only constant arguments. Return false if pExpr -** references columns but not columns of tables found in pSrcList. +** Check to see if pExpr references any tables in pSrcList. +** Possible return values: +** +** 1 pExpr does references a table in pSrcList. +** +** 0 pExpr references some table that is not defined in either +** pSrcList or in subqueries of pExpr itself. +** +** -1 pExpr only references no tables at all, or it only +** references tables defined in subqueries of pExpr itself. +** +** As currently used, pExpr is always an aggregate function call. That +** fact is exploited for efficiency. */ -int sqlite3FunctionUsesThisSrc(Expr *pExpr, SrcList *pSrcList){ +int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ Walker w; - struct SrcCount cnt; - assert( pExpr->op==TK_AGG_FUNCTION ); + struct RefSrcList x; memset(&w, 0, sizeof(w)); - w.xExprCallback = exprSrcCount; - w.xSelectCallback = selectSrcCount; - w.u.pSrcCount = &cnt; - cnt.pSrc = pSrcList; - cnt.iSrcInner = (pSrcList&&pSrcList->nSrc)?pSrcList->a[0].iCursor:0x7FFFFFFF; - cnt.nThis = 0; - cnt.nOther = 0; + memset(&x, 0, sizeof(x)); + w.xExprCallback = exprRefToSrcList; + w.xSelectCallback = selectRefEnter; + w.xSelectCallback2 = selectRefLeave; + w.u.pRefSrcList = &x; + x.db = pParse->db; + x.pRef = pSrcList; + assert( pExpr->op==TK_AGG_FUNCTION ); + assert( ExprUseXList(pExpr) ); sqlite3WalkExprList(&w, pExpr->x.pList); #ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(pExpr, EP_WinFunc) ){ sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter); } #endif - return cnt.nThis>0 || cnt.nOther==0; + sqlite3DbFree(pParse->db, x.aiExclude); + if( w.eCode & 0x01 ){ + return 1; + }else if( w.eCode ){ + return 0; + }else{ + return -1; + } } /* @@ -5957,6 +6135,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ && (k = addAggInfoColumn(pParse->db, pAggInfo))>=0 ){ pCol = &pAggInfo->aCol[k]; + assert( ExprUseYTab(pExpr) ); pCol->pTab = pExpr->y.pTab; pCol->iTable = pExpr->iTable; pCol->iColumn = pExpr->iColumn; @@ -6020,7 +6199,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ pItem = &pAggInfo->aFunc[i]; pItem->pFExpr = pExpr; pItem->iMem = ++pParse->nMem; - assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( ExprUseUToken(pExpr) ); pItem->pFunc = sqlite3FindFunction(pParse->db, pExpr->u.zToken, pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); diff --git a/src/fkey.c b/src/fkey.c index 9f622f40c..6ee35bf32 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -215,7 +215,9 @@ int sqlite3FkLocateIndex( */ if( pParent->iPKey>=0 ){ if( !zKey ) return 0; - if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zName, zKey) ) return 0; + if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zCnName, zKey) ){ + return 0; + } } }else if( paiCol ){ assert( nCol>1 ); @@ -257,11 +259,11 @@ int sqlite3FkLocateIndex( /* If the index uses a collation sequence that is different from ** the default collation sequence for the column, this index is ** unusable. Bail out early in this case. */ - zDfltColl = pParent->aCol[iCol].zColl; + zDfltColl = sqlite3ColumnColl(&pParent->aCol[iCol]); if( !zDfltColl ) zDfltColl = sqlite3StrBINARY; if( sqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break; - zIdxCol = pParent->aCol[iCol].zName; + zIdxCol = pParent->aCol[iCol].zCnName; for(j=0; jaCol[j].zCol, zIdxCol)==0 ){ if( aiCol ) aiCol[i] = pFKey->aCol[j].iFrom; @@ -485,7 +487,7 @@ static Expr *exprTableRegister( pCol = &pTab->aCol[iCol]; pExpr->iTable = regBase + sqlite3TableColumnToStorage(pTab,iCol) + 1; pExpr->affExpr = pCol->affinity; - zColl = pCol->zColl; + zColl = sqlite3ColumnColl(pCol); if( zColl==0 ) zColl = db->pDfltColl->zName; pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl); }else{ @@ -508,6 +510,7 @@ static Expr *exprTableColumn( ){ Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0); if( pExpr ){ + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pTab; pExpr->iTable = iCursor; pExpr->iColumn = iCol; @@ -594,7 +597,7 @@ static void fkScanChildren( pLeft = exprTableRegister(pParse, pTab, regData, iCol); iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; assert( iCol>=0 ); - zCol = pFKey->pFrom->aCol[iCol].zName; + zCol = pFKey->pFrom->aCol[iCol].zCnName; pRight = sqlite3Expr(db, TK_ID, zCol); pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); pWhere = sqlite3ExprAnd(pParse, pWhere, pEq); @@ -629,7 +632,7 @@ static void fkScanChildren( i16 iCol = pIdx->aiColumn[i]; assert( iCol>=0 ); pLeft = exprTableRegister(pParse, pTab, regData, iCol); - pRight = sqlite3Expr(db, TK_ID, pTab->aCol[iCol].zName); + pRight = sqlite3Expr(db, TK_ID, pTab->aCol[iCol].zCnName); pEq = sqlite3PExpr(pParse, TK_IS, pLeft, pRight); pAll = sqlite3ExprAnd(pParse, pAll, pEq); } @@ -699,6 +702,25 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ } } +/* +** Clear the apTrigger[] cache of CASCADE triggers for all foreign keys +** in a particular database. This needs to happen when the schema +** changes. +*/ +void sqlite3FkClearTriggerCache(sqlite3 *db, int iDb){ + HashElem *k; + Hash *pHash = &db->aDb[iDb].pSchema->tblHash; + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k)){ + Table *pTab = sqliteHashData(k); + FKey *pFKey; + if( !IsOrdinaryTable(pTab) ) continue; + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + fkTriggerDelete(db, pFKey->apTrigger[0]); pFKey->apTrigger[0] = 0; + fkTriggerDelete(db, pFKey->apTrigger[1]); pFKey->apTrigger[1] = 0; + } + } +} + /* ** This function is called to generate code that runs when table pTab is ** being dropped from the database. The SrcList passed as the second argument @@ -718,12 +740,12 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ */ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ sqlite3 *db = pParse->db; - if( (db->flags&SQLITE_ForeignKeys) && !IsVirtual(pTab) ){ + if( (db->flags&SQLITE_ForeignKeys) && IsOrdinaryTable(pTab) ){ int iSkip = 0; Vdbe *v = sqlite3GetVdbe(pParse); assert( v ); /* VDBE has already been allocated */ - assert( pTab->pSelect==0 ); /* Not a view */ + assert( IsOrdinaryTable(pTab) ); if( sqlite3FkReferences(pTab)==0 ){ /* Search for a deferred foreign key constraint for which this table ** is the child table. If one cannot be found, return without @@ -731,7 +753,7 @@ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ ** the entire DELETE if there are no outstanding deferred constraints ** when this statement is run. */ FKey *p; - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ if( p->isDeferred || (db->flags & SQLITE_DeferFKs) ) break; } if( !p ) return; @@ -820,7 +842,7 @@ static int fkParentIsModified( if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){ Column *pCol = &pTab->aCol[iKey]; if( zKey ){ - if( 0==sqlite3StrICmp(pCol->zName, zKey) ) return 1; + if( 0==sqlite3StrICmp(pCol->zCnName, zKey) ) return 1; }else if( pCol->colFlags & COLFLAG_PRIMKEY ){ return 1; } @@ -887,13 +909,14 @@ void sqlite3FkCheck( /* If foreign-keys are disabled, this function is a no-op. */ if( (db->flags&SQLITE_ForeignKeys)==0 ) return; + if( !IsOrdinaryTable(pTab) ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the ** child table (the table that the foreign key definition is part of). */ - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ Table *pTo; /* Parent table of foreign key pFKey */ Index *pIdx = 0; /* Index on key columns in pTo */ int *aiFree = 0; @@ -960,7 +983,7 @@ void sqlite3FkCheck( ** values read from the parent table are NULL. */ if( db->xAuth ){ int rcauth; - char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName; + char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zCnName; rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); bIgnore = (rcauth==SQLITE_IGNORE); } @@ -1075,10 +1098,10 @@ u32 sqlite3FkOldmask( Table *pTab /* Table being modified */ ){ u32 mask = 0; - if( pParse->db->flags&SQLITE_ForeignKeys ){ + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ FKey *p; int i; - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ for(i=0; inCol; i++) mask |= COLUMN_MASK(p->aCol[i].iFrom); } for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ @@ -1128,19 +1151,19 @@ int sqlite3FkRequired( ){ int eRet = 1; /* Value to return if bHaveFK is true */ int bHaveFK = 0; /* If FK processing is required */ - if( pParse->db->flags&SQLITE_ForeignKeys ){ + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ if( !aChange ){ /* A DELETE operation. Foreign key processing is required if the ** table in question is either the child or parent table for any ** foreign key constraint. */ - bHaveFK = (sqlite3FkReferences(pTab) || pTab->pFKey); + bHaveFK = (sqlite3FkReferences(pTab) || pTab->u.tab.pFKey); }else{ /* This is an UPDATE. Foreign key processing is only required if the ** operation modifies one or more child or parent key columns. */ FKey *p; /* Check if any child key columns are being modified. */ - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ if( fkChildIsModified(pTab, p, aChange, chngRowid) ){ if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) eRet = 2; bHaveFK = 1; @@ -1233,8 +1256,8 @@ static Trigger *fkActionTrigger( assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKeynCol) ); assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); sqlite3TokenInit(&tToCol, - pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName); - sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zName); + pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zCnName); + sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zCnName); /* Create the expression "OLD.zToCol = zFromCol". It is important ** that the "OLD.zToCol" term is on the LHS of the = operator, so @@ -1279,7 +1302,7 @@ static Trigger *fkActionTrigger( testcase( pCol->colFlags & COLFLAG_STORED ); pDflt = 0; }else{ - pDflt = pCol->pDflt; + pDflt = sqlite3ColumnExpr(pFKey->pFrom, pCol); } if( pDflt ){ pNew = sqlite3ExprDup(db, pDflt, 0); @@ -1416,9 +1439,9 @@ void sqlite3FkDelete(sqlite3 *db, Table *pTab){ FKey *pFKey; /* Iterator variable */ FKey *pNext; /* Copy of pFKey->pNextFrom */ - assert( db==0 || IsVirtual(pTab) - || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); - for(pFKey=pTab->pFKey; pFKey; pFKey=pNext){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pNext){ + assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); /* Remove the FK from the fkeyHash hash table. */ if( !db || db->pnBytesFreed==0 ){ diff --git a/src/func.c b/src/func.c index dc19ec722..6a2903d32 100644 --- a/src/func.c +++ b/src/func.c @@ -571,9 +571,9 @@ static void last_insert_rowid( /* ** Implementation of the changes() SQL function. ** -** IMP: R-62073-11209 The changes() SQL function is a wrapper -** around the sqlite3_changes() C/C++ function and hence follows the same -** rules for counting changes. +** IMP: R-32760-32347 The changes() SQL function is a wrapper +** around the sqlite3_changes64() C/C++ function and hence follows the +** same rules for counting changes. */ static void changes( sqlite3_context *context, @@ -582,12 +582,12 @@ static void changes( ){ sqlite3 *db = sqlite3_context_db_handle(context); UNUSED_PARAMETER2(NotUsed, NotUsed2); - sqlite3_result_int(context, sqlite3_changes(db)); + sqlite3_result_int64(context, sqlite3_changes64(db)); } /* ** Implementation of the total_changes() SQL function. The return value is -** the same as the sqlite3_total_changes() API function. +** the same as the sqlite3_total_changes64() API function. */ static void total_changes( sqlite3_context *context, @@ -596,9 +596,9 @@ static void total_changes( ){ sqlite3 *db = sqlite3_context_db_handle(context); UNUSED_PARAMETER2(NotUsed, NotUsed2); - /* IMP: R-52756-41993 This function is a wrapper around the - ** sqlite3_total_changes() C/C++ interface. */ - sqlite3_result_int(context, sqlite3_total_changes(db)); + /* IMP: R-11217-42568 This function is a wrapper around the + ** sqlite3_total_changes64() C/C++ interface. */ + sqlite3_result_int64(context, sqlite3_total_changes64(db)); } /* @@ -1715,97 +1715,167 @@ static void minMaxFinalize(sqlite3_context *context){ /* ** group_concat(EXPR, ?SEPARATOR?) +** +** The SEPARATOR goes before the EXPR string. This is tragic. The +** groupConcatInverse() implementation would have been easier if the +** SEPARATOR were appended after EXPR. And the order is undocumented, +** so we could change it, in theory. But the old behavior has been +** around for so long that we dare not, for fear of breaking something. */ +typedef struct { + StrAccum str; /* The accumulated concatenation */ +#ifndef SQLITE_OMIT_WINDOWFUNC + int nAccum; /* Number of strings presently concatenated */ + int nFirstSepLength; /* Used to detect separator length change */ + /* If pnSepLengths!=0, refs an array of inter-string separator lengths, + ** stored as actually incorporated into presently accumulated result. + ** (Hence, its slots in use number nAccum-1 between method calls.) + ** If pnSepLengths==0, nFirstSepLength is the length used throughout. + */ + int *pnSepLengths; +#endif +} GroupConcatCtx; + static void groupConcatStep( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zVal; - StrAccum *pAccum; + GroupConcatCtx *pGCC; const char *zSep; int nVal, nSep; assert( argc==1 || argc==2 ); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum)); - - if( pAccum ){ + pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); + if( pGCC ){ sqlite3 *db = sqlite3_context_db_handle(context); - int firstTerm = pAccum->mxAlloc==0; - pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH]; - if( !firstTerm ){ - if( argc==2 ){ - zSep = (char*)sqlite3_value_text(argv[1]); - nSep = sqlite3_value_bytes(argv[1]); - }else{ - zSep = ","; - nSep = 1; + int firstTerm = pGCC->str.mxAlloc==0; + pGCC->str.mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH]; + if( argc==1 ){ + if( !firstTerm ){ + sqlite3_str_appendchar(&pGCC->str, 1, ','); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = 1; + } +#endif + }else if( !firstTerm ){ + zSep = (char*)sqlite3_value_text(argv[1]); + nSep = sqlite3_value_bytes(argv[1]); + if( zSep ){ + sqlite3_str_append(&pGCC->str, zSep, nSep); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + nSep = 0; + } + if( nSep != pGCC->nFirstSepLength || pGCC->pnSepLengths != 0 ){ + int *pnsl = pGCC->pnSepLengths; + if( pnsl == 0 ){ + /* First separator length variation seen, start tracking them. */ + pnsl = (int*)sqlite3_malloc64((pGCC->nAccum+1) * sizeof(int)); + if( pnsl!=0 ){ + int i = 0, nA = pGCC->nAccum-1; + while( inFirstSepLength; + } + }else{ + pnsl = (int*)sqlite3_realloc64(pnsl, pGCC->nAccum * sizeof(int)); + } + if( pnsl!=0 ){ + if( ALWAYS(pGCC->nAccum>0) ){ + pnsl[pGCC->nAccum-1] = nSep; + } + pGCC->pnSepLengths = pnsl; + }else{ + sqlite3StrAccumSetError(&pGCC->str, SQLITE_NOMEM); + } } - if( zSep ) sqlite3_str_append(pAccum, zSep, nSep); +#endif } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = sqlite3_value_bytes(argv[1]); + } + pGCC->nAccum += 1; +#endif zVal = (char*)sqlite3_value_text(argv[0]); nVal = sqlite3_value_bytes(argv[0]); - if( zVal ) sqlite3_str_append(pAccum, zVal, nVal); + if( zVal ) sqlite3_str_append(&pGCC->str, zVal, nVal); } } + #ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatInverse( sqlite3_context *context, int argc, sqlite3_value **argv ){ - int n; - StrAccum *pAccum; + GroupConcatCtx *pGCC; assert( argc==1 || argc==2 ); + (void)argc; /* Suppress unused parameter warning */ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum)); - /* pAccum is always non-NULL since groupConcatStep() will have always + pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); + /* pGCC is always non-NULL since groupConcatStep() will have always ** run frist to initialize it */ - if( ALWAYS(pAccum) ){ - n = sqlite3_value_bytes(argv[0]); - if( argc==2 ){ - n += sqlite3_value_bytes(argv[1]); + if( ALWAYS(pGCC) ){ + int nVS; + /* Must call sqlite3_value_text() to convert the argument into text prior + ** to invoking sqlite3_value_bytes(), in case the text encoding is UTF16 */ + (void)sqlite3_value_text(argv[0]); + nVS = sqlite3_value_bytes(argv[0]); + pGCC->nAccum -= 1; + if( pGCC->pnSepLengths!=0 ){ + assert(pGCC->nAccum >= 0); + if( pGCC->nAccum>0 ){ + nVS += *pGCC->pnSepLengths; + memmove(pGCC->pnSepLengths, pGCC->pnSepLengths+1, + (pGCC->nAccum-1)*sizeof(int)); + } }else{ - n++; + /* If removing single accumulated string, harmlessly over-do. */ + nVS += pGCC->nFirstSepLength; } - if( n>=(int)pAccum->nChar ){ - pAccum->nChar = 0; + if( nVS>=(int)pGCC->str.nChar ){ + pGCC->str.nChar = 0; }else{ - pAccum->nChar -= n; - memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar); + pGCC->str.nChar -= nVS; + memmove(pGCC->str.zText, &pGCC->str.zText[nVS], pGCC->str.nChar); + } + if( pGCC->str.nChar==0 ){ + pGCC->str.mxAlloc = 0; + sqlite3_free(pGCC->pnSepLengths); + pGCC->pnSepLengths = 0; } - if( pAccum->nChar==0 ) pAccum->mxAlloc = 0; } } #else # define groupConcatInverse 0 #endif /* SQLITE_OMIT_WINDOWFUNC */ static void groupConcatFinalize(sqlite3_context *context){ - StrAccum *pAccum; - pAccum = sqlite3_aggregate_context(context, 0); - if( pAccum ){ - if( pAccum->accError==SQLITE_TOOBIG ){ - sqlite3_result_error_toobig(context); - }else if( pAccum->accError==SQLITE_NOMEM ){ - sqlite3_result_error_nomem(context); - }else{ - sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1, - sqlite3_free); - } + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + sqlite3ResultStrAccum(context, &pGCC->str); +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3_free(pGCC->pnSepLengths); +#endif } } #ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatValue(sqlite3_context *context){ - sqlite3_str *pAccum; - pAccum = (sqlite3_str*)sqlite3_aggregate_context(context, 0); - if( pAccum ){ + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + StrAccum *pAccum = &pGCC->str; if( pAccum->accError==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(context); }else if( pAccum->accError==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); }else{ const char *zText = sqlite3_str_value(pAccum); - sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT); + sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT); } } } @@ -1869,11 +1939,12 @@ int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ int nExpr; assert( pExpr!=0 ); assert( pExpr->op==TK_FUNCTION ); + assert( ExprUseXList(pExpr) ); if( !pExpr->x.pList ){ return 0; } - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); nExpr = pExpr->x.pList->nExpr; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pDef = sqlite3FindFunction(db, pExpr->u.zToken, nExpr, SQLITE_UTF8, 0); #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION if( pDef==0 ) return 0; @@ -1897,6 +1968,7 @@ int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ Expr *pEscape = pExpr->x.pList->a[2].pExpr; char *zEscape; if( pEscape->op!=TK_STRING ) return 0; + assert( !ExprHasProperty(pEscape, EP_IntValue) ); zEscape = pEscape->u.zToken; if( zEscape[0]==0 || zEscape[1]!=0 ) return 0; if( zEscape[0]==aWc[0] ) return 0; @@ -2123,12 +2195,12 @@ void sqlite3RegisterBuiltinFunctions(void){ */ static FuncDef aBuiltinFunc[] = { /***** Functions only available with SQLITE_TESTCTRL_INTERNAL_FUNCTIONS *****/ +#if !defined(SQLITE_UNTESTABLE) TEST_FUNC(implies_nonnull_row, 2, INLINEFUNC_implies_nonnull_row, 0), TEST_FUNC(expr_compare, 2, INLINEFUNC_expr_compare, 0), TEST_FUNC(expr_implies_expr, 2, INLINEFUNC_expr_implies_expr, 0), -#ifdef SQLITE_DEBUG - TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0), -#endif + TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0), +#endif /* !defined(SQLITE_UNTESTABLE) */ /***** Regular functions *****/ #ifdef SQLITE_SOUNDEX FUNCTION(soundex, 1, 0, 0, soundexFunc ), @@ -2160,11 +2232,11 @@ void sqlite3RegisterBuiltinFunctions(void){ FUNCTION(min, -1, 0, 1, minmaxFunc ), FUNCTION(min, 0, 0, 1, 0 ), WAGGREGATE(min, 1, 0, 1, minmaxStep, minMaxFinalize, minMaxValue, 0, - SQLITE_FUNC_MINMAX ), + SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER ), FUNCTION(max, -1, 1, 1, minmaxFunc ), FUNCTION(max, 0, 1, 1, 0 ), WAGGREGATE(max, 1, 1, 1, minmaxStep, minMaxFinalize, minMaxValue, 0, - SQLITE_FUNC_MINMAX ), + SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER ), FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH), FUNCTION(instr, 2, 0, 0, instrFunc ), @@ -2200,9 +2272,10 @@ void sqlite3RegisterBuiltinFunctions(void){ WAGGREGATE(total, 1,0,0, sumStep,totalFinalize,totalFinalize,sumInverse, 0), WAGGREGATE(avg, 1,0,0, sumStep, avgFinalize, avgFinalize, sumInverse, 0), WAGGREGATE(count, 0,0,0, countStep, - countFinalize, countFinalize, countInverse, SQLITE_FUNC_COUNT ), + countFinalize, countFinalize, countInverse, + SQLITE_FUNC_COUNT|SQLITE_FUNC_ANYORDER ), WAGGREGATE(count, 1,0,0, countStep, - countFinalize, countFinalize, countInverse, 0 ), + countFinalize, countFinalize, countInverse, SQLITE_FUNC_ANYORDER ), WAGGREGATE(group_concat, 1, 0, 0, groupConcatStep, groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, @@ -2277,6 +2350,7 @@ void sqlite3RegisterBuiltinFunctions(void){ for(p=sqlite3BuiltinFunctions.a[i]; p; p=p->u.pHash){ int n = sqlite3Strlen30(p->zName); int h = p->zName[0] + n; + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); printf(" %s(%d)", p->zName, h); } printf("\n"); diff --git a/src/global.c b/src/global.c index 4648c26d9..34b7d9583 100644 --- a/src/global.c +++ b/src/global.c @@ -298,6 +298,18 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { */ FuncDefHash sqlite3BuiltinFunctions; +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +/* +** Counter used for coverage testing. Does not come into play for +** release builds. +** +** Access to this global variable is not mutex protected. This might +** result in TSAN warnings. But as the variable does not exist in +** release builds, that should not be a concern. +*/ +unsigned int sqlite3CoverageCounter; +#endif /* SQLITE_COVERAGE_TEST || SQLITE_DEBUG */ + #ifdef VDBE_PROFILE /* ** The following performance counter can be used in place of @@ -347,3 +359,45 @@ const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER; ** Name of the default collating sequence */ const char sqlite3StrBINARY[] = "BINARY"; + +/* +** Standard typenames. These names must match the COLTYPE_* definitions. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +** +** sqlite3StdType[] The actual names of the datatypes. +** +** sqlite3StdTypeLen[] The length (in bytes) of each entry +** in sqlite3StdType[]. +** +** sqlite3StdTypeAffinity[] The affinity associated with each entry +** in sqlite3StdType[]. +** +** sqlite3StdTypeMap[] The type value (as returned from +** sqlite3_column_type() or sqlite3_value_type()) +** for each entry in sqlite3StdType[]. +*/ +const unsigned char sqlite3StdTypeLen[] = { 3, 4, 3, 7, 4, 4 }; +const char sqlite3StdTypeAffinity[] = { + SQLITE_AFF_NUMERIC, + SQLITE_AFF_BLOB, + SQLITE_AFF_INTEGER, + SQLITE_AFF_INTEGER, + SQLITE_AFF_REAL, + SQLITE_AFF_TEXT +}; +const char sqlite3StdTypeMap[] = { + 0, + SQLITE_BLOB, + SQLITE_INTEGER, + SQLITE_INTEGER, + SQLITE_FLOAT, + SQLITE_TEXT +}; +const char *sqlite3StdType[] = { + "ANY", + "BLOB", + "INT", + "INTEGER", + "REAL", + "TEXT" +}; diff --git a/src/hash.h b/src/hash.h index 951cc06c9..3f491e45c 100644 --- a/src/hash.h +++ b/src/hash.h @@ -91,6 +91,6 @@ void sqlite3HashClear(Hash*); /* ** Number of entries in a hash table */ -/* #define sqliteHashCount(H) ((H)->count) // NOT USED */ +#define sqliteHashCount(H) ((H)->count) #endif /* SQLITE_HASH_H */ diff --git a/src/insert.c b/src/insert.c index 1ed0b4315..7fc9a8972 100644 --- a/src/insert.c +++ b/src/insert.c @@ -43,7 +43,7 @@ void sqlite3OpenTable( }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); assert( pPk!=0 ); - assert( pPk->tnum==pTab->tnum ); + assert( pPk->tnum==pTab->tnum || CORRUPT_DB ); sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pPk); VdbeComment((v, "%s", pTab->zName)); @@ -110,28 +110,68 @@ const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ } /* +** Make changes to the evolving bytecode to do affinity transformations +** of values that are about to be gathered into a row for table pTab. +** +** For ordinary (legacy, non-strict) tables: +** ----------------------------------------- +** ** Compute the affinity string for table pTab, if it has not already been ** computed. As an optimization, omit trailing SQLITE_AFF_BLOB affinities. ** -** If the affinity exists (if it is no entirely SQLITE_AFF_BLOB values) and -** if iReg>0 then code an OP_Affinity opcode that will set the affinities -** for register iReg and following. Or if affinities exists and iReg==0, +** If the affinity string is empty (because it was all SQLITE_AFF_BLOB entries +** which were then optimized out) then this routine becomes a no-op. +** +** Otherwise if iReg>0 then code an OP_Affinity opcode that will set the +** affinities for register iReg and following. Or if iReg==0, ** then just set the P4 operand of the previous opcode (which should be ** an OP_MakeRecord) to the affinity string. ** ** A column affinity string has one character per column: ** -** Character Column affinity -** ------------------------------ -** 'A' BLOB -** 'B' TEXT -** 'C' NUMERIC -** 'D' INTEGER -** 'E' REAL +** Character Column affinity +** --------- --------------- +** 'A' BLOB +** 'B' TEXT +** 'C' NUMERIC +** 'D' INTEGER +** 'E' REAL +** +** For STRICT tables: +** ------------------ +** +** Generate an appropropriate OP_TypeCheck opcode that will verify the +** datatypes against the column definitions in pTab. If iReg==0, that +** means an OP_MakeRecord opcode has already been generated and should be +** the last opcode generated. The new OP_TypeCheck needs to be inserted +** before the OP_MakeRecord. The new OP_TypeCheck should use the same +** register set as the OP_MakeRecord. If iReg>0 then register iReg is +** the first of a series of registers that will form the new record. +** Apply the type checking to that array of registers. */ void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ int i, j; - char *zColAff = pTab->zColAff; + char *zColAff; + if( pTab->tabFlags & TF_Strict ){ + if( iReg==0 ){ + /* Move the previous opcode (which should be OP_MakeRecord) forward + ** by one slot and insert a new OP_TypeCheck where the current + ** OP_MakeRecord is found */ + VdbeOp *pPrev; + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + pPrev = sqlite3VdbeGetOp(v, -1); + assert( pPrev!=0 ); + assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed ); + pPrev->opcode = OP_TypeCheck; + sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, pPrev->p3); + }else{ + /* Insert an isolated OP_Typecheck */ + sqlite3VdbeAddOp2(v, OP_TypeCheck, iReg, pTab->nNVCol); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + } + return; + } + zColAff = pTab->zColAff; if( zColAff==0 ){ sqlite3 *db = sqlite3VdbeDb(v); zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1); @@ -157,6 +197,8 @@ void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ if( iReg ){ sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i); }else{ + assert( sqlite3VdbeGetOp(v, -1)->opcode==OP_MakeRecord + || sqlite3VdbeDb(v)->mallocFailed ); sqlite3VdbeChangeP4(v, -1, zColAff, i); } } @@ -240,24 +282,30 @@ void sqlite3ComputeGeneratedColumns( ** that appropriate affinity has been applied to the regular columns */ sqlite3TableAffinity(pParse->pVdbe, pTab, iRegStore); - if( (pTab->tabFlags & TF_HasStored)!=0 - && (pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1))->opcode==OP_Affinity - ){ - /* Change the OP_Affinity argument to '@' (NONE) for all stored - ** columns. '@' is the no-op affinity and those columns have not - ** yet been computed. */ - int ii, jj; - char *zP4 = pOp->p4.z; - assert( zP4!=0 ); - assert( pOp->p4type==P4_DYNAMIC ); - for(ii=jj=0; zP4[jj]; ii++){ - if( pTab->aCol[ii].colFlags & COLFLAG_VIRTUAL ){ - continue; - } - if( pTab->aCol[ii].colFlags & COLFLAG_STORED ){ - zP4[jj] = SQLITE_AFF_NONE; + if( (pTab->tabFlags & TF_HasStored)!=0 ){ + pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1); + if( pOp->opcode==OP_Affinity ){ + /* Change the OP_Affinity argument to '@' (NONE) for all stored + ** columns. '@' is the no-op affinity and those columns have not + ** yet been computed. */ + int ii, jj; + char *zP4 = pOp->p4.z; + assert( zP4!=0 ); + assert( pOp->p4type==P4_DYNAMIC ); + for(ii=jj=0; zP4[jj]; ii++){ + if( pTab->aCol[ii].colFlags & COLFLAG_VIRTUAL ){ + continue; + } + if( pTab->aCol[ii].colFlags & COLFLAG_STORED ){ + zP4[jj] = SQLITE_AFF_NONE; + } + jj++; } - jj++; + }else if( pOp->opcode==OP_TypeCheck ){ + /* If an OP_TypeCheck was generated because the table is STRICT, + ** then set the P3 operand to indicate that generated columns should + ** not be checked */ + pOp->p3 = 1; } } @@ -293,7 +341,7 @@ void sqlite3ComputeGeneratedColumns( int x; pCol->colFlags |= COLFLAG_BUSY; w.eCode = 0; - sqlite3WalkExpr(&w, pCol->pDflt); + sqlite3WalkExpr(&w, sqlite3ColumnExpr(pTab, pCol)); pCol->colFlags &= ~COLFLAG_BUSY; if( w.eCode & COLFLAG_NOTAVAIL ){ pRedo = pCol; @@ -302,13 +350,13 @@ void sqlite3ComputeGeneratedColumns( eProgress = 1; assert( pCol->colFlags & COLFLAG_GENERATED ); x = sqlite3TableColumnToStorage(pTab, i) + iRegStore; - sqlite3ExprCodeGeneratedColumn(pParse, pCol, x); + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, x); pCol->colFlags &= ~COLFLAG_NOTAVAIL; } } }while( pRedo && eProgress ); if( pRedo ){ - sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pRedo->zName); + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pRedo->zCnName); } pParse->iSelfTab = 0; } @@ -703,7 +751,7 @@ void sqlite3Insert( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_INSERT, 0, &tmask); - isView = pTab->pSelect!=0; + isView = IsView(pTab); #else # define pTrigger 0 # define tmask 0 @@ -794,7 +842,7 @@ void sqlite3Insert( } for(i=0; inId; i++){ for(j=0; jnCol; j++){ - if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ + if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zCnName)==0 ){ pColumn->a[i].idx = j; if( i!=j ) bIdListInOrder = 0; if( j==pTab->iPKey ){ @@ -804,7 +852,7 @@ void sqlite3Insert( if( pTab->aCol[j].colFlags & (COLFLAG_STORED|COLFLAG_VIRTUAL) ){ sqlite3ErrorMsg(pParse, "cannot INSERT into generated column \"%s\"", - pTab->aCol[j].zName); + pTab->aCol[j].zCnName); goto insert_cleanup; } #endif @@ -989,7 +1037,7 @@ void sqlite3Insert( pTab->zName); goto insert_cleanup; } - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "cannot UPSERT a view"); goto insert_cleanup; } @@ -1088,7 +1136,9 @@ void sqlite3Insert( }else if( pColumn==0 ){ /* Hidden columns that are not explicitly named in the INSERT ** get there default value */ - sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); continue; } } @@ -1097,13 +1147,17 @@ void sqlite3Insert( if( j>=pColumn->nId ){ /* A column not named in the insert column list gets its ** default value */ - sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); continue; } k = j; }else if( nColumn==0 ){ /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */ - sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); continue; }else{ k = i - nHidden; @@ -1618,7 +1672,7 @@ void sqlite3GenerateConstraintChecks( db = pParse->db; v = pParse->pVdbe; assert( v!=0 ); - assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + assert( !IsView(pTab) ); /* This table is not a VIEW */ nCol = pTab->nCol; /* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for @@ -1669,7 +1723,7 @@ void sqlite3GenerateConstraintChecks( } if( onError==OE_Replace ){ if( b2ndPass /* REPLACE becomes ABORT on the 2nd pass */ - || pCol->pDflt==0 /* REPLACE is ABORT if no DEFAULT value */ + || pCol->iDflt==0 /* REPLACE is ABORT if no DEFAULT value */ ){ testcase( pCol->colFlags & COLFLAG_VIRTUAL ); testcase( pCol->colFlags & COLFLAG_STORED ); @@ -1691,7 +1745,8 @@ void sqlite3GenerateConstraintChecks( VdbeCoverage(v); assert( (pCol->colFlags & COLFLAG_GENERATED)==0 ); nSeenReplace++; - sqlite3ExprCodeCopy(pParse, pCol->pDflt, iReg); + sqlite3ExprCodeCopy(pParse, + sqlite3ColumnExpr(pTab, pCol), iReg); sqlite3VdbeJumpHere(v, addr1); break; } @@ -1701,7 +1756,7 @@ void sqlite3GenerateConstraintChecks( case OE_Rollback: case OE_Fail: { char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, - pCol->zName); + pCol->zCnName); sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, iReg); sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); @@ -1954,6 +2009,7 @@ void sqlite3GenerateConstraintChecks( if( onError==OE_Replace /* IPK rule is REPLACE */ && onError!=overrideError /* Rules for other constraints are different */ && pTab->pIndex /* There exist other constraints */ + && !upsertIpkDelay /* IPK check already deferred by UPSERT */ ){ ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1; VdbeComment((v, "defer IPK REPLACE until last")); @@ -2119,7 +2175,7 @@ void sqlite3GenerateConstraintChecks( testcase( sqlite3TableColumnToStorage(pTab, iField)!=iField ); x = sqlite3TableColumnToStorage(pTab, iField) + regNewData + 1; sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i); - VdbeComment((v, "%s", pTab->aCol[iField].zName)); + VdbeComment((v, "%s", pTab->aCol[iField].zCnName)); } } sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); @@ -2170,7 +2226,8 @@ void sqlite3GenerateConstraintChecks( ** ** This is not possible for ENABLE_PREUPDATE_HOOK builds, as the row ** must be explicitly deleted in order to ensure any pre-update hook - ** is invoked. */ + ** is invoked. */ + assert( IsOrdinaryTable(pTab) ); #ifndef SQLITE_ENABLE_PREUPDATE_HOOK if( (ix==0 && pIdx->pNext==0) /* Condition 3 */ && pPk==pIdx /* Condition 2 */ @@ -2178,7 +2235,7 @@ void sqlite3GenerateConstraintChecks( && ( 0==(db->flags&SQLITE_RecTriggers) || /* Condition 4 */ 0==sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0)) && ( 0==(db->flags&SQLITE_ForeignKeys) || /* Condition 5 */ - (0==pTab->pFKey && 0==sqlite3FkReferences(pTab))) + (0==pTab->u.tab.pFKey && 0==sqlite3FkReferences(pTab))) ){ sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; @@ -2213,7 +2270,7 @@ void sqlite3GenerateConstraintChecks( x = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i); VdbeComment((v, "%s.%s", pTab->zName, - pTab->aCol[pPk->aiColumn[i]].zName)); + pTab->aCol[pPk->aiColumn[i]].zCnName)); } } if( isUpdate ){ @@ -2277,7 +2334,8 @@ void sqlite3GenerateConstraintChecks( assert( onError==OE_Replace ); nConflictCk = sqlite3VdbeCurrentAddr(v) - addrConflictCk; - assert( nConflictCk>0 ); + assert( nConflictCk>0 || db->mallocFailed ); + testcase( nConflictCk<=0 ); testcase( nConflictCk>1 ); if( regTrigCnt ){ sqlite3MultiWrite(pParse); @@ -2360,6 +2418,7 @@ void sqlite3GenerateConstraintChecks( if( ipkTop ){ sqlite3VdbeGoto(v, ipkTop); VdbeComment((v, "Do IPK REPLACE")); + assert( ipkBottom>0 ); sqlite3VdbeJumpHere(v, ipkBottom); } @@ -2412,7 +2471,7 @@ void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){ if( pTab->pSchema->file_format<2 ) return; for(i=pTab->nCol-1; i>0; i--){ - if( pTab->aCol[i].pDflt!=0 ) break; + if( pTab->aCol[i].iDflt!=0 ) break; if( pTab->aCol[i].colFlags & COLFLAG_PRIMKEY ) break; } sqlite3VdbeChangeP5(v, i+1); @@ -2477,7 +2536,7 @@ void sqlite3CompleteInsertion( v = pParse->pVdbe; assert( v!=0 ); - assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + assert( !IsView(pTab) ); /* This table is not a VIEW */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ /* All REPLACE indexes are at the end of the list */ assert( pIdx->onError!=OE_Replace @@ -2490,7 +2549,6 @@ void sqlite3CompleteInsertion( } pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - assert( pParse->nested==0 ); pik_flags |= OPFLAG_NCHANGE; pik_flags |= (update_flags & OPFLAG_SAVEPOSITION); if( update_flags==0 ){ @@ -2563,8 +2621,9 @@ int sqlite3OpenTableAndIndices( assert( op==OP_OpenWrite || p5==0 ); if( IsVirtual(pTab) ){ /* This routine is a no-op for virtual tables. Leave the output - ** variables *piDataCur and *piIdxCur uninitialized so that valgrind - ** can detect if they are used by mistake in the caller. */ + ** variables *piDataCur and *piIdxCur set to illegal cursor numbers + ** for improved error detection. */ + *piDataCur = *piIdxCur = -999; return 0; } iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); @@ -2779,13 +2838,8 @@ static int xferOptimization( if( HasRowid(pDest)!=HasRowid(pSrc) ){ return 0; /* source and destination must both be WITHOUT ROWID or not */ } -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pSrc) ){ - return 0; /* tab2 must not be a virtual table */ - } -#endif - if( pSrc->pSelect ){ - return 0; /* tab2 may not be a view */ + if( !IsOrdinaryTable(pSrc) ){ + return 0; /* tab2 may not be a view or virtual table */ } if( pDest->nCol!=pSrc->nCol ){ return 0; /* Number of columns must be the same in tab1 and tab2 */ @@ -2793,6 +2847,9 @@ static int xferOptimization( if( pDest->iPKey!=pSrc->iPKey ){ return 0; /* Both tables must have the same INTEGER PRIMARY KEY */ } + if( (pDest->tabFlags & TF_Strict)!=0 && (pSrc->tabFlags & TF_Strict)==0 ){ + return 0; /* Cannot feed from a non-strict into a strict table */ + } for(i=0; inCol; i++){ Column *pDestCol = &pDest->aCol[i]; Column *pSrcCol = &pSrc->aCol[i]; @@ -2829,7 +2886,9 @@ static int xferOptimization( ** This requirement could be relaxed for VIRTUAL columns, I suppose. */ if( (pDestCol->colFlags & COLFLAG_GENERATED)!=0 ){ - if( sqlite3ExprCompare(0, pSrcCol->pDflt, pDestCol->pDflt, -1)!=0 ){ + if( sqlite3ExprCompare(0, + sqlite3ColumnExpr(pSrc, pSrcCol), + sqlite3ColumnExpr(pDest, pDestCol), -1)!=0 ){ testcase( pDestCol->colFlags & COLFLAG_VIRTUAL ); testcase( pDestCol->colFlags & COLFLAG_STORED ); return 0; /* Different generator expressions */ @@ -2839,7 +2898,8 @@ static int xferOptimization( if( pDestCol->affinity!=pSrcCol->affinity ){ return 0; /* Affinity must be the same on all columns */ } - if( sqlite3_stricmp(pDestCol->zColl, pSrcCol->zColl)!=0 ){ + if( sqlite3_stricmp(sqlite3ColumnColl(pDestCol), + sqlite3ColumnColl(pSrcCol))!=0 ){ return 0; /* Collating sequence must be the same on all columns */ } if( pDestCol->notNull && !pSrcCol->notNull ){ @@ -2847,11 +2907,15 @@ static int xferOptimization( } /* Default values for second and subsequent columns need to match. */ if( (pDestCol->colFlags & COLFLAG_GENERATED)==0 && i>0 ){ - assert( pDestCol->pDflt==0 || pDestCol->pDflt->op==TK_SPAN ); - assert( pSrcCol->pDflt==0 || pSrcCol->pDflt->op==TK_SPAN ); - if( (pDestCol->pDflt==0)!=(pSrcCol->pDflt==0) - || (pDestCol->pDflt && strcmp(pDestCol->pDflt->u.zToken, - pSrcCol->pDflt->u.zToken)!=0) + Expr *pDestExpr = sqlite3ColumnExpr(pDest, pDestCol); + Expr *pSrcExpr = sqlite3ColumnExpr(pSrc, pSrcCol); + assert( pDestExpr==0 || pDestExpr->op==TK_SPAN ); + assert( pDestExpr==0 || !ExprHasProperty(pDestExpr, EP_IntValue) ); + assert( pSrcExpr==0 || pSrcExpr->op==TK_SPAN ); + assert( pSrcExpr==0 || !ExprHasProperty(pSrcExpr, EP_IntValue) ); + if( (pDestExpr==0)!=(pSrcExpr==0) + || (pDestExpr!=0 && strcmp(pDestExpr->u.zToken, + pSrcExpr->u.zToken)!=0) ){ return 0; /* Default values must be the same for all columns */ } @@ -2888,7 +2952,8 @@ static int xferOptimization( ** the extra complication to make this rule less restrictive is probably ** not worth the effort. Ticket [6284df89debdfa61db8073e062908af0c9b6118e] */ - if( (db->flags & SQLITE_ForeignKeys)!=0 && pDest->pFKey!=0 ){ + assert( IsOrdinaryTable(pDest) ); + if( (db->flags & SQLITE_ForeignKeys)!=0 && pDest->u.tab.pFKey!=0 ){ return 0; } #endif diff --git a/src/loadext.c b/src/loadext.c index aeea837c9..4edefec0c 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -480,6 +480,11 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_database_file_object, /* Version 3.34.0 and later */ sqlite3_txn_state, + /* Version 3.36.1 and later */ + sqlite3_changes64, + sqlite3_total_changes64, + /* Version 3.37.0 and later */ + sqlite3_autovacuum_pages, }; /* True if x is the directory separator character diff --git a/src/main.c b/src/main.c index a04a6bc0f..804719f17 100644 --- a/src/main.c +++ b/src/main.c @@ -1086,7 +1086,7 @@ void sqlite3_set_last_insert_rowid(sqlite3 *db, sqlite3_int64 iRowid){ /* ** Return the number of changes in the most recent call to sqlite3_exec(). */ -int sqlite3_changes(sqlite3 *db){ +sqlite3_int64 sqlite3_changes64(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; @@ -1095,11 +1095,14 @@ int sqlite3_changes(sqlite3 *db){ #endif return db->nChange; } +int sqlite3_changes(sqlite3 *db){ + return (int)sqlite3_changes64(db); +} /* ** Return the number of changes since the database handle was opened. */ -int sqlite3_total_changes(sqlite3 *db){ +sqlite3_int64 sqlite3_total_changes64(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; @@ -1108,6 +1111,9 @@ int sqlite3_total_changes(sqlite3 *db){ #endif return db->nTotalChange; } +int sqlite3_total_changes(sqlite3 *db){ + return (int)sqlite3_total_changes64(db); +} /* ** Close all open savepoints. This function only manipulates fields of the @@ -1132,7 +1138,9 @@ void sqlite3CloseSavepoints(sqlite3 *db){ ** with SQLITE_ANY as the encoding. */ static void functionDestroy(sqlite3 *db, FuncDef *p){ - FuncDestructor *pDestructor = p->u.pDestructor; + FuncDestructor *pDestructor; + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); + pDestructor = p->u.pDestructor; if( pDestructor ){ pDestructor->nRef--; if( pDestructor->nRef==0 ){ @@ -1236,7 +1244,7 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ /* Convert the connection into a zombie and then close it. */ - db->magic = SQLITE_MAGIC_ZOMBIE; + db->eOpenState = SQLITE_STATE_ZOMBIE; sqlite3LeaveMutexAndCloseZombie(db); return SQLITE_OK; } @@ -1300,7 +1308,7 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ ** or if the connection has not yet been closed by sqlite3_close_v2(), ** then just leave the mutex and return. */ - if( db->magic!=SQLITE_MAGIC_ZOMBIE || connectionIsBusy(db) ){ + if( db->eOpenState!=SQLITE_STATE_ZOMBIE || connectionIsBusy(db) ){ sqlite3_mutex_leave(db->mutex); return; } @@ -1386,7 +1394,7 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ sqlite3_free(db->auth.zAuthPW); #endif - db->magic = SQLITE_MAGIC_ERROR; + db->eOpenState = SQLITE_STATE_ERROR; /* The temp-database schema is allocated differently from the other schema ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). @@ -1395,8 +1403,11 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ ** structure? */ sqlite3DbFree(db, db->aDb[1].pSchema); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } sqlite3_mutex_leave(db->mutex); - db->magic = SQLITE_MAGIC_CLOSED; + db->eOpenState = SQLITE_STATE_CLOSED; sqlite3_mutex_free(db->mutex); assert( sqlite3LookasideUsed(db,0)==0 ); if( db->lookaside.bMalloced ){ @@ -1449,7 +1460,7 @@ void sqlite3RollbackAll(sqlite3 *db, int tripCode){ /* Any deferred constraint violations have now been resolved. */ db->nDeferredCons = 0; db->nDeferredImmCons = 0; - db->flags &= ~(u64)SQLITE_DeferFKs; + db->flags &= ~(u64)(SQLITE_DeferFKs|SQLITE_CorruptRdOnly); /* If one has been configured, invoke the rollback-hook callback */ if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){ @@ -1784,7 +1795,7 @@ int sqlite3_busy_timeout(sqlite3 *db, int ms){ */ void sqlite3_interrupt(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) && (db==0 || db->magic!=SQLITE_MAGIC_ZOMBIE) ){ + if( !sqlite3SafetyCheckOk(db) && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) ){ (void)SQLITE_MISUSE_BKPT; return; } @@ -1813,7 +1824,6 @@ int sqlite3CreateFunc( FuncDestructor *pDestructor ){ FuncDef *p; - int nName; int extraFlags; assert( sqlite3_mutex_held(db->mutex) ); @@ -1823,7 +1833,7 @@ int sqlite3CreateFunc( || ((xFinal==0)!=(xStep==0)) /* Both or neither of xFinal and xStep */ || ((xValue==0)!=(xInverse==0)) /* Both or neither of xValue, xInverse */ || (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG) - || (255<(nName = sqlite3Strlen30( zFunctionName))) + || (255nRef==0 ){ - assert( rc!=SQLITE_OK ); + assert( rc!=SQLITE_OK || (xStep==0 && xFinal==0) ); xDestroy(p); sqlite3_free(pArg); } @@ -2286,6 +2307,34 @@ void *sqlite3_preupdate_hook( } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +/* +** Register a function to be invoked prior to each autovacuum that +** determines the number of pages to vacuum. +*/ +int sqlite3_autovacuum_pages( + sqlite3 *db, /* Attach the hook to this database */ + unsigned int (*xCallback)(void*,const char*,u32,u32,u32), + void *pArg, /* Argument to the function */ + void (*xDestructor)(void*) /* Destructor for pArg */ +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + if( xDestructor ) xDestructor(pArg); + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } + db->xAutovacPages = xCallback; + db->pAutovacPagesArg = pArg; + db->xAutovacDestr = xDestructor; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + + #ifndef SQLITE_OMIT_WAL /* ** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint(). @@ -2808,6 +2857,8 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ if( newLimit>=0 ){ /* IMP: R-52476-28732 */ if( newLimit>aHardLimit[limitId] ){ newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ + }else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){ + newLimit = 1; } db->aLimit[limitId] = newLimit; } @@ -3079,7 +3130,7 @@ int sqlite3ParseUri( */ static const char *uriParameter(const char *zFilename, const char *zParam){ zFilename += sqlite3Strlen30(zFilename) + 1; - while( zFilename[0] ){ + while( ALWAYS(zFilename!=0) && zFilename[0] ){ int x = strcmp(zFilename, zParam); zFilename += sqlite3Strlen30(zFilename) + 1; if( x==0 ) return zFilename; @@ -3139,8 +3190,8 @@ static int openDatabase( ** dealt with in the previous code block. Besides these, the only ** valid input flags for sqlite3_open_v2() are SQLITE_OPEN_READONLY, ** SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, SQLITE_OPEN_SHAREDCACHE, - ** SQLITE_OPEN_PRIVATECACHE, and some reserved bits. Silently mask - ** off all other flags. + ** SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_EXRESCODE, and some reserved + ** bits. Silently mask off all other flags. */ flags &= ~( SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_EXCLUSIVE | @@ -3175,9 +3226,9 @@ static int openDatabase( } } sqlite3_mutex_enter(db->mutex); - db->errMask = 0xff; + db->errMask = (flags & SQLITE_OPEN_EXRESCODE)!=0 ? 0xffffffff : 0xff; db->nDb = 2; - db->magic = SQLITE_MAGIC_BUSY; + db->eOpenState = SQLITE_STATE_BUSY; db->aDb = db->aDbStatic; db->lookaside.bDisable = 1; db->lookaside.sz = 0; @@ -3189,7 +3240,15 @@ static int openDatabase( db->nextAutovac = -1; db->szMmap = sqlite3GlobalConfig.szMmap; db->nextPagesize = 0; + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ +#ifdef SQLITE_ENABLE_SORTER_MMAP + /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map + ** the temporary files used to do external sorts (see code in vdbesort.c) + ** is disabled. It can still be used either by defining + ** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the + ** SQLITE_TESTCTRL_SORTER_MMAP test-control at runtime. */ db->nMaxSorterMmap = 0x7FFFFFFF; +#endif db->flags |= SQLITE_ShortColNames | SQLITE_EnableTrigger | SQLITE_EnableView @@ -3337,7 +3396,7 @@ static int openDatabase( db->aDb[1].zDbSName = "temp"; db->aDb[1].safety_level = PAGER_SYNCHRONOUS_OFF; - db->magic = SQLITE_MAGIC_OPEN; + db->eOpenState = SQLITE_STATE_OPEN; if( db->mallocFailed ){ goto opendb_out; } @@ -3399,12 +3458,12 @@ static int openDatabase( sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); - assert( db!=0 || rc==SQLITE_NOMEM ); - if( rc==SQLITE_NOMEM ){ + assert( db!=0 || (rc&0xff)==SQLITE_NOMEM ); + if( (rc&0xff)==SQLITE_NOMEM ){ sqlite3_close(db); db = 0; }else if( rc!=SQLITE_OK ){ - db->magic = SQLITE_MAGIC_SICK; + db->eOpenState = SQLITE_STATE_SICK; } *ppDb = db; #ifdef SQLITE_ENABLE_SQLLOG @@ -3415,7 +3474,7 @@ static int openDatabase( } #endif sqlite3_free_filename(zOpen); - return rc & 0xff; + return rc; } @@ -3715,7 +3774,7 @@ int sqlite3_table_column_metadata( /* Locate the table in question */ pTab = sqlite3FindTable(db, zTableName, zDbName); - if( !pTab || pTab->pSelect ){ + if( !pTab || IsView(pTab) ){ pTab = 0; goto error_out; } @@ -3726,7 +3785,7 @@ int sqlite3_table_column_metadata( }else{ for(iCol=0; iColnCol; iCol++){ pCol = &pTab->aCol[iCol]; - if( 0==sqlite3StrICmp(pCol->zName, zColumnName) ){ + if( 0==sqlite3StrICmp(pCol->zCnName, zColumnName) ){ break; } } @@ -3753,7 +3812,7 @@ int sqlite3_table_column_metadata( */ if( pCol ){ zDataType = sqlite3ColumnType(pCol,0); - zCollSeq = pCol->zColl; + zCollSeq = sqlite3ColumnColl(pCol); notnull = pCol->notNull!=0; primarykey = (pCol->colFlags & COLFLAG_PRIMKEY)!=0; autoinc = pTab->iPKey==iCol && (pTab->tabFlags & TF_Autoincrement)!=0; @@ -4203,12 +4262,16 @@ int sqlite3_test_control(int op, ...){ */ case SQLITE_TESTCTRL_IMPOSTER: { sqlite3 *db = va_arg(ap, sqlite3*); + int iDb; sqlite3_mutex_enter(db->mutex); - db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); - db->init.busy = db->init.imposterTable = va_arg(ap,int); - db->init.newTnum = va_arg(ap,int); - if( db->init.busy==0 && db->init.newTnum>0 ){ - sqlite3ResetAllSchemasOfConnection(db); + iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); + if( iDb>=0 ){ + db->init.iDb = iDb; + db->init.busy = db->init.imposterTable = va_arg(ap,int); + db->init.newTnum = va_arg(ap,int); + if( db->init.busy==0 && db->init.newTnum>0 ){ + sqlite3ResetAllSchemasOfConnection(db); + } } sqlite3_mutex_leave(db->mutex); break; @@ -4420,7 +4483,7 @@ const char *sqlite3_uri_key(const char *zFilename, int N){ if( zFilename==0 || N<0 ) return 0; zFilename = databaseName(zFilename); zFilename += sqlite3Strlen30(zFilename) + 1; - while( zFilename[0] && (N--)>0 ){ + while( ALWAYS(zFilename) && zFilename[0] && (N--)>0 ){ zFilename += sqlite3Strlen30(zFilename) + 1; zFilename += sqlite3Strlen30(zFilename) + 1; } @@ -4463,12 +4526,14 @@ sqlite3_int64 sqlite3_uri_int64( ** corruption. */ const char *sqlite3_filename_database(const char *zFilename){ + if( zFilename==0 ) return 0; return databaseName(zFilename); } const char *sqlite3_filename_journal(const char *zFilename){ + if( zFilename==0 ) return 0; zFilename = databaseName(zFilename); zFilename += sqlite3Strlen30(zFilename) + 1; - while( zFilename[0] ){ + while( ALWAYS(zFilename) && zFilename[0] ){ zFilename += sqlite3Strlen30(zFilename) + 1; zFilename += sqlite3Strlen30(zFilename) + 1; } @@ -4479,7 +4544,7 @@ const char *sqlite3_filename_wal(const char *zFilename){ return 0; #else zFilename = sqlite3_filename_journal(zFilename); - zFilename += sqlite3Strlen30(zFilename) + 1; + if( zFilename ) zFilename += sqlite3Strlen30(zFilename) + 1; return zFilename; #endif } diff --git a/src/malloc.c b/src/malloc.c index b8a88f128..932cecc21 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -316,7 +316,7 @@ void *sqlite3_malloc64(sqlite3_uint64 n){ ** TRUE if p is a lookaside memory allocation from db */ #ifndef SQLITE_OMIT_LOOKASIDE -static int isLookaside(sqlite3 *db, void *p){ +static int isLookaside(sqlite3 *db, const void *p){ return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd); } #else @@ -327,18 +327,18 @@ static int isLookaside(sqlite3 *db, void *p){ ** Return the size of a memory allocation previously obtained from ** sqlite3Malloc() or sqlite3_malloc(). */ -int sqlite3MallocSize(void *p){ +int sqlite3MallocSize(const void *p){ assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); - return sqlite3GlobalConfig.m.xSize(p); + return sqlite3GlobalConfig.m.xSize((void*)p); } -static int lookasideMallocSize(sqlite3 *db, void *p){ +static int lookasideMallocSize(sqlite3 *db, const void *p){ #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE return plookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL; #else return db->lookaside.szTrue; #endif } -int sqlite3DbMallocSize(sqlite3 *db, void *p){ +int sqlite3DbMallocSize(sqlite3 *db, const void *p){ assert( p!=0 ); #ifdef SQLITE_DEBUG if( db==0 || !isLookaside(db,p) ){ @@ -365,7 +365,7 @@ int sqlite3DbMallocSize(sqlite3 *db, void *p){ } } } - return sqlite3GlobalConfig.m.xSize(p); + return sqlite3GlobalConfig.m.xSize((void*)p); } sqlite3_uint64 sqlite3_msize(void *p){ assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); diff --git a/src/mem2.c b/src/mem2.c index ac031508c..04d6298db 100644 --- a/src/mem2.c +++ b/src/mem2.c @@ -149,7 +149,7 @@ static void adjustStats(int iSize, int increment){ ** This routine checks the guards at either end of the allocation and ** if they are incorrect it asserts. */ -static struct MemBlockHdr *sqlite3MemsysGetHeader(void *pAllocation){ +static struct MemBlockHdr *sqlite3MemsysGetHeader(const void *pAllocation){ struct MemBlockHdr *p; int *pInt; u8 *pU8; @@ -396,7 +396,7 @@ void sqlite3MemdebugSetType(void *p, u8 eType){ ** ** assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); */ -int sqlite3MemdebugHasType(void *p, u8 eType){ +int sqlite3MemdebugHasType(const void *p, u8 eType){ int rc = 1; if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ struct MemBlockHdr *pHdr; @@ -418,7 +418,7 @@ int sqlite3MemdebugHasType(void *p, u8 eType){ ** ** assert( sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); */ -int sqlite3MemdebugNoType(void *p, u8 eType){ +int sqlite3MemdebugNoType(const void *p, u8 eType){ int rc = 1; if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ struct MemBlockHdr *pHdr; diff --git a/src/memdb.c b/src/memdb.c index 4e5751f93..31b2324b9 100644 --- a/src/memdb.c +++ b/src/memdb.c @@ -272,7 +272,7 @@ static int memdbRead( */ static int memdbEnlarge(MemStore *p, sqlite3_int64 newSz){ unsigned char *pNew; - if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || p->nMmap>0 ){ + if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || NEVER(p->nMmap>0) ){ return SQLITE_FULL; } if( newSz>p->szMax ){ @@ -331,8 +331,9 @@ static int memdbTruncate(sqlite3_file *pFile, sqlite_int64 size){ MemStore *p = ((MemFile*)pFile)->pStore; int rc = SQLITE_OK; memdbEnter(p); - if( NEVER(size>p->sz) ){ - rc = SQLITE_FULL; + if( size>p->sz ){ + /* This can only happen with a corrupt wal mode db */ + rc = SQLITE_CORRUPT; }else{ p->sz = size; } @@ -471,7 +472,7 @@ static int memdbFetch( ){ MemStore *p = ((MemFile*)pFile)->pStore; memdbEnter(p); - if( iOfst+iAmt>p->sz ){ + if( iOfst+iAmt>p->sz || (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)!=0 ){ *pp = 0; }else{ p->nMmap++; @@ -505,10 +506,9 @@ static int memdbOpen( MemFile *pFile = (MemFile*)pFd; MemStore *p = 0; int szName; - if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ - return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFd, flags, pOutFlags); - } - memset(pFile, 0, sizeof(*p)); + UNUSED_PARAMETER(pVfs); + + memset(pFile, 0, sizeof(*pFile)); szName = sqlite3Strlen30(zName); if( szName>1 && zName[0]=='/' ){ int i; @@ -567,8 +567,9 @@ static int memdbOpen( p->szMax = sqlite3GlobalConfig.mxMemdbSize; } pFile->pStore = p; - assert( pOutFlags!=0 ); /* True because flags==SQLITE_OPEN_MAIN_DB */ - *pOutFlags = flags | SQLITE_OPEN_MEMORY; + if( pOutFlags!=0 ){ + *pOutFlags = flags | SQLITE_OPEN_MEMORY; + } pFd->pMethods = &memdb_io_methods; memdbLeave(p); return SQLITE_OK; @@ -809,10 +810,11 @@ int sqlite3_deserialize( sqlite3_mutex_enter(db->mutex); if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; iDb = sqlite3FindDbName(db, zSchema); - if( iDb<0 ){ + testcase( iDb==1 ); + if( iDb<2 && iDb!=0 ){ rc = SQLITE_ERROR; goto end_deserialize; - } + } zSql = sqlite3_mprintf("ATTACH x AS %Q", zSchema); if( zSql==0 ){ rc = SQLITE_NOMEM; diff --git a/src/memjournal.c b/src/memjournal.c index 598d5cc02..63ef2ad7b 100644 --- a/src/memjournal.c +++ b/src/memjournal.c @@ -265,7 +265,7 @@ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ p->pFirst = 0; }else{ i64 iOff = p->nChunkSize; - for(pIter=p->pFirst; ALWAYS(pIter) && iOff<=size; pIter=pIter->pNext){ + for(pIter=p->pFirst; ALWAYS(pIter) && iOffpNext){ iOff += p->nChunkSize; } if( ALWAYS(pIter) ){ diff --git a/src/os.c b/src/os.c index f1798ff96..0c3c9d898 100644 --- a/src/os.c +++ b/src/os.c @@ -161,6 +161,7 @@ int sqlite3OsSectorSize(sqlite3_file *id){ return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE); } int sqlite3OsDeviceCharacteristics(sqlite3_file *id){ + if( NEVER(id->pMethods==0) ) return 0; return id->pMethods->xDeviceCharacteristics(id); } #ifndef SQLITE_OMIT_WAL @@ -315,12 +316,15 @@ int sqlite3OsOpenMalloc( rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags); if( rc!=SQLITE_OK ){ sqlite3_free(pFile); + *ppFile = 0; }else{ *ppFile = pFile; } }else{ + *ppFile = 0; rc = SQLITE_NOMEM_BKPT; } + assert( *ppFile!=0 || rc!=SQLITE_OK ); return rc; } void sqlite3OsCloseFree(sqlite3_file *pFile){ diff --git a/src/os_unix.c b/src/os_unix.c index d45fdabab..cd619f5c0 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -3951,7 +3951,9 @@ static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){ /* Forward declaration */ static int unixGetTempname(int nBuf, char *zBuf); -static int unixFcntlExternalReader(unixFile*, int*); +#ifndef SQLITE_OMIT_WAL + static int unixFcntlExternalReader(unixFile*, int*); +#endif /* ** Information and control of an open file handle. @@ -4070,7 +4072,12 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ #endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */ case SQLITE_FCNTL_EXTERNAL_READER: { +#ifndef SQLITE_OMIT_WAL return unixFcntlExternalReader((unixFile*)id, (int*)pArg); +#else + *(int*)pArg = 0; + return SQLITE_OK; +#endif } } return SQLITE_NOTFOUND; @@ -5791,25 +5798,35 @@ static int fillInUnixFile( return rc; } +/* +** Directories to consider for temp files. +*/ +static const char *azTempDirs[] = { + 0, + 0, + "/var/tmp", + "/usr/tmp", + "/tmp", + "." +}; + +/* +** Initialize first two members of azTempDirs[] array. +*/ +static void unixTempFileInit(void){ + azTempDirs[0] = getenv("SQLITE_TMPDIR"); + azTempDirs[1] = getenv("TMPDIR"); +} + /* ** Return the name of a directory in which to put temporary files. ** If no suitable temporary file directory can be found, return NULL. */ static const char *unixTempFileDir(void){ - static const char *azDirs[] = { - 0, - 0, - "/var/tmp", - "/usr/tmp", - "/tmp", - "." - }; unsigned int i = 0; struct stat buf; const char *zDir = sqlite3_temp_directory; - if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR"); - if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR"); while(1){ if( zDir!=0 && osStat(zDir, &buf)==0 @@ -5818,8 +5835,8 @@ static const char *unixTempFileDir(void){ ){ return zDir; } - if( i>=sizeof(azDirs)/sizeof(azDirs[0]) ) break; - zDir = azDirs[i++]; + if( i>=sizeof(azTempDirs)/sizeof(azTempDirs[0]) ) break; + zDir = azTempDirs[i++]; } return 0; } @@ -6125,6 +6142,11 @@ static int unixOpen( } memset(p, 0, sizeof(unixFile)); +#ifdef SQLITE_ASSERT_NO_FILES + /* Applications that never read or write a persistent disk files */ + assert( zName==0 ); +#endif + if( eType==SQLITE_OPEN_MAIN_DB ){ UnixUnusedFd *pUnused; pUnused = findReusableFd(zName, flags); @@ -8086,6 +8108,9 @@ int sqlite3_os_init(void){ assert( UNIX_SHM_DMS==128 ); /* Byte offset of the deadman-switch */ #endif + /* Initialize temp file dir array. */ + unixTempFileInit(); + return SQLITE_OK; } diff --git a/src/pager.c b/src/pager.c index 12a78cde8..a2fc10f23 100644 --- a/src/pager.c +++ b/src/pager.c @@ -630,6 +630,7 @@ struct Pager { u8 noLock; /* Do not lock (except in WAL mode) */ u8 readOnly; /* True for a read-only database */ u8 memDb; /* True to inhibit all file I/O */ + u8 memVfs; /* VFS-implemented memory database */ /************************************************************************** ** The following block contains those class members that change during @@ -679,8 +680,8 @@ struct Pager { i16 nReserve; /* Number of unused bytes at end of each page */ u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */ u32 sectorSize; /* Assumed sector size during rollback */ - int pageSize; /* Number of bytes in a page */ Pgno mxPgno; /* Maximum allowed size of the database */ + i64 pageSize; /* Number of bytes in a page */ i64 journalSizeLimit; /* Size limit for persistent journal files */ char *zFilename; /* Name of the database file */ char *zJournal; /* Name of the journal file */ @@ -3024,6 +3025,7 @@ static int readDbPage(PgHdr *pPg){ */ static void pager_write_changecounter(PgHdr *pPg){ u32 change_counter; + if( NEVER(pPg==0) ) return; /* Increment the value just read and write it back to byte 24. */ change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1; @@ -3915,7 +3917,7 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){ #if defined(SQLITE_DEBUG) static void assertTruncateConstraintCb(PgHdr *pPg){ assert( pPg->flags&PGHDR_DIRTY ); - assert( !subjRequiresPage(pPg) || pPg->pgno<=pPg->pPager->dbSize ); + assert( pPg->pgno<=pPg->pPager->dbSize || !subjRequiresPage(pPg) ); } static void assertTruncateConstraint(Pager *pPager){ sqlite3PcacheIterateDirty(pPager->pPCache, assertTruncateConstraintCb); @@ -4858,6 +4860,7 @@ int sqlite3PagerOpen( pPager->zWal = 0; } #endif + (void)pPtr; /* Suppress warning about unused pPtr value */ if( nPathname ) sqlite3DbFree(0, zPathname); pPager->pVfs = pVfs; @@ -4870,7 +4873,7 @@ int sqlite3PagerOpen( rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); assert( !memDb ); #ifndef SQLITE_OMIT_DESERIALIZE - memJM = (fout&SQLITE_OPEN_MEMORY)!=0; + pPager->memVfs = memJM = (fout&SQLITE_OPEN_MEMORY)!=0; #endif readOnly = (fout&SQLITE_OPEN_READONLY)!=0; @@ -5255,7 +5258,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** may mean that the pager was in the error-state when this ** function was called and the journal file does not exist. */ - if( !isOpen(pPager->jfd) ){ + if( !isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){ sqlite3_vfs * const pVfs = pPager->pVfs; int bExists; /* True if journal file exists */ rc = sqlite3OsAccess( @@ -6737,8 +6740,8 @@ int sqlite3PagerRefcount(Pager *pPager){ ** used by the pager and its associated cache. */ int sqlite3PagerMemUsed(Pager *pPager){ - int perPageSize = pPager->pageSize + pPager->nExtra + sizeof(PgHdr) - + 5*sizeof(void*); + int perPageSize = pPager->pageSize + pPager->nExtra + + (int)(sizeof(PgHdr) + 5*sizeof(void*)); return perPageSize*sqlite3PcachePagecount(pPager->pPCache) + sqlite3MallocSize(pPager) + pPager->pageSize; @@ -6807,7 +6810,7 @@ void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){ ** Return true if this is an in-memory or temp-file backed pager. */ int sqlite3PagerIsMemdb(Pager *pPager){ - return pPager->tempFile; + return pPager->tempFile || pPager->memVfs; } /* @@ -6932,14 +6935,14 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ } pPager->nSavepoint = nNew; - /* If this is a release of the outermost savepoint, truncate - ** the sub-journal to zero bytes in size. */ + /* Truncate the sub-journal so that it only includes the parts + ** that are still in use. */ if( op==SAVEPOINT_RELEASE ){ PagerSavepoint *pRel = &pPager->aSavepoint[nNew]; if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){ /* Only truncate if it is an in-memory sub-journal. */ if( sqlite3JournalIsInMemory(pPager->sjfd) ){ - i64 sz = (pPager->pageSize+4)*pRel->iSubRec; + i64 sz = (pPager->pageSize+4)*(i64)pRel->iSubRec; rc = sqlite3OsTruncate(pPager->sjfd, sz); assert( rc==SQLITE_OK ); } @@ -7127,7 +7130,7 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ pPgOld = sqlite3PagerLookup(pPager, pgno); assert( !pPgOld || pPgOld->nRef==1 || CORRUPT_DB ); if( pPgOld ){ - if( pPgOld->nRef>1 ){ + if( NEVER(pPgOld->nRef>1) ){ sqlite3PagerUnrefNotNull(pPgOld); return SQLITE_CORRUPT_BKPT; } diff --git a/src/parse.y b/src/parse.y index 359b02bc4..97a26c28e 100644 --- a/src/parse.y +++ b/src/parse.y @@ -196,16 +196,19 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;} temp(A) ::= TEMP. {A = pParse->db->init.busy==0;} %endif SQLITE_OMIT_TEMPDB temp(A) ::= . {A = 0;} -create_table_args ::= LP columnlist conslist_opt(X) RP(E) table_options(F). { +create_table_args ::= LP columnlist conslist_opt(X) RP(E) table_option_set(F). { sqlite3EndTable(pParse,&X,&E,F,0); } create_table_args ::= AS select(S). { sqlite3EndTable(pParse,0,0,0,S); sqlite3SelectDelete(pParse->db, S); } -%type table_options {int} -table_options(A) ::= . {A = 0;} -table_options(A) ::= WITHOUT nm(X). { +%type table_option_set {u32} +%type table_option {u32} +table_option_set(A) ::= . {A = 0;} +table_option_set(A) ::= table_option(A). +table_option_set(A) ::= table_option_set(X) COMMA table_option(Y). {A = X|Y;} +table_option(A) ::= WITHOUT nm(X). { if( X.n==5 && sqlite3_strnicmp(X.z,"rowid",5)==0 ){ A = TF_WithoutRowid | TF_NoVisibleRowid; }else{ @@ -213,9 +216,17 @@ table_options(A) ::= WITHOUT nm(X). { sqlite3ErrorMsg(pParse, "unknown table option: %.*s", X.n, X.z); } } +table_option(A) ::= nm(X). { + if( X.n==6 && sqlite3_strnicmp(X.z,"strict",6)==0 ){ + A = TF_Strict; + }else{ + A = 0; + sqlite3ErrorMsg(pParse, "unknown table option: %.*s", X.n, X.z); + } +} columnlist ::= columnlist COMMA columnname carglist. columnlist ::= columnname carglist. -columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} +columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,A,Y);} // Declare some tokens early in order to influence their values, to // improve performance and reduce the executable size. The goal here is @@ -1028,9 +1039,9 @@ idlist(A) ::= nm(Y). ExprClearVVAProperties(p); p->iAgg = -1; p->pLeft = p->pRight = 0; - p->x.pList = 0; p->pAggInfo = 0; - p->y.pTab = 0; + memset(&p->x, 0, sizeof(p->x)); + memset(&p->y, 0, sizeof(p->y)); p->op2 = 0; p->iTable = 0; p->iColumn = 0; @@ -1254,20 +1265,28 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { */ sqlite3ExprUnmapAndDelete(pParse, A); A = sqlite3Expr(pParse->db, TK_INTEGER, N ? "1" : "0"); - }else if( Y->nExpr==1 && sqlite3ExprIsConstant(Y->a[0].pExpr) ){ - Expr *pRHS = Y->a[0].pExpr; - Y->a[0].pExpr = 0; - sqlite3ExprListDelete(pParse->db, Y); - pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); - A = sqlite3PExpr(pParse, TK_EQ, A, pRHS); - if( N ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); }else{ - A = sqlite3PExpr(pParse, TK_IN, A, 0); - if( A ){ - A->x.pList = Y; - sqlite3ExprSetHeightAndFlags(pParse, A); - }else{ + Expr *pRHS = Y->a[0].pExpr; + if( Y->nExpr==1 && sqlite3ExprIsConstant(pRHS) && A->op!=TK_VECTOR ){ + Y->a[0].pExpr = 0; sqlite3ExprListDelete(pParse->db, Y); + pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); + A = sqlite3PExpr(pParse, TK_EQ, A, pRHS); + }else{ + A = sqlite3PExpr(pParse, TK_IN, A, 0); + if( A==0 ){ + sqlite3ExprListDelete(pParse->db, Y); + }else if( A->pLeft->op==TK_VECTOR ){ + int nExpr = A->pLeft->x.pList->nExpr; + Select *pSelectRHS = sqlite3ExprListToValues(pParse, nExpr, Y); + if( pSelectRHS ){ + parserDoubleLinkSelect(pParse, pSelectRHS); + sqlite3PExprAddSelect(pParse, A, pSelectRHS); + } + }else{ + A->x.pList = Y; + sqlite3ExprSetHeightAndFlags(pParse, A); + } } if( N ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); } @@ -1619,7 +1638,8 @@ cmd ::= ANALYZE nm(X) dbnm(Y). {sqlite3Analyze(pParse, &X, &Y);} %endif //////////////////////// ALTER TABLE table ... //////////////////////////////// -%ifndef SQLITE_OMIT_ALTERTABLE +%ifndef SQLITE_OMIT_ALTERTABLE +%ifndef SQLITE_OMIT_VIRTUALTABLE cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). { sqlite3AlterRenameTable(pParse,X,&Z); } @@ -1643,7 +1663,8 @@ cmd ::= ALTER TABLE fullname(X) RENAME kwcolumn_opt nm(Y) TO nm(Z). { kwcolumn_opt ::= . kwcolumn_opt ::= COLUMNKW. -%endif SQLITE_OMIT_ALTERTABLE +%endif SQLITE_OMIT_VIRTUALTABLE +%endif SQLITE_OMIT_ALTERTABLE //////////////////////// CREATE VIRTUAL TABLE ... ///////////////////////////// %ifndef SQLITE_OMIT_VIRTUALTABLE diff --git a/src/pcache.c b/src/pcache.c index 36829be41..14d1e7cde 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -243,11 +243,14 @@ static int numberOfCachePages(PCache *p){ ** suggested cache size is set to N. */ return p->szCache; }else{ + i64 n; /* IMPLEMANTATION-OF: R-59858-46238 If the argument N is negative, then the ** number of cache pages is adjusted to be a number of pages that would ** use approximately abs(N*1024) bytes of memory based on the current ** page size. */ - return (int)((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); + n = ((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); + if( n>1000000000 ) n = 1000000000; + return (int)n; } } diff --git a/src/pcache1.c b/src/pcache1.c index 3eae6b63c..a93b14689 100644 --- a/src/pcache1.c +++ b/src/pcache1.c @@ -817,12 +817,18 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ */ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ PCache1 *pCache = (PCache1 *)p; + u32 n; + assert( nMax>=0 ); if( pCache->bPurgeable ){ PGroup *pGroup = pCache->pGroup; pcache1EnterMutex(pGroup); - pGroup->nMaxPage += (nMax - pCache->nMax); + n = (u32)nMax; + if( n > 0x7fff0000 - pGroup->nMaxPage + pCache->nMax ){ + n = 0x7fff0000 - pGroup->nMaxPage + pCache->nMax; + } + pGroup->nMaxPage += (n - pCache->nMax); pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; - pCache->nMax = nMax; + pCache->nMax = n; pCache->n90pct = pCache->nMax*9/10; pcache1EnforceMaxPage(pCache); pcache1LeaveMutex(pGroup); @@ -838,7 +844,7 @@ static void pcache1Shrink(sqlite3_pcache *p){ PCache1 *pCache = (PCache1*)p; if( pCache->bPurgeable ){ PGroup *pGroup = pCache->pGroup; - int savedMaxPage; + unsigned int savedMaxPage; pcache1EnterMutex(pGroup); savedMaxPage = pGroup->nMaxPage; pGroup->nMaxPage = 0; diff --git a/src/pragma.c b/src/pragma.c index 84f29c2fd..ba84af757 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -467,7 +467,11 @@ void sqlite3Pragma( /* Locate the pragma in the lookup table */ pPragma = pragmaLocate(zLeft); - if( pPragma==0 ) goto pragma_out; + if( pPragma==0 ){ + /* IMP: R-43042-22504 No error messages are generated if an + ** unknown pragma is issued. */ + goto pragma_out; + } /* Make sure the database schema is loaded if the pragma requires that */ if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){ @@ -1117,6 +1121,14 @@ void sqlite3Pragma( }else{ db->flags &= ~mask; if( mask==SQLITE_DeferFKs ) db->nDeferredImmCons = 0; + if( (mask & SQLITE_WriteSchema)!=0 + && sqlite3_stricmp(zRight, "reset")==0 + ){ + /* IMP: R-60817-01178 If the argument is "RESET" then schema + ** writing is disabled (as with "PRAGMA writable_schema=OFF") and, + ** in addition, the schema is reloaded. */ + sqlite3ResetAllSchemasOfConnection(db); + } } /* Many of the flag-pragmas modify the code generated by the SQL @@ -1157,6 +1169,7 @@ void sqlite3Pragma( sqlite3ViewGetColumnNames(pParse, pTab); for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ int isHidden = 0; + const Expr *pColExpr; if( pCol->colFlags & COLFLAG_NOINSERT ){ if( pPragma->iArg==0 ){ nHidden++; @@ -1177,13 +1190,16 @@ void sqlite3Pragma( }else{ for(k=1; k<=pTab->nCol && pPk->aiColumn[k-1]!=i; k++){} } - assert( pCol->pDflt==0 || pCol->pDflt->op==TK_SPAN || isHidden>=2 ); + pColExpr = sqlite3ColumnExpr(pTab,pCol); + assert( pColExpr==0 || pColExpr->op==TK_SPAN || isHidden>=2 ); + assert( pColExpr==0 || !ExprHasProperty(pColExpr, EP_IntValue) + || isHidden>=2 ); sqlite3VdbeMultiLoad(v, 1, pPragma->iArg ? "issisii" : "issisi", i-nHidden, - pCol->zName, + pCol->zCnName, sqlite3ColumnType(pCol,""), pCol->notNull ? 1 : 0, - pCol->pDflt && isHidden<2 ? pCol->pDflt->u.zToken : 0, + (isHidden>=2 || pColExpr==0) ? 0 : pColExpr->u.zToken, k, isHidden); } @@ -1191,6 +1207,81 @@ void sqlite3Pragma( } break; + /* + ** PRAGMA table_list + ** + ** Return a single row for each table, virtual table, or view in the + ** entire schema. + ** + ** schema: Name of attached database hold this table + ** name: Name of the table itself + ** type: "table", "view", "virtual", "shadow" + ** ncol: Number of columns + ** wr: True for a WITHOUT ROWID table + ** strict: True for a STRICT table + */ + case PragTyp_TABLE_LIST: { + int ii; + pParse->nMem = 6; + sqlite3CodeVerifyNamedSchema(pParse, zDb); + for(ii=0; iinDb; ii++){ + HashElem *k; + Hash *pHash; + int initNCol; + if( zDb && sqlite3_stricmp(zDb, db->aDb[ii].zDbSName)!=0 ) continue; + + /* Ensure that the Table.nCol field is initialized for all views + ** and virtual tables. Each time we initialize a Table.nCol value + ** for a table, that can potentially disrupt the hash table, so restart + ** the initialization scan. + */ + pHash = &db->aDb[ii].pSchema->tblHash; + initNCol = sqliteHashCount(pHash); + while( initNCol-- ){ + for(k=sqliteHashFirst(pHash); 1; k=sqliteHashNext(k) ){ + Table *pTab; + if( k==0 ){ initNCol = 0; break; } + pTab = sqliteHashData(k); + if( pTab->nCol==0 ){ + char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName); + if( zSql ){ + sqlite3_stmt *pDummy = 0; + (void)sqlite3_prepare(db, zSql, -1, &pDummy, 0); + (void)sqlite3_finalize(pDummy); + sqlite3DbFree(db, zSql); + } + pHash = &db->aDb[ii].pSchema->tblHash; + break; + } + } + } + + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k) ){ + Table *pTab = sqliteHashData(k); + const char *zType; + if( zRight && sqlite3_stricmp(zRight, pTab->zName)!=0 ) continue; + if( IsView(pTab) ){ + zType = "view"; + }else if( IsVirtual(pTab) ){ + zType = "virtual"; + }else if( pTab->tabFlags & TF_Shadow ){ + zType = "shadow"; + }else{ + zType = "table"; + } + sqlite3VdbeMultiLoad(v, 1, "sssiii", + db->aDb[ii].zDbSName, + sqlite3PreferredTableName(pTab->zName), + zType, + pTab->nCol, + (pTab->tabFlags & TF_WithoutRowid)!=0, + (pTab->tabFlags & TF_Strict)!=0 + ); + } + } + } + break; + #ifdef SQLITE_DEBUG case PragTyp_STATS: { Index *pIdx; @@ -1200,7 +1291,7 @@ void sqlite3Pragma( for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); sqlite3VdbeMultiLoad(v, 1, "ssiii", - pTab->zName, + sqlite3PreferredTableName(pTab->zName), 0, pTab->szTabRow, pTab->nRowLogEst, @@ -1250,7 +1341,7 @@ void sqlite3Pragma( for(i=0; iaiColumn[i]; sqlite3VdbeMultiLoad(v, 1, "iisX", i, cnum, - cnum<0 ? 0 : pTab->aCol[cnum].zName); + cnum<0 ? 0 : pTab->aCol[cnum].zCnName); if( pPragma->iArg ){ sqlite3VdbeMultiLoad(v, 4, "isiX", pIdx->aSortOrder[i], @@ -1319,11 +1410,13 @@ void sqlite3Pragma( pParse->nMem = 6; for(i=0; iu.pHash ){ + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); pragmaFunclistLine(v, p, 1, showInternFunc); } } for(j=sqliteHashFirst(&db->aFunc); j; j=sqliteHashNext(j)){ p = (FuncDef*)sqliteHashData(j); + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); pragmaFunclistLine(v, p, 0, showInternFunc); } } @@ -1357,8 +1450,8 @@ void sqlite3Pragma( FKey *pFK; Table *pTab; pTab = sqlite3FindTable(db, zRight, zDb); - if( pTab ){ - pFK = pTab->pFKey; + if( pTab && IsOrdinaryTable(pTab) ){ + pFK = pTab->u.tab.pFKey; if( pFK ){ int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); int i = 0; @@ -1371,7 +1464,7 @@ void sqlite3Pragma( i, j, pFK->zTo, - pTab->aCol[pFK->aCol[j].iFrom].zName, + pTab->aCol[pFK->aCol[j].iFrom].zCnName, pFK->aCol[j].zCol, actionName(pFK->aAction[1]), /* ON UPDATE */ actionName(pFK->aAction[0]), /* ON DELETE */ @@ -1417,7 +1510,7 @@ void sqlite3Pragma( pTab = (Table*)sqliteHashData(k); k = sqliteHashNext(k); } - if( pTab==0 || pTab->pFKey==0 ) continue; + if( pTab==0 || !IsOrdinaryTable(pTab) || pTab->u.tab.pFKey==0 ) continue; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; sqlite3CodeVerifySchema(pParse, iDb); @@ -1425,7 +1518,8 @@ void sqlite3Pragma( if( pTab->nCol+regRow>pParse->nMem ) pParse->nMem = pTab->nCol + regRow; sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead); sqlite3VdbeLoadString(v, regResult, pTab->zName); - for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ pParent = sqlite3FindTable(db, pFK->zTo, zDb); if( pParent==0 ) continue; pIdx = 0; @@ -1447,7 +1541,8 @@ void sqlite3Pragma( if( pFK ) break; if( pParse->nTabnTab = i; addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v); - for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ pParent = sqlite3FindTable(db, pFK->zTo, zDb); pIdx = 0; aiCols = 0; @@ -1461,6 +1556,7 @@ void sqlite3Pragma( ** regRow..regRow+n. If any of the child key values are NULL, this ** row cannot cause an FK violation. Jump directly to addrOk in ** this case. */ + if( regRow+pFK->nCol>pParse->nMem ) pParse->nMem = regRow+pFK->nCol; for(j=0; jnCol; j++){ int iCol = aiCols ? aiCols[j] : pFK->aCol[j].iFrom; sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, iCol, regRow+j); @@ -1647,8 +1743,9 @@ void sqlite3Pragma( int loopTop; int iDataCur, iIdxCur; int r1 = -1; + int bStrict; - if( pTab->tnum<1 ) continue; /* Skip VIEWs or VIRTUAL TABLEs */ + if( !IsOrdinaryTable(pTab) ) continue; if( pObjTab && pObjTab!=pTab ) continue; pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, @@ -1668,23 +1765,48 @@ void sqlite3Pragma( /* Sanity check on record header decoding */ sqlite3VdbeAddOp3(v, OP_Column, iDataCur, pTab->nNVCol-1,3); sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + VdbeComment((v, "(right-most column)")); } - /* Verify that all NOT NULL columns really are NOT NULL */ + /* Verify that all NOT NULL columns really are NOT NULL. At the + ** same time verify the type of the content of STRICT tables */ + bStrict = (pTab->tabFlags & TF_Strict)!=0; for(j=0; jnCol; j++){ char *zErr; - int jmp2; + Column *pCol = pTab->aCol + j; + int doError, jmp2; if( j==pTab->iPKey ) continue; - if( pTab->aCol[j].notNull==0 ) continue; + if( pCol->notNull==0 && !bStrict ) continue; + doError = bStrict ? sqlite3VdbeMakeLabel(pParse) : 0; sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); if( sqlite3VdbeGetOp(v,-1)->opcode==OP_Column ){ sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); } - jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); - zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, - pTab->aCol[j].zName); - sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); - integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, jmp2); + if( pCol->notNull ){ + jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, + pCol->zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + if( bStrict && pCol->eCType!=COLTYPE_ANY ){ + sqlite3VdbeGoto(v, doError); + }else{ + integrityCheckResultRow(v); + } + sqlite3VdbeJumpHere(v, jmp2); + } + if( (pTab->tabFlags & TF_Strict)!=0 + && pCol->eCType!=COLTYPE_ANY + ){ + jmp2 = sqlite3VdbeAddOp3(v, OP_IsNullOrType, 3, 0, + sqlite3StdTypeMap[pCol->eCType-1]); + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "non-%s value in %s.%s", + sqlite3StdType[pCol->eCType-1], + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + sqlite3VdbeResolveLabel(v, doError); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, jmp2); + } } /* Verify CHECK constraints */ if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ @@ -2218,12 +2340,12 @@ void sqlite3Pragma( case PragTyp_ANALYSIS_LIMIT: { sqlite3_int64 N; if( zRight - && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK + && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK /* IMP: R-40975-20399 */ && N>=0 ){ db->nAnalysisLimit = (int)(N&0x7fffffff); } - returnSingleInt(v, db->nAnalysisLimit); + returnSingleInt(v, db->nAnalysisLimit); /* IMP: R-57594-65522 */ break; } diff --git a/src/pragma.h b/src/pragma.h index be99befb2..6eb05763c 100644 --- a/src/pragma.h +++ b/src/pragma.h @@ -43,13 +43,14 @@ #define PragTyp_SOFT_HEAP_LIMIT 35 #define PragTyp_SYNCHRONOUS 36 #define PragTyp_TABLE_INFO 37 -#define PragTyp_TEMP_STORE 38 -#define PragTyp_TEMP_STORE_DIRECTORY 39 -#define PragTyp_THREADS 40 -#define PragTyp_WAL_AUTOCHECKPOINT 41 -#define PragTyp_WAL_CHECKPOINT 42 -#define PragTyp_LOCK_STATUS 43 -#define PragTyp_STATS 44 +#define PragTyp_TABLE_LIST 38 +#define PragTyp_TEMP_STORE 39 +#define PragTyp_TEMP_STORE_DIRECTORY 40 +#define PragTyp_THREADS 41 +#define PragTyp_WAL_AUTOCHECKPOINT 42 +#define PragTyp_WAL_CHECKPOINT 43 +#define PragTyp_LOCK_STATUS 44 +#define PragTyp_STATS 45 /* Property flags associated with various pragma. */ #define PragFlg_NeedSchema 0x01 /* Force schema load before running */ @@ -82,45 +83,51 @@ static const char *const pragCName[] = { /* 13 */ "pk", /* 14 */ "hidden", /* table_info reuses 8 */ - /* 15 */ "seqno", /* Used by: index_xinfo */ - /* 16 */ "cid", - /* 17 */ "name", - /* 18 */ "desc", - /* 19 */ "coll", - /* 20 */ "key", - /* 21 */ "name", /* Used by: function_list */ - /* 22 */ "builtin", - /* 23 */ "type", - /* 24 */ "enc", - /* 25 */ "narg", - /* 26 */ "flags", - /* 27 */ "tbl", /* Used by: stats */ - /* 28 */ "idx", - /* 29 */ "wdth", - /* 30 */ "hght", - /* 31 */ "flgs", - /* 32 */ "seq", /* Used by: index_list */ - /* 33 */ "name", - /* 34 */ "unique", - /* 35 */ "origin", - /* 36 */ "partial", - /* 37 */ "table", /* Used by: foreign_key_check */ - /* 38 */ "rowid", - /* 39 */ "parent", - /* 40 */ "fkid", - /* index_info reuses 15 */ - /* 41 */ "seq", /* Used by: database_list */ - /* 42 */ "name", - /* 43 */ "file", - /* 44 */ "busy", /* Used by: wal_checkpoint */ - /* 45 */ "log", - /* 46 */ "checkpointed", - /* collation_list reuses 32 */ - /* 47 */ "database", /* Used by: lock_status */ - /* 48 */ "status", - /* 49 */ "cache_size", /* Used by: default_cache_size */ + /* 15 */ "schema", /* Used by: table_list */ + /* 16 */ "name", + /* 17 */ "type", + /* 18 */ "ncol", + /* 19 */ "wr", + /* 20 */ "strict", + /* 21 */ "seqno", /* Used by: index_xinfo */ + /* 22 */ "cid", + /* 23 */ "name", + /* 24 */ "desc", + /* 25 */ "coll", + /* 26 */ "key", + /* 27 */ "name", /* Used by: function_list */ + /* 28 */ "builtin", + /* 29 */ "type", + /* 30 */ "enc", + /* 31 */ "narg", + /* 32 */ "flags", + /* 33 */ "tbl", /* Used by: stats */ + /* 34 */ "idx", + /* 35 */ "wdth", + /* 36 */ "hght", + /* 37 */ "flgs", + /* 38 */ "seq", /* Used by: index_list */ + /* 39 */ "name", + /* 40 */ "unique", + /* 41 */ "origin", + /* 42 */ "partial", + /* 43 */ "table", /* Used by: foreign_key_check */ + /* 44 */ "rowid", + /* 45 */ "parent", + /* 46 */ "fkid", + /* index_info reuses 21 */ + /* 47 */ "seq", /* Used by: database_list */ + /* 48 */ "name", + /* 49 */ "file", + /* 50 */ "busy", /* Used by: wal_checkpoint */ + /* 51 */ "log", + /* 52 */ "checkpointed", + /* collation_list reuses 38 */ + /* 53 */ "database", /* Used by: lock_status */ + /* 54 */ "status", + /* 55 */ "cache_size", /* Used by: default_cache_size */ /* module_list pragma_list reuses 9 */ - /* 50 */ "timeout", /* Used by: busy_timeout */ + /* 56 */ "timeout", /* Used by: busy_timeout */ }; /* Definitions of all built-in pragmas */ @@ -171,7 +178,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "busy_timeout", /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 50, 1, + /* ColNames: */ 56, 1, /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "cache_size", @@ -210,7 +217,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "collation_list", /* ePragTyp: */ PragTyp_COLLATION_LIST, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 32, 2, + /* ColNames: */ 38, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) @@ -245,14 +252,14 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "database_list", /* ePragTyp: */ PragTyp_DATABASE_LIST, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, - /* ColNames: */ 41, 3, + /* ColNames: */ 47, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) {/* zName: */ "default_cache_size", /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, - /* ColNames: */ 49, 1, + /* ColNames: */ 55, 1, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -282,7 +289,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "foreign_key_check", /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 37, 4, + /* ColNames: */ 43, 4, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) @@ -325,7 +332,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "function_list", /* ePragTyp: */ PragTyp_FUNCTION_LIST, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 21, 6, + /* ColNames: */ 27, 6, /* iArg: */ 0 }, #endif #endif @@ -354,23 +361,23 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "index_info", /* ePragTyp: */ PragTyp_INDEX_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 15, 3, + /* ColNames: */ 21, 3, /* iArg: */ 0 }, {/* zName: */ "index_list", /* ePragTyp: */ PragTyp_INDEX_LIST, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 32, 5, + /* ColNames: */ 38, 5, /* iArg: */ 0 }, {/* zName: */ "index_xinfo", /* ePragTyp: */ PragTyp_INDEX_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 15, 6, + /* ColNames: */ 21, 6, /* iArg: */ 1 }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "integrity_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -404,7 +411,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "lock_status", /* ePragTyp: */ PragTyp_LOCK_STATUS, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 47, 2, + /* ColNames: */ 53, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -478,7 +485,7 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "quick_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -543,7 +550,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "stats", /* ePragTyp: */ PragTyp_STATS, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, - /* ColNames: */ 27, 5, + /* ColNames: */ 33, 5, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -559,6 +566,11 @@ static const PragmaName aPragmaName[] = { /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 8, 6, /* iArg: */ 0 }, + {/* zName: */ "table_list", + /* ePragTyp: */ PragTyp_TABLE_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1, + /* ColNames: */ 15, 6, + /* iArg: */ 0 }, {/* zName: */ "table_xinfo", /* ePragTyp: */ PragTyp_TABLE_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, @@ -634,7 +646,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "wal_checkpoint", /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, /* ePragFlg: */ PragFlg_NeedSchema, - /* ColNames: */ 44, 3, + /* ColNames: */ 50, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -645,4 +657,4 @@ static const PragmaName aPragmaName[] = { /* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError }, #endif }; -/* Number of pragmas: 67 on by default, 77 total. */ +/* Number of pragmas: 68 on by default, 78 total. */ diff --git a/src/prepare.c b/src/prepare.c index 6dcb3842e..97541beac 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -29,10 +29,15 @@ static void corruptSchema( pData->rc = SQLITE_NOMEM_BKPT; }else if( pData->pzErrMsg[0]!=0 ){ /* A error message has already been generated. Do not overwrite it */ - }else if( pData->mInitFlags & (INITFLAG_AlterRename|INITFLAG_AlterDrop) ){ + }else if( pData->mInitFlags & (INITFLAG_AlterMask) ){ + static const char *azAlterType[] = { + "rename", + "drop column", + "add column" + }; *pData->pzErrMsg = sqlite3MPrintf(db, "error in %s %s after %s: %s", azObj[0], azObj[1], - (pData->mInitFlags & INITFLAG_AlterRename) ? "rename" : "drop column", + azAlterType[(pData->mInitFlags&INITFLAG_AlterMask)-1], zExtra ); pData->rc = SQLITE_ERROR; @@ -134,7 +139,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ } } db->init.orphanTrigger = 0; - db->init.azInit = argv; + db->init.azInit = (const char**)argv; pStmt = 0; TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0); rc = db->errCode; @@ -153,6 +158,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ } } } + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ sqlite3_finalize(pStmt); }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){ corruptSchema(pData, argv, 0); @@ -741,7 +747,7 @@ static int sqlite3Prepare( sParse.checkSchema = 0; } if( sParse.rc!=SQLITE_OK && sParse.rc!=SQLITE_DONE ){ - if( sParse.checkSchema ){ + if( sParse.checkSchema && db->init.busy==0 ){ schemaIsValid(&sParse); } if( sParse.pVdbe ){ @@ -802,6 +808,7 @@ static int sqlite3LockAndPrepare( ** reset is considered a permanent error. */ rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); assert( rc==SQLITE_OK || *ppStmt==0 ); + if( rc==SQLITE_OK || db->mallocFailed ) break; }while( rc==SQLITE_ERROR_RETRY || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) ); sqlite3BtreeLeaveAll(db); diff --git a/src/printf.c b/src/printf.c index f663e1b1e..e63518450 100644 --- a/src/printf.c +++ b/src/printf.c @@ -145,7 +145,7 @@ static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ /* ** Set the StrAccum object to an error mode. */ -static void setStrAccumError(StrAccum *p, u8 eError){ +void sqlite3StrAccumSetError(StrAccum *p, u8 eError){ assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG ); p->accError = eError; if( p->mxAlloc ) sqlite3_str_reset(p); @@ -181,12 +181,12 @@ static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){ char *z; if( pAccum->accError ) return 0; if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){ - setStrAccumError(pAccum, SQLITE_TOOBIG); + sqlite3StrAccumSetError(pAccum, SQLITE_TOOBIG); return 0; } z = sqlite3DbMallocRaw(pAccum->db, n); if( z==0 ){ - setStrAccumError(pAccum, SQLITE_NOMEM); + sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM); } return z; } @@ -925,7 +925,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ return 0; } if( p->mxAlloc==0 ){ - setStrAccumError(p, SQLITE_TOOBIG); + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); return p->nAlloc - p->nChar - 1; }else{ char *zOld = isMalloced(p) ? p->zText : 0; @@ -938,7 +938,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ } if( szNew > p->mxAlloc ){ sqlite3_str_reset(p); - setStrAccumError(p, SQLITE_TOOBIG); + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); return 0; }else{ p->nAlloc = (int)szNew; @@ -956,7 +956,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ sqlite3_str_reset(p); - setStrAccumError(p, SQLITE_NOMEM); + sqlite3StrAccumSetError(p, SQLITE_NOMEM); return 0; } } @@ -1029,7 +1029,7 @@ static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){ memcpy(zText, p->zText, p->nChar+1); p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ - setStrAccumError(p, SQLITE_NOMEM); + sqlite3StrAccumSetError(p, SQLITE_NOMEM); } p->zText = zText; return zText; @@ -1044,6 +1044,22 @@ char *sqlite3StrAccumFinish(StrAccum *p){ return p->zText; } +/* +** Use the content of the StrAccum passed as the second argument +** as the result of an SQL function. +*/ +void sqlite3ResultStrAccum(sqlite3_context *pCtx, StrAccum *p){ + if( p->accError ){ + sqlite3_result_error_code(pCtx, p->accError); + sqlite3_str_reset(p); + }else if( isMalloced(p) ){ + sqlite3_result_text(pCtx, p->zText, p->nChar, SQLITE_DYNAMIC); + }else{ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + sqlite3_str_reset(p); + } +} + /* ** This singleton is an sqlite3_str object that is returned if ** sqlite3_malloc() fails to provide space for a real one. This diff --git a/src/resolve.c b/src/resolve.c index 93e10b6e4..27b260e05 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -87,6 +87,7 @@ static void resolveAlias( }else{ incrAggFunctionDepth(pDup, nSubquery); if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken); } @@ -190,6 +191,7 @@ Bitmask sqlite3ExprColUsed(Expr *pExpr){ Table *pExTab; n = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); pExTab = pExpr->y.pTab; assert( pExTab!=0 ); if( (pExTab->tabFlags & TF_HasGenerated)!=0 @@ -303,7 +305,7 @@ static int lookupName( u8 hCol; pTab = pItem->pTab; assert( pTab!=0 && pTab->zName!=0 ); - assert( pTab->nCol>0 ); + assert( pTab->nCol>0 || pParse->nErr ); if( pItem->pSelect && (pItem->pSelect->selFlags & SF_NestedFrom)!=0 ){ int hit = 0; pEList = pItem->pSelect->pEList; @@ -327,16 +329,16 @@ static int lookupName( if( sqlite3StrICmp(zTabName, zTab)!=0 ){ continue; } + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT && pItem->zAlias ){ sqlite3RenameTokenRemap(pParse, 0, (void*)&pExpr->y.pTab); } } - if( 0==(cntTab++) ){ - pMatch = pItem; - } hCol = sqlite3StrIHash(zCol); for(j=0, pCol=pTab->aCol; jnCol; j++, pCol++){ - if( pCol->hName==hCol && sqlite3StrICmp(pCol->zName, zCol)==0 ){ + if( pCol->hName==hCol + && sqlite3StrICmp(pCol->zCnName, zCol)==0 + ){ /* If there has been exactly one prior match and this match ** is for the right-hand table of a NATURAL JOIN or is in a ** USING clause, then skip this match. @@ -352,9 +354,14 @@ static int lookupName( break; } } + if( 0==cnt && VisibleRowid(pTab) ){ + cntTab++; + pMatch = pItem; + } } if( pMatch ){ pExpr->iTable = pMatch->iCursor; + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pMatch->pTab; /* RIGHT JOIN not (yet) supported */ assert( (pMatch->fg.jointype & JT_RIGHT)==0 ); @@ -409,7 +416,9 @@ static int lookupName( pSchema = pTab->pSchema; cntTab++; for(iCol=0, pCol=pTab->aCol; iColnCol; iCol++, pCol++){ - if( pCol->hName==hCol && sqlite3StrICmp(pCol->zName, zCol)==0 ){ + if( pCol->hName==hCol + && sqlite3StrICmp(pCol->zCnName, zCol)==0 + ){ if( iCol==pTab->iPKey ){ iCol = -1; } @@ -426,6 +435,7 @@ static int lookupName( #ifndef SQLITE_OMIT_UPSERT if( pExpr->iTable==EXCLUDED_TABLE_NUMBER ){ testcase( iCol==(-1) ); + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT ){ pExpr->iColumn = iCol; pExpr->y.pTab = pTab; @@ -438,6 +448,7 @@ static int lookupName( }else #endif /* SQLITE_OMIT_UPSERT */ { + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pTab; if( pParse->bReturning ){ eNewExprOp = TK_REGISTER; @@ -474,7 +485,7 @@ static int lookupName( && pMatch && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && sqlite3IsRowid(zCol) - && VisibleRowid(pMatch->pTab) + && ALWAYS(VisibleRowid(pMatch->pTab)) ){ cnt = 1; pExpr->iColumn = -1; @@ -512,8 +523,8 @@ static int lookupName( ){ Expr *pOrig; assert( pExpr->pLeft==0 && pExpr->pRight==0 ); - assert( pExpr->x.pList==0 ); - assert( pExpr->x.pSelect==0 ); + assert( ExprUseXList(pExpr)==0 || pExpr->x.pList==0 ); + assert( ExprUseXSelect(pExpr)==0 || pExpr->x.pSelect==0 ); pOrig = pEList->a[j].pExpr; if( (pNC->ncFlags&NC_AllowAgg)==0 && ExprHasProperty(pOrig, EP_Agg) ){ sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs); @@ -585,7 +596,7 @@ static int lookupName( sqlite3VdbeAddDblquoteStr(db, pParse->pVdbe, zCol); #endif pExpr->op = TK_STRING; - pExpr->y.pTab = 0; + memset(&pExpr->y, 0, sizeof(pExpr->y)); return WRC_Prune; } if( sqlite3ExprIdToTrueFalse(pExpr) ){ @@ -671,7 +682,9 @@ Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){ Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0); if( p ){ SrcItem *pItem = &pSrc->a[iSrc]; - Table *pTab = p->y.pTab = pItem->pTab; + Table *pTab; + assert( ExprUseYTab(p) ); + pTab = p->y.pTab = pItem->pTab; p->iTable = pItem->iCursor; if( p->y.pTab->iPKey==iCol ){ p->iColumn = -1; @@ -738,6 +751,7 @@ static void notValidImpl( static int exprProbability(Expr *p){ double r = -1.0; if( p->op!=TK_FLOAT ) return -1; + assert( !ExprHasProperty(p, EP_IntValue) ); sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); assert( r>=0.0 ); if( r>1.0 ) return -1; @@ -786,6 +800,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pSrcList && pSrcList->nSrc>=1 ); pItem = pSrcList->a; pExpr->op = TK_COLUMN; + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pItem->pTab; pExpr->iTable = pItem->iCursor; pExpr->iColumn--; @@ -817,6 +832,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } sqlite3WalkExpr(pWalker, pExpr->pLeft); if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ + testcase( ExprHasProperty(pExpr, EP_FromJoin) ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); if( pExpr->op==TK_NOTNULL ){ pExpr->u.zToken = "true"; ExprSetProperty(pExpr, EP_IsTrue); @@ -852,6 +869,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_ID ){ zDb = 0; zTable = 0; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); zColumn = pExpr->u.zToken; }else{ Expr *pLeft = pExpr->pLeft; @@ -864,12 +882,15 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ zDb = 0; }else{ assert( pRight->op==TK_DOT ); + assert( !ExprHasProperty(pRight, EP_IntValue) ); zDb = pLeft->u.zToken; pLeft = pRight->pLeft; pRight = pRight->pRight; } + assert( ExprUseUToken(pLeft) && ExprUseUToken(pRight) ); zTable = pLeft->u.zToken; zColumn = pRight->u.zToken; + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, (void*)pExpr, (void*)pRight); sqlite3RenameTokenRemap(pParse, (void*)&pExpr->y.pTab, (void*)pLeft); @@ -894,7 +915,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #ifndef SQLITE_OMIT_WINDOWFUNC Window *pWin = (IsWindowFunc(pExpr) ? pExpr->y.pWin : 0); #endif - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); zId = pExpr->u.zToken; nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); @@ -1058,7 +1079,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #ifndef SQLITE_OMIT_WINDOWFUNC if( pWin ){ Select *pSel = pNC->pWinSelect; - assert( pWin==pExpr->y.pWin ); + assert( pWin==0 || (ExprUseYWin(pExpr) && pWin==pExpr->y.pWin) ); if( IN_RENAME_OBJECT==0 ){ sqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef); if( pParse->db->mallocFailed ) break; @@ -1071,7 +1092,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ }else #endif /* SQLITE_OMIT_WINDOWFUNC */ { - NameContext *pNC2 = pNC; + NameContext *pNC2; /* For looping up thru outer contexts */ pExpr->op = TK_AGG_FUNCTION; pExpr->op2 = 0; #ifndef SQLITE_OMIT_WINDOWFUNC @@ -1079,16 +1100,22 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ sqlite3WalkExpr(pWalker, pExpr->y.pWin->pFilter); } #endif - while( pNC2 && !sqlite3FunctionUsesThisSrc(pExpr, pNC2->pSrcList) ){ + pNC2 = pNC; + while( pNC2 + && sqlite3ReferencesSrcList(pParse, pExpr, pNC2->pSrcList)==0 + ){ pExpr->op2++; pNC2 = pNC2->pNext; } assert( pDef!=0 || IN_RENAME_OBJECT ); if( pNC2 && pDef ){ assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg ); + assert( SQLITE_FUNC_ANYORDER==NC_OrderAgg ); testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 ); - pNC2->ncFlags |= NC_HasAgg | (pDef->funcFlags & SQLITE_FUNC_MINMAX); - + testcase( (pDef->funcFlags & SQLITE_FUNC_ANYORDER)!=0 ); + pNC2->ncFlags |= NC_HasAgg + | ((pDef->funcFlags^SQLITE_FUNC_ANYORDER) + & (SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER)); } } pNC->ncFlags |= savedAllowFlags; @@ -1104,15 +1131,17 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #endif case TK_IN: { testcase( pExpr->op==TK_IN ); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ int nRef = pNC->nRef; testcase( pNC->ncFlags & NC_IsCheck ); testcase( pNC->ncFlags & NC_PartIdx ); testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); - sqlite3ResolveNotValid(pParse, pNC, "subqueries", - NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr); - sqlite3WalkSelect(pWalker, pExpr->x.pSelect); + if( pNC->ncFlags & NC_SelfRef ){ + notValidImpl(pParse, pNC, "subqueries", pExpr); + }else{ + sqlite3WalkSelect(pWalker, pExpr->x.pSelect); + } assert( pNC->nRef>=nRef ); if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); @@ -1159,6 +1188,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pExpr->pLeft!=0 ); nLeft = sqlite3ExprVectorSize(pExpr->pLeft); if( pExpr->op==TK_BETWEEN ){ + assert( ExprUseXList(pExpr) ); nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr); if( nRight==nLeft ){ nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr); @@ -1207,7 +1237,9 @@ static int resolveAsName( UNUSED_PARAMETER(pParse); if( pE->op==TK_ID ){ - char *zCol = pE->u.zToken; + const char *zCol; + assert( !ExprHasProperty(pE, EP_IntValue) ); + zCol = pE->u.zToken; for(i=0; inExpr; i++){ if( pEList->a[i].eEName==ENAME_NAME && sqlite3_stricmp(pEList->a[i].zEName, zCol)==0 @@ -1631,7 +1663,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ p->pOrderBy = 0; } - /* Recursively resolve names in all subqueries + /* Recursively resolve names in all subqueries in the FROM clause */ for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; @@ -1675,7 +1707,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ pGroupBy = p->pGroupBy; if( pGroupBy || (sNC.ncFlags & NC_HasAgg)!=0 ){ assert( NC_MinMaxAgg==SF_MinMaxAgg ); - p->selFlags |= SF_Aggregate | (sNC.ncFlags&NC_MinMaxAgg); + assert( NC_OrderAgg==SF_OrderByReqd ); + p->selFlags |= SF_Aggregate | (sNC.ncFlags&(NC_MinMaxAgg|NC_OrderAgg)); }else{ sNC.ncFlags &= ~NC_AllowAgg; } @@ -1858,8 +1891,8 @@ int sqlite3ResolveExprNames( Walker w; if( pExpr==0 ) return SQLITE_OK; - savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); - pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); w.pParse = pNC->pParse; w.xExprCallback = resolveExprStep; w.xSelectCallback = (pNC->ncFlags & NC_NoSelect) ? 0 : resolveSelectStep; @@ -1902,8 +1935,8 @@ int sqlite3ResolveExprListNames( w.xSelectCallback = resolveSelectStep; w.xSelectCallback2 = 0; w.u.pNC = pNC; - savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); - pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); for(i=0; inExpr; i++){ Expr *pExpr = pList->a[i].pExpr; if( pExpr==0 ) continue; @@ -1921,10 +1954,11 @@ int sqlite3ResolveExprListNames( assert( EP_Win==NC_HasWin ); testcase( pNC->ncFlags & NC_HasAgg ); testcase( pNC->ncFlags & NC_HasWin ); - if( pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin) ){ + if( pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg) ){ ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); - savedHasAgg |= pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); - pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); + savedHasAgg |= pNC->ncFlags & + (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); } if( w.pParse->nErr>0 ) return WRC_Abort; } diff --git a/src/select.c b/src/select.c index 7d8fd5939..7f15c2acb 100644 --- a/src/select.c +++ b/src/select.c @@ -271,7 +271,7 @@ int sqlite3ColumnIndex(Table *pTab, const char *zCol){ u8 h = sqlite3StrIHash(zCol); Column *pCol; for(pCol=pTab->aCol, i=0; inCol; pCol++, i++){ - if( pCol->hName==h && sqlite3StrICmp(pCol->zName, zCol)==0 ) return i; + if( pCol->hName==h && sqlite3StrICmp(pCol->zCnName, zCol)==0 ) return i; } return -1; } @@ -347,6 +347,9 @@ static void addWhereTerm( pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight); pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); + assert( pE2!=0 || pEq==0 ); /* Due to db->mallocFailed test + ** in sqlite3DbMallocRawNN() called from + ** sqlite3PExpr(). */ if( pEq && isOuterJoin ){ ExprSetProperty(pEq, EP_FromJoin); assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); @@ -388,10 +391,13 @@ void sqlite3SetJoinExpr(Expr *p, int iTable){ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(p, EP_NoReduce); p->iRightJoinTable = iTable; - if( p->op==TK_FUNCTION && p->x.pList ){ - int i; - for(i=0; ix.pList->nExpr; i++){ - sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable); + if( p->op==TK_FUNCTION ){ + assert( ExprUseXList(p) ); + if( p->x.pList ){ + int i; + for(i=0; ix.pList->nExpr; i++){ + sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable); + } } } sqlite3SetJoinExpr(p->pLeft, iTable); @@ -414,10 +420,13 @@ static void unsetJoinExpr(Expr *p, int iTable){ if( p->op==TK_COLUMN && p->iTable==iTable ){ ExprClearProperty(p, EP_CanBeNull); } - if( p->op==TK_FUNCTION && p->x.pList ){ - int i; - for(i=0; ix.pList->nExpr; i++){ - unsetJoinExpr(p->x.pList->a[i].pExpr, iTable); + if( p->op==TK_FUNCTION ){ + assert( ExprUseXList(p) ); + if( p->x.pList ){ + int i; + for(i=0; ix.pList->nExpr; i++){ + unsetJoinExpr(p->x.pList->a[i].pExpr, iTable); + } } } unsetJoinExpr(p->pLeft, iTable); @@ -470,7 +479,7 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){ int iLeftCol; /* Matching column in the left table */ if( IsHiddenColumn(&pRightTab->aCol[j]) ) continue; - zName = pRightTab->aCol[j].zName; + zName = pRightTab->aCol[j].zCnName; if( tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 1) ){ addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, j, isOuter, &p->pWhere); @@ -873,7 +882,9 @@ static void fixDistinctOpenEph( int iVal, /* Value returned by codeDistinct() */ int iOpenEphAddr /* Address of OP_OpenEphemeral instruction for iTab */ ){ - if( eTnctType==WHERE_DISTINCT_UNIQUE || eTnctType==WHERE_DISTINCT_ORDERED ){ + if( pParse->nErr==0 + && (eTnctType==WHERE_DISTINCT_UNIQUE || eTnctType==WHERE_DISTINCT_ORDERED) + ){ Vdbe *v = pParse->pVdbe; sqlite3VdbeChangeToNoop(v, iOpenEphAddr); if( sqlite3VdbeGetOp(v, iOpenEphAddr+1)->opcode==OP_Explain ){ @@ -930,9 +941,13 @@ static void selectExprDefer( struct ExprList_item *pItem = &pEList->a[i]; if( pItem->u.x.iOrderByCol==0 ){ Expr *pExpr = pItem->pExpr; - Table *pTab = pExpr->y.pTab; - if( pExpr->op==TK_COLUMN && pExpr->iColumn>=0 && pTab && !IsVirtual(pTab) - && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF) + Table *pTab; + if( pExpr->op==TK_COLUMN + && pExpr->iColumn>=0 + && ALWAYS( ExprUseYTab(pExpr) ) + && (pTab = pExpr->y.pTab)!=0 + && IsOrdinaryTable(pTab) + && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF)!=0 ){ int j; for(j=0; jiTable = pExpr->iTable; + assert( ExprUseYTab(pNew) ); pNew->y.pTab = pExpr->y.pTab; pNew->iColumn = pPk ? pPk->aiColumn[k] : -1; pExtra = sqlite3ExprListAppend(pParse, pExtra, pNew); @@ -1801,7 +1817,7 @@ static const char *columnTypeImpl( break; } - assert( pTab && pExpr->y.pTab==pTab ); + assert( pTab && ExprUseYTab(pExpr) && pExpr->y.pTab==pTab ); if( pS ){ /* The "table" is actually a sub-select or a view in the FROM clause ** of the SELECT statement. Return the declaration type and origin @@ -1835,7 +1851,7 @@ static const char *columnTypeImpl( zType = "INTEGER"; zOrigCol = "rowid"; }else{ - zOrigCol = pTab->aCol[iCol].zName; + zOrigCol = pTab->aCol[iCol].zCnName; zType = sqlite3ColumnType(&pTab->aCol[iCol],0); } zOrigTab = pTab->zName; @@ -1861,9 +1877,11 @@ static const char *columnTypeImpl( ** statement. */ NameContext sNC; - Select *pS = pExpr->x.pSelect; - Expr *p = pS->pEList->a[0].pExpr; - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + Select *pS; + Expr *p; + assert( ExprUseXSelect(pExpr) ); + pS = pExpr->x.pSelect; + p = pS->pEList->a[0].pExpr; sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; @@ -1992,7 +2010,8 @@ void sqlite3GenerateColumnNames( assert( p!=0 ); assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */ - assert( p->op!=TK_COLUMN || p->y.pTab!=0 ); /* Covering idx not yet coded */ + assert( p->op!=TK_COLUMN + || (ExprUseYTab(p) && p->y.pTab!=0) ); /* Covering idx not yet coded */ if( pEList->a[i].zEName && pEList->a[i].eEName==ENAME_NAME ){ /* An AS clause always takes first priority */ char *zName = pEList->a[i].zEName; @@ -2007,7 +2026,7 @@ void sqlite3GenerateColumnNames( if( iCol<0 ){ zCol = "rowid"; }else{ - zCol = pTab->aCol[iCol].zName; + zCol = pTab->aCol[iCol].zCnName; } if( fullName ){ char *zName = 0; @@ -2088,11 +2107,14 @@ int sqlite3ColumnsFromExprList( pColExpr = pColExpr->pRight; assert( pColExpr!=0 ); } - if( pColExpr->op==TK_COLUMN && (pTab = pColExpr->y.pTab)!=0 ){ + if( pColExpr->op==TK_COLUMN + && ALWAYS( ExprUseYTab(pColExpr) ) + && (pTab = pColExpr->y.pTab)!=0 + ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; if( iCol<0 ) iCol = pTab->iPKey; - zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid"; + zName = iCol>=0 ? pTab->aCol[iCol].zCnName : "rowid"; }else if( pColExpr->op==TK_ID ){ assert( !ExprHasProperty(pColExpr, EP_IntValue) ); zName = pColExpr->u.zToken; @@ -2120,7 +2142,7 @@ int sqlite3ColumnsFromExprList( zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt); if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt); } - pCol->zName = zName; + pCol->zCnName = zName; pCol->hName = sqlite3StrIHash(zName); sqlite3ColumnPropertiesFromName(0, pCol); if( zName && sqlite3HashInsert(&ht, zName, pCol)==pCol ){ @@ -2130,7 +2152,7 @@ int sqlite3ColumnsFromExprList( sqlite3HashClear(&ht); if( db->mallocFailed ){ for(j=0; jpEList->a; for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; - int n, m; + i64 n, m; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; zType = columnType(&sNC, p, 0, 0, 0); @@ -2182,17 +2204,21 @@ void sqlite3SelectAddColumnTypeAndCollation( pCol->affinity = sqlite3ExprAffinity(p); if( zType ){ m = sqlite3Strlen30(zType); - n = sqlite3Strlen30(pCol->zName); - pCol->zName = sqlite3DbReallocOrFree(db, pCol->zName, n+m+2); - if( pCol->zName ){ - memcpy(&pCol->zName[n+1], zType, m+1); + n = sqlite3Strlen30(pCol->zCnName); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + if( pCol->zCnName ){ + memcpy(&pCol->zCnName[n+1], zType, m+1); pCol->colFlags |= COLFLAG_HASTYPE; + }else{ + testcase( pCol->colFlags & COLFLAG_HASTYPE ); + pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); } } if( pCol->affinity<=SQLITE_AFF_NONE ) pCol->affinity = aff; pColl = sqlite3ExprCollSeq(pParse, p); - if( pColl && pCol->zColl==0 ){ - pCol->zColl = sqlite3DbStrDup(db, pColl->zName); + if( pColl ){ + assert( pTab->pIndex==0 ); + sqlite3ColumnSetColl(db, pCol, pColl->zName); } } pTab->szTabRow = 1; /* Any non-zero value works */ @@ -2356,7 +2382,7 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ */ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ ExprList *pOrderBy = p->pOrderBy; - int nOrderBy = p->pOrderBy->nExpr; + int nOrderBy = ALWAYS(pOrderBy!=0) ? pOrderBy->nExpr : 0; sqlite3 *db = pParse->db; KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); if( pRet ){ @@ -2428,7 +2454,7 @@ static void generateWithRecursiveQuery( SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */ int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */ Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */ - Select *pSetup = p->pPrior; /* The setup query */ + Select *pSetup; /* The setup query */ Select *pFirstRec; /* Left-most recursive term */ int addrTop; /* Top of the loop */ int addrCont, addrBreak; /* CONTINUE and BREAK addresses */ @@ -2512,7 +2538,6 @@ static void generateWithRecursiveQuery( ** iDistinct table. pFirstRec is left pointing to the left-most ** recursive term of the CTE. */ - pFirstRec = p; for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){ if( pFirstRec->selFlags & SF_Aggregate ){ sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported"); @@ -2978,6 +3003,7 @@ static int multiSelect( int nCol; /* Number of columns in result set */ assert( p->pNext==0 ); + assert( p->pEList!=0 ); nCol = p->pEList->nExpr; pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); if( !pKeyInfo ){ @@ -3012,7 +3038,11 @@ static int multiSelect( multi_select_end: pDest->iSdst = dest.iSdst; pDest->nSdst = dest.nSdst; - sqlite3SelectDelete(db, pDelete); + if( pDelete ){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3SelectDelete, + pDelete); + } return rc; } #endif /* SQLITE_OMIT_COMPOUND_SELECT */ @@ -3325,6 +3355,7 @@ static int multiSelectOrderBy( for(i=1; db->mallocFailed==0 && i<=p->pEList->nExpr; i++){ struct ExprList_item *pItem; for(j=0, pItem=pOrderBy->a; ju.x.iOrderByCol>0 ); if( pItem->u.x.iOrderByCol==i ) break; } @@ -3351,6 +3382,7 @@ static int multiSelectOrderBy( struct ExprList_item *pItem; aPermute[0] = nOrderBy; for(i=1, pItem=pOrderBy->a; i<=nOrderBy; i++, pItem++){ + assert( pItem!=0 ); assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; @@ -3663,7 +3695,7 @@ static Expr *substExpr( } pExpr->pLeft = substExpr(pSubst, pExpr->pLeft); pExpr->pRight = substExpr(pSubst, pExpr->pRight); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ substSelect(pSubst, pExpr->x.pSelect, 1); }else{ substExprList(pSubst, pExpr->x.pList); @@ -3754,10 +3786,10 @@ static void recomputeColumnsUsed( ** new cursor number assigned, set an entry in the aCsrMap[] array ** to map the old cursor number to the new: ** -** aCsrMap[iOld] = iNew; +** aCsrMap[iOld+1] = iNew; ** ** The array is guaranteed by the caller to be large enough for all -** existing cursor numbers in pSrc. +** existing cursor numbers in pSrc. aCsrMap[0] is the array size. ** ** If pSrc contains any sub-selects, call this routine recursively ** on the FROM clause of each such sub-select, with iExcept set to -1. @@ -3773,10 +3805,11 @@ static void srclistRenumberCursors( for(i=0, pItem=pSrc->a; inSrc; i++, pItem++){ if( i!=iExcept ){ Select *p; - if( !pItem->fg.isRecursive || aCsrMap[pItem->iCursor]==0 ){ - aCsrMap[pItem->iCursor] = pParse->nTab++; + assert( pItem->iCursor < aCsrMap[0] ); + if( !pItem->fg.isRecursive || aCsrMap[pItem->iCursor+1]==0 ){ + aCsrMap[pItem->iCursor+1] = pParse->nTab++; } - pItem->iCursor = aCsrMap[pItem->iCursor]; + pItem->iCursor = aCsrMap[pItem->iCursor+1]; for(p=pItem->pSelect; p; p=p->pPrior){ srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1); } @@ -3784,18 +3817,28 @@ static void srclistRenumberCursors( } } +/* +** *piCursor is a cursor number. Change it if it needs to be mapped. +*/ +static void renumberCursorDoMapping(Walker *pWalker, int *piCursor){ + int *aCsrMap = pWalker->u.aiCol; + int iCsr = *piCursor; + if( iCsr < aCsrMap[0] && aCsrMap[iCsr+1]>0 ){ + *piCursor = aCsrMap[iCsr+1]; + } +} + /* ** Expression walker callback used by renumberCursors() to update ** Expr objects to match newly assigned cursor numbers. */ static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ - int *aCsrMap = pWalker->u.aiCol; int op = pExpr->op; - if( (op==TK_COLUMN || op==TK_IF_NULL_ROW) && aCsrMap[pExpr->iTable] ){ - pExpr->iTable = aCsrMap[pExpr->iTable]; + if( op==TK_COLUMN || op==TK_IF_NULL_ROW ){ + renumberCursorDoMapping(pWalker, &pExpr->iTable); } - if( ExprHasProperty(pExpr, EP_FromJoin) && aCsrMap[pExpr->iRightJoinTable] ){ - pExpr->iRightJoinTable = aCsrMap[pExpr->iRightJoinTable]; + if( ExprHasProperty(pExpr, EP_FromJoin) ){ + renumberCursorDoMapping(pWalker, &pExpr->iRightJoinTable); } return WRC_Continue; } @@ -4139,7 +4182,8 @@ static int flattenSubquery( if( pSrc->nSrc>1 ){ if( pParse->nSelect>500 ) return 0; - aCsrMap = sqlite3DbMallocZero(db, pParse->nTab*sizeof(int)); + aCsrMap = sqlite3DbMallocZero(db, ((i64)pParse->nTab+1)*sizeof(int)); + if( aCsrMap ) aCsrMap[0] = pParse->nTab; } } @@ -4862,7 +4906,7 @@ static int pushDownWhereTerms( */ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ int eRet = WHERE_ORDERBY_NORMAL; /* Return value */ - ExprList *pEList = pFunc->x.pList; /* Arguments to agg function */ + ExprList *pEList; /* Arguments to agg function */ const char *zFunc; /* Name of aggregate function pFunc */ ExprList *pOrderBy; u8 sortFlags = 0; @@ -4870,6 +4914,8 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ assert( *ppMinMax==0 ); assert( pFunc->op==TK_AGG_FUNCTION ); assert( !IsWindowFunc(pFunc) ); + assert( ExprUseXList(pFunc) ); + pEList = pFunc->x.pList; if( pEList==0 || pEList->nExpr!=1 || ExprHasProperty(pFunc, EP_WinFunc) @@ -4877,6 +4923,7 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ ){ return eRet; } + assert( !ExprHasProperty(pFunc, EP_IntValue) ); zFunc = pFunc->u.zToken; if( sqlite3StrICmp(zFunc, "min")==0 ){ eRet = WHERE_ORDERBY_MIN; @@ -4904,7 +4951,13 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ ** ** where table is a database table, not a sub-select or view. If the query ** does match this pattern, then a pointer to the Table object representing -** is returned. Otherwise, 0 is returned. +** is returned. Otherwise, NULL is returned. +** +** This routine checks to see if it is safe to use the count optimization. +** A correct answer is still obtained (though perhaps more slowly) if +** this routine returns NULL when it could have returned a table pointer. +** But returning the pointer when NULL should have been returned can +** result in incorrect answers and/or crashes. So, when in doubt, return NULL. */ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ Table *pTab; @@ -4912,19 +4965,26 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ assert( !p->pGroupBy ); - if( p->pWhere || p->pEList->nExpr!=1 - || p->pSrc->nSrc!=1 || p->pSrc->a[0].pSelect + if( p->pWhere + || p->pEList->nExpr!=1 + || p->pSrc->nSrc!=1 + || p->pSrc->a[0].pSelect + || pAggInfo->nFunc!=1 ){ return 0; } pTab = p->pSrc->a[0].pTab; + assert( pTab!=0 ); + assert( !IsView(pTab) ); + if( !IsOrdinaryTable(pTab) ) return 0; pExpr = p->pEList->a[0].pExpr; - assert( pTab && !pTab->pSelect && pExpr ); - - if( IsVirtual(pTab) ) return 0; + assert( pExpr!=0 ); if( pExpr->op!=TK_AGG_FUNCTION ) return 0; - if( NEVER(pAggInfo->nFunc==0) ) return 0; + if( pExpr->pAggInfo!=pAggInfo ) return 0; if( (pAggInfo->aFunc[0].pFunc->funcFlags&SQLITE_FUNC_COUNT)==0 ) return 0; + assert( pAggInfo->aFunc[0].pFExpr==pExpr ); + testcase( ExprHasProperty(pExpr, EP_Distinct) ); + testcase( ExprHasProperty(pExpr, EP_WinFunc) ); if( ExprHasProperty(pExpr, EP_Distinct|EP_WinFunc) ) return 0; return pTab; @@ -4953,6 +5013,7 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ pParse->checkSchema = 1; return SQLITE_ERROR; } + assert( pFrom->fg.isCte==0 ); pFrom->u2.pIBIndex = pIdx; return SQLITE_OK; } @@ -5210,6 +5271,10 @@ static int resolveFromTermToCte( if( db->mallocFailed ) return 2; pFrom->pSelect->selFlags |= SF_CopyCte; assert( pFrom->pSelect ); + if( pFrom->fg.isIndexedBy ){ + sqlite3ErrorMsg(pParse, "no such index: \"%s\"", pFrom->u1.zIndexedBy); + return 2; + } pFrom->fg.isCte = 1; pFrom->u2.pCteUse = pCteUse; pCteUse->nUse++; @@ -5464,30 +5529,31 @@ static int selectExpander(Walker *pWalker, Select *p){ return WRC_Abort; } #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) - if( IsVirtual(pTab) || pTab->pSelect ){ + if( !IsOrdinaryTable(pTab) ){ i16 nCol; u8 eCodeOrig = pWalker->eCode; if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; assert( pFrom->pSelect==0 ); - if( pTab->pSelect - && (db->flags & SQLITE_EnableView)==0 - && pTab->pSchema!=db->aDb[1].pSchema - ){ - sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited", - pTab->zName); + if( IsView(pTab) ){ + if( (db->flags & SQLITE_EnableView)==0 + && pTab->pSchema!=db->aDb[1].pSchema + ){ + sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited", + pTab->zName); + } + pFrom->pSelect = sqlite3SelectDup(db, pTab->u.view.pSelect, 0); } #ifndef SQLITE_OMIT_VIRTUALTABLE - assert( SQLITE_VTABRISK_Normal==1 && SQLITE_VTABRISK_High==2 ); - if( IsVirtual(pTab) + else if( ALWAYS(IsVirtual(pTab)) && pFrom->fg.fromDDL - && ALWAYS(pTab->pVTable!=0) - && pTab->pVTable->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) + && ALWAYS(pTab->u.vtab.p!=0) + && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) ){ sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", pTab->zName); } + assert( SQLITE_VTABRISK_Normal==1 && SQLITE_VTABRISK_High==2 ); #endif - pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect, 0); nCol = pTab->nCol; pTab->nCol = -1; pWalker->eCode = 1; /* Turn on Select.selId renumbering */ @@ -5587,7 +5653,7 @@ static int selectExpander(Walker *pWalker, Select *p){ zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; } for(j=0; jnCol; j++){ - char *zName = pTab->aCol[j].zName; + char *zName = pTab->aCol[j].zCnName; char *zColname; /* The computed column name */ char *zToFree; /* Malloced string that needs to be freed */ Token sColname; /* Computed column name as a token */ @@ -5844,7 +5910,7 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ for(pFunc=pAggInfo->aFunc, i=0; inFunc; i++, pFunc++){ if( pFunc->iDistinct>=0 ){ Expr *pE = pFunc->pFExpr; - assert( !ExprHasProperty(pE, EP_xIsSelect) ); + assert( ExprUseXList(pE) ); if( pE->x.pList==0 || pE->x.pList->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one " "argument"); @@ -5869,8 +5935,9 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ int i; struct AggInfo_func *pF; for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){ - ExprList *pList = pF->pFExpr->x.pList; - assert( !ExprHasProperty(pF->pFExpr, EP_xIsSelect) ); + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); + pList = pF->pFExpr->x.pList; sqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } @@ -5904,9 +5971,10 @@ static void updateAccumulator( int nArg; int addrNext = 0; int regAgg; - ExprList *pList = pF->pFExpr->x.pList; - assert( !ExprHasProperty(pF->pFExpr, EP_xIsSelect) ); + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); assert( !IsWindowFunc(pF->pFExpr) ); + pList = pF->pFExpr->x.pList; if( ExprHasProperty(pF->pFExpr, EP_WinFunc) ){ Expr *pFilter = pF->pFExpr->y.pWin->pFilter; if( pAggInfo->nAccumulator @@ -6019,8 +6087,16 @@ static void explainSimpleCount( static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ if( pExpr->op!=TK_AND ){ Select *pS = pWalker->u.pSelect; + /* This routine is called before the HAVING clause of the current + ** SELECT is analyzed for aggregates. So if pExpr->pAggInfo is set + ** here, it indicates that the expression is a correlated reference to a + ** column from an outer aggregate query, or an aggregate function that + ** belongs to an outer query. Do not move the expression to the WHERE + ** clause in this obscure case, as doing so may corrupt the outer Select + ** statements AggInfo structure. */ if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) && ExprAlwaysFalse(pExpr)==0 + && pExpr->pAggInfo==0 ){ sqlite3 *db = pWalker->pParse->db; Expr *pNew = sqlite3Expr(db, TK_INTEGER, "1"); @@ -6144,7 +6220,9 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ if( p->pGroupBy ) return 0; pExpr = p->pEList->a[0].pExpr; if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */ + assert( ExprUseUToken(pExpr) ); if( sqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Is count() */ + assert( ExprUseXList(pExpr) ); if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */ if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */ pSub = p->pSrc->a[0].pSelect; @@ -6286,11 +6364,16 @@ int sqlite3Select( } #endif - /* If the SF_UpdateFrom flag is set, then this function is being called + /* If the SF_UFSrcCheck flag is set, then this function is being called ** as part of populating the temp table for an UPDATE...FROM statement. ** In this case, it is an error if the target object (pSrc->a[0]) name - ** or alias is duplicated within FROM clause (pSrc->a[1..n]). */ - if( p->selFlags & SF_UpdateFrom ){ + ** or alias is duplicated within FROM clause (pSrc->a[1..n]). + ** + ** Postgres disallows this case too. The reason is that some other + ** systems handle this case differently, and not all the same way, + ** which is just confusing. To avoid this, we follow PG's lead and + ** disallow it altogether. */ + if( p->selFlags & SF_UFSrcCheck ){ SrcItem *p0 = &p->pSrc->a[0]; for(i=1; ipSrc->nSrc; i++){ SrcItem *p1 = &p->pSrc->a[i]; @@ -6302,6 +6385,12 @@ int sqlite3Select( goto select_end; } } + + /* Clear the SF_UFSrcCheck flag. The check has already been performed, + ** and leaving this flag set can cause errors if a compound sub-query + ** in p->pSrc is flattened into this query and this function called + ** again as part of compound SELECT processing. */ + p->selFlags &= ~SF_UFSrcCheck; } if( pDest->eDest==SRT_Output ){ @@ -6373,6 +6462,39 @@ int sqlite3Select( if( (pSub->selFlags & SF_Aggregate)!=0 ) continue; assert( pSub->pGroupBy==0 ); + /* If a FROM-clause subquery has an ORDER BY clause that is not + ** really doing anything, then delete it now so that it does not + ** interfere with query flattening. See the discussion at + ** https://sqlite.org/forum/forumpost/2d76f2bcf65d256a + ** + ** Beware of these cases where the ORDER BY clause may not be safely + ** omitted: + ** + ** (1) There is also a LIMIT clause + ** (2) The subquery was added to help with window-function + ** processing + ** (3) The subquery is in the FROM clause of an UPDATE + ** (4) The outer query uses an aggregate function other than + ** the built-in count(), min(), or max(). + ** (5) The ORDER BY isn't going to accomplish anything because + ** one of: + ** (a) The outer query has a different ORDER BY clause + ** (b) The subquery is part of a join + ** See forum post 062d576715d277c8 + */ + if( pSub->pOrderBy!=0 + && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ + && pSub->pLimit==0 /* Condition (1) */ + && (pSub->selFlags & SF_OrderByReqd)==0 /* Condition (2) */ + && (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */ + && OptimizationEnabled(db, SQLITE_OmitOrderBy) + ){ + SELECTTRACE(0x100,pParse,p, + ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); + sqlite3ExprListDelete(db, pSub->pOrderBy); + pSub->pOrderBy = 0; + } + /* If the outer query contains a "complex" result set (that is, ** if the result set of the outer query uses functions or subqueries) ** and if the subquery contains an ORDER BY clause and if @@ -6515,7 +6637,8 @@ int sqlite3Select( ** inside the subquery. This can help the subquery to run more efficiently. */ if( OptimizationEnabled(db, SQLITE_PushDown) - && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) + && (pItem->fg.isCte==0 + || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor, (pItem->fg.jointype & JT_OUTER)!=0) ){ @@ -6576,6 +6699,7 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); if( pItem->iCursor!=pCteUse->iCur ){ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur); + VdbeComment((v, "%!S", pItem)); } pSub->nSelectRow = pCteUse->nRowEst; }else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){ @@ -6913,7 +7037,7 @@ int sqlite3Select( } for(i=0; inFunc; i++){ Expr *pExpr = pAggInfo->aFunc[i].pFExpr; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); sNC.ncFlags |= NC_InAggFunc; sqlite3ExprAnalyzeAggList(&sNC, pExpr->x.pList); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -6968,7 +7092,9 @@ int sqlite3Select( if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 - && pAggInfo->aFunc[0].pFExpr->x.pList + && ALWAYS(pAggInfo->aFunc[0].pFExpr!=0) + && ALWAYS(ExprUseXList(pAggInfo->aFunc[0].pFExpr)) + && pAggInfo->aFunc[0].pFExpr->x.pList!=0 ){ Expr *pExpr = pAggInfo->aFunc[0].pFExpr->x.pList->a[0].pExpr; pExpr = sqlite3ExprDup(db, pExpr, 0); @@ -7289,6 +7415,7 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc); } }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){ + assert( ExprUseXList(pAggInfo->aFunc[0].pFExpr) ); pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; } diff --git a/src/shell.c.in b/src/shell.c.in index 0f8de61a1..543141c9e 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -17,6 +17,18 @@ #define _CRT_SECURE_NO_WARNINGS #endif +/* +** Optionally #include a user-defined header, whereby compilation options +** may be set prior to where they take effect, but after platform setup. +** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include +** file. Note that this macro has a like effect on sqlite3.c compilation. +*/ +#ifdef SQLITE_CUSTOM_INCLUDE +# define INC_STRINGIFY_(f) #f +# define INC_STRINGIFY(f) INC_STRINGIFY_(f) +# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE) +#endif + /* ** Determine if we are dealing with WinRT, which provides only a subset of ** the full Win32 API. @@ -623,19 +635,38 @@ static int strlenChar(const char *z){ } /* -** Return true if zFile does not exist or if it is not an ordinary file. +** Return open FILE * if zFile exists, can be opened for read +** and is an ordinary file or a character stream source. +** Otherwise return 0. */ +static FILE * openChrSource(const char *zFile){ #ifdef _WIN32 -# define notNormalFile(X) 0 + struct _stat x = {0}; +# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) + /* On Windows, open first, then check the stream nature. This order + ** is necessary because _stat() and sibs, when checking a named pipe, + ** effectively break the pipe as its supplier sees it. */ + FILE *rv = fopen(zFile, "rb"); + if( rv==0 ) return 0; + if( _fstat(_fileno(rv), &x) != 0 + || !STAT_CHR_SRC(x.st_mode)){ + fclose(rv); + rv = 0; + } + return rv; #else -static int notNormalFile(const char *zFile){ - struct stat x; - int rc; - memset(&x, 0, sizeof(x)); - rc = stat(zFile, &x); - return rc || !S_ISREG(x.st_mode); -} + struct stat x = {0}; + int rc = stat(zFile, &x); +# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) + if( rc!=0 ) return 0; + if( STAT_CHR_SRC(x.st_mode) ){ + return fopen(zFile, "rb"); + }else{ + return 0; + } #endif +#undef STAT_CHR_SRC +} /* ** This routine reads a line of text from FILE in, stores @@ -825,7 +856,7 @@ static void appendText(ShellText *p, char const *zAppend, char quote){ } } - if( p->n+len>=p->nAlloc ){ + if( p->z==0 || p->n+len>=p->nAlloc ){ p->nAlloc = p->nAlloc*2 + len + 20; p->z = realloc(p->z, p->nAlloc); if( p->z==0 ) shell_out_of_memory(); @@ -971,7 +1002,7 @@ static void shellAddSchemaName( sqlite3 *db = sqlite3_context_db_handle(pCtx); UNUSED_PARAMETER(nVal); if( zIn!=0 && strncmp(zIn, "CREATE ", 7)==0 ){ - for(i=0; i<(int)(sizeof(aPrefix)/sizeof(aPrefix[0])); i++){ + for(i=0; ibSafeMode ){ + va_list ap; + char *zMsg; + va_start(ap, zErrMsg); + zMsg = sqlite3_vmprintf(zErrMsg, ap); + va_end(ap); + raw_printf(stderr, "line %d: ", p->lineno); + utf8_printf(stderr, "%s\n", zMsg); + exit(1); + } +} + /* ** SQL function: edit(VALUE) ** edit(VALUE,EDITOR) @@ -1397,7 +1455,6 @@ static void editFunc( sqlite3_int64 i, j; if( hasCRNL ){ /* If the original contains \r\n then do no conversions back to \n */ - j = sz; }else{ /* If the file did not originally contain \r\n then convert any new ** \r\n back into \n */ @@ -1705,17 +1762,14 @@ static void output_csv(ShellState *p, const char *z, int bSep){ if( z==0 ){ utf8_printf(out,"%s",p->nullValue); }else{ - int i; - int nSep = strlen30(p->colSeparator); + unsigned i; for(i=0; z[i]; i++){ - if( needCsvQuote[((unsigned char*)z)[i]] - || (z[i]==p->colSeparator[0] && - (nSep==1 || memcmp(z, p->colSeparator, nSep)==0)) ){ + if( needCsvQuote[((unsigned char*)z)[i]] ){ i = 0; break; } } - if( i==0 ){ + if( i==0 || strstr(z, p->colSeparator)!=0 ){ char *zQuoted = sqlite3_mprintf("\"%w\"", z); utf8_printf(out, "%s", zQuoted); sqlite3_free(zQuoted); @@ -1754,6 +1808,49 @@ static BOOL WINAPI ConsoleCtrlHandler( #endif #ifndef SQLITE_OMIT_AUTHORIZATION +/* +** This authorizer runs in safe mode. +*/ +static int safeModeAuth( + void *pClientData, + int op, + const char *zA1, + const char *zA2, + const char *zA3, + const char *zA4 +){ + ShellState *p = (ShellState*)pClientData; + static const char *azProhibitedFunctions[] = { + "edit", + "fts3_tokenizer", + "load_extension", + "readfile", + "writefile", + "zipfile", + "zipfile_cds", + }; + UNUSED_PARAMETER(zA2); + UNUSED_PARAMETER(zA3); + UNUSED_PARAMETER(zA4); + switch( op ){ + case SQLITE_ATTACH: { + failIfSafeMode(p, "cannot run ATTACH in safe mode"); + break; + } + case SQLITE_FUNCTION: { + int i; + for(i=0; iout, "\n"); + if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4); return SQLITE_OK; } #endif @@ -2505,17 +2603,16 @@ static int run_table_dump_query( } /* -** Allocate space and save off current error string. +** Allocate space and save off string indicating current error. */ static char *save_err_msg( - sqlite3 *db /* Database to query */ + sqlite3 *db, /* Database to query */ + const char *zWhen, /* Qualifier (format) wrapper */ + int rc /* Error code returned from API */ ){ - int nErrMsg = 1+strlen30(sqlite3_errmsg(db)); - char *zErrMsg = sqlite3_malloc64(nErrMsg); - if( zErrMsg ){ - memcpy(zErrMsg, sqlite3_errmsg(db), nErrMsg); - } - return zErrMsg; + if( zWhen==0 ) + zWhen = "%s (%d)"; + return sqlite3_mprintf(zWhen, sqlite3_errmsg(db), rc); } #ifdef __linux__ @@ -3082,9 +3179,9 @@ static void exec_prepared_stmt_columnar( z = (const char*)sqlite3_column_text(pStmt,i); azData[nRow*nColumn + i] = z ? strdup(z) : 0; } - }while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ); + }while( sqlite3_step(pStmt)==SQLITE_ROW ); if( nColumn>p->nWidth ){ - p->colWidth = realloc(p->colWidth, nColumn*2*sizeof(int)); + p->colWidth = realloc(p->colWidth, (nColumn+1)*2*sizeof(int)); if( p->colWidth==0 ) shell_out_of_memory(); for(i=p->nWidth; icolWidth[i] = 0; p->nWidth = nColumn; @@ -3226,7 +3323,7 @@ static void exec_prepared_stmt( int nCol = sqlite3_column_count(pStmt); void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1); if( !pData ){ - rc = SQLITE_NOMEM; + shell_out_of_memory(); }else{ char **azCols = (char **)pData; /* Names of result columns */ char **azVals = &azCols[nCol]; /* Results */ @@ -3437,7 +3534,7 @@ static int shell_exec( rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); if( SQLITE_OK != rc ){ if( pzErrMsg ){ - *pzErrMsg = save_err_msg(db); + *pzErrMsg = save_err_msg(db, "in prepare, %s (%d)", rc); } }else{ if( !pStmt ){ @@ -3551,7 +3648,7 @@ static int shell_exec( zSql = zLeftover; while( IsSpace(zSql[0]) ) zSql++; }else if( pzErrMsg ){ - *pzErrMsg = save_err_msg(db); + *pzErrMsg = save_err_msg(db, "stepping, %s (%d)", rc); } /* clear saved stmt handle */ @@ -3865,6 +3962,7 @@ static const char *(azHelp[]) = { " -c, --create Create a new archive", " -u, --update Add or update files with changed mtime", " -i, --insert Like -u but always add even if unchanged", + " -r, --remove Remove files from archive", " -t, --list List contents of archive", " -x, --extract Extract files from archive", " Optional arguments:", @@ -3872,6 +3970,7 @@ static const char *(azHelp[]) = { " -f FILE, --file FILE Use archive FILE (default is current db)", " -a FILE, --append FILE Open FILE using the apndvfs VFS", " -C DIR, --directory DIR Read/extract files from directory DIR", + " -g, --glob Use glob matching for names in archive", " -n, --dryrun Show the SQL that would have occurred", " Examples:", " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", @@ -3892,6 +3991,7 @@ static const char *(azHelp[]) = { ".changes on|off Show number of rows changed by SQL", ".check GLOB Fail if output since .testcase does not match", ".clone NEWDB Clone data into NEWDB from the existing database", + ".connection [close] [#] Open or close an auxiliary database connection", ".databases List names and files of attached databases", ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", ".dbinfo ?DB? Show status information about the database", @@ -3968,6 +4068,7 @@ static const char *(azHelp[]) = { " table ASCII-art table", " tabs Tab-separated values", " tcl TCL list elements", + ".nonce STRING Disable safe mode for one command if the nonce matches", ".nullvalue STRING Use STRING in place of NULL values", ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", " If FILE begins with '|' then open as a pipe", @@ -4232,15 +4333,16 @@ static void session_close(OpenSession *pSession){ ** Close all OpenSession objects and release all associated resources. */ #if defined(SQLITE_ENABLE_SESSION) -static void session_close_all(ShellState *p){ - int i; - for(i=0; inSession; i++){ - session_close(&p->aSession[i]); +static void session_close_all(ShellState *p, int i){ + int j; + struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i]; + for(j=0; jnSession; j++){ + session_close(&pAuxDb->aSession[j]); } - p->nSession = 0; + pAuxDb->nSession = 0; } #else -# define session_close_all(X) +# define session_close_all(X,Y) #endif /* @@ -4305,8 +4407,8 @@ int deduceDatabaseType(const char *zName, int dfltZip){ #ifndef SQLITE_OMIT_DESERIALIZE /* ** Reconstruct an in-memory database using the output from the "dbtotxt" -** program. Read content from the file in p->zDbFilename. If p->zDbFilename -** is 0, then read from standard input. +** program. Read content from the file in p->aAuxDb[].zDbFilename. +** If p->aAuxDb[].zDbFilename is 0, then read from standard input. */ static unsigned char *readHexDb(ShellState *p, int *pnData){ unsigned char *a = 0; @@ -4317,12 +4419,13 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){ int j, k; int rc; FILE *in; + const char *zDbFilename = p->pAuxDb->zDbFilename; unsigned int x[16]; char zLine[1000]; - if( p->zDbFilename ){ - in = fopen(p->zDbFilename, "r"); + if( zDbFilename ){ + in = fopen(zDbFilename, "r"); if( in==0 ){ - utf8_printf(stderr, "cannot open \"%s\" for reading\n", p->zDbFilename); + utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename); return 0; } nLine = 0; @@ -4363,7 +4466,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){ &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); if( rc==17 ){ k = iOffset+j; - if( k+16<=n ){ + if( k+16<=n && k>=0 ){ int ii; for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; } @@ -4564,17 +4667,18 @@ static void shellEscapeCrnl( */ static void open_db(ShellState *p, int openFlags){ if( p->db==0 ){ + const char *zDbFilename = p->pAuxDb->zDbFilename; if( p->openMode==SHELL_OPEN_UNSPEC ){ - if( p->zDbFilename==0 || p->zDbFilename[0]==0 ){ + if( zDbFilename==0 || zDbFilename[0]==0 ){ p->openMode = SHELL_OPEN_NORMAL; }else{ - p->openMode = (u8)deduceDatabaseType(p->zDbFilename, + p->openMode = (u8)deduceDatabaseType(zDbFilename, (openFlags & OPEN_DB_ZIPFILE)!=0); } } switch( p->openMode ){ case SHELL_OPEN_APPENDVFS: { - sqlite3_open_v2(p->zDbFilename, &p->db, + sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); break; } @@ -4588,13 +4692,13 @@ static void open_db(ShellState *p, int openFlags){ break; } case SHELL_OPEN_READONLY: { - sqlite3_open_v2(p->zDbFilename, &p->db, + sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READONLY|p->openFlags, 0); break; } case SHELL_OPEN_UNSPEC: case SHELL_OPEN_NORMAL: { - sqlite3_open_v2(p->zDbFilename, &p->db, + sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); break; } @@ -4602,7 +4706,7 @@ static void open_db(ShellState *p, int openFlags){ globalDb = p->db; if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", - p->zDbFilename, sqlite3_errmsg(p->db)); + zDbFilename, sqlite3_errmsg(p->db)); if( openFlags & OPEN_DB_KEEPALIVE ){ sqlite3_open(":memory:", &p->db); return; @@ -4649,7 +4753,7 @@ static void open_db(ShellState *p, int openFlags){ #endif if( p->openMode==SHELL_OPEN_ZIPFILE ){ char *zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", p->zDbFilename); + "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_free(zSql); } @@ -4660,7 +4764,7 @@ static void open_db(ShellState *p, int openFlags){ int nData = 0; unsigned char *aData; if( p->openMode==SHELL_OPEN_DESERIALIZE ){ - aData = (unsigned char*)readFile(p->zDbFilename, &nData); + aData = (unsigned char*)readFile(zDbFilename, &nData); }else{ aData = readHexDb(p, &nData); if( aData==0 ){ @@ -4679,6 +4783,9 @@ static void open_db(ShellState *p, int openFlags){ } #endif } + if( p->bSafeModePersist && p->db!=0 ){ + sqlite3_set_authorizer(p->db, safeModeAuth, p); + } } /* @@ -5276,7 +5383,7 @@ static void tryToCloneSchema( zQuery); goto end_schema_xfer; } - while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ + while( sqlite3_step(pQuery)==SQLITE_ROW ){ zName = sqlite3_column_text(pQuery, 0); zSql = sqlite3_column_text(pQuery, 1); printf("%s... ", zName); fflush(stdout); @@ -5664,8 +5771,7 @@ static void newTempFile(ShellState *p, const char *zSuffix){ p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix); } if( p->zTempFile==0 ){ - raw_printf(stderr, "out of memory\n"); - exit(1); + shell_out_of_memory(); } } @@ -6025,6 +6131,7 @@ struct ArCommand { u8 bZip; /* True if the archive is a ZIP */ u8 bDryRun; /* True if --dry-run */ u8 bAppend; /* True if --append */ + u8 bGlob; /* True if --glob */ u8 fromCmdLine; /* Run from -A instead of .archive */ int nArg; /* Number of command arguments */ char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */ @@ -6072,21 +6179,24 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ #define AR_CMD_EXTRACT 4 #define AR_CMD_LIST 5 #define AR_CMD_HELP 6 +#define AR_CMD_REMOVE 7 /* ** Other (non-command) switches. */ -#define AR_SWITCH_VERBOSE 7 -#define AR_SWITCH_FILE 8 -#define AR_SWITCH_DIRECTORY 9 -#define AR_SWITCH_APPEND 10 -#define AR_SWITCH_DRYRUN 11 +#define AR_SWITCH_VERBOSE 8 +#define AR_SWITCH_FILE 9 +#define AR_SWITCH_DIRECTORY 10 +#define AR_SWITCH_APPEND 11 +#define AR_SWITCH_DRYRUN 12 +#define AR_SWITCH_GLOB 13 static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ switch( eSwitch ){ case AR_CMD_CREATE: case AR_CMD_EXTRACT: case AR_CMD_LIST: + case AR_CMD_REMOVE: case AR_CMD_UPDATE: case AR_CMD_INSERT: case AR_CMD_HELP: @@ -6099,6 +6209,9 @@ static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ case AR_SWITCH_DRYRUN: pAr->bDryRun = 1; break; + case AR_SWITCH_GLOB: + pAr->bGlob = 1; + break; case AR_SWITCH_VERBOSE: pAr->bVerbose = 1; break; @@ -6137,6 +6250,7 @@ static int arParseCommand( { "extract", 'x', AR_CMD_EXTRACT, 0 }, { "insert", 'i', AR_CMD_INSERT, 0 }, { "list", 't', AR_CMD_LIST, 0 }, + { "remove", 'r', AR_CMD_REMOVE, 0 }, { "update", 'u', AR_CMD_UPDATE, 0 }, { "help", 'h', AR_CMD_HELP, 0 }, { "verbose", 'v', AR_SWITCH_VERBOSE, 0 }, @@ -6144,6 +6258,7 @@ static int arParseCommand( { "append", 'a', AR_SWITCH_APPEND, 1 }, { "directory", 'C', AR_SWITCH_DIRECTORY, 1 }, { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 }, + { "glob", 'g', AR_SWITCH_GLOB, 0 }, }; int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch); struct ArSwitch *pEnd = &aSwitch[nSwitch]; @@ -6260,11 +6375,13 @@ static int arParseCommand( /* ** This function assumes that all arguments within the ArCommand.azArg[] -** array refer to archive members, as for the --extract or --list commands. -** It checks that each of them are present. If any specified file is not -** present in the archive, an error is printed to stderr and an error -** code returned. Otherwise, if all specified arguments are present in -** the archive, SQLITE_OK is returned. +** array refer to archive members, as for the --extract, --list or --remove +** commands. It checks that each of them are "present". If any specified +** file is not present in the archive, an error is printed to stderr and an +** error code returned. Otherwise, if all specified arguments are present +** in the archive, SQLITE_OK is returned. Here, "present" means either an +** exact equality when pAr->bGlob is false or a "name GLOB pattern" match +** when pAr->bGlob is true. ** ** This function strips any trailing '/' characters from each argument. ** This is consistent with the way the [tar] command seems to work on @@ -6275,11 +6392,11 @@ static int arCheckEntries(ArCommand *pAr){ if( pAr->nArg ){ int i, j; sqlite3_stmt *pTest = 0; + const char *zSel = (pAr->bGlob) + ? "SELECT name FROM %s WHERE glob($name,name)" + : "SELECT name FROM %s WHERE name=$name"; - shellPreparePrintf(pAr->db, &rc, &pTest, - "SELECT name FROM %s WHERE name=$name", - pAr->zSrcTable - ); + shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable); j = sqlite3_bind_parameter_index(pTest, "$name"); for(i=0; inArg && rc==SQLITE_OK; i++){ char *z = pAr->azArg[i]; @@ -6307,14 +6424,16 @@ static int arCheckEntries(ArCommand *pAr){ ** identify all archive members that match the command arguments held ** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning. ** The caller is responsible for eventually calling sqlite3_free() on -** any non-NULL (*pzWhere) value. +** any non-NULL (*pzWhere) value. Here, "match" means strict equality +** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. */ static void arWhereClause( int *pRc, - ArCommand *pAr, + ArCommand *pAr, char **pzWhere /* OUT: New WHERE clause */ ){ char *zWhere = 0; + const char *zSameOp = (pAr->bGlob)? "GLOB" : "="; if( *pRc==SQLITE_OK ){ if( pAr->nArg==0 ){ zWhere = sqlite3_mprintf("1"); @@ -6324,8 +6443,8 @@ static void arWhereClause( for(i=0; inArg; i++){ const char *z = pAr->azArg[i]; zWhere = sqlite3_mprintf( - "%z%s name = '%q' OR substr(name,1,%d) = '%q/'", - zWhere, zSep, z, strlen30(z)+1, z + "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", + zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z ); if( zWhere==0 ){ *pRc = SQLITE_NOMEM; @@ -6379,6 +6498,47 @@ static int arListCommand(ArCommand *pAr){ } +/* +** Implementation of .ar "Remove" command. +*/ +static int arRemoveCommand(ArCommand *pAr){ + int rc = 0; + char *zSql = 0; + char *zWhere = 0; + + if( pAr->nArg ){ + /* Verify that args actually exist within the archive before proceeding. + ** And formulate a WHERE clause to match them. */ + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + } + if( rc==SQLITE_OK ){ + zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", + pAr->zSrcTable, zWhere); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", zSql); + }else{ + char *zErr = 0; + rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); + if( rc!=SQLITE_OK ){ + sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); + }else{ + rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0); + } + } + if( zErr ){ + utf8_printf(stdout, "ERROR: %s\n", zErr); + sqlite3_free(zErr); + } + } + } + sqlite3_free(zWhere); + sqlite3_free(zSql); + return rc; +} + /* ** Implementation of .ar "eXtract" command. */ @@ -6631,7 +6791,7 @@ static int arDotCommand( int flags; if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT - || cmd.eCmd==AR_CMD_UPDATE ){ + || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){ flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; }else{ flags = SQLITE_OPEN_READONLY; @@ -6687,6 +6847,10 @@ static int arDotCommand( rc = arCreateOrUpdateCommand(&cmd, 1, 0); break; + case AR_CMD_REMOVE: + rc = arRemoveCommand(&cmd); + break; + default: assert( cmd.eCmd==AR_CMD_UPDATE ); rc = arCreateOrUpdateCommand(&cmd, 1, 1); @@ -7381,7 +7545,6 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ } #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ - /* ** If an input line begins with "." then invoke this routine to ** process that line. @@ -7443,6 +7606,8 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); if( booleanValue(azArg[1]) ){ sqlite3_set_authorizer(p->db, shellAuth, p); + }else if( p->bSafeModePersist ){ + sqlite3_set_authorizer(p->db, safeModeAuth, p); }else{ sqlite3_set_authorizer(p->db, 0, 0); } @@ -7452,6 +7617,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){ open_db(p, 0); + failIfSafeMode(p, "cannot run .archive in safe mode"); rc = arDotCommand(p, 0, azArg, nArg); }else #endif @@ -7466,6 +7632,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int j; int bAsync = 0; const char *zVfs = 0; + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); for(j=1; j=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ + test_breakpoint(); + }else + if( c=='c' && strcmp(azArg[0],"cd")==0 ){ + failIfSafeMode(p, "cannot run .cd in safe mode"); if( nArg==2 ){ #if defined(_WIN32) || defined(WIN32) wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); @@ -7565,13 +7740,6 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - /* The undocumented ".breakpoint" command causes a call to the no-op - ** routine named test_breakpoint(). - */ - if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ - test_breakpoint(); - }else - if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){ if( nArg==2 ){ setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); @@ -7607,6 +7775,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ + failIfSafeMode(p, "cannot run .clone in safe mode"); if( nArg==2 ){ tryToClone(p, azArg[1]); }else{ @@ -7615,6 +7784,52 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else + if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){ + if( nArg==1 ){ + /* List available connections */ + int i; + for(i=0; iaAuxDb); i++){ + const char *zFile = p->aAuxDb[i].zDbFilename; + if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){ + zFile = "(not open)"; + }else if( zFile==0 ){ + zFile = "(memory)"; + }else if( zFile[0]==0 ){ + zFile = "(temporary-file)"; + } + if( p->pAuxDb == &p->aAuxDb[i] ){ + utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile); + }else if( p->aAuxDb[i].db!=0 ){ + utf8_printf(stdout, " %d: %s\n", i, zFile); + } + } + }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ + int i = azArg[1][0] - '0'; + if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && iaAuxDb) ){ + p->pAuxDb->db = p->db; + p->pAuxDb = &p->aAuxDb[i]; + globalDb = p->db = p->pAuxDb->db; + p->pAuxDb->db = 0; + } + }else if( nArg==3 && strcmp(azArg[1], "close")==0 + && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){ + int i = azArg[2][0] - '0'; + if( i<0 || i>=ArraySize(p->aAuxDb) ){ + /* No-op */ + }else if( p->pAuxDb == &p->aAuxDb[i] ){ + raw_printf(stderr, "cannot close the active database connection\n"); + rc = 1; + }else if( p->aAuxDb[i].db ){ + session_close_all(p, i); + close_db(p->aAuxDb[i].db); + p->aAuxDb[i].db = 0; + } + }else{ + raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); + rc = 1; + } + }else + if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){ char **azName = 0; int nName = 0; @@ -8060,7 +8275,7 @@ static int do_meta_command(char *zLine, ShellState *p){ " FROM sqlite_schema UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " - "ORDER BY rowid", + "ORDER BY x", callback, &data, 0 ); if( rc==SQLITE_OK ){ @@ -8076,8 +8291,6 @@ static int do_meta_command(char *zLine, ShellState *p){ raw_printf(p->out, "/* No STAT tables available */\n"); }else{ raw_printf(p->out, "ANALYZE sqlite_schema;\n"); - sqlite3_exec(p->db, "SELECT 'ANALYZE sqlite_schema'", - callback, &data, 0); data.cMode = data.mode = MODE_Insert; data.zDestTable = "sqlite_stat1"; shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); @@ -8124,7 +8337,13 @@ static int do_meta_command(char *zLine, ShellState *p){ int nSkip = 0; /* Initial lines to skip */ int useOutputMode = 1; /* Use output mode to determine separators */ + failIfSafeMode(p, "cannot run .import in safe mode"); memset(&sCtx, 0, sizeof(sCtx)); + sCtx.z = sqlite3_malloc64(120); + if( sCtx.z==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } if( p->mode==MODE_Ascii ){ xRead = ascii_read_one_field; }else{ @@ -8234,6 +8453,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( sCtx.in==0 ){ utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); rc = 1; + import_cleanup(&sCtx); goto meta_command_exit; } if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ @@ -8430,7 +8650,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); i = 0; - while( sqlite3_step(pStmt)==SQLITE_ROW ){ + while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ char zLabel[20]; const char *zCol = (const char*)sqlite3_column_text(pStmt,2); i++; @@ -8575,6 +8795,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ const char *zFile, *zProc; char *zErrMsg = 0; + failIfSafeMode(p, "cannot run .load in safe mode"); if( nArg<2 ){ raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); rc = 1; @@ -8593,6 +8814,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif if( c=='l' && strncmp(azArg[0], "log", n)==0 ){ + failIfSafeMode(p, "cannot run .log in safe mode"); if( nArg!=2 ){ raw_printf(stderr, "Usage: .log FILENAME\n"); rc = 1; @@ -8663,6 +8885,20 @@ static int do_meta_command(char *zLine, ShellState *p){ p->cMode = p->mode; }else + if( c=='n' && strcmp(azArg[0], "nonce")==0 ){ + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .nonce NONCE\n"); + rc = 1; + }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){ + raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", p->lineno, azArg[1]); + exit(1); + }else{ + p->bSafeMode = 0; + return 0; /* Return immediately to bypass the safe mode reset + ** at the end of this procedure */ + } + }else + if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ if( nArg==2 ){ sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, @@ -8705,16 +8941,8 @@ static int do_meta_command(char *zLine, ShellState *p){ char *zNewFilename = 0; /* Name of the database file to open */ int iName = 1; /* Index in azArg[] of the filename */ int newFlag = 0; /* True to delete file before opening */ - /* Close the existing database */ - session_close_all(p); - close_db(p->db); - p->db = 0; - p->zDbFilename = 0; - sqlite3_free(p->zFreeOnClose); - p->zFreeOnClose = 0; - p->openMode = SHELL_OPEN_UNSPEC; - p->openFlags = 0; - p->szMax = 0; + int openMode = SHELL_OPEN_UNSPEC; + /* Check for command-line arguments */ for(iName=1; iNameopenMode = SHELL_OPEN_ZIPFILE; + openMode = SHELL_OPEN_ZIPFILE; #endif }else if( optionMatch(z, "append") ){ - p->openMode = SHELL_OPEN_APPENDVFS; + openMode = SHELL_OPEN_APPENDVFS; }else if( optionMatch(z, "readonly") ){ - p->openMode = SHELL_OPEN_READONLY; + openMode = SHELL_OPEN_READONLY; }else if( optionMatch(z, "nofollow") ){ p->openFlags |= SQLITE_OPEN_NOFOLLOW; #ifndef SQLITE_OMIT_DESERIALIZE }else if( optionMatch(z, "deserialize") ){ - p->openMode = SHELL_OPEN_DESERIALIZE; + openMode = SHELL_OPEN_DESERIALIZE; }else if( optionMatch(z, "hexdb") ){ - p->openMode = SHELL_OPEN_HEXDB; + openMode = SHELL_OPEN_HEXDB; }else if( optionMatch(z, "maxsize") && iName+1szMax = integerValue(azArg[++iName]); #endif /* SQLITE_OMIT_DESERIALIZE */ @@ -8750,21 +8978,40 @@ static int do_meta_command(char *zLine, ShellState *p){ zNewFilename = sqlite3_mprintf("%s", z); } } + + /* Close the existing database */ + session_close_all(p, -1); + close_db(p->db); + p->db = 0; + p->pAuxDb->zDbFilename = 0; + sqlite3_free(p->pAuxDb->zFreeOnClose); + p->pAuxDb->zFreeOnClose = 0; + p->openMode = openMode; + p->openFlags = 0; + p->szMax = 0; + /* If a filename is specified, try to open it first */ if( zNewFilename || p->openMode==SHELL_OPEN_HEXDB ){ - if( newFlag ) shellDeleteFile(zNewFilename); - p->zDbFilename = zNewFilename; + if( newFlag && !p->bSafeMode ) shellDeleteFile(zNewFilename); + if( p->bSafeMode + && p->openMode!=SHELL_OPEN_HEXDB + && zNewFilename + && strcmp(zNewFilename,":memory:")!=0 + ){ + failIfSafeMode(p, "cannot open disk-based database files in safe mode"); + } + p->pAuxDb->zDbFilename = zNewFilename; open_db(p, OPEN_DB_KEEPALIVE); if( p->db==0 ){ utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); sqlite3_free(zNewFilename); }else{ - p->zFreeOnClose = zNewFilename; + p->pAuxDb->zFreeOnClose = zNewFilename; } } if( p->db==0 ){ /* As a fall-back open a TEMP database */ - p->zDbFilename = 0; + p->pAuxDb->zDbFilename = 0; open_db(p, 0); } }else @@ -8780,6 +9027,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int bBOM = 0; int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); if( c=='e' ){ eMode = 'x'; bOnce = 2; @@ -8909,7 +9157,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rx = sqlite3_prepare_v2(p->db, "SELECT key, quote(value) " "FROM temp.sqlite_parameters;", -1, &pStmt, 0); - while( sqlite3_step(pStmt)==SQLITE_ROW ){ + while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), sqlite3_column_text(pStmt,1)); } @@ -9052,6 +9300,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){ FILE *inSaved = p->in; int savedLineno = p->lineno; + failIfSafeMode(p, "cannot run .read in safe mode"); if( nArg!=2 ){ raw_printf(stderr, "Usage: .read FILE\n"); rc = 1; @@ -9072,7 +9321,7 @@ static int do_meta_command(char *zLine, ShellState *p){ pclose(p->in); } #endif - }else if( notNormalFile(azArg[1]) || (p->in = fopen(azArg[1], "rb"))==0 ){ + }else if( (p->in = openChrSource(azArg[1]))==0 ){ utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ @@ -9090,6 +9339,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_backup *pBackup; int nTimeout = 0; + failIfSafeMode(p, "cannot run .restore in safe mode"); if( nArg==2 ){ zSrcFile = azArg[1]; zDb = "main"; @@ -9293,7 +9543,8 @@ static int do_meta_command(char *zLine, ShellState *p){ #if defined(SQLITE_ENABLE_SESSION) if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){ - OpenSession *pSession = &p->aSession[0]; + struct AuxDb *pAuxDb = p->pAuxDb; + OpenSession *pSession = &pAuxDb->aSession[0]; char **azCmd = &azArg[1]; int iSes = 0; int nCmd = nArg - 1; @@ -9301,15 +9552,15 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg<=1 ) goto session_syntax_error; open_db(p, 0); if( nArg>=3 ){ - for(iSes=0; iSesnSession; iSes++){ - if( strcmp(p->aSession[iSes].zName, azArg[1])==0 ) break; + for(iSes=0; iSesnSession; iSes++){ + if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; } - if( iSesnSession ){ - pSession = &p->aSession[iSes]; + if( iSesnSession ){ + pSession = &pAuxDb->aSession[iSes]; azCmd++; nCmd--; }else{ - pSession = &p->aSession[0]; + pSession = &pAuxDb->aSession[0]; iSes = 0; } } @@ -9338,6 +9589,7 @@ static int do_meta_command(char *zLine, ShellState *p){ */ if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){ FILE *out = 0; + failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]); if( nCmd!=2 ) goto session_syntax_error; if( pSession->p==0 ) goto session_not_open; out = fopen(azCmd[1], "wb"); @@ -9371,9 +9623,9 @@ static int do_meta_command(char *zLine, ShellState *p){ */ if( strcmp(azCmd[0], "close")==0 ){ if( nCmd!=1 ) goto session_syntax_error; - if( p->nSession ){ + if( pAuxDb->nSession ){ session_close(pSession); - p->aSession[iSes] = p->aSession[--p->nSession]; + pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession]; } }else @@ -9384,7 +9636,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int ii; if( nCmd>2 ) goto session_syntax_error; ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); - if( p->nSession ){ + if( pAuxDb->nSession ){ ii = sqlite3session_enable(pSession->p, ii); utf8_printf(p->out, "session %s enable flag = %d\n", pSession->zName, ii); @@ -9397,7 +9649,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( strcmp(azCmd[0], "filter")==0 ){ int ii, nByte; if( nCmd<2 ) goto session_syntax_error; - if( p->nSession ){ + if( pAuxDb->nSession ){ for(ii=0; iinFilter; ii++){ sqlite3_free(pSession->azFilter[ii]); } @@ -9422,7 +9674,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int ii; if( nCmd>2 ) goto session_syntax_error; ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); - if( p->nSession ){ + if( pAuxDb->nSession ){ ii = sqlite3session_indirect(pSession->p, ii); utf8_printf(p->out, "session %s indirect flag = %d\n", pSession->zName, ii); @@ -9435,7 +9687,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( strcmp(azCmd[0], "isempty")==0 ){ int ii; if( nCmd!=1 ) goto session_syntax_error; - if( p->nSession ){ + if( pAuxDb->nSession ){ ii = sqlite3session_isempty(pSession->p); utf8_printf(p->out, "session %s isempty flag = %d\n", pSession->zName, ii); @@ -9446,8 +9698,8 @@ static int do_meta_command(char *zLine, ShellState *p){ ** List all currently open sessions */ if( strcmp(azCmd[0],"list")==0 ){ - for(i=0; inSession; i++){ - utf8_printf(p->out, "%d %s\n", i, p->aSession[i].zName); + for(i=0; inSession; i++){ + utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); } }else @@ -9460,17 +9712,17 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nCmd!=3 ) goto session_syntax_error; zName = azCmd[2]; if( zName[0]==0 ) goto session_syntax_error; - for(i=0; inSession; i++){ - if( strcmp(p->aSession[i].zName,zName)==0 ){ + for(i=0; inSession; i++){ + if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ utf8_printf(stderr, "Session \"%s\" already exists\n", zName); goto meta_command_exit; } } - if( p->nSession>=ArraySize(p->aSession) ){ - raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(p->aSession)); + if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ + raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); goto meta_command_exit; } - pSession = &p->aSession[p->nSession]; + pSession = &pAuxDb->aSession[pAuxDb->nSession]; rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); if( rc ){ raw_printf(stderr, "Cannot open session: error code=%d\n", rc); @@ -9479,7 +9731,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } pSession->nFilter = 0; sqlite3session_table_filter(pSession->p, session_filter, pSession); - p->nSession++; + pAuxDb->nSession++; pSession->zName = sqlite3_mprintf("%s", zName); }else /* If no command name matches, show a syntax error */ @@ -9752,6 +10004,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ){ char *zCmd; int i, x; + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); if( nArg<2 ){ raw_printf(stderr, "Usage: .system COMMAND\n"); rc = 1; @@ -9808,7 +10061,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } raw_printf(p->out, "\n"); utf8_printf(p->out, "%12.12s: %s\n", "filename", - p->zDbFilename ? p->zDbFilename : ""); + p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); }else if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ @@ -9877,8 +10130,10 @@ static int do_meta_command(char *zLine, ShellState *p){ } } rc = sqlite3_finalize(pStmt); - appendText(&s, " ORDER BY 1", 0); - rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + appendText(&s, " ORDER BY 1", 0); + rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0); + } freeText(&s); if( rc ) return shellDatabaseError(p->db); @@ -9953,29 +10208,31 @@ static int do_meta_command(char *zLine, ShellState *p){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ + int unSafe; /* Not valid for --safe mode */ const char *zUsage; /* Usage notes */ } aCtrl[] = { - { "always", SQLITE_TESTCTRL_ALWAYS, "BOOLEAN" }, - { "assert", SQLITE_TESTCTRL_ASSERT, "BOOLEAN" }, - /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, "" },*/ - /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, "" },*/ - { "byteorder", SQLITE_TESTCTRL_BYTEORDER, "" }, - { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,"BOOLEAN" }, - /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, "" },*/ - { "imposter", SQLITE_TESTCTRL_IMPOSTER, "SCHEMA ON/OFF ROOTPAGE"}, - { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, "" }, - { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,"BOOLEAN" }, - { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT, "BOOLEAN" }, - { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS, "DISABLE-MASK" }, + { "always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, + { "assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" }, + /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/ + /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ + { "byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, + { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, + /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ + { "imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, + { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, + { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, + { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, + { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, #ifdef YYCOVERAGE - { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE, "" }, -#endif - { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE, "OFFSET " }, - { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE, "" }, - { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, "" }, - { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, "SEED ?db?" }, - { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, "" }, - { "tune", SQLITE_TESTCTRL_TUNE, "ID VALUE" }, + { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" }, +#endif + { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " }, + { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" }, + { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" }, + { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" }, + { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, + { "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, + { "tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, }; int testctrl = -1; int iCtrl = -1; @@ -10023,6 +10280,11 @@ static int do_meta_command(char *zLine, ShellState *p){ if( testctrl<0 ){ utf8_printf(stderr,"Error: unknown test-control: %s\n" "Use \".testctrl --help\" for help\n", zCmd); + }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){ + utf8_printf(stderr, + "line %d: \".testctrl %s\" may not be used in safe mode\n", + p->lineno, aCtrl[iCtrl].zCtrlName); + exit(1); }else{ switch(testctrl){ @@ -10155,6 +10417,13 @@ static int do_meta_command(char *zLine, ShellState *p){ break; } #endif + case SQLITE_TESTCTRL_SORTER_MMAP: + if( nArg==3 ){ + int opt = (unsigned int)integerValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, p->db, opt); + isOk = 3; + } + break; } } if( isOk==0 && iCtrl>=0 ){ @@ -10396,7 +10665,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int j; assert( nArg<=ArraySize(azArg) ); p->nWidth = nArg-1; - p->colWidth = realloc(p->colWidth, p->nWidth*sizeof(int)*2); + p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; for(j=1; joutCount--; if( p->outCount==0 ) output_reset(p); } + p->bSafeMode = p->bSafeModePersist; return rc; } -/* -** Return TRUE if a semicolon occurs anywhere in the first N characters -** of string z[]. +/* Line scan result and intermediate states (supporting scan resumption) */ -static int line_contains_semicolon(const char *z, int N){ - int i; - for(i=0; iout, "changes: %3d total_changes: %d\n", - sqlite3_changes(p->db), sqlite3_total_changes(p->db)); + char zLineBuf[2000]; + sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, + "changes: %lld total_changes: %lld", + sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); + raw_printf(p->out, "%s\n", zLineBuf); } return 0; } @@ -10545,10 +10874,10 @@ static int process_input(ShellState *p){ int nLine; /* Length of current line */ int nSql = 0; /* Bytes of zSql[] used */ int nAlloc = 0; /* Allocated zSql[] space */ - int nSqlPrior = 0; /* Bytes of zSql[] used by prior line */ int rc; /* Error code */ int errCnt = 0; /* Number of errors seen */ int startline = 0; /* Line number for start of current input */ + QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ p->lineno = 0; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ @@ -10564,8 +10893,17 @@ static int process_input(ShellState *p){ seenInterrupt = 0; } p->lineno++; - if( nSql==0 && _all_whitespace(zLine) ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine); + if( QSS_INPLAIN(qss) + && line_is_command_terminator(zLine) + && line_is_complete(zSql, nSql) ){ + memcpy(zLine,";",2); + } + qss = quickscan(zLine, qss); + if( QSS_PLAINWHITE(qss) && nSql==0 ){ + if( ShellHasFlag(p, SHFLG_Echo) ) + printf("%s\n", zLine); + /* Just swallow single-line whitespace */ + qss = QSS_Start; continue; } if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ @@ -10578,18 +10916,17 @@ static int process_input(ShellState *p){ errCnt++; } } + qss = QSS_Start; continue; } - if( line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){ - memcpy(zLine,";",2); - } + /* No single-line dispositions remain; accumulate line(s). */ nLine = strlen30(zLine); if( nSql+nLine+2>=nAlloc ){ - nAlloc = nSql+nLine+100; + /* Grow buffer by half-again increments when big. */ + nAlloc = nSql+(nSql>>1)+nLine+100; zSql = realloc(zSql, nAlloc); if( zSql==0 ) shell_out_of_memory(); } - nSqlPrior = nSql; if( nSql==0 ){ int i; for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} @@ -10602,8 +10939,7 @@ static int process_input(ShellState *p){ memcpy(zSql+nSql, zLine, nLine+1); nSql += nLine; } - if( nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior) - && sqlite3_complete(zSql) ){ + if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ errCnt += runOneSqlLine(p, zSql, p->in, startline); nSql = 0; if( p->outCount ){ @@ -10612,12 +10948,15 @@ static int process_input(ShellState *p){ }else{ clearTempFile(p); } - }else if( nSql && _all_whitespace(zSql) ){ + p->bSafeMode = p->bSafeModePersist; + qss = QSS_Start; + }else if( nSql && QSS_PLAINWHITE(qss) ){ if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql); nSql = 0; + qss = QSS_Start; } } - if( nSql && !_all_whitespace(zSql) ){ + if( nSql && QSS_PLAINDARK(qss) ){ errCnt += runOneSqlLine(p, zSql, p->in, startline); } free(zSql); @@ -10778,10 +11117,12 @@ static const char zOptions[] = #endif " -newline SEP set output row separator. Default: '\\n'\n" " -nofollow refuse to open symbolic links to database files\n" + " -nonce STRING set the safe-mode escape nonce\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" + " -safe enable safe-mode\n" " -separator SEP set output column separator. Default: '|'\n" #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" @@ -10829,6 +11170,7 @@ static void main_init(ShellState *data) { memset(data, 0, sizeof(*data)); data->normalMode = data->cMode = data->mode = MODE_List; data->autoExplain = 1; + data->pAuxDb = &data->aAuxDb[0]; memcpy(data->colSeparator,SEP_Column, 2); memcpy(data->rowSeparator,SEP_Row, 2); data->showHeader = 0; @@ -10992,7 +11334,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ ** this compile-time option to embed this shell program in larger ** applications. */ extern void SQLITE_SHELL_DBNAME_PROC(const char**); - SQLITE_SHELL_DBNAME_PROC(&data.zDbFilename); + SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename); warnInmemoryDb = 0; } #endif @@ -11007,8 +11349,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ char *z; z = argv[i]; if( z[0]!='-' ){ - if( data.zDbFilename==0 ){ - data.zDbFilename = z; + if( data.aAuxDb->zDbFilename==0 ){ + data.aAuxDb->zDbFilename = z; }else{ /* Excesss arguments are interpreted as SQL (or dot-commands) and ** mean that nothing is read from stdin */ @@ -11066,6 +11408,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( n<0 ) n = 0; sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; + }else if( strcmp(z,"-threadsafe")==0 ){ + int n; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + switch( n ){ + case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; + case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; + default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; + } #ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(z,"-vfstrace")==0 ){ extern int vfstrace_register( @@ -11118,6 +11468,11 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ sqlite3MemTraceActivate(stderr); }else if( strcmp(z,"-bail")==0 ){ bail_on_error = 1; + }else if( strcmp(z,"-nonce")==0 ){ + free(data.zNonce); + data.zNonce = strdup(argv[++i]); + }else if( strcmp(z,"-safe")==0 ){ + /* no-op - catch this on the second pass */ } } verify_uninitialized(); @@ -11148,9 +11503,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } } - if( data.zDbFilename==0 ){ + if( data.pAuxDb->zDbFilename==0 ){ #ifndef SQLITE_OMIT_MEMORYDB - data.zDbFilename = ":memory:"; + data.pAuxDb->zDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0); @@ -11165,7 +11520,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ ** files from being created if a user mistypes the database name argument ** to the sqlite command-line tool. */ - if( access(data.zDbFilename, 0)==0 ){ + if( access(data.pAuxDb->zDbFilename, 0)==0 ){ open_db(&data, 0); } @@ -11244,8 +11599,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ "%s",cmdline_option_value(argc,argv,++i)); }else if( strcmp(z,"-header")==0 ){ data.showHeader = 1; - }else if( strcmp(z,"-noheader")==0 ){ + ShellSetFlag(&data, SHFLG_HeaderSet); + }else if( strcmp(z,"-noheader")==0 ){ data.showHeader = 0; + ShellSetFlag(&data, SHFLG_HeaderSet); }else if( strcmp(z,"-echo")==0 ){ ShellSetFlag(&data, SHFLG_Echo); }else if( strcmp(z,"-eqp")==0 ){ @@ -11278,6 +11635,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ i+=2; }else if( strcmp(z,"-lookaside")==0 ){ i+=2; + }else if( strcmp(z,"-threadsafe")==0 ){ + i+=2; + }else if( strcmp(z,"-nonce")==0 ){ + i += 2; }else if( strcmp(z,"-mmap")==0 ){ i++; }else if( strcmp(z,"-memtrace")==0 ){ @@ -11336,6 +11697,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ readStdin = 0; break; #endif + }else if( strcmp(z,"-safe")==0 ){ + data.bSafeMode = data.bSafeModePersist = 1; }else{ utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); raw_printf(stderr,"Use -help for a list of options.\n"); @@ -11419,10 +11782,16 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ free(azCmd); set_table_name(&data, 0); if( data.db ){ - session_close_all(&data); + session_close_all(&data, -1); close_db(data.db); } - sqlite3_free(data.zFreeOnClose); + for(i=0; i */ int sqlite3_changes(sqlite3*); +sqlite3_int64 sqlite3_changes64(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the total number of rows inserted, modified or +** ^These functions return the total number of rows inserted, modified or ** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed ** since the database connection was opened, including those executed as -** part of trigger programs. ^Executing any other type of SQL statement -** does not affect the value returned by sqlite3_total_changes(). +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the +** connection exceeds the maximum value supported by type "int", then +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by +** sqlite3_total_changes(). ** ** ^Changes made as part of [foreign key actions] are included in the ** count, but those made as part of REPLACE constraint resolution are @@ -2554,6 +2600,7 @@ int sqlite3_changes(sqlite3*); ** */ int sqlite3_total_changes(sqlite3*); +sqlite3_int64 sqlite3_total_changes64(sqlite3*); /* ** CAPI3REF: Interrupt A Long-Running Query @@ -3383,6 +3430,14 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** +** [[OPEN_EXRESCODE]] ^(
      [SQLITE_OPEN_EXRESCODE]
      +**
      The database connection comes up in "extended result code mode". +** In other words, the database behaves has if +** [sqlite3_extended_result_codes(db,1)] where called on the database +** connection as soon as the connection is created. In addition to setting +** the extended result code mode, this flag also causes [sqlite3_open_v2()] +** to return an extended result code.
      +** ** [[OPEN_NOFOLLOW]] ^(
      [SQLITE_OPEN_NOFOLLOW]
      **
      The database filename is not allowed to be a symbolic link
      ** )^ @@ -3390,7 +3445,15 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** If the 3rd parameter to sqlite3_open_v2() is not one of the ** required combinations shown above optionally combined with other ** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] -** then the behavior is undefined. +** then the behavior is undefined. Historic versions of SQLite +** have silently ignored surplus bits in the flags parameter to +** sqlite3_open_v2(), however that behavior might not be carried through +** into future versions of SQLite and so applications should not rely +** upon it. Note in particular that the SQLITE_OPEN_EXCLUSIVE flag is a no-op +** for sqlite3_open_v2(). The SQLITE_OPEN_EXCLUSIVE does *not* cause +** the open to fail if the database already exists. The SQLITE_OPEN_EXCLUSIVE +** flag is intended for use by the [sqlite3_vfs|VFS interface] only, and not +** by sqlite3_open_v2(). ** ** ^The fourth parameter to sqlite3_open_v2() is the name of the ** [sqlite3_vfs] object that defines the operating system interface that @@ -4158,12 +4221,17 @@ int sqlite3_prepare16_v3( ** are managed by SQLite and are automatically freed when the prepared ** statement is finalized. ** ^The string returned by sqlite3_expanded_sql(P), on the other hand, -** is obtained from [sqlite3_malloc()] and must be free by the application +** is obtained from [sqlite3_malloc()] and must be freed by the application ** by passing it to [sqlite3_free()]. +** +** ^The sqlite3_normalized_sql() interface is only available if +** the [SQLITE_ENABLE_NORMALIZE] compile-time option is defined. */ const char *sqlite3_sql(sqlite3_stmt *pStmt); char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); +#ifdef SQLITE_ENABLE_NORMALIZE const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); +#endif /* ** CAPI3REF: Determine If An SQL Statement Writes The Database @@ -6347,6 +6415,72 @@ sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +**

      ^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +**

      The callback is not reentrant. The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +**

      ^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is cancelled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +**

      If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +**

      +**     unsigned int demonstration_autovac_pages_callback(
      +**       void *pClientData,
      +**       const char *zSchema,
      +**       unsigned int nDbPage,
      +**       unsigned int nFreePage,
      +**       unsigned int nBytePerPage
      +**     ){
      +**       return nFreePage;
      +**     }
      +** 
      +*/ +int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + /* ** CAPI3REF: Data Change Notification Callbacks ** METHOD: sqlite3 @@ -9010,8 +9144,9 @@ void sqlite3_log(int iErrCode, const char *zFormat, ...); ** ** A single database handle may have at most a single write-ahead log callback ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^Note that the -** [sqlite3_wal_autocheckpoint()] interface and the +** previously registered write-ahead log callback. ^The return value is +** a copy of the third parameter from the previous call, if any, or 0. +** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will ** overwrite any prior [sqlite3_wal_hook()] settings. */ @@ -9878,6 +10013,10 @@ unsigned char *sqlite3_serialize( ** database is currently in a read transaction or is involved in a backup ** operation. ** +** It is not possible to deserialized into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** ** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 217601fd9..9767daa01 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -337,6 +337,13 @@ struct sqlite3_api_routines { sqlite3_file *(*database_file_object)(const char*); /* Version 3.34.0 and later */ int (*txn_state)(sqlite3*,const char*); + /* Version 3.36.1 and later */ + sqlite3_int64 (*changes64)(sqlite3*); + sqlite3_int64 (*total_changes64)(sqlite3*); + /* Version 3.37.0 and later */ + int (*autovacuum_pages)(sqlite3*, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, void(*)(void*)); }; /* @@ -643,6 +650,11 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_database_file_object sqlite3_api->database_file_object /* Version 3.34.0 and later */ #define sqlite3_txn_state sqlite3_api->txn_state +/* Version 3.36.1 and later */ +#define sqlite3_changes64 sqlite3_api->changes64 +#define sqlite3_total_changes64 sqlite3_api->total_changes64 +/* Version 3.37.0 and later */ +#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 91af412bb..12534c6f2 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -181,6 +181,17 @@ # define _USE_32BIT_TIME_T #endif +/* Optionally #include a user-defined header, whereby compilation options +** may be set prior to where they take effect, but after platform setup. +** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include +** file. +*/ +#ifdef SQLITE_CUSTOM_INCLUDE +# define INC_STRINGIFY_(f) #f +# define INC_STRINGIFY(f) INC_STRINGIFY_(f) +# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE) +#endif + /* The public SQLite interface. The _FILE_OFFSET_BITS macro must appear ** first in QNX. Also, the _USE_32BIT_TIME_T macro must appear first for ** MinGW. @@ -214,11 +225,12 @@ #ifndef __has_extension # define __has_extension(x) 0 /* compatibility with non-clang compilers */ #endif -#if GCC_VERSION>=4007000 || \ - (__has_extension(c_atomic) && __has_extension(c_atomic_store_n)) +#if GCC_VERSION>=4007000 || __has_extension(c_atomic) +# define SQLITE_ATOMIC_INTRINSICS 1 # define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED) # define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED) #else +# define SQLITE_ATOMIC_INTRINSICS 0 # define AtomicLoad(PTR) (*(PTR)) # define AtomicStore(PTR,VAL) (*(PTR) = (VAL)) #endif @@ -423,11 +435,12 @@ ** is significant and used at least once. On switch statements ** where multiple cases go to the same block of code, testcase() ** can insure that all cases are evaluated. -** */ -#ifdef SQLITE_COVERAGE_TEST - void sqlite3Coverage(int); -# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); } +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +# ifndef SQLITE_AMALGAMATION + extern unsigned int sqlite3CoverageCounter; +# endif +# define testcase(X) if( X ){ sqlite3CoverageCounter += (unsigned)__LINE__; } #else # define testcase(X) #endif @@ -457,6 +470,14 @@ # define VVA_ONLY(X) #endif +/* +** Disable ALWAYS() and NEVER() (make them pass-throughs) for coverage +** and mutation testing +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif + /* ** The ALWAYS and NEVER macros surround boolean expressions which ** are intended to always be true or false, respectively. Such @@ -472,7 +493,7 @@ ** be true and false so that the unreachable code they specify will ** not be counted as untested code. */ -#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) # define ALWAYS(X) (1) # define NEVER(X) (0) #elif !defined(NDEBUG) @@ -483,26 +504,6 @@ # define NEVER(X) (X) #endif -/* -** The harmless(X) macro indicates that expression X is usually false -** but can be true without causing any problems, but we don't know of -** any way to cause X to be true. -** -** In debugging and testing builds, this macro will abort if X is ever -** true. In this way, developers are alerted to a possible test case -** that causes X to be true. If a harmless macro ever fails, that is -** an opportunity to change the macro into a testcase() and add a new -** test case to the test suite. -** -** For normal production builds, harmless(X) is a no-op, since it does -** not matter whether expression X is true or false. -*/ -#ifdef SQLITE_DEBUG -# define harmless(X) assert(!(X)); -#else -# define harmless(X) -#endif - /* ** Some conditionals are optimizations only. In other words, if the ** conditionals are replaced with a constant 1 (true) or 0 (false) then @@ -566,6 +567,13 @@ # undef SQLITE_ENABLE_EXPLAIN_COMMENTS #endif +/* +** SQLITE_OMIT_VIRTUALTABLE implies SQLITE_OMIT_ALTERTABLE +*/ +#if defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_ALTERTABLE) +# define SQLITE_OMIT_ALTERTABLE +#endif + /* ** Return true (non-zero) if the input is an integer that is too large ** to fit in 32-bits. This macro is used inside of various testcase() @@ -687,7 +695,7 @@ ** number of pages. A negative number N translations means that a buffer ** of -1024*N bytes is allocated and used for as many pages as it will hold. ** -** The default value of "20" was choosen to minimize the run-time of the +** The default value of "20" was chosen to minimize the run-time of the ** speedtest1 test program with options: --shrink-memory --reprepare */ #ifndef SQLITE_DEFAULT_PCACHE_INITSZ @@ -849,6 +857,7 @@ typedef INT16_TYPE LogEst; # define SQLITE_PTRSIZE __SIZEOF_POINTER__ # elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ defined(_M_ARM) || defined(__arm__) || defined(__x86) || \ + (defined(__APPLE__) && defined(__POWERPC__)) || \ (defined(__TOS_AIX__) && !defined(__64BIT__)) # define SQLITE_PTRSIZE 4 # else @@ -1043,11 +1052,25 @@ struct BusyHandler { /* ** Name of table that holds the database schema. +** +** The PREFERRED names are used whereever possible. But LEGACY is also +** used for backwards compatibility. +** +** 1. Queries can use either the PREFERRED or the LEGACY names +** 2. The sqlite3_set_authorizer() callback uses the LEGACY name +** 3. The PRAGMA table_list statement uses the PREFERRED name +** +** The LEGACY names are stored in the internal symbol hash table +** in support of (2). Names are translated using sqlite3PreferredTableName() +** for (3). The sqlite3FindTable() function takes care of translating +** names for (1). +** +** Note that "sqlite_temp_schema" can also be called "temp.sqlite_schema". */ -#define DFLT_SCHEMA_TABLE "sqlite_master" -#define DFLT_TEMP_SCHEMA_TABLE "sqlite_temp_master" -#define ALT_SCHEMA_TABLE "sqlite_schema" -#define ALT_TEMP_SCHEMA_TABLE "sqlite_temp_schema" +#define LEGACY_SCHEMA_TABLE "sqlite_master" +#define LEGACY_TEMP_SCHEMA_TABLE "sqlite_temp_master" +#define PREFERRED_SCHEMA_TABLE "sqlite_schema" +#define PREFERRED_TEMP_SCHEMA_TABLE "sqlite_temp_schema" /* @@ -1059,7 +1082,7 @@ struct BusyHandler { ** The name of the schema table. The name is different for TEMP. */ #define SCHEMA_TABLE(x) \ - ((!OMIT_TEMPDB)&&(x==1)?DFLT_TEMP_SCHEMA_TABLE:DFLT_SCHEMA_TABLE) + ((!OMIT_TEMPDB)&&(x==1)?LEGACY_TEMP_SCHEMA_TABLE:LEGACY_SCHEMA_TABLE) /* ** A convenience macro that returns the number of elements in @@ -1520,10 +1543,10 @@ struct sqlite3 { u8 mTrace; /* zero or more SQLITE_TRACE flags */ u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ + u8 eOpenState; /* Current condition of the connection */ int nextPagesize; /* Pagesize after VACUUM if >0 */ - u32 magic; /* Magic number for detect library misuse */ - int nChange; /* Value returned by sqlite3_changes() */ - int nTotalChange; /* Value returned by sqlite3_total_changes() */ + i64 nChange; /* Value returned by sqlite3_changes() */ + i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ int aLimit[SQLITE_N_LIMIT]; /* Limits */ int nMaxSorterMmap; /* Maximum size of regions mapped by sorter */ struct sqlite3InitInfo { /* Information used during initialization */ @@ -1533,7 +1556,7 @@ struct sqlite3 { unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ unsigned imposterTable : 1; /* Building an imposter table */ unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ - char **azInit; /* "type", "name", and "tbl_name" columns */ + const char **azInit; /* "type", "name", and "tbl_name" columns */ } init; int nVdbeActive; /* Number of VDBEs currently running */ int nVdbeRead; /* Number of active VDBEs that read or write */ @@ -1543,10 +1566,10 @@ struct sqlite3 { int nExtension; /* Number of loaded extensions */ void **aExtension; /* Array of shared library handles */ union { - void (*xLegacy)(void*,const char*); /* Legacy trace function */ - int (*xV2)(u32,void*,void*,void*); /* V2 Trace function */ + void (*xLegacy)(void*,const char*); /* mTrace==SQLITE_TRACE_LEGACY */ + int (*xV2)(u32,void*,void*,void*); /* All other mTrace values */ } trace; - void *pTraceArg; /* Argument to the trace function */ + void *pTraceArg; /* Argument to the trace function */ #ifndef SQLITE_OMIT_DEPRECATED void (*xProfile)(void*,const char*,u64); /* Profiling function */ void *pProfileArg; /* Argument to profile function */ @@ -1557,6 +1580,9 @@ struct sqlite3 { void (*xRollbackCallback)(void*); /* Invoked at every commit. */ void *pUpdateArg; void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); + void *pAutovacPagesArg; /* Client argument to autovac_pages */ + void (*xAutovacDestr)(void*); /* Destructor for pAutovacPAgesArg */ + unsigned int (*xAutovacPages)(void*,const char*,u32,u32,u32); Parse *pParse; /* Current parse */ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK void *pPreUpdateArg; /* First argument to xPreUpdateCallback */ @@ -1686,6 +1712,7 @@ struct sqlite3 { #define SQLITE_CountRows HI(0x00001) /* Count rows changed by INSERT, */ /* DELETE, or UPDATE and return */ /* the count using a callback. */ +#define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */ /* Flags used only if debugging */ #ifdef SQLITE_DEBUG @@ -1732,6 +1759,8 @@ struct sqlite3 { #define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */ #define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */ #define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */ +#define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */ + /* TH3 expects this value ^^^^^^^^^^ to be 0x40000. Coordinate any change */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -1746,17 +1775,16 @@ struct sqlite3 { */ #define ConstFactorOk(P) ((P)->okConstFactor) -/* -** Possible values for the sqlite.magic field. -** The numbers are obtained at random and have no special meaning, other -** than being distinct from one another. +/* Possible values for the sqlite3.eOpenState field. +** The numbers are randomly selected such that a minimum of three bits must +** change to convert any number to another or to zero */ -#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ -#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ -#define SQLITE_MAGIC_SICK 0x4b771290 /* Error and awaiting close */ -#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ -#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ -#define SQLITE_MAGIC_ZOMBIE 0x64cffc7f /* Close with last statement close */ +#define SQLITE_STATE_OPEN 0x76 /* Database is open */ +#define SQLITE_STATE_CLOSED 0xce /* Database is closed */ +#define SQLITE_STATE_SICK 0xba /* Error and awaiting close */ +#define SQLITE_STATE_BUSY 0x6d /* Database currently in use */ +#define SQLITE_STATE_ERROR 0xd5 /* An SQLITE_MISUSE error occurred */ +#define SQLITE_STATE_ZOMBIE 0xa7 /* Close with last statement close */ /* ** Each SQL function is defined by an instance of the following @@ -1781,7 +1809,7 @@ struct FuncDef { union { FuncDef *pHash; /* Next with a different name but the same hash */ FuncDestructor *pDestructor; /* Reference counted destructor function */ - } u; + } u; /* pHash if SQLITE_FUNC_BUILTIN, pDestructor otherwise */ }; /* @@ -1811,12 +1839,13 @@ struct FuncDestructor { ** are assert() statements in the code to verify this. ** ** Value constraints (enforced via assert()): -** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg -** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG -** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG -** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API -** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API -** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS +** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg +** SQLITE_FUNC_ANYORDER == NC_OrderAgg == SF_OrderByReqd +** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG +** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG +** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API +** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API +** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS ** SQLITE_FUNC_ENCMASK depends on SQLITE_UTF* macros in the API */ #define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */ @@ -1841,6 +1870,8 @@ struct FuncDestructor { #define SQLITE_FUNC_SUBTYPE 0x00100000 /* Result likely to have sub-type */ #define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */ #define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */ +#define SQLITE_FUNC_BUILTIN 0x00800000 /* This is a built-in function */ +#define SQLITE_FUNC_ANYORDER 0x08000000 /* count/min/max aggregate */ /* Identifier numbers for each in-line function */ #define INLINEFUNC_coalesce 0 @@ -1918,44 +1949,51 @@ struct FuncDestructor { ** parameter. */ #define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define SFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define MFUNCTION(zName, nArg, xPtr, xFunc) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ - {nArg, SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } #define TEST_FUNC(zName, nArg, iArg, mFlags) \ - {nArg, SQLITE_UTF8|SQLITE_FUNC_INTERNAL|SQLITE_FUNC_TEST| \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INTERNAL|SQLITE_FUNC_TEST| \ SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } #define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ 0, 0, xFunc, 0, 0, 0, #zName, {0} } #define PURE_DATE(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ (void*)&sqlite3Config, 0, xFunc, 0, 0, 0, #zName, {0} } #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ - {nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ pArg, 0, xFunc, 0, 0, 0, #zName, } #define LIKEFUNC(zName, nArg, arg, flags) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ (void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} } #define WAGGREGATE(zName, nArg, arg, nc, xStep, xFinal, xValue, xInverse, f) \ - {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \ SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,xValue,xInverse,#zName, {0}} #define INTERNAL_FUNCTION(zName, nArg, xFunc) \ - {nArg, SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ 0, 0, xFunc, 0, 0, 0, #zName, {0} } @@ -2011,18 +2049,42 @@ struct Module { ** or equal to the table column index. It is ** equal if and only if there are no VIRTUAL ** columns to the left. +** +** Notes on zCnName: +** The zCnName field stores the name of the column, the datatype of the +** column, and the collating sequence for the column, in that order, all in +** a single allocation. Each string is 0x00 terminated. The datatype +** is only included if the COLFLAG_HASTYPE bit of colFlags is set and the +** collating sequence name is only included if the COLFLAG_HASCOLL bit is +** set. */ struct Column { - char *zName; /* Name of this column, \000, then the type */ - Expr *pDflt; /* Default value or GENERATED ALWAYS AS value */ - char *zColl; /* Collating sequence. If NULL, use the default */ - u8 notNull; /* An OE_ code for handling a NOT NULL constraint */ - char affinity; /* One of the SQLITE_AFF_... values */ - u8 szEst; /* Estimated size of value in this column. sizeof(INT)==1 */ - u8 hName; /* Column name hash for faster lookup */ - u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ + char *zCnName; /* Name of this column */ + unsigned notNull :4; /* An OE_ code for handling a NOT NULL constraint */ + unsigned eCType :4; /* One of the standard types */ + char affinity; /* One of the SQLITE_AFF_... values */ + u8 szEst; /* Est size of value in this column. sizeof(INT)==1 */ + u8 hName; /* Column name hash for faster lookup */ + u16 iDflt; /* 1-based index of DEFAULT. 0 means "none" */ + u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ }; +/* Allowed values for Column.eCType. +** +** Values must match entries in the global constant arrays +** sqlite3StdTypeLen[] and sqlite3StdType[]. Each value is one more +** than the offset into these arrays for the corresponding name. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +*/ +#define COLTYPE_CUSTOM 0 /* Type appended to zName */ +#define COLTYPE_ANY 1 +#define COLTYPE_BLOB 2 +#define COLTYPE_INT 3 +#define COLTYPE_INTEGER 4 +#define COLTYPE_REAL 5 +#define COLTYPE_TEXT 6 +#define SQLITE_N_STDTYPE 6 /* Number of standard types */ + /* Allowed values for Column.colFlags. ** ** Constraints: @@ -2039,6 +2101,7 @@ struct Column { #define COLFLAG_STORED 0x0040 /* GENERATED ALWAYS AS ... STORED */ #define COLFLAG_NOTAVAIL 0x0080 /* STORED column not yet calculated */ #define COLFLAG_BUSY 0x0100 /* Blocks recursion on GENERATED columns */ +#define COLFLAG_HASCOLL 0x0200 /* Has collating sequence name in zCnName */ #define COLFLAG_GENERATED 0x0060 /* Combo: _STORED, _VIRTUAL */ #define COLFLAG_NOINSERT 0x0062 /* Combo: _HIDDEN, _STORED, _VIRTUAL */ @@ -2168,15 +2231,13 @@ struct VTable { #define SQLITE_VTABRISK_High 2 /* -** The schema for each SQL table and view is represented in memory -** by an instance of the following structure. +** The schema for each SQL table, virtual table, and view is represented +** in memory by an instance of the following structure. */ struct Table { char *zName; /* Name of the table or view */ Column *aCol; /* Information about each column */ Index *pIndex; /* List of SQL indexes on this table. */ - Select *pSelect; /* NULL for tables. Points to definition if a view. */ - FKey *pFKey; /* Linked list of all foreign keys in this table */ char *zColAff; /* String defining the affinity of each column */ ExprList *pCheck; /* All CHECK constraints */ /* ... also used as column name list in a VIEW */ @@ -2192,15 +2253,24 @@ struct Table { LogEst costMult; /* Cost multiplier for using this table */ #endif u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ -#ifndef SQLITE_OMIT_ALTERTABLE - int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ -#endif -#ifndef SQLITE_OMIT_VIRTUALTABLE - int nModuleArg; /* Number of arguments to the module */ - char **azModuleArg; /* 0: module 1: schema 2: vtab name 3...: args */ - VTable *pVTable; /* List of VTable objects. */ -#endif - Trigger *pTrigger; /* List of triggers stored in pSchema */ + u8 eTabType; /* 0: normal, 1: virtual, 2: view */ + union { + struct { /* Used by ordinary tables: */ + int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ + ExprList *pDfltList; /* DEFAULT clauses on various columns. + ** Or the AS clause for generated columns. */ + } tab; + struct { /* Used by views: */ + Select *pSelect; /* View definition */ + } view; + struct { /* Used by virtual tables only: */ + int nArg; /* Number of arguments to the module */ + char **azArg; /* 0: module 1: schema 2: vtab name 3...: args */ + VTable *p; /* List of VTable objects. */ + } vtab; + } u; + Trigger *pTrigger; /* List of triggers on this object */ Schema *pSchema; /* Schema that contains this table */ }; @@ -2219,24 +2289,35 @@ struct Table { ** TF_HasStored == COLFLAG_STORED ** TF_HasHidden == COLFLAG_HIDDEN */ -#define TF_Readonly 0x0001 /* Read-only system table */ -#define TF_HasHidden 0x0002 /* Has one or more hidden columns */ -#define TF_HasPrimaryKey 0x0004 /* Table has a primary key */ -#define TF_Autoincrement 0x0008 /* Integer primary key is autoincrement */ -#define TF_HasStat1 0x0010 /* nRowLogEst set from sqlite_stat1 */ -#define TF_HasVirtual 0x0020 /* Has one or more VIRTUAL columns */ -#define TF_HasStored 0x0040 /* Has one or more STORED columns */ -#define TF_HasGenerated 0x0060 /* Combo: HasVirtual + HasStored */ -#define TF_WithoutRowid 0x0080 /* No rowid. PRIMARY KEY is the key */ -#define TF_StatsUsed 0x0100 /* Query planner decisions affected by +#define TF_Readonly 0x00000001 /* Read-only system table */ +#define TF_HasHidden 0x00000002 /* Has one or more hidden columns */ +#define TF_HasPrimaryKey 0x00000004 /* Table has a primary key */ +#define TF_Autoincrement 0x00000008 /* Integer primary key is autoincrement */ +#define TF_HasStat1 0x00000010 /* nRowLogEst set from sqlite_stat1 */ +#define TF_HasVirtual 0x00000020 /* Has one or more VIRTUAL columns */ +#define TF_HasStored 0x00000040 /* Has one or more STORED columns */ +#define TF_HasGenerated 0x00000060 /* Combo: HasVirtual + HasStored */ +#define TF_WithoutRowid 0x00000080 /* No rowid. PRIMARY KEY is the key */ +#define TF_StatsUsed 0x00000100 /* Query planner decisions affected by ** Index.aiRowLogEst[] values */ -#define TF_NoVisibleRowid 0x0200 /* No user-visible "rowid" column */ -#define TF_OOOHidden 0x0400 /* Out-of-Order hidden columns */ -#define TF_HasNotNull 0x0800 /* Contains NOT NULL constraints */ -#define TF_Shadow 0x1000 /* True for a shadow table */ -#define TF_HasStat4 0x2000 /* STAT4 info available for this table */ -#define TF_Ephemeral 0x4000 /* An ephemeral table */ -#define TF_Eponymous 0x8000 /* An eponymous virtual table */ +#define TF_NoVisibleRowid 0x00000200 /* No user-visible "rowid" column */ +#define TF_OOOHidden 0x00000400 /* Out-of-Order hidden columns */ +#define TF_HasNotNull 0x00000800 /* Contains NOT NULL constraints */ +#define TF_Shadow 0x00001000 /* True for a shadow table */ +#define TF_HasStat4 0x00002000 /* STAT4 info available for this table */ +#define TF_Ephemeral 0x00004000 /* An ephemeral table */ +#define TF_Eponymous 0x00008000 /* An eponymous virtual table */ +#define TF_Strict 0x00010000 /* STRICT mode */ + +/* +** Allowed values for Table.eTabType +*/ +#define TABTYP_NORM 0 /* Ordinary table */ +#define TABTYP_VTAB 1 /* Virtual table */ +#define TABTYP_VIEW 2 /* A view */ + +#define IsView(X) ((X)->eTabType==TABTYP_VIEW) +#define IsOrdinaryTable(X) ((X)->eTabType==TABTYP_NORM) /* ** Test to see whether or not a table is a virtual table. This is @@ -2244,9 +2325,9 @@ struct Table { ** table support is omitted from the build. */ #ifndef SQLITE_OMIT_VIRTUALTABLE -# define IsVirtual(X) ((X)->nModuleArg) +# define IsVirtual(X) ((X)->eTabType==TABTYP_VTAB) # define ExprIsVtab(X) \ - ((X)->op==TK_COLUMN && (X)->y.pTab!=0 && (X)->y.pTab->nModuleArg) + ((X)->op==TK_COLUMN && (X)->y.pTab!=0 && (X)->y.pTab->eTabType==TABTYP_VTAB) #else # define IsVirtual(X) 0 # define ExprIsVtab(X) 0 @@ -2635,10 +2716,10 @@ typedef int ynVar; ** tree. ** ** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, -** or TK_STRING), then Expr.token contains the text of the SQL literal. If -** the expression is a variable (TK_VARIABLE), then Expr.token contains the +** or TK_STRING), then Expr.u.zToken contains the text of the SQL literal. If +** the expression is a variable (TK_VARIABLE), then Expr.u.zToken contains the ** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), -** then Expr.token contains the name of the function. +** then Expr.u.zToken contains the name of the function. ** ** Expr.pRight and Expr.pLeft are the left and right subexpressions of a ** binary operator. Either or both may be NULL. @@ -2678,7 +2759,7 @@ typedef int ynVar; ** help reduce memory requirements, sometimes an Expr object will be ** truncated. And to reduce the number of memory allocations, sometimes ** two or more Expr objects will be stored in a single memory allocation, -** together with Expr.zToken strings. +** together with Expr.u.zToken strings. ** ** If the EP_Reduced and EP_TokenOnly flags are set when ** an Expr object is truncated. When EP_Reduced is set, then all @@ -2747,8 +2828,7 @@ struct Expr { } y; }; -/* -** The following are the meanings of bits in the Expr.flags field. +/* The following are the meanings of bits in the Expr.flags field. ** Value restrictions: ** ** EP_Agg == NC_HasAgg == SF_HasAgg @@ -2787,14 +2867,12 @@ struct Expr { #define EP_FromDDL 0x40000000 /* Originates from sqlite_schema */ /* 0x80000000 // Available */ -/* -** The EP_Propagate mask is a set of properties that automatically propagate +/* The EP_Propagate mask is a set of properties that automatically propagate ** upwards into parent nodes. */ #define EP_Propagate (EP_Collate|EP_Subquery|EP_HasFunc) -/* -** These macros can be used to test, set, or clear bits in the +/* Macros can be used to test, set, or clear bits in the ** Expr.flags field. */ #define ExprHasProperty(E,P) (((E)->flags&(P))!=0) @@ -2804,6 +2882,16 @@ struct Expr { #define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue) #define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse) +/* Macros used to ensure that the correct members of unions are accessed +** in Expr. +*/ +#define ExprUseUToken(E) (((E)->flags&EP_IntValue)==0) +#define ExprUseUValue(E) (((E)->flags&EP_IntValue)!=0) +#define ExprUseXList(E) (((E)->flags&EP_xIsSelect)==0) +#define ExprUseXSelect(E) (((E)->flags&EP_xIsSelect)!=0) +#define ExprUseYTab(E) (((E)->flags&(EP_WinFunc|EP_Subrtn))==0) +#define ExprUseYWin(E) (((E)->flags&EP_WinFunc)!=0) +#define ExprUseYSub(E) (((E)->flags&EP_Subrtn)!=0) /* Flags for use with Expr.vvaFlags */ @@ -2886,11 +2974,12 @@ struct ExprList { unsigned bSorterRef :1; /* Defer evaluation until after sorting */ unsigned bNulls: 1; /* True if explicit "NULLS FIRST/LAST" */ union { - struct { + struct { /* Used by any ExprList other than Parse.pConsExpr */ u16 iOrderByCol; /* For ORDER BY, column number in result set */ u16 iAlias; /* Index into Parse.aAlias[] for zName */ } x; - int iConstExprReg; /* Register in which Expr value is cached */ + int iConstExprReg; /* Register in which Expr value is cached. Used only + ** by Parse.pConstExpr */ } u; } a[1]; /* One slot for each expression in the list */ }; @@ -2928,6 +3017,13 @@ struct IdList { /* ** The SrcItem object represents a single term in the FROM clause of a query. ** The SrcList object is mostly an array of SrcItems. +** +** Union member validity: +** +** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc +** u1.pFuncArg fg.isTabFunc && !fg.isIndexedBy +** u2.pIBIndex fg.isIndexedBy && !fg.isCte +** u2.pCteUse fg.isCte && !fg.isIndexedBy */ struct SrcItem { Schema *pSchema; /* Schema to which this item is fixed */ @@ -3076,31 +3172,33 @@ struct NameContext { ** Allowed values for the NameContext, ncFlags field. ** ** Value constraints (all checked via assert()): -** NC_HasAgg == SF_HasAgg == EP_Agg -** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** NC_HasAgg == SF_HasAgg == EP_Agg +** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** NC_OrderAgg == SF_OrderByReqd == SQLITE_FUNC_ANYORDER ** NC_HasWin == EP_Win ** */ -#define NC_AllowAgg 0x00001 /* Aggregate functions are allowed here */ -#define NC_PartIdx 0x00002 /* True if resolving a partial index WHERE */ -#define NC_IsCheck 0x00004 /* True if resolving a CHECK constraint */ -#define NC_GenCol 0x00008 /* True for a GENERATED ALWAYS AS clause */ -#define NC_HasAgg 0x00010 /* One or more aggregate functions seen */ -#define NC_IdxExpr 0x00020 /* True if resolving columns of CREATE INDEX */ -#define NC_SelfRef 0x0002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */ -#define NC_VarSelect 0x00040 /* A correlated subquery has been seen */ -#define NC_UEList 0x00080 /* True if uNC.pEList is used */ -#define NC_UAggInfo 0x00100 /* True if uNC.pAggInfo is used */ -#define NC_UUpsert 0x00200 /* True if uNC.pUpsert is used */ -#define NC_UBaseReg 0x00400 /* True if uNC.iBaseReg is used */ -#define NC_MinMaxAgg 0x01000 /* min/max aggregates seen. See note above */ -#define NC_Complex 0x02000 /* True if a function or subquery seen */ -#define NC_AllowWin 0x04000 /* Window functions are allowed here */ -#define NC_HasWin 0x08000 /* One or more window functions seen */ -#define NC_IsDDL 0x10000 /* Resolving names in a CREATE statement */ -#define NC_InAggFunc 0x20000 /* True if analyzing arguments to an agg func */ -#define NC_FromDDL 0x40000 /* SQL text comes from sqlite_schema */ -#define NC_NoSelect 0x80000 /* Do not descend into sub-selects */ +#define NC_AllowAgg 0x000001 /* Aggregate functions are allowed here */ +#define NC_PartIdx 0x000002 /* True if resolving a partial index WHERE */ +#define NC_IsCheck 0x000004 /* True if resolving a CHECK constraint */ +#define NC_GenCol 0x000008 /* True for a GENERATED ALWAYS AS clause */ +#define NC_HasAgg 0x000010 /* One or more aggregate functions seen */ +#define NC_IdxExpr 0x000020 /* True if resolving columns of CREATE INDEX */ +#define NC_SelfRef 0x00002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */ +#define NC_VarSelect 0x000040 /* A correlated subquery has been seen */ +#define NC_UEList 0x000080 /* True if uNC.pEList is used */ +#define NC_UAggInfo 0x000100 /* True if uNC.pAggInfo is used */ +#define NC_UUpsert 0x000200 /* True if uNC.pUpsert is used */ +#define NC_UBaseReg 0x000400 /* True if uNC.iBaseReg is used */ +#define NC_MinMaxAgg 0x001000 /* min/max aggregates seen. See note above */ +#define NC_Complex 0x002000 /* True if a function or subquery seen */ +#define NC_AllowWin 0x004000 /* Window functions are allowed here */ +#define NC_HasWin 0x008000 /* One or more window functions seen */ +#define NC_IsDDL 0x010000 /* Resolving names in a CREATE statement */ +#define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */ +#define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */ +#define NC_NoSelect 0x080000 /* Do not descend into sub-selects */ +#define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */ /* ** An instance of the following object describes a single ON CONFLICT @@ -3183,9 +3281,10 @@ struct Select { ** "Select Flag". ** ** Value constraints (all checked via assert()) -** SF_HasAgg == NC_HasAgg -** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX -** SF_FixedLimit == WHERE_USE_LIMIT +** SF_HasAgg == NC_HasAgg +** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX +** SF_OrderByReqd == NC_OrderAgg == SQLITE_FUNC_ANYORDER +** SF_FixedLimit == WHERE_USE_LIMIT */ #define SF_Distinct 0x0000001 /* Output should be DISTINCT */ #define SF_All 0x0000002 /* Includes the ALL keyword */ @@ -3210,10 +3309,11 @@ struct Select { #define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ #define SF_View 0x0200000 /* SELECT statement is a view */ #define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ -#define SF_UpdateFrom 0x0800000 /* Statement is an UPDATE...FROM */ +#define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ #define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ +#define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ /* ** The results of a SELECT can be distributed in several ways, as defined @@ -3455,7 +3555,8 @@ struct Parse { AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ - Parse *pParentParse; /* Parent parser if this parser is nested */ + TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ + ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */ union { int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */ Returning *pReturning; /* The RETURNING clause */ @@ -3510,14 +3611,14 @@ struct Parse { Token sArg; /* Complete text of a module argument */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ #endif - TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ With *pWith; /* Current WITH clause, or NULL */ - ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */ #ifndef SQLITE_OMIT_ALTERTABLE RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */ #endif }; +/* Allowed values for Parse.eParseMode +*/ #define PARSE_MODE_NORMAL 0 #define PARSE_MODE_DECLARE_VTAB 1 #define PARSE_MODE_RENAME 2 @@ -3739,8 +3840,10 @@ typedef struct { /* ** Allowed values for mInitFlags */ +#define INITFLAG_AlterMask 0x0003 /* Types of ALTER */ #define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ #define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ +#define INITFLAG_AlterAdd 0x0003 /* Reparse after an ADD COLUMN */ /* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled ** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning @@ -3861,8 +3964,8 @@ struct Walker { int n; /* A counter */ int iCur; /* A cursor number */ SrcList *pSrcList; /* FROM clause */ - struct SrcCount *pSrcCount; /* Counting column references */ struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ + struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ int *aiCol; /* array of column indexes */ struct IdxCover *pIdxCover; /* Check for index coverage */ struct IdxExprTrans *pIdxTrans; /* Convert idxed expr to column */ @@ -4047,7 +4150,7 @@ void sqlite3WindowListDelete(sqlite3 *db, Window *p); Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8); void sqlite3WindowAttach(Parse*, Expr*, Window*); void sqlite3WindowLink(Select *pSel, Window *pWin); -int sqlite3WindowCompare(Parse*, Window*, Window*, int); +int sqlite3WindowCompare(const Parse*, const Window*, const Window*, int); void sqlite3WindowCodeInit(Parse*, Select*); void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int); int sqlite3WindowRewrite(Parse*, Select*); @@ -4179,8 +4282,8 @@ void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); void *sqlite3DbRealloc(sqlite3 *, void *, u64); void sqlite3DbFree(sqlite3*, void*); void sqlite3DbFreeNN(sqlite3*, void*); -int sqlite3MallocSize(void*); -int sqlite3DbMallocSize(sqlite3*, void*); +int sqlite3MallocSize(const void*); +int sqlite3DbMallocSize(sqlite3*, const void*); void *sqlite3PageMalloc(int); void sqlite3PageFree(void*); void sqlite3MemSetDefault(void); @@ -4296,6 +4399,7 @@ void sqlite3ErrorMsg(Parse*, const char*, ...); int sqlite3ErrorToParser(sqlite3*,int); void sqlite3Dequote(char*); void sqlite3DequoteExpr(Expr*); +void sqlite3DequoteToken(Token*); void sqlite3TokenInit(Token*,char*); int sqlite3KeywordCode(const unsigned char*, int); int sqlite3RunParser(Parse*, const char*, char **); @@ -4315,16 +4419,17 @@ Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); void sqlite3PExprAddSelect(Parse*, Expr*, Select*); Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*); Expr *sqlite3ExprSimplifiedAndOr(Expr*); -Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*, int); -void sqlite3ExprFunctionUsable(Parse*,Expr*,FuncDef*); +Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int); +void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); void sqlite3ExprDelete(sqlite3*, Expr*); void sqlite3ExprDeferredDelete(Parse*, Expr*); void sqlite3ExprUnmapAndDelete(Parse*, Expr*); ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); +Select *sqlite3ExprListToValues(Parse*, int, ExprList*); void sqlite3ExprListSetSortOrder(ExprList*,int,int); -void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int); +void sqlite3ExprListSetName(Parse*,ExprList*,const Token*,int); void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*); void sqlite3ExprListDelete(sqlite3*, ExprList*); u32 sqlite3ExprListFlags(const ExprList*); @@ -4340,6 +4445,10 @@ void sqlite3ResetAllSchemasOfConnection(sqlite3*); void sqlite3ResetOneSchema(sqlite3*,int); void sqlite3CollapseDatabaseArray(sqlite3*); void sqlite3CommitInternalChanges(sqlite3*); +void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*); +Expr *sqlite3ColumnExpr(Table*,Column*); +void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl); +const char *sqlite3ColumnColl(Column*); void sqlite3DeleteColumnNames(sqlite3*,Table*); void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); @@ -4361,14 +4470,14 @@ void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int); #else # define sqlite3ColumnPropertiesFromName(T,C) /* no-op */ #endif -void sqlite3AddColumn(Parse*,Token*,Token*); +void sqlite3AddColumn(Parse*,Token,Token); void sqlite3AddNotNull(Parse*, int); void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int); void sqlite3AddCheckConstraint(Parse*, Expr*, const char*, const char*); void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*); void sqlite3AddCollateType(Parse*, Token*); void sqlite3AddGenerated(Parse*,Expr*,Token*); -void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); +void sqlite3EndTable(Parse*,Token*,Token*,u32,Select*); void sqlite3AddReturning(Parse*,ExprList*); int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); @@ -4478,7 +4587,7 @@ void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int); void sqlite3ExprCodeMove(Parse*, int, int, int); void sqlite3ExprCode(Parse*, Expr*, int); #ifndef SQLITE_OMIT_GENERATED_COLUMNS -void sqlite3ExprCodeGeneratedColumn(Parse*, Column*, int); +void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int); #endif void sqlite3ExprCodeCopy(Parse*, Expr*, int); void sqlite3ExprCodeFactorable(Parse*, Expr*, int); @@ -4497,23 +4606,24 @@ Table *sqlite3FindTable(sqlite3*,const char*, const char*); #define LOCATE_VIEW 0x01 #define LOCATE_NOERR 0x02 Table *sqlite3LocateTable(Parse*,u32 flags,const char*, const char*); +const char *sqlite3PreferredTableName(const char*); Table *sqlite3LocateTableItem(Parse*,u32 flags,SrcItem *); Index *sqlite3FindIndex(sqlite3*,const char*, const char*); void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*); void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); void sqlite3Vacuum(Parse*,Token*,Expr*); int sqlite3RunVacuum(char**, sqlite3*, int, sqlite3_value*); -char *sqlite3NameFromToken(sqlite3*, Token*); -int sqlite3ExprCompare(Parse*,Expr*, Expr*, int); -int sqlite3ExprCompareSkip(Expr*, Expr*, int); -int sqlite3ExprListCompare(ExprList*, ExprList*, int); -int sqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int); +char *sqlite3NameFromToken(sqlite3*, const Token*); +int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int); +int sqlite3ExprCompareSkip(Expr*,Expr*,int); +int sqlite3ExprListCompare(const ExprList*,const ExprList*, int); +int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int); int sqlite3ExprImpliesNonNullRow(Expr*,int); void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*); void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx); -int sqlite3FunctionUsesThisSrc(Expr*, SrcList*); +int sqlite3ReferencesSrcList(Parse*, Expr*, SrcList*); Vdbe *sqlite3GetVdbe(Parse*); #ifndef SQLITE_UNTESTABLE void sqlite3PrngSaveState(void); @@ -4538,7 +4648,7 @@ int sqlite3ExprIsTableConstant(Expr*,int); #ifdef SQLITE_ENABLE_CURSOR_HINTS int sqlite3ExprContainsSubquery(Expr*); #endif -int sqlite3ExprIsInteger(Expr*, int*); +int sqlite3ExprIsInteger(const Expr*, int*); int sqlite3ExprCanBeNull(const Expr*); int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); int sqlite3IsRowid(const char*); @@ -4563,11 +4673,11 @@ void sqlite3MayAbort(Parse*); void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8); void sqlite3UniqueConstraint(Parse*, int, Index*); void sqlite3RowidConstraint(Parse*, int, Table*); -Expr *sqlite3ExprDup(sqlite3*,Expr*,int); -ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int); -SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int); -IdList *sqlite3IdListDup(sqlite3*,IdList*); -Select *sqlite3SelectDup(sqlite3*,Select*,int); +Expr *sqlite3ExprDup(sqlite3*,const Expr*,int); +ExprList *sqlite3ExprListDup(sqlite3*,const ExprList*,int); +SrcList *sqlite3SrcListDup(sqlite3*,const SrcList*,int); +IdList *sqlite3IdListDup(sqlite3*,const IdList*); +Select *sqlite3SelectDup(sqlite3*,const Select*,int); FuncDef *sqlite3FunctionSearch(int,const char*); void sqlite3InsertBuiltinFuncs(FuncDef*,int); FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8); @@ -4705,7 +4815,7 @@ const char *sqlite3IndexAffinityStr(sqlite3*, Index*); void sqlite3TableAffinity(Vdbe*, Table*, int); char sqlite3CompareAffinity(const Expr *pExpr, char aff2); int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); -char sqlite3TableColumnAffinity(Table*,int); +char sqlite3TableColumnAffinity(const Table*,int); char sqlite3ExprAffinity(const Expr *pExpr); int sqlite3Atoi64(const char*, i64*, int, u8); int sqlite3DecOrHexToI64(const char*, i64*); @@ -4734,14 +4844,14 @@ void sqlite3SetTextEncoding(sqlite3 *db, u8); CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr); CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr); int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*); -Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int); -Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*); +Expr *sqlite3ExprAddCollateToken(const Parse *pParse, Expr*, const Token*, int); +Expr *sqlite3ExprAddCollateString(const Parse*,Expr*,const char*); Expr *sqlite3ExprSkipCollate(Expr*); Expr *sqlite3ExprSkipCollateAndLikely(Expr*); int sqlite3CheckCollSeq(Parse *, CollSeq *); int sqlite3WritableSchema(sqlite3*); int sqlite3CheckObjectName(Parse*, const char*,const char*,const char*); -void sqlite3VdbeSetChanges(sqlite3 *, int); +void sqlite3VdbeSetChanges(sqlite3 *, i64); int sqlite3AddInt64(i64*,i64); int sqlite3SubInt64(i64*,i64); int sqlite3MulInt64(i64*,i64); @@ -4766,11 +4876,15 @@ sqlite3_value *sqlite3ValueNew(sqlite3 *); #ifndef SQLITE_OMIT_UTF16 char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); #endif -int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **); +int sqlite3ValueFromExpr(sqlite3 *, const Expr *, u8, u8, sqlite3_value **); void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); #ifndef SQLITE_AMALGAMATION extern const unsigned char sqlite3OpcodeProperty[]; extern const char sqlite3StrBINARY[]; +extern const unsigned char sqlite3StdTypeLen[]; +extern const char sqlite3StdTypeAffinity[]; +extern const char sqlite3StdTypeMap[]; +extern const char *sqlite3StdType[]; extern const unsigned char sqlite3UpperToLower[]; extern const unsigned char *sqlite3aLTb; extern const unsigned char *sqlite3aEQb; @@ -4814,9 +4928,9 @@ int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*); void sqlite3ColumnDefault(Vdbe *, Table *, int, int); void sqlite3AlterFinishAddColumn(Parse *, Token *); void sqlite3AlterBeginAddColumn(Parse *, SrcList *); -void sqlite3AlterDropColumn(Parse*, SrcList*, Token*); -void *sqlite3RenameTokenMap(Parse*, void*, Token*); -void sqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom); +void sqlite3AlterDropColumn(Parse*, SrcList*, const Token*); +const void *sqlite3RenameTokenMap(Parse*, const void*, const Token*); +void sqlite3RenameTokenRemap(Parse*, const void *pTo, const void *pFrom); void sqlite3RenameExprUnmap(Parse*, Expr*); void sqlite3RenameExprlistUnmap(Parse*, ExprList*); CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*); @@ -4860,6 +4974,8 @@ int sqlite3OpenTempDatabase(Parse *); void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); char *sqlite3StrAccumFinish(StrAccum*); +void sqlite3StrAccumSetError(StrAccum*, u8); +void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); void sqlite3SelectDestInit(SelectDest*,int,int); Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); @@ -4912,7 +5028,7 @@ void sqlite3AutoLoadExtensions(sqlite3*); #endif #ifdef SQLITE_OMIT_VIRTUALTABLE -# define sqlite3VtabClear(Y) +# define sqlite3VtabClear(D,T) # define sqlite3VtabSync(X,Y) SQLITE_OK # define sqlite3VtabRollback(X) # define sqlite3VtabCommit(X) @@ -4949,9 +5065,11 @@ int sqlite3ReadOnlyShadowTables(sqlite3 *db); #ifndef SQLITE_OMIT_VIRTUALTABLE int sqlite3ShadowTableName(sqlite3 *db, const char *zName); int sqlite3IsShadowTableOf(sqlite3*,Table*,const char*); + void sqlite3MarkAllShadowTablesOf(sqlite3*, Table*); #else # define sqlite3ShadowTableName(A,B) 0 # define sqlite3IsShadowTableOf(A,B,C) 0 +# define sqlite3MarkAllShadowTablesOf(A,B) #endif int sqlite3VtabEponymousTableInit(Parse*,Module*); void sqlite3VtabEponymousTableClear(sqlite3*,Module*); @@ -4994,7 +5112,7 @@ const char *sqlite3JournalModename(int); # define sqlite3CteDelete(D,C) # define sqlite3CteWithAdd(P,W,C) ((void*)0) # define sqlite3WithDelete(x,y) -# define sqlite3WithPush(x,y,z) +# define sqlite3WithPush(x,y,z) ((void*)0) #endif #ifndef SQLITE_OMIT_UPSERT Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); @@ -5027,6 +5145,7 @@ const char *sqlite3JournalModename(int); int sqlite3FkRequired(Parse*, Table*, int*, int); u32 sqlite3FkOldmask(Parse*, Table*); FKey *sqlite3FkReferences(Table *); + void sqlite3FkClearTriggerCache(sqlite3*,int); #else #define sqlite3FkActions(a,b,c,d,e,f) #define sqlite3FkCheck(a,b,c,d,e,f) @@ -5034,6 +5153,7 @@ const char *sqlite3JournalModename(int); #define sqlite3FkOldmask(a,b) 0 #define sqlite3FkRequired(a,b,c,d) 0 #define sqlite3FkReferences(a) 0 + #define sqlite3FkClearTriggerCache(a,b) #endif #ifndef SQLITE_OMIT_FOREIGN_KEY void sqlite3FkDelete(sqlite3 *, Table*); @@ -5091,7 +5211,7 @@ void sqlite3MemJournalOpen(sqlite3_file *); void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p); #if SQLITE_MAX_EXPR_DEPTH>0 - int sqlite3SelectExprHeight(Select *); + int sqlite3SelectExprHeight(const Select *); int sqlite3ExprCheckHeight(Parse*, int); #else #define sqlite3SelectExprHeight(x) 0 @@ -5162,8 +5282,8 @@ SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...); */ #ifdef SQLITE_MEMDEBUG void sqlite3MemdebugSetType(void*,u8); - int sqlite3MemdebugHasType(void*,u8); - int sqlite3MemdebugNoType(void*,u8); + int sqlite3MemdebugHasType(const void*,u8); + int sqlite3MemdebugNoType(const void*,u8); #else # define sqlite3MemdebugSetType(X,Y) /* no-op */ # define sqlite3MemdebugHasType(X,Y) 1 @@ -5188,10 +5308,10 @@ int sqlite3DbpageRegister(sqlite3*); int sqlite3DbstatRegister(sqlite3*); #endif -int sqlite3ExprVectorSize(Expr *pExpr); -int sqlite3ExprIsVector(Expr *pExpr); +int sqlite3ExprVectorSize(const Expr *pExpr); +int sqlite3ExprIsVector(const Expr *pExpr); Expr *sqlite3VectorFieldSubexpr(Expr*, int); -Expr *sqlite3ExprForVectorField(Parse*,Expr*,int); +Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int); void sqlite3VectorErrorMsg(Parse*, Expr*); #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 2206bca32..d379f8d5a 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -181,6 +181,7 @@ struct SqliteDb { int nVMStep; /* Another statistic for most recent operation */ int nTransaction; /* Number of nested [transaction] methods */ int openFlags; /* Flags used to open. (SQLITE_OPEN_URI) */ + int nRef; /* Delete object when this reaches 0 */ #ifdef SQLITE_TEST int bLegacyPrepare; /* True to use sqlite3_prepare() */ #endif @@ -517,64 +518,84 @@ static void flushStmtCache(SqliteDb *pDb){ pDb->stmtList = 0; } +/* +** Increment the reference counter on the SqliteDb object. The reference +** should be released by calling delDatabaseRef(). +*/ +static void addDatabaseRef(SqliteDb *pDb){ + pDb->nRef++; +} + +/* +** Decrement the reference counter associated with the SqliteDb object. +** If it reaches zero, delete the object. +*/ +static void delDatabaseRef(SqliteDb *pDb){ + assert( pDb->nRef>0 ); + pDb->nRef--; + if( pDb->nRef==0 ){ + flushStmtCache(pDb); + closeIncrblobChannels(pDb); + sqlite3_close(pDb->db); + while( pDb->pFunc ){ + SqlFunc *pFunc = pDb->pFunc; + pDb->pFunc = pFunc->pNext; + assert( pFunc->pDb==pDb ); + Tcl_DecrRefCount(pFunc->pScript); + Tcl_Free((char*)pFunc); + } + while( pDb->pCollate ){ + SqlCollate *pCollate = pDb->pCollate; + pDb->pCollate = pCollate->pNext; + Tcl_Free((char*)pCollate); + } + if( pDb->zBusy ){ + Tcl_Free(pDb->zBusy); + } + if( pDb->zTrace ){ + Tcl_Free(pDb->zTrace); + } + if( pDb->zTraceV2 ){ + Tcl_Free(pDb->zTraceV2); + } + if( pDb->zProfile ){ + Tcl_Free(pDb->zProfile); + } + if( pDb->zBindFallback ){ + Tcl_Free(pDb->zBindFallback); + } + if( pDb->zAuth ){ + Tcl_Free(pDb->zAuth); + } + if( pDb->zNull ){ + Tcl_Free(pDb->zNull); + } + if( pDb->pUpdateHook ){ + Tcl_DecrRefCount(pDb->pUpdateHook); + } + if( pDb->pPreUpdateHook ){ + Tcl_DecrRefCount(pDb->pPreUpdateHook); + } + if( pDb->pRollbackHook ){ + Tcl_DecrRefCount(pDb->pRollbackHook); + } + if( pDb->pWalHook ){ + Tcl_DecrRefCount(pDb->pWalHook); + } + if( pDb->pCollateNeeded ){ + Tcl_DecrRefCount(pDb->pCollateNeeded); + } + Tcl_Free((char*)pDb); + } +} + /* ** TCL calls this procedure when an sqlite3 database command is ** deleted. */ static void SQLITE_TCLAPI DbDeleteCmd(void *db){ SqliteDb *pDb = (SqliteDb*)db; - flushStmtCache(pDb); - closeIncrblobChannels(pDb); - sqlite3_close(pDb->db); - while( pDb->pFunc ){ - SqlFunc *pFunc = pDb->pFunc; - pDb->pFunc = pFunc->pNext; - assert( pFunc->pDb==pDb ); - Tcl_DecrRefCount(pFunc->pScript); - Tcl_Free((char*)pFunc); - } - while( pDb->pCollate ){ - SqlCollate *pCollate = pDb->pCollate; - pDb->pCollate = pCollate->pNext; - Tcl_Free((char*)pCollate); - } - if( pDb->zBusy ){ - Tcl_Free(pDb->zBusy); - } - if( pDb->zTrace ){ - Tcl_Free(pDb->zTrace); - } - if( pDb->zTraceV2 ){ - Tcl_Free(pDb->zTraceV2); - } - if( pDb->zProfile ){ - Tcl_Free(pDb->zProfile); - } - if( pDb->zBindFallback ){ - Tcl_Free(pDb->zBindFallback); - } - if( pDb->zAuth ){ - Tcl_Free(pDb->zAuth); - } - if( pDb->zNull ){ - Tcl_Free(pDb->zNull); - } - if( pDb->pUpdateHook ){ - Tcl_DecrRefCount(pDb->pUpdateHook); - } - if( pDb->pPreUpdateHook ){ - Tcl_DecrRefCount(pDb->pPreUpdateHook); - } - if( pDb->pRollbackHook ){ - Tcl_DecrRefCount(pDb->pRollbackHook); - } - if( pDb->pWalHook ){ - Tcl_DecrRefCount(pDb->pWalHook); - } - if( pDb->pCollateNeeded ){ - Tcl_DecrRefCount(pDb->pCollateNeeded); - } - Tcl_Free((char*)pDb); + delDatabaseRef(pDb); } /* @@ -1246,6 +1267,7 @@ static int SQLITE_TCLAPI DbTransPostCmd( } pDb->disableAuth--; + delDatabaseRef(pDb); return rc; } @@ -1579,6 +1601,7 @@ static void dbEvalInit( Tcl_IncrRefCount(pArray); } p->evalFlags = evalFlags; + addDatabaseRef(p->pDb); } /* @@ -1719,6 +1742,7 @@ static void dbEvalFinalize(DbEvalContext *p){ } Tcl_DecrRefCount(p->pSql); dbReleaseColumnNames(p); + delDatabaseRef(p->pDb); } /* @@ -2207,7 +2231,7 @@ static int SQLITE_TCLAPI DbObjCmd( return TCL_ERROR; } pResult = Tcl_GetObjResult(interp); - Tcl_SetIntObj(pResult, sqlite3_changes(pDb->db)); + Tcl_SetWideIntObj(pResult, sqlite3_changes64(pDb->db)); break; } @@ -2623,8 +2647,10 @@ static int SQLITE_TCLAPI DbObjCmd( for(i=2; idb)); + Tcl_SetWideIntObj(pResult, sqlite3_total_changes64(pDb->db)); break; } @@ -3435,6 +3461,7 @@ static int SQLITE_TCLAPI DbObjCmd( ** opened above. If not using NRE, evaluate the script directly, then ** call function DbTransPostCmd() to commit (or rollback) the transaction ** or savepoint. */ + addDatabaseRef(pDb); /* DbTransPostCmd() calls delDatabaseRef() */ if( DbUseNre() ){ Tcl_NRAddCallback(interp, DbTransPostCmd, cd, 0, 0, 0); (void)Tcl_NREvalObj(interp, pScript, 0); @@ -3842,6 +3869,7 @@ static int SQLITE_TCLAPI DbMain( }else{ Tcl_CreateObjCommand(interp, zArg, DbObjCmd, (char*)p, DbDeleteCmd); } + p->nRef = 1; return TCL_OK; } @@ -3949,7 +3977,9 @@ static const char *tclsh_main_loop(void){ return zMainloop; } -#define TCLSH_MAIN main /* Needed to fake out mktclapp */ +#ifndef TCLSH_MAIN +# define TCLSH_MAIN main +#endif int SQLITE_CDECL TCLSH_MAIN(int argc, char **argv){ Tcl_Interp *interp; int i; diff --git a/src/test1.c b/src/test1.c index 7cb56d3be..6060290b9 100644 --- a/src/test1.c +++ b/src/test1.c @@ -4850,6 +4850,7 @@ static int SQLITE_TCLAPI test_open_v2( { "SQLITE_OPEN_PRIVATECACHE", SQLITE_OPEN_PRIVATECACHE }, { "SQLITE_OPEN_WAL", SQLITE_OPEN_WAL }, { "SQLITE_OPEN_URI", SQLITE_OPEN_URI }, + { "SQLITE_OPEN_EXRESCODE", SQLITE_OPEN_EXRESCODE }, { 0, 0 } }; rc = Tcl_GetIndexFromObjStruct(interp, apFlag[i], aFlag, sizeof(aFlag[0]), @@ -5348,37 +5349,6 @@ static int SQLITE_TCLAPI test_stmt_int( return TCL_OK; } -/* -** Usage: sqlite_set_magic DB MAGIC-NUMBER -** -** Set the db->magic value. This is used to test error recovery logic. -*/ -static int SQLITE_TCLAPI sqlite_set_magic( - void * clientData, - Tcl_Interp *interp, - int argc, - char **argv -){ - sqlite3 *db; - if( argc!=3 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " DB MAGIC", 0); - return TCL_ERROR; - } - if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR; - if( strcmp(argv[2], "SQLITE_MAGIC_OPEN")==0 ){ - db->magic = SQLITE_MAGIC_OPEN; - }else if( strcmp(argv[2], "SQLITE_MAGIC_CLOSED")==0 ){ - db->magic = SQLITE_MAGIC_CLOSED; - }else if( strcmp(argv[2], "SQLITE_MAGIC_BUSY")==0 ){ - db->magic = SQLITE_MAGIC_BUSY; - }else if( strcmp(argv[2], "SQLITE_MAGIC_ERROR")==0 ){ - db->magic = SQLITE_MAGIC_ERROR; - }else if( Tcl_GetInt(interp, argv[2], (int*)&db->magic) ){ - return TCL_ERROR; - } - return TCL_OK; -} /* ** Usage: sqlite3_interrupt DB @@ -8264,6 +8234,96 @@ static int SQLITE_TCLAPI test_decode_hexdb( return TCL_OK; } +/* +** Client data for the autovacuum_pages callback. +*/ +struct AutovacPageData { + Tcl_Interp *interp; + char *zScript; +}; +typedef struct AutovacPageData AutovacPageData; + +/* +** Callback functions for sqlite3_autovacuum_pages +*/ +static unsigned int test_autovacuum_pages_callback( + void *pClientData, + const char *zSchema, + unsigned int nFilePages, + unsigned int nFreePages, + unsigned int nBytePerPage +){ + AutovacPageData *pData = (AutovacPageData*)pClientData; + Tcl_DString str; + unsigned int x; + char zBuf[100]; + Tcl_DStringInit(&str); + Tcl_DStringAppend(&str, pData->zScript, -1); + Tcl_DStringAppendElement(&str, zSchema); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%u", nFilePages); + Tcl_DStringAppendElement(&str, zBuf); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%u", nFreePages); + Tcl_DStringAppendElement(&str, zBuf); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%u", nBytePerPage); + Tcl_DStringAppendElement(&str, zBuf); + Tcl_ResetResult(pData->interp); + Tcl_Eval(pData->interp, Tcl_DStringValue(&str)); + Tcl_DStringFree(&str); + x = nFreePages; + (void)Tcl_GetIntFromObj(0, Tcl_GetObjResult(pData->interp), (int*)&x); + return x; +} + +/* +** Usage: sqlite3_autovacuum_pages DB SCRIPT +** +** Add an autovacuum-pages callback to database connection DB. The callback +** will invoke SCRIPT, after appending parameters. +** +** If SCRIPT is an empty string or is omitted, then the callback is +** cancelled. +*/ +static int SQLITE_TCLAPI test_autovacuum_pages( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + AutovacPageData *pData; + sqlite3 *db; + int rc; + const char *zScript; + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB ?SCRIPT?"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + zScript = objc==3 ? Tcl_GetString(objv[2]) : 0; + if( zScript ){ + size_t nScript = strlen(zScript); + pData = sqlite3_malloc64( sizeof(*pData) + nScript + 1 ); + if( pData==0 ){ + Tcl_AppendResult(interp, "out of memory", (void*)0); + return TCL_ERROR; + } + pData->interp = interp; + pData->zScript = (char*)&pData[1]; + memcpy(pData->zScript, zScript, nScript+1); + rc = sqlite3_autovacuum_pages(db,test_autovacuum_pages_callback, + pData, sqlite3_free); + }else{ + rc = sqlite3_autovacuum_pages(db, 0, 0, 0); + } + if( rc ){ + char zBuf[1000]; + sqlite3_snprintf(sizeof(zBuf), zBuf, + "sqlite3_autovacuum_pages() returns %d", rc); + Tcl_AppendResult(interp, zBuf, (void*)0); + return TCL_ERROR; + } + return TCL_OK; +} + /* ** Register commands with the TCL interpreter. @@ -8319,7 +8379,6 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "breakpoint", (Tcl_CmdProc*)test_breakpoint }, { "sqlite3_key", (Tcl_CmdProc*)test_key }, { "sqlite3_rekey", (Tcl_CmdProc*)test_rekey }, - { "sqlite_set_magic", (Tcl_CmdProc*)sqlite_set_magic }, { "sqlite3_interrupt", (Tcl_CmdProc*)test_interrupt }, { "sqlite_delete_function", (Tcl_CmdProc*)delete_function }, { "sqlite_delete_collation", (Tcl_CmdProc*)delete_collation }, @@ -8556,6 +8615,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "atomic_batch_write", test_atomic_batch_write, 0 }, { "sqlite3_mmap_warm", test_mmap_warm, 0 }, { "sqlite3_config_sorterref", test_config_sorterref, 0 }, + { "sqlite3_autovacuum_pages", test_autovacuum_pages, 0 }, { "decode_hexdb", test_decode_hexdb, 0 }, { "test_write_db", test_write_db, 0 }, { "sqlite3_register_cksumvfs", test_register_cksumvfs, 0 }, diff --git a/src/test_config.c b/src/test_config.c index 86e4df38f..acf98e36d 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -589,7 +589,7 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "sqlite_options", "schema_version", "1", TCL_GLOBAL_ONLY); #endif -#ifdef SQLITE_ENABLE_SESSION +#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) Tcl_SetVar2(interp, "sqlite_options", "session", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "session", "0", TCL_GLOBAL_ONLY); diff --git a/src/test_multiplex.c b/src/test_multiplex.c index 5ef1ec183..ff1281715 100644 --- a/src/test_multiplex.c +++ b/src/test_multiplex.c @@ -962,26 +962,79 @@ static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){ ** element is the argument to the pragma or NULL if the pragma has no ** argument. */ - if( aFcntl[1] && sqlite3_stricmp(aFcntl[1],"multiplex_truncate")==0 ){ - if( aFcntl[2] && aFcntl[2][0] ){ - if( sqlite3_stricmp(aFcntl[2], "on")==0 - || sqlite3_stricmp(aFcntl[2], "1")==0 ){ - pGroup->bTruncate = 1; - }else - if( sqlite3_stricmp(aFcntl[2], "off")==0 - || sqlite3_stricmp(aFcntl[2], "0")==0 ){ - pGroup->bTruncate = 0; + if( aFcntl[1] && sqlite3_strnicmp(aFcntl[1],"multiplex_",10)==0 ){ + sqlite3_int64 sz = 0; + (void)multiplexFileSize(pConn, &sz); + /* + ** PRAGMA multiplex_truncate=BOOLEAN; + ** PRAGMA multiplex_truncate; + ** + ** Turn the multiplexor truncate feature on or off. Return either + ** "on" or "off" to indicate the new setting. If the BOOLEAN argument + ** is omitted, just return the current value for the truncate setting. + */ + if( sqlite3_stricmp(aFcntl[1],"multiplex_truncate")==0 ){ + if( aFcntl[2] && aFcntl[2][0] ){ + if( sqlite3_stricmp(aFcntl[2], "on")==0 + || sqlite3_stricmp(aFcntl[2], "1")==0 ){ + pGroup->bTruncate = 1; + }else + if( sqlite3_stricmp(aFcntl[2], "off")==0 + || sqlite3_stricmp(aFcntl[2], "0")==0 ){ + pGroup->bTruncate = 0; + } } + /* EVIDENCE-OF: R-27806-26076 The handler for an SQLITE_FCNTL_PRAGMA + ** file control can optionally make the first element of the char** + ** argument point to a string obtained from sqlite3_mprintf() or the + ** equivalent and that string will become the result of the pragma + ** or the error message if the pragma fails. + */ + aFcntl[0] = sqlite3_mprintf(pGroup->bTruncate ? "on" : "off"); + rc = SQLITE_OK; + break; } - /* EVIDENCE-OF: R-27806-26076 The handler for an SQLITE_FCNTL_PRAGMA - ** file control can optionally make the first element of the char** - ** argument point to a string obtained from sqlite3_mprintf() or the - ** equivalent and that string will become the result of the pragma - ** or the error message if the pragma fails. + /* + ** PRAGMA multiplex_enabled; + ** + ** Return 0 or 1 depending on whether the multiplexor is enabled or + ** disabled, respectively. */ - aFcntl[0] = sqlite3_mprintf(pGroup->bTruncate ? "on" : "off"); - rc = SQLITE_OK; - break; + if( sqlite3_stricmp(aFcntl[1],"multiplex_enabled")==0 ){ + aFcntl[0] = sqlite3_mprintf("%d", pGroup->bEnabled!=0); + rc = SQLITE_OK; + break; + } + /* + ** PRAGMA multiplex_chunksize; + ** + ** Return the chunksize for the multiplexor, or no-op if the + ** multiplexor is not active. + */ + if( sqlite3_stricmp(aFcntl[1],"multiplex_chunksize")==0 + && pGroup->bEnabled + ){ + aFcntl[0] = sqlite3_mprintf("%u", pGroup->szChunk); + rc = SQLITE_OK; + break; + } + /* + ** PRAGMA multiplex_filecount; + ** + ** Return the number of disk files currently in use by the + ** multiplexor. This should be the total database size size + ** divided by the chunksize and rounded up. + */ + if( sqlite3_stricmp(aFcntl[1],"multiplex_filecount")==0 ){ + int n = 0; + int ii; + for(ii=0; iinReal; ii++){ + if( pGroup->aReal[ii].p!=0 ) n++; + } + aFcntl[0] = sqlite3_mprintf("%d", n); + rc = SQLITE_OK; + break; + } } /* If the multiplexor does not handle the pragma, pass it through ** into the default case. */ diff --git a/src/test_tclsh.c b/src/test_tclsh.c index 9988214b0..707c16812 100644 --- a/src/test_tclsh.c +++ b/src/test_tclsh.c @@ -87,6 +87,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ extern int Sqlitetestintarray_Init(Tcl_Interp*); extern int Sqlitetestvfs_Init(Tcl_Interp *); extern int Sqlitetestrtree_Init(Tcl_Interp*); + extern int Sqlitetestrtreedoc_Init(Tcl_Interp*); extern int Sqlitequota_Init(Tcl_Interp*); extern int Sqlitemultiplex_Init(Tcl_Interp*); extern int SqliteSuperlock_Init(Tcl_Interp*); @@ -156,6 +157,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ Sqlitetestintarray_Init(interp); Sqlitetestvfs_Init(interp); Sqlitetestrtree_Init(interp); + Sqlitetestrtreedoc_Init(interp); Sqlitequota_Init(interp); Sqlitemultiplex_Init(interp); SqliteSuperlock_Init(interp); diff --git a/src/tokenize.c b/src/tokenize.c index 5d250e6f6..e128d1614 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -573,6 +573,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ int mxSqlLen; /* Max length of an SQL string */ + Parse *pParentParse = 0; /* Outer parse context, if any */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ #endif @@ -608,7 +609,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ assert( pParse->pNewTrigger==0 ); assert( pParse->nVar==0 ); assert( pParse->pVList==0 ); - pParse->pParentParse = db->pParse; + pParentParse = db->pParse; db->pParse = pParse; while( 1 ){ n = sqlite3GetToken((u8*)zSql, &tokenType); @@ -723,8 +724,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); } sqlite3DbFree(db, pParse->pVList); - db->pParse = pParse->pParentParse; - pParse->pParentParse = 0; + db->pParse = pParentParse; assert( nErr==0 || pParse->rc!=SQLITE_OK ); return nErr; } diff --git a/src/treeview.c b/src/treeview.c index c737a1b98..1b19ea679 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -142,6 +142,8 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ } if( pItem->fg.jointype & JT_LEFT ){ sqlite3_str_appendf(&x, " LEFT-JOIN"); + }else if( pItem->fg.jointype & JT_CROSS ){ + sqlite3_str_appendf(&x, " CROSS-JOIN"); } if( pItem->fg.fromDDL ){ sqlite3_str_appendf(&x, " DDL"); @@ -440,6 +442,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewLine(pView, "COLUMN(%d)%s%s", pExpr->iColumn, zFlgs, zOp2); }else{ + assert( ExprUseYTab(pExpr) ); sqlite3TreeViewLine(pView, "{%d:%d} pTab=%p%s", pExpr->iTable, pExpr->iColumn, pExpr->y.pTab, zFlgs); @@ -459,11 +462,13 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ } #ifndef SQLITE_OMIT_FLOATING_POINT case TK_FLOAT: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_STRING: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%Q", pExpr->u.zToken); break; } @@ -472,17 +477,19 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ break; } case TK_TRUEFALSE: { - sqlite3TreeViewLine(pView, - sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE"); + sqlite3TreeViewLine(pView,"%s%s", + sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE", zFlgs); break; } #ifndef SQLITE_OMIT_BLOB_LITERAL case TK_BLOB: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_VARIABLE: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"VARIABLE(%s,%d)", pExpr->u.zToken, pExpr->iColumn); break; @@ -492,12 +499,14 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ break; } case TK_ID: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"ID \"%w\"", pExpr->u.zToken); break; } #ifndef SQLITE_OMIT_CAST case TK_CAST: { /* Expressions of the form: CAST(pLeft AS token) */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"CAST %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; @@ -547,6 +556,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ } case TK_SPAN: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; @@ -558,6 +568,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ ** up in the treeview output as "SOFT-COLLATE". Explicit COLLATE ** operators that appear in the original SQL always have the ** EP_Collate bit set and appear in treeview output as just "COLLATE" */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView, "%sCOLLATE %Q%s", !ExprHasProperty(pExpr, EP_Collate) ? "SOFT-" : "", pExpr->u.zToken, zFlgs); @@ -573,6 +584,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ pFarg = 0; pWin = 0; }else{ + assert( ExprUseXList(pExpr) ); pFarg = pExpr->x.pList; #ifndef SQLITE_OMIT_WINDOWFUNC pWin = ExprHasProperty(pExpr, EP_WinFunc) ? pExpr->y.pWin : 0; @@ -580,6 +592,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ pWin = 0; #endif } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); if( pExpr->op==TK_AGG_FUNCTION ){ sqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q%s agg=%d[%d]/%p", pExpr->op2, pExpr->u.zToken, zFlgs, @@ -611,11 +624,13 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: { + assert( ExprUseXSelect(pExpr) ); sqlite3TreeViewLine(pView, "EXISTS-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_SELECT: { + assert( ExprUseXSelect(pExpr) ); sqlite3TreeViewLine(pView, "subquery-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; @@ -623,7 +638,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ case TK_IN: { sqlite3TreeViewLine(pView, "IN flags=0x%x", pExpr->flags); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); }else{ sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); @@ -644,9 +659,12 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ ** Z is stored in pExpr->pList->a[1].pExpr. */ case TK_BETWEEN: { - Expr *pX = pExpr->pLeft; - Expr *pY = pExpr->x.pList->a[0].pExpr; - Expr *pZ = pExpr->x.pList->a[1].pExpr; + const Expr *pX, *pY, *pZ; + pX = pExpr->pLeft; + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + pY = pExpr->x.pList->a[0].pExpr; + pZ = pExpr->x.pList->a[1].pExpr; sqlite3TreeViewLine(pView, "BETWEEN"); sqlite3TreeViewExpr(pView, pX, 1); sqlite3TreeViewExpr(pView, pY, 1); @@ -668,6 +686,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ case TK_CASE: { sqlite3TreeViewLine(pView, "CASE"); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + assert( ExprUseXList(pExpr) ); sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); break; } @@ -680,6 +699,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ case OE_Fail: zType = "fail"; break; case OE_Ignore: zType = "ignore"; break; } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView, "RAISE %s(%Q)", zType, pExpr->u.zToken); break; } @@ -692,12 +712,16 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ } case TK_VECTOR: { char *z = sqlite3_mprintf("VECTOR%s",zFlgs); + assert( ExprUseXList(pExpr) ); sqlite3TreeViewBareExprList(pView, pExpr->x.pList, z); sqlite3_free(z); break; } case TK_SELECT_COLUMN: { - sqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn); + sqlite3TreeViewLine(pView, "SELECT-COLUMN %d of [0..%d]%s", + pExpr->iColumn, pExpr->iTable-1, + pExpr->pRight==pExpr->pLeft ? " (SELECT-owner)" : ""); + assert( ExprUseXSelect(pExpr->pLeft) ); sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0); break; } @@ -714,6 +738,15 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewExpr(pView, &tmp, 0); break; } + case TK_ROW: { + if( pExpr->iColumn<=0 ){ + sqlite3TreeViewLine(pView, "First FROM table rowid"); + }else{ + sqlite3TreeViewLine(pView, "First FROM table column %d", + pExpr->iColumn-1); + } + break; + } default: { sqlite3TreeViewLine(pView, "op=%d", pExpr->op); break; diff --git a/src/trigger.c b/src/trigger.c index 90e6ef4a7..7e3a053a7 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -219,12 +219,12 @@ void sqlite3BeginTrigger( /* INSTEAD of triggers are only for views and views only support INSTEAD ** of triggers. */ - if( pTab->pSelect && tr_tm!=TK_INSTEAD ){ + if( IsView(pTab) && tr_tm!=TK_INSTEAD ){ sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S", (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a); goto trigger_orphan_error; } - if( !pTab->pSelect && tr_tm==TK_INSTEAD ){ + if( !IsView(pTab) && tr_tm==TK_INSTEAD ){ sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF" " trigger on table: %S", pTableName->a); goto trigger_orphan_error; @@ -361,7 +361,7 @@ void sqlite3FinishTrigger( z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n); testcase( z==0 ); sqlite3NestedParse(pParse, - "INSERT INTO %Q." DFLT_SCHEMA_TABLE + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE " VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", db->aDb[iDb].zDbSName, zName, pTrig->table, z); @@ -675,7 +675,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ */ if( (v = sqlite3GetVdbe(pParse))!=0 ){ sqlite3NestedParse(pParse, - "DELETE FROM %Q." DFLT_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'", + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'", db->aDb[iDb].zDbSName, pTrigger->zName ); sqlite3ChangeCookie(pParse, iDb); @@ -877,11 +877,11 @@ static ExprList *sqlite3ExpandReturning( for(jj=0; jjnCol; jj++){ Expr *pNewExpr; if( IsHiddenColumn(pTab->aCol+jj) ) continue; - pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zName); + pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zCnName); pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); if( !db->mallocFailed ){ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; - pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zName); + pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zCnName); pItem->eEName = ENAME_NAME; } } @@ -926,13 +926,14 @@ static void codeReturningTrigger( sSelect.pSrc = &sFrom; sFrom.nSrc = 1; sFrom.a[0].pTab = pTab; + sFrom.a[0].iCursor = -1; sqlite3SelectPrep(pParse, &sSelect, 0); if( db->mallocFailed==0 && pParse->nErr==0 ){ sqlite3GenerateColumnNames(pParse, &sSelect); } sqlite3ExprListDelete(db, sSelect.pEList); pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab); - if( pNew ){ + if( !db->mallocFailed ){ NameContext sNC; memset(&sNC, 0, sizeof(sNC)); if( pReturning->nRetCol==0 ){ @@ -944,7 +945,9 @@ static void codeReturningTrigger( sNC.ncFlags = NC_UBaseReg; pParse->eTriggerOp = pTrigger->op; pParse->pTriggerTab = pTab; - if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK ){ + if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK + && !db->mallocFailed + ){ int i; int nCol = pNew->nExpr; int reg = pParse->nMem+1; @@ -952,16 +955,17 @@ static void codeReturningTrigger( pReturning->iRetReg = reg; for(i=0; ia[i].pExpr; + assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */ sqlite3ExprCodeFactorable(pParse, pCol, reg+i); } sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i); sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1); sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1); } - sqlite3ExprListDelete(db, pNew); - pParse->eTriggerOp = 0; - pParse->pTriggerTab = 0; } + sqlite3ExprListDelete(db, pNew); + pParse->eTriggerOp = 0; + pParse->pTriggerTab = 0; } diff --git a/src/update.c b/src/update.c index 132232724..484bee47c 100644 --- a/src/update.c +++ b/src/update.c @@ -60,13 +60,14 @@ static void updateVirtualTable( */ void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ assert( pTab!=0 ); - if( !pTab->pSelect ){ + if( !IsView(pTab) ){ sqlite3_value *pValue = 0; u8 enc = ENC(sqlite3VdbeDb(v)); Column *pCol = &pTab->aCol[i]; - VdbeComment((v, "%s.%s", pTab->zName, pCol->zName)); + VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName)); assert( inCol ); - sqlite3ValueFromExpr(sqlite3VdbeDb(v), pCol->pDflt, enc, + sqlite3ValueFromExpr(sqlite3VdbeDb(v), + sqlite3ColumnExpr(pTab,pCol), enc, pCol->affinity, &pValue); if( pValue ){ sqlite3VdbeAppendP4(v, pValue, P4_MEM); @@ -236,7 +237,7 @@ static void updateFromSelect( pList = sqlite3ExprListAppend(pParse, pList, pNew); } eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom; - }else if( pTab->pSelect ){ + }else if( IsView(pTab) ){ for(i=0; inCol; i++){ pList = sqlite3ExprListAppend(pParse, pList, exprRowColumn(pParse, i)); } @@ -259,8 +260,9 @@ static void updateFromSelect( } } pSelect = sqlite3SelectNew(pParse, pList, - pSrc, pWhere2, pGrp, 0, pOrderBy2, SF_UpdateFrom|SF_IncludeHidden, pLimit2 + pSrc, pWhere2, pGrp, 0, pOrderBy2, SF_UFSrcCheck|SF_IncludeHidden, pLimit2 ); + if( pSelect ) pSelect->selFlags |= SF_OrderByReqd; sqlite3SelectDestInit(&dest, eDest, iEph); dest.iSDParm2 = (pPk ? pPk->nKeyCol : -1); sqlite3Select(pParse, pSelect, &dest); @@ -360,7 +362,7 @@ void sqlite3Update( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask); - isView = pTab->pSelect!=0; + isView = IsView(pTab); assert( pTrigger || tmask==0 ); #else # define pTrigger 0 @@ -449,13 +451,16 @@ void sqlite3Update( */ chngRowid = chngPk = 0; for(i=0; inExpr; i++){ + u8 hCol = sqlite3StrIHash(pChanges->a[i].zEName); /* If this is an UPDATE with a FROM clause, do not resolve expressions ** here. The call to sqlite3Select() below will do that. */ if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){ goto update_cleanup; } for(j=0; jnCol; j++){ - if( sqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zEName)==0 ){ + if( pTab->aCol[j].hName==hCol + && sqlite3StrICmp(pTab->aCol[j].zCnName, pChanges->a[i].zEName)==0 + ){ if( j==pTab->iPKey ){ chngRowid = 1; pRowidExpr = pChanges->a[i].pExpr; @@ -469,7 +474,7 @@ void sqlite3Update( testcase( pTab->aCol[j].colFlags & COLFLAG_STORED ); sqlite3ErrorMsg(pParse, "cannot UPDATE generated column \"%s\"", - pTab->aCol[j].zName); + pTab->aCol[j].zCnName); goto update_cleanup; } #endif @@ -493,7 +498,7 @@ void sqlite3Update( { int rc; rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName, - j<0 ? "ROWID" : pTab->aCol[j].zName, + j<0 ? "ROWID" : pTab->aCol[j].zCnName, db->aDb[iDb].zDbSName); if( rc==SQLITE_DENY ){ goto update_cleanup; @@ -525,8 +530,10 @@ void sqlite3Update( for(i=0; inCol; i++){ if( aXRef[i]>=0 ) continue; if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 ) continue; - if( sqlite3ExprReferencesUpdatedColumn(pTab->aCol[i].pDflt, - aXRef, chngRowid) ){ + if( sqlite3ExprReferencesUpdatedColumn( + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + aXRef, chngRowid) + ){ aXRef[i] = 99999; bProgress = 1; } diff --git a/src/upsert.c b/src/upsert.c index 982dc7dbc..fb6c7c0c0 100644 --- a/src/upsert.c +++ b/src/upsert.c @@ -287,7 +287,7 @@ void sqlite3UpsertDoUpdate( k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); sqlite3VdbeAddOp3(v, OP_Column, iCur, k, iPk+i); VdbeComment((v, "%s.%s", pIdx->zName, - pTab->aCol[pPk->aiColumn[i]].zName)); + pTab->aCol[pPk->aiColumn[i]].zCnName)); } sqlite3VdbeVerifyAbortable(v, OE_Abort); i = sqlite3VdbeAddOp4Int(v, OP_Found, iDataCur, 0, iPk, nPk); diff --git a/src/util.c b/src/util.c index fc0c2042b..8452aea66 100644 --- a/src/util.c +++ b/src/util.c @@ -21,16 +21,6 @@ #include #endif -/* -** Routine needed to support the testcase() macro. -*/ -#ifdef SQLITE_COVERAGE_TEST -void sqlite3Coverage(int x){ - static unsigned dummy = 0; - dummy += (unsigned)x; -} -#endif - /* ** Calls to sqlite3FaultSim() are used to simulate a failure during testing, ** or to bypass normal error detection during testing in order to let @@ -60,11 +50,21 @@ int sqlite3FaultSim(int iTest){ #ifndef SQLITE_OMIT_FLOATING_POINT /* ** Return true if the floating point value is Not a Number (NaN). +** +** Use the math library isnan() function if compiled with SQLITE_HAVE_ISNAN. +** Otherwise, we have our own implementation that works on most systems. */ int sqlite3IsNaN(double x){ + int rc; /* The value return */ +#if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN u64 y; memcpy(&y,&x,sizeof(y)); - return IsNaN(y); + rc = IsNaN(y); +#else + rc = isnan(x); +#endif /* HAVE_ISNAN */ + testcase( rc ); + return rc; } #endif /* SQLITE_OMIT_FLOATING_POINT */ @@ -89,8 +89,14 @@ int sqlite3Strlen30(const char *z){ ** the column name if and only if the COLFLAG_HASTYPE flag is set. */ char *sqlite3ColumnType(Column *pCol, char *zDflt){ - if( (pCol->colFlags & COLFLAG_HASTYPE)==0 ) return zDflt; - return pCol->zName + strlen(pCol->zName) + 1; + if( pCol->colFlags & COLFLAG_HASTYPE ){ + return pCol->zCnName + strlen(pCol->zCnName) + 1; + }else if( pCol->eCType ){ + assert( pCol->eCType<=SQLITE_N_STDTYPE ); + return (char*)sqlite3StdType[pCol->eCType-1]; + }else{ + return zDflt; + } } /* @@ -261,11 +267,34 @@ void sqlite3Dequote(char *z){ z[j] = 0; } void sqlite3DequoteExpr(Expr *p){ + assert( !ExprHasProperty(p, EP_IntValue) ); assert( sqlite3Isquote(p->u.zToken[0]) ); p->flags |= p->u.zToken[0]=='"' ? EP_Quoted|EP_DblQuoted : EP_Quoted; sqlite3Dequote(p->u.zToken); } +/* +** If the input token p is quoted, try to adjust the token to remove +** the quotes. This is not always possible: +** +** "abc" -> abc +** "ab""cd" -> (not possible because of the interior "") +** +** Remove the quotes if possible. This is a optimization. The overall +** system should still return the correct answer even if this routine +** is always a no-op. +*/ +void sqlite3DequoteToken(Token *p){ + unsigned int i; + if( p->n<2 ) return; + if( !sqlite3Isquote(p->z[0]) ) return; + for(i=1; in-1; i++){ + if( sqlite3Isquote(p->z[i]) ) return; + } + p->n -= 2; + p->z++; +} + /* ** Generate a Token object from a string */ @@ -1371,13 +1400,13 @@ static void logBadConnection(const char *zType){ ** used as an argument to sqlite3_errmsg() or sqlite3_close(). */ int sqlite3SafetyCheckOk(sqlite3 *db){ - u32 magic; + u8 eOpenState; if( db==0 ){ logBadConnection("NULL"); return 0; } - magic = db->magic; - if( magic!=SQLITE_MAGIC_OPEN ){ + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_OPEN ){ if( sqlite3SafetyCheckSickOrOk(db) ){ testcase( sqlite3GlobalConfig.xLog!=0 ); logBadConnection("unopened"); @@ -1388,11 +1417,11 @@ int sqlite3SafetyCheckOk(sqlite3 *db){ } } int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ - u32 magic; - magic = db->magic; - if( magic!=SQLITE_MAGIC_SICK && - magic!=SQLITE_MAGIC_OPEN && - magic!=SQLITE_MAGIC_BUSY ){ + u8 eOpenState; + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_SICK && + eOpenState!=SQLITE_STATE_OPEN && + eOpenState!=SQLITE_STATE_BUSY ){ testcase( sqlite3GlobalConfig.xLog!=0 ); logBadConnection("invalid"); return 0; diff --git a/src/vacuum.c b/src/vacuum.c index 93e2307d1..bcab1de14 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -151,8 +151,8 @@ SQLITE_NOINLINE int sqlite3RunVacuum( Btree *pTemp; /* The temporary database we vacuum into */ u32 saved_mDbFlags; /* Saved value of db->mDbFlags */ u64 saved_flags; /* Saved value of db->flags */ - int saved_nChange; /* Saved value of db->nChange */ - int saved_nTotalChange; /* Saved value of db->nTotalChange */ + i64 saved_nChange; /* Saved value of db->nChange */ + i64 saved_nTotalChange; /* Saved value of db->nTotalChange */ u32 saved_openFlags; /* Saved value of db->openFlags */ u8 saved_mTrace; /* Saved trace settings */ Db *pDb = 0; /* Database to detach at end of vacuum */ @@ -250,7 +250,9 @@ SQLITE_NOINLINE int sqlite3RunVacuum( /* Do not attempt to change the page size for a WAL database */ if( sqlite3PagerGetJournalMode(sqlite3BtreePager(pMain)) - ==PAGER_JOURNALMODE_WAL ){ + ==PAGER_JOURNALMODE_WAL + && pOut==0 + ){ db->nextPagesize = 0; } diff --git a/src/vdbe.c b/src/vdbe.c index 4442f7d9f..5ca253a07 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -671,6 +671,19 @@ static Mem *out2Prerelease(Vdbe *p, VdbeOp *pOp){ } } +/* +** Return the symbolic name for the data type of a pMem +*/ +static const char *vdbeMemTypeName(Mem *pMem){ + static const char *azTypes[] = { + /* SQLITE_INTEGER */ "INT", + /* SQLITE_FLOAT */ "REAL", + /* SQLITE_TEXT */ "TEXT", + /* SQLITE_BLOB */ "BLOB", + /* SQLITE_NULL */ "NULL" + }; + return azTypes[sqlite3_value_type(pMem)-1]; +} /* ** Execute as much of a VDBE program as we can. @@ -2502,6 +2515,22 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ break; } +/* Opcode: IsNullOrType P1 P2 P3 * * +** Synopsis: if typeof(r[P1]) IN (P3,5) goto P2 +** +** Jump to P2 if the value in register P1 is NULL or has a datatype P3. +** P3 is an integer which should be one of SQLITE_INTEGER, SQLITE_FLOAT, +** SQLITE_BLOB, SQLITE_NULL, or SQLITE_TEXT. +*/ +case OP_IsNullOrType: { /* jump, in1 */ + int doTheJump; + pIn1 = &aMem[pOp->p1]; + doTheJump = (pIn1->flags & MEM_Null)!=0 || sqlite3_value_type(pIn1)==pOp->p3; + VdbeBranchTaken( doTheJump, 2); + if( doTheJump ) goto jump_to_p2; + break; +} + /* Opcode: ZeroOrNull P1 P2 P3 * * ** Synopsis: r[P2] = 0 OR NULL ** @@ -2870,6 +2899,108 @@ case OP_Column: { } } +/* Opcode: TypeCheck P1 P2 P3 P4 * +** Synopsis: typecheck(r[P1@P2]) +** +** Apply affinities to the range of P2 registers beginning with P1. +** Take the affinities from the Table object in P4. If any value +** cannot be coerced into the correct type, then raise an error. +** +** This opcode is similar to OP_Affinity except that this opcode +** forces the register type to the Table column type. This is used +** to implement "strict affinity". +** +** GENERATED ALWAYS AS ... STATIC columns are only checked if P3 +** is zero. When P3 is non-zero, no type checking occurs for +** static generated columns. Virtual columns are computed at query time +** and so they are never checked. +** +** Preconditions: +** +**
        +**
      • P2 should be the number of non-virtual columns in the +** table of P4. +**
      • Table P4 should be a STRICT table. +**
      +** +** If any precondition is false, an assertion fault occurs. +*/ +case OP_TypeCheck: { + Table *pTab; + Column *aCol; + int i; + + assert( pOp->p4type==P4_TABLE ); + pTab = pOp->p4.pTab; + assert( pTab->tabFlags & TF_Strict ); + assert( pTab->nNVCol==pOp->p2 ); + aCol = pTab->aCol; + pIn1 = &aMem[pOp->p1]; + for(i=0; inCol; i++){ + if( aCol[i].colFlags & COLFLAG_GENERATED ){ + if( aCol[i].colFlags & COLFLAG_VIRTUAL ) continue; + if( pOp->p3 ){ pIn1++; continue; } + } + assert( pIn1 < &aMem[pOp->p1+pOp->p2] ); + applyAffinity(pIn1, aCol[i].affinity, encoding); + if( (pIn1->flags & MEM_Null)==0 ){ + switch( aCol[i].eCType ){ + case COLTYPE_BLOB: { + if( (pIn1->flags & MEM_Blob)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_INTEGER: + case COLTYPE_INT: { + if( (pIn1->flags & MEM_Int)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_TEXT: { + if( (pIn1->flags & MEM_Str)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_REAL: { + if( pIn1->flags & MEM_Int ){ + /* When applying REAL affinity, if the result is still an MEM_Int + ** that will fit in 6 bytes, then change the type to MEM_IntReal + ** so that we keep the high-resolution integer value but know that + ** the type really wants to be REAL. */ + testcase( pIn1->u.i==140737488355328LL ); + testcase( pIn1->u.i==140737488355327LL ); + testcase( pIn1->u.i==-140737488355328LL ); + testcase( pIn1->u.i==-140737488355329LL ); + if( pIn1->u.i<=140737488355327LL && pIn1->u.i>=-140737488355328LL){ + pIn1->flags |= MEM_IntReal; + pIn1->flags &= ~MEM_Int; + }else{ + pIn1->u.r = (double)pIn1->u.i; + pIn1->flags |= MEM_Real; + pIn1->flags &= ~MEM_Int; + } + }else if( (pIn1->flags & MEM_Real)==0 ){ + goto vdbe_type_error; + } + break; + } + default: { + /* COLTYPE_ANY. Accept anything. */ + break; + } + } + } + REGISTER_TRACE((int)(pIn1-aMem), pIn1); + pIn1++; + } + assert( pIn1 == &aMem[pOp->p1+pOp->p2] ); + break; + +vdbe_type_error: + sqlite3VdbeError(p, "cannot store %s value in %s column %s.%s", + vdbeMemTypeName(pIn1), sqlite3StdType[aCol[i].eCType-1], + pTab->zName, aCol[i].zCnName); + rc = SQLITE_CONSTRAINT_DATATYPE; + goto abort_due_to_error; +} + /* Opcode: Affinity P1 P2 * P4 * ** Synopsis: affinity(r[P1@P2]) ** @@ -3533,8 +3664,16 @@ case OP_Transaction: { assert( pOp->p2>=0 && pOp->p2<=2 ); assert( pOp->p1>=0 && pOp->p1nDb ); assert( DbMaskTest(p->btreeMask, pOp->p1) ); - if( pOp->p2 && (db->flags & SQLITE_QueryOnly)!=0 ){ - rc = SQLITE_READONLY; + assert( rc==SQLITE_OK ); + if( pOp->p2 && (db->flags & (SQLITE_QueryOnly|SQLITE_CorruptRdOnly))!=0 ){ + if( db->flags & SQLITE_QueryOnly ){ + /* Writes prohibited by the "PRAGMA query_only=TRUE" statement */ + rc = SQLITE_READONLY; + }else{ + /* Writes prohibited due to a prior SQLITE_CORRUPT in the current + ** transaction */ + rc = SQLITE_CORRUPT; + } goto abort_due_to_error; } pBt = db->aDb[pOp->p1].pBt; @@ -3576,7 +3715,8 @@ case OP_Transaction: { } } assert( pOp->p5==0 || pOp->p4type==P4_INT32 ); - if( pOp->p5 + if( rc==SQLITE_OK + && pOp->p5 && (iMeta!=pOp->p3 || db->aDb[pOp->p1].pSchema->iGeneration!=pOp->p4.i) ){ @@ -3673,6 +3813,7 @@ case OP_SetCookie: { /* When the schema cookie changes, record the new cookie internally */ pDb->pSchema->schema_cookie = pOp->p3 - pOp->p5; db->mDbFlags |= DBFLAG_SchemaChange; + sqlite3FkClearTriggerCache(db, pOp->p1); }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ pDb->pSchema->file_format = pOp->p3; @@ -3786,6 +3927,8 @@ case OP_ReopenIdx: { pCur = p->apCsr[pOp->p1]; if( pCur && pCur->pgnoRoot==(u32)pOp->p2 ){ assert( pCur->iDb==pOp->p3 ); /* Guaranteed by the code generator */ + assert( pCur->eCurType==CURTYPE_BTREE ); + sqlite3BtreeClearCursor(pCur->uc.pCursor); goto open_cursor_set_hints; } /* If the cursor is not currently open or is open on a different @@ -3968,7 +4111,7 @@ case OP_OpenEphemeral: { aMem[pOp->p3].z = ""; } pCx = p->apCsr[pOp->p1]; - if( pCx && !pCx->hasBeenDuped ){ + if( pCx && !pCx->hasBeenDuped && ALWAYS(pOp->p2<=pCx->nField) ){ /* If the ephermeral table is already open and has no duplicates from ** OP_OpenDup, then erase all existing content so that the table is ** empty again, rather than creating a new table. */ @@ -4273,6 +4416,7 @@ case OP_SeekGT: { /* jump, in3, group */ /* If the P3 value could not be converted into an integer without ** loss of information, then special processing is required... */ if( (newType & (MEM_Int|MEM_IntReal))==0 ){ + int c; if( (newType & MEM_Real)==0 ){ if( (newType & MEM_Null) || oc>=OP_SeekGE ){ VdbeBranchTaken(1,2); @@ -4282,7 +4426,8 @@ case OP_SeekGT: { /* jump, in3, group */ if( rc!=SQLITE_OK ) goto abort_due_to_error; goto seek_not_found; } - }else + } + c = sqlite3IntFloatCompare(iKey, pIn3->u.r); /* If the approximation iKey is larger than the actual real search ** term, substitute >= for > and < for <=. e.g. if the search term @@ -4291,7 +4436,7 @@ case OP_SeekGT: { /* jump, in3, group */ ** (x > 4.9) -> (x >= 5) ** (x <= 4.9) -> (x < 5) */ - if( pIn3->u.r<(double)iKey ){ + if( c>0 ){ assert( OP_SeekGE==(OP_SeekGT-1) ); assert( OP_SeekLT==(OP_SeekLE-1) ); assert( (OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001) ); @@ -4300,14 +4445,14 @@ case OP_SeekGT: { /* jump, in3, group */ /* If the approximation iKey is smaller than the actual real search ** term, substitute <= for < and > for >=. */ - else if( pIn3->u.r>(double)iKey ){ + else if( c<0 ){ assert( OP_SeekLE==(OP_SeekLT+1) ); assert( OP_SeekGT==(OP_SeekGE+1) ); assert( (OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001) ); if( (oc & 0x0001)==(OP_SeekLT & 0x0001) ) oc++; } } - rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)iKey, 0, &res); + rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)iKey, 0, &res); pC->movetoTarget = iKey; /* Used by OP_Delete */ if( rc!=SQLITE_OK ){ goto abort_due_to_error; @@ -4354,7 +4499,7 @@ case OP_SeekGT: { /* jump, in3, group */ { int i; for(i=0; iuc.pCursor, &r, 0, 0, &res); + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &res); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } @@ -4773,7 +4918,7 @@ case OP_Found: { /* jump, in3 */ } } } - rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res); + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &res); if( pFree ) sqlite3DbFreeNN(db, pFree); if( rc!=SQLITE_OK ){ goto abort_due_to_error; @@ -4882,7 +5027,7 @@ case OP_NotExists: /* jump, in3 */ pCrsr = pC->uc.pCursor; assert( pCrsr!=0 ); res = 0; - rc = sqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res); + rc = sqlite3BtreeTableMoveto(pCrsr, iKey, 0, &res); assert( rc==SQLITE_OK || res==0 ); pC->movetoTarget = iKey; /* Used by OP_Delete */ pC->nullRow = 0; @@ -5039,7 +5184,7 @@ case OP_NewRowid: { /* out2 */ do{ sqlite3_randomness(sizeof(v), &v); v &= (MAX_ROWID>>1); v++; /* Ensure that v is greater than zero */ - }while( ((rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)v, + }while( ((rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)v, 0, &res))==SQLITE_OK) && (res==0) && (++cnt<100)); @@ -5129,7 +5274,7 @@ case OP_Insert: { assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) ); }else{ pTab = 0; - zDb = 0; /* Not needed. Silence a compiler warning. */ + zDb = 0; } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK @@ -5282,13 +5427,14 @@ case OP_Delete: { pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor); } }else{ - zDb = 0; /* Not needed. Silence a compiler warning. */ - pTab = 0; /* Not needed. Silence a compiler warning. */ + zDb = 0; + pTab = 0; } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update-hook if required. */ - if( db->xPreUpdateCallback && pOp->p4.pTab ){ + assert( db->xPreUpdateCallback==0 || pTab==pOp->p4.pTab ); + if( db->xPreUpdateCallback && pTab ){ assert( !(opflags & OPFLAG_ISUPDATE) || HasRowid(pTab)==0 || (aMem[pOp->p3].flags & MEM_Int) @@ -5329,7 +5475,7 @@ case OP_Delete: { /* Invoke the update-hook if required. */ if( opflags & OPFLAG_NCHANGE ){ p->nChange++; - if( db->xUpdateCallback && HasRowid(pTab) ){ + if( db->xUpdateCallback && ALWAYS(pTab!=0) && HasRowid(pTab) ){ db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName, pC->movetoTarget); assert( pC->iDb>=0 ); @@ -5916,7 +6062,8 @@ case OP_SorterInsert: { /* in2 */ ** an UPDATE or DELETE statement and the index entry to be updated ** or deleted is not found. For some uses of IdxDelete ** (example: the EXCEPT operator) it does not matter that no matching -** entry is found. For those cases, P5 is zero. +** entry is found. For those cases, P5 is zero. Also, do not raise +** this (self-correcting and non-critical) error if in writable_schema mode. */ case OP_IdxDelete: { VdbeCursor *pC; @@ -5937,12 +6084,12 @@ case OP_IdxDelete: { r.nField = (u16)pOp->p3; r.default_rc = 0; r.aMem = &aMem[pOp->p2]; - rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res); + rc = sqlite3BtreeIndexMoveto(pCrsr, &r, &res); if( rc ) goto abort_due_to_error; if( res==0 ){ rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); if( rc ) goto abort_due_to_error; - }else if( pOp->p5 ){ + }else if( pOp->p5 && !sqlite3WritableSchema(db) ){ rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); goto abort_due_to_error; } @@ -6250,7 +6397,7 @@ case OP_Destroy: { /* out2 */ ** See also: Destroy */ case OP_Clear: { - int nChange; + i64 nChange; sqlite3VdbeIncrWriteCounter(p, 0); nChange = 0; @@ -6376,7 +6523,7 @@ case OP_ParseSchema: { }else #endif { - zSchema = DFLT_SCHEMA_TABLE; + zSchema = LEGACY_SCHEMA_TABLE; initData.db = db; initData.iDb = iDb; initData.pzErrMsg = &p->zErrMsg; @@ -7596,6 +7743,7 @@ case OP_VFilter: { /* jump */ pCur = p->apCsr[pOp->p1]; assert( memIsValid(pQuery) ); REGISTER_TRACE(pOp->p3, pQuery); + assert( pCur!=0 ); assert( pCur->eCurType==CURTYPE_VTAB ); pVCur = pCur->uc.pVCur; pVtab = pVCur->pVtab; @@ -7607,7 +7755,6 @@ case OP_VFilter: { /* jump */ iQuery = (int)pQuery->u.i; /* Invoke the xFilter method */ - res = 0; apArg = p->apArg; for(i = 0; iapCsr[pOp->p1]; + assert( pCur!=0 ); assert( pCur->eCurType==CURTYPE_VTAB ); assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pDest = &aMem[pOp->p3]; @@ -7697,8 +7845,8 @@ case OP_VNext: { /* jump */ int res; VdbeCursor *pCur; - res = 0; pCur = p->apCsr[pOp->p1]; + assert( pCur!=0 ); assert( pCur->eCurType==CURTYPE_VTAB ); if( pCur->nullRow ){ break; @@ -7794,7 +7942,7 @@ case OP_VUpdate: { const sqlite3_module *pModule; int nArg; int i; - sqlite_int64 rowid; + sqlite_int64 rowid = 0; Mem **apArg; Mem *pX; @@ -8239,6 +8387,18 @@ default: { /* This is really OP_Noop, OP_Explain */ rc = SQLITE_CORRUPT_BKPT; } assert( rc ); +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeTrace ){ + const char *zTrace = p->zSql; + if( zTrace==0 ){ + if( aOp[0].opcode==OP_Trace ){ + zTrace = aOp[0].p4.z; + } + if( zTrace==0 ) zTrace = "???"; + } + printf("ABORT-due-to-error (rc=%d): %s\n", rc, zTrace); + } +#endif if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){ sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); } @@ -8249,6 +8409,9 @@ default: { /* This is really OP_Noop, OP_Explain */ (int)(pOp - aOp), p->zSql, p->zErrMsg); sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); + if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ + db->flags |= SQLITE_CorruptRdOnly; + } rc = SQLITE_ERROR; if( resetSchemaOnFault>0 ){ sqlite3ResetOneSchema(db, resetSchemaOnFault-1); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index b6e028880..599d06416 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -180,8 +180,8 @@ struct VdbeFrame { int nMem; /* Number of entries in aMem */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ - int nChange; /* Statement changes (Vdbe.nChange) */ - int nDbChange; /* Value of db->nChange */ + i64 nChange; /* Statement changes (Vdbe.nChange) */ + i64 nDbChange; /* Value of db->nChange */ }; /* Magic number for sanity checking on VdbeFrame objects */ @@ -388,7 +388,7 @@ struct Vdbe { u32 cacheCtr; /* VdbeCursor row cache generation counter */ int pc; /* The program counter */ int rc; /* Value to return */ - int nChange; /* Number of db changes made since last reset */ + i64 nChange; /* Number of db changes made since last reset */ int iStatement; /* Statement number (or 0 if has no opened stmt) */ i64 iCurrentTime; /* Value of julianday('now') for this statement */ i64 nFkConstraint; /* Number of imm. FK constraints this VM */ @@ -526,13 +526,18 @@ void sqlite3VdbeMemSetInt64(Mem*, i64); void sqlite3VdbeMemSetPointer(Mem*, void*, const char*, void(*)(void*)); void sqlite3VdbeMemInit(Mem*,sqlite3*,u16); void sqlite3VdbeMemSetNull(Mem*); +#ifndef SQLITE_OMIT_INCRBLOB void sqlite3VdbeMemSetZeroBlob(Mem*,int); +#else +int sqlite3VdbeMemSetZeroBlob(Mem*,int); +#endif #ifdef SQLITE_DEBUG int sqlite3VdbeMemIsRowSet(const Mem*); #endif int sqlite3VdbeMemSetRowSet(Mem*); int sqlite3VdbeMemMakeWriteable(Mem*); int sqlite3VdbeMemStringify(Mem*, u8, u8); +int sqlite3IntFloatCompare(i64,double); i64 sqlite3VdbeIntValue(Mem*); int sqlite3VdbeMemIntegerify(Mem*); double sqlite3VdbeRealValue(Mem*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 5de7c64c2..17df807de 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -362,8 +362,8 @@ void sqlite3_value_free(sqlite3_value *pOld){ ** the function result. ** ** The setStrOrError() function calls sqlite3VdbeMemSetStr() to store the -** result as a string or blob but if the string or blob is too large, it -** then sets the error code to SQLITE_TOOBIG +** result as a string or blob. Appropriate errors are set if the string/blob +** is too big or if an OOM occurs. ** ** The invokeValueDestructor(P,X) routine invokes destructor function X() ** on value P is not going to be used and need to be destroyed. @@ -375,8 +375,16 @@ static void setResultStrOrError( u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - if( sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel)==SQLITE_TOOBIG ){ - sqlite3_result_error_toobig(pCtx); + int rc = sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel); + if( rc ){ + if( rc==SQLITE_TOOBIG ){ + sqlite3_result_error_toobig(pCtx); + }else{ + /* The only errors possible from sqlite3VdbeMemSetStr are + ** SQLITE_TOOBIG and SQLITE_NOMEM */ + assert( rc==SQLITE_NOMEM ); + sqlite3_result_error_nomem(pCtx); + } } } static int invokeValueDestructor( @@ -533,8 +541,12 @@ int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){ return SQLITE_TOOBIG; } +#ifndef SQLITE_OMIT_INCRBLOB sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); return SQLITE_OK; +#else + return sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); +#endif } void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ pCtx->isError = errCode ? errCode : -1; @@ -1546,7 +1558,11 @@ int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ Vdbe *p = (Vdbe *)pStmt; rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ +#ifndef SQLITE_OMIT_INCRBLOB sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#else + rc = sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#endif sqlite3_mutex_leave(p->db->mutex); } return rc; @@ -1834,6 +1850,7 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ u32 nRec; u8 *aRec; + assert( p->pCsr->eCurType==CURTYPE_BTREE ); nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); aRec = sqlite3DbMallocRaw(db, nRec); if( !aRec ) goto preupdate_old_out; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index d22a6a559..a11d41c47 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -241,8 +241,10 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ if( p->nOpAlloc<=i ){ return growOp3(p, op, p1, p2, p3); } + assert( p->aOp!=0 ); p->nOp++; pOp = &p->aOp[i]; + assert( pOp!=0 ); pOp->opcode = (u8)op; pOp->p5 = 0; pOp->p1 = p1; @@ -1485,7 +1487,7 @@ char *sqlite3VdbeDisplayComment( if( zOpName[nOpName+1] ){ int seenCom = 0; char c; - zSynopsis = zOpName += nOpName + 1; + zSynopsis = zOpName + nOpName + 1; if( strncmp(zSynopsis,"IF ",3)==0 ){ sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); zSynopsis = zAlt; @@ -1558,6 +1560,7 @@ static void displayP4Expr(StrAccum *p, Expr *pExpr){ const char *zOp = 0; switch( pExpr->op ){ case TK_STRING: + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3_str_appendf(p, "%Q", pExpr->u.zToken); break; case TK_INTEGER: @@ -1660,7 +1663,7 @@ char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){ case P4_COLLSEQ: { static const char *const encnames[] = {"?", "8", "16LE", "16BE"}; CollSeq *pColl = pOp->p4.pColl; - assert( pColl->enc>=0 && pColl->enc<4 ); + assert( pColl->enc<4 ); sqlite3_str_appendf(&x, "%.18s-%s", pColl->zName, encnames[pColl->enc]); break; @@ -1904,8 +1907,8 @@ static void releaseMemArray(Mem *p, int N){ */ testcase( p->flags & MEM_Agg ); testcase( p->flags & MEM_Dyn ); - testcase( p->xDel==sqlite3VdbeFrameMemDel ); if( p->flags&(MEM_Agg|MEM_Dyn) ){ + testcase( (p->flags & MEM_Dyn)!=0 && p->xDel==sqlite3VdbeFrameMemDel ); sqlite3VdbeMemRelease(p); }else if( p->szMalloc ){ sqlite3DbFreeNN(db, p->zMalloc); @@ -3005,9 +3008,9 @@ int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ ** has made changes and is in autocommit mode, then commit those ** changes. If a rollback is needed, then do the rollback. ** -** This routine is the only way to move the state of a VM from -** SQLITE_MAGIC_RUN to SQLITE_MAGIC_HALT. It is harmless to -** call this on a VM that is in the SQLITE_MAGIC_HALT state. +** This routine is the only way to move the sqlite3eOpenState of a VM from +** SQLITE_STATE_RUN to SQLITE_STATE_HALT. It is harmless to +** call this on a VM that is in the SQLITE_STATE_HALT state. ** ** Return an error code. If the commit could not complete because of ** lock contention, return SQLITE_BUSY. If SQLITE_BUSY is returned, it @@ -3053,9 +3056,15 @@ int sqlite3VdbeHalt(Vdbe *p){ sqlite3VdbeEnter(p); /* Check for one of the special errors */ - mrc = p->rc & 0xff; - isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR - || mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL; + if( p->rc ){ + mrc = p->rc & 0xff; + isSpecialError = mrc==SQLITE_NOMEM + || mrc==SQLITE_IOERR + || mrc==SQLITE_INTERRUPT + || mrc==SQLITE_FULL; + }else{ + mrc = isSpecialError = 0; + } if( isSpecialError ){ /* If the query was read-only and the error code is SQLITE_INTERRUPT, ** no rollback is necessary. Otherwise, at least a savepoint @@ -3107,6 +3116,9 @@ int sqlite3VdbeHalt(Vdbe *p){ return SQLITE_ERROR; } rc = SQLITE_CONSTRAINT_FOREIGNKEY; + }else if( db->flags & SQLITE_CorruptRdOnly ){ + rc = SQLITE_CORRUPT; + db->flags &= ~SQLITE_CorruptRdOnly; }else{ /* The auto-commit flag is true, the vdbe program was successful ** or hit an 'OR FAIL' constraint and there are no deferred foreign @@ -3501,7 +3513,7 @@ int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){ assert( p->deferredMoveto ); assert( p->isTable ); assert( p->eCurType==CURTYPE_BTREE ); - rc = sqlite3BtreeMovetoUnpacked(p->uc.pCursor, 0, p->movetoTarget, 0, &res); + rc = sqlite3BtreeTableMoveto(p->uc.pCursor, p->movetoTarget, 0, &res); if( rc ) return rc; if( res!=0 ) return SQLITE_CORRUPT_BKPT; #ifdef SQLITE_TEST @@ -4285,7 +4297,7 @@ SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){ ** number. Return negative, zero, or positive if the first (i64) is less than, ** equal to, or greater than the second (double). */ -static int sqlite3IntFloatCompare(i64 i, double r){ +int sqlite3IntFloatCompare(i64 i, double r){ if( sizeof(LONGDOUBLE_TYPE)>8 ){ LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i; testcase( x0x7fffffff ); assert( m.n>=0 ); if( unlikely(szHdr<3 || szHdr>(unsigned)m.n) ){ @@ -5009,7 +5021,7 @@ int sqlite3VdbeIdxKeyCompare( ** This routine sets the value to be returned by subsequent calls to ** sqlite3_changes() on the database handle 'db'. */ -void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){ +void sqlite3VdbeSetChanges(sqlite3 *db, i64 nChange){ assert( sqlite3_mutex_held(db->mutex) ); db->nChange = nChange; db->nTotalChange += nChange; @@ -5211,6 +5223,8 @@ void sqlite3VdbePreUpdateHook( } } + assert( pCsr!=0 ); + assert( pCsr->eCurType==CURTYPE_BTREE ); assert( pCsr->nField==pTab->nCol || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1) ); diff --git a/src/vdbeblob.c b/src/vdbeblob.c index a4e79bf89..512442fd2 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -75,7 +75,10 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ } if( rc==SQLITE_ROW ){ VdbeCursor *pC = v->apCsr[0]; - u32 type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; + u32 type; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; testcase( pC->nHdrParsed==p->iCol ); testcase( pC->nHdrParsed==p->iCol+1 ); if( type<12 ){ @@ -167,7 +170,7 @@ int sqlite3_blob_open( sqlite3ErrorMsg(&sParse, "cannot open table without rowid: %s", zTable); } #ifndef SQLITE_OMIT_VIEW - if( pTab && pTab->pSelect ){ + if( pTab && IsView(pTab) ){ pTab = 0; sqlite3ErrorMsg(&sParse, "cannot open view: %s", zTable); } @@ -187,7 +190,7 @@ int sqlite3_blob_open( /* Now search pTab for the exact column. */ for(iCol=0; iColnCol; iCol++) { - if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){ + if( sqlite3StrICmp(pTab->aCol[iCol].zCnName, zColumn)==0 ){ break; } } @@ -212,7 +215,8 @@ int sqlite3_blob_open( ** key columns must be indexed. The check below will pick up this ** case. */ FKey *pFKey; - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ int j; for(j=0; jnCol; j++){ if( pFKey->aCol[j].iFrom==iCol ){ @@ -419,6 +423,8 @@ static int blobReadWrite( */ sqlite3_int64 iKey; iKey = sqlite3BtreeIntegerKey(p->pCsr); + assert( v->apCsr[0]!=0 ); + assert( v->apCsr[0]->eCurType==CURTYPE_BTREE ); sqlite3VdbePreUpdateHook( v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol ); diff --git a/src/vdbemem.c b/src/vdbemem.c index dc177161b..570a2eb38 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -200,6 +200,7 @@ int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){ #ifndef SQLITE_OMIT_UTF16 int rc; #endif + assert( pMem!=0 ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( desiredEnc==SQLITE_UTF8 || desiredEnc==SQLITE_UTF16LE || desiredEnc==SQLITE_UTF16BE ); @@ -332,6 +333,7 @@ static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){ ** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails. */ int sqlite3VdbeMemMakeWriteable(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); if( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ){ @@ -356,6 +358,7 @@ int sqlite3VdbeMemMakeWriteable(Mem *pMem){ #ifndef SQLITE_OMIT_INCRBLOB int sqlite3VdbeMemExpandBlob(Mem *pMem){ int nByte; + assert( pMem!=0 ); assert( pMem->flags & MEM_Zero ); assert( (pMem->flags&MEM_Blob)!=0 || MemNullNochng(pMem) ); testcase( sqlite3_value_nochange(pMem) ); @@ -371,6 +374,8 @@ int sqlite3VdbeMemExpandBlob(Mem *pMem){ if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ return SQLITE_NOMEM_BKPT; } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db,pMem->z) >= nByte ); memset(&pMem->z[pMem->n], 0, pMem->u.nZero); pMem->n += pMem->u.nZero; @@ -383,6 +388,7 @@ int sqlite3VdbeMemExpandBlob(Mem *pMem){ ** Make sure the given Mem is \u0000 terminated. */ int sqlite3VdbeMemNulTerminate(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); testcase( (pMem->flags & (MEM_Term|MEM_Str))==(MEM_Term|MEM_Str) ); testcase( (pMem->flags & (MEM_Term|MEM_Str))==0 ); @@ -410,6 +416,7 @@ int sqlite3VdbeMemNulTerminate(Mem *pMem){ int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ const int nByte = 32; + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !(pMem->flags&MEM_Zero) ); assert( !(pMem->flags&(MEM_Str|MEM_Blob)) ); @@ -445,6 +452,7 @@ int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ sqlite3_context ctx; Mem t; assert( pFunc!=0 ); + assert( pMem!=0 ); assert( pFunc->xFinalize!=0 ); assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -595,6 +603,7 @@ static SQLITE_NOINLINE i64 memIntValue(Mem *pMem){ } i64 sqlite3VdbeIntValue(Mem *pMem){ int flags; + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); flags = pMem->flags; @@ -623,6 +632,7 @@ static SQLITE_NOINLINE double memRealValue(Mem *pMem){ return val; } double sqlite3VdbeRealValue(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); if( pMem->flags & MEM_Real ){ @@ -655,6 +665,7 @@ int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){ */ void sqlite3VdbeIntegerAffinity(Mem *pMem){ i64 ix; + assert( pMem!=0 ); assert( pMem->flags & MEM_Real ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -682,6 +693,7 @@ void sqlite3VdbeIntegerAffinity(Mem *pMem){ ** Convert pMem to type integer. Invalidate any prior representations. */ int sqlite3VdbeMemIntegerify(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -696,6 +708,7 @@ int sqlite3VdbeMemIntegerify(Mem *pMem){ ** Invalidate any prior representations. */ int sqlite3VdbeMemRealify(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -729,6 +742,7 @@ int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ ** as much of the string as we can and ignore the rest. */ int sqlite3VdbeMemNumerify(Mem *pMem){ + assert( pMem!=0 ); testcase( pMem->flags & MEM_Int ); testcase( pMem->flags & MEM_Real ); testcase( pMem->flags & MEM_IntReal ); @@ -838,6 +852,7 @@ void sqlite3ValueSetNull(sqlite3_value *p){ ** Delete any previous value and set the value to be a BLOB of length ** n containing all zeros. */ +#ifndef SQLITE_OMIT_INCRBLOB void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ sqlite3VdbeMemRelease(pMem); pMem->flags = MEM_Blob|MEM_Zero; @@ -847,6 +862,21 @@ void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ pMem->enc = SQLITE_UTF8; pMem->z = 0; } +#else +int sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ + int nByte = n>0?n:1; + if( sqlite3VdbeMemGrow(pMem, nByte, 0) ){ + return SQLITE_NOMEM_BKPT; + } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db, pMem->z)>=nByte ); + memset(pMem->z, 0, nByte); + pMem->n = n>0?n:0; + pMem->flags = MEM_Blob; + pMem->enc = SQLITE_UTF8; + return SQLITE_OK; +} +#endif /* ** The pMem is known to contain content that needs to be destroyed prior @@ -1080,6 +1110,7 @@ int sqlite3VdbeMemSetStr( int iLimit; /* Maximum allowed string or blob size */ u16 flags = 0; /* New value for pMem->flags */ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); @@ -1388,7 +1419,7 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ #ifdef SQLITE_ENABLE_STAT4 static int valueFromFunction( sqlite3 *db, /* The database connection */ - Expr *p, /* The expression to evaluate */ + const Expr *p, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 aff, /* Affinity to use */ sqlite3_value **ppVal, /* Write the new value here */ @@ -1405,8 +1436,10 @@ static int valueFromFunction( assert( pCtx!=0 ); assert( (p->flags & EP_TokenOnly)==0 ); + assert( ExprUseXList(p) ); pList = p->x.pList; if( pList ) nVal = pList->nExpr; + assert( !ExprHasProperty(p, EP_IntValue) ); pFunc = sqlite3FindFunction(db, p->u.zToken, nVal, enc, 0); assert( pFunc ); if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 @@ -1482,7 +1515,7 @@ static int valueFromFunction( */ static int valueFromExpr( sqlite3 *db, /* The database connection */ - Expr *pExpr, /* The expression to evaluate */ + const Expr *pExpr, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 affinity, /* Affinity to use */ sqlite3_value **ppVal, /* Write the new value here */ @@ -1510,7 +1543,9 @@ static int valueFromExpr( assert( (pExpr->flags & EP_TokenOnly)==0 || pCtx==0 ); if( op==TK_CAST ){ - u8 aff = sqlite3AffinityType(pExpr->u.zToken,0); + u8 aff; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + aff = sqlite3AffinityType(pExpr->u.zToken,0); rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx); testcase( rc!=SQLITE_OK ); if( *ppVal ){ @@ -1583,6 +1618,7 @@ static int valueFromExpr( #ifndef SQLITE_OMIT_BLOB_LITERAL else if( op==TK_BLOB ){ int nVal; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( pExpr->u.zToken[0]=='x' || pExpr->u.zToken[0]=='X' ); assert( pExpr->u.zToken[1]=='\'' ); pVal = valueNew(db, pCtx); @@ -1600,6 +1636,7 @@ static int valueFromExpr( } #endif else if( op==TK_TRUEFALSE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pVal = valueNew(db, pCtx); if( pVal ){ pVal->flags = MEM_Int; @@ -1637,7 +1674,7 @@ static int valueFromExpr( */ int sqlite3ValueFromExpr( sqlite3 *db, /* The database connection */ - Expr *pExpr, /* The expression to evaluate */ + const Expr *pExpr, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 affinity, /* Affinity to use */ sqlite3_value **ppVal /* Write the new value here */ diff --git a/src/vdbesort.c b/src/vdbesort.c index 397935aa8..8bf7b5717 100644 --- a/src/vdbesort.c +++ b/src/vdbesort.c @@ -1289,7 +1289,7 @@ static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){ sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize); sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte); sqlite3OsFetch(pFd, 0, (int)nByte, &p); - sqlite3OsUnfetch(pFd, 0, p); + if( p ) sqlite3OsUnfetch(pFd, 0, p); } } #else @@ -2007,6 +2007,7 @@ static int vdbeIncrMergerNew( vdbeMergeEngineFree(pMerger); rc = SQLITE_NOMEM_BKPT; } + assert( *ppOut!=0 || rc!=SQLITE_OK ); return rc; } diff --git a/src/vdbetrace.c b/src/vdbetrace.c index 1095e7f58..ae8ad3115 100644 --- a/src/vdbetrace.c +++ b/src/vdbetrace.c @@ -84,11 +84,9 @@ char *sqlite3VdbeExpandSql( #ifndef SQLITE_OMIT_UTF16 Mem utf8; /* Used to convert UTF16 into UTF8 for display */ #endif - char zBase[100]; /* Initial working space */ db = p->db; - sqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase), - db->aLimit[SQLITE_LIMIT_LENGTH]); + sqlite3StrAccumInit(&out, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); if( db->nVdbeExec>1 ){ while( *zRawSql ){ const char *zStart = zRawSql; diff --git a/src/vtab.c b/src/vtab.c index c9dcadae4..2c787c6c4 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -192,7 +192,7 @@ void sqlite3VtabLock(VTable *pVTab){ VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){ VTable *pVtab; assert( IsVirtual(pTab) ); - for(pVtab=pTab->pVTable; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); + for(pVtab=pTab->u.vtab.p; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); return pVtab; } @@ -205,7 +205,8 @@ void sqlite3VtabUnlock(VTable *pVTab){ assert( db ); assert( pVTab->nRef>0 ); - assert( db->magic==SQLITE_MAGIC_OPEN || db->magic==SQLITE_MAGIC_ZOMBIE ); + assert( db->eOpenState==SQLITE_STATE_OPEN + || db->eOpenState==SQLITE_STATE_ZOMBIE ); pVTab->nRef--; if( pVTab->nRef==0 ){ @@ -220,21 +221,24 @@ void sqlite3VtabUnlock(VTable *pVTab){ /* ** Table p is a virtual table. This function moves all elements in the -** p->pVTable list to the sqlite3.pDisconnect lists of their associated +** p->u.vtab.p list to the sqlite3.pDisconnect lists of their associated ** database connections to be disconnected at the next opportunity. ** Except, if argument db is not NULL, then the entry associated with -** connection db is left in the p->pVTable list. +** connection db is left in the p->u.vtab.p list. */ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ VTable *pRet = 0; - VTable *pVTable = p->pVTable; - p->pVTable = 0; + VTable *pVTable; + + assert( IsVirtual(p) ); + pVTable = p->u.vtab.p; + p->u.vtab.p = 0; /* Assert that the mutex (if any) associated with the BtShared database ** that contains table p is held by the caller. See header comments ** above function sqlite3VtabUnlockList() for an explanation of why ** this makes it safe to access the sqlite3.pDisconnect list of any - ** database connection that may have an entry in the p->pVTable list. + ** database connection that may have an entry in the p->u.vtab.p list. */ assert( db==0 || sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); @@ -244,7 +248,7 @@ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ assert( db2 ); if( db2==db ){ pRet = pVTable; - p->pVTable = pRet; + p->u.vtab.p = pRet; pRet->pNext = 0; }else{ pVTable->pNext = db2->pDisconnect; @@ -272,7 +276,7 @@ void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ assert( sqlite3BtreeHoldsAllMutexes(db) ); assert( sqlite3_mutex_held(db->mutex) ); - for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){ + for(ppVTab=&p->u.vtab.p; *ppVTab; ppVTab=&(*ppVTab)->pNext){ if( (*ppVTab)->db==db ){ VTable *pVTab = *ppVTab; *ppVTab = pVTab->pNext; @@ -335,37 +339,41 @@ void sqlite3VtabUnlockList(sqlite3 *db){ ** database connection. */ void sqlite3VtabClear(sqlite3 *db, Table *p){ + assert( IsVirtual(p) ); if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p); - if( p->azModuleArg ){ + if( p->u.vtab.azArg ){ int i; - for(i=0; inModuleArg; i++){ - if( i!=1 ) sqlite3DbFree(db, p->azModuleArg[i]); + for(i=0; iu.vtab.nArg; i++){ + if( i!=1 ) sqlite3DbFree(db, p->u.vtab.azArg[i]); } - sqlite3DbFree(db, p->azModuleArg); + sqlite3DbFree(db, p->u.vtab.azArg); } } /* -** Add a new module argument to pTable->azModuleArg[]. +** Add a new module argument to pTable->u.vtab.azArg[]. ** The string is not copied - the pointer is stored. The ** string will be freed automatically when the table is ** deleted. */ static void addModuleArgument(Parse *pParse, Table *pTable, char *zArg){ - sqlite3_int64 nBytes = sizeof(char *)*(2+pTable->nModuleArg); + sqlite3_int64 nBytes; char **azModuleArg; sqlite3 *db = pParse->db; - if( pTable->nModuleArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){ + + assert( IsVirtual(pTable) ); + nBytes = sizeof(char *)*(2+pTable->u.vtab.nArg); + if( pTable->u.vtab.nArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many columns on %s", pTable->zName); } - azModuleArg = sqlite3DbRealloc(db, pTable->azModuleArg, nBytes); + azModuleArg = sqlite3DbRealloc(db, pTable->u.vtab.azArg, nBytes); if( azModuleArg==0 ){ sqlite3DbFree(db, zArg); }else{ - int i = pTable->nModuleArg++; + int i = pTable->u.vtab.nArg++; azModuleArg[i] = zArg; azModuleArg[i+1] = 0; - pTable->azModuleArg = azModuleArg; + pTable->u.vtab.azArg = azModuleArg; } } @@ -388,10 +396,11 @@ void sqlite3VtabBeginParse( pTable = pParse->pNewTable; if( pTable==0 ) return; assert( 0==pTable->pIndex ); + pTable->eTabType = TABTYP_VTAB; db = pParse->db; - assert( pTable->nModuleArg==0 ); + assert( pTable->u.vtab.nArg==0 ); addModuleArgument(pParse, pTable, sqlite3NameFromToken(db, pModuleName)); addModuleArgument(pParse, pTable, 0); addModuleArgument(pParse, pTable, sqlite3DbStrDup(db, pTable->zName)); @@ -408,11 +417,11 @@ void sqlite3VtabBeginParse( ** sqlite_schema table, has already been made by sqlite3StartTable(). ** The second call, to obtain permission to create the table, is made now. */ - if( pTable->azModuleArg ){ + if( pTable->u.vtab.azArg ){ int iDb = sqlite3SchemaToIndex(db, pTable->pSchema); assert( iDb>=0 ); /* The database the table is being created in */ sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, - pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName); + pTable->u.vtab.azArg[0], pParse->db->aDb[iDb].zDbSName); } #endif } @@ -440,9 +449,10 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ sqlite3 *db = pParse->db; /* The database connection */ if( pTab==0 ) return; + assert( IsVirtual(pTab) ); addArgumentToVtab(pParse); pParse->sArg.z = 0; - if( pTab->nModuleArg<1 ) return; + if( pTab->u.vtab.nArg<1 ) return; /* If the CREATE VIRTUAL TABLE statement is being entered for the ** first time (in other words if the virtual table is actually being @@ -475,7 +485,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE " " + "UPDATE %Q." LEGACY_SCHEMA_TABLE " " "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q " "WHERE rowid=#%d", db->aDb[iDb].zDbSName, @@ -495,18 +505,14 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ iReg = ++pParse->nMem; sqlite3VdbeLoadString(v, iReg, pTab->zName); sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg); - } - - /* If we are rereading the sqlite_schema table create the in-memory - ** record of the table. The xConnect() method is not called until - ** the first time the virtual table is used in an SQL statement. This - ** allows a schema that contains virtual tables to be loaded before - ** the required virtual table implementations are registered. */ - else { + }else{ + /* If we are rereading the sqlite_schema table create the in-memory + ** record of the table. */ Table *pOld; Schema *pSchema = pTab->pSchema; const char *zName = pTab->zName; - assert( sqlite3SchemaMutexHeld(db, 0, pSchema) ); + assert( zName!=0 ); + sqlite3MarkAllShadowTablesOf(db, pTab); pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab); if( pOld ){ sqlite3OomFault(db); @@ -557,13 +563,16 @@ static int vtabCallConstructor( VtabCtx sCtx; VTable *pVTable; int rc; - const char *const*azArg = (const char *const*)pTab->azModuleArg; - int nArg = pTab->nModuleArg; + const char *const*azArg; + int nArg = pTab->u.vtab.nArg; char *zErr = 0; char *zModuleName; int iDb; VtabCtx *pCtx; + assert( IsVirtual(pTab) ); + azArg = (const char *const*)pTab->u.vtab.azArg; + /* Check that the virtual-table is not already being initialized */ for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){ if( pCtx->pTab==pTab ){ @@ -590,7 +599,7 @@ static int vtabCallConstructor( pVTable->eVtabRisk = SQLITE_VTABRISK_Normal; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - pTab->azModuleArg[1] = db->aDb[iDb].zDbSName; + pTab->u.vtab.azArg[1] = db->aDb[iDb].zDbSName; /* Invoke the virtual table constructor */ assert( &db->pVtabCtx ); @@ -629,12 +638,12 @@ static int vtabCallConstructor( int iCol; u16 oooHidden = 0; /* If everything went according to plan, link the new VTable structure - ** into the linked list headed by pTab->pVTable. Then loop through the + ** into the linked list headed by pTab->u.vtab.p. Then loop through the ** columns of the table to see if any of them contain the token "hidden". ** If so, set the Column COLFLAG_HIDDEN flag and remove the token from ** the type string. */ - pVTable->pNext = pTab->pVTable; - pTab->pVTable = pVTable; + pVTable->pNext = pTab->u.vtab.p; + pTab->u.vtab.p = pVTable; for(iCol=0; iColnCol; iCol++){ char *zType = sqlite3ColumnType(&pTab->aCol[iCol], ""); @@ -687,16 +696,17 @@ int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){ int rc; assert( pTab ); - if( !IsVirtual(pTab) || sqlite3GetVTable(db, pTab) ){ + assert( IsVirtual(pTab) ); + if( sqlite3GetVTable(db, pTab) ){ return SQLITE_OK; } /* Locate the required virtual table module */ - zMod = pTab->azModuleArg[0]; + zMod = pTab->u.vtab.azArg[0]; pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); if( !pMod ){ - const char *zModule = pTab->azModuleArg[0]; + const char *zModule = pTab->u.vtab.azArg[0]; sqlite3ErrorMsg(pParse, "no such module: %s", zModule); rc = SQLITE_ERROR; }else{ @@ -759,10 +769,10 @@ int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){ const char *zMod; pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); - assert( pTab && IsVirtual(pTab) && !pTab->pVTable ); + assert( pTab && IsVirtual(pTab) && !pTab->u.vtab.p ); /* Locate the required virtual table module */ - zMod = pTab->azModuleArg[0]; + zMod = pTab->u.vtab.azArg[0]; pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); /* If the module has been registered and includes a Create method, @@ -799,6 +809,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ Table *pTab; char *zErr = 0; Parse sParse; + int initBusy; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){ @@ -818,17 +829,23 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ memset(&sParse, 0, sizeof(sParse)); sParse.eParseMode = PARSE_MODE_DECLARE_VTAB; sParse.db = db; + /* We should never be able to reach this point while loading the + ** schema. Nevertheless, defend against that (turn off db->init.busy) + ** in case a bug arises. */ + assert( db->init.busy==0 ); + initBusy = db->init.busy; + db->init.busy = 0; sParse.nQueryLoop = 1; if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr) && sParse.pNewTable && !db->mallocFailed - && !sParse.pNewTable->pSelect - && !IsVirtual(sParse.pNewTable) + && IsOrdinaryTable(sParse.pNewTable) ){ if( !pTab->aCol ){ Table *pNew = sParse.pNewTable; Index *pIdx; pTab->aCol = pNew->aCol; + sqlite3ExprListDelete(db, pNew->u.tab.pDfltList); pTab->nNVCol = pTab->nCol = pNew->nCol; pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid); pNew->nCol = 0; @@ -864,6 +881,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ } sqlite3DeleteTable(db, sParse.pNewTable); sqlite3ParserReset(&sParse); + db->init.busy = initBusy; assert( (rc&0xff)==rc ); rc = sqlite3ApiExit(db, rc); @@ -883,10 +901,13 @@ int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){ Table *pTab; pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); - if( pTab!=0 && ALWAYS(pTab->pVTable!=0) ){ + if( ALWAYS(pTab!=0) + && ALWAYS(IsVirtual(pTab)) + && ALWAYS(pTab->u.vtab.p!=0) + ){ VTable *p; int (*xDestroy)(sqlite3_vtab *); - for(p=pTab->pVTable; p; p=p->pNext){ + for(p=pTab->u.vtab.p; p; p=p->pNext){ assert( p->pVtab ); if( p->pVtab->nRef>0 ){ return SQLITE_LOCKED; @@ -900,9 +921,9 @@ int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){ rc = xDestroy(p->pVtab); /* Remove the sqlite3_vtab* from the aVTrans[] array, if applicable */ if( rc==SQLITE_OK ){ - assert( pTab->pVTable==p && p->pNext==0 ); + assert( pTab->u.vtab.p==p && p->pNext==0 ); p->pVtab = 0; - pTab->pVTable = 0; + pTab->u.vtab.p = 0; sqlite3VtabUnlock(p); } sqlite3DeleteTable(db, pTab); @@ -1116,6 +1137,7 @@ FuncDef *sqlite3VtabOverloadFunction( /* Check to see the left operand is a column in a virtual table */ if( NEVER(pExpr==0) ) return pDef; if( pExpr->op!=TK_COLUMN ) return pDef; + assert( ExprUseYTab(pExpr) ); pTab = pExpr->y.pTab; if( pTab==0 ) return pDef; if( !IsVirtual(pTab) ) return pDef; @@ -1190,8 +1212,9 @@ void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ /* ** Check to see if virtual table module pMod can be have an eponymous ** virtual table instance. If it can, create one if one does not already -** exist. Return non-zero if the eponymous virtual table instance exists -** when this routine returns, and return zero if it does not exist. +** exist. Return non-zero if either the eponymous virtual table instance +** exists when this routine returns or if an attempt to create it failed +** and an error message was left in pParse. ** ** An eponymous virtual table instance is one that is named after its ** module, and more importantly, does not require a CREATE VIRTUAL TABLE @@ -1218,8 +1241,9 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ } pMod->pEpoTab = pTab; pTab->nTabRef = 1; + pTab->eTabType = TABTYP_VTAB; pTab->pSchema = db->aDb[0].pSchema; - assert( pTab->nModuleArg==0 ); + assert( pTab->u.vtab.nArg==0 ); pTab->iPKey = -1; pTab->tabFlags |= TF_Eponymous; addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); @@ -1230,7 +1254,6 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ sqlite3ErrorMsg(pParse, "%s", zErr); sqlite3DbFree(db, zErr); sqlite3VtabEponymousTableClear(db, pMod); - return 0; } return 1; } diff --git a/src/wal.c b/src/wal.c index c3f84dd2e..a0ec91965 100644 --- a/src/wal.c +++ b/src/wal.c @@ -161,7 +161,10 @@ ** HASHTABLE_NPAGE_ONE frames. The values of HASHTABLE_NPAGE_ONE and ** HASHTABLE_NPAGE are selected so that together the wal-index header and ** first index block are the same size as all other index blocks in the -** wal-index. +** wal-index. The values are: +** +** HASHTABLE_NPAGE 4096 +** HASHTABLE_NPAGE_ONE 4062 ** ** Each index block contains two sections, a page-mapping that contains the ** database page number associated with each wal frame, and a hash-table @@ -397,6 +400,70 @@ struct WalCkptInfo { }; #define READMARK_NOT_USED 0xffffffff +/* +** This is a schematic view of the complete 136-byte header of the +** wal-index file (also known as the -shm file): +** +** +-----------------------------+ +** 0: | iVersion | \ +** +-----------------------------+ | +** 4: | (unused padding) | | +** +-----------------------------+ | +** 8: | iChange | | +** +-------+-------+-------------+ | +** 12: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | +** 16: | mxFrame | | First copy of the +** +-----------------------------+ | WalIndexHdr object +** 20: | nPage | | +** +-----------------------------+ | +** 24: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 32: | aSalt | | +** | | | +** +-----------------------------+ | +** 40: | aCksum | | +** | | / +** +-----------------------------+ +** 48: | iVersion | \ +** +-----------------------------+ | +** 52: | (unused padding) | | +** +-----------------------------+ | +** 56: | iChange | | +** +-------+-------+-------------+ | +** 60: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | Second copy of the +** 64: | mxFrame | | WalIndexHdr +** +-----------------------------+ | +** 68: | nPage | | +** +-----------------------------+ | +** 72: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 80: | aSalt | | +** | | | +** +-----------------------------+ | +** 88: | aCksum | | +** | | / +** +-----------------------------+ +** 96: | nBackfill | +** +-----------------------------+ +** 100: | 5 read marks | +** | | +** | | +** | | +** | | +** +-------+-------+------+------+ +** 120: | Write | Ckpt | Rcvr | Rd0 | \ +** +-------+-------+------+------+ ) 8 lock bytes +** | Read1 | Read2 | Rd3 | Rd4 | / +** +-------+-------+------+------+ +** 128: | nBackfillAttempted | +** +-----------------------------+ +** 132: | (unused padding) | +** +-----------------------------+ +*/ /* A block of WALINDEX_LOCK_RESERVED bytes beginning at ** WALINDEX_LOCK_OFFSET is reserved for locks. Since some systems @@ -553,9 +620,13 @@ struct WalIterator { ** so. It is safe to enlarge the wal-index if pWal->writeLock is true ** or pWal->exclusiveMode==WAL_HEAPMEMORY_MODE. ** -** If this call is successful, *ppPage is set to point to the wal-index -** page and SQLITE_OK is returned. If an error (an OOM or VFS error) occurs, -** then an SQLite error code is returned and *ppPage is set to 0. +** Three possible result scenarios: +** +** (1) rc==SQLITE_OK and *ppPage==Requested-Wal-Index-Page +** (2) rc>=SQLITE_ERROR and *ppPage==NULL +** (3) rc==SQLITE_OK and *ppPage==NULL // only if iPage==0 +** +** Scenario (3) can only occur when pWal->writeLock is false and iPage==0 */ static SQLITE_NOINLINE int walIndexPageRealloc( Wal *pWal, /* The WAL context */ @@ -588,7 +659,9 @@ static SQLITE_NOINLINE int walIndexPageRealloc( rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] ); - assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 ); + assert( pWal->apWiData[iPage]!=0 + || rc!=SQLITE_OK + || (pWal->writeLock==0 && iPage==0) ); testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK ); if( rc==SQLITE_OK ){ if( iPage>0 && sqlite3FaultSim(600) ) rc = SQLITE_NOMEM; @@ -927,8 +1000,8 @@ struct WalHashLoc { ** slot in the hash table is set to N, it refers to frame number ** (pLoc->iZero+N) in the log. ** -** Finally, set pLoc->aPgno so that pLoc->aPgno[1] is the page number of the -** first frame indexed by the hash table, frame (pLoc->iZero+1). +** Finally, set pLoc->aPgno so that pLoc->aPgno[0] is the page number of the +** first frame indexed by the hash table, frame (pLoc->iZero). */ static int walHashGet( Wal *pWal, /* WAL handle */ @@ -940,7 +1013,7 @@ static int walHashGet( rc = walIndexPage(pWal, iHash, &pLoc->aPgno); assert( rc==SQLITE_OK || iHash>0 ); - if( rc==SQLITE_OK ){ + if( pLoc->aPgno ){ pLoc->aHash = (volatile ht_slot *)&pLoc->aPgno[HASHTABLE_NPAGE]; if( iHash==0 ){ pLoc->aPgno = &pLoc->aPgno[WALINDEX_HDR_SIZE/sizeof(u32)]; @@ -948,7 +1021,8 @@ static int walHashGet( }else{ pLoc->iZero = HASHTABLE_NPAGE_ONE + (iHash-1)*HASHTABLE_NPAGE; } - pLoc->aPgno = &pLoc->aPgno[-1]; + }else if( NEVER(rc==SQLITE_OK) ){ + rc = SQLITE_ERROR; } return rc; } @@ -1030,8 +1104,9 @@ static void walCleanupHash(Wal *pWal){ /* Zero the entries in the aPgno array that correspond to frames with ** frame numbers greater than pWal->hdr.mxFrame. */ - nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit+1]); - memset((void *)&sLoc.aPgno[iLimit+1], 0, nByte); + nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit]); + assert( nByte>=0 ); + memset((void *)&sLoc.aPgno[iLimit], 0, nByte); #ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT /* Verify that the every entry in the mapping region is still reachable @@ -1040,11 +1115,11 @@ static void walCleanupHash(Wal *pWal){ if( iLimit ){ int j; /* Loop counter */ int iKey; /* Hash key */ - for(j=1; j<=iLimit; j++){ + for(j=0; j=0 ); + memset((void*)sLoc.aPgno, 0, nByte); } /* If the entry in aPgno[] is already set, then the previous writer @@ -1087,9 +1162,9 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ ** Remove the remnants of that writers uncommitted transaction from ** the hash-table before writing any new entries. */ - if( sLoc.aPgno[idx] ){ + if( sLoc.aPgno[idx-1] ){ walCleanupHash(pWal); - assert( !sLoc.aPgno[idx] ); + assert( !sLoc.aPgno[idx-1] ); } /* Write the aPgno[] array entry and the hash-table slot. */ @@ -1097,7 +1172,7 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ for(iKey=walHash(iPage); sLoc.aHash[iKey]; iKey=walNextHash(iKey)){ if( (nCollide--)==0 ) return SQLITE_CORRUPT_BKPT; } - sLoc.aPgno[idx] = iPage; + sLoc.aPgno[idx-1] = iPage; AtomicStore(&sLoc.aHash[iKey], (ht_slot)idx); #ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT @@ -1118,19 +1193,18 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ */ if( (idx&0x3ff)==0 ){ int i; /* Loop counter */ - for(i=1; i<=idx; i++){ + for(i=0; iapWiData[iPg] = aPrivate; for(iFrame=iFirst; iFrame<=iLast; iFrame++){ @@ -1410,14 +1485,43 @@ int sqlite3WalOpen( assert( zWalName && zWalName[0] ); assert( pDbFd ); + /* Verify the values of various constants. Any changes to the values + ** of these constants would result in an incompatible on-disk format + ** for the -shm file. Any change that causes one of these asserts to + ** fail is a backward compatibility problem, even if the change otherwise + ** works. + ** + ** This table also serves as a helpful cross-reference when trying to + ** interpret hex dumps of the -shm file. + */ + assert( 48 == sizeof(WalIndexHdr) ); + assert( 40 == sizeof(WalCkptInfo) ); + assert( 120 == WALINDEX_LOCK_OFFSET ); + assert( 136 == WALINDEX_HDR_SIZE ); + assert( 4096 == HASHTABLE_NPAGE ); + assert( 4062 == HASHTABLE_NPAGE_ONE ); + assert( 8192 == HASHTABLE_NSLOT ); + assert( 383 == HASHTABLE_HASH_1 ); + assert( 32768 == WALINDEX_PGSZ ); + assert( 8 == SQLITE_SHM_NLOCK ); + assert( 5 == WAL_NREADER ); + assert( 24 == WAL_FRAME_HDRSIZE ); + assert( 32 == WAL_HDRSIZE ); + assert( 120 == WALINDEX_LOCK_OFFSET + WAL_WRITE_LOCK ); + assert( 121 == WALINDEX_LOCK_OFFSET + WAL_CKPT_LOCK ); + assert( 122 == WALINDEX_LOCK_OFFSET + WAL_RECOVER_LOCK ); + assert( 123 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(0) ); + assert( 124 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(1) ); + assert( 125 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(2) ); + assert( 126 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(3) ); + assert( 127 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(4) ); + /* In the amalgamation, the os_unix.c and os_win.c source files come before ** this source file. Verify that the #defines of the locking byte offsets ** in os_unix.c and os_win.c agree with the WALINDEX_LOCK_OFFSET value. ** For that matter, if the lock offset ever changes from its initial design ** value of 120, we need to know that so there is an assert() to check it. */ - assert( 120==WALINDEX_LOCK_OFFSET ); - assert( 136==WALINDEX_HDR_SIZE ); #ifdef WIN_SHM_BASE assert( WIN_SHM_BASE==WALINDEX_LOCK_OFFSET ); #endif @@ -1719,7 +1823,6 @@ static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ int nEntry; /* Number of entries in this segment */ ht_slot *aIndex; /* Sorted index for this segment */ - sLoc.aPgno++; if( (i+1)==nSegment ){ nEntry = (int)(iLast - sLoc.iZero); }else{ @@ -2858,7 +2961,8 @@ int sqlite3WalSnapshotRecover(Wal *pWal){ rc = walHashGet(pWal, walFramePage(i), &sLoc); if( rc!=SQLITE_OK ) break; - pgno = sLoc.aPgno[i-sLoc.iZero]; + assert( i - sLoc.iZero - 1 >=0 ); + pgno = sLoc.aPgno[i-sLoc.iZero-1]; iDbOff = (i64)(pgno-1) * szPage; if( iDbOff+szPage<=szDb ){ @@ -3091,7 +3195,7 @@ int sqlite3WalFindFrame( iKey = walHash(pgno); while( (iH = AtomicLoad(&sLoc.aHash[iKey]))!=0 ){ u32 iFrame = iH + sLoc.iZero; - if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH]==pgno ){ + if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH-1]==pgno ){ assert( iFrame>iRead || CORRUPT_DB ); iRead = iFrame; } diff --git a/src/walker.c b/src/walker.c index 8c0308473..f24c052d6 100644 --- a/src/walker.c +++ b/src/walker.c @@ -75,7 +75,7 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ assert( !ExprHasProperty(pExpr, EP_WinFunc) ); pExpr = pExpr->pRight; continue; - }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + }else if( ExprUseXSelect(pExpr) ){ assert( !ExprHasProperty(pExpr, EP_WinFunc) ); if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; }else{ diff --git a/src/where.c b/src/where.c index 5315520db..7a8342675 100644 --- a/src/where.c +++ b/src/where.c @@ -286,8 +286,10 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ iColumn = pScan->aiColumn[pScan->iEquiv-1]; iCur = pScan->aiCur[pScan->iEquiv-1]; assert( pWC!=0 ); + assert( iCur>=0 ); do{ for(pTerm=pWC->a+k; knTerm; k++, pTerm++){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 || pTerm->leftCursor<0 ); if( pTerm->leftCursor==iCur && pTerm->u.x.leftColumn==iColumn && (iColumn!=XN_EXPR @@ -329,7 +331,8 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ } } if( (pTerm->eOperator & (WO_EQ|WO_IS))!=0 - && (pX = pTerm->pExpr->pRight)->op==TK_COLUMN + && (pX = pTerm->pExpr->pRight, ALWAYS(pX!=0)) + && pX->op==TK_COLUMN && pX->iTable==pScan->aiCur[0] && pX->iColumn==pScan->aiColumn[0] ){ @@ -726,6 +729,7 @@ static int termCanDriveIndex( return 0; } if( (pTerm->prereqRight & notReady)!=0 ) return 0; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); if( pTerm->u.x.leftColumn<0 ) return 0; aff = pSrc->pTab->aCol[pTerm->u.x.leftColumn].affinity; if( !sqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0; @@ -798,14 +802,17 @@ static void constructAutomaticIndex( sqlite3ExprDup(pParse->db, pExpr, 0)); } if( termCanDriveIndex(pTerm, pSrc, notReady) ){ - int iCol = pTerm->u.x.leftColumn; - Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS ); testcase( iCol==BMS-1 ); if( !sentWarning ){ sqlite3_log(SQLITE_WARNING_AUTOINDEX, "automatic index on %s(%s)", pTable->zName, - pTable->aCol[iCol].zName); + pTable->aCol[iCol].zCnName); sentWarning = 1; } if( (idxCols & cMask)==0 ){ @@ -851,8 +858,11 @@ static void constructAutomaticIndex( idxCols = 0; for(pTerm=pWC->a; pTermu.x.leftColumn; - Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS-1 ); testcase( iCol==BMS ); if( (idxCols & cMask)==0 ){ @@ -979,6 +989,7 @@ static sqlite3_index_info *allocateIndexInfo( testcase( pTerm->eOperator & WO_ALL ); if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pTerm->u.x.leftColumn>=(-1) ); nTerm++; } @@ -1039,6 +1050,7 @@ static sqlite3_index_info *allocateIndexInfo( ){ continue; } + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pTerm->u.x.leftColumn>=(-1) ); pIdxCons[j].iColumn = pTerm->u.x.leftColumn; pIdxCons[j].iTermOffset = i; @@ -1802,6 +1814,7 @@ void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) zType[2] = 'L'; if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C'; if( pTerm->eOperator & WO_SINGLE ){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); sqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}", pTerm->leftCursor, pTerm->u.x.leftColumn); }else if( (pTerm->eOperator & WO_OR)!=0 && pTerm->u.pOrInfo!=0 ){ @@ -1819,7 +1832,7 @@ void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ sqlite3DebugPrintf(" prob=%-3d prereq=%llx,%llx", pTerm->truthProb, (u64)pTerm->prereqAll, (u64)pTerm->prereqRight); } - if( pTerm->u.x.iField ){ + if( (pTerm->eOperator & (WO_OR|WO_AND))==0 && pTerm->u.x.iField ){ sqlite3DebugPrintf(" iField=%d", pTerm->u.x.iField); } if( pTerm->iParent>=0 ){ @@ -1983,7 +1996,8 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ assert( pWInfo!=0 ); for(i=0; inLevel; i++){ WhereLevel *pLevel = &pWInfo->a[i]; - if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE) ){ + if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE)!=0 ){ + assert( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0 ); sqlite3DbFree(db, pLevel->u.in.aInLoop); } } @@ -2011,7 +2025,8 @@ static void whereUndoExprMods(WhereInfo *pWInfo){ /* ** Return TRUE if all of the following are true: ** -** (1) X has the same or lower cost that Y +** (1) X has the same or lower cost, or returns the same or fewer rows, +** than Y. ** (2) X uses fewer WHERE clause terms than Y ** (3) Every WHERE clause term used by X is also used by Y ** (4) X skips at least as many columns as Y @@ -2034,11 +2049,8 @@ static int whereLoopCheaperProperSubset( if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ return 0; /* X is not a subset of Y */ } + if( pX->rRun>pY->rRun && pX->nOut>pY->nOut ) return 0; if( pY->nSkip > pX->nSkip ) return 0; - if( pX->rRun >= pY->rRun ){ - if( pX->rRun > pY->rRun ) return 0; /* X costs more than Y */ - if( pX->nOut > pY->nOut ) return 0; /* X costs more than Y */ - } for(i=pX->nLTerm-1; i>=0; i--){ if( pX->aLTerm[i]==0 ) continue; for(j=pY->nLTerm-1; j>=0; j--){ @@ -2054,8 +2066,8 @@ static int whereLoopCheaperProperSubset( } /* -** Try to adjust the cost of WhereLoop pTemplate upwards or downwards so -** that: +** Try to adjust the cost and number of output rows of WhereLoop pTemplate +** upwards or downwards so that: ** ** (1) pTemplate costs less than any other WhereLoops that are a proper ** subset of pTemplate @@ -2076,16 +2088,20 @@ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ /* Adjust pTemplate cost downward so that it is cheaper than its ** subset p. */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut-1)); - pTemplate->rRun = p->rRun; - pTemplate->nOut = p->nOut - 1; + pTemplate->rRun, pTemplate->nOut, + MIN(p->rRun, pTemplate->rRun), + MIN(p->nOut - 1, pTemplate->nOut))); + pTemplate->rRun = MIN(p->rRun, pTemplate->rRun); + pTemplate->nOut = MIN(p->nOut - 1, pTemplate->nOut); }else if( whereLoopCheaperProperSubset(pTemplate, p) ){ /* Adjust pTemplate cost upward so that it is costlier than p since ** pTemplate is a proper subset of p */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut+1)); - pTemplate->rRun = p->rRun; - pTemplate->nOut = p->nOut + 1; + pTemplate->rRun, pTemplate->nOut, + MAX(p->rRun, pTemplate->rRun), + MAX(p->nOut + 1, pTemplate->nOut))); + pTemplate->rRun = MAX(p->rRun, pTemplate->rRun); + pTemplate->nOut = MAX(p->nOut + 1, pTemplate->nOut); } } } @@ -2416,9 +2432,12 @@ static int whereRangeVectorLen( char aff; /* Comparison affinity */ char idxaff = 0; /* Indexed columns affinity */ CollSeq *pColl; /* Comparison collation sequence */ - Expr *pLhs = pTerm->pExpr->pLeft->x.pList->a[i].pExpr; - Expr *pRhs = pTerm->pExpr->pRight; - if( pRhs->flags & EP_xIsSelect ){ + Expr *pLhs, *pRhs; + + assert( ExprUseXList(pTerm->pExpr->pLeft) ); + pLhs = pTerm->pExpr->pLeft->x.pList->a[i].pExpr; + pRhs = pTerm->pExpr->pRight; + if( ExprUseXSelect(pRhs) ){ pRhs = pRhs->x.pSelect->pEList->a[i].pExpr; }else{ pRhs = pRhs->x.pList->a[i].pExpr; @@ -2579,7 +2598,7 @@ static int whereLoopAddBtreeIndex( if( eOp & WO_IN ){ Expr *pExpr = pTerm->pExpr; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ /* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */ int i; nIn = 46; assert( 46==sqlite3LogEst(25) ); @@ -2720,7 +2739,7 @@ static int whereLoopAddBtreeIndex( if( nInMul==0 && pProbe->nSample && ALWAYS(pNew->u.btree.nEq<=pProbe->nSampleCol) - && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect)) + && ((eOp & WO_IN)==0 || ExprUseXList(pTerm->pExpr)) && OptimizationEnabled(db, SQLITE_Stat4) ){ Expr *pExpr = pTerm->pExpr; @@ -2984,7 +3003,6 @@ static int whereLoopAddBtree( int iSortIdx = 1; /* Index number */ int b; /* A boolean value */ LogEst rSize; /* number of rows in the table */ - LogEst rLogSize; /* Logarithm of the number of rows in the table */ WhereClause *pWC; /* The parsed WHERE clause */ Table *pTab; /* Table being queried */ @@ -2997,6 +3015,7 @@ static int whereLoopAddBtree( assert( !IsVirtual(pSrc->pTab) ); if( pSrc->fg.isIndexedBy ){ + assert( pSrc->fg.isCte==0 ); /* An INDEXED BY clause specifies a particular index to use */ pProbe = pSrc->u2.pIBIndex; }else if( !HasRowid(pTab) ){ @@ -3027,7 +3046,6 @@ static int whereLoopAddBtree( pProbe = &sPk; } rSize = pTab->nRowLogEst; - rLogSize = estLog(rSize); #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* Automatic indexes */ @@ -3041,8 +3059,10 @@ static int whereLoopAddBtree( && !pSrc->fg.isRecursive /* Not a recursive common table expression. */ ){ /* Generate auto-index WhereLoops */ + LogEst rLogSize; /* Logarithm of the number of rows in the table */ WhereTerm *pTerm; WhereTerm *pWCEnd = pWC->a + pWC->nTerm; + rLogSize = estLog(rSize); for(pTerm=pWC->a; rc==SQLITE_OK && pTermprereqRight & pNew->maskSelf ) continue; if( termCanDriveIndex(pTerm, pSrc, 0) ){ @@ -3060,7 +3080,7 @@ static int whereLoopAddBtree( ** those objects, since there is no opportunity to add schema ** indexes on subqueries and views. */ pNew->rSetup = rLogSize + rSize; - if( pTab->pSelect==0 && (pTab->tabFlags & TF_Ephemeral)==0 ){ + if( !IsView(pTab) && (pTab->tabFlags & TF_Ephemeral)==0 ){ pNew->rSetup += 28; }else{ pNew->rSetup -= 10; @@ -4082,7 +4102,7 @@ static i8 wherePathSatisfiesOrderBy( if( obSat==obDone ) return (i8)nOrderBy; if( !isOrderDistinct ){ for(i=nOrderBy-1; i>0; i--){ - Bitmask m = MASKBIT(i) - 1; + Bitmask m = ALWAYS(ipWInfo; if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0; @@ -4605,7 +4626,8 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ pLoop = pBuilder->pNew; pLoop->wsFlags = 0; pLoop->nSkip = 0; - pTerm = sqlite3WhereFindTerm(pWC, iCur, -1, 0, WO_EQ|WO_IS, 0); + pTerm = whereScanInit(&scan, pWC, iCur, -1, WO_EQ|WO_IS, 0); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); if( pTerm ){ testcase( pTerm->eOperator & WO_IS ); pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW; @@ -4624,7 +4646,8 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ ) continue; opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ; for(j=0; jnKeyCol; j++){ - pTerm = sqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx); + pTerm = whereScanInit(&scan, pWC, iCur, j, opMask, pIdx); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); if( pTerm==0 ) break; testcase( pTerm->eOperator & WO_IS ); pLoop->aLTerm[j] = pTerm; @@ -4653,8 +4676,14 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } + if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS; #ifdef SQLITE_DEBUG pLoop->cId = '0'; +#endif +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace ){ + sqlite3DebugPrintf("whereShortCut() used to compute solution\n"); + } #endif return 1; } @@ -5211,7 +5240,7 @@ WhereInfo *sqlite3WhereBegin( pTab = pTabItem->pTab; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); pLoop = pLevel->pWLoop; - if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ + if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){ /* Do nothing */ }else #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -5336,6 +5365,7 @@ WhereInfo *sqlite3WhereBegin( for(ii=0; iinErr ) goto whereBeginError; pLevel = &pWInfo->a[ii]; wsFlags = pLevel->pWLoop->wsFlags; #ifndef SQLITE_OMIT_AUTOMATIC_INDEX @@ -5457,7 +5487,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ }else{ sqlite3VdbeResolveLabel(v, pLevel->addrCont); } - if( pLoop->wsFlags & WHERE_IN_ABLE && pLevel->u.in.nIn>0 ){ + if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){ struct InLoop *pIn; int j; sqlite3VdbeResolveLabel(v, pLevel->addrNxt); @@ -5526,8 +5556,14 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } if( (ws & WHERE_INDEXED) - || ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx) + || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) ){ + if( ws & WHERE_MULTI_OR ){ + Index *pIx = pLevel->u.pCoveringIdx; + int iDb = sqlite3SchemaToIndex(db, pIx->pSchema); + sqlite3VdbeAddOp3(v, OP_ReopenIdx, pLevel->iIdxCur, pIx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIx); + } sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); } if( pLevel->op==OP_Return ){ @@ -5574,7 +5610,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ ** created for the ONEPASS optimization. */ if( (pTab->tabFlags & TF_Ephemeral)==0 - && pTab->pSelect==0 + && !IsView(pTab) && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){ int ws = pLoop->wsFlags; @@ -5604,7 +5640,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ if( pLoop->wsFlags & (WHERE_INDEXED|WHERE_IDX_ONLY) ){ pIdx = pLoop->u.btree.pIndex; }else if( pLoop->wsFlags & WHERE_MULTI_OR ){ - pIdx = pLevel->u.pCovidx; + pIdx = pLevel->u.pCoveringIdx; } if( pIdx && !db->mallocFailed diff --git a/src/whereInt.h b/src/whereInt.h index c5dd83cc8..f651e790c 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -78,7 +78,7 @@ struct WhereLevel { u8 eEndLoopOp; /* IN Loop terminator. OP_Next or OP_Prev */ } *aInLoop; /* Information about each nested IN operator */ } in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */ - Index *pCovidx; /* Possible covering index for WHERE_MULTI_OR */ + Index *pCoveringIdx; /* Possible covering index for WHERE_MULTI_OR */ } u; struct WhereLoop *pWLoop; /* The selected WhereLoop object */ Bitmask notReady; /* FROM entries not usable at this level */ diff --git a/src/wherecode.c b/src/wherecode.c index 84fa1a141..460ac4fe3 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -29,7 +29,7 @@ static const char *explainIndexColumnName(Index *pIdx, int i){ i = pIdx->aiColumn[i]; if( i==XN_EXPR ) return ""; if( i==XN_ROWID ) return "rowid"; - return pIdx->pTable->aCol[i].zName; + return pIdx->pTable->aCol[i].zCnName; } /* @@ -416,16 +416,23 @@ static Expr *removeUnindexableInClauseTerms( Expr *pNew; pNew = sqlite3ExprDup(db, pX, 0); if( db->mallocFailed==0 ){ - ExprList *pOrigRhs = pNew->x.pSelect->pEList; /* Original unmodified RHS */ - ExprList *pOrigLhs = pNew->pLeft->x.pList; /* Original unmodified LHS */ + ExprList *pOrigRhs; /* Original unmodified RHS */ + ExprList *pOrigLhs; /* Original unmodified LHS */ ExprList *pRhs = 0; /* New RHS after modifications */ ExprList *pLhs = 0; /* New LHS after mods */ int i; /* Loop counter */ Select *pSelect; /* Pointer to the SELECT on the RHS */ + assert( ExprUseXSelect(pNew) ); + pOrigRhs = pNew->x.pSelect->pEList; + assert( pNew->pLeft!=0 ); + assert( ExprUseXList(pNew->pLeft) ); + pOrigLhs = pNew->pLeft->x.pList; for(i=iEq; inLTerm; i++){ if( pLoop->aLTerm[i]->pExpr==pX ){ - int iField = pLoop->aLTerm[i]->u.x.iField - 1; + int iField; + assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 ); + iField = pLoop->aLTerm[i]->u.x.iField - 1; if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */ pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); pOrigRhs->a[iField].pExpr = 0; @@ -540,7 +547,7 @@ static int codeEqualityTerm( } iTab = 0; - if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){ + if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){ eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab); }else{ sqlite3 *db = pParse->db; @@ -562,8 +569,8 @@ static int codeEqualityTerm( sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0); VdbeCoverageIf(v, bRev); VdbeCoverageIf(v, !bRev); - assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); pLoop->wsFlags |= WHERE_IN_ABLE; if( pLevel->u.in.nIn==0 ){ pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); @@ -1105,7 +1112,7 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ assert( nReg>0 ); if( p && sqlite3ExprIsVector(p) ){ #ifndef SQLITE_OMIT_SUBQUERY - if( (p->flags & EP_xIsSelect) ){ + if( ExprUseXSelect(p) ){ Vdbe *v = pParse->pVdbe; int iSelect; assert( p->op==TK_SELECT ); @@ -1115,7 +1122,9 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ #endif { int i; - ExprList *pList = p->x.pList; + const ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; assert( nReg<=pList->nExpr ); for(i=0; ia[i].pExpr, iReg+i); @@ -1168,10 +1177,10 @@ static int whereIndexExprTransNode(Walker *p, Expr *pExpr){ pExpr->op = TK_COLUMN; pExpr->iTable = pX->iIdxCur; pExpr->iColumn = pX->iIdxCol; - pExpr->y.pTab = 0; testcase( ExprHasProperty(pExpr, EP_Skip) ); testcase( ExprHasProperty(pExpr, EP_Unlikely) ); - ExprClearProperty(pExpr, EP_Skip|EP_Unlikely); + ExprClearProperty(pExpr, EP_Skip|EP_Unlikely|EP_WinFunc|EP_Subrtn); + pExpr->y.pTab = 0; return WRC_Prune; }else{ return WRC_Continue; @@ -1186,7 +1195,7 @@ static int whereIndexExprTransColumn(Walker *p, Expr *pExpr){ if( pExpr->op==TK_COLUMN ){ IdxExprTrans *pX = p->u.pIdxTrans; if( pExpr->iTable==pX->iTabCur && pExpr->iColumn==pX->iTabCol ){ - assert( pExpr->y.pTab!=0 ); + assert( ExprUseYTab(pExpr) && pExpr->y.pTab!=0 ); preserveExpr(pX, pExpr); pExpr->affExpr = sqlite3TableColumnAffinity(pExpr->y.pTab,pExpr->iColumn); pExpr->iTable = pX->iIdxCur; @@ -1234,15 +1243,16 @@ static void whereIndexExprTrans( for(iIdxCol=0; iIdxColnColumn; iIdxCol++){ i16 iRef = pIdx->aiColumn[iIdxCol]; if( iRef==XN_EXPR ){ - assert( aColExpr->a[iIdxCol].pExpr!=0 ); + assert( aColExpr!=0 && aColExpr->a[iIdxCol].pExpr!=0 ); x.pIdxExpr = aColExpr->a[iIdxCol].pExpr; if( sqlite3ExprIsConstant(x.pIdxExpr) ) continue; w.xExprCallback = whereIndexExprTransNode; #ifndef SQLITE_OMIT_GENERATED_COLUMNS }else if( iRef>=0 && (pTab->aCol[iRef].colFlags & COLFLAG_VIRTUAL)!=0 - && (pTab->aCol[iRef].zColl==0 - || sqlite3StrICmp(pTab->aCol[iRef].zColl, sqlite3StrBINARY)==0) + && ((pTab->aCol[iRef].colFlags & COLFLAG_HASCOLL)==0 + || sqlite3StrICmp(sqlite3ColumnColl(&pTab->aCol[iRef]), + sqlite3StrBINARY)==0) ){ /* Check to see if there are direct references to generated columns ** that are contained in the index. Pulling the generated column @@ -1422,7 +1432,12 @@ Bitmask sqlite3WhereCodeOneLoopStart( pLevel->p1 = iCur; pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext; pLevel->p2 = sqlite3VdbeCurrentAddr(v); - iIn = pLevel->u.in.nIn; + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + if( pLoop->wsFlags & WHERE_IN_ABLE ){ + iIn = pLevel->u.in.nIn; + }else{ + iIn = 0; + } for(j=nConstraint-1; j>=0; j--){ pTerm = pLoop->aLTerm[j]; if( (pTerm->eOperator & WO_IN)!=0 ) iIn--; @@ -1499,9 +1514,6 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg); VdbeCoverage(v); pLevel->op = OP_Noop; - if( (pTerm->prereqAll & pLevel->notReady)==0 ){ - pTerm->wtFlags |= TERM_CODED; - } }else if( (pLoop->wsFlags & WHERE_IPK)!=0 && (pLoop->wsFlags & WHERE_COLUMN_RANGE)!=0 ){ @@ -1872,8 +1884,19 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** range (if any). */ nConstraint = nEq; + assert( pLevel->p2==0 ); if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; + if( addrSeekScan ){ + /* For a seek-scan that has a range on the lowest term of the index, + ** we have to make the top of the loop be code that sets the end + ** condition of the range. Otherwise, the OP_SeekScan might jump + ** over that initialization, leaving the range-end value set to the + ** range-start value, resulting in a wrong answer. + ** See ticket 5981a8c041a3c2f3 (2021-11-02). + */ + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + } codeExprOrVector(pParse, pRight, regBase+nEq, nTop); whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); if( (pRangeEnd->wtFlags & TERM_VNULL)==0 @@ -1907,7 +1930,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3DbFree(db, zEndAff); /* Top of the loop body */ - pLevel->p2 = sqlite3VdbeCurrentAddr(v); + if( pLevel->p2==0 ) pLevel->p2 = sqlite3VdbeCurrentAddr(v); /* Check if the index cursor is past the end of the range. */ if( nConstraint ){ @@ -2314,7 +2337,10 @@ Bitmask sqlite3WhereCodeOneLoopStart( } } ExplainQueryPlanPop(pParse); - pLevel->u.pCovidx = pCov; + assert( pLevel->pWLoop==pLoop ); + assert( (pLoop->wsFlags & WHERE_MULTI_OR)!=0 ); + assert( (pLoop->wsFlags & WHERE_IN_ABLE)==0 ); + pLevel->u.pCoveringIdx = pCov; if( pCov ) pLevel->iIdxCur = iCovCur; if( pAndExpr ){ pAndExpr->pLeft = 0; @@ -2458,12 +2484,13 @@ Bitmask sqlite3WhereCodeOneLoopStart( #endif assert( !ExprHasProperty(pE, EP_FromJoin) ); assert( (pTerm->prereqRight & pLevel->notReady)!=0 ); + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.x.leftColumn, notReady, WO_EQ|WO_IN|WO_IS, 0); if( pAlt==0 ) continue; if( pAlt->wtFlags & (TERM_CODED) ) continue; if( (pAlt->eOperator & WO_IN) - && (pAlt->pExpr->flags & EP_xIsSelect) + && ExprUseXSelect(pAlt->pExpr) && (pAlt->pExpr->x.pSelect->pEList->nExpr>1) ){ continue; diff --git a/src/whereexpr.c b/src/whereexpr.c index 31f2ea438..53dd14031 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -195,6 +195,7 @@ static int isLikeOrGlob( #ifdef SQLITE_EBCDIC if( *pnoCase ) return 0; #endif + assert( ExprUseXList(pExpr) ); pList = pExpr->x.pList; pLeft = pList->a[1].pExpr; @@ -210,7 +211,8 @@ static int isLikeOrGlob( sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); }else if( op==TK_STRING ){ - z = (u8*)pRight->u.zToken; + assert( !ExprHasProperty(pRight, EP_IntValue) ); + z = (u8*)pRight->u.zToken; } if( z ){ @@ -239,7 +241,9 @@ static int isLikeOrGlob( pPrefix = sqlite3Expr(db, TK_STRING, (char*)z); if( pPrefix ){ int iFrom, iTo; - char *zNew = pPrefix->u.zToken; + char *zNew; + assert( !ExprHasProperty(pPrefix, EP_IntValue) ); + zNew = pPrefix->u.zToken; zNew[cnt] = 0; for(iFrom=iTo=0; iFromop!=TK_COLUMN || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT - || IsVirtual(pLeft->y.pTab) /* Value might be numeric */ + || (ALWAYS( ExprUseYTab(pLeft) ) + && pLeft->y.pTab + && IsVirtual(pLeft->y.pTab)) /* Might be numeric */ ){ int isNum; double rDummy; @@ -291,6 +297,7 @@ static int isLikeOrGlob( if( op==TK_VARIABLE ){ Vdbe *v = pParse->pVdbe; sqlite3VdbeSetVarmask(v, pRight->iColumn); + assert( !ExprHasProperty(pRight, EP_IntValue) ); 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 @@ -364,6 +371,7 @@ static int isAuxiliaryVtabOperator( Expr *pCol; /* Column reference */ int i; + assert( ExprUseXList(pExpr) ); pList = pExpr->x.pList; if( pList==0 || pList->nExpr!=2 ){ return 0; @@ -377,9 +385,11 @@ static int isAuxiliaryVtabOperator( ** MATCH(expression,vtab_column) */ pCol = pList->a[1].pExpr; + assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 ); if( ExprIsVtab(pCol) ){ for(i=0; iu.zToken, aOp[i].zOp)==0 ){ *peOp2 = aOp[i].eOp2; *ppRight = pList->a[0].pExpr; @@ -400,6 +410,7 @@ static int isAuxiliaryVtabOperator( ** with function names in an arbitrary case. */ pCol = pList->a[0].pExpr; + assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 ); if( ExprIsVtab(pCol) ){ sqlite3_vtab *pVtab; @@ -409,7 +420,8 @@ static int isAuxiliaryVtabOperator( pVtab = sqlite3GetVTable(db, pCol->y.pTab)->pVtab; assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); - pMod = (sqlite3_module *)pVtab->pModule; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction!=0 ){ i = pMod->xFindFunction(pVtab,2, pExpr->u.zToken, &xNotUsed, &pNotUsed); if( i>=SQLITE_INDEX_CONSTRAINT_FUNCTION ){ @@ -424,10 +436,12 @@ static int isAuxiliaryVtabOperator( int res = 0; Expr *pLeft = pExpr->pLeft; Expr *pRight = pExpr->pRight; + assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); testcase( pLeft->op==TK_COLUMN && pLeft->y.pTab==0 ); if( ExprIsVtab(pLeft) ){ res++; } + assert( pRight==0 || pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); testcase( pRight && pRight->op==TK_COLUMN && pRight->y.pTab==0 ); if( pRight && ExprIsVtab(pRight) ){ res++; @@ -680,6 +694,7 @@ static void exprAnalyzeOrTerm( pOrTerm->u.pAndInfo = pAndInfo; pOrTerm->wtFlags |= TERM_ANDINFO; pOrTerm->eOperator = WO_AND; + pOrTerm->leftCursor = -1; pAndWC = &pAndInfo->wc; memset(pAndWC->aStatic, 0, sizeof(pAndWC->aStatic)); sqlite3WhereClauseInit(pAndWC, pWC->pWInfo); @@ -722,11 +737,10 @@ static void exprAnalyzeOrTerm( ** empty. */ pOrInfo->indexable = indexable; + pTerm->eOperator = WO_OR; + pTerm->leftCursor = -1; if( indexable ){ - pTerm->eOperator = WO_OR; pWC->hasOr = 1; - }else{ - pTerm->eOperator = WO_OR; } /* For a two-way OR, attempt to implementation case 2. @@ -799,6 +813,7 @@ static void exprAnalyzeOrTerm( assert( pOrTerm->wtFlags & (TERM_COPIED|TERM_VIRTUAL) ); continue; } + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); iColumn = pOrTerm->u.x.leftColumn; iCursor = pOrTerm->leftCursor; pLeft = pOrTerm->pExpr->pLeft; @@ -819,6 +834,7 @@ static void exprAnalyzeOrTerm( okToChngToIN = 1; for(; i>=0 && okToChngToIN; i--, pOrTerm++){ assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); if( pOrTerm->leftCursor!=iCursor ){ pOrTerm->wtFlags &= ~TERM_OR_OK; }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR @@ -855,6 +871,7 @@ static void exprAnalyzeOrTerm( for(i=pOrWc->nTerm-1, pOrTerm=pOrWc->a; i>=0; i--, pOrTerm++){ if( (pOrTerm->wtFlags & TERM_OR_OK)==0 ) continue; assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pOrTerm->leftCursor==iCursor ); assert( pOrTerm->u.x.leftColumn==iColumn ); pDup = sqlite3ExprDup(db, pOrTerm->pExpr->pRight, 0); @@ -867,7 +884,7 @@ static void exprAnalyzeOrTerm( if( pNew ){ int idxNew; transferJoinMarkings(pNew, pExpr); - assert( !ExprHasProperty(pNew, EP_xIsSelect) ); + assert( ExprUseXList(pNew) ); pNew->x.pList = pList; idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); @@ -995,6 +1012,7 @@ static int exprMightBeIndexed( assert( TK_ISop==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ + assert( ExprUseXList(pExpr) ); pExpr = pExpr->x.pList->a[0].pExpr; } @@ -1061,7 +1079,7 @@ static void exprAnalyze( if( op==TK_IN ){ assert( pExpr->pRight==0 ); if( sqlite3ExprCheckIN(pParse, pExpr) ) return; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect); }else{ pTerm->prereqRight = sqlite3WhereExprListUsage(pMaskSet, pExpr->x.pList); @@ -1097,11 +1115,13 @@ static void exprAnalyze( if( pTerm->u.x.iField>0 ){ assert( op==TK_IN ); assert( pLeft->op==TK_VECTOR ); + assert( ExprUseXList(pLeft) ); pLeft = pLeft->x.pList->a[pTerm->u.x.iField-1].pExpr; } if( exprMightBeIndexed(pSrc, prereqLeft, aiCurCol, pLeft, op) ){ pTerm->leftCursor = aiCurCol[0]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pTerm->u.x.leftColumn = aiCurCol[1]; pTerm->eOperator = operatorMask(op) & opMask; } @@ -1139,12 +1159,18 @@ static void exprAnalyze( } pNew->wtFlags |= exprCommute(pParse, pDup); pNew->leftCursor = aiCurCol[0]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pNew->u.x.leftColumn = aiCurCol[1]; testcase( (prereqLeft | extraRight) != prereqLeft ); pNew->prereqRight = prereqLeft | extraRight; pNew->prereqAll = prereqAll; pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask; - }else if( op==TK_ISNULL && 0==sqlite3ExprCanBeNull(pLeft) ){ + }else + if( op==TK_ISNULL + && !ExprHasProperty(pExpr,EP_FromJoin) + && 0==sqlite3ExprCanBeNull(pLeft) + ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pExpr->op = TK_TRUEFALSE; pExpr->u.zToken = "false"; ExprSetProperty(pExpr, EP_IsFalse); @@ -1170,9 +1196,11 @@ static void exprAnalyze( ** BETWEEN term is skipped. */ else if( pExpr->op==TK_BETWEEN && pWC->op==TK_AND ){ - ExprList *pList = pExpr->x.pList; + ExprList *pList; int i; static const u8 ops[] = {TK_GE, TK_LE}; + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; assert( pList!=0 ); assert( pList->nExpr==2 ); for(i=0; i<2; i++){ @@ -1265,8 +1293,12 @@ static void exprAnalyze( const char *zCollSeqName; /* Name of collating sequence */ const u16 wtFlags = TERM_LIKEOPT | TERM_VIRTUAL | TERM_DYNAMIC; + assert( ExprUseXList(pExpr) ); pLeft = pExpr->x.pList->a[1].pExpr; pStr2 = sqlite3ExprDup(db, pStr1, 0); + assert( pStr1==0 || !ExprHasProperty(pStr1, EP_IntValue) ); + assert( pStr2==0 || !ExprHasProperty(pStr2, EP_IntValue) ); + /* Convert the lower bound to upper-case and the upper bound to ** lower-case (upper-case is less than lower-case in ASCII) so that @@ -1341,8 +1373,8 @@ static void exprAnalyze( for(i=0; ipLeft, i); - Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i); + Expr *pLeft = sqlite3ExprForVectorField(pParse, pExpr->pLeft, i, nLeft); + Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i, nLeft); pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight); transferJoinMarkings(pNew, pExpr); @@ -1366,6 +1398,7 @@ static void exprAnalyze( else if( pExpr->op==TK_IN && pTerm->u.x.iField==0 && pExpr->pLeft->op==TK_VECTOR + && ALWAYS( ExprUseXSelect(pExpr) ) && pExpr->x.pSelect->pPrior==0 #ifndef SQLITE_OMIT_WINDOWFUNC && pExpr->x.pSelect->pWin==0 @@ -1529,14 +1562,15 @@ Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ if( p->pRight ){ mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pRight); assert( p->x.pList==0 ); - }else if( ExprHasProperty(p, EP_xIsSelect) ){ + }else if( ExprUseXSelect(p) ){ if( ExprHasProperty(p, EP_VarSelect) ) pMaskSet->bVarSelect = 1; mask |= exprSelectUsage(pMaskSet, p->x.pSelect); }else if( p->x.pList ){ mask |= sqlite3WhereExprListUsage(pMaskSet, p->x.pList); } #ifndef SQLITE_OMIT_WINDOWFUNC - if( (p->op==TK_FUNCTION || p->op==TK_AGG_FUNCTION) && p->y.pWin ){ + if( (p->op==TK_FUNCTION || p->op==TK_AGG_FUNCTION) && ExprUseYWin(p) ){ + assert( p->y.pWin!=0 ); mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pPartition); mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pOrderBy); mask |= sqlite3WhereExprUsage(pMaskSet, p->y.pWin->pFilter); @@ -1611,6 +1645,7 @@ void sqlite3WhereTabFuncArgs( if( pColRef==0 ) return; pColRef->iTable = pItem->iCursor; pColRef->iColumn = k++; + assert( ExprUseYTab(pColRef) ); pColRef->y.pTab = pTab; pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); diff --git a/src/window.c b/src/window.c index 2afa7c12c..d7c412a3a 100644 --- a/src/window.c +++ b/src/window.c @@ -581,7 +581,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } /* Window functions that use all window interfaces: xStep, xFinal, ** xValue, and xInverse */ #define WINDOWFUNCALL(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ name ## StepFunc, name ## FinalizeFunc, name ## ValueFunc, \ name ## InvFunc, name ## Name, {0} \ } @@ -589,7 +589,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } /* Window functions that are implemented using bytecode and thus have ** no-op routines for their methods */ #define WINDOWFUNCNOOP(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ noopStepFunc, noopValueFunc, noopValueFunc, \ noopStepFunc, name ## Name, {0} \ } @@ -598,7 +598,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } ** same routine for xFinalize and xValue and which never call ** xInverse. */ #define WINDOWFUNCX(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ name ## StepFunc, name ## ValueFunc, name ## ValueFunc, \ noopStepFunc, name ## Name, {0} \ } @@ -908,9 +908,7 @@ static ExprList *exprListAppendList( if( bIntToNull ){ int iDummy; Expr *pSub; - for(pSub=pDup; ExprHasProperty(pSub, EP_Skip); pSub=pSub->pLeft){ - assert( pSub ); - } + pSub = sqlite3ExprSkipCollateAndLikely(pDup); if( sqlite3ExprIsInteger(pSub, &iDummy) ){ pSub->op = TK_NULL; pSub->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse); @@ -943,7 +941,8 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_AGG_FUNCTION && pExpr->pAggInfo==0 ){ - sqlite3ErrorMsg(pWalker->pParse, + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3ErrorMsg(pWalker->pParse, "misuse of aggregate: %s()", pExpr->u.zToken); } return WRC_Continue; @@ -1031,7 +1030,9 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ ** window function - one for the accumulator, another for interim ** results. */ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - ExprList *pArgs = pWin->pOwner->x.pList; + ExprList *pArgs; + assert( ExprUseXList(pWin->pOwner) ); + pArgs = pWin->pOwner->x.pList; if( pWin->pFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); @@ -1068,11 +1069,14 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ ("New window-function subquery in FROM clause of (%u/%p)\n", p->selId, p)); p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside + ** of sqlite3DbMallocRawNN() called from + ** sqlite3SrcListAppend() */ if( p->pSrc ){ Table *pTab2; p->pSrc->a[0].pSelect = pSub; sqlite3SrcListAssignCursors(pParse, p->pSrc); - pSub->selFlags |= SF_Expanded; + pSub->selFlags |= SF_Expanded|SF_OrderByReqd; pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE); pSub->selFlags |= (selFlags & SF_Aggregate); if( pTab2==0 ){ @@ -1095,7 +1099,11 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ sqlite3SelectDelete(db, pSub); } if( db->mallocFailed ) rc = SQLITE_NOMEM; - sqlite3DbFree(db, pTab); + + /* Defer deleting the temporary table pTab because if an error occurred, + ** there could still be references to that table embedded in the + ** result-set or ORDER BY clause of the SELECT statement p. */ + sqlite3ParserAddCleanup(pParse, sqlite3DbFree, pTab); } if( rc ){ @@ -1344,7 +1352,12 @@ void sqlite3WindowLink(Select *pSel, Window *pWin){ ** different, or 2 if it cannot be determined if the objects are identical ** or not. Identical window objects can be processed in a single scan. */ -int sqlite3WindowCompare(Parse *pParse, Window *p1, Window *p2, int bFilter){ +int sqlite3WindowCompare( + const Parse *pParse, + const Window *p1, + const Window *p2, + int bFilter +){ int res; if( NEVER(p1==0) || NEVER(p2==0) ) return 1; if( p1->eFrmType!=p2->eFrmType ) return 1; @@ -1416,8 +1429,11 @@ void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){ ** regApp+1: integer value used to ensure keys are unique ** regApp+2: output of MakeRecord */ - ExprList *pList = pWin->pOwner->x.pList; - KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pList, 0, 0); + ExprList *pList; + KeyInfo *pKeyInfo; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pList, 0, 0); pWin->csrApp = pParse->nTab++; pWin->regApp = pParse->nMem+1; pParse->nMem += 3; @@ -1505,7 +1521,9 @@ static void windowCheckValue(Parse *pParse, int reg, int eCond){ ** with the object passed as the only argument to this function. */ static int windowArgCount(Window *pWin){ - ExprList *pList = pWin->pOwner->x.pList; + const ExprList *pList; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; return (pList ? pList->nExpr : 0); } @@ -1690,6 +1708,7 @@ static void windowAggStep( int addrIf = 0; if( pWin->pFilter ){ int regTmp; + assert( ExprUseXList(pWin->pOwner) ); assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); regTmp = sqlite3GetTempReg(pParse); @@ -1703,6 +1722,7 @@ static void windowAggStep( int iOp = sqlite3VdbeCurrentAddr(v); int iEnd; + assert( ExprUseXList(pWin->pOwner) ); nArg = pWin->pOwner->x.pList->nExpr; regArg = sqlite3GetTempRange(pParse, nArg); sqlite3ExprCodeExprList(pParse, pWin->pOwner->x.pList, regArg, 0, 0); @@ -1717,6 +1737,7 @@ static void windowAggStep( if( pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ CollSeq *pColl; assert( nArg>0 ); + assert( ExprUseXList(pWin->pOwner) ); pColl = sqlite3ExprNNCollSeq(pParse, pWin->pOwner->x.pList->a[0].pExpr); sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); } @@ -1902,6 +1923,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ FuncDef *pFunc = pWin->pFunc; + assert( ExprUseXList(pWin->pOwner) ); if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){ diff --git a/test/aggnested.test b/test/aggnested.test index dcb1f95c9..35d5f1e3a 100644 --- a/test/aggnested.test +++ b/test/aggnested.test @@ -306,6 +306,48 @@ do_execsql_test 5.5 { b, b; } +#------------------------------------------------------------------------- +# dbsqlfuzz a779227f721a834df95f4f42d0c31550a1f8b8a2 +# +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a); + CREATE TABLE t2(b); + + INSERT INTO t1 VALUES('x'); + INSERT INTO t2 VALUES(1); +} + +do_execsql_test 6.1.1 { + SELECT ( + SELECT t2.b FROM (SELECT t2.b AS c FROM t1) GROUP BY 1 HAVING t2.b + ) + FROM t2 GROUP BY 'constant_string'; +} {1} +do_execsql_test 6.1.2 { + SELECT ( + SELECT c FROM (SELECT t2.b AS c FROM t1) GROUP BY c HAVING t2.b + ) + FROM t2 GROUP BY 'constant_string'; +} {1} + +do_execsql_test 6.2.0 { + UPDATE t2 SET b=0 +} +do_execsql_test 6.2.1 { + SELECT ( + SELECT t2.b FROM (SELECT t2.b AS c FROM t1) GROUP BY 1 HAVING t2.b + ) + FROM t2 GROUP BY 'constant_string'; +} {{}} +do_execsql_test 6.2.2 { + SELECT ( + SELECT c FROM (SELECT t2.b AS c FROM t1) GROUP BY c HAVING t2.b + ) + FROM t2 GROUP BY 'constant_string'; +} {{}} + + diff --git a/test/alter.test b/test/alter.test index 634e318e3..0088858a1 100644 --- a/test/alter.test +++ b/test/alter.test @@ -915,4 +915,24 @@ do_catchsql_test alter-18.1 { ALTER TABLE log RENAME COLUMN a TO x; } {1 {error in trigger tr1: no such table: main.logx}} +# 2021-10-13 dbsqlfuzz e89174cbfad2d904f06b5e24df0a22510b6a1c1e +reset_db +do_execsql_test alter-19.1 { + CREATE TABLE t1(x); + CREATE TABLE t2(c); + CREATE TRIGGER r1 AFTER INSERT ON t2 BEGIN + UPDATE t2 SET (c)=( + EXISTS(SELECT 1 WHERE (WITH cte1(a) AS (SELECT 1 FROM t1 WHERE (SELECT 1 WHERE (WITH cte2(b) AS (VALUES(1))SELECT b FROM cte2)))SELECT a FROM cte1)) + ); + END; + ALTER TABLE t2 RENAME TO t3; +} {} +do_execsql_test alter-19.2 { + SELECT name FROM sqlite_schema WHERE sql LIKE '%t2%'; +} {} +do_execsql_test alter-19.3 { + SELECT name FROM sqlite_schema WHERE sql LIKE '%t3%' ORDER BY name; +} {r1 t3} + + finish_test diff --git a/test/alter3.test b/test/alter3.test index 30bc1cbba..c6f26b0c5 100644 --- a/test/alter3.test +++ b/test/alter3.test @@ -13,8 +13,6 @@ # file format change that may be used in the future to implement # "ALTER TABLE ... ADD COLUMN". # -# $Id: alter3.test,v 1.11 2008/03/19 00:21:31 drh Exp $ -# set testdir [file dirname $argv0] @@ -395,4 +393,51 @@ do_test alter3-8.2 { } } [list $::sql] +# 2021-07-20: Add support for detecting CHECK and NOT NULL constraint +# violations in ALTER TABLE ADD COLUMN +# +reset_db +do_execsql_test alter3-9.1 { + CREATE TABLE t1(a,b); + INSERT INTO t1 VALUES(1, 2), ('null!',NULL), (3,4); +} {} +do_catchsql_test alter3-9.2 { + ALTER TABLE t1 ADD COLUMN c CHECK(a!=1); +} {1 {CHECK constraint failed}} +do_catchsql_test alter3-9.3 { + ALTER TABLE t1 ADD COLUMN c CHECK(a!=3); +} {1 {CHECK constraint failed}} +do_catchsql_test alter3-9.4 { + ALTER TABLE t1 ADD COLUMN c CHECK(a!=2); +} {0 {}} +do_catchsql_test alter3-9.5 { + ALTER TABLE t1 ADD COLUMN d AS (b+1) NOT NULL; +} {1 {NOT NULL constraint failed}} +do_catchsql_test alter3-9.6 { + ALTER TABLE t1 ADD COLUMN d AS (b+1) NOT NULL CHECK(a!=1); +} {1 {CHECK constraint failed}} +do_catchsql_test alter3-9.7 { + ALTER TABLE t1 ADD COLUMN d AS (b+1) NOT NULL CHECK(a!=3); +} {1 {NOT NULL constraint failed}} + +do_execsql_test alter3-9.10 { + CREATE TEMP TABLE t0(m,n); + INSERT INTO t0 VALUES(1, 2), ('null!',NULL), (3,4); + ATTACH ':memory:' AS aux1; + CREATE TABLE aux1.t2(x,y); + INSERT INTO t2 VALUES(1, 2), ('null!',NULL), (3,4); +} {} +do_catchsql_test alter3-9.11 { + ALTER TABLE t0 ADD COLUMN xtra1 AS (n+1) NOT NULL CHECK(m!=1); +} {1 {CHECK constraint failed}} +do_catchsql_test alter3-9.12 { + ALTER TABLE t0 ADD COLUMN xtra1 AS (n+1) NOT NULL CHECK(m!=3); +} {1 {NOT NULL constraint failed}} +do_catchsql_test alter3-9.13 { + ALTER TABLE t2 ADD COLUMN xtra1 AS (y+1) NOT NULL CHECK(x!=1); +} {1 {CHECK constraint failed}} +do_catchsql_test alter3-9.14 { + ALTER TABLE t2 ADD COLUMN xtra1 AS (y+1) NOT NULL CHECK(x!=3); +} {1 {NOT NULL constraint failed}} + finish_test diff --git a/test/altercorrupt.test b/test/altercorrupt.test index 5c50fa4a4..f24cb309a 100644 --- a/test/altercorrupt.test +++ b/test/altercorrupt.test @@ -14,6 +14,12 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix altercorrupt +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + database_may_be_corrupt #-------------------------------------------------------------------------- diff --git a/test/alterfault.test b/test/alterfault.test new file mode 100644 index 000000000..b6b42973e --- /dev/null +++ b/test/alterfault.test @@ -0,0 +1,41 @@ +# 2021 November 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 regression tests for SQLite library. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix alterfault + +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a); +} +faultsim_save_and_close + +do_faultsim_test 1.1 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + ALTER TABLE t1 ADD COLUMN b CHECK (a!=1) + } +} -test { + faultsim_test_result {0 {}} +} + + + +finish_test diff --git a/test/altermalloc3.test b/test/altermalloc3.test index 5e4e75149..4c10f48fe 100644 --- a/test/altermalloc3.test +++ b/test/altermalloc3.test @@ -43,5 +43,36 @@ do_faultsim_test 1 -prep { faultsim_test_result {0 {}} } +#------------------------------------------------------------------------- +# dbsqlfuzz e3dd84cda3848016a6a6024c7249d09bc2ef2615 +# +reset_db +do_execsql_test 2.0 { + CREATE TABLE t2(k,v); + CREATE TRIGGER r2 AFTER INSERT ON t2 BEGIN + UPDATE t2 SET (k,v)= ( + (WITH cte1(a) AS ( SELECT 1 FROM ( SELECT * FROM t2 ) ) + SELECT a FROM cte1 + ), 1); + END; +} + +faultsim_save_and_close +faultsim_restore_and_reopen + +do_execsql_test 2.1 { + ALTER TABLE t2 RENAME TO t2x; +} + +do_faultsim_test 2.2 -prep { + faultsim_restore_and_reopen + db eval { SELECT * FROM sqlite_master } +} -body { + execsql { + ALTER TABLE t2 RENAME TO t2x; + } +} -test { + faultsim_test_result {0 {}} +} finish_test diff --git a/test/alterqf.test b/test/alterqf.test index ce00f3b59..6a8964186 100644 --- a/test/alterqf.test +++ b/test/alterqf.test @@ -17,6 +17,13 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix alterqf +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + + sqlite3_test_control SQLITE_TESTCTRL_INTERNAL_FUNCTIONS db do_execsql_test 1.0 { diff --git a/test/altertab.test b/test/altertab.test index 079a382d8..6d8347ec1 100644 --- a/test/altertab.test +++ b/test/altertab.test @@ -540,6 +540,7 @@ ifcapable fts3 { do_execsql_test 16.0 { CREATE VIRTUAL TABLE y1 USING fts3; + VACUUM; } do_catchsql_test 16.10 { @@ -968,4 +969,16 @@ do_execsql_test 31.3 { ALTER TABLE t1x RENAME q TO x; } +# 2021-07-02 OSSFuzz https://oss-fuzz.com/testcase-detail/5517690440646656 +# Bad assert() statement +# +reset_db +do_catchsql_test 32.0 { + CREATE TABLE t1(x); + CREATE TRIGGER r1 BEFORE INSERT ON t1 BEGIN + UPDATE t1 SET x=x FROM (SELECT*); + END; + ALTER TABLE t1 RENAME TO x; +} {1 {error in trigger r1: no tables specified}} + finish_test diff --git a/test/altertab3.test b/test/altertab3.test index 1823b21ed..2b9aac3ef 100644 --- a/test/altertab3.test +++ b/test/altertab3.test @@ -648,4 +648,42 @@ do_catchsql_test 26.6 { } {1 {error in trigger xx: ambiguous column name: xx}} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 27.1 { + CREATE TABLE t1(a, b AS ((WITH w1 (xyz) AS ( SELECT t1.b FROM t1 ) SELECT 123) IN ()), c); +} + +do_execsql_test 27.2 { + ALTER TABLE t1 DROP COLUMN c; + SELECT sql FROM sqlite_schema WHERE name = 't1'; +} { + {CREATE TABLE t1(a, b AS ((WITH w1 (xyz) AS ( SELECT t1.b FROM t1 ) SELECT 123) IN ()))} +} + +do_execsql_test 27.3 { + CREATE TABLE t0(c0 , c1 AS (CASE TRUE NOT IN () WHEN NULL THEN CASE + 0xa ISNULL WHEN NOT + 0x9 THEN t0.c1 ELSE CURRENT_TIME LIKE CAST (t0.c1 REGEXP '-([1-9]\d*.\d*|0\.\d*[1-9]\d*)'ESCAPE (c1) COLLATE BINARY BETWEEN c1 AND c1 NOT IN (WITH t4 (c0) AS (WITH t3 (c0) AS NOT MATERIALIZED (WITH RECURSIVE t2 (c0) AS (WITH RECURSIVE t1 AS (VALUES (x'717171ff71717171' ) ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c0 GROUP BY 0x9 ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c1 ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c0 GROUP BY typeof(0x9 ) ) SELECT DISTINCT t0.c0 FROM t0 NOT INDEXED WHERE t0.c0 =t0.c0 GROUP BY typeof(typeof(0x9 ) ) ) IN t0 BETWEEN typeof(typeof(typeof(hex(*) FILTER (WHERE + x'5ccd1e68' ) ) ) ) AND 1 >0xa AS BLOB (+4.4E4 , -0xe ) ) END <> c1 IN () END ) VIRTUAL , c35 PRIMARY KEY , c60 , c64 NUMERIC (-6.8 , -0xE ) ) WITHOUT ROWID ; +} {} + +do_execsql_test 27.4 { + ALTER TABLE t0 DROP COLUMN c60; +} {} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 28.1 { + CREATE TABLE t1(a,b,c,d); + CREATE TRIGGER AFTER INSERT ON t1 BEGIN + UPDATE t1 SET (c,d)=(a,b); + END; + ALTER TABLE t1 RENAME TO t2; +} + +do_execsql_test 28.2 { + SELECT sql FROM sqlite_schema WHERE type='trigger' +} {{CREATE TRIGGER AFTER INSERT ON "t2" BEGIN + UPDATE "t2" SET (c,d)=(a,b); + END}} + finish_test diff --git a/test/analyze4.test b/test/analyze4.test index e3b0d23f6..d4f1921e4 100644 --- a/test/analyze4.test +++ b/test/analyze4.test @@ -20,6 +20,12 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + do_test analyze4-1.0 { db eval { CREATE TABLE t1(a,b); diff --git a/test/atof1.test b/test/atof1.test index 0505fb226..a68266c9b 100644 --- a/test/atof1.test +++ b/test/atof1.test @@ -19,6 +19,11 @@ if {$::longdouble_size<=8} { finish_test return } +if {![info exists tcl_platform(machine)] + || $::tcl_platform(machine)!="x86_64"} { + finish_test + return +} expr srand(1) for {set i 1} {$i<20000} {incr i} { diff --git a/test/auth3.test b/test/auth3.test index 4377bcdc0..abc973433 100644 --- a/test/auth3.test +++ b/test/auth3.test @@ -115,18 +115,20 @@ do_test auth3-2.2 { # an authorizer failure during an ALTER TABLE. The solution (I think) is # to disable the authorizer during schema parsing. # -proc auth {code args} { - if {$code=="SQLITE_READ" && [regexp {DoNotRead} $args]} { - return SQLITE_DENY +ifcapable altertable { + proc auth {code args} { + if {$code=="SQLITE_READ" && [regexp {DoNotRead} $args]} { + return SQLITE_DENY + } + return SQLITE_OK } - return SQLITE_OK + do_execsql_test auth3-3.0 { + CREATE TEMPORARY TABLE TempTable ( + key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, + value TEXT NOT NULL ON CONFLICT FAIL); + ALTER TABLE TempTable RENAME TO DoNotRead; + SELECT name FROM temp.sqlite_master; + } {DoNotRead sqlite_autoindex_DoNotRead_1} } -do_execsql_test auth3-3.0 { - CREATE TEMPORARY TABLE TempTable ( - key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, - value TEXT NOT NULL ON CONFLICT FAIL); - ALTER TABLE TempTable RENAME TO DoNotRead; - SELECT name FROM temp.sqlite_master; -} {DoNotRead sqlite_autoindex_DoNotRead_1} finish_test diff --git a/test/autoindex5.test b/test/autoindex5.test index 36695d7f7..aa8dec27d 100644 --- a/test/autoindex5.test +++ b/test/autoindex5.test @@ -123,6 +123,10 @@ do_execsql_test 2.1 { SELECT sum(z) FROM vvv WHERE x='aaa' ) FROM one; } {8.0} + +# At one point the following was returning "no such column: rowid". This +# was incorrect - "rowid" matches against the rowid of table t1 in this +# query. do_catchsql_test 2.2 { DROP TABLE t1; CREATE TABLE t1(aaa); @@ -136,7 +140,7 @@ do_catchsql_test 2.2 { ) WHERE bbb = 1 ) ); -} {1 {no such column: rowid}} +} {0 9} # Ticket https://www.sqlite.org/src/info/787fa716be3a7f65 # Segfault due to multiple uses of the same subquery where the diff --git a/test/autovacuum.test b/test/autovacuum.test index 431c4b8a2..245ea8b51 100644 --- a/test/autovacuum.test +++ b/test/autovacuum.test @@ -9,9 +9,8 @@ # #*********************************************************************** # This file implements regression tests for SQLite library. The -# focus of this file is testing the SELECT statement. +# focus of this file is testing the autovacuum feature. # -# $Id: autovacuum.test,v 1.29 2009/04/06 17:50:03 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/autovacuum2.test b/test/autovacuum2.test new file mode 100644 index 000000000..a3c409839 --- /dev/null +++ b/test/autovacuum2.test @@ -0,0 +1,87 @@ +# 2021-10-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. The +# focus of this file is testing the sqlite3_autovacuum_pages() interface +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If this build of the library does not support auto-vacuum, omit this +# whole file. +ifcapable {!autovacuum || !pragma} { + finish_test + return +} + +# Demonstrate basic sqlite3_autovacuum_pages functionality +# +do_execsql_test autovacuum2-1.0 { + PRAGMA page_size=1024; + PRAGMA auto_vacuum=FULL; + CREATE TABLE t1(x); + VACUUM; + INSERT INTO t1(x) VALUES(zeroblob(10000)); + PRAGMA page_count; +} {12} +proc autovac_page_callback {schema filesize freesize pagesize} { + global autovac_callback_data + lappend autovac_callback_data $schema $filesize $freesize $pagesize + return [expr {$freesize/2}] +} +sqlite3_autovacuum_pages db autovac_page_callback +set autovac_callback_data {} +do_execsql_test autovacuum2-1.1 { + BEGIN; + DELETE FROM t1; + PRAGMA freelist_count; + PRAGMA page_count; +} {9 12} +do_execsql_test autovacuum2-1.2 { + COMMIT; +} {} +do_test autovacuum2-1.3 { + set autovac_callback_data +} {main 12 9 1024} +do_execsql_test autovacuum2-1.4 { + PRAGMA freelist_count; + PRAGMA page_count; +} {5 8} +do_execsql_test autovacuum2-1.5 { + PRAGMA integrity_check; +} {ok} + +# Disable the autovacuum-pages callback. Then do any transaction. +# The database should shrink to minimal size +# +sqlite3_autovacuum_pages db +do_execsql_test autovacuum2-1.10 { + CREATE TABLE t2(x); + PRAGMA freelist_count; +} {0} + +# Rig the autovacuum-pages callback to always return zero. No +# autovacuum will happen. +# +proc autovac_page_callback_off {schema filesize freesize pagesize} { + return 0 +} +sqlite3_autovacuum_pages db autovac_page_callback_off +do_execsql_test autovacuum2-1.20 { + BEGIN; + INSERT INTO t1(x) VALUES(zeroblob(10000)); + DELETE FROM t1; + PRAGMA freelist_count; + COMMIT; + PRAGMA freelist_count; +} {9 9} + +finish_test diff --git a/test/bestindex1.test b/test/bestindex1.test index 0d6946994..e93d7349d 100644 --- a/test/bestindex1.test +++ b/test/bestindex1.test @@ -323,5 +323,8 @@ do_test 4.1 { ] ] +do_catchsql_test 5.0 { + SELECT * FROM tcl('abc'); +} {1 {wrong number of arguments}} finish_test diff --git a/test/capi2.test b/test/capi2.test index 0680cf530..de47ab3d4 100644 --- a/test/capi2.test +++ b/test/capi2.test @@ -64,13 +64,13 @@ do_test capi2-1.4 { } {t1 1} do_test capi2-1.5 { get_column_names $VM -} {name rowid text INTEGER} +} {name rowid TEXT INTEGER} do_test capi2-1.6 { sqlite3_step $VM } {SQLITE_DONE} do_test capi2-1.7 { list [sqlite3_column_count $VM] [get_row_values $VM] [get_column_names $VM] -} {2 {} {name rowid text INTEGER}} +} {2 {} {name rowid TEXT INTEGER}} # This used to be SQLITE_MISUSE. But now we automatically reset prepared # statements. @@ -91,7 +91,7 @@ ifcapable autoreset { do_test capi2-1.9 { sqlite3_reset $VM list [sqlite3_column_count $VM] [get_row_values $VM] [get_column_names $VM] -} {2 {} {name rowid text INTEGER}} +} {2 {} {name rowid TEXT INTEGER}} do_test capi2-1.10 { sqlite3_data_count $VM } {0} @@ -120,13 +120,13 @@ do_test capi2-2.2 { lappend r [sqlite3_column_count $VM] \ [get_row_values $VM] \ [get_column_names $VM] -} {SQLITE_ROW 2 {t1 1} {name rowid text INTEGER}} +} {SQLITE_ROW 2 {t1 1} {name rowid TEXT INTEGER}} do_test capi2-2.3 { set r [sqlite3_step $VM] lappend r [sqlite3_column_count $VM] \ [get_row_values $VM] \ [get_column_names $VM] -} {SQLITE_DONE 2 {} {name rowid text INTEGER}} +} {SQLITE_DONE 2 {} {name rowid TEXT INTEGER}} do_test capi2-2.4 { sqlite3_finalize $VM } {SQLITE_OK} @@ -141,7 +141,7 @@ do_test capi2-2.6 { lappend r [sqlite3_column_count $VM] \ [get_row_values $VM] \ [get_column_names $VM] -} {SQLITE_DONE 2 {} {name rowid text INTEGER}} +} {SQLITE_DONE 2 {} {name rowid TEXT INTEGER}} do_test capi2-2.7 { sqlite3_finalize $VM } {SQLITE_OK} diff --git a/test/carray01.test b/test/carray01.test index 6db4f5eab..d6243eb27 100644 --- a/test/carray01.test +++ b/test/carray01.test @@ -62,10 +62,34 @@ do_test 120 { sqlite3_carray_bind -int64 $STMT 3 1 2 3 4 5 6 7 run_stmt $STMT 0 } {1} +do_test 121 { + sqlite3_carray_bind -int64 -transient $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT 0 +} {1} +do_test 122 { + sqlite3_carray_bind -int64 -static $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT 0 +} {1} +do_test 123 { + sqlite3_carray_bind -int32 -transient $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT 0 +} {1} +do_test 124 { + sqlite3_carray_bind -int32 -static $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT 0 +} {1} +do_test 125 { + sqlite3_carray_bind -int32 $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT 0 +} {1} do_test 130 { sqlite3_carray_bind -int64 $STMT 3 1 2 3 4 6 7 run_stmt $STMT 0 } {0} +do_test 131 { + sqlite3_carray_bind -int64 -transient $STMT 3 1 2 3 4 6 7 + run_stmt $STMT 0 +} {0} do_test 131 { sqlite3_carray_bind -int64 -static $STMT 3 1 2 3 4 6 7 run_stmt $STMT 0 @@ -74,6 +98,14 @@ do_test 140 { sqlite3_carray_bind -double $STMT 3 1 2 3 4 5 6 7 run_stmt $STMT 0 } {1} +do_test 141 { + sqlite3_carray_bind -double -transient $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT 0 +} {1} +do_test 142 { + sqlite3_carray_bind -double -static $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT 0 +} {1} do_test 150 { sqlite3_carray_bind -double $STMT 3 1 2 3 4 6 7 run_stmt $STMT 0 diff --git a/test/changes.test b/test/changes.test new file mode 100644 index 000000000..21db075f9 --- /dev/null +++ b/test/changes.test @@ -0,0 +1,90 @@ +# 2021 June 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. +# +#*********************************************************************** +# +# Tests for the sqlite3_changes() and sqlite3_total_changes() APIs. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix changes + +# To test that the change-counters do not suffer from 32-bit signed integer +# rollover, add the following line to the array of test cases below. The +# test will take will over an hour to run. +# +# 7 (1<<31)+10 "" +# + +foreach {tn nRow wor} { + 1 50 "" + 2 50 "WITHOUT ROWID" + + 3 5000 "" + 4 5000 "WITHOUT ROWID" + + 5 50000 "" + 6 50000 "WITHOUT ROWID" +} { + reset_db + set nBig [expr $nRow] + + do_execsql_test 1.$tn.0 " + PRAGMA journal_mode = off; + CREATE TABLE t1(x INTEGER PRIMARY KEY) $wor; + " {off} + + do_execsql_test 1.$tn.1 { + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i < $nBig + ) + INSERT INTO t1 SELECT i FROM s; + } + + do_test 1.$tn.2 { + db changes + } [expr $nBig] + + do_test 1.$tn.3 { + db total_changes + } [expr $nBig] + + do_execsql_test 1.$tn.4 { + INSERT INTO t1 VALUES(-1) + } + + do_test 1.$tn.5 { + db changes + } [expr 1] + + do_test 1.$tn.6 { + db total_changes + } [expr {$nBig+1}] + + do_execsql_test 1.$tn.7a { + SELECT count(*) FROM t1 + } [expr {$nBig+1}] + + do_execsql_test 1.$tn.7 { + DELETE FROM t1 + } + + do_test 1.$tn.8 { + db changes + } [expr {$nBig+1}] + + do_test 1.$tn.9 { + db total_changes + } [expr {2*($nBig+1)}] +} + +finish_test + + diff --git a/test/columncount.test b/test/columncount.test index 669a35a72..d9956b40f 100644 --- a/test/columncount.test +++ b/test/columncount.test @@ -16,6 +16,12 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix columncount +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + proc do_ccsql_test {tn sql res} { uplevel [list do_test $tn [subst -nocommands { diff --git a/test/corruptL.test b/test/corruptL.test index 5ade7010d..b7ff45d2e 100644 --- a/test/corruptL.test +++ b/test/corruptL.test @@ -1179,9 +1179,12 @@ do_catchsql_test 14.1 { PRAGMA integrity_check; } {1 {database disk image is malformed}} -do_catchsql_test 14.2 { - ALTER TABLE t1 RENAME TO alkjalkjdfiiiwuer987lkjwer82mx97sf98788s9789s; -} {1 {database disk image is malformed}} +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable altertable { + do_catchsql_test 14.2 { + ALTER TABLE t1 RENAME TO alkjalkjdfiiiwuer987lkjwer82mx97sf98788s9789s; + } {1 {database disk image is malformed}} +} extra_schema_checks 1 #------------------------------------------------------------------------- diff --git a/test/corruptN.test b/test/corruptN.test index 8600e438d..468578822 100644 --- a/test/corruptN.test +++ b/test/corruptN.test @@ -184,7 +184,7 @@ if {![info exists ::G(perm:presql)]} { do_catchsql_test 4.2 { PRAGMA writable_schema = 1; REPLACE INTO x1 VALUES(5, 2, 3); - } {1 {database disk image is malformed}} + } {0 {}} } @@ -224,5 +224,53 @@ ifcapable json1&&vtab { } }; # ifcapable json1&&vtab +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 6.0 { + PRAGMA auto_vacuum = 0; + PRAGMA page_size=1024; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1(b) VALUES(zeroblob(300)),(zeroblob(300)),(zeroblob(300)),(zeroblob(300)); + CREATE TABLE t2(a); + CREATE TRIGGER t1tr BEFORE UPDATE ON t1 BEGIN DELETE FROM t2; END; + PRAGMA writable_schema=ON; + UPDATE sqlite_schema SET rootpage=3 WHERE rowid=2; + PRAGMA writable_schema=RESET; + INSERT INTO t2 VALUES('active'),('boomer'),('atom'),('atomic'), + ('alpha channel backup abandon test aback boomer atom alpha active'); +} +do_catchsql_test 6.1 { + UPDATE t1 SET b=zeroblob(299); +} {1 {database disk image is malformed}} + +reset_db +do_execsql_test 6.2 { + -- Make "t1" a large table. Large enough that the children of the root + -- node are interior nodes. + PRAGMA page_size = 1024; + PRAGMA auto_vacuum = 0; + CREATE TABLE t1(x); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500 + ) + INSERT INTO t1 SELECT zeroblob(300) FROM s; + + CREATE TABLE t2(y); + CREATE TRIGGER tr BEFORE UPDATE ON t1 BEGIN + DELETE FROM t2; + END; + + -- Set the root of table t2 to 137 - the leftmost child of the root of t1. + PRAGMA writable_schema = ON; + UPDATE sqlite_schema SET rootpage = 137 WHERE name='t2'; + PRAGMA writable_schema = RESET; +} + +do_catchsql_test 6.3 { + -- Run an UPDATE on t1 that will hit a child of page 136. Have the trigger + -- clear page 136 and its children. Assert fails. + UPDATE t1 SET x='hello world' WHERE rowid=1; +} {1 {database disk image is malformed}} finish_test diff --git a/test/count.test b/test/count.test index 9769b765f..fdcb21e81 100644 --- a/test/count.test +++ b/test/count.test @@ -233,5 +233,16 @@ do_eqp_test count-7.4 { `--SCAN t1 } +do_execsql_test count-8.0 { + CREATE TABLE t7(a INT,b TEXT,c BLOB,d REAL); + CREATE TABLE t8(a INT,b TEXT,c BLOB,d REAL); + CREATE INDEX t8a ON t8(a); +} +do_catchsql_test count-8.1 { + SELECT * FROM t8 WHERE (a, b) IN ( + SELECT count(t8.b), count(*) FROM t7 AS ra0 ORDER BY count(*) + ) AND t8.b=0; +} {1 {misuse of aggregate: count()}} + finish_test diff --git a/test/delete.test b/test/delete.test index c15ac53c6..a448e52dd 100644 --- a/test/delete.test +++ b/test/delete.test @@ -100,7 +100,7 @@ do_test delete-5.2.1 { } {200} do_test delete-5.2.2 { execsql {DELETE FROM table1} -} {202} +} {200} do_test delete-5.2.3 { execsql {BEGIN TRANSACTION} for {set i 1} {$i<=200} {incr i} { diff --git a/test/e_blobbytes.test b/test/e_blobbytes.test index d38f56bf3..c24318c56 100644 --- a/test/e_blobbytes.test +++ b/test/e_blobbytes.test @@ -14,6 +14,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix e_blobbytes +ifcapable !incrblob { + finish_test + return +} + do_execsql_test 1.0 { CREATE TABLE q1(r INTEGER PRIMARY KEY, s TEXT); WITH d(a, b) AS ( diff --git a/test/e_blobclose.test b/test/e_blobclose.test index 268319785..40291cf03 100644 --- a/test/e_blobclose.test +++ b/test/e_blobclose.test @@ -14,6 +14,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix e_blobclose +ifcapable !incrblob { + finish_test + return +} + set dots [string repeat . 40] do_execsql_test 1.0 { CREATE TABLE x1(a INTEGER PRIMARY KEY, b DOTS); diff --git a/test/e_blobopen.test b/test/e_blobopen.test index a6168042e..41fd13c67 100644 --- a/test/e_blobopen.test +++ b/test/e_blobopen.test @@ -14,6 +14,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix e_blobopen +ifcapable !incrblob { + finish_test + return +} + forcedelete test.db2 do_execsql_test 1.0 { diff --git a/test/e_blobwrite.test b/test/e_blobwrite.test index afcfc281a..8d8588e6a 100644 --- a/test/e_blobwrite.test +++ b/test/e_blobwrite.test @@ -14,6 +14,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix e_blobwrite +ifcapable !incrblob { + finish_test + return +} + #-------------------------------------------------------------------------- # EVIDENCE-OF: R-62898-22698 This function is used to write data into an # open BLOB handle from a caller-supplied buffer. N bytes of data are diff --git a/test/e_changes.test b/test/e_changes.test index a77e22a2e..2eb77d313 100644 --- a/test/e_changes.test +++ b/test/e_changes.test @@ -25,10 +25,10 @@ proc do_changes_test {tn sql res} { #-------------------------------------------------------------------------- -# EVIDENCE-OF: R-15996-49369 This function returns the number of rows -# modified, inserted or deleted by the most recently completed INSERT, -# UPDATE or DELETE statement on the database connection specified by the -# only parameter. +# EVIDENCE-OF: R-58361-29089 The changes() function returns the number +# of database rows that were changed or inserted or deleted by the most +# recently completed INSERT, DELETE, or UPDATE statement, exclusive of +# statements in lower-level triggers. # do_execsql_test 1.0 { CREATE TABLE t1(a, b); @@ -108,7 +108,7 @@ foreach {tn schema} { #-------------------------------------------------------------------------- -# EVIDENCE-OF: R-44877-05564 Executing any other type of SQL statement +# X-EVIDENCE-OF: R-44877-05564 Executing any other type of SQL statement # does not modify the value returned by this function. # reset_db @@ -123,7 +123,9 @@ do_changes_test 2.2 { do_changes_test 2.3 { SELECT count(x) FROM t1 } {47 47} do_changes_test 2.4 { DROP TABLE t1 } 47 do_changes_test 2.5 { CREATE TABLE t1(x) } 47 -do_changes_test 2.6 { ALTER TABLE t1 ADD COLUMN b } 47 +ifcapable altertable { + do_changes_test 2.6 { ALTER TABLE t1 ADD COLUMN b } 47 +} #-------------------------------------------------------------------------- diff --git a/test/e_createtable.test b/test/e_createtable.test index c2cd1bc2c..fa43d94b5 100644 --- a/test/e_createtable.test +++ b/test/e_createtable.test @@ -1272,9 +1272,10 @@ do_createtable_tests 4.4 { 14 "INSERT INTO t2 VALUES(NULL, NULL)" {} } -# EVIDENCE-OF: R-35113-43214 Unless the column is an INTEGER PRIMARY KEY -# or the table is a WITHOUT ROWID table or the column is declared NOT -# NULL, SQLite allows NULL values in a PRIMARY KEY column. +# EVIDENCE-OF: R-40010-16873 Unless the column is an INTEGER PRIMARY KEY +# or the table is a WITHOUT ROWID table or a STRICT table or the column +# is declared NOT NULL, 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 behavior. Attempting @@ -1304,6 +1305,14 @@ do_catchsql_test 4.5.5 { CREATE TABLE t5(s, u INT PRIMARY KEY NOT NULL, v); INSERT INTO t5 VALUES(1, NULL, 2); } {1 {NOT NULL constraint failed: t5.u}} +do_catchsql_test 4.5.6 { + CREATE TABLE t6(s INT, u INT PRIMARY KEY, v INT) STRICT; + INSERT INTO t6 VALUES(1, NULL, 2); +} {1 {NOT NULL constraint failed: t6.u}} +do_catchsql_test 4.5.7 { + CREATE TABLE t7(s INT, u INT PRIMARY KEY NOT NULL, v INT) STRICT; + INSERT INTO t7 VALUES(1, NULL, 2); +} {1 {NOT NULL constraint failed: t7.u}} # 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 diff --git a/test/e_expr.test b/test/e_expr.test index 242c503dc..93ca0268c 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -84,12 +84,12 @@ db func regexp -argcount 2 regexfunc # in the documentation exist and that the relative precedences of the # operators are also as the documentation suggests. # -# EVIDENCE-OF: R-15514-65163 SQLite understands the following binary +# X-EVIDENCE-OF: R-15514-65163 SQLite understands the following binary # operators, in order from highest to lowest precedence: || * / % + - # << >> & | < <= > >= = == != <> IS IS # NOT IN LIKE GLOB MATCH REGEXP AND OR # -# EVIDENCE-OF: R-38759-38789 Operators IS and IS NOT have the same +# X-EVIDENCE-OF: R-38759-38789 Operators IS and IS NOT have the same # precedence as =. # @@ -180,7 +180,7 @@ do_execsql_test e_expr-1.6 { # Check that the four unary prefix operators mentioned in the # documentation exist. # -# EVIDENCE-OF: R-13958-53419 Supported unary prefix operators are these: +# X-EVIDENCE-OF: R-13958-53419 Supported unary prefix operators are these: # - + ~ NOT # do_execsql_test e_expr-2.1 { SELECT - 10 } {-10} @@ -368,7 +368,7 @@ db collate reverse reverse_collate # EVIDENCE-OF: R-59577-33471 The COLLATE operator is a unary postfix # operator that assigns a collating sequence to an expression. # -# EVIDENCE-OF: R-36231-30731 The COLLATE operator has a higher +# X-EVIDENCE-OF: R-36231-30731 The COLLATE operator has a higher # precedence (binds more tightly) than any binary operator and any unary # prefix operator except "~". # @@ -860,7 +860,7 @@ foreach {tn x expr res nEval} { } [list $nEval $res] } -# EVIDENCE-OF: R-05155-34454 The precedence of the BETWEEN operator is +# X-EVIDENCE-OF: R-05155-34454 The precedence of the BETWEEN operator is # the same as the precedence as operators == and != and LIKE and groups # left to right. # diff --git a/test/e_fkey.test b/test/e_fkey.test index e5eb0d49c..ab3c29033 100644 --- a/test/e_fkey.test +++ b/test/e_fkey.test @@ -2513,9 +2513,11 @@ proc test_efkey_6 {tn zAlter isError} { } -test_efkey_6 1 "ALTER TABLE tbl ADD COLUMN c REFERENCES xx" 0 -test_efkey_6 2 "ALTER TABLE tbl ADD COLUMN c DEFAULT NULL REFERENCES xx" 0 -test_efkey_6 3 "ALTER TABLE tbl ADD COLUMN c DEFAULT 0 REFERENCES xx" 1 +ifcapable altertable { + test_efkey_6 1 "ALTER TABLE tbl ADD COLUMN c REFERENCES xx" 0 + test_efkey_6 2 "ALTER TABLE tbl ADD COLUMN c DEFAULT NULL REFERENCES xx" 0 + test_efkey_6 3 "ALTER TABLE tbl ADD COLUMN c DEFAULT 0 REFERENCES xx" 1 +} #------------------------------------------------------------------------- # Test that ALTER TABLE adjusts REFERENCES clauses when the parent table @@ -2532,6 +2534,7 @@ test_efkey_6 3 "ALTER TABLE tbl ADD COLUMN c DEFAULT 0 REFERENCES xx" 1 # statement or statements stored in the sqlite_schema table are modified # to reflect the new parent table name. # +ifcapable altertable { do_test e_fkey-56.1 { drop_all_tables execsql { @@ -2569,6 +2572,7 @@ do_test e_fkey-56.4 { {CREATE TABLE c2(e, f, FOREIGN KEY(f) REFERENCES "p" ON UPDATE CASCADE)} \ {CREATE TABLE c3(e, 'f col 2', FOREIGN KEY('f col 2') REFERENCES "p" ON UPDATE CASCADE)} \ ] +} #------------------------------------------------------------------------- # Check that a DROP TABLE does an implicit DELETE FROM. Which does not @@ -2769,6 +2773,7 @@ do_test e_fkey-60.6 { # EVIDENCE-OF: R-54142-41346 The properties of the DROP TABLE and ALTER # TABLE commands described above only apply if foreign keys are enabled. # +ifcapable altertable { do_test e_fkey-61.1.1 { drop_all_tables execsql { CREATE TABLE t1(a, b) ; INSERT INTO t1 VALUES(1, 2) } @@ -2830,6 +2835,7 @@ do_test e_fkey-61.3.2 { do_test e_fkey-61.3.3 { execsql { PRAGMA foreign_keys = ON } } {} +} ########################################################################### ### SECTION 6: Limits and Unsupported Features diff --git a/test/e_totalchanges.test b/test/e_totalchanges.test index ee163c914..bb5cfba8a 100644 --- a/test/e_totalchanges.test +++ b/test/e_totalchanges.test @@ -32,10 +32,9 @@ do_execsql_test 1.0 { #-------------------------------------------------------------------------- -# EVIDENCE-OF: R-65438-26258 This function returns the total number of -# rows inserted, modified or deleted by all INSERT, UPDATE or DELETE -# statements completed since the database connection was opened, -# including those executed as part of trigger programs. +# EVIDENCE-OF: R-38914-26427 The total_changes() function returns the +# number of row changes caused by INSERT, UPDATE or DELETE statements +# since the current database connection was opened. # # 1.1.*: different types of I/U/D statements, # 1.2.*: trigger programs. @@ -95,24 +94,26 @@ do_tc_test 1.2.2 { #-------------------------------------------------------------------------- # EVIDENCE-OF: R-61766-15253 Executing any other type of SQL statement # does not affect the value returned by sqlite3_total_changes(). -do_tc_test 2.1 { - INSERT INTO t1 VALUES(1, 2), (3, 4); - INSERT INTO t2 VALUES(1, 2), (3, 4); -} {15} -do_tc_test 2.2 { - SELECT count(*) FROM t1; -} {2 15} -do_tc_test 2.3 { - CREATE TABLE t4(a, b); - ALTER TABLE t4 ADD COLUMN c; - CREATE INDEX i4 ON t4(c); - ALTER TABLE t4 RENAME TO t5; - ANALYZE; - BEGIN; - DROP TABLE t2; - ROLLBACK; - VACUUM; -} {15} +ifcapable altertable { + do_tc_test 2.1 { + INSERT INTO t1 VALUES(1, 2), (3, 4); + INSERT INTO t2 VALUES(1, 2), (3, 4); + } {15} + do_tc_test 2.2 { + SELECT count(*) FROM t1; + } {2 15} + do_tc_test 2.3 { + CREATE TABLE t4(a, b); + ALTER TABLE t4 ADD COLUMN c; + CREATE INDEX i4 ON t4(c); + ALTER TABLE t4 RENAME TO t5; + ANALYZE; + BEGIN; + DROP TABLE t2; + ROLLBACK; + VACUUM; + } {15} +} #-------------------------------------------------------------------------- diff --git a/test/e_uri.test b/test/e_uri.test index dbcc6f3e0..92a3df78d 100644 --- a/test/e_uri.test +++ b/test/e_uri.test @@ -25,6 +25,7 @@ proc parse_uri {uri} { set ::uri_open [list] set DB [sqlite3_open_v2 $uri { SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_WAL + SQLITE_OPEN_EXRESCODE } tvfs] set fileName [sqlite3_db_filename $DB main] sqlite3_close $DB diff --git a/test/fkey1.test b/test/fkey1.test index fa8733588..db93be501 100644 --- a/test/fkey1.test +++ b/test/fkey1.test @@ -222,5 +222,54 @@ do_execsql_test 6.2 { INSERT INTO c1 VALUES(1); } {} +# 2021-07-03 https://sqlite.org/forum/forumpost/a6b0c05277 +# 2021-07-07 https://sqlite.org/forum/forumpost/79c9e4797d +# Failure to allocate enough registers in the VDBE for a +# PRAGMA foreign_key_check when the foreign key has more +# columns than the table. +# +reset_db +do_execsql_test 7.1 { + PRAGMA foreign_keys=OFF; + CREATE TABLE t1(a,b,c,FOREIGN KEY(a,a,a,a,a,a,a,a,a,a,a,a,a,a) REFERENCES t0); + INSERT INTO t1 VALUES(1,2,3); + PRAGMA foreign_key_check; +} {t1 1 t0 0} +do_execsql_test 7.2 { + DROP TABLE t1; + CREATE TABLE t1(a,b,c AS(1),d, FOREIGN KEY(c,d,b,a,b,d,b,c) REFERENCES t0); + PRAGMA foreign_key_check; +} {} + +# 2021-12-31 forum https://sqlite.org/forum/forumpost/24bd1fef7e9323ef +# Memory leak caused by sqlite3NestedParse() running on a corrupt system +# table. Discovered by Jingzhou Fu. +# +reset_db +do_execsql_test 8.1 { + PRAGMA writable_schema=ON; + PRAGMA foreign_keys = ON; + CREATE TABLE sqlite_stat1 (tbl INTEGER PRIMARY KEY DESC, idx UNIQUE DEFAULT NULL) WITHOUT ROWID; + PRAGMA writable_schema=OFF; + CREATE TABLE sqlsim4(stat PRIMARY KEY);; + CREATE TABLE t1(sqlsim7 REFERENCES sqlite_stat1 ON DELETE CASCADE); + DROP table "sqlsim4"; +} {} +# 2022-01-01 dbsqlfuzz 1c57440219f6f0aedf5e8f72a8ddd75f15aea381 +# Follow-up case to the above. Assertion is not true if the schema +# is corrupt. +reset_db +database_may_be_corrupt +do_execsql_test 8.2 { + CREATE TABLE t1(a REFERENCES sqlite_stat1 ON DELETE CASCADE); + CREATE TABLE t2(a TEXT PRIMARY KEY); + PRAGMA writable_schema=ON; + CREATE TABLE sqlite_stat1(tbl INTEGER PRIMARY KEY DESC, idx UNIQUE DEFAULT NULL) WITHOUT ROWID; + UPDATE sqlite_schema SET name='sqlite_autoindex_sqlite_stat1_1' WHERE name='sqlite_autoindex_sqlite_stat1_2'; + PRAGMA writable_schema=RESET; +} {} +do_catchsql_test 8.3 { + REINDEX; +} {1 {database disk image is malformed}} finish_test diff --git a/test/fts3corrupt4.test b/test/fts3corrupt4.test index 044fd4e64..f0b83a390 100644 --- a/test/fts3corrupt4.test +++ b/test/fts3corrupt4.test @@ -6805,7 +6805,428 @@ do_execsql_test 50.1 { {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} } +#------------------------------------------------------------------------- +# +reset_db +do_test 51.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 28672 pagesize 4096 filename crash-11cf359576eb28.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........ +| 32: 00 00 00 02 00 00 00 01 00 00 00 07 00 00 00 04 ................ +| 96: 00 00 00 00 0d 0e b1 00 06 0d a4 00 0f 8d 0f 21 ...............! +| 112: 0e b9 0d c8 0e 7e 0d a4 0d a4 00 00 00 00 00 00 .....~.......... +| 3488: 00 00 00 00 22 07 06 17 11 11 01 31 74 61 62 6c ...........1tabl +| 3504: 65 74 32 74 32 07 43 52 45 41 54 45 20 54 41 42 et2t2.CREATE TAB +| 3520: 4c 45 20 74 32 28 78 29 81 33 05 07 17 1f 1f 01 LE t2(x).3...... +| 3536: 82 35 74 61 62 6c 65 74 31 5f 73 65 67 64 69 72 .5tablet1_segdir +| 3552: 74 31 5f 73 65 67 64 69 72 05 43 52 45 41 54 45 t1_segdir.CREATE +| 3568: 20 54 41 42 4c 45 20 27 74 31 5f 73 65 67 64 69 TABLE 't1_segdi +| 3584: 72 27 28 6c 65 76 65 6c 20 49 4e 54 45 47 45 52 r'(level INTEGER +| 3600: 2c 69 64 78 20 49 4e 54 45 47 45 52 2c 73 74 61 ,idx INTEGER,sta +| 3616: 72 74 5f 62 6c 6f 63 6b 20 49 4e 54 45 47 45 52 rt_block INTEGER +| 3632: 2c 6c 65 61 76 65 73 5f 65 6e 64 5f 62 6c 6f 63 ,leaves_end_bloc +| 3648: 6b 20 49 4e 54 45 47 45 52 2c 65 6e 64 5f 62 6c k INTEGER,end_bl +| 3664: 6f 63 6b 20 49 4e 54 45 47 45 52 2c 72 6f 6f 74 ock INTEGER,root +| 3680: 20 42 4c 4f 42 2c 50 52 49 4d 41 52 59 20 4b 45 BLOB,PRIMARY KE +| 3696: 59 28 6c 65 76 65 6c 2c 20 69 64 78 29 29 31 06 Y(level, idx))1. +| 3712: 06 17 45 1f 01 00 69 6e 64 65 78 73 71 6c 69 74 ..E...indexsqlit +| 3728: 65 5f 61 75 74 6f 69 6e 64 65 78 5f 74 31 5f 73 e_autoindex_t1_s +| 3744: 65 67 64 69 72 5f 31 74 31 5f 73 65 67 64 69 72 egdir_1t1_segdir +| 3760: 06 0f c7 00 08 00 00 00 00 66 04 07 17 23 23 01 .........f...##. +| 3776: 81 13 74 61 62 6c 65 74 31 5f 73 65 67 6d 65 6e ..tablet1_segmen +| 3792: 74 73 74 31 5f 73 65 67 6d 65 6e 74 73 04 43 52 tst1_segments.CR +| 3808: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 EATE TABLE 't1_s +| 3824: 65 67 6d 65 6e 74 73 27 28 62 6c 6f 63 6b 69 64 egments'(blockid +| 3840: 20 49 4e 54 45 47 45 52 20 50 51 49 4d 41 52 59 INTEGER PQIMARY +| 3856: 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 KEY, block BLOB +| 3872: 29 6a 03 07 17 21 21 01 81 1f 74 61 62 6c 65 74 )j...!!...tablet +| 3888: 31 5f 63 6f 6e 74 65 6e 74 74 31 5f 63 6f 6e 74 1_contentt1_cont +| 3904: 65 6e 74 03 43 52 45 41 54 45 20 54 41 42 4c 45 ent.CREATE TABLE +| 3920: 20 27 74 31 5f 63 6f 6e 74 65 6e 74 27 28 64 6f 't1_content'(do +| 3936: 63 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d cid INTEGER PRIM +| 3952: 41 52 59 20 4b 45 59 2c 20 27 63 30 61 27 2c 20 ARY KEY, 'c0a', +| 3968: 27 63 31 62 27 2c 20 27 63 32 63 27 29 38 02 06 'c1b', 'c2c')8.. +| 3984: 17 11 11 08 5f 74 61 62 6c 65 74 31 74 31 43 52 ...._tablet1t1CR +| 4000: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4016: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 33 LE t1 USING fts3 +| 4032: 28 61 2c 62 2c 63 29 00 00 00 00 00 00 00 00 00 (a,b,c)......... +| page 3 offset 8192 +| 0: 0d 00 00 00 25 0b 48 00 0f d8 0f af 0f 86 0f 74 ....%.H........t +| 16: 0f 61 0f 4e 0f 2f 0f 0f 0e ef 0e d7 0e be 0e a5 .a.N./.......... +| 32: 0e 8d 0e 74 0e 5b 0e 40 0e 24 0e 08 0d ef 0d d5 ...t.[.@.$...... +| 48: 0d bb 0d a0 0d 84 0d 68 0d 4f 0d 35 0d 1b 0c fb .......h.O.5.... +| 64: 0c da 0c b9 0c 99 0c 78 0c 57 0c 3e 0c 24 0c 0a .......x.W.>.$.. +| 80: 0b 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .H.............. +| 2880: 00 00 00 00 00 00 00 00 81 3f 25 06 00 82 7f 00 .........?%..... +| 2896: 00 43 4f 4d 50 49 4c 45 52 3d 67 63 63 2d 35 2e .COMPILER=gcc-5. +| 2912: 34 2e 30 20 32 30 31 36 30 36 30 39 20 44 45 42 4.0 20160609 DEB +| 2928: 55 47 20 45 4e 41 42 4c 45 20 44 42 53 54 41 54 UG ENABLE DBSTAT +| 2944: 20 56 54 41 42 20 45 4e 41 42 4c 45 20 46 54 53 VTAB ENABLE FTS +| 2960: 34 20 45 4e 41 42 4c 45 20 46 54 53 35 20 45 4e 4 ENABLE FTS5 EN +| 2976: 41 42 4c 45 20 47 45 4f 59 0f 4c 59 20 45 4e 41 ABLE GEOY.LY ENA +| 2992: 42 4c 45 20 4a 53 4f 4e 31 20 45 4e 41 42 4c 45 BLE JSON1 ENABLE +| 3008: 20 4d 45 4d 53 59 53 35 20 45 4e 41 42 4c 45 20 MEMSYS5 ENABLE +| 3024: 52 54 52 45 45 20 4d 41 58 20 4d 45 4d 4f 52 59 RTREE MAX MEMORY +| 3040: 3d 35 30 30 30 30 30 30 30 20 4f 4d 49 54 20 4d =50000000 OMIT M +| 3056: 4f 41 44 20 45 58 54 45 4e 53 49 4f 4e 20 54 48 OAD EXTENSION TH +| 3072: 52 45 41 44 53 41 46 45 3d 30 18 24 05 00 25 0f READSAFE=0.$..%. +| 3088: 19 54 48 52 45 41 44 53 41 46 45 3d 30 58 42 49 .THREADSAFE=0XBI +| 3104: 4e 41 52 59 18 23 05 00 25 0f 19 54 48 52 45 41 NARY.#..%..THREA +| 3120: 44 53 41 46 45 3d 30 58 4e 4f 43 41 53 45 17 22 DSAFE=0XNOCASE.. +| 3136: 05 00 25 0f 17 54 48 52 45 41 44 53 41 46 45 3d ..%..THREADSAFE= +| 3152: 30 58 52 54 52 49 4d 1f 21 05 00 33 0f 19 4f 4d 0XRTRIM.!..3..OM +| 3168: 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 4f IT LOAD EXTENSIO +| 3184: 4e 58 42 49 4e 41 52 59 1f 20 05 00 33 0f 19 4f NXBINARY. ..3..O +| 3200: 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 MIT LOAD EXTENSI +| 3216: 4f 4e 58 4e 4f 43 41 53 45 1e 1f 05 00 33 0f 17 ONXNOCASE....3.. +| 3232: 4f 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 OMIT LOAD EXTENS +| 3248: 49 4f 4e 58 52 54 52 49 4d 1f 1e 05 00 33 0f 19 IONXRTRIM....3.. +| 3264: 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 30 MAX MEMORY=50000 +| 3280: 30 30 30 58 42 49 4e 41 52 59 1f 1d 05 00 33 0f 000XBINARY....3. +| 3296: 19 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 .MAX MEMORY=5000 +| 3312: 30 30 30 30 58 4e 4f 43 41 53 45 1e 1c 05 00 33 0000XNOCASE....3 +| 3328: 0f 17 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 ..MAX MEMORY=500 +| 3344: 30 30 30 30 30 58 52 54 52 49 4d 18 1b 05 00 25 00000XRTRIM....% +| 3360: 0f 19 45 4e 41 42 4c 45 20 52 54 52 45 45 58 42 ..ENABLE RTREEXB +| 3376: 49 4e 41 52 59 18 1a 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3392: 4c 45 20 52 54 52 45 45 58 4e 4f 43 41 53 45 17 LE RTREEXNOCASE. +| 3408: 19 05 00 25 0f 17 45 4e 41 42 4c 45 20 52 54 52 ...%..ENABLE RTR +| 3424: 45 45 58 52 54 52 49 4d 1a 18 05 00 29 0f 19 45 EEXRTRIM....)..E +| 3440: 4e 41 42 4c 45 20 4d 45 4d 53 59 53 35 58 42 49 NABLE MEMSYS5XBI +| 3456: 4e 41 52 59 1a 17 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3472: 45 20 4d 45 4d 53 59 53 35 58 4e 4f 43 41 53 45 E MEMSYS5XNOCASE +| 3488: 19 16 05 00 29 0f 17 45 4e 41 42 4c 45 20 4d 45 ....)..ENABLE ME +| 3504: 4d 53 59 53 35 58 52 54 52 49 4d 18 15 05 00 25 MSYS5XRTRIM....% +| 3520: 0f 19 45 4e 41 42 4c 45 20 4a 53 4f 4e 31 58 42 ..ENABLE JSON1XB +| 3536: 49 4e 41 52 59 18 14 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3552: 4c 45 20 4a 53 4f 4e 31 58 4e 4f 43 41 53 45 17 LE JSON1XNOCASE. +| 3568: 13 05 00 25 0f 17 45 4e 41 42 4c 45 20 4a 53 4f ...%..ENABLE JSO +| 3584: 4e 31 58 52 54 52 49 4d 1a 12 05 00 29 0f 19 45 N1XRTRIM....)..E +| 3600: 4e 41 42 4c 45 20 47 45 4f 50 4f 4c 59 58 42 49 NABLE GEOPOLYXBI +| 3616: 4e 41 52 59 1a 11 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3632: 45 20 47 45 4f 50 4f 4c 59 58 4e 4f 43 41 53 45 E GEOPOLYXNOCASE +| 3648: 19 10 05 00 29 0f 17 45 4e 41 42 4c 45 20 47 45 ....)..ENABLE GE +| 3664: 4f 50 4f 4c 59 58 52 54 52 49 4d 17 0f 05 00 23 OPOLYXRTRIM....# +| 3680: 0f 19 45 4e 41 42 4c 45 20 46 54 53 35 58 42 49 ..ENABLE FTS5XBI +| 3696: 4e 41 52 59 17 0e 05 00 23 0f 19 45 4e 41 42 4c NARY....#..ENABL +| 3712: 45 20 46 54 53 35 58 4e 4f 43 41 53 45 16 0d 05 E FTS5XNOCASE... +| 3728: 00 23 0f 17 45 4e 41 42 4c 45 20 46 54 53 35 58 .#..ENABLE FTS5X +| 3744: 52 54 52 49 4d 17 0c 05 00 23 0f 19 45 4e 41 42 RTRIM....#..ENAB +| 3760: 4c 45 20 46 54 53 34 58 42 49 4e 41 52 59 17 0b LE FTS4XBINARY.. +| 3776: 05 00 23 0f 19 45 4e 41 42 4c 45 20 46 54 53 34 ..#..ENABLE FTS4 +| 3792: 58 4e 4f 43 41 53 45 16 0a 05 00 23 0f 17 45 4e XNOCASE....#..EN +| 3808: 41 42 4c 45 20 46 54 53 34 58 52 54 52 49 4d 1e ABLE FTS4XRTRIM. +| 3824: 09 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3840: 54 41 54 20 56 54 41 42 58 42 49 4e 41 52 59 1e TAT VTABXBINARY. +| 3856: 08 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3872: 54 41 54 20 56 54 41 42 58 4e 4f 43 41 53 45 1d TAT VTABXNOCASE. +| 3888: 07 05 00 31 0f 17 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3904: 54 41 54 20 56 54 41 42 58 52 54 52 49 4d 11 06 TAT VTABXRTRIM.. +| 3920: 05 00 17 0f 19 44 45 42 55 47 58 42 49 4e 41 52 .....DEBUGXBINAR +| 3936: 59 11 05 05 00 17 0f 19 44 45 42 55 47 58 4e 4f Y.......DEBUGXNO +| 3952: 43 41 53 45 10 04 05 00 17 0f 17 44 45 42 55 47 CASE.......DEBUG +| 3968: 58 52 54 52 49 4d 27 03 05 00 43 10 19 43 4f 4d XRTRIM'...C..COM +| 3984: 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e 30 20 PILER=gcc-5.4.0 +| 4000: 32 30 31 36 30 36 30 39 58 42 49 4e 41 52 59 27 20160609XBINARY' +| 4016: 02 05 00 43 0f 19 43 4f 4d 50 49 4c 45 52 3d 67 ...C..COMPILER=g +| 4032: 63 63 2d 35 2e 34 2e 30 20 32 30 31 36 30 36 30 cc-5.4.0 2016060 +| 4048: 39 58 4e 4f 43 41 53 45 26 01 05 00 43 0f 17 43 9XNOCASE&...C..C +| 4064: 4f 4d 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e OMPILER=gcc-5.4. +| 4080: 30 20 32 30 31 36 30 36 30 39 58 52 54 52 49 4d 0 20160609XRTRIM +| page 4 offset 12288 +| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................ +| page 5 offset 16384 +| 0: 0d 00 00 00 02 0b a0 00 0c ad 0b a0 00 00 00 00 ................ +| 2976: 82 0a 02 08 08 09 08 08 17 84 06 30 20 32 35 33 ...........0 253 +| 2992: 00 01 30 04 25 06 1b 00 00 08 32 30 31 36 30 36 ..0.%.....201606 +| 3008: 30 39 03 25 07 00 00 01 34 03 25 05 00 00 01 35 09.%....4.%....5 +| 3024: 03 25 04 00 01 07 30 30 30 30 30 30 30 03 25 1a .%....0000000.%. +| 3040: 00 00 08 63 6f 6d 70 69 6c 65 72 03 25 02 00 00 ...compiler.%... +| 3056: 06 64 62 73 74 61 74 03 25 0a 00 01 04 65 62 75 .dbstat.%....ebu +| 3072: 67 03 25 08 00 00 06 65 6e 61 62 6c 65 09 25 09 g.%....enable.%. +| 3088: 05 04 04 04 04 04 00 01 08 78 74 65 6e 73 69 6f .........xtensio +| 3104: 6e 03 25 1d 00 00 04 66 74 73 34 03 25 0d 00 03 n.%....fts4.%... +| 3120: 01 35 03 25 0f 00 00 03 67 63 63 03 25 03 00 01 .5.%....gcc.%... +| 3136: 06 65 6f 70 6f 6c 79 03 25 11 00 00 05 6a 73 6f .eopoly.%....jso +| 3152: 6e 31 03 25 13 00 00 04 6c 6f 61 64 03 25 1c 00 n1.%....load.%.. +| 3168: 00 03 6d 61 78 03 25 18 00 01 05 65 6d 6f 72 79 ..max.%....emory +| 3184: 03 25 19 00 03 04 73 79 73 35 03 25 15 00 00 04 .%....sys5.%.... +| 3200: 6f 6d 69 74 03 25 1b 00 00 05 72 74 72 65 65 03 omit.%....rtree. +| 3216: 25 17 00 00 0a 74 68 72 65 61 64 73 61 66 65 03 %....threadsafe. +| 3232: 25 1e 00 00 04 76 74 61 62 03 25 0b 00 86 50 01 %....vtab.%...P. +| 3248: 08 08 08 08 08 17 8d 12 30 20 38 33 35 00 01 30 ........0 835..0 +| 3264: 12 01 06 00 01 06 00 01 06 00 1f 03 00 01 03 00 ................ +| 3280: 01 03 00 00 08 32 30 31 36 30 36 30 39 09 01 07 .....20160609... +| 3296: 00 01 07 00 01 07 00 00 01 34 09 01 05 00 01 05 .........4...... +| 3312: 00 01 05 00 00 01 35 09 01 04 00 01 04 00 01 04 ......5......... +| 3328: 00 01 07 30 30 30 30 30 30 30 09 1c 04 00 01 04 ...0000000...... +| 3344: 00 01 04 00 00 06 62 69 6e 61 72 79 3c 03 01 02 ......binary<... +| 3360: 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 ................ +| 3376: 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 5f ..............._ +| 3392: 03 01 02 02 00 03 01 02 02 00 03 01 02 02 00 03 ................ +| 3408: 01 02 02 00 03 01 02 02 00 00 08 63 6f 6d 70 69 ...........compi +| 3424: 6c 65 72 09 01 02 00 01 02 00 01 02 00 00 06 64 ler............d +| 3440: 62 73 74 61 74 09 07 03 00 01 03 00 01 03 00 01 bstat........... +| 3456: 04 65 62 75 67 09 04 02 00 01 02 00 01 02 00 00 .ebug........... +| 3472: 06 65 6e 61 62 6c 65 3f 07 02 00 01 02 00 01 02 .enable?........ +| 3488: 00 01 02 00 01 02 00 01 02 00 01 02 00 01 02 00 ................ +| 3504: 01 02 00 01 02 00 01 02 00 01 02 00 01 02 00 01 ................ +| 3520: 02 00 01 01 00 01 02 00 01 02 00 01 02 00 01 02 ................ +| 3536: 00 01 02 00 01 02 00 01 08 78 74 65 6e 73 69 6f .........xtensio +| 3552: 6e 09 1f 04 00 01 04 00 01 04 00 00 04 66 74 73 n............fts +| 3568: 34 09 0a 03 00 01 03 00 01 03 00 03 01 35 09 0d 4............5.. +| 3584: 03 00 01 03 00 01 03 00 00 03 67 63 63 09 01 03 ..........gcc... +| 3600: 00 01 03 00 01 03 00 01 06 65 6f 70 6f 6c 79 09 .........eopoly. +| 3616: 10 03 00 01 03 00 01 03 00 00 05 6a 73 6f 6e 31 ...........json1 +| 3632: 09 13 03 00 01 03 00 01 03 00 00 04 6c 6f 61 64 ............load +| 3648: 09 1f 03 00 01 03 00 01 03 00 00 03 6d 61 78 09 ............max. +| 3664: 1c 02 00 01 02 00 01 02 00 01 05 65 6d 6f 72 79 ...........emory +| 3680: 09 1c 03 00 01 03 00 01 03 00 03 04 73 79 73 35 ............sys5 +| 3696: 09 16 03 00 01 03 00 01 03 00 00 06 6e 6f 63 61 ............noca +| 3712: 73 65 3c 02 01 02 02 00 03 01 02 02 00 03 01 02 se<............. +| 3728: 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 ................ +| 3744: 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 00 ................ +| 3760: 03 01 02 02 00 03 01 02 02 00 03 01 02 02 00 00 ................ +| 3776: 04 6f 6d 69 74 09 1f 02 00 01 02 00 01 02 00 00 .omit........... +| 3792: 05 72 74 72 65 65 09 19 03 00 01 03 00 01 03 00 .rtree.......... +| 3808: 03 02 69 6d 3c 01 01 02 02 00 03 01 02 02 00 03 ..im<........... +| 3824: 01 02 02 00 03 01 02 02 00 03 01 02 02 00 03 01 ................ +| 3840: 02 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 ................ +| 3856: 02 00 03 01 02 02 00 03 01 02 02 00 03 01 02 02 ................ +| 3872: 00 00 0a 74 68 72 65 61 64 73 61 66 65 09 22 02 ...threadsafe... +| 3888: 00 01 02 00 01 02 00 00 04 76 74 61 62 09 07 04 .........vtab... +| 3904: 00 01 04 00 01 04 00 00 01 78 b4 01 01 01 01 02 .........x...... +| 3920: 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 ................ +| 3936: 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 01 ................ +| 3952: 01 01 03 00 01 01 01 02 00 01 01 01 02 00 01 01 ................ +| 3968: 01 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 ................ +| 3984: 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 ................ +| 4000: 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 ................ +| 4016: 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 01 ................ +| 4032: 01 01 02 00 01 01 01 02 00 01 01 01 02 00 01 01 ................ +| 4048: 01 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 ................ +| 4064: 02 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 ................ +| 4080: 00 01 01 01 02 00 01 01 01 02 00 01 01 01 02 00 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 02 0f f5 00 0f fb 0f f5 00 00 00 00 ................ +| 4080: 00 00 00 00 00 05 04 08 09 01 02 04 04 08 08 09 ................ +| page 7 offset 24576 +| 0: 0d 00 00 00 05 0f b8 00 0f f4 0f e9 0f d6 00 00 ................ +| 4016: 00 00 00 00 00 00 00 00 0d 05 02 23 61 75 74 6f ...........#auto +| 4032: 6d 65 72 67 65 3d 35 0d 04 02 23 6d 65 72 67 65 merge=5...#merge +| 4048: 3d 31 30 30 2c 38 11 03 02 2b 69 6e 74 65 67 72 =100,8...+integr +| 4064: 69 74 79 2d 63 68 65 63 6b 09 02 02 1b 72 65 62 ity-check....reb +| 4080: 75 69 6c 64 0a 01 02 1d 6f 70 74 69 6d 69 7a 65 uild....optimize +| end crash-11cf359576eb28.db +}]} {} + +set saved $sqlite_fts3_enable_parentheses +set sqlite_fts3_enable_parentheses 1 +do_catchsql_test 51.1 { + SELECT 'xyzzy',offsets(t1) FROM t1 WHERE t1 MATCH 'rtree OR json1''rtree NEAR "json1 enable"'; +} {1 {database disk image is malformed}} +set sqlite_fts3_enable_parentheses $saved + +#------------------------------------------------------------------------- +# +reset_db +do_test 52.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 28672 pagesize 4096 filename crash-fd33f4b1c8348b.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 92 00 00 00 07 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 07 00 00 00 04 ................ +| 96: 00 00 00 00 0d 0e ef 00 08 0d 13 00 0f bd 0f 5f ..............._ +| 112: 0e f7 0e 06 0e bc 0d a4 0d 4d 0d 13 00 00 00 00 .........M...... +| 3344: 00 00 00 38 08 06 17 11 11 08 5f 74 61 62 6c 65 ...8......_table +| 3360: 74 32 74 32 43 52 45 41 54 45 20 56 49 52 54 55 t2t2CREATE VIRTU +| 3376: 41 4c 20 54 41 42 4c 45 20 74 32 20 55 53 49 4e AL TABLE t2 USIN +| 3392: 47 20 66 74 73 34 61 75 78 28 74 31 29 55 07 07 G fts4aux(t1)U.. +| 3408: 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 73 74 ......tablet1_st +| 3424: 61 74 74 31 5f 73 74 61 74 07 43 52 45 41 54 45 att1_stat.CREATE +| 3440: 20 54 41 42 4c 45 20 27 74 31 5f 73 74 61 74 27 TABLE 't1_stat' +| 3456: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM +| 3472: 41 52 59 20 4b 45 59 2c 20 76 61 6c 75 65 20 42 ARY KEY, value B +| 3488: 4c 4f 42 29 60 06 07 17 21 21 01 81 0b 74 61 62 LOB)`...!!...tab +| 3504: 6c 65 74 31 5f 64 6f 63 73 69 7a 65 74 31 5f 64 let1_docsizet1_d +| 3520: 6f 63 73 69 7a 65 06 43 52 45 41 54 45 20 54 41 ocsize.CREATE TA +| 3536: 42 4c 45 20 27 74 31 5f 64 6f 63 73 69 7a 65 27 BLE 't1_docsize' +| 3552: 28 64 6f 63 69 64 20 49 4e 54 45 47 45 52 20 50 (docid INTEGER P +| 3568: 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 69 7a 65 RIMARY KEY, size +| 3584: 20 42 4c 4f 42 29 81 33 04 07 17 1f 1f 01 82 35 BLOB).3.......5 +| 3600: 74 61 62 6c 65 74 31 5f 73 65 67 64 69 72 74 31 tablet1_segdirt1 +| 3616: 5f 73 65 67 64 69 72 04 43 52 45 41 54 45 20 54 _segdir.CREATE T +| 3632: 41 42 4c 45 20 27 74 31 5f 73 65 67 64 69 72 27 ABLE 't1_segdir' +| 3648: 28 6c 65 76 65 6c 20 49 4e 54 45 47 45 52 2c 69 (level INTEGER,i +| 3664: 64 78 20 49 4e 64 45 47 45 52 2c 73 74 61 72 74 dx INdEGER,start +| 3680: 5f 62 6c 6f 63 6b 20 49 4e 54 45 47 45 52 2c 6c _block INTEGER,l +| 3696: 65 61 76 65 73 5f 65 6e 64 5f 62 6c 6f 63 6b 20 eaves_end_block +| 3712: 49 4e 54 45 47 45 52 2c 65 6e 64 5f 62 6c 6f 63 INTEGER,end_bloc +| 3728: 6b 20 49 4e 54 45 47 45 52 2c 72 6f 6f 74 20 42 k INTEGER,root B +| 3744: 4c 4f 42 2c 50 52 49 4d 41 52 59 20 4b 45 59 28 LOB,PRIMARY KEY( +| 3760: 6c 65 76 65 6c 2c 20 69 64 78 29 29 31 05 06 17 level, idx))1... +| 3776: 45 1f 01 00 69 6e 64 65 78 73 71 6c 69 74 65 5f E...indexsqlite_ +| 3792: 61 75 74 6f 69 6e 64 65 78 5f 74 31 5f 73 65 67 autoindex_t1_seg +| 3808: 64 69 72 5f 31 74 31 5f 73 65 67 64 69 72 05 00 dir_1t1_segdir.. +| 3824: 00 00 08 00 00 00 00 66 03 07 17 23 23 01 81 13 .......f...##... +| 3840: 74 61 62 6c 65 74 31 5f 73 65 67 6d 65 6e 74 73 tablet1_segments +| 3856: 74 31 5f 73 65 67 6d 65 6e 74 73 03 43 52 45 41 t1_segments.CREA +| 3872: 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 65 67 TE TABLE 't1_seg +| 3888: 6d 65 6e 74 73 27 28 62 6c 6f 63 6b 69 64 20 49 ments'(blockid I +| 3904: 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b NTEGER PRIMARY K +| 3920: 45 59 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 5c EY, block BLOB). +| 3936: 02 07 17 21 21 01 81 03 74 61 62 6c 65 74 31 5f ...!!...tablet1_ +| 3952: 63 6f 6e 74 65 6e 74 74 31 5f 63 6f 6e 74 65 6e contentt1_conten +| 3968: 74 02 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 t.CREATE TABLE ' +| 3984: 74 31 5f 63 6f 6e 74 65 6e 74 27 28 64 6f 63 69 t1_content'(doci +| 4000: 64 20 49 4e 54 45 47 45 52 e6 50 52 49 4d 41 52 d INTEGER.PRIMAR +| 4016: 59 20 4b 45 59 2c 20 27 63 30 61 27 29 41 01 06 Y KEY, 'c0a')A.. +| 4032: 17 11 11 08 71 74 61 62 6c 65 74 31 74 31 43 52 ....qtablet1t1CR +| 4048: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4064: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 34 LE t1 USING fts4 +| 4080: 28 61 2c 70 72 65 66 69 78 3d 27 31 2c 32 27 29 (a,prefix='1,2') +| page 2 offset 4096 +| 0: 0d 00 00 00 08 0e 22 00 0f c4 0f 00 00 00 00 00 ................ +| 3616: 00 00 42 08 04 00 81 09 73 75 6e 74 20 69 6e 20 ..B.....sunt in +| 3632: 63 75 6c 70 61 20 71 75 69 20 6f 66 66 69 63 69 culpa qui offici +| 3648: 61 20 64 65 73 65 72 75 6e 74 20 6d 6f 6c 6c 69 a deserunt molli +| 3664: 74 20 61 6e 69 6d 20 69 64 20 65 73 74 20 7c 61 t anim id est |a +| 3680: 62 6f 72 75 6d 2e 32 07 03 00 6b 45 78 63 65 70 borum.2...kExcep +| 3696: 74 65 75 72 20 73 69 6e 74 20 6f 63 63 61 65 63 teur sint occaec +| 3712: 61 74 20 63 75 70 69 64 61 74 61 74 20 6e 6f 6e at cupidatat non +| 3728: 20 70 72 6f 69 64 65 6e 74 2c 29 06 03 00 59 63 proident,)...Yc +| 3744: 69 6c 6c 75 6d 20 64 6f 6c 6f 72 65 20 65 20 66 illum dolore e f +| 3760: 75 67 69 61 74 20 6e 75 6c 6c 61 20 70 61 72 69 ugiat nulla pari +| 3776: 61 74 75 72 2e 42 05 04 00 81 09 44 75 69 73 20 atur.B.....Duis +| 3792: 61 75 74 65 20 69 72 75 72 65 20 64 6f 6c 6f 72 aute irure dolor +| 3808: 20 69 6e 20 72 65 60 72 65 68 65 6e 64 65 72 69 in re`rehenderi +| 3824: 74 20 69 6e 20 76 70 6c 75 70 74 61 74 65 20 76 t in vpluptate v +| 3840: 65 6c 69 72 c0 65 73 73 65 29 04 03 00 59 6e 69 elir.esse)...Yni +| 3856: 73 6a 20 75 74 20 61 6c 69 71 75 69 70 20 65 20 sj ut aliquip e +| 3872: 65 20 63 6f 6d 6d 6f 64 6f 20 63 6f 6e 73 65 71 e commodo conseq +| 3888: 75 61 74 2e 46 03 04 00 29 11 55 74 20 65 6e 69 uat.F...).Ut eni +| 3904: 6d 20 61 64 20 6d 69 6e 69 6d 20 76 65 6e 69 61 m ad minim venia +| 3920: 6d 2c 20 71 75 69 73 20 6e 6f 73 74 72 75 64 20 m, quis nostrud +| 3936: 65 78 65 72 63 69 7a 71 74 69 6f 6e 20 75 6c 6c exercizqtion ull +| 3952: 61 6d 63 6f 20 6c 61 62 6f 72 69 73 46 02 04 00 amco laborisF... +| 3968: 81 11 73 65 64 20 64 6f 20 65 69 75 73 6d 6f 64 ..sed do eiusmod +| 3984: 20 74 65 6d 70 6f 72 20 69 6e 63 69 64 69 64 75 tempor incididu +| 4000: 6e 74 20 75 74 20 6c 61 62 6f 72 65 20 65 74 20 nt ut labore et +| 4016: 64 6f 6c 6f 72 65 20 6d 61 67 6e 61 20 61 6c 69 dolore magna ali +| 4032: 71 75 61 2e 3a 01 03 00 7b 4c 6f 72 65 6d 20 69 qua.:....Lorem i +| 4048: 72 63 75 6d 20 64 6f 6c 6f 72 20 73 69 74 20 61 rcum dolor sit a +| 4064: 6d 65 74 2c 20 63 6f 6e 78 65 63 74 65 64 75 72 met, conxectedur +| 4080: 20 61 64 69 70 69 73 00 00 00 00 00 00 00 00 00 adipis......... +| page 4 offset 12288 +| 0: 0d 00 00 00 03 0a c1 00 0d 61 0c 54 0a c1 00 00 .........a.T.... +| 2752: 00 83 10 03 08 02 08 08 08 17 86 0e 08 00 30 20 ..............0 +| 2768: 33 38 35 00 02 61 64 06 01 08 00 02 04 00 01 01 385..ad......... +| 2784: 6c 06 02 0c 00 02 04 00 01 01 6d 03 01 06 10 01 l.........m..... +| 2800: 01 6e 03 08 09 00 01 01 75 03 05 03 00 00 02 63 .n......u......c +| 2816: 69 03 06 02 00 01 01 6f 07 01 07 00 03 07 03 00 i......o........ +| 2832: 01 01 75 06 75 05 00 01 04 00 00 02 64 65 03 08 ..u.u.......de.. +| 2848: 07 00 01 01 6f 0d 01 04 00 01 03 09 00 03 05 00 ....o........... +| 2864: 01 03 00 01 01 75 03 05 02 00 00 02 65 69 03 02 .....u......ei.. +| 2880: 04 00 01 01 6c 03 01 44 00 01 01 6e 03 03 03 00 ....l..D...n.... +| 2896: 01 01 73 06 05 0b 00 03 0b 00 01 01 74 03 02 09 ..s.........t... +| 2912: 00 01 01 78 06 03 09 00 04 02 00 00 02 66 75 03 ...x.........fu. +| 2928: 06 05 00 00 02 69 64 03 08 0a 00 01 01 6e 0a 02 .....id......n.. +| 2944: 06 00 03 06 04 00 03 03 00 01 01 70 03 01 03 00 ...........p.... +| 2960: 01 01 72 03 05 04 00 00 02 6c 61 09 02 08 00 01 ..r......la..... +| 2976: 0b 00 05 0c 00 01 01 6f 03 01 02 00 00 02 6d 61 .......o......ma +| 2992: 03 02 0b 00 01 01 69 03 03 05 00 01 01 6f 03 08 ......i......o.. +| 3008: 08 00 00 02 6e 69 03 04 02 00 01 01 6f 06 03 08 ....ni......o... +| 3024: 00 04 06 00 01 01 75 03 06 06 00 00 02 6f 63 03 ......u......oc. +| 3040: 07 04 00 01 01 66 03 08 06 00 00 02 70 61 03 06 .....f......pa.. +| 3056: 07 00 01 11 72 03 07 07 00 00 02 71 75 06 03 07 ....r......qu... +| 3072: 00 05 05 00 00 02 72 65 03 05 07 00 00 02 73 65 ......re......se +| 3088: 03 02 02 00 01 01 69 06 01 05 00 06 03 00 01 01 ......i......... +| 3104: 75 03 08 02 00 00 02 74 65 03 02 05 00 00 02 75 u......te......u +| 3120: 6c 13 03 0a 00 01 01 74 09 02 07 00 01 02 00 01 l......t........ +| 3136: 02 ff ff 02 76 65 06 03 06 00 02 0a 00 01 01 6f ....ve.........o +| 3152: 03 05 09 00 82 0a 02 08 02 08 08 08 17 84 02 04 ................ +| 3168: 00 30 20 32 35 31 00 01 61 13 01 06 04 00 01 0c .0 251..a....... +| 3184: 00 01 04 00 01 04 00 01 03 00 03 09 00 00 01 63 ...............c +| 3200: 10 01 07 00 03 07 03 00 02 02 00 01 05 00 01 04 ................ +| 3216: 00 00 01 64 11 01 04 00 01 03 09 00 03 02 05 00 ...d............ +| 3232: 01 03 00 02 07 00 00 01 65 1b 01 09 00 01 04 07 ........e....... +| 3248: 00 01 03 08 00 01 05 03 00 01 1c eb 01 04 00 01 ................ +| 3264: 0e 80 01 0b 00 00 01 66 03 06 05 00 00 01 69 0f .......f......i. +| 3280: 01 03 00 01 06 0b 23 04 04 04 00 03 03 09 00 00 ......#......... +| 3296: 01 6c 0c 01 02 00 01 08 00 01 0b 00 05 0c 00 00 .l.............. +| 3312: 01 6d 09 02 0b 00 01 05 00 05 08 00 00 01 6e 0c .m............n. +| 3328: 03 08 00 01 02 00 02 06 00 01 06 00 00 01 6f 06 ..............o. +| 3344: 07 04 00 01 06 00 00 01 70 06 06 07 00 01 07 00 ........p....... +| 3360: 00 01 71 06 03 07 00 05 05 00 00 01 72 03 05 07 ..q.........r... +| 3376: 00 00 02 73 0c 01 05 00 01 02 00 05 03 00 01 02 ...s............ +| 3392: 00 00 01 74 03 02 05 00 00 01 75 0a 02 07 00 01 ...t......u..... +| 3408: 02 0a 00 01 03 00 00 01 76 07 03 06 00 02 09 03 ........v....... +| 3424: 00 85 1c 01 08 08 08 08 08 17 8a 2a 30 20 36 35 ...........*0 65 +| 3440: 35 00 02 61 64 03 03 04 00 02 08 69 70 69 73 63 5..ad......ipisc +| 3456: 69 6e 67 03 01 08 00 01 05 6c 69 71 75 61 03 02 ing......liqua.. +| 3472: 0c 00 05 02 69 70 03 04 04 00 01 03 6d 65 74 03 ....ip......met. +| 3488: 01 06 00 01 03 6e 69 6d 03 08 09 00 01 03 75 74 .....nim......ut +| 3504: 65 03 05 03 00 00 06 63 69 6c 6c 75 6d 03 06 02 e......cillum... +| 3520: 00 01 06 6f 6d 6d 6f 64 6f 03 04 07 00 02 09 6e ...ommodo......n +| 3536: 73 65 63 74 65 74 75 72 03 01 07 00 05 04 71 75 sectetur......qu +| 3552: 61 73 03 04 08 00 01 04 75 6c 70 61 03 08 04 00 as......ulpa.... +| 3568: 02 07 70 69 64 61 74 61 74 03 07 05 00 00 08 64 ..pidatat......d +| 3584: 65 73 65 72 75 6e 74 03 08 07 00 01 01 6f 03 09 eserunt......o.. +| 3600: b3 00 02 03 6c 6f 72 06 01 04 00 04 05 00 05 01 ....lor......... +| 3616: 65 06 02 0a 00 04 03 00 01 03 75 69 73 03 05 02 e.........uis... +| 3632: 00 00 01 65 07 04 05 03 00 02 04 00 01 06 69 75 ...e..........iu +| 3648: 73 6d 6f 64 03 02 04 00 01 03 6c 69 74 03 01 09 smod......lit... +| 3664: 00 01 03 6e 69 6d 03 03 03 00 01 03 73 73 65 03 ...nim......sse. +| 3680: 05 0b 00 02 01 74 03 08 0b 00 01 01 74 03 02 09 .....t......t... +| 3696: 00 01 08 78 63 65 70 64 65 75 72 03 07 02 00 02 ...xcepdeur..... +| 3712: 0a 65 72 63 69 74 61 74 69 6f 6e 03 03 09 00 00 .ercitation..... +| 3728: 06 66 75 67 69 61 74 03 06 05 00 00 02 69 64 03 .fugiat......id. +| 3744: 08 0a 00 01 01 6e 07 05 06 04 00 03 03 00 02 08 .....n.......... +| 3760: 63 69 64 69 64 75 6e 74 03 02 06 00 01 04 70 73 cididunt......ps +| 3776: 75 6c f3 01 03 00 01 04 72 75 72 65 03 05 04 00 ul......rure.... +| 3792: 00 06 6c 61 62 6f 72 65 03 02 08 00 05 02 69 73 ..labore......is +| 3808: 03 03 0b 00 05 02 75 6d 03 08 0c 00 01 04 6f 72 ......um......or +| 3824: 65 6d 03 01 02 00 00 05 6d 61 67 6e 61 03 02 0b em......magna... +| 3840: 00 01 04 69 6e 69 6d 03 03 05 00 01 05 6f 6c 6c ...inim......oll +| 3856: 69 74 03 08 08 00 00 04 6e 69 73 69 03 04 02 00 it......nisi.... +| 3872: 01 02 6f 6e 03 07 06 00 02 05 73 74 72 75 64 03 ..on......strud. +| 3888: 03 08 00 01 04 75 6c 6c 61 03 06 06 00 00 08 6f .....ulla......o +| 3904: 63 63 61 65 63 61 74 03 07 04 00 01 06 66 66 69 ccaecat......ffi +| 3920: 63 69 61 03 08 06 00 00 08 70 61 72 69 61 74 75 cia......pariatu +| 3936: 72 03 06 07 00 01 07 72 6f 69 64 65 6e 74 03 07 r......roident.. +| 3952: 07 00 00 03 71 75 69 03 08 15 00 03 01 73 03 03 ....qui......s.. +| 3968: 07 00 00 0d 72 65 70 72 65 68 65 6e 64 65 72 69 ....reprehenderi +| 3984: 74 03 05 07 00 00 03 73 65 64 03 02 01 ff ff f0 t......sed...... +| 4000: b9 6e 74 03 07 03 00 02 01 74 03 01 05 00 01 03 .nt......t...... +| 4016: 75 6e 74 03 08 02 00 00 06 74 65 6d 70 6f 72 03 unt......tempor. +| 4032: 02 05 00 00 07 75 6c 6c 61 6d 63 6f 03 03 0a 00 .....ullamco.... +| 4048: 01 01 74 09 02 07 00 01 02 00 01 03 00 00 05 76 ..t............v +| 4064: 65 6c 69 74 03 05 0a 00 02 04 6e 69 61 6d 03 03 elit......niam.. +| 4080: 06 00 01 08 6f 6c 75 70 74 61 74 65 03 05 09 00 ....oluptate.... +| page 5 offset 16384 +| 0: 0a 00 00 00 03 0f eb 00 0f fb 0f f3 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 07 04 02 08 01 ................ +| 4080: 08 00 03 07 04 02 08 01 04 00 02 04 04 08 08 09 ................ +| page 6 offset 20480 +| 0: 0d 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 ................ +| 4048: 04 08 03 00 0e 0b 04 07 03 00 0e 06 04 06 03 00 ................ +| 4064: 0e 06 04 05 03 00 0e 0a 04 04 03 00 0e 07 04 03 ................ +| 4080: 03 00 0d fa 04 02 03 00 0e 0b 04 00 00 00 00 00 ................ +| page 7 offset 24576 +| 4080: 00 00 00 00 00 00 00 07 00 03 00 00 00 00 00 00 ................ +| end crash-fd33f4b1c8348b.db +}]} {} +do_catchsql_test 52.1 { + SELECT * FROM t1, t2; +} {1 {database disk image is malformed}} finish_test diff --git a/test/fts3f.test b/test/fts3f.test new file mode 100644 index 000000000..d9a57cbc3 --- /dev/null +++ b/test/fts3f.test @@ -0,0 +1,57 @@ +# 2006 September 9 +# +# 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 is testing the FTS3 module. +# +# $Id: fts3aa.test,v 1.1 2007/08/20 17:38:42 shess Exp $ +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix fts3f + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts3 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts3(x); + BEGIN; + INSERT INTO ft VALUES('a one'), ('b one'), ('c one'); +} + +do_test 1.1 { + set ret [list] + db eval { SELECT docid FROM ft WHERE ft MATCH 'one' } { + if { $docid==2 } { + db eval COMMIT + } + lappend ret $docid + } + set ret +} {1 2 3} + +do_execsql_test 1.2 { + BEGIN; + INSERT INTO ft VALUES('a one'), ('b one'), ('c one'); +} + +do_execsql_test 1.3 { + SELECT docid, optimize(ft) FROM ft WHERE ft MATCH 'one' +} { + 1 {Index optimized} 2 {Index already optimal} 3 {Index already optimal} + 4 {Index already optimal} + 5 {Index already optimal} 6 {Index already optimal} +} + +finish_test diff --git a/test/fts3offsets.test b/test/fts3offsets.test index 4a1e63aa4..24c339dc1 100644 --- a/test/fts3offsets.test +++ b/test/fts3offsets.test @@ -118,6 +118,18 @@ do_execsql_test 1.4.1 { 1 {(A) (B) (C)} } +do_execsql_test 1.5.0 { + CREATE VIRTUAL TABLE x1 USING fts3(x); + INSERT INTO x1 VALUES('A A A'); + INSERT INTO x1 VALUES('A A A'); +} +do_execsql_test 1.5.1 { + SELECT offsets(x1) FROM x1 WHERE x1 MATCH 'a OR b AND c NEAR d' +} { + {0 0 0 1 0 0 2 1 0 0 4 1} + {0 0 0 1 0 0 2 1 0 0 4 1} +} + set sqlite_fts3_enable_parentheses 0 finish_test diff --git a/test/func.test b/test/func.test index 38c003773..ca1027f50 100644 --- a/test/func.test +++ b/test/func.test @@ -1459,12 +1459,15 @@ do_execsql_test func-33.10 { do_catchsql_test func-33.11 { INSERT INTO t33a VALUES(1,2); } {1 {unsafe use of testdirectonly()}} + +ifcapable altertable { do_execsql_test func-33.20 { ALTER TABLE t33a RENAME COLUMN a TO aaa; SELECT sql FROM sqlite_master WHERE name='r1'; } {{CREATE TRIGGER r1 AFTER INSERT ON t33a BEGIN INSERT INTO t33b(x,y) VALUES(testdirectonly(new.aaa),new.b); END}} +} # 2020-01-09 Yongheng fuzzer find # The bug is in the register-validity debug logic, not in the SQLite core diff --git a/test/func3.test b/test/func3.test index a535bae7b..0221a0dfd 100644 --- a/test/func3.test +++ b/test/func3.test @@ -198,6 +198,14 @@ do_test func3-5.59 { } [db eval {EXPLAIN SELECT min(1.0+'2.0',4*11)}] +# Test the outcome of specifying NULL xStep and xFinal pointers (normally +# used to delete any existing function) and a non-NULL xDestroy when there +# is no existing function to destroy. +# +do_test func3-6.0 { + sqlite3_create_function_v2 db nofunc 1 utf8 +} {} + finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index 3020086a2..f5a7b092e 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -302,6 +302,108 @@ static VFile *createVFile(const char *zName, int sz, unsigned char *pData){ return pNew; } +/* Return true if the line is all zeros */ +static int allZero(unsigned char *aLine){ + int i; + for(i=0; i<16 && aLine[i]==0; i++){} + return i==16; +} + +/* +** Render a database and query as text that can be input into +** the CLI. +*/ +static void renderDbSqlForCLI( + FILE *out, /* Write to this file */ + const char *zFile, /* Name of the database file */ + unsigned char *aDb, /* Database content */ + int nDb, /* Number of bytes in aDb[] */ + unsigned char *zSql, /* SQL content */ + int nSql /* Bytes of SQL */ +){ + fprintf(out, ".print ******* %s *******\n", zFile); + if( nDb>100 ){ + int i, j; /* Loop counters */ + int pgsz; /* Size of each page */ + int lastPage = 0; /* Last page number shown */ + int iPage; /* Current page number */ + unsigned char *aLine; /* Single line to display */ + unsigned char buf[16]; /* Fake line */ + unsigned char bShow[256]; /* Characters ok to display */ + + memset(bShow, '.', sizeof(bShow)); + for(i=' '; i<='~'; i++){ + if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = i; + } + pgsz = (aDb[16]<<8) | aDb[17]; + if( pgsz==0 ) pgsz = 65536; + if( pgsz<512 || (pgsz&(pgsz-1))!=0 ) pgsz = 4096; + fprintf(out,".open --hexdb\n"); + fprintf(out,"| size %d pagesize %d filename %s\n",nDb,pgsz,zFile); + for(i=0; inDb ){ + memset(buf, 0, sizeof(buf)); + memcpy(buf, aDb+i, nDb-i); + aLine = buf; + }else{ + aLine = aDb + i; + } + if( allZero(aLine) ) continue; + iPage = i/pgsz + 1; + if( lastPage!=iPage ){ + fprintf(out,"| page %d offset %d\n", iPage, (iPage-1)*pgsz); + lastPage = iPage; + } + fprintf(out,"| %5d:", i-(iPage-1)*pgsz); + for(j=0; j<16; j++) fprintf(out," %02x", aLine[j]); + fprintf(out," "); + for(j=0; j<16; j++){ + unsigned char c = (unsigned char)aLine[j]; + fputc( bShow[c], stdout); + } + fputc('\n', stdout); + } + fprintf(out,"| end %s\n", zFile); + }else{ + fprintf(out,".open :memory:\n"); + } + fprintf(out,".testctrl prng_seed 1 db\n"); + fprintf(out,".testctrl internal_functions\n"); + fprintf(out,"%.*s", nSql, zSql); + if( nSql>0 && zSql[nSql-1]!='\n' ) fprintf(out, "\n"); +} + +/* +** Read the complete content of a file into memory. Add a 0x00 terminator +** and return a pointer to the result. +** +** The file content is held in memory obtained from sqlite_malloc64() which +** should be freed by the caller. +*/ +static char *readFile(const char *zFilename, long *sz){ + FILE *in; + long nIn; + unsigned char *pBuf; + + *sz = 0; + if( zFilename==0 ) return 0; + in = fopen(zFilename, "rb"); + if( in==0 ) return 0; + fseek(in, 0, SEEK_END); + *sz = nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc64( nIn+1 ); + if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ + pBuf[nIn] = 0; + fclose(in); + return (char*)pBuf; + } + sqlite3_free(pBuf); + *sz = 0; + fclose(in); + return 0; +} + /* ** Implementation of the "readfile(X)" SQL function. The entire content @@ -313,25 +415,15 @@ static void readfileFunc( int argc, sqlite3_value **argv ){ - const char *zName; - FILE *in; long nIn; void *pBuf; + const char *zName = (const char*)sqlite3_value_text(argv[0]); - zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; - in = fopen(zName, "rb"); - if( in==0 ) return; - fseek(in, 0, SEEK_END); - nIn = ftell(in); - rewind(in); - pBuf = sqlite3_malloc64( nIn ); - if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ + pBuf = readFile(zName, &nIn); + if( pBuf ){ sqlite3_result_blob(context, pBuf, nIn, sqlite3_free); - }else{ - sqlite3_free(pBuf); } - fclose(in); } /* @@ -750,7 +842,11 @@ static int block_troublesome_sql( (void)zArg3; (void)zArg4; if( eCode==SQLITE_PRAGMA ){ - if( eVerbosity==0 ){ + if( sqlite3_stricmp("busy_timeout",zArg1)==0 + && (zArg2==0 || strtoll(zArg2,0,0)>100 || strtoll(zArg2,0,10)>100) + ){ + return SQLITE_DENY; + }else if( eVerbosity==0 ){ if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0 || sqlite3_stricmp("parser_trace", zArg1)==0 || sqlite3_stricmp("temp_store_directory", zArg1)==0 @@ -762,9 +858,16 @@ static int block_troublesome_sql( oomCounter = atoi(zArg2); } }else if( eCode==SQLITE_ATTACH ){ - if( zArg1==0 || (zArg1[0]!=0 && strcmp(zArg1,":memory:")!=0) ){ - return SQLITE_DENY; + /* Deny the ATTACH if it is attaching anything other than an in-memory + ** database. */ + if( zArg1==0 ) return SQLITE_DENY; + if( strcmp(zArg1,":memory:")==0 ) return SQLITE_OK; + if( sqlite3_strglob("file:*[?]vfs=memdb", zArg1)==0 + && sqlite3_strglob("file:*[^/a-zA-Z0-9_.]*[?]vfs=memdb", zArg1)!=0 + ){ + return SQLITE_OK; } + return SQLITE_DENY; } return SQLITE_OK; } @@ -844,7 +947,13 @@ static int runDbSql(sqlite3 *db, const char *zSql){ } /* Invoke this routine to run a single test case */ -int runCombinedDbSqlInput(const uint8_t *aData, size_t nByte, int iTimeout){ +int runCombinedDbSqlInput( + const uint8_t *aData, /* Combined DB+SQL content */ + size_t nByte, /* Size of aData in bytes */ + int iTimeout, /* Use this timeout */ + int bScript, /* If true, just render CLI output */ + int iSqlId /* SQL identifier */ +){ int rc; /* SQLite API return value */ int iSql; /* Index in aData[] of start of SQL */ unsigned char *aDb = 0; /* Decoded database content */ @@ -870,6 +979,14 @@ int runCombinedDbSqlInput(const uint8_t *aData, size_t nByte, int iTimeout){ iSql = decodeDatabase((unsigned char*)aData, (int)nByte, &aDb, &nDb); if( iSql<0 ) return 0; nSql = (int)(nByte - iSql); + if( bScript ){ + char zName[100]; + sqlite3_snprintf(sizeof(zName),zName,"dbsql%06d.db",iSqlId); + renderDbSqlForCLI(stdout, zName, aDb, nDb, + (unsigned char*)(aData+iSql), nSql); + sqlite3_free(aDb); + return 0; + } if( eVerbosity>=3 ){ printf( "****** %d-byte input, %d-byte database, %d-byte script " @@ -973,7 +1090,7 @@ int runCombinedDbSqlInput(const uint8_t *aData, size_t nByte, int iTimeout){ if( rc!=SQLITE_OK ){ fprintf(stdout, "sqlite3_close() returns %d\n", rc); } - if( eVerbosity>=2 ){ + if( eVerbosity>=2 && !bScript ){ fprintf(stdout, "Peak memory usages: %f MB\n", sqlite3_memory_highwater(1) / 1000000.0); } @@ -1447,6 +1564,7 @@ static void showHelp(void){ " -q|--quiet Reduced output\n" " --rebuild Rebuild and vacuum the database file\n" " --result-trace Show the results of each SQL command\n" +" --script Output CLI script instead of running tests\n" " --skip N Skip the first N test cases\n" " --spinner Use a spinner to show progress\n" " --sqlid N Use only SQL where sqlid=N\n" @@ -1476,6 +1594,7 @@ int main(int argc, char **argv){ int vdbeLimitFlag = 0; /* --limit-vdbe */ int infoFlag = 0; /* --info */ int nSkip = 0; /* --skip */ + int bScript = 0; /* --script */ int bSpinner = 0; /* True for --spinner */ int timeoutTest = 0; /* undocumented --timeout-test flag */ int runFlags = 0; /* Flags sent to runSql() */ @@ -1503,6 +1622,7 @@ int main(int argc, char **argv){ int nV; /* How much to increase verbosity with -vvvv */ sqlite3_int64 tmStart; /* Start of each test */ + sqlite3_config(SQLITE_CONFIG_URI,1); registerOomSimulator(); sqlite3_initialize(); iBegin = timeOfDay(); @@ -1573,7 +1693,7 @@ int main(int argc, char **argv){ }else if( strcmp(z,"load-dbsql")==0 ){ zInsSql = "INSERT INTO xsql(sqltext)" - "VALUES(CAST(readtextfile(?1) AS text))"; + "VALUES(readfile(?1))"; iFirstInsArg = i+1; openFlags4Data = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; dbSqlOnly = 1; @@ -1609,6 +1729,9 @@ int main(int argc, char **argv){ if( strcmp(z,"result-trace")==0 ){ runFlags |= SQL_OUTPUT; }else + if( strcmp(z,"script")==0 ){ + bScript = 1; + }else if( strcmp(z,"skip")==0 ){ if( i>=argc-1 ) fatalError("missing arguments on %s", argv[i]); nSkip = atoi(argv[++i]); @@ -1657,6 +1780,16 @@ int main(int argc, char **argv){ } return 0; }else + if( strcmp(z,"is-dbsql")==0 ){ + i++; + for(i++; izName); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "SELECT count(*) FROM sqlite_schema", 0, 0, 0); + } if( rc ){ - fatalError("cannot open source database %s - %s", - azSrcDb[iSrcDb], sqlite3_errmsg(db)); + sqlite3_close(db); + zRawData = readFile(azSrcDb[iSrcDb], &nRawData); + if( zRawData==0 ){ + fatalError("input file \"%s\" is not recognized\n", azSrcDb[iSrcDb]); + } + sqlite3_open(":memory:", &db); } /* Print the description, if there is one */ @@ -1718,6 +1860,7 @@ int main(int argc, char **argv){ sqlite3_finalize(pStmt); printf("\n"); sqlite3_close(db); + sqlite3_free(zRawData); continue; } @@ -1742,6 +1885,21 @@ int main(int argc, char **argv){ sqlite3_free(zSql); if( rc ) fatalError("cannot change description: %s", sqlite3_errmsg(db)); } + if( zRawData ){ + zInsSql = "INSERT INTO xsql(sqltext) VALUES(?1)"; + rc = sqlite3_prepare_v2(db, zInsSql, -1, &pStmt, 0); + if( rc ) fatalError("cannot prepare statement [%s]: %s", + zInsSql, sqlite3_errmsg(db)); + sqlite3_bind_text(pStmt, 1, zRawData, nRawData, SQLITE_STATIC); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc ) fatalError("insert failed for %s", argv[i]); + sqlite3_finalize(pStmt); + rebuild_database(db, dbSqlOnly); + zInsSql = 0; + sqlite3_free(zRawData); + zRawData = 0; + } ossFuzzThisDb = ossFuzz; /* If the CONFIG(name,value) table exists, read db-specific settings @@ -1785,7 +1943,7 @@ int main(int argc, char **argv){ while( rc==0 && fgets(zLine,sizeof(zLine),stdin)!=0 ){ size_t kk = strlen(zLine); while( kk>0 && zLine[kk-1]<=' ' ) kk--; - sqlite3_bind_text(pStmt, 1, zLine, kk, SQLITE_STATIC); + sqlite3_bind_text(pStmt, 1, zLine, (int)kk, SQLITE_STATIC); if( verboseFlag ) printf("loading %.*s\n", (int)kk, zLine); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); @@ -1872,7 +2030,7 @@ int main(int argc, char **argv){ } /* Print the description, if there is one */ - if( !quietFlag ){ + if( !quietFlag && !bScript ){ zDbName = azSrcDb[iSrcDb]; i = (int)strlen(zDbName) - 1; while( i>0 && zDbName[i-1]!='/' && zDbName[i-1]!='\\' ){ i--; } @@ -1929,12 +2087,16 @@ int main(int argc, char **argv){ /* Run a test using each SQL script against each database. */ - if( !verboseFlag && !quietFlag && !bSpinner ) printf("%s:", zDbName); + if( !verboseFlag && !quietFlag && !bSpinner && !bScript ){ + printf("%s:", zDbName); + } for(pSql=g.pFirstSql; pSql; pSql=pSql->pNext){ tmStart = timeOfDay(); if( isDbSql(pSql->a, pSql->sz) ){ sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d",pSql->id); - if( bSpinner ){ + if( bScript ){ + /* No progress output */ + }else if( bSpinner ){ int nTotal =g.nSql; int idx = pSql->seq; printf("\r%s: %d/%d ", zDbName, idx, nTotal); @@ -1955,10 +2117,10 @@ int main(int argc, char **argv){ if( nSkip>0 ){ nSkip--; }else{ - runCombinedDbSqlInput(pSql->a, pSql->sz, iTimeout); + runCombinedDbSqlInput(pSql->a, pSql->sz, iTimeout, bScript, pSql->id); } nTest++; - if( bTimer ){ + if( bTimer && !bScript ){ sqlite3_int64 tmEnd = timeOfDay(); printf("%lld %s\n", tmEnd - tmStart, g.zTestName); } @@ -1971,7 +2133,9 @@ int main(int argc, char **argv){ const char *zVfs = "inmem"; sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d,dbid=%d", pSql->id, pDb->id); - if( bSpinner ){ + if( bScript ){ + /* No progress output */ + }else if( bSpinner ){ int nTotal = g.nDb*g.nSql; int idx = pSql->seq*g.nDb + pDb->id - 1; printf("\r%s: %d/%d ", zDbName, idx, nTotal); @@ -1993,6 +2157,14 @@ int main(int argc, char **argv){ nSkip--; continue; } + if( bScript ){ + char zName[100]; + sqlite3_snprintf(sizeof(zName), zName, "db%06d.db", + pDb->id>1 ? pDb->id : pSql->id); + renderDbSqlForCLI(stdout, zName, + pDb->a, pDb->sz, pSql->a, pSql->sz); + continue; + } createVFile("main.db", pDb->sz, pDb->a); sqlite3_randomness(0,0); if( ossFuzzThisDb ){ @@ -2065,7 +2237,9 @@ int main(int argc, char **argv){ } } } - if( bSpinner ){ + if( bScript ){ + /* No progress output */ + }else if( bSpinner ){ int nTotal = g.nDb*g.nSql; printf("\r%s: %d/%d \n", zDbName, nTotal, nTotal); }else if( !quietFlag && !verboseFlag ){ @@ -2081,7 +2255,7 @@ int main(int argc, char **argv){ } /* End loop over all source databases */ - if( !quietFlag ){ + if( !quietFlag && !bScript ){ sqlite3_int64 iElapse = timeOfDay() - iBegin; printf("fuzzcheck: 0 errors out of %d tests in %d.%03d seconds\n" "SQLite %s %s\n", diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db index 86217e4b421375154ba0316867b7d9aa9514c191..966b73730c2c0c73069714e73127ecf2b056a306 100644 GIT binary patch delta 165725 zcmeFa34B!5`8Pi2W+pRpXR^$l>>jC!?Jfworb3REM zDf@`M$9~QBu@~91Y!`cy^|6Q9y=)u1h26-mV^^??*!gTN>tN@wFk8&#vsr8go5X5a z4fC)nR>21Lw=rpccfC%RG`D+(u1l9Rr+Ydwjos6b&g`Cwbb9v`q;=hsk&f>kkF>UX z9MUn}wMf0)W0AVLMAbqua z0McJ}=ONwGorCn=?kuFYcV{Bq+-*mCO}7o{)!mpO;i~Rbq!)LmAic0#MB3eLM%s05 zw+TP1yNyWOx|5ME?ZzYt!EQa$McqkA=XDE68@n0OY26q`VL}&@QRgG^orgp@7s=oa zNCvD!lCuU$)@mfWPMwZ@)q&(-8kaVAkWWyOqR?b7RbPkdwvyp^nAqg}gS=4}J!AvA)U{2XwOet%` zjI!yIkW8vWG6B=c{Fq1P#RRf0&nToy4U*v_kPO2dvLTp4R)M)=#h5yliJ4;(CXR_! z==}c-L-LoQNdAnO(|w7F)BT|w$)}h+-3Nn^yn_kUy@>hK{iFoRPRyb1UKzA*bd+B1-xjak&qNk`IvdDG3rwCSc})^yV_XSzB} zmd=N{(v@SXa3lpT#Ly-+cUSY$N3FXVj3q~HV;2eEuI#go9g7@al6x7|me|))?*TEL z_P;I}cr8YC#Y1*KquzTAmDK)~?bb2N|FeVStC9;N%8lTLqqC;n7 zXHa{EK8uR(vp+~<=GjxNPOsv0IX(FEIqASlQkl^aaJZd5huXZ&KA+L~Pulyax6^KC zVQOr(&!pXx?Ai404zZMWTxK`&xzIJ$&yYJ6dEVr@raFEyqxSL>585`_J}4@erB#OA zZkI=NxEx-^$NxDLcZ<*MRlH7b(ChJeT?&39ULQZB!(wp<;zRZ_4b+^>b#|&^zNSgXqA^MKU#iQJ6|QJBl;u@ZIS-lj_FLmRGi| zT^f=nYMu3nvCanMDKq7ArP5#NtdMJGPLNwhIkn$l{D!mARq2l13XOI|+b$~}wt7YD z(sRPC>#NZNG~R)c=Vy*;oO$DC4n6=_06J0@N;PWLZ`1Y(wD&^G0FpmRdxKA@g*x_# z1^51Pp~UC|MH)_HKTDIDM1jxJ>eQK?(lmjWvZ@a?OZiOQ>yaD+J@~a`rja*Dx6;;- zWaal$)Q3XS{fy2UAziJ$S0W8$H1;OxE`BSOHhpQyQFoV05kbxGmM&oYY8uslD%H{Z z7fO|^hCbMro1IiYvxatmD&^9OEz&6Jx=8A-mFG^GIic>1x(T(@=hV$Aul7|`#*^xb z8ga_ZS#?dbF+j8H<)BBNRX3dv?>cQ{gNjlkHZ;)(Lyg5GUz?ju7xkEj)52s+sh!#q{?##Bw%-w%j9@>+vcNefx$u*n?rMl*`&ygv%=BBj9Xkw)JU2F0e#cQ`MRP8+4NQ%;4Gw4-Ziqj8p1cCyp!y;D{>fTT~lN=8v8L0hHQA#!F zSb;97rET3XN2SkAjVTsa<}#nd;fRE*9T72vlNO&N;t$h-yUitoai%yTz<3dqY4L;| zksf!%5pnq)5$Dew5e3gUybjveY8pdn%gp1t&ir3_pxHQ_j!3|d-$=;ipeaku=h6qh z^a5%(B;`>}tN8}{=#8{&a;!1Sn;R^88ot!LxxYjTwd}QyA^GCcYw7bf*4g6fRpG_U z!^`oXHa=$E%0^J}abP}P<#vSmn}PTh2cNveK?mZ^ zekbOOkFC?;(*XaJ&ro4bdHnWdP4^&~~j5=G5(OiC;%W?m+Au_uX#4cAf^N0U3}^ zycraQdc?E=wCOgxoGvd}*|xfUajIocb9aG_B6z(BMG# zSDo&!nDQ^uA6u5B*ZtuP9cT795aHsDCNSbUwrOqyGWRnx(jaH$Vqic3gBLgya1L^S zDu0}|KS`t2d_oIU5Nw&zDwlK(pJk7lzDBx~SLh9o4!q5NX_limHbXIDOX9aYOtyTR_z!7ovwt^T+(3>`Kn=dcdXUNgCzkh z*YL6f=|GEALRO#j=VO_SpD`e`bEO@;3$H*>(v`X~RW8gdXKwg$r0upON*gTwg4Z{Q zY_~`UsljYbrd{3AC#Ev3EZCtP7fQ`Ebc8+Q2Y}uQ)b>THg#EW!4;eKBBF0KsmbPXk;<@#5SYJNNL#1hBv>f#dnYikJBh z7TFAk;`|R**{K%U!?g8msZ8BFQcBgZPMNxIkTi#JZt>6T*jZ}fBdntX7fD6`Hnurx zzW(1-0mGgQA)acgUOXk{W21F!G!TWG*}gF9LFe8{*YMs6xKBeS$D+LpIR@f#^$^-V zO`1vjZIY~xyWFb-3+Ww4z3;rR%=1h}648YM_xfNw=PeN006L zJN)tCYSkH%l~LEnmK9X=`;1E3+LyMFs=6~rQ14R)y$THb6K!c!QSVo&6V&FF(pE-k zdD2w1;!xTv+ARlFHA|b5or=@xchCX1G(vs(kKhfCqyP75g%y&krAIg?m`UtL=^Szt zO9iQrp>aUE5INSO#bN0$r=k+P>s(-}s*yJKce##t#;GbdNmn!V+EG%wCOo=Gxm8+9 z^%qJrQdV`eF6{Fg98UP+5Tl{{PqRJ28I1hx_vcq7zUi^03bktNfU7fJp!2NVIb z$4{2af*k$Dc#O&uc#P1}VCRVf;IVwhe|hCwc=z4Z^MRQ6e<`NE!{Tr$o`~1)1^wyA z+JgnI@A8P=$jWeNbuhd*6mIW4TRpH*8ZI1*zPo=2q;2(Us*&{yUh1WXFA-heO=pv1 zboRy`Q2W1hOZ6P;WeSXy=6xqP_HzcS{`W#)`6<~1r$cnNxII9QK@5eC_P9#MfKl2t z-JYGSDWhb%ZkBy=#p!Yl)x`2kvi=J*_TMA3{?qLnxFAo&6JyfZxjJ?($WRBf{duAR zr0Ubu({LyyRsoPQtB)Q&aMpcAA>PEUjUp;6SZRLZ0e3PFiaUD4r8W_n{PA z&x?QOC^QExBg_mJ`ENWF{qxAkM5UBG6`gaQ2H*%E6A=je1adh4Y zOEIm#Pb{FEcF5iRLd$=z-~X3V!C`7!w=_WGqsCM1r_!t=JWM~)_#q(m2ZhMSvC`Z9 z`kG`8U=ygJR+5eKaFzI!dSo?~GjbjF*UiW4?b)=t{c?nnEoqcW)e5h_=E0z{HPu2dfmYYHhahx&B~>{db3d& zBGabvrMdKA0~=&Pjq|A`O|RQAnf8{H-9zd9clCYd zIe!rh*n!v0ixvc~?%Q<6sw8#>?aekhXyh%XpZ9&e(1a1v>CRO5Hn1kf&WjfRZX)fi zm1?MYymT{-p2bS6bFeuwNse@`a+SMjUlXfPQ$5V5p~eMNJ5d_)_#E~nGlp7Lbp|?R z>Z!38(&!p{GJRES${^D`R!}g$b{2MfoqP*trtEWi<=NVa;*;xUPN1@RtdI`RG!3AJ zFH=o)aBI4euDmThlXetk=uhJ!c%I40rhzKMh%g$M%lS$cUczH-FMnIYNQnr40v+JAcCeZCmj1Jn>Xe_4>tIbns%n0+}xBA4V>M7-%{=xj^?q(rsC`yi zCpkVd70_>HW!q0@*tG5~CS?T6R55@ZY?wADf!LI|+^*P^R4cC76}K~f!Kd&!QV*ZW z(ilCk#F(RvuZrSv5G-ht6W9 zEUA9pENb79QUMnDlp}g^k;td-@Q3nD8tY<>{{oP{&S>Qvmd$6jC0hH@SbBB5q(swS z8$?BO*{es7_;CfZ*(3+T}7%q#dCgS5?^tkC-lGxI5D0o(TP0;dY#%+rQCL><#j zIqC~%F)t(WZPSobLEsnk#0c~5eqhUr16z$azNxMjTiSD{pnc(J@rRSt`bK6GsA7V2 zJFWJxN9n*gX~O0?EQJdq5m|{Uo9*=FZ{-FR19{$eE%L9w8#1{_uq4phT1ISi^3W3& zHHXP=;Rt;3^}BO^QXD%Cd3@*qkD}fSnR{ubS__mEEdqt?{ z#jL@}?QOxpsxU{&Rg`W{89;@9G^gZrDlORl;oM>iHhP-sr%xYOJARrRh$`!I>U@>n z%4*pDD31QhmM>iqTbM9oiPt{_^BO-5$mM~dE!<8hI2uE#feR@~=0P0)Rve2=nlqZ} zG%Ff;MR;wEn4m2eT*Pvt=iFu{+bA~WL}Ez6aV55B_ut$mP(;mOg!a8?Q=;-{US5TF=G;ed7Z2;wL&&s5&t6&{=#@9wGeXze|kg#5);!y+aH0I)h zYFhkl-Za{MZNW5ZH|I}J<0|JPheBUw=1->sIr-CQ`F51SnS(CaQIJoEN^xzBifd!5 z229x8F~FD_#3on}d&DgPK-vo0Uo}9cswWGIso~rK)9Bj?yplKZz{X9;E`A$lv%b!= z(Tn>FM$iSHBm2{(`93sjZl&TwBm`fi$jn_3k zhwNw5kv(TFYAae$IG#Vgs4$zx{w}|S{-?FjmKt&@LD(Zi99-22>E`|XD(YHYDAC{6 z&YJ(FG@rA*k9_4ho z>An3WWfZMp*|c%G$!rL7Rm}CFvke(~-L=lXq4twQ=Loy))oDp3f%C};}qONI5?er;U z)=h|B7Fxgp(UFn-$`-jIQD*|jyJXm7&1sOw&6gE%Vw{UrXvv>a>ZtFfl>C2@c|XD}t^Q7K$$Nx_nb1_Jfn?4>vPrp?1GrpMDP;JLRjcu$~_je4Gh3GB6ucL<-vicLy zVLqKXG}lUdw&e}rPp+lLC3z*ZYfat&>MghAQq^5~297LG!r*ca?P5kxj23LP{8kJ? z`X8)U(3U?iGtGWAeJE`oY##WX{F|e_qGX^Vwo5EnCbYM@uiaOe`(brdU2!CmJj>N1Y1_9Dl)or9+4@qJ{zb^Yz~zg0yx7SI`Fs&qFdTM! z!__W#1Z*GO^_+1b4d^wR9hG5JehMb=r06(7y@jA0?RXtJ{rx2cH12C-3EAzbg*0=r zIfXvoUy}2KQ1a{+|4~Q46~=}&=BkU7odv^aY)-!ASTavd9iVjo9!bxNlk|w#|5Cha zBXwMwKkhgRPaj?l!h9;?uK!o)d0HgkaE3w-XE0`8xjLM*4eJwn&GbG9_!m z#{e!XoC-9eLyD@0oIcc$tpIA7FJ@$<&>!D37W}K!eA3CN`MR=Tiznm^_`Knu)9Zze zr8^V~23mYAfnd;aIut%lqwtzB9&6FDmOk6qGhmcy%z;s+#8;D@97*;ReL`p|C`^bs z{W4hR%GHp5;se2lcsTw1bz6SQ5njJ+-FiCzy^OqQbbsb41V6TApq&cuTc_J^`N1Ixc*fsRU$Hqy0({6YoiCr6=`QjCQ*W9!w z$#`88XNv;YzdER|>egQ+u}!q+$znNrZO_!c6o8_BnQEnv(Mkx(lya(Kqe+N0SU!ppskq ze)9H8#+k_!>TM}4=-c{oP++$bD@?z#@7leqlh~GM(?IfRTDTq35%%Ve^bM16!gGHkD9R_`0^SgH@v8~bFdpFZd zXO*T=`W=}?eb4Vd%Va3f>mEIuzI`PpEBe#VGyCR#DlyX?Kkfum^HpnK`)AiN!_8>m zowRclL*qYxJ&D~F?f$%retd~IvTxU+9*dy}n(=XcbN;b~nF{r~n^M6gN6-D+*?leF zY+>x~&7C>d7%y#y8RRvg=$wC4KDL{UPh$5|U;{Lid!NyVE$l|OCm6G0kJ{1~m~{*< zWLK@f8oYNo?cAL?OdWf(-N@ME>fyI+CPo{t*XI+wkml>Te-<^S>Fu=f9UE4&PWJ#6 z&#^DUlbyuI*e~L4N@0D-?{*VA-~JJt5_bPY${=}`y^Y2^Y9Gv&($0tNw`qMk?X zm+~qer0zTHLz0w8m^ST_+BdJTcd(DBd%8Vq@XF4W;c)QmaPXYA<+A1lAqRq;OV@_E z14II6IB3rc(g-^6)xZjppOjnx>ut%9H4f~OivGRIuIviCoe)A{Xc>W?B;thmyVBx_ z9Pta$TppCOe#nFCbrU{UG-`>xAS$<}jF?g1i6MGOZkp(X?%@>fR&e|}xw};^9lq3N zr%eyT&kJ5@LsQz8hgZg2N;=zKr-l{wVhnYl)n3E2QM~d8aOhfa5{E9I7v@SHpXLSz zS1|agwRoK{CxmAgT)(QFLD=v+aSb`QB5D_5P=kxSpmtHa7NEZA;=4e0{yIHdAMl9_X!>$<3H6*0?Dy^0wn^|r z*{p8apW ztj`#HZ--8&A8A|Ngy&w&w+F5XZJu;}nX+)bD za_F;?Z}cfJY`;TmYj{Z@xPEb<6}GdBHQj!~o3-D?RXVEUd{l7I{wswXns#SiEnQG; z@{2*AAI@Y!Ah({{yizAzn4BIjgc~OucB=smiGNObIkk9wjt~riQ!IHY4o67is)cEA zCgHfihG_-qxH||Q=(1A3cgQtAO8B(Dt(EI z8L;^(I;gSPx-LELrsd)OL!!s&q+v^~PNN4ruRG|ao@a~yNW0ex>3Xj2qCiXWCRRt= zLdCw#-VzUAOpj8}Qj=Yk!^Jbdx87l958vG22kmjB%%k4f7KNVb6tWBpB5vm*+W$El zNEa@&mhjG3b)j$Eoe7Q;@Y+i_H^p)Z_a=xrxW`Sec}e)SaKAjB6CH}HMa#y$oZN8n zz|FXu6ZerqF7D_A$0nYOyE?&>kLLpM_j}+f&GRvbC-@O+xwsPvCZFGnyOBU@<+->c z30$hs)5rLgxH}1)sgOf;vG%%RRq;7Q>s_&jy7w4Fqwd(f(8R{UR>VZxqdNxn^(7XE)HT( z6>{@A@XozJC?w@kaWVaIsr?z-%5Zzz%Fe~BTHDqxUcF*z=PK%52J&%CfAW&BOi6hj zF5%8j`~H4^{d_(&Pf+7i_EPSXWN&t_{ZBlR;!vYR08YNd;()^*956I+a#V8{R6o_v zlSb3nPP@l@im_{3q4`E($|K&8vnDRRx3zaJUA}ZfxMs80l9&6>QysML3HZb}ED;Lm z>xGs&mJY)O?9Iaasyfrl!Soo2a(KPeU5fr6`h{e(sXn*Q<#J(3#ts*;&GrFCEN65; zp~`0aU-*zcNy>oafr8?ujz=v8OeOZ5fuTHY_Yunes7KlHoqd(kP?7dpav8dAX((X&{6kPDN6))1>?(8 zTKrg^Opd4Rsbg{FDD3NXR)h16vATY=A&!I5a-~{3OPw8aP|9V+PM#XRsQGhW6K6vgq3udk*tc?W2~d z)bktBC1E6g3|NV~;ll99O${DPlhN&p@U2bS_K@hY#CzpJ-}uU1mE!QCRtG2#|3|$~ z=jBOY?Rn4L&I$~P#|g5r`S8HZTs|h8I&zu7AAiCRoaUsCF9zn)Zz|Ht1)rZf&ViBD zKWZ(u^q9op21#{*m{mJTPBquiq1CBIJqBN)-oFkUPS>@W%x-uyN4Q^2M2rOasklHv zx`_KKp;hoD_PVv-1R(M9H1rLzft^D=E^Cf3p(a{ zAkA*%wbD7R0PNVXQblJFyrd6aaBjeh8$M-AtrIyuDnm$=z&vw_1R&=)#P1G+*#gV$ z@z5kR9v({taK`_*eaOZldwvEMRARv$yAW;t*Z;+JXl8Fx&zX8~ zl>O~_?lcdMAqOP8z2)dVZJ8=TTmS~g3XUZ+W2atGjn|j{4@=^(axAYI)w(iyp zRCcTH-))VseqbC(EAF*k-%mOg7%{s%l7U()Sr%$f>`~vp-&!K5HD8L)GJ5qH5u4UJ z-A~oG9=6`i=)uRVBUmr>uC+F(-p8#L_!zFYrqMXnnr8zH9d$T;UYfezdKrKEXS8p< z^%fxzp%ojfzoIX@t5 zSk$Kup+JshJk|G^Q)ytER2FTRk4+85XrztKEFKCtLhbp*8R_zPSlw`YrWKlnj!d8D70*cxRLZMXFNal* zTtBxiKner%^~E=c*Yo>PdE?Wx?e?H2E?s1iys2~??GL3)Q>(5n9j0lSpQVM@mOe%8 zx0tV{9p#8NbKel-YhB0l3*N|6|J?&zU$L20?-c-lCuIE5Of*s^KyxuRHv z;2+g)M+8bPh$+e@U|i z&pebzzWN21vo#qG3>TNaJ)UZK-71F>s^XzvnG5L91QU;>)V16sQ{gmIDg`P{18~97 zZt_v@9zzC9H*K%LgJ?jJ(L|3dPxYJE%d_jwm@U_jr++R_&8c0#0Sd#0rYSROo94^Y z>gJcPt6X0J_3JSc-KngP+g8EeHm1m2&v#h(7I<{emJHfB%3>SZRM#-Qc6?ndToCNA zcuoDr7>Eb76qa(FTWnjgiVh4i4WfaTyh8f%TiAX5Y_d7E+*!W7B5r=QKE4Z*2vxN% zZi!Mivvx=s{lZo*;%dTN`eXfv2Prog?>A+nU*&lTF zTZXj;TEeXeLd5wTE@k12x4_|)9i7O6FgPaEY1=Bm_;s}B`Qip+#P6;KO9d5D$skJi z6;Gh6jU^eW7%90NYu;0d5G;P$_F{G>HK$q(0y^@{jj4H5d{1TrH7vHFJ@c%=jp(c#lz?)d#q;KRG$h09>=govKf*`(IG?f(5PT^WMJxEI10Ce6@?8P z#I9HPvi*>{CYt7UJ@6kLcl@Wv#DA-TM4M$;SWK*)0w2GvHKt}76Ew|5M3TTz)3VJW z;H9TtNg7RC_a$XzaSj*ACnrSrGpj|<)mYTd(r~6#2oR<##IKxBD zS7oJkX^I&h*iH)u2L%d0;~XH265?lmoW)S#n*NYs=tw0;5pL+1oKQSzk8sh1qZ1x$ zCGoJU{~AX#F=V4S;;46nOy*%gb1i}50&3G+O6~nY{bHN)r8-uMP5C@#`>J3d7^lxh zf^q8l=YY+#cXM^xT54;|NTau26b9%QguNkkr&3bDD7e?!5%UbX=H-`Y-(BfBe1R{S zc{HH~7(b1_mpPh4AOSZQH)0}}&k+uQ9|Zx#6$F!tAVOXQJajnXcZ7o;ei!m{K%wH_ z8VZwrOF-6qq+FX_Wc9)94nngZ>Ul3UR+i*is=5C=3RELjU}GvPkg7#~CBZ!MJ5;e>%DiP^ObCxp7RIE6VCot| z{4w6rAhtk*P;OvyAT5iCU4}i-7R*`;G@}8?&jGMA5a=U*RNzMiepFB$?m~hZIG+Q} zV#J3U5Cn&ZIEz56iol{IB0@5WU=upxK@DJGB2aKf5T+;MMhz}BfrmhY=o2~FE-WJK zM8GaZpn!-td}tQB8IFYT$AJk7lyEo!OS9R9S(<5?xsZTk=s z<+g7!&Gg0myfW&|wOG}$pG!YM=%`+cm4?5BErQX>_8Gi}y|k|@b12<>P>N91J*!V@7p0*s4Rx+QOeptGl(e`(w-Ta|_)ZHoNsN3(C{=n!KtNjMZWR+Bv zVqZ#od!)N5{jbtcUhXyO^1|(Y(O)HzR(v2`uP$@guVd=f|09iNu_$-9*O_y<#y*!v zvv@r|TnR)%>5U~jXy1p@QmCW0x$KCxwNom>W*Be{76A3=t%I1NHoEQQnpp5FIxyVo zp%t0-X4?NiaV}>}Bts2n(1>l^8enE|&ejE?gg0P%n; zgjV`PJ`QgP?Z&Inj()HU_)tK*j$bTK{vePq|HT$0eQ=L?0B7Ka*tvkn>*ZSj*q6dT zg|=-FbLrrfxuzR;JcFjoS z4cfh1YBFI{2%0g)Lk(@#TzdDQm`6PhOLjJI0S>$h>Jt7iu&q<^QPp}=w%!wofW_|4 z%_Z*yO95@%UXn~_Z-+JdTN5od+VTdL$X_Q~q`bJ$F$&HahzB=g%Iq4B6=Bc_DhPE>5bzkr9%B&tD#Y-0V?ALL>eO$@ zfU1&5cwEHCD!z$;Z4jw=ME`E0783K|KT<@>8!BegmdlOyxz7K}L50SWjd77?w|NvF zs9)30ZDvm?NEK%oKr2CrS~&T`1^|*p*cMB05!CSs_8||PZO))x(K5N0hG`M1BDd&OQSW9#-2N{h(?tP`Lyv&y_r7vB+Wqb-&hGlgPemt=Jh@)1?sT{RzWkLxx-X|0Qq3pZg0*_^Lb{f6XZ1qxu1WB_uXqm+O z`xZ3^tW&91u`EZ)jaEw~f=;n{mcP^c^DSv)pKdub8RQa5PHOa60?7^^xCk%2?JVPH z?30#OrcfXPyU1ZPAtowkSu(A@Beur~C)N;q&D+H89d@`S_)Mi#oRL?U$yL|DMv$ekAtO&z zdms6r6WH+Dlq{OEH`f9kiFp{+in*R5xxAsaY4()aJeJ8k7)`?4tZ71>CuTq?Po}LG z*|L*o%T4w3D!2k7m$v>U#gd-zm+)45JuTQSqCLy)PHMa?V-?@4OieBGdNh>5v4uRF zYWKj@t!FYezLYyN!b0rIFiPK!J(^-V@U%2|uwUeO3OKZyr`SmWgX@h{BI5NZ|8eZU z{i}k~82fMKgPZL4j99_vq`#s@8~%IA;l7=VTH^Zg-h+Y39;VFb1ggG0nsutv>6W#x(?c#Ia4#L|kl;Gq4D7 z;P$|pFmW7TwdNZ6{^kvtF!(?E9hszbB+aL-8#7-%eusSQg_*1QzW6q3u1d@25N^u) z`LPEyk$gsp1?}HbW2b#j{|2(@KWy*GhN6y`S;!-~JJUuRcZh}b%=@-N>bOrV5y3!2 zVEgCrO?>w@i=+lmkc3)(I9KjiPC++J)*uQTl*dN?{hh01NMb{p4~z<@Y^n_ z3dW#A$*{ma5HQiP7ZyJUekqNkn^!|KwRZ=UZwP{KRe#rNKZ`L$APCzxaCrHMidWmu zHLnS;Y-wAyv=fdyEA8WH-;MTMYCp>kpTcwOGpM)Sel?q*ZtJv90wWv0r^Q40SSiG?Y!3C5_?gadXjf<75bm|5j)2|g_r&vd~$L!XluJYUA;YI-y#UR z9wXZdd-YM({+$>6J#Bjx9!1CWW*K8c)W#Kd&ykkhN)@4)0oy0E=N%E&yuZeL-5f%n zx?Anx=eWXP!+0RQ*dcayidxMsW zba1`c<>o7r8?3>TY4QEy^!^rWGzTu!^uikZwbZ@Vp1=9a)HHheZEOXX(}8!y0zsDP znJ-eS=$9``S+uR$8tkWG8*?x1cB#HnS*%U9r26+O+cM38T+VU4!WuT3t~>uyc= zR9tWgx8T;K!0H&M;`lSm9gsjau$-vIULoIF1ST5AR! z`nx%q(uayB+64y3069Dup{)z-i|CANO3&b;Z4T>IZ4Qe;+sFFsaXr(Ul3CLAx}@v5 zHCnF#=j6AHmzP!Ob(@-aOKD*WEV1{dv8t{w(8NQqG6Qb}HrNpe1h}3xp7U_iD36od zKk%Eb>M(NL`iSS0pbt5|JIiz6@{r^Ev)Vm~;2g8Yat>%GuxZMr6IfY!p#5=jzMU8B zQmVOPm+#Z^tOH6J4}`sV4g=$crMLDNc3S`u@$)cjVzAk(-N3_cjkiPH+>{JFfp#4# zF6c0D&MVe*0kBVs<=oYHjl#PHt&G+#G#(aXvAf_XkV|w-fdQZgX6ErGh0$reC7$!+ zeG1=v#=D^`@rV%o;+0^}*@LK|@qz*DQbR+c6@)zr#I4wQ7dH`865T>~q05N%id&I^ zNzgJpdND>+JBQK|?kYIf#tW=H*F?qY#dbD)qP25w42J>G&Ycjq+?o{>T8T+G619FOB0?0UOMPQuq(Ev1| z1zilZpo;;}L#X0lMF6EDDyT*UfUwxn6@zt5ZqNz6X*}m>X=%YAC30Alc#&qmz$IP4 zGU`s;gvJx?PUL(cC$_H=IgcMa=8+t1VE8rc0P`JAl>IpHsqI@tk-N({*IL9AO`34);W@ISvF1I5J*9F~As!2j>DeVM!#i z_@E8eY=6!lz*3LrFq730Xm$HHV(_nc!+@&9IhG0STVcrPf+=r&g;p zkhlRD){g!Gjsrc`1~gNe!q?{mMpZ=kh$>JSD`70oFcznV2Cz6$QVDX(11N*mJB9+V zHUoUsBNha--3kx-udRL;-tP-ynpoZyKrKAau_o?2eE>n8=jb1K1u9J~A0q*n5D}w+ z0&$`ZBWH{Z&qL!MTaKKj^9wQhrr{4QAER*G=L3t9{k$}5f0J2VcOeV&g|JmmR~1Mmzs455(cPq=_{c?k{YsA zA0G(JvNkXpX-9cXWI*e=M&Ee_oKEux;v<0x)m{*%@(Oqhj1CHDgq}YDyrAVnoDA;rYZM06l2=#2`5Q0X|yZIMv4kn0B7mMuEe>11t>ZEjZRd zivfl_7he(Jgm^ASHx)OS8lH`jPK9qZA{Qf`zzkZB0|BZ5#PWN*RS~mQOwIX`tnavjhAa3H)A+AK+$kJcq7wV!;;#&&HSnP7rx6 z#ujk9dY+5-QQJ%9t3FG62MY0<@lFCDXvQ7ZVO`pK0F6V9IFtxhvqt!_?nHnRusfDJ zmNwR!62kidEDCnK6s$J|e~Mq>OVFcX8qW7>1IRu2Vh?lJ@VHQ4 zEXzUpJLCfJ*54}@h3?9xLAO-sSUuQr*UQDOlX9^zAkwR}psAin$|LZVCz!Et`)7_oqlmNwTMHyte)@QW_I zg+HwwSPL4X46Fbi018d+TXKw8AfBFF0IYxtP7n*aj*$z1;f@y!PQVU$Js|aa#RBp4 zpuahw`==5MFpuARHqfaOa!&JCBwR#M2ZI zPDefvPftKdGy}7dzz@>#@_|bv+vDN@2re4&M~aPb*cn7Zp+$*Vvqu8=}{}*LjRW&qU=v!lr!h zHKjRl72-R#5xCJnArF%Ts8ti42fl^gjgK9InrRQOcQm zy%}z?u=MASK8@!U=LudvHQ%3`MZM>WlASxu9@##EZvZ}i(tR<@fbQLFqYuu9ne~9} zavEK?mnEBcNC93Ku`tZYdoL&!>B{9o8tk6Gy_jWpapNoQ34?ZP2T%mq!o&eU;7sD_ z@rGC`-_IN#B^h$wkfWr8TIgu(FLL=1;@5XFho{FILWh7I=~FO=APF(rq6W5-7hyWJrwLcpy&ucd1={6f9AK;2Z}FM-Bvn6_eUpTbR`$R1L*xwo?|K`#WK}<1D`f1i|07&B@p@Ni0aG^# z0z%pqe{Sla-dSnx?+id)@wlJ8Ca7O8U@3Ydo23&5vRN3}Nz8b^r%JDTZAD^~w85*S zLrc=E)PA`r3Qmz^nGK?PnGMFSF|+*4I~>D(=_#M!qdK2&!7u@TR{ExeeWR`_#<%6w znYHX^d@F&$&TTw9sP&#O;uom4!3^7Q3bBc7Cw|VF$tF^3IpeYaSSszFi7?q)%JD&a z7JhsRBF>@6l&P#xz^3)msq7(MDh=jGh=aJL5;2SR568fGhp|qopU!sDo0ae`?HbOA z4o$KbyfK+OCF74{ihp!!MoB-d9kqX<=rFo;Q3bzMV20GhT zmal_#&Mdh+{=wg}%0Q(SU8S-m7(YP(VjgFBO`vskxEy%+sL~2P?d!zfx-#yx5#p-> zK7&vTRtG~0coug6hFsNtSTI7DgdK;LaJ5q6_XQL`*V%ggeqRVSHBOJO#RCskPfN%d z!n@$w@%6xrCVbIQbEj#N%efVTW`wYg+aAo4$JNcASBE(7uA?HRD@V>NV)1GF`xtM0 z4gXTb)PK4E{R7eTgAByb{*gG%fhawlfgt%KYktZ1j{)u5f?c!T-CF1)W>fVc_7CIc zj1q$$kS*{Xu)i{6w8H@`6G^NeySk{E{hTGUbiiZ=72n6+P~UE4YfT0xVnCmrC??Tm ztC`EpvTpF;>#_IsFDC7}Le~{%TUF;8_7W5_3JcbkVC~U;hS1}*E5Sc@I3Yb zunBBzWK)Kkb20l2_#t1_U#^uLKyPef4%K}%dj*#ZsOlTl2r717Q+ zv1l4LGf^FQ6Z-)7WL1AF`_`1~!Xm%QnP{&~9dS4Nxsc4@g#-(y57wf_ms7E1GFsWQ z>R;|-)}$m>>ZhA+td_QJgI81g11x>><3MV+XEB4i>LGR=Dj!7q`dF!2-pk%WZZPfJ z$#Q6`%9<&C7i&~sew2A|y^Qufg;36iwlgC=^Awuh@&wz8>*aL$F2oYpxC14I?q=ta zV+TuBhds^iH5m}I^Xt4sYtvQpFW6Tsot1FTCP`O?k591_T@|>SV&fIFe0p7U2rnx* z;2ORGVf$fPW~5h}i($x*jVO&<;8r+smMI?I6qa|~e!p%Kme)}ZQk61~FC8yBcS_wn zxzim-6fr!35ZmzhUxxqy!Lxxbc(yYbJ6RhJp9A|B84h*)`cMU7ZCT~zm9XyJwD9CD z%sZ}ycwp!y0wQW$X*&J^!>EKweuCFIt-b}g+ZTF8-G!z>Q=n%8Ml0VCUfI^t+SXEj zTKWF*h!33;@pY+Zxw(jLnu>s|4ZqV{DDT=7EA!vA__yBh*ohyO5 zCgM{Law23W59W<)lVBQo%*Wv2i^acNfpF{vO1x~s?hC=hmIt(0>MPsYI@<$F!lPVq zt4qxibCi=mmRr(Phj{BpVvNPu>XuRcy#{aD>oI0T+b>B=<*|YjUT*=J_W6Y3u4Auh zF@kkkZ2$i?u+yah&v0#;W3e#e@D#svcH8Qes}e94zlM0{t2@KTUtI;O*%hJVuP$#} z(Rudq7uSaaiArk`f`4VF+!m3wlCd}SU*iRjy9A(*U1^19Xe$Cnz-ZEzp#^V*Pr$zp zaa6d~G?cauEzadJw&x^(DHbiAL*EgI{PzLO_I^e#tyq!e(YJ;X;rc&-Hw1{+;4R+H znD;EbT4c%-kANK2&P=saRYV+OjDs8PS)H3*It<-OcsyZ|lmi%JE{}>lvlibfsGTlX z;G;Hqo7>HL`t^&pp*;=OuKyLV!BJTMq#rxON&ja~F=9WK(g(jmi0)=)(f^ys#eR~> zrlQ#;Im}68jh2nH>3Vyz^=M@CpUgZg%?u@U%M&GL+Ht1|p=oQ)McFc}$fwVer`OH` zlGCJpHeScZLjv$K<0T`iK|R(TPd#D;5i;{>5shOBqDDHPiv8CYG8U?a;g77>e|rsQ zoRmuZaEsWW%`5GBvv>f#U1UoC{-rzkbe3+7xcPZjX&9l`k*q4uv%UDTPt`>mVCr}o zpQAx))eCHi#ZUuzcp`!Yu`|`L-he4)GK2KYT&n&pjk9x8&;J;6)B*3ZX(j{A*e}5c z9;^tHu;pW>FvCc_?g{PTa`o@OWl0buotr;lVZ&%d=&#la(!sxgx4QomHXn>vH3hm; zCaGu5NO^^+xnHt>8Vt31-OaG5M3ubc5bFJ>)kKGy4HjzXP8p^?{%7_r+CAz=LlHRd zku&=XUTkI~Hl2r0Zf-lT|Jo4xqC0gE7Z~Ax2OrVKmefntE5Byz@MZ+#Hl++#o&R8a zEdm>(9=IVZKZ(*GOgg}2zOl4N$ec?>t6?s>`@YOM>N8eh0-ma++JzZ&)icwD`;Z$) zwapnXse3a7CvxM}L*cAV_}=BM8I|hpTp`137>|#JogHhAI8eCA%<6iZ#_4qjVE7mB zpNU=_C{zh-^y8(%3X=gb-M48ERMWvZh{%IzX{q$nN}Ow`hCs|Va5F`!d$gyZvTDAGfmMMlhLli{%a?;-auE)6IKbV{_!&e0Zo`qMFIFcz(})F zZC_uy7!krANx44bK4e8H1<=v$8tH${TrUJtlqr9q9@5~ zDTd3yw(Vk+E_l+S`SI)Mn;DilbZbP&R=;`DQp?Qn+vhf*3OpD52z3Pa?mRv8^$zOc zUy(cGEwPFReA44H;4E2yPnYd1i!%vsOw@LXSVX&DfjIM{2KXz~jjhSZ1FeW4U;7dK5{kya zE&YoxQcX4T&~-ymV)W?smGV06KTd`!m2&&ar7Jok<-=eT;6+xED|tAD808P*0^%<1 zc|goAYA%bpM)7Zu#*9+kiH|pr9934PezQP0q&G|hjAr#uQnHGPy;{H*KeF-J538W( zqhHEb*qp~h!Z3pYpDVj1=h))c0;K1m#F@17ZTKLje^~q@_1RWoj1k}V_)sSpbsqwT z-ZWmBH&d^>LBlDro*tQLM@W(%Xs;Dvba8AocSv}cQp)N|GdQ6{kY_pB52 znGc7h@Lxa&p)~cQ z47KZ8p$n+DWz(WYpsCSEjm49lLRm6fjDZT$)?Zmis$(w}b{ikxBs`)w%+l+&q-tf= zgEt6iK$M}!ZxXiY4YP3r-?rDV*$qO;<6DGpKrSMWZxuG^4Rdhwf+IJNr2@0nv3Cp4 z08Gxt8_xj}wqC1Vf1l8fx?5HKL&7&u87z0w152$Vc+j;>b#||?9XD6dfd>)Y2;p+F zRR5#GFHx+`xA`8Hq2@jze2QGVh!8hImYV+);rB@f)&Z*&>ewX|sHMB1AT`X>>mG(Q zn83f3cacF>FMnPzC5dd+?xeX~S!{~k$5@%^H!lhUli4yfvXgoirf(r{6+XhyuqZuE zeeET|0Ts&{+V_|A3n~4L)Me_SUkMT#xt0#PZ5OGRy(OHB+&Vf~ZCj_F_pWd$a_ec| zTw5;f+Mha!>POjzsXhBK&W1Dey7v{92=({xROhY02O4pQGm+wbPufl+%3Q@_)w9wRBptL6iLh?~tWwpn`FF z%d{1uunbhtv~$T+(ePo%VrA4&>_Z&AYZ5vc5tZA(T}mgi%yYB_valIVyT@MIHYN3>S?-*!ROuBKQuTaMqWSxi;zeDH@LVi1h!~0-=aa+>I?|GepCQC1>Ye z^C&l6G<;%=vWF>)ES5(mop~q~KbZy=sa!V~B9KAaXa9 zmeanAk{lHOtWna6M+mP8R&EoHrQqNQYnW2nz9>0QD;*=e1S;I@E{6xz+N1&6unEFr zc=L_4eVOnT*_S~!*xM&%zE(d|xSkien>;mCe_v8FwZwt=TKJSW?ZIkc0?NBNSW1pE zAxX;!2_K<0TeR&cBj=l7CJz`eB9M58HtHo!kW~LxJh|w$s5^l7?%e*9trF>vlRgSLmhny-diJ zrGRMIu!@!9qiySjVl&s$QtgK<6rE5Qgg$J*e%Io!M(*JD0jprScRrFdTQqzWVa=?T zoj9rwCaX{|rc7W0-mUoFrJcSZ#UjuNF8MTDV(zAceDdHkgt!N9KyUEe4jMZnX|Oiz zM&U91x`$d@l3&qYyG1C*uX}gZrmT(1a4p+~aS);I(>4Z@t2qrt-On)z@h?1gKTbuH zE;!@W58W?}$KwwW=N6rtUxH^BWW<5m`W9h1vL7VZYGl$8jvbQrd^iY;y=5}6{7{p>!*xq9jUGfOR6CQg(up7BnH184GI5DxO z)-zswNnp)=RP%36?ZRnYlEtI?dC@Mo$)wDt6Hc)&?SIUepC>5y)7`I>G#BanNqJUcu`odzx8pgbwYZc{?=FZw?09R^7OmfUub{y zZ81~7b2m1Z;t;L!4FN4YCA=p}HE58+8k8uoF?tN&J?B(<@41!Ucb-bKG7u%P=@=KUbi=F}%pGKKF}Wv5Rkr(uF82n>7SmgfTOWB5Tdl_`aao#I(PmbC!vc-Z?K) z>mFmN<|`JT7r2+SpKHVnjw*jtL@nDTp5(cg>0nLjt=iUkB0@~`RK>qj^OL{;iu;@N ziS~1u7$-?{M8gp;8}zO_N^4bFG3<>P#mG{uL{Fhr5+Wyw^_B;Y1b_fmzlU@v~jmF3m$rD z&do#NcI$jSt(A4`dpviXHtsco$*&OK7Z5{g}`I{39Tk?coO z=h2$cViRR75F2RQa|i@=tP!Y9^+qvOJ1|DP6!ZGVoZ0kzxrlJj2JRg?_;+cN*4-yQ z&1s#l5Z~vxcX7xe_K+p`^8PfdeH5P58~h^29o=<yeVh1+zf0NtM)D%#GD9ds}Q2 zjNkg1-5tV{U}O^yW7rqWi@PF|?oX;+ zLy!s1$QZZMLSx<$?Vx0dSh=xe8!Q!`Q6p{E7^$b;{y6PqrBu#Q%ctT9s!WlV zXy5rHndkX<+CEakd1D_5=Zy%lr~WKnLfguuYOOv=>cZ9-J^?uN(p{{%&>msab^4X` zi?y{wq$d1I(l+*$z=YZ>qoFbiziW&XFiXpzRq6pcuB5u>u-D%EH?F64WU7>dVv~2x zkggP^<=AmBO7FNMRo``J&^^TpMm`nAXJ~(Zj!)#Y)XSw$@nk3J{Su$8b-PM>E4q0y zTxJj^8ZN_ba)s#u?+VfIR=xg8h{nCO*1M#UyqV9sDeWStt~~k$Y#{B{J%@RD7pJIody~zByG?UGMygMB$MZw!UFZA}lf0+OW^1$54|}+W4V>b?cM_d7t-^!<~9J?R_TAL?;?S z>D%TQJyc;eI<#)zNUxcsi?MJ|)1blp5#_>kmG@~UXNg@fU+*$1+Rqo_c{c+-}tHt3TO37W!+0bB((ND?flm-(X189-wtRo0h6|%{OMt zQln^iv7S(LZ|zE_(JYwxK@Uw|gY}~q%Ve;)d|!MAlmB}e=1U$U%!~L7=ya*EMw{Q) zxCx9ZE2#<$pu#wee(ap&q{pj_P)!>6AsW};Xy&w-3gb6M3qK4vU_f+$aWA$0$0o+tCS9{Vex2fS7PFIG9H-It?eS~q*4yKA zX)2!(pT}Y`ops`jCxhpn76T?^?e_R&4rm|ISNa%Omq9<9qrLID{lZm!x`dB?0*u}G z+;NF*#leWkg>#a#%FUv8UX#b&a5^&K+={T^<56?Z1vQH< zY?y=NL58p!SlX~8KLCKiQv8f+5A(V%?WzCIOqT-1K~ir+FwO%jWvhlR=*flmU6E;N z>mMk_!h5dh9R7U1nX<2o$wM9Ef;7HJ$)eUBjzn5gq^uK45M;oVt5h&59E$Hqs>&C;Y4*d$*Yp)+l;+r+ z3RN(6Nkgc)H2t`?_ATRbCdKljsmdvO$@#YNaqa67##m787;=pgrx5lubG2=|Q)Nzj zX{^x)dD6h8LPHkA$q$TF@$iw(40g<;RivBlWg@-?hMK6h()D zbD-c<6g)u1jYpJ7!7~>dw}bDDL$On}u`_aOIBm3KBK=j@#U^wwGSS@4AeY-pRmqLg_Y?K2${mj-<3V`!2oNQ&9fwb9%h` z*L&Qr_qc|ZKcja?;QYm&)!W#o{i3&Lq1YPjHg}OF`N7BI|m| zz)4|S<@fRfvsEk*NBXzdLf(@Wy8$?Id< zh^GzwI=b%?xwDo%!t@Sa+hJiShwD{z&5lr{Cf4U;W$o>urbjp{e|4mBzelJOH0~k! ziUDdI8mF*3`%qt7f=oT{%`j5Rw{kH|X2)KeQA~B;$~o}tg~SlRa_XmBzLjru2Y|;3 z`~hI^0jv`-GHO_i47eYI;p1QCXvzL%YdQOu`6D>#ci+hsc?gyOBX;H<$=2@Z3Jzb! zNN^lnVrzNO6+W1ssQ4r1aLiPz`2t(2)LX&oL5?nRD*b+On$;IQBnwt}VSLtR(t;IF zU*V%GoS&^Ykpr$k1OgmbU38j7#qB&zG12Pna@YLQu5Ne(mmxxj+Y^D)3r6nEZ*UbL z>`$-|!Rui=tDAoDb&F{GpUqiu{)lA+Z#V{V+HedV`igwLPqkqUgZ_U3fP(ACCdHC_ zqUkGzzwXkBYCKNlbxsqxqOG1}x)O60D(4;!s1c@^K87g0krv-!yGE=2CVm@FYZuwz z#c;jUl}fL%HLyXyp6svjy~O}1D1V@KVEnUf1R& z@mV~dZ6o3lcmjd2aBrC_2!{+8#dvK$u@b^$e~X_=Q zNUt^9{wVl7rP@#BwiJXSSRVg3cFPT9KgRp1?+x+y!mj8+ltg>0DTD0BEb{?B>T8a1;5WKfaPlL#=dpC9o&3YG`*fB<1A+4>n z4X4)kV&A07YFjJS-!5H5U*zMM>$xp9bRa&o_9uQ0Z8O`t^DeNB0l4;)+2#cF+^Mzk znN(S5v(SctHXJkE8lSFom}R2C_^jOSAeFOl<{_8q1mlHN6eSv?m`FZ0CBmdeNcH;AJy`;e+hLq}a;U*YWAot@)G&RAEt z=tM7A4P09ShqC9@Q*CWbp`% z=li>s76%IYROAPjEm=~ts9|=<-%wMG3F$8NlzTzl?bhBG!P|B*Zw>S<>r%qa7)Gy2`PGGzpLAz~ZY46f_ znW}GyU-vK5`)0E3WKFn*4y>+P64!W<0tt-_}27=t8Y1qKD5nP-ukTcqkIDqM-Z)Bt&C3;DxgmB&P)Xhz{SeUg5 z0IEMf$tInTWOee2W3M}dMCr|?e3y2p*=n}_G{B})TV8T%|e|tXJo;TqTx!&|K zmft%xu>tu!e<%6tP5Y_W0AM56FE&XuWwGf#ZNo~_QX|jbjSvyJ4ywM)WYL^!O|O|m zufgC&HH&5Hr9xN#;EMBAQ$sapK^t`!DsV4VEyNP{!@lfTtzfEZ7zHjF( znN?i)`EHfk<(o{GK%H^FCf=xDdH{C#`X3K!;x_${hcxj{{f`z+yhs0|702lM9}hz@ zr2nx~6Cct4ctjH))Bku>6L;%>?9#-&`X3sdXfa`ddRp&?$29RdJ^T0CCEu7A@LK)L zrt7hcJ+6rdkT390WRv5dsYEL~YrX!!e*KnH+Seyc zY(KU~Tl1Y|8KT~gGs${sdIs4b{jbX!m559jS*EOb8?d@l>b2+WwH>Rg~>1n6{deTXw zoTl0TZhBY18PxQ{*+=Qv5iU7={E)2u^n*!a=TP;Bv*$dzKqf(XcIXA8V0m6pPyt*~ zI9E7C`5l3KP8)PG`w3p#qsSLXM(iDr7)F2;D&-qjtC-ZSt61$O(iTT*Ed6x1Y-jxQ z`70c1x@cIhQ*$L1)`JRMHu)8vdx7@3V{izSB;ye3Me4IfuF$H|gTf z^YK7XePf;(Q4*Ksq$4D(|F5ueuAFZ+Mt%C=VaYHH7@Q2?IpJg)Qh;j_1aL$E01|${ z+%Wl!`B;_%6Qm3{7G7Lu&?s;ta(VzU^@Xb7alw{_=g2*gO9fUgkDMf#Ne>|qE$l%MvP@dtAs&p#4G(k zVRd0V*_CMBFvJY}g2lPRV6zK)6;TP@D>qnz3!nscp{-NlXbXgqATEGRZ~ z6b4?w@OOOJaDk*|G6IQE9NzSe^a*0AvcHL?qBeH{$30RWk@%(g~_ z7~okgYg0&X6I6teiZE<{&R_C z9Eil0l1Qto60$4>b}YcfP{`Q}Y5Oylu2MemxC?03OEEdr?@3!1+O{U!6jnCGNxCI- zxOH`kFnTZ;KpEdX{7@%j_)xxsy7CE~EUq?R6n{my>dM6MnA=RWcW`bO8gnqGNAj7q zEGcFlCMXeaOQm5MX)#$*RB*U_Ba&9sBXJ4D*HAuyW@y7>*_~j|7t_^TIDHnjJ2Md` zX>3)(P`b37vl)w^z#I+B(8sdV*-fSlw2)Z~M)y>Ck!KDS!fjmdR1xuw?DXaJ@Xmhd z8B4+Se;4VYClbeR9Any?W;Pl>FbEs@4n`+KJI=11e$3Aw z#v9)vmlZ+bhD?(m}_!4Z)()!WV(7%vW2#FNi<(Kku zQ=OU%uY$m@4uSnXxfs3giuC?PlVcLXCoWIVwJY{Ef4*q1`YWRGo1#p&uz>1c$jG8< z=!Z>k?E?jTNEK`31zP=8@(reJ-jHSB79lzJ^HhP#V1h53KCHx~9kr zd06ZnxJj-s)&_k3+On`|`r>T2rz8Zt7&oGKVlgdq0xYD);|d^>nAZmgKP;l*o2N76 z>FZMEB}}->V^LG#@(t#UlP)|^#y~%va4g*0Nlq7iB_3C}by{LiU?*Xr1o9x8E^Y;} zic1l6n$0m?yMvQ9zY8AVz#nBth@XbPoR&tPZ;=zNPM=%x1l^_Op_*EXzeRqOcb9~l zM`qIl>GBS(`Bqur;u)fj6ZocO`Wjv03M2S2+%Lg_`)rj9d6%1QPg~WLEX}2OKQkjn&vRs^IZk@`F0vp1QXh$jgmrdydox0=+>Mh zI&ej549%J(y2DGpPRNX|dEI>_C2lthWbH*P#IiC(CND35!ZC;B)Ree5%-HCS%m=C< z+?tm`onMnpbnUpVSDl;8kb;EUc*Io*xg35qq$%bPmPN$=Y|0EJjOxm zJ!uLJ-;tg~Yc@C#l6FtJgN~*rr&+-w+KZ#s=jE);x5ry>B53~(rc%eRw8Se5YnEJC zQ{gH0rPnE-N!{jD1|y1tY8M^RR~d7tCIW{{Or#R8zr3htqE>Ni<=* z+*x6Vp>PYZ7w6OB@fkU^b-SDx%QQb79$w+a!!lBebYBCEHUbqh@5hKh6FxX7G1{c| zrQUv_y)f_W)!)AI2-6q1`o*NBo$vArT&IoeW3tI!278c#f}GSw#$MXA4iF~B+gB(P zN3qxl0R3*u_xnpCBfennqWM+!VWUA#!N}ko5`PGP*BlDz5-lXR=s|Iqdnu(8 zQ`U^K1H3RI-rS>9I!19GT_=j#*3S`xIDvjJh4`^^urdOOAa-o;}YFrqD*k_BQWs zcr^!WX3q`fGk?QU+PWBVJ)dte`{=fZ5z^LvJA8U>xI?zF0FAbA!?4sLdVtakMvU$c zuy+0Up<@jy3Pv@sZO%Fkt{(or6%8dln1UH@wWFBg7e5PoD+t&XM&y;B*TK^F#5%Ji zWCyb8vT?Dw-TyUyg0BA(v+@@_C4B$YGJTeh5y0LhlhIITI+UKA{a^4E0el6fIDlEu zyO#9?P1`P~{VR-wai}D=;Yjn9{8s!V{nz+O9=V>hbtmJc@%dD_4v}DfE3Wb%Fcpl? zDaMWhjo>V8ETWx@{acKs{i*+mv&1mg@@t%h$0}KM*il494UVql{N5IW7(B72bNP&z zx8grxGuR9MV>Uyl9kN3@y=*CVmiwQAh3CPiq~2tF-4PFzgiil5Ly2!c0MYpttY}_n zk@9p7Lm%KkMc5JjMv;p@UajlIiBhvXyq<=rbNV zhZlu+u1=Qwxgxzlj=x)bQ1f7$=|AL7J!tgbtrGqDbw{z4MM|D)uSJkxdypJ=$d}Od zSHO4>UTNtxv5V;{y6lg(`0(qEvEc*5%)|d(p4I34EDJRK7n*&e?D$t@+>29xMX;rL zsq*H3F4BgtnbCuDh993X6lau>N8w9q;J*rwnt76bPM0S#i8KK~PQNaK_Up`|h_nlz zb3^AypS16R6i0_hX~Gr=C45a)??}b5EB+hN()xdc#Mr^vzj|9q_^qVF8D|}?=M|oh zUF*_ebhT_djYrRa^AAPJ|M$s^{~c=wXIFlewg0Nn_&>_b`Ts;190~0V|5qkmGOehc zHhvb{se@Iw$$tO8X!yT-s~Ka%xcsd+KkwIeoc|&J_@598|39+H{C@z$$UA$J$y8{+ zy3zdCB*6cOY5ZTigPiyqNn=c(%9A6G^;j8rpl8!W_ltX@O7Pc$Yyrew@>s_p}6}Sf1bO{5$kK~ z4*3+llO#`P2j-{o{f`iu4%@scEwtE;|v_jMb&5+JA(mgPkpONu0Rg( z5SapC)ILP5jA(t%HyNSt>CpP<+cCHiHs{N(e|huOzEDS1HUC^2L(Yuk+RWcT7xQoL zYkox+0}A_z#w5w14(ipQ%Adq>o^Z>wL$u*$IVD22Dst>I0Gngre-3lAlERg<62j|e zto|oN9MO$iU2cSMLI4*wZ^O;AI?>fz{wGJ^C{FXF4f+@II)X{cD^8aa) zrEMpr@tfDjWJM_T+w0R~!u2=Vq|*&dAD|8~moVMoc~k+6ro-{mY#kcKuTGnb+6j^E zhli7OC-iqm8YSzmY6brbI*iMQTAX>ZM4`rX?Yg)k4;t^P-ie8{CN4Yq0$<6$%jwTc zpW06wV0tYT!WFrP&e*l)jdBAIK%1)#TsOnj;QW`!+az7J`Sgf0 z$=kZHrfb_l36Mj3bKU;WaArp4IElxHT<=KzXnKEhI@vdy6R3^i&&zV#h@1=1>C*@p zt^Yo8T}}s?pNG>5h4y^feo~qiSD>F;GytPt$8x3{w#iF>3!XdTH=*g?$^sCHOtCxU zFFK?JnawGD^yAaFcxNqjNs$jz&zt4t5mF2?(lXOasj{0XkDfk<9?#GR74juP+JBqe zi#?%)<42WK)mFLdnc_o%X*o<-7^{tcC&|$vM*NQw<*)JF2=CbTKz1hWoeXdCud>ds zUpF}b_zi>ZGyM|IKDsNQ#aF}g^K(yHaz>lZ{rm!jDQX~2d$Ta{!MXYYu<8u?B0HH8 z?HLw5XNVh&sAv8vDW}k}6nIh`D&vdJmLuATx}9_XLux)pPpb3ltbZgopbhZxG?u`a z4fab_$E==zexNh{HzH`=!i4+60OXu6n1RDPoP7ISwTEc<)1(MfezrBo)-G!wi)E3v z1?#qzYIoS}EI<1eW{N79@Qu-oH_BV1j96EgK5caRa3PkSPm?n{Sn(O8|I5?(v-vOE z)eCXDA~bro3#aoT=EC1p*f^6BpO5Bq_9kS|UP?@->JbR0UAH!I9NqasYTCbiu=HPw z%Zz?U>Ato3CFL-n7~3BfvS7*%YS|-*;owc3{~=TU&*@iW^h=J5V-jh_)&xJzdeUaH z{gcDUN_zeEoB}#BJ~fVsO6g~xNd9(Guwc{Fn;;`bm~DjI&StSp@)Z&^^;X&OYkK#y ziS3N-+5aBC%KZGW(r#Y@D5Car%YR$D{cmo>z%|)MEHAQBpRugBe43EN4!BJ?6SqfB zy3ey}pO4S|7Hjoy%GwN@4Df;31ZneXd27Au6b*YqjB5p%jG1)n92GG@p3v>4E~K+r zoxZ>`i87O!915_<18U2@hMW}I9nYNZcF4!tZ)p=c*-4L(qMf=~j_1xM!81>NMzXt7 z>C|oV^8cfv=J`0>|1`~=vH6D`CO+YPU*}VEUQXGsF3aa^>i_%2__rX^LBGlRO9x-D zxnMWkix<-2{vyLGN60u2;WQY5KO?~>nU~li{VgZoSgj(gM(aRE%X1hlXWqP21>(-5 z^EBjK!Wo4$WM)CC27D{_RG0F#PMVv99cH_#i_!K3jw=s8IPF!YECAl6LFi07Qst>k zOl^V*-<~aURaV?Y?zy0U=Ocd)tPVbgk4uA z(xE!Rrkl#dve2~cKn(tf8TZ;hTGP1h^x!_Lhppf1guL+ADLKZ7ZzOSBV+zBAfvCnT z>dtvy`$?03&q;34aCH*v$U3TA2w#~2kIQ>FUU1O%NhT+CIt07v>fQ2eGYhqG&jMyd zIYkc#rg@%`pT~{%&Fifd@|FEmm}qa{+4V_+H|U~$tvRWwEToNYq#NlB<~7k_t;JrB02W$XVX@Jo%hDAN z7zL0XV_^!2xVUY$Pjq~!waaj(mm37%P2B_v5q2UbP-P`$k(~1K(r8YZGioN~az%0w z_?4Y%={8%zKE9IPwD2NxY6{z(_013TiFkkd`vSg$+2fro19t3@S40aSeu35z=mVR1 zW>oL5U^g(dkt*HNa6$yp++|;>d-0k31Oh#DM+0)~O~4icf|XpHZ_jIR<=JWRLrNlz zUYn@&TokHXWJkb;2D?At56-s-=PX)O6R4}h`HmgIi5A)y)X(wT{lSL0%WGi2*zqzZ zrR633@^QemC1i1SYTd$T(vL@Cu9B||sAU+uA#Csl!>N(`Sjl9ws_PZ9ijXAhb$j&D z+VDTizvoG;H>c9JNja7rI4{F%{7mz2c;YZ`j$bv)=9MzyCYfPgyWV7LgbRbRqFB+R ztLSFR@W+4|<b=bdGx?yC1{^8Xi9r-`@NyFvfFO1fdv<$ zA}|w0yrTW)+DLYvXnxz>d3pB!%tY+m+vl8>9l2YfeXk@YhFf1wCP>ApDd_k}l&2|q zZZ{%apv(+)tx}~N#+OyE42oGl$(CC)5aWrsFVppo} zi&!*!_SoP+{kSMnv&Ssc4#7N?z9=#$gg1Xt;VUm~H;96Y^ssLxpeFCvHF5 zItY4>hX%@W9N>BISkD`BRUppaGlG$ZemNH2eezLG5xQTUsbZ&oeV^#159QfL0fmNE2)-l>SqoqWNbN!=S&x z(_Ty_ZLpg6D1ztKOcgNpkGGXF$ea;zrrQ%^?gwp{b633i8%3%FWLHM}!}j)r5+fB( zHnaF;LfMrKD)8qw=p0OMNoIT-@#1nRzU4G#vbo|+#Ptappz?O27#}SzlH6pUVa}wN z1_Lh-Xz=;8ArGOC_RCUdcneA*otj}z*X$1SJ4RmUt%><&wxIQ)Y3m#rgf@271{9n3 zAghA5JI%|q>E-4t8NP|oms(292tnsD-zGDF{kL`h484xI8dQI8paQ*iRhp+uQhxxY zMY=-6P|e1L%3VXu!$gK1vftgD7!HA}q!2=BUMN=Npvb+Ib;5;wrjiqQg!8BUdR%~vSG z(3=A4AknZ>XCTolz$pwU!r&`cssqvMy032JRZN^5i0=hjRi?Qgrwy2D-h*}w+58s~ zeK5oPo(Z12<{wPE)|%5L2}twTzy&2rd|BG6%gyVIz#Mwoz&{;vJlZ%$k=Hp@2%k4} zv_p?rX{Q69;*1=rzRl z_CAYDQ|2jFT6Lqts*SwK+=^*58gBq-&<^t?dU%I<7&SlBxf30@)f`8S*8x=itv^`$ zYggQ2ev7%(2otD!t9ct-c+E4|7_;4t%RU>dR zYD{VLoQWA`+o`(Bk{7NVB+}RmEEV*{cuPW0=J)6HAli2+i%ZOc&cm~^6mFA{{gGuBmauzZ4%=Z)#p- zp6Yn~{^M5^D@(n#-m(&4+rtaB1Yh`ZdOi4-LKpbm2qmlcvW3jJe)J?}tItpw=1ySN zc#5LlsX)@-@xtPX{-DbC*P8#jzHXtzU@>5P446bm$vsV z^DWpN8H910?=goZ>`Shl(SA>&_Q^fwG)$f;@U=DrAQ=GjH^P^%Q0cGLJz_oy1~@I; zSsexB?T%@ZsNKKIJPtC^rZWg9+l9Doy*XQ-Zug2>xW3jZIRN1K1 zc50I@(X9+oEdFdLZ3csk!(2_I}uii=+I+nIMq zZ{=a}d)~BM9U~g1I!e$VaX9^iIx+qo^=CT;_FHFqg@33m~QJPE)FDU*r~{Bzsr`}mQ>&1A)k>sp z6)1*6vDyb|p~}>&c`7`kgj^NTWP}`*E$G>*2Wc18jkJ@>bhn8rJIJ-DJ&^KhvBA(N zaB3k=hX1Y>ApJ;%ti->s<|2JhMTPlyRR_|OsvYUuY8Rw$sBl5!_p6ym|Ey*p-KS!= z$?s58kZx6>faf=>h~CIwt1?@x^=cf_Yt&ezm#JV*e3QzY+f)^*OulioiUpBhrb0gA z>r^Asxhe*V52_#mex@oQovbogsIe-CbhwIc%c;Wvwsd|8L&vxP|BUx4Jj`AA-yhyLgG&%vL6z7WYDA@Xs5s6p~Dh>p7h zuAtm4KyTyL%|xP3L$YcLk|itv$lQrYE}Vd*2KKj{AK-S}%rQu&k3up9W6Dj!kaA-& zqTGlJkX$eb$v_Mz=fgO1UJM}DSXzP9)f-8VUP!uQ47oxKAy#9+U!%J3t-EFB3OL&L>@ku@yFI2!7ckj%j-8YV*bWf;R2l~EWr!w8I;VK~Oj zFbIQXD8pD8V4-6$WMHHWi5MtD0w)RE42!sU{4ma-;=`6M=F9js{0e>vzaadeyvWF9 zaw!O+IqYpm-$vm({yqK}{|5gG!Ua9U@8uuoAK@S5@8NIfZ{aud&HOshYNf&0gkh7s zP3RgarwM2zQl}>Lkd)MfE|N@5=p*rOP3R=?qbBr{_;Tzsbqoh82A1WFeVH=(=4 zKR2Pj#6LEnuf)fjazFs$!%gTU@u4R4l6ZfU9qFA-=qK^^rp`#WG@-l1a1;7VY;HnV ziR+utSK{SO=qK@#CUlgzv?&4U+$K=ESlfiY5~nv=kxpttmx<$=&}ZWCCUlxOs0qC$ z_HRPBiG7>UZ;fK_CiI->YC>;`-J8&VVmB4!EvBn$kSZz&Tr{d6Zs9w1CDK#sMMytX zLEpmr>QbbK)g?&(s4haPspu!+5f$_-+^5ze-L8U|h3nNI(hX`g(reV&NUu^s)k5Q1 zbq4-f#i(0Yu7bRUCF*3Pi_}R-FH}M1!fbUMM$}*&(ligLuL(28=xxHwjXj!bkQOxI z<;Lu$0Mg7R6k$wjLJ`J2noyAROw%}| zk2hfkNjsY`g`@|XMk2ktX#~5 zHUs6RQ1QnqpkHCmjdRuUP{3lN8WluRjrxVqt`lZhxd`*=tsl%UQ`a}lv&r-mw#CCf zHUqU;Sg`3W1p!DtGa~>ALdf(yZq6mEqy)5er_A@7q)C8bXkhQ0Pc7@Mu~c-}+)8Fs zLJWtst#rDfn?kPoi&hlVc5Dw-+n)jpG(F=`e`*_@e@{rr-uJ!mzN&`xn zAJ90#=nLQ&9;oq#Ts5J9*Hs!Sclv7S%a6=OXknn#@A7-#{T>K1kQ*@L|YHk!LVUCAEHU$Y17CLbYBmg3y)dW$|QUD=AVZd4maxN?Nd&)`~xKqGWUS6vNk^-T$yHkG3HmU>Yiml-<6a;;1sT{98XF2>|L@0)sR@*LLggjc-RlowKYuQd z0so1<@7(!}{I)9~h%@N{g#eOWFNsm}8N}QeK=IM1eD1SJj{&(yA3A4(Dww^|OXaoY z0C^8Wrxfs(p-||5+pa_>Tiaa2L~Og#F?SpNK-nl`GaLF2_CcrAvNJGeZ5e3SkJE;r z4>XK#IXij47m!r7wfGH=UR#cT*(wgK+S(v~`&u`fr=$$aPW1OE)Y`|QZ?g0-0fKNKgY?>PwyyzuRQVD2)2Xmo@7LcH!U9VB6{Qb6 zLhkFNWKStOmqWC(Q!Fm+a!D}cXX1_1hZUqY;He3g0vZPES%fWv8<(BSP3RJflF{Am zcZa++zFJqXq&8FwCXK18b8;vn%g&v~G{dT-XJOd^D}v%Oghdm)zN8j(fF%uj2BAR{ zf6%4OWwa(ZP!_H--gx$n$bn!~D`gs8Yu zYx%^?ahtADZq~NilnafJHRx@@dUl-Cuh)1dsHrs37yp_g5KYl=D*M@E36DpXWD z@m_|RdK@-Cc$Oe}F?l;F&+)=Vcjqd=tTzakXkxMQlVB0n=x|Jo(!%?m8=#GGDnRWP z8a2n^^!}VSxK#PlD9yle(>&rTl=bvXAH}5z>Rol}bkXoaKtm=%j0o7Y3Qe@TLfN8i ztyF?|!=-c-idSvwK;>Q&FI=vPBTyVKT%n0$^gpiD#0g53nT1Jw9w#2Fl<5RTvrIiI z%tmeJOl3IUdKuojj%F-SCTr8Hl@lfjVfXHyz;3SAw#`vSq4n!;FsQR|Vkhdkh=ZcM zOTFhSe>4hLapveXX#X$AIsv@)mZ0}`Cw^SU&gGtPEg>VIP+f@bt zLeME?EmzWV_2t`H9$+GkTh}@Jftm#{R*aR78Ipbzp z`;A;1=zHXg#^>F+k1Gju15gRkJ6r0yX(D>h;a zTa>@brEi{^2P7pop202J0H<_xB7X(I$D)iR3OofpZ{!WZi&x~8mHQDTI8=-ao9M%rRKn+u9{xSX0i`r$y1 zgI$a@&Rw*$W=R9NF0`dPYH%U}#24KT1GfSKsu(y5`p<%5cDTWw%VuR1i0odPV2kz8 zfWtV_9q~e{#RSkeCSzzP7pEIJ5*DSq5t1`Kljbc=2gc8$^dcNVs4=!II&~n&J~w)v zq8q08BSt4BE>9Gjr=%omhT`@{a>`4+ksRzsA~`;9)KsP18#QO~`O55rM!?Pm(D&{V z(5>6YOjqdTpB(m_yvc?3$rI<6b{iI)l2b5u?y80r_Gt_93Oa}^KtE(Sy3k!*LUy?F zx>SlAZ#L8C`M}PpzCE^pbyvK&ux@z`IX-~dZ}_onTRVb=$gqAo)A6eLf zAHQ^PEVO@cmAciy4}h)ItvI&WAa7Wt21UcxYDQAJPT>ad>_CKPuhMyTINV+JgO}k5 zdzG?GUmvnSYyhssYKq-8wmj??R#m{8LIiS?Y6gJbZ3<_kV-6*o*5trw=&xfDP`q+= zP8X`kMfk6^(?l~J|0qsk3vV`EmCO5R_N$5{LAfI|-`x;l{F^Wh9BH%{u!gi}j#7@W zh05*V(4?)oOld-xw<{HyHfYLX2})PtMr}{Lr3wKnALgrQ_h-r~wszd4ty#}*-MT>m zs`ehGUV=7OxLM;mXME4y{glE>Oar%t{CSA{VV|0Dg4cd}M&UUL3S8k9+Bn>XD6!8e z&4R?H#;w|(TvkNOwv2&fzc2$Z*hT54O$RKstgPF#J^AeF&xMxX(Wyg9yfhHGTM!_2}av$`z!e)H$pwpM1290_Q*Y2Rc=PMKnE_%c%E9eC@gk>a4+rMqGV||{h(YZQ1gq5 zYx8~u1`7t^chtHqV?alzJ$k_6#IuJKJAL_z0s#b{%|(`b^^T*h)0G%H%3C_Ke%r=x zej&lFcSkhRwrb!`aVpM`mvM4Sxr*LD(V$Z>C|tPzg^*f<-lAb0y|sdM)b02_5Fx3S zTO4r+#GAVnF~Fjs?IrDSiKPclh##bVm**l{s>d=BLcl}h=!bAZZGq;8QQxnvCe7!w zAk?$aLW?KKb7*aX)uLV0$8wEe67HzFFXITc|W6vR7ials_u z$g@VUy{% z_!QyE=0WN_oK?oMJEzc>>(b>o%w`m|dt0T70H*tN2}NfIkA(-Ra}eqT_pVYFXj=o8 z*{If|xcB#(7_u-i39#gI^go^mb4MLo*9DfNn4^E_n7h5%SvsJq!7jim%CYAw1*S2B zWb{B&7KDtAaAl#=8g`d59m!0XHef+=1rVgKu}H~KaKH{xrwl3zI=BUhx7%Jzw8&*8 zk!)(r1~B*dHxiQ)a09d*B~bT3?dF5v@27*;nll<9HJ3xl0dG+V!q6d>dGRRzVQ*w# zNc!v{SE)zA;_obFLRD!gHlpkz-U!~!wgtE)h%dGUxZ=T;8mM#-=M4|ndP31WwguA- zG@z}*wm1Ou0J=fJkrJ4pgR(Y@roLD-@Cqnq0ELS8dQcY2@L{XWR9LLxdPU2jL@$H* z&f1CpP@ytt)8YANO-w>PSrc&?PqUpTyMmrbqke^^Wb_29ncfqC){gWi*36UYJJs~Ne?aKO}4X|(AI(! zlcJAGTPtcx*_c2EY_F2uo?qnZjZuD){mc48nTPIY+$cH(=V=0?M3=`6(7D@-lvEl$ z4`B6hl4>VJTXM9L9YfGo|8qt_=@^3cx}DYkzsS{lKbm_+-@9Q11Vs@e7^t)(WXF_x zu-0Lf#lO(HcKqdZd+58X%;}aguhMpvj@~DwC;9-5jvj8CD;QG*n|TdqqJTe`Dy7n2 zuH%!3g6so~bR!dkQH_461JyapQ`l@_B!f(LWBUw(%J=Nq;$kSgcAEFFoKMd2RvQf; zkz}O@T?p#)`qem*G3O}Rr3jlbVNe9yXnEVVU-Il zj9piB(D7$2sdVpKa*{WHZm4*Lvv`H8c%`#=C4BtbOlbhZy8{0gEk&flmG~cjM2ba( z7eDzuNl6)JM*3Uc4iydnVr&6kRR9h6f~_;6Sm^9=@t){{>4Nx?qdAUvxj-e~z8^3Cp4{h8;;Hdl-`Y{A<-(yV~sja<} zJ$_(!Qmix{x4i&Dk|&1`yk*qt@3AxsbWL-tUfPRvb93w@ZSMURLM-|ZlJv}%NbQyU zS1MkfxLj*&wWx@h`w6dKd71t&H*uqO=OdOG0@aPM=$ZSqH6tt!^V**txAf)`4P2sF zz9Q-qps#QTXzeUGbKf+^avhDSuvpHr>XRYeTJq@VGVE#BE==rxwsl`Nt-J>j6VvJw zomBUEtc70gYau%Frc%v~BYz|O$biZ1j_!N|eJk=pd9V^YP-pJ!(0zts7iXWlsKGs8 z@7k&=Tgnxw-NqOXhzE*;3as)*-yFM$)^3Rv>BQw$qcH@DP&h~jpRo3%qnB8_aphXk zZtL9h_gbszZ;>8~yp4JT!GL6-*J zxN(45kHbYk&_{D#y?&xvCmQbR!E&Z(-@j}TARoMf+P_AQi6$16#Db_K!t2_JV-`C{ z#qV0CYmG-Om%<&tI6s|{F4N;w&;}Q24o!#+pek(*T3~p*nag`qOloq z)Z9BUDg5BgqbM%M`n`7GuNJoiT=vB>-%(_uKx>yK>E-2cC$yT6EL$MI7Fn_LF$gEA z<}*ux4qTkjMf>10%Tk{DCW8pB{?c+0D?h}&qt%?U%m-=Slxz)ecw;1eKG|f__A6G8 z#JxxHPg{Cx{o<|T6=^BDYB1}n#SujtIjkSB65idFX*G&0?49|Xpm(r|nWQe$QE2ek zQg%uy(SZqYvUp{zbt)`5gpaPBuQrH=Ez>mI@P!VbEqtsMjk6x)Nc31o(R7b>67&Av z>9Km)zaO)IYXj7!)H;oKIcasN^$)BPpJ?S?>vMRh-1K z<<>W?(7WSE6uVuh=jpJ|3N@EO_>|netj(N9d%Bmk4<}+Ja*^D}8ivEdEq$zTJ@?be zgYoI~UPhdaj$5(q+ff0^uACzoZFoP9$jaNCa`5D971qIc>;Gc!&Eul1*Z<+UXJ^*? zzUQ7{QD6p8Kv5V3#2qwuWO2*V6a>s2P~0_P%61h(xw2f)$`(_>%unTp*`6u3Sa~YZ zth8L9^|a~p{tS!N>3m=3e4p3z{GLA^YJ|C$&%Rur>$=|W3rF{k{VnHFo-_;M)jS@o z#U3Q=4DLW9i;Y&w2njTqVJ1jU6b6fLr9!&51#UZzx3t< zYbx83V;QRsXx=)md?AL7e`2-{oBD1b@C`a|&gG|}OZa@tFhG#ecMN@w-`_KBU;g_C zhBn9VA4wc+nM(AivoEX3uw28QG1QVT{KOhYS-ucxN}?-#x=Q%W>k|iNFTGTW({4w^=Tzt^dT0-*_+J z$3*GdtxgvIU{owZl6%d$bY-)}PH{aV-e;?)S{4ez?<{MZrA}ba7g{pa!Zp@+yTvHb zsNIO^K6AG^lIA{X8Ne3Iu&hxdXnp#A69S#jwjj{CR>e|wS>6)Z@e)h`wN{{%J(fu< zx74x?H`O%#iHNV*H;XNS_@<%gmn@I5Czo3$8q^>VIQfgy_P zJ1ZhmS#T=T+USiL7I^JHWjTj$cGl;L=^26T+KI8D){0cT%o5HbYb|GRQ=+<;cuc&d z77JZjVCloA?6o|G>oWUvi6v6a&=UU;)LI9_PLwOKf`gWgdR|c{4P70Tg0ZAh^?Bqi z6*U{-ChK3Hmh>~tRAYUR20$O2mIkEX+%-`WHJ|ikc#>0bEP=T1UNCn?J~!$_=S9cD zxjg}9{G=W@ebCAkmOvLq0>bSeJWX;>aQdfmBYvtEkdFNpFW(VOl)KV0g}&S#;H2Ui zoG42)@2Bc~OK&9s0#|ad%#e-mwcz)(Kr(v0-s}-52st*SzkF;-=znd~k13oT)vu zbHUMTgn?UaVIaz6v52{IM-ue`yQECUGxL?m=|{ zCRbHSpHMy#{yHeo;z9qf4vhby3I2m&|NoDP_Az1PCsV!>Q*)&&5_9G628YpaNzb2N zNFT4YcBa#hS?&MTs__4@InY_lCk7QKx&r^Z@gLsdvw#0XTQ8~|QWD25;T+3d&ajE3 zOrsp26=`*hF%Mxa~%* zx?80VQQZxpZ)w201?6`@hInEG7Y|yG6DPVEdr{N22n)H#nM0{8*RmZlmo{X_i1HO$ z)rUd+(b?>a*DM_c!GlziDQ~l-BX)2tcNOnI-X&2ETCv$u#>K5igdS{54di+M+R6BC z!i@pijucmDd6Jzgw1lY%vRA$afK)oNOFfb=OZv#h1#3oH}40CP@F_9c)T2QrI?-}bPyC6<1mAf%HE zEP?F63l@L8b!`Ntt*`{+pdxLL2&awvEl##(g=Ml}NaP2gY13H9LCbK6jw)>=rIlNb z;K{37Bf3$p57JWOdW)6@_$&+mbQtfThAQ?_y=Aox5(xhfAqD-x@D99e2_o^d35dZg zo7*0pHmS>m;VYt}$1Qb_8a9+p#u&qB_#vaED*GNSY<@Ud{sHg=$X1|(5-Logx}^U7 z7AH{scLAZ=zWu#k@)XIje9D`bB=8vlR6JCZ(#_hNs_((ZcU((LuLn+0tlCmTf4Ppxhd6H?I3GpljFY;j)ETvzM0QFu3od0(*}RkPPmS^5ZCZER{1 zD?eqOCa~EbTdEua>WhHok49c<~uW}U?;gWSR5 zuT&c=cK;>$SiDY5P%m zzOD=HUL7K`{xR0&M!gRQ!)HtRbC=mpuQdnF0}zNdjg@3tVa_dEZ{5IRQmm(O%1S5C zht{5~Ppb71q>nT83n>im*#tIul+_{d9vpxV!c8|>;@O$^tvv-cbFj4$orFW#uCX^B zu0bw8)NOv#39!E1XeM}8X-`P!J12JyIITW#7ow`WSnTV z@zFh;q|qVKboH8)O%*SBa;Rdmu0Q3z?3u)--D2H{<{3fd=gh%0CEq%MEtq8OjCvZi zs(sUV5%vovZN?7052nsjBER5|kEn%5hISN*9}3yn3T;-FH{H4wrOISA!)&|MY|mV4 zwoa{uj}@uAviiHME`iUiBTT>0`WqT2hbr%}cvl9=bpj91A=#P5y{Y+_!(B7yDRWF2kL`z%`3w?l~6k58~kk4O2;TGbmFeRIawW2Y4v`P9B+QvI*}dS>z|^qpboLV@zS ze_R8orJc^Up0iHDWPRP*Ko#R{v8;BmRV%RE53SMY0qFS8bm0pfS4V4Su3zrMSlzMw zFHQ0jAHTO(KQ6^k_IuW6S@9*|X@NDlZFl;miP=>7Y-Auiye1@FptO%HPxwo|Z=CfV z|Lr-nb&vHTiQR!{#z+rl{V!Ty!Em2T-VxT`tp6qJD`?9irfsrbgKnsVPChOMv0}Z= zh9IXm*=}Q4bCT7R_MGh|UXFQGr?F;{dn`iVifcn2VhEpp2$h#n`GLIP(VVzQyXaBT zCt>zP@ke4cspTXecU#UH9l+Asrh?8wRQ=W^sud^nupc; zmUV%!F7#tlLTry=E-qu0XRSqWlX%^F6MvzAIz-yOQfrq}&Uq`OneQM9mc3~`Ah4C4 zZLgw>6jUOYS7M*P5_dd0-RFSj&A%|UOw^vdDgX^?S29Vkmin7&0X0^rwe1<~;4N21 zXM(K#s3FLGii_vp~xPc@f>N7&{9TLB99 z0BzlEokAG!$#m>HgN?ceTXWb?(`~_6*Ms1bF^^}ti1+>KM<)6LL6Ox`2=dLhEmv#r z+gIxEx3wf>S>dX>*j9%%55bq7@hekjv)5MJQqYG#3R>Qt@Y>Tp1fmMd7XIjMntk`$ zzR>D@9ki;Gm@pp8I7(Y>6HVH6>%)AIUY<$ggUX*sD)xy=yuF@|Ty(i;?GAGZgGp`? z>QK$J>ug+`NFV|8|M4(wdDND~Qa9S(LU%kuc~f;BN~^TwvAZ9)tu*RFp|H&6`HiK? zJ8c_H+KpAEJ_H_q(r;ZFOTA}+Vuw#k!lb;&3%&uLO)Tf6Wrvy%XT3nrKW7`I)^27c z`>bmPs@GX(Qo~_u0w2-u3Hx5~w@MA=JZ?+ov$;1zA z-{ScvY4V5aZFDtBTF?4?Y9e@HN|uDEUr2Qb(0fIF;vv z>_-E15%4ZfYI!2UWd8(D)Z&S~%zIM&MPS#gb}fqYJnjBKgk4&+Uqo^C(e97MY&KT0 z@4`36c7H1B1Qrl%{~3?IK(zs4E-UD0-;A3t(pjsR&IY^fR}H#It?C*y)3@7m*!?l~ zQk3^OWOaZlqYNiV+^>tL;;)oYmYr%?CQ!o*uw z&yc02+!gi@{WlOg+y1gzdxVuOwog^_1Rn9{_A&`O?AP0qzLgFhj))?0yIo;OZ?j(4 z8KYt}ug(Oj_$LnP@lS{7*?|muEmniqDvN!cKm<|He&0+VXWFaG+E?ml`l4_MFCXu3 z33?;To}$<8M?=64KgxcJozAw$nAO@h=&+=?SjZ&%M57KNB`(fuNovXykWSAQ*gLCD z+G7=Sd_b#joy>2|H)-FhnCXkgcdY)m+voC`@;W|mQ{8J}V9=Okk7H9_k+KE0@isfC zdOnzG?}pX-9jfi?jAobi+7%5Am~TI-)*gpHf}PLd6HMx5|4dC4TkOH?*QIs?cI5_k zHr)`eW@w2|{S9@JZ5?5s40&q1-N6==+gIz=+V@!cF}qRCEce?d8aQ$KGacKD@1!~7 z4Myr>vUg^NhwMLL$ieulJw-w%93d`Uw~18$v?h))g1`FnFj(a8*$?U9QJ5Bs=i3Lc zi4WVKL^(g=ogGRQX@-$(_C|X(E`7{SCfnm-Umau!XBY*${AE4O>RyV-!U~aO-_L7R z%$i<|;D->E_8&B0hy94Zzxnf71|8_!S^xH8hw`JliR#B0wREVP(M-M1p#mS(+IyhC zKi!|@>x!5i5$%tI6LY)KNTc4e$Fu!=?1NCt50J}e%+t+&2F2>Am)M#gZ5DstXUOBV zccA*CNOABB_Jdd_&yshcqvzi=!csNEb7sA+8)oj<_WVNmDa*)^Umdb)L{{h5+wGrG z_?Ko2yK>CF+o%H*^3ynF@&Sucx?Avni;Ba zpEH}5&ewF(09?TO<~sTdMov`Y1^$*w4$441Pj(CxlIig4a=LyhtZ@^QXuy+jV;uN~ z45}Xd(2)YiwCHF~b2l%#jg1BTcVM%72%3XGnoLU*Da~MBOkYly9M#{!OL^4mfeN`h z1a+b-dx8umzh@5M+RMS=rIB4U<;h?Jbz5UKRQ20v)RxX$P*|0-(W&M72s(LC=P+`; zfnR4(a=+F=&BsDQg+-KiIM~gh)J`oAO-Dywm3#hyv2FQUPXG;)qXKE%Taos5vbS4c zDsExwgV%apDuQu7o)YDxZo6Eas?s(GQvHyq@GAH0AR2L}U_ls#pz4MItzHnes)Vi2 zNogb)*Jb(Q(FZpMNi2JOlH&k|<8_;hPuDN;`I?$1$RlXwdGlNf|4NCYn&<43D8Ag; zn`-y#v@Ew+zQaEczMiy1G^dw`31Fg1UD@2!s)r>`i=RU!t5t zx?xmypIxS1O>zi}zhJkjY5!IGU@BfCcVe+kcF8{uFH`X|GLFbSK#Pndl9{+rzFVM@ z@9b+U(}O5IMfR$DB+#VA@;5x;D0YX>K1moz`APKD=`U}v+29(RU3hkU^DU`_M>2xB< zVWh(sL0CO^6UY_c{T}|#4L#wip1sI1#0bVHpf3=lT*#DNE)Ik(gpt1hzfDlCPRqw1 z)2Jp}o-A{8cdPa_5zd-$lB7JZIe0GI8WZ45+VXRQg$V#II9hu13lf3uhSMPK3(27y=Qyoxbe`z^?RDK?Z zE}l98EF;X3Jt;j?9%gjUoL@8-21vRuQyxm6X2QXFcD9@>{#{+m5gFTQh=Kzxss)5J zngQ@v)6`T1rk=+mQ_r8Vv~bGo8FOZoQcjMXCc#e>)>FU(xjP=TuwL4hBYQFDO>{Cx z_Gt6F!tG5$kN*1(YVV)6zG;7y{{d9w|9%9l{PU18{ikE*4@CmXylqgn zmkT9=2k3t=C|9<<@;}zW^!Hi(M>+f{1FxG0L4fr;W(+vPV#W@qa~ZafmC0Vy|9<-X zp(*_H^Ty0<$@+h+nZIJ~jqooo@J9S!%;?r40aSit$@pWj{!*mg|LvLqWIYu4m}1{m zNwI;`VW9)}nm0DU`l{g5MTX(*8({7CMNevBw-;8RMOO_M*WF*j-A+W zoVMz<>nLwZ)D|jx&Gi*uHoj+-F9(JT%rrIXRe0Y_iHal7?7$}}{%qi2e*Xt*d^vEh zqXnkUL92=G!4n1z&K?Ugx}$-W{Jon6Ix;7)BjL}xs{{iiOZ?E05KEw?PkWnm!J4&7 z=rAZXwFf-pQ&Pclket*DXth)bCn=~3s6G7bg-O5^6oQo_X&U#>hd(*@40qp@<%S<} z65u@AAIV*5_Rzft!qVyZ6Q<5I%4oH5!eW~p-uUnW88dcp`c2%(Ni_$;hM7yfK>6J~ zeC#myXzqBBOlL2P{T&q`5sv+{m_sECjRUE9X(Sf^NVSdrL$DKlFvTrWP-pc4WAC_# zh!x%?3G_*4HGqMC!rZw>XWcx;{b$S_NzrPj?wG&5rhH||qP14Z&Vg**I4~@MMoYHf zj_m+Dj+U%rV$0I#SJQ?Xn^8FEg z_T|^IEtGF)Qx@jU-u!6H(%2$^9OL|ZT+1b@xIaXx3Z5UWzZEf-OM6g;WDB>)^8E>5 zkh~Ub1wA;*6hqQBM<*Q!4GRkC$WKxak{(lXS0;~w|bZWX+v*k?c5!yxo+1rtuaB&Si@y|Q1gaS(^ag0EjBgH*bvtJBj(I)W|LAXX; z7wHDG;TG{Fjc}d)nj`AeEI<~27QoZaejO)5^Bm$7FMzfklG!aHW7#nx>@AbR#Y{FM zMBISq)HJ2D2rXMj5n48#hPJMOSwajI%V>%Tnqjv`Jcrk{RQajJLo0{Fs#|ZhhEjm~%tQdut^i*PoHKjIC%qF*26e%qfe>IM>e9&(3 zxx6nyY|!cx(a^vxZCxd{ILOhXG4^nDi_ZAlP@fk!e-`=8Li(Tq-gCHVRCB2tu7tV( z_EL;}rJ6nRPx}fkiRw)4?Ow^y>P{5IOn2-S?_j<(u?$7E&>hc-J`#&`3GA?1gkHF< z)q9~mtp0}Qj?_`?nPK7tykKi@^N$7g>nPEHB<-~MusD_0xa|=Py0-bak%Jq0orvYn z%M!j88-(i4b_-o}h=IH-40^Rr1`Jufnu;?49N0Zc9D}qSG~j+61o>QXhmmu@AAnwP z74%Y*W{BI6nuH3uD1D@~pMXs4&3>LO2H>qgI$R{BQ@;;wN81%wU0LjUT~~g<&;`-q z`TX`j`0amys697T(z5Uc;^!!NFdbfmoAvWWBTHB(eI~H3OJSr@>q6-8GXB=*wjp%5 zla$Y{EEjhfIp6$6nW0d(p*&8c9!lPk(%ZZ|@4~-YOn^>$t~7`GKOnxM1_yI`l`e*g zPN{FDSs&Wg-zXlTcuT3vXEu>dnJ2FC_fu$8YCRh-}_MP zRy|O3(oETIVw>v3LX*e9++Qh8`nlHt9LUn|FI6Hhq9E3FE*qSK&9fE&=5eC>g*6lU%1EkZ9 zY2OfGt_o<)cKZyrdzY94)z7~aK3=W!wwIq!{#Ep| zY(L1(?BQxqK55#kabB%U{Fj>CDfs6OT@SLGsQ-N3C_;s<^HJG@%1U)^c5S!_P1co{ zL|O1xkAGF7LbWWk2)L{zu>hlMU|4WM~AcnvjR|jw0&EtUGt@%7yPb1$^%hdT^jCAc}vBHuHnzAH* z?C6oui_YAu8_aRPx?WXTLq<~ZeY!(*b&4qY=O^kR^ZJ82SaDUl-c*?{I_T>{SUUlq^JT?M2Z&IQ{5VE7XjRK zLE;GRYv3WTR~%0#8sS3;D&VyCRU7KOAV5s;+)TcHXV8G_QfInkM+CY{`9P^RpR;U2 zvXJGP*J%5+?F(T6qagwFzz;wJ#2JHR0Q9+2BoD*TMwtK7+W@&rpGSm0zu#yU96;lb zjfp6T;9jzGi%ORi7sk??f^j=7{YS)Dy3;CkxFMZ@eD8==QYiO!$ru@zn9?Hjv`RfK zLJ#yv>Ej1yXYt?#OJTo?2%+N71!y0>h_KQQi&3P);L5UoWld`yhrv7QyXXx_&$6`BLFqHJy*U`>5lcx-j>3Csh&HcLkh5eFE#_% zGFS5V@(|{JNGD*z{w&@{wWrkS>_o5x!-;MvNh#7Lh62b~WrjU(m-t&tTi${nKz|9I zC{YqTQFL(c7$~i7&5C$d6mOBt+v6l{wJ1SDrhZ+ee!Y$U-D-Tv>FbRK>&_c2UeN1q zLb*nv65uQPX|bPlsQlcXl~ddmLD_6-e~CoW;XCrdgMEsLb@)$kBWmEa+w z%VyF))iDB#>nEMU%^W(sTzrqxrl1s=U)cJxhV$y_0(FnYMzwj6v_h-zizUIANSOcvA&A?3phBwXZ|zExs$Ahz zzdOiD39}-!Yc-Z1fBp{ayiV z17Zx@x6ogKDb()_7XwOInHIvuA>Whb2>CBlt;zk-q;kdCW$1%a!V=c$;n!U5Sz6*&B~a?`e5h ziJIwL48Mua*GL|duH^6}-vF)ZdS5OqarXe-jJ-2`2wib;LQAn3iVeO1+yED>ZsrYq zI^XD%VFPi6B*IknqsYzZItZOv@O;Vq!1l)>&@Q@g!UElFO8-@y&5ECu9zu=g(2)hw z4}Tm8z4l0NqDALY?L?l`Sz9Q**{IHE$Isf}$2j?OFpXyGB|!Vrq}O?RMeN85sh{wN zL1KIfys2tkF{PU&jJN~RIShe$APSZEW?o9UlkAX5j!E|zjB52Opx(X_dckO1JI6N= z+#c`&iEyNmyz zZzYVgWJ<&8U>SE>h^iyIq zxJkzg4D&TOqElL-A&eRm6@vyem{e{7RDE)P#ZGsfHV5kYc^-g~8oQ?hJ#Zw<#2Lco zuKXh#3ltPBOe>)JY~fx3B&6+1Qp*+4th8M5CbfnVf-bq`3cPDu*jN(5TmqaIr)lAV zgxUy)dc^tnh(BU_6K&n0CIRJVPj6wjUSI%-$gzI2I89P)EVpR{>3~Mb2bQdVh@VE7-Wyb1hzCnOeW%CXmNY~$&dh1)%CL~5gHIVU?6oj4L zb4uF6D&mw}|JYxQzFtn=G0NLi)`%0plx|8__>tt{0JHtPr0vYJP^-dJLNosPm!GOZ zJ}7sd1V$^uF@!DqR=QI#>F%gn>4U4wQ)+)X7|L-JEB!KLw31yOt=MrGXwAtdunk{J zxBKOxa@O#DWN(2bq&p6&*>68dT5YvO(Ce208Z9h?jim@S7QJsM5*B&F6R38qxfL3{ zL@%#G#&@p0!#4!$0N8*d95++ww_p&`l_B4|SeQ|sC$NRTOZUSoPNln>^48)gimT5U zdEk=s16z_rIFVq#tqqPNau+I=MLGdo!)q>)>pngG)$;{F{MSV*@p0 z$V$5rwKJ!GEOfX|K^pDFnncjRyPNZR}W_UEIbQ=*T$lN686H-!AruCt` zjDWlr_*?cuM5A7ENNcScZScY(lztPO7vdAt9(XkxWDOKfbY)$@&GdrP6-l}E4lA8l z9}ooB^yWQcXZdf8I-5QgBY2LR=!wdpY_4}*6|as(V8Ip6b;lxs%bLl>3E+ApzmjT ztf1#AUK|mMUtobs27Q`jia1SOzwrD*6}=)h@C3I|`Z3QJv}Qf_|6T>Dw@smj(Gf9p zy*S7~k1WH9rCUdjh1T5{^%&xeRGxNIZJ1RDO>-xq7nRR2VzBU_T37^ZLNP+vf{W`o#$VF0j$PB|Iu7EnUFxlcOmcRcP(3OHUs&a#J& zbumO65|hD3rK_RJM*^Dcco?<`6SSQ8m6$*!%Yleo-QR4ra{idqo+*jFp#MywGwn$?M^fEP97g^U7KsoRDW&It|J@Iz#TkluyKSY~I70$mU59{4|IHxW z_J4Zlt^6N`9)P5+gD@s~TG8Utc}1mdXJVz5KP10LyMw~g!g)ngXBSP4N#$E+EMOC{ z!-r-fl3fcLG~e5!jTfZln2IS-+@nw6pN`o1Ud}Cu_S{AZ$3udRt@91*>0Cz&4ZO|M zk$M&dgi`$!VDVnWWH>U+(em|w`Jvt!=0OH1vs076lzsLeK;N!-*5qHY z9>aVz<G!zI+%{U?i>J;Kf9H}_nJFVSW!TT(1$9& ziwt$-w<#OAq!=BCqCPay&CC>EsM z$)5XkiG|Z9ny6tdmMHsG(M*pnvkqukWS;Nnk?4v0qMpVwezrU~*dNSkcxL;E{I_Eh z3$$Mik`Lm%`G%mX%9^Bl;N%ZGO`BwMuF8>i3(XlJmk`93N;>+HBbE+j$TB|U8Mdzv+u$gc|Q&D-Q&`1%ZMULYS8xHQ)XUw0tr6}SAjWfRLR zkoQ8$dX}13$Vb`h)8$@<7O@@2vm1vqlW|W+AHtfQZ!ICVAfQ%O$-8-Rp2TN2Rb01$ zo1j?UhSX}w9wN_U$G^7Yg!(7PAir17w>To!kv&XqB1=#Cd)o0&Ie_}ElQ*)O0(rh) ztGZyf@whw>=^h08fFp(VeoB5rW6&J}*M34iv;N54kLI3{MK*t@yjHEz9R}ub zy&S>1{vu}xY<``*7ABY@RHs)uQT!Kn8|%~A0c-Z7SLH&A-!6}*!!eM~#Py~`D!Xjg zvQ160-rt@t)7If=r}}33D%*8h9t{oAH}GRjdq&m@Ugr5k4iwbM$#mo!vqD)v$Yc51 zc$6x>wRfaLpTeiB`BV9K%uy*!h8^H1`DR|qhuF*n2Rxo1{8@G*ERXILm@eVEQ}?qx zpSqrtqmXd+Ie9!HBa@WmI1T*d%HQNPbxIPwao+F4rF)g0{aqd;8BAiO7Hx04wAzDqE(!^;w9=2e=j6?pOKS~6q%t}yBV%CtppovPxrJ_=vH345 zxknqNYhFBc?sl?$BX5-V+qYw0Gpo|Q6rKNK%-Ya75`{;{aa|`zi*-V|E7f!N_$j!mjs!5#-+ri#`TJW2Uy)P z1iX1K*cn1!zHM~c(LpVz|4}Moy=oLx4C%%_F+PN~JMTXtE~47ACXpK7lV!T)J^2Zf z?)AMReL2vuh44A*#~>T&;gj%Z+kDLlvf5jKAFB<9%WB#sIp42+sK>B=gI)PPvZKJ< zFUs7E{3cb7cQml~56B3Js(XtnhkuK{zd5}r>eqFL}3~tZ;o7?FD{QU6_J|B>DEf~n()-K>W zhExA;Su51&bsvJY&r8isb~p+@Bfrm@zn3Mv^ARgaaNH{}tV;{g6(6&bB*$7 zUZ>XmgHGPB530T_-^>nla+n0RLg!e8$3CITw=@Z~BGysN9ydBBBcV?T+h#cZ{)v2q z8av8vI`e~UW_GLNJ3Rjxo!BKm&t4QA!|~`Dc4Ck06`1`?8GxH_Tn?uaSbKJQJ4pWeW?z>sdN`u$+M0WfoiwI z>RSAR1T6er0~~j#br;!Z-$%yz^;+*Dg|DGVaD2iVZ*oMS7~fFdc3m>-HO%oQzFnfT zvmD`c^;YoB#n;O=;(^8)VuRNhst;)O`XMS!p)drhu--zz+w*-BAkxruR(`KTR&)LK zmD**#o59bvlyGS+-xt5a=PIlGK>`<9!vL+Fietfj6aJKA2MxH{aa65qqLR^$M5>L3 zNv~psV+Z?jg2RH6G}Bor5QIRJ93TYJeM_Dvoyj!iIWr(N@$hAbG`)&4XF8rC%M^#i zj58c>`**?bXzT0T<+#A{F<$$gPL?>D*!R;NHhlYmc?%sUz!-JH7D&79adf1{ovx1D z0sC=kdeUU)0dpg)qj|X48Gw+Zo5hY$P3W92eC304h4^n9ml~JAl$bG=xJg_-!eE zUOyYfpK?6GR=?>eQ5*CIBnlAp))8`7mT=6m#2??_=^*}e3v!*|=@Zb%f-pdDr}U?S zLs`Eoj-!|%+vJY>PW!7U(ve^#iu!)*sG}{%LJX|&6F$)O5(&xriIjT=9ahok_}rjF zDbDHo>`M#??5&oY{X^9 zV1YMRA(}d#bLadF0EpPx5n6rP5zT(rD>~%gfjmJ9mKmcG6`%v*{!1emioFws4Mr+q zwEROy89n7t#!}hG4lBF)vLjVsp)TbMycO{ z#<7;N&p6TnZ_y)8b)#bvOH0)Q#8P}KFbFR<8#}V!!~CW2P|iR-f=Rd)hfy~bpvt~f zKDf0XSyPM>E12|=JBIu6V0`v>@lr~d0WWlPS~!JYbR49^Z#trBXICXhuMb7RqsTKx ziDB=IR$y3e)5t%DA=rt;4pD%BB`CXK3XP_m?ja6Vb3ytU7V6##4j1|uI@=*C9C|jl ziJI~aA`C$lAR)&dot2>&IbmxzD$7u8EU2GSVASOUdxzOLSXqHK>x`_s(wQ*|_()pK z01pT(xVQ2ZVAhwU5W1S6K+ad`;}F1G9ZdGil7otiK-ExWj`IAwTnvCbyyXg@O+ABT zZDL$PY*qSa3O5?b)MtRwNp6Lq#I%WsnOT%`0crmJ3p|R_`-3&$um#KrMFW(P7DN;t zJbHL~#_(GQ-$c?6R_=$Y??%EEaR7BrQywtFv3_YGkJj*Fni8RIj;6H^SGTG&r3QMd zyJV)zJwaOQzF!kao4f1tD07LrkBuW$hNOf2KdvCQA3_T*0m1#l;RczWPghp7c{n)u zB8o%(alyE?#iils0~(o5_tAEy^0xv<)AfTA{Bza>b)&?A%14GaF9~2;I#8tn6fSH2 zVWUHcw?X3vDbG-k22&53nWUKM-p;`m%K9=wYIR|lt`bDmbVziQj9W!tp;q;B6dz3+ zv5n~CpP1l-?&fVUPz+XW7m8kK6D+d9NP+ zJfuSl+Q{O96+Pq!P#mBuhYLffI8B+Rjf=kRZ+kI4E+E*Dh&WdXDSc?v67_I;af#YX zU3y>z%GhQ!>v@08ilrUtiqB6>LG|~WyC#-SpI4Yy(B(h>6-`|SD!sJ@2%$tLc0fgV z&p>55eRnLxrN_afG;wiN-LgPh%|kv6I;RBFcL5eStbJtkqo=I2*&rx?>iY?i&RciOWwUw>Ykqpbl)u(Q~5vLI>8{=A!GzLQIyH z-1&B_Pan^=coibYL5?7<4{9IjYtOT5yP&yr-ea)eTj9pStL4zgJRc6HLU zzl;EN;WQGzkmBji%aUS;b1rXOSYSbm;{}&_hT?4>6fp)4)8NXs^g@Ku^pA|;= zuwr(%grv&rheH*&;L`mXheQ`{);p*&6C`!FrrN1bGDdTyCD6nFatHZcoAS70Q)%Ji z(pYGnTaL4|x7hSE49PrpO&$Wx%r3e;rqmldmCj@;DYWf&L$Kc4j|ZTklV>0{rlDnE zJ}F-m9O~!a`D~)vX5qn6u>1@kJ!WhyotOxk+qdsCw_1-8vsisU_s=O)8y}hKn+DkV zFn)*_Nh5MV*!-a<$^#9i=L6|0Q;#0#Vg5;_oLoa79|Xxp9ijnzD^g{WYa-QVD}zn? z-s_k6ZU+m)LVq$0$?V0l53eUZY+{c)N3-!`m1nX0_2rDd!zg?Kq&`Hr%`(MYs2Ps6CVPIHxNR=WDIvcXQXAc7Y%1$GzE^e3kx)&5l8z_;gIe?dE;a!@?S?(1j94lWEm0v}8vZhgB%gkM; zfagS|zlr7D;R5MH#V(};d#_%h+Gb@Axu4UBKv0$hY7{CC)|8N#9C-&-%ynMiFAb(V zpKA{_u691g8m~Cr;5fZqbF+{@Wh<33azF2ch+L-JNhGm&>VR@k)x^j9+#Ev-iHe;?FHvrzMbenY;?ctX(rB8_6aRI9e)z!&32s#XuII0 zoVhN=4D~+W+V}vYlG%!b%C?1QO+R&uj_U@U3VaaCuPf1%Jy(%vS0CtR8n4>y)X*4W z;G|Mwd+d4H|Mp$ zp$70Vb@L{~QC^V}+_f7Bol?P&kNApRlUwupkMnWgAGk^AM7=w~8d`i*F;MM6RIt64 z0sdNYy%8Orqjb=A!?INUm>6X0m*B_EpwX)8X94&dZwiq73q^|=mB$rEsMCI7QDItt z5F!*iTu7%C+d{fg<5Z*7FBdGmM{7m2&ON!RX_lv zAB71Idq~rRIQ6-Wt_RqR70L!khM7!Truc3A`fPS~j}|OM@@fV87<~>Mxei-P)2*&= zS#qWF4la$Ry2-A&Y~DJh0{M+$byHm&7%|LQ!0z6lEXNaLscxh*i)tEOm)L+!$`O65 zW;|1+&g4tdO={zFvTv4F^<_+ZGjwCMy_^bH9_c~m1A!P`NYH8?o3A8M*#sOWPRUp| z?6)W{a>CiXH0K&9BTp_+e0(lUpz;p`p#Yhp$)>qGl!vI+t`1Ffl5#3#)Glzu?jL}DRyuaoBDxLoGKNjY~67(TD3DFJjO1vKf?3Z>9^ziU*q zpX$2B_i1s9pHnX{{F->wsYG}(bFN)4{GJeX>a5K=l)5|yoYH_ENUeha6TwU_M*`_A z(b`8X&d6Sg?x7i31F@O)X@h1b#Vp1wUBY?m3vVx+7t6uc9RmCJ%gXH2KA`<7S$IC7 z^?ibeMqI#}x~IR#1sVK?-vReH=wqtBAmkhOt?(gE&E-3I7te3;DtRGW>BRdvLrt}> zDeGwcS7wR#_;`FKvz*7BH5yvEL5tI@NWK;rC@Os}%X!ub){tx7$V6C) zT&G#tait8a@*F27luDn+a%!C~s@cIk%3ut2?3-bcB~*6C)s@xkRiK9axV$!ZQKDWYGq*Z<8 zugZ2x`^I&EihDbELyM}PTOZ@&aUIG=^LkF@m!=)@`sQ$lRLB8IE;}85-hqJk_C?K@gPp6U%+L!72HRlT2Bx`!HDNj4^Qu8v-VBRZA@n5R$ zLnUXD_ge)<{s){QHEe>ZI5!zp!xnUg`ghO_W%YH+DgS0Mi==W*Db<)j<$^z1 zP_?Ym1hIr)6lf$GI;#7zGoR{43Dx_Z@A6#dfP`HO)6^$Q4Oi3Xi&)*OPLOcG?ixU~ zJ2V5?=C73kl%kl`z3DV!gIngRZQo3{;YJD7z2n?T;a@w8scg3MHx~M>|L{ByzOdk? zYdq;(!mi~hYBkI4A03MTE* zegnta-<+$kI?L1(Wy!~CKYtAEOw=AR9@RJ%k2N&=OBCDk5NUf~jz!+<#H ziOJJ?vXY(7p@LAryfMy}t}kP?4ay?8p?+X8u;Mk&JN)fjPGXoQpXz%>){uLnG83db z&b9tg;bUnFojAE;{i;QbiF8d2qy+^`f(M-a1Za)7DWKR;>F=aw*;PVQ>Oum+85I~x z)ek$D({sZDqN|>Z4x*@NO{W%ih7YXJLHaowua9>Ilf1#%xoY|+HoEd97#_=3!t{=4 z3TvqT0d1f%&eJWiIBsee_p*hVNpmCfZ%&*2IhICoHZ6L@SxE^4!S4o=`Y2w2yQsdi z7)0+q>I^oE|r>)5T$yi=5D2A@vsLLH_P4I=sc1 z&Q@hwI^axGY5*$!G$J=0nh zyEn_q5svxW@oqXfPROMC(SnuI&buF?hHoMgNE{@0qPfQ-A}MXKTu%?ZU_;2nLrPfH zT^&UFbfwNjAHA!U$y0-a9*>8}5uI+jR|1R5nRu71$@li5&7I{8@?Mk!N&SF51TCMP z4u*;O`hXC6uctaP#LagUh&3(oGhW!VHU*1c_^faX%Lhflrfim6vm~*yzp%nrIh`Cpq>)?LWNUrcczis z9SOpbjmo$Dy$5ed>;#Ful4g!{nb>v7DS**7DiXt2an9VAl4bWqoiD#XZnY)gsKP0bqnJ0q|(RRFD{X=aC>aY9j!%`um`FF0rT z2V*69lQq?ZeQ_+yUeDLnrXfxl?>t25U7d%iEYh`-4IGXJQR}N%)wK32PjJKm^`nO6k`cA$9{=g#Sj+xW)nMRm+4^Ug=MCiy;xAp) zg!1n@nCF->M%X^fi8-(ibFHl=!~HdRmU$W!y~baD@Xt6MghTBo{KcJAd@W*UyPy>X zs#KMJSNq-#CH)fYdeeA22XJ$i{?Lnd-(n~iA_$kBk{s^Io$rt$~jQS+U109%&H_w}0dt^~^dSV?BNrxldHZD%#_`@{)Dqy7a}*ULFl z&C&{-b^bEFNX>=L23jj?0Jr`|nai@jaPGknd5Ja8aN5bNtsH z#yVHC2ReAZ-TRBpS1-P99Dr*Rf22Wl!c1A}^pgka55mLAc^WhBv@)J0{pdWdQ|k{? zd968+2M_D5X5+3ocjE1Oegr)Be1i+F<@zI(lNk9TJE(OH#A;wghS3?=Qb99 zlk2KLa-^$Ct$&kk4UL3TzW*H1(8-mEq~D^go=6}HB`27@OPw)v`5u>@1~8|g+M}^h z;CM{}dm_el8*1}5Yj{aXfYv43WfWN7?yj>s9b&ay@6Sgnw;bxw7DW%Sp9l)fWLcB@Cg_JgAlrw8W$qZvM-z$ zFs^rNOx3r!;FYh^H}FX*|F!lb#P9OyxQV_hbUj3`u5$gx z;zzjNLkGUcXJjDTd9Q1XhUV^fu2bvZugd$@#d7DkYSiqd(XL_W`ctgAuQOZCYL>X3 z^_TKP0MIqT?Ehl#y#u2vy8iKP*_J{A$=>aI?-ED?N!Sf(^n{{FO%MbGB})iJ38aBR zktHms2udKzK_A2f8!9CzFcw-65fLO&#D8#Z5~ zIH%=94mrZzLC**FA)GzJH=Fi-wz4d?myA2}E*{G#;g9ie&*O@?7ThZt# zmA09h+N+$*z0lN*hFd82eN!LGad!Mh9zTZh!(FiU10Ey5<7K<2oBFFc>Tcu1{vqXl z0`SF6@B{K3C4n)k3JL-DQUPhC=%(%Y9302F5&Su4d8O1&MpPk9pp7hzI+s8zkV2SE2ok za!+ViQ_ing{+K{1o}?WyQu!l_Oa(ZwC%m6J=dc!*3>kcD0Q>ROk#eNE-q0V z&2`a2j^;8GOOi{Ip=EstuJ9Wt$LqKWaYsax5oKfHWSQZdJ^6;B!ivP8?~<~jl9|53 zB7Fa|K;*44%=wLzvM(vOn0lK-w+u298$9_7#vh}Wvr4+)PIq=jH+|v+9|VG{v+Ll;W!7Q!DAjo)_|~t<7UfZ%>j-u>rn&8V2qjkx?nTWC8%ZLN9wkw{(WZb5-S^>6-O*5wS z$)K|*jIoiG3A{jX{%{WaiYZA|UPLRGN21#P9ZeLs8J^Pj&D5E~dcZ49rY!KZ&%ni+ z50WST#I$5UiG#DH;qmm*A;f0MsWsqDa1AM+Q;d*PuovV%e4&3QhyHC1RhNYKX=StL zluRm|J*&`Hk=S;-$IanqPfaXLN`;O`+#7`nI&(f8u6Erj7JX{ND zTSn=;6}Y?Z`WkCyX1tQSi=KIAOtb3W$o_o=-{ae1yi;NYq3eRQK`H8 zT6L;{gQ%*VTcm;0vHmvg!4-dCu)#xLK4Nju>}TBin&_|Ks(PgmyLfKE62>v*AvuaL%0K&{QZFD3lAHj>)M|>bFe}D4>ctSe^?8dgA2j zO4@Jn_iq-nQwyo^60&iA8-bT-h4vi9yro^KOk>{Hgrg9EuQxAa)7RnjjDP%<&Q=O# zRC`(|poN82&7W)^>1t>wt~beo_HS(E{o%85Ft$_poy}U^QFia5{hd~>3qQ#wY%sxB zfY-yeddy&R6$%|}8(M_JZBuc6;oGrdT=c?5QwH@bfqZlNbW{|H>!I}U^D0Xud~>?! z{$fK)B*;Nwo5dQPP1k4ArreHta%3w9^CJ~FvEW*zeXoQ*<+diX3%4~L9l@_IQ05x5zxIG&>5!S`P0NPSaE}ZR4%K^jlM{4w_NTZsqTP^( zVwS}> zruK-LuI&o#kA^z$Gg^ zn0TDw@BL0_=BTm?V$g7bvm4OS^3Kvg4QhkwRh>2fO!<0l8^C8(5Xpb>TRlHJ?_KCsh*(^$lG_8jvpD9Hu0IyM%cf%-1O?Yz)+! zN+t_;lj&Lu=a+Pmdbj(UcuwvPattHLi51r8_RUD@+Hjpu1c4IkfyK!7;PJrbA6z*{`2T>U*rG?3sHxjqv$ zEYf_#$JD6aanQ71qYY@_!y~5+3EtsYtAkT4*nj1J8r%Dl2-ljB!oh~P392cG@?aQ?2}pvo$w(y1n>D=OgfI(ltv zriNeq)55cHFJ1nUe~3IPPO8dLzbOmO>`ZO!{!4HA`@?!F_#eOVhim7@!f#U=i*B4fIx14O1USoI9Z$}2Zl!@m0;%P;jdyw#mpi!4c!S2M%j(f)A3C%ni+g4hU>yh z`XsY~-IK-iWYG`iFf&=q!o6yCve<+76@Omd-6`dNwY8i6tff|HjX`LQfYx|1wEoM1 zYm`k8(Onpuj2Jj_tay_=CwaM}Mqf4hD%g!IxPyQ=$UK)0F`3FKt^H8v%m}{XneU-fRP%i$12b{)Y6uVx8<)Il2_8ncXL_FJK zzEoxfp$JJZ#Xdh@M`*4xC;|XtIXBjsoIALnE-n-LfE+2jIvH0`zg(_+Te;%$i~HMJ z-_#jpqYdv5=4w%GE6~_(`t0GFWkCMVdePyxkZ_@~NRf;JL$w zwbF<*BaGL8^cN(h|1nnv<^ko)%L+qxZIxngEM`&m!womTd=Aq23}1*PR}b>bUVu5? z`6dH1(^4>@!6BUzs%KuBDD@HNwFMtWf4X6CyA-2;iN&+pez#$Y+yExE9BsBGwl*v) zMV`N>EB1$G^O9-#$7aQU`DyvbX2p+j3hf$gs0_-?Nk%dzOr~mZmIou|ib?Y0<^l-X@j@v(DnEA@2^sAFYMNEBxH&9jCOItf@F#;uM1~$TkXB z=9@CZH}m4!i{VTcvSwmZXquA56p>p!f(}T*y(mF0mH-XcHuUC@gfQ!Z4w>X0g-{K> zfgNS>6dWN`jWU`1{2hSH&GCBqi`>}|dAXpTU^Q*~0YB!Dw*fgX91#J-{@sNqyRL+mD}$ZAe_>m(-P zZ~|vKyef<;SPE3~XH_AqoSo}Pov(@)_9Xe+Hp?KW!O;uOl8hp#J#!!qKlX)lgt#aT)F~HoL}2P z?ga(rVHc1Kpu=tX&iPie?p+`6`Mg zl^2!GC@P1)6U-{#5#`YcA+R`X&=sS_A#kfGhBjR8X(seDbsjg=4T0JQBESvyGj#qG z8g73sJaUy+%q~G0Z`U9#$Ii=#rgdcGJ-KAI%qj6jQ&NawFDb+X{5f|0VM5SNj$FL) z5kRZ{gGP@XgkQyeKB623zW2)5CL>b{&S+&Tm1jjByC#-v*!$?nsVWc&%i{GU4)nxEG0gw8RLI&WGTqP7FEN>sDUdF)n; zYHk`gfT&qIBr@f4TE&;hEfab{XpsdMS;bh5@vf;K>(GLDrBo*4av|Yjg=6JtMCde{ z+O>w0>67N^(ZiETG4JE%UiQb7bFU#xJ~L8^r2Gc<8MA-r&`wrs=#Wq;D>;j^r%o*| zswkIrr%Pr+bsi@?VahkDVRmfS)kaGs?V1$}&m9MZ#EX=gBQH{FuE|d@QT3Ma2$;rm z1`_ky5f>-$Mw@e|S|{6VI4^Ir5!$;7DW=JtqJbW0cbs~|sB=6_`f39)0;SA07%4f< zWVpPRcTgSsN;Rq>=v(Nse!u#eT?e7YGgKWd`iJpzvTAAjHexjHyQJMS#j=3fS?+HTBHXB-Z^^sqTM8qVq#kZWB^vT>NJxx=wu7&x>@k%iB~K zD$>P1h~7D30Q@a*Sv%k&5RjWz`Oez1t3nV|kPrCF5iHjXryb1rn zC6XT^?D?Cqze)7CK5me}Wd#n-26716Zjb&xiQ}>YhyS0GI4&!2 z_@~NXoxeT0l&=LHNc`i~(Er*6lK*lyNPk7@XuBl(pIsRFVQ$}g3HOi5eizJyANP%a za*ucEO#Cgg;OF;ve~*OmmjsMnxPocBng=f%yo&g17e@b2!uX%v617PfJ}yyUp(lle z4BQ~LT|ou!js967gG)GnRmk`o1dOi!KinJrqXI^e|NmCN802rg82R%{mcLWL5dHtR z0tUaO{J#`1#CDem7?-$hp|Ut=2`{a33)Flhy4~t$5O}unMxE@*C1~Z`>dA$+{uVPt zT~t{@IpZSB>C6`z1HIZEZZ@hWMBPZ9J<{NsyI0EkyU}5}{EA7w+@a176ueU~C(29J8^lqX)6d**tkmBY8RNvjy z?oYhR)V!Y9;rtCydtWc3hb!!>6~h$>$+A;+>Dy1w<^lF3l6ROI(Rki6eT27eboK_V znb}X6KEa!Z`b>w-TY9tUuuhE{scNdyM0->8un?>_4Pw(ynf`@r-O0P%^gC0ZHT{n2 z)5-g+iLvLuGr{e+E`z+gOmKtqlL>BcbeZIBFb!oR&za!GSC>WJ7fm;BUmz^jsdR9q z5iPu=WUm;63~dA-Diy-lsWZ<9unX6(m>)18h}L|n`dl{b)Ad@}GXVEb>nnN6Uabx; zsdT-mzS3f2u4n;{E)|NbG$SHtN7+=~DHOgChCO$65Xyt$W*RMmUG14-q*E552My^H z-k#gS>8YPph!iqBp2^q2F`Ab~Uip?Ju)P zVNf1~Q<|nyIA&`sGZ;;M5)qLqDJfmPG%V~SV`Vfqi0RlV#KVUqYIqQv+^Pg3c1tbe z0h{63X!SBEq(>Vfjg&evwu>^6_PA6+L}4FX`{4?kXCg;j1XNX#1>Vk*MhzN0X4D97 zfpgwv+2rI_d0}#pd8tSXwN%Kj4_oVr=@Xesd-lW_sApDe%m@VBkX^U+gTI=q2ju1r z5)lXLH z4CheplK5oiF$=frwE>t2G*970t|W~lIE}jg_YMod`M{I*U_JN6z-`u9UIc{*2Cj-|c3w4+GeVLHf; zCJEhv--D=Ow;7&wlLdIz)m=d=CJTtj=n8q%8qtJ{o~U8TzOQB!wIH$+iY!|LG~lO1)(G^%q| zlkp*01IHifmg&mLC85>J+duOeIfTsn72`o_&^Sa3u$46n(`_%#r%B) zmDUK8<)C?RTM@GLq=G?~CoqgHgDl-tzBJbR3d&WFXPRKQafs7UMMIPNKUJtez|yXXP%lE^a%p%r=XpaT9Kkq4^{rGNWr3Y>7Pyx zw1bndiNO{4^)_=Sw&5D#8l_P;_PNvm+&6t!aQ>6B3l+p_p%gL0!kr4}@}7+eM8eGg zT&(i-i_+0_&uC#3-@>^5MLuhY7Y@O^>s&nJsvOn9p?=fFOaNrQw`6}QA3#H$!hl2X&aW)BtyE3u}x zF3{vM*rbT?ZZtvzHsRimvv{^K75Lrm=z2cep0H^ zi>Ur5A&+OjX7$fP8EeTFUm}clrUxl!thTcXI99bpKn44S0!tw!_&O6`tAT(PH8->;24b2Y`FO<`i97_wap?L3v3&x)erl0=^8yhl8azvSy0 z)0I45M1`rljE1(3%NQ&B2CCU}i=pPtA(6V_gNBbBHNFo`x;7%NM*LT#3gL~fzcRiP zRVS(7TDGgIBjr65Z485lQ26NFI$mv|1B2tss9#srZ7TQ~jOZU<6a}^Q?x|Qe_z$W- z)VHf@Uqoo<$VkpehTUvkR41)iGJEDck>0M0a`*V7abHONkLO7iCzVh4AtrGf6W9f= zTeUbZxx!OB>rH&n-BGP=G!X3Py8QIq?C>h0HN1i%Y55;v5T!_5Me=~ zn*a(bOrnBYH4z*IYC!oX(#rPceteSq)9JU>v)X337Ob2?cRigQ5;Ka`_r?yyp1f8V zi>+o7RV4`{*rchAXiY4qSv=-91`1O!pn z-9WJNoJL!2H0LAMn`spru|x>RE*_&%{1Vj$b*gN-%1+!RxUpeRL94|qX1s7etV2H& zN@b{n*7w}n`UMWvEVlOnEL4KY{o6G9nDAyaaEsGgd9Ol-*{tzC!2`cJg~F(4c#O(~ z6VoZ(J;sdejI>PVyUMJ_+I>ra^HhbdgrXlZy$3=srEB0v>K$RLl8Fb+-Q}*8Qf{$k z6g%0+k_LBROU+A_G`LwfjdBVFla82=E6LZv)PX+uBtjZ;KE^eInsJkMFUWIf2`2au ziKM90q^Z-U&$s~>7_I(t6FN;Pn%HR~(mN%zv0oGEEZj5IG`^#$S-8}!8hr8lfWZ?{ zZ<`lD9+zcuUzk1r^#h2mvw3VZZBgg|>3hQXC|h^%*KF&_zAWe#p82DpOG-WENYom_2pC|OBg zbG33ckKp5Z)7(fWuhU}JQ0V4R(=Kf;d_$RG^jEzH9_(OqyieIK3sahg|8@5uw1#dj zH8sGqPQgurjU8Ac^ajG-M5WU-kZ;xsT)wHKv)9Dm&X&~*|ANgTVo-iMM23oCv$+TO zG_JiJ!iH|q-u{7D_#dj$w!v)UJJiij4GADz+sEd%h`;q{=SluKMhe9FJyO8GyVUuSP_6S@Mv1x)jd@Ed;knRlV~VeECi zEgi9)S=CPV`EJ32HM5ZFss#9h*(MOSbV zlc!$YUV}L33f*GnyR##_=W@P~fiFA^v(6?pJas5^OURdLfus3j!e&%@8}qH=8TUr3 z;C(aNd>6<1r_uK9baqgjl|?@gexvd{&JR-VXcpiQN4He=_Ord&jDWKx?KGX(nau*c zVKn}zmdW0oOgY~CH2Rby?1N8?0`2X*>1C|+H33^TteRy29J0;va#ngAsZ88&9;zZV z@8)^$Vx{lMsRzyQ&x1wB9rx<)W~J}*8sO$)Aj9fQQtdb=jE6a*0IW;BV1Aj{TNt6_ z6IuOCUce;#FKWd}iE|lu;BV!Hpz~dVVV3|oxNfJpBcmJR zr^%$ChE}Eu?@_mX=5pGUV7?k2Y|INO`g66Ob&E2?ql@kyYEfx+v63m~o0W)$W9i3k zZg0K=AMPb(0QT3-I*mvL+tm{H%wT5)apv_}m5x|xZ}SSZye8^h=20r$8djQPM)Zu@ zE3_6FF1AcE--J!!K460h5v%43dUo(SvsJ}TgUl&8IAc`0`ze2y=?)G|lafv!&<0p@ z7c(3U>HbCe4W@hAAi51MZHhJ3?5k8We4y#pveG=>KEbJVp2a+NN4lA5wE>GpaVENL z9oYG(u6s5+UZ>J#ISDn^KpvaDs1Awbzm=DYwm^Nc>Q`yM|Zu1Y23G z$pS^%E1cn85f&@?E_t;|_axsaYiaMJf{z}!%X}MKR&3UQmRaG^$bBKMm&`TajZOX$ zRJNH)Hw$Aax<+$~E%aEzRBfXz8**=<(x-$G7mRz*Mi^N1J~jVhexJirPo+D+C!m*$ zK_IqL%YEUmUT(vmj1hW@wLB2+I`7e;kGwpdCS{8D%C@~e*ae0z+zIF*zWx$qQ$my& zEA=~$JDc#ggOs;N4j!^GBFhMlHJ#;Q*3&GyKa9FgLd$$pjAVGt|htQLSZ!)h2!0^EKBP?^Up>cp$L%`Yf+%*BXcKIHBc_=kOK78AbT6R-i zU(G}8v1iN^0L30^xX(16fuKBsx4m4--bW>`s|^&LWjVw4G?)hfrUq8ER`>x-#r@$} z!F?T;;~buStaO9fsisYzg243CT9)y|7pUQJQwJs-Hh&4y{~|m>nNyX#wO3_YdVp5O zXwuoP7|Q_o7u*HX$qT*2&JHwhgL9=;#}O3uFJTCsHe2rJpATZgHD$Bj&1M7#)V)kA z3)QgqaYSo z8s7z}0v|4eJTUvw$`O)YFp9MBN3|ogS%BisW_!Ok_s0;tN(*(^@6J>ROIW|%X81D{ z2MR%!67fs5+7#BjgG*T#Gv$Bqnk3E_g1^TFZSx5R0ccswgDt!gr8^v^&QZ+fyNLoJn1-E@Ztf2c zqX(3GmCq@+DK{%ODAz03DpxD3m3Jx^D;Fp$m1WAA%Bji%<+aMu$`Q)J%KpmU%1ot4 z*+m(zbSllt_R0u~I2P{qD-_0;1K=o(2Lh#d?hcgT`Fvm&o=*pgIcteu?*$OY-S}GI zW;`1MbMZVHn1kn$0Bq0xb9v*2z)a-%SKvlGR|clzxil~Z&sze8c>1RYeE4TdU=p4a z0@vd?J}?o_ae)bVjtyLkXMSKjo`VBp@f;8sgJ<8sXgt#bSL2xy$j37=fDpXKjsds_ zH(CQD@Qe!#!!tZE6i-#)Duu#uE^sBDKLxJ9^Si(xJkR(81M$xnfdP1a8t9K_UI0$i z4Z{OC9Mu0ifKxyHmgNX4^V zAO%lj00C6=nm`hsia;XJ@LV7P&mRJah@<-^;E%^YUk5tk`9;8u=Z66a&vyeNp05R5 zc)k*F;<+>6z;j!`hUdnB70(9(2t=e?5fJd47l^~NEYKd$8w1=8LSY~p&yj(4c;*K9 zj@dWBWzwDj?zN#y0CAsmLO_H0*F^`^cINgU_L(Dbm)L+5%kvOGj{$`DSgFQ*KPcYk zIKIL0>rs{NeX6=nSjBL*3iqD6lT;dE{*da+;$s-RmsEq1m|z*raK70Y4K*|0S8BhK z;gr%3fLd5nAB{~#%|98#FQJ8xV<2=Nkk78|%=yqYO5U2Cy+6n@9dfdSz=Sw04T)8o z_~G(yt?mShd`xG@2?#NeXF-So-6!z2ul2J6z4=yIYX6kZ76{;#$621k*UzY-lQx~i zY1%ccaiRtOB6XjWv_PH0yakq*&~MKzxLDPSMB?!q)RQDD3!5 zOEN0>iW=r3LUixhmS<>0g=HM;T51t6rX3OXOgQ*9{$ zu$?uEdbod?sqaV6uCa7B>V8}o9_XY|?6kHe!q@bDmhI@lx2Wc)pzGB$-|KZGF0;h5 zE7w|{M}WW9^62nC|Hdi3f1)S`P<4~iLL<-!`y$?$yJr4wVNXs8viBl_P>JIs@Tf+EVETC z?||hl&V#@gaLl)s)2#8J1s*#)K4(b>s{n`3yVEYNO{6S?F^cY(NjZ#wSW?R(b-h{sxg+; zAGVBAt8j6-@=QSmkl=Cqixb0mZ6G+i0_IJLxAICv?^|5tv0o+OKOTGBpwftLjohi^2-*92c7f$Om zPT;)+Jo*@RYl_u>X)pWoUbbgd68}^(PCy`{7Ryw*mmPw=B=pk52K->z6Q8PxH4OK)oI31?e(bq+sHCwo~QHs!dPAaWed`XP7$&?>2)6)W*bw zG;fa-k2eLEp5C;c9uNMH+scnR#?z#p*2y~( zt3k`)4A4FV6z$U1%0Ps; z=x<$*YP<4kA^HxqLiE)m%-L+~Znkf*H3x4go` zD)&&Q(D!6@FY?sZt*b^(?ZxU|HqOK7hlp@9Ehx7x;_LJtUZ^*#J7Sy!wC`%-`2KbD_n-2yMRG zoc-#DGR=PZ$1tWjX#EYHAHmKxTEE9adroU&=U#)qLYgzq8c&JGtS|Es9Z6@8fy`BV zVlwHPH>_*1@qYV;HBv>7tn1*ucQ0}ddCU43&zZ;0zGeME8G{3J9Ge^Q@OH=Pd1gBO z^p15?SUT=VGN}GttA+NwYt_=r?^?5H)XyL-w>-D(PfAEC0 zBW*fq#o6F*pwC~Nu;PFm{~?yN74Z#+Tf0Q2bE=GDL5FA8y=U!7C*HA!)B5+UFR8M8 zbmRM0n?22g@BA#kyN3f2{9m?5{vWM>-l0R2 zxYrk?{l*DCXfGh*TkTOx2QA#|A#7PqZtu7-cZFE!rlt~$EnEI6CMI~r5rc9^iC=+Q z%MP9rQn{Zs{`YRs;aXCg6SiP!|6FTabb2?g%pz;QoUgN8s7_u~M0M08Mp_d^AHT@q z_U_{3`91n}?_~^Lk%)7HjFd{1u2AhzrSto`M3sKS)IcwdqA9clfjR=6_|)n)>PM|l z3-rX|oE}6Ue6ir3Iik_}F<+j;@pm=TG|D(L8nXu`7MSr4P-zTp{ZiGP3fkfP5-9Kt zlCGhxuNyj0pKTo+Ed7M_u#Tg}IOeKww5VzRvktQk^Ms`F%vC8T?Q(GC68*KzHD7*j za2V*?Y`eUJO`x+?b{qXZ&+cUY@2pj7>eAcZpJ$)A`bxW=cID)c@UwZ`!coPYoB2qRClEsQ7P4sVSk zp7}@f>Z;3a8YU^_L`{orh=F3U#WGif^JOk%>8G-&jgF^OY+ZY&>>5x%jie4XCrhq( zT&;xf=7n1wzblo{{&>SCQS%mC9KDxd>qw0awnS<~Yx=3)9{gZqI*Z!Id$Q#VtK8TO z7WE8&@9MG1?>ErNGN*|+F9~`$1zmc+h)Y^g}#_K-1eUjvC6#OqC!@(&|a;kUUxbWb$qeC zH?MFO<@d8&*oYk4Q)=3Lg}npi-HBOt4z%IwMWLTfC$F%luwIRh7uBn;4MJPOqK?VX z4-3ARvZ%M@_q^bH8H+l>-wUqB>~tG#<2Oh8a;p2y8plf3a0uQR%h6!19bAP%U%{e2 zl8a9a7N0{=pE+*9=x&{88>yx<<7`Gsf7lLzO`)I5qE5+qrv&rf#G=mdcX6am9*)X1 z#D*MX>%dAj@^)%R0D|{#w7IEEeXy~4Yt>|Lv`6dH5C#c= zR53Hoi{Xyd>O4rA6}EFUVULZ+%h4~Qx*FiC$L?%YQ{Or#5Pzjj!@mTm!R7R_;iB_t zHO;x-X2t=Helf0Eq3mYMV58%m(^X0@+tt~LU9cSAd7&k^6Tlj%w_()Itg;F0nndRl zYHGhB`1v-fs3D8$%Db-)s*XW=CbQXJ0C)i8iLtZ$|Fvm zan2^@JYxGmb?@!=SXyzn)2Z`J@u3T=D5l2Qg%*U{quA()&W&oC`v$M{Sg_I>DmiYm zLMz01NKF-Q1S`d*;&DE(Q~1E<-Qd*H=8tSLalV)A)16ncg(qwa5kR8CiB;2VgL;-i zznb=bW=m#+XF8uz)BO{j@&ppK%y611y~KHdx2U57H#nWlRqAYkaQCh)PG`)5My-Ag zvsdteDfs|&I^k5XsrzVaFQ<(*&UQw!ftAjk2qba_pPSvmZQ_1rpU=zeb=&2p{zbkU z?HJ63d@#d1IZfJhK7(s1=B%v?H9zFkvEhq3^1X9gTe}ZX>9@8t8gYm7Q)-UmRA;I4 zDb?x~LEzUh`*Pkw?cErF6-_q8&(Z{G^Mh2^Wb?3L)y{2dTCK91v>q?os->07oOYId zkMp3KMwWovY-|h;P953TIKMPxW<%`}Oa;8?S2zW#t#-QERrfg=j_J^*VuiHX1w8fMkOZ^83nTFsIPz8)L%loMD6NS0Wr3 zBS2VCW0z}VB||9~y17m&-U}zjZJxT*62w{FGS3auX5W%J2;R-+#i!gfg54fi8 z9!SX-1P(*B&@JKC~wOC@OJxMw`7H(c+^KAu({iGnxwWl|WOou@Zb{RakU zUwmlDG8BzTkd>wp#dW>jEC;J|9>;0jwNdc`-R+E@q=t`vipkPS)Xo)eiR8L7aI2V@ zP?(UE1O*u(E*tz^t8Qc;IELr3G;y95Dj_7b`5DWQ8|vZl^o9m?Qd^9At{*4IlS6=d zr6mfkt3q+Q#l#Ei@&9Q(ZG+0gNh7BH)q@6%ZuO&4IX@xi=4lz?m`R;Rbs9gnENgmJ zetFL9q1Rt~O}9`qfWlDR3aAa`_8&QlcaMhy_a<^zKuK*C(YgoQ+cmRsO1Ijsd%(El zp7w#>VTwC9gieDRml_Q;vec2p^{JxR-KCCMD%$eAb2xSV%qolexXjT%N%NCz5*wOq z-wp=nS%;OJ9=j~=Z>0GtwtmcB>DVPRIGZTuJBNc^nQng;OS=~+#&doLuFMqr%``s) zYYtrEqp0IW2hL;_`YjB+;c=|#e_O%p*3pUc}? z9%}0;)@86R9}LqMw4cB4Y4&!j@3eL1%-$wB`}350zy@XTMB6? z{er{BH|0IqUWBo!1q<$9$M5K1O*|3Fn5WQBGO(qQu2tw`1p@M#PBj?+; z0hoPMdd6yDj)C$(zCgZJsA|G;`y-gEp?tXavo6*0;E&{QFS0K8$ZuEkw*#!p8u@K3 ze|w36F~jiH`oMSX`qW|P^2x!%?Cu%VRAslbA+`1$YC2J6kD)Io*#wpTW!B|kxp*Nj zeu#D1AiquHZ?DkSskS)QZ=;=W=pk+_UZp?Gz>sZ`Ik1tC_Kz?yVVFX&UO3)V=wD@E z_wjxaLof3+M@eeWr?ArYA-dg2xi*`XN+#Oh<_uUklA1uiZP|Q|_si4r8uN&9)zgDj zzYfL-ECwd^Nfp~}a;D02evHoCX@i-}avRKK^lwl@gRLLcuCy6BZ}qfX(Q&4^$Hry* zH%Ymg=UpS`eT&3?PMF)QwZYs*|29>vvtb@;<#~9AVorg1j`cW?%2m9}G#lh9PEgeo z5F^|hZO~}bzelA%Vkw+Cg^tDbcD|~{0lofx&b|9QX<0BTFxlJ;jL_@Idy;auVQGW; zc@J;R)Uv~dC}2BnAKba7@(0kFj=c*5OK=Pg{TSpT(DSYF_R*XZSdTnEQPnpb zPwjS&r$1BG8StMWI6gF3}mnSpG1cxOuj1 zIDgjy)9p>5=H)irW+)6gmOq+*{}WN4pHvz2EPovGtiA=9ck~t;Cm{v{%bzIM>krmz zWcdX=PrwGk{G?-m$`Fn#W&2JQ+q>D(U#T)g;7XYncqaj}DI|Cqx=5BklPC6?%SGlA zJC1%8hA5U_!V_l>;$-}J$3T^#9m}tf^M&X>s*h&*l{~4H*dtdA%b(Aa-sy!VHutr= z4G_zxf^9amXZZ`|@*&Y&Zl?p~FSf%h>M+hJREAiVe}|l972jH3=RJsH`OD?{hH`Pb zS{9KFCYE0%pfqiAigCBHtc#X<5*NG<0Fo zTAs8p9Qo4Hvb_)(4PBX3%M)K78Ei6{Ne|2AKC>IC?C;VDeoz!PhYLD*9nL~6X> z{00Z27YCvnlj?cW!so!n_4>(f0^v8n&@qkj9nL;%V2*P)u*l1#XXM&f+3aCZTrs3E zX(vxe*Er-JcW2UG{$5+dYaYpKPG{17x#m^D#xoE^k-y^5~N;dJMNDA$Wg z&HQWLhqm_Z(z_eIh5Vd>i+zUPO!`PJm>ewFhe@CDch7Ouj=f4xXKvzP_hr&4o-QT? z!R*JRGjh4?V7VM7eaGMH&)K45vM_5#xd~YcG~|->8;*?{m*D88RhZolxcW2coXx9b z8B3iA{077* zXS4&h5(>kWOp2Bhe+bq!m`SnxeM_w!gElcZXjd`G%o8DL%0oYdNp`tVsEA{wUF)5zE=UK3Y{`|D_d5yL&wv;vc;3@RQTgphS{u}@qNxQ1UVd1;exml;p4O1*> zKv2I}cR#AXU2mj+Z-|PdRr{SsBMj`e&7A@{VT$D^5HtILvmb4I(Rqu}kY96U0GD-- zwP#2?qSP9oHD(xvdk`nMCj(Bo56eGBvqN7vGoi}&zVmLX{oI+xera+doSp#(-f!ZV zCTFv2FSM9%cg?50Z$ixX9CylEkA`bl>3r8V6?^6#9LlN;V=1T6Igu5dfSj!|jHCQU zXDa120_A5nJ7LLV7|*;p;!$jn$DO!WR~W8kn$KhiCeYT`oYP>L?i|nC*5X*;I2uMn z0%r;h6FDGIv%lK~bJA+P3kvc`y^g1L=edR|IShlLh4iX3nm0KNF)5wz@J!cJ(_7AN zbm9vqBH8f|=%AUMe$N@BVuyZlW&`d?)O6e#&+AzZ9pN)h4g2~7C&G>bOj$qDP{6!v zT}>+XhRT(K{JtPGc^^4@sc56k1?!6A&Yt{N46kIM4D#e4aH1&62iOJ#dDXkncZ?u{ zXCHUUBzB72GJmb3A%n&E;P|5AA63v;Gce_9SA>$ai*tR8*_g_zdWwEb)0@s{-cAFw zn4fWN&awjOlIB;)0x-K8^b6XcYlzm%)$>){az?#On zB2+B8i|ZTo@8ZQR} zw^cBd(Xbh=actsX*Q0=R&cU5CF z=TOWx*Fj!?SB7CU$S}|4;6W2w6kn0wZ({iot|=f7eO+sKMU{L^+zj9^1xMG#C6C)Y zQciMBhvKxWnW9r%@vNlCwF)iVOu2(xMdY~B^&tbCJCJk>la4#HV6<_@`J#-%^Lc|Q zEP6a2+V^T)k!*9BYcvMsR{o(QZwWI3_>RaUvw%reu9NcW2;&oJmQ(#4s#p$naHL_j zuP`j+iD`W3$}X5}UZ_kiR7EWxIlttSeMZKE3ucrx~n5Cyu#&Ty9c@Y ztN3^qv+wV8MFPYC2hqhr9F6P!GtQ-Qy^A?bSP{Z=KH&64jmxYuEMYkw*IqRZ8{^`) z9fsR3w;pciK*#f*yd{tS9W37hJiVX;yhFHBRv4D@*5i2V?Pb(?LvH;}mNQUpWk#@- zWt4Nodt}hs^Lk{i_AcHe3`O2>{mP{6t`^MF-8{*}a{7pSRm{`k z>?~Kkg2hz2PRpgaGJ&C*<%=#maN`Bn2P(r#4o)WyPG1=_cyL(5DtUy+HNe#p#Ej?^ z@hy4YVcyc!g(mXFu}W5Y#I+L5-a|f*>ogz9NV)U(axgr+$`%=Q2UGozE(4o-+_e^? zznaD9UB4+g)yd$Uar0kEe0~?m;1Yug@ii(|`;qfeIbjV4MmUcp{0`4>AH~$V$~b;U zv0HP*`EtYe)7E3oX9;A-%-4sE8F%nrM6oxGViK+iJ+6TaG@(7<_!n&+{>Qm?$slduAmQLZ6mf81c$~lW2= znGc}lJZ6nzc|GM(+rq@-&L$-%I9+Av>Nz~A9J*KKq27AQ4*nFc$wdp7xDwgW!6MXS z4NsHQ;=Jp!goS~y$mU&!ZB&2C^#Vtjg))JpQyy^^TLVK*4Cr>|Tkd)gY~v(^6 z@J70Fa3f_*8ORn+5Ep4whG*#TLh%MVI$exr3kt-=>TtuencggTG{{Ux6u8-9BGu0p z5i#~Q5iz!(5q0ePDdNL`@j2QWz~&OULyV;B%ETC|-zFxIxL6Eh71PDLk>h#VdONm} z5>>khT0T|mNGG2`MV94aELATO9hCDZN>vx5$`doi&M2^x>h2bCbWtMWRL8K3nr{~c zst;EgD78|IV?8Rs2dE6YX?l&ghN{;App<4YoNcNU#~^(VZM_o!-A!T{{d%uxrGi>f zL$zh1mhGJ{_D7Dr(BBn*Q)>+k=-*VTUoJXWr^O=9o(%h_E+FEB;|>ug9EKODX$Beu zsGs9)KQwt!Ol5eH)>euz*1Si&32z4|W*uiY=8HFy^(HZr)vggIBmE`%^*%8hn%1I` zU9nc&sa6{fX42U*bXy**O$$Vk8gCYz^vZ2w6cyhucA$2*i0v8GiZ`PC%XH>F5od4@ ziyx_thC|#%1TFHGZuGo$BJSZMSDOi!dCML0z!{QrwJ4Sm>h+dlef@oqx8)ZCx zgBl;jL>zrCER4GC0cIo~6QKoSI8G~fp%vhij+zf)oU+~mx1ciMF!D_i=Wi!OoS+-t zqR$VDomf({_%`0&rj?_VIIsRl+^3E(ywlS+*Ea(W{%01E_^lXQv#{W=vK30H92r|EQY+ofR%xUX#mB4+;4GlMi;WRDWE~U}P95>AF zEK*-xc2J*+q*3lS>Gu+|j_$uDGKW&^QUquU07UI&zqr9*#Cs zx8?CEVOi-J8Svq9wxb16NhHq1L(8j0>s~`C^Yj)?_o-9UY0pCzs}Rz}f-V==7voxE zh5Q%Qk8c-`ZZvI)=ti%WMigU2N@JuBG_ov0OQTYaH+RgLvy3+kHC`ylbEh-uo*rKO z=ka99fAEJw3tb(|H2o3xZ6sbHbtA{4?m|?Q%Od(y!Mup*PTjq5(Ud*8D7^K~sM7^qRaK9^78HPjeN(W1`4PjS6?bgkU`srwaG-~w&(uXvuMY@_!T`jfO z8ooz8zfj9TNum=WbmC%?UCfCDIPd8xb&nWsE zc^AeXV{c87x&USbcb??#OT%>TZIpLd+Q`<=kP1}rJw~&RN~@@3q&tGGpD7inBaF(j zvZ7fPlPYEuBbxe~QXK18B3*9?yGmAE1+;}w7&oe)yeTkPqj*!6?yIO|9iVA^TZ&TB zJ-0|VXbn*JH)?20qT9*8eZus&O244FFv<&aU%}R{lBPg^Qs>rih-R~!?vYM%<_hKt zxx?L{I*X-!YL!ukIMtF-$)c7@-)U7iz|K!`L%57yDSd;wjI^+ayPTJOTo*P}qu5cx z$KYKI_&`Uqk#4CIjL8>BQyA7} zd(<374OdG2_*9k9BX_v3XEBdRX8}Mo)n&T7QAx4NvpUN?g}t;<>IE=ksII5`1NKS1 zWXD^35+BD}Y+j-?P=Pv3!_M_}LtnEo(mj+twOz_X{T;|RK5itf_&Gf6URQfPD|kj) zkCL%9XTOG(&pruaZprh~Og4A7^lMnyFvM}6!qAOy6$>1YZb3g_T4fYSnd-*T#X-3& zJQ92&9b&H@meNs^g;vj$K=Avz$53RkG~zrobU4DDN1KcB)&Glx1#6Z@v&JUrTW#0~ zjbe|750^Z_wnusdaHf7}E^qlo?yJFQ!k?YbX%WkLQG)8v-kVfrmXqOzbyH|exP6Mz z0qwJR*vfn)WdIf@Z5`&ut;}cAyE+wYJy$*;jbcFaN0sc{SqTA%jS_h>L3i@1Bn?~i zowQK>|Fw55fK8QWJ15Qcq)n41O)pTIoRiWErA^u-ZGl27H@QiP*ZH!ZF-j4>BKdY>+l~6@qw|PX2O7 zAr(w^%%+YLEdku-ut=!q04G^R;j~IpP1IO9-L|3zti^xfWG8<^u3wPA`Ajk=_2wY;NgCy>4`jLo#lyhQ3%JY-PW7(`jB7`t7V!@+v{GlXrl`cX$uI`E|9dHe{IK=Tq|wc)(DiJuy8ju=8Y?}X== z{H08SYV^Q1`6%!}&TNW^4J%0xFoJC2%o4_lW%Am5ZLrFZe0G&JDVGSxrTf#95u1^W z%wjC60xwE~W$lNURtjE1ezz@&X}cbL2ZIGaLk3qSj76Km^w9&LGPJ-xdgO#L7w|NS zMi1;iZ~&9ZW4RE#_CX_u4L7SczY&7kxL8EQID>_wxG-OrJ z8ha~~z`*~=l$dxiNh6RRIM30*9oR#T4F$yMBYNoDPtltXjf#tw8J2p2BHhW7{3gw9O^BS@V0T@e_wj z(zu1sm2`JWcq=>Tqp5-LJX&Hlgi})>+(DyW=$1~WuXAYCyw97HO$AzE8GoMOLXk%t z|4Yx^?nKtM6UIb!X|QDO3~JVJ@nI~8ZRbA2avHI>+e`|*=ZGik9nN9sRcEF-x^ulL z|A>*%pAeeUXi!?XVYEwdh_vd6aR~JtY%o$wA46=!z(IxMhL3H(AR{q#p?&+iMbY}c z@sS~uS4Wixz}Fg?sY|5Rl`$^L7$4h{Zb;GfqGxIlF!75420`h1mlB>1??Tmq@T9N4 zwFAs(B&lkHK}75I5>THCK9{OzIc%Nxm`U76CV@K!(geNM!qar%uhBMY=@y~p1M{DRqT-ka+Q;$^l6v@AEY0ln@VIDOb8Qz(4Q&qGD^ zrZZ~==*f5l2-@&{$UV*hrO{krH{RtF_r4<;6iA08O<5?+LbT*5UL}p}D;(6RfxbQ~ zJM`}%a+&XiX92rGZw@t25kxX=c1&vX-tB80QIa&3&FwCYkWR64g?aSg?=XJ$%@tP2 z-VFAES{#R^R=+TmYny^k-{|Ni-aQDM=Ai3XmJV!ex z3nOSvqeCslPZfTTiEfaDS-_5>wm2qAi~YiFI5wCL<}(kXRQQojJps#{GIkMp_8+oM z)i55jfA|$HyB`k709{UTE7l5&P)uwd@z^t3CxFz!kHsniYZ9=S z8{>omY50$XJ-GUK8Z}-QO|B~4LTT+LAx5vBgy3E|dM97{;KzamWf<4=xG+EsWs!Rp zv8}IMss3@{Bb9oJN^z%?9mtU$+a*j;f>Q5x<>%A!40Ck1loU?}RLL(;VNK0%lWB#( z-e{^G6Q51*AJQ3>efkhTJ~Dxdj*AFdc|XcQ%cdB4`tuomoSMyh>p}-^5<8D&(WAF& zBk0}(*i9>bD!!XGkX{j-p8+0!tLO-|-mX$3;bdC!MPji$HNPKaZ#zZ*x2@?^^pR0Z z<^)|=6IhPvDLuUzS>CK*52TYM{}f{iy*nW~QZ3Jip|@fjbRtLDLo28Jb-P0AZtc|} zSynrD4m~(S+tuXB_k;ME-=SKYPt#tANuz0fEwT*5Wg z<>x0eG2p3eQCi&?7pttSApib2JB@o$jAY454VHE-%>{0jb{Eo$P(Tbpa?aMS`PMG@ zi#uY98dJ$X#N0%!lT``QvD-{N0pmWbewSkjS8BY$?B-}@qdJ@!jA`V$)AR?b zU#tE-Rlcm8M6-WwTu;+}tt?<6uA6?XoKAo8K)5k=z1j;~U*t72EP*ekf>(_<)BYM$ zSu|GVOo}uM(rH2b%f#_Oc3$Q)k5uLcsnuuBR>GA&P-`ln{k3L6o7Lu#lRv>+LvOrp zG||pFQQg;oPNu`Cq&O@2h7|zFi+Pr9m*RtYrG0#g+4dHRXQjmHO@J>_BQNKbB>TPOiC| z=0YBnHa%cUq!I7yJhF)Vu6K1?5V^`r)HI(TVjh~Ac3r8xjZ|JSx4fJWHJ*$mdx=gY z`(|B&VYCbsDPB-0UVv44M%R?uP^?lsE-OCdC`$p0j5QRJG0bYD;+y%6O(Q@Di>%{$ zPTdybm^hdJbs`&0K{wBi$Pc`LF1@b2Ze`ue%5`74buT|T*vI4Pg-?IsKw+4atMDDoja|?vi~`OE&FFn{myC+ zD(Su#1v4QC+(7ovv_jK+_UO>UUkRbAUu96Ax9#=x=pO{x%`PYNXl{+P?ubxMcu9{A2bOqGQ z=d^+;5*;B0PYN3`-PR05u59f=k_N_zcMAz8^ozwv<03MsYd`)T=6tBQ=-;>XaBR{fZYKDP4G z)V~Sa;BzdX>gQ}0>f0qORVw&gVCd08X;LroA)pi+ts1F?6GvkvUnCv)jk*9j2A@Vx zk{-~Ad)4Xy7EOIv#d@W6;bJ|<^NW8ru%Sey_*7OALAaAqd=%APhw5HJ?w(dm)O@e17{=PD97i|aud=e@`*5vgRJh&x>LukRl=m(2cM+SFJd_+at1R@< z-v#Iz9Jd1Y`dp|+V9i4+wr2>%gI%9md(iY=;y7uAU0jM=x<%sdwCNG!f0OlgsY)$Y zbCM=m{9L8vS3LBv6(~%tbs`fgkCbNj6bWUxm8$1i?X>28)mU2om~}a|L}GiVMQK}0 z(_+NUtd5hY^(E_1STR3Q^0!@Fvs-{$wHNFr*=Vnns)s~}ILbdK)Ukq&lTf_PsD<05 zV-C@ciWS4a+3PF%H0n~|R&5nap&4SH^tS<`3Lr`YZf%vst+$N?L1&u9&ax5QD@6_ds&a5M4aK_GwK+5ITycwu7j>*-HM-Ju^j!Z1U@9*(WAQm7g`+>DVWx zPn1}3ov|AxU*scvz&wL|wt`vDFn^RHD=&cPF=HOBS!J%G>3=olQOnE9IOaE8hiP=j zuZ^R>(tU`*myVAyXVML?8uOWQbz_Zb-Ix0iF36l=? zPWCN07q!%xDhb}jMqJsvL4A`gr+xn-7x+HT>q6#p*_+>>UITA~9Dh;vRtCK^s+So) zq=F~f*~>hU_PuHBk2750gAja>oz~LMh;a~`5zXcE`GQn`(s(P4%r{%9>aWTar7J*p z=bMKpOM`1b3y*{=}39vRflSF3_7(87#7g_1q^kSK*s~#!V`{9`k*mOsnRU7s?%!I%D?S z38lfb8R^3oie=+7MkS>QNpmYazWJ2{gCo)gC6j9{v_>#tcX7764kgUTZgUmt+?k#Z zuPe9^I{0qdd0ccvqwGxSCE1x#BBP4V$yrm+msry@M6XiIC87te*LS5}{S5|9t?b$D zDHmt!RI?!+u~zG*O?BekJpaJz>V}(;gQN^&-Ayy+iXNW7|E}>3v#>aEvGFgxGFR-b z)bRJBvuu=93q+_nK(*q`LOZrQmy3{V6RSr65RQ*FLn!;oV6V0m0RJ7U}PDx?W zrt{L)EUum3EFF(hWx{kmqB+rYx9CJ1Zu9-N-5kAgk0`J&Y$5Z{Y_lo2TD*@*Np=Ml zsZo4Qwrg9-Jv263g0M9>c`fV2XrN5>w;e&$Mj0#J)_n`XCN*h%@Z$z_H>a{d`qgYPa zt$ccC%-=Yh*z|z-Iy?WvwCo^Y$HfndD`@&T&JgOtI98I2VT~!f(_cG)QGg^{g@;!TRO0w zY0(FAq*Hf^kIP-=ajJSZESnD8Bi@hiPF*c-Ip3+~$lusbT$$z;spzLMU7(z+3bqo3Ys#16(XVy%k^?^8x2tx0f*q2;e6(6I!Emc~3E z!-)6%6ZGHBVFZpON7T~b^RnzqjzLCaUFkOE5+6G;jEpcnVy z4qF!es##3Z%XvN0u_r<9Eh5m17j3G}{+dlY6RoLx^0<#VIy^2qvb{@3gj*x}u#VfI zG(w|#0R7l|EG~-9>Ef-dqYkCat??e(UWgSR>u9#lO^6Qt_O?*h#;1%im)w|Ti-_C2 zBc6?&7+s&f$rY6W|7D$djUEPINu*gzeQz=A$kmFiTHJaf9dPln9H5qF(L(DAkcj6W zx8O@Z+9J;EY|ezxrkBjfmX$!mJ}_p});?S``dwj*x=T}wpjImr6v_m)d2az7Cc9w{ z2E{|n(Yj<%ER_evkI@8AO71C|zU?-Zje<6FDj`z7)f?qw8pes%UqJ=dPMxC7F_l>kXS|eMkiCj zu6smmsKO@vQ29jG-J+N0_ctar!0p|9gM6%Ew|KLL--|OIpnGOU#?hM&UXc3zL_{PV ze-P60B4}0oAxX7Ye(@|=Nb-bqm>xT!E22l=(nTfmliiT&nd-`chvo6JEI;lt#-u2d z!P9KI@flsWFd$!ayci5C*Lpq1=qoy2=}ixHI|ePokb9+tT7RjVPm}iPA|o%11TU|g zL5&u{s$y^nojRmT4t?05r70?Xys{_6yY?aK`@wy>bS@AY*)WuP{#F+r#(opMUpGTn zmz|LY&*L*Py`&RE1uN6&fA?d=fAK4wCJcf*pV2<6KJ4VPI=ey61;=q#1!vuDF5o%c z`Y8?7AQtsut*Xq+q54I-?zHAJy_tUg2b+WJPiHi`RKdnsENd>3GeHcAQ_PisG(WvLD6isZk(-_bpP z6s??ko=UN5l6-u~hvLsPSZVcEC@ZK4i7h)mLzjDXi zhJ{#tRdgKdN`udd63_p-IlW;4)C{NKgYH!IiMW<+hn`raZkUhD8*wxh)1){JcXJ#c z{8DOOWYKZN|3wUP3gXT}Y`pbVb1E%gtLg$utEE+wnUjsJEpq?VQbDJF8g+<`bp-l; zYVJ*~{Zx@um8!B);v<$^4(kwCSURm)Vz~kNjkuK-7o9s26;0>%M%lyIa>e}{a~vnq z^re=+k@|2{E=_;ha-1@kVo{LIWa=JXiJ+xpEM^XrhM!nU$-EM`Dc48jKueku z&~9Th=v=GESZXEZXRu1_o?j)CQvJs#PdlUF9=}e;4C^Fmn$8IpEdPe23U|)asc%*( zo@s0LH4bOI1U-Yhp#OBztfJzc)}3rK>@_^!k{X7hd6QZV zn#!YHS+9}s^9uOqZ{pi;Q}YC+Aa!>--_q(8+Z4*B+$qI2bUxCpO>bDHQfy`Y$-a!C z|1gXA$AdpvE}r4y9lH~5esWo9?$j*s-pX9T9JW{B2g>Y+_4K#_v6s2PPm>Ym!m(ou znf_|otny&5%s{_+WwrTpf^$ksfJ*|Pox1?K85ezL=8%Pp@U3N+d<|)hU{WrzQuujR z23#7PseNtKzSqFcix1j=(~ff2FPD|9VmTdb=$s#h!|UgRp)$)~Q42MgbUQO%wX-VNYaaj3Oo0?24%gPzlrd%_`)!?H z{@CuHHzTxsQ4EcJS!bq^E8@btws)#a#!g$c)0*BKcAR#q)ypnvmMaZ9j6u)&jB41b zmG^~Y&py8-rK5}G2lBHRaKRw+c3I|`f0db2R~VrkUV0G?NY~qz`Eo!M&+TlVp1B!r zB(w6JHzIN$X>UECT#&et&CB_oo6>fX)Z`jmvS${G+b%aFtIWuCA#ZuL*%LTJ!#n9r zX?By`N7||eW@XGkI\F!4%tA%C;IYCDI`6>QIihj7XTa%#f%ilFA7>l5NSG82|v zT+})+|1`X9S;t_xl9d2LfX;leQ4C;zwtXxjGw;QA;CvIcjr;Ra2{XA_rmwk?Z z0h3U&q+_>mjD76raRX(0^l7p^z`Q{l4S3rc&1Hqo47G9#sBbSrS6?RthYMe!t=pT8 zCFPSxIRopE-mk;S)`spqiHvP_U1ZOTOxdOFcLVB>4T$#Q2IydPj4e*)$>|puAKx*iNvTmVwXGD>5RRmPwYAiNwyFsDjiy|yTXLQ)BIyd-qomQ&-s_oY2 zykya!!Xd-ILa`_&H5uqg`^}ft1cPiMDh^c6t*GnBD4W)9>Oxhe`_*zX+K?+)$9Y4& z$_5xZzU1a1_Egqgx1h~zrG$pf*sfxopUx%4*-Y6O@NNXQ^ycN(vNJr;X%DucfES{H zxK`W#A7pQ{G5x3NRu`^N!1vT(Qi}sBc6^-Dn$R)fv$+6JG&l!fC;`2lj2DRV%{5tL zSX56=t}ibe@jcn#vU`xs6dZc;f5>6AA?d9Exhu;#Thl_Nii!%A>aZ+mU7H8#<9qO+ z+Pm;8#QFQOvjPxD^<)LyWypJ)m*s`?fsi4Y7w^vYQR4``Z6I5=mE}!!;X}5|7!0x% z999Bs{f7TaTmj@PhJs(`5x6E`?3Xb1LPszDnr_ki;@Xe*W|X#15FTby>O6k?P@u{7 zI&WxyFgxrD&2h6n1+Ks!@cbj(h_u`fMu*)DddE zR6M}h+_6%btO%Q+n2ZJ)X?SwV3IIqS^zlBdq~reR2@D8BjOxZRryom})6HhazdF<% z?PZ}cJ)L)Hc<}l8Gg9*`XR(qNPjdo3nWhPs`j4BIE2y3y&tv9nbDX&5qVUm0;z(T>^9 z(Gsq?f`u>gpOPb_ijiDXoXa4^(q4#3av?sW=Il5Gh#U*kOTi-NdXA{lIg3ut(_|vM zXIw6sG#ZPvw91(%fBmm?d{4}sbmv&-YsA$$&w&OlKOo)E_CV51+x^_fwCnnqpVPs! zYLQ+4Z&LNym~c+_t7FGrI&y5oQR(0TTRCzO*CB-7cZ?U zc0zHt1DPsnDs`%KO2q~R5S9&Si@PHp$zGuXDshwdi*x7BQT0k)4$YhAOpV|_-#%pp z{Q1}Wc##gSqm|amOy|F#Apd}jjV_fAoZ`DnZc54~wW!Z8=%i%8VraM_Tbi)g z`53yyIjMfV;NcQZJy$5V9zLRccw zmZg@aFL0$VaHlVHr7v`+2N#@&o-<_gk;a_B9wlvW()}6p*t3T}V}5!GUgL?+=BM-R zGI2=axFD%C8Q*D}DcXP6PB9KtR@Ritvoc5z@@o~wQwfb43Erze){w?cr0SXo5lXI( z%Ba*((TzIV>DB@i&jK#rLf@#)mW8 zmy@02_63eVPu2f0q)^c=!ytX<`N$uk_fgRdz*NQCBC@*W1)2Q}UPzna1 zte6+%C*#jlGFze&XhvUyIbF7kIer*7ut7VlB;M!cl)&QUbXdk*x6h4;$_e@`RaP#D z$JMiyR$B2gG?itR7p>8o;^edBd4fPqO0%JJ+fFNWd+E-5!09g7Y0yS9ju$hV9vJzy z*V!&VWk90P2?mjenOv61xkHh!ggKub1oZT*RN515OJZ!W2s*Z2-<^^M7&2)24lvJV z?huT0WRM{b2)03Or>)l+GPEP@fgJptM*ShoPwFQOi#n6b>SIM8*H4D+#U>)rQwDUVxhMF4Q4UT1OaLpYvgC_m+WVic5iwR_2j+R?+NebO91nio$w^hY=Y@A1t??a`b139LMPz;#netirw430BI>Q&eS_hfkjTd8qT zTx!qxHM82B=i0!`;2b~8F~jn_c&eD>ViGEq!$R;7pZ#&u-F`O15SeRfqi*A1`PDL#*7&#Uj}E+^5%{1 z-!#E6mGz#{N?-4`h?_~T@Wv<%_6r`Ktc{SWo};s>*x|43SeBYP_=*i<;xw;i(*f)><<^T(z0B^%C@@<1{yWXF^XDrh|kzhg||2l z>M&)p$IWzVn?7OL$bZ43tpM_0`r|eT!#Bq|YK`{(<3_`=A6qTA|l(3IZ#yNVZ zsu=WN|Dl61ln@Z&zR%Hz_U~~FB-b6zGLpJFB3Te=CO0_5K_hBTux3V(My(37A;yFI ztu+%d31*vo6b^m`Mqha`!@uUFxKn9Yjy@rr4K&6KmbVDpk2=!GHA*mFp*%|vT(Bw5 zk{QiHuriF-#K{eeZ=!Ca zFRoi~S>(Z3`J@9gbbF}o6g%oUD?2MEXKG$fcFhcg|2gPD$RvJ7I#ut2}u8#RUser(Z( zk4Z`P2kK_cGcf?l{}|F7)vveoVQ8?K8l%mrO|N5nw0@{14ZY_;v*i=IVPDi4E+6PK zxT`nrk1AoOj-rF5$bei-_~q$FO9?F-Y^kS$zABOC9EuV#O*S5i>d(1C2elRzTo@Pa z3K%@p_@E_0g%y!kUeu*hb%Z_v5u4_NdV%?ro2m7=C@+}IX7KEnT6C;X6P2~qbS6(1 zN1q*zVjH{$3muf;zZ|ebA`0=IyLHtW-VLQW}OeHyz~R zZJ{Gvubp36Gn;i-{|ZaJRJ7O<&*^e7Rp37ah<$2GqpH2oVnBZwLiG!rpuH(TRlwBb zQD(Vl)Hp*%dGGdV%zJ(s``!2Tpytf5=!8~eJHZk=3m()3_Ob2dr_5G^HZjgnNz?t| zL#f4WFmxF5b97*#v#X>3*FCTv3b)a++nw=r@*VyrbkYO2Ij2UH03OI=%Y8V`z;X{o zRgycxFr3wijqEo&`%>(1n6oo|Esij(=c&3>*@Yf_9s4%N4@Ko@T)td%KQh}iIoJ$3 zdDfg*cKT{Q;L^jz@s|0r;qM>|43F@Bh|~eJ;2zfZp{bAZV|BhVUn#zq#lpNmF?1W& z7-m(J1{V}Bm{n0$NsVhGtyHlBJb;y;(m4@LIL5adx2o7*Q{!Jqr`KDo)bE8TE&Y$*5*b_X2E95P%H4>} zC~fyDz@{ssH7`U}K}jW2O_BcaG-?ePc)dvb!5&mOuqDHM*&4@ObRccO%Fw=vGxguBc<5M|VSrJ3xjLW|hW?;n0I4ttLu(!4v=ZNy}AUxV{M+IRmdOaGLDdkU6*2P}Qp6?$gr zdwn-cA7qwZ#4|(o=dWn#WiTU?{JSmJUu~})ajOagG@M2wSmbhyrkf@;4*+#8HLtUL zZ%~bF{h<+X&HBsMA8I?_#g)rAr^`3t(TYvSz8DPH#;&{k7T*GF*9-Fz$zMDEc zd8MKkJb6Vg!0Q?M7e}oyz`(DEsO>j&<=0l|GO;i)tMeYJsFBukB`p)C}oyd40Z5(b{^<9<* z&1`eMx9987{2Tw1k^B5F1MYF_eZK6o&f^l$`y`GbJKM>B6v|zfx3G-NGYNb{K8<=C z8iglKDxO+4>z)5PjNWhoM$ZuW{y!ul&zMw(z7&2d-;Z9r(IAp#IQF6bd02kV)kNh_ ze<>{ATYez2<6#h`u8hv_y%d^GVz<^0nXfJL=wzR0D4sEN`arP-LP{4R^11&e*!wLO zhHq!JQ(WW_sG~0tx5!y_>78AD&|SyOI;yMiYcQ1SzCR45{|}0XBar%<6D>Q@1HYRS zJ?NSfeVG$ISazZ#KsyYUft}3$PW=Wp^*?N@^ZFkWIr)FJ)p-Fdei2v_ezm}o0+@72 z&weE=KK%PL=_7tnlYYZBlYZ7LoAi<2&7>DyGwGL^^ilucaS`S$kG^Kfza?qq%Pe`s z)w1M6(LBfq`IVM@{P$SeW(VuQBV>zdy6?|3S@q$u+ayZq@_e-mJ^}SJeT7_g@EC|2vY(k<9PkiB$gm za8#g}`omEW|NE?ZU=98vkTI3nyvcE%CSxk_Y~oMBbn<8t#&~w#WmG-bvie&Ev3`RICVtt$f9-=iNX>_%`d%oTAhQUU z)A1Lq3IDyb+{>&F)RXx&^T;oq;K=@g(#-Xe{EIF6uT)G}4c#}xE!SqaOH~u(8IFlq z8yZM8+~D|cpWKG9s#rn0K97!PB>o?2a!WFQ4k;99q;)bhW$}gYXP!$v`Vyjv+-!E> znJktEVh34SZK4%dkY)Hr(_Rb|*S%%34tx&oC=UX8qlXN>ek`lZ7FvEwwD+6HH(WLa jwh1__Q-wjmVbqE!OnmoU5Et;RCq4-iUmBB(G%WrY}oDr9@I6iI;4q=YI8fowLr(n%-^3c?~H0zzUiM-c_= zJ{5vut{w3Ctcby0P{7`W_w+pr>i^x@B!JKN|9@ca&h7V}d+xcX-C1+*qvD#j8^orJ zL~*nr82bok{}wfuxb;ZwH>q}C{VpAo8?674^p}gWaK~cW_nTTt4RcfCXw75lgEm1? zP*KwTfY7kK#bUUN%GOxttro7an)>!d&Z+1Ggr7vK5xyH8kMON1I+R|IjzakN=tzW* zMTaANFj|FhXLK0CTcSe{-W07s*c2U%a8q;;!t0^~5ndY|fbi;Q0O9IrIl^VpGK7s$ z55k2}m#|up=0!^pnH%kg@X{zBN`15hVO_KcVQsV!;ml}0!s$`WUYZ*1iEwh%fpB6p z7h!cY2jPflHo{@iOoSEDbcBPWX$UWfrXuVgO+i>5O+x64CL;7i69i$kgD z!QZf=!o65f;ch>IJADXl^CGy#h2Ulx}p(2PYDHWwqft`NahSWDr`UI?z}32+IQ zLJn3+$izYk z8CWMF#fTsQ>w`b(-)lMY{TR*V1x4LS0sqhWW7!tv5$R#auoSUpwkQo9wQy;8ad^qn zISVg!)YmO(oF7?s#TAZFa7lOxYU#=Kg!(dj^-0yEN`irQ*A-7<7um%JbfS^TY2qI` z(L~4Zk546MZ_&lZWQj~Cn(1`!+&Ws6DmK%CJ51Br#60l@Rgq+DJ(gQ24iOdX*tZg> z@%4nQv}Q$mW7=zo{-PRdc}uyqOHAL zJf}O`C3&%RWChXCi;xPD1pN> ziI!D~z35=8sAv5u#5Yw#@~j2HC4dKs`BHD@94Wq`8YBV8{k5>FC1F^jN_9qA*gJ|2 z#VbkFX0oNw_*tTXzMEI5rzfh#QbTI1-XIGPBy^XMnQ?;nZ%N@xnMT<);%3$~SzMwM z)0v~%q7#{VvG}vTb-zv_J*Mk4w`8z&S5>`rIR{y-pYK(@QLHcI+l89 z-&c+l+IU;Bi8`_*GYic!ZpxA_)hVDbLN7X5-aDH*oYHJ|okO}pC-$aj zu{4`%m&A>u3rnOB%kC|GCW(FM*0j79Mg`J*otRJG{$Luxko~AmETC~A={k13Q@Tv2 z7EW{wx!4hGy%caQ^-6E(#3DLWRp6j4h0gKLAtWZyqSaC} zty(0lm&*NgIvV#jJJulO>%;&#ezj$jv#*pwr(VlVqEo-v5~*R6L1wi}r8{(De>yl@ zYNf+RisLALdqysMVYxKZpv23<{yFrAJ~xG)Stlj4sZl9WGz@%vX`C$lxPUdRm9~ls zCtZW+^coS9Stq@&6EC3XI%zfiGF(ii_$zGr?7#-8*{mdDOAcjIwn?{&h6Gu-DGnzn zo%mdI(B}1G27TWu6|$OE>2@(|FsC|_0BpaW2eH2m3+=Md9BkPaanP;z>LwV5tZzt? zh3mtFCQI1Co1~#SrUOke7$ngr=xz{w4<`&O6xGcWQ)J=hLapM3edH8&;!RzyWT@JZ zpDYVale*LCRi<-_L+|d;fmR5@2qwRy+hNqFDMEBjtXtF`!s+FxmT;;p+%vwrDjW5Y zu3c1c%nPHbF^E1gKGA)t6E9@VdU=sZjwBh|%pf;vC^&{Sn|SK?PWe~%hef^tgiH{| zQnO9|ihXxVm#h=VvF1cx(e8mTQV$xF_MEZOv7i`awW*NUa=)S)6#qUv?x zZd!erIHKJlE3zu|6r`R)&*sWhwWTowu)DMic1f)NaQRu$BwX@$e>y@fMi`SxGO{VKnpU_#`=-2E46{C;LeGAMD@jWQ$HGEC|r{qq<7kwMtH+ zhV^oM>kYC(JKxhOY{>?BD;i%&N4LtoSixrbJ;WB#(d*?j+SMZ0Qv5CQ1oldk>_d73 z9VNMxPHmJ`dYUlox^40Iq?(I_mv`ifjxxON4~f-1MmmztG4?6(nw{7cx6hh!sW zxeT*h&g5q`Ag!QgqdARby&zv`PHBwXJ5 zh8$LU;YF>$EDN4R9Z1;7SETXnU@di3I z!(gWBs)W9*{e=7n#=Z9D;y$wQ(Dd$t>x{zcrj@;AVfU);v?BU;P-1VeuEI(@YpA(2 zaUSh@N6us$KarQC`zY-`9N&+*zmPl3lCX}Q+?<)IqxgIDCpo@eLx+x}Po>gXS@rDc zpX6bvu%0TvNt?{w6oU1b6B~^w zPPy7BY;2oYAPXl8yGLlCSL2jYNw~T_URh{RiZOhxLOFe3sTk?;Or4bur71;5;rcCx zB3amx%&Tg-dge}74r6sTVW?Xup|YqK>v&u(5eclCfz8NP?$%2}6RUPA9*LFpQH*+} zuPoesX?F`I>X&4l)%qLSTQj40E$!;7%rFW!v@9vX7N7S&i`AAYx9Nqg=(UBKMZJ$@ zmn&8s?R`hhrqq086Z_7i*kwt$kvW`79~~aUNXM z-3c~7wT$hDj&r-y{$hLzZ5pa9k%VpS!@x{3sap9)R-AxKYIiL?Yp78+OD((7GRb+3 z4TvWQH?>bw`YMV`7H&yDzu*=Ca63xeO8Z_YIK=)@s|;3+I$^0GslrD>bK|9IQ;ip^ zGWcvY+Vhf>M&Dnp#2JNM8#6qzuua44*s9QV4N4jfo})Zx6n3`Ebz?>spHnw(LP0K@ zH&u4utqoUpj|h`4`dbVl!K~C zW9g*|L8{=8^h;`s%XiMIHYrAyu~xYZ&EMX>S$RlS{IYOsoHhdXeUlOgOuegpo3dM0 z%8=pe99omoqy1*(cWl>t+IJ{d$VxdfSD(#1e-}<>6}y$E0lE7y_y?%+i?}W9hC7u8 zwEZBH?^S*W_1oj7_m4@#Y1}W8kxhO;*@MizbkY-_OU4>o3JbI;FQQmmS?gMyVCnmn zFA>{k0`e*;Eb~$2bDbhQ5i zL>Bl_c@fb=bXrUX7xI&mXHW)Vbth?)dStnN8A#vXA6`6A7Ct{qC-e-xUMA9--;x9L zu}+_<7oJ4d&(eu^EwkBtS>KA8KF1mx^b18=eWO~*9=7Voqo3zVJeuxh!b{p zlck?+P%32Mcxep&JA3PkIkWXzTiIY)II-mX)WeU67s$d_s+P)7b{4?(4-|ieChW*~ zmwiy6?~TTf(1cwXm#~>7`a2POm5%B`TAfb)YlwBwQB%5u_44XJMC>SuIhj|pYs>Xl zVy>_AR=!~0ROnNX_Xcle8k;dpe>ddHH+|)lr>6H|B_s83=`6xqZl~Alb^AO%AMK80 z{-9n&3?HNIJ2W#rj3q$R{XRn}LZh)h2$Dg7skWdoI^={^TP%dVBhX zZ2U?+=Kw#ZqhWo2cEK8bFN0Dg3tO+`y##3227QT9INeq|45v@$4g%B%@)i2JEjSdZ zBf3{;5KsMdzkP@-e9^ail$jzmOwWi?^_I-Z{6zf_&u8rP=1jAWPP~$Sn$xk*>FAO4 z;jsgAPZIzk3185JZ_unjBpGOW1An)UxuXb$#rSgNH(TT z-;UTRa=o9vm6bZvGex%N5&c1%GC~%PWio&xTW1u0*pM_F;zMyacobSyYgOsP=k*S~ z@K4O;M{=#wm9kQc`U=)l5dO`?S2A-&mYbk{pt1A+!RE=lm0o?om`M$mtSt6UhrS41 z|3pW}WZm`1gX$z5)$Y^F?eFL%oQA)E#%DFK`7h`6Pq!<1`qk0NnSPW8VQ6a3W6#P1SC+*snxsn6n6A4O>;P=@# zpFiSWIy(3>#23_t0_S`{WU24S%4l@blaC#YR`oEWj0uPe9t%qtnDZaNqft{638Izu z-DC8zK!N%a)IIEvpqe4J_D+>qkG|?jNsObTtFmupJ0mIt^U_u7Pt^49>`aP#-S8Fr z!=uJ2%7xg5Wqg4zV2e%}?w5E^Ky5LR68fvZGtU#1yK$Ez?KiXRAgwWQ_W>Q5h=>A8VYz z1%wXUcwcGz=8i$C#sO%l2l*(VgN5!#?62Cyv%}~u@>VlgVWZkZr1VPl16tFP>Cylo z>fKN*ikTF>F>@pvJWTyaqJAOuO)-mhAJvcMBYQ*#g%HNZ)eGt}Rm`PecJ_U()3XqR=Zq<4xtAf?J!J%z?n0buDVcNOBu7(r8n-% z&7}`-$?ic7lA6jo#;8|gzaA@^MK5_(=!*ogCmnsmFqQT7sn3cmXRZ3FDE4CVI<=oc z87B+B&+3N%)7n%Ii(*cDld6cycv;xAg69U<&}Q|Z1YJ?%(-{M(wr5c)o4QSXP$w4B zgns%d?Bbi$zj0mB1h+n!rSDL`;%cG`-Yx9O@^`7nbYcmK%&?4(|5j*V(L2?z3`(^u ze7TGp13c`G2h|yp0_7`YZ1BaY^xO4z1O4Yo^;UMUO`T^@Cc<#AuJyv>N5(o_fQ0NsYkskw2O=Osq@&PXVq;w(MO9O0njpEQ2hpF zGLFWJW6c#OS>x#L4mHJ4cAImOEIb+hzjkwgkGq<^_nO*URA537%PHY^^=pVk#fh9{ zpG@Q5wb)qbO?8(f1w2&UM{&`?d_x80)E3*=x_8voI(`43Gl-LWU!jsrO~nR<0$J95 z?2Th;sZJa~M?X>9+2beFQ95xT9r{%rOuH^HB(PJ*)Ws5w-y6S;_I#{PZ1oui(QOwP zpnp21%I#mMIvL8M!LmM?G9<&3?9Fe~emZe5)f)5=4^OF|>%%E}?n^1jpbtMy z94xs*Ecdg-B9R)il?>$EmSbN}w{5WxP6(Uo!_G*3IUct&5(>HKyDj$qqKEcAVy{Ya zIh}rI#N>23!;zp9f`T}MR&BLM#4traOmc}X+U2m%CD4l!D$P+;YJ1e4n&LD0JgDb! zx}9hsgkd4(CdWQCnZD~`&(}F45nAwaVmj@;!F~yC>S<4tJYKr*G5c0%bhjK%>}U0K z)eic7PI~chVuchaqXQo$9pqrIp_gAtyg&-oQ$?PVO0PU&?N4AtXu`kXE|?R(N*B!M^G_q>`amAR>*)jo`C zn^!-grby)xs?N7(OYR6Q%C~1oz6hOoH8Dl1ucJJ&XM?&O%D1Q3JmntD*o8F#>l~qe z^HZkM-~xLO#hho->d%uRMl|mUdddL6{;wsLqT#u(CHAugBe7Oo9+x{9p$SD8tEY|{itGc# zIuc(`Ofk7o(_QYy!cgApiTSNxCRu3JcDvPt-drwkz0>ESd9Np4C_3rHc6$k*a#OM0 z6CXsAzPhflxSTZM7W?HK^wlNy0;%3lufCDkpY}gv9}B(xnjQ9G7SnWZq|ROA3^~0{ zs{Om2>lwsZ^u(Kqqp0ocq@KXPSNqyarEsLRA7G3ocfOU_hH+NEo!CqAmQ!}AJ-0X< z_M4pb^$|P)JRWC#sNNY0mEqUrtgj2=L0YgLzjW~J#Jm(&y~&T5A0_-K!I9SKbWyy^ z-UoA<^}IbGVT$p)?H5SydRD#Lo};l9wbbFZ=K+(OZnx)2k$S3p(SF3}Di84~2Sc>+ z4*O84%tf!>VRzA%V~O!7y7MLb=Tay_wLW_Q>$&q^NkRJ6XV2k4%k8{*vV zfIs5&m6tiW{ZpV0zdZvR>ubL~9c{Q?v5&+-IIhfol~nJcBV~3s-FA$qGr{@+P1-tMY$4A@P(|D7~~o_{^jN@*t(N28sJ|0Jc+l_wM9(}H}nYPf{8b^=l{ zoN&^b|0MNFbVVXgXE5Bgkj_Xs?F-nGWD~bm9NlNmw1k!|UL0PybXI+EX_)4u74)!` zfwDNu%H8DwpW9PLr&Gjw8hg1#HTErY2TGTh6fCDk2TUB!`^=`^=TR$`sG>tQx*mKtz24chunY`K1%c#thg=aE(qy5R)YN+8ye8Zi2HL~!+2rZQ% z^+!=J&O=5ci?kYA#g-l8%H3FQ`+mb1 z#8%LRZ2fFHQC8%j3Qzh0YWt&b9J~23Lzb${l7(-Av7tYDBCQY0IAr()@K}nGu4H)w zb3WGum1ON`T}6lPG3JtYCiJLH%d${^S_s56RN_D-(@yF7p=hWHq+#j}-M zHe(t)xFRhNTui!gH2ro(nv*@AZLAi_nr{4=&$5Y5Cm6$QUWV~TaAqsgD$glOC<+~b zAT~2G$7mE8D*dRJ#1^XTrS@TtCyj4u+|D+3=y2vNks#xaq^8E)<7~|{#>+G+dLvbq zsD5%jYkZvj;5FJX!&a8CrwDT7D*#=ybFsXRiAklQ6CVAd~o^Ph0 zu3sV5_JY)f{tj#XZKq~O_Ef0pja)q?h_}#$SM@0rUzAnEdwQCksW<+vA=(Z)G&*}3 z9sC_4TH6oVKk-g`OYM`5dRguvh&^C1O+)WP!|2_t^b@j+eO)h#)@1Did?Z{;5Ogyc$+yYxxgT!ms*Xyw?uM9|JSA z#Qk*iAH|>1!IGSJ`4W4u(ZAN4+V^vS>$z4Yp~Lu_e%2g}V`$enp3uh8hAtU9`b<_S ztk#C?bXM`Lalbb6M=7so&T(>#Fkg25imwGZe~j|-b3m1zHFBy1CHLs;@cC$V4ODV3 z8{1{sCWtlwLIxl%nu`H&lcIOn(Lfr!f%&ICw7TnZ8^#h{`m7vYXa-b$kdU{F&BW zJ#JOk*DiqfLKQOn>^<33qUPoZ;%j2Pz`eQ7``hkmfFH&@lq_d2Sxky(6kpwywMZ5Y z4&rH4^bf0zW=^zb^YiiL%{>>wxq^47MESCmqcnPm^=ELQYakm-9Kj!d3;Wc@kKJodc{vvJ}PbC0ILp(3=CT`6Z_5ZZM2B&U(%_ znx=c4V2n(o#!af4IN333+~o0&r7p*)Nsi%_VZ4Gs&aCbW9q1hlhNIgDV1X!lPiaesd6m!Ii^ga>cbF#p6-=vOYd8fKe*fH$T5E6 zuqw?1s5nTwrer0r9yMv)lrdvEeUe;`g5bFElO2;MPN^#KI30B*j$lc!#1R@`np8EW zYUpIt?=0%=;<^UaIV#5ubJPuTIklJIAm;$nl#U9UzUG1(XAzh2Za^| z=jVl&gu(@NCC{2S{K#gf!<(e!_~I_UZ|R%`Vf07Q?McvsRHZ6( z+v1`ui(`3g1dbJh!sN;jK<{`u6(=clXJ%nyI$(Jz2WIDbEH4R_a3&h;ao$I8N}7IY zZhS_5i6eh*zkCOlk}qzFqoA`iWcG&=deVtMGIMeYQK%;0(bt9Fe!Rxn3IGsQI>j`l zApcUwr*v|TBRARM>gO!XKZ`$Ae0!0YT98784<)2g_02^Nsj!fa|HILj98z8!&2@{Z zJTH^?r%>e^Vj7f*AAD`^*}BM@nm7yy1r*Vo;TDHOix$^YunBG?Cw_xgdCk0(-n4pX zauQvAaePL6sTbSAQQ&nteI;0m042O(PJ?0(!OM@?5>x+s!2qPC1L&8fmP|UPS`D-! z0gh;EZYegVIL3^+u*$J;+5GvAs!2mDtE(LOfkFpWFS9sl_btVJRChtJu*5Wy_AIkp z6vy8kK$L}{0**MeuUA}p_vkWc?o2rys?QU*TkYhqr^vd%_n#74%f2y&ViuK}a*!yqZBRfpcA_*r1sX#b^?%z;v2rQL_ zJHdBPHH{&+%k(d!`1aLPmcYlPR7+!5x=q(&An#D~-TBbxdQD$~lYfu)ew;Rz)_jpH zvsu@r6^m?5qiMZf|D+%;7oJ4JZR&k_m&w9wBQS))^!>pE6E*ZKu+nX!)dKUl$xTyN zm~xHcv281s$-;e|{hwNw_A7ht3R5{K=(`y32TWdRf^}N_uz(WYGr{h>#`Gmloa0bg zBV7_r8tjjx0%e$*<^Z4mEi_lhQalP z5Og^lFu51ap9jY-2NBBTWRW`cewJkdd}z7>>-29XpFp}!{7(JB)4niqz2VPH{zj|)3%%`?PO%?OnK*a+E4ylO+%l1^_|bG7 zCUu6KUz*_W^pojBRo`3CtrL1TSEj2^4_qM&XIA|M@U)eExac6(tjkquVN&q|5_k6u`&Q{cGdFql8qi6TvJEBpZVKKOeb zY!VIZcZ*rkL8fYGD}>EF-b^}^Zq$fz<0n=X77n5{nPxl7vztHCi88Bts_+Sk9mp_` zQ1tL8KPHR=4h&Imy7Ed{*mos0%a$p0i1@_O^PSwAy7>zz)|);p^s_}{ z&CSsLjy4aYz3a^u+IqRUkxG}E;RGT`7E0KX14Y0v^FwOu46~x>rV6^LF?B?XW7sBj zchX7-<;8p;(^}t4>O=Dfn0qPk9hc&0@DlSOdU>$f$KvYDr{O6u)4ZPAE;DcCEyvT* zv2c{$y53w8>oV@mg2&iZ6U^60@La8Q<-$XJvbjtp{bAc^^`7)qvT&sAyl(QkXQY|p zBzUsJpV_r8E{Ba=Y<^Vi?)nE22Db^r%~j+aZ|?Z>RMGE!?COa5YDIrafP&%_VE>7_ zYvyWMI8@oyVIqfm>aB*WWa07Tu0(CJXGhU00$(J4H6o22nqeLy`7eGW1YpI$dxv z$SSZ?o7?1|O^;fP^k|2r4=wsgGR?=S4~?*sho2RTB%knY3UbXDb$Tb7yBWF6cVa7l3o&M=?9;0C}LtHkpT#BWzyF)~_>r zIpdF0IwxfQh$E8Y{A{0Oi8%=eaD_z(a1v@*XNtmAe?$7&nXbCtGK+4!9_;6@rV<JiXcmTKr*8BW#nsjPX*ikb*Th3-_$I8?C`YjO}V)6n5zKi@F<3 z=DZM2avqwdBdBo)p*tPBOi8CbpU34>?FVLsd;@H8rBKGA9RK5xEXOZG4U*+G&_BAM zcgF&FTpnl}xoR=EB|Vj!0)t>Y;DK$hYP@nmsKilU;s{gaWkB_L-zCQz!-G7WlMM|b z=b5ZLz)#BKjn_xWdYie9o+>i+pyu0ff#K&O(?F{;;;V;c!Uw~J&qF`lW?n&06q|a} z4Li+w^aRY2?4AbFa(4>empK`*d4-ChR5v;yXx_j<3R)3 zKQsW9DRi;U`fwOeIUX zf(+e~XTllJm)zFFT46kMUmE@buCFyu`cNTIx%egSqiQH6q zlR5Je)5yxHqsEQsW~EG!@47LeMc~+^F&69CUI@wp|)vqQTqE$yED$)_QrYqq>r=T4%40=Zt&|!QT>e zvxT>S;4c|&l%mkx{>`Z2BRg`d*(Di!HXEYQ&)wclq)M$ENpbZ_a^UGj7dlK((QPw7 zuRbDM`iKe?1e;2E3qjT-T9!h&Blf}FriT-TWr>Dsrbdm(ck%oX_4(P7z;0J8wUQ(i zu)GD9N1zSMuzW*v^3BB0nrAs-FJyU*mNJq4C^S!DO+m|2yhW~86_-LoD}z|RZorUti`l9U^OLxMg{qzdIR1twASzdbbXMU zq`G41RM}v%uSR}3&+~@xczj;B(;x8r{oyj7KV} zh!@sC7!+OZ0RDyeZpJU3*Z|y4SdC);&M)pXYXgjB(Y~7%d$OM!-GUfQ7`|j2cm1w1 z6Fkvv1LOQ?{wy^yQS~@;n$G16@{1WmprB3|X6|KJ9z(7Wz>4~! z&}(d5bQZelfG#XQfNpt!zX7^_SB;yS9`~&`q|^6bWa(+*dvXrdnv;}lk2~b|;_xX3HSeFTK3L6ZRk`K z6w`u-(=u8oE8iH_$4B9|vbpzJc$RW_)^T{kmV`GB&$=2Pr$Fbx3Wgy+#_lUCgSpj~ zp#iLhL&M_^V7we)Zf;{a2P4ZoK*0LgWWDE3wR@tS$mjHNvX6$>PhDZj zP6Bdz_@seO+OoNPb#$h`tv`7uCB?6XofCjVVJr&QtcOf!#D(o}7U6u(NWjNu7Xh}^ z)#1T>12|LyK7O19eEc|r2~9iB+#W0)fa`T~Yqk%42k_79hFubyL<`Xr+KT&j^Rb+B8g%h2 zp-zUz-H945=b%twNztEDLl?N9Nzw?Nc$om35~Sn#*o^w94Vy9HEb`<7m}Pzr67E}2 z<_GeW`D>7HKJrA)+i3k@p~gM~p*m+Po!w@!>2~3iyPRh0aYLt8vT&h*BLy9-N|2La zdIt&fY8%K2|5a=tivK5WBVIF7j;AAA%z0GNJIPGHZh@+zZcBQazgurtbrIBK1;G*= zZQ$`P8Z~*OBNzOy0;IJj_L=Lt)B{iIafp8NfB z%VVMvCkxjlpM@P8dWGeH2#G7(tzu-qITTz;ELSAv~#azmR{mFZ{bAMZW&Jp;*)Uw?xYw`-#%HB;13OPQL9Xe1AD(aorKVKsQoD`M2VbnI21GHs4I0JW)8rnb$3t5N-RqP|;JRWc z7Bi7MIoBKHn$x3ET~hV`ELYHlf=gZff}Pva#Zhy)W5mSqQ>ytDQjWKp-q#7KrL)!F z@5#K*uzp!I4O;m{T?@rmF$PRB_cfK!sv@Y+s{5MCsOjz81iGoOsksNlB+lSRB3?WI zb{O%n#_ESLNjI2Ms_bXVr1uZl;)=kUbOHnpL-5Stw9sop1somqK^!3Xa78BbHMSQ`^SgE>jbz2bG9i&aAm9zT8y5y za=If=N`Ny6d~d(+V@%*_0s6hnW{^Jc=eosnll7fNCtQn3_Ho^+qlNN<)CEbn~y45S#m?1$Pgn zp%nbw2F>F^%O9$O8-3e+U7cmo))y=XOj6aYSy9|1e7tk(h}MjlUgW@wmcigxE87oS zel#iBaGv1T*s!@otAh?#6d9<(Rjj9?Bx@foACJ6tX%sgJw=U=^l!f~qelNFgAQv|} zrBPI!WF2(gC8x;@9+MBF7r5r-8$9Glvu4pNZn#Y@lnn*6_3GjPy($}WXpL%+=~S8( z_q0E-e59A8F--o%0zIKLmM%#tfJ?|hOK(El{jicS)SeCKrKUl8i21IE*MK_AD zK%TPPrrpOoc2UoTG}vAlslRpgvGCn?;X03-#* z2Vnm}Py-3kO^qXp)2ZSmi=NtED1eTWOTq)!n)B)HW;LB^AGIiYw@G8mCcG!-wtfcJ z9Oq-X87-$SHBiF5lo^*a@7lV<%yUCp;KKW@Q2z;}*27aPLjqo#KxZW4sEd??$%Thvqzu1h_yuzxfuR1l}XllxRnjajOHJyPn zU=3fySj-8m>5i2LOhB8nQ8%zA<}&9hs{_t-y?f8PKlrB#&%_Y%Zu*cW@MUmc|m};U`2+RN%r?@nwDLJ^b1~D7QJ5=Cb z_DrP%I}EAhxH;cKFMruHQLTfZ41t`SPH5AA{U$M)nuaMh+A3Ll(%dgYPQQsXC z-+-;0MG4o_nSRz@*a&Cw9t=n~;-d(JFX%pi^!SBev8F=w;TH=T0s9y6;Map+H-6ok z6zGN&2ug&V5!;sa94`>>q8jCOSQOAk?NEh_B6Pm8!+NIkGOE}ih#j1_cn-WpNi-ja z&tJidgH%rDYE_L@3f2Q)!TK6Ew0S{DJ;pks?uGCjIu-i4j<%Ge#ZeXXU z00cZOdT#aw=VeplDN6?Bew!7yI>x~w_kWk}RE2YOmooxxkJHd_1gh6MAT&hoa1it? z9DqJHfJ6{zXhEQ34aIzWLHOa*3sQuz#$Sem144fpj%Rm0#L3kjA6i5Tj{{x*aqz;0ABpiTycF=|W4(^f{2)jKse&~Oodx`kNXbL+Gbpt%h7pXyl zw#r)T7#slEd18U8K~V;TmKGTn`yGn#TvdC^4RY#O#6Cw2i1;!yQ+ z)a6US88f~CJ#rLv!AN%TU$CN!&fBQvsl*c4^X04*boZ zGcP=U&g?~t>I*tuLSPc<8dV5>8*QXy1;gEbC3W4(n6m<()=*59-&wO@$+89TkoYgF zRf$%V_j~qFD@wZ-i}rpC<}5r{wa;C0x(4fj(Fh>m8U{(cTac2)yE#$YE*dsCqlHio zaJL9e5b4EFkKm?R(E?c@?t!PJo^yKGaWsG>;FJ#1WF1!v)%od^XwA{6T+5LQRIb<@ z{94km)lj;^_xwDC9oQOF)dW-UDO|S1wQJByuCU|#pC8URD0wst+y&o+hl{410r;=I z0R#n~avt7rI?1_4^rMLbI9)jb(t73R40w64LBLJ)$^`|SLgjeZ4i?Ti!RwwAE0e zm2(|D=37qjpC>!$$Wp~jQ4zNqV~T5>lU|pmq(>J}*t=023T_iMk4{UW-@bv{hjRqJ zwRGDc%97?#Ys3#3J#?4}Vs;uuxB#*T2((G{T&XLv`I$ASQ zA-ZLZ)u?&;8*Z$O_LGJC)$@8NWpF>d4IZPzto?bAMVAbN{E7Bww@$XUD5cP;bf4%+ z?5oMvG)SKdm^{sjYv#BTchd+Gf3pr@i)yVWp%z=zk`r~}s|DTcRuX%l&N@+)r1|aj zR{q(JrI1_oQw8C5VJdKBwBe>XQ4f44F5=BhYdJAE)n>v6{iMZP@}h1i^0elsQQM1_ zICi{$akj{2U18lR>ZJx0T7ugKRxXn-rCl4XxD?uC{Xm7$q)HHP5PJ&yg(`I6Gi(?e z#Ro5%b9ombzJagUFd*v3w!V_51Y;epxapE894Yn%cv>wTc*mxgq|4ULje3E>7i*)V zin%F;{UUs`0EIY%#W5Jz%UP|UW& z$~PSv;>zucoz84AC(*8Wd_kt@M3#kSzSvha@gZTr7&SHi-H~dCax)0F4{2ajIf(${ zM5`x?AZmhiIW6i{kSw`Ow0pnxkW^kzd&by$kTX5rM#sn4;;H;CYn|lt)7Iy#S$d6h zxM=3*))^AiQ3-Ebv-O}(A=r<{y^RfjwB2f#aBVOD9cyU{WHDIZUG<(wO{6^Jf|%_o z_j+l~JJyW2a*wM%T;}qGp#*pPJ(T-`wSYc&$EwEj8bSE2dIO$_A7p_3#;g-*O;DEU z!xyX<^AQGKv}Pvx!hWaU6!3+-b@hI~I~0JIMDv|is8R%JIqkdCx~8zq6@j_L4`CC$ zSY3I&FC0RPF@utaTRI~At|63~EvF-?G~y*|0w4P&FIgu_AvYDgZ+(Ymyl*wq)|ah~ zbk#Ad5m&uzhpiXV>U*G|NqNzlK;Ip+lH~N0^%ZLxjlb8*ueS;U2pZ(H2(`Upy_4T5 zTX~;#BknexJYwwyf8LV6SudlNuVTV4{mmM(!PE^sBv1?JRbEb&_gneBq>2vAHQ=R$ zlh!_xKSJ-{kGf8pc@jT$WIbvfJB#zbj;WPHr%V}(Ye@rQZ=F9#=+61;jQ>|bp?!x{ z!oAUz^xg~BBkZ+1txjycRrDSJz%WyMg+jWDk7o^A_keY;%0DD?WEtN%^Qg@pZ(~(& zS`UkCRl7Bc{fm1oQWPF)+WuY3fr;zhhpiEV6435NquLTWINX-NM!son2hm%D3fIsJ z?bd9T_m1@>I$O`$?zY06Ytv!tcDC~a>!o`A=Ylv@_#C_beZ#evM)8G~t&{iwv|Yd9 z)|4ndBlEb6r!1$A{>2IG^G~g*&2Mcz)ws|)%Afr=@$75Y8^2-xzy4uQLH~75{jN&q{8F^4cXr6@3ZL86U)ACIi~srW zzed{KLw8ej(yGhRacVbnT3ji-cD785>q9G^NVrxr&Ljz9(!&YEjIhnjiQ+>$o3v9Y zhAOEMXiqq9fUSeB3Nhe@KTt!BKNY3WuCyd8)yCmNdmlZnCQ`$fN|p-yaF>47PN$kx zJssCW$$I8HI0f&mOEa~!&BNLLd$NrxUQ5b>S4LcnlAAlN;M`P5@$*s&@EysdRBp&i zp>-=$`_UWQi{dl8)E|H039s2%CdYVvxEyh-3>2(OJ^kt&W2%Nv?vw(&T8AGk9??B8p^6qSs%hq-pcYEjv*o&{-gDK zy@Ia>;bQ^Vq?_rvGgg;LYQAex6dw(GImD2NOL5X>w0Q${jET>u56!wBH270|fNxwr z)CO^qO(o~=*8E3y+Cpf#g+BO}Qx@5FpDIZ=GTCC|3_~lErik8Qaw+EE&X5`#Tn`T?_A)*otgDE3zzv^OMaMC3JqjU)e)<><~(Lw zC9-?R+Ae|d;ttw<#FmTCq+}VX_Lt&kXz%eXL(7SQ3azb( zqqrw&mdv%A+g&RG7hc<@4(e62pBK z2HP3#cPQOYgYU4-p{<$V;nmT~XmN6CYxE z?>aI>7Qe;u4Zbd+&&j0|H#pwrSr4-br*rp8tZjm0tk&v&HsQxyTtRexf&>o1&v>y% z*i$=rUjW%-Jl@Wp+Qs94+-|#%Lk3oML1J`Uqpg^O=coprM`?E;E}cW<<4%Y~_t}2St69<>JWo(wFl7tNSZ7

      uGh z7!1I0;D*^_jM=2ZsL+;BGhl8X6<`5k{;3%cv=GwBzanO+s3mQ1r;qz=@ z9(LF+)kgIUH5(i*_QygSc!&yfE--DE?J?dc&|%+hn}dC~*p?{Lu1sJ|7cSw$Z9m^- z+bVWCv0e;8Nw^sN##rOaq3B2egwA5f-$cModiJIH(GkeFQKOFjk+L)dr*G0Bb=L5;DA&^&+anl zxCQAYI$fRGhczs7G)e4_IW~M*xeFw5oW8v51*Z(nqo1PnjuZ}`54FY)$ZqN7U3~lAnQTPy@#)@k6 z)65a|6+CZJzv_}as<}EPnf9D2=|#1pl^LwU8+RFS>3LgEg@3bZPgM-X+YZ~t>GaYY zsP_&9gDGq2eOF>q>mJ)1=WTsIYddPI(c{wxws+tUe@S#az9EUrLpR3_qYun+c_!&( zlRi2QUoTJK?N-pvcWk{)((w&fM#n-*)nw9Pl=!YKV3I!ERS_Kn@AlY%!b!h&BD~j9 z6~!m6?upX|#*^)A=X`3iF5yTYJtD`QQ5BB*VG`euPoo9i-jnqnH}@ZG2_7$`Q{LXeKHxFI1J%TM z#|V^L+-QJk5E?1|RSZNpb5-o`okuNylko+BarnBF_dYqDo-XUHry*~c)9J(4(|XWr zUpdnB@R45X57TXgeQa@gr#NaHW`D8Lrsu`N6tS z{-8kx%NiQOi&w79uN*QopT-Tq7tXJIJi$!yDlX| z{NHX)G;(rwyw%<5HYQnc8H1}2CCRX9cC;Fb!YO?9r|ICxNKX&5_Mzh!^fuCW7xdmJ z;Z|9RC9V`KyCmmwU~W$CDtr>squn!>zM|dEoI$NGXV}^BthoENl3&wNPkJUD zU6*dB19IkA+N);f+~_DurSx{anU39;Ze));;>xtb-_X&QQ*gTRt7E5}n zUYnB3*7S;N)QWsdo5m!D*(C*Wq{Y9ZeQQ&DP{(B%AGccjO(5qFCB0cg-#C8PL6E+u z=0v#FXK-u*rx{;D-&|h5<-T55vG> zR%ka#K8>AD0|jv)O1r_lS41a*c~@eExg#014YTHCMqup*2hVkyaA@S-dq@En@AcPE z!-KkPdU{g<{JG6>xC*$tM>gd1`_~(sEelc-sBNUIW^hkS?wJfHODGG@^~|KBnf7#Q z2tuoOWTeeVJv@o&X6~X1iLT6qFN+)+Vf9hp=E5Y%bQzD?rcs9{QKk45HUqaaCAv8i z-f3$J4$5E7uROwKDt|?O%-;zv5tI1EN=@^w`Q7}B8n?&cV++IL?lLG-aXW4SpY0+# zHP>RG`D5d%jY8Xy=oEb2(a1A20r;1jhDIkt#bxeJ`OghgqLXCdu2KzD?ALK|Ix$iD zC#M5*#A68m4}0$&71h=K4|DI#+!;`u-kAnt0aUS8iA-O#weO*5)0%p#l-0EbFPNO=lgyCdjELWde@J&<}U6%ZJ&MG zKD&zj6n`py&DCiNo`g@~BgOxY{@?H^|2KTf|IfmwKua|wBwT?Hvq;z_Y!|i&>xEolrLas02=j$m z!ZcxmFiIFMJSU_GJ%uhp2cfmlRA?mB7kq+KFbi5CG*i(|(NdA1h*i{8cocSpQ4y|C z!-(-u@fY!~_=EVh_?7sXSRtMj-xW`ZN5li-9`P-)K-?g%6<3Kb*H?56k%CjuyKBK# zczzg!U`Kf<2u)e#&LEsKDBlPol#Mbk2uFv?++agImj+|-3LM7f}rdYjKH&9(1B-6(2i$h(2A!sXu-2i&WrM zUrfb+f;v2J2Z43Pw?XXcipxO|sEUh0SSc#b264w!oDO1hQj`Q?^B1Bx5yW6A4h2O# z4+I4~cLyR15lk%ga!uVsSZ4cef3@dGLVoHi){!`HDWQaq8Yr{gy+D;z=|ei zF|eZPy%<>0^jZw8Xu2;3R@D0!!*o);Z*eUIQc@3J3g7?fe$Go2z*FWg0t})7X((MQ9)ou8WEg< z=g{DEJO>1UA*ojo@)oIc5O|W>1%W428zNQ*`r-NiNhGaAH2nW2lGg8}>#K#I?PM){ zG>i9HuAP3Wle)di1MQBa%)k7$i}2Vb3I+xSD??7p1P1u|3K(;4Jf$5K8n$v$06ro&3}vYBj;I3Z ztEhD#Z9-6_KVHywC0(2`G*`E9DwQnNNOWR^wo?rfWRzRrHCS1wMFgtw+={7?vyRrb ze2j$|2em6KCE8}C`u^epDWP9Q2C6unL^M*?!b#0=;lss2|II6+d%;z zz`6=x@`~Dlpip6^F08Da=QXkNFSOqZG$O{?fF@1R!pHun+V50PNYo?*90SqWQqY`m z2H@N6k2V}ZBiYI0T7(qhc(;=9Ivn4ZH8bS0;w##0wKNQrY<73jm+D6H-OZlYJ^)W~ zdj$SvIKLulnCDw zo(eZ>(L(o;z|P;-HWNjKhgv+N8^%%}Xx~7hm*yR<{}QFoge2mxfzkaA{iL(vvyZZ` zHrUGEQtF1{;tz+w-zhCrbx=2yT03>MSXOOaiCP7(Z+j>4&P`$v1K|p-h(yQgQJr^m z^I4`%w-^=GXKllEXGH4Y&_jfFST~RL_vo@!(g@B`0cnfET!y5tLBK|iy6E+j0`N3< zU}#N-<&d^1a3(2YAmX5cZnyzHxunzGcwld(g9mm+991mTxhea5_(}OI%VJ_} z+UnlNclh*Jpv$L1rQ%(h_f-QuWsA@$ctN3igX8NVwVi{IX>B{{-V+rGtRPFL74jZw z4fIgbb)d6i!yuhf>ylXguDUOt%rh|qK?FHRW1Og2TnqYfAul<3pC_}|M(QF2MTnvq zrQOoKMX&v;9|eAZzBB8$Qddj%?Y1|8=bVw3rqkzrjrb?RS{+#Eil*pXOO~))e_CYk zzpiT|C>5<*HDiI-^Zo&G@A0Z?`(U8SxSYkN+Ihy{u+$!WNjYkUPt}o5U{5uL%BKv55r^RTuDVA{Q6VrL0ufXwocztU=u_2 zl^B)rSj7YX$()Z?aQG_*QS})en5rf{B(jRTqz8-bs}6c_@Voe}l* z&$C%xeLZw^qB5j-9EahU5T+QqOA1Wjfdu&n*`tF3+Wv}Rq&uxlURz}dXK4-f1psO| z%U2rqifn%qeX>eajG&5KRV*v%rni8-9?+LE=^6b4ASab_w7LY=ysdr&+Je`y6RmmG zM-YtQagA|Q>xOa%#T)fOj;R;$8%0-7V+R;+elo*(wH?vsXsXztkE1xPuA*j7>(1gf z>HA>9KRwt@`|5vINrB0j+SaGr`+&E?iOX?t5~|-%cTelWpKfXk=cg#fldw`hk}7}K zFJ|qp>MR0#G+eI`m5K?^MpNk+5z>soHYXeE3ZE^oeQElBYAG;P8S?Y9#Jk7W)}(Fz z0E90e^nMzE`WygG@J=S(JIaB4omt@>W~v_EVH8s+>0MobElt-iM2Dxsv%cPw*U13- zL20mwtmBLN66BgjKDR!R7RAEH$;wQ91Z{pvKa^U$9ByTo=ja=tWICdG=s)=H*}^be znxcFLHF!C^G0SeGm%#L2uh)yTs;9n5o?XARc!{1b${9@ex&9$8H!JkWKa(JibSP=5w! zS_IcWaQ-eTvM9x9*u}Ee>K2HUB^oaADG-2LXI)>a{1eRc_%eMT%ChR8W$=}B68)cv z{&(WA*B5wqUfQ4wz+ppiUH)mE{AuyJi2=BODjLlDJe~K8-*1+z%`^;W*yNgvlsL^W ziz;7LDJknqeHH@7>4BpV#Znds>NBB~RqgEtE`N;By!fI#D$7`D7rh?4Y^q@_t4!Cc z0PC0fpr}|*8NXr2%U`3C9`6%`eWD#3aaF$+i}Mwhvt2(8en?m7JM#jg$@#thdr`51 zN?+B#!7#;jA}jt;{~8AAMf4$!_n}zs!^#cg1GAJNOx~m(FWQ#8Cokjo8q>e4oB3b8 zkn7&Xd$EeHUD36p@+TMVIB9~~uwGQGrR!Y`&r$MCM1gzdHMkSmSR7lIZO|CT$aF5T z>U+QeJ8Cv`!?amPdOIeO%Vzjmo!8Kygqw(z5FrIrDx3;zat7u?1*Q!FupgLRZ#XVU ziabEFfd@x>oo)6SMgx?MR4_w7g0PC3(>Vps#KvFgQrK^PgB`~PTpi`i;1$f~vuX3| zLj&+|bm(!FgevhCqIkVH-oRI9J_$zs2DYV{p$MBQJP^GtQ@)egd)*B!Feh#(c^DAI z7W}qS%6i=vo^`mQxU=C~r4)b@p9-wQ_1b}y*xjHDQ*2-B4#2(9u8I_`)s$NK4|=6fOU#0q91#qJ0Hr!oxL@U^ijr+G;(-w_i;Smz9I#vjOFXy?Fb8?JG5@5YzRN;Xbl{NQJkJsciJ;hRsO)h^|)~lUSE4hP!H&gjglG zKtvf+5E{@h6QG|#Qy;^G6OKdO;bHrp_NhnksrNm_-!;`?@e7U5%RBgaN-&0Prjo|S z4XoX5!@Fp(jP&D-cPZH!RtvVI#)a(Ey6C0yXvC%sNEl~)R8#d3I^Xub%?ML`nLRNWhIgDVQO2o!!GDh56)I|9*8&?{V?#Eg#GEX!jdmjp zH80gOHo{qOl?t0V>a$zvMw5a%#2HVE3OMVFH}Yj&$@IS%uHv#b)M#gawlJ!3^j~M! z?O`5~0TW-zh`PZFB;yMp-$ojZY*sttN=a0F!;+R8H6p9s)i_km;~u|_Sjrqz03X-u zXoZF_;p#DUALDPBb&$p>zRykjuKhnw6`!5}E9Ei0NhdPlr9M5**ntflV0;S@{=kRX zMcKBno^0~-#youbkyTV1;=pflhItsqf3FPXPpl{}x-WK$D#J0}pLABWHaaLL-J%ZT zjHU8O^Zb3)+>rw7Py?`wDqXrTs@#dtG+gb9?VoHMj6uG0tZxuh_HR)&IjxTcL%m-z z#<62ljh)c7@6ffo%=d%g8X)=|F<)NWZ>TLdTZJGS1}Bwd8tbtxGmR&(U;dn1FtO#+ zJ>MWl8Qy^d?FsteouX?Utm~JId|g+g`d>(U+;BCwq)%;jXSs2oMg@-zJ8>QTcL6nX zuQpbqz&#ZBof0-X>`%9|TC62=KsXZwJ_LL-;e8c|hCUenC?P^?rG2KbW_0qlK~KZ8 zjPn%`no-8{MjQJz%cxT*i{PM{%VXaXG;2Bp;T>Y9q6UO$`lNQ)pHHoDBcGnX;rC~5 z<(3Fm@}Z@zcp!1&HW@V!4#eM_DjbMkR4$OVjpZai>i){r)(6AtEn^i3APGh-qP8uS zc`OMmvE1^LKug~?c9DV2&8`b6=yqd1TrS?GLcfL#X02Q!G`Tja4&`+jLzhE;cO}J+dQ<(a&2@j3ykuQ`+gqc9^m_Y9+pZonQ5F_O@-b^x{33&Fiw;P(C$0u_QioXyT5DZ-mu+TPdHI3x4I3n4~pN%t+ z&>RVp(uxQt2`v7+sa(jrVW>?%{ARRr%&QS*&JGVqs+9=eH!I1A z?kv#e3#yTvl1Qb#3Ff`jr*F&@43Fwdjsg#r%!-br)Xrfa)4ms@8#2#e)7NO$OQ&X- zlc;J$n1;n?o39Ei_65^tm;*jmvM_p(K!w*-E;`=U>}0FcOko<86_LA)44@4MK;R=Q z{gm*m`78(M69&pA9;}Rs<|)*En(2(Ftjm1E!Z5q@`uqu5>ug>|2;LD%m<`#Wft9hx6NB(Ra$m*M=5ggr ziSBHItIwL?oCCm@R7T;~5L(`pbi2NhrG9JpT40Dtrb6S5Xwy~WClvdUVJkiIndZ^l z!>0bM>uS?+K-`#gUIg!|I>%W~UmKfI`NxLm>B2Enqr7@1Gfg~i2xFVpnxH481V8CJ z<2rJmGTq3FF?~qx3x*gT35OvP&QeqrFHb&tbP+`m(x#Wd7Hu*uLBRw%@wox)*nAV% zvC1awwU12Gg}keV?(EcdQzv9-%3iB9EE427N0@VKSfjU1mr!Lhc&RnvVybLG>BkWg zV&h6v8k>92^fKycNsDq!ACT*;VFE)aq!h$OKWD&!_U}$0NL8zveh~VxlX98bY_lpUnw=#)J2bq4ODwDZ3Q{OlJiBBC_=@1h<^B2yAyOt4`Rgr9j z-JHj12W2M~SP+d+l^(&&*G*?C9ci*MS9^0nV2SI@+wfGIOO$yH%si6v`VjFGVLd<2 zRtlP<<{&(v+>@u|0V?@MHHbcsHP;JMc0C#vbRxyh;r}Pa&cSbw-?Nl4MU&6LPl0f0 zq8YSWh_V~=PS@b{Krdc~%DF0umFdm6nJ95%Yhu2|&>sUj+kVrCLkL zYYab8he4*f?5_kfUlU-ZRH?wbZDt1V7BSKF_f!o8S~w`CB_RMWoP!LCfuc^2uus_V zXUyACQ9nABYPtZS1{&>CU}($AD^=qKYV0$QVw*dd7b+z;1~u4}Bjr$-azL>r2ro0Y z%r!`wQry-2P>__p0qH=Nu-P0futwd@mjKKl(oZx$V!J9-Zp56N9-YTnA9GJM3Ma6? zncz<(*?a>~f~`5-yq(3_%-F8l&oSxP4+G71lpjnvk4#2pect>NvJD~qJo9a8^M;`{ z6?)CDvBSg7J&M7Mf`eqBPnf>%?hp?IC{e+7QL%^R;85Z_HD1HUUqo^Fq zQc}a#3N`JH#J6$S%uQh;%Ur0jgG092u$=bgn){2&@z}~u8Tj<~kW%6>a}gy@3M-+L z^Udz!b>EK z*xZc+I4WN>8mD*A#Ot~nxQ?MSG1GVt2E1O(pUW_FO zB?V@p+~i{*6(9?U@T0sEJXuZs9*_IPggmaM`zio;pWWWaEoqPx~-uJ{Vg$+ zZnvxsQ@)ftIS4E8x40qff7O&I&;_R@R#GlTb<3#wLkmJ2M_3TzSc%9<8MqB3ZZ($B z)ndy`mgch*V6wkVi5D&NS^7~+XMw$6V1Wz%Rkt`BQu#`5(GJMh8dxA-Q?6k8&X!08 zs~2avq7;=YS;dEzSbhJ;BOPZ*BjjoTgVo%tjh>=^#bx)C)v9;wc8d-IqXo^)} z&UTjX@s^$2H?}DE>g|(st)oJJVBv|e0{gL}#fEv6liN40h_c6O5nrjHWvrNc=fTGu zmesi#<-;kv(h`26VIR50d#b0ClqpyXI{C_A2F@ncS{IWjL8s|@)MH}jq? z-vK%D)=J7(QO{b6Crb`nJHRqY{bW8Y2@y+#C47+P2%7y7!Nx#*y8}gI<2j>LNx`;3 ztfwUlYuX{SO+=y1@_@4OKZHo z&Z>`CPRj^xgVzmIeZrDRz-<`ov(187Y08aMU1~Ya-rr$q&Dp5R&A@GmP;+s+E9CaA zi_u-40M zbFzp=9Mr;%$pKtQ(zC7B1KG>p7K_hV;BGJuagax`YQ0bkEb|1bndzEaJp5{>+=SmY z${D7$=9Y{zutw)BmjLPZ14%cB3%T)&&QsMKTzB)|Huj}Hhb{ft%}*>24C0%)-ujFk zyJ&e9x!DUd!0XKL6YjgU>stq7y#ncU35J3wIafrvC9QJMoo;N!*J2 zi%}H##yWz1{*5J43{$>cG&%???ytGq!{0evTk+dP)uXM5k$lrK9o6lo>habV?3r7Z z4-{(Up3(4#HHw7~vJQbvB~hVcGpa4CM1^uMf&?{;q^gdJE%eb4>mD}a7t1Q#Rtjm_ zCh=RovKdykRmiJj`;+6eh;oXp<@BgW!~U$k%lajTZa)=l6I)ZJ&w7a&eAX&_IzR!3 z6&~{a)~x{LAXR@N4kF#x_95(G18Xn!V;AgD-V+ZCUkh4rz6C*3)GP=~?i*9&j#AtZ zM-=sKrr@5|mB)*k2VsML1Fm*~gqk$&pj}zKzd^7GCg{MLG^jNfS#5O3WZf<)_aO_T z^zPP4EWMZYRWY|}LTzfi+~}b8W3AVCj}BA(kCuUSYKawI^w!pCLWLfSk{Y*GOwY}> zgtCQ8t@VXN`PO&j*>i;4Q5GEMcPvd=-&R&w)GCj%6v?_z$o=cMfldy#j->?}t3NmK z?b*VC0VW0*ztfh4A#A4tV@yQ4CTBh#S?m$&bE!WzJS`^C9FsfTh$Kn z;8@%g>jh+ckIH_w9AZb(t=*C1{oI5SKfBvi0l87_7pzHa-Yn~KQBiSDXg#Ao(m6DH@td*WH@H+FPtRSkR@eX#ZSp>3kRUZUw19<~VDB z^DEMW_J6e`K&DvRxdM0%T_xDnKesPeoh&Ct(cAbv}Vfw!n%w7de~YUb$&sCT?%Np9JfNlMR}QgTO7?P zIZj(h`Gzn=G=$PVmGq7UL*}G)5A6$W+?e94Bb2n~h*r&}zGGd3{pd?7sBf)HC8S7X zeU?Krm8$lN4Jh#?#Yhg|6{@JPB2dT&Rs;%Be#H{rwU%KgEE31i-hiSVdCywA)7=lO zDg?TQ7;b)REsmEE<<;DrRh^4pSBwO&be2NN3eQ`?ays*gwXG^IUx5gG%WHipgr1!d zYy~QxCNN9}gM4B86os!-(x28-l;1!yvBY9)N041T8z!(Gm#v2Z@C~{?r6Ck&wpul8 z+7)ZKY=nLSj?cfbE`&W%B`Wxqas=D6Eb+RvCl-$rn^f{%YyTSjrPG!}Vmq4ulXaP> z{EiZIw(0D}Th>b&RT~hZ6Znji3DKJyn+IWpes~(MyPXW)H>mvwN-pq3tAF5oaXw4CHi={lUDiIM~ zW%~npZnKOc>sFX9btpmq^&aKq?J;p*kqf7IuzH$ww~Q=%GfjHiTj6Wvf7SkLdaviUEWb)rT_G zxC`w`w!tt>1=g}Hv};W>4+J)QkjtnO*ECioDIbJ{s1&S6cUu>MqiYOw#YWl& z2%<^}UbPL|)@U07qJm;M+LHmsJ))!%QAAA(n%c5x$28jj%4%*4XU}!FwGx2-Ts0ls<+@UaBUSKn7R2?BQZ%2WGCxgZiBq>@5Q`O$pJ(#2n`AiBpW=oM&5&#b;-`BPV?Bg3Y@H74<`L~MDb|l^GSP0-=we3}l zDjmE2o!Kc+)yuX&SmAct_k2F9^mKi8tcTnm+ZG}^lI@B{)d`YLEVKgKLLde@&TwIp zl8z8Q8$YzwhAjNJ?F*s!iY-~G0uTLXJ(aGu52eCNTbD4EW$oNxXQ*XxkDop1t50nR z<5ggTkoLOmHPi(?YS{6nZFKZwgTArt2HtF3YquN4jfj{?*;Ott6_(j0%3t7cvp;Uy zG{|A6z()IjDEWIyPkV+-INA3|cPV>JLm0ee+hR-a&zUJ4U$5 zb{U18bp5iu0ZYE=xCu>z#rA2m_%~aosET0Mui8BV%&W|-)qUGXlBjZ1MI+}Dw%lu< zhk_m|xZ;2um z;fPzTLw9>A1h64c$V%Dh$X8J2TJu#vb}&<7jHxB#j{$#sQ46K7RiCCu zzd9DuraDq=*9M`D5 zReBcOaQmn7+JJG}LJ6LXL}xzRn`OT#SKEs80m;VV-?L*6FW0EBK?@F#oCHrrJJrz8 z-EGfT^LTEmnXqsWiZUh$nga`iJ(MBeXY$c}Qyxf$fgH%~kyUL<8U(vziey2{Z?%22 zq-uhXZCTL?2ZYbrjf|mG_?Fa>Z7#Nt2K4P%QK>@%eqLI{B|JwuJTk29spwtD9`f99 zq*K;t#{=d%Y3KV^2TI6P$5G{nj=Ah%HTH3q>T>pzizKp~!x7*BYxg;-DgLkqH{C)< z3xOSe-`+zhsyedjISwrbyu=Ys-ZJ|QQPqjAbNfcjE*<*bMH)z1KihAxiJ#clp{dTS zVvrMC5TYk5S>l)oFpIH9SF;~Kwc}!|>OvJmoDccLi(?62*efvcx-xw?dt)$RTTAoU z?yu~F@C^#>z3jv`RN5z~5&ZqAltWrcB|A0sscgOdGM@-NS*g;#9afm{I?xs{1lB=_suwHO*dbK|h=r^$ z-`*7af%83{(wmjmwripC^k=l;@q|sT=h%-P^r2Fny^1Dwaw6#SS9U?f4g!Z>mG+6n z4IL_#3WmWyB{4wf!*+Ah>ZSOD_9EQYRNe7QVL4OnLj+dd+P)05jw+%K6looOD5<5R zR8;k&gho;|i~HGL0d#D%pUE5LfI3x(sy`(pNO^>{HGm~2fpN*I+B*yYW&kBcIZh!M znlqDiOme`wRW*?11nkp9@;0^CVvD*umLS_8ma~+9OFZbfNAU`amR{`XcvVzA#}fWh zlnU(K-i~%C`aA+QA;L|*Q>ahzk6bP`Ioa_-sMH(Na-Hj+E^#|;RWg3C63}ZAh}^rt z@f@uzvxACwg7WPg>S0v$J0{BAa{GK>IMY$c^3MQ;JZU(qO0{}*=de3N~tdZ%k9kpQ*tJSD(z5Yun%~l-T#4A zM*l1hFid?^BU#$#jx7q(4YUvETM8>(XYZh(>@)`#8LLLKv`WWG9G_3{{G$x(7?$>p z1Jjsq3xsXqFX|9(cd)g8*e7BCcSsOjhp5I<(mh)e-yj@NyL;KePu_7XV~oAG$P!08 zCqw43%F#tsjVJE|&GW~GZ zAk43;_58~yJ~S}rQq7>W`fzkn_^LA>-Nw3MST|8}CuUN`0Q)wUI@2z@3{lOZfL@x) zydxcz|J92=axY$_fLTf{-YBp7*+OHFas~_{mV?j}QrW0x*di&cv zc@9;Kws+*KCy1TwctkuU@3c2(sc+i>TxB~6LGSz6YZYH~;PR-N$BGghJ;g)i_9D6Y z`BXjJQ4Z(A&ZrtvrZMF!?5UJkZNE#{v+vOOYmV1N)dE(X=D;R{p;*9H-EhpnT3yJh z$H?D0+w&-Yk=?~G8qdfzFJjdbcvATlJFcFS9eVj|Caa#}xQrF{wtWz5cE@pEC8`3s z*C$w+cdp|Zkq!LSu?O`AIX!TWmH+N&0t#R;rPPTz!cIJJbivyaN-@Ml9P&DE;AJTl zw31R;@)`ShEJlqA{FxBdOH|MngjcH8j)Q2FGno>5+t;z(q0T{)QnkFrD3%au-+%*r zrrpR6>YaTskzZy>D!U}IVS}989-jw<^8o8JMD+?Kh1%QEo+eTX>3)TpRN~kOAK!#~ zvADL*y8;=U&MHy0f)(74F2H)^^g_AI*#xz|N=bTqU%H*?M0~`34t1VOs!i5@Qas!4 zbq)vkD_N4+9tlu(+nbSgx}%VWjdeC*ZR1oLm`6la0Bx~~Ct2Ow>+UvupYRSsWIs63t7r&8r~w3_vm<0dBTE-5T; zpcAKih-x)oV+P^h{Z5|kETpP25gELXKAtI;Quax)ytfws^BTU+HqrOPovZ2Hh0X^o zuA}n;#tdxQ!x}5wljZEMps5EOYeZEZrM)6rSn3#OzQ~UDaCXCJuVdBC9LXZf&v5RR zr^I?F4M=vDmFnmq#}1GYKsW=!gAzA? zf1u{LPS33xtKkUU3i_YW`FxfWX>TLXmbp)(@@*OX0`kqaUxU_&GL+>Hbf!X|0MrBl zi~B_=PgXosx>@8LXC>O*N=d~L?I`h&=$~cuF`y6kchxrTPc1%yolJFhRPf1qkYjy2 zC7p~|Ox~U9b~W=u$23cwk!mg!dx*${{{r{v@qd=@R8WXj?Vy6=p-0IRt{zIdtua+B zUgQ2vhI=RF>Ctab7J2VVQSlHYX3CXIdfRHk1!Tl=(4b^n-M`|{D58mHG=CO+>cm?p&tRZDO|ujf_oxH*&q z#Lr2e2Ej)DK=%NV?V9d-4j2NzHPi(w)tRnBz;T%Z;jSjE#T?f;yn&CpCp3y>6+6LO zn!L~jn>^JO7MSA^L5@5Vn#nY%>$-$ZQ|+$LNAP>9*dCh166d-XVJ~|t zG@WI|d)^VWJPIECW5Szd3*@><6+1)UqSRcMlf75&YKt!YKpCUdSojxReBu8{)$heF zV=F#)ZNPjUgQ4lkp_rF%!MQ`~oe6OTqa5QN9fSQjLH^)`o+TSBM3f7EpUHk7c zRN2ZkmiB(_N|sdLBHL|N+RFVtVn)|e*JV}Tx%eIF4(U6%F0uG5&uUD-4(?Po?VPi@ zjJCU!lHj?(KD*_L1%!yqUFCurv#OdUj1S!g5y2b}L^=5u$<2QF)rG5`>Srq0pk4Q)b4jg z)vqk=d<^#7^g`Egd5`~%Quc$OavgAe#!BwEAfUjKSS!Q#J4^c%c}dqeVmIq+a325& z_o=#_!@-h|IwuM2+FVZ)sKtGOnOJOdb87U@$8>aMEBB6n(4Z4?vn=wAe87YpRKrq- zx!wU8(=BuvAL74QMwojQMmNv>Z$Jou$fX`K`0QH+dFlgqPmusfBro*{ri}P{$m>}u z5FojUk5UNBu<)u1leC>#H=8?JEN3o~au(Os4Z2E#75zi$1WRokF;AdPgFPMCydLi5NWg@f6#9rN z6>2Y~7D+y6VPT1;_IAG^iBc%bnCV`n$lGppL0iP#Pn5z~#vGinl;1eiE`!8Y42G*G zQL05LT_T>NsjFQY_Q6`WLu9MUo$&&PWS|IwHKlum3=Ho1clU-aW|bq|k!U`gQhGfbrS3}Eo8&+~ozM=DEH$5Zx4(kE2*z6QSL zest*s&XAIz3A)DF5QN8W1vm3b#TjN;WFk1)@Ff z6F29CeB|82K{B%USG(H)5q6eT=mN0`^j>9^O*~%AnhFZ6U+6ksRRqhKpoS85>hG>9ro9@o zMBtk#L>MlrIA>fzmC5cGY16yzm)MH!ZYB0Ka6JElvHky?;gq@yUJ9@saZNwTiXSxL zzjHlDx>f31ENij{W_W+2drRRCPTz6T&iWIV+5J*9T%wI_pt#2Ty?4TbG2;&}VDT zjeIJ(>Plni=PT&qBpeVBDkW`u5NS2Fjumh}<6Y zGnMDKHdA&^C`hpoDTc1^3?+VOz>{5!I)>f(+-*W{Vkt06-Jtl>7}V7DEBA5K)DVi) zu}2Oidp?nmxjxW5_dq==L~2B-n>}?Y{Xk4MbUPLOSgmi}c>r|^I1{!0C#VUWQzXS5 z9TbFV?=J~YXW*;k4DU*P{NkzL88y5pRJn%&B(S7a5rY}_o$YvopWIpMcPPC~3Vrhc zsI;tuE{Ma0nQGjZGQ4e=tux2Kx!7|32>PCA!p8izw zTFeUGjpkHpj({ej((@B^%3HGPneN5{Z~QzzB&1f96RvK?PW1M45?OM+h^2Bntyzv9 z{83hj1LO%11R!4LC7z**?h(+_w0WSX392h+1Z1q(>lT9Vc+6Q@GYO7xIIt+&Jt?fDk*6aDF_99yt^q7D(Y=GSlB6W6zM*NvD(|?w@~O8^?rTRTShSDOtA_4D z0u65C*(pk$nBdTMh01q34<{dBJN#4T(__aElg$F{I17PO;?J5x93UvwN?oaFmAWTi zWm5%?zb=5|SyoW!S}m}=6whS5c7soS*BrKBfafKksyj<+9Py06+7I@eL1GV9aNN}d zTlbUE+RW;|;Q11Z2_6rox{Ly|rg>}t38u&oM?DP^pH8D$rwN{8IC8oGmOd<_t^0K_ zaD48y^eDj)zr=B8qebW{FqMv9dm{!B~wOc_t%s>M;*$l zW_k>OGX<2jlqt}k(XLI*JI`|iF!f^ha@q9SmI( zF#oCop0;mw4SGNnW{SoMVbXIHI2D@7QWM<^ z<&k`zs^_?H;IHu-C&$yCV zpY5JPpa)I@q~X-}r-&jdtcvinFLrpMQS1fWy6V6y$1cxdDom{NJd54qu>#vOAm<*= zuRdMa%7dN^g(N}SRZ63>JD!Cs`wP!pf%W>p1C=Ix9m$l(JuAbc;7l0AcHo6z4*8y^ zz931dNFGC_mp$Eh_F^b*|s&v{>?^4;Eslxg=4pyWN?^zd4oM<5FILWB!KJ?I=c z1WkcCIA0m^PTW&q4dGk!c;i{8uHI8BQG(@vwZ{x)FmGqb zoQ`_8%A*%#sUrPUzx2eJIMJhgduRP)HQrxCAvM;0&l zjsrcPsSGLUQUfAYGOs&KTDjR4T!cl^ye5IS_=OBREdHg}9#fv#Q@oP_=17i<^rc=q zd5?IZeiFGUh(&y#cW9d1U^TluZd`-ZCm|Xs-9)5HUam4+YZdnu>w%)Hl3u zU^C3+m4QOq=H(R98eYjs_RbD(BHq?g!Ub;wYP_k=eDaL-4rC8@d*`URf%iG{Kd_e^ z^p=TX677gv9HI;25ztOvh zrj@Be*^fV9R*KSgO0x(ZD9d42Q+CU`p_E*#uEk!s<=qA>yv0&hd-n-^ja{d2w|N7! zZ-zI8qo5BR{LR}(lyY>o?b953;A3iT zpZ-SpKjVX0SDR{u`dWrbyZ3k(LoxmGza}eE&$F~J-zFRZZ=wr(*b%p{6S~mAreeLq zeZ{!#?W1SIedF0=lW&JwrG^(4yIlMHjy@}uU-X1i_Q%*LHbnUHgfOWnx7}jgl;6yJ z3W6~9s@pdmZGb&0?MH-0AGizB0jBiJ{~Tn>20oYzNX1OqS^mOcZj7^|K{p>XSER#K z^u4MnJm2Ex)1#N~utF^zk!@}%<-=bmVAhYSo+ED9M2eUt8COIHkA z9I6U=h1#&|H=|sNFzMKiv5PTW6_4i$deK^v!0D8Z(}Hg!;j(ONq>p)SMIDt-^AmJ( zsIMdQR6Th=N&3lt7kxRxSC@J2J^6TwPLAT4et+@~+mCVl{lSy>QrNWkIzlAu3uDQL zBf&3}-l3DzcowDqaXY8!=l=7Vf-flqd=F1<&Ax_%SgHu`bto9$Pz_;Zn;pxFC#{b!*8X-D(Per@Vkl6Pp6y#Ag2Wa&j4^{Th`(h1G(RwXTYUCaF|TWp4rCBl{!WtYOCAB@At?n$npuLiT*^KjDkdoEKx-9^L_S1t|*Sgi+cL}kZ(c+<#lpHkT={#ILl`;5v?qs997BTcNkF6s!fe~x=7&tQ@>d_iSrksWX^`DaAzf5IiNXZ)I4`uBmqRyb`msECv7xl_T;aDIt%1n1A`3;o(nXfi$ z>5n>#TwhVymnit$nMe<7l4kA&BkVe&BiL=|uP8!^)kf0+!`EjnvObtSv~ z;VgRhlQnRg0`q-t`oS4#g=om{Wo--nZ^_y3P}(Z2j;b9|PG&ynKQDj0OJ}1{M_H*~ zMU}-u6iq1>9F#3YwqqL(`@6|`s;DRmYy4LINE3M<`b;eTxc`8hQccwdp_J&I6Lka7 z{!FE@{%82oaIiLK{EzQw|DpEB{8r}7iuxW|?vehG0J-IoD9A0PU;d4vLALY< zB@K$K&tf-5eTKI`>1==WFnLgv)&KaOJwsYN9(Y>dH$B zv59i_M>Sv#&qaNT{A#L5i;Q87%cCyhO+#mwp_TkmfFxU}Yh)uXM#1hv9ZE^5yu+XK zg`f_jfDbi#{dMJ@s%uf2&+mc$c~n!@xokM#jUo~;k2)5-QFyATjV|&UZ@Ty zV3Yg!Zd6lRkl@Gsu8NYq$g8y!*WBNV%18L(`C+g|&aR`&`y%VroCuFU>dALM(n0CJ zL~7~EpHY5V(9VxxeGs)=E@PmIM^QE^oaBQc-;-0~6*(;UDMY!cd52tGjgXQ!KFUs&SW%Q{iPSxP zn9P+cw^Gh*|1`>9h`kA$OhEoj7+NMVG$@;&joG4PZbXsiLXDC@nuh zUYC>I^2g$m>8ji-1V8#LtCs8%&+N`YLb zkGx}jG4xd%e-!Q8ABp+Z*1t>vicuX2|NCLo;bBx~e!Lu%b4O9pn;e2pfFQBJhv-5g z_V1_8n^)zGe)3KSK*^$CeaIapA9?D!bXgHqkIEQ71(Z?S<&5>{vYLNWFEcsPXV!`{Hv)I~EkEdb-x~xV1@{_*W^yO(^-I~)WN3JfKE*p7uPY$WaZN$)J zEC2XE&MNt=Rmak0Cz5p^`NHUnGGG0gQ){DKWkb5`;iaD(UXQD6#7|TpViFMX_wlt= zMqA@it(vu@ikX<0?|%st{C$$WBv%$k<*`xm{3tsozsEy839I6+(XX!|Sstf=8=2#4 zSVOezm$Nsa@|L{##V5s^Qp)dw>uFNu@d{{085bhk)Q~EVk%!Z&6E9vFd0r%(bPxGr zZ$VrB^rbOdwts`%27qsX&FSPL&}-TAu`~AV^G%eB6V_Ax+zud*2X6BNR zm0PKlrMdNf&mA@Uz5Vt6^YZy{;GBEzInO!gx#u~{^L!tky}m1uep!R1USj_Q_!V92 z_H$k*%6%ED^1bWat>}CVD!VT?{U0Y3esnkNUgBJ-&@{L=k))FOEo8sI+*r=vGKa9{ z`3e5dBaH@bG&iH>Z@XP|NpVF!5Sc!MmUOCm0kovv^2^N%Bk>VB{GPiBo!Q~W$ECX( zJ&>U<8jmw5cb6Nz_!aMsmLRZrLPA^CG1)c1c-#e}$%}06G9;^?j7O=cwaZQ0+Pl*D zm^5HH(-R7f?0^XnB!x~KK?fI(cL8easM|kVzzfJ+6jWT2P@f=fBLQoTRc@o>_oj@; z0Qbrt&r7gTRaY1O>%t{PrfZ5(Za1o(mC%r`X1VIqzV2LtdIH?64c-hsG3PnleQh%;dMqeBVCnS~Sdq36lb7&Wfd(Pjm z=WOK*#GbQs9N%;P&d+=V9J~q7Qq3~XW5!Ua_XY%Gm9&etJ}_!jeQK!7l$ExnTF&S3eQUBv-Z5iVKjbbwp3 z2tyw%8h9PB&3w$=ip5QI8f!ygIA0l{(839B5okE>IGE1}BjC5XXm$eD5BIj0{=6gU zyB9#({S|?+q>GWu`|MZ}0QRr%=UZ&RKbG><@%jXnU12obMpN#4AVqSzcv& zyz$C6{`gd;yN&osf4q=-?sQ>inD55UP$*)~CJAQ(2xkF}I2(DHGtr1w`Qtw3Og7?2 z{qbq!Y?E+|;v*!H=9*o>>`I|K9Ob-*%S=OBIaCRyxHAf* z@#K9k`)gapoVyXfKjrhwKw&j=?&oE%G-G3jK_7Go@yflPPs%=)X3aC| z{esqalZMmPLDG}FM!SV7dZN0-EU5uMB@(vs>LXdAU9JL}dZ7dha-?qKsV#Y~?l7L} zC1v8q_)#g8SJ{I=#R;AXUNLi|=5%V1Veo=D533%*%BZsvm|3uhbAg&=FxKM2BE~C#xfLq-Du!fENqw*WM*|| zOTS?dcC(@lGH#!dDU?^V9ojL8@><@?J-nb+47K@%=w6nzfY*j@bnxQcJkvf-lEU9y z0==?LQngW(K}{}yzMs{smtbldx>WiV%{;)%#4aLB0=(iNIgk~vkn^DzZkEev;QLa! zML5W;tBmnGM7ghGEd;_`oCf2hS*Y@N6XMlIvjtwqVSl%vi5CnSzDGs>}ay2YK9(k7z1>iCqi##p}fzCf6H>GNkbqcHc4DJNjSl~ z!^H<@2Il*vlF?-+DR&VVH*SkmL!m3cMER$s`KaL(rF|g{rAFJNvkW9|LegpG%#wi8 z0tVYNkkRQo%bUk^bK3yCdw`^G;s&itkEM*p6r?OD<#KF(JS7IFR0eC@%xY zwacUgD%^_iaO)RRI^Z*Fq%rJwiyRCRf8->_aT1}wix$l=%KeGY2XpW7IPY+}7$iqo zgmWx)m4r1_j{_2nZ4{w`zHYWKPs>gQ#Cw?m`J3HsagM-&=&=I2{tFqm#iBZ}T#*8C~-b zON6eFj~fI2JIz~$?MUh#xv!ZObdl$wx<4q)DqrDU7;IExttHj^sG$qqIw zOJ0m_zd>QWWF+;JfXA36%{KE%Cxti1#s4!B4XXm-PZl;!x?(ifkfS#_RWMJw&nLLM zGYPkV%qx%Q93H}6*)01vdbeqP59w3FoCCJV_D=xQd2>S8H(psYQ@mBqV3-h~=qucz zkG_y(svIJJOcg7nQo8g8sI7Wk#w~N^La@IOuCjr4$M5huafl9RXOB=0dG80WT<*Fhuqq7(|n#h&Nix#SVQZkx~f^(KZvfx9g|1XVg zfy{P)dt;}|*Z_&bKlw%!S@{Ji*v!J_$+hr?>HxTpRKl4o*BGKGSVoEzmMt~n?ZUl) z7{m)`$2)8>ixIq}I4Zm&sjPdY41FarlG08{v;TFLVVGqMonf&)RSc1LaNa32W=r~cl{}U$ z+$%o^DjKkXR{1^jP_Fb0Z*LT@MCarJNCixj!3vMaUaLiHNc(Td&(Wc3Ih4J6T%HyX zB*wtsC?E=2B?I>1oUBsCIXRYc?#i&CN>l_k;FP=^G{(~UTM#nA%@p_(xhRK0;XzSg zFDl#E#A3o07FxWrD&^yE%Z&f5v4X(!l?L1SzxDrkY&8k zn9A)6t{BeCxV#bJvivU@w@N214r+E)j%00r#)7~i#?y!(Wg#4HD0XVvPYGflUX>q5 zx|7yh6q)AKRmRhuV5K2Y?G!7OU6k#t@|v8E9DqEyA>R$KiwS7oFt}Dy8Zqg%d>W73 zRDMg2*%+tb8bXxla1H7MrC23H3(YDjSnI(&d+}8nHUV{%7Z6k7kVAo&uWRxsYIp@A zs4P?&gmjJWgef@0z6i1ZXoT{4K!E6JMYR{@2!CIF_=g-1kbSu^?Yk?7(3pBkIMw}4 zuE!`!8HM+;Vt8D^m0YayLqMR|BzbUh3hWkeWfZ3*vJUaee~_msF69l9no$_Kj(Jq& zIFg$K2U)>^)h)Rhd#s6q>rgR~!j8)*EfJ+z#3bt19<<#_N5|)7Dy^6=S(%FT7R=hl zD6b`L%T#cJkc>f{o31!mhYqOFAttA^8rnKJ8E3Y=mFBEnssxVO-Cy}KppMv~-M~o$M~)si zX!KBu9|fK&Y-^d$KFCvmh%9!b?L(C3+2kS048(BaI#elT?~G7JAO?7W3=2+tyI7ji zM_nxMQ_dJAh6RpMZs0RdrJ9juD7NM+myi{PV?8X0O*CTZ%=xM^)6C{hRwf|pBNY6q zGKbBdqU0lM29@`=M6&AVm2!B|C{i-*7V%L^x}iP~MG_Ubhkxm|z&u-`$yYu@d31g~G_HJ%5_bMz2ujTk442xZ0W7e9~k-$w3JYo5g8P zANFk1yYg&L&|}+_kD<=2hT^7I0eXR$P0kdpHI6+Mpu)Fq62f{{D(lcfK+s)O0hqQ^ znIYQhL2W9W7w{q*dO&&GVi)1_N$ky>s}%|66@ls!_SInpsCi-^D%h;(6xv=*X9Y)< zaSmHJl)Mu7yHn}V&q}XSO+8Ebmo2^N=QBz@yVwf_^`+e17ALGJ)Ml)AwNZUPTK}nv z6Q>`PIMD{ZzpXuuA{dHjj!t+2ZvpxAntqfWzp4PNPV7(450y?dZnM&q*PcaxT~L2y zOMh4LK-Due_GxuK<^QGDVM}i)d0?mk=`^X~7=lxbSR1GoH0zrMwF2ZhKgkEPOtucT06B8XOV3fo`lrvj> zlr3(o4uL9M?*QuqveMZL@#+~vE4C6JN3NNTYREk$u3pN}VY%1W?oc!_M={5LusMOUCboGoBH(ix@D%Qq*3QI2kx!oNEbO8~#?FR)qDi`U%^RrskmJ zd>WVw_?Ec^b!`+^r@qMYGSsDby8wri7O4GZsn}uVcU4ETN!`>tHe0mS^vh6&CInfS zub28V+Atbzm`Gu7)W1SyyDhtD-E?I+HDlQLSAB)~Wa@c!jM1yGzaOjaWe5AI$*5ow zmEPd<&Smv+s#v886!E9h^?#^kSNp3+1Y3;Nv<=t&_Xd9QinhLnR@2@WI2F4rp{RWV zZZ1vFnE#8=A=B>6I2ZTo8*2#wC~9WhOHlC)F;8_!J~&cyH1=g8Z0N(c>Zs$(KI-|V1zK#fQ``RPCc!u386!Vi5a!nHn_1&CAE_+ulrz- zC>Hqum@dBHi$OTv7maYl|FdV=2aaW@{{QhTYj4K=65D0(RIZ@h*bB@CdtL8Hgl6w> z7;9bf;uEm_;2nhUm=`COwmsfw5pMB5gYXkCeC*mj^!7uz(%To|5-;W#+Z-?SzHJ5G z-U#!&*$AKX_C%QN?SU}Ui-ESa^L9g+;Kgv;BE4NOTDE%LM-T>i(-GRd7;l@|i}AMJ z@^(V_hZm!6{mt6};W;lK`iVxD}QtEC?D7!sbwd2EsCC!)0OOz9= zTA6o>S{M*477n}jlbD!DwLdCt>FQIePMv>IyRk0{Re0TrwVHNy;e0>Z%=!Km{Q8g? zKhTHCU8Jm`>J+V6BAPYtCAhsfW{%;ve2t-^Eb$6X@tYp; z4gAiArKs{u=^14m>$F?F%Gc^96P2}9%W)B}CQ<75Sa2opS8s#txm2630ha6s6@Gz4 zX!zDBFquBD;!s_jM`_LKDpZ-TGS*+yMq*PDq1hOwm`c$WZ#C^OR5=H~3wXb! zK!2c)mH<;p^(hM7V~JxdShM2IIcgJ%lC(*{%Y=$t=mpiowo2MGV5!yo0mVo8Sa9+A0F5g{Y^|>%>8iQffXE?$T7ov(K3~b&M zD(tJ3N$h=>_B3SKdz`m%x8T;kL(i7cycG}u`KmS_v2t3!8|%%3ky-(Jvx)X1*nJtz z>!QW+bwlXi;;Swjo}w)RUCZfkoZ6gv%vHxTh_73sO|qKSwdTb24^P4<7>J(Y%0+oT z9P+U){6RjOmae@7@v;;Y!U0%@b{DJgRf&nM41@-F6;V@Jk4$Y7YX5+8#|A}Gr?jBS zY<`Z`z+sa)LLroDJF-hpYT+PZb!mSs!-fOPIWHr*4J+!eUAEd(tLaS%@z!k3K&>yb zt)qfn!O)~F*7(}Eg4UF9-d~`#a@akvEZ#5@nNw)_yW0DK;)X?WKA=Hu;xN;#*vuu` z1W*f=UGd|$?gl~5m1$hgeZo51g0@>|(Mk=zaO(#JQM?%BqKZOuBx$dhU6en@(u}UW zXATAsx@RJNuuH=-!6a_PuEtu-7Oc|7qLfWEV5$XPVAg6=5M#7DC?FX}~u2RG?>>neDIDk}Y)dh^AP? zN-F3P3k|8G(NIob5^bfBx-iwtUo5PqoUPG;?3rWQ3h>f)D#(cq=RimrOFgdfHPx5w zsrsIkm`WduwzG=US}*kd4rVqt91Y{88ttN;qf`IUk@q%Ra9%4i2a3D4bn)T9>pC~C zNnw31Y9FAxzd%p@hmtgV8mIFFa1SnPXHn%>th`jf7n*a^`ZztgP>A4kw?ZYmDQ$A} zxBo*ZfzUHF@VZuR5pfE=LsyrI2z~fp0%!!u@#>vfITE8P&r@Z=}$Jw;o z+N)^GK4$jDIACpkGio{~zQD}v0iN5~(jQnk0a||f3DNyHOkpS+?eM_pUpzbxa$KB!4jal!8o@6@*xBtcHRw{YK)0W+g_gFE}??*Qt zW#-v2*;o|c10Q>b1>hO)W^8x9zj3b%jRC`m)i z=XRc5w!i@Mb@;8doHJkNo2mY|#0NW(xjZd}+>dw? z1I3e{+I`J92&IvR&KPA8k8H~DSgg#F=NT#5TEa4bn<+tGWQ1pSpjb1vz}Er*BEylE zL?cFek^{vvv!3uJSxs*zA*}^FKGGv&|8*KIu4dNJ9_Z+c-&1X^98QZ5L*iR2z@g4QlL((}`klA>z zxwL8HGB);vXMs6DJfF;*WsTwLqr1FZw3!fg*f>8agl)%o9FnHB$_%dXM(FdceQjV%H_ zi_2^HWY7=uTN&{Wni-`}VlTP%*M)oj+^?C;*Eo?E&0@(EUmM&Vavb_ZwkKZyFBtpx zO-=M)thTmR(_$_w5((*bY3sTWf%d0*M3$PQZv~s**p#gIw%giSO{JWH64~^2`Xs!5 zb5jRB!psqYXFhGDKh{y-V#g9F-DDYOO2-nYl{iiFb;J&zo9jVCt&2YYj@ShA%(md^%NU0M8~P2 z!^W&5^hqLzvi-teLh&nlLk_n(3zpLG{Xoyoh+2mwnj-+eV(Gca+nb)(hl{o}?q!cx z$?(Ehz#9m7j-%d3Y%ne;)XxYGa~qT8RdbEG4K`jL*w%&lGGJS$;GJ+9zeS%F=m>q= z;d{ht+Ry-L@pNFiE>P&3`shGM@ci+NPRYWnUWu+yu`-{WU6{>Z!w49@C$~Y_rd9hfnjq7JDv*%?1;WKVSOdAUQ9w*|bztj`3Ts>f#LQ#B5|Hga|f`5m}Qf_Hs z-oZbqjb_JmY^ANH;*#^_FIf!%4Z_Y`t0EtNI!YnTr*5(cF-qdsKYp zzw1ZPcL`MVV=$EK_lF!{_CNL8b_<%YJt;t^4+n+bE{$t)o2u>vciq^%NshTBA|t&N zx}^;QOyMT;9@A$0;Xg%AUw5m?+m*I|*yLT&8|(x8+O#+xn@wk)X)+~vT1($!_-IF^ z{DmJX_)th<>yn%NX?7R~cmaJ)@Hlup;D1R CO>+bQ diff --git a/test/gencol1.test b/test/gencol1.test index 43f48dff7..ee0ebc53f 100644 --- a/test/gencol1.test +++ b/test/gencol1.test @@ -585,5 +585,34 @@ do_execsql_test gencol1-20.2 { SELECT * FROM tab; } {2001-01-01 0 0 5 {}} +# 2021-07-30 forum https://sqlite.org/forum/forumpost/ff3ffe09251c105b?t=h +# +ifcapable vtab { +reset_db + do_execsql_test gencol1-21.1 { + CREATE TABLE t1( + a integer primary key, + b int generated always as (a+5), + c text GENERATED ALWAYS as (printf('%08x',a)), + d Generated + Always + AS ('xyzzy'), + e int Always default(5) + ); + INSERT INTO t1(a) VALUES(5); + SELECT name, type FROM pragma_table_xinfo('t1'); + } {a INTEGER b INT c TEXT d {} e INT} +} + +# 2021-09-07 forum https://sqlite.org/forum/forumpost/699b44b3ee +# +reset_db +do_execsql_test gencol1-22.1 { + CREATE TABLE t0(a PRIMARY KEY,b TEXT AS ('2') UNIQUE); + INSERT INTO t0(a) VALUES(2); + SELECT * FROM t0 AS x JOIN t0 AS y + WHERE x.b='2' + AND (y.a=2 OR (x.b LIKE '2*' AND y.a=x.b)); +} {2 2 2 2} finish_test diff --git a/test/hook.test b/test/hook.test index 1dba87f27..bb868df8b 100644 --- a/test/hook.test +++ b/test/hook.test @@ -677,30 +677,32 @@ do_preupdate_test 7.4.2.3 { DELETE main t5 1 1 a 1 } -do_execsql_test 7.5.1.0 { - CREATE TABLE t7(a, b); - INSERT INTO t7 VALUES('one', 'two'); - INSERT INTO t7 VALUES('three', 'four'); - ALTER TABLE t7 ADD COLUMN c DEFAULT NULL; -} - -do_preupdate_test 7.5.1.1 { - DELETE FROM t7 WHERE a = 'one' -} { - DELETE main t7 1 1 one two {} -} - -do_preupdate_test 7.5.1.2 { - UPDATE t7 SET b = 'five' -} { - UPDATE main t7 2 2 three four {} three five {} -} - -do_execsql_test 7.5.2.0 { - CREATE TABLE t8(a, b); - INSERT INTO t8 VALUES('one', 'two'); - INSERT INTO t8 VALUES('three', 'four'); - ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx'; +ifcapable altertable { + do_execsql_test 7.5.1.0 { + CREATE TABLE t7(a, b); + INSERT INTO t7 VALUES('one', 'two'); + INSERT INTO t7 VALUES('three', 'four'); + ALTER TABLE t7 ADD COLUMN c DEFAULT NULL; + } + + do_preupdate_test 7.5.1.1 { + DELETE FROM t7 WHERE a = 'one' + } { + DELETE main t7 1 1 one two {} + } + + do_preupdate_test 7.5.1.2 { + UPDATE t7 SET b = 'five' + } { + UPDATE main t7 2 2 three four {} three five {} + } + + do_execsql_test 7.5.2.0 { + CREATE TABLE t8(a, b); + INSERT INTO t8 VALUES('one', 'two'); + INSERT INTO t8 VALUES('three', 'four'); + ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx'; + } } if 0 { @@ -848,48 +850,53 @@ do_preupdate_test 7.6.4 { } # No preupdate callbacks for modifying sqlite_master. -do_preupdate_test 8.1 { CREATE TABLE x1(x, y); } { } -do_preupdate_test 8.2 { ALTER TABLE x1 ADD COLUMN z } { } -do_preupdate_test 8.3 { ALTER TABLE x1 RENAME TO y1 } { } -do_preupdate_test 8.4 { CREATE INDEX y1x ON y1(x) } { } -do_preupdate_test 8.5 { CREATE VIEW v1 AS SELECT * FROM y1 } { } -do_preupdate_test 8.6 { DROP TABLE y1 } { } +ifcapable altertable { + do_preupdate_test 8.1 { CREATE TABLE x1(x, y); } { } + do_preupdate_test 8.2 { ALTER TABLE x1 ADD COLUMN z } { } + do_preupdate_test 8.3 { ALTER TABLE x1 RENAME TO y1 } { } + do_preupdate_test 8.4 { CREATE INDEX y1x ON y1(x) } { } + do_preupdate_test 8.5 { CREATE VIEW v1 AS SELECT * FROM y1 } { } + do_preupdate_test 8.6 { DROP TABLE y1 } { } +} #------------------------------------------------------------------------- reset_db db preupdate hook preupdate_hook -do_execsql_test 9.0 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); - CREATE TABLE t2(a, b INTEGER PRIMARY KEY); -} -do_preupdate_test 9.1 { - INSERT INTO t1 VALUES(456, NULL, NULL); -} { - INSERT main t1 456 456 0 456 {} {} -} -do_execsql_test 9.2 { - ALTER TABLE t1 ADD COLUMN d; -} -do_preupdate_test 9.3 { - INSERT INTO t1(a, b, c) VALUES(457, NULL, NULL); -} { - INSERT main t1 457 457 0 457 {} {} {} -} -do_preupdate_test 9.4 { - DELETE FROM t1 WHERE a=456 -} { - DELETE main t1 456 456 0 456 {} {} {} + +ifcapable altertable { + do_execsql_test 9.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t2(a, b INTEGER PRIMARY KEY); + } + do_preupdate_test 9.1 { + INSERT INTO t1 VALUES(456, NULL, NULL); + } { + INSERT main t1 456 456 0 456 {} {} + } + do_execsql_test 9.2 { + ALTER TABLE t1 ADD COLUMN d; + } + do_preupdate_test 9.3 { + INSERT INTO t1(a, b, c) VALUES(457, NULL, NULL); + } { + INSERT main t1 457 457 0 457 {} {} {} + } + do_preupdate_test 9.4 { + DELETE FROM t1 WHERE a=456 + } { + DELETE main t1 456 456 0 456 {} {} {} + } + do_preupdate_test 9.5 { + INSERT INTO t2 DEFAULT VALUES; + } { + INSERT main t2 1 1 0 {} 1 + } + do_preupdate_test 9.6 { + INSERT INTO t1 DEFAULT VALUES; + } { + INSERT main t1 458 458 0 458 {} {} {} + } } -do_preupdate_test 9.5 { - INSERT INTO t2 DEFAULT VALUES; -} { - INSERT main t2 1 1 0 {} 1 -} -do_preupdate_test 9.6 { - INSERT INTO t1 DEFAULT VALUES; -} { - INSERT main t1 458 458 0 458 {} {} {} -} do_execsql_test 10.0 { diff --git a/test/in4.test b/test/in4.test index 07bd54770..a3fe22e78 100644 --- a/test/in4.test +++ b/test/in4.test @@ -470,4 +470,51 @@ do_execsql_test 11.2 { AND c BETWEEN 'abc' AND 'xyz'; } {/ SeekScan /} +# 2021-06-25 ticket 6dcbfd11cf666e21 +# Another problem with OP_SeekScan +# +reset_db +do_execsql_test 12.0 { + CREATE TABLE t1(a,b,c); + CREATE INDEX t1abc ON t1(a,b,c); + CREATE INDEX t1bca on t1(b,c,a); + INSERT INTO t1 VALUES(56,1119,1115); + INSERT INTO t1 VALUES(57,1147,1137); + INSERT INTO t1 VALUES(100,1050,1023); + INSERT INTO t1 VALUES(101,1050,1023); + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t1','t1abc','358677 2 2 1'); + INSERT INTO sqlite_stat1 VALUES('t1','t1bca','358677 4 2 1'); + ANALYZE sqlite_schema; + SELECT * FROM t1 NOT INDEXED + WHERE (b = 1137 AND c IN (97, 98)) + OR (b = 1119 AND c IN (1115, 1023)); +} {56 1119 1115} +do_execsql_test 12.1 { + SELECT * FROM t1 + WHERE (b = 1137 AND c IN (97, 98)) + OR (b = 1119 AND c IN (1115, 1023)); +} {56 1119 1115} + +# 2021-11-02 ticket 5981a8c041a3c2f3 +# Another OP_SeekScan problem. +# +reset_db +do_execsql_test 13.0 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, a INT, b INT, c INT); + INSERT INTO t1 VALUES(10,1,2,5); + INSERT INTO t1 VALUES(20,1,3,5); + INSERT INTO t1 VALUES(30,1,2,4); + INSERT INTO t1 VALUES(40,1,3,4); + ANALYZE sqlite_master; + INSERT INTO sqlite_stat1 VALUES('t1','t1x','84000 3 2 1'); + CREATE INDEX t1x ON t1(a,b,c); + PRAGMA writable_schema=RESET; + SELECT * FROM t1 + WHERE a=1 + AND b IN (2,3) + AND c BETWEEN 4 AND 5 + ORDER BY +id; +} {10 1 2 5 20 1 3 5 30 1 2 4 40 1 3 4} + finish_test diff --git a/test/incrblob3.test b/test/incrblob3.test index 5f2e860d0..b2bc8f8e0 100644 --- a/test/incrblob3.test +++ b/test/incrblob3.test @@ -14,6 +14,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +ifcapable !incrblob { + finish_test + return +} + sqlite3 db test.db sqlite3_db_config_lookaside db 0 0 0 diff --git a/test/incrblobfault.test b/test/incrblobfault.test index 10c2c8ecb..d070babe2 100644 --- a/test/incrblobfault.test +++ b/test/incrblobfault.test @@ -14,6 +14,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +ifcapable !incrblob { + finish_test + return +} + set testprefix incrblobfault do_execsql_test 1.0 { diff --git a/test/indexexpr1.test b/test/indexexpr1.test index a31b87695..92dfc8689 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -75,18 +75,20 @@ do_execsql_test indexexpr1-150eqp { ORDER BY +rowid; } {/USING INDEX t1abx/} -do_execsql_test indexexpr1-160 { - ALTER TABLE t1 ADD COLUMN d; - UPDATE t1 SET d=length(a); - CREATE INDEX t1a2 ON t1(SUBSTR(a, 27, 3)) WHERE d>=29; - SELECT rowid, b, c FROM t1 - WHERE substr(a,27,3)=='ord' AND d>=29; -} {1 1 1} -do_execsql_test indexexpr1-160eqp { - EXPLAIN QUERY PLAN - SELECT rowid, b, c FROM t1 - WHERE substr(a,27,3)=='ord' AND d>=29; -} {/USING INDEX t1a2/} +ifcapable altertable { + do_execsql_test indexexpr1-160 { + ALTER TABLE t1 ADD COLUMN d; + UPDATE t1 SET d=length(a); + CREATE INDEX t1a2 ON t1(SUBSTR(a, 27, 3)) WHERE d>=29; + SELECT rowid, b, c FROM t1 + WHERE substr(a,27,3)=='ord' AND d>=29; + } {1 1 1} + do_execsql_test indexexpr1-160eqp { + EXPLAIN QUERY PLAN + SELECT rowid, b, c FROM t1 + WHERE substr(a,27,3)=='ord' AND d>=29; + } {/USING INDEX t1a2/} +} # ORDER BY using an indexed expression # @@ -166,18 +168,20 @@ do_execsql_test indexexpr1-250eqp { ORDER BY +id; } {/USING INDEX t1abx/} -do_execsql_test indexexpr1-260 { - ALTER TABLE t1 ADD COLUMN d; - UPDATE t1 SET d=length(a); - CREATE INDEX t1a2 ON t1(SUBSTR(a, 27, 3)) WHERE d>=29; - SELECT id, b, c FROM t1 - WHERE substr(a,27,3)=='ord' AND d>=29; -} {1 1 1} -do_execsql_test indexexpr1-260eqp { - EXPLAIN QUERY PLAN - SELECT id, b, c FROM t1 - WHERE substr(a,27,3)=='ord' AND d>=29; -} {/USING INDEX t1a2/} +ifcapable altertable { + do_execsql_test indexexpr1-260 { + ALTER TABLE t1 ADD COLUMN d; + UPDATE t1 SET d=length(a); + CREATE INDEX t1a2 ON t1(SUBSTR(a, 27, 3)) WHERE d>=29; + SELECT id, b, c FROM t1 + WHERE substr(a,27,3)=='ord' AND d>=29; + } {1 1 1} + do_execsql_test indexexpr1-260eqp { + EXPLAIN QUERY PLAN + SELECT id, b, c FROM t1 + WHERE substr(a,27,3)=='ord' AND d>=29; + } {/USING INDEX t1a2/} +} do_catchsql_test indexexpr1-300 { diff --git a/test/io.test b/test/io.test index e1af808a3..dfadcd136 100644 --- a/test/io.test +++ b/test/io.test @@ -640,4 +640,6 @@ foreach {tn sql} { } sqlite3_simulate_device -char {} -sectorsize 0 +unregister_devsim + finish_test diff --git a/test/ioerr.test b/test/ioerr.test index f42beef5b..29a3dede8 100644 --- a/test/ioerr.test +++ b/test/ioerr.test @@ -334,6 +334,7 @@ do_ioerr_test ioerr-12 -ckrefcount true -erc 1 -tclprep { } sqlite3_simulate_device -char {} -sectorsize 0 catch {db close} +unregister_devsim do_ioerr_test ioerr-13 -ckrefcount true -erc 1 -sqlprep { PRAGMA auto_vacuum = incremental; diff --git a/test/istrue.test b/test/istrue.test index 13eccabc2..f1ba63248 100644 --- a/test/istrue.test +++ b/test/istrue.test @@ -143,20 +143,22 @@ foreach {tn val} [list 1 NaN 2 -NaN 3 NaN0 4 -NaN0 5 Inf 6 -Inf] { } {0} } -do_execsql_test istrue-700 { - CREATE TABLE t7( - a INTEGER PRIMARY KEY, - b BOOLEAN DEFAULT false, - c BOOLEAN DEFAULT true - ); - INSERT INTO t7(a) VALUES(1); - INSERT INTO t7(a,b,c) VALUES(2,true,false); - ALTER TABLE t7 ADD COLUMN d BOOLEAN DEFAULT false; - ALTER TABLE t7 ADD COLUMN e BOOLEAN DEFAULT true; - INSERT INTO t7(a,b,c) VALUES(3,true,false); - INSERT INTO t7 VALUES(4,false,true,true,false); - SELECT *,'x' FROM t7 ORDER BY a; -} {1 0 1 0 1 x 2 1 0 0 1 x 3 1 0 0 1 x 4 0 1 1 0 x} +ifcapable altertable { + do_execsql_test istrue-700 { + CREATE TABLE t7( + a INTEGER PRIMARY KEY, + b BOOLEAN DEFAULT false, + c BOOLEAN DEFAULT true + ); + INSERT INTO t7(a) VALUES(1); + INSERT INTO t7(a,b,c) VALUES(2,true,false); + ALTER TABLE t7 ADD COLUMN d BOOLEAN DEFAULT false; + ALTER TABLE t7 ADD COLUMN e BOOLEAN DEFAULT true; + INSERT INTO t7(a,b,c) VALUES(3,true,false); + INSERT INTO t7 VALUES(4,false,true,true,false); + SELECT *,'x' FROM t7 ORDER BY a; + } {1 0 1 0 1 x 2 1 0 0 1 x 3 1 0 0 1 x 4 0 1 1 0 x} +} do_execsql_test istrue-710 { SELECT 0.5 IS TRUE COLLATE NOCASE; diff --git a/test/json104.test b/test/json104.test index e56e7edef..8d95e6014 100644 --- a/test/json104.test +++ b/test/json104.test @@ -125,6 +125,9 @@ do_execsql_test json104-313 { do_execsql_test json104-314 { SELECT json_patch('{}','{"a":{"bb":{"ccc":null}}}'); } {{{"a":{"bb":{}}}}} +do_execsql_test json104-320 { + SELECT json_patch('{"x":{"one":1}}','{"x":{"two":2},"x":"three"}'); +} {{{"x":"three"}}} #------------------------------------------------------------------------- diff --git a/test/memdb1.test b/test/memdb1.test index ae2f5d50d..5e219a4c0 100644 --- a/test/memdb1.test +++ b/test/memdb1.test @@ -195,6 +195,22 @@ do_test 620 { lappend rc $msg } {1 {wrong # args: should be "db serialize ?DATABASE?"}} +# 2021-07-19 https://sqlite.org/forum/forumpost/e1cbb5f450b98aa6 +# The TEMP database cannot participate in serialization or +# deserialization. +# +reset_db +do_test 650 { + db eval { + CREATE TEMP TABLE t0(a); + CREATE TABLE t1(x); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<1000) + INSERT INTO t1(x) SELECT random() FROM c; + } + set rc [catch {db deserialize temp [db serialize main]} err] + lappend rc err +} {1 err} + #------------------------------------------------------------------------- ifcapable vtab { reset_db @@ -213,4 +229,42 @@ ifcapable vtab { } {1 {table t1 already exists}} } + +#------------------------------------------------------------------------- +# dbsqlfuzz 0a13dfb474d4f2f11a48a2ea57075c96fb456dd7 +# +if {[wal_is_capable]} { + reset_db + do_execsql_test 800 { + PRAGMA auto_vacuum = 0; + PRAGMA page_size = 8192; + PRAGMA journal_mode = wal; + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + CREATE TABLE t2(x, y); + } {wal} + db close + + set fd [open test.db] + fconfigure $fd -translation binary -encoding binary + set data [read $fd [expr 20*1024]] + + sqlite3 db "" + db deserialize $data + + do_execsql_test 810 { + PRAGMA locking_mode = exclusive; + SELECT * FROM t1 + } {exclusive 1 2} + + do_execsql_test 820 { + INSERT INTO t1 VALUES(3, 4); + SELECT * FROM t1; + } {1 2 3 4} + + do_catchsql_test 830 { + PRAGMA wal_checkpoint; + } {1 {database disk image is malformed}} +} + finish_test diff --git a/test/memjournal2.test b/test/memjournal2.test new file mode 100644 index 000000000..97d35a98d --- /dev/null +++ b/test/memjournal2.test @@ -0,0 +1,62 @@ +# 2022 Jan 01 +# +# 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. +# +#*********************************************************************** +# Tests focused on the in-memory journal. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix memjournal2 + +do_execsql_test 1.0 { + PRAGMA journal_mode = memory; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE); +} {memory} + +set nRow [expr 2000] + +do_execsql_test 1.1 { + BEGIN; + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$nRow + ) + INSERT INTO t1 SELECT NULL, randomblob(700) FROM s; +} + +for {set jj 200} {$jj <= 300} {incr jj} { + do_execsql_test 1.2.$jj.1 { + SAVEPOINT one; + UPDATE t1 SET b=randomblob(700) WHERE a<=$jj; + } + do_execsql_test 1.2.$jj.2 { + SAVEPOINT two; + UPDATE t1 SET b=randomblob(700) WHERE a==1; + ROLLBACK TO two; + RELEASE two; + } + do_execsql_test 1.2.$jj.3 { + SAVEPOINT two; + UPDATE t1 SET b=randomblob(700) WHERE a==1; + ROLLBACK TO two; + RELEASE two; + } + + do_execsql_test 1.2.$jj.4 { + PRAGMA integrity_check; + ROLLBACK TO one; + RELEASE one; + } {ok} +} + + +finish_test + + diff --git a/test/minmax.test b/test/minmax.test index 295fac4e9..81bd46dbe 100644 --- a/test/minmax.test +++ b/test/minmax.test @@ -646,6 +646,16 @@ do_execsql_test 14.2 { SELECT min(a) FROM t14 WHERE b='2' AND a>'50'; } {100} - +# 2021-08-21. https://sqlite.org/forum/forumpost/cfcb4b461d +# +reset_db +do_execsql_test 15.1 { + CREATE TABLE t1(a); + CREATE TABLE t2(b); + CREATE TABLE t3(c); + INSERT INTO t1 VALUES(0); + INSERT INTO t2 VALUES(5); + SELECT MIN((SELECT b FROM t2 UNION SELECT x FROM (SELECT x FROM (SELECT 1 AS x WHERE t1.a=1) UNION ALL SELECT c FROM t3))) FROM t1; +} {5} finish_test diff --git a/test/misc1.test b/test/misc1.test index c14a31ebd..758d4082e 100644 --- a/test/misc1.test +++ b/test/misc1.test @@ -728,7 +728,14 @@ do_execsql_test misc1-26.0 { # 2017-12-29 # # The following behaviors (duplicate column names on an INSERT or UPDATE) -# are undocumented. These tests are added to ensure that historical behavior +# are undocumented. <<--- Not so. There is a long-standing requirement +# in lang_update.in to say that when the columns to be updated appear more +# than once in an UPDATE statement that only the rightmost expression is used. +# See e_update-1.6.* for the tests. This is unfortunate, since omitting +# that requirement would greatly simplify the fix to the problem identified +# by forum post https://sqlite.org/forum/info/16ca0e9f32c38567 +# +# These tests are added to ensure that historical behavior # does not change accidentally. # # For duplication columns on an INSERT, the first value is used. diff --git a/test/mmap1.test b/test/mmap1.test index 7dcd1f8a4..3362f7187 100644 --- a/test/mmap1.test +++ b/test/mmap1.test @@ -12,7 +12,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -ifcapable !mmap { +ifcapable !mmap||!incrblob { finish_test return } diff --git a/test/multiplex.test b/test/multiplex.test index 6ea328906..2f1a02fb8 100644 --- a/test/multiplex.test +++ b/test/multiplex.test @@ -182,6 +182,11 @@ do_test multiplex-2.1.3 { file size [multiplex_name test.x 0] } {4096} do_test multiplex-2.1.4 { execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } } {} +do_execsql_test multiplex-2.1.5 { + PRAGMA multiplex_enabled; + PRAGMA multiplex_filecount; + PRAGMA multiplex_chunksize; +} {1 1 2147418112} do_test multiplex-2.2.1 { execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } @@ -267,6 +272,18 @@ do_test multiplex-2.5.8 { do_test multiplex-2.5.9 { file size [multiplex_name test.x 0] } [list $g_chunk_size] do_test multiplex-2.5.10 { file size [multiplex_name test.x 1] } [list $g_chunk_size] +do_execsql_test multiplex-2.5.11 { + PRAGMA multiplex_enabled; + PRAGMA multiplex_filecount; + PRAGMA multiplex_chunksize; +} {1 3 65536} +sqlite3 db test.x +do_execsql_test multiplex-2.5.12 { + PRAGMA multiplex_filecount; + PRAGMA multiplex_chunksize; +} {3 65536} + + do_test multiplex-2.5.99 { db close diff --git a/test/notnull2.test b/test/notnull2.test index 75a519e9a..12fffe28c 100644 --- a/test/notnull2.test +++ b/test/notnull2.test @@ -109,4 +109,24 @@ do_execsql_test 3.1 { SELECT * FROM t0 WHERE ((c0 NOT NULL) AND 1) OR (c0 == NULL); } {0} +# 2021-07-22 https://sqlite.org/forum/forumpost/2078b7edd2 +# +reset_db +do_execsql_test 4.0 { + SELECT *, '/' + FROM ( + SELECT NULL val FROM (SELECT 1) + UNION ALL + SELECT 'missing' FROM (SELECT 1) + ) a + LEFT JOIN (SELECT 1) + ON a.val IS NULL; +} {{} 1 / missing {} /} +do_execsql_test 4.1 { + CREATE TABLE t1(a INT); + INSERT INTO t1(a) VALUES(1); + CREATE TABLE t2(b INT); + SELECT * FROM (SELECT 3 AS c FROM t1) AS t3 LEFT JOIN t2 ON c IS NULL; +} {3 {}} + finish_test diff --git a/test/pager1.test b/test/pager1.test index a1e06d15c..ff7d3e406 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -21,6 +21,10 @@ if {[atomic_batch_write test.db]} { finish_test return } +ifcapable !incrblob { + finish_test + return +} # 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). @@ -1940,20 +1944,22 @@ do_test pager1-18.4 { } {1 {database disk image is malformed}} db2 close extra_schema_checks 0 -do_test pager1-18.5 { - sqlite3 db "" - sqlite3_db_config db DEFENSIVE 0 - execsql { - CREATE TABLE t1(a, b); - CREATE TABLE t2(a, b); - PRAGMA writable_schema = 1; - UPDATE sqlite_master SET rootpage=5 WHERE tbl_name = 't1'; - PRAGMA writable_schema = 0; - ALTER TABLE t1 RENAME TO x1; - } - catchsql { SELECT * FROM x1 } -} {1 {database disk image is malformed}} -db close +ifcapable altertable { + do_test pager1-18.5 { + sqlite3 db "" + sqlite3_db_config db DEFENSIVE 0 + execsql { + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b); + PRAGMA writable_schema = 1; + UPDATE sqlite_master SET rootpage=5 WHERE tbl_name = 't1'; + PRAGMA writable_schema = 0; + ALTER TABLE t1 RENAME TO x1; + } + catchsql { SELECT * FROM x1 } + } {1 {database disk image is malformed}} + db close +} extra_schema_checks 1 do_test pager1-18.6 { diff --git a/test/permutations.test b/test/permutations.test index b652f8571..0987daeb1 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -1138,7 +1138,14 @@ proc run_tests {name args} { set ::G(perm:dbconfig) $options(-dbconfig) set ::G(perm:presql) $options(-presql) - foreach file [lsort $options(-files)] { + set filelist [lsort $options(-files)] + if {[info exists ::env(TCLTEST_PART)]} { + regexp {^([0-9]*)/([0-9]*)$} $::env(TCLTEST_PART) -> A B + set nFile [expr {([llength $filelist]+$B-1)/$B}] + set filelist [lrange $filelist [expr ($A-1)*$nFile] [expr $A*$nFile-1]] + } + + foreach file $filelist { if {[file tail $file] == $file} { set file [file join $::testdir $file] } if {[info exists ::env(SQLITE_TEST_PATTERN_LIST)]} { @@ -1212,6 +1219,7 @@ if {[file tail $argv0] == "permutations.test"} { set S $::testspec($suite) set i 1 } else { + set suite default set S [list] set i 0 } @@ -1232,9 +1240,10 @@ if {[file tail $argv0] == "permutations.test"} { set extra [list -files $files] } - eval run_tests $suite $S $extra + eval [list run_tests $suite] $S $extra } } main $argv + set argv {} finish_test } diff --git a/test/pragma.test b/test/pragma.test index 04f5bd0fb..ba61882d5 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -532,29 +532,31 @@ Page 6 is never used} {row 1 missing from index i2}} # Verify that PRAGMA integrity_check catches UNIQUE and NOT NULL # constraint violations. # -sqlite3_db_config db DEFENSIVE 0 -do_execsql_test pragma-3.20 { - CREATE TABLE t1(a,b); - CREATE INDEX t1a ON t1(a); - INSERT INTO t1 VALUES(1,1),(2,2),(3,3),(2,4),(NULL,5),(NULL,6); - PRAGMA writable_schema=ON; - UPDATE sqlite_master SET sql='CREATE UNIQUE INDEX t1a ON t1(a)' - WHERE name='t1a'; - UPDATE sqlite_master SET sql='CREATE TABLE t1(a NOT NULL,b)' - WHERE name='t1'; - PRAGMA writable_schema=OFF; - ALTER TABLE t1 RENAME TO t1x; - PRAGMA integrity_check; -} {{non-unique entry in index t1a} {NULL value in t1x.a} {non-unique entry in index t1a} {NULL value in t1x.a}} -do_execsql_test pragma-3.21 { - PRAGMA integrity_check(3); -} {{non-unique entry in index t1a} {NULL value in t1x.a} {non-unique entry in index t1a}} -do_execsql_test pragma-3.22 { - PRAGMA integrity_check(2); -} {{non-unique entry in index t1a} {NULL value in t1x.a}} -do_execsql_test pragma-3.23 { - PRAGMA integrity_check(1); -} {{non-unique entry in index t1a}} +ifcapable altertable { + sqlite3_db_config db DEFENSIVE 0 + do_execsql_test pragma-3.20 { + CREATE TABLE t1(a,b); + CREATE INDEX t1a ON t1(a); + INSERT INTO t1 VALUES(1,1),(2,2),(3,3),(2,4),(NULL,5),(NULL,6); + PRAGMA writable_schema=ON; + UPDATE sqlite_master SET sql='CREATE UNIQUE INDEX t1a ON t1(a)' + WHERE name='t1a'; + UPDATE sqlite_master SET sql='CREATE TABLE t1(a NOT NULL,b)' + WHERE name='t1'; + PRAGMA writable_schema=OFF; + ALTER TABLE t1 RENAME TO t1x; + PRAGMA integrity_check; + } {{non-unique entry in index t1a} {NULL value in t1x.a} {non-unique entry in index t1a} {NULL value in t1x.a}} + do_execsql_test pragma-3.21 { + PRAGMA integrity_check(3); + } {{non-unique entry in index t1a} {NULL value in t1x.a} {non-unique entry in index t1a}} + do_execsql_test pragma-3.22 { + PRAGMA integrity_check(2); + } {{non-unique entry in index t1a} {NULL value in t1x.a}} + do_execsql_test pragma-3.23 { + PRAGMA integrity_check(1); + } {{non-unique entry in index t1a}} +} # PRAGMA integrity check (or more specifically the sqlite3BtreeCount() # interface) used to leave index cursors in an inconsistent state @@ -564,7 +566,7 @@ do_execsql_test pragma-3.23 { # that problem has been fixed. # do_test pragma-3.30 { - db close + catch { db close } delete_file test.db sqlite3 db test.db db eval { @@ -815,7 +817,7 @@ do_test pragma-6.7 { ORDER BY cid} } [concat \ {0 one INT 1 -1 0} \ - {1 two text 0 {} 0} \ + {1 two TEXT 0 {} 0} \ {2 three {VARCHAR(45, 65)} 0 'abcde' 0} \ {3 four REAL 0 X'abcdef' 0} \ {4 five {} 0 CURRENT_TIME 0} \ @@ -1957,14 +1959,16 @@ do_test 23.3 { capture_pragma db2 out {PRAGMA index_list(t1)} db2 eval {SELECT seq, name, "unique", origin, '|' FROM out ORDER BY seq} } {0 i3 0 c | 1 i2 0 c | 2 i2x 0 c | 3 i1 0 c |} -do_test 23.4 { - db eval { - ALTER TABLE t1 ADD COLUMN e; - } - db2 eval { - PRAGMA table_info(t1); - } -} {/4 e {} 0 {} 0/} +ifcapable altertable { + do_test 23.4 { + db eval { + ALTER TABLE t1 ADD COLUMN e; + } + db2 eval { + PRAGMA table_info(t1); + } + } {/4 e {} 0 {} 0/} +} do_test 23.5 { db eval { DROP TABLE t2; diff --git a/test/quote.test b/test/quote.test index 212885c05..9810a3ca0 100644 --- a/test/quote.test +++ b/test/quote.test @@ -141,43 +141,45 @@ do_execsql_test 2.5 { # 2021-03-13 # ticket 1c24a659e6d7f3a1 -reset_db -do_catchsql_test 3.0 { - CREATE TABLE t1(a,b); - CREATE INDEX x1 on t1("b"); - ALTER TABLE t1 DROP COLUMN b; -} {1 {error in index x1 after drop column: no such column: b}} -do_catchsql_test 3.1 { - DROP TABLE t1; - CREATE TABLE t1(a,"b"); - CREATE INDEX x1 on t1("b"); - ALTER TABLE t1 DROP COLUMN b; -} {1 {error in index x1 after drop column: no such column: b}} -do_catchsql_test 3.2 { - DROP TABLE t1; - CREATE TABLE t1(a,'b'); - CREATE INDEX x1 on t1("b"); - ALTER TABLE t1 DROP COLUMN b; -} {1 {error in index x1 after drop column: no such column: b}} -do_catchsql_test 3.3 { - DROP TABLE t1; - CREATE TABLE t1(a,"b"); - CREATE INDEX x1 on t1('b'); - ALTER TABLE t1 DROP COLUMN b; -} {1 {error in index x1 after drop column: no such column: b}} -do_catchsql_test 3.4 { - DROP TABLE t1; - CREATE TABLE t1(a, b, c); - CREATE INDEX x1 ON t1("a"||"b"); - INSERT INTO t1 VALUES(1,2,3),(1,4,5); - ALTER TABLE t1 DROP COLUMN b; -} {1 {error in index x1 after drop column: no such column: b}} -do_catchsql_test 3.5 { - DROP TABLE t1; - CREATE TABLE t1(a, b, c); - CREATE INDEX x1 ON t1("a"||"x"); - INSERT INTO t1 VALUES(1,2,3),(1,4,5); - ALTER TABLE t1 DROP COLUMN b; -} {0 {}} +ifcapable altertable { + reset_db + do_catchsql_test 3.0 { + CREATE TABLE t1(a,b); + CREATE INDEX x1 on t1("b"); + ALTER TABLE t1 DROP COLUMN b; + } {1 {error in index x1 after drop column: no such column: b}} + do_catchsql_test 3.1 { + DROP TABLE t1; + CREATE TABLE t1(a,"b"); + CREATE INDEX x1 on t1("b"); + ALTER TABLE t1 DROP COLUMN b; + } {1 {error in index x1 after drop column: no such column: b}} + do_catchsql_test 3.2 { + DROP TABLE t1; + CREATE TABLE t1(a,'b'); + CREATE INDEX x1 on t1("b"); + ALTER TABLE t1 DROP COLUMN b; + } {1 {error in index x1 after drop column: no such column: b}} + do_catchsql_test 3.3 { + DROP TABLE t1; + CREATE TABLE t1(a,"b"); + CREATE INDEX x1 on t1('b'); + ALTER TABLE t1 DROP COLUMN b; + } {1 {error in index x1 after drop column: no such column: b}} + do_catchsql_test 3.4 { + DROP TABLE t1; + CREATE TABLE t1(a, b, c); + CREATE INDEX x1 ON t1("a"||"b"); + INSERT INTO t1 VALUES(1,2,3),(1,4,5); + ALTER TABLE t1 DROP COLUMN b; + } {1 {error in index x1 after drop column: no such column: b}} + do_catchsql_test 3.5 { + DROP TABLE t1; + CREATE TABLE t1(a, b, c); + CREATE INDEX x1 ON t1("a"||"x"); + INSERT INTO t1 VALUES(1,2,3),(1,4,5); + ALTER TABLE t1 DROP COLUMN b; + } {0 {}} +} finish_test diff --git a/test/releasetest.tcl b/test/releasetest.tcl deleted file mode 100755 index da6113a19..000000000 --- a/test/releasetest.tcl +++ /dev/null @@ -1,1100 +0,0 @@ -#!/usr/bin/tclsh -# -# 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: - - --buildonly (Just build testfixture - do not run) - --config CONFIGNAME (Run only CONFIGNAME) - --dryrun (Print what would have happened) - -f|--force (Run even if uncommitted changes) - --info (Show diagnostic info) - --jobs N (Use N processes - default 1) - --keep (Delete no files after each test run) - --msvc (Use MSVC as the compiler) - --platform PLATFORM (see below) - --progress (Show progress messages) - --quick (Run "veryquick.test" only) - --veryquick (Run "make smoketest" only) - --with-tcl=DIR (Use TCL build at DIR) - -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", "Darwin-i386", -"Darwin-x86_64", "Windows NT-intel", and "Windows NT-amd64". - -Every test begins with a fresh run of the configure script at the top -of the SQLite source tree. -} - -# Return a timestamp of the form HH:MM:SS -# -proc now {} { - return [clock format [clock seconds] -format %H:%M:%S] -} - -# Omit comments (text between # and \n) in a long multi-line string. -# -proc strip_comments {in} { - regsub -all {#[^\n]*\n} $in {} out - return $out -} - -array set ::Configs [strip_comments { - "Default" { - -O2 - --disable-amalgamation --disable-shared - --enable-session - } - "Sanitize" { - CC=clang -fsanitize=undefined - -DSQLITE_ENABLE_STAT4 - -DCONFIG_SLOWDOWN_FACTOR=5.0 - --enable-session - } - "Stdcall" { - -DUSE_STDCALL=1 - -O2 - } - "Have-Not" { - # The "Have-Not" configuration sets all possible -UHAVE_feature options - # in order to verify that the code works even on platforms that lack - # these support services. - -DHAVE_FDATASYNC=0 - -DHAVE_GMTIME_R=0 - -DHAVE_ISNAN=0 - -DHAVE_LOCALTIME_R=0 - -DHAVE_LOCALTIME_S=0 - -DHAVE_MALLOC_USABLE_SIZE=0 - -DHAVE_STRCHRNUL=0 - -DHAVE_USLEEP=0 - -DHAVE_UTIME=0 - } - "Unlock-Notify" { - -O2 - -DSQLITE_ENABLE_UNLOCK_NOTIFY - -DSQLITE_THREADSAFE - -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 - } - "User-Auth" { - -O2 - -DSQLITE_USER_AUTHENTICATION=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 - -DSQLITE_ENABLE_STMT_SCANSTATUS - -DSQLITE_LIKE_DOESNT_MATCH_BLOBS - -DSQLITE_ENABLE_CURSOR_HINTS - --enable-json1 - } - "Check-Symbols" { - -DSQLITE_MEMDEBUG=1 - -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_MEMSYS5=1 - -DSQLITE_ENABLE_MEMSYS3=1 - -DSQLITE_ENABLE_COLUMN_METADATA=1 - -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 - -DSQLITE_SECURE_DELETE=1 - -DSQLITE_SOUNDEX=1 - -DSQLITE_ENABLE_ATOMIC_WRITE=1 - -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 - -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_STMT_SCANSTATUS - --enable-json1 --enable-fts5 --enable-session - } - "Debug-One" { - --disable-shared - -O2 -funsigned-char - -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_COLUMN_METADATA=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_HIDDEN_COLUMNS - -DSQLITE_MAX_ATTACHED=125 - -DSQLITE_MUTATION_TEST - --enable-fts5 --enable-json1 - } - "Fast-One" { - -O6 - -DSQLITE_ENABLE_FTS4=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_RBU - -DSQLITE_MAX_ATTACHED=125 - -DLONGDOUBLE_TYPE=double - --enable-session - } - "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_ENABLE_HIDDEN_COLUMNS - -DSQLITE_TEMP_STORE=3 - --enable-json1 - } - "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 - --enable-json1 --enable-fts5 --enable-session - } - "Locking-Style" { - -O2 - -DSQLITE_ENABLE_LOCKING_STYLE=1 - } - "Apple" { - -Os - -DHAVE_GMTIME_R=1 - -DHAVE_ISNAN=1 - -DHAVE_LOCALTIME_R=1 - -DHAVE_PREAD=1 - -DHAVE_PWRITE=1 - -DHAVE_USLEEP=1 - -DHAVE_USLEEP=1 - -DHAVE_UTIME=1 - -DSQLITE_DEFAULT_CACHE_SIZE=1000 - -DSQLITE_DEFAULT_CKPTFULLFSYNC=1 - -DSQLITE_DEFAULT_MEMSTATUS=1 - -DSQLITE_DEFAULT_PAGE_SIZE=1024 - -DSQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS=1 - -DSQLITE_ENABLE_API_ARMOR=1 - -DSQLITE_ENABLE_AUTO_PROFILE=1 - -DSQLITE_ENABLE_FLOCKTIMEOUT=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 - -DSQLITE_ENABLE_FTS3_TOKENIZER=1 - if:os=="Darwin" -DSQLITE_ENABLE_LOCKING_STYLE=1 - -DSQLITE_ENABLE_PERSIST_WAL=1 - -DSQLITE_ENABLE_PURGEABLE_PCACHE=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_SNAPSHOT=1 - # -DSQLITE_ENABLE_SQLLOG=1 - -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 - -DSQLITE_MAX_LENGTH=2147483645 - -DSQLITE_MAX_VARIABLE_NUMBER=500000 - # -DSQLITE_MEMDEBUG=1 - -DSQLITE_NO_SYNC=1 - -DSQLITE_OMIT_AUTORESET=1 - -DSQLITE_OMIT_LOAD_EXTENSION=1 - -DSQLITE_PREFER_PROXY_LOCKING=1 - -DSQLITE_SERIES_CONSTRAINT_VERIFY=1 - -DSQLITE_THREADSAFE=2 - -DSQLITE_USE_URI=1 - -DSQLITE_WRITE_WALFRAME_PREBUFFERED=1 - -DUSE_GUARDED_FD=1 - -DUSE_PREAD=1 - --enable-json1 --enable-fts5 - } - "Extra-Robustness" { - -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 - -DSQLITE_MAX_ATTACHED=62 - } - "Devkit" { - -DSQLITE_DEFAULT_FILE_FORMAT=4 - -DSQLITE_MAX_ATTACHED=30 - -DSQLITE_ENABLE_COLUMN_METADATA - -DSQLITE_ENABLE_FTS4 - -DSQLITE_ENABLE_FTS5 - -DSQLITE_ENABLE_FTS4_PARENTHESIS - -DSQLITE_DISABLE_FTS4_DEFERRED - -DSQLITE_ENABLE_RTREE - --enable-json1 --enable-fts5 - } - "No-lookaside" { - -DSQLITE_TEST_REALLOC_STRESS=1 - -DSQLITE_OMIT_LOOKASIDE=1 - -DHAVE_USLEEP=1 - } - "Valgrind" { - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_FTS4 - -DSQLITE_ENABLE_RTREE - -DSQLITE_ENABLE_HIDDEN_COLUMNS - -DCONFIG_SLOWDOWN_FACTOR=8.0 - --enable-json1 - } - - # The next group of configurations are used only by the - # Failure-Detection platform. They are all the same, but we need - # different names for them all so that they results appear in separate - # subdirectories. - # - Fail0 {-O0} - Fail2 {-O0} - Fail3 {-O0} - Fail4 {-O0} - FuzzFail1 {-O0} - FuzzFail2 {-O0} -}] - -array set ::Platforms [strip_comments { - Linux-x86_64 { - "Check-Symbols" checksymbols - "Fast-One" "fuzztest test" - "Debug-One" "mptest test" - "Have-Not" test - "Secure-Delete" test - "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" - "User-Auth" tcltest - "Update-Delete-Limit" test - "Extra-Robustness" test - "Device-Two" "threadtest test" - "No-lookaside" test - "Devkit" test - "Apple" test - "Sanitize" {QUICKTEST_OMIT=func4.test,nan.test test} - "Device-One" fulltest - "Default" "threadtest fulltest" - "Valgrind" valgrindtest - } - Linux-i686 { - "Devkit" test - "Have-Not" test - "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" - "Device-One" test - "Device-Two" test - "Default" "threadtest fulltest" - } - Darwin-i386 { - "Locking-Style" "mptest test" - "Have-Not" test - "Apple" "threadtest fulltest" - } - Darwin-x86_64 { - "Locking-Style" "mptest test" - "Have-Not" test - "Apple" "threadtest fulltest" - } - "Windows NT-intel" { - "Stdcall" test - "Have-Not" test - "Default" "mptest fulltestonly" - } - "Windows NT-amd64" { - "Stdcall" test - "Have-Not" test - "Default" "mptest fulltestonly" - } - - # The Failure-Detection platform runs various tests that deliberately - # fail. This is used as a test of this script to verify that this script - # correctly identifies failures. - # - Failure-Detection { - Fail0 "TEST_FAILURE=0 test" - Sanitize "TEST_FAILURE=1 test" - Fail2 "TEST_FAILURE=2 valgrindtest" - Fail3 "TEST_FAILURE=3 valgrindtest" - Fail4 "TEST_FAILURE=4 test" - FuzzFail1 "TEST_FAILURE=5 test" - FuzzFail2 "TEST_FAILURE=5 valgrindtest" - } -}] - - -# End of configuration section. -######################################################################### -######################################################################### - -# Configuration verification: Check that each entry in the list of configs -# specified for each platforms exists. -# -foreach {key value} [array get ::Platforms] { - foreach {v t} $value { - if {0==[info exists ::Configs($v)]} { - puts stderr "No such configuration: \"$v\"" - exit -1 - } - } -} - -# Output log. Disabled for slave interpreters. -# -if {[lindex $argv end]!="--slave"} { - set LOG [open releasetest-out.txt w] - proc PUTS {txt} { - puts $txt - puts $::LOG $txt - flush $::LOG - } - proc PUTSNNL {txt} { - puts -nonewline $txt - puts -nonewline $::LOG $txt - flush $::LOG - } - proc PUTSERR {txt} { - puts stderr $txt - puts $::LOG $txt - flush $::LOG - } - puts $LOG "$argv0 $argv" - set tm0 [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S} -gmt 1] - puts $LOG "start-time: $tm0 UTC" -} else { - proc PUTS {txt} { - puts $txt - } - proc PUTSNNL {txt} { - puts -nonewline $txt - } - proc PUTSERR {txt} { - puts stderr $txt - } -} - -# Open the file $logfile and look for a report on the number of errors -# and the number of test cases run. Add these values to the global -# $::NERRCASE and $::NTESTCASE variables. -# -# If any errors occur, then write into $errmsgVar the text of an appropriate -# one-line error message to show on the output. -# -proc count_tests_and_errors {logfile rcVar errmsgVar} { - if {$::DRYRUN} return - upvar 1 $rcVar rc $errmsgVar errmsg - set fd [open $logfile rb] - set seen 0 - while {![eof $fd]} { - set line [gets $fd] - if {[regexp {(\d+) errors out of (\d+) tests} $line all nerr ntest]} { - incr ::NERRCASE $nerr - incr ::NTESTCASE $ntest - set seen 1 - if {$nerr>0} { - set rc 1 - set errmsg $line - } - } - if {[regexp {runtime error: +(.*)} $line all msg]} { - # skip over "value is outside range" errors - if {[regexp {value .* is outside the range of representable} $line]} { - # noop - } elseif {[regexp {overflow: .* cannot be represented} $line]} { - # noop - } else { - incr ::NERRCASE - if {$rc==0} { - set rc 1 - set errmsg $msg - } - } - } - if {[regexp {fatal error +(.*)} $line all msg]} { - incr ::NERRCASE - if {$rc==0} { - set rc 1 - set errmsg $msg - } - } - if {[regexp {ERROR SUMMARY: (\d+) errors.*} $line all cnt] && $cnt>0} { - incr ::NERRCASE - if {$rc==0} { - set rc 1 - set errmsg $all - } - } - if {[regexp {^VERSION: 3\.\d+.\d+} $line]} { - set v [string range $line 9 end] - if {$::SQLITE_VERSION eq ""} { - set ::SQLITE_VERSION $v - } elseif {$::SQLITE_VERSION ne $v} { - set rc 1 - set errmsg "version conflict: {$::SQLITE_VERSION} vs. {$v}" - } - } - } - close $fd - if {$::BUILDONLY} { - incr ::NTESTCASE - if {$rc!=0} { - set errmsg "Build failed" - } - } elseif {!$seen} { - set rc 1 - set errmsg "Test did not complete" - if {[file readable core]} { - append errmsg " - core file exists" - } - } -} - -#-------------------------------------------------------------------------- -# This command is invoked as the [main] routine for scripts run with the -# "--slave" option. -# -# For each test (i.e. "configure && make test" execution), the master -# process spawns a process with the --slave option. It writes two lines -# to the slaves stdin. The first contains a single boolean value - the -# value of ::TRACE to use in the slave script. The second line contains a -# list in the same format as each element of the list passed to the -# [run_all_test_suites] command in the master process. -# -# The slave then runs the "configure && make test" commands specified. It -# exits successfully if the tests passes, or with a non-zero error code -# otherwise. -# -proc run_slave_test {} { - # Read global vars configuration from stdin. - set V [gets stdin] - foreach {::TRACE ::MSVC ::DRYRUN ::KEEPFILES} $V {} - - # Read the test-suite configuration from stdin. - set T [gets stdin] - foreach {title dir configOpts testtarget makeOpts cflags opts} $T {} - - # Create and switch to the test directory. - set normaldir [file normalize $dir] - set ::env(SQLITE_TMPDIR) $normaldir - trace_cmd file mkdir $dir - trace_cmd cd $dir - catch {file delete core} - catch {file delete test.log} - - # Run the "./configure && make" commands. - set rc 0 - set rc [catch [configureCommand $configOpts]] - if {!$rc} { - if {[info exists ::env(TCLSH_CMD)]} { - set savedEnv(TCLSH_CMD) $::env(TCLSH_CMD) - } else { - unset -nocomplain savedEnv(TCLSH_CMD) - } - set ::env(TCLSH_CMD) [file nativename [info nameofexecutable]] - - # Create a file called "makecommand.sh" containing the text of - # the make command line. - catch { - set cmd [makeCommand $testtarget $makeOpts $cflags $opts] - set fd [open makecommand.sh w] - foreach e $cmd { - if {[string first " " $e]>=0} { - puts -nonewline $fd "\"$e\"" - } else { - puts -nonewline $fd $e - } - puts -nonewline $fd " " - } - puts $fd "" - close $fd - } msg - - # Run the make command. - set rc [catch {trace_cmd exec {*}$cmd >>& test.log} msg] - if {[info exists savedEnv(TCLSH_CMD)]} { - set ::env(TCLSH_CMD) $savedEnv(TCLSH_CMD) - } else { - unset -nocomplain ::env(TCLSH_CMD) - } - } - - # Clean up lots of extra files if --keep was not specified. - if {$::KEEPFILES==0} { cleanup $normaldir } - - # Exis successfully if the test passed, or with a non-zero error code - # otherwise. - exit $rc -} - -# This command is invoked in the master process each time a slave -# file-descriptor is readable. -# -proc slave_fileevent {fd T tm1} { - global G - foreach {title dir configOpts testtarget makeOpts cflags opts} $T {} - - if {[eof $fd]} { - fconfigure $fd -blocking 1 - set rc [catch { close $fd }] - - set errmsg {} - set logfile [file join $dir test.log] - if {[file exists $logfile]} { - count_tests_and_errors [file join $dir test.log] rc errmsg - } elseif {$rc==0 && !$::DRYRUN} { - set rc 1 - set errmsg "no test.log file..." - } - - if {!$::TRACE} { - set tm2 [clock seconds] - set hours [expr {($tm2-$tm1)/3600}] - set minutes [expr {(($tm2-$tm1)/60)%60}] - set seconds [expr {($tm2-$tm1)%60}] - set tm [format (%02d:%02d:%02d) $hours $minutes $seconds] - - if {$rc} { - set status FAIL - incr ::NERR - } else { - set status Ok - } - - set n [string length $title] - if {$::PROGRESS_MSGS} { - PUTS "finished: ${title}[string repeat . [expr {53-$n}]] $status $tm" - } else { - PUTS "${title}[string repeat . [expr {63-$n}]] $status $tm" - } - if {$errmsg!=""} {PUTS " $errmsg"} - flush stdout - } - - incr G(nJob) -1 - } else { - set line [gets $fd] - if {[string trim $line] != ""} { - puts "Trace : $title - \"$line\"" - } - } -} - -#-------------------------------------------------------------------------- -# The only argument passed to this function is a list of test-suites to -# run. Each "test-suite" is itself a list consisting of the following -# elements: -# -# * Test title (for display). -# * The name of the directory to run the test in. -# * The argument for [configureCommand] -# * The first argument for [makeCommand] -# * The second argument for [makeCommand] -# * The third argument for [makeCommand] -# -proc run_all_test_suites {alltests} { - global G - set tests $alltests - - set G(nJob) 0 - - while {[llength $tests]>0 || $G(nJob)>0} { - if {$G(nJob)>=$::JOBS || [llength $tests]==0} { - vwait G(nJob) - } - - if {[llength $tests]>0} { - set T [lindex $tests 0] - set tests [lrange $tests 1 end] - foreach {title dir configOpts testtarget makeOpts cflags opts} $T {} - if {$::PROGRESS_MSGS && !$::TRACE} { - set n [string length $title] - PUTS "starting: ${title} at [now]" - flush stdout - } - - # Run the job. - # - set tm1 [clock seconds] - incr G(nJob) - set script [file normalize [info script]] - set fd [open "|[info nameofexecutable] $script --slave" r+] - fconfigure $fd -blocking 0 - fileevent $fd readable [list slave_fileevent $fd $T $tm1] - puts $fd [list $::TRACE $::MSVC $::DRYRUN $::KEEPFILES] - puts $fd [list {*}$T] - flush $fd - } - } -} - -proc add_test_suite {listvar name testtarget config} { - upvar $listvar alltests - - # 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 makeOpts "" - set cflags [expr {$::MSVC ? "-Zi" : "-g"}] - set opts "" - set title ${name}($testtarget) - set configOpts $::WITHTCL - set skip 0 - - regsub -all {#[^\n]*\n} $config \n config - foreach arg $config { - if {$skip} { - set skip 0 - continue - } - if {[regexp {^-[UD]} $arg]} { - lappend opts $arg - } elseif {[regexp {^[A-Z]+=} $arg]} { - lappend testtarget $arg - } elseif {[regexp {^if:([a-z]+)(.*)} $arg all key tail]} { - # Arguments of the form 'if:os=="Linux"' will cause the subsequent - # argument to be skipped if the $tcl_platform(os) is not "Linux", for - # example... - set skip [expr !(\$::tcl_platform($key)$tail)] - } elseif {[regexp {^--(enable|disable)-} $arg]} { - if {$::MSVC} { - if {$arg eq "--disable-amalgamation"} { - lappend makeOpts USE_AMALGAMATION=0 - continue - } - if {$arg eq "--disable-shared"} { - lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 - continue - } - if {$arg eq "--enable-fts5"} { - lappend opts -DSQLITE_ENABLE_FTS5 - continue - } - if {$arg eq "--enable-json1"} { - lappend opts -DSQLITE_ENABLE_JSON1 - continue - } - if {$arg eq "--enable-shared"} { - lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 - continue - } - } - lappend configOpts $arg - } else { - if {$::MSVC} { - if {$arg eq "-g"} { - lappend cflags -Zi - continue - } - if {[regexp -- {^-O(\d+)$} $arg all level]} then { - lappend makeOpts OPTIMIZATIONS=$level - continue - } - } - lappend cflags $arg - } - } - - # Disable sync to make testing faster. - # - lappend opts -DSQLITE_NO_SYNC=1 - - # Some configurations already set HAVE_USLEEP; in that case, skip it. - # - if {[lsearch -regexp $opts {^-DHAVE_USLEEP(?:=|$)}]==-1} { - lappend opts -DHAVE_USLEEP=1 - } - - # Add the define for this platform. - # - if {$::tcl_platform(platform)=="windows"} { - lappend opts -DSQLITE_OS_WIN=1 - } else { - lappend opts -DSQLITE_OS_UNIX=1 - } - - # Set the sub-directory to use. - # - set dir [string tolower [string map {- _ " " _} $name]] - - # Join option lists into strings, using space as delimiter. - # - set makeOpts [join $makeOpts " "] - set cflags [join $cflags " "] - set opts [join $opts " "] - - lappend alltests [list \ - $title $dir $configOpts $testtarget $makeOpts $cflags $opts] -} - -# The following procedure returns the "configure" command to be exectued for -# the current platform, which may be Windows (via MinGW, etc). -# -proc configureCommand {opts} { - if {$::MSVC} return [list]; # This is not needed for MSVC. - set result [list trace_cmd exec] - if {$::tcl_platform(platform)=="windows"} { - lappend result sh - } - lappend result $::SRCDIR/configure --enable-load-extension - foreach x $opts {lappend result $x} - lappend result >& test.log -} - -# The following procedure returns the "make" command to be executed for the -# specified targets, compiler flags, and options. -# -proc makeCommand { targets makeOpts cflags opts } { - set result [list] - if {$::MSVC} { - set nmakeDir [file nativename $::SRCDIR] - set nmakeFile [file nativename [file join $nmakeDir Makefile.msc]] - lappend result nmake /f $nmakeFile TOP=$nmakeDir - set tclDir [file nativename [file normalize \ - [file dirname [file dirname [info nameofexecutable]]]]] - lappend result "TCLDIR=$tclDir" - if {[regexp {USE_STDCALL=1} $cflags]} { - lappend result USE_STDCALL=1 - } - } else { - lappend result make - } - foreach makeOpt $makeOpts { - lappend result $makeOpt - } - lappend result clean - foreach target $targets { - lappend result $target - } - lappend result CFLAGS=$cflags OPTS=$opts -} - -# The following procedure prints its arguments if ::TRACE is true. -# And it executes the command of its arguments in the calling context -# if ::DRYRUN is false. -# -proc trace_cmd {args} { - if {$::TRACE} { - PUTS $args - } - set res "" - if {!$::DRYRUN} { - set res [uplevel 1 $args] - } - return $res -} - - -# 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 ::SRCDIR [file normalize [file dirname [file dirname $::argv0]]] - set ::QUICK 0 - set ::MSVC 0 - set ::BUILDONLY 0 - set ::DRYRUN 0 - set ::TRACE 0 - set ::JOBS 1 - set ::PROGRESS_MSGS 0 - set ::WITHTCL {} - set ::FORCE 0 - set ::KEEPFILES 0 ;# Keep extra files after test run - set config {} - set platform $::tcl_platform(os)-$::tcl_platform(machine) - - for {set i 0} {$i < [llength $argv]} {incr i} { - set x [lindex $argv $i] - if {[regexp {^--[a-z]} $x]} {set x [string range $x 1 end]} - switch -glob -- $x { - -slave { - run_slave_test - exit - } - - # Undocumented legacy option: --srcdir DIRECTORY - # - # DIRECTORY is the root of the SQLite checkout. This sets the - # SRCDIR global variable. But that variable is already set - # automatically so there really is no reason to have this option. - # - -srcdir { - incr i - set ::SRCDIR [file normalize [lindex $argv $i]] - } - - -platform { - incr i - set platform [lindex $argv $i] - } - - -jobs { - incr i - set ::JOBS [lindex $argv $i] - } - - -progress { - set ::PROGRESS_MSGS 1 - } - - -quick { - set ::QUICK 1 - } - -veryquick { - set ::QUICK 2 - } - - -config { - incr i - set config [lindex $argv $i] - } - - -msvc { - set ::MSVC 1 - } - - -buildonly { - set ::BUILDONLY 1 - } - - -dryrun { - set ::DRYRUN 1 - } - - -force - - -f { - set ::FORCE 1 - } - - -trace { - set ::TRACE 1 - } - - -info { - PUTS "Command-line Options:" - PUTS " --srcdir $::SRCDIR" - PUTS " --platform [list $platform]" - PUTS " --config [list $config]" - if {$::QUICK} { - if {$::QUICK==1} {PUTS " --quick"} - if {$::QUICK==2} {PUTS " --veryquick"} - } - if {$::MSVC} {PUTS " --msvc"} - if {$::BUILDONLY} {PUTS " --buildonly"} - if {$::DRYRUN} {PUTS " --dryrun"} - if {$::TRACE} {PUTS " --trace"} - PUTS "\nAvailable --platform options:" - foreach y [lsort [array names ::Platforms]] { - PUTS " [list $y]" - } - PUTS "\nAvailable --config options:" - foreach y [lsort [array names ::Configs]] { - PUTS " [list $y]" - } - exit - } - - -g { - lappend ::EXTRACONFIG [lindex $argv $i] - } - - -keep { - set ::KEEPFILES 1 - } - - -with-tcl=* { - set ::WITHTCL -$x - } - - -D* - - -O* - - -enable-* - - -disable-* - - *=* { - lappend ::EXTRACONFIG [lindex $argv $i] - } - - default { - PUTSERR "" - PUTSERR [string trim $::USAGE_MESSAGE] - exit -1 - } - } - } - - if {0==[info exists ::Platforms($platform)]} { - PUTS "Unknown platform: $platform" - PUTSNNL "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 - } - - if {$config!=""} { - if {[llength $config]==1} {lappend config fulltest} - set ::CONFIGLIST $config - } else { - if {$::JOBS>1} { - set ::CONFIGLIST {} - foreach {target zConfig} [lreverse $::Platforms($platform)] { - append ::CONFIGLIST [format " %-25s %s\n" \ - [list $zConfig] [list $target]] - } - } else { - set ::CONFIGLIST $::Platforms($platform) - } - } - PUTS "Running the following test configurations for $platform:" - PUTS " [string trim $::CONFIGLIST]" - PUTSNNL "Flags:" - if {$::PROGRESS_MSGS} {PUTSNNL " --progress"} - if {$::DRYRUN} {PUTSNNL " --dryrun"} - if {$::BUILDONLY} {PUTSNNL " --buildonly"} - if {$::MSVC} {PUTSNNL " --msvc"} - switch -- $::QUICK { - 1 {PUTSNNL " --quick"} - 2 {PUTSNNL " --veryquick"} - } - if {$::JOBS>1} {PUTSNNL " --jobs $::JOBS"} - PUTS "" -} - -# Check to see if there are uncommitted changes in the SQLite source -# checkout. Exit if there are. Except: Do nothing if the --force -# flag is used. Also, ignore this test if the fossil binary is -# unavailable, or if the source tree is not a valid fossil checkout. -# -proc check_uncommitted {} { - if {$::FORCE} return - set pwd [pwd] - cd $::SRCDIR - if {[catch {exec fossil changes} res]==0 && [string trim $res]!=""} { - puts "ERROR: The check-out contains uncommitted changes:" - puts $res - puts "Use the -f or --force options to override" - exit 1 - } - cd $pwd -} - -# A test run has just finished in directory $dir. This command deletes all -# non-essential files from the directory. Specifically, everything except -# -# * The "testfixture" and "sqlite3" binaries, -# * The "test-out.log" and "test.log" log files. -# -proc cleanup {dir} { - set K(testfixture) 1 - set K(testfixture.exe) 1 - set K(sqlite3) 1 - set K(sqlite3.exe) 1 - set K(test-out.txt) 1 - set K(test.log) 1 - - foreach f [glob -nocomplain [file join $dir *]] { - set tail [file tail $f] - if {[info exists K($tail)]==0} { - file delete -force $f - } - } -} - - -# Main routine. -# -proc main {argv} { - - # Process any command line options. - set ::EXTRACONFIG {} - process_options $argv - if {!$::DRYRUN} check_uncommitted - PUTS [string repeat * 79] - - set ::NERR 0 - set ::NTEST 0 - set ::NTESTCASE 0 - set ::NERRCASE 0 - set ::SQLITE_VERSION {} - set STARTTIME [clock seconds] - foreach {zConfig target} $::CONFIGLIST { - if {$::MSVC && ($zConfig eq "Sanitize" || "checksymbols" in $target - || "valgrindtest" in $target)} { - PUTS "Skipping $zConfig / $target for MSVC..." - continue - } - if {$target ne "checksymbols"} { - switch -- $::QUICK { - 1 {set target quicktest} - 2 {set target smoketest} - } - if {$::BUILDONLY} { - set target testfixture - if {$::tcl_platform(platform)=="windows"} { - append target .exe - } - } - } - set config_options [concat $::Configs($zConfig) $::EXTRACONFIG] - - incr NTEST - add_test_suite all $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. - if {$target!="checksymbols" && $target!="valgrindtest" - && $target!="fuzzoomtest" && !$::BUILDONLY && $::QUICK<2} { - set debug_idx [lsearch -glob $config_options -DSQLITE_DEBUG*] - set xtarget $target - regsub -all {fulltest[a-z]*} $xtarget test xtarget - regsub -all {fuzzoomtest} $xtarget fuzztest xtarget - if {$debug_idx < 0} { - incr NTEST - append config_options " -DSQLITE_DEBUG=1 -DSQLITE_EXTRA_IFNULLROW=1" - add_test_suite all "${zConfig}_debug" $xtarget $config_options - } else { - incr NTEST - regsub { *-DSQLITE_MEMDEBUG[^ ]* *} $config_options { } config_options - regsub { *-DSQLITE_DEBUG[^ ]* *} $config_options { } config_options - add_test_suite all "${zConfig}_ndebug" $xtarget $config_options - } - } - } - - run_all_test_suites $all - - set elapsetime [expr {[clock seconds]-$STARTTIME}] - set hr [expr {$elapsetime/3600}] - set min [expr {($elapsetime/60)%60}] - set sec [expr {$elapsetime%60}] - set etime [format (%02d:%02d:%02d) $hr $min $sec] - if {$::JOBS>1} {append etime " $::JOBS cores"} - if {[catch {exec hostname} HNAME]==0} {append etime " on $HNAME"} - PUTS [string repeat * 79] - incr ::NERRCASE $::NERR - PUTS "$::NERRCASE failures out of $::NTESTCASE tests in $etime" - if {$::SQLITE_VERSION ne ""} { - PUTS "SQLite $::SQLITE_VERSION" - } -} - -main $argv diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl index 00be20228..45966fd23 100644 --- a/test/releasetest_data.tcl +++ b/test/releasetest_data.tcl @@ -22,14 +22,6 @@ # "fulltest"). The program may be invoked as follows: # set USAGE { -$argv0 platforms - List available platforms. - -$argv0 tests ?-nodebug? PLATFORM - List tests in a specified platform. If the -nodebug switch is - specified, synthetic debug/ndebug configurations are omitted. Each - test is a combination of a configuration and a makefile target. - $argv0 script ?-msvc? CONFIGURATION TARGET Given a configuration and make target, return a bash (or, if -msvc is specified, batch) script to execute the test. The first argument @@ -37,6 +29,14 @@ $argv0 script ?-msvc? CONFIGURATION TARGET $argv0 configurations List available configurations. + +$argv0 platforms + List available platforms. + +$argv0 tests ?-nodebug? PLATFORM + List tests in a specified platform. If the -nodebug switch is + specified, synthetic debug/ndebug configurations are omitted. Each + test is a combination of a configuration and a makefile target. } # Omit comments (text between # and \n) in a long multi-line string. @@ -149,6 +149,7 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_RBU -DSQLITE_MAX_ATTACHED=125 -DSQLITE_MAX_MMAP_SIZE=12884901888 + -DSQLITE_ENABLE_SORTER_MMAP=1 -DLONGDOUBLE_TYPE=double --enable-session } @@ -288,56 +289,61 @@ if {$tcl_platform(os)=="Darwin"} { array set ::Platforms [strip_comments { Linux-x86_64 { - "Check-Symbols*" checksymbols - "Fast-One" "QUICKTEST_INCLUDE=rbu.test fuzztest test" - "Debug-One" "mptest test" - "Debug-Two" "test" - "Have-Not" test - "Secure-Delete" test - "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" - "User-Auth" tcltest - "Update-Delete-Limit" test - "Extra-Robustness" test - "Device-Two" "threadtest test" - "No-lookaside" test - "Devkit" test - "Apple" test - "Sanitize" test - "Device-One" fulltest - "Default" "threadtest fulltest" - "Valgrind*" valgrindtest + "Check-Symbols*" "" checksymbols + "Fast-One" QUICKTEST_INCLUDE=rbu.test "fuzztest test" + "Debug-One" "" "mptest test" + "Debug-Two" "" test + "Have-Not" "" test + "Secure-Delete" "" test + "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test + "User-Auth" "" tcltest + "Update-Delete-Limit" "" test + "Extra-Robustness" "" test + "Device-Two" "" "threadtest test" + "No-lookaside" "" test + "Devkit" "" test + "Apple" "" test + "Sanitize*" "" test + "Device-One" "" alltest + "Default" "" "threadtest fuzztest alltest" + "Valgrind*" "" valgrindtest } Linux-i686 { - "Devkit" test - "Have-Not" test - "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" - "Device-One" test - "Device-Two" test - "Default" "threadtest fulltest" + "Devkit" "" test + "Have-Not" "" test + "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test + "Device-One" "" test + "Device-Two" "" test + "Default" "" "threadtest fuzztest alltest" } Darwin-i386 { - "Locking-Style" "mptest test" - "Have-Not" test - "Apple" "threadtest fulltest" + "Locking-Style" "" "mptest test" + "Have-Not" "" test + "Apple" "" "threadtest fuzztest alltest" } Darwin-x86_64 { - "Locking-Style" "mptest test" - "Have-Not" test - "Apple" "threadtest fulltest" + "Locking-Style" "" "mptest test" + "Have-Not" "" test + "Apple" "" "threadtest fuzztest alltest" + } + Darwin-arm64 { + "Locking-Style" "" "mptest test" + "Have-Not" "" test + "Apple" "" "threadtest fuzztest alltest" } "Windows NT-intel" { - "Stdcall" test - "Have-Not" test - "Windows-Memdebug*" test - "Windows-Win32Heap*" test - "Default" "mptest fulltestonly" + "Stdcall" "" test + "Have-Not" "" test + "Windows-Memdebug*" "" test + "Windows-Win32Heap*" "" test + "Default" "" "mptest fulltestonly" } "Windows NT-amd64" { - "Stdcall" test - "Have-Not" test - "Windows-Memdebug*" test - "Windows-Win32Heap*" test - "Default" "mptest fulltestonly" + "Stdcall" "" test + "Have-Not" "" test + "Windows-Memdebug*" "" test + "Windows-Win32Heap*" "" test + "Default" "" "mptest fulltestonly" } # The Failure-Detection platform runs various tests that deliberately @@ -345,13 +351,13 @@ array set ::Platforms [strip_comments { # correctly identifies failures. # Failure-Detection { - Fail0* "TEST_FAILURE=0 test" - Sanitize* "TEST_FAILURE=1 test" - Fail2* "TEST_FAILURE=2 valgrindtest" - Fail3* "TEST_FAILURE=3 valgrindtest" - Fail4* "TEST_FAILURE=4 test" - FuzzFail1* "TEST_FAILURE=5 test" - FuzzFail2* "TEST_FAILURE=5 valgrindtest" + Fail0* "TEST_FAILURE=0" test + Sanitize* "TEST_FAILURE=1" test + Fail2* "TEST_FAILURE=2" valgrindtest + Fail3* "TEST_FAILURE=3" valgrindtest + Fail4* "TEST_FAILURE=4" test + FuzzFail1* "TEST_FAILURE=5" test + FuzzFail2* "TEST_FAILURE=5" valgrindtest } }] @@ -367,7 +373,7 @@ array set ::Platforms [strip_comments { # specified for each platforms exists. # foreach {key value} [array get ::Platforms] { - foreach {v t} $value { + foreach {v vars t} $value { if {[string range $v end end]=="*"} { set v [string range $v 0 end-1] } @@ -438,6 +444,7 @@ proc main_script {args} { lappend opts -DSQLITE_EXTRA_IFNULLROW set config [string range $config 0 end-6] } + regexp {^(.*)-[0-9]+} $config -> config # Ensure that the named configuration exists. # @@ -473,6 +480,7 @@ proc main_script {args} { if {$bRemoveDebug} { if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1" || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1" + || $param=="--enable-debug" } { continue } @@ -585,32 +593,57 @@ proc main_tests {args} { exit 1 } - foreach {config target} $::Platforms($p) { - set bNosynthetic 0 + set lTest [list] + + foreach {config vars target} $::Platforms($p) { if {[string range $config end end]=="*"} { - set bNosynthetic 1 set config [string range $config 0 end-1] - } - puts "$config \"$target\"" - if {$bNodebug==0 && $bNosynthetic==0} { - set iHas [string first SQLITE_DEBUG $::Configs($config)] - set dtarget [list] - set iQTI [lsearch -glob $target QUICKTEST_*] - if {$iQTI>=0} { - lappend dtarget [lindex $target $iQTI] + } elseif {$bNodebug==0} { + set dtarget test + if {[lsearch $target fuzztest]<0 && [lsearch $target test]<0} { + set dtarget tcltest } - if {[lsearch $target tcltest]>=0} { - lappend dtarget tcltest + if {$vars!=""} { set dtarget "$vars $dtarget" } + + if {[string first SQLITE_DEBUG $::Configs($config)]>=0 + || [string first --enable-debug $::Configs($config)]>=0 + } { + lappend lTest "$config-ndebug \"$dtarget\"" } else { - lappend dtarget test + lappend lTest "$config-debug \"$dtarget\"" } - if {$iHas>=0} { - puts "$config-ndebug \"$dtarget\"" - } else { - puts "$config-debug \"$dtarget\"" + } + + if {[llength $target]==1 && ([string match "*TEST_FAILURE*" $vars] || ( + [lsearch $target "valgrindtest"]<0 + && [lsearch $target "alltest"]<0 + && [lsearch $target "fulltestonly"]<0 + && ![string match Sanitize* $config] + ))} { + if {$vars!=""} { set target "$vars $target" } + lappend lTest "$config \"$target\"" + } else { + set idir -1 + foreach t $target { + if {$t=="valgrindtest" || $t=="alltest" || $t=="fulltestonly" + || [string match Sanitize* $config] + } { + if {$vars!=""} { set t "$vars $t" } + for {set ii 1} {$ii<=4} {incr ii} { + lappend lTest "$config-[incr idir] \"TCLTEST_PART=$ii/4 $t\"" + } + } else { + if {$vars!=""} { set t "$vars $t" } + lappend lTest "$config-[incr idir] \"$t\"" + } } } } + + foreach l $lTest { + puts $l + } + } if {[llength $argv]==0} { usage } diff --git a/test/returning1.test b/test/returning1.test index dc4655231..c64e72f72 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -318,4 +318,19 @@ do_test 12.4 { lsort [array names cname] } {{"x"+"y"} *} +ifcapable rtree { +#------------------------------------------------------------------------- +# Based on dbsqlfuzz find crash-ffbba524cac354b2a61bfd677cec9d2a4333f49a +reset_db +do_execsql_test 13.0 { + CREATE VIRTUAL TABLE t1 USING rtree(a, b, c); + CREATE TABLE t2(x); +} + +do_execsql_test 13.1 { + INSERT INTO t1(a,b,c) VALUES(1,2,3) + RETURNING (SELECT b FROM t2); +} {{}} +} ;# end ifcapable rtree + finish_test diff --git a/test/returningfault.test b/test/returningfault.test new file mode 100644 index 000000000..8bf6fbfe0 --- /dev/null +++ b/test/returningfault.test @@ -0,0 +1,36 @@ +# 2022 January 5 +# +# 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 + + +do_execsql_test 1.0 { + CREATE TABLE t1 (b); +} {} +faultsim_save_and_close + +do_faultsim_test pagerfault-1 -faults oom-t* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + INSERT INTO t1(b) VALUES(65) RETURNING ( + SELECT * FROM sqlite_temp_schema + ) AS aaa; + } +} -test { + faultsim_test_result {1 {sub-select returns 5 columns - expected 1}} +} + + +finish_test diff --git a/test/rowid.test b/test/rowid.test index f4fa1ce8d..4327004d3 100644 --- a/test/rowid.test +++ b/test/rowid.test @@ -18,6 +18,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix rowid # Basic ROWID functionality tests. # @@ -786,5 +787,34 @@ do_execsql_test rowid-15.2 { ); } {1 {}} +#------------------------------------------------------------------------- +# Check that an unqualified "rowid" can be used in join queries so long +# as only one of the source objects has a rowid column. +# +reset_db +do_execsql_test 16.0 { + CREATE TABLE t1(x); + CREATE TABLE t2(y PRIMARY KEY) WITHOUT ROWID; + CREATE VIEW v1 AS SELECT x FROM t1; + CREATE TABLE t3(z); + + INSERT INTO t1(rowid, x) VALUES(1, 1); + INSERT INTO t2(y) VALUES(2); + INSERT INTO t3(rowid, z) VALUES(3, 3); +} + +do_execsql_test 16.1 { SELECT rowid FROM t1, t2; } {1} +do_execsql_test 16.2 { SELECT rowid FROM t1, v1; } {1} +do_execsql_test 16.3 { SELECT rowid FROM t3, v1; } {3} +do_execsql_test 16.4 { SELECT rowid FROM t3, (SELECT 123); } {3} + +do_execsql_test 16.5 { SELECT rowid FROM t2, t1; } {1} +do_execsql_test 16.6 { SELECT rowid FROM v1, t1; } {1} +do_execsql_test 16.7 { SELECT rowid FROM v1, t3; } {3} +do_execsql_test 16.8 { SELECT rowid FROM (SELECT 123), t3; } {3} + +do_catchsql_test 16.5 { SELECT rowid FROM t1, t3; } {1 {no such column: rowid}} + + finish_test diff --git a/test/rowvalueA.test b/test/rowvalueA.test new file mode 100644 index 000000000..dc5a7a014 --- /dev/null +++ b/test/rowvalueA.test @@ -0,0 +1,77 @@ +# 2021 July 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. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix rowvalueA + +do_execsql_test 1.0 { + SELECT (1, 2) IN ( (3, 4), (5, 6), (1, 3) ); +} {0} + +do_execsql_test 1.1 { + SELECT (1, 2) IN ( (3, 4), (5, 6), (1, 2) ); +} {1} + +do_execsql_test 1.2 { + SELECT (1, 2) IN ( (3, 2) ); +} {0} + +do_execsql_test 1.3 { + SELECT (1, 2) IN ( (1, 2) ); +} {1} + +do_execsql_test 1.4 { + SELECT (1, 2) IN ( ); +} {0} + +do_execsql_test 1.5 { + SELECT (1, 2) NOT IN ( ); +} {1} + +for {set ii 0} {$ii < 2000} {incr ii} { + lappend L "($ii, $ii)" +} + +do_execsql_test 1.6.1 " + SELECT (400,400) IN ( [join $L ,] ) +" 1 + +do_execsql_test 1.6.2 " + SELECT (1500,1500) IN ( [join $L ,] ) +" 1 + +do_execsql_test 1.6.2 " + SELECT (1500,1499) IN ( [join $L ,] ) +" 0 + +#------------------------------------------------------------------------- + +do_catchsql_test 2.0 { + SELECT (1, 2) IN ( (1, 2), (3, 4, 5), (5, 6) ) +} {1 {IN(...) element has 3 terms - expected 2}} + +do_catchsql_test 2.1 { + SELECT (1, 2) IN ( (1, 2), 4, (5, 6) ) +} {1 {IN(...) element has 1 term - expected 2}} + +do_catchsql_test 2.2 { + SELECT (1, 2, 3) IN ( (1, 2), (3, 4), (5, 6) ) +} {1 {IN(...) element has 2 terms - expected 3}} + +do_catchsql_test 2.3 { + SELECT 2 IN ( (1, 2), (3, 4), (5, 6) ) +} {1 {row value misused}} + +finish_test + diff --git a/test/rowvaluefault.test b/test/rowvaluefault.test index ac1b236ba..a06ef886e 100644 --- a/test/rowvaluefault.test +++ b/test/rowvaluefault.test @@ -68,4 +68,22 @@ do_faultsim_test 6 -faults oom* -body { faultsim_test_result {0 {2 3}} } +do_faultsim_test 7 -faults oom* -body { + execsql { + SELECT fou FROM xyz + WHERE (one, two, thr) IN ( ('a','b','c'), ('A','A','A'), (1,2,3) ); + } +} -test { + faultsim_test_result {0 1} +} + +do_faultsim_test 8 -faults oom* -body { + execsql { + SELECT fou FROM xyz + WHERE (two, one) IN ( ('a','b','c'), ('A','A','A'), (1,2,3) ); + } +} -test { + faultsim_test_result {1 {IN(...) element has 3 terms - expected 2}} +} + finish_test diff --git a/test/schema3.test b/test/schema3.test index ba7d745eb..39d463233 100644 --- a/test/schema3.test +++ b/test/schema3.test @@ -16,6 +16,12 @@ source $testdir/tester.tcl source $testdir/malloc_common.tcl source $testdir/lock_common.tcl +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + # This block tests that if one client modifies the database schema, a # second client updates its internal cache of the database schema before # executing any queries. Specifically, it does not return a "no such column" diff --git a/test/shell1.test b/test/shell1.test index 330276120..c4e2ceb88 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -18,6 +18,8 @@ # shell1-1.*: Basic command line option handling. # shell1-2.*: Basic "dot" command token parsing. # shell1-3.*: Basic test that "dot" command can be called. +# shell1-{4-8}.*: Test various "dot" commands's functionality. +# shell1-9.*: Basic test that "dot" commands and SQL intermix ok. # set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -51,7 +53,7 @@ do_test shell1-1.1.2 { # error on extra options do_test shell1-1.1.3 { catchcmd "test.db FOO test.db BAD" ".quit" -} {1 {Error: near "FOO": syntax error}} +} {1 {Error: in prepare, near "FOO": syntax error (1)}} # -help do_test shell1-1.2.1 { @@ -76,7 +78,7 @@ do_test shell1-1.3.2 { } {0 {}} do_test shell1-1.3.3 { catchcmd "-init FOO test.db BAD .quit" "" -} {1 {Error: near "BAD": syntax error}} +} {1 {Error: in prepare, near "BAD": syntax error (1)}} # -echo print commands before execution do_test shell1-1.4.1 { @@ -1224,4 +1226,22 @@ do_test shell1-8.4 { | 6683623321994527 | -47 | +------------------+-----+}} +#---------------------------------------------------------------------------- +# Test cases shell1-9.*: Basic test that "dot" commands and SQL intermix ok. +# +do_test shell1-9.1 { + catchcmd :memory: { +.mode csv +/* +x */ select 1,2; --x + -- .nada +; +.mode csv +--x +select 2,1; select 3,4; +} +} {0 {1,2 +2,1 +3,4}} + finish_test diff --git a/test/shell2.test b/test/shell2.test index 2de6bf751..6b4dff515 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -43,10 +43,10 @@ do_test shell2-1.1.1 { # Shell silently ignores extra parameters. # Ticket [f5cb008a65]. do_test shell2-1.2.1 { - set rc [catch { eval exec $CLI \":memory:\" \"select+3\" \"select+4\" } msg] - list $rc $msg + catchcmdex {:memory: "select+3" "select+4"} } {0 {3 -4}} +4 +}} # Test a problem reported on the mailing list. The shell was at one point # returning the generic SQLITE_ERROR message ("SQL error or missing database") @@ -63,7 +63,7 @@ do_test shell2-1.3 { UPDATE OR REPLACE t5 SET a = 4 WHERE a = 1; } -} {1 {Error: near line 9: too many levels of trigger recursion}} +} {1 {Error: near line 9: stepping, too many levels of trigger recursion (1)}} @@ -123,7 +123,7 @@ SELECT * FROM foo;} # NB. whitespace is important do_test shell2-1.4.5 { forcedelete foo.db - catchcmd "foo.db" {.echo ON + catchcmdex "foo.db" {.echo ON CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); CREATE TABLE foo2(b); @@ -155,7 +155,7 @@ SELECT * FROM foo2; # NB. whitespace is important do_test shell2-1.4.6 { forcedelete foo.db - catchcmd "foo.db" {.echo ON + catchcmdex "foo.db" {.echo ON .headers ON CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); diff --git a/test/shell3.test b/test/shell3.test index 63c30a268..243da976f 100644 --- a/test/shell3.test +++ b/test/shell3.test @@ -18,6 +18,7 @@ # # shell3-1.*: Basic tests for running SQL statments from command line. # shell3-2.*: Basic tests for running SQL file from command line. +# shell3-3.*: Basic tests for processing odd SQL constructs. # set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -26,6 +27,7 @@ db close forcedelete test.db test.db-journal test.db-wal sqlite3 db test.db + # There are inconsistencies in command-line argument quoting on Windows. # In particular, individual applications are responsible for command-line # parsing in Windows, not the shell. Depending on whether the sqlite3.exe @@ -66,7 +68,7 @@ do_test shell3-1.6 { } {0 {}} do_test shell3-1.7 { catchcmd "foo.db \"CREATE TABLE\"" -} {1 {Error: incomplete input}} +} {1 {Error: in prepare, incomplete input (1)}} #---------------------------------------------------------------------------- # shell3-2.*: Basic tests for running SQL file from command line. @@ -96,6 +98,41 @@ do_test shell3-2.6 { } {0 {}} do_test shell3-2.7 { catchcmd "foo.db" "CREATE TABLE" -} {1 {Error: near line 1: incomplete input}} +} {1 {Error: near line 1: in prepare, incomplete input (1)}} + + +#---------------------------------------------------------------------------- +# shell3-3.*: Basic tests for processing odd SQL constructs. +# + +# Run combinations of odd identifiers, comments, semicolon placement +do_test shell3-3.1 { + forcedelete foo.db + set rc [ catchcmd "foo.db" {CREATE TABLE t1(" +a--. +" --x +); CREATE TABLE t2("a[""b""]"); +.header on +INSERT INTO t1 VALUES (' +x''y'); +INSERT INTO t2 VALUES (' +/*. +.*/ x +''y'); +SELECT * from t1 limit 1; +SELECT * from t2 limit 1; +} ] + set fexist [file exist foo.db] + list $rc $fexist +} {{0 { +a--. + + +x'y +a["b"] + +/*. +.*/ x +'y}} 1} finish_test diff --git a/test/shell5.test b/test/shell5.test index 8ec9a632b..dc99a7ace 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -456,4 +456,19 @@ CREATE TABLE t7(a, b, c); db eval { SELECT * FROM t7 ORDER BY a } } {1 2 3 4 5 {} 6 7 8} +do_test shell5-4.3 { + forcedelete shell5.csv + set fd [open shell5.csv w] + puts $fd ",," + puts $fd "1,2,3" + close $fd + catchcmd test.db [string trim { +.mode csv +CREATE TABLE t8(a, b, c); +.import -skip 1 shell5.csv t8 +.nullvalue # + }] + db eval { SELECT * FROM t8 } +} {1 2 3} + finish_test diff --git a/test/shell8.test b/test/shell8.test index 3658a8ac5..ddb4a47b8 100644 --- a/test/shell8.test +++ b/test/shell8.test @@ -44,6 +44,10 @@ proc populate_dir {dirname spec} { } } +proc dir_content {dirname} { + lsort [glob -nocomplain $dirname/*] +} + proc dir_to_list {dirname {n -1}} { if {$n<0} {set n [llength [file split $dirname]]} @@ -170,8 +174,23 @@ foreach {tn tcl} { } } -finish_test - - +do_test 2.1.1 { + populate_dir ar2 { + file1 "abcd" + file2 "efgh" + junk1 "j1" + junk2 "j2" + dir1/file3 "ijkl" + } + populate_dir ar4 { + file2 "efgh" + } + catchcmd shell8.db {.ar -c} + catchcmd shell8.db {.ar -C ar2 -i .} + catchcmd shell8.db {.ar -r ./file2 ./dir1} + catchcmd shell8.db {.ar -g -r ./ju*2} + catchcmd shell8.db {.ar -C ar4 -x .} + regsub -all {ar4} [dir_content ar4] ar2 +} {ar2/file1 ar2/file2 ar2/junk1} finish_test diff --git a/test/shrink.test b/test/shrink.test index 7c9bed08b..bc4a707bf 100644 --- a/test/shrink.test +++ b/test/shrink.test @@ -24,7 +24,7 @@ do_test shrink-1.1 { CREATE TABLE t1(x,y); INSERT INTO t1 VALUES(randomblob(1000000),1); } - set ::baseline sqlite3_memory_used + set ::baseline [sqlite3_memory_used] # EVIDENCE-OF: R-58814-63508 The sqlite3_db_release_memory(D) interface # attempts to free as much heap memory as possible from database # connection D. diff --git a/test/skipscan2.test b/test/skipscan2.test index 47b2b3fff..aa870d456 100644 --- a/test/skipscan2.test +++ b/test/skipscan2.test @@ -157,7 +157,6 @@ do_execsql_test skipscan2-2.1 { CREATE INDEX peoplew_idx1 ON peoplew(role, height); INSERT INTO peoplew(name,role,height) SELECT name, role, height FROM people; - ALTER TABLE people RENAME TO old_people; SELECT name FROM peoplew WHERE height>=180 ORDER BY +name; } {David Jack Patrick Quiana Xavier} do_execsql_test skipscan2-2.2 { diff --git a/test/sorterref.test b/test/sorterref.test index 28445c6e7..adf1cf53b 100644 --- a/test/sorterref.test +++ b/test/sorterref.test @@ -14,6 +14,12 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix sorterref +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + do_execsql_test 1.0 { CREATE TABLE t1(a, b, c); INSERT INTO t1 VALUES(1, 2, 3); diff --git a/test/speedtest1.c b/test/speedtest1.c index 9e8c39608..83c503c8c 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -34,6 +34,7 @@ static const char zHelp[] = " --sqlonly No-op. Only show the SQL that would have been run.\n" " --shrink-memory Invoke sqlite3_db_release_memory() frequently.\n" " --size N Relative test size. Default=100\n" + " --strict Use STRICT table where appropriate\n" " --stats Show statistics at the end\n" " --temp N N from 0 to 9. 0: no temp table. 9: all temp tables\n" " --testset T Run test-set T (main, cte, rtree, orm, fp, debug)\n" @@ -947,7 +948,7 @@ void testset_main(void){ speedtest1_begin_test(210, "ALTER TABLE ADD COLUMN, and query"); - speedtest1_exec("ALTER TABLE z2 ADD COLUMN d DEFAULT 123"); + speedtest1_exec("ALTER TABLE z2 ADD COLUMN d INT DEFAULT 123"); speedtest1_exec("SELECT sum(d) FROM z2"); speedtest1_end_test(); @@ -2317,8 +2318,22 @@ int main(int argc, char **argv){ if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); g.nReserve = atoi(argv[++i]); }else if( strcmp(z,"without-rowid")==0 ){ - g.zWR = "WITHOUT ROWID"; + if( strstr(g.zWR,"WITHOUT")!=0 ){ + /* no-op */ + }else if( strstr(g.zWR,"STRICT")!=0 ){ + g.zWR = "WITHOUT ROWID,STRICT"; + }else{ + g.zWR = "WITHOUT ROWID"; + } g.zPK = "PRIMARY KEY"; + }else if( strcmp(z,"strict")==0 ){ + if( strstr(g.zWR,"STRICT")!=0 ){ + /* no-op */ + }else if( strstr(g.zWR,"WITHOUT")!=0 ){ + g.zWR = "WITHOUT ROWID,STRICT"; + }else{ + g.zWR = "STRICT"; + } }else if( strcmp(z, "help")==0 || strcmp(z,"?")==0 ){ printf(zHelp, argv[0]); exit(0); diff --git a/test/sqldiff1.test b/test/sqldiff1.test index ea4e1f999..4ea5efbbf 100644 --- a/test/sqldiff1.test +++ b/test/sqldiff1.test @@ -35,6 +35,10 @@ do_test sqldiff-1.0 { DELETE FROM x2.t2 WHERE a=48; INSERT INTO x2.t1(a,b) VALUES(1234,'hello'); INSERT INTO x2.t2(a,b) VALUES(50.5,'xyzzy'); + INSERT INTO x2.t2(a,b) VALUES(51.5,''); + INSERT INTO x2.t2(a,b) VALUES(52.5,''||X'0d0a'); + INSERT INTO x2.t2(a,b) VALUES(53.5,'one'||X'0a0d'); + INSERT INTO x2.t2(a,b) VALUES(54.5,'one'||X'0a'||'two'); CREATE TABLE x2.t3(a,b,c); INSERT INTO x2.t3 VALUES(111,222,333); CREATE TABLE main.t4(x,y,z); @@ -50,6 +54,11 @@ do_test sqldiff-1.1 { INSERT INTO t1(a,b) VALUES(1234,'hello'); DELETE FROM t2 WHERE a=48; INSERT INTO t2(a,b) VALUES(50.5,'xyzzy'); +INSERT INTO t2(a,b) VALUES(51.5,''); +INSERT INTO t2(a,b) VALUES(52.5,''||X'0d0a'); +INSERT INTO t2(a,b) VALUES(53.5,'one'||X'0a0d'); +INSERT INTO t2(a,b) VALUES(54.5,'one'||X'0a' +||'two'); CREATE TABLE t3(a,b,c); INSERT INTO t3(rowid,a,b,c) VALUES(1,111,222,333); DROP TABLE t4;} diff --git a/test/sqllimits1.test b/test/sqllimits1.test index 594c06157..6b7d7b609 100644 --- a/test/sqllimits1.test +++ b/test/sqllimits1.test @@ -293,8 +293,14 @@ do_test sqllimits1-5.9 { } {1 {string or blob too big}} do_test sqllimits1-5.10 { - set ::str [string repeat %J 2100] - catchsql { SELECT strftime($::str, '2003-10-31') } + # Prior to 3.37.0 strftime() allocated a large static buffer into + # which to format its output. Using that strategy, 2100 repeats was + # enough to exceed 100KiB and provoke the error. As of 3.37.0 strftime() + # uses the StrAccum functions, so it requires 12100 to fail. + # + # set ::str [string repeat %J 2100] + set ::str [string repeat %J 12100] + catchsql { SELECT length(strftime($::str, '2003-10-31')) } } {1 {string or blob too big}} do_test sqllimits1-5.11 { @@ -395,7 +401,7 @@ do_test sqllimits1-5.19 { unset blobvalue ifcapable datetime { - set strvalue [string repeat D [expr {$SQLITE_LIMIT_LENGTH-12}]] + set strvalue [string repeat D [expr {$SQLITE_LIMIT_LENGTH-11}]] do_test sqllimits1-5.20 { catchsql {SELECT strftime('%Y ' || $::strvalue, '2008-01-02')} } [list 0 [list "2008 $strvalue"]] @@ -863,16 +869,21 @@ do_test sqllimits1-15.2 { # This test case doesn't really belong with the other limits tests. # It is in this file because it is taxing to run, like the limits tests. # -do_test sqllimits1-16.1 { - set ::N [expr int(([expr pow(2,32)]/50) + 1)] - expr (($::N*50) & 0xffffffff)<55 -} {1} -do_test sqllimits1-16.2 { - set ::format "[string repeat A 60][string repeat "%J" $::N]" - catchsql { - SELECT strftime($::format, 1); - } -} {1 {string or blob too big}} +# Update for 3.37.0: strftime() used to allocate a large static buffer +# into which it would write its result. With that implementation, the +# following would trigger an SQLITE_TOOBIG error. But strftime() now +# uses the StrAccum functions, causing this test to fail. +# +#do_test sqllimits1-16.1 { +# set ::N [expr int(([expr pow(2,32)]/50) + 1)] +# expr (($::N*50) & 0xffffffff)<55 +#} {1} +#do_test sqllimits1-16.2 { +# set ::format "[string repeat A 60][string repeat "%J" $::N]" +# catchsql { +# SELECT strftime($::format, 1); +# } +#} {1 {string or blob too big}} do_catchsql_test sqllimits1.17.0 { SELECT *,*,*,*,*,*,*,* FROM ( diff --git a/test/stat.test b/test/stat.test index 5eb7d6f76..4705844ce 100644 --- a/test/stat.test +++ b/test/stat.test @@ -36,8 +36,10 @@ do_execsql_test stat-0.0 { } {/0 name TEXT .* 1 path TEXT .* 9 pgsize INTEGER/} # Attempts to drop an eponymous virtual table are a no-op. -do_execsql_test stat-0.1 { +do_catchsql_test stat-0.1a { DROP TABLE dbstat; +} {1 {table dbstat may not be dropped}} +do_execsql_test stat-0.1b { PRAGMA table_info=dbstat; } {/0 name TEXT .* 1 path TEXT .* 9 pgsize INTEGER/} @@ -315,4 +317,12 @@ do_execsql_test 8.5 { SELECT * FROM st4 WHERE schema = NULL; } {} +#------------------------------------------------------------------------- +reset_db +breakpoint +do_catchsql_test 9.1 { + CREATE TABLE dbstat(x, y); + DROP TABLE nosuchdb.dbstat; +} {/1 {(no such table: nosuchdb.dbstat|table dbstat may not be dropped)}/} + finish_test diff --git a/test/statfault.test b/test/statfault.test index ce79e328d..b5980d417 100644 --- a/test/statfault.test +++ b/test/statfault.test @@ -41,5 +41,15 @@ do_faultsim_test 1 -faults * -prep { faultsim_test_result {0 8} } +do_faultsim_test 2 -faults * -prep { + faultsim_restore_and_reopen + register_dbstat_vtab db + execsql { SELECT 1 FROM sqlite_master LIMIT 1 } +} -body { + db eval { SELECT * FROM sss } { db eval { SELECT randomblob(5000) } } +} -test { + faultsim_test_result {0 {}} +} finish_test + diff --git a/test/strict1.test b/test/strict1.test new file mode 100644 index 000000000..67483413f --- /dev/null +++ b/test/strict1.test @@ -0,0 +1,138 @@ +# 2021-08-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 file is testing STRICT tables. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix strict1 + +# STRICT tables have on a limited number of allowed datatypes. +# +do_catchsql_test strict1-1.1 { + CREATE TABLE t1(a) STRICT; +} {1 {missing datatype for t1.a}} +do_catchsql_test strict1-1.2 { + CREATE TABLE t1(a PRIMARY KEY) STRICT, WITHOUT ROWID; +} {1 {missing datatype for t1.a}} +do_catchsql_test strict1-1.3 { + CREATE TABLE t1(a PRIMARY KEY) WITHOUT ROWID, STRICT; +} {1 {missing datatype for t1.a}} +do_catchsql_test strict1-1.4 { + CREATE TABLE t1(a BANJO PRIMARY KEY) WITHOUT ROWID, STRICT; +} {1 {unknown datatype for t1.a: "BANJO"}} +do_catchsql_test strict1-1.5 { + CREATE TABLE t1(a TEXT PRIMARY KEY, b INT, c INTEGER, d REAL, e BLOB, f DATE) strict; +} {1 {unknown datatype for t1.f: "DATE"}} +do_catchsql_test strict1-1.6 { + CREATE TABLE t1(a TEXT PRIMARY KEY, b INT, c INTEGER, d REAL, e BLOB, f TEXT(50)) WITHOUT ROWID, STRICT; +} {1 {unknown datatype for t1.f: "TEXT(50)"}} + +do_execsql_test strict1-2.0 { + CREATE TABLE t1( + a INT, + b INTEGER, + c BLOB, + d TEXT, + e REAL + ) STRICT; +} {} +ifcapable vtab { + do_execsql_test strict1-2.0a { + SELECT strict FROM pragma_table_list('t1'); + } {1} +} +do_catchsql_test strict1-2.1 { + INSERT INTO t1(a) VALUES('xyz'); +} {1 {cannot store TEXT value in INT column t1.a}} +do_catchsql_test strict1-2.2 { + INSERT INTO t1(b) VALUES('xyz'); +} {1 {cannot store TEXT value in INTEGER column t1.b}} +do_catchsql_test strict1-2.3 { + INSERT INTO t1(c) VALUES('xyz'); +} {1 {cannot store TEXT value in BLOB column t1.c}} +do_catchsql_test strict1-2.4 { + INSERT INTO t1(d) VALUES(x'3142536475'); +} {1 {cannot store BLOB value in TEXT column t1.d}} +do_catchsql_test strict1-2.5 { + INSERT INTO t1(e) VALUES('xyz'); +} {1 {cannot store TEXT value in REAL column t1.e}} + + +do_execsql_test strict1-3.1 { + INSERT INTO t1(a, b) VALUES(1,2),('3','4'),(5.0, 6.0),(null,null); + SELECT a, b, '|' FROM t1; +} {1 2 | 3 4 | 5 6 | {} {} |} +do_catchsql_test strict1-3.2 { + INSERT INTO t1(a) VALUES(1.2); +} {1 {cannot store REAL value in INT column t1.a}} +do_catchsql_test strict1-3.3 { + INSERT INTO t1(a) VALUES(x'313233'); +} {1 {cannot store BLOB value in INT column t1.a}} +do_catchsql_test strict1-3.4 { + INSERT INTO t1(b) VALUES(1.2); +} {1 {cannot store REAL value in INTEGER column t1.b}} +do_catchsql_test strict1-3.5 { + INSERT INTO t1(b) VALUES(x'313233'); +} {1 {cannot store BLOB value in INTEGER column t1.b}} + +do_execsql_test strict1-4.1 { + DELETE FROM t1; + INSERT INTO t1(c) VALUES(x'313233'), (NULL); + SELECT typeof(c), c FROM t1; +} {blob 123 null {}} +do_catchsql_test strict1-4.2 { + INSERT INTO t1(c) VALUES('456'); +} {1 {cannot store TEXT value in BLOB column t1.c}} + +do_execsql_test strict1-5.1 { + DELETE FROM t1; + INSERT INTO t1(d) VALUES('xyz'),(4),(5.5),(NULL); + SELECT typeof(d), d FROM t1; +} {text xyz text 4 text 5.5 null {}} +do_catchsql_test strict1-5.2 { + INSERT INTO t1(d) VALUES(x'4567'); +} {1 {cannot store BLOB value in TEXT column t1.d}} + +do_execsql_test strict1-6.1 { + DELETE FROM t1; + INSERT INTO t1(e) VALUES(1),(2.5),('3'),('4.5'),(6.0),(NULL); + SELECT typeof(e), e FROM t1; +} {real 1.0 real 2.5 real 3.0 real 4.5 real 6.0 null {}} +do_catchsql_test strict1-6.2 { + INSERT INTO t1(e) VALUES('xyz'); +} {1 {cannot store TEXT value in REAL column t1.e}} +do_catchsql_test strict1-6.3 { + INSERT INTO t1(e) VALUES(x'3456'); +} {1 {cannot store BLOB value in REAL column t1.e}} + +ifcapable altertable { + do_execsql_test strict1-7.1 { + DROP TABLE IF EXISTS t4; + CREATE TABLE t4( + a INT AS (b*2) VIRTUAL, + b INT AS (c*2) STORED, + c INT PRIMARY KEY + ) STRICT; + INSERT INTO t4(c) VALUES(1); + SELECT * FROM t4; + } {4 2 1} + do_catchsql_test strict1-7.2 { + ALTER TABLE t4 ADD COLUMN d VARCHAR; + } {1 {error in table t4 after add column: unknown datatype for t4.d: "VARCHAR"}} + do_catchsql_test strict1-7.3 { + ALTER TABLE t4 ADD COLUMN d; + } {1 {error in table t4 after add column: missing datatype for t4.d}} +} + +finish_test diff --git a/test/strict2.test b/test/strict2.test new file mode 100644 index 000000000..be5952284 --- /dev/null +++ b/test/strict2.test @@ -0,0 +1,158 @@ +# 2021-08-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. +# +#*********************************************************************** +# +# This file implements regression tests for SQLite library. The +# focus of this file is testing STRICT tables. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix strict2 + +# PRAGMA integrity_check on a STRICT table should verify that +# all of the values are of the correct type. +# +do_execsql_test strict2-1.1 { + CREATE TABLE t1( + a INT, + b INTEGER, + c TEXT, + d REAL, + e BLOB + ) STRICT; + CREATE TABLE t1nn( + a INT NOT NULL, + b INTEGER NOT NULL, + c TEXT NOT NULL, + d REAL NOT NULL, + e BLOB NOT NULL + ) STRICT; + CREATE TABLE t2(a,b,c,d,e); + INSERT INTO t1(a,b,c,d,e) VALUES(1,1,'one',1.0,x'b1'),(2,2,'two',2.25,x'b2b2b2'); + PRAGMA writable_schema=on; + UPDATE sqlite_schema SET rootpage=(SELECT rootpage FROM sqlite_schema WHERE name='t1'); +} {} +db close +sqlite3 db test.db +do_execsql_test strict2-1.2 { + PRAGMA quick_check('t1'); +} {ok} +do_execsql_test strict2-1.3 { + UPDATE t2 SET a=2.5 WHERE b=2; + PRAGMA quick_check('t1'); +} {{non-INT value in t1.a}} +do_execsql_test strict2-1.4 { + UPDATE t2 SET a='xyz' WHERE b=2; + PRAGMA quick_check('t1'); +} {{non-INT value in t1.a}} +do_execsql_test strict2-1.5 { + UPDATE t2 SET a=x'445566' WHERE b=2; + PRAGMA quick_check('t1'); +} {{non-INT value in t1.a}} +do_execsql_test strict2-1.6 { + UPDATE t2 SET a=2.5 WHERE b=2; + PRAGMA quick_check('t1nn'); +} {{non-INT value in t1nn.a}} +do_execsql_test strict2-1.7 { + UPDATE t2 SET a='xyz' WHERE b=2; + PRAGMA quick_check('t1nn'); +} {{non-INT value in t1nn.a}} +do_execsql_test strict2-1.8 { + UPDATE t2 SET a=x'445566' WHERE b=2; + PRAGMA quick_check('t1nn'); +} {{non-INT value in t1nn.a}} + +do_execsql_test strict2-1.13 { + UPDATE t2 SET a=2 WHERE b=2; + UPDATE t2 SET b=2.5 WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-INTEGER value in t1.b}} +do_execsql_test strict2-1.14 { + UPDATE t2 SET b='two' WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-INTEGER value in t1.b}} +do_execsql_test strict2-1.15 { + UPDATE t2 SET b=x'b0b1b2b3b4' WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-INTEGER value in t1.b}} +do_execsql_test strict2-1.16 { + UPDATE t2 SET b=NULL WHERE a=2; + PRAGMA quick_check('t1'); +} {ok} +do_execsql_test strict2-1.17 { + UPDATE t2 SET b=2.5 WHERE a=2; + PRAGMA quick_check('t1nn'); +} {{non-INTEGER value in t1nn.b}} +do_execsql_test strict2-1.18 { + UPDATE t2 SET b=NULL WHERE a=2; + PRAGMA quick_check('t1nn'); +} {{NULL value in t1nn.b}} + +do_execsql_test strict2-1.23 { + UPDATE t2 SET b=2 WHERE a=2; + UPDATE t2 SET c=9 WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-TEXT value in t1.c}} +do_execsql_test strict2-1.24 { + UPDATE t2 SET c=9.5 WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-TEXT value in t1.c}} +do_execsql_test strict2-1.25 { + UPDATE t2 SET c=x'b0b1b2b3b4' WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-TEXT value in t1.c}} + +do_execsql_test strict2-1.33 { + UPDATE t2 SET c='two' WHERE a=2; + UPDATE t2 SET d=9 WHERE a=2; + PRAGMA quick_check('t1'); +} {ok} +do_execsql_test strict2-1.34 { + UPDATE t2 SET d='nine' WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-REAL value in t1.d}} +do_execsql_test strict2-1.35 { + UPDATE t2 SET d=x'b0b1b2b3b4' WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-REAL value in t1.d}} + +do_execsql_test strict2-1.43 { + UPDATE t2 SET d=2.5 WHERE a=2; + UPDATE t2 SET e=9 WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-BLOB value in t1.e}} +do_execsql_test strict2-1.44 { + UPDATE t2 SET e=9.5 WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-BLOB value in t1.e}} +do_execsql_test strict2-1.45 { + UPDATE t2 SET e='hello' WHERE a=2; + PRAGMA quick_check('t1'); +} {{non-BLOB value in t1.e}} + +do_execsql_test strict2-2.0 { + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(a INT, b ANY) STRICT; + INSERT INTO t2(a,b) VALUES(1,2),(3,4.5),(5,'six'),(7,x'8888'),(9,NULL); + PRAGMA integrity_check(t2); +} {ok} + +do_execsql_test strict2-3.0 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(id ANY PRIMARY KEY, x TEXT); + INSERT INTO t1 VALUES(1,2),('three','four'),(x'5555','six'),(NULL,'eight'); + PRAGMA writable_schema=ON; + UPDATE sqlite_schema SET sql=(sql||'STRICT') WHERE name='t1'; + PRAGMA writable_schema=RESET; + PRAGMA integrity_check(t1); +} {{NULL value in t1.id}} + +finish_test diff --git a/test/tabfunc01.test b/test/tabfunc01.test index 797267b33..d3d93792d 100644 --- a/test/tabfunc01.test +++ b/test/tabfunc01.test @@ -32,8 +32,14 @@ do_execsql_test tabfunc01-1.1b { PRAGMA table_xinfo(generate_series); } {0 value {} 0 {} 0 0 1 start {} 0 {} 0 1 2 stop {} 0 {} 0 1 3 step {} 0 {} 0 1} do_execsql_test tabfunc01-1.2 { - SELECT *, '|' FROM generate_series LIMIT 5; + SELECT *, '|' FROM generate_series(0) LIMIT 5; } {0 | 1 | 2 | 3 | 4 |} +do_catchsql_test tabfunc01-1.2b { + SELECT *, '|' FROM generate_series LIMIT 5; +} {1 {first argument to "generate_series()" missing or unusable}} +do_catchsql_test tabfunc01-1.2c { + SELECT *, '|' FROM generate_series(value) LIMIT 5; +} {1 {first argument to "generate_series()" missing or unusable}} do_catchsql_test tabfunc01-1.3 { CREATE VIRTUAL TABLE t1 USING generate_series; } {1 {no such module: generate_series}} @@ -104,7 +110,7 @@ do_execsql_test tabfunc01-2.2 { } {2 1 | 2 2 | 3 1 | 3 2 | 3 3 |} do_execsql_test tabfunc01-2.50 { - SELECT * FROM generate_series() LIMIT 5; + SELECT * FROM generate_series(0) LIMIT 5; } {0 1 2 3 4} do_execsql_test tabfunc01-3.1 { diff --git a/test/tclsqlite.test b/test/tclsqlite.test index c111325bc..c2fa522e6 100644 --- a/test/tclsqlite.test +++ b/test/tclsqlite.test @@ -166,7 +166,7 @@ do_test tcl-2.1 { ifcapable schema_pragmas { do_test tcl-2.2 { execsql "PRAGMA table_info(t\u0123x)" - } "0 a int 0 {} 0 1 b\u1235 float 0 {} 0" + } "0 a INT 0 {} 0 1 b\u1235 float 0 {} 0" } do_test tcl-2.3 { execsql "INSERT INTO t\u0123x VALUES(1,2.3)" @@ -848,4 +848,40 @@ do_catchsql_test 19.911 { } {1 {invalid command name "bind_fallback_does_not_exist"}} db bind_fallback {} +#------------------------------------------------------------------------- +do_test 20.0 { + db transaction { + db close + } +} {} + +do_test 20.1 { + sqlite3 db test.db + set rc [catch { + db eval {SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3} { db close } + } msg] + list $rc $msg +} {1 {invalid command name "db"}} + + +proc closedb {} { + db close + return 10 +} +proc func1 {} { return 1 } + +sqlite3 db test.db +db func closedb closedb +db func func1 func1 + +do_test 20.2 { + set rc [catch { + db eval { + SELECT closedb(),func1() UNION ALL SELECT 20,30 UNION ALL SELECT 30,40 + } + } msg] + list $rc $msg +} {0 {10 1 20 30 30 40}} + finish_test + diff --git a/test/tester.tcl b/test/tester.tcl index 304808b6b..bfcc831ff 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -89,6 +89,9 @@ # verbose # +# Only run this script once. If sourced a second time, make it a no-op +if {[info exists ::tester_tcl_has_run]} return + # Set the precision of FP arithmatic used by the interpreter. And # configure SQLite to take database file locks on the page that begins # 64KB into the database file instead of the one 1GB in. This means @@ -1200,13 +1203,36 @@ proc speed_trial_summary {name} { } } -# Run this routine last +# Clear out left-over configuration setup from the end of a test # -proc finish_test {} { - catch {db close} +proc finish_test_precleanup {} { catch {db1 close} catch {db2 close} catch {db3 close} + catch {unregister_devsim} + catch {unregister_jt_vfs} + catch {unregister_demovfs} +} + +# Run this routine last +# +proc finish_test {} { + global argv + finish_test_precleanup + if {[llength $argv]>0} { + # If additional test scripts are specified on the command-line, + # run them also, before quitting. + proc finish_test {} { + finish_test_precleanup + return + } + foreach extra $argv { + puts "Running \"$extra\"" + db_delete_and_reopen + uplevel #0 source $extra + } + } + catch {db close} if {0==[info exists ::SLAVE]} { finalize_testing } } proc finalize_testing {} { @@ -1904,21 +1930,23 @@ proc do_ioerr_test {testname args} { set ::sqlite_io_error_hardhit 0 set r [catch $::ioerrorbody msg] set ::errseen $r - set rc [sqlite3_errcode $::DB] - if {$::ioerropts(-erc)} { - # If we are in extended result code mode, make sure all of the - # IOERRs we get back really do have their extended code values. - # If an extended result code is returned, the sqlite3_errcode - # TCLcommand will return a string of the form: SQLITE_IOERR+nnnn - # where nnnn is a number - if {[regexp {^SQLITE_IOERR} $rc] && ![regexp {IOERR\+\d} $rc]} { - return $rc - } - } else { - # If we are not in extended result code mode, make sure no - # extended error codes are returned. - if {[regexp {\+\d} $rc]} { - return $rc + if {[info commands db]!=""} { + set rc [sqlite3_errcode db] + if {$::ioerropts(-erc)} { + # If we are in extended result code mode, make sure all of the + # IOERRs we get back really do have their extended code values. + # If an extended result code is returned, the sqlite3_errcode + # TCLcommand will return a string of the form: SQLITE_IOERR+nnnn + # where nnnn is a number + if {[regexp {^SQLITE_IOERR} $rc] && ![regexp {IOERR\+\d} $rc]} { + return $rc + } + } else { + # If we are not in extended result code mode, make sure no + # extended error codes are returned. + if {[regexp {\+\d} $rc]} { + return $rc + } } } # The test repeats as long as $::go is non-zero. $::go starts out @@ -2495,3 +2523,5 @@ extra_schema_checks 1 source $testdir/thread_common.tcl source $testdir/malloc_common.tcl + +set tester_tcl_has_run 1 diff --git a/test/threadtest3.c b/test/threadtest3.c index 41c0fb9ac..3a12c5889 100644 --- a/test/threadtest3.c +++ b/test/threadtest3.c @@ -78,15 +78,31 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include + +#ifdef _WIN32 +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# define O_BINARY 0 +#endif #include "test_multiplex.h" @@ -436,8 +452,13 @@ struct Thread { int iTid; /* Thread number within test */ void* pArg; /* Pointer argument passed by caller */ +#ifdef _WIN32 + uintptr_t winTid; /* Thread handle */ +#else pthread_t tid; /* Thread id */ +#endif char *(*xProc)(int, void*); /* Thread main proc */ + char *zRes; /* Value returned by xProc */ Thread *pNext; /* Next in this list of threads */ }; @@ -471,9 +492,13 @@ static void print_and_free_err(Error *p){ static void system_error(Error *pErr, int iSys){ pErr->rc = iSys; +#if _WIN32 + pErr->zErr = sqlite3_mprintf("%s", strerror(iSys)); +#else pErr->zErr = (char *)sqlite3_malloc(512); strerror_r(iSys, pErr->zErr, 512); pErr->zErr[511] = '\0'; +#endif } static void sqlite_error( @@ -512,7 +537,7 @@ static void clear_error_x( } static int busyhandler(void *pArg, int n){ - usleep(10*1000); + sqlite3_sleep(10); return 1; } @@ -749,10 +774,20 @@ static void integrity_check_x( } } +#ifdef _WIN32 +static unsigned __stdcall launch_thread_main(void *pArg){ + Thread *p = (Thread *)pArg; + p->zRes = p->xProc(p->iTid, p->pArg); + _endthreadex(0); + return 0; /* NOT REACHED */ +} +#else static void *launch_thread_main(void *pArg){ Thread *p = (Thread *)pArg; - return (void *)p->xProc(p->iTid, p->pArg); + p->zRes = p->xProc(p->iTid, p->pArg); + return 0; } +#endif static void launch_thread_x( Error *pErr, /* IN/OUT: Error code */ @@ -771,7 +806,13 @@ static void launch_thread_x( p->pArg = pArg; p->xProc = xProc; +#ifdef _WIN32 + rc = SQLITE_OK; + p->winTid = _beginthreadex(0, 0, launch_thread_main, (void*)p, 0, 0); + if( p->winTid==0 ) rc = errno ? errno : rc; +#else rc = pthread_create(&p->tid, NULL, launch_thread_main, (void *)p); +#endif if( rc!=0 ){ system_error(pErr, rc); sqlite3_free(p); @@ -789,29 +830,47 @@ static void join_all_threads_x( Thread *p; Thread *pNext; for(p=pThreads->pThread; p; p=pNext){ +#ifndef _WIN32 void *ret; - pNext = p->pNext; +#endif int rc; + pNext = p->pNext; + +#ifdef _WIN32 + do { + rc = WaitForSingleObjectEx((HANDLE)p->winTid, INFINITE, TRUE); + }while( rc==WAIT_IO_COMPLETION ); + CloseHandle((HANDLE)p->winTid); +#else rc = pthread_join(p->tid, &ret); +#endif + if( rc!=0 ){ if( pErr->rc==SQLITE_OK ) system_error(pErr, rc); }else{ - printf("Thread %d says: %s\n", p->iTid, (ret==0 ? "..." : (char *)ret)); + printf("Thread %d says: %s\n", p->iTid, (p->zRes==0 ? "..." : p->zRes)); fflush(stdout); } + sqlite3_free(p->zRes); sqlite3_free(p); } pThreads->pThread = 0; } +#ifdef _WIN32 +# define THREADTEST3_STAT _stat +#else +# define THREADTEST3_STAT stat +#endif + static i64 filesize_x( Error *pErr, const char *zFile ){ i64 iRet = 0; if( pErr->rc==SQLITE_OK ){ - struct stat sStat; - if( stat(zFile, &sStat) ){ + struct THREADTEST3_STAT sStat; + if( THREADTEST3_STAT(zFile, &sStat) ){ iRet = -1; }else{ iRet = sStat.st_size; @@ -836,12 +895,12 @@ static void filecopy_x( int fd2; unlink(zTo); - fd1 = open(zFrom, O_RDONLY); + fd1 = open(zFrom, O_RDONLY|O_BINARY); if( fd1<0 ){ system_error(pErr, errno); return; } - fd2 = open(zTo, O_RDWR|O_CREAT|O_EXCL, 0644); + fd2 = open(zTo, O_RDWR|O_CREAT|O_EXCL|O_BINARY, 0644); if( fd2<0 ){ system_error(pErr, errno); close(fd1); @@ -967,7 +1026,7 @@ static char *walthread1_ckpt_thread(int iTid, void *pArg){ opendb(&err, &db, "test.db", 0); while( !timetostop(&err) ){ - usleep(500*1000); + sqlite3_sleep(500); execsql(&err, &db, "PRAGMA wal_checkpoint"); if( err.rc==SQLITE_OK ) nCkpt++; clear_error(&err, SQLITE_BUSY); @@ -1415,7 +1474,7 @@ static void dynamic_triggers(int nMs){ launch_thread(&err, &threads, dynamic_triggers_2, 0); launch_thread(&err, &threads, dynamic_triggers_2, 0); - sleep(2); + sqlite3_sleep(2*1000); sqlite3_enable_shared_cache(0); launch_thread(&err, &threads, dynamic_triggers_2, 0); diff --git a/test/tkt-2d1a5c67d.test b/test/tkt-2d1a5c67d.test index 0d12a6ec4..1f797686b 100644 --- a/test/tkt-2d1a5c67d.test +++ b/test/tkt-2d1a5c67d.test @@ -19,7 +19,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix tkt-2d1a5c67d -ifcapable {!vtab} {finish_test; return} +ifcapable {!vtab || !incrblob} {finish_test; return} if {[wal_is_capable]==0} {finish_test; return} for {set ii 1} {$ii<=10} {incr ii} { diff --git a/test/tkt-8454a207b9.test b/test/tkt-8454a207b9.test index 88a8614f8..20e142057 100644 --- a/test/tkt-8454a207b9.test +++ b/test/tkt-8454a207b9.test @@ -18,6 +18,12 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + do_test tkt-8454a207b9.1 { db eval { CREATE TABLE t1(a); diff --git a/test/tkt-f67b41381a.test b/test/tkt-f67b41381a.test index 1ddec988c..43e5cc7db 100644 --- a/test/tkt-f67b41381a.test +++ b/test/tkt-f67b41381a.test @@ -15,6 +15,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix tkt-f67b41381a +ifcapable !altertable { + finish_test + return +} + do_execsql_test 1.0 { CREATE TABLE t1(a); INSERT INTO t1 VALUES(1); diff --git a/test/transitive1.test b/test/transitive1.test index 875533849..80c53e8ff 100644 --- a/test/transitive1.test +++ b/test/transitive1.test @@ -380,4 +380,50 @@ do_execsql_test transitive1-630 { SELECT ALL * FROM t1,t0 WHERE (likely(t1.c0=t0.c1) AND t1.c0=t0.c0); } {} +#------------------------------------------------------------------------- +# 2021-08-31 forum https://sqlite.org/forum/forumpost/8d1b58f112 +reset_db +do_execsql_test transitive1-700 { + CREATE TABLE t1(a INT PRIMARY KEY); + INSERT INTO t1(a) VALUES(1),(2),(3); + CREATE TABLE t2(x INTEGER PRIMARY KEY,y INT); + INSERT INTO t2(y) VALUES(2),(3); +} + +do_execsql_test transitive1-710 { + SELECT * FROM t1 CROSS JOIN t2 WHERE t2.y=t1.a AND t1.a=t2.x +} {} + +do_execsql_test transitive1-720 { + SELECT * FROM t1 CROSS JOIN t2 WHERE likely(t2.y=t1.a) AND unlikely(t1.a=t2.x) +} {} + +# 2021-10-04 forum https://sqlite.org/forum/forumpost/a65cacbf5e1c41ba +# +reset_db +do_execsql_test transitive1-800 { + CREATE TABLE t1(a INT); + INSERT INTO t1 VALUES(0),(3); + CREATE TABLE t2(b INT UNIQUE, c INT); + INSERT INTO t2 VALUES(1,4) ,(0,5); + SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a AND b IS a); + SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE a=c AND a IS b); + SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE a=c AND b IS a); + SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a AND a IS b); +} {} +do_execsql_test transitive1-810 { + CREATE TABLE t3(a INTEGER PRIMARY KEY,b); + INSERT INTO t3(a,b) VALUES(1,2),(5,5),(7,11); + SELECT * FROM t3 WHERE a=b AND a='5'; +} {5 5} +do_execsql_test transitive1-811 { + SELECT * FROM t3 WHERE a=b AND a='4'; +} {} +do_execsql_test transitive1-812 { + SELECT * FROM t3 WHERE a=b AND a='7'; +} {} +do_execsql_test transitive1-813 { + SELECT * FROM t3 WHERE a=b AND a='5x'; +} {} + finish_test diff --git a/test/tt3_checkpoint.c b/test/tt3_checkpoint.c index 060a69821..ec9d0727e 100644 --- a/test/tt3_checkpoint.c +++ b/test/tt3_checkpoint.c @@ -75,7 +75,7 @@ static char *checkpoint_starvation_reader(int iTid, void *pArg){ i64 iCount1, iCount2; sql_script(&err, &db, "BEGIN"); iCount1 = execsql_i64(&err, &db, "SELECT count(x) FROM t1"); - usleep(CHECKPOINT_STARVATION_READMS*1000); + sqlite3_sleep(CHECKPOINT_STARVATION_READMS); iCount2 = execsql_i64(&err, &db, "SELECT count(x) FROM t1"); sql_script(&err, &db, "COMMIT"); @@ -107,7 +107,7 @@ static void checkpoint_starvation_main(int nMs, CheckpointStarvationCtx *p){ for(i=0; i<4; i++){ launch_thread(&err, &threads, checkpoint_starvation_reader, 0); - usleep(CHECKPOINT_STARVATION_READMS*1000/4); + sqlite3_sleep(CHECKPOINT_STARVATION_READMS/4); } sqlite3_wal_hook(db.db, checkpoint_starvation_walhook, (void *)p); diff --git a/test/tt3_vacuum.c b/test/tt3_vacuum.c index 023fdfd74..c41a6b487 100644 --- a/test/tt3_vacuum.c +++ b/test/tt3_vacuum.c @@ -23,9 +23,9 @@ static char *vacuum1_thread_writer(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ - opendb(&err, &db, "test.db", 0); i64 i = 0; + opendb(&err, &db, "test.db", 0); while( !timetostop(&err) ){ i++; diff --git a/test/update.test b/test/update.test index dd96124b4..7be360726 100644 --- a/test/update.test +++ b/test/update.test @@ -619,16 +619,18 @@ do_test update-14.4 { # Ticket [https://www.sqlite.org/src/tktview/43107840f1c02] on 2014-10-29 # An assertion fault on UPDATE # -do_execsql_test update-15.1 { - CREATE TABLE t15(a INTEGER PRIMARY KEY, b); - INSERT INTO t15(a,b) VALUES(10,'abc'),(20,'def'),(30,'ghi'); - ALTER TABLE t15 ADD COLUMN c; - CREATE INDEX t15c ON t15(c); - INSERT INTO t15(a,b) - VALUES(5,'zyx'),(15,'wvu'),(25,'tsr'),(35,'qpo'); - UPDATE t15 SET c=printf("y%d",a) WHERE c IS NULL; - SELECT a,b,c,'|' FROM t15 ORDER BY a; -} {5 zyx y5 | 10 abc y10 | 15 wvu y15 | 20 def y20 | 25 tsr y25 | 30 ghi y30 | 35 qpo y35 |} +ifcapable altertable { + do_execsql_test update-15.1 { + CREATE TABLE t15(a INTEGER PRIMARY KEY, b); + INSERT INTO t15(a,b) VALUES(10,'abc'),(20,'def'),(30,'ghi'); + ALTER TABLE t15 ADD COLUMN c; + CREATE INDEX t15c ON t15(c); + INSERT INTO t15(a,b) + VALUES(5,'zyx'),(15,'wvu'),(25,'tsr'),(35,'qpo'); + UPDATE t15 SET c=printf("y%d",a) WHERE c IS NULL; + SELECT a,b,c,'|' FROM t15 ORDER BY a; + } {5 zyx y5 | 10 abc y10 | 15 wvu y15 | 20 def y20 | 25 tsr y25 | 30 ghi y30 | 35 qpo y35 |} +} # Unreleased bug in UPDATE caused by the UPSERT changes. # Found by OSSFuzz as soon as the UPSERT changes landed on trunk. diff --git a/test/upfrom2.test b/test/upfrom2.test index 81c847a60..a1f00953e 100644 --- a/test/upfrom2.test +++ b/test/upfrom2.test @@ -365,5 +365,21 @@ foreach {tn update nm} { "1 {target object/alias may not appear in FROM clause: $nm}" } +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a); +} + +do_execsql_test 6.1 { + UPDATE t1 SET a = 1 FROM ( + SELECT * FROM t1 + ) +} {} +do_execsql_test 6.2 { + UPDATE t1 SET a = 1 FROM ( + SELECT * FROM t1 UNION ALL SELECT * FROM t1 + ) +} {} finish_test diff --git a/test/upsert1.test b/test/upsert1.test index 5250a5d2f..a321d6171 100644 --- a/test/upsert1.test +++ b/test/upsert1.test @@ -241,4 +241,18 @@ do_catchsql_test upsert1-1000 { ON CONFLICT(c2) DO UPDATE SET c1 = c0; } {1 {NOT NULL constraint failed: t0.c0}} +# 2021-12-29 forum post https://sqlite.org/forum/forumpost/06b16b8b29f8c8c3 +# By Jingzhou Fu. When there is both an INTEGER PRIMARY KEY ON CONFLICT REPLACE +# and an upsert on a constraint other than the INTEGER PRIMARY KEY, the +# constraint checking logic generates invalid bytecode which might result +# in a NULL pointer dereference. +# +reset_db +do_execsql_test upsert1-1100 { + CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b UNIQUE); + INSERT INTO t1(b) VALUES(22); + INSERT INTO t1 VALUES(2,22) ON CONFLICT (b) DO NOTHING; + SELECT * FROM t1; +} {1 22} + finish_test diff --git a/test/vacuum-into.test b/test/vacuum-into.test index cd1c57a26..a46d95cf1 100644 --- a/test/vacuum-into.test +++ b/test/vacuum-into.test @@ -100,4 +100,37 @@ do_test vacuum-into-510 { db2 close db close +# Change the page-size on a VACUUM INTO even if the original +# database is in WAL mode. +# +if {[wal_is_capable]} { + forcedelete test.db + forcedelete test.db2 + do_test vacuum-into-600 { + sqlite3 db test.db + db eval { + PRAGMA page_size=4096; + PRAGMA journal_mode=WAL; + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(19); + CREATE INDEX t1a ON t1(a); + PRAGMA integrity_check; + } + } {wal ok} + do_execsql_test vacuum-into-610 { + PRAGMA page_size; + } {4096} + do_execsql_test vacuum-into-620 { + PRAGMA page_size=1024; + VACUUM INTO 'test.db2'; + } {} + do_test vacuum-into-630 { + sqlite3 db test.db2 + db eval { + PRAGMA page_size; + PRAGMA integrity_check; + } + } {1024 ok} +} + finish_test diff --git a/test/vacuum3.test b/test/vacuum3.test index 484a7d448..e6a1c2b9e 100644 --- a/test/vacuum3.test +++ b/test/vacuum3.test @@ -81,7 +81,17 @@ do_test vacuum3-2.1 { execsql { PRAGMA page_size = 1024; VACUUM; - ALTER TABLE t1 ADD COLUMN d; + } + ifcapable altertable { + execsql { ALTER TABLE t1 ADD COLUMN d; } + } else { + execsql { + DROP TABLE t1; + CREATE TABLE t1(a, b, c, d); + INSERT INTO t1 VALUES(1, 2, 3, NULL); + } + } + execsql { UPDATE t1 SET d = randomblob(1000); } file size test.db diff --git a/test/view.test b/test/view.test index b30d4162d..85202d7d0 100644 --- a/test/view.test +++ b/test/view.test @@ -55,6 +55,11 @@ do_test view-1.1.110 { SELECT * FROM v1temp ORDER BY a; } } {0 {1 2 4 5 7 8 1 2 4 5 7 8}} +ifcapable vtab { + do_execsql_test view-1.1.120 { + SELECT name, type FROM pragma_table_list('v1'); + } {v1 view} +} do_test view-1.2 { catchsql { ROLLBACK; diff --git a/test/vtab1.test b/test/vtab1.test index 3d2e23336..4b8fb9c70 100644 --- a/test/vtab1.test +++ b/test/vtab1.test @@ -1558,4 +1558,36 @@ ifcapable fts3 { } } +# 2021-07-04 https://sqlite.org/forum/forumpost/16ca0e9f32 +# Yu Liang crash involving UPDATE on a virtual table with +# a duplicate column in a vector changeset and invoking the +# query flattener for UNION ALL. +# +reset_db +register_echo_module db +do_catchsql_test 25.0 { + CREATE TABLE t0(a); + CREATE VIRTUAL TABLE t1 USING echo(t0); + WITH t3(a) AS (SELECT * FROM t1 UNION ALL SELECT * FROM t1) + UPDATE t1 SET (a,a) = (SELECT 1, 0) FROM t3; +} {0 {}} + +#-------------------------------------------------------------------------- +# +reset_db +load_static_extension db wholenumber +do_execsql_test 26.1 { + CREATE VIRTUAL TABLE t1 USING wholenumber; + CREATE TABLE tx(a, b, c); +} +do_test 26.2 { + sqlite3 db2 test.db + db2 eval { CREATE TABLE ty(x, y) } + db2 close +} {} +do_execsql_test 26.3 { + SELECT value FROM t1 WHERE value<5 +} {1 2 3 4} + + finish_test diff --git a/test/vtabA.test b/test/vtabA.test index eddaa70d1..4c9beae02 100644 --- a/test/vtabA.test +++ b/test/vtabA.test @@ -128,7 +128,7 @@ proc analyse_parse {columns decltype_list} { do_test vtabA-2.1 { analyse_parse {(a text, b integer hidden, c hidden)} {a b c} -} {a text integer {}} +} {a TEXT integer {}} do_test vtabA-2.2 { analyse_parse {(a hidden , b integerhidden, c hidden1)} {a b c} diff --git a/test/vtabK.test b/test/vtabK.test new file mode 100644 index 000000000..07fe9c131 --- /dev/null +++ b/test/vtabK.test @@ -0,0 +1,83 @@ +# 2020-09-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 for a strange scenario discovered by +# dbsqlfuzz (0ad6d441f9bf3dfc32626a9900bc1700495b16f9) in which a +# virtual table is named "sqlite_stat1". +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix vtabK + +ifcapable !vtab||!rtree||!fts5 { + finish_test + return +} + +do_execsql_test 100 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(123); + PRAGMA writable_schema=ON; + CREATE VIRTUAL TABLE sqlite_stat1 USING fts5(a); + PRAGMA writable_schema=OFF; + CREATE VIRTUAL TABLE t3 USING fts5(b); + INSERT INTO t3 VALUES('this is a test'); +} +do_catchsql_test 110 { + CREATE VIRTUAL TABLE t2 USING rtree(id,x,y); +} {1 {no such column: stat}} +do_execsql_test 120 { + SELECT * FROM t1; +} {123} +do_execsql_test 130 { + INSERT INTO t3(b) VALUES('Four score and seven years ago'); + SELECT * FROM t3 WHERE t3 MATCH 'this'; +} {{this is a test}} +do_execsql_test 140 { + SELECT * FROM t3 WHERE t3 MATCH 'four seven'; +} {{Four score and seven years ago}} +do_execsql_test 150 { + INSERT INTO sqlite_stat1(a) + VALUES('We hold these truths to be self-evident...'); + SELECT * FROM sqlite_stat1; +} {{We hold these truths to be self-evident...}} +do_catchsql_test 160 { + ANALYZE; +} {1 {database disk image is malformed}} +do_execsql_test 170 { + PRAGMA integrity_check; +} {ok} + +# Follow-on dbsqlfuzz bc02a0cde82dee801a8d6f653d2831680f87dca1 +reset_db +do_execsql_test 200 { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES('Ebed-malech'); + CREATE TABLE x(a); + PRAGMA writable_schema=ON; + CREATE VIRTUAL TABLE sqlite_stat1 USING fts5(a); +} {} +do_catchsql_test 210 { + CREATE VIRTUAL TABLE t2 USING rtree(id,x,y); +} {1 {no such column: stat}} +do_execsql_test 220 { + SELECT * FROM t1; +} {Ebed-malech} + +# Follow-on dbsqlfuzz a097eaad43c3c845b236126df92fb49b25449b0c +reset_db +do_catchsql_test 300 { + CREATE VIRTUAL TABLE t1 USING rtree(a,b,c); + CREATE TABLE t2(x); + ALTER TABLE t2 ADD d GENERATED ALWAYS AS (c IN (SELECT 1 FROM t1)) VIRTUAL; +} {1 {error in table t2 after add column: subqueries prohibited in generated columns}} + +finish_test diff --git a/test/where.test b/test/where.test index 298248a6e..8ee57b8b6 100644 --- a/test/where.test +++ b/test/where.test @@ -1571,4 +1571,27 @@ do_execsql_test where-26.8 { SELECT '-1'>=0 AND '-1'<=t1.c0 FROM t1; } {1} +# 2021-07-19 https://sqlite.org/forum/forumpost/2bdb86a068 +# Lose of precision when doing comparisons between integer and +# floating point values that are near 9223372036854775807 in the +# OP_SeekGE opcode (and similar). +# +# Valgrind documentation acknowledges that under valgrind, FP calculations +# may not be as accurate as on x86/amd64 hardware. This seems to be causing +# these tests to fail. +# +# https://valgrind.org/docs/manual/manual-core.html#manual-core.limits +# +if {[permutation]!="valgrind"} { + reset_db + do_execsql_test where-27.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY); + INSERT INTO t1(a) VALUES(9223372036854775807); + SELECT 1 FROM t1 WHERE a>=(9223372036854775807+1); + } {} + do_execsql_test where-27.2 { + SELECT a>=9223372036854775807+1 FROM t1; + } {0} +} + finish_test diff --git a/test/whereE.test b/test/whereE.test index 31086b249..cd9f81d53 100644 --- a/test/whereE.test +++ b/test/whereE.test @@ -18,6 +18,12 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set ::testprefix whereE +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + do_execsql_test 1.1 { CREATE TABLE t1(a,b); INSERT INTO t1 VALUES(1,10), (2,20), (3,30), (2,22), (3, 33); diff --git a/test/window1.test b/test/window1.test index 886bf468f..84331d115 100644 --- a/test/window1.test +++ b/test/window1.test @@ -1267,13 +1267,15 @@ do_catchsql_test 31.3 { } {1 {frame ending offset must be a non-negative integer}} # 2019-11-16 chromium issue 1025467 -db close -sqlite3 db :memory: -do_catchsql_test 32.10 { - CREATE VIEW a AS SELECT NULL INTERSECT SELECT NULL ORDER BY s() OVER R; - CREATE TABLE a0 AS SELECT 0; - ALTER TABLE a0 RENAME TO S; -} {1 {error in view a: 1st ORDER BY term does not match any column in the result set}} +ifcapable altertable { + db close + sqlite3 db :memory: + do_catchsql_test 32.10 { + CREATE VIEW a AS SELECT NULL INTERSECT SELECT NULL ORDER BY s() OVER R; + CREATE TABLE a0 AS SELECT 0; + ALTER TABLE a0 RENAME TO S; + } {1 {error in view a: 1st ORDER BY term does not match any column in the result set}} +} reset_db do_execsql_test 33.1 { @@ -2175,5 +2177,31 @@ do_catchsql_test 69.2 { SELECT * FROM t1 WHERE b <= (SELECT b FROM t1 ORDER BY lead(b) OVER () AND sum(a)); } {1 {misuse of aggregate: sum()}} +# 2021-06-23 +# Forum https://sqlite.org/forum/forumpost/31e0432608 +# +reset_db +do_execsql_test 70.0 { + CREATE TABLE t1(a); +} +do_execsql_test 70.1 { + SELECT substr(a,4,lag(a,7) OVER(PARTITION BY 'cf23' ORDER BY 2)) AS ca0 FROM t1 ORDER BY ca0; +} +do_execsql_test 70.2 { + SELECT substr(a,4,lag(a,7) OVER(PARTITION BY 'cf23' ORDER BY likely(2))) AS ca0 FROM t1 ORDER BY ca0; +} + +# 2021-11-07 +# Bug report from Wang Ke +# https://sqlite.org/forum/forumpost/9ba4f60ff8 +reset_db +do_catchsql_test 71.0 { + CREATE TABLE t0(a); + SELECT a FROM t0, (SELECT a AS b FROM t0) + WHERE (a,1)=(SELECT 2,2 UNION SELECT sum(b),max(b) OVER(ORDER BY b) ORDER BY 2) + AND b=4 + ORDER BY b; +} {/1 {.*}/} + finish_test diff --git a/test/windowB.test b/test/windowB.test index 30380aee2..52221c445 100644 --- a/test/windowB.test +++ b/test/windowB.test @@ -9,6 +9,7 @@ # #*********************************************************************** # Test cases for RANGE BETWEEN and especially with NULLS LAST +# and for varying separator handling by group_concat(). # set testdir [file dirname $argv0] @@ -356,5 +357,12 @@ do_execsql_test 8.1 { FROM t1; } {111 660 938 979} +do_execsql_test 9.0 { + CREATE TABLE seps(x); + INSERT INTO seps(x) VALUES ('1'), ('22'), ('333'), ('4444'); + SELECT group_concat('-', x) + OVER ( ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING ) + FROM seps; +} {-22- -22-333- -333-4444- -4444-} finish_test diff --git a/test/windowC.test b/test/windowC.test new file mode 100644 index 000000000..013876f9b --- /dev/null +++ b/test/windowC.test @@ -0,0 +1,85 @@ +# 2021-09-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. +# +#*********************************************************************** +# Test cases for varying separator handling by group_concat(). +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix windowC + +ifcapable !windowfunc { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE x1(i INTEGER PRIMARY KEY, x); +} + +foreach {tn bBlob seps} { + 1 0 {a b c def g} + 2 0 {abcdefg {} {} abcdefg} + 3 0 {a bc def ghij klmno pqrstu} + 4 1 {a bc def ghij klmno pqrstu} + 5 1 {, , , , , , , , , , , , ....... , ,} +} { + foreach type {text blob} { + do_test 1.$type.$tn.1 { + execsql { DELETE FROM x1 } + foreach s $seps { + if {$type=="text"} { + execsql {INSERT INTO x1 VALUES(NULL, $s)} + } else { + execsql {INSERT INTO x1 VALUES(NULL, CAST ($s AS blob))} + } + } + } {} + + foreach {tn2 win} { + 1 "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING" + 2 "ROWS BETWEEN 2 PRECEDING AND CURRENT ROW" + 3 "ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING" + } { + do_test 1.$type.$tn.2.$tn2 { + db eval " + SELECT group_concat('val', x) OVER ( ORDER BY i $win ) AS val FROM x1 + " { + if {[string range $val 0 2]!="val" + || [string range $val end-2 end]!="val" + } { + error "unexpected return value: $val" + } + } + } {} + } + } +} + +# 2021-10-12 dbsqlfuzz 6c31db077a14149a7b22a1069294bdb068be8a96 +# +reset_db +do_execsql_test 2.0 { + PRAGMA encoding=UTF16le; + WITH separator(x) AS (VALUES(',a,'),(',bc,')), + value(y) AS (VALUES(1),(x'5585d09013455178cd11ce4a')) + SELECT group_concat(y,x) OVER (ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) + FROM separator, value; +} {{} 1 蕕郐䔓硑ᇍ䫎 1} +reset_db +do_execsql_test 2.1 { + PRAGMA encoding=UTF16be; + WITH separator(x) AS (VALUES(',a,'),(',bc,')), + value(y) AS (VALUES(1),(x'5585d09013455178cd11ce4a')) + SELECT group_concat(y,x) OVER (ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) + FROM separator, value; +} {{} 1 喅킐ፅ典촑칊 1} + +finish_test diff --git a/test/windowfault.test b/test/windowfault.test index b5d743311..5340ce08f 100644 --- a/test/windowfault.test +++ b/test/windowfault.test @@ -292,4 +292,43 @@ do_faultsim_test 12 -faults oom* -prep { faultsim_test_result {0 {}} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 13.0 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, a, b); + INSERT INTO t1 VALUES(1, '1', 'a'); + INSERT INTO t1 VALUES(2, '22', 'b'); + INSERT INTO t1 VALUES(3, '333', 'c'); + INSERT INTO t1 VALUES(4, '4444', 'dddd'); + INSERT INTO t1 VALUES(5, '55555', 'e'); + INSERT INTO t1 VALUES(6, '666666', 'f'); + INSERT INTO t1 VALUES(7, '7777777', 'gggggggggg'); +} {} + +set queryres [list {*}{ + 1b22 + 1b22c333 + 22c333dddd4444 + 333dddd4444e55555 + 4444e55555f666666 + 55555f666666gggggggggg7777777 + 666666gggggggggg7777777 +}] +do_execsql_test 13.1 { + SELECT group_concat(a, b) OVER ( + ORDER BY id RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING + ) FROM t1 +} $queryres + +do_faultsim_test 13 -faults oom* -prep { +} -body { + execsql { + SELECT group_concat(a, b) OVER ( + ORDER BY id RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING + ) FROM t1 + } +} -test { + faultsim_test_result [list 0 $::queryres] +} + finish_test diff --git a/test/with2.test b/test/with2.test index 02f808ea2..660df52b7 100644 --- a/test/with2.test +++ b/test/with2.test @@ -552,63 +552,77 @@ do_execsql_test 10.1 { # 2021-05-21 # Forum post https://sqlite.org/forum/forumpost/aa4a7a3980 # +ifcapable altertable { reset_db -do_execsql_test 11.1 { - CREATE TABLE t1(a); - CREATE VIEW v2(c) AS - WITH x AS ( - WITH y AS ( - WITH z AS(SELECT * FROM t1) - SELECT * FROM v2 - ) SELECT a - ) SELECT * from t1; - ALTER TABLE t1 RENAME COLUMN a TO b; - SELECT sql FROM sqlite_schema WHERE name='t1'; -} {{CREATE TABLE t1(b)}} -do_catchsql_test 11.2 { - INSERT INTO t1 VALUES(55); - SELECT * FROM v2; -} {0 55} -do_catchsql_test 11.3 { - DROP VIEW v2; - CREATE VIEW v2(c) AS - WITH x AS ( - WITH y AS ( - WITH z AS(SELECT * FROM t1) - SELECT * FROM v2 - ) SELECT a - ) SELECT * from t1, x; - SELECT * FROM v2; -} {1 {no such column: a}} -do_catchsql_test 11.4 { - DROP VIEW v2; - CREATE VIEW v2(c) AS - WITH x AS ( - WITH y AS ( - WITH z AS(SELECT * FROM t1) - SELECT * FROM v2 - ) SELECT * - ) SELECT * from t1, x; - SELECT * FROM v2; -} {1 {no tables specified}} -do_catchsql_test 11.5 { - WITH x AS ( - WITH y AS ( - WITH z AS(SELECT * FROM t1) - SELECT * FROM no_such_table - ) SELECT a - ) SELECT * from t1; -} {0 55} + do_execsql_test 11.1 { + CREATE TABLE t1(a); + CREATE VIEW v2(c) AS + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM v2 + ) SELECT a + ) SELECT * from t1; + ALTER TABLE t1 RENAME COLUMN a TO b; + SELECT sql FROM sqlite_schema WHERE name='t1'; + } {{CREATE TABLE t1(b)}} + do_catchsql_test 11.2 { + INSERT INTO t1 VALUES(55); + SELECT * FROM v2; + } {0 55} + do_catchsql_test 11.3 { + DROP VIEW v2; + CREATE VIEW v2(c) AS + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM v2 + ) SELECT a + ) SELECT * from t1, x; + SELECT * FROM v2; + } {1 {no such column: a}} + do_catchsql_test 11.4 { + DROP VIEW v2; + CREATE VIEW v2(c) AS + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM v2 + ) SELECT * + ) SELECT * from t1, x; + SELECT * FROM v2; + } {1 {no tables specified}} + do_catchsql_test 11.5 { + WITH x AS ( + WITH y AS ( + WITH z AS(SELECT * FROM t1) + SELECT * FROM no_such_table + ) SELECT a + ) SELECT * from t1; + } {0 55} +} # 2021-05-23 dbsqlfuzz 6b7a144674e215f06ddfeb9042c873d9ee956ac0 */ reset_db -do_execsql_test 12.1 { - CREATE TABLE t1(a); - INSERT INTO t1 VALUES(1),('hello'),(4.25),(NULL),(x'3c626c6f623e'); - CREATE VIEW v2(c) AS WITH x AS (WITH y AS (WITH z AS(SELECT * FROM t1) SELECT * FROM v2) SELECT a) SELECT * from t1; - CREATE VIEW v3(c) AS WITH x AS (WITH y AS (WITH z AS(SELECT * FROM v2) SELECT * FROM v3) SELECT a) SELECT * from t1; - ALTER TABLE t1 RENAME TO t1x; - SELECT quote(c) FROM v3; -} {1 'hello' 4.25 NULL X'3C626C6F623E'} +ifcapable altertable { + do_execsql_test 12.1 { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(1),('hello'),(4.25),(NULL),(x'3c626c6f623e'); + CREATE VIEW v2(c) AS WITH x AS (WITH y AS (WITH z AS(SELECT * FROM t1) SELECT * FROM v2) SELECT a) SELECT * from t1; + CREATE VIEW v3(c) AS WITH x AS (WITH y AS (WITH z AS(SELECT * FROM v2) SELECT * FROM v3) SELECT a) SELECT * from t1; + ALTER TABLE t1 RENAME TO t1x; + SELECT quote(c) FROM v3; + } {1 'hello' 4.25 NULL X'3C626C6F623E'} +} + +# 2021-08-11 https://sqlite.org/forum/forumpost/d496c3d29bc93736 +reset_db +do_execsql_test 13.1 { + WITH + t1(x) AS (SELECT 111), + t2(y) AS (SELECT 222), + t3(z) AS (SELECT * FROM t2 WHERE false UNION ALL SELECT * FROM t2) + SELECT * FROM t1, t3; +} {111 222} finish_test diff --git a/test/without_rowid1.test b/test/without_rowid1.test index e4e69eb72..119111552 100644 --- a/test/without_rowid1.test +++ b/test/without_rowid1.test @@ -38,6 +38,9 @@ integrity_check without_rowid1-1.0ic do_execsql_test_if_vtab without_rowid1-1.0ixi { SELECT name, key FROM pragma_index_xinfo('t1'); } {c 1 a 1 b 0 d 0} +do_execsql_test_if_vtab without_rowid1-1.0tl { + SELECT wr FROM pragma_table_list('t1'); +} {1} do_execsql_test without_rowid1-1.1 { SELECT *, '|' FROM t1 ORDER BY +c, a; @@ -455,15 +458,32 @@ do_execsql_test 13.10 { # 2021-05-13 https://sqlite.org/forum/forumpost/6c8960f545 reset_db -do_execsql_test 14.1 { - CREATE TABLE t1(a INT PRIMARY KEY) WITHOUT ROWID; - INSERT INTO t1(a) VALUES(10); - ALTER TABLE t1 ADD COLUMN b INT; - SELECT * FROM t1 WHERE a=20 OR (a=10 AND b=10); -} {} -do_execsql_test 14.2 { - CREATE TABLE dual AS SELECT 'X' AS dummy; - EXPLAIN QUERY PLAN SELECT * FROM dual, t1 WHERE a=10 AND b=10; -} {~/b=/} - +ifcapable altertable { + do_execsql_test 14.1 { + CREATE TABLE t1(a INT PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t1(a) VALUES(10); + ALTER TABLE t1 ADD COLUMN b INT; + SELECT * FROM t1 WHERE a=20 OR (a=10 AND b=10); + } {} + do_execsql_test 14.2 { + CREATE TABLE dual AS SELECT 'X' AS dummy; + EXPLAIN QUERY PLAN SELECT * FROM dual, t1 WHERE a=10 AND b=10; + } {~/b=/} +} + +# 2022-01-01 https://sqlite.org/forum/forumpost/b03d86f951 PoC #1 +# Omit an assert() from 2013 that no longer serves any purpose and +# is no longer always true. +# +reset_db +do_execsql_test 15.1 { + PRAGMA writable_schema=ON; + CREATE TABLE sqlite_sequence (name PRIMARY KEY) WITHOUT ROWID; + PRAGMA writable_schema=OFF; + CREATE TABLE c1(x); + INSERT INTO sqlite_sequence(name) VALUES('c0'),('c1'),('c2'); + ALTER TABLE c1 RENAME TO a; + SELECT name FROM sqlite_sequence ORDER BY +name; +} {a c0 c2} + finish_test diff --git a/test/without_rowid5.test b/test/without_rowid5.test index 31a440ad8..d1c494484 100644 --- a/test/without_rowid5.test +++ b/test/without_rowid5.test @@ -15,6 +15,10 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +ifcapable !incrblob { + finish_test + return +} # EVIDENCE-OF: R-36924-43758 By default, every row in SQLite has a # special column, usually called the "rowid", that uniquely identifies @@ -186,6 +190,87 @@ do_execsql_test without_rowid5-5.9 { SELECT count(*) FROM nnw; } {1} +# Ticket f2be158c57aaa8c6 (2021-08-18) +# NOT NULL ON CONFLICT clauses work on WITHOUT ROWID tables now. +# +do_test without_rowid5-5.100 { + db eval { + DROP TABLE IF EXISTS t5; + CREATE TABLE t5( + a INT NOT NULL ON CONFLICT ROLLBACK, + b TEXT, + c TEXT, + PRIMARY KEY(a,b) + ) WITHOUT ROWID; + BEGIN; + INSERT INTO t5(a,b,c) VALUES(1,2,3); + } + catch {db eval {INSERT INTO t5(a,b,c) VALUES(NULL,6,7);}} + db eval { + SELECT * FROM t5; + } +} {} +do_test without_rowid5-5.101 { + db eval { + DROP TABLE IF EXISTS t5; + CREATE TABLE t5( + a INT NOT NULL ON CONFLICT ABORT, + b TEXT, + c TEXT, + PRIMARY KEY(a,b) + ) WITHOUT ROWID; + BEGIN; + INSERT INTO t5(a,b,c) VALUES(1,2,3); + } + catch {db eval {INSERT INTO t5(a,b,c) VALUES(NULL,6,7);}} + db eval { + COMMIT; + SELECT * FROM t5; + } +} {1 2 3} +do_test without_rowid5-5.102 { + db eval { + DROP TABLE IF EXISTS t5; + CREATE TABLE t5( + a INT NOT NULL ON CONFLICT FAIL, + b TEXT, + c TEXT, + PRIMARY KEY(a,b) + ) WITHOUT ROWID; + } + catch {db eval {INSERT INTO t5(a,b,c) VALUES(1,2,3),(NULL,4,5),(6,7,8);}} + db eval { + SELECT * FROM t5; + } +} {1 2 3} +do_test without_rowid5-5.103 { + db eval { + DROP TABLE IF EXISTS t5; + CREATE TABLE t5( + a INT NOT NULL ON CONFLICT IGNORE, + b TEXT, + c TEXT, + PRIMARY KEY(a,b) + ) WITHOUT ROWID; + INSERT INTO t5(a,b,c) VALUES(1,2,3),(NULL,4,5),(6,7,8); + SELECT * FROM t5; + } +} {1 2 3 6 7 8} +do_test without_rowid5-5.104 { + db eval { + DROP TABLE IF EXISTS t5; + CREATE TABLE t5( + a INT NOT NULL ON CONFLICT REPLACE DEFAULT 3, + b TEXT, + c TEXT, + PRIMARY KEY(a,b) + ) WITHOUT ROWID; + INSERT INTO t5(a,b,c) VALUES(1,2,3),(NULL,4,5),(6,7,8); + SELECT * FROM t5; + } +} {1 2 3 3 4 5 6 7 8} + + # EVIDENCE-OF: R-12643-30541 The incremental blob I/O mechanism does not # work for WITHOUT ROWID tables. # diff --git a/test/zeroblob.test b/test/zeroblob.test index 9daa7d8d9..df234eea4 100644 --- a/test/zeroblob.test +++ b/test/zeroblob.test @@ -19,10 +19,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix zeroblob -ifcapable !incrblob { - finish_test - return -} +# ifcapable !incrblob { finish_test return } test_set_config_pagecache 0 0 @@ -42,12 +39,17 @@ do_test zeroblob-1.1 { execsql { INSERT INTO t1 VALUES(2,3,4,zeroblob(1000000)); } - set ::sqlite3_max_blobsize -} {10} +} {} + +ifcapable incrblob { + do_test zeroblob-1.1.1 { + set ::sqlite3_max_blobsize + } {10} + do_test zeroblob-1.1.2 { + expr {[sqlite3_memory_highwater]<$::memused+35000} + } {1} +} -do_test zeroblob-1.1.1 { - expr {[sqlite3_memory_highwater]<$::memused+35000} -} {1} do_test zeroblob-1.2 { execsql { SELECT length(d) FROM t1 @@ -78,8 +80,12 @@ do_test zeroblob-1.5 { execsql { INSERT INTO t1 VALUES(4,5,zeroblob(10000),zeroblob(10000)); } - set ::sqlite3_max_blobsize -} {11} +} {} +ifcapable incrblob { + do_test zeroblob-1.5.1 { + set ::sqlite3_max_blobsize + } {11} +} do_test zeroblob-1.6 { execsql { SELECT length(c), length(d) FROM t1 @@ -94,8 +100,12 @@ do_test zeroblob-1.7 { execsql { INSERT INTO t1 VALUES(5,zeroblob(10000),NULL,zeroblob(10000)); } - set ::sqlite3_max_blobsize -} {10} +} {} +ifcapable incrblob { + do_test zeroblob-1.7.1 { + set ::sqlite3_max_blobsize + } {10} +} do_test zeroblob-1.8 { execsql { SELECT length(b), length(d) FROM t1 WHERE a=5 @@ -214,12 +224,14 @@ do_test zeroblob-7.2 { do_test zeroblob-7.3 { sqlite3_finalize $::STMT } {SQLITE_OK} -do_test zeroblob-7.4 { - set ::sqlite3_max_blobsize -} {0} -do_test zeroblob-7.5 { - expr {[sqlite3_memory_highwater]<$::memused+10000} -} {1} +ifcapable incrblob { + do_test zeroblob-7.4 { + set ::sqlite3_max_blobsize + } {0} + do_test zeroblob-7.5 { + expr {[sqlite3_memory_highwater]<$::memused+10000} + } {1} +} # Test that MakeRecord can handle a value with some real content # and a zero-blob tail. diff --git a/test/zeroblobfault.test b/test/zeroblobfault.test new file mode 100644 index 000000000..0ae5beae0 --- /dev/null +++ b/test/zeroblobfault.test @@ -0,0 +1,28 @@ +# 2021 November 8 +# +# 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 zeroblobfault +set quoted_res [db one { SELECT quote(zeroblob(2000)) }] + +do_faultsim_test 1 -prep { + sqlite3 db test.db +} -body { + execsql { SELECT quote(zeroblob(2000)) } +} -test { + faultsim_test_result [list 0 $::quoted_res] +} + +finish_test diff --git a/tool/lemon.c b/tool/lemon.c index 75fc7aa2f..d4e157a1c 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -917,8 +917,11 @@ void FindStates(struct lemon *lemp) lemp->errorcnt++; sp = lemp->startRule->lhs; } - }else{ + }else if( lemp->startRule ){ sp = lemp->startRule->lhs; + }else{ + ErrorMsg(lemp->filename,0,"Internal error - no start rule\n"); + exit(1); } /* Make sure the start symbol doesn't occur on the right-hand side of @@ -1083,7 +1086,7 @@ void FindLinks(struct lemon *lemp) ** which the link is attached. */ for(i=0; instate; i++){ stp = lemp->sorted[i]; - for(cfp=stp->cfp; cfp; cfp=cfp->next){ + for(cfp=stp?stp->cfp:0; cfp; cfp=cfp->next){ cfp->stp = stp; } } @@ -1092,7 +1095,7 @@ void FindLinks(struct lemon *lemp) ** links are used in the follow-set computation. */ for(i=0; instate; i++){ stp = lemp->sorted[i]; - for(cfp=stp->cfp; cfp; cfp=cfp->next){ + for(cfp=stp?stp->cfp:0; cfp; cfp=cfp->next){ for(plp=cfp->bplp; plp; plp=plp->next){ other = plp->cfp; Plink_add(&other->fplp,cfp); @@ -1115,6 +1118,7 @@ void FindFollowSets(struct lemon *lemp) int change; for(i=0; instate; i++){ + assert( lemp->sorted[i]!=0 ); for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ cfp->status = INCOMPLETE; } @@ -1123,6 +1127,7 @@ void FindFollowSets(struct lemon *lemp) do{ progress = 0; for(i=0; instate; i++){ + assert( lemp->sorted[i]!=0 ); for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ if( cfp->status==COMPLETE ) continue; for(plp=cfp->fplp; plp; plp=plp->next){ @@ -1172,7 +1177,14 @@ void FindActions(struct lemon *lemp) /* Add the accepting token */ if( lemp->start ){ sp = Symbol_find(lemp->start); - if( sp==0 ) sp = lemp->startRule->lhs; + if( sp==0 ){ + if( lemp->startRule==0 ){ + fprintf(stderr, "internal error on source line %d: no start rule\n", + __LINE__); + exit(1); + } + sp = lemp->startRule->lhs; + } }else{ sp = lemp->startRule->lhs; } @@ -1299,21 +1311,7 @@ static struct config **basisend = 0; /* End of list of basis configs */ /* Return a pointer to a new configuration */ PRIVATE struct config *newconfig(void){ - struct config *newcfg; - if( freelist==0 ){ - int i; - int amt = 3; - freelist = (struct config *)calloc( amt, sizeof(struct config) ); - if( freelist==0 ){ - fprintf(stderr,"Unable to allocate memory for a new configuration."); - exit(1); - } - for(i=0; inext; - return newcfg; + return (struct config*)calloc(1, sizeof(struct config)); } /* The configuration "old" is no longer used */ @@ -1932,8 +1930,12 @@ static FILE *errstream; static void errline(int n, int k, FILE *err) { int spcnt, i; - if( g_argv[0] ) fprintf(err,"%s",g_argv[0]); - spcnt = lemonStrlen(g_argv[0]) + 1; + if( g_argv[0] ){ + fprintf(err,"%s",g_argv[0]); + spcnt = lemonStrlen(g_argv[0]) + 1; + }else{ + spcnt = 0; + } for(i=1; isp->index>=lemp->nterminal ){ + if( ap->sp->index>=lemp->nterminal + && (lemp->errsym==0 || ap->sp->index!=lemp->errsym->index) + ){ act = lemp->minReduce + ap->x.rp->iRule; }else{ act = lemp->minShiftReduce + ap->x.rp->iRule; @@ -4090,7 +4094,7 @@ void print_stack_union( int *plineno, /* Pointer to the line number */ int mhflag /* True if generating makeheaders output */ ){ - int lineno = *plineno; /* The line number of the output */ + int lineno; /* The line number of the output */ char **types; /* A hash table of datatypes */ int arraysize; /* Size of the "types" array */ int maxdtlength; /* Maximum length of any ".datatype" field. */ @@ -5337,7 +5341,8 @@ int Strsafe_insert(const char *data) newnp->from = &(array.ht[h]); array.ht[h] = newnp; } - free(x1a->tbl); + /* free(x1a->tbl); // This program was originally for 16-bit machines. + ** Don't worry about freeing memory on modern platforms. */ *x1a = array; } /* Insert the new data */ @@ -5505,7 +5510,9 @@ int Symbol_insert(struct symbol *data, const char *key) newnp->from = &(array.ht[h]); array.ht[h] = newnp; } - free(x2a->tbl); + /* free(x2a->tbl); // This program was originally written for 16-bit + ** machines. Don't worry about freeing this trivial amount of memory + ** on modern platforms. Just leak it. */ *x2a = array; } /* Insert the new data */ @@ -5841,7 +5848,9 @@ int Configtable_insert(struct config *data) newnp->from = &(array.ht[h]); array.ht[h] = newnp; } - free(x4a->tbl); + /* free(x4a->tbl); // This code was originall written for 16-bit machines. + ** on modern machines, don't worry about freeing this trival amount of + ** memory. */ *x4a = array; } /* Insert the new data */ diff --git a/tool/lempar.c b/tool/lempar.c index 35c3768bb..8cc57897d 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -223,9 +223,9 @@ struct yyParser { }; typedef struct yyParser yyParser; +#include #ifndef NDEBUG #include -#include static FILE *yyTraceFILE = 0; static char *yyTracePrompt = 0; #endif /* NDEBUG */ @@ -882,8 +882,8 @@ void Parse( yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); if( yyact >= YY_MIN_REDUCE ){ unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */ - assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); #ifndef NDEBUG + assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); if( yyTraceFILE ){ int yysize = yyRuleInfoNRhs[yyruleno]; if( yysize ){ @@ -981,14 +981,13 @@ void Parse( yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); yymajor = YYNOCODE; }else{ - while( yypParser->yytos >= yypParser->yystack - && (yyact = yy_find_reduce_action( - yypParser->yytos->stateno, - YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE - ){ + while( yypParser->yytos > yypParser->yystack ){ + yyact = yy_find_reduce_action(yypParser->yytos->stateno, + YYERRORSYMBOL); + if( yyact<=YY_MAX_SHIFTREDUCE ) break; yy_pop_parser_stack(yypParser); } - if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ + if( yypParser->yytos <= yypParser->yystack || yymajor==0 ){ yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); yy_parse_failed(yypParser); #ifndef YYNOERRORRECOVERY diff --git a/tool/logest.c b/tool/logest.c index e936e02cb..8ed0b19ec 100644 --- a/tool/logest.c +++ b/tool/logest.c @@ -75,6 +75,9 @@ static sqlite3_uint64 logEstToInt(LogEst x){ x /= 10; if( n>=5 ) n -= 2; else if( n>=1 ) n -= 1; + if( x>60 ){ + return (((sqlite3_uint64)0xffffffff)<<32)+(sqlite3_uint64)0xffffffff; + } if( x>=3 ) return (n+8)<<(x-3); return (n+8)>>(3-x); } diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl index 2f15ac9fb..629460945 100644 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -120,7 +120,6 @@ set boolean_defnil_options { SQLITE_MUTEX_PTHREADS SQLITE_MUTEX_W32 SQLITE_NEED_ERR_NAME - SQLITE_NOINLINE SQLITE_NO_SYNC SQLITE_OMIT_ALTERTABLE SQLITE_OMIT_ANALYZE @@ -216,6 +215,7 @@ set value2_options { # and is a single scalar. # set value_options { + SQLITE_ATOMIC_INTRINSICS SQLITE_BITMASK_TYPE SQLITE_DEFAULT_CACHE_SIZE SQLITE_DEFAULT_FILE_FORMAT @@ -330,7 +330,7 @@ foreach name_defval $boolean_defnnz_options { foreach b $boolean_defnil_options { set name [trim_name $b] set options($name) [subst { -#if $b +#ifdef $b "$name", #endif }] diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c index bbb0ccf29..fe25b5abc 100644 --- a/tool/mkkeywordhash.c +++ b/tool/mkkeywordhash.c @@ -52,7 +52,7 @@ struct Keyword { /* ** Define masks used to determine which keywords are allowed */ -#ifdef SQLITE_OMIT_ALTERTABLE +#if defined(SQLITE_OMIT_ALTERTABLE) || defined(SQLITE_OMIT_VIRTUALTABLE) # define ALTER 0 #else # define ALTER 0x00000001 @@ -513,6 +513,7 @@ int main(int argc, char **argv){ bestSize = nKeyword; bestCount = nKeyword*nKeyword; for(i=nKeyword/2; i<=2*nKeyword; i++){ + if( i<=0 ) continue; for(j=0; j

      SQLITE_STMTSTATUS_FILTER_HIT
      +** SQLITE_STMTSTATUS_FILTER_MISS
      +**
      ^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal. +** ** [[SQLITE_STMTSTATUS_MEMUSED]]
      SQLITE_STMTSTATUS_MEMUSED
      **
      ^This is the approximate number of bytes of heap memory ** used to store the prepared statement. ^This value is not actually @@ -8481,6 +8543,8 @@ int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_VM_STEP 4 #define SQLITE_STMTSTATUS_REPREPARE 5 #define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 #define SQLITE_STMTSTATUS_MEMUSED 99 /* @@ -9449,19 +9513,269 @@ int sqlite3_vtab_nochange(sqlite3_context*); /* ** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info ** ** This function may only be called from within a call to the [xBestIndex] -** method of a [virtual table]. +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. +** +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. +** +** The return value is computed as follows: ** -** The first argument must be the sqlite3_index_info object that is the -** first parameter to the xBestIndex() method. The second argument must be -** an index into the aConstraint[] array belonging to the sqlite3_index_info -** structure passed to xBestIndex. This function returns a pointer to a buffer -** containing the name of the collation sequence for the corresponding -** constraint. +**
        +**
      1. If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +**

      2. If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +**

      3. Otherwise, "BINARY" is returned. +**

      */ SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer that is +** either 0, 1, or 2. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +**
      1. +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +**

      2. +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +**

      3. +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all "aOrderBy" columns +** are adjacent.)^ ^(Furthermore, only a single row for each particular +** combination of values in the columns identified by the "aOrderBy" field +** needs to be returned.)^ ^It is always ok for two or more rows with the same +** values in all "aOrderBy" columns to be returned, as long as all such rows +** are adjacent. ^The virtual table may, if it chooses, omit extra rows +** that have the same value for all columns identified by "aOrderBy". +** ^However omitting the extra rows is optional. +** This mode is used for a DISTINCT query. +**

      +** +** ^For the purposes of comparing virtual table output values to see if the +** values are same value for sorting purposes, two NULL values are considered +** to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** the "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +**
        +**
      1. +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +**

      2. +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +**

      +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +**
        +**
      1. The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +**

      2. The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +**

      )^ +** +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. +*/ +int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) must be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing use the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_MISUSE])^ or perhaps +** exhibit some other undefined or harmful behavior. +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +**
      +**    for(rc=sqlite3_vtab_in_first(pList, &pVal);
      +**        rc==SQLITE_OK && pVal
      +**        rc=sqlite3_vtab_in_next(pList, &pVal)
      +**    ){
      +**      // do something with pVal
      +**    }
      +**    if( rc!=SQLITE_OK ){
      +**      // an error has occurred
      +**    }
      +** 
      )^ +** +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. +*/ +int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); + /* ** CAPI3REF: Conflict resolution modes ** KEYWORDS: {conflict resolution mode} diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 9767daa01..2eac4f3f0 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -344,6 +344,13 @@ struct sqlite3_api_routines { int (*autovacuum_pages)(sqlite3*, unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), void*, void(*)(void*)); + /* Version 3.38.0 and later */ + int (*error_offset)(sqlite3*); + int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**); + int (*vtab_distinct)(sqlite3_index_info*); + int (*vtab_in)(sqlite3_index_info*,int,int); + int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); + int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); }; /* @@ -655,6 +662,13 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_total_changes64 sqlite3_api->total_changes64 /* Version 3.37.0 and later */ #define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages +/* Version 3.38.0 and later */ +#define sqlite3_error_offset sqlite3_api->error_offset +#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value +#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct +#define sqlite3_vtab_in sqlite3_api->vtab_in +#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first +#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 12534c6f2..3f11f1571 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1231,10 +1231,11 @@ typedef struct With With; /* ** A bit in a Bitmask */ -#define MASKBIT(n) (((Bitmask)1)<<(n)) -#define MASKBIT64(n) (((u64)1)<<(n)) -#define MASKBIT32(n) (((unsigned int)1)<<(n)) -#define ALLBITS ((Bitmask)-1) +#define MASKBIT(n) (((Bitmask)1)<<(n)) +#define MASKBIT64(n) (((u64)1)<<(n)) +#define MASKBIT32(n) (((unsigned int)1)<<(n)) +#define SMASKBIT32(n) ((n)<=31?((unsigned int)1)<<(n):0) +#define ALLBITS ((Bitmask)-1) /* A VList object records a mapping between parameters/variables/wildcards ** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer @@ -1527,6 +1528,7 @@ struct sqlite3 { u32 nSchemaLock; /* Do not reset the schema when non-zero */ unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ int errCode; /* Most recent error code (SQLITE_*) */ + int errByteOffset; /* Byte offset of error in SQL statement */ int errMask; /* & result codes with this before returning */ int iSysErrno; /* Errno value from last system error */ u32 dbOptFlags; /* Flags to enable/disable optimizations */ @@ -1761,6 +1763,9 @@ struct sqlite3 { #define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */ #define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */ /* TH3 expects this value ^^^^^^^^^^ to be 0x40000. Coordinate any change */ +#define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */ +#define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */ +#define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -1934,7 +1939,7 @@ struct FuncDestructor { ** are interpreted in the same way as the first 4 parameters to ** FUNCTION(). ** -** WFUNCTION(zName, nArg, iArg, xStep, xFinal, xValue, xInverse) +** WAGGREGATE(zName, nArg, iArg, xStep, xFinal, xValue, xInverse) ** Used to create an aggregate function definition implemented by ** the C functions xStep and xFinal. The first four parameters ** are interpreted in the same way as the first 4 parameters to @@ -1961,6 +1966,10 @@ struct FuncDestructor { #define MFUNCTION(zName, nArg, xPtr, xFunc) \ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } +#define JFUNCTION(zName, nArg, iArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ {nArg, SQLITE_FUNC_BUILTIN|\ SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ @@ -2815,7 +2824,10 @@ struct Expr { ** TK_VARIABLE: variable number (always >= 1). ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ - int iRightJoinTable; /* If EP_FromJoin, the right table of the join */ + union { + int iRightJoinTable; /* If EP_FromJoin, the right table of the join */ + int iOfst; /* else: start of token from start of statement */ + } w; AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ union { Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL @@ -3577,6 +3589,7 @@ struct Parse { **************************************************************************/ int aTempReg[8]; /* Holding area for temporary registers */ + Parse *pOuterParse; /* Outer Parse object when nested */ Token sNameToken; /* Token with unqualified schema object name */ /************************************************************************ @@ -3627,7 +3640,8 @@ struct Parse { /* ** Sizes and pointers of various parts of the Parse object. */ -#define PARSE_HDR_SZ offsetof(Parse,aTempReg) /* Recursive part w/o aColCache*/ +#define PARSE_HDR(X) (((char*)(X))+offsetof(Parse,zErrMsg)) +#define PARSE_HDR_SZ (offsetof(Parse,aTempReg)-offsetof(Parse,zErrMsg)) /* Recursive part w/o aColCache*/ #define PARSE_RECURSE_SZ offsetof(Parse,sLastToken) /* Recursive part */ #define PARSE_TAIL_SZ (sizeof(Parse)-PARSE_RECURSE_SZ) /* Non-recursive part */ #define PARSE_TAIL(X) (((char*)(X))+PARSE_RECURSE_SZ) /* Pointer to tail */ @@ -3922,6 +3936,7 @@ struct Sqlite3Config { int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ #endif int bLocaltimeFault; /* True to fail localtime() calls */ + int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ int iOnceResetThreshold; /* When to reset OP_Once counters */ u32 szSorterRef; /* Min size in bytes to use sorter-refs */ unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */ @@ -4402,7 +4417,7 @@ void sqlite3DequoteExpr(Expr*); void sqlite3DequoteToken(Token*); void sqlite3TokenInit(Token*,char*); int sqlite3KeywordCode(const unsigned char*, int); -int sqlite3RunParser(Parse*, const char*, char **); +int sqlite3RunParser(Parse*, const char*); void sqlite3FinishCoding(Parse*); int sqlite3GetTempReg(Parse*); void sqlite3ReleaseTempReg(Parse*,int); @@ -4563,10 +4578,12 @@ void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*); #endif +void sqlite3CodeChangeCount(Vdbe*,int,const char*); void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*); void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*, Upsert*); -WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int); +WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*, + ExprList*,Select*,u16,int); void sqlite3WhereEnd(WhereInfo*); LogEst sqlite3WhereOutputRowCount(WhereInfo*); int sqlite3WhereIsDistinct(WhereInfo*); @@ -4681,9 +4698,14 @@ Select *sqlite3SelectDup(sqlite3*,const Select*,int); FuncDef *sqlite3FunctionSearch(int,const char*); void sqlite3InsertBuiltinFuncs(FuncDef*,int); FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8); +void sqlite3QuoteValue(StrAccum*,sqlite3_value*); void sqlite3RegisterBuiltinFunctions(void); void sqlite3RegisterDateTimeFunctions(void); +void sqlite3RegisterJsonFunctions(void); void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) + int sqlite3JsonTableFunctions(sqlite3*); +#endif int sqlite3SafetyCheckOk(sqlite3*); int sqlite3SafetyCheckSickOrOk(sqlite3*); void sqlite3ChangeCookie(Parse*, int); @@ -4773,14 +4795,8 @@ int sqlite3Utf8CharLen(const char *pData, int nByte); u32 sqlite3Utf8Read(const u8**); LogEst sqlite3LogEst(u64); LogEst sqlite3LogEstAdd(LogEst,LogEst); -#ifndef SQLITE_OMIT_VIRTUALTABLE LogEst sqlite3LogEstFromDouble(double); -#endif -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_ENABLE_STAT4) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) u64 sqlite3LogEstToInt(LogEst); -#endif VList *sqlite3VListAdd(sqlite3*,VList*,const char*,int,int); const char *sqlite3VListNumToName(VList*,int); int sqlite3VListNameToNum(VList*,const char*,int); @@ -4967,17 +4983,20 @@ int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, FuncDestructor *pDestructor ); void sqlite3NoopDestructor(void*); -void sqlite3OomFault(sqlite3*); +void *sqlite3OomFault(sqlite3*); void sqlite3OomClear(sqlite3*); int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); +int sqlite3StrAccumEnlarge(StrAccum*, int); char *sqlite3StrAccumFinish(StrAccum*); void sqlite3StrAccumSetError(StrAccum*, u8); void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); void sqlite3SelectDestInit(SelectDest*,int,int); Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); +void sqlite3RecordErrorByteOffset(sqlite3*,const char*); +void sqlite3RecordErrorOffsetOfExpr(sqlite3*,const Expr*); void sqlite3BackupRestart(sqlite3_backup *); void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *); @@ -5082,11 +5101,17 @@ int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **); int sqlite3VtabCallConnect(Parse*, Table*); int sqlite3VtabCallDestroy(sqlite3*, int, const char *); int sqlite3VtabBegin(sqlite3 *, VTable *); + FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); +#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ + && !defined(SQLITE_OMIT_VIRTUALTABLE) + void sqlite3VtabWriteAll(sqlite3_index_info*); +#endif sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); -void sqlite3ParserReset(Parse*); +void sqlite3ParseObjectInit(Parse*,sqlite3*); +void sqlite3ParseObjectReset(Parse*); void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*); #ifdef SQLITE_ENABLE_NORMALIZE char *sqlite3Normalize(Vdbe*, const char*); diff --git a/src/tclsqlite.c b/src/tclsqlite.c index d379f8d5a..8c184809f 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -1943,15 +1943,16 @@ static int SQLITE_TCLAPI DbObjCmd( "close", "collate", "collation_needed", "commit_hook", "complete", "config", "copy", "deserialize", "enable_load_extension", - "errorcode", "eval", "exists", - "function", "incrblob", "interrupt", - "last_insert_rowid", "nullvalue", "onecolumn", - "preupdate", "profile", "progress", - "rekey", "restore", "rollback_hook", - "serialize", "status", "timeout", - "total_changes", "trace", "trace_v2", - "transaction", "unlock_notify", "update_hook", - "version", "wal_hook", 0 + "errorcode", "erroroffset", "eval", + "exists", "function", "incrblob", + "interrupt", "last_insert_rowid", "nullvalue", + "onecolumn", "preupdate", "profile", + "progress", "rekey", "restore", + "rollback_hook", "serialize", "status", + "timeout", "total_changes", "trace", + "trace_v2", "transaction", "unlock_notify", + "update_hook", "version", "wal_hook", + 0 }; enum DB_enum { DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK, @@ -1959,15 +1960,15 @@ static int SQLITE_TCLAPI DbObjCmd( DB_CLOSE, DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK, DB_COMPLETE, DB_CONFIG, DB_COPY, DB_DESERIALIZE, DB_ENABLE_LOAD_EXTENSION, - DB_ERRORCODE, DB_EVAL, DB_EXISTS, - DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT, - DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN, - DB_PREUPDATE, DB_PROFILE, DB_PROGRESS, - DB_REKEY, DB_RESTORE, DB_ROLLBACK_HOOK, - DB_SERIALIZE, DB_STATUS, DB_TIMEOUT, - DB_TOTAL_CHANGES, DB_TRACE, DB_TRACE_V2, - DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, - DB_VERSION, DB_WAL_HOOK + DB_ERRORCODE, DB_ERROROFFSET, DB_EVAL, + DB_EXISTS, DB_FUNCTION, DB_INCRBLOB, + DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_NULLVALUE, + DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE, + DB_PROGRESS, DB_REKEY, DB_RESTORE, + DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS, + DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE, + DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY, + DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK, }; /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */ @@ -2730,6 +2731,17 @@ static int SQLITE_TCLAPI DbObjCmd( break; } + /* + ** $db erroroffset + ** + ** Return the numeric error code that was returned by the most recent + ** call to sqlite3_exec(). + */ + case DB_ERROROFFSET: { + Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_error_offset(pDb->db))); + break; + } + /* ** $db exists $sql ** $db onecolumn $sql diff --git a/src/test1.c b/src/test1.c index 6060290b9..455c4b257 100644 --- a/src/test1.c +++ b/src/test1.c @@ -1097,7 +1097,7 @@ static int SQLITE_TCLAPI test_drop_modules( ){ sqlite3 *db; - if( argc!=2 ){ + if( argc<2 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB\"", 0); return TCL_ERROR; @@ -3977,6 +3977,102 @@ static int SQLITE_TCLAPI test_bind_blob( return TCL_OK; } +/* +** Usage: sqlite3_bind_value_from_preupdate STMT N NEW|OLD IDX +** +** Test the sqlite3_bind_value interface using sqlite3_value objects +** obtained from either sqlite3_preupdate_new() (if arg[3]=="new") or +** sqlite3_preupdate_old() if (arg[3]=="old"). IDX is the index to +** pass to the sqlite3_preupdate_xxx() function. +*/ +static int SQLITE_TCLAPI test_bind_value_from_preupdate( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_stmt *pStmt; + int idx; + int bidx; + const char *z3 = 0; + sqlite3 *db = 0; + sqlite3_value *pVal = 0; + + if( objc!=5 ){ + Tcl_WrongNumArgs(interp, 1, objv, "STMT N NEW|OLD IDX"); + return TCL_ERROR; + } + + if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR; + z3 = Tcl_GetString(objv[3]); + if( Tcl_GetIntFromObj(interp, objv[4], &bidx) ) return TCL_ERROR; + db = sqlite3_db_handle(pStmt); + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( z3[0]=='n' ){ + sqlite3_preupdate_new(db, bidx, &pVal); + }else if( z3[0]=='o' ){ + sqlite3_preupdate_old(db, bidx, &pVal); + }else{ + Tcl_AppendResult(interp, "expected new or old, got: ", z3, (char*)0); + return TCL_ERROR; + } + sqlite3_bind_value(pStmt, idx, pVal); +#endif + + return TCL_OK; +} + +/* +** Usage: sqlite3_bind_value_from_select STMT N SELECT +** +** Test the sqlite3_bind_value interface. STMT is a prepared statement. +** N is the index of a wildcard in the prepared statement. +*/ +static int SQLITE_TCLAPI test_bind_value_from_select( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_stmt *pStmt; + sqlite3_stmt *pStmt2; + int idx; + const char *zSql = 0; + sqlite3 *db = 0; + int rc = SQLITE_OK; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "STMT N SELECT"); + return TCL_ERROR; + } + + if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR; + zSql = Tcl_GetString(objv[3]); + db = sqlite3_db_handle(pStmt); + + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt2, 0); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, "error in SQL: ", sqlite3_errmsg(db), (char*)0); + return TCL_ERROR; + } + if( sqlite3_step(pStmt2)==SQLITE_ROW ){ + sqlite3_value *pVal = sqlite3_column_value(pStmt2, 0); + sqlite3_bind_value(pStmt, idx, pVal); + } + rc = sqlite3_finalize(pStmt2); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, + "error runnning SQL: ", sqlite3_errmsg(db), (char*)0 + ); + return TCL_ERROR; + } + + return TCL_OK; +} + #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -4366,6 +4462,34 @@ static int SQLITE_TCLAPI test_errmsg( return TCL_OK; } + +/* +** Usage: sqlite3_error_offset DB +** +** Return the byte offset into the input UTF8 SQL for the most recent +** error, or -1 of the error does not refer to a specific token. +*/ +static int SQLITE_TCLAPI test_error_offset( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3 *db; + int iByteOffset; + + if( objc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetString(objv[0]), " DB", 0); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + + iByteOffset = sqlite3_error_offset(db); + Tcl_SetObjResult(interp, Tcl_NewIntObj(iByteOffset)); + return TCL_OK; +} + /* ** Usage: test_errmsg16 DB ** @@ -7083,6 +7207,55 @@ static int SQLITE_TCLAPI test_print_eqp( } #endif /* SQLITE_OMIT_EXPLAIN */ +#include +/* +** This is an alternative localtime_r() implementation used for testing +** the 'localtime' and 'utc' modifiers of date-time functions. Because +** the OS-supplied localtime_r() is locale-dependent, this alternative is +** provided as a stable test platform. +** +** Operation: +** +** (1) Localtime is 30 minutes earlier than (west of) UTC on +** even days (counting from 1970-01-01) +** +** (2) Localtime is 30 minutes later than (east of) UTC on odd days. +** +** (3) The function fails for the specific date/time value +** of 2000-05-29 14:16:00 in order to test the ability of +** SQLite to deal with localtime_r() failures. +*/ +static int testLocaltime(const void *aliasT, void *aliasTM){ + const time_t t = *(const time_t*)aliasT; + struct tm *pTm = (struct tm *)aliasTM; + time_t altT; + sqlite3_int64 iJD; + int Z, A, B, C, D, E, X1, S; + + if( (t/86400) & 1 ){ + altT = t + 1800; /* 30 minutes later on odd days */ + }else{ + altT = t - 1800; /* 30 minutes earlier on even days */ + } + iJD = (sqlite3_int64)(altT + 210866760000); + Z = (int)((iJD + 43200)/86400); + A = (int)((Z - 1867216.25)/36524.25); + A = Z + 1 + A - (A/4); + B = A + 1524; + C = (int)((B - 122.1)/365.25); + D = (36525*(C&32767))/100; + E = (int)((B-D)/30.6001); + X1 = (int)(30.6001*E); + pTm->tm_mday = B - D - X1; + pTm->tm_mon = E<14 ? E-2 : E-14; + pTm->tm_year = (pTm->tm_mon>1 ? C - 4716 : C - 4715) - 1900; + S = (int)((iJD + 43200)%86400); + pTm->tm_hour = S/3600; + pTm->tm_min = (S/60)%60; + pTm->tm_sec = S % 60; + return t==959609760; /* Special case: 2000-05-29 14:16:00 fails */ +} + /* ** sqlite3_test_control VERB ARGS... */ @@ -7130,11 +7303,11 @@ static int SQLITE_TCLAPI test_test_control( case SQLITE_TESTCTRL_LOCALTIME_FAULT: { int val; if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 2, objv, "ONOFF"); + Tcl_WrongNumArgs(interp, 2, objv, "0|1|2"); return TCL_ERROR; } - if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR; - sqlite3_test_control(iFlag, val); + if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR; + sqlite3_test_control(iFlag, val, testLocaltime); break; } @@ -7484,6 +7657,7 @@ static int SQLITE_TCLAPI optimization_control( { "stat4", SQLITE_Stat4 }, { "skip-scan", SQLITE_SkipScan }, { "push-down", SQLITE_PushDown }, + { "balanced-merge", SQLITE_BalancedMerge }, }; if( objc!=4 ){ @@ -7539,6 +7713,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( #ifndef SQLITE_OMIT_VIRTUALTABLE extern int sqlite3_prefixes_init(sqlite3*,char**,const sqlite3_api_routines*); #endif + extern int sqlite3_qpvtab_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_regexp_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_remember_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_series_init(sqlite3*,char**,const sqlite3_api_routines*); @@ -7569,6 +7744,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( #ifndef SQLITE_OMIT_VIRTUALTABLE { "prefixes", sqlite3_prefixes_init }, #endif + { "qpvtab", sqlite3_qpvtab_init }, { "regexp", sqlite3_regexp_init }, { "remember", sqlite3_remember_init }, { "series", sqlite3_series_init }, @@ -8411,6 +8587,8 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_bind_text", test_bind_text ,0 }, { "sqlite3_bind_text16", test_bind_text16 ,0 }, { "sqlite3_bind_blob", test_bind_blob ,0 }, + { "sqlite3_bind_value_from_select",test_bind_value_from_select ,0 }, + { "sqlite3_bind_value_from_preupdate",test_bind_value_from_preupdate ,0 }, #ifndef SQLITE_OMIT_VIRTUALTABLE { "sqlite3_carray_bind", test_carray_bind ,0 }, #endif @@ -8422,6 +8600,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_errcode", test_errcode ,0 }, { "sqlite3_extended_errcode", test_ex_errcode ,0 }, { "sqlite3_errmsg", test_errmsg ,0 }, + { "sqlite3_error_offset", test_error_offset ,0 }, { "sqlite3_errmsg16", test_errmsg16 ,0 }, { "sqlite3_open", test_open ,0 }, { "sqlite3_open16", test_open16 ,0 }, diff --git a/src/test_bestindex.c b/src/test_bestindex.c index 2cd79baf2..67a0c8258 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -299,7 +299,21 @@ static int tclFilter( const char *zVal = (const char*)sqlite3_value_text(argv[ii]); Tcl_Obj *pVal; if( zVal==0 ){ + sqlite3_value *pMem; pVal = Tcl_NewObj(); + for(rc=sqlite3_vtab_in_first(argv[ii], &pMem); + rc==SQLITE_OK && pMem; + rc=sqlite3_vtab_in_next(argv[ii], &pMem) + ){ + Tcl_Obj *pVal2 = 0; + zVal = (const char*)sqlite3_value_text(pMem); + if( zVal ){ + pVal2 = Tcl_NewStringObj(zVal, -1); + }else{ + pVal2 = Tcl_NewObj(); + } + Tcl_ListObjAppendElement(interp, pVal, pVal2); + } }else{ pVal = Tcl_NewStringObj(zVal, -1); } @@ -374,20 +388,13 @@ static int tclEof(sqlite3_vtab_cursor *pVtabCursor){ return (pCsr->pStmt==0); } -static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ - tcl_vtab *pTab = (tcl_vtab*)tab; - Tcl_Interp *interp = pTab->interp; - Tcl_Obj *pArg; - Tcl_Obj *pScript; +static void testBestIndexObjConstraints( + Tcl_Interp *interp, + sqlite3_index_info *pIdxInfo +){ int ii; - int rc = SQLITE_OK; - - pScript = Tcl_DuplicateObj(pTab->pCmd); - Tcl_IncrRefCount(pScript); - Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xBestIndex", -1)); - - pArg = Tcl_NewObj(); - Tcl_IncrRefCount(pArg); + Tcl_Obj *pRes = Tcl_NewObj(); + Tcl_IncrRefCount(pRes); for(ii=0; iinConstraint; ii++){ struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii]; Tcl_Obj *pElem = Tcl_NewObj(); @@ -424,6 +431,10 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ zOp = "isnull"; break; case SQLITE_INDEX_CONSTRAINT_IS: zOp = "is"; break; + case SQLITE_INDEX_CONSTRAINT_LIMIT: + zOp = "limit"; break; + case SQLITE_INDEX_CONSTRAINT_OFFSET: + zOp = "offset"; break; } Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("op", -1)); @@ -433,15 +444,21 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("usable", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->usable)); - Tcl_ListObjAppendElement(0, pArg, pElem); + Tcl_ListObjAppendElement(0, pRes, pElem); Tcl_DecrRefCount(pElem); } - Tcl_ListObjAppendElement(0, pScript, pArg); - Tcl_DecrRefCount(pArg); + Tcl_SetObjResult(interp, pRes); + Tcl_DecrRefCount(pRes); +} - pArg = Tcl_NewObj(); - Tcl_IncrRefCount(pArg); +static void testBestIndexObjOrderby( + Tcl_Interp *interp, + sqlite3_index_info *pIdxInfo +){ + int ii; + Tcl_Obj *pRes = Tcl_NewObj(); + Tcl_IncrRefCount(pRes); for(ii=0; iinOrderBy; ii++){ struct sqlite3_index_orderby const *pOrder = &pIdxInfo->aOrderBy[ii]; Tcl_Obj *pElem = Tcl_NewObj(); @@ -452,17 +469,150 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("desc", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pOrder->desc)); - Tcl_ListObjAppendElement(0, pArg, pElem); + Tcl_ListObjAppendElement(0, pRes, pElem); Tcl_DecrRefCount(pElem); } - Tcl_ListObjAppendElement(0, pScript, pArg); - Tcl_DecrRefCount(pArg); + Tcl_SetObjResult(interp, pRes); + Tcl_DecrRefCount(pRes); +} - Tcl_ListObjAppendElement(0, pScript, Tcl_NewWideIntObj(pIdxInfo->colUsed)); +/* +** Implementation of the handle passed to each xBestIndex callback. This +** object features the following sub-commands: +** +** $hdl constraints +** $hdl orderby +** $hdl mask +** +** $hdl distinct +** Return the result (an integer) of calling sqlite3_vtab_distinct() +** on the index-info structure. +** +** $hdl in IDX BOOLEAN +** Wrapper around sqlite3_vtab_in(). Returns an integer. +** +** $hdl rhs_value IDX ?DEFAULT? +** Wrapper around sqlite3_vtab_rhs_value(). +*/ +static int SQLITE_TCLAPI testBestIndexObj( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + const char *azSub[] = { + "constraints", /* 0 */ + "orderby", /* 1 */ + "mask", /* 2 */ + "distinct", /* 3 */ + "in", /* 4 */ + "rhs_value", /* 5 */ + 0 + }; + int ii; + sqlite3_index_info *pIdxInfo = (sqlite3_index_info*)clientData; + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); + return TCL_ERROR; + } + if( Tcl_GetIndexFromObj(interp, objv[1], azSub, "sub-command", 0, &ii) ){ + return TCL_ERROR; + } + + if( ii<4 && objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + if( ii==4 && objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "INDEX BOOLEAN"); + return TCL_ERROR; + } + if( ii==5 && objc!=3 && objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "INDEX ?DEFAULT?"); + return TCL_ERROR; + } + + switch( ii ){ + case 0: assert( sqlite3_stricmp(azSub[ii], "constraints")==0 ); + testBestIndexObjConstraints(interp, pIdxInfo); + break; + + case 1: assert( sqlite3_stricmp(azSub[ii], "orderby")==0 ); + testBestIndexObjOrderby(interp, pIdxInfo); + break; + + case 2: assert( sqlite3_stricmp(azSub[ii], "mask")==0 ); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(pIdxInfo->colUsed)); + break; + + case 3: assert( sqlite3_stricmp(azSub[ii], "distinct")==0 ); { + int bDistinct = sqlite3_vtab_distinct(pIdxInfo); + Tcl_SetObjResult(interp, Tcl_NewIntObj(bDistinct)); + break; + } + + case 4: assert( sqlite3_stricmp(azSub[ii], "in")==0 ); { + int iCons; + int bHandle; + if( Tcl_GetIntFromObj(interp, objv[2], &iCons) + || Tcl_GetBooleanFromObj(interp, objv[3], &bHandle) + ){ + return TCL_ERROR; + } + Tcl_SetObjResult(interp, + Tcl_NewIntObj(sqlite3_vtab_in(pIdxInfo, iCons, bHandle)) + ); + break; + } + + case 5: assert( sqlite3_stricmp(azSub[ii], "rhs_value")==0 ); { + int iCons = 0; + int rc; + sqlite3_value *pVal = 0; + const char *zVal = ""; + if( Tcl_GetIntFromObj(interp, objv[2], &iCons) ){ + return TCL_ERROR; + } + rc = sqlite3_vtab_rhs_value(pIdxInfo, iCons, &pVal); + if( rc!=SQLITE_OK && rc!=SQLITE_NOTFOUND ){ + Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE); + return TCL_ERROR; + } + if( pVal ){ + zVal = (const char*)sqlite3_value_text(pVal); + }else if( objc==4 ){ + zVal = Tcl_GetString(objv[3]); + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(zVal, -1)); + break; + } + } + + return TCL_OK; +} + +static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + tcl_vtab *pTab = (tcl_vtab*)tab; + Tcl_Interp *interp = pTab->interp; + int rc = SQLITE_OK; + + static int iNext = 43; + char zHdl[24]; + Tcl_Obj *pScript; + + pScript = Tcl_DuplicateObj(pTab->pCmd); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xBestIndex", -1)); + + sqlite3_snprintf(sizeof(zHdl), zHdl, "bestindex%d", iNext++); + Tcl_CreateObjCommand(interp, zHdl, testBestIndexObj, pIdxInfo, 0); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(zHdl, -1)); rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); + Tcl_DeleteCommand(interp, zHdl); Tcl_DecrRefCount(pScript); + if( rc!=TCL_OK ){ const char *zErr = Tcl_GetStringResult(interp); rc = SQLITE_ERROR; @@ -489,6 +639,7 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("%s", zErr); }else{ + int ii; int iArgv = 1; for(ii=0; rc==SQLITE_OK && ii' ){ + *tokenType = TK_PTR; + return 2 + (z[2]=='>'); } *tokenType = TK_MINUS; return 1; @@ -559,13 +562,9 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ } /* -** Run the parser on the given SQL string. The parser structure is -** passed in. An SQLITE_ status code is returned. If an error occurs -** then an and attempt is made to write an error message into -** memory obtained from sqlite3_malloc() and to make *pzErrMsg point to that -** error message. +** Run the parser on the given SQL string. */ -int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ +int sqlite3RunParser(Parse *pParse, const char *zSql){ int nErr = 0; /* Number of errors encountered */ void *pEngine; /* The LEMON-generated LALR(1) parser */ int n = 0; /* Length of the next token token */ @@ -586,7 +585,6 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ } pParse->rc = SQLITE_OK; pParse->zTail = zSql; - assert( pzErrMsg!=0 ); #ifdef SQLITE_DEBUG if( db->flags & SQLITE_ParserTrace ){ printf("parser: [[[%s]]]\n", zSql); @@ -629,6 +627,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ #endif /* SQLITE_OMIT_WINDOWFUNC */ if( AtomicLoad(&db->u1.isInterrupted) ){ pParse->rc = SQLITE_INTERRUPT; + pParse->nErr++; break; } if( tokenType==TK_SPACE ){ @@ -658,7 +657,10 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed); #endif /* SQLITE_OMIT_WINDOWFUNC */ }else{ - sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql); + Token x; + x.z = zSql; + x.n = n; + sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", &x); break; } } @@ -686,41 +688,26 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ if( db->mallocFailed ){ pParse->rc = SQLITE_NOMEM_BKPT; } - if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){ - pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); - } - assert( pzErrMsg!=0 ); - if( pParse->zErrMsg ){ - *pzErrMsg = pParse->zErrMsg; - sqlite3_log(pParse->rc, "%s in \"%s\"", - *pzErrMsg, pParse->zTail); - pParse->zErrMsg = 0; + if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){ + if( pParse->zErrMsg==0 ){ + pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); + } + sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); nErr++; } pParse->zTail = zSql; - if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){ - sqlite3VdbeDelete(pParse->pVdbe); - pParse->pVdbe = 0; - } -#ifndef SQLITE_OMIT_SHARED_CACHE - if( pParse->nested==0 ){ - sqlite3DbFree(db, pParse->aTableLock); - pParse->aTableLock = 0; - pParse->nTableLock = 0; - } -#endif #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3_free(pParse->apVtabLock); #endif - if( !IN_SPECIAL_PARSE ){ + if( pParse->pNewTable && !IN_SPECIAL_PARSE ){ /* If the pParse->declareVtab flag is set, do not delete any table ** structure built up in pParse->pNewTable. The calling code (see vtab.c) ** will take responsibility for freeing the Table structure. */ sqlite3DeleteTable(db, pParse->pNewTable); } - if( !IN_RENAME_OBJECT ){ + if( pParse->pNewTrigger && !IN_RENAME_OBJECT ){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); } sqlite3DbFree(db, pParse->pVList); diff --git a/src/treeview.c b/src/treeview.c index 1b19ea679..86f06e832 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -412,7 +412,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3_str_appendf(&x, " fg.af=%x.%c", pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n'); if( ExprHasProperty(pExpr, EP_FromJoin) ){ - sqlite3_str_appendf(&x, " iRJT=%d", pExpr->iRightJoinTable); + sqlite3_str_appendf(&x, " iRJT=%d", pExpr->w.iRightJoinTable); } if( ExprHasProperty(pExpr, EP_FromDDL) ){ sqlite3_str_appendf(&x, " DDL"); diff --git a/src/trigger.c b/src/trigger.c index 7e3a053a7..5df6b0c0b 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -446,6 +446,7 @@ static TriggerStep *triggerStepAllocate( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; + if( pParse->nErr ) return 0; pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); if( pTriggerStep ){ char *z = (char*)&pTriggerStep[1]; @@ -918,6 +919,7 @@ static void codeReturningTrigger( assert( v!=0 ); assert( pParse->bReturning ); + assert( db->pParse==pParse ); pReturning = pParse->u1.pReturning; assert( pTrigger == &(pReturning->retTrig) ); memset(&sSelect, 0, sizeof(sSelect)); @@ -928,7 +930,8 @@ static void codeReturningTrigger( sFrom.a[0].pTab = pTab; sFrom.a[0].iCursor = -1; sqlite3SelectPrep(pParse, &sSelect, 0); - if( db->mallocFailed==0 && pParse->nErr==0 ){ + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); sqlite3GenerateColumnNames(pParse, &sSelect); } sqlite3ExprListDelete(db, sSelect.pEList); @@ -946,7 +949,7 @@ static void codeReturningTrigger( pParse->eTriggerOp = pTrigger->op; pParse->pTriggerTab = pTab; if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK - && !db->mallocFailed + && ALWAYS(!db->mallocFailed) ){ int i; int nCol = pNew->nExpr; @@ -957,6 +960,9 @@ static void codeReturningTrigger( Expr *pCol = pNew->a[i].pExpr; assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */ sqlite3ExprCodeFactorable(pParse, pCol, reg+i); + if( sqlite3ExprAffinity(pCol)==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, reg+i); + } } sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i); sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1); @@ -1107,8 +1113,8 @@ static TriggerPrg *codeRowTrigger( Vdbe *v; /* Temporary VM */ NameContext sNC; /* Name context for sub-vdbe */ SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ - Parse *pSubParse; /* Parse context for sub-vdbe */ int iEndTrigger = 0; /* Label to jump to if WHEN is false */ + Parse sSubParse; /* Parse context for sub-vdbe */ assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); assert( pTop->pVdbe ); @@ -1130,19 +1136,17 @@ static TriggerPrg *codeRowTrigger( /* Allocate and populate a new Parse context to use for coding the ** trigger sub-program. */ - pSubParse = sqlite3StackAllocZero(db, sizeof(Parse)); - if( !pSubParse ) return 0; + sqlite3ParseObjectInit(&sSubParse, db); memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pSubParse; - pSubParse->db = db; - pSubParse->pTriggerTab = pTab; - pSubParse->pToplevel = pTop; - pSubParse->zAuthContext = pTrigger->zName; - pSubParse->eTriggerOp = pTrigger->op; - pSubParse->nQueryLoop = pParse->nQueryLoop; - pSubParse->disableVtab = pParse->disableVtab; - - v = sqlite3GetVdbe(pSubParse); + sNC.pParse = &sSubParse; + sSubParse.pTriggerTab = pTab; + sSubParse.pToplevel = pTop; + sSubParse.zAuthContext = pTrigger->zName; + sSubParse.eTriggerOp = pTrigger->op; + sSubParse.nQueryLoop = pParse->nQueryLoop; + sSubParse.disableVtab = pParse->disableVtab; + + v = sqlite3GetVdbe(&sSubParse); if( v ){ VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", pTrigger->zName, onErrorText(orconf), @@ -1168,14 +1172,14 @@ static TriggerPrg *codeRowTrigger( if( db->mallocFailed==0 && SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) ){ - iEndTrigger = sqlite3VdbeMakeLabel(pSubParse); - sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); + iEndTrigger = sqlite3VdbeMakeLabel(&sSubParse); + sqlite3ExprIfFalse(&sSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); } sqlite3ExprDelete(db, pWhen); } /* Code the trigger program into the sub-vdbe. */ - codeTriggerProgram(pSubParse, pTrigger->step_list, orconf); + codeTriggerProgram(&sSubParse, pTrigger->step_list, orconf); /* Insert an OP_Halt at the end of the sub-program. */ if( iEndTrigger ){ @@ -1183,23 +1187,24 @@ static TriggerPrg *codeRowTrigger( } sqlite3VdbeAddOp0(v, OP_Halt); VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf))); + transferParseError(pParse, &sSubParse); - transferParseError(pParse, pSubParse); - if( db->mallocFailed==0 && pParse->nErr==0 ){ + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); } - pProgram->nMem = pSubParse->nMem; - pProgram->nCsr = pSubParse->nTab; + pProgram->nMem = sSubParse.nMem; + pProgram->nCsr = sSubParse.nTab; pProgram->token = (void *)pTrigger; - pPrg->aColmask[0] = pSubParse->oldmask; - pPrg->aColmask[1] = pSubParse->newmask; + pPrg->aColmask[0] = sSubParse.oldmask; + pPrg->aColmask[1] = sSubParse.newmask; sqlite3VdbeDelete(v); + }else{ + transferParseError(pParse, &sSubParse); } - assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg ); - sqlite3ParserReset(pSubParse); - sqlite3StackFree(db, pSubParse); - + assert( !sSubParse.pTriggerPrg && !sSubParse.nMaxArg ); + sqlite3ParseObjectReset(&sSubParse); return pPrg; } @@ -1232,6 +1237,7 @@ static TriggerPrg *getRowTrigger( /* If an existing TriggerPrg could not be located, create a new one. */ if( !pPrg ){ pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf); + pParse->db->errByteOffset = -1; } return pPrg; @@ -1254,7 +1260,7 @@ void sqlite3CodeRowTriggerDirect( Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */ TriggerPrg *pPrg; pPrg = getRowTrigger(pParse, p, pTab, orconf); - assert( pPrg || pParse->nErr || pParse->db->mallocFailed ); + assert( pPrg || pParse->nErr ); /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program ** is a pointer to the sub-vdbe containing the trigger program. */ diff --git a/src/update.c b/src/update.c index 484bee47c..a43d0eac5 100644 --- a/src/update.c +++ b/src/update.c @@ -347,9 +347,11 @@ void sqlite3Update( memset(&sContext, 0, sizeof(sContext)); db = pParse->db; - if( pParse->nErr || db->mallocFailed ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto update_cleanup; } + assert( db->mallocFailed==0 ); /* Locate the table which we want to update. */ @@ -721,7 +723,7 @@ void sqlite3Update( if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){ flags |= WHERE_ONEPASS_MULTIROW; } - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags,iIdxCur); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,0,0,flags,iIdxCur); if( pWInfo==0 ) goto update_cleanup; /* A one-pass strategy that might update more than one row may not @@ -1121,9 +1123,7 @@ void sqlite3Update( ** that information. */ if( regRowCount ){ - sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC); + sqlite3CodeChangeCount(v, regRowCount, "rows updated"); } update_cleanup: @@ -1245,7 +1245,9 @@ static void updateVirtualTable( regRowid = ++pParse->nMem; /* Start scanning the virtual table */ - pWInfo = sqlite3WhereBegin(pParse, pSrc,pWhere,0,0,WHERE_ONEPASS_DESIRED,0); + pWInfo = sqlite3WhereBegin( + pParse, pSrc, pWhere, 0, 0, 0, WHERE_ONEPASS_DESIRED, 0 + ); if( pWInfo==0 ) return; /* Populate the argument registers. */ diff --git a/src/util.c b/src/util.c index 8452aea66..84608739a 100644 --- a/src/util.c +++ b/src/util.c @@ -117,7 +117,11 @@ static SQLITE_NOINLINE void sqlite3ErrorFinish(sqlite3 *db, int err_code){ void sqlite3Error(sqlite3 *db, int err_code){ assert( db!=0 ); db->errCode = err_code; - if( err_code || db->pErr ) sqlite3ErrorFinish(db, err_code); + if( err_code || db->pErr ){ + sqlite3ErrorFinish(db, err_code); + }else{ + db->errByteOffset = -1; + } } /* @@ -127,6 +131,7 @@ void sqlite3Error(sqlite3 *db, int err_code){ void sqlite3ErrorClear(sqlite3 *db){ assert( db!=0 ); db->errCode = SQLITE_OK; + db->errByteOffset = -1; if( db->pErr ) sqlite3ValueSetNull(db->pErr); } @@ -147,17 +152,8 @@ void sqlite3SystemError(sqlite3 *db, int rc){ ** handle "db". The error code is set to "err_code". ** ** If it is not NULL, string zFormat specifies the format of the -** error string in the style of the printf functions: The following -** format characters are allowed: -** -** %s Insert a string -** %z A string that should be freed after use -** %d Insert an integer -** %T Insert a token -** %S Insert the first element of a SrcList -** -** zFormat and any string tokens that follow it are assumed to be -** encoded in UTF-8. +** error string. zFormat and any string tokens that follow it are +** assumed to be encoded in UTF-8. ** ** To clear the most recent error for sqlite handle "db", sqlite3Error ** should be called with err_code set to SQLITE_OK and zFormat set @@ -181,13 +177,6 @@ void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *zFormat, ...){ /* ** Add an error message to pParse->zErrMsg and increment pParse->nErr. -** The following formatting characters are allowed: -** -** %s Insert a string -** %z A string that should be freed after use -** %d Insert an integer -** %T Insert a token -** %S Insert the first element of a SrcList ** ** This function should be used to report any error that occurs while ** compiling an SQL statement (i.e. within sqlite3_prepare()). The @@ -200,11 +189,19 @@ void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){ char *zMsg; va_list ap; sqlite3 *db = pParse->db; + assert( db!=0 ); + assert( db->pParse==pParse ); + db->errByteOffset = -2; va_start(ap, zFormat); zMsg = sqlite3VMPrintf(db, zFormat, ap); va_end(ap); + if( db->errByteOffset<-1 ) db->errByteOffset = -1; if( db->suppressErr ){ sqlite3DbFree(db, zMsg); + if( db->mallocFailed ){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } }else{ pParse->nErr++; sqlite3DbFree(db, pParse->zErrMsg); @@ -1586,7 +1583,6 @@ LogEst sqlite3LogEst(u64 x){ return a[x&7] + y - 10; } -#ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Convert a double into a LogEst ** In other words, compute an approximation for 10*log2(x). @@ -1601,16 +1597,9 @@ LogEst sqlite3LogEstFromDouble(double x){ e = (a>>52) - 1022; return e*10; } -#endif /* SQLITE_OMIT_VIRTUALTABLE */ -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_ENABLE_STAT4) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) /* ** Convert a LogEst into an integer. -** -** Note that this routine is only used when one or more of various -** non-standard compile-time options is enabled. */ u64 sqlite3LogEstToInt(LogEst x){ u64 n; @@ -1618,17 +1607,9 @@ u64 sqlite3LogEstToInt(LogEst x){ x /= 10; if( n>=5 ) n -= 2; else if( n>=1 ) n -= 1; -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) if( x>60 ) return (u64)LARGEST_INT64; -#else - /* If only SQLITE_ENABLE_STAT4 is on, then the largest input - ** possible to this routine is 310, resulting in a maximum x of 31 */ - assert( x<=60 ); -#endif return x>=3 ? (n+8)<<(x-3) : (n+8)>>(3-x); } -#endif /* defined SCANSTAT or STAT4 or ESTIMATED_ROWS */ /* ** Add a new name/number pair to a VList. This might require that the diff --git a/src/vdbe.c b/src/vdbe.c index 5ca253a07..fa56689df 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -241,7 +241,6 @@ static VdbeCursor *allocateCursor( Vdbe *p, /* The virtual machine */ int iCur, /* Index of the new VdbeCursor */ int nField, /* Number of fields in the table or index */ - int iDb, /* Database the cursor belongs to, or -1 */ u8 eCurType /* Type of the new cursor */ ){ /* Find the memory cell that will be used to store the blob of memory @@ -298,7 +297,6 @@ static VdbeCursor *allocateCursor( p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->zMalloc; memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); pCx->eCurType = eCurType; - pCx->iDb = iDb; pCx->nField = nField; pCx->aOffset = &pCx->aType[nField]; if( eCurType==CURTYPE_BTREE ){ @@ -671,6 +669,29 @@ static Mem *out2Prerelease(Vdbe *p, VdbeOp *pOp){ } } +/* +** Compute a bloom filter hash using pOp->p4.i registers from aMem[] beginning +** with pOp->p3. Return the hash. +*/ +static u64 filterHash(const Mem *aMem, const Op *pOp){ + int i, mx; + u64 h = 0; + + assert( pOp->p4type==P4_INT32 ); + for(i=pOp->p3, mx=i+pOp->p4.i; iflags & (MEM_Int|MEM_IntReal) ){ + h += p->u.i; + }else if( p->flags & MEM_Real ){ + h += sqlite3VdbeIntValue(p); + }else if( p->flags & (MEM_Str|MEM_Blob) ){ + h += p->n; + if( p->flags & MEM_Zero ) h += p->u.nZero; + } + } + return h; +} + /* ** Return the symbolic name for the data type of a pMem */ @@ -1325,12 +1346,18 @@ case OP_SoftNull: { ** Synopsis: r[P2]=P4 (len=P1) ** ** P4 points to a blob of data P1 bytes long. Store this -** blob in register P2. +** blob in register P2. If P4 is a NULL pointer, then construct +** a zero-filled blob that is P1 bytes long in P2. */ case OP_Blob: { /* out2 */ assert( pOp->p1 <= SQLITE_MAX_LENGTH ); pOut = out2Prerelease(p, pOp); - sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0); + if( pOp->p4.z==0 ){ + sqlite3VdbeMemSetZeroBlob(pOut, pOp->p1); + if( sqlite3VdbeMemExpandBlob(pOut) ) goto no_mem; + }else{ + sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0); + } pOut->enc = encoding; UPDATE_MAX_BLOBSIZE(pOut); break; @@ -1479,24 +1506,22 @@ case OP_IntCopy: { /* out2 */ break; } -/* Opcode: ChngCntRow P1 P2 * * * -** Synopsis: output=r[P1] +/* Opcode: FkCheck * * * * * ** -** Output value in register P1 as the chance count for a DML statement, -** due to the "PRAGMA count_changes=ON" setting. Or, if there was a -** foreign key error in the statement, trigger the error now. +** Halt with an SQLITE_CONSTRAINT error if there are any unresolved +** foreign key constraint violations. If there are no foreign key +** constraint violations, this is a no-op. ** -** This opcode is a variant of OP_ResultRow that checks the foreign key -** immediate constraint count and throws an error if the count is -** non-zero. The P2 opcode must be 1. +** FK constraint violations are also checked when the prepared statement +** exits. This opcode is used to raise foreign key constraint errors prior +** to returning results such as a row change count or the result of a +** RETURNING clause. */ -case OP_ChngCntRow: { - assert( pOp->p2==1 ); +case OP_FkCheck: { if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){ goto abort_due_to_error; } - /* Fall through to the next case, OP_ResultRow */ - /* no break */ deliberate_fall_through + break; } /* Opcode: ResultRow P1 P2 * * * @@ -2134,7 +2159,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); - if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; + if( pIn1==pIn3 ) flags3 = flags1 | MEM_Str; } if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn3->flags & MEM_Int ); @@ -2602,10 +2627,18 @@ case OP_Offset: { /* out3 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; pOut = &p->aMem[pOp->p3]; - if( NEVER(pC==0) || pC->eCurType!=CURTYPE_BTREE ){ + if( pC==0 || pC->eCurType!=CURTYPE_BTREE ){ sqlite3VdbeMemSetNull(pOut); }else{ - sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + if( pC->deferredMoveto ){ + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + } + if( sqlite3BtreeEof(pC->uc.pCursor) ){ + sqlite3VdbeMemSetNull(pOut); + }else{ + sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + } } break; } @@ -2664,6 +2697,7 @@ case OP_Column: { assert( pC!=0 ); assert( p2<(u32)pC->nField ); aOffset = pC->aOffset; + assert( aOffset==pC->aType+pC->nField ); assert( pC->eCurType!=CURTYPE_VTAB ); assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); assert( pC->eCurType!=CURTYPE_SORTER ); @@ -2959,6 +2993,8 @@ case OP_TypeCheck: { break; } case COLTYPE_REAL: { + testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_Real ); + testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_IntReal ); if( pIn1->flags & MEM_Int ){ /* When applying REAL affinity, if the result is still an MEM_Int ** that will fit in 6 bytes, then change the type to MEM_IntReal @@ -2976,7 +3012,7 @@ case OP_TypeCheck: { pIn1->flags |= MEM_Real; pIn1->flags &= ~MEM_Int; } - }else if( (pIn1->flags & MEM_Real)==0 ){ + }else if( (pIn1->flags & (MEM_Real|MEM_IntReal))==0 ){ goto vdbe_type_error; } break; @@ -3215,7 +3251,7 @@ case OP_MakeRecord: { testcase( uu==127 ); testcase( uu==128 ); testcase( uu==32767 ); testcase( uu==32768 ); testcase( uu==8388607 ); testcase( uu==8388608 ); - testcase( uu==2147483647 ); testcase( uu==2147483648 ); + testcase( uu==2147483647 ); testcase( uu==2147483648LL ); testcase( uu==140737488355327LL ); testcase( uu==140737488355328LL ); if( uu<=127 ){ if( (i&1)==i && file_format>=4 ){ @@ -3343,7 +3379,7 @@ case OP_MakeRecord: { break; } -/* Opcode: Count P1 P2 p3 * * +/* Opcode: Count P1 P2 P3 * * ** Synopsis: r[P2]=count() ** ** Store the number of entries (an integer value) in the table or index @@ -3991,8 +4027,9 @@ case OP_OpenWrite: assert( pOp->p1>=0 ); assert( nField>=0 ); testcase( nField==0 ); /* Table with INTEGER PRIMARY KEY and nothing else */ - pCur = allocateCursor(p, pOp->p1, nField, iDb, CURTYPE_BTREE); + pCur = allocateCursor(p, pOp->p1, nField, CURTYPE_BTREE); if( pCur==0 ) goto no_mem; + pCur->iDb = iDb; pCur->nullRow = 1; pCur->isOrdered = 1; pCur->pgnoRoot = p2; @@ -4034,7 +4071,7 @@ case OP_OpenDup: { assert( pOrig ); assert( pOrig->isEphemeral ); /* Only ephemeral cursors can be duplicated */ - pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE); + pCx = allocateCursor(p, pOp->p1, pOrig->nField, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->isEphemeral = 1; @@ -4042,10 +4079,10 @@ case OP_OpenDup: { pCx->isTable = pOrig->isTable; pCx->pgnoRoot = pOrig->pgnoRoot; pCx->isOrdered = pOrig->isOrdered; - pCx->pBtx = pOrig->pBtx; + pCx->ub.pBtx = pOrig->ub.pBtx; pCx->hasBeenDuped = 1; pOrig->hasBeenDuped = 1; - rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, pCx->pKeyInfo, pCx->uc.pCursor); /* The sqlite3BtreeCursor() routine can only fail for the first cursor ** opened for a database. Since there is already an open cursor when this @@ -4118,16 +4155,16 @@ case OP_OpenEphemeral: { assert( pCx->isEphemeral ); pCx->seqCount = 0; pCx->cacheStatus = CACHE_STALE; - rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0); + rc = sqlite3BtreeClearTable(pCx->ub.pBtx, pCx->pgnoRoot, 0); }else{ - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE); + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; pCx->isEphemeral = 1; - rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->ub.pBtx, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1, 0); + rc = sqlite3BtreeBeginTrans(pCx->ub.pBtx, 1, 0); if( rc==SQLITE_OK ){ /* If a transient index is required, create it by calling ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before @@ -4136,26 +4173,26 @@ case OP_OpenEphemeral: { */ if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot, + rc = sqlite3BtreeCreateTable(pCx->ub.pBtx, &pCx->pgnoRoot, BTREE_BLOBKEY | pOp->p5); if( rc==SQLITE_OK ){ assert( pCx->pgnoRoot==SCHEMA_ROOT+1 ); assert( pKeyInfo->db==db ); assert( pKeyInfo->enc==ENC(db) ); - rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, pKeyInfo, pCx->uc.pCursor); } pCx->isTable = 0; }else{ pCx->pgnoRoot = SCHEMA_ROOT; - rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->ub.pBtx, SCHEMA_ROOT, BTREE_WRCSR, 0, pCx->uc.pCursor); pCx->isTable = 1; } } pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); if( rc ){ - sqlite3BtreeClose(pCx->pBtx); + sqlite3BtreeClose(pCx->ub.pBtx); } } } @@ -4179,7 +4216,7 @@ case OP_SorterOpen: { assert( pOp->p1>=0 ); assert( pOp->p2>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_SORTER); + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_SORTER); if( pCx==0 ) goto no_mem; pCx->pKeyInfo = pOp->p4.pKeyInfo; assert( pCx->pKeyInfo->db==db ); @@ -4228,7 +4265,7 @@ case OP_OpenPseudo: { assert( pOp->p1>=0 ); assert( pOp->p3>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, CURTYPE_PSEUDO); + pCx = allocateCursor(p, pOp->p1, pOp->p3, CURTYPE_PSEUDO); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->seekResult = pOp->p2; @@ -5680,6 +5717,10 @@ case OP_Rowid: { /* out2 */ ** Move the cursor P1 to a null row. Any OP_Column operations ** that occur while the cursor is on the null row will always ** write a NULL. +** +** Or, if P1 is a Pseudo-Cursor (a cursor opened using OP_OpenPseudo) +** just reset the cache for that cursor. This causes the row of +** content held by the pseudo-cursor to be reparsed. */ case OP_NullRow: { VdbeCursor *pC; @@ -6168,9 +6209,9 @@ case OP_IdxRowid: { /* out2 */ pTabCur->movetoTarget = rowid; pTabCur->deferredMoveto = 1; assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 ); - pTabCur->aAltMap = pOp->p4.ai; - assert( !pC->isEphemeral ); assert( !pTabCur->isEphemeral ); + pTabCur->ub.aAltMap = pOp->p4.ai; + assert( !pC->isEphemeral ); pTabCur->pAltCursor = pC; }else{ pOut = out2Prerelease(p, pOp); @@ -7692,7 +7733,7 @@ case OP_VOpen: { pVCur->pVtab = pVtab; /* Initialize vdbe cursor object */ - pCur = allocateCursor(p, pOp->p1, 0, -1, CURTYPE_VTAB); + pCur = allocateCursor(p, pOp->p1, 0, CURTYPE_VTAB); if( pCur ){ pCur->uc.pVCur = pVCur; pVtab->nRef++; @@ -7705,6 +7746,34 @@ case OP_VOpen: { } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VInitIn P1 P2 P3 * * +** Synopsis: r[P2]=ValueList(P1,P3) +** +** Set register P2 to be a pointer to a ValueList object for cursor P1 +** with cache register P3 and output register P3+1. This ValueList object +** can be used as the first argument to sqlite3_vtab_in_first() and +** sqlite3_vtab_in_next() to extract all of the values stored in the P1 +** cursor. Register P3 is used to hold the values returned by +** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). +*/ +case OP_VInitIn: { /* out2 */ + VdbeCursor *pC; /* The cursor containing the RHS values */ + ValueList *pRhs; /* New ValueList object to put in reg[P2] */ + + pC = p->apCsr[pOp->p1]; + pRhs = sqlite3_malloc64( sizeof(*pRhs) ); + if( pRhs==0 ) goto no_mem; + pRhs->pCsr = pC->uc.pCursor; + pRhs->pOut = &aMem[pOp->p3]; + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Null; + sqlite3VdbeMemSetPointer(pOut, pRhs, "ValueList", sqlite3_free); + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VFilter P1 P2 P3 P4 * ** Synopsis: iplan=r[P3] zplan='P4' @@ -8130,6 +8199,77 @@ case OP_Function: { /* group */ break; } +/* Opcode: FilterAdd P1 * P3 P4 * +** Synopsis: filter(P1) += key(P3@P4) +** +** Compute a hash on the P4 registers starting with r[P3] and +** add that hash to the bloom filter contained in r[P1]. +*/ +case OP_FilterAdd: { + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags & MEM_Blob ); + assert( pIn1->n>0 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; iip3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= pIn1->n; + pIn1->z[h/8] |= 1<<(h&7); + break; +} + +/* Opcode: Filter P1 P2 P3 P4 * +** Synopsis: if key(P3@P4) not in filter(P1) goto P2 +** +** Compute a hash on the key contained in the P4 registers starting +** with r[P3]. Check to see if that hash is found in the +** bloom filter hosted by register P1. If it is not present then +** maybe jump to P2. Otherwise fall through. +** +** False negatives are harmless. It is always safe to fall through, +** even if the value is in the bloom filter. A false negative causes +** more CPU cycles to be used, but it should still yield the correct +** answer. However, an incorrect answer may well arise from a +** false positive - if the jump is taken when it should fall through. +*/ +case OP_Filter: { /* jump */ + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( (pIn1->flags & MEM_Blob)!=0 ); + assert( pIn1->n >= 1 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; iip3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= pIn1->n; + if( (pIn1->z[h/8] & (1<<(h&7)))==0 ){ + VdbeBranchTaken(1, 2); + p->aCounter[SQLITE_STMTSTATUS_FILTER_HIT]++; + goto jump_to_p2; + }else{ + p->aCounter[SQLITE_STMTSTATUS_FILTER_MISS]++; + VdbeBranchTaken(0, 2); + } + break; +} + /* Opcode: Trace P1 P2 * P4 * ** ** Write P4 on the statement trace output if statement tracing is diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 599d06416..f02b37c6a 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -75,7 +75,7 @@ typedef struct AuxData AuxData; typedef struct VdbeCursor VdbeCursor; struct VdbeCursor { u8 eCurType; /* One of the CURTYPE_* values above */ - i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ + i8 iDb; /* Index of cursor database in db->aDb[] */ u8 nullRow; /* True if pointing to a row with no data */ u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ u8 isTable; /* True for rowid tables. False for indexes */ @@ -88,9 +88,11 @@ struct VdbeCursor { Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ Bool hasBeenDuped:1; /* This cursor was source or target of OP_OpenDup */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ - Btree *pBtx; /* Separate file holding temporary table */ + union { /* pBtx for isEphermeral. pAltMap otherwise */ + Btree *pBtx; /* Separate file holding temporary table */ + u32 *aAltMap; /* Mapping from table to index column numbers */ + } ub; i64 seqCount; /* Sequence counter */ - u32 *aAltMap; /* Mapping from table to index column numbers */ /* Cached OP_Column parse information is only valid if cacheStatus matches ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of @@ -430,7 +432,7 @@ struct Vdbe { bft bIsReader:1; /* True for statements that read */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ - u32 aCounter[7]; /* Counters used by sqlite3_stmt_status() */ + u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ char *zSql; /* Text of the SQL statement that generated this */ #ifdef SQLITE_ENABLE_NORMALIZE char *zNormSql; /* Normalization of the associated SQL statement */ @@ -480,6 +482,24 @@ struct PreUpdate { Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; +/* +** An instance of this object is used to pass an vector of values into +** OP_VFilter, the xFilter method of a virtual table. The vector is the +** set of values on the right-hand side of an IN constraint. +** +** The value as passed into xFilter is an sqlite3_value with a "pointer" +** type, such as is generated by sqlite3_result_pointer() and read by +** sqlite3_value_pointer. Such values have MEM_Term|MEM_Subtype|MEM_Null +** and a subtype of 'p'. The sqlite3_vtab_in_first() and _next() interfaces +** know how to use this object to step through all the values in the +** right operand of the IN constraint. +*/ +typedef struct ValueList ValueList; +struct ValueList { + BtCursor *pCsr; /* An ephemeral table holding all values */ + sqlite3_value *pOut; /* Register to hold each decoded output value */ +}; + /* ** Function prototypes */ @@ -492,7 +512,7 @@ int sqlite3VdbeCursorRestore(VdbeCursor*); u32 sqlite3VdbeSerialTypeLen(u32); u8 sqlite3VdbeOneByteSerialTypeLen(u8); u32 sqlite3VdbeSerialPut(unsigned char*, Mem*, u32); -u32 sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); +void sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int); int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *); @@ -538,7 +558,7 @@ int sqlite3VdbeMemSetRowSet(Mem*); int sqlite3VdbeMemMakeWriteable(Mem*); int sqlite3VdbeMemStringify(Mem*, u8, u8); int sqlite3IntFloatCompare(i64,double); -i64 sqlite3VdbeIntValue(Mem*); +i64 sqlite3VdbeIntValue(const Mem*); int sqlite3VdbeMemIntegerify(Mem*); double sqlite3VdbeRealValue(Mem*); int sqlite3VdbeBooleanValue(Mem*, int ifNull); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 17df807de..6939813a2 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -846,6 +846,70 @@ int sqlite3_vtab_nochange(sqlite3_context *p){ return sqlite3_value_nochange(p->pOut); } +/* +** Implementation of sqlite3_vtab_in_first() (if bNext==0) and +** sqlite3_vtab_in_next() (if bNext!=0). +*/ +static int valueFromValueList( + sqlite3_value *pVal, /* Pointer to the ValueList object */ + sqlite3_value **ppOut, /* Store the next value from the list here */ + int bNext /* 1 for _next(). 0 for _first() */ +){ + int rc; + ValueList *pRhs; + + *ppOut = 0; + if( pVal==0 ) return SQLITE_MISUSE; + pRhs = (ValueList*)sqlite3_value_pointer(pVal, "ValueList"); + if( pRhs==0 ) return SQLITE_MISUSE; + if( bNext ){ + rc = sqlite3BtreeNext(pRhs->pCsr, 0); + }else{ + int dummy = 0; + rc = sqlite3BtreeFirst(pRhs->pCsr, &dummy); + assert( rc==SQLITE_OK || sqlite3BtreeEof(pRhs->pCsr) ); + if( sqlite3BtreeEof(pRhs->pCsr) ) rc = SQLITE_DONE; + } + if( rc==SQLITE_OK ){ + u32 sz; /* Size of current row in bytes */ + Mem sMem; /* Raw content of current row */ + memset(&sMem, 0, sizeof(sMem)); + sz = sqlite3BtreePayloadSize(pRhs->pCsr); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pRhs->pCsr,(int)sz,&sMem); + if( rc==SQLITE_OK ){ + u8 *zBuf = (u8*)sMem.z; + u32 iSerial; + sqlite3_value *pOut = pRhs->pOut; + int iOff = 1 + getVarint32(&zBuf[1], iSerial); + sqlite3VdbeSerialGet(&zBuf[iOff], iSerial, pOut); + pOut->enc = ENC(pOut->db); + if( (pOut->flags & MEM_Ephem)!=0 && sqlite3VdbeMemMakeWriteable(pOut) ){ + rc = SQLITE_NOMEM; + }else{ + *ppOut = pOut; + } + } + sqlite3VdbeMemRelease(&sMem); + } + return rc; +} + +/* +** Set the iterator value pVal to point to the first value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 0); +} + +/* +** Set the iterator value pVal to point to the next value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 1); +} + /* ** Return the current time for a statement. If the current time ** is requested more than once within the same run of a single prepared @@ -1530,7 +1594,10 @@ int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ break; } case SQLITE_FLOAT: { - rc = sqlite3_bind_double(pStmt, i, pValue->u.r); + assert( pValue->flags & (MEM_Real|MEM_IntReal) ); + rc = sqlite3_bind_double(pStmt, i, + (pValue->flags & MEM_Real) ? pValue->u.r : (double)pValue->u.i + ); break; } case SQLITE_BLOB: { diff --git a/src/vdbeaux.c b/src/vdbeaux.c index a11d41c47..0f3d0eb87 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -1378,8 +1378,7 @@ void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ */ static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){ assert( p->nOp>0 || p->aOp==0 ); - assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed - || p->pParse->nErr>0 ); + assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->pParse->nErr>0 ); if( p->nOp ){ assert( p->aOp ); sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment); @@ -2470,8 +2469,6 @@ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx==0 ){ return; } - assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE ); - assert( pCx->pBtx==0 || pCx->isEphemeral ); switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); @@ -3252,6 +3249,7 @@ int sqlite3VdbeTransferError(Vdbe *p){ sqlite3ValueSetNull(db->pErr); } db->errCode = rc; + db->errByteOffset = -1; return rc; } @@ -3573,7 +3571,7 @@ int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){ if( p->deferredMoveto ){ u32 iMap; assert( !p->isEphemeral ); - if( p->aAltMap && (iMap = p->aAltMap[1+*piCol])>0 && !p->nullRow ){ + if( p->ub.aAltMap && (iMap = p->ub.aAltMap[1+*piCol])>0 && !p->nullRow ){ *pp = p->pAltCursor; *piCol = iMap - 1; return SQLITE_OK; @@ -3851,14 +3849,14 @@ u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ /* ** Deserialize the data blob pointed to by buf as serial type serial_type -** and store the result in pMem. Return the number of bytes read. +** and store the result in pMem. ** ** This function is implemented as two separate routines for performance. ** The few cases that require local variables are broken out into a separate ** routine so that in most cases the overhead of moving the stack pointer ** is avoided. */ -static u32 serialGet( +static void serialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ Mem *pMem /* Memory cell to write value into */ @@ -3892,9 +3890,8 @@ static u32 serialGet( memcpy(&pMem->u.r, &x, sizeof(x)); pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real; } - return 8; } -u32 sqlite3VdbeSerialGet( +void sqlite3VdbeSerialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ Mem *pMem /* Memory cell to write value into */ @@ -3905,13 +3902,13 @@ u32 sqlite3VdbeSerialGet( pMem->flags = MEM_Null|MEM_Zero; pMem->n = 0; pMem->u.nZero = 0; - break; + return; } case 11: /* Reserved for future use */ case 0: { /* Null */ /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */ pMem->flags = MEM_Null; - break; + return; } case 1: { /* EVIDENCE-OF: R-44885-25196 Value is an 8-bit twos-complement @@ -3919,7 +3916,7 @@ u32 sqlite3VdbeSerialGet( pMem->u.i = ONE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 1; + return; } case 2: { /* 2-byte signed integer */ /* EVIDENCE-OF: R-49794-35026 Value is a big-endian 16-bit @@ -3927,7 +3924,7 @@ u32 sqlite3VdbeSerialGet( pMem->u.i = TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 2; + return; } case 3: { /* 3-byte signed integer */ /* EVIDENCE-OF: R-37839-54301 Value is a big-endian 24-bit @@ -3935,7 +3932,7 @@ u32 sqlite3VdbeSerialGet( pMem->u.i = THREE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 3; + return; } case 4: { /* 4-byte signed integer */ /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit @@ -3947,7 +3944,7 @@ u32 sqlite3VdbeSerialGet( #endif pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 4; + return; } case 5: { /* 6-byte signed integer */ /* EVIDENCE-OF: R-50385-09674 Value is a big-endian 48-bit @@ -3955,13 +3952,14 @@ u32 sqlite3VdbeSerialGet( pMem->u.i = FOUR_BYTE_UINT(buf+2) + (((i64)1)<<32)*TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 6; + return; } case 6: /* 8-byte signed integer */ case 7: { /* IEEE floating point */ /* These use local variables, so do them in a separate routine ** to avoid having to move the frame pointer in the common case */ - return serialGet(buf,serial_type,pMem); + serialGet(buf,serial_type,pMem); + return; } case 8: /* Integer 0 */ case 9: { /* Integer 1 */ @@ -3969,7 +3967,7 @@ u32 sqlite3VdbeSerialGet( /* EVIDENCE-OF: R-18143-12121 Value is the integer 1. */ pMem->u.i = serial_type-8; pMem->flags = MEM_Int; - return 0; + return; } default: { /* EVIDENCE-OF: R-14606-31564 Value is a BLOB that is (N-12)/2 bytes in @@ -3980,10 +3978,10 @@ u32 sqlite3VdbeSerialGet( pMem->z = (char *)buf; pMem->n = (serial_type-12)/2; pMem->flags = aFlag[serial_type&1]; - return pMem->n; + return; } } - return 0; + return; } /* ** This routine is used to allocate sufficient space for an UnpackedRecord @@ -4046,7 +4044,8 @@ void sqlite3VdbeRecordUnpack( /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ pMem->szMalloc = 0; pMem->z = 0; - d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); + sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); + d += sqlite3VdbeSerialTypeLen(serial_type); pMem++; if( (++u)>=p->nField ) break; } @@ -4130,7 +4129,8 @@ static int vdbeRecordCompareDebug( /* Extract the values to be compared. */ - d1 += sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1); + sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1); + d1 += sqlite3VdbeSerialTypeLen(serial_type1); /* Do the comparison */ diff --git a/src/vdbeblob.c b/src/vdbeblob.c index 512442fd2..a18ee05b5 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -152,10 +152,9 @@ int sqlite3_blob_open( sqlite3_mutex_enter(db->mutex); pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); - do { - memset(&sParse, 0, sizeof(Parse)); + while(1){ + sqlite3ParseObjectInit(&sParse,db); if( !pBlob ) goto blob_open_out; - sParse.db = db; sqlite3DbFree(db, zErr); zErr = 0; @@ -332,7 +331,9 @@ int sqlite3_blob_open( goto blob_open_out; } rc = blobSeekToRow(pBlob, iRow, &zErr); - } while( (++nAttempt)=SQLITE_MAX_SCHEMA_RETRY || rc!=SQLITE_SCHEMA ) break; + sqlite3ParseObjectReset(&sParse); + } blob_open_out: if( rc==SQLITE_OK && db->mallocFailed==0 ){ @@ -343,7 +344,7 @@ int sqlite3_blob_open( } sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); sqlite3DbFree(db, zErr); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; diff --git a/src/vdbemem.c b/src/vdbemem.c index 570a2eb38..adbc5df99 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -596,12 +596,12 @@ static SQLITE_NOINLINE i64 doubleToInt64(double r){ ** ** If pMem represents a string value, its encoding might be changed. */ -static SQLITE_NOINLINE i64 memIntValue(Mem *pMem){ +static SQLITE_NOINLINE i64 memIntValue(const Mem *pMem){ i64 value = 0; sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); return value; } -i64 sqlite3VdbeIntValue(Mem *pMem){ +i64 sqlite3VdbeIntValue(const Mem *pMem){ int flags; assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -916,6 +916,7 @@ void sqlite3VdbeMemSetPointer( void (*xDestructor)(void*) ){ assert( pMem->flags==MEM_Null ); + vdbeMemClear(pMem); pMem->u.zPType = zPType ? zPType : ""; pMem->z = pPtr; pMem->flags = MEM_Null|MEM_Dyn|MEM_Subtype|MEM_Term; @@ -1530,11 +1531,7 @@ static int valueFromExpr( assert( pExpr!=0 ); while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; -#if defined(SQLITE_ENABLE_STAT4) if( op==TK_REGISTER ) op = pExpr->op2; -#else - if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; -#endif /* Compressed expressions only appear when parsing the DEFAULT clause ** on a table column definition, and hence only when pCtx==0. This @@ -1649,7 +1646,7 @@ static int valueFromExpr( no_mem: #ifdef SQLITE_ENABLE_STAT4 - if( pCtx==0 || pCtx->pParse->nErr==0 ) + if( pCtx==0 || NEVER(pCtx->pParse->nErr==0) ) #endif sqlite3OomFault(db); sqlite3DbFree(db, zVal); diff --git a/src/vdbesort.c b/src/vdbesort.c index 8bf7b5717..3958662cc 100644 --- a/src/vdbesort.c +++ b/src/vdbesort.c @@ -960,7 +960,8 @@ int sqlite3VdbeSorterInit( } #endif - assert( pCsr->pKeyInfo && pCsr->pBtx==0 ); + assert( pCsr->pKeyInfo ); + assert( !pCsr->isEphemeral ); assert( pCsr->eCurType==CURTYPE_SORTER ); szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nKeyField-1)*sizeof(CollSeq*); sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); diff --git a/src/vtab.c b/src/vtab.c index 2c787c6c4..b50ccd24a 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -807,7 +807,6 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ VtabCtx *pCtx; int rc = SQLITE_OK; Table *pTab; - char *zErr = 0; Parse sParse; int initBusy; @@ -826,9 +825,9 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ pTab = pCtx->pTab; assert( IsVirtual(pTab) ); - memset(&sParse, 0, sizeof(sParse)); + sqlite3ParseObjectInit(&sParse, db); sParse.eParseMode = PARSE_MODE_DECLARE_VTAB; - sParse.db = db; + sParse.disableTriggers = 1; /* We should never be able to reach this point while loading the ** schema. Nevertheless, defend against that (turn off db->init.busy) ** in case a bug arises. */ @@ -836,11 +835,12 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ initBusy = db->init.busy; db->init.busy = 0; sParse.nQueryLoop = 1; - if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr) - && sParse.pNewTable - && !db->mallocFailed + if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) + && ALWAYS(sParse.pNewTable!=0) + && ALWAYS(!db->mallocFailed) && IsOrdinaryTable(sParse.pNewTable) ){ + assert( sParse.zErrMsg==0 ); if( !pTab->aCol ){ Table *pNew = sParse.pNewTable; Index *pIdx; @@ -870,8 +870,9 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ } pCtx->bDeclared = 1; }else{ - sqlite3ErrorWithMsg(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr); - sqlite3DbFree(db, zErr); + sqlite3ErrorWithMsg(db, SQLITE_ERROR, + (sParse.zErrMsg ? "%s" : 0), sParse.zErrMsg); + sqlite3DbFree(db, sParse.zErrMsg); rc = SQLITE_ERROR; } sParse.eParseMode = PARSE_MODE_NORMAL; @@ -880,7 +881,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ sqlite3VdbeFinalize(sParse.pVdbe); } sqlite3DeleteTable(db, sParse.pNewTable); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); db->init.busy = initBusy; assert( (rc&0xff)==rc ); diff --git a/src/wal.c b/src/wal.c index a0ec91965..fdc4ac39b 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2603,7 +2603,9 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ } /* Allocate a buffer to read frames into */ - szFrame = pWal->hdr.szPage + WAL_FRAME_HDRSIZE; + assert( (pWal->szPage & (pWal->szPage-1))==0 ); + assert( pWal->szPage>=512 && pWal->szPage<=65536 ); + szFrame = pWal->szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame); if( aFrame==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -2617,7 +2619,7 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** the caller. */ aSaveCksum[0] = pWal->hdr.aFrameCksum[0]; aSaveCksum[1] = pWal->hdr.aFrameCksum[1]; - for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->hdr.szPage); + for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->szPage); iOffset+szFrame<=szWal; iOffset+=szFrame ){ diff --git a/src/where.c b/src/where.c index 7a8342675..1e5ac5b75 100644 --- a/src/where.c +++ b/src/where.c @@ -30,8 +30,14 @@ */ typedef struct HiddenIndexInfo HiddenIndexInfo; struct HiddenIndexInfo { - WhereClause *pWC; /* The Where clause being analyzed */ - Parse *pParse; /* The parsing context */ + WhereClause *pWC; /* The Where clause being analyzed */ + Parse *pParse; /* The parsing context */ + int eDistinct; /* Value to return from sqlite3_vtab_distinct() */ + u32 mIn; /* Mask of terms that are IN (...) */ + u32 mHandleIn; /* Terms that vtab will handle as IN (...) */ + sqlite3_value *aRhs[1]; /* RHS values for constraints. MUST BE LAST + ** because extra space is allocated to hold up + ** to nTerm such values */ }; /* Forward declaration of methods */ @@ -234,7 +240,12 @@ static int whereOrInsert( Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){ int i; assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 ); - for(i=0; in; i++){ + assert( pMaskSet->n>0 || pMaskSet->ix[0]<0 ); + assert( iCursor>=-1 ); + if( pMaskSet->ix[0]==iCursor ){ + return 1; + } + for(i=1; in; i++){ if( pMaskSet->ix[i]==iCursor ){ return MASKBIT(i); } @@ -419,16 +430,16 @@ static WhereTerm *whereScanInit( if( pIdx ){ int j = iColumn; iColumn = pIdx->aiColumn[j]; - if( iColumn==XN_EXPR ){ - pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; - pScan->zCollName = pIdx->azColl[j]; - pScan->aiColumn[0] = XN_EXPR; - return whereScanInitIndexExpr(pScan); - }else if( iColumn==pIdx->pTable->iPKey ){ + if( iColumn==pIdx->pTable->iPKey ){ iColumn = XN_ROWID; }else if( iColumn>=0 ){ pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; pScan->zCollName = pIdx->azColl[j]; + }else if( iColumn==XN_EXPR ){ + pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; + pScan->zCollName = pIdx->azColl[j]; + pScan->aiColumn[0] = XN_EXPR; + return whereScanInitIndexExpr(pScan); } }else if( iColumn==XN_EXPR ){ return 0; @@ -671,12 +682,14 @@ static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ int i; if( !sqlite3WhereTrace ) return; for(i=0; inConstraint; i++){ - sqlite3DebugPrintf(" constraint[%d]: col=%d termid=%d op=%d usabled=%d\n", + sqlite3DebugPrintf( + " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", i, p->aConstraint[i].iColumn, p->aConstraint[i].iTermOffset, p->aConstraint[i].op, - p->aConstraint[i].usable); + p->aConstraint[i].usable, + sqlite3_vtab_collation(p,i)); } for(i=0; inOrderBy; i++){ sqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n", @@ -712,9 +725,9 @@ static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ ** index existed. */ static int termCanDriveIndex( - WhereTerm *pTerm, /* WHERE clause term to check */ - SrcItem *pSrc, /* Table we are trying to access */ - Bitmask notReady /* Tables in outer loops of the join */ + const WhereTerm *pTerm, /* WHERE clause term to check */ + const SrcItem *pSrc, /* Table we are trying to access */ + const Bitmask notReady /* Tables in outer loops of the join */ ){ char aff; if( pTerm->leftCursor!=pSrc->iCursor ) return 0; @@ -745,11 +758,11 @@ static int termCanDriveIndex( ** and to set up the WhereLevel object pLevel so that the code generator ** makes use of the automatic index. */ -static void constructAutomaticIndex( +static SQLITE_NOINLINE void constructAutomaticIndex( Parse *pParse, /* The parsing context */ - WhereClause *pWC, /* The WHERE clause */ - SrcItem *pSrc, /* The FROM clause term to get the next index */ - Bitmask notReady, /* Mask of cursors that are not available */ + const WhereClause *pWC, /* The WHERE clause */ + const SrcItem *pSrc, /* The FROM clause term to get the next index */ + const Bitmask notReady, /* Mask of cursors that are not available */ WhereLevel *pLevel /* Write new index here */ ){ int nKeyCol; /* Number of columns in the constructed index */ @@ -791,13 +804,13 @@ static void constructAutomaticIndex( idxCols = 0; for(pTerm=pWC->a; pTermpExpr; - assert( !ExprHasProperty(pExpr, EP_FromJoin) /* prereq always non-zero */ - || pExpr->iRightJoinTable!=pSrc->iCursor /* for the right-hand */ - || pLoop->prereq!=0 ); /* table of a LEFT JOIN */ - if( pLoop->prereq==0 - && (pTerm->wtFlags & TERM_VIRTUAL)==0 - && !ExprHasProperty(pExpr, EP_FromJoin) - && sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) ){ + /* Make the automatic index a partial index if there are terms in the + ** WHERE clause (or the ON clause of a LEFT join) that constrain which + ** rows of the target table (pSrc) that can be used. */ + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && ((pSrc->fg.jointype&JT_LEFT)==0 || ExprHasProperty(pExpr,EP_FromJoin)) + && sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) + ){ pPartial = sqlite3ExprAnd(pParse, pPartial, sqlite3ExprDup(pParse->db, pExpr, 0)); } @@ -904,6 +917,10 @@ static void constructAutomaticIndex( sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); + if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ + pLevel->regFilter = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 10000, pLevel->regFilter); + } /* Fill the automatic index with content */ pTabItem = &pWC->pWInfo->pTabList->a[pLevel->iFrom]; @@ -926,6 +943,10 @@ static void constructAutomaticIndex( regBase = sqlite3GenerateIndexKey( pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0 ); + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, + regBase, pLoop->u.btree.nEq); + } sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); @@ -952,22 +973,149 @@ static void constructAutomaticIndex( } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ +/* +** Generate bytecode that will initialize a Bloom filter that is appropriate +** for pLevel. +** +** If there are inner loops within pLevel that have the WHERE_BLOOMFILTER +** flag set, initialize a Bloomfilter for them as well. Except don't do +** this recursive initialization if the SQLITE_BloomPulldown optimization has +** been turned off. +** +** When the Bloom filter is initialized, the WHERE_BLOOMFILTER flag is cleared +** from the loop, but the regFilter value is set to a register that implements +** the Bloom filter. When regFilter is positive, the +** sqlite3WhereCodeOneLoopStart() will generate code to test the Bloom filter +** and skip the subsequence B-Tree seek if the Bloom filter indicates that +** no matching rows exist. +** +** This routine may only be called if it has previously been determined that +** the loop would benefit from a Bloom filter, and the WHERE_BLOOMFILTER bit +** is set. +*/ +static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( + WhereInfo *pWInfo, /* The WHERE clause */ + int iLevel, /* Index in pWInfo->a[] that is pLevel */ + WhereLevel *pLevel, /* Make a Bloom filter for this FROM term */ + Bitmask notReady /* Loops that are not ready */ +){ + int addrOnce; /* Address of opening OP_Once */ + int addrTop; /* Address of OP_Rewind */ + int addrCont; /* Jump here to skip a row */ + const WhereTerm *pTerm; /* For looping over WHERE clause terms */ + const WhereTerm *pWCEnd; /* Last WHERE clause term */ + Parse *pParse = pWInfo->pParse; /* Parsing context */ + Vdbe *v = pParse->pVdbe; /* VDBE under construction */ + WhereLoop *pLoop = pLevel->pWLoop; /* The loop being coded */ + int iCur; /* Cursor for table getting the filter */ + + assert( pLoop!=0 ); + assert( v!=0 ); + assert( pLoop->wsFlags & WHERE_BLOOMFILTER ); + + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + do{ + const SrcItem *pItem; + const Table *pTab; + u64 sz; + sqlite3WhereExplainBloomFilter(pParse, pWInfo, pLevel); + addrCont = sqlite3VdbeMakeLabel(pParse); + iCur = pLevel->iTabCur; + pLevel->regFilter = ++pParse->nMem; + + /* The Bloom filter is a Blob held in a register. Initialize it + ** to zero-filled blob of at least 80K bits, but maybe more if the + ** estimated size of the table is larger. We could actually + ** measure the size of the table at run-time using OP_Count with + ** P3==1 and use that value to initialize the blob. But that makes + ** testing complicated. By basing the blob size on the value in the + ** sqlite_stat1 table, testing is much easier. + */ + pItem = &pWInfo->pTabList->a[pLevel->iFrom]; + assert( pItem!=0 ); + pTab = pItem->pTab; + assert( pTab!=0 ); + sz = sqlite3LogEstToInt(pTab->nRowLogEst); + if( sz<10000 ){ + sz = 10000; + }else if( sz>10000000 ){ + sz = 10000000; + } + sqlite3VdbeAddOp2(v, OP_Blob, (int)sz, pLevel->regFilter); + + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + pWCEnd = &pWInfo->sWC.a[pWInfo->sWC.nTerm]; + for(pTerm=pWInfo->sWC.a; pTermpExpr; + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsTableConstant(pExpr, iCur) + ){ + sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); + } + } + if( pLoop->wsFlags & WHERE_IPK ){ + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, r1); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, 1); + sqlite3ReleaseTempReg(pParse, r1); + }else{ + Index *pIdx = pLoop->u.btree.pIndex; + int n = pLoop->u.btree.nEq; + int r1 = sqlite3GetTempRange(pParse, n); + int jj; + for(jj=0; jjaiColumn[jj]; + assert( pIdx->pTable==pItem->pTab ); + sqlite3ExprCodeGetColumnOfTable(v, pIdx->pTable, iCur, iCol,r1+jj); + } + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, n); + sqlite3ReleaseTempRange(pParse, r1, n); + } + sqlite3VdbeResolveLabel(v, addrCont); + sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrTop); + pLoop->wsFlags &= ~WHERE_BLOOMFILTER; + if( OptimizationDisabled(pParse->db, SQLITE_BloomPulldown) ) break; + while( ++iLevel < pWInfo->nLevel ){ + const SrcItem *pTabItem; + pLevel = &pWInfo->a[iLevel]; + pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + if( pTabItem->fg.jointype & JT_LEFT ) continue; + pLoop = pLevel->pWLoop; + if( NEVER(pLoop==0) ) continue; + if( pLoop->prereq & notReady ) continue; + if( (pLoop->wsFlags & (WHERE_BLOOMFILTER|WHERE_COLUMN_IN)) + ==WHERE_BLOOMFILTER + ){ + /* This is a candidate for bloom-filter pull-down (early evaluation). + ** The test that WHERE_COLUMN_IN is omitted is important, as we are + ** not able to do early evaluation of bloom filters that make use of + ** the IN operator */ + break; + } + } + }while( iLevel < pWInfo->nLevel ); + sqlite3VdbeJumpHere(v, addrOnce); +} + + #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Allocate and populate an sqlite3_index_info structure. It is the ** responsibility of the caller to eventually release the structure -** by passing the pointer returned by this function to sqlite3_free(). +** by passing the pointer returned by this function to freeIndexInfo(). */ static sqlite3_index_info *allocateIndexInfo( - Parse *pParse, /* The parsing context */ + WhereInfo *pWInfo, /* The WHERE clause */ WhereClause *pWC, /* The WHERE clause being analyzed */ Bitmask mUnusable, /* Ignore terms with these prereqs */ SrcItem *pSrc, /* The FROM clause term that is the vtab */ - ExprList *pOrderBy, /* The ORDER BY clause */ u16 *pmNoOmit /* Mask of terms not to omit */ ){ int i, j; int nTerm; + Parse *pParse = pWInfo->pParse; struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_orderby *pIdxOrderBy; struct sqlite3_index_constraint_usage *pUsage; @@ -976,10 +1124,21 @@ static sqlite3_index_info *allocateIndexInfo( int nOrderBy; sqlite3_index_info *pIdxInfo; u16 mNoOmit = 0; + const Table *pTab; + int eDistinct = 0; + ExprList *pOrderBy = pWInfo->pOrderBy; + + assert( pSrc!=0 ); + pTab = pSrc->pTab; + assert( pTab!=0 ); + assert( IsVirtual(pTab) ); - /* Count the number of possible WHERE clause constraints referring - ** to this virtual table */ + /* Find all WHERE clause constraints referring to this virtual table. + ** Mark each term with the TERM_OK flag. Set nTerm to the number of + ** terms found. + */ for(i=nTerm=0, pTerm=pWC->a; inTerm; i++, pTerm++){ + pTerm->wtFlags &= ~TERM_OK; if( pTerm->leftCursor != pSrc->iCursor ) continue; if( pTerm->prereqRight & mUnusable ) continue; assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); @@ -989,9 +1148,21 @@ static sqlite3_index_info *allocateIndexInfo( testcase( pTerm->eOperator & WO_ALL ); if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); - assert( pTerm->u.x.leftColumn>=(-1) ); + assert( pTerm->u.x.leftColumn>=XN_ROWID ); + assert( pTerm->u.x.leftColumnnCol ); + + /* tag-20191211-002: WHERE-clause constraints are not useful to the + ** right-hand table of a LEFT JOIN. See tag-20191211-001 for the + ** equivalent restriction for ordinary tables. */ + if( (pSrc->fg.jointype & JT_LEFT)!=0 + && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) + ){ + continue; + } nTerm++; + pTerm->wtFlags |= TERM_OK; } /* If the ORDER BY clause contains only columns in the current @@ -1003,11 +1174,47 @@ static sqlite3_index_info *allocateIndexInfo( int n = pOrderBy->nExpr; for(i=0; ia[i].pExpr; - if( pExpr->op!=TK_COLUMN || pExpr->iTable!=pSrc->iCursor ) break; + Expr *pE2; + + /* Skip over constant terms in the ORDER BY clause */ + if( sqlite3ExprIsConstant(pExpr) ){ + continue; + } + + /* Virtual tables are unable to deal with NULLS FIRST */ if( pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL ) break; + + /* First case - a direct column references without a COLLATE operator */ + if( pExpr->op==TK_COLUMN && pExpr->iTable==pSrc->iCursor ){ + assert( pExpr->iColumn>=XN_ROWID && pExpr->iColumnnCol ); + continue; + } + + /* 2nd case - a column reference with a COLLATE operator. Only match + ** of the COLLATE operator matches the collation of the column. */ + if( pExpr->op==TK_COLLATE + && (pE2 = pExpr->pLeft)->op==TK_COLUMN + && pE2->iTable==pSrc->iCursor + ){ + const char *zColl; /* The collating sequence name */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( pExpr->u.zToken!=0 ); + assert( pE2->iColumn>=XN_ROWID && pE2->iColumnnCol ); + pExpr->iColumn = pE2->iColumn; + if( pE2->iColumn<0 ) continue; /* Collseq does not matter for rowid */ + zColl = sqlite3ColumnColl(&pTab->aCol[pE2->iColumn]); + if( zColl==0 ) zColl = sqlite3StrBINARY; + if( sqlite3_stricmp(pExpr->u.zToken, zColl)==0 ) continue; + } + + /* No matches cause a break out of the loop */ + break; } - if( i==n){ + if( i==n ){ nOrderBy = n; + if( (pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY)) ){ + eDistinct = 1 + ((pWInfo->wctrlFlags & WHERE_DISTINCTBY)!=0); + } } } @@ -1015,47 +1222,35 @@ static sqlite3_index_info *allocateIndexInfo( */ pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo) + (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm - + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) ); + + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) + + sizeof(sqlite3_value*)*nTerm ); if( pIdxInfo==0 ){ sqlite3ErrorMsg(pParse, "out of memory"); return 0; } pHidden = (struct HiddenIndexInfo*)&pIdxInfo[1]; - pIdxCons = (struct sqlite3_index_constraint*)&pHidden[1]; + pIdxCons = (struct sqlite3_index_constraint*)&pHidden->aRhs[nTerm]; pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm]; pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy]; - pIdxInfo->nOrderBy = nOrderBy; pIdxInfo->aConstraint = pIdxCons; pIdxInfo->aOrderBy = pIdxOrderBy; pIdxInfo->aConstraintUsage = pUsage; pHidden->pWC = pWC; pHidden->pParse = pParse; + pHidden->eDistinct = eDistinct; + pHidden->mIn = 0; for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){ u16 op; - if( pTerm->leftCursor != pSrc->iCursor ) continue; - if( pTerm->prereqRight & mUnusable ) continue; - assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); - testcase( pTerm->eOperator & WO_IN ); - testcase( pTerm->eOperator & WO_IS ); - testcase( pTerm->eOperator & WO_ISNULL ); - testcase( pTerm->eOperator & WO_ALL ); - if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; - if( pTerm->wtFlags & TERM_VNULL ) continue; - - /* tag-20191211-002: WHERE-clause constraints are not useful to the - ** right-hand table of a LEFT JOIN. See tag-20191211-001 for the - ** equivalent restriction for ordinary tables. */ - if( (pSrc->fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - ){ - continue; - } - assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); - assert( pTerm->u.x.leftColumn>=(-1) ); + if( (pTerm->wtFlags & TERM_OK)==0 ) continue; pIdxCons[j].iColumn = pTerm->u.x.leftColumn; pIdxCons[j].iTermOffset = i; op = pTerm->eOperator & WO_ALL; - if( op==WO_IN ) op = WO_EQ; + if( op==WO_IN ){ + if( (pTerm->wtFlags & TERM_SLICE)==0 ){ + pHidden->mIn |= SMASKBIT32(j); + } + op = WO_EQ; + } if( op==WO_AUX ){ pIdxCons[j].op = pTerm->eMatchOp; }else if( op & (WO_ISNULL|WO_IS) ){ @@ -1088,17 +1283,42 @@ static sqlite3_index_info *allocateIndexInfo( j++; } + assert( j==nTerm ); pIdxInfo->nConstraint = j; - for(i=0; ia[i].pExpr; - pIdxOrderBy[i].iColumn = pExpr->iColumn; - pIdxOrderBy[i].desc = pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC; + if( sqlite3ExprIsConstant(pExpr) ) continue; + assert( pExpr->op==TK_COLUMN + || (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN + && pExpr->iColumn==pExpr->pLeft->iColumn) ); + pIdxOrderBy[j].iColumn = pExpr->iColumn; + pIdxOrderBy[j].desc = pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC; + j++; } + pIdxInfo->nOrderBy = j; *pmNoOmit = mNoOmit; return pIdxInfo; } +/* +** Free an sqlite3_index_info structure allocated by allocateIndexInfo() +** and possibly modified by xBestIndex methods. +*/ +static void freeIndexInfo(sqlite3 *db, sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden; + int i; + assert( pIdxInfo!=0 ); + pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->pParse!=0 ); + assert( pHidden->pParse->db==db ); + for(i=0; inConstraint; i++){ + sqlite3ValueFree(pHidden->aRhs[i]); /* IMP: R-14553-25174 */ + pHidden->aRhs[i] = 0; + } + sqlite3DbFree(db, pIdxInfo); +} + /* ** The table object reference passed as the second argument to this function ** must represent a virtual table. This function invokes the xBestIndex() @@ -1120,7 +1340,9 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ int rc; whereTraceIndexInfoInputs(p); + pParse->db->nSchemaLock++; rc = pVtab->pModule->xBestIndex(pVtab, p); + pParse->db->nSchemaLock--; whereTraceIndexInfoOutputs(p); if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){ @@ -1894,9 +2116,9 @@ void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){ sqlite3_free(z); } if( p->wsFlags & WHERE_SKIPSCAN ){ - sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); + sqlite3DebugPrintf(" f %06x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); }else{ - sqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm); + sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm); } sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); if( p->nLTerm && (sqlite3WhereTrace & 0x100)!=0 ){ @@ -2356,11 +2578,11 @@ static void whereLoopOutputAdjust( LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */ assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); - for(i=pWC->nTerm, pTerm=pWC->a; i>0; i--, pTerm++){ + for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ assert( pTerm!=0 ); - if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) break; - if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; if( (pTerm->prereqAll & notAllowed)!=0 ) continue; + if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; + if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) continue; for(j=pLoop->nLTerm-1; j>=0; j--){ pX = pLoop->aLTerm[j]; if( pX==0 ) continue; @@ -2368,6 +2590,22 @@ static void whereLoopOutputAdjust( if( pX->iParent>=0 && (&pWC->a[pX->iParent])==pTerm ) break; } if( j<0 ){ + if( pLoop->maskSelf==pTerm->prereqAll ){ + /* If there are extra terms in the WHERE clause not used by an index + ** that depend only on the table being scanned, and that will tend to + ** cause many rows to be omitted, then mark that table as + ** "self-culling". + ** + ** 2022-03-24: Self-culling only applies if either the extra terms + ** are straight comparison operators that are non-true with NULL + ** operand, or if the loop is not a LEFT JOIN. + */ + if( (pTerm->eOperator & 0x3f)!=0 + || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0 + ){ + pLoop->wsFlags |= WHERE_SELFCULL; + } + } if( pTerm->truthProb<=0 ){ /* If a truth probability is specified using the likelihood() hints, ** then use the probability provided by the application. */ @@ -2395,7 +2633,9 @@ static void whereLoopOutputAdjust( } } } - if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; + if( pLoop->nOut > nRow-iReduce ){ + pLoop->nOut = nRow - iReduce; + } } /* @@ -2940,7 +3180,7 @@ static int whereUsablePartialIndex( for(i=0, pTerm=pWC->a; inTerm; i++, pTerm++){ Expr *pExpr; pExpr = pTerm->pExpr; - if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab) + if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->w.iRightJoinTable==iTab) && (isLeft==0 || ExprHasProperty(pExpr, EP_FromJoin)) && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) && (pTerm->wtFlags & TERM_VNULL)==0 @@ -3242,6 +3482,15 @@ static int whereLoopAddBtree( #ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return true if pTerm is a virtual table LIMIT or OFFSET term. +*/ +static int isLimitTerm(WhereTerm *pTerm){ + assert( pTerm->eOperator==WO_AUX || pTerm->eMatchOp==0 ); + return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT + && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; +} + /* ** Argument pIdxInfo is already populated with all constraints that may ** be used by the virtual table identified by pBuilder->pNew->iTab. This @@ -3269,9 +3518,11 @@ static int whereLoopAddVirtualOne( u16 mExclude, /* Exclude terms using these operators */ sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */ u16 mNoOmit, /* Do not omit these constraints */ - int *pbIn /* OUT: True if plan uses an IN(...) op */ + int *pbIn, /* OUT: True if plan uses an IN(...) op */ + int *pbRetryLimit /* OUT: Retry without LIMIT/OFFSET */ ){ WhereClause *pWC = pBuilder->pWC; + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage; int i; @@ -3294,6 +3545,7 @@ static int whereLoopAddVirtualOne( pIdxCons->usable = 0; if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight && (pTerm->eOperator & mExclude)==0 + && (pbRetryLimit || !isLimitTerm(pTerm)) ){ pIdxCons->usable = 1; } @@ -3309,6 +3561,7 @@ static int whereLoopAddVirtualOne( pIdxInfo->estimatedRows = 25; pIdxInfo->idxFlags = 0; pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; + pHidden->mHandleIn = 0; /* Invoke the virtual table xBestIndex() method */ rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); @@ -3326,8 +3579,8 @@ static int whereLoopAddVirtualOne( mxTerm = -1; assert( pNew->nLSlot>=nConstraint ); - for(i=0; iaLTerm[i] = 0; - pNew->u.vtab.omitMask = 0; + memset(pNew->aLTerm, 0, sizeof(pNew->aLTerm[0])*nConstraint ); + memset(&pNew->u.vtab, 0, sizeof(pNew->u.vtab)); pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; for(i=0; ieMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET ){ + pNew->u.vtab.bOmitOffset = 1; + } } - if( (pTerm->eOperator & WO_IN)!=0 ){ + if( SMASKBIT32(i) & pHidden->mHandleIn ){ + pNew->u.vtab.mHandleIn |= MASKBIT32(iTerm); + }else if( (pTerm->eOperator & WO_IN)!=0 ){ /* A virtual table that is constrained by an IN clause may not ** consume the ORDER BY clause because (1) the order of IN terms ** is not necessarily related to the order of output terms and @@ -3372,6 +3630,21 @@ static int whereLoopAddVirtualOne( pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; *pbIn = 1; assert( (mExclude & WO_IN)==0 ); } + + if( isLimitTerm(pTerm) && *pbIn ){ + /* If there is an IN(...) term handled as an == (separate call to + ** xFilter for each value on the RHS of the IN) and a LIMIT or + ** OFFSET term handled as well, the plan is unusable. Set output + ** variable *pbRetryLimit to true to tell the caller to retry with + ** LIMIT and OFFSET disabled. */ + if( pIdxInfo->needToFreeIdxStr ){ + sqlite3_free(pIdxInfo->idxStr); + pIdxInfo->idxStr = 0; + pIdxInfo->needToFreeIdxStr = 0; + } + *pbRetryLimit = 1; + return SQLITE_OK; + } } } @@ -3416,11 +3689,19 @@ static int whereLoopAddVirtualOne( } /* -** If this function is invoked from within an xBestIndex() callback, it -** returns a pointer to a buffer containing the name of the collation -** sequence associated with element iCons of the sqlite3_index_info.aConstraint -** array. Or, if iCons is out of range or there is no active xBestIndex -** call, return NULL. +** Return the collating sequence for a constraint passed into xBestIndex. +** +** pIdxInfo must be an sqlite3_index_info structure passed into xBestIndex. +** This routine depends on there being a HiddenIndexInfo structure immediately +** following the sqlite3_index_info structure. +** +** Return a pointer to the collation name: +** +** 1. If there is an explicit COLLATE operator on the constaint, return it. +** +** 2. Else, if the column has an alternative collation, return that. +** +** 3. Otherwise, return "BINARY". */ const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; @@ -3437,6 +3718,88 @@ const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){ return zRet; } +/* +** Return true if constraint iCons is really an IN(...) constraint, or +** false otherwise. If iCons is an IN(...) constraint, set (if bHandle!=0) +** or clear (if bHandle==0) the flag to handle it using an iterator. +*/ +int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + u32 m = SMASKBIT32(iCons); + if( m & pHidden->mIn ){ + if( bHandle==0 ){ + pHidden->mHandleIn &= ~m; + }else if( bHandle>0 ){ + pHidden->mHandleIn |= m; + } + return 1; + } + return 0; +} + +/* +** This interface is callable from within the xBestIndex callback only. +** +** If possible, set (*ppVal) to point to an object containing the value +** on the right-hand-side of constraint iCons. +*/ +int sqlite3_vtab_rhs_value( + sqlite3_index_info *pIdxInfo, /* Copy of first argument to xBestIndex */ + int iCons, /* Constraint for which RHS is wanted */ + sqlite3_value **ppVal /* Write value extracted here */ +){ + HiddenIndexInfo *pH = (HiddenIndexInfo*)&pIdxInfo[1]; + sqlite3_value *pVal = 0; + int rc = SQLITE_OK; + if( iCons<0 || iCons>=pIdxInfo->nConstraint ){ + rc = SQLITE_MISUSE; /* EV: R-30545-25046 */ + }else{ + if( pH->aRhs[iCons]==0 ){ + WhereTerm *pTerm = &pH->pWC->a[pIdxInfo->aConstraint[iCons].iTermOffset]; + rc = sqlite3ValueFromExpr( + pH->pParse->db, pTerm->pExpr->pRight, ENC(pH->pParse->db), + SQLITE_AFF_BLOB, &pH->aRhs[iCons] + ); + testcase( rc!=SQLITE_OK ); + } + pVal = pH->aRhs[iCons]; + } + *ppVal = pVal; + + if( rc==SQLITE_OK && pVal==0 ){ /* IMP: R-19933-32160 */ + rc = SQLITE_NOTFOUND; /* IMP: R-36424-56542 */ + } + + return rc; +} + +/* +** Return true if ORDER BY clause may be handled as DISTINCT. +*/ +int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->eDistinct==0 + || pHidden->eDistinct==1 + || pHidden->eDistinct==2 ); + return pHidden->eDistinct; +} + +#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ + && !defined(SQLITE_OMIT_VIRTUALTABLE) +/* +** Cause the prepared statement that is associated with a call to +** xBestIndex to open write transactions on all attached schemas. +** This is used by the (built-in) sqlite_dbpage virtual table. +*/ +void sqlite3VtabWriteAll(sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + Parse *pParse = pHidden->pParse; + int nDb = pParse->db->nDb; + int i; + for(i=0; ipNew->iTab. That table is guaranteed to be a virtual table. @@ -3478,6 +3841,7 @@ static int whereLoopAddVirtual( WhereLoop *pNew; Bitmask mBest; /* Tables used by best possible plan */ u16 mNoOmit; + int bRetry = 0; /* True to retry with LIMIT/OFFSET disabled */ assert( (mPrereq & mUnusable)==0 ); pWInfo = pBuilder->pWInfo; @@ -3486,8 +3850,7 @@ static int whereLoopAddVirtual( pNew = pBuilder->pNew; pSrc = &pWInfo->pTabList->a[pNew->iTab]; assert( IsVirtual(pSrc->pTab) ); - p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy, - &mNoOmit); + p = allocateIndexInfo(pWInfo, pWC, mUnusable, pSrc, &mNoOmit); if( p==0 ) return SQLITE_NOMEM_BKPT; pNew->rSetup = 0; pNew->wsFlags = WHERE_VIRTUALTABLE; @@ -3495,14 +3858,22 @@ static int whereLoopAddVirtual( pNew->u.vtab.needFree = 0; nConstraint = p->nConstraint; if( whereLoopResize(pParse->db, pNew, nConstraint) ){ - sqlite3DbFree(pParse->db, p); + freeIndexInfo(pParse->db, p); return SQLITE_NOMEM_BKPT; } /* First call xBestIndex() with all constraints usable. */ WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName)); WHERETRACE(0x40, (" VirtualOne: all usable\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, &bRetry + ); + if( bRetry ){ + assert( rc==SQLITE_OK ); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, 0 + ); + } /* If the call to xBestIndex() with all terms enabled produced a plan ** that does not require any source tables (IOW: a plan with mBest==0) @@ -3520,7 +3891,7 @@ static int whereLoopAddVirtual( if( bIn ){ WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn); + pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn, 0); assert( bIn==0 ); mBestNoIn = pNew->prereq & ~mPrereq; if( mBestNoIn==0 ){ @@ -3547,7 +3918,7 @@ static int whereLoopAddVirtual( WHERETRACE(0x40, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext)); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn); + pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn, 0); if( pNew->prereq==mPrereq ){ seenZero = 1; if( bIn==0 ) seenZeroNoIN = 1; @@ -3560,7 +3931,7 @@ static int whereLoopAddVirtual( if( rc==SQLITE_OK && seenZero==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn); + pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn, 0); if( bIn==0 ) seenZeroNoIN = 1; } @@ -3570,12 +3941,12 @@ static int whereLoopAddVirtual( if( rc==SQLITE_OK && seenZeroNoIN==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn); + pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn, 0); } } if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr); - sqlite3DbFreeNN(pParse->db, p); + freeIndexInfo(pParse->db, p); WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc)); return rc; } @@ -3619,7 +3990,6 @@ static int whereLoopAddOr( int i, j; sSubBuild = *pBuilder; - sSubBuild.pOrderBy = 0; sSubBuild.pOrSet = &sCur; WHERETRACE(0x200, ("Begin processing OR-clause %p\n", pTerm)); @@ -3631,6 +4001,7 @@ static int whereLoopAddOr( tempWC.pOuter = pWC; tempWC.op = TK_AND; tempWC.nTerm = 1; + tempWC.nBase = 1; tempWC.a = pOrTerm; sSubBuild.pWC = &tempWC; }else{ @@ -4738,6 +5109,150 @@ static void showAllWhereLoops(WhereInfo *pWInfo, WhereClause *pWC){ # define WHERETRACE_ALL_LOOPS(W,C) #endif +/* Attempt to omit tables from a join that do not affect the result. +** For a table to not affect the result, the following must be true: +** +** 1) The query must not be an aggregate. +** 2) The table must be the RHS of a LEFT JOIN. +** 3) Either the query must be DISTINCT, or else the ON or USING clause +** must contain a constraint that limits the scan of the table to +** at most a single row. +** 4) The table must not be referenced by any part of the query apart +** from its own USING or ON clause. +** +** For example, given: +** +** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); +** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); +** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); +** +** then table t2 can be omitted from the following: +** +** SELECT v1, v3 FROM t1 +** LEFT JOIN t2 ON (t1.ipk=t2.ipk) +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +** +** or from: +** +** SELECT DISTINCT v1, v3 FROM t1 +** LEFT JOIN t2 +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +*/ +static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( + WhereInfo *pWInfo, + Bitmask notReady +){ + int i; + Bitmask tabUsed; + + /* Preconditions checked by the caller */ + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_OmitNoopJoin) ); + + /* These two preconditions checked by the caller combine to guarantee + ** condition (1) of the header comment */ + assert( pWInfo->pResultSet!=0 ); + assert( 0==(pWInfo->wctrlFlags & WHERE_AGG_DISTINCT) ); + + tabUsed = sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pResultSet); + if( pWInfo->pOrderBy ){ + tabUsed |= sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pOrderBy); + } + for(i=pWInfo->nLevel-1; i>=1; i--){ + WhereTerm *pTerm, *pEnd; + SrcItem *pItem; + WhereLoop *pLoop; + pLoop = pWInfo->a[i].pWLoop; + pItem = &pWInfo->pTabList->a[pLoop->iTab]; + if( (pItem->fg.jointype & JT_LEFT)==0 ) continue; + if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)==0 + && (pLoop->wsFlags & WHERE_ONEROW)==0 + ){ + continue; + } + if( (tabUsed & pLoop->maskSelf)!=0 ) continue; + pEnd = pWInfo->sWC.a + pWInfo->sWC.nTerm; + for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ + if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin) + || pTerm->pExpr->w.iRightJoinTable!=pItem->iCursor + ){ + break; + } + } + } + if( pTerm drop loop %c not used\n", pLoop->cId)); + notReady &= ~pLoop->maskSelf; + for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ + pTerm->wtFlags |= TERM_CODED; + } + } + if( i!=pWInfo->nLevel-1 ){ + int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); + memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); + } + pWInfo->nLevel--; + assert( pWInfo->nLevel>0 ); + } + return notReady; +} + +/* +** Check to see if there are any SEARCH loops that might benefit from +** using a Bloom filter. Consider a Bloom filter if: +** +** (1) The SEARCH happens more than N times where N is the number +** of rows in the table that is being considered for the Bloom +** filter. +** (2) Some searches are expected to find zero rows. (This is determined +** by the WHERE_SELFCULL flag on the term.) +** (3) Bloom-filter processing is not disabled. (Checked by the +** caller.) +** (4) The size of the table being searched is known by ANALYZE. +** +** This block of code merely checks to see if a Bloom filter would be +** appropriate, and if so sets the WHERE_BLOOMFILTER flag on the +** WhereLoop. The implementation of the Bloom filter comes further +** down where the code for each WhereLoop is generated. +*/ +static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( + const WhereInfo *pWInfo +){ + int i; + LogEst nSearch; + + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_BloomFilter) ); + nSearch = pWInfo->a[0].pWLoop->nOut; + for(i=1; inLevel; i++){ + WhereLoop *pLoop = pWInfo->a[i].pWLoop; + const unsigned int reqFlags = (WHERE_SELFCULL|WHERE_COLUMN_EQ); + if( (pLoop->wsFlags & reqFlags)==reqFlags + /* vvvvvv--- Always the case if WHERE_COLUMN_EQ is defined */ + && ALWAYS((pLoop->wsFlags & (WHERE_IPK|WHERE_INDEXED))!=0) + ){ + SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab]; + Table *pTab = pItem->pTab; + pTab->tabFlags |= TF_StatsUsed; + if( nSearch > pTab->nRowLogEst + && (pTab->tabFlags & TF_HasStat1)!=0 + ){ + testcase( pItem->fg.jointype & JT_LEFT ); + pLoop->wsFlags |= WHERE_BLOOMFILTER; + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + WHERETRACE(0xffff, ( + "-> use Bloom-filter on loop %c because there are ~%.1e " + "lookups into %s which has only ~%.1e rows\n", + pLoop->cId, (double)sqlite3LogEstToInt(nSearch), pTab->zName, + (double)sqlite3LogEstToInt(pTab->nRowLogEst))); + } + } + nSearch += pLoop->nOut; + } +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -4832,6 +5347,7 @@ WhereInfo *sqlite3WhereBegin( Expr *pWhere, /* The WHERE clause */ ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */ ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */ + Select *pLimit, /* Use this LIMIT/OFFSET clause, if any */ u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */ int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number ** If WHERE_USE_LIMIT, then the limit amount */ @@ -4866,13 +5382,6 @@ WhereInfo *sqlite3WhereBegin( /* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */ testcase( pOrderBy && pOrderBy->nExpr==BMS-1 ); if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0; - sWLB.pOrderBy = pOrderBy; - - /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via - ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ - if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ - wctrlFlags &= ~WHERE_WANT_DISTINCT; - } /* The number of tables in the FROM clause is limited by the number of ** bits in a Bitmask @@ -4915,11 +5424,18 @@ WhereInfo *sqlite3WhereBegin( pWInfo->wctrlFlags = wctrlFlags; pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; +#ifndef SQLITE_OMIT_VIRTUALTABLE + pWInfo->pLimit = pLimit; +#endif memset(&pWInfo->nOBSat, 0, offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ pMaskSet = &pWInfo->sMaskSet; + pMaskSet->n = 0; + pMaskSet->ix[0] = -99; /* Initialize ix[0] to a value that can never be + ** a valid cursor number, to avoid an initial + ** test for pMaskSet->n==0 in sqlite3WhereGetMask() */ sWLB.pWInfo = pWInfo; sWLB.pWC = &pWInfo->sWC; sWLB.pNew = (WhereLoop*)(((char*)pWInfo)+nByteWInfo); @@ -4932,7 +5448,6 @@ WhereInfo *sqlite3WhereBegin( /* Split the WHERE clause into separate subexpressions where each ** subexpression is separated by an AND operator. */ - initMaskSet(pMaskSet); sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo); sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND); @@ -4940,7 +5455,9 @@ WhereInfo *sqlite3WhereBegin( */ if( nTabList==0 ){ if( pOrderBy ) pWInfo->nOBSat = pOrderBy->nExpr; - if( wctrlFlags & WHERE_WANT_DISTINCT ){ + if( (wctrlFlags & WHERE_WANT_DISTINCT)!=0 + && OptimizationEnabled(db, SQLITE_DistinctOpt) + ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); @@ -4978,6 +5495,7 @@ WhereInfo *sqlite3WhereBegin( /* Analyze all of the subexpressions. */ sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC); + sqlite3WhereAddLimit(&pWInfo->sWC, pLimit); if( db->mallocFailed ) goto whereBeginError; /* Special case: WHERE terms that do not refer to any tables in the join @@ -4991,7 +5509,7 @@ WhereInfo *sqlite3WhereBegin( ** FROM ... WHERE random()>0; -- eval random() once per row ** FROM ... WHERE (SELECT random())>0; -- eval random() once overall */ - for(ii=0; iinTerm; ii++){ + for(ii=0; iinBase; ii++){ WhereTerm *pT = &sWLB.pWC->a[ii]; if( pT->wtFlags & TERM_VIRTUAL ) continue; if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){ @@ -5001,7 +5519,12 @@ WhereInfo *sqlite3WhereBegin( } if( wctrlFlags & WHERE_WANT_DISTINCT ){ - if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ + if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ + /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via + ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ + wctrlFlags &= ~WHERE_WANT_DISTINCT; + pWInfo->wctrlFlags &= ~WHERE_WANT_DISTINCT; + }else if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ /* The DISTINCT marking is pointless. Ignore it. */ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; }else if( pOrderBy==0 ){ @@ -5072,9 +5595,10 @@ WhereInfo *sqlite3WhereBegin( if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ pWInfo->revMask = ALLBITS; } - if( pParse->nErr || db->mallocFailed ){ + if( pParse->nErr ){ goto whereBeginError; } + assert( db->mallocFailed==0 ); #ifdef WHERETRACE_ENABLED if( sqlite3WhereTrace ){ sqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut); @@ -5102,34 +5626,15 @@ WhereInfo *sqlite3WhereBegin( } #endif - /* Attempt to omit tables from the join that do not affect the result. - ** For a table to not affect the result, the following must be true: - ** - ** 1) The query must not be an aggregate. - ** 2) The table must be the RHS of a LEFT JOIN. - ** 3) Either the query must be DISTINCT, or else the ON or USING clause - ** must contain a constraint that limits the scan of the table to - ** at most a single row. - ** 4) The table must not be referenced by any part of the query apart - ** from its own USING or ON clause. - ** - ** For example, given: - ** - ** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); - ** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); - ** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); - ** - ** then table t2 can be omitted from the following: - ** - ** SELECT v1, v3 FROM t1 - ** LEFT JOIN t2 ON (t1.ipk=t2.ipk) - ** LEFT JOIN t3 ON (t1.ipk=t3.ipk) - ** - ** or from: + /* Attempt to omit tables from a join that do not affect the result. + ** See the comment on whereOmitNoopJoin() for further information. ** - ** SELECT DISTINCT v1, v3 FROM t1 - ** LEFT JOIN t2 - ** LEFT JOIN t3 ON (t1.ipk=t3.ipk) + ** This query optimization is factored out into a separate "no-inline" + ** procedure to keep the sqlite3WhereBegin() procedure from becoming + ** too large. If sqlite3WhereBegin() becomes too large, that prevents + ** some C-compiler optimizers from in-lining the + ** sqlite3WhereCodeOneLoopStart() procedure, and it is important to + ** in-line sqlite3WhereCodeOneLoopStart() for performance reasons. */ notReady = ~(Bitmask)0; if( pWInfo->nLevel>=2 @@ -5137,49 +5642,20 @@ WhereInfo *sqlite3WhereBegin( && 0==(wctrlFlags & WHERE_AGG_DISTINCT) /* condition (1) above */ && OptimizationEnabled(db, SQLITE_OmitNoopJoin) ){ - int i; - Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pResultSet); - if( sWLB.pOrderBy ){ - tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy); - } - for(i=pWInfo->nLevel-1; i>=1; i--){ - WhereTerm *pTerm, *pEnd; - SrcItem *pItem; - pLoop = pWInfo->a[i].pWLoop; - pItem = &pWInfo->pTabList->a[pLoop->iTab]; - if( (pItem->fg.jointype & JT_LEFT)==0 ) continue; - if( (wctrlFlags & WHERE_WANT_DISTINCT)==0 - && (pLoop->wsFlags & WHERE_ONEROW)==0 - ){ - continue; - } - if( (tabUsed & pLoop->maskSelf)!=0 ) continue; - pEnd = sWLB.pWC->a + sWLB.pWC->nTerm; - for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){ - if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - || pTerm->pExpr->iRightJoinTable!=pItem->iCursor - ){ - break; - } - } - } - if( pTerm drop loop %c not used\n", pLoop->cId)); - notReady &= ~pLoop->maskSelf; - for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){ - pTerm->wtFlags |= TERM_CODED; - } - } - if( i!=pWInfo->nLevel-1 ){ - int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); - memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); - } - pWInfo->nLevel--; - nTabList--; - } + notReady = whereOmitNoopJoin(pWInfo, notReady); + nTabList = pWInfo->nLevel; + assert( nTabList>0 ); + } + + /* Check to see if there are any SEARCH loops that might benefit from + ** using a Bloom filter. + */ + if( pWInfo->nLevel>=2 + && OptimizationEnabled(db, SQLITE_BloomFilter) + ){ + whereCheckIfBloomFilterIsUseful(pWInfo); } + #if defined(WHERETRACE_ENABLED) if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */ sqlite3DebugPrintf("---- WHERE clause at end of analysis:\n"); @@ -5266,6 +5742,7 @@ WhereInfo *sqlite3WhereBegin( if( pWInfo->eOnePass==ONEPASS_OFF && pTab->nColtabFlags & (TF_HasGenerated|TF_WithoutRowid))==0 + && (pLoop->wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))==0 ){ /* If we know that only a prefix of the record will be used, ** it is advantageous to reduce the "column count" field in @@ -5368,13 +5845,17 @@ WhereInfo *sqlite3WhereBegin( if( pParse->nErr ) goto whereBeginError; pLevel = &pWInfo->a[ii]; wsFlags = pLevel->pWLoop->wsFlags; + if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){ + if( (wsFlags & WHERE_AUTO_INDEX)!=0 ){ #ifndef SQLITE_OMIT_AUTOMATIC_INDEX - if( (pLevel->pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 ){ - constructAutomaticIndex(pParse, &pWInfo->sWC, - &pTabList->a[pLevel->iFrom], notReady, pLevel); + constructAutomaticIndex(pParse, &pWInfo->sWC, + &pTabList->a[pLevel->iFrom], notReady, pLevel); +#endif + }else{ + sqlite3ConstructBloomFilter(pWInfo, ii, pLevel, notReady); + } if( db->mallocFailed ) goto whereBeginError; } -#endif addrExplain = sqlite3WhereExplainOneScan( pParse, pTabList, pLevel, wctrlFlags ); @@ -5422,6 +5903,26 @@ WhereInfo *sqlite3WhereBegin( } #endif +#ifdef SQLITE_DEBUG +/* +** Return true if cursor iCur is opened by instruction k of the +** bytecode. Used inside of assert() only. +*/ +static int cursorIsOpen(Vdbe *v, int iCur, int k){ + while( k>=0 ){ + VdbeOp *pOp = sqlite3VdbeGetOp(v,k--); + if( pOp->p1!=iCur ) continue; + if( pOp->opcode==OP_Close ) return 0; + if( pOp->opcode==OP_OpenRead ) return 1; + if( pOp->opcode==OP_OpenWrite ) return 1; + if( pOp->opcode==OP_OpenDup ) return 1; + if( pOp->opcode==OP_OpenAutoindex ) return 1; + if( pOp->opcode==OP_OpenEphemeral ) return 1; + } + return 0; +} +#endif /* SQLITE_DEBUG */ + /* ** Generate the end of the WHERE loop. See comments on ** sqlite3WhereBegin() for additional information. @@ -5674,6 +6175,11 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ ){ int x = pOp->p2; assert( pIdx->pTable==pTab ); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + if( pOp->opcode==OP_Offset ){ + /* Do not need to translate the column number */ + }else +#endif if( !HasRowid(pTab) ){ Index *pPk = sqlite3PrimaryKeyIndex(pTab); x = pPk->aiColumn[x]; @@ -5687,9 +6193,22 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ pOp->p2 = x; pOp->p1 = pLevel->iIdxCur; OpcodeRewriteTrace(db, k, pOp); + }else{ + /* Unable to translate the table reference into an index + ** reference. Verify that this is harmless - that the + ** table being referenced really is open. + */ +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 + || cursorIsOpen(v,pOp->p1,k) + || pOp->opcode==OP_Offset + ); +#else + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 + || cursorIsOpen(v,pOp->p1,k) + ); +#endif } - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 - || pWInfo->eOnePass ); }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; pOp->opcode = OP_IdxRowid; diff --git a/src/whereInt.h b/src/whereInt.h index f651e790c..3fc39f6b4 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -64,6 +64,7 @@ struct WhereLevel { u32 iLikeRepCntr; /* LIKE range processing counter register (times 2) */ int addrLikeRep; /* LIKE range processing address */ #endif + int regFilter; /* Bloom filter */ u8 iFrom; /* Which entry in the FROM clause */ u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */ int p1, p2; /* Operands of the opcode used to end the loop */ @@ -122,10 +123,12 @@ struct WhereLoop { } btree; struct { /* Information for virtual tables */ int idxNum; /* Index number */ - u8 needFree; /* True if sqlite3_free(idxStr) is needed */ + u32 needFree : 1; /* True if sqlite3_free(idxStr) is needed */ + u32 bOmitOffset : 1; /* True to let virtual table handle offset */ i8 isOrdered; /* True if satisfies ORDER BY */ u16 omitMask; /* Terms that may be omitted */ char *idxStr; /* Index identifier string */ + u32 mHandleIn; /* Terms to handle as IN(...) instead of == */ } vtab; } u; u32 wsFlags; /* WHERE_* flags describing the plan */ @@ -269,7 +272,7 @@ struct WhereTerm { #define TERM_COPIED 0x0008 /* Has a child */ #define TERM_ORINFO 0x0010 /* Need to free the WhereTerm.u.pOrInfo object */ #define TERM_ANDINFO 0x0020 /* Need to free the WhereTerm.u.pAndInfo obj */ -#define TERM_OR_OK 0x0040 /* Used during OR-clause processing */ +#define TERM_OK 0x0040 /* Used during OR-clause processing */ #define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */ #define TERM_LIKEOPT 0x0100 /* Virtual terms from the LIKE optimization */ #define TERM_LIKECOND 0x0200 /* Conditionally this LIKE operator term */ @@ -282,6 +285,7 @@ struct WhereTerm { #else # define TERM_HIGHTRUTH 0 /* Only used with STAT4 */ #endif +#define TERM_SLICE 0x8000 /* One slice of a row-value/vector comparison */ /* ** An instance of the WhereScan object is used as an iterator for locating @@ -292,11 +296,11 @@ struct WhereScan { WhereClause *pWC; /* WhereClause currently being scanned */ const char *zCollName; /* Required collating sequence, if not NULL */ Expr *pIdxExpr; /* Search for this index expression */ + int k; /* Resume scanning at this->pWC->a[this->k] */ + u32 opMask; /* Acceptable operators */ char idxaff; /* Must match this affinity, if zCollName!=NULL */ + unsigned char iEquiv; /* Current slot in aiCur[] and aiColumn[] */ unsigned char nEquiv; /* Number of entries in aiCur[] and aiColumn[] */ - unsigned char iEquiv; /* Next unused slot in aiCur[] and aiColumn[] */ - u32 opMask; /* Acceptable operators */ - int k; /* Resume scanning at this->pWC->a[this->k] */ int aiCur[11]; /* Cursors in the equivalence class */ i16 aiColumn[11]; /* Corresponding column number in the eq-class */ }; @@ -320,6 +324,7 @@ struct WhereClause { u8 hasOr; /* True if any a[].eOperator is WO_OR */ int nTerm; /* Number of terms */ int nSlot; /* Number of entries in a[] */ + int nBase; /* Number of terms through the last non-Virtual */ WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */ #if defined(SQLITE_SMALL_STACK) WhereTerm aStatic[1]; /* Initial static space for a[] */ @@ -377,11 +382,6 @@ struct WhereMaskSet { int ix[BMS]; /* Cursor assigned to each bit */ }; -/* -** Initialize a WhereMaskSet object -*/ -#define initMaskSet(P) (P)->n=0 - /* ** This object is a convenience wrapper holding all information needed ** to construct WhereLoop objects for a particular query. @@ -389,7 +389,6 @@ struct WhereMaskSet { struct WhereLoopBuilder { WhereInfo *pWInfo; /* Information about this WHERE */ WhereClause *pWC; /* WHERE clause terms */ - ExprList *pOrderBy; /* ORDER BY clause */ WhereLoop *pNew; /* Template WhereLoop */ WhereOrSet *pOrSet; /* Record best loops here, if not NULL */ #ifdef SQLITE_ENABLE_STAT4 @@ -457,6 +456,9 @@ struct WhereInfo { ExprList *pOrderBy; /* The ORDER BY clause or NULL */ ExprList *pResultSet; /* Result set of the query */ Expr *pWhere; /* The complete WHERE clause */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + Select *pLimit; /* Used to access LIMIT expr/registers for vtabs */ +#endif int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ int iContinue; /* Jump here to continue with next record */ int iBreak; /* Jump here to break out of the loop */ @@ -510,8 +512,14 @@ int sqlite3WhereExplainOneScan( WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ ); +int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +); #else # define sqlite3WhereExplainOneScan(u,v,w,x) 0 +# define sqlite3WhereExplainBloomFilter(u,v,w) 0 #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS void sqlite3WhereAddScanStatus( @@ -536,6 +544,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( void sqlite3WhereClauseInit(WhereClause*,WhereInfo*); void sqlite3WhereClauseClear(WhereClause*); void sqlite3WhereSplit(WhereClause*,Expr*,u8); +void sqlite3WhereAddLimit(WhereClause*, Select*); Bitmask sqlite3WhereExprUsage(WhereMaskSet*, Expr*); Bitmask sqlite3WhereExprUsageNN(WhereMaskSet*, Expr*); Bitmask sqlite3WhereExprListUsage(WhereMaskSet*, ExprList*); @@ -604,5 +613,8 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); #define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */ #define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */ #define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */ +#define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ +#define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ +#define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ #endif /* !defined(SQLITE_WHEREINT_H) */ diff --git a/src/wherecode.c b/src/wherecode.c index 460ac4fe3..6944c3acd 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -176,19 +176,27 @@ int sqlite3WhereExplainOneScan( explainIndexRange(&str, pLoop); } }else if( (flags & WHERE_IPK)!=0 && (flags & WHERE_CONSTRAINT)!=0 ){ - const char *zRangeOp; + char cRangeOp; +#if 0 /* Better output, but breaks many tests */ + const Table *pTab = pItem->pTab; + const char *zRowid = pTab->iPKey>=0 ? pTab->aCol[pTab->iPKey].zCnName: + "rowid"; +#else + const char *zRowid = "rowid"; +#endif + sqlite3_str_appendf(&str, " USING INTEGER PRIMARY KEY (%s", zRowid); if( flags&(WHERE_COLUMN_EQ|WHERE_COLUMN_IN) ){ - zRangeOp = "="; + cRangeOp = '='; }else if( (flags&WHERE_BOTH_LIMIT)==WHERE_BOTH_LIMIT ){ - zRangeOp = ">? AND rowid<"; + sqlite3_str_appendf(&str, ">? AND %s", zRowid); + cRangeOp = '<'; }else if( flags&WHERE_BTM_LIMIT ){ - zRangeOp = ">"; + cRangeOp = '>'; }else{ assert( flags&WHERE_TOP_LIMIT); - zRangeOp = "<"; + cRangeOp = '<'; } - sqlite3_str_appendf(&str, - " USING INTEGER PRIMARY KEY (rowid%s?)",zRangeOp); + sqlite3_str_appendf(&str, "%c?)", cRangeOp); } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( (flags & WHERE_VIRTUALTABLE)!=0 ){ @@ -211,6 +219,56 @@ int sqlite3WhereExplainOneScan( } return ret; } + +/* +** Add a single OP_Explain opcode that describes a Bloom filter. +** +** Or if not processing EXPLAIN QUERY PLAN and not in a SQLITE_DEBUG and/or +** SQLITE_ENABLE_STMT_SCANSTATUS build, then OP_Explain opcodes are not +** required and this routine is a no-op. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. +*/ +int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +){ + int ret = 0; + SrcItem *pItem = &pWInfo->pTabList->a[pLevel->iFrom]; + Vdbe *v = pParse->pVdbe; /* VM being constructed */ + sqlite3 *db = pParse->db; /* Database handle */ + char *zMsg; /* Text to add to EQP output */ + int i; /* Loop counter */ + WhereLoop *pLoop; /* The where loop */ + StrAccum str; /* EQP output string */ + char zBuf[100]; /* Initial space for EQP output string */ + + sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "BLOOM FILTER ON %S (", pItem); + pLoop = pLevel->pWLoop; + if( pLoop->wsFlags & WHERE_IPK ){ + const Table *pTab = pItem->pTab; + if( pTab->iPKey>=0 ){ + sqlite3_str_appendf(&str, "%s=?", pTab->aCol[pTab->iPKey].zCnName); + }else{ + sqlite3_str_appendf(&str, "rowid=?"); + } + }else{ + for(i=pLoop->nSkip; iu.btree.nEq; i++){ + const char *z = explainIndexColumnName(pLoop->u.btree.pIndex, i); + if( i>pLoop->nSkip ) sqlite3_str_append(&str, " AND ", 5); + sqlite3_str_appendf(&str, "%s=?", z); + } + } + sqlite3_str_append(&str, ")", 1); + zMsg = sqlite3StrAccumFinish(&str); + ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), + pParse->addrExplain, 0, zMsg,P4_DYNAMIC); + return ret; +} #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS @@ -734,6 +792,7 @@ static int codeAllEqualityTerms( VdbeCoverageIf(v, bRev!=0); VdbeComment((v, "begin skip-scan on %s", pIdx->zName)); j = sqlite3VdbeAddOp0(v, OP_Goto); + assert( pLevel->addrSkip==0 ); pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT), iIdxCur, 0, regBase, nSkip); VdbeCoverageIf(v, bRev==0); @@ -766,6 +825,9 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j); } } + } + for(j=nSkip; jaLTerm[j]; if( pTerm->eOperator & WO_IN ){ if( pTerm->pExpr->flags & EP_xIsSelect ){ /* No affinity ever needs to be (or should be) applied to a value @@ -780,7 +842,8 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk); VdbeCoverage(v); } - if( pParse->db->mallocFailed==0 && pParse->nErr==0 ){ + if( pParse->nErr==0 ){ + assert( pParse->db->mallocFailed==0 ); if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){ zAff[j] = SQLITE_AFF_BLOB; } @@ -970,7 +1033,7 @@ static void codeCursorHint( sWalker.pParse = pParse; sWalker.u.pCCurHint = &sHint; pWC = &pWInfo->sWC; - for(i=0; inTerm; i++){ + for(i=0; inBase; i++){ pTerm = &pWC->a[i]; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( pTerm->prereqAll & pLevel->notReady ) continue; @@ -1000,7 +1063,7 @@ static void codeCursorHint( if( pTabItem->fg.jointype & JT_LEFT ){ Expr *pExpr = pTerm->pExpr; if( !ExprHasProperty(pExpr, EP_FromJoin) - || pExpr->iRightJoinTable!=pTabItem->iCursor + || pExpr->w.iRightJoinTable!=pTabItem->iCursor ){ sWalker.eCode = 0; sWalker.xExprCallback = codeCursorHintIsOrFunction; @@ -1301,6 +1364,65 @@ static void whereApplyPartialIndexConstraints( } } +/* +** This routine is called right after An OP_Filter has been generated and +** before the corresponding index search has been performed. This routine +** checks to see if there are additional Bloom filters in inner loops that +** can be checked prior to doing the index lookup. If there are available +** inner-loop Bloom filters, then evaluate those filters now, before the +** index lookup. The idea is that a Bloom filter check is way faster than +** an index lookup, and the Bloom filter might return false, meaning that +** the index lookup can be skipped. +** +** We know that an inner loop uses a Bloom filter because it has the +** WhereLevel.regFilter set. If an inner-loop Bloom filter is checked, +** then clear the WhereLevel.regFilter value to prevent the Bloom filter +** from being checked a second time when the inner loop is evaluated. +*/ +static SQLITE_NOINLINE void filterPullDown( + Parse *pParse, /* Parsing context */ + WhereInfo *pWInfo, /* Complete information about the WHERE clause */ + int iLevel, /* Which level of pWInfo->a[] should be coded */ + int addrNxt, /* Jump here to bypass inner loops */ + Bitmask notReady /* Loops that are not ready */ +){ + while( ++iLevel < pWInfo->nLevel ){ + WhereLevel *pLevel = &pWInfo->a[iLevel]; + WhereLoop *pLoop = pLevel->pWLoop; + if( pLevel->regFilter==0 ) continue; + if( pLevel->pWLoop->nSkip ) continue; + /* ,--- Because sqlite3ConstructBloomFilter() has will not have set + ** vvvvv--' pLevel->regFilter if this were true. */ + if( NEVER(pLoop->prereq & notReady) ) continue; + if( pLoop->wsFlags & WHERE_IPK ){ + WhereTerm *pTerm = pLoop->aLTerm[0]; + int regRowid; + assert( pTerm!=0 ); + assert( pTerm->pExpr!=0 ); + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + regRowid = sqlite3GetTempReg(pParse); + regRowid = codeEqualityTerm(pParse, pTerm, pLevel, 0, 0, regRowid); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, regRowid, 1); + VdbeCoverage(pParse->pVdbe); + }else{ + u16 nEq = pLoop->u.btree.nEq; + int r1; + char *zStartAff; + + assert( pLoop->wsFlags & WHERE_INDEXED ); + assert( (pLoop->wsFlags & WHERE_COLUMN_IN)==0 ); + r1 = codeAllEqualityTerms(pParse,pLevel,0,0,&zStartAff); + codeApplyAffinity(pParse, r1, nEq, zStartAff); + sqlite3DbFree(pParse->db, zStartAff); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, r1, nEq); + VdbeCoverage(pParse->pVdbe); + } + pLevel->regFilter = 0; + } +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -1403,7 +1525,6 @@ Bitmask sqlite3WhereCodeOneLoopStart( int iReg; /* P3 Value for OP_VFilter */ int addrNotFound; int nConstraint = pLoop->nLTerm; - int iIn; /* Counter for IN constraints */ iReg = sqlite3GetTempRange(pParse, nConstraint+2); addrNotFound = pLevel->addrBrk; @@ -1412,11 +1533,27 @@ Bitmask sqlite3WhereCodeOneLoopStart( pTerm = pLoop->aLTerm[j]; if( NEVER(pTerm==0) ) continue; if( pTerm->eOperator & WO_IN ){ - codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); - addrNotFound = pLevel->addrNxt; + if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ + int iTab = pParse->nTab++; + int iCache = ++pParse->nMem; + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab); + sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); + }else{ + codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); + addrNotFound = pLevel->addrNxt; + } }else{ Expr *pRight = pTerm->pExpr->pRight; codeExprOrVector(pParse, pRight, iTarget, 1); + if( pTerm->eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET + && pLoop->u.vtab.bOmitOffset + ){ + assert( pTerm->eOperator==WO_AUX ); + assert( pWInfo->pLimit!=0 ); + assert( pWInfo->pLimit->iOffset>0 ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pLimit->iOffset); + VdbeComment((v,"Zero OFFSET counter")); + } } } sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); @@ -1433,44 +1570,54 @@ Bitmask sqlite3WhereCodeOneLoopStart( pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext; pLevel->p2 = sqlite3VdbeCurrentAddr(v); assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); - if( pLoop->wsFlags & WHERE_IN_ABLE ){ - iIn = pLevel->u.in.nIn; - }else{ - iIn = 0; - } - for(j=nConstraint-1; j>=0; j--){ + + for(j=0; jaLTerm[j]; - if( (pTerm->eOperator & WO_IN)!=0 ) iIn--; if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){ disableTerm(pLevel, pTerm); - }else if( (pTerm->eOperator & WO_IN)!=0 - && sqlite3ExprVectorSize(pTerm->pExpr->pLeft)==1 + continue; + } + if( (pTerm->eOperator & WO_IN)!=0 + && (SMASKBIT32(j) & pLoop->u.vtab.mHandleIn)==0 + && !db->mallocFailed ){ Expr *pCompare; /* The comparison operator */ Expr *pRight; /* RHS of the comparison */ VdbeOp *pOp; /* Opcode to access the value of the IN constraint */ + int iIn; /* IN loop corresponding to the j-th constraint */ /* Reload the constraint value into reg[iReg+j+2]. The same value ** was loaded into the same register prior to the OP_VFilter, but ** the xFilter implementation might have changed the datatype or - ** encoding of the value in the register, so it *must* be reloaded. */ - assert( pLevel->u.in.aInLoop!=0 || db->mallocFailed ); - if( !db->mallocFailed ){ - assert( iIn>=0 && iInu.in.nIn ); + ** encoding of the value in the register, so it *must* be reloaded. + */ + for(iIn=0; ALWAYS(iInu.in.nIn); iIn++){ pOp = sqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[iIn].addrInTop); - assert( pOp->opcode==OP_Column || pOp->opcode==OP_Rowid ); - assert( pOp->opcode!=OP_Column || pOp->p3==iReg+j+2 ); - assert( pOp->opcode!=OP_Rowid || pOp->p2==iReg+j+2 ); - testcase( pOp->opcode==OP_Rowid ); - sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3); + if( (pOp->opcode==OP_Column && pOp->p3==iReg+j+2) + || (pOp->opcode==OP_Rowid && pOp->p2==iReg+j+2) + ){ + testcase( pOp->opcode==OP_Rowid ); + sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3); + break; + } } /* Generate code that will continue to the next row if - ** the IN constraint is not satisfied */ + ** the IN constraint is not satisfied + */ pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0); - assert( pCompare!=0 || db->mallocFailed ); - if( pCompare ){ - pCompare->pLeft = pTerm->pExpr->pLeft; + if( !db->mallocFailed ){ + int iFld = pTerm->u.x.iField; + Expr *pLeft = pTerm->pExpr->pLeft; + assert( pLeft!=0 ); + if( iFld>0 ){ + assert( pLeft->op==TK_VECTOR ); + assert( ExprUseXList(pLeft) ); + assert( iFld<=pLeft->x.pList->nExpr ); + pCompare->pLeft = pLeft->x.pList->a[iFld-1].pExpr; + }else{ + pCompare->pLeft = pLeft; + } pCompare->pRight = pRight = sqlite3Expr(db, TK_REGISTER, 0); if( pRight ){ pRight->iTable = iReg+j+2; @@ -1479,11 +1626,11 @@ Bitmask sqlite3WhereCodeOneLoopStart( ); } pCompare->pLeft = 0; - sqlite3ExprDelete(db, pCompare); } + sqlite3ExprDelete(db, pCompare); } } - assert( iIn==0 || db->mallocFailed ); + /* These registers need to be preserved in case there is an IN operator ** loop. So we could deallocate the registers here (and potentially ** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0. But it seems @@ -1511,6 +1658,12 @@ Bitmask sqlite3WhereCodeOneLoopStart( iRowidReg = codeEqualityTerm(pParse, pTerm, pLevel, 0, bRev, iReleaseReg); if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg); addrNxt = pLevel->addrNxt; + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + iRowidReg, 1); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg); VdbeCoverage(v); pLevel->op = OP_Noop; @@ -1836,6 +1989,12 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeAddOp2(v, OP_Integer, 1, regBignull); VdbeComment((v, "NULL-scan pass ctr")); } + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + regBase, nEq); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev]; assert( op!=0 ); @@ -2163,7 +2322,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn); /* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y - ** Then for every term xN, evaluate as the subexpression: xN AND z + ** Then for every term xN, evaluate as the subexpression: xN AND y ** That way, terms in y that are factored into the disjunction will ** be picked up by the recursive calls to sqlite3WhereBegin() below. ** @@ -2175,6 +2334,20 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** This optimization also only applies if the (x1 OR x2 OR ...) term ** is not contained in the ON clause of a LEFT JOIN. ** See ticket http://www.sqlite.org/src/info/f2369304e4 + ** + ** 2022-02-04: Do not push down slices of a row-value comparison. + ** In other words, "w" or "y" may not be a slice of a vector. Otherwise, + ** the initialization of the right-hand operand of the vector comparison + ** might not occur, or might occur only in an OR branch that is not + ** taken. dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1. + ** + ** 2022-03-03: Do not push down expressions that involve subqueries. + ** The subquery might get coded as a subroutine. Any table-references + ** in the subquery might be resolved to index-references for the index on + ** the OR branch in which the subroutine is coded. But if the subroutine + ** is invoked from a different OR branch that uses a different index, such + ** index-references will not work. tag-20220303a + ** https://sqlite.org/forum/forumpost/36937b197273d403 */ if( pWC->nTerm>1 ){ int iTerm; @@ -2183,9 +2356,12 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( &pWC->a[iTerm] == pTerm ) continue; testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL ); testcase( pWC->a[iTerm].wtFlags & TERM_CODED ); - if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED))!=0 ) continue; + testcase( pWC->a[iTerm].wtFlags & TERM_SLICE ); + if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED|TERM_SLICE))!=0 ){ + continue; + } if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue; - testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO ); + if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */ pExpr = sqlite3ExprDup(db, pExpr, 0); pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr); } @@ -2226,9 +2402,9 @@ Bitmask sqlite3WhereCodeOneLoopStart( /* Loop through table entries that match term pOrTerm. */ ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1)); WHERETRACE(0xffff, ("Subplan for OR-clause:\n")); - pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, + pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, 0, WHERE_OR_SUBCLAUSE, iCovCur); - assert( pSubWInfo || pParse->nErr || db->mallocFailed ); + assert( pSubWInfo || pParse->nErr ); if( pSubWInfo ){ WhereLoop *pSubLoop; int addrExplain = sqlite3WhereExplainOneScan( @@ -2467,7 +2643,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** then we cannot use the "t1.a=t2.b" constraint, but we can code ** the implied "t1.a=123" constraint. */ - for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ + for(pTerm=pWC->a, j=pWC->nBase; j>0; j--, pTerm++){ Expr *pE, sEAlt; WhereTerm *pAlt; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; @@ -2512,7 +2688,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( pLevel->addrFirst = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin); VdbeComment((v, "record LEFT JOIN hit")); - for(pTerm=pWC->a, j=0; jnTerm; j++, pTerm++){ + for(pTerm=pWC->a, j=0; jnBase; j++, pTerm++){ testcase( pTerm->wtFlags & TERM_VIRTUAL ); testcase( pTerm->wtFlags & TERM_CODED ); if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; diff --git a/src/whereexpr.c b/src/whereexpr.c index 53dd14031..19dd886de 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -79,6 +79,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); } pTerm = &pWC->a[idx = pWC->nTerm++]; + if( (wtFlags & TERM_VIRTUAL)==0 ) pWC->nBase = pWC->nTerm; if( p && ExprHasProperty(p, EP_Unlikely) ){ pTerm->truthProb = sqlite3LogEst(p->iTable) - 270; }else{ @@ -421,7 +422,7 @@ static int isAuxiliaryVtabOperator( assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); assert( !ExprHasProperty(pExpr, EP_IntValue) ); - pMod = (sqlite3_module *)pVtab->pModule; + pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction!=0 ){ i = pMod->xFindFunction(pVtab,2, pExpr->u.zToken, &xNotUsed, &pNotUsed); if( i>=SQLITE_INDEX_CONSTRAINT_FUNCTION ){ @@ -465,7 +466,7 @@ static int isAuxiliaryVtabOperator( static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ if( pDerived ){ pDerived->flags |= pBase->flags & EP_FromJoin; - pDerived->iRightJoinTable = pBase->iRightJoinTable; + pDerived->w.iRightJoinTable = pBase->w.iRightJoinTable; } } @@ -795,7 +796,7 @@ static void exprAnalyzeOrTerm( pOrTerm = pOrWc->a; for(i=pOrWc->nTerm-1; i>=0; i--, pOrTerm++){ assert( pOrTerm->eOperator & WO_EQ ); - pOrTerm->wtFlags &= ~TERM_OR_OK; + pOrTerm->wtFlags &= ~TERM_OK; if( pOrTerm->leftCursor==iCursor ){ /* This is the 2-bit case and we are on the second iteration and ** current term is from the first iteration. So skip this term. */ @@ -836,7 +837,7 @@ static void exprAnalyzeOrTerm( assert( pOrTerm->eOperator & WO_EQ ); assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); if( pOrTerm->leftCursor!=iCursor ){ - pOrTerm->wtFlags &= ~TERM_OR_OK; + pOrTerm->wtFlags &= ~TERM_OK; }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR && sqlite3ExprCompare(pParse, pOrTerm->pExpr->pLeft, pLeft, -1) )){ @@ -852,7 +853,7 @@ static void exprAnalyzeOrTerm( if( affRight!=0 && affRight!=affLeft ){ okToChngToIN = 0; }else{ - pOrTerm->wtFlags |= TERM_OR_OK; + pOrTerm->wtFlags |= TERM_OK; } } } @@ -869,7 +870,7 @@ static void exprAnalyzeOrTerm( Expr *pNew; /* The complete IN operator */ for(i=pOrWc->nTerm-1, pOrTerm=pOrWc->a; i>=0; i--, pOrTerm++){ - if( (pOrTerm->wtFlags & TERM_OR_OK)==0 ) continue; + if( (pOrTerm->wtFlags & TERM_OK)==0 ) continue; assert( pOrTerm->eOperator & WO_EQ ); assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pOrTerm->leftCursor==iCursor ); @@ -1070,10 +1071,13 @@ static void exprAnalyze( if( db->mallocFailed ){ return; } + assert( pWC->nTerm > idxTerm ); pTerm = &pWC->a[idxTerm]; pMaskSet = &pWInfo->sMaskSet; pExpr = pTerm->pExpr; + assert( pExpr!=0 ); /* Because malloc() has not failed */ assert( pExpr->op!=TK_AS && pExpr->op!=TK_COLLATE ); + pMaskSet->bVarSelect = 0; prereqLeft = sqlite3WhereExprUsage(pMaskSet, pExpr->pLeft); op = pExpr->op; if( op==TK_IN ){ @@ -1084,16 +1088,30 @@ static void exprAnalyze( }else{ pTerm->prereqRight = sqlite3WhereExprListUsage(pMaskSet, pExpr->x.pList); } - }else if( op==TK_ISNULL ){ - pTerm->prereqRight = 0; + prereqAll = prereqLeft | pTerm->prereqRight; }else{ pTerm->prereqRight = sqlite3WhereExprUsage(pMaskSet, pExpr->pRight); + if( pExpr->pLeft==0 + || ExprHasProperty(pExpr, EP_xIsSelect|EP_IfNullRow) + || pExpr->x.pList!=0 + ){ + prereqAll = sqlite3WhereExprUsageNN(pMaskSet, pExpr); + }else{ + prereqAll = prereqLeft | pTerm->prereqRight; + } } - pMaskSet->bVarSelect = 0; - prereqAll = sqlite3WhereExprUsageNN(pMaskSet, pExpr); if( pMaskSet->bVarSelect ) pTerm->wtFlags |= TERM_VARSELECT; + +#ifdef SQLITE_DEBUG + if( prereqAll!=sqlite3WhereExprUsageNN(pMaskSet, pExpr) ){ + printf("\n*** Incorrect prereqAll computed for:\n"); + sqlite3TreeViewExpr(0,pExpr,0); + abort(); + } +#endif + if( ExprHasProperty(pExpr, EP_FromJoin) ){ - Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->iRightJoinTable); + Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iRightJoinTable); prereqAll |= x; extraRight = x-1; /* ON clause terms may not be used with an index ** on left table of a LEFT JOIN. Ticket #3015 */ @@ -1361,7 +1379,10 @@ static void exprAnalyze( ** no longer used. ** ** This is only required if at least one side of the comparison operation - ** is not a sub-select. */ + ** is not a sub-select. + ** + ** tag-20220128a + */ if( (pExpr->op==TK_EQ || pExpr->op==TK_IS) && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 && sqlite3ExprVectorSize(pExpr->pRight)==nLeft @@ -1378,7 +1399,7 @@ static void exprAnalyze( pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight); transferJoinMarkings(pNew, pExpr); - idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC); + idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_SLICE); exprAnalyze(pSrc, pWC, idxNew); } pTerm = &pWC->a[idxTerm]; @@ -1408,7 +1429,7 @@ static void exprAnalyze( int i; for(i=0; ipLeft); i++){ int idxNew; - idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL); + idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL|TERM_SLICE); pWC->a[idxNew].u.x.iField = i+1; exprAnalyze(pSrc, pWC, idxNew); markTermAsChild(pWC, idxNew, idxTerm); @@ -1441,7 +1462,7 @@ static void exprAnalyze( 0, sqlite3ExprDup(db, pRight, 0)); if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ ExprSetProperty(pNewExpr, EP_FromJoin); - pNewExpr->iRightJoinTable = pExpr->iRightJoinTable; + pNewExpr->w.iRightJoinTable = pExpr->w.iRightJoinTable; } idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); @@ -1504,6 +1525,113 @@ void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){ } } +/* +** Add either a LIMIT (if eMatchOp==SQLITE_INDEX_CONSTRAINT_LIMIT) or +** OFFSET (if eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET) term to the +** where-clause passed as the first argument. The value for the term +** is found in register iReg. +** +** In the common case where the value is a simple integer +** (example: "LIMIT 5 OFFSET 10") then the expression codes as a +** TK_INTEGER so that it will be available to sqlite3_vtab_rhs_value(). +** If not, then it codes as a TK_REGISTER expression. +*/ +static void whereAddLimitExpr( + WhereClause *pWC, /* Add the constraint to this WHERE clause */ + int iReg, /* Register that will hold value of the limit/offset */ + Expr *pExpr, /* Expression that defines the limit/offset */ + int iCsr, /* Cursor to which the constraint applies */ + int eMatchOp /* SQLITE_INDEX_CONSTRAINT_LIMIT or _OFFSET */ +){ + Parse *pParse = pWC->pWInfo->pParse; + sqlite3 *db = pParse->db; + Expr *pNew; + int iVal = 0; + + if( sqlite3ExprIsInteger(pExpr, &iVal) && iVal>=0 ){ + Expr *pVal = sqlite3Expr(db, TK_INTEGER, 0); + if( pVal==0 ) return; + ExprSetProperty(pVal, EP_IntValue); + pVal->u.iValue = iVal; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + }else{ + Expr *pVal = sqlite3Expr(db, TK_REGISTER, 0); + if( pVal==0 ) return; + pVal->iTable = iReg; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + } + if( pNew ){ + WhereTerm *pTerm; + int idx; + idx = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_VIRTUAL); + pTerm = &pWC->a[idx]; + pTerm->leftCursor = iCsr; + pTerm->eOperator = WO_AUX; + pTerm->eMatchOp = eMatchOp; + } +} + +/* +** Possibly add terms corresponding to the LIMIT and OFFSET clauses of the +** SELECT statement passed as the second argument. These terms are only +** added if: +** +** 1. The SELECT statement has a LIMIT clause, and +** 2. The SELECT statement is not an aggregate or DISTINCT query, and +** 3. The SELECT statement has exactly one object in its from clause, and +** that object is a virtual table, and +** 4. There are no terms in the WHERE clause that will not be passed +** to the virtual table xBestIndex method. +** 5. The ORDER BY clause, if any, will be made available to the xBestIndex +** method. +** +** LIMIT and OFFSET terms are ignored by most of the planner code. They +** exist only so that they may be passed to the xBestIndex method of the +** single virtual table in the FROM clause of the SELECT. +*/ +void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ + assert( p==0 || (p->pGroupBy==0 && (p->selFlags & SF_Aggregate)==0) ); + if( (p && p->pLimit) /* 1 */ + && (p->selFlags & (SF_Distinct|SF_Aggregate))==0 /* 2 */ + && (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pTab)) /* 3 */ + ){ + ExprList *pOrderBy = p->pOrderBy; + int iCsr = p->pSrc->a[0].iCursor; + int ii; + + /* Check condition (4). Return early if it is not met. */ + for(ii=0; iinTerm; ii++){ + if( pWC->a[ii].wtFlags & TERM_CODED ){ + /* This term is a vector operation that has been decomposed into + ** other, subsequent terms. It can be ignored. See tag-20220128a */ + assert( pWC->a[ii].wtFlags & TERM_VIRTUAL ); + assert( pWC->a[ii].eOperator==0 ); + continue; + } + if( pWC->a[ii].leftCursor!=iCsr ) return; + } + + /* Check condition (5). Return early if it is not met. */ + if( pOrderBy ){ + for(ii=0; iinExpr; ii++){ + Expr *pExpr = pOrderBy->a[ii].pExpr; + if( pExpr->op!=TK_COLUMN ) return; + if( pExpr->iTable!=iCsr ) return; + if( pOrderBy->a[ii].sortFlags & KEYINFO_ORDER_BIGNULL ) return; + } + } + + /* All conditions are met. Add the terms to the where-clause object. */ + assert( p->pLimit->op==TK_LIMIT ); + whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, + iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); + if( p->iOffset>0 ){ + whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight, + iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET); + } + } +} + /* ** Initialize a preallocated WhereClause structure. */ @@ -1515,6 +1643,7 @@ void sqlite3WhereClauseInit( pWC->hasOr = 0; pWC->pOuter = 0; pWC->nTerm = 0; + pWC->nBase = 0; pWC->nSlot = ArraySize(pWC->aStatic); pWC->a = pWC->aStatic; } @@ -1525,17 +1654,34 @@ void sqlite3WhereClauseInit( ** sqlite3WhereClauseInit(). */ void sqlite3WhereClauseClear(WhereClause *pWC){ - int i; - WhereTerm *a; sqlite3 *db = pWC->pWInfo->pParse->db; - for(i=pWC->nTerm-1, a=pWC->a; i>=0; i--, a++){ - if( a->wtFlags & TERM_DYNAMIC ){ - sqlite3ExprDelete(db, a->pExpr); + assert( pWC->nTerm>=pWC->nBase ); + if( pWC->nTerm>0 ){ + WhereTerm *a = pWC->a; + WhereTerm *aLast = &pWC->a[pWC->nTerm-1]; +#ifdef SQLITE_DEBUG + int i; + /* Verify that every term past pWC->nBase is virtual */ + for(i=pWC->nBase; inTerm; i++){ + assert( (pWC->a[i].wtFlags & TERM_VIRTUAL)!=0 ); } - if( a->wtFlags & TERM_ORINFO ){ - whereOrInfoDelete(db, a->u.pOrInfo); - }else if( a->wtFlags & TERM_ANDINFO ){ - whereAndInfoDelete(db, a->u.pAndInfo); +#endif + while(1){ + assert( a->eMatchOp==0 || a->eOperator==WO_AUX ); + if( a->wtFlags & TERM_DYNAMIC ){ + sqlite3ExprDelete(db, a->pExpr); + } + if( a->wtFlags & (TERM_ORINFO|TERM_ANDINFO) ){ + if( a->wtFlags & TERM_ORINFO ){ + assert( (a->wtFlags & TERM_ANDINFO)==0 ); + whereOrInfoDelete(db, a->u.pOrInfo); + }else{ + assert( (a->wtFlags & TERM_ANDINFO)!=0 ); + whereAndInfoDelete(db, a->u.pAndInfo); + } + } + if( a==aLast ) break; + a++; } } if( pWC->a!=pWC->aStatic ){ @@ -1548,15 +1694,38 @@ void sqlite3WhereClauseClear(WhereClause *pWC){ ** These routines walk (recursively) an expression tree and generate ** a bitmask indicating which tables are used in that expression ** tree. +** +** sqlite3WhereExprUsage(MaskSet, Expr) -> +** +** Return a Bitmask of all tables referenced by Expr. Expr can be +** be NULL, in which case 0 is returned. +** +** sqlite3WhereExprUsageNN(MaskSet, Expr) -> +** +** Same as sqlite3WhereExprUsage() except that Expr must not be +** NULL. The "NN" suffix on the name stands for "Not Null". +** +** sqlite3WhereExprListUsage(MaskSet, ExprList) -> +** +** Return a Bitmask of all tables referenced by every expression +** in the expression list ExprList. ExprList can be NULL, in which +** case 0 is returned. +** +** sqlite3WhereExprUsageFull(MaskSet, ExprList) -> +** +** Internal use only. Called only by sqlite3WhereExprUsageNN() for +** complex expressions that require pushing register values onto +** the stack. Many calls to sqlite3WhereExprUsageNN() do not need +** the more complex analysis done by this routine. Hence, the +** computations done by this routine are broken out into a separate +** "no-inline" function to avoid the stack push overhead in the +** common case where it is not needed. */ -Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ +static SQLITE_NOINLINE Bitmask sqlite3WhereExprUsageFull( + WhereMaskSet *pMaskSet, + Expr *p +){ Bitmask mask; - if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ - return sqlite3WhereGetMask(pMaskSet, p->iTable); - }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ - assert( p->op!=TK_IF_NULL_ROW ); - return 0; - } mask = (p->op==TK_IF_NULL_ROW) ? sqlite3WhereGetMask(pMaskSet, p->iTable) : 0; if( p->pLeft ) mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pLeft); if( p->pRight ){ @@ -1578,6 +1747,15 @@ Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ #endif return mask; } +Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ + if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return sqlite3WhereGetMask(pMaskSet, p->iTable); + }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ + assert( p->op!=TK_IF_NULL_ROW ); + return 0; + } + return sqlite3WhereExprUsageFull(pMaskSet, p); +} Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){ return p ? sqlite3WhereExprUsageNN(pMaskSet,p) : 0; } @@ -1647,6 +1825,7 @@ void sqlite3WhereTabFuncArgs( pColRef->iColumn = k++; assert( ExprUseYTab(pColRef) ); pColRef->y.pTab = pTab; + pItem->colUsed |= sqlite3ExprColUsed(pColRef); pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); diff --git a/src/window.c b/src/window.c index d7c412a3a..bed6ab636 100644 --- a/src/window.c +++ b/src/window.c @@ -957,7 +957,11 @@ static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ */ int sqlite3WindowRewrite(Parse *pParse, Select *p){ int rc = SQLITE_OK; - if( p->pWin && p->pPrior==0 && ALWAYS((p->selFlags & SF_WinRewrite)==0) ){ + if( p->pWin + && p->pPrior==0 + && ALWAYS((p->selFlags & SF_WinRewrite)==0) + && ALWAYS(!IN_RENAME_OBJECT) + ){ Vdbe *v = sqlite3GetVdbe(pParse); sqlite3 *db = pParse->db; Select *pSub = 0; /* The subquery */ @@ -1032,6 +1036,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ ExprList *pArgs; assert( ExprUseXList(pWin->pOwner) ); + assert( pWin->pFunc!=0 ); pArgs = pWin->pOwner->x.pList; if( pWin->pFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); @@ -1106,12 +1111,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ sqlite3ParserAddCleanup(pParse, sqlite3DbFree, pTab); } - if( rc ){ - if( pParse->nErr==0 ){ - assert( pParse->db->mallocFailed ); - sqlite3ErrorToParser(pParse->db, SQLITE_NOMEM); - } - } + assert( rc==SQLITE_OK || pParse->nErr!=0 ); return rc; } diff --git a/test/alterauth2.test b/test/alterauth2.test index 27ded1c3e..6f9242d36 100644 --- a/test/alterauth2.test +++ b/test/alterauth2.test @@ -99,6 +99,7 @@ do_auth_test 1.2 { do_auth_test 1.3 { ALTER TABLE t2 DROP COLUMN c; } { + {SQLITE_ALTER_TABLE main t2 c {}} {SQLITE_FUNCTION {} like {} {}} {SQLITE_FUNCTION {} sqlite_drop_column {} {}} {SQLITE_FUNCTION {} sqlite_rename_quotefix {} {}} diff --git a/test/altercol.test b/test/altercol.test index d0c5c40ea..e39793aa9 100644 --- a/test/altercol.test +++ b/test/altercol.test @@ -553,8 +553,9 @@ do_execsql_test 13.1.3 { sqlite3_db_config db DEFENSIVE 0 do_execsql_test 13.1.4 { - PRAGMA writable_schema = 1; + PRAGMA writable_schema = ON; UPDATE sqlite_master SET sql = 'CREATE INDEX x1i ON x1(j)' WHERE name='x1i'; + PRAGMA writable_schema = OFF; } {} do_catchsql_test 13.1.5 { @@ -562,7 +563,9 @@ do_catchsql_test 13.1.5 { } {1 {error in index x1i: no such column: j}} do_execsql_test 13.1.6 { + PRAGMA writable_schema = ON; UPDATE sqlite_master SET sql = '' WHERE name='x1i'; + PRAGMA writable_schema = OFF; } {} do_catchsql_test 13.1.7 { @@ -570,7 +573,9 @@ do_catchsql_test 13.1.7 { } {1 {error in index x1i: }} do_execsql_test 13.1.8 { + PRAGMA writable_schema = ON; DELETE FROM sqlite_master WHERE name = 'x1i'; + PRAGMA writable_schema = OFF; } do_execsql_test 13.2.0 { @@ -838,6 +843,8 @@ do_execsql_test 22.0 { #------------------------------------------------------------------------- # reset_db +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_execsql_test 22.0 { CREATE TABLE t1(a, b); CREATE INDEX x1 on t1("c"=b); @@ -859,5 +866,69 @@ do_execsql_test 23.0 { } {ok {CREATE TABLE t1("x" "b",c)}} +# 2022-02-04 +# Do not complain about syntax errors in the schema if +# in PRAGMA writable_schema=ON mode. +# +reset_db +do_execsql_test 23.0 { + CREATE TABLE t1(a INT, b REAL, c TEXT, d BLOB, e ANY); + CREATE INDEX t1abx ON t1(a, b, a+b) WHERE c IS NOT NULL; + CREATE VIEW t2 AS SELECT a+10, b*5.0, xyz FROM t1; -- unknown column "xyz" + CREATE TABLE schema_copy(name TEXT, sql TEXT); + INSERT INTO schema_copy(name,sql) SELECT name, sql FROM sqlite_schema WHERE sql IS NOT NULL; +} {} +do_catchsql_test 23.1 { + ALTER TABLE t1 RENAME COLUMN e TO eeee; +} {1 {error in view t2: no such column: xyz}} +do_execsql_test 23.2 { + SELECT name, sql FROM sqlite_master + EXCEPT SELECT name, sql FROM schema_copy; +} {} +do_execsql_test 23.3 { + BEGIN; + PRAGMA writable_schema=ON; + ALTER TABLE t1 RENAME COLUMN e TO eeee; + PRAGMA writable_schema=OFF; + SELECT name FROM sqlite_master + WHERE (name, sql) NOT IN (SELECT name, sql FROM schema_copy); + ROLLBACK; +} {t1} +do_execsql_test 23.10 { + DROP VIEW t2; + CREATE TRIGGER r3 AFTER INSERT ON t1 BEGIN + INSERT INTO t3(x,y) VALUES(new.a, new.b); + INSERT INTO t4(p) VALUES(new.c); -- no such table "t4" + END; + DELETE FROM schema_copy; + INSERT INTO schema_copy(name,sql) SELECT name, sql FROM sqlite_schema WHERE sql IS NOT NULL; +} {} +do_catchsql_test 23.11 { + ALTER TABLE t1 RENAME COLUMN e TO eeee; +} {1 {error in trigger r3: no such table: main.t3}} +do_execsql_test 23.12 { + SELECT name, sql FROM sqlite_master + EXCEPT SELECT name, sql FROM schema_copy; +} {} +do_execsql_test 23.13 { + BEGIN; + PRAGMA writable_schema=ON; + ALTER TABLE t1 RENAME COLUMN e TO eeee; + PRAGMA writable_schema=OFF; + SELECT name FROM sqlite_master + WHERE (name, sql) NOT IN (SELECT name, sql FROM schema_copy); + ROLLBACK; +} {t1} +do_execsql_test 23.20 { + CREATE TABLE t4(id INTEGER PRIMARY KEY, c1 INT, c2 INT); + CREATE VIEW t4v1 AS SELECT id, c1, c99 FROM t4; + DELETE FROM schema_copy; + INSERT INTO schema_copy SELECT name, sql FROM sqlite_schema; + BEGIN; + PRAGMA writable_schema=ON; + ALTER TABLE t4 RENAME to t4new; + SELECT name FROM sqlite_schema WHERE (name,sql) NOT IN (SELECT * FROM schema_copy); + ROLLBACK; +} {t4new} finish_test diff --git a/test/altermalloc2.test b/test/altermalloc2.test index 3d22ad945..a90be88cf 100644 --- a/test/altermalloc2.test +++ b/test/altermalloc2.test @@ -26,6 +26,11 @@ do_execsql_test 1.0 { } faultsim_save_and_close +set ::TMPDBERROR [list 1 \ + {unable to open a temporary database file for storing temporary tables} +] + + do_faultsim_test 1 -prep { faultsim_restore_and_reopen } -body { @@ -33,7 +38,7 @@ do_faultsim_test 1 -prep { ALTER TABLE t1 RENAME abcd TO dcba } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } catch {db close} @@ -52,7 +57,7 @@ do_faultsim_test 2 -prep { ALTER TABLE t1 RENAME abcd TO dcba } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } @@ -70,7 +75,7 @@ do_faultsim_test 3 -prep { ALTER TABLE t1 RENAME abcd TO dcba } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } reset_db @@ -95,7 +100,7 @@ do_faultsim_test 4 -faults oom-* -prep { ALTER TABLE rr RENAME a TO c; } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } reset_db @@ -116,7 +121,7 @@ do_faultsim_test 5 -faults oom-* -prep { ALTER TABLE rr RENAME TO c; } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } finish_test diff --git a/test/altermalloc3.test b/test/altermalloc3.test index 4c10f48fe..7023b1c0f 100644 --- a/test/altermalloc3.test +++ b/test/altermalloc3.test @@ -21,6 +21,10 @@ ifcapable !altertable { return } +set ::TMPDBERROR [list 1 \ + {unable to open a temporary database file for storing temporary tables} +] + do_execsql_test 1.0 { CREATE TABLE x1( one, two, three, PRIMARY KEY(one), @@ -40,7 +44,7 @@ do_faultsim_test 1 -prep { } -body { execsql { ALTER TABLE t1 DROP COLUMN c } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } #------------------------------------------------------------------------- @@ -72,7 +76,7 @@ do_faultsim_test 2.2 -prep { ALTER TABLE t2 RENAME TO t2x; } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } finish_test diff --git a/test/alterqf.test b/test/alterqf.test index 6a8964186..423a9fa86 100644 --- a/test/alterqf.test +++ b/test/alterqf.test @@ -25,6 +25,8 @@ ifcapable !altertable { sqlite3_test_control SQLITE_TESTCTRL_INTERNAL_FUNCTIONS db +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_execsql_test 1.0 { CREATE TABLE t1(a, b, c); @@ -61,8 +63,7 @@ foreach {tn before after} { 10 {CREATE TABLE t2(abc, xyz CHECK (xyz != "123"))} {CREATE TABLE t2(abc, xyz CHECK (xyz != '123'))} - 11 { - CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN + 11 {CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN SELECT max("str", new."a") FROM t1 WHERE group_concat("b", ",") OVER (ORDER BY c||"str"); UPDATE t1 SET c= b + "str"; @@ -70,8 +71,7 @@ foreach {tn before after} { SELECT 1 FROM t1 AS o WHERE o."a" = "o.a" AND t1.b IN("t1.b") ); END; - } { - CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN + } {CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN SELECT max('str', new."a") FROM t1 WHERE group_concat("b", ',') OVER (ORDER BY c||'str'); UPDATE t1 SET c= b + 'str'; @@ -89,6 +89,8 @@ foreach {tn before after} { #------------------------------------------------------------------------- reset_db +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_execsql_test 2.0 { CREATE TABLE x1( one, two, three, PRIMARY KEY(one), diff --git a/test/altertab2.test b/test/altertab2.test index 9c1ad5813..def9e5668 100644 --- a/test/altertab2.test +++ b/test/altertab2.test @@ -355,7 +355,7 @@ do_execsql_test 8.5 { reset_db do_catchsql_test 8.6 { CREATE TABLE t0(c0); - CREATE INDEX i0 ON t0(LIKELIHOOD(1,2) AND 0); + CREATE INDEX i0 ON t0(likelihood(1,2) AND 0); ALTER TABLE t0 RENAME TO t1; SELECT sql FROM sqlite_master WHERE name='i0'; } {1 {error in index i0: second argument to likelihood() must be a constant between 0.0 and 1.0}} diff --git a/test/atof1.test b/test/atof1.test index a68266c9b..0505fb226 100644 --- a/test/atof1.test +++ b/test/atof1.test @@ -19,11 +19,6 @@ if {$::longdouble_size<=8} { finish_test return } -if {![info exists tcl_platform(machine)] - || $::tcl_platform(machine)!="x86_64"} { - finish_test - return -} expr srand(1) for {set i 1} {$i<20000} {incr i} { diff --git a/test/auth.test b/test/auth.test index d8f23a15e..d8afa2dbf 100644 --- a/test/auth.test +++ b/test/auth.test @@ -2069,6 +2069,15 @@ ifcapable {altertable} { do_test auth-1.302 { set authargs } {main t5 {} {}} + db eval BEGIN + set authargs {} + do_execsql_test auth-1.302-drop-1 { + ALTER TABLE t5 DROP COLUMN new_col_1; + } {} + db eval ROLLBACK + do_test auth-1.302-drop-2 { + set authargs + } {main t5 new_col_1 {}} do_test auth-1.303 { proc auth {code arg1 arg2 arg3 arg4 args} { if {$code=="SQLITE_ALTER_TABLE"} { @@ -2088,6 +2097,16 @@ ifcapable {altertable} { do_test auth-1.305 { set authargs } {main t5 {} {}} + db eval BEGIN + set authargs {} + do_execsql_test auth-1.305-drop-1 { + ALTER TABLE t5 DROP COLUMN new_col_1; + SELECT 1 FROM sqlite_schema WHERE name='t5' AND sql LIKE '%new_col_1%'; + } {1} + db eval ROLLBACK + do_test auth-1.305-drop-2 { + set authargs + } {main t5 new_col_1 {}} do_test auth-1.306 { proc auth {code arg1 arg2 arg3 arg4 args} { if {$code=="SQLITE_ALTER_TABLE"} { @@ -2104,10 +2123,22 @@ ifcapable {altertable} { set x [execsql {SELECT sql FROM temp.sqlite_master WHERE type='t5'}] regexp new_col_3 $x } {0} - do_test auth-1.308 { set authargs } {main t5 {} {}} + db eval BEGIN + set authargs {} + do_catchsql_test auth-1.308-drop-1 { + ALTER TABLE t5 DROP COLUMN new_col_1; + } {1 {not authorized}} + do_execsql_test auth-1.308-drop-2 { + SELECT 1 FROM sqlite_schema WHERE name='t5' AND sql LIKE '%new_col_1%'; + } {1} + do_test auth-1.308-drop-3 { + set authargs + } {main t5 new_col_1 {}} + db eval ROLLBACK + execsql {DROP TABLE t5} } ;# ifcapable altertable @@ -2159,7 +2190,7 @@ ifcapable {cte} { # MAIN: CREATE TABLE t1(a,b); # ifcapable altertable&&vtab { - do_test 1.350 { + do_test auth-1.350 { proc auth {code arg1 arg2 arg3 arg4 args} { if {$code=="SQLITE_ALTER_TABLE"} { set ::authargs [list $arg1 $arg2 $arg3 $arg4] @@ -2177,7 +2208,7 @@ ifcapable altertable&&vtab { do_test auth-1.352 { set authargs } {main t1 {} {}} - do_test 1.353 { + do_test auth-1.353 { proc auth {code arg1 arg2 arg3 arg4 args} { if {$code=="SQLITE_ALTER_TABLE"} { set ::authargs [list $arg1 $arg2 $arg3 $arg4] @@ -2195,7 +2226,7 @@ ifcapable altertable&&vtab { do_test auth-1.355 { set authargs } {main t1 {} {}} - do_test 1.356 { + do_test auth-1.356 { proc auth {code arg1 arg2 arg3 arg4 args} { if {$code=="SQLITE_ALTER_TABLE"} { set ::authargs [list $arg1 $arg2 $arg3 $arg4] @@ -2207,10 +2238,10 @@ ifcapable altertable&&vtab { ALTER TABLE t1 RENAME COLUMN bcdefg TO b; } } {1 {not authorized}} - do_execsql_test auth-1.356 { + do_execsql_test auth-1.357 { SELECT name FROM pragma_table_info('t1') ORDER BY cid; } {a bcdefg} - do_test auth-1.357 { + do_test auth-1.358 { set authargs } {main t1 {} {}} } diff --git a/test/autoindex4.test b/test/autoindex4.test index 0e7a80df2..24604af58 100644 --- a/test/autoindex4.test +++ b/test/autoindex4.test @@ -79,5 +79,98 @@ do_execsql_test autoindex4-3.1 { ORDER BY Items.ItemName; } {Item1 Item2} +# 2021-11-30 - Enhancement to help the automatic index mechanism to +# create a partial index more often. +# +unset -nocomplain id data1 data2 jointype onclause whereclause answer +foreach {id data1 data2 jointype onclause whereclause answer} { + 1 + VALUES(1,2),(3,4) + VALUES(1,2),(3,4) + {LEFT JOIN} + a=x + {y=4 OR y IS NULL} + {3 4 3 4} + + 2 + VALUES(1,2),(3,4) + VALUES(1,2),(3,4) + {LEFT JOIN} + {a=x AND y=4} + {coalesce(y,4)==4} + {1 2 {} {} 3 4 3 4} + + 3 + VALUES(1,2),(3,4) + VALUES(1,2),(3,4) + {JOIN} + {a=x} + {y=4 OR y IS NULL} + {3 4 3 4} + + 4 + VALUES(1,2),(3,4) + VALUES(1,2),(3,4) + {JOIN} + {a=x AND y=4} + {coalesce(y,4)==4} + {3 4 3 4} + + 5 + VALUES(1,2),(3,4),(NULL,4) + VALUES(1,2),(3,4) + {LEFT JOIN} + a=x + {y=4 OR y IS NULL} + {3 4 3 4 {} 4 {} {}} + + 6 + VALUES(1,2),(3,4) + VALUES(1,2),(3,4),(NULL,4) + {LEFT JOIN} + {a=x AND y=4} + {coalesce(y,4)==4} + {1 2 {} {} 3 4 3 4} + + 7 + VALUES(1,2),(3,4),(NULL,4) + VALUES(1,2),(3,4),(NULL,4) + {JOIN} + {a=x} + {y=4 OR y IS NULL} + {3 4 3 4} + + 8 + VALUES(1,2),(3,4) + VALUES(1,2),(3,4) + {JOIN} + {a=x AND y=4} + {coalesce(y,4)==4} + {3 4 3 4} +} { + do_test autoindex4-4.$id.0 { + db eval { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INT, b INT); + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(x INT, y INT); + } + db eval "INSERT INTO t1(a,b) $data1;" + db eval "INSERT INTO t2(x,y) $data2;" + } {} + set sql "SELECT * FROM t1 $jointype t2 ON $onclause WHERE $whereclause" + # puts "sql = $sql" + do_test autoindex4-4.$id.1 { + db eval {PRAGMA automatic_index=ON;} + db eval $sql + } $answer + do_test autoindex4-4.$id.2 { + db eval {PRAGMA automatic_index=OFF;} + db eval $sql + } $answer +} + + + finish_test diff --git a/test/backup.test b/test/backup.test index 829b32452..1572ded55 100644 --- a/test/backup.test +++ b/test/backup.test @@ -969,4 +969,13 @@ foreach {tn file rc} { db2 close } +# 2021-01-31 https://sqlite.org/forum/forumpost/8b39fbf3e7 +# +do_test backup-11.1 { + sqlite3 db1 :memory: + sqlite3 db2 :memory: + sqlite3_backup B db1 main db2 temp + B finish +} {SQLITE_OK} + finish_test diff --git a/test/bestindex1.test b/test/bestindex1.test index e93d7349d..e505a8bc4 100644 --- a/test/bestindex1.test +++ b/test/bestindex1.test @@ -29,7 +29,10 @@ proc vtab_command {method args} { } xBestIndex { - set clist [lindex $args 0] + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + if {[llength $clist]!=1} { error "unexpected constraint list" } catch { array unset C } array set C [lindex $clist 0] @@ -76,10 +79,13 @@ proc t1_vtab {mode method args} { } xBestIndex { + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set SQL_FILTER {SELECT * FROM t1x WHERE a='%1%'} set SQL_SCAN {SELECT * FROM t1x} - set clist [lindex $args 0] set idx 0 for {set idx 0} {$idx < [llength $clist]} {incr idx} { array unset C @@ -177,7 +183,10 @@ proc vtab_command {method args} { } xBestIndex { - set clist [lindex $args 0] + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + #puts $clist set W [list] set U [list] @@ -287,12 +296,17 @@ proc vtab_command {method args} { } xBestIndex { - set clist [lindex $args 0] + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + lappend ::bestindex_calls $clist set ret "cost 1000000 idxnum 555" for {set i 0} {$i < [llength $clist]} {incr i} { array set C [lindex $clist $i] - if {$C(usable)} { lappend ret use $i } + if {$C(usable)} { + lappend ret use $i + } } return $ret } diff --git a/test/bestindex2.test b/test/bestindex2.test index 81ed9ebf6..c47a721cf 100644 --- a/test/bestindex2.test +++ b/test/bestindex2.test @@ -47,7 +47,10 @@ proc vtab_cmd {tbl cols method args} { return "CREATE TABLE $tbl ([join $cols ,])" } xBestIndex { - foreach {clist orderby mask} $args {} + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set mask [$hdl mask] set cons [list] set used [list] diff --git a/test/bestindex3.test b/test/bestindex3.test index 1ee3975f8..6aa3321c7 100644 --- a/test/bestindex3.test +++ b/test/bestindex3.test @@ -34,7 +34,10 @@ proc vtab_cmd {bOmit method args} { } xBestIndex { - foreach {clist orderby mask} $args {} + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set mask [$hdl mask] set ret [list] set use use diff --git a/test/bestindex4.test b/test/bestindex4.test index f90bf41e9..483656d3f 100644 --- a/test/bestindex4.test +++ b/test/bestindex4.test @@ -48,7 +48,10 @@ proc vtab_cmd {param method args} { } xBestIndex { - foreach {clist orderby mask} $args {} + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set mask [$hdl mask] set ret [list] @@ -135,7 +138,11 @@ proc vtab_command {method args} { } xBestIndex { - set clist [lindex $args 0] + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set mask [$hdl mask] + if {[llength $clist]!=1} { error "unexpected constraint list" } catch { array unset C } array set C [lindex $clist 0] diff --git a/test/bestindex5.test b/test/bestindex5.test index f7334653d..35df58a98 100644 --- a/test/bestindex5.test +++ b/test/bestindex5.test @@ -14,7 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -set testprefix bestindex4 +set testprefix bestindex5 ifcapable !vtab { finish_test @@ -44,7 +44,10 @@ proc vtab_cmd {method args} { } xBestIndex { - foreach {clist orderby mask} $args {} + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set mask [$hdl mask] set cost 1000000.0 set ret [list] diff --git a/test/bestindex6.test b/test/bestindex6.test index 8396d07ce..8926ec4a1 100644 --- a/test/bestindex6.test +++ b/test/bestindex6.test @@ -28,7 +28,11 @@ proc vtab_command {src method args} { } xBestIndex { - set clist [lindex $args 0] + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set mask [$hdl mask] + set wlist 1 set iCons 0 diff --git a/test/bestindex7.test b/test/bestindex7.test index f8d42b2b0..a84b9efe9 100644 --- a/test/bestindex7.test +++ b/test/bestindex7.test @@ -28,7 +28,11 @@ proc vtab_command {src method args} { } xBestIndex { - set clist [lindex $args 0] + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + set mask [$hdl mask] + set iCons 0 set ret [list] foreach cons $clist { diff --git a/test/bestindex8.test b/test/bestindex8.test new file mode 100644 index 000000000..681640077 --- /dev/null +++ b/test/bestindex8.test @@ -0,0 +1,463 @@ +# 2020-01-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 +set testprefix bestindex8 + +ifcapable !vtab { + finish_test + return +} + +register_tcl_module db + +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b)" + } + + xBestIndex { + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + lappend ::lBestIndexDistinct [$hdl distinct] + + #puts "ORDERBY: $orderby" + set iCons 0 + set ret [list] + foreach cons $clist { + catch { array unset C } + array set C $cons + if {$C(usable)} { + lappend ret use $iCons + } + incr iCons + } + if {$orderby=="{column 0 desc 0} {column 1 desc 0}" + || $orderby=="{column 0 desc 0}" + } { + lappend ret orderby 1 + lappend ret idxnum 1 + set ::lOrderByConsumed 1 + } + return $ret + } + + xFilter { + set idxnum [lindex $args 0] + if {$idxnum} { + return [list sql "SELECT rowid, a, b FROM $src order by 2, 3"] + } + return [list sql "SELECT rowid, a, b FROM $src"] + } + + } + + return {} +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + INSERT INTO t1 VALUES('a', 'b'), ('c', 'd'); + INSERT INTO t1 VALUES('a', 'b'), ('c', 'd'); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); + + CREATE TABLE t0(c0); + INSERT INTO t0(c0) VALUES (1), (0); +} + +foreach {tn sql bDistinct idxinsert bConsumed res} { + 1 "SELECT a, b FROM vt1" 0 0 0 {a b c d a b c d} + 2 "SELECT DISTINCT a, b FROM vt1" 2 1 1 {a b c d} + 3 "SELECT DISTINCT a FROM vt1" 2 1 1 {a c} + 4 "SELECT DISTINCT b FROM vt1" 2 1 0 {b d} + 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 0 1 1 {b d} + 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 0 1 1 {1 0} + 7 "SELECT DISTINCT a, b FROM vt1 ORDER BY a, b" 1 0 1 {a b c d} + 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 0 1 1 {a b c d} + 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 0 1 1 {a c} + + 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 1 1 {a b} + 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 1 1 {a b} +} { + set ::lBestIndexDistinct "" + set ::lOrderByConsumed 0 + do_execsql_test 1.$tn.1 $sql $res + do_test 1.$tn.2 { + set ::lBestIndexDistinct + } $bDistinct + do_test 1.$tn.3 { + expr {[lsearch [execsql "explain $sql"] IdxInsert]>=0} + } $idxinsert + do_test 1.$tn.4 { + set ::lOrderByConsumed + } $bConsumed +} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b)" + } + + xBestIndex { + set hdl [lindex $args 0] + set ret [list] + + set iCons 0 + foreach cons [$hdl constraints] { + array set C $cons + if {($C(op)=="limit" || $C(op)=="offset") && $C(usable)} { + lappend ret use $iCons + } + incr iCons + } + + return $ret + } + + xFilter { + lappend ::lFilterArgs [lindex $args 2] + return [list sql "SELECT rowid, a, b FROM $src"] + } + + } + + return {} +} + +do_execsql_test 2.0 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); +} + +do_test 2.1 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 LIMIT 10 } + set ::lFilterArgs +} {10} + +do_test 2.2 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 LIMIT 5 OFFSET 50 } + set ::lFilterArgs +} {{5 50}} + +do_test 2.3 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 ORDER BY a, b LIMIT 1 OFFSET 1 } + set ::lFilterArgs +} {{1 1}} + +do_test 2.4 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 ORDER BY a, +b LIMIT 1 OFFSET 1 } + set ::lFilterArgs +} {{}} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b)" + } + + xBestIndex { + set hdl [lindex $args 0] + set lCons [$hdl constraints] + + set ret [list] + for {set i 0} {$i < [llength $lCons]} {incr i} { + array set C [lindex $lCons $i] + if {$C(usable)} { + lappend ret use $i + $hdl in $i 1 + } + } + return $ret + } + + xFilter { + set lArg [lindex $args 2] + lappend ::lFilterArg {*}$lArg + return [list sql "SELECT rowid, a, b FROM $src"] + } + + } + + return {} +} + +do_execsql_test 3.0 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); +} + +foreach {tn sql lfa} { + 1 "SELECT * FROM vt1 WHERE b IN (10, 20, 30)" {{10 20 30}} + 2 "SELECT * FROM vt1 WHERE b IN ('abc', 'def')" {{abc def}} + 3 "SELECT * FROM vt1 WHERE a IS NULL AND b IN ('abc', 'def')" {{} {abc def}} + 4 "SELECT * FROM vt1 WHERE a IN (1,2,3) AND b IN ('abc', 'def')" + {{1 2 3} {abc def}} + + 5 "SELECT * FROM vt1 + WHERE a IN (SELECT 1 UNION SELECT 2) AND b IN ('abc', 'def')" + {{1 2} {abc def}} + + 6 "SELECT * FROM vt1 + WHERE b IN ('abc', 'def') AND a IN (SELECT 1 UNION SELECT 2)" + {{abc def} {1 2}} +} { + do_test 3.$tn { + set ::lFilterArg [list] + execsql $sql + set ::lFilterArg + } $lfa +} + +#explain_i { SELECT * FROM vt1 WHERE b IN (10, 20, 30) } + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b, c)" + } + + xBestIndex { + set hdl [lindex $args 0] + set lCons [$hdl constraints] + + set ret [list] + for {set i 0} {$i < [llength $lCons]} {incr i} { + lappend ::lBestIndexRhs [$hdl rhs_value $i -] + } + return $ret + } + + xFilter { + return [list sql "SELECT rowid, a, b, c FROM $src"] + } + + } + + return {} +} + +do_execsql_test 4.0 { + CREATE TABLE t1(a, b, c); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); +} + +foreach {tn sql lbir} { + 1 "SELECT * FROM vt1 WHERE b = 10" {10} + 2 "SELECT * FROM vt1 WHERE a = 'abc' AND b < 30" {abc 30} + 3 "SELECT * FROM vt1 WHERE a = 'abc' AND b < 30+2" {abc -} + 4 "SELECT * FROM vt1 WHERE a IN (1,2,3) AND b < 30+2" {- -} + 5 "SELECT * FROM vt1 WHERE a IS 111 AND b < 30+2" {111 -} +} { + do_test 4.$tn { + set ::lBestIndexRhs [list] + execsql $sql + set ::lBestIndexRhs + } $lbir +} + +#------------------------------------------------------------------------- +reset_db +db cache size 0 +register_tcl_module db + +set ::vtab_handle_in 1 +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b, c)" + } + + xBestIndex { + set lCols [list a b c] + + set hdl [lindex $args 0] + set lCons [$hdl constraints] + set lOrder [$hdl order] + + set L "" + set O "" + set W [list] + set a 0 + for {set i 0} {$i < [llength $lCons]} {incr i} { + array set C [lindex $lCons $i] + if {$C(usable)} { + if { $C(op)=="eq" } { + set bIn 0 + if {$::vtab_handle_in} { set bIn [$hdl in $i 1] } + if {$bIn} { + lappend W "[lindex $lCols $C(column)] IN (%I$a%)" + } else { + lappend W "[lindex $lCols $C(column)] = %$a%" + } + lappend ret omit $i + } + if { $C(op)=="limit" } { set L " LIMIT %$a%" ; lappend ret use $i } + if { $C(op)=="offset" } { set O " OFFSET %$a%" ; lappend ret use $i } + incr a + } + } + + set order "" + set selectlist "rowid, a, b, c" + if {[llength $lOrder]} { + array set sl [list] + set lO [list] + foreach s $lOrder { + array set C $s + set ad "" + if {$C(desc)} { set ad " DESC" } + lappend lO "[lindex $lCols $C(column)]$ad" + set sl($C(column)) 1 + } + if {[$hdl distinct]==2} { + set selectlist "DISTINCT 0" + foreach i {0 1 2} { + if {[info exists sl($i)]} { + append selectlist ", [lindex $lCols $i]" + } else { + append selectlist ", 0" + } + } + } else { + set order " ORDER BY [join $lO ,]" + } + } + + set where "" + if {[llength $W]} { set where " WHERE [join $W { AND }]" } + set sql "SELECT $selectlist FROM $src$where$order$L$O" + + lappend ret idxStr $sql + return $ret + } + + xFilter { + foreach {idxnum idxstr lArg} $args {} + set ii 0 + set sql $idxstr + foreach a $lArg { + set sql [string map [list %$ii% $a] $sql] + set sql [string map [list %I$ii% [join $a ,]] $sql] + incr ii + } + lappend ::lFilterSql $sql + + if {[regexp {OFFSET (.*)$} $sql -> off]} { + set real_sql " + WITH c(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM c WHERE i<$off ) + SELECT 0,0,0,0 FROM c + UNION ALL SELECT * FROM ( + $sql + ) + " + } else { + set real_sql $sql + } + + return [list sql $real_sql] + } + + } + + return {} +} + +do_execsql_test 5.0 { + CREATE TABLE t1(a, b, c); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(2, 3, 4); + INSERT INTO t1 VALUES(3, 4, 5); + INSERT INTO t1 VALUES(1, 5, 6); + INSERT INTO t1 VALUES(2, 6, 7); + INSERT INTO t1 VALUES(3, 7, 8); + INSERT INTO t1 VALUES(1, 8, 9); + INSERT INTO t1 VALUES(2, 9, 0); +} + +proc do_vtab_test {tn sql vtsql {res {}}} { + set ::lFilterSql [list] + uplevel [list do_execsql_test $tn.1 $sql $res] + uplevel [list do_test $tn.2 {set ::lFilterSql} [list {*}$vtsql]] +} + +do_vtab_test 5.1.1 { + SELECT DISTINCT a FROM vt1 +} { + {SELECT DISTINCT 0, a, 0, 0 FROM t1} +} {1 2 3} + +do_vtab_test 5.1.2 { + SELECT DISTINCT a FROM vt1 ORDER BY a +} { + {SELECT rowid, a, b, c FROM t1 ORDER BY a} +} {1 2 3} + +do_vtab_test 5.1.3 { + SELECT DISTINCT a FROM vt1 WHERE c IN (4,5,6,7,8) +} { + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c IN (4,5,6,7,8)} +} {2 3 1} + +set ::vtab_handle_in 0 +do_vtab_test 5.1.4 { + SELECT DISTINCT a FROM vt1 WHERE c IN (4,5,6,7,8) +} { + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 4} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 5} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 6} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 7} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 8} +} {2 3 1} + +set ::vtab_handle_in 1 +do_vtab_test 5.1.5a { + SELECT a, b, c FROM vt1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2 +} { + {SELECT rowid, a, b, c FROM t1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2} +} {1 5 6 2 6 7} + +set ::vtab_handle_in 0 +do_vtab_test 5.1.5b { + SELECT a, b, c FROM vt1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2 +} { + {SELECT rowid, a, b, c FROM t1 WHERE c = 4} + {SELECT rowid, a, b, c FROM t1 WHERE c = 5} + {SELECT rowid, a, b, c FROM t1 WHERE c = 6} + {SELECT rowid, a, b, c FROM t1 WHERE c = 7} +} {1 5 6 2 6 7} +set ::vtab_handle_in 1 + +finish_test diff --git a/test/bind2.test b/test/bind2.test new file mode 100644 index 000000000..1ebf35c2e --- /dev/null +++ b/test/bind2.test @@ -0,0 +1,63 @@ +# 2022 Feb 10 +# +# 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 bind2 + + +# Test that using bind_value() on a REAL sqlite3_value that was stored +# as an INTEGER works properly. +# +# 1.1: An IntReal value read from a table, +# 1.2: IntReal values obtained via the sqlite3_preupdate_old|new() +# interfaces. +# +do_execsql_test 1.0 { + CREATE TABLE t1(a REAL); + INSERT INTO t1 VALUES(42.0); + SELECT * FROM t1; +} {42.0} + +do_test 1.1 { + set stmt [sqlite3_prepare db "SELECT ?" -1 tail] + sqlite3_bind_value_from_select $stmt 1 "SELECT a FROM t1" + sqlite3_step $stmt + sqlite3_column_text $stmt 0 +} {42.0} +sqlite3_finalize $stmt + +ifcapable !preupdate { + finish_test + return +} + +proc preup {args} { + set stmt [sqlite3_prepare db "SELECT ?" -1 tail] + sqlite3_bind_value_from_preupdate $stmt 1 old 0 + sqlite3_step $stmt + lappend ::reslist [sqlite3_column_text $stmt 0] + sqlite3_reset $stmt + sqlite3_bind_value_from_preupdate $stmt 1 new 0 + sqlite3_step $stmt + lappend ::reslist [sqlite3_column_text $stmt 0] + sqlite3_finalize $stmt +} +db preupdate hook preup + +do_test 1.2 { + set ::reslist [list] + execsql { UPDATE t1 SET a=43; } + set ::reslist +} {42.0 43.0} + +finish_test diff --git a/test/capi3d.test b/test/capi3d.test index b56d7929b..7cad2870c 100644 --- a/test/capi3d.test +++ b/test/capi3d.test @@ -100,8 +100,32 @@ proc test_is_readonly {testname sql truth} { sqlite3_finalize $STMT set rc } $sql] $truth + + # EVIDENCE-OF: R-61212-30018 If prepared statement X is an EXPLAIN or + # EXPLAIN QUERY PLAN statement, then sqlite3_stmt_readonly(X) returns + # the same value as if the EXPLAIN or EXPLAIN QUERY PLAN prefix were + # omitted. + # + do_test $testname.explain [format { + set DB [sqlite3_connection_pointer db] + set STMT [sqlite3_prepare $DB {EXPLAIN %s} -1 TAIL] + set rc [sqlite3_stmt_readonly $STMT] + sqlite3_finalize $STMT + set rc + } $sql] $truth + do_test $testname.eqp [format { + set DB [sqlite3_connection_pointer db] + set STMT [sqlite3_prepare $DB {EXPLAIN QUERY PLAN %s} -1 TAIL] + set rc [sqlite3_stmt_readonly $STMT] + sqlite3_finalize $STMT + set rc + } $sql] $truth } +# EVIDENCE-OF: R-23332-64992 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. +# 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)} @@ -120,6 +144,68 @@ do_test capi3-2.49 { } 1 +# EVIDENCE-OF: R-04929-09147 This routine returns false if there is any +# possibility that the statement might change the database file. +# +# EVIDENCE-OF: R-13288-53765 A false return does not guarantee that the +# statement will change the database file. +# +# EVIDENCE-OF: R-22182-18548 For example, an UPDATE statement might have +# a WHERE clause that makes it a no-op, but the sqlite3_stmt_readonly() +# result would still be false. +# +# EVIDENCE-OF: R-50998-48593 Similarly, a CREATE TABLE IF NOT EXISTS +# statement is a read-only no-op if the table already exists, but +# sqlite3_stmt_readonly() still returns false for such a statement. +# +db eval { + CREATE TABLE t2(a,b,c); + INSERT INTO t2 VALUES(1,2,3); +} +test_is_readonly capi3d-2.11 {UPDATE t2 SET a=a+1 WHERE false} 0 +test_is_readonly capi3d-2.12 {CREATE TABLE IF NOT EXISTS t2(x,y)} 0 + + +# EVIDENCE-OF: R-37014-01401 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. +# +test_is_readonly capi3d-2.13 {ATTACH ':memory:' AS mem1} 1 +db eval {ATTACH ':memory:' AS mem1} +test_is_readonly capi3d-2.14 {DETACH mem1} 1 +db eval {DETACH mem1} + +# EVIDENCE-OF: R-07474-04783 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. +# +test_is_readonly capi3d-2.15 {BEGIN} 1 +test_is_readonly capi3d-2.16 {COMMIT} 1 +test_is_readonly capi3d-2.17 {SAVEPOINT one} 1 +test_is_readonly capi3d-2.18 {RELEASE one} 1 + +# EVIDENCE-OF: R-36961-63052 The sqlite3_stmt_readonly() interface +# returns true for BEGIN since BEGIN merely sets internal flags, but the +# BEGIN IMMEDIATE and BEGIN EXCLUSIVE commands do touch the database and +# so sqlite3_stmt_readonly() returns false for those commands. +# +test_is_readonly capi3d-2.19 {BEGIN IMMEDIATE} 0 +test_is_readonly capi3d-2.20 {BEGIN EXCLUSIVE} 0 + +# EVIDENCE-OF: R-21769-42523 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. +# +proc evalsql {sql} {db eval $sql} +db func eval evalsql +test_is_readonly capi3d-2.21 {SELECT eval('DELETE FROM t1') FROM t2} 1 + # Tests for the is-explain interface. # proc test_is_explain {testname sql truth} { diff --git a/test/check.test b/test/check.test index 94fe1d14e..10d1cf4be 100644 --- a/test/check.test +++ b/test/check.test @@ -21,6 +21,8 @@ ifcapable !check { finish_test return } +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_test check-1.1 { execsql { @@ -138,6 +140,8 @@ do_test check-2.2 { } {1 2.2 three} db close sqlite3 db test.db +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_test check-2.3 { execsql { INSERT INTO t2 VALUES(NULL, NULL, NULL); diff --git a/test/collate1.test b/test/collate1.test index 007dd7c37..b65b85047 100644 --- a/test/collate1.test +++ b/test/collate1.test @@ -338,6 +338,7 @@ do_test collate1-5.3 { #------------------------------------------------------------------------- # Fix problems with handling collation sequences named '"""'. # +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_execsql_test 6.1 { SELECT """"""""; } {\"\"\"} diff --git a/test/ctime.test b/test/ctime.test index 1f07c1a94..26b2fa2ee 100644 --- a/test/ctime.test +++ b/test/ctime.test @@ -81,6 +81,7 @@ ifcapable threadsafe2 { # SQLITE_THREADSAFE should pretty much always be defined # one way or the other, and it must have a value of 0 or 1. +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_test ctime-1.4.1 { catchsql { SELECT sqlite_compileoption_used('SQLITE_THREADSAFE'); diff --git a/test/date.test b/test/date.test index f002334d1..62233ad8f 100644 --- a/test/date.test +++ b/test/date.test @@ -239,138 +239,70 @@ datetest 5.13 {datetime('1994-04-16 14:00:00Zulu')} NULL datetest 5.14 {datetime('1994-04-16 14:00:00Z +05:00')} NULL datetest 5.15 {datetime('1994-04-16 14:00:00 +05:00 Z')} NULL -# localtime->utc and utc->localtime conversions. These tests only work -# if the localtime is in the US Eastern Time (the time in Charlotte, NC -# and in New York.) +# localtime->utc and utc->localtime conversions. # -# On non-Vista Windows platform, '2006-03-31' is treated incorrectly as being -# in DST giving a 4 hour offset instead of 5. In 2007, DST was extended to -# start three weeks earlier (second Sunday in March) and end one week -# later (first Sunday in November). Older Windows systems apply this -# new rule incorrectly to dates prior to 2007. +# Use SQLITE_TESTCTRL_LOCALTIME_FAULT=2 to set an alternative localtime_r() +# implementation that is not locale-dependent. This testing localtime_r() +# operates as follows: # -# It might be argued that this is masking a problem on non-Vista Windows -# platform. A ticket has already been opened for this issue -# (http://www.sqlite.org/cvstrac/tktview?tn=2322). This is just to prevent -# more confusion/reports of the issue. +# (1) Localtime is 30 minutes earlier than (west of) UTC on +# even days (counting from 1970-01-01) # - -# $tzoffset_old should be 5 if DST is working correctly. -set tzoffset_old [db one { - SELECT CAST(24*(julianday('2006-03-31') - - julianday('2006-03-31','localtime'))+0.5 - AS INT) -}] - -# $tzoffset_new should be 4 if DST is working correctly. -set tzoffset_new [db one { - SELECT CAST(24*(julianday('2007-03-31') - - julianday('2007-03-31','localtime'))+0.5 - AS INT) -}] - -# Warn about possibly broken Windows DST implementations. -if {$::tcl_platform(platform)=="windows" && $tzoffset_new==4 && $tzoffset_old==4} { - puts "******************************************************************" - puts "N.B.: The DST support provided by your current O/S seems to be" - puts "suspect in that it is reporting incorrect DST values for dates" - puts "prior to 2007. This is the known case for most (all?) non-Vista" - puts "Windows versions. Please see ticket #2322 for more information." - puts "******************************************************************" +# (2) Localtime is 30 minutes later than (east of) UTC on odd days. +# +# (3) The function fails for the specific date/time value +# of 2000-05-29 14:16:00 in order to test the ability of +# SQLite to deal with localtime_r() failures. +# +proc local_to_utc {tn utc local} { + do_execsql_test date-$tn "SELECT datetime('$utc','localtime')" [list $local] +} +proc utc_to_local {tn local utc} { + do_execsql_test date-$tn "SELECT datetime('$local','utc')" [list $utc] } -if {$tzoffset_new==4} { - datetest 6.1 {datetime('2000-10-29 05:59:00','localtime')}\ - {2000-10-29 01:59:00} - datetest 6.1.1 {datetime('2006-10-29 05:59:00','localtime')}\ - {2006-10-29 01:59:00} - datetest 6.1.2 {datetime('2007-11-04 05:59:00','localtime')}\ - {2007-11-04 01:59:00} - - # If the new and old DST rules seem to be working correctly... - if {$tzoffset_new==4 && $tzoffset_old==5} { - datetest 6.2 {datetime('2000-10-29 06:00:00','localtime')}\ - {2000-10-29 01:00:00} - datetest 6.2.1 {datetime('2006-10-29 06:00:00','localtime')}\ - {2006-10-29 01:00:00} - } - datetest 6.2.2 {datetime('2007-11-04 06:00:00','localtime')}\ - {2007-11-04 01:00:00} - - # If the new and old DST rules seem to be working correctly... - if {$tzoffset_new==4 && $tzoffset_old==5} { - datetest 6.3 {datetime('2000-04-02 06:59:00','localtime')}\ - {2000-04-02 01:59:00} - datetest 6.3.1 {datetime('2006-04-02 06:59:00','localtime')}\ - {2006-04-02 01:59:00} - } - datetest 6.3.2 {datetime('2007-03-11 07:00:00','localtime')}\ - {2007-03-11 03:00:00} - - datetest 6.4 {datetime('2000-04-02 07:00:00','localtime')}\ - {2000-04-02 03:00:00} - datetest 6.4.1 {datetime('2006-04-02 07:00:00','localtime')}\ - {2006-04-02 03:00:00} - datetest 6.4.2 {datetime('2007-03-11 07:00:00','localtime')}\ - {2007-03-11 03:00:00} - - datetest 6.5 {datetime('2000-10-29 01:59:00','utc')} {2000-10-29 05:59:00} - datetest 6.5.1 {datetime('2006-10-29 01:59:00','utc')} {2006-10-29 05:59:00} - datetest 6.5.2 {datetime('2007-11-04 01:59:00','utc')} {2007-11-04 05:59:00} - - # If the new and old DST rules seem to be working correctly... - if {$tzoffset_new==4 && $tzoffset_old==5} { - datetest 6.6 {datetime('2000-10-29 02:00:00','utc')} {2000-10-29 07:00:00} - datetest 6.6.1 {datetime('2006-10-29 02:00:00','utc')} {2006-10-29 07:00:00} - } - datetest 6.6.2 {datetime('2007-11-04 02:00:00','utc')} {2007-11-04 07:00:00} +sqlite3_test_control SQLITE_TESTCTRL_LOCALTIME_FAULT 2 +local_to_utc 6.1 {2000-10-29 12:00:00} {2000-10-29 12:30:00} +utc_to_local 6.2 {2000-10-29 12:30:00} {2000-10-29 12:00:00} +local_to_utc 6.3 {2000-10-30 12:00:00} {2000-10-30 11:30:00} +utc_to_local 6.4 {2000-10-30 11:30:00} {2000-10-30 12:00:00} - # If the new and old DST rules seem to be working correctly... - if {$tzoffset_new==4 && $tzoffset_old==5} { - datetest 6.7 {datetime('2000-04-02 01:59:00','utc')} {2000-04-02 06:59:00} - datetest 6.7.1 {datetime('2006-04-02 01:59:00','utc')} {2006-04-02 06:59:00} - } - datetest 6.7.2 {datetime('2007-03-11 01:59:00','utc')} {2007-03-11 06:59:00} - - datetest 6.8 {datetime('2000-04-02 02:00:00','utc')} {2000-04-02 06:00:00} - datetest 6.8.1 {datetime('2006-04-02 02:00:00','utc')} {2006-04-02 06:00:00} - datetest 6.8.2 {datetime('2007-03-11 02:00:00','utc')} {2007-03-11 06:00:00} - - # The 'utc' modifier is a no-op if the LHS is known to already be in UTC - datetest 6.9.1 {datetime('2015-12-23 12:00:00','utc')} {2015-12-23 17:00:00} - datetest 6.9.2 {datetime('2015-12-23 12:00:00z','utc')} {2015-12-23 12:00:00} - datetest 6.9.3 {datetime('2015-12-23 12:00:00-03:00','utc')} \ - {2015-12-23 15:00:00} - datetest 6.9.4 {datetime('2015-12-23 12:00:00','utc','utc','utc')} \ - {2015-12-23 17:00:00} - - - datetest 6.10 {datetime('2000-01-01 12:00:00','localtime')} \ - {2000-01-01 07:00:00} - datetest 6.11 {datetime('1969-01-01 12:00:00','localtime')} \ - {1969-01-01 07:00:00} - datetest 6.12 {datetime('2039-01-01 12:00:00','localtime')} \ - {2039-01-01 07:00:00} - datetest 6.13 {datetime('2000-07-01 12:00:00','localtime')} \ - {2000-07-01 08:00:00} - datetest 6.14 {datetime('1969-07-01 12:00:00','localtime')} \ - {1969-07-01 07:00:00} - datetest 6.15 {datetime('2039-07-01 12:00:00','localtime')} \ - {2039-07-01 07:00:00} - set sqlite_current_time \ - [db eval {SELECT strftime('%s','2000-07-01 12:34:56')}] - datetest 6.16 {datetime('now','localtime')} {2000-07-01 08:34:56} - datetest 6.17 {datetime('now','localtimex')} NULL - datetest 6.18 {datetime('now','localtim')} NULL - set sqlite_current_time 0 -} +local_to_utc 6.5 {2000-10-28 23:59:59} {2000-10-28 23:29:59} +local_to_utc 6.6 {2000-10-29 00:00:00} {2000-10-29 00:30:00} -# These two are a bit of a scam. They are added to ensure that 100% of -# the date.c file is covered by testing, even when the time-zone -# is not -0400 (the condition for running of the block of tests above). +# The previous two cases establish that no such localtime as +# 2000-10-29 00:10:00 exists. Verify that we get a reasonable +# answer if we try to convert this non-existant localtime to utc? # -datetest 6.19 {datetime('2039-07-01 12:00:00','localtime',null)} NULL -datetest 6.20 {datetime('2039-07-01 12:00:00','utc',null)} NULL +utc_to_local 6.7 {2000-10-29 00:10:00} {2000-10-28 23:40:00} + +local_to_utc 6.8 {2022-02-10 23:59:59} {2022-02-11 00:29:59} +local_to_utc 6.9 {2022-02-11 00:00:00} {2022-02-10 23:30:00} +local_to_utc 6.10 {2022-02-10 23:45:00} {2022-02-11 00:15:00} +local_to_utc 6.11 {2022-02-11 00:45:00} {2022-02-11 00:15:00} + +# The previous two cases show that two different UTC values give +# the same localtime of 2022-02-11 00:15:00. When converting from +# that localtime back to UTC, we should get one or the other of +# the two UTC values. +# +utc_to_local 6.12 {2022-02-11 00:15:00} {2022-02-11 00:45:00} + +# If localtime_r() fails, the datetime() SQL function should raise an error +# +do_catchsql_test date-6.20 { + SELECT datetime('2000-05-29 14:16:00','localtime'); +} {1 {local time unavailable}} + +# Modifiers work for dates that are way out of band for localtime_r() +# +local_to_utc 6.21 {1800-10-29 12:00:00} {1800-10-29 12:30:00} +utc_to_local 6.22 {1800-10-29 12:30:00} {1800-10-29 12:00:00} +local_to_utc 6.23 {3000-10-30 12:00:00} {3000-10-30 11:30:00} +utc_to_local 6.24 {3000-10-30 11:30:00} {3000-10-30 12:00:00} + +# Restore the use of the OS localtime_r() before going on... +sqlite3_test_control SQLITE_TESTCTRL_LOCALTIME_FAULT 0 # Date-time functions that contain NULL arguments return a NULL # result. @@ -607,6 +539,9 @@ datetest 17.5 {datetime(2457828,'start of year')} {2017-01-01 00:00:00} datetest 17.6 {datetime(37,'start of year')} NULL datetest 17.7 {datetime(38,'start of year')} {-4712-01-01 00:00:00} - +# 2022-03-04 https://sqlite.org/forum/forumpost/2ffbaa2c3fd7fb82 +# The 'localtime' modifier should preserve fractional seconds. +# +datetest 18.1 {strftime('%f',1.234,'unixepoch','localtime')} {01.234} finish_test diff --git a/test/date3.test b/test/date3.test new file mode 100644 index 000000000..1798a3478 --- /dev/null +++ b/test/date3.test @@ -0,0 +1,149 @@ +# 2022-01-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 date and time functions. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Skip this whole file if date and time functions are omitted +# at compile-time +# +ifcapable {!datetime} { + finish_test + return +} + +proc datetest {tnum expr result} { + do_test date3-$tnum [subst { + execsql "SELECT coalesce($expr,'NULL')" + }] [list $result] +} +set tcl_precision 15 + +# EVIDENCE-OF: R-45708-63005 unixepoch(time-value, modifier, modifier, +# ...) +# +datetest 1.1 {unixepoch('1970-01-01')} {0} +datetest 1.2 {unixepoch('1969-12-31 23:59:59')} {-1} +datetest 1.3 {unixepoch('2106-02-07 06:28:15')} {4294967295} +datetest 1.4 {unixepoch('2106-02-07 06:28:16')} {4294967296} +datetest 1.5 {unixepoch('9999-12-31 23:59:59')} {253402300799} +datetest 1.6 {unixepoch('0000-01-01 00:00:00')} {-62167219200} + +# EVIDENCE-OF: R-30877-63179 The unixepoch() function returns a unix +# timestamp - the number of seconds since 1970-01-01 00:00:00 UTC. +# +for {set i 1} {$i<=100} {incr i} { + set x [expr {int(rand()*0xfffffffff)-0xffffffff}] + datetest 1.7.$i "unixepoch($x,'unixepoch')==$x" {1} +} + +# EVIDENCE-OF: R-62992-54137 The unixepoch() always returns an integer, +# even if the input time-value has millisecond precision. +# +datetest 1.8 {unixepoch('2022-01-27 12:59:28.052')} {1643288368} + +# EVIDENCE-OF: R-05412-24332 If the time-value is numeric (the +# DDDDDDDDDD format) then the 'auto' modifier causes the time-value to +# interpreted as either a julian day number or a unix timestamp, +# depending on its magnitude. +# +# EVIDENCE-OF: R-56763-40111 If the value is between 0.0 and +# 5373484.499999, then it is interpreted as a julian day number +# (corresponding to dates between -4713-11-24 12:00:00 and 9999-12-31 +# 23:59:59, inclusive). +# +# EVIDENCE-OF: R-07289-49223 For numeric values outside of the range of +# valid julian day numbers, but within the range of -210866760000 to +# 253402300799, the 'auto' modifier causes the value to be interpreted +# as a unix timestamp. +# +# EVIDENCE-OF: R-20795-34947 Other numeric values are out of range and +# cause a NULL return. +# +foreach {tn jd date} { + 2.1 0.0 {-4713-11-24 12:00:00} + 2.2 5373484.4999999 {9999-12-31 23:59:59} + 2.3 2440587.5 {1970-01-01 00:00:00} + 2.4 2440587.49998843 {1969-12-31 23:59:59} + 2.5 2440615.7475463 {1970-01-29 05:56:28} + + 2.10 -1 {1969-12-31 23:59:59} + 2.11 5373485 {1970-03-04 04:38:05} + 2.12 -210866760000 {-4713-11-24 12:00:00} + 2.13 253402300799 {9999-12-31 23:59:59} + + 2.20 -210866760001 {NULL} + 2.21 253402300800 {NULL} +} { + datetest $tn "datetime($jd,'auto')" $date +} + +# EVIDENCE-OF: R-38886-35357 The 'auto' modifier is a no-op for text +# time-values. +# +datetest 2.30 {date('2022-01-29','auto')==date('2022-01-29')} {1} + +# EVIDENCE-OF: R-53132-26856 The 'auto' modifier can be used to work +# with date/time values even in cases where it is not known if the +# julian day number or unix timestamp formats are in use. +# +do_execsql_test date3-2.40 { + WITH tx(timeval,datetime) AS ( + VALUES('2022-01-27 13:15:44','2022-01-27 13:15:44'), + (2459607.05260275,'2022-01-27 13:15:44'), + (1643289344,'2022-01-27 13:15:44') + ) + SELECT datetime(timeval,'auto') == datetime FROM tx; +} {1 1 1} + +# EVIDENCE-OF: R-49255-55373 The "unixepoch" modifier (11) only works if +# it immediately follows a time value in the DDDDDDDDDD format. +# +# EVIDENCE-OF: R-23075-39245 This modifier causes the DDDDDDDDDD to be +# interpreted not as a Julian day number as it normally would be, but as +# Unix Time - the number of seconds since 1970. +# +datetest 3.1 {datetime(2459607.05,'+1 hour','unixepoch')} {NULL} +datetest 3.2 {datetime(2459607.05,'unixepoch','+1 hour')} {1970-01-29 12:13:27} + +# EVIDENCE-OF: R-21150-52363 The "julianday" modifier must immediately +# follow the initial time-value which must be of the form DDDDDDDDD. +# +# EVIDENCE-OF: R-31176-64601 Any other use of the 'julianday' modifier +# is an error and causes the function to return NULL. +# +# EVIDENCE-OF: R-32483-36353 The 'julianday' modifier forces the +# time-value number to be interpreted as a julian-day number. +# +# EVIDENCE-OF: R-25859-20124 The only difference is that adding +# 'julianday' forces the DDDDDDDDD time-value format, and causes a NULL +# to be returned if any other time-value format is used. +# +datetest 4.1 {datetime(2459607,'julianday')} {2022-01-27 12:00:00} +datetest 4.2 {datetime(2459607,'+1 hour','julianday')} {NULL} +datetest 4.3 {datetime('2022-01-27','julianday')} {NULL} + + + +# EVIDENCE-OF: R-33431-18865 Unix timestamps for the first 63 days of +# 1970 will be interpreted as julian day numbers. +# +do_execsql_test date3-5.0 { + WITH inc(x) AS (VALUES(-10) UNION ALL SELECT x+1 FROM inc WHERE x<100) + SELECT count(*) FROM inc + WHERE datetime('1970-01-01',format('%+d days',x)) + <> datetime(unixepoch('1970-01-01',format('%+d days',x)),'auto'); +} {63} + +finish_test diff --git a/test/dbpage.test b/test/dbpage.test index 288af8931..0646a70b0 100644 --- a/test/dbpage.test +++ b/test/dbpage.test @@ -102,4 +102,10 @@ do_catchsql_test 270 { PRAGMA aux1.integrity_check; } {0 ok} +db close +sqlite3 db :memory: +do_execsql_test 300 { + SELECT * FROM sqlite_temp_schema, sqlite_dbpage; +} {} + finish_test diff --git a/test/e_createtable.test b/test/e_createtable.test index fa43d94b5..3ecffae5c 100644 --- a/test/e_createtable.test +++ b/test/e_createtable.test @@ -493,10 +493,10 @@ do_execsql_test e_createtable-1.7.0 { 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}} + 3 "CREATE TABLE v1(a, b)" {{view 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}} + 6 "CREATE TABLE auxa.view1(a, b)" {{view view1 already exists}} } do_createtable_tests 1.7.2 { 1 "CREATE TABLE auxa.t1(a, b)" {} diff --git a/test/e_expr.test b/test/e_expr.test index 93ca0268c..3f8d5ad85 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -265,13 +265,10 @@ do_execsql_test e_expr-6.4 {SELECT -72%5} {-2} do_execsql_test e_expr-6.5 {SELECT 72.35%5} {2.0} #------------------------------------------------------------------------- -# Test that the results of all binary operators are either numeric or -# NULL, except for the || operator, which may evaluate to either a text -# value or NULL. -# -# EVIDENCE-OF: R-20665-17792 The result of any binary operator is either -# a numeric value or NULL, except for the || concatenation operator -# which always evaluates to either NULL or a text value. +# EVIDENCE-OF: R-15904-00746 The result of any binary operator is either +# a numeric value or NULL, except for the || concatenation operator, and +# the -> and ->> extract operators which evaluate to either +# NULL or a text value. # set literals { 1 'abc' 2 'hexadecimal' 3 '' diff --git a/test/e_fkey.test b/test/e_fkey.test index ab3c29033..3662a3998 100644 --- a/test/e_fkey.test +++ b/test/e_fkey.test @@ -48,9 +48,9 @@ proc do_detail_test {tn sql res} { ########################################################################### #------------------------------------------------------------------------- -# EVIDENCE-OF: R-33710-56344 In order to use foreign key constraints in +# EVIDENCE-OF: R-37672-59189 In order to use foreign key constraints in # SQLite, the library must be compiled with neither -# SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined. +# SQLITE_OMIT_FOREIGN_KEY nor SQLITE_OMIT_TRIGGER defined. # ifcapable trigger&&foreignkey { do_test e_fkey-1 { diff --git a/test/eval.test b/test/eval.test index 360d158f3..159e8754d 100644 --- a/test/eval.test +++ b/test/eval.test @@ -81,7 +81,7 @@ do_test eval-3.1 { } {1 {} 102 2 {} 103 3 {} 104 4 {} 105} do_test eval-4.1 { - execsql { SELECT test_eval('SELECT "abcdefghij"') } + execsql { SELECT test_eval('SELECT ''abcdefghij''') } } {abcdefghij} finish_test diff --git a/test/expr.test b/test/expr.test index ec5c55c2e..c64b6cb70 100644 --- a/test/expr.test +++ b/test/expr.test @@ -970,6 +970,7 @@ do_realnum_test expr-13.7 { } } {9.22337203685478e+18} +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_execsql_test expr-13.8 { SELECT "" <= ''; } {1} diff --git a/test/fts3aj.test b/test/fts3aj.test index f3d46f2ad..0c8969116 100644 --- a/test/fts3aj.test +++ b/test/fts3aj.test @@ -6,8 +6,6 @@ # This file implements regression tests for SQLite library. This # tests creating fts3 tables in an attached database. # -# $Id: fts3aj.test,v 1.1 2007/08/20 17:38:42 shess Exp $ -# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -25,14 +23,14 @@ sqlite3 db2 test2.db db eval { CREATE VIRTUAL TABLE t3 USING fts3(content); - INSERT INTO t3 (rowid, content) VALUES(1, "hello world"); + INSERT INTO t3 (rowid, content) VALUES(1, 'hello world'); } db2 eval { CREATE VIRTUAL TABLE t1 USING fts3(content); - INSERT INTO t1 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t1 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t1 (rowid, content) VALUES(3, "cruel world"); + INSERT INTO t1 (rowid, content) VALUES(1, 'hello world'); + INSERT INTO t1 (rowid, content) VALUES(2, 'hello there'); + INSERT INTO t1 (rowid, content) VALUES(3, 'cruel world'); } # This has always worked because the t1_* tables used by fts3 will be @@ -56,9 +54,9 @@ do_test fts3aj-1.2 { execsql { ATTACH DATABASE 'test2.db' AS two; CREATE VIRTUAL TABLE two.t2 USING fts3(content); - INSERT INTO t2 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t2 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t2 (rowid, content) VALUES(3, "cruel world"); + INSERT INTO t2 (rowid, content) VALUES(1, 'hello world'); + INSERT INTO t2 (rowid, content) VALUES(2, 'hello there'); + INSERT INTO t2 (rowid, content) VALUES(3, 'cruel world'); SELECT rowid FROM t2 WHERE t2 MATCH 'hello'; DETACH DATABASE two; } @@ -74,8 +72,8 @@ do_test fts3aj-1.3 { ATTACH DATABASE 'test2.db' AS two; CREATE VIRTUAL TABLE two.t3 USING fts3(content); - INSERT INTO two.t3 (rowid, content) VALUES(2, "hello there"); - INSERT INTO two.t3 (rowid, content) VALUES(3, "cruel world"); + INSERT INTO two.t3 (rowid, content) VALUES(2, 'hello there'); + INSERT INTO two.t3 (rowid, content) VALUES(3, 'cruel world'); SELECT rowid FROM two.t3 WHERE t3 MATCH 'hello'; DETACH DATABASE two; diff --git a/test/fts3ak.test b/test/fts3ak.test index a263f0b74..080efe52b 100644 --- a/test/fts3ak.test +++ b/test/fts3ak.test @@ -21,17 +21,17 @@ ifcapable !fts3 { db eval { CREATE VIRTUAL TABLE t1 USING fts3(content); - INSERT INTO t1 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t1 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t1 (rowid, content) VALUES(3, "cruel world"); + INSERT INTO t1 (rowid, content) VALUES(1, 'hello world'); + INSERT INTO t1 (rowid, content) VALUES(2, 'hello there'); + INSERT INTO t1 (rowid, content) VALUES(3, 'cruel world'); } # Test that possibly-buffered inserts went through after commit. do_test fts3ak-1.1 { execsql { BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(4, "false world"); - INSERT INTO t1 (rowid, content) VALUES(5, "false door"); + INSERT INTO t1 (rowid, content) VALUES(4, 'false world'); + INSERT INTO t1 (rowid, content) VALUES(5, 'false door'); COMMIT TRANSACTION; SELECT rowid FROM t1 WHERE t1 MATCH 'world'; } @@ -42,8 +42,8 @@ do_test fts3ak-1.1 { do_test fts3ak-1.2 { execsql { BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(6, "another world"); - INSERT INTO t1 (rowid, content) VALUES(7, "another test"); + INSERT INTO t1 (rowid, content) VALUES(6, 'another world'); + INSERT INTO t1 (rowid, content) VALUES(7, 'another test'); SELECT rowid FROM t1 WHERE t1 MATCH 'world'; COMMIT TRANSACTION; } @@ -54,8 +54,8 @@ do_test fts3ak-1.2 { do_test fts3ak-1.3 { execsql { BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(8, "second world"); - INSERT INTO t1 (rowid, content) VALUES(9, "second sight"); + INSERT INTO t1 (rowid, content) VALUES(8, 'second world'); + INSERT INTO t1 (rowid, content) VALUES(9, 'second sight'); SELECT rowid FROM t1 WHERE t1 MATCH 'world'; ROLLBACK TRANSACTION; } @@ -73,8 +73,8 @@ do_test fts3ak-1.4 { do_test fts3ak-1.5 { execsql { BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(10, "second world"); - INSERT INTO t1 (rowid, content) VALUES(11, "second sight"); + INSERT INTO t1 (rowid, content) VALUES(10, 'second world'); + INSERT INTO t1 (rowid, content) VALUES(11, 'second sight'); ROLLBACK TRANSACTION; SELECT rowid FROM t1 WHERE t1 MATCH 'world'; } @@ -84,7 +84,7 @@ do_test fts3ak-1.5 { do_test fts3ak-1.6 { execsql { BEGIN; - INSERT INTO t1 (rowid, content) VALUES(12, "third world"); + INSERT INTO t1 (rowid, content) VALUES(12, 'third world'); COMMIT; SELECT rowid FROM t1 WHERE t1 MATCH 'third'; } @@ -95,7 +95,7 @@ do_test fts3ak-1.6 { do_test fts3ak-1.7 { execsql { BEGIN; - INSERT INTO t1 (rowid, content) VALUES(13, "third dimension"); + INSERT INTO t1 (rowid, content) VALUES(13, 'third dimension'); CREATE TABLE x (c); COMMIT; SELECT rowid FROM t1 WHERE t1 MATCH 'dimension'; diff --git a/test/fts3corrupt.test b/test/fts3corrupt.test index 828964b1b..8b958db5f 100644 --- a/test/fts3corrupt.test +++ b/test/fts3corrupt.test @@ -178,7 +178,7 @@ do_catchsql_test 6.10 { INSERT INTO f_segments (blockid) values (16); INSERT INTO f_segments values (0, x''); INSERT INTO f_stat VALUES (1,x'cf0f01'); - INSERT INTO f(f) VALUES ("merge=1"); + INSERT INTO f(f) VALUES ('merge=1'); } {1 {database disk image is malformed}} # 2020-03-02 https://bugs.chromium.org/p/chromium/issues/detail?id=1057441 diff --git a/test/fts3dropmod.test b/test/fts3dropmod.test new file mode 100644 index 000000000..d6b1677ac --- /dev/null +++ b/test/fts3dropmod.test @@ -0,0 +1,44 @@ +# 2021 December 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 regression tests for SQLite library. The +# focus of this script is testing the FTS3 module. +# +# $Id: fts3aa.test,v 1.1 2007/08/20 17:38:42 shess Exp $ +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix fts3dropmod + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts3 { + finish_test + return +} + +sqlite3_drop_modules db fts3 +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts3(x); +} +do_catchsql_test 1.1 { + CREATE VIRTUAL TABLE t2 USING fts4(x); +} {1 {no such module: fts4}} + +reset_db +sqlite3_drop_modules db fts4 +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts4(x); +} +do_catchsql_test 2.1 { + CREATE VIRTUAL TABLE t2 USING fts3(x); +} {1 {no such module: fts3}} + +finish_test diff --git a/test/fts4noti.test b/test/fts4noti.test index 670720397..87a05714b 100644 --- a/test/fts4noti.test +++ b/test/fts4noti.test @@ -173,7 +173,7 @@ do_execsql_test 6.1.1 { CREATE VIRTUAL TABLE t1 USING fts4( poiCategory, poiCategoryId, notindexed=poiCategoryId ); - INSERT INTO t1(poiCategory, poiCategoryId) values ("Restaurant", 6021); + INSERT INTO t1(poiCategory, poiCategoryId) values ('Restaurant', 6021); } do_execsql_test 6.1.2 { @@ -194,7 +194,7 @@ do_execsql_test 6.2.1 { CREATE VIRTUAL TABLE t1 USING fts4( poiCategory, poiCategoryId, notindexed=poiCategory ); - INSERT INTO t1(poiCategory, poiCategoryId) values ("Restaurant", 6021); + INSERT INTO t1(poiCategory, poiCategoryId) values ('Restaurant', 6021); } do_execsql_test 6.2.2 { diff --git a/test/func.test b/test/func.test index ca1027f50..df9d4dacf 100644 --- a/test/func.test +++ b/test/func.test @@ -1007,32 +1007,32 @@ do_test func-21.2 { } {1 {wrong number of arguments to function replace()}} do_test func-21.3 { execsql { - SELECT typeof(replace("This is the main test string", NULL, "ALT")); + SELECT typeof(replace('This is the main test string', NULL, 'ALT')); } } {null} do_test func-21.4 { execsql { - SELECT typeof(replace(NULL, "main", "ALT")); + SELECT typeof(replace(NULL, 'main', 'ALT')); } } {null} do_test func-21.5 { execsql { - SELECT typeof(replace("This is the main test string", "main", NULL)); + SELECT typeof(replace('This is the main test string', 'main', NULL)); } } {null} do_test func-21.6 { execsql { - SELECT replace("This is the main test string", "main", "ALT"); + SELECT replace('This is the main test string', 'main', 'ALT'); } } {{This is the ALT test string}} do_test func-21.7 { execsql { - SELECT replace("This is the main test string", "main", "larger-main"); + SELECT replace('This is the main test string', 'main', 'larger-main'); } } {{This is the larger-main test string}} do_test func-21.8 { execsql { - SELECT replace("aaaaaaa", "a", "0123456789"); + SELECT replace('aaaaaaa', 'a', '0123456789'); } } {0123456789012345678901234567890123456789012345678901234567890123456789} @@ -1315,7 +1315,7 @@ do_test func-29.1 { CREATE TABLE t29(id INTEGER PRIMARY KEY, x, y); INSERT INTO t29 VALUES(1, 2, 3), (2, NULL, 4), (3, 4.5, 5); INSERT INTO t29 VALUES(4, randomblob(1000000), 6); - INSERT INTO t29 VALUES(5, "hello", 7); + INSERT INTO t29 VALUES(5, 'hello', 7); } db close sqlite3 db test.db @@ -1507,4 +1507,19 @@ do_execsql_test func-35.200 { PRAGMA integrity_check; } {ok} +# 2021-01-07: The -> and ->> operators. +# +proc ptr1 {a b} { return "$a->$b" } +db func -> ptr1 +proc ptr2 {a b} { return "$a->>$b" } +db func ->> ptr2 +do_execsql_test func-36.100 { + SELECT 123 -> 456 +} {123->456} +do_execsql_test func-36.110 { + SELECT 123 ->> 456 +} {123->>456} + + + finish_test diff --git a/test/func6.test b/test/func6.test index 1e16a7ca3..fe90a755d 100644 --- a/test/func6.test +++ b/test/func6.test @@ -171,4 +171,13 @@ do_execsql_test func6-200 { SELECT offrec( sqlite_offset(y), x, y ) FROM t2 ORDER BY x } $z100 +# 2022-03-14 dbsqlfuzz 474499f3977d95fdf2dbcd99c50be1d0082e4c92 +reset_db +do_execsql_test func6-300 { + CREATE TABLE t2(a INT, b INT PRIMARY KEY) WITHOUT ROWID; + CREATE INDEX x3 ON t2(b); + CREATE TABLE t1(a INT PRIMARY KEY, b TEXT); + SELECT * FROM t1 WHERE a IN (SELECT sqlite_offset(b) FROM t2); +} {} + finish_test diff --git a/test/fuzz-oss1.test b/test/fuzz-oss1.test index e77b7ed0f..46feeb62e 100644 --- a/test/fuzz-oss1.test +++ b/test/fuzz-oss1.test @@ -329,6 +329,8 @@ ifcapable !fts3 { db close forcedelete test.db sqlite3 db test.db +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_test fuzz-oss1-gnomeshell { db eval { CREATE TABLE Resource (ID INTEGER NOT NULL PRIMARY KEY, Uri TEXT NOT diff --git a/test/fuzz.test b/test/fuzz.test index 0deed3b63..83dc79bc7 100644 --- a/test/fuzz.test +++ b/test/fuzz.test @@ -372,7 +372,7 @@ integrity_check fuzz-7.5.integrity #---------------------------------------------------------------- # Many CREATE and DROP TABLE statements: # -set E [list table duplicate {no such col} {ambiguous column name} {use DROP}] +set E [list table view duplicate {no such col} {ambiguous column name} {use DROP}] do_fuzzy_test fuzz-8.1 -template {[CreateOrDropTableOrView]} -errorlist $E close $::log diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index f5a7b092e..2ea0898ef 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -156,6 +156,14 @@ static struct GlobalVars { char zTestName[100]; /* Name of current test */ } g; +/* +** Include the external vt02.c module, if requested by compile-time +** options. +*/ +#ifdef VT02_SOURCES +# include "vt02.c" +#endif + /* ** Print an error message and quit. */ @@ -1052,6 +1060,10 @@ int runCombinedDbSqlInput( ** deserialize to do this because deserialize depends on ATTACH */ sqlite3_set_authorizer(cx.db, block_troublesome_sql, 0); +#ifdef VT02_SOURCES + sqlite3_vt02_init(cx.db, 0, 0); +#endif + /* Consistent PRNG seed */ #ifdef SQLITE_TESTCTRL_PRNG_SEED sqlite3_table_column_metadata(cx.db, 0, "x", 0, 0, 0, 0, 0, 0); diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db index 966b73730c2c0c73069714e73127ecf2b056a306..de7799f945f521444c9788aa2f933692ca84a6ba 100644 GIT binary patch delta 98311 zcmeFa2Y6J~_BTBHlsRY4%p?<%ke*Cv>0x>jY665Ngiu8h$fQxEBoGu7g|T2mNd#FI z5U`tc8x8%-=-(CRr5j(& z(%zu~NPC5fkS2u!NG%~RQgg_SR1LY1Dyu?;_>w{eCX@7YC?DzfpcBZV#m(-5SE6rJFS#klqxEMS5K*2I;zx z1L+l^Xrvd1qL5w?T4l%Axgi_U#*h{1*&zdIO~``utdNFuYDh&oA*3K36Oxe*2}wwO zA1xWH&BFR1&PTF;9+GG0B6)lclAU!(?hGQ?ScT;3nMl^mKyu-kNR~}Uvh;K$jnj}UoQh=L zWF&L2psW@v$*QrCtZF=xGq9L!$`~XQu#{{Z){vE93E8S4rAP-4MN%>tNdPOy{8%{V zDM8}KvauXLk~ANZL=O@x7S8komd*5Ute5GVJS0c3W~MK&WTwxsVy1)rki3mmGriCU z$zxbK(|uSt)18?}wmXsBg0(ZAVF%ho=Yri-y0^3u?#i)?K+rnq!T8f}tmrO`abrVf&{i;`@L>5>3-mRpl) z=)Tw<{QM#8Qf9+)V^yBU7)?*Fj~VfZ+-!=XcLuZh!zNui+OC+ciZcodj1lGI%BPe& zM^2hB-Z{vb9r-gSJ13{CVuaJ>95lFYxwFJG(3n&{reZ|-bmx+$CG!?K>F%FWd&Rm7 z-2;uXin4KMoLTN%7@W7La1I^1KQ7&p7p|_!okL}ZZAr0#;v(O`Q(L0+nhY1^?TDKs z`wPAF;=sN+_PU&$fyQL?XZRGSn_N5M=COL(vLmi1Z9NE^Vq?LM%(_%Uz?{Ax)1_ zOh@W5c8a+wC5bxPZ5rKpGs~fe;*pvmM(3pX0pW2qRyvSZd;P_6{Chm z=A^AG(@g6^EWnr7v#S`JNv(5PfE*9oqUq?kWf1W#x8dk7q ze*OY>xh%~dL5IG!B~a;AjPsWpm`i3=lsT8h^DP@#BjZ;tV=K(8np)msTTb-mj%p@R z`%0F(F-f^X)Luh--(jV^y_s!e{G$*{msl-rb6DK8?nCw{-!qSuN#uN$?GuIT_^}l% zol!?`#YIETXLIR=*O`z1`i*S`qYK_-H;6*@6xzbhr_!@?ALVGNfYB^R@_KUBum@=8 zIyQyP;qCumrHsZWE89+;jA{JsMz)2CTIQ1DBK9F2-J3g?S6;_fXe^FDw~5`(tP5#b ztYX@^=ywY@T$#W3{2u(!&FEqjju6w?)5z-nZBzf7G3hHdYo_M@CQR4;Au-9aE&WSZ3tifVl^&-1fV&_s>U2X-H z{#%~F57y;6CAw*qR8H?bma9|L%j_GroF+c)>_vNCbEeYHtx-wTU&&N?)hp~)nXMq_ z?!0))_|lfghkk1-VH``gMq=lM_a=2b$<7c;MwVN5;N>gCcqL$mFIQnvgHHO>2WZz0` z6<3b3UuCwMCVrP$M6-9YZT!I3EK6n~Y8=*k7}@^CTy$YppA=ekFN@+^zGF}6YLa4l zu7{{A7W*f`PK&S4HS={$YLHa$Ak!rj6Dy^1ry{v^HCZv;J33sJ&$CGzW%Xi|xQu2+ z<)!mp4(X6=y?kX> zI$Y9ReuYyyPiAW=lqb!ls-@8tbXvYt!_)doA4}|N+MJlVo>R88KxWs_mtPyBI37PJ zvvpKaBVEC-c1h>RTJt35urr*&jdO7P^!G`x$gG9-mS;QZnjC2?UsNnzC$np5@9eCh zw6G>CntxoKmCUGln6!+qsLjd<9|h}a+c2q~|5BeNF^VdYEYw^o?V(5JW)0#E!=&|$ zZQwV~&#GsXKT^t}%7s}e{N8eDs>H72EsL@iGHRV5Rr1frOKwIhmSx3JN<-EUVnc7_ zRZ}FDam#q=2h`2?jzJObpD68<*!BFe@lue{j#XJb__^JRB(|BD6n7=RX0EhMW;fHsm)LN=;I5nv z5;fGY7+SnaYM~1jOKYSp0Xi0new`m^kgzqjlJm!?G;$S4J?ZF68L@Qqho~OZaFwp` zswN3rdKHRVtL(j`fYnxxDZ;=~bEC!`HP+IWiU8Exl&G*W+@zywu`IY!#13$q>m* zJHe5g+!Wd_jil!7l15K&mrBH2O)_hx(6l@kHLQ}YeCPwxlQP>yiynwe;8D9J&dB*( z!6<4r61R|RUqS{Sv`fliZI4Rh&8!!%ctYC6)Vt8Y1HAv*Y%k+QPf0IGY&W%ptg+mA zEUSVMvL5Cc&q*IL*2a|=Bv3G?U2i1^sj`2xkKfQC&6nAOwD(@=bShmV=kYbKNEgWL zA=@6EFgNC#y02v-h@S{|ism+3=j#QV}i>&FL@DB%O?@%4`l zjOU+xB;99bz4?t_NOiJ$Kf1SvCdN5tQ~MM-m3LU=d`6Yl!YcZzpCg)9-mco|?$K%{ zz3uK1yYb$rIqQeMUcit1Q;K7(om$__?n9-M9F2F(aeyekjNZ;mzm@*Z*k1l_fdh0h z{%%#HA%`VwvSZQZbnsIti(Wh+P2m+kNV^$(g1_r_^pdz`X4G3@w)A)YhTm~J<99Ln zOo=^7oy>6)-=fI1GJA?Ex{Q(isL=nCueQ6nf5G5Xy7*uk>@h@GC6m~_M(}mCm7sSDuX~? z;mQcn{Ht`RE!w}nW4%t5!xE zd2Idy{>z23%Gd{V?Ad~I1nCUn53ZI^lOc;_zM6oAxrMC^?F$g?4Su&7{qD$`hl$yvCfZm^L~5ITv2aOjNl-9!NP! zav`s~OTJNJpSSIhH(1qN#ne1${jPc^oz_q3N&C6%Qolm~j?sW8Wz6e?@`p0}Cs!U7 zOZhLZwBuX*+U0e5TqX44ez^}Xd{QpcSsrhAL0%`bqir4X?W&rum~O~EUM)ZPs+=LI z-=f~{x$=g*$-)Y0+aY-qJ&qdAnGt=kw!~Eb%5| zyrm~{dB<5<<0PDmtW4xe)VZDYr`b2q-DgXq zch|FCLU8TRA-JM5-J;T{Hm5RGRa}bcCE7J2FHuP#u|kytdf_fX>b<}c(<#y@i_ zNfL9=R;v(|w$N~!zKh@Y4ek1a`%k6U4S0j!5_{CqeSXHuDx!grH_yi0jE|=SnN8WmT@Penv z<#yNN#wfnpRWLV+R_2%vx; z1y1=X_N*#KFWqjxr`YQ%D)xH3#jaws8_C0aMmVzwnSrj%Kv%&4^88(S{;oVf@_b!+ zzOFnU^1LF?Kr7y^OfND$B6Afo@i^Ry2bu1!w=w2$o*Q}K=;1EnE1ZXc8SeT>rn^3z z=?$<)-Cm!o29tssT}5uMw;s#tqQp<-Y}y&6I0lut!!zeCa=QcdftsR{0Cq>9 zB;fVb20Q_Gpf2+9)D{H-B}HC5sSnh-eApyjgGTAfuvPW=aTU92!tJ`yT9L@XkE@>9D@?(Q~L3OcWr@Kmu>QO^@QVcvO#vkFyz;8t;8lD6c3lw8t z;%_miRUVfge`2$uu*mSbeBr;b+W1}Mh$-pH!bZoDD>D5q&+$C*x*#$y+7xx4`0=5T z5DmqTAVqICU5eVp9&)d$#V@sZR)ZR90xq`?q+Nr*tMNCsggc1upvUDd0aX?U@e!1Y zJg*0&>nXtpRb$QFuDWoyT=g{|S5RIZ<|sn0^{yIFAS!oNWBICm7zHR5mAXnwRv|&> zj4;jCVH!Zl0n~tvA~rX67!HT-^8+)2P18jqSmPQoM+G#Arag zT{%Vh=UE2 zCXNdH2s#OKfbf2=Lk|sFs4BU~SCo3q!E4D0z#qdhuM2h4=OabGGC zwPigKsR`l-^17rZPef|+KxzsfOU8`qlH!@gK7Tm`T^wxg;u3F7Q8lvb>t|x^FcE_5 z3HImmKo)V=qf8yREm))*EE~Bevut`xS9-6Cu;u?DS4JHtxH6{tRJI)9%Ksfx4)eW$ z*Z5<$e4Hy6VG93>D+>l%>;Wr3neQCu%74O?)5FW-J&7wv7R>8|a9(WC)idowPT|cb zxce#0IsBG8@L%y}kX6@fAlRO#Flg+V6a0Bqw>;tY{zv}YB_Keq{S9aPpYdlm##Rm2r$pmqBiP6Si%h7^;e@3UpDJiRjKpdu5 zNC7xtP?@_Pt=ECF>oDY6kUL1qU4wlc7A`^cxVA+I9TZR^C;&nwE%^mUAt;hgx151?D$3#r-Zmrb{Fb84tqmhyKMJz{iB^mmVpq*P&H!*`Aq&%g zA`4p)bC0#I#@d$PB7najK#F3X`uLfUeZ;j0MBv5`wuawZLK72XW9f-&VWVn(T~-nd zkaw}TOn6+FD3{;o^ZSeaK5Dtf9=i%xP;3EQzr8-M-y852d5bZ1pp5QZLkUt@OXv3DE~^hLW%nC?Iw#?Zt{F zjUL6cy}sM}5X%RQRGwmb(!+Uf#q{-=-T4_k#GOR^;5$M>K8pFq;fyh6`0Hni>xiee zE&%z)PmP26#wS5a5gP3US%#vXNKpYF1Com+b7he7lP1`5DrH`xyvpAkt(>bfA5EO7 z%vaJk_`Q73M5RG95_a-Z8H1KuVL2;v%r}(#*D0W%OFQFSdE%kZ%nGP;ozjPAuT=oIDWuM;l|sxYZKHv_3cdX#Or`0TtY-i~@X%`7X~dCdciVT<@h)5x?+u>@q!1GSD9XP@p%Ik(`Dij@EL8&9IG-IRxjGS+{mWs4=7&#&0fWj)M8XTkSkA$ zIR(naVol_!&nTzq>QKdW={P#1Yi_=|Lvb@UgzxWEToN0w@fD>`Emcg{6o((vk@qpE zyIxVwlvybS%VS3ISurvD7*Bdj`B_zmE2bNL#|!3C>t2UWN6*r&R9PNVu)Xb|BI)Hf zKLTK@QVO1aF&P}vuWFs}Zjz_>b= z%8n+!u)9O`%Iq|LbY@C3qxR8B`Mh0K<1A{qV!C#2q$5VOdWk;1bjOER-W?ZpL$t?NBn$>KE}wTAEVa{)zv0u*Tt9v+j;!?KSq2rx+GlV48C9-6EAJxO zTA-?Q)o`_mo!&M=1?uc9+BMhip?k(hIq1&k6Jx1ke3ZK%_>a8Gxu$%7%?A zcQ*O5gU;%FCs2I!ab;AVJ(%b2?{w$nFAOfvUO~;r?CHRUZrf)&gKk%o6KLp@qSc)TK#d9LvlMQxKfQH%Q2gkf)}dkI2LEERD0@!vzl76oD(Kd z!+M;y2Un^(ja_61Hq5GOEU^-YXA+_#Zg=Po{X@$aSz&74#lUdqqL+=)6* z)Rr|bYh`tK604n*wo1*QiJ#e%$n|klyy136W*4ojzCg`$V{lF9)<&8UT^4jJmIM<& zXviP*Z7{%oS@8p~Fx{QY8B;O2e9{zjYsv)d$vk&b9GN_U*sJ#WihLk4FDF&AFRMbBPqt5dSF@~f%wlc-cF z*GJC7wm7@U$uYWM(X?}mopOz^0-i^x^H`Qe?&&d}XwyUv$1_m9* zR0s2&J||7QQcH-=nJ}$IBRznHA+(Nz5>e~ZZ_8mG7s=Z~Z=R38S6wrRwVs4pMph7`4QIF@#bVley<-qBy+mDNQ>Q7W%~9bF^CQh-_zi6v)NP7#I`S##mb+?cRx{gfP=A%! zlD19id5St6k1y;RXS3StRM}$+FWsi@k<=zM1|0I?=xg}3x2cUXTh5g`)n8?4g@@ig z0E{A_a~3{jhq_Y+wD^$MkwJjZ_2eb3>T|Mme$mE@fU`+@K>bXXRvO^!YEPc>pn6zV zE&$ww8Xr;9c%MgArmJTvrh5Q#KhB9aE!Lg<@+Vb`tgmX9&w$vvnnJfLnb%dBljvwO z&g{o?h z0m^zs1t{whAc5kS^ObL?m&xo>>YNmxM61rvnZcJIP~X&)8H(xMlV(pW@}4P1Gw<_} z`nIHo(Aec%IgF`bSJ1I<;%?!0e4);i*_Hg*_i-_d#~)L_lGs&%oyGYXFZo7&P6DR* z7>fr)>N_=4S7$1wJ0^t(ynDI%9Hw7$|B|y5(Yw9F=mXiDs$Pah z*U`Z@ZFBho#Y`G&;f)REMT}Nmr^j-RWZ;M=ZcRRu z&&oI7F0&h{(+nBX&S z$5xsj(%5aZtkrA%{NA(7Q)T&%8b4)v z&2c=r$_z-`cD`+XGJt4mow-0$f?{_7+|`$wI~1Uwx2hUnyujQntG8m=7RwZN5O=8eHC|C;+bMNJ5+|TZz&RdcI6J2QrBJE!4tTPYb zLspyn=xUu}S_jG!N6OaA%>~M%tyQ(udAa#~rL8qsLzP#US1PR!#8vZ-E6n{R0r6$K z__4JqR+$dIm~>2tM)y(YzNC>60sr1+jE&t-6Te6t$;WOmKP1CbW7nO@FY(u|Hy6lk zciXI#)m;L9>t^$0GaOD@&P=Xiyz-iq9ZXyr{sjzhsCUw+NQFD^G*1y2V0TUG2*QnF z1Yf(u3&s$?WgcIWG0~-S=pdWvbd`R@&Y$L;8LS$8c%`U+|21ZMe37@R-(=tZ;3w zhHHD8GMh4PKVF-YANa|f8ZP<_W!{^+3l*Lww|!y$P*LY9rUM4Y(!|Q*>h&Z(&;O>g zFZ{N%FP_@jaPi`Qyqo?*dUQsoGX zA_mfd&+9y9Q2M_yFO8ZSo|iXh*LI7amtiVdJkb9F?K^7zHZ6ssU)De4zk0Q3m7UF_OSGes`Wk9~hhhe3zi{t0+Ov!w?4zYJ z`eZ25cPZv94Zc>T+ESo;c}$g-CR64p?R|ET%5F^Y@rqjQK3UWsHhCPPvYS))c^rLEczq-9kgW{ z`;3BVX?Ja$k+wuk9hT^GYPr*b1GP!}NM>J9%MQy_xVLB;uUf9bso_iN?9itI=9xa9 zE<8=Uh?3`OP1kMB$e?#`N(0JU(h_*bIPGGP*>S6Dz13*yZ)YbS6$X+F` znLx>6KYo4X`3;Kcx(K5x`k%k*6ZVscQq}LJ;8S+L?G%Gxrng6OU!X^T!*pYF3Ns6g z;MIVLV#5{{i>%1@1v=Y^?W+c0XZF%NSLUS%jI#>7PNOY0gH}5&I{BQLJrnT)y8BOY zjr@PRZECVbJIek~Ca|nOA}~Q$K79V1%re7dHqiKU5+hb%jg9@yrXN;5Y7FfiVh7h> zmZ-t|wd}s6l!D(B`p=X>olnPSQp@N4;%R%fJ@fzD-u*A5yFSa`sLfP|!`4 zJ7IRjX#y2Pg%jtebBg#S;PXdvioL~=9KXjG$?+6*<-ocd$#HjOz=ZECuZS7(+cS?Q zCMCsEb8?bFtADjw=(7zuJ!r}P#2&h{*Zif67rAHAS63t@(&~qzVwB&lqH~Qof!^3{ zjV87;yGn76nd~GzDLaD(zT&V`gIjXYz>#bKwSSbP&=n4=gAR_0R!aV>b&r^y;H6_? zhdGYEcbT@~MH zrHbj(CffD0=H<8Eqn#zG|3>}43O#=U{d{$tPXB&X+sxZrwX=0~nPR$ii6~n}4fA_O zV~=R}=n`8$tO;tFum*rCAJX0?*R8O{x2MFJY49W36Y4LBdLWVe5$$=7YQ35S1;v_Gr4JLyK1>?Pk|GyuCvkBAX={)#()T zi}pFJF?l_N5`PL!c+(ceYhKm1NU~({Qe{8YP3>8FDWzBCMe$4C&@PlU$q2&g=%ZaZ zYCJXP=_-|^+IR6+4rqnwg_Sx#)Y|yN2er}o%SL;D)P~a5A$klydO%wu(S)6j4Rr4Z z+N6zseK6fTM28dX0ZnN;tVxO_MbS{j98bxT{wRO-3#|ZEL{pV+hEeLM_A&l)(4iqZ zY=K{EX!ty+Ozxu=o8hH(KWRIZlxOl+(9oZ?`AU!5hA!v#{H#rs%o1b(xLfb}Nn3H; zT9M7~?W4abWPUhxZ=0Q0NCjVME%fLcnuAK;g8pE3HpX1|j;)b9a`bb<1>@<>V_F_n z8D=}}$ds`>a`rTVpS#!A<#aGCy88%~9z_4VR;67Bs?yPU60)N$2JrpDp= zI-20p8)^L285ZJ$7`R&)lf{z&aEeP5eX$L%lm)KTIloJM>5sYyI7TmM*Q>Bswx z#i;z3y_G88)tvNRnogq842l`1=bqFZK5LxbKRf|WerS+h443Oqv~g70Ltnyw@aqo6p8RAwc^`_8h{dO@@m zWq^)5$?=YGbi|O_#_3`=WYM9g?D4`_bqA|ke{4t`Ip2x8n%HRtsEijF$?Mz&1stJ zoLoM|Su?1n?@$UQWT%>;LWguqEvbnqO0h5CqINwl#j+_2isyW1Rt@bs+n&bCXxDxR zX8*_7ei)vB!2B9JjTY^X*6EtDDHggj3JUNC7uX-7m^5nwopZK5PU;(=iCLCdx@e*O z9vXTP-1j_DmY##c+$z|`g}{7u3h)-Ycg4#NwU>6egK%;U}*{Jbluu|c77qOe=z8jlyeC$j} zoV?ySBC+iO`C3_9*KodK+EOb{jZ$GI?2QYhgeWjlF1enyS44+Jbir7~PjNk6jZDeo zr+1#UkCLkEsq%Jv31S{pJ!dbKN&=Mnwmn6v_0yi`>?4_*TzA-$@YMOdy*DeOMdB|% z&3fM6%UaUy1{ZYGRnOa((S-->!=#!b8vTO3L@KGKpAXpGa!GwXx!JrEH;2WN^JS))h2bvfq=TJy zU$$8HN#%&eF=@i-MvgFr)pp-bU@(BaK}1JzJM+o$l0BWiXirJ8)(ja^B#a4OR}Q^{ zI$-9CVM@x024qn?o|Ln?@EcmqkfI^(Na^yVCQrBJ0_ph zcGhIi%PGsIoX4#hMqZt>UPRr&U``$1|8CFn|C^rCFIMMtbK*+H^f-8x*wW>%+T$$P zx&ovN6>8;E_JcI|HG3PyylzjR(XZRfXx-~}FD-h*o<$kY*!#x;^XvB2hoN$~1L8~x z6cy8r+@4FfJY(-gtVedTQSb75>+6GFKsygVV;?QfyPUR4y;=nXz;t6_r0K;E08!HZ zfzqG9Jyq``v4Xbg`ellGA?y=f^wwP>A#9}J07w8g&>xj~_`6@l#WJ1{)W49Jmk#+d z0eg8?wexw^>7AGEXFn=^8+mjrvSz;-#UtWrm=;dPbmD zyfphdy^hy!)Nf!af?G+Y;`fbg{iX^lzx@V%mc+{V{58lCZO@a~FzUD`JDXNtrEA=~ zRqxOM5!v-+&LZmYCy(cQ?$k%fteltqGiMCrD)AzEJD-40%eIcCO-Bk6E zX3&EN6Z`Sxz4}LzIvhPdjb{!@|0Jw%j1RNaakO{4C4+pkfTU<%mS)hp_rTvvGr^3T zt!XxbMy-mEeO7-+V&kdh12gpMFX+&#SJ0uJ(f0rs0M5O&(&D4RhxJU}uTwv0R=$T9 zJGyg3x##SV!he24mt=J;8lT7yovCF&2D#c|@L!+O;mv*Yv$UXSvWdY;GfQg{O~KSs zbzjOZzWE%jhS9-%O~jv)CR69gv|;q?_33^2*&pf%pfiPHI#NqG8h%zI^V*b5edpki z3X{QwoV}_4D3NQO_5wlgX%<`rDmVnxXt^@&Tz>x7`sFg4PV60X3T+*nK9`_GjHCA3 zEfxIJ@APB@prV*-(-3p!Cmk_o&ZL-+%)@q%v^*wTXM{CoxX1YdxOJr`@V<)WkY*lk zl3Pr}G3Fxejz9>Av=@$3fX>(0ELPbvbLWf@2J+qjLg#8kkEY=>i`-F`MBct4F%x1< zl4UIYd|sl9Kb&T%WMogWd@qJRn~ud;>iF5omg^vqCHPGx974Rm6aj>iEr7_Dq%|ZprR23Z}kjrf{fmdE7-sod&eSwmiTHJ(+{ZMJv)%$R+)dYfmxH{DX z;O>E=Z&7U!sxW^^um=7&0x#^TtA{5o{w@MLYrOXmBo9j?slo04{^|a1@d2{-h{!d>Iofw1WL;`K39IRj0^6USCAU@fC$7 zmJ=up_!8}mlWsFz*@UA(h&Yyj>k}mv|yYiUVB&8GKA5 zIpLw;m=u2r2?1J%5_hB;PjR;xSKRd$*2`HwPG~DU{t|ez#+CTIqW6*VP(X);yzzDR z;k2dJ-UBkqrh5Als=Zc^r`C2vcuRjSHj45Vr#obHnzp>|OrZEVxa>LS*wZX|UG1Jn zGwZ>y8s^xWX?Rt3GOCI3*>Y*Zt@c1>7!CpK&T)KHFv9B3!_0}GctINTd6wT1!9SIF zAsH0`zu}@QKhGMW1e=13sQr@cG^$+I(}qBJ$JZJ{(YSHL%7&lTg-G#0%nm~)7B$UV zP?zoV0RSU#8rjtGbbPWT&4J*d_2bS*Ew6IzF=;MQ#ahD`Wy&j*16qE~d zsHzXF5@_aya5Iaj#hEq@_&duT;lCHt#QkOPFFd}3=F4%Aad zv?M&DWi;`Dv;oH()I_kfiNB=3dAy-KscnHJU1c_yTPz19bqN|+@tYYt@6TuK{P2u% z4Co)>QLg-5?Uz`RRTVau-%i|eG_(r(!<0Nacy|h-oa{*H5gya6;W4e|q2`R!e>0{F z!($53UP>txZNfl}S}y8dqZ=&cni_?s3cJ^6S9pyO{Y<))*adQ&Se}D2bv$pz8EBH$ z@Uk8mI60~y_?|dS)Kf<}_X-T~FOUnb{A+Dv&-s$g=bCmU*QXt0OGszyDfmEIcr!i${fQ-3W*fR^@{pxWV=+AXQwt1^%_vLU@u=l!c;;+k=B}q* z&a{Bo-K{W}Vq=TU8)(}HW+$Q3;R+ox9sR@%nlVX0EBx`CDT*$c1|vX?X)t)KTfd?> z1T^BcO_;*~y`>mY)S=szJOI+c1d3es0eFidnduiHA&THGDm+Jui`{UOM63&#G>T~3 zby3N*_3L;`p#YV8JtaVx3#%KfV_tA~*y5n4gVPRdAJElFo9?Lz^}w=??OwJgq|%Mz zcn#FzbwJmB^?pbN@EeC;D{6wr04#g8Vbr>Cy$W00gsm^!OxV2UqQ}m&rNx3tLqCk0 zCBI>vERGi2bhml89>yzJ=le(KCbv z2kuC4mW2`m+!KC+eza49|KMmR?h)ZM?Aa)wtl|e8Jbb~xFDA}Mg9o? zL2W?dy5Z0m<~XJACH54&a7uQy;F60LH?3%>3!^!KQ~eM50Fg$xUc` zAc6p@`a2Y*4_6>S-QtnS`d`fOe`uouH(E#}Kq5B-dKuP9FAFhl6F2tE3u{O*~N7i7++75z-ZrW(CaqcLJ^- z0@|NO{#%|e97OJm=}8aO&aPY5w~oJ!zIddE16Lezfots%FY)ht z7zSgv(y_u==QaS5vn14@)q|_^5cl|GEb-FD1@oHfW-qO4TvoTJrp`H{d}P_waZ|dG#ehiJQmE3! z9P1D6vs<7z#TFZA%%PS~jfA{Wy{0-RkEs~ttZ!QC3ocvUy38{Q&PO1lU8y?{)r_6454~3to{QHVNI(*c;<0aAVCT)K%Hw4LkV0;TVZ(XmnW!Fdbv zv+5T(>zV*0AosNnC(X)ZQSrg*#Y>vIGALik-w6P4Y<5|mz; z%y7)k$zFv-pbPAYmZOtNz+9wzpz*X)U+b9wsgT)G5I3&V^7-gWsD=G@2F{k zKK;t;^XHErk>$jBRW=@u4g!L{9Jo@cSC3raI0ObmHXTHOdg-9)AVi3l^z{v)6xh0I zK`;Zzwx);9$L1QlnSS^6OG5~IyGz(~N~ko~7%uL^ceYqU2%LLMPf;9j2WE|qr`Waj zcnO{!S!?b6gphwbK6lZfJ8el+akDj(9?LaukxJlWkZ0V4D3l;Ksn*9GnZ~(c0rhXR zAWrSa@9AYsMvN>GU85Gqj$4G=3jdJ%dTTQ6f7(_^>lA5WdYJ8<;C=HJHHCG)UA&Rn z`xqPTr|9mf@LSME;2Ya;fac*Ixc3{|g_Qq|&4}}4Im;&xFRKK=zYvQp;%}SiJ+rh7 zW*P&Uoy9DJ`ezxP^!LkP3UeLNd&_lOvZ!^kk!+rnEB>Q~MMh?Vb6EM5)62mlvV!mv zaKjw~S4a4vv@9|b=wJ81|K|KSnB&@a#MIH>CdIg^^&_Bfp1aJ9VChG6Crv&A6K2dE zDQT9eVwo_1JH$jGSfHA3a74ja2`M3 zV_ePb)A^b@lQO8s z3%sD*cuhoTb-twrX~^=p9bBt3jz~iH*9P?gLYf39bCv!i-#^aSqA@35JjHmHsSnmx1RF~mTC@0Y|y(1cj8&5b!tjBb8etro!O(Ud>Tu=D=2 zj8_;Co6XB|Ch@8fMhoMg1dUV)QCyo>ee08HSSok$QzT9}wvUkIZP^MzKt7{66UXu7D z|HpYo2@}Ckr6;(u(g4)_@7WadmH`;~Y6G_BCjoOqE`XfD)y94ruqVn!4Ix9N=^^88 zzU~U6$;_Un()EbDShc~ha#yo~n@w-Tix%#FL+MC+j6n^(v+Z;y6p6`VTbs z9JyB*eR<9%)IWuK0ePD;?td4LaCvV4J)mM)@XU zZHmmd_yxP{3nbp~u7QBhZzG;Aa-{>kDD#lv;YA-BFUSB>I$s9LKJl;t8{@lN`9i$- z9=-09j`FXM8qfk9Cq$yx`7buBDgvoCwB`WV?y!<<93C^OV#1_yxF6B#6l)w} z=~@v>_fuZ}Sk5C7-;-=@Rw2VwI;~f#pRdUb_2ch3t$hT%a@!bND}SJmb%H=vR#w^u z^RjH~6PkPk0SLD|l9$9YT-N6d74cS|$fXv=Hhtq;vHfVlK>Ujf$C_CvTQO}lcjsHh zs1Q(9`X_x=ZJohq53=5dK&sSo7}3z1KgtR4#pA6lFcgoqj-Z`utN=TnYi*?dO;*1k zudgZQnsi`9M_6}j>{}YT)Vi0RA8PgU=xXaRVD@KO*HG&@)^(!nzo~OPjKAyFSo0&5 zM8BH-5WjGu^->8yj52oyU=35O7i%Ks>%KsD2g4oDJl?sx!}oPyH>4lQeMxjWAHBr- zAUodX*9_2%i6gD$1 zZlFIkTxnJK#~~~BXd&dsvWpafDV3x^#hH0dFCeTY0M%U*GVCdV)|PINzz>5DB=4zTl-698BWBql^Tv(;mNVp zdX_B7HbjfJ1^9s5tk2*tJMGH1Ld|n0cA_LlQSb*VtRXwBr^{wJx?;q!Wz<+~n@Rm& zw`x?e-+CWU-(~#}Q{bS+zgg$dj!Xbbz8GQcL5>$4siK&L7FeAbboKYvM2h~*x|mn) zw%&&q5F!0F>!9p+z)FV%!uWD>P zsQC;>0<0-kGymeSwI>FhK|$SCN?Q|c`*`zs8)D*)|EF~~I_%^@o9$y*(=u$!M19*t zeVG(=*ly!@erLT%SBrpeE)=^c$eWq1NlKPY$tKp%#PM~~&o(tQfUZ_-*XucV214*D z?+|8@QtD7_eZXMlK9^1l;U9mvN_YT8a;e|D@ zovnIYxF}HLNSG0~K93;W9V2Wu^4+EMQk#}6tIMIiqZzItr#Wol87AL=9tGfLiP7}l z=NVQ?ot2xM(sgO*!a+0y=ipK^@4ULfxPwq<2wU*%ui|6q?I-MClIJe?u&w^y3J-Ua zoK2aF^WtN0uf%<&%b(Kg%Fm%!rYHI+>Wswctc>QKkvK`%GZM@&ofMaNplr{9%JgTJ zM2=rT4jp~+RQlL!OQy=R9B}8&x2BM@9ycDe0w_%%0C!!TpsB}8Y&H1jTDq&AM)hYT zPN9E&Zyic^%jq$|PJoUU_cyY`&fQ_p!))lw03P<`I}rvVIXi*&*4c*0u+s< zctGMvplb#umT}jVL_br*{*iR7fn`(W3swY5{}BI*$IDO0XGn4`ZQEppW0`KNmJuo; zX147J^}o$(m(dmX_dBinV!tB4W;O@P#0tzw%*8JbJ|iY>LA7RRQNQUOYh~`(l)+p zO`@UWY_H=umOWcngb=@A?>Sg{?X8h}lPN9Wt3nK}dohEp&rIwQ=Yj`vio&YAp<1vWSV z!jZAi_BMa7!S;r(mMNwy;zZqx>ELa)L_YjHTRVdVeH&u&(t?D%Rs8;yHk*YFhB8%Wb0+1>qIeQOj$I;Yriz<+V1a<+L?=2r(AchS~81e%jTx^Q`bo zHEo(pjknp#xb+6x*OEF89i70HO}4p+y1*CSX1f-)@wPi{4=UGSbCqV?# zqoFK9`KcTFgvKbQJI8fDjN&WqvAxSIldnh(jfP@-P~^c*Ta`Ky<)_fzHroW+1J6F5_>_KdMpP86uT(#SdR$zxAr7a&iT$g=ezel&-Z`+=w6w<_u8wMcfIBJ z1^}t>Omn&dREiSa2?loJwWwTC7*~HX>PMY40;(At4Fo=D1lW+A%!LD-TyK(w5-o~Q zST$u`MF7UZTjsPxa>=?$nduy|426V8G1XMcx9EG_gvv06L80@g2y^5GU9@TBcEWDu z%~a@g&QojSY0K)Q96GJm#?Y#8t(vYn_0i1zAs<*iroJ2n3cH9V+T+_0>eGo~eQQt& z#Z>oA)NO3i4^iv5U`OSrzy=%@-B+V_X<>|f;+Lpg_-(4c7BxwdU}d~RsWd~K3Pqu` zUacRZ=9bC3rnc0gGhMw8xCHVntC>NJ&`l-GsE^V=tmYQKRh&PdG)PCjBrI4zH82*o z8@V?)g2-xM)t-O|7)G~%L`$Q}8doP}#H z%X=@XkHGrb^bMjgujbouRLZIU)yhqbH`3P2wp`vb?bx!e`n6(|gnc$33`eNSXHm=b zE}@c-mH#mpe1VioWdA?sf=>@8c>j|w_}~QZf7AtE?_9M1|DX%LurSYm*99NFRjKc$ zD+^mB$nb=yd5m1E^e5@{L9y;i@u;|8+$%mH-Y?dORU*Wes@yP5aBf(b6cvQ(m81^B zv7q{T5a2=TYr!mpKL)Y0sjmdn5q=d+Mfh0|0!;OVAavN&?*_4VtKSSJA$%bikFX&K z#ZvW?!A=Md20I{pJP7B4>W6}H7)t8h!C2hfAB;hGZ?GN0Z9x~pjX{7(sn>BIgX&d5 zC&Gn6co|gtgEoYPK?}k$LD1*wAwd~oUJ(8U)qR3kKh!BfY<%i=L7alrrXXgp+7QG9 zR!adscYX`3K=@5yd01Gb>dU}V+*09^HPU@^iAf!h$i8vs~<>TCcKc-5PMGK8-M z<|BM5Fb`p404=0?CNKx#(ZEcEj|HY9+#M)ExHC|U@ZLZX!s-B2c~qMMg$OqUrXmak zrXXAy;0t->ioittSsIvta8Y19!t%has#^4tBZ$RHBL|VVbR#$eVN(#Bn)FT3kMQ## z5|=Iqk*xG#a2mq1!N~|;4i3WL~Rr3t~I2!{lRAiO0w7-9e5AcVbxw;;?64nUX|?2j-$2qibELogR%n_w@5 z=3q~R#$XRrVRir+tNsI5rD2tc|EaH1%>%1dXpfz%1%%sIhaub+L~m*~1y>?m8$_RK zf-go}gA5LN`yy_y+8bg#xAT!iq}Ai7u6E4UD$#}GuHYC7`qu4xyXUGLGq zqTx^y_q6Uuuw*<66>oTOilMPt`o|*8_QKIa3ePpNdce8{#PrhoS5;I5xZ?c4JdV>FWexS1k30pS_nwI)y zf;t0oI8b^<2W?xUAH+A)V0BCYSPT!hZl)IYc#(dzAo01dnl5eD=l@BFG?99((|4ks zTlBGX-#UE>W!>mvP;8qtAt?mQE3%vrXp72%b7q+84ra=+m<8&vUO$66bhlX8ZM`h> zxw?aD4Vk7|8jO$tagh^b;EEK8OjjZk`I#t^-dPXL+L2rJ#z{*Obj>F&Zc#POS6=R0 z3K=k$0;i>7Wzw} zyYHJlXO<60Kds^;YE0F~Q^z~?2co!G$6Zmf1YkQbgSb;aA-Y)v1VNCSKThDXS4Ff% zaifJ;eNGC5@i5EG$u3DsrTH85W17!?ieQEsH|P`ptq3LpLp3ZUhT+l|O5CU~fMS-n zQb=(6+|8%dqT3cORgebf-&VnGbmO{P9PEc&OO%R!=oJ|oQ4wO>tJbkKYxHRXzK6W; zKkBMlg%h%#E)~OYw9j-)BjvZ%MS={pRNmZY4yDiTWck0F?Se@W-2J<5oA58X?f+w) zw)~GivMRk1a!U7q+#5!vH}vRMLF^hPcI75_9YpPeJpw=td3o8*6Vk{hWV8Ghb7Jko ze!aTct?ip(!dqc)LQ2-C-nuAI$gY>`1wp%cQ(OR;B=>Sf=v+3ZQ2(hQZN!&%(WzmE z3+$5_`gRI)1?=l}`ejsmtz9_1rnMR*g-JklYr~6FsmXpSpe|V?3)WRM?UX!;+>-Sf zC=5HXOp_--$iJtTwV2L+0Y@5Df)!^pXxWnYl(7rTkFwq+(CTX{ z6L2q?w@~OL(1qEF?YJM-?|HntsbIF)pY@sC1&9j^jtNEDE+umo&0Gle(3ImsZxs}2 zX#T@V(QSR0{9au`i!{C>)t6EP1w=kM=)`f%i!(0>X4-NBo}xy@iVjkJ2hmpcdi`k5 z{fUlnckSI97)7%45Ep>~C(=d6HzD!`t*XW-X_#&8&v7*|7 zYg-nX^bsy7$_wlCF?z1j03ka^8TV(>kahZwb}n#F%K_XSR8K;x3b@IjrgV6KJl`4V zf4vUIO|2^_N#E&gymBwo^A#??P)eQGxk@N^y~{|it#=tjZ!!SSTo82M<{H@g*iK*E z;|fyUA{Ta$FjWm5-{vaQ&78M18GehG07z@aV%KVVw#MZEc2VpS*JpCqOst*o(~*@3 z_rYb|7L#$Q>rJA2U5{wJDV}6q`Ml|Lf?RH(US7l>>b=pG&XXpRZ+D$1+Tm(1`e%~7 z-1Vc7$TTZlT7jG^T|Fds8eq7nWTk5|72NIWBrac0^H#aQ#Noa9=qi_&UPwg`3`l@U z-=d*yth6@D9N)3o{W9eFW5(ohAieH$OXkimU)sG3*FA#T05lSAmf8PJQkZ#ksRqk$ z)wAFSR&LP87@Kt&U|*vMR=~f)FiBjG9qR?W21DymoW3g;h%2n1NHj-Wg2SbPUKSnN zuO1T`8RsFyr*1c#;cc4N;EIyDWz!Z`%r7tLHGIJEk)tN`(A8{8cqRPdxIhtCH0Se? za(~Bk=@JnK&E`$|CK0GTH}24TVddmqqn{$CWm44|oDj3<&}RKAzN^;rQ|CRncF?VN z=~F1MRv+hphBcJLxI!Ah#1*A8D8QvFYx*sGKyQ&1`Qr;#Wuc!{GhD=_l1^qid(ObZ*_xR420G%!DxNE(qX5? z>-EY<%Kkk?*$eKcf*i{^>bW=4tLAWX)YvA$N`G08q9vY)jUD;_c=e~orwwhv6MZ&K z*S5LeTej|B@HpRKYD3$TFsDas&^LsJns+yBApf+X(w>dzRPKwI61!Urf7(G+ShLyn zEW-pr`;aLBPWJ1a`Sc&8^krz~6K4LqeE4s#uh9F|;d|o)C7hG4@c7X0(O{cNrjXJA zV%6Ri7x05i)jBSs>ELToMs}flQagc7Tc)oSw7U?m4%+*iHtQ*fz9Q-5~TRP_LvgVr=UZJ#8)LiG$h$ZSNX3ivdpwmuy@t z(1N?Uq#41)PZw*F(n1Rc=+dqwxn|P2ZLV+mCwo~#jVo8+>oc4+-|OluhIi57c&2)kHSTbE z1?s=T^*UdfpB4P<{gtjR;DhgSL7Rgw#|4$}kl16DYxjS(CP%cc$^XH^o2ja(Si+v( zfc6PdFk(}fs!VL+RxXVqj7vLe_ZA1HtHaI?;d5&|pD*+&)63*}L)7r2$iBT5fl|B& zdSLT=2!i4w;;e1bJtbM(j-?bh`lV2uE%77zL%1F%U>U?g{f9yQXM(al%L2HS@U5^g z)IXWln|5TV6GFu{l?vAB+u3+Gm8Las%q`u-yT={AWPbU~=`-ii@pTv=Tpx{}L<|~t ze`NXu{q(J_Bj2GNw0^yQO!IQoP~jpn-Q&7|{JSl3)lk(oS7&x&8+Op>u5;%#!&NS+ zfbOhrNkv*b=Mn4&*VNUOzN~Q#&|#xSBw8axZPJt8b;NO6B&u>Bp6>0FV%uRl3&lp;AEB-L7SH z{B~Cg-}UuW{92^Vk>Kv?hB_M`MHMqE=9EBXt=t!)2LKeh*)>jbFD;wDFtmEyyx0G$ zarK$&&*!Y>b*NdpZO%N(`_AN|@+5$fA6z0t!8G=4N5M*GCPVtP>TTIZgD=U^l(k-O zW|+)^APl2J<)RykZCs~Gb%-Lj@O2E^CSOpy(7Rhegq#E=_rIe|wJnq>9Xt-9*B@l) z%-STIa`0`Pzq_@~L}-G74a^D9NM)TnOnqWZ(bRT>J}#p597%bX!!3&SJi&TCmd>`* zmXgP!Eskix)-9&S=k?J^{$9IjNaE)(Qd?K@U8?)L%POquB2hkyqB9K^|dYOfYFv1T{Y@Q`s?Lazy-p zP4oZDmhHO;V)BCrp}Kc>XW%uwooH!GJ2vR|g##RA%&4&g=>an&>J zCjAoS;W~}*6yKa70C}2fH|T--QKg6a)f@;PVt6{meAzm-zDHm-gcQor&S%hZ&fT;3 z&J4_goNZi7ZVrv|oYmj2S80Xg^ueq8Q|#Oxn6pD~?1Phfz#;C{0}io)Tb{3CcRa2? z5H8KdIi!N8Fq;lqWdnoV@=1ZMsMiBE;5p>-0>!Yoy@ zY*u}wFQST5`q#xAFXf)ruL8b8<7wCpcHXCVK-!cBOK5ItnC{^n4p-y)BfQvp5kj!J zmw-aRQMYMxr`YgHu4COigNx_iHm@RNlc4DNS9nu^JH2G-lBHcjy6s+$u+tydx&43z zHYPq%Sq$NjsnzHlYhyWXTNSg5ng{>D!J|fxf!a3LdsI>d*mfvWdTn-FGu_oGR`c&m z@^1>l&!Y-!fG^SiaWPsCBaYB%1i;@dfDF*pMdO4lF2cRh9Zo~r zCjt0)I(TWaO*DiTC%d8Cn@M$tBHPw@lD-PxB?jiH!;XdYg9NQF1 zFQ4iQ1-kQPFydB>1X_Q?U-S?|zl#+VM)KXbkvsq#-qUkoF>altSAQI?1S;7-o+A*={IXRAm6b?d_d192$&5l`z8G=B3DRM!9o11e4d@;V-mPKC-tRT zp^P=Wt?#4a;E<}*^lCk%ZgKDFp&WOH9efxoJZ(Iw-@$f&s9&uS%1Itzm>jCW!T#L- z;IjT9?YW)yzygR|L#izx({t!d_aqbh{8J$138(76&_i((%8K9cGQY!(28w(40!Bz< zq1cb*U)3Mss*7=McVB@G{89g%Mh#@1!y!?5*-@OtRt=Nm=;dqrRO!8k<_8wC->>Om zW&IY)`#zl+CU>EaB313F&!^Cg8qoy|6eGHUJlFMIp4@HlbFc6pUg5P>8y*W6&NFqC z0Zs5BQ_K7>v>UC4ya+W=f?f~F9o@eqxoCc&UP~#zMrqiQc7|c1^jBndi6$o-Ze^3) z2KdofOeqESeuT(ztTx^-Qi%BEp0L0oNV?U^YQ*vr4F?42BEGv!8)xVNGMj9;9tEEn zVLL*cXMEb8HRKrjK%)_{z97Q`I9UBK4!TZVdl|Y=e3RZuR*Td=!l7WNcT926{C#p7 zjb4+`M(mnK_w_O?(Dxspoa1qFhmtyd=ZwyvtCtfs+fK!F4e?=CN7%CISZ_mbn$i!= za!Zr`FJgt4;`)IvQfrZV(FYc(GvLUR^yKMhFp#Z}0ovbu#YyR7I7hn<#9AU0hH#0m z7*N88Vw0%$Q0&X3%QNhu+|HO$MkCJSSNcbqsBo$|fv0i{6&1ysX-PlBdivy?+`FcK ztRxBJ;8@{^ssAuRyRA)NDOBb{y98AQ435x9JvGpfB9LyJsuShb38y$g@j0%qyLM&m z>OhPrt;5hd3&VxwRM+1yOZs+0Qs8!mQJE@4T&eCFSOzZpRHX-ay6srQKtmrv_^N)8 z0b}qx5)O57(wQ+TBlDa$tPt4V;f6^Xp@|MeC-)`y17ZU6tTEg!vaiM&q6Oi5c6_~| zuZm93lNZskNrqeC^gix)@q$P<4oTV6cADV};RhzHbpIv*zSN)=g&)Zm4vR+j)9#BR z8~H=5N})}DqOG^sX0x(I?kYr_$s_*Ej^EFdZpd^$!#9Ut*zsC^f8zneqx9WEgB9Rd zyB8P|SxSZB9S)AQ;2{-YnU)yr5t@r zt6>n`b*rJAs@$mVo9hj5n{tDE`=wkm&X+TJefqQGn+*L`;55*{X@+N*XRiCCNN0)+ z7{_61f#t1opHNX*Svv=eoDH4%Ly_g(;cgH0pUdvop-f;fN2pafUKq*%f+pa1+o2X| zxP%nd+YZHHdc5L>mf3VbzF$9NilBB`?oZf;GQ%;kevjb}NdiK~MP=(9&pRs;>|C|x z4S!%I>PMmT3&Zn$3e|88FT+FqcQ8I4{r64MZjl;_?3-xZ0mI{>8t7W%aT1^kdh7v$}0nKq<Fp4nzGt%qy1NtJ5u2gX)?=AH!c#H=M_VHNfQi% zPI&)NzZ^Vi7@^XrLAt1A^7-sR`m5J$so8IMyA_t6R!vZ4vV&(0k7}f#1U+G@J1pnY zuJ;U`b?WH#w+Df^yH%lg`jh#6Lyku6tQrthQNjC$nHsgdHaCd+E5FUv1A+qU_JQHF zpivu3kub?Zn-8i0dXW0{2?+9aos8h#OO$=&41ZMMB{prI$G zdQBvqe>6IRJ>xb43{BmMnifRfN@vPN4dn;zv20aGV-U|f(~eqpwy^}yljv+exf^{b$(`ARp2kPfmR)JV4kG|I`WyFX#b6{g4Ku#3QD-0Q8-!8m za3lIM_1=EL2=a{Jzq{Q%HmGI0M;NCGk~$e}L3JM(Y|NQ&1meBg%Px*Hx&=y}WSqn* z#~N2_M0Fa~CBT8_oZ-gd)Np^BcGa_I&SqC88*Ny8$n=Z4FI`-0bkmgsMhja~XxxBg zGN|f^5vFi{BcSZmnI!8SSX^cpf%&Eer`;_UQdzl(vu>PNKrn1JQWZI@v_DlGLB=)4 z-`JUX##Ep<=g@*5)U6p>V5rd{I*(`9UQ~ByC56WOR6Hji@6#Ss;x`u2nFe&lXMQ8J zjeFnb@X(#pjf<813HcA^`U5=yJpncZ@H}+YTc3APpJ|R1_Vf&+1NzBZ+dwD2OdL*E z(sbeMbkLZFQuL-vZ=wO@0^>Tid%f}d2q_4Y?1w^J!F>4IA?yMMqV2}dctLvA-(zg2 z7J~+^c-)T=A)Ojj$yR#oUgH#vy6@)MK|RxNH#UhHbuQxfW4)g|~uW6So7HdXMosy7`tPzL}Vjf1hd`NOCtzM76vEcnxc`8hUP;v0rP;C>;Sj znn5t~P^Q7)>a+78;r1J|LT$6~sPU6C`%f5)H0psUi~B8j1s+ooq6B*sX)^b+s4!ExyA_A<}b!C(9okv*a3eGr_;^V ztp2=l3yNvR0#OxGC(Lh@JMi^xY)$W=3CSjqO;L906K2*7umBdm|aV<@etfdT{X1B0Zbe`=U0fqj}F z$B4po+B`!B-e9r}yulf4>l|K!svnGQX82una{5C(lWi@N9|KN)hCEgjW>J|+hMRzF z8Eyh*17%%~p{mbCiGE*Yx3V5RoY+fjKSqVvpqy|qg-C^lcNQx zV&9OUoy8Q%mjnqaK}=oX!%2u{-z-`fq6^hb0Xk9lCKyaVYxqv4FY}C zRc@r@+48%>;+oe1zc{hU*p@DS3GBl+&x>QY?~#$La)P0kNEkpejh!#=7vRxjvLQ!g zy~^d61uz8{WXW6jh)RXwTTfY{9^K_i+WB{*g(feS9{~e?K@WKgo%$Us^t7Jx#~`R~ zm(zsXS*{@W0!_6-hTpj5lq>O0Tg5wV1?5J_@Fa7G+&f&TB;PmkBXrkKreY99rv7Zy zW?6?p=VVjwk?T|vG;P#D=3Q@kL1eG&kb4TM)!FGRu)|a;usJ(r65)nq!MgBaEWcL9 z!D3#QkHmc>7YVdkmU~l~rA;{7_q4oUBehkBolB*~lus+X3NG>eF@x1c;=(Vrq zOld1>N)(u;O{4=?+%oI^jl4oD!DjRE3LfhUd*VlVlql68+77nBY}yDiXM$-vi~qa) zS3$Us;_fqLu^!jt`y#mU=Tn&sAHkgfocdWf;j7+`YZo;hmAkPE_nNWt(Nu!Ph ztr?PwU5q!K=F#g!X(!^=QPYbu&hS2WH1PtC!<)U~)CA^8P${mHuqupF;t3E=)W>XPe z{Kd4I_5Mh<hw372uP2EkgHT}#W zlhkIX$oeiaH44Hr6c=O4rIksr5!ze{R{yTGChUuM>K*I@nCf%KDra@3SO0OWFpQOS z0S3GAcj{AERacV+m^#}{CXu!bHKo=>o7U9dVZuZ_&gAz@*F`37G(k3Sf)*?^ZDAg# z=~0pO++tcEF5wV)bRw@(nliY-U{ycQ>K--$e#*1abcU+z<^*;y!i3Xj;eDofNrD08 zg+#tFayGE~#cj)jUDRRwmfzeeKl4^;#06(8%_saQ;$x6u3_c30lgp;n%1-z)ne1i< zJMpbM5AweI<%g8=*GHS)R9?RV)eh5_B!4ew6GnYB>-~i3B~f@4$aN;T&pBv%NhQ1n zt6UQd4&I;wi+LYgwM|}*-9j|K!+ZWD9eM>=wHJTGVbgQkG=f$+Ot&yt2%iWSlGuS) zO({ae+56-mBn9;yc^&Q*c(qR5J1Yna(nI5UMay|*z>QO%-d1Uz!myTQi0}++zKt$! zlGL>7uclQb2TkC(g{jZ5K+sePS$C6r5Ke=MLPq`DydTc7#sMZ1meN9VDZ5x^l2F9I zq7B}n1wUh9tG!2pt_@!l2#Z25Hti$R9tn2DTOTsb2bSPQQ-2=aPi;RpeIfjX8aJ69 zU>MD^$m+i`ZIRTltgH_KFRqeJ9}x!OUQV&Rci)U4gyl?Gmlz+(%0$iL6?z-j`^@x` zT=5T;QF7VGEAs(eI4}32Gv`gad2SuRts^w=drzJ>!=cTGY(a!6Ui@QZwxyZj<{j|x za%q71R?7bZ?12q;LG9xH`lL?mKCKzvNxFe6*`3hLU$SVE`8n|89xw${XjP88AE9_3 z@q&T1r@q9RelXPw>=~PRun6(EDH?s>&T0NET)51=p6+0~+~yLM@F~?TG)*H6=nOik z!j6>kpS={~kQ z-F#T9R;))AgB(!MUD?Zn&6((}j{yXQ55BdOW$H;;D; zyck!er-bSrG)|+mmyP>HUn(8lV|DAN3gj3WgPJMob54rtpwX>ChWA2HrS&pd*| zo$Gbu(?Tj8-)o#EmKIa&qsGa4Kwtw)%HxF+zNDi2-!S_5N$V4ee;lf9Fk8yV=Gc&R zV_of4{+_+bcMBj?{x&Q!o^sb}uu^o8)B@1155OnE)BR%26#E#ssXJaV!5LUP9OAEw zAX&aWpf_9R7q#M0WGIpt2<*w!8893(zqMJoMWw;f%B}rFh65j%3FKXC#X0kw(?XF{dr=(nzRJ^1YoLf@vY)I$lpLIf&F{e{oI{Jd% zieq|MSc~6EUjpTwb~vqg@@W7DCJ+FC5diG92Pp}OfsQ$t(3y|^b{KsehXhH@(? z-c(j(a)~`czB{4;bClhq1%td<3)kK6(9ZQ#QJZ#@*+6a~5Zjewytx8}N`BSdQJ*VZX=lD&(?v zAIFNTkt7t$KxY#|KuV_~fIWQUY0Q0%1XitNqOCi@pmQEGzo1x`?oB6C2TNa?GZTIq z5??mIjd(KRS(*1q>)!=-{WUXC<|P{)c-jg^<4G(@qGYE7&sp=?m^0@6xO1=rjaEru z5$~9PMN}spykaS2{`btg@G_b%HCgi5fb-_>!X=n~UdMU4gQWltsm&EAtOZ%Qpy>g; zJt&ACxETnF00i|p&|Rzs=2>AGuPhJk$ZOHDIBh&U%u zr~b9OB@WHq>~QHnaXLb|=VLo^WugDXEztF@f9QR zLgld8ADD-R{M^~4QDSA%@qATV35wXTFqr>|)kWqTkVtjw9HCJh;_7d5Is;9Z_-Geb ztoh>~W*$rrcR)Q{s#(Fl;LL-R>|mJ(QbbBve`_!g z#0fUmma&92N9uwrkxtBYf;@0qKI01QIQ<{$-N_EiRZB9P)?mvQ*!|HK*lE7g+0s=K zr4%}t4o8^Zlv%7Q>eJ2gf+%%kYLDeQp9qJ>TYeGQZ<&^GL6p4gQnW5sWEk5Y3NRYN z>q1MpzztJ2YHFS`1NQFmaK8|~I0&=jFUvzE%fBib*(JB7v#8R&A9J-v1miFPh#XjEIsvAZ`v2WK~4rqnJY}ZyxAzGGOkPW7~@RVtEri%t32`3x}0U;=} zUfV3wwQ5*$KN>>iX-OO+-8Uf+xcXHzXyWG_{e)`x(cvR#5Uyd=bgzBLk;3z}s_IpH z%zO|Hht`|@y(4ECfgHskXR-P4inecv(;Z9fzxo>X0D z>)i_1AsRuT`BcRvB7mqx%Lq!Voe4GCOQLgM2!OKoH6P^~#eOiib=wdSYk%ta|0rM%M1=Ao}wT33mlY}(!1wjHU2 zg|U@^1a!*I-=tzHoU5}!gQ~JX7e^kGQ%h$8RvldmfK2yvu-VKUhA<-~3lOiKv~<{w zLCtyiv&M;0z-!|`fd1R&&IO|LqDWhE&z_n5-ZuxHTKZ;VTI|Ux%}7g2Ey~F*&dexDON9^f(FUs_ zgd^7y{#!gb_qa?Tuq47cJBLq&B2sFKrx&W^S%y+JQ`4^IE#(3UJ~l-t>Xonf5B1>J<)ICK*FxQXjzU4 z;-_J!%&-=qXi@`kW6A}Jix7I=3 zsPY}-XlghMM&ha&q5%>UYj5_k)*7c0=22s0n?6*yE?mzu0FQk>EjSI1f6IXZPOSuM zK2Sc4@3gYAuhd(uXW&kr&N{6RMl85?OK>=rh+jhz|BExNo#~x>WD~pZvKg*A&^VC& zNagIA%jQav?KqEBlDF8x)>huiuEbfff>zMVYU@fC>}U6 zp-DTk$C9lRRKnucwtQb;Xh^LH&E;JWT8rqe^XB$!^Y>_c-lSSlTI%tpdYR{x^^8#S zu{nyq{Mq8*?W@%Yx0CRSv>6_!;@s?w_pP7}uI5=ST%ggXR12)T{?>h3X(Tkz!1$PH zT}`{V*Y)MYt*b=|w!l&)<=tX?mY@mKo&B0`)p4uZh6M@z|G*3^q>*0Rei|_{aV|PR z`YW$^fEsQ~fbXULx;N>-qJ(4?JHdKc1R~Ojg|kz)DTEz9$H)8K48l zL6mesYDdR<+S;HKQ;V_M}i;f2{5! zqpj@;9bd(V!RBMR!2(u$r*)dBU4#3(07goASGrhO;iu+L1O{Js8WEC|ogcwSDCu?c zgLEU#x{Q`SWgX21-fewOfM7*-r?^N9Mucoml&e!Zvo-Me|BQ8A601bPwzLnDsA$Py?(( zE4ZllLVYS~42;kUc2u%YIap$i_Xmr`G` zo)vc1d|>vnh*T?3E!#e5{gl`5HbL0cQo}rv-F3>kPa};+7j6h~@C*Cf1}WXh=dV?n z&V;_Jqqrt3q@C|sA?KkF4qd+C;w=@lMbGf^4MvU4gAA{WJ!ri%no zk5tdjej$xTzFW<&Fn5aW6G32P`bOGD`S+N=rat4WOWE&U8>Zn?WV$LrpqXxiK=Wy4 z`ay~pXvMh1ELw6N%!@b2CBpgS5ZyU8rMvB6E{Qo&XnhOX3Mj{jI&)73@@IxXDRH)9 zwyTeAg&O!quKu=XBaYQ;gMh{P#>)FfDc;=rfi`eppG5JVW8Pi11c7xOYy-6GaWc)a z-C+C9N--dj%M)rC8(|y91u}2-kShxid8fohd ztA#nn+rCi=&y#7H?F#jHz??$|-L|{fQ&Vh1Md1bVPE$jJ@-WQ%o*n0E1C;G{P!1AJ zNi=z5+gNJ&JO&`TZ4=b&K%s3+I51UBMFwC1-eEh;PR+1osDzi9P!2H@Ctydgv2$(v zMS!acixMW%#Wb6mj?cD5(Sh;SGVTnE=u~+^IIG=k>n^bRa@%kLq8MRW0@OPyY^x!P zAz@|0hwRc4TZLAdpbq;A)|6->hGR~WK-sHpuShSVjZQM}9IIYM7l+w8(xyJT3JzF{ z77df$WCfFT*M#~FHo)edVg-e|Z9;3F#UhYc<0e~mBCA}XjfT8vmw7!MsJ4xPYIIzo zbpf8VbROkSwjBbeRM!9t9$QTPy*7vK(qUJ{84C!eq?SN4B(I}BS`C%BOv;N% zh~*RcgAt}n7-`CF3ahxZ2sC?B@JX=&N;HUYeE%0dsUxI!6hltP;gl*Wq6-v1 zBPhSoAdI3~}@@_BOj ziN=fnLhqZOa+mdRgW<*Fv@Bg394;dwczda(#r`SKrAvon3?$N=W`-xb63HS_JPIY7 z4kH#sQl2Oohn{mA6n|!@RH;rntjOhZ$i8 zKfeM}A5I2vcFlrl#FLtuh5jmr-|k8jC&Vk`gFO>Zl#>2)A$+Ja*9QE@8lbP0;^y!H z0K-oJ_Urf*`olFrV#ttWQH3Y9`4Cmq?4~N-pF9-^?f+ym1K(8Kh(Er#*@mzpwfRSn z7TeY_1BT}}dul3bHghdb_5<#{hps;yE{z!w%F{=Ad*< z%A|nC|0hLi%?g=%aU8^?JI@=j`5#=HH2;G%%Hv@%4VW)AM^$Rzf{z8yP<&Aw%Xx;k zh}tc4gTT1h&9UKfDe5g-=ZK42dj$cX@xV&HLiMN0w`{8-KB`I$0^_E3M(Ek)`)%#H zF@$u13Z0gvY{jd#)e7hE5>vlz13mW%UA@z}g8<4slMdE6V<@-AS;S_R>qw}Xo-~HW zSLm{#c;i&F{TFN()IuS<>#}WuO8OXSen#F;oNxRCwznwOe`AYNOGQAi=YF3CvN>06 zcL)(*R96OlV1#kh!M?1=_qG=WZbu;j=k{Z#5o=g8LO2woxBq0DB?(`%f}NsDIBbJl z@ZuNAvsjtvTr03^H*7&k@Uu$EZW6UuwyX@oGQGC?VCi_e8g4gAUm?wJS&VE4_i|B$ zeX~ZmN=@ZbTNW$ZAJs~w>afdwQG^8w2aaZrwkJ|qw7ojw`|3Ht5|o+42r26AP)~|5 zYM4%A&lTvcw)SM{J0$%#YI@Cnj{U`D@2Y|fcIyI70_8qvX&`XODwr?M{s1>B$bH*> zJ1cv}-d|v^)YXF?JQB|oCPW+PXrDFBB*WTo%xL&>B3=}6X+I7{_3Qw@a!FE{R z&0sK)IV|WjVNWj$PFIKhjf15xZf&I+=@;NVLFt3+TiLcT_POCeOonBK-AZ85ZKoAu z?cLeelkE-_v~a7vd4+T)N#&#qj~RS)W|>L{09yS)wrY@+Ca~9w?AZdiYw$vA0FL%g zt_V6fUZvxv0Ha}H@r)&oj1Q|i(3Rdz3Vn0a0@3`SS3vG+hU18aOTUL~;bJMF*W z)<~YT1dx%N>|bd$GZjW6S}s8ONwx?&R$y;OKfEW#&~+^=qq>f==Q4kdeTSgYK;)w_ zAMwt}5?GJzc1Q>{7S{Bn{Uw3Dw$q-2TPtgN+Wvwvf_SzzYI+XHsTe#uHe!$cD2iyO zrbhb!KS66;)U?3Sxw^;kLp1Fu_-O}^ zK+M|A!+lJwf6)#!Q5brjFJet5Ard+*UWbjz8TMGE9AJ%VVrlDSqocZEx|wx7X@3Xh zb|1<6Vv11hS$T@CE&=RT?PHdaG~y}yX!gZ9dmD6UT(!FkW6!>AzXh?|)7d_Dz=7wf zBC0PexSzfEo;?wdJ3w@;f%MsxR#2NT>@n&2T{B^&9)Es&Gv_-!Y4sewMtiPG zW(%9_TSS#6A+7pQa(lYkSGAj7pXhjkE&S2GMFYx}4iz~&Rp-|kSdX9s-RH?xl?l~V z_6ST4SIyAwtkS*6b)}m0T z*e$Al?3O4;gjSf(3apO4n48|CLxV6*{IZQwJK9nmZ4S-Q=)sP7*4yrAz`#mGjzHep zDSpb=PKLG9A=DV0zwwf!(bjs$8M-kfc{JB-@w_s=x>HOCJ5C(m+SAy`Y{wB~ z*n<^l90!Ez-;bH;_yk8Op-nng=ROw2@_(>*0NZi^K!PunJ8t2b^`xc&DlEvkD44Vm z21?zPwvjo>gx5o#?GP+ihy669wZ)vwIM^`|joJTDdJsxkAI}NB>Nc@W2O62K8p!8Q z??V~EGU_(eaayB+1W40|F8K`}Dysp1C3lzu7A2Zo=DTj+jWLp}f!SJtqc6r;o+k^& z!Eqg6a)b)K&6>tIOo-Z-E!gB(k8Ld6;pVkss8xTob3eAA$}w1E!>2l)ltj${%5ym8 zvtt2=4hGF7jx~rfkhZ?8@$ivU#`er`Ohsz95Y~lQwxyQ}r}}Pl9d9ApAUgfM{Ymz8 znPV_w46gPzbY$NRR6(OGYN{iTExXOJ9A&0I_hE_S19T#g0947)`55f*@}YzM z5;+eT#N!dwb)%s9y58{$vKU$2SO(_ECdXymZN78Q@8N0EWkB^ZT;Og8v?wsNmHosw4pOq4N0C)?gI{O%?FNX z3NptkQ8SSi>~~zIQiKmU>Y~J&Zd$6KSp}H=PPSilETsC683a8tv z`$eu!qynK7rkP4-&pNO{z3Si_)HLRO$#EKM=^?R%_6JnGfNky=Oy9kV^T^f7Dl^@l zV@%?o6;^NE(!YL(st6*^+f-_H@RS2Q^%u`MdSf_2rRF!s39609PLf-X^@H3)lf7)n z2acyupklf-HyJAH4?46AxZirMdJ)uhLsqr%I&AQumjXfLX{HwA!%w|n{92PiVQ9FCnjatXP z`s*MtggLC>7smmWQZ>4`LIq`>FwI<6aLu6>m{xNB28?31;E>}%fo-rlG4Z-ZI^V`B z8Yeg*#l=W_nWxA(3Ysz&C-H}K-%jqu+!p62Y8Xw&r8`Hloe55GsMFgxPYc3An%~vA zkS%sOp~|upKGW?~FxMHf*QRc%&WG6MPEHuEEkKHkAl(~fPhy^U=h6r@vqZ ziuTS2RR5|Br=Hz9*qJS`cID2`IRCvV)7hC0COP}ivzgAx{8tn^Tj5Mq2^(OR;oPEt z^HY@z&Zkl1$t>3z0daoSHaMCqp8;fTc%;?{!bU2HiGZc|a_6Jr8bNFuCbs1OgCYkEG5y1w;69zRhTydkKkxu=c?6y#|E)v>gD4%W zB7UEN4^t^xt(JR22ZhJv+39#*{00bTo^-rOM_Kr9fLD030fL6DEemJ$jI<2MgCTUL zOR>q(BLOBKDm^bTFFi`cYsKr^^82P*{%_*xJQ@GcK>zrq?q6l`?;8k}xv714x&Lhg z4Qg&6-Y59q1I#0(jrcu$g5LaqcVwus(m6i@jRiPKrLofeN@FD=LPjYv=3UFXF$-OK zQx#jE!@;^LW?OHAwvu;jOOK$y_{k;d|KhXIJ3dmB5ybldaa*#}wG_XlgR+oXCPqf< zb0n^OfKKEsjpt4!$A4}q9U0W>w%&h{Q)`0FBcUZDT`F}_tG!*&4xGN(g1Vu~P`$ti z1G5V(Ahd=)arKqTLuVkS^RF9(2)ImbiD6c`|?k-3zHVCz*H~X^5R!!sM=)syCFhC?_;jE ztQwK@n_$rMA;BAkH!>fHe9Xb)Kq%QmGcGBV@ir1MC3W8W@lw&=VTT8QnN}* zGIW{6yeQe3Ilipo93OXcj1qz|iC_No9DJM!MsOzTl!>v&+ma_3`dy5_fa+;^uES)? zY7N(7lVf0d3%SPn8%OL+tsp`#0w8OYaczDyv8%o5&jd^+L*pLNQ#Y<(Ge zu0-VTTE;R@h=1HX^*QufC4Z?jmolw*YJ9#nPjsFFkELXbIabB*n{!p-^LKn&;%`n) z5s*;*o~&YBkrx; z^27~z$k;m5`KE@^%I748JAU(L;9Dzy`A7V|B@wud=5f+X!57!@SlA?$RG=K2;m^R8 z4Gl7Ak@@*OW*1KtU71nhYyQpOfurw-Oa@IAdJhI{h9Bm;Jb@Bi%18V+5>pbzxXt40 z1AO&nbMC!Ay^{AhpZgdaEj`aa%S__~jKAaqsCmFB!vHT^hYDiy0nfXiucR2sq2K&( z%Qtu)+G+XdJxLJK^d0*Os}Hl|+aKeQ2Y%P!{2BL1bK zJJ7ihSEOQ|D}n|T)!zc>NWzuvAvf%_A(BX)bi9|5cc&L07y5|~paL0|1uZmcI z(m8r0fF`(?a=vTfF;~9BvV;0y8Q}4e8rRF??vuHEBJ`9;juoX8 zF;nrBLsVk0wj{$}r)R=RJA^2q#L@peTSP!?FWeBLY48e*8J>mmP32CATb^LxV;$Iv zPckyOY=vjh`clcBC!yeCwtNyw9>w8;lT_r*Hwt`@_i4IHUUQ6}mJE3-q@ftuf2w>Y zPXQg;QVTR2AIC_sC^G{c-uzsL#SK5<%b7!R=Aae+NR~%R%?cGY8^8G{ycorm#U({P zTwYwo#U;KH`~}vU4hxCW6$o?q17C4*X?7Ye4>bVb*qh+~oD)Z(_b3mhBmAK^Kd4mZfj5VL!6!EF$822q%GVl+ zBaGu7TSRjz*q?BZ4Mo@dfPdmG;zbH&qZo%3VbN;7hqh>HdI?{gl&}bb=vapNi zAoTfo+%%mJUfh*Wa(q5GGSBe=#IU(QyhpInmlS1JrsiNED7h5*GWgc5l$b|I^<)=i zW~Z0y5l|O@!Jr&)Qff%5{N<1YH z^!xdL*em%5{J)mJIUY2ew-|3qJ$`ERN5nXI_MA-S{p>*jGWd}30JZf+z>b=)w(GzL zgdd~~r@GPfepn2G5cC&gpGwE`DaaB#I$r-PVjH9dfB#>T+aYQjly(Uw7pK8_o7_Zg zSN=E2Z5{SWh1~m($Zfut{!40G$I0!=e>Qxe@k*JEygGFe?@Zh%JWSAYl<5L(aj_`nk+A_u{F0Ebd@0H-W9IRd8T z0BWf@K?lvU64ZFD%&^q79Kh72rUqrBnNT*FCU_gPOrg=r^!Hi&0BZN%@Avurp8xaw zyZ0>FYkb%Au6Mob9XLtBXX6IQT-aZ7B@z=xV6PyU}5&7e<^a>Kt9Q6BPCoUMS);K%o$a;@eYnGY5AAr923Hw1d7 z=g+)%?$mf6q;#fFo0&grc3%E$80&tZpkVqm{#TxFZhk<6Ies!+7Ts)g8AS?aPn$V+ zN_>0IoKBuOo;mH4B*hGAi>Qy68$&mFOA1xw%uN`1YTz{cQDT8$MeP#cgTs1GDeXUjTiT z8-{pquB25FBzIEow7KvG{u3{s%HD ziL+C0BO|d%+Ui!wO117Oq%JNJ8jm?Taz6v$$^eYec-bOHnA0z z^ya8uN>o=vt0wt>9s@&Q~Y)pg%_2uFUNMAr_29NM@gQ^K;43a?amt!%{+5XS@OOH zUS&AT{29S$9@f$gbV=ze`rLvyId_F047YUmrhJChjKF43;726?%M{O!pEE&RzeQed zlL|T+k@FIvlmEDByLMl<#4J_tvsQl)n^j_ZMc{Ukcw*PB+d54W4vYc+m#aL^lWD&A<~w(~SEZXV}X1qU5V zV~AM@!@TdBrtxMr;J1lgTby`JDE}_zDhG8hz51FdOW4fha?@EjYyQBro&aZWcIb%d zb|GX7&cjqHo|W`a;-u+7ukTCOGeB-BSzv(Ajw9LCA95L1#WS zMN|DoQyW&$U|OXLc?E@cvCQM*2ZFY9E~JBVp<;PP6Aja9<^=er@);qQQouyM4_f}C z*ROEngp;h1?D?m~UIHWtGzR_zUCA=*!~}dVHAAA{sJXi;J(se75$_SwsqO)bn_D`O z8_ch$@^h)M*xVQDoE3*ej{sW(nPL{Np^AK&Vk|U83+Gufh0d(zlBu&mwFfOQg!Ws) z0*C;b{{#-mTxRA5H8r~_^9wP7ZT!iUEWm9`)hQ8LujkFn@we|DOEy>i+}Phdi7NJ6 z6QJLj9TIXkHDvMLJ!-~_bCBj$s^4k;18~&!(4+{dtZaywBtRv#>Z~{d#$6|&12h#v!?*nNj8nu|&=7tiZdA*#-qpoT}4iPMH-CtlPdI zcfsU21@jSbV;|%l5$d4KkFo~b>NGG^;l~V>AE4q25ij$NrZ3gdVQDxe#xqRzd02Jwi?hi8yQxTc zjWv8FIs{Bz4D0rX>6k`13`Gm;AoZ;fBvXZ2%!SRc>>TS+l%H(gKvT5hbHeK^JI@Mv zjM~mx6V=~sN#~2Cnq^OgUp1IfwCqK%jbdj3Dl5JvmiwuCkeJP?BgOt)Z^iepWgCn9 z)3i^Zt+TB|*{)de#}JiY&o)@aPJ;gEA)_CHL02R`hB-8@1BkeyEmkATvWqa-eh4kS zMRgr5C2WNxE`+`cyf&C&z1J=7R}1g3s=LHKu%*^Pge|pqS=B_*4e6Da&6#XkNAVdI zHx8B}LJayCYuKrU6~8v!#U_;i$7R1mWwddm!MRCw$8cE9;JP<^pQm zAWr3DdyofVQ~g!{4Q3r$_fncQ<7x}`{W)GyZRGW^@TW2P-Yswj&jA}YFhe~ z7CJFOno}&h!ptRbelQK=-(RxqO=gG(UkmS$N+-rzPO+j5Vi6=Y^3959>1md|)qFt3 z4lfaJ6NEEV_h&*O6)|fft0)q4xmo|hHsUHskX4zzDk?aakW4w-MQ-R&bCzmmTjS{P zDswpNxfdlE#7BnsPX#Ycf9!FJl-Gpcueh}r|%D%JJIO1 zqM7fs&+$7)-ozNwjYNjTU1|uy7e1wZgRcK(UQEv!HQiZlqgaAdDoFD+^Bxon{?RYL z0K;P5BjQG?^qX;Mc+DbHPE?0vj%UwOpLUwtq0lORsvNT4Qq*G2T&hr6D%j2<@w`&& zcNA5uF|m}lMaU-A>OvrSB$ypa(s>fLX<_qryZEA3_?}fAHjjXH*p6)>BK;EN2si8# z^96{;SJjwx*jhg8CkQ8-9ZFjlsp>8Bj=)MO$}?YOwj<)Vg7708r)oP<{d?vG>{^pa zrD8cY%SfffC04fInvLbS%iKgM`*n~_uQYcN*#39KJAz?A4kzh^_%ES} z8h$rB7&b;E03QhPyrQ4PbL_s4#HYFS{kpLhP$m^w`e2rkZ-r_n5YlS){ih;?(SN48 zyDiuFp6SHAUx+aD`U{I1gtbbQ-8B!f?PtV0x#WD*P?6ZP_2L{%Gluqp_>=zk*Y5Je zj`Hta_{?r&d%hR%5V$P9<_e42sr>;PBq{=;K8W}OZVjy~DCaJSc(=@BXKswYa=mzz zW6ZYf%U?vuZvO?lLDtKy_$o=6G{U6)O?|^fGkfY!ai$<#rARa6yjRFE`CypK(K z)VxY*=x>%W(+n~w>t(SLVx7G;@l-Ti>_p{i^9uO?;}EQOX{%3Y$y|p>{1S@Xr+Q1|I1yJ_4D;5UY`sMZ+nxtGPy{ z4JB_!O%v<%lURqT$Gnv1njv`@qzxmlS5r>F8>3lPrif+Q*vH%k(1cT_%ltYOSu9Ig zzf5zrN~JZjvSM)-?D=*UL)nts%q7Sc!OE8LxQspKUnoT#tGLm8T&0a<-oMo~0()zi zxi^YNk#C~PO%)cQJ*8Z?+1T_f^F+PY&kbX*v)<@aDDMv3nQEh1R8+(iw6(~5Cp~dO z1g>lz^vxXX7^=F0EpfS4To@qX7)ZF?e0OPMS!0Gc8N#oNL_J5&WjwOo-YSa@N99$Z zkLnBNQ9RDXs)8*Z;U5Fej?FUX1*`n9$2Hd+D(JQ5y-t5yXiakS-Giw9HM5mf?>BFO z2=XrN4h}9%YH2OZ_qq9bHARjQp$8PCwUY0&xwv&Zq(H6m6%DuhqO~z!z4-`k63rw2 zy#lnI`Mx$|>vA-9>yEe|C#N}36r|WMrc92|%&=wosQE<z3f%y zfhw&`(p6I?Mf%kqir8yTqHV$EMFCszVww2@N;#>4TQXm%Gkc+)xg~NXT{szWfhs|%6xk7{%3%2}kJUd*>ol4lzdMvqda|zPInocub zY=;xm1Uxi_FA_b!9BI2Bi14?gnFq}~G}_etS^ijfu`tinYZLe1=8p*uY6{qks7*Z%|j;R^Vj zs5nNGLy=J(mfSdbTBnSQ`)gCEE=O!2%+p9FrC?7{?NW=4N~6S=c`}IdJh30&Ko0|L zAF`%U);2Mn<-9BcxE8Dyy6>bJqTJfftjcS?L$$Y7tWug!qo#@GT9Tf&xLRpcg+f#) znahO3=@LynYko$hO=nF$GY}2tU=dq&&O8U(wF_&?SKpBP*ockD>7iPi zB^9+o7a&R;K|e0FL{Q&dW^K6*>PhWJYEsx{$ube(-^MayL?=MmE~Zh$Y+M~DOtGZ0 zUU8OsP|s$vCt0Rxw1bX}@{8<=Buk-Mqs;(FgQ;u?6m-VVuxwPV9?=FSy2bGXOwCQa zXov9qgF10@ok{hx(OA(L^Leb(S2Tw5F_s}b&mDYEwGnRaZq__YB{k+-3whUMo@pp$ z@6sglPJRqfhw**&9DO&@vYOsnY`Mxh^|O48sT$6=Ry($%$O5&mnXj7Hs_n0b{ z<>Xr`RP5jo%OFg0Hfu^VXQ^04p=GBnT2S*TfWd} z{IKBuqpG!83!1H0MWWGrLsa!ai@2@lps+_MX^`Ux8H>WLDm7GZ2RW=ugMx5(QvM6I z`XO&0+RpzV!Zv-R)}Ade$?~Aa%zc<33{$2=^{5=uc z{oN7j{CFYN34RDXY5(+hM);FI72#KYaOkxs{K*Jw{7DFp_@S_-J?KwBxX&MtaEm_< z;Tk_bFCX_i5tjI6gp2(W!Uz10prt|D8GdN;X{Y#Y2q*ci2=Da6s;2gKzZqc%zX_q} zk4D(W-v*)1AB7vjZ+^_Krrr;OxSG%WTtB7OZ$S8_AEcgUk6(*$hd%@}8|KFZYhLiH z@#o?abS!L6Ne_h6O3=NfVUtR_;bTk*dKq>{Nf(6OO41P~m!Pjg06NgdUzBzMHLbPHri%9V94dNK3TD?%!CMPuEwoiqBsrGS*##i>rOTFc zD(yqeJH!?O4iIn~0k>UeOH! z4T1NCtM@2qhXLDN2Brh{&@QEohx7+;LUe%icLmZSD%+~BZpFF>S>yNo241?>u>y0Z zEvB;V`aS{VTYJ$08oywKeh^hhSo;ePv#Pfe!I`N}ve$DcW0mvJPu}~q(3pv~p3sKK z!9kZEY=s8+FvpE6pma!I%m;A^e#=?iTb4u>t_~I`t!@WHk2m`Wi#ln)tfrI>+Hfo* z?TXYCRyDznaRGSCSZRWFjtV+HzT1Fr7baO(aHXEQ^ExwSbhPJiSeB`wy5k#eAI0uW zv(7dIJGsRppI4nxzDC5ef?)lW1F#IgCziVjkBL;wX*#V1%Oo~|6n`ID%M&= z7zP+<)vCB-lFK}5o#lCfH@Za0SjK8jT8dS?&4o(DYN}bH{fYPApx!sy`c^P^A8|3G zbr4P|-jZ)ahphs_1ejF_SiMdJL~lQ4?au*l3WAU^AxSSr*|WNuK{FAVG# zC}&0+#K5t+jv7kL13-960-HL``m9QLnj!~@P&%G&eNQbsL&d!`pD9!)YaA*bg`dOO z*74lgh&@~dE0X5KFsiSDKlZanVGTTIt~G`&nrnSTEv#p+!AZ8_<@^m6`jGXCAUwD4 zN%%cyaBu#)O4z_XRl~$^krgI}(QuGGz0G1u(2h(Mb^*c4pR`t#UC7ZS%OC6@F6n_)-`xEbA_f}iib3Y^AOzZWR)NfhU zuubJ1XocSSyI_3_RpcFP1y}h4E4a$L$veyncf_?;xFdd*yxG==*pidhWm;h`n{?j# zj=+wcv0m44?-$R*(`85XAlfFu-@x;~Si^yNwXCd%zdcNKz~A`IX;b<7-hG6vxAILlmRV3y#bbWx_U4AY&{FRNii~ zt01H27;J1u&-64-rgejjkrbU~jHlYcMmZ7!ylMED$i>g`)a~_l?V8sWmV@|@fG5q${rvCxNtu-53s^+i(wC?5DEhGxRB4rT9;YNzg+ z)R*dtO%^UR!L|MInpl=<>_9)cAv`fV#b~E~GsD}^-E+Y~t%x&rf`#1*xI3VG-Nu1} zk1nSe_tMCFA#~yB?lI~hjiE?lP+=#?4TX1xLcii!0hM>;MV$6@ZhL6B1a&3u@HpUr z8!}UY*j)N9#dw$|PsH{Zw%TNDlaSONf=Ow~UAuOLi7FWQ=|IoE7-kKfFU9vX;B69C zT-miwrmcc@{cZjZ{Aqn7cNmJTEK=w0w%VV2y#1t1e2$ zu;%r~jc}#Xt|YZ1yg8;hcjhx8(RvhxK$y2H{1SIJ%JiVuU@Jcr9EKepRTu@6T~HiL zBk%MMaHK$jK0KC|>EUxN=iL~vughb(iD~U?_*Jup->lqf;2+kfwIT2?Q=dmd58F`E z3pTUjJKggL5=XLW)tSrzg+YI&1tdfIxjg1PpgDz;K%n=c4Q z@ocP5r;6V8spKfs;yrq^ek4i#tnacDdK+vDzC|^^*&C_%02}z(?sx->47dHP!js_T zIt#1{M%rLi@Exkk(&L>Y+V-Xv2I&gFw!wMvN=E`4X|+|Uh4;yORs)|*l5L3^E`z-P z(&V#gZrd~53AZp!f0BJ3Z%b4Q#~BK)Qd3re7S=|Bv>#I8G23J|CB?Qut5XG)2C44| zD#dGXiSBSae==M{!t7ZO+e8f9#(EoEnct~>j*60OjU07%v^~j(uJ%Bbzax$4X{!u* z7oT-h_PnYmYb>z62DXA^pK)uKzf*a;QB#7}txBIl@=JO*ZZ6u7QVm{K=(MlG1^xEW zE!>kz_9WX9%6MOwju-f-a~eO{UbT%-<#!`FR}Uu{VRlImF;g$DDB6!H^Jo3*)VS6* zhf~45=#2+^I?jt8u5^%g5p*Wp~n! zLC-)V4}{rgd5Y~m8qvo7fNF9+)$P!@R4G0tAF#Al39vo#n~fXX(0<}gONCXuVcK5o z>;gwBXxq_x2hyWV8W%(tHeRvqB1f9l!FC_9!K`E|d5g83&?jH97Je6kw4ahsv{!P` zh3qcYl~mak=bmYlP0Y{B%>&+qMhrs8so!LBEFZFETz*SR@+IXqi+gpJyvY#x$?w<& z?zwZlJq=kSGjAW3Ddo<3VCLLOxl?^J@6Deh^&c^GDCFX#nX__zbMoW82~<5UauoGH z4+8m zg+Q6H=CtycLk)CbTWCA__*ub9Pp3l;Z|yr_q7mL_Mh+Z3X5?`0QYM}t)bw9};-i5mK)cc>lDL9>=6 z$aJDZcp>>^c<51Jb&au#F>}+U_zo>{pjZ_B2Hrk+c=?E^SUT`x`!F)=V%pF>2O((m zT=y_7`MQJxHMxx4!g?C!BKO$L{$s`rqYpLV?E}4j<4#kXnB1hvNqGs<*vz4029AnP zNlHuVmIMi!(3n^%?Hbyq&A>5P1Na(ic7n5jiw`bKz{N30`#H&*G!|C>n9YhO85kvk z$WQS&+Y8Fg?h7jGYlA!NoAKj6kwu(v6PkFKS9yx+9<;gt8^Tzwnw-uGFWKe^{}Cs2 z0M9}DC1|DeZfU7$P?F(x8PoqKJhA+L#tsDRFr9;Sni}3t{9{j^RjU2+zvBaL@xI0t(RS?rf(X3-kBA`ie3+<#o>+IBynKHp-mHPl#AKZYW)9OTcu81{zTChM|`S>uy|EI|3 zf7{@3?OSEA=M+w#HaCCLoczKEAh4U?Y;bbAZ*uhX{A->XS}FY|V@y(8$pJ(%o=ueo0W9{F<4*;32z zfQ7snJ#1(6XAgAn!wcR|;}m*Gv5W99hS5E*=FN~t3lI~PE4|; z2^W~}M`Y&AmWc{i<$Kz=&^C#37HGqGqifZ|4|r~}_vOv?qayzJ75S-V7@dl-&0smx zY%gjcoD$`OEwYL+_R%W#%52*>LAXT4dK)}r&*k*kWhxH0rLr#bZO7pdk2dbmyI9da z3(U_l6Yp}8MEkw8>qUDu<+QV|X2Uny z!gL{>K?=e{*HPO8<=ZSqn$@U7ciyu7uCObA2EbQYRH5w|h2kq@0QeV}abyJ_Se}8H zM!0P$C*QsmuCdxWTM9%S;%pivWx}xB_P3G(id>n z?HfWwi+(k90Ff(%>bB`$qMS803wyiP)(b6as4!OxX3R+&-mr99YI-{nZlgc9t=DoV zf!__`eSWC?oF%-q&%*?46J1nwKYtg9UKDHif}afjv`)W!h#wM!-vrDiMZRV0*;8NHu6gyImsq`?BWZw$Ou}}yBG=6J?+=woeqWaoCVJSt9)mXKC z!E&7S0p6;3H^V5~>-d;`b`i9^N*B(88*SI{2aF?4)o;S)n(u&_xr$hgon83N2Kg6V z1Z{lIhR5hDHdrOoMN)PQiE6q#(@ z&Okc93J_wcskhn8vJP7AZ$1q9%nlUxf%y`w(wPE+1-A}Eq_Cza780mII*|!wsD|ZC zu)P5$`XK!yJl4z#4g9uHT5i7?5CR}dDGMt!BArQB?RTg+4G#`&kj@HWJ;d&n9LwP1 zXMy89kF>GESYB0SX2bx7#g?Gjmu9~^I0Ux2E++r`>@QM<;&O^|4v0>Oq+Tka)P9^x<92>!wyF)F|#9!<#O73evsnW?*X0YAOO7->vNN<@R3kx;R!i*S<EbDSp!H6gx!R^=QWtJtbqONziD8KcrAQ1)P}0t{rIF7MR)S^Z?Y6U}#^>|xfm ztq?88llwGnDf@AXJqD?XtZ)Ty|L1nGl$CA2bKi9Peob)C;2_{b&QQR(xxr-Abv)3` z52t`X&AoNF`KF2E=`Kx(A)ea%O7 z;vJnA4-K|RAq1~kx)io?ttAPM179XqQ2ju>?7p9*9j67u0*PMQxX!YGlFMy<%EPUT z=&N$ud;1=?t1u*~toX2fsftyP)MsF&o$k<^HwlWhhR0T>+Ox1|9@ZwY>nrVQEaY_B z_zDCWI(=m40%Gr5c5sl|vUgV7dtxlQu*^yuUg9yrU$OdBhlItv);5+`*_EBe635Qlz96f29Q2No@)>Z9Q>^PfJ)bm%JQ|h{|8i_gGZ8)`gOdj?yPK@wkxpk z6-tGN$~24NkfXzqbv)X>{S;*HWc)h|r<0c%HrlY8xEp{q_=|n+%ef zWm9>w74F~u^C>hLBLjD2uUXFi*Y~nwUT;6}ZS=Rdo=O!|{gutf(2@Y|*V(_uyxv9)oAq7>_DekmYIBF5sJ6_uflABt<9SoJQ^Phr z@iQWU8arq^uuGrYqtTf`RGh1g-}h+;)O6b!`ytdc7(Qwe4}i-5NI61>!+Vbd&oMzd z`0m``@NnwB6do(oBU$J-cG8C6!vDMB-y!u1{PY-laIC*iaL}*HI}-id>2Z|zW&nP} zXk!oUi|nOF`*=V#oN7X>cQIg>&G;Gt`+JZFE;ymlZ+R7*tfD%Y`v!=QBWPvyN zp>hC>p|Wso8aq79(O<=~;;hS*+TqSD3Rdh`B@Tz?rU^|y!z;X#>IPe3YuMy?8(@y3 zvPqg~20XSH44>C5^{tbCfWrZVa~IWRTQ_h3u-hZIsI;_C>%gc^c0dR$FEn_c+wqbn zBm=bQf4~`A_)v|)Zvxe%%63-!pgl!UkYGxL1M>SF9sMwQ6Ui&tMzf3z`&Kmv@g8bA z2Wqtbl1)+$v|UP9Cov&5;yOB&WFITgT|FJ!RJt4{m?Lh3Po3TlPzjk3DP8jKlez&~ z0mn?yl<|{pFNbG5e(=1z3UWx;TQdbddIJGQ9@|)HTP?8iY{ztb!cp)Z+kCcYv;)R; zbyHYovbBf6`rPF>io|=_#zQu+m|Es)18aYe0}iWnQ{nW_)>dHcd=617^aC~EIF3y{ z8ExLr3VYc>gyHS+DZ1{pSt;jreFfDX(?AwBNCz9T57<`IAlWvRrMUS)TX4lbLYdDQ zR5-x?FUp#))w9OAjy8a2CJ1WHQi1+Tusz45haBh7_AFM{U26e)eZ>;bDVV^qw~bd` zK)x$Dgn*vcK@G}l8$^x8j$Z7xhhbwmq(6+?rrkPj9N`b)OwA)2-F+xOhnfm(P>Fcl z!O-|zDt=wRl;vdD7b_F?05#3G;mBU=IEYld0}J{zl1Av)vD$SG$k6HLk^GPb_lxz8 zcXc6f|M-J)H_2kdH#;g}dwVuoc!);)V6CFcMys2BvDJ})QVa2}hUcJOuQ>KoWrlGa zOWffwDPAFuOyI2Ff$WJr4oIcx7NGnRI&sOdn3aCvSRk;W?>aQfqFKs<4>_JR1m6a~ z+$1MiGT5hYJK_b64w4AEhpFb2V-U~053TuG@CS}UUC5x|ph_cDw))b6la4rpZrO%V zKOPPanOovyHuw|AUVvSUI+h1s1FHvw-SZVrtb-8MiB#=n1^9fE$vxrp_75 ze^}-M~;%~<;6j+G@rJzqG2%$$xM`^?ysfu0CH?C8bOc_OWk}k8_XbJj4E{&89 zPlPZ!nyR$hX=1RBQgVzEeU%tyq$j_SdNE_T)T9bog-X}5n*CC#z}7}dv$!{>nnO}w zHT`{`(MnRa^aS|AQZ!#vX0?a{|sC5gtP=N;2OJw)vuOt?v6Mhan`HuS*nYa-c-6k=)!X>CRw_~{WLZGAw{Uz z*;on4)6rSV(Fh|LT)Ddi{R~*- z^K9`G($asz0fZj}4@AYqJgK0-){dlmjrAl=#=|Fk*(~PC8w2nV;7MWqZ4Hv&p4P5djfkfPkM$sG>e)c!6)Sww6&dO#z|o9XH3v1 z?9Gx)N|7B@b|$7BE6tQZ(qj4_5cbWN8bU&{VL(@E9V>>}Kf!N|x>xY4WHqZL_;tf{ zRDnkS#SzB4QpIY@5nFzj;a3hi-0K|INbSft!|0|-&q$N^t&pY$hd}zgdO&NvRL>-b z{?!d8|43|^wAOgu-WLiPunRTs2~d!wGo3{^rMUZYUd5=janEjx1 z4nY0lP((F!`^bAjIzg35F>(KdhXI~VA<%2%PxB1*VrX+-z|aZ@cvT>SHc894?iy-1 z!rs^_WvYeO$onyP#mUbZ7m{O&G=^Q>F3s15z-874(Kq0(-Xqnj^oO^0@{bMBOPVF7-^Zn9<7rWXt~>>Z-?Ay7$XTMO{b*gy0>45 z^MhhujqYNF~c|s+5_vQB{b_qh@O3a zK@wGX_436EeW}PSYQc*3zz@zoZ793vqO@6ycdzW#(k^B1H_$hmq+;4NN6O~gemL#< zT^a%1xa@K%1RlZvkpARqaoI0RIEJrE5LT(BvQMN&lC-jJ?^T&;xd*5h!fqLxFbdVn z-Si*rko@C~ep=9&ZX^L&IgJ5k8^Y!{w=LdbGS% z8w?Wk-2ne!VL!PY)t+>~*28h2u4lo667-*~>FvKeIB3g~TUs};$L;bQA>@cuYV!c5B0gDQ;);s?pl z*Zdb%eHYT1azb$d8ZuNqpw@hi`xf+Pv)}6&M=6`ygs`+M8FDS(GRH+%gPIn3-6k!R zj#1%v%+aVsrMoSBy3aGmFUog!x1KKL$}z$PikdFNF-o5NwOaU|Ij-{LUA^5}x_H0b zPWWN3!_BK4s8l%xRa|6_U>;RlDQoD5IkE}tJ9(}g&F-#tZc)>bdGbKv5_5#PpAp!G z1#-ShxXg@^?w`P4n&%FolnM zIe((6wQh+eTiqY2=_|k6z>EJ(Wqx;O<|uK$rw}l|Fr(YOTg5g!<{qMKgI_7T1oUcB zyn7TpSU)8{sSF zl99SU(@6LXQ=Hh#4PIH|l|UVT!6hP}v8zBOsY8rXKmLA;WJ z*?}gmG2>AFov3puOxz_8#tMifOOOe+epmB>4nqG)r0Qu2s{9I45*@ zg7i^TLvAk{w$A;kn$ByT<`8`wFgwY6T~24cbWYr}^leE9b-Ji=xT_uO6XraMsAwvT zbYjm(I-yE}hxDolcQPBY)x8h>ncz}tj%C*E?ypg)#c5XQ+d&!=E4f#t`zThS!`a4= z?u8LFY*f98Hg0vf$|pJ_*zi5>-54|27N+M@lvM`)Qt?bz0?phfh*T;#d$VWvyYt~O z(d`VTi|w80rkQHqb@yZU9CE(^)u)ASBkj7bEIGY}S*tk+QevR9R;pQx3GR8+{TATr z%;B<8%{n*U3(}l#p(A$6EO%#8k=_}_W`5vajoA+Gu1 zOYm6f>A;dRoF`EpEV+SBNZ}1|evL2acCB^+zYKE1g}2^C)_V8ttZ1DJ6j#waXC(Z! z;;B4oy(^qggqx~2xsq5)mh&TI_E1BE8xnk@o!{asj#)4AeiUqSY3cHKXIquNJypHx zM%@#Xy5pJk7oMRaKL9P1w(K4|>v6^XwwgkpbH{4Z_(*pk;a+D4s<(KwY}i#k<1b$k zVzh>?kmc4VlGo=XXTyt}aN`3e ziq*vHmU04Z!Q8`6S9_Lo(p7;j?Q_}b!b+#8(!-K=k_Qtlc`(t^%0T8cW_9zzEnSDr z>L;D(db|f+KTjN-E)-?+fNQYMnXl8_6B6{;);MZ7?^0Y~MX~!YxXM(t=o_a@U%u#c zbEjPT?$mI>WoGqW4xNrSI7-71rw@bO-BD z+>&A!CcNytF3^!lJl8MC#mn|$O*@pbxjgntb8K(cRHek;%VV!L$M%6ABq!c9QfL-) zmeHdh8#Ng7QBA^SnUi}D#&C_HK;)4Ma}c%41RFEBCPd9v)TnRURP4i;*`TwYpDLL z3_ZO?a;Z|RKWloAw|grt}jr-l#&BkQ=L-s5st`8 zWXSRYGxFnYy^?+#Yx>0bH@Bowyh`pswSG4=@*d$wbcy>>CCBZo=?h*%E0SbDl8Qc_ zeh_Oqqg394CHVokK`AwuH8t?0$OI=OGLJjq)$n0g1k3okx#$qq^o>&VS-^E*3x?a4 z;vOL6vq#T6rz$nv!I~~8IiBY^UUqh*jLz;5mXYRuOi3TgntoK$U&N_^vnG@pySgLG z?{$Z>ZjH_ZN}ge?sY%K660c)77HLwiW&nq?CQLGh^c5ss9qW!_jttBNUpOOhZBvqV z@lpo?TkUp1(|X!=5g;UV&pscSG>s2Lv9BZ;E^YN}ofpFOOYlDDjSP7i#jPyyxLUBB|iCo%a` zp09Gb4Ee~L*M4CA4uJt#&7GxzkG3S7aM2t#_6w zc_)*6&Uxkrp%BffglZ)zmwjI5j(`=4YqA$l6$?R+ju`6NqNL@qYQdQSbkf1uiQTcz z{e%*mPuYUg&8|M}4pwpU0gkL-l!Wyeja9+>G==Ie$R^5>o#ARo>w>%DOs4u-u29(8 z5$<%Esh7byl_H;z!`QnU+}#8^ABs9s_PDexW1LG-v(Vqms>9JTvu<(+7VK2$Ke}Sr zkDJ|X!3c@QSsk<0{if31G^$x3n_0wUR} z6iER2+|R0QN;7+!o0-n4WuEOs4QTiJeS(W->=VqbL`ZL?!5LKLa?0$F18yyt_sv8} zG)dQRD@Zxy4s3&&)U*dBt4{$`=|LCX3o`x^`1$DiM5$yJ)g-#RvB)>vjkvcaI&B;; znmL{FoRVI^s=XYN(m5`LRPigZg_JqQmBJd+osk%v58P`NV@Ai_{2^OEn}h^sf6BQo z2UE&zZWyFk?y44O{6ZH5L07moD>ckv)xDK2pKR_jc%xcRJ9haKH|#TYf=&~qeuh7(pV5)=*x5TPTqf3cook=c z;5@1u!AGlsk5<;VZUakP?|NTJnhzghywtyNHU{I!Ca?-`t-F<&Qa`j&jBQf>PaLI{k;H5^2ZYm6SO@sd zxY~SREg|nUP+@1MIc3#es|Q+{oOB|PPF!$Azm&WxkB$8yc(kf=tp`gm*b`XOP-x1) zG#42?!R+r4j~cq+Erb;VVHFsXVs={Z!3hqc%0yCzGmM^7dm=emwMhZ;VX8RewzB#K zP6=yI=g~2Dq-Vd9Ugrvf>p|BCN>T|GJ3W1vIo9(n)}hZCPRX^d z0gxK$Q{r3>_s>E4Whtr94*15^eamtuj@n#jTP%7{TQ=#W>lvkrJaxatpz=y2ENv5Q&K%Xh zry)UVc6vKEtyNm;pOuIEZj#3QXChU8=L)vo(y)>}RRO5A>D%KaSXNBA$yz2Ci|e0| zH2Hr3S{j}SG11JSQvZ=7Mvan&j2JweYVWs1!?A9HQoLsZozhFzs1{?QbEnP7pEK7t zqkt~2mmFP^CMPdQ=;%#)VCJ;>`319br^e6cQAs{TCmDEVM6^%*&D$d{wMTllWSH}7 zpEQ|PJSRB~T%=1-T?$fN;_i5BRBNOGF2k}_nf^(jr=JhLK~EFt>4(9^)AKvU7`KhSoFscVy&Q;KMMs(`<-m6-9ciYK1K(xvye{`^rHFUq z8nK#=%;IEpuse`t4IP=ozje*uYpLN)Eam!8w}#n{I=?}Yr|85&&;|ca={~POT293t zcQpOj7Dou@;4pA-`0WguZa)b(h}(Ti=5=)9mII!XwXY~CPm{dzpGW#SCFL2a`Cjfy zmmYDqVZ%;4_oD7+VW!TFvjS%}MLr^%I2F4?$-JI2A9g`<|7+(P$oCxiF1a!o2-QPM z>IU-t1h&S-&Hxx(ovy6}>eEW{^W^)@<)z9dA((Y*+{-?E+>n- z;yjHkFOW~=&bXE015I2#}02Tu1$>aFiOedP@NdC2jPBhbx{Oct; z@g|15g+@GpsI8oQPw^NnV6$68) z?lzS58R&_wZr3ryLwurz^t-{?8KF|?cQP<{zzG3Cm&BLlE;wG46Zka-E?V`KRD7Oa zJknhJ?p{S;bmFU<^0k{b{wjClmy)9jY_GDE(@ITi0yXWS%$+rB@)Qlg*yHV1vE&>Q>{=yoELM0?0Hp5so`XgLgt(Uv~O@C zr$;Mgqr8^u-=sdTfmdcJa(#y^M=5Hr0J9k-V2rBtZ{0jZE0iI68-9$@Ozn85j!BQZ zc(w14+#m0Y=2F*re7y^g)O<+RVn~AZ@1?>B>_L~@N`<_MEbmk1SX_!gczlMhV{~j3 zdUz|7q;Hoo!l4jqOUxhg7u^DfM50at4+0 zrV@FAGEciQLD#w4vYuDmfsWVGv3t>O<@B5QG3843I+E|lM0|C>(|LoW;M|y}6o@{e zW3xEGvz%cpKG^e`QU5Bt5ZF?m zQrT`-JWKk(^(pdyMs+?X7+JM0&dB zo_In!&!x6&#aBg@~$Vj zLXM`=bhi_f38=&Sw>zH#Up-e=XxavrSm^`<8YhABeGV(0@6oG#H~gzREzWcgUNVr_F4S zK;HB8)jYt(`Mw-U1UAS880m33IEUkmG7;ZX({ee$@a6db6GZ~}IJlNT7J*T}NZEcF zbmY&nM3s-?#$5EB+=h)<;U0mf^Eh{ZZm+IzrHpxrcVmHGf|k=%+$DhZtAVSY4fOmn zXXImk{_oY=^_1^liI)K9c)H`r}pH+-gsA-YF^6UhB1ON>@Z9}9f{MU|KMCeCqRP~H=2<+l8~{{~8Y z9LVuEOO%unp9Q|Ju|&7>{bk_$It@GO1oke#n5zsyOips01uyyorw!W}?u6wO<-0cU ztzvST@_jP!ttPpf>r;}VJvv$?IK$cHA~%?ohG2+%DfvzZ@`b=~q1?T7%3ehl4U-4( zEGPbwm8P^TxYP|{T7!GN2C_sj zd8$&x5%`W|@^t=Xs@7*1_VD!lp@yyqtlM$6|TRTQ0ErVf{7Xy zIAfT264S0Sh$KXKurNP&gQ_r?nY>mhc}Jiz3(4zTlUQ!O`+1~VVTFmO){g)VJ~PV6 ztaq7x{fPmujt{Oa5MQ3&vE&nOQ{wn*{b>X z1hpqN)Z2B6H#a386~QvwQ67D4VW2gbVtW_gG9`=1U-IwD0%yM z`qK8^o`o7i>YCgAlUc_;o;pEe=!7;pv)~L5-#uw~4p#n1XYWt;birM6tmjFp@Oe_% zpMyO*6-ABk$Sfl+u3BKjCwOf9Y&3Lbg}-?AsM!9Io*{zD(2X(%c_y%&(ReRV8M;&U zAWt%74f6D6v&MPWAgTxR?vI1?L#N>$F8yHW$%1o~Mtf1!5YN5TIN5U-Zw`0p%3&S{ zOBwEI$7wl3Z<0rNVpxhMu2x{BMIQM2MrPbWG(+2af(pid@td5kAY#onLo=?1tnXyY)Co7eL=Bul4)MR4hE4@`~$Ohw(o zkjcE!adj&8$wQuw$losjP1XcYPZe!nDD(yb4j2#TFt%&PO z(xY(@IvC+G{3jT=5`bY84Q=p@V-u=9Td=%Fvxe<)4yDr~FLIP;8N;%x<7`;ZrE%G8 z_IsXE4CGi21h|yLJ@pJDsOO}F;Z7b2`QmkP$CU4JEW5;$hht)&XB|(!iw}pBPtK#j z_%C>rA-kKhpT)v;yyj`3$jw+p1!p{~(ZYDjJnYFQ+v}cxF@U%aQDA5f_jH9u#8l5) z%Ir?$4R&Oab$nFMgvN!lo!@&#V?6HR2`=6eCIj#tR|W;_>OVYZm7Rf$@oVOjIAD! zQ$vq?dhxpN{ZD&gDhC=WEbHPvSH^!DU;Op|T&P`m`TKb5kcyk&X``$fg{y10ANHV> zRvH4WOs9;2ar|i_4);~V43;@R?sWyWHhg4Y6ClVileY;CjA3zqGr4121Ey&fHGJ=R zg%A5El!D2rVV*P&)Vm5$g)DoW$BISKGwz(qFq^~S;c)a(mc-M%&N<4^+_WSjH^+Uh zO!!8gPXeFKCj^dk(s_5B{4XU^u|Q;)&!S2^mzC}+C+PwX6--yQtP?KoVt5GZ z?s2pE3OBOF`{NcV5G4mj(K3 zT@eR%gJJnU*3Bbq;{{K+z@p0HnlS4tsBE8S4PRv0$`mOS(xbc#uJ7yOaL+b8#=m;- z5qlqEcl$h7c;8pD%%h$=l(kmHzaM8cFL?U?a}RSwJsYh&K{Z=E6_lZh+sp3S7Pk#i zPr?qB=W{*-inUq8Dn6Lvjpa6yW$ljJjqa2(X*j3^4q~D*X#c0RFM*4y-1}$F48t&I z1{szaHeoQCbI!~FGwg^9pqM5KD4LlI!{Ukxil$ymOmE|qDU zVs*=QylpOJXt%P|{C}T8(e!%nd*Aq~=(dcas;zvp%+uCj$j{Qxv zU65?Fyo)r0E*PZ`nMNmZP45X>({4kpq(Qm^TbsZfBq0~(pSCF!@e|gd zra-9_`=6u>CMknjPGH^dA0oY?R_iyX(diSmkdC(6`m4=Kt!He}^v<8Q0Gb*k1yh%w zZ9$B}q$xOm3$+iI;5mzuzESJ-PoiCePI z)9zx)O|!EkGaEEO;@x;BwMI)34bunvQewK~%jA6NQ|x9`pNgBHNBL1(uB2z_g%TW~ zdgK)sNHD94C2m%|NXPq0aZDL0eS)o*sNSrBOL?^Pn%ba$xrb-AXA;W5D=Wx41^sDZ zrf@&oQX&C8RR0PnD5Y6!Zn?AsTdz`4h1AF%ohVJj)-H1Q6OePBC&be;dBP?toGL{! z{Z#2JhWKteI!T2hpepGk4&6h0^95|xc(?Yl*t?_$RcysvX(kSRjf{6m%h}5LQWXw; zotlRUp{#9|)U0AJEs_Rk1^qs9pOt4JBUgTh7G89u)52wvj(*x>(oU&&#|vW|@TA82$c+*l6?`qZ2;FI>}$BsnGZw>ac&Revl+K z>k&MnR{sXZ_uEXfMhfxw9S^{s9^ASqV3ixCm4a6P7QTHC>ZbSo9;Db~GO(X7I#7)J zvt(y=$K+xd!M{qV{`Aq~;vY#Rth_~f43E`H3v=Wosfhfycku-F(=MqC zY$)Ug>py@;RQZfqAC*1_igl{eOzqRnel%{GoJX&xDIK#+<$UfE_&dP2 z0Bc1@t7Ne9B-ynHz%6=0{#dKO-R(|EX3c-fSfSg`Vo0|vl1*Udk+D3qJ}d|DRsaY9 z59sagt@Ngt5ZO#W{UODw0Mv6%I)vI^#j5opjbzc}#JC9RUTO;As0noVOjN7}RAnGO z1$>ObiwkoJ%ZBP7QZoEKy~@1&YXG>67h?kY4J{}{$PREU)_KrQTDJs7QoR@uLwoa7 zW^I|ZU*533Kn*dOBY-wC2XVtoP$%W!z46p^pd(P*eUgz-cltbez`o47YvGrL)Tbpwk&cEcK$%cnQ`3=ThTJD06Xe7o0Nt^55l`0Bc;< z{Jj3f-sm2_v=$8MIHb4alAIh)1+D%uQlL{QBmr$ctWDa%-)bMhlgWGGO%%9Xc2L(3 zFk|s?u=BY5v&sNqGb}x0xNGSMxx2`@wD23rOuxT@Y3s;0(kSZjt#m8xT_8uX9p6g8 zah{eUd#Lb~JcatDqG!A2>J8ozLl`l^50@x?dB6SyY!>p<^vT1ri%ROH0kq<{G$;tN zb^N%2bO%1-j4_mRPRb_}$)kx*NPW~+kCm34lx#^ZC&+P~mw|1%jh``nYPYMo4-+`^ zVYeDSA$7z5-4rWTCd5XC$`Uw{I(Y^WNae9zQoU{up|K|==j9u~yzC`U7DW=xt&0Ym*F6D0c; z5RLyR?sS@z5F2BbrKJD8wBU96Hxb?82;ctafG&^cx;x{!*2@v!4pzmQBy=#@8=`iD zhBBFRO?~l?&`ABA*JAg3TGcs8AW8H`q% ze@6AtZL%+(r13d8l24wqsQgQ5C~JFDj#4#ll5(kPf_x8Yev-~oivcK%=*J)7^Et() zD(O_RTGCTnx-)#=zJ%mEC}!hYvehs;yq~sb$^BXN-hklR}u^N4ivyPGbpAI0fwM%xKaGYYdO1)|N z3sOA4&k(wJT>h4=|3&f$suMKrc6lXLU6c)M{aMMQ7EjKYQ2_)m9^|L`GC7ppbY2?k z?>mjx1@Rk|@rL;EsXG==MK+*pgq+`F$0<70Rf)!0C4a|s{_;5#G*IC(xj$PIEh8f) zdzf0&DL^i}!zzE}HFMf2=YkBiB2e}QBkFB3Jn#Xb@-JG|&&+eL*$n^(y&MZ8=F+t$ zs1S)*cwC;zud&%rRmEaud7sMv*EPP2r=#{OfQz_@L5>wF-!l;GH*()&jHEpdxhJ)M zB^g-3qnBPmIctxX0j+kH_I@Dsp&={eD%zN#jAbUbd`1f{ocibG*V+CIxhJQ=X+9zU zgMHUi?#jas)>L`u^;6v<_#EUN?AaW-NKjoMPZ4;oR#bQKr5J-*$4dIikE=Q5&AkFD zRENm$VO8BMSF+i+$QOKls{yZG&LF!eu*E~=`6}Pv0Uxa*(|zWjsOf;PkG9<}ZJ-1O zmul;N=xhs)$y0d~iUQK!<+mA@;1snWs;TiT*X>Tp!>Q#-*^fegmu|jBRMtLP{=iQ& zQ{%In*ADifn6+{oU=c*$jhTyQXnfwD%>~)V>)23d=p$EbU?W2|vv&l*du^OA&n-+U6@kD*^@`SY?Fg~XfiD`_F%ma z$Zz2E7^&;x>-&Yn=r67nL4j<+5qUB8qlhD49EemC`FXs?I69rHfH(6S`5Am?Bll4W5WmM{ zgtbJ8auT{ow5LjDY_y_Gf=wTy_%igs9e|!unSInomH9bSKfOx#__)RMG(P)t39a&p z;Biyzj}ocUsd<%DER<(}wNUQHpH*qTeeql}{U-mY6}w~GPVQ4uj8K|Fd#Ct9ao!Z3wc<&@nj9q*itqfkjIW4b&nbtR1Mc@+4T5U=H2isQ#q_AFu;6a!TFr^h!PGhig4CqRX;jxw-CZn8tKi7ONuG*S@+_9axh@t! z%`WFa#P6iwz|>T(jd_(oPi8vsQL{gjTqeE`Z8#=rd`APi;eC(_Xk3YGq~tQ$9B7AL zTM9;Hzzh5MxLgW#KQE1-nXegwO$Gh?XX!7&96|vG_}8LXQvzTFb4p}K*sz=td4)(& zE4EIbR?!I~S#G7~5@?@`ryGM!m!KubS5BWk!wL{dl+t``3KQ%e6r#uz=pSPgv%Xis zkV|lb{IXHB{Gw!tn(nCt7c!F7ya0(ZKNUC18#*knZ!w%B5>O2f8H_YDOCLo4IBK-e zN}CW&(O&_4X-}Ogfch*B3DnHHZIlZ&G}d7Ew2C__dQGn^Pbl>o4dcCkd%WK$@&^ve zvr3-QvN?0-4Yf`f);Fgx&)TtRg|)%ABx}5tj*iuYQA?Iy%OMx7(Cx(p-T_lFdJGD5 z`2=|s@9Z&*85Ir2;BwhXVR6cAYV$)8K;UuN&UVEqwcaHoku^Ufjf0=Fmwc;tZD26I z_IC_i-mNm|Q0-_M{7qU62m zEg2eCv7AiXM5RD0#^VYAH=QiM!FLR%x~{S>i!dt3FbkzHw@tCa?fHx}lFc_M_hMfv z?fp?k7%oJ4MCW@aZ0+&<>ScUsO5dnijLP%34W0MXd8btvDq;r1`V^1+(#_IrwyB$P zyGjr<`J#k4d3WVY%uzk4ej}FJDp^^Htt{Gp0E+&>P^FqZ;8f;dg6oBHEG341_(}@A zq9Al(6H=AckgGQxj*$~7e}z1QK|?sFQ-OdWN13D*vsafazKaFsDyJ|j1NueGK{QhN zQ>_W;vVX4^VdEKK-a@8di?pIpSQlvQRPm`_}pZCxI8>_GTwq)gI_w=A7s3rF&*`8X<_u}R9! zSf~a-h(YAZ3vDFpt@_V*e&>r2eeXe#1J%Gn=eqpBYjBXEx_ZdDrl^1Qi4P*vhOdQEzoHozAq3FXO&mI(6WXVR`VDWWlb~dtkd;ulAlKdDo9i>p`!ib%qju5H>Mlh>* zMOllPXFS#qD_l(;s8=SLrR-4-s@39zk+dj6j9@WuC|_eDn#9aYjbl&;!m92uE?0$S zWB}maomK{Deh%0_9ys(MX76>MF_G4;Fm~mY^1L_E>idip^mx57nyOz>#qo!kOo6Yf zdg-K!%Gr~^Kurm|RK3+HWqO+agd<$%vG%gM^$MsDcB!1y`noEJR_s;<0MQ*T6hKI* z%+c}gj0{vDfe_vesP*);;nras-*|k@%oNY8In?78IN|m!u*A_F_Zv%ey?a|_MfNCE zzeR)UmtFv*Igl7@^7Z=v0Ka6ES@$JIi^|VhF{cLZ6grx0h@ryKfdN!9C~&sF*H;0t zrvPCN;73)FKyo~h3ak!ypf1<{vWE%Kx33w)-Q~T84A0NM;y^05^6g>P{G4IMPHWj! zTZ#-1ozuz!vvrD!g=Oesz4L7)nPRPAbTk7+t4`F$BZ{CKuZst+baFb1t^)e%KGwQZ zGBYvPEDF@!NlU|YW%TSMA%q3x>7w~1;PrI%*WFB>bAII%-dlH{hWnnV>J`;!fBC;_ zbUHjZGKmc1i~-bU6YR9gCK%{LdvGK@`hZcPsV~$vYRp>glRoNt)sx)K zyhf#aIj z=dOtp@tNLWnv4X?cNGL0#VR&RZ+~3H-aW0P2=v(piX@2DRGl9cLfIdNui*@o8VcwF zw6v-lnxBnvX(EDp;$<3 zm`W8<34p1iN*%`t5NDHH(dKXgJ_XLsDdlTi>kii3=m%Y>@DI&!y6q9a5H3&}&YnX# zbHhKmMgoEaUyv`+OQR~|99sF_rDToXI+NO`g(SGRxCVRW>B37Lf(V(JUpD8u`frc|Y#(K2Y z-R#f>#f?;&;P7ZREW)0w)z%u|y6_504SnsI><_D5qt=R?1M429S{7LV@6RRT+eewv zX8#43xtER}3{UHPgkTjLqu4j%LGELx&nR6~e--$u9>CilZXO@;emecDQbj#KR^I58 zeJKz3ic9G9A4(B{BGUR-`J1lvjwtU_KEO`@$!~^-DIxG_zsvgcu*-O&K#fmBKAJuW z1c>$>da>cz8MR2t{FbjDNo+-qeS}IcF1xS17SIYS_<^a^y|3M+6_>uKtu=B+`%K;z zQv|VgXFt0DHEB5=oeFfZ_Wq%V*?@uemjr{jBEyw|_ z8`*=@I(QXTY+bRv47*m+wjQB((xy9=EcVJs z`!ij%wZXuPnE|<6)HKn)OfNpNqPjK+ggfK0&rK62*;D+*#-$@_P0${>3n-Z#nPj&? zU3&-(NK8{{{}XG(Dms1I7EEj21E12_iN-GU*>JNz>rrDrfG=0G!&@R9Q2d^btU;Q> zq3}@FqU#!i5YAYCg};Y2>~H{x2PnJF|AWTE_U_DnxBUp-{aP~J95sbaueBqwPJE12 zKW*ovKuhfDc>n8=trvwAZmFHGa81npK=eU$&a(h&7FXJT!|BbmV6faw^S`#&Qpri! z>~Bp13U%~_$WS)!VSBz#s10FT*V>ElFpokCl$=mN0&6;>9St?Mch_`U(9gfUlR4~+ zAZ{ej%E+y3`v!XvZutax8YAJ`c+$>&8=IKtQBIHLI%4-C(TYzp&ts7@RZPFkzQo(x zHj`(4jzO&$8+P(Qm67urFSndZD5@q6)==QR%g|Eo-cKhTGE(^IPr zg|uapA&8API9hbt+Hk~A`DEf^`Js+y@e+68q9_P4Ik4DA3kXhz3Vtjx%u$Ymf!4E$ z8$T}gmeuh-+U<>%!)hZ~b2rBrYycce+(!?FIcBqY-5oOwLTwZ~<#Gf0#ofz|Ko@>8Wt2F;cC+y^Pw18p99{{d2bYPOOU7EMvFml$4gqgZ*h~$nZ{2!q-VF;*2KzJo6gB7~Na0ZONIm;`Aas_ttH z2P8m|5@x1$gW92XmnYMqzQ%Y;%rg4Id@3@kZ^L6eWmMm9fJRsbNh;F#tHi;&R)R)52V%8S-t& zGTH-@QdhrJO#8Ok}Wkxmiq20y|K8CYXAXX{^1ARY*;v!!vx%w7jQrnjU;t zcyrvfM!N%jL#f+TV-QXC(@%D}6$PY3JmxplTAY)cpJ$!wnKUf{>=HxshU5&+jBZYE^?rq8MYEk>w@ zvnNqeh@qanCS6U)p*P7zW?zZ43-cO3Fa*@!Ca~3?Rq^w--IR#F5y2 zd?)M1C6IoPS3Awp-#V~(fPQB6j2d|kwZEo{=qiE5h3l1);w%_}hmh~KGDDh)N2zoT>3>tS@KFFFw4505`-7S$;B*0JI`a67WCHE*g+J7!4Ag)Kn?A*kD zQ)c!b-ebh@ZZmG3IKOvl&%gD^y1gPkeL|wOdO$ztO!y7YL`BnwXQIpoRPSC^*=xWg zsf4TNxZHsV< zNkY^-;c`&E2EpQ*Bt@QAF=KjZ<@8b+-38(P|3S~7MfY`$^YxxhrZ^Q?&sPQO=+S$9 zF=uWK3)3O|U#9eu;rSL~qm_mPmm+mq7-;4Wo)dJUURN>0h=)@tXPzEacmM4`y;yLwGSAQGzsqH%feJ zXEVrXhyUqN1RA!V?0;npbsC8O-O#IF5)|2`(?j9qJ#tg_F+)V$RRi#p!mcM!#WDUYbj;9jShNq+*oP zxlBzo{kB&Q4&vh$lu8A+qCYF%xI7f#-LrmM%F+d8D6_=-4mE+Z`X-w!}kMz;h-gyeQk zUH{u5mqqpW`o?;P7y0iN7i%zyD-`fSSd?l)<5yv!240jd_#F08J?wKBYxq7*v$M7v zZOe1)(u*IixwSSHoO2vBRsz((is8C28avczyu6l|(3d$zZr}c=l^m%v|3@o%8vK!1 zpQE_jsTH5Uvz+y6or)O-I(~(k{1NTF7@9?e!yWfG>ebUm(J}h)N z#g0c7>U@NYLLGOo)(V;xb85R&pMDPLZJ#cgReKXX)6a2ED;~iOKBK@tfx_NC+7SfB z>No>?zfkkZ$Z^z4krFNc-0>+Z-{*J?(*4SgJ{AL$EAUh8 zE_U=SM=bXJM%6!SAN&`lPB_EukN67W?^OL%W|m}dwyF&VT{?8p6cC-dfhhM$yqz{jsi!oSn%A&CCgqI6z!iLo^$^fhD?hJ-> z0hHr#?naWSc|29cJ2RL^aW>;9(4mxtMo`=H$ZNy?F>JrnxfR}2jn5a8Q!nC&s<-;N z*|rpCfm&-ox`iQ#Ja>o2Ki3ihCa<<*PK#=1Kv)?!`wYS4nXH}&z9474+7BeE*cC#a zkAqZ~=%vY?hnY;7aFnYCkB|IMPg}`RZ~uae(tt9E9r1Z2>^9 z&Ue0zEnvp{i_Jz>P>6E`kh&E;7Gb5r-IfjPK(Vt{rvh-_80U6DYXCdEA*x|f z$XHfd;%vgPU1^af4Dih(9NoBZFzXG`Nbk1*YB@W{`6{=Q4KWQlH`zL+dLD;Z>GUeg z->5y)Ie^xjk6*<~20GWH#bW;=TDR*(yXk7t<~z^mv`KL8Khl%!-RjH|{L68-NPKKa zN7uTpYDd;o>MTW9l&%+7CW!k9`=ZVn2g#J{WVK%>>ng9Tj=#w2yjs>KZ!^YUE$as7 z&pHs$y|UGri)M_2jIQSvh16bo%^vS-c2o67@o%to2jUN)WN@c*24n^R&Uem(gjc+r zmAvS^ck*>ord%gwM~kNZMas0RrR;dwF!ipy?022B{8Xu|_lM3qRH7mC-LADM*ETQe zFAY7&z0LW~a|=TN;OyO@2oJJ>f8CSz4sZ;j zg%g|$Df>I;A>M6Ey!WJ!)wcl32-)-bTu%`rFhsyi<=-vG-3qT{tU;iaxT; zrl#ARm4vQcOe51Ok(h``T{~~wcCDDBt`qb28;CjjFJg|lUd*vqi|HMU8JEQz0Wm!{ z60`JrG0UzMv-~STOovF+zW_o2ou(-|mG z;d+5r4;yBaYJ?+)FBaVdHsS5~)tKV$prbLCn8t&aA@tSn@tJ&HAXIbw5)XjEmV4E` zspU}oP>o>@IAn`9X}fhSZq%v@(^2+O3oltW%%w#)StO8PIg8nX595*4U;x_N56&;1 z{|uB^{D{A?qITz06`SIlu);gP%G*D#T^6zJ+0LC_!3SXTZaS?B?|O}}-TBF=<;!%| z5afK7rCx}C4+qz>gnZ}wsL}k%!9z%fdssq~^C^_F`6dj**Y`p-=>l@d-3U)Z)>l5g>52aK9Upz(exkzNE@Tr>lCRQ&frQI8`J%QWb~}Re7Q>s2tG@l@aZxl5V;7c|=;x zBhqv(k@%O0)Sp8nWEPQt8ASZ26Vc`BbnK@bA`hkzxj%`>*AsPlI(BOU{rl;7A{WOJ zIr}1!Q)7s{J(|dYQAA!JL1e=)BFl#oQ3ny3Gl0m{7l@4ON94u6L`L@^lGU5Y&~ze$ zdlDHyHf8!}349ARdB>ZnNM1GDY@`x;_`<@J^`?evGyJS4wje0~rCj;s}Ap7Z#M-bUh zHq^c0B(j|>s4ESn3U%v(i7X}i=@teO(QK%jTZc#v*-@A6OJporQP&!ksnA71r;|p{aIG4)C%Nd?Pv!Gj;4Ao~%LC~hZ zpB-On%@#2-s;)Oo@4_OXVm*^EwmthqFZUfjDQjBp=&8V>xq9gC&Ti^uo1l9z#uyU= z%FNI}s2X6=!}=|(5gZK=>QHCV4TKR^$nVR>3;l~FAN;;AQ+Saqp?d*Bv)BqO7{I3S zYydj%n0bc!q3lnI4Z^Abp*Mp!-(&T_HicVMK8Eoe8%$iXsPa(Zdj{FlnG;G<>PoO` z5lhBdDc9GBxXrO`A$uOP;!D|VDbEdS z4>wm>>Okr$hWK$VbMtICxF#|`+`N|MFkC*H&6Zde6kcbW|IN+zF^q$Xd=^z4DlF4# z9|4s&St?enW~B_jUc|yUHWEs+4e_w*4m*w~C$TgRoS(4^TH#Una2^X|P}M+iL+d;? z7S3N~iTLCd%WMYozF_OLLZd-_oxKF9ePWLzwG_i(*q^~g;C_)Ef_+7-FB^ju=U6I( z9$`Z1-<8oHuTE#1y%u~CY>U|~sNESm7Sq?U*?Q){b0usWGfjl)c0spq@_!hvoiMKQ zB?WJ8Ar^fyiIegDJEZsO(zCB~4&?0i3Bg8N+2=f)0P2mXJjfo!1U#vdc$mMMHDl$w z885I&u=x?|iapENVV+F}=VBi+^C8X5Q(%8$WH;<}gayjX7spkw{sQyIiqp)_$x}U1 ze$*ESz@!Qmj6c1{d>ESsg)?KCK*0@`4JBW)en5?_$2Di!WRB16(i5t4sT-WA3DjfE zdG;yKrvF>*Kx&_Ynbqudj?I9y9krGf z#~Go@$(b-EfEy&UAYAR_Ch+1S`sH%CdAwmLR3&m_aaklchi5N?8pn-=%&WGZ(9O+_ z#L!0EZH}#gjp=oZ5E^q6dG-o?H_Oxs>G2Vst%RO0a?9|QL~aJJU)9^$cAzt>cmhp5 zam~0(JS%|8@1mWsGKTAllUs6Ycvc9NUq!WnN#8_S@%HVgAO-~;x#>9jPE@#8Rj)#6 zM{W#0z8A$Y@M*;vprAc>65hHW)e^Hiaz%_4;rj2R#xQ8ym1_j)52J!{Zx^l~$5vzE z4^fjCl=b1#@!Je8p26(jqI@CvQPl6+fL()`12~bPA%pv!W5rn6gNk5MKkfpxgl}hX zSqygb(RQ3UkXymAwU8=BH%9jm&QGS!-fD*LSv^>iKWuM1|V#zpe zB*!)Y+wQjcImmvIDKKRLR|xZ_af`W4$?)*5?J{1=1S%$S#c=-9I4jK+A>nvtE;mdTLj~QT zvGAm=%OBoZ%K2gcMVy@pTaM+1;KaqGCUHHn`Z_#Z$Vi5z+{ZlI3hGiW4}R~;{J{35 zwE>=7!4;ZVIA*NnN|_uc=+^pZlTFRHG+2_J=X|_uIIKewyk&rw~9HjU;{^! z#7@YXAvmGxv3@izE73a6n^2k04=u{+kc*#e;=3D z+Z^Fe^Q;Ue5Ad&pK1VrZ;QT1Q6BHZ>+zjp+f#KNlFxQBcS8zQf=E9!uar>FLgBm!9 zafyu+7&bl2o#)sgj4y4R!XWj%=!QVIj$!x(?iOQ*QK;f*6qm#P_d>ED9c+oX?h-ei zmOZFEz`X#e%lT+re1)6Gvm>y7Io}jpT;tC0>?p(!Fx15cHQY6x9Yf)kR?AyZ`iL<{W76-!vavVlqmsQHd-03X(HeX-|b?l5DOczvA`!J*+@>*wAj z=^ebXQQ5|Dr^pZD*eR$Ur@V%nWquUTPNQJvNwRk#MRvc1CvALtp1p^HuXgi&U@h#S z=nCM!@HlB13E>a%>?~9~*1v>#PJSWJ&Y=*=M;I7sD~7+qiEoqmAEMyasc7xr2sap(YkJI}5_jEu)WoLawoHLGJ#EIugyla?K5cnZi>F%n$>P$_{6{$ zcz9CGZ_`x$HL#BZ$bl%}$$_{HIXj)cP%cq{p8I0Y4Sb}5)yHYuc$#Tz%6IUy1fijzD}?=vVqLItH=p5if2Gtw&~30KHJZPO z=^%X`-vSzi^07F255Jyc_sYxo)h4l#peyKIw112fx8k(Ho#1ZbPJs_U<|8ro44)>mD9outW4iNDa(Kj7gW-XDYR@OOFkCl(ro zDKtUV5orB33pw8P?Jq3!(e6G-6#l>`HsMw8n0pL`cHtg=|A_bHIo%U1^w-LC?<|C3 z!EgLDj_I&4h;Ct3O;mS|rbw1XR$vUOcdA;nBh-y@^k8yAwyb-NwXt$V&(c z#zwM0zM$}&iLZDjK5tv#nFJ4-2~FX9KfxD!`3f{x^-!82kmdpe(wwX{IGJI|ST>if z$rS2BOfh?)HEjS_cS{bGt@2F-cUMa&Tn84RIkRy{&MbM~&)ld^<&i=^QH&FGA5DIy z(c$n}BcY4IbSgQPy8L4AeIx@OVj}HNnHe5O36F8wUA_gwRp$Vspk$-qfb8vpt@wZ- z!shF|h|_im1w8HDs`m+z*l@RSm1p&#dXEqQB?pB}u)QT@;D=>GGS3=7H41Sw`wDtE zfyC~JLqaLf8p6`IgjtZkOK1Sm$A!7z-X-{8%u(SDSxgjkzXm(l|?H14#%2f z%q8IyCdVz9lOpJrXphLz{8V_+B($Ib3zdHw8^VA~LIVDDQ!sL(w$V?Z#aPp7A{4%2 z3c!#%LN{4#CFmA)hnvmx@ziyMc*a`d#fO5MV@--538TflnHOM|p4+OiNL(z4JN(yj%a$110oz>`N> z+ax60D z#p6uqQ8cY1vYz5&CTanlo=_MsC1L0Q(Za*N5IGd;bP`|2@B4^;0!v3{x>%pbsKMej zL4HxkMY1y8i{xew)W6!itDxIDOXIT{JjfCy$bLl(0N*_ETe;8Dab0LN>Hqv;qTIWv zM`s%4Vb5Bv0}QIDwXGp-wPz@gN34vXZ^~TiZLSVj_^Y%|4Z5mG2-t-r!S^fizhiziziNiv~*=9zIsW_;n_qKu82=~Zc-DtT0?sUa$ybF z_nNp*i(=e-O$i5bWBoCuMm)!JQ<@fUp=B-bmUxfnvW;-@zUYs^cf>oqkmF7=Le95h zC`R5FnJm5_==M;c>3LV$a8Y*R(nq3!m#0*xv&-#drMSTFJpv1S2&$mfRshB6nyOJh zg4;u+p*+PQ9&Yqk82Y)S?>Ux-4@>=%7^c*d&T))H*yc~(Nkhpci-QE+_TFBJah6+} z!DP?DsRISwH{QA6Y4=2Ga0Y|?ZFN%Ms{|=Tq}VW905v(5u{beN0zF%VIVI9$26><9 z)9_#`DV<}BfxR7^h|N-^4|ui&Trt6MFzlC@dXPFongp4k7h%H7!B+gNy%fbuOPh>N za3_MQ4^6<9ou&7AwhSs?;es%%yEIl7hYGqIu}{sixt|nmqV@~Rnhp_kHB}D!l?SmWF(WUks4yYk`Z56(NIg*br%(7PzSXA&9odnG*T>t4RMr zPK+ES-Oyqd%ow@=myDI>>e;JW1$Xd%wp52_MOp;|a7eE72G6ffY6h+!gX&}BS<+?R zw5EwWF)^`8QbJM^l=cp3fJt+u3%pRA&@3J@LW0|2*L>-yo}vn+rP7b!?iDr;GnYxP zbNsr*(U3hdxB$w6MFW)G4zc3i6;eN*Uq3PlTq7l43@VVwoqP>T`-d=w;u?vfP{XKy zP>-h$i&Hj81-u9(?Iu*-40%8imHoa3$UhbgShPjTW)x${>K;O}Zq;Nhfvj|jBGla> zk!Mi~)hmN(o3>jb&uoO@)7DouWRdcikQlR9B6eE71l=gu1I}(wg=+-gtZ4+>e}OqYZ7g`_Cov%VV$vijf7h0ApZT}RTvi%9L#X<*HVQK z8--IINfEq!aH(S?O{Y=MMHJ!kFH%EsKQVp?;#EG5_L2Gzc}I7$WdSi!?JPWuhI%{ zZO2gA5Zb_7+b7=I-h%z-UE7}5=EQ4_^&#G($H7$`yI-rZ8($uxza@yH1>H3xl35(Q z<}Vzf&&hvqTk%hBEC0@|w|1-#w1J`pg+g65AjK%K8O{67$9K8Q2kAg zU4ZJN;Tv&tv7VxLaeMT?LO~N}Fjx}|_wdO`y;Wpm(K=3F%ZcZx_G*gx>K|jm3H@1y zHGTCV48EB{^dnGK=qUo8t)E64e<)1Tx4<^J`qSF(KW%YnBKBOZKfr7C`)d+@0%^-b zlc3jHy#d?K)t@l1@xT`Af08fnA3sjeedS%nTf>xeLxm<+6@9KC8?X0a|8BY-(`xz* z8D>tNMf{Q?@3r(=i1xlwwH#_*R=ooMKfF{AaT z8HOFuf6dreC>+wq%EAOe_ZUhy`93u;l7VT1@kM!sUS#4#LHF`3P|xXGW9Lf!F^;3y zK+c;XEg;hrTL=4J(Nh%V4rC0I`r*K9`ZsxY7cxdjei&GzzsIwC(7G~);zhUg6kYok zSRKQ3xN$Q^M)i*Vo-9rhba!)Mf0~?tuRqie;l$g-==bD@)`7p`{pB^k$Q!ZZk$!?K zP7!pkPt{7MLAE`B4!!=;?~z$;QQOJndua=N0<21vtuvt!)#T;6^20U#ndKVpcZTFS^ zME3J0cQ#h|%B^{c{Xn(-0!mx?Tc{mHCmQ5}_Jv+K79SiLC~xL?_G<#9cNF8HVuG9s zVPoTbaAAl%pVzbBvfNp;Ztm|Q`a!`IS%j2Bmi>6iDaR5Qze9CHxg3v0%AM)kA5hsw zZUZGAS;1PTJe7l9b?mEQZv(k^@oc#jZ1Ttybau)@d1ILqIQAE`NtFB`sH1!wKXJ=3 zRK;V+Y$nm(DN(*n-=4rtk4&4^W^%MF=8|i401D?Cy_(xC7|%4G25r*h@dDf0W-9JU zlQTGp{YiUm4o;@Zv!DCg4RP;S`7>>Zb3By37#9mMP2@s29wOTyHH;)0K9OXOoo>lN z+a!6Wx1a!@CCSlr7$aHWh~-oF(!q?O6`w4~)1B&U9-pC~HBi5f6VrMXr3Ie3VS{ zzv=HX(=up=%8p_nhE61XQe@+q6l9w8Ue+=%6F^LW)~F9Ci{&UR8KE)%H=c5!s;?Z2 z_h-m;7#)}1%$J+d;YYOG9a8(tQ(@lLn62dR%eOW0=4w^hp!5~l7jJBjaWSavA2AQg z7Rh;#-&!_c?I794G`%c=mxss{{?c&@sBPqVP%=}_#O%IuxV9p3c2HNy+y0?3joC_U zu~L|!6@GiawU>hC4$c()5kJ8jy4)*-fn@cOI%4u*9q<@+29hMN=X zQUv8U(Sr3)%QbpwA%R153u)gtOTQ<1mZ1AQ`)P&b;kH2OL%Am>hpr2lNgH(l-yVw6FkD;MAo;TpM+j0S8k+}XZ85m4<`RSqgD2f&pd}K1> zdw1nl9EEL#`!Xj>^=Ua}^(jmh#Pp@6IfCw;AdSD~VE9=cY9#g=Y<0~RbZ4~Chj#?Z zNKbxdB7qgShB)SDxtJAg=xM@jzsbWm)(FSnraM~8<2V)tRs9+_g#2%1{ow>dm7X<* z{nas(p~@A~15e6^_B@Nhv`=HYFzjJ5lyfW=(ms!Q19#aB19%EMW^|DbV7afM2T#YE z89k)2P!k^Og#34d=v3mXn4Y*U&@f2?$!XmqmB+p$=!R&}F#I zi3Gsl63{iv`Hgqo_w$bRM5weH!y$1c?d}WqhZ|v48$%AHy252BFom0e7Pd_6+Q@K} zV@Xix(35-H*g)=WGTfYR+XFSb^aMUQVn~EmoeeImA8)9Ugx__#->DDO@_Q!C#lM;v zI9??D1|2lrT&@qNso^Js5uemEQ1CK8H9SjeF_$%mn=AFXT1&Lg$=npWA2)8-kEEe8 zL@#Lrq6JiY!rQ`=OU`;Yp}m35l3RjO5tf3~?peLiP^W8Hm=>nr*J>Vl?DLVO82+H@ zTZVr`o&iS?6|M~wz85|dU+QaE%2P0hS@gkBGTS*8XdU;33X8EP-W^~F;#nFf7sBb> zb%=q^UE6}w=3C(6i3TGtci1<0KCMvAwR)&kRjut_kTyi04DQCpWB6!;fr1k0s1(PN z&-Y=ep$GXRg6?V<1rHOk)i~o$P7|I$MqD4rGBs%gnSIGiqGrx}o9PRM&hY-2zSgka z`@J8*5F~%F1%*Xv4V=I~&l?_ldu)HGTpT_fDw?~-Lz&z8tJYXOuKc(mQq)}8t442& zLn{qiII%Z1HxM$cVY?vv1%n^qQVcD<)epkLVd2!h{wCE_uT+B}qc*(x^G5YNfoU?D zyFPo~STt9D$q*(o9>YH~)NtYeYGn97w3hilYc0#GH5x|7_q?o(_>bD}8-hg5$N#7D z2-wAqhU$Z%(NF`Sbl9;r)XuP1KjZ6O#f(AqjO&Ge$TZ(8(~D5KAT(BMllIC~%h+dj zV#W@7YD;%D_L-gbd+jue3QYi}^1hmhYIvynLz1;$PQd@6xYwI#W%c7%)-)P;eGx?K*=3XrDsFwh;VO*dfGARJScV03`$IU zE41-YTSsryp5*^eI+FyDN`yb;JtI{E>8|TD-BI9vZgIA8Gc#gIwlR}0-YNO~enz~*ECnwQv^0bk5lSSZ^ zq@fUA$M-zOoHc$*pcQZ$r7UP2;=dd9ca0A?ig9sk;@$4#1a~sHZpf$cVwG{T1`C^V z#dwz7+=C5NJGT1V_zn{bs4NAP20F)Jw!gH3ZGXo|AU00&+G!mYG>XY(u!)yOXo1Jh z(D*&01>?RnUSe!L6vV{z#>_LuLWbXbZ4BYqYfum$lLW(!Qp9t1{?uSCRlL6&Dw1Lf zHBRDr$d8hY<+VogY&XHagF!tYzo3qQ!}|qAGtB?V*h)|Mk`sR#j~Gf;&sKc}-Ohih z^2zof2VVcn=<-I3xYEOdi6XgM8iF#6PRRlV6Yca~M?t2$gjLBBOc$Yf1DhGLMUw~v zMAI%@WijPS6jn-oEzSj*0VWf=eN6^gq#nChI}BYVlF7iPpB0IRA6obcVBMo0fB|0^E~KbfP!c^jNQ9%l@4EUlaqRm?n5~q3IN7 zd`CTFQ|pMj1qE7kCiNdG)Jj^k1#$;{PCS=fo{rX> zD@o2VSmIAuyAG=S+LMZ~`}Erzn+O{=uBpF(v8!E{#{&5fT{Vx8Cc zD9-=Hw1^{+NZKUV2YB&{X@On?bzMk)#-NvR*K_7}d6%F|RNdzgf8Q2{oqskRVb9xq z#3(wHan95M+#j2&{>e2pxDyxLGfkGna6wm@`m7B*VNJL%Hp(iYzmEAnyZeupi820(ew2$Ms8_!$+m&8aC_a+>0%?M!4E%P<)%;OK4`-`HJj&l1yt3M91 zear&h7R}U0T{PK0H&z_dla-9)zAUzx=hL<+&t_glN5^Qjn^l4KOojgD9D4i>blYJh zA3MPOm7Z`UE~mL0GjMm;aB4k4_of!fNrei|5{MUEW;Z8s6cOU?L3Xa0&PgK8LwJt+ z7OD=Jld*Y2^BMX^JCiaKIWCROU(&blAZxyv_Bn3z3%taA-?RP0>5x-p9tx@L3>SSOpOV0w!A z0R5^KrVKLQ#qV00{q!9713b(%yEMKG9L9DuUn2Q_#Eiq{Y1H{XGv9>IqRkDUYKJ)l zh(7~t?PeavagQJ^%s?w(ck@TY@lSBEmpK%74K(-TU}l0j1K;m$Zb23Ota17!hf{i( zgS;NsFOZsI&eNc+Doki@-iwXmHB@(W25GQ-h`B%@V0dAs`HsZ>RccflLSB}6gT(#z zRvWbex<;74U=sI`lu0qBEh05P%G>-O=sIM6o!8iWnsZ0b1o-yx@9_6U$=~QI`f25QEd!iOU-L#eo=pw0vRuB z$7tT(5DE1QEedW~X3pXW5>x6l(@|ZCd4v``QhqT%fVhTc6NcY4cj2JZ8|LE#j#2(F z_XArub7z{%$cWwNnunm_sF{X9-#5+s=;RO!Z<@z5oV?$>hGPZ{y<@K6;WV1tFeBJ{ z5@sNLqIo@jdc+)T5Mu@1yWTPIB3wUZ4l5pE4um$h%$I2l^5&A+Y7C9c8Jb|;=Pr?O z5xfP}FU>E}HfxKS&h4%5n;#KyxMr-`N&Z_`^U~*Rb=TBn#ovyYcQQDcV)lXbOYtLM z%EG|jIOT$QwMf{P!rNv-5Y_%{rby2-QN3FW-j0R$=o!@hZl3X9#GSy(ubI~hBEe)% zj?x$$NnQ%?yK$^asGheV{%e(_;zUddh6pWY!*%FXRu3 zY`K6{1?F-NrXcB=tX)?VqYfx$OJ8Vn#5@_wJ}{>j7n=z~a@4$^yzXA+#ZYyP$jUr( zXnC!fypTY2iI&3@`Z{2~594;3KwBA3YZV2d%V25B;K?3SKf+O&XUSp{LASrXCc;=v zgpl>qR0;$h)&iI*P?P27EUIZ!2WvTtm5G$b!H0s{w{9Rfd&e4|^RL-#5N;PNblggC zg7?f<@rG=$j*+HlNS z?6jn^BusN!ItfBkI&g!+?Ll5u>fz%^i_=hVv8*N&-rQSjFYH#&GRKRG*)SGzT3XW3 z6mNONiS>!mM&zeh#>y-OC$+S^O6Z*ORLc=TY)MC4oYw)^yv`CxtD$U77&C^-$E}W3 z2{W^!`?H5WIJ<-8Iy1y93sIXBCZy%l2c0aLT6~6&h030mUU0I&cN{$Vn%UsHZWagL z=xm8J3Gq$bV_;p8Wh9R8Ygxw=PAaRHjpl{{mM41FnvPg4;mnY@F+?TU&9Q&gyUr2> z;Tb_bcrwe9$}xBOD9cYqQA5gTxFLexud_Jed_w~Nrv}DJ5VgxvUmGS(m*lF1omn;U zS)mX*f=^By>K;W0CVUconnC(5ORK-ZI{G5T)DLki1#%|H3E(WVgusWrDXc!Zv)m9? zy&RVU7dy*gkl#%fptg)w4d-;rR}yQ1!dwe^TPozL+v*{G=KQ`+p(&|5=Y^l@~xKbf=CFK0&VZ&)la0R zkZwzdsT6N1O#R#4uFHMYj)XOFKD+y`8ntc-wl^)y#V+ecs2yk(usk$$Tryj2Pdc6P ztZ<|@aNE;|5IfnHHVoVCu~cxZb8!=ygetTA<|`1o=4B{cLF1m-4aGU{S{8HVRhIW9 zkw7`CpR*XaroigZP|ab`@3Q4hu?JO}4slm4E;!+iYYnyG0T#@@V#$;VLQ`F1iRHy! zRHP3mw=7-2dCOwKLANY~V(N>+9m@rSK-1h24WHs|H;{m?TpSMj!BWat@8U<6B9SmO zuMBx6hXe0DvIJ_GOTw=f3Z~Z6alLi*3ey7hD_kn!W!7p@(^L$K_4%3^1{H;=1kc(2 zT#*jk&H40U?1k+OeJJLk%Q&1WnjEKk$Y|60zY8cR8Y|BE?~M#?(;-^_WWti%7+ zoX3)aLYBg)Ho zkE`I0afi6Q+zxIFw}C6>3b^Ie)lzkmmM$qKdk7yX*7YP44far0r|9dUv`$g)p)kJi zi-*<#;f^Pc=+_=f%N4%##1OsaX-xEzCyMA<59N3Yr#!S|3dcN@)+yv2@X%R_u+Kx8 zox*O9ljv3t<#q~NJYhs%^Mn#D@=%JWP~f2hAz_7w90OsI$Dimt4<&aB(>!*f<2+WP zV>~n|2!lN|Hw*nebV4Ka_E3V0(9J`Ws?fnB5pCCFKf|I)}9IyE>a_tvZwFH!2~(_^;JzL@%jRiJnp?6GfHu#2->$ zB)Uu07Wz_k6wz1JkwjOinM7Y!hZ9|@4kene<_)3C94#Z4pP^F3n4hY?KyO1g zB%_Dg5g&U76aC#Ykmz?FYEAq&&qED~?|FI?J?2R#deB2@zv3PbEf(T-Pj{kgJl%*c z@z61V=<#$SI?vOA=p0WwqSHNXiH`N863z0oBHG*2l4v(ibD}LhDI~&Bm1^eyTdsH> zRsH{xEUp{$|36v$zvYTY(01v6NEVm+c_t8ZBXLiTPcII9UNw$}lky(N)XQd{k;p5=J`tA`=iS5`0z3TFF%j8UcirPec}mt32(kG6Hx4t4*|s{!LU}= zd|dde&py4_pJEodS`8EE;9K#54ZG=#dAVYxWB11qRv(2DnWOWYSqUK0 z&^kt@MZLmpZNrP&2$%(DI4flyC0Z$rG#kr)pv4znZfISPn^UX{C|Mb7d#ppezDU_E zOBj@;TDQqBZS+@ZvwH05_K=p|6sU@bQ()6>*{<>5ia~Na>sdClyuFon8S^N>r!hZ2 ztMQ1&BU`tMZvvI0xOSM{-AV_;dFUDt(UxJy4C@0)Ab_G56Opnen-C@WhZGmQH^ACN zRJTo02jgD@taR{5v33{7{SV!@Mxcs7v zCU&OvO$~7=jMa3p7=?-2bqPfTt!V~wePoC&v`Bbxwzaz#3dt{r zAxo_<;NZDdN=+CJvA@-6M`c}Dve4R#8CI;;se~!qA$a=&rZ2YcV_LQ&{|fl5kqPs- z)cV*=$rHNuUiXWB8G#jRtZg{`N-9(s6N7Df1QDn$W}9`Io>6M`ZtHk)_3~(S1d{HX zFuABGM$IDs%IoCP&y%rYueA+h1?6R$de=bVr04)jPNz+*yS;55!ySjMgXs_w_PN|` z!MU9>5#8TeU*hoI2`gnjt;G|y*0wxc{8X6%$4^;1v32NT{0$DC+!UKZ@cY&~Y(28_ zh(8!UIcp_c%WIIOrz0EZ!-yIVdo_-rtQg{a0~CJiKLNA%MifxNYOUZ#JR#Vq>Waw` z$Fw4w@Pw>=e`2)lga=oxzJ#aT{F#-K8?RYE;R#Qfvy&%qOpVpwAdMn8%(0eFby#rM zI)xL9sfO3-WKBy8x^E>M=T?Y6Y3&RvPg80(yB%0hKahVWcfb&+!K#c7WRjL!$%yVDGQ) zNXVQa4#Ct7wiz6{<7^S4q!RpaZ~LcwmnGVI@)Cc5xGsk*LxAU5V&$dh;C{#-$|qx4 zifyMPssvsufzlv*Dr`!%)i?4dm%gMD*sahzm$nCcnk_}*PZV@e$3sS%ZJfj(FG*F$ zL527KBSjt5u^89Zc9BW^VQTUyofz02lQ0zT@9h2OEnu@b7uM@;`+{V7`}s&PW5d3- z&rKX(0crW+ba;?yyGsS$p(((2K-R01$rkqrZNQWPkZKQTgspRI8Jv+nT`))`Ok8O* z?NNL9Vw%k*@s-r{yKo}M_9OH-(y#?KnQp5hCBKJNKTy&Jx@X%yqOb4MmeC*$?pIU3oDv6LOj7FOz^%5Oq|J{Zhf@euvD>y) z=G3Y1@R04C#9uFKtL8xEVa=LX*0xi#!CkKX|H-TU)G4^R+%}x7`4K7OQ?Q|phV~KL z9jgB_)|{|8DGT?MZ7}A&ZCfC5{AW-a!~4R-!?qq!u{0#KXu`M&_}yunAC>p73lppbe4JEMyNvm8rTj8&w8~b8!qxLa=$Iszuo~Z?QI8kz*%FA0TX3_QG+;YwTY{TwZ9f=9b()~t_H@si z&XguVOMwpnQ*LXFd{zFZEmYvt=~^7R9Z-Qpa88Txg_b_PgSF}A&MOntT(mIdAt&*- z>Be1bT~Dc|v;JQlT`){iZZVF(hs~mtGYroglw|5M-@=;5jsje5Rwk3tzdM*U&cng) z@7X$n?Vzur$Tn{>RD3N<@b>$*cF(osHHsg{e@~efiZ)~)6je_Sgo5+7rcd=WEkyZB zFR6qZ`@Oj~*hb)qNTm-A!H1>&RGO(z#cFq_Y3!3@)Ib>_@%M?}AD(jBqoHz`D*X|N zM>+v&tR&E^^hnE1T{GUd2w!t6FVf?m;EtkH;ORuAHGTUTY$?hJxb|Z(z4m3VUkAL{ zjDE)PzhG6G@->6u{-F(F!Y4KZ2DMi1kn(>6#@WN+;s!?%mbX_{5xcW#-Y9q)YD{lr zudBI-e-yV?2^P0UM~Phaq1u!oV|Wkc2Is^7N{{~}duxA2Nc+g{2Yb#be?rYA3WogG zv~-z4aUW$O$Nz;nzj7BDe%w!qBO5%%mcM(he za*pAJEG3B}KQ4KTG6XY6DX;T{%bL?VdNE{QqpbM9EOdVT?N}v%XVixmH+~)8n5YcV zQz*Y$5-HdmVjm1C3ltOP=PD=loH_^7<|-$6f|Te4dZS2`oidyAm4yW4#Fo>Q(;T$% z*cJHRX`h3AmME)4y*gK0>LzLm@p`t(bFnv&#st&42`WL_4i0)=V5y02Q_Nm4$Ga&9 zBv*`nwyr!+*f-O=X9^X1_lyNzsZt05x>_Lws1Fui(l{s>qBzlq@d27=B3QBTit;9< zEo@Zi1EbqRXY5u1@9j|nB|5whX+Yjt z^Fzv6El7XU>|TN|A5muUi~^q{tpq7Qt`MX=0QVhH=%qJhK5iduQ>k>Jjy0b&whe5J z>{H9nDuGN4+SOH^hhgWGn@rR;%E1u-Q=9|ltyJ>=^Q{I0G5$m94U{1J>>UOb4CoC8 zmC*QOrGNFpyh>{;0$mJIY!qZv_OdXXd)k`95k^9x#Nlwr z*{rP6dQ%g^Mr@_m2(;Lun;W2l1cTr$W^!u`v{Ql%3cu)P97b=;-QbAHm?2 zSxRfjeiE$5T`v1xNkgA~)C5Wk>@$ncDhgCoQ7$6Y(q!XE4*~QV*?-`~So)q&{G~7& zOClX1JS>W{&!?Q(O6E9;r`&dmUnhaBv%ML%Ote#$7C}=IeF*Squ~UXlK}(-_1}jeZ zG=d@fO+J*J9^Mq@+U&uwS#s2;7aZ83yrrF-{AQ5$m(mDY&r{lhd#qzOq{rB;*kZm? z$-w=H=E-60?As_ze_;Ww317Tpw?en+^u~&cUJf(Ven|rFJ7xF9kPh~5C>I#2uR7vj zm9JwJywb5PZlYNB(wtc zq#OnN>eaPj>%R7RvW9EhovW4l4)+YT_vS>3Aqc6MGspfi`978Qb=Yu({VHQ=;Nl(h z-j7lC*9;m$?MO1xk0`Z+5`!I@J1n%JPkX2gvBw?0;b_Wme2$%xfh2N3pB|Of6xc6n zrAs-H;uiwJDI06=&6sPa-D_vqpJ%U+wyE}Iq}X+K0#BDD*fU|(K)VVTZ`)gfyu?lr zCY?YzxA!6%z=H#I(xKq{=nN=0MlT!5{5g6Ej#*^i$g!@VzGEXrtg;Wl=}YYmWICvjl>`k5Ds-ez53qeDq(kjkhXu1%+qdwnCzecf z?B(#ldV7Y*(xLj47>yNWc7IAZyKApRX^Z_Y#xm%2P)a-|ZMA30BH_T^ZlgTg_7mO1fJ*kWT zhsi~3hW778k-$%H`TwKMGn!O==raJ=L;C@E5N)^oy}8XaMs&hJU_aSMKuxA&0k-Z) zlHrqg?E=RS>eL7-$8mMwBBOUI3_fg`$#CCUdmp_(2)ZAhIx=L{IoiT7WNV~{0BmQv z|AU7H8V?j&6Nb{;cZ7c0IIBa@pHJ-s{TT*nF?JOfU9r#CUT>ysv4dnSXX4 zr0$c**a*TD`@~|w8oNa3Lgr97Sas0eq*%2tD!;Fd(otwjbv)*f`_WEk7^5NQs(lr@ z^X)r1Z260Qsa~|utA_?@(j|DiI|cuQu~_QkATX}`M|%|%%y%@xvK+g>Fq3yQ6lrbI z-H6iGNi7s5j9)X?Lp^xc+iJLWqOTBXc0*CC4tz z|4W%mi^h1zCt3xR6BK9fRlJuMpk|}P0@5wfp&@uNJ#~7@+Vi$?Ub|m#|$Qe%aT*+xsdO9@# zO%1g6cXVtO*-KbxaZKSf-M80@kAmP{jyr5F?C<5E)4p)~8wAUAa+LJ3C zgYWHkB$LfJQA=nPC^RNe0x`31)=CfM%Xd3&3nHP=s%bPZr5~j3cPNI{Yr;H1R86YZ zjV5YUq;O&({d5hKZZ*-~@{ohVM#U%`aZtbyP&lq#*TUwL4$8BxaD1RAIA_)g*#)Vp zl7)-UIPzqHkYop+9?;VM$q$BAJ2r|NigG<6<6wQ3ITERC8loERgZJ)_BDPIWmO{9A}wG5lJP{RDj z!;wGyJ7dpK-$Qi&P1uwcOKy|X_d3rByI@tc)CjWQ^zQ-<`8pS|Lp@*00U$uyUtt!)q5c%Q>F#@%cqxZ{S^nwRH{EwH zeM8(g+Fzs>()IKGjfx(DvO>R195ujq6Foc%H`n;3VTU2U5A|V-n@oIG;QmEMI-SUT+xN2f0i?d<`wk~mV*U}|5rpPUk6G_+?f>KKz2l>*wy@!x z=`%nIOz$n3p`;;IT7rOd5g@A~FAPH^JNdOBhm zL==%C2_hB{-e;d6UhjS1?~m`^Uk;u*=j^`M-fQh=J!>CHmyNgXB>gb$9-J72L^K*1 z=`Ga&#@B7GF;QVqeOQ!J02trfqeP8|!ffA&zJA$Q+(>nxVq8HGI!68j|IrpzuNadw zs-0+n52*7?#xMArdB$)fh3aG4YqDeZlp4=!qhY=TifmGpat`R`CIKimO11gkOvEPpFqL`X_pP5E zNujFZ+LsI-1)~j4%1ur7`|+*QAw@Xd)0UW#XGrkP(e$MyJozEo8u_!DfW#u z!hoMKR3FFlTN^=(i(gNZZ-UW56 z8w6@$(K?`sdOt?T1ENjk$gBp*eorx-ji-t$1~%rOV@(@$QW7t#y!kS@6&VMcepaY{rSuz~ z&Ae@hsW0BWMsd%^`Y3s7t3>Kj*UZSHhngyMniR}e7^(jrrzT#VZF&IB{1Zz22USls zx1_qylC|vFXR4L9K5QN;fhyBftWf<)mp(8R@SItuX-NG#^&e<-)3S86iIcL-bqaDH zHhnFseS`YnXFSFSkC=e;o6*f&zzgP^o<>{##lA6SILr+E%(Rh8?=ibM^qT2gJh(~M zkHd1-XQ633QohC4zc#7CfB1&!PYGJ~^^+znG4{7iKMCtob*+gxqs){g(eQUo19|KU z(;E^DQ6t_rVQ;h2guM;imv?B>DfS&x9*t<%bQpWrn9e8wFfGzI_0h~Oaf7L%Bh;I` z@rr25dDqmHzguVW3D9ZfZ;o!1#o8t!2Ppp<%5C3t0hKK?WsrBf369Y3;J<3p2tuTKjfg%^)E)T%86{B&{t{St0 zUsIb6IteFJP0S+|Y79`=22z;xe`1g}~u{90E+*vvHHD|V3fNM7=tGn}QP4sa-7Yx4vbxXk$q z$w%p@b>*CK(+rq&ozIL$@^i`>b1#Ye#+zfbnsyigE0gfC`9*nGK$*$97>;!FejFLVlvUVfkp$r)zLg!ApuGGvS~57A2WYf za>R6y+=GoRN?rw7=k%`TITBFiVg%8xA^2ePXmfvwXQZ3q|ImVV4>iIT+rtc7Y)hU$ z(L6~idDPg4_x3UOP++;3pJkjO$peQla29gnK=T(89FtWUW~g(?i*j_(8dA;KJb9@3 zWhG5-Yd%P>e#Wu9p`AHXqHp^fEB{^c6e;yi*$HKzO@9nD?&i~Xn|;!qI{W}Cxc{z2 z1dso-4ud4=Ei5H#e2!n$YRbO{vuSNprQ-rsQE`D(k;6!3)cAulWa2xo0J* z?cdb{CHMSE=Jzz}_UONt8>|Rf=ghtD?CgwU1`E?1PPBcC%rL!9GsE=Skp}gW`ccjY zx;=FHM^l_UVF(lE7P&u<=}uWcnVz7U8|u4h!A*4&`neYltY5!qa?*ys)X{Y7W7xWo zO zDB&|yVbnZ8UmZ2upf0Jqyf>+!gDUFFF=7P1Bqpkxm%nCyR1%gpbyq6wZh4P4EHs0! zryEyZGrZI;fJH%-Y zd`2lfNO5M%JDj{r2t4>K#_~^pnC%K_C>6bJZpM}y=8H<{A&PTbE>S1Hu_KioFwf_0 zlBJ&lAe_{hDj+-x>{{B{%WbEMS`W@G$2Dz9!=HC~s3PDq!FQ>ZiZ`e%L$q*iUjDP+ zN@-!sMn2Nm(pD)w!ntF_;K(szmAOC8^1cFwv)u8f=?tM>h=tx}e22H1EdwOj(4^Lh zI8m}%B1%}!a-T7s)@Gf^fW`0iMWn)a3C1~II;`w+=Ut%85IsGlg z0`paAvi$m=UElFO%O?_yY-QJx2vlzuuQ_6PP!vQ{$uB)%nIggHR_U?;NcGQH&^>Iq zE+JAt`g>wf4Yz!!Rj4Nc&|tX%gLIVo8Oka(PUh*)SR&fSLTOVd4j@8{;=mn%b-Z@L zgo5s>sAUak`8Ow7Li*>5x)h|VqE^hf({y=i;KJ0;1I=K;A~Bij?zg`|a84adWuxtG zN*iq-$z!h>NGf?Mc?b>t-Oz!uUa+h9{TD5#Rnk2?f1V{*seS=zOrz9I_D}!I$5o-& zy3pcNse8hgSh)4~;_-_uvn2hD;+X|KpnV9Ji|*X%4a)&Zkmjjp@}-UT80-;ld&(9_ z{Q6SMIJK0|nadUMA_XxJ)zqboq**?vna5f?!H5(plwU}igD0W;gnRJ0uSleCtm z3WBck3}+v-tWdz3R{m%rm?B16A$mXRV|`63UT{&E0@d@1%U9zBtIC2CtXDbiqQ$S| zgfA^8VL5{WX>F<4lr|1Ln#)gHMuKavHpYgz|F*J)oI|mDf`*&bTIv*1(WdeV7Kt4{ zSk5V=*NO+VEH6HFY0uw!&uGy7p0@x9R$M$Nxtt1*#9F!HbIWK&@y|C7i&88s&aHX` z>Z{dIJR)l^x2U%O(%6pyd)p`()Fs=e6u?vA7vVMh{GC=RQNv=VCQ9jME#gHtEE6y| zU#F@Y79&AfdYz{KWx1O#sH~6x>Nkp0)3c~5S!t!yO=GjEYKGE485f$A;lNpam&BhK ztQ{nUdI{yXi-kYwSvR=-hARyM0|j=titZ*K#jI5Z=u(W;q)@-f!wcuXf>K5YU>*%yf52OBQ$?N{ry9?xALEq7*gojVL~AF! zwvw0k5{!1*RO@xzzC$S&T8Xni>v^4~zpT-r;qocts98_D{;;^{>IpFA+$?Q!7bX5_ zN#jvnt%ymZUX12ky(x7}dx<-xTVcmjui^SftRF~xu&1>nZr5^swspTefJC={x)DNRX{)e$dbxwFIX>2 z#e?GE{Trn&qnJ-DlQx}<4=9SCn)nw@yJ;OKelMrxS&4w|jk11@#5YpC&6ZHyX-_$g zaK;%y-voW@widlsv30x^E>v&|IWdy!<0LSr7aq~+H>G)T1cRjB$KMtznwaP+u6v;| zCq836jwH8kN8UfSqJmEDONlBk&C?{=AF~5_u}nsRQ;m` z?yx$o#mqn29+$Z-qff!qeQ!?zOfBdc7@hSNN6`)VQo0fUugYUsa5Y8)h;#VF>Z3x z`Y_a_9?DpL`gQ99fKm6}Rb1Z6Pj$VN%jgq@Z9PBzmUV$fp{}HIgFU`DV|g@pYGFgS zrF2&2NX3O_eaWyy8>O7IP7;l}kCqRxxv1g~>t&8zV}&tSy&oqQiOq^DhUnS5&iWlP zKakQs<*rmJmuye-v@+`)l!kZQbC6QItbe|ApcK!JPjvZ?3Wq#LPcJIrUaR zL$6xr{X5pcX_3iMN_owSU^lH0vL)lMnATMG5f<#EF3L%NHxHwEjv=Eea(`;UMyAAi z7Z0kjZbEL=ocX?WgH-(2uBNm{vW+G*NU%6r)_ownQV6*(QF`aViX z<8vsLKwH<`EjLVTN}H4vJg(gtC;-;`8jgE0DnxUE__yBL3oZFgd7A=&zP}sa@F=;L zGeCnnD0_*~`+0fWg29yhi}k2ReHi!0srFC|qHNp_uK*OC`U}opWnG4mGF;fyvbqRViSzcR`8C}!&S#mn!*L4S(vBjol& zab*q^ur{{uadVa{?6x$Vf-JSBLO^e}z9MbyYy*3q`W)9zS2}R6yTbOkM6>R(%}}U+ zq7lm#zy52D$8NRi9fjbaQJ*JYf${{`^tSnt@&)p>@PY2KzwIFIE^>NB3hZx%V^Yp@ zllyF)wVI)jzBGo>*G&~-FZgrW)B@~JYo~}lULbn*NBmvpqIWD$!j}4TOBWyUNs-xToY#6x~oDdb}+uq}AS+>T=wx04gC;>JaWjl?#E985{*;WKYDWlQ? z1E{y$rrA`H2_MLu(YE!pA+B{2rPR7rwB8q|<%#2L3(UOyhy`l;HNR z+o%YO04gIzt*=t`WE)ni9Gh6Je&y5&wxc)+-J*D$-k+`P0#Jgj4_(f&A?in#vMEhn z9@|oUcCEN*L65CVl_Ozqo~%@H*>g4+>kdq@b;aoVozma7?WNM8;LJ^X)Ycta(ZMOH z+_%8C4F&pxYM)I3MyH=m2a*_D6P*g^e}HX;=WkV=HN-XZY;L7S#BosHprSRldpYeT zTR#l{{Z{ZPJYXAqr=fCa*>*)2np$joQK9~eQn%SA@pB7p$93vq*r$&XJt6PYZ@t;J z;9(#{CW`LrA}jOP;R_Q*nY*#d3E}mc=?3rIZ2MO1$2Ahy_qR0z9mzUdSGv{P_TJW2 zHs~4(&MdWUP|Ee9ntUZx@hFXwGgsPF66@C6uEW;IxewXiMliHpViG2AvVAV8*|)|9 zYdVJ6hayFx!)U;VxL*7?;Y3Oo_U^QuQ2|WgI{_c@x2kQhf3?|XJ1R+fdg?fY^z#R7 zIC_FF!yYqbn(Pg38I=5u?Hzvgux*k;qeY61fh+pc(vnlEY?Ea#gf$ITfW zj1Q_LHIA4yF_iA~ySU30+kOn`rxKl8Pl@`)Ql-ltq|VoD<+N;6K+82XwtxV+NxAxA zB}*H(p}Yk9Lwxaj+lw;q@!@Agai&nT*1nzJyJQ;zihiVS*KY!C^)k!0`P-A~e zQa3~LcJ5nm>nVwbdK{oUgZ+LLZx9$>O>l*2J-+%)Z5TOO^p*oRQw zNm~@!<8+Ih-`h* zAe%i&_8LhIFE&k(e4FfHKD^NmCHikwWF^Q3DRyQuP9=xh*9j#|6XM$W76;e>?zjKR zWzX7wlt(8b0?bVIkoztBbc6%3f2&g$Jcbirc>;09Z2LW8e1otsO4E`~Z?e1Y?nVjY?XU2WY4$g@nu5o{-4Ya45-3Ye@;gHd zMyaAc1!6AOJ{8U#i|u1YHt^-sr0~tz$v35v1(qA4s9R{$EA|X2m3^b_r{VMeru|Jq zIlJ(#`SyDyHGI@Zx2F7@WDwO9+QAyqhNP$M2*I-0t^&A?XRfv9VYK2SJ`tV^X*yB%Tqgnw zRoOv>+gaekdkdoLaa7pK3#Q4P4h?GKbn~B|+x5cZJb$j^7s{|`0ZV;V!<2JUbD0Y7 zcUM!!8OO7+sU5*X*{1DXceQET0g1yQeVzjY^@JT^KDvRkz!Bz*&z{Vk~n zXAW^gPE|dowNgzlS6eZbdUEDaH`KVaOU^B@t^8@fOOkp~_Bhv*A_zq$SKP4Qql6tU zd!lO%$DXxsm1y-ycYofXa9mO=3dZqLog-P&_t_O)Fcv{Yyi_^Ekwru2#5AV75_dFb z7#wg^N=Ht8sdlb&7SC<$n5u+zbos|lp!#DS;7aVz*BC&=L1D}toCX}qIe0UKN88D}@D>*~6~ZJRp~CGI{d@9vt&lUg~7 zl+s`>@8v>}$ck3ZQ9LKrF}buFVWx@zJ?yJk_^NykHcsk_50!K#+u&zr)f_4W_3^DcKL zf&A@=Gl%M4c3_A_X&$2db=nY>t#M4Cv@?zhE}Q4L3mtgT*_V&^btFmB!(9G>V<_n5 z(AN_6806S14Wsgdjvka#qG=2qf+J1`*IHH7r5U1(Ps{sk1~q3GeI@)t2!0{ubYYmo zsD_)S=1~S4eyLLM{bOB-(u`mb;)|u}OFMM4C`}e;7CHeYx%q2iYdYH9^$q8}?8t+# z&vXjyOOwr+3!U2)ynCFZmq4ObpGwT7ym#F#0WEYqrj#Dz+?)0V*jK&fOjS_MCy7av zwb&uxj+#+aIntd#yQeyvaQkN*qcJz(h%wD!Rj4OcgbOCpt{IMThS8O&1rxACDi|f| zGlfd+NqRcJ%9%_<@*H*{IfOIn6Cz9#0^R)mknwPXQiA2}YwbR|^__D%z13CIk*iKP zW(nJ~W-Oi zD;$r*ZpP*7ojS~>O!skI2Rc1+Ub$3W?p)t6IPx-_H`uk-frVxgrEbtBQ_U9VWWIUQ zu2686-gTe+#nW80+KtnZm?h47O0nvN=Zv$R#M?GG`l+O6xqg&02Hm>O*@V119cK{B zqV^1e24F74qTtVJ?x(y{j_-Kf9>?pF^gLI0cVS5Z-Kq+dh;APUf6q8x08s7Ycqo1UH2Hi28&m{y@XmqFN?h?T`%jP-` z2r1c&zpZhEl~NwXJ?OrCM?|%fJB`q3V7>msZs$d39TO!fpJMxJ9;1rC5=+V3#xagZ zJnLK_XH>vx4?D4-Wt2F|pzTc1fIjzbM7Suq?3e&Y&H8K3TzG`{3H}2+8dbj9dlvdN?HL9Y-6sj48Mfhj|LLyWw#98WyF*x^Xe$(-_ zTA`WC`K6ksIB=dCs~3GyLmAy2Z{gI~j&(6g^AcCq+24Ve+u!1$x%l;EuDog=EO$fM zKT_dC@#qz<{M{Y{wBYH)rVUdsqp5SFq|nU60hGOt#wEKzf_u<`^GmD*MCg)nTdhu& zT2laB|3Vt0FMJUDqitSFDRq>KIrSp`7I4wS4qy$c;vFwS2L0*+=3<_6Af*SKl?qKE zr9Psm=j2n4YVqx2Z(Udq$DW^(sL zXN5wcDdwUHj;91Ox4jY1XziSZ*A{Y7uJ|pj)Oi&Vs4Ow`>9%Mi<+N!UO^e$*$0;<6 zI8{*Dd9osa0X1y4h2EO!1=J3$YpfjLm~I>$|p z1$DD`jPpTyb*JO$I~@QhNR;LcD*qlcf zmW#x%Z*uuXrw+^HImdFaSUTZ>9i@4T%j=x+(to!;e>%)LN~I`xmQ%BwjU>Hh*%p7n zGYE7dAcwuF=3S?gD^@yJK~DbyCVM0Vi<_pDvky3zAwpET18%`ln&p)Ju`~b9#7IGe zj&C&rpQ2g8*@v8auo=AF{O9Fzzs=cIPE2AU(EdF)p2V`*x8c*3lwM;`7oyEBiTUw1 z+V&mJ&2$Vt-ae~cLv!>x>qwu)nDNY&b zJcLpAwPTH3_OG6zF5tTUvac17w^DV6qq~?*1rXrt+$of?$kCRwN*pNM-}0@|)(R(#8=7rg z?sE210Q+Sxm$Tgt)0DG{yt7?_J6lwFe<<%`E)&+NpJ~je&NmdA9b7-a3Auw#n8pi^ zIDyd7?Bx1P`L`5D36+j>xETF#x191WuD@SAs`}Ug+sII7oc!x=z-0h>xKNE5E zbkX@8?)KC2G1#?alsU#=MutV;x+u*7T0S0I>a6Q_*z5C z#;MVcB_IROax~-3e>n$Y41dJw^>&Sdhj(|aY*+{h3n5I$QJRk_{a1SzT0c^gNwMDm z@|gC3+b0&t{+t|kU6!b+)>Ws_9OUIE6PIHuirr9^-qjkZegc0P`yjgXq$`HHEpci~ z251_Sr2v}GJB?kBp!lD1y5#VqD2p9!z#HH!qv7|tT65>7t{QBfr;^+18mrM9-ZQvh z3cqS~M3 z-DBFf8be9C3#TSgnkq4?K=J$^)y;U!RR%T8ohz#56E9U$=4lPWU4>nrp{U2i%zBG{ z?BjZkJ{aq|!O2Ok&(K$&iOFT*_2XSQ7MZxrxlp0`oU-RD%$$|!Dpl}~cCOy&&#Ga=_7~+BvJ=&=JOgG=k<4Xlbd}N()x<(!!U6^^_Z*ZPEq7menp;LU&GPHnaOE? z*kOKK*2=YzO3xv-EM~<`aDTZ96q;|Se5@vwra$S5(8OeyhOiLo`J?`>PN>1RT;ImI z8ha+RGozjJHHw01e0r$MgJuPQQF98RJsq9ofid~t&9_!AGN6o)IZl&^^Teg;t}Gyg zuwx>$+(w0_hN|6aMDZTwszF}0l)lQ{h0;zXUXh!O(PS_nX}(KE;8NJM_VjmkSBi1D zS+vC&N`K!SA@3z^mpeltmUX!<9~A28zz$CoJE)xEy3xA-?eu$vTsn!rvz*@802GU- zi*^{rz5?&>lpbC=O1KuDLA5+Yxa1_qDS`F#(YLDR~B^L}K@Gk5^qiQMegUJ=zJKZYagnyKGw* zxo#>nnji4;B~ZKSg~oHIG^N+{I9V@Q;u^2e{LJ~&5E7jmFLhl*F)mZl77qd>mb$7? zf;uXn5xa`lu5=B>T|Ji<#16u)W4!ly{$!2oYmKI0CbCuB$<~2vYh+=55gA{l<)0;w zAnyTRODY_!9nWb&1B@Y}9GtVm^%}DKl~Q92M>%i2uf2i{dtJXPG}kD#nfNKk2lZv- zK8SXO<~Newb{$ydc4Omx&C@|11HY5>uIm=n{ggaYG(;7nSRqWdwXOupnCi>kddQUz zcn*#~8kYFFGFL0veVEG{yML9Z?4HsPr*!7Uf;{tN5nbr+@%uPZVykBmmJoE zi}RN29!bOHhXHvJU1M+Lo`+Q)+4;)SZ|gn%WNff@!2nX2lInL_auV~i}_WVXGXM)80lY#0)Q7v+f>lJ zAB5On=sm|(k9hmZ1!%_QX&$&8)*0OcsPr$5mv6LnYZVdzJuSVb#Y|f!iJWB;eXWTr z54iFbBIBv@=Vq#WUwcI~f|SK=`bxtAy; z7b2%SaPBzV4JTqZ<&THF7V9ZuhB^*_+hwGCm@tHT$EmRtk8+<;mSjd-Id+P>PNF^K z(a*vu#SQ)1w{?a)4G^6NqCes2i5UNqpZ3mo1LL3LZY~^gCAVnxtr{&Qv1T$aeZ~z( zpCB!J+-;(oS?((o^Mw02*GzT?gqid2l(9Lj84~U1?Jv7Ahx*KP?*In!kg(2n7t27$jnUm``c(H2T2>ytP?YsQQtT_Rj)P{p z%jx_Bs1u5PMPj5_1b%o0N=dL;!yxT0bps8UO!YhTP&8M%f0W~}?6aqE zo7EWpls{5srj(-!>|z?bd-4-y?l+avU6h*YzV)thi(ACRO>OJOS@bp-`QYE1+R+XB z*)BKiXYDApiyL%L6>iWywWri{_Y|ISz&%?lb>tCWxj&Nl)5GptvV-TcX|z05*_+m^ zgiF=3@7+ycx7Btkx;ui?trai)``H*V?qHDA0?YKJdl(hQwrWI)3XhrdzH(PK626;D zbHpe8Xzl*wWUlz$4T>jFdTC*uJnI(dzdzisYt`_&tQkf1A9(tcUh>%W-52$b0OK&y$2YbY|S4Um)JkXa9wKQ;S6A!S9z36hK3+TXD572@4Qu#JvLos^xYo$Jv z>-8eu<$hmtzR&3?2Y3+cg%<9eUJu+m`(dTfKF(uzHPD$foVjWC|)KI3^%(ml8% zDFR#BRceT`T$ui zxqPTTkyEF6a9A>!it=6H*Us~N2=Yxt@zhrGQ>C8Xa2KEBxh4TNm`+KHsD%lPN*m{T zGL^syrY}jtG1xrMt6FI&XZyUM076ZUVv_sji|RbY+3{Yih4W{40RJhC({`exOFV60 zq|9!PmxWY&L>6DeXgPaEP}Ul4W0C1X5$L37j<=i0t~=$4?Q^U~JAl41c_whyJD#^S zzyZdM#c6+Ok#~rK-+SLPR6qdp^`3!zUzzBEEUG=Iw^2@k%f{_Cdk!n5Y+7zI1UPSn z3x_)6w|n4-Ka%sy#Nt@DLSM|U@AeGVNwI*(dzvcLuo`L~r#Ovg3TIsOniaxyyMo$% z>NzZpru0X>FVN`+wN74q$n&I9LQs={2cV9T-e-BnRj;hdX~$64FTMLH&0=Urdz1BX zlr_im#hs-VM=F1M+sU;b%e9?->DV>h<>|oFzwyiwW=83L?-R7)v^SHowC>k<;7Lzo zohlBrBO?568pTT9b|s5oazV&J)JZ}FMKRMx;co)Rt@pfvP3drNE~C`oiJ!Dvx;rtckmp?SOcxNn*jp}uCa-#YN`*F;(qGr!$EVAEtpPzl z=z2+_ug7~phFY%ewe^PQkVK4Qav77K7$yJp5xe_-s264j(?V_n;6nLZHspX zvWPL{h@I0uDW*L*WLo3@h;)Iso}=nG!@HDq)Z^d}8+$t=*XJp>SPP*S>&2c}`vTP; zNd^~%(fg(rv7w`W?7OJiJEi29tI3^e50HQv)D~29j~HRj$=8V4F%n0Q+9@0RM&RB0 zL&M=Dnyb^D?+DE`n%?(#Gc;O&2DMYEsIfc#PLk8GdF$M;dDBj#xT&7woU+aL8V2xG zto2Xr!ZujcaXMvA@O{Q#hP^FO)ES5(;f33mb|$A*=>d?-D)R;KNiU6?e`@WuNeXQq zE&tjB)wYcn$Nt)U90>zK(|4aIiw?YIaPiU(-ceeGHbS+#wf9ls-3VRdo$UD#hOT?O z`xM$)oV_&(dw`sao=4<;&uogz#&}9=W~dOG(9!abb2xhs-lABydmRrN=-q^T5hkU% z)6N;ITu(~>^k+|L>kuzgeCO=OmDro~LR367OJ2o0v40lMB4iRJ9AKHc7jiv)t|}61`RA>&8>&c<185B9cDS zV^81T^9W-4dRJ%=SCDhdAj(SwEUK*7d!ItPm~%H{cSofqkVB#%{K_Kl^9qIbb;@k( zevl>}@#y&DYOh_v3o2YGlF0vlh4u}~?C6$@0K@5JvmwGYOTB(%x`Z;jxEJ0j#~di= zuWJkW(h9E$Pv7L+10w$=HZx_s>V0tQYVS6Ua8|Bsut%Y_1bwu2>Glp0_G=3nhA zrbL2hOpw=Um&wL9j{V7d5k}C7+TTS(l~QJ@_J|Pe=jmvUHiZiRgob}Kq)%mpPLqu_ z+U2}F&(#X1=^frEtHdB(MVWiG@Zn$SgM&P>Z^j=T@gmxR_FYcD z=z)rk#yrI}6MbHc)G7}!Q&HO0B5qeRN*k{I4)GcEEh%M`w>P6S$M8;)wh{ID(z{)u zg=uk-_TK-CaL$6Ju|vODB(;`{Uf06eEUS&Tj$=MdnuP$9r@ey}+V`k>i0Kuondg0u z-jaNC_?23(T8oWz(K+t~rA7-6W9|D?|B&ZFiruTd_)i7o_7gg?b_3-mcn98T$z4#@ zHhDDk)DbZ(C;Co_cR-BS!i)XbRqrf_j&kz7?OIvlMm)0r$GS2SEwd43N!RKBr{0+_ z(%nSTSpB_smYGtjuzRx^6?sJ~oYw!G^shl8mKlV-z<2BMw-xdGJg@}2oA?kHL(5d# z#E{C^M%AF5UQN0}Rfj!G$veQ;Tj(t*xpLUKDuI-0zlGdlZ@$qVB^%IW3{h(vZ$))&5b%2|?BNOe}_ecZ_BLyU3Q zet$FkPjOQvu@>y!@jwI^(O(;gKmTVfI3TJ7qCV{&T0Ra#^W9dyVJOvJs{GaU5M$=P zgS!f_ZkM)9M6Xgb-FuxU?1l?eQqVK zALfHY;B!f@h~n?3N;T9AOPcQjr2Pk6U*=7cM3zTn0sbKsb9F6t>#Dgk2DXc$ zK*W4P)#>h~q70bev1=i`w%+6GOIdIDU?x7-NVPS~_pU~G_h0>ADX~g4)&cw-rpj>v zH&<=+rbx2XOX=ss>Hb(>cXS`@jb6_XPMhXkEzH{36Cz+^OPD^AUVtX{t2am~yWB&$ zYLr*@gVt6dc3|u+z(&S+;fD9n^S(6-Z8b{;vAy8&Hra>eADjXgX8ce8E(LgCiQPzP z_vyF9BA-KQWm?ozf~zB{(QJC-P?OFS-t1a2Gb+k(FoLz-@L?nu*FBLOTz5t$;<_m^ z0dM1fhLaH>_YKD)AipqFMV`a;XyjR34@YnW`TzaBVRZ!iAj6W#leiW|#^Cx=WE8Gb zBah)ab4(-~|BQ-&B-t=50+M9ILy_UQ4voMM#qeO{VO%pKAW1g#j({=7a8KlZT)Rew z;MzJe7}vx|Ca&Q~2Cjh!Fa-vCWFW4lNPk=#Mf%~Yh=4Cye7&0I>4|II z%t#OXb3Sqpu0KY)$-4g*!Mb|#k z7T2W6UAP7#sknM0$+$WrNw~&D5^*&|0MF5>BF%A)iiFV-HzFZiuSI|k(q4(o4C0@k zBLQ5`NBp>+iTH5+HsZzgSj2*R$7~Tqu}<>-PoIaXnEm4c9{j7!8^Y1(R`IT7Yq&p#qE`&8r2IaGhF!ain>? z;3*{Wu%Ie$pl<$uFlNvE|4EtMXzDD~vQh3V)UweNvrx-MkIah3b?_`~dmHtcg&H?% zHw!YZQL9;~aU=gMOwUHvS*UlT=vlC2HPX#eM@4xJ*Tq_6xI7EBH=Lg(M*Eov>TRfr zpsw2EEOqgWhO`*x8!Ti`PfX!2c=<)oYZ8~d7$<||HjyYH5` zYmx5&9)8WsfA_S;4(7I`Tgssl9~`f>-*DVoPcw-vulsCT$;+vwzQb4+KS!P?IrjxG zbP|}r7SXL#kCU=OhEfsT5n4`^_7vrpdtRg7KF=7$6A}B+oHo8evNQz8WuEs8WvoUp z_PTYxrYKJhXWO(hCA!|+^A>wI`o2TnwOl<`3t%ob^R@DK-%<8ZeS+8-E(TgG)zh2m zw);ABuN^)~CxTL4Yx6JpBKAamg;KvqqdkEV{Xq5WL^Rd|K1PmbDL-VG$yw99(`5fNl|NUfN>D|WHE5aJ8~YOs+TWHoio~m;b~VIl!EZk9)5O08h5iLe{N8Xh zTk(Ap7+P=nf_!hRexVUbF#NzmL zyx)Vwgx&Zim(BL$(5K?Bn04~feX9-P+;(g;s<_$@I^$OUM7KgNm5BxJ=cJ1y3 zDa$8*uSA3D{qXdQ(lvoO*}q?|0-*|Tv1zpbXGx0Y`qV&-f=>_hW4GaX(x0V~5*dU@ zK}m01Zi=)5b~5Q-HF_ArKo`5*907WIU&Dsy@5%})Rh@Vo$J0OX&;0LlbQR@j%K6*H zqrZ8kYpCpJzh3xc;#4V0*Q}vDgz}{DJv00jYRLrmCx0H!QhWQSiC19RdqKYM-*9iC zqC+t)siFkFquu8D4`|_qU*6nr;*3^-9?%u8_}`ZM%g$9tVxR?y{5bYk|9&xgbq=aJ z7K6Yrzx$i>b8q_J5VRd}@Awfi6{b^o9j8C)-w8$NfWA3rbnsi{Z#+~q+hXCu$$n^h z=>KP>t!4gNwHoM+Z4L2pM2j%G|EMsU=&=% zUyI$d4ojadp+Uzge8-PykRbU+#dCK1;Q?Lm7a$W|h*Fh-qwK!o{|<#}<(E$h5W6T< z8#qd3Rc27<{JT7i@_-tw#hrA~wprhxZKakXDJTyo{5Yg&B~sYK`)d5@0*ss5C=jM3 zb?j8~EcM^dH@^43pcPTA4jKO`cg012l|rAqIyur>6;(Cz&I1w^Rf{D*g;N*$k75WM ziAfZNPo>nwelKY>fh?YN)jv~7S+2lZz_0xe2o!E!PydHoJ_&Rb7U=9g{=4ps`U!l$ zIshNvcAP!HKU?CL^nuA*HBhBzMQqq^^!Gr9t8vr799@SM2@xFMA825``upJp(Zpi2 z(vc8I*2Kxr`m3?Z+!he`iSmJ8;ywQeAu51*`g^<;%cHOpE#Dv>Rma$|9NwPnHI%c! zKcA|;_qU>%nf@V^@q-_>?kHVnEmt<;S(Kk;vnXMQdHaKfKY8{YG0ib+3^HYW!LNokyU#IFWPRBGT) z_{US`cYdt9Z2~VUrS4SJBv41*ZUG(N=n!D7x;;$pjsMj+jj2(3po6~0dT*p1!ruSK zz{CJN!r9#eOC`0~_VwiL4+QQ(;j3(F?%y-O8d$aO?HNGK&i;YbT0sEtaf9`2>Xd+m zsv7&7P@&%E;Uz&vR40T7qs&)wq+OsUsQyR{mJn=== zL-8s~?wkPoR+J8$B(vmX4$3)=#!rv3XRi3E^pBvHF1{DAz@(ji4g4sjmjXJ<`7`(m zW%Nie@X>jpR3)|SogkMdi#>&*HzZzK9(Y_KWpnhx&=r_suZPr>l97O;$q~XWMD06Fq84kn=$g*yCKz?%+m0QDJPAUx@Q_?AI80RxlIz;;BgqEB& zJfRemyit5&$VE}CP{S&aTMCqn*cK?KBC;|sk>D=`YP|RBbjR}1zi=e5Losj?> ztuy!p$}o+yhsZDQ#mjZO0w&5D7HmZ5PI*0>jva8qJ_P8L^s9C!U?>c4$YTa)4;P<_ zzNM;`!PW}hOwP`dQ~Fe-v?JgqbSlo8qI7vw-!*8VjQ2yWc>21~!wTNgGz>fE#k+!L zg)X1d3gRmy`pOi(hqdj3*CmCnfU-LVjhtc)S1D=fqe0joqjV9fbc9oRfHS;FNv-C` zTcNY)X5oZ@QridHa_6)lj2XJwB;6AXP~EWv3wP}iJb<6(P;PIG2ygEo@LakVsk}Ip z#C=LwF;a;1-VdmyHR^WAhb~*2g!ei94`oDeM^R!b>`b9|vE^@SuuP-jA0zfk4sr z4|nHB)50qNLcA4<1`9;P*RQg(hbVUT@NtpchM2tJYM9(Y-{C;&!+7Cvp?i(u7KGv8{6Y{8 z&N>*hrw0XZOb~91x`kAIFdifUdBHQdTSU%L;d?pH8Nz-oZ$mJaN?#0Gsg*a>L?l!K ziHSZLr|ZUW|sqf^ZyPnjUVZphmv1S<^-c);CF77HomYhw)k-kSB)0)XVWE zt)V^6i*;{N>g&PQG;mh-(@H=`ftm4033M&CN zd_Jh51#uxACHDx)eYlLWHw7`y=7|i>Z3x;GIylb17{&-HlSj~UN>e0U;LDqX%}~)5 zl)XLZr=03wm6Bjs&}RCZ^Ff}Hne7!BxM+)$L2F1k$;LW>m8vq+wU@ z1aJ}PHq+5f5H2Z}hAz{vcXWIW4jZ=wPRonQ7FvEX-bj}y1S>w4GsbczM&?$ky(WdJ z^kb|v?rjNM<=QbF{g=!WOW1qzlWkP#4%(USCWI6JF$w$d-Y&|X6iQ>)=Fl$r>2At? z4#m94A){E?+EW^%z-ik;ugj13Q1+BiDwX{T$IXtGx0jBdM(qz>M8~(PxSdG_ zAVwiSCrnE?fmbR4tP7=4Rhgu@v&M-5xsU3<3V_mKmfVc{0iFzje|v7iVLUrPM}G<6 zZk~Mi0UiBK+!e}qAJWn5$TBoN!3ZwA1aRT$J`zhCURW%@@G%`#22&}kbApB{CSgQm z+!tRaafLbN8gKQP{6g-Vjg)aO+9 zNdlzWp#&k-j#K*egq~D-3soIdl>kS5-4`75a(q4N+$9J~QIrUVubUFS_)7i)fYUAU z5U(c^gn0d$a(hBFxoYH&_y)F`_(bmfMmzvd6$5U6|1HNXl@rXo{lf{$--MByxFY^D zymtTXUr$o@N5N(QONShc#c4ktJ#_ogDUMkyr|`(_A5K&Gu%M5z0#)I)?AyQAQ2jOu zQtd{9SQl%lazp5DvHaV`iX)rhb>DH!4mqU>x4$_%zWRP4$Le<=6lML=ZY1N3uouqkCdDEfb4aGm_* z5|!`AZlGpl$jsgs!u#bPf2Q=TP&#=xKx~dU9}h(J->c0vxyvteqo3oUZa@Uv`PA~@ zCHdDn9HIrALBWpy4yyM)Y$$5p30{yN)pMh3;)8#!EobFNS15f03Lq93#sVW6?H5YD z6%YQ*-k~3r8r@aUAGW2eSFoh_RwQhLX`(1>t8X{JpV-5M zJiu8bwE3?kcKgk6R>M3qp*rj zz8Ff7ph`i(Vdq>lkEEh$hePfDt;w_jFD65mLBcf`60}rP9118k?K@C@T*&y(k{}y1 z^;$Z0J>-{nz(PhG5d%Q4qpB>(6)XiO<$FEV?+JM1ov&C05V6fbMVSd*gsj*rztM;? zcLp$oreM9Hx`P26*{u!TBT0=ZcYgrgvrdfGCLO50Gyr0OF$onAP@gqa8r!=T2^jC2 z(O!UjZb(Y_N7nCG0zTC88BrC;cDaq>XhXXMq*#FzL4aBHrug=N__i5s=$e3K>2Kjq zP+;^h5@ApQ^|_&Y0*ELMg+5loz}9lEoa8SJNtzjYjL71*hIIHp58%~I?N8p_r;MbV9-F9lEWqi;B~%p;-}bN4P{AU-$nV8RSlmcv#(CvL)dHSe5bHgtxrkm z*qSnJ;Vv{`hh)E_cR;-Z*24F@csuQuM-JmG~)dyCj#fE&FuhK4xFT7l`Z9IP@1w_lG59(4Oi)j)!a?8is7wV**sgW4sOz zL$>QXvd=1id92~fyTOGJ8o`f^3NORcPVDoFr!_A^YM*=sLE=%o&fwUIPvPO;!~+pk z@oF$E53Vlk3yW8=msg9GMW|@{uCVTlA1gj+XsB-NOA+^B@!CD?OB1hM?i!NYq6hnWi2LC$ zhRuyq+mB9tEah<&r6>D(%UN$~C`vE(^%wVLO&f;Xz3j^n_Z8b4p7&4Cy05675Gw4dH6&fn82p?&z=Q;1kN7B?$^G@`q)P3P@w5~EdRin==zBkg16ZeIy zC5;{@%=-ISbs!9An;w?6kLCXu!k?TCw*x%poA7)p{UO|%|2!Plp@Huw-)GQp)7CbJ zYW9?(d1vxgL-9*F76u+j{{T+>nr~L{wl82UQs^I~v_oO|KYs=NNTDA}nTNtjlyNBB zg`YYRF2+xY57oH^%*r{6=EAJ3e;8IDIe&Px9|=D}b!Wp5iJUb+DODp|WMdJ_rGB_5 zhKW;NZvKDjx)QLcsy58r8D_Y5KnF!OLBMtHGBbBjz%2o_($s`pDt{Mn-vvWcbAiUn zg%n))TocMHQxn%ysVU{Yg<7V$VfB|*E?8;#r~U7_E~%K$!}ECWoO{lDzVn^$ob#P; z!TzYgB8M4l*NeYx%u98E+w*Z}JKR_9cRF~J9SJDD;h*-VBVRf_bovKp-8(;^d*STL zNu0*m{_9QzcoUJW<%qLBuO}atF2CUDdHJlf2G(ssZejG7@Y7GcvJ6-?$bVz!al{E- z+)?Kt_SJ2t3sq#^0hm_dY$4FV>sEO;u`|4yG7;=m2q)YJy`w~!!wZOB`UZjMHvUfCuJ~W z+9TL)rNR z8Idv^4drX-B^=_9I1z;Ts1p&te~=|sAS-Y>D1J>Yrc6~X5yauJnv^+fji7WvhI{cb zXI+lLIG~Cn?@$TP6-KXZyp2(JsDd6|G%(<%SIpv+>1VXN&>$U*R|Az~iVaer+&$)$ z9*2QjAc~`@;{X{U_S(uTfZ`=~URPq!=?b|!Wwce^;6-v+dRrwL%pRhov(cU80uXWx z2T_yPTERd;(HNhx{3R@)qLc&1^UEwfK<hiIfj`g6SY>*6jULxc{ z6q78UM;bM`KFi9K=b(iNl)76UK+$dF?-(F%#;aFY+)*du>Vf5U84Nvc;WU55 z8PEGS`hjugAB{Gu%A>NQ&Y$@>Uo}|ZfkCFLnevysj#tSf%k?O?wH(7fO_f^m>ZZ=*-kje4hY2{HRFi*th0PqXE4L41v+Xfifo9naK1=;e-fm#4!6}3LRX_1f-exRi9FcFaxOMV*4AU%1cgh8v?P_z{cfqH#d8sBG z*k<5k7+?wm3}7qj$FWL(Q-IORN!8bb88ydG_qFwk~4KpTBL?Yy&xdHX(v&%i(Q z@jX@X^*cDrI*racV3jS)4#Hdr{kQJM zP)>eyE^~oB8m+9RG6ga_<#l-zd(ow=#H%$_cG-D?bDZHZFMi0$8SxLtJ;%}#6;8gj zlqty180#WuE`%HgSk>UhXpHS1m)(*PS&BV0;?Ml?(x0lHyO8Zep3!N$}EcbckRf`~g zLwF60@U%i!g#5lrJvz4$6-7)_aNeG* zL{rM!_^MzCsysbdX^0QDQppSjnuX!Yk9gWf6+R`5cAOJLa_1s|+-S#y=qbgNK_q>Lqsg^EWia6}uYv_^>?xG7a`o2_CQ z`Zo|kI+SSEAXhnqpAg@3ih`s4R0RqL@pC#d01X1_MLg|fmVA6Bh`VS>jso|L0_9~q z?WW*G*ngnzVHEk262yw7+Ve_5%)Xo)fm-|wTVCl&7crhIRf#QjbBHA#RA33Nv9C^~Do9QzeP zJisjP8;}lCFlfh=)mQ@r@eny)1IaR`C~nriRDr%zJdC{Lyta*~O%T7J7q$c2t=*t} z#z7^K`94yn;C(q#ZW;9*q3s71=o>!5sLtLEnbdLz8WhE&35f%enlwR1sIL_dYrYeL zT@a5^#$g5L-@OVy|9(l^Pbgj*yIl!o9S$1Y`xTYHtwhcp)ybbcTfi93a^)M8JWhol zqY&@crlc>ig%HHA;boxU_;o_z$FCD~aj$|b8K;$#csfaikrrs9E0oX7f#NsK zGsk8Q8J^j9_yCH%0?Ot-AxvW*e6PSAT0BK%7nCt<`~_t?o=#KxMWu)>`b8Oxr!(Yj zCm^AGd%;B?win)`v`P%SRH^)dNpqIYk3ja=u-}xQQS=;@r3rZY!+5G-ae_M2#Ae@8 zUP96H6eOs#HqBA9Q1V+Ud0wc+D*jYTkfY6CZD$q4@5x)LjiI#O8X{}k^d!m+Rs#qL z_mGkPKurrAHMMZcXs7y^R!uz)x$$4--KcT%!(bKWhvE-&fwE%jNp zrKyViwNb01zDY>hf ztmoCiyodjzb(tDeQ~7F4fdA7ljqAuUOkrQlQ#~N%pD1#hynlJ%thhi$jAA&v#Cg?L zEUmqoVPbDDRWrFa_(-!h2R_#q5b$!Zy3z*wl(=$rmnk4{iNEg|*obl4M}w`}Fu<5?^5;N< z$a|z3vQiyWThi4x)Iip`&fU@J&#r!@))a7UT=t5Zj1)9l7QK5_>&=2ss@JS02W3B{ ze$Mt(s4aPj%aS?Tx9o@S)o88`j6I{m)hpXBQbwBq3;Xn073au;DD4Th{-T=BGvj1F zf$(y(PdfZ5W@A_Y)?4)*t{$wMA;6IJx{4E6))jR)8+%Q?y^kE5#=$+MRtlVGw)oOXx;#;`R+9{hLA#w94ZDx_Gwx+D=T0S)T+H5Eh1PA*oPO}1V9O*;V36!=;wXj#~Yq@4g zsyp!RKT3N~gEQqOEr?3bYcO+mYKKMt&KAF2Z8@&tCfHa@utb*{7?weLyqD5r)Kwf< z30(~fw^3x~E}G5ij|1j``n+$a4VuGJPx4S^mUf-p?5PEy2AzUi zs#vfxG%Q#W%siTF1#Dh_ZMV(R1?JF(c;#Svax+P(@jJtO&oR?T?UuDG8KnSP_D14-I7AKo{ktL66gtkYC&6NbJ8Ro>JPu{2-%5Wlq~?{pQ; z>IE9~z0wnuTI6U%JHjEonm4cUPmjb2c z=tBx0?1UsL*@>}n99Mggz({M^>lmf2u86@^KA;oeEs)HkvX>wIK(#*4Zj0qpl~(TSME)Uv#2^U8+Be+@)Or zL7!sYvsx@>aJ>2go48MV9q*o|viTa+YX`Lj0se>u{KY`t4v$hSwSU**>y8N-_?VWr zV{qG|41$Z_^fY)JsSR1bAb(nd2#&;JFOUZj#gwINpN zxy2d27ntLwhO~PU6p~UG4h@Mp|I&WK1n;WriEt@~-B4kBEoDZ0hd2o{>;`o$th{g|}AIyBj_PqdUw|vkQ%CRX(-*EYTwTeXStf7%3&;dy1h)G#baH;ba|28POPz9 zHh6NL305=Nf}5_qgGebUUEDeJ!F(4tb(9bUGtvUdGR`#;wTz(NJ^^X|Cc2R3PZ~)p zXWJ3JCD-*Ho<>pT6c^P5jj}3iD=F=R+7j#asq0Z9MC7+qD9EmW0|?R=Uge9C&L2GhM1Z96l@iTm@DMzB+z_-}8RhX**IJ zPZy?H1GYr@`a#6DEjz5qvE-vQ5LWX87j!QLj)zZ<=H+7`k)l?Vh=D0M*XlaKU*(2H}FdCFBHNN`90 z)>e})Z%0(f@;7T^1GU4I4Q2ZeHm)C$@@U=K+A3w-c6DP@D_oP&(N`h-qjBqtpuN+0 z&yYIF`lIV6w*-}@uIPi1NmDM-+;=e*WSp1{w7GTKTPp=U?hAO+E8O&y}`0wQFBf7#OJOY4z(Ktz_&q% zX~<9P@+O4;fe%x!VE4P|(wnUExT`wS%smMG0Z!pG9>qyAiz>fy zWl_uRuATRLk!LL0W21cB`EXJbwR>;k_LTgy&>pW7FzQb)qiI6^InKPnaGBIh4d%D4U z(8tY+p2LckxQ9bZmNufe-F7z}*yj#p$;Ix^Q3n#{L|OXqxU>Om*m5`F)p}ai0`m20 zncaf4NA4DE`TOoyM8P+L#jSUr!CJ8r&^IyfYO&QL5z1aN*myH#PmKL*^T%!hm9Yoi z_qMug;MGSgdur@{Q_+yvVm9(K_dJtX`WP42r(*gkh_EY%~~+H!YibC9$VCATFdHulChZAvNMS{g9@OZSKHjKx}V){Wi7_QD3f zQPlaId#zR4io!c+*83q5lu^@MmnByi1SrEjqZ=v$=`&`zVEpHEX8FZnv0b#PaZnn4 zkQ`Kl4gAer8esKhL(RaYjLtgVawDvtv=g=NrLsTW(UegazSVsq1luN$r$5TRgiKs~ z7ooF#0iL6v+x|ISe4|;3k`d34lZOp@oPimrtr)Y zrDHSweJ=w7U!W9L?Dq5q>LWn?B}-j_WLqq-ljk~S#aC4JURVpBie%c{9pPy#EZy@n zULS`BDWaTx+0~j3E_Qm@;PseV^#<&m^BUL{&9u^yi*@s|4+nR7r&! z5O)7wsJquqq#XCWMg@Z~*;kzP^uW6EKdZ5PU!%OO(J!!!3J*N$i-RQxtM;wu8aAYV z-}xQ6T~0+KJ+?2p5zYGE^9}2N$nzF}{l?B8kAo0(NVZSe`6He)B>f#)Oz8 znzCBFdOr$a1Vi&D`~?YWEQ+4VMe(DMdV;6_p!D7`IJi`J-bRoAL|TuS84Qi);OQpI z9ugY}oxw#(_A;E4FvR8df)156Ww z|0-&v-?rM}k!qJ%=0cTU`^Y-3eX#M)&eUMCuIV*_E|5|i>j<2k5I&Hy67;4l)2o-D zR1lTe!Xl}nnc9)R@5S~s(bw1{c(^bKZPVtgV>hEQ;>xj!46rY}OFT68|d!QEr^{dfwc*c+Z!l%|FUvCc2x8^In% z=`*5Yc^dZRY;R9}Y5=r)od)RZ1go8Uz(>x_3+c`J4%UlNwl*>%Aa77?4^LeV4okE> zij_SRk!)fq?e%SZ$+Jh#O|94PKB`S9Qim#2BVM7asd_tFVu@df$g}#}pjga9&|<4X zi+d2-RQ+0jHGoGEZ^_Cw=`94YKUzb$0(*U07jM-cpd%|Ds`oVs_6AkqHYC73$<8d$ z8vt3ODp;LWu$CBD<%h5;4`O}aAfx&q*46s20Z@l--K4h$8S4R~dqZBx)PIIK(->wC zWuNu#WXsCEdto{AsSfp^9r>d#>xbb%sn22=Wk%cHDk$Ttpu9uT#t)%{NBqNsZL-6F zO^-0KsIT-m8=hL7)?YT+?MVltr^5g1Vag)XguN+wnSNkHZV22%EAFO%o6iTi8I`5$ z?^D{=>QFd62Mwap2SOi~^Pii;OUKiJa!2X8lyX`Bf^%$NgF9M~<7@9#aveqnF)*!> zw!z+#;yUYbq?qH6lRi!-D*h~d0J~AC+rcGIP;gD05ANlEtWRL&Lsa^u5YIAxH|W!f z3VWGzI6wC=v7>+LI=)IqTAzrqRGg^~CvbIFnmsH6#-+XV^}c#kZ)qW;G4jEe3Jr7cKA1#9jn6Z^eD_{ ztH6Zk=VPa0+CwnY@58(=C7!DSGw2@7X8;o(pO2q{-K)ZU{!y4ORDt>8zrgJA5X}En zh1v5#n8qxub`K_kyV(2u8_bNVFf$*8*|!SJevgG|vQykbN5Vaki#x)9X5k%KF|Z2C z>i1Cg2FfA-x-JZ@x-JZ>x-JZV{JJpWp><*8L!?CNx_?04Sr@V%1i8a|HNfiNA|mCE z@U{0(Y67T(@%Iv2w>)eYJc&|uhd^m7ghpHh0evx-dWYCd}TxcAyphVBDKFv zc?ZH9QQA1YE1PoK3k{!r9HsxJpJAs0;$ygN33QO@f9NAjY*)Pp?4dm_c2p7#uk=300; +} {2} + + + finish_test diff --git a/test/json101.test b/test/json101.test index 0d59f2e83..184527917 100644 --- a/test/json101.test +++ b/test/json101.test @@ -15,11 +15,6 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -ifcapable !json1 { - finish_test - return -} - do_execsql_test json101-1.1.00 { SELECT json_array(1,2.5,null,'hello'); } {[1,2.5,null,"hello"]} @@ -846,5 +841,13 @@ do_execsql_test json-16.30 { SELECT unicode(json_extract('"\uD834\uDD1E"','$')); } {119070} +# 2022-01-30 dbsqlfuzz 4678cf825d27f87c9b8343720121e12cf944b71a +do_execsql_test json-17.1 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(a,b,c); + CREATE TABLE t2(d); + SELECT * FROM t1 LEFT JOIN t2 ON (SELECT b FROM json_each ORDER BY 1); +} {} finish_test diff --git a/test/json102.test b/test/json102.test index 18a6bbf5c..f551c4b82 100644 --- a/test/json102.test +++ b/test/json102.test @@ -18,11 +18,6 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -ifcapable !json1 { - finish_test - return -} - do_execsql_test json102-100 { SELECT json_object('ex','[52,3.14159]'); } {{{"ex":"[52,3.14159]"}}} @@ -337,4 +332,60 @@ do_execsql_test json102-1501 { SELECT sum(json_valid(json_quote('a'||char(x)||'z'))) FROM c ORDER BY x; } {31} +# 2022-01-10 tests for -> and ->> operators +# +reset_db +do_execsql_test json102-1600 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, x JSON); + INSERT INTO t1(id,x) VALUES + (1, '{"a":null}'), + (2, '{"a":123}'), + (3, '{"a":4.5}'), + (4, '{"a":"six"}'), + (5, '{"a":[7,8]}'), + (6, '{"a":{"b":9}}'), + (7, '{"b":999}'); + SELECT + id, + x->'a' AS '->', + CASE WHEN subtype(x->'a') THEN 'json' ELSE typeof(x->'a') END AS 'type', + x->>'a' AS '->>', + CASE WHEN subtype(x->>'a') THEN 'json' ELSE typeof(x->>'a') END AS 'type', + json_extract(x,'$.a') AS 'json_extract', + CASE WHEN subtype(json_extract(x,'$.a')) + THEN 'json' ELSE typeof(json_extract(x,'$.a')) END AS 'type' + FROM t1 ORDER BY id; +} [list \ + 1 null json {} null {} null \ + 2 123 json 123 integer 123 integer \ + 3 4.5 json 4.5 real 4.5 real \ + 4 {"six"} json six text six text \ + 5 {[7,8]} json {[7,8]} text {[7,8]} json \ + 6 {{"b":9}} json {{"b":9}} text {{"b":9}} json \ + 7 {} null {} null {} null +] +do_execsql_test json102-1610 { + DELETE FROM t1; + INSERT INTO t1(x) VALUES('[null,123,4.5,"six",[7,8],{"b":9}]'); + WITH c(y) AS (VALUES(0),(1),(2),(3),(4),(5),(6)) + SELECT + y, + x->y AS '->', + CASE WHEN subtype(x->y) THEN 'json' ELSE typeof(x->y) END AS 'type', + x->>y AS '->>', + CASE WHEN subtype(x->>y) THEN 'json' ELSE typeof(x->>y) END AS 'type', + json_extract(x,format('$[%d]',y)) AS 'json_extract', + CASE WHEN subtype(json_extract(x,format('$[%d]',y))) + THEN 'json' ELSE typeof(json_extract(x,format('$[%d]',y))) END AS 'type' + FROM c, t1 ORDER BY y; +} [list \ + 0 null json {} null {} null \ + 1 123 json 123 integer 123 integer \ + 2 4.5 json 4.5 real 4.5 real \ + 3 {"six"} json six text six text \ + 4 {[7,8]} json {[7,8]} text {[7,8]} json \ + 5 {{"b":9}} json {{"b":9}} text {{"b":9}} json \ + 6 {} null {} null {} null +] + finish_test diff --git a/test/json103.test b/test/json103.test index 35580ce4e..e7483073b 100644 --- a/test/json103.test +++ b/test/json103.test @@ -14,11 +14,6 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -ifcapable !json1 { - finish_test - return -} - do_execsql_test json103-100 { CREATE TABLE t1(a,b,c); WITH RECURSIVE c(x) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<100) diff --git a/test/json104.test b/test/json104.test index 8d95e6014..56dd2738c 100644 --- a/test/json104.test +++ b/test/json104.test @@ -15,11 +15,6 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix json104 -ifcapable !json1 { - finish_test - return -} - # This is the example from pages 2 and 3 of RFC-7396 do_execsql_test json104-100 { SELECT json_patch('{ diff --git a/test/json105.test b/test/json105.test index 46d36774f..83face188 100644 --- a/test/json105.test +++ b/test/json105.test @@ -15,11 +15,6 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix json104 -ifcapable !json1 { - finish_test - return -} - # This is the example from pages 2 and 3 of RFC-7396 db eval { CREATE TABLE t1(j); diff --git a/test/merge1.test b/test/merge1.test new file mode 100644 index 000000000..36969e8fc --- /dev/null +++ b/test/merge1.test @@ -0,0 +1,143 @@ +# 2021-12-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. +# +#*********************************************************************** +# +# Testing the compound-SELECT merge algorithm to ensure that it works +# when it tries to balance the merge tree. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix merge1 + +ifcapable !vtab { + finish_test + return +} + +load_static_extension db series + + +optimization_control db all on +do_execsql_test 100 { + WITH data(v) AS ( + SELECT value FROM generate_series(1,35,3) + UNION ALL + SELECT value FROM generate_series(10,30,4) + UNION ALL + SELECT value FROM generate_series(20,50,5) + UNION ALL + SELECT value FROM generate_series(30,60,6) + UNION ALL + SELECT value FROM generate_series(1,50,7) + UNION ALL + SELECT value FROM generate_series(10,80,8) + ) + SELECT v FROM data ORDER BY v; +} {1 1 4 7 8 10 10 10 13 14 15 16 18 18 19 20 22 22 22 25 25 26 26 28 29 30 30 30 31 34 34 35 36 36 40 42 42 43 45 48 50 50 50 54 58 60 66 74} +do_eqp_test 101 { + WITH data(v) AS ( + SELECT value FROM generate_series(1,35,3) + UNION ALL + SELECT value FROM generate_series(10,30,4) + UNION ALL + SELECT value FROM generate_series(20,50,5) + UNION ALL + SELECT value FROM generate_series(30,60,6) + UNION ALL + SELECT value FROM generate_series(1,50,7) + UNION ALL + SELECT value FROM generate_series(10,80,8) + ) + SELECT v FROM data ORDER BY v; +} { + QUERY PLAN + `--MERGE (UNION ALL) + |--LEFT + | `--MERGE (UNION ALL) + | |--LEFT + | | `--MERGE (UNION ALL) + | | |--LEFT + | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + | | `--RIGHT + | | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + | `--RIGHT + | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + `--RIGHT + `--MERGE (UNION ALL) + |--LEFT + | `--MERGE (UNION ALL) + | |--LEFT + | | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + | `--RIGHT + | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + `--RIGHT + `--SCAN generate_series VIRTUAL TABLE INDEX 23: +} + +# Same test with the blanced-merge optimization +# disabled. Should give the exact same answer. +# +optimization_control db balanced-merge off +db cache flush +do_execsql_test 110 { + WITH data(v) AS ( + SELECT value FROM generate_series(1,35,3) + UNION ALL + SELECT value FROM generate_series(10,30,4) + UNION ALL + SELECT value FROM generate_series(20,50,5) + UNION ALL + SELECT value FROM generate_series(30,60,6) + UNION ALL + SELECT value FROM generate_series(1,50,7) + UNION ALL + SELECT value FROM generate_series(10,80,8) + ) + SELECT v FROM data ORDER BY v; +} {1 1 4 7 8 10 10 10 13 14 15 16 18 18 19 20 22 22 22 25 25 26 26 28 29 30 30 30 31 34 34 35 36 36 40 42 42 43 45 48 50 50 50 54 58 60 66 74} +do_eqp_test 111 { + WITH data(v) AS ( + SELECT value FROM generate_series(1,35,3) + UNION ALL + SELECT value FROM generate_series(10,30,4) + UNION ALL + SELECT value FROM generate_series(20,50,5) + UNION ALL + SELECT value FROM generate_series(30,60,6) + UNION ALL + SELECT value FROM generate_series(1,50,7) + UNION ALL + SELECT value FROM generate_series(10,80,8) + ) + SELECT v FROM data ORDER BY v; +} { + QUERY PLAN + `--MERGE (UNION ALL) + |--LEFT + | `--MERGE (UNION ALL) + | |--LEFT + | | `--MERGE (UNION ALL) + | | |--LEFT + | | | `--MERGE (UNION ALL) + | | | |--LEFT + | | | | `--MERGE (UNION ALL) + | | | | |--LEFT + | | | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + | | | | `--RIGHT + | | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + | | | `--RIGHT + | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + | | `--RIGHT + | | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + | `--RIGHT + | `--SCAN generate_series VIRTUAL TABLE INDEX 23: + `--RIGHT + `--SCAN generate_series VIRTUAL TABLE INDEX 23: +} diff --git a/test/misc1.test b/test/misc1.test index 758d4082e..b1b1b083c 100644 --- a/test/misc1.test +++ b/test/misc1.test @@ -652,7 +652,7 @@ do_catchsql_test misc1-21.2 { # 2015-04-15 do_execsql_test misc1-22.1 { - SELECT ""+3 FROM (SELECT ""+5); + SELECT ''+3 FROM (SELECT ''+5); } {3} # 2015-04-19: NULL pointer dereference on a corrupt schema diff --git a/test/pager1.test b/test/pager1.test index ff7d3e406..79598e2a7 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -2925,4 +2925,32 @@ do_test 43.3 { sqlite3_db_status db CACHE_MISS 0 } {0 1 0} +# 2022-03-01 Forum post https://sqlite.org/forum/forumpost/3b9e894312 +# Ensure that max_page_count gets adjusted upward, if needed, on a +# ROLLBACK. +# +db close +sqlite3 db :memory: +do_execsql_test 44.1 { + PRAGMA page_size=4096; + PRAGMA auto_vacuum=FULL; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b ANY); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<50) + INSERT INTO t1(a,b) SELECT x, zeroblob(1000) FROM c; + CREATE TABLE t2 AS SELECT * FROM t1; + PRAGMA page_count; +} {31} +do_execsql_test 44.2 { + BEGIN; + DROP TABLE t2; + PRAGMA incremental_vacuum=50; + PRAGMA page_count; + PRAGMA max_page_count=2; +} {16 16} +do_execsql_test 44.3 { + ROLLBACK; + PRAGMA page_count; + PRAGMA max_page_count; +} {31 31} + finish_test diff --git a/test/printf2.test b/test/printf2.test index 998038f88..2f6208b17 100644 --- a/test/printf2.test +++ b/test/printf2.test @@ -12,10 +12,13 @@ # focus of this file is testing the printf() SQL function. # # -# EVIDENCE-OF: R-63057-40065 The printf(FORMAT,...) SQL function works +# EVIDENCE-OF: R-32560-14372 The format(FORMAT,...) SQL function works # like the sqlite3_mprintf() C-language function and the printf() # function from the standard C library. # +# EVIDENCE-OF: R-64900-53159 The printf() SQL function is an alias for +# the format() SQL function. +# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -24,7 +27,7 @@ source $testdir/tester.tcl # then the result is NULL. # do_execsql_test printf2-1.1 { - SELECT quote(printf()), quote(printf(NULL,1,2,3)); + SELECT quote(format()), quote(format(NULL,1,2,3)); } {NULL NULL} @@ -32,31 +35,31 @@ do_execsql_test printf2-1.2 { SELECT printf('hello'); } {hello} do_execsql_test printf2-1.3 { - SELECT printf('%d,%d,%d',55,-11,3421); + SELECT format('%d,%d,%d',55,-11,3421); } {55,-11,3421} do_execsql_test printf2-1.4 { SELECT printf('%d,%d,%d',55,'-11',3421); } {55,-11,3421} do_execsql_test printf2-1.5 { - SELECT printf('%d,%d,%d,%d',55,'-11',3421); + SELECT format('%d,%d,%d,%d',55,'-11',3421); } {55,-11,3421,0} do_execsql_test printf2-1.6 { SELECT printf('%.2f',3.141592653); } {3.14} do_execsql_test printf2-1.7 { - SELECT printf('%.*f',2,3.141592653); + SELECT format('%.*f',2,3.141592653); } {3.14} do_execsql_test printf2-1.8 { SELECT printf('%*.*f',5,2,3.141592653); } {{ 3.14}} do_execsql_test printf2-1.9 { - SELECT printf('%d',314159.2653); + SELECT format('%d',314159.2653); } {314159} do_execsql_test printf2-1.10 { SELECT printf('%lld',314159.2653); } {314159} do_execsql_test printf2-1.11 { - SELECT printf('%lld%n',314159.2653,'hi'); + SELECT format('%lld%n',314159.2653,'hi'); } {314159} do_execsql_test printf2-1.12 { SELECT printf('%n',0); @@ -65,7 +68,7 @@ do_execsql_test printf2-1.12 { # EVIDENCE-OF: R-17002-27534 The %z format is interchangeable with %s. # do_execsql_test printf2-1.12 { - SELECT printf('%.*z',5,'abcdefghijklmnop'); + SELECT format('%.*z',5,'abcdefghijklmnop'); } {abcde} do_execsql_test printf2-1.13 { SELECT printf('%c','abcdefghijklmnop'); diff --git a/test/quote.test b/test/quote.test index 9810a3ca0..6d7b317ea 100644 --- a/test/quote.test +++ b/test/quote.test @@ -173,6 +173,7 @@ ifcapable altertable { INSERT INTO t1 VALUES(1,2,3),(1,4,5); ALTER TABLE t1 DROP COLUMN b; } {1 {error in index x1 after drop column: no such column: b}} + sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 do_catchsql_test 3.5 { DROP TABLE t1; CREATE TABLE t1(a, b, c); diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl index 45966fd23..314331aee 100644 --- a/test/releasetest_data.tcl +++ b/test/releasetest_data.tcl @@ -99,7 +99,6 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_STMT_SCANSTATUS -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_ENABLE_CURSOR_HINTS - --enable-json1 } "Check-Symbols" { -DSQLITE_MEMDEBUG=1 @@ -117,7 +116,7 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_STMT_SCANSTATUS - --enable-json1 --enable-fts5 --enable-session + --enable-fts5 --enable-session } "Debug-One" { --disable-shared @@ -134,7 +133,7 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_HIDDEN_COLUMNS -DSQLITE_MAX_ATTACHED=125 -DSQLITE_MUTATION_TEST - --enable-fts5 --enable-json1 + --enable-fts5 } "Debug-Two" { -DSQLITE_DEFAULT_MEMSTATUS=0 @@ -170,7 +169,6 @@ array set ::Configs [strip_comments { -DSQLITE_OMIT_VIRTUALTABLE=1 -DSQLITE_ENABLE_HIDDEN_COLUMNS -DSQLITE_TEMP_STORE=3 - --enable-json1 } "Device-Two" { -DSQLITE_4_BYTE_ALIGNED_MALLOC=1 @@ -188,7 +186,7 @@ array set ::Configs [strip_comments { -DSQLITE_OMIT_TRACE=1 -DSQLITE_TEMP_STORE=3 -DSQLITE_THREADSAFE=2 - --enable-json1 --enable-fts5 --enable-session + --enable-fts5 --enable-session } "Locking-Style" { -O2 @@ -232,7 +230,7 @@ array set ::Configs [strip_comments { -DSQLITE_WRITE_WALFRAME_PREBUFFERED=1 -DUSE_GUARDED_FD=1 -DUSE_PREAD=1 - --enable-json1 --enable-fts5 + --enable-fts5 } "Extra-Robustness" { -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 @@ -247,7 +245,7 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_FTS4_PARENTHESIS -DSQLITE_DISABLE_FTS4_DEFERRED -DSQLITE_ENABLE_RTREE - --enable-json1 --enable-fts5 + --enable-fts5 } "No-lookaside" { -DSQLITE_TEST_REALLOC_STRESS=1 @@ -259,7 +257,6 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_HIDDEN_COLUMNS -DCONFIG_SLOWDOWN_FACTOR=8.0 - --enable-json1 } "Windows-Memdebug" { @@ -503,9 +500,6 @@ proc main_script {args} { --enable-fts5 { lappend opts -DSQLITE_ENABLE_FTS5 } - --enable-json1 { - lappend opts -DSQLITE_ENABLE_JSON1 - } --enable-shared { lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 } diff --git a/test/returning1.test b/test/returning1.test index c64e72f72..fb058a643 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -333,4 +333,47 @@ do_execsql_test 13.1 { } {{}} } ;# end ifcapable rtree +# 2021-12-01 Forum post https://sqlite.org/forum/forumpost/793beaf322 +# Need to report foreign key constraint errors prior to RETURNING +# +reset_db +do_execsql_test 14.0 { + PRAGMA foreign_keys(1); + CREATE TABLE Parent(id INTEGER PRIMARY KEY); + CREATE TABLE Child(id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES Parent(id)); +} {} +do_catchsql_test 14.1 { + INSERT INTO child(parent_id) VALUES(123) RETURNING id; +} {1 {FOREIGN KEY constraint failed}} + +# 2021-12-28 Forum post https://sqlite.org/forum/forumpost/e0c7574ab2 +# Incorrect affinity for REAL values that can be represented as integers. +# +reset_db +sqlite3_test_control SQLITE_TESTCTRL_INTERNAL_FUNCTIONS db +do_execsql_test 15.0 { + CREATE TABLE t1(x REAL); + INSERT INTO t1(x) VALUES(5.0) RETURNING x, affinity(x); +} {5.0 real} +do_execsql_test 15.1 { + UPDATE t1 SET x=x+1 RETURNING x, affinity(x); +} {6.0 real} +do_execsql_test 15.2 { + DELETE FROM t1 RETURNING x, affinity(x); +} {6.0 real} + +# 2022-02-28 Forum post https://sqlite.org/forum/forumpost/595e132f71 +# RETURNING with the xfer optimization +# +reset_db +do_execsql_test 16.0 { + CREATE TABLE t1(a,b,c); + INSERT INTO t1 VALUES(1,2,3),('a','b','c'); + CREATE TEMP TABLE t2(x,y,z); + INSERT INTO t2 SELECT * FROM t1 RETURNING *; +} {1 2 3 a b c} +do_execsql_test 16.1 { + SELECT * FROM t2; +} {1 2 3 a b c} + finish_test diff --git a/test/rowvalue.test b/test/rowvalue.test index c92d34482..92d3260e6 100644 --- a/test/rowvalue.test +++ b/test/rowvalue.test @@ -692,6 +692,39 @@ do_execsql_test 30.3 { UPDATE t2 SET (d,d,a)=(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t1 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t1 UNION SELECT x ORDER BY 1) ORDER BY 1) ORDERa)|9 AS blob, 2, 3) FROM t1 WHERE x' END, '|' FROM t1; } {1 | 2 'X' | 3 'Z' |} +# 2022-03-06 https://sqlite.org/forum/forumpost/2024e94071 +# Harmless assertion fault following a syntax error. +# +reset_db +do_catchsql_test trigger1-23.1 { + CREATE TABLE t1(a INT); + CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN + INSERT INTO t1 SELECT e_master LIMIT 1,#1; + END; +} {1 {near "#1": syntax error}} + finish_test diff --git a/test/update.test b/test/update.test index 7be360726..66efd10ec 100644 --- a/test/update.test +++ b/test/update.test @@ -627,7 +627,7 @@ ifcapable altertable { CREATE INDEX t15c ON t15(c); INSERT INTO t15(a,b) VALUES(5,'zyx'),(15,'wvu'),(25,'tsr'),(35,'qpo'); - UPDATE t15 SET c=printf("y%d",a) WHERE c IS NULL; + UPDATE t15 SET c=printf('y%d',a) WHERE c IS NULL; SELECT a,b,c,'|' FROM t15 ORDER BY a; } {5 zyx y5 | 10 abc y10 | 15 wvu y15 | 20 def y20 | 25 tsr y25 | 30 ghi y30 | 35 qpo y35 |} } diff --git a/test/upfrom2.test b/test/upfrom2.test index a1f00953e..600fa05e4 100644 --- a/test/upfrom2.test +++ b/test/upfrom2.test @@ -382,4 +382,24 @@ do_execsql_test 6.2 { ) } {} +# 2022-03-21 +# https://sqlite.org/forum/forumpost/929168fdd6 +# +reset_db +do_execsql_test 7.0 { + CREATE TABLE t1(a); + INSERT INTO t1(a) VALUES(11),(22),(33),(44),(55); + CREATE VIEW t2(b,c) AS SELECT a, COUNT(*) OVER () FROM t1; + CREATE TABLE t3(x,y); + CREATE TRIGGER t2r1 INSTEAD OF UPDATE ON t2 BEGIN + INSERT INTO t3(x,y) VALUES(new.b,new.c); + END; + SELECT * FROM t2; +} {11 5 22 5 33 5 44 5 55 5} +do_execsql_test 7.1 { + UPDATE t2 SET c=t1.a FROM t1 WHERE t2.b=t1.a; + SELECT * FROM t3; +} {11 11 22 22 33 33 44 44 55 55} + + finish_test diff --git a/test/upsert2.test b/test/upsert2.test index 1aa499e60..5cbc4656a 100644 --- a/test/upsert2.test +++ b/test/upsert2.test @@ -72,7 +72,7 @@ do_execsql_test upsert2-300 { CREATE TABLE record(x TEXT, y TEXT); CREATE TRIGGER r1 BEFORE INSERT ON t1 BEGIN INSERT INTO record(x,y) - VALUES('before-insert',printf('%d,%d,%d',new.a,new.b,new.c)); + VALUES('before-insert',format('%d,%d,%d',new.a,new.b,new.c)); END; CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN INSERT INTO record(x,y) @@ -80,7 +80,7 @@ do_execsql_test upsert2-300 { END; CREATE TRIGGER r3 BEFORE UPDATE ON t1 BEGIN INSERT INTO record(x,y) - VALUES('before-update',printf('%d,%d,%d/%d,%d,%d', + VALUES('before-update',format('%d,%d,%d/%d,%d,%d', old.a,old.b,old.c,new.a,new.b,new.c)); END; CREATE TRIGGER r4 AFTER UPDATE ON t1 BEGIN @@ -123,7 +123,7 @@ do_execsql_test upsert2-400 { CREATE TABLE t1(a INT PRIMARY KEY, b int, c DEFAULT 0) WITHOUT ROWID; CREATE TRIGGER r1 BEFORE INSERT ON t1 BEGIN INSERT INTO record(x,y) - VALUES('before-insert',printf('%d,%d,%d',new.a,new.b,new.c)); + VALUES('before-insert',format('%d,%d,%d',new.a,new.b,new.c)); END; CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN INSERT INTO record(x,y) @@ -131,7 +131,7 @@ do_execsql_test upsert2-400 { END; CREATE TRIGGER r3 BEFORE UPDATE ON t1 BEGIN INSERT INTO record(x,y) - VALUES('before-update',printf('%d,%d,%d/%d,%d,%d', + VALUES('before-update',format('%d,%d,%d/%d,%d,%d', old.a,old.b,old.c,new.a,new.b,new.c)); END; CREATE TRIGGER r4 AFTER UPDATE ON t1 BEGIN diff --git a/test/utf16align.test b/test/utf16align.test index f026d9575..ef10bc659 100644 --- a/test/utf16align.test +++ b/test/utf16align.test @@ -32,6 +32,7 @@ ifcapable !utf16 { do_test utf16align-1.0 { set unaligned_string_counter 0 add_alignment_test_collations [sqlite3_connection_pointer db] + sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 execsql { PRAGMA encoding=UTF16; CREATE TABLE t1( diff --git a/test/vtab6.test b/test/vtab6.test index f4504b017..ffbe430c6 100644 --- a/test/vtab6.test +++ b/test/vtab6.test @@ -351,6 +351,7 @@ do_test vtab6-4.10 { # A test for ticket #247. # do_test vtab6-7.1 { + sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 execsql { INSERT INTO t7 VALUES ("pa1", 1); INSERT INTO t7 VALUES ("pa2", NULL); diff --git a/test/vtabdistinct.test b/test/vtabdistinct.test new file mode 100644 index 000000000..e18e121e5 --- /dev/null +++ b/test/vtabdistinct.test @@ -0,0 +1,41 @@ +# 2022-01-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 for sqlite3_vtab_distinct() interface. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix vtabdistinct + +ifcapable !vtab { + finish_test + return +} +load_static_extension db qpvtab + +do_execsql_test 1.1 { + SELECT ix FROM qpvtab WHERE vn='sqlite3_vtab_distinct'; +} {0} +do_execsql_test 1.2 { + SELECT DISTINCT ix FROM qpvtab WHERE vn='sqlite3_vtab_distinct'; +} {2} +do_execsql_test 1.3 { + SELECT distinct vn, ix FROM qpvtab(3) + WHERE +vn IN ('sqlite3_vtab_distinct','nOrderBy'); +} {nOrderBy 2 sqlite3_vtab_distinct 2} +do_execsql_test 1.4 { + SELECT vn, ix FROM qpvtab + GROUP BY vn + HAVING vn='sqlite3_vtab_distinct'; +} {sqlite3_vtab_distinct 1} + +finish_test diff --git a/test/vtabrhs1.test b/test/vtabrhs1.test new file mode 100644 index 000000000..0476a36f9 --- /dev/null +++ b/test/vtabrhs1.test @@ -0,0 +1,76 @@ +# 2022-01-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 for sqlite3_vtab_rhs_value() interface. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix vtabrhs1 + +ifcapable !vtab { + finish_test + return +} +load_static_extension db qpvtab + +# EVIDENCE-OF: R-60223-49197 When the sqlite3_vtab_rhs_value(P,J,V) +# interface is invoked from within the xBestIndex method of a virtual +# table implementation, with P being a copy of the sqlite3_index_info +# object pointer passed into xBestIndex and J being a 0-based index into +# P->aConstraint[], then this routine attempts to set *V to the value +# of the right-hand operand of that constraint if the right-hand operand +# is known. +# +do_execsql_test 1.1 { + SELECT rhs FROM qpvtab + WHERE cn='a' + AND a=12345 +} {12345} +do_execsql_test 1.2 { + SELECT rhs FROM qpvtab + WHERE cn='a' + AND a<>4.5 +} {4.5} +do_execsql_test 1.3 { + SELECT rhs FROM qpvtab + WHERE cn='a' + AND 'quokka' < a +} {'quokka'} +do_execsql_test 1.4 { + SELECT rhs FROM qpvtab + WHERE cn='a' + AND a IS NULL +} {{}} +do_execsql_test 1.5 { + SELECT rhs FROM qpvtab + WHERE cn='a' + AND a GLOB x'0123' +} {x'0123'} + +# EVIDENCE-OF: R-37799-62852 If the right-hand operand is not known, +# then *V is set to a NULL pointer. +# +do_execsql_test 2.1 { + SELECT typeof(rhs) FROM qpvtab WHERE cn='a' AND a=format('abc'); +} {null} +do_execsql_test 2.2 { + SELECT typeof(rhs) FROM qpvtab WHERE cn='a' AND a=?2 +} {null} + +# EVIDENCE-OF: R-14553-25174 When xBestIndex returns, the sqlite3_value +# object returned by sqlite3_vtab_rhs_value() is automatically +# deallocated. +# +# Where this not the case, the following "finish_test" statement would +# report a memory leak. +# +finish_test diff --git a/test/walro2.test b/test/walro2.test index 34408c169..1eb9d1c3e 100644 --- a/test/walro2.test +++ b/test/walro2.test @@ -50,357 +50,362 @@ do_execsql_test 0.0 { CREATE TABLE t1(x); } {wal} set MINSHMSZ [file size test.db-shm] +set dfltpgsz [db one {PRAGMA page_size}] foreach bZeroShm {0 1} { -set TN [expr $bZeroShm+1] -do_multiclient_test tn { - - # Close all connections and delete the database. - # - code1 { db close } - code2 { db2 close } - code3 { db3 close } - forcedelete test.db - - # Do not run tests with the connections in the same process. - # - if {$tn==2} continue - - foreach c {code1 code2 code3} { - $c { - sqlite3_shutdown - sqlite3_config_uri 1 - } - } - - do_test $TN.1.1 { - code2 { sqlite3 db2 test.db } - sql2 { - CREATE TABLE t1(x, y); - PRAGMA journal_mode = WAL; - INSERT INTO t1 VALUES('a', 'b'); - INSERT INTO t1 VALUES('c', 'd'); - } - file exists test.db-shm - } {1} - - do_test $TN.1.2.1 { - copy_to_test2 $bZeroShm - code1 { - sqlite3 db file:test.db2?readonly_shm=1 - } - - sql1 { SELECT * FROM t1 } - } {a b c d} - do_test $TN.1.2.2 { - sql1 { SELECT * FROM t1 } - } {a b c d} - - do_test $TN.1.3.1 { - code3 { sqlite3 db3 test.db2 } - sql3 { SELECT * FROM t1 } - } {a b c d} - - do_test $TN.1.3.2 { - sql1 { SELECT * FROM t1 } - } {a b c d} - - code1 { db close } - code2 { db2 close } - code3 { db3 close } - - do_test $TN.2.1 { - code2 { sqlite3 db2 test.db } - sql2 { - INSERT INTO t1 VALUES('e', 'f'); - INSERT INTO t1 VALUES('g', 'h'); - } - file exists test.db-shm - } {1} - - do_test $TN.2.2 { - copy_to_test2 $bZeroShm - code1 { - sqlite3 db file:test.db2?readonly_shm=1 - } - sql1 { - BEGIN; - SELECT * FROM t1; - } - } {a b c d e f g h} - - do_test $TN.2.3.1 { - code3 { sqlite3 db3 test.db2 } - sql3 { SELECT * FROM t1 } - } {a b c d e f g h} - do_test $TN.2.3.2 { - sql3 { INSERT INTO t1 VALUES('i', 'j') } - code3 { db3 close } - sql1 { COMMIT } - } {} - do_test $TN.2.3.3 { - sql1 { SELECT * FROM t1 } - } {a b c d e f g h i j} - - - #----------------------------------------------------------------------- - # 3.1.*: That a readonly_shm connection can read a database file if both - # the *-wal and *-shm files are zero bytes in size. - # - # 3.2.*: That it flushes the cache if, between transactions on a db with a - # zero byte *-wal file, some other connection modifies the db, then - # does "PRAGMA wal_checkpoint=truncate" to truncate the wal file - # back to zero bytes in size. - # - # 3.3.*: That, if between transactions some other process wraps the wal - # file, the readonly_shm client reruns recovery. - # - catch { code1 { db close } } - catch { code2 { db2 close } } - catch { code3 { db3 close } } - do_test $TN.3.1.0 { - list [file exists test.db-wal] [file exists test.db-shm] - } {0 0} - do_test $TN.3.1.1 { - close [open test.db-wal w] - close [open test.db-shm w] - code1 { - sqlite3 db file:test.db?readonly_shm=1 - } - sql1 { SELECT * FROM t1 } - } {a b c d e f g h} - - do_test $TN.3.2.0 { - list [file size test.db-wal] [file size test.db-shm] - } {0 0} - do_test $TN.3.2.1 { - code2 { sqlite3 db2 test.db } - sql2 { INSERT INTO t1 VALUES(1, 2) ; PRAGMA wal_checkpoint=truncate } - code2 { db2 close } - sql1 { SELECT * FROM t1 } - } {a b c d e f g h 1 2} - do_test $TN.3.2.2 { - list [file size test.db-wal] [file size test.db-shm] - } [list 0 $MINSHMSZ] - - do_test $TN.3.3.0 { - code2 { sqlite3 db2 test.db } - sql2 { - INSERT INTO t1 VALUES(3, 4); - INSERT INTO t1 VALUES(5, 6); - INSERT INTO t1 VALUES(7, 8); - INSERT INTO t1 VALUES(9, 10); - } - code2 { db2 close } - code1 { db close } - list [file size test.db-wal] [file size test.db-shm] - } [list [wal_file_size 4 1024] $MINSHMSZ] - do_test $TN.3.3.1 { - code1 { sqlite3 db file:test.db?readonly_shm=1 } - sql1 { SELECT * FROM t1 } - } {a b c d e f g h 1 2 3 4 5 6 7 8 9 10} - do_test $TN.3.3.2 { - code2 { sqlite3 db2 test.db } - sql2 { - PRAGMA wal_checkpoint; - DELETE FROM t1; - INSERT INTO t1 VALUES('i', 'ii'); - } - code2 { db2 close } - list [file size test.db-wal] [file size test.db-shm] - } [list [wal_file_size 4 1024] $MINSHMSZ] - do_test $TN.3.3.3 { - sql1 { SELECT * FROM t1 } - } {i ii} - - #----------------------------------------------------------------------- - # - # - catch { code1 { db close } } - catch { code2 { db2 close } } - catch { code3 { db3 close } } - - do_test $TN.4.0 { - code1 { forcedelete test.db } - code1 { sqlite3 db test.db } - sql1 { - PRAGMA journal_mode = wal; - CREATE TABLE t1(x); - INSERT INTO t1 VALUES('hello'); - INSERT INTO t1 VALUES('world'); - } - - copy_to_test2 $bZeroShm - - code1 { db close } - } {} - - do_test $TN.4.1.1 { - code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } - sql2 { SELECT * FROM t1 } - } {hello world} - - do_test $TN.4.1.2 { - code3 { sqlite3 db3 test.db2 } - sql3 { - INSERT INTO t1 VALUES('!'); - PRAGMA wal_checkpoint = truncate; - } - code3 { db3 close } - } {} - do_test $TN.4.1.3 { - sql2 { SELECT * FROM t1 } - } {hello world !} - - catch { code1 { db close } } - catch { code2 { db2 close } } - catch { code3 { db3 close } } - - do_test $TN.4.2.1 { - code1 { sqlite3 db test.db } - sql1 { - INSERT INTO t1 VALUES('!'); - INSERT INTO t1 VALUES('!'); - - PRAGMA cache_size = 10; - CREATE TABLE t2(x); - - BEGIN; - WITH s(i) AS ( - SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500 - ) - INSERT INTO t2 SELECT randomblob(500) FROM s; - SELECT count(*) FROM t2; - } - } {500} - set sz [file size test.db-wal] - do_test $TN.4.2.2.(sz=$sz) { - expr {$sz>400000} - } {1} - do_test $TN.4.2.4 { - file_control_persist_wal db 1; db close - - copy_to_test2 $bZeroShm - code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } - sql2 { - SELECT * FROM t1; - SELECT count(*) FROM t2; - } - } {hello world ! ! 0} - - #----------------------------------------------------------------------- - # - # - catch { code1 { db close } } - catch { code2 { db2 close } } - catch { code3 { db3 close } } - - do_test $TN.5.0 { - code1 { forcedelete test.db } - code1 { sqlite3 db test.db } - sql1 { - PRAGMA journal_mode = wal; - CREATE TABLE t1(x); - INSERT INTO t1 VALUES('hello'); - INSERT INTO t1 VALUES('world'); - INSERT INTO t1 VALUES('!'); - INSERT INTO t1 VALUES('world'); - INSERT INTO t1 VALUES('hello'); - } - - copy_to_test2 $bZeroShm + for {set pgsz 512} {$pgsz<=65536} {set pgsz [expr {$pgsz*2}]} { + set TN [expr $bZeroShm+1]-$pgsz + do_multiclient_test tn { + + # Close all connections and delete the database. + # + code1 { db close } + code2 { db2 close } + code3 { db3 close } + forcedelete test.db + + # Do not run tests with the connections in the same process. + # + if {$tn==2} continue - code1 { db close } - } {} - - do_test $TN.5.1 { - code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } - sql2 { - SELECT * FROM t1; - } - } {hello world ! world hello} - - do_test $TN.5.2 { - code1 { - proc handle_read {op args} { - if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} { - set ::res2 [sql2 { SELECT * FROM t1 }] + foreach c {code1 code2 code3} { + $c { + sqlite3_shutdown + sqlite3_config_uri 1 } - puts "$msg xRead $args" - return "SQLITE_OK" } - testvfs tvfs -fullshm 1 - - sqlite3 db file:test.db2?vfs=tvfs - db eval { SELECT * FROM sqlite_master } - - tvfs filter xRead - tvfs script handle_read - } - sql1 { - PRAGMA wal_checkpoint = truncate; - } - code1 { set ::res2 } - } {hello world ! world hello} - - do_test $TN.5.3 { - code1 { db close } - code1 { tvfs delete } - } {} - - #----------------------------------------------------------------------- - # - # - catch { code1 { db close } } - catch { code2 { db2 close } } - catch { code3 { db3 close } } - - do_test $TN.6.1 { - code1 { forcedelete test.db } - code1 { sqlite3 db test.db } - sql1 { - PRAGMA journal_mode = wal; - CREATE TABLE t1(x); - INSERT INTO t1 VALUES('hello'); - INSERT INTO t1 VALUES('world'); - INSERT INTO t1 VALUES('!'); - INSERT INTO t1 VALUES('world'); - INSERT INTO t1 VALUES('hello'); - } - - copy_to_test2 $bZeroShm - code1 { db close } - } {} - - do_test $TN.6.2 { - code1 { - set ::nRem 5 - proc handle_read {op args} { - if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} { - incr ::nRem -1 - if {$::nRem==0} { - code2 { sqlite3 db2 test.db2 } - sql2 { PRAGMA wal_checkpoint = truncate } + do_test $TN.1.1 { + code2 { sqlite3 db2 test.db } + sql2 "PRAGMA page_size=$::pgsz" + sql2 { + CREATE TABLE t1(x, y); + PRAGMA journal_mode = WAL; + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t1 VALUES('c', 'd'); + } + file exists test.db-shm + } {1} + + do_test $TN.1.2.1 { + copy_to_test2 $bZeroShm + code1 { + sqlite3 db file:test.db2?readonly_shm=1 + } + + sql1 { SELECT * FROM t1 } + } {a b c d} + do_test $TN.1.2.2 { + sql1 { SELECT * FROM t1 } + } {a b c d} + + do_test $TN.1.3.1 { + code3 { sqlite3 db3 test.db2 } + sql3 { SELECT * FROM t1 } + } {a b c d} + + do_test $TN.1.3.2 { + sql1 { SELECT * FROM t1 } + } {a b c d} + + code1 { db close } + code2 { db2 close } + code3 { db3 close } + + do_test $TN.2.1 { + code2 { sqlite3 db2 test.db } + sql2 "PRAGMA page_size=$::pgsz;" + sql2 { + INSERT INTO t1 VALUES('e', 'f'); + INSERT INTO t1 VALUES('g', 'h'); + } + file exists test.db-shm + } {1} + + do_test $TN.2.2 { + copy_to_test2 $bZeroShm + code1 { + sqlite3 db file:test.db2?readonly_shm=1 + } + sql1 { + BEGIN; + SELECT * FROM t1; + } + } {a b c d e f g h} + + do_test $TN.2.3.1 { + code3 { sqlite3 db3 test.db2 } + sql3 { SELECT * FROM t1 } + } {a b c d e f g h} + do_test $TN.2.3.2 { + sql3 { INSERT INTO t1 VALUES('i', 'j') } + code3 { db3 close } + sql1 { COMMIT } + } {} + do_test $TN.2.3.3 { + sql1 { SELECT * FROM t1 } + } {a b c d e f g h i j} + + + #----------------------------------------------------------------------- + # 3.1.*: That a readonly_shm connection can read a database file if both + # the *-wal and *-shm files are zero bytes in size. + # + # 3.2.*: That it flushes the cache if, between transactions on a db with a + # zero byte *-wal file, some other connection modifies the db, then + # does "PRAGMA wal_checkpoint=truncate" to truncate the wal file + # back to zero bytes in size. + # + # 3.3.*: That, if between transactions some other process wraps the wal + # file, the readonly_shm client reruns recovery. + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + do_test $TN.3.1.0 { + list [file exists test.db-wal] [file exists test.db-shm] + } {0 0} + do_test $TN.3.1.1 { + close [open test.db-wal w] + close [open test.db-shm w] + code1 { + sqlite3 db file:test.db?readonly_shm=1 + } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h} + + do_test $TN.3.2.0 { + list [file size test.db-wal] [file size test.db-shm] + } {0 0} + do_test $TN.3.2.1 { + code2 { sqlite3 db2 test.db } + sql2 { INSERT INTO t1 VALUES(1, 2) ; PRAGMA wal_checkpoint=truncate } + code2 { db2 close } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h 1 2} + if {$pgsz!=$dfltpgsz} continue + do_test $TN.3.2.2 { + list [file size test.db-wal] [file size test.db-shm] + } [list 0 $MINSHMSZ] + do_test $TN.3.3.0 { + code2 { sqlite3 db2 test.db } + sql2 { + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); + INSERT INTO t1 VALUES(7, 8); + INSERT INTO t1 VALUES(9, 10); + } + code2 { db2 close } + code1 { db close } + list [file size test.db-wal] [file size test.db-shm] + } [list [wal_file_size 4 1024] $MINSHMSZ] + do_test $TN.3.3.1 { + code1 { sqlite3 db file:test.db?readonly_shm=1 } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h 1 2 3 4 5 6 7 8 9 10} + do_test $TN.3.3.2 { + code2 { sqlite3 db2 test.db } + sql2 { + PRAGMA wal_checkpoint; + DELETE FROM t1; + INSERT INTO t1 VALUES('i', 'ii'); + } + code2 { db2 close } + list [file size test.db-wal] [file size test.db-shm] + } [list [wal_file_size 4 1024] $MINSHMSZ] + do_test $TN.3.3.3 { + sql1 { SELECT * FROM t1 } + } {i ii} + + #----------------------------------------------------------------------- + # + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.4.0 { + code1 { forcedelete test.db } + code1 { sqlite3 db test.db } + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + } + + copy_to_test2 $bZeroShm + + code1 { db close } + } {} + + do_test $TN.4.1.1 { + code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } + sql2 { SELECT * FROM t1 } + } {hello world} + + do_test $TN.4.1.2 { + code3 { sqlite3 db3 test.db2 } + sql3 { + INSERT INTO t1 VALUES('!'); + PRAGMA wal_checkpoint = truncate; + } + code3 { db3 close } + } {} + do_test $TN.4.1.3 { + sql2 { SELECT * FROM t1 } + } {hello world !} + + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.4.2.1 { + code1 { sqlite3 db test.db } + sql1 { + INSERT INTO t1 VALUES('!'); + INSERT INTO t1 VALUES('!'); + + PRAGMA cache_size = 10; + CREATE TABLE t2(x); + + BEGIN; + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500 + ) + INSERT INTO t2 SELECT randomblob(500) FROM s; + SELECT count(*) FROM t2; + } + } {500} + set sz [file size test.db-wal] + do_test $TN.4.2.2.(sz=$sz) { + expr {$sz>400000} + } {1} + do_test $TN.4.2.4 { + file_control_persist_wal db 1; db close + + copy_to_test2 $bZeroShm + code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } + sql2 { + SELECT * FROM t1; + SELECT count(*) FROM t2; + } + } {hello world ! ! 0} + + #----------------------------------------------------------------------- + # + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.5.0 { + code1 { forcedelete test.db } + code1 { sqlite3 db test.db } + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('!'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('hello'); + } + + copy_to_test2 $bZeroShm + + code1 { db close } + } {} + + do_test $TN.5.1 { + code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } + sql2 { + SELECT * FROM t1; + } + } {hello world ! world hello} + + do_test $TN.5.2 { + code1 { + proc handle_read {op args} { + if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} { + set ::res2 [sql2 { SELECT * FROM t1 }] + } + puts "$msg xRead $args" + return "SQLITE_OK" } + testvfs tvfs -fullshm 1 + + sqlite3 db file:test.db2?vfs=tvfs + db eval { SELECT * FROM sqlite_master } + + tvfs filter xRead + tvfs script handle_read } - return "SQLITE_OK" - } - testvfs tvfs -fullshm 1 - - tvfs filter xRead - tvfs script handle_read - - sqlite3 db file:test.db2?readonly_shm=1&vfs=tvfs - db eval { SELECT * FROM t1 } + sql1 { + PRAGMA wal_checkpoint = truncate; + } + code1 { set ::res2 } + } {hello world ! world hello} + + do_test $TN.5.3 { + code1 { db close } + code1 { tvfs delete } + } {} + + #----------------------------------------------------------------------- + # + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.6.1 { + code1 { forcedelete test.db } + code1 { sqlite3 db test.db } + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('!'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('hello'); + } + + copy_to_test2 $bZeroShm + + code1 { db close } + } {} + + do_test $TN.6.2 { + code1 { + set ::nRem 5 + proc handle_read {op args} { + if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} { + incr ::nRem -1 + if {$::nRem==0} { + code2 { sqlite3 db2 test.db2 } + sql2 { PRAGMA wal_checkpoint = truncate } + } + } + return "SQLITE_OK" + } + testvfs tvfs -fullshm 1 + + tvfs filter xRead + tvfs script handle_read + + sqlite3 db file:test.db2?readonly_shm=1&vfs=tvfs + db eval { SELECT * FROM t1 } + } + } {hello world ! world hello} + + do_test $TN.6.3 { + code1 { db close } + code1 { tvfs delete } + } {} } - } {hello world ! world hello} - - do_test $TN.6.3 { - code1 { db close } - code1 { tvfs delete } - } {} -} + } ;# for pgsz } ;# foreach bZeroShm finish_test diff --git a/test/where7.test b/test/where7.test index 830530634..f76e8aba8 100644 --- a/test/where7.test +++ b/test/where7.test @@ -23361,4 +23361,28 @@ do_eqp_test where7-3.2 { `--USE TEMP B-TREE FOR ORDER BY } +# 2022-03-03 https://sqlite.org/forum/forumpost/36937b197273d403 +# +# In the multi-index OR, if there is an auxiliary WHERE clause term +# that includes a subquery and that subquery is pushed down into the +# OR-clause subqueries, WHERE subquery might get coded as a subroutine. +# In that case, the covering-index optimizer will attempt to change +# table-references into index-references. But it will do so for the +# index of the OR branch in which the subquery is coded. If the +# subquery subroutine is called from a different OR branch, the +# index might be different and the index-reference will no longer +# work. tag-20220303a +# +reset_db +do_execsql_test 4.1 { + CREATE TABLE t0(w); + INSERT INTO t0(w) VALUES(1); + CREATE TABLE t1(x INT, y INT PRIMARY KEY, z); + INSERT INTO t1 VALUES(0,111,222); + CREATE INDEX t1zxy ON t1(z,x,y); + SELECT y FROM t1 + WHERE (z=222 OR y=111) + AND (false OR EXISTS(SELECT 1 FROM t0 WHERE t1.y)); +} {111} + finish_test diff --git a/test/window6.test b/test/window6.test index c6889c72a..b5e677208 100644 --- a/test/window6.test +++ b/test/window6.test @@ -148,6 +148,7 @@ do_execsql_test 5.5 { # ifcapable !icu { + sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 do_execsql_test 6.0 { SELECT LIKE('!', '', '!') x WHERE x; } {} diff --git a/test/without_rowid1.test b/test/without_rowid1.test index 119111552..3c33f733a 100644 --- a/test/without_rowid1.test +++ b/test/without_rowid1.test @@ -475,15 +475,16 @@ ifcapable altertable { # Omit an assert() from 2013 that no longer serves any purpose and # is no longer always true. # -reset_db -do_execsql_test 15.1 { - PRAGMA writable_schema=ON; - CREATE TABLE sqlite_sequence (name PRIMARY KEY) WITHOUT ROWID; - PRAGMA writable_schema=OFF; - CREATE TABLE c1(x); - INSERT INTO sqlite_sequence(name) VALUES('c0'),('c1'),('c2'); - ALTER TABLE c1 RENAME TO a; - SELECT name FROM sqlite_sequence ORDER BY +name; -} {a c0 c2} - +ifcapable altertable { + reset_db + do_execsql_test 15.1 { + PRAGMA writable_schema=ON; + CREATE TABLE sqlite_sequence (name PRIMARY KEY) WITHOUT ROWID; + PRAGMA writable_schema=OFF; + CREATE TABLE c1(x); + INSERT INTO sqlite_sequence(name) VALUES('c0'),('c1'),('c2'); + ALTER TABLE c1 RENAME TO a; + SELECT name FROM sqlite_sequence ORDER BY +name; + } {a c0 c2} +} finish_test diff --git a/tool/lemon.c b/tool/lemon.c index d4e157a1c..11e25c8a1 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -4290,7 +4290,6 @@ void ReportTable( int sqlFlag /* Generate the *.sql file too */ ){ FILE *out, *in, *sql; - char line[LINESIZE]; int lineno; struct state *stp; struct action *ap; @@ -4765,7 +4764,6 @@ void ReportTable( /* Generate a table containing the symbolic name of every symbol */ for(i=0; insymbol; i++){ - lemon_sprintf(line,"\"%s\",",lemp->symbols[i]->name); fprintf(out," /* %4d */ \"%s\",\n",i, lemp->symbols[i]->name); lineno++; } tplt_xfer(lemp->name,in,out,&lineno); diff --git a/tool/logest.c b/tool/logest.c index 8ed0b19ec..d916b43d3 100644 --- a/tool/logest.c +++ b/tool/logest.c @@ -75,9 +75,7 @@ static sqlite3_uint64 logEstToInt(LogEst x){ x /= 10; if( n>=5 ) n -= 2; else if( n>=1 ) n -= 1; - if( x>60 ){ - return (((sqlite3_uint64)0xffffffff)<<32)+(sqlite3_uint64)0xffffffff; - } + if( x>60 ) return (((sqlite3_uint64)0xffffffff)<<32)+(sqlite3_uint64)0xffffffff; if( x>=3 ) return (n+8)<<(x-3); return (n+8)>>(3-x); } @@ -152,7 +150,7 @@ int main(int argc, char **argv){ }else if( z[0]=='^' ){ a[n++] = (LogEst)atoi(z+1); }else if( isInteger(z) ){ - a[n++] = logEstFromInteger(atoi(z)); + a[n++] = logEstFromInteger(atoll(z)); }else if( isFloat(z) && z[0]!='-' ){ a[n++] = logEstFromDouble(atof(z)); }else{ @@ -164,6 +162,8 @@ int main(int argc, char **argv){ printf("%5d (%f)\n", a[i], 1.0/(double)logEstToInt(-a[i])); }else if( a[i]<10 ){ printf("%5d (%f)\n", a[i], logEstToInt(a[i]+100)/1024.0); + }else if( a[i]>100 ){ + printf("%5d (%lld)\n", a[i], logEstToInt(a[i])); }else{ sqlite3_uint64 x = logEstToInt(a[i]+100)*100/1024; printf("%5d (%lld.%02lld)\n", a[i], x/100, x%100); diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl old mode 100644 new mode 100755 index 629460945..745a59a22 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -5,9 +5,82 @@ # const char **azCompileOpt[] # # definition used in src/ctime.c, run this script from -# the checkout root. It alters src/ctime.c in-place. +# the checkout root. It generates src/ctime.c . # + +set ::headWarning {/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkctimec.tcl. +** +** To modify this header, edit any of the various lists in that script +** which specify categories of generated conditionals in this file. +*/} + +# Make { and } easier to put into literals (even on EBCDIC machines.) +regexp {(\{)(\})} "{}" ma ::lb ::rb + +set ::headCode " +/* +** 2010 February 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. +** +************************************************************************* +** +** This file implements routines used to report what compile-time options +** SQLite was built with. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS /* IMP: R-16824-07538 */ + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) +#include \"config.h\" +#define SQLITECONFIG_H 1 +#endif + +/* These macros are provided to \"stringify\" the value of the define +** for those options in which the value is meaningful. */ +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) + +/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This +** option requires a separate macro because legal values contain a single +** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE=\"100,100\") */ +#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 \",\" #opt2 +#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt) +#include \"sqliteInt.h\" + +/* +** An array of names of all compile-time options. This array should +** be sorted A-Z. +** +** This array looks large, but in a typical installation actually uses +** only a handful of compile-time options, so most times this array is usually +** rather short and uses little memory space. +*/ +static const char * const sqlite3azCompileOpt\[\] = $::lb +" + +set ::tailCode " +$::rb ; + +const char **sqlite3CompileOptions(int *pnOpt){ + *pnOpt = sizeof(sqlite3azCompileOpt) / sizeof(sqlite3azCompileOpt\[0\]); + return (const char**)sqlite3azCompileOpt; +} + +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ +" + # All Boolean compile time options which default to something # other than 0 or empty. The default is paired with the PP # symbol so that a differing define can be detected. @@ -70,7 +143,6 @@ set boolean_defnil_options { SQLITE_ENABLE_HIDDEN_COLUMNS SQLITE_ENABLE_ICU SQLITE_ENABLE_IOTRACE - SQLITE_ENABLE_JSON1 SQLITE_ENABLE_LOAD_EXTENSION SQLITE_ENABLE_LOCKING_STYLE SQLITE_ENABLE_MATH_FUNCTIONS @@ -151,6 +223,7 @@ set boolean_defnil_options { SQLITE_OMIT_INCRBLOB SQLITE_OMIT_INTEGRITY_CHECK SQLITE_OMIT_INTROSPECTION_PRAGMAS + SQLITE_OMIT_JSON SQLITE_OMIT_LIKE_OPTIMIZATION SQLITE_OMIT_LOAD_EXTENSION SQLITE_OMIT_LOCALTIME @@ -354,60 +427,18 @@ foreach v $value2_options { }] } -# Split a string on a regex, return all parts in order. -# Any elements with an even index may be empty. -# Elements with odd indices will match the regex. -proc split_on_re {re str {nrepps 1}} { - set chunks {} - set cix 0 - set resm [regexp -all -inline -indices $re $str] - if {[llength $resm]==0} { - return $str - } - set rix 0 - while {$rix < [llength $resm]} { - set mre [lindex $resm $rix] - incr rix $nrepps - set mbx [lindex $mre 0] - set mex [lindex $mre 1] - lappend chunks [string range $str $cix [expr $mbx - 1]] - lappend chunks [string range $str $mbx $mex] - set cix [expr $mex + 1] - } - lappend chunks [string range $str $cix end] - return $chunks -} - - set ctime_c "src/ctime.c" -if {[catch {set cfd [open $ctime_c r]}]!=0} { - puts stderr "File '$ctime_c' unreadable. Run this script from checkout root." - exit 1; -} - -set ctfc [read $cfd] -close $cfd - -set re {/\*\s+\*+\s*((BEGIN)|(END)) CODE GENERATED BY (\S+)\s+\*/\s+} -set renpp 5 - -set ctfcChunks [split_on_re $re $ctfc $renpp] -if {[llength $ctfcChunks] != 5} { - puts stderr "File '$ctime_c' has too few generated code markers." - exit 1; -} if {[catch {set cfd [open $ctime_c w]}]!=0} { puts stderr "File '$ctime_c' unwritable." exit 1; } -puts -nonewline $cfd [lindex $ctfcChunks 0] -puts -nonewline $cfd [lindex $ctfcChunks 1] +puts $cfd $::headWarning; +puts $cfd $::headCode; foreach o [lsort [array names options]] { puts $cfd [string trim $options($o)] } -puts -nonewline $cfd [lindex $ctfcChunks 3] -puts -nonewline $cfd [lindex $ctfcChunks 4] +puts -nonewline $cfd $::tailCode; close $cfd diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 9d59e17b1..1a35fc0ea 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -429,7 +429,7 @@ foreach file { fts3_unicode.c fts3_unicode2.c - json1.c + json.c rtree.c icu.c fts3_icu.c diff --git a/tool/omittest.tcl b/tool/omittest.tcl index 9f24d6d3d..b54f2984d 100644 --- a/tool/omittest.tcl +++ b/tool/omittest.tcl @@ -218,6 +218,7 @@ proc main {argv} { SQLITE_OMIT_INCRBLOB \ SQLITE_OMIT_INTEGRITY_CHECK \ SQLITE_OMIT_INTROSPECTION_PRAGMAS \ + SQLITE_OMIT_JSON \ SQLITE_OMIT_LIKE_OPTIMIZATION \ SQLITE_OMIT_LOAD_EXTENSION \ SQLITE_OMIT_LOCALTIME \ diff --git a/tool/sqldiff.c b/tool/sqldiff.c index da444bf9a..0a017037c 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -407,31 +407,31 @@ static void printQuoted(FILE *out, sqlite3_value *X){ if( zArg==0 ){ fprintf(out, "NULL"); }else{ - int inctl = 0; - int i, j; + int inctl = 0; + int i, j; fprintf(out, "'"); - for(i=j=0; zArg[i]; i++){ - char c = zArg[i]; - int ctl = iscntrl(c); - if( ctl>inctl ){ - inctl = ctl; - fprintf(out, "%.*s'||X'%02x", i-j, &zArg[j], c); - j = i+1; - }else if( ctl ){ - fprintf(out, "%02x", c); - j = i+1; - }else{ - if( inctl ){ - inctl = 0; - fprintf(out, "'\n||'"); - } - if( c=='\'' ){ - fprintf(out, "%.*s'", i-j+1, &zArg[j]); - j = i+1; - } - } - } - fprintf(out, "%s'", &zArg[j]); + for(i=j=0; zArg[i]; i++){ + char c = zArg[i]; + int ctl = iscntrl(c); + if( ctl>inctl ){ + inctl = ctl; + fprintf(out, "%.*s'||X'%02x", i-j, &zArg[j], c); + j = i+1; + }else if( ctl ){ + fprintf(out, "%02x", c); + j = i+1; + }else{ + if( inctl ){ + inctl = 0; + fprintf(out, "'\n||'"); + } + if( c=='\'' ){ + fprintf(out, "%.*s'", i-j+1, &zArg[j]); + j = i+1; + } + } + } + fprintf(out, "%s'", &zArg[j]); } break; } diff --git a/tool/vdbe-compress.tcl b/tool/vdbe-compress.tcl index 9477f4afe..5e63c96a9 100644 --- a/tool/vdbe-compress.tcl +++ b/tool/vdbe-compress.tcl @@ -65,7 +65,7 @@ while {![eof stdin]} { # set vlist {} set seenDecl 0 -set namechars {abcdefghijklmnopqrstuvwxyz} +set namechars {abcefghjklmnopqrstuvwxyz} set nnc [string length $namechars] while {![eof stdin]} { set line [gets stdin] diff --git a/tool/warnings.sh b/tool/warnings.sh index 0754a0f3f..a839d235b 100644 --- a/tool/warnings.sh +++ b/tool/warnings.sh @@ -20,7 +20,7 @@ echo '********** No optimizations. Includes FTS4/5, GEOPOLY, JSON1 ***' echo '********** ' Options: $WARNING_OPTS gcc -c $WARNING_OPTS -std=c89 \ -ansi -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_GEOPOLY \ - -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_JSON1 \ + -DSQLITE_ENABLE_FTS5 \ sqlite3.c if test x`uname` = 'xLinux'; then echo '********** Android configuration ******************************' @@ -56,5 +56,5 @@ echo '********** Optimized -O3. Includes FTS4/5, GEOPOLY, JSON1 ******' echo '********** ' Options: $WARNING_OPTS gcc -O3 -c $WARNING_OPTS -std=c89 \ -ansi -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_GEOPOLY \ - -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_JSON1 \ + -DSQLITE_ENABLE_FTS5 \ sqlite3.c From 814b062188a58ff908208497f94dcc5e71d2f3ee Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 27 Apr 2022 14:07:22 -0400 Subject: [PATCH 066/340] Snapshot of upstream SQLite 3.38.3 --- VERSION | 2 +- configure | 18 +++++++-------- manifest | 48 +++++++++++++++++++------------------- manifest.uuid | 2 +- src/btree.c | 6 +++-- src/expr.c | 32 ++++++++++++++++++++++++++ src/json.c | 44 +++++++++++++++++++++++++++-------- src/malloc.c | 3 ++- src/select.c | 19 +++++++-------- src/shell.c.in | 3 ++- src/sqliteInt.h | 1 + src/vdbemem.c | 6 ++++- src/where.c | 5 ++-- src/window.c | 2 +- test/autoindex1.test | 20 ++++++++++++++-- test/corruptL.test | 55 ++++++++++++++++++++++++++++++++++++++++++++ test/json101.test | 17 ++++++++++++++ test/shell2.test | 9 ++++++++ test/windowB.test | 45 ++++++++++++++++++++++++++++++++++++ 19 files changed, 272 insertions(+), 65 deletions(-) diff --git a/VERSION b/VERSION index 9c4d7c1ff..e641704a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.38.2 +3.38.3 diff --git a/configure b/configure index 30c2e50c2..0ad56dd00 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.69 for sqlite 3.38.2. +# Generated by GNU Autoconf 2.69 for sqlite 3.38.3. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.38.2' -PACKAGE_STRING='sqlite 3.38.2' +PACKAGE_VERSION='3.38.3' +PACKAGE_STRING='sqlite 3.38.3' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1468,7 +1468,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.38.2 to adapt to many kinds of systems. +\`configure' configures sqlite 3.38.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1533,7 +1533,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.38.2:";; + short | recursive ) echo "Configuration of sqlite 3.38.3:";; esac cat <<\_ACEOF @@ -1661,7 +1661,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.38.2 +sqlite configure 3.38.3 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2080,7 +2080,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.38.2, which was +It was created by sqlite $as_me 3.38.3, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12390,7 +12390,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.38.2, which was +This file was extended by sqlite $as_me 3.38.3, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12456,7 +12456,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.38.2 +sqlite config.status 3.38.3 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/manifest b/manifest index f300e60e8..1f22eab23 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.38.2 -D 2022-03-26T13:51:10.240 +C Version\s3.38.3 +D 2022-04-27T12:03:15.047 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in b210ad2733317f1a4353085dfb9d385ceec30b0e6a61d20a5accabecac6b1949 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3 F README.md 2dd87a5c1d108b224921f3dd47dea567973f706e1f6959386282a626f459a70c -F VERSION 2c46bf3c621980e333221949b5ea8ce0bf5e6f0c328e6b228501ea3190abe467 +F VERSION b09048f5104cef7fbc4405635ec966be2481d532c0dbd46afd5c17f957de2dc0 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -34,7 +34,7 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 40cf509efcf3fe6062a5f6a25742a9622cfb6e2df127ba7b5e2400de41994dd8 x +F configure 1290cbee049f2db602990f1ed2a4eed248c36d1e41d23eed37ccdfd7b8bed13e x F configure.ac 3ef6eeff4387585bfcab76b0c3f6e15a0618587bb90245dd5d44e4378141bb35 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -492,7 +492,7 @@ F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7 F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6 -F src/btree.c 1ebe34ee736e05ead62bcf762a71c5779526356b0616dec67f20478d008e2eb4 +F src/btree.c 4ac832687f84d4c6c753f90274f7581f6c80a9ea69c2f045a5d07cb043cdf381 F src/btree.h 74d64b8f28cfa4a894d14d4ed64fa432cd697b98b61708d4351482ae15913e22 F src/btreeInt.h 8be97d3939d626f734ec1b577efa4e6e186da00daf5b3227af199ca1c24cdd71 F src/build.c a0cc68fe8172c0a31b54576f9c6c0fe6f7c82b1b5e1387afdd6a5a13132bc131 @@ -503,7 +503,7 @@ F src/date.c 15082566229d4b1e5f24fdb490bf9bcc68824b911d70e3573ef075a1b9e2d26f F src/dbpage.c 30ff075a9a9156bd2cccb5c1ea579f2afbaa64989648e8c9d72cb4a3417f5136 F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d F src/delete.c b5f1716b4d723db48254ee0f896e362cd029e865e05414139ea7f539f3884e1d -F src/expr.c b90a029105a93a93a0ed5e5f8c5eaed8f19043a3b62e4c4d235a4611d9ada178 +F src/expr.c 7b9d66f012ba22bda14261c9a4d0600f4b800fad4cf5868f6a32355f037880cc F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 06e4ac33031b02dde7130c12e79cddf4dc5cfa72b23d8e63a3c26878fc9c1d3c F src/func.c a3407a6fbb0d4088d8d502e46f0ace63e0aeae7467ae23a9ca9815bbf9239761 @@ -513,11 +513,11 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 6c0641efc3636732a02f45defe358899d695c065fc26fbba1b2cab415b5b2e57 -F src/json.c 225b00422112ecd7094a555f3ace16b25d7d5894062b823269ed03899907c2a2 +F src/json.c 7ad9c97f36c43176bf4bd1397647dfdca540850554569c302b5380bd1e35ccf5 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c aa919a6a7884f8b34d7b791841b24d14b1b0ab43f45b3940f4851043b2855c0c F src/main.c 0840cee6984034c7e73cc747a1562c7eaed4673694bf20b00980aaa0672c0405 -F src/malloc.c fec841aa0a0400a6f7d20706178a5d8e8219a6bf562b6fe712c17f6c26813266 +F src/malloc.c a9127efdcef92d6934c6339ea9813075b90edc0ce2e5c723556381a3828fb720 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 @@ -552,12 +552,12 @@ F src/printf.c 05d8dfd2018bc4fc3ddb8b37eb97ccef7abf985643fa1caebdcf2916ca90fa32 F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35 -F src/shell.c.in 69d1e59da4881f096ab47fbd3e6d99794f3e4a43f41fd9e4d2e845c9b8d20fd5 +F src/select.c ba0cd3d264fa788c8d65f063d5ec3e1d65b0dac206c822a564a59ba550210cfc +F src/shell.c.in ec4b952f7d9c02919e8e8394be16bcbff55c105fd080895602d10fc8fa5328b8 F src/sqlite.h.in b93deee892f1bc4030e5c8712df9e21d786a1bf8e921ab8dc987eaf1e44c676f F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 -F src/sqliteInt.h e7b93bb693cb14259f6c3faadc6d7c824ff509aac673622a76c161a96c5872d0 +F src/sqliteInt.h 272c5ca0a16a1963c0c956524c615a27af57be950b97a395d39cb9fd9b31fc4e F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -630,7 +630,7 @@ F src/vdbeInt.h b45599a2b59f1ce042512ab6786b0b82a8cf3002f6b0fa60b4834e2cd3ac61d8 F src/vdbeapi.c 8863ffb5a7bac42fe9a68aaa3526ee29fc18fb02a9b27188b756de41e33856e9 F src/vdbeaux.c 0d7659fe8cb38ce86092b9bc5131c99a834a04eb78745e54acb77d79d7af2fb5 F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd -F src/vdbemem.c 69d3092d47b7a3a466a161961abd92bca7ab148abd8b497438eb17c6afdcbad8 +F src/vdbemem.c 2db321ed3722f78c7f1e6743ff24ae68717fcc44c157817ae7eaf0a83439af98 F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c @@ -639,11 +639,11 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c 48d9836b7bdf301783719156fd50d45685a601605334035c559ed2d655df9fb6 +F src/where.c be30bf0151f5cc77b2d75a664f2ab60632974165ad2bab6df9deca2fc3284264 F src/whereInt.h 15d2975c3b4c193c78c26674400a840da8647fe1777ae3b026e2d15937b38a03 F src/wherecode.c 84be340684393248b9f3ecbce9b87c8a6f818149b52302702ea0b8d2a9d51faf F src/whereexpr.c 2a71f5491798460c9590317329234d332d9eb1717cba4f3403122189a75c465e -F src/window.c 731980c0887f7ec9859f5e0d3c69d5fbeb6e512a9e1d338935f53938eaba431e +F src/window.c 8546f510dfff88e4627f004969940c648acf91c13aa155229849f926a8137b9c F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test eecb0dabee4b7765a8465439d5e99429279ffba23ca74a7eae270a452799f9e7 @@ -704,7 +704,7 @@ F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec F test/autoinc.test 997d6f185f138229dc4251583a1d04816423dddc2fc034871a01aeb1d728cb39 -F test/autoindex1.test fe27af92eaf884bd9c38f94be3e8afa04ec494e5eefb189902026181a6175f5e +F test/autoindex1.test b2a87aca5c3717264ef431025c1eb855f6012c9694a3c85c678c762aa0dedf58 F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df F test/autoindex3.test 2d13958a5617e987624a428d7aed91bf51f322b49b476e3573fadec697ce6da5 F test/autoindex4.test 75cb1191a552b8201351f5a50d160fcb9387a0fbbfb820c77798bfee7da3f8cf @@ -813,7 +813,7 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test a17bbf54fdde78d43cf3cc34b0057719fd4a173a3d824285b67dc5257c064c7b F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test 7d3440831ca24ba64305583c4d4506d417d3f89f5775c0b7cc8102db078f8ff5 +F test/corruptL.test 21a951d1eb09120f3c1561af5bac30ed49be2d9dfcad039f71759c5d9e28a349 F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 F test/corruptN.test 60b5a62944b4f0029ba07edaa5fd8e670539d6b0a8d99db26c068d435675cbfe F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 @@ -1151,7 +1151,7 @@ F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ff F test/jrnlmode.test 9b5bc01dac22223cb60ec2d5f97acf568d73820794386de5634dcadbea9e1946 F test/jrnlmode2.test 8759a1d4657c064637f8b079592651530db738419e1d649c6df7048cd724363d F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa -F test/json101.test d7c84854acafaf80f883e183ac4248ea2742615086c94a61a46ad7d7382ce123 +F test/json101.test 9d46b8e254c4e23306da175dd226d5c4f164db6b294bcea98e5dcd891ba48c91 F test/json102.test 327e77275f338c028faefa2da5164daf6b142a165e3015ff2a6e4251ddc6a0ac F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test a502dc01853aada95d721b3b275afbe2dc18fffdac1fea6e96fb20c13586bbb5 @@ -1389,7 +1389,7 @@ F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6 -F test/shell2.test 89e4b2db062d52baed75022227b462d085cff495809de1699652779d8e0257d6 +F test/shell2.test 7a3a23a9f57b99453f1679b1fe8072cb30e382a622874c0c4d97695fadb0a787 F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759 F test/shell5.test 0a9920d81fae28c45cd5dbd1deb809487a23c5f4b422a49f9d31c85f926d4a9c @@ -1813,7 +1813,7 @@ F test/window8.tcl 5e02e41d9d9a80f597063aed1a381eb19d1d0ef677a4f0df352c5365cf23f F test/window8.test 4ab16817414af0c904abe2ebdf88eb6c2b00058b84f9748c6174ff11fc45f1ed F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae4836 F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be -F test/windowB.test b67bda5645f3226790e1a360c4225241840b84adb5aa2e69bfb0b27eef3b84d9 +F test/windowB.test f2fb42b864b0cf431c956407583e9478a74c3642bdf8737fdcb6ff4a40298b07 F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2 F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b @@ -1944,10 +1944,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4977d8f453bcd56fb1dfab02cc5b283fb32d402bafdf2b6112e7dd322b7c32ad -R da11ed5f90621f5fb7c0aa8d0a45881c -T +sym-release * -T +sym-version-3.38.2 * +P b9c7d2c1bdc529b8c367e4a0894f4c966957cfa4b427175d5aa10af6a25f4f12 +R 3239c458ee20a7ad3be4e144155c5ad4 +T +sym-relese * +T +sym-version-3.38.3 * U drh -Z 1c2a1cc2a9288218f72e9d4db2ebcd7a +Z 3eea06f2546971f0d6539e228b8fb587 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7696ab715..b7c942c5f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d33c709cc0af66bc5b6dc6216eba9f1f0b40960b9ae83694c986fbf4c1d6f08f +9547e2c38a1c6f751a77d4d796894dec4dc5d8f5d79b1cd39e1ffc50df7b3be4 diff --git a/src/btree.c b/src/btree.c index 116342390..d881b6220 100644 --- a/src/btree.c +++ b/src/btree.c @@ -1609,6 +1609,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ ** fragmented bytes within the page. */ memcpy(&aData[iAddr], &aData[pc], 2); aData[hdr+7] += (u8)x; + testcase( pc+x>maxPC ); + return &aData[pc]; }else if( x+pc > maxPC ){ /* This slot extends off the end of the usable part of the page */ *pRc = SQLITE_CORRUPT_PAGE(pPg); @@ -5808,7 +5810,7 @@ int sqlite3BtreeIndexMoveto( assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); assert( pPage->isInit ); if( pPage->leaf ){ - assert( pCur->ixpPage->nCell ); + assert( pCur->ixpPage->nCell || CORRUPT_DB ); pCur->ix = (u16)idx; *pRes = c; rc = SQLITE_OK; @@ -8332,7 +8334,7 @@ static int balance_nonroot( iOvflSpace += sz; assert( sz<=pBt->maxLocal+23 ); assert( iOvflSpace <= (int)pBt->pageSize ); - for(k=0; b.ixNx[k]<=i && ALWAYS(kiCursor. +** +** (2) pExpr cannot use subqueries or non-deterministic functions. +** +** (*) ** Not applicable to this branch ** +** +** (4) If pSrc is the right operand of a LEFT JOIN, then... +** (4a) pExpr must come from an ON clause.. +** (4b) and specifically the ON clause associated with the LEFT JOIN. +** +** (5) If pSrc is not the right operand of a LEFT JOIN or the left +** operand of a RIGHT JOIN, then pExpr must be from the WHERE +** clause, not an ON clause. +*/ +int sqlite3ExprIsTableConstraint(Expr *pExpr, const SrcItem *pSrc){ + if( pSrc->fg.jointype & JT_LEFT ){ + if( !ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (4a) */ + if( pExpr->w.iRightJoinTable!=pSrc->iCursor ) return 0; /* rule (4b) */ + }else{ + if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (5) */ + } + return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ +} + /* ** sqlite3WalkExpr() callback used by sqlite3ExprIsConstantOrGroupBy(). diff --git a/src/json.c b/src/json.c index ab1d32d6a..7dc55c84c 100644 --- a/src/json.c +++ b/src/json.c @@ -1119,14 +1119,15 @@ static JsonNode *jsonLookupStep( *pzErr = zPath; return 0; } + testcase( nKey==0 ); }else{ zKey = zPath; for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){} nKey = i; - } - if( nKey==0 ){ - *pzErr = zPath; - return 0; + if( nKey==0 ){ + *pzErr = zPath; + return 0; + } } j = 1; for(;;){ @@ -2274,6 +2275,33 @@ static int jsonEachNext(sqlite3_vtab_cursor *cur){ return SQLITE_OK; } +/* Append an object label to the JSON Path being constructed +** in pStr. +*/ +static void jsonAppendObjectPathElement( + JsonString *pStr, + JsonNode *pNode +){ + int jj, nn; + const char *z; + assert( pNode->eType==JSON_STRING ); + assert( pNode->jnFlags & JNODE_LABEL ); + assert( pNode->eU==1 ); + z = pNode->u.zJContent; + nn = pNode->n; + assert( nn>=2 ); + assert( z[0]=='"' ); + assert( z[nn-1]=='"' ); + if( nn>2 && sqlite3Isalpha(z[1]) ){ + for(jj=2; jjeType==JSON_OBJECT ); if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--; - assert( pNode->eType==JSON_STRING ); - assert( pNode->jnFlags & JNODE_LABEL ); - assert( pNode->eU==1 ); - jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1); + jsonAppendObjectPathElement(pStr, pNode); } } @@ -2372,8 +2397,7 @@ static int jsonEachColumn( if( p->eType==JSON_ARRAY ){ jsonPrintf(30, &x, "[%d]", p->iRowid); }else if( p->eType==JSON_OBJECT ){ - assert( pThis->eU==1 ); - jsonPrintf(pThis->n, &x, ".%.*s", pThis->n-2, pThis->u.zJContent+1); + jsonAppendObjectPathElement(&x, pThis); } } jsonResult(&x); diff --git a/src/malloc.c b/src/malloc.c index 21e524589..cfda60a0b 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -750,8 +750,9 @@ char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ ** Free any prior content in *pz and replace it with a copy of zNew. */ void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ + char *z = sqlite3DbStrDup(db, zNew); sqlite3DbFree(db, *pz); - *pz = sqlite3DbStrDup(db, zNew); + *pz = z; } /* diff --git a/src/select.c b/src/select.c index 6719d4747..d6d609740 100644 --- a/src/select.c +++ b/src/select.c @@ -4822,8 +4822,7 @@ static int pushDownWhereTerms( Parse *pParse, /* Parse context (for malloc() and error reporting) */ Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ Expr *pWhere, /* The WHERE clause of the outer query */ - int iCursor, /* Cursor number of the subquery */ - int isLeftJoin /* True if pSubq is the right term of a LEFT JOIN */ + SrcItem *pSrc /* The subquery term of the outer FROM clause */ ){ Expr *pNew; int nChng = 0; @@ -4858,10 +4857,11 @@ static int pushDownWhereTerms( return 0; /* restriction (3) */ } while( pWhere->op==TK_AND ){ - nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, - iCursor, isLeftJoin); + nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, pSrc); pWhere = pWhere->pLeft; } + +#if 0 /* Legacy code. Checks now done by sqlite3ExprIsTableConstraint() */ if( isLeftJoin && (ExprHasProperty(pWhere,EP_FromJoin)==0 || pWhere->w.iRightJoinTable!=iCursor) @@ -4873,7 +4873,9 @@ static int pushDownWhereTerms( ){ return 0; /* restriction (5) */ } - if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){ +#endif + + if( sqlite3ExprIsTableConstraint(pWhere, pSrc) ){ nChng++; pSubq->selFlags |= SF_PushDown; while( pSubq ){ @@ -4881,8 +4883,8 @@ static int pushDownWhereTerms( pNew = sqlite3ExprDup(pParse->db, pWhere, 0); unsetJoinExpr(pNew, -1); x.pParse = pParse; - x.iTable = iCursor; - x.iNewTable = iCursor; + x.iTable = pSrc->iCursor; + x.iNewTable = pSrc->iCursor; x.isLeftJoin = 0; x.pEList = pSubq->pEList; pNew = substExpr(&x, pNew); @@ -6664,8 +6666,7 @@ int sqlite3Select( if( OptimizationEnabled(db, SQLITE_PushDown) && (pItem->fg.isCte==0 || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) - && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor, - (pItem->fg.jointype & JT_OUTER)!=0) + && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem) ){ #if SELECTTRACE_ENABLED if( sqlite3SelectTrace & 0x100 ){ diff --git a/src/shell.c.in b/src/shell.c.in index 45cbb436e..c0b5898e6 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -11560,7 +11560,8 @@ static int process_input(ShellState *p){ qss = QSS_Start; } } - if( nSql && QSS_PLAINDARK(qss) ){ + if( nSql ){ + /* This may be incomplete. Let the SQL parser deal with that. */ errCnt += runOneSqlLine(p, zSql, p->in, startline); } free(zSql); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 3f11f1571..78c16bea1 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -4662,6 +4662,7 @@ int sqlite3ExprIsConstantNotJoin(Expr*); int sqlite3ExprIsConstantOrFunction(Expr*, u8); int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); int sqlite3ExprIsTableConstant(Expr*,int); +int sqlite3ExprIsTableConstraint(Expr*,const SrcItem*); #ifdef SQLITE_ENABLE_CURSOR_HINTS int sqlite3ExprContainsSubquery(Expr*); #endif diff --git a/src/vdbemem.c b/src/vdbemem.c index adbc5df99..184f811da 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -204,7 +204,11 @@ int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){ assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( desiredEnc==SQLITE_UTF8 || desiredEnc==SQLITE_UTF16LE || desiredEnc==SQLITE_UTF16BE ); - if( !(pMem->flags&MEM_Str) || pMem->enc==desiredEnc ){ + if( !(pMem->flags&MEM_Str) ){ + pMem->enc = desiredEnc; + return SQLITE_OK; + } + if( pMem->enc==desiredEnc ){ return SQLITE_OK; } assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); diff --git a/src/where.c b/src/where.c index 1e5ac5b75..3bd640cb2 100644 --- a/src/where.c +++ b/src/where.c @@ -808,8 +808,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( ** WHERE clause (or the ON clause of a LEFT join) that constrain which ** rows of the target table (pSrc) that can be used. */ if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && ((pSrc->fg.jointype&JT_LEFT)==0 || ExprHasProperty(pExpr,EP_FromJoin)) - && sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) + && sqlite3ExprIsTableConstraint(pExpr, pSrc) ){ pPartial = sqlite3ExprAnd(pParse, pPartial, sqlite3ExprDup(pParse->db, pExpr, 0)); @@ -1048,7 +1047,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( for(pTerm=pWInfo->sWC.a; pTermpExpr; if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && sqlite3ExprIsTableConstant(pExpr, iCur) + && sqlite3ExprIsTableConstraint(pExpr, pItem) ){ sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); } diff --git a/src/window.c b/src/window.c index bed6ab636..e4313fcb0 100644 --- a/src/window.c +++ b/src/window.c @@ -1729,7 +1729,7 @@ static void windowAggStep( for(iEnd=sqlite3VdbeCurrentAddr(v); iOpopcode==OP_Column && pOp->p1==pWin->iEphCsr ){ + if( pOp->opcode==OP_Column && pOp->p1==pMWin->iEphCsr ){ pOp->p1 = csr; } } diff --git a/test/autoindex1.test b/test/autoindex1.test index 6b437f186..fac40f8d5 100644 --- a/test/autoindex1.test +++ b/test/autoindex1.test @@ -542,7 +542,23 @@ do_execsql_test autoindex1-1020 { SELECT count(*) FROM t11 LEFT JOIN t12 WHERE t12.y IS t11.w; } 0 - - +# 2022-04-25 +# https://sqlite.org/forum/forumpost/0d3200f4f3bcd3a3 +# +reset_db +do_execsql_test autoindex-1100 { + CREATE TABLE t1(a INT, b INT); + CREATE TABLE t2(c INT, d INT); + CREATE TABLE t3(e TEXT, f TEXT); + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t2 VALUES(1, 2); + INSERT INTO t3 VALUES('abc', 'def'); +} {} +do_execsql_test autoindex-1110 { + SELECT * FROM t1, t2 LEFT JOIN t3 ON (t2.d=1) WHERE t2.c = +t1.a; +} {1 1 1 2 {} {}} +do_execsql_test autoindex-1120 { + SELECT * FROM t1 LEFT JOIN t2 ON (t2.c=+t1.a) LEFT JOIN t3 ON (t2.d IS NULL); +} {1 1 1 2 {} {}} finish_test diff --git a/test/corruptL.test b/test/corruptL.test index b7ff45d2e..eef91b774 100644 --- a/test/corruptL.test +++ b/test/corruptL.test @@ -1429,4 +1429,59 @@ do_catchsql_test 18.1 { } {1 {JSON cannot hold BLOB values}} } ;# ifcapable json1 +#------------------------------------------------------------------------- +reset_db +do_test 19.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 20480 pagesize 4096 filename crash-f022eb0ce64d27.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 05 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 96: 00 00 00 00 0d 0f f8 00 04 0e d2 00 0f 08 0f d3 ................ +| 112: 0f ae 0e d2 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3792: 00 00 34 04 06 17 0f 11 01 57 69 6e 64 65 78 61 ..4......Windexa +| 3808: 74 31 05 43 52 45 41 54 45 20 55 4e 49 51 55 45 t1.CREATE UNIQUE +| 3824: 20 49 4e 44 45 58 20 61 20 4f 4e 20 74 31 28 61 INDEX a ON t1(a +| 3840: 2c 20 30 20 7c 20 61 29 81 23 01 07 17 11 11 01 , 0 | a).#...... +| 3856: 82 31 74 61 62 6c 65 74 31 74 31 02 43 52 45 41 .1tablet1t1.CREA +| 3872: 54 45 20 54 41 42 4c 45 20 74 31 28 0a 20 20 20 TE TABLE t1(. +| 3888: 20 67 63 62 20 41 53 20 28 62 2a 31 29 2c 0a 20 gcb AS (b*1),. +| 3904: 20 20 20 61 20 49 34 54 45 47 45 52 20 50 52 49 a I4TEGER PRI +| 3920: 4d 41 52 59 20 4b 45 59 2c 0a 20 20 20 20 67 63 MARY KEY,. gc +| 3936: 63 20 41 53 20 28 74 32 2b 30 29 2c 0a 20 20 20 c AS (t2+0),. +| 3952: 20 62 20 55 4e 49 51 55 45 2c 0a 20 20 20 20 67 b UNIQUE,. g +| 3968: 63 61 20 41 53 20 28 31 2a 61 2b 30 29 2c 0a 20 ca AS (1*a+0),. +| 3984: 20 20 20 74 32 20 55 4e 49 51 55 45 0a 20 20 29 t2 UNIQUE. ) +| 4000: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 23 03 WITHOUT ROWID#. +| 4016: 06 17 37 11 01 00 69 6e 64 65 78 73 71 6c 69 74 ..7...indexsqlit +| 4032: 65 5f 61 75 74 6f 69 6e 64 65 78 5f 74 31 5f 33 e_autoindex_t1_3 +| 4048: 74 31 02 23 02 06 17 37 11 01 00 69 6e 64 65 78 t1.#...7...index +| 4064: 73 71 6c 69 74 65 5f 61 75 74 6f 69 6e 64 65 78 sqlite_autoindex +| 4080: 5f 74 31 5f 32 74 31 02 00 00 00 08 00 00 00 00 _t1_2t1......... +| page 2 offset 4096 +| 0: 0a 00 00 00 05 0f d8 00 0f f8 0f f8 9f e8 0f e0 ................ +| 16: 0f d8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4048: 00 00 00 00 00 00 00 00 07 04 01 02 00 0f 13 88 ................ +| 4064: 07 04 01 02 00 0e 0f a0 07 04 01 02 00 0d 0b b8 ................ +| 4080: 07 04 01 02 00 0c 07 d0 07 04 01 02 00 0b 03 e8 ................ +| page 5 offset 16384 +| 0: 0a 00 00 00 05 0f e2 00 0f fa 0f f4 0f ee 0f e8 ................ +| 16: 0f e2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4064: 00 00 05 03 01 01 0f 0f 05 03 01 01 0e 0e 05 03 ................ +| 4080: 01 01 0d 0d 05 03 01 01 0c 0c 05 03 01 01 0b 0b ................ +| end crash-f022eb0ce64d27.db +}]} {} + +do_execsql_test 19.1 { + PRAGMA writable_schema=ON; +} + +do_catchsql_test 19.2 { + UPDATE t1 SET a=1; +} {1 {UNIQUE constraint failed: index 'a'}} + finish_test diff --git a/test/json101.test b/test/json101.test index 184527917..b84846d88 100644 --- a/test/json101.test +++ b/test/json101.test @@ -850,4 +850,21 @@ do_execsql_test json-17.1 { SELECT * FROM t1 LEFT JOIN t2 ON (SELECT b FROM json_each ORDER BY 1); } {} +# 2022-04-04 forum post https://sqlite.org/forum/forumpost/c082aeab43 +do_execsql_test json-18.1 { + SELECT json_valid('{"":5}'); +} {1} +do_execsql_test json-18.2 { + SELECT json_extract('{"":5}', '$.""'); +} {5} +do_execsql_test json-18.3 { + SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1].hi'); +} {6} +do_execsql_test json-18.4 { + SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1]."hi"'); +} {6} +do_catchsql_test json-18.5 { + SELECT json_extract('{"":8}', '$.'); +} {1 {JSON path error near ''}} + finish_test diff --git a/test/shell2.test b/test/shell2.test index db8cd96ab..29712c7f0 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -188,4 +188,13 @@ b 2 }} +# Test for rejection of incomplete input at EOF. +# Reported at https://sqlite.org/forum/forumpost/718f489a43be3197 +do_test shell2-1.4.7 { + catchcmd ":memory:" { + SELECT 'unclosed;} +} {1 {Parse error near line 2: unrecognized token: "'unclosed;" + SELECT 'unclosed; + ^--- error here}} + finish_test diff --git a/test/windowB.test b/test/windowB.test index 52221c445..0a9aef724 100644 --- a/test/windowB.test +++ b/test/windowB.test @@ -365,4 +365,49 @@ do_execsql_test 9.0 { FROM seps; } {-22- -22-333- -333-4444- -4444-} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 10.1 { + CREATE TABLE t1(i INTEGER PRIMARY KEY, v); + INSERT INTO t1 VALUES( 1, 'one' ); + INSERT INTO t1 VALUES( 2, 'two' ); +} + +do_execsql_test 10.2 { + SELECT + json_group_array( v ) OVER w, + json_group_array( v ) OVER w + FROM t1 + window w as ( + range between unbounded preceding and unbounded following + ) +} { + {["one","two"]} + {["one","two"]} + {["one","two"]} + {["one","two"]} +} + +do_execsql_test 10.3 { + SELECT + group_concat( v ) OVER w, + json_group_array( v ) OVER w, + json_group_array( v ) OVER w, + group_concat( v ) OVER w + FROM t1 + window w as ( + range between unbounded preceding and unbounded following + ) +} { + one,two + {["one","two"]} + {["one","two"]} + one,two + + one,two + {["one","two"]} + {["one","two"]} + one,two +} + finish_test From 4c6cbd8c253533aeb54b02269c828b1aab55774a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 3 May 2022 16:11:39 -0400 Subject: [PATCH 067/340] bump version number and changelog --- CHANGELOG.md | 7 +++++++ src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3c0aba8e..3f0599774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. +## [unreleased] - (? 2022 - [unreleased changes]) +- Updates source code baseline to upstream SQLite 3.38.3 +- Simplifies OpenSSL version conditional code +- Fixes issue where PRAGMA cipher_memory_security could report OFF when it was actually ON +- Fixes fix unfreed OpenSSL allocation when compiled against version 3 +- Fixes support for building against recent versions of BoringSSL + ## [4.5.1] - (March 2022 - [4.5.1 changes]) - Updates source code baseline to upstream SQLite 3.37.2 - Adds PRAGMA cipher_log and cipher_log_level features to allow logging of TRACE, DEBUG, INFO, WARN, and ERROR messages to stdout, stderr, file, or logcat diff --git a/src/crypto.h b/src/crypto.h index 7d59159fa..0ecb9a66e 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -84,7 +84,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.1 +#define CIPHER_VERSION_NUMBER 4.5.2 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index 5fbeed953..6c5f01388 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.1 community}} +} {{4.5.2 community}} db close file delete -force test.db From ce5bd0c0b1545f888a39a3f4154f4c033128853f Mon Sep 17 00:00:00 2001 From: Micah Moore Date: Thu, 5 May 2022 09:17:58 -0400 Subject: [PATCH 068/340] Update README.md Fixes typo `consider` > `considered` in Testing section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2a44397c..daff9d995 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ $ make The full SQLite test suite will not complete successfully when using SQLCipher. In some cases encryption interferes with low-level tests that require access to database file data or features which are unsupported by SQLCipher. Those tests that are intended to support encryption are intended for non-SQLCipher implementations. In addition, because SQLite tests are not always isolated, if one test fails it can trigger a domino effect with other failures in later steps. -As a result, the SQLCipher package includes it's own independent tests that exercise and verify the core functionality of the SQLCipher extensions. This test suite is intended to provide an abbreviated verification of SQLCipher's internal logic; it does not perform an exhaustive test of the SQLite database system as a whole or verify functionality on specific platforms. Because SQLCipher is based on stable upstream builds of SQLite, it is consider a basic assumption that the core SQLite library code is operating properly (the SQLite core is almost untouched in SQLCipher). Thus, the additional SQLCipher-specific test provide the requisite verification that the library is operating as expected with SQLCipher's security features enabled. +As a result, the SQLCipher package includes it's own independent tests that exercise and verify the core functionality of the SQLCipher extensions. This test suite is intended to provide an abbreviated verification of SQLCipher's internal logic; it does not perform an exhaustive test of the SQLite database system as a whole or verify functionality on specific platforms. Because SQLCipher is based on stable upstream builds of SQLite, it is considered a basic assumption that the core SQLite library code is operating properly (the SQLite core is almost untouched in SQLCipher). Thus, the additional SQLCipher-specific test provide the requisite verification that the library is operating as expected with SQLCipher's security features enabled. To run SQLCipher specific tests, configure as described here and run the following to execute the tests and receive a report of the results: From 00dbcf017806aa5e924978f3331a4da79a70734b Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 9 May 2022 08:32:28 -0400 Subject: [PATCH 069/340] Snapshot of upstream SQLite 3.38.5 --- VERSION | 2 +- configure | 18 +++++++++--------- manifest | 34 +++++++++++++++++----------------- manifest.uuid | 2 +- src/prepare.c | 10 +++++++++- src/shell.c.in | 15 +++++++++++---- src/vdbe.c | 2 ++ src/wherecode.c | 4 ++++ test/indexexpr1.test | 19 +++++++++++++++++++ test/join5.test | 32 ++++++++++++++++++++++++++++++++ test/shell1.test | 17 +++++++++++++++++ test/shell5.test | 30 ++++++++++++++++++++++++++++++ 12 files changed, 152 insertions(+), 33 deletions(-) diff --git a/VERSION b/VERSION index e641704a9..f94ed442b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.38.3 +3.38.5 diff --git a/configure b/configure index 0ad56dd00..50e4ee456 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.69 for sqlite 3.38.3. +# Generated by GNU Autoconf 2.69 for sqlite 3.38.5. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.38.3' -PACKAGE_STRING='sqlite 3.38.3' +PACKAGE_VERSION='3.38.5' +PACKAGE_STRING='sqlite 3.38.5' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1468,7 +1468,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.38.3 to adapt to many kinds of systems. +\`configure' configures sqlite 3.38.5 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1533,7 +1533,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.38.3:";; + short | recursive ) echo "Configuration of sqlite 3.38.5:";; esac cat <<\_ACEOF @@ -1661,7 +1661,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.38.3 +sqlite configure 3.38.5 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2080,7 +2080,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.38.3, which was +It was created by sqlite $as_me 3.38.5, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12390,7 +12390,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.38.3, which was +This file was extended by sqlite $as_me 3.38.5, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12456,7 +12456,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.38.3 +sqlite config.status 3.38.5 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/manifest b/manifest index 1f22eab23..0b6d0669f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.38.3 -D 2022-04-27T12:03:15.047 +C Version\s3.38.5 +D 2022-05-06T15:25:27.476 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in b210ad2733317f1a4353085dfb9d385ceec30b0e6a61d20a5accabecac6b1949 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3 F README.md 2dd87a5c1d108b224921f3dd47dea567973f706e1f6959386282a626f459a70c -F VERSION b09048f5104cef7fbc4405635ec966be2481d532c0dbd46afd5c17f957de2dc0 +F VERSION 2cef599294698e267cbcdadf44f14a0637a0015c1872cb37a16fe723d7c7add0 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -34,7 +34,7 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 1290cbee049f2db602990f1ed2a4eed248c36d1e41d23eed37ccdfd7b8bed13e x +F configure d616d46e32a1c2324a561b464175907b976ff62e8946b14e9954268841fc0bb8 x F configure.ac 3ef6eeff4387585bfcab76b0c3f6e15a0618587bb90245dd5d44e4378141bb35 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -547,13 +547,13 @@ F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65 F src/pragma.c 7c024d690a3dc93f61830f11f900e4af2357f31d081b0c79099ca5e28919cba7 F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad -F src/prepare.c fd940149c691684e7c1073c3787a7170e44852b02d1275d2e30a5b58e89cfcaf +F src/prepare.c c62820c15dcb63013519c8e41d9f928d7478672cc902cfd0581c733c271dbf45 F src/printf.c 05d8dfd2018bc4fc3ddb8b37eb97ccef7abf985643fa1caebdcf2916ca90fa32 F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c ba0cd3d264fa788c8d65f063d5ec3e1d65b0dac206c822a564a59ba550210cfc -F src/shell.c.in ec4b952f7d9c02919e8e8394be16bcbff55c105fd080895602d10fc8fa5328b8 +F src/shell.c.in e2bc31eca23a1d1d2aaea02dcfde6dfe0ee96bdfa6cbcc36c79f02e457bef18c F src/sqlite.h.in b93deee892f1bc4030e5c8712df9e21d786a1bf8e921ab8dc987eaf1e44c676f F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 @@ -624,7 +624,7 @@ F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 602fe229f32a96ceccae4f40824129669582096f7c355f53dbac156c9fecef23 F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 -F src/vdbe.c 8239c69f3cb6fa27bac30d60c5a1ebac723abc007ff870ffc3af93e5159c7216 +F src/vdbe.c dafb1c9c2f58555bf6f384fe049a4b4fc2408b500216b37769f8faa82a46e57a F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe F src/vdbeInt.h b45599a2b59f1ce042512ab6786b0b82a8cf3002f6b0fa60b4834e2cd3ac61d8 F src/vdbeapi.c 8863ffb5a7bac42fe9a68aaa3526ee29fc18fb02a9b27188b756de41e33856e9 @@ -641,7 +641,7 @@ F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b F src/where.c be30bf0151f5cc77b2d75a664f2ab60632974165ad2bab6df9deca2fc3284264 F src/whereInt.h 15d2975c3b4c193c78c26674400a840da8647fe1777ae3b026e2d15937b38a03 -F src/wherecode.c 84be340684393248b9f3ecbce9b87c8a6f818149b52302702ea0b8d2a9d51faf +F src/wherecode.c eae0a5778fa0ffe398cb4ecb55d3e20ee9b85a985c8c1aa9cea03d0a3069c213 F src/whereexpr.c 2a71f5491798460c9590317329234d332d9eb1717cba4f3403122189a75c465e F src/window.c 8546f510dfff88e4627f004969940c648acf91c13aa155229849f926a8137b9c F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 @@ -1114,7 +1114,7 @@ F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6 F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3 F test/index9.test 0aa3e509dddf81f93380396e40e9bb386904c1054924ba8fa9bcdfe85a8e7721 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 -F test/indexexpr1.test 8f7241410e351679010f14cd7cd30357622d04a784508ff54ba5ce99f64a2228 +F test/indexexpr1.test 3360c2a29a8844e7c4b13293567025281257f9e13a31854cfff6959cede11502 F test/indexexpr2.test 2c7abe3c48f8aaa5a448615ab4d13df3662185d28419c00999670834a3f0b484 F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 @@ -1143,7 +1143,7 @@ F test/join.test 25cf0ac11c3b81fedfd166f9062166bdb39dea92f5a7c16cacbf6dc1f7f6702 F test/join2.test 9bdc615841b91c97a16d68bad9508aea11fa0c6b34e5689847bcc4dac70e4990 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 -F test/join5.test 0d63c7e43b3160b9d4b93f196ef83b6efc7751b9edd0d18c53a46fbec7a49cfc +F test/join5.test e1ada8581c279fa210ce6c301bcd286cf0b42bfd0f0a2036b1ee0301e245ab75 F test/join6.test f809c025fa253f9e150c0e9afd4cef8813257bceeb6f46e04041228c9403cc2c F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 @@ -1388,11 +1388,11 @@ F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6 +F test/shell1.test f7a2ef8260aa01f20be3185118213b1ae70518fdcd2105f3e25b021b5ca800ac F test/shell2.test 7a3a23a9f57b99453f1679b1fe8072cb30e382a622874c0c4d97695fadb0a787 F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759 -F test/shell5.test 0a9920d81fae28c45cd5dbd1deb809487a23c5f4b422a49f9d31c85f926d4a9c +F test/shell5.test 78a7a8516b1e7de560748881424f621321549023d3e5f7ed2e1c56497f64c06c F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae @@ -1944,10 +1944,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b9c7d2c1bdc529b8c367e4a0894f4c966957cfa4b427175d5aa10af6a25f4f12 -R 3239c458ee20a7ad3be4e144155c5ad4 -T +sym-relese * -T +sym-version-3.38.3 * +P 7fcdb760a4df8c5c11e94cecdc61bb8f40c86b70aa05f48e17520443a3d3b451 +R c70a3244d878106624c489b9bff97d77 +T +sym-release * +T +sym-version-3.38.5 * U drh -Z 3eea06f2546971f0d6539e228b8fb587 +Z 54d3523b3ce9c5f872539bc3270e6853 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b7c942c5f..21a09c325 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9547e2c38a1c6f751a77d4d796894dec4dc5d8f5d79b1cd39e1ffc50df7b3be4 +78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe diff --git a/src/prepare.c b/src/prepare.c index d402da4a3..f55ff72fc 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -663,6 +663,14 @@ void sqlite3ParseObjectInit(Parse *pParse, sqlite3 *db){ if( db->mallocFailed ) sqlite3ErrorMsg(pParse, "out of memory"); } +/* +** Maximum number of times that we will try again to prepare a statement +** that returns SQLITE_ERROR_RETRY. +*/ +#ifndef SQLITE_MAX_PREPARE_RETRY +# define SQLITE_MAX_PREPARE_RETRY 25 +#endif + /* ** Compile the UTF-8 encoded SQL statement zSql into a statement handle. */ @@ -837,7 +845,7 @@ static int sqlite3LockAndPrepare( rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); assert( rc==SQLITE_OK || *ppStmt==0 ); if( rc==SQLITE_OK || db->mallocFailed ) break; - }while( rc==SQLITE_ERROR_RETRY + }while( (rc==SQLITE_ERROR_RETRY && (cnt++)cmOpts.bWordWrap; + const char *zEmpty = ""; + const char *zShowNull = p->nullValue; rc = sqlite3_step(pStmt); if( rc!=SQLITE_ROW ) return; @@ -3407,12 +3409,14 @@ static void exec_prepared_stmt_columnar( if( wx<0 ) wx = -wx; if( useNextLine ){ uz = azNextLine[i]; + if( uz==0 ) uz = (u8*)zEmpty; }else if( p->cmOpts.bQuote ){ sqlite3_free(azQuoted[i]); azQuoted[i] = quoted_column(pStmt,i); uz = (const unsigned char*)azQuoted[i]; }else{ uz = (const unsigned char*)sqlite3_column_text(pStmt,i); + if( uz==0 ) uz = (u8*)zShowNull; } azData[nRow*nColumn + i] = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw); @@ -3426,7 +3430,7 @@ static void exec_prepared_stmt_columnar( nTotal = nColumn*(nRow+1); for(i=0; inullValue; + if( z==0 ) z = (char*)zEmpty; n = strlenChar(z); j = i%nColumn; if( n>p->actualWidth[j] ) p->actualWidth[j] = n; @@ -3530,7 +3534,10 @@ columnar_end: utf8_printf(p->out, "Interrupt\n"); } nData = (nRow+1)*nColumn; - for(i=0; i0, ' ', x'0a'||' '))\ ||')' AS ColsSpec \ FROM (\ - SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \ + SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \ FROM ColNames ORDER BY cpos\ )"; static const char * const zRenamesDone = "SELECT group_concat(" - " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff))," + " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff))," " ','||x'0a')" "FROM ColNames WHERE suff<>'' OR chop!=0" ; diff --git a/src/vdbe.c b/src/vdbe.c index fa56689df..0e937078f 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -987,6 +987,8 @@ case OP_Gosub: { /* jump */ /* Most jump operations do a goto to this spot in order to update ** the pOp pointer. */ jump_to_p2: + assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */ + assert( pOp->p2nOp ); /* Jumps must be in range */ pOp = &aOp[pOp->p2 - 1]; break; } diff --git a/src/wherecode.c b/src/wherecode.c index 6944c3acd..d112de555 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -1235,6 +1235,7 @@ static void preserveExpr(IdxExprTrans *pTrans, Expr *pExpr){ static int whereIndexExprTransNode(Walker *p, Expr *pExpr){ IdxExprTrans *pX = p->u.pIdxTrans; if( sqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){ + pExpr = sqlite3ExprSkipCollate(pExpr); preserveExpr(pX, pExpr); pExpr->affExpr = sqlite3ExprAffinity(pExpr); pExpr->op = TK_COLUMN; @@ -1394,6 +1395,8 @@ static SQLITE_NOINLINE void filterPullDown( /* ,--- Because sqlite3ConstructBloomFilter() has will not have set ** vvvvv--' pLevel->regFilter if this were true. */ if( NEVER(pLoop->prereq & notReady) ) continue; + assert( pLevel->addrBrk==0 ); + pLevel->addrBrk = addrNxt; if( pLoop->wsFlags & WHERE_IPK ){ WhereTerm *pTerm = pLoop->aLTerm[0]; int regRowid; @@ -1420,6 +1423,7 @@ static SQLITE_NOINLINE void filterPullDown( VdbeCoverage(pParse->pVdbe); } pLevel->regFilter = 0; + pLevel->addrBrk = 0; } } diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 92dfc8689..042132b81 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -487,4 +487,23 @@ ifcapable like_match_blobs { } {2.0} } +# 2022-04-30 https://sqlite.org/forum/info/7efabf4b03328e57 +# Assertion fault during a DELETE INDEXED BY. +# +reset_db +do_execsql_test indexexpr-1900 { + CREATE TABLE t1(x TEXT PRIMARY KEY, y TEXT, z INT); + INSERT INTO t1(x,y,z) VALUES('alpha','ALPHA',1),('bravo','charlie',1); + CREATE INDEX i1 ON t1(+y COLLATE NOCASE); + SELECT * FROM t1; +} {alpha ALPHA 1 bravo charlie 1} +do_execsql_test indexexpr-1910 { + DELETE FROM t1 INDEXED BY i1 + WHERE x IS +y COLLATE NOCASE IN (SELECT z FROM t1) + RETURNING *; +} {alpha ALPHA 1} +do_execsql_test indexexpr-1920 { + SELECT * FROM t1; +} {bravo charlie 1} + finish_test diff --git a/test/join5.test b/test/join5.test index a5cedd8cb..5073d9831 100644 --- a/test/join5.test +++ b/test/join5.test @@ -393,6 +393,38 @@ do_execsql_test 11.4 { SELECT count(*) FROM t1 LEFT JOIN t2 ON c=b WHERE d>=300; } {2} +# 2022-05-03 https://sqlite.org/forum/forumpost/2482b32700384a0f +# Bloom-filter pull-down does not handle NOT NULL constraints correctly. +# +reset_db +do_execsql_test 12.1 { + CREATE TABLE t1(a INT, b INT, c INT); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) + INSERT INTO t1(a,b,c) SELECT x, x*1000, x*1000000 FROM c; + CREATE TABLE t2(b INT, x INT); + INSERT INTO t2(b,x) SELECT b, a FROM t1 WHERE a%3==0; + CREATE INDEX t2b ON t2(b); + CREATE TABLE t3(c INT, y INT); + INSERT INTO t3(c,y) SELECT c, a FROM t1 WHERE a%4==0; + CREATE INDEX t3c ON t3(c); + INSERT INTO t1(a,b,c) VALUES(200, 200000, NULL); + ANALYZE; +} {} +do_execsql_test 12.2 { + SELECT * FROM t1 NATURAL JOIN t2 NATURAL JOIN t3 WHERE x>0 AND y>0 + ORDER BY +a; +} { + 12 12000 12000000 12 12 + 24 24000 24000000 24 24 + 36 36000 36000000 36 36 + 48 48000 48000000 48 48 + 60 60000 60000000 60 60 + 72 72000 72000000 72 72 + 84 84000 84000000 84 84 + 96 96000 96000000 96 96 +} + + finish_test diff --git a/test/shell1.test b/test/shell1.test index cbbadc58c..3c7061c55 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -1221,6 +1221,23 @@ do_test shell1-8.4 { +------------------+-----+ | 6683623321994527 | -47 | +------------------+-----+}} +do_test shell1-8.5 { + catchcmd ":memory: --box" { +create table t(a text, b int); +insert into t values ('too long for one line', 1), ('shorter', NULL); +.header on +.width 10 10 +.nullvalue NADA +select * from t;} +} {0 {┌────────────┬────────────┐ +│ a │ b │ +├────────────┼────────────┤ +│ too long f │ 1 │ +│ or one lin │ │ +│ e │ │ +├────────────┼────────────┤ +│ shorter │ NADA │ +└────────────┴────────────┘}} #---------------------------------------------------------------------------- # Test cases shell1-9.*: Basic test that "dot" commands and SQL intermix ok. diff --git a/test/shell5.test b/test/shell5.test index d63a0a20d..00d89f8d7 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -540,4 +540,34 @@ Columns renamed during .import shell5.csv due to duplicates: "CoW" to "CoW_3", "cOw" to "cOw_4"}} +#---------------------------------------------------------------------------- +# Tests for preserving utf-8 that is not also ASCII. +# + +do_test shell5-6.1 { + set out [open shell5.csv w] + fconfigure $out -translation lf + puts $out {あい,うえお} + puts $out {1,2} + close $out + forcedelete test.db + catchcmd test.db {.import -csv shell5.csv t1 +.mode line +SELECT * FROM t1;} +} {0 { あい = 1 +うえお = 2}} + +do_test shell5-6.2 { + set out [open shell5.csv w] + fconfigure $out -translation lf + puts $out {1,2} + puts $out {あい,うえお} + close $out + forcedelete test.db + catchcmd test.db {.import -csv shell5.csv t1 +.mode line +SELECT * FROM t1;} +} {0 { 1 = あい + 2 = うえお}} + finish_test From a6c04011cac4a8f22fa7f4a18effca0f9092c4f9 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 22 Jul 2022 10:24:59 -0400 Subject: [PATCH 070/340] Snapshot of upstream SQLite 3.39.2 --- Makefile.in | 136 +- Makefile.msc | 2 +- README.md | 28 +- VERSION | 2 +- configure | 18 +- ext/fiddle/EXPORTED_FUNCTIONS.fiddle | 7 + ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api | 51 + ext/fiddle/EXPORTED_RUNTIME_METHODS | 14 + ext/fiddle/Makefile | 34 + ext/fiddle/SqliteTestUtil.js | 144 + ext/fiddle/emscripten.css | 24 + ext/fiddle/fiddle-worker.js | 301 + ext/fiddle/fiddle.html | 283 + ext/fiddle/fiddle.js | 809 + ext/fiddle/index.md | 94 + ext/fiddle/sqlite3-api.js | 1781 + ext/fiddle/sqlite3-worker.js | 44 + ext/fiddle/testing.css | 31 + ext/fiddle/testing1.html | 33 + ext/fiddle/testing1.js | 185 + ext/fiddle/testing2.html | 32 + ext/fiddle/testing2.js | 235 + ext/fts3/fts3.c | 14 +- ext/fts3/fts3_write.c | 3 + ext/fts5/fts5_expr.c | 3 + ext/fts5/fts5_index.c | 2 +- ext/fts5/test/fts5fault4.test | 8 +- ext/icu/README.txt | 14 +- ext/misc/regexp.c | 2 +- ext/misc/shathree.c | 2 +- ext/misc/stmt.c | 125 +- ext/misc/zipfile.c | 4 + ext/rbu/rbubusy.test | 87 + ext/rtree/rtree1.test | 23 + ext/rtree/rtree3.test | 8 +- ext/rtree/rtreeC.test | 2 +- main.mk | 8 +- manifest | 322 +- manifest.uuid | 2 +- src/alter.c | 73 +- src/analyze.c | 10 +- src/attach.c | 6 +- src/btree.c | 247 +- src/btreeInt.h | 4 +- src/build.c | 127 +- src/ctime.c | 6 +- src/dbpage.c | 4 +- src/delete.c | 13 +- src/expr.c | 195 +- src/fkey.c | 27 +- src/func.c | 7 +- src/global.c | 5 +- src/insert.c | 19 +- src/json.c | 2 +- src/loadext.c | 19 +- src/main.c | 62 +- src/mem5.c | 9 +- src/os.h | 7 + src/os_unix.c | 267 +- src/pager.c | 21 +- src/pager.h | 5 +- src/parse.y | 98 +- src/pcache.c | 3 +- src/pragma.c | 23 +- src/pragma.h | 2 +- src/printf.c | 18 +- src/resolve.c | 237 +- src/select.c | 910 +- src/shell.c.in | 420 +- src/sqlite.h.in | 36 +- src/sqlite3ext.h | 12 + src/sqliteInt.h | 324 +- src/status.c | 3 +- src/tclsqlite.c | 2 +- src/test1.c | 6 +- src/test4.c | 15 + src/tokenize.c | 3 +- src/treeview.c | 591 +- src/trigger.c | 50 +- src/update.c | 10 +- src/vacuum.c | 1 + src/vdbe.c | 471 +- src/vdbe.h | 30 +- src/vdbeInt.h | 126 +- src/vdbeapi.c | 192 +- src/vdbeaux.c | 478 +- src/vdbemem.c | 70 +- src/where.c | 357 +- src/whereInt.h | 34 +- src/wherecode.c | 259 +- src/whereexpr.c | 78 +- src/window.c | 32 +- test/affinity3.test | 32 + test/aggnested.test | 11 + test/altermalloc3.test | 7 + test/altertab3.test | 52 +- test/altertrig.test | 163 + test/analyze9.test | 2 +- test/atof1.test | 4 + test/autoindex1.test | 2 +- test/autoindex4.test | 25 + test/bestindex8.test | 2 +- test/bestindex9.test | 108 + test/btree01.test | 24 + test/changes2.test | 95 + test/collate2.test | 24 +- test/corruptL.test | 6 +- test/count.test | 9 +- test/distinct.test | 48 + test/e_select.test | 9 +- test/e_vacuum.test | 4 +- test/eqp.test | 12 +- test/expr.test | 24 + test/fkey8.test | 22 + test/fts3corrupt.test | 38 + test/fts3corrupt4.test | 435 + test/fts3corrupt6.test | 5 +- test/fts3fault.test | 8 +- test/fts3fault2.test | 5 +- test/fts3join.test | 2 +- test/func7.test | 6 + test/fuzzcheck.c | 194 +- test/fuzzdata1.db | Bin 4419584 -> 4421632 bytes test/fuzzinvariants.c | 337 + test/in.test | 9 + test/incrblob3.test | 38 + test/index6.test | 22 +- test/index9.test | 1 + test/join.test | 85 +- test/join2.test | 31 +- test/join5.test | 6 +- test/join7.test | 355 + test/join8.test | 804 + test/join9.test | 565 + test/joinA.test | 214 + test/joinB.test | 7252 ++++ test/joinC.test | 4594 +++ test/joinD.test | 35134 ++++++++++++++++++++ test/joinE.test | 443 + test/joinF.test | 613 + test/joinH.test | 72 + test/malloc_common.tcl | 1 + test/merge1.test | 2 + test/permutations.test | 3 +- test/regexp1.test | 22 + test/rowvalue.test | 61 +- test/rowvalue3.test | 3 +- test/select1.test | 6 +- test/select3.test | 7 +- test/shell1.test | 2 +- test/shell2.test | 18 +- test/shell3.test | 2 +- test/shell4.test | 7 +- test/shell5.test | 2 +- test/shell8.test | 2 +- test/subtype1.test | 28 + test/swarmvtab3.test | 4 +- test/tester.tcl | 36 +- test/tkt3935.test | 6 +- test/upfrom4.test | 130 + test/vtab6.test | 20 +- test/where.test | 25 +- test/where9.test | 4 +- test/window1.test | 5 + test/windowD.test | 94 + test/with1.test | 8 +- test/with6.test | 69 +- tool/dbtotxt.c | 13 +- tool/lemon.c | 1 + tool/mkctimec.tcl | 2 +- tool/mkopcodeh.tcl | 3 - tool/mkpragmatab.tcl | 2 +- tool/mksqlite3c.tcl | 3 +- tool/split-sqlite3c.tcl | 5 + 174 files changed, 61291 insertions(+), 2201 deletions(-) create mode 100644 ext/fiddle/EXPORTED_FUNCTIONS.fiddle create mode 100644 ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api create mode 100644 ext/fiddle/EXPORTED_RUNTIME_METHODS create mode 100644 ext/fiddle/Makefile create mode 100644 ext/fiddle/SqliteTestUtil.js create mode 100644 ext/fiddle/emscripten.css create mode 100644 ext/fiddle/fiddle-worker.js create mode 100644 ext/fiddle/fiddle.html create mode 100644 ext/fiddle/fiddle.js create mode 100644 ext/fiddle/index.md create mode 100644 ext/fiddle/sqlite3-api.js create mode 100644 ext/fiddle/sqlite3-worker.js create mode 100644 ext/fiddle/testing.css create mode 100644 ext/fiddle/testing1.html create mode 100644 ext/fiddle/testing1.js create mode 100644 ext/fiddle/testing2.html create mode 100644 ext/fiddle/testing2.js create mode 100644 ext/rbu/rbubusy.test create mode 100644 test/altertrig.test create mode 100644 test/bestindex9.test create mode 100644 test/changes2.test create mode 100644 test/fuzzinvariants.c create mode 100644 test/join7.test create mode 100644 test/join8.test create mode 100644 test/join9.test create mode 100644 test/joinA.test create mode 100644 test/joinB.test create mode 100644 test/joinC.test create mode 100644 test/joinD.test create mode 100644 test/joinE.test create mode 100644 test/joinF.test create mode 100644 test/joinH.test create mode 100644 test/upfrom4.test create mode 100644 test/windowD.test diff --git a/Makefile.in b/Makefile.in index e5d30d030..63120ffb2 100644 --- a/Makefile.in +++ b/Makefile.in @@ -503,6 +503,7 @@ TESTSRC2 = \ $(TOP)/src/pcache1.c \ $(TOP)/src/select.c \ $(TOP)/src/tokenize.c \ + $(TOP)/src/treeview.c \ $(TOP)/src/utf.c \ $(TOP)/src/util.c \ $(TOP)/src/vdbeapi.c \ @@ -630,7 +631,7 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB -FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c +FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c $(TOP)/test/fuzzinvariants.c DBFUZZ_OPT = # This is the default Makefile target. The objects listed here @@ -874,7 +875,7 @@ hash.lo: $(TOP)/src/hash.c $(HDR) insert.lo: $(TOP)/src/insert.c $(HDR) $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/insert.c -json.lo: $(TOP)/src/json.c +json.lo: $(TOP)/src/json.c $(HDR) $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/json.c legacy.lo: $(TOP)/src/legacy.c $(HDR) @@ -1512,3 +1513,134 @@ sqlite3.def: $(REAL_LIBOBJ) sqlite3.dll: $(REAL_LIBOBJ) sqlite3.def $(TCC) -shared -o $@ sqlite3.def \ -Wl,"--strip-all" $(REAL_LIBOBJ) + + +# +# fiddle/wasm section +# +fiddle_dir = ext/fiddle +fiddle_dir_abs = $(TOP)/$(fiddle_dir) +# ^^^ some emcc opts require absolute paths +fiddle_html = $(fiddle_dir)/fiddle.html +fiddle_module_js = $(fiddle_dir)/fiddle-module.js +sqlite3_wasm_js = $(fiddle_dir)/sqlite3.js +sqlite3_wasm = $(fiddle_dir)/sqlite3.wasm +#emcc_opt = -O0 +#emcc_opt = -O1 +#emcc_opt = -O2 +#emcc_opt = -O3 +emcc_opt = -Oz +emcc_flags = $(emcc_opt) -sALLOW_TABLE_GROWTH -sSTRICT_JS \ + -sENVIRONMENT=web -sMODULARIZE \ + -sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \ + -sDYNAMIC_EXECUTION=0 \ + -I. $(SHELL_OPT) +$(fiddle_module_js): Makefile sqlite3.c shell.c \ + $(fiddle_dir)/EXPORTED_RUNTIME_METHODS \ + $(fiddle_dir)/EXPORTED_FUNCTIONS.fiddle + emcc -o $@ $(emcc_flags) \ + -sEXPORT_NAME=initFiddleModule \ + -sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.fiddle \ + sqlite3.c shell.c + gzip < $@ > $@.gz + gzip < $(fiddle_dir)/fiddle-module.wasm > $(fiddle_dir)/fiddle-module.wasm.gz +$(sqlite3_wasm_js): Makefile sqlite3.c \ + $(fiddle_dir)/sqlite3-api.js \ + $(fiddle_dir)/EXPORTED_RUNTIME_METHODS \ + $(fiddle_dir)/EXPORTED_FUNCTIONS.sqlite3-api + emcc -o $@ $(emcc_flags) \ + -sEXPORT_NAME=initSqlite3Module \ + -sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.sqlite3-api \ + --post-js=$(fiddle_dir)/sqlite3-api.js \ + --no-entry \ + sqlite3.c + gzip < $@ > $@.gz + gzip < $(sqlite3_wasm) > $(sqlite3_wasm).gz + gzip < $(fiddle_dir)/sqlite3-api.js > $(fiddle_dir)/sqlite3-api.js.gz +$(fiddle_dir)/fiddle.js.gz: $(fiddle_dir)/fiddle.js + gzip < $< > $@ +$(fiddle_dir)/sqlite3-api.js.gz: $(fiddle_dir)/sqlite3-api.js + gzip < $< > $@ + +fiddle_generated = $(fiddle_module_js) $(fiddle_module_js).gz \ + $(fiddle_dir)/fiddle-module.wasm \ + $(fiddle_dir)/fiddle-module.wasm.gz \ + $(fiddle_dir)/fiddle.js.gz +sqlite3_wasm_generated = \ + $(sqlite3_wasm) $(sqlite3_wasm).gz \ + $(sqlite3_wasm_js) $(sqlite3_wasm_js).gz \ + $(fiddle_dir)/sqlite3.js.gz \ + $(fiddle_dir)/sqlite3-api.js.gz + +clean-wasm: + rm -f $(fiddle_generated) $(sqlite3_wasm_generated) +clean: clean-wasm +fiddle: $(fiddle_module_js) $(fiddle_dir)/fiddle.js.gz +sqlite3-wasm: $(sqlite3_wasm_js) +wasm: fiddle sqlite3-wasm +######################################################################## +# Explanation of the emcc build flags follows. Full docs for these can +# be found at: +# +# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js +# +# -sENVIRONMENT=web: elides bootstrap code related to non-web JS +# environments like node.js. Removing this makes the output a tiny +# tick larger but hypothetically makes it more portable to +# non-browser JS environments. +# +# -sMODULARIZE: changes how the generated code is structured to avoid +# declaring a global Module object and instead installing a function +# which loads and initialized the module. The function is named... +# +# -sEXPORT_NAME=jsFunctionName (see -sMODULARIZE) +# +# -sEXPORTED_RUNTIME_METHODS=@/absolute/path/to/file: a file +# containing a list of emscripten-supplied APIs, one per line, which +# must be exported into the generated JS. Must be an absolute path! +# +# -sEXPORTED_FUNCTIONS=@/absolute/path/to/file: a file containing a +# list of C functions, one per line, which must be exported via wasm +# so they're visible to JS. C symbols names in that file must all +# start with an underscore for reasons known only to the emcc +# developers. e.g., _sqlite3_open_v2 and _sqlite3_finalize. Must be +# an absolute path! +# +# -sSTRICT_JS ensures that the emitted JS code includes the 'use +# strict' option. Note that -sSTRICT is more broadly-scoped and +# results in build errors. +# +# -sALLOW_TABLE_GROWTH is required for (at a minimum) the UDF-binding +# feature. +# +# -sDYNAMIC_EXECUTION=0 disables eval() and the Function constructor. +# If the build runs without these, it's preferable to use this flag +# because certain execution environments disallow those constructs. +# This flag is not strictly necessary, however. +# +# -sWASM_BIGINT is UNTESTED but "should" allow the int64-using C APIs +# to work with JS/wasm, insofar as the JS environment supports the +# BigInt type. That support requires an extremely recent browser: +# Safari didn't get that support until late 2020. +# +# --no-entry: for compiling library code with no main(). If this is +# not supplied and the code has a main(), it is called as part of the +# module init process. Note that main() is #if'd out of shell.c +# (renamed) when building in wasm mode. +# +# --pre-js/--post-js=FILE relative or absolute paths to JS files to +# prepend/append to the emcc-generated bootstrapping JS. It's +# easier/faster to develop with separate JS files (reduces rebuilding +# requirements) but certain configurations, namely -sMODULARIZE, may +# require using at least a --pre-js file. They can be used +# individually and need not be paired. +# +# -O0..-O3 and -Oz: optimization levels affect not only C-style +# optimization but whether or not the resulting generated JS code +# gets minified. -O0 compiles _much_ more quickly than -O3 or -Oz, +# and doesn't minimize any JS code, so is recommended for +# development. -O3 or -Oz are recommended for deployment, but +# primarily because -Oz will shrink the wasm file notably. JS-side +# minification makes little difference in terms of overall +# distributable size. +######################################################################## diff --git a/Makefile.msc b/Makefile.msc index d67b7f0b8..42da9b9e6 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1705,7 +1705,7 @@ FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBSTAT_VTAB FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB -FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c +FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c $(TOP)\test\fuzzinvariants.c OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION KV_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ diff --git a/README.md b/README.md index 9e8bb2610..7ad42061c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

      SQLite Source Repository

      -This repository contains the complete source code for the -[SQLite database engine](https://sqlite.org/). Some test scripts +This repository contains the complete source code for the +[SQLite database engine](https://sqlite.org/). Some test scripts are also included. However, many other test scripts and most of the documentation are managed separately. @@ -15,7 +15,7 @@ The [Fossil repository](https://sqlite.org/src/timeline) contains the urtext. If you are reading this on GitHub or some other Git repository or service, then you are looking at a mirror. The names of check-ins and other artifacts in a Git mirror are different from the official -names for those objects. The offical names for check-ins are +names for those objects. The official names for check-ins are found in a footer on the check-in comment for authorized mirrors. The official check-in name can also be seen in the `manifest.uuid` file in the root of the tree. Always use the official name, not the @@ -30,7 +30,7 @@ verify its integrity, there are hints on how to do that in the If you do not want to use Fossil, you can download tarballs or ZIP archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows: - * Lastest trunk check-in as + * Latest trunk check-in as [Tarball](https://www.sqlite.org/src/tarball/sqlite.tar.gz), [ZIP-archive](https://www.sqlite.org/src/zip/sqlite.zip), or [SQLite-archive](https://www.sqlite.org/src/sqlar/sqlite.sqlar). @@ -47,11 +47,11 @@ archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows: then click on the "Tarball" or "ZIP Archive" links on the information page. -If you do want to use Fossil to check out the source tree, +If you do want to use Fossil to check out the source tree, first install Fossil version 2.0 or later. (Source tarballs and precompiled binaries available [here](https://www.fossil-scm.org/fossil/uv/download.html). Fossil is -a stand-alone program. To install, simply download or build the single +a stand-alone program. To install, simply download or build the single executable file and put that file someplace on your $PATH.) Then run commands like this: @@ -61,7 +61,7 @@ Then run commands like this: fossil open ~/Fossils/sqlite.fossil After setting up a repository using the steps above, you can always -update to the lastest version using: +update to the latest version using: fossil update trunk ;# latest trunk check-in fossil update release ;# latest official release @@ -136,7 +136,7 @@ the "tclsqlite.c" file which implements the extension and only later escaped to the wild as an independent library.) Test scripts and programs are found in the **test/** subdirectory. -Addtional test code is found in other source repositories. +Additional test code is found in other source repositories. See [How SQLite Is Tested](http://www.sqlite.org/testing.html) for additional information. @@ -170,7 +170,7 @@ at just the right spots. Note that comment text in the sqlite3.h file is used to generate much of the SQLite API documentation. The Tcl scripts used to generate that documentation are in a separate source repository. -The SQL language parser is **parse.c** which is generate from a grammar in +The SQL language parser is **parse.c** which is generated from a grammar in the src/parse.y file. The conversion of "parse.y" into "parse.c" is done by the [lemon](./doc/lemon.html) LALR(1) parser generator. The source code for lemon is at tool/lemon.c. Lemon uses the tool/lempar.c file as a @@ -180,7 +180,7 @@ generates parse.c. The **opcodes.h** header file contains macros that define the numbers corresponding to opcodes in the "VDBE" virtual machine. The opcodes.h -file is generated by the scanning the src/vdbe.c source file. The +file is generated by scanning the src/vdbe.c source file. The Tcl script at ./mkopcodeh.tcl does this scan and generates opcodes.h. A second Tcl script, ./mkopcodec.tcl, then scans opcodes.h to generate the **opcodes.c** source file, which contains a reverse mapping from @@ -237,7 +237,7 @@ prepared statements, the description of [how transactions work](http://www.sqlite.org/atomiccommit.html), and the [overview of the query planner](http://www.sqlite.org/optoverview.html). -Years of effort have gone into optimizating SQLite, both +Years of effort have gone into optimizing SQLite, both for small size and high performance. And optimizations tend to result in complex code. So there is a lot of complexity in the current SQLite implementation. It will not be the easiest library in the world to hack. @@ -294,11 +294,11 @@ Key files: building the "testfixture.exe" program. The testfixture.exe program is an enhanced Tcl shell. The testfixture.exe program runs scripts in the test/ folder to validate the core SQLite code. The testfixture program - (and some other test programs too) is build and run when you type + (and some other test programs too) is built and run when you type "make test". * **ext/misc/json1.c** - This file implements the various JSON functions - that are build into SQLite. + that are built into SQLite. There are many other source files. Each has a succinct header comment that describes its purpose and role within the larger system. @@ -307,7 +307,7 @@ describes its purpose and role within the larger system. ## Verifying Code Authenticity The `manifest` file at the root directory of the source tree -contains either a SHA3-256 hash (for newer files) or a SHA1 hash (for +contains either a SHA3-256 hash (for newer files) or a SHA1 hash (for older files) for every source file in the repository. The name of the version of the entire source tree is just the SHA3-256 hash of the `manifest` file itself, possibly with the diff --git a/VERSION b/VERSION index f94ed442b..5f7b534c9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.38.5 +3.39.2 diff --git a/configure b/configure index 50e4ee456..24135817d 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.69 for sqlite 3.38.5. +# Generated by GNU Autoconf 2.69 for sqlite 3.39.2. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.38.5' -PACKAGE_STRING='sqlite 3.38.5' +PACKAGE_VERSION='3.39.2' +PACKAGE_STRING='sqlite 3.39.2' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1468,7 +1468,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.38.5 to adapt to many kinds of systems. +\`configure' configures sqlite 3.39.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1533,7 +1533,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.38.5:";; + short | recursive ) echo "Configuration of sqlite 3.39.2:";; esac cat <<\_ACEOF @@ -1661,7 +1661,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.38.5 +sqlite configure 3.39.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2080,7 +2080,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.38.5, which was +It was created by sqlite $as_me 3.39.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12390,7 +12390,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.38.5, which was +This file was extended by sqlite $as_me 3.39.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12456,7 +12456,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.38.5 +sqlite config.status 3.39.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/fiddle/EXPORTED_FUNCTIONS.fiddle b/ext/fiddle/EXPORTED_FUNCTIONS.fiddle new file mode 100644 index 000000000..b96ce4e67 --- /dev/null +++ b/ext/fiddle/EXPORTED_FUNCTIONS.fiddle @@ -0,0 +1,7 @@ +_fiddle_exec +_fiddle_interrupt +_fiddle_experiment +_fiddle_the_db +_fiddle_db_arg +_fiddle_db_filename +_fiddle_reset_db diff --git a/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api b/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api new file mode 100644 index 000000000..3127b294d --- /dev/null +++ b/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api @@ -0,0 +1,51 @@ +_sqlite3_bind_blob +_sqlite3_bind_double +_sqlite3_bind_int +_sqlite3_bind_int64 +_sqlite3_bind_null +_sqlite3_bind_parameter_count +_sqlite3_bind_parameter_index +_sqlite3_bind_text +_sqlite3_changes +_sqlite3_clear_bindings +_sqlite3_close_v2 +_sqlite3_column_blob +_sqlite3_column_bytes +_sqlite3_column_count +_sqlite3_column_count +_sqlite3_column_double +_sqlite3_column_int +_sqlite3_column_int64 +_sqlite3_column_name +_sqlite3_column_text +_sqlite3_column_type +_sqlite3_compileoption_get +_sqlite3_compileoption_used +_sqlite3_create_function_v2 +_sqlite3_data_count +_sqlite3_db_filename +_sqlite3_errmsg +_sqlite3_exec +_sqlite3_finalize +_sqlite3_interrupt +_sqlite3_libversion +_sqlite3_open +_sqlite3_open_v2 +_sqlite3_prepare_v2 +_sqlite3_prepare_v2 +_sqlite3_reset +_sqlite3_result_blob +_sqlite3_result_double +_sqlite3_result_error +_sqlite3_result_int +_sqlite3_result_null +_sqlite3_result_text +_sqlite3_sourceid +_sqlite3_sql +_sqlite3_step +_sqlite3_value_blob +_sqlite3_value_bytes +_sqlite3_value_double +_sqlite3_value_text +_sqlite3_value_type +_free diff --git a/ext/fiddle/EXPORTED_RUNTIME_METHODS b/ext/fiddle/EXPORTED_RUNTIME_METHODS new file mode 100644 index 000000000..763722d23 --- /dev/null +++ b/ext/fiddle/EXPORTED_RUNTIME_METHODS @@ -0,0 +1,14 @@ +ALLOC_NORMAL +FS +UTF8ToString +addFunction +allocate +allocateUTF8OnStack +ccall +cwrap +getValue +removeFunction +setValue +stackAlloc +stackRestore +stackSave diff --git a/ext/fiddle/Makefile b/ext/fiddle/Makefile new file mode 100644 index 000000000..093f9260f --- /dev/null +++ b/ext/fiddle/Makefile @@ -0,0 +1,34 @@ +# This GNU makefile exists primarily to simplify/speed up development +# from emacs. It is not part of the canonical build process. +default: + $(MAKE) -C ../.. wasm -e emcc_opt=-O0 + +clean: + $(MAKE) -C ../../ clean-wasm + +fiddle_files = emscripten.css fiddle.html \ + fiddle.js fiddle-module.js \ + fiddle-module.wasm fiddle-worker.js \ + $(wildcard *.wasm.gz) $(wildcard *.js.gz) + +# fiddle_remote is the remote destination for the fiddle app. It +# must be a [user@]HOST:/path for rsync. +# Note that the target "should probably" contain a symlink of +# index.html -> fiddle.html. +fiddle_remote ?= +ifeq (,$(fiddle_remote)) +ifneq (,$(wildcard /home/stephan)) + fiddle_remote = wh2:www/wh/sqlite3/. +else ifneq (,$(wildcard /home/drh)) + #fiddle_remote = if appropriate, add that user@host:/path here +endif +endif + +$(fiddle_files): default + +push-fiddle: $(fiddle_files) + @if [ x = "x$(fiddle_remote)" ]; then \ + echo "fiddle_remote must be a [user@]HOST:/path for rsync"; \ + exit 1; \ + fi + rsync -va $(fiddle_files) $(fiddle_remote) diff --git a/ext/fiddle/SqliteTestUtil.js b/ext/fiddle/SqliteTestUtil.js new file mode 100644 index 000000000..cf78946dc --- /dev/null +++ b/ext/fiddle/SqliteTestUtil.js @@ -0,0 +1,144 @@ +/* + 2022-05-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 bootstrapping code used by various test scripts + which live in this file's directory. +*/ +(function(){ + /* querySelectorAll() proxy */ + const EAll = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelectorAll(arguments[arguments.length-1]); + }; + /* querySelector() proxy */ + const E = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelector(arguments[arguments.length-1]); + }; + + /** + Helpers for writing sqlite3-specific tests. + */ + self/*window or worker*/.SqliteTestUtil = { + /** Running total of the number of tests run via + this API. */ + counter: 0, + /** + If expr is a function, it is called and its result + is returned, coerced to a bool, else expr, coerced to + a bool, is returned. + */ + toBool: function(expr){ + return (expr instanceof Function) ? !!expr() : !!expr; + }, + /** abort() if expr is false. If expr is a function, it + is called and its result is evaluated. + */ + assert: function f(expr, msg){ + if(!f._){ + f._ = ('undefined'===typeof abort + ? (msg)=>{throw new Error(msg)} + : abort); + } + ++this.counter; + if(!this.toBool(expr)){ + f._(msg || "Assertion failed."); + } + return this; + }, + /** Identical to assert() but throws instead of calling + abort(). */ + affirm: function(expr, msg){ + ++this.counter; + if(!this.toBool(expr)) throw new Error(msg || "Affirmation failed."); + return this; + }, + /** Calls f() and squelches any exception it throws. If it + does not throw, this function throws. */ + mustThrow: function(f, msg){ + ++this.counter; + let err; + try{ f(); } catch(e){err=e;} + if(!err) throw new Error(msg || "Expected exception."); + return this; + }, + /** Throws if expr is truthy or expr is a function and expr() + returns truthy. */ + throwIf: function(expr, msg){ + ++this.counter; + if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); + return this; + }, + /** Throws if expr is falsy or expr is a function and expr() + returns falsy. */ + throwUnless: function(expr, msg){ + ++this.counter; + if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); + return this; + } + }; + + + /** + This is a module object for use with the emscripten-installed + initSqlite3Module() factory function. + */ + self.sqlite3TestModule = { + postRun: [ + /* function(theModule){...} */ + ], + //onRuntimeInitialized: function(){}, + /* Proxy for C-side stdout output. */ + print: function(){ + console.log.apply(console, Array.prototype.slice.call(arguments)); + }, + /* Proxy for C-side stderr output. */ + printErr: function(){ + console.error.apply(console, Array.prototype.slice.call(arguments)); + }, + /** + Called by the module init bits to report loading + progress. It gets passed an empty argument when loading is + done (after onRuntimeInitialized() and any this.postRun + callbacks have been run). + */ + setStatus: function f(text){ + if(!f.last){ + f.last = { text: '', step: 0 }; + f.ui = { + status: E('#module-status'), + progress: E('#module-progress'), + spinner: E('#module-spinner') + }; + } + if(text === f.last.text) return; + f.last.text = text; + if(f.ui.progress){ + f.ui.progress.value = f.last.step; + f.ui.progress.max = f.last.step + 1; + } + ++f.last.step; + if(text) { + f.ui.status.classList.remove('hidden'); + f.ui.status.innerText = text; + }else{ + if(f.ui.progress){ + f.ui.progress.remove(); + f.ui.spinner.remove(); + delete f.ui.progress; + delete f.ui.spinner; + } + f.ui.status.classList.add('hidden'); + } + } + }; +})(self/*window or worker*/); diff --git a/ext/fiddle/emscripten.css b/ext/fiddle/emscripten.css new file mode 100644 index 000000000..7e3dc811d --- /dev/null +++ b/ext/fiddle/emscripten.css @@ -0,0 +1,24 @@ +/* emcscript-related styling, used during the module load/intialization processes... */ +.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } +div.emscripten { text-align: center; } +div.emscripten_border { border: 1px solid black; } +#module-spinner { overflow: visible; } +#module-spinner > * { + margin-top: 1em; +} +.spinner { + height: 50px; + width: 50px; + margin: 0px auto; + animation: rotation 0.8s linear infinite; + border-left: 10px solid rgb(0,150,240); + border-right: 10px solid rgb(0,150,240); + border-bottom: 10px solid rgb(0,150,240); + border-top: 10px solid rgb(100,0,200); + border-radius: 100%; + background-color: rgb(200,100,250); +} +@keyframes rotation { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} diff --git a/ext/fiddle/fiddle-worker.js b/ext/fiddle/fiddle-worker.js new file mode 100644 index 000000000..ca562323c --- /dev/null +++ b/ext/fiddle/fiddle-worker.js @@ -0,0 +1,301 @@ +/* + 2022-05-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 is the JS Worker file for the sqlite3 fiddle app. It loads the + sqlite3 wasm module and offers access to the db via the Worker + message-passing interface. + + Forewarning: this API is still very much Under Construction and + subject to any number of changes as experience reveals what those + need to be. + + Because we can have only a single message handler, as opposed to an + arbitrary number of discrete event listeners like with DOM elements, + we have to define a lower-level message API. Messages abstractly + look like: + + { type: string, data: type-specific value } + + Where 'type' is used for dispatching and 'data' is a + 'type'-dependent value. + + The 'type' values expected by each side of the main/worker + connection vary. The types are described below but subject to + change at any time as this experiment evolves. + + Workers-to-Main types + + - stdout, stderr: indicate stdout/stderr output from the wasm + layer. The data property is the string of the output, noting + that the emscripten binding emits these one line at a time. Thus, + if a C-side puts() emits multiple lines in a single call, the JS + side will see that as multiple calls. Example: + + {type:'stdout', data: 'Hi, world.'} + + - module: Status text. This is intended to alert the main thread + about module loading status so that, e.g., the main thread can + update a progress widget and DTRT when the module is finished + loading and available for work. Status messages come in the form + + {type:'module', data:{ + type:'status', + data: {text:string|null, step:1-based-integer} + } + + with an incrementing step value for each subsequent message. When + the module loading is complete, a message with a text value of + null is posted. + + - working: data='start'|'end'. Indicates that work is about to be + sent to the module or has just completed. This can be used, e.g., + to disable UI elements which should not be activated while work + is pending. Example: + + {type:'working', data:'start'} + + Main-to-Worker types: + + - shellExec: data=text to execute as if it had been entered in the + sqlite3 CLI shell app (as opposed to sqlite3_exec()). This event + causes the worker to emit a 'working' event (data='start') before + it starts and a 'working' event (data='end') when it finished. If + called while work is currently being executed it emits stderr + message instead of doing actual work, as the underlying db cannot + handle concurrent tasks. Example: + + {type:'shellExec', data: 'select * from sqlite_master'} + + - More TBD as the higher-level db layer develops. +*/ + +/* + Apparent browser(s) bug: console messages emitted may be duplicated + in the console, even though they're provably only run once. See: + + https://stackoverflow.com/questions/49659464 + + Noting that it happens in Firefox as well as Chrome. Harmless but + annoying. +*/ +"use strict"; +(function(){ + /** + Posts a message in the form {type,data} unless passed more than 2 + args, in which case it posts {type, data:[arg1...argN]}. + */ + const wMsg = function(type,data){ + postMessage({ + type, + data: arguments.length<3 + ? data + : Array.prototype.slice.call(arguments,1) + }); + }; + + const stdout = function(){wMsg('stdout', Array.prototype.slice.call(arguments));}; + const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));}; + + self.onerror = function(/*message, source, lineno, colno, error*/) { + const err = arguments[4]; + if(err && 'ExitStatus'==err.name){ + /* This is relevant for the sqlite3 shell binding but not the + lower-level binding. */ + fiddleModule.isDead = true; + stderr("FATAL ERROR:", err.message); + stderr("Restarting the app requires reloading the page."); + wMsg('error', err); + } + console.error(err); + fiddleModule.setStatus('Exception thrown, see JavaScript console: '+err); + }; + + const Sqlite3Shell = { + /** Returns the name of the currently-opened db. */ + dbFilename: function f(){ + if(!f._) f._ = fiddleModule.cwrap('fiddle_db_filename', "string", ['string']); + return f._(); + }, + /** + Runs the given text through the shell as if it had been typed + in by a user. Fires a working/start event before it starts and + working/end event when it finishes. + */ + exec: function f(sql){ + if(!f._) f._ = fiddleModule.cwrap('fiddle_exec', null, ['string']); + if(fiddleModule.isDead){ + stderr("shell module has exit()ed. Cannot run SQL."); + return; + } + wMsg('working','start'); + try { + if(f._running){ + stderr('Cannot run multiple commands concurrently.'); + }else{ + f._running = true; + f._(sql); + } + } finally { + delete f._running; + wMsg('working','end'); + } + }, + resetDb: function f(){ + if(!f._) f._ = fiddleModule.cwrap('fiddle_reset_db', null); + stdout("Resetting database."); + f._(); + stdout("Reset",this.dbFilename()); + }, + /* Interrupt can't work: this Worker is tied up working, so won't get the + interrupt event which would be needed to perform the interrupt. */ + interrupt: function f(){ + if(!f._) f._ = fiddleModule.cwrap('fiddle_interrupt', null); + stdout("Requesting interrupt."); + f._(); + } + }; + + self.onmessage = function f(ev){ + ev = ev.data; + if(!f.cache){ + f.cache = { + prevFilename: null + }; + } + //console.debug("worker: onmessage.data",ev); + switch(ev.type){ + case 'shellExec': Sqlite3Shell.exec(ev.data); return; + case 'db-reset': Sqlite3Shell.resetDb(); return; + case 'interrupt': Sqlite3Shell.interrupt(); return; + /** Triggers the export of the current db. Fires an + event in the form: + + {type:'db-export', + data:{ + filename: name of db, + buffer: contents of the db file (Uint8Array), + error: on error, a message string and no buffer property. + } + } + */ + case 'db-export': { + const fn = Sqlite3Shell.dbFilename(); + stdout("Exporting",fn+"."); + const fn2 = fn ? fn.split(/[/\\]/).pop() : null; + try{ + if(!fn2) throw new Error("DB appears to be closed."); + wMsg('db-export',{ + filename: fn2, + buffer: fiddleModule.FS.readFile(fn, {encoding:"binary"}) + }); + }catch(e){ + /* Post a failure message so that UI elements disabled + during the export can be re-enabled. */ + wMsg('db-export',{ + filename: fn, + error: e.message + }); + } + return; + } + case 'open': { + /* Expects: { + buffer: ArrayBuffer | Uint8Array, + filename: for logging/informational purposes only + } */ + const opt = ev.data; + let buffer = opt.buffer; + if(buffer instanceof Uint8Array){ + }else if(buffer instanceof ArrayBuffer){ + buffer = new Uint8Array(buffer); + }else{ + stderr("'open' expects {buffer:Uint8Array} containing an uploaded db."); + return; + } + const fn = ( + opt.filename + ? opt.filename.split(/[/\\]/).pop().replace('"','_') + : ("db-"+((Math.random() * 10000000) | 0)+ + "-"+((Math.random() * 10000000) | 0)+".sqlite3") + ); + /* We cannot delete the existing db file until the new one + is installed, which means that we risk overflowing our + quota (if any) by having both the previous and current + db briefly installed in the virtual filesystem. */ + fiddleModule.FS.createDataFile("/", fn, buffer, true, true); + const oldName = Sqlite3Shell.dbFilename(); + Sqlite3Shell.exec('.open "/'+fn+'"'); + if(oldName && oldName !== fn){ + try{fiddleModule.FS.unlink(oldName);} + catch(e){/*ignored*/} + } + stdout("Replaced DB with",fn+"."); + return; + } + }; + console.warn("Unknown fiddle-worker message type:",ev); + }; + + /** + emscripten module for use with build mode -sMODULARIZE. + */ + const fiddleModule = { + print: stdout, + printErr: stderr, + /** + Intercepts status updates from the emscripting module init + and fires worker events with a type of 'status' and a + payload of: + + { + text: string | null, // null at end of load process + step: integer // starts at 1, increments 1 per call + } + + We have no way of knowing in advance how many steps will + be processed/posted, so creating a "percentage done" view is + not really practical. One can be approximated by giving it a + current value of message.step and max value of message.step+1, + though. + + When work is finished, a message with a text value of null is + submitted. + + After a message with text==null is posted, the module may later + post messages about fatal problems, e.g. an exit() being + triggered, so it is recommended that UI elements for posting + status messages not be outright removed from the DOM when + text==null, and that they instead be hidden until/unless + text!=null. + */ + setStatus: function f(text){ + if(!f.last) f.last = { step: 0, text: '' }; + else if(text === f.last.text) return; + f.last.text = text; + wMsg('module',{ + type:'status', + data:{step: ++f.last.step, text: text||null} + }); + } + }; + + importScripts('fiddle-module.js'); + /** + initFiddleModule() is installed via fiddle-module.js due to + building with: + + emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initFiddleModule + */ + initFiddleModule(fiddleModule).then(function(thisModule){ + wMsg('fiddle-ready'); + }); +})(); diff --git a/ext/fiddle/fiddle.html b/ext/fiddle/fiddle.html new file mode 100644 index 000000000..3570b283e --- /dev/null +++ b/ext/fiddle/fiddle.html @@ -0,0 +1,283 @@ + + + + + + SQLite3 Fiddle + + + + + + + +
      + +
      +
      +
      Initializing app...
      +
      + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
      +
      +
      Downloading...
      +
      + +
      + + + +
      +
      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      +
      + + + + +
      +
      +
      +
      +
      + + + +
      +
      +
      +
      +
      + + + + diff --git a/ext/fiddle/fiddle.js b/ext/fiddle/fiddle.js new file mode 100644 index 000000000..619ce4eca --- /dev/null +++ b/ext/fiddle/fiddle.js @@ -0,0 +1,809 @@ +/* + 2022-05-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 is the main entry point for the sqlite3 fiddle app. It sets up the + various UI bits, loads a Worker for the db connection, and manages the + communication between the UI and worker. +*/ +(function(){ + 'use strict'; + /* Recall that the 'self' symbol, except where locally + overwritten, refers to the global window or worker object. */ + + const storage = (function(NS/*namespace object in which to store this module*/){ + /* Pedantic licensing note: this code originated in the Fossil SCM + source tree, where it has a different license, but the person who + ported it into sqlite is the same one who wrote it for fossil. */ + 'use strict'; + NS = NS||{}; + + /** + This module provides a basic wrapper around localStorage + or sessionStorage or a dummy proxy object if neither + of those are available. + */ + const tryStorage = function f(obj){ + if(!f.key) f.key = 'storage.access.check'; + try{ + obj.setItem(f.key, 'f'); + const x = obj.getItem(f.key); + obj.removeItem(f.key); + if(x!=='f') throw new Error(f.key+" failed") + return obj; + }catch(e){ + return undefined; + } + }; + + /** Internal storage impl for this module. */ + const $storage = + tryStorage(window.localStorage) + || tryStorage(window.sessionStorage) + || tryStorage({ + // A basic dummy xyzStorage stand-in + $$$:{}, + setItem: function(k,v){this.$$$[k]=v}, + getItem: function(k){ + return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined; + }, + removeItem: function(k){delete this.$$$[k]}, + clear: function(){this.$$$={}} + }); + + /** + For the dummy storage we need to differentiate between + $storage and its real property storage for hasOwnProperty() + to work properly... + */ + const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage; + + /** + A prefix which gets internally applied to all storage module + property keys so that localStorage and sessionStorage across the + same browser profile instance do not "leak" across multiple apps + being hosted by the same origin server. Such cross-polination is + still there but, with this key prefix applied, it won't be + immediately visible via the storage API. + + With this in place we can justify using localStorage instead of + sessionStorage. + + One implication of using localStorage and sessionStorage is that + their scope (the same "origin" and client application/profile) + allows multiple apps on the same origin to use the same + storage. Thus /appA/foo could then see changes made via + /appB/foo. The data do not cross user- or browser boundaries, + though, so it "might" arguably be called a + feature. storageKeyPrefix was added so that we can sandbox that + state for each separate app which shares an origin. + + See: https://fossil-scm.org/forum/forumpost/4afc4d34de + + Sidebar: it might seem odd to provide a key prefix and stick all + properties in the topmost level of the storage object. We do that + because adding a layer of object to sandbox each app would mean + (de)serializing that whole tree on every storage property change. + e.g. instead of storageObject.projectName.foo we have + storageObject[storageKeyPrefix+'foo']. That's soley for + efficiency's sake (in terms of battery life and + environment-internal storage-level effort). + */ + const storageKeyPrefix = ( + $storageHolder===$storage/*localStorage or sessionStorage*/ + ? ( + (NS.config ? + (NS.config.projectCode || NS.config.projectName + || NS.config.shortProjectName) + : false) + || window.location.pathname + )+'::' : ( + '' /* transient storage */ + ) + ); + + /** + A proxy for localStorage or sessionStorage or a + page-instance-local proxy, if neither one is availble. + + Which exact storage implementation is uses is unspecified, and + apps must not rely on it. + */ + NS.storage = { + storageKeyPrefix: storageKeyPrefix, + /** Sets the storage key k to value v, implicitly converting + it to a string. */ + set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v), + /** Sets storage key k to JSON.stringify(v). */ + setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)), + /** Returns the value for the given storage key, or + dflt if the key is not found in the storage. */ + get: (k,dflt)=>$storageHolder.hasOwnProperty( + storageKeyPrefix+k + ) ? $storage.getItem(storageKeyPrefix+k) : dflt, + /** Returns true if the given key has a value of "true". If the + key is not found, it returns true if the boolean value of dflt + is "true". (Remember that JS persistent storage values are all + strings.) */ + getBool: function(k,dflt){ + return 'true'===this.get(k,''+(!!dflt)); + }, + /** Returns the JSON.parse()'d value of the given + storage key's value, or dflt is the key is not + found or JSON.parse() fails. */ + getJSON: function f(k,dflt){ + try { + const x = this.get(k,f); + return x===f ? dflt : JSON.parse(x); + } + catch(e){return dflt} + }, + /** Returns true if the storage contains the given key, + else false. */ + contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k), + /** Removes the given key from the storage. Returns this. */ + remove: function(k){ + $storage.removeItem(storageKeyPrefix+k); + return this; + }, + /** Clears ALL keys from the storage. Returns this. */ + clear: function(){ + this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k)); + return this; + }, + /** Returns an array of all keys currently in the storage. */ + keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)), + /** Returns true if this storage is transient (only available + until the page is reloaded), indicating that fileStorage + and sessionStorage are unavailable. */ + isTransient: ()=>$storageHolder!==$storage, + /** Returns a symbolic name for the current storage mechanism. */ + storageImplName: function(){ + if($storage===window.localStorage) return 'localStorage'; + else if($storage===window.sessionStorage) return 'sessionStorage'; + else return 'transient'; + }, + + /** + Returns a brief help text string for the currently-selected + storage type. + */ + storageHelpDescription: function(){ + return { + localStorage: "Browser-local persistent storage with an "+ + "unspecified long-term lifetime (survives closing the browser, "+ + "but maybe not a browser upgrade).", + sessionStorage: "Storage local to this browser tab, "+ + "lost if this tab is closed.", + "transient": "Transient storage local to this invocation of this page." + }[this.storageImplName()]; + } + }; + return NS.storage; + })({})/*storage API setup*/; + + + /** Name of the stored copy of SqliteFiddle.config. */ + const configStorageKey = 'sqlite3-fiddle-config'; + + /** + The SqliteFiddle object is intended to be the primary + app-level object for the main-thread side of the sqlite + fiddle application. It uses a worker thread to load the + sqlite WASM module and communicate with it. + */ + const SF/*local convenience alias*/ + = window.SqliteFiddle/*canonical name*/ = { + /* Config options. */ + config: { + /* If true, SqliteFiddle.echo() will auto-scroll the + output widget to the bottom when it receives output, + else it won't. */ + autoScrollOutput: true, + /* If true, the output area will be cleared before each + command is run, else it will not. */ + autoClearOutput: false, + /* If true, SqliteFiddle.echo() will echo its output to + the console, in addition to its normal output widget. + That slows it down but is useful for testing. */ + echoToConsole: false, + /* If true, display input/output areas side-by-side. */ + sideBySide: true, + /* If true, swap positions of the input/output areas. */ + swapInOut: false + }, + /** + Emits the given text, followed by a line break, to the + output widget. If given more than one argument, they are + join()'d together with a space between each. As a special + case, if passed a single array, that array is used in place + of the arguments array (this is to facilitate receiving + lists of arguments via worker events). + */ + echo: function f(text) { + /* Maintenance reminder: we currently require/expect a textarea + output element. It might be nice to extend this to behave + differently if the output element is a non-textarea element, + in which case it would need to append the given text as a TEXT + node and add a line break. */ + if(!f._){ + f._ = document.getElementById('output'); + f._.value = ''; // clear browser cache + } + if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); + else if(1===arguments.length && Array.isArray(text)) text = text.join(' '); + // These replacements are necessary if you render to raw HTML + //text = text.replace(/&/g, "&"); + //text = text.replace(//g, ">"); + //text = text.replace('\n', '
      ', 'g'); + if(null===text){/*special case: clear output*/ + f._.value = ''; + return; + }else if(this.echo._clearPending){ + delete this.echo._clearPending; + f._.value = ''; + } + if(this.config.echoToConsole) console.log(text); + if(this.jqTerm) this.jqTerm.echo(text); + f._.value += text + "\n"; + if(this.config.autoScrollOutput){ + f._.scrollTop = f._.scrollHeight; + } + }, + _msgMap: {}, + /** Adds a worker message handler for messages of the given + type. */ + addMsgHandler: function f(type,callback){ + if(Array.isArray(type)){ + type.forEach((t)=>this.addMsgHandler(t, callback)); + return this; + } + (this._msgMap.hasOwnProperty(type) + ? this._msgMap[type] + : (this._msgMap[type] = [])).push(callback); + return this; + }, + /** Given a worker message, runs all handlers for msg.type. */ + runMsgHandlers: function(msg){ + const list = (this._msgMap.hasOwnProperty(msg.type) + ? this._msgMap[msg.type] : false); + if(!list){ + console.warn("No handlers found for message type:",msg); + return false; + } + //console.debug("runMsgHandlers",msg); + list.forEach((f)=>f(msg)); + return true; + }, + /** Removes all message handlers for the given message type. */ + clearMsgHandlers: function(type){ + delete this._msgMap[type]; + return this; + }, + /* Posts a message in the form {type, data} to the db worker. Returns this. */ + wMsg: function(type,data){ + this.worker.postMessage({type, data}); + return this; + }, + /** + Prompts for confirmation and, if accepted, deletes + all content and tables in the (transient) database. + */ + resetDb: function(){ + if(window.confirm("Really destroy all content and tables " + +"in the (transient) db?")){ + this.wMsg('db-reset'); + } + return this; + }, + /** Stores this object's config in the browser's storage. */ + storeConfig: function(){ + storage.setJSON(configStorageKey,this.config); + } + }; + + if(1){ /* Restore SF.config */ + const storedConfig = storage.getJSON(configStorageKey); + if(storedConfig){ + /* Copy all properties to SF.config which are currently in + storedConfig. We don't bother copying any other + properties: those have been removed from the app in the + meantime. */ + Object.keys(SF.config).forEach(function(k){ + if(storedConfig.hasOwnProperty(k)){ + SF.config[k] = storedConfig[k]; + } + }); + } + } + + SF.worker = new Worker('fiddle-worker.js'); + SF.worker.onmessage = (ev)=>SF.runMsgHandlers(ev.data); + SF.addMsgHandler(['stdout', 'stderr'], (ev)=>SF.echo(ev.data)); + + /* querySelectorAll() proxy */ + const EAll = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelectorAll(arguments[arguments.length-1]); + }; + /* querySelector() proxy */ + const E = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelector(arguments[arguments.length-1]); + }; + + /** Handles status updates from the Module object. */ + SF.addMsgHandler('module', function f(ev){ + ev = ev.data; + if('status'!==ev.type){ + console.warn("Unexpected module-type message:",ev); + return; + } + if(!f.ui){ + f.ui = { + status: E('#module-status'), + progress: E('#module-progress'), + spinner: E('#module-spinner') + }; + } + const msg = ev.data; + if(f.ui.progres){ + progress.value = msg.step; + progress.max = msg.step + 1/*we don't know how many steps to expect*/; + } + if(1==msg.step){ + f.ui.progress.classList.remove('hidden'); + f.ui.spinner.classList.remove('hidden'); + } + if(msg.text){ + f.ui.status.classList.remove('hidden'); + f.ui.status.innerText = msg.text; + }else{ + if(f.ui.progress){ + f.ui.progress.remove(); + f.ui.spinner.remove(); + delete f.ui.progress; + delete f.ui.spinner; + } + f.ui.status.classList.add('hidden'); + /* The module can post messages about fatal problems, + e.g. an exit() being triggered or assertion failure, + after the last "load" message has arrived, so + leave f.ui.status and message listener intact. */ + } + }); + + /** + The 'fiddle-ready' event is fired (with no payload) when the + wasm module has finished loading. Interestingly, that happens + _before_ the final module:status event */ + SF.addMsgHandler('fiddle-ready', function(){ + SF.clearMsgHandlers('fiddle-ready'); + self.onSFLoaded(); + }); + + /** + Performs all app initialization which must wait until after the + worker module is loaded. This function removes itself when it's + called. + */ + self.onSFLoaded = function(){ + delete this.onSFLoaded; + // Unhide all elements which start out hidden + EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); + E('#btn-reset').addEventListener('click',()=>SF.resetDb()); + const taInput = E('#input'); + const btnClearIn = E('#btn-clear'); + btnClearIn.addEventListener('click',function(){ + taInput.value = ''; + },false); + // Ctrl-enter and shift-enter both run the current SQL. + taInput.addEventListener('keydown',function(ev){ + if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){ + ev.preventDefault(); + ev.stopPropagation(); + btnShellExec.click(); + } + }, false); + const taOutput = E('#output'); + const btnClearOut = E('#btn-clear-output'); + btnClearOut.addEventListener('click',function(){ + taOutput.value = ''; + if(SF.jqTerm) SF.jqTerm.clear(); + },false); + const btnShellExec = E('#btn-shell-exec'); + btnShellExec.addEventListener('click',function(ev){ + let sql; + ev.preventDefault(); + if(taInput.selectionStart e.addEventListener('click', cmdClick, false) + ); + + btnInterrupt.addEventListener('click',function(){ + SF.wMsg('interrupt'); + }); + + /** Initiate a download of the db. */ + const btnExport = E('#btn-export'); + const eLoadDb = E('#load-db'); + const btnLoadDb = E('#btn-load-db'); + btnLoadDb.addEventListener('click', ()=>eLoadDb.click()); + /** + Enables (if passed true) or disables all UI elements which + "might," if timed "just right," interfere with an + in-progress db import/export/exec operation. + */ + const enableMutatingElements = function f(enable){ + if(!f._elems){ + f._elems = [ + /* UI elements to disable while import/export are + running. Normally the export is fast enough + that this won't matter, but we really don't + want to be reading (from outside of sqlite) the + db when the user taps btnShellExec. */ + btnShellExec, btnExport, eLoadDb + ]; + } + f._elems.forEach( enable + ? (e)=>e.removeAttribute('disabled') + : (e)=>e.setAttribute('disabled','disabled') ); + }; + btnExport.addEventListener('click',function(){ + enableMutatingElements(false); + SF.wMsg('db-export'); + }); + SF.addMsgHandler('db-export', function(ev){ + enableMutatingElements(true); + ev = ev.data; + if(ev.error){ + SF.echo("Export failed:",ev.error); + return; + } + const blob = new Blob([ev.buffer], {type:"application/x-sqlite3"}); + const a = document.createElement('a'); + document.body.appendChild(a); + a.href = window.URL.createObjectURL(blob); + a.download = ev.filename; + a.addEventListener('click',function(){ + setTimeout(function(){ + SF.echo("Exported (possibly auto-downloaded):",ev.filename); + window.URL.revokeObjectURL(a.href); + a.remove(); + },500); + }); + a.click(); + }); + /** + Handle load/import of an external db file. + */ + eLoadDb.addEventListener('change',function(){ + const f = this.files[0]; + const r = new FileReader(); + const status = {loaded: 0, total: 0}; + enableMutatingElements(false); + r.addEventListener('loadstart', function(){ + SF.echo("Loading",f.name,"..."); + }); + r.addEventListener('progress', function(ev){ + SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes."); + }); + const that = this; + r.addEventListener('load', function(){ + enableMutatingElements(true); + SF.echo("Loaded",f.name+". Opening db..."); + SF.wMsg('open',{ + filename: f.name, + buffer: this.result + }); + }); + r.addEventListener('error',function(){ + enableMutatingElements(true); + SF.echo("Loading",f.name,"failed for unknown reasons."); + }); + r.addEventListener('abort',function(){ + enableMutatingElements(true); + SF.echo("Cancelled loading of",f.name+"."); + }); + r.readAsArrayBuffer(f); + }); + + EAll('fieldset.collapsible').forEach(function(fs){ + const btnToggle = E(fs,'legend > .fieldset-toggle'), + content = EAll(fs,':scope > div'); + btnToggle.addEventListener('click', function(){ + fs.classList.toggle('collapsed'); + content.forEach((d)=>d.classList.toggle('hidden')); + }, false); + }); + + /** + Given a DOM element, this routine measures its "effective + height", which is the bounding top/bottom range of this element + and all of its children, recursively. For some DOM structure + cases, a parent may have a reported height of 0 even though + children have non-0 sizes. + + Returns 0 if !e or if the element really has no height. + */ + const effectiveHeight = function f(e){ + if(!e) return 0; + if(!f.measure){ + f.measure = function callee(e, depth){ + if(!e) return; + const m = e.getBoundingClientRect(); + if(0===depth){ + callee.top = m.top; + callee.bottom = m.bottom; + }else{ + callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; + callee.bottom = Math.max(callee.bottom, m.bottom); + } + Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); + if(0===depth){ + //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); + f.extra += callee.bottom - callee.top; + } + return f.extra; + }; + } + f.extra = 0; + f.measure(e,0); + return f.extra; + }; + + /** + Returns a function, that, as long as it continues to be invoked, + will not be triggered. The function will be called after it stops + being called for N milliseconds. If `immediate` is passed, call + the callback immediately and hinder future invocations until at + least the given time has passed. + + If passed only 1 argument, or passed a falsy 2nd argument, + the default wait time set in this function's $defaultDelay + property is used. + + Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function + */ + const debounce = function f(func, wait, immediate) { + var timeout; + if(!wait) wait = f.$defaultDelay; + return function() { + const context = this, args = Array.prototype.slice.call(arguments); + const later = function() { + timeout = undefined; + if(!immediate) func.apply(context, args); + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if(callNow) func.apply(context, args); + }; + }; + debounce.$defaultDelay = 500 /*arbitrary*/; + + const ForceResizeKludge = (function(){ + /* Workaround for Safari mayhem regarding use of vh CSS + units.... We cannot use vh units to set the main view + size because Safari chokes on that, so we calculate + that height here. Larger than ~95% is too big for + Firefox on Android, causing the input area to move + off-screen. */ + const appViews = EAll('.app-view'); + const elemsToCount = [ + /* Elements which we need to always count in the + visible body size. */ + E('body > header'), + E('body > footer') + ]; + const resized = function f(){ + if(f.$disabled) return; + const wh = window.innerHeight; + var ht; + var extra = 0; + elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false); + ht = wh - extra; + appViews.forEach(function(e){ + e.style.height = + e.style.maxHeight = [ + "calc(", (ht>=100 ? ht : 100), "px", + " - 2em"/*fudge value*/,")" + /* ^^^^ hypothetically not needed, but both + Chrome/FF on Linux will force scrollbars on the + body if this value is too small. */ + ].join(''); + }); + }; + resized.$disabled = true/*gets deleted when setup is finished*/; + window.addEventListener('resize', debounce(resized, 250), false); + return resized; + })(); + + /** Set up a selection list of examples */ + (function(){ + const xElem = E('#select-examples'); + const examples = [ + {name: "Help", sql: +`-- ================================================ +-- Use ctrl-enter or shift-enter to execute sqlite3 +-- shell commands and SQL. +-- If a subset of the text is currently selected, +-- only that part is executed. +-- ================================================ +.help`}, + {name: "Timer on", sql: ".timer on"}, + {name: "Setup table T", sql:`.nullvalue NULL +CREATE TABLE t(a,b); +INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012); +SELECT * FROM t;`}, + {name: "Table list", sql: ".tables"}, + {name: "Box Mode", sql: ".mode box"}, + {name: "JSON Mode", sql: ".mode json"}, + {name: "Mandlebrot", sql: `WITH RECURSIVE + xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2), + yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0), + m(iter, cx, cy, x, y) AS ( + SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis + UNION ALL + SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m + WHERE (x*x + y*y) < 4.0 AND iter<28 + ), + m2(iter, cx, cy) AS ( + SELECT max(iter), cx, cy FROM m GROUP BY cx, cy + ), + a(t) AS ( + SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') + FROM m2 GROUP BY cy + ) +SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`} + ]; + const newOpt = function(lbl,val){ + const o = document.createElement('option'); + o.value = val; + if(!val) o.setAttribute('disabled',true); + o.appendChild(document.createTextNode(lbl)); + xElem.appendChild(o); + }; + newOpt("Examples (replaces input!)"); + examples.forEach((o)=>newOpt(o.name, o.sql)); + //xElem.setAttribute('disabled',true); + xElem.selectedIndex = 0; + xElem.addEventListener('change', function(){ + taInput.value = '-- ' + + this.selectedOptions[0].innerText + + '\n' + this.value; + SF.dbExec(this.value); + }); + })()/* example queries */; + + SF.echo(null/*clear any output generated by the init process*/); + if(window.jQuery && window.jQuery.terminal){ + /* Set up the terminal-style view... */ + const eTerm = window.jQuery('#view-terminal').empty(); + SF.jqTerm = eTerm.terminal(SF.dbExec.bind(SF),{ + prompt: 'sqlite> ', + greetings: false /* note that the docs incorrectly call this 'greeting' */ + }); + /* Set up a button to toggle the views... */ + const head = E('header#titlebar'); + const btnToggleView = document.createElement('button'); + btnToggleView.appendChild(document.createTextNode("Toggle View")); + head.appendChild(btnToggleView); + btnToggleView.addEventListener('click',function f(){ + EAll('.app-view').forEach(e=>e.classList.toggle('hidden')); + if(document.body.classList.toggle('terminal-mode')){ + ForceResizeKludge(); + } + }, false); + btnToggleView.click()/*default to terminal view*/; + } + SF.dbExec(null/*init the db and output the header*/); + SF.echo('This experimental app is provided in the hope that it', + 'may prove interesting or useful but is not an officially', + 'supported deliverable of the sqlite project. It is subject to', + 'any number of changes or outright removal at any time.\n'); + delete ForceResizeKludge.$disabled; + ForceResizeKludge(); + + btnShellExec.click(); + }/*onSFLoaded()*/; +})(); diff --git a/ext/fiddle/index.md b/ext/fiddle/index.md new file mode 100644 index 000000000..9d1f8d83e --- /dev/null +++ b/ext/fiddle/index.md @@ -0,0 +1,94 @@ +This directory houses a "fiddle"-style application which embeds a +[Web Assembly (WASM)](https://en.wikipedia.org/wiki/WebAssembly) +build of the sqlite3 shell app into an HTML page, effectively running +the shell in a client-side browser. + +It requires [emscripten][] and that the build environment be set up for +emscripten. A mini-HOWTO for setting that up follows... + +First, install the Emscripten SDK, as documented +[here](https://emscripten.org/docs/getting_started/downloads.html) and summarized +below for Linux environments: + +``` +# Clone the emscripten repository: +$ git clone https://github.com/emscripten-core/emsdk.git +$ cd emsdk + +# Download and install the latest SDK tools: +$ ./emsdk install latest + +# Make the "latest" SDK "active" for the current user: +$ ./emsdk activate latest +``` + +Those parts only need to be run once. The following needs to be run for each +shell instance which needs the `emcc` compiler: + +``` +# Activate PATH and other environment variables in the current terminal: +$ source ./emsdk_env.sh + +$ which emcc +/path/to/emsdk/upstream/emscripten/emcc +``` + +That `env` script needs to be sourced for building this application from the +top of the sqlite3 build tree: + +``` +$ make fiddle +``` + +Or: + +``` +$ cd ext/fiddle +$ make +``` + +That will generate the fiddle application under +[ext/fiddle](/dir/ext/fiddle), as `fiddle.html`. That application +cannot, due to XMLHttpRequest security limitations, run if the HTML +file is opened directly in the browser (i.e. if it is opened using a +`file://` URL), so it needs to be served via an HTTP server. For +example, using [althttpd][]: + +``` +$ cd ext/fiddle +$ althttpd -debug 1 -jail 0 -port 9090 -root . +``` + +Then browse to `http://localhost:9090/fiddle.html`. + +Note that when serving this app via [althttpd][], it must be a version +from 2022-05-17 or newer so that it recognizes the `.wasm` file +extension and responds with the mimetype `application/wasm`, as the +WASM loader is pedantic about that detail. + +# Known Quirks and Limitations + +Some "impedence mismatch" between C and WASM/JavaScript is to be +expected. + +## No I/O + +sqlite3 shell commands which require file I/O or pipes are disabled in +the WASM build. + +## `exit()` Triggered from C + +When C code calls `exit()`, as happens (for example) when running an +"unsafe" command when safe mode is active, WASM's connection to the +sqlite3 shell environment has no sensible choice but to shut down +because `exit()` leaves it in a state we can no longer recover +from. The JavaScript-side application attempts to recognize this and +warn the user that restarting the application is necessary. Currently +the only way to restart it is to reload the page. Restructuring the +shell code such that it could be "rebooted" without restarting the +JS app would require some invasive changes which are not currently +on any TODO list but have not been entirely ruled out long-term. + + +[emscripten]: https://emscripten.org +[althttpd]: https://sqlite.org/althttpd diff --git a/ext/fiddle/sqlite3-api.js b/ext/fiddle/sqlite3-api.js new file mode 100644 index 000000000..5057eb53f --- /dev/null +++ b/ext/fiddle/sqlite3-api.js @@ -0,0 +1,1781 @@ +/* + 2022-05-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 is intended to be appended to the emcc-generated + sqlite3.js via emcc: + + emcc ... -sMODULARIZE -sEXPORT_NAME=initSqlite3Module --post-js=THIS_FILE + + It is loaded by importing the emcc-generated sqlite3.js, then: + + initSqlite3Module({module object}).then( + function(theModule){ + theModule.sqlite3 == an object containing this file's + deliverables: + { + api: bindings for much of the core sqlite3 APIs, + SQLite3: high-level OO API wrapper + } + }); + + It is up to the caller to provide a module object compatible with + emcc, but it can be a plain empty object. The object passed to + initSqlite3Module() will get populated by the emscripten-generated + bits and, in part, by the code from this file. Specifically, this file + installs the `theModule.sqlite3` part shown above. + + The resulting sqlite3.api object wraps the standard sqlite3 C API in + a way as close to its native form as JS allows for. The + sqlite3.SQLite3 object provides a higher-level wrapper more + appropriate for general client-side use in JS. + + Because using certain parts of the low-level API properly requires + some degree of WASM-related magic, it is not recommended that that + API be used as-is in client-level code. Rather, client code should + use the higher-level OO API or write a custom wrapper on top of the + lower-level API. In short, most of the C-style API is used in an + intuitive manner from JS but any C-style APIs which take + pointers-to-pointer arguments require WASM-specific interfaces + installed by emcscripten-generated code. Those which take or return + only integers, doubles, strings, or "plain" pointers to db or + statement objects can be used in "as normal," noting that "pointers" + in wasm are simply 32-bit integers. + + # Goals and Non-goals of this API + + Goals: + + - Except where noted in the non-goals, provide a more-or-less + complete wrapper to the sqlite3 C API, insofar as WASM feature + parity with C allows for. In fact, provide at least 3... + + - (1) The aforementioned C-style API. (2) An OO-style API on + top of that, designed to run in the same thread (main window or + Web Worker) as the C API. (3) A less-capable wrapper which can + work across the main window/worker boundary, where the sqlite3 API + is one of those and this wrapper is in the other. That + constellation places some considerable limitations on how the API + can be interacted with, but keeping the DB operations out of the + UI thread is generally desirable. + + - Insofar as possible, support client-side storage using JS + filesystem APIs. As of this writing, such things are still very + much TODO. + + Non-goals: + + - As WASM is a web-centric technology and UTF-8 is the King of + Encodings in that realm, there are no current plans to support the + UTF16-related APIs. They would add a complication to the bindings + for no appreciable benefit. + + - Supporting old or niche-market platforms. WASM is built for a + modern web and requires modern platforms. + +*/ +if(!Module.postRun) Module.postRun = []; +/* ^^^^ the name Module is, in this setup, scope-local in the generated + file sqlite3.js, with which this file gets combined at build-time. */ +Module.postRun.push(function(namespace/*the module object, the target for + installed features*/){ + 'use strict'; + /* For reference: sql.js does essentially everything we want and + it solves much of the wasm-related voodoo, but we'll need a + different structure because we want the db connection to run in + a worker thread and feed data back into the main + thread. Regardless of those differences, it makes a great point + of reference: + + https://github.com/sql-js/sql.js + + Some of the specific design goals here: + + - Bind a low-level sqlite3 API which is close to the native one + in terms of usage. + + - Create a higher-level one, more akin to sql.js and + node.js-style implementations. This one would speak directly + to the low-level API. This API could be used by clients who + import the low-level API directly into their main thread + (which we don't want to recommend but also don't want to + outright forbid). + + - Create a second higher-level one which speaks to the + low-level API via worker messages. This one would be intended + for use in the main thread, talking to the low-level UI via + worker messages. Because workers have only a single message + channel, some acrobatics will be needed here to feed async + work results back into client-side callbacks (as those + callbacks cannot simply be passed to the worker). Exactly + what those acrobatics should look like is not yet entirely + clear and much experimentation is pending. + */ + + const SQM = namespace/*the sqlite module object */; + + /** + Set up the main sqlite3 binding API here, mimicking the C API as + closely as we can. + + Attribution: though not a direct copy/paste, much of what + follows is strongly influenced by the sql.js implementation. + */ + const api = { + /* It is important that the following integer values match + those from the C code. Ideally we could fetch them from the + C API, e.g., in the form of a JSON object, but getting that + JSON string constructed within our current confines is + currently not worth the effort. + + Reminder to self: we could probably do so by adding the + proverbial level of indirection, calling in to C to get it, + and having that C func call an + emscripten-installed/JS-implemented library function which + builds the result object: + + const obj = {}; + sqlite3__get_enum(function(key,val){ + obj[key] = val; + }); + + but whether or not we can pass a function that way, via a + (void*) is as yet unknown. + */ + /* Minimum subset of sqlite result codes we'll need. */ + SQLITE_OK: 0, + SQLITE_ROW: 100, + SQLITE_DONE: 101, + /* sqlite data types */ + SQLITE_INTEGER: 1, + SQLITE_FLOAT: 2, + SQLITE_TEXT: 3, + SQLITE_BLOB: 4, + SQLITE_NULL: 5, + /* create_function() flags */ + SQLITE_DETERMINISTIC: 0x000000800, + SQLITE_DIRECTONLY: 0x000080000, + SQLITE_INNOCUOUS: 0x000200000, + /* sqlite encodings, used for creating UDFs, noting that we + will only support UTF8. */ + SQLITE_UTF8: 1 + }; + const cwrap = SQM.cwrap; + [/* C-side functions to bind. Each entry is an array with 3 or 4 + elements: + + ["c-side name", + "result type" (cwrap() syntax), + [arg types in cwrap() syntax] + ] + + If it has 4 elements, the first one is an alternate name to + use for the JS-side binding. That's required when overloading + a binding for two different uses. + */ + ["sqlite3_bind_blob","number",["number", "number", "number", "number", "number"]], + ["sqlite3_bind_double","number",["number", "number", "number"]], + ["sqlite3_bind_int","number",["number", "number", "number"]], + /*Noting that JS/wasm combo does not currently support 64-bit integers: + ["sqlite3_bind_int64","number",["number", "number", "number"]],*/ + ["sqlite3_bind_null","void",["number"]], + ["sqlite3_bind_parameter_count", "number", ["number"]], + ["sqlite3_bind_parameter_index","number",["number", "string"]], + ["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]], + ["sqlite3_changes", "number", ["number"]], + ["sqlite3_clear_bindings","number",["number"]], + ["sqlite3_close_v2", "number", ["number"]], + ["sqlite3_column_blob","number", ["number", "number"]], + ["sqlite3_column_bytes","number",["number", "number"]], + ["sqlite3_column_count", "number", ["number"]], + ["sqlite3_column_count","number",["number"]], + ["sqlite3_column_double","number",["number", "number"]], + ["sqlite3_column_int","number",["number", "number"]], + /*Noting that JS/wasm combo does not currently support 64-bit integers: + ["sqlite3_column_int64","number",["number", "number"]],*/ + ["sqlite3_column_name","string",["number", "number"]], + ["sqlite3_column_text","string",["number", "number"]], + ["sqlite3_column_type","number",["number", "number"]], + ["sqlite3_compileoption_get", "string", ["number"]], + ["sqlite3_compileoption_used", "number", ["string"]], + ["sqlite3_create_function_v2", "number", + ["number", "string", "number", "number","number", + "number", "number", "number", "number"]], + ["sqlite3_data_count", "number", ["number"]], + ["sqlite3_db_filename", "string", ["number", "string"]], + ["sqlite3_errmsg", "string", ["number"]], + ["sqlite3_exec", "number", ["number", "string", "number", "number", "number"]], + ["sqlite3_finalize", "number", ["number"]], + ["sqlite3_interrupt", "void", ["number"]], + ["sqlite3_libversion", "string", []], + ["sqlite3_open", "number", ["string", "number"]], + //["sqlite3_open_v2", "number", ["string", "number", "number", "string"]], + //^^^^ TODO: add the flags needed for the 3rd arg + ["sqlite3_prepare_v2", "number", ["number", "string", "number", "number", "number"]], + ["sqlite3_prepare_v2_sqlptr", "sqlite3_prepare_v2", + /* Impl which requires that the 2nd argument be a pointer to + the SQL string, instead of being converted to a + string. This is used for cases where we require a non-NULL + value for the final argument (exec()'ing multiple + statements from one input string). */ + "number", ["number", "number", "number", "number", "number"]], + ["sqlite3_reset", "number", ["number"]], + ["sqlite3_result_blob",null,["number", "number", "number", "number"]], + ["sqlite3_result_double",null,["number", "number"]], + ["sqlite3_result_error",null,["number", "string", "number"]], + ["sqlite3_result_int",null,["number", "number"]], + ["sqlite3_result_null",null,["number"]], + ["sqlite3_result_text",null,["number", "string", "number", "number"]], + ["sqlite3_sourceid", "string", []], + ["sqlite3_sql", "string", ["number"]], + ["sqlite3_step", "number", ["number"]], + ["sqlite3_value_blob", "number", ["number"]], + ["sqlite3_value_bytes","number",["number"]], + ["sqlite3_value_double","number",["number"]], + ["sqlite3_value_text", "string", ["number"]], + ["sqlite3_value_type", "number", ["number"]] + //["sqlite3_normalized_sql", "string", ["number"]] + ].forEach(function(a){ + const k = (4==a.length) ? a.shift() : a[0]; + api[k] = cwrap.apply(this, a); + }); + + /* What follows is colloquially known as "OO API #1". It is a + binding of the sqlite3 API which is designed to be run within + the same thread (main or worker) as the one in which the + sqlite3 WASM binding was initialized. This wrapper cannot use + the sqlite3 binding if, e.g., the wrapper is in the main thread + and the sqlite3 API is in a worker. */ + + /** Memory for use in some pointer-to-pointer-passing routines. */ + const pPtrArg = stackAlloc(4); + /** Throws a new error, concatenating all args with a space between + each. */ + const toss = function(){ + throw new Error(Array.prototype.join.call(arguments, ' ')); + }; + + /** + The DB class wraps a sqlite3 db handle. + + It accepts the following argument signatures: + + - () + - (undefined) (same effect as ()) + - (filename[,buffer]) + - (buffer) + + Where a buffer indicates a Uint8Array holding an sqlite3 db + image. + + If the filename is provided, only the last component of the + path is used - any path prefix is stripped and certain + "special" characters are replaced with `_`. If no name is + provided, a random name is generated. The resulting filename is + the one used for accessing the db file within root directory of + the emscripten-supplied virtual filesystem, and is set (with no + path part) as the DB object's `filename` property. + + Note that the special sqlite3 db names ":memory:" and "" + (temporary db) have no special meanings here. We can apparently + only export images of DBs which are stored in the + pseudo-filesystem provided by the JS APIs. Since exporting and + importing images is an important usability feature for this + class, ":memory:" DBs are not supported (until/unless we can + find a way to export those as well). The naming semantics will + certainly evolve as this API does. + */ + const DB = function(arg){ + let buffer, fn; + if(arg instanceof Uint8Array){ + buffer = arg; + arg = undefined; + }else if(arguments.length){ /*(filename[,buffer])*/ + if('string'===typeof arg){ + const p = arg.split('/').pop().replace(':','_'); + if(p) fn = p; + if(arguments.length>1){ + buffer = arguments[1]; + } + }else if(undefined!==arg){ + toss("Invalid arguments to DB constructor.", + "Expecting (), (undefined), (name,buffer),", + "or (buffer), where buffer an sqlite3 db ", + "as a Uint8Array."); + } + } + if(!fn){ + fn = "db-"+((Math.random() * 10000000) | 0)+ + "-"+((Math.random() * 10000000) | 0)+".sqlite3"; + } + if(buffer){ + if(!(buffer instanceof Uint8Array)){ + toss("Expecting Uint8Array image of db contents."); + } + FS.createDataFile("/", fn, buffer, true, true); + } + setValue(pPtrArg, 0, "i32"); + this.checkRc(api.sqlite3_open(fn, pPtrArg)); + this._pDb = getValue(pPtrArg, "i32"); + this.filename = fn; + this._statements = {/*map of open Stmt _pointers_ to Stmt*/}; + this._udfs = {/*map of UDF names to wasm function _pointers_*/}; + }; + + /** + Internal-use enum for mapping JS types to DB-bindable types. + These do not (and need not) line up with the SQLITE_type + values. All values in this enum must be truthy and distinct + but they need not be numbers. + */ + const BindTypes = { + null: 1, + number: 2, + string: 3, + boolean: 4, + blob: 5 + }; + BindTypes['undefined'] == BindTypes.null; + + /** + This class wraps sqlite3_stmt. Calling this constructor + directly will trigger an exception. Use DB.prepare() to create + new instances. + */ + const Stmt = function(){ + if(BindTypes!==arguments[2]){ + toss("Do not call the Stmt constructor directly. Use DB.prepare()."); + } + this.db = arguments[0]; + this._pStmt = arguments[1]; + this.columnCount = api.sqlite3_column_count(this._pStmt); + this.parameterCount = api.sqlite3_bind_parameter_count(this._pStmt); + this._allocs = [/*list of alloc'd memory blocks for bind() values*/] + }; + + /** Throws if the given DB has been closed, else it is returned. */ + const affirmDbOpen = function(db){ + if(!db._pDb) toss("DB has been closed."); + return db; + }; + + /** Returns true if n is a 32-bit (signed) integer, + else false. */ + const isInt32 = function(n){ + return (n===n|0 && n<0xFFFFFFFF) ? true : undefined; + }; + + /** + Expects to be passed (arguments) from DB.exec() and + DB.execMulti(). Does the argument processing/validation, throws + on error, and returns a new object on success: + + { sql: the SQL, obt: optionsObj, cbArg: function} + + cbArg is only set if the opt.callback is set, in which case + it's a function which expects to be passed the current Stmt + and returns the callback argument of the type indicated by + the input arguments. + */ + const parseExecArgs = function(args){ + const out = {}; + switch(args.length){ + case 1: + if('string'===typeof args[0]){ + out.sql = args[0]; + out.opt = {}; + }else if(args[0] && 'object'===typeof args[0]){ + out.opt = args[0]; + out.sql = out.opt.sql; + } + break; + case 2: + out.sql = args[0]; + out.opt = args[1]; + break; + default: toss("Invalid argument count for exec()."); + }; + if('string'!==typeof out.sql) toss("Missing SQL argument."); + if(out.opt.callback || out.opt.resultRows){ + switch((undefined===out.opt.rowMode) + ? 'stmt' : out.opt.rowMode) { + case 'object': out.cbArg = (stmt)=>stmt.get({}); break; + case 'array': out.cbArg = (stmt)=>stmt.get([]); break; + case 'stmt': out.cbArg = (stmt)=>stmt; break; + default: toss("Invalid rowMode:",out.opt.rowMode); + } + } + return out; + }; + + /** If object opts has _its own_ property named p then that + property's value is returned, else dflt is returned. */ + const getOwnOption = (opts, p, dflt)=> + opts.hasOwnProperty(p) ? opts[p] : dflt; + + DB.prototype = { + /** + Expects to be given an sqlite3 API result code. If it is + falsy, this function returns this object, else it throws an + exception with an error message from sqlite3_errmsg(), + using this object's db handle. Note that if it's passed a + non-error code like SQLITE_ROW or SQLITE_DONE, it will + still throw but the error string might be "Not an error." + The various non-0 non-error codes need to be checked for in + client code where they are expected. + */ + checkRc: function(sqliteResultCode){ + if(!sqliteResultCode) return this; + toss("sqlite result code",sqliteResultCode+":", + api.sqlite3_errmsg(this._pDb) || "Unknown db error."); + }, + /** + Finalizes all open statements and closes this database + connection. This is a no-op if the db has already been + closed. If the db is open and alsoUnlink is truthy then the + this.filename entry in the pseudo-filesystem will also be + removed (and any error in that attempt is silently + ignored). + */ + close: function(alsoUnlink){ + if(this._pDb){ + let s; + const that = this; + Object.keys(this._statements).forEach(function(k,s){ + delete that._statements[k]; + if(s && s._pStmt) s.finalize(); + }); + Object.values(this._udfs).forEach(SQM.removeFunction); + delete this._udfs; + delete this._statements; + api.sqlite3_close_v2(this._pDb); + delete this._pDb; + if(this.filename){ + if(alsoUnlink){ + try{SQM.FS.unlink('/'+this.filename);} + catch(e){/*ignored*/} + } + delete this.filename; + } + } + }, + /** + Similar to this.filename but will return NULL for + special names like ":memory:". Not of much use until + we have filesystem support. Throws if the DB has + been closed. If passed an argument it then it will return + the filename of the ATTACHEd db with that name, else it assumes + a name of `main`. + */ + fileName: function(dbName){ + return api.sqlite3_db_filename(affirmDbOpen(this)._pDb, dbName||"main"); + }, + /** + Compiles the given SQL and returns a prepared Stmt. This is + the only way to create new Stmt objects. Throws on error. + */ + prepare: function(sql){ + affirmDbOpen(this); + setValue(pPtrArg,0,"i32"); + this.checkRc(api.sqlite3_prepare_v2(this._pDb, sql, -1, pPtrArg, null)); + const pStmt = getValue(pPtrArg, "i32"); + if(!pStmt) toss("Empty SQL is not permitted."); + const stmt = new Stmt(this, pStmt, BindTypes); + this._statements[pStmt] = stmt; + return stmt; + }, + /** + This function works like execMulti(), and takes most of the + same arguments, but is more efficient (performs much less + work) when the input SQL is only a single statement. If + passed a multi-statement SQL, it only processes the first + one. + + This function supports the following additional options not + supported by execMulti(): + + - .multi: if true, this function acts as a proxy for + execMulti() and behaves identically to that function. + + - .resultRows: if this is an array, each row of the result + set (if any) is appended to it in the format specified + for the `rowMode` property, with the exception that the + only legal values for `rowMode` in this case are 'array' + or 'object', neither of which is the default. It is legal + to use both `resultRows` and `callback`, but `resultRows` + is likely much simpler to use for small data sets and can + be used over a WebWorker-style message interface. + + - .columnNames: if this is an array and the query has + result columns, the array is passed to + Stmt.getColumnNames() to append the column names to it + (regardless of whether the query produces any result + rows). If the query has no result columns, this value is + unchanged. + + The following options to execMulti() are _not_ supported by + this method (they are simply ignored): + + - .saveSql + */ + exec: function(/*(sql [,optionsObj]) or (optionsObj)*/){ + affirmDbOpen(this); + const arg = parseExecArgs(arguments); + if(!arg.sql) return this; + else if(arg.opt.multi){ + return this.execMulti(arg, undefined, BindTypes); + } + const opt = arg.opt; + let stmt, rowTarget; + try { + if(Array.isArray(opt.resultRows)){ + if(opt.rowMode!=='array' && opt.rowMode!=='object'){ + toss("Invalid rowMode for resultRows array: must", + "be one of 'array' or 'object'."); + } + rowTarget = opt.resultRows; + } + stmt = this.prepare(arg.sql); + if(stmt.columnCount && Array.isArray(opt.columnNames)){ + stmt.getColumnNames(opt.columnNames); + } + if(opt.bind) stmt.bind(opt.bind); + if(opt.callback || rowTarget){ + while(stmt.step()){ + const row = arg.cbArg(stmt); + if(rowTarget) rowTarget.push(row); + if(opt.callback){ + stmt._isLocked = true; + opt.callback(row, stmt); + stmt._isLocked = false; + } + } + }else{ + stmt.step(); + } + }finally{ + if(stmt){ + delete stmt._isLocked; + stmt.finalize(); + } + } + return this; + + }/*exec()*/, + /** + Executes one or more SQL statements in the form of a single + string. Its arguments must be either (sql,optionsObject) or + (optionsObject). In the latter case, optionsObject.sql + must contain the SQL to execute. Returns this + object. Throws on error. + + If no SQL is provided, or a non-string is provided, an + exception is triggered. Empty SQL, on the other hand, is + simply a no-op. + + The optional options object may contain any of the following + properties: + + - .sql = the SQL to run (unless it's provided as the first + argument). + + - .bind = a single value valid as an argument for + Stmt.bind(). This is ONLY applied to the FIRST non-empty + statement in the SQL which has any bindable + parameters. (Empty statements are skipped entirely.) + + - .callback = a function which gets called for each row of + the FIRST statement in the SQL (if it has any result + rows). The second argument passed to the callback is + always the current Stmt object (so that the caller + may collect column names, or similar). The first + argument passed to the callback defaults to the current + Stmt object but may be changed with ... + + - .rowMode = a string describing what type of argument + should be passed as the first argument to the callback. A + value of 'object' causes the results of `stmt.get({})` to + be passed to the object. A value of 'array' causes the + results of `stmt.get([])` to be passed to the callback. + A value of 'stmt' is equivalent to the default, passing + the current Stmt to the callback (noting that it's always + passed as the 2nd argument). Any other value triggers an + exception. + + - saveSql = an optional array. If set, the SQL of each + executed statement is appended to this array before the + statement is executed (but after it is prepared - we + don't have the string until after that). Empty SQL + statements are elided. + + See also the exec() method, which is a close cousin of this + one. + + ACHTUNG #1: The callback MUST NOT modify the Stmt + object. Calling any of the Stmt.get() variants, + Stmt.getColumnName(), or similar, is legal, but calling + step() or finalize() is not. Routines which are illegal + in this context will trigger an exception. + + ACHTUNG #2: The semantics of the `bind` and `callback` + options may well change or those options may be removed + altogether for this function (but retained for exec()). + Generally speaking, neither bind parameters nor a callback + are generically useful when executing multi-statement SQL. + */ + execMulti: function(/*(sql [,obj]) || (obj)*/){ + affirmDbOpen(this); + const arg = (BindTypes===arguments[2] + /* ^^^ Being passed on from exec() */ + ? arguments[0] : parseExecArgs(arguments)); + if(!arg.sql) return this; + const opt = arg.opt; + const stack = stackSave(); + let stmt; + let bind = opt.bind; + let rowMode = ( + (opt.callback && opt.rowMode) + ? opt.rowMode : false); + try{ + let pSql = SQM.allocateUTF8OnStack(arg.sql) + const pzTail = stackAlloc(4); + while(getValue(pSql, "i8")){ + setValue(pPtrArg, 0, "i32"); + setValue(pzTail, 0, "i32"); + this.checkRc(api.sqlite3_prepare_v2_sqlptr( + this._pDb, pSql, -1, pPtrArg, pzTail + )); + const pStmt = getValue(pPtrArg, "i32"); + pSql = getValue(pzTail, "i32"); + if(!pStmt) continue; + if(opt.saveSql){ + opt.saveSql.push(api.sqlite3_sql(pStmt).trim()); + } + stmt = new Stmt(this, pStmt, BindTypes); + if(bind && stmt.parameterCount){ + stmt.bind(bind); + bind = null; + } + if(opt.callback && null!==rowMode){ + while(stmt.step()){ + stmt._isLocked = true; + callback(arg.cbArg(stmt), stmt); + stmt._isLocked = false; + } + rowMode = null; + }else{ + // Do we need to while(stmt.step()){} here? + stmt.step(); + } + stmt.finalize(); + stmt = null; + } + }finally{ + if(stmt){ + delete stmt._isLocked; + stmt.finalize(); + } + stackRestore(stack); + } + return this; + }/*execMulti()*/, + /** + Creates a new scalar UDF (User-Defined Function) which is + accessible via SQL code. This function may be called in any + of the following forms: + + - (name, function) + - (name, function, optionsObject) + - (name, optionsObject) + - (optionsObject) + + In the final two cases, the function must be defined as the + 'callback' property of the options object. In the final + case, the function's name must be the 'name' property. + + This can only be used to create scalar functions, not + aggregate or window functions. UDFs cannot be removed from + a DB handle after they're added. + + On success, returns this object. Throws on error. + + When called from SQL, arguments to the UDF, and its result, + will be converted between JS and SQL with as much fidelity + as is feasible, triggering an exception if a type + conversion cannot be determined. Some freedom is afforded + to numeric conversions due to friction between the JS and C + worlds: integers which are larger than 32 bits will be + treated as doubles, as JS does not support 64-bit integers + and it is (as of this writing) illegal to use WASM + functions which take or return 64-bit integers from JS. + + The optional options object may contain flags to modify how + the function is defined: + + - .arity: the number of arguments which SQL calls to this + function expect or require. The default value is the + callback's length property (i.e. the number of declared + parameters it has). A value of -1 means that the function + is variadic and may accept any number of arguments, up to + sqlite3's compile-time limits. sqlite3 will enforce the + argument count if is zero or greater. + + The following properties correspond to flags documented at: + + https://sqlite.org/c3ref/create_function.html + + - .deterministic = SQLITE_DETERMINISTIC + - .directOnly = SQLITE_DIRECTONLY + - .innocuous = SQLITE_INNOCUOUS + + + Maintenance reminder: the ability to add new + WASM-accessible functions to the runtime requires that the + WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH` + flag. + */ + createFunction: function f(name, callback,opt){ + switch(arguments.length){ + case 1: /* (optionsObject) */ + opt = name; + name = opt.name; + callback = opt.callback; + break; + case 2: /* (name, callback|optionsObject) */ + if(!(callback instanceof Function)){ + opt = callback; + callback = opt.callback; + } + break; + default: break; + } + if(!opt) opt = {}; + if(!(callback instanceof Function)){ + toss("Invalid arguments: expecting a callback function."); + }else if('string' !== typeof name){ + toss("Invalid arguments: missing function name."); + } + if(!f._extractArgs){ + /* Static init */ + f._extractArgs = function(argc, pArgv){ + let i, pVal, valType, arg; + const tgt = []; + for(i = 0; i < argc; ++i){ + pVal = getValue(pArgv + (4 * i), "i32"); + valType = api.sqlite3_value_type(pVal); + switch(valType){ + case api.SQLITE_INTEGER: + case api.SQLITE_FLOAT: + arg = api.sqlite3_value_double(pVal); + break; + case SQLITE_TEXT: + arg = api.sqlite3_value_text(pVal); + break; + case SQLITE_BLOB:{ + const n = api.sqlite3_value_bytes(ptr); + const pBlob = api.sqlite3_value_blob(ptr); + arg = new Uint8Array(n); + let i; + for(i = 0; i < n; ++i) arg[i] = HEAP8[pBlob+i]; + break; + } + default: + arg = null; break; + } + tgt.push(arg); + } + return tgt; + }/*_extractArgs()*/; + f._setResult = function(pCx, val){ + switch(typeof val) { + case 'boolean': + api.sqlite3_result_int(pCx, val ? 1 : 0); + break; + case 'number': { + (isInt32(val) + ? api.sqlite3_result_int + : api.sqlite3_result_double)(pCx, val); + break; + } + case 'string': + api.sqlite3_result_text(pCx, val, -1, + -1/*==SQLITE_TRANSIENT*/); + break; + case 'object': + if(null===val) { + api.sqlite3_result_null(pCx); + break; + }else if(undefined!==val.length){ + const pBlob = + SQM.allocate(val, SQM.ALLOC_NORMAL); + api.sqlite3_result_blob(pCx, pBlob, val.length, -1/*==SQLITE_TRANSIENT*/); + SQM._free(blobptr); + break; + } + // else fall through + default: + toss("Don't not how to handle this UDF result value:",val); + }; + }/*_setResult()*/; + }/*static init*/ + const wrapper = function(pCx, argc, pArgv){ + try{ + f._setResult(pCx, callback.apply(null, f._extractArgs(argc, pArgv))); + }catch(e){ + api.sqlite3_result_error(pCx, e.message, -1); + } + }; + const pUdf = SQM.addFunction(wrapper, "viii"); + let fFlags = 0; + if(getOwnOption(opt, 'deterministic')) fFlags |= api.SQLITE_DETERMINISTIC; + if(getOwnOption(opt, 'directOnly')) fFlags |= api.SQLITE_DIRECTONLY; + if(getOwnOption(opt, 'innocuous')) fFlags |= api.SQLITE_INNOCUOUS; + name = name.toLowerCase(); + try { + this.checkRc(api.sqlite3_create_function_v2( + this._pDb, name, + (opt.hasOwnProperty('arity') ? +opt.arity : callback.length), + api.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf, + null/*xStep*/, null/*xFinal*/, null/*xDestroy*/)); + }catch(e){ + SQM.removeFunction(pUdf); + throw e; + } + if(this._udfs.hasOwnProperty(name)){ + SQM.removeFunction(this._udfs[name]); + } + this._udfs[name] = pUdf; + return this; + }/*createFunction()*/, + /** + Prepares the given SQL, step()s it one time, and returns + the value of the first result column. If it has no results, + undefined is returned. If passed a second argument, it is + treated like an argument to Stmt.bind(), so may be any type + supported by that function. Throws on error (e.g. malformed + SQL). + */ + selectValue: function(sql,bind){ + let stmt, rc; + try { + stmt = this.prepare(sql).bind(bind); + if(stmt.step()) rc = stmt.get(0); + }finally{ + if(stmt) stmt.finalize(); + } + return rc; + }, + + /** + Exports a copy of this db's file as a Uint8Array and + returns it. It is technically not legal to call this while + any prepared statement are currently active because, + depending on the platform, it might not be legal to read + the db while a statement is locking it. Throws if this db + is not open or has any opened statements. + + The resulting buffer can be passed to this class's + constructor to restore the DB. + + Maintenance reminder: the corresponding sql.js impl of this + feature closes the current db, finalizing any active + statements and (seemingly unnecessarily) destroys any UDFs, + copies the file, and then re-opens it (without restoring + the UDFs). Those gymnastics are not necessary on the tested + platform but might be necessary on others. Because of that + eventuality, this interface currently enforces that no + statements are active when this is run. It will throw if + any are. + */ + exportBinaryImage: function(){ + affirmDbOpen(this); + if(Object.keys(this._statements).length){ + toss("Cannot export with prepared statements active!", + "finalize() all statements and try again."); + } + return FS.readFile(this.filename, {encoding:"binary"}); + } + }/*DB.prototype*/; + + + /** Throws if the given Stmt has been finalized, else stmt is + returned. */ + const affirmStmtOpen = function(stmt){ + if(!stmt._pStmt) toss("Stmt has been closed."); + return stmt; + }; + + /** Returns an opaque truthy value from the BindTypes + enum if v's type is a valid bindable type, else + returns a falsy value. As a special case, a value of + undefined is treated as a bind type of null. */ + const isSupportedBindType = function(v){ + let t = BindTypes[(null===v||undefined===v) ? 'null' : typeof v]; + switch(t){ + case BindTypes.boolean: + case BindTypes.null: + case BindTypes.number: + case BindTypes.string: + return t; + default: + if(v instanceof Uint8Array) return BindTypes.blob; + return undefined; + } + }; + + /** + If isSupportedBindType(v) returns a truthy value, this + function returns that value, else it throws. + */ + const affirmSupportedBindType = function(v){ + return isSupportedBindType(v) || toss("Unsupport bind() argument type."); + }; + + /** + If key is a number and within range of stmt's bound parameter + count, key is returned. + + If key is not a number then it is checked against named + parameters. If a match is found, its index is returned. + + Else it throws. + */ + const affirmParamIndex = function(stmt,key){ + const n = ('number'===typeof key) + ? key : api.sqlite3_bind_parameter_index(stmt._pStmt, key); + if(0===n || (n===key && (n!==(n|0)/*floating point*/))){ + toss("Invalid bind() parameter name: "+key); + } + else if(n<1 || n>stmt.parameterCount) toss("Bind index",key,"is out of range."); + return n; + }; + + /** Throws if ndx is not an integer or if it is out of range + for stmt.columnCount, else returns stmt. + + Reminder: this will also fail after the statement is finalized + but the resulting error will be about an out-of-bounds column + index. + */ + const affirmColIndex = function(stmt,ndx){ + if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){ + toss("Column index",ndx,"is out of range."); + } + return stmt; + }; + + /** + If stmt._isLocked is truthy, this throws an exception + complaining that the 2nd argument (an operation name, + e.g. "bind()") is not legal while the statement is "locked". + Locking happens before an exec()-like callback is passed a + statement, to ensure that the callback does not mutate or + finalize the statement. If it does not throw, it returns stmt. + */ + const affirmUnlocked = function(stmt,currentOpName){ + if(stmt._isLocked){ + toss("Operation is illegal when statement is locked:",currentOpName); + } + return stmt; + }; + + /** + Binds a single bound parameter value on the given stmt at the + given index (numeric or named) using the given bindType (see + the BindTypes enum) and value. Throws on error. Returns stmt on + success. + */ + const bindOne = function f(stmt,ndx,bindType,val){ + affirmUnlocked(stmt, 'bind()'); + if(!f._){ + f._ = { + string: function(stmt, ndx, val, asBlob){ + const bytes = intArrayFromString(val,true); + const pStr = SQM.allocate(bytes, ALLOC_NORMAL); + stmt._allocs.push(pStr); + const func = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text; + return func(stmt._pStmt, ndx, pStr, bytes.length, 0); + } + }; + } + affirmSupportedBindType(val); + ndx = affirmParamIndex(stmt,ndx); + let rc = 0; + switch((null===val || undefined===val) ? BindTypes.null : bindType){ + case BindTypes.null: + rc = api.sqlite3_bind_null(stmt._pStmt, ndx); + break; + case BindTypes.string:{ + rc = f._.string(stmt, ndx, val, false); + break; + } + case BindTypes.number: { + const m = (isInt32(val) + ? api.sqlite3_bind_int + /*It's illegal to bind a 64-bit int + from here*/ + : api.sqlite3_bind_double); + rc = m(stmt._pStmt, ndx, val); + break; + } + case BindTypes.boolean: + rc = api.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0); + break; + case BindTypes.blob: { + if('string'===typeof val){ + rc = f._.string(stmt, ndx, val, true); + }else{ + const len = val.length; + if(undefined===len){ + toss("Binding a value as a blob requires", + "that it have a length member."); + } + const pBlob = SQM.allocate(val, ALLOC_NORMAL); + stmt._allocs.push(pBlob); + rc = api.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0); + } + } + default: toss("Unsupported bind() argument type."); + } + if(rc) stmt.db.checkRc(rc); + return stmt; + }; + + /** Frees any memory explicitly allocated for the given + Stmt object. Returns stmt. */ + const freeBindMemory = function(stmt){ + let m; + while(undefined !== (m = stmt._allocs.pop())){ + SQM._free(m); + } + return stmt; + }; + + Stmt.prototype = { + /** + "Finalizes" this statement. This is a no-op if the + statement has already been finalizes. Returns + undefined. Most methods in this class will throw if called + after this is. + */ + finalize: function(){ + if(this._pStmt){ + affirmUnlocked(this,'finalize()'); + freeBindMemory(this); + delete this.db._statements[this._pStmt]; + api.sqlite3_finalize(this._pStmt); + delete this.columnCount; + delete this.parameterCount; + delete this._pStmt; + delete this.db; + delete this._isLocked; + } + }, + /** Clears all bound values. Returns this object. + Throws if this statement has been finalized. */ + clearBindings: function(){ + freeBindMemory( + affirmUnlocked(affirmStmtOpen(this), 'clearBindings()') + ); + api.sqlite3_clear_bindings(this._pStmt); + this._mayGet = false; + return this; + }, + /** + Resets this statement so that it may be step()ed again + from the beginning. Returns this object. Throws if this + statement has been finalized. + + If passed a truthy argument then this.clearBindings() is + also called, otherwise any existing bindings, along with + any memory allocated for them, are retained. + */ + reset: function(alsoClearBinds){ + affirmUnlocked(this,'reset()'); + if(alsoClearBinds) this.clearBindings(); + api.sqlite3_reset(affirmStmtOpen(this)._pStmt); + this._mayGet = false; + return this; + }, + /** + Binds one or more values to its bindable parameters. It + accepts 1 or 2 arguments: + + If passed a single argument, it must be either an array, an + object, or a value of a bindable type (see below). + + If passed 2 arguments, the first one is the 1-based bind + index or bindable parameter name and the second one must be + a value of a bindable type. + + Bindable value types: + + - null is bound as NULL. + + - undefined as a standalone value is a no-op intended to + simplify certain client-side use cases: passing undefined + as a value to this function will not actually bind + anything and this function will skip confirmation that + binding is even legal. (Those semantics simplify certain + client-side uses.) Conversely, a value of undefined as an + array or object property when binding an array/object + (see below) is treated the same as null. + + - Numbers are bound as either doubles or integers: doubles + if they are larger than 32 bits, else double or int32, + depending on whether they have a fractional part. (It is, + as of this writing, illegal to call (from JS) a WASM + function which either takes or returns an int64.) + Booleans are bound as integer 0 or 1. It is not expected + the distinction of binding doubles which have no + fractional parts is integers is significant for the + majority of clients due to sqlite3's data typing + model. This API does not currently support the BigInt + type. + + - Strings are bound as strings (use bindAsBlob() to force + blob binding). + + - Uint8Array instances are bound as blobs. + + If passed an array, each element of the array is bound at + the parameter index equal to the array index plus 1 + (because arrays are 0-based but binding is 1-based). + + If passed an object, each object key is treated as a + bindable parameter name. The object keys _must_ match any + bindable parameter names, including any `$`, `@`, or `:` + prefix. Because `$` is a legal identifier chararacter in + JavaScript, that is the suggested prefix for bindable + parameters. + + It returns this object on success and throws on + error. Errors include: + + - Any bind index is out of range, a named bind parameter + does not match, or this statement has no bindable + parameters. + + - Any value to bind is of an unsupported type. + + - Passed no arguments or more than two. + + - The statement has been finalized. + */ + bind: function(/*[ndx,] arg*/){ + affirmStmtOpen(this); + let ndx, arg; + switch(arguments.length){ + case 1: ndx = 1; arg = arguments[0]; break; + case 2: ndx = arguments[0]; arg = arguments[1]; break; + default: toss("Invalid bind() arguments."); + } + if(undefined===arg){ + /* It might seem intuitive to bind undefined as NULL + but this approach simplifies certain client-side + uses when passing on arguments between 2+ levels of + functions. */ + return this; + }else if(!this.parameterCount){ + toss("This statement has no bindable parameters."); + } + this._mayGet = false; + if(null===arg){ + /* bind NULL */ + return bindOne(this, ndx, BindTypes.null, arg); + } + else if(Array.isArray(arg)){ + /* bind each entry by index */ + if(1!==arguments.length){ + toss("When binding an array, an index argument is not permitted."); + } + arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); + return this; + } + else if('object'===typeof arg/*null was checked above*/){ + /* bind by name */ + if(1!==arguments.length){ + toss("When binding an object, an index argument is not permitted."); + } + Object.keys(arg) + .forEach(k=>bindOne(this, k, + affirmSupportedBindType(arg[k]), + arg[k])); + return this; + }else{ + return bindOne(this, ndx, + affirmSupportedBindType(arg), arg); + } + toss("Should not reach this point."); + }, + /** + Special case of bind() which binds the given value + using the BLOB binding mechanism instead of the default + selected one for the value. The ndx may be a numbered + or named bind index. The value must be of type string, + Uint8Array, or null/undefined (both treated as null). + + If passed a single argument, a bind index of 1 is assumed. + */ + bindAsBlob: function(ndx,arg){ + affirmStmtOpen(this); + if(1===arguments.length){ + ndx = 1; + arg = arguments[0]; + } + const t = affirmSupportedBindType(arg); + if(BindTypes.string !== t && BindTypes.blob !== t + && BindTypes.null !== t){ + toss("Invalid value type for bindAsBlob()"); + } + this._mayGet = false; + return bindOne(this, ndx, BindTypes.blob, arg); + }, + /** + Steps the statement one time. If the result indicates that + a row of data is available, true is returned. If no row of + data is available, false is returned. Throws on error. + */ + step: function(){ + affirmUnlocked(this, 'step()'); + const rc = api.sqlite3_step(affirmStmtOpen(this)._pStmt); + switch(rc){ + case api.SQLITE_DONE: return this._mayGet = false; + case api.SQLITE_ROW: return this._mayGet = true; + default: + this._mayGet = false; + console.warn("sqlite3_step() rc=",rc,"SQL =", + api.sqlite3_sql(this._pStmt)); + this.db.checkRc(rc); + }; + }, + /** + Fetches the value from the given 0-based column index of + the current data row, throwing if index is out of range. + + Requires that step() has just returned a truthy value, else + an exception is thrown. + + By default it will determine the data type of the result + automatically. If passed a second arugment, it must be one + of the enumeration values for sqlite3 types, which are + defined as members of the sqlite3 module: SQLITE_INTEGER, + SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value, + except for undefined, will trigger an exception. Passing + undefined is the same as not passing a value. It is legal + to, e.g., fetch an integer value as a string, in which case + sqlite3 will convert the value to a string. + + If ndx is an array, this function behaves a differently: it + assigns the indexes of the array, from 0 to the number of + result columns, to the values of the corresponding column, + and returns that array. + + If ndx is a plain object, this function behaves even + differentlier: it assigns the properties of the object to + the values of their corresponding result columns. + + Blobs are returned as Uint8Array instances. + + Potential TODO: add type ID SQLITE_JSON, which fetches the + result as a string and passes it (if it's not null) to + JSON.parse(), returning the result of that. Until then, + getJSON() can be used for that. + */ + get: function(ndx,asType){ + if(!affirmStmtOpen(this)._mayGet){ + toss("Stmt.step() has not (recently) returned true."); + } + if(Array.isArray(ndx)){ + let i = 0; + while(i32bits */ + } + case api.SQLITE_FLOAT: + return api.sqlite3_column_double(this._pStmt, ndx); + case api.SQLITE_TEXT: + return api.sqlite3_column_text(this._pStmt, ndx); + case api.SQLITE_BLOB: { + const n = api.sqlite3_column_bytes(this._pStmt, ndx); + const ptr = api.sqlite3_column_blob(this._pStmt, ndx); + const rc = new Uint8Array(n); + for(let i = 0; i < n; ++i) rc[i] = HEAP8[ptr + i]; + if(n && this.db._blobXfer instanceof Array){ + /* This is an optimization soley for the + Worker-based API. These values will be + transfered to the main thread directly + instead of being copied. */ + this.db._blobXfer.push(rc.buffer); + } + return rc; + } + default: toss("Don't know how to translate", + "type of result column #"+ndx+"."); + } + abort("Not reached."); + }, + /** Equivalent to get(ndx) but coerces the result to an + integer. */ + getInt: function(ndx){return this.get(ndx,api.SQLITE_INTEGER)}, + /** Equivalent to get(ndx) but coerces the result to a + float. */ + getFloat: function(ndx){return this.get(ndx,api.SQLITE_FLOAT)}, + /** Equivalent to get(ndx) but coerces the result to a + string. */ + getString: function(ndx){return this.get(ndx,api.SQLITE_TEXT)}, + /** Equivalent to get(ndx) but coerces the result to a + Uint8Array. */ + getBlob: function(ndx){return this.get(ndx,api.SQLITE_BLOB)}, + /** + A convenience wrapper around get() which fetches the value + as a string and then, if it is not null, passes it to + JSON.parse(), returning that result. Throws if parsing + fails. If the result is null, null is returned. An empty + string, on the other hand, will trigger an exception. + */ + getJSON: function(ndx){ + const s = this.get(ndx, api.SQLITE_STRING); + return null===s ? s : JSON.parse(s); + }, + /** + Returns the result column name of the given index, or + throws if index is out of bounds or this statement has been + finalized. This can be used without having run step() + first. + */ + getColumnName: function(ndx){ + return api.sqlite3_column_name( + affirmColIndex(affirmStmtOpen(this),ndx)._pStmt, ndx + ); + }, + /** + If this statement potentially has result columns, this + function returns an array of all such names. If passed an + array, it is used as the target and all names are appended + to it. Returns the target array. Throws if this statement + cannot have result columns. This object's columnCount member + holds the number of columns. + */ + getColumnNames: function(tgt){ + affirmColIndex(affirmStmtOpen(this),0); + if(!tgt) tgt = []; + for(let i = 0; i < this.columnCount; ++i){ + tgt.push(api.sqlite3_column_name(this._pStmt, i)); + } + return tgt; + }, + /** + If this statement has named bindable parameters and the + given name matches one, its 1-based bind index is + returned. If no match is found, 0 is returned. If it has no + bindable parameters, the undefined value is returned. + */ + getParamIndex: function(name){ + return (affirmStmtOpen(this).parameterCount + ? api.sqlite3_bind_parameter_index(this._pStmt, name) + : undefined); + } + }/*Stmt.prototype*/; + + /** OO binding's namespace. */ + const SQLite3 = { + version: { + lib: api.sqlite3_libversion(), + ooApi: "0.0.1" + }, + DB, + Stmt, + /** + Reports info about compile-time options. It has several + distinct uses: + + If optName is an array then it is expected to be a list of + compilation options and this function returns an object + which maps each such option to true or false, indicating + whether or not the given option was included in this + build. That object is returned. + + If optName is an object, its keys are expected to be + compilation options and this function sets each entry to + true or false. That object is returned. + + If passed no arguments then it returns an object mapping + all known compilation options to their compile-time values, + or boolean true if they are defined with no value. + + In all other cases it returns true if the given option was + active when when compiling the sqlite3 module, else false. + + Compile-time option names may optionally include their + "SQLITE_" prefix. When it returns an object of all options, + the prefix is elided. + */ + compileOptionUsed: function f(optName){ + if(!arguments.length){ + if(!f._opt){ + f._rx = /^([^=]+)=(.+)/; + f._rxInt = /^-?\d+$/; + f._opt = function(opt, rv){ + const m = f._rx.exec(opt); + rv[0] = (m ? m[1] : opt); + rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; + }; + } + const rc = {}, ov = [0,0]; + let i = 0, k; + while((k = api.sqlite3_compileoption_get(i++))){ + f._opt(k,ov); + rc[ov[0]] = ov[1]; + } + return rc; + } + else if(Array.isArray(optName)){ + const rc = {}; + optName.forEach((v)=>{ + rc[v] = api.sqlite3_compileoption_used(v); + }); + return rc; + } + else if('object' === typeof optName){ + Object.keys(optName).forEach((k)=> { + optName[k] = api.sqlite3_compileoption_used(k); + }); + return optName; + } + return ( + 'string'===typeof optName + ) ? !!api.sqlite3_compileoption_used(optName) : false; + } + }/*SQLite3 object*/; + + namespace.sqlite3 = { + api: api, + SQLite3 + }; + + if(self === self.window){ + /* This is running in the main window thread, so we're done. */ + setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0); + return; + } + /****************************************************************** + End of main window thread. What follows is only intended for use + in Worker threads. + ******************************************************************/ + + /* + UNDER CONSTRUCTION + + We need an API which can proxy the DB API via a Worker message + interface. The primary quirky factor in such an API is that we + cannot pass callback functions between the window thread and a + worker thread, so we have to receive all db results via + asynchronous message-passing. + + Certain important considerations here include: + + - Support only one db connection or multiple? The former is far + easier, but there's always going to be a user out there who + wants to juggle six database handles at once. Do we add that + complexity or tell such users to write their own code using + the provided lower-level APIs? + + - Fetching multiple results: do we pass them on as a series of + messages, with start/end messages on either end, or do we + collect all results and bundle them back in a single message? + The former is, generically speaking, more memory-efficient but + the latter far easier to implement in this environment. The + latter is untennable for large data sets. Despite a web page + hypothetically being a relatively limited environment, there + will always be those users who feel that they should/need to + be able to work with multi-hundred-meg (or larger) blobs, and + passing around arrays of those may quickly exhaust the JS + engine's memory. + + TODOs include, but are not limited to: + + - The ability to manage multiple DB handles. This can + potentially be done via a simple mapping of DB.filename or + DB._pDb (`sqlite3*` handle) to DB objects. The open() + interface would need to provide an ID (probably DB._pDb) back + to the user which can optionally be passed as an argument to + the other APIs (they'd default to the first-opened DB, for + ease of use). Client-side usability of this feature would + benefit from making another wrapper class (or a singleton) + available to the main thread, with that object proxying all(?) + communication with the worker. + + - Revisit how virtual files are managed. We currently delete DBs + from the virtual filesystem when we close them, for the sake + of saving memory (the VFS lives in RAM). Supporting multiple + DBs may require that we give up that habit. Similarly, fully + supporting ATTACH, where a user can upload multiple DBs and + ATTACH them, also requires the that we manage the VFS entries + better. As of this writing, ATTACH will fail fatally in the + fiddle app (but not the lower-level APIs) because it runs in + safe mode, where ATTACH is disabled. + */ + + /** + Helper for managing Worker-level state. + */ + const wState = { + db: undefined, + open: function(arg){ + if(!arg && this.db) return this.db; + else if(this.db) this.db.close(); + return this.db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg)); + }, + close: function(alsoUnlink){ + if(this.db){ + this.db.close(alsoUnlink); + this.db = undefined; + } + }, + affirmOpen: function(){ + return this.db || toss("DB is not opened."); + }, + post: function(type,data,xferList){ + if(xferList){ + self.postMessage({type, data},xferList); + xferList.length = 0; + }else{ + self.postMessage({type, data}); + } + } + }; + + /** + A level of "organizational abstraction" for the Worker + API. Each method in this object must map directly to a Worker + message type key. The onmessage() dispatcher attempts to + dispatch all inbound messages to a method of this object, + passing it the event.data part of the inbound event object. All + methods must return a plain Object containing any response + state, which the dispatcher may amend. All methods must throw + on error. + */ + const wMsgHandler = { + xfer: [/*Temp holder for "transferable" postMessage() state.*/], + /** + Proxy for DB.exec() which expects a single argument of type + string (SQL to execute) or an options object in the form + expected by exec(). The notable differences from exec() + include: + + - The default value for options.rowMode is 'array' because + the normal default cannot cross the window/Worker boundary. + + - A function-type options.callback property cannot cross + the window/Worker boundary, so is not useful here. If + options.callback is a string then it is assumed to be a + message type key, in which case a callback function will be + applied which posts each row result via: + + postMessage({type: thatKeyType, data: theRow}) + + And, at the end of the result set (whether or not any + result rows were produced), it will post an identical + message with data:null to alert the caller than the result + set is completed. + + The callback proxy must not recurse into this interface, or + results are undefined. (It hypothetically cannot recurse + because an exec() call will be tying up the Worker thread, + causing any recursion attempt to wait until the first + exec() is completed.) + + The response is the input options object (or a synthesized + one if passed only a string), noting that + options.resultRows and options.columnNames may be populated + by the call to exec(). + + This opens/creates the Worker's db if needed. + */ + exec: function(ev){ + const opt = ( + 'string'===typeof ev.data + ) ? {sql: ev.data} : (ev.data || {}); + if(!opt.rowMode){ + /* Since the default rowMode of 'stmt' is not useful + for the Worker interface, we'll default to + something else. */ + opt.rowMode = 'array'; + }else if('stmt'===opt.rowMode){ + toss("Invalid rowMode for exec(): stmt mode", + "does not work in the Worker API."); + } + const db = wState.open(); + if(opt.callback || opt.resultRows instanceof Array){ + // Part of a copy-avoidance optimization for blobs + db._blobXfer = this.xfer; + } + const callbackMsgType = opt.callback; + if('string' === typeof callbackMsgType){ + const that = this; + opt.callback = + (row)=>wState.post(callbackMsgType,row,this.xfer); + } + try { + db.exec(opt); + if(opt.callback instanceof Function){ + opt.callback = callbackMsgType; + wState.post(callbackMsgType, null); + } + }finally{ + delete db._blobXfer; + if('string'===typeof callbackMsgType){ + opt.callback = callbackMsgType; + } + } + return opt; + }/*exec()*/, + /** + Proxy for DB.exportBinaryImage(). Throws if the db has not + been opened. Response is an object: + + { + buffer: Uint8Array (db file contents), + filename: the current db filename, + mimetype: string + } + */ + export: function(ev){ + const db = wState.affirmOpen(); + const response = { + buffer: db.exportBinaryImage(), + filename: db.filename, + mimetype: 'application/x-sqlite3' + }; + this.xfer.push(response.buffer.buffer); + return response; + }/*export()*/, + /** + Proxy for the DB constructor. Expects to be passed a single + object or a falsy value to use defaults. The object may + have a filename property to name the db file (see the DB + constructor for peculiarities and transformations) and/or a + buffer property (a Uint8Array holding a complete database + file's contents). The response is an object: + + { + filename: db filename (possibly differing from the input) + } + + If the Worker's db is currently opened, this call closes it + before proceeding. + */ + open: function(ev){ + wState.close(/*true???*/); + const args = [], data = (ev.data || {}); + if(data.filename) args.push(data.filename); + if(data.buffer){ + args.push(data.buffer); + this.xfer.push(data.buffer.buffer); + } + const db = wState.open(args); + return {filename: db.filename}; + }, + /** + Proxy for DB.close(). If ev.data may either be a boolean or + an object with an `unlink` property. If that value is + truthy then the db file (if the db is currently open) will + be unlinked from the virtual filesystem, else it will be + kept intact. The response object is: + + {filename: db filename _if_ the db is is opened when this + is called, else the undefined value + } + */ + close: function(ev){ + const response = { + filename: wState.db && wState.db.filename + }; + if(wState.db){ + wState.close(!!(ev.data && 'object'===typeof ev.data) + ? ev.data.unlink : ev.data); + } + return response; + } + }/*wMsgHandler*/; + + /** + UNDER CONSTRUCTION! + + A subset of the DB API is accessible via Worker messages in the form: + + { type: apiCommand, + data: apiArguments } + + As a rule, these commands respond with a postMessage() of their + own in the same form, but will, if needed, transform the `data` + member to an object and may add state to it. The responses + always have an object-format `data` part. If the inbound `data` + is an object which has a `messageId` property, that property is + always mirrored in the result object, for use in client-side + dispatching of these asynchronous results. Exceptions thrown + during processing result in an `error`-type event with a + payload in the form: + + { + message: error string, + errorClass: class name of the error type, + input: ev.data, + [messageId: if set in the inbound message] + } + + The individual APIs are documented in the wMsgHandler object. + */ + self.onmessage = function(ev){ + ev = ev.data; + let response, evType = ev.type; + try { + if(wMsgHandler.hasOwnProperty(evType) && + wMsgHandler[evType] instanceof Function){ + response = wMsgHandler[evType](ev); + }else{ + toss("Unknown db worker message type:",ev.type); + } + }catch(err){ + evType = 'error'; + response = { + message: err.message, + errorClass: err.name, + input: ev + }; + } + if(!response.messageId && ev.data + && 'object'===typeof ev.data && ev.data.messageId){ + response.messageId = ev.data.messageId; + } + wState.post(evType, response, wMsgHandler.xfer); + }; + + setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0); +}); diff --git a/ext/fiddle/sqlite3-worker.js b/ext/fiddle/sqlite3-worker.js new file mode 100644 index 000000000..fe7423f4c --- /dev/null +++ b/ext/fiddle/sqlite3-worker.js @@ -0,0 +1,44 @@ +/* + 2022-05-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. + + *********************************************************************** + + This is a JS Worker file for the main sqlite3 api. It loads + sqlite3.js, initializes the module, and postMessage()'s a message + after the module is initialized: + + {type: 'sqlite3-api', data: 'ready'} + + This seemingly superfluous level of indirection is necessary when + loading sqlite3.js via a Worker. Loading sqlite3.js from the main + window thread elides the Worker-specific API. Instantiating a worker + with new Worker("sqlite.js") will not (cannot) call + initSqlite3Module() to initialize the module due to a + timing/order-of-operations conflict (and that symbol is not exported + in a way that a Worker loading it that way can see it). Thus JS + code wanting to load the sqlite3 Worker-specific API needs to pass + _this_ file (or equivalent) to the Worker constructor and then + listen for an event in the form shown above in order to know when + the module has completed initialization. sqlite3.js will fire a + similar event, with data:'loaded' as the final step in its loading + process. Whether or not we _really_ need both 'loaded' and 'ready' + events is unclear, but they are currently separate events primarily + for the sake of clarity in the timing of when it's okay to use the + loaded module. At the time the 'loaded' event is fired, it's + possible (but unknown and unknowable) that the emscripten-generated + module-setup infrastructure still has work to do. Thus it is + hypothesized that client code is better off waiting for the 'ready' + even before using the API. +*/ +"use strict"; +importScripts('sqlite3.js'); +initSqlite3Module().then(function(){ + setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'ready'}), 0); +}); diff --git a/ext/fiddle/testing.css b/ext/fiddle/testing.css new file mode 100644 index 000000000..f87dbd2cf --- /dev/null +++ b/ext/fiddle/testing.css @@ -0,0 +1,31 @@ +textarea { + font-family: monospace; +} +header { + font-size: 130%; + font-weight: bold; +} +.hidden, .initially-hidden { + position: absolute !important; + opacity: 0 !important; + pointer-events: none !important; + display: none !important; +} +fieldset.options { + font-size: 75%; +} +fieldset > legend { + padding: 0 0.5em; +} +span.labeled-input { + padding: 0.25em; + margin: 0.25em 0.5em; + border-radius: 0.25em; + white-space: nowrap; + background: #0002; +} +.center { text-align: center; } +.error { + color: red; + background-color: yellow; +} diff --git a/ext/fiddle/testing1.html b/ext/fiddle/testing1.html new file mode 100644 index 000000000..bf22f30ff --- /dev/null +++ b/ext/fiddle/testing1.html @@ -0,0 +1,33 @@ + + + + + + + + + sqlite3-api.js tests + + + +
      sqlite3-api.js tests
      + +
      +
      +
      Initializing app...
      +
      + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
      +
      +
      Downloading...
      +
      + +
      +
      Everything on this page happens in the dev console.
      + + + + + diff --git a/ext/fiddle/testing1.js b/ext/fiddle/testing1.js new file mode 100644 index 000000000..a6f0062dc --- /dev/null +++ b/ext/fiddle/testing1.js @@ -0,0 +1,185 @@ +/* + 2022-05-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. + + *********************************************************************** + + A basic test script for sqlite3-api.js. This file must be run in + main JS thread and sqlite3.js must have been loaded before it. +*/ +(function(){ + const T = self.SqliteTestUtil; + const log = console.log.bind(console); + + const assert = function(condition, text) { + if (!condition) { + throw new Error('Assertion failed' + (text ? ': ' + text : '')); + } + }; + + const test1 = function(db,sqlite3){ + const api = sqlite3.api; + log("Basic sanity tests..."); + T.assert(db._pDb); + let st = db.prepare("select 3 as a"); + //log("statement =",st); + T.assert(st._pStmt) + .assert(!st._mayGet) + .assert('a' === st.getColumnName(0)) + .assert(st === db._statements[st._pStmt]) + .assert(1===st.columnCount) + .assert(0===st.parameterCount) + .mustThrow(()=>st.bind(1,null)) + .assert(true===st.step()) + .assert(3 === st.get(0)) + .mustThrow(()=>st.get(1)) + .mustThrow(()=>st.get(0,~api.SQLITE_INTEGER)) + .assert(3 === st.get(0,api.SQLITE_INTEGER)) + .assert(3 === st.getInt(0)) + .assert('3' === st.get(0,api.SQLITE_TEXT)) + .assert('3' === st.getString(0)) + .assert(3.0 === st.get(0,api.SQLITE_FLOAT)) + .assert(3.0 === st.getFloat(0)) + .assert(st.get(0,api.SQLITE_BLOB) instanceof Uint8Array) + .assert(st.getBlob(0) instanceof Uint8Array) + .assert(3 === st.get([])[0]) + .assert(3 === st.get({}).a) + .assert(3 === st.getJSON(0)) + .assert(st._mayGet) + .assert(false===st.step()) + .assert(!st._mayGet) + ; + let pId = st._pStmt; + st.finalize(); + T.assert(!st._pStmt) + .assert(!db._statements[pId]); + + let list = []; + db.exec({ + sql:`CREATE TABLE t(a,b); +INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`, + multi: true, + saveSql: list, + bind: [5,6] + }); + T.assert(2 === list.length); + //log("Exec'd SQL:", list); + let counter = 0, colNames = []; + list.length = 0; + db.exec("SELECT a a, b b FROM t",{ + rowMode: 'object', + resultRows: list, + columnNames: colNames, + callback: function(row,stmt){ + ++counter; + T.assert(row.a%2 && row.a<6); + } + }); + T.assert(2 === colNames.length) + .assert('a' === colNames[0]) + .assert(3 === counter) + .assert(3 === list.length); + list.length = 0; + db.exec("SELECT a a, b b FROM t",{ + rowMode: 'array', + callback: function(row,stmt){ + ++counter; + T.assert(Array.isArray(row)) + .assert(0===row[1]%2 && row[1]<7); + } + }); + T.assert(6 === counter); + }; + + const testUDF = function(db){ + log("Testing UDF..."); + db.createFunction("foo",function(a,b){return a+b}); + T.assert(7===db.selectValue("select foo(3,4)")). + assert(5===db.selectValue("select foo(3,?)",2)). + assert(5===db.selectValue("select foo(?,?)",[1,4])). + assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5})); + db.createFunction("bar", { + arity: -1, + callback: function(){ + var rc = 0; + for(let i = 0; i < arguments.length; ++i) rc += arguments[i]; + return rc; + } + }); + + log("Testing DB::selectValue() w/ UDF..."); + T.assert(0===db.selectValue("select bar()")). + assert(1===db.selectValue("select bar(1)")). + assert(3===db.selectValue("select bar(1,2)")). + assert(-1===db.selectValue("select bar(1,2,-4)")); + + const eqApprox = function(v1,v2,factor=0.05){ + return v1>=(v2-factor) && v1<=(v2+factor); + }; + + T.assert('hi' === db.selectValue("select ?",'hi')). + assert(null===db.selectValue("select null")). + assert(null === db.selectValue("select ?",null)). + assert(null === db.selectValue("select ?",[null])). + assert(null === db.selectValue("select $a",{$a:null})). + assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))) + ; + }; + + const testAttach = function(db){ + log("Testing ATTACH..."); + db.exec({ + sql:[ + "attach 'foo.db' as foo", + "create table foo.bar(a)", + "insert into foo.bar(a) values(1),(2),(3)" + ].join(';'), + multi: true + }); + T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a')); + db.exec("detach foo"); + T.mustThrow(()=>db.exec("select * from foo.bar")); + }; + + const runTests = function(Module){ + T.assert(Module._free instanceof Function). + assert(Module.allocate instanceof Function). + assert(Module.addFunction instanceof Function). + assert(Module.removeFunction instanceof Function); + const sqlite3 = Module.sqlite3; + const api = sqlite3.api; + const oo = sqlite3.SQLite3; + console.log("Loaded module:",api.sqlite3_libversion(), + api.sqlite3_sourceid()); + log("Build options:",oo.compileOptionUsed()); + const db = new oo.DB(); + try { + log("DB:",db.filename); + [ + test1, testUDF, testAttach + ].forEach((f)=>{ + const t = T.counter; + f(db, sqlite3); + log("Test count:",T.counter - t); + }); + }finally{ + db.close(); + } + log("Total Test count:",T.counter); + }; + + initSqlite3Module(self.sqlite3TestModule).then(function(theModule){ + /** Use a timeout so that we are (hopefully) out from + under the module init stack when our setup gets + run. Just on principle, not because we _need_ to + be. */ + //console.debug("theModule =",theModule); + setTimeout(()=>runTests(theModule), 0); + }); +})(); diff --git a/ext/fiddle/testing2.html b/ext/fiddle/testing2.html new file mode 100644 index 000000000..b773c4aa4 --- /dev/null +++ b/ext/fiddle/testing2.html @@ -0,0 +1,32 @@ + + + + + + + + + sqlite3-worker.js tests + + + +
      sqlite3-worker.js tests
      + +
      +
      +
      Initializing app...
      +
      + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
      +
      +
      Downloading...
      +
      + +
      +
      Everything on this page happens in the dev console.
      + + + + diff --git a/ext/fiddle/testing2.js b/ext/fiddle/testing2.js new file mode 100644 index 000000000..4bb21b95f --- /dev/null +++ b/ext/fiddle/testing2.js @@ -0,0 +1,235 @@ +/* + 2022-05-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. + + *********************************************************************** + + A basic test script for sqlite3-worker.js. +*/ +(function(){ + const T = self.SqliteTestUtil; + const SW = new Worker("sqlite3-worker.js"); + /** Posts a worker message as {type:type, data:data}. */ + const wMsg = function(type,data){ + SW.postMessage({type, data}); + return SW; + }; + const log = console.log.bind(console); + const warn = console.warn.bind(console); + const error = console.error.bind(console); + + SW.onerror = function(event){ + error("onerror",event); + }; + + /** + A queue for callbacks which are to be run in response to async + DB commands. See the notes in runTests() for why we need + this. The event-handling plumbing of this file requires that + any DB command which includes a `messageId` property also have + a queued callback entry, as the existence of that property in + response payloads is how it knows whether or not to shift an + entry off of the queue. + */ + const MsgHandlerQueue = { + queue: [], + id: 0, + push: function(type,callback){ + this.queue.push(callback); + return type + '-' + (++this.id); + }, + shift: function(){ + return this.queue.shift(); + } + }; + + const testCount = ()=>log("Total test count:",T.counter); + + const runOneTest = function(eventType, eventData, callback){ + T.assert(eventData && 'object'===typeof eventData); + /* ^^^ that is for the testing and messageId-related code, not + a hard requirement of all of the Worker-exposed APIs. */ + eventData.messageId = MsgHandlerQueue.push(eventType,function(ev){ + log("runOneTest",eventType,"result",ev.data); + if(callback instanceof Function){ + callback(ev); + testCount(); + } + }); + wMsg(eventType, eventData); + }; + + /** Methods which map directly to onmessage() event.type keys. + They get passed the inbound event.data. */ + const dbMsgHandler = { + open: function(ev){ + log("open result",ev.data); + }, + exec: function(ev){ + log("exec result",ev.data); + }, + export: function(ev){ + log("exec result",ev.data); + }, + error: function(ev){ + error("ERROR from the worker:",ev.data); + }, + resultRowTest1: function f(ev){ + if(undefined === f.counter) f.counter = 0; + if(ev.data) ++f.counter; + //log("exec() result row:",ev.data); + T.assert(null===ev.data || 'number' === typeof ev.data.b); + } + }; + + const runTests = function(){ + const mustNotReach = ()=>{ + throw new Error("This is not supposed to be reached."); + }; + /** + "The problem" now is that the test results are async. We + know, however, that the messages posted to the worker will + be processed in the order they are passed to it, so we can + create a queue of callbacks to handle them. The problem + with that approach is that it's not error-handling + friendly, in that an error can cause us to bypass a result + handler queue entry. We have to perform some extra + acrobatics to account for that. + */ + runOneTest('open', {filename:'testing2.sqlite3'}, function(ev){ + //log("open result",ev); + T.assert('testing2.sqlite3'===ev.data.filename) + .assert(ev.data.messageId); + }); + runOneTest('exec',{ + sql: ["create table t(a,b)", + "insert into t(a,b) values(1,2),(3,4),(5,6)" + ].join(';'), + multi: true, + resultRows: [], columnNames: [] + }, function(ev){ + ev = ev.data; + T.assert(0===ev.resultRows.length) + .assert(0===ev.columnNames.length); + }); + runOneTest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], + }, function(ev){ + ev = ev.data; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0][0]) + .assert(6===ev.resultRows[2][1]) + .assert(2===ev.columnNames.length) + .assert('b'===ev.columnNames[1]); + }); + runOneTest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], + rowMode: 'object' + }, function(ev){ + ev = ev.data; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0].a) + .assert(6===ev.resultRows[2].b) + }); + runOneTest('exec',{sql:'intentional_error'}, mustNotReach); + // Ensure that the message-handler queue survives ^^^ that error... + runOneTest('exec',{ + sql:'select 1', + resultRows: [], + //rowMode: 'array', // array is the default in the Worker interface + }, function(ev){ + ev = ev.data; + T.assert(1 === ev.resultRows.length) + .assert(1 === ev.resultRows[0][0]); + }); + runOneTest('exec',{ + sql: 'select a a, b b from t order by a', + callback: 'resultRowTest1', + rowMode: 'object' + }, function(ev){ + T.assert(3===dbMsgHandler.resultRowTest1.counter); + dbMsgHandler.resultRowTest1.counter = 0; + }); + runOneTest('exec',{sql: 'delete from t where a>3'}); + runOneTest('exec',{ + sql: 'select count(a) from t', + resultRows: [] + },function(ev){ + ev = ev.data; + T.assert(1===ev.resultRows.length) + .assert(2===ev.resultRows[0][0]); + }); + runOneTest('export',{}, function(ev){ + ev = ev.data; + T.assert('string' === typeof ev.filename) + .assert(ev.buffer instanceof Uint8Array) + .assert(ev.buffer.length > 1024) + .assert('application/x-sqlite3' === ev.mimetype); + }); + + /***** close() tests must come last. *****/ + runOneTest('close',{unlink:true},function(ev){ + ev = ev.data; + T.assert('string' === typeof ev.filename); + }); + runOneTest('close',{unlink:true},function(ev){ + ev = ev.data; + T.assert(undefined === ev.filename); + }); + }; + + SW.onmessage = function(ev){ + if(!ev.data || 'object'!==typeof ev.data){ + warn("Unknown sqlite3-worker message type:",ev); + return; + } + ev = ev.data/*expecting a nested object*/; + //log("main window onmessage:",ev); + if(ev.data && ev.data.messageId){ + /* We're expecting a queued-up callback handler. */ + const f = MsgHandlerQueue.shift(); + if('error'===ev.type){ + dbMsgHandler.error(ev); + return; + } + T.assert(f instanceof Function); + f(ev); + return; + } + switch(ev.type){ + case 'sqlite3-api': + switch(ev.data){ + case 'loaded': + log("Message:",ev); return; + case 'ready': + log("Message:",ev); + self.sqlite3TestModule.setStatus(null); + setTimeout(runTests, 0); + return; + default: + warn("Unknown sqlite3-api message type:",ev); + return; + } + default: + if(dbMsgHandler.hasOwnProperty(ev.type)){ + try{dbMsgHandler[ev.type](ev);} + catch(err){ + error("Exception while handling db result message", + ev,":",err); + } + return; + } + warn("Unknown sqlite3-api message type:",ev); + } + }; + + log("Init complete, but async init bits may still be running."); +})(); diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 097338f54..1a1672512 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -4308,8 +4308,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ char *aPoslist = 0; /* Position list for deferred tokens */ int nPoslist = 0; /* Number of bytes in aPoslist */ int iPrev = -1; /* Token number of previous deferred token */ - - assert( pPhrase->doclist.bFreeList==0 ); + char *aFree = (pPhrase->doclist.bFreeList ? pPhrase->doclist.pList : 0); for(iToken=0; iTokennToken; iToken++){ Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; @@ -4323,6 +4322,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ if( pList==0 ){ sqlite3_free(aPoslist); + sqlite3_free(aFree); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; @@ -4343,6 +4343,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ nPoslist = (int)(aOut - aPoslist); if( nPoslist==0 ){ sqlite3_free(aPoslist); + sqlite3_free(aFree); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; @@ -4382,6 +4383,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ } pPhrase->doclist.pList = aOut; + assert( p1 && p2 ); if( fts3PoslistPhraseMerge(&aOut, nDistance, 0, 1, &p1, &p2) ){ pPhrase->doclist.bFreeList = 1; pPhrase->doclist.nList = (int)(aOut - pPhrase->doclist.pList); @@ -4394,6 +4396,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ } } + if( pPhrase->doclist.pList!=aFree ) sqlite3_free(aFree); return SQLITE_OK; } #endif /* SQLITE_DISABLE_FTS4_DEFERRED */ @@ -5568,11 +5571,10 @@ static int fts3EvalTestExpr( default: { #ifndef SQLITE_DISABLE_FTS4_DEFERRED - if( pCsr->pDeferred - && (pExpr->iDocid==pCsr->iPrevId || pExpr->bDeferred) - ){ + if( pCsr->pDeferred && (pExpr->bDeferred || ( + pExpr->iDocid==pCsr->iPrevId && pExpr->pPhrase->doclist.pList + ))){ Fts3Phrase *pPhrase = pExpr->pPhrase; - assert( pExpr->bDeferred || pPhrase->doclist.bFreeList==0 ); if( pExpr->bDeferred ){ fts3EvalInvalidatePoslist(pPhrase); } diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 201e5813c..f1a4bd6bb 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -3899,6 +3899,8 @@ static int fts3IncrmergePush( pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix); } pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix); + assert( nPrefix+nSuffix<=nTerm ); + assert( nPrefix>=0 ); memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix); pBlk->n += nSuffix; @@ -4021,6 +4023,7 @@ static int fts3IncrmergeAppend( pLeaf = &pWriter->aNodeWriter[0]; nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm); nSuffix = nTerm - nPrefix; + if(nSuffix<=0 ) return FTS_CORRUPT_VTAB; nSpace = sqlite3Fts3VarintLen(nPrefix); nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index d9c1dd0fd..66bd304d4 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -1627,6 +1627,9 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset( }else{ if( pRet->nPhrase>0 ){ Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1]; + assert( pParse!=0 ); + assert( pParse->apPhrase!=0 ); + assert( pParse->nPhrase>=2 ); assert( pLast==pParse->apPhrase[pParse->nPhrase-2] ); if( pPhrase->nTerm==0 ){ fts5ExprPhraseFree(pPhrase); diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 6b88f31c0..d2ec9ad5a 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -296,7 +296,7 @@ struct Fts5Index { sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */ sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */ sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */ - sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */ + sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */ sqlite3_stmt *pIdxSelect; int nRead; /* Total number of blocks read */ diff --git a/ext/fts5/test/fts5fault4.test b/ext/fts5/test/fts5fault4.test index 877e0228a..1d0d5c9b7 100644 --- a/ext/fts5/test/fts5fault4.test +++ b/ext/fts5/test/fts5fault4.test @@ -16,12 +16,16 @@ source [file join [file dirname [info script]] fts5_common.tcl] source $testdir/malloc_common.tcl set testprefix fts5fault4 -# If SQLITE_ENABLE_FTS3 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return } +set ::TMPDBERROR [list 1 \ + {unable to open a temporary database file for storing temporary tables} +] + #------------------------------------------------------------------------- # An OOM while dropping an fts5 table. # @@ -391,7 +395,7 @@ do_faultsim_test 14.1 -faults oom-t* -prep { } -body { db eval { ALTER TABLE "tbl one" RENAME TO "tbl two" } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } finish_test diff --git a/ext/icu/README.txt b/ext/icu/README.txt index be443f54e..c3c35a2c6 100644 --- a/ext/icu/README.txt +++ b/ext/icu/README.txt @@ -153,18 +153,12 @@ SQLite. Documentation follows. the ICU extension LIKE operator is 50000, defined in source file "icu.c". - 3.3 Collation Sequence Security Issue + 3.3 Collation Sequence Security Internally, SQLite assumes that indices stored in database files are sorted according to the collation sequence indicated by the SQL schema. Changing the definition of a collation sequence after an index has been built is therefore equivalent to database - corruption. The SQLite library is not very well tested under - these conditions, and may contain potential buffer overruns - or other programming errors that could be exploited by a malicious - programmer. - - If the ICU extension is used in an environment where potentially - malicious users may execute arbitrary SQL (i.e. gears), they - should be prevented from invoking the icu_load_collation() function, - possibly using the authorisation callback. + corruption. The SQLite library is well tested for robustness in + the fact of database corruption. Database corruption may well + lead to incorrect answers, but should not cause memory errors. diff --git a/ext/misc/regexp.c b/ext/misc/regexp.c index b626ca424..52973cc73 100644 --- a/ext/misc/regexp.c +++ b/ext/misc/regexp.c @@ -685,7 +685,7 @@ static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); pRe->zInit[j++] = 0x80 | (x&0x3f); }else if( x<=0xffff ){ - pRe->zInit[j++] = (unsigned char)(0xd0 | (x>>12)); + pRe->zInit[j++] = (unsigned char)(0xe0 | (x>>12)); pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); pRe->zInit[j++] = 0x80 | (x&0x3f); }else{ diff --git a/ext/misc/shathree.c b/ext/misc/shathree.c index 1f100986e..f80dd855d 100644 --- a/ext/misc/shathree.c +++ b/ext/misc/shathree.c @@ -19,7 +19,7 @@ ** The sha3(X) function computes the SHA3 hash of the input X, or NULL if ** X is NULL. ** -** The sha3_query(Y) function evalutes all queries in the SQL statements of Y +** The sha3_query(Y) function evaluates all queries in the SQL statements of Y ** and returns a hash of their results. ** ** The SIZE argument is optional. If omitted, the SHA3-256 hash algorithm diff --git a/ext/misc/stmt.c b/ext/misc/stmt.c index 876b0e5cb..1687f4197 100644 --- a/ext/misc/stmt.c +++ b/ext/misc/stmt.c @@ -30,6 +30,16 @@ SQLITE_EXTENSION_INIT1 #ifndef SQLITE_OMIT_VIRTUALTABLE + +#define STMT_NUM_INTEGER_COLUMN 10 +typedef struct StmtRow StmtRow; +struct StmtRow { + sqlite3_int64 iRowid; /* Rowid value */ + char *zSql; /* column "sql" */ + int aCol[STMT_NUM_INTEGER_COLUMN+1]; /* all other column values */ + StmtRow *pNext; /* Next row to return */ +}; + /* stmt_vtab is a subclass of sqlite3_vtab which will ** serve as the underlying representation of a stmt virtual table */ @@ -47,8 +57,7 @@ typedef struct stmt_cursor stmt_cursor; struct stmt_cursor { sqlite3_vtab_cursor base; /* Base class - must be first */ sqlite3 *db; /* Database connection for this cursor */ - sqlite3_stmt *pStmt; /* Statement cursor is currently pointing at */ - sqlite3_int64 iRowid; /* The rowid */ + StmtRow *pRow; /* Current row */ }; /* @@ -92,7 +101,7 @@ static int stmtConnect( "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," "reprep,run,mem)"); if( rc==SQLITE_OK ){ - pNew = sqlite3_malloc( sizeof(*pNew) ); + pNew = sqlite3_malloc64( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); @@ -114,7 +123,7 @@ static int stmtDisconnect(sqlite3_vtab *pVtab){ */ static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ stmt_cursor *pCur; - pCur = sqlite3_malloc( sizeof(*pCur) ); + pCur = sqlite3_malloc64( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); pCur->db = ((stmt_vtab*)p)->db; @@ -122,10 +131,21 @@ static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ return SQLITE_OK; } +static void stmtCsrReset(stmt_cursor *pCur){ + StmtRow *pRow = 0; + StmtRow *pNext = 0; + for(pRow=pCur->pRow; pRow; pRow=pNext){ + pNext = pRow->pNext; + sqlite3_free(pRow); + } + pCur->pRow = 0; +} + /* ** Destructor for a stmt_cursor. */ static int stmtClose(sqlite3_vtab_cursor *cur){ + stmtCsrReset((stmt_cursor*)cur); sqlite3_free(cur); return SQLITE_OK; } @@ -136,8 +156,9 @@ static int stmtClose(sqlite3_vtab_cursor *cur){ */ static int stmtNext(sqlite3_vtab_cursor *cur){ stmt_cursor *pCur = (stmt_cursor*)cur; - pCur->iRowid++; - pCur->pStmt = sqlite3_next_stmt(pCur->db, pCur->pStmt); + StmtRow *pNext = pCur->pRow->pNext; + sqlite3_free(pCur->pRow); + pCur->pRow = pNext; return SQLITE_OK; } @@ -151,39 +172,11 @@ static int stmtColumn( int i /* Which column to return */ ){ stmt_cursor *pCur = (stmt_cursor*)cur; - switch( i ){ - case STMT_COLUMN_SQL: { - sqlite3_result_text(ctx, sqlite3_sql(pCur->pStmt), -1, SQLITE_TRANSIENT); - break; - } - case STMT_COLUMN_NCOL: { - sqlite3_result_int(ctx, sqlite3_column_count(pCur->pStmt)); - break; - } - case STMT_COLUMN_RO: { - sqlite3_result_int(ctx, sqlite3_stmt_readonly(pCur->pStmt)); - break; - } - case STMT_COLUMN_BUSY: { - sqlite3_result_int(ctx, sqlite3_stmt_busy(pCur->pStmt)); - break; - } - default: { - assert( i==STMT_COLUMN_MEM ); - i = SQLITE_STMTSTATUS_MEMUSED + - STMT_COLUMN_NSCAN - SQLITE_STMTSTATUS_FULLSCAN_STEP; - /* Fall thru */ - } - case STMT_COLUMN_NSCAN: - case STMT_COLUMN_NSORT: - case STMT_COLUMN_NAIDX: - case STMT_COLUMN_NSTEP: - case STMT_COLUMN_REPREP: - case STMT_COLUMN_RUN: { - sqlite3_result_int(ctx, sqlite3_stmt_status(pCur->pStmt, - i-STMT_COLUMN_NSCAN+SQLITE_STMTSTATUS_FULLSCAN_STEP, 0)); - break; - } + StmtRow *pRow = pCur->pRow; + if( i==STMT_COLUMN_SQL ){ + sqlite3_result_text(ctx, pRow->zSql, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_int(ctx, pRow->aCol[i]); } return SQLITE_OK; } @@ -194,7 +187,7 @@ static int stmtColumn( */ static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ stmt_cursor *pCur = (stmt_cursor*)cur; - *pRowid = pCur->iRowid; + *pRowid = pCur->pRow->iRowid; return SQLITE_OK; } @@ -204,7 +197,7 @@ static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ */ static int stmtEof(sqlite3_vtab_cursor *cur){ stmt_cursor *pCur = (stmt_cursor*)cur; - return pCur->pStmt==0; + return pCur->pRow==0; } /* @@ -219,9 +212,53 @@ static int stmtFilter( int argc, sqlite3_value **argv ){ stmt_cursor *pCur = (stmt_cursor *)pVtabCursor; - pCur->pStmt = 0; - pCur->iRowid = 0; - return stmtNext(pVtabCursor); + sqlite3_stmt *p = 0; + sqlite3_int64 iRowid = 1; + StmtRow **ppRow = 0; + + stmtCsrReset(pCur); + ppRow = &pCur->pRow; + for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){ + const char *zSql = sqlite3_sql(p); + sqlite3_int64 nSql = zSql ? strlen(zSql)+1 : 0; + StmtRow *pNew = (StmtRow*)sqlite3_malloc64(sizeof(StmtRow) + nSql); + + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(StmtRow)); + if( zSql ){ + pNew->zSql = (char*)&pNew[1]; + memcpy(pNew->zSql, zSql, nSql); + } + pNew->aCol[STMT_COLUMN_NCOL] = sqlite3_column_count(p); + pNew->aCol[STMT_COLUMN_RO] = sqlite3_stmt_readonly(p); + pNew->aCol[STMT_COLUMN_BUSY] = sqlite3_stmt_busy(p); + pNew->aCol[STMT_COLUMN_NSCAN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_FULLSCAN_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_NSORT] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_SORT, 0 + ); + pNew->aCol[STMT_COLUMN_NAIDX] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_AUTOINDEX, 0 + ); + pNew->aCol[STMT_COLUMN_NSTEP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_VM_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_REPREP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_REPREPARE, 0 + ); + pNew->aCol[STMT_COLUMN_RUN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_RUN, 0 + ); + pNew->aCol[STMT_COLUMN_MEM] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_MEMUSED, 0 + ); + pNew->iRowid = iRowid++; + *ppRow = pNew; + ppRow = &pNew->pNext; + } + + return SQLITE_OK; } /* diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index 243705ff1..f818fbc11 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -2175,6 +2175,10 @@ static int zipfileRegister(sqlite3 *db){ zipfileRollback, /* xRollback */ zipfileFindFunction, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollback */ + 0 /* xShadowName */ }; int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0); diff --git a/ext/rbu/rbubusy.test b/ext/rbu/rbubusy.test new file mode 100644 index 000000000..8cc47db8f --- /dev/null +++ b/ext/rbu/rbubusy.test @@ -0,0 +1,87 @@ +# 2014 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. +# +#*********************************************************************** +# +# + +source [file join [file dirname [info script]] rbu_common.tcl] +set ::testprefix rbubusy + +db close +sqlite3_shutdown +test_sqlite3_log xLog +reset_db + +set db_sql { + CREATE TABLE t1(a PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1000, 2000, 3000); +} + +set rbu_sql { + CREATE TABLE data_t1(a, b, c, rbu_control); + INSERT INTO data_t1 VALUES(1, 2, 3, 0); + INSERT INTO data_t1 VALUES(4, 5, 6, 0); + INSERT INTO data_t1 VALUES(7, 8, 9, 0); +} + +do_test 1.1 { + forcedelete rbu.db + sqlite3 rbu rbu.db + rbu eval $rbu_sql + rbu close + + db eval $db_sql +} {} + +do_execsql_test 1.2 { + BEGIN; + SELECT * FROM t1 +} {1000 2000 3000} + +do_test 1.3 { + sqlite3rbu rbu test.db rbu.db + rbu step +} {SQLITE_OK} + +do_test 1.4 { + while 1 { + set rc [rbu step] + if {$rc!="SQLITE_OK"} break + } + set rc +} {SQLITE_BUSY} + +do_test 1.5 { + rbu step +} {SQLITE_BUSY} + +do_test 1.6 { + db eval COMMIT + rbu step +} {SQLITE_BUSY} +catch { rbu close } + +do_test 1.7 { + sqlite3rbu rbu test.db rbu.db + while 1 { + set rc [rbu step] + if {$rc!="SQLITE_OK"} break + } + set rc +} {SQLITE_DONE} + +rbu close + +db close +sqlite3_shutdown +test_sqlite3_log +sqlite3_initialize +finish_test + diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index 0423bf557..034155341 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -733,4 +733,27 @@ do_execsql_test 19.1 { SELECT v0.x FROM v0, rt0 WHERE v0.x = rt0.b; } {0.0} +# 2022-06-20 https://sqlite.org/forum/forumpost/57bdf2217d +# +reset_db +do_execsql_test 20.0 { + CREATE VIRTUAL TABLE rt0 USING rtree(id, x0, x1); + CREATE TABLE t0(a INT); + CREATE TABLE t1(b INT); + INSERT INTO rt0 VALUES(0, 0, 0); +} +do_catchsql_test 20.1 { + SELECT * FROM t1 JOIN t0 ON x0>a RIGHT JOIN rt0 ON true WHERE +x0 = 0; +} {1 {ON clause references tables to its right}} +do_catchsql_test 20.2 { + SELECT * FROM t1 JOIN t0 ON x0>a RIGHT JOIN rt0 ON true WHERE x0 = 0; +} {1 {ON clause references tables to its right}} +db null - +do_execsql_test 20.3 { + SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE +x0 = 0; +} {- - 0 0.0 0.0} +do_execsql_test 20.4 { + SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE x0 = 0; +} {- - 0 0.0 0.0} + finish_test diff --git a/ext/rtree/rtree3.test b/ext/rtree/rtree3.test index e37d18ee8..c980863db 100644 --- a/ext/rtree/rtree3.test +++ b/ext/rtree/rtree3.test @@ -23,6 +23,10 @@ ifcapable !rtree { return } +set ::TMPDBERROR [list 1 \ + {unable to open a temporary database file for storing temporary tables} +] + # Test summary: # # rtree3-1: Test OOM in simple CREATE TABLE, INSERT, DELETE and SELECT @@ -196,9 +200,9 @@ do_test rtree3-7.prep { do_faultsim_test rtree3-7 -faults oom-* -prep { faultsim_restore_and_reopen } -body { - execsql { ALTER TABLE rt RENAME TO rt2 } + execsql { ALTER TABLE rt RENAME TO rt2 } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } do_faultsim_test rtree3-8 -faults oom-* -prep { diff --git a/ext/rtree/rtreeC.test b/ext/rtree/rtreeC.test index 75afcd7e2..bddc7d303 100644 --- a/ext/rtree/rtreeC.test +++ b/ext/rtree/rtreeC.test @@ -342,7 +342,7 @@ do_eqp_execsql_test 7.2 { QUERY PLAN |--SCAN xdir |--SCAN rt VIRTUAL TABLE INDEX 2:B0D1 - `--SCAN ydir + `--SCAN ydir LEFT-JOIN } { 5 1 2 7 12 14 {} 5 2 2 7 8 12 10 diff --git a/main.mk b/main.mk index be16034e5..35e42d6ca 100644 --- a/main.mk +++ b/main.mk @@ -423,6 +423,7 @@ TESTSRC2 = \ $(TOP)/src/select.c \ $(TOP)/src/threads.c \ $(TOP)/src/tokenize.c \ + $(TOP)/src/treeview.c \ $(TOP)/src/utf.c \ $(TOP)/src/util.c \ $(TOP)/src/vdbeapi.c \ @@ -546,6 +547,9 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB +FUZZSRC += $(TOP)/test/fuzzcheck.c +FUZZSRC += $(TOP)/test/ossfuzz.c +FUZZSRC += $(TOP)/test/fuzzinvariants.c DBFUZZ_OPT = KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ ST_OPT = -DSQLITE_THREADSAFE=0 @@ -604,10 +608,10 @@ dbfuzz2$(EXE): $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h $(TCCX) -I. -g -O0 -DSTANDALONE -o dbfuzz2$(EXE) \ $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c $(TLIBS) $(THREADLIB) -fuzzcheck$(EXE): $(TOP)/test/fuzzcheck.c sqlite3.c sqlite3.h $(TOP)/test/ossfuzz.c +fuzzcheck$(EXE): $(FUZZSRC) sqlite3.c sqlite3.h $(TCCX) -o fuzzcheck$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \ - $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) $(THREADLIB) + $(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB) ossshell$(EXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h $(TCCX) -o ossshell$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ diff --git a/manifest b/manifest index 0b6d0669f..7a3c6e963 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.38.5 -D 2022-05-06T15:25:27.476 +C Version\s3.39.2 +D 2022-07-21T15:24:47.212 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in b210ad2733317f1a4353085dfb9d385ceec30b0e6a61d20a5accabecac6b1949 +F Makefile.in 4e1cacdce04497567464116cf5798674e8a21e15b4d44838ceaa8a49c527028c F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 -F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3 -F README.md 2dd87a5c1d108b224921f3dd47dea567973f706e1f6959386282a626f459a70c -F VERSION 2cef599294698e267cbcdadf44f14a0637a0015c1872cb37a16fe723d7c7add0 +F Makefile.msc de7cb3e095ce2fdc33513ccd76ebdaeda1483d0ddab0410fe65cbdeadd4c0ee1 +F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e +F VERSION 10105f5660ef070881d1a9cd2ac5cea1ab539c85bc0a4196d3fb8c7de513aa6c F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -34,7 +34,7 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure d616d46e32a1c2324a561b464175907b976ff62e8946b14e9954268841fc0bb8 x +F configure cafd64df0dc76ea68574e355fd5831de9836a95ac5750a1fb98e176a44f6bf12 x F configure.ac 3ef6eeff4387585bfcab76b0c3f6e15a0618587bb90245dd5d44e4378141bb35 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -55,6 +55,23 @@ F ext/expert/expert1.test 3c642a4e7bbb14f21ddab595436fb465a4733f47a0fe5b2855e1d5 F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0ff5d9cdfac204 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 +F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3 +F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api 540b9dec63a3a62a256e2f030827848a92e9b9d9b6fa5c0188295a4a1c5382cd +F ext/fiddle/EXPORTED_RUNTIME_METHODS b831017ba67ba993b34a27400cef2f6095bd6789c0fc4eba7e7a251c207be31c +F ext/fiddle/Makefile e25d34a0e1324f771d64c09c592601b97219282011587e6ce410fa8acdedb913 +F ext/fiddle/SqliteTestUtil.js 559731c3e8e0de330ec7d292e6c1846566408caee6637acc8a119ac338a8781c +F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f +F ext/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba833865ab64a1b4ae +F ext/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08 +F ext/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8 +F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf +F ext/fiddle/sqlite3-api.js ccf4bd0c1c5bbb3be3469573423d6c53991941bec497eac63e9f17ea13bf8952 +F ext/fiddle/sqlite3-worker.js a9c2b614beca187dbdd8c053ec2770cc61ec1ac9c0ec6398ceb49a79f705a421 +F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a +F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a +F ext/fiddle/testing1.js e2fa02ac8adbd21c69bc50cfcb79bfc26af0d30a8d6b95ac473a17e0dc9de733 +F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4 +F ext/fiddle/testing2.js 7b45b4e7fddbd51dbaf89b6722c02758051b34bac5a98c11b569a7e7572f88ee F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea @@ -85,7 +102,7 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 6634a3854e70afa8710ee5e3a7253cd0f0c89d4cce207fcbfe2ead3bad1db7d5 +F ext/fts3/fts3.c 8cd361bd8612de3e5ec451d33ced8575cbc5af6744e7efc05d54bde4d2df3cd0 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe F ext/fts3/fts3Int.h dafdc371f9fbab175744b06cfe019d5f040cdfdbd11fea752f5dc28d45b04c05 F ext/fts3/fts3_aux.c f0dc9bd98582615b7750218899bd0c729879b6bbf94d1be57ca1833ff49afc6f @@ -103,7 +120,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c 3109c1a232da86474e196cc7db754445a354409f141e08cb11c846cdb17bdf31 +F ext/fts3/fts3_write.c 85279b980f99253c296006503a13f92957ec49b716123083f021acc74545ecfc F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -117,9 +134,9 @@ F ext/fts5/fts5Int.h 36fd4a05e6e6307e3bac359a589d5f090b903afe0e7ae15db84f0ff90c7 F ext/fts5/fts5_aux.c f558e1fb9f0f86a4f7489e258c162e1f947de5ff2709087fbb465fddb7092f98 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 501e7d3566bc92766b0e11c0109a7c5a6146bc41144195459af5422f6c2078aa -F ext/fts5/fts5_expr.c fcd0770d53028c2b53a15d0f53bf6d0e01b1bf3dd97630b9fedf0801f03aa3ec +F ext/fts5/fts5_expr.c 40174a64829d30cc86e8266306ad24980f6911edd5ca0b8c1ce7821ea1341b88 F ext/fts5/fts5_hash.c d4fb70940359f2120ccd1de7ffe64cc3efe65de9e8995b822cd536ff64c96982 -F ext/fts5/fts5_index.c fdfbc8a62827ec1d1b6f207a1e59c1c4986c3ce245592b5128ffe738867cfcd1 +F ext/fts5/fts5_index.c 3e47d9c56e4e9a6dee78bc32e006d6a28a3b5ec9ff84f3b8c381c78323201720 F ext/fts5/fts5_main.c 6078ae86d3b813753a4f1201054550aff21a3f660e97b30f200d2b1472874151 F ext/fts5/fts5_storage.c 76c6085239eb44424004c022e9da17a5ecd5aaec859fba90ad47d3b08f4c8082 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae @@ -174,7 +191,7 @@ F ext/fts5/test/fts5eb.test a973baadac524dbbb4ad9b0e99030e12cabde2c6b28e0ac43729 F ext/fts5/test/fts5fault1.test d28a65caee75db6897c3cf1358c5230d3bb2a3bf7fb31062c19c7e5382b3d2bd F ext/fts5/test/fts5fault2.test 69c8fdbef830cd0d450908d4504d5bb86609e255af99c421c20a0756251fe344 F ext/fts5/test/fts5fault3.test da2f9e3e56ff5740d68ebdd6877c97089e7ed28ddff28a0da87a6afea27e5522 -F ext/fts5/test/fts5fault4.test 1c1db5fcfe59401e7833146100f1d8de284a0a686fac31ddac9fb56c459f725b +F ext/fts5/test/fts5fault4.test 87a10d0caee57da587c7588b0c8d25d2930197399b4812ad1e4d574c75324cee F ext/fts5/test/fts5fault5.test a336e4e11847de24c9497f80cce18e00bb3fab7fb11f97d04eb9af898900a762 F ext/fts5/test/fts5fault6.test a0fc0a8f99e4b16500c31dfc7e38e1defe0f1693ac47650517ac7b723b1956f8 F ext/fts5/test/fts5fault7.test 0acbec416edb24b8881f154e99c31e9ccf73f539cfcd164090be139e9e97ed4c @@ -237,7 +254,7 @@ F ext/fts5/tool/fts5txt2db.tcl c0d43c8590656f8240e622b00957b3a0facc49482411a9fdc F ext/fts5/tool/loadfts5.tcl 95b03429ee6b138645703c6ca192c3ac96eaf093 F ext/fts5/tool/mkfts5c.tcl 3eba8e9bee4221ed165f3304b51b2a74a705f4ec5df3d044573a2be539534af8 F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c -F ext/icu/README.txt 1c48ffaf7f255bd73d00a35f68f6de357c2a6594f16cb00506a151be23694706 +F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c 91c021c7e3e8bbba286960810fa303295c622e323567b2e6def4ce58e4466e60 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -317,17 +334,17 @@ F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196 F ext/misc/qpvtab.c 09738419e25f603a35c0ac8bd0a04daab794f48d08a9bc07a6085b9057b99009 -F ext/misc/regexp.c b267fd05ff8d38b22f4c2809d7b7a2c61d522e9faf2feb928dbb9662e4a3a386 +F ext/misc/regexp.c 03e483711534c437b2e29648d2a4b7730f5cb781a434ac8150907376bc4489f6 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 F ext/misc/series.c 8d79354f2c3d46b95ee21272a07cf0bcabb58d1f2b06d9e7b8a31dca1dacb3e5 F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d -F ext/misc/shathree.c 9b6fce315bf8b37bd72786086d1f0974701f651e83022a93244e0e8f56f98a45 +F ext/misc/shathree.c 7b17615869a495659f1569ada1d8d3d21b4a24614f2746d93cc87ef7c0b6b36d F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6 -F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a +F ext/misc/stmt.c ed05ad78013edccd43f4fc33d8244001f6f886eb2dfd2106ba33616ac514c6fb F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b @@ -339,7 +356,7 @@ F ext/misc/vfsstat.c 474d08efc697b8eba300082cb1eb74a5f0f3df31ed257db1cb07e72ab0e F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546 -F ext/misc/zipfile.c 3e76c6c3f73e05bfc7b2f743c268071c31e9aec0491282506ecb79cdef69d6f2 +F ext/misc/zipfile.c 22afe121d1a5e318453b7cdbc0f5492161d2fd4fce548ff3605da05e89be7140 F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test c62904bd9526dcdc3496a21199aaf14ae191bbadbf67f076bf16be6b3f2115c2 @@ -358,6 +375,7 @@ F ext/rbu/rbuA.test b34a90cb495682c25b5fc03a9d5e7a4fc99541c29256f25e2e2a4f6542b4 F ext/rbu/rbuB.test 52b07158824c6927b7e25554ace92a695cdebfc296ae3d308ac386984aded9bc F ext/rbu/rbuC.test 80f1cc2fb74f44b1128fd0ed8eedab3a76fefeb72a947860e2869ef76fc8dc6b F ext/rbu/rbu_common.tcl 60d904133ff843fe72cc0514e9dd2486707181e6e0fbab20979da28c48d21de9 +F ext/rbu/rbubusy.test f38ef557358564491b8a2ee70e4cad31e40fcea57a16f27bc56ba40a59bbde50 F ext/rbu/rbucollate.test cac528a9a46318cba42e61258bb42660bbbf4fdb9a8c863de5a54ad0c658d197 F ext/rbu/rbucrash.test 000981a1fe8a6e4d9a684232f6a129e66a3ef595f5ed74655e2f9c68ffa613b4 F ext/rbu/rbucrash2.test efa143cc94228eb0266d3f1abfbee60a5838a84cef7cc3fcb8c145b74d96fd41 @@ -398,9 +416,9 @@ F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c cc3f89c11abcf114fa60d74709ae8b5bc1eae5a261b30bc1bb7085089c03bfab F ext/rtree/rtree.c d7b4b8b81d8d54376a7f81de5be85ec58b37c11604bcf42984a8418b34158d93 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 -F ext/rtree/rtree1.test 35c3bc0def71317b7601ee0d1149e7df2cd8fc4f13ec89a64761ac3f46ca123f +F ext/rtree/rtree1.test d47f58832145fcfed9067bc457ca8664962196c4566c17a1ebd679367db55d11 F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d -F ext/rtree/rtree3.test 4ee5d7df86040efe3d8d84f141f2962a7745452200a7cba1db06f86d97050499 +F ext/rtree/rtree3.test 272594f88c344e973864008bbe4c71fd3a41a264c097d568593ee7886d83d409 F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2e55d3142 F ext/rtree/rtree6.test 9ce3691c1aac43070a9f194f0ebf54372db346c5a82241fd11b525ed53ce9f3f @@ -409,7 +427,7 @@ F ext/rtree/rtree8.test 2d99006a1386663978c9e1df167554671e4f711c419175b39f332719 F ext/rtree/rtree9.test fd3c9384ef8aabbc127b3878764070398f136eebc551cd20484b570f2cc1956a F ext/rtree/rtreeA.test a7fd235d8194115fa2e14d300337931eb2e960fe8a46cdfb66add2206412ea41 F ext/rtree/rtreeB.test 4cec297f8e5c588654bbf3c6ed0903f10612be8a2878055dd25faf8c71758bc9 -F ext/rtree/rtreeC.test c4bfa9a61c6788c03e4a9ce40ab2cfc6100982559effd9842d1b658e1d47aa5f +F ext/rtree/rtreeC.test 2978b194d09b13e106bdb0e1c5b408b9d42eb338c1082bf43c87ef43bd626147 F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc F ext/rtree/rtreeE.test e65d3fc625da1800b412fc8785817327d43ccfec5f5973912d8c9e471928caa9 F ext/rtree/rtreeF.test 81ffa7ef51c4e4618d497a57328c265bf576990c7070633b623b23cd450ed331 @@ -473,7 +491,7 @@ F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 80ac3d2f82eb21c6fb4423f9c7f8e207abb51be6676845a00e246cfb22f9c77c +F main.mk 50e98928657b6d5aeb9445f6130964bd644861cfa049d689db41da317d449517 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -485,44 +503,44 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a -F src/alter.c 006325f8844c65d885b3ba469b4c08d9dd0cd3e9ec481d5bcff621f224cb2302 -F src/analyze.c 7518b99e07c5494111fe3bd867f28f804b6c5c1ad0703ec3d116de9bab3fa516 -F src/attach.c f26d400f3ffe2cdca01406bca70e5f58c5488bf165b4fc37c228136dfcf1b583 +F src/alter.c 0390ca1d69ec3626cfa9f153114b7ab233e6b2bada6a9eb91361ed385fe90deb +F src/analyze.c aabdf3769c7fd9954a8ec508eb7041ae174b66f88d12c47199fabbea9a646467 +F src/attach.c 4431f82f0247bf3aaf91589acafdff77d1882235c95407b36da1585c765fbbc8 F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7 F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6 -F src/btree.c 4ac832687f84d4c6c753f90274f7581f6c80a9ea69c2f045a5d07cb043cdf381 +F src/btree.c b5a74c39d3123dadd190019c0afadd7f62b43dfe0712a39e9d0ff2fe26be5d93 F src/btree.h 74d64b8f28cfa4a894d14d4ed64fa432cd697b98b61708d4351482ae15913e22 -F src/btreeInt.h 8be97d3939d626f734ec1b577efa4e6e186da00daf5b3227af199ca1c24cdd71 -F src/build.c a0cc68fe8172c0a31b54576f9c6c0fe6f7c82b1b5e1387afdd6a5a13132bc131 +F src/btreeInt.h 8ce1332edd89dfd2461d561ac10a0ab5601c8e06200cb5230596c3caaf54482e +F src/build.c 29fcc97af5197511788a571ed35a001eea472cbe3bcdbae88178e17fcafd4341 F src/callback.c 4c19af69835787bfe790ac560f3071a824eb629f34e41f97b52ce5235c77de1c F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c 2cce39df1a13e05b7633e6d21b651f21492471f991dd7b323a4ee4e7b7f0b7f1 +F src/ctime.c 026dbdcdbd8c3cde98a88483ee88310ff43150ab164ad768f12cc700a11495ad F src/date.c 15082566229d4b1e5f24fdb490bf9bcc68824b911d70e3573ef075a1b9e2d26f -F src/dbpage.c 30ff075a9a9156bd2cccb5c1ea579f2afbaa64989648e8c9d72cb4a3417f5136 +F src/dbpage.c 90661a87e1db8bfbc8d2ebbdcd3749651ddb287c555c07a28fb17c7c591ffb68 F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d -F src/delete.c b5f1716b4d723db48254ee0f896e362cd029e865e05414139ea7f539f3884e1d -F src/expr.c 7b9d66f012ba22bda14261c9a4d0600f4b800fad4cf5868f6a32355f037880cc +F src/delete.c a8e844af211a48b13b5b358be77a12c860c6a557c21990ad51a548e2536500ce +F src/expr.c 4907afcb86d72b5525d8767515ce425ec53c7a2d3664441b46cef5b376ee0cba F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c 06e4ac33031b02dde7130c12e79cddf4dc5cfa72b23d8e63a3c26878fc9c1d3c -F src/func.c a3407a6fbb0d4088d8d502e46f0ace63e0aeae7467ae23a9ca9815bbf9239761 -F src/global.c a3daa18a1696aadd94f18d37cbbdebf0bbdb827b8397a534f021cd56c15cd0f9 +F src/fkey.c d965ede15d8360c09ed59348940649ee647b192e784466837d7aefa836d1d91e +F src/func.c 8f72e88cccdee22185133c10f96ccd61dc34c5ea4b1fa9a73c237ef59b2e64f1 +F src/global.c e83ee571b79ee3adc32e380cf554cf1254bc43763d23786c71721fbcdfbbb965 F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 6c0641efc3636732a02f45defe358899d695c065fc26fbba1b2cab415b5b2e57 -F src/json.c 7ad9c97f36c43176bf4bd1397647dfdca540850554569c302b5380bd1e35ccf5 +F src/insert.c 173845e5a6bac96ae937409e4f876b631f26b31dabb9df8fd0eb3b130b2bb3a7 +F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c aa919a6a7884f8b34d7b791841b24d14b1b0ab43f45b3940f4851043b2855c0c -F src/main.c 0840cee6984034c7e73cc747a1562c7eaed4673694bf20b00980aaa0672c0405 +F src/loadext.c 853385cc7a604157e137585097949252d5d0c731768e16b044608e5c95c3614b +F src/main.c b91c7e71af6f33640c35b8239a285040aad8dfcfdaaf979152e743c0f8017ea8 F src/malloc.c a9127efdcef92d6934c6339ea9813075b90edc0ce2e5c723556381a3828fb720 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 -F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944 +F src/mem5.c 5a3dbd8ac8a6501152a4fc1fcae9b0900c2d7eb0589c4ec7456fdde15725a26c F src/memdb.c c2dc88f97c410eb68a24468344b65526685e18354ddfd15906750c1eaf9dc2dd F src/memjournal.c 8bd50ae6d9c6d34b3a96cc3b4f567f9935dc358444d872ab48901a8c11ad82a6 F src/msvc.h 3a15918220367a8876be3fa4f2abe423a861491e84b864fb2b7426bf022a28f8 @@ -533,39 +551,39 @@ F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f9 F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab -F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8 +F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 -F src/os_unix.c df6142ed776b5e52c7c3106767283be2d510b63d72beb3b205898462f340a176 +F src/os_unix.c 2df2b33db88f00af13805d4573ee126bc5973f9e3b91d03c575fa7ba64e7dc41 F src/os_win.c a8ea80037e81127ca01959daa87387cc135f325c88dc745376c4f760de852a10 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c e4f4c1e07b2cc4fa44fc0bd51957ca6066f6b8c0b0e0388f11a6728b50d8f4e6 -F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f -F src/parse.y 0f02b27cdaa334441463153fff3ceb780fea006ab53ffd6ef566d4468f93e924 -F src/pcache.c 0aab73936341fad83d107cf62c6a7bc2d2d5fb9aaec8c3ce61e19fc18e4560fc +F src/pager.c 74596fc3d5d8a50de32c37225fc300cccd5ea27ea303c5f4b845d6572f999c5f +F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 +F src/parse.y 8e67d820030d2655b9942ffe61c1e7e6b96cea2f2f72183533299393907d0564 +F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65 -F src/pragma.c 7c024d690a3dc93f61830f11f900e4af2357f31d081b0c79099ca5e28919cba7 -F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad +F src/pragma.c d1aead03e8418ff586c7cfca344c50a914b8eb06abd841e8e91a982d823671da +F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c c62820c15dcb63013519c8e41d9f928d7478672cc902cfd0581c733c271dbf45 -F src/printf.c 05d8dfd2018bc4fc3ddb8b37eb97ccef7abf985643fa1caebdcf2916ca90fa32 +F src/printf.c e99ee9741e79ae3873458146f59644276657340385ade4e76a5f5d1c25793764 F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c -F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e +F src/resolve.c f0d663c9b1ceeb3e7d262ede872dd3b24b323a7cc11d84c05a39d962e7d64b07 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c ba0cd3d264fa788c8d65f063d5ec3e1d65b0dac206c822a564a59ba550210cfc -F src/shell.c.in e2bc31eca23a1d1d2aaea02dcfde6dfe0ee96bdfa6cbcc36c79f02e457bef18c -F src/sqlite.h.in b93deee892f1bc4030e5c8712df9e21d786a1bf8e921ab8dc987eaf1e44c676f +F src/select.c a5bff2d563375cbbc6a542e5b61fda2b0833c4ccbae8fef8bb789932995aff68 +F src/shell.c.in 2b85128ca8ea13fc2dc32f971d628d9f688a324a30f469619b817ce490764fcb +F src/sqlite.h.in 01573eae96721f2a8ee2a9e3b7140ceeba2e9c44350911890b89b8ff0dcf6781 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 -F src/sqliteInt.h 272c5ca0a16a1963c0c956524c615a27af57be950b97a395d39cb9fd9b31fc4e +F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d +F src/sqliteInt.h 059d5a017ebf488c7484f79ea507f56b2bf4bb700f340abf91c5d2227869f275 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 -F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 +F src/status.c 4a3da6d77eeb3531cb0dbdf7047772a2a1b99f98c69e90ce009c75fe6328b2c0 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 1f6673991147bc2cecc08a40d22f9803b84c805b24b499fe727f392256f73474 -F src/test1.c 87fda59eea3ac1eba1baef37c1967565cb1b8d6d264649f2e57f252ca5989914 +F src/tclsqlite.c 4e64ba300a5a26e0f1170e09032429faeb65e45e8f3d1a7833e8edb69fc2979e +F src/test1.c 1356984e97bff07e4a8cc3863e892f05b3348678a74783bb6f350b76316736f1 F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 -F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159 +F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d F src/test6.c ae73a3a42bbc982fb9e301b84d30bda65a307be48c6dff20aba1461e17a9b0ce F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010 @@ -616,21 +634,21 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 6661a9fa660ecbd3ac0df1acd2ec788b3a8122b4316022bcdaf476ea6754a8de -F src/treeview.c a84b57d15e46007d8b1ae249344b3f0b7f3c62def908b98baaa54935a57c8476 -F src/trigger.c 5fc3cde35cc4de510be68bb2db4dcff0ce0e1625f43e28a0920be9a6f010cd3f -F src/update.c f875b0d59da5c3055a0b2ac20560e1650229c6787e78de5e9836267b5cbb8359 +F src/tokenize.c 36eb0799e487759bbe73e5742b82ee676f06cea2515ff578d03c59a74ccf2d6c +F src/treeview.c 07787f67cd297a6d09d04b8d70c06769c60c9c1d9080378f93929c16f8fd3298 +F src/trigger.c 61bea163b1fa3039bc572ed8312461b978e5c527e5301f302b078f4c1ccdec6a +F src/update.c c52a7991bece0453d22c77c08469512ee2f1391c12503fd347d1c939220c5877 F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 602fe229f32a96ceccae4f40824129669582096f7c355f53dbac156c9fecef23 -F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 -F src/vdbe.c dafb1c9c2f58555bf6f384fe049a4b4fc2408b500216b37769f8faa82a46e57a -F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe -F src/vdbeInt.h b45599a2b59f1ce042512ab6786b0b82a8cf3002f6b0fa60b4834e2cd3ac61d8 -F src/vdbeapi.c 8863ffb5a7bac42fe9a68aaa3526ee29fc18fb02a9b27188b756de41e33856e9 -F src/vdbeaux.c 0d7659fe8cb38ce86092b9bc5131c99a834a04eb78745e54acb77d79d7af2fb5 +F src/vacuum.c bb346170b0b54c6683bba4a5983aea40485597fdf605c87ec8bc2e199fe88cd8 +F src/vdbe.c 3df15f0f2cd9d8faeeea18398a62f16c07fda2bfd8d726186f07508ad69b40e2 +F src/vdbe.h 07641758ca8b4f4c6d81ea667ea167c541e6ece21f5574da11e3d21ec37e2662 +F src/vdbeInt.h 2cad0aeeb106371ed0e0946bab89f60627087068847afc2451c05056961c18da +F src/vdbeapi.c 602610f1252d59cd69742f78a1e2f6fbae40a4b407f5506a6a7b869b0df08ff2 +F src/vdbeaux.c 328b866880e67526300aa2361c5e50beb3d97dd8746db37dd4aa0135aea60e4d F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd -F src/vdbemem.c 2db321ed3722f78c7f1e6743ff24ae68717fcc44c157817ae7eaf0a83439af98 +F src/vdbemem.c 5ebf05c0182addedb1607ade848e1c83cef40981df94d1abfab0c59288c6064f F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c @@ -639,16 +657,16 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c be30bf0151f5cc77b2d75a664f2ab60632974165ad2bab6df9deca2fc3284264 -F src/whereInt.h 15d2975c3b4c193c78c26674400a840da8647fe1777ae3b026e2d15937b38a03 -F src/wherecode.c eae0a5778fa0ffe398cb4ecb55d3e20ee9b85a985c8c1aa9cea03d0a3069c213 -F src/whereexpr.c 2a71f5491798460c9590317329234d332d9eb1717cba4f3403122189a75c465e -F src/window.c 8546f510dfff88e4627f004969940c648acf91c13aa155229849f926a8137b9c +F src/where.c e7b7c9d37f4f8d14d4d05b20e57ae43712a51ffed7d45f547a23f9c42ee483dd +F src/whereInt.h b48ca529ffe293c18cbfa8326af18a09e39910de66fb3e96ef788c7cbf8ef3a7 +F src/wherecode.c 0b09abfcb88c61c6a6984a3e065786631ff35495e9bdf865e6b74ab0a1299c5b +F src/whereexpr.c 55a39f42aaf982574fbf52906371a84cceed98a994422198dfd03db4fce4cc46 +F src/window.c fff1b51757438c664e471d5184634e48dcdf8ea34b640f3b1b0810b1e06de18c F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 -F test/affinity3.test eecb0dabee4b7765a8465439d5e99429279ffba23ca74a7eae270a452799f9e7 +F test/affinity3.test b5c19d504dec222c0dc66642673d23dce915d35737b68e74d9f237b80493eb53 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 -F test/aggnested.test cc47afa5e11e0d6771a85a4993fa6ff721480ddb53ea538ec3fdbafb720bd505 +F test/aggnested.test 7269d07ac879fce161cb26c8fabe65cba5715742fac8a1fccac570dcdaf28f00 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 F test/alter.test 313073774ab5c3f2ef1d3f0d03757c9d3a81284ae7e1b4a6ca34db088f886896 @@ -665,11 +683,12 @@ F test/alterfault.test 289067108947bedca27534edd4ff251bcd298cf84402d7b24eaa37493 F test/alterlegacy.test f38c6d06cda39e1f7b955bbce57f2e3ef5b7cb566d3d1234502093e228c15811 F test/altermalloc.test 167a47de41b5c638f5f5c6efb59784002b196fff70f98d9b4ed3cd74a3fb80c9 F test/altermalloc2.test 17fb3724c4b004c469c27dc4ef181608aa644555fbd3f3236767584f73747c81 -F test/altermalloc3.test 55e606edf4b0acfbbd851ddfe93cfdddfae43d103644dcfd6008ae4ab3c44adf +F test/altermalloc3.test 8531b3086f0a7889f43971a579a8c81832d5123f4703d8c86b89ba1136c63b9a F test/alterqf.test ff6c6f881485c29ed699b8ef4774864ca1b0c01a6c08f5cdd624a008e4b40fca F test/altertab.test 7273b8506eab46342be016af78028df49f3bd99037412f997a8f1011b37a6912 F test/altertab2.test 62597b6fd08feaba1b6bfe7d31dac6117c67e06dc9ce9c478a3abe75b5926de0 -F test/altertab3.test 5929f522fd6fd708396ad9f317d4af9ff1a93e460df85bb1d54d4499eeb94960 +F test/altertab3.test 6c432fbb9963e0bd6549bf1422f6861d744ee5a80cb3298564e81e556481df16 +F test/altertrig.test fb5951d21a2c954be3b8a8cf8e10b5c0fa20687c53fd67d63cea88d08dd058d5 F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 547bb700f903107b38611b014ca645d6b5bb819f5210d7bf39c40802aafeb7d7 F test/analyze3.test 4440c4932247adb2b4e0c838f657c19dc7af4f56859255436dc4e855f39b9324 @@ -678,7 +697,7 @@ F test/analyze5.test fa5131952303ac4146aba101b116b9c8cb89e2637531c334a6df7f7d19d F test/analyze6.test 028f5bdfc9e5b5294768fa9a7185b8cd1d019aa7aab5b2f8ee42d7271d9a3b28 F test/analyze7.test 079d17c495e396bdbd6cc6a083112788a6fbfb3b95c42e760e4270a53c9ead8f F test/analyze8.test 29ef237d8a59b39cc31c3310134fefe96a690b195e3deed5ecb652839089f15c -F test/analyze9.test 162cd40d713310fbe7a63a5ecff5d3a013777d86809618838177d9b2af4dd09c +F test/analyze9.test 30e1cb99336045a384a11d97900720184333c88174b3b89bc07444ea39e7df19 F test/analyzeC.test 1111830ad355d29a294a5dda654dd5f6a8622c6a223a4f7b7b3d091df7a7a42b F test/analyzeD.test 485f621cfd2ef0a8f8ac79672586651bfa495bd899db50461bb4b558400ab3c1 F test/analyzeE.test 69d130f9ba78c9853dcd5a18317e81f462a72d704cec0c4c30afb220213acd29 @@ -690,7 +709,7 @@ F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7 F test/async4.test 1787e3952128aa10238bf39945126de7ca23685a F test/async5.test 383ab533fdb9f7ad228cc99ee66e1acb34cc0dc0 -F test/atof1.test 10049623e77006691c4c2978c1dc8a3f75276377a53417811aa85bda7493f963 +F test/atof1.test 1f24a6f16e44892848298bd8e488159d4a60cf47be003d32747a136103c9bbac F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d061 F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c @@ -704,10 +723,10 @@ F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec F test/autoinc.test 997d6f185f138229dc4251583a1d04816423dddc2fc034871a01aeb1d728cb39 -F test/autoindex1.test b2a87aca5c3717264ef431025c1eb855f6012c9694a3c85c678c762aa0dedf58 +F test/autoindex1.test cdc336e80cfd586c0e09426d58bec412db7527ca22dfabe88eab690e3acbb406 F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df F test/autoindex3.test 2d13958a5617e987624a428d7aed91bf51f322b49b476e3573fadec697ce6da5 -F test/autoindex4.test 75cb1191a552b8201351f5a50d160fcb9387a0fbbfb820c77798bfee7da3f8cf +F test/autoindex4.test 5df39313526b6f22a26bd119bbd97ca69f28386ab3c671fc10568d921c41eb08 F test/autoindex5.test 2ee94f033b87ca0160e08d81034c507aff8e230df2627f0304fa309b2fee19a3 F test/autovacuum.test 00671369bbf96c6a49989a9425f5b78b94075d6a4b031e5e00000c2c32f365df F test/autovacuum2.test 76f7eb4fe6a6bf6d33a196a7141dba98886d2fb53a268d7feca285d5da4759d7 @@ -731,7 +750,8 @@ F test/bestindex4.test 3039894f2dad50f3a68443dffad1b44c9b067ac03870102df1ce3d9a4 F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb75dc98d F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4 F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e -F test/bestindex8.test abd0016fc04f19dc382976750b06df5463d2757e11e78a8ba7d7dc50671f3337 +F test/bestindex8.test 333ad8c6a554b885a49b68c019166eda92b05f493a92b36b0acdf7f766d04dad +F test/bestindex9.test bf2eb8556e8d5c00ef3ee18c521751cd03c1b55454b6e7683b4c6742e3131b23 F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -751,7 +771,7 @@ F test/boundary3.tcl 23361e108a125dca9c4080c2feb884fe54d69243 F test/boundary3.test 56ef82096b4329aca2be74fa1e2b0f762ea0eb45 F test/boundary4.tcl 0bb4b1a94f4fc5ae59b79b9a2b7a140c405e2983 F test/boundary4.test 89e02fa66397b8a325d5eb102b5806f961f8ec4b -F test/btree01.test e08b3613540145b353f20c81cb18ead54ff12e0f +F test/btree01.test fef17d9e999ac4f04095948e3438fbe674f4e07bb2c63bb1cad41d87baee077f F test/btree02.test 7555a5440453d900410160a52554fe6478af4faf53098f7235f1f443d5a1d6cc F test/btreefault.test c2bcb542685eea44621275cfedbd8a13f65201e3 F test/busy.test 510dc6daaad18bcbbc085bcc6217d6dc418def5e73f72ce1475eea0cb7834727 @@ -769,6 +789,7 @@ F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212 F test/cast.test 336fa21989b5170ebcaf90c24266be22dd97b3e23d1fad5ecf6ad4efb04c4423 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b +F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9 F test/check.test 56e4ed457e9f8683b9fc56f5b964f461f6e8a8dd5a13f3d495408215d66419ed F test/checkfault.test da6cb3d50247169efcb20bdf57863a3ccfa1d27d9e55cd324f0680096970f014 F test/chunksize.test 427d87791743486cbf0c3b8c625002f3255cb3a89c6eba655a98923b1387b760 @@ -776,7 +797,7 @@ F test/close.test eccbad8ecd611d974cbf47278c3d4e5874faf02d811338d5d348af42d56d64 F test/closure01.test 9905883f1b171a4638f98fc764879f154e214a306d3d8daf412a15e7f3a9b1e0 F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91 F test/collate1.test 71a6f27fdc93a92f14d8ab80c05e1937656a5a03197e1a10157314554d630ce8 -F test/collate2.test 9aaa410a00734e48bcb27f3872617d6f69b2a621 +F test/collate2.test 471c6f74573382b89b0f8b88a05256faa52f7964f9e4799e76708a3b1ece6ba4 F test/collate3.test 89defc49983ddfbf0a0555aca8c0521a676f56a5 F test/collate4.test c953715fb498b87163e3e73dd94356bff1f317bd F test/collate5.test 65d928034d30d2d263a80f6359f7549ee1598ec6 @@ -813,11 +834,11 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test a17bbf54fdde78d43cf3cc34b0057719fd4a173a3d824285b67dc5257c064c7b F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test 21a951d1eb09120f3c1561af5bac30ed49be2d9dfcad039f71759c5d9e28a349 +F test/corruptL.test ecce40d7b9b909a670a42a45d86e30d927735d7e7f09041af438b19529d35532 F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 F test/corruptN.test 60b5a62944b4f0029ba07edaa5fd8e670539d6b0a8d99db26c068d435675cbfe F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 -F test/count.test 013d64569c15563c59472d17ec4ddc681034aaee65e8b01880bc88315fd4c673 +F test/count.test cd4bd531066e8d77ef8fe1e3fc8253d042072e117ccab214b290cf83f1602249 F test/countofview.test e17d6e6688cf74f22783c9ec6e788c0790ee4fbbaee713affd00b1ac0bb39b86 F test/coveridxscan.test f35c7208dedc4f98e471c569df64c0f95a49f6e072d8dc7c8f99bdee2697de1b F test/crash.test fb9dc4a02dcba30d4aa5c2c226f98b220b2b959f @@ -859,7 +880,7 @@ F test/descidx1.test edc8adee58d491b06c7157c50364eaf1c3605c9c19f8093cb1ea2b6184f F test/descidx2.test a0ba347037ff3b811f4c6ceca5fd0f9d5d72e74e59f2d9de346a9d2f6ad78298 F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a1999b926 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e -F test/distinct.test 3e4210ef9cd1985aeec44939ad912c4621fbea9bb4a9c565696cebfe184b2ec5 +F test/distinct.test a7687c2fb50c93f6a486936c51439a93221c6e1188f9bc7b27b3ec26f9c58b1e F test/distinct2.test cd1d15a4a2abf579298f7161e821ed50c0119136fe0424db85c52cf0adc230d1 F test/distinctagg.test d76ef2e91fe810630c176d6bd0a58c14d5851c3125f0a1d977db87ba76359639 F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 @@ -877,12 +898,12 @@ F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5e F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e F test/e_reindex.test 2b0e29344497d9a8a999453a003cb476b6b1d2eef2d6c120f83c2d3a429f3164 F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 -F test/e_select.test c5425a423da06d0494119db8361ebfc6de302929f7546ca596d56224137e0360 +F test/e_select.test 89fa483f68d868f1be3d6f56180ed42d979979c266e051bf8b5e66a251e6b44a F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f3b30e9b41e4 F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 F test/e_uri.test a3d30c7d6d7700150055b6660ee42da7f05dee2c150ce08bf6a9ededc26468cc -F test/e_vacuum.test 0d8832a2ce92350db0d0cff47b419465fd9772562e1f77ff7d9478c07a4980d2 +F test/e_vacuum.test 89fc48e8beee2f9dfd6de1fbb2edea6542dae9121dc0fbe6313764169e742104 F test/e_wal.test ae9a593207a77d711443ee69ffe081fda9243625 F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8 F test/e_walckpt.test 28c371a6bb5e5fe7f31679c1df1763a19d19e8a0 @@ -892,14 +913,14 @@ F test/enc.test 9a7be5479da985381d740b15f432800f65e2c87029ee57a318f42cb2eb43763a F test/enc2.test 848bf05f15b011719f478dddb7b5e9aea35e39e457493cba4c4eef75d849a5ec F test/enc3.test 6807f7a7740a00361ca8d0ccd66bc60c8dc5f2b6 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test bfe979eb1f4b8ab7a3bd7db6d16c2e6c6be0e5a3aada2227716f3fd3a9d76b69 +F test/eqp.test 473aea9599b4b7af46614b55198cd78167e4eccd48e60812a40db47c5c41dea9 F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/eval.test 73969a2d43a511bf44080c44485a8c4d796b6a4f038d19e491867081155692c0 F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650bf0747 F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/expr.test e1afcdb1038e4d3fa67a3df323347c38750946e2e1b4e385bdc75d26284f2dac +F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 F test/exprfault.test 497cc0b8fe6a677f49b55cb485e040f709ec2834b84f25912fe9c2dfeeda33db F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9 @@ -919,7 +940,7 @@ F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d F test/fkey5.test 6727452e163a427147e84e739da18713da553d79f9783559b04fdcd36d5c7421 F test/fkey6.test d078a1e323a740062bed38df32b8a736fd320dc0 F test/fkey7.test 64fb28da03da5dfe3cdef5967aa7e832c2507bf7fb8f0780cacbca1f2338d031 -F test/fkey8.test 48ef829d63f5f7b37aabd4df9363ac05f65539d1da8c4a44251631769d920579 +F test/fkey8.test 51deda7f1a1448bca95875e4a6e1a3a75b4bd7215e924e845bd60de60e4d84bf F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749 F test/fordelete.test ba98f14446b310f9c9d935b97ec748753d0144a28b356ba30d1f4f6958fdde5c F test/format4.test eeae341953db8b6bda7f549044797c3278a6cc345d11ada81471671b654f8ef4 @@ -983,12 +1004,12 @@ F test/fts3b.test c15c4a9d04e210d0be67e54ce6a87b927168fbf9c1e3faec8c1a732c366fd4 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c F test/fts3conf.test c84bbaec81281c1788aa545ac6e78a6bd6cde2bdbbce2da261690e3659f5a76b -F test/fts3corrupt.test 43c6c89b994e90997590ece4dfa9c9325c9b61cddd7c97e158498da8b1de79f8 +F test/fts3corrupt.test 6732477c5ace050c5758a40a8b5706c8c0cccd416b9c558e0e15224805a40e57 F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0 F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cdb764b491f -F test/fts3corrupt4.test 799ff994b964fed7201be6b6b62c7ff2ef7bb3da6c02b9eaf0d96a5a4d9b6ca3 +F test/fts3corrupt4.test 589e043d1114ea02c83530e459ef5c2d6adce63094e72826ed3bceb02e795116 F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5 -F test/fts3corrupt6.test 657b4b8e5791d8d4adc93c90588fb25f1c7346544dd877c6c298a0746749146d +F test/fts3corrupt6.test f417c910254f32c0bc9ead7affa991a1d5aec35b3b32a183ffb05eea78289525 F test/fts3cov.test 7eacdbefd756cfa4dc2241974e3db2834e9b372ca215880e00032222f32194cf F test/fts3d.test 2bd8c97bcb9975f2334147173b4872505b6a41359a4f9068960a36afe07a679f F test/fts3defer.test f4c20e4c7153d20a98ee49ee5f3faef624fefc9a067f8d8d629db380c4d9f1de @@ -1003,11 +1024,11 @@ F test/fts3expr3.test c4d4a7d6327418428c96e0a3a1137c251b8dfbf8 F test/fts3expr4.test f5b2832549f01b1f7f73389fa21d4b875499bc95bf7c8b36271844888c6a0938 F test/fts3expr5.test a5b9a053becbdb8e973fbf4d6d3abaabeb42d511d1848bd57931f3e0a1cf983e F test/fts3f.test 8c438d5e1cab526b0021988fb1dc70cf3597b006a33ffd6c955ee89929077fe3 -F test/fts3fault.test 798e45af84be7978ca33d5bdc94246eb44724db24174b5d8e9b1ac46c57fb08d -F test/fts3fault2.test 6a17a11d8034b1c4eca9f3091649273d56c49ff049e2173df8060f94341e9da0 +F test/fts3fault.test f4e1342acfe6d216a001490e8cd52afac1f9ffe4a11bbcdcb296129a45c5df45 +F test/fts3fault2.test 7b2741e5095367238380b0fcdb837f36c24484c7a5f353659b387df63cf039ec F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641 F test/fts3fuzz001.test e3c7b0ce9b04cc02281dcc96812a277f02df03cd7dc082055d87e11eb18aaf56 -F test/fts3join.test b285c919559af5b093c51abb2c07ce7ec0156dbc9573f444b78dabd9f3040db2 +F test/fts3join.test ee25def5e763ea8879c19e74f862d5191410ccc7259338887a3685e97f512662 F test/fts3malloc.test b0e4c133b8d61d4f6d112d8110f8320e9e453ef6 F test/fts3matchinfo.test aa66cc50615578b30f6df9984819ae5b702511cf8a94251ec7c594096a703a4a F test/fts3matchinfo2.test 00144e841704b8debfcdf6097969cd9f2a1cf759e2203cda42583648f2e6bf58 @@ -1056,7 +1077,7 @@ F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 F test/func5.test 863e6d1bd0013d09c17236f8a13ea34008dd857d87d85a13a673960e4c25d82a F test/func6.test 9cc9b1f43b435af34fe1416eb1e318c8920448ea7a6962f2121972f5215cb9b0 -F test/func7.test b9e2a1a30a8562b00841b4a21a5d2d81754fa3ab99275fd71fd5279287b44b1c +F test/func7.test adbfc910385a6ffd15dc47be3c619ef070c542fcb7488964badb17b2d9a4d080 F test/fuzz-oss1.test 514dcabb24687818ea949fa6760229eaacad74ca70157743ef36d35bbe01ffb0 F test/fuzz.test 4608c1310cff4c3014a84bcced6278139743e080046e5f6784b0de7b069371d8 F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1 @@ -1064,8 +1085,8 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c e34696a5db46738118b2efd14fb71f8458ecf0f482df8bbae18fa1d64db9ab7b -F test/fuzzdata1.db d36e88741b4f23bcbaaf55b006290669d03c6c891cf13c7b3a53bc1b097b693f +F test/fuzzcheck.c 7b501d55631c2d759e0bed02ed329904a35690fc6563d7b6cc69b7788a024f26 +F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e42ed2 @@ -1076,6 +1097,7 @@ F test/fuzzdata8.db ca9a97f401b06b0d5376139ec7e1f9e773e13345a9a2d9ccc0032cdbfede F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc +F test/fuzzinvariants.c c1c7ae6be4c5fe4e5b845ce2b166c17a3d0f8ef545ab0248c9990505010070da F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c F test/gencol1.test cc0dbb0ee116e5602e18ea7d47f2a0f76b26e09a823b7c36ef254370c2b0f3c1 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 @@ -1087,7 +1109,7 @@ F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 F test/icu.test 716a6b89fbabe5cc63e0cd4c260befb08fd7b9d761f04d43669233292f0753b1 F test/ieee754.test b0945d12be7d255f3dfa18e2511b17ca37e0edd2b803231c52d05b86c04ab26e F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 -F test/in.test 15de58ee017f43d36390812e9a51217d1b2db7758f97d0df48296ef178ea560b +F test/in.test 55503d3633c434120d8b2d5966a34f0d9a55393a589050b1366afe4b188093c7 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 F test/in4.test fdd1d8134da8376985c2edba6035a2de1f6c731524d2ffa651419e8fe2cd1c5a @@ -1095,7 +1117,7 @@ F test/in5.test b32ce7f4a93f44c5dee94af16886d922cc16ebe33c8e1765c73d4049d0f4b40f F test/in6.test f5f40d6816a8bb7c784424b58a10ac38efb76ab29127a2c17399e0cbeeda0e4b F test/incrblob.test c9b96afc292aeff43d6687bcb09b0280aa599822 F test/incrblob2.test a494c9e848560039a23974b9119cfc2cf3ad3bd15cc2694ee6367ae537ef8f1f -F test/incrblob3.test d47be78a46da142dd60b93772db6e03936a8a36a3b6129dff8d11923dfdc6d63 +F test/incrblob3.test 67621a04b3084113bf38ce03797d70eca012d9d8f948193b8f655df577b0da6f F test/incrblob4.test 21a52a6843a56cdcce968c6a86b72a7066d0e6ba F test/incrblob_err.test 89372a28f1d98254f03fed705f9efcd34ef61a674df16d2dbb4726944a2de5e9 F test/incrblobfault.test de274b1e329169c2c3438f9528994807ea8201ebf38ae9f157d34bf3ec0cc549 @@ -1109,10 +1131,10 @@ F test/index2.test f835d5e13ca163bd78c4459ca15fd2e4ed487407 F test/index3.test 51685f39345462b84fcf77eb8537af847fdf438cc96b05c45d6aaca4e473ade0 F test/index4.test ab92e736d5946840236cd61ac3191f91a7856bf6 F test/index5.test 8621491915800ec274609e42e02a97d67e9b13e7 -F test/index6.test 5a7ab531c692ff3b3d139ef8ea6709fab40f9c6862ed418b4976752a5481da3a +F test/index6.test b376a648e85aa71c50074382784e6cb0c126ec46e43d1ad15af9a4d234c52e65 F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6eb3 F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3 -F test/index9.test 0aa3e509dddf81f93380396e40e9bb386904c1054924ba8fa9bcdfe85a8e7721 +F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 F test/indexexpr1.test 3360c2a29a8844e7c4b13293567025281257f9e13a31854cfff6959cede11502 F test/indexexpr2.test 2c7abe3c48f8aaa5a448615ab4d13df3662185d28419c00999670834a3f0b484 @@ -1139,12 +1161,22 @@ F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9 -F test/join.test 25cf0ac11c3b81fedfd166f9062166bdb39dea92f5a7c16cacbf6dc1f7f67020 -F test/join2.test 9bdc615841b91c97a16d68bad9508aea11fa0c6b34e5689847bcc4dac70e4990 +F test/join.test 5c7f917aa219a125d1df517d3df384c79f74860fdfb2e48c0a599f1e2b3e9ed8 +F test/join2.test 466b07233820f5deee66a6c3bf6e4500c8bbf7b83649e67606f5f649c07928c0 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 -F test/join5.test e1ada8581c279fa210ce6c301bcd286cf0b42bfd0f0a2036b1ee0301e245ab75 +F test/join5.test d22b6cba8fb59ab3f1c82701434c360705eb12d4ce200c449f37b018fc47681a F test/join6.test f809c025fa253f9e150c0e9afd4cef8813257bceeb6f46e04041228c9403cc2c +F test/join7.test 2268dcbb54b724391dda3748ea95c60d960607ffeed67885675998e7117697f6 +F test/join8.test ef5fb09a7ce6b59addb8bd16e11607db6c44a0afcac5774a5dc03193fd0a1df5 +F test/join9.test 9056ddd3b0c0f4f9d658f4521038d9a37dc23ead8ca9a505d0b0db2b6a471e05 +F test/joinA.test 7eab225dc1c1ab258a5e62513a4ed7cabbd3db971d59d5d92f4fb6fa14c12f6a +F test/joinB.test 1b2ba3fc8568b49411787fccbf540570c148e9b6a53a30f80691cb6268098ded +F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f207 +F test/joinD.test 1a430af8dac5b68663f13df534ffe98775e582bac2305b80f1e8eb4ab778672a +F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b +F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 +F test/joinH.test e67d1d6a8c7141caf981a07386caa7fda0362baa09e03669f9a4fbee812806d0 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e @@ -1201,7 +1233,7 @@ F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test 25897506da0098cea09b302ff432b0fb6d8002773c1e0fc9732aa8b444bfd455 F test/mallocL.test fb311ff80afddf3b1a75e52289081f4754d901dc F test/mallocM.test 78bbe9d3da84a5c679123cdb40d7b2010b18fc46e13897e4f253c6ba6fbff134 -F test/malloc_common.tcl aac62499b76be719fac31e7a3e54a7fd53272e7f +F test/malloc_common.tcl 806c50379cf4fa65008cd4d5af18273e5dac8ab62d1d4316c76aa2ecd2e54018 F test/malloctraceviewer.tcl b7a54595270c1d201abf1c3f3d461f27eaf24cdef623ad08a0fe5e411264c8a9 F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f @@ -1212,7 +1244,7 @@ F test/memjournal2.test 89a4e0d1084170a281efa4d54c2677599f986f44227f98f7dfae2828 F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 F test/memsubsys1.test 9e7555a22173b8f1c96c281ce289b338fcba2abe8b157f8798ca195bbf1d347e F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08 -F test/merge1.test 0ade470d77b689c4a64dc7f736527fcd893140bcafa70af8f7b98523cfb83f16 +F test/merge1.test 2de6d6ef8d25402764b1aab49d8f9d7f89208c89a6674e437f76de4c812157b8 F test/minmax.test fe638b55d77d2375531a8f549b338eafcd9adfbd2f72df37ed77d9b26ca0a71a F test/minmax2.test cf9311babb6f0518d04e42fd6a42c619531c4309a9dd790a2c4e9b3bc595e0de F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 @@ -1285,7 +1317,7 @@ F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035c F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 68b6dcd2667acdc643140d502c0b6c503abe444495cf5d16aa3a4f0391604020 +F test/permutations.test cf5f31bab83a452288b2a050880152cdf99d62e9aab71948268d549debcc6942 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f F test/pragma.test cae534c12a033a5c319ccc94f50b32811acdef9f67bf19a82ff42697caccd69f F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f @@ -1310,7 +1342,7 @@ F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/recover.test ccb8c2623902a92ebb76770edd075cb4f75a4760bb7afde38026572c6e79070d -F test/regexp1.test 0c3ff80f66b0eff80e623eb5db7a3dad512095c573d78ac23009785f6d8f51ce +F test/regexp1.test 4a44e014664a109bbb1c37d29d9b61ca5aa5a7f49cc564c95208a80f818e3377 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d F test/releasetest_data.tcl 11ba48a21ed1c808147b0e77c6e93d204577f4327ffe6d7c3b34cd3c01eac3a2 @@ -1325,9 +1357,9 @@ F test/round1.test 768018b04522ca420b1aba8a24bd76091d269f3bce3902af3ec6ebcee41ab F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test e29025be95baf6b32f0d5edef59a7633028325896a98f1caa8019559ca910350 -F test/rowvalue.test 228b312f8526ed000ecda559a6a9adf30aa2d79dcb12afa5c04eebaafcf55eae +F test/rowvalue.test baf4fa3ec1a8c1c920c3faa5fd25959cb454bbd99ac8960397c34549d9fc4abe F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b -F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256 +F test/rowvalue3.test 1347e25ca11c547c5a6ff0cc5626f95aa9740e9275bfaec096029f57cb2130ce F test/rowvalue4.test 441e7e366ac6d939a3a95a574031c56ec2a854077a91d66eee5ff1d86cb5be58 F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3068ea7 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 @@ -1356,9 +1388,9 @@ F test/schema6.test e4bd1f23d368695eb9e7b51ef6e02ca0642ea2ab4a52579959826b5e7dce F test/schemafault.test 1936bceca55ac82c5efbcc9fc91a1933e45c8d1e1d106b9a7e56c972a5a2a51e F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 -F test/select1.test 3d23f66bf9ba77570acfe2ca5f1540ece17037cc64ab1a00efec9758ac29c268 +F test/select1.test 692e84cfa29c405854c69e8a4027183d64c22952866a123fabbce741a379e889 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 -F test/select3.test c49fbb758903f3718e2de5aa4655eda4838131cbea24a86db908f8b6889aa68c +F test/select3.test 399a0b23f8618bfe07ea24fb67f2952f620d6eba662bccabf6f4f71c2d89e6c9 F test/select4.test f0684d3da3bccacbe2a1ebadf6fb49d9df6f53acb4c6ebc228a88d0d6054cc7b F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f390ae F test/select6.test 9b2fb4ffedf52e1b5703cfcae1212e7a4a063f014c0458d78d29aca3db766d1f @@ -1388,14 +1420,14 @@ F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test f7a2ef8260aa01f20be3185118213b1ae70518fdcd2105f3e25b021b5ca800ac -F test/shell2.test 7a3a23a9f57b99453f1679b1fe8072cb30e382a622874c0c4d97695fadb0a787 -F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c -F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759 -F test/shell5.test 78a7a8516b1e7de560748881424f621321549023d3e5f7ed2e1c56497f64c06c +F test/shell1.test e4b4de56f454708e0747b52915135baa2cbfec4965406d6eaf02a4a5c22a9880 +F test/shell2.test c536c2aab4852608f8a606262330797abc4d964a4c2c782a7760f54ea1f17a6a +F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a +F test/shell4.test 7dc8a515705bc093d8ffe381670e8fa7a969661e8ed177c35c847e3c6dfc35e2 +F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f -F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae +F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915 F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@ -1449,11 +1481,11 @@ F test/subquery.test 3a1a5b600b8d4f504d2a2c61f33db820983dba94a0ef3e4aedca8f0165e F test/subquery2.test 90cf944b9de8204569cf656028391e4af1ccc8c0cc02d4ef38ee3be8de1ffb12 F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303 F test/substr.test a673e3763e247e9b5e497a6cacbaf3da2bd8ec8921c0677145c109f2e633f36b -F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8 +F test/subtype1.test 45c85632abd38f7ea9b33f17448d966d67550f552e0822bab74576814d0d1718 F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12 F test/swarmvtab.test 250231404fcac88f61a6c147bb0e3a118ed879278cd3ccb0ae2d3a729e1e8e26 F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27cbd0aa5c -F test/swarmvtab3.test 247aa38b6ebd2b99db2075847ae47e789ac34f1c2ab5c720dfcffd990004c544 +F test/swarmvtab3.test 41a3ab47cb7a834d4e5336425103b617410a67bb95d335ef536f887587ece073 F test/swarmvtabfault.test 8a67a9f27c61073a47990829e92bc0c64420a807cb642b15a25f6c788210ed95 F test/symlink.test 72b22238d4405ba34df8e60b335d290a3b1129fd5c260835c944c1e4e77288a9 F test/symlink2.test 9531f475a53d8781c4f81373f87faf2e2aff4f5fb2102ec6386e0c827916a670 @@ -1473,7 +1505,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test d2940417496e2b9548e01d09990763fbe88c316504033256d51493e1f1a5ce6a F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl f577d040078dd4fb56ea1ccaa71f6bfd0ebf0bd0ac373304db4269b5474f9cb7 +F test/tester.tcl 76771269dcc20b2c2d1d6f1175dd50d1eebddc004aebac865483f1829a5cd398 F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1627,7 +1659,7 @@ F test/tkt3911.test 74cd324f3ba653040cc6d94cc4857b290d12d633 F test/tkt3918.test ea78bf164e4d55cbde0d83c671ef6fbe930a0032 F test/tkt3922.test f26be40ab4fe6c00795629bd2006d96e270d9b1a F test/tkt3929.test cdf67acf5aa936ec4ffead81db87f8a71fe40e59 -F test/tkt3935.test e15261fedb9e30a4305a311da614a5d8e693c767 +F test/tkt3935.test 1ffcfffc148df51c8a01d1b3efae2d6c44cbeb0af1e0c5b88f4afe9a86d4ddb6 F test/tkt3992.test f3e7d548ac26f763b47bc0f750da3d03c81071da F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd F test/tkt4018.test 18dbc6617f7a4b90e938d1bd6d26ad18daafaf08 @@ -1682,6 +1714,7 @@ F test/upfrom1.tcl 8859d9d437f03b44174c4524a7a734a391fd4526fcff65be08285dafc9dc9 F test/upfrom1.test 8cb06689e99cd707d884faa16da0e8eb26ff658bb01c47ddf72fadade666e6e1 F test/upfrom2.test 66f3ebf721b3cebd922faee5c386bf244f816d416b57c000753ff51af62328a1 F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0 +F test/upfrom4.test 1cd82e9423e02b1f63d069e8665c6c3932ec424fd0043d033cc0ba99abf33236 F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6 F test/upsert1.test b0ae2f58680c5205b4bc1cdeed3c3d444057c506f6c44494fa3eac60731d68a2 F test/upsert2.test 720e94d09f7362a282bc69b3c6b83d51daeaaf0440eb4920a08b86518b8c7496 @@ -1710,7 +1743,7 @@ F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c840 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e F test/vtab4.test 8e73ed268f3d596bc3590f45fc948fb40f28e9c3 F test/vtab5.test 889f444970393c73f1e077e2bdc5d845e157a391 -F test/vtab6.test 82d5bb8fd3c0643102c1209e9ea353b168b7eb9c8db4406ab2ee2cbbdaead62c +F test/vtab6.test 2525a2fe2e44ccbed1d758cb2977fb8ab8f07d5312ed8d8799d3529647d11f2f F test/vtab7.test 70c6f4a1d6177144a8236e4172d5fba92e683440374664ad1f04851fbb335d3c F test/vtab8.test e19fa4a538fcd1bb66c22825fa8f71618fb13583 F test/vtab9.test ea58d2b95d61955f87226381716b2d0b1d4e4f9b @@ -1768,7 +1801,7 @@ F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2a F test/walvfs.test bccb3e0d235ef85e276f491d34db32c9ada1ea67be8d9f10aabe7b30319ec656 F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec F test/wapptest.tcl 899594e25684861d5b0c0880fb012364def50ef8097041b8ddf74be5ba7fa270 x -F test/where.test f114842c1851d257a26770f2ad55119b084001c0e1b8c214f886f45152d37cd8 +F test/where.test d13cd7c24e80009d2b54e2f7a8893c457afa49c64f99359c9eb3fe668ba1d9d4 F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6 F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 @@ -1776,7 +1809,7 @@ F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b F test/where7.test 1c1bf436bf31b913d4764a2b62ac6e98b9681e5c7ae2b562605592a56b7e946b F test/where8.test 461ca40265ed996a6305da99bb024b0e41602bb586acf544c08f95922358e49f -F test/where9.test 1ffb75edc50a8faa6e7bd77f8221d783febb00b44b0bdb32fb48cec6e38eca95 +F test/where9.test 2db942671a002621eff4f713e347bb25243295f79d8990297cd160bebcfde3f7 F test/whereA.test 9d1077b117f1b68d5f739d94f36956c36cf995eb87bb19b77b2e81af020edd20 F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5 F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6 @@ -1798,7 +1831,7 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2 F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972 F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test 18aadea36d7a9fab1474b1d80002d82304a342e0471793841c66afde14a381b8 +F test/window1.test ae87c4ea4e689725c7e1c826ab2b10704d5b6d31f9b9e5abadded996ba53a1d4 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -1815,16 +1848,17 @@ F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be F test/windowB.test f2fb42b864b0cf431c956407583e9478a74c3642bdf8737fdcb6ff4a40298b07 F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2 +F test/windowD.test 65cf5a765fb8072450e8a0de2979ce7f09a38d87724fe1280c6444073e3da49b F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df -F test/with1.test 7bc5abfe4c80c0cef8a90f5a66d60b9982e8ccd7350c8eb70611323a3b8e07ba +F test/with1.test 9ad67fdeb2bbd808a5763c9060e214ea232f9b18d846ea3a59756dc38bdc3880 F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df F test/with3.test ad32d13ad50661e6fa305f62a0717649c348792e7b658bf2644976227a9e0373 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8 -F test/with6.test 661d7e416bef6c0a2556b2c9f0c8178a5b15932bed65246abed99723a8d4e7c0 +F test/with6.test c18592592b5a1c5802fd4e933d506f7b34ebbe8fdd585229793e960ae58d433f F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64 F test/without_rowid1.test a5210b8770dc4736bca4e74bc96588f43025ad03ad6a80f885afd36d9890e217 F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99 @@ -1849,7 +1883,7 @@ F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367 F tool/cg_anno.tcl c1f875f5a4c9caca3d59937b16aff716f8b1883935f1b4c9ae23124705bc8099 x F tool/checkSpacing.c 810e51703529a204fc4e1eb060e9ab663e3c06d2 F tool/dbhash.c 5da0c61032d23d74f2ab84ffc5740f0e8abec94f2c45c0b4306be7eb3ae96df0 -F tool/dbtotxt.c b2221864a20fb391c46bd31bc1fbdc4a96f5c8a89bef58f421eb9b9c36b1702c +F tool/dbtotxt.c 2620a354d109b70d71bf758d2a64e042d74e0aaa5bd6e447900ed3542410d7f9 F tool/dbtotxt.md c9a57af8739957ef36d2cfad5c4b1443ff3688ed33e4901ee200c8b651f43f3c F tool/enlargedb.c 3e8b2612b985cfa7e3e8800031ee191b43ae80de96abb5abbd5eada62651ee21 F tool/extract-sqlite3h.tcl 069ceab0cee26cba99952bfa08c0b23e35941c837acabe143f0c355d96c9e2eb x @@ -1862,7 +1896,7 @@ F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5 F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f -F tool/lemon.c 1c5a14f6044193e42864c36de48359026fa2cdcf205a43cc1a31116101e27258 +F tool/lemon.c ea5c8589c7749e9bd32ba10432aeeed3c16e215de72a12ada2bc707884837149 F tool/lempar.c 57478ea48420da05faa873c6d1616321caa5464644588c97fbe8e0ea04450748 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 @@ -1871,18 +1905,18 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl 3147e9dfc4ad774e94f80084789ebaada9da9b6e66ddab16438cfc07999b6047 x +F tool/mkctimec.tcl ac96a74f5e6d9dac672d5229f79c583d3357a50e7d098e473e6b2ce2f8ae1704 x F tool/mkkeywordhash.c 35bfc41adacc4aa6ef6fca7fd0c63e0ec0534b78daf4d0cfdebe398216bbffc3 F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a895ab33 F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef -F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd +F tool/mkopcodeh.tcl 5dab48c49a25452257494e9601702ab63adaba6bd54a9b382615fa52661c8f8c F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa -F tool/mkpragmatab.tcl de206c64b6e9ac8cd5e3cbd0ffe456f07d5710605ef8385d677e60ce3335ea12 +F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d F tool/mkshellc.tcl df5d249617f9cc94d5c48eb0401673eb3f31f383ecbc54e8a13ca3dd97e89450 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f -F tool/mksqlite3c.tcl 6f9e05facb51e906a1a7ef9f95274ef2ec91bf88b96732a9aed40647c605f419 +F tool/mksqlite3c.tcl eee7e9d9c58abb1045f6ed74ad95ad26e8d22be29fdc431deea5267fb3fa049c F tool/mksqlite3h.tcl 1f5e4a1dbbbc43c83cc6e74fe32c6c620502240b66c7c0f33a51378e78fc4edf F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 @@ -1908,7 +1942,7 @@ F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd -F tool/split-sqlite3c.tcl 3efcd4240b738f6bb2b5af0aea7e1e0ef9bc1c61654f645076cec883030b710c +F tool/split-sqlite3c.tcl 6b02bb5444cabc477c0d6eb3ecedcc827063e9ac41954831e90c72c481aa9d30 F tool/sqldiff.c 4f967c199c5f93eec64978e3a625d6c07fb1162212b1d48f65740d9eb4607eee F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 @@ -1944,10 +1978,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7fcdb760a4df8c5c11e94cecdc61bb8f40c86b70aa05f48e17520443a3d3b451 -R c70a3244d878106624c489b9bff97d77 +P 9141e873c575b049ce7aeaf313d05966f1966087caf33a6c80d2416a28571a21 +R 8d3cb391daa96d7d2c40e5bc9d5676de T +sym-release * -T +sym-version-3.38.5 * +T +sym-version-3.39.2 * U drh -Z 54d3523b3ce9c5f872539bc3270e6853 +Z 89ae78e7fc05abf10cde154f943a869d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 21a09c325..56a5ce79f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe +698edb77537b67c41adc68f9b892db56bcf9a55e00371a61420f3ddd668e6603 diff --git a/src/alter.c b/src/alter.c index 1ec74cdcc..d8a998287 100644 --- a/src/alter.c +++ b/src/alter.c @@ -858,11 +858,10 @@ static void unmapColumnIdlistNames( Parse *pParse, const IdList *pIdList ){ - if( pIdList ){ - int ii; - for(ii=0; iinId; ii++){ - sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName); - } + int ii; + assert( pIdList!=0 ); + for(ii=0; iinId; ii++){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName); } } @@ -881,7 +880,7 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){ if( ALWAYS(p->pEList) ){ ExprList *pList = p->pEList; for(i=0; inExpr; i++){ - if( pList->a[i].zEName && pList->a[i].eEName==ENAME_NAME ){ + if( pList->a[i].zEName && pList->a[i].fg.eEName==ENAME_NAME ){ sqlite3RenameTokenRemap(pParse, 0, (void*)pList->a[i].zEName); } } @@ -890,8 +889,11 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){ SrcList *pSrc = p->pSrc; for(i=0; inSrc; i++){ sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName); - sqlite3WalkExpr(pWalker, pSrc->a[i].pOn); - unmapColumnIdlistNames(pParse, pSrc->a[i].pUsing); + if( pSrc->a[i].fg.isUsing==0 ){ + sqlite3WalkExpr(pWalker, pSrc->a[i].u3.pOn); + }else{ + unmapColumnIdlistNames(pParse, pSrc->a[i].u3.pUsing); + } } } @@ -927,7 +929,7 @@ void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){ sWalker.xExprCallback = renameUnmapExprCb; sqlite3WalkExprList(&sWalker, pEList); for(i=0; inExpr; i++){ - if( ALWAYS(pEList->a[i].eEName==ENAME_NAME) ){ + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) ){ sqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zEName); } } @@ -1085,7 +1087,7 @@ static void renameColumnElistNames( int i; for(i=0; inExpr; i++){ const char *zName = pEList->a[i].zEName; - if( ALWAYS(pEList->a[i].eEName==ENAME_NAME) + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) && ALWAYS(zName!=0) && 0==sqlite3_stricmp(zName, zOld) ){ @@ -1315,27 +1317,33 @@ static int renameResolveTrigger(Parse *pParse){ if( rc==SQLITE_OK && pStep->zTarget ){ SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep); if( pSrc ){ - int i; - for(i=0; inSrc && rc==SQLITE_OK; i++){ - SrcItem *p = &pSrc->a[i]; - p->iCursor = pParse->nTab++; - if( p->pSelect ){ - sqlite3SelectPrep(pParse, p->pSelect, 0); - sqlite3ExpandSubquery(pParse, p); - assert( i>0 ); - assert( pStep->pFrom->a[i-1].pSelect ); - sqlite3SelectPrep(pParse, pStep->pFrom->a[i-1].pSelect, 0); - }else{ - p->pTab = sqlite3LocateTableItem(pParse, 0, p); - if( p->pTab==0 ){ - rc = SQLITE_ERROR; - }else{ - p->pTab->nTabRef++; - rc = sqlite3ViewGetColumnNames(pParse, p->pTab); + Select *pSel = sqlite3SelectNew( + pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 + ); + if( pSel==0 ){ + pStep->pExprList = 0; + pSrc = 0; + rc = SQLITE_NOMEM; + }else{ + sqlite3SelectPrep(pParse, pSel, 0); + rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK; + assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList ); + assert( pSrc==pSel->pSrc ); + if( pStep->pExprList ) pSel->pEList = 0; + pSel->pSrc = 0; + sqlite3SelectDelete(db, pSel); + } + if( pStep->pFrom ){ + int i; + for(i=0; ipFrom->nSrc && rc==SQLITE_OK; i++){ + SrcItem *p = &pStep->pFrom->a[i]; + if( p->pSelect ){ + sqlite3SelectPrep(pParse, p->pSelect, 0); } } } - if( rc==SQLITE_OK && db->mallocFailed ){ + + if( db->mallocFailed ){ rc = SQLITE_NOMEM; } sNC.pSrcList = pSrc; @@ -1787,6 +1795,15 @@ static void renameTableFunc( if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ renameTokenFind(&sParse, &sCtx, pStep->zTarget); } + if( pStep->pFrom ){ + int i; + for(i=0; ipFrom->nSrc; i++){ + SrcItem *pItem = &pStep->pFrom->a[i]; + if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, pItem->zName); + } + } + } } } } diff --git a/src/analyze.c b/src/analyze.c index 5bbd34f9a..39009899a 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -847,9 +847,14 @@ static void statGet( ** * "WHERE a=? AND b=?" matches 2 rows. ** ** If D is the count of distinct values and K is the total number of - ** rows, then each estimate is computed as: + ** rows, then each estimate is usually computed as: ** ** I = (K+D-1)/D + ** + ** In other words, I is K/D rounded up to the next whole integer. + ** However, if I is between 1.0 and 1.1 (in other words if I is + ** close to 1.0 but just a little larger) then do not round up but + ** instead keep the I value at 1.0. */ sqlite3_str sStat; /* Text of the constructed "stat" line */ int i; /* Loop counter */ @@ -860,6 +865,7 @@ static void statGet( for(i=0; inKeyCol; i++){ u64 nDistinct = p->current.anDLt[i] + 1; u64 iVal = (p->nRow + nDistinct - 1) / nDistinct; + if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1; sqlite3_str_appendf(&sStat, " %llu", iVal); assert( p->current.anEq[i] ); } @@ -1023,7 +1029,7 @@ static void analyzeOneTable( memcpy(pStat1->zName, "sqlite_stat1", 13); pStat1->nCol = 3; pStat1->iPKey = -1; - sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNBLOB); + sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNAMIC); } #endif diff --git a/src/attach.c b/src/attach.c index e587f6cc5..1732be27b 100644 --- a/src/attach.c +++ b/src/attach.c @@ -480,7 +480,11 @@ static int fixSelectCb(Walker *p, Select *pSelect){ pItem->fg.fromDDL = 1; } #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) - if( sqlite3WalkExpr(&pFix->w, pList->a[i].pOn) ) return WRC_Abort; + if( pList->a[i].fg.isUsing==0 + && sqlite3WalkExpr(&pFix->w, pList->a[i].u3.pOn) + ){ + return WRC_Abort; + } #endif } if( pSelect->pWith ){ diff --git a/src/btree.c b/src/btree.c index d881b6220..facb951d3 100644 --- a/src/btree.c +++ b/src/btree.c @@ -214,7 +214,7 @@ static int hasSharedCacheTableLock( int bSeen = 0; for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){ Index *pIdx = (Index *)sqliteHashData(p); - if( pIdx->tnum==(int)iRoot ){ + if( pIdx->tnum==iRoot ){ if( bSeen ){ /* Two or more indexes share the same root page. There must ** be imposter tables. So just return true. The assert is not @@ -807,7 +807,7 @@ void sqlite3BtreeClearCursor(BtCursor *pCur){ /* ** In this version of BtreeMoveto, pKey is a packed index record ** such as is generated by the OP_MakeRecord opcode. Unpack the -** record and then call BtreeMovetoUnpacked() to do the work. +** record and then call sqlite3BtreeIndexMoveto() to do the work. */ static int btreeMoveto( BtCursor *pCur, /* Cursor open on the btree to be searched */ @@ -1327,6 +1327,7 @@ static void btreeParseCell( ** the space used by the cell pointer. ** ** cellSizePtrNoPayload() => table internal nodes +** cellSizePtrTableLeaf() => table leaf nodes ** cellSizePtr() => all index nodes & table leaf nodes */ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ @@ -1352,13 +1353,6 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ }while( *(pIter)>=0x80 && pIterintKey ){ - /* pIter now points at the 64-bit integer key value, a variable length - ** integer. The following block moves pIter to point at the first byte - ** past the end of the key value. */ - pEnd = &pIter[9]; - while( (*pIter++)&0x80 && pItermaxLocal ); testcase( nSize==(u32)pPage->maxLocal+1 ); if( nSize<=pPage->maxLocal ){ @@ -1398,6 +1392,58 @@ static u16 cellSizePtrNoPayload(MemPage *pPage, u8 *pCell){ assert( debuginfo.nSize==(u16)(pIter - pCell) || CORRUPT_DB ); return (u16)(pIter - pCell); } +static u16 cellSizePtrTableLeaf(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pItermaxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + if( nSize<4 ) nSize = 4; + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} #ifdef SQLITE_DEBUG @@ -1411,7 +1457,7 @@ static u16 cellSize(MemPage *pPage, int iCell){ #ifndef SQLITE_OMIT_AUTOVACUUM /* ** The cell pCell is currently part of page pSrc but will ultimately be part -** of pPage. (pSrc and pPager are often the same.) If pCell contains a +** of pPage. (pSrc and pPage are often the same.) If pCell contains a ** pointer to an overflow page, insert an entry into the pointer-map for ** the overflow page that will be valid after pCell has been moved to pPage. */ @@ -1586,7 +1632,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ const int hdr = pPg->hdrOffset; /* Offset to page header */ u8 * const aData = pPg->aData; /* Page data */ int iAddr = hdr + 1; /* Address of ptr to pc */ - int pc = get2byte(&aData[iAddr]); /* Address of a free slot */ + u8 *pTmp = &aData[iAddr]; /* Temporary ptr into aData[] */ + int pc = get2byte(pTmp); /* Address of a free slot */ int x; /* Excess size of the slot */ int maxPC = pPg->pBt->usableSize - nByte; /* Max address for a usable slot */ int size; /* Size of the free slot */ @@ -1596,7 +1643,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ /* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each ** freeblock form a big-endian integer which is the size of the freeblock ** in bytes, including the 4-byte header. */ - size = get2byte(&aData[pc+2]); + pTmp = &aData[pc+2]; + size = get2byte(pTmp); if( (x = size - nByte)>=0 ){ testcase( x==4 ); testcase( x==3 ); @@ -1623,7 +1671,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ return &aData[pc + x]; } iAddr = pc; - pc = get2byte(&aData[pc]); + pTmp = &aData[pc]; + pc = get2byte(pTmp); if( pc<=iAddr+size ){ if( pc ){ /* The next slot in the chain is not past the end of the current slot */ @@ -1657,6 +1706,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ u8 * const data = pPage->aData; /* Local cache of pPage->aData */ int top; /* First byte of cell content area */ int rc = SQLITE_OK; /* Integer return code */ + u8 *pTmp; /* Temp ptr into data[] */ int gap; /* First byte of gap between cell pointers and cell content */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -1675,7 +1725,8 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ ** then the cell content offset of an empty page wants to be 65536. ** However, that integer is too large to be stored in a 2-byte unsigned ** integer, so a value of 0 is used in its place. */ - top = get2byte(&data[hdr+5]); + pTmp = &data[hdr+5]; + top = get2byte(pTmp); assert( top<=(int)pPage->pBt->usableSize ); /* by btreeComputeFreeSpace() */ if( gap>top ){ if( top==0 && pPage->pBt->usableSize==65536 ){ @@ -1757,6 +1808,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ u16 x; /* Offset to cell content area */ u32 iEnd = iStart + iSize; /* First byte past the iStart buffer */ unsigned char *data = pPage->aData; /* Page content */ + u8 *pTmp; /* Temporary ptr into data[] */ assert( pPage->pBt!=0 ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -1819,7 +1871,8 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PAGE(pPage); data[hdr+7] -= nFrag; } - x = get2byte(&data[hdr+5]); + pTmp = &data[hdr+5]; + x = get2byte(pTmp); if( iStart<=x ){ /* The new freeblock is at the beginning of the cell content area, ** so just extend the cell content area rather than create another @@ -1863,7 +1916,6 @@ static int decodeFlags(MemPage *pPage, int flagByte){ pPage->leaf = (u8)(flagByte>>3); assert( PTF_LEAF == 1<<3 ); flagByte &= ~PTF_LEAF; pPage->childPtrSize = 4-4*pPage->leaf; - pPage->xCellSize = cellSizePtr; pBt = pPage->pBt; if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ /* EVIDENCE-OF: R-07291-35328 A value of 5 (0x05) means the page is an @@ -1875,6 +1927,7 @@ static int decodeFlags(MemPage *pPage, int flagByte){ pPage->intKey = 1; if( pPage->leaf ){ pPage->intKeyLeaf = 1; + pPage->xCellSize = cellSizePtrTableLeaf; pPage->xParseCell = btreeParseCellPtr; }else{ pPage->intKeyLeaf = 0; @@ -1892,12 +1945,17 @@ static int decodeFlags(MemPage *pPage, int flagByte){ assert( (PTF_ZERODATA|PTF_LEAF)==10 ); pPage->intKey = 0; pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; pPage->xParseCell = btreeParseCellPtrIndex; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; }else{ /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is ** an error. */ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; return SQLITE_CORRUPT_PAGE(pPage); } pPage->max1bytePayload = pBt->max1bytePayload; @@ -2251,7 +2309,9 @@ static int getAndInitPage( pCur->pPage = pCur->apPage[pCur->iPage]; } testcase( pgno==0 ); - assert( pgno!=0 || rc==SQLITE_CORRUPT ); + assert( pgno!=0 || rc==SQLITE_CORRUPT + || rc==SQLITE_IOERR_NOMEM + || rc==SQLITE_NOMEM ); return rc; } @@ -3873,12 +3933,17 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ } do { MemPage *pFreePg; + Pgno dbSize = btreePagecount(pBt); rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iNear, eMode); if( rc!=SQLITE_OK ){ releasePage(pLastPg); return rc; } releasePage(pFreePg); + if( iFreePg>dbSize ){ + releasePage(pLastPg); + return SQLITE_CORRUPT_BKPT; + } }while( bCommit && iFreePg>nFin ); assert( iFreePgpPage; + int c; + int nCell; /* Size of the pCell cell in bytes */ + u8 *pCell = findCellPastPtr(pPage, idx); + + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + testcase( pCell+nCell+1==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + testcase( pCell+nCell+2==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* If the record extends into overflow pages, do not attempt + ** the optimization. */ + c = 99; + } + return c; +} + +/* +** Return true (non-zero) if pCur is current pointing to the last +** page of a table. +*/ +static int cursorOnLastPage(BtCursor *pCur){ + int i; + assert( pCur->eState==CURSOR_VALID ); + for(i=0; iiPage; i++){ + MemPage *pPage = pCur->apPage[i]; + if( pCur->aiIdx[i]nCell ) return 0; + } + return 1; +} + /* Move the cursor so that it points to an entry in an index table ** near the key pIdxKey. Return a success code. ** @@ -5690,6 +5818,43 @@ int sqlite3BtreeIndexMoveto( || pIdxKey->default_rc==-1 ); + + /* Check to see if we can skip a lot of work. Two cases: + ** + ** (1) If the cursor is already pointing to the very last cell + ** in the table and the pIdxKey search key is greater than or + ** equal to that last cell, then no movement is required. + ** + ** (2) If the cursor is on the last page of the table and the first + ** cell on that last page is less than or equal to the pIdxKey + ** search key, then we can start the search on the current page + ** without needing to go back to root. + */ + if( pCur->eState==CURSOR_VALID + && pCur->pPage->leaf + && cursorOnLastPage(pCur) + ){ + int c; + if( pCur->ix==pCur->pPage->nCell-1 + && (c = indexCellCompare(pCur, pCur->ix, pIdxKey, xRecordCompare))<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + *pRes = c; + return SQLITE_OK; /* Cursor already pointing at the correct spot */ + } + if( pCur->iPage>0 + && indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + pCur->curFlags &= ~BTCF_ValidOvfl; + if( !pCur->pPage->isInit ){ + return SQLITE_CORRUPT_BKPT; + } + goto bypass_moveto_root; /* Start search on the current page */ + } + pIdxKey->errCode = SQLITE_OK; + } + rc = moveToRoot(pCur); if( rc ){ if( rc==SQLITE_EMPTY ){ @@ -5699,12 +5864,14 @@ int sqlite3BtreeIndexMoveto( } return rc; } + +bypass_moveto_root: assert( pCur->pPage ); assert( pCur->pPage->isInit ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->pPage->nCell > 0 ); - assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey ); - assert( pCur->curIntKey || pIdxKey ); + assert( pCur->curIntKey==0 ); + assert( pIdxKey!=0 ); for(;;){ int lwr, upr, idx, c; Pgno chldPg; @@ -5718,7 +5885,7 @@ int sqlite3BtreeIndexMoveto( ** be the right kind (index or table) of b-tree page. Otherwise ** a moveToChild() or moveToRoot() call would have detected corruption. */ assert( pPage->nCell>0 ); - assert( pPage->intKey==(pIdxKey==0) ); + assert( pPage->intKey==0 ); lwr = 0; upr = pPage->nCell-1; idx = upr>>1; /* idx = (lwr+upr)/2; */ @@ -8585,7 +8752,6 @@ static int anotherValidCursor(BtCursor *pCur){ */ static int balance(BtCursor *pCur){ int rc = SQLITE_OK; - const int nMin = pCur->pBt->usableSize * 2 / 3; u8 aBalanceQuickSpace[13]; u8 *pFree = 0; @@ -8597,7 +8763,11 @@ static int balance(BtCursor *pCur){ MemPage *pPage = pCur->pPage; if( NEVER(pPage->nFree<0) && btreeComputeFreeSpace(pPage) ) break; - if( pPage->nOverflow==0 && pPage->nFree<=nMin ){ + if( pPage->nOverflow==0 && pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* No rebalance required as long as: + ** (1) There are no overflow cells + ** (2) The amount of free space on the page is less than 2/3rds of + ** the total usable space on the page. */ break; }else if( (iPage = pCur->iPage)==0 ){ if( pPage->nOverflow && (rc = anotherValidCursor(pCur))==SQLITE_OK ){ @@ -8817,7 +8987,7 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ ** pX.pData,nData,nZero fields must be zero. ** ** If the seekResult parameter is non-zero, then a successful call to -** MovetoUnpacked() to seek cursor pCur to (pKey,nKey) has already +** sqlite3BtreeIndexMoveto() to seek cursor pCur to (pKey,nKey) has already ** been performed. In other words, if seekResult!=0 then the cursor ** is currently pointing to a cell that will be adjacent to the cell ** to be inserted. If seekResult<0 then pCur points to a cell that is @@ -8835,7 +9005,7 @@ int sqlite3BtreeInsert( BtCursor *pCur, /* Insert data into the table of this cursor */ const BtreePayload *pX, /* Content of the row to be inserted */ int flags, /* True if this is likely an append */ - int seekResult /* Result of prior MovetoUnpacked() call */ + int seekResult /* Result of prior IndexMoveto() call */ ){ int rc; int loc = seekResult; /* -1: before desired location +1: after */ @@ -8874,7 +9044,12 @@ int sqlite3BtreeInsert( } } + /* Ensure that the cursor is not in the CURSOR_FAULT state and that it + ** points to a valid cell. + */ if( pCur->eState>=CURSOR_REQUIRESEEK ){ + testcase( pCur->eState==CURSOR_REQUIRESEEK ); + testcase( pCur->eState==CURSOR_FAULT ); rc = moveToRoot(pCur); if( rc && rc!=SQLITE_EMPTY ) return rc; } @@ -8986,7 +9161,8 @@ int sqlite3BtreeInsert( assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) ); assert( pPage->leaf || !pPage->intKey ); if( pPage->nFree<0 ){ - if( pCur->eState>CURSOR_INVALID ){ + if( NEVER(pCur->eState>CURSOR_INVALID) ){ + /* ^^^^^--- due to the moveToRoot() call above */ rc = SQLITE_CORRUPT_BKPT; }else{ rc = btreeComputeFreeSpace(pPage); @@ -8997,7 +9173,7 @@ int sqlite3BtreeInsert( TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); - assert( pPage->isInit ); + assert( pPage->isInit || CORRUPT_DB ); newCell = pBt->pTmpSpace; assert( newCell!=0 ); if( flags & BTREE_PREFORMAT ){ @@ -9148,7 +9324,11 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ u32 nRem; /* Bytes of data still to copy */ getCellInfo(pSrc); - aOut += putVarint32(aOut, pSrc->info.nPayload); + if( pSrc->info.nPayload<0x80 ){ + *(aOut++) = pSrc->info.nPayload; + }else{ + aOut += sqlite3PutVarint(aOut, pSrc->info.nPayload); + } if( pDest->pKeyInfo==0 ) aOut += putVarint(aOut, iKey); nIn = pSrc->info.nLocal; aIn = pSrc->info.pPayload; @@ -9308,7 +9488,8 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ bPreserve = (flags & BTREE_SAVEPOSITION)!=0; if( bPreserve ){ if( !pPage->leaf - || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3) + || (pPage->nFree+pPage->xCellSize(pPage,pCell)+2) > + (int)(pBt->usableSize*2/3) || pPage->nCell==1 /* See dbfuzz001.test for a test case */ ){ /* A b-tree rebalance will be required after deleting this entry. @@ -9404,7 +9585,15 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** been corrected, so be it. Otherwise, after balancing the leaf node, ** walk the cursor up the tree to the internal node and balance it as ** well. */ - rc = balance(pCur); + assert( pCur->pPage->nOverflow==0 ); + assert( pCur->pPage->nFree>=0 ); + if( pCur->pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* Optimization: If the free space is less than 2/3rds of the page, + ** then balance() will always be a no-op. No need to invoke it. */ + rc = SQLITE_OK; + }else{ + rc = balance(pCur); + } if( rc==SQLITE_OK && pCur->iPage>iCellDepth ){ releasePageNotNull(pCur->pPage); pCur->iPage--; diff --git a/src/btreeInt.h b/src/btreeInt.h index dc4662932..1f45553dc 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -295,7 +295,7 @@ struct MemPage { u8 *aData; /* Pointer to disk image of the page data */ u8 *aDataEnd; /* One byte past the end of the entire page - not just ** the usable space, the entire page. Used to prevent - ** corruption-induced of buffer overflow. */ + ** corruption-induced buffer overflow. */ u8 *aCellIdx; /* The cell index area */ u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */ DbPage *pDbPage; /* Pager page handle */ @@ -600,7 +600,7 @@ struct BtCursor { /* ** The database page the PENDING_BYTE occupies. This page is never used. */ -# define PENDING_BYTE_PAGE(pBt) PAGER_MJ_PGNO(pBt) +#define PENDING_BYTE_PAGE(pBt) ((Pgno)((PENDING_BYTE/((pBt)->pageSize))+1)) /* ** These macros define the location of the pointer-map entry for a diff --git a/src/build.c b/src/build.c index f7614af62..31ab81b09 100644 --- a/src/build.c +++ b/src/build.c @@ -172,9 +172,7 @@ void sqlite3FinishCoding(Parse *pParse){ int i; int reg; - if( NEVER(pReturning->nRetCol==0) ){ - assert( CORRUPT_DB ); - }else{ + if( pReturning->nRetCol ){ sqlite3VdbeAddOp0(v, OP_FkCheck); addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); @@ -214,7 +212,9 @@ void sqlite3FinishCoding(Parse *pParse){ int iDb, i; assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); sqlite3VdbeJumpHere(v, 0); - for(iDb=0; iDbnDb; iDb++){ + assert( db->nDb>0 ); + iDb = 0; + do{ Schema *pSchema; if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; sqlite3VdbeUsesBtree(v, iDb); @@ -229,7 +229,7 @@ void sqlite3FinishCoding(Parse *pParse){ if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); VdbeComment((v, "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); - } + }while( ++iDbnDb ); #ifndef SQLITE_OMIT_VIRTUALTABLE for(i=0; inVtabLock; i++){ char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); @@ -268,9 +268,7 @@ void sqlite3FinishCoding(Parse *pParse){ if( pParse->bReturning ){ Returning *pRet = pParse->u1.pReturning; - if( NEVER(pRet->nRetCol==0) ){ - assert( CORRUPT_DB ); - }else{ + if( pRet->nRetCol ){ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); } } @@ -333,8 +331,6 @@ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); db->mDbFlags |= DBFLAG_PreferBuiltin; sqlite3RunParser(pParse, zSql); - sqlite3DbFree(db, pParse->zErrMsg); - pParse->zErrMsg = 0; db->mDbFlags = savedDbFlags; sqlite3DbFree(db, zSql); memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); @@ -1892,7 +1888,7 @@ void sqlite3AddPrimaryKey( pTab->keyConf = (u8)onError; assert( autoInc==0 || autoInc==1 ); pTab->tabFlags |= autoInc*TF_Autoincrement; - if( pList ) pParse->iPkSortOrder = pList->a[0].sortFlags; + if( pList ) pParse->iPkSortOrder = pList->a[0].fg.sortFlags; (void)sqlite3HasExplicitNulls(pParse, pList); }else if( autoInc ){ #ifndef SQLITE_OMIT_AUTOINCREMENT @@ -2386,7 +2382,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey); } - pList->a[0].sortFlags = pParse->iPkSortOrder; + pList->a[0].fg.sortFlags = pParse->iPkSortOrder; assert( pParse->pNewTable==pTab ); pTab->iPKey = -1; sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0, @@ -3057,7 +3053,6 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ Table *pSelTab; /* A fake table from which we get the result set */ Select *pSel; /* Copy of the SELECT that implements the view */ int nErr = 0; /* Number of errors encountered */ - int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ #ifndef SQLITE_OMIT_VIRTUALTABLE int rc; @@ -3115,8 +3110,9 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ pSel = sqlite3SelectDup(db, pTable->u.view.pSelect, 0); if( pSel ){ u8 eParseMode = pParse->eParseMode; + int nTab = pParse->nTab; + int nSelect = pParse->nSelect; pParse->eParseMode = PARSE_MODE_NORMAL; - n = pParse->nTab; sqlite3SrcListAssignCursors(pParse, pSel->pSrc); pTable->nCol = -1; DisableLookaside; @@ -3128,7 +3124,8 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ #else pSelTab = sqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE); #endif - pParse->nTab = n; + pParse->nTab = nTab; + pParse->nSelect = nSelect; if( pSelTab==0 ){ pTable->nCol = 0; nErr++; @@ -3873,8 +3870,8 @@ int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){ if( pList ){ int i; for(i=0; inExpr; i++){ - if( pList->a[i].bNulls ){ - u8 sf = pList->a[i].sortFlags; + if( pList->a[i].fg.bNulls ){ + u8 sf = pList->a[i].fg.sortFlags; sqlite3ErrorMsg(pParse, "unsupported use of NULLS %s", (sf==0 || sf==3) ? "FIRST" : "LAST" ); @@ -4227,7 +4224,7 @@ void sqlite3CreateIndex( goto exit_create_index; } pIndex->azColl[i] = zColl; - requestedSortOrder = pListItem->sortFlags & sortOrderMask; + requestedSortOrder = pListItem->fg.sortFlags & sortOrderMask; pIndex->aSortOrder[i] = (u8)requestedSortOrder; } @@ -4670,18 +4667,17 @@ IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token *pToken){ if( pList==0 ){ pList = sqlite3DbMallocZero(db, sizeof(IdList) ); if( pList==0 ) return 0; + }else{ + IdList *pNew; + pNew = sqlite3DbRealloc(db, pList, + sizeof(IdList) + pList->nId*sizeof(pList->a)); + if( pNew==0 ){ + sqlite3IdListDelete(db, pList); + return 0; + } + pList = pNew; } - pList->a = sqlite3ArrayAllocate( - db, - pList->a, - sizeof(pList->a[0]), - &pList->nId, - &i - ); - if( i<0 ){ - sqlite3IdListDelete(db, pList); - return 0; - } + i = pList->nId++; pList->a[i].zName = sqlite3NameFromToken(db, pToken); if( IN_RENAME_OBJECT && pList->a[i].zName ){ sqlite3RenameTokenMap(pParse, (void*)pList->a[i].zName, pToken); @@ -4695,10 +4691,10 @@ IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token *pToken){ void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ int i; if( pList==0 ) return; + assert( pList->eU4!=EU4_EXPR ); /* EU4_EXPR mode is not currently used */ for(i=0; inId; i++){ sqlite3DbFree(db, pList->a[i].zName); } - sqlite3DbFree(db, pList->a); sqlite3DbFreeNN(db, pList); } @@ -4708,7 +4704,7 @@ void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ */ int sqlite3IdListIndex(IdList *pList, const char *zName){ int i; - if( pList==0 ) return -1; + assert( pList!=0 ); for(i=0; inId; i++){ if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i; } @@ -4911,8 +4907,11 @@ void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg); sqlite3DeleteTable(db, pItem->pTab); if( pItem->pSelect ) sqlite3SelectDelete(db, pItem->pSelect); - if( pItem->pOn ) sqlite3ExprDelete(db, pItem->pOn); - if( pItem->pUsing ) sqlite3IdListDelete(db, pItem->pUsing); + if( pItem->fg.isUsing ){ + sqlite3IdListDelete(db, pItem->u3.pUsing); + }else if( pItem->u3.pOn ){ + sqlite3ExprDelete(db, pItem->u3.pOn); + } } sqlite3DbFreeNN(db, pList); } @@ -4940,14 +4939,13 @@ SrcList *sqlite3SrcListAppendFromTerm( Token *pDatabase, /* Name of the database containing pTable */ Token *pAlias, /* The right-hand side of the AS subexpression */ Select *pSubquery, /* A subquery used in place of a table name */ - Expr *pOn, /* The ON clause of a join */ - IdList *pUsing /* The USING clause of a join */ + OnOrUsing *pOnUsing /* Either the ON clause or the USING clause */ ){ SrcItem *pItem; sqlite3 *db = pParse->db; - if( !p && (pOn || pUsing) ){ + if( !p && pOnUsing!=0 && (pOnUsing->pOn || pOnUsing->pUsing) ){ sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", - (pOn ? "ON" : "USING") + (pOnUsing->pOn ? "ON" : "USING") ); goto append_from_error; } @@ -4967,15 +4965,27 @@ SrcList *sqlite3SrcListAppendFromTerm( if( pAlias->n ){ pItem->zAlias = sqlite3NameFromToken(db, pAlias); } - pItem->pSelect = pSubquery; - pItem->pOn = pOn; - pItem->pUsing = pUsing; + if( pSubquery ){ + pItem->pSelect = pSubquery; + if( pSubquery->selFlags & SF_NestedFrom ){ + pItem->fg.isNestedFrom = 1; + } + } + assert( pOnUsing==0 || pOnUsing->pOn==0 || pOnUsing->pUsing==0 ); + assert( pItem->fg.isUsing==0 ); + if( pOnUsing==0 ){ + pItem->u3.pOn = 0; + }else if( pOnUsing->pUsing ){ + pItem->fg.isUsing = 1; + pItem->u3.pUsing = pOnUsing->pUsing; + }else{ + pItem->u3.pOn = pOnUsing->pOn; + } return p; append_from_error: assert( p==0 ); - sqlite3ExprDelete(db, pOn); - sqlite3IdListDelete(db, pUsing); + sqlite3ClearOnOrUsing(db, pOnUsing); sqlite3SelectDelete(db, pSubquery); return 0; } @@ -5020,6 +5030,7 @@ SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ p1 = pNew; memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem)); sqlite3DbFree(pParse->db, p2); + p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype); } } return p1; @@ -5056,14 +5067,34 @@ void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){ ** The operator is "natural cross join". The A and B operands are stored ** in p->a[0] and p->a[1], respectively. The parser initially stores the ** operator with A. This routine shifts that operator over to B. +** +** Additional changes: +** +** * All tables to the left of the right-most RIGHT JOIN are tagged with +** JT_LTORJ (mnemonic: Left Table Of Right Join) so that the +** code generator can easily tell that the table is part of +** the left operand of at least one RIGHT JOIN. */ -void sqlite3SrcListShiftJoinType(SrcList *p){ - if( p ){ - int i; - for(i=p->nSrc-1; i>0; i--){ - p->a[i].fg.jointype = p->a[i-1].fg.jointype; - } +void sqlite3SrcListShiftJoinType(Parse *pParse, SrcList *p){ + (void)pParse; + if( p && p->nSrc>1 ){ + int i = p->nSrc-1; + u8 allFlags = 0; + do{ + allFlags |= p->a[i].fg.jointype = p->a[i-1].fg.jointype; + }while( (--i)>0 ); p->a[0].fg.jointype = 0; + + /* All terms to the left of a RIGHT JOIN should be tagged with the + ** JT_LTORJ flags */ + if( allFlags & JT_RIGHT ){ + for(i=p->nSrc-1; ALWAYS(i>0) && (p->a[i].fg.jointype&JT_RIGHT)==0; i--){} + i--; + assert( i>=0 ); + do{ + p->a[i].fg.jointype |= JT_LTORJ; + }while( (--i)>=0 ); + } } } diff --git a/src/ctime.c b/src/ctime.c index 1463e4f55..a9391213d 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -307,9 +307,6 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_ENABLE_RTREE "ENABLE_RTREE", #endif -#ifdef SQLITE_ENABLE_SELECTTRACE - "ENABLE_SELECTTRACE", -#endif #ifdef SQLITE_ENABLE_SESSION "ENABLE_SESSION", #endif @@ -331,6 +328,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_ENABLE_STMT_SCANSTATUS "ENABLE_STMT_SCANSTATUS", #endif +#ifdef SQLITE_ENABLE_TREETRACE + "ENABLE_TREETRACE", +#endif #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION "ENABLE_UNKNOWN_SQL_FUNCTION", #endif diff --git a/src/dbpage.c b/src/dbpage.c index 575ee6771..003997b95 100644 --- a/src/dbpage.c +++ b/src/dbpage.c @@ -156,7 +156,7 @@ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ ){ pIdxInfo->orderByConsumed = 1; } - sqlite3VtabWriteAll(pIdxInfo); + sqlite3VtabUsesAllSchemas(pIdxInfo); return SQLITE_OK; } @@ -334,7 +334,7 @@ static int dbpageUpdate( goto update_fail; } pBt = pTab->db->aDb[iDb].pBt; - if( pgno<1 || pBt==0 || pgno>(int)sqlite3BtreeLastPage(pBt) ){ + if( pgno<1 || pBt==0 || pgno>sqlite3BtreeLastPage(pBt) ){ zErr = "bad page number"; goto update_fail; } diff --git a/src/delete.c b/src/delete.c index 7b769e6a1..df378d2d5 100644 --- a/src/delete.c +++ b/src/delete.c @@ -128,8 +128,8 @@ void sqlite3MaterializeView( assert( pFrom->nSrc==1 ); pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); - assert( pFrom->a[0].pOn==0 ); - assert( pFrom->a[0].pUsing==0 ); + assert( pFrom->a[0].fg.isUsing==0 ); + assert( pFrom->a[0].u3.pOn==0 ); } pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, SF_IncludeHidden, pLimit); @@ -300,7 +300,6 @@ void sqlite3DeleteFrom( assert( db->mallocFailed==0 ); assert( pTabList->nSrc==1 ); - /* Locate the table which we want to delete. This table has to be ** put in an SrcList structure because some of the subroutines we ** will be calling are designed to work with multiple tables and expect @@ -325,6 +324,14 @@ void sqlite3DeleteFrom( # define isView 0 #endif +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Delete() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewDelete(pParse->pWith, pTabList, pWhere, + pOrderBy, pLimit, pTrigger); + } +#endif + #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT if( !isView ){ pWhere = sqlite3LimitWhere( diff --git a/src/expr.c b/src/expr.c index 1ac573314..c0b2bee94 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1069,6 +1069,7 @@ Expr *sqlite3ExprFunction( sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */ return 0; } + assert( !ExprHasProperty(pNew, EP_InnerON|EP_OuterON) ); pNew->w.iOfst = (int)(pToken->z - pParse->zTail); if( pList && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] @@ -1249,6 +1250,18 @@ void sqlite3ExprDelete(sqlite3 *db, Expr *p){ if( p ) sqlite3ExprDeleteNN(db, p); } +/* +** Clear both elements of an OnOrUsing object +*/ +void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ + if( p==0 ){ + /* Nothing to clear */ + }else if( p->pOn ){ + sqlite3ExprDeleteNN(db, p->pOn); + }else if( p->pUsing ){ + sqlite3IdListDelete(db, p->pUsing); + } +} /* ** Arrange to cause pExpr to be deleted when the pParse is deleted. @@ -1335,7 +1348,7 @@ static int dupedExprStructSize(const Expr *p, int flags){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); - assert( !ExprHasProperty(p, EP_FromJoin) ); + assert( !ExprHasProperty(p, EP_OuterON) ); assert( !ExprHasProperty(p, EP_MemToken) ); assert( !ExprHasVVAProperty(p, EP_NoReduce) ); if( p->pLeft || p->x.pList ){ @@ -1515,6 +1528,7 @@ With *sqlite3WithDup(sqlite3 *db, With *p){ pRet->a[i].pSelect = sqlite3SelectDup(db, p->a[i].pSelect, 0); pRet->a[i].pCols = sqlite3ExprListDup(db, p->a[i].pCols, 0); pRet->a[i].zName = sqlite3DbStrDup(db, p->a[i].zName); + pRet->a[i].eM10d = p->a[i].eM10d; } } } @@ -1615,11 +1629,8 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){ } } pItem->zEName = sqlite3DbStrDup(db, pOldItem->zEName); - pItem->sortFlags = pOldItem->sortFlags; - pItem->eEName = pOldItem->eEName; - pItem->done = 0; - pItem->bNulls = pOldItem->bNulls; - pItem->bSorterRef = pOldItem->bSorterRef; + pItem->fg = pOldItem->fg; + pItem->fg.done = 0; pItem->u = pOldItem->u; } return pNew; @@ -1671,8 +1682,12 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){ pTab->nTabRef++; } pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags); - pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn, flags); - pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing); + if( pOldItem->fg.isUsing ){ + assert( pNewItem->fg.isUsing ); + pNewItem->u3.pUsing = sqlite3IdListDup(db, pOldItem->u3.pUsing); + }else{ + pNewItem->u3.pOn = sqlite3ExprDup(db, pOldItem->u3.pOn, flags); + } pNewItem->colUsed = pOldItem->colUsed; } return pNew; @@ -1682,22 +1697,16 @@ IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){ int i; assert( db!=0 ); if( p==0 ) return 0; - pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); + assert( p->eU4!=EU4_EXPR ); + pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew)+(p->nId-1)*sizeof(p->a[0]) ); if( pNew==0 ) return 0; pNew->nId = p->nId; - pNew->a = sqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) ); - if( pNew->a==0 ){ - sqlite3DbFreeNN(db, pNew); - return 0; - } - /* Note that because the size of the allocation for p->a[] is not - ** necessarily a power of two, sqlite3IdListAppend() may not be called - ** on the duplicate created by this function. */ + pNew->eU4 = p->eU4; for(i=0; inId; i++){ struct IdList_item *pNewItem = &pNew->a[i]; - struct IdList_item *pOldItem = &p->a[i]; + const struct IdList_item *pOldItem = &p->a[i]; pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName); - pNewItem->idx = pOldItem->idx; + pNewItem->u4 = pOldItem->u4; } return pNew; } @@ -1921,16 +1930,16 @@ void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int eNulls){ ); pItem = &p->a[p->nExpr-1]; - assert( pItem->bNulls==0 ); + assert( pItem->fg.bNulls==0 ); if( iSortOrder==SQLITE_SO_UNDEFINED ){ iSortOrder = SQLITE_SO_ASC; } - pItem->sortFlags = (u8)iSortOrder; + pItem->fg.sortFlags = (u8)iSortOrder; if( eNulls!=SQLITE_SO_UNDEFINED ){ - pItem->bNulls = 1; + pItem->fg.bNulls = 1; if( iSortOrder!=eNulls ){ - pItem->sortFlags |= KEYINFO_ORDER_BIGNULL; + pItem->fg.sortFlags |= KEYINFO_ORDER_BIGNULL; } } } @@ -1956,7 +1965,7 @@ void sqlite3ExprListSetName( assert( pList->nExpr>0 ); pItem = &pList->a[pList->nExpr-1]; assert( pItem->zEName==0 ); - assert( pItem->eEName==ENAME_NAME ); + assert( pItem->fg.eEName==ENAME_NAME ); pItem->zEName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n); if( dequote ){ /* If dequote==0, then pName->z does not point to part of a DDL @@ -1991,7 +2000,7 @@ void sqlite3ExprListSetSpan( assert( pList->nExpr>0 ); if( pItem->zEName==0 ){ pItem->zEName = sqlite3DbSpanDup(db, zStart, zEnd); - pItem->eEName = ENAME_SPAN; + pItem->fg.eEName = ENAME_SPAN; } } } @@ -2163,9 +2172,9 @@ Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ /* If pWalker->eCode is 2 then any term of the expression that comes from - ** the ON or USING clauses of a left join disqualifies the expression + ** the ON or USING clauses of an outer join disqualifies the expression ** from being considered constant. */ - if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_FromJoin) ){ + if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_OuterON) ){ pWalker->eCode = 0; return WRC_Abort; } @@ -2288,7 +2297,7 @@ int sqlite3ExprIsTableConstant(Expr *p, int iCur){ ** Check pExpr to see if it is an invariant constraint on data source pSrc. ** This is an optimization. False negatives will perhaps cause slower ** queries, but false positives will yield incorrect answers. So when in -** double, return 0. +** doubt, return 0. ** ** To be an invariant constraint, the following must be true: ** @@ -2296,24 +2305,28 @@ int sqlite3ExprIsTableConstant(Expr *p, int iCur){ ** ** (2) pExpr cannot use subqueries or non-deterministic functions. ** -** (*) ** Not applicable to this branch ** +** (3) pSrc cannot be part of the left operand for a RIGHT JOIN. +** (Is there some way to relax this constraint?) ** ** (4) If pSrc is the right operand of a LEFT JOIN, then... ** (4a) pExpr must come from an ON clause.. -** (4b) and specifically the ON clause associated with the LEFT JOIN. + (4b) and specifically the ON clause associated with the LEFT JOIN. ** ** (5) If pSrc is not the right operand of a LEFT JOIN or the left ** operand of a RIGHT JOIN, then pExpr must be from the WHERE ** clause, not an ON clause. */ int sqlite3ExprIsTableConstraint(Expr *pExpr, const SrcItem *pSrc){ + if( pSrc->fg.jointype & JT_LTORJ ){ + return 0; /* rule (3) */ + } if( pSrc->fg.jointype & JT_LEFT ){ - if( !ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (4a) */ - if( pExpr->w.iRightJoinTable!=pSrc->iCursor ) return 0; /* rule (4b) */ + if( !ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (4a) */ + if( pExpr->w.iJoin!=pSrc->iCursor ) return 0; /* rule (4b) */ }else{ - if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (5) */ + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (5) */ } - return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ + return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ } @@ -2643,7 +2656,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** all members of the RHS set, skipping duplicates. ** ** A cursor is opened on the b-tree object that is the RHS of the IN operator -** and pX->iTable is set to the index of that cursor. +** and the *piTab parameter is set to the index of that cursor. ** ** The returned value of this function indicates the b-tree type, as follows: ** @@ -2663,7 +2676,10 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** If the RHS of the IN operator is a list or a more complex subquery, then ** an ephemeral table might need to be generated from the RHS and then ** pX->iTable made to point to the ephemeral table instead of an -** existing table. +** existing table. In this case, the creation and initialization of the +** ephmeral table might be put inside of a subroutine, the EP_Subrtn flag +** will be set on pX and the pX->y.sub fields will be set to show where +** the subroutine is coded. ** ** The inFlags parameter must contain, at a minimum, one of the bits ** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP but not both. If inFlags contains @@ -2724,12 +2740,13 @@ int sqlite3FindInIndex( ){ Select *p; /* SELECT to the right of IN operator */ int eType = 0; /* Type of RHS table. IN_INDEX_* */ - int iTab = pParse->nTab++; /* Cursor of the RHS table */ + int iTab; /* Cursor of the RHS table */ int mustBeUnique; /* True if RHS must be unique */ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ assert( pX->op==TK_IN ); mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; + iTab = pParse->nTab++; /* If the RHS of this IN(...) operator is a SELECT, and if it matters ** whether or not the SELECT result contains NULL values, check whether @@ -2895,6 +2912,8 @@ int sqlite3FindInIndex( && ExprUseXList(pX) && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) ){ + pParse->nTab--; /* Back out the allocation of the unused cursor */ + iTab = -1; /* Cursor is not allocated */ eType = IN_INDEX_NOOP; } @@ -3061,6 +3080,7 @@ void sqlite3CodeRhsOfIN( assert( ExprUseYSub(pExpr) ); sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); + assert( iTab!=pExpr->iTable ); sqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable); sqlite3VdbeJumpHere(v, addrOnce); return; @@ -3072,8 +3092,7 @@ void sqlite3CodeRhsOfIN( assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); pExpr->y.sub.regReturn = ++pParse->nMem; pExpr->y.sub.iAddr = - sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; - VdbeComment((v, "return address")); + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } @@ -3175,6 +3194,7 @@ void sqlite3CodeRhsOfIN( ** expression we need to rerun this code each time. */ if( addrOnce && !sqlite3ExprIsConstant(pE2) ){ + sqlite3VdbeChangeToNoop(v, addrOnce-1); sqlite3VdbeChangeToNoop(v, addrOnce); ExprClearProperty(pExpr, EP_Subrtn); addrOnce = 0; @@ -3195,8 +3215,11 @@ void sqlite3CodeRhsOfIN( sqlite3VdbeJumpHere(v, addrOnce); /* Subroutine return */ assert( ExprUseYSub(pExpr) ); - sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); - sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); sqlite3ClearTempRegCache(pParse); } } @@ -3250,9 +3273,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ ExprSetProperty(pExpr, EP_Subrtn); pExpr->y.sub.regReturn = ++pParse->nMem; pExpr->y.sub.iAddr = - sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; - VdbeComment((v, "return address")); - + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; /* The evaluation of the EXISTS/SELECT must be repeated every time it ** is encountered if any of the following is true: @@ -3325,8 +3346,11 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ /* Subroutine return */ assert( ExprUseYSub(pExpr) ); - sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); - sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); sqlite3ClearTempRegCache(pParse); return rReg; } @@ -3760,6 +3784,7 @@ void sqlite3ExprCodeGetColumnOfTable( } if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); + VdbeComment((v, "%s.rowid", pTab->zName)); }else{ int op; int x; @@ -3930,7 +3955,17 @@ static int exprCodeInlineFunction( caseExpr.x.pList = pFarg; return sqlite3ExprCodeTarget(pParse, &caseExpr, target); } - +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + case INLINEFUNC_sqlite_offset: { + Expr *pArg = pFarg->a[0].pExpr; + if( pArg->op==TK_COLUMN && pArg->iTable>=0 ){ + sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + break; + } +#endif default: { /* The UNLIKELY() function is a no-op. The result is the value ** of the first argument. @@ -4469,20 +4504,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC - if( (pDef->funcFlags & SQLITE_FUNC_OFFSET)!=0 && ALWAYS(pFarg!=0) ){ - Expr *pArg = pFarg->a[0].pExpr; - if( pArg->op==TK_COLUMN ){ - sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); - }else{ - sqlite3VdbeAddOp2(v, OP_Null, 0, target); - } - }else -#endif - { - sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg, - pDef, pExpr->op2); - } + sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg, + pDef, pExpr->op2); if( nFarg ){ if( constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); @@ -4512,16 +4535,18 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ } case TK_SELECT_COLUMN: { int n; - if( pExpr->pLeft->iTable==0 ){ - pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft); + Expr *pLeft = pExpr->pLeft; + if( pLeft->iTable==0 || pParse->withinRJSubrtn > pLeft->op2 ){ + pLeft->iTable = sqlite3CodeSubselect(pParse, pLeft); + pLeft->op2 = pParse->withinRJSubrtn; } - assert( pExpr->pLeft->op==TK_SELECT || pExpr->pLeft->op==TK_ERROR ); - n = sqlite3ExprVectorSize(pExpr->pLeft); + assert( pLeft->op==TK_SELECT || pLeft->op==TK_ERROR ); + n = sqlite3ExprVectorSize(pLeft); if( pExpr->iTable!=n ){ sqlite3ErrorMsg(pParse, "%d columns assigned %d values", pExpr->iTable, n); } - return pExpr->pLeft->iTable + pExpr->iColumn; + return pLeft->iTable + pExpr->iColumn; } case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(pParse); @@ -4552,8 +4577,24 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ exprCodeBetween(pParse, pExpr, target, 0, 0); return target; } + case TK_COLLATE: { + if( !ExprHasProperty(pExpr, EP_Collate) + && ALWAYS(pExpr->pLeft) + && pExpr->pLeft->op==TK_FUNCTION + ){ + inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); + if( inReg!=target ){ + sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); + inReg = target; + } + sqlite3VdbeAddOp1(v, OP_ClrSubtype, inReg); + return inReg; + }else{ + pExpr = pExpr->pLeft; + goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. */ + } + } case TK_SPAN: - case TK_COLLATE: case TK_UPLUS: { pExpr = pExpr->pLeft; goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. OSSFuzz. */ @@ -4794,7 +4835,9 @@ int sqlite3ExprCodeRunJustOnce( struct ExprList_item *pItem; int i; for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ - if( pItem->reusable && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 ){ + if( pItem->fg.reusable + && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 + ){ return pItem->u.iConstExprReg; } } @@ -4817,7 +4860,7 @@ int sqlite3ExprCodeRunJustOnce( p = sqlite3ExprListAppend(pParse, p, pExpr); if( p ){ struct ExprList_item *pItem = &p->a[p->nExpr-1]; - pItem->reusable = regDest<0; + pItem->fg.reusable = regDest<0; if( regDest<0 ) regDest = ++pParse->nMem; pItem->u.iConstExprReg = regDest; } @@ -4951,7 +4994,7 @@ int sqlite3ExprCodeExprList( for(pItem=pList->a, i=0; ipExpr; #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( pItem->bSorterRef ){ + if( pItem->fg.bSorterRef ){ i--; n--; }else @@ -5045,8 +5088,8 @@ static void exprCodeBetween( ** so that the sqlite3ExprCodeTarget() routine will not attempt to move ** it into the Parse.pConstExpr list. We should use a new bit for this, ** for clarity, but we are out of bits in the Expr.flags field so we - ** have to reuse the EP_FromJoin bit. Bummer. */ - pDel->flags |= EP_FromJoin; + ** have to reuse the EP_OuterON bit. Bummer. */ + pDel->flags |= EP_OuterON; sqlite3ExprCodeTarget(pParse, &exprAnd, dest); } sqlite3ReleaseTempReg(pParse, regFree1); @@ -5576,7 +5619,7 @@ int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){ int res; Expr *pExprA = pA->a[i].pExpr; Expr *pExprB = pB->a[i].pExpr; - if( pA->a[i].sortFlags!=pB->a[i].sortFlags ) return 1; + if( pA->a[i].fg.sortFlags!=pB->a[i].fg.sortFlags ) return 1; if( (res = sqlite3ExprCompare(0, pExprA, pExprB, iTab)) ) return res; } return 0; @@ -5731,7 +5774,7 @@ int sqlite3ExprImpliesExpr( static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_AGG_FUNCTION ); - if( ExprHasProperty(pExpr, EP_FromJoin) ) return WRC_Prune; + if( ExprHasProperty(pExpr, EP_OuterON) ) return WRC_Prune; switch( pExpr->op ){ case TK_ISNOT: case TK_ISNULL: @@ -5828,8 +5871,8 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ ** False positives are not allowed, however. A false positive may result ** in an incorrect answer. ** -** Terms of p that are marked with EP_FromJoin (and hence that come from -** the ON or USING clauses of LEFT JOINS) are excluded from the analysis. +** Terms of p that are marked with EP_OuterON (and hence that come from +** the ON or USING clauses of OUTER JOINS) are excluded from the analysis. ** ** This routine is used to check if a LEFT JOIN can be converted into ** an ordinary JOIN. The p argument is the WHERE clause. If the WHERE diff --git a/src/fkey.c b/src/fkey.c index b464743c7..a05792514 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -390,7 +390,6 @@ static void fkLookupParent( }else{ int nCol = pFKey->nCol; int regTemp = sqlite3GetTempRange(pParse, nCol); - int regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); @@ -429,12 +428,11 @@ static void fkLookupParent( } sqlite3VdbeGoto(v, iOk); } - - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec, + + sqlite3VdbeAddOp4(v, OP_Affinity, regTemp, nCol, 0, sqlite3IndexAffinityStr(pParse->db,pIdx), nCol); - sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v); - - sqlite3ReleaseTempReg(pParse, regRec); + sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regTemp, nCol); + VdbeCoverage(v); sqlite3ReleaseTempRange(pParse, regTemp, nCol); } } @@ -536,14 +534,10 @@ static Expr *exprTableColumn( ** Operation | FK type | Action taken ** -------------------------------------------------------------------------- ** DELETE immediate Increment the "immediate constraint counter". -** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT immediate Decrement the "immediate constraint counter". ** ** DELETE deferred Increment the "deferred constraint counter". -** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT deferred Decrement the "deferred constraint counter". ** @@ -1191,9 +1185,9 @@ int sqlite3FkRequired( ** ** It returns a pointer to a Trigger structure containing a trigger ** equivalent to the ON UPDATE or ON DELETE action specified by pFKey. -** If the action is "NO ACTION" or "RESTRICT", then a NULL pointer is -** returned (these actions require no special handling by the triggers -** sub-system, code for them is created by fkScanChildren()). +** If the action is "NO ACTION" then a NULL pointer is returned (these actions +** require no special handling by the triggers sub-system, code for them is +** created by fkScanChildren()). ** ** For example, if pFKey is the foreign key and pTab is table "p" in ** the following schema: @@ -1322,18 +1316,23 @@ static Trigger *fkActionTrigger( nFrom = sqlite3Strlen30(zFrom); if( action==OE_Restrict ){ + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); Token tFrom; + Token tDb; Expr *pRaise; tFrom.z = zFrom; tFrom.n = nFrom; + tDb.z = db->aDb[iDb].zDbSName; + tDb.n = sqlite3Strlen30(tDb.z); + pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed"); if( pRaise ){ pRaise->affExpr = OE_Abort; } pSelect = sqlite3SelectNew(pParse, sqlite3ExprListAppend(pParse, 0, pRaise), - sqlite3SrcListAppend(pParse, 0, &tFrom, 0), + sqlite3SrcListAppend(pParse, 0, &tDb, &tFrom), pWhere, 0, 0, 0, 0, 0 ); diff --git a/src/func.c b/src/func.c index 7ccb5118e..ec50a17a3 100644 --- a/src/func.c +++ b/src/func.c @@ -2098,11 +2098,11 @@ static void logFunc( switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){ case 1: /* Convert from natural logarithm to log base 10 */ - ans *= 1.0/M_LN10; + ans /= M_LN10; break; case 2: /* Convert from natural logarithm to log base 2 */ - ans *= 1.0/M_LN2; + ans /= M_LN2; break; default: break; @@ -2241,8 +2241,7 @@ void sqlite3RegisterBuiltinFunctions(void){ INLINE_FUNC(likelihood, 2, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), INLINE_FUNC(likely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), #ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC - {1, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_OFFSET|SQLITE_FUNC_TYPEOF, - 0, 0, noopFunc, 0, 0, 0, "sqlite_offset", {0} }, + INLINE_FUNC(sqlite_offset, 1, INLINEFUNC_sqlite_offset, 0 ), #endif FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), diff --git a/src/global.c b/src/global.c index 6ad3bb465..9e3c07a51 100644 --- a/src/global.c +++ b/src/global.c @@ -290,6 +290,9 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0x7ffffffe, /* iOnceResetThreshold */ SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */ 0, /* iPrngSeed */ +#ifdef SQLITE_DEBUG + {0,0,0,0,0,0} /* aTune */ +#endif }; /* @@ -344,7 +347,7 @@ int sqlite3PendingByte = 0x40000000; /* ** Tracing flags set by SQLITE_TESTCTRL_TRACEFLAGS. */ -u32 sqlite3SelectTrace = 0; +u32 sqlite3TreeTrace = 0; u32 sqlite3WhereTrace = 0; #include "opcodes.h" diff --git a/src/insert.c b/src/insert.c index c0b247263..9b97b99a3 100644 --- a/src/insert.c +++ b/src/insert.c @@ -765,6 +765,14 @@ void sqlite3Insert( #endif assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Insert() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewInsert(pParse->pWith, pTabList, pColumn, pSelect, pList, + onError, pUpsert, pTrigger); + } +#endif + /* If pTab is really a view, make sure it has been initialized. ** ViewGetColumnNames() is a no-op if pTab is not a view. */ @@ -843,13 +851,15 @@ void sqlite3Insert( */ bIdListInOrder = (pTab->tabFlags & (TF_OOOHidden|TF_HasStored))==0; if( pColumn ){ + assert( pColumn->eU4!=EU4_EXPR ); + pColumn->eU4 = EU4_IDX; for(i=0; inId; i++){ - pColumn->a[i].idx = -1; + pColumn->a[i].u4.idx = -1; } for(i=0; inId; i++){ for(j=0; jnCol; j++){ if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zCnName)==0 ){ - pColumn->a[i].idx = j; + pColumn->a[i].u4.idx = j; if( i!=j ) bIdListInOrder = 0; if( j==pTab->iPKey ){ ipkColumn = i; assert( !withoutRowid ); @@ -1151,7 +1161,8 @@ void sqlite3Insert( } } if( pColumn ){ - for(j=0; jnId && pColumn->a[j].idx!=i; j++){} + assert( pColumn->eU4==EU4_IDX ); + for(j=0; jnId && pColumn->a[j].u4.idx!=i; j++){} if( j>=pColumn->nId ){ /* A column not named in the insert column list gets its ** default value */ @@ -2282,7 +2293,7 @@ void sqlite3GenerateConstraintChecks( if( isUpdate ){ /* If currently processing the PRIMARY KEY of a WITHOUT ROWID ** table, only conflict if the new PRIMARY KEY values are actually - ** different from the old. + ** different from the old. See TH3 withoutrowid04.test. ** ** For a UNIQUE index, only conflict if the PRIMARY KEY values ** of the matched index row are different from the original PRIMARY diff --git a/src/json.c b/src/json.c index 7dc55c84c..3636d3665 100644 --- a/src/json.c +++ b/src/json.c @@ -1626,7 +1626,7 @@ static JsonNode *jsonMergePatch( if( pPatch->eType!=JSON_OBJECT ){ return pPatch; } - assert( iTarget>=0 && iTargetnNode ); + assert( iTargetnNode ); pTarget = &pParse->aNode[iTarget]; assert( (pPatch->jnFlags & JNODE_APPEND)==0 ); if( pTarget->eType!=JSON_OBJECT ){ diff --git a/src/loadext.c b/src/loadext.c index 603516e18..dd15d9a4d 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -487,11 +487,28 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_autovacuum_pages, /* Version 3.38.0 and later */ sqlite3_error_offset, +#ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3_vtab_rhs_value, sqlite3_vtab_distinct, sqlite3_vtab_in, sqlite3_vtab_in_first, - sqlite3_vtab_in_next + sqlite3_vtab_in_next, +#else + 0, + 0, + 0, + 0, + 0, +#endif + /* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE + sqlite3_deserialize, + sqlite3_serialize, +#else + 0, + 0, +#endif + sqlite3_db_name }; /* True if x is the directory separator character diff --git a/src/main.c b/src/main.c index 8747f07b5..d905ac8ea 100644 --- a/src/main.c +++ b/src/main.c @@ -4097,6 +4097,28 @@ int sqlite3_test_control(int op, ...){ volatile int x = 0; assert( /*side-effects-ok*/ (x = va_arg(ap,int))!=0 ); rc = x; +#if defined(SQLITE_DEBUG) + /* Invoke these debugging routines so that the compiler does not + ** issue "defined but not used" warnings. */ + if( x==9999 ){ + sqlite3ShowExpr(0); + sqlite3ShowExpr(0); + sqlite3ShowExprList(0); + sqlite3ShowIdList(0); + sqlite3ShowSrcList(0); + sqlite3ShowWith(0); + sqlite3ShowUpsert(0); + sqlite3ShowTriggerStep(0); + sqlite3ShowTriggerStepList(0); + sqlite3ShowTrigger(0); + sqlite3ShowTriggerList(0); +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3ShowWindow(0); + sqlite3ShowWinFunc(0); +#endif + sqlite3ShowSelect(0); + } +#endif break; } @@ -4358,8 +4380,8 @@ int sqlite3_test_control(int op, ...){ ** ** "ptr" is a pointer to a u32. ** - ** op==0 Store the current sqlite3SelectTrace in *ptr - ** op==1 Set sqlite3SelectTrace to the value *ptr + ** op==0 Store the current sqlite3TreeTrace in *ptr + ** op==1 Set sqlite3TreeTrace to the value *ptr ** op==3 Store the current sqlite3WhereTrace in *ptr ** op==3 Set sqlite3WhereTrace to the value *ptr */ @@ -4367,10 +4389,10 @@ int sqlite3_test_control(int op, ...){ int opTrace = va_arg(ap, int); u32 *ptr = va_arg(ap, u32*); switch( opTrace ){ - case 0: *ptr = sqlite3SelectTrace; break; - case 1: sqlite3SelectTrace = *ptr; break; - case 2: *ptr = sqlite3WhereTrace; break; - case 3: sqlite3WhereTrace = *ptr; break; + case 0: *ptr = sqlite3TreeTrace; break; + case 1: sqlite3TreeTrace = *ptr; break; + case 2: *ptr = sqlite3WhereTrace; break; + case 3: sqlite3WhereTrace = *ptr; break; } break; } @@ -4387,10 +4409,12 @@ int sqlite3_test_control(int op, ...){ case SQLITE_TESTCTRL_LOGEST: { double rIn = va_arg(ap, double); LogEst rLogEst = sqlite3LogEstFromDouble(rIn); - u64 iInt = sqlite3LogEstToInt(rLogEst); - va_arg(ap, int*)[0] = rLogEst; - va_arg(ap, u64*)[0] = iInt; - va_arg(ap, int*)[0] = sqlite3LogEst(iInt); + int *pI1 = va_arg(ap,int*); + u64 *pU64 = va_arg(ap,u64*); + int *pI2 = va_arg(ap,int*); + *pI1 = rLogEst; + *pU64 = sqlite3LogEstToInt(rLogEst); + *pI2 = sqlite3LogEst(*pU64); break; } @@ -4605,6 +4629,24 @@ Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ return iDb<0 ? 0 : db->aDb[iDb].pBt; } +/* +** Return the name of the N-th database schema. Return NULL if N is out +** of range. +*/ +const char *sqlite3_db_name(sqlite3 *db, int N){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + if( N<0 || N>=db->nDb ){ + return 0; + }else{ + return db->aDb[N].zDbSName; + } +} + /* ** Return the filename of the database associated with a database ** connection. diff --git a/src/mem5.c b/src/mem5.c index c194a6b77..b61b93e11 100644 --- a/src/mem5.c +++ b/src/mem5.c @@ -420,8 +420,13 @@ static void *memsys5Realloc(void *pPrior, int nBytes){ */ static int memsys5Roundup(int n){ int iFullSz; - if( n > 0x40000000 ) return 0; - for(iFullSz=mem5.szAtom; iFullSz0x40000000 ) return 0; + for(iFullSz=mem5.szAtom*8; iFullSz=n ) return iFullSz/2; return iFullSz; } diff --git a/src/os.h b/src/os.h index 1a8eff329..aeb01721c 100644 --- a/src/os.h +++ b/src/os.h @@ -39,6 +39,13 @@ # define SQLITE_MAX_PATHLEN FILENAME_MAX #endif +/* Maximum number of symlinks that will be resolved while trying to +** expand a filename in xFullPathname() in the VFS. +*/ +#ifndef SQLITE_MAX_SYMLINK +# define SQLITE_MAX_SYMLINK 200 +#endif + /* ** The default size of a disk sector */ diff --git a/src/os_unix.c b/src/os_unix.c index e6deaa728..b933de376 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -6017,20 +6017,23 @@ static int findCreateFileMode( ** ** where NN is a decimal number. The NN naming schemes are ** used by the test_multiplex.c module. + ** + ** In normal operation, the journal file name will always contain + ** a '-' character. However in 8+3 filename mode, or if a corrupt + ** rollback journal specifies a super-journal with a goofy name, then + ** the '-' might be missing or the '-' might be the first character in + ** the filename. In that case, just return SQLITE_OK with *pMode==0. */ - nDb = sqlite3Strlen30(zPath) - 1; - while( zPath[nDb]!='-' ){ - /* In normal operation, the journal file name will always contain - ** a '-' character. However in 8+3 filename mode, or if a corrupt - ** rollback journal specifies a super-journal with a goofy name, then - ** the '-' might be missing. */ - if( nDb==0 || zPath[nDb]=='.' ) return SQLITE_OK; + nDb = sqlite3Strlen30(zPath) - 1; + while( nDb>0 && zPath[nDb]!='.' ){ + if( zPath[nDb]=='-' ){ + memcpy(zDb, zPath, nDb); + zDb[nDb] = '\0'; + rc = getFileMode(zDb, pMode, pUid, pGid); + break; + } nDb--; } - memcpy(zDb, zPath, nDb); - zDb[nDb] = '\0'; - - rc = getFileMode(zDb, pMode, pUid, pGid); }else if( flags & SQLITE_OPEN_DELETEONCLOSE ){ *pMode = 0600; }else if( flags & SQLITE_OPEN_URI ){ @@ -6420,86 +6423,99 @@ static int unixAccess( } /* -** If the last component of the pathname in z[0]..z[j-1] is something -** other than ".." then back it out and return true. If the last -** component is empty or if it is ".." then return false. +** A pathname under construction */ -static int unixBackupDir(const char *z, int *pJ){ - int j = *pJ; - int i; - if( j<=0 ) return 0; - for(i=j-1; i>0 && z[i-1]!='/'; i--){} - if( i==0 ) return 0; - if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0; - *pJ = i-1; - return 1; -} +typedef struct DbPath DbPath; +struct DbPath { + int rc; /* Non-zero following any error */ + int nSymlink; /* Number of symlinks resolved */ + char *zOut; /* Write the pathname here */ + int nOut; /* Bytes of space available to zOut[] */ + int nUsed; /* Bytes of zOut[] currently being used */ +}; + +/* Forward reference */ +static void appendAllPathElements(DbPath*,const char*); /* -** Convert a relative pathname into a full pathname. Also -** simplify the pathname as follows: -** -** Remove all instances of /./ -** Remove all isntances of /X/../ for any X +** Append a single path element to the DbPath under construction */ -static int mkFullPathname( - const char *zPath, /* Input path */ - char *zOut, /* Output buffer */ - int nOut /* Allocated size of buffer zOut */ +static void appendOnePathElement( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zName, /* Name to append to pPath. Not zero-terminated */ + int nName /* Number of significant bytes in zName */ ){ - int nPath = sqlite3Strlen30(zPath); - int iOff = 0; - int i, j; - if( zPath[0]!='/' ){ - if( osGetcwd(zOut, nOut-2)==0 ){ - return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); + assert( nName>0 ); + assert( zName!=0 ); + if( zName[0]=='.' ){ + if( nName==1 ) return; + if( zName[1]=='.' && nName==2 ){ + if( pPath->nUsed<=1 ){ + pPath->rc = SQLITE_ERROR; + return; + } + assert( pPath->zOut[0]=='/' ); + while( pPath->zOut[--pPath->nUsed]!='/' ){} + return; } - iOff = sqlite3Strlen30(zOut); - zOut[iOff++] = '/'; } - if( (iOff+nPath+1)>nOut ){ - /* SQLite assumes that xFullPathname() nul-terminates the output buffer - ** even if it returns an error. */ - zOut[iOff] = '\0'; - return SQLITE_CANTOPEN_BKPT; - } - sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath); - - /* Remove duplicate '/' characters. Except, two // at the beginning - ** of a pathname is allowed since this is important on windows. */ - for(i=j=1; zOut[i]; i++){ - zOut[j++] = zOut[i]; - while( zOut[i]=='/' && zOut[i+1]=='/' ) i++; + if( pPath->nUsed + nName + 2 >= pPath->nOut ){ + pPath->rc = SQLITE_ERROR; + return; } - zOut[j] = 0; - - assert( zOut[0]=='/' ); - for(i=j=0; zOut[i]; i++){ - if( zOut[i]=='/' ){ - /* Skip over internal "/." directory components */ - if( zOut[i+1]=='.' && zOut[i+2]=='/' ){ - i += 1; - continue; + pPath->zOut[pPath->nUsed++] = '/'; + memcpy(&pPath->zOut[pPath->nUsed], zName, nName); + pPath->nUsed += nName; +#if defined(HAVE_READLINK) && defined(HAVE_LSTAT) + if( pPath->rc==SQLITE_OK ){ + const char *zIn; + struct stat buf; + pPath->zOut[pPath->nUsed] = 0; + zIn = pPath->zOut; + if( osLstat(zIn, &buf)!=0 ){ + if( errno!=ENOENT ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); } - - /* If this is a "/.." directory component then back out the - ** previous term of the directory if it is something other than "..". - */ - if( zOut[i+1]=='.' - && zOut[i+2]=='.' - && zOut[i+3]=='/' - && unixBackupDir(zOut, &j) - ){ - i += 2; - continue; + }else if( S_ISLNK(buf.st_mode) ){ + ssize_t got; + char zLnk[SQLITE_MAX_PATHLEN+2]; + if( pPath->nSymlink++ > SQLITE_MAX_SYMLINK ){ + pPath->rc = SQLITE_CANTOPEN_BKPT; + return; } + got = osReadlink(zIn, zLnk, sizeof(zLnk)-2); + if( got<=0 || got>=(ssize_t)sizeof(zLnk)-2 ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); + return; + } + zLnk[got] = 0; + if( zLnk[0]=='/' ){ + pPath->nUsed = 0; + }else{ + pPath->nUsed -= nName + 1; + } + appendAllPathElements(pPath, zLnk); } - if( ALWAYS(j>=0) ) zOut[j] = zOut[i]; - j++; } - if( NEVER(j==0) ) zOut[j++] = '/'; - zOut[j] = 0; - return SQLITE_OK; +#endif +} + +/* +** Append all path elements in zPath to the DbPath under construction. +*/ +static void appendAllPathElements( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zPath /* Path to append to pPath. Is zero-terminated */ +){ + int i = 0; + int j = 0; + do{ + while( zPath[i] && zPath[i]!='/' ){ i++; } + if( i>j ){ + appendOnePathElement(pPath, &zPath[j], i-j); + } + j = i+1; + }while( zPath[i++] ); } /* @@ -6517,86 +6533,27 @@ static int unixFullPathname( int nOut, /* Size of output buffer in bytes */ char *zOut /* Output buffer */ ){ -#if !defined(HAVE_READLINK) || !defined(HAVE_LSTAT) - return mkFullPathname(zPath, zOut, nOut); -#else - int rc = SQLITE_OK; - int nByte; - int nLink = 0; /* Number of symbolic links followed so far */ - const char *zIn = zPath; /* Input path for each iteration of loop */ - char *zDel = 0; - - assert( pVfs->mxPathname==MAX_PATHNAME ); + DbPath path; UNUSED_PARAMETER(pVfs); - - /* It's odd to simulate an io-error here, but really this is just - ** using the io-error infrastructure to test that SQLite handles this - ** function failing. This function could fail if, for example, the - ** current working directory has been unlinked. - */ - SimulateIOError( return SQLITE_ERROR ); - - do { - - /* Call stat() on path zIn. Set bLink to true if the path is a symbolic - ** link, or false otherwise. */ - int bLink = 0; - struct stat buf; - if( osLstat(zIn, &buf)!=0 ){ - if( errno!=ENOENT ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); - } - }else{ - bLink = S_ISLNK(buf.st_mode); - } - - if( bLink ){ - nLink++; - if( zDel==0 ){ - zDel = sqlite3_malloc(nOut); - if( zDel==0 ) rc = SQLITE_NOMEM_BKPT; - }else if( nLink>=SQLITE_MAX_SYMLINKS ){ - rc = SQLITE_CANTOPEN_BKPT; - } - - if( rc==SQLITE_OK ){ - nByte = osReadlink(zIn, zDel, nOut-1); - if( nByte<0 ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); - }else{ - if( zDel[0]!='/' ){ - int n; - for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--); - if( nByte+n+1>nOut ){ - rc = SQLITE_CANTOPEN_BKPT; - }else{ - memmove(&zDel[n], zDel, nByte+1); - memcpy(zDel, zIn, n); - nByte += n; - } - } - zDel[nByte] = '\0'; - } - } - - zIn = zDel; - } - - assert( rc!=SQLITE_OK || zIn!=zOut || zIn[0]=='/' ); - if( rc==SQLITE_OK && zIn!=zOut ){ - rc = mkFullPathname(zIn, zOut, nOut); + path.rc = 0; + path.nUsed = 0; + path.nSymlink = 0; + path.nOut = nOut; + path.zOut = zOut; + if( zPath[0]!='/' ){ + char zPwd[SQLITE_MAX_PATHLEN+2]; + if( osGetcwd(zPwd, sizeof(zPwd)-2)==0 ){ + return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); } - if( bLink==0 ) break; - zIn = zOut; - }while( rc==SQLITE_OK ); - - sqlite3_free(zDel); - if( rc==SQLITE_OK && nLink ) rc = SQLITE_OK_SYMLINK; - return rc; -#endif /* HAVE_READLINK && HAVE_LSTAT */ + appendAllPathElements(&path, zPwd); + } + appendAllPathElements(&path, zPath); + zOut[path.nUsed] = 0; + if( path.rc || path.nUsed<2 ) return SQLITE_CANTOPEN_BKPT; + if( path.nSymlink ) return SQLITE_OK_SYMLINK; + return SQLITE_OK; } - #ifndef SQLITE_OMIT_LOAD_EXTENSION /* ** Interfaces for opening a shared library, finding entry points diff --git a/src/pager.c b/src/pager.c index 01086dcfa..95e6eb8af 100644 --- a/src/pager.c +++ b/src/pager.c @@ -681,6 +681,7 @@ struct Pager { u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */ u32 sectorSize; /* Assumed sector size during rollback */ Pgno mxPgno; /* Maximum allowed size of the database */ + Pgno lckPgno; /* Page number for the locking page */ i64 pageSize; /* Number of bytes in a page */ i64 journalSizeLimit; /* Size limit for persistent journal files */ char *zFilename; /* Name of the database file */ @@ -1667,7 +1668,7 @@ static int readJournalHdr( ** journal file descriptor is advanced to the next sector boundary before ** anything is written. The format is: ** -** + 4 bytes: PAGER_MJ_PGNO. +** + 4 bytes: PAGER_SJ_PGNO. ** + N bytes: super-journal filename in utf-8. ** + 4 bytes: N (length of super-journal name in bytes, no nul-terminator). ** + 4 bytes: super-journal name checksum. @@ -1715,7 +1716,7 @@ static int writeSuperJournal(Pager *pPager, const char *zSuper){ /* Write the super-journal data to the end of the journal file. If ** an error occurs, return the error code to the caller. */ - if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_MJ_PGNO(pPager)))) + if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_SJ_PGNO(pPager)))) || (0 != (rc = sqlite3OsWrite(pPager->jfd, zSuper, nSuper, iHdrOff+4))) || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper, nSuper))) || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper+4, cksum))) @@ -2225,7 +2226,7 @@ static u32 pager_cksum(Pager *pPager, const u8 *aData){ ** corrupted, SQLITE_DONE is returned. Data is considered corrupted in ** two circumstances: ** -** * If the record page-number is illegal (0 or PAGER_MJ_PGNO), or +** * If the record page-number is illegal (0 or PAGER_SJ_PGNO), or ** * If the record is being rolled back from the main journal file ** and the checksum field does not match the record content. ** @@ -2285,7 +2286,7 @@ static int pager_playback_one_page( ** it could cause invalid data to be written into the journal. We need to ** detect this invalid data (with high probability) and ignore it. */ - if( pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){ + if( pgno==0 || pgno==PAGER_SJ_PGNO(pPager) ){ assert( !isSavepnt ); return SQLITE_DONE; } @@ -2622,6 +2623,7 @@ static int pager_truncate(Pager *pPager, Pgno nPage){ memset(pTmp, 0, szPage); testcase( (newSize-szPage) == currentSize ); testcase( (newSize-szPage) > currentSize ); + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &newSize); rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage); } if( rc==SQLITE_OK ){ @@ -3743,6 +3745,7 @@ int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){ pPager->pTmpSpace = pNew; pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize); pPager->pageSize = pageSize; + pPager->lckPgno = (Pgno)(PENDING_BYTE/pageSize) + 1; }else{ sqlite3PageFree(pNew); } @@ -5512,7 +5515,7 @@ static int getPageNormal( if( pPg->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ - assert( pgno!=PAGER_MJ_PGNO(pPager) ); + assert( pgno!=PAGER_SJ_PGNO(pPager) ); pPager->aStat[PAGER_STAT_HIT]++; return SQLITE_OK; @@ -5523,7 +5526,7 @@ static int getPageNormal( ** (*) obsolete. Was: maximum page number is 2^31 ** (2) Never try to fetch the locking page */ - if( pgno==PAGER_MJ_PGNO(pPager) ){ + if( pgno==PAGER_SJ_PGNO(pPager) ){ rc = SQLITE_CORRUPT_BKPT; goto pager_acquire_err; } @@ -5922,7 +5925,7 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ /* We should never write to the journal file the page that ** contains the database locks. The following assert verifies ** that we do not. */ - assert( pPg->pgno!=PAGER_MJ_PGNO(pPager) ); + assert( pPg->pgno!=PAGER_SJ_PGNO(pPager) ); assert( pPager->journalHdr<=pPager->journalOff ); pData2 = pPg->pData; @@ -6101,7 +6104,7 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ Pgno pg = pg1+ii; PgHdr *pPage; if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ - if( pg!=PAGER_MJ_PGNO(pPager) ){ + if( pg!=PAGER_SJ_PGNO(pPager) ){ rc = sqlite3PagerGet(pPager, pg, &pPage, 0); if( rc==SQLITE_OK ){ rc = pager_write(pPage); @@ -6579,7 +6582,7 @@ int sqlite3PagerCommitPhaseOne( ** last page is never written out to disk, leaving the database file ** undersized. Fix this now if it is the case. */ if( pPager->dbSize>pPager->dbFileSize ){ - Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_MJ_PGNO(pPager)); + Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_SJ_PGNO(pPager)); assert( pPager->eState==PAGER_WRITER_DBMOD ); rc = pager_truncate(pPager, nNew); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; diff --git a/src/pager.h b/src/pager.h index 8d899bd1a..eb1306096 100644 --- a/src/pager.h +++ b/src/pager.h @@ -43,14 +43,15 @@ typedef struct Pager Pager; typedef struct PgHdr DbPage; /* -** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is +** Page number PAGER_SJ_PGNO is never used in an SQLite database (it is ** reserved for working around a windows/posix incompatibility). It is ** used in the journal to signify that the remainder of the journal file ** is devoted to storing a super-journal name - there are no more pages to ** roll back. See comments for function writeSuperJournal() in pager.c ** for details. */ -#define PAGER_MJ_PGNO(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) +#define PAGER_SJ_PGNO_COMPUTED(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) +#define PAGER_SJ_PGNO(x) ((x)->lckPgno) /* ** Allowed values for the flags parameter to sqlite3PagerOpen(). diff --git a/src/parse.y b/src/parse.y index 2680e640a..d627f22ba 100644 --- a/src/parse.y +++ b/src/parse.y @@ -570,7 +570,7 @@ selectnowith(A) ::= selectnowith(A) multiselect_op(Y) oneselect(Z). { Token x; x.n = 0; parserDoubleLinkSelect(pParse, pRhs); - pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0); + pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0); pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0); } if( pRhs ){ @@ -685,7 +685,7 @@ as(X) ::= . {X.n = 0; X.z = 0;} from(A) ::= . {A = 0;} from(A) ::= FROM seltablist(X). { A = X; - sqlite3SrcListShiftJoinType(A); + sqlite3SrcListShiftJoinType(pParse,A); } // "seltablist" is a "Select Table List" - the content of the FROM clause @@ -695,33 +695,35 @@ stl_prefix(A) ::= seltablist(A) joinop(Y). { if( ALWAYS(A && A->nSrc>0) ) A->a[A->nSrc-1].fg.jointype = (u8)Y; } stl_prefix(A) ::= . {A = 0;} -seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) as(Z) indexed_opt(I) - on_opt(N) using_opt(U). { - A = sqlite3SrcListAppendFromTerm(pParse,A,&Y,&D,&Z,0,N,U); +seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) as(Z) on_using(N). { + A = sqlite3SrcListAppendFromTerm(pParse,A,&Y,&D,&Z,0,&N); +} +seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) as(Z) indexed_by(I) on_using(N). { + A = sqlite3SrcListAppendFromTerm(pParse,A,&Y,&D,&Z,0,&N); sqlite3SrcListIndexedBy(pParse, A, &I); } -seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) - on_opt(N) using_opt(U). { - A = sqlite3SrcListAppendFromTerm(pParse,A,&Y,&D,&Z,0,N,U); +seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) on_using(N). { + A = sqlite3SrcListAppendFromTerm(pParse,A,&Y,&D,&Z,0,&N); sqlite3SrcListFuncArgs(pParse, A, E); } %ifndef SQLITE_OMIT_SUBQUERY - seltablist(A) ::= stl_prefix(A) LP select(S) RP - as(Z) on_opt(N) using_opt(U). { - A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,S,N,U); + seltablist(A) ::= stl_prefix(A) LP select(S) RP as(Z) on_using(N). { + A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,S,&N); } - seltablist(A) ::= stl_prefix(A) LP seltablist(F) RP - as(Z) on_opt(N) using_opt(U). { - if( A==0 && Z.n==0 && N==0 && U==0 ){ + seltablist(A) ::= stl_prefix(A) LP seltablist(F) RP as(Z) on_using(N). { + if( A==0 && Z.n==0 && N.pOn==0 && N.pUsing==0 ){ A = F; }else if( F->nSrc==1 ){ - A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,0,N,U); + A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,0,&N); if( A ){ SrcItem *pNew = &A->a[A->nSrc-1]; SrcItem *pOld = F->a; pNew->zName = pOld->zName; pNew->zDatabase = pOld->zDatabase; pNew->pSelect = pOld->pSelect; + if( pNew->pSelect && (pNew->pSelect->selFlags & SF_NestedFrom)!=0 ){ + pNew->fg.isNestedFrom = 1; + } if( pOld->fg.isTabFunc ){ pNew->u1.pFuncArg = pOld->u1.pFuncArg; pOld->u1.pFuncArg = 0; @@ -734,9 +736,9 @@ seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) sqlite3SrcListDelete(pParse->db, F); }else{ Select *pSubquery; - sqlite3SrcListShiftJoinType(F); + sqlite3SrcListShiftJoinType(pParse,F); pSubquery = sqlite3SelectNew(pParse,0,F,0,0,0,0,SF_NestedFrom,0); - A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,pSubquery,N,U); + A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,pSubquery,&N); } } %endif SQLITE_OMIT_SUBQUERY @@ -794,13 +796,14 @@ joinop(X) ::= JOIN_KW(A) nm(B) nm(C) JOIN. // // INSERT INTO tab SELECT * FROM aaa JOIN bbb WHERE true ON CONFLICT ... // -// The [AND] and [OR] precedence marks in the rules for on_opt cause the +// The [AND] and [OR] precedence marks in the rules for on_using cause the // ON in this context to always be interpreted as belonging to the JOIN. // -%type on_opt {Expr*} -%destructor on_opt {sqlite3ExprDelete(pParse->db, $$);} -on_opt(N) ::= ON expr(E). {N = E;} -on_opt(N) ::= . [OR] {N = 0;} +%type on_using {OnOrUsing} +//%destructor on_using {sqlite3ClearOnOrUsing(pParse->db, &$$);} +on_using(N) ::= ON expr(E). {N.pOn = E; N.pUsing = 0;} +on_using(N) ::= USING LP idlist(L) RP. {N.pOn = 0; N.pUsing = L;} +on_using(N) ::= . [OR] {N.pOn = 0; N.pUsing = 0;} // Note that this block abuses the Token type just a little. If there is // no "INDEXED BY" clause, the returned token is empty (z==0 && n==0). If @@ -813,15 +816,11 @@ on_opt(N) ::= . [OR] {N = 0;} // recognizes and interprets this as a special case. // %type indexed_opt {Token} +%type indexed_by {Token} indexed_opt(A) ::= . {A.z=0; A.n=0;} -indexed_opt(A) ::= INDEXED BY nm(X). {A = X;} -indexed_opt(A) ::= NOT INDEXED. {A.z=0; A.n=1;} - -%type using_opt {IdList*} -%destructor using_opt {sqlite3IdListDelete(pParse->db, $$);} -using_opt(U) ::= USING LP idlist(L) RP. {U = L;} -using_opt(U) ::= . {U = 0;} - +indexed_opt(A) ::= indexed_by(A). +indexed_by(A) ::= INDEXED BY nm(X). {A = X;} +indexed_by(A) ::= NOT INDEXED. {A.z=0; A.n=1;} %type orderby_opt {ExprList*} %destructor orderby_opt {sqlite3ExprListDelete(pParse->db, $$);} @@ -925,7 +924,18 @@ where_opt_ret(A) ::= WHERE expr(X) RETURNING selcollist(Y). cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F) where_opt_ret(W) orderby_opt(O) limit_opt(L). { sqlite3SrcListIndexedBy(pParse, X, &I); - X = sqlite3SrcListAppendList(pParse, X, F); + if( F ){ + SrcList *pFromClause = F; + if( pFromClause->nSrc>1 ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pFromClause,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pFromClause = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + X = sqlite3SrcListAppendList(pParse, X, pFromClause); + } sqlite3ExprListCheckLength(pParse,Y,"set list"); #ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT if( O || L ){ @@ -941,7 +951,18 @@ cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F) where_opt_ret(W). { sqlite3SrcListIndexedBy(pParse, X, &I); sqlite3ExprListCheckLength(pParse,Y,"set list"); - X = sqlite3SrcListAppendList(pParse, X, F); + if( F ){ + SrcList *pFromClause = F; + if( pFromClause->nSrc>1 ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pFromClause,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pFromClause = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + X = sqlite3SrcListAppendList(pParse, X, pFromClause); + } sqlite3Update(pParse,X,Y,W,R,0,0,0); } %endif @@ -1034,7 +1055,7 @@ idlist(A) ::= nm(Y). p->affExpr = 0; p->flags = EP_Leaf; ExprClearVVAProperties(p); - p->iAgg = -1; + /* p->iAgg = -1; // Not required */ p->pLeft = p->pRight = 0; p->pAggInfo = 0; memset(&p->x, 0, sizeof(p->x)); @@ -1219,6 +1240,14 @@ expr(A) ::= expr(A) IS NOT expr(Y). { A = sqlite3PExpr(pParse,TK_ISNOT,A,Y); binaryToUnaryIfNull(pParse, Y, A, TK_NOTNULL); } +expr(A) ::= expr(A) IS NOT DISTINCT FROM expr(Y). { + A = sqlite3PExpr(pParse,TK_IS,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_ISNULL); +} +expr(A) ::= expr(A) IS DISTINCT FROM expr(Y). { + A = sqlite3PExpr(pParse,TK_ISNOT,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_NOTNULL); +} expr(A) ::= NOT(B) expr(X). {A = sqlite3PExpr(pParse, @B, X, 0);/*A-overwrites-B*/} @@ -1264,7 +1293,8 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { ** regardless of the value of expr1. */ sqlite3ExprUnmapAndDelete(pParse, A); - A = sqlite3Expr(pParse->db, TK_INTEGER, N ? "1" : "0"); + A = sqlite3Expr(pParse->db, TK_STRING, N ? "true" : "false"); + if( A ) sqlite3ExprIdToTrueFalse(A); }else{ Expr *pRHS = Y->a[0].pExpr; if( Y->nExpr==1 && sqlite3ExprIsConstant(pRHS) && A->op!=TK_VECTOR ){ diff --git a/src/pcache.c b/src/pcache.c index 76cc4bb7a..14d1e7cde 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -551,8 +551,7 @@ void sqlite3PcacheDrop(PgHdr *p){ ** make it so. */ void sqlite3PcacheMakeDirty(PgHdr *p){ - assert( p->nRef>0 || p->pCache->bPurgeable==0 ); - testcase( p->nRef==0 ); + assert( p->nRef>0 ); assert( sqlite3PcachePageSanity(p) ); if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){ /*OPTIMIZATION-IF-FALSE*/ p->flags &= ~PGHDR_DONT_WRITE; diff --git a/src/pragma.c b/src/pragma.c index b13de5494..9860da86d 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -307,15 +307,16 @@ static void pragmaFunclistLine( int isBuiltin, /* True if this is a built-in function */ int showInternFuncs /* True if showing internal functions */ ){ + u32 mask = + SQLITE_DETERMINISTIC | + SQLITE_DIRECTONLY | + SQLITE_SUBTYPE | + SQLITE_INNOCUOUS | + SQLITE_FUNC_INTERNAL + ; + if( showInternFuncs ) mask = 0xffffffff; for(; p; p=p->pNext){ const char *zType; - static const u32 mask = - SQLITE_DETERMINISTIC | - SQLITE_DIRECTONLY | - SQLITE_SUBTYPE | - SQLITE_INNOCUOUS | - SQLITE_FUNC_INTERNAL - ; static const char *azEnc[] = { 0, "utf8", "utf16le", "utf16be" }; assert( SQLITE_FUNC_ENCMASK==0x3 ); @@ -807,7 +808,7 @@ void sqlite3Pragma( */ #ifndef SQLITE_OMIT_AUTOVACUUM case PragTyp_INCREMENTAL_VACUUM: { - int iLimit, addr; + int iLimit = 0, addr; if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){ iLimit = 0x7fffffff; } @@ -1495,7 +1496,6 @@ void sqlite3Pragma( HashElem *k; /* Loop counter: Next table in schema */ int x; /* result variable */ int regResult; /* 3 registers to hold a result row */ - int regKey; /* Register to hold key for checking the FK */ int regRow; /* Registers to hold a row from pTab */ int addrTop; /* Top of a loop checking foreign keys */ int addrOk; /* Jump here if the key is OK */ @@ -1503,7 +1503,6 @@ void sqlite3Pragma( regResult = pParse->nMem+1; pParse->nMem += 4; - regKey = ++pParse->nMem; regRow = ++pParse->nMem; k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash); while( k ){ @@ -1570,9 +1569,9 @@ void sqlite3Pragma( /* Generate code to query the parent index for a matching parent ** key. If a match is found, jump to addrOk. */ if( pIdx ){ - sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey, + sqlite3VdbeAddOp4(v, OP_Affinity, regRow, pFK->nCol, 0, sqlite3IndexAffinityStr(db,pIdx), pFK->nCol); - sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0); + sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regRow, pFK->nCol); VdbeCoverage(v); }else if( pParent ){ int jmp = sqlite3VdbeCurrentAddr(v)+2; diff --git a/src/pragma.h b/src/pragma.h index 6eb05763c..7270db1db 100644 --- a/src/pragma.h +++ b/src/pragma.h @@ -251,7 +251,7 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) {/* zName: */ "database_list", /* ePragTyp: */ PragTyp_DATABASE_LIST, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, + /* ePragFlg: */ PragFlg_Result0, /* ColNames: */ 47, 3, /* iArg: */ 0 }, #endif diff --git a/src/printf.c b/src/printf.c index 7bf6f2aeb..3602e1fcb 100644 --- a/src/printf.c +++ b/src/printf.c @@ -803,8 +803,8 @@ void sqlite3_str_vappendf( case etSQLESCAPE: /* %q: Escape ' characters */ case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */ case etSQLESCAPE3: { /* %w: Escape " characters */ - int i, j, k, n, isnull; - int needQuote; + i64 i, j, k, n; + int needQuote, isnull; char ch; char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ char *escarg; @@ -884,8 +884,14 @@ void sqlite3_str_vappendf( sqlite3_str_appendall(pAccum, pItem->zName); }else if( pItem->zAlias ){ sqlite3_str_appendall(pAccum, pItem->zAlias); - }else if( ALWAYS(pItem->pSelect) ){ - sqlite3_str_appendf(pAccum, "SUBQUERY %u", pItem->pSelect->selId); + }else{ + Select *pSel = pItem->pSelect; + assert( pSel!=0 ); + if( pSel->selFlags & SF_NestedFrom ){ + sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId); + }else{ + sqlite3_str_appendf(pAccum, "(subquery-%u)", pSel->selId); + } } length = width = 0; break; @@ -948,7 +954,9 @@ void sqlite3RecordErrorByteOffset(sqlite3 *db, const char *z){ ** as the error offset. */ void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){ - while( pExpr && (ExprHasProperty(pExpr,EP_FromJoin) || pExpr->w.iOfst<=0) ){ + while( pExpr + && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0) + ){ pExpr = pExpr->pLeft; } if( pExpr==0 ) return; diff --git a/src/resolve.c b/src/resolve.c index 480694f6f..9512e3a42 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -85,51 +85,24 @@ static void resolveAlias( sqlite3ExprDelete(db, pDup); pDup = 0; }else{ + Expr temp; incrAggFunctionDepth(pDup, nSubquery); if( pExpr->op==TK_COLLATE ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken); } - - /* Before calling sqlite3ExprDelete(), set the EP_Static flag. This - ** prevents ExprDelete() from deleting the Expr structure itself, - ** allowing it to be repopulated by the memcpy() on the following line. - ** The pExpr->u.zToken might point into memory that will be freed by the - ** sqlite3DbFree(db, pDup) on the last line of this block, so be sure to - ** make a copy of the token before doing the sqlite3DbFree(). - */ - ExprSetProperty(pExpr, EP_Static); - sqlite3ExprDelete(db, pExpr); - memcpy(pExpr, pDup, sizeof(*pExpr)); - if( !ExprHasProperty(pExpr, EP_IntValue) && pExpr->u.zToken!=0 ){ - assert( (pExpr->flags & (EP_Reduced|EP_TokenOnly))==0 ); - pExpr->u.zToken = sqlite3DbStrDup(db, pExpr->u.zToken); - pExpr->flags |= EP_MemToken; - } + memcpy(&temp, pDup, sizeof(Expr)); + memcpy(pDup, pExpr, sizeof(Expr)); + memcpy(pExpr, &temp, sizeof(Expr)); if( ExprHasProperty(pExpr, EP_WinFunc) ){ if( ALWAYS(pExpr->y.pWin!=0) ){ pExpr->y.pWin->pOwner = pExpr; } } - sqlite3DbFree(db, pDup); - } -} - - -/* -** Return TRUE if the name zCol occurs anywhere in the USING clause. -** -** Return FALSE if the USING clause is NULL or if it does not contain -** zCol. -*/ -static int nameInUsingClause(IdList *pUsing, const char *zCol){ - if( pUsing ){ - int k; - for(k=0; knId; k++){ - if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1; - } + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprDelete, + pDup); } - return 0; } /* @@ -147,7 +120,7 @@ int sqlite3MatchEName( ){ int n; const char *zSpan; - if( pItem->eEName!=ENAME_TAB ) return 0; + if( pItem->fg.eEName!=ENAME_TAB ) return 0; zSpan = pItem->zEName; for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){} if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){ @@ -208,6 +181,29 @@ Bitmask sqlite3ExprColUsed(Expr *pExpr){ } } +/* +** Create a new expression term for the column specified by pMatch and +** iColumn. Append this new expression term to the FULL JOIN Match set +** in *ppList. Create a new *ppList if this is the first term in the +** set. +*/ +static void extendFJMatch( + Parse *pParse, /* Parsing context */ + ExprList **ppList, /* ExprList to extend */ + SrcItem *pMatch, /* Source table containing the column */ + i16 iColumn /* The column number */ +){ + Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0); + if( pNew ){ + pNew->iTable = pMatch->iCursor; + pNew->iColumn = iColumn; + pNew->y.pTab = pMatch->pTab; + assert( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ); + ExprSetProperty(pNew, EP_CanBeNull); + *ppList = sqlite3ExprListAppend(pParse, *ppList, pNew); + } +} + /* ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up ** that name in the set of source tables in pSrcList and make the pExpr @@ -253,11 +249,13 @@ static int lookupName( NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */ - Table *pTab = 0; /* Table hold the row */ + Table *pTab = 0; /* Table holding the row */ Column *pCol; /* A column of pTab */ + ExprList *pFJMatch = 0; /* Matches for FULL JOIN .. USING */ assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ + assert( zDb==0 || zTab!=0 ); assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); /* Initialize the node to no-match */ @@ -306,26 +304,65 @@ static int lookupName( pTab = pItem->pTab; assert( pTab!=0 && pTab->zName!=0 ); assert( pTab->nCol>0 || pParse->nErr ); - if( pItem->pSelect && (pItem->pSelect->selFlags & SF_NestedFrom)!=0 ){ + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + if( pItem->fg.isNestedFrom ){ + /* In this case, pItem is a subquery that has been formed from a + ** parenthesized subset of the FROM clause terms. Example: + ** .... FROM t1 LEFT JOIN (t2 RIGHT JOIN t3 USING(x)) USING(y) ... + ** \_________________________/ + ** This pItem -------------^ + */ int hit = 0; + assert( pItem->pSelect!=0 ); pEList = pItem->pSelect->pEList; + assert( pEList!=0 ); + assert( pEList->nExpr==pTab->nCol ); for(j=0; jnExpr; j++){ - if( sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){ - cnt++; - cntTab = 2; - pMatch = pItem; - pExpr->iColumn = j; - hit = 1; + if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){ + continue; } + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } + } + cnt++; + cntTab = 2; + pMatch = pItem; + pExpr->iColumn = j; + pEList->a[j].fg.bUsed = 1; + hit = 1; + if( pEList->a[j].fg.bUsingTerm ) break; } if( hit || zTab==0 ) continue; } - if( zDb ){ - if( pTab->pSchema!=pSchema ) continue; - if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue; - } + assert( zDb==0 || zTab!=0 ); if( zTab ){ - const char *zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName; + const char *zTabName; + if( zDb ){ + if( pTab->pSchema!=pSchema ) continue; + if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue; + } + zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName; assert( zTabName!=0 ); if( sqlite3StrICmp(zTabName, zTab)!=0 ){ continue; @@ -340,18 +377,37 @@ static int lookupName( if( pCol->hName==hCol && sqlite3StrICmp(pCol->zCnName, zCol)==0 ){ - /* If there has been exactly one prior match and this match - ** is for the right-hand table of a NATURAL JOIN or is in a - ** USING clause, then skip this match. - */ - if( cnt==1 ){ - if( pItem->fg.jointype & JT_NATURAL ) continue; - if( nameInUsingClause(pItem->pUsing, zCol) ) continue; + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } } cnt++; pMatch = pItem; /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j; + if( pItem->fg.isNestedFrom ){ + sqlite3SrcItemColumnUsed(pItem, j); + } break; } } @@ -364,9 +420,7 @@ static int lookupName( pExpr->iTable = pMatch->iCursor; assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pMatch->pTab; - /* RIGHT JOIN not (yet) supported */ - assert( (pMatch->fg.jointype & JT_RIGHT)==0 ); - if( (pMatch->fg.jointype & JT_LEFT)!=0 ){ + if( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ){ ExprSetProperty(pExpr, EP_CanBeNull); } pSchema = pExpr->y.pTab->pSchema; @@ -520,7 +574,7 @@ static int lookupName( assert( pEList!=0 ); for(j=0; jnExpr; j++){ char *zAs = pEList->a[j].zEName; - if( pEList->a[j].eEName==ENAME_NAME + if( pEList->a[j].fg.eEName==ENAME_NAME && sqlite3_stricmp(zAs, zCol)==0 ){ Expr *pOrig; @@ -607,11 +661,37 @@ static int lookupName( } /* - ** cnt==0 means there was not match. cnt>1 means there were two or - ** more matches. Either way, we have an error. + ** cnt==0 means there was not match. + ** cnt>1 means there were two or more matches. + ** + ** cnt==0 is always an error. cnt>1 is often an error, but might + ** be multiple matches for a NATURAL LEFT JOIN or a LEFT JOIN USING. */ + assert( pFJMatch==0 || cnt>0 ); + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); if( cnt!=1 ){ const char *zErr; + if( pFJMatch ){ + if( pFJMatch->nExpr==cnt-1 ){ + if( ExprHasProperty(pExpr,EP_Leaf) ){ + ExprClearProperty(pExpr,EP_Leaf); + }else{ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + } + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + pExpr->op = TK_FUNCTION; + pExpr->u.zToken = "coalesce"; + pExpr->x.pList = pFJMatch; + cnt = 1; + goto lookupname_end; + }else{ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + } + } zErr = cnt==0 ? "no such column" : "ambiguous column name"; if( zDb ){ sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol); @@ -624,6 +704,16 @@ static int lookupName( pParse->checkSchema = 1; pTopNC->nNcErr++; } + assert( pFJMatch==0 ); + + /* Remove all substructure from pExpr */ + if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + ExprSetProperty(pExpr, EP_Leaf); + } /* If a column from a table in pSrcList is referenced, then record ** this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes @@ -643,16 +733,7 @@ static int lookupName( pMatch->colUsed |= sqlite3ExprColUsed(pExpr); } - /* Clean up and return - */ - if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ - sqlite3ExprDelete(db, pExpr->pLeft); - pExpr->pLeft = 0; - sqlite3ExprDelete(db, pExpr->pRight); - pExpr->pRight = 0; - } pExpr->op = eNewExprOp; - ExprSetProperty(pExpr, EP_Leaf); lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); @@ -837,7 +918,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } sqlite3WalkExpr(pWalker, pExpr->pLeft); if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ - testcase( ExprHasProperty(pExpr, EP_FromJoin) ); + testcase( ExprHasProperty(pExpr, EP_OuterON) ); assert( !ExprHasProperty(pExpr, EP_IntValue) ); if( pExpr->op==TK_NOTNULL ){ pExpr->u.zToken = "true"; @@ -1246,7 +1327,7 @@ static int resolveAsName( assert( !ExprHasProperty(pE, EP_IntValue) ); zCol = pE->u.zToken; for(i=0; inExpr; i++){ - if( pEList->a[i].eEName==ENAME_NAME + if( pEList->a[i].fg.eEName==ENAME_NAME && sqlite3_stricmp(pEList->a[i].zEName, zCol)==0 ){ return i+1; @@ -1367,7 +1448,7 @@ static int resolveCompoundOrderBy( return 1; } for(i=0; inExpr; i++){ - pOrderBy->a[i].done = 0; + pOrderBy->a[i].fg.done = 0; } pSelect->pNext = 0; while( pSelect->pPrior ){ @@ -1382,7 +1463,7 @@ static int resolveCompoundOrderBy( for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ int iCol = -1; Expr *pE, *pDup; - if( pItem->done ) continue; + if( pItem->fg.done ) continue; pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr); if( NEVER(pE==0) ) continue; if( sqlite3ExprIsInteger(pE, &iCol) ){ @@ -1435,7 +1516,7 @@ static int resolveCompoundOrderBy( sqlite3ExprDelete(db, pE); pItem->u.x.iOrderByCol = (u16)iCol; } - pItem->done = 1; + pItem->fg.done = 1; }else{ moreToDo = 1; } @@ -1443,7 +1524,7 @@ static int resolveCompoundOrderBy( pSelect = pSelect->pNext; } for(i=0; inExpr; i++){ - if( pOrderBy->a[i].done==0 ){ + if( pOrderBy->a[i].fg.done==0 ){ sqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any " "column in the result set", i+1); return 1; @@ -1733,8 +1814,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ sNC.uNC.pEList = p->pEList; sNC.ncFlags |= NC_UEList; if( p->pHaving ){ - if( !pGroupBy ){ - sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING"); + if( (p->selFlags & SF_Aggregate)==0 ){ + sqlite3ErrorMsg(pParse, "HAVING clause on a non-aggregate query"); return WRC_Abort; } if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; diff --git a/src/select.c b/src/select.c index d6d609740..7d0c5f8e5 100644 --- a/src/select.c +++ b/src/select.c @@ -21,7 +21,7 @@ */ typedef struct DistinctCtx DistinctCtx; struct DistinctCtx { - u8 isTnct; /* True if the DISTINCT keyword is present */ + u8 isTnct; /* 0: Not distinct. 1: DISTICT 2: DISTINCT and ORDER BY */ u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ int tabTnct; /* Ephemeral table used for DISTINCT processing */ int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ @@ -204,6 +204,52 @@ static Select *findRightmost(Select *p){ ** ** If an illegal or unsupported join type is seen, then still return ** a join type, but put an error in the pParse structure. +** +** These are the valid join types: +** +** +** pA pB pC Return Value +** ------- ----- ----- ------------ +** CROSS - - JT_CROSS +** INNER - - JT_INNER +** LEFT - - JT_LEFT|JT_OUTER +** LEFT OUTER - JT_LEFT|JT_OUTER +** RIGHT - - JT_RIGHT|JT_OUTER +** RIGHT OUTER - JT_RIGHT|JT_OUTER +** FULL - - JT_LEFT|JT_RIGHT|JT_OUTER +** FULL OUTER - JT_LEFT|JT_RIGHT|JT_OUTER +** NATURAL INNER - JT_NATURAL|JT_INNER +** NATURAL LEFT - JT_NATURAL|JT_LEFT|JT_OUTER +** NATURAL LEFT OUTER JT_NATURAL|JT_LEFT|JT_OUTER +** NATURAL RIGHT - JT_NATURAL|JT_RIGHT|JT_OUTER +** NATURAL RIGHT OUTER JT_NATURAL|JT_RIGHT|JT_OUTER +** NATURAL FULL - JT_NATURAL|JT_LEFT|JT_RIGHT +** NATURAL FULL OUTER JT_NATRUAL|JT_LEFT|JT_RIGHT +** +** To preserve historical compatibly, SQLite also accepts a variety +** of other non-standard and in many cases non-sensical join types. +** This routine makes as much sense at it can from the nonsense join +** type and returns a result. Examples of accepted nonsense join types +** include but are not limited to: +** +** INNER CROSS JOIN -> same as JOIN +** NATURAL CROSS JOIN -> same as NATURAL JOIN +** OUTER LEFT JOIN -> same as LEFT JOIN +** LEFT NATURAL JOIN -> same as NATURAL LEFT JOIN +** LEFT RIGHT JOIN -> same as FULL JOIN +** RIGHT OUTER FULL JOIN -> same as FULL JOIN +** CROSS CROSS CROSS JOIN -> same as JOIN +** +** The only restrictions on the join type name are: +** +** * "INNER" cannot appear together with "OUTER", "LEFT", "RIGHT", +** or "FULL". +** +** * "CROSS" cannot appear together with "OUTER", "LEFT", "RIGHT, +** or "FULL". +** +** * If "OUTER" is present then there must also be one of +** "LEFT", "RIGHT", or "FULL" */ int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ int jointype = 0; @@ -216,13 +262,13 @@ int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ u8 nChar; /* Length of the keyword in characters */ u8 code; /* Join type mask */ } aKeyword[] = { - /* natural */ { 0, 7, JT_NATURAL }, - /* left */ { 6, 4, JT_LEFT|JT_OUTER }, - /* outer */ { 10, 5, JT_OUTER }, - /* right */ { 14, 5, JT_RIGHT|JT_OUTER }, - /* full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER }, - /* inner */ { 23, 5, JT_INNER }, - /* cross */ { 28, 5, JT_INNER|JT_CROSS }, + /* (0) natural */ { 0, 7, JT_NATURAL }, + /* (1) left */ { 6, 4, JT_LEFT|JT_OUTER }, + /* (2) outer */ { 10, 5, JT_OUTER }, + /* (3) right */ { 14, 5, JT_RIGHT|JT_OUTER }, + /* (4) full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER }, + /* (5) inner */ { 23, 5, JT_INNER }, + /* (6) cross */ { 28, 5, JT_INNER|JT_CROSS }, }; int i, j; apAll[0] = pA; @@ -245,18 +291,15 @@ int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ } if( (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) || - (jointype & JT_ERROR)!=0 + (jointype & JT_ERROR)!=0 || + (jointype & (JT_OUTER|JT_LEFT|JT_RIGHT))==JT_OUTER ){ - const char *zSp = " "; - assert( pB!=0 ); - if( pC==0 ){ zSp++; } - sqlite3ErrorMsg(pParse, "unknown or unsupported join type: " - "%T %T%s%T", pA, pB, zSp, pC); - jointype = JT_INNER; - }else if( (jointype & JT_OUTER)!=0 - && (jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ){ - sqlite3ErrorMsg(pParse, - "RIGHT and FULL OUTER JOINs are not currently supported"); + const char *zSp1 = " "; + const char *zSp2 = " "; + if( pB==0 ){ zSp1++; } + if( pC==0 ){ zSp2++; } + sqlite3ErrorMsg(pParse, "unknown join type: " + "%T%s%T%s%T", pA, zSp1, pB, zSp2, pC); jointype = JT_INNER; } return jointype; @@ -277,8 +320,25 @@ int sqlite3ColumnIndex(Table *pTab, const char *zCol){ } /* -** Search the first N tables in pSrc, from left to right, looking for a -** table that has a column named zCol. +** Mark a subquery result column as having been used. +*/ +void sqlite3SrcItemColumnUsed(SrcItem *pItem, int iCol){ + assert( pItem!=0 ); + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + if( pItem->fg.isNestedFrom ){ + ExprList *pResults; + assert( pItem->pSelect!=0 ); + pResults = pItem->pSelect->pEList; + assert( pResults!=0 ); + assert( iCol>=0 && iColnExpr ); + pResults->a[iCol].fg.bUsed = 1; + } +} + +/* +** Search the tables iStart..iEnd (inclusive) in pSrc, looking for a +** table that has a column named zCol. The search is left-to-right. +** The first match found is returned. ** ** When found, set *piTab and *piCol to the table index and column index ** of the matching column and return TRUE. @@ -287,22 +347,27 @@ int sqlite3ColumnIndex(Table *pTab, const char *zCol){ */ static int tableAndColumnIndex( SrcList *pSrc, /* Array of tables to search */ - int N, /* Number of tables in pSrc->a[] to search */ + int iStart, /* First member of pSrc->a[] to check */ + int iEnd, /* Last member of pSrc->a[] to check */ const char *zCol, /* Name of the column we are looking for */ int *piTab, /* Write index of pSrc->a[] here */ int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */ - int bIgnoreHidden /* True to ignore hidden columns */ + int bIgnoreHidden /* Ignore hidden columns */ ){ int i; /* For looping over tables in pSrc */ int iCol; /* Index of column matching zCol */ + assert( iEndnSrc ); + assert( iStart>=0 ); assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */ - for(i=0; ia[i].pTab, zCol); if( iCol>=0 && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0) ){ if( piTab ){ + sqlite3SrcItemColumnUsed(&pSrc->a[i], iCol); *piTab = i; *piCol = iCol; } @@ -313,66 +378,19 @@ static int tableAndColumnIndex( } /* -** This function is used to add terms implied by JOIN syntax to the -** WHERE clause expression of a SELECT statement. The new term, which -** is ANDed with the existing WHERE clause, is of the form: -** -** (tab1.col1 = tab2.col2) -** -** where tab1 is the iSrc'th table in SrcList pSrc and tab2 is the -** (iSrc+1)'th. Column col1 is column iColLeft of tab1, and col2 is -** column iColRight of tab2. -*/ -static void addWhereTerm( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* List of tables in FROM clause */ - int iLeft, /* Index of first table to join in pSrc */ - int iColLeft, /* Index of column in first table */ - int iRight, /* Index of second table in pSrc */ - int iColRight, /* Index of column in second table */ - int isOuterJoin, /* True if this is an OUTER join */ - Expr **ppWhere /* IN/OUT: The WHERE clause to add to */ -){ - sqlite3 *db = pParse->db; - Expr *pE1; - Expr *pE2; - Expr *pEq; - - assert( iLeftnSrc>iRight ); - assert( pSrc->a[iLeft].pTab ); - assert( pSrc->a[iRight].pTab ); - - pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft); - pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight); - - pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); - assert( pE2!=0 || pEq==0 ); /* Due to db->mallocFailed test - ** in sqlite3DbMallocRawNN() called from - ** sqlite3PExpr(). */ - if( pEq && isOuterJoin ){ - ExprSetProperty(pEq, EP_FromJoin); - assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); - ExprSetVVAProperty(pEq, EP_NoReduce); - pEq->w.iRightJoinTable = pE2->iTable; - } - *ppWhere = sqlite3ExprAnd(pParse, *ppWhere, pEq); -} - -/* -** Set the EP_FromJoin property on all terms of the given expression. -** And set the Expr.w.iRightJoinTable to iTable for every term in the +** Set the EP_OuterON property on all terms of the given expression. +** And set the Expr.w.iJoin to iTable for every term in the ** expression. ** -** The EP_FromJoin property is used on terms of an expression to tell -** the LEFT OUTER JOIN processing logic that this term is part of the +** The EP_OuterON property is used on terms of an expression to tell +** the OUTER JOIN processing logic that this term is part of the ** join restriction specified in the ON or USING clause and not a part ** of the more general WHERE clause. These terms are moved over to the ** WHERE clause during join processing but we need to remember that they ** originated in the ON or USING clause. ** -** The Expr.w.iRightJoinTable tells the WHERE clause processing that the -** expression depends on table w.iRightJoinTable even if that table is not +** The Expr.w.iJoin tells the WHERE clause processing that the +** expression depends on table w.iJoin even if that table is not ** explicitly mentioned in the expression. That information is needed ** for cases like this: ** @@ -385,39 +403,48 @@ static void addWhereTerm( ** after the t1 loop and rows with t1.x!=5 will never appear in ** the output, which is incorrect. */ -void sqlite3SetJoinExpr(Expr *p, int iTable){ +void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){ + assert( joinFlag==EP_OuterON || joinFlag==EP_InnerON ); while( p ){ - ExprSetProperty(p, EP_FromJoin); + ExprSetProperty(p, joinFlag); assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(p, EP_NoReduce); - p->w.iRightJoinTable = iTable; + p->w.iJoin = iTable; if( p->op==TK_FUNCTION ){ assert( ExprUseXList(p) ); if( p->x.pList ){ int i; for(i=0; ix.pList->nExpr; i++){ - sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable); + sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag); } } } - sqlite3SetJoinExpr(p->pLeft, iTable); + sqlite3SetJoinExpr(p->pLeft, iTable, joinFlag); p = p->pRight; } } -/* Undo the work of sqlite3SetJoinExpr(). In the expression p, convert every -** term that is marked with EP_FromJoin and w.iRightJoinTable==iTable into -** an ordinary term that omits the EP_FromJoin mark. +/* Undo the work of sqlite3SetJoinExpr(). This is used when a LEFT JOIN +** is simplified into an ordinary JOIN, and when an ON expression is +** "pushed down" into the WHERE clause of a subquery. +** +** Convert every term that is marked with EP_OuterON and w.iJoin==iTable into +** an ordinary term that omits the EP_OuterON mark. Or if iTable<0, then +** just clear every EP_OuterON and EP_InnerON mark from the expression tree. ** -** This happens when a LEFT JOIN is simplified into an ordinary JOIN. +** If nullable is true, that means that Expr p might evaluate to NULL even +** if it is a reference to a NOT NULL column. This can happen, for example, +** if the table that p references is on the left side of a RIGHT JOIN. +** If nullable is true, then take care to not remove the EP_CanBeNull bit. +** See forum thread https://sqlite.org/forum/forumpost/b40696f50145d21c */ -static void unsetJoinExpr(Expr *p, int iTable){ +static void unsetJoinExpr(Expr *p, int iTable, int nullable){ while( p ){ - if( ExprHasProperty(p, EP_FromJoin) - && (iTable<0 || p->w.iRightJoinTable==iTable) ){ - ExprClearProperty(p, EP_FromJoin); + if( iTable<0 || (ExprHasProperty(p, EP_OuterON) && p->w.iJoin==iTable) ){ + ExprClearProperty(p, EP_OuterON|EP_InnerON); + if( iTable>=0 ) ExprSetProperty(p, EP_InnerON); } - if( p->op==TK_COLUMN && p->iTable==iTable ){ + if( p->op==TK_COLUMN && p->iTable==iTable && !nullable ){ ExprClearProperty(p, EP_CanBeNull); } if( p->op==TK_FUNCTION ){ @@ -425,30 +452,37 @@ static void unsetJoinExpr(Expr *p, int iTable){ if( p->x.pList ){ int i; for(i=0; ix.pList->nExpr; i++){ - unsetJoinExpr(p->x.pList->a[i].pExpr, iTable); + unsetJoinExpr(p->x.pList->a[i].pExpr, iTable, nullable); } } } - unsetJoinExpr(p->pLeft, iTable); + unsetJoinExpr(p->pLeft, iTable, nullable); p = p->pRight; } } /* ** This routine processes the join information for a SELECT statement. -** ON and USING clauses are converted into extra terms of the WHERE clause. -** NATURAL joins also create extra WHERE clause terms. +** +** * A NATURAL join is converted into a USING join. After that, we +** do not need to be concerned with NATURAL joins and we only have +** think about USING joins. +** +** * ON and USING clauses result in extra terms being added to the +** WHERE clause to enforce the specified constraints. The extra +** WHERE clause terms will be tagged with EP_OuterON or +** EP_InnerON so that we know that they originated in ON/USING. ** ** The terms of a FROM clause are contained in the Select.pSrc structure. ** The left most table is the first entry in Select.pSrc. The right-most ** table is the last entry. The join operator is held in the entry to -** the left. Thus entry 0 contains the join operator for the join between +** the right. Thus entry 1 contains the join operator for the join between ** entries 0 and 1. Any ON or USING clauses associated with the join are -** also attached to the left entry. +** also attached to the right entry. ** ** This routine returns the number of errors encountered. */ -static int sqliteProcessJoin(Parse *pParse, Select *p){ +static int sqlite3ProcessJoin(Parse *pParse, Select *p){ SrcList *pSrc; /* All tables in the FROM clause */ int i, j; /* Loop counters */ SrcItem *pLeft; /* Left table being joined */ @@ -459,49 +493,41 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){ pRight = &pLeft[1]; for(i=0; inSrc-1; i++, pRight++, pLeft++){ Table *pRightTab = pRight->pTab; - int isOuter; + u32 joinType; if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue; - isOuter = (pRight->fg.jointype & JT_OUTER)!=0; + joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON; - /* When the NATURAL keyword is present, add WHERE clause terms for - ** every column that the two tables have in common. + /* If this is a NATURAL join, synthesize an approprate USING clause + ** to specify which columns should be joined. */ if( pRight->fg.jointype & JT_NATURAL ){ - if( pRight->pOn || pRight->pUsing ){ + IdList *pUsing = 0; + if( pRight->fg.isUsing || pRight->u3.pOn ){ sqlite3ErrorMsg(pParse, "a NATURAL join may not have " "an ON or USING clause", 0); return 1; } for(j=0; jnCol; j++){ char *zName; /* Name of column in the right table */ - int iLeft; /* Matching left table */ - int iLeftCol; /* Matching column in the left table */ if( IsHiddenColumn(&pRightTab->aCol[j]) ) continue; zName = pRightTab->aCol[j].zCnName; - if( tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 1) ){ - addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, j, - isOuter, &p->pWhere); + if( tableAndColumnIndex(pSrc, 0, i, zName, 0, 0, 1) ){ + pUsing = sqlite3IdListAppend(pParse, pUsing, 0); + if( pUsing ){ + assert( pUsing->nId>0 ); + assert( pUsing->a[pUsing->nId-1].zName==0 ); + pUsing->a[pUsing->nId-1].zName = sqlite3DbStrDup(pParse->db, zName); + } } } - } - - /* Disallow both ON and USING clauses in the same join - */ - if( pRight->pOn && pRight->pUsing ){ - sqlite3ErrorMsg(pParse, "cannot have both ON and USING " - "clauses in the same join"); - return 1; - } - - /* Add the ON clause to the end of the WHERE clause, connected by - ** an AND operator. - */ - if( pRight->pOn ){ - if( isOuter ) sqlite3SetJoinExpr(pRight->pOn, pRight->iCursor); - p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->pOn); - pRight->pOn = 0; + if( pUsing ){ + pRight->fg.isUsing = 1; + pRight->fg.isSynthUsing = 1; + pRight->u3.pUsing = pUsing; + } + if( pParse->nErr ) return 1; } /* Create extra terms on the WHERE clause for each column named @@ -511,27 +537,88 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){ ** Report an error if any column mentioned in the USING clause is ** not contained in both tables to be joined. */ - if( pRight->pUsing ){ - IdList *pList = pRight->pUsing; + if( pRight->fg.isUsing ){ + IdList *pList = pRight->u3.pUsing; + sqlite3 *db = pParse->db; + assert( pList!=0 ); for(j=0; jnId; j++){ char *zName; /* Name of the term in the USING clause */ int iLeft; /* Table on the left with matching column name */ int iLeftCol; /* Column number of matching column on the left */ int iRightCol; /* Column number of matching column on the right */ + Expr *pE1; /* Reference to the column on the LEFT of the join */ + Expr *pE2; /* Reference to the column on the RIGHT of the join */ + Expr *pEq; /* Equality constraint. pE1 == pE2 */ zName = pList->a[j].zName; iRightCol = sqlite3ColumnIndex(pRightTab, zName); if( iRightCol<0 - || !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 0) + || tableAndColumnIndex(pSrc, 0, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)==0 ){ sqlite3ErrorMsg(pParse, "cannot join using column %s - column " "not present in both tables", zName); return 1; } - addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, iRightCol, - isOuter, &p->pWhere); + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + /* This branch runs if the query contains one or more RIGHT or FULL + ** JOINs. If only a single table on the left side of this join + ** contains the zName column, then this branch is a no-op. + ** But if there are two or more tables on the left side + ** of the join, construct a coalesce() function that gathers all + ** such tables. Raise an error if more than one of those references + ** to zName is not also within a prior USING clause. + ** + ** We really ought to raise an error if there are two or more + ** non-USING references to zName on the left of an INNER or LEFT + ** JOIN. But older versions of SQLite do not do that, so we avoid + ** adding a new error so as to not break legacy applications. + */ + ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */ + static const Token tkCoalesce = { "coalesce", 8 }; + while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)!=0 ){ + if( pSrc->a[iLeft].fg.isUsing==0 + || sqlite3IdListIndex(pSrc->a[iLeft].u3.pUsing, zName)<0 + ){ + sqlite3ErrorMsg(pParse, "ambiguous reference to %s in USING()", + zName); + break; + } + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + } + if( pFuncArgs ){ + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0); + } + } + pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol); + sqlite3SrcItemColumnUsed(pRight, iRightCol); + pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); + assert( pE2!=0 || pEq==0 ); + if( pEq ){ + ExprSetProperty(pEq, joinType); + assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); + ExprSetVVAProperty(pEq, EP_NoReduce); + pEq->w.iJoin = pE2->iTable; + } + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pEq); } } + + /* Add the ON clause to the end of the WHERE clause, connected by + ** an AND operator. + */ + else if( pRight->u3.pOn ){ + sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType); + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); + pRight->u3.pOn = 0; + pRight->fg.isOn = 1; + } } return 0; } @@ -920,7 +1007,7 @@ static void fixDistinctOpenEph( ** retrieved directly from table t1. If the values are very large, this ** can be more efficient than storing them directly in the sorter records. ** -** The ExprList_item.bSorterRef flag is set for each expression in pEList +** The ExprList_item.fg.bSorterRef flag is set for each expression in pEList ** for which the sorter-reference optimization should be enabled. ** Additionally, the pSort->aDefer[] array is populated with entries ** for all cursors required to evaluate all selected expressions. Finally. @@ -980,7 +1067,7 @@ static void selectExprDefer( nDefer++; } } - pItem->bSorterRef = 1; + pItem->fg.bSorterRef = 1; } } } @@ -1111,7 +1198,7 @@ static void selectInnerLoop( for(i=0; inExpr; i++){ if( pEList->a[i].u.x.iOrderByCol>0 #ifdef SQLITE_ENABLE_SORTER_REFERENCES - || pEList->a[i].bSorterRef + || pEList->a[i].fg.bSorterRef #endif ){ nResultCol--; @@ -1473,7 +1560,7 @@ KeyInfo *sqlite3KeyInfoFromExprList( assert( sqlite3KeyInfoIsWriteable(pInfo) ); for(i=iStart, pItem=pList->a+iStart; iaColl[i-iStart] = sqlite3ExprNNCollSeq(pParse, pItem->pExpr); - pInfo->aSortFlags[i-iStart] = pItem->sortFlags; + pInfo->aSortFlags[i-iStart] = pItem->fg.sortFlags; } } return pInfo; @@ -1612,7 +1699,7 @@ static void generateSortTail( } for(i=0, iCol=nKey+bSeq-1; i=0; i--){ #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( aOutEx[i].bSorterRef ){ + if( aOutEx[i].fg.bSorterRef ){ sqlite3ExprCode(pParse, aOutEx[i].pExpr, regRow+i); }else #endif @@ -2015,7 +2102,7 @@ void sqlite3GenerateColumnNames( assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */ assert( p->op!=TK_COLUMN || (ExprUseYTab(p) && p->y.pTab!=0) ); /* Covering idx not yet coded */ - if( pEList->a[i].zEName && pEList->a[i].eEName==ENAME_NAME ){ + if( pEList->a[i].zEName && pEList->a[i].fg.eEName==ENAME_NAME ){ /* An AS clause always takes first priority */ char *zName = pEList->a[i].zEName; sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT); @@ -2100,22 +2187,25 @@ int sqlite3ColumnsFromExprList( *paCol = aCol; for(i=0, pCol=aCol; imallocFailed; i++, pCol++){ + struct ExprList_item *pX = &pEList->a[i]; + struct ExprList_item *pCollide; /* Get an appropriate name for the column */ - if( (zName = pEList->a[i].zEName)!=0 && pEList->a[i].eEName==ENAME_NAME ){ + if( (zName = pX->zEName)!=0 && pX->fg.eEName==ENAME_NAME ){ /* If the column contains an "AS " phrase, use as the name */ }else{ - Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pEList->a[i].pExpr); + Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pX->pExpr); while( ALWAYS(pColExpr!=0) && pColExpr->op==TK_DOT ){ pColExpr = pColExpr->pRight; assert( pColExpr!=0 ); } if( pColExpr->op==TK_COLUMN && ALWAYS( ExprUseYTab(pColExpr) ) - && (pTab = pColExpr->y.pTab)!=0 + && ALWAYS( pColExpr->y.pTab!=0 ) ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; + pTab = pColExpr->y.pTab; if( iCol<0 ) iCol = pTab->iPKey; zName = iCol>=0 ? pTab->aCol[iCol].zCnName : "rowid"; }else if( pColExpr->op==TK_ID ){ @@ -2123,7 +2213,7 @@ int sqlite3ColumnsFromExprList( zName = pColExpr->u.zToken; }else{ /* Use the original text of the column expression as its name */ - zName = pEList->a[i].zEName; + assert( zName==pX->zEName ); /* pointer comparison intended */ } } if( zName && !sqlite3IsTrueOrFalse(zName) ){ @@ -2136,7 +2226,10 @@ int sqlite3ColumnsFromExprList( ** append an integer to the name so that it becomes unique. */ cnt = 0; - while( zName && sqlite3HashFind(&ht, zName)!=0 ){ + while( zName && (pCollide = sqlite3HashFind(&ht, zName))!=0 ){ + if( pCollide->fg.bUsingTerm ){ + pCol->colFlags |= COLFLAG_NOEXPAND; + } nName = sqlite3Strlen30(zName); if( nName>0 ){ for(j=nName-1; j>0 && sqlite3Isdigit(zName[j]); j--){} @@ -2147,8 +2240,11 @@ int sqlite3ColumnsFromExprList( } pCol->zCnName = zName; pCol->hName = sqlite3StrIHash(zName); + if( pX->fg.bNoExpand ){ + pCol->colFlags |= COLFLAG_NOEXPAND; + } sqlite3ColumnPropertiesFromName(0, pCol); - if( zName && sqlite3HashInsert(&ht, zName, pCol)==pCol ){ + if( zName && sqlite3HashInsert(&ht, zName, pX)==pX ){ sqlite3OomFault(db); } } @@ -2405,7 +2501,7 @@ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ } assert( sqlite3KeyInfoIsWriteable(pRet) ); pRet->aColl[i] = pColl; - pRet->aSortFlags[i] = pOrderBy->a[i].sortFlags; + pRet->aSortFlags[i] = pOrderBy->a[i].fg.sortFlags; } } @@ -2623,7 +2719,7 @@ static int multiSelectOrderBy( ** The "LIMIT of exactly 1" case of condition (1) comes about when a VALUES ** clause occurs within scalar expression (ex: "SELECT (VALUES(1),(2),(3))"). ** The sqlite3CodeSubselect will have added the LIMIT 1 clause in tht case. -** Since the limit is exactly 1, we only need to evalutes the left-most VALUES. +** Since the limit is exactly 1, we only need to evaluate the left-most VALUES. */ static int multiSelectValues( Parse *pParse, /* Parsing context */ @@ -3616,12 +3712,40 @@ static int multiSelectOrderBy( ** ** All references to columns in table iTable are to be replaced by corresponding ** expressions in pEList. +** +** ## About "isOuterJoin": +** +** The isOuterJoin column indicates that the replacement will occur into a +** position in the parent that NULL-able due to an OUTER JOIN. Either the +** target slot in the parent is the right operand of a LEFT JOIN, or one of +** the left operands of a RIGHT JOIN. In either case, we need to potentially +** bypass the substituted expression with OP_IfNullRow. +** +** Suppose the original expression integer constant. Even though the table +** has the nullRow flag set, because the expression is an integer constant, +** it will not be NULLed out. So instead, we insert an OP_IfNullRow opcode +** that checks to see if the nullRow flag is set on the table. If the nullRow +** flag is set, then the value in the register is set to NULL and the original +** expression is bypassed. If the nullRow flag is not set, then the original +** expression runs to populate the register. +** +** Example where this is needed: +** +** CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); +** CREATE TABLE t2(x INT UNIQUE); +** +** SELECT a,b,m,x FROM t1 LEFT JOIN (SELECT 59 AS m,x FROM t2) ON b=x; +** +** When the subquery on the right side of the LEFT JOIN is flattened, we +** have to add OP_IfNullRow in front of the OP_Integer that implements the +** "m" value of the subquery so that a NULL will be loaded instead of 59 +** when processing a non-matched row of the left. */ typedef struct SubstContext { Parse *pParse; /* The parsing context */ int iTable; /* Replace references to this table */ int iNewTable; /* New table number */ - int isLeftJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ + int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ ExprList *pEList; /* Replacement expressions */ } SubstContext; @@ -3647,10 +3771,11 @@ static Expr *substExpr( Expr *pExpr /* Expr in which substitution occurs */ ){ if( pExpr==0 ) return 0; - if( ExprHasProperty(pExpr, EP_FromJoin) - && pExpr->w.iRightJoinTable==pSubst->iTable + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) + && pExpr->w.iJoin==pSubst->iTable ){ - pExpr->w.iRightJoinTable = pSubst->iNewTable; + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + pExpr->w.iJoin = pSubst->iNewTable; } if( pExpr->op==TK_COLUMN && pExpr->iTable==pSubst->iTable @@ -3671,7 +3796,7 @@ static Expr *substExpr( sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ sqlite3 *db = pSubst->pParse->db; - if( pSubst->isLeftJoin && pCopy->op!=TK_COLUMN ){ + if( pSubst->isOuterJoin && pCopy->op!=TK_COLUMN ){ memset(&ifNullRow, 0, sizeof(ifNullRow)); ifNullRow.op = TK_IF_NULL_ROW; ifNullRow.pLeft = pCopy; @@ -3685,14 +3810,20 @@ static Expr *substExpr( sqlite3ExprDelete(db, pNew); return pExpr; } - if( pSubst->isLeftJoin ){ + if( pSubst->isOuterJoin ){ ExprSetProperty(pNew, EP_CanBeNull); } - if( ExprHasProperty(pExpr,EP_FromJoin) ){ - sqlite3SetJoinExpr(pNew, pExpr->w.iRightJoinTable); + if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){ + sqlite3SetJoinExpr(pNew, pExpr->w.iJoin, + pExpr->flags & (EP_OuterON|EP_InnerON)); } sqlite3ExprDelete(db, pExpr); pExpr = pNew; + if( pExpr->op==TK_TRUEFALSE ){ + pExpr->u.iValue = sqlite3ExprTruthValue(pExpr); + pExpr->op = TK_INTEGER; + ExprSetProperty(pExpr, EP_IntValue); + } /* Ensure that the expression now has an implicit collation sequence, ** just as it did when it was a column of a view or sub-query. */ @@ -3853,8 +3984,8 @@ static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ if( op==TK_COLUMN || op==TK_IF_NULL_ROW ){ renumberCursorDoMapping(pWalker, &pExpr->iTable); } - if( ExprHasProperty(pExpr, EP_FromJoin) ){ - renumberCursorDoMapping(pWalker, &pExpr->w.iRightJoinTable); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + renumberCursorDoMapping(pWalker, &pExpr->w.iJoin); } return WRC_Continue; } @@ -3939,6 +4070,7 @@ static void renumberCursors( ** table and ** (3c) the outer query may not be an aggregate. ** (3d) the outer query may not be DISTINCT. +** See also (26) for restrictions on RIGHT JOIN. ** ** (4) The subquery can not be DISTINCT. ** @@ -3990,6 +4122,9 @@ static void renumberCursors( ** (17d2) DISTINCT ** (17e) the subquery may not contain window functions, and ** (17f) the subquery must not be the RHS of a LEFT JOIN. +** (17g) either the subquery is the first element of the outer +** query or there are no RIGHT or FULL JOINs in any arm +** of the subquery. (This is a duplicate of condition (27b).) ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, @@ -4037,6 +4172,23 @@ static void renumberCursors( ** function in the select list or ORDER BY clause, flattening ** is not attempted. ** +** (26) The subquery may not be the right operand of a RIGHT JOIN. +** See also (3) for restrictions on LEFT JOIN. +** +** (27) The subquery may not contain a FULL or RIGHT JOIN unless it +** is the first element of the parent query. This must be the +** the case if: +** (27a) the subquery is not compound query, and +** (27b) the subquery is a compound query and the RIGHT JOIN occurs +** in any arm of the compound query. (See also (17g).) +** +** (28) The subquery is not a MATERIALIZED CTE. +** +** (29) Either the subquery is not the right-hand operand of a join with an +** ON or USING clause nor the right-hand operand of a NATURAL JOIN, or +** the right-most table within the FROM clause of the subquery +** is not part of an outer join. +** ** ** In this routine, the "p" parameter is a pointer to the outer query. ** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query @@ -4062,7 +4214,7 @@ static int flattenSubquery( SrcList *pSubSrc; /* The FROM clause of the subquery */ int iParent; /* VDBE cursor number of the pSub result set temp table */ int iNewParent = -1;/* Replacement table for iParent */ - int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ + int isOuterJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ SrcItem *pSubitem; /* The subquery */ @@ -4135,26 +4287,64 @@ static int flattenSubquery( ** ** See also tickets #306, #350, and #3300. */ - if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){ - isLeftJoin = 1; - if( pSubSrc->nSrc>1 /* (3a) */ - || isAgg /* (3b) */ - || IsVirtual(pSubSrc->a[0].pTab) /* (3c) */ - || (p->selFlags & SF_Distinct)!=0 /* (3d) */ + if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){ + if( pSubSrc->nSrc>1 /* (3a) */ + || isAgg /* (3c) */ + || IsVirtual(pSubSrc->a[0].pTab) /* (3b) */ + || (p->selFlags & SF_Distinct)!=0 /* (3d) */ + || (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */ ){ return 0; } + isOuterJoin = 1; } #ifdef SQLITE_EXTRA_IFNULLROW else if( iFrom>0 && !isAgg ){ - /* Setting isLeftJoin to -1 causes OP_IfNullRow opcodes to be generated for + /* Setting isOuterJoin to -1 causes OP_IfNullRow opcodes to be generated for ** every reference to any result column from subquery in a join, even ** though they are not necessary. This will stress-test the OP_IfNullRow ** opcode. */ - isLeftJoin = -1; + isOuterJoin = -1; } #endif + assert( pSubSrc->nSrc>0 ); /* True by restriction (7) */ + if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + return 0; /* Restriction (27a) */ + } + if( pSubitem->fg.isCte && pSubitem->u2.pCteUse->eM10d==M10d_Yes ){ + return 0; /* (28) */ + } + + /* Restriction (29): + ** + ** We do not want two constraints on the same term of the flattened + ** query where one constraint has EP_InnerON and the other is EP_OuterON. + ** To prevent this, one or the other of the following conditions must be + ** false: + ** + ** (29a) The right-most entry in the FROM clause of the subquery + ** must not be part of an outer join. + ** + ** (29b) The subquery itself must not be the right operand of a + ** NATURAL join or a join that as an ON or USING clause. + ** + ** These conditions are sufficient to keep an EP_OuterON from being + ** flattened into an EP_InnerON. Restrictions (3a) and (27a) prevent + ** an EP_InnerON from being flattened into an EP_OuterON. + */ + if( pSubSrc->nSrc>=2 + && (pSubSrc->a[pSubSrc->nSrc-1].fg.jointype & JT_OUTER)!=0 + ){ + if( (pSubitem->fg.jointype & JT_NATURAL)!=0 + || pSubitem->fg.isUsing + || NEVER(pSubitem->u3.pOn!=0) /* ON clause already shifted into WHERE */ + || pSubitem->fg.isOn + ){ + return 0; + } + } + /* Restriction (17): If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries ** that make up the compound SELECT are allowed to be aggregate or distinct @@ -4164,7 +4354,7 @@ static int flattenSubquery( if( pSub->pOrderBy ){ return 0; /* Restriction (20) */ } - if( isAgg || (p->selFlags & SF_Distinct)!=0 || isLeftJoin>0 ){ + if( isAgg || (p->selFlags & SF_Distinct)!=0 || isOuterJoin>0 ){ return 0; /* (17d1), (17d2), or (17f) */ } for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){ @@ -4182,6 +4372,12 @@ static int flattenSubquery( ){ return 0; } + if( iFrom>0 && (pSub1->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + /* Without this restriction, the JT_LTORJ flag would end up being + ** omitted on left-hand tables of the right join that is being + ** flattened. */ + return 0; /* Restrictions (17g), (27b) */ + } testcase( pSub1->pSrc->nSrc>1 ); } @@ -4198,6 +4394,7 @@ static int flattenSubquery( if( pSrc->nSrc>1 ){ if( pParse->nSelect>500 ) return 0; + if( OptimizationDisabled(db, SQLITE_FlttnUnionAll) ) return 0; aCsrMap = sqlite3DbMallocZero(db, ((i64)pParse->nTab+1)*sizeof(int)); if( aCsrMap ) aCsrMap[0] = pParse->nTab; } @@ -4222,7 +4419,7 @@ static int flattenSubquery( pSubitem->zName = 0; pSubitem->zAlias = 0; pSubitem->pSelect = 0; - assert( pSubitem->pOn==0 ); + assert( pSubitem->fg.isUsing!=0 || pSubitem->u3.pOn==0 ); /* If the sub-query is a compound SELECT statement, then (by restrictions ** 17 and 18 above) it must be a UNION ALL and the parent query must @@ -4332,6 +4529,7 @@ static int flattenSubquery( for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ int nSubSrc; u8 jointype = 0; + u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ; assert( pSub!=0 ); pSubSrc = pSub->pSrc; /* FROM clause of subquery */ nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ @@ -4366,13 +4564,16 @@ static int flattenSubquery( ** outer query. */ for(i=0; ia[i+iFrom].pUsing); - assert( pSrc->a[i+iFrom].fg.isTabFunc==0 ); - pSrc->a[i+iFrom] = pSubSrc->a[i]; + SrcItem *pItem = &pSrc->a[i+iFrom]; + if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); + assert( pItem->fg.isTabFunc==0 ); + *pItem = pSubSrc->a[i]; + pItem->fg.jointype |= ltorj; iNewParent = pSubSrc->a[i].iCursor; memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); } - pSrc->a[iFrom].fg.jointype = jointype; + pSrc->a[iFrom].fg.jointype &= JT_LTORJ; + pSrc->a[iFrom].fg.jointype |= jointype | ltorj; /* Now begin substituting subquery result set expressions for ** references to the iParent in the outer query. @@ -4407,8 +4608,8 @@ static int flattenSubquery( } pWhere = pSub->pWhere; pSub->pWhere = 0; - if( isLeftJoin>0 ){ - sqlite3SetJoinExpr(pWhere, iNewParent); + if( isOuterJoin>0 ){ + sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON); } if( pWhere ){ if( pParent->pWhere ){ @@ -4422,7 +4623,7 @@ static int flattenSubquery( x.pParse = pParse; x.iTable = iParent; x.iNewTable = iNewParent; - x.isLeftJoin = isLeftJoin; + x.isOuterJoin = isOuterJoin; x.pEList = pSub->pEList; substSelect(&x, pParent, 0); } @@ -4457,8 +4658,8 @@ static int flattenSubquery( sqlite3WalkSelect(&w,pSub1); sqlite3SelectDelete(db, pSub1); -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p,("After flattening:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -4479,6 +4680,8 @@ struct WhereConst { int nConst; /* Number for COLUMN=CONSTANT terms */ int nChng; /* Number of times a constant is propagated */ int bHasAffBlob; /* At least one column in apExpr[] as affinity BLOB */ + u32 mExcludeOn; /* Which ON expressions to exclude from considertion. + ** Either EP_OuterON or EP_InnerON|EP_OuterON */ Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */ }; @@ -4541,7 +4744,11 @@ static void constInsert( static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ Expr *pRight, *pLeft; if( NEVER(pExpr==0) ) return; - if( ExprHasProperty(pExpr, EP_FromJoin) ) return; + if( ExprHasProperty(pExpr, pConst->mExcludeOn) ){ + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + return; + } if( pExpr->op==TK_AND ){ findConstInWhere(pConst, pExpr->pRight); findConstInWhere(pConst, pExpr->pLeft); @@ -4577,9 +4784,10 @@ static int propagateConstantExprRewriteOne( int i; if( pConst->pOomFault[0] ) return WRC_Prune; if( pExpr->op!=TK_COLUMN ) return WRC_Continue; - if( ExprHasProperty(pExpr, EP_FixedCol|EP_FromJoin) ){ + if( ExprHasProperty(pExpr, EP_FixedCol|pConst->mExcludeOn) ){ testcase( ExprHasProperty(pExpr, EP_FixedCol) ); - testcase( ExprHasProperty(pExpr, EP_FromJoin) ); + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); return WRC_Continue; } for(i=0; inConst; i++){ @@ -4703,6 +4911,17 @@ static int propagateConstants( x.nChng = 0; x.apExpr = 0; x.bHasAffBlob = 0; + if( ALWAYS(p->pSrc!=0) + && p->pSrc->nSrc>0 + && (p->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 + ){ + /* Do not propagate constants on any ON clause if there is a + ** RIGHT JOIN anywhere in the query */ + x.mExcludeOn = EP_InnerON | EP_OuterON; + }else{ + /* Do not propagate constants through the ON clause of a LEFT JOIN */ + x.mExcludeOn = EP_OuterON; + } findConstInWhere(&x, p->pWhere); if( x.nConst ){ memset(&w, 0, sizeof(w)); @@ -4828,6 +5047,7 @@ static int pushDownWhereTerms( int nChng = 0; if( pWhere==0 ) return 0; if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0; + if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ) return 0; #ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pPrior ){ @@ -4863,13 +5083,13 @@ static int pushDownWhereTerms( #if 0 /* Legacy code. Checks now done by sqlite3ExprIsTableConstraint() */ if( isLeftJoin - && (ExprHasProperty(pWhere,EP_FromJoin)==0 - || pWhere->w.iRightJoinTable!=iCursor) + && (ExprHasProperty(pWhere,EP_OuterON)==0 + || pWhere->w.iJoin!=iCursor) ){ return 0; /* restriction (4) */ } - if( ExprHasProperty(pWhere,EP_FromJoin) - && pWhere->w.iRightJoinTable!=iCursor + if( ExprHasProperty(pWhere,EP_OuterON) + && pWhere->w.iJoin!=iCursor ){ return 0; /* restriction (5) */ } @@ -4881,11 +5101,11 @@ static int pushDownWhereTerms( while( pSubq ){ SubstContext x; pNew = sqlite3ExprDup(pParse->db, pWhere, 0); - unsetJoinExpr(pNew, -1); + unsetJoinExpr(pNew, -1, 1); x.pParse = pParse; x.iTable = pSrc->iCursor; x.iNewTable = pSrc->iCursor; - x.isLeftJoin = 0; + x.isOuterJoin = 0; x.pEList = pSubq->pEList; pNew = substExpr(&x, pNew); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -4958,7 +5178,7 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ } *ppMinMax = pOrderBy = sqlite3ExprListDup(db, pEList, 0); assert( pOrderBy!=0 || db->mallocFailed ); - if( pOrderBy ) pOrderBy->a[0].sortFlags = sortFlags; + if( pOrderBy ) pOrderBy->a[0].fg.sortFlags = sortFlags; return eRet; } @@ -5094,7 +5314,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); if( pNew==0 ) return WRC_Abort; memset(&dummy, 0, sizeof(dummy)); - pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0,0); + pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0); if( pNewSrc==0 ) return WRC_Abort; *pNew = *p; p->pSrc = pNewSrc; @@ -5427,7 +5647,7 @@ int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ if( pFrom->zAlias ){ pTab->zName = sqlite3DbStrDup(pParse->db, pFrom->zAlias); }else{ - pTab->zName = sqlite3MPrintf(pParse->db, "subquery_%u", pSel->selId); + pTab->zName = sqlite3MPrintf(pParse->db, "%!S", pFrom); } while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); @@ -5439,11 +5659,35 @@ int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ #else pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */ #endif + return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; +} - return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; +/* +** Check the N SrcItem objects to the right of pBase. (N might be zero!) +** If any of those SrcItem objects have a USING clause containing zName +** then return true. +** +** If N is zero, or none of the N SrcItem objects to the right of pBase +** contains a USING clause, or if none of the USING clauses contain zName, +** then return false. +*/ +static int inAnyUsingClause( + const char *zName, /* Name we are looking for */ + SrcItem *pBase, /* The base SrcItem. Looking at pBase[1] and following */ + int N /* How many SrcItems to check */ +){ + while( N>0 ){ + N--; + pBase++; + if( pBase->fg.isUsing==0 ) continue; + if( NEVER(pBase->u3.pUsing==0) ) continue; + if( sqlite3IdListIndex(pBase->u3.pUsing, zName)>=0 ) return 1; + } + return 0; } + /* ** This routine is a Walker callback for "expanding" a SELECT statement. ** "Expanding" means to do the following: @@ -5593,7 +5837,7 @@ static int selectExpander(Walker *pWalker, Select *p){ /* Process NATURAL keywords, and ON and USING clauses of joins. */ assert( db->mallocFailed==0 || pParse->nErr!=0 ); - if( pParse->nErr || sqliteProcessJoin(pParse, p) ){ + if( pParse->nErr || sqlite3ProcessJoin(pParse, p) ){ return WRC_Abort; } @@ -5641,7 +5885,7 @@ static int selectExpander(Walker *pWalker, Select *p){ pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr); if( pNew ){ pNew->a[pNew->nExpr-1].zEName = a[k].zEName; - pNew->a[pNew->nExpr-1].eEName = a[k].eEName; + pNew->a[pNew->nExpr-1].fg.eEName = a[k].fg.eEName; a[k].zEName = 0; } a[k].pExpr = 0; @@ -5656,32 +5900,60 @@ static int selectExpander(Walker *pWalker, Select *p){ zTName = pE->pLeft->u.zToken; } for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ - Table *pTab = pFrom->pTab; - Select *pSub = pFrom->pSelect; - char *zTabName = pFrom->zAlias; - const char *zSchemaName = 0; - int iDb; - if( zTabName==0 ){ + Table *pTab = pFrom->pTab; /* Table for this data source */ + ExprList *pNestedFrom; /* Result-set of a nested FROM clause */ + char *zTabName; /* AS name for this data source */ + const char *zSchemaName = 0; /* Schema name for this data source */ + int iDb; /* Schema index for this data src */ + IdList *pUsing; /* USING clause for pFrom[1] */ + + if( (zTabName = pFrom->zAlias)==0 ){ zTabName = pTab->zName; } if( db->mallocFailed ) break; - if( pSub==0 || (pSub->selFlags & SF_NestedFrom)==0 ){ - pSub = 0; + assert( (int)pFrom->fg.isNestedFrom == IsNestedFrom(pFrom->pSelect) ); + if( pFrom->fg.isNestedFrom ){ + assert( pFrom->pSelect!=0 ); + pNestedFrom = pFrom->pSelect->pEList; + assert( pNestedFrom!=0 ); + assert( pNestedFrom->nExpr==pTab->nCol ); + }else{ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; } + pNestedFrom = 0; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; } + if( i+1nSrc + && pFrom[1].fg.isUsing + && (selFlags & SF_NestedFrom)!=0 + ){ + int ii; + pUsing = pFrom[1].u3.pUsing; + for(ii=0; iinId; ii++){ + const char *zUName = pUsing->a[ii].zName; + pRight = sqlite3Expr(db, TK_ID, zUName); + pNew = sqlite3ExprListAppend(pParse, pNew, pRight); + if( pNew ){ + struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + pX->zEName = sqlite3MPrintf(db,"..%s", zUName); + pX->fg.eEName = ENAME_TAB; + pX->fg.bUsingTerm = 1; + } + } + }else{ + pUsing = 0; + } for(j=0; jnCol; j++){ char *zName = pTab->aCol[j].zCnName; - char *zColname; /* The computed column name */ - char *zToFree; /* Malloced string that needs to be freed */ - Token sColname; /* Computed column name as a token */ + struct ExprList_item *pX; /* Newly added ExprList term */ assert( zName ); - if( zTName && pSub - && sqlite3MatchEName(&pSub->pEList->a[j], 0, zTName, 0)==0 + if( zTName + && pNestedFrom + && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0)==0 ){ continue; } @@ -5695,57 +5967,75 @@ static int selectExpander(Walker *pWalker, Select *p){ ){ continue; } + if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + && zTName==0 + && (selFlags & (SF_NestedFrom))==0 + ){ + continue; + } tableSeen = 1; - if( i>0 && zTName==0 ){ - if( (pFrom->fg.jointype & JT_NATURAL)!=0 - && tableAndColumnIndex(pTabList, i, zName, 0, 0, 1) + if( i>0 && zTName==0 && (selFlags & SF_NestedFrom)==0 ){ + if( pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0 ){ - /* In a NATURAL join, omit the join columns from the - ** table to the right of the join */ - continue; - } - if( sqlite3IdListIndex(pFrom->pUsing, zName)>=0 ){ /* In a join with a USING clause, omit columns in the ** using clause from the table on the right. */ continue; } } pRight = sqlite3Expr(db, TK_ID, zName); - zColname = zName; - zToFree = 0; - if( longNames || pTabList->nSrc>1 ){ + if( (pTabList->nSrc>1 + && ( (pFrom->fg.jointype & JT_LTORJ)==0 + || (selFlags & SF_NestedFrom)!=0 + || !inAnyUsingClause(zName,pFrom,pTabList->nSrc-i-1) + ) + ) + || IN_RENAME_OBJECT + ){ Expr *pLeft; pLeft = sqlite3Expr(db, TK_ID, zTabName); pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); + if( IN_RENAME_OBJECT && pE->pLeft ){ + sqlite3RenameTokenRemap(pParse, pLeft, pE->pLeft); + } if( zSchemaName ){ pLeft = sqlite3Expr(db, TK_ID, zSchemaName); pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr); } - if( longNames ){ - zColname = sqlite3MPrintf(db, "%s.%s", zTabName, zName); - zToFree = zColname; - } }else{ pExpr = pRight; } pNew = sqlite3ExprListAppend(pParse, pNew, pExpr); - sqlite3TokenInit(&sColname, zColname); - sqlite3ExprListSetName(pParse, pNew, &sColname, 0); - if( pNew && (p->selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ - struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; - sqlite3DbFree(db, pX->zEName); - if( pSub ){ - pX->zEName = sqlite3DbStrDup(db, pSub->pEList->a[j].zEName); + if( pNew==0 ){ + break; /* OOM */ + } + pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ + if( pNestedFrom ){ + pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); testcase( pX->zEName==0 ); }else{ pX->zEName = sqlite3MPrintf(db, "%s.%s.%s", - zSchemaName, zTabName, zColname); + zSchemaName, zTabName, zName); testcase( pX->zEName==0 ); } - pX->eEName = ENAME_TAB; + pX->fg.eEName = ENAME_TAB; + if( (pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0) + || (pUsing && sqlite3IdListIndex(pUsing, zName)>=0) + || (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + ){ + pX->fg.bNoExpand = 1; + } + }else if( longNames ){ + pX->zEName = sqlite3MPrintf(db, "%s.%s", zTabName, zName); + pX->fg.eEName = ENAME_NAME; + }else{ + pX->zEName = sqlite3DbStrDup(db, zName); + pX->fg.eEName = ENAME_NAME; } - sqlite3DbFree(db, zToFree); } } if( !tableSeen ){ @@ -5769,6 +6059,12 @@ static int selectExpander(Walker *pWalker, Select *p){ p->selFlags |= SF_ComplexResult; } } +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ + SELECTTRACE(0x100,pParse,p,("After result-set wildcard expansion:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif return WRC_Continue; } @@ -6159,8 +6455,8 @@ static void havingToWhere(Parse *pParse, Select *p){ sWalker.xExprCallback = havingToWhereExprCb; sWalker.u.pSelect = p; sqlite3WalkExpr(&sWalker, p->pHaving); -#if SELECTTRACE_ENABLED - if( sWalker.eCode && (sqlite3SelectTrace & 0x100)!=0 ){ +#if TREETRACE_ENABLED + if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){ SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6292,8 +6588,8 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ p->pEList->a[0].pExpr = pExpr; p->selFlags &= ~SF_Aggregate; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6302,6 +6598,29 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ } #endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */ +/* +** If any term of pSrc, or any SF_NestedFrom sub-query, is not the same +** as pSrcItem but has the same alias as p0, then return true. +** Otherwise return false. +*/ +static int sameSrcAlias(SrcItem *p0, SrcList *pSrc){ + int i; + for(i=0; inSrc; i++){ + SrcItem *p1 = &pSrc->a[i]; + if( p1==p0 ) continue; + if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){ + return 1; + } + if( p1->pSelect + && (p1->pSelect->selFlags & SF_NestedFrom)!=0 + && sameSrcAlias(p0, p1->pSelect->pSrc) + ){ + return 1; + } + } + return 0; +} + /* ** Generate code for the SELECT statement given in the p argument. ** @@ -6346,10 +6665,14 @@ int sqlite3Select( } assert( db->mallocFailed==0 ); if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain)); - if( sqlite3SelectTrace & 0x100 ){ - sqlite3TreeViewSelect(0, p, 0); + if( sqlite3TreeTrace & 0x10100 ){ + if( (sqlite3TreeTrace & 0x10001)==0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d", + __FILE__, __LINE__); + } + sqlite3ShowSelect(p); } #endif @@ -6363,9 +6686,9 @@ int sqlite3Select( pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ if( p->pOrderBy ){ -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(1,pParse,p, ("dropping superfluous ORDER BY:\n")); - if( sqlite3SelectTrace & 0x100 ){ + if( sqlite3TreeTrace & 0x100 ){ sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); } #endif @@ -6384,8 +6707,8 @@ int sqlite3Select( } assert( db->mallocFailed==0 ); assert( p->pEList!=0 ); -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x104 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x104 ){ SELECTTRACE(0x104,pParse,p, ("after name resolution:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6402,15 +6725,12 @@ int sqlite3Select( ** disallow it altogether. */ if( p->selFlags & SF_UFSrcCheck ){ SrcItem *p0 = &p->pSrc->a[0]; - for(i=1; ipSrc->nSrc; i++){ - SrcItem *p1 = &p->pSrc->a[i]; - if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){ - sqlite3ErrorMsg(pParse, - "target object/alias may not appear in FROM clause: %s", - p0->zAlias ? p0->zAlias : p0->pTab->zName - ); - goto select_end; - } + if( sameSrcAlias(p0, p->pSrc) ){ + sqlite3ErrorMsg(pParse, + "target object/alias may not appear in FROM clause: %s", + p0->zAlias ? p0->zAlias : p0->pTab->zName + ); + goto select_end; } /* Clear the SF_UFSrcCheck flag. The check has already been performed, @@ -6429,8 +6749,8 @@ int sqlite3Select( assert( pParse->nErr ); goto select_end; } -#if SELECTTRACE_ENABLED - if( p->pWin && (sqlite3SelectTrace & 0x108)!=0 ){ +#if TREETRACE_ENABLED + if( p->pWin && (sqlite3TreeTrace & 0x108)!=0 ){ SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6458,14 +6778,16 @@ int sqlite3Select( /* Convert LEFT JOIN into JOIN if there are terms of the right table ** of the LEFT JOIN used in the WHERE clause. */ - if( (pItem->fg.jointype & JT_LEFT)!=0 + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==JT_LEFT && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ SELECTTRACE(0x100,pParse,p, ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); - unsetJoinExpr(p->pWhere, pItem->iCursor); + assert( pItem->iCursor>=0 ); + unsetJoinExpr(p->pWhere, pItem->iCursor, + pTabList->a[0].fg.jointype & JT_LTORJ); } /* No futher action if this term of the FROM clause is no a subquery */ @@ -6518,7 +6840,9 @@ int sqlite3Select( ){ SELECTTRACE(0x100,pParse,p, ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); - sqlite3ExprListDelete(db, pSub->pOrderBy); + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprListDelete, + pSub->pOrderBy); pSub->pOrderBy = 0; } @@ -6544,7 +6868,7 @@ int sqlite3Select( && i==0 && (p->selFlags & SF_ComplexResult)!=0 && (pTabList->nSrc==1 - || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) + || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) ){ continue; } @@ -6568,9 +6892,9 @@ int sqlite3Select( */ if( p->pPrior ){ rc = multiSelect(pParse, p, pDest); -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end compound-select processing\n")); - if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -6589,8 +6913,8 @@ int sqlite3Select( && OptimizationEnabled(db, SQLITE_PropagateConst) && propagateConstants(pParse, p) ){ -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p,("After constant propagation:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6668,8 +6992,8 @@ int sqlite3Select( || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem) ){ -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p, ("After WHERE-clause push-down into subquery %d:\n", pSub->selId)); sqlite3TreeViewSelect(0, p, 0); @@ -6685,18 +7009,19 @@ int sqlite3Select( /* Generate code to implement the subquery ** - ** The subquery is implemented as a co-routine if: + ** The subquery is implemented as a co-routine if all of the following are + ** true: + ** ** (1) the subquery is guaranteed to be the outer loop (so that ** it does not need to be computed more than once), and ** (2) the subquery is not a CTE that should be materialized - ** - ** TODO: Are there other reasons beside (1) and (2) to use a co-routine - ** implementation? + ** (3) the subquery is not part of a left operand for a RIGHT JOIN */ if( i==0 && (pTabList->nSrc==1 - || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */ - && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */ + || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) /* (1) */ + && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */ + && (pTabList->a[0].fg.jointype & JT_LTORJ)==0 /* (3) */ ){ /* Implement a co-routine that will return a single row of the result ** set on each invocation. @@ -6742,11 +7067,11 @@ int sqlite3Select( ** the same view can reuse the materialization. */ int topAddr; int onceAddr = 0; - int retAddr; pItem->regReturn = ++pParse->nMem; - topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn); + topAddr = sqlite3VdbeAddOp0(v, OP_Goto); pItem->addrFillSub = topAddr+1; + pItem->fg.isMaterialized = 1; if( pItem->fg.isCorrelated==0 ){ /* If the subquery is not correlated and if we are not inside of ** a trigger, then we only need to compute the value of the subquery @@ -6761,9 +7086,9 @@ int sqlite3Select( sqlite3Select(pParse, pSub, &dest); pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); - retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn); + sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1); VdbeComment((v, "end %!S", pItem)); - sqlite3VdbeChangeP1(v, topAddr, retAddr); + sqlite3VdbeJumpHere(v, topAddr); sqlite3ClearTempRegCache(pParse); if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){ CteUse *pCteUse = pItem->u2.pCteUse; @@ -6787,8 +7112,8 @@ int sqlite3Select( pHaving = p->pHaving; sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6822,9 +7147,10 @@ int sqlite3Select( ** the sDistinct.isTnct is still set. Hence, isTnct represents the ** original setting of the SF_Distinct flag, not the current setting */ assert( sDistinct.isTnct ); + sDistinct.isTnct = 2; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -6857,6 +7183,18 @@ int sqlite3Select( */ if( pDest->eDest==SRT_EphemTab ){ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr); + if( p->selFlags & SF_NestedFrom ){ + /* Delete or NULL-out result columns that will never be used */ + int ii; + for(ii=pEList->nExpr-1; ii>0 && pEList->a[ii].fg.bUsed==0; ii--){ + sqlite3ExprDelete(db, pEList->a[ii].pExpr); + sqlite3DbFree(db, pEList->a[ii].zEName); + pEList->nExpr--; + } + for(ii=0; iinExpr; ii++){ + if( pEList->a[ii].fg.bUsed==0 ) pEList->a[ii].pExpr->op = TK_NULL; + } + } } /* Set the limiter. @@ -7006,8 +7344,9 @@ int sqlite3Select( ** ORDER BY to maximize the chances of rows being delivered in an ** order that makes the ORDER BY redundant. */ for(ii=0; iinExpr; ii++){ - u8 sortFlags = sSort.pOrderBy->a[ii].sortFlags & KEYINFO_ORDER_DESC; - pGroupBy->a[ii].sortFlags = sortFlags; + u8 sortFlags; + sortFlags = sSort.pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; + pGroupBy->a[ii].fg.sortFlags = sortFlags; } if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ orderByGrp = 1; @@ -7076,8 +7415,8 @@ int sqlite3Select( } pAggInfo->mxReg = pParse->nMem; if( db->mallocFailed ) goto select_end; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ int ii; SELECTTRACE(0x400,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); sqlite3TreeViewSelect(0, p, 0); @@ -7165,7 +7504,8 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); SELECTTRACE(1,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, - 0, (WHERE_GROUPBY|(orderByGrp ? WHERE_SORTBYGROUP : 0)|distFlag), 0 + 0, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) + | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 ); if( pWInfo==0 ){ sqlite3ExprListDelete(db, pDistinct); @@ -7347,7 +7687,7 @@ int sqlite3Select( VdbeComment((v, "indicate accumulator empty")); sqlite3VdbeAddOp1(v, OP_Return, regReset); - if( eDist!=WHERE_DISTINCT_NOOP ){ + if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){ struct AggInfo_func *pF = &pAggInfo->aFunc[0]; fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); } @@ -7471,8 +7811,10 @@ int sqlite3Select( eDist = sqlite3WhereIsDistinct(pWInfo); updateAccumulator(pParse, regAcc, pAggInfo, eDist); if( eDist!=WHERE_DISTINCT_NOOP ){ - struct AggInfo_func *pF = &pAggInfo->aFunc[0]; - fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + struct AggInfo_func *pF = pAggInfo->aFunc; + if( pF ){ + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } } if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc); @@ -7539,9 +7881,9 @@ int sqlite3Select( } #endif -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end processing\n")); - if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif diff --git a/src/shell.c.in b/src/shell.c.in index 9626c4662..4e921cb6e 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -229,6 +229,16 @@ static void setTextMode(FILE *file, int isOutput){ # define setTextMode(X,Y) #endif +/* +** When compiling with emcc (a.k.a. emscripten), we're building a +** WebAssembly (WASM) bundle and need to disable and rewire a few +** things. +*/ +#ifdef __EMSCRIPTEN__ +#define SQLITE_SHELL_WASM_MODE +#else +#undef SQLITE_SHELL_WASM_MODE +#endif /* True if the timer is enabled */ static int enableTimer = 0; @@ -691,6 +701,7 @@ static char *local_getline(char *zLine, FILE *in){ ** be freed by the caller or else passed back into this routine via the ** zPrior argument for reuse. */ +#ifndef SQLITE_SHELL_WASM_MODE static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ char *zPrompt; char *zResult; @@ -710,7 +721,7 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ } return zResult; } - +#endif /* !SQLITE_SHELL_WASM_MODE */ /* ** Return the value of a hexadecimal digit. Return -1 if the input @@ -798,7 +809,7 @@ static void freeText(ShellText *p){ ** If the third argument, quote, is not '\0', then it is used as a ** quote character for zAppend. */ -static void appendText(ShellText *p, char const *zAppend, char quote){ +static void appendText(ShellText *p, const char *zAppend, char quote){ int len; int i; int nAppend = strlen30(zAppend); @@ -1009,16 +1020,18 @@ INCLUDE test_windirent.h INCLUDE test_windirent.c #define dirent DIRENT #endif -INCLUDE ../ext/misc/shathree.c -INCLUDE ../ext/misc/fileio.c -INCLUDE ../ext/misc/completion.c -INCLUDE ../ext/misc/appendvfs.c INCLUDE ../ext/misc/memtrace.c +INCLUDE ../ext/misc/shathree.c INCLUDE ../ext/misc/uint.c INCLUDE ../ext/misc/decimal.c INCLUDE ../ext/misc/ieee754.c INCLUDE ../ext/misc/series.c INCLUDE ../ext/misc/regexp.c +#ifndef SQLITE_SHELL_WASM_MODE +INCLUDE ../ext/misc/fileio.c +INCLUDE ../ext/misc/completion.c +INCLUDE ../ext/misc/appendvfs.c +#endif #ifdef SQLITE_HAVE_ZLIB INCLUDE ../ext/misc/zipfile.c INCLUDE ../ext/misc/sqlar.c @@ -1149,8 +1162,18 @@ struct ShellState { char *zNonce; /* Nonce for temporary safe-mode excapes */ EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ +#ifdef SQLITE_SHELL_WASM_MODE + struct { + const char * zInput; /* Input string from wasm/JS proxy */ + const char * zPos; /* Cursor pos into zInput */ + } wasm; +#endif }; +#ifdef SQLITE_SHELL_WASM_MODE +static ShellState shellState; +#endif + /* Allowed values for ShellState.autoEQP */ @@ -1191,7 +1214,7 @@ struct ShellState { #define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */ #define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ #define SHFLG_CountChanges 0x00000020 /* .changes setting */ -#define SHFLG_Echo 0x00000040 /* .echo or --echo setting */ +#define SHFLG_Echo 0x00000040 /* .echo on/off, or --echo setting */ #define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */ #define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */ #define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */ @@ -1815,7 +1838,11 @@ static int safeModeAuth( UNUSED_PARAMETER(zA4); switch( op ){ case SQLITE_ATTACH: { +#ifndef SQLITE_SHELL_WASM_MODE + /* In WASM builds the filesystem is a virtual sandbox, so + ** there's no harm in using ATTACH. */ failIfSafeMode(p, "cannot run ATTACH in safe mode"); +#endif break; } case SQLITE_FUNCTION: { @@ -2944,6 +2971,9 @@ static int str_in_array(const char *zStr, const char **azArray){ ** all opcodes that occur between the p2 jump destination and the opcode ** itself by 2 spaces. ** +** * Do the previous for "Return" instructions for when P2 is positive. +** See tag-20220407a in wherecode.c and vdbe.c. +** ** * For each "Goto", if the jump destination is earlier in the program ** and ends on one of: ** Yield SeekGt SeekLt RowSetRead Rewind @@ -2958,7 +2988,8 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ int iOp; /* Index of operation in p->aiIndent[] */ - const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", 0 }; + const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", + "Return", 0 }; const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead", "Rewind", 0 }; const char *azGoto[] = { "Goto", 0 }; @@ -3016,7 +3047,7 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ p->aiIndent[iOp] = 0; p->nIndent = iOp+1; - if( str_in_array(zOp, azNext) ){ + if( str_in_array(zOp, azNext) && p2op>0 ){ for(i=p2op; iaiIndent[i] += 2; } if( str_in_array(zOp, azGoto) && p2opnIndent @@ -3042,7 +3073,7 @@ static void explain_data_delete(ShellState *p){ } /* -** Disable and restore .wheretrace and .selecttrace settings. +** Disable and restore .wheretrace and .treetrace/.selecttrace settings. */ static unsigned int savedSelectTrace; static unsigned int savedWhereTrace; @@ -3816,11 +3847,6 @@ static int shell_exec( pArg->cnt = 0; } - /* echo the sql statement if echo on */ - if( pArg && ShellHasFlag(pArg, SHFLG_Echo) ){ - utf8_printf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql); - } - /* Show the EXPLAIN QUERY PLAN if .eqp is on */ if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ sqlite3_stmt *pExplain; @@ -4219,13 +4245,14 @@ static int run_schema_dump_query( ** Text of help messages. ** ** The help text for each individual command begins with a line that starts -** with ".". Subsequent lines are supplimental information. +** with ".". Subsequent lines are supplemental information. ** ** There must be two or more spaces between the end of the command and the ** start of the description of what that command does. */ static const char *(azHelp[]) = { -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) +#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \ + && !defined(SQLITE_SHELL_WASM_MODE) ".archive ... Manage SQL archives", " Each command must have exactly one of the following options:", " -c, --create Create a new archive", @@ -4251,20 +4278,28 @@ static const char *(azHelp[]) = { #ifndef SQLITE_OMIT_AUTHORIZATION ".auth ON|OFF Show authorizer callbacks", #endif +#ifndef SQLITE_SHELL_WASM_MODE ".backup ?DB? FILE Backup DB (default \"main\") to FILE", " Options:", " --append Use the appendvfs", " --async Write to FILE without journal and fsync()", +#endif ".bail on|off Stop after hitting an error. Default OFF", ".binary on|off Turn binary output on or off. Default OFF", +#ifndef SQLITE_SHELL_WASM_MODE ".cd DIRECTORY Change the working directory to DIRECTORY", +#endif ".changes on|off Show number of rows changed by SQL", +#ifndef SQLITE_SHELL_WASM_MODE ".check GLOB Fail if output since .testcase does not match", ".clone NEWDB Clone data into NEWDB from the existing database", +#endif ".connection [close] [#] Open or close an auxiliary database connection", ".databases List names and files of attached databases", ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) ".dbinfo ?DB? Show status information about the database", +#endif ".dump ?OBJECTS? Render database content as SQL", " Options:", " --data-only Output only INSERT statements", @@ -4281,9 +4316,13 @@ static const char *(azHelp[]) = { " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", #endif " trigger Like \"full\" but also show trigger bytecode", +#ifndef SQLITE_SHELL_WASM_MODE ".excel Display the output of next command in spreadsheet", " --bom Put a UTF8 byte-order mark on intermediate file", +#endif +#ifndef SQLITE_SHELL_WASM_MODE ".exit ?CODE? Exit this program with return-code CODE", +#endif ".expert EXPERIMENTAL. Suggest indexes for queries", ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", ".filectrl CMD ... Run various sqlite3_file_control() operations", @@ -4292,6 +4331,7 @@ static const char *(azHelp[]) = { ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", ".headers on|off Turn display of headers on or off", ".help ?-all? ?PATTERN? Show help text for PATTERN", +#ifndef SQLITE_SHELL_WASM_MODE ".import FILE TABLE Import data from FILE into TABLE", " Options:", " --ascii Use \\037 and \\036 as column and row separators", @@ -4306,6 +4346,7 @@ static const char *(azHelp[]) = { " from the \".mode\" output mode", " * If FILE begins with \"|\" then it is a command that generates the", " input text.", +#endif #ifndef SQLITE_OMIT_TEST_CONTROL ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", #endif @@ -4319,10 +4360,12 @@ static const char *(azHelp[]) = { ".lint OPTIONS Report potential schema issues.", " Options:", " fkey-indexes Find missing foreign key indexes", -#ifndef SQLITE_OMIT_LOAD_EXTENSION +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_WASM_MODE) ".load FILE ?ENTRY? Load an extension library", #endif +#ifndef SQLITE_SHELL_WASM_MODE ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", +#endif ".mode MODE ?OPTIONS? Set output mode", " MODE is one of:", " ascii Columns/rows delimited by 0x1F and 0x1E", @@ -4347,16 +4390,23 @@ static const char *(azHelp[]) = { " --quote Quote output text as SQL literals", " --noquote Do not quote output text", " TABLE The name of SQL table used for \"insert\" mode", +#ifndef SQLITE_SHELL_WASM_MODE ".nonce STRING Suspend safe mode for one command if nonce matches", +#endif ".nullvalue STRING Use STRING in place of NULL values", +#ifndef SQLITE_SHELL_WASM_MODE ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", " If FILE begins with '|' then open as a pipe", " --bom Put a UTF8 byte-order mark at the beginning", " -e Send output to the system text editor", " -x Send output as CSV to a spreadsheet (same as \".excel\")", + /* Note that .open is (partially) available in WASM builds but is + ** currently only intended to be used by the fiddle tool, not + ** end users, so is "undocumented." */ ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", " Options:", " --append Use appendvfs to append database to the end of FILE", +#endif #ifndef SQLITE_OMIT_DESERIALIZE " --deserialize Load into memory using sqlite3_deserialize()", " --hexdb Load the output of \"dbtotxt\" as an in-memory db", @@ -4366,12 +4416,14 @@ static const char *(azHelp[]) = { " --nofollow Do not follow symbolic links", " --readonly Open FILE readonly", " --zip FILE is a ZIP archive", +#ifndef SQLITE_SHELL_WASM_MODE ".output ?FILE? Send output to FILE or stdout if FILE is omitted", " If FILE begins with '|' then open it as a pipe.", " Options:", " --bom Prefix output with a UTF8 byte-order mark", " -e Send output to the system text editor", " -x Send output as CSV to a spreadsheet", +#endif ".parameter CMD ... Manage SQL parameter bindings", " clear Erase all bindings", " init Initialize the TEMP table that holds bindings", @@ -4388,9 +4440,11 @@ static const char *(azHelp[]) = { " --reset Reset the count for each input and interrupt", #endif ".prompt MAIN CONTINUE Replace the standard prompts", +#ifndef SQLITE_SHELL_WASM_MODE ".quit Exit this program", ".read FILE Read input from FILE or command output", " If FILE begins with \"|\", it is a command that generates the input.", +#endif #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) ".recover Recover as much data as possible from corrupt db.", " --freelist-corrupt Assume the freelist is corrupt", @@ -4399,8 +4453,10 @@ static const char *(azHelp[]) = { " --no-rowids Do not attempt to recover rowid values", " that are not also INTEGER PRIMARY KEYs", #endif +#ifndef SQLITE_SHELL_WASM_MODE ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", +#endif ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", ".schema ?PATTERN? Show the CREATE statements matching PATTERN", " Options:", @@ -4434,7 +4490,7 @@ static const char *(azHelp[]) = { " --sha3-384 Use the sha3-384 algorithm", " --sha3-512 Use the sha3-512 algorithm", " Any other argument is a LIKE pattern for tables to hash", -#ifndef SQLITE_NOHAVE_SYSTEM +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) ".shell CMD ARGS... Run CMD ARGS... in a system shell", #endif ".show Show the current values for various settings", @@ -4443,11 +4499,13 @@ static const char *(azHelp[]) = { " on Turn on automatic stat display", " stmt Show statement stats", " vmstep Show the virtual machine step count only", -#ifndef SQLITE_NOHAVE_SYSTEM +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) ".system CMD ARGS... Run CMD ARGS... in a system shell", #endif ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", +#ifndef SQLITE_SHELL_WASM_MODE ".testcase NAME Begin redirecting output to 'testcase-out.txt'", +#endif ".testctrl CMD ... Run various sqlite3_test_control() operations", " Run \".testctrl\" with no arguments for details", ".timeout MS Try opening locked tables for MS milliseconds", @@ -4992,20 +5050,24 @@ static void open_db(ShellState *p, int openFlags){ #ifndef SQLITE_OMIT_LOAD_EXTENSION sqlite3_enable_load_extension(p->db, 1); #endif - sqlite3_fileio_init(p->db, 0, 0); sqlite3_shathree_init(p->db, 0, 0); - sqlite3_completion_init(p->db, 0, 0); sqlite3_uint_init(p->db, 0, 0); sqlite3_decimal_init(p->db, 0, 0); sqlite3_regexp_init(p->db, 0, 0); sqlite3_ieee_init(p->db, 0, 0); sqlite3_series_init(p->db, 0, 0); +#ifndef SQLITE_SHELL_WASM_MODE + sqlite3_fileio_init(p->db, 0, 0); + sqlite3_completion_init(p->db, 0, 0); +#endif #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) sqlite3_dbdata_init(p->db, 0, 0); #endif #ifdef SQLITE_HAVE_ZLIB - sqlite3_zipfile_init(p->db, 0, 0); - sqlite3_sqlar_init(p->db, 0, 0); + if( !p->bSafeModePersist ){ + sqlite3_zipfile_init(p->db, 0, 0); + sqlite3_sqlar_init(p->db, 0, 0); + } #endif sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, shellAddSchemaName, 0, 0); @@ -5775,6 +5837,7 @@ static int db_int(sqlite3 *db, const char *zSql){ return res; } +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) /* ** Convert a 2-byte or 4-byte big-endian integer into a native integer */ @@ -5881,6 +5944,8 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion); return 0; } +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) + && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ /* ** Print the current sqlite3_errmsg() value to stderr and return 1. @@ -7834,7 +7899,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ -/* +/* * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it. * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE, * close db and set it to 0, and return the columns spec, to later @@ -7917,6 +7982,13 @@ UPDATE ColNames AS t SET reps=\ static const char * const zColDigits = "\ SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \ "; +#else + /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */ + static const char * const zColDigits = "\ +SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \ + WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \ + ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \ +"; #endif static const char * const zRenameRank = #ifdef SHELL_COLUMN_RENAME_CLEAN @@ -8002,11 +8074,7 @@ FROM (\ /* Formulate the columns spec, close the DB, zero *pDb. */ char *zColsSpec = 0; int hasDupes = db_int(*pDb, zHasDupes); -#ifdef SQLITE_ENABLE_MATH_FUNCTIONS int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; -#else -# define nDigits 2 -#endif if( hasDupes ){ #ifdef SHELL_COLUMN_RENAME_CLEAN rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); @@ -8117,7 +8185,8 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \ + && !defined(SQLITE_SHELL_WASM_MODE) if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){ open_db(p, 0); failIfSafeMode(p, "cannot run .archive in safe mode"); @@ -8125,6 +8194,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif +#ifndef SQLITE_SHELL_WASM_MODE if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) ){ @@ -8193,6 +8263,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } close_db(pDest); }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){ if( nArg==2 ){ @@ -8223,6 +8294,7 @@ static int do_meta_command(char *zLine, ShellState *p){ test_breakpoint(); }else +#ifndef SQLITE_SHELL_WASM_MODE if( c=='c' && strcmp(azArg[0],"cd")==0 ){ failIfSafeMode(p, "cannot run .cd in safe mode"); if( nArg==2 ){ @@ -8242,6 +8314,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; } }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){ if( nArg==2 ){ @@ -8252,6 +8325,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else +#ifndef SQLITE_SHELL_WASM_MODE /* Cancel output redirection, if it is currently set (by .testcase) ** Then read the content of the testcase-out.txt file and compare against ** azArg[1]. If there are differences, report an error and exit. @@ -8276,7 +8350,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_free(zRes); }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#ifndef SQLITE_SHELL_WASM_MODE if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ failIfSafeMode(p, "cannot run .clone in safe mode"); if( nArg==2 ){ @@ -8286,6 +8362,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; } }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){ if( nArg==1 ){ @@ -8411,11 +8488,11 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ rc = shell_dbinfo_command(p, nArg, azArg); }else -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){ open_db(p, 0); rc = recoverDatabaseCmd(p, nArg, azArg); @@ -8428,7 +8505,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int i; int savedShowHeader = p->showHeader; int savedShellFlags = p->shellFlgs; - ShellClearFlag(p, + ShellClearFlag(p, SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); for(i=1; i1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); rc = 2; }else +#endif /* The ".explain" command is automatic now. It is largely pointless. It ** retained purely for backwards compatibility */ @@ -8832,6 +8911,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else +#ifndef SQLITE_SHELL_WASM_MODE if( c=='i' && strncmp(azArg[0], "import", n)==0 ){ char *zTable = 0; /* Insert data into this table */ char *zSchema = 0; /* within this schema (may default to "main") */ @@ -8853,16 +8933,12 @@ static int do_meta_command(char *zLine, ShellState *p){ failIfSafeMode(p, "cannot run .import in safe mode"); memset(&sCtx, 0, sizeof(sCtx)); - sCtx.z = sqlite3_malloc64(120); - if( sCtx.z==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } if( p->mode==MODE_Ascii ){ xRead = ascii_read_one_field; }else{ xRead = csv_read_one_field; } + rc = 1; for(i=1; iout, "ERROR: extra argument: \"%s\". Usage:\n", z); showHelp(p->out, "import"); - rc = 1; goto meta_command_exit; } }else if( strcmp(z,"-v")==0 ){ @@ -8896,7 +8971,6 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z); showHelp(p->out, "import"); - rc = 1; goto meta_command_exit; } } @@ -8904,7 +8978,6 @@ static int do_meta_command(char *zLine, ShellState *p){ utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n", zFile==0 ? "FILE" : "TABLE"); showHelp(p->out, "import"); - rc = 1; goto meta_command_exit; } seenInterrupt = 0; @@ -8916,21 +8989,18 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nSep==0 ){ raw_printf(stderr, "Error: non-null column separator required for import\n"); - rc = 1; goto meta_command_exit; } if( nSep>1 ){ - raw_printf(stderr, + raw_printf(stderr, "Error: multi-character column separators not allowed" " for import\n"); - rc = 1; goto meta_command_exit; } nSep = strlen30(p->rowSeparator); if( nSep==0 ){ raw_printf(stderr, "Error: non-null row separator required for import\n"); - rc = 1; goto meta_command_exit; } if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){ @@ -8944,7 +9014,6 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nSep>1 ){ raw_printf(stderr, "Error: multi-character row separators not allowed" " for import\n"); - rc = 1; goto meta_command_exit; } sCtx.cColSep = p->colSeparator[0]; @@ -8955,7 +9024,6 @@ static int do_meta_command(char *zLine, ShellState *p){ if( sCtx.zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN raw_printf(stderr, "Error: pipes are not supported in this OS\n"); - rc = 1; goto meta_command_exit; #else sCtx.in = popen(sCtx.zFile+1, "r"); @@ -8968,8 +9036,6 @@ static int do_meta_command(char *zLine, ShellState *p){ } if( sCtx.in==0 ){ utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); - rc = 1; - import_cleanup(&sCtx); goto meta_command_exit; } if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ @@ -8983,6 +9049,11 @@ static int do_meta_command(char *zLine, ShellState *p){ output_c_string(p->out, zSep); utf8_printf(p->out, "\n"); } + sCtx.z = sqlite3_malloc64(120); + if( sCtx.z==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } /* Below, resources must be freed before exit. */ while( (nSkip--)>0 ){ while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} @@ -9131,6 +9202,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sCtx.nRow, sCtx.nErr, sCtx.nLine-1); } }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ #ifndef SQLITE_UNTESTABLE if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){ @@ -9320,7 +9392,7 @@ static int do_meta_command(char *zLine, ShellState *p){ lintDotCommand(p, azArg, nArg); }else -#ifndef SQLITE_OMIT_LOAD_EXTENSION +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_WASM_MODE) if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ const char *zFile, *zProc; char *zErrMsg = 0; @@ -9342,6 +9414,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif +#ifndef SQLITE_SHELL_WASM_MODE if( c=='l' && strncmp(azArg[0], "log", n)==0 ){ failIfSafeMode(p, "cannot run .log in safe mode"); if( nArg!=2 ){ @@ -9353,6 +9426,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->pLog = output_file_open(zFile, 0); } }else +#endif if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){ const char *zMode = 0; @@ -9477,6 +9551,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->cMode = p->mode; }else +#ifndef SQLITE_SHELL_WASM_MODE if( c=='n' && strcmp(azArg[0], "nonce")==0 ){ if( nArg!=2 ){ raw_printf(stderr, "Usage: .nonce NONCE\n"); @@ -9491,6 +9566,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** at the end of this procedure */ } }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ if( nArg==2 ){ @@ -9512,6 +9588,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* Check for command-line arguments */ for(iName=1; iNameszMax = integerValue(azArg[++iName]); #endif /* SQLITE_OMIT_DESERIALIZE */ - }else if( z[0]=='-' ){ + }else +#endif /* !SQLITE_SHELL_WASM_MODE */ + if( z[0]=='-' ){ utf8_printf(stderr, "unknown option: %s\n", z); rc = 1; goto meta_command_exit; @@ -9559,6 +9638,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* If a filename is specified, try to open it first */ if( zFN || p->openMode==SHELL_OPEN_HEXDB ){ if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN); +#ifndef SQLITE_SHELL_WASM_MODE if( p->bSafeMode && p->openMode!=SHELL_OPEN_HEXDB && zFN @@ -9566,6 +9646,9 @@ static int do_meta_command(char *zLine, ShellState *p){ ){ failIfSafeMode(p, "cannot open disk-based database files in safe mode"); } +#else + /* WASM mode has its own sandboxed pseudo-filesystem. */ +#endif if( zFN ){ zNewFilename = sqlite3_mprintf("%s", zFN); shell_check_oom(zNewFilename); @@ -9588,6 +9671,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else +#ifndef SQLITE_SHELL_WASM_MODE if( (c=='o' && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0)) || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0) @@ -9596,9 +9680,10 @@ static int do_meta_command(char *zLine, ShellState *p){ int bTxtMode = 0; int i; int eMode = 0; - int bBOM = 0; - int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ + int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ + unsigned char zBOM[4]; /* Byte-order mark to using if --bom is present */ + zBOM[0] = 0; failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); if( c=='e' ){ eMode = 'x'; @@ -9611,7 +9696,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ){ if( z[1]=='-' ) z++; if( strcmp(z,"-bom")==0 ){ - bBOM = 1; + zBOM[0] = 0xef; + zBOM[1] = 0xbb; + zBOM[2] = 0xbf; + zBOM[3] = 0; }else if( c!='e' && strcmp(z,"-x")==0 ){ eMode = 'x'; /* spreadsheet */ }else if( c!='e' && strcmp(z,"-e")==0 ){ @@ -9680,7 +9768,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->out = stdout; rc = 1; }else{ - if( bBOM ) fprintf(p->out,"\357\273\277"); + if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } #endif @@ -9693,12 +9781,13 @@ static int do_meta_command(char *zLine, ShellState *p){ p->out = stdout; rc = 1; } else { - if( bBOM ) fprintf(p->out,"\357\273\277"); + if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } } sqlite3_free(zFile); }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){ open_db(p,0); @@ -9868,10 +9957,13 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else +#ifndef SQLITE_SHELL_WASM_MODE if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ rc = 2; }else +#endif +#ifndef SQLITE_SHELL_WASM_MODE if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){ FILE *inSaved = p->in; int savedLineno = p->lineno; @@ -9906,7 +9998,9 @@ static int do_meta_command(char *zLine, ShellState *p){ p->in = inSaved; p->lineno = savedLineno; }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#ifndef SQLITE_SHELL_WASM_MODE if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){ const char *zSrcFile; const char *zDb; @@ -9958,6 +10052,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } close_db(pSrc); }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ @@ -10114,7 +10209,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ + if( (c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0) + || (c=='t' && n==9 && strncmp(azArg[0], "treetrace", n)==0) + ){ unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); }else @@ -10581,7 +10678,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(zSql); }else -#ifndef SQLITE_NOHAVE_SYSTEM +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) if( c=='s' && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0) ){ @@ -10602,7 +10699,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(zCmd); if( x ) raw_printf(stderr, "System command returns %d\n", x); }else -#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) */ if( c=='s' && strncmp(azArg[0], "show", n)==0 ){ static const char *azBool[] = { "off", "on", "trigger", "full"}; @@ -10614,7 +10711,7 @@ static int do_meta_command(char *zLine, ShellState *p){ goto meta_command_exit; } utf8_printf(p->out, "%12.12s: %s\n","echo", - azBool[ShellHasFlag(p, SHFLG_Echo)]); + azBool[ShellHasFlag(p, SHFLG_Echo)]); utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); utf8_printf(p->out, "%12.12s: %s\n","explain", p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); @@ -10782,6 +10879,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(azResult); }else +#ifndef SQLITE_SHELL_WASM_MODE /* Begin redirecting output to the file "testcase-out.txt" */ if( c=='t' && strcmp(azArg[0],"testcase")==0 ){ output_reset(p); @@ -10795,6 +10893,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); } }else +#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ #ifndef SQLITE_UNTESTABLE if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){ @@ -11364,7 +11463,7 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss){ cWait = 0; qss = QSS_SETV(qss, 0); goto PlainScan; - default: assert(0); + default: assert(0); } } } @@ -11385,7 +11484,7 @@ static int line_is_command_terminator(char *zLine){ zLine += 2; /* SQL Server */ else return 0; - return quickscan(zLine,QSS_Start)==QSS_Start; + return quickscan(zLine, QSS_Start)==QSS_Start; } /* @@ -11462,6 +11561,42 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ return 0; } +static void echo_group_input(ShellState *p, const char *zDo){ + if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); +} + +#ifdef SQLITE_SHELL_WASM_MODE +/* +** Alternate one_input_line() impl for wasm mode. This is not in the primary impl +** because we need the global shellState and cannot access it from that function +** without moving lots of code around (creating a larger/messier diff). +*/ +static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ + /* Parse the next line from shellState.wasm.zInput. */ + const char *zBegin = shellState.wasm.zPos; + const char *z = zBegin; + char *zLine = 0; + int nZ = 0; + + UNUSED_PARAMETER(in); + UNUSED_PARAMETER(isContinuation); + if(!z || !*z){ + return 0; + } + while(*z && isspace(*z)) ++z; + zBegin = z; + for(; *z && '\n'!=*z; ++nZ, ++z){} + if(nZ>0 && '\r'==zBegin[nZ-1]){ + --nZ; + } + shellState.wasm.zPos = z; + zLine = realloc(zPrior, nZ+1); + shell_check_oom(zLine); + memcpy(zLine, zBegin, (size_t)nZ); + zLine[nZ] = 0; + return zLine; +} +#endif /* SQLITE_SHELL_WASM_MODE */ /* ** Read input from *in and process it. If *in==0 then input @@ -11511,14 +11646,13 @@ static int process_input(ShellState *p){ } qss = quickscan(zLine, qss); if( QSS_PLAINWHITE(qss) && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) - printf("%s\n", zLine); /* Just swallow single-line whitespace */ + echo_group_input(p, zLine); qss = QSS_Start; continue; } if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine); + echo_group_input(p, zLine); if( zLine[0]=='.' ){ rc = do_meta_command(zLine, p); if( rc==2 ){ /* exit requested */ @@ -11551,6 +11685,7 @@ static int process_input(ShellState *p){ nSql += nLine; } if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ + echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); nSql = 0; if( p->outCount ){ @@ -11562,13 +11697,14 @@ static int process_input(ShellState *p){ p->bSafeMode = p->bSafeModePersist; qss = QSS_Start; }else if( nSql && QSS_PLAINWHITE(qss) ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql); + echo_group_input(p, zSql); nSql = 0; qss = QSS_Start; } } if( nSql ){ /* This may be incomplete. Let the SQL parser deal with that. */ + echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); } free(zSql); @@ -11707,7 +11843,7 @@ static const char zOptions[] = #if !defined(SQLITE_OMIT_DESERIALIZE) " -deserialize open the database using sqlite3_deserialize()\n" #endif - " -echo print commands before execution\n" + " -echo print inputs before execution\n" " -init FILENAME read/process named file\n" " -[no]header turn headers on or off\n" #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) @@ -11843,14 +11979,25 @@ static char *cmdline_option_value(int argc, char **argv, int i){ # endif #endif +#ifdef SQLITE_SHELL_WASM_MODE +# define main fiddle_main +#endif + #if SQLITE_SHELL_IS_UTF8 int SQLITE_CDECL main(int argc, char **argv){ #else int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ char **argv; +#endif +#ifdef SQLITE_DEBUG + sqlite3_int64 mem_main_enter = sqlite3_memory_used(); #endif char *zErrMsg = 0; +#ifdef SQLITE_SHELL_WASM_MODE +# define data shellState +#else ShellState data; +#endif const char *zInitFile = 0; int i; int rc = 0; @@ -11866,8 +12013,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ setBinaryMode(stdin, 0); setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ +#ifdef SQLITE_SHELL_WASM_MODE + stdin_is_interactive = 0; + stdout_is_console = 1; +#else stdin_is_interactive = isatty(0); stdout_is_console = isatty(1); +#endif #if !defined(_WIN32_WCE) if( getenv("SQLITE_DEBUG_BREAK") ){ @@ -12123,7 +12275,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif } data.out = stdout; +#ifndef SQLITE_SHELL_WASM_MODE sqlite3_appendvfs_init(0,0,0); +#endif /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database @@ -12389,6 +12543,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ rc = process_input(&data); } } +#ifndef SQLITE_SHELL_WASM_MODE + /* In WASM mode we have to leave the db state in place so that + ** client code can "push" SQL into it after this call returns. */ free(azCmd); set_table_name(&data, 0); if( data.db ){ @@ -12415,5 +12572,136 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ /* Clear the global data structure so that valgrind will detect memory ** leaks */ memset(&data, 0, sizeof(data)); +#ifdef SQLITE_DEBUG + if( sqlite3_memory_used()>mem_main_enter ){ + utf8_printf(stderr, "Memory leaked: %u bytes\n", + (unsigned int)(sqlite3_memory_used()-mem_main_enter)); + } +#endif +#endif /* !SQLITE_SHELL_WASM_MODE */ return rc; } + + +#ifdef SQLITE_SHELL_WASM_MODE +/* Only for emcc experimentation purposes. */ +int fiddle_experiment(int a,int b){ + return a + b; +} + +/* Only for emcc experimentation purposes. + + Define this function in JS using: + + emcc ... --js-library somefile.js + + containing: + +mergeInto(LibraryManager.library, { + my_foo: function(){ + console.debug("my_foo()",arguments); + } +}); +*/ +/*extern void my_foo(sqlite3 *);*/ +/* Only for emcc experimentation purposes. */ +sqlite3 * fiddle_the_db(){ + printf("fiddle_the_db(%p)\n", (const void*)globalDb); + /*my_foo(globalDb);*/ + return globalDb; +} +/* Only for emcc experimentation purposes. */ +sqlite3 * fiddle_db_arg(sqlite3 *arg){ + printf("fiddle_db_arg(%p)\n", (const void*)arg); + return arg; +} + +/* +** Intended to be called via a SharedWorker() while a separate +** SharedWorker() (which manages the wasm module) is performing work +** which should be interrupted. Unfortunately, SharedWorker is not +** portable enough to make real use of. +*/ +void fiddle_interrupt(void){ + if(globalDb) sqlite3_interrupt(globalDb); +} + +/* +** Returns the filename of the given db name, assuming "main" if +** zDbName is NULL. Returns NULL if globalDb is not opened. +*/ +const char * fiddle_db_filename(const char * zDbName){ + return globalDb + ? sqlite3_db_filename(globalDb, zDbName ? zDbName : "main") + : NULL; +} + +/* +** Closes, unlinks, and reopens the db using its current filename (or +** the default if the db is currently closed). It is assumed, for +** purposes of the fiddle build, that the file is in a transient +** virtual filesystem within the browser. +*/ +void fiddle_reset_db(void){ + char *zFilename = 0; + if(0==globalDb){ + shellState.pAuxDb->zDbFilename = "/fiddle.sqlite3"; + }else{ + zFilename = + sqlite3_mprintf("%s", sqlite3_db_filename(globalDb, "main")); + shell_check_oom(zFilename); + close_db(globalDb); + shellDeleteFile(zFilename); + shellState.db = 0; + shellState.pAuxDb->zDbFilename = zFilename; + } + open_db(&shellState, 0); + sqlite3_free(zFilename); +} + +/* +** Trivial exportable function for emscripten. Needs to be exported using: +** +** emcc ..flags... -sEXPORTED_FUNCTIONS=_fiddle_exec -sEXPORTED_RUNTIME_METHODS=ccall,cwrap +** +** (Note the underscore before the function name.) It processes zSql +** as if it were input to the sqlite3 shell and redirects all output +** to the wasm binding. +*/ +void fiddle_exec(const char * zSql){ + static int once = 0; + int rc = 0; + if(!once){ + /* Simulate an argv array for main() */ + static char * argv[] = {"fiddle", + "-bail", + "-safe"}; + rc = fiddle_main((int)(sizeof(argv)/sizeof(argv[0])), argv); + once = rc ? -1 : 1; + memset(&shellState.wasm, 0, sizeof(shellState.wasm)); + printf( + "SQLite version %s %.19s\n" /*extra-version-info*/, + sqlite3_libversion(), sqlite3_sourceid() + ); + puts("WASM shell"); + puts("Enter \".help\" for usage hints."); + if(once>0){ + fiddle_reset_db(); + } + if(shellState.db){ + printf("Connected to %s.\n", fiddle_db_filename(NULL)); + }else{ + fprintf(stderr,"ERROR initializing db!\n"); + return; + } + } + if(once<0){ + puts("DB init failed. Not executing SQL."); + }else if(zSql && *zSql){ + shellState.wasm.zInput = zSql; + shellState.wasm.zPos = zSql; + process_input(&shellState); + memset(&shellState.wasm, 0, sizeof(shellState.wasm)); + } +} +#endif /* SQLITE_SHELL_WASM_MODE */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 6062f79c2..e2281e497 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -5593,7 +5593,8 @@ unsigned int sqlite3_value_subtype(sqlite3_value*); ** object D and returns a pointer to that copy. ^The [sqlite3_value] returned ** is a [protected sqlite3_value] object even if the input is not. ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a -** memory allocation fails. +** memory allocation fails. ^If V is a [pointer value], then the result +** of sqlite3_value_dup(V) is a NULL value. ** ** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object ** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer @@ -6275,6 +6276,28 @@ int sqlite3_get_autocommit(sqlite3*); */ sqlite3 *sqlite3_db_handle(sqlite3_stmt*); +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer of N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +const char *sqlite3_db_name(sqlite3 *db, int N); + /* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 @@ -9554,8 +9577,8 @@ SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); ** of a [virtual table] implementation. The result of calling this ** interface from outside of xBestIndex() is undefined and probably harmful. ** -** ^The sqlite3_vtab_distinct() interface returns an integer that is -** either 0, 1, or 2. The integer returned by sqlite3_vtab_distinct() +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() ** gives the virtual table additional information about how the query ** planner wants the output to be ordered. As long as the virtual table ** can meet the ordering requirements of the query planner, it may set @@ -9587,6 +9610,13 @@ SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); ** that have the same value for all columns identified by "aOrderBy". ** ^However omitting the extra rows is optional. ** This mode is used for a DISTINCT query. +**
    5. +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means +** that the query planner needs only distinct rows but it does need the +** rows to be sorted.)^ ^The virtual table implementation is free to omit +** rows that are identical in all aOrderBy columns, if it wants to, but +** it is not required to omit any rows. This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. **

    6. + SQLite3 Fiddle + Powered by + SQLite3 +
    ** ** ^For the purposes of comparing virtual table output values to see if the diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 2eac4f3f0..2cdd0e429 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -351,6 +351,12 @@ struct sqlite3_api_routines { int (*vtab_in)(sqlite3_index_info*,int,int); int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); + /* Version 3.39.0 and later */ + int (*deserialize)(sqlite3*,const char*,unsigned char*, + sqlite3_int64,sqlite3_int64,unsigned); + unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, + unsigned int); + const char *(*db_name)(sqlite3*,int); }; /* @@ -669,6 +675,12 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_vtab_in sqlite3_api->vtab_in #define sqlite3_vtab_in_first sqlite3_api->vtab_in_first #define sqlite3_vtab_in_next sqlite3_api->vtab_in_next +/* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE +#define sqlite3_deserialize sqlite3_api->deserialize +#define sqlite3_serialize sqlite3_api->serialize +#endif +#define sqlite3_db_name sqlite3_api->db_name #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 78c16bea1..106a5d147 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -939,8 +939,19 @@ typedef INT16_TYPE LogEst; /* ** Round up a number to the next larger multiple of 8. This is used ** to force 8-byte alignment on 64-bit architectures. +** +** ROUND8() always does the rounding, for any argument. +** +** ROUND8P() assumes that the argument is already an integer number of +** pointers in size, and so it is a no-op on systems where the pointer +** size is 8. */ #define ROUND8(x) (((x)+7)&~7) +#if SQLITE_PTRSIZE==8 +# define ROUND8P(x) (x) +#else +# define ROUND8P(x) (((x)+7)&~7) +#endif /* ** Round down to the nearest multiple of 8 @@ -1003,22 +1014,23 @@ typedef INT16_TYPE LogEst; #endif /* -** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not -** the Select query generator tracing logic is turned on. +** TREETRACE_ENABLED will be either 1 or 0 depending on whether or not +** the Abstract Syntax Tree tracing logic is turned on. */ #if !defined(SQLITE_AMALGAMATION) -extern u32 sqlite3SelectTrace; +extern u32 sqlite3TreeTrace; #endif #if defined(SQLITE_DEBUG) \ - && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE)) -# define SELECTTRACE_ENABLED 1 + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \ + || defined(SQLITE_ENABLE_TREETRACE)) +# define TREETRACE_ENABLED 1 # define SELECTTRACE(K,P,S,X) \ - if(sqlite3SelectTrace&(K)) \ + if(sqlite3TreeTrace&(K)) \ sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ sqlite3DebugPrintf X #else # define SELECTTRACE(K,P,S,X) -# define SELECTTRACE_ENABLED 0 +# define TREETRACE_ENABLED 0 #endif /* @@ -1103,7 +1115,7 @@ struct BusyHandler { ** pointer will work here as long as it is distinct from SQLITE_STATIC ** and SQLITE_TRANSIENT. */ -#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomFault) +#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomClear) /* ** When SQLITE_OMIT_WSD is defined, it means that the target platform does @@ -1179,6 +1191,7 @@ typedef struct Lookaside Lookaside; typedef struct LookasideSlot LookasideSlot; typedef struct Module Module; typedef struct NameContext NameContext; +typedef struct OnOrUsing OnOrUsing; typedef struct Parse Parse; typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; @@ -1766,6 +1779,9 @@ struct sqlite3 { #define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */ #define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */ #define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */ +#define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */ +#define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ + /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -1868,7 +1884,7 @@ struct FuncDestructor { #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ #define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */ -#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */ +/* 0x8000 -- available for reuse */ #define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */ #define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */ #define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */ @@ -1885,6 +1901,7 @@ struct FuncDestructor { #define INLINEFUNC_expr_compare 3 #define INLINEFUNC_affinity 4 #define INLINEFUNC_iif 5 +#define INLINEFUNC_sqlite_offset 6 #define INLINEFUNC_unlikely 99 /* Default case */ /* @@ -2111,6 +2128,7 @@ struct Column { #define COLFLAG_NOTAVAIL 0x0080 /* STORED column not yet calculated */ #define COLFLAG_BUSY 0x0100 /* Blocks recursion on GENERATED columns */ #define COLFLAG_HASCOLL 0x0200 /* Has collating sequence name in zCnName */ +#define COLFLAG_NOEXPAND 0x0400 /* Omit this column when expanding "*" */ #define COLFLAG_GENERATED 0x0060 /* Combo: _STORED, _VIRTUAL */ #define COLFLAG_NOINSERT 0x0062 /* Combo: _HIDDEN, _STORED, _VIRTUAL */ @@ -2517,6 +2535,11 @@ struct KeyInfo { struct UnpackedRecord { KeyInfo *pKeyInfo; /* Collation and sort-order information */ Mem *aMem; /* Values */ + union { + char *z; /* Cache of aMem[0].z for vdbeRecordCompareString() */ + i64 i; /* Cache of aMem[0].u.i for vdbeRecordCompareInt() */ + } u; + int n; /* Cache of aMem[0].n used by vdbeRecordCompareString() */ u16 nField; /* Number of entries in apMem[] */ i8 default_rc; /* Comparison result if keys are equal */ u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */ @@ -2825,7 +2848,7 @@ struct Expr { ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ union { - int iRightJoinTable; /* If EP_FromJoin, the right table of the join */ + int iJoin; /* If EP_OuterON or EP_InnerON, the right table */ int iOfst; /* else: start of token from start of statement */ } w; AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ @@ -2846,29 +2869,29 @@ struct Expr { ** EP_Agg == NC_HasAgg == SF_HasAgg ** EP_Win == NC_HasWin */ -#define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */ -#define EP_Distinct 0x000002 /* Aggregate function with DISTINCT keyword */ -#define EP_HasFunc 0x000004 /* Contains one or more functions of any kind */ -#define EP_FixedCol 0x000008 /* TK_Column with a known fixed value */ +#define EP_OuterON 0x000001 /* Originates in ON/USING clause of outer join */ +#define EP_InnerON 0x000002 /* Originates in ON/USING of an inner join */ +#define EP_Distinct 0x000004 /* Aggregate function with DISTINCT keyword */ +#define EP_HasFunc 0x000008 /* Contains one or more functions of any kind */ #define EP_Agg 0x000010 /* Contains one or more aggregate functions */ -#define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */ -#define EP_DblQuoted 0x000040 /* token.z was originally in "..." */ -#define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */ -#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE operator */ -#define EP_Commuted 0x000200 /* Comparison operator has been commuted */ -#define EP_IntValue 0x000400 /* Integer value contained in u.iValue */ -#define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */ -#define EP_Skip 0x001000 /* Operator does not contribute to affinity */ -#define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ -#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ +#define EP_FixedCol 0x000020 /* TK_Column with a known fixed value */ +#define EP_VarSelect 0x000040 /* pSelect is correlated, not constant */ +#define EP_DblQuoted 0x000080 /* token.z was originally in "..." */ +#define EP_InfixFunc 0x000100 /* True for an infix function: LIKE, GLOB, etc */ +#define EP_Collate 0x000200 /* Tree contains a TK_COLLATE operator */ +#define EP_Commuted 0x000400 /* Comparison operator has been commuted */ +#define EP_IntValue 0x000800 /* Integer value contained in u.iValue */ +#define EP_xIsSelect 0x001000 /* x.pSelect is valid (otherwise x.pList is) */ +#define EP_Skip 0x002000 /* Operator does not contribute to affinity */ +#define EP_Reduced 0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ #define EP_Win 0x008000 /* Contains window functions */ -#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */ -#define EP_IfNullRow 0x020000 /* The TK_IF_NULL_ROW opcode */ -#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */ -#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ -#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ -#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ - /* 0x400000 // Available */ +#define EP_TokenOnly 0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ +#define EP_MemToken 0x020000 /* Need to sqlite3DbFree() Expr.zToken */ +#define EP_IfNullRow 0x040000 /* The TK_IF_NULL_ROW opcode */ +#define EP_Unlikely 0x080000 /* unlikely() or likelihood() function */ +#define EP_ConstFunc 0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ +#define EP_CanBeNull 0x200000 /* Can be null despite NOT NULL constraint */ +#define EP_Subquery 0x400000 /* Tree contains a TK_SELECT operator */ #define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ #define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */ #define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */ @@ -2891,8 +2914,8 @@ struct Expr { #define ExprHasAllProperty(E,P) (((E)->flags&(P))==(P)) #define ExprSetProperty(E,P) (E)->flags|=(P) #define ExprClearProperty(E,P) (E)->flags&=~(P) -#define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue) -#define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse) +#define ExprAlwaysTrue(E) (((E)->flags&(EP_OuterON|EP_IsTrue))==EP_IsTrue) +#define ExprAlwaysFalse(E) (((E)->flags&(EP_OuterON|EP_IsFalse))==EP_IsFalse) /* Macros used to ensure that the correct members of unions are accessed ** in Expr. @@ -2979,12 +3002,18 @@ struct ExprList { struct ExprList_item { /* For each expression in the list */ Expr *pExpr; /* The parse tree for this expression */ char *zEName; /* Token associated with this expression */ - u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */ - unsigned eEName :2; /* Meaning of zEName */ - unsigned done :1; /* A flag to indicate when processing is finished */ - unsigned reusable :1; /* Constant expression is reusable */ - unsigned bSorterRef :1; /* Defer evaluation until after sorting */ - unsigned bNulls: 1; /* True if explicit "NULLS FIRST/LAST" */ + struct { + u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */ + unsigned eEName :2; /* Meaning of zEName */ + unsigned done :1; /* Indicates when processing is finished */ + unsigned reusable :1; /* Constant expression is reusable */ + unsigned bSorterRef :1; /* Defer evaluation until after sorting */ + unsigned bNulls :1; /* True if explicit "NULLS FIRST/LAST" */ + unsigned bUsed :1; /* This column used in a SF_NestedFrom subquery */ + unsigned bUsingTerm:1; /* Term from the USING clause of a NestedFrom */ + unsigned bNoExpand: 1; /* Term is an auxiliary in NestedFrom and should + ** not be expanded by "*" in parent queries */ + } fg; union { struct { /* Used by any ExprList other than Parse.pConsExpr */ u16 iOrderByCol; /* For ORDER BY, column number in result set */ @@ -3019,13 +3048,25 @@ struct ExprList { ** If "a" is the k-th column of table "t", then IdList.a[0].idx==k. */ struct IdList { + int nId; /* Number of identifiers on the list */ + u8 eU4; /* Which element of a.u4 is valid */ struct IdList_item { char *zName; /* Name of the identifier */ - int idx; /* Index in some Table.aCol[] of a column named zName */ - } *a; - int nId; /* Number of identifiers on the list */ + union { + int idx; /* Index in some Table.aCol[] of a column named zName */ + Expr *pExpr; /* Expr to implement a USING variable -- NOT USED */ + } u4; + } a[1]; }; +/* +** Allowed values for IdList.eType, which determines which value of the a.u4 +** is valid. +*/ +#define EU4_NONE 0 /* Does not use IdList.a.u4 */ +#define EU4_IDX 1 /* Uses IdList.a.u4.idx */ +#define EU4_EXPR 2 /* Uses IdList.a.u4.pExpr -- NOT CURRENTLY USED */ + /* ** The SrcItem object represents a single term in the FROM clause of a query. ** The SrcList object is mostly an array of SrcItems. @@ -3053,15 +3094,22 @@ struct SrcItem { unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */ unsigned isTabFunc :1; /* True if table-valued-function syntax */ unsigned isCorrelated :1; /* True if sub-query is correlated */ + unsigned isMaterialized:1; /* This is a materialized view */ unsigned viaCoroutine :1; /* Implemented as a co-routine */ unsigned isRecursive :1; /* True for recursive reference in WITH */ unsigned fromDDL :1; /* Comes from sqlite_schema */ unsigned isCte :1; /* This is a CTE */ unsigned notCte :1; /* This item may not match a CTE */ + unsigned isUsing :1; /* u3.pUsing is valid */ + unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ + unsigned isSynthUsing :1; /* u3.pUsing is synthensized from NATURAL */ + unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ } fg; 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 */ + union { + Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */ + IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */ + } u3; Bitmask colUsed; /* Bit N (1<" clause */ @@ -3073,6 +3121,15 @@ struct SrcItem { } u2; }; +/* +** The OnOrUsing object represents either an ON clause or a USING clause. +** It can never be both at the same time, but it can be neither. +*/ +struct OnOrUsing { + Expr *pOn; /* The ON clause of a join */ + IdList *pUsing; /* The USING clause of a join */ +}; + /* ** The following structure describes the FROM clause of a SELECT statement. ** Each table or subquery in the FROM clause is a separate element of @@ -3101,14 +3158,15 @@ struct SrcList { /* ** Permitted values of the SrcList.a.jointype field */ -#define JT_INNER 0x0001 /* Any kind of inner or cross join */ -#define JT_CROSS 0x0002 /* Explicit use of the CROSS keyword */ -#define JT_NATURAL 0x0004 /* True for a "natural" join */ -#define JT_LEFT 0x0008 /* Left outer join */ -#define JT_RIGHT 0x0010 /* Right outer join */ -#define JT_OUTER 0x0020 /* The "OUTER" keyword is present */ -#define JT_ERROR 0x0040 /* unknown or unsupported join type */ - +#define JT_INNER 0x01 /* Any kind of inner or cross join */ +#define JT_CROSS 0x02 /* Explicit use of the CROSS keyword */ +#define JT_NATURAL 0x04 /* True for a "natural" join */ +#define JT_LEFT 0x08 /* Left outer join */ +#define JT_RIGHT 0x10 /* Right outer join */ +#define JT_OUTER 0x20 /* The "OUTER" keyword is present */ +#define JT_LTORJ 0x40 /* One of the LEFT operands of a RIGHT JOIN + ** Mnemonic: Left Table Of Right Join */ +#define JT_ERROR 0x80 /* unknown or unsupported join type */ /* ** Flags appropriate for the wctrlFlags parameter of sqlite3WhereBegin() @@ -3131,7 +3189,7 @@ struct SrcList { #define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */ #define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */ #define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ - /* 0x1000 not currently used */ +#define WHERE_RIGHT_JOIN 0x1000 /* Processing a RIGHT JOIN */ /* 0x2000 not currently used */ #define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */ /* 0x8000 not currently used */ @@ -3327,6 +3385,9 @@ struct Select { #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ +/* True if S exists and has SF_NestedFrom */ +#define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) + /* ** The results of a SELECT can be distributed in several ways, as defined ** by one of the following macros. The "SRT" prefix means "SELECT Result @@ -3538,6 +3599,7 @@ struct Parse { u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 disableVtab; /* Disable all virtual tables for this parse */ + u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif @@ -3710,20 +3772,20 @@ struct AuthContext { #define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */ /* - * Each trigger present in the database schema is stored as an instance of - * struct Trigger. - * - * Pointers to instances of struct Trigger are stored in two ways. - * 1. In the "trigHash" hash table (part of the sqlite3* that represents the - * database). This allows Trigger structures to be retrieved by name. - * 2. All triggers associated with a single table form a linked list, using the - * pNext member of struct Trigger. A pointer to the first element of the - * linked list is stored as the "pTrigger" member of the associated - * struct Table. - * - * The "step_list" member points to the first element of a linked list - * containing the SQL statements specified as the trigger program. - */ +** Each trigger present in the database schema is stored as an instance of +** struct Trigger. +** +** Pointers to instances of struct Trigger are stored in two ways. +** 1. In the "trigHash" hash table (part of the sqlite3* that represents the +** database). This allows Trigger structures to be retrieved by name. +** 2. All triggers associated with a single table form a linked list, using the +** pNext member of struct Trigger. A pointer to the first element of the +** linked list is stored as the "pTrigger" member of the associated +** struct Table. +** +** The "step_list" member points to the first element of a linked list +** containing the SQL statements specified as the trigger program. +*/ struct Trigger { char *zName; /* The name of the trigger */ char *table; /* The table or view to which the trigger applies */ @@ -3750,43 +3812,48 @@ struct Trigger { #define TRIGGER_AFTER 2 /* - * An instance of struct TriggerStep is used to store a single SQL statement - * that is a part of a trigger-program. - * - * Instances of struct TriggerStep are stored in a singly linked list (linked - * using the "pNext" member) referenced by the "step_list" member of the - * associated struct Trigger instance. The first element of the linked list is - * the first step of the trigger-program. - * - * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or - * "SELECT" statement. The meanings of the other members is determined by the - * value of "op" as follows: - * - * (op == TK_INSERT) - * orconf -> stores the ON CONFLICT algorithm - * pSelect -> If this is an INSERT INTO ... SELECT ... statement, then - * this stores a pointer to the SELECT statement. Otherwise NULL. - * zTarget -> Dequoted name of the table to insert into. - * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then - * this stores values to be inserted. Otherwise NULL. - * pIdList -> If this is an INSERT INTO ... () VALUES ... - * statement, then this stores the column-names to be - * inserted into. - * - * (op == TK_DELETE) - * zTarget -> Dequoted name of the table to delete from. - * pWhere -> The WHERE clause of the DELETE statement if one is specified. - * Otherwise NULL. - * - * (op == TK_UPDATE) - * zTarget -> Dequoted name of the table to update. - * pWhere -> The WHERE clause of the UPDATE statement if one is specified. - * Otherwise NULL. - * pExprList -> A list of the columns to update and the expressions to update - * them to. See sqlite3Update() documentation of "pChanges" - * argument. - * - */ +** An instance of struct TriggerStep is used to store a single SQL statement +** that is a part of a trigger-program. +** +** Instances of struct TriggerStep are stored in a singly linked list (linked +** using the "pNext" member) referenced by the "step_list" member of the +** associated struct Trigger instance. The first element of the linked list is +** the first step of the trigger-program. +** +** The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or +** "SELECT" statement. The meanings of the other members is determined by the +** value of "op" as follows: +** +** (op == TK_INSERT) +** orconf -> stores the ON CONFLICT algorithm +** pSelect -> The content to be inserted - either a SELECT statement or +** a VALUES clause. +** zTarget -> Dequoted name of the table to insert into. +** pIdList -> If this is an INSERT INTO ... () VALUES ... +** statement, then this stores the column-names to be +** inserted into. +** pUpsert -> The ON CONFLICT clauses for an Upsert +** +** (op == TK_DELETE) +** zTarget -> Dequoted name of the table to delete from. +** pWhere -> The WHERE clause of the DELETE statement if one is specified. +** Otherwise NULL. +** +** (op == TK_UPDATE) +** zTarget -> Dequoted name of the table to update. +** pWhere -> The WHERE clause of the UPDATE statement if one is specified. +** Otherwise NULL. +** pExprList -> A list of the columns to update and the expressions to update +** them to. See sqlite3Update() documentation of "pChanges" +** argument. +** +** (op == TK_SELECT) +** pSelect -> The SELECT statement +** +** (op == TK_RETURNING) +** pExprList -> The list of expressions that follow the RETURNING keyword. +** +*/ struct TriggerStep { u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT, ** or TK_RETURNING */ @@ -4141,7 +4208,7 @@ struct Window { Window **ppThis; /* Pointer to this object in Select.pWin list */ Window *pNextWin; /* Next window function belonging to this SELECT */ Expr *pFilter; /* The FILTER expression */ - FuncDef *pFunc; /* The function */ + FuncDef *pWFunc; /* The function */ int iEphCsr; /* Partition buffer or Peer buffer */ int regAccum; /* Accumulator */ int regResult; /* Interim result */ @@ -4396,18 +4463,53 @@ char *sqlite3VMPrintf(sqlite3*,const char*, va_list); #endif #if defined(SQLITE_DEBUG) + void sqlite3TreeViewLine(TreeView*, const char *zFormat, ...); void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*); void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); + void sqlite3TreeViewBareIdList(TreeView*, const IdList*, const char*); + void sqlite3TreeViewIdList(TreeView*, const IdList*, u8, const char*); + void sqlite3TreeViewColumnList(TreeView*, const Column*, int, u8); void sqlite3TreeViewSrcList(TreeView*, const SrcList*); void sqlite3TreeViewSelect(TreeView*, const Select*, u8); void sqlite3TreeViewWith(TreeView*, const With*, u8); + void sqlite3TreeViewUpsert(TreeView*, const Upsert*, u8); +#if TREETRACE_ENABLED + void sqlite3TreeViewDelete(const With*, const SrcList*, const Expr*, + const ExprList*,const Expr*, const Trigger*); + void sqlite3TreeViewInsert(const With*, const SrcList*, + const IdList*, const Select*, const ExprList*, + int, const Upsert*, const Trigger*); + void sqlite3TreeViewUpdate(const With*, const SrcList*, const ExprList*, + const Expr*, int, const ExprList*, const Expr*, + const Upsert*, const Trigger*); +#endif +#ifndef SQLITE_OMIT_TRIGGER + void sqlite3TreeViewTriggerStep(TreeView*, const TriggerStep*, u8, u8); + void sqlite3TreeViewTrigger(TreeView*, const Trigger*, u8, u8); +#endif #ifndef SQLITE_OMIT_WINDOWFUNC void sqlite3TreeViewWindow(TreeView*, const Window*, u8); void sqlite3TreeViewWinFunc(TreeView*, const Window*, u8); +#endif + void sqlite3ShowExpr(const Expr*); + void sqlite3ShowExprList(const ExprList*); + void sqlite3ShowIdList(const IdList*); + void sqlite3ShowSrcList(const SrcList*); + void sqlite3ShowSelect(const Select*); + void sqlite3ShowWith(const With*); + void sqlite3ShowUpsert(const Upsert*); +#ifndef SQLITE_OMIT_TRIGGER + void sqlite3ShowTriggerStep(const TriggerStep*); + void sqlite3ShowTriggerStepList(const TriggerStep*); + void sqlite3ShowTrigger(const Trigger*); + void sqlite3ShowTriggerList(const Trigger*); +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC + void sqlite3ShowWindow(const Window*); + void sqlite3ShowWinFunc(const Window*); #endif #endif - void sqlite3SetString(char **, sqlite3*, const char*); void sqlite3ErrorMsg(Parse*, const char*, ...); @@ -4556,13 +4658,14 @@ SrcList *sqlite3SrcListEnlarge(Parse*, SrcList*, int, int); SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2); SrcList *sqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*); SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, - Token*, Select*, Expr*, IdList*); + Token*, Select*, OnOrUsing*); void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *); void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*); int sqlite3IndexedByLookup(Parse *, SrcItem *); -void sqlite3SrcListShiftJoinType(SrcList*); +void sqlite3SrcListShiftJoinType(Parse*,SrcList*); void sqlite3SrcListAssignCursors(Parse*, SrcList*); void sqlite3IdListDelete(sqlite3*, IdList*); +void sqlite3ClearOnOrUsing(sqlite3*, OnOrUsing*); void sqlite3SrcListDelete(sqlite3*, SrcList*); Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**); void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*, @@ -4760,7 +4863,8 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); int sqlite3JoinType(Parse*, Token*, Token*, Token*); int sqlite3ColumnIndex(Table *pTab, const char *zCol); -void sqlite3SetJoinExpr(Expr*,int); +void sqlite3SrcItemColumnUsed(SrcItem*,int); +void sqlite3SetJoinExpr(Expr*,int,u32); void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); void sqlite3DeferForeignKey(Parse*, int); #ifndef SQLITE_OMIT_AUTHORIZATION @@ -5106,7 +5210,7 @@ int sqlite3VtabBegin(sqlite3 *, VTable *); FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); #if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ && !defined(SQLITE_OMIT_VIRTUALTABLE) - void sqlite3VtabWriteAll(sqlite3_index_info*); + void sqlite3VtabUsesAllSchemas(sqlite3_index_info*); #endif sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); diff --git a/src/status.c b/src/status.c index f0e307c2d..e7f215844 100644 --- a/src/status.c +++ b/src/status.c @@ -334,8 +334,7 @@ int sqlite3_db_status( db->pnBytesFreed = &nByte; for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){ - sqlite3VdbeClearObject(db, pVdbe); - sqlite3DbFree(db, pVdbe); + sqlite3VdbeDelete(pVdbe); } db->pnBytesFreed = 0; diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 8c184809f..4e25a6501 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -2965,7 +2965,7 @@ static int SQLITE_TCLAPI DbObjCmd( } if( objc==(6+isReadonly) ){ - zDb = Tcl_GetString(objv[2]); + zDb = Tcl_GetString(objv[2+isReadonly]); } zTable = Tcl_GetString(objv[objc-3]); zColumn = Tcl_GetString(objv[objc-2]); diff --git a/src/test1.c b/src/test1.c index 455c4b257..6ccd4512f 100644 --- a/src/test1.c +++ b/src/test1.c @@ -8908,9 +8908,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ (char*)&sqlite3_sync_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_fullsync_count", (char*)&sqlite3_fullsync_count, TCL_LINK_INT); -#if defined(SQLITE_ENABLE_SELECTTRACE) - Tcl_LinkVar(interp, "sqlite3_unsupported_selecttrace", - (char*)&sqlite3SelectTrace, TCL_LINK_INT); +#if defined(SQLITE_ENABLE_TREETRACE) + Tcl_LinkVar(interp, "sqlite3_unsupported_treetrace", + (char*)&sqlite3TreeTrace, TCL_LINK_INT); #endif #if defined(SQLITE_ENABLE_FTS3) && defined(SQLITE_TEST) Tcl_LinkVar(interp, "sqlite_fts3_enable_parentheses", diff --git a/src/test4.c b/src/test4.c index 5da9cc488..2043a3383 100644 --- a/src/test4.c +++ b/src/test4.c @@ -60,6 +60,11 @@ struct Thread { #define N_THREAD 26 static Thread threadset[N_THREAD]; +static void test_barrier(){ + sqlite3_mutex *pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_APP1); + sqlite3_mutex_enter(pMutex); + sqlite3_mutex_leave(pMutex); +} /* ** The main loop for a thread. Threads use busy waiting. @@ -76,16 +81,20 @@ static void *test_thread_main(void *pArg){ p->db = 0; } p->pStmt = 0; + test_barrier(); p->completed = 1; while( p->opnum<=p->completed ) sched_yield(); + test_barrier(); while( p->xOp ){ if( p->zErr && p->zErr!=p->zStaticErr ){ sqlite3_free(p->zErr); p->zErr = 0; } (*p->xOp)(p); + test_barrier(); p->completed++; while( p->opnum<=p->completed ) sched_yield(); + test_barrier(); } if( p->pStmt ){ sqlite3_finalize(p->pStmt); @@ -99,6 +108,7 @@ static void *test_thread_main(void *pArg){ sqlite3_free(p->zErr); p->zErr = 0; } + test_barrier(); p->completed++; #ifndef SQLITE_OMIT_DEPRECATED sqlite3_thread_cleanup(); @@ -166,7 +176,9 @@ static int SQLITE_TCLAPI tcl_thread_create( ** Wait for a thread to reach its idle state. */ static void test_thread_wait(Thread *p){ + test_barrier(); while( p->opnum>p->completed ) sched_yield(); + test_barrier(); } /* @@ -456,6 +468,7 @@ static int SQLITE_TCLAPI tcl_thread_compile( threadset[i].xOp = do_compile; sqlite3_free(threadset[i].zArg); threadset[i].zArg = sqlite3_mprintf("%s", argv[2]); + test_barrier(); threadset[i].opnum++; return TCL_OK; } @@ -507,6 +520,7 @@ static int SQLITE_TCLAPI tcl_thread_step( } test_thread_wait(&threadset[i]); threadset[i].xOp = do_step; + test_barrier(); threadset[i].opnum++; return TCL_OK; } @@ -551,6 +565,7 @@ static int SQLITE_TCLAPI tcl_thread_finalize( threadset[i].xOp = do_finalize; sqlite3_free(threadset[i].zArg); threadset[i].zArg = 0; + test_barrier(); threadset[i].opnum++; return TCL_OK; } diff --git a/src/tokenize.c b/src/tokenize.c index a727078ba..b147cdb40 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -614,6 +614,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){ mxSqlLen -= n; if( mxSqlLen<0 ){ pParse->rc = SQLITE_TOOBIG; + pParse->nErr++; break; } #ifndef SQLITE_OMIT_WINDOWFUNC @@ -710,7 +711,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){ if( pParse->pNewTrigger && !IN_RENAME_OBJECT ){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); } - sqlite3DbFree(db, pParse->pVList); + if( pParse->pVList ) sqlite3DbFreeNN(db, pParse->pVList); db->pParse = pParentParse; assert( nErr==0 || pParse->rc!=SQLITE_OK ); return nErr; diff --git a/src/treeview.c b/src/treeview.c index 86f06e832..89a128dff 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -24,40 +24,44 @@ ** Add a new subitem to the tree. The moreToFollow flag indicates that this ** is not the last item in the tree. */ -static TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){ +static void sqlite3TreeViewPush(TreeView **pp, u8 moreToFollow){ + TreeView *p = *pp; if( p==0 ){ - p = sqlite3_malloc64( sizeof(*p) ); - if( p==0 ) return 0; + *pp = p = sqlite3_malloc64( sizeof(*p) ); + if( p==0 ) return; memset(p, 0, sizeof(*p)); }else{ p->iLevel++; } assert( moreToFollow==0 || moreToFollow==1 ); - if( p->iLevelbLine) ) p->bLine[p->iLevel] = moreToFollow; - return p; + if( p->iLevel<(int)sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow; } /* ** Finished with one layer of the tree */ -static void sqlite3TreeViewPop(TreeView *p){ +static void sqlite3TreeViewPop(TreeView **pp){ + TreeView *p = *pp; if( p==0 ) return; p->iLevel--; - if( p->iLevel<0 ) sqlite3_free(p); + if( p->iLevel<0 ){ + sqlite3_free(p); + *pp = 0; + } } /* ** Generate a single line of output for the tree, with a prefix that contains ** all the appropriate tree lines */ -static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ +void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ va_list ap; int i; StrAccum acc; - char zBuf[500]; + char zBuf[1000]; sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); if( p ){ - for(i=0; iiLevel && ibLine)-1; i++){ + for(i=0; iiLevel && i<(int)sizeof(p->bLine)-1; i++){ sqlite3_str_append(&acc, p->bLine[i] ? "| " : " ", 4); } sqlite3_str_append(&acc, p->bLine[i] ? "|-- " : "'-- ", 4); @@ -78,10 +82,57 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ ** Shorthand for starting a new tree item that consists of a single label */ static void sqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){ - p = sqlite3TreeViewPush(p, moreFollows); + sqlite3TreeViewPush(&p, moreFollows); sqlite3TreeViewLine(p, "%s", zLabel); } +/* +** Show a list of Column objects in tree format. +*/ +void sqlite3TreeViewColumnList( + TreeView *pView, + const Column *aCol, + int nCol, + u8 moreToFollow +){ + int i; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, "COLUMNS"); + for(i=0; inCte>0 ){ - pView = sqlite3TreeViewPush(pView, 1); + sqlite3TreeViewPush(&pView, moreToFollow); for(i=0; inCte; i++){ StrAccum x; char zLine[1000]; @@ -111,6 +162,10 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){ } sqlite3_str_appendf(&x, ")"); } + if( pCte->eM10d!=M10d_Any ){ + sqlite3_str_appendf(&x, " %sMATERIALIZED", + pCte->eM10d==M10d_No ? "NOT " : ""); + } if( pCte->pUse ){ sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse, pCte->pUse->nUse); @@ -118,9 +173,9 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){ sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, inCte-1); sqlite3TreeViewSelect(pView, pCte->pSelect, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } @@ -129,10 +184,12 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){ */ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ int i; + if( pSrc==0 ) return; for(i=0; inSrc; i++){ const SrcItem *pItem = &pSrc->a[i]; StrAccum x; - char zLine[100]; + int n = 0; + char zLine[1000]; sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); x.printfFlags |= SQLITE_PRINTF_INTERNAL; sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem); @@ -140,26 +197,48 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx", pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed); } - if( pItem->fg.jointype & JT_LEFT ){ + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==(JT_LEFT|JT_RIGHT) ){ + sqlite3_str_appendf(&x, " FULL-OUTER-JOIN"); + }else if( pItem->fg.jointype & JT_LEFT ){ sqlite3_str_appendf(&x, " LEFT-JOIN"); + }else if( pItem->fg.jointype & JT_RIGHT ){ + sqlite3_str_appendf(&x, " RIGHT-JOIN"); }else if( pItem->fg.jointype & JT_CROSS ){ sqlite3_str_appendf(&x, " CROSS-JOIN"); } + if( pItem->fg.jointype & JT_LTORJ ){ + sqlite3_str_appendf(&x, " LTORJ"); + } if( pItem->fg.fromDDL ){ sqlite3_str_appendf(&x, " DDL"); } if( pItem->fg.isCte ){ sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse); } + if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ + sqlite3_str_appendf(&x, " ON"); + } sqlite3StrAccumFinish(&x); - sqlite3TreeViewItem(pView, zLine, inSrc-1); + sqlite3TreeViewItem(pView, zLine, inSrc-1); + n = 0; + if( pItem->pSelect ) n++; + if( pItem->fg.isTabFunc ) n++; + if( pItem->fg.isUsing ) n++; + if( pItem->fg.isUsing ){ + sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); + } if( pItem->pSelect ){ - sqlite3TreeViewSelect(pView, pItem->pSelect, 0); + if( pItem->pTab ){ + Table *pTab = pItem->pTab; + sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1); + } + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0); } if( pItem->fg.isTabFunc ){ sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:"); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } @@ -173,11 +252,11 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ sqlite3TreeViewLine(pView, "nil-SELECT"); return; } - pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); if( p->pWith ){ sqlite3TreeViewWith(pView, p->pWith, 1); cnt = 1; - sqlite3TreeViewPush(pView, 1); + sqlite3TreeViewPush(&pView, 1); } do{ if( p->selFlags & SF_WhereBegin ){ @@ -191,7 +270,7 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ (int)p->nSelectRow ); } - if( cnt++ ) sqlite3TreeViewPop(pView); + if( cnt++ ) sqlite3TreeViewPop(&pView); if( p->pPrior ){ n = 1000; }else{ @@ -214,24 +293,24 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ #ifndef SQLITE_OMIT_WINDOWFUNC if( p->pWin ){ Window *pX; - pView = sqlite3TreeViewPush(pView, (n--)>0); + sqlite3TreeViewPush(&pView, (n--)>0); sqlite3TreeViewLine(pView, "window-functions"); for(pX=p->pWin; pX; pX=pX->pNextWin){ sqlite3TreeViewWinFunc(pView, pX, pX->pNextWin!=0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif if( p->pSrc && p->pSrc->nSrc ){ - pView = sqlite3TreeViewPush(pView, (n--)>0); + sqlite3TreeViewPush(&pView, (n--)>0); sqlite3TreeViewLine(pView, "FROM"); sqlite3TreeViewSrcList(pView, p->pSrc); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pWhere ){ sqlite3TreeViewItem(pView, "WHERE", (n--)>0); sqlite3TreeViewExpr(pView, p->pWhere, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pGroupBy ){ sqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY"); @@ -239,7 +318,7 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ if( p->pHaving ){ sqlite3TreeViewItem(pView, "HAVING", (n--)>0); sqlite3TreeViewExpr(pView, p->pHaving, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #ifndef SQLITE_OMIT_WINDOWFUNC if( p->pWinDefn ){ @@ -248,7 +327,7 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ for(pX=p->pWinDefn; pX; pX=pX->pNextWin){ sqlite3TreeViewWindow(pView, pX, pX->pNextWin!=0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif if( p->pOrderBy ){ @@ -260,9 +339,9 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ if( p->pLimit->pRight ){ sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pPrior ){ const char *zOp = "UNION"; @@ -275,7 +354,7 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ } p = p->pPrior; }while( p!=0 ); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #ifndef SQLITE_OMIT_WINDOWFUNC @@ -291,24 +370,24 @@ void sqlite3TreeViewBound( switch( eBound ){ case TK_UNBOUNDED: { sqlite3TreeViewItem(pView, "UNBOUNDED", moreToFollow); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_CURRENT: { sqlite3TreeViewItem(pView, "CURRENT", moreToFollow); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_PRECEDING: { sqlite3TreeViewItem(pView, "PRECEDING", moreToFollow); sqlite3TreeViewExpr(pView, pExpr, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_FOLLOWING: { sqlite3TreeViewItem(pView, "FOLLOWING", moreToFollow); sqlite3TreeViewExpr(pView, pExpr, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } } @@ -321,12 +400,13 @@ void sqlite3TreeViewBound( */ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ int nElement = 0; + if( pWin==0 ) return; if( pWin->pFilter ){ sqlite3TreeViewItem(pView, "FILTER", 1); sqlite3TreeViewExpr(pView, pWin->pFilter, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - pView = sqlite3TreeViewPush(pView, more); + sqlite3TreeViewPush(&pView, more); if( pWin->zName ){ sqlite3TreeViewLine(pView, "OVER %s (%p)", pWin->zName, pWin); }else{ @@ -337,9 +417,9 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ if( pWin->eFrmType ) nElement++; if( pWin->eExclude ) nElement++; if( pWin->zBase ){ - sqlite3TreeViewPush(pView, (--nElement)>0); + sqlite3TreeViewPush(&pView, (--nElement)>0); sqlite3TreeViewLine(pView, "window: %s", pWin->zBase); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( pWin->pPartition ){ sqlite3TreeViewExprList(pView, pWin->pPartition, nElement>0,"PARTITION-BY"); @@ -357,7 +437,7 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ sqlite3TreeViewItem(pView, zBuf, (--nElement)>0); sqlite3TreeViewBound(pView, pWin->eStart, pWin->pStart, 1); sqlite3TreeViewBound(pView, pWin->eEnd, pWin->pEnd, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( pWin->eExclude ){ char zBuf[30]; @@ -372,11 +452,11 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ zExclude = zBuf; break; } - sqlite3TreeViewPush(pView, 0); + sqlite3TreeViewPush(&pView, 0); sqlite3TreeViewLine(pView, "EXCLUDE %s", zExclude); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -385,11 +465,12 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ ** Generate a human-readable explanation for a Window Function object */ void sqlite3TreeViewWinFunc(TreeView *pView, const Window *pWin, u8 more){ - pView = sqlite3TreeViewPush(pView, more); + if( pWin==0 ) return; + sqlite3TreeViewPush(&pView, more); sqlite3TreeViewLine(pView, "WINFUNC %s(%d)", - pWin->pFunc->zName, pWin->pFunc->nArg); + pWin->pWFunc->zName, pWin->pWFunc->nArg); sqlite3TreeViewWindow(pView, pWin, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -400,10 +481,10 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ const char *zBinOp = 0; /* Binary operator */ const char *zUniOp = 0; /* Unary operator */ char zFlgs[200]; - pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); if( pExpr==0 ){ sqlite3TreeViewLine(pView, "nil"); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); return; } if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){ @@ -411,8 +492,11 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0); sqlite3_str_appendf(&x, " fg.af=%x.%c", pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n'); - if( ExprHasProperty(pExpr, EP_FromJoin) ){ - sqlite3_str_appendf(&x, " iRJT=%d", pExpr->w.iRightJoinTable); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + sqlite3_str_appendf(&x, " outer.iJoin=%d", pExpr->w.iJoin); + } + if( ExprHasProperty(pExpr, EP_InnerON) ){ + sqlite3_str_appendf(&x, " inner.iJoin=%d", pExpr->w.iJoin); } if( ExprHasProperty(pExpr, EP_FromDDL) ){ sqlite3_str_appendf(&x, " DDL"); @@ -636,7 +720,17 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ break; } case TK_IN: { - sqlite3TreeViewLine(pView, "IN flags=0x%x", pExpr->flags); + sqlite3_str *pStr = sqlite3_str_new(0); + char *z; + sqlite3_str_appendf(pStr, "IN flags=0x%x", pExpr->flags); + if( pExpr->iTable ) sqlite3_str_appendf(pStr, " iTable=%d",pExpr->iTable); + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + sqlite3_str_appendf(pStr, " subrtn(%d,%d)", + pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); + } + z = sqlite3_str_finish(pStr); + sqlite3TreeViewLine(pView, z); + sqlite3_free(z); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); if( ExprUseXSelect(pExpr) ){ sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); @@ -760,7 +854,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewLine(pView, "%s%s", zUniOp, zFlgs); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } @@ -782,13 +876,25 @@ void sqlite3TreeViewBareExprList( int j = pList->a[i].u.x.iOrderByCol; char *zName = pList->a[i].zEName; int moreToFollow = inExpr - 1; - if( pList->a[i].eEName!=ENAME_NAME ) zName = 0; if( j || zName ){ - sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); moreToFollow = 0; sqlite3TreeViewLine(pView, 0); if( zName ){ - fprintf(stdout, "AS %s ", zName); + switch( pList->a[i].fg.eEName ){ + default: + fprintf(stdout, "AS %s ", zName); + break; + case ENAME_TAB: + fprintf(stdout, "TABLE-ALIAS-NAME(\"%s\") ", zName); + if( pList->a[i].fg.bUsed ) fprintf(stdout, "(used) "); + if( pList->a[i].fg.bUsingTerm ) fprintf(stdout, "(USING-term) "); + if( pList->a[i].fg.bNoExpand ) fprintf(stdout, "(NoExpand) "); + break; + case ENAME_SPAN: + fprintf(stdout, "SPAN(\"%s\") ", zName); + break; + } } if( j ){ fprintf(stdout, "iOrderByCol=%d", j); @@ -798,7 +904,7 @@ void sqlite3TreeViewBareExprList( } sqlite3TreeViewExpr(pView, pList->a[i].pExpr, moreToFollow); if( j || zName ){ - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } } @@ -809,9 +915,376 @@ void sqlite3TreeViewExprList( u8 moreToFollow, const char *zLabel ){ - pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); sqlite3TreeViewBareExprList(pView, pList, zLabel); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } +/* +** Generate a human-readable explanation of an id-list. +*/ +void sqlite3TreeViewBareIdList( + TreeView *pView, + const IdList *pList, + const char *zLabel +){ + if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; + if( pList==0 ){ + sqlite3TreeViewLine(pView, "%s (empty)", zLabel); + }else{ + int i; + sqlite3TreeViewLine(pView, "%s", zLabel); + for(i=0; inId; i++){ + char *zName = pList->a[i].zName; + int moreToFollow = inId - 1; + if( zName==0 ) zName = "(null)"; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, 0); + if( pList->eU4==EU4_NONE ){ + fprintf(stdout, "%s\n", zName); + }else if( pList->eU4==EU4_IDX ){ + fprintf(stdout, "%s (%d)\n", zName, pList->a[i].u4.idx); + }else{ + assert( pList->eU4==EU4_EXPR ); + if( pList->a[i].u4.pExpr==0 ){ + fprintf(stdout, "%s (pExpr=NULL)\n", zName); + }else{ + fprintf(stdout, "%s\n", zName); + sqlite3TreeViewPush(&pView, inId-1); + sqlite3TreeViewExpr(pView, pList->a[i].u4.pExpr, 0); + sqlite3TreeViewPop(&pView); + } + } + sqlite3TreeViewPop(&pView); + } + } +} +void sqlite3TreeViewIdList( + TreeView *pView, + const IdList *pList, + u8 moreToFollow, + const char *zLabel +){ + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewBareIdList(pView, pList, zLabel); + sqlite3TreeViewPop(&pView); +} + +/* +** Generate a human-readable explanation of a list of Upsert objects +*/ +void sqlite3TreeViewUpsert( + TreeView *pView, + const Upsert *pUpsert, + u8 moreToFollow +){ + if( pUpsert==0 ) return; + sqlite3TreeViewPush(&pView, moreToFollow); + while( pUpsert ){ + int n; + sqlite3TreeViewPush(&pView, pUpsert->pNextUpsert!=0 || moreToFollow); + sqlite3TreeViewLine(pView, "ON CONFLICT DO %s", + pUpsert->isDoUpdate ? "UPDATE" : "NOTHING"); + n = (pUpsert->pUpsertSet!=0) + (pUpsert->pUpsertWhere!=0); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertTarget, (n--)>0, "TARGET"); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertSet, (n--)>0, "SET"); + if( pUpsert->pUpsertWhere ){ + sqlite3TreeViewItem(pView, "WHERE", (n--)>0); + sqlite3TreeViewExpr(pView, pUpsert->pUpsertWhere, 0); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); + pUpsert = pUpsert->pNextUpsert; + } + sqlite3TreeViewPop(&pView); +} + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an DELETE statement. +*/ +void sqlite3TreeViewDelete( + const With *pWith, + const SrcList *pTabList, + const Expr *pWhere, + const ExprList *pOrderBy, + const Expr *pLimit, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, "DELETE"); + if( pWith ) n++; + if( pTabList ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an INSERT statement. +*/ +void sqlite3TreeViewInsert( + const With *pWith, + const SrcList *pTabList, + const IdList *pColumnList, + const Select *pSelect, + const ExprList *pExprList, + int onError, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + TreeView *pView = 0; + int n = 0; + const char *zLabel = "INSERT"; + switch( onError ){ + case OE_Replace: zLabel = "REPLACE"; break; + case OE_Ignore: zLabel = "INSERT OR IGNORE"; break; + case OE_Rollback: zLabel = "INSERT OR ROLLBACK"; break; + case OE_Abort: zLabel = "INSERT OR ABORT"; break; + case OE_Fail: zLabel = "INSERT OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pColumnList ) n++; + if( pSelect ) n++; + if( pExprList ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "INTO"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pColumnList ){ + sqlite3TreeViewIdList(pView, pColumnList, (--n)>0, "COLUMNS"); + } + if( pSelect ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "DATA-SOURCE"); + sqlite3TreeViewSelect(pView, pSelect, 0); + sqlite3TreeViewPop(&pView); + } + if( pExprList ){ + sqlite3TreeViewExprList(pView, pExprList, (--n)>0, "VALUES"); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an UPDATE statement. +*/ +void sqlite3TreeViewUpdate( + const With *pWith, + const SrcList *pTabList, + const ExprList *pChanges, + const Expr *pWhere, + int onError, + const ExprList *pOrderBy, + const Expr *pLimit, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + const char *zLabel = "UPDATE"; + switch( onError ){ + case OE_Replace: zLabel = "UPDATE OR REPLACE"; break; + case OE_Ignore: zLabel = "UPDATE OR IGNORE"; break; + case OE_Rollback: zLabel = "UPDATE OR ROLLBACK"; break; + case OE_Abort: zLabel = "UPDATE OR ABORT"; break; + case OE_Fail: zLabel = "UPDATE OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pChanges ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pChanges ){ + sqlite3TreeViewExprList(pView, pChanges, (--n)>0, "SET"); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#ifndef SQLITE_OMIT_TRIGGER +/* +** Show a human-readable graph of a TriggerStep +*/ +void sqlite3TreeViewTriggerStep( + TreeView *pView, + const TriggerStep *pStep, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pStep==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pStep->pNext!=0)); + do{ + if( cnt++ && pStep->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "%s", pStep->zSpan ? pStep->zSpan : "RETURNING"); + }while( showFullList && (pStep = pStep->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} + +/* +** Show a human-readable graph of a Trigger +*/ +void sqlite3TreeViewTrigger( + TreeView *pView, + const Trigger *pTrigger, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pTrigger==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pTrigger->pNext!=0)); + do{ + if( cnt++ && pTrigger->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "TRIGGER %s", pTrigger->zName); + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewTriggerStep(pView, pTrigger->step_list, 0, 1); + sqlite3TreeViewPop(&pView); + }while( showFullList && (pTrigger = pTrigger->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} +#endif /* SQLITE_OMIT_TRIGGER */ + + +/* +** These simplified versions of the tree-view routines omit unnecessary +** parameters. These variants are intended to be used from a symbolic +** debugger, such as "gdb", during interactive debugging sessions. +** +** This routines are given external linkage so that they will always be +** accessible to the debugging, and to avoid warnings about unused +** functions. But these routines only exist in debugging builds, so they +** do not contaminate the interface. +*/ +void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } +void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} +void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } +void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } +void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); } +void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); } +void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); } +#ifndef SQLITE_OMIT_TRIGGER +void sqlite3ShowTriggerStep(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,0); +} +void sqlite3ShowTriggerStepList(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,1); +} +void sqlite3ShowTrigger(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,0); } +void sqlite3ShowTriggerList(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,1);} +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +void sqlite3ShowWindow(const Window *p){ sqlite3TreeViewWindow(0,p,0); } +void sqlite3ShowWinFunc(const Window *p){ sqlite3TreeViewWinFunc(0,p,0); } +#endif + #endif /* SQLITE_DEBUG */ diff --git a/src/trigger.c b/src/trigger.c index 5df6b0c0b..3b7d0d9e8 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -52,9 +52,7 @@ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){ Trigger *pList; /* List of triggers to return */ HashElem *p; /* Loop variable for TEMP triggers */ - if( pParse->disableTriggers ){ - return 0; - } + assert( pParse->disableTriggers==0 ); pTmpSchema = pParse->db->aDb[1].pSchema; p = sqliteHashFirst(&pTmpSchema->trigHash); pList = pTab->pTrigger; @@ -67,11 +65,10 @@ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){ ){ pTrig->pNext = pList; pList = pTrig; - }else if( pTrig->op==TK_RETURNING + }else if( pTrig->op==TK_RETURNING ){ #ifndef SQLITE_OMIT_VIRTUALTABLE - && pParse->db->pVtabCtx==0 + assert( pParse->db->pVtabCtx==0 ); #endif - ){ assert( pParse->bReturning ); assert( &(pParse->u1.pReturning->retTrig) == pTrig ); pTrig->table = pTab->zName; @@ -517,7 +514,7 @@ TriggerStep *sqlite3TriggerInsertStep( TriggerStep *sqlite3TriggerUpdateStep( Parse *pParse, /* Parser */ Token *pTableName, /* Name of the table to be updated */ - SrcList *pFrom, + SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */ @@ -730,13 +727,22 @@ static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){ return 0; } +/* +** Return true if any TEMP triggers exist +*/ +static int tempTriggersExist(sqlite3 *db){ + if( NEVER(db->aDb[1].pSchema==0) ) return 0; + if( sqliteHashFirst(&db->aDb[1].pSchema->trigHash)==0 ) return 0; + return 1; +} + /* ** Return a list of all triggers on table pTab if there exists at least ** one trigger that must be fired when an operation of type 'op' is ** performed on the table, and, if that operation is an UPDATE, if at ** least one of the columns in pChanges is being modified. */ -Trigger *sqlite3TriggersExist( +static SQLITE_NOINLINE Trigger *triggersReallyExist( Parse *pParse, /* Parse context */ Table *pTab, /* The table the contains the triggers */ int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ @@ -799,6 +805,22 @@ Trigger *sqlite3TriggersExist( } return (mask ? pList : 0); } +Trigger *sqlite3TriggersExist( + Parse *pParse, /* Parse context */ + Table *pTab, /* The table the contains the triggers */ + int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ + ExprList *pChanges, /* Columns that change in an UPDATE statement */ + int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ +){ + assert( pTab!=0 ); + if( (pTab->pTrigger==0 && !tempTriggersExist(pParse->db)) + || pParse->disableTriggers + ){ + if( pMask ) *pMask = 0; + return 0; + } + return triggersReallyExist(pParse,pTab,op,pChanges,pMask); +} /* ** Convert the pStep->zTarget string into a SrcList and return a pointer @@ -828,6 +850,14 @@ SrcList *sqlite3TriggerStepSrc( } if( pStep->pFrom ){ SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0); + if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup); } }else{ @@ -883,7 +913,7 @@ static ExprList *sqlite3ExpandReturning( if( !db->mallocFailed ){ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zCnName); - pItem->eEName = ENAME_NAME; + pItem->fg.eEName = ENAME_NAME; } } }else{ @@ -892,7 +922,7 @@ static ExprList *sqlite3ExpandReturning( if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName); - pItem->eEName = pList->a[i].eEName; + pItem->fg.eEName = pList->a[i].fg.eEName; } } } diff --git a/src/update.c b/src/update.c index a43d0eac5..3fd78a0b5 100644 --- a/src/update.c +++ b/src/update.c @@ -376,6 +376,14 @@ void sqlite3Update( # define isView 0 #endif +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Update() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewUpdate(pParse->pWith, pTabList, pChanges, pWhere, + onError, pOrderBy, pLimit, pUpsert, pTrigger); + } +#endif + /* If there was a FROM clause, set nChangeFrom to the number of expressions ** in the change-list. Otherwise, set it to 0. There cannot be a FROM ** clause if this function is being called to generate code for part of @@ -1020,7 +1028,7 @@ void sqlite3Update( }else{ sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid); } - VdbeCoverageNeverTaken(v); + VdbeCoverage(v); } /* Do FK constraint checks. */ diff --git a/src/vacuum.c b/src/vacuum.c index bcab1de14..9899b63cf 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -368,6 +368,7 @@ SQLITE_NOINLINE int sqlite3RunVacuum( assert( rc==SQLITE_OK ); if( pOut==0 ){ + nRes = sqlite3BtreeGetRequestedReserve(pTemp); rc = sqlite3BtreeSetPageSize(pMain, sqlite3BtreeGetPageSize(pTemp), nRes,1); } diff --git a/src/vdbe.c b/src/vdbe.c index 0e937078f..ef9a23f64 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -266,12 +266,12 @@ static VdbeCursor *allocateCursor( int nByte; VdbeCursor *pCx = 0; nByte = - ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + + ROUND8P(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0); assert( iCur>=0 && iCurnCursor ); if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/ - sqlite3VdbeFreeCursor(p, p->apCsr[iCur]); + sqlite3VdbeFreeCursorNN(p, p->apCsr[iCur]); p->apCsr[iCur] = 0; } @@ -301,7 +301,7 @@ static VdbeCursor *allocateCursor( pCx->aOffset = &pCx->aType[nField]; if( eCurType==CURTYPE_BTREE ){ pCx->uc.pCursor = (BtCursor*) - &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; + &pMem->z[ROUND8P(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; sqlite3BtreeCursorZero(pCx->uc.pCursor); } return pCx; @@ -740,7 +740,7 @@ int sqlite3VdbeExec( #endif /*** INSERT STACK UNION HERE ***/ - assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ + assert( p->eVdbeState==VDBE_RUN_STATE ); /* sqlite3_step() verifies this */ sqlite3VdbeEnter(p); #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( db->xProgress ){ @@ -983,26 +983,39 @@ case OP_Gosub: { /* jump */ pIn1->flags = MEM_Int; pIn1->u.i = (int)(pOp-aOp); REGISTER_TRACE(pOp->p1, pIn1); - - /* Most jump operations do a goto to this spot in order to update - ** the pOp pointer. */ -jump_to_p2: - assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */ - assert( pOp->p2nOp ); /* Jumps must be in range */ - pOp = &aOp[pOp->p2 - 1]; - break; + goto jump_to_p2_and_check_for_interrupt; } -/* Opcode: Return P1 * * * * +/* Opcode: Return P1 P2 P3 * * +** +** Jump to the address stored in register P1. If P1 is a return address +** register, then this accomplishes a return from a subroutine. ** -** Jump to the next instruction after the address in register P1. After -** the jump, register P1 becomes undefined. +** If P3 is 1, then the jump is only taken if register P1 holds an integer +** values, otherwise execution falls through to the next opcode, and the +** OP_Return becomes a no-op. If P3 is 0, then register P1 must hold an +** integer or else an assert() is raised. P3 should be set to 1 when +** this opcode is used in combination with OP_BeginSubrtn, and set to 0 +** otherwise. +** +** The value in register P1 is unchanged by this opcode. +** +** P2 is not used by the byte-code engine. However, if P2 is positive +** and also less than the current address, then the "EXPLAIN" output +** formatter in the CLI will indent all opcodes from the P2 opcode up +** to be not including the current Return. P2 should be the first opcode +** in the subroutine from which this opcode is returning. Thus the P2 +** value is a byte-code indentation hint. See tag-20220407a in +** wherecode.c and shell.c. */ case OP_Return: { /* in1 */ pIn1 = &aMem[pOp->p1]; - assert( pIn1->flags==MEM_Int ); - pOp = &aOp[pIn1->u.i]; - pIn1->flags = MEM_Undefined; + if( pIn1->flags & MEM_Int ){ + if( pOp->p3 ){ VdbeBranchTaken(1, 2); } + pOp = &aOp[pIn1->u.i]; + }else if( ALWAYS(pOp->p3) ){ + VdbeBranchTaken(0, 2); + } break; } @@ -1025,7 +1038,14 @@ case OP_InitCoroutine: { /* jump */ assert( !VdbeMemDynamic(pOut) ); pOut->u.i = pOp->p3 - 1; pOut->flags = MEM_Int; - if( pOp->p2 ) goto jump_to_p2; + if( pOp->p2==0 ) break; + + /* Most jump operations do a goto to this spot in order to update + ** the pOp pointer. */ +jump_to_p2: + assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */ + assert( pOp->p2nOp ); /* Jumps must be in range */ + pOp = &aOp[pOp->p2 - 1]; break; } @@ -1127,11 +1147,10 @@ case OP_Halt: { VdbeFrame *pFrame; int pcx; - pcx = (int)(pOp - aOp); #ifdef SQLITE_DEBUG if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); } #endif - if( pOp->p1==SQLITE_OK && p->pFrame ){ + if( p->pFrame && pOp->p1==SQLITE_OK ){ /* Halt the sub-program. Return control to the parent frame. */ pFrame = p->pFrame; p->pFrame = pFrame->pParent; @@ -1153,7 +1172,6 @@ case OP_Halt: { } p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; - p->pc = pcx; assert( pOp->p5<=4 ); if( p->rc ){ if( pOp->p5 ){ @@ -1170,6 +1188,7 @@ case OP_Halt: { }else{ sqlite3VdbeError(p, "%s", pOp->p4.z); } + pcx = (int)(pOp - aOp); sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg); } rc = sqlite3VdbeHalt(p); @@ -1295,6 +1314,28 @@ case OP_String: { /* out2 */ break; } +/* Opcode: BeginSubrtn * P2 * * * +** Synopsis: r[P2]=NULL +** +** Mark the beginning of a subroutine that can be entered in-line +** or that can be called using OP_Gosub. The subroutine should +** be terminated by an OP_Return instruction that has a P1 operand that +** is the same as the P2 operand to this opcode and that has P3 set to 1. +** If the subroutine is entered in-line, then the OP_Return will simply +** fall through. But if the subroutine is entered using OP_Gosub, then +** the OP_Return will jump back to the first instruction after the OP_Gosub. +** +** This routine works by loading a NULL into the P2 register. When the +** return address register contains a NULL, the OP_Return instruction is +** a no-op that simply falls through to the next instruction (assuming that +** the OP_Return opcode has a P3 value of 1). Thus if the subroutine is +** entered in-line, then the OP_Return will cause in-line execution to +** continue. But if the subroutine is entered via OP_Gosub, then the +** OP_Return will cause a return to the address following the OP_Gosub. +** +** This opcode is identical to OP_Null. It has a different name +** only to make the byte code easier to read and verify. +*/ /* Opcode: Null P1 P2 P3 * * ** Synopsis: r[P2..P3]=NULL ** @@ -1307,6 +1348,7 @@ case OP_String: { /* out2 */ ** NULL values will not compare equal even if SQLITE_NULLEQ is set on ** OP_Ne or OP_Eq. */ +case OP_BeginSubrtn: case OP_Null: { /* out2 */ int cnt; u16 nullFlag; @@ -1437,11 +1479,16 @@ case OP_Move: { break; } -/* Opcode: Copy P1 P2 P3 * * +/* Opcode: Copy P1 P2 P3 * P5 ** Synopsis: r[P2@P3+1]=r[P1@P3+1] ** ** Make a copy of registers P1..P1+P3 into registers P2..P2+P3. ** +** If the 0x0002 bit of P5 is set then also clear the MEM_Subtype flag in the +** destination. The 0x0001 bit of P5 indicates that this Copy opcode cannot +** be merged. The 0x0001 bit is used by the query planner and does not +** come into play during query execution. +** ** This instruction makes a deep copy of the value. A duplicate ** is made of any string or blob constant. See also OP_SCopy. */ @@ -1456,6 +1503,9 @@ case OP_Copy: { memAboutToChange(p, pOut); sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); Deephemeralize(pOut); + if( (pOut->flags & MEM_Subtype)!=0 && (pOp->p5 & 0x0002)!=0 ){ + pOut->flags &= ~MEM_Subtype; + } #ifdef SQLITE_DEBUG pOut->pScopyFrom = 0; #endif @@ -1536,45 +1586,32 @@ case OP_FkCheck: { ** the result row. */ case OP_ResultRow: { - Mem *pMem; - int i; assert( p->nResColumn==pOp->p2 ); assert( pOp->p1>0 || CORRUPT_DB ); assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); - /* Invalidate all ephemeral cursor row caches */ p->cacheCtr = (p->cacheCtr + 2)|1; - - /* Make sure the results of the current row are \000 terminated - ** and have an assigned type. The results are de-ephemeralized as - ** a side effect. - */ - 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]); - REGISTER_TRACE(pOp->p1+i, &pMem[i]); + p->pResultSet = &aMem[pOp->p1]; #ifdef SQLITE_DEBUG - /* The registers in the result will not be used again when the - ** prepared statement restarts. This is because sqlite3_column() - ** APIs might have caused type conversions of made other changes to - ** the register values. Therefore, we can go ahead and break any - ** OP_SCopy dependencies. */ - pMem[i].pScopyFrom = 0; -#endif + { + Mem *pMem = p->pResultSet; + int i; + for(i=0; ip2; i++){ + assert( memIsValid(&pMem[i]) ); + REGISTER_TRACE(pOp->p1+i, &pMem[i]); + /* The registers in the result will not be used again when the + ** prepared statement restarts. This is because sqlite3_column() + ** APIs might have caused type conversions of made other changes to + ** the register values. Therefore, we can go ahead and break any + ** OP_SCopy dependencies. */ + pMem[i].pScopyFrom = 0; + } } +#endif if( db->mallocFailed ) goto no_mem; - if( db->mTrace & SQLITE_TRACE_ROW ){ db->trace.xV2(SQLITE_TRACE_ROW, db->pTraceArg, p, 0); } - - - /* Return SQLITE_ROW - */ p->pc = (int)(pOp - aOp) + 1; rc = SQLITE_ROW; goto vdbe_return; @@ -1629,7 +1666,7 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } - if( sqlite3VdbeMemGrow(pOut, (int)nByte+3, pOut==pIn2) ){ + if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ goto no_mem; } MemSetTypeFlag(pOut, MEM_Str); @@ -1641,9 +1678,9 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n); assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); pIn1->flags = flags1; + if( encoding>SQLITE_UTF8 ) nByte &= ~1; pOut->z[nByte]=0; pOut->z[nByte+1] = 0; - pOut->z[nByte+2] = 0; pOut->flags |= MEM_Term; pOut->n = (int)nByte; pOut->enc = encoding; @@ -2088,23 +2125,23 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ assert( (pOp->p5 & SQLITE_AFF_MASK)!=SQLITE_AFF_TEXT || CORRUPT_DB ); /* Common case of comparison of two integers */ if( pIn3->u.i > pIn1->u.i ){ - iCompare = +1; if( sqlite3aGTb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } + iCompare = +1; }else if( pIn3->u.i < pIn1->u.i ){ - iCompare = -1; if( sqlite3aLTb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } + iCompare = -1; }else{ - iCompare = 0; if( sqlite3aEQb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } + iCompare = 0; } VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3); break; @@ -2131,11 +2168,11 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** then the result is always NULL. ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. */ - iCompare = 1; /* Operands are not equal */ VdbeBranchTaken(2,3); if( pOp->p5 & SQLITE_JUMPIFNULL ){ goto jump_to_p2; } + iCompare = 1; /* Operands are not equal */ break; } }else{ @@ -2241,9 +2278,8 @@ case OP_ElseEq: { /* same as TK_ESCAPE, jump */ ** Set the permutation used by the OP_Compare operator in the next ** instruction. The permutation is stored in the P4 operand. ** -** The permutation is only valid until the next OP_Compare that has -** the OPFLAG_PERMUTE bit set in P5. Typically the OP_Permutation should -** occur immediately prior to the OP_Compare. +** The permutation is only valid for the next opcode which must be +** an OP_Compare that has the OPFLAG_PERMUTE bit set in P5. ** ** The first integer in the P4 integer array is the length of the array ** and does not become part of the permutation. @@ -2275,6 +2311,8 @@ case OP_Permutation: { ** The comparison is a sort comparison, so NULLs compare equal, ** NULLs are less than numbers, numbers are less than strings, ** and strings are less than blobs. +** +** This opcode must be immediately followed by an OP_Jump opcode. */ case OP_Compare: { int n; @@ -2333,6 +2371,7 @@ case OP_Compare: { break; } } + assert( pOp[1].opcode==OP_Jump ); break; } @@ -2341,8 +2380,11 @@ case OP_Compare: { ** Jump to the instruction at address P1, P2, or P3 depending on whether ** in the most recent OP_Compare instruction the P1 vector was less than ** equal to, or greater than the P2 vector, respectively. +** +** This opcode must immediately follow an OP_Compare opcode. */ case OP_Jump: { /* jump */ + assert( pOp>aOp && pOp[-1].opcode==OP_Compare ); if( iCompare<0 ){ VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1]; }else if( iCompare==0 ){ @@ -2647,7 +2689,7 @@ case OP_Offset: { /* out3 */ #endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ /* Opcode: Column P1 P2 P3 P4 P5 -** Synopsis: r[P3]=PX +** Synopsis: r[P3]=PX cursor P1 column P2 ** ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional @@ -2669,7 +2711,7 @@ case OP_Offset: { /* out3 */ case OP_Column: { u32 p2; /* column number to retrieve */ VdbeCursor *pC; /* The VDBE cursor */ - BtCursor *pCrsr; /* The BTree cursor */ + BtCursor *pCrsr; /* The B-Tree cursor corresponding to pC */ u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */ int len; /* The length of the serialized data for the column */ int i; /* Loop counter */ @@ -2683,21 +2725,14 @@ case OP_Column: { Mem *pReg; /* PseudoTable input register */ assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pC = p->apCsr[pOp->p1]; - assert( pC!=0 ); p2 = (u32)pOp->p2; - /* If the cursor cache is stale (meaning it is not currently point at - ** the correct row) then bring it up-to-date by doing the necessary - ** B-Tree seek. */ - rc = sqlite3VdbeCursorMoveto(&pC, &p2); - if( rc ) goto abort_due_to_error; - - assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - pDest = &aMem[pOp->p3]; - memAboutToChange(p, pDest); +op_column_restart: assert( pC!=0 ); - assert( p2<(u32)pC->nField ); + assert( p2<(u32)pC->nField + || (pC->eCurType==CURTYPE_PSEUDO && pC->seekResult==0) ); aOffset = pC->aOffset; assert( aOffset==pC->aType+pC->nField ); assert( pC->eCurType!=CURTYPE_VTAB ); @@ -2706,21 +2741,37 @@ case OP_Column: { if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/ if( pC->nullRow ){ - if( pC->eCurType==CURTYPE_PSEUDO ){ + if( pC->eCurType==CURTYPE_PSEUDO && pC->seekResult>0 ){ /* For the special case of as pseudo-cursor, the seekResult field ** identifies the register that holds the record */ - assert( pC->seekResult>0 ); pReg = &aMem[pC->seekResult]; assert( pReg->flags & MEM_Blob ); assert( memIsValid(pReg) ); pC->payloadSize = pC->szRow = pReg->n; pC->aRow = (u8*)pReg->z; }else{ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); sqlite3VdbeMemSetNull(pDest); goto op_column_out; } }else{ pCrsr = pC->uc.pCursor; + if( pC->deferredMoveto ){ + u32 iMap; + assert( !pC->isEphemeral ); + if( pC->ub.aAltMap && (iMap = pC->ub.aAltMap[1+p2])>0 ){ + pC = pC->pAltCursor; + p2 = iMap - 1; + goto op_column_restart; + } + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + }else if( sqlite3BtreeCursorHasMoved(pCrsr) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; + } assert( pC->eCurType==CURTYPE_BTREE ); assert( pCrsr ); assert( sqlite3BtreeCursorIsValid(pCrsr) ); @@ -2728,15 +2779,15 @@ case OP_Column: { pC->aRow = sqlite3BtreePayloadFetch(pCrsr, &pC->szRow); assert( pC->szRow<=pC->payloadSize ); assert( pC->szRow<=65536 ); /* Maximum page size is 64KiB */ - if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - goto too_big; - } } pC->cacheStatus = p->cacheCtr; - pC->iHdrOffset = getVarint32(pC->aRow, aOffset[0]); + if( (aOffset[0] = pC->aRow[0])<0x80 ){ + pC->iHdrOffset = 1; + }else{ + pC->iHdrOffset = sqlite3GetVarint32(pC->aRow, aOffset); + } pC->nHdrParsed = 0; - if( pC->szRowaRow does not have to hold the entire row, but it does at least ** need to cover the header of the record. If pC->aRow does not contain @@ -2776,6 +2827,10 @@ case OP_Column: { testcase( aOffset[0]==0 ); goto op_column_read_header; } + }else if( sqlite3BtreeCursorHasMoved(pC->uc.pCursor) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; } /* Make sure at least the first p2+1 entries of the header have been @@ -2844,6 +2899,8 @@ case OP_Column: { ** columns. So the result will be either the default value or a NULL. */ if( pC->nHdrParsed<=p2 ){ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); if( pOp->p4type==P4_MEM ){ sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static); }else{ @@ -2861,6 +2918,8 @@ case OP_Column: { */ assert( p2nHdrParsed ); assert( rc==SQLITE_OK ); + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); assert( sqlite3VdbeCheckMemInvariants(pDest) ); if( VdbeMemDynamic(pDest) ){ sqlite3VdbeMemSetNull(pDest); @@ -2881,6 +2940,7 @@ case OP_Column: { pDest->n = len = (t-12)/2; pDest->enc = encoding; if( pDest->szMalloc < len+2 ){ + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; pDest->flags = MEM_Null; if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem; }else{ @@ -2913,6 +2973,7 @@ case OP_Column: { */ sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); }else{ + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest); if( rc!=SQLITE_OK ) goto abort_due_to_error; sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); @@ -3125,7 +3186,6 @@ case OP_MakeRecord: { Mem *pLast; /* Last field of the record */ int nField; /* Number of fields in the record */ char *zAffinity; /* The affinity string for the record */ - int file_format; /* File format to use for encoding */ u32 len; /* Length of a field */ u8 *zHdr; /* Where to write next byte of the header */ u8 *zPayload; /* Where to write next byte of the payload */ @@ -3154,7 +3214,6 @@ case OP_MakeRecord: { pData0 = &aMem[nField]; nField = pOp->p2; pLast = &pData0[nField-1]; - file_format = p->minWriteFileFormat; /* Identify the output register */ assert( pOp->p3p1 || pOp->p3>=pOp->p1+pOp->p2 ); @@ -3256,7 +3315,7 @@ case OP_MakeRecord: { testcase( uu==2147483647 ); testcase( uu==2147483648LL ); testcase( uu==140737488355327LL ); testcase( uu==140737488355328LL ); if( uu<=127 ){ - if( (i&1)==i && file_format>=4 ){ + if( (i&1)==i && p->minWriteFileFormat>=4 ){ pRec->uTemp = 8+(u32)uu; }else{ nData++; @@ -3361,18 +3420,60 @@ case OP_MakeRecord: { zPayload = zHdr + nHdr; /* Write the record */ - zHdr += putVarint32(zHdr, nHdr); + if( nHdr<0x80 ){ + *(zHdr++) = nHdr; + }else{ + zHdr += sqlite3PutVarint(zHdr,nHdr); + } assert( pData0<=pLast ); pRec = pData0; - do{ + while( 1 /*exit-by-break*/ ){ serial_type = pRec->uTemp; /* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more - ** additional varints, one per column. */ - zHdr += putVarint32(zHdr, serial_type); /* serial type */ - /* EVIDENCE-OF: R-64536-51728 The values for each column in the record + ** additional varints, one per column. + ** EVIDENCE-OF: R-64536-51728 The values for each column in the record ** immediately follow the header. */ - zPayload += sqlite3VdbeSerialPut(zPayload, pRec, serial_type); /* content */ - }while( (++pRec)<=pLast ); + if( serial_type<=7 ){ + *(zHdr++) = serial_type; + if( serial_type==0 ){ + /* NULL value. No change in zPayload */ + }else{ + u64 v; + u32 i; + if( serial_type==7 ){ + assert( sizeof(v)==sizeof(pRec->u.r) ); + memcpy(&v, &pRec->u.r, sizeof(v)); + swapMixedEndianFloat(v); + }else{ + v = pRec->u.i; + } + len = i = sqlite3SmallTypeSizes[serial_type]; + assert( i>0 ); + while( 1 /*exit-by-break*/ ){ + zPayload[--i] = (u8)(v&0xFF); + if( i==0 ) break; + v >>= 8; + } + zPayload += len; + } + }else if( serial_type<0x80 ){ + *(zHdr++) = serial_type; + if( serial_type>=14 && pRec->n>0 ){ + assert( pRec->z!=0 ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + }else{ + zHdr += sqlite3PutVarint(zHdr, serial_type); + if( pRec->n ){ + assert( pRec->z!=0 ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + } + if( pRec==pLast ) break; + pRec++; + } assert( nHdr==(int)(zHdr - (u8*)pOut->z) ); assert( nByte==(int)(zPayload - (u8*)pOut->z) ); @@ -3591,7 +3692,10 @@ case OP_Savepoint: { } } if( rc ) goto abort_due_to_error; - + if( p->eVdbeState==VDBE_HALT_STATE ){ + rc = SQLITE_DONE; + goto vdbe_return; + } break; } @@ -3695,6 +3799,7 @@ case OP_AutoCommit: { */ case OP_Transaction: { Btree *pBt; + Db *pDb; int iMeta = 0; assert( p->bIsReader ); @@ -3714,7 +3819,8 @@ case OP_Transaction: { } goto abort_due_to_error; } - pBt = db->aDb[pOp->p1].pBt; + pDb = &db->aDb[pOp->p1]; + pBt = pDb->pBt; if( pBt ){ rc = sqlite3BtreeBeginTrans(pBt, pOp->p2, &iMeta); @@ -3755,8 +3861,7 @@ case OP_Transaction: { assert( pOp->p5==0 || pOp->p4type==P4_INT32 ); if( rc==SQLITE_OK && pOp->p5 - && (iMeta!=pOp->p3 - || db->aDb[pOp->p1].pSchema->iGeneration!=pOp->p4.i) + && (iMeta!=pOp->p3 || pDb->pSchema->iGeneration!=pOp->p4.i) ){ /* ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema @@ -3783,6 +3888,11 @@ case OP_Transaction: { } p->expired = 1; rc = SQLITE_SCHEMA; + + /* Set changeCntOn to 0 to prevent the value returned by sqlite3_changes() + ** from being modified in sqlite3VdbeHalt(). If this statement is + ** reprepared, changeCntOn will be set again. */ + p->changeCntOn = 0; } if( rc ) goto abort_due_to_error; break; @@ -3849,7 +3959,7 @@ case OP_SetCookie: { rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3); if( pOp->p2==BTREE_SCHEMA_VERSION ){ /* When the schema cookie changes, record the new cookie internally */ - pDb->pSchema->schema_cookie = pOp->p3 - pOp->p5; + *(u32*)&pDb->pSchema->schema_cookie = *(u32*)&pOp->p3 - pOp->p5; db->mDbFlags |= DBFLAG_SchemaChange; sqlite3FkClearTriggerCache(db, pOp->p1); }else if( pOp->p2==BTREE_FILE_FORMAT ){ @@ -4082,8 +4192,8 @@ case OP_OpenDup: { pCx->pgnoRoot = pOrig->pgnoRoot; pCx->isOrdered = pOrig->isOrdered; pCx->ub.pBtx = pOrig->ub.pBtx; - pCx->hasBeenDuped = 1; - pOrig->hasBeenDuped = 1; + pCx->noReuse = 1; + pOrig->noReuse = 1; rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, pCx->pKeyInfo, pCx->uc.pCursor); /* The sqlite3BtreeCursor() routine can only fail for the first cursor @@ -4150,7 +4260,7 @@ case OP_OpenEphemeral: { aMem[pOp->p3].z = ""; } pCx = p->apCsr[pOp->p1]; - if( pCx && !pCx->hasBeenDuped && ALWAYS(pOp->p2<=pCx->nField) ){ + if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){ /* If the ephermeral table is already open and has no duplicates from ** OP_OpenDup, then erase all existing content so that the table is ** empty again, rather than creating a new table. */ @@ -4899,11 +5009,8 @@ case OP_NoConflict: /* jump, in3 */ case OP_NotFound: /* jump, in3 */ case OP_Found: { /* jump, in3 */ int alreadyExists; - int takeJump; int ii; VdbeCursor *pC; - int res; - UnpackedRecord *pFree; UnpackedRecord *pIdxKey; UnpackedRecord r; @@ -4918,14 +5025,15 @@ case OP_Found: { /* jump, in3 */ #ifdef SQLITE_DEBUG pC->seekOp = pOp->opcode; #endif - pIn3 = &aMem[pOp->p3]; + r.aMem = &aMem[pOp->p3]; assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0 ); assert( pC->isTable==0 ); - if( pOp->p4.i>0 ){ + r.nField = (u16)pOp->p4.i; + if( r.nField>0 ){ + /* Key values in an array of registers */ r.pKeyInfo = pC->pKeyInfo; - r.nField = (u16)pOp->p4.i; - r.aMem = pIn3; + r.default_rc = 0; #ifdef SQLITE_DEBUG for(ii=0; iip3+ii, &r.aMem[ii]); } #endif - pIdxKey = &r; - pFree = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &pC->seekResult); }else{ - assert( pIn3->flags & MEM_Blob ); - rc = ExpandBlob(pIn3); + /* Composite key generated by OP_MakeRecord */ + assert( r.aMem->flags & MEM_Blob ); + assert( pOp->opcode!=OP_NoConflict ); + rc = ExpandBlob(r.aMem); assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); if( rc ) goto no_mem; - pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); + pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); if( pIdxKey==0 ) goto no_mem; - sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); - } - pIdxKey->default_rc = 0; - takeJump = 0; - if( pOp->opcode==OP_NoConflict ){ - /* For the OP_NoConflict opcode, take the jump if any of the - ** input fields are NULL, since any key with a NULL will not - ** conflict */ - for(ii=0; iinField; ii++){ - if( pIdxKey->aMem[ii].flags & MEM_Null ){ - takeJump = 1; - break; - } - } + sqlite3VdbeRecordUnpack(pC->pKeyInfo, r.aMem->n, r.aMem->z, pIdxKey); + pIdxKey->default_rc = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult); + sqlite3DbFreeNN(db, pIdxKey); } - rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &res); - if( pFree ) sqlite3DbFreeNN(db, pFree); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } - pC->seekResult = res; - alreadyExists = (res==0); + alreadyExists = (pC->seekResult==0); pC->nullRow = 1-alreadyExists; pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; @@ -4971,9 +5067,25 @@ case OP_Found: { /* jump, in3 */ VdbeBranchTaken(alreadyExists!=0,2); if( alreadyExists ) goto jump_to_p2; }else{ - VdbeBranchTaken(takeJump||alreadyExists==0,2); - if( takeJump || !alreadyExists ) goto jump_to_p2; - if( pOp->opcode==OP_IfNoHope ) pC->seekHit = pOp->p4.i; + if( !alreadyExists ){ + VdbeBranchTaken(1,2); + goto jump_to_p2; + } + if( pOp->opcode==OP_NoConflict ){ + /* For the OP_NoConflict opcode, take the jump if any of the + ** input fields are NULL, since any key with a NULL will not + ** conflict */ + for(ii=0; iiopcode==OP_IfNoHope ){ + pC->seekHit = pOp->p4.i; + } } break; } @@ -5664,7 +5776,7 @@ case OP_RowData: { } /* Opcode: Rowid P1 P2 * * * -** Synopsis: r[P2]=rowid +** Synopsis: r[P2]=PX rowid of P1 ** ** Store in register P2 an integer which is the key of the table entry that ** P1 is currently point to. @@ -5720,16 +5832,24 @@ case OP_Rowid: { /* out2 */ ** that occur while the cursor is on the null row will always ** write a NULL. ** -** Or, if P1 is a Pseudo-Cursor (a cursor opened using OP_OpenPseudo) -** just reset the cache for that cursor. This causes the row of -** content held by the pseudo-cursor to be reparsed. +** If cursor P1 is not previously opened, open it now to a special +** pseudo-cursor that always returns NULL for every column. */ case OP_NullRow: { VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; - assert( pC!=0 ); + if( pC==0 ){ + /* If the cursor is not already open, create a special kind of + ** pseudo-cursor that always gives null rows. */ + pC = allocateCursor(p, pOp->p1, 1, CURTYPE_PSEUDO); + if( pC==0 ) goto no_mem; + pC->seekResult = 0; + pC->isTable = 1; + pC->noReuse = 1; + pC->uc.pCursor = sqlite3BtreeFakeValidCursor(); + } pC->nullRow = 1; pC->cacheStatus = CACHE_STALE; if( pC->eCurType==CURTYPE_BTREE ){ @@ -5902,7 +6022,7 @@ case OP_Rewind: { /* jump */ break; } -/* Opcode: Next P1 P2 P3 P4 P5 +/* Opcode: Next P1 P2 P3 * P5 ** ** Advance cursor P1 so that it points to the next key/data pair in its ** table or index. If there are no more key/value pairs then fall through @@ -5921,15 +6041,12 @@ case OP_Rewind: { /* jump */ ** omitted if that index had been unique. P3 is usually 0. P3 is ** always either 0 or 1. ** -** P4 is always of type P4_ADVANCE. The function pointer points to -** sqlite3BtreeNext(). -** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. ** ** See also: Prev */ -/* Opcode: Prev P1 P2 P3 P4 P5 +/* Opcode: Prev P1 P2 P3 * P5 ** ** Back up cursor P1 so that it points to the previous key/data pair in its ** table or index. If there is no previous key/value pairs then fall through @@ -5949,9 +6066,6 @@ case OP_Rewind: { /* jump */ ** omitted if that index had been unique. P3 is usually 0. P3 is ** always either 0 or 1. ** -** P4 is always of type P4_ADVANCE. The function pointer points to -** sqlite3BtreePrevious(). -** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. */ @@ -5969,30 +6083,33 @@ case OP_SorterNext: { /* jump */ assert( isSorter(pC) ); rc = sqlite3VdbeSorterNext(db, pC); goto next_tail; + case OP_Prev: /* jump */ -case OP_Next: /* jump */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p5aCounter) ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); assert( pC->eCurType==CURTYPE_BTREE ); - assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext ); - assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious ); + assert( pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE + || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope + || pC->seekOp==OP_NullRow); + rc = sqlite3BtreePrevious(pC->uc.pCursor, pOp->p3); + goto next_tail; - /* The Next opcode is only used after SeekGT, SeekGE, Rewind, and Found. - ** The Prev opcode is only used after SeekLT, SeekLE, and Last. */ - assert( pOp->opcode!=OP_Next - || pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE +case OP_Next: /* jump */ + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p5aCounter) ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->deferredMoveto==0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found || pC->seekOp==OP_NullRow|| pC->seekOp==OP_SeekRowid || pC->seekOp==OP_IfNoHope); - assert( pOp->opcode!=OP_Prev - || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE - || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope - || pC->seekOp==OP_NullRow); + rc = sqlite3BtreeNext(pC->uc.pCursor, pOp->p3); - rc = pOp->p4.xAdvance(pC->uc.pCursor, pOp->p3); next_tail: pC->cacheStatus = CACHE_STALE; VdbeBranchTaken(rc==SQLITE_OK,2); @@ -6179,9 +6296,9 @@ case OP_IdxRowid: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->eCurType==CURTYPE_BTREE || IsNullCursor(pC) ); assert( pC->uc.pCursor!=0 ); - assert( pC->isTable==0 ); + assert( pC->isTable==0 || IsNullCursor(pC) ); assert( pC->deferredMoveto==0 ); assert( !pC->nullRow || pOp->opcode==OP_IdxRowid ); @@ -6210,6 +6327,7 @@ case OP_IdxRowid: { /* out2 */ pTabCur->nullRow = 0; pTabCur->movetoTarget = rowid; pTabCur->deferredMoveto = 1; + pTabCur->cacheStatus = CACHE_STALE; assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 ); assert( !pTabCur->isEphemeral ); pTabCur->ub.aAltMap = pOp->p4.ai; @@ -6344,7 +6462,7 @@ case OP_IdxGE: { /* jump */ rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); if( rc ) goto abort_due_to_error; res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, &r, 0); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); } /* End of inlined sqlite3VdbeIdxKeyCompare() */ @@ -7225,6 +7343,7 @@ case OP_AggStep: { pCtx->pVdbe = p; pCtx->skipFlag = 0; pCtx->isError = 0; + pCtx->enc = encoding; pCtx->argc = n; pOp->p4type = P4_FUNCCTX; pOp->p4.pCtx = pCtx; @@ -7354,9 +7473,6 @@ case OP_AggFinal: { } sqlite3VdbeChangeEncoding(pMem, encoding); UPDATE_MAX_BLOBSIZE(pMem); - if( sqlite3VdbeMemTooBig(pMem) ){ - goto too_big; - } break; } @@ -7864,7 +7980,6 @@ case OP_VColumn: { VdbeCursor *pCur = p->apCsr[pOp->p1]; assert( pCur!=0 ); - assert( pCur->eCurType==CURTYPE_VTAB ); assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pDest = &aMem[pOp->p3]; memAboutToChange(p, pDest); @@ -7872,11 +7987,13 @@ case OP_VColumn: { sqlite3VdbeMemSetNull(pDest); break; } + assert( pCur->eCurType==CURTYPE_VTAB ); pVtab = pCur->uc.pVCur->pVtab; pModule = pVtab->pModule; assert( pModule->xColumn ); memset(&sContext, 0, sizeof(sContext)); sContext.pOut = pDest; + sContext.enc = encoding; assert( pOp->p5==OPFLAG_NOCHNG || pOp->p5==0 ); if( pOp->p5 & OPFLAG_NOCHNG ){ sqlite3VdbeMemSetNull(pDest); @@ -7895,9 +8012,6 @@ case OP_VColumn: { REGISTER_TRACE(pOp->p3, pDest); UPDATE_MAX_BLOBSIZE(pDest); - if( sqlite3VdbeMemTooBig(pDest) ){ - goto too_big; - } if( rc ) goto abort_due_to_error; break; } @@ -8164,6 +8278,7 @@ case OP_Function: { /* group */ if( pCtx->pOut != pOut ){ pCtx->pVdbe = p; pCtx->pOut = pOut; + pCtx->enc = encoding; for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; } assert( pCtx->pVdbe==p ); @@ -8190,17 +8305,27 @@ case OP_Function: { /* group */ if( rc ) goto abort_due_to_error; } - /* Copy the result of the function into register P3 */ - if( pOut->flags & (MEM_Str|MEM_Blob) ){ - sqlite3VdbeChangeEncoding(pOut, encoding); - if( sqlite3VdbeMemTooBig(pOut) ) goto too_big; - } + assert( (pOut->flags&MEM_Str)==0 + || pOut->enc==encoding + || db->mallocFailed ); + assert( !sqlite3VdbeMemTooBig(pOut) ); REGISTER_TRACE(pOp->p3, pOut); UPDATE_MAX_BLOBSIZE(pOut); break; } +/* Opcode: ClrSubtype P1 * * * * +** Synopsis: r[P1].subtype = 0 +** +** Clear the subtype from register P1. +*/ +case OP_ClrSubtype: { /* in1 */ + pIn1 = &aMem[pOp->p1]; + pIn1->flags &= ~MEM_Subtype; + break; +} + /* Opcode: FilterAdd P1 * P3 P4 * ** Synopsis: filter(P1) += key(P3@P4) ** @@ -8320,7 +8445,7 @@ case OP_Init: { /* jump */ #ifndef SQLITE_OMIT_TRACE if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0 - && !p->doingRerun + && p->minWriteFileFormat!=254 /* tag-20220401a */ && (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 ){ #ifndef SQLITE_OMIT_DEPRECATED @@ -8549,7 +8674,7 @@ default: { /* This is really OP_Noop, OP_Explain */ testcase( sqlite3GlobalConfig.xLog!=0 ); sqlite3_log(rc, "statement aborts at %d: [%s] %s", (int)(pOp - aOp), p->zSql, p->zErrMsg); - sqlite3VdbeHalt(p); + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ db->flags |= SQLITE_CorruptRdOnly; diff --git a/src/vdbe.h b/src/vdbe.h index 3257ff68a..5909d3995 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -63,7 +63,6 @@ struct VdbeOp { #ifdef SQLITE_ENABLE_CURSOR_HINTS Expr *pExpr; /* Used when p4type is P4_EXPR */ #endif - int (*xAdvance)(BtCursor *, int); } p4; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ @@ -114,21 +113,19 @@ typedef struct VdbeOpList VdbeOpList; #define P4_COLLSEQ (-2) /* P4 is a pointer to a CollSeq structure */ #define P4_INT32 (-3) /* P4 is a 32-bit signed integer */ #define P4_SUBPROGRAM (-4) /* P4 is a pointer to a SubProgram structure */ -#define P4_ADVANCE (-5) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -#define P4_TABLE (-6) /* P4 is a pointer to a Table structure */ +#define P4_TABLE (-5) /* P4 is a pointer to a Table structure */ /* Above do not own any resources. Must free those below */ -#define P4_FREE_IF_LE (-7) -#define P4_DYNAMIC (-7) /* Pointer to memory from sqliteMalloc() */ -#define P4_FUNCDEF (-8) /* P4 is a pointer to a FuncDef structure */ -#define P4_KEYINFO (-9) /* P4 is a pointer to a KeyInfo structure */ -#define P4_EXPR (-10) /* P4 is a pointer to an Expr tree */ -#define P4_MEM (-11) /* P4 is a pointer to a Mem* structure */ -#define P4_VTAB (-12) /* P4 is a pointer to an sqlite3_vtab structure */ -#define P4_REAL (-13) /* P4 is a 64-bit floating point value */ -#define P4_INT64 (-14) /* P4 is a 64-bit signed integer */ -#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ -#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ -#define P4_DYNBLOB (-17) /* Pointer to memory from sqliteMalloc() */ +#define P4_FREE_IF_LE (-6) +#define P4_DYNAMIC (-6) /* Pointer to memory from sqliteMalloc() */ +#define P4_FUNCDEF (-7) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-8) /* P4 is a pointer to a KeyInfo structure */ +#define P4_EXPR (-9) /* P4 is a pointer to an Expr tree */ +#define P4_MEM (-10) /* P4 is a pointer to a Mem* structure */ +#define P4_VTAB (-11) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_REAL (-12) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ +#define P4_INTARRAY (-14) /* P4 is a vector of 32-bit integers */ +#define P4_FUNCCTX (-15) /* P4 is a pointer to an sqlite3_context object */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 @@ -201,8 +198,10 @@ void sqlite3VdbeEndCoroutine(Vdbe*,int); #endif #if defined(SQLITE_DEBUG) void sqlite3VdbeVerifyAbortable(Vdbe *p, int); + void sqlite3VdbeNoJumpsOutsideSubrtn(Vdbe*,int,int,int); #else # define sqlite3VdbeVerifyAbortable(A,B) +# define sqlite3VdbeNoJumpsOutsideSubrtn(A,B,C,D) #endif VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); #ifndef SQLITE_OMIT_EXPLAIN @@ -247,7 +246,6 @@ int sqlite3VdbeMakeLabel(Parse*); void sqlite3VdbeRunOnlyOnce(Vdbe*); void sqlite3VdbeReusable(Vdbe*); void sqlite3VdbeDelete(Vdbe*); -void sqlite3VdbeClearObject(sqlite3*,Vdbe*); void sqlite3VdbeMakeReady(Vdbe*,Parse*); int sqlite3VdbeFinalize(Vdbe*); void sqlite3VdbeResolveLabel(Vdbe*, int); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index f02b37c6a..0e17c7d91 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -86,7 +86,7 @@ struct VdbeCursor { Bool isEphemeral:1; /* True for an ephemeral table */ Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ - Bool hasBeenDuped:1; /* This cursor was source or target of OP_OpenDup */ + Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ union { /* pBtx for isEphermeral. pAltMap otherwise */ Btree *pBtx; /* Separate file holding temporary table */ @@ -134,6 +134,11 @@ struct VdbeCursor { u32 aType[1]; /* Type values record decode. MUST BE LAST */ }; +/* Return true if P is a null-only cursor +*/ +#define IsNullCursor(P) \ + ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) + /* ** A value for VdbeCursor.cacheStatus that means the cache is always invalid. @@ -208,16 +213,16 @@ struct sqlite3_value { const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ } u; + 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 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ u8 eSubtype; /* Subtype for this value */ - int n; /* Number of characters in string value, excluding '\0' */ - char *z; /* String or BLOB value */ /* ShallowCopy only needs to copy the information above */ - char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ + sqlite3 *db; /* The associated database connection */ int szMalloc; /* Size of the zMalloc allocation */ u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */ - sqlite3 *db; /* The associated database connection */ + char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ void (*xDel)(void*);/* Destructor for Mem.z - only valid if MEM_Dyn */ #ifdef SQLITE_DEBUG Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */ @@ -229,11 +234,43 @@ struct sqlite3_value { ** Size of struct Mem not including the Mem.zMalloc member or anything that ** follows. */ -#define MEMCELLSIZE offsetof(Mem,zMalloc) +#define MEMCELLSIZE offsetof(Mem,db) -/* One or more of the following flags are set to indicate the validOK +/* One or more of the following flags are set to indicate the ** representations of the value stored in the Mem struct. ** +** * MEM_Null An SQL NULL value +** +** * MEM_Null|MEM_Zero An SQL NULL with the virtual table +** UPDATE no-change flag set +** +** * MEM_Null|MEM_Term| An SQL NULL, but also contains a +** MEM_Subtype pointer accessible using +** sqlite3_value_pointer(). +** +** * MEM_Null|MEM_Cleared Special SQL NULL that compares non-equal +** to other NULLs even using the IS operator. +** +** * MEM_Str A string, stored in Mem.z with +** length Mem.n. Zero-terminated if +** MEM_Term is set. This flag is +** incompatible with MEM_Blob and +** MEM_Null, but can appear with MEM_Int, +** MEM_Real, and MEM_IntReal. +** +** * MEM_Blob A blob, stored in Mem.z length Mem.n. +** Incompatible with MEM_Str, MEM_Null, +** MEM_Int, MEM_Real, and MEM_IntReal. +** +** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus +** MEM.u.i extra 0x00 bytes at the end. +** +** * MEM_Int Integer stored in Mem.u.i. +** +** * MEM_Real Real stored in Mem.u.r. +** +** * MEM_IntReal Real stored as an integer in Mem.u.i. +** ** If the MEM_Null flag is set, then the value is an SQL NULL value. ** For a pointer type created using sqlite3_bind_pointer() or ** sqlite3_result_pointer() the MEM_Term and MEM_Subtype flags are also set. @@ -244,6 +281,7 @@ struct sqlite3_value { ** set, then the string is nul terminated. The MEM_Int and MEM_Real ** flags may coexist with the MEM_Str flag. */ +#define MEM_Undefined 0x0000 /* Value is undefined */ #define MEM_Null 0x0001 /* Value is NULL (or a pointer) */ #define MEM_Str 0x0002 /* Value is a string */ #define MEM_Int 0x0004 /* Value is an integer */ @@ -251,28 +289,24 @@ struct sqlite3_value { #define MEM_Blob 0x0010 /* Value is a BLOB */ #define MEM_IntReal 0x0020 /* MEM_Int that stringifies like MEM_Real */ #define MEM_AffMask 0x003f /* Mask of affinity bits */ + +/* Extra bits that modify the meanings of the core datatypes above +*/ #define MEM_FromBind 0x0040 /* Value originates from sqlite3_bind() */ -#define MEM_Undefined 0x0080 /* Value is undefined */ + /* 0x0080 // Available */ #define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ -#define MEM_TypeMask 0xc1bf /* Mask of type bits */ - +#define MEM_Term 0x0200 /* String in Mem.z is zero terminated */ +#define MEM_Zero 0x0400 /* Mem.i contains count of 0s appended to blob */ +#define MEM_Subtype 0x0800 /* Mem.eSubtype is valid */ +#define MEM_TypeMask 0x0dbf /* Mask of type bits */ -/* Whenever Mem contains a valid string or blob representation, one of -** the following flags must be set to determine the memory management -** policy for Mem.z. The MEM_Term flag tells us whether or not the -** string is \000 or \u0000 terminated +/* Bits that determine the storage for Mem.z for a string or blob or +** aggregate accumulator. */ -#define MEM_Term 0x0200 /* String in Mem.z is zero terminated */ -#define MEM_Dyn 0x0400 /* Need to call Mem.xDel() on Mem.z */ -#define MEM_Static 0x0800 /* Mem.z points to a static string */ -#define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */ -#define MEM_Agg 0x2000 /* Mem.z points to an agg function context */ -#define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */ -#define MEM_Subtype 0x8000 /* Mem.eSubtype is valid */ -#ifdef SQLITE_OMIT_INCRBLOB - #undef MEM_Zero - #define MEM_Zero 0x0000 -#endif +#define MEM_Dyn 0x1000 /* Need to call Mem.xDel() on Mem.z */ +#define MEM_Static 0x2000 /* Mem.z points to a static string */ +#define MEM_Ephem 0x4000 /* Mem.z points to an ephemeral string */ +#define MEM_Agg 0x8000 /* Mem.z points to an agg function context */ /* Return TRUE if Mem X contains dynamically allocated content - anything ** that needs to be deallocated to avoid a leak. @@ -294,11 +328,15 @@ struct sqlite3_value { && (X)->n==0 && (X)->u.nZero==0) /* -** Return true if a memory cell is not marked as invalid. This macro +** Return true if a memory cell has been initialized and is valid. ** is for use inside assert() statements only. +** +** A Memory cell is initialized if at least one of the +** MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob, or MEM_IntReal bits +** is set. It is "undefined" if all those bits are zero. */ #ifdef SQLITE_DEBUG -#define memIsValid(M) ((M)->flags & MEM_Undefined)==0 +#define memIsValid(M) ((M)->flags & MEM_AffMask)!=0 #endif /* @@ -336,6 +374,7 @@ struct sqlite3_context { Vdbe *pVdbe; /* The VM that owns this context */ int iOp; /* Instruction number of OP_Function */ int isError; /* Error code returned by the function. */ + u8 enc; /* Encoding to use for results */ u8 skipFlag; /* Skip accumulator loading if true */ u8 argc; /* Number of arguments */ sqlite3_value *argv[1]; /* Argument set */ @@ -384,7 +423,6 @@ struct Vdbe { Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ Parse *pParse; /* Parsing context used to create this Vdbe */ ynVar nVar; /* Number of entries in aVar[] */ - u32 iVdbeMagic; /* Magic number defining state of the SQL statement */ int nMem; /* Number of memory locations currently allocated */ int nCursor; /* Number of slots in apCsr[] */ u32 cacheCtr; /* VdbeCursor row cache generation counter */ @@ -422,11 +460,10 @@ struct Vdbe { u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ - u8 doingRerun; /* True if rerunning after an auto-reprepare */ + u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ bft explain:2; /* True if EXPLAIN present on SQL command */ bft changeCntOn:1; /* True to update the change-counter */ - bft runOnlyOnce:1; /* Automatically expire on reset */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ @@ -453,13 +490,12 @@ struct Vdbe { }; /* -** The following are allowed values for Vdbe.magic +** The following are allowed values for Vdbe.eVdbeState */ -#define VDBE_MAGIC_INIT 0x16bceaa5 /* Building a VDBE program */ -#define VDBE_MAGIC_RUN 0x2df20da3 /* VDBE is ready to execute */ -#define VDBE_MAGIC_HALT 0x319c2973 /* VDBE has completed execution */ -#define VDBE_MAGIC_RESET 0x48fa9f76 /* Reset and ready to run again */ -#define VDBE_MAGIC_DEAD 0x5606c3c8 /* The VDBE has been deallocated */ +#define VDBE_INIT_STATE 0 /* Prepared statement under construction */ +#define VDBE_READY_STATE 1 /* Ready to run but not yet started */ +#define VDBE_RUN_STATE 2 /* Run in progress */ +#define VDBE_HALT_STATE 3 /* Finished. Need reset() or finalize() */ /* ** Structure used to store the context required by the @@ -500,18 +536,31 @@ struct ValueList { sqlite3_value *pOut; /* Register to hold each decoded output value */ }; +/* Size of content associated with serial types that fit into a +** single-byte varint. +*/ +#ifndef SQLITE_AMALGAMATION +extern const u8 sqlite3SmallTypeSizes[]; +#endif + /* ** Function prototypes */ void sqlite3VdbeError(Vdbe*, const char *, ...); void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*); +void sqlite3VdbeFreeCursorNN(Vdbe*,VdbeCursor*); void sqliteVdbePopStack(Vdbe*,int); +int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p); int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor*); -int sqlite3VdbeCursorMoveto(VdbeCursor**, u32*); int sqlite3VdbeCursorRestore(VdbeCursor*); u32 sqlite3VdbeSerialTypeLen(u32); u8 sqlite3VdbeOneByteSerialTypeLen(u8); -u32 sqlite3VdbeSerialPut(unsigned char*, Mem*, u32); +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT + u64 sqlite3FloatSwap(u64 in); +# define swapMixedEndianFloat(X) X = sqlite3FloatSwap(X) +#else +# define swapMixedEndianFloat(X) +#endif void sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int); @@ -569,6 +618,7 @@ int sqlite3VdbeMemCast(Mem*,u8,u8); int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*); int sqlite3VdbeMemFromBtreeZeroOffset(BtCursor*,u32,Mem*); void sqlite3VdbeMemRelease(Mem *p); +void sqlite3VdbeMemReleaseMalloc(Mem*p); int sqlite3VdbeMemFinalize(Mem*, FuncDef*); #ifndef SQLITE_OMIT_WINDOWFUNC int sqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 6939813a2..4e719ede3 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -345,6 +345,9 @@ sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){ sqlite3ValueFree(pNew); pNew = 0; } + }else if( pNew->flags & MEM_Null ){ + /* Do not duplicate pointer values */ + pNew->flags &= ~(MEM_Term|MEM_Subtype); } return pNew; } @@ -375,7 +378,8 @@ static void setResultStrOrError( u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - int rc = sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel); + Mem *pOut = pCtx->pOut; + int rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); if( rc ){ if( rc==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(pCtx); @@ -385,6 +389,11 @@ static void setResultStrOrError( assert( rc==SQLITE_NOMEM ); sqlite3_result_error_nomem(pCtx); } + return; + } + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ + sqlite3_result_error_toobig(pCtx); } } static int invokeValueDestructor( @@ -528,17 +537,22 @@ void sqlite3_result_text16le( } #endif /* SQLITE_OMIT_UTF16 */ void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ + Mem *pOut = pCtx->pOut; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - sqlite3VdbeMemCopy(pCtx->pOut, pValue); + sqlite3VdbeMemCopy(pOut, pValue); + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ + sqlite3_result_error_toobig(pCtx); + } } void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){ - assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - sqlite3VdbeMemSetZeroBlob(pCtx->pOut, n); + sqlite3_result_zeroblob64(pCtx, n>0 ? n : 0); } int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ Mem *pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){ + sqlite3_result_error_toobig(pCtx); return SQLITE_TOOBIG; } #ifndef SQLITE_OMIT_INCRBLOB @@ -554,8 +568,8 @@ void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode; #endif if( pCtx->pOut->flags & MEM_Null ){ - sqlite3VdbeMemSetStr(pCtx->pOut, sqlite3ErrStr(errCode), -1, - SQLITE_UTF8, SQLITE_STATIC); + setResultStrOrError(pCtx, sqlite3ErrStr(errCode), -1, SQLITE_UTF8, + SQLITE_STATIC); } } @@ -629,80 +643,83 @@ static int sqlite3Step(Vdbe *p){ int rc; assert(p); - if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){ - /* We used to require that sqlite3_reset() be called before retrying - ** 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 those 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( (rc = p->rc&0xff)==SQLITE_BUSY || 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. */ db = p->db; - if( db->mallocFailed ){ - p->rc = SQLITE_NOMEM; - return SQLITE_NOMEM_BKPT; - } + if( p->eVdbeState!=VDBE_RUN_STATE ){ + restart_step: + if( p->eVdbeState==VDBE_READY_STATE ){ + if( p->expired ){ + p->rc = SQLITE_SCHEMA; + rc = SQLITE_ERROR; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ + /* If this statement was prepared using saved SQL and an + ** error has occurred, then return the error code in p->rc to the + ** caller. Set the error code in the database handle to the same + ** value. + */ + rc = sqlite3VdbeTransferError(p); + } + goto end_of_step; + } - if( p->pc<0 && p->expired ){ - p->rc = SQLITE_SCHEMA; - rc = SQLITE_ERROR; - if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ - /* If this statement was prepared using saved SQL and an - ** error has occurred, then return the error code in p->rc to the - ** caller. Set the error code in the database handle to the same value. - */ - rc = sqlite3VdbeTransferError(p); - } - goto end_of_step; - } - if( p->pc<0 ){ - /* If there are no other statements currently running, then - ** reset the interrupt flag. This prevents a call to sqlite3_interrupt - ** from interrupting a statement that has not yet started. - */ - if( db->nVdbeActive==0 ){ - AtomicStore(&db->u1.isInterrupted, 0); - } + /* If there are no other statements currently running, then + ** reset the interrupt flag. This prevents a call to sqlite3_interrupt + ** from interrupting a statement that has not yet started. + */ + if( db->nVdbeActive==0 ){ + AtomicStore(&db->u1.isInterrupted, 0); + } - assert( db->nVdbeWrite>0 || db->autoCommit==0 - || (db->nDeferredCons==0 && db->nDeferredImmCons==0) - ); + assert( db->nVdbeWrite>0 || db->autoCommit==0 + || (db->nDeferredCons==0 && db->nDeferredImmCons==0) + ); #ifndef SQLITE_OMIT_TRACE - if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 - && !db->init.busy && p->zSql ){ - sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime); - }else{ - assert( p->startTime==0 ); - } + if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 + && !db->init.busy && p->zSql ){ + sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime); + }else{ + assert( p->startTime==0 ); + } #endif - db->nVdbeActive++; - if( p->readOnly==0 ) db->nVdbeWrite++; - if( p->bIsReader ) db->nVdbeRead++; - p->pc = 0; + db->nVdbeActive++; + if( p->readOnly==0 ) db->nVdbeWrite++; + if( p->bIsReader ) db->nVdbeRead++; + p->pc = 0; + p->eVdbeState = VDBE_RUN_STATE; + }else + + if( ALWAYS(p->eVdbeState==VDBE_HALT_STATE) ){ + /* We used to require that sqlite3_reset() be called before retrying + ** 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 those 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( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){ + sqlite3_reset((sqlite3_stmt*)p); + }else{ + return SQLITE_MISUSE_BKPT; + } +#else + sqlite3_reset((sqlite3_stmt*)p); +#endif + assert( p->eVdbeState==VDBE_READY_STATE ); + goto restart_step; + } } + #ifdef SQLITE_DEBUG p->rcApp = SQLITE_OK; #endif @@ -717,7 +734,12 @@ static int sqlite3Step(Vdbe *p){ db->nVdbeExec--; } - if( rc!=SQLITE_ROW ){ + if( rc==SQLITE_ROW ){ + assert( p->rc==SQLITE_OK ); + assert( db->mallocFailed==0 ); + db->errCode = SQLITE_ROW; + return SQLITE_ROW; + }else{ #ifndef SQLITE_OMIT_TRACE /* If the statement completed successfully, invoke the profile callback */ checkProfileCallback(db, p); @@ -769,7 +791,6 @@ int sqlite3_step(sqlite3_stmt *pStmt){ } db = v->db; sqlite3_mutex_enter(db->mutex); - v->doingRerun = 0; while( (rc = sqlite3Step(v))==SQLITE_SCHEMA && cnt++ < SQLITE_MAX_SCHEMA_RETRY ){ int savedPc = v->pc; @@ -795,7 +816,13 @@ int sqlite3_step(sqlite3_stmt *pStmt){ break; } sqlite3_reset(pStmt); - if( savedPc>=0 ) v->doingRerun = 1; + if( savedPc>=0 ){ + /* Setting minWriteFileFormat to 254 is a signal to the OP_Init and + ** OP_Trace opcodes to *not* perform SQLITE_TRACE_STMT because it has + ** already been done once on a prior invocation that failed due to + ** SQLITE_SCHEMA. tag-20220401a */ + v->minWriteFileFormat = 254; + } assert( v->expired==0 ); } sqlite3_mutex_leave(db->mutex); @@ -1104,15 +1131,15 @@ static const Mem *columnNullValue(void){ #endif = { /* .u = */ {0}, + /* .z = */ (char*)0, + /* .n = */ (int)0, /* .flags = */ (u16)MEM_Null, /* .enc = */ (u8)0, /* .eSubtype = */ (u8)0, - /* .n = */ (int)0, - /* .z = */ (char*)0, - /* .zMalloc = */ (char*)0, + /* .db = */ (sqlite3*)0, /* .szMalloc = */ (int)0, /* .uTemp = */ (u32)0, - /* .db = */ (sqlite3*)0, + /* .zMalloc = */ (char*)0, /* .xDel = */ (void(*)(void*))0, #ifdef SQLITE_DEBUG /* .pScopyFrom = */ (Mem*)0, @@ -1409,7 +1436,7 @@ static int vdbeUnbind(Vdbe *p, int i){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(p->db->mutex); - if( p->iVdbeMagic!=VDBE_MAGIC_RUN || p->pc>=0 ){ + if( p->eVdbeState!=VDBE_READY_STATE ){ sqlite3Error(p->db, SQLITE_MISUSE); sqlite3_mutex_leave(p->db->mutex); sqlite3_log(SQLITE_MISUSE, @@ -1762,7 +1789,7 @@ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ */ int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; - return v!=0 && v->iVdbeMagic==VDBE_MAGIC_RUN && v->pc>=0; + return v!=0 && v->eVdbeState==VDBE_RUN_STATE; } /* @@ -1808,8 +1835,7 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ sqlite3_mutex_enter(db->mutex); v = 0; db->pnBytesFreed = (int*)&v; - sqlite3VdbeClearObject(db, pVdbe); - sqlite3DbFree(db, pVdbe); + sqlite3VdbeDelete(pVdbe); db->pnBytesFreed = 0; sqlite3_mutex_leave(db->mutex); }else{ diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 0f3d0eb87..7c3be404e 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -35,7 +35,7 @@ Vdbe *sqlite3VdbeCreate(Parse *pParse){ p->pNext = db->pVdbe; p->pPrev = 0; db->pVdbe = p; - p->iVdbeMagic = VDBE_MAGIC_INIT; + assert( p->eVdbeState==VDBE_INIT_STATE ); p->pParse = pParse; pParse->pVdbe = p; assert( pParse->aLabel==0 ); @@ -180,7 +180,7 @@ static int growOpArray(Vdbe *v, int nOp){ return SQLITE_NOMEM; } - assert( nOp<=(1024/sizeof(Op)) ); + assert( nOp<=(int)(1024/sizeof(Op)) ); assert( nNew>=(v->nOpAlloc+nOp) ); pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op)); if( pNew ){ @@ -236,7 +236,7 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ VdbeOp *pOp; i = p->nOp; - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( op>=0 && op<0xff ); if( p->nOpAlloc<=i ){ return growOp3(p, op, p1, p2, p3); @@ -568,7 +568,7 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){ void sqlite3VdbeResolveLabel(Vdbe *v, int x){ Parse *p = v->pParse; int j = ADDR(x); - assert( v->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( v->eVdbeState==VDBE_INIT_STATE ); assert( j<-p->nLabel ); assert( j>=0 ); #ifdef SQLITE_DEBUG @@ -588,14 +588,20 @@ void sqlite3VdbeResolveLabel(Vdbe *v, int x){ ** Mark the VDBE as one that can only be run one time. */ void sqlite3VdbeRunOnlyOnce(Vdbe *p){ - p->runOnlyOnce = 1; + sqlite3VdbeAddOp2(p, OP_Expire, 1, 1); } /* -** Mark the VDBE as one that can only be run multiple times. +** Mark the VDBE as one that can be run multiple times. */ void sqlite3VdbeReusable(Vdbe *p){ - p->runOnlyOnce = 0; + int i; + for(i=1; ALWAYS(inOp); i++){ + if( ALWAYS(p->aOp[i].opcode==OP_Expire) ){ + p->aOp[1].opcode = OP_Noop; + break; + } + } } #ifdef SQLITE_DEBUG /* sqlite3AssertMayAbort() logic */ @@ -699,6 +705,8 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ int hasInitCoroutine = 0; Op *pOp; VdbeOpIter sIter; + + if( v==0 ) return 0; memset(&sIter, 0, sizeof(sIter)); sIter.v = v; @@ -782,7 +790,7 @@ void sqlite3VdbeAssertAbortable(Vdbe *p){ ** (3) Update the Vdbe.readOnly and Vdbe.bIsReader flags to accurately ** indicate what the prepared statement actually does. ** -** (4) Initialize the p4.xAdvance pointer on opcodes that use it. +** (4) (discontinued) ** ** (5) Reclaim the memory allocated for storing labels. ** @@ -828,25 +836,6 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ p->bIsReader = 1; break; } - case OP_Next: - case OP_SorterNext: { - pOp->p4.xAdvance = sqlite3BtreeNext; - pOp->p4type = P4_ADVANCE; - /* The code generator never codes any of these opcodes as a jump - ** to a label. They are always coded as a jump backwards to a - ** known address */ - assert( pOp->p2>=0 ); - break; - } - case OP_Prev: { - pOp->p4.xAdvance = sqlite3BtreePrevious; - pOp->p4type = P4_ADVANCE; - /* The code generator never codes any of these opcodes as a jump - ** to a label. They are always coded as a jump backwards to a - ** known address */ - assert( pOp->p2>=0 ); - break; - } #ifndef SQLITE_OMIT_VIRTUALTABLE case OP_VUpdate: { if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2; @@ -882,18 +871,104 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ if( pOp==p->aOp ) break; pOp--; } - sqlite3DbFree(p->db, pParse->aLabel); - pParse->aLabel = 0; + if( aLabel ){ + sqlite3DbFreeNN(p->db, pParse->aLabel); + pParse->aLabel = 0; + } pParse->nLabel = 0; *pMaxFuncArgs = nMaxArgs; assert( p->bIsReader!=0 || DbMaskAllZero(p->btreeMask) ); } +#ifdef SQLITE_DEBUG +/* +** Check to see if a subroutine contains a jump to a location outside of +** the subroutine. If a jump outside the subroutine is detected, add code +** that will cause the program to halt with an error message. +** +** The subroutine consists of opcodes between iFirst and iLast. Jumps to +** locations within the subroutine are acceptable. iRetReg is a register +** that contains the return address. Jumps to outside the range of iFirst +** through iLast are also acceptable as long as the jump destination is +** an OP_Return to iReturnAddr. +** +** A jump to an unresolved label means that the jump destination will be +** beyond the current address. That is normally a jump to an early +** termination and is consider acceptable. +** +** This routine only runs during debug builds. The purpose is (of course) +** to detect invalid escapes out of a subroutine. The OP_Halt opcode +** is generated rather than an assert() or other error, so that ".eqp full" +** will still work to show the original bytecode, to aid in debugging. +*/ +void sqlite3VdbeNoJumpsOutsideSubrtn( + Vdbe *v, /* The byte-code program under construction */ + int iFirst, /* First opcode of the subroutine */ + int iLast, /* Last opcode of the subroutine */ + int iRetReg /* Subroutine return address register */ +){ + VdbeOp *pOp; + Parse *pParse; + int i; + sqlite3_str *pErr = 0; + assert( v!=0 ); + pParse = v->pParse; + assert( pParse!=0 ); + if( pParse->nErr ) return; + assert( iLast>=iFirst ); + assert( iLastnOp ); + pOp = &v->aOp[iFirst]; + for(i=iFirst; i<=iLast; i++, pOp++){ + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ){ + int iDest = pOp->p2; /* Jump destination */ + if( iDest==0 ) continue; + if( pOp->opcode==OP_Gosub ) continue; + if( iDest<0 ){ + int j = ADDR(iDest); + assert( j>=0 ); + if( j>=-pParse->nLabel || pParse->aLabel[j]<0 ){ + continue; + } + iDest = pParse->aLabel[j]; + } + if( iDestiLast ){ + int j = iDest; + for(; jnOp; j++){ + VdbeOp *pX = &v->aOp[j]; + if( pX->opcode==OP_Return ){ + if( pX->p1==iRetReg ) break; + continue; + } + if( pX->opcode==OP_Noop ) continue; + if( pX->opcode==OP_Explain ) continue; + if( pErr==0 ){ + pErr = sqlite3_str_new(0); + }else{ + sqlite3_str_appendchar(pErr, 1, '\n'); + } + sqlite3_str_appendf(pErr, + "Opcode at %d jumps to %d which is outside the " + "subroutine at %d..%d", + i, iDest, iFirst, iLast); + break; + } + } + } + } + if( pErr ){ + char *zErr = sqlite3_str_finish(pErr); + sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_INTERNAL, OE_Abort, 0, zErr, 0); + sqlite3_free(zErr); + sqlite3MayAbort(pParse); + } +} +#endif /* SQLITE_DEBUG */ + /* ** Return the address of the next instruction to be inserted. */ int sqlite3VdbeCurrentAddr(Vdbe *p){ - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); return p->nOp; } @@ -978,7 +1053,7 @@ VdbeOp *sqlite3VdbeAddOpList( int i; VdbeOp *pOut, *pFirst; assert( nOp>0 ); - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){ return 0; } @@ -1130,7 +1205,6 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ case P4_REAL: case P4_INT64: case P4_DYNAMIC: - case P4_DYNBLOB: case P4_INTARRAY: { sqlite3DbFree(db, p4); break; @@ -1170,13 +1244,16 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ ** nOp entries. */ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ + assert( nOp>=0 ); if( aOp ){ - Op *pOp; - for(pOp=&aOp[nOp-1]; pOp>=aOp; pOp--){ + Op *pOp = &aOp[nOp-1]; + while(1){ /* Exit via break */ if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS sqlite3DbFree(db, pOp->zComment); #endif + if( pOp==aOp ) break; + pOp--; } sqlite3DbFreeNN(db, aOp); } @@ -1238,7 +1315,7 @@ void sqlite3VdbeReleaseRegisters( u32 mask, /* Mask of registers to NOT release */ int bUndefine /* If true, mark registers as undefined */ ){ - if( N==0 ) return; + if( N==0 || OptimizationDisabled(pParse->db, SQLITE_ReleaseReg) ) return; assert( pParse->pVdbe ); assert( iFirst>=1 ); assert( iFirst+N-1<=pParse->nMem ); @@ -1302,7 +1379,7 @@ void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){ sqlite3 *db; assert( p!=0 ); db = p->db; - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( p->aOp!=0 || db->mallocFailed ); if( db->mallocFailed ){ if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4); @@ -1430,7 +1507,7 @@ VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ /* C89 specifies that the constant "dummy" will be initialized to all ** zeros, which is correct. MSVC generates a warning, nevertheless. */ static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */ - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); if( addr<0 ){ addr = p->nOp - 1; } @@ -1497,8 +1574,11 @@ char *sqlite3VdbeDisplayComment( if( c=='4' ){ sqlite3_str_appendall(&x, zP4); }else if( c=='X' ){ - sqlite3_str_appendall(&x, pOp->zComment); - seenCom = 1; + if( pOp->zComment && pOp->zComment[0] ){ + sqlite3_str_appendall(&x, pOp->zComment); + seenCom = 1; + break; + } }else{ int v1 = translateP(c, pOp); int v2; @@ -1727,10 +1807,6 @@ char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){ zP4 = "program"; break; } - case P4_DYNBLOB: - case P4_ADVANCE: { - break; - } case P4_TABLE: { zP4 = pOp->p4.pTab->zName; break; @@ -1862,21 +1938,40 @@ void sqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){ /* ** Initialize an array of N Mem element. +** +** This is a high-runner, so only those fields that really do need to +** be initialized are set. The Mem structure is organized so that +** the fields that get initialized are nearby and hopefully on the same +** cache line. +** +** Mem.flags = flags +** Mem.db = db +** Mem.szMalloc = 0 +** +** All other fields of Mem can safely remain uninitialized for now. They +** will be initialized before use. */ static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ - while( (N--)>0 ){ - p->db = db; - p->flags = flags; - p->szMalloc = 0; + if( N>0 ){ + do{ + p->flags = flags; + p->db = db; + p->szMalloc = 0; #ifdef SQLITE_DEBUG - p->pScopyFrom = 0; + p->pScopyFrom = 0; #endif - p++; + p++; + }while( (--N)>0 ); } } /* -** Release an array of N Mem elements +** Release auxiliary memory held in an array of N Mem elements. +** +** After this routine returns, all Mem elements in the array will still +** be valid. Those Mem elements that were not holding auxiliary resources +** will be unchanged. Mem elements which had something freed will be +** set to MEM_Undefined. */ static void releaseMemArray(Mem *p, int N){ if( p && N ){ @@ -1909,12 +2004,17 @@ static void releaseMemArray(Mem *p, int N){ if( p->flags&(MEM_Agg|MEM_Dyn) ){ testcase( (p->flags & MEM_Dyn)!=0 && p->xDel==sqlite3VdbeFrameMemDel ); sqlite3VdbeMemRelease(p); + p->flags = MEM_Undefined; }else if( p->szMalloc ){ sqlite3DbFreeNN(db, p->zMalloc); p->szMalloc = 0; + p->flags = MEM_Undefined; } - - p->flags = MEM_Undefined; +#ifdef SQLITE_DEBUG + else{ + p->flags = MEM_Undefined; + } +#endif }while( (++p)nChildMem]; assert( sqlite3VdbeFrameIsValid(p) ); for(i=0; inChildCsr; i++){ - sqlite3VdbeFreeCursor(p->v, apCsr[i]); + if( apCsr[i] ) sqlite3VdbeFreeCursorNN(p->v, apCsr[i]); } releaseMemArray(aMem, p->nChildMem); sqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0); @@ -2112,7 +2212,7 @@ int sqlite3VdbeList( Op *pOp; /* Current opcode */ assert( p->explain ); - assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); + assert( p->eVdbeState==VDBE_RUN_STATE ); assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); /* Even though this opcode does not use dynamic strings for @@ -2267,11 +2367,11 @@ struct ReusableSpace { static void *allocSpace( struct ReusableSpace *p, /* Bulk memory available for allocation */ void *pBuf, /* Pointer to a prior allocation */ - sqlite3_int64 nByte /* Bytes of memory needed */ + sqlite3_int64 nByte /* Bytes of memory needed. */ ){ assert( EIGHT_BYTE_ALIGNMENT(p->pSpace) ); if( pBuf==0 ){ - nByte = ROUND8(nByte); + nByte = ROUND8P(nByte); if( nByte <= p->nFree ){ p->nFree -= nByte; pBuf = &p->pSpace[p->nFree]; @@ -2292,14 +2392,15 @@ void sqlite3VdbeRewind(Vdbe *p){ int i; #endif assert( p!=0 ); - assert( p->iVdbeMagic==VDBE_MAGIC_INIT || p->iVdbeMagic==VDBE_MAGIC_RESET ); + assert( p->eVdbeState==VDBE_INIT_STATE + || p->eVdbeState==VDBE_READY_STATE + || p->eVdbeState==VDBE_HALT_STATE ); /* There should be at least one opcode. */ assert( p->nOp>0 ); - /* Set the magic to VDBE_MAGIC_RUN sooner rather than later. */ - p->iVdbeMagic = VDBE_MAGIC_RUN; + p->eVdbeState = VDBE_READY_STATE; #ifdef SQLITE_DEBUG for(i=0; inMem; i++){ @@ -2355,7 +2456,7 @@ void sqlite3VdbeMakeReady( assert( p!=0 ); assert( p->nOp>0 ); assert( pParse!=0 ); - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( pParse==p->pParse ); p->pVList = pParse->pVList; pParse->pVList = 0; @@ -2378,7 +2479,7 @@ void sqlite3VdbeMakeReady( ** opcode array. This extra memory will be reallocated for other elements ** of the prepared statement. */ - n = ROUND8(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ + n = ROUND8P(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ x.pSpace = &((u8*)p->aOp)[n]; /* Unused opcode memory */ assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) ); x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ @@ -2466,9 +2567,9 @@ void sqlite3VdbeMakeReady( ** happens to hold. */ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ - if( pCx==0 ){ - return; - } + if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); +} +void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); @@ -2496,14 +2597,12 @@ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ ** Close all cursors in the current frame. */ static void closeCursorsInFrame(Vdbe *p){ - if( p->apCsr ){ - int i; - for(i=0; inCursor; i++){ - VdbeCursor *pC = p->apCsr[i]; - if( pC ){ - sqlite3VdbeFreeCursor(p, pC); - p->apCsr[i] = 0; - } + int i; + for(i=0; inCursor; i++){ + VdbeCursor *pC = p->apCsr[i]; + if( pC ){ + sqlite3VdbeFreeCursorNN(p, pC); + p->apCsr[i] = 0; } } } @@ -2552,9 +2651,7 @@ static void closeAllCursors(Vdbe *p){ } assert( p->nFrame==0 ); closeCursorsInFrame(p); - if( p->aMem ){ - releaseMemArray(p->aMem, p->nMem); - } + releaseMemArray(p->aMem, p->nMem); while( p->pDelFrame ){ VdbeFrame *pDel = p->pDelFrame; p->pDelFrame = pDel->pParent; @@ -2994,7 +3091,8 @@ int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; p->errorAction = OE_Abort; sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); - return SQLITE_ERROR; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; + return SQLITE_CONSTRAINT_FOREIGNKEY; } return SQLITE_OK; } @@ -3033,9 +3131,7 @@ int sqlite3VdbeHalt(Vdbe *p){ ** one, or the complete transaction if there is no statement transaction. */ - if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){ - return SQLITE_OK; - } + assert( p->eVdbeState==VDBE_RUN_STATE ); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; } @@ -3044,7 +3140,7 @@ int sqlite3VdbeHalt(Vdbe *p){ /* No commit or rollback needed if the program never started or if the ** SQL statement does not read or write a database file. */ - if( p->pc>=0 && p->bIsReader ){ + if( p->bIsReader ){ int mrc; /* Primary error code from p->rc */ int eStatementOp = 0; int isSpecialError; /* Set to true if a 'special' error */ @@ -3192,15 +3288,13 @@ int sqlite3VdbeHalt(Vdbe *p){ } /* We have successfully halted and closed the VM. Record this fact. */ - if( p->pc>=0 ){ - db->nVdbeActive--; - if( !p->readOnly ) db->nVdbeWrite--; - if( p->bIsReader ) db->nVdbeRead--; - assert( db->nVdbeActive>=db->nVdbeRead ); - assert( db->nVdbeRead>=db->nVdbeWrite ); - assert( db->nVdbeWrite>=0 ); - } - p->iVdbeMagic = VDBE_MAGIC_HALT; + db->nVdbeActive--; + if( !p->readOnly ) db->nVdbeWrite--; + if( p->bIsReader ) db->nVdbeRead--; + assert( db->nVdbeActive>=db->nVdbeRead ); + assert( db->nVdbeRead>=db->nVdbeWrite ); + assert( db->nVdbeWrite>=0 ); + p->eVdbeState = VDBE_HALT_STATE; checkActiveVdbeCnt(db); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; @@ -3282,8 +3376,8 @@ static void vdbeInvokeSqllog(Vdbe *v){ ** again. ** ** To look at it another way, this routine resets the state of the -** virtual machine from VDBE_MAGIC_RUN or VDBE_MAGIC_HALT back to -** VDBE_MAGIC_INIT. +** virtual machine from VDBE_RUN_STATE or VDBE_HALT_STATE back to +** VDBE_READY_STATE. */ int sqlite3VdbeReset(Vdbe *p){ #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) @@ -3297,7 +3391,7 @@ int sqlite3VdbeReset(Vdbe *p){ ** error, then it might not have been halted properly. So halt ** it now. */ - sqlite3VdbeHalt(p); + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); /* If the VDBE has been run even partially, then transfer the error code ** and error message from the VDBE into the main database structure. But @@ -3311,13 +3405,6 @@ int sqlite3VdbeReset(Vdbe *p){ }else{ db->errCode = p->rc; } - if( p->runOnlyOnce ) p->expired = 1; - }else if( p->rc && p->expired ){ - /* The expired flag was set on the VDBE before the first call - ** to sqlite3_step(). For consistency (since sqlite3_step() was - ** called), set the database error in this case as well. - */ - sqlite3ErrorWithMsg(db, p->rc, p->zErrMsg ? "%s" : 0, p->zErrMsg); } /* Reset register contents and reclaim error message memory. @@ -3374,7 +3461,6 @@ int sqlite3VdbeReset(Vdbe *p){ } } #endif - p->iVdbeMagic = VDBE_MAGIC_RESET; return p->rc & db->errMask; } @@ -3384,7 +3470,10 @@ int sqlite3VdbeReset(Vdbe *p){ */ int sqlite3VdbeFinalize(Vdbe *p){ int rc = SQLITE_OK; - if( p->iVdbeMagic==VDBE_MAGIC_RUN || p->iVdbeMagic==VDBE_MAGIC_HALT ){ + assert( VDBE_RUN_STATE>VDBE_READY_STATE ); + assert( VDBE_HALT_STATE>VDBE_READY_STATE ); + assert( VDBE_INIT_STATEeVdbeState>=VDBE_READY_STATE ){ rc = sqlite3VdbeReset(p); assert( (rc & p->db->errMask)==rc ); } @@ -3436,22 +3525,24 @@ void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, int mask){ ** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with ** the database connection and frees the object itself. */ -void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ +static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; assert( p->db==0 || p->db==db ); - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + if( p->aColName ){ + releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + sqlite3DbFreeNN(db, p->aColName); + } for(pSub=p->pProgram; pSub; pSub=pNext){ pNext = pSub->pNext; vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } - if( p->iVdbeMagic!=VDBE_MAGIC_INIT ){ + if( p->eVdbeState!=VDBE_INIT_STATE ){ releaseMemArray(p->aVar, p->nVar); - sqlite3DbFree(db, p->pVList); - sqlite3DbFree(db, p->pFree); + if( p->pVList ) sqlite3DbFreeNN(db, p->pVList); + if( p->pFree ) sqlite3DbFreeNN(db, p->pFree); } vdbeFreeOpArray(db, p->aOp, p->nOp); - sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); #ifdef SQLITE_ENABLE_NORMALIZE sqlite3DbFree(db, p->zNormSql); @@ -3484,17 +3575,17 @@ void sqlite3VdbeDelete(Vdbe *p){ db = p->db; assert( sqlite3_mutex_held(db->mutex) ); sqlite3VdbeClearObject(db, p); - if( p->pPrev ){ - p->pPrev->pNext = p->pNext; - }else{ - assert( db->pVdbe==p ); - db->pVdbe = p->pNext; - } - if( p->pNext ){ - p->pNext->pPrev = p->pPrev; + if( db->pnBytesFreed==0 ){ + if( p->pPrev ){ + p->pPrev->pNext = p->pNext; + }else{ + assert( db->pVdbe==p ); + db->pVdbe = p->pNext; + } + if( p->pNext ){ + p->pNext->pPrev = p->pPrev; + } } - p->iVdbeMagic = VDBE_MAGIC_DEAD; - p->db = 0; sqlite3DbFreeNN(db, p); } @@ -3529,7 +3620,7 @@ int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){ ** is supposed to be pointing. If the row was deleted out from under the ** cursor, set the cursor to point to a NULL row. */ -static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ +int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p){ int isDifferentRow, rc; assert( p->eCurType==CURTYPE_BTREE ); assert( p->uc.pCursor!=0 ); @@ -3545,41 +3636,9 @@ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ ** if need be. Return any I/O error from the restore operation. */ int sqlite3VdbeCursorRestore(VdbeCursor *p){ - assert( p->eCurType==CURTYPE_BTREE ); + assert( p->eCurType==CURTYPE_BTREE || IsNullCursor(p) ); if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ - return handleMovedCursor(p); - } - return SQLITE_OK; -} - -/* -** Make sure the cursor p is ready to read or write the row to which it -** was last positioned. Return an error code if an OOM fault or I/O error -** prevents us from positioning the cursor to its correct position. -** -** If a MoveTo operation is pending on the given cursor, then do that -** MoveTo now. If no move is pending, check to see if the row has been -** deleted out from under the cursor and if it has, mark the row as -** a NULL row. -** -** If the cursor is already pointing to the correct row and that row has -** not been deleted out from under the cursor, then this routine is a no-op. -*/ -int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){ - VdbeCursor *p = *pp; - assert( p->eCurType==CURTYPE_BTREE || p->eCurType==CURTYPE_PSEUDO ); - if( p->deferredMoveto ){ - u32 iMap; - assert( !p->isEphemeral ); - if( p->ub.aAltMap && (iMap = p->ub.aAltMap[1+*piCol])>0 && !p->nullRow ){ - *pp = p->pAltCursor; - *piCol = iMap - 1; - return SQLITE_OK; - } - return sqlite3VdbeFinishMoveto(p); - } - if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ - return handleMovedCursor(p); + return sqlite3VdbeHandleMovedCursor(p); } return SQLITE_OK; } @@ -3590,7 +3649,7 @@ int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){ ** sqlite3VdbeSerialType() ** sqlite3VdbeSerialTypeLen() ** sqlite3VdbeSerialLen() -** sqlite3VdbeSerialPut() +** sqlite3VdbeSerialPut() <--- in-lined into OP_MakeRecord as of 2022-04-02 ** sqlite3VdbeSerialGet() ** ** encapsulate the code that serializes values for storage in SQLite @@ -3702,7 +3761,7 @@ u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ /* ** The sizes for serial types less than 128 */ -static const u8 sqlite3SmallTypeSizes[] = { +const u8 sqlite3SmallTypeSizes[128] = { /* 0 1 2 3 4 5 6 7 8 9 */ /* 0 */ 0, 1, 2, 3, 4, 6, 8, 8, 0, 0, /* 10 */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, @@ -3771,7 +3830,7 @@ u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ ** so we trust him. */ #ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT -static u64 floatSwap(u64 in){ +u64 sqlite3FloatSwap(u64 in){ union { u64 r; u32 i[2]; @@ -3784,59 +3843,8 @@ static u64 floatSwap(u64 in){ u.i[1] = t; return u.r; } -# define swapMixedEndianFloat(X) X = floatSwap(X) -#else -# define swapMixedEndianFloat(X) -#endif - -/* -** Write the serialized data blob for the value stored in pMem into -** buf. It is assumed that the caller has allocated sufficient space. -** Return the number of bytes written. -** -** nBuf is the amount of space left in buf[]. The caller is responsible -** for allocating enough space to buf[] to hold the entire field, exclusive -** of the pMem->u.nZero bytes for a MEM_Zero value. -** -** Return the number of bytes actually written into buf[]. The number -** of bytes in the zero-filled tail is included in the return value only -** if those bytes were zeroed in buf[]. -*/ -u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ - u32 len; - - /* Integer and Real */ - if( serial_type<=7 && serial_type>0 ){ - u64 v; - u32 i; - if( serial_type==7 ){ - assert( sizeof(v)==sizeof(pMem->u.r) ); - memcpy(&v, &pMem->u.r, sizeof(v)); - swapMixedEndianFloat(v); - }else{ - v = pMem->u.i; - } - len = i = sqlite3SmallTypeSizes[serial_type]; - assert( i>0 ); - do{ - buf[--i] = (u8)(v&0xFF); - v >>= 8; - }while( i ); - return len; - } - - /* String or blob */ - if( serial_type>=12 ){ - assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0) - == (int)sqlite3VdbeSerialTypeLen(serial_type) ); - len = pMem->n; - if( len>0 ) memcpy(buf, pMem->z, len); - return len; - } +#endif /* SQLITE_MIXED_ENDIAN_64BIT_FLOAT */ - /* NULL or constants 0 or 1 */ - return 0; -} /* Input "x" is a sequence of unsigned characters that represent a ** big-endian integer. Return the equivalent native integer @@ -4002,10 +4010,10 @@ UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( ){ UnpackedRecord *p; /* Unpacked record to return */ int nByte; /* Number of bytes required for *p */ - nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); + nByte = ROUND8P(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); if( !p ) return 0; - p->aMem = (Mem*)&((char*)p)[ROUND8(sizeof(UnpackedRecord))]; + p->aMem = (Mem*)&((char*)p)[ROUND8P(sizeof(UnpackedRecord))]; assert( pKeyInfo->aSortFlags!=0 ); p->pKeyInfo = pKeyInfo; p->nField = pKeyInfo->nKeyField + 1; @@ -4241,8 +4249,8 @@ static int vdbeCompareMemString( }else{ rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); } - sqlite3VdbeMemRelease(&c1); - sqlite3VdbeMemRelease(&c2); + sqlite3VdbeMemReleaseMalloc(&c1); + sqlite3VdbeMemReleaseMalloc(&c2); return rc; } } @@ -4503,14 +4511,22 @@ int sqlite3VdbeRecordCompareWithSkip( ** two elements in the keys are equal. Fix the various stack variables so ** that this routine begins comparing at the second field. */ if( bSkip ){ - u32 s1; - idx1 = 1 + getVarint32(&aKey1[1], s1); + u32 s1 = aKey1[1]; + if( s1<0x80 ){ + idx1 = 2; + }else{ + idx1 = 1 + sqlite3GetVarint32(&aKey1[1], &s1); + } szHdr1 = aKey1[0]; d1 = szHdr1 + sqlite3VdbeSerialTypeLen(s1); i = 1; pRhs++; }else{ - idx1 = getVarint32(aKey1, szHdr1); + if( (szHdr1 = aKey1[0])<0x80 ){ + idx1 = 1; + }else{ + idx1 = sqlite3GetVarint32(aKey1, &szHdr1); + } d1 = szHdr1; i = 0; } @@ -4766,7 +4782,8 @@ static int vdbeRecordCompareInt( return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); } - v = pPKey2->aMem[0].u.i; + assert( pPKey2->u.i == pPKey2->aMem[0].u.i ); + v = pPKey2->u.i; if( v>lhs ){ res = pPKey2->r1; }else if( vaMem[0].flags & MEM_Str ); + assert( pPKey2->aMem[0].n == pPKey2->n ); + assert( pPKey2->aMem[0].z == pPKey2->u.z ); vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); - serial_type = (u8)(aKey1[1]); - if( serial_type >= 0x80 ){ - sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type); - } + serial_type = (signed char)(aKey1[1]); + +vrcs_restart: if( serial_type<12 ){ + if( serial_type<0 ){ + sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type); + if( serial_type>=12 ) goto vrcs_restart; + assert( CORRUPT_DB ); + } res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ }else if( !(serial_type & 0x01) ){ res = pPKey2->r2; /* (pKey1/nKey1) is a blob */ @@ -4820,15 +4843,15 @@ static int vdbeRecordCompareString( pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; return 0; /* Corruption */ } - nCmp = MIN( pPKey2->aMem[0].n, nStr ); - res = memcmp(&aKey1[szHdr], pPKey2->aMem[0].z, nCmp); + nCmp = MIN( pPKey2->n, nStr ); + res = memcmp(&aKey1[szHdr], pPKey2->u.z, nCmp); if( res>0 ){ res = pPKey2->r2; }else if( res<0 ){ res = pPKey2->r1; }else{ - res = nStr - pPKey2->aMem[0].n; + res = nStr - pPKey2->n; if( res==0 ){ if( pPKey2->nField>1 ){ res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); @@ -4883,6 +4906,7 @@ RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ p->r2 = 1; } if( (flags & MEM_Int) ){ + p->u.i = p->aMem[0].u.i; return vdbeRecordCompareInt; } testcase( flags & MEM_Real ); @@ -4892,6 +4916,8 @@ RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ && p->pKeyInfo->aColl[0]==0 ){ assert( flags & MEM_Str ); + p->u.z = p->aMem[0].z; + p->n = p->aMem[0].n; return vdbeRecordCompareString; } } @@ -4964,14 +4990,14 @@ int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* Fetch the integer off the end of the index record */ sqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v); *rowid = v.u.i; - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_OK; /* Jump here if database corruption is detected after m has been ** allocated. Free the m object and return SQLITE_CORRUPT. */ idx_rowid_corruption: testcase( m.szMalloc!=0 ); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_CORRUPT_BKPT; } @@ -5013,7 +5039,7 @@ int sqlite3VdbeIdxKeyCompare( return rc; } *res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, pUnpacked, 0); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_OK; } @@ -5180,7 +5206,7 @@ static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ int i; for(i=0; iaMem[i]; - if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem); + if( pMem->zMalloc ) sqlite3VdbeMemReleaseMalloc(pMem); } sqlite3DbFreeNN(db, p); } diff --git a/src/vdbemem.c b/src/vdbemem.c index 184f811da..fd4ab0ccc 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -457,9 +457,10 @@ int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ Mem t; assert( pFunc!=0 ); assert( pMem!=0 ); + assert( pMem->db!=0 ); assert( pFunc->xFinalize!=0 ); assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef ); - assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( sqlite3_mutex_held(pMem->db->mutex) ); memset(&ctx, 0, sizeof(ctx)); memset(&t, 0, sizeof(t)); t.flags = MEM_Null; @@ -467,6 +468,7 @@ int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ ctx.pOut = &t; ctx.pMem = pMem; ctx.pFunc = pFunc; + ctx.enc = ENC(t.db); pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */ assert( (pMem->flags & MEM_Dyn)==0 ); if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); @@ -488,12 +490,14 @@ int sqlite3VdbeMemAggValue(Mem *pAccum, Mem *pOut, FuncDef *pFunc){ assert( pFunc!=0 ); assert( pFunc->xValue!=0 ); assert( (pAccum->flags & MEM_Null)!=0 || pFunc==pAccum->u.pDef ); - assert( pAccum->db==0 || sqlite3_mutex_held(pAccum->db->mutex) ); + assert( pAccum->db!=0 ); + assert( sqlite3_mutex_held(pAccum->db->mutex) ); memset(&ctx, 0, sizeof(ctx)); sqlite3VdbeMemSetNull(pOut); ctx.pOut = pOut; ctx.pMem = pAccum; ctx.pFunc = pFunc; + ctx.enc = ENC(pAccum->db); pFunc->xValue(&ctx); return ctx.isError; } @@ -559,6 +563,14 @@ void sqlite3VdbeMemRelease(Mem *p){ } } +/* Like sqlite3VdbeMemRelease() but faster for cases where we +** know in advance that the Mem is not MEM_Dyn or MEM_Agg. +*/ +void sqlite3VdbeMemReleaseMalloc(Mem *p){ + assert( !VdbeMemDynamic(p) ); + if( p->szMalloc ) vdbeMemClear(p); +} + /* ** Convert a 64-bit IEEE double into a 64-bit signed integer. ** If the double is out of range of a 64-bit signed integer then @@ -1103,6 +1115,13 @@ void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){ ** stored without allocating memory, then it is. If a memory allocation ** is required to store the string, then value of pMem is unchanged. In ** either case, SQLITE_TOOBIG is returned. +** +** The "enc" parameter is the text encoding for the string, or zero +** to store a blob. +** +** If n is negative, then the string consists of all bytes up to but +** excluding the first zero character. The n parameter must be +** non-negative for blobs. */ int sqlite3VdbeMemSetStr( Mem *pMem, /* Memory cell to set to string value */ @@ -1113,11 +1132,12 @@ int sqlite3VdbeMemSetStr( ){ i64 nByte = n; /* New value for pMem->n */ int iLimit; /* Maximum allowed string or blob size */ - u16 flags = 0; /* New value for pMem->flags */ + u16 flags; /* New value for pMem->flags */ assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( enc!=0 || n>=0 ); /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ if( !z ){ @@ -1130,7 +1150,6 @@ int sqlite3VdbeMemSetStr( }else{ iLimit = SQLITE_MAX_LENGTH; } - flags = (enc==0?MEM_Blob:MEM_Str); if( nByte<0 ){ assert( enc!=0 ); if( enc==SQLITE_UTF8 ){ @@ -1138,7 +1157,23 @@ int sqlite3VdbeMemSetStr( }else{ for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){} } - flags |= MEM_Term; + flags= MEM_Str|MEM_Term; + }else if( enc==0 ){ + flags = MEM_Blob; + enc = SQLITE_UTF8; + }else{ + flags = MEM_Str; + } + if( nByte>iLimit ){ + if( xDel && xDel!=SQLITE_TRANSIENT ){ + if( xDel==SQLITE_DYNAMIC ){ + sqlite3DbFree(pMem->db, (void*)z); + }else{ + xDel((void*)z); + } + } + sqlite3VdbeMemSetNull(pMem); + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); } /* The following block sets the new values of Mem.z and Mem.xDel. It @@ -1150,9 +1185,6 @@ int sqlite3VdbeMemSetStr( if( flags&MEM_Term ){ nAlloc += (enc==SQLITE_UTF8?1:2); } - if( nByte>iLimit ){ - return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); - } testcase( nAlloc==0 ); testcase( nAlloc==31 ); testcase( nAlloc==32 ); @@ -1174,16 +1206,7 @@ int sqlite3VdbeMemSetStr( pMem->n = (int)(nByte & 0x7fffffff); pMem->flags = flags; - if( enc ){ - pMem->enc = enc; -#ifdef SQLITE_ENABLE_SESSION - }else if( pMem->db==0 ){ - pMem->enc = SQLITE_UTF8; -#endif - }else{ - assert( pMem->db!=0 ); - pMem->enc = ENC(pMem->db); - } + pMem->enc = enc; #ifndef SQLITE_OMIT_UTF16 if( enc>SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){ @@ -1191,9 +1214,6 @@ int sqlite3VdbeMemSetStr( } #endif - if( nByte>iLimit ){ - return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); - } return SQLITE_OK; } @@ -1471,10 +1491,12 @@ static int valueFromFunction( goto value_from_function_out; } - assert( pCtx->pParse->rc==SQLITE_OK ); + testcase( pCtx->pParse->rc==SQLITE_ERROR ); + testcase( pCtx->pParse->rc==SQLITE_OK ); memset(&ctx, 0, sizeof(ctx)); ctx.pOut = pVal; ctx.pFunc = pFunc; + ctx.enc = ENC(db); pFunc->xSFunc(&ctx, nVal, apVal); if( ctx.isError ){ rc = ctx.isError; @@ -1550,8 +1572,8 @@ static int valueFromExpr( rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx); testcase( rc!=SQLITE_OK ); if( *ppVal ){ - sqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8); - sqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8); + sqlite3VdbeMemCast(*ppVal, aff, enc); + sqlite3ValueApplyAffinity(*ppVal, affinity, enc); } return rc; } diff --git a/src/where.c b/src/where.c index 3bd640cb2..6c2c64d18 100644 --- a/src/where.c +++ b/src/where.c @@ -102,7 +102,7 @@ int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){ } pInner = &pWInfo->a[pWInfo->nLevel-1]; assert( pInner->addrNxt!=0 ); - return pInner->addrNxt; + return pInner->pRJ ? pWInfo->iContinue : pInner->addrNxt; } /* @@ -253,6 +253,30 @@ Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){ return 0; } +/* Allocate memory that is automatically freed when pWInfo is freed. +*/ +void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte){ + WhereMemBlock *pBlock; + pBlock = sqlite3DbMallocRawNN(pWInfo->pParse->db, nByte+sizeof(*pBlock)); + if( pBlock ){ + pBlock->pNext = pWInfo->pMemToFree; + pBlock->sz = nByte; + pWInfo->pMemToFree = pBlock; + pBlock++; + } + return (void*)pBlock; +} +void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte){ + void *pNew = sqlite3WhereMalloc(pWInfo, nByte); + if( pNew && pOld ){ + WhereMemBlock *pOldBlk = (WhereMemBlock*)pOld; + pOldBlk--; + assert( pOldBlk->szsz); + } + return pNew; +} + /* ** Create a new mask for cursor iCursor. ** @@ -306,7 +330,7 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ && (iColumn!=XN_EXPR || sqlite3ExprCompareSkip(pTerm->pExpr->pLeft, pScan->pIdxExpr,iCur)==0) - && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin)) + && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_OuterON)) ){ if( (pTerm->eOperator & WO_EQUIV)!=0 && pScan->nEquivaiCur) @@ -658,6 +682,7 @@ static void translateColumnToCopy( pOp->p1 = pOp->p2 + iRegister; pOp->p2 = pOp->p3; pOp->p3 = 0; + pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */ }else if( pOp->opcode==OP_Rowid ){ pOp->opcode = OP_Sequence; pOp->p1 = iAutoidxCur; @@ -732,14 +757,17 @@ static int termCanDriveIndex( char aff; if( pTerm->leftCursor!=pSrc->iCursor ) return 0; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0; - if( (pSrc->fg.jointype & JT_LEFT) - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - && (pTerm->eOperator & WO_IS) - ){ - /* Cannot use an IS term from the WHERE clause as an index driver for - ** the RHS of a LEFT JOIN. Such a term can only be used if it is from - ** the ON clause. */ - return 0; + assert( (pSrc->fg.jointype & JT_RIGHT)==0 ); + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + return 0; /* See tag-20191211-001 */ + } } if( (pTerm->prereqRight & notReady)!=0 ) return 0; assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); @@ -1080,7 +1108,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( const SrcItem *pTabItem; pLevel = &pWInfo->a[iLevel]; pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; - if( pTabItem->fg.jointype & JT_LEFT ) continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue; pLoop = pLevel->pWLoop; if( NEVER(pLoop==0) ) continue; if( pLoop->prereq & notReady ) continue; @@ -1153,12 +1181,20 @@ static sqlite3_index_info *allocateIndexInfo( assert( pTerm->u.x.leftColumnnCol ); /* tag-20191211-002: WHERE-clause constraints are not useful to the - ** right-hand table of a LEFT JOIN. See tag-20191211-001 for the + ** right-hand table of a LEFT JOIN nor to the either table of a + ** RIGHT JOIN. See tag-20191211-001 for the ** equivalent restriction for ordinary tables. */ - if( (pSrc->fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - ){ - continue; + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + continue; + } } nTerm++; pTerm->wtFlags |= TERM_OK; @@ -1181,7 +1217,7 @@ static sqlite3_index_info *allocateIndexInfo( } /* Virtual tables are unable to deal with NULLS FIRST */ - if( pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL ) break; + if( pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) break; /* First case - a direct column references without a COLLATE operator */ if( pExpr->op==TK_COLUMN && pExpr->iTable==pSrc->iCursor ){ @@ -1211,8 +1247,10 @@ static sqlite3_index_info *allocateIndexInfo( } if( i==n ){ nOrderBy = n; - if( (pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY)) ){ - eDistinct = 1 + ((pWInfo->wctrlFlags & WHERE_DISTINCTBY)!=0); + if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) ){ + eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); + }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ + eDistinct = 1; } } } @@ -1291,7 +1329,7 @@ static sqlite3_index_info *allocateIndexInfo( || (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN && pExpr->iColumn==pExpr->pLeft->iColumn) ); pIdxOrderBy[j].iColumn = pExpr->iColumn; - pIdxOrderBy[j].desc = pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC; + pIdxOrderBy[j].desc = pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC; j++; } pIdxInfo->nOrderBy = j; @@ -1395,7 +1433,7 @@ static int whereKeyStats( #endif assert( pRec!=0 ); assert( pIdx->nSample>0 ); - assert( pRec->nField>0 && pRec->nField<=pIdx->nSampleCol ); + assert( pRec->nField>0 ); /* Do a binary search to find the first sample greater than or equal ** to pRec. If pRec contains a single field, the set of samples to search @@ -1441,7 +1479,7 @@ static int whereKeyStats( ** it is extended to two fields. The duplicates that this creates do not ** cause any problems. */ - nField = pRec->nField; + nField = MIN(pRec->nField, pIdx->nSample); iCol = 0; iSample = pIdx->nSample * nField; do{ @@ -2032,7 +2070,7 @@ void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ memcpy(zType, "....", 5); if( pTerm->wtFlags & TERM_VIRTUAL ) zType[0] = 'V'; if( pTerm->eOperator & WO_EQUIV ) zType[1] = 'E'; - if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) zType[2] = 'L'; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) zType[2] = 'L'; if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C'; if( pTerm->eOperator & WO_SINGLE ){ assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); @@ -2213,15 +2251,7 @@ static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ ** Free a WhereInfo structure */ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ - int i; assert( pWInfo!=0 ); - for(i=0; inLevel; i++){ - WhereLevel *pLevel = &pWInfo->a[i]; - if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE)!=0 ){ - assert( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0 ); - sqlite3DbFree(db, pLevel->u.in.aInLoop); - } - } sqlite3WhereClauseClear(&pWInfo->sWC); while( pWInfo->pLoops ){ WhereLoop *p = pWInfo->pLoops; @@ -2229,6 +2259,11 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ whereLoopDelete(db, p); } assert( pWInfo->pExprMods==0 ); + while( pWInfo->pMemToFree ){ + WhereMemBlock *pNext = pWInfo->pMemToFree->pNext; + sqlite3DbFreeNN(db, pWInfo->pMemToFree); + pWInfo->pMemToFree = pNext; + } sqlite3DbFreeNN(db, pWInfo); } @@ -2597,10 +2632,11 @@ static void whereLoopOutputAdjust( ** ** 2022-03-24: Self-culling only applies if either the extra terms ** are straight comparison operators that are non-true with NULL - ** operand, or if the loop is not a LEFT JOIN. + ** operand, or if the loop is not an OUTER JOIN. */ if( (pTerm->eOperator & 0x3f)!=0 - || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0 + || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype + & (JT_LEFT|JT_LTORJ))==0 ){ pLoop->wsFlags |= WHERE_SELFCULL; } @@ -2807,12 +2843,29 @@ static int whereLoopAddBtreeIndex( if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue; /* tag-20191211-001: Do not allow constraints from the WHERE clause to - ** be used by the right table of a LEFT JOIN. Only constraints in the - ** ON clause are allowed. See tag-20191211-002 for the vtab equivalent. */ - if( (pSrc->fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - ){ - continue; + ** be used by the right table of a LEFT JOIN nor by the left table of a + ** RIGHT JOIN. Only constraints in the ON clause are allowed. + ** See tag-20191211-002 for the vtab equivalent. + ** + ** 2022-06-06: See https://sqlite.org/forum/forumpost/206d99a16dd9212f + ** for an example of a WHERE clause constraints that may not be used on + ** the right table of a RIGHT JOIN because the constraint implies a + ** not-NULL condition on the left table of the RIGHT JOIN. + ** + ** 2022-06-10: The same condition applies to termCanDriveIndex() above. + ** https://sqlite.org/forum/forumpost/51e6959f61 + */ + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + continue; + } } if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){ @@ -3164,23 +3217,26 @@ static int indexMightHelpWithOrderBy( */ static int whereUsablePartialIndex( int iTab, /* The table for which we want an index */ - int isLeft, /* True if iTab is the right table of a LEFT JOIN */ + u8 jointype, /* The JT_* flags on the join */ WhereClause *pWC, /* The WHERE clause of the query */ Expr *pWhere /* The WHERE clause from the partial index */ ){ int i; WhereTerm *pTerm; - Parse *pParse = pWC->pWInfo->pParse; + Parse *pParse; + + if( jointype & JT_LTORJ ) return 0; + pParse = pWC->pWInfo->pParse; while( pWhere->op==TK_AND ){ - if( !whereUsablePartialIndex(iTab,isLeft,pWC,pWhere->pLeft) ) return 0; + if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0; pWhere = pWhere->pRight; } if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0; for(i=0, pTerm=pWC->a; inTerm; i++, pTerm++){ Expr *pExpr; pExpr = pTerm->pExpr; - if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->w.iRightJoinTable==iTab) - && (isLeft==0 || ExprHasProperty(pExpr, EP_FromJoin)) + if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab) + && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON)) && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) && (pTerm->wtFlags & TERM_VNULL)==0 ){ @@ -3289,13 +3345,14 @@ static int whereLoopAddBtree( #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* Automatic indexes */ if( !pBuilder->pOrSet /* Not part of an OR optimization */ - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + && (pWInfo->wctrlFlags & (WHERE_RIGHT_JOIN|WHERE_OR_SUBCLAUSE))==0 && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 && !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */ && !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */ && HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */ && !pSrc->fg.isCorrelated /* Not a correlated subquery */ && !pSrc->fg.isRecursive /* Not a recursive common table expression. */ + && (pSrc->fg.jointype & JT_RIGHT)==0 /* Not the right tab of a RIGHT JOIN */ ){ /* Generate auto-index WhereLoops */ LogEst rLogSize; /* Logarithm of the number of rows in the table */ @@ -3345,9 +3402,8 @@ static int whereLoopAddBtree( for(; rc==SQLITE_OK && pProbe; pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++ ){ - int isLeft = (pSrc->fg.jointype & JT_OUTER)!=0; if( pProbe->pPartIdxWhere!=0 - && !whereUsablePartialIndex(pSrc->iCursor, isLeft, pWC, + && !whereUsablePartialIndex(pSrc->iCursor, pSrc->fg.jointype, pWC, pProbe->pPartIdxWhere) ){ testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */ @@ -3455,7 +3511,14 @@ static int whereLoopAddBtree( } ApplyCostMultiplier(pNew->rRun, pTab->costMult); whereLoopOutputAdjust(pWC, pNew, rSize); - rc = whereLoopInsert(pBuilder, pNew); + if( (pSrc->fg.jointype & JT_RIGHT)!=0 && pProbe->aColExpr ){ + /* Do not do an SCAN of a index-on-expression in a RIGHT JOIN + ** because the cursor used to access the index might not be + ** positioned to the correct row during the right-join no-match + ** loop. */ + }else{ + rc = whereLoopInsert(pBuilder, pNew); + } pNew->nOut = rSize; if( rc ) break; } @@ -3630,6 +3693,7 @@ static int whereLoopAddVirtualOne( *pbIn = 1; assert( (mExclude & WO_IN)==0 ); } + assert( pbRetryLimit || !isLimitTerm(pTerm) ); if( isLimitTerm(pTerm) && *pbIn ){ /* If there is an IN(...) term handled as an == (separate call to ** xFilter for each value on the RHS of the IN) and a LIMIT or @@ -3777,9 +3841,7 @@ int sqlite3_vtab_rhs_value( */ int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; - assert( pHidden->eDistinct==0 - || pHidden->eDistinct==1 - || pHidden->eDistinct==2 ); + assert( pHidden->eDistinct>=0 && pHidden->eDistinct<=3 ); return pHidden->eDistinct; } @@ -3787,15 +3849,26 @@ int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ && !defined(SQLITE_OMIT_VIRTUALTABLE) /* ** Cause the prepared statement that is associated with a call to -** xBestIndex to open write transactions on all attached schemas. +** xBestIndex to potentiall use all schemas. If the statement being +** prepared is read-only, then just start read transactions on all +** schemas. But if this is a write operation, start writes on all +** schemas. +** ** This is used by the (built-in) sqlite_dbpage virtual table. */ -void sqlite3VtabWriteAll(sqlite3_index_info *pIdxInfo){ +void sqlite3VtabUsesAllSchemas(sqlite3_index_info *pIdxInfo){ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; Parse *pParse = pHidden->pParse; int nDb = pParse->db->nDb; int i; - for(i=0; iwriteMask ){ + for(i=0; ipTabList->a + pNew->iTab; iCur = pItem->iCursor; + /* The multi-index OR optimization does not work for RIGHT and FULL JOIN */ + if( pItem->fg.jointype & JT_RIGHT ) return SQLITE_OK; + for(pTerm=pWC->a; pTermeOperator & WO_OR)!=0 && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 @@ -4091,8 +4167,11 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ SrcItem *pEnd = &pTabList->a[pWInfo->nLevel]; sqlite3 *db = pWInfo->pParse->db; int rc = SQLITE_OK; + int bFirstPastRJ = 0; + int hasRightJoin = 0; WhereLoop *pNew; + /* Loop over the tables in the join, from left to right */ pNew = pBuilder->pNew; whereLoopInit(pNew); @@ -4102,18 +4181,30 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ pNew->iTab = iTab; pBuilder->iPlanLimit += SQLITE_QUERY_PLANNER_LIMIT_INCR; pNew->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor); - if( (pItem->fg.jointype & (JT_LEFT|JT_CROSS))!=0 ){ - /* This condition is true when pItem is the FROM clause term on the - ** right-hand-side of a LEFT or CROSS JOIN. */ - mPrereq = mPrior; - }else{ + if( bFirstPastRJ + || (pItem->fg.jointype & (JT_OUTER|JT_CROSS|JT_LTORJ))!=0 + ){ + /* Add prerequisites to prevent reordering of FROM clause terms + ** across CROSS joins and outer joins. The bFirstPastRJ boolean + ** prevents the right operand of a RIGHT JOIN from being swapped with + ** other elements even further to the right. + ** + ** The JT_LTORJ case and the hasRightJoin flag work together to + ** prevent FROM-clause terms from moving from the right side of + ** a LEFT JOIN over to the left side of that join if the LEFT JOIN + ** is itself on the left side of a RIGHT JOIN. + */ + if( pItem->fg.jointype & JT_LTORJ ) hasRightJoin = 1; + mPrereq |= mPrior; + bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0; + }else if( !hasRightJoin ){ mPrereq = 0; } #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pItem->pTab) ){ SrcItem *p; for(p=&pItem[1]; pfg.jointype & (JT_LEFT|JT_CROSS)) ){ + if( mUnusable || (p->fg.jointype & (JT_OUTER|JT_CROSS)) ){ mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor); } } @@ -4238,7 +4329,9 @@ static i8 wherePathSatisfiesOrderBy( pLoop = pLast; } if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ - if( pLoop->u.vtab.isOrdered && (wctrlFlags & WHERE_DISTINCTBY)==0 ){ + if( pLoop->u.vtab.isOrdered + && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) + ){ obSat = obDone; } break; @@ -4416,16 +4509,18 @@ static i8 wherePathSatisfiesOrderBy( /* Make sure the sort order is compatible in an ORDER BY clause. ** Sort order is irrelevant for a GROUP BY clause. */ if( revSet ){ - if( (rev ^ revIdx)!=(pOrderBy->a[i].sortFlags&KEYINFO_ORDER_DESC) ){ + if( (rev ^ revIdx) + != (pOrderBy->a[i].fg.sortFlags&KEYINFO_ORDER_DESC) + ){ isMatch = 0; } }else{ - rev = revIdx ^ (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC); + rev = revIdx ^ (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC); if( rev ) *pRevMask |= MASKBIT(iLoop); revSet = 1; } } - if( isMatch && (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL) ){ + if( isMatch && (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL) ){ if( j==pLoop->u.btree.nEq ){ pLoop->wsFlags |= WHERE_BIGNULL_SORT; }else{ @@ -4505,7 +4600,7 @@ static i8 wherePathSatisfiesOrderBy( ** SELECT * FROM t1 GROUP BY y,x ORDER BY y,x; -- IsSorted()==0 */ int sqlite3WhereIsSorted(WhereInfo *pWInfo){ - assert( pWInfo->wctrlFlags & WHERE_GROUPBY ); + assert( pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY) ); assert( pWInfo->wctrlFlags & WHERE_SORTBYGROUP ); return pWInfo->sorted; } @@ -4906,12 +5001,12 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ } pWInfo->bOrderedInnerLoop = 0; if( pWInfo->pOrderBy ){ + pWInfo->nOBSat = pFrom->isOrdered; if( pWInfo->wctrlFlags & WHERE_DISTINCTBY ){ if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){ pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; } }else{ - pWInfo->nOBSat = pFrom->isOrdered; pWInfo->revMask = pFrom->revLoop; if( pWInfo->nOBSat<=0 ){ pWInfo->nOBSat = 0; @@ -4990,7 +5085,11 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ pItem = pWInfo->pTabList->a; pTab = pItem->pTab; if( IsVirtual(pTab) ) return 0; - if( pItem->fg.isIndexedBy ) return 0; + if( pItem->fg.isIndexedBy || pItem->fg.notIndexed ){ + testcase( pItem->fg.isIndexedBy ); + testcase( pItem->fg.notIndexed ); + return 0; + } iCur = pItem->iCursor; pWC = &pWInfo->sWC; pLoop = pBuilder->pNew; @@ -5163,7 +5262,7 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( WhereLoop *pLoop; pLoop = pWInfo->a[i].pWLoop; pItem = &pWInfo->pTabList->a[pLoop->iTab]; - if( (pItem->fg.jointype & JT_LEFT)==0 ) continue; + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ) continue; if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)==0 && (pLoop->wsFlags & WHERE_ONEROW)==0 ){ @@ -5173,8 +5272,8 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( pEnd = pWInfo->sWC.a + pWInfo->sWC.nTerm; for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ - if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - || pTerm->pExpr->w.iRightJoinTable!=pItem->iCursor + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON) + || pTerm->pExpr->w.iJoin!=pItem->iCursor ){ break; } @@ -5405,7 +5504,7 @@ WhereInfo *sqlite3WhereBegin( ** field (type Bitmask) it must be aligned on an 8-byte boundary on ** some architectures. Hence the ROUND8() below. */ - nByteWInfo = ROUND8(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); + nByteWInfo = ROUND8P(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop)); if( db->mallocFailed ){ sqlite3DbFree(db, pWInfo); @@ -5495,7 +5594,7 @@ WhereInfo *sqlite3WhereBegin( /* Analyze all of the subexpressions. */ sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC); sqlite3WhereAddLimit(&pWInfo->sWC, pLimit); - if( db->mallocFailed ) goto whereBeginError; + if( pParse->nErr ) goto whereBeginError; /* Special case: WHERE terms that do not refer to any tables in the join ** (constant expressions). Evaluate each such term, and jump over all the @@ -5727,8 +5826,10 @@ WhereInfo *sqlite3WhereBegin( /* noop */ }else #endif - if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 - && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){ + if( ((pLoop->wsFlags & WHERE_IDX_ONLY)==0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0) + || (pTabItem->fg.jointype & (JT_LTORJ|JT_RIGHT))!=0 + ){ int op = OP_OpenRead; if( pWInfo->eOnePass!=ONEPASS_OFF ){ op = OP_OpenWrite; @@ -5797,6 +5898,7 @@ WhereInfo *sqlite3WhereBegin( iIndexCur = pParse->nTab++; } pLevel->iIdxCur = iIndexCur; + assert( pIx!=0 ); assert( pIx->pSchema==pTab->pSchema ); assert( iIndexCur>=0 ); if( op ){ @@ -5830,6 +5932,37 @@ WhereInfo *sqlite3WhereBegin( } } if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb); + if( (pTabItem->fg.jointype & JT_RIGHT)!=0 + && (pLevel->pRJ = sqlite3WhereMalloc(pWInfo, sizeof(WhereRightJoin)))!=0 + ){ + WhereRightJoin *pRJ = pLevel->pRJ; + pRJ->iMatch = pParse->nTab++; + pRJ->regBloom = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 65536, pRJ->regBloom); + pRJ->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, pRJ->regReturn); + assert( pTab==pTabItem->pTab ); + if( HasRowid(pTab) ){ + KeyInfo *pInfo; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, 1); + pInfo = sqlite3KeyInfoAlloc(pParse->db, 1, 0); + if( pInfo ){ + pInfo->aColl[0] = 0; + pInfo->aSortFlags[0] = 0; + sqlite3VdbeAppendP4(v, pInfo, P4_KEYINFO); + } + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, pPk->nKeyCol); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + /* The nature of RIGHT JOIN processing is such that it messes up + ** the output order. So omit any ORDER BY/GROUP BY elimination + ** optimizations. We need to do an actual sort for RIGHT JOIN. */ + pWInfo->nOBSat = 0; + pWInfo->eDistinct = WHERE_DISTINCT_UNORDERED; + } } pWInfo->iTop = sqlite3VdbeCurrentAddr(v); if( db->mallocFailed ) goto whereBeginError; @@ -5841,9 +5974,20 @@ WhereInfo *sqlite3WhereBegin( for(ii=0; iinErr ) goto whereBeginError; pLevel = &pWInfo->a[ii]; wsFlags = pLevel->pWLoop->wsFlags; + pSrc = &pTabList->a[pLevel->iFrom]; + if( pSrc->fg.isMaterialized ){ + if( pSrc->fg.isCorrelated ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub); + }else{ + int iOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub); + sqlite3VdbeJumpHere(v, iOnce); + } + } if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){ if( (wsFlags & WHERE_AUTO_INDEX)!=0 ){ #ifndef SQLITE_OMIT_AUTOMATIC_INDEX @@ -5935,6 +6079,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ SrcList *pTabList = pWInfo->pTabList; sqlite3 *db = pParse->db; int iEnd = sqlite3VdbeCurrentAddr(v); + int nRJ = 0; /* Generate loop termination code. */ @@ -5942,6 +6087,17 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ for(i=pWInfo->nLevel-1; i>=0; i--){ int addr; pLevel = &pWInfo->a[i]; + if( pLevel->pRJ ){ + /* Terminate the subroutine that forms the interior of the loop of + ** the RIGHT JOIN table */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeResolveLabel(v, pLevel->addrCont); + pLevel->addrCont = 0; + pRJ->endSubrtn = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_Return, pRJ->regReturn, pRJ->addrSubrtn, 1); + VdbeCoverage(v); + nRJ++; + } pLoop = pLevel->pWLoop; if( pLevel->op!=OP_Noop ){ #ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT @@ -5969,7 +6125,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } #endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */ /* The common case: Advance to the next row */ - sqlite3VdbeResolveLabel(v, pLevel->addrCont); + if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont); sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3); sqlite3VdbeChangeP5(v, pLevel->p5); VdbeCoverage(v); @@ -5984,7 +6140,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ #ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT if( addrSeek ) sqlite3VdbeJumpHere(v, addrSeek); #endif - }else{ + }else if( pLevel->addrCont ){ sqlite3VdbeResolveLabel(v, pLevel->addrCont); } if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){ @@ -6034,6 +6190,10 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } } sqlite3VdbeResolveLabel(v, pLevel->addrBrk); + if( pLevel->pRJ ){ + sqlite3VdbeAddOp3(v, OP_Return, pLevel->pRJ->regReturn, 0, 1); + VdbeCoverage(v); + } if( pLevel->addrSkip ){ sqlite3VdbeGoto(v, pLevel->addrSkip); VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName)); @@ -6077,12 +6237,8 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ pWInfo->pTabList->a[pLevel->iFrom].pTab->zName)); } - /* The "break" point is here, just past the end of the outer loop. - ** Set it. - */ - sqlite3VdbeResolveLabel(v, pWInfo->iBreak); - assert( pWInfo->nLevel<=pTabList->nSrc ); + if( pWInfo->pExprMods ) whereUndoExprMods(pWInfo); for(i=0, pLevel=pWInfo->a; inLevel; i++, pLevel++){ int k, last; VdbeOp *pOp, *pLastOp; @@ -6092,6 +6248,15 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ assert( pTab!=0 ); pLoop = pLevel->pWLoop; + /* Do RIGHT JOIN processing. Generate code that will output the + ** unmatched rows of the right operand of the RIGHT JOIN with + ** all of the columns of the left operand set to NULL. + */ + if( pLevel->pRJ ){ + sqlite3WhereRightJoinLoop(pWInfo, i, pLevel); + continue; + } + /* For a co-routine, change all OP_Column references to the table of ** the co-routine into OP_Copy of result contained in a register. ** OP_Rowid becomes OP_Null. @@ -6103,29 +6268,6 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ continue; } -#ifdef SQLITE_ENABLE_EARLY_CURSOR_CLOSE - /* Close all of the cursors that were opened by sqlite3WhereBegin. - ** Except, do not close cursors that will be reused by the OR optimization - ** (WHERE_OR_SUBCLAUSE). And do not close the OP_OpenWrite cursors - ** created for the ONEPASS optimization. - */ - if( (pTab->tabFlags & TF_Ephemeral)==0 - && !IsView(pTab) - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 - ){ - int ws = pLoop->wsFlags; - if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){ - sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); - } - if( (ws & WHERE_INDEXED)!=0 - && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0 - && pLevel->iIdxCur!=pWInfo->aiCurOnePass[1] - ){ - sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur); - } - } -#endif - /* If this scan uses an index, make VDBE code substitutions to read data ** from the index instead of from the table where possible. In some cases ** this optimization prevents the table from ever being read, which can @@ -6226,10 +6368,15 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } } + /* The "break" point is here, just past the end of the outer loop. + ** Set it. + */ + sqlite3VdbeResolveLabel(v, pWInfo->iBreak); + /* Final cleanup */ - if( pWInfo->pExprMods ) whereUndoExprMods(pWInfo); pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); + pParse->withinRJSubrtn -= nRJ; return; } diff --git a/src/whereInt.h b/src/whereInt.h index 3fc39f6b4..bc96ff7e6 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -32,6 +32,28 @@ typedef struct WhereLoopBuilder WhereLoopBuilder; typedef struct WhereScan WhereScan; typedef struct WhereOrCost WhereOrCost; typedef struct WhereOrSet WhereOrSet; +typedef struct WhereMemBlock WhereMemBlock; +typedef struct WhereRightJoin WhereRightJoin; + +/* +** This object is a header on a block of allocated memory that will be +** automatically freed when its WInfo oject is destructed. +*/ +struct WhereMemBlock { + WhereMemBlock *pNext; /* Next block in the chain */ + u64 sz; /* Bytes of space */ +}; + +/* +** Extra information attached to a WhereLevel that is a RIGHT JOIN. +*/ +struct WhereRightJoin { + int iMatch; /* Cursor used to determine prior matched rows */ + int regBloom; /* Bloom filter for iRJMatch */ + int regReturn; /* Return register for the interior subroutine */ + int addrSubrtn; /* Starting address for the interior subroutine */ + int endSubrtn; /* The last opcode in the interior subroutine */ +}; /* ** This object contains information needed to implement a single nested @@ -65,6 +87,7 @@ struct WhereLevel { int addrLikeRep; /* LIKE range processing address */ #endif int regFilter; /* Bloom filter */ + WhereRightJoin *pRJ; /* Extra information for RIGHT JOIN */ u8 iFrom; /* Which entry in the FROM clause */ u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */ int p1, p2; /* Operands of the opcode used to end the loop */ @@ -478,6 +501,7 @@ struct WhereInfo { int iEndWhere; /* End of the WHERE clause itself */ WhereLoop *pLoops; /* List of all WhereLoop objects */ WhereExprMod *pExprMods; /* Expression modifications */ + WhereMemBlock *pMemToFree;/* Memory to free when this object destroyed */ Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ WhereClause sWC; /* Decomposition of the WHERE clause */ WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ @@ -503,6 +527,8 @@ WhereTerm *sqlite3WhereFindTerm( u32 op, /* Mask of WO_xx values describing operator */ Index *pIdx /* Must be compatible with this index, if not NULL */ ); +void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte); +void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte); /* wherecode.c: */ #ifndef SQLITE_OMIT_EXPLAIN @@ -539,6 +565,11 @@ Bitmask sqlite3WhereCodeOneLoopStart( WhereLevel *pLevel, /* The current level pointer */ Bitmask notReady /* Which tables are currently available */ ); +SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +); /* whereexpr.c: */ void sqlite3WhereClauseInit(WhereClause*,WhereInfo*); @@ -581,8 +612,9 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); #define WO_AND 0x0400 /* Two or more AND-connected terms */ #define WO_EQUIV 0x0800 /* Of the form A==B, both columns */ #define WO_NOOP 0x1000 /* This term does not restrict search space */ +#define WO_ROWVAL 0x2000 /* A row-value term */ -#define WO_ALL 0x1fff /* Mask of all possible WO_* values */ +#define WO_ALL 0x3fff /* Mask of all possible WO_* values */ #define WO_SINGLE 0x01ff /* Mask of all non-compound WO_* values */ /* diff --git a/src/wherecode.c b/src/wherecode.c index d112de555..04f1c374d 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -204,6 +204,9 @@ int sqlite3WhereExplainOneScan( pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr); } #endif + if( pItem->fg.jointype & JT_LEFT ){ + sqlite3_str_appendf(&str, " LEFT-JOIN"); + } #ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS if( pLoop->nOut>=10 ){ sqlite3_str_appendf(&str, " (~%llu rows)", @@ -347,7 +350,7 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ int nLoop = 0; assert( pTerm!=0 ); while( (pTerm->wtFlags & TERM_CODED)==0 - && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin)) + && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_OuterON)) && (pLevel->notReady & pTerm->prereqAll)==0 ){ if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){ @@ -608,16 +611,21 @@ static int codeEqualityTerm( if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){ eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab); }else{ - sqlite3 *db = pParse->db; - pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); - - if( !db->mallocFailed ){ + Expr *pExpr = pTerm->pExpr; + if( pExpr->iTable==0 || !ExprHasProperty(pExpr, EP_Subrtn) ){ + sqlite3 *db = pParse->db; + pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); + if( !db->mallocFailed ){ + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap,&iTab); + pExpr->iTable = iTab; + } + sqlite3ExprDelete(db, pX); + }else{ aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab); - pTerm->pExpr->iTable = iTab; } - sqlite3ExprDelete(db, pX); - pX = pTerm->pExpr; + pX = pExpr; } if( eType==IN_INDEX_INDEX_DESC ){ @@ -640,8 +648,9 @@ static int codeEqualityTerm( i = pLevel->u.in.nIn; pLevel->u.in.nIn += nEq; pLevel->u.in.aInLoop = - sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop, - sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); + sqlite3WhereRealloc(pTerm->pWC->pWInfo, + pLevel->u.in.aInLoop, + sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); pIn = pLevel->u.in.aInLoop; if( pIn ){ int iMap = 0; /* Index in aiMap[] */ @@ -1062,8 +1071,8 @@ static void codeCursorHint( */ if( pTabItem->fg.jointype & JT_LEFT ){ Expr *pExpr = pTerm->pExpr; - if( !ExprHasProperty(pExpr, EP_FromJoin) - || pExpr->w.iRightJoinTable!=pTabItem->iCursor + if( !ExprHasProperty(pExpr, EP_OuterON) + || pExpr->w.iJoin!=pTabItem->iCursor ){ sWalker.eCode = 0; sWalker.xExprCallback = codeCursorHintIsOrFunction; @@ -1071,7 +1080,7 @@ static void codeCursorHint( if( sWalker.eCode ) continue; } }else{ - if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) continue; } /* All terms in pWLoop->aLTerm[] except pEndRange are used to initialize @@ -1119,13 +1128,21 @@ static void codeCursorHint( ** ** OP_DeferredSeek $iCur $iRowid ** +** Which causes a seek on $iCur to the row with rowid $iRowid. +** ** However, if the scan currently being coded is a branch of an OR-loop and -** the statement currently being coded is a SELECT, then P3 of OP_DeferredSeek -** is set to iIdxCur and P4 is set to point to an array of integers -** containing one entry for each column of the table cursor iCur is open -** on. For each table column, if the column is the i'th column of the -** index, then the corresponding array entry is set to (i+1). If the column -** does not appear in the index at all, the array entry is set to 0. +** the statement currently being coded is a SELECT, then additional information +** is added that might allow OP_Column to omit the seek and instead do its +** lookup on the index, thus avoiding an expensive seek operation. To +** enable this optimization, the P3 of OP_DeferredSeek is set to iIdxCur +** and P4 is set to an array of integers containing one entry for each column +** in the table. For each table column, if the column is the i'th +** column of the index, then the corresponding array entry is set to (i+1). +** If the column does not appear in the index at all, the array entry is set +** to 0. The OP_Column opcode can check this array to see if the column it +** wants is in the index and if it is, it will substitute the index cursor +** and column number and continue with those new values, rather than seeking +** the table cursor. */ static void codeDeferredSeek( WhereInfo *pWInfo, /* Where clause context */ @@ -1141,7 +1158,7 @@ static void codeDeferredSeek( pWInfo->bDeferredSeek = 1; sqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur); - if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE) + if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) && DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask) ){ int i; @@ -1241,7 +1258,6 @@ static int whereIndexExprTransNode(Walker *p, Expr *pExpr){ pExpr->op = TK_COLUMN; pExpr->iTable = pX->iIdxCur; pExpr->iColumn = pX->iIdxCol; - testcase( ExprHasProperty(pExpr, EP_Skip) ); testcase( ExprHasProperty(pExpr, EP_Unlikely) ); ExprClearProperty(pExpr, EP_Skip|EP_Unlikely|EP_WinFunc|EP_Subrtn); pExpr->y.pTab = 0; @@ -1497,7 +1513,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** initialize a memory cell that records if this table matches any ** row of the left table of the join. */ - assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE) + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) || pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0 ); if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ @@ -1508,7 +1524,10 @@ Bitmask sqlite3WhereCodeOneLoopStart( /* Compute a safe address to jump to if we discover that the table for ** this loop is empty and can never contribute content. */ - for(j=iLevel; j>0 && pWInfo->a[j].iLeftJoin==0; j--){} + for(j=iLevel; j>0; j--){ + if( pWInfo->a[j].iLeftJoin ) break; + if( pWInfo->a[j].pRJ ) break; + } addrHalt = pWInfo->a[j].addrBrk; /* Special case of a FROM clause subquery implemented as a co-routine */ @@ -2135,7 +2154,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( /* Seek the table cursor, if required */ omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0; + && (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0; if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ }else if( HasRowid(pIdx->pTable) ){ @@ -2169,7 +2188,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** move forward to the next index. ** https://sqlite.org/src/info/4e8e4857d32d401f */ - if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){ + if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ){ whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo); } @@ -2188,7 +2207,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( /* The following assert() is not a requirement, merely an observation: ** The OR-optimization doesn't work for the right hand table of ** a LEFT JOIN: */ - assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ); + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ); } /* Record the instruction used to terminate the loop. */ @@ -2392,7 +2411,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( Expr *pDelete; /* Local copy of OR clause term */ int jmp1 = 0; /* Address of jump operation */ testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pOrExpr, EP_FromJoin) + && !ExprHasProperty(pOrExpr, EP_OuterON) ); /* See TH3 vtab25.400 and ticket 614b25314c766238 */ pDelete = pOrExpr = sqlite3ExprDup(db, pOrExpr, 0); if( db->mallocFailed ){ @@ -2530,6 +2549,14 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeGoto(v, pLevel->addrBrk); sqlite3VdbeResolveLabel(v, iLoopBody); + /* Set the P2 operand of the OP_Return opcode that will end the current + ** loop to point to this spot, which is the top of the next containing + ** loop. The byte-code formatter will use that P2 value as a hint to + ** indent everything in between the this point and the final OP_Return. + ** See tag-20220407a in vdbe.c and shell.c */ + assert( pLevel->op==OP_Return ); + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + if( pWInfo->nLevel>1 ){ sqlite3StackFree(db, pOrTab); } if( !untestedTerms ) disableTerm(pLevel, pTerm); }else @@ -2592,10 +2619,22 @@ Bitmask sqlite3WhereCodeOneLoopStart( } pE = pTerm->pExpr; assert( pE!=0 ); - if( (pTabItem->fg.jointype&JT_LEFT) && !ExprHasProperty(pE,EP_FromJoin) ){ - continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){ + if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){ + /* Defer processing WHERE clause constraints until after outer + ** join processing. tag-20220513a */ + continue; + }else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT + && !ExprHasProperty(pE,EP_OuterON) ){ + continue; + }else{ + Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin); + if( m & pLevel->notReady ){ + /* An ON clause that is not ripe */ + continue; + } + } } - if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){ iNext = 2; continue; @@ -2654,7 +2693,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; if( (pTerm->eOperator & WO_EQUIV)==0 ) continue; if( pTerm->leftCursor!=iCur ) continue; - if( pTabItem->fg.jointype & JT_LEFT ) continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue; pE = pTerm->pExpr; #ifdef WHERETRACE_ENABLED /* 0x800 */ if( sqlite3WhereTrace & 0x800 ){ @@ -2662,7 +2701,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); } #endif - assert( !ExprHasProperty(pE, EP_FromJoin) ); + assert( !ExprHasProperty(pE, EP_OuterON) ); assert( (pTerm->prereqRight & pLevel->notReady)!=0 ); assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.x.leftColumn, notReady, @@ -2685,6 +2724,47 @@ Bitmask sqlite3WhereCodeOneLoopStart( pAlt->wtFlags |= TERM_CODED; } + /* For a RIGHT OUTER JOIN, record the fact that the current row has + ** been matched at least once. + */ + if( pLevel->pRJ ){ + Table *pTab; + int nPk; + int r; + int jmp1 = 0; + WhereRightJoin *pRJ = pLevel->pRJ; + + /* pTab is the right-hand table of the RIGHT JOIN. Generate code that + ** will record that the current row of that table has been matched at + ** least once. This is accomplished by storing the PK for the row in + ** both the iMatch index and the regBloom Bloom filter. + */ + pTab = pWInfo->pTabList->a[pLevel->iFrom].pTab; + if( HasRowid(pTab) ){ + r = sqlite3GetTempRange(pParse, 2); + sqlite3ExprCodeGetColumnOfTable(v, pTab, pLevel->iTabCur, -1, r+1); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + r = sqlite3GetTempRange(pParse, nPk+1); + for(iPk=0; iPkaiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+1+iPk); + } + } + jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, 0, r+1, nPk); + VdbeCoverage(v); + VdbeComment((v, "match against %s", pTab->zName)); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r+1, nPk, r); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pRJ->iMatch, r, r+1, nPk); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pRJ->regBloom, 0, r+1, nPk); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3VdbeJumpHere(v, jmp1); + sqlite3ReleaseTempRange(pParse, r, nPk+1); + } + /* For a LEFT OUTER JOIN, generate code that will record the fact that ** at least one row of the right table has matched the left table. */ @@ -2692,6 +2772,30 @@ Bitmask sqlite3WhereCodeOneLoopStart( pLevel->addrFirst = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin); VdbeComment((v, "record LEFT JOIN hit")); + if( pLevel->pRJ==0 ){ + goto code_outer_join_constraints; /* WHERE clause constraints */ + } + } + + if( pLevel->pRJ ){ + /* Create a subroutine used to process all interior loops and code + ** of the RIGHT JOIN. During normal operation, the subroutine will + ** be in-line with the rest of the code. But at the end, a separate + ** loop will run that invokes this subroutine for unmatched rows + ** of pTab, with all tables to left begin set to NULL. + */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn); + pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v); + assert( pParse->withinRJSubrtn < 255 ); + pParse->withinRJSubrtn++; + + /* WHERE clause constraints must be deferred until after outer join + ** row elimination has completed, since WHERE clause constraints apply + ** to the results of the OUTER JOIN. The following loop generates the + ** appropriate WHERE clause constraint checks. tag-20220513a. + */ + code_outer_join_constraints: for(pTerm=pWC->a, j=0; jnBase; j++, pTerm++){ testcase( pTerm->wtFlags & TERM_VIRTUAL ); testcase( pTerm->wtFlags & TERM_CODED ); @@ -2700,6 +2804,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( assert( pWInfo->untestedTerms ); continue; } + if( pTabItem->fg.jointype & JT_LTORJ ) continue; assert( pTerm->pExpr ); sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); pTerm->wtFlags |= TERM_CODED; @@ -2719,3 +2824,93 @@ Bitmask sqlite3WhereCodeOneLoopStart( #endif return pLevel->notReady; } + +/* +** Generate the code for the loop that finds all non-matched terms +** for a RIGHT JOIN. +*/ +SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +){ + Parse *pParse = pWInfo->pParse; + Vdbe *v = pParse->pVdbe; + WhereRightJoin *pRJ = pLevel->pRJ; + Expr *pSubWhere = 0; + WhereClause *pWC = &pWInfo->sWC; + WhereInfo *pSubWInfo; + WhereLoop *pLoop = pLevel->pWLoop; + SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + SrcList sFrom; + Bitmask mAll = 0; + int k; + + ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pTab->zName)); + sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, + pRJ->regReturn); + for(k=0; ka[k].pWLoop->maskSelf; + sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); + iIdxCur = pWInfo->a[k].iIdxCur; + if( iIdxCur ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } + } + if( (pTabItem->fg.jointype & JT_LTORJ)==0 ){ + mAll |= pLoop->maskSelf; + for(k=0; knTerm; k++){ + WhereTerm *pTerm = &pWC->a[k]; + if( (pTerm->wtFlags & (TERM_VIRTUAL|TERM_SLICE))!=0 + && pTerm->eOperator!=WO_ROWVAL + ){ + break; + } + if( pTerm->prereqAll & ~mAll ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue; + pSubWhere = sqlite3ExprAnd(pParse, pSubWhere, + sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); + } + } + sFrom.nSrc = 1; + sFrom.nAlloc = 1; + memcpy(&sFrom.a[0], pTabItem, sizeof(SrcItem)); + sFrom.a[0].fg.jointype = 0; + assert( pParse->withinRJSubrtn < 100 ); + pParse->withinRJSubrtn++; + pSubWInfo = sqlite3WhereBegin(pParse, &sFrom, pSubWhere, 0, 0, 0, + WHERE_RIGHT_JOIN, 0); + if( pSubWInfo ){ + int iCur = pLevel->iTabCur; + int r = ++pParse->nMem; + int nPk; + int jmp; + int addrCont = sqlite3WhereContinueLabel(pSubWInfo); + Table *pTab = pTabItem->pTab; + if( HasRowid(pTab) ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, r); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + pParse->nMem += nPk - 1; + for(iPk=0; iPkaiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk); + } + } + jmp = sqlite3VdbeAddOp4Int(v, OP_Filter, pRJ->regBloom, 0, r, nPk); + VdbeCoverage(v); + sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, addrCont, r, nPk); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, jmp); + sqlite3VdbeAddOp2(v, OP_Gosub, pRJ->regReturn, pRJ->addrSubrtn); + sqlite3WhereEnd(pSubWInfo); + } + sqlite3ExprDelete(pParse->db, pSubWhere); + ExplainQueryPlanPop(pParse); + assert( pParse->withinRJSubrtn>0 ); + pParse->withinRJSubrtn--; +} diff --git a/src/whereexpr.c b/src/whereexpr.c index 19dd886de..8c9233ebd 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -64,7 +64,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ if( pWC->nTerm>=pWC->nSlot ){ WhereTerm *pOld = pWC->a; sqlite3 *db = pWC->pWInfo->pParse->db; - pWC->a = sqlite3DbMallocRawNN(db, sizeof(pWC->a[0])*pWC->nSlot*2 ); + pWC->a = sqlite3WhereMalloc(pWC->pWInfo, sizeof(pWC->a[0])*pWC->nSlot*2 ); if( pWC->a==0 ){ if( wtFlags & TERM_DYNAMIC ){ sqlite3ExprDelete(db, p); @@ -73,10 +73,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ return 0; } memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm); - if( pOld!=pWC->aStatic ){ - sqlite3DbFree(db, pOld); - } - pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); + pWC->nSlot = pWC->nSlot*2; } pTerm = &pWC->a[idx = pWC->nTerm++]; if( (wtFlags & TERM_VIRTUAL)==0 ) pWC->nBase = pWC->nTerm; @@ -464,9 +461,9 @@ static int isAuxiliaryVtabOperator( ** a join, then transfer the appropriate markings over to derived. */ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ - if( pDerived ){ - pDerived->flags |= pBase->flags & EP_FromJoin; - pDerived->w.iRightJoinTable = pBase->w.iRightJoinTable; + if( pDerived && ExprHasProperty(pBase, EP_OuterON|EP_InnerON) ){ + pDerived->flags |= pBase->flags & (EP_OuterON|EP_InnerON); + pDerived->w.iJoin = pBase->w.iJoin; } } @@ -920,7 +917,7 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr){ CollSeq *pColl; if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; - if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0; + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; aff1 = sqlite3ExprAffinity(pExpr->pLeft); aff2 = sqlite3ExprAffinity(pExpr->pRight); if( aff1!=aff2 @@ -951,7 +948,9 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ int i; for(i=0; inSrc; i++){ mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect); - mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn); + if( pSrc->a[i].fg.isUsing==0 ){ + mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].u3.pOn); + } if( pSrc->a[i].fg.isTabFunc ){ mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg); } @@ -1106,18 +1105,32 @@ static void exprAnalyze( if( prereqAll!=sqlite3WhereExprUsageNN(pMaskSet, pExpr) ){ printf("\n*** Incorrect prereqAll computed for:\n"); sqlite3TreeViewExpr(0,pExpr,0); - abort(); + assert( 0 ); } #endif - if( ExprHasProperty(pExpr, EP_FromJoin) ){ - Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iRightJoinTable); - prereqAll |= x; - extraRight = x-1; /* ON clause terms may not be used with an index - ** on left table of a LEFT JOIN. Ticket #3015 */ - if( (prereqAll>>1)>=x ){ - sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); - return; + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) ){ + Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + prereqAll |= x; + extraRight = x-1; /* ON clause terms may not be used with an index + ** on left table of a LEFT JOIN. Ticket #3015 */ + if( (prereqAll>>1)>=x ){ + sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); + return; + } + }else if( (prereqAll>>1)>=x ){ + /* The ON clause of an INNER JOIN references a table to its right. + ** Most other SQL database engines raise an error. But SQLite versions + ** 3.0 through 3.38 just put the ON clause constraint into the WHERE + ** clause and carried on. Beginning with 3.39, raise an error only + ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite + ** more like other systems, and also preserves legacy. */ + if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); + return; + } + ExprClearProperty(pExpr, EP_InnerON); } } pTerm->prereqAll = prereqAll; @@ -1185,7 +1198,7 @@ static void exprAnalyze( pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask; }else if( op==TK_ISNULL - && !ExprHasProperty(pExpr,EP_FromJoin) + && !ExprHasProperty(pExpr,EP_OuterON) && 0==sqlite3ExprCanBeNull(pLeft) ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); @@ -1256,7 +1269,7 @@ static void exprAnalyze( else if( pExpr->op==TK_NOTNULL ){ if( pExpr->pLeft->op==TK_COLUMN && pExpr->pLeft->iColumn>=0 - && !ExprHasProperty(pExpr, EP_FromJoin) + && !ExprHasProperty(pExpr, EP_OuterON) ){ Expr *pNewExpr; Expr *pLeft = pExpr->pLeft; @@ -1404,7 +1417,7 @@ static void exprAnalyze( } pTerm = &pWC->a[idxTerm]; pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */ - pTerm->eOperator = 0; + pTerm->eOperator = WO_ROWVAL; } /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create @@ -1460,9 +1473,9 @@ static void exprAnalyze( Expr *pNewExpr; pNewExpr = sqlite3PExpr(pParse, TK_MATCH, 0, sqlite3ExprDup(db, pRight, 0)); - if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ - ExprSetProperty(pNewExpr, EP_FromJoin); - pNewExpr->w.iRightJoinTable = pExpr->w.iRightJoinTable; + if( ExprHasProperty(pExpr, EP_OuterON) && pNewExpr ){ + ExprSetProperty(pNewExpr, EP_OuterON); + pNewExpr->w.iJoin = pExpr->w.iJoin; } idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); @@ -1605,7 +1618,7 @@ void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ /* This term is a vector operation that has been decomposed into ** other, subsequent terms. It can be ignored. See tag-20220128a */ assert( pWC->a[ii].wtFlags & TERM_VIRTUAL ); - assert( pWC->a[ii].eOperator==0 ); + assert( pWC->a[ii].eOperator==WO_ROWVAL ); continue; } if( pWC->a[ii].leftCursor!=iCsr ) return; @@ -1617,7 +1630,7 @@ void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ Expr *pExpr = pOrderBy->a[ii].pExpr; if( pExpr->op!=TK_COLUMN ) return; if( pExpr->iTable!=iCsr ) return; - if( pOrderBy->a[ii].sortFlags & KEYINFO_ORDER_BIGNULL ) return; + if( pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) return; } } @@ -1684,9 +1697,6 @@ void sqlite3WhereClauseClear(WhereClause *pWC){ a++; } } - if( pWC->a!=pWC->aStatic ){ - sqlite3DbFree(db, pWC->a); - } } @@ -1813,6 +1823,7 @@ void sqlite3WhereTabFuncArgs( if( pArgs==0 ) return; for(j=k=0; jnExpr; j++){ Expr *pRhs; + u32 joinType; while( knCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){k++;} if( k>=pTab->nCol ){ sqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d", @@ -1829,9 +1840,12 @@ void sqlite3WhereTabFuncArgs( pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); - if( pItem->fg.jointype & JT_LEFT ){ - sqlite3SetJoinExpr(pTerm, pItem->iCursor); + if( pItem->fg.jointype & (JT_LEFT|JT_LTORJ) ){ + joinType = EP_OuterON; + }else{ + joinType = EP_InnerON; } + sqlite3SetJoinExpr(pTerm, pItem->iCursor, joinType); whereClauseInsert(pWC, pTerm, TERM_DYNAMIC); } } diff --git a/src/window.c b/src/window.c index e4313fcb0..893668664 100644 --- a/src/window.c +++ b/src/window.c @@ -724,7 +724,7 @@ void sqlite3WindowUpdate( } } } - pWin->pFunc = pFunc; + pWin->pWFunc = pFunc; } /* @@ -916,7 +916,7 @@ static ExprList *exprListAppendList( } } pList = sqlite3ExprListAppend(pParse, pList, pDup); - if( pList ) pList->a[nInit+i].sortFlags = pAppend->a[i].sortFlags; + if( pList ) pList->a[nInit+i].fg.sortFlags = pAppend->a[i].fg.sortFlags; } } return pList; @@ -1036,9 +1036,9 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ ExprList *pArgs; assert( ExprUseXList(pWin->pOwner) ); - assert( pWin->pFunc!=0 ); + assert( pWin->pWFunc!=0 ); pArgs = pWin->pOwner->x.pList; - if( pWin->pFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ + if( pWin->pWFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); pWin->bExprArgs = 1; @@ -1420,7 +1420,7 @@ void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){ } for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *p = pWin->pFunc; + FuncDef *p = pWin->pWFunc; if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){ /* The inline versions of min() and max() require a single ephemeral ** table and 3 registers. The registers are used as follows: @@ -1437,7 +1437,7 @@ void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){ pWin->csrApp = pParse->nTab++; pWin->regApp = pParse->nMem+1; pParse->nMem += 3; - if( pKeyInfo && pWin->pFunc->zName[1]=='i' ){ + if( pKeyInfo && pWin->pWFunc->zName[1]=='i' ){ assert( pKeyInfo->aSortFlags[0]==0 ); pKeyInfo->aSortFlags[0] = KEYINFO_ORDER_DESC; } @@ -1660,7 +1660,7 @@ static void windowAggStep( Vdbe *v = sqlite3GetVdbe(pParse); Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; int regArg; int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin); int i; @@ -1774,7 +1774,7 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ if( pMWin->regStartRowid==0 - && (pWin->pFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pWin->pWFunc->funcFlags & SQLITE_FUNC_MINMAX) && (pWin->eStart!=TK_UNBOUNDED) ){ sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); @@ -1788,12 +1788,12 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){ int nArg = windowArgCount(pWin); if( bFin ){ sqlite3VdbeAddOp2(v, OP_AggFinal, pWin->regAccum, nArg); - sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); sqlite3VdbeAddOp2(v, OP_Copy, pWin->regAccum, pWin->regResult); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); }else{ sqlite3VdbeAddOp3(v, OP_AggValue,pWin->regAccum,nArg,pWin->regResult); - sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); } } } @@ -1922,7 +1922,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; assert( ExprUseXList(pWin->pOwner) ); if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName @@ -1994,7 +1994,7 @@ static int windowInitAccum(Parse *pParse, Window *pMWin){ int nArg = 0; Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; assert( pWin->regAccum ); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); nArg = MAX(nArg, windowArgCount(pWin)); @@ -2024,7 +2024,7 @@ static int windowCacheFrame(Window *pMWin){ Window *pWin; if( pMWin->regStartRowid ) return 1; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; if( (pFunc->zName==nth_valueName) || (pFunc->zName==first_valueName) || (pFunc->zName==leadName) @@ -2117,7 +2117,7 @@ static void windowCodeRangeTest( assert( op==OP_Ge || op==OP_Gt || op==OP_Le ); assert( pOrderBy && pOrderBy->nExpr==1 ); - if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_DESC ){ + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_DESC ){ switch( op ){ case OP_Ge: op = OP_Le; break; case OP_Gt: op = OP_Lt; break; @@ -2150,7 +2150,7 @@ static void windowCodeRangeTest( ** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is ** not taken, control jumps over the comparison operator coded below this ** block. */ - if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_BIGNULL ){ + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_BIGNULL ){ /* This block runs if reg1 contains a NULL. */ int addr = sqlite3VdbeAddOp1(v, OP_NotNull, reg1); VdbeCoverage(v); switch( op ){ @@ -2382,7 +2382,7 @@ Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p){ pNew->zName = sqlite3DbStrDup(db, p->zName); pNew->zBase = sqlite3DbStrDup(db, p->zBase); pNew->pFilter = sqlite3ExprDup(db, p->pFilter, 0); - pNew->pFunc = p->pFunc; + pNew->pWFunc = p->pWFunc; pNew->pPartition = sqlite3ExprListDup(db, p->pPartition, 0); pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, 0); pNew->eFrmType = p->eFrmType; diff --git a/test/affinity3.test b/test/affinity3.test index ef1533a8f..48942de72 100644 --- a/test/affinity3.test +++ b/test/affinity3.test @@ -30,11 +30,24 @@ do_execsql_test affinity3-100 { FROM customer c LEFT JOIN apr i ON i.id=c.id; + CREATE VIEW v1rj AS + SELECT c.id, i.apr + FROM apr i + RIGHT JOIN customer c ON i.id=c.id; + CREATE VIEW v2 AS SELECT c.id, v1.apr FROM customer c LEFT JOIN v1 ON v1.id=c.id; + CREATE VIEW v2rj AS + SELECT c.id, v1.apr + FROM v1 RIGHT JOIN customer c ON v1.id=c.id; + + CREATE VIEW v2rjrj AS + SELECT c.id, v1rj.apr + FROM v1rj RIGHT JOIN customer c ON v1rj.id=c.id; + INSERT INTO customer (id) VALUES (1); INSERT INTO apr (id, apr) VALUES (1, 12); INSERT INTO customer (id) VALUES (2); @@ -44,16 +57,35 @@ do_execsql_test affinity3-110 { PRAGMA automatic_index=ON; SELECT id, (apr / 100), typeof(apr) apr_type FROM v1; } {1 0.12 real 2 0.1201 real} +do_execsql_test affinity3-111 { + PRAGMA automatic_index=ON; + SELECT id, (apr / 100), typeof(apr) apr_type FROM v1rj; +} {1 0.12 real 2 0.1201 real} do_execsql_test affinity3-120 { SELECT id, (apr / 100), typeof(apr) apr_type FROM v2; } {1 0.12 real 2 0.1201 real} +do_execsql_test affinity3-121 { + SELECT id, (apr / 100), typeof(apr) apr_type FROM v2rj; +} {1 0.12 real 2 0.1201 real} +do_execsql_test affinity3-122 { + SELECT id, (apr / 100), typeof(apr) apr_type FROM v2rjrj; +} {1 0.12 real 2 0.1201 real} do_execsql_test affinity3-130 { PRAGMA automatic_index=OFF; SELECT id, (apr / 100), typeof(apr) apr_type FROM v1; } {1 0.12 real 2 0.1201 real} +do_execsql_test affinity3-131 { + SELECT id, (apr / 100), typeof(apr) apr_type FROM v1rj; +} {1 0.12 real 2 0.1201 real} do_execsql_test affinity3-140 { SELECT id, (apr / 100), typeof(apr) apr_type FROM v2; } {1 0.12 real 2 0.1201 real} +do_execsql_test affinity3-141 { + SELECT id, (apr / 100), typeof(apr) apr_type FROM v2rj; +} {1 0.12 real 2 0.1201 real} +do_execsql_test affinity3-142 { + SELECT id, (apr / 100), typeof(apr) apr_type FROM v2rjrj; +} {1 0.12 real 2 0.1201 real} # Ticket https://www.sqlite.org/src/info/7ffd1ca1d2ad4ecf (2017-01-16) # Incorrect affinity when using automatic indexes diff --git a/test/aggnested.test b/test/aggnested.test index 35d5f1e3a..1b8b60880 100644 --- a/test/aggnested.test +++ b/test/aggnested.test @@ -137,6 +137,17 @@ do_test aggnested-3.1 { GROUP BY curr.id1); } } {1 1} +do_test aggnested-3.1-rj { + db eval { + SELECT + (SELECT sum(value2==xyz) FROM t2) + FROM + (SELECT curr.value1 as xyz + FROM t1 AS other RIGHT JOIN t1 AS curr + GROUP BY curr.id1); + } +} {1 1} + do_test aggnested-3.2 { db eval { DROP TABLE IF EXISTS t1; diff --git a/test/altermalloc3.test b/test/altermalloc3.test index 7023b1c0f..2dc0b46f2 100644 --- a/test/altermalloc3.test +++ b/test/altermalloc3.test @@ -21,6 +21,7 @@ ifcapable !altertable { return } + set ::TMPDBERROR [list 1 \ {unable to open a temporary database file for storing temporary tables} ] @@ -47,6 +48,7 @@ do_faultsim_test 1 -prep { faultsim_test_result {0 {}} $::TMPDBERROR } + #------------------------------------------------------------------------- # dbsqlfuzz e3dd84cda3848016a6a6024c7249d09bc2ef2615 # @@ -59,6 +61,11 @@ do_execsql_test 2.0 { SELECT a FROM cte1 ), 1); END; + + CREATE TRIGGER r1 AFTER INSERT ON t2 BEGIN + UPDATE t2 SET k=1 FROM t2 AS one, t2 AS two NATURAL JOIN t2 AS three + WHERE one.k=two.v; + END; } faultsim_save_and_close diff --git a/test/altertab3.test b/test/altertab3.test index 2b9aac3ef..5fd17f3a2 100644 --- a/test/altertab3.test +++ b/test/altertab3.test @@ -645,7 +645,7 @@ do_execsql_test 26.5 { } {} do_catchsql_test 26.6 { ALTER TABLE t1 RENAME TO t2; -} {1 {error in trigger xx: ambiguous column name: xx}} +} {1 {error in trigger xx: no such column: xx}} #------------------------------------------------------------------------- @@ -686,4 +686,54 @@ do_execsql_test 28.2 { UPDATE "t2" SET (c,d)=(a,b); END}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 29.1 { + CREATE TABLE t1(x, y); + CREATE TRIGGER Trigger1 DELETE ON t1 + BEGIN + SELECT t1.*, t1.x FROM t1 ORDER BY t1.x; + END; +} + + +do_execsql_test 29.2 { + ALTER TABLE t1 RENAME x TO z; +} + +do_execsql_test 29.3 { + ALTER TABLE t1 RENAME TO t2; +} + +do_execsql_test 29.4 { + CREATE TRIGGER tr2 AFTER DELETE ON t2 BEGIN + SELECT z, y FROM ( + SELECT t2.* FROM t2 + ); + END; +} + +do_execsql_test 29.5 { + DELETE FROM t2 +} + +do_execsql_test 29.6 { + ALTER TABLE t2 RENAME TO t3; +} + +do_execsql_test 29.7 { + SELECT sql FROM sqlite_schema WHERE type='trigger' +} { + {CREATE TRIGGER Trigger1 DELETE ON "t3" + BEGIN + SELECT "t3".*, "t3".z FROM "t3" ORDER BY "t3".z; + END} + {CREATE TRIGGER tr2 AFTER DELETE ON "t3" BEGIN + SELECT z, y FROM ( + SELECT "t3".* FROM "t3" + ); + END} +} + finish_test diff --git a/test/altertrig.test b/test/altertrig.test new file mode 100644 index 000000000..934a63666 --- /dev/null +++ b/test/altertrig.test @@ -0,0 +1,163 @@ +# 2022 May 27 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#************************************************************************* +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix altertrig + +# If SQLITE_OMIT_ALTERTABLE is defined, omit this file. +ifcapable !altertable { + finish_test + return +} + +proc collapse_whitespace {in} { + regsub -all {[ \t\n]+} [string trim $in] { } +} + +proc do_whitespace_sql_test {tn sql res} { + set got [execsql $sql] + set wgot [list] + set wres [list] + foreach g $got { lappend wgot [collapse_whitespace $g] } + foreach r $res { lappend wres [collapse_whitespace $r] } + + uplevel [list do_test $tn [list set {} $wgot] $wres] +} + +do_execsql_test 1.0 { + CREATE TABLE t1(x); + CREATE TABLE t2(y); + CREATE TABLE t3(z); + CREATE TABLE t4(a); + + CREATE TRIGGER r1 INSERT ON t1 BEGIN + UPDATE t1 SET d='xyz' FROM t2, t3; + END; +} + +do_whitespace_sql_test 1.1 { + ALTER TABLE t3 RENAME TO t5; + SELECT sql FROM sqlite_schema WHERE type='trigger'; +} {{ + CREATE TRIGGER r1 INSERT ON t1 BEGIN + UPDATE t1 SET d='xyz' FROM t2, "t5"; + END +}} + +do_execsql_test 1.2 { + DROP TRIGGER r1; + CREATE TRIGGER r1 INSERT ON t1 BEGIN + UPDATE t1 SET d='xyz' FROM t2, (SELECT * FROM t5); + END; +} + +do_whitespace_sql_test 1.3 { + ALTER TABLE t5 RENAME TO t3; + SELECT sql FROM sqlite_schema WHERE type='trigger'; +} {{ + CREATE TRIGGER r1 INSERT ON t1 BEGIN + UPDATE t1 SET d='xyz' FROM t2, (SELECT * FROM "t3"); + END +}} + +foreach {tn alter update final} { + 1 { + ALTER TABLE t3 RENAME TO t10 + } { + UPDATE t1 SET d='xyz' FROM t2, (SELECT * FROM t3) + } { + UPDATE t1 SET d='xyz' FROM t2, (SELECT * FROM "t10") + } + + 2 { + ALTER TABLE t3 RENAME TO t10 + } { + UPDATE t1 SET a='xyz' FROM t3, (SELECT * FROM (SELECT e FROM t3)) + } { + UPDATE t1 SET a='xyz' FROM "t10", (SELECT * FROM (SELECT e FROM "t10")) + } + + 3 { + ALTER TABLE t3 RENAME e TO abc + } { + UPDATE t1 SET a='xyz' FROM t3, (SELECT * FROM (SELECT e FROM t3)) + } { + UPDATE t1 SET a='xyz' FROM t3, (SELECT * FROM (SELECT abc FROM t3)) + } + + 4 { + ALTER TABLE t2 RENAME c TO abc + } { + UPDATE t1 SET a='xyz' FROM t3, (SELECT 1 FROM t2 WHERE c) + } { + UPDATE t1 SET a='xyz' FROM t3, (SELECT 1 FROM t2 WHERE abc) + } + + 5 { + ALTER TABLE t2 RENAME c TO abc + } { + UPDATE t1 SET a=t2.c FROM t2 + } { + UPDATE t1 SET a=t2.abc FROM t2 + } + + 6 { + ALTER TABLE t2 RENAME c TO abc + } { + UPDATE t1 SET a=t2.c FROM t2, t3 + } { + UPDATE t1 SET a=t2.abc FROM t2, t3 + } + + 7 { + ALTER TABLE t4 RENAME e TO abc + } { + UPDATE t1 SET a=1 FROM t3 NATURAL JOIN t4 WHERE t4.e=a + } { + UPDATE t1 SET a=1 FROM t3 NATURAL JOIN t4 WHERE t4.abc=a + } + + 8 { + ALTER TABLE t4 RENAME TO abc + } { + UPDATE t1 SET a=1 FROM t3 NATURAL JOIN t4 WHERE t4.e=a + } { + UPDATE t1 SET a=1 FROM t3 NATURAL JOIN "abc" WHERE "abc".e=a + } + +} { + reset_db + do_execsql_test 2.$tn.1 { + CREATE TABLE t1(a,b); + CREATE TABLE t2(c,d); + CREATE TABLE t3(e,f); + CREATE TABLE t4(e,f); + } + do_execsql_test 2.$tn.2 " + CREATE TRIGGER r1 INSERT ON t1 BEGIN + $update; + END + " + do_execsql_test 2.$tn.3 $alter + + do_whitespace_sql_test 2.$tn.4 { + SELECT sqL FROM sqlite_schema WHERE type='trigger' + } "{ + CREATE TRIGGER r1 INSERT ON t1 BEGIN + $final; + END + }" +} + +finish_test + diff --git a/test/analyze9.test b/test/analyze9.test index a7f9b5dea..b2121fd07 100644 --- a/test/analyze9.test +++ b/test/analyze9.test @@ -399,7 +399,7 @@ do_test 9.2 { for {set i 0} {$i < 100} {incr i} { execsql "INSERT INTO t1 VALUES('x', 'y', 'z', $i, [expr $i/2])" } - for {set i 0} {$i < 20} {incr i} { + for {set i 0} {$i < 21} {incr i} { execsql "INSERT INTO t1 VALUES('x', 'y', 'z', 101, $i)" } for {set i 102} {$i < 200} {incr i} { diff --git a/test/atof1.test b/test/atof1.test index 0505fb226..5959c5d5b 100644 --- a/test/atof1.test +++ b/test/atof1.test @@ -19,6 +19,10 @@ if {$::longdouble_size<=8} { finish_test return } +if {$::tcl_platform(machine)!="x86_64"} { + finish_test + return +} expr srand(1) for {set i 1} {$i<20000} {incr i} { diff --git a/test/autoindex1.test b/test/autoindex1.test index fac40f8d5..4b290c796 100644 --- a/test/autoindex1.test +++ b/test/autoindex1.test @@ -283,7 +283,7 @@ do_eqp_test autoindex1-600a { | `--CORRELATED SCALAR SUBQUERY xxxxxx | `--SEARCH later USING COVERING INDEX sqlite_autoindex_flock_owner_1 (flock_no=? AND owner_change_date>? AND owner_change_date1} -} {1 {a GROUP BY clause is required before HAVING}} +do_execsql_test count-2.9a { + SELECT count(*) FROM t2 HAVING count(*)>1; +} {} +do_execsql_test count-2.9b { + SELECT count(*) FROM t2 HAVING count(*)<10; +} {0} do_test count-2.10 { uses_op_count {SELECT count(*) FROM (SELECT 1)} } {0} diff --git a/test/distinct.test b/test/distinct.test index a3cb1afc8..446f85bb8 100644 --- a/test/distinct.test +++ b/test/distinct.test @@ -301,4 +301,52 @@ do_execsql_test 8.0 { SELECT DISTINCT pid FROM person where pid = 10; } {10} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 9.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('a', 'a'); + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t1 VALUES('a', 'c'); + + INSERT INTO t1 VALUES('b', 'a'); + INSERT INTO t1 VALUES('b', 'b'); + INSERT INTO t1 VALUES('b', 'c'); + + INSERT INTO t1 VALUES('a', 'a'); + INSERT INTO t1 VALUES('b', 'b'); + + INSERT INTO t1 VALUES('A', 'A'); + INSERT INTO t1 VALUES('B', 'B'); +} + +foreach {tn idx} { + 1 { } + 2 { CREATE INDEX i1 ON t1(a, b); } + 3 { CREATE INDEX i1 ON t1(b, a); } + 4 { CREATE INDEX i1 ON t1(a COLLATE nocase, b COLLATE nocase); } + 5 { CREATE INDEX i1 ON t1(b COLLATE nocase, a COLLATE nocase); } +} { + + execsql { DROP INDEX IF EXISTS i1 } + execsql $idx + + do_execsql_test 9.$tn.1 { + SELECT DISTINCT a, b FROM t1 ORDER BY a, b + } { + A A B B + a a a b a c + b a b b b c + } + + do_execsql_test 9.$tn.1 { + SELECT DISTINCT a COLLATE nocase, b COLLATE nocase FROM t1 + ORDER BY a COLLATE nocase, b COLLATE nocase + } { + a a a b a c + b a b b b c + } +} + + finish_test diff --git a/test/e_select.test b/test/e_select.test index a4e74d30e..5a3f0d30d 100644 --- a/test/e_select.test +++ b/test/e_select.test @@ -96,7 +96,7 @@ do_join_test e_select-0.1.3 { } {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}} +} {1 {near "USING": syntax error}} 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}} @@ -618,11 +618,12 @@ foreach {tn select res} { } { do_join_test e_select-1.7.$tn $select $res } -# EVIDENCE-OF: R-42531-52874 If the join-operator is a "LEFT JOIN" or + +# EVIDENCE-OF: R-24610-05866 If the join-operator 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). +# original left-hand input dataset that does not match any row in the +# right-hand dataset. # do_execsql_test e_select-1.8.0 { CREATE TABLE t7(a, b, c); diff --git a/test/e_vacuum.test b/test/e_vacuum.test index cfe7f8464..63bf7c125 100644 --- a/test/e_vacuum.test +++ b/test/e_vacuum.test @@ -190,8 +190,8 @@ if {![nonzero_reserved_bytes]} { } } -# EVIDENCE-OF: R-55119-57913 By default, VACUUM only works only on the -# main database. +# EVIDENCE-OF: R-40347-36128 By default, VACUUM operates on the main +# database. forcedelete test.db2 create_db { PRAGMA auto_vacuum = NONE } do_execsql_test e_vacuum-2.1.1 { diff --git a/test/eqp.test b/test/eqp.test index 19d6ea91a..eda95776f 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -94,9 +94,9 @@ do_eqp_test 1.7.1 { SELECT * FROM t3 JOIN (SELECT 1) } { QUERY PLAN - |--MATERIALIZE SUBQUERY xxxxxx + |--MATERIALIZE (subquery-xxxxxx) | `--SCAN CONSTANT ROW - |--SCAN SUBQUERY xxxxxx + |--SCAN (subquery-xxxxxx) `--SCAN t3 } do_eqp_test 1.7.2 { @@ -123,13 +123,13 @@ do_eqp_test 1.8 { SELECT * FROM t3 JOIN (SELECT 1 UNION SELECT 2) } { QUERY PLAN - |--MATERIALIZE SUBQUERY xxxxxx + |--MATERIALIZE (subquery-xxxxxx) | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--UNION USING TEMP B-TREE | `--SCAN CONSTANT ROW - |--SCAN SUBQUERY xxxxxx + |--SCAN (subquery-xxxxxx) `--SCAN t3 } do_eqp_test 1.9 { @@ -282,10 +282,10 @@ det 3.2.1 { SELECT * FROM (SELECT * FROM t1 ORDER BY x LIMIT 10) ORDER BY y LIMIT 5 } { QUERY PLAN - |--CO-ROUTINE SUBQUERY xxxxxx + |--CO-ROUTINE (subquery-xxxxxx) | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY - |--SCAN SUBQUERY xxxxxx + |--SCAN (subquery-xxxxxx) `--USE TEMP B-TREE FOR ORDER BY } det 3.2.2 { diff --git a/test/expr.test b/test/expr.test index c64b6cb70..55b1dc950 100644 --- a/test/expr.test +++ b/test/expr.test @@ -181,29 +181,53 @@ if {[working_64bit_int]} { } test_expr expr-1.111 {i1=NULL, i2=8} {i1 IS i2} 0 +test_expr expr-1.111b {i1=NULL, i2=8} {i1 IS NOT DISTINCT FROM i2} 0 test_expr expr-1.112 {i1=NULL, i2=NULL} {i1 IS i2} 1 +test_expr expr-1.112b {i1=NULL, i2=NULL} {i1 IS NOT DISTINCT FROM i2} 1 test_expr expr-1.113 {i1=6, i2=NULL} {i1 IS i2} 0 +test_expr expr-1.113b {i1=6, i2=NULL} {i1 IS NOT DISTINCT FROM i2} 0 test_expr expr-1.114 {i1=6, i2=6} {i1 IS i2} 1 +test_expr expr-1.114b {i1=6, i2=6} {i1 IS NOT DISTINCT FROM i2} 1 test_expr expr-1.115 {i1=NULL, i2=8} \ {CASE WHEN i1 IS i2 THEN 'yes' ELSE 'no' END} no +test_expr expr-1.115b {i1=NULL, i2=8} \ + {CASE WHEN i1 IS NOT DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} no test_expr expr-1.116 {i1=NULL, i2=NULL} \ {CASE WHEN i1 IS i2 THEN 'yes' ELSE 'no' END} yes +test_expr expr-1.116b {i1=NULL, i2=NULL} \ + {CASE WHEN i1 IS NOT DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} yes test_expr expr-1.117 {i1=6, i2=NULL} \ {CASE WHEN i1 IS i2 THEN 'yes' ELSE 'no' END} no +test_expr expr-1.117b {i1=6, i2=NULL} \ + {CASE WHEN i1 IS NOT DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} no test_expr expr-1.118 {i1=8, i2=8} \ {CASE WHEN i1 IS i2 THEN 'yes' ELSE 'no' END} yes +test_expr expr-1.118b {i1=8, i2=8} \ + {CASE WHEN i1 IS NOT DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} yes test_expr expr-1.119 {i1=NULL, i2=8} {i1 IS NOT i2} 1 +test_expr expr-1.119b {i1=NULL, i2=8} {i1 IS DISTINCT FROM i2} 1 test_expr expr-1.120 {i1=NULL, i2=NULL} {i1 IS NOT i2} 0 +test_expr expr-1.120b {i1=NULL, i2=NULL} {i1 IS DISTINCT FROM i2} 0 test_expr expr-1.121 {i1=6, i2=NULL} {i1 IS NOT i2} 1 +test_expr expr-1.121b {i1=6, i2=NULL} {i1 IS DISTINCT FROM i2} 1 test_expr expr-1.122 {i1=6, i2=6} {i1 IS NOT i2} 0 +test_expr expr-1.122b {i1=6, i2=6} {i1 IS DISTINCT FROM i2} 0 test_expr expr-1.123 {i1=NULL, i2=8} \ {CASE WHEN i1 IS NOT i2 THEN 'yes' ELSE 'no' END} yes +test_expr expr-1.123b {i1=NULL, i2=8} \ + {CASE WHEN i1 IS DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} yes test_expr expr-1.124 {i1=NULL, i2=NULL} \ {CASE WHEN i1 IS NOT i2 THEN 'yes' ELSE 'no' END} no +test_expr expr-1.124b {i1=NULL, i2=NULL} \ + {CASE WHEN i1 IS DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} no test_expr expr-1.125 {i1=6, i2=NULL} \ {CASE WHEN i1 IS NOT i2 THEN 'yes' ELSE 'no' END} yes +test_expr expr-1.125b {i1=6, i2=NULL} \ + {CASE WHEN i1 IS DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} yes test_expr expr-1.126 {i1=8, i2=8} \ {CASE WHEN i1 IS NOT i2 THEN 'yes' ELSE 'no' END} no +test_expr expr-1.126b {i1=8, i2=8} \ + {CASE WHEN i1 IS DISTINCT FROM i2 THEN 'yes' ELSE 'no' END} no do_catchsql_test expr-1.127 { SELECT 1 IS #1; diff --git a/test/fkey8.test b/test/fkey8.test index 085190e67..2d72f6fcf 100644 --- a/test/fkey8.test +++ b/test/fkey8.test @@ -228,4 +228,26 @@ do_execsql_test 5.3 { PRAGMA integrity_check; } {ok} +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +do_execsql_test 6.1 { + PRAGMA foreign_keys = on; + CREATE TABLE c1(b); + INSERT INTO c1 VALUES(123); +} + +do_execsql_test 6.2 { + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.p1(a INTEGER PRIMARY KEY); + CREATE TABLE aux.c1(b REFERENCES p1(a) ON DELETE RESTRICT); + + INSERT INTO aux.p1 VALUES(123); +} + +do_execsql_test 6.3 { + DELETE FROM aux.p1 WHERE a=123; +} + + finish_test diff --git a/test/fts3corrupt.test b/test/fts3corrupt.test index 8b958db5f..69d5030be 100644 --- a/test/fts3corrupt.test +++ b/test/fts3corrupt.test @@ -193,4 +193,42 @@ do_catchsql_test 7.10 { SELECT matchinfo( f , 'pcx') FROM f WHERE b MATCH x'c533'; } {1 {database disk image is malformed}} +reset_db +sqlite3_fts3_may_be_corrupt 1 +do_execsql_test 8.1 { + CREATE VIRTUAL TABLE f USING fts3(a); + INSERT INTO f(f) VALUES('nodesize=24'); + BEGIN; + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789'); + + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X'); + COMMIT; + BEGIN; + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789'); + + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X'); + INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X'); + COMMIT; + + SELECT count(*) FROM f_segments; +} {4} + +do_execsql_test 8.2 { + UPDATE f_segments SET block = ( + SELECT block FROM f_segments WHERE blockid=1 + ) WHERE blockid=2 +} + +do_catchsql_test 8.3 { + INSERT INTO f(f) VALUES('merge=2,2'); +} {1 {database disk image is malformed}} +sqlite3_fts3_may_be_corrupt 0 + finish_test diff --git a/test/fts3corrupt4.test b/test/fts3corrupt4.test index f0b83a390..f8e89b3a7 100644 --- a/test/fts3corrupt4.test +++ b/test/fts3corrupt4.test @@ -7033,6 +7033,8 @@ set sqlite_fts3_enable_parentheses $saved #------------------------------------------------------------------------- # +set saved $sqlite_fts3_enable_parentheses +set sqlite_fts3_enable_parentheses 1 reset_db do_test 52.0 { sqlite3 db {} @@ -7228,5 +7230,438 @@ do_catchsql_test 52.1 { SELECT * FROM t1, t2; } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +# +reset_db +do_test 53.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 8192 pagesize 1024 filename crash-7bc.txt.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 04 00 01 01 00 40 20 20 00 00 00 00 00 00 00 08 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 96: 00 00 00 00 0d 02 f3 00 07 01 51 00 03 c8 03 63 ..........Q....c +| 112: 02 fb 02 0a 02 c0 01 a8 01 51 00 00 00 00 00 00 .........Q...... +| 336: 00 55 07 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 .U........tablet +| 352: 31 5f 73 74 61 74 74 31 5f 73 74 61 74 07 43 52 1_statt1_stat.CR +| 368: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 EATE TABLE 't1_s +| 384: 74 61 74 27 28 69 64 20 49 4e 54 45 47 45 52 20 tat'(id INTEGER +| 400: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 61 6c PRIMARY KEY, val +| 416: 75 65 20 42 4c 4f 42 29 60 06 07 17 21 21 01 81 ue BLOB)`...!!.. +| 432: 0b 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 .tablet1_docsize +| 448: 74 31 5f 64 6f 63 73 69 7a 65 06 43 52 45 41 54 t1_docsize.CREAT +| 464: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs +| 480: 69 7a 65 27 28 64 6f 63 69 64 20 49 4e 54 45 47 ize'(docid INTEG +| 496: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 512: 73 69 7a 65 20 42 4c 4f 42 29 81 33 04 07 17 1f size BLOB).3.... +| 528: 1f 01 82 35 74 61 62 6c 65 74 31 5f 73 65 67 64 ...5tablet1_segd +| 544: 69 72 74 31 5f 73 65 67 64 69 72 04 43 52 45 41 irt1_segdir.CREA +| 560: 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 65 67 TE TABLE 't1_seg +| 576: 64 69 72 27 28 6c 65 76 65 6c 20 49 4e 54 45 47 dir'(level INTEG +| 592: 45 52 2c 69 64 78 20 49 4e 54 45 47 45 52 2c 73 ER,idx INTEGER,s +| 608: 74 61 72 74 5f 62 6c 6f 63 6b 20 49 4e 54 45 47 tart_block INTEG +| 624: 45 52 2c 6c 65 61 76 65 73 5f 65 6e 64 5f 62 6c ER,leaves_end_bl +| 640: 6f 63 6b 20 49 4e 54 45 47 45 52 2c 65 6e 64 5f ock INTEGER,end_ +| 656: 62 6c 6f 63 6b 20 49 4e 54 45 47 45 52 2c 72 6f block INTEGER,ro +| 672: 6f 74 20 42 4c 4f 42 2c 50 52 49 4d 41 52 59 20 ot BLOB,PRIMARY +| 688: 4b 45 59 28 6c 65 76 65 6c 2c 20 69 64 78 29 29 KEY(level, idx)) +| 704: 31 05 06 17 45 1f 01 00 69 6e 64 65 78 73 71 6c 1...E...indexsql +| 720: 69 74 65 5f 61 75 74 6f 69 6e 64 65 78 5f 74 31 ite_autoindex_t1 +| 736: 5f 73 65 67 64 69 72 5f 31 74 31 5f 73 65 67 64 _segdir_1t1_segd +| 752: 69 72 05 00 00 00 08 00 00 00 00 66 03 07 17 23 ir.........f...# +| 768: 23 01 81 13 74 61 62 6c 65 74 31 5f 73 65 67 6d #...tablet1_segm +| 784: 65 6e 74 73 74 31 5f 73 65 67 6d 65 6e 74 73 03 entst1_segments. +| 800: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 816: 5f 73 65 67 6d 65 6e 74 73 27 28 62 6c 6f 63 6b _segments'(block +| 832: 69 64 20 49 4e 53 45 47 45 52 20 50 52 49 4d 41 id INSEGER PRIMA +| 848: 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 4c RY KEY, block BL +| 864: 4f 42 29 63 02 07 17 21 21 01 81 11 74 61 62 6c OB)c...!!...tabl +| 880: 65 74 31 5f 63 6f 6e 74 65 6e 74 74 31 5f 63 6f et1_contentt1_co +| 896: 6e 74 65 6e 74 02 43 52 45 41 54 45 20 54 41 42 ntent.CREATE TAB +| 912: 4c 45 20 27 74 31 5f 63 6f 6e 74 65 6e 74 27 28 LE 't1_content'( +| 928: 64 6f 63 69 64 20 49 4e 54 45 47 45 52 20 50 52 docid INTEGER PR +| 944: 49 4d 41 52 59 20 4b 45 59 2c 20 27 63 30 30 27 IMARY KEY, 'c00' +| 960: 2c 20 27 63 31 62 27 29 36 01 06 17 11 11 08 5b , 'c1b')6......[ +| 976: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE +| 992: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1 +| 1008: 20 55 53 49 4e 47 20 66 74 73 34 28 30 2c 62 29 USING fts4(0,b) +| page 2 offset 1024 +| 0: 0d 00 00 00 03 00 0f 00 00 23 00 16 00 0f 00 05 .........#...... +| 16: 03 04 00 08 0f 61 0b 02 04 00 08 1b 41 54 45 20 .....a......ATE +| 32: 32 3a 50 87 5a 01 05 00 08 8f 37 66 30 30 30 30 2:P.Z.....7f0000 +| 48: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 64: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 80: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 96: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 112: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 128: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 144: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 160: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 176: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 192: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 208: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 224: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 240: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 256: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 272: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 288: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 304: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 320: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 336: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 352: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 368: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 384: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 400: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 416: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 432: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 448: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 464: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 480: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 496: 30 40 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0@00000000000000 +| 512: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 528: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 544: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 560: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 576: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 592: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 608: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 624: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 640: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 656: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 672: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 688: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 704: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 720: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 736: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 752: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 768: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 784: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 800: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 816: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 832: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 848: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 864: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 880: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 896: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 912: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 928: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 944: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 960: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 976: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 992: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 1008: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| page 3 offset 2048 +| 0: 0d 00 00 00 02 03 86 00 03 f4 03 86 00 00 00 00 ................ +| 896: 00 00 00 00 00 00 87 62 02 04 00 8f 48 00 d5 07 .......b....H... +| 912: 66 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 f000000000000000 +| 928: 30 30 30 30 3a 30 30 30 30 30 30 30 30 30 30 30 0000:00000000000 +| 944: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 960: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 976: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 992: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 1008: 00 00 00 08 0a 01 03 00 1a 00 01 30 03 01 02 00 ...........0.... +| page 4 offset 3072 +| 0: 0d 00 00 00 03 03 9e 00 03 ed 03 bc 03 9e 00 00 ................ +| 912: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1c 03 ................ +| 928: 07 08 01 08 08 15 2c 02 30 20 31 36 00 01 30 03 ......,.0 16..0. +| 944: 03 02 00 00 01 61 05 03 01 01 02 00 2f 02 07 08 .....a....../... +| 960: 09 08 08 15 54 30 20 33 36 00 01 30 03 02 02 00 ....T0 36..0.... +| 976: 00 01 32 05 02 01 01 03 00 00 03 61 74 65 05 02 ..2........ate.. +| 992: 01 01 02 00 00 01 70 05 02 01 01 04 00 11 01 07 ......p......... +| 1008: 08 08 09 01 17 14 02 32 20 39 39 37 01 01 01 66 .......2 997...f +| page 5 offset 4096 +| 0: 0a 00 00 00 03 03 ee 00 03 fb 03 f5 03 ee 00 00 ................ +| 992: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 04 ................ +| 1008: 08 01 01 02 03 05 04 08 09 01 02 04 04 08 08 09 ................ +| page 6 offset 5120 +| 0: 0d 00 00 00 03 03 eb 00 00 00 00 00 00 00 00 00 ................ +| 992: 00 00 00 00 00 00 00 00 00 00 00 05 03 03 00 10 ................ +| 1008: 01 01 05 02 03 00 10 01 03 05 01 03 00 10 01 01 ................ +| page 7 offset 6144 +| 0: 0d 00 00 00 01 03 f6 00 03 f6 00 00 00 00 00 00 ................ +| 1008: 00 00 00 00 00 00 08 00 03 00 16 03 08 c5 e0 07 ................ +| page 8 offset 7168 +| 0: 00 00 00 00 30 30 30 30 30 30 30 30 30 30 30 30 ....000000000000 +| 16: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 32: 30 30 30 30 30 30 30 30 30 30 30 30 30 bc 30 30 0000000000000.00 +| 48: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 64: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 80: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 96: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 112: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 128: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 144: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 160: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 176: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 192: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 c0 30 00000000000000.0 +| 208: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 224: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 240: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 256: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 272: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 288: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 304: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 320: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 336: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 352: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 368: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 384: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 400: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 416: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 432: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 448: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 464: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 480: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 496: 30 30 30 30 30 30 30 30 30 30 30 40 30 30 30 30 00000000000@0000 +| 512: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 528: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 544: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 560: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 576: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 592: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 608: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 624: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 640: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 656: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 672: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 688: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 704: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 720: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 736: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 752: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 768: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 784: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 800: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 816: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 832: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 848: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 864: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 880: 30 30 30 30 30 30 30 30 30 05 01 00 00 00 00 00 000000000....... +| end crash-7bc.txt.db +}]} {} + +do_execsql_test 53.1 { + SELECT*FROM t1 WHERE t1 MATCH'ATE"0"OR"2D:P"""ATE"0"OR"2:P"""'; +} {0 {ATE 2:P}} +set sqlite_fts3_enable_parentheses $saved + +#------------------------------------------------------------------------- +# +reset_db +do_test 54.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 8192 pagesize 1024 filename crash-365.txt.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 04 00 01 01 00 40 20 20 00 00 00 00 00 00 00 08 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................ +| 96: 00 00 00 00 0d 02 f3 00 07 01 51 00 03 c8 03 63 ..........Q....c +| 112: 02 fb 02 0a 02 c0 01 a8 01 51 00 00 00 00 00 00 .........Q...... +| 336: 00 55 07 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 .U........tablet +| 352: 31 5f 73 74 61 74 74 31 5f 73 74 61 74 07 43 52 1_statt1_stat.CR +| 368: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 EATE TABLE 't1_s +| 384: 74 61 74 27 28 69 64 20 49 4e 54 45 47 45 52 20 tat'(id INTEGER +| 400: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 61 6c PRIMARY KEY, val +| 416: 75 65 20 42 4c 4f 42 29 60 06 07 17 21 21 01 81 ue BLOB)`...!!.. +| 432: 0b 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 .tablet1_docsize +| 448: 74 31 5f 64 6f 63 73 69 7a 65 06 43 52 45 41 54 t1_docsize.CREAT +| 464: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs +| 480: 69 7a 65 27 28 64 6f 63 69 64 20 49 4e 54 45 47 ize'(docid INTEG +| 496: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 512: 73 69 7a 65 20 42 4c 4f 42 29 81 33 04 07 17 1f size BLOB).3.... +| 528: 1f 01 82 35 74 61 62 6c 65 74 31 5f 73 65 67 64 ...5tablet1_segd +| 544: 69 72 74 31 5f 73 65 67 64 69 72 04 43 52 45 41 irt1_segdir.CREA +| 560: 54 45 20 54 41 42 4c 45 20 27 74 31 5f 73 65 67 TE TABLE 't1_seg +| 576: 64 69 72 27 28 6c 65 76 65 6c 20 49 4e 54 45 47 dir'(level INTEG +| 592: 45 52 2c 69 64 78 20 49 4e 54 45 47 45 52 2c 73 ER,idx INTEGER,s +| 608: 74 61 72 74 5f 62 6c 6f 63 6b 20 49 4e 54 45 47 tart_block INTEG +| 624: 45 52 2c 6c 65 61 76 65 73 5f 65 6e 64 5f 62 6c ER,leaves_end_bl +| 640: 6f 63 6b 20 49 4e 54 45 47 45 52 2c 65 6e 64 5f ock INTEGER,end_ +| 656: 62 6c 6f 63 6b 20 49 4e 54 45 47 45 52 2c 72 6f block INTEGER,ro +| 672: 6f 74 20 42 4c 4f 42 2c 50 52 49 4d 41 52 59 20 ot BLOB,PRIMARY +| 688: 4b 45 59 28 6c 65 76 65 6c 2c 20 69 64 78 29 29 KEY(level, idx)) +| 704: 31 05 06 17 45 1f 01 00 69 6e 64 65 78 73 71 6c 1...E...indexsql +| 720: 69 74 65 5f 61 75 74 6f 69 6e 64 65 78 5f 74 31 ite_autoindex_t1 +| 736: 5f 73 65 67 64 69 72 5f 31 74 31 5f 73 65 67 64 _segdir_1t1_segd +| 752: 69 72 05 00 00 00 08 00 00 00 00 66 03 07 17 23 ir.........f...# +| 768: 23 01 81 13 74 61 62 6c 65 74 31 5f 73 65 67 6d #...tablet1_segm +| 784: 65 6e 74 73 74 31 5f 73 65 67 6d 65 6e 74 73 03 entst1_segments. +| 800: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 816: 5f 73 65 67 6d 65 6e 74 73 27 28 62 6c 6f 63 6b _segments'(block +| 832: 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 id INTEGER PRIMA +| 848: 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 4c RY KEY, block BL +| 864: 4f 42 29 63 02 07 17 21 21 01 81 11 74 61 62 6c OB)c...!!...tabl +| 880: 65 74 31 5f 63 6f 6e 74 65 6e 74 74 31 5f 63 6f et1_contentt1_co +| 896: 6e 74 65 6e 74 02 43 52 45 41 54 45 20 54 41 42 ntent.CREATE TAB +| 912: 4c 45 20 27 74 31 5f 63 6f 6e 74 65 6e 74 27 28 LE 't1_content'( +| 928: 64 6f 63 69 64 20 49 4e 54 45 47 45 52 20 50 52 docid INTEGER PR +| 944: 49 4d 41 52 59 20 4b 45 59 2c 20 27 63 30 30 27 IMARY KEY, 'c00' +| 960: 2c 20 27 63 31 62 27 29 36 01 06 17 11 11 08 5b , 'c1b')6......[ +| 976: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE +| 992: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1 +| 1008: 20 55 53 49 4e 47 20 66 74 73 34 28 30 2c 62 29 USING fts4(0,b) +| page 2 offset 1024 +| 0: 0d 00 00 00 03 00 0f 00 00 23 00 16 00 0f 00 05 .........#...... +| 16: 03 04 00 08 0f 61 0b 02 04 00 08 1b 41 54 45 20 .....a......ATE +| 32: 32 3a 50 87 5a 01 05 00 08 8f 37 66 30 30 30 30 2:P.Z.....7f0000 +| 48: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 64: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 80: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 96: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 112: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 128: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 144: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 160: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 176: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 192: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 208: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 224: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 240: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 256: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 272: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 288: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 304: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 320: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 336: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 352: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 368: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 384: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 400: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 416: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 432: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 448: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 464: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 480: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 496: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 512: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 528: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 544: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 560: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 576: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 592: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 608: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 624: 30 30 30 30 30 30 30 30 30 30 1b 30 30 30 30 30 0000000000.00000 +| 640: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 656: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 672: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 688: 30 30 30 30 30 30 30 30 2f 30 30 30 30 30 30 30 00000000/0000000 +| 704: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 720: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 736: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 752: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 768: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 784: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 800: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 816: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 832: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 848: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 864: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 880: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 896: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 912: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 928: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 944: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 960: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 976: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 992: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 1008: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| page 3 offset 2048 +| 0: 0d 00 00 00 02 03 86 00 03 f4 03 86 00 00 00 00 ................ +| 896: 00 00 00 00 00 00 87 62 02 04 00 8f 48 00 d5 07 .......b....H... +| 912: 66 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 f000000000000000 +| 928: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 944: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 960: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 976: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 992: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 1008: 00 00 00 08 0a 01 03 00 1a 00 01 30 03 01 02 00 ...........0.... +| page 4 offset 3072 +| 0: 0d 00 00 00 03 03 9e 00 03 ed 03 bc 03 9e 00 01 ................ +| 912: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1c 03 ................ +| 928: 07 08 01 08 08 15 2c 02 30 20 31 36 00 01 30 03 ......,.0 16..0. +| 944: 03 02 00 00 01 61 05 03 01 01 02 00 2f 02 07 08 .....a....../... +| 960: 09 08 08 15 54 30 20 33 36 00 01 30 03 02 02 00 ....T0 36..0.... +| 976: 00 01 32 05 02 01 01 03 00 00 03 61 74 65 05 02 ..2........ate.. +| 992: 01 01 02 00 00 01 70 05 02 01 01 04 00 11 01 07 ......p......... +| 1008: 08 08 09 01 17 14 02 32 20 39 39 37 01 01 01 66 .......2 997...f +| page 5 offset 4096 +| 0: 0a 00 00 00 03 03 ee 00 03 fb 03 f5 03 ee 00 00 ................ +| 992: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 04 ................ +| 1008: 08 01 01 02 03 05 04 08 09 01 02 04 04 08 08 09 ................ +| page 6 offset 5120 +| 0: 0d 00 00 00 03 03 eb 00 03 f9 03 f2 00 00 00 00 ................ +| 992: 00 00 00 00 00 00 00 00 00 00 00 05 03 03 00 10 ................ +| 1008: 01 01 05 02 03 00 10 01 03 05 01 03 00 10 01 01 ................ +| page 7 offset 6144 +| 0: 0d 00 00 00 01 03 f6 00 03 f6 00 00 00 00 00 00 ................ +| 1008: 00 00 00 00 00 00 08 00 03 00 16 03 03 05 e0 07 ................ +| page 8 offset 7168 +| 0: 00 00 00 00 30 30 30 30 30 30 30 30 30 30 30 30 ....000000000000 +| 16: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 32: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 48: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 64: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 80: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 96: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 112: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 128: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 144: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 160: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 176: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 192: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 208: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 224: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 240: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 256: 30 30 30 30 30 2f 30 30 30 30 30 30 30 30 30 30 00000/0000000000 +| 272: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 288: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 304: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 320: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 336: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 352: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 368: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 384: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 400: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 416: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 432: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 448: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 464: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 480: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 496: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 512: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 528: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 544: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 560: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 576: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 592: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 608: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 624: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 640: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 656: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 672: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 688: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 704: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 720: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 736: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 752: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 768: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 784: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 800: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 816: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 832: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 848: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 864: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000 +| 880: 30 30 30 30 30 30 30 30 30 05 01 01 01 02 00 00 000000000....... +| end crash-365.txt.db +}]} {} + +do_execsql_test 54.1 { + SELECT rowid, quote(matchinfo(t1,'pcxybspcxybs')) FROM t1 WHERE t1 MATCH'ATE"0"OR"2:P"""'; +} finish_test diff --git a/test/fts3corrupt6.test b/test/fts3corrupt6.test index fde2fba2e..a3c5fd8da 100644 --- a/test/fts3corrupt6.test +++ b/test/fts3corrupt6.test @@ -64,7 +64,8 @@ do_execsql_test 2.1 { #------------------------------------------------------------------------- reset_db -do_execsql_test 3.0 { +breakpoint +do_catchsql_test 3.0 { CREATE VIRTUAL TABLE main.Table0 USING fts3(); INSERT INTO Table0 VALUES (1), (printf('%8.1280000X') ), (1), (printf('%8.1280000X') ), (1) ; INSERT INTO Table0 VALUES (0), (printf('%8.1280000X%8.1280000X') ), (1), (printf('%1280000.1280000X%#1280000.1280000E%8.1280000X') ), (1) ; @@ -72,7 +73,7 @@ do_execsql_test 3.0 { UPDATE Table0_segdir SET start_block = 1; INSERT INTO Table0 VALUES (1) ; INSERT INTO Table0(Table0) VALUES('merge=6,8'); -} +} {1 {database disk image is malformed}} set sqlite_fts3_enable_parentheses $saved_sqlite_fts3_enable_parentheses finish_test diff --git a/test/fts3fault.test b/test/fts3fault.test index 707e884fa..21defd282 100644 --- a/test/fts3fault.test +++ b/test/fts3fault.test @@ -18,6 +18,10 @@ set ::testprefix fts3fault # If SQLITE_ENABLE_FTS3 is not defined, omit this file. ifcapable !fts3 { finish_test ; return } +set ::TMPDBERROR [list 1 \ + {unable to open a temporary database file for storing temporary tables} +] + # Test error handling in the sqlite3Fts3Init() function. This is the # function that registers the FTS3 module and various support functions # with SQLite. @@ -49,7 +53,7 @@ do_faultsim_test 2 -prep { } -body { execsql { ALTER TABLE t1 RENAME TO t2 } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } # Test error handling in the special case where a single prefix query @@ -194,7 +198,7 @@ do_faultsim_test 8.2 -faults oom-t* -prep { } -body { execsql { SELECT mit(matchinfo(t8, 's')) FROM t8 WHERE t8 MATCH 'a b c' } } -test { - faultsim_test_result {0 3} + faultsim_test_result {0 3} $::TMPDBERROR } do_faultsim_test 8.3 -prep { faultsim_restore_and_reopen diff --git a/test/fts3fault2.test b/test/fts3fault2.test index 5c182d73c..913f9baa0 100644 --- a/test/fts3fault2.test +++ b/test/fts3fault2.test @@ -215,12 +215,15 @@ do_faultsim_test 8.1 -faults oom* -prep { faultsim_test_result {0 {}} } +set ::TMPDBERROR [list 1 \ + {unable to open a temporary database file for storing temporary tables} +] do_faultsim_test 8.2 -faults oom* -prep { faultsim_restore_and_reopen } -body { execsql { ALTER TABLE t8 RENAME TO t8ii } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} $::TMPDBERROR } #------------------------------------------------------------------------- diff --git a/test/fts3join.test b/test/fts3join.test index 9a7994193..3333b1ab4 100644 --- a/test/fts3join.test +++ b/test/fts3join.test @@ -100,7 +100,7 @@ do_eqp_test 4.2 { |--MATERIALIZE rr | `--SCAN ft4 VIRTUAL TABLE INDEX 3: |--SCAN t4 - `--SEARCH rr USING AUTOMATIC COVERING INDEX (docid=?) + `--SEARCH rr USING AUTOMATIC COVERING INDEX (docid=?) LEFT-JOIN } finish_test diff --git a/test/func7.test b/test/func7.test index c8ae2931e..bb4f80b32 100644 --- a/test/func7.test +++ b/test/func7.test @@ -60,6 +60,12 @@ do_execsql_test func7-pg-170 { do_execsql_test func7-pg-180 { SELECT log10(1000.0) } {3.0} +do_execsql_test func7-pg-181 { + SELECT format('%.30f', log10(100.0) ); +} {2.000000000000000000000000000000} +do_execsql_test func7-pg-182 { + SELECT format('%.30f', ln(exp(2.0)) ); +} {2.000000000000000000000000000000} do_execsql_test func7-pg-190 { SELECT log(2.0, 64.0) } {6.0} diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index 2ea0898ef..fe5626221 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -153,6 +153,7 @@ static struct GlobalVars { int nSql; /* Number of SQL scripts */ Blob *pFirstSql; /* First SQL script */ unsigned int uRandom; /* Seed for the SQLite PRNG */ + unsigned int nInvariant; /* Number of invariant checks run */ char zTestName[100]; /* Name of current test */ } g; @@ -830,6 +831,14 @@ static int progress_handler(void *pClientData) { return rc; } +/* +** Flag bits set by block_troublesome_sql() +*/ +#define BTS_SELECT 0x000001 +#define BTS_NONSELECT 0x000002 +#define BTS_BADFUNC 0x000004 +#define BTS_BADPRAGMA 0x000008 /* Sticky for rest of the script */ + /* ** Disallow debugging pragmas such as "PRAGMA vdbe_debug" and ** "PRAGMA parser_trace" since they can dramatically increase the @@ -838,64 +847,158 @@ static int progress_handler(void *pClientData) { ** Also block ATTACH if attaching a file from the filesystem. */ static int block_troublesome_sql( - void *Notused, + void *pClientData, int eCode, const char *zArg1, const char *zArg2, const char *zArg3, const char *zArg4 ){ - (void)Notused; - (void)zArg2; + unsigned int *pBtsFlags = (unsigned int*)pClientData; + (void)zArg3; (void)zArg4; - if( eCode==SQLITE_PRAGMA ){ - if( sqlite3_stricmp("busy_timeout",zArg1)==0 - && (zArg2==0 || strtoll(zArg2,0,0)>100 || strtoll(zArg2,0,10)>100) - ){ - return SQLITE_DENY; - }else if( eVerbosity==0 ){ - if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0 - || sqlite3_stricmp("parser_trace", zArg1)==0 - || sqlite3_stricmp("temp_store_directory", zArg1)==0 + switch( eCode ){ + case SQLITE_PRAGMA: { + if( sqlite3_stricmp("busy_timeout",zArg1)==0 + && (zArg2==0 || strtoll(zArg2,0,0)>100 || strtoll(zArg2,0,10)>100) ){ return SQLITE_DENY; + }else if( sqlite3_stricmp("hard_heap_limit", zArg1)==0 + || sqlite3_stricmp("reverse_unordered_selects", zArg1)==0 + ){ + /* BTS_BADPRAGMA is sticky. A hard_heap_limit or + ** revert_unordered_selects should inhibit all future attempts + ** at verifying query invariants */ + *pBtsFlags |= BTS_BADPRAGMA; + }else if( eVerbosity==0 ){ + if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0 + || sqlite3_stricmp("parser_trace", zArg1)==0 + || sqlite3_stricmp("temp_store_directory", zArg1)==0 + ){ + return SQLITE_DENY; + } + }else if( sqlite3_stricmp("oom",zArg1)==0 + && zArg2!=0 && zArg2[0]!=0 ){ + oomCounter = atoi(zArg2); } - }else if( sqlite3_stricmp("oom",zArg1)==0 - && zArg2!=0 && zArg2[0]!=0 ){ - oomCounter = atoi(zArg2); + *pBtsFlags |= BTS_NONSELECT; + break; + } + case SQLITE_ATTACH: { + /* Deny the ATTACH if it is attaching anything other than an in-memory + ** database. */ + *pBtsFlags |= BTS_NONSELECT; + if( zArg1==0 ) return SQLITE_DENY; + if( strcmp(zArg1,":memory:")==0 ) return SQLITE_OK; + if( sqlite3_strglob("file:*[?]vfs=memdb", zArg1)==0 + && sqlite3_strglob("file:*[^/a-zA-Z0-9_.]*[?]vfs=memdb", zArg1)!=0 + ){ + return SQLITE_OK; + } + return SQLITE_DENY; + } + case SQLITE_SELECT: { + *pBtsFlags |= BTS_SELECT; + break; } - }else if( eCode==SQLITE_ATTACH ){ - /* Deny the ATTACH if it is attaching anything other than an in-memory - ** database. */ - if( zArg1==0 ) return SQLITE_DENY; - if( strcmp(zArg1,":memory:")==0 ) return SQLITE_OK; - if( sqlite3_strglob("file:*[?]vfs=memdb", zArg1)==0 - && sqlite3_strglob("file:*[^/a-zA-Z0-9_.]*[?]vfs=memdb", zArg1)!=0 - ){ - return SQLITE_OK; + case SQLITE_FUNCTION: { + static const char *azBadFuncs[] = { + "avg", + "count", + "cume_dist", + "current_date", + "current_time", + "current_timestamp", + "date", + "datetime", + "decimal_sum", + "dense_rank", + "first_value", + "geopoly_group_bbox", + "group_concat", + "implies_nonnull_row", + "json_group_array", + "json_group_object", + "julianday", + "lag", + "last_value", + "lead", + "max", + "min", + "nth_value", + "ntile", + "percent_rank", + "random", + "randomblob", + "rank", + "row_number", + "sqlite_offset", + "strftime", + "sum", + "time", + "total", + "unixepoch", + }; + int first, last; + first = 0; + last = sizeof(azBadFuncs)/sizeof(azBadFuncs[0]) - 1; + do{ + int mid = (first+last)/2; + int c = sqlite3_stricmp(azBadFuncs[mid], zArg2); + if( c<0 ){ + first = mid+1; + }else if( c>0 ){ + last = mid-1; + }else{ + *pBtsFlags |= BTS_BADFUNC; + break; + } + }while( first<=last ); + break; + } + case SQLITE_READ: { + /* Benign */ + break; + } + default: { + *pBtsFlags |= BTS_NONSELECT; } - return SQLITE_DENY; } return SQLITE_OK; } +/* Implementation found in fuzzinvariant.c */ +int fuzz_invariant( + sqlite3 *db, /* The database connection */ + sqlite3_stmt *pStmt, /* Test statement stopped on an SQLITE_ROW */ + int iCnt, /* Invariant sequence number, starting at 0 */ + int iRow, /* The row number for pStmt */ + int nRow, /* Total number of output rows */ + int *pbCorrupt, /* IN/OUT: Flag indicating a corrupt database file */ + int eVerbosity /* How much debugging output */ +); + /* ** Run the SQL text */ -static int runDbSql(sqlite3 *db, const char *zSql){ +static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){ int rc; sqlite3_stmt *pStmt; + int bCorrupt = 0; while( isspace(zSql[0]&0x7f) ) zSql++; if( zSql[0]==0 ) return SQLITE_OK; if( eVerbosity>=4 ){ printf("RUNNING-SQL: [%s]\n", zSql); fflush(stdout); } + (*pBtsFlags) &= ~BTS_BADPRAGMA; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc==SQLITE_OK ){ + int nRow = 0; while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){ - if( eVerbosity>=5 ){ + nRow++; + if( eVerbosity>=4 ){ int j; for(j=0; j=5 ) */ } /* End while( SQLITE_ROW */ - if( rc!=SQLITE_DONE && eVerbosity>=4 ){ + if( rc==SQLITE_DONE ){ + if( (*pBtsFlags)==BTS_SELECT + && !sqlite3_stmt_isexplain(pStmt) + && nRow>0 + ){ + int iRow = 0; + sqlite3_reset(pStmt); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + int iCnt = 0; + iRow++; + for(iCnt=0; iCnt<99999; iCnt++){ + rc = fuzz_invariant(db, pStmt, iCnt, iRow, nRow, + &bCorrupt, eVerbosity); + if( rc==SQLITE_DONE ) break; + if( rc!=SQLITE_ERROR ) g.nInvariant++; + if( eVerbosity>0 ){ + if( rc==SQLITE_OK ){ + printf("invariant-check: ok\n"); + }else if( rc==SQLITE_CORRUPT ){ + printf("invariant-check: failed due to database corruption\n"); + } + } + } + } + } + }else if( eVerbosity>=4 ){ printf("SQL-ERROR: (%d) %s\n", rc, sqlite3_errmsg(db)); fflush(stdout); } @@ -971,6 +1099,7 @@ int runCombinedDbSqlInput( char *zSql = 0; /* SQL text to run */ int nSql; /* Bytes of SQL text */ FuzzCtx cx; /* Fuzzing context */ + unsigned int btsFlags = 0; /* Parsing flags */ if( nByte<10 ) return 0; if( sqlite3_initialize() ) return 0; @@ -1058,7 +1187,7 @@ int runCombinedDbSqlInput( /* Block debug pragmas and ATTACH/DETACH. But wait until after ** deserialize to do this because deserialize depends on ATTACH */ - sqlite3_set_authorizer(cx.db, block_troublesome_sql, 0); + sqlite3_set_authorizer(cx.db, block_troublesome_sql, &btsFlags); #ifdef VT02_SOURCES sqlite3_vt02_init(cx.db, 0, 0); @@ -1083,7 +1212,7 @@ int runCombinedDbSqlInput( char cSaved = zSql[i+1]; zSql[i+1] = 0; if( sqlite3_complete(zSql+j) ){ - rc = runDbSql(cx.db, zSql+j); + rc = runDbSql(cx.db, zSql+j, &btsFlags); j = i+1; } zSql[i+1] = cSaved; @@ -1093,7 +1222,7 @@ int runCombinedDbSqlInput( } } if( jZ)J+gCW&YU^Zesj*5oBZ0{mi)Ti zmVK?m!hC!}KlZuxpWl90yfw_nm>gL>?U+A1`9x4artk2ySC{$tH2LWCB+$7h`}z2U z{Qi6*=(gv?-x1Hu@$m^h$`^xnd2))6PtZ$G^Z>O#KE%hzzM#5;kB@D%2PuJXmMsMx zU+moYxP9y6c814-#}|);$BM@fk3XIOJb`#@ctqYHJi&NE@HD{_iYE+DQ#{S^G{@5d zPdJ_kJdt=>;%SAaHJ&zj+Tv-4r#&7Ao+vyW@N~q}i9c?S9ygXfz=NhmTUjQW8dzF9 z4z#2=6STN^IOwtBRM4ZvgFqiE?hpECaUak}io1h8T-*ipNO3%9QE?pTL&XN@gT*e; z2a271#`NfQti}Ds(fEB|F%%PcZ*g1Ddx|6I*BQ}ezK4rdespH^y+V{~$AX39u4)}?ogkB#dR*F8Sbh;_vp zaS4fCyT^9!92XxS-z7G_Yv=g51Pc45X=Ll=jqGPEU6KIqj?A>dX#*fU@l&JaBo-h> zQOekq0cL)A@zTz>i!*t4eCOLlf>`l*yu4d2UK)RU-rMq-yz-VWMQvu&?x95GYlDL1oCb5&%juQLwX6&g#m*zZ9t$T1=;G-7I;=sD4q;@^}( zDIu$G{Qk!yIaTx~i&a>7yL#oFxkvOkOx;&h)8EA$*)6Gz!JxUk(2`29I=%8j+cz zIUG4l93~$}p2=rvXBWnmNfJZ}jJu`i;HCH>hTGt@w3HE!A?btD@H5_#p5{P>;y?T{ zFhDg&zm!p9Qc^@ZbY@JALsD|u0P`oLz)wxXZ}GM>gaJc`D#VL(<%tUFb7<)!z%wl~ zHC5}?+soT5=@>D1&|RY(>6xP%q!5FlUJ5cbI}Z#@vNgdf7WhiN8g2V;w1J0~(ub>IJ#M zq*r~56NCcrUr$>wW|`GznUBR_`7FxkO$vP}YP#R3;t0v7fC^k#8WxV1<5)Ny8845r zwm@ocIz313NNp#`-B@$lm#DU;QiCiC6}_M zq8dM+Dj(k^mM<(5*g4jlLZ`KOP$~#4ZcB0F2-D(Fz`poMePR^ojGJu*>V z%G!G?&z>X?VC|^xX<4ScEQne+NuFMqElW0EpFeyo&$3HCe;f>}`%WsDSR76JLX4Ia zd$$}$O>*Q1=$n}`rru2X4Jyx(Bc+a#&t{6+CP(w{a^#k*aH;I)*W1T(mTjWYeeLx9 z8Iu2o{>7@9D-^=U&XQa3Z|BI(ng52AVnw7^_b|fE3iw;o{bVXs@t*-)vc9HS^?j z#$2>$t=5Wi7s=Ns4XmlpGP$*VbaMa9%we_-Ba35h6{Gw`^2gME8OFfsIdb2cTsf05 zi&?ERn}baYDGbZAyE}UncjVHB04BSnQqI$5QPw`5mhpIa~AU z#WG|3nH3nAES@g3lk1h8G)uOTW0ic9b>@!M@^%aBLOI`ey7gA#@2^FlN(qwB7D`U= zYNLrL(wrY!FTcd3?vl?&@92fro6^_q>EvO0-a?zsQzI zLpI3ESRy~QLB5AceZ(Nm$wm#?B*&6xlROP$Ud)!l9JvWy*e>T%#b&t~>qVzG%P+8= zl+@G9K9oFL2G-L<kTB^UOABs5ZNxVRLZ$eewa?~L&>33y;yHmxL@8& zpYM}5Qqu#n!Ul^BQ|W;N@^tH6&~hG~-X-5p4WW?QtbgbB50v=>57F)Pg_As zaYkdZKkYsww`S>$2@;JgMDvOdp~)=>^Knt3{D`RCPG6DymNgSKbQLuWr_g)lt!$Ji z`4DxzPu7U;m5U1R$50v9Tia7^-+cUc|=m_7BPdjE$xn zV^z_nVSMjHaw{fHG8YpodU)S4s4U;z)sJoz$q8&CMP0+(9(qJ>%Gd--eggw=-()$2 zHa{%4k;aR$p6u`rJ&C{Zu)NyBrqIbB(fiqt$qQHxoqtTuXW5i-R9?>}Q&_)-6?Vl@ zIf^2V$s?sKF}ey4G%RG^7e_H(uO5Q}E*z7eWizO%7V|p4L{6X&isjwXbWtX`4b}~$ zkudYrD1=e?B@cRn}8avA`U0BHLcF zK8^`@`EhwQo99i1NG(Ld6hY8QPsmQTjA9eeVsO|*Panswvy_T{Lr0kj%b%20wuC?Q zr0mbw;(tmokjtUgMQF>I+eC(JeiZVgJd1whvd&gF%43QhK+irUhf1r2YQsLz!$mb4 zY1Y#im@CDe@&sE!#x(3Z4ehj3(z9|CwwyAamEVz=7;0fVy<2K9rJj&ettCDp@pP>c zNktyH8J#&HN3cAqJt6OB>%H9rX1(alb8gq* zPs{O?`hu)l{ZMw8x9s5;(G@RHn# z9(qx3VYQ%qYj61vUzA6)W_0B>^y$7gu-46e3Dc@6t$#^g$HK&trqHQZHyi)y8}f9f(uF-pt;$IVp-XS0>vz2=2QuZh)K}ibejp3r5=|H0hLFwA$`e?0 z{Zb@);k!{ze|qpNAVnvde@>30&(31hcJy{j#v>s?-W*Biuyc11IVM}9Fya+jbPgTC z&dZaTgL2Nx2~vB=@5NMA=jDf(n`d0W1jYW3$zOy`-`q7FpC|O7y}#g$vF9z>0Jyl- zg74mv+pw5B6M|{b+Zg>$y1C=Fh6d5Ww=u>wuTn(wIhxNH>r7DxbZpK4#uoCxyV#QA zZcDiPUr2}*OLEV`Pq9Ke-<229z9aHN`t?2RJlMsBmU2ImoizCa3~G>8^zHj{FiZSj zUMWBnaec7#6n%irKB2Lm4}n7(h2Uo|VQ^w+$FLbrhd+|LVta3>#zoN|%RRBZ#~)8`xLG3@2zCtH*yNy_nF*5>L=EroMl*UMOxvi7Okn@ z=U6wA8yL@_RbR*lSyE%0ZZV!suYW0jBMlO3xe3BAd<76bkSBkQd2LMr=hi~#p;UHN zJ}C_l+02dV4(Y=AyWhwym^8#(5XHiV*A9yQPX2=p=IE<#jNRp3v%o%uKYd;9!q_l= zy-p5dfEpn`%U{yRKVZrI`g`=$P{NCE=#w8H$#9xH9sQI0H~M=0kMg4|gOd7T#X~|E z4f_e3e7Y$9I|ctN*U_)P$WCdr5VHDptUj?V6+31ADvx8CRJ&B{$nBIc!Db`X{Uo>c zn^ZDN;6q^`X{=!*X}`;ztt0V5@lJJck-UX$e_)Xr!Q=kGcF4w4c3WjMz3~@-@=0GM zkj{oG0dzi0$@R@D87FX<`En=VN7B>Sx*_pfYz*zc$NP6nzU(G&D9bW|h>)x=1u$iU z03s0SgdSelO6fz-rJ@PnF=Y$wV9Em8rYI`|3ObfdKpTfPisv?^;E)2qJV#VLp0eza zCe{yf)&7MUvDT^#XW9ImRq4rqLL5=ZGH;~Pl!`Ke7w`913fXjT>aGCgAps{gdQn4? zO&LP>1S*@^RMFt^g!fHphfP@}O%a)L{=~@*(kF4$7{&k-u!v8j+z2I<_DxkrP@SI= z0gcQOVojtiAxab49j~Z#K150N%Pj#|*hpdHFlNA{pLkCII}J63(e@_F`)s}tt)A(* zP-Qou0CL<)@E3YtOcTPOqROVqN;aoatNm_?!oO&yj29RLHnR5tIvRN9(ih>14$zU? z#>?{*-~(Fm9$P}$J>-_?h!olzsrb?SmdYw12(%=NQd%h+CEx}?fS+-S3~i;rtRq^T zze1Tr2U;s10StiSb$|f~`#E-h@Z3-DwNnNHuOMl@kabz1L+Q=d3Z7Tk8X?nffEy_H zCVkOC>Bv@zq}RxCQ_PNVMJZGjJ8Q**RK?2jI>UIXl*fX~<**{!qRSP~PUXU=+OZtA zLtBUPNYJSAW}uGpCZO%h?V#<-{XpB6Gtf3=Ye8F=tpsf)jIcJ}GB>ED4E9MIE7L)J z%7m5jW?2mAUu90vKgzT`{PTO63i?|aDh~X$Oa}d_Oc*eKEb9dNLs>`A@5_WS^F~<| zXkA%*(C^BGVe{LvR-oUMg@b-w25WQRmt`TKUzACppO-EN{j78@=%=Mwpr4cqW9yaD zG|<}8p+0#&ftO2#we{mtVQu}WGzs*>(%zt#N`=k!-=)Il`a!9%w!UAAb_Bjxs)7Ei zREYR)sSxO$QX$IQr7c0X;?LgN%0CB!OIsn&(18jIZuX+zsmf6h894#xcI8N@5k0o^ zXwX<%Jwzd?ov7J7px0TIM4B^H`2*NqSSKhyRXNQ%6;1%-as4BjQucgh1zldK*b8T3 zPn}ZQQSdb=cbF1EJJXaRV0#SEcs@B@u`wWP^KJGtk!1{K#?1^R1IG=MwPU=J0F>P%x^)d+$d$nXr-Hg4rg&1aLf=BB~r9G6QdcMR2$0u zO%Cu~Uh2B@4$(sy2|Qz*os23em2=5NRy>#ZPv9D4NTL}dYl ziM=JvSY;$_AE&Hk?mO$#=>GA_G-lLHP_{Fm{044`-r>08Cpn?89Xe-hX@XZF8PkDt zFjNESqe(y!3nzK!hH|Hq#(Hqm0IXH8ibYMKT{616pi*7DZj?+h(3JzMiN=IpfPif!6g-R(MouiDSKV~Zr@(1P$ z0Ms9;m+A6cB}(M!hhJAQpkU^iuUw<63zcM;5mDD6p~9y0QwpYG?Oeqn6)Z0u=za0* zB4rvyFH#Dra)~muaEa0qcEbj_)`-NT($HI-G#XPOYmE{`XO}9g8Pr@qYE>~`_R^qb z${1QX1QN7niYDe`0wwt>tuQX`YE*0=4qRn0mc}brLgQ?h_YKCrN()wFwx&}0Sno*z z%Zx}xji&+p9cz`v5^#Qj392b{JJ#x|doe>Z)}nRqu2rHXfc<)d%!REO%MEYCsMk6r z9%jYdb;?+rMA!~+HD4C3QvxF3Fk1%grsy zP}oRw4!{@%dxgK36O$WQ0aAJXp!PHV3aI~>aDFB2<4FiGHVaS4twkgd?-;M*& z5+S=m+qNs3v{*<6H;E}(2z|00`duWnHN={WSHpx*PIB+So}9l!>5MZ01kuQ`9blm$ z)k@#)P@3X&0PDp_oJ7FAA067Mw4f0?u`_(PQyJhZ!QO>aKqNhI5XZGKyMRFAb}2IL z!(TuFVeiJ7eGp1#cPR;sdF^NDlrWmVTd{>;2&E774x(OV04+kt^L9hW{dX&!t>zG; z)w?k(zS|9c>xk`9QgCE2t#H|U6cy(N=t8HOJ(!5Ag{o3kZ2Q>sAR9ey-2b5SEeNc%3pn-y03pK?8 zO~K;S%2XNu;h-{FlENgPz1{<5?juSoT7Qp{7;ts-*s@T`XD3CyC`a;l?om!ztW6}J zeWr0Uf~xLU(%Dxvhn1d^1lt>SQsGI=J*Lc~7mAbtfE;kr#8Jj^Z1f);RR+<y_iX;>DW#XM)JbGA^&%3<@qv;`L9ZyWg%2nL`TCcY=Dt$23xIktbbHX8G1!P(F|(po8*T zf>P*JWeR`#U1gw!{apCIa+oRnf%lcKeXT00S;5iFF^sYgDgONHkCb;TOykejD(?hH z@M_*`uF@P*3})o`M43Pt-zb&r&zf(Q_I@meJHA&gGxoR1)kWZXm2dq?k$suYJ-;ds z_*xBUV7}1g^E~7)WtXo6ALDNC(hsJ^d?HhGBo@n`x2mH8gsYMoUvA^aL)4+ninE?A zfKGpc3E1vEr5!mwRr1UxQQLKbr%&pnj-m_EY8Sd5r3O%D6pk-1I@AO73;qJ=Fn!x7u!IhEP$S8|gSwY`byOpS zty8#J@Txl<>!{uXQy_R9u_ocD6o-CRhf<}YCeqAkwYl&l)@NQwx?2sRFj;M76&}x9 zS^Ln_GTy|<>YiXV%oV3~5H3AOSud(~iA}dNn1N#@8|d}BAfZ1w3^k=tQ#--kXgXt% z*oW3?>H>fT%*u4?;Z(C=&UBbqHww-C%BdDuVa{x*d2c7R2c;{jm41y;GhrBP;L{)H z991HZOPvlp(I9^pddRIdrDeLB2LI%(ybxqIy{M~I)ZMLyvq3z=t!fOWMD)vOx)7@l zf)x=4?$(r~sR?vBQMJ+Av1rnEV13Ifs0X<*=H1qZ%PiJ)vpoi&rG#hQo z^{0U~=WI{48J&w)&jW;@q6gr#H2tWFY7(U;qP2P#)t{~k>-@*!uby4h36uoA0C7M) z6+j#fO-K|n4W;+Hs(Y~8n+DMQZt6uiI8SdD3sVnu3WYw6P3xWRY7#bm47;?#o@y)| z?4h>9cAwoxT}SSoY5@G6!Vp^6SM9>L_EZNjDO;SLlRF4MVmEb4uyU+Jhx8Gmb1h{+ z=Y7;QbiOy#F|xP1)^0|(n6D;@SDS%P!~t8BoJLdos!b(fm-Mbj`}(R!8Jp(SOh&SL z3fn$>pZ5!^VZEst!!v>Y=!c1t(;!tMZR@YX0LUE!)JO|Z1lBr*XAe|+S=dbez+GyJ zRhlhUMNB#@^SNqs9+sv~VFH;5Nl;NFz3x=oQc-twffzdc*L2k(!D1NI8Y4Ay5oYj+ zQECY=4tyC26q~7T75>h9Dd%AXiI^ymvS!P{|JxHM|1?ntnKS`Y>EfIT?X<$apBSl&$Nm=SNSP^qp ztMf#HjV@=Yxoi>7n5@ob5;p6Nlw9tWWdvQCfqBsH7_45SxbU+0dM9oIk zSNF(F^%+}WxhwV9)ifTt2=9+TDyA!-XS z=?dnmhp_3RW%Fp`JV*@_iri3F*ZFF`RYKl+g>_n>&SJh^!Azbv7pQ{)pN=d;>4tKSFY-SMFaWQ(vn4L9{>`wmF*Uzl>xWu3khNL z&0=*T^Y`XBzC`^E=8LeNUM)=}=Q4FUpb%I+0~CbQo!QnIbotdzR=ToWog+M~8|Hg^ z8kM)8+E+VCba92c6ksCxPxwd8o_TzwdO&I_ti-@CuyLlN@OYX6pIxm6(sOIn7l1P`!|U~2xei0&*R|>p7&Xk%Zq|U*jxcK6>W;qa z)v>@mCL{c9rzUxr=*`6{Igd`|snx>RfjOTbW>70_Bwfyjn%j6g=}-YWsWp96pn3p% zOdhBppl~CSTi)?IfX1#@;|phMfrXpY;V?Z2g9x44k1gxuCXDAOZ|>(eVMX?AR(k`u zguQ|(+K|IWQ@5y@!r{1q;?`imMsLNqYA1jg+AHKbu~j{3MSK)I9m(;?@bxyeJN%%g zPs?8^MP#x&?q|W2V52}5cfDu~+K$}O`3e_mubV&W3#qqpC zwWr0Jh!Eb^Lijm!>X;f80Bqtc7+l_^QJG`tg?llO6TE{C(q<8jMJvnhQy0UHiL-XS zWV`QE+ws!-)yHgr4jD()zXJ5_a(Pz~A{>>4^&dq?!}*R)tM|QXFin|>8Re-|{Y8~9 z``ZkPI;@7%;rG;R9$O5c#9uB^kNb%jt3CA#mS``eY|j6zQvH2l#2NmKI+~9^t_}!f zJ@`Y1qakzQE4b&{;rA`vBaA9)%yYj)G5qs)SEvK zy&~3tUOStY!xLL5PBZ4{H|6!`AAF+1_1vT8Gqsx}yqpM`scpzmbQKXh*ZnjbUH=p9 z&-ewi@Y+{u29S%nUO;b|^dB+>6<$^2goD=W(i1v1^RA}2tLhQ{>^Ev(iv%z0Hm`-C z;k6y-VhwiU-*J{X{}np(%WG;Z|JOBjs|CK2T;SvTC8eY$S#D(-u%&fY#qS0@K%=dvnC^FnS_Ta|75k zWC)`vHm$Q2hLhWJ1yYb*>naQ;5&MBtaXyfvh@pu3uC#)ch)=YCq<3vxJ(XuAPBh9qEhz7&T zTM3MFrMdPhj2!vjiA_cni450fz_iiO0xQ*pqe)rfw0TRSV9p~n1l=IuYjaQ04fAMb zOYIt<75FWpS6gYLy{=ia2n;^0wHE+~OmuO<1;xGCMqAEi*6(4+O{HzMJS&VgTY>Mv zj^Ec#>tq2yf&uSqz#>z=B2^nltvhH_;Yu~dxzI@)D-tsN1vuGv$r)ITS0h_%0#4h^LrG>-kYHpk; zo<$$Jv?g>w*Lngp!MO3L6)0(gId=-^S3`^Ux_es@GEbvYw^l9?j2Y2SLB zIm_moMZ1;fnaV8eib=ViW7Bc?!d11Xhjxznas;NTOvH2{;0^t;glc7`_CC_oivZ#D3gIrhLb}tgr1*{T#JajRRq6TWo*5HN_vzR^@h$#`oQCl0<0AlKf zU=*Ld3spC7-0(AL>tHQfYG%&2WdbaVy{GjMZ5E6;W>qMa4p@~mR67-f6TQzu2G^vI z?Moh%p}})58S8r2oJo<_U4{c&@hjNE2ToMmm*N(at)bZG z0vDKUvns}b&a4;-I-?>Tbb7^5&}kLIkvz3Rc%-LPh!*Em^ajnY5UreC0f(_It0Dn( zQiX6TPpoi(PN+~p$5+Up<0^zE##X?EY#US23N*8#1?Z@XP`J)*BP+xUV;WNJGM+e=cKmW<;D>=E@SvMvpq5iTpF zYZnSfX*&Se&BI+;CRS*BCf3rnjR}LKHub0DIg~wG`#^#TwBcwgU^mkI$umZ~50Q$u zjxVdn0VcdMRvUyPMaE=pA*~yy4Zvx_44g7gJlDr#IqyAQOOYI6nXX=ngC^1jinGMV zdTZze>>yT{B{$U{(Y8*~-mt>Txp9T}$aXSI+s8U_Q2_&@ztX1RcVMf+LYpD@F(&FbBA06tvPwb?i_m`78~46OZ5vFbdFGlKV6A&%**W@--s17pFR zN7r|2$@I!BY)r9UNM1D^J8{M`%}!%yWBYT9;tQ#EEta~B+0aYxIVkYLY^_>stbx8M z6}opHZYXEvRdcaMin|Y+Oi4u*{bd*7s3dOgIV#>1zMaqW!%b{%r`I81~LRp zn)|RvX!wp!|TXIkERO`)n4+MiYc_sxth&ePVAYZc&1FOHDMK8*FO2S<&9Us|IbVo4mA zS0*ynm!DgQy#)c2*mq5pHsjCcX}`mxDN<%r`wc)A{Tg`;r>`~u;Ux?FHHV(xsC{J} zhyjCby8!m8CYu4A>MfIKemb4otW6Twf2;R!kiS_==ZCgvxL65SCXlGuWs0_GYlXp4 z#Eha)8`^bHYe6%&V}uSCJ=l#t+pcXyup{gs-Kb~>#2v&#c51||fU|%_r^f@9U)+WL z(?rg(^u%s$CoGEa^I?MJ!g{r659CTEMCy4H`0snP8U|1eV@Ht$JZ-AE1MS?}G?4Q5 zYkvVwH`s6N^vD722@_5unti+)&GQdxfea{lauqBkm3M1r1V9E7TQo4S13h?1^T0T1 z4(;6DiNTacS}C<9%?6wddsj3cMjB%J#_`GbXag+PvFNtm6!s+iZB=&z@(q4kdxtU( zX>c(3*yhsdZmvuG@?ou$FTiBnQ0Hs>Y>}4eYniXn@C4_6U?95_+3D^=a2 zHL+)U|AS(e)SMCG&*iA7sU*!4r$xkz+{!$mum)Ij+mFyelMl3^wC_tCc`_c?6rOrs zlPqjLAAV8$##dTky3xcHw1#TDCELHE-9p$IZkXwnJIDN?H?;QGzCNrSW*eDEc$(83n;+K;qrzHA9Ue?`N&f7w5{ z@`BH_C?>&Ry1lVCy!G)PKiB41*a|-RD>3Spo2pNKS4-kwUDXm99H8yL#mc%?#6g>* zB)ISzMndR)I1{w^4sCz_TbxxM`3|slwK;Ql>E`ef*){@+2Lk6g$pM!jEa9l*zbPS+EDx-fD`tHatZXzAKKHv?_zeMGQ=JKsVx=o-JBlL)cY@O zI*@zbo3Ic<+GceAFC6Z|%mbAvocks?)8E*2LpjFa+r9uHrtb;4n-=;yS4z0e>UAt2 ze;9w%*BQfD1iv6TZ}ImNnl$4XBm&ku3JGP1cQ)EO2k%&6pbn&t+_MDU)5Q(}~#?wmtaQO?n9 z7lk;SiM)3Q=L?MOsOjYFA7BMSDO@A8>mp?DKznz&awzzi>pB%JbV_vY30D*P=6;t= z2)B3Vd=LD>|EL0&sCL)HI=>5K4t}k>Gt9#F)%0?PNx~I;D{Cb0pX7Xz1>6lD@FL&- zqE|n>UtVF(Aui2x-t=Wf zG(0(`lUmcFH$1Cu<4I`yYy78b=Po~9@TPOUueAc~o~UPc{J&xMq{)t; z%3pb#mz~r7C70xLmcoYUE(%>7Bk?Dnat`seJ`TQ5QSy9OH$tyW=lAuBnT)m2$M!Ur z-Vy!w3=Ma<2J>O>J5K~iy5#c)McJ?$iC(73SDmdX^plv`Jhy2KVl-lNog$Xo#%n%v z{sp%?{r#15f56G@K_0i5UIjk`mDKK!QR%ai|IFYxtPKi4hL`Z1q2bM9Dn*+m&O>8T{n_i3gD*Q!#-rK_UKSZM)|b7( zt0QA#8Bb^%vnW7-_os<{?S#V@wQhg2mf(WQ;Seu`g%)vUj-s9p^8M|B)OMU}5jpa~TlxWPtECyfNkyi`HpL9$OYV*N z++XS?`CP=I0=qMIkC}qyw}+c}dh#nrgg{^6_ji=DA*P6qU3O(sY@9QY7cF(ZXQ8~4 zF-l=oObcGM-}yHaV}34QUmCNSvFl~|XeZOl@>^8J{>ff8IYPkB)!Dt>^k`Xpux zW53pX7Gv|1;K=cAK8G?~DSY0Oi;rrpDIfbL51!;2!ub5}Vh&j>b|1R& zqtnG>>tfCXNXe4Vv*t2D`-Z!gP*s*QhFAX@P zk=<0A>BQ;5$8N8Qb+rqCi>Q4!S1k){x^t;#Fc`wAiFK1hMZ2)(AJG7}eC(k$ySY16lx>;8@+*v#)T7(p0SF_GUGAxt0Y=X<~yoJq{;Bh_O5%5(S3c zUO>p6LqkWpj)(?2fr+ zZ+pMPlg=|{xGEWISu@L39Vm|0-U`usgQ(eJmpj0*GtM(yxC^UOG||mNc-dmtD8|~= zEOq@EAZ0d`APn2Cl(x>*H=q;two$k5Z7%vkq<7qz<{2sEkNV!b=ehX$^{xWOqH6M8 zNkRB9LGmsa5Sy}dp$A8F5@s!PQ|-dX?r|MsEV^bN)F3=eh28;;IurgQfE1(8xwDT4 zM|NUD`$PUy*5*vDIqZ5NK*a78nj?enyT>)1QSwn&q*dS9$}^s49d#XHET*Q|H8Mb4 zP^{mfv2)8_wpCkvgL!DRD+11No>ApG$^@2;L;U@5QDpLeEb^Qv5|0C^>jqUl=Grgj zvz01Ox-wW?%_&hf=YN!qr;68Ih)Dh$29WG@=V3av+cf|&Zy9G?*C_lA*Vine=1nmm zx>0q4Gv2g2WG{2pG75PM-GsHypMUd~E0SUP$7 zp&K8$hFW{x@AgdL$sfCL7Vlb9EBN>3(q}FpqCWq{K49;A+dj~UUVvm*ItwIm-_nc3 zc=eC2G+)-si|JuL@XipiM?~;`f4Ttw`%}oD7}h=i7C(}Y>pcR+rx+d@*(_ifO3uBb zB;hT-N%5yEjf#4>uHdGXz5-5VRbLYb=isT%`e|x^%oQdCm~YM4(akfTBe5?$&NT_T zHBf?=@ovgF>T(H2qiJVv{cylY;71rF{vpEw!RVvL16CXxUX!E;+u%728KU1{)=_BL z7#_CNIi2y>N9kk{YsXkR{iE|JFU`<<28b~76XNvf? zt?!rA_q~%>&eJn|+4#a-eUbM=60Im{k=~nL%he}}SZ$oIMf`BzMLIQvf8p=mq%YPt z!oGs&!VEB0YpQEYV+twZyb{WG~h4g~`Ocq=COP$g^Hw=iAk@M%b`%n`x=u zmwGSPbLi7$`Z)S!xju!Wou^a zmKlq6e;T|>A7H8UX=IyDzpm1U!xCafqJUjGJ+T_>*7Mjky3Pjx1WW;&-)*-$!=r&r_Bo9UTAjws5@*B8U$V79O!p*@g` zj!*EQt^Ns>HmGnu;iDL^h=>yBH|X7QxkB%c@$^65tfhk+^fCP5jXEr#VbtD$RwwU7 z0l&@qTzX=YKAc|Itj~i1#l1xrv0|p0Pi=vkgN3GazICfUiBY?~`X<~c(CxHjyFMBw zkOofi6utu;-K;U;0qV083f#Iw=eUlbONF~o9_A2n-#u-Qev;}A>H*Yew{D}Hm!ezU zmZt5|?eNl=O;6vWpJ(B`{a(EvgEPkTA*b)xm(f*1!9Vut-Rm#5QQCg}EyQ}kVevCO zL1siek{+k3?RpFP^`Jga*c9q7#^l|tPcXk3(Uo!v^*qWtq_-5I=zH~^l)FbCNq&WB zT>IM+z7YxSypC$HUGA;_66x!4)9hj$42=8k*PB!7y?Q?R->XL;vg?ji5<%(su0fsN zi5|RP_Y*mTsoj108;J0N5v#s@#QpkY_>USAir}h=JFLGC-weVBV8rz3xRv;b9z&BJ z&<~3U^9?loP5@CbUS z-NSki4L_pyhT8`26IE0&Q>11f#tXIAXZ!nMeXuvs%PR**wr80L0Y|eKK28A{LaDMu zkEd;q>966#7q`lSnVr5pin%xZsD3}ad`#cT68Z3AJ%LF$>Y1Mmi{_pZ9XA3J2RpFrdRZT(jszN~5R|SHus}iEGt%Qr%zNT^o=;}%#?yAapperkd zperhch|4SU;1{+ptAtaw z&jQd?k5JKI43lRVQvdw8aTcJbtZcJ@pLb$f*VT%HM_nr9qnb+rRj z_K05Z;K>C2zXnhk|8Ef#h2@yY=7&{UQr!t%qle4&A@D91C1I+Y3GNDgyA^)M4LvF9 z1(%AJ+}@-opfusW0_Ci8;6JxIO{* z9&)b2>kHOSk?KeKllppb2Hr@Eo{ht?qXt4;e^TFqFR~ys6i%fY{Yk_vV*OSr*57Ew zw{ble;L3FDH85{R=})6be_^@4#k?80p21$xPi)TpsLx3(eAl1RN5D5}1^_k4fIanD zy-<8xO=QbGq0fLTvElEmKu4^I44PFri1MG)zrkG#SpEA`<@0(f9GUpM)vfye6vrp5 zGM`|j%RY@wYtJeDDYzNU?Gp(n;9*4J;j{pX#JvjaqGsW>)bB+-5+2TmO5q25QO{x{ zdB#h6EW^zNl-diABSLxK#F^^jSM)=;JAuT-6x+lIrOV~ImHv2DUxiRTM0(>RWT?IH zZC$6V=k%b$*Yq4XBN3Z+=Lj-NpJJGOF(OEr^TAsz;tDn<@;6{x55}o}|X@cnDIlU+DQD77NpKqH` z6?qX z0e>X|l>1TLTL3Wkyrs8*=Mw&*#)|r~ERGUz!_O2b$~&s)wRiL-UdJKOUSG<4S6_gO z5ZB(-OA+%IcUZ5_=6Dr5#25d$c*nKgG=z&n1l4 zMZ)!6KlrwNsIO$XoPC5Y5>bEn$doxXz#Op#4?^aLKGvH6;@=@hUy8k~cM!KJOpM!? z8pIBw+RHdVtf1sty@--(0sNYL3KW!ZMNgwEwE!Lwp8^4&`V2j?;flVGp7;cRb^HW> zjrtVGZLzp|Q!m;>Ut;gB`c&UfOTGbw5oLgg!FUWk_bEop+n?)hk$S|-Wbx;E7B&46 zyYr)8=%LnCkZq{9Ent*N1HRNF%&5a*W+F!-+&@dM>!awGuh8F*e5L0FIPm>2SBEJb z$oaLtEx?wRl03ql=%8o5)+bYlN7wvaW>P;Id==9wkv_Pp@1P&P(P4gSR8U*0`yOl7 zQ+6YP9{W~rg~+>{@AM(G;0BawrVjoN@X24K7Qq#Wj}%~p{ee|2<(mE=Zd_oV^e2cH zObOR97(TtGZ>C?b>(hZ*%?n1z>k@SgqN{a!cM*-ZVGKoCjbv|P)eV3QOq3h?2Y}b+ zO3_ea7A#g6nEj}z4g&pAhbkg|(L)M<)bFmp^so&u$ISijAN7-f(RWlRQOVDGCUyH+ zR}e7>Kcp$QDU10k58&DiPH3nvi9EmHNE0rudgFSV-M}{vtcH~`{0%rxf75Y0_cx4& zMZW?pyMEVuTbp~=W~(uvF;6g^`5hRv8Hq0ja&Yd+tndLMHUmw*m8}I?{sOFTDdL5J z!Ea)?)&8Z=Hor9SkE}Sx{)PGV{NMU48vM6@jN1Q=MR3+nx^cJhyZGGPcX|pw3t&vJ zA|49g)4HP`fBF$t62S25cVm4*J*hm@*O&{tkhk1v$(R73-PUSc1HAs9?Urb)pHT^* z{XbuQC46w0Ppb@bGgB$l4qfF3fT1ixFwJS9HKsPFu7Sol@1=@^z}V&%fFc33&}L)- zW1EXGwkoqoCc?AKsRa!>sl;wPVU0oS5TP7tOdxktBZ$rfArXjs6FyZZ5}TA_{(TZ` zR3J(Uu=z4jHkQYMyrzi}!X!9;>Nl*=c1Cx^a~i>d%|S{J#$UO?Mu1eXzDnm8!tfOU z;kq(EYZ*>=aEWAJU82gUbF*K}&aX;KpSf2V(b}OT;sKQLe6}!iP0V>0d z5kU2*WIqLm7&GZ|3&XE4(h!$99WL}x1Ha~UxTO(K|B5swTj9pph_L!d;}D&1X{5oP z1rPPDtW~Tt$K^8ww05UHZHzkDrA*`jR^2GSt+8B!i)53xpE6n^ZE!o|0u}=t=Wa0x zpr!4NZ((Xew-k#2stx!oo%6YER#&hKy^Y#%NmQG!Q@c8YW_c?6LGk zjPW{954M^|B-o+rf!&279P`%c#t{G?fVtybyC;nRrHp=U4^rYazueb=ooG5` zq+or%HqZ#-ladWYwN3M`&H4Qd4h&?T6T}IgPwtO7hU=opKaNp)ijl%mD#yZR^Nhh} z(qJQl|2D+S4#lVl7tGH?^&$S@<@3S(wLG)2ucqNvR5%6I@n(+kbB*D2D#b{(*MB#>Ahl|}I2oOXjnO^B z_zqyoMD}LxDKm{5u-Dyc!M(H4B-|;OZF~%)p^3K}*kUToF&^=&_JK$^rp+``j%Cd? zEWk2okNglV18u1ao7T!g6zZi&H|$af~WYA4M;?wr!kao+ibw45XO&e!G6q|^Woc!bPH?7 zkL)mZT7X#)g^2L#vql16z1zTTvljfw9^%=;Lo{Fn|KxzN!XibAZv-H23p-RV zcNniaWSsD&c@G#<`T2W|2Ye-ldoP%rVkYsOT6VLZIYYbb;$vuN#u#xzm9 z8Nc+i(Ftm~X}G!ZtMQUm0us6<+%&%EZ!Cv&)8Cy=v75sKDQ%ST4v%5(mnG)nH>~au z3%}XKJ;jgbgt#AM{APqZ$B$=+yB|WK_U?3^7T`W&Wo};A*4@NHHx(}*Nu($G(qDzf ztYdY)Zd|S8H=@CeZo1uD=&wxoRB~K0j`6*kJ6qz{ySVL)Q0pUXALVQb&*Fb}b@%n9 z+JWxww6dc+lupGPlF5zNT{dnqe!aK5g@sGq+~JalR3Wn)#0VbAxAt;RW7fBK1yslL zx-O84U93rR@3#uuB)V&ZcNnJfUsBvFd||j;IN1FcM%!@raQ@sdccrfg4Zk(oy7K3S zyMML7*n`h|m~*5HmonY^ne~GWGpf5&>@cjksJ9FMYP37u3VY0vEcZyhbE3Nm6LA!P zdUf7;n?TpI+*Z0a$^AI%$&u?ViwG{*)HnjOsA`Pipzc%LxxiA`A)@iWyKuXyE*Uf3 zZ>n3h0NOOx=jG;DjMK%<6!$iA402DWeFKdaw0nlzE1;`4Y zs;U=!%WQWm#`^IibKG}Z0An!qhw+W`-7T01Ilqky|8jvl#R72Duw1zK;ze#i-huqc zV)q&E9ZqvgN#dSm?k6n>BscdY=qH#iuXHQcDzPU~&PsQNEl&K|wK^5wCo-2dY&gNR zV3pfWeOI{ye203!TZALpDt8oM5SCSiN>;f!E`R2%c0Xm2G9;f;Z@(wK?hfL8*SQy3 zq)*eT)5TsHO&_mxxAU7neeulcz3Gpw?h$-gzPrYseL+zh-Pwd6as1FWH$LqGFM6ri z^E&Xo``x(~>*uf@e$9s;awjouxLDVJT{bvJnp{1Lau@NjZuya$3Vvz$6a*fZ+CMZb==)E zK>S|^Jn=#fm2W@cUhZqHLsopH`H?t`k&W&YKIu(&IV1I)yPQH#yW5FGo!Xywzg_c^ zJ5+*CKkSS<%o6bP!C}?#=^qnctqA83@FcVT09L;UHH{No<_1e+g=|r0Vf;dd=KSXm z+<}CG?(z-aAT@&_QZQ0zs;vl7*s35f!CnCYhZoU`14l%C*qNiXs871yOlF>kJ9t z_4oeu<>6VGbI#st_r2;~+Yddm{YC#E!0mo$>FqE0VNb9>@9z(|&7TbToFB*1?9ck4 zy|-`m!_HtQe_y~Xe;>dte36(v-LC z>W9kS{uRB z+0gCVhi5Mb9Fn~Za8UMA!2a1w028wp1NO;Y1lThh`hR=3?1g}xvljq%%x*s)f7)fw z12nQ>7_h6^P}$qP*)U|-UD?ps+hem~eX+O7hTh)ZJbMOUgKTK;?e(%@jBn){n)+Av z2#oukY*x$P&Sn$GFWIb?znP8hjJ%$W?u@*ajqZ%RnvL#^{4N{a8TnN`(f;0&eufcw^t-?+m!!kMoZ9b$%F0>}&lU0oV9p)3HC1>Ti!f ztNrZ&GyU;^tNcE|m3{*-!>^;T%h?#O$V=HMEb?MD3X8mujbb9tXQP0~bJ;*0c{UqJ zBfrcBw#d)3fg$pZ~)nQV+^n=-BH9A9KBKp7Vg0FqchZnfI+TakLwk)nDT+8law{Nk`b=Cq+(9vz!NXDhO zYEeN>Tss<<7q?7YUE!(~E;Zv#`D1u)=euTzE^A%G%Cio4f7woqLj~R4$LZ3qaU@zj zW*E<&R{sC8^jh#PGM(JK@mLX6^U6YDLh+RR~Xvx+V**sjor6$oR3g>9uud(mc zy)G@S+I2d+)76xEzU2}&Oewp(q^4+)&V#h_-Z&nT!w78p^{UGo&cS_NW0z609C+~@zv)sW8!ttK`UfR3 zhXH}r;;!+p4WjF>$R!q5bi+%Y*KpT=N%e&2XmRUERn5IeqUev}o}vSHUHxsm3-tJx z^y>*&Yl@Mz-A5{r{iN&ZP;%ZzdjJ0bXQC;n*bwn;W70m826D+yiL7?+ zz7=gtK`#;MUw7XUUv_slg^erN(+%>std#sM6de-XN2PG=toC7Z=1`?%cU6i>cH2xK znk2gyRk~kLL16Q|!99<>KNb_l|Ij zznn;W%)LBJ8Wo(yg=1R+?FHvCS~OYUJ}cQ^k+s7kCj-&|$own|PpPdm^LpGm@$C|K zyv-JdJz)vw7)(pj+%qandM4ZtPo{sc0&VzXRptk_n`_0&Bo8)U`y+IDSzI13`=Wo@ z7&;;GlX10zPq^XrHkqq2fUpwRh?sq@xo}OrKeM;F8zZu5mb;x~gK^nDtt@R5v3RSy zVFlW@&%IB$X1E<@`3rGll3SK+Q;`;W4B!<*UUGM_i=-{Y>U{TCl5NiB^8Rp2DRgg^W(JGg*DBJrkKA4n z^RD|M#H*8T?8iskKiHrl`1vC@j?>H!o^o%n!Zxz;Q+GOCc@VsFG|V3}&$v^eE4Y5f z9c{5OIbSTI&$&-Z^!2B4O~h|sxTC{um7u?vNt?fMZ=rE-cD819>1?CdF1v?7X&`;& zz5#W?t*_j;@nTWd*X}i0Kf0fz#^1Q7LPfCQ8}~BFR{4JRm%elRIVfaWYg%^QU7O~A z@75r=@BiK%-@I;Ex#nHF4eHdTXUvT8Q-6++i1ycc=v7L5#8iK6iu%Dl9rlYQKe!cy zk;?wTJF#Tuu zMjQJQnXl_p-Rl^0#D}`>ZVuDNqU-K{)&`8zZ0dsV-BqaYy1QaliQCPmGBObn_n4=e z=u_g}!nea*zTs|Vb%&K(MxAb<4oz;ln^|3~=S;Zeu1-J6N=2G+)4hf-mrHYm(nQ~K z$HFxSIcw68TkguXSbU4(Z#8fX&s~u=+;TUD=TP=7cQbenndwSyxyMS1NdB*TeVA1a zD~B79Pz(CyH+LE>_|yG^NXYisBr&U*XP87grh6OE;ftOs;^({W&0&;!*Zmv2CTy(1 zxx92R+S4!hj%QJrm{-B`tVKA&J(ID`BLOZ(Dor5n{OO)1iHQ-O${Y?7C6A|5hdfQi zpLWlZFj`pEvsOrzJdi`6{@;k+M@Dh5s^|4Ej*YYtW_a^OT~Dz^B-Qb>ki^0m&!0Fj zaL4VT{c)adbhV0S13N>MWsdQD8D{H*2${3*RimnBxTxLKGdhgJFqS22;dwWV=DR#k zQmyk&yNHhUJQ616%bq3{@vO&lP7?EV&wnhUkLGEG-|ak5{3ZW}4RT4m=MOPI!E@Il z=5>U265b4Dt)l3r9&50ZXLXpZ7b-A6RQtnSJwc%E>(Rrw%E!d~WY6CgnzhbjrI`&p zaPqz5h7$gw$3s`Wo^^0z_y@xNo`qpz;V@5Ux~WX_3$Xq-{SRU9t$Y@uih$jWVvT?)^F~i z^kffD932Ytbfi0}o^$5Fhq`E)XMPwI>)cN9*$U4qVPg3cp5~I+v&!>@MJ!+M85Bk* zy+HNNI?uDxNRhhUb2H2~sy!sp?-#xe9?oOjle;M>Hu}wgWm^>iB~*UQ*r;V z`CE$E?>yZ>Rgk~lgZNL?_S6ywuX^5;Y+wf;#*J?pUCQ(%lIuqg49~Ybx|nnw^Ab9L z467FO$wMR&{js;IL~(cA$zt*?PX$SuFJ}GM6KUlG&>J29DC-~i+S9=+-bTD)TG5ej zYg*}u=Jn7ocRVX{=X-y(i0OAdxDnv-e=UtBhk2jPs^E>3$_sl1F9h@|0f=jk$^kHS zj!FRtZI0*wWJ5<(09>KN8Gvl)a0DR0IqU)Om5#^hMp0J5Q@VgUT8qe1|pp(8v1 z(a=%eyzl!~pgD-<%|J828v&LAN&-y)uLl|f{v3dNo&8#%A>dD`ff)SxG0*_;YJf$S z9|H9NzYo*}{4P)j@Y_Ibz;6Pz0KX2@1iTWc0r*v*I^g9%HNZ=Os(=>*RRAvpDg&Mm zR02E~hz2|xhywgFz-oXm0uI2>19rgA0;w!a{U^YhfinTt415}3HtCZ)(@NvgaMui!0pfeQ9uIxFi;Ng1Ai*uasP6__x;NNkNH_k@SdNw1n>G61HR*D zEx}v<#{u8;Gh1=gzkstH@y`c5?4JkthM%fMgG}C+Cj##GPXOHKXQuOY|2V+c{9^(4`o{pi>K~2RK=xPsqww&uebg^Y23AIz|bwl%y{B$hD#Ek|wdZpj9D zV#DZvV&gsJ6{@w{>!6)c-d{vk6K~6~d+XinjiE~;>ms0VWAE$b1Lgi5cQY>zVMDms zu;hPnPxHP*yIXs&SRpn%T-!MBU`b@i-mziymDjtIW48Wp*N98cd8$ctQ}aF}GTV7a z!?K`xaeYL*cQbSI4`b};?J3z{sM}cMpY;%@I(vf>bMX)7=;0kAv77$C)jZRKn)gA? zp}l-KSCThTk|JMOmh81kcCmbrw*m;%DDO2Kd;AqTk7q`BZ{je_IPVq2BOl_85Oc?P zt5`(lB=03jJUYP}gWprUKT2Zuqh64k%*VX7!Sb;*|Sit#~EcG_Fi0zBL=Ow-%GPIe0CW(IO-rCYDvs1ki8v#Q!BlNm0GCjf7 zoR#J2C9*TUh-OsleojKpN@B`dZ&iy_TP$1et%znl+QMf|HU+dd};zNcF{v=e?V(QUg(Gr+0oBoBQtPi;=_!d%W2esi9c*s&{}*Y9vnW z_XaI&vAdtD9+U=$z0Jg>8(!!!K)uR~wgK+-CSqBRcd(7cgD1v{v>b1=MSNH2{SLQV zzjCq|d4%;pA`;RGh+fH?Mp_YDgNPP$*g^+LPm)xspNm|5ce zEsTACHfo&uWpXX@EEJn=csEL-`fuL;k`2bNjS7ED&)Nc3z2R-|BVk)I<#ky%Ji_cX zLa(hNjANl_d)GTmVn?9+MRm@-ChrXsSrugP(=SGO?6f;vzC~Gf`IeY9*b8mfrplgY zMTbavm?=wa7|LaN#7A~{Lzpcdg_|NloY)vGe}^+=O#S1;o~rWckOX1MCavl9+!__d z!J6{lR;hzHP)~l#!k1Ns_|?{QFhZ+J+oR$;?8wmBB)Gc^Jom z7IH0d=yT7PFj;`B5|=zOLX^UU_QVoyd_%;LYKp*m|Rc zx%W!wCwncneh}8fso@5>9X0PS52O=ylo&dbELVa^axq6v;I}(%eICe$R921xwQ@XX z8%akVm2o5O5cxwJe6`c-@yld!Y$!0Wr_lQTWvMEP&co#=!}x$IJQV6Gqd03->NZ;b zUFt77jFHtawtuaqm<@91oA`dbJTVOJKqsG9>d~bqvb7Qshcu zd;vD>q9|Alnj=@Su-uUW^W+Bkar%0mJeti>>)@!1?>pzqb1m@s*|bkyN-LJgxD90~ zD)HiCfPJZWytY)1p!#X(mG>6QzOY3B_{c88XyQJw|H(25KHm-MD`RQLQaP43Es+nw z1*p?f`Bxi+l-04J3X~R7Nt{_G`*1a;h)$Jp^+eQad4E}H?K!e)#@vT{d#vtXg?mUA#a}fds$Qt!pE7xH^qzw2Wafg~Of35=sN5ge;4J*t5 z@Mz;b5QFqA{j^RV34iD00y&l5TQ3jB0mph9WTz?XtgOWt7SY~~@+SB^WgnH7QNkKI zoZfg+UJj?B_M7D2Sk6dqPn|Z&D=2?6N`|@_j=<*g@TcS!tguc`DtnHkN>9tRSlG#k zzq_ejbyB`S!Rtoh5fOIh>UPL+_v8D(~T_ zP7N@gE|QxOUfAjR?Q$}8cvjv_InT*MnZGK_rh=_Szo+`4TjQ7`IHi?#C_Ab^WOQx9eqZ zc-rWkC}d}vHi{Bnmf?Bp$7b&=OPmP5qz(aIwpDr8xeT{v)bj`($wjN)F9NblLXM&3 zL3y1xF65~uqp?!qlGCXmU)};8APWA}Rt?3(^Xm-SS1515F%Wqpy;mfAP2XsDD8tA% zP_cEzaxdmZGQ4Fk2T3)>p*Lg%6KTLRX(hEkioUDQ)9iibIx-xbG%f2b?6P&af~)Ye zGJW}$JP+Ezq_^c`a3;o5H%GHPJ@<~h80tY_%Al?9B25ghZ1W>RCcG!ld?22yW1+Br zC_}vtInA*$2no%R5rk~!NDo3PbEE~q&pTG+Li%!~=0f6fEYF3MPa~eA03G~5E&i)av&x;`sP3ybo9xA1nB6U z!Zr!4mGDKpqvBQ-Qmq?0GZ9< z$$?z%aOXe@cerx6-EleG;5*4P{#pf))?K&^AQm928Pl`R9@z7_!nq~pW zxsIj*9+dgH5CoSN$coRX^nPR^A9AI)_GK9URB*3l#|4X|+l604(808*->VPG6!OaNl2 zqd|cCvVH)fr=wnAC}7YrNTu+Hn8jI&FRM1$Z_o}*(13{%oX`VDHNeJyB?&2 zahJfKz1Tzvr(xg7ud$v!eLiQHOex=j57p@KxAHEIg_PcjUyq|5m*n!|!|&vZ5_{Tb z#PVANpty!w4E_Nday%XQLH?Hg>v0*3O|3yC-^=4f)Q>VEN3#1_R9S}VbnPd373=G9 z;G`7GOhl4M{#m{vvC=(bZ0P;Y5;=oa^DFCxo}apbI)S5JU8gKx1@YQVkZ-;UAbm8y zok*MhEB7>`oSIm08|5$9t-AqhwNS1ne*=5<*Dvxg^Vs73mo@3^uNaChOhv|1%y-!6 zH~c2=gNOX3L0}<|{4Ik&{9UdgF(;hfEmTS*+(+j1ge0m}WPo+9Rs z+{0v&SI*_M6Cm5kHu~f*`6N5y0XM%#avqeYFoKrJQZ#wqpS&?fp>lt(PK z{$MRfQ-6yxoA#E-jYS`uLK1tVt-POI^axjGO001Q2aSTpQJsoPYug|^4-7rOUQy|8 zUgvK1$aoqWp{xsqQ7x;3=n<*3muNs0Wj#eZl%}-Ot}LZ0QHqzm4rKw2aVmBi=Txjz zCrW8eUpth?$q}W*o0kok?HecdM=6c@X7$t2%2Vtel}uN8W3Sw(5dS39PgRUAja<)B~fSf4QtOwx8$lt?P6q8yech(lGC=8|nZ zy08l`icvzZu2>{+)u5u|>G>MU6Rcjx43%mrqi9u4Wuf#a{Z&(WkGWiMb9kLfJ!>m1 z;XRKm<7itQr3w97Tj@%(Yb&eha2@3UyxddjDz78*|9uHyQ9Wf37+J8~ccHBZrll=G}t4{a7B#rsVa@a(fgZ2sXGB}&X`uFN)l-oeh^=&5ujS6ihOrNk+V z=|oGV5%|~}EirHjw{ zFYzcp)8}pRdy)9Nt8T4veQ)5Jy2pB8stGkmkMOKELn2P(P>>trE0npJg+RKpMFESOl_eH8pZ*HJUW(ev(o&R&gL6 z7$mDW(hilO!|fE@;m3uQmbh6w*-_CTFn33#X`cpxpnhkix6OvW^a@~&m*{m(>A+QS z(8vLbBde?OzEnY=P8}tx*j*V0e)q^EC05E(l@XLMO{q?Idnngn6GsxnE@obL31b?7 zOZgsEYSOJf${nkn7qU6aOWG)HD7qW^Z^uBalIDpRxyUk%mBr2^1=c?&W0PSl9SBjm z#Q>#?_#|0@X7R{Cr2#l#ft_X@`_!AM#Ezi~6g`zo^9)rQ(GIs#fjlD=Lwqt!87h%F zL~-A5Pt^yL#*9H$pN$i}gU<<5`j!ok3Xo+Mq(cR7onC;OWlz#S3iJkfmAJHu$ zqke8JapW=X-)Ns=6?>*DwSjpKYL%6uj2DL=!wTY0Hu3UI-Z|=X6=H#Jx>A|i^aj$H z4oF;&kt9fB*IcC@@-J4Nmh3$SBn?iE={!8f9Umcz<|$qAd7=4vIip{!jEBnZ$UNmK ziAD{;RGg$77UR>E{wQxbAe?IFnJ80{2OeBUR$yAeul~qVC0QbWhBDPWv=_ZMm#hd?-C&h6i&8U{3}z-WQbXwzS1Z552oIs4 z9t2{g5AA;fHI1dHHOg&ru2FKy)f)#)-+xchDx@vTWiH`f&WGwyRjgX4jFnhT2hu17 zl}9OQgR;kDB5H-6$8J=1v-v-izl-Soq_SCJP2EaIS%!I3uo=6q7ca3{-ot`BWnab; zIrS9w4zLjqBwrxTJ&mEKux-k81V2%>FnjR8_j&YjmeP`LZBf1vmq@Wn;?{PhfMyD{2%sF9P5>lqrpz4rI@=-87YAYfVu{@ORyNBmVHX6Ucvr< z*aL8UI}%@cLAgY4Z%|^hb|{~-dJUfMM?s}Z^1acF=|hv-FdKOqD-LAw@ezL~r-op?%VTG8e5;7n8F+|J_k9;KONG9lHt`evd#%wB|AZa$Z%*Y=_# z!DBpx>5bQvSn3(^l?COgMVO&`+mPZMdxyr82HCO z)Z0(dnILFrKk-*k`5gm#7y~*k9|H<@V`U^ab2F_g0MGGDzLG^VaY-W;6k_HM7THDE z{}6ZJA#_bB@TD8mtgEtv{lihF%T%pk3VYrVN(*96~G<%FlKB2S>!)ZM94b*x6QND=4 z)gz8v@DPv;PG3;^QB14~NAYWlE$cJIEu5byI4Jc9q1s6Q1DJyIpDF>^_rVRoGCmQz z{_ua~3x1}+o-+ltn=OD5^ZvyPO6#zM+{xs=q`XQc=RhAue+v?{`YR=_ypapXfKbCi z1w49Ixom?v3ynke(-d<}8AD6XD-Q4=r3g|OXDoP-nSWx*AN*W-nZ}&QUisrirJIx@ zkgN+_$}TC0D*BZ&88Hx2Zh+z*I;ZTX6IYZBFcPI14RYSbJaV1Eiu&-5B3siFa%XWl zyQuZI*b8TtrR+rWzQZb+S6Z`Y>E`#!ZXC?}UsT~V_o_0Sw)~)6ptx_q^o_frL}dL0 zGQ6-f_Y@L8DW9^ME!55_^!qjCQyl**#THFnt}8!Ci$!b+^H+-mn!s@_Z-92t$G5=f zE~C>!<&@ZRTWKY6uSaD437QxtP%T7Tx0rdvk-rppuHjNh z?tP@6#R!^Gzi*VbW>a(27--2vLrHy|)oNz4)sl+f7w51M>$rZ&MFIsm7lt z33s@<8Yl8-d<*p!ZR)2^r9&S>AgCRo_BXX@vDCkrdOh@|q?X!LT#Hl>!JNl^@CR*m zsyY}H{QeTB^Jr%q^)|IUq12~CP1V_S>7){4&1ey{n;%5~Drz#vIyX~otET$Fj3ic9 z<1N&$zIumzb<_yjSWC?Y7m`q09c-~x!Ro-3RLxM(GxgMb@F9uyRcOg^Xs?Yr9=fRy`)M4h^C;Qr)J64(e01$F0_u z>WU+7^+(pZA@N_dS3%d}`b7SXL>jHv)V1t&mpPgjRXZ_MSD}?`NZ2S6DBP$1F2w}n z)edm*79HEGyCt^knUWQ*xp-e`CK`8CM_M5FV-~f~>Y`4kdS8KibzD|d+IB&S3vVB6 zYR=@HebqrcBhQAWd{>oN!EmonfT=Mu*AXRM)kzXO2r&Z~TOHaIrB)Qd?&>NF417C! zt7D|{ja#OV3%2BkKl#cP*dffF%tSzk2B_!hR7177nLDPtTA7+9s;ya@kU3tU61Y+1 z@C|o;x}BsR1fNk>(4isf4%*pY4T9Ie^fXHsz!q*{o*@&5$@*fJB{atrK)+3*T0_(h zP(j>JIz-Jejer@U-sjQIwC6ASq|IK5C?5#Xj$llCjh3t(yR#ledv8wWV9@K4)p9L!{uL*ea zIDQhWw)|1BTK1|~0QiHL2l%~U73LYiipfs}s{ua|tZV&PbOk&uSmSz1BmkZi9awXG zLa^fXBf(0_4}}W&fq+kr-&P$ug63y!1qLRz;{I>z;^@`ua37xZNRrgO~5w= zYn_h@*1jGQl>rY6*1_T~biiW4jVuz}$U?!5ED+^cbBxIDc*qm*U2=#ZS11@{jd^a6 z)y_G=`GA2S>&pGXnSj|rRy!XGvcB|Sa1!8w-~_<^!Eu25f@1(*53=s`wIJ(G_XdXn zz8V|?_)2gP;GQ6BOP5k|z96{P=LOgLoZwoY6w`4_*9EHqu1yVA!Jjq3N`OxUqX1V2q1bhN zCuRX=1|tDi1^HXMkf(d4@j> z<^=0-FleS)KeQ_Unyk)}hKZD|<{q%MY!4VAQl94=!}(JWoV|Hb#mOUPUBM0B+q1r; z$X#kIoK;a;ge1D`R-+}bs#v;+p%hys-a&uu!v-HcS#8T}d_{$fp+PXitG#6yz7Sa4 zp#0+q+s6*^+FsR-QxYfyx3k|XG`OE0RuRgs0HovNeDwwDT&Sv6DC*K__7qj8oI>?S zu$?=K)N@jbiKOBiKr(}p-@u#4MC@VJZINcLO4 zi9LghvyQ8Ma6L|1MXe@HI<79TK7J2J6}oX86l@_q@&Sm&0y^-4dW*f+GXH_83VraA znhwoa;tBN&i!HS*g+N(wJcCo)%Fz!OUWNYpM16y8x0$`lzDyJC&!~-Ido738xW4ON zZO>5j2dayff2N*Wf(sdR6-CteQz( zzEW3;@bhXri89WqY5X-@d~iX<-7FQv#!G6h>5rDVEQH|T*XnPy;}>-~ow@?58Nn%> zP^Vl}U$SO+L`9MKtr{N2cYwo^Vb%{hX3_Z{)JNIi`v96*;)AQI%fk2Sta3b@6qnRr zQ+IQ`tq0RH5+5afquvRYs4&<@|35O;qoj{i1oZt;65cT7ARz+;LYx5*q3^tOnW^&QWsbyKfl^%`O z=J0iaX+s}I(}?C*(ptl=wzHC!Z-X)Ip#(=NX^0)sP_u{Yf|=(~-%)8DBE`+h+60N- zuC0AS%PS_-p_8@*JEhjvx`fLDnlxNaXwIF|40bB1tvyXEYzZyOlD48l)wMeGbT#c8 zI#fe@Qi>JWi#mWm7OAzgZivdxd(#fOWKXD1f7jG%i0A5PgE?MpNnPzXD{H|h`vbKK z9gIPzpl8fNQYf{G=8c&0$i(ST^>h|(3CKRCO%1d+O_kWagf8*-6u zE^-caXsz|-BC9^!xXxKkv|mj1Sg3iz(G)|Aqcm-sY0&e^BC)xqKp)apOQOO9nj3m- zEu11+YGk7ogTcF5@@HYy+fOhYW*l# z)sn$L!37y1FAEW9@ z_?t;@x7W^rm)_YygY~lq4?v9vP}J?N^`}iQYK;*=LbD=w_fW3pbG0w9bBk_bXJ_rU zX<%Dv-)uQF$>nY2G0`bg_4_V&;gO5n8{_QK?2542V0 zre-Fw3r8Q#Wf40Dpd;w9L`{YwY-b|3AzADkpn0WGA0_bvqSg!$v4b_Z!Sok9hiK1P z*siw9+^4g+-zJbgLYvCQwN>UWjh7#3lf;gZ+6(X_Ly!(_ITel8z6SGv-za04*f~Li zMzF-tB13&Xl;7aX2-^85dJuh)_Q2QS0&ky3byIl8oLP5&Y)2xJ2Im_?J5w;a@65nH zFk0X@kM{yCoes~|E+Y)V&N-ZFg4ns>Ub%?AvOpUES1`Q6mq`fc!?j$5T24eHAZ?cg z2D6S^@b1(qZ6Bp9*V=-bbZZf0nRbTh3C;g_QSD(olbG!-{jYkefKVY2FJx8;C#ga*GsdcB4w;^Gn{6vwoS?eu{n@?+fC6Ie08^+eNj?W@_ z>BO~*WYT`du@iyU(GbREkQSw4A`A_l8Zmn2?MhhL8OWVzZP$Sp)Db(`GFt^1h_~nsDYodGK*IomUnD5Wqok3 zM@zob;5&6)t3)jev?@@BwJ$)Kd~NpXdICi@Lt~Djq?^TBXEwC0uJhpAHxFyOh4)^<#SxQv0VGi$UEw#oOkuFOn4 zT;S0Uw3nEfxL@E$cmW>b!51^=!;{+6P-cPQJ;f$9_s5!S5%QPXGZNiCqs8-4)dw?7 zrDLCI>zLlJh8p31^DuG3=~Ooz5mnx93T?m_n#QWJe`GzYZDYUo(k6y_=I}*K(cjN& z2CngbpjCMNvZ?hd>#)nHe~kxmUe>15@hez89k24BcmGza#Oy-m;CnUOaZOuFe}AVf z03T2~f_tU=rniK=IwIk!rdUMBn~b2v&p-eb+UiR85x{5lg60snuWQ|*K@$l#wcjNU zcb`s&?r1G($7k)E(3<~(*x{Cd|7z2iXINK>&@(;g=U+6RIQ$Fm6wS$TTU$Y2{jRm5 z@xNp1+Hf1c?6_JEHaN6x~4Y zNQZt=7t^f2w62JbIj*{%Mt}Ukik9lSpH>`KJs|#i6#cPGjk2aS&ubAXIIda(LN(si zrkGAP_p$9}Y(1H?i>k1)MjljTPry0mUQx3{MO`|hHsfDC#Fa3;I>IHOuBn{p#jG%` z1C0vToD0JJGSmdcNq~N{5+dWpf?Tpz7%kI?`HC5X+nB^CbH5w|0^AMsxiw z>fBcUhLXGJW9aRc`f`!hLWk7>IN~|^QTE@-gn=eOKSZwMJ}cez=%?sze?6|e+vP$i zCb7KDeSC5Hd(>IgVfVb3)JDU%VNhba=wDQf$T0F?oy%N!ADaNSl@fLJ>0gIx)VhoQ zB;9SV>rTVOp3y!JX1IG55WlH9w1Pdwa$SFy*%7pPH0@}n52a0pK2Y?HM^7T=)BV>d z_c=P%UY`Z^U8fFuYm4x9*WZw+RcF02baueA3~gcK23Og>qjI=P`LyhNbo6il4 z&4(Vy*(RTx(K_D&g`BfhKD0j0mibn|7I|Eu=6MSMo8@tvn&!;_Y?3z>uyG!@qfs6l zR-Fy=;JWCH$%9tQ*&vVGR6h@@EoZ$vZdu(tsJ5JS@}Sys*3RSR)yjiv%ULrIsx4>r zJZ@yQJXXj#tL8zs<*bs&ZLOTw8?aIy6mrh!JZ^DRUROY8US~i@9=F?`2Zfw7G7m~j zXG9*izG9vZSRqdZ4A0{}7+Szp8B#D3aBu;QEddh?8Uyw#s0-M)pekUW0y|*u0`8k$`HKO2=FbD{kq>>DvwR-(flgbV3(%U! z9Uhj~2GEkn-7e*Ex0lOf<=ovo%q_>?dG!JR$^(AKpLw+b@1*8oiaGws!xVGe&O?!o z-}5RX+pq%m3jQrG8t~UVC*c2~);Oe)n_JEzLm`)}PiIa$w2*(IH3K@nl65Ka( zeIVI}f)v6dJ3;@R6SqMmUw{>3_3yxi14S{-dPLvwK;~1!_3>dn^M-TwY>9rDh;5?t zG<~f2YLY&IZ|g4ILR{kGNA*3D4eoENt8f7ty)nqBMy_QYtA=(SY^(~G*Sf4wsP@|D zLA4iZ>|2T2=oyc3|kxiLfU!HT_q#TX|(Owm8JFgrh6{}Wbjv3*WO49Wr-mc^5!U`RyAC`j%U0rZSG&=f}UX#>K`U>`I%k=Vdj5xaqyDH9+_kT*C4t~D)Dg6;}|Bav4;YyW? z8{hROrR5L?u&=VlE={BCXZ1RC;u*af*|(ruetuD}99AoDDecVC>%eAQoTb-;Gu+uM zy$5uGNuk-z9Vdch$9v5uaoz@Crr!y zFX|SW{j&Zkt=NfC*LQ&c*~N`rI)tJqap@%t8(n-!&!d*F=zr4ggP6giUe#MjRVi*C zX70#W^=LZ&s_tYaIIRzj$wuYwysCGFYAs?kIiPPuJQrl04Ks7Y zgL*pi-dHWAm8n8s9MoT9&#SZsbfXvp{lXz#<)|&Q%N}b~jA_nuvQK_fvS;i45aeq_ zwm#YhEmm5?5T@3Cy%GJHt=moYTD7v5E%+L!w0aK|x5ls6Vg@$Cbgdhw3#rG3SE0@PnfwVO7* zf#ncKmrv<0iRTWZo$LjcF_mt7r0=8+NA+aMOE=!cU_J4sJ_Y(M{QjEx$?K=VaBlw& zTSUe?`WP#?P`In4g^F2EtKZchPwR`e{1#onAZQ@p$<5MWoyE+udu&3A7`g z+p$-?cv6RNYJ$LngRQ>66)8y_$`Xc)J)h|D2<$E{e~Og=dvM7a9VUxTWhu(j*z!gj zaq2U@D&mTky|&W&FZA*B_2>Gl6mK)y(vf#{cX-A8uA#yC2yFe1v-(AFhv-}Q_@tiG zU!pb_^hekM9p?d1{ar%8sAsYE>EtE7CM><&yIX0+SNdYGn#lG7?R%Y>GBWY#3y~DuY$7{n)NtM_*UNni!ho-;>qu@8Gv!bY^l$hsPjLtxWaDW^*H0N zw8Lgp&iV<14gv8etVJtm=Su2tg!+6e{e4Y;msVW^x4rJVe%y4>g1djQK95e6=xL{w77(~fzv!=lIHLXw$@{Cm**g4x@+69Nzv+D;@BfY2lyn=L z%j(Mrg=2#5B(Wwl(_MSe$({c&RE73m_^HP>lJD6ZEQ~~g4pa&eXB<~c<2YF z+w(yKd4m0WGADicw_b~`{G~r9Hr&;5h+dRGmKgkA8m}_3t(2YWaPu>X0&nJ zmV$YXiNeo4D;ry&*1AyH_)N0S<5f~p(`c17#OOrVs~H2zf{b^{$GO{T#@p-~o*~hV znnuggLP93-TF=0!;w#PSWE`SzY8VBeV^U+oO*r2dT!jl${~wLl^oM)>Zb zi%u~r((@^(UCDH#JjKjJWus@JoF(;)C`&;(a?dO+*-Uz*p5Xy$H(OR*&#;42Gn~@$ zhcSlJ?D|GK_=_E=Z;XeUExG}Qc8QqTz!)yE*(z-y?QCeYrr%=>A1j$kpHDnygwxc9 zhF6LR4WrrOm^p@(5*h)VYKT@=q)Ls9wYY~Ssjbn5jx{oLDV*!mC#$iMKS%{pevdhaWt)o5sx#9<^;Kq`Aid|3n=LQcA`8R^=fLUxG@I#rr~(x#g-__ zH5*l^)68gRgS{&a!T7letD70!aU}8~oHqKqnbC#SVwf_VO3ZF%gd<7rP%E-)hL zObeq4Xz=}f7uy;RTD!oArp_&mR=5&zMoVM1REx7b28!Dt&d{?K8s6}f0;sFf#?$5& zhC;Vm8JbCeCx)IUwMK7L6ECzjo@P~#JJuL#5>Tw;)yU{Y}HE@m@q%v(L zb&AKht(A?gAeAR&V+5U$jowr&8&HiZM!%@ThYz1Td>Hp2fSnjo;G#>4F)A!BtQ>6~ zf(>|jS98L{+=YSgFss?mYMG@~*Idy;`FmKa8O`R1v(h}uOC4Sldc z!%AuOK%)vYV;6_>PNU05b=WLBK(<(?8;wEy4(di1E2wB1H7vj<6dOf#3{2y8jJzEs z8t8nKP@eJ)BMKC{w8-l4&$_{DF~)~c))|qRFr12fSlk+&_8HUZln+ZjHQs2+WIxRt z>XdWw#y%@TXwRqqiAJ-^16wp7Kf$~YB4y^p@o8EC2;L+EG^(BPJnT2i+Z$~W{|x0e zqLb~7+R(_Dbx7)f^0o$hcn6~@HS1tNW5>T{bTACM*1>R6P6wlXq{~%e zcD1jdb6NgyI@r;Ou*FAoDFBi0h^dfJ_N_eK>S*-0Ch-OfHUxK7CKw-c!~yKXs5X2q zI~gm@Xa=oA&$~JsQ`zS_1MZKB*gQLRG5(;aKE^D1y{oYjSD(1L8K1GUG`8DP1dVBH zcVjx!WDvH}m$T?{4`YxtlrzkrpQ{;oMnOJ_CrWoO%JojHt!G1tfu~wjG(V1pW1+7?w zB?VfM2r3*?AB&+bMu^(h!e)GZx=|@S%~B9@L>XkXDocq9Jxza~jaNhM*^pJZ2aY46ZY=GvP%=I`bGh z-}+Fhiqvj~!C_di!_I|EG{mkdG%nG=A*}i!n6*-jL|k=rZ}%~~;Dr>Usim+S6OT}z z;6=rUDSQm5I7)pKwEJF-%${2TKjp+(#t^phK&;!j5Ka1Imf>P*hW*%l_RPli&SZRc z==rtTMsF*Vao%-XQKLCVUl8z_bBqa;G6&nAW3JI0^t!Z-R@yNK{Kl-g7;ky5A&Zl9 z(PXJ=aK6!BGG*K8JRU8W4B43^wg-6zE#j6`F<*iZDj?1#!ifvffzjdxeigCWX+Y^ecP8xYXd4a!oL7w;@L5Tzg2Q0E$hXhNFt7aXL= zr5lKPR1ec|G>zYE45ruC7&U0yO5-rdI2e6g0K5mh*1=Uq2|Ph|WEwD!HW2;SU>k?5 z`Nmo-EQA#(OPKYvfvxCGV?7;OYuGAwZqub@M0%&fhX1b=ba10#UVwtj8IFOZaACj^ zOU9GNWsYhDe`B*+^EMgZ!-snGW+Tz`p@#3kEbcm{qfZ-%ZrEI)Sz!65)@oFR)GVVt z6Xr~~vMe9vLTsvH5H!l%ZnQPeWrL&PY{3_eC42@dlpvJNLBn<$b@-+=Sfx4`)dkvA zQKGC}MpH3&C%7THxZ4;kj=ly;MHBX5KXgBs6jGSW>@Cg*jMrWP_W*i$A6bM%gZCOu zg!@%&akTw4qcMAvJXi(Xd-^&UB%QmYfkZv_8y*gNbRPxe%54XXW^DL*Fkc0_auB7( zb5b4-u0na_&oQP|RD#yOjJg^qEC(8V>W^m5CWA z{_ebGwC7vWLJbSmB`FJpa^^eef0JF=S_^R|6qchrq4^+HQgok&=9-!qaS zZkvVeIA(kYar>cy5q{}7Mtt@AXg`g62kgY14=`a9@8$RqIr^Euvkn_?)2VH33F})Cq6^pUk2H&eA)O9UHJ;X9A6oq(Zws6cZeut zru`ae$MEkj>Do6&3|0QdI7{Dui(ig!Iqi4GEsl1I9=>XPK?i?;@HC#BSHTH6t^)u0 zAB}TX7M#ry;HBQ0qOTcO;ZB0`KcZtl8?n+vR%PJ&mvhD>+H~1S7H?fQ-~~F@mP&Fgz zVA}ROcAv+}es7~$YmGkP9SWzT7&!A3qpy&p8O_?GHxy&;7}YFRQ1<&H6i?B2jdy6n z-=Onz{{mae`%6V};jRHkiP^a&zIBond;%Qk?$yB%CoI0_*f#`hb0>Pu>gxtNTVnNX z1c#7n^KD}eAsv@YC5FU~3Y1vhH;SeARdKxKbrrYD`-Zb}x?cs~Iu4v3V&_3xtkkoj zuM&k<^fd-I0g;vIjrlSGj0Cvlnx)V6tP#Tsm#{n%N-|Fd*KxO^uOUQ!h&L%vU2lr; zjYGs!R5BC10t8l0S(yc)>XaYpTf;|;(x=kwO1`1A-R>K~P8u25gDd$AQXRgh!6$4= z@Ws(>hwmk@2iu)K7Vj@QeT}SP%=51p5?zz(MfvJmEv$N;73Hf&gQ9#ju+cvj@-WqL6qi`8Lr9ReUw5UnO4yoMCEE!}koG8sT%0v$C%X2Lyrw5UHM^3zdD3 z@rmnn^BFf(Rq=ItAmWYl17WY}LzLFfi~GYW^;t1YcFzA4vlZ=3G287vE$#>SNikdR zJ}zdv+Ua7p;hidGtL4dJw&a~C?g98wF9DYIs$*5D`E@nvqjwQtwr2QDq6v5`_TwTP5$IPM%fU62q zQ}JhIA=fmckZYP=I1@0fkn6jma2jB0;bg$&gauV?JEWR@OI) zxqZdOI5y%eDrO^SVKKM0pqNde`NeU7dBtr37Z&yhTu{h;HouVP$Gk$G6LSl@0nRDp zo||3Bm6%n?#m_9{f>R3FqC2CIOL?pi_*0$J3mNIOLY^#B3%N6o7k2`jQpgizav^u@ z|Dhep^m$T|(g;x!&@Z`VhRH0cNfMI51AC76&^9^AhIK#$SJJZ2> zzGbYPQt=z9>e9OU$Xf|PFnqY>00;OW^EUMP66rvUZ@(3sEn@48#zLRf+gFuNMf++p z+M0A?h!3ZB8~WxVxXP!I* z5rXd3r-GQ@8vP1h`$(*BL8w@7tZzLBA&B)=HT|tu;#$Xh$-@Q8*@S=t4>)8l==!IP7XmC&ZyN_>?8LFsO2;+(*%-5(SkZz=D zLTv{6;=_}RI-v)f{!4<<6qV?UfQstW^3uwdBB)Ah5`C>Wh(Y?j%9eiV%s~vG)ZrEg zyzI(`D-38`x|Qtnn-PvoTHcC2?eBZf3~qFg}l8i9_dJ>#H0T%({auJPuV!BUHP<_NlP zg82YgYRswLG;5-{0qiusZ(Sdl@Z;Gu`@c)xk#GKv-5EjBFPdUbphpVKKJ?OLvyCl6 zsu#5dMv~_&a}UqK7N5nhjiJ&4^KUO_u%nvK3lR_*Z` zSo0bq=g^DF=ZAXG^M&T?lsU`1j}EuMAH}|@=6mR?BD0mI7nz^eym5G^LAh1!_u6%{JUG96Ja7b#A74vF7ur(p{mHXJI92d^ISL>Smcg)4Uap;86*oU9-(w!C2#~ zt>CFqkL@_g<(O-(Wj9FGbKIY!5MMIP1Gb#1qWg#7N*VNSHszL@htNOgoA7aDPqZ=J(j0zyk9}yw|lh)g8Qh zAvoT-Jf`kU)v`rq_r(Eg6Wu9Xi_KeD$F?@2?LCN|USeLr+hS||?+RSL)I689dg~lo zpqjJ{9R&^>D|Y^dP-QqcDu;40W`1RIW)JV57G z0~g+DPH%ElnR~pn#=Jqt#v2tRtwme**MNt|CfHhYxX%&Hy^MN~*0^v^5Cs+-aWT-u ziM8e|XyUf71-)X`TWc#mT4(mA3$<8`#p{^3A{88b+{8(~=hvBgGml;E zXnQx(D_=W|9Al!m_2y12AJiJD@m*USO@13^XJTHlwO1FdH$S51n|iR2GIp9{DQr8| zMe!zcUpA?(9zvkpUSl#}#`Fp*(@`!n^PW|GLxegb{d40+S2Wj)K0IO^K_u*rS;}}46G^J7^X!N%J@~)H=zjFl$17`BMqz(+f>pP ztEgX`HdpIed$t)qx=}CwMyReg;F5{y|3=)t(;C`EO@7;aMo%|BFxRLn-!re)v-WIb z;9m(SEp4qgC*cIHEKZ#`YVY_3+9UNBeT@e8zM$*1NudiEf6S5>>H zDPNj#PfD6v`i&W<;`*s6-*L1>cf(yMNiY)^P>kInH^~D zWbTnz_2KrGxAZs}RqAi4HPDR^OS9?>vRL%$jWA1JNlgs3ypAj6{?;F{ln`afq)vaB zL)9HomU8I3qAiA(I$N&6_ff1R3#TliEk||CCv1eSv$dOJ)r2@pf7B)3l1|A%mO)g0 z+`NlSx6Fssm>!l)9ZgTPBv8!TW*_x|c*|ft6kwMUESZME`gT=Rt@&0+v~*#De^&!K zsM`}Qa}6+d{yo(Kr|iQ}W-R?wZ0SR*bFK9>|949bO}vKY@9S^L)6sRCWu01KwyeT< z3CntQh264JuU;QynJKCHnU)82>h)okML6B7x5lU&hgeDwkY!mYsk25{w&C+!%THAD zyQNy4m94$!S@PAK(UvEW-9*bIHG7=pNj=|Ui-AQY#;947EPM2PPHJN^2RPM(Q!GRD zOwl(+s0dhwd!(jYR>2W4k@y2YVRU1`~+Q?FH9kP8B2_1P-RP|Za*Mg-};RX?qix=(HTLaXmALIH%@#Y>~_otl5p+0`VlCATYi70o|v|Qa$ zPd#RY@~5RhJ^8BTF$BM=1vj>JH1Db1)r$4% zoF>b59kU6o9mVCXqVgv#R&{TSWgNDv=yQxcoiTttoY8>YozUkPyE!8PyE>sVF?MlA z0LE20VMc9?bwbf&jB!GXVvKf%0!BGI0!BJR06ROOS20F7;rhTB?t~i180HKF?Bs-| z#Te=|0(Nu;0ERdr_ZvGnp?fg~JE1Bv2048I1D)*wO-^WIj7BGvN5%lBC!oKx$^$=s zPS}SVeVuwhAEyqmy;B18c0!9_^s27{^sHYE=uuw@XsBNWsIOlMsHe=S}X8j_-8}$nTf2(IL(7)@;k#)eY^{foKUSA6MOFgTE zuGP;6{JDM};7|3e9=cl3O06sPvjKmsXI0S;^)ms#uV;nPcl9#>zpbB+N|>E}0W+M? z=NJb#q0ce)cS7}IOm{+`W9;Wl1nldC8pfFB>ZbvIU0($FRsB@JFY5~dzo?%A_<4N+;Ai!d z0Y9zJ2fSQA3Gh<=M8J#n696yNk7vcuC-vj-@^O70;79diQGmtCdZQ2PSKT9uHcd@mK8cS0^5lD!){ngBo&0?VIA9w0lT_GX8qd|qJw{~${Utd2KIQrF_2@* z;0xzZ%W9qaorm=xJY@oJslZz!b$rR_#>CbnSj#v`TY2|KzShk;_KvU-+|vz zeR<)pXsxNy3ffSm{t#%z?HIp=S(gC)hgz8wL|PX~>g)*XRvp`zG2#fcR;d+H)TRIw@z&k6MrXZ9CB9Z*xKL{{Xjr1PMqSp+3O>iv{b8(isal?7U8!eH_(u3R zMgWz+VEJ90nr7XNFEXqe)m!T>8${HKv$}JvW$G7Zt4R+^Y6TUvTbT+DvVsaKR!~83 zw{CQnr)CefKCEXBm@@}iqzB%$KKftyZG^R4V$B_RZzKv=CuUiH)U&v@DUscY0=`H^Tsn%b2>} z>Ib#`@0OKxX`J<-`p9%E{1fhWH9$M#xYL?%U_0$i zS$BQPX{yy*WE~}`BlcKv?o_U_%w*EDSbg{*>mh{hx7xwntp+7-e%h)#qb@7-EEPBl%%LbxosYsZ}bT}5bd?@H?aSFMk*O}Sie{asJyW@`cEthRJjUv0Ej zX`FvssRdR$J>SdPO+8#`#m$0`wpbyRKnSSSI2~;US=4(^V~$EmYVsS_AOneatrOG- z-m*TWXIEiuWALt(4D5dt%N4UCR1N;nxZ7s%mDzDC=!x+_-P%oA?gM5Zg&-nbi9_^5^dw&MI<)Gh08v?dI`Z`ND` zxAvcDZX%5uCD~>|?(&RkuWr3<-Km3XK3k)X4kz2f)Ze^pVS3sT);U^D@v`A)gseul zx3xF0C(zn7H73lq8th`6Z7Z3sS%<4NM%%;4FvK=PhdP6otv(`+iWd##ordL{+VHmM(+w+i~5DlmZ67FvS@)CQ^YXab=5T3=BrmD zvTWyp*hkn>*ap3G78;^P<=OV>sG-H?+uGdmwzu_a{dijuN-nUq*Qq`7ZU4q|k!_%k zrgX7QzB{qjH{;akBJP{+LS z>NKEgr0J}7+-AF^-S@eX7M!->7WWM{Pqo9nwp`rvNi%obwi%dzr#ZWA5o&Oq?K3^^ zhP%_M0T0?H=%HPkwa2!dZvfSTLsa7gg z7NF`H3HXu=n^MMOuHk?$x`qKBbzy_b_=0N);1L%#_>6~Lg8&b?nA3gU#hmUz7jwD~ zx|lI~z{QNw{VryV$i<9NovSzCZWr@K_qmua+EwLZ#^_!bGe$dI%oy!(F=Mpd#f;H5 z7c)j%U0{riTU^Zi*1BQ$yzg2U^S(7M z=6$PO%=@l!bpWh#F~7UoRmH4sr7Hk%mCFxsrOO9!h07bT!sQ9L+@%Lx=41|asS`Y? zafuTgr*W|pyryxHvm9`t6Rf6jfwL5_+*txx=9~{$>IAE4EOCO#G|qR<2At~zlWCme z1bbH;&sg9D%V(VI1j}d4cY@+pK{p%54fy=Pr59CPq@s0kGpsS z)A*Qc0A3z-^#|PVN(bEM0;6c$>tcrY5my@E9#EO9sc;AYTRc#A>ruYKlj@bbalU3Jyp-AY`E`k zj{3{fw#9mB9?dyu+iIBm*AjZ(_KjYeuiiLfyQSyLqV6oMmu#QwrBd~mml=ip+6a_p za@xMs)343^cpQ%g7AtuzV$VNdJ>QAn`-W|tv{?Q14IAlTrm^k3?GadKB)(OI-vXztR z9h;A4;Cbi$ZJWU>2Aav4&gJTZ@7TO0?Eqm2**>zp*5<;Yy1z?@8`o{Gux7WWiZ1^X zn*$r(wXLH1_iVjAU98IP(H7bf_Yeekpt9Q$?S00H%)GoTpApU~vcGT3Vy3#fgm#{` z!7uE2TQ=Q#-*%cA-Ws_2{@hkZ<_~P}Tuh@b*wWOOKCsoWpZ$!FY_s7NjGp`4c9Bv( zv#qDsF4_jd@0ZI>fK}ReHn`0K^NQ2`sKqi>_oGcn%;VNna=>Bw?TW2Q3Q}LYYI{h6 znI$r+ z`-H77qa-_sKp5sg1jUBfpH(0Cv{&%mkx}jKAY?It_D+&IAi(~$PU@`22HJ1xrARe0 z#I8qNq#a&r=7rmjF%uaZX@5s!A|W&(BLycp)9roKk}mcJs0rik72qEuI(w?mb+=dR zpcqbyxBsk%%lX(udslV`PJq+4epXYmDzMuvXQNvHzrVtBmi| zW3&V99&dkAbDdFJ&>B3Y#@<)mHp#w5Cnc-K0(&5Ou-LvzQXigXU#C-J=Ga?wbjH;m z&cS@`u&C^##?G<-z-nP*i5-_ck{VO- zAGxHfu@#(4f7MuN_crjAHnn-~TvX(O&d5*$*4u+nnXUG7xa!c?5kglLdnzuYw!i3Z ze)!T*AN5wP9j7lXYV20~bxloNJN~cA*wu&bwXa7b9hdS;PqBa6*rz%3Ow@5ap{1A9n+NTEm_JN9 zf*HrU-P%}=cvii9%&xc@MH>gy&>y<~9o2@H?Njv>(`cWoZmzf2;j!61nQ}bs0qW*P zyyJvoY_t70J@4(-;!2_ryR1$*Z3h>RE!Wy*Z2|r;cI#vJrSgULEp&CY{S)=|b9UH* zk3^~pEfwK2xZaxUc%8~e*!hG*!#wf123 z%kS*K1<(~^Hfx`>B{-&Dx?%@An{XY|R=xeRJ&6^@iP!BApvI}mH|)2Vk4(IUcQwhw z@fNh5rM$eB@bbE5Kc;^5Z;T7vY$SU)nhct{I9g5hcf2Xl?Jw;?>ifP9IF8F#6a5`u zY8S|3Qm2ME;&t41A9X~q1F~2`nB!Gket@+zCH;KiQWv z8}s`5F|Mgt5M=7v=-68r+ zN34#wgKI6^p_sYP4|c58@imQ{<|lgrQ1zMNj>S40mWa-BUmGqS(fdX_ zu&7Ja*gS{Hz`M2XuQM_0Cpn(e^t-hg-0~UfwgShW@YSVmo9b{GaN2)VvEv0@-F$~H z4AcGQJK*DFfPP=Qu}$D?Oc_my`X}2BZ35G2N^b&NY3kP$s(-Fs`f>1Yrj+Ag$4tq` z!R45ej)NgFB_0R2WJ)-mpm(+lYFvfqu8m6oyEHBY^lJbQWb$nQ_ha&D0LNo$-vB1Z zH zm@odKK?nGGg9P|l12`(447cVE5r7em;CW5qjmdyvjXb!W8qr&(&_?uxsj6cm+G+}EM9WMa8qqRS za03rYP{T67zy=;mQv+&hGB%)crho<%Y4UGi_WP`>2=I)n5b(5%8TC^xX4G3;lL1e< zm{D(bamo`eX4IQp%%~rCp>oDX7c=S&RW4?sT`p#!oi1jf>s_PKgPu*ygumuuM*3A( z4&eWxkjDJ4zFlBDT_HOicC?56Lda~#$I>G8=h=>OK1eiuu49i>XRsT{Q0`dITGQ$X z+W&7&o6L<+@hakR5pR1{Jns0~z*Z5pcOsrfgeo6)T!s_H4UXso@*xMxe$nwc z+;!reScY3IWqiY?wdO;O0Isy%>vUN9Ft3XeZmpB%xJ6?7?peb+Lil}!$ z=?>O+8(g-0;TRy%rEeSqxuQcU>5L~QJ8Gnbxnd}Yv-yYL>VmB7(7<)i~Q7`*K`LNA?js9>W@&pWWu3f=Cuu>Y0r zp!)WEj)$Po{}@BB_W8(R!Sf|Y87f@iSWQ}=dj!-MEJ8tP%wdEt9BaL(7sZ^LtlLoaW~QRj1d`FF6hXAH<2zTSTKkYN;> zLMdKyl$1=_UUIx4iOqrRUcCd{+)oZGN&@f*~ZrOW7H15?$&9FPdBL4_uHJXnGs#(!gY1EFHO><^ z1j^%JPZ=5{--9E>dxPW}SWA`!%dylDBuCQJVA+pigXK8d7A(ip{9w5=y%Q{d1f#@5 z9pq+aVXH@=09;QWB9G9_5hL!r3aSj1eMspj&t@KYZT{aPaLzmw1EL^>^tvW^UKxEF`|A=ex=8@FCz0*!OnlRjDrRA;>A%iSg1S{^TNlE%`+UYJ2+XnQXV^Jp%78U1+! zhf0#7WbD(lWJJ*I-tquLvA*4OdMiSOG$`aZ3q6)3hq4~EIANn;( z4rXIV&?#gD56p>}7z2{!XxKSUNX9D3SMNziOW9JO23NSG%DAwwKidCm3UZ!LS*h~9 zQ1o0%MOV(H&VA(RI1Rj}4+3XXaGKnod0Qwjkaq;lO~cfMLbY0IdpFY6G|bR)b$MSI zPJhc(TpWaR)1_)+e+)Wq10Os^~&+f*=}nG$RhCmUw^Y&Y^xqFdV6c#T zYNTJwB$UnzIhvLV+0M@Eao|;}D*~s$*f3dYBS~g*$c%+ zoM0a;cT=+l$~Y@#AlqPh0jviP4VEWsUOBpPbS$L~ks)Z%ks%nv${}(eU1K|bSq=J{ zGE{~*1Tt9z=UL-q;JcCXV(oy=thRR(eLqrO$wam~motQGk1R}tomsN1?WSryjN6h1 z%HFDel)Op@>%cWR^3$5mC$KFWI4sYT4fJx3oKiPh4xrFn*`yC@44{2^=(nVis2mb> zBKsJ0Nz7Wx-?$)Vy-E$(N`@S zBYQ~D7A+Zzx!RE)A1kkuLMT2@UW(HZDD^(3q}A{OF&KCTX*tS#@ z$ekl&MrDtU$r58ThsN}Pzcvu3JHI^}d&3A@?q%r3Mei@bO1M~n^&L-t7RXOYJ?W__ zay||j7z*VPIAoAhC})E-?kkk*K{%#N#d5LGk*RXJ!K`mLg6e0B2KQXlto z(sUqP*eFkzNAsp-wT%u<2GV(Zx?F(sp(!(f|A*7gc^IIo86XM6X#Wh^49jOMfz$7f3A$#llG!CXw#d2TDD#j$-SB##nDFz|@XEE?hCjC$>n>CsRujj?6 z^yHaf0t9WDDNlf*{}(esxE&NWOSZBZfAt{lhBVqbOYRLC`|>RLJo_Z9hU1^vSYA(+ zVyd0Q69~e0v*nSRHGD3|h10k>@^Daw{d43}PzKXnP>s=Kn=224$@@KX(RVrY-dr#g z*%UetaieJ9Jb5H27uuT+1AH_!gg%)kTNq`ppGBfvj#4f2F-Abw+e+k(Y>|!?jIuh? z#=&vHG_Vwic@nKDm6vJMtHK@kU@1s{`dK-M2A6?7nSixMKXL_kmB~+oeAvo?m!={V z3%^_*N7e;E-Gz7uHY%4B=)?kfjxHRCzPOxKsAT^Wl|?QfQ2 zhRvar3i${SD`4VK58QPy$ij~ z?@ZdV-0!9mIS|(6V2K{8ln1j8rN-74gDXu}%SCKM4+a6BcUPZVEq}*U>UK3Sc2PcmNs`gcNK35#8Uz;&-ilNi%>4sVK)N#?;FFX;cm7;puhq9PNs}Ie(AoOUc!! zjd25r{`>3YoqR48UK9WJbqwv@fPN{w2RWux%N}atJ#uGuOFn#~ybbc^2OBZ}csm1V zqP5iaG<1`kuMr*pwm^S6vq>(Z$2Q45$hj1mUECxO^U7(28BsMTOzWrq9Onb^Q}(B; zo8^v7UDs-Z7NVj(y?N8&>K3$X?H0MC|5m=kh1Mt zv52;9#jsr5Du+rs`uA2KJ&DF{lZWY!x1+t=KZ};+p+dn)6nf;pDXFb z?Q)eAaG>uF9L4rm`|XrBOYlG5a4+a8PN(jYZITZa>;f`t&-Hi)1Z%>5V9Si^r}xQZ zkb(}ZcmQ7psw*Fq;X*rDJ^rwKj~I9(;UaU;cIwXX$y=& zDE=~!ccq(8%7Y;Jhy&=@I9ho?9$|=Ol2Ua51nQdu=;!{l{V5Q?l~2jR!0ULI#$Nmwj(i^Y=frw(&~e<35XpZX7FjPzv6x9 zr-L9Oc6UBk4DQ{c62e@|5Uyd2I(=y!jJFkgD@dD-Y;K?HOeC=nOJ%84Fkyw7o0 z`>8{qze5hmgZQoxkYjEN!oom8i_k#&wp*0tm9797C>%Ockic{=X`RANR}7n z&*6O_<`|fY0)4w7wEdVI=G7PDXFrZ}pvUk5eCi&<3YytAEI=V5c$TWz5r=bpBsXoEZnI4qzM)mufWjcDXbdbANmt)LGY(bfveI1XgEoa&B4GJ)WD98B6$ zuJCYeFFS(@n=nPjxj+k_a{(uO*91gmqO=ooFC5vKasoXRKwC}#U-{E({NqR8pOE_l zH+F5tj}M)12I?wrmb(E#YR}K$nRYj0VtkG#KGU=kdOc$Yiam)$o;2hn_zVwv@Fdt2 z1AW6kdTv<=WwoGDI$GEQkr2KgxSbbUphXCU0PaP(@8jKdO8ypT@R`%{9-zUYXTW82 zpr6mkEqiY|@mnYH_uR~si=BOt2 zu0Zn}vWc}(HPLqkzW#F1gf{wR(X zo|C`e4c@iB{GLgB&x5{nq3E}OE1Un2F>q50)~M1 zz6@<7wf_p6!ZPV>)CK)S(N*TCf;=ep?vuxK(DwwG)dGdYk+LCqp}mS24)f2dCTR^BD~rpAmK zl{a!&_9&l4s`GQXPNM5y$*(g`+~DBMj?}#YiQRY^JjI$X<@?F_5k@QLmhA5bB6ZiN zU(35C`sF*6%LscI4^k{$`5q&8_)V}$_1~kB%$(z}VrxuK4Qv1P9!xnupyfc@YxB6| zt+eBeJlel!AB@r3#3o?v4Tb;T8WEahqF=7am*5uX-K+9LpgjwIlFxzch@a)75E!oi zEbnLST=n?2TEx+XYd}dODB>4652DM0U%(j-U)_YWbGPJb;MFU)x7rY&S`5Z zII&Ed_2Nk&-Bg44CyNqmd(&kPF^`sbh*=;apLmELK}25n6i-V9wA4%d3glhlE#9N~ z-lCEk+KWk)*9D83^)D~PhtcnWB7iLG4IDKsrbAhjuf<*~b zr((9|6xORP)gI6u#9arOxEvw|)7%asSv#fLg_C5`s1ZT1T-!Dpiw0#Yjna0uCU0h%lNOEh6Y}C(%jcbQ5V!CsgOVXyH$$NYROY za0-9#6lf5tdp5yEYM6KiWaITPkq5F77mkcpQdzi&2ibUle=6wT;Ua;qgrnWZ!$qRE zcCSWK(=zHEAyN!W`LJtaOHe2sjz9@(BSaA2zW7&IG>GE)2ysX3o#vMSnz#9-8_|%cn0*ju8t% zbI!$xaheaCV*VXzHq=eSPH?@3HZ1}i(!?D& zxQPd7P!nf8u!*af*~C>;ny`&*5>414HpxxgrH-b7fc7Q~S-Y8MIsDbL9RA8#4uAQq z4e%dlv9D}W&+=~gOJ{omRvkObUH#%&?&_mwLjYem%OiT^EVu0NS+3BbGZlc(pW)r` zgJv~=mm~$V1n4LUB9KRmtLwa5$gz2_4y=`BGv7K ztmfHQbT@S2_`2sXs=p;){I)1tnjC)lco`vQ;0(5b#+2xM?mKar_z8(OF+ z9cVJSpNOI>{qXh3e!z0?^#g|LOr~^^B}LGbbdd-KZ%evZB88EzKi)b~N`En!*--c; z?aXLnSAX$6_^LGn#A}A$t%Y|a`0Vbc3E5E2n?)#5hA4o1Z8M8l>Sz|->6Z)XFw!l2!{iMF*DD1O zJd4#ZNoY;k6B_6Dg%~WsMnVx?G`SfLeiRJJBZ}~(>57PvGB_@r1AN#_5SqQceL}*i zMG)VZ|w}xRZ<_;1az+q`os}QBJS^n@1Bcf?Q7KZi5L1?&7mlGrDqd}q@SUYL37zox5 zUj);{!6KZs(t|~uM_L+sk4guNp1ukE2!{}(PYj_q2aB;_^N=K%+7A(-`o1RyQO*#N z&iV~(nYVU_AH6sP1!dkHW};t*hALLFm0X zR15|Gg@hqAaTq3l<}kGN9)4Od3_b7?05~wMnV;iX+Xlqe+%TjM87_K2rL4t`8IE-M zlsf{wzJ9m}hE$K|&U9?J2&DUm3wT84r;~W{ONg1jc;TFqo~a3`@pNT4R)}K+;^IbN z97;!^>vH(%VLU;$KR7~s0MS z;FES@A??T#{lIi-p&w)+VL4sP68)tz+CB;y3>+mQ`3g$zWcw(r!!@Hs07P>{ri0aU zf5r)$N1?%9+31#^N1`CQ0!VZ!7P>z^jFzDMAa-wj0BS!>y`6M^P4IlU<4jG$^ z83!}P96Ybh#WI(8N(JNQHFVzVxguKE+|C{AHCjN)kP8HpGa6&R+8sK0G@9_>XklcB zCt9AkQ**R1_{3ZOOz1Isko5$Pc5~*x;&XO+qE)w(X%=eRLv#7v?Zk>Zp)_TT@M8}s z%eZo#Xu}v04MoJ0W5gcKQ9Ky3d|;dDV@0wA?>1valA%NEw;kw%v6!*Jt>1PaDNl5P zM31sV$(n~b70Az>XjL9?>GV7-sKz`IV=%UU-;r+Sfy29;C&Gi%=PzsP%OTDFKosyc zor@Vfa-2vu__ck3S6^?R{AORIu((q=VMVWbOTmRn`~}_v=wk-^Mgxu5#|sM>P*fz$ z9d#y8gm?{U=G{-Nj8OXLc*q0TDV-oDLkIBi1TjQ&fJP>pUu(A|5pKWY%}z1T41LLJ@1| z!RlYUhERSXhORqR6++yCud_nY0_=?9d(z>lpiZpg#4*^ZcwbT^y7=a@8}Z)FW_`N? zE!1mta|V*Yeg9O^g%%W{hX&BzB9UO|&pZG^;{vc}Ifg&8Hm5T!<}iw#Cc-o#=|$3X zd^v5J=nP$R)ihKCn;+c#FQ%dSedyLS^a%E&IB3~)Ot9SPkT(7?9ef4c_m}bK-RPI; zVy0o3c6AWvzGj99f}&IV2G4$eG1KOkP2)PGHe=(Ed$DSU@M>?<7KOQaFemL!y^1l5 z^u?h1AL9wa|LS6~m@11gUXe4!aF+jzA&TNTkcwvtQ0hYVv_@wR^z@Z_s=M1)~9ca8S?+Z^E9 z_vWBJmboI#kfYHBPPcTf=*;vB$dF%OoQpb+qL>|+j^EBj7Flg6d(0D^HN~*ropk0r z5zR*B)gagW=~MGW6l;hp+^=8H6E&>8T?;qf933-9w1>qr@;x?R?AC;R=&u5E1w2de$bun?um>b`QYb($)| z^7m|g>F@&d@Z^Q!U#yf^3k$P5^-)pN9@=#^ca`HsM;1fa=Dp3lwxnKkZLz3RomE(RbE~jCE?0^5x|8k5v|c1p-5L=gRnda&SPd7}fa6%L)#dUA zteq~^A_S@pq)Vik)tIHL5a&-?+~I0WcTg=Hq}qVle+^0LbTvLot--qVuEA3|KMkn? z0ytcQjF!18bACMr;rkl!XiMF#oLVFNsQ+4MN|sR3T5$P`xi)chd9A3>{Lu?8B7rW} zVvOt7;Y0YHu7%#<*L9%&K3u(FARP!wq0Q@oAaT{rdOTlPk7zHN!r?tP0IPet!z(tR zCx>njsnoJ>wp;O?+m$U4? z8MyMsCXC0t&FD1SW;`9jlVsw2lIi+pFc!E1uNI^$fSZ&f`E!fkgJ6aXa8i4h=%^Pb z;cJ+)`*jPlJG}+NX5Na0#k%K z0zs&AoPJO;u_l+_hYb7j6bPe~XL;uD#(=+Z zAB5F3cZ%5E_~h1o7>#Mlmo1w z+y`Mln||CUz67~KW#h=S9}D*8elZZ6nDV1yB5YpJZ(YgxC?wVqb&rYhd?u<|8;K}d z@fc9=Fn66ld`!&Jw9>6{mCs_Omp?92wcSVP>AArt9v2SmK5C8Z{sbC4-d*g1Cme7WicvAF*)*Se9H1zZ!9v2{)$xn%{;WPB@ z(ptCp7cE{mbMc&#;+~iz;^cH%{glWwOk-oJeNO>f-+T&4U@A7##b!exGhv0U zr$vlLc?2EWo6(7Wd|Is6oKg;=>cMegwDTFfv!WhOez|2C&tj57Q(u$qen<011^%HM{^kcKHalA(nGoH?ItIb@s_+@fdbWOH{EK$R5oORd1+}(6Ip; z^fD++Fb#Mas455!8nHZ{co~!=kX$c|sX)jG>O;}5U)B0CL7WID>S^81Vt743H9~y3-Vfl~tjEk8;KcgXIZ=ZhPAK_0(yLCOq7c=`B{oUgo@k~! z)9o!t?K~@d=vo697T{c5hIyyBK$_JkK4#4tzc>bTj^$I1uD>m`@ zQ>=0Eo(QIM?~2~q=Hg%P-W2{GxDc5AzlSMddk+H!0n!Wf;(iXG_b?6LeGe6}(9iEd zr)nni`@+t)Cj}IH9;;~A`w*7~w7oXsm7aW`C(8TSV;Vt&{soQ7aH{+l_UDGt;t!y5 z8A{K5AnfcwxEd_*hd_e>JwQ7Ke2Dozh$=r6>HY)JMUid<=!l{HA7S*$KN1rdc+s0v zgr_?4BdF#0oM`nR+W#4HS@SXI3l5gL-#gL2K87YRm!dzxO30x#pNKsm7Eu?(HV})0 z7sMP^%hl-V$OTk6=_160k@UbtaUb3Lm6%(1T^RJ`Ex7sevY1aXH?_A({I*bBsumb&RzrpO8JN2AFVU(W zzQmACp`=S-Vhg?!{V47$VSzoU;!j(I62}?UeJvKUH-@|G+HeJnCZ`KL!>C__4_Np$ zb~fd2geQc7zt!58D!#!golAk&fZOjh(~my<2F1d+@osl7X^r=z%x|&hHk*zF{4MmE zZ^4Gl`g;zFTa-$@1l0Gpelw`szY_)t{CjQ(C6ZSEgc*MPd!c}n&%FUGtOdLrt^`nd zn391ZT*O1zh4%db62GwRS)ogFGCg!H3pk`0D>M5?F;5pWfL_CI`;@*uvszeCx@+{% zBfR%o^pmjBo5M1@(Alfl09l3YVs|xSIJ6sCegaBg$;sx^)Su96<=2Fler`(z2U%$8 z&l;6NZ>7+`eij4h00Kx{0|~_4bb=P0g5MW`33S5oIOnD z(tiP7!R~bjJommX=7YjuN_L@FuM36fx|r6!ug}y|6523`Vm*{-P82`|>y!jf7;4Qu z)sE|vNIiyUnzZKlKQ}BPW=>4PFhsUg{XMc9rTi?s^@S~~Y5%{)NsuA*eRAC`bhlQo zSAG*wkP*=T3%vRx_a&$N_+=U8+z<=3sk-Ep4~gH!GRRK<-6j~r-o(uE{$El2HFdq| zUeR@bhzTG=Z~cLC`oagtl2al4vJ8~8@s?QNUgt|r1@qf75Y!{L{|dw*h1;mn;oGQ@ z3v0XZl#%xTDejS?cJEgfXhBO(MdEqc?tRK68daru(+WxH2s*h_QkFqpsn#iNMZlg{ zr!3dZhx&2%=2Es^86<_en{Y^{^rRfU;!C%4Gr4J~s$RmK+#3$tGZX2!zY+zCiWEgyw|6Sum39Xx5umL=E(_>zfC5cF zHzK($h1S*%^oLRD27K2^clElQNzj2~q-mGWv3PSRZg=&SQfdakM5(v4BQu zl}5w6((N#G*a*&LG`$%KDpeMtc<4({4gV|1FD3!`EI&2uuQ5Pg-6=Oh=?+HVPAA6G zxd>EpUl(NxC}H6#sH?gt1s-WW^G=O(7dRjiy_Cm;lSEHODw%u;f_tn}6nYFQuh!>5 z8sQ#sYBXJlQnEn(MLzJG?4kHkezcOnhx6{t1=PcfoY4v&S3qgWbpA7NU2n>Y$tz|6 zS7Pda1^40L!v6|RNk{OM{|ILKV9+l(RX|r`l|`DUHHU|%7t?z<YeXJ4=)P{R`IDS?&%~tn5ysz0d1DyIgR8;6hGW6TYRdrEkT+##b%yb z#W~HUhCa%4Eu`es3U|%+C7@SdPgCZwa93009wJm^6rK7XYXDD%Ap{G5N z1oyr0yg-s3xZg|(q{A6XqIbI#i>|uIX#*#JMzf>(U+H3~u)o54eCseO}iOR`Owr%y@kLpHlI-y=C~-su2N zJEwhepQWdHBW{6~XPq65{?ws7D0RkNF1!F0C5{&$)66>o(8>9OV=g-#?j99pvT4U~ z#YBgMVuZ~3S300V`qS%~%1%ganB3WP zafA}-l`seq zmXhk#r(a)WmG<}GKAu{$`}{{J9m-N5CT1z6Ac=>wl!cxt{qb?~eAynL ze5y{v-*2{}hchL2Y|eO4=Y|}hI`du0({d4z(bf!qdNNnBvR%ene~KN0xi)IFk`Gm+ z)|8wvN`_Z@3J+6yTZjWw@J_6mZjDhEKmtYpSn_uK>!<1GW+@!)fDSpnLKZ zE0m5}9{y{)yh@gPXnwr6iTJ03K3q#IkM zcwuIYm;hus;;w)l6M4cwyLFrj{QTRyD(Ul1p_-&6Vo}Ob)vzB%D z<;>o6bE48ila)c-^D+8UCMlU7gWY7T_1&9%+{4QqGpMbpG8C_E{XnB(}%+D-0 z%{p7Clrha>x;v!^%)_mz%wMsfyzDf{*WKZLASR=*?b3K^C{lJoQHr+_w0D{^kj6}N z7ZdJgGcQe3w$LBb6*Gu31S?$KRjNeL{27=eQ~9f*^!*If`mq_x40kotivRX~L$R_2 z^C4@dvR!K@eL7PaPOr^E@?B*hRWW4{2V!PpDV>?E^aW+bG%)jXlEy#JKRxTN5Y_?A zKRxrW5O}mhP|4|HH=;+~qG;V*)NMvvWh3d@Tx2|*Kk83~^AI`huE;m%fzW9ZrPj1) zDwvOn+GRc}aT-sOCQi0SAs|7cEQ?N;Q*>@-m%6!1M>_u%^fFAA3>Kd`r?DLh6#yy% z6&NDCQd83U%sE}!R-zV+)90n&HcH$Lxmt?ZeXJB+P*{eV&g0Ozbh}JRf-tHP$NX|s z2Xrl&@YI)f41u6~8mFk*#-r~7(BXFz|01(ny(Is!V- ztYyj|L*<L)Z8hXcuB@Ls`j09FhSB$c7#v;$)K#=b z8BFojij_WFql}~@)yO?HBr}Gu7}uoVJ*UUT!J#{;96xR1WK^7Q#}X#zs{~btg0%DcLgp=A|Sp(MeqUg zPyu=9C$TU~``igZi?wL3FQx6j5|sf- zDdpM_!zt~&4`}ZAH6|mHDKVWp8D`DMNL!^1HUTkytF%FUwT7Jlo?1K>NYgfpah8bV zAPPPi7;i95=yri~rVn8z;tbzl#>23HBaKm1Y{65fABJs?^#LS40#f|rVR&pDp0jBq zeC}PxJ(U+M;sGX+Z3~!u7!Rdfjo-0VtrzRJi85;0ilHppD$13UaRzr3gH~!|2=|v@ zftfQiD<5>gW+W_gA(f|SevH|jyYEY(NCny7N zmg#wVr#Ql^MWi*G8!?d!ABU}a`BA9c9HT+gqY#O*W~h|ccVU=OF(B0v4OzKe!bTOl zKn}Y@SpDwQvds3uRPBKF$}|8jhcb=I1_amRWqAZOh^6mgsFYsy5XrYmY_!ZkS`Y$N z-H0{np53A^;==&g_^+ThQAZ@q2{6IG=e8jcH-C@FRew3>K{~Gu{`=>9L>3*~BLvwV z!>ETphRaVM!$|R;(>zir)9-HpgW7_Itjj`L_&X`;8Y}QX=-W2klXfxIb-*WILjS8uj~8~ zY*x|01gdDnYOPzN7^61QU=v9rT)i!gA`H%gmm<-OA@+7OLRuEG6(3D=o5bx{F{*(e zGV4$7CNY6Cmc~RTQD(E4iQu%iK_I=}4568SBOp-?-EXmmf3({T9kA~JUcK}H+#LUd z=nK5h%cF{eV23$|)kj~Ebmbsc5sM9P*4_fMH!%`ZV;=z=Uw;BofXe@NtFI#H8g0>k zZD9B#nlkUB2`}$X-#sZ7(CH^3XfGUs7|lN9@mlDULvRnS|3(zTY=N5|K-^fZ4lB-q zk%m8oB?>~uyj=7G!jxRzqb=u2z%(C5?BV)T5VvLgd=cIIGz_E7tPfRzuQeLBK(ap% zWN?c18Mx_7j5$z4zw`_wd@bcXD;}WiX9Z0CbD+R#22@fb>NN2=aUVJku+GpB1pXy$ zJcamB`)^^DI*&jj96BN@V2xFK1=xBGI=Jq6tWEA>RPLi6pBI%duxPQEzIaLW>7Ja5 zBY+9oeY}8s4l8AKr}G6w&(@jseo+7GwR_F$K8|B>SMM>dtB;}W-CSQvSC5J2ZmwHA z4PO)y>gZ4{)xRhf(dysfn)lUst~+sU!B=h=m6b1{zQL#itq8=aO%y@7fNok%7l3{^ zF6@>-1JL<0T3gL}AH$QXbp;UC2#U~g)c$)B5BqQQbOP=1O$J2()ajuUqK4{T0g3SG z1P`ZT(|i`E?|v1z!&fq0R0UBko7);u(t<&e41`Qk~U|>I#_KliIV93vm#k- zcc~>yo0v=I--d86b%GJ2d|Jh5A{p!7uE$WVz&q!hc!=f4FWNPX%HI*X1qn`GghzcKW>foH zA|TMug(T`G{F@=88uG44go*VcHHzq6*uF&WWg(5c00gnj_SEC4ohcUY3t)=HF+kym z5IWQ6J#b9Ar`wM`-TL)#rLm9sYXkR_??EwTTj7Vzd>^D_DvT*Lu1Z1>AZ=Jb>HR6d@=*YrW=!waf zAvx`>U~t!E&~TFRV#re>&1(Z=X8GWXP$IVGV0q+d7lUEQy54MWM^`gUKpf1x_a^=^ zxdWu1<^xcZGt~ryknk_Lklg<(oPmbUdSwJ?*MN{S6nN);s08 z3uTLo&*`0Xm7#pi#V7P${hv?b{lr1M+W+|^-ZL5>3Vd3djq)jtM|oJ|6Ntajn0|*e zrr(nqkM9YM&-)$JZbNxM*Q01k2{M|KIY^O z_Bdyv-0fsCk`wE`9R?gC!gTle)fKp z+s>{*x%DicaQw+5sC=UE|2Tu{=oBM(fAU8BnJw$(x=t|+u9H_UC(`v!tmhAQdbMWq z*NE0c(aEpHc2@k@Rs0R&(TGn4(x=~uD!TMH?5KDGYY^){#Rm9BVb=scjMf3GTxW%G zs{1EGy3h~dtWN#bxT;OBErGth2B%@)HOStb-=P-v9VD;)JLJihDTi61H|Kk(P#ZOc zLCj};k8o$^p+13h`Fqg>PYJR#m)`#YE!LX#=)h!rxNN;^jRDEO4o%wjqu`T6syP6$ z*fi&@_oTf?kc2A_msXEJu4I9nM|uB3`fr7aZ5)06FL;DCtZ>RH(;|~R$|>A+zX{7g zfW^G8WFPzC7{}GvQ;F~6%fad)zxA$lH^Eh7YMaBLkTn$e zns8Z6yL-s7aPz!P{V5?xj)t#y0{}VgAQ_0X#?8Q&(rXcWw@syG+ zOpb@k2G-cDK2hkw{lVUHtbZ?zg_>)yJ`D{4acYBs(vWbO2j^2^qddkwvWeCXl#c)r zD>e3!OK56;8Ed!=qx;HjhTG86m+?@X-w(BRj1GAfFE*p-dVkpj=L%`kEtbJNmQorR zfxI(!L9w@C==`u>us)CqBBULzA_nSlN!lX3V;m&6A<=s6ga; z97qW0rej0GjZns8=FPxZZi<}=o~F@?$c~9}1>z$r0$q_QXLL_YhId}2t37=9`#XRr zD-$y|&umkGT!pv&?f7`B45k+{p%ov#}7!Z#|GQSM545#Nx$lPz@1xtEJrRS%shisWnITfKTYf zM?rgZO9U_GNaS0Sknj@H7L=Hj%;UOSFEPSV70zsU)ufw?klXREU}0W{37O0P0HQ#enWEGHmeqI&lN(x`nhFkR*Y?LP(0Y&QbZ z)PSgPiX2N{Pl1NY*4QgYni9a2gE%ABVpL4c`vk;!d z^xb;R&27}i6l_Wwjl6ZOem{lf$&Ik{iVjDyQxsp(1@aAf(#BfDZ>heHV{!%&p1wnl zv>**&T>p$N+#$26CLcifNUb2Gdcpa8SqW>4z+@@eXUPGSG7Ai5EkNz?EO7EK3S>Wu zEI{q=1+s59_bnXML=3Y;22sT<*`M+YWfZa}s|)1<6&A{&<`Nl4-xfl1AS_e@JybLs z>U9IB9b$RtIt(G;sr39D(50tYkLZ2N%;|a4=R~C-p&;~)HjaR%#GyheiXkLEHc!?bVZO)1>Lq-4u&tG9{XUi zEQfhieEr%InFn^SawnVD1$1DE{24{rWFp%gzbf|_^Bzt@+LU}$xkuj!OhL#;y>W$2 z7Eqy0+Ngag)KP*7%_aLX06h63G|QzQmdT?qsMv-YL2*@>#QnUfI=4)Y?{j-(;g;Qw?_Pu1LiHH6eaKGgtYpg`bGATYKIz)fDdOX3fy XGU@0_=*o%o#Y(vzVV>oy6IwEPL;Qy$jaZQDcjWV2fSsUBIqbP$S0{g;eUo62qhz zgNaE@Q%p~c5!0(;dNJ|;o4XW!-|zk2=l?ukp64Ffxie?ZoN2!~=gdud^&U%l)gDV$ z^Y9QKpRkX8>i@IucXqUf_!xaZ@XUPNpPhLkC}5oL=n=21_wfn+_-r!hyyyG*_=Nm% zrVHry=fvL;&n)!u34W9>1MT$WOdp@17oX?>YA+k=<6~dwY3t)-8|Uun;}iIL;VRJk z@?tiZ*|(S386FEBUpx{XD;_^Q{&)iL1mdyb5qX2~1mg+86N)DcPdJ_icpBnqgr_l{ z2t1K^n&4@Qrx~8+cv|3TiKi8w)_9`uw87IBPdhyAd6_+W(nNNg2hEJOvT-R8G(T?& zXkOk#(8u%c1$`_p5%kf#VW5xX4FY{Q4=Mz#1RTlkxjVUOUjkd96VyuPOaLJG#*KSf0ioyFYr`_mMu0eSDAm8kP$7 z75?<`**r3@f#kEDQn(gLS#N~c`1mBbfJqtS^1>;hrP7vWj+39@731Z277>gB{yq=+ z_>QssWI16O%${Mo&qEZxzSSOIf7CT?y1a{4&X8m1lVrISb(kR!VBk7SR_MeGdAi@d zc@dIN24!>t+Xtt}ajY?2oFdO&vTkegECblILgN0x#lpKGYp*N>g` zxklkz8olK^C9fT2MHo#f_P88Qf%D`iSzG@6JXGN~FRzVg<6kjxP;UHsxhIvbkfZ3z68R!iDfiLn8e!Ad z$lWM(nS4{))IU$*2@B-@tbDmVim@14vPCnMc%2N?Ono-U&F$k8`i~no!nSE_p5tyV zpHS-!=$d&;<-X;s<#CMZ_3t@qaWdwtPa&jyBG?Ag3+v>GQml|QE=y@m=^Nxev~Iop zDRbSOeIiwDkl(Qy$l08G*2;|0>(k^2UYRC0XRHHNwn8VF9RS`oipr30vp62LN#1E; zohaqU_VsTi{=pUu2&tpwvyBpZc{LO&iZtSfw#nz1)J^i)?41PYv<5VBr@VlE+#z>o zU1{_puU5>|eXI+`MJdrj#r`5&7aF=tUcox^ie2(yCiRwlHc`qP)R25qjwSbQc_!;s zHZ779W~#}xPqy5GRv(gOHc%AKp}yI2TQ-3DF7XcbUUiiRl8xjptiQ;1 znGK_qhvbLo`NJqVlss$oX4yyNeQZeWpL3|eQCVU4h-@?H!K3ml>tJX+jm|zGAESqk zp>q<&yT_>Ns2pio?4Xkm%BN`8gJ{)~6Y?XXRT3qhklRp?lk&sV=p-r{URSA|zCJ0x zWxW?Hy9$=0DJ)msPRS3U1zjJKH8w_MJ3KpyEg6 zC2Txz{g@oU*f_eGtcqrg;QJoOdL&IT=XEf;Hmd|$O7Gg$k8bA4o!De*^Q#=qhvmx+ z7`u-WUdPDGnjweK)&jYOG)au~1l2p{B>sAVyw<`}==odd_2goC5lg0v#d12EMu~2D zBTJ(2p|#82DmU62St5^>rix*eakO?#@xE|lpnh8-+o`fdewNLm(ke{ybdTJLJ}i~@ zNHay5g!WiekVevsx{;aoET+q|rIvS5fR4$kX>}IgBl$gx(l|6=&oO z`u1te|Apqr6500Aq-W&=BCkXPpOqW2`QBHMt1(@DMxHJRdg@u(!Io2OCo~&uw$Rh3 z<<@K&<@|x(G80xlC#!5JKlz;O&)AZG6=Trza+oz04VrL=+^E!#LY|Z7(9h4yI$KjG zlqq`vJzF7%Nvnl^!>{QPqMFS#=d8S)t)l9)@)K+&84Iv))i%~n@h{4uYy~C0D8D25 zi18MF(7P`O)9`chaBE(gNId(s(u8u#<%V?roE*tEQq?(mKilB#BQWbl=UD_F(Ln3HRH;wxBwtafl>l#w6Ojxx{7O|3Tk?&|&h#(9kDKw_^#d1qdg zJJ6w5WsRD?2A(%xg_8V;9Z{N7)@xYdN?w(N{b%M|QF?^8^zql^zD%Ofm&7zU9}+^o zuVec7ddpOt$A&rdb=mN>`1(-B8`zppzAiVmGL&!bE&uWB@_5#Ou3ZB6thces&3^+E zE1WjIA#Y$|VjWYc;)1-Ij=hPk?C=H5adRJ5#F|Z@|N7p+PGd% z$kG=Wrna2|@d){-@-2*dv*Dk;Eze>KRc0f#bZ(~*x_lXZzWW_HkjZzXzWfdv9!;^| zLt%~Hl_#_IHEWPi{tr_e{&eD9%!_uk@I5(>K7SV@wXL@|5+4o;^5%$t4;ywHkz<-Q z3Zq=1CGUY9dtaW$T2spVawn-3MEByF()Z<)%;1S1U}j?5$E?r6UT^M`Q9pF*LHllZ z>PCA%k_}*rdf|WgNN&L#cP9kX;L8~C8r?o{M;n9a;AMK*>wI^r0>}_I2un=PHSFdc>$rampix&7fAcd8LG0WZA*1Q|7jjo@-zM|YF9B>~ zzm#LKedFa0TKScnNZr4}&fV3UZ z0d2e`Kf*>)d_SzA9e`5z9DT@EeMu>lI(C@cphcr%z>3IuFOKcm(erTLBiQP+8 z>&1R6qZs&Xrs}`t=6+N2#|qdNw=FuNu8Pg1S(FaeF?gX+iXCf>ncwEC$ZRx^^HruY zHi?p>mGShtRSBZ!?MfiM*+dDTi%peO-=zGB0#lhUGl47OpT?dHY2RYwY5!sG-%a@n zf2BKPQ%!^+gz8HHfyyRYW>Xf??}5rXX|gHTIXxn~tD@~#oJ?{N;aG6 zP2C-)oD|4lvljrwH&BMs;c#Uun=YDoAK`ri+SNc=Bc+H;DH1vt^2T%27|yWcV>O;a zsjZbT%9^i?q3RGN5<0kFh&6?_MJS=Pr>COQ#Rz4%Uur(~{mm4f;$_lLyeHt6+M2>? zXQc80TOdTMVS1s7vWF6yK`j@XD1Ji4OMyjDpyqx0U}YL5Go^{|#QeE+>g-o95&yEe za-TpIILm$j7*WeQmA-7H=ztNa?Y+DzfCbQ)_t{cP?jbjY-UrgYDAc*Ijj{%C01Zl_ zfo+w|5)UNeii@ znX)2VReEDn2G473t&r&tUE73k4>q8QQ7$ zDBY=`nbMHHh*csj`Dv8#rtTHHGFGwD&$HApskd@XsHGD(LTp3g~Zz!m#mcVKnGZh3!FqEEGnLn}x6e1y&bApuii2 zQJ~igTY-L82;l<1Eo=(su zMeCJ(*uDZ^&R-1rQvL$abNO>XU(AO!EbxW=WYDwuLiURMNubZ?j{`lEp9uO~{&3LK z`9naT%||nAk%i!6izu8A+NcnGYz+%%gElCf1sYyB12n8~8faQ*A=ueM3ZW2NaN#J> zphD=wW-lBFYAcKf4J?E)&K6Jz9oYN}#aNLFg(jZK7utF{zYpkB`NBNM^I_BrEYC-t zz$f$3_kpMKMYleY-x9PeAEPJGlWzwt%@;Z-Ny}Sx=T{p@JrbLLY^B zkTZs&8YLjOnceGCMJU2dp*!!~3>+uP=wx^FY*I zP4>j9S9p(-W{t$QFfy(wMS`fIGJA8X-DT;ZX2tT*tj5#RsD7$zjSlsl}aJ zPCT6%t{j2IPi)I*UVl0@LfHq?A1a(eIU}+0;ZR(={BIehT!Q5ejTlcSHb6x=qp{5v zs7fdLc(l?V{f{H_oi8(Ba)eM5Y3&$=q?V#)^ITmWtaPDyW0k*v%!N^b((hHyvUb@s zz<5&s$Oe?WLRm#uS1I=F#n?Zm7r=THtr(O#PKl(<@k$PmIR;}#K5YU<5OA^iHhF=_ zG7a0n?TJbv&Iu;#sOd_)ciR>nn%!4viL-*4KYp^}tb6023*pKLzeNSnby6lKDcuB2 zc#}F#Ro)T1PYX(AN`UXmf*6WgBzB7^p@gZ*9>DlJL=d}w{YJc?Kod$Zs8+t`0M=`B z$9j3BrvR$lo~bN?0Iqrgl9jQvGeud?jJxa8=-6~+CezDjC_5Q|dM&puVsZu0uD|6@ z*)lqF$ZU+z)PHbNyD|%qLAhT^p-(zt*qhCmSP7P9Zkgl7k za6jFcqs#$duN8kr-8kxWhdL$}bgAPr#yf5N=0nY$y+&Gqy~d*LeZ;F=)+>9Na)e%A zpv=R$;dvDx6xCnExd9dafzv{5(kfATKf1jdQ~l4y%HOPS_A2EGx-wO9h{SeuYpIe; zSI6P?V@s7u6t_S*%8x9=xM%&4^D13krnI43E0yaQLlM#@pMv=ERZ0v4xJONgga#YX zFC#GNs+K8H)M729dUg%8I)I|qLX!S4EaXXJW$cZwChCicBPr`f`Y+yy6nS&kqpL0@RD>W8OA$P%NXJ+E!u=h zIgCand!c?~G4NGU<5>Xrs4dD;2{=82;(&*#A`?}-yG4nX0MTpgBbECwE=zMUITN=4 z;P=|9bcCrdb*nNF2MBfmyv&z5Ta|#w{`W?$?%<4C6Nje*&haw}MuE>TacXZxRl6{h z2W?Y2Nh5`&*tE!_!bl2x2z;xCF@OHzHs!E@?l`Jzbafx{AKIa`VpAw%FU|^=cPLF@ z55y6$EhX*(wraamnPk1Mz6K_lx87sYqe$PcI03`a0E2eyS2SsfkP(gzQ^pYbbUzfHD%3aB zx)`rayOSwi5V;Ou%S}I^bO6?eG#W+i2NZ2I%YkwY-J!$2Bz^W zIa^V2Jb-?5D$iD$;*0?G4ae~SNiG^rN~E;vPSwCjNLU|oNNFIg6ffd-;k;M68!EX* z$~x@Z7dm4BT6Gx9Xppb~-A83daD>@=1brHKREeX{jwrGeD2l>K*Mu${foggj#nCrF zbm}~kk14UV`!JT0;A0TTGz1zn?if1ED)il4Xe}1l2^OPfrUv;>$CUAs6ejuX^Bx~l z3o)QK<|thPzTP^qFjVr%q&Bb1P53)GN`=K5BKc&QR>?>zeOMXAzAS%4=_yGtn_(|~ zQ-x2hL|H&DaVz!!L&JFi38{`kK8$*WA{SpAc`$eQh_{j3xLbQ z?=BM0UoTXKFzop-|HIN!tP}%$d{T_F*SHcTlRA{(FcxulnQ>HA0(jPfpD0y=ne~UA z6AGJCsYm%F;QLJr3!9n7eqnQm0Im7!Wy;t<){3`2t-Qvp00cYYDEF4KhMIenAinI3 zqF5wY+qO||FVzq&8P1!W1rnrIr|li*I=XIk2M;yFc?q_&dJUauq8}(!rsPeR1vDS z%|4-6`K?QezlHrm3Ev6a`7^q_7{A>HO&{${-8-IlD?Z z4v_KSE#+H^dP#wahAVCP@7I*Q7OR3prwTPF)04fjK&gdUQ~e=+eY!oL5B2 zuREw2R@g1?5Xz6vbX51j_E*cGKaMu4MD9-NEVvPCYVT~&?2WQd*^PnF%&!LSy>SPl3er*@*NeN-F0-3{_Q-dzoc z1+g|;XByB$Z2*Jwr*9?;)B{BiyYnSbyH^uQsF$J=KfAD&X)SebHN; zOvC%2+w|V5KYc4~?4RH-I3Z2PXdkq5w9rd2&`GV}U4%Hp=!3rMULXV0#+e?kUV?-2 z>>jZ+^;4%)*ja34?_2dNv#)n5&O?^2jMvj?l4`S$+m z047aC0ZMQPUtqjCJy?0XZQD^}l%BQ>OJR~Iw~j6jgf^yugKgxv;ls^0Q^cFCfHT6f zBnr-?8H3dZ65x#2_s6patB)}@!>gCXA!-HehHzgV11x|=+0Oj~cEvu>fjdZB%&jNVS&*xCHZCA^aH6vB1PLN_`CLSI!uyVK$l>sTKVr2)F=tgz&7nLU}KkKHjkO2<^vzWPtl2D zr>fh96Y~hA6v#C!znrQzwXnw7$!cpD45O0ON`MF)sxEuYgc!W5)o2Quu9gBiAaKG= zh~mFZSD$6pW!Rw<+P_krL#=12bEKtW$1*L;sC%qP3nOW^IuoYEluhaakzk{%v(;3V z$`kKb=Q6RCZ>EG(UTMbAaM2R#PH%iRwo!ma1c5P(-#zt?MDeWYPZlv}Kuk0C2z* zdh>D!4O2>o+N!#)P}8lx$ZRTWJ}E0P!7X07Os2P2s@Re#cLSPSn-EANR;s7~T^~3R|PjAjbxEB|s8bJp+7%^NHEm*>v@l_Ex&KQJp88rkfUedl{8C zrm9!kOLQqsT@Hkia0?Dkv$M<6)dNzvFcSmWz+j%Pj-$#=P+02>jDpl7Y7G67q0Wc- z1w-LWx;b21NN;XZ1L?WV>I;aN!j!L3bIDc=g5S5OLjgm~S!nV{YCD)a>U*Q_HgzKK zkl8ByZKu%fnDq_CvblgNwyPdt2EhW*OU$0O+E}`}14;%)7vt*CPIa&qz-5!!eRF8Y zF0~&Zk;x1-1!QNU3!B{aJAfu`Q#)oa)&jG4V}Q0XZ6;K41pC$VyD_Aryt&Wp##-&( zqxOaiG5iA7klGwJnz2_MCp?OqC~h-`ZS+2jua*L&q2WTn)BDuttq6cCW(X;Joe>f(#$uVoc8bbFSzz*0>Y~G)|lv^nhCF@ z*S01q%6dTE33G<&W-^&=KZJ!v^DfM0!h~!n7e~l!EL)0Kf)=C(^Rplfen(hl%o^{d z-bXNxjheyURx=Yc>T?vsR;R?i=w)_H9prVDF5st*sqm8nS8Nl=Z2?_Bp|+td52}B% zSe|x5?P;-gmV6F07sAh@iW0RAjKwxJV@Oe_I%Q6v7jiM4J9@_+q)j49Mk@;+QkTMU ziPL$FWP2V`Tk(R2)kkfB5s7Z~?|_({a#0uXM>GR=1r!|*r#g1B-nnWpO<#sot zs8Yk@q&APJ5p?{jn#^NM0U`KH9`%%;2%Fklvu26*Ldr(`)>Ep#FZ43{1uWzDomK|~ zvhMukOKP%(8Rh5I=d3^xXWmrzT03ScMLqcF3#yy3xbjNXS3($7n^J6#w5S|AXYl)K zHjjNzo#>CW2fF7BRir|3F7DpR%8kq9>^AEpO;al!j{=M2w5-v^z!+c$vA?HWL;@k+)Y;;4? zA}H}TrsMT$H4)BNbJ>92#?gPs6qNlV_M&D#sk!vlkLo>AyjY~YHjJ859r&|9seLUH z{H!}_j14t!>^MC)I>}@3^_?Hq5 zskuc%6obFkf))pA{(R#<>J^I!y4p43Kb5ia-jDcEzoGonqfHi`}BOM zT9aXJb8R0G5fMrxa3rRr8_*Hm#Pt(Pd6^VV8l zW<^lf)}C0%v990WM!OE|1%6BDmA2Y=ug}&jGLf>{X)gdBnNXv$6SVhYdu=5EmUmM_ zZYqe@(yXxlY>zY9@uRZV-U7S?gXA|rNv3=`vDzeR?$Bnyo@9zs>C^!C9a^Hl0FT{@ z=6JQZ6+0T z)ruql8#CykEyZ@m5?9$xYYl+4{{l99$UTiNc7qVhyKB9Hx6l`J%#nuoWh1923)ORP3X;@+G4<-yZBnOv$d`vUz`7>3Wbj<_7R7YnsTVw z%t24|)>>MM(*C`0r*M}e%Hh)ItypN5?^d+YRB(1*%+?JYTT+xS{8~%;X&0D&L1<+eRSnQOOOmkAAe?zF33c6YhMnFGPsoOA37>ba-lFkpq9tgOzii(D71A)EF-cA|;X#l&#<`0ETqBbM71S`U> zy#r_|eK-;`#LiJo3swtrs>fhZznO?S8`dF#akPE37A-X}r{4wvq{XJxe2g{+mLIb! zlo|!t6+c$1$WBII_)XMi0qaywLci^ugns*CqSlQrPQsMJ&jesGyz+Q$Ks8%w^?jI! zjVX4rRvBc4({M3^7uCo14G+3kgYSGiE+<5};W@RpbdLsY=7#^&-q<|?w1ImVXs8=b zS$mLsFsS5)N7c^U@R{0fyW>Isa>LJT`@;?YsqIHM;{R>mh)WK(Y&Trqwg=pBdE558 zg%5t8yEW);H(cJfoo?Ze-{NkThJQA>BS16U@O<0S-3>w0+`>7((H#i7!EFIuU%Vc4 zUGYlLwZ-tX+SU{=1zlad1awt#D(K4Mg`g{n;d!<#FBX2|WyQh|yR>*NXln8Opo@#) zi?%H)hBwx>uy`7L=e7mKlkwyJV%%@A%`S#MY_p2TfX*x)2|A-#c#@|V3r})Nv1oU4 zv1swMVtCeVNyXhkrxtesol@KpbaFAg%C`H8V?ZYr!*6VxSR4&Hp;%~Qd@+2;wtI`4 zf{rb21mC%BOmQfFj4rl;CKjWIY@>>WnP+4%107K$+C98Tgd+?q5-lED1P`-qNRepY zJw>8TgNsC)1{H}m4J?`hdd&@ozwL^2Fe&Y&0Foj_l63qjAhJAkI0bqjf) zb-O^Hb~`~&xrOW{ZU<F=pnZyjGrxC{&|j~j0iZpL`h#{a5?bt%RwRZ@haxd#VvB?_^&%nr7jE&l zqe!$sDH5{(9|m}MedpVKe7i8RPXTlg7cH>A7EZ;2ZJ&x&wMAXRU>rNVXJ>)FlasU$ zC7eBK556^N{^Xvf9YvI4{Sjwv3c$n5$=YC?HWKgG7So0lZ2%4$W>A%RD!MTp>wE9% z+CZtbSh79q)wzVULE@OPxyFJz16zs}rpzrhhq&#twAZb$jBZ}#J=8rvTgzhYxTt^u z>|bk9dwzD#1#FOTAb|~HfjXC;o2UIMu1uIQ*JkyurGO;Ph1y)4Cd|XDWf8UjO{_c5 z;`rb_ajaIy(3-{CNw`F@+Ag3QhqMHGIaP}QUN+Hs=|XJHi5mb^CN9B#Xo%vAscH)r zyTm2fMtd(s0rnAWE44XoBCU(qcmsV07CXKBbnQzkFI|R3Q(S!5VhS#5>__WXXd7@o z@J98Ev#}}1V=J{daUQA9nZUQM(grbD8ojq&YO)T5CkmTa?2m{ezrF%ey|Y#;%RUMj zlh$f$q)vCT=*PFM14e?U69%By>HUpbIi+mWR!Uv(%r%hGwgIk|rfIiWFJ7FErI+>O zH!`$73^q~|i+KB3=~tphP!u%oYpeJgYia8!*I6BUl43wyMw;!v>NdumAE zqmAN+_G;Bk_(y?G#V(VRrL7k>N)bMaLM>?bF|9G(zaQiC9?^f@==1&BW>_gCU+@CRp`Tb!$-9zOo%-}R29wBk7VuV^p?Jfx{qd0Y#jil;S0;OP&c&DvKfzNy7ic-Ax8 z8OBohsMFe5i+irW)t zzDTHVdGOR$+nMI&g7T1$nx`A zh?ULdp>JwiEyCuRb;tv+7YcTz(i|<+KF<3ebi1UyQb<3Kqn-wmG+!Jd5pGhSd2;p{ zV9p)4pn}kAI2mQ#z&ermjHdA6?`x8UE#RX+)V}kT5LO5uToW8MYpd~=Y<)qyhi-qQ zHQ_0rXwNXXT=%~P)SFs?bvEgorcu>3Z3vZDX&=B`ifAN-rk>LrBGn@S>2JM58l=tU zLq5~8Eo=#|`a-+z%a-zs-)OK2EdLj-JmY(<4U=F`-C5Te-un2@KWOtTY$cyoEe76- zTFti?(zM`T|EP6gaG|#TSvyI?e!)Q{;urM8T5V14yD0=0+4PL2Y{DhJ^eT7Z1nnH+GN0L zSeyid?zHw6mXZK*y-*1l+DqO7^ zI-Uk<7xNMIASzjMEEk~NoDk8}+v=DFyq@+B42BT2AzidO76G)2vy>^9%O9M{&oP&U zatyn-eF4W!w-jZ1 z$5h(B)6d;hyfV%2RZ|>|#rvdyZ98r5CUr%cMmVNhx9(c)j^ybP4j4!_mp5^QhY8y% zc|EJCjbkEt&n^y$QThbOJSx>3;{$eOE_65lmn;qkt~%`~cRKn9SOHJ6*U^&3PA8#g zTiW-4GlhanoHr&;xZa^vW-Vi|kCVbp%M*(I= z`5Z@An*dMb^|n;#F-`gWrH%)f^(Y$kAeWXqZu_zmG-SqlfuX7P6}HaBk?DHQXQmFWevbkK9*Tv8_8A@ysKRNHOA8=&CKeEwL$h){5 zuQKan;8|3|v-rR8bl<^~poLfYFHbvm`|*q-$CHd#W;kAE!fIQH$5X?y?7y&l!emKM z?C-qAn~qujJfp&~(brlE9_5s zQ}TJ8+BCohB>I=8{fM1E?Aw^RJhfR2Vme}1I|@bFC(FNg{0+A}{ZsAOAMo7HAh#iA zSjIoVB2`CXRQmiTX2t2u;coc%F_=Vp@=}MLgJBM1&y@e{@C{(kQ>88DJjLGU7+p6M zK4gM_1+`i3Xh>-oQapmiJmfDt|EEPrPb7z6HVeOEi)qWOXOQAkco+4EHdyHU1w1d)=;kpc zqNNh&O=5=ov-6bF-5E=XMcOJ}7!@d1BA=r^$GRO^Gd`aQGhwBD#7o)dXWetqMT2d6jNkj@7DjGI|-6N4&!$d#j^4 zAH6ANpFgi!6Vuva{RC`2ql_z#9=y-)n3sIn=R9Li%ukH}wm+uAU+OOTJVlp+6?4ou zETTI;#q-sFsrc(VDyHh)F@yQC+?X%?rJj<{B^(y8^<2N>Rqi|$ z^S&?nmc}&Tjh~9S?$4@uLV-4%@x-$+PgvMZO4#UlrLNp2Cf1!-IQV`p@s1Z_-e3Vg z9qZ+WI|b)5T<;O1$B)&^L6Bpp5cme3pYLLgNTL6c_4_efB=#E*|2U@5LQx;b1oP9M z#0+NacN+b3%yPcEDrQKa1W(LUUVw-SALMJkjoHT7ujSvz*!(2;Z)&!ei?TkGGkiv;91V0j4%8p=8(l=`-g7cayWVH?=j~CgpcJ}bE;F;B z9_AcrVYkX9XBSE8C;1%qZbG#g#_)(hr^*5?nVsE;Ny%O;M*j?o3XEAo&F#+5nNN9; z^NRopr^H;7b#`OtoB;o<2JQipPmVz6NEm_T!EW8%!i}Ti;ofx|JpAVfr`-%Qvio7J zaonVe_D+?zZ0h{d4~Wh)IA#=KliMYdHPNo0EL81q`j@wN`brktGJfd;$7ZYuy&Zmp z7N+wGvF*_(lXOHu&vBl@(6HO7jp~f&w|@oB$=(1}EYY2czXVqg(Y2r3M-Y@ajN&7l zrzpL>^Qn;Aw{N3V@X_^dc#xj-2IbW%V-HnLbpX5j*aOSEIa>w57t}i5S;YdwGMBsW z0mp@A+c>ndU#zzFkn#aeB|x}{sAJtLn1Nmz;YO~ z?>5t!5s1hi>@njgd8ng1MzFp4u_*UQp18s_+(w8E=P3B1V7$JU} zKfSw}lW*MS%wVi_`3`4%5F5`E9&iGdMQ1K{XTo%zIU=i`jED?jX986X@< z+1}xdNz&;*B119w)XY9^9L0$VMGpN>SsO9A{1N910V0Mc+Z-J{E5|vDQG(mq#2S;? z%sq)Gxt+O;Y2~HPu>skcBzRPYXs# z|1sA~B3B$we9F0Qt?~Pxm5KJtQ^S`9LK5RkaGYo1SFnw zZsmA22HyoKe@BdqF68Ou=xACNk~cW27=?Tk(^agd{`|X-oJ|h(I&swQT>iD--IGh-JAr`=STVuV*Cc>X;^qbz;;ZHr<>aFWL zn9#uyG_nWa58%-uF)8nX7_dmx%NM~><|SM)C~vQK2oM&CJNez{p%37XIrU@~FbpN< z-BptC7Pn_4eI(`db6%qzE`1dnQXZ?X3#8|J=r2&~5@)!mexY^bu5RuHyrPHR7qZ;rTdcyhGpS|--1lEb zz~ujJ1T3u?0S;cWTu=066SG(AOJHrlH;S53{2IMCy}DYTA_BQ_m=E8vVZ7 zC@>^czPbC|-F z?!%q@AsPAri#x53-6;A!Lmv%;hiTwOg`?<+P5OHHA7eM`I-}cL^a*roi$08Ux9TaB zycJBP?5%n-S+?O%U$5*?NH03NO+ScWG1DGwRtPad=)iW}gZprZs5JG~hSGNEOJQ&@ z+l+E%LTdmS9iP}i>uLWk9ctdC_orc*sQcSpdWvaRStxFSz&cV>q&a^mQxC_2xa|To-Rdtx4aEn+Up{ zmhIQa!}f6(n`nwSpf90;2X#(;4(k5&#BO~oeRM#diJJ_%l>GphH1WoSj?C7dr|M&R z0QEVf+u)uwgWc;>wQSuEZ>8DtQQ7)M`aDbDLa9ilV$x5+4`aHIM;*~u(6=Xb8~t@y z?+%ZQm(3CVE!>5Jr{Y)mL~E1EsC2*Hn0`N|FA%ncnkzSH$MwnPha$Rqb%x63QqqHZ zQ-pe+R{p!@8fy8BDDn(qBjDSMpOjJ~Sp$BOlhM!FyDjZ~%^)xJUF4 z;F&=*0W6r$=%JKasK?OMNA=?(3VjoeekmH@T)b%en7&z3MH)VJfaw2+F~r_{3{w*} z`WgvC`0U5^QD*ShBl-@iEJUZZD$s*ybiUpjP8)bjR8hhGA~g}gU#Pt%+dl>R5N`yS zR}PMB&oU7rj%G1@q5?96QHe+INIQ!4S6SR0Wx>o&-@Elba39rkG*e$H(K8V}HM&&q z#DoLJ{G3=ccYAc)EA2=~FQJW3=$o=nLp^>^>674AfqHhsFOw6Gke2Fm7|pJy^_^CP zbZzQMH=ok`&|goZli{byfYbgdJ(|*=!E&8gs=I0Zv-+0uQ({FvP4Q3ak!eqQ+GAZj z<$;sh{)G58pZ!6PKj?7}Jh1j-9t-Hv(v6@;O4ouOE`2T1@QaDBJyV6R<9=Ed;&QJS} zQaC*A+e_h^wQnnhH`czjR0rKs3J6zf+7h^p?Q2R_g03zR;;t&054y5M2)d#qO~|;s1ioVXvJyCP?Mq9< z&a$KgPF#Cx$vvQpOW>xpFDikX*1n*m2k86~_+jnyO5iuP&n!=9uorpY zBexfN1fv2^bI^QGGtfMbX!qkD!TK>zBr5IA6e$RqTT>j?$T@d&M) z^oWX2co0fppWEgJZh!k&w@^!>dokz;_d?L2ZlR*VZbS>%`@8Q4jd#xi?c<&S z+RZ&3w4*y2G}fI2s=I~$9Pax-Js#0|#XS+Uy<7D9{~A0;TZ$(Td&uiEK z`@VuXx8t-v8CN4xzJ<>h)4?H9{YZaK-zdK72H(Joarl_w8O)O#&*|HM{tzEGeIZ7}`!r;?ZT z;c#T)V^;O`-I8$9!YcDAR=VU@_3pIyy#5s2jOKQVgwyaaqVQ;1^ct3!p<+Wd3$Les zuj@_V;jFFnUb^|Zp2WuR#5eRQlOv z{<^5ILHHgb!EwbJwP#<}bxL|k56Zp-^f6w9s@?s5OwMSa;+juLRb&lUj7 zJGki8kM(6<-yslTUrM{8FTxdy>sRywL;_+1RaYhCx6*I;C zDG=EGcNAay87{rJKf|z16&~=KF}UM%eKlLm*%#<35ev9Usbk)ivL1uXC%@D~5fyls z9DOPFE4{6>K z6MsGNEx_DTO88nIN}=BYhb$5Ib!tRAc>`N`>38~m;ARY={)B{J3VcTILUVu6k9gTM zysqO0>koQ=fMu|mK=ZHbL#(SI*f4KHP-r*}xS>ayfrul_M2S(*&1LI(t*k z2#9K%($?8_dRrR$qdtwA{)pN5>5t|TG505ZSMbPz31eJcqGDskznAupL9)_ zjB0wXB~|~4MeC`CMkjjoXTa{{U!aCXe?W<5+K^v>p8P1~7d;2=Kz!c-Bg@xt(7<2y z6SV9%eQQ9!TJ|~BXv!l|$F?b&FcIW_lWcwi%UBn;K8<+c@Yn78l2Ho2{XtQF5>W$4aPyLnJd6QY&cp1+e1=#jv&h*X6hVX&t~Jya@uOM6#$)6kYK)+X zAw~%h_P;mzYvF*y49YOfT}-90h7c${%$N+2jo3?bB4~A~ji_t5G0A();vm4ax%Q(- z04;7{BmrETt1o81S!5idvdqZ@tvM*aq45NB@TrZAR?G@?y(Jo<-NtnKw6Rf)P$;0| zs{qqj{s!^#NF#(v@buK|OJTCn9bubBFu9r;2WeDe{FU0+2#_*1mO6Q5Q+z={iV?XH z=@3D8OWAHm+`RPZ=ZH<HqMYcAT7He_cpIZ5y%%Lnv%-V3Swz$ulPSHO;V<4~AhZg8 z>H7SoFmJVKXCQdB8}(^#RO1eYi88>i8>L4ZD<$|zws^ZHu?^CO$VMe@bK)3R&m@4B zE5`STb3#w#(Ziatow78ep%tzL+@myy!f?$PKn{n|5a7PH0taO{j4i^Su~~%Em^$mg zN5>erZaYIaQYrRztbA3!V~74^s1Xlqj1ep#$wSzR!unwI-lH2Q0Z9^P2nEz61kyAE z&Fo9p4CA1GcA+$P5O%&BF2j$Wi8Dfk zY294fqiMH}8u7Lpb?;!r1HhOku3Ap5>AP4%#`RHSJZ_f$`zw`~bi@Dwq`@lrumq!Y z4Qi{*!x?TvXCoJ=21xBWZ?u*sgt1djSIo|wI4mJkyJ9fj;~m+xb(-&wcrR?43TR2G z-HcU0J2>IjC)g;hhtZRQdl(Jq&o7+#+1+SR6LS@37||5fM~vidMt#OOU?g0Y{1+aR zy&w(uM56J*Afpel{YG!Tv8RD^+<2-OgmL#)FJlp|cmfqX2^0j7gSp(@2qC?XF;jdg z1NxsMCPyC(an#s~zU*Vn5?{32w24_V;s}iR>m!XIK6QwJm^S!r#LApL)Zlc7A9cFR&??qBY}T6)9Ao(0m58F%rY}(8;{V)WMdc~ zbH9{5q9OICPg)%=@6hUUz59S(00@!R_=VkWVJmYb=@Wm}o^Zk9xjL}pv z!WeF^`965Y@X`(9Ky(rK$+gJ%0VYQi(3`oZFE(z%Y*#P+kyPVRm=jU`C$Jxy__~%Y zCdpFcVZYKe2!w$tT7t?X3ni>H2GgS}jN`aYipCD0Z7YovunvOBV7_3LQN)A|&ns|$ zp1j8B$|OIbQq!^n_B#J$t&wJ7fjnu0;bJU+@7-vWSb$`5w;9jU9~o$(jpuGMHd`e4 zlD5(khOIK{HA5$Gv(gWni~zo7tD!I!#8Kq91@=H>AHg$s8jTS-#dCLw#BjY3u>( zCyizN>eEJ}01;h%SllHybu*JMeZknnn7jO(@sk;R^nmxE485h}^C(zNs$Rw@efL$v z8BmtF)MG{=?ca$0OCN>w)JwoOIjczVR)i zo3FJUVgn;7{72)ONbSnse$V*G!rF89hsJ;a@uiBx;x?TrpDDvIdfw0FAj@Z%+U4b+ z8#k;>=GEUA@TcGY%n0M5-y45hnZkpAG=4NAOt4W13*gQ=8P4&vUyT|5!c=(JYZgS$ z&7t*=88Zd%hWzqBMtjDdJLUS(Dmm^HU?yMU=h}%*Oyuk z#WGkr4Lf?}TrBB#=cnraE{DKJ1n2IfD8GGxYbIlt`1ieCO)Olx$F;{QOqDymSPlvu z&VL)>TICBn=Hk(=zkLNNsvn8n_?dAoH%8((*J%FSB-ig2*e>yP5OWfB=F(KxerA1t z(`-*SiXDN)82mc(uajJ(tT5H&&UTIEnKNCXOoUbG^Ra^Ty_!HfEE7AT~&+B{FgBU zH!J8Tn69R~lz>vPS&i`wzdPOV9p>xfT?4Tn1=FGom!0}%xB`5KA|2sWILBqU+5k#n z1y(3O!^PP>JY|#XDT}~81(9^~Z&w@McdKi$MY=Z91IzI^Xz!DBSF6y4vzFdJtGBsH z!m{@%waIiLifo6g+@E}RxQ0?}rYn;0JB}a9a^X8MaLE^lt+6fNcf^%yv3`b9U-8io zy5bS>Ha*UXttr!$$SY2`KClX(elZN#f4kc8ibpU?MR<+`Z%DE7SC@zod9t-=X{FLi6tAJLA=~4qM zEGJLLhVhVRTsK+B_h5eG-l)W=K~WQ;?x9@2*haj~X;)7(Sip@BrvL3y`Ob5$mA=;N z$oeBaTNsxh#2v_|zT+xlq`v1WqOez8twf?ut>1UOUH*m(U%462!z*3kmVloRj_@Ez z;BoQsiwF(@pJ&#aVD*bo+a$qdey{}B|6ywLW_z;*jrgzET!9vZ(|qo#vLaId+BYtI z*7EjeE-bL$yS}lqN&Nav@D@Qe_P+(6v3$bMu0w$$E+EfqAw)%us5&@y08jeIg@Axt zr(&Nn!}5<%o3>sZtm5gdV%IaO{LJN~oZT)FqH4cg9uWJ2)r!D@!}u0@eB7SeOrf#Z zbj!nHu|iI*TOpxIlgnxmO%mi=-X!+Al_l{TEn~y*Wu5Y zh>mSxVGr;iH8zl?<(9$R9+XoC6LHYVvT>j%%HX06da!IP=GTcQAqB5B0gR;xuNC-MqHVpIu@jn*|$|&m!nqJlgG_4Fq`k;+v@FoOpD1)g! zXnh&(a0RU^gH=6fZCV-3%o&%ix>|$}CF&-BpH5 zjzK%i-~|cVQPu}^ds-R1HbL9UdVy{&!^O3rEoD7GH-Bi{Ml~sArYxb)i^q2h; zk8t^1@t`vM#~xH;zwAK;_K!SZZU4{%ruGj!;AVf%0}l3gJ?KsQJ0A3={cR6=)BctR zy=kxXbOir{W%eQXalsP@`UZtPsk>@?T7h*wt@2K>-&w^Mun&u`(A7*aOpVXXK^s)w zOUw)ej@&Ub2s7g+>fJx~1%?nQF^XsK>LFqjhm{XENAZx@4<&wne5~TjZ;Uqo9%=qP zDfSAZH)h0k-Ru$w>=J4SC4^3ZknHZQOG!dUN`goygwUjh z&=G`?1|mfe$x%@Zpdd)iCOfG#E2tC|ELiXX8%mQ08v?)gxtj#=@%#Jh;|ovrPC3(O zPMb3p@!{m?7D$q8CQ+kXnx1eTs0gpo?|0st6AhAJdNdaJyy%_gZIJzb%C}-2d!VJthth%zWR(b-%aUKZWcCN&r7*-;H)uND|xQ?$#DK5v1b=;(HAB;yuD zhf;BAR2Ld|Bx?A>Q6$>G z7=2cc*P|b+CZ&kn1JOwVHeRfe zqV}cew=LqhFS^9W)*~pRVy`sI5GbRGd5Q#yZHJ;0C2L1yqYdYx*3r0^qptC}Q5wju zzjynOzKX6hYj=&#?}~0secz83wkYItbGtU0?RrWyI~gqpvK0w(ej6Dg(6gZ4?4M#E zzY*;fjyD(;$Q~~oUQSaAicOd%-+RbLDW-VX`bmFgQMY8dd z!|MPzOw5ts24CS{YemnyoC_CicCneDf8tk;$!hUeRi{m|@gna2FWH|GF+ol}P%N}L zpR(9^@%Q?dRBH%@G!&y7IGZ48ZRcxd-#knE0-b|xyodDt|IKuy8h7PJe{$Z0`v3nV zZ{a*_CV$SKoVWTx+`-WSLhkJBYqKuqnPHMP4~=ghQ4;6GjUzrS1~dL=MNUuWfaD(B2<LZBl@)?;d5i@8#)e8MH+fB2xtjw<9uU-~cGLL3rP zRH$#g6Q*;MxEhZV*5N7ug?4Y<|hv&TL^dJhkMMCEMdjHr-4nhV60osz#e%ckU5=3Y?%*ry(wd z(!-*`UY?77lbayK>qSm)pcS%&=b4jjCsrPH{s$tP4OR21Qp%go4bn{CTTa}&sV1&| z=)}F7*}nIj;Z;TWIp;Unf=@ZI1%KvTWrZ?j-IvY`m;%95Xdyc7og# zH!nN!XvV^vU!1F8MvM&qgZqrd)wK7e(9oAr2`mb;M>kf znDQWJLmGD5S;y89akV%u1UL4a)oJZ*XG^IA9lY&qBenOZyMEi5B*`M-Z|9l-t2>|y zT-Bl$bnBioov&J~3*m1!?QiHBSa935Bw*75*H0qJ<_ZZA3#_i{0hCt5b&E}M*5Pra zij?BE)iq5L6CAGE7BR1;>jg{BO;-<*P}hYW$U`VBnNAnFT8qEy0VOL4)}fb?TcDix z0%&1F*VDB0(Y7#!4R;NdL}Hlhj>ImO*2R}6Yvn4li1?N+xB~$$uh?LBzjk(c4V$<|ibj#H!~k~jSeZB4^-cgd+{(&PFx1FC#Wg>G z=F6^U#MgggV8ja^*O!u*ue(~o%I~hbE*)9nTAXvwiPzO)THNZ_wcZE?)pR)qj*e7^RE(2`)W= z5&~S1^#9;;(dEvr)nfi*uDcd7ceKkY*#U%7??=pVWgz07GmuuzbzKz;Q(ZLzC}W}PmRLBi68%5& zEaE&FX|8L0Uwd8i^8TLax+~tuc3E)JKhR^t>6v&8(SaJS7`mP5S`6jEKU$w{_I;LX zy!iAf*G|}>N=0#$6VN2Cvz1AD)6Hu#=hffZjl}r%inVaSZ%OcSi6*C92Svt()GQB zi`Gj|bDN($?FzAojptlHN#e;*%->&_zdv);w}=g2yUt0rnZT>~b5pLx1<9CyZpMot z7hUxv@%RPTFBUQ62N#r{ATj-QfN<}@v_r*&pLtG!ko;F%5hC)MtG8qWxwx+BgJqp= z;R+S|e{~&`Y;*pJ8*v(4Smzo<(KlUqMC`Up7ZZPX;g0@X9&^ptT^=Th;4j?uB#H`f zB?v6uYLYZhr2g%yVdZ<&>+Jt1>mT^K(*B>_EoiUiYEMVLZ)l|>F>V*#3UDuH=am0i zM4;P&+5(y&Dm>v z!R6V5y?S~NAebMw!?W$r5wte2UwOj7qB#M4q!>%Y{23?7A_R!v2dX^QK}50=!^K>=rMWal7ONGj6ZqWfte2R|dT6bpzh>qw*%lUZ+pQ1dYQ?)>1_v?mp29QU>=iw z2l6HZ?$4V9xG#_C#l3kG0QcmL2Ye$h8E|*rIKW+bNr12CjRoA92ZA}R=8n8E_;@XE zG~lawkdWA|UKU7f&l?H2Esu%u|3l#u2EU3_Wn&fohySQnJ@-l*v~p{Q|8r@I>kZsH zB#~3kea#}W8@pi<0Y0R1Pt3H4PR-m?BxXYXEprR^cF6{oV{PI;5%V6hlS1Ec+i7cv z`<89lzawbxeoYda+PT}qGC#`wHuD>6m;5iTIqtV+Vhmwz#fP z?p*ixVnwREQ2?&0@XvYfCIPfQ5ya__Vu^*plJ?N)k(! zyIaC{%Ksb#op;V&>6Tcd=s%1;l>~BivhagC58@dm4z6>b0G>5m^C{1|>sh2wvGh52 zbt~%-E1Q@viKG|ZZBhN_G52WrP1gWA6yuiZ)+_E6wk8!wPe~$vn>*SfH5JQWb+5O= z0e|!<+6O@M`wXQ*UF0+KAJ~8C%;5Y@j&)D_;4~ z{S|wkUwYbY9{M~)&zy7rB(ctJ9nJnzfo;?uu203Z&)tpyvFJ1RJqwMz=?eSD(I;+w z2)BdwYP` ze$72cvPC05oJXE^?Gx|aa38X;^Cf@VQQI{_yXDpbB$t@=hx_*cR-~=NWsB3U9Tc7J zS}4~4>0T#^2KU^9C3Zf)PT{zWoR>l7H<#o|0aoxAmB_k_D^~f109s*{SBu)!t8#Z^SH!^HRa<0BB!PdGMehj_b4Y=zA0AKm(N&i9-ubaf+(@Bq5MMtY@Ko7M~T;( z$Y%oBQ@o%0>p*W5gj5&%!{xhHsjJx6T7J`F@(w1I+JW}h(dyBbhH?)P-(3#Guzum* zR6#%u^>Y*Jhk)KM3vor#)PfMawi4sUgEemV23DI@Hl)Ubm>vbD`*aQFMwFZ&NwFf0 zC2ssM<^$RGMa!Y$zy;T{AQM%YNk&;dkIT@1xjqz~I?Fj0Hrak^33o_yIMk5;mc;il zG8q4!2%cLR{6&CxFHUY{5x?}2!Ku90OKxEim;1|K<99##vLy22xuI?U4tHMJg-1 zipomnY@_K&ij2o<$I2h@)!B@u9GD>9O9BSgysd$VsytP7(LGsyCV(A8!JMI$0u!1P zxgPbJB>y2Lh;EZ*HGp+rPgD3N*&iospw#$aHe5MRe&&IgwRSt6|)W_^nIjxfU2qug{mCq~)11 zo?OX7C0@w@*s~I!SF+?F3R{I=A z0G2{=S@LhTWVrc?^jDywklNyGw(P+>n<99nJe)0uf}fH1RF>D3{jp}weK=2he@*@l zQ-Y}JYPoGrx*Sw3Gr~Ioy@1D~xqTJrA4n>Y6sHm-@R{}fiLS4a{`rIJRY}F zUQc`0$rE73ggQVz2Yv)zW{;z%F-!$4ImaEdP4W})J0NS+?^(Gq8!KhDuEeI%)s~QJ zG~XzPSfK=fZ5;1+7^WBKmyPmh*h42Alhf$k=j5TdAK7%1>@XRbtWd`A1ntU^*TXRC z;Bk2=#cq%T=@7}wK>v4rUjCh#h>Wfj_q@EEie5m;kTkOwQT`tJqTG&U)fw@X-!-WA zW;v8Onan5-OcVPyqnnxD$xNi#d*pOVd0C#u3_~VtVfcIO7R;Ggmr1^44e^J+ zlqcmkVHogpPfZ4jjb< z`tVhGCQ~z+O&RqXI=vf#>-S-jef65$(c(>e0NMH-auUmBcVKX^YtGz*(Ls8c^6!#& zO7Nw+8#$z^0ugiZu%GyLuMCSTD;+J9SJKH>WxM!vKerfENOo;5?giZWlJ~F)(~5!o zeME$NNgQRzz~(D)FaJE|MT6#0ajSYsGjg zx=Ar=|NV2aJ1{*l-+}CWQjMY5eKIU|i{%lJPgW!z1cRn-CA@^|@(%PZr5{3b5)R2u z>V8;$iuSyTJ~;jskX|?}hg;LT<_DV%5ho7IQ%(9JOW|r{Q1MZD6C{E_{0S2)nVmT} zlM$?iMKcpOu?K>x6zr(&y!6;?_C2hV+2nSNj4{ z^Rx`Vui(_|nLhAn_6#4mGkdxZe3^Z@4;-02tpMzleOUoGFZ|;K9H)I~0oV=uV+CL` z?L!K{UfLfm0Apz%TmY8Ro=^a0(jH#`Hqt&QpD_>22kU4bkPmjz-aj8KqWzJ4aDw)J z`QZNSeeg>|1!J0yg)8TeXLK#{)L=a`9na zu%Py)Uhc~#Ua+0^#@=Cojl5imQ17FF4b!|_mk@6}U;{7LsJ@pwd42&8M?G&pz`9

    sK$YfI;E#!Fd?tr7wT)B7g(E5UbLwq$#`9US zm_GYjZZ3{}F82e$&g(HvtUfP~F)s^bn?#P8q#M2Zm0VzJhvjF|G>ZlDy$Y8SwDf{} z*M@rlEBpil9-ESzN;VMjneZNs6IMKXQHB@|68)?t9FRz!@8qvw+>hNpnV$GwewGP! zoCJs)ODR9dADRz1!RNy-@;J)%Z} zHMu%8u&7HUCx&l}TXG#5dIS5jhxXl&zh}$+Y-(;-L+JW-d7P+qQ-;?`wo$89nV|t) z`LCSK5`4a^QjTR79H}P!CSR0T44;|g58wKSoXNuatfv0&Xa7W%>c)>m;M6&fmS-t#aMOu_D*1q_r!C`3g1xXnM zr-I5rsgDQ`Q2JPye9oH7X~%EMRc!RJO*zGu{$gOo`YTLNg#L>e5V%%)v`$Sp;GyF!u`L^B4lBD?5QOPM8^uLqdDG>#hARyx>* zpiXdr%Jbum+DdQpg?O_+l4*DyWwqZ4>jOyYDqSTTXxt1l(Ky;0qS)!TdP)p^UtgI= zyXq@li5e&iXj}sjiU3w>9HMjxjLaYQk4)7=lo*!yXPC_%CxEFXzw3Ucq4F%7Qf1KA z`sk{J`pN)05vmMenS91be^r+>Qu;Avoe}E)j%chzP*y!<4TaaoRPEAOsX^BpD~I6( z^*|G)tz;XAuI$0fVvNwk6pIA18|P>;z0^#3ibd;~r&4oe3}uHa3qcOw30K}_A{fLT zg43vP3nfCDz*)x8=9Wq;`mKf169jfez(=r^PidvRfd>WdGYL<$R$c?83#$AcWc48B zV)@1PaqW~3KyOAyDF1;CesFtbGi>mYH<{K&C^czmdj;>Ew!&UBt(7vVGOQV0>YzNv zq-9nTHzS_bbX2}#fx3UM7%h%RDxm07{Y=2&c1kTVCrX)Z+RlU2y^)}Fr)X7aPbr$R zm`*yC7NBvV{(KT%H}L0CI^x1?VEWdi)4P+)t@JYKTl2ezIP6v$uyOr$k8+jXQk2%X zEolD!OvUd-;;yR1N$`FVt1DYgDcu95cBIEVXy+1v4BtwY{4_cft7K4k4>U7%r1CuF z#way&dMm+H+(Xf5R5ztI9BW)jP^NISY^D)NgIl?hK-T9aCTP)eG;8AhBo%0^VqXtn zpyb}lRNNs!Ar&Rbw7!>e21ffwdIGl@S`oEDJkv+fn5aE6Ly44fx+!&OPYgJTut$_f zY*rNK7AUT>L~q0>-MEf+8a+y}=L}GeOMwEFjghGOAY}x|;|dI;D0Yt0fbPXBS74uy zB=C*Qr128Q^e7$%Oi>!r%^}Kd_AQ$2pYPXY1wTT@IdH-n2rNjNoGjo+X)bUZ^4ezbQ ziNo{I2(fL6(i8}m;djn5B^eUDBToXE*p_C7XDGcSvSuiW;_#CS44uY|!mOO392Utd zlr|ErT&;AV;1$YXsYOM;mB4<*inxnim9X5Zc#=*01MdVU1Fi zo?e5=UT2*$lb%_tWb!kTU<@BhW2LlphyhWNndXlf^o;VG6v@m%Q*g~nf7-hqRqsf( zHYk5WFONU@6#WP|s^g~=ty=oh0;U(r^FCB^J(0apNiyYgI4Gq<%4CY)q`YR56QTa^ zq#R{C8wdFF_YjYevO!`QU6#Ev!#pZ}L5X1NgDh;C~C#VqSH1N3FxTe;llU-OSzgrpyOnlKd^}V>u-WLdH8 z5t6s1-Dh!_CyIn~N<@H^%$)^M;MdCMK~;h>?FAq&z)GL_UKvQ?iV6esTZ%2`8^tLc z=M|WTPb4$}>2E06^wpQ>j0pl{0u<)svF+n7;zRj--za@y--X)E7Kp)=edT+lLqKf7 zWODwfyiV6I;ebfIig~~CvJzF*D1dV{f5ZF*OundmYl9#RjYIY`6n;w?OG~~}>>y4m z5Tr29NDwEptmgI|@eY3{GeNHO>qrM*Ps{#1f;{)=OGK}EtTB>t;>!tywO#wqm2E#(tjCakDK zF!lId`AK?0ME=1$df{IcF>3^^wk|DP(oHQ z-;#QoAC2Vx#P2t`$AfaLY6_%g2vkA2m^sA}n+i*}DwQcy8BJ>%ctydrdbHbDO~sv9 zt7u+beFHDXS=GmBWpx!EH7+TUVp|P$i39>8%&zVOslrGm3TG{KCGP!EaTP}i`+u}mR{x}k za|&FeMXIyu!lz2OHM5-$cL@<81~*1~;jX|;wYjO92Qp+(nA#cE-!!O=`WJaxszJ1_ zxq1-vM{EmqsKr(X3u7ji{}(;iS}g+kfrJpQ<65AsCd0PpOAviE+p8XHU8FRZCmm>| zUJ|`Js6*i3oUq_xb3E!TN{CbO*kv2Ftqr0yUwtvKlUmmTN114Kp2VuZm5Bo7WZ{+R zHfjyp=~n*)eSlKlz)RR-psKzHOK+4hm}~PV#r9U8rPmC#k7{O$Sk-6p zU3fy{jM7H5?4yphKpudp)HSESI-Qzc21$$crPAi_m8ih3zEtJ zB^ErC_X#kyMCQig`T!NT&Ds!V0Ap)R>qFG)!Z%3Gwuo0ARg)!mm4$S4q6#k&6V))>hD(|VR61>$h**v4Oj1{Yn0RTD+5x@< z-kYTMfggk5$!Z&jHnhoVN7F3QtWGzvW3u|41$w(RQ`N>G3+VM}m@A#d=;=t!@&UYg z;XIQ#cBJ3Ft*l`-T%h6DCYNtSXTP^roy>xT zb*gNU2J_!OW=s!?+K(;*ie>?QdRz;?&}gJ1#kCt@Jr$ASfv9|@KOo)Ii`JuP|wo)U3@Cq)dS`%rWR z{6Me-_I<(9$`b-cI`-oNhB@~41j}LH6)cB+M??U=E!qISC7J`iDOdn|OoRd+6)b)} zB3S%z8>W1;y0Ukc!U9}CU*`B-qe*Ea!hk1rYU4Ihh7cl#0ncllU|`nr#Ws5^Zu z!`duHu)j}zZ0_nFNv9e7sYhI|A?u8--^cpF9@#mH-amj_O;+z zedoSmt|HaHXH~bpYJx<3V}W$HMUqzSe;2 zeJuf>@$mqy^YH+!^)&%p<6}AbYF|UZr+q9fUzO&ohd)pG>Hx0v1p}_|IRL*GvjMYx zH374H|Hpa`OoS?yD$}Z1lqyssRUHqyHRUC9IX{oJO|c8rF#n>wGfPdChKrP3UP8n8 zFSZ7A0~je%UNv*%Vq+0lZ@8(%B8`}^xxm`S!JkCfJ#xkFgQ`=qjYS~?E0cUcvzj#t zrlObC#yR=w;~;%2Hkf%7UxH2TdWt%bus?NYz`iiBU*?L>eQLM`5;J@}B{GWC?h>SB zvElwLbDls>)VR|j><=`y43yJE5p_tt13|$NbuASgQG3%zN7XDka72Xxx}*ovnt`f(gUcx?755JkgJsBl<0FQCe3`utM1Ee z47#A=jT{UAZ3g+)f7CFW1pKorlTW3S7t~jB>ge_8&P85EwuO5>i6>y`wIp(?8HHE~5594OxZRgJdr8$Q|ghm$s?+*|5)cJ1|GdPd@*_$%sd z-ybS8yS4rw8R1{*tokG!{*PKMaFBpU2xdt)dV@?OBy2M11LTQaP zchHnc6Q)D-(_KUo5PU@I+iJ&58Cm(du4sSFoy*!`*qtIR0>yy(MX__Jn_C<3FD>bw z(^0#{%C++93wtES6gO`=b<&!_0j?Mnr78Rf;Z{`}L?u4W37NJQNI_2Ryj2gVQeHMJ z+3SIFuIk>!P$LgSLPkrdWLdz!8schsRl+q6zJ^ii#2K$3_RTe|}h$ZPyvL2vcaz64Rd zwYLWKXm1{dkOxpS8H8%B->J2PFAB|y+`as{+Ah&P$M!9Himi`mf0}BzEE^C16ND^N zDSe>U6#lUO7u#ZfNIct|k7iOd`mEMd?26YGN+7zM560YM3$|=?zcZ866!sw+Y(fW$ zt)sL~63rN<$&in29fnqj+)?=O_s}4Yhl8BKA~I2f=}dyyI#zqp!s@qdbFLk!_2opdy3wkj}2f9r$Xu^+PVyA4nLuN zD~*Nc4TOo@W#y$-r$NiKM`1*UG(7kjT)%YGZ#>)$Y1=H&qs6?8d3AcNwue$ypnE2Y zmse6p>x>*|_fdW8o>rg2S?@QQX7_frIdr%Ui` z6|+nGva*oY{^qS-p*ei7YEahAFOTOX=O0CGKH_Ty&Ja+=WhcUileU;n@72Z%EXN z(2tz;YCBk4SFGhp)XAsSmYx)`KCP{#ssN?1vIE5((=Od_8KGs+nE#I18I&0-mT|_; z77=qu^CJ5pWUnG(4{4yR0+{A&03AK{oXVz`_wV&<8jP)e*J@M5F|95Lj;_a0BEQYO zvZ+9UP_keZOrT%i*19ucv9j@lp~v3UwzEL%!L+Zjh5X7;e=;%deOMOb`5$eQM1Op! zb!KJTgQ+Lb(NkJ3R3)JJ%qlLF=-L@9@d+PrmiIo^UKa8>?RhZOXElwVI1k&k&I$W~6 z)f%#aZ&4NmWe=8xtQWP-w)zMyZ=Anp4*!4&`^R^hA=P`Z5eVKPY0+REDZalr2Nd(AHX{9Ws95%d4cQv(5Tld9?LV?Toq1n3cz z`%TwYwCZo1e&KZLZ*3Y=536evhQAM8y`y==;XBwosHUW+Z~Bkko`j??s$>M(gL1G- z`a-7v-TaOp41ht0Q1JPXK(XASca&fuw>Ln4ob_*zO64}xw@`3fJ%$e4Q5RFHP45X; zo#Vpvbo%QC3t+H-y@*P_3&)modz>RA-kG#2h71NAAUInI4N+Zj(^Ch%%i z%L*xk*z75HWg)ZugKi>l9QdriC)R6p>?A%we-%^z@bm%o3<-z zRYnLzK;#)E4g~AFB*@P2_vZ&Nc5a|I&Z(!rhHJhFjr3a&$ApRf!FmD~Tn){|CDfwJ zA^JfP-$)--9tf(twKX%k&?S(23j8pj@cvx*4oa%3zf0}IbOn?Jx+dO&XJih9b0t)+ z#BNUTDU4;m8t(MufunP)?>ZY%LfTBP0Sm~Ze???&#^+d{w((j1wF1?3DZ#ng4 zBE6#yy$>>X;mk)xclAMJo~q91Xt;QyWzqVN)S)xV{rO&(8gxk0-(~gLKMS$Z$n6-I z@c#Pe)q^sPLMZT7EWBS2fqG=YzZ9WyiIm$(AI>F5`EhpWuRlZgdg;2uFv~Ia6hV-K*as1USCWYpdW#fbBLZQ z+{5%ji5d^n8;S`DdXYrG4A<-BJf`DOgfV(5QyKoc{z9up#e`6BlD@zk4{K)M!bd4} zqW%k_5o9LzDP1onuIi6s!WI3|=p*y0#p0%40IxdbvX z$G8&6#2iT_kcm0QmOv)vNGyST#WAMD1~|GHA}+_M;<a4gnUN#5XdGZnQ6hwQj&4Px0J|1JPUPrP1mi$Q=ORdm9G)WB z13HW%2(%n}5o`b*T2U`RwFm}^4y6b}EQegw1<+k&0J@4`py+TGxdEe#V4�!w`U- ziXdHbL>6&VI~H-nIut=<QOGT}7xnntV?vP@bUOFBvhS8;C za5495LNUx(9cl5!-SB5nF?ZU);&y-oidz8oFK!I@NO4`je#JEa`xbNG^eK7*uy@g1 zKwBZ_vKBf40}7)6Ers0SQeg+cDupc6yH|*Ax8E&fLEfE07Ucb1*c9+~A@JM(N-GS- zpFayR#q56+Vv5;+FXSSJ7h}rUe=DpFc&o4$;Qyh7*=`E&##OOILwNUrHbS_^>-}-3 zCRuOH!Y~}&$@&E8F~Y+AMTAe#<0VM&u!i23Y_M@#3FGa)*pQGM+uC%1<&Wz>NFzCU z8KutD*HUy?llz#G#mtF%zC=Gw*I#4-pMRl_78hseFs&a|xp_p3k5aJt^F{QPb-4(Q z4vjJD!&hQVy~^!M;SxJk7Q395&(J}Q&(XC2TN0RYyy4duGMpRp^*$`;`&UF!;)4bH zCl;pbpV0q?vRiCfs#~l~)u-#nENm9H60Q&X>UC+>Q~E5r{>{S*XshgRV5 zxwaNt&s%+PdPk+`HG|4G&G^Cz!ns0kV_s%o8D-)Sz;P5eP=xbo9cq^;!uhPeSfbC? z>LXw@=Uk`572ymTwO)UTHg3>2*qsWz$46k>PY*4e#^LMj(G@|N@r6@4|2Cb?Qj(Hh-B}?(r@i@j-Op}u@>Gi4R z%jlx3JM}sNp@mCm>lVE+6v|~=^roKs`seGF>vo4+4uEPcKHoXfrk+x3~a=qdhsRqrHOs|QpWN!NCu_(yi=LDp&kRg(Rm3wG!ZYhXZ?nf}imJM?AUC+ubv5+7BUZE2lVNWS7xb8h533nT-Vs|1O4SH z&=od8OP~Eff|m>Qp5|4J(1!!7Q@BrG2S+i;IvYCa=0eY4()^)ne=hXhY_^r&jBdP* z(SNy6SJ@rR?8?uO>fvp9=JqdYMKz1`fp9-Rsz^`dSE|#S`%COlthb=Qi}aeN1g?H% zU^{-4D!u6g)ml}ohce}yUY^M8Is^s>x;qa`uT>O=>oWQVkn8Csyg@~XGo^Y}3AFE_ z>)4X4hxA`fu^8N0zOA92|x-$gswfGl$=-8idn zrM1Vg=&qm8-=L>X=u;pEyKzFl#DwOx&q0Z9xrQAi^Fw_sU-N`f%PM~%YiQ+3eTHcw z2HWLwG%aX(9izTDeF_T-A}}-mb7%BPY`hMq@OS)K*HZ3B`fy0U%y|3`7W+QdaoGxF zXyzg?RjH6b7+N!Td_;L@~Iw&X9)#9?5rXNt{RfmwK8F# zcHizO9@CiqembXHY0bCT=HWHi?6`Bf zjk?&4NIG&-cLr83Vk0n&&{_R8%Dt$61ELY>;Nzl?KYhpV^+{~-m7UCKwh8?QJ%Y zs&9f887(C7%r)#45Lsc$gt5r#s~gPDuvvLi&iEVU+KoCn|Ha5ZCgT1J&Whgp4V=V) z-}FU%|1ygP$N2{#iSFLg2hr-^^%JIf7fg=d*5}d5KlC@PI5Pi{a0n&-rO$=R?95+! zj0Hzv?K}Ez9H40XLUP~LH&{pfPo68pAz8&8HIa1v;|Tp8$~FRB`$rh4ojC3F-o2O27F z_*XY(;_VA#JKYR27E@x7k;Aty%PSeM#&(zkVtoiw?Qv4l3(G+yT^(}`9)jDEg)226>j3wwRzC&@JV zGMDo>8XaQ10}&pZfK0+S81{%82JeLwW0ARJAKK-q~Y(rjf_nYi+$b5_*Ama z<)w2y+-RRO)`+8PO^rt@J)y@H&7t(B##?N2o(X0o+=!@1WYF~_qZa)gX4GI8Fqs%$ z#8PTM;{biv%qYfrCv`BKl-t&TpEA`5qWyD>s@C+tqD6Ei+<4l2Rt8n)OnYY-edw3w zMhM$}rDKH3F;yd@h4BbPX3J-wQAZ4;4kZjXdeGlv4V~JzH0r??4Anb8*IF8}I6zS# zJnpwLf?@1!{ucPnr)7Es4enrQ6wulT#cA2Rj;9=5@B+-2mlmLQ*XJ5lDSRO+8@v$Z zENN}jl8PvLq0z`+u$l5mYr_Q!z-(AqYojJbw=o>B0DA~wAkA)Lbb-y;zBWcO#BsrG zF|v!rthUBTiChtei~ee7=&avLA3|Frj1F{io)Jh>BMdj5oH57GY-;#o!%DI30ZvDt zdDW8G25d;SNkGhKMUYnKncl3f*)Wnt2{i@PAKqqeJS6m)*uj z76&;MW4L*qV%dfp?I=+}8&4`mb@NcgBY+%eRWZAoafeQn!nna5>4Iu>gC!WsoC%U* zt!5m<@mW#T+O$|V`tli?j+-#yMjN`K8@-_Tx}PA?F#53yECVYq+-OTp>UwI@*M^Y@ z8P@$-^0>$7=@(>~N2)p87j_sCzRm_NPUGZEpG9#!Fm6wGF?v!&SK|>n(gpJ~tSkC2 ztE(}PKOImco$6xrqtvcOb$+G+WX7oCD7w(q7!%MbpbBjmi=8;N3mTr%4HN9`Zbmoq zb~F6m-kQ;d?l^RQ?`DM2qcKJ+8rRLJ=}5zi+IXmmQF)5nQ+%A!k`}~Z(L_AZ#^M-b z8@m=yr*Dtz(0TR-N3uHBXob7njb?H3a>?t<1n#1Gp;T;Tk)qY1m9>( zQ5=?-Q5nzx=3X~=VTO0duxpH}9VZN=((YL4DxK+WOqUe?)1A_K7!gbwq`Uq7^kolY zj}^|m=hNU}Mw>cA+OKG=yx|+AEn5~;m zB_oXPFtWZo!YHM*QN}^uyHR3X@fhC9Lv!XB^(u~-$EtQN26K$-b08@mj43lA$cDaU=+r)$Ki*)3191>TD7*FB8@xUG1gg#t|^|(G2YwBVuHiM#8M$LeJ#fu+Ekct(6Lyd#Y1H3#R z2hW^sIM|H=pD)93YKON$U=xfNb!v_!b8W6sJ22f+>^FKDg9%lUARd~l@+ySVmN`Z! zyWveIX{0}mjV{l@J`B^MqdFH?pBgvENUfY};2jj+mCZ>(<05bdO`2yIa6Nz%3on^O z;Oss-4+9eLP^#+GWxgSU1i-Egtt)eGb!pr%9OS4_AcZb4u=XmpD6?B$USPDg6sPfl z<8Lnlsy|E-gyYypsgrTom)FSb&E>F+9<<09#&#*`U1@6?n)LA^BbrY@?BV9O>j@kQ zFmVCfX@2j1!g$2Wr$2Ad?Wx6LV*s65Y;>XBi;cE8=4UMi6=Pq5ZF2JxL#8Q9uzgmo zY_e5+xCBIqRM)rE7%Z7A@^l`Lc6@Zz*LxAP!Fj2fEp56VlLuwZzLD z-$&Lk53=hYoWe zRzttna4bPTlr3>+Es0j_FgmeTDjPRcIGb-LsQykB3H;gYG-8+0Sd{F>_Co|{g^ma+ zuHHrif#y}0=<7Ey!*IjOkJg{7K26wb)a4iA?k5G~1|KBoZO(^eSN4OX96V&S!&SROApSaY!SLDn`C((d z#6*Ta`A(pNmgN{Wy(EsO&)x)w6NjyEHup3v7@ZkLNi5-%>OcoRG;UD*+eT>3@JOdK zV^~QH&^S3=18}SF8Y`?lk&$W-HrffKMV~A`Zq&f}9{aa3jKbeH-l2qjkeHU9Fzmwh z7FHYGe&6WI@4@*S=dVrt7Mz^3K14_NejsPA#nY%pFDGVk*OFcj7E=TI;!{QfSah?P z+|$Mn;_Z*P;rl;A#ri&g1Etrcdq06jy8SVx@4)gLpKy+IAg5{DIZ#{uD-nG|w?9Sa zf&lRM7O#p`RPzgC5Y_qI7>;*r(W%=wU_a9x4drD-}o6f$7N$F$=?}EtwZ_!8%S>kQ}7!` z3(@X-WRzYS%dBRiqqEj;oyVnsJj~FTm*iYJ5)nZx~~#+D#PkpI?nOuv|f+v-I^% zR#=E#opwQ&HT9^xkhqBrH=Q`oIe{myC(D{wE_ry!ksM1$-?$y92LP z5Lma27pd=G*z+fe{eNLPN>c^8q^ktF0DoVjmUoTk{bnt^R=Lx=a;AZlbkF#nG9}Lq zioRnE#4Rc$AG&F?XEUWfYxEE7Rx%BxKs5TcF&Lq3D0Q#iT#OCy)VEl1y5Aq3&J-Nz zd7IW&^>ktvNe@H_M0~>%@9Wh(t0gN42H576SHch{gFG*?r3uLCINDv)(+h{|^_re_ zAQjT=p3O`uWHjYN!{kq@(IAIs472#zQM~*06gM3nydt-Z1_papvrBhBp^x*#N_}g4 zYExisPfM!R)@W1_6bm*N6m-Li@21wE5haV6JKP|tdLzp*ET28Mc?;VIZ=Ej`cC>G2*rIU0F-uzMm14w339`nr*4 z2HTiqnBS>%xUnZLC)NY4Yq)1SE<$;>QR*nDxQ}%41k$kP9vvjYGtE7hm_*2cr>9U4 zUPq4b)TN(VcoYcFQPQ^$c;RuJ5H*{4;5GKsGFEt=D@y=8TgD2(Ps&(H_i-7k1wSeq z0C=X1m4m0tSZ#Nzj8%ju%UCJ;VHqn5KPc-B_Wy10U}j)3`PtdaAU zwFk^AgI>;Yu#9zm2g+b_@7SMK#=6mcWvv1Cma#H;PgzUAH_BKQxVwxM!@J7D0beg` z2Dr0~mBc&BpvZH)Rt8&q$E#(mQQck!eVt=l8LLvamQ@AJElmS_rIb~vTS{4#`f@3& zQeP@%RqEza)>FP%nl=`HUMOWf>GP$m0H#uIcTOp{a#Ja*K%XmR<>$syR`)(z3LiU; z4W%;R`chW(K2yrATvy6%T3gBuSX0WCUR}x+eY%u&nX5{nWOO`L%Id`xrBEq4vP)U7 znN=Fdb<8YT2AEO8HBB$!nl3L%0Zc35`YtPhKGLzYWHR8BC0y$zCCPw`OStM!l(3F< zQ3>l<7nX1X-Ybg*e77tH@SU=5fNz(v#`di;Zor#mthYT@<^eofX0R6ea2dDhP#LSR z%gR`ZTUy5LD=EVb7DsUz>sX7*xUGd{tZ5Zxod6e$_4mrgF> z`SE{fCSZpK)>>tKm4L>U2x$_$#r!fOtg2ZZJUA*^dxkOPn`z_a zgtx|8dzP{ss~dhJRTEm>1{*{$+;4dBxB{*Tf(35g-7|>xwe#$?LWl&R3-C%%VM^0y$9z)J~a0 z{AJwj=y~4O2x}caG`P%l$)2bhixPUatL=_RPbr19b~n#Rx*q8PF&g2iLkGrt@DgPw zPftj^Vh5qraos#^#Iz{SUek~}E1(i>Aa&9_OX;f9(~(WOD;k9Y%^0Xyl07y$?(z&a z9g4v}5tonXYGS?{dkq~{JPZAWx)skFDO?;;JoQXV?kv2;QJy$ZpcEW-tq8FTD(e_d zU{z zQ|aa%gzfglbp6N0F1Z;#693ZSnt^Cb566qr7glFZUX?>?S> z5>4!f?VN|4#`W`9anTS{Vp}D`2x`*bbDUC}d%|c!Pfs*Ohcq##=m^f!o9^}ZyhJkx zdhpueY)>!FHV`tcl!2ZzkYr()uhKJpJ&9sT5Bc`=$w-fB!Uc&*MRVKG^--QDAZ&Z6 z#18c4XwPYOv;=V%l15NjqNiWwpqrBrkFnPR(>yiN(*~6F`a}>*eR$D+!KyR=$sIv2 zkA<{x`6H;y;UrHH$n3bep2qatIL}Tf!!Xx6(BsLTBcQX9|NcT4P8-K~j?lXkJo)Tc z30!{iWKT!hGRYG}zKNc$YzA&rrSzEr)Sh+EbS?EMu?5}J?^a=r<_7H-#a!nu}ImO#zLzD8pM@s#9cW;_jE;ORr3%=5eq$DtTN+^?SJk*RDxkia)Ww~B!0 z7kG9+wT4QE(ujqgn=E_Fiu32DMV@mY!m(z~v%)OX=?~tt7#%;A2iK2O%vyqVZYr~I zd!CCu{G?|M3*#~ySAN%_T}wT)S+|yHe;{ztGS4&?|7FFPfud6ydIuCZCa#l~D-CcL zX1Ql5+n8jUxa(0^y5}(HarA~+dR?lP;h6+ulavfk8O?ptgU2>AJs(vqO+y#O(d-pk zBf6M{%_cX?(~56~r9<;l9@x&hlu2CkGa}nl$ND4_!09W{_3N@dO{;Yd>Rh^n--oFP zYHeLync{Qz$i*$DpQBgVy7CJWAcIRR*AS!mGSP>EIT?9+)HQ^-2nkO;Q#FloG7Pns>wG^2^>M*Y6eT_E~ApU?02&xaSU*?XtmGwq!BoS8YJ<6R=q5^g{d_OA!H z$5l`pQrmeQM~6(QG&F>@;|L+Z<_HUeR!(dHD}=3{4Ul52hHEItC+zJ-#vPD`OE;!^ z(B&N%jFUG)YgJTQ@b+wF0(hi8|jT(b|Z(FC}LA;Tb?*!(YyI+gDT~T)Nl$U^!&-&QoHd6?y`QAxC6N1jcuuESo6BKEj1IU zedzX72Nt+MQlX~LlQ>coE#98$@8Q+El2v-J9m8ZX!72z2W$oF$N4XpMqp5SK@l&ak zUy-_vF$)%k*;B>$ar<&xnBq*mO_4iOzxFbl!>}2ltgx~*b$=rDs}_l__oB*x#;itC z(UYk!VR_$9SgTcCR&Z3LD4 z<*LwU)Z4$Mrs-KXhTU?FbVaBL|6IqXu-4~1;Jfi|>TJE#>#_Uh>26i4TUPc|XG-Q@ z-P!z6LsfEF!_{4$<~lu1^fafayL`1rUw-s6U(`z$b!LEhgMo$Y17!}Um?(1^wKbUA zR6LcMq&nJ}%_w}Jxt8QtQ;XHuQ1e?l_0M2)H%X0+G@sV7&7!qXeKyo&xQg_3di8ck z^AJguyP6|)Z2IV7s$w-#V$D6&Clbwl^-zLc?Pg9h^wBpfr83QuLw9o|O-wY$s9U<5 zryH+mrVdlg z|3Y&J^D1>wU-MEt_A#%fl2pqzb=)BHPM!Me0P_@_IhhPe2v);)xX+|%!8h^$aJ*O+)=%- z!Q4s5(s(Tn6mzuen>pGao6Ke$fZ1VwS;rey)^^tdNlvyvDKp1{qbmdwcn!`~t>1Cb z%7z+$5;d;=`h@vKNqu&&IUUctHR}FlUZ-PAMQgFM4WHs9tJNvbn#*){J- z&Vt*k3(fAd*W1!UJ@~x2zqVo2)yS@-9LcUeebk(xbDNCxu5xO*x`Gb8#6i%$JwT(D zwS{(XLD|o~WZtV+3r=X2_Bu$UB6mx+din(VhFVagc~?JB$Fd%_1K(Ss=DcUFKowjx$4Y9%zs(=&So*sXzoDw1m~DE72d*T?xn)wh z_pYKGqs2mpxsA?VG2hhgJX+_+6-R2;=jP|7S8thr*KRvn8=#g+mdTRVFjVrrd27X& zW^ZV{eZDk9=hNST*}1Ww0~#A+Uk5Zc#y$>AxsAOYP#qZ498et?6$fVRM&W?!z$iN~ zcQ@J{P#qX;4$R<sim zWb|>Ay5Yy$VSwx9(8Kj~=-_%dB)IMlCnu5t-nsd6#g zW|fPenz&!N5bnLo1#s_H&WHPVB`b37RL+I_SLGbIe^kzf`+MaqxW84FAnO*tRu;qk zrIPhLw=0X_{#?o0o}VfU;ohp84)TD zf|L{oR4K+@j%2t!9Z=O6dpMw~F?M%At77cth=rT%h=H5r=n9>WG11WlFA0v$aN`|N zv>4+Y(8n2L9ZbP$z^Wk2v%!B)NWiH&WDs$j|Svdjj z7nRv?Kd&4Q_gdvRxK}I3!o5<-+O^A-qv2kv9K|}H&nida<hCwqD~pY)Kmn z#KUs+Mxe#rfNsi^&`*JuQgu;?WvO2MHQ2%;Ad)}yYq*64RJ3K4PSUA2I$JypZhBXO z&X)OVNt|W1o_>wBJf^NsvJ~Pm5hC)~YKxf`bg?A=7tiTw3D>cHd@T$wBR!S8Vg5_a zHCw>-0fw0jEmxbCtJMM-s90DA3JbtaUkihsJ{E|>ffg2r11u1S>6Tr32ECjmOX4Rk zS)Tf@?+&+=NUYWd+(x8!>X$3Y_myve{pW^=I6oEkyLpH@VUFd7w%YwbCJ2Lk9<0t?U^&ND7UV^i`+C*B$TA8S z>Xlj!N~(RCWw?$u6$Iv zC^{*rop)FwbgaL7AdizE^ht}Gnn;!w4A$i*EaMEk(|g^J2a5N2=EH+79cgS*5M}rPpPOFH}MuQiKag;u?-t+DLk z-RN?yv>Cs!~lPThIO<~V^+6cM7NY; z)%XuAEe*Vm-c;8gTdwIzd~6x5qNh$U@O?xWr>OQes{iMft=f&HQ~4eCT)wijGbHJo znO(0pzp}XT;yPv$T6ELbmap}I`oDk69pbldEz>16_oij1PU@lF`N=X3kew#|YT1G{ z@?U~8R8Q+4Ah){}Gl{}f>q{tH zigmE0PPbWK)?q`V^_qHLSVK{5!dj<()yJBG$3FZxz}iC(<77cY@GWXT#QLjh8fNv@ ztL-zb7r?7WS(AC6dgvI9lrh$QI@OV5&BeE}t$TH99p7X4pxPJ9FOWXssuZB#v z_SUKSh1S>E{!&m8mwuEQI*UIPGRyj->NDTk5;Zl?`U&dzH}p(b(+#q|$y?!qmT`h{ zYG|4E=`!tek63%@_?#BF?cGgQo7!u=wMs8dQ2*Ry{aZVSIs_!4R#^@@ZvwBM}s6NkG0Wx2AWqf>$oeXMrI2qJzFLg4i+2&+av(?F{W{ZIvKbwcQRyM<}78jy3}ceyTl0qVqENGY`Vzl33s8>9qt0B8{GL$ z9o%^ihL>|40FcHx4!}j@Y)1*)S&m}3B@W<3W3i(M?o0=8qOr&UoM@ck08TU(I)D(3 z1&*n3r#XNSjZ+Q%yW;rt9j&KZxJKVv2YnX%kR;Gjd)=&rctqceEt#k+aR;h7_gFDw? z2Y0SP4(?pfI|bZ<4(?(D9NfkJ4`)JXV}Q(C&G65Es_~fhOYGrLe>rZQtA`E$f>*4Y z3T$s0&)2PY^?VgoMzw8{k%`3(f_pdx}JWkwYF1DXRTl3 z@eDt{W6jpHI`^V=BX(lmxo91z;nlKbwC7W6M=E~bI#N4=nB#iC`Mz}xtA`uI_kLg< zDa}!T`@l*%?BLw|k#!IDSj2v8-NNSyKek?GK#R!LA6rXc2*K*5?aXX^|HNu=4}&&w zierJg^HZybc1h*R0J45zeY45ZdRb2hqT)-|H&_i@UP{+K!>YlWOV*`SdD+^vxdRBc zEV{|JHRXo+x1!>E?LEClinOe(Azp(V%gJ`dI)p)SSrKi!XoU&ckE|JV_lorl8~7-P zh43G&#gux@+8=u~(!aJQtFKO zs2TmjaevUTK-K+b6%u1pERw+E82$0PwFahX-~7Y+q=em($Z`=5AA#coOuq0p=hc!u z2dYoqvu@VGL|39@)AYNUI@@*wB}lexRd%;s;*F9ap0*DSe2NetB0Sjky!vzt+akVr zGQ`gYQPEBf53oV>h6UK#N@Ta$yw$XJwvTmEkQy9p(;Kh_GCW+15N;a;F&SlBr)Tts zIS+DEU=i46OHzxv+NvZdYvW=$YHX}+k)$3-vMtuZG<|q?TL%L#&aPkdPm=X?_b(eZ z;b%i*oY2OeryerfmbqYFcULSG8RK%pj*$4_|D?N1n+@hx+wXbOiUqbI>XkGbY*R-e zPE)GBwxg`i4H;ldG)SG)p+lIWj3Kt(xFOkY>p@!<*m}M?!iH^mjP@F{ekaEMC*>@# z%~Y@0ZNnrrG0XO@r0U1ne$u&Axs4=@wWG8-wgZ|OjSbli-V@f_x~ZGYa|Ln;(~Nm_9&;ny?WwKhH6_bG`4DZEvTTdTHADW{TW*Y9?#gut4rUu z?bfpzb_1>|3bx7Wgp0Q4^lSuc!-6IcUu=0zwRuy?V%tW#wZ?Wyee2)06p0TiY``Wj zBqJmnfJ(N%MI|F`*ln@cwn3FXwZ-bB!D{>^E`Qu5+i@V;FKihw40+9VOs8JHZi7XW zxUX!8eqzdeg_VQhf?Yds(5N0&_u zws+QXxxLgu?d{hjSl5aT=A2@J?I&=-Yoxu8q{f8fnXX6Mt9d1K8w}t5X34SZ zaA%Gh+u45CoevXki0AwQb!^B32eZPlry+NVtuIa8-a3G;hPC&ks_$)4w69B$7u{H5 z^HJ~LvBkpprW%WDGz`*2H9mz4HA#)d)WLx5jPb%AWngXV`Y~LPw;nGHLkAIY3SMc_7`-#l)52>OFl{6JlTF9X1dhP)9g+IHaDgh*D6IN!coswlC0gMs>#*kQUPMZd4vh#FuSQ?Hst z{efovYXCD%-D?0UP2FmO^)EE*adHlx<4*#gnc_|YZkb|F0>zkOP6E@Kx}NNzcQk8x z5-89VS-k{qMD<*_-c^8JCa)@B9Fu1i(2dEX3W&z!UIh?iYF-7%VsfjJf$dC&D!?j} zz6wyvq^kmaGD%f{zouqYjArjwg~Gj8#W?nE6=T=Gs~Ef9sbcKtm69qF~ zbuNc{#kmabWhbNJOHR~~@lz*i)cA?Bgt6VnPKL!FIT;rJ+sUx_Lnp)H51b5(-*=+g zjqf=N;J)je2KS zfoV-`tK;CdspiIQU5(l@l?GI!Dom}aQC5?GHA-e`S&fpJ{Hj2^$y7BTuCZzs+!j?J z)Z|+Q;!HkO{K<39Jh*3^xp3>9=o!X3Cj;@*PJG^2>l_F7l#_vYjgx`+NhbsGY9|Bn zDkq3DI!m1lksVHk$dyip$ZtB)xtcYvVW@q=$w2uHCpwYw|8Nq<_|~J@BhB>C8G0|Y zFLzr4IM$x_-?01B{)P6Q0F2UNyO}~3+ndvoMfN}%x7gl|?kuv4Cck0Wk&8&%vF4w$ z#2!j7Ew;yNhjF`-b&0(n#VxUWVLJoD(y^C#?GpP}(o&cpwl_C0_}i>~t)04YsXY|e z2AA1uHRH9L1DJ@;>NBhC=k?8}K;n|$s{>b~`MSn&t=+zgY0H|&el3mfevxRyi} zTbWbJTkSvLp5ZO_d8{Pyr`^xn$KiGYdoY!hf(>5L%bql3yZwmz#}4}u@Y*t099a5l z7v!t{xYG`ZAwO>4-w^coU-pxnM<>oLUzK;+D|B>bnG&FW|CD`$DWteAGDvL ztEF~7y0zM#sCGPHH|Vu(1&La?if~-w>!FGl?B5u8d%}jsfa4tCrHYre0=#U$rlTj% z+e4p~cR*(&zh?gurzh}Ecz=y@Zrg{`&@J|9OqN1_Y_N}IZK|s_G%7lgyv#mZ-CAvb zLer}@eW%vGO2;bI&9L8p!`@4xt3TO$GqwFG?p=F4efBLH7q314$6Cw&%YK0VIA@>5 z`q#~g4Oy6$+db4HZ`++Zz~Y$~?AHyN-gV3a!8G9=JMJ?0#D1J!{kL5OQl9yd9gCq* zyKWBrpW+=>-@R-Hz79}lUgat&x{AI*`?jG98rv^M%T)d83;Xwy3c8^+X{+wOVgFRe zwmLTZbGG@gk@cbdXG*_qAE`#)wg*bU)Z!O=8w2mW+>B$?H|&ea`HLM#CA13LWG~Rs z{X6#Eh7jKPg5UspT`?J2a!CT#8koW7c4KdL}j+Z)9o`;-{Ese)K?!wSyj@mEC0=rGvULXJ0-CsZY?yZvM+&yv)OE#*Q9icD)OIhE~kz<(y&rHuosfK5$U z%3a_-q6*s?R>yK?(>ZJ|ZEho9kYvhfE05v}70P{g_yx44PXgsu^lhdQm+-Zfhe$TE z1 zQs_?`+sbX|QmDKfhBdZ!lml@m%AsEFv@{%#xV5XJyq8R;Q|9w|uI6++0yW-Oy$~S- z{%p}=&+I6Nt7oF*b`l+olEb6{|K#FBH>2gznvsoC%$DUj6!he<@PvC6Pt*NA4y9Rn>Yx#@5kY9)`m?em&3=Ch(h?{?$Wn!|Kwq z;HETQ^ji<_qdF^6;W?H3&2mphyiinV#hY$M?}gs93|I7Uaq(`X zTV{09Vs)WKhVkE->MbkChsr0`F2lCUEb1%Eal8Ql@@}aGpS_tRTB}8hoFl3Jirhg{ z@T|%sk&Z6*QLML-j=d?n(V^aQB(3i)U(xhEgE-h9_5fuIRrW!>%*Fk8@<_fO2{o;S zE$=H&1IoSLSDq&sma{b>c(My%X%Q#&dD$SCc(>ok=qQ9J=*`o?| z#s47_p8f;mP8uN4GHF8>2FRf_e}HUbOZH{|cuv3`!1#gk(^NP}4x{ve_)7dB5N{bQ zd(+GbXyxln1NdambDd7t@d%V(qP#`)sK|1B5R}-qveB|=Etuo8`w0@ zmJRg!XgQ%`tlWayjFCO06jfkUIlZmowMcn zIOKq@KFY!tn~71TRy1S+`jj6Xo**w{5xr8&egf7a<;Ni{DLi1(VUdTmr zhBv+kl06UXbyp|hMJIA)ANN7k5MCHkh^vJqNTF1cCnvz<#r`~57=rc9=5SynO_+%2 z&63SwU8;j9C|`~-wAVK)MqzzEq5_==1=dRjInZMGB`jdx<6TdPKu`IrpV)@C^Afy2jR@X(5Z3;0lnz?cN4^lqV? zjf0*EGvpL$0BxItMk<{lhhr=H{u!vRe*6Dv_SNq@~iSIn7#?z5r@;e|zV z8<>eoFM^=l$Iof}97OLIfgLGyqexEGq+36_RRqe%&x8b(Y2!?Jv}C8RW2wnkEEe1!B?*= zM4->yJ6n??y`@{6o#!E_A~Y|nNqm$h@J zD^vJ=2rXDCA7ZUYxsl(4DQy)#1-(f*PSP*LD1DdTSpwp~0>7K)$$r=yE_bFUSId1_ z0aI>m3c*#VYverM_6{6@=uzqmYvk`?>v|nH@sRa$fco}YumUJs?yZw2Yqk?zsj)+M zgM3xnu?~5%5N#N?L+(lG>oMrhY?KSM`}_(X4(Uem>p+WfGeqgXH_6-hpebxBK9o9) z_HIV4}#8%4(Zk0Dj6^L7p zqFgH=9PlBm=FfoT9zDDl?fNPp}FbqD3)5L4|B$-xj)DTm~45K}V`$-{lHqkXkBywBi1X(Pg@ z?0Gp&HNla1Z+2M#U49XjbL>SqTAzQi3+;VDp2SDA&=lhiqmKq3L6s)s34DCiVRvs5O%gB> zg!$Q$qw*j_7>kzDqYzZz9+eAe^D*?5CC6lcsXJ{shG^aB%rOv~OuokfR+5l+GWi{c zm_L17?jOe?F@zmj!-- zQco^nd%C2`GicLmawnSg8isEsZGKHoqx-LcLmzuh_B14RJ(*GQx*Q9k5cj&QKq$<5 z9Rq1FIbWA2@)|)|25or*jM4fAya&>TH{|~0c>+2A_=eox(4Q+1;QWt)>kK~jiy%Tx>k|e}=6`jnYyAHXS&Hg$e z(uUI;PPqgVjILFHX9cvO3I&{oo2&3Mm9AIGOR*ngQZ&OHk_d#4uFl5<-2)K5HF#3?(_52s}TTe@*|@)%5z z*4D|d=uVcBy&gkiIsf2@P(7snGJ2_A9t-x=;tWW{4T)z^(8cuB8GLLJeS8MxT}UZs z!RHoG#aT>G=F|PNAb%c{JWyM>#+{AlFz80sLDIcYhvEMHIq)DOCBG$i!C{&SZ=sfa zY2#a9MLzT z3yiHO{2lnZQNMS9APltg9RLtLeak;OSAm9{M}Z`oeIByfRu7(X9eCTJi}G&pw*Kz|J^0hl@5&__1P`Ro-a~IMeGg*MkB+@3cf#uM$M2z(!4NyJ zuyh6V?U^W#p=aL5bTtT`;SYGGejuAzDODc&fahBu$Sn-mHdsbOugD!q{~jf`yV9HA(`fHU5E~H``Z2f{pnfAAmp{fhy!45@ zir)DILbv2od9|^of2YLGi{^wa#LUB36Qa)g6l|AOQ{}^H+f{j|Z`Yq1gG+0$tM8Jm z)6Va#^5fv?wEaV{tJ=$$33pJ3T#@a#y6&31pJK0po8pR)Yw}CHs#^{_e8`M;e2z*0 zr!ODpdPn(NxOb`P1fMZ39~9oF;!AlwZ?bD3fjWOB`|uvU@~N&k?|%i^3;1>|oL>vzQ3%_E6DkA9e9dM?B)c$Wd3saO? ze*^sW=eGx z@eO$VIh|O?mg~y8(s`Y*)8l&4QW~T_uNSzK46}`L9AtD8xKc(Zdg_FEWXxu-=O2e14uq8(WIWnzh$(nq?A& zYM#FUIGaXs?Zpd}9w0hHF5vkPMYI-OAso_Mi|ed(T8T5xCb5K$wGiNMZ*&f%zXC-I z`l5{}Wn{lHo2f!QwG#%~-wtK`rmdI`{*AnfpimXlG{dj%O|f><{#N2JebQbGfaJIq zEc(*)AQ7*bZH?quX>=_}tf#BN0-`Bcm>@wAYo2zFRjYzLdNV}Kg03qrR1B7MHKk;X z7lD);E!xwuFws^6w6GN&24cRC#*i>|6m99oY2oXU0EIzWrxQ*OAJ;++H%W}d=Q(#pIh-^nm) zD}v}kl;}c#M~i4pv!o^JM&mn)*+AImJBg93ODdntsj{fFvlwG&j$Xu1gDAR-Flqak z06`i)Wu%?G&{D7ULg|scfsS<%ZCLq*YwH^0#Hh(#1un!h(%D$FeRZ7hr$^#MD#YK( zI8=%c*X>}S{_*jm$l%557u0J!{T(mf#?s5#1c6;TQ2XE#!FLiwJ9SEu*eO*2SCXNZ zXa&g?*h_SQ*m5;Qpby^i|eq6YFbo>Wm40^I$ly=P{(Vv^Xn8|7N1wgtMYT}IO{oe zUE$8I>kM~RT{zs5y7q93>#)FWnpwxIw?%bW)-=sHjfG@W;c3o#`e|N+FF2h6ciQQG zaHpQO!ku!u8{EmKJHefF8q4dZiKnrQZpuH6)o)W?Z7G(;O}VwR@sd-E6=u_fTCRia zTBdY-Ef#T2<7%;1YZ_bIAMTi1Gu+X&Oz5auURxen%cak%Z4P(DDPBSzeu@bic8W`x zc}l4^KNSIY@F`9;=#(Fp#Z3cG@w)VYQ(UtCr?ARy>Q}?H(zk}m>QloN z*t>=sD6NLGR%)0;QNtw4HCS6V*=w+DY_ipGm0D}kP&rf1vHu(A*#Gr&?El(1_?y&o z7PzmT7Lz$1QsF9GC3KStjW4*@bXlI6DjO z^Jk~SwbXFWFxRBOP33iLQ%d6o3q1F%iGkarhVyUQW`XDAn$~cWYPi^mr6)P-g#W*t z7l_m4Bv)YPliZ4(PPT*_eG(ODiaN;|bv()Bb~xz{_rcv6;nhrTST*;R(CPxXA=Q%+ zwV7|TF3t3Fuq!h!9ciIeq%h1YGgF=o>@eOY!s$_~XzdZhR}l?5)t*c?F$5Fr2{sW6T({9C<^k8~ z?09QK33kz!(Pw!Z3SzFY!!CXRj#?p$Hw|4HXlP9%h3KeFn4ywaL>nR@vdN-|2x_f} zC|af9o7swprnlJd0Q{tm^tK}U@u>~y@h>VOh8$@i7qLwII`9;^w^+}Z4Pd4*7jJs2FKWcp zPc-*T@S1swRiJrIp&oR%j~JxQqr0= z#~xn16%abQy-5M>sBWMrka|$!ATb%xlm-c0;x$-=(p$rYkHM?!sZ2UP7`7AU6>O=4Batg}kbP)p__fEPP0UU=nfAY)_ZS>tv z^`@a2q6eMI5OMT!1~OJ2tW*>ImLbMr7OUkwd8lXy476gX=wrxdrNI567)qZH1yswU zJ3~c(nw5#Njmd;Zj;jD`Gf~b7uIE?zIotL8C7!i)KCHcEiU0~2CZeGX*6L&UFvJ^6 znZr?%)x$vQ>0u(6UKu9*=ofzaf}bLWi(sFauo-h_7Zi0$j7jWFgNBPpl7|D?WO3S9 zpi2A%(=PTJPKSq!kLjfm_|}snFb1!V0FJC1Ap)`bc5MV|XAZTftK2jl_^!F;cX^bQ^&^=;x96 z+$SSZ9n)PgnvX&T1+M4ZQNRv8M~MKSMJ+iz0)ZB_=S_H~_eY`QRkD*g6l3h>C^4F6 z!I^YxjA+v$c}_xf49r`ii7mBmR5DuFBm*5CjYxWZvmB1pj@}+ETDW_~*Xmg3r}>N+ zgYJcsZ=(^qatwxbGwweyHaP~h`qmh9vMyu6Zv4igt(Uu^^&N|7%bK18Xy;gAWD_G= zmbgE2tT1?WHs6nl9@fWl3X^os`!^&aWt<2yO$D9Nnknv%rxrDk-Ssctm%Z1{G zDNJJS<)MRz(BXU(wIomYc(|^ZsST!_dO3hG+)5`*Jbz!lyzUN3q5h zuK_e}s)&R%E1fE230B9ZiaKy&d>)%>ra>IBni8i_r{R5G0a#lm+xPBTi}k&1&DVWc zEzHW6!QlQh^n_UjsGFX&w?M=gda%TYZ$t|$HxB1ymfG$ta@miF#t)N;el`9O=;M}mYU(Crn(l3Q#3M(#g z_Ydd3Vg}~E{ai8e?Bf%bKchIGX-KTanjhC<=?vknvnEh#k%*-(MG!!}IlhH{E)reo zN|A7*!$qPG=JU&o@a^E4Vjw*|6G0&}AqcL_6!H2I{fCiXu}C4`V&RA7J|yo<{fhx_ ztUN$LNeA!214i`W@6N8ZsGEyLF?K%Fy%O=HVHoSw_OB8lbYd1pTfl6L{J&?3K*Lb3 zBQ2okY!Ph8(1ZfVn?G9wv9N+AYkqxsHfSC~VUMF9em7gRHw><7HV22z5}w%OjC^02D<0D(@X$g(7~)RL z=809TRe*}@zeD_~>bFD>%9t+}vZ;>7&uaPJo-bPS0_VzxVz8&$eF4TI4)yI@2=Jz< z7t9Z5>rKZNq1wkU79X*qVI_8vHPWMk(rE2^nFqe(PREyG-V7x}c~(Yd$2wQ_IAB-VOuxUsJAJe(j^dWTMW|v}3ujO5@dw%SDOAC;rN@yo3q@ z^#(e;0v-77PFKxouTi|Dxf1IA1|kjg*9xdUCer>gaaP;MgC*UDFgMy!4qk@k%vGbB zJe#Zc%f&RwFk4#y;F#-HK~ns%3ZiYtCec07HK1zG zYP3HVEz8_o@0VAD!_T1HeJJ+66=>YBHQ+sk)Pn0l^C(^eW5e$OI9ueVCsUUuYs}3$bBG&0@OUbkeXr^MVXb%YnG#!I0>Hs&E zX|!D1jPVh<4q|sH$BU&Y>p+!T^J%-?{^&-Hq zfD4Jp9qA|npu^BexUdP$@WXmQvU#pj=B^h$)N=#0A9E>hgUANP05uVGZG%{(nS&Rc zNeo@tfmW{Ah=jNzXNO3o-!=jXcrtkdAV1)hKDxDp zL|U{NbbrQAHT)E{1w5huCSla~JB?GkoX&TP@W8Sw*n~v0wupfZ#ILjEy+6MU3%9U*IHYCFmAsQUXI@6ci zz}cBq;cY5)A$l z+WPR5$in+EME&hiBnx(Bk@^_M7wg}PT$GkRCN{9@ef1#iUF}%`rICc2W^hM{6v)}f z&@}>3>clK76>E?P38$rxiwG%-_C79pK?;GO2>R=BjOPyQ6;5eSpkUC%Lo9E5LL`x+ z0z=^_JSFS_IfA;r`UJ=hSL1ey;S%o?EtmN7(Mrcv5xa14v@d$ly1kg?cIMh`@OW>p*oYNG49Rrt2syeB zT@?2Y>=#dA0(5LY^kXTm{QEwI{CnYwK{1k-|F}5cuvZT<{YwjUdxOjL|y>F<%-?@*y!s8pO5MfgFc0Ege|#ycor&o?P`FLW`dVzwPg$ z^ONVrR876CwMhsqdkKTPVV6d^5=0$F!A811JL@neM}{N9N;ywK zrr$Y?+3pBew9gKUcE-3q>CrJ*kQfXhJ@GWQgdf2yau~>oq>qn?Zcu@POAf>25kliK zrepFk(V^W$eY50-#rQdMXHS`1P&By{(u>piwEP%4!Pmzy{ysd0*-tKAKL+lSgQah= z-Y|htSm}u#t?Br25vIu;K}VlWX-hvI7pr+zR^Ero`bGrOwiofvdUsga<&~HzN zXJV6QTiX1Jn56BO325>x!lY>Vt7wg>i2ILs7+LyYKtdhSmIkYU!&4|v6-%+&dRY|^ z@;oKTd=0Y?I6+vE#*}6`9EgFfuVJ_>qvNlEvn(au>(I3@&kvyV*U>HDbYP2!ncAzq z^XWyj?G4PkUVI(sb0Oljp{h5)fUdocHH8Hn*3mr$b)9i~KH1+8ne^rxBFeoNd^(?= z>k8(1S?AMOd3po1N+-ZFXH&olu%lU&aRU9XgdRPCmMx|;C&V*g_Qh{vA;ANsji*_a zz+#)<#F|E@O2l-hqDm;KSt;FJ9*QJ)Loge_;)@$SLN6 z#TUOO+NggzG5eJKsbw`fKuhXX4Ho4GgNPU?&s0NmDZJl2i)uW03UiwNwGdV9Yk}1R>HAY)gNyJ?y3^oopVeYuPdE)$+wC;; z+^w;q4J||foj46f)k^iM6YC^x4K&S_>6Y_I4d&SM&L>l3k< z)pO`|T6Tv&g(Spu6C!t{zkUs5-Q{Jw=S8%^OQ3Y3T6V!V!`>fcc6z2Bm&=LaesDpC}GT|pfk?~~S^ zW3<(LX=s4ruP>;}`j6POq80on){USo8(ldf{`Xk%>*h7FZurBV3)yN&8EoY$P-K29 zuyl7Db#V0u444%^U=;e@5M$j_v0o;;E}u)Q5#{5GCEozc%X8)D_!0PP#rGJ&TB#=1 z zL&YsIo2@N8NNCmX7&}9|!oG}pOLR0qocyn$I8ybKXpZ^ALj~}rML%H_7m(i_h=s<& z`_Lypp&w17yxp$4)2hUW(tgI0B(|Eje#rOG&j2w~9?n5=&A$@A0eE<*HVx{Q+rl6L z@@EDq!LZ^#Z$3SFI*=0?un z#8mgBgl=91b;TU35W*HeQ}pnnl3UOdyzo^IFZ%3v^vI=@_b2ehJDF*bbnXu@=OtJs zc6~35eLIr*Pl$lU9Bl@cWAQVQO6~}E{q(wp{AQ;4f1xj!?!fuzFCbyu$#Dld?Ric4 z_|d^TVyona`~H+TDwUKF+R|ESP8al-DQWjutg`Zj6 z(e(4`JxS=41r()Mrg2|xPLJYA3oEQ|{y&tj4c*f#W9fncxpsqDjd}I0=%N8RRJkd$ zU87-MeM_o#gV(X<|9F|`cyoN)$Un0x>hSHH`WCd`U3rA|c__0rpLz8`wBG};HsJ0>#w+biQp-l@3h>btu>U~G+Mc&im^tQ%bmP{whN?hEU&zo={;*L+tB zo1GR*Cj*oaNLR$jlMp+jSR@zfU8>OguFSA#1J?PwpFsJN1#$lF@cH~IRlj}@=3?nj|yjyN64$6?5uGx*n6g3hT36pX}8UH8z5qCXNntRMKc-lrmTAIt%KDxhk@4l+vf- zt}y5a)o0S>C}ju$ftU#HCi^NrG%i|+;UjvDT>^x-lOtN;(+K!(JYD!66OniND<=1_ zUQ7edIsb=u5_?bhFYkmN@Xr1ZZx#y%{jB=&bgQ#6N1K`za1(T4(GSx*U6f8(In&0v zdsn3=?dz(HckixcHJY=!43W1YMw#l8adC7c2CVtW0Q9r8Sf#agI4;YTA3}WS(O4w` zb1SebT-Fe)BxB~OJ!i$?c_L*VgTET5WYDU3Wua@VFR8~ZpAG&AG&vsK=kIuB4gH*; z6gJ0=oJIA;4365-tweC?u8A04GwI@BWOOqXY?u-i8}&(2aA8&wMhdpAa@^b`B@3ED z2mu^}NkEoAB%w2HPlhu(89l0?Da-a$pR813r>d52UpJ&%;CeQ7$3Xg^yV9L@c1NF^ z*N_ChtetDhd1()18qpI~ltSx!D#h-}30Pyp7Vi}a%5?gxr*cfwnl;o?Ng4*lqnThl z#~xK$>8I3}QgtfILN9GRX%NAKVc;xqfL= zgk-I=O{rhPIZcDS#!tTHS5&{KsaRN7HY)`@CoIo%H5f=)N>?okHoH-r4K;p2C=u>$ z&KM8{*R^mTL6m4*PliE$OenD)%@WMIvgk8Um+`{nE0)bm0AMM*}xy}fC=rD*Ie;EXsJ*}P^qGH(B`)(O~iQ6CyFwacVDfP zG*1=T<2Z#s4^B+PdZR;3?1M_^PH**5iVVpK@kwOui;mPSKDqFWk)stsYlxo$H}>(> z9zEle=AYqpyjfbN6@5|k&-*DmAz$Tm9+Cr<2p*CwUmHClT`;@&OrWcE7?0D|VTy^4 z4p58|-^=4liAh2$RN$_G%0d^PIMbT>#3Ia2@iULM4uaHd#dP$fw+1QO=)z#I-YdhE zU;rE25C$c^lD$gKgmb5#LC1!m!NQoM$I;hAlra)eY&tq$h-*yDOvksbr(?7Q^N5cl z&EuU6JZT7g)|sG({r5s>Zdt<>p9Ol)4TWs#!dWHb3LMmCWF|UbXUfim5XOzznP`_4 z#c6)c6MKN9XpU&6nZqz9qne%*-4j6Y+%p|1^MET~HyS)#Ndt~R^b9WAQs^}szu`f* zvXn%9Qul6T%Ti`R939I7dO4bf(VXybte%*EK9nq}c|uRn5%*ud-QXMhU%ts+rDtN) zvQdg&f;7}}8kz%{T{Rj7NO>T7@)&sZYNFql4vbMOymw=zFNKdoPfH)Gi~|O6Wid`k zaqp49ZG@Yqx#otBgOKRf^qfj}#{t<~8?VfC)tlg;BrK((@-nhfPMIfpS`!%Pk!-~R zjir{+@7ao-4o*-~ag7Pu!P<~zj*>!qhbhgWf^~f_2j4R{J$ul(93>M%Qe#pHM)Zh5 zxrz-!@*j__dECcyl~$COr%Yt*Jis0{?F>suaH?D-hTh0kI#pa3zMcpeeug!)bj>5J zE8WRcqP2-MMEOMY*$Med8r%P5f!6TuL0)K5_oVJzC%u_2t*|n*wvEqbx;qi$bQm*< zK@?x8SZV0H=x?hgD<$Mzj@gv<+G>h2nZBJ2a-Nx@6w|WC0Nt5%PT7xcOi}s)D`?De z;#AbpV3#mEH&rQ?#<&FXgaTxDcN$=aHUt)&fn;OE)99ZBk&n!_NF%ALK-ocGOn>lG zG`mn)ucb7U`J5T!eA^k%U#2~r8&U?wCkep2adiP8;XxA8en;~q24OnJbEQO1ljlmGF7 zRXlhVotfm~`yefZR?S9dp4dcdFx{SwjPp5BPs*8tz`O?n-C3CT$X zXNn%k72Y_cJ|D2K!vYM8Tl1A}IHai*Xf;nEB}x(9TcE^YlB&teaSI{EHBlEwctdk$ zxlJCyl)nVIN6y8NJHJFJ#7q_3(oTz)VwCD=|7O60lBG%ytpgkaPC!@j^z^A)2RxNHkL(csm}Kx(-LQwPo) zoNvP#Wf83yhZZ^O%cLxU;uk*)+?Y^rew;^tKZ2qC#agt2^S+-a9a{^=IeDEj80SIJ zZdm=>qV%EX*C|#OltGy1Y+VcZ-(fu@(6#l7px4&}y863%gK2|e=7Tup=uw+6&%C?= z0(=uw9^C`)MbMohJO(XEZj6m|@dnLxaGZV5>_XED)auzG6+n9r6?Xoz4g{{YTD zQs1*@ciOWVG~q2uUwjtx?adHTKmpvQNaIfZwWbh3^F(hD^TeZ9s``dF%vGe|aYu+lrmwKiyn)_{C0$u4GpoWj={IN^<$vJgFSvgX&lq z-Ty3Fbo{>{#a`QmK`*-^?AwKLG2tK>kaIVhE-Vh3l1XQ=(zjcgf|)UXCeYSBn3)!y zwKk>jrRjUX%`7gDb9*$gsmTK0Cy?EcyBCq4QvCI1293(qP|bhf3Ue?h2#ey@tBp{&8oa!?u+sRuA`;NhrMeZkpGS7*z8 z8p8p+wuL_D?9c~d<~{>*Gq|M_0J_pV>F6`iXQXQ!k*c0Wfv-G`ZW?_+>CDsZ1Il~@ z)YF=}v@2FmF`d&rtHf(Na9MsO$RK`^XQ4|~LDMb}R}JCeTzgH(sW`Em0c zKkJ{z^N5B4@jN=jaF_JQ*Fxy}^O#!XyAbZydI&#D6dDJHJq_f10eXVG|1Bs$tI}C# zbN|-}Ehs5lFx<686A!$2~kU3aaZL?SV;w(+Ydh{!;mY%HIdBI=D}*L~K|_ zE8q+JwC-p=$Tr@I!#6+TW(P#9=O9$n&Ha!JFYnjtu&h>96=0)<5bSmUBrR)&n7?~K zn*}wC@_PRKHLcr&Swj#+IMDgHtmpm!O{+?7{uw@X zK;^^S$oDey_%qr_vIV$$`a{lPc^d5xI0--x0{|qAcet)i`-zAwtGyi_w?wo5nS7>Q z%Tgy|n%Htxo67B{z-V*c0guJ|)k->zPR98G_6q>_LU~d4Hj|*k?!NafR`OV?p2Yp)~1EKdHJ2Dz)}~O^54MnPCZ^Xcg=^sRacGx+Ja? z|1Bukg@1tkhjbH<4CW7@+Xl)PrgO?UD6XsLASTMLgFjMDXqtQ}7^Z;_CwSO}79W8Z zhI-rktG7L#a2}Wmw$|oOt{gESH$q9`=f?^kzVWfP9_AV5T#zf%M79b`WfO5VrXFzQ zNbfigf&p^Uqs3#FjY=MnWOek;UyU_ug%8ZsTKyC4?@1k1f)V+`wDS; z+%Gwe*6`;x0h@(mpG`TM?f+2|A-(W?y z{Tnpgcw3vxxwm1nR+{)_+S?#iY z`8{oeH3xfR?763Hl-UPe`Yk-U6TXF%oMFzYbUyj5maYEpzQ%GIAzEEO;x7Nt4(^G# z8D?qf`k`CG%+9&9kbdA!LHZZB#Dcx=mSDQG?k-5rxFvq>J$*IOcXf&UI;~60*E{-5 zr0u!{^qtZrD(<9Sgw(BTNOfJJ;#_(zQl~x`sY6di+NSqIdP0{l$ELUSX#BjTOUT)A zUBU;C=@RYurb`0Y-f%sE^r%Zh_Fi`_KzhU_!Fq>XHAoMA?{K{T8jaWVJ;L#JJLO8; z-#aBT@D=AIq`z}YFJE>_7~*f8LciZQg?=wNrGGCvCFpOLQz9aFI;CSTI75)`a0(TF z?O20!yFy8VMGB`jem?HiLX(9)PexQ^aDWlJYdW!)}9WZ z3vb_#&9T~8vdQFwC|D#@t&&TENiOJ6ctJv@@|9q!vpgX}rItr^A{^Ec?Y68FJ;$DG z2$3k)0WMs~IjwVE&FgR1SFK&Z3fbQ&fX7WOsV9u3ka#89xr zL__{Ai^V8BA3-^=w|s9{d0-@b09PaF84+#4yfTV@%1fh2?5+-#l9lyl?}p-p?m*8V zfF8n$-N_Ei3O}0_Im30F_3uH?^73fcLoa#= zd-E9ZXVmS0USyw+p&V-{kmp_NU|@8O#t<}h2bS@%R07*i+EjG1_okhk)dypJrXM8( z4I97Fhc*~AEQ_N}#){%%9GFq$VPL2pB^b@(aq%<=W}OeC489i+F6$$kmmBy$%OmjQw`WZQvgdb^SDtKV+5m1y?{b&%%=g6WB^`D zL+t@(V?(^CN9q0V!(=^XPp25zG3b9aw+;uVu1=>cSY-DB8G|Uz`*x@f1!~q*1xkU2 z>ggTfS+LLUSA1;{KACpl<{-kwUb%+O>6%B`{A4B#hBc;4bp_~>1;7wLz;*%T7+q56 z4a~+EVP{{s+y}ssiELrl953Ty$3W#n+hNc z_MiYAM*})Sfda25OfbCE-9A|A;BbJqPNM3XeGDyv3qm>S>c-Oe2h+1)J~!&>cpdhR z0pk3zz#Ql5YQu(RWM*Y#rWsY#nL1Tv46ah8P;o?v>lp8h;U@)9VT$Es{^Vhbhwlc~ z+7zBV4wNo8miI>sFd?ptquKCBsMlLg!EC!R5pr?uBa|yA0g9gmB z<4Z*}htC&LC!RE(8nE9i$WJ#BQNEx~2sF>k2E4i>JUAphGfVpNm|kT#g6j4`fnbX= zoS!bnbiYkmdAaooQOyK-y=MLc)j5y=4c3vATq`C&i01VUqON5D|_ zlwdd}O{50pUTM_l_*ICW2muARz0ES)4+<)bV0$S&#Py}n0Js24=-YunhI+G?QEzBg zY>j8wD=CssmQi2+VHtIWv%?>lV_70|HiHM3(@bdBE#)-Nf`1+=r%}8E2i(+#d#%3E z@B^b~IJA7NFoI`R(0op>pkeCt`3RF1s<<=9SHKIhNE~vneo=@8e+8z(YJ;LU=V9eP zRuleqqY2}x9R0Ef4A1Lh`V-%+qfUk3V9+2GH|am%h$+;JZ3@-xR-D+xWwn$KH%bS< zFp(PEK9w}yJ(W^;cqJ8ZWF@6QVPh}MpvY=@UM{B_V_1Euy^>}@r(#E>hFcz`o*Xxg z>iEHF)PuK5?uW=houa&!|2B=f2k3CFY=iZFw4Oq^?oo>7(ke>8Ud7TXnx?!jg}ket zQu%fj#tPn-dWhpG)sU!b#L1s5RM4US03X7yP6vrPoB5c?2PPDi6irUZ#@0feTY_Y9 zsJ0$rVr~t@D7G`?r&t9%=2ogz+#mSWb=o$umz9R0kBrtza}h7h_m66GefuKg^FDbH(pr{Me!@M2##gxY0r8PK1_OA4JVMVADf^+apG|X{1(`9 z=*R7|F|K_$v4L<3Yz{;o4)uBAq0DmtO=zG=s>4&s%nYeJIlEL#k50N3tdY}Pco z(~YW&7UQ*nX1P+KT07)Gb8!Qu*hGA~ zhZ!%UsN9lg=1T%_Y_@DU%wrV9ar|;{`A}~&%TaTPnJ-pYZ+(pBam!<9JKHEhr&P)Q b6@-6%8p*AzASFle=c{N9TrKk+r|16z6m0xm diff --git a/test/fuzzinvariants.c b/test/fuzzinvariants.c new file mode 100644 index 000000000..a601e7aa4 --- /dev/null +++ b/test/fuzzinvariants.c @@ -0,0 +1,337 @@ +/* +** 2022-06-14 +** +** 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 library is used by fuzzcheck to test query invariants. +** +** An sqlite3_stmt is passed in that has just returned SQLITE_ROW. This +** routine does: +** +** * Record the output of the current row +** * Construct an alternative query that should return the same row +** * Run the alternative query and verify that it does in fact return +** the same row +** +*/ +#include "sqlite3.h" +#include +#include +#include +#include + +/* Forward references */ +static char *fuzz_invariant_sql(sqlite3_stmt*, int); +static int sameValue(sqlite3_stmt*,int,sqlite3_stmt*,int); +static void reportInvariantFailed(sqlite3_stmt*,sqlite3_stmt*,int); + +/* +** Do an invariant check on pStmt. iCnt determines which invariant check to +** perform. The first check is iCnt==0. +** +** *pbCorrupt is a flag that, if true, indicates that the database file +** is known to be corrupt. A value of non-zero means "yes, the database +** is corrupt". A zero value means "we do not know whether or not the +** database is corrupt". The value might be set prior to entry, or this +** routine might set the value. +** +** Return values: +** +** SQLITE_OK This check was successful. +** +** SQLITE_DONE iCnt is out of range. +** +** SQLITE_CORRUPT The invariant failed, but the underlying database +** file is indicating that it is corrupt, which might +** be the cause of the malfunction. +** +** SQLITE_INTERNAL The invariant failed, and the database file is not +** corrupt. (This never happens because this function +** will call abort() following an invariant failure.) +** +** (other) Some other kind of error occurred. +*/ +int fuzz_invariant( + sqlite3 *db, /* The database connection */ + sqlite3_stmt *pStmt, /* Test statement stopped on an SQLITE_ROW */ + int iCnt, /* Invariant sequence number, starting at 0 */ + int iRow, /* Current row number */ + int nRow, /* Number of output rows from pStmt */ + int *pbCorrupt, /* IN/OUT: Flag indicating a corrupt database file */ + int eVerbosity /* How much debugging output */ +){ + char *zTest; + sqlite3_stmt *pTestStmt = 0; + int rc; + int i; + int nCol; + int nParam; + + if( *pbCorrupt ) return SQLITE_DONE; + nParam = sqlite3_bind_parameter_count(pStmt); + if( nParam>100 ) return SQLITE_DONE; + zTest = fuzz_invariant_sql(pStmt, iCnt); + if( zTest==0 ) return SQLITE_DONE; + rc = sqlite3_prepare_v2(db, zTest, -1, &pTestStmt, 0); + if( rc ){ + if( eVerbosity ){ + printf("invariant compile failed: %s\n%s\n", + sqlite3_errmsg(db), zTest); + } + sqlite3_free(zTest); + sqlite3_finalize(pTestStmt); + return rc; + } + sqlite3_free(zTest); + nCol = sqlite3_column_count(pStmt); + for(i=0; i=2 ){ + char *zSql = sqlite3_expanded_sql(pTestStmt); + printf("invariant-sql #%d:\n%s\n", iCnt, zSql); + sqlite3_free(zSql); + } + while( (rc = sqlite3_step(pTestStmt))==SQLITE_ROW ){ + for(i=0; i=nCol ) break; + } + if( rc==SQLITE_DONE ){ + /* No matching output row found */ + sqlite3_stmt *pCk = 0; + rc = sqlite3_prepare_v2(db, "PRAGMA integrity_check", -1, &pCk, 0); + if( rc ){ + sqlite3_finalize(pCk); + sqlite3_finalize(pTestStmt); + return rc; + } + rc = sqlite3_step(pCk); + if( rc!=SQLITE_ROW + || sqlite3_column_text(pCk, 0)==0 + || strcmp((const char*)sqlite3_column_text(pCk,0),"ok")!=0 + ){ + *pbCorrupt = 1; + sqlite3_finalize(pCk); + sqlite3_finalize(pTestStmt); + return SQLITE_CORRUPT; + } + sqlite3_finalize(pCk); + rc = sqlite3_prepare_v2(db, + "SELECT 1 FROM bytecode(?1) WHERE opcode='VOpen'", -1, &pCk, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_pointer(pCk, 1, pStmt, "stmt-pointer", 0); + rc = sqlite3_step(pCk); + } + sqlite3_finalize(pCk); + if( rc==SQLITE_DONE ){ + reportInvariantFailed(pStmt, pTestStmt, iRow); + return SQLITE_INTERNAL; + }else if( eVerbosity>0 ){ + printf("invariant-error ignored due to the use of virtual tables\n"); + } + } + sqlite3_finalize(pTestStmt); + return SQLITE_OK; +} + + +/* +** Generate SQL used to test a statement invariant. +** +** Return 0 if the iCnt is out of range. +*/ +static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ + const char *zIn; + size_t nIn; + const char *zAnd = "WHERE"; + int i; + sqlite3_str *pTest; + sqlite3_stmt *pBase = 0; + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc; + int nCol = sqlite3_column_count(pStmt); + int mxCnt; + int bDistinct = 0; + int bOrderBy = 0; + int nParam = sqlite3_bind_parameter_count(pStmt); + + iCnt++; + switch( iCnt % 4 ){ + case 1: bDistinct = 1; break; + case 2: bOrderBy = 1; break; + case 3: bDistinct = bOrderBy = 1; break; + } + iCnt /= 4; + mxCnt = nCol; + if( iCnt<0 || iCnt>mxCnt ) return 0; + zIn = sqlite3_sql(pStmt); + if( zIn==0 ) return 0; + nIn = strlen(zIn); + while( nIn>0 && (isspace(zIn[nIn-1]) || zIn[nIn-1]==';') ) nIn--; + if( strchr(zIn, '?') ) return 0; + pTest = sqlite3_str_new(0); + sqlite3_str_appendf(pTest, "SELECT %s* FROM (%s", + bDistinct ? "DISTINCT " : "", zIn); + sqlite3_str_appendf(pTest, ")"); + rc = sqlite3_prepare_v2(db, sqlite3_str_value(pTest), -1, &pBase, 0); + if( rc ){ + sqlite3_finalize(pBase); + pBase = pStmt; + } + for(i=0; i'3' || isdigit(zSuffix[2])) + ){ + /* This is a randomized column name and so cannot be used in the + ** WHERE clause. */ + continue; + } + if( i+1!=iCnt ) continue; + if( zColName==0 ) continue; + if( sqlite3_column_type(pStmt, i)==SQLITE_NULL ){ + sqlite3_str_appendf(pTest, " %s \"%w\" ISNULL", zAnd, zColName); + }else{ + sqlite3_str_appendf(pTest, " %s \"%w\"=?%d", zAnd, zColName, + i+1+nParam); + } + zAnd = "AND"; + } + if( pBase!=pStmt ) sqlite3_finalize(pBase); + if( bOrderBy ){ + sqlite3_str_appendf(pTest, " ORDER BY 1"); + } + return sqlite3_str_finish(pTest); +} + +/* +** Return true if and only if v1 and is the same as v2. +*/ +static int sameValue(sqlite3_stmt *pS1, int i1, sqlite3_stmt *pS2, int i2){ + int x = 1; + int t1 = sqlite3_column_type(pS1,i1); + int t2 = sqlite3_column_type(pS2,i2); + if( t1!=t2 ){ + if( (t1==SQLITE_INTEGER && t2==SQLITE_FLOAT) + || (t1==SQLITE_FLOAT && t2==SQLITE_INTEGER) + ){ + /* Comparison of numerics is ok */ + }else{ + return 0; + } + } + switch( sqlite3_column_type(pS1,i1) ){ + case SQLITE_INTEGER: { + x = sqlite3_column_int64(pS1,i1)==sqlite3_column_int64(pS2,i2); + break; + } + case SQLITE_FLOAT: { + x = sqlite3_column_double(pS1,i1)==sqlite3_column_double(pS2,i2); + break; + } + case SQLITE_TEXT: { + const char *z1 = (const char*)sqlite3_column_text(pS1,i1); + const char *z2 = (const char*)sqlite3_column_text(pS2,i2); + x = ((z1==0 && z2==0) || (z1!=0 && z2!=0 && strcmp(z1,z1)==0)); + break; + } + case SQLITE_BLOB: { + int len1 = sqlite3_column_bytes(pS1,i1); + const unsigned char *b1 = sqlite3_column_blob(pS1,i1); + int len2 = sqlite3_column_bytes(pS2,i2); + const unsigned char *b2 = sqlite3_column_blob(pS2,i2); + if( len1!=len2 ){ + x = 0; + }else if( len1==0 ){ + x = 1; + }else{ + x = (b1!=0 && b2!=0 && memcmp(b1,b2,len1)==0); + } + break; + } + } + return x; +} + +/* +** Print a single row from the prepared statement +*/ +static void printRow(sqlite3_stmt *pStmt, int iRow){ + int i, nCol; + nCol = sqlite3_column_count(pStmt); + for(i=0; i0 LEFT JOIN t1 ON c=3 WHERE t1.a<>0; +} {} + finish_test diff --git a/test/index9.test b/test/index9.test index 2d4d94111..00e99a8e5 100644 --- a/test/index9.test +++ b/test/index9.test @@ -34,6 +34,7 @@ do_execsql_test 1.0 { CREATE TABLE t1(x, y); CREATE INDEX t1x ON t1(x) WHERE y=45; } +unset -nocomplain a set y [expr 45] do_sqluses_test 1.1 { SELECT * FROM t1 WHERE x=? AND y=$y } {t1 t1x} set y [expr 45.1] diff --git a/test/join.test b/test/join.test index 221dbdd09..38143c056 100644 --- a/test/join.test +++ b/test/join.test @@ -250,6 +250,19 @@ do_test join-2.1 { } } {1 2 3 4 2 3 4 5 3 4 5 {}} +# EVIDENCE-OF: R-52129-05406 you can say things like "OUTER LEFT NATURAL +# JOIN" which means the same as "NATURAL LEFT OUTER JOIN". +do_test join-2.1b { + execsql { + SELECT * FROM t1 OUTER LEFT NATURAL JOIN t2; + } +} {1 2 3 4 2 3 4 5 3 4 5 {}} +do_test join-2.1c { + execsql { + SELECT * FROM t1 NATURAL LEFT OUTER JOIN t2; + } +} {1 2 3 4 2 3 4 5 3 4 5 {}} + # ticket #3522 do_test join-2.1.1 { execsql2 { @@ -272,11 +285,13 @@ do_test join-2.2 { SELECT * FROM t2 NATURAL LEFT OUTER JOIN t1; } } {1 2 3 {} 2 3 4 1 3 4 5 2} -do_test join-2.3 { - catchsql { - SELECT * FROM t1 NATURAL RIGHT OUTER JOIN t2; - } -} {1 {RIGHT and FULL OUTER JOINs are not currently supported}} + +#do_test join-2.3 { +# catchsql { +# SELECT * FROM t1 NATURAL RIGHT OUTER JOIN t2; +# } +#} {1 {RIGHT and FULL OUTER JOINs are not currently supported}} + do_test join-2.4 { execsql { SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.d @@ -307,7 +322,7 @@ do_test join-3.3 { catchsql { SELECT * FROM t1 JOIN t2 ON t1.a=t2.b USING(b); } -} {1 {cannot have both ON and USING clauses in the same join}} +} {1 {near "USING": syntax error}} do_test join-3.4.1 { catchsql { SELECT * FROM t1 JOIN t2 USING(a); @@ -326,36 +341,39 @@ do_test join-3.6 { SELECT * FROM t1 JOIN t2 ON t3.a=t2.b; } } {1 {no such column: t3.a}} + +# EVIDENCE-OF: R-47973-48020 you cannot say "INNER OUTER JOIN", because +# that would be contradictory. do_test join-3.7 { catchsql { SELECT * FROM t1 INNER OUTER JOIN t2; } -} {1 {unknown or unsupported join type: INNER OUTER}} +} {1 {unknown join type: INNER OUTER}} do_test join-3.8 { catchsql { SELECT * FROM t1 INNER OUTER CROSS JOIN t2; } -} {1 {unknown or unsupported join type: INNER OUTER CROSS}} +} {1 {unknown join type: INNER OUTER CROSS}} do_test join-3.9 { catchsql { SELECT * FROM t1 OUTER NATURAL INNER JOIN t2; } -} {1 {unknown or unsupported join type: OUTER NATURAL INNER}} +} {1 {unknown join type: OUTER NATURAL INNER}} do_test join-3.10 { catchsql { SELECT * FROM t1 LEFT BOGUS JOIN t2; } -} {1 {unknown or unsupported join type: LEFT BOGUS}} +} {1 {unknown join type: LEFT BOGUS}} do_test join-3.11 { catchsql { SELECT * FROM t1 INNER BOGUS CROSS JOIN t2; } -} {1 {unknown or unsupported join type: INNER BOGUS CROSS}} +} {1 {unknown join type: INNER BOGUS CROSS}} do_test join-3.12 { catchsql { SELECT * FROM t1 NATURAL AWK SED JOIN t2; } -} {1 {unknown or unsupported join type: NATURAL AWK SED}} +} {1 {unknown join type: NATURAL AWK SED}} do_test join-4.1 { execsql { @@ -1039,5 +1057,48 @@ do_execsql_test join-25.1 { SELECT count(*) FROM v0 LEFT JOIN t0 ON v0.c0; } {1} +# 2022-04-21 Parser issue detected by dbsqlfuzz +# +reset_db +do_catchsql_test join-26.1 { + CREATE TABLE t4(a,b); + CREATE TABLE t5(a,c); + CREATE TABLE t6(a,d); + SELECT * FROM t5 JOIN ((t4 JOIN (t5 JOIN t6)) t7); +} {/1 {.*}/} + +# 2022-06-09 Invalid subquery flattening caused by +# check-in 3f45007d544e5f78 and detected by dbsqlfuzz +# +reset_db +do_execsql_test join-27.1 { + CREATE TABLE t1(a INT,b INT,c INT); INSERT INTO t1 VALUES(NULL,NULL,NULL); + CREATE TABLE t2(d INT,e INT); INSERT INTO t2 VALUES(NULL,NULL); + CREATE INDEX x2 ON t1(c,b); + CREATE TABLE t3(x INT); INSERT INTO t3 VALUES(NULL); +} +do_execsql_test join-27.2 { + WITH t99(b) AS MATERIALIZED ( + SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3) + ) + SELECT 5 FROM t2 JOIN t99 ON b IN (1,2,3); +} {} +do_execsql_test join-27.3 { + WITH t99(b) AS NOT MATERIALIZED ( + SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3) + ) + SELECT 5 FROM t2 JOIN t99 ON b IN (1,2,3); +} {} +do_execsql_test join-27.4 { + WITH t99(b) AS (SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3)) + SELECT 5 FROM t2 JOIN t99 ON b IN (1,2,3); +} {} +do_execsql_test join-27.5 { + SELECT 5 + FROM t2 JOIN ( + SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3) + ) AS t99 ON b IN (1,2,3); +} {} + finish_test diff --git a/test/join2.test b/test/join2.test index 170000ca2..e549c4934 100644 --- a/test/join2.test +++ b/test/join2.test @@ -63,6 +63,12 @@ do_test join2-1.6 { t1 NATURAL LEFT OUTER JOIN t2 NATURAL JOIN t3 } } {1 11 111 1111} +do_test join2-1.6-rj { + execsql { + SELECT * FROM + t2 NATURAL RIGHT OUTER JOIN t1 NATURAL JOIN t3 + } +} {11 111 1 1111} ifcapable subquery { do_test join2-1.7 { execsql { @@ -70,6 +76,12 @@ ifcapable subquery { t1 NATURAL LEFT OUTER JOIN (t2 NATURAL JOIN t3) } } {1 11 111 1111 2 22 {} {} 3 33 {} {}} + do_test join2-1.7-rj { + execsql { + SELECT a, b, c, d FROM + t2 NATURAL JOIN t3 NATURAL RIGHT JOIN t1 + } + } {1 11 111 1111 2 22 {} {} 3 33 {} {}} } #------------------------------------------------------------------------- @@ -88,6 +100,9 @@ do_execsql_test 2.0 { do_catchsql_test 2.1 { SELECT * FROM aa LEFT JOIN cc ON (a=b) JOIN bb ON (b=coalesce(c,1)); } {1 {ON clause references tables to its right}} +do_catchsql_test 2.1b { + SELECT * FROM aa RIGHT JOIN cc ON (a=b) JOIN bb ON (b=coalesce(c,1)); +} {1 {ON clause references tables to its right}} do_catchsql_test 2.2 { SELECT * FROM aa JOIN cc ON (a=b) JOIN bb ON (b=c); } {0 {one one one}} @@ -114,7 +129,7 @@ do_eqp_test 3.1 { } { QUERY PLAN |--SCAN t1 - `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN } do_eqp_test 3.2 { @@ -122,7 +137,7 @@ do_eqp_test 3.2 { } { QUERY PLAN |--SCAN t1 - `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) + `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN } #------------------------------------------------------------------------- @@ -162,15 +177,15 @@ do_eqp_test 4.1.5 { } { QUERY PLAN |--SCAN c1 - |--SEARCH c2 USING INTEGER PRIMARY KEY (rowid=?) - `--SEARCH c3 USING INTEGER PRIMARY KEY (rowid=?) + |--SEARCH c2 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN + `--SEARCH c3 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN } do_eqp_test 4.1.6 { SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); } { QUERY PLAN |--SCAN c1 - `--SEARCH c3 USING INTEGER PRIMARY KEY (rowid=?) + `--SEARCH c3 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN } do_execsql_test 4.2.0 { @@ -209,15 +224,15 @@ do_eqp_test 4.2.5 { } { QUERY PLAN |--SCAN c1 - |--SEARCH c2 USING INDEX sqlite_autoindex_c2_1 (k=?) - `--SEARCH c3 USING INDEX sqlite_autoindex_c3_1 (k=?) + |--SEARCH c2 USING INDEX sqlite_autoindex_c2_1 (k=?) LEFT-JOIN + `--SEARCH c3 USING INDEX sqlite_autoindex_c3_1 (k=?) LEFT-JOIN } do_eqp_test 4.2.6 { SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); } { QUERY PLAN |--SCAN c1 - `--SEARCH c3 USING INDEX sqlite_autoindex_c3_1 (k=?) + `--SEARCH c3 USING INDEX sqlite_autoindex_c3_1 (k=?) LEFT-JOIN } # 2017-11-23 (Thanksgiving day) diff --git a/test/join5.test b/test/join5.test index 5073d9831..2ffe3f8ac 100644 --- a/test/join5.test +++ b/test/join5.test @@ -280,9 +280,9 @@ do_eqp_test 7.2 { |--SCAN t1 `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH t2 USING INDEX t2xy (x=? AND y=?) + | `--SEARCH t2 USING INDEX t2xy (x=? AND y=?) LEFT-JOIN `--INDEX 2 - `--SEARCH t2 USING INDEX t2xy (x=? AND y=?) + `--SEARCH t2 USING INDEX t2xy (x=? AND y=?) LEFT-JOIN } do_execsql_test 7.3 { @@ -303,7 +303,7 @@ do_eqp_test 7.4 { } { QUERY PLAN |--SCAN t3 - `--SEARCH t4 USING INDEX t4xz (x=?) + `--SEARCH t4 USING INDEX t4xz (x=?) LEFT-JOIN } do_eqp_test 7.4b { SELECT * FROM t3 CROSS JOIN t4 ON (t4.x = t3.x) WHERE (+t4.y = ? OR t4.z = ?); diff --git a/test/join7.test b/test/join7.test new file mode 100644 index 000000000..42cc35741 --- /dev/null +++ b/test/join7.test @@ -0,0 +1,355 @@ +# 2022-04-09 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# 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 RIGHT and FULL OUTER JOINs. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +foreach {id schema} { + 1 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE INDEX t1a ON t1(a); + CREATE TABLE t2(c INT, d INT); + INSERT INTO t2 VALUES(3,33),(4,44),(5,55); + CREATE INDEX t2c ON t2(c); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 2 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE INDEX t1ab ON t1(a,b); + CREATE TABLE t2(c INT, d INT); + INSERT INTO t2 VALUES(3,33),(4,44),(5,55); + CREATE INDEX t2cd ON t2(c,d); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 3 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE INDEX t1a ON t1(a); + CREATE TABLE t2(c INT, d INT PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t2 VALUES(3,33),(4,44),(5,55); + CREATE INDEX t2c ON t2(c); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 4 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE TABLE t2(c INTEGER PRIMARY KEY, d INT); + INSERT INTO t2 VALUES(3,33),(4,44),(5,55); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 5 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE TABLE t2(c INT PRIMARY KEY, d INT) WITHOUT ROWID; + INSERT INTO t2 VALUES(3,33),(4,44),(5,55); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 6 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE VIEW t2(c,d) AS VALUES(3,33),(4,44),(5,55); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 7 { + CREATE VIEW t1(a,b) AS VALUES(1,2),(1,3),(1,4); + CREATE TABLE t2(c INTEGER PRIMARY KEY, d INT); + INSERT INTO t2 VALUES(3,33),(4,44),(5,55); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 8 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE TABLE t2(c INT, d INT); + INSERT INTO t2 VALUES(3,33),(4,44),(5,55); + CREATE VIEW dual(dummy) AS VALUES('x'); + } + 9 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE TABLE t2a(c INTEGER PRIMARY KEY, i1 INT); + CREATE TABLE t2b(i1 INTEGER PRIMARY KEY, d INT); + CREATE VIEW t2(c,d) AS SELECT c, d FROM t2a NATURAL JOIN t2b; + INSERT INTO t2a VALUES(3,93),(4,94),(5,95),(6,96),(7,97); + INSERT INTO t2b VALUES(91,11),(92,22),(93,33),(94,44),(95,55); + CREATE TABLE dual(dummy TEXT); + INSERT INTO dual(dummy) VALUES('x'); + } + 10 { + CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a,b)) WITHOUT ROWID; + INSERT INTO t1 VALUES(1,2),(1,3),(1,4); + CREATE TABLE t2a(c INTEGER PRIMARY KEY, i1 INT); + CREATE TABLE t2b(i1 INTEGER PRIMARY KEY, d INT); + CREATE VIEW t2(c,d) AS SELECT c, d FROM t2a NATURAL JOIN t2b; + INSERT INTO t2a VALUES(3,93),(4,94),(5,95),(6,96),(7,97); + INSERT INTO t2b VALUES(91,11),(92,22),(93,33),(94,44),(95,55); + CREATE TABLE dual(dummy TEXT); + INSERT INTO dual(dummy) VALUES('x'); + } +} { + reset_db + db nullvalue NULL + do_execsql_test join7-$id.setup $schema {} + + # Verified against PG-14 for case 1 + do_execsql_test join7-$id.10 { + SELECT b, d FROM t1 FULL OUTER JOIN t2 ON b=c ORDER BY +b; + } { + NULL 55 + 2 NULL + 3 33 + 4 44 + } + + # Verified against PG-14 for case 1 + do_execsql_test join7-$id.20 { + SELECT a, c FROM t1 FULL OUTER JOIN t2 ON b=c ORDER BY +b; + } { + NULL 5 + 1 NULL + 1 3 + 1 4 + } + + do_execsql_test join7-$id.30 { + SELECT * FROM t1 FULL OUTER JOIN t2 ON b=c ORDER BY +b; + } { + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.31 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c ORDER BY +b; + } { + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.32 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c + WHERE b=c + ORDER BY +b; + } { + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.33 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c + WHERE b>0 + ORDER BY +b; + } { + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.34 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c + WHERE b>0 OR b IS NULL + ORDER BY +b; + } { + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.35 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c AND b>3 AND c>4 + ORDER BY coalesce(b,c,0); + } { + 1 2 NULL NULL + NULL NULL 3 33 + 1 3 NULL NULL + NULL NULL 4 44 + 1 4 NULL NULL + NULL NULL 5 55 + } + do_execsql_test join7-$id.36 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c AND b>3 WHERE c>4 + ORDER BY coalesce(b,c,0); + } { + NULL NULL 5 55 + } + do_execsql_test join7-$id.37 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c WHERE b>3 AND c>4 + ORDER BY coalesce(b,c,0); + } { + } + do_execsql_test join7-$id.38 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c WHERE b>3 OR c>4 + ORDER BY coalesce(b,c,0); + } { + 1 4 4 44 + NULL NULL 5 55 + } + do_execsql_test join7-$id.39 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c AND (b>3 OR c>4) + ORDER BY coalesce(b,c,0); + } { + 1 2 NULL NULL + NULL NULL 3 33 + 1 3 NULL NULL + 1 4 4 44 + NULL NULL 5 55 + } + do_execsql_test join7-$id.40 { + SELECT * FROM t1 RIGHT OUTER JOIN t2 ON b=c ORDER BY +b; + } { + NULL NULL 5 55 + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.50 { + SELECT t1.*, t2.* FROM t2 LEFT OUTER JOIN t1 ON b=c ORDER BY +b; + } { + NULL NULL 5 55 + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.60 { + SELECT * FROM dual JOIN t1 ON true RIGHT OUTER JOIN t2 ON b=c ORDER BY +b; + } { + NULL NULL NULL 5 55 + x 1 3 3 33 + x 1 4 4 44 + } + do_execsql_test join7-$id.70 { + SELECT t1.*, t2.* + FROM t2 LEFT JOIN (dual JOIN t1 ON true) ON b=c ORDER BY +b; + } { + NULL NULL 5 55 + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.80 { + SELECT * FROM dual CROSS JOIN t1 RIGHT OUTER JOIN t2 ON b=c ORDER BY +b; + } { + NULL NULL NULL 5 55 + x 1 3 3 33 + x 1 4 4 44 + } + do_execsql_test join7-$id.81 { + SELECT dual.*, t1.*, t2.* + FROM t1 CROSS JOIN dual RIGHT OUTER JOIN t2 ON b=c ORDER BY +b; + } { + NULL NULL NULL 5 55 + x 1 3 3 33 + x 1 4 4 44 + } + do_execsql_test join7-$id.90 { + SELECT * FROM t1 LEFT OUTER JOIN t2 ON b=c ORDER BY +b; + } { + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.100 { + SELECT * FROM t1 FULL OUTER JOIN t2 ON b=c AND a=1 ORDER BY +b; + } { + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + do_execsql_test join7-$id.101 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c AND a=1 ORDER BY +b; + } { + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + + # Verified against PG-14 for case 1 + do_execsql_test join7-$id.110 { + SELECT * FROM t1 FULL OUTER JOIN t2 ON b=c WHERE a=1 ORDER BY +b; + } { + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + + do_execsql_test join7-$id.111 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c WHERE a=1 ORDER BY +b; + } { + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + + # Verified against PG-14 for case 1 + do_execsql_test join7-$id.115 { + SELECT * FROM t1 FULL OUTER JOIN t2 ON b=c + WHERE a=1 OR a IS NULL ORDER BY +b; + } { + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + + do_execsql_test join7-$id.116 { + SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c + WHERE a=1 OR a IS NULL ORDER BY +b; + } { + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 3 33 + 1 4 4 44 + } + + # Verified against PG-14 for case 1: + do_execsql_test join7-$id.120 { + SELECT * FROM t1 FULL OUTER JOIN t2 ON b=c WHERE a IS NULL ORDER BY +d; + } { + NULL NULL 5 55 + } + + # Verified against PG-14 for case 1: + do_execsql_test join7-$id.130 { + SELECT * FROM t1 FULL OUTER JOIN t2 ON b=c AND d<=0 ORDER BY +b, +d; + } { + NULL NULL 3 33 + NULL NULL 4 44 + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 NULL NULL + 1 4 NULL NULL + } + + # Verified against PG-14 for case 1: + do_execsql_test join7-$id.140 { + SELECT a, b, c, d + FROM t2 FULL OUTER JOIN t1 ON b=c AND d<=0 ORDER BY +b, +d; + } { + NULL NULL 3 33 + NULL NULL 4 44 + NULL NULL 5 55 + 1 2 NULL NULL + 1 3 NULL NULL + 1 4 NULL NULL + } + + do_execsql_test join7-$id.141 { + SELECT a, b, c, d + FROM t2 FULL OUTER JOIN t1 ON b=c AND d<=0 + ORDER BY +b, +d LIMIT 2 OFFSET 2 + } { + NULL NULL 5 55 + 1 2 NULL NULL + } +} +finish_test diff --git a/test/join8.test b/test/join8.test new file mode 100644 index 000000000..481430556 --- /dev/null +++ b/test/join8.test @@ -0,0 +1,804 @@ +# 2022-04-12 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file implements tests for RIGHT and FULL OUTER JOINs. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !vtab { + finish_test + return +} + +db null NULL +# EVIDENCE-OF: R-33754-02880 you can say "LEFT RIGHT JOIN" which is the +# same as "FULL JOIN". +do_execsql_test join8-10 { + CREATE TABLE t1(a,b,c); + CREATE TABLE t2(x,y); + CREATE INDEX t2x ON t2(x); + SELECT avg(DISTINCT b) FROM (SELECT * FROM t2 LEFT RIGHT JOIN t1 ON c); +} {NULL} + +# Pending optimization opportunity: +# Row-value initialization subroutines must be called from with the +# RIGHT JOIN body subroutine before the first use of any register containing +# the results of that subroutine. This seems dodgy. Test case: +# +reset_db +do_execsql_test join8-1000 { + CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT,b,c,d,e,f,g,h,j,k,l,m,n,o,p,q,r,s); + CREATE INDEX t1x1 ON t1(g+h,j,k); + CREATE INDEX t1x2 ON t1(b); + INSERT INTO t1 DEFAULT VALUES; +} {} +do_catchsql_test join8-1010 { + SELECT a + FROM ( + SELECT a + FROM ( + SELECT a + FROM ( + SELECT a FROM t1 NATURAL LEFT JOIN t1 + WHERE (b, 2 ) IS ( SELECT 2 IN(2,2),2) + ) + NATURAL LEFT FULL JOIN t1 + WHERE ( rowid , 1 )<=(CASE 5 WHEN 619 THEN 841 ELSE 3374391096 END,0) + ORDER BY a ASC + ) + NATURAL LEFT JOIN t1 + WHERE (b, 2 ) IS ( SELECT 3 IN(3,3),3) + ) + NATURAL LEFT FULL JOIN t1 + WHERE ( rowid , 1 )<=(CASE 5 WHEN 619 THEN 841 ELSE 3374391096 END,0) + ORDER BY a ASC; +} {0 1} + +# Pending issue #2: (now resolved) +# Jump to addrHalt inside the RIGHT JOIN body subroutine bypasses the +# OP_Return, resulting in a subroutine loop. Test case: +# +reset_db +do_execsql_test join8-2000 { + CREATE TABLE t1(a int, b int, c int); + INSERT INTO t1 VALUES(1,2,3),(4,5,6); + CREATE TABLE t2(d int, e int); + INSERT INTO t2 VALUES(3,333),(4,444); + CREATE TABLE t3(f int, g int); + PRAGMA automatic_index=off; +} {} +do_catchsql_test join8-2010 { + SELECT * FROM t1 RIGHT JOIN t2 ON c=d JOIN t3 ON f=e; +} {0 {}} + +# Demonstrate that nested FULL JOINs and USING clauses work +# +reset_db +load_static_extension db series +do_execsql_test join8-3000 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, a INT); + CREATE TABLE t2(id INTEGER PRIMARY KEY, b INT); + CREATE TABLE t3(id INTEGER PRIMARY KEY, c INT); + CREATE TABLE t4(id INTEGER PRIMARY KEY, d INT); + CREATE TABLE t5(id INTEGER PRIMARY KEY, e INT); + CREATE TABLE t6(id INTEGER PRIMARY KEY, f INT); + CREATE TABLE t7(id INTEGER PRIMARY KEY, g INT); + CREATE TABLE t8(id INTEGER PRIMARY KEY, h INT); + INSERT INTO t1 SELECT value, 1 FROM generate_series(1,256) WHERE value & 1; + INSERT INTO t2 SELECT value, 1 FROM generate_series(1,256) WHERE value & 2; + INSERT INTO t3 SELECT value, 1 FROM generate_series(1,256) WHERE value & 4; + INSERT INTO t4 SELECT value, 1 FROM generate_series(1,256) WHERE value & 8; + INSERT INTO t5 SELECT value, 1 FROM generate_series(1,256) WHERE value & 16; + INSERT INTO t6 SELECT value, 1 FROM generate_series(1,256) WHERE value & 32; + INSERT INTO t7 SELECT value, 1 FROM generate_series(1,256) WHERE value & 64; + INSERT INTO t8 SELECT value, 1 FROM generate_series(1,256) WHERE value & 128; + CREATE TABLE t9 AS + SELECT id, h, g, f, e, d, c, b, a + FROM t1 + NATURAL FULL JOIN t2 + NATURAL FULL JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + NATURAL FULL JOIN t6 + NATURAL FULL JOIN t7 + NATURAL FULL JOIN t8; +} {} +do_execsql_test join8-3010 { + SELECT count(*) FROM t9; +} {255} +do_execsql_test join8-3020 { + SELECT id, count(*) FROM t9 GROUP BY id HAVING count(*)!=1; +} {} +do_execsql_test join8-3030 { + UPDATE t9 SET a=0 WHERE a IS NULL; + UPDATE t9 SET b=0 WHERE b IS NULL; + UPDATE t9 SET c=0 WHERE c IS NULL; + UPDATE t9 SET d=0 WHERE d IS NULL; + UPDATE t9 SET e=0 WHERE e IS NULL; + UPDATE t9 SET f=0 WHERE f IS NULL; + UPDATE t9 SET g=0 WHERE g IS NULL; + UPDATE t9 SET h=0 WHERE h IS NULL; + SELECT count(*) FROM t9 WHERE id=128*h+64*g+32*f+16*e+8*d+4*c+2*b+a; +} {255} +do_execsql_test join8-3040 { + SELECT * FROM t9 WHERE id<>128*h+64*g+32*f+16*e+8*d+4*c+2*b+a; +} {} + +# 2022-04-21 dbsqlfuzz find +# +reset_db +do_execsql_test join8-4000 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, a, b); + INSERT INTO t1 VALUES(1,5555,4); + CREATE INDEX i1a ON t1(a); + CREATE INDEX i1b ON t1(b); + SELECT a FROM t1 NATURAL RIGHT JOIN t1 WHERE a=5555 OR (1,b)==(SELECT 2 IN (2,2),4); +} {5555} + +# 2022-04-23 dbsqlfuzz c7ee5500e3abddec3557016de777713b80c790d3 +# Escape from the right-join body subroutine via the ORDER BY LIMIT optimization. +# +reset_db +db null - +do_catchsql_test join8-5000 { + CREATE TABLE t1(x); + INSERT INTO t1(x) VALUES(NULL),(NULL); + CREATE TABLE t2(c, d); + INSERT INTO t2(c,d) SELECT x, x FROM t1; + CREATE INDEX t2dc ON t2(d, c); + SELECT (SELECT c FROM sqlite_temp_schema FULL JOIN t2 ON d IN (1,2,3) ORDER BY d) AS x FROM t1; +} {0 {- -}} + +# 2022-04-29 dbsqlfuzz 19f1102a70cf966ab249de56d944fc20dbebcfcf +# +reset_db +do_execsql_test join8-6000 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT, d REAL); + INSERT INTO t1 VALUES(1,'A','aa',2.5); + SELECT * FROM t1 AS t2 NATURAL RIGHT JOIN t1 AS t3 + WHERE (a,b) IN (SELECT rowid, b FROM t1); +} {1 A aa 2.5} +do_execsql_test join8-6010 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INT PRIMARY KEY, b TEXT, c TEXT, d INT) WITHOUT ROWID; + INSERT INTO t1 VALUES(15,'xray','baker',42); + SELECT value, t1.* FROM json_each('7') NATURAL RIGHT JOIN t1 + WHERE (a,b) IN (SELECT a, b FROM t1); +} {7 15 xray baker 42} +do_execsql_test join8-6020 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY,b); + INSERT INTO t1 VALUES(0,NULL),(1,2); + SELECT value, t1.* FROM json_each('17') NATURAL RIGHT JOIN t1 + WHERE (a,b) IN (SELECT rowid, b FROM t1); +} {17 1 2} + +# Bloom filter usage by RIGHT and FULL JOIN +# +reset_db +do_execsql_test join8-7000 { +CREATE TABLE t1(a INT, b INT, c INT, d INT); + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<10) + INSERT INTO t1(a,b,c,d) SELECT x, x+100, x+200, x+300 FROM c; + CREATE TABLE t2(b INT, x INT); + INSERT INTO t2(b,x) SELECT b, a FROM t1 WHERE a%2=0; + CREATE INDEX t2b ON t2(b); + CREATE TABLE t3(c INT, y INT); + INSERT INTO t3(c,y) SELECT c, a FROM t1 WHERE a%3=0; + CREATE INDEX t3c ON t3(c); + CREATE TABLE t4(d INT, z INT); + INSERT INTO t4(d,z) SELECT d, a FROM t1 WHERE a%5=0; + CREATE INDEX t4d ON t4(d); + INSERT INTO t1(a,b,c,d) VALUES + (96,NULL,296,396), + (97,197,NULL,397), + (98,198,298,NULL), + (99,NULL,NULL,NULL); + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t4','t4d','20 1'); + INSERT INTO sqlite_stat1 VALUES('t3','t3c','32 1'); + INSERT INTO sqlite_stat1 VALUES('t2','t2b','48 1'); + INSERT INTO sqlite_stat1 VALUES('t1',NULL,'100'); + ANALYZE sqlite_schema; +} {} +db null - +do_execsql_test join8-7010 { + WITH t0 AS MATERIALIZED ( + SELECT t1.*, t2.*, t3.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + ) + SELECT * FROM t0 FULL JOIN t4 ON t0.a=t4.d AND t4.z>0 + ORDER BY coalesce(t0.a, t0.y+200, t4.d); +} { + 6 106 206 306 106 6 206 6 - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 +} + +# EVIDENCE-OF: R-33754-02880 you can say "LEFT RIGHT JOIN" which is the +# same as "FULL JOIN". +do_execsql_test join8-7011 { + WITH t0 AS MATERIALIZED ( + SELECT t1.*, t2.*, t3.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + ) + SELECT * FROM t0 LEFT RIGHT JOIN t4 ON t0.a=t4.d AND t4.z>0 + ORDER BY coalesce(t0.a, t0.y+200, t4.d); +} { + 6 106 206 306 106 6 206 6 - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 +} + +do_execsql_test join8-7020 { + EXPLAIN QUERY PLAN + WITH t0 AS MATERIALIZED ( + SELECT t1.*, t2.*, t3.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + ) + SELECT * FROM t0 FULL JOIN t4 ON t0.a=t4.d AND t4.z>0 + ORDER BY coalesce(t0.a, t0.y+200, t4.d); +} {/.*BLOOM FILTER ON t2.*BLOOM FILTER ON t3.*BLOOM FILTER ON t4.*/} + +# 2022-05-12 Difference with PG found (by Dan) while exploring +# https://sqlite.org/forum/forumpost/677a0ab93fcd9ccd +# +reset_db +do_execsql_test join8-8000 { + CREATE TABLE t1(a INT, b INT); + CREATE TABLE t2(c INT, d INT); + CREATE TABLE t3(e INT, f INT); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t2 VALUES(3, 4); + INSERT INTO t3 VALUES(5, 6); +} {} +do_execsql_test join8-8010 { + SELECT * + FROM t3 LEFT JOIN t2 ON true + JOIN t1 ON (t3.e IS t2.c); +} {} +do_execsql_test join8-8020 { + SELECT * + FROM t3 LEFT JOIN t2 ON true + JOIN t1 ON (t3.e IS NOT DISTINCT FROM t2.c); +} {} + +# 2022-05-13 The idea of reusing subquery cursors does not +# work, if the cursors are used both for scanning and lookups. +# +reset_db +db null - +do_execsql_test join8-9000 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT, d REAL); + INSERT INTO t1 VALUES(1,'E','bb',NULL),(2,NULL,NULL,NULL); + SELECT * FROM t1 NATURAL RIGHT JOIN t1 AS t2 WHERE (a,b) IN (SELECT a+0, b FROM t1); +} {1 E bb -} + +# 2022-05-14 https://sqlite.org/forum/forumpost/c06b10ad7e +# +reset_db +db null - +do_execsql_test join8-10000 { + CREATE TABLE t1(c0 INT UNIQUE); + CREATE TABLE t2(c0); + CREATE TABLE t2i(c0 INT); + CREATE TABLE t3(c0 INT); + INSERT INTO t1 VALUES(1); + INSERT INTO t2 VALUES(2); + INSERT INTO t2i VALUES(2); + INSERT INTO t3 VALUES(3); +} {} +do_execsql_test join8-10010 { + SELECT DISTINCT t1.c0, t3.c0 + FROM t2 NATURAL JOIN t1 RIGHT JOIN t3 ON t1.c0; +} {- 3} +do_execsql_test join8-10020 { + SELECT t1.c0, t3.c0 + FROM t2 NATURAL JOIN t1 RIGHT JOIN t3 ON t1.c0; +} {- 3} +do_execsql_test join8-10030 { + SELECT DISTINCT t1.c0, t3.c0 + FROM t2 NATURAL CROSS JOIN t1 RIGHT JOIN t3 ON t1.c0; +} {- 3} +do_execsql_test join8-10040 { + SELECT t1.c0, t3.c0 + FROM t1 NATURAL CROSS JOIN t2 RIGHT JOIN t3 ON t1.c0; +} {- 3} +do_execsql_test join8-10050 { + SELECT DISTINCT t1.c0, t3.c0 + FROM t2i NATURAL JOIN t1 RIGHT JOIN t3 ON t1.c0; +} {- 3} +do_execsql_test join8-10060 { + SELECT DISTINCT +t1.c0, t3.c0 + FROM t2 NATURAL JOIN t1 RIGHT JOIN t3 ON t1.c0; +} {- 3} +do_execsql_test join8-10070 { + SELECT DISTINCT +t1.c0, t3.c0 + FROM t1 NATURAL CROSS JOIN t2 RIGHT JOIN t3 ON t1.c0; +} {- 3} +do_execsql_test join8-10080 { + SELECT DISTINCT t1.c0, t3.c0 + FROM t2 NATURAL JOIN t1 RIGHT JOIN t3 ON t1.c0<>0; +} {- 3} + +# 2022-05-14 +# index-on-expr scan on a RIGHT JOIN +# dbsqlfuzz 39ee60004ff027a9e2846cf76e02cd5ac0953739 +# +reset_db +db null - +do_execsql_test join8-11000 { + CREATE TABLE t1(a); + CREATE TABLE t2(b); + INSERT INTO t2 VALUES(0),(1),(2); + SELECT * FROM t1 RIGHT JOIN t2 ON (a=b) WHERE 99+(b+1)!=99; +} {- 0 - 1 - 2} +do_execsql_test join8-11010 { + CREATE INDEX t2b ON t2(b+1) WHERE b IS NOT NULL; + SELECT * FROM t1 RIGHT JOIN t2 ON (a=b) WHERE 99+(b+1)!=99; +} {- 0 - 1 - 2} +do_execsql_test join8-11020 { + DROP TABLE t1; + DROP TABLE t2; + CREATE TABLE t1(a); + CREATE TABLE t2(b, c, d); + INSERT INTO t2 VALUES(1, 3, 'not-4'); + SELECT b, d FROM t1 RIGHT JOIN t2 WHERE (b+0)=1 AND d!=4; +} {1 not-4} +do_execsql_test join8-11030 { + CREATE INDEX i2 ON t2((b+0), d); + SELECT b, d FROM t1 RIGHT JOIN t2 WHERE (b+0)=1 AND d!=4; +} {1 not-4} +do_execsql_test join8-11040 { + DROP INDEX i2; + CREATE INDEX i2 ON t2((b+0), d) WHERE d IS NOT NULL; + SELECT b, d FROM t1 RIGHT JOIN t2 WHERE (b+0)=1 AND d!=4; +} {1 not-4} + +# 2022-05-23 +# NATURAL JOIN name resolution is more forgiving with LEFT JOIN +# https://sqlite.org/forum/forumpost/e90a8e6e6f +# +reset_db +db null - +do_execsql_test join8-12000 { + CREATE TABLE t1(a INT); INSERT INTO t1 VALUES(0),(1); + CREATE TABLE t2(a INT); INSERT INTO t2 VALUES(0),(2); + CREATE TABLE t3(a INT); INSERT INTO t3 VALUES(0),(3); +} {} +do_catchsql_test join8-12010 { + SELECT * FROM t1 RIGHT JOIN t2 ON t2.a<>0 NATURAL RIGHT JOIN t3; +} {1 {ambiguous reference to a in USING()}} +do_catchsql_test join8-12020 { + SELECT * FROM t1 RIGHT JOIN t2 ON t2.a<>0 NATURAL LEFT JOIN t3; +} {1 {ambiguous reference to a in USING()}} +do_catchsql_test join8-12030 { + SELECT * FROM t1 LEFT JOIN t2 ON t2.a<>0 NATURAL RIGHT JOIN t3; +} {1 {ambiguous reference to a in USING()}} + +# The following query should probably also return the same error as the +# previous three cases. However, historical versions of SQLite have always +# let it pass. We will not "fix" this, since to do so might break legacy +# applications. +# +do_catchsql_test join8-12040 { + SELECT * FROM t1 LEFT JOIN t2 ON t2.a<>0 NATURAL LEFT JOIN t3; +} {0 {0 2 1 2}} + +# 2022-05-24 +# https://sqlite.org/forum/forumpost/687b0bf563a1d4f1 +# +reset_db +do_execsql_test join8-13000 { + CREATE TABLE t0(t TEXT, u TEXT); INSERT INTO t0 VALUES('t', 'u'); + CREATE TABLE t1(v TEXT, w TEXT); INSERT INTO t1 VALUES('v', 'w'); + CREATE TABLE t2(x TEXT, y TEXT); INSERT INTO t2 VALUES('x', 'y'); + SELECT * FROM t0 JOIN t1 ON (t2.x NOTNULL) LEFT JOIN t2 ON false; + SELECT * FROM t0 JOIN t1 ON (t2.x NOTNULL) LEFT JOIN t2 ON false + WHERE t2.y ISNULL; +} {} + +# 2022-05-25 +# https://sqlite.org/forum/forumpost/5cfe08eed6 +# +reset_db +do_execsql_test join8-14000 { + CREATE TABLE t0(a TEXT, b TEXT, c TEXT); + CREATE TABLE t1(a TEXT); + INSERT INTO t1 VALUES('1'); + CREATE VIEW v0 AS SELECT 'xyz' AS d; + SELECT * FROM v0 RIGHT JOIN t1 ON t1.a<>'' INNER JOIN t0 ON t0.c<>''; + SELECT * FROM v0 RIGHT JOIN t1 ON t1.a<>'' INNER JOIN t0 ON t0.c<>'' WHERE b ISNULL; +} {} +do_execsql_test join8-14010 { + CREATE TABLE y0(a INT); + CREATE TABLE y1(b INT); INSERT INTO y1 VALUES(1), (2); + CREATE TABLE y2(c INT); INSERT INTO y2 VALUES(3), (4); +} {} +db null - +do_execsql_test join8-14020 { + SELECT * FROM y0 RIGHT JOIN y1 ON true INNER JOIN y2 ON true WHERE y2.c!=99 AND y2.c!=98; +} { + - 1 3 + - 1 4 + - 2 3 + - 2 4 +} + +# 2022-05-30 +# https://sqlite.org/forum/forumpost/3902c7b833 +# +reset_db +do_execsql_test join8-15000 { + CREATE TABLE t1(x INT); + CREATE TABLE t2(y INT); + CREATE TABLE t3(z INT); + INSERT INTO t1 VALUES(10); + INSERT INTO t3 VALUES(20),(30); +} +do_execsql_test join8-15010 { + SELECT * FROM t1 LEFT JOIN t2 ON true JOIN t3 ON t2.y IS NOT NULL; +} {} +do_execsql_test join8-15020 { + SELECT * FROM t1 LEFT JOIN t2 ON true JOIN t3 ON t2.y IS NOT NULL + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600); +} {} +do_execsql_test join8-15100 { + PRAGMA automatic_index = 0; + CREATE TABLE t4(x TEXT); + CREATE TABLE t5(y TEXT); + CREATE TABLE t6(z TEXT); + INSERT INTO t4 VALUES('a'), ('b'); + INSERT INTO t5 VALUES('b'), ('c'); + INSERT INTO t6 VALUES('a'), ('d'); +} {} +db null - +do_execsql_test join8-15110 { + SELECT * FROM t4 LEFT JOIN t5 ON x=y LEFT JOIN t6 ON (x=z) ORDER BY +x; +} {a - a b b -} +do_execsql_test join8-15120 { + SELECT * FROM t4 LEFT JOIN t5 ON x=y LEFT JOIN t6 ON (x=z) + WHERE t5.y!='x' AND t4.x!='x'; +} {b b -} + +# 2022-05-31 +# https://sqlite.org/forum/forumpost/c2554d560b +reset_db +do_execsql_test join8-16000 { + CREATE TABLE t1(a TEXT); + CREATE TABLE t2(b TEXT); + CREATE TABLE t3(c TEXT); + INSERT INTO t2(b) VALUES ('x'); + INSERT INTO t3(c) VALUES ('y'), ('z'); +} {} +db null - +do_execsql_test join8-16010 { + SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>''; +} {- x -} +do_execsql_test join8-16020 { + SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>'' WHERE c IS NULL; +} {- x -} +do_execsql_test join8-16020 { + SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>'' WHERE c IS NULL; +} {} +do_execsql_test join8-16030 { + SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>''; +} {} +do_execsql_test join8-16040 { + SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>'' WHERE c<>''; +} {} +do_execsql_test join8-16050 { + SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>'' WHERE c IS NOT NULL; +} {} +do_execsql_test join8-16060 { + SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>'' WHERE c<>''; +} {} +do_execsql_test join8-16070 { + SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>'' WHERE c IS NOT NULL; +} {} + +# 2022-06-01 +# https://sqlite.org/forum/forumpost/087de2d9ec +# +reset_db +do_execsql_test join8-17000 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, x INT, y INT); + CREATE TABLE t2(z INT); + INSERT INTO t1(id,x,y) VALUES(1, 0, 0); +} {} +db null NULL +do_execsql_test join8-17010 { + SELECT * FROM t2 RIGHT JOIN t1 ON true; +} {NULL 1 0 0} +do_execsql_test join8-17020 { + SELECT 99=id AND 0=y AS "truth" FROM t2 RIGHT JOIN t1 ON true; +} {0} +do_execsql_test join8-17030 { + SELECT (99, 0)==(id, y) AS "truth" FROM t2 RIGHT JOIN t1; +} {0} +do_execsql_test join8-17040 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE 99=id AND 0=y; +} {} +do_execsql_test join8-17041 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE 99=+id AND 0=y; +} {} +do_execsql_test join8-17050 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE (99, 0)==(id,y); +} {} +do_execsql_test join8-17051 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE (99, 0)==(+id,y); +} {} +do_execsql_test join8-17060 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE 1=id AND 0=y; +} {NULL 1 0 0} +do_execsql_test join8-17061 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE 1=+id AND 0=y; +} {NULL 1 0 0} +do_execsql_test join8-17070 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE (1, 0)==(id,y); +} {NULL 1 0 0} +do_execsql_test join8-17071 { + SELECT * FROM t2 RIGHT JOIN t1 WHERE (1, 0)==(+id,y); +} {NULL 1 0 0} +do_execsql_test join8-17080 { + CREATE TABLE t3(a INTEGER PRIMARY KEY, b INT); + CREATE TABLE t4(x INT, y INT); + INSERT INTO t3(a,b) VALUES(1, 3); +} {} +do_execsql_test join8-17090 { + SELECT t3.a FROM t4 RIGHT JOIN t3 ON (x=a) WHERE (b, 4)=(SELECT 3, 4); +} {1} +do_execsql_test join8-17091 { + SELECT t3.a FROM t4 RIGHT JOIN t3 ON (x=a) WHERE (b, 4) IS (SELECT 3, 4); +} {1} + +# 2022-06-06 +# https://sqlite.org/forum/forumpost/206d99a16dd9212f +# tag-20191211-001 +# +reset_db +do_execsql_test join8-18000 { + CREATE TABLE t1(a BOOLEAN); INSERT INTO t1 VALUES (false); + CREATE TABLE t2(x INT); INSERT INTO t2 VALUES (0); + SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 RIGHT JOIN t1 ON true WHERE (x NOTNULL)=a; +} {} +do_execsql_test join8-18010 { + CREATE INDEX t1a ON t1(a); + SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 RIGHT JOIN t1 ON true WHERE (x NOTNULL)=a; +} {} + +do_execsql_test join8-18020 { + CREATE TABLE t3(z); + INSERT INTO t3 VALUES('t3value'); + SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 RIGHT JOIN t1 ON true INNER JOIN t3 ON (x NOTNULL)=a; +} {} + +ifcapable rtree { + do_execsql_test join8-18030 { + CREATE VIRTUAL TABLE rtree1 USING rtree(a, x1, x2); + INSERT INTO rtree1 VALUES(0, 0, 0); + } + do_execsql_test join8-18040 { + SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 + RIGHT JOIN rtree1 ON true INNER JOIN t3 ON (x NOTNULL)=+a; + } {} + do_execsql_test join8-18050 { + SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 + RIGHT JOIN rtree1 ON true INNER JOIN t3 ON (x NOTNULL)=a; + } {} +} + + +reset_db +do_execsql_test join8-19000 { + CREATE TABLE t1(a INT); + CREATE TABLE t2(b INT, c INT); + CREATE TABLE t3(d INT); + + INSERT INTO t1 VALUES(10); + INSERT INTO t2 VALUES(50,51); + INSERT INTO t3 VALUES(299); + + CREATE INDEX t2b ON t2( (b IS NOT NULL) ); +} + +do_execsql_test join8-19010 { + SELECT * FROM t1 LEFT JOIN t2 ON true INNER JOIN t3 ON (b IS NOT NULL)=0; +} + +# 2022-06-07 +# https://sqlite.org/forum/forumpost/323f86cc30 +reset_db +do_execsql_test join8-20000 { + CREATE TABLE t1(x TEXT); + INSERT INTO t1(x) VALUES('aaa'); + CREATE VIEW v0(y) AS SELECT x FROM t1; + CREATE TABLE t2(z TEXT); +} {} +db null - +do_execsql_test join8-20010 { + SELECT * FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc'; +} {- - aaa} +do_execsql_test join8-20020 { + SELECT * FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc' ORDER BY z; +} {- - aaa} +do_execsql_test join8-20030 { + SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc'; +} {99} +do_execsql_test join8-20040 { + SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc' ORDER BY z; +} {99} +do_execsql_test join8-20050 { + SELECT count(*) + FROM (SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'' RIGHT JOIN t1 ON z<>'') AS "t3"; +} {1} +do_execsql_test join8-20060 { + SELECT count(*) + FROM (SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'' RIGHT JOIN t1 ON z<>'' ORDER BY z) AS "t3"; +} {1} + +# 2022-06-10 +# https://sqlite.org/forum/forumpost/8e4c352937e82929 +# +# Do not allow constant propagation between ON and WHERE clause terms. +# (Updated 2022-06-20) See also https://sqlite.org/forum/forumpost/57bdf2217d +# +reset_db +do_execsql_test join8-21000 { + CREATE TABLE t1(a INT,b BOOLEAN); + CREATE TABLE t2(c INT); INSERT INTO t2 VALUES(NULL); + CREATE TABLE t3(d INT); +} +do_execsql_test join8-21010 { + SELECT (b IS TRUE) FROM t1 JOIN t3 ON (b=TRUE) RIGHT JOIN t2 ON TRUE; +} {0} +do_execsql_test join8-22020 { + SELECT * FROM t1 JOIN t3 ON (b=TRUE) RIGHT JOIN t2 ON TRUE WHERE (b IS TRUE); +} {} +do_execsql_test join8-22030 { + DROP TABLE t1; + DROP TABLE t2; + DROP TABLE t3; + CREATE TABLE t1(a INT); + CREATE TABLE t2(b INT); + CREATE TABLE t3(c INTEGER PRIMARY KEY, d INT); + CREATE INDEX t3d ON t3(d); + INSERT INTO t3 VALUES(0, 0); +} +do_catchsql_test join8-22031 { + SELECT * FROM t1 JOIN t2 ON d>b RIGHT JOIN t3 ON true WHERE +d = 0; +} {1 {ON clause references tables to its right}} +do_catchsql_test join8-22040 { + SELECT * FROM t1 JOIN t2 ON d>b RIGHT JOIN t3 ON true WHERE d = 0; +} {1 {ON clause references tables to its right}} + + +# 2022-06-10 +# https://sqlite.org/forum/forumpost/51e6959f61 +# +# Restrictions on the usage of WHERE clause constraints by joins that are +# involved with a RIGHT JOIN must also be applied to automatic indexes. +# +reset_db +do_execsql_test join8-22000 { + CREATE TABLE t1(a INT); + CREATE TABLE t2(b INT); + CREATE TABLE t3(c TEXT); INSERT INTO t3 VALUES('x'); + CREATE TABLE t4(d TEXT); INSERT INTO t4 VALUES('y'); + SELECT 99 + FROM t1 + LEFT JOIN t2 ON true + RIGHT JOIN t3 ON true + RIGHT JOIN t4 ON true + WHERE a=b; +} {} + +# 2022-06-13 +# https://sqlite.org/forum/forumpost/b40696f501 +# +# This optimization that converts "x ISNULL" into "FALSE" when column "x" has a +# NOT NULL constraint is too aggresive if the query contains RIGHT JOIN. +# +reset_db +db null - +do_execsql_test join8-23000 { + CREATE TABLE t1(a TEXT); + INSERT INTO t1 VALUES('c'); + CREATE TABLE t2(b TEXT, c TEXT NOT NULL); + INSERT INTO t2 VALUES('a', 'b'); + CREATE TABLE t3(d TEXT); + INSERT INTO t3 VALUES('x'); + CREATE TABLE t4(e TEXT); + INSERT INTO t4 VALUES('y'); +} +do_execsql_test join8-23010 { + SELECT * + FROM t1 + LEFT JOIN t2 ON TRUE + JOIN t3 ON c='' + RIGHT JOIN t4 ON b=''; +} {- - - - y} +do_execsql_test join8-23020 { + SELECT * + FROM t1 + LEFT JOIN t2 ON TRUE + JOIN t3 ON c='' + RIGHT JOIN t4 ON b='' + WHERE d ISNULL +} {- - - - y} + +# 2022-06-14 +# dbsqlfuzz 2f3101834d14325a976f601b9267a0fd323d6bbd +# +# When the OP_NullRow opcode creates a new cursor, it must +# set the cursor to no-reuse so that an OP_OpenEphemeral in +# a subroutine does not try to reuse it. +# +reset_db +db null - +do_execsql_test join8-24000 { + CREATE TABLE t4(b INT, c INT); + CREATE TABLE t5(a INT, f INT); + INSERT INTO t5 VALUES(1,2); + WITH t7(x, y) AS (SELECT 100, 200 FROM t5) + SELECT * FROM t4 JOIN t7 ON true RIGHT JOIN (SELECT y AS z FROM t7) AS t6 ON (x=z); +} {- - - - 200} + +# 2022-06-20 +# forum/forumpost/6650cd40b5634f35 +# +reset_db +do_execsql_test join8-25000 { + CREATE TABLE t1(a1 INT); + CREATE TABLE t2(b2 INT); + CREATE TABLE t3(c3 INT, d3 INT UNIQUE); + CREATE TABLE t4(e4 INT, f4 TEXT); + INSERT INTO t3(c3, d3) VALUES (2, 1); + INSERT INTO t4(f4) VALUES ('x'); + CREATE INDEX i0 ON t3(c3) WHERE d3 ISNULL; + ANALYZE main; +} +db null - +do_execsql_test join8-25010 { + SELECT * FROM t1 LEFT JOIN t2 ON true JOIN t3 ON (b2 IN (a1)) FULL JOIN t4 ON true; +} {- - - - - x} +do_execsql_test join8-25020 { + SELECT 1 FROM t1 LEFT JOIN t2 ON true JOIN t3 ON (b2 IN (a1)) FULL JOIN t4 ON true; +} {1} + +# 2022-07-13 +# forum/forumpost/174afeae57 +# +reset_db +db null - +do_execsql_test join8-26000 { + CREATE TABLE t1(a INT); + CREATE TABLE t2(b INT, c INT); + CREATE VIEW t3(d) AS SELECT NULL FROM t2 FULL OUTER JOIN t1 ON c=a UNION ALL SELECT b FROM t2; + INSERT INTO t1(a) VALUES (NULL); + INSERT INTO t2(b, c) VALUES (99, NULL); + SELECT DISTINCT b, c, d FROM t2, t3 WHERE b<>0 + UNION SELECT DISTINCT b, c, d FROM t2, t3 WHERE b ISNULL; +} {99 - - 99 - 99} + +finish_test diff --git a/test/join9.test b/test/join9.test new file mode 100644 index 000000000..e547d4ce7 --- /dev/null +++ b/test/join9.test @@ -0,0 +1,565 @@ +# 2022-04-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 regression tests for SQLite library. +# +# This file implements tests for RIGHT and FULL OUTER JOINs. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +foreach {id schema} { + 1 { + CREATE TABLE t3(id INTEGER PRIMARY KEY, w TEXT); + CREATE TABLE t4(id INTEGER PRIMARY KEY, x TEXT); + CREATE TABLE t5(id INTEGER PRIMARY KEY, y TEXT); + CREATE TABLE t6(id INTEGER PRIMARY KEY, z INT); + CREATE VIEW dual(dummy) AS VALUES('x'); + INSERT INTO t3(id,w) VALUES(2,'two'),(3,'three'),(6,'six'),(7,'seven'); + INSERT INTO t4(id,x) VALUES(2,'alice'),(4,'bob'),(6,'cindy'),(8,'dave'); + INSERT INTO t5(id,y) VALUES(1,'red'),(2,'orange'),(3,'yellow'),(4,'green'), + (5,'blue'); + INSERT INTO t6(id,z) VALUES(3,333),(4,444),(5,555),(0,1000),(9,999); + } + 2 { + CREATE TABLE t3(id INT PRIMARY KEY, w TEXT) WITHOUT ROWID; + CREATE TABLE t4(id INT PRIMARY KEY, x TEXT) WITHOUT ROWID; + CREATE TABLE t5(id INT PRIMARY KEY, y TEXT) WITHOUT ROWID; + CREATE TABLE t6(id INT PRIMARY KEY, z INT) WITHOUT ROWID; + CREATE TABLE dual(dummy TEXT); + INSERT INTO dual(dummy) VALUES('x'); + INSERT INTO t3(id,w) VALUES(2,'two'),(3,'three'),(6,'six'),(7,'seven'); + INSERT INTO t4(id,x) VALUES(2,'alice'),(4,'bob'),(6,'cindy'),(8,'dave'); + INSERT INTO t5(id,y) VALUES(1,'red'),(2,'orange'),(3,'yellow'),(4,'green'), + (5,'blue'); + INSERT INTO t6(id,z) VALUES(3,333),(4,444),(5,555),(0,1000),(9,999); + } + 3 { + CREATE TABLE t3x(id INTEGER PRIMARY KEY, w TEXT); + CREATE TABLE t4x(id INTEGER PRIMARY KEY, x TEXT); + CREATE TABLE t5x(id INTEGER PRIMARY KEY, y TEXT); + CREATE TABLE t6x(id INTEGER PRIMARY KEY, z INT); + CREATE VIEW dual(dummy) AS VALUES('x'); + INSERT INTO t3x(id,w) VALUES(2,'two'),(3,'three'),(6,'six'),(7,'seven'); + INSERT INTO t4x(id,x) VALUES(2,'alice'),(4,'bob'),(6,'cindy'),(8,'dave'); + INSERT INTO t5x(id,y) VALUES(1,'red'),(2,'orange'),(3,'yellow'),(4,'green'), + (5,'blue'); + INSERT INTO t6x(id,z) VALUES(3,333),(4,444),(5,555),(0,1000),(9,999); + CREATE VIEW t3 AS SELECT * FROM t3x LIMIT 1000; + CREATE VIEW t4 AS SELECT * FROM t4x LIMIT 1000; + CREATE VIEW t5 AS SELECT * FROM t5x LIMIT 1000; + CREATE VIEW t6 AS SELECT * FROM t6x LIMIT 1000; + } + 4 { + CREATE TABLE t3a(id INTEGER PRIMARY KEY, w TEXT); + CREATE TABLE t3b(id INTEGER PRIMARY KEY, w TEXT); + CREATE TABLE t4a(id INTEGER PRIMARY KEY, x TEXT); + CREATE TABLE t4b(id INTEGER PRIMARY KEY, x TEXT); + CREATE TABLE t5a(id INTEGER PRIMARY KEY, y TEXT); + CREATE TABLE t5b(id INTEGER PRIMARY KEY, y TEXT); + CREATE TABLE t6a(id INTEGER PRIMARY KEY, z INT); + CREATE TABLE t6b(id INTEGER PRIMARY KEY, z INT); + CREATE VIEW dual(dummy) AS VALUES('x'); + INSERT INTO t3a(id,w) VALUES(2,'two'),(3,'three'); + INSERT INTO t3b(id,w) VALUES(6,'six'),(7,'seven'); + INSERT INTO t4a(id,x) VALUES(2,'alice'),(4,'bob'); + INSERT INTO t4b(id,x) VALUES(6,'cindy'),(8,'dave'); + INSERT INTO t5a(id,y) VALUES(1,'red'),(2,'orange'),(3,'yellow'); + INSERT INTO t5b(id,y) VALUES(4,'green'),(5,'blue'); + INSERT INTO t6a(id,z) VALUES(3,333),(4,444); + INSERT INTO t6b(id,z) VALUES(5,555),(0,1000),(9,999); + CREATE VIEW t3 AS SELECT * FROM t3a UNION ALL SELECT * FROM t3b; + CREATE VIEW t4 AS SELECT * FROM t4a UNION ALL SELECT * FROM t4b; + CREATE VIEW t5 AS SELECT * FROM t5a UNION ALL SELECT * FROM t5b; + CREATE VIEW t6 AS SELECT * FROM t6a UNION ALL SELECT * FROM t6b; + } + 5 { + CREATE TABLE t3a(id INTEGER PRIMARY KEY, w TEXT) WITHOUT ROWID; + CREATE TABLE t3b(id INTEGER PRIMARY KEY, w TEXT); + CREATE TABLE t4a(id INTEGER PRIMARY KEY, x TEXT) WITHOUT ROWID; + CREATE TABLE t4b(id INTEGER PRIMARY KEY, x TEXT) WITHOUT ROWID; + CREATE TABLE t5a(id INTEGER PRIMARY KEY, y TEXT); + CREATE TABLE t5b(id INTEGER PRIMARY KEY, y TEXT) WITHOUT ROWID; + CREATE TABLE t6a(id INTEGER PRIMARY KEY, z INT); + CREATE TABLE t6b(id INTEGER PRIMARY KEY, z INT); + CREATE VIEW dual(dummy) AS VALUES('x'); + INSERT INTO t3a(id,w) VALUES(2,'two'),(3,'three'); + INSERT INTO t3b(id,w) VALUES(6,'six'),(7,'seven'); + INSERT INTO t4a(id,x) VALUES(2,'alice'),(4,'bob'); + INSERT INTO t4b(id,x) VALUES(6,'cindy'),(8,'dave'); + INSERT INTO t5a(id,y) VALUES(1,'red'),(2,'orange'),(3,'yellow'); + INSERT INTO t5b(id,y) VALUES(4,'green'),(5,'blue'); + INSERT INTO t6a(id,z) VALUES(3,333),(4,444); + INSERT INTO t6b(id,z) VALUES(5,555),(0,1000),(9,999); + CREATE VIEW t3 AS SELECT * FROM t3a UNION ALL SELECT * FROM t3b; + CREATE VIEW t4 AS SELECT * FROM t4a UNION ALL SELECT * FROM t4b LIMIT 50; + CREATE VIEW t5 AS SELECT * FROM t5a UNION ALL SELECT * FROM t5b LIMIT 100; + CREATE VIEW t6 AS SELECT * FROM t6a UNION ALL SELECT * FROM t6b; + } +} { + reset_db + db nullvalue - + do_execsql_test join9-$id.setup $schema {} + + # Verifid by PG-14 for case 1 + do_execsql_test join9-$id.100 { + SELECT *, t4.id, t5.id, t6.id + FROM t4 NATURAL LEFT JOIN t5 NATURAL LEFT JOIN t6 + ORDER BY 1; + } { + 2 alice orange - 2 2 - + 4 bob green 444 4 4 4 + 6 cindy - - 6 - - + 8 dave - - 8 - - + } + + do_execsql_test join9-$id.101 { + SELECT *, t4.id, t5.id, t6.id + FROM t4 NATURAL LEFT JOIN t5 NATURAL LEFT JOIN t6 + ORDER BY id; + } { + 2 alice orange - 2 2 - + 4 bob green 444 4 4 4 + 6 cindy - - 6 - - + 8 dave - - 8 - - + } + do_execsql_test join9-$id.102 { + SELECT *, t4.id, t5.id, t6.id + FROM t4 LEFT JOIN t5 USING(id) LEFT JOIN t6 USING(id) + ORDER BY id; + } { + 2 alice orange - 2 2 - + 4 bob green 444 4 4 4 + 6 cindy - - 6 - - + 8 dave - - 8 - - + } + + # Verifid by PG-14 using case 1 + do_execsql_test join9-$id.200 { + SELECT id, x, y, z, t4.id, t5.id, t6.id + FROM t5 NATURAL RIGHT JOIN t4 NATURAL LEFT JOIN t6 + ORDER BY 1; + } { + 2 alice orange - 2 2 - + 4 bob green 444 4 4 4 + 6 cindy - - 6 - - + 8 dave - - 8 - - + } + + do_execsql_test join9-$id.201 { + SELECT id, x, y, z, t4.id, t5.id, t6.id + FROM t5 NATURAL RIGHT JOIN t4 NATURAL LEFT JOIN t6 + ORDER BY id; + } { + 2 alice orange - 2 2 - + 4 bob green 444 4 4 4 + 6 cindy - - 6 - - + 8 dave - - 8 - - + } + + # Verified by PG-14 using case 1 + do_execsql_test join9-$id.300 { + SELECT *, t4.id, t5.id, t6.id + FROM t4 NATURAL RIGHT JOIN t5 NATURAL RIGHT JOIN t6 + ORDER BY 1; + } { + 0 - - 1000 - - 0 + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 9 - - 999 - - 9 + } + + do_execsql_test join9-$id.301 { + SELECT *, t4.id, t5.id, t6.id + FROM t4 NATURAL RIGHT JOIN t5 NATURAL RIGHT JOIN t6 + ORDER BY id; + } { + 0 - - 1000 - - 0 + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 9 - - 999 - - 9 + } + + # Verified by PG-14 for case 1 + do_execsql_test join9-$id.400 { + SELECT *, t4.id, t5.id, t6.id + FROM t4 NATURAL FULL JOIN t5 NATURAL FULL JOIN t6 + ORDER BY 1; + } { + 0 - - 1000 - - 0 + 1 - red - - 1 - + 2 alice orange - 2 2 - + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 6 cindy - - 6 - - + 8 dave - - 8 - - + 9 - - 999 - - 9 + } + + do_execsql_test join9-$id.401 { + SELECT *, t4.id, t5.id, t6.id + FROM t4 NATURAL FULL JOIN t5 NATURAL FULL JOIN t6 + ORDER BY id; + } { + 0 - - 1000 - - 0 + 1 - red - - 1 - + 2 alice orange - 2 2 - + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 6 cindy - - 6 - - + 8 dave - - 8 - - + 9 - - 999 - - 9 + } + do_execsql_test join9-$id.402 { + SELECT id, x, y, z, t4.id, t5.id, t6.id + FROM t4 NATURAL FULL JOIN t6 NATURAL FULL JOIN t5 + ORDER BY id; + } { + 0 - - 1000 - - 0 + 1 - red - - 1 - + 2 alice orange - 2 2 - + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 6 cindy - - 6 - - + 8 dave - - 8 - - + 9 - - 999 - - 9 + } + do_execsql_test join9-$id.403 { + SELECT id, x, y, z, t4.id, t5.id, t6.id + FROM t5 NATURAL FULL JOIN t4 NATURAL FULL JOIN t6 + ORDER BY id; + } { + 0 - - 1000 - - 0 + 1 - red - - 1 - + 2 alice orange - 2 2 - + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 6 cindy - - 6 - - + 8 dave - - 8 - - + 9 - - 999 - - 9 + } + do_execsql_test join9-$id.404 { + SELECT id, x, y, z, t4.id, t5.id, t6.id + FROM t5 NATURAL FULL JOIN t6 NATURAL FULL JOIN t4 + ORDER BY id; + } { + 0 - - 1000 - - 0 + 1 - red - - 1 - + 2 alice orange - 2 2 - + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 6 cindy - - 6 - - + 8 dave - - 8 - - + 9 - - 999 - - 9 + } + do_execsql_test join9-$id.405 { + SELECT id, x, y, z, t4.id, t5.id, t6.id + FROM t6 NATURAL FULL JOIN t4 NATURAL FULL JOIN t5 + ORDER BY id; + } { + 0 - - 1000 - - 0 + 1 - red - - 1 - + 2 alice orange - 2 2 - + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 6 cindy - - 6 - - + 8 dave - - 8 - - + 9 - - 999 - - 9 + } + do_execsql_test join9-$id.406 { + SELECT id, x, y, z, t4.id, t5.id, t6.id + FROM t6 NATURAL FULL JOIN t5 NATURAL FULL JOIN t4 + ORDER BY id; + } { + 0 - - 1000 - - 0 + 1 - red - - 1 - + 2 alice orange - 2 2 - + 3 - yellow 333 - 3 3 + 4 bob green 444 4 4 4 + 5 - blue 555 - 5 5 + 6 cindy - - 6 - - + 8 dave - - 8 - - + 9 - - 999 - - 9 + } + + # Verified by PG-14 using case 1 + do_execsql_test join9-$id.500 { + SELECT id, w, x, y, z + FROM t3 FULL JOIN t4 USING(id) + NATURAL FULL JOIN t5 + FULL JOIN t6 USING(id) + ORDER BY 1; + } { + 0 - - - 1000 + 1 - - red - + 2 two alice orange - + 3 three - yellow 333 + 4 - bob green 444 + 5 - - blue 555 + 6 six cindy - - + 7 seven - - - + 8 - dave - - + 9 - - - 999 + } + + # Verified by PG-14 using case 1 + do_execsql_test join9-$id.600 { + SELECT id, w, x, y, z + FROM t3 JOIN dual AS d1 ON true + FULL JOIN t4 USING(id) + JOIN dual AS d2 ON true + NATURAL FULL JOIN t5 + JOIN dual AS d3 ON true + FULL JOIN t6 USING(id) + CROSS JOIN dual AS d4 + ORDER BY 1; + } { + 0 - - - 1000 + 1 - - red - + 2 two alice orange - + 3 three - yellow 333 + 4 - bob green 444 + 5 - - blue 555 + 6 six cindy - - + 7 seven - - - + 8 - dave - - + 9 - - - 999 + } + + # Verified by PG-14 using case 1 + do_execsql_test join9-$id.700 { + SELECT id, w, x, y, z + FROM t3 JOIN dual AS d1 ON true + FULL JOIN t4 USING(id) + JOIN dual AS d2 ON true + NATURAL FULL JOIN t5 + JOIN dual AS d3 ON true + FULL JOIN t6 USING(id) + CROSS JOIN dual AS d4 + WHERE x<>'bob' OR x IS NULL + ORDER BY 1; + } { + 0 - - - 1000 + 1 - - red - + 2 two alice orange - + 3 three - yellow 333 + 5 - - blue 555 + 6 six cindy - - + 7 seven - - - + 8 - dave - - + 9 - - - 999 + } + + # Verified by PG-14 using case 1 + do_execsql_test join9-$id.800 { + WITH t7(id,a) AS MATERIALIZED (SELECT * FROM t4 WHERE false) + SELECT * + FROM t7 + JOIN t7 AS t7b USING(id) + FULL JOIN t3 USING(id); + } { + 2 - - two + 3 - - three + 6 - - six + 7 - - seven + } + + # Verified by PG-14 + do_execsql_test join9-$id.900 { + SELECT * + FROM (t3 NATURAL FULL JOIN t4) + NATURAL FULL JOIN + (t5 NATURAL FULL JOIN t6) + ORDER BY 1; + } { + 0 - - - 1000 + 1 - - red - + 2 two alice orange - + 3 three - yellow 333 + 4 - bob green 444 + 5 - - blue 555 + 6 six cindy - - + 7 seven - - - + 8 - dave - - + 9 - - - 999 + } + do_execsql_test join9-$id.910 { + SELECT * + FROM t3 NATURAL FULL JOIN + (t4 NATURAL FULL JOIN + (t5 NATURAL FULL JOIN t6)) + ORDER BY 1; + } { + 0 - - - 1000 + 1 - - red - + 2 two alice orange - + 3 three - yellow 333 + 4 - bob green 444 + 5 - - blue 555 + 6 six cindy - - + 7 seven - - - + 8 - dave - - + 9 - - - 999 + } + do_execsql_test join9-$id.920 { + SELECT * + FROM t3 FULL JOIN ( + t4 FULL JOIN ( + t5 FULL JOIN t6 USING (id) + ) USING(id) + ) USING(id) + ORDER BY 1; + } { + 0 - - - 1000 + 1 - - red - + 2 two alice orange - + 3 three - yellow 333 + 4 - bob green 444 + 5 - - blue 555 + 6 six cindy - - + 7 seven - - - + 8 - dave - - + 9 - - - 999 + } + do_execsql_test join9-$id.920 { + SELECT * + FROM t3 FULL JOIN ( + t4 FULL JOIN ( + t5 FULL JOIN t6 USING (id) + ) USING(id) + ) USING(id) + ORDER BY 1; + } { + 0 - - - 1000 + 1 - - red - + 2 two alice orange - + 3 three - yellow 333 + 4 - bob green 444 + 5 - - blue 555 + 6 six cindy - - + 7 seven - - - + 8 - dave - - + 9 - - - 999 + } + + # Verified by PG-14 + do_execsql_test join9-$id.930 { + SELECT * + FROM t3 FULL JOIN ( + t4 FULL JOIN ( + t5 FULL JOIN t6 USING(id) + ) USING(id) + ) AS j1 ON j1.id=t3.id + ORDER BY coalesce(t3.id,j1.id); + } { + - - 0 - - 1000 + - - 1 - red - + 2 two 2 alice orange - + 3 three 3 - yellow 333 + - - 4 bob green 444 + - - 5 - blue 555 + 6 six 6 cindy - - + 7 seven - - - - + - - 8 dave - - + - - 9 - - 999 + } + + # Verified by PG-14 + do_execsql_test join9-$id.940 { + SELECT * + FROM t3 FULL JOIN ( + t4 RIGHT JOIN ( + t5 FULL JOIN t6 USING(id) + ) USING(id) + ) AS j1 ON j1.id=t3.id + ORDER BY coalesce(t3.id,j1.id); + } { + - - 0 - - 1000 + - - 1 - red - + 2 two 2 alice orange - + 3 three 3 - yellow 333 + - - 4 bob green 444 + - - 5 - blue 555 + 6 six - - - - + 7 seven - - - - + - - 9 - - 999 + } + + # Verified by PG-14 + do_execsql_test join9-$id.950 { + SELECT * + FROM t3 FULL JOIN ( + t4 LEFT JOIN ( + t5 FULL JOIN t6 USING(id) + ) USING(id) + ) AS j1 ON j1.id=t3.id + ORDER BY coalesce(t3.id,j1.id); + } { + 2 two 2 alice orange - + 3 three - - - - + - - 4 bob green 444 + 6 six 6 cindy - - + 7 seven - - - - + - - 8 dave - - + } + + # Restriction (27) in the query flattener + # Verified by PG-14 + do_execsql_test join9-$id.1000 { + WITH t56(id,y,z) AS (SELECT * FROM t5 FULL JOIN t6 USING(id) LIMIT 50) + SELECT id,x,y,z FROM t4 JOIN t56 USING(id) + ORDER BY 1; + } { + 2 alice orange - + 4 bob green 444 + } + + # Verified by PG-14 + do_execsql_test join9-$id.1010 { + SELECT id,x,y,z + FROM t4 INNER JOIN (t5 FULL JOIN t6 USING(id)) USING(id) + ORDER BY 1; + } { + 2 alice orange - + 4 bob green 444 + } + + # Verified by PG-14 + do_execsql_test join9-$id.1020 { + SELECT id,x,y,z + FROM t4 FULL JOIN t5 USING(id) INNER JOIN t6 USING(id) + ORDER BY 1; + } { + 3 - yellow 333 + 4 bob green 444 + 5 - blue 555 + } + + # Verified by PG-14 + do_execsql_test join9-$id.1030 { + WITH t45(id,x,y) AS (SELECT * FROM t4 FULL JOIN t5 USING(id) LIMIT 50) + SELECT id,x,y,z FROM t45 JOIN t6 USING(id) + ORDER BY 1; + } { + 3 - yellow 333 + 4 bob green 444 + 5 - blue 555 + } + +} +finish_test diff --git a/test/joinA.test b/test/joinA.test new file mode 100644 index 000000000..d6bb678c5 --- /dev/null +++ b/test/joinA.test @@ -0,0 +1,214 @@ +# 2022-04-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. +# +# This file implements tests for RIGHT and FULL OUTER JOINs. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +foreach {id schema} { + 1 { + CREATE TABLE t1(a INT, b INT, c INT, d INT); + CREATE TABLE t2(c INT, d INT, e INT, f INT); + CREATE TABLE t3(a INT, b INT, e INT, f INT); + CREATE TABLE t4(a INT, c INT, d INT, f INT); + INSERT INTO t1 VALUES(11,21,31,41),(12,22,32,42),(15,25,35,45),(18,28,38,48); + INSERT INTO t2 VALUES(12,22,32,42),(13,23,33,43),(15,25,35,45),(17,27,37,47); + INSERT INTO t3 VALUES(14,24,34,44),(15,25,35,45),(16,26,36,46); + INSERT INTO t4 VALUES(11,21,31,41),(13,23,33,43),(16,26,36,46),(19,29,39,49); + } + 2 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT, c INT, d INT); + CREATE TABLE t2(c INT, d INTEGER PRIMARY KEY, e INT, f INT); + CREATE TABLE t3(a INT, b INT, e INTEGER PRIMARY KEY, f INT); + CREATE TABLE t4(a INT, c INT, d INT, f INT PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t1 VALUES(11,21,31,41),(12,22,32,42),(15,25,35,45),(18,28,38,48); + INSERT INTO t2 VALUES(12,22,32,42),(13,23,33,43),(15,25,35,45),(17,27,37,47); + INSERT INTO t3 VALUES(14,24,34,44),(15,25,35,45),(16,26,36,46); + INSERT INTO t4 VALUES(11,21,31,41),(13,23,33,43),(16,26,36,46),(19,29,39,49); + } + 3 { + CREATE TABLE t1a(a INT, b INT, c INT, d INT); + CREATE TABLE t2a(c INT, d INT, e INT, f INT); + CREATE TABLE t3a(a INT, b INT, e INT, f INT); + CREATE TABLE t4a(a INT, c INT, d INT, f INT); + INSERT INTO t1a VALUES(11,21,31,41),(12,22,32,42); + INSERT INTO t2a VALUES(12,22,32,42),(13,23,33,43); + INSERT INTO t3a VALUES(14,24,34,44),(15,25,35,45); + INSERT INTO t4a VALUES(11,21,31,41),(13,23,33,43); + CREATE TABLE t1b(a INT, b INT, c INT, d INT); + CREATE TABLE t2b(c INT, d INT, e INT, f INT); + CREATE TABLE t3b(a INT, b INT, e INT, f INT); + CREATE TABLE t4b(a INT, c INT, d INT, f INT); + INSERT INTO t1b VALUES(15,25,35,45),(18,28,38,48); + INSERT INTO t2b VALUES(15,25,35,45),(17,27,37,47); + INSERT INTO t3b VALUES(15,25,35,45),(16,26,36,46); + INSERT INTO t4b VALUES(16,26,36,46),(19,29,39,49); + CREATE VIEW t1 AS SELECT * FROM t1a UNION SELECT * FROM t1b; + CREATE VIEW t2 AS SELECT * FROM t2a UNION SELECT * FROM t2b; + CREATE VIEW t3 AS SELECT * FROM t3a UNION SELECT * FROM t3b; + CREATE VIEW t4 AS SELECT * FROM t4a UNION SELECT * FROM t4b; + } +} { + reset_db + db nullvalue - + do_execsql_test joinA-$id.setup $schema {} + + # Verified by PG-14 + do_execsql_test joinA-$id.100 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + INNER JOIN t2 USING(c,d) + INNER JOIN t3 USING(a,b,f) + INNER JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } {} + + + # Verified by PG-14 + do_execsql_test joinA-$id.110 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + LEFT JOIN t2 USING(c,d) + LEFT JOIN t3 USING(a,b,f) + LEFT JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + 11 21 31 41 - - - + 12 22 32 42 - - - + 15 25 35 45 - - - + 18 28 38 48 - - - + } + + # Verified by PG-14 + do_execsql_test joinA-$id.120 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + LEFT JOIN t2 USING(c,d) + RIGHT JOIN t3 USING(a,b,f) + LEFT JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + 14 24 - - - 44 34 + 15 25 - - - 45 35 + 16 26 - - - 46 36 + } + + # Verified by PG-14 + do_execsql_test joinA-$id.130 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + RIGHT JOIN t2 USING(c,d) + LEFT JOIN t3 USING(a,b,f) + RIGHT JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + 11 - 21 31 - 41 - + 13 - 23 33 - 43 - + 16 - 26 36 - 46 - + 19 - 29 39 - 49 - + } + + # Verified by PG-14 + do_execsql_test joinA-$id.140 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + FULL JOIN t2 USING(c,d) + LEFT JOIN t3 USING(a,b,f) + RIGHT JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + 11 - 21 31 - 41 - + 13 - 23 33 - 43 - + 16 - 26 36 - 46 - + 19 - 29 39 - 49 - + } + + # Verified by PG-14 + do_execsql_test joinA-$id.150 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + RIGHT JOIN t2 USING(c,d) + FULL JOIN t3 USING(a,b,f) + RIGHT JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + 11 - 21 31 - 41 - + 13 - 23 33 - 43 - + 16 - 26 36 - 46 - + 19 - 29 39 - 49 - + } + + # Verified by PG-14 + do_execsql_test joinA-$id.160 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + RIGHT JOIN t2 USING(c,d) + LEFT JOIN t3 USING(a,b,f) + FULL JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + - - 12 22 32 42 - + - - 13 23 33 43 - + - - 15 25 35 45 - + - - 17 27 37 47 - + 11 - 21 31 - 41 - + 13 - 23 33 - 43 - + 16 - 26 36 - 46 - + 19 - 29 39 - 49 - + } + + # Verified by PG-14 + do_execsql_test joinA-$id.170 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + LEFT JOIN t2 USING(c,d) + RIGHT JOIN t3 USING(a,b,f) + FULL JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + 11 - 21 31 - 41 - + 13 - 23 33 - 43 - + 14 24 - - - 44 34 + 15 25 - - - 45 35 + 16 26 - - - 46 36 + 16 - 26 36 - 46 - + 19 - 29 39 - 49 - + } + + # Verified by PG-14 + do_execsql_test joinA-$id.200 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + FULL JOIN t2 USING(c,d) + FULL JOIN t3 USING(a,b,f) + FULL JOIN t4 USING(a,c,d,f) + ORDER BY 1 nulls first, 3 nulls first; + } { + - - 12 22 32 42 - + - - 13 23 33 43 - + - - 15 25 35 45 - + - - 17 27 37 47 - + 11 - 21 31 - 41 - + 11 21 31 41 - - - + 12 22 32 42 - - - + 13 - 23 33 - 43 - + 14 24 - - - 44 34 + 15 25 - - - 45 35 + 15 25 35 45 - - - + 16 26 - - - 46 36 + 16 - 26 36 - 46 - + 18 28 38 48 - - - + 19 - 29 39 - 49 - + } +} +finish_test diff --git a/test/joinB.test b/test/joinB.test new file mode 100644 index 000000000..baaeae677 --- /dev/null +++ b/test/joinB.test @@ -0,0 +1,7252 @@ +set testdir [file dirname $argv0] +# 2022-04-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. +# +#*********************************************************************** +# +# This file implements tests for JOINs. +# +# The test case output is all generated by PostgreSQL 14. This test module +# was created as follows: +# +# 1. Run a TCL script (included at the bottom of this file) that +# generates an input script for "psql" that will run man +# diverse tests on joins. +# +# 2. Run the script from step (1) through psql and collect the +# output. +# +# 3. Make a few minor global search-and-replace operations to convert +# the psql output into a form suitable for this test module. +# +# 4. Add this header, and the script content at the footer. +# +source $testdir/tester.tcl +db nullvalue - +db eval { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + DROP TABLE IF EXISTS t3; + DROP TABLE IF EXISTS t4; + DROP TABLE IF EXISTS t5; + CREATE TABLE t1(a INT, b INT, c INT); + CREATE TABLE t2(a INT, b INT, d INT); + CREATE TABLE t3(a INT, b INT, e INT); + CREATE TABLE t4(a INT, b INT, f INT); + CREATE TABLE t5(a INT, b INT, g INT); + INSERT INTO t1 VALUES(11,21,31),(12,22,32),(15,25,35),(17,27,37); + INSERT INTO t2 VALUES(12,22,32),(13,23,33),(15,25,35),(18,28,38), + (NULL,NULL,36); + INSERT INTO t4 VALUES(11,21,31),(13,23,33),(15,25,35),(19,29,39); + INSERT INTO t3 SELECT * FROM t1 UNION SELECT * FROM t2 UNION SELECT * FROM t4; + INSERT INTO t5 SELECT * FROM t3 WHERE a>=15; +} +do_execsql_test joinB-1 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-2 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-3 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-4 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-5 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-6 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-7 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-8 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL INNER JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-9 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-10 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-11 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 +} +do_execsql_test joinB-12 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 12 32 32 32 - - + 15 35 35 35 35 35 +} +do_execsql_test joinB-13 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-14 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-15 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-16 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL INNER JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-17 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-18 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-19 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-20 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-21 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-22 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-23 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-24 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL INNER JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-25 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-26 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-27 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-28 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-29 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-30 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-31 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-32 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL INNER JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-33 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-34 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-35 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-36 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-37 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-38 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-39 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-40 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-41 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-42 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-43 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 +} +do_execsql_test joinB-44 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 12 32 32 32 - - + 15 35 35 35 35 35 +} +do_execsql_test joinB-45 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-46 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-47 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-48 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-49 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-50 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-51 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-52 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-53 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-54 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-55 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-56 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-57 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-58 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-59 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-60 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-61 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-62 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-63 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-64 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-65 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-66 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-67 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-68 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-69 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-70 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-71 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-72 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-73 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-74 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-75 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-76 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-77 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-78 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-79 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-80 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-81 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-82 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-83 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-84 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-85 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-86 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-87 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-88 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-89 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-90 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-91 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-92 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-93 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-94 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-95 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-96 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-97 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-98 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-99 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-100 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-101 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-102 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-103 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-104 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL FULL JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-105 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-106 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-107 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-108 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-109 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-110 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-111 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-112 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL FULL JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-113 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-114 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-115 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-116 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-117 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-118 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-119 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-120 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL FULL JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-121 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-122 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-123 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-124 { + SELECT a, c, d, e, f, g + FROM t1 + INNER JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-125 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-126 { + SELECT a, b, c, d, e, f, g + FROM t1 + INNER JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-127 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + INNER JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-128 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL INNER JOIN t2 + NATURAL FULL JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-129 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-130 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-131 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 +} +do_execsql_test joinB-132 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-133 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-134 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-135 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-136 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-137 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 +} +do_execsql_test joinB-138 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 +} +do_execsql_test joinB-139 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 +} +do_execsql_test joinB-140 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 15 35 35 35 35 35 + 17 37 - - - - +} +do_execsql_test joinB-141 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-142 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-143 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-144 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-145 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-146 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-147 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-148 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-149 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-150 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-151 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-152 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-153 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 19 - - - 19 19 +} +do_execsql_test joinB-154 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 19 - - - 39 39 +} +do_execsql_test joinB-155 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 19 - - - 19 19 +} +do_execsql_test joinB-156 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 + 17 37 - - - - +} +do_execsql_test joinB-157 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-158 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-159 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-160 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-161 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-162 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-163 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 +} +do_execsql_test joinB-164 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-165 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-166 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-167 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-168 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-169 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 +} +do_execsql_test joinB-170 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 +} +do_execsql_test joinB-171 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 +} +do_execsql_test joinB-172 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 15 35 35 35 35 35 + 17 37 - - - - +} +do_execsql_test joinB-173 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-174 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-175 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-176 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-177 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-178 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-179 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-180 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-181 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-182 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-183 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-184 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-185 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 19 - - - 19 19 +} +do_execsql_test joinB-186 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 19 - - - 39 39 +} +do_execsql_test joinB-187 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 19 - - - 19 19 +} +do_execsql_test joinB-188 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 + 17 37 - - - - +} +do_execsql_test joinB-189 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-190 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-191 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - - 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-192 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-193 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-194 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-195 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-196 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-197 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-198 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-199 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-200 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-201 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-202 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-203 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-204 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-205 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-206 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-207 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-208 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-209 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-210 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-211 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-212 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-213 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-214 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-215 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-216 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-217 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-218 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-219 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-220 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-221 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-222 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-223 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-224 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-225 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-226 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-227 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-228 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-229 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-230 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-231 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-232 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-233 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-234 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-235 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-236 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-237 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-238 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-239 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-240 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-241 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-242 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-243 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-244 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - - - 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-245 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-246 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-247 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-248 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-249 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-250 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - - 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-251 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-252 { + SELECT a, c, d, e, f, g + FROM t1 + LEFT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - - - 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - +} +do_execsql_test joinB-253 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-254 { + SELECT a, b, c, d, e, f, g + FROM t1 + LEFT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - - 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-255 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + LEFT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - - 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - - 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-256 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL LEFT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-257 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-258 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-259 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 +} +do_execsql_test joinB-260 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-261 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-262 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-263 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-264 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-265 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 18 - 18 18 - 18 +} +do_execsql_test joinB-266 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 18 - 38 38 - 38 +} +do_execsql_test joinB-267 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 +} +do_execsql_test joinB-268 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 18 - 38 38 - - +} +do_execsql_test joinB-269 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-270 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - 38 38 - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-271 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-272 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-273 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-274 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-275 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-276 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-277 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-278 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-279 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-280 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-281 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-282 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 18 - 38 38 - 38 + 19 - - - 39 39 +} +do_execsql_test joinB-283 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-284 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 18 - 38 38 - - +} +do_execsql_test joinB-285 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-286 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - 38 38 - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-287 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-288 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL INNER JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-289 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-290 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-291 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 +} +do_execsql_test joinB-292 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-293 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-294 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-295 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-296 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-297 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 18 - 18 18 - 18 +} +do_execsql_test joinB-298 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 18 - 38 38 - 38 +} +do_execsql_test joinB-299 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 +} +do_execsql_test joinB-300 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 18 - 38 38 - - +} +do_execsql_test joinB-301 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-302 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - 38 38 - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-303 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-304 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-305 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-306 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-307 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-308 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-309 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-310 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-311 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-312 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-313 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-314 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 18 - 38 38 - 38 + 19 - - - 39 39 +} +do_execsql_test joinB-315 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - - 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-316 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 - - - 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 18 - 38 38 - - +} +do_execsql_test joinB-317 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-318 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - 38 38 - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-319 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - - 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-320 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-321 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-322 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-323 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-324 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-325 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-326 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-327 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-328 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-329 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-330 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-331 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-332 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-333 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-334 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-335 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-336 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-337 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-338 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-339 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-340 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-341 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-342 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-343 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-344 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-345 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-346 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-347 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-348 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-349 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-350 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-351 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-352 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-353 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-354 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-355 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-356 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-357 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-358 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-359 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-360 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-361 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-362 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-363 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-364 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-365 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-366 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-367 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-368 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-369 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-370 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-371 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-372 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-373 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-374 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-375 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 - - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-376 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-377 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-378 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 - - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-379 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-380 { + SELECT a, c, d, e, f, g + FROM t1 + RIGHT JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-381 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-382 { + SELECT a, b, c, d, e, f, g + FROM t1 + RIGHT JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-383 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + RIGHT JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 - - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-384 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL RIGHT JOIN t2 + NATURAL FULL JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-385 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-386 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-387 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 +} +do_execsql_test joinB-388 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-389 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-390 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-391 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-392 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL INNER JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-393 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 +} +do_execsql_test joinB-394 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 +} +do_execsql_test joinB-395 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 +} +do_execsql_test joinB-396 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - - - - + 18 - 38 38 - - +} +do_execsql_test joinB-397 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-398 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-399 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-400 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL INNER JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-401 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-402 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-403 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-404 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-405 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-406 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-407 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-408 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL INNER JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-409 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-410 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 + 19 - - - 39 39 +} +do_execsql_test joinB-411 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-412 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 INNER JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - - - - + 18 - 38 38 - - +} +do_execsql_test joinB-413 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-414 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + INNER JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-415 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + INNER JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-416 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL INNER JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-417 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 +} +do_execsql_test joinB-418 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 +} +do_execsql_test joinB-419 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 +} +do_execsql_test joinB-420 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-421 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-422 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-423 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - - 19 +} +do_execsql_test joinB-424 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-425 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 +} +do_execsql_test joinB-426 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 +} +do_execsql_test joinB-427 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 +} +do_execsql_test joinB-428 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - - - - + 18 - 38 38 - - +} +do_execsql_test joinB-429 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-430 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - - - 39 +} +do_execsql_test joinB-431 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - - 19 +} +do_execsql_test joinB-432 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-433 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-434 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - - 39 39 +} +do_execsql_test joinB-435 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - - 19 19 +} +do_execsql_test joinB-436 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-437 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-438 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-439 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-440 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-441 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-442 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 + 19 - - - 39 39 +} +do_execsql_test joinB-443 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-444 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 LEFT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - - 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - - - - + 18 - 38 38 - - +} +do_execsql_test joinB-445 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-446 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + LEFT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - - 39 39 +} +do_execsql_test joinB-447 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + LEFT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - - 19 19 +} +do_execsql_test joinB-448 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL LEFT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-449 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-450 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-451 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-452 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-453 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-454 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-455 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-456 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-457 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-458 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-459 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-460 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-461 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-462 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-463 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-464 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-465 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-466 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-467 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-468 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-469 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-470 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-471 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-472 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-473 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-474 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-475 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-476 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 RIGHT JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-477 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-478 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + RIGHT JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-479 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + RIGHT JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-480 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL RIGHT JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-481 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-482 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-483 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-484 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + INNER JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-485 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-486 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + INNER JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-487 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + INNER JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-488 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL FULL JOIN t3 + NATURAL INNER JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-489 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-490 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-491 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-492 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + LEFT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-493 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-494 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + LEFT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-495 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + LEFT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-496 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL FULL JOIN t3 + NATURAL LEFT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-497 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-498 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 19 - - 39 39 39 +} +do_execsql_test joinB-499 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 +} +do_execsql_test joinB-500 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + RIGHT JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 13 - 33 33 33 - + 15 35 35 35 35 35 +} +do_execsql_test joinB-501 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-502 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + RIGHT JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 - - - - 37 + 18 28 - - - - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-503 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + RIGHT JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 - - - - 17 + 18 - - - - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-504 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL FULL JOIN t3 + NATURAL RIGHT JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +do_execsql_test joinB-505 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + INNER JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-506 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + INNER JOIN t5 USING(a,b) + WHERE a<>13 + ORDER BY 1 NULLS FIRST; +} { + 15 35 35 35 35 35 + 17 37 - 37 - 37 + 18 - 38 38 - 38 + 19 - - 39 39 39 +} +do_execsql_test joinB-507 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + LEFT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-508 { + SELECT a, c, d, e, f, g + FROM t1 + FULL JOIN (t2 FULL JOIN t3 USING(a)) USING(a) + FULL JOIN (t4 LEFT JOIN t5 USING(a)) USING(a) + WHERE a<=18 + ORDER BY 1 NULLS FIRST; +} { + 11 31 - 31 31 - + 12 32 32 32 - - + 13 - 33 33 33 - + 15 35 35 35 35 35 + 17 37 - 37 - - + 18 - 38 38 - - +} +do_execsql_test joinB-509 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + RIGHT JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-510 { + SELECT a, b, c, d, e, f, g + FROM t1 + FULL JOIN t2 USING(a,b) + FULL JOIN t3 USING(a,b) + FULL JOIN t4 USING(a,b) + RIGHT JOIN t5 USING(a,b) + WHERE d<>33 OR d IS NULL + ORDER BY 1 NULLS FIRST; +} { + 15 25 35 35 35 35 35 + 17 27 37 - 37 - 37 + 18 28 - 38 38 - 38 + 19 29 - - 39 39 39 +} +do_execsql_test joinB-511 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 + FULL JOIN t2 USING(a) + FULL JOIN t3 USING(a) + FULL JOIN t4 USING(a) + FULL JOIN t5 USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 +} +do_execsql_test joinB-512 { + SELECT b, c, d, e, f, g + FROM t1 + NATURAL FULL JOIN t2 + NATURAL FULL JOIN t3 + NATURAL FULL JOIN t4 + NATURAL FULL JOIN t5 + WHERE b BETWEEN 12 AND 17 + ORDER BY 1 NULLS FIRST; +} { +} +finish_test + +############################################################################## +# Here is the original TCL script that generated the psql input file: +# +# +# puts " +# \\pset border off +# \\pset tuples_only on +# \\pset null - +# +# DROP TABLE IF EXISTS t1; +# DROP TABLE IF EXISTS t2; +# DROP TABLE IF EXISTS t3; +# DROP TABLE IF EXISTS t4; +# DROP TABLE IF EXISTS t5; +# CREATE TABLE t1(a INT, b INT, c INT); +# CREATE TABLE t2(a INT, b INT, d INT); +# CREATE TABLE t3(a INT, b INT, e INT); +# CREATE TABLE t4(a INT, b INT, f INT); +# CREATE TABLE t5(a INT, b INT, g INT); +# INSERT INTO t1 VALUES(11,21,31),(12,22,32),(15,25,35),(17,27,37); +# INSERT INTO t2 VALUES(12,22,32),(13,23,33),(15,25,35),(18,28,38),(NULL,NULL,36); +# INSERT INTO t4 VALUES(11,21,31),(13,23,33),(15,25,35),(19,29,39); +# INSERT INTO t3 SELECT * FROM t1 UNION SELECT * FROM t2 UNION SELECT * FROM t4; +# INSERT INTO t5 SELECT * FROM t3 WHERE a>=15; +# " +# +# proc echo {prefix txt} { +# regsub -all {\n} $txt \n$prefix txt +# puts "$prefix$txt" +# } +# +# set n 0 +# set k 0 +# foreach j1 {INNER LEFT RIGHT FULL} { +# foreach j2 {INNER LEFT RIGHT FULL} { +# foreach j3 {INNER LEFT RIGHT FULL} { +# foreach j4 {INNER LEFT RIGHT FULL} { +# +# incr n +# incr k +# set q1 "" +# append q1 "SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a\n" +# append q1 " FROM t1\n" +# append q1 " $j1 JOIN t2 USING(a)\n" +# append q1 " $j2 JOIN t3 USING(a)\n" +# append q1 " $j3 JOIN t4 USING(a)\n" +# append q1 " $j4 JOIN t5 USING(a)\n" +# append q1 " ORDER BY 1 NULLS FIRST;" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# switch [expr {$k%4}] { +# 0 { +# set q2 "" +# append q2 "SELECT b, c, d, e, f, g\n" +# append q2 " FROM t1\n" +# append q2 " NATURAL $j1 JOIN t2\n" +# append q2 " NATURAL $j2 JOIN t3\n" +# append q2 " NATURAL $j3 JOIN t4\n" +# append q2 " NATURAL $j4 JOIN t5\n" +# append q2 " WHERE b BETWEEN 12 AND 17\n" +# append q2 " ORDER BY 1 NULLS FIRST;" +# incr n +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q2 +# echo "\\qecho " "\} \{" +# puts $q2 +# echo "\\qecho " "\}" +# } +# 1 { +# set q2 "" +# append q2 "SELECT a, c, d, e, f, g\n" +# append q2 " FROM t1\n" +# append q2 " $j1 JOIN t2 USING(a,b)\n" +# append q2 " $j2 JOIN t3 USING(a,b)\n" +# append q2 " $j3 JOIN t4 USING(a,b)\n" +# append q2 " $j4 JOIN t5 USING(a,b)\n" +# append q2 " WHERE a<>13\n" +# append q2 " ORDER BY 1 NULLS FIRST;" +# incr n +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q2 +# echo "\\qecho " "\} \{" +# puts $q2 +# echo "\\qecho " "\}" +# } +# 2 { +# set q2 "" +# append q2 "SELECT a, c, d, e, f, g\n" +# append q2 " FROM t1\n" +# append q2 " $j1 JOIN (t2 $j2 JOIN t3 USING(a)) USING(a)\n" +# append q2 " $j3 JOIN (t4 $j4 JOIN t5 USING(a)) USING(a)\n" +# append q2 " WHERE a<=18\n" +# append q2 " ORDER BY 1 NULLS FIRST;" +# incr n +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q2 +# echo "\\qecho " "\} \{" +# puts $q2 +# echo "\\qecho " "\}" +# } +# 3 { +# set q2 "" +# append q2 "SELECT a, b, c, d, e, f, g\n" +# append q2 " FROM t1\n" +# append q2 " $j1 JOIN t2 USING(a,b)\n" +# append q2 " $j2 JOIN t3 USING(a,b)\n" +# append q2 " $j3 JOIN t4 USING(a,b)\n" +# append q2 " $j4 JOIN t5 USING(a,b)\n" +# append q2 " WHERE d<>33 OR d IS NULL\n" +# append q2 " ORDER BY 1 NULLS FIRST;" +# incr n +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q2 +# echo "\\qecho " "\} \{" +# puts $q2 +# echo "\\qecho " "\}" +# } +# } +# +# } +# } +# } +# } +############################################################################## diff --git a/test/joinC.test b/test/joinC.test new file mode 100644 index 000000000..a6f939585 --- /dev/null +++ b/test/joinC.test @@ -0,0 +1,4594 @@ +# 2022-04-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. +# +#*********************************************************************** +# +# This file implements tests for JOINs. +# +# The test case output is all generated by PostgreSQL 14. This test module +# was created as follows: +# +# 1. Run a TCL script (included at the bottom of this file) that +# generates an input script for "psql" that will run man +# diverse tests on joins. +# +# 2. Run the script from step (1) through psql and collect the +# output. +# +# 3. Make a few minor global search-and-replace operations to convert +# the psql output into a form suitable for this test module. +# +# 4. Add this header, and the script content at the footer. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +db nullvalue - +db eval { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + DROP TABLE IF EXISTS t3; + DROP TABLE IF EXISTS t4; + DROP TABLE IF EXISTS t5; + CREATE TABLE t1(a INT, b INT, c INT); + CREATE TABLE t2(a INT, b INT, d INT); + CREATE TABLE t3(a INT, b INT, e INT); + CREATE TABLE t4(a INT, b INT, f INT); + CREATE TABLE t5(a INT, b INT, g INT); + INSERT INTO t1 VALUES(11,21,31),(12,22,32),(15,25,35),(17,27,37); + INSERT INTO t2 VALUES(12,22,32),(13,23,33),(15,25,35),(18,28,38), + (NULL,NULL,36); + INSERT INTO t4 VALUES(11,21,31),(13,23,33),(15,25,35),(19,29,39); + INSERT INTO t3 SELECT * FROM t1 UNION SELECT * FROM t2 UNION SELECT * FROM t4; + INSERT INTO t5 SELECT * FROM t3 WHERE a>=15; +} +do_execsql_test joinC-1 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-2 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-3 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-4 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-5 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-6 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-7 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-8 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-9 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-10 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-11 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-12 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-13 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-14 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-15 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-16 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-17 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-18 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-19 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-20 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-21 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-22 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-23 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-24 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-25 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-26 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-27 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-28 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-29 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-30 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-31 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-32 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-33 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-34 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-35 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-36 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-37 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-38 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-39 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-40 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-41 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-42 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-43 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-44 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-45 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-46 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-47 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-48 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-49 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-50 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-51 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-52 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-53 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-54 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-55 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-56 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-57 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-58 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-59 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-60 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-61 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-62 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-63 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-64 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 INNER JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-65 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-66 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-67 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-68 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-69 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-70 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-71 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-72 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-73 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-74 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-75 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-76 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-77 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-78 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-79 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-80 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-81 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-82 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-83 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-84 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-85 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-86 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-87 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-88 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-89 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-90 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-91 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-92 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-93 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-94 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-95 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-96 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-97 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-98 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-99 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-100 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-101 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-102 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-103 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-104 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-105 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-106 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-107 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-108 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-109 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-110 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-111 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-112 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-113 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-114 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-115 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-116 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-117 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-118 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-119 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-120 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-121 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-122 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-123 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-124 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-125 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-126 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + +} +do_execsql_test joinC-127 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 - - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-128 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 LEFT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 12 12 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + +} +do_execsql_test joinC-129 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-130 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-131 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-132 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-133 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-134 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-135 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-136 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-137 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + +} +do_execsql_test joinC-138 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 + +} +do_execsql_test joinC-139 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-140 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-141 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-142 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-143 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-144 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-145 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 18 - 18 - - - + +} +do_execsql_test joinC-146 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 - - - + +} +do_execsql_test joinC-147 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-148 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-149 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-150 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-151 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-152 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-153 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 18 - 18 - - - + +} +do_execsql_test joinC-154 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 - - - + +} +do_execsql_test joinC-155 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-156 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-157 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-158 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - - + +} +do_execsql_test joinC-159 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-160 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 18 - 18 + +} +do_execsql_test joinC-161 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 + +} +do_execsql_test joinC-162 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 + +} +do_execsql_test joinC-163 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-164 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-165 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-166 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-167 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-168 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-169 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 19 - - 19 19 19 + +} +do_execsql_test joinC-170 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 19 - - 19 19 19 + +} +do_execsql_test joinC-171 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-172 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-173 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-174 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-175 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-176 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-177 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-178 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-179 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-180 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-181 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-182 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-183 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-184 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-185 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-186 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-187 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-188 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-189 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-190 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-191 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-192 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 RIGHT JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-193 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-194 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-195 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-196 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-197 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-198 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-199 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-200 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-201 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-202 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + +} +do_execsql_test joinC-203 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-204 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-205 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-206 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-207 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-208 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 INNER JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-209 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + +} +do_execsql_test joinC-210 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + +} +do_execsql_test joinC-211 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-212 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-213 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-214 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-215 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-216 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-217 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + +} +do_execsql_test joinC-218 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + +} +do_execsql_test joinC-219 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-220 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-221 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-222 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - - + +} +do_execsql_test joinC-223 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-224 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 LEFT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 18 - 18 + +} +do_execsql_test joinC-225 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-226 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-227 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-228 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-229 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-230 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-231 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-232 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-233 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-234 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-235 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - - - - + 12 12 - - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-236 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + 11 11 - 11 11 - + 12 12 - - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-237 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-238 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-239 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-240 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 RIGHT JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-241 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-242 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-243 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-244 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 INNER JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-245 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-246 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-247 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-248 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 LEFT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-249 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-250 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - - - - + 18 - 18 - - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-251 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - - - - + 12 12 12 - - - + 13 - 13 - - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-252 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 RIGHT JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + 11 11 - 11 11 - + 12 12 12 - - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-253 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 INNER JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-254 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 LEFT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - - + 18 - 18 18 - - + 19 - - 19 19 19 + +} +do_execsql_test joinC-255 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 RIGHT JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 - - + 12 12 12 12 - - + 13 - 13 13 - - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +do_execsql_test joinC-256 { + SELECT a, t1.a, t2.a, t3.a, t4.a, t5.a + FROM t1 FULL JOIN ( + t2 FULL JOIN ( + t3 FULL JOIN ( + t4 FULL JOIN t5 USING(a) + ) USING(a) + ) USING(a) + ) USING(a) + ORDER BY 1 NULLS FIRST; +} { + - - - - - - + - - - - - - + 11 11 - 11 11 - + 12 12 12 12 - - + 13 - 13 13 13 - + 15 15 15 15 15 15 + 17 17 - 17 - 17 + 18 - 18 18 - 18 + 19 - - 19 19 19 + +} +finish_test diff --git a/test/joinD.test b/test/joinD.test new file mode 100644 index 000000000..c520d6a28 --- /dev/null +++ b/test/joinD.test @@ -0,0 +1,35134 @@ +# 2022-05-04 +# revised 2022-05-31 +# +# 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 for JOINs that use Bloom filters. +# +# The test case output is (mostly) all generated by PostgreSQL 14. This +# test module was created as follows: +# +# 1. Run a TCL script (included at the bottom of this file) that +# generates an input script for "psql" that will run man +# diverse tests on joins. +# +# 2. Run the script from step (1) through psql and collect the +# output. +# +# 3. Make a few minor global search-and-replace operations to convert +# the psql output into a form suitable for this test module. +# +# 4. Add this header, and the script content at the footer. +# +# A few extra tests that were not generated from postgresql output are +# added at the end. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +db nullvalue - +db eval { + CREATE TABLE t1(a INT, b INT, c INT, d INT); + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<95) + INSERT INTO t1(a,b,c,d) SELECT x, x+100, x+200, x+300 FROM c; + CREATE TABLE t2(b INT, x INT); + INSERT INTO t2(b,x) SELECT b, a FROM t1 WHERE a%2=0; + CREATE INDEX t2b ON t2(b); + CREATE TABLE t3(c INT, y INT); + INSERT INTO t3(c,y) SELECT c, a FROM t1 WHERE a%3=0; + CREATE INDEX t3c ON t3(c); + CREATE TABLE t4(d INT, z INT); + INSERT INTO t4(d,z) SELECT d, a FROM t1 WHERE a%5=0; + CREATE INDEX t4d ON t4(d); + INSERT INTO t1(a,b,c,d) VALUES + (96,NULL,296,396), + (97,197,NULL,397), + (98,198,298,NULL), + (99,NULL,NULL,NULL); + ANALYZE; +} +do_execsql_test joinD-1 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-2 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-3 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-4 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-5 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-6 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-7 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-8 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-9 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-10 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-11 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-12 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-13 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-14 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-15 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-16 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-17 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-18 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-19 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-20 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-21 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-22 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-23 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-24 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-25 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-26 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-27 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-28 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-29 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-30 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-31 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-32 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-33 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-34 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-35 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-36 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-37 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-38 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-39 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-40 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-41 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-42 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-43 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-44 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-45 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-46 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-47 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-48 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-49 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-50 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-51 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-52 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-53 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-54 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-55 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-56 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-57 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-58 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-59 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-60 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-61 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-62 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-63 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-64 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-65 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-66 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-67 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-68 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-69 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-70 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-71 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-72 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-73 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-74 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-75 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-76 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-77 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-78 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-79 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-80 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-81 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-82 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-83 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-84 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-85 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-86 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-87 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-88 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-89 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-90 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-91 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-92 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-93 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-94 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-95 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-96 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-97 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-98 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-99 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-100 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-101 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-102 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-103 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-104 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-105 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-106 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-107 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-108 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-109 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-110 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-111 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-112 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-113 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-114 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-115 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-116 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-117 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-118 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-119 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-120 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-121 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-122 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-123 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-124 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-125 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-126 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-127 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-128 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-129 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-130 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-131 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-132 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-133 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-134 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-135 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-136 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-137 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-138 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-139 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-140 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-141 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-142 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-143 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-144 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-145 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-146 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-147 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-148 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-149 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-150 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-151 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-152 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-153 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-154 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-155 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-156 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-157 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-158 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-159 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-160 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-161 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-162 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-163 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-164 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-165 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-166 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-167 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-168 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-169 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-170 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-171 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-172 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-173 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-174 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-175 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-176 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-177 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-178 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-179 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-180 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-181 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-182 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-183 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-184 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-185 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-186 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - +} +do_execsql_test joinD-187 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-188 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-189 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-190 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-191 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-192 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-193 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-194 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-195 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-196 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-197 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-198 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-199 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-200 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-201 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-202 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-203 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-204 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-205 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-206 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-207 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-208 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-209 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-210 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-211 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-212 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-213 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-214 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-215 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-216 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-217 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-218 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-219 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-220 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-221 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-222 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-223 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-224 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-225 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-226 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-227 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-228 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-229 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-230 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-231 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-232 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-233 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-234 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-235 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-236 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-237 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-238 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-239 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-240 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-241 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-242 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-243 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-244 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-245 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-246 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-247 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-248 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-249 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-250 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-251 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-252 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-253 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-254 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-255 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-256 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-257 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-258 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-259 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-260 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-261 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-262 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-263 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-264 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-265 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-266 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-267 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-268 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-269 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-270 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-271 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-272 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-273 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-274 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-275 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-276 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-277 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-278 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-279 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-280 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-281 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-282 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-283 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-284 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-285 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-286 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-287 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-288 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-289 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-290 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-291 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-292 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-293 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-294 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-295 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-296 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-297 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-298 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-299 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-300 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 INNER JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-301 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-302 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-303 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-304 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-305 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-306 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-307 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-308 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-309 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-310 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-311 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-312 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-313 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-314 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-315 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-316 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-317 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-318 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-319 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-320 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-321 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-322 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-323 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-324 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-325 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-326 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-327 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-328 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-329 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-330 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-331 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-332 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-333 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-334 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-335 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-336 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-337 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-338 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-339 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-340 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-341 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-342 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-343 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-344 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-345 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-346 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-347 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-348 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-349 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-350 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-351 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-352 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-353 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-354 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-355 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-356 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-357 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-358 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-359 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-360 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-361 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-362 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-363 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-364 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-365 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-366 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-367 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-368 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-369 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-370 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-371 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-372 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-373 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-374 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-375 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-376 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-377 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-378 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-379 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-380 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-381 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-382 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-383 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-384 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-385 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-386 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-387 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-388 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-389 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-390 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-391 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-392 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-393 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-394 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-395 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-396 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-397 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-398 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-399 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-400 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-401 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-402 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-403 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-404 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-405 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-406 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-407 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-408 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-409 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-410 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-411 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-412 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-413 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-414 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-415 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-416 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-417 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-418 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-419 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-420 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-421 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-422 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-423 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-424 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-425 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-426 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-427 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-428 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-429 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-430 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-431 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-432 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-433 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-434 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-435 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-436 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-437 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-438 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-439 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-440 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-441 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-442 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-443 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-444 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-445 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-446 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-447 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-448 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-449 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-450 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-451 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-452 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-453 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-454 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-455 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-456 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-457 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-458 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-459 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-460 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-461 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-462 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-463 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-464 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-465 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-466 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-467 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-468 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-469 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-470 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-471 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-472 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-473 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-474 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-475 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-476 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-477 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-478 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-479 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-480 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-481 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-482 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-483 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-484 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-485 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-486 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - +} +do_execsql_test joinD-487 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-488 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-489 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-490 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-491 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-492 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-493 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-494 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-495 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-496 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-497 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-498 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-499 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-500 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-501 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-502 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-503 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-504 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-505 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-506 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-507 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-508 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-509 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-510 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-511 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-512 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-513 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-514 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-515 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-516 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-517 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-518 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-519 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-520 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-521 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-522 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-523 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-524 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-525 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-526 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-527 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-528 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-529 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-530 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-531 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-532 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-533 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-534 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-535 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-536 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-537 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-538 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-539 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-540 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-541 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-542 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-543 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-544 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-545 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-546 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-547 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-548 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-549 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-550 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-551 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-552 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-553 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-554 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-555 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-556 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-557 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-558 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-559 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-560 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-561 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-562 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-563 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-564 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-565 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-566 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-567 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-568 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-569 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-570 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-571 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-572 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-573 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-574 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-575 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-576 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-577 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-578 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-579 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-580 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-581 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-582 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-583 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-584 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-585 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-586 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-587 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-588 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-589 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-590 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-591 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-592 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-593 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-594 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-595 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-596 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-597 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-598 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-599 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-600 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 LEFT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-601 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-602 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-603 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-604 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-605 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-606 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-607 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-608 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-609 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-610 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-611 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-612 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-613 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-614 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-615 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-616 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-617 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-618 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-619 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-620 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-621 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-622 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-623 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-624 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-625 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-626 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-627 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-628 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-629 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-630 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-631 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-632 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-633 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-634 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-635 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-636 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-637 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-638 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-639 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-640 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-641 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-642 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-643 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-644 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-645 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-646 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-647 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-648 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-649 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-650 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-651 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-652 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-653 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-654 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-655 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-656 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-657 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-658 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-659 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-660 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-661 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-662 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-663 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-664 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-665 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-666 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-667 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-668 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-669 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-670 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-671 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-672 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-673 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-674 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-675 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-676 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-677 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-678 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-679 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-680 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-681 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-682 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-683 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-684 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-685 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-686 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-687 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-688 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-689 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-690 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-691 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-692 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-693 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-694 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-695 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-696 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-697 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-698 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-699 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-700 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-701 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-702 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-703 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-704 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-705 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-706 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-707 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-708 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-709 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-710 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-711 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-712 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-713 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-714 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-715 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-716 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-717 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-718 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-719 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-720 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-721 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-722 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-723 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-724 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-725 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-726 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-727 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-728 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-729 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-730 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-731 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-732 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-733 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-734 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-735 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-736 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-737 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-738 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-739 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-740 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-741 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-742 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-743 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-744 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-745 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-746 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-747 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-748 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-749 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-750 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-751 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-752 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-753 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-754 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-755 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-756 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-757 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-758 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-759 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-760 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-761 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-762 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-763 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-764 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-765 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-766 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-767 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-768 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-769 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-770 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-771 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-772 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-773 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-774 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-775 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-776 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-777 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-778 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-779 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-780 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-781 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-782 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-783 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-784 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-785 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-786 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - +} +do_execsql_test joinD-787 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-788 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-789 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-790 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-791 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-792 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-793 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-794 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-795 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-796 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-797 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-798 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-799 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-800 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-801 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-802 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-803 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-804 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-805 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-806 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-807 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-808 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-809 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-810 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-811 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-812 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-813 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-814 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-815 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-816 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-817 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-818 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-819 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-820 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-821 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-822 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-823 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-824 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-825 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-826 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-827 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-828 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 315 15 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 345 45 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 375 75 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-829 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-830 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-831 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-832 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-833 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-834 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-835 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-836 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-837 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-838 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-839 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-840 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-841 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-842 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-843 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-844 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-845 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-846 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-847 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-848 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-849 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-850 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-851 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-852 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-853 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-854 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-855 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-856 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-857 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-858 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-859 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-860 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-861 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-862 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-863 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-864 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-865 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-866 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-867 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-868 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-869 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-870 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-871 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-872 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-873 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-874 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-875 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-876 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-877 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-878 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-879 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 +} +do_execsql_test joinD-880 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-881 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-882 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-883 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-884 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-885 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-886 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-887 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-888 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - +} +do_execsql_test joinD-889 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-890 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-891 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-892 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-893 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-894 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-895 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-896 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-897 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-898 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-899 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b IS NOT DISTINCT FROM t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-900 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 RIGHT JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - 203 3 - - + - - - - - - 209 9 - - + - - - - - - 215 15 - - + - - - - - - 221 21 - - + - - - - - - 227 27 - - + - - - - - - 233 33 - - + - - - - - - 239 39 - - + - - - - - - 245 45 - - + - - - - - - 251 51 - - + - - - - - - 257 57 - - + - - - - - - 263 63 - - + - - - - - - 269 69 - - + - - - - - - 275 75 - - + - - - - - - 281 81 - - + - - - - - - 287 87 - - + - - - - - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 315 15 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 345 45 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 375 75 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-901 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-902 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-903 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-904 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-905 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-906 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-907 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-908 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-909 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-910 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-911 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-912 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-913 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-914 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-915 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-916 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-917 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-918 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-919 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-920 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-921 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-922 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-923 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-924 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-925 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-926 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-927 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-928 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-929 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-930 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-931 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-932 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-933 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-934 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-935 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-936 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-937 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-938 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-939 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-940 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-941 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-942 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-943 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-944 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-945 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-946 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-947 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-948 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-949 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-950 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-951 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-952 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-953 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-954 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-955 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-956 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-957 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-958 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-959 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-960 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-961 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-962 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-963 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + INNER JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-964 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-965 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-966 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-967 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + INNER JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-968 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + INNER JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-969 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-970 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-971 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-972 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-973 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-974 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-975 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-976 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-977 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-978 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-979 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-980 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-981 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-982 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-983 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-984 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-985 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-986 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-987 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-988 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - +} +do_execsql_test joinD-989 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-990 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-991 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-992 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-993 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-994 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-995 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-996 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-997 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-998 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-999 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1000 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1001 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1002 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-1003 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1004 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1005 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1006 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1007 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1008 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1009 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1010 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1011 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1012 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1013 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1014 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1015 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1016 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1017 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1018 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1019 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1020 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1021 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1022 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1023 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1024 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-1025 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1026 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1027 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-1028 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1029 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1030 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1031 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + LEFT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1032 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1033 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1034 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1035 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + LEFT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1036 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + LEFT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1037 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1038 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1039 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1040 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1041 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1042 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1043 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1044 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1045 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1046 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1047 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1048 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1049 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1050 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1051 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1052 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1053 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1054 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1055 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1056 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1057 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1058 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-1059 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-1060 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1061 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1062 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1063 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1064 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1065 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1066 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1067 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1068 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1069 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1070 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1071 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1072 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1073 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1074 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1075 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1076 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1077 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1078 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1079 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1080 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1081 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1082 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1083 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1084 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1085 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1086 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1087 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1088 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1089 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1090 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1091 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1092 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-1093 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1094 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1095 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1096 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1097 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1098 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1099 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + RIGHT JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1100 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1101 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1102 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c IS NOT DISTINCT FROM t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1103 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + RIGHT JOIN t3 ON t1.c IS NOT DISTINCT FROM t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1104 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 + - - - - - - - - 305 5 + - - - - - - - - 310 10 + - - - - - - - - 320 20 + - - - - - - - - 325 25 + - - - - - - - - 335 35 + - - - - - - - - 340 40 + - - - - - - - - 350 50 + - - - - - - - - 355 55 + - - - - - - - - 365 65 + - - - - - - - - 370 70 + - - - - - - - - 380 80 + - - - - - - - - 385 85 + - - - - - - - - 395 95 +} +do_execsql_test joinD-1105 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1106 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1107 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1108 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1109 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1110 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1111 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1112 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1113 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1114 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1115 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1116 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + INNER JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1117 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1118 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1119 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + INNER JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1120 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + INNER JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1121 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1122 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1123 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1124 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1125 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-1126 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - +} +do_execsql_test joinD-1127 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1128 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1129 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1130 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1131 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1132 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + LEFT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1133 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1134 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1135 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + LEFT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1136 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + LEFT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1137 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1138 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1139 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1140 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1141 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1142 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1143 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1144 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1145 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1146 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1147 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1148 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + RIGHT JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1149 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 + - - - - - - - - 300 0 +} +do_execsql_test joinD-1150 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1151 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + RIGHT JOIN t4 ON t1.d IS NOT DISTINCT FROM t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1152 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + RIGHT JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d IS NOT DISTINCT FROM t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1153 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1154 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1155 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE (t2.x>0 OR t2.x IS NULL) + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1156 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON true + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t1.b=t2.b AND t2.x>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1157 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 3 103 203 303 - - 203 3 - - + 6 106 206 306 106 6 206 6 - - + 9 109 209 309 - - 209 9 - - + 12 112 212 312 112 12 212 12 - - + 15 115 215 315 - - 215 15 315 15 + 18 118 218 318 118 18 218 18 - - + 21 121 221 321 - - 221 21 - - + 24 124 224 324 124 24 224 24 - - + 27 127 227 327 - - 227 27 - - + 30 130 230 330 130 30 230 30 330 30 + 33 133 233 333 - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + 39 139 239 339 - - 239 39 - - + 42 142 242 342 142 42 242 42 - - + 45 145 245 345 - - 245 45 345 45 + 48 148 248 348 148 48 248 48 - - + 51 151 251 351 - - 251 51 - - + 54 154 254 354 154 54 254 54 - - + 57 157 257 357 - - 257 57 - - + 60 160 260 360 160 60 260 60 360 60 + 63 163 263 363 - - 263 63 - - + 66 166 266 366 166 66 266 66 - - + 69 169 269 369 - - 269 69 - - + 72 172 272 372 172 72 272 72 - - + 75 175 275 375 - - 275 75 375 75 + 78 178 278 378 178 78 278 78 - - + 81 181 281 381 - - 281 81 - - + 84 184 284 384 184 84 284 84 - - + 87 187 287 387 - - 287 87 - - + 90 190 290 390 190 90 290 90 390 90 + 93 193 293 393 - - 293 93 - - +} +do_execsql_test joinD-1158 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t3.y>0 OR t3.y IS NULL + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1159 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 5 105 205 305 - - - - 305 5 + 10 110 210 310 110 10 - - 310 10 + 15 115 215 315 - - 215 15 315 15 + 20 120 220 320 120 20 - - 320 20 + 25 125 225 325 - - - - 325 25 + 30 130 230 330 130 30 230 30 330 30 + 35 135 235 335 - - - - 335 35 + 40 140 240 340 140 40 - - 340 40 + 45 145 245 345 - - 245 45 345 45 + 50 150 250 350 150 50 - - 350 50 + 55 155 255 355 - - - - 355 55 + 60 160 260 360 160 60 260 60 360 60 + 65 165 265 365 - - - - 365 65 + 70 170 270 370 170 70 - - 370 70 + 75 175 275 375 - - 275 75 375 75 + 80 180 280 380 180 80 - - 380 80 + 85 185 285 385 - - - - 385 85 + 90 190 290 390 190 90 290 90 390 90 + 95 195 295 395 - - - - 395 95 +} +do_execsql_test joinD-1160 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z IS NULL OR t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - +} +do_execsql_test joinD-1161 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1162 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t4.z>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 15 115 215 315 - - 215 15 315 15 + 30 130 230 330 130 30 230 30 330 30 + 45 145 245 345 - - 245 45 345 45 + 60 160 260 360 160 60 260 60 360 60 + 75 175 275 375 - - 275 75 375 75 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1163 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d AND t4.z>0 + WHERE t2.x>0 AND t3.y>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1164 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b=t2.b + FULL JOIN t3 ON t1.c=t3.c + FULL JOIN t4 ON t1.d=t4.d + WHERE t2.x>0 AND t3.y>0 AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 30 130 230 330 130 30 230 30 330 30 + 60 160 260 360 160 60 260 60 360 60 + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1165 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t1.b = t2.b AND t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 0 100 200 300 - - - - - - + 1 101 201 301 - - - - - - + 2 102 202 302 102 2 - - - - + 3 103 203 303 - - 203 3 - - + 4 104 204 304 104 4 - - - - + 5 105 205 305 - - - - 305 5 + 6 106 206 306 106 6 206 6 - - + 7 107 207 307 - - - - - - + 8 108 208 308 108 8 - - - - + 9 109 209 309 - - 209 9 - - + 10 110 210 310 110 10 - - 310 10 + 11 111 211 311 - - - - - - + 12 112 212 312 112 12 212 12 - - + 13 113 213 313 - - - - - - + 14 114 214 314 114 14 - - - - + 15 115 215 315 - - 215 15 315 15 + 16 116 216 316 116 16 - - - - + 17 117 217 317 - - - - - - + 18 118 218 318 118 18 218 18 - - + 19 119 219 319 - - - - - - + 20 120 220 320 120 20 - - 320 20 + 21 121 221 321 - - 221 21 - - + 22 122 222 322 122 22 - - - - + 23 123 223 323 - - - - - - + 24 124 224 324 124 24 224 24 - - + 25 125 225 325 - - - - 325 25 + 26 126 226 326 126 26 - - - - + 27 127 227 327 - - 227 27 - - + 28 128 228 328 128 28 - - - - + 29 129 229 329 - - - - - - + 30 130 230 330 130 30 230 30 330 30 + 31 131 231 331 - - - - - - + 32 132 232 332 132 32 - - - - + 33 133 233 333 - - 233 33 - - + 34 134 234 334 134 34 - - - - + 35 135 235 335 - - - - 335 35 + 36 136 236 336 136 36 236 36 - - + 37 137 237 337 - - - - - - + 38 138 238 338 138 38 - - - - + 39 139 239 339 - - 239 39 - - + 40 140 240 340 140 40 - - 340 40 + 41 141 241 341 - - - - - - + 42 142 242 342 142 42 242 42 - - + 43 143 243 343 - - - - - - + 44 144 244 344 144 44 - - - - + 45 145 245 345 - - 245 45 345 45 + 46 146 246 346 146 46 - - - - + 47 147 247 347 - - - - - - + 48 148 248 348 148 48 248 48 - - + 49 149 249 349 - - - - - - + 50 150 250 350 150 50 - - 350 50 + 51 151 251 351 - - 251 51 - - + 52 152 252 352 152 52 - - - - + 53 153 253 353 - - - - - - + 54 154 254 354 154 54 254 54 - - + 55 155 255 355 - - - - 355 55 + 56 156 256 356 156 56 - - - - + 57 157 257 357 - - 257 57 - - + 58 158 258 358 158 58 - - - - + 59 159 259 359 - - - - - - + 60 160 260 360 160 60 260 60 360 60 + 61 161 261 361 - - - - - - + 62 162 262 362 162 62 - - - - + 63 163 263 363 - - 263 63 - - + 64 164 264 364 164 64 - - - - + 65 165 265 365 - - - - 365 65 + 66 166 266 366 166 66 266 66 - - + 67 167 267 367 - - - - - - + 68 168 268 368 168 68 - - - - + 69 169 269 369 - - 269 69 - - + 70 170 270 370 170 70 - - 370 70 + 71 171 271 371 - - - - - - + 72 172 272 372 172 72 272 72 - - + 73 173 273 373 - - - - - - + 74 174 274 374 174 74 - - - - + 75 175 275 375 - - 275 75 375 75 + 76 176 276 376 176 76 - - - - + 77 177 277 377 - - - - - - + 78 178 278 378 178 78 278 78 - - + 79 179 279 379 - - - - - - + 80 180 280 380 180 80 - - 380 80 + 81 181 281 381 - - 281 81 - - + 82 182 282 382 182 82 - - - - + 83 183 283 383 - - - - - - + 84 184 284 384 184 84 284 84 - - + 85 185 285 385 - - - - 385 85 + 86 186 286 386 186 86 - - - - + 87 187 287 387 - - 287 87 - - + 88 188 288 388 188 88 - - - - + 89 189 289 389 - - - - - - + 90 190 290 390 190 90 290 90 390 90 + 91 191 291 391 - - - - - - + 92 192 292 392 192 92 - - - - + 93 193 293 393 - - 293 93 - - + 94 194 294 394 194 94 - - - - + 95 195 295 395 - - - - 395 95 + 96 - 296 396 - - - - - - + 97 197 - 397 - - - - - - + 98 198 298 - - - - - - - + 99 - - - - - - - - - + - - - - 100 0 - - - - + - - - - - - 200 0 - - + - - - - - - - - 300 0 +} +do_execsql_test joinD-1166 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 2 102 202 302 102 2 - - - - + 4 104 204 304 104 4 - - - - + 6 106 206 306 106 6 206 6 - - + 8 108 208 308 108 8 - - - - + 10 110 210 310 110 10 - - 310 10 + 12 112 212 312 112 12 212 12 - - + 14 114 214 314 114 14 - - - - + 16 116 216 316 116 16 - - - - + 18 118 218 318 118 18 218 18 - - + 20 120 220 320 120 20 - - 320 20 + 22 122 222 322 122 22 - - - - + 24 124 224 324 124 24 224 24 - - + 26 126 226 326 126 26 - - - - + 28 128 228 328 128 28 - - - - + 30 130 230 330 130 30 230 30 330 30 + 32 132 232 332 132 32 - - - - + 34 134 234 334 134 34 - - - - + 36 136 236 336 136 36 236 36 - - + 38 138 238 338 138 38 - - - - + 40 140 240 340 140 40 - - 340 40 + 42 142 242 342 142 42 242 42 - - + 44 144 244 344 144 44 - - - - + 46 146 246 346 146 46 - - - - + 48 148 248 348 148 48 248 48 - - + 50 150 250 350 150 50 - - 350 50 + 52 152 252 352 152 52 - - - - + 54 154 254 354 154 54 254 54 - - + 56 156 256 356 156 56 - - - - + 58 158 258 358 158 58 - - - - + 60 160 260 360 160 60 260 60 360 60 + 62 162 262 362 162 62 - - - - + 64 164 264 364 164 64 - - - - + 66 166 266 366 166 66 266 66 - - + 68 168 268 368 168 68 - - - - + 70 170 270 370 170 70 - - 370 70 + 72 172 272 372 172 72 272 72 - - + 74 174 274 374 174 74 - - - - + 76 176 276 376 176 76 - - - - + 78 178 278 378 178 78 278 78 - - + 80 180 280 380 180 80 - - 380 80 + 82 182 282 382 182 82 - - - - + 84 184 284 384 184 84 284 84 - - + 86 186 286 386 186 86 - - - - + 88 188 288 388 188 88 - - - - + 90 190 290 390 190 90 290 90 390 90 + 92 192 292 392 192 92 - - - - + 94 194 294 394 194 94 - - - - +} +do_execsql_test joinD-1167 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t3.y>0 + FULL JOIN t4 ON t1.d = t4.d AND t4.z>0 + WHERE t1.b = t2.b AND t1.c = t3.c + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 6 106 206 306 106 6 206 6 - - + 12 112 212 312 112 12 212 12 - - + 18 118 218 318 118 18 218 18 - - + 24 124 224 324 124 24 224 24 - - + 30 130 230 330 130 30 230 30 330 30 + 36 136 236 336 136 36 236 36 - - + 42 142 242 342 142 42 242 42 - - + 48 148 248 348 148 48 248 48 - - + 54 154 254 354 154 54 254 54 - - + 60 160 260 360 160 60 260 60 360 60 + 66 166 266 366 166 66 266 66 - - + 72 172 272 372 172 72 272 72 - - + 78 178 278 378 178 78 278 78 - - + 84 184 284 384 184 84 284 84 - - + 90 190 290 390 190 90 290 90 390 90 +} +do_execsql_test joinD-1168 { + SELECT t1.*, t2.*, t3.*, t4.* + FROM t1 FULL JOIN t2 ON t2.x>0 + FULL JOIN t3 ON t1.c = t3.c AND t3.y>0 + FULL JOIN t4 ON t4.z>0 + WHERE t1.b = t2.b AND t1.d = t4.d + ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0); +} { + 10 110 210 310 110 10 - - 310 10 + 20 120 220 320 120 20 - - 320 20 + 30 130 230 330 130 30 230 30 330 30 + 40 140 240 340 140 40 - - 340 40 + 50 150 250 350 150 50 - - 350 50 + 60 160 260 360 160 60 260 60 360 60 + 70 170 270 370 170 70 - - 370 70 + 80 180 280 380 180 80 - - 380 80 + 90 190 290 390 190 90 290 90 390 90 +} +############################################################################# +# The following are extra tests added manually +do_execsql_test joinD-extra-1000 { + CREATE VIEW v1 AS + SELECT * + FROM t1 INNER JOIN t2 ON t1.b=t2.b AND t2.x>0 + RIGHT JOIN t3 ON t1.c=t3.c AND t3.y>0 + LEFT JOIN t4 ON t1.d=t4.d AND t4.z>0; + CREATE TRIGGER v1r1 INSTEAD OF UPDATE OF c ON v1 BEGIN + UPDATE t1 SET c=new.c WHERE (a,b,c,d) IS (old.a,old.b,old.c,old.d); + UPDATE t3 SET c=new.c WHERE (c,y) IS (old.c,old.y); + END; + SELECT * FROM v1 WHERE y BETWEEN 30 AND 40 ORDER BY y; +} { + 30 130 230 330 130 30 230 30 330 30 + - - - - - - 233 33 - - + 36 136 236 336 136 36 236 36 - - + - - - - - - 239 39 - - +} +do_execsql_test joinD-extra-1010 { + BEGIN; + UPDATE v1 SET c=c+1000 WHERE y BETWEEN 30 and 40; + SELECT * FROM v1 WHERE y BETWEEN 30 AND 40 ORDER BY y; + ROLLBACK; +} { + 30 130 1230 330 130 30 1230 30 330 30 + - - - - - - 233 33 - - + 36 136 1236 336 136 36 1236 36 - - + - - - - - - 239 39 - - +} +finish_test +############################################################################# +# This is the TCL script used to generate the psql script that generated +# the data above. +# +# puts " +# \\pset border off +# \\pset tuples_only on +# \\pset null - +# +# DROP TABLE IF EXISTS t1; +# DROP TABLE IF EXISTS t2; +# DROP TABLE IF EXISTS t3; +# DROP TABLE IF EXISTS t4; +# CREATE TABLE t1(a INT, b INT, c INT, d INT); +# WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<95) +# INSERT INTO t1(a,b,c,d) SELECT x, x+100, x+200, x+300 FROM c; +# CREATE TABLE t2(b INT, x INT); +# INSERT INTO t2(b,x) SELECT b, a FROM t1 WHERE a%2=0; +# CREATE INDEX t2b ON t2(b); +# CREATE TABLE t3(c INT, y INT); +# INSERT INTO t3(c,y) SELECT c, a FROM t1 WHERE a%3=0; +# CREATE INDEX t3c ON t3(c); +# CREATE TABLE t4(d INT, z INT); +# INSERT INTO t4(d,z) SELECT d, a FROM t1 WHERE a%5=0; +# CREATE INDEX t4d ON t4(d); +# INSERT INTO t1(a,b,c,d) VALUES +# (96,NULL,296,396), +# (97,197,NULL,397), +# (98,198,298,NULL), +# (99,NULL,NULL,NULL); +# " +# +# proc echo {prefix txt} { +# regsub -all {\n} $txt \n$prefix txt +# puts "$prefix$txt" +# } +# +# set n 0 +# foreach j1 {INNER LEFT RIGHT FULL} { +# foreach j2 {INNER LEFT RIGHT FULL} { +# foreach j3 {INNER LEFT RIGHT FULL} { +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# if {$j1!="FULL"} { +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b IS NOT DISTINCT FROM t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# } +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " WHERE t2.x>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " WHERE (t2.x>0 OR t2.x IS NULL)\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON true\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " WHERE t1.b=t2.b AND t2.x>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " WHERE t3.y>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " WHERE t3.y>0 OR t3.y IS NULL\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d\n" +# append q1 " WHERE t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d\n" +# append q1 " WHERE t4.z IS NULL OR t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d\n" +# append q1 " WHERE t2.x>0 AND t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d\n" +# append q1 " WHERE t4.z>0 AND t3.y>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " WHERE t2.x>0 AND t3.y>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d\n" +# append q1 " WHERE t2.x>0 AND t3.y>0 AND t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# set op1 [expr {$j1=="FULL"?"=":"IS NOT DISTINCT FROM"}] +# set op2 [expr {$j2=="FULL"?"=":"IS NOT DISTINCT FROM"}] +# set op3 [expr {$j3=="FULL"?"=":"IS NOT DISTINCT FROM"}] +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b $op1 t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c $op2 t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d $op3 t4.d AND t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c $op2 t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d $op3 t4.d AND t4.z>0\n" +# append q1 " WHERE t1.b $op1 t2.b\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d $op3 t4.d AND t4.z>0\n" +# append q1 " WHERE t1.b $op1 t2.b AND t1.c $op2 t3.c\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c $op2 t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t4.z>0\n" +# append q1 " WHERE t1.b $op1 t2.b AND t1.d $op3 t4.d\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# if {$j1!="FULL"} { +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b IN (t2.b,-2,-3) AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c=t3.c AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# } +# +# if {$j2!="FULL"} { +# incr n +# set q1 "" +# append q1 "SELECT t1.*, t2.*, t3.*, t4.*\n" +# append q1 " FROM t1 $j1 JOIN t2 ON t1.b=t2.b AND t2.x>0\n" +# append q1 " $j2 JOIN t3 ON t1.c IN (-4,t3.c,-5) AND t3.y>0\n" +# append q1 " $j3 JOIN t4 ON t1.d=t4.d AND t4.z>0\n" +# append q1 " ORDER BY coalesce(t1.a,t2.b,t3.c,t4.d,0);" +# +# echo "\\qecho " "do_execsql_test joinB-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# } +# } +# } +# } +# diff --git a/test/joinE.test b/test/joinE.test new file mode 100644 index 000000000..8c8e72ab2 --- /dev/null +++ b/test/joinE.test @@ -0,0 +1,443 @@ +# 2022-05-13 +# +# 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 for JOINs that use Bloom filters. +# +# The test case output is (mostly) all generated by PostgreSQL 14. This +# test module was created as follows: +# +# 1. Run a TCL script (included at the bottom of this file) that +# generates an input script for "psql" that will run man +# diverse tests on joins. +# +# 2. Run the script from step (1) through psql and collect the +# output. +# +# 3. Make a few minor global search-and-replace operations to convert +# the psql output into a form suitable for this test module. +# +# 4. Add this header, and the script content at the footer. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +db nullvalue - +db eval { + CREATE TABLE t1(a INT); + INSERT INTO t1 VALUES(1),(NULL); + CREATE TABLE t2(b INT); + INSERT INTO t2 VALUES(2),(NULL); +} +do_execsql_test joinE-1 { + SELECT a, b + FROM t1 INNER JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 2 + 1 - + - 2 + - - +} +do_execsql_test joinE-2 { + SELECT a, b + FROM t1 INNER JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { + - 2 + - - +} +do_execsql_test joinE-3 { + SELECT a, b + FROM t1 INNER JOIN t2 ON a IS NULL + ORDER BY coalesce(a,b,3); +} { + - 2 + - - +} +do_execsql_test joinE-4 { + SELECT a, b + FROM t1 INNER JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - - +} +do_execsql_test joinE-5 { + SELECT a, b + FROM t1 INNER JOIN t2 ON b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - - +} +do_execsql_test joinE-6 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 2 + 1 - + - 2 + - - +} +do_execsql_test joinE-7 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { + - 2 + - - +} +do_execsql_test joinE-8 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON a IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - 2 + - - +} +do_execsql_test joinE-9 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - - +} +do_execsql_test joinE-10 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - - +} +do_execsql_test joinE-11 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 2 + 1 - + - 2 + - - +} +do_execsql_test joinE-12 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { + - 2 + - - +} +do_execsql_test joinE-13 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON a IS NULL + ORDER BY coalesce(a,b,3); +} { + - 2 + - - +} +do_execsql_test joinE-14 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - - +} +do_execsql_test joinE-15 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - 2 + - - +} +do_execsql_test joinE-16 { + SELECT a, b + FROM t1 FULL JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 2 + 1 - + - 2 + - - +} +do_execsql_test joinE-17 { + SELECT a, b + FROM t1 FULL JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { + - 2 + - - +} + +# PG-14 is unable to perform this join. It says: FULL JOIN is only +# supported with merge-joinable or hash-joinable join conditions +# +# do_execsql_test joinE-18 { +# SELECT a, b +# FROM t1 FULL JOIN t2 ON a IS NULL +# ORDER BY coalesce(a,b,3); +# } { +# } + +do_execsql_test joinE-19 { + SELECT a, b + FROM t1 FULL JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - + - - +} + +# PG-14 is unable to perform this join. It says: FULL JOIN is only +# supported with merge-joinable or hash-joinable join conditions +# +# do_execsql_test joinE-20 { +# SELECT a, b +# FROM t1 FULL JOIN t2 ON b IS NULL +# ORDER BY coalesce(a,b,3); +# } { +# } + +db eval { + DELETE FROM t1; + INSERT INTO t1 VALUES(1); + DELETE FROM t2; + INSERT INTO t2 VALUES(NULL); +} + +do_execsql_test joinE-21 { + SELECT a, b + FROM t1 INNER JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-22 { + SELECT a, b + FROM t1 INNER JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { +} +do_execsql_test joinE-23 { + SELECT a, b + FROM t1 INNER JOIN t2 ON a IS NULL + ORDER BY coalesce(a,b,3); +} { +} +do_execsql_test joinE-24 { + SELECT a, b + FROM t1 INNER JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-25 { + SELECT a, b + FROM t1 INNER JOIN t2 ON b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-26 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-27 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { +} +do_execsql_test joinE-28 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON a IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-29 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-30 { + SELECT a, b + FROM t1 LEFT JOIN t2 ON b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-31 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 - +} + +do_execsql_test joinE-32 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { +} + +do_execsql_test joinE-33 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON a IS NULL + ORDER BY coalesce(a,b,3); +} { + - - +} +do_execsql_test joinE-34 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-35 { + SELECT a, b + FROM t1 RIGHT JOIN t2 ON b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-36 { + SELECT a, b + FROM t1 FULL JOIN t2 ON true + ORDER BY coalesce(a,b,3); +} { + 1 - +} +do_execsql_test joinE-37 { + SELECT a, b + FROM t1 FULL JOIN t2 ON true WHERE a IS NULL + ORDER BY coalesce(a,b,3); +} { +} + +# PG-14 is unable +# +# do_execsql_test joinE-38 { +# SELECT a, b +# FROM t1 FULL JOIN t2 ON a IS NULL +# ORDER BY coalesce(a,b,3); +# } { +# } + +do_execsql_test joinE-39 { + SELECT a, b + FROM t1 FULL JOIN t2 ON true WHERE b IS NULL + ORDER BY coalesce(a,b,3); +} { + 1 - +} + +# PG-14 is unable +# do_execsql_test joinE-40 { +# SELECT a, b +# FROM t1 FULL JOIN t2 ON b IS NULL +# ORDER BY coalesce(a,b,3); +# } { +# } + +finish_test + +############################################################################## +# This is the PG-14 test script generator +# +# puts " +# \\pset border off +# \\pset tuples_only on +# \\pset null - +# +# DROP TABLE IF EXISTS t1; +# DROP TABLE IF EXISTS t2; +# CREATE TABLE t1(a INT); +# INSERT INTO t1 VALUES(1),(NULL); +# CREATE TABLE t2(b INT); +# INSERT INTO t2 VALUES(2),(NULL); +# " +# +# proc echo {prefix txt} { +# regsub -all {\n} $txt \n$prefix txt +# puts "$prefix$txt" +# } +# +# set n 0 +# set k 0 +# foreach j1 {INNER LEFT RIGHT FULL} { +# foreach on1 { +# true +# {true WHERE a IS NULL} +# {a IS NULL} +# {true WHERE b IS NULL} +# {b IS NULL} +# } { +# +# incr n +# incr k +# set q1 "" +# append q1 "SELECT a, b\n" +# append q1 " FROM t1 $j1 JOIN t2 ON $on1\n" +# append q1 " ORDER BY coalesce(a,b,3);" +# +# echo "\\qecho " "do_execsql_test joinE-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# } +# } +# +# puts " +# DELETE FROM t1; +# INSERT INTO t1 VALUES(1); +# DELETE FROM t2; +# INSERT INTO t2 VALUES(NULL); +# " +# +# foreach j1 {INNER LEFT RIGHT FULL} { +# foreach on1 { +# true +# {true WHERE a IS NULL} +# {a IS NULL} +# {true WHERE b IS NULL} +# {b IS NULL} +# } { +# +# incr n +# incr k +# set q1 "" +# append q1 "SELECT a, b\n" +# append q1 " FROM t1 $j1 JOIN t2 ON $on1\n" +# append q1 " ORDER BY coalesce(a,b,3);" +# +# echo "\\qecho " "do_execsql_test joinE-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# } +# } diff --git a/test/joinF.test b/test/joinF.test new file mode 100644 index 000000000..0848e37d1 --- /dev/null +++ b/test/joinF.test @@ -0,0 +1,613 @@ +# 2022-05-31 +# +# 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 for JOINs +# +# The test case output is (mostly) all generated by PostgreSQL 14. This +# test module was created as follows: +# +# 1. Run a TCL script (included at the bottom of this file) that +# generates an input script for "psql" that will run man +# diverse tests on joins. +# +# 2. Run the script from step (1) through psql and collect the +# output. +# +# 3. Make a few minor global search-and-replace operations to convert +# the psql output into a form suitable for this test module. +# +# 4. Add this header, and the script content at the footer. +# +# A few extra tests that were not generated from postgresql output are +# added at the end. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +db nullvalue - +db eval { + CREATE TABLE t1(x INT); + CREATE TABLE t2(y INT); + CREATE TABLE t3(z INT); + CREATE TABLE t4(w INT); + INSERT INTO t1 VALUES(10); + INSERT INTO t3 VALUES(20),(30); + INSERT INTO t4 VALUES(50); +} +do_execsql_test joinF-1 { + SELECT * + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-2 { + SELECT * + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-3 { + SELECT * + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-4 { + SELECT * + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-5 { + SELECT * + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - - 50 +} +do_execsql_test joinF-6 { + SELECT * + FROM t1 INNER JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-7 { + SELECT * + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-8 { + SELECT * + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-9 { + SELECT * + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-10 { + SELECT * + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-11 { + SELECT * + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - - 50 +} +do_execsql_test joinF-12 { + SELECT * + FROM t1 INNER JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-13 { + SELECT * + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-14 { + SELECT * + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-15 { + SELECT * + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-16 { + SELECT * + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-17 { + SELECT * + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-18 { + SELECT * + FROM t1 INNER JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-19 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-20 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-21 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-22 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-23 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - - 50 +} +do_execsql_test joinF-24 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-25 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + 10 - - 50 +} +do_execsql_test joinF-26 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-27 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + 10 - - 50 +} +do_execsql_test joinF-28 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-29 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + 10 - - 50 +} +do_execsql_test joinF-30 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-31 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-32 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-33 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-34 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-35 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-36 { + SELECT * + FROM t1 LEFT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-37 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-38 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-39 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-40 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-41 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - - 50 +} +do_execsql_test joinF-42 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + INNER JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-43 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-44 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-45 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-46 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-47 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - - 50 +} +do_execsql_test joinF-48 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + LEFT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { +} +do_execsql_test joinF-49 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-50 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + INNER JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-51 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-52 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + LEFT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-53 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +do_execsql_test joinF-54 { + SELECT * + FROM t1 RIGHT JOIN t2 ON true + RIGHT JOIN t3 ON t2.y IS NOT NULL + RIGHT JOIN t4 ON true + WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600) + ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0); +} { + - - 20 50 + - - 30 50 +} +finish_test + +############################################################################ +# This is the TCL script used to generate the psql script that generated +# the data above. +# +# puts " +# \\pset border off +# \\pset tuples_only on +# \\pset null - +# +# DROP TABLE IF EXISTS t1; +# DROP TABLE IF EXISTS t2; +# DROP TABLE IF EXISTS t3; +# DROP TABLE IF EXISTS t4; +# CREATE TABLE t1(x INT); +# CREATE TABLE t2(y INT); +# CREATE TABLE t3(z INT); +# CREATE TABLE t4(w INT); +# INSERT INTO t1 VALUES(10); +# INSERT INTO t3 VALUES(20),(30); +# INSERT INTO t4 VALUES(50); +# " +# +# proc echo {prefix txt} { +# regsub -all {\n} $txt \n$prefix txt +# puts "$prefix$txt" +# } +# +# set n 0 +# foreach j1 {INNER LEFT RIGHT} { +# foreach j2 {INNER LEFT RIGHT} { +# foreach j3 {INNER LEFT RIGHT} { +# +# incr n +# set q1 "" +# append q1 "SELECT *\n" +# append q1 " FROM t1 $j1 JOIN t2 ON true\n" +# append q1 " $j2 JOIN t3 ON t2.y IS NOT NULL\n" +# append q1 " $j3 JOIN t4 ON true\n" +# append q1 " ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);" +# +# echo "\\qecho " "do_execsql_test joinF-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# incr n +# set q1 "" +# append q1 "SELECT *\n" +# append q1 " FROM t1 $j1 JOIN t2 ON true\n" +# append q1 " $j2 JOIN t3 ON t2.y IS NOT NULL\n" +# append q1 " $j3 JOIN t4 ON true\n" +# append q1 " WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)\n" +# append q1 " ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);" +# +# echo "\\qecho " "do_execsql_test joinF-$n \{" +# echo "\\qecho X " $q1 +# echo "\\qecho " "\} \{" +# puts $q1 +# echo "\\qecho " "\}" +# +# } +# } +# } +# diff --git a/test/joinH.test b/test/joinH.test new file mode 100644 index 000000000..983286120 --- /dev/null +++ b/test/joinH.test @@ -0,0 +1,72 @@ +# 2022 May 17 +# +# 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. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix joinH + +do_execsql_test 1.0 { + CREATE TABLE t1(a INT); + CREATE TABLE t2(b INT); + INSERT INTO t2(b) VALUES(NULL); +} + +db nullvalue NULL + +do_execsql_test 1.1 { + SELECT DISTINCT a FROM t1 FULL JOIN t2 ON true WHERE (b ISNULL); +} {NULL} +do_execsql_test 1.2 { + SELECT a FROM t1 FULL JOIN t2 ON true; +} {NULL} +do_execsql_test 1.3 { + SELECT a FROM t1 FULL JOIN t2 ON true WHERE (b ISNULL); +} {NULL} +do_execsql_test 1.4 { + SELECT DISTINCT a FROM t1 FULL JOIN t2 ON true; +} {NULL} + +#----------------------------------------------------------- + +reset_db +do_execsql_test 2.0 { + CREATE TABLE r3(x); + CREATE TABLE r4(y INTEGER PRIMARY KEY); + INSERT INTO r4 VALUES(55); +} + +do_execsql_test 2.1 { + SELECT 'value!' FROM r3 FULL JOIN r4 ON (y=x); +} {value!} + +do_execsql_test 2.2 { + SELECT 'value!' FROM r3 FULL JOIN r4 ON (y=x) WHERE +y=55; +} {value!} + +#----------------------------------------------------------- +reset_db +do_execsql_test 3.1 { + CREATE TABLE t0 (c0); + CREATE TABLE t1 (c0); + CREATE TABLE t2 (c0 , c1 , c2 , UNIQUE (c0), UNIQUE (c2 DESC)); + INSERT INTO t2 VALUES ('x', 'y', 'z'); + ANALYZE; + CREATE VIEW v0(c0) AS SELECT FALSE; +} + +do_catchsql_test 3.2 { + SELECT * FROM t0 LEFT OUTER JOIN t1 ON v0.c0 INNER JOIN v0 INNER JOIN t2 ON (t2.c2 NOT NULL); +} {1 {ON clause references tables to its right}} + + +finish_test diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl index 2d0e57e4f..7d3c942f7 100644 --- a/test/malloc_common.tcl +++ b/test/malloc_common.tcl @@ -664,6 +664,7 @@ proc do_write_test {name tbl sql} { if {$::DO_MALLOC_TEST } { set answers [list {1 {out of memory}} {0 {}}] + lappend answers [list 1 {unable to open a temporary database file for storing temporary tables}] if {$::DO_MALLOC_TEST==1} { set modes {100000 persistent} } else { diff --git a/test/merge1.test b/test/merge1.test index 36969e8fc..7ec4dab10 100644 --- a/test/merge1.test +++ b/test/merge1.test @@ -141,3 +141,5 @@ do_eqp_test 111 { `--RIGHT `--SCAN generate_series VIRTUAL TABLE INDEX 23: } + +finish_test diff --git a/test/permutations.test b/test/permutations.test index 0987daeb1..f2710da0f 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -217,7 +217,8 @@ test_suite "valgrind" -prefix "" -description { fail under valgrind) omitted. } -files [ test_set $allquicktests -exclude *malloc* *ioerr* *fault* *_err* wal.test \ - shell*.test crash8.test atof1.test selectG.test \ + shell2.test shell6.test shell7.test \ + crash8.test atof1.test selectG.test \ tkt-fc62af4523.test numindex1.test corruptK.test ] -initialize { set ::G(valgrind) 1 diff --git a/test/regexp1.test b/test/regexp1.test index 1eb56c672..569dd66c2 100644 --- a/test/regexp1.test +++ b/test/regexp1.test @@ -239,4 +239,26 @@ do_execsql_test regexp1-2.22 { SELECT 'abc$¢€xyz' REGEXP '^abc[^\u0025-X][^ -\u007f][^\u20ab]xyz$' } {1} +# 2022-07-03 +# https://sqlite.org/forum/forumpost/96692f8ba5 +# The REGEXP extension mishandles the prefix search optimization when +# the prefix contains 3-byte UTF8 characters. +# +reset_db +load_static_extension db regexp +do_execsql_test regexp1-3.1 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, a TEXT); + INSERT INTO t1(id, a) VALUES(1, '日本語'); + SELECT a, hex(a), length(a) FROM t1; +} {日本語 E697A5E69CACE8AA9E 3} +do_execsql_test regexp1-3.2 { + SELECT * FROM t1 WHERE a='日本語'; +} {1 日本語} +do_execsql_test regexp1-3.3 { + SELECT * FROM t1 WHERE a LIKE '日本語'; +} {1 日本語} +do_execsql_test regexp1-3.4 { + SELECT * FROM t1 wHERE a REGEXP '日本語'; +} {1 日本語} + finish_test diff --git a/test/rowvalue.test b/test/rowvalue.test index 92d3260e6..59b44d938 100644 --- a/test/rowvalue.test +++ b/test/rowvalue.test @@ -260,11 +260,23 @@ do_catchsql_test 11.8 { # do_execsql_test 12.1 { DROP TABLE IF EXISTS t1; - CREATE TABLE t1(a,b); INSERT INTO t1 VALUES(1,2); + CREATE TABLE t1(a INT,b INT); INSERT INTO t1 VALUES(1,2); DROP TABLE IF EXISTS t2; - CREATE TABLE t2(x,y); INSERT INTO t2 VALUES(3,4); + CREATE TABLE t2(x INT,y INT); INSERT INTO t2 VALUES(3,4); SELECT *,'x' FROM t1 LEFT JOIN t2 ON (a,b)=(x,y); } {1 2 {} {} x} +db null - +do_execsql_test 12.2 { + SELECT t1.*, t2.* FROM t2 RIGHT JOIN t1 ON (a,b)=(x,y); +} {1 2 - -} +do_execsql_test 12.3 { + SELECT t1.*, t2.* FROM t1 FULL JOIN t2 ON (a,b)=(x,y) + ORDER BY coalesce(a,x); +} { + 1 2 - - + - - 3 4 +} +db null {} foreach {tn sql} { @@ -540,7 +552,7 @@ do_execsql_test 19.36 { SELECT * FROM t1 WHERE (3,32)>=(a,b) ORDER BY a DESC; } {2 22 1 11} -# 2018-02-18: Memory leak nexted row-value. Detected by OSSFuzz. +# 2018-02-18: Memory leak nested row-value. Detected by OSSFuzz. # do_catchsql_test 20.1 { SELECT 1 WHERE (2,(2,0)) IS (2,(2,0)); @@ -632,9 +644,15 @@ do_execsql_test 26.10 { do_execsql_test 26.20 { SELECT 2 FROM t1 LEFT JOIN t0 ON (c0, x'') != (NULL, 0); } {2} +do_execsql_test 26.21 { + SELECT 21 FROM t0 RIGHT JOIN t1 ON (c0, x'') != (NULL, 0); +} {21} do_execsql_test 26.30 { SELECT 3 FROM t1 LEFT JOIN t0 WHERE (c0, x'') != (NULL, 0); } {3} +do_execsql_test 26.31 { + SELECT 31 FROM t0 RIGHT JOIN t1 WHERE (c0, x'') != (NULL, 0); +} {31} # 2019-12-30 ticket 892575cdba4e1e36 # @@ -700,6 +718,9 @@ do_execsql_test 31.1 { CREATE TABLE b(b1 UNIQUE,b2); SELECT * FROM a LEFT JOIN b ON b2=NULL AND b2=5 WHERE (b1,substr(b.b1,1,1))==(SELECT 1024,'b'); } {} +do_execsql_test 31.1b { + SELECT * FROM b RIGHT JOIN a ON b2=NULL AND b2=5 WHERE (b1,substr(b.b1,1,1))==(SELECT 1024,'b'); +} {} do_execsql_test 31.2 { CREATE TABLE t1(a); INSERT INTO t1 VALUES(0); @@ -707,6 +728,9 @@ do_execsql_test 31.2 { INSERT INTO t2 VALUES(NULL,123,456); SELECT * FROM t1 LEFT JOIN t2 ON b=NULL WHERE (c,d)==(SELECT 123, 456+a); } {} +do_execsql_test 31.2b { + SELECT * FROM t2 RIGHT JOIN t1 ON b=NULL WHERE (c,d)==(SELECT 123, 456+a); +} {} # 2022-02-03 dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1 reset_db @@ -727,4 +751,35 @@ do_execsql_test 32.1 { WHERE a=1234 OR a<=567; } {500 502} +# 2022-07-15 +# https://sqlite.org/forum/forumpost/3607259d3c +# +reset_db +do_execsql_test 33.1 { + CREATE TABLE t1(a INT, b INT PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t1(a, b) VALUES (0, 1),(15,-7),(3,100); + ANALYZE; +} {} +do_execsql_test 33.2 { + SELECT * FROM t1 WHERE (b,a) BETWEEN (0,5) AND (99,-2); +} {0 1} +do_execsql_test 33.3 { + SELECT * FROM t1 WHERE (b,a) BETWEEN (-8,5) AND (0,-2); +} {15 -7} +do_execsql_test 33.3 { + SELECT * FROM t1 WHERE (b,a) BETWEEN (3,5) AND (100,4); +} {3 100} +do_execsql_test 33.3 { + SELECT * FROM t1 WHERE (b,a) BETWEEN (3,5) AND (100,2); +} {} +do_execsql_test 33.3 { + SELECT * FROM t1 WHERE (a,b) BETWEEN (-2,99) AND (1,0); +} {0 1} +do_execsql_test 33.3 { + SELECT * FROM t1 WHERE (a,b) BETWEEN (14,99) AND (16,0); +} {15 -7} +do_execsql_test 33.3 { + SELECT * FROM t1 WHERE (a,b) BETWEEN (2,99) AND (4,0); +} {3 100} + finish_test diff --git a/test/rowvalue3.test b/test/rowvalue3.test index 988eea900..80ebeb761 100644 --- a/test/rowvalue3.test +++ b/test/rowvalue3.test @@ -213,7 +213,8 @@ do_execsql_test 5.0 { CREATE TABLE T2(a TEXT PRIMARY KEY,n INT); INSERT INTO T2(a, n) VALUES('aaa',0); SELECT * FROM T2 - WHERE (a,n) IN (SELECT T1.a, V.n FROM T1, (SELECT * FROM (SELECT 0 n)) V); + WHERE (a,n) IN (SELECT T1.a, V.n + FROM T1, (SELECT * FROM (SELECT 0 n) T3) V); } {aaa 0} diff --git a/test/select1.test b/test/select1.test index e22907da1..44e63d252 100644 --- a/test/select1.test +++ b/test/select1.test @@ -545,14 +545,14 @@ do_test select1-6.9.7 { set x [execsql2 { SELECT * FROM test1 a, (select 5, 6) LIMIT 1 }] - regsub -all {subquery_[0-9a-fA-F_]+} $x {subquery} x + regsub -all {subquery-\d+} $x {subquery-0} x set x -} {a.f1 11 a.f2 22 subquery.5 5 subquery.6 6} +} {a.f1 11 a.f2 22 (subquery-0).5 5 (subquery-0).6 6} do_test select1-6.9.8 { set x [execsql2 { SELECT * FROM test1 a, (select 5 AS x, 6 AS y) AS b LIMIT 1 }] - regsub -all {subquery_[0-9a-fA-F]+_} $x {subquery} x + regsub -all {subquery-\d+} $x {subquery-0} x set x } {a.f1 11 a.f2 22 b.x 5 b.y 6} do_test select1-6.9.9 { diff --git a/test/select3.test b/test/select3.test index fec8ba4a5..690514b49 100644 --- a/test/select3.test +++ b/test/select3.test @@ -119,10 +119,9 @@ do_test select3-2.14 { # Cannot have a HAVING without a GROUP BY # -do_test select3-3.1 { - set v [catch {execsql {SELECT log, count(*) FROM t1 HAVING log>=4}} msg] - lappend v $msg -} {1 {a GROUP BY clause is required before HAVING}} +do_execsql_test select3-3.1 { + SELECT log, count(*) FROM t1 HAVING log>=4 +} {} # Toss in some HAVING clauses # diff --git a/test/shell1.test b/test/shell1.test index 3c7061c55..26426535b 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -23,7 +23,7 @@ # set testdir [file dirname $argv0] source $testdir/tester.tcl -set CLI [test_find_cli] +set CLI [test_cli_invocation] db close forcedelete test.db test.db-journal test.db-wal sqlite3 db test.db diff --git a/test/shell2.test b/test/shell2.test index 29712c7f0..2237404e5 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -136,16 +136,13 @@ SELECT * FROM foo1; SELECT * FROM foo2; INSERT INTO foo1(a) VALUES(1); CREATE TABLE foo2(b); INSERT INTO foo2(b) VALUES(1); -SELECT * FROM foo1; +SELECT * FROM foo1; SELECT * FROM foo2; 1 -SELECT * FROM foo2; 1 -INSERT INTO foo1(a) VALUES(2); -INSERT INTO foo2(b) VALUES(2); -SELECT * FROM foo1; +INSERT INTO foo1(a) VALUES(2); INSERT INTO foo2(b) VALUES(2); +SELECT * FROM foo1; SELECT * FROM foo2; 1 2 -SELECT * FROM foo2; 1 2 }} @@ -170,19 +167,16 @@ CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); CREATE TABLE foo2(b); INSERT INTO foo2(b) VALUES(1); -SELECT * FROM foo1; +SELECT * FROM foo1; SELECT * FROM foo2; a 1 -SELECT * FROM foo2; b 1 -INSERT INTO foo1(a) VALUES(2); -INSERT INTO foo2(b) VALUES(2); -SELECT * FROM foo1; +INSERT INTO foo1(a) VALUES(2); INSERT INTO foo2(b) VALUES(2); +SELECT * FROM foo1; SELECT * FROM foo2; a 1 2 -SELECT * FROM foo2; b 1 2 diff --git a/test/shell3.test b/test/shell3.test index e5a0c124e..f8d69946e 100644 --- a/test/shell3.test +++ b/test/shell3.test @@ -22,7 +22,7 @@ # set testdir [file dirname $argv0] source $testdir/tester.tcl -set CLI [test_find_cli] +set CLI [test_cli_invocation] db close forcedelete test.db test.db-journal test.db-wal sqlite3 db test.db diff --git a/test/shell4.test b/test/shell4.test index ee7d2b856..068072202 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -23,7 +23,8 @@ # set testdir [file dirname $argv0] source $testdir/tester.tcl -set CLI [test_find_cli] +set CLI [test_cli_invocation] +set CLI_ONLY [test_find_cli] db close forcedelete test.db test.db-journal test.db-wal sqlite3 db test.db @@ -130,13 +131,13 @@ do_test shell4-3.1 { set fd [open t1.txt wb] puts $fd "SELECT 'squirrel';" close $fd - exec $::CLI :memory: --interactive ".read t1.txt" + exec $::CLI_ONLY :memory: --interactive ".read t1.txt" } {squirrel} do_test shell4-3.2 { set fd [open t1.txt wb] puts $fd "SELECT 'pound: \302\243';" close $fd - exec $::CLI :memory: --interactive ".read t1.txt" + exec $::CLI_ONLY :memory: --interactive ".read t1.txt" } {pound: £} do_test shell4-4.1 { diff --git a/test/shell5.test b/test/shell5.test index 00d89f8d7..39018a0ce 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -21,7 +21,7 @@ # set testdir [file dirname $argv0] source $testdir/tester.tcl -set CLI [test_find_cli] +set CLI [test_cli_invocation] db close forcedelete test.db test.db-journal test.db-wal diff --git a/test/shell8.test b/test/shell8.test index ddb4a47b8..bee603923 100644 --- a/test/shell8.test +++ b/test/shell8.test @@ -19,7 +19,7 @@ set testprefix shell8 ifcapable !vtab { finish_test; return } -set CLI [test_find_cli] +set CLI [test_cli_invocation] # Check to make sure the shell has been compiled with ".archive" support. # diff --git a/test/subtype1.test b/test/subtype1.test index 101961102..d15f737bc 100644 --- a/test/subtype1.test +++ b/test/subtype1.test @@ -28,4 +28,32 @@ do_execsql_test subtype1-130 { SELECT test_setsubtype('hello',123); } {hello} +# 2022-06-09 +# https://sqlite.org/forum/forumpost/3d9caa45cbe38c78 +# +# Avoid carrying subtypes through into a subquery that has been flattened +# or to which the outer WHERE clause has been pushed down. +# +reset_db +do_execsql_test subtype1-200 { + CREATE TABLE t1(a); INSERT INTO t1 VALUES ('x'); + CREATE VIEW t2(b) AS SELECT json(TRUE); + CREATE TABLE t3(b); INSERT INTO t3 VALUES(json(TRUE)); +} +do_execsql_test subtype1-210 { + SELECT * FROM t3, t1 WHERE NOT json_quote(b); +} {1 x} +do_execsql_test subtype1-220 { + SELECT * FROM t2, t1 WHERE NOT json_quote(b); +} {1 x} +do_execsql_test subtype1-230 { + WITH t4(a) AS MATERIALIZED (SELECT json(1)) SELECT subtype(a) FROM t4; +} {0} +do_execsql_test subtype1-231 { + WITH t4(a) AS NOT MATERIALIZED (SELECT json(1)) SELECT subtype(a) FROM t4; +} {0} + + + + finish_test diff --git a/test/swarmvtab3.test b/test/swarmvtab3.test index b062f9e95..8ca247130 100644 --- a/test/swarmvtab3.test +++ b/test/swarmvtab3.test @@ -148,11 +148,13 @@ catch { array unset ::dbcache } # random integer between 0 and 1,000,000 # 0 and 99. do_test 2.1 { + catch { array unset ctx_used } for {set i 0} {$i < 100} {incr i} { while 1 { set ctx [expr abs(int(rand() *1000000))] - if {[info exists ::dbcache($ctx)]==0} break + if {[info exists ctx_used($ctx)]==0} break } + set ctx_used($ctx) 1 set file test_remote.db$ctx forcedelete $file diff --git a/test/tester.tcl b/test/tester.tcl index bfcc831ff..f65182885 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -184,7 +184,7 @@ proc get_pwd {} { set comSpec {C:\Windows\system32\cmd.exe} } return [string map [list \\ /] \ - [string trim [exec -- $comSpec /c echo %CD%]]] + [string trim [exec -- $comSpec /c CD]]] } else { return [pwd] } @@ -1011,6 +1011,7 @@ proc query_plan_graph {sql} { append a [append_graph " " dx cx 0] regsub -all { 0x[A-F0-9]+\y} $a { xxxxxx} a regsub -all {(MATERIALIZE|CO-ROUTINE|SUBQUERY) \d+\y} $a {\1 xxxxxx} a + regsub -all {\((join|subquery)-\d+\)} $a {(\1-xxxxxx)} a return $a } @@ -2477,7 +2478,7 @@ proc test_find_binary {nm} { } # Find the name of the 'shell' executable (e.g. "sqlite3.exe") to use for -# the tests in shell[1-5].test. If no such executable can be found, invoke +# the tests in shell*.test. If no such executable can be found, invoke # [finish_test ; return] in the callers context. # proc test_find_cli {} { @@ -2486,6 +2487,37 @@ proc test_find_cli {} { return $prog } +# Find invocation of the 'shell' executable (e.g. "sqlite3.exe") to use +# for the tests in shell*.test with optional valgrind prefix when the +# environment variable SQLITE_CLI_VALGRIND_OPT is set. The set value +# operates as follows: +# empty or 0 => no valgrind prefix; +# 1 => valgrind options for memory leak check; +# other => use value as valgrind options. +# If shell not found, invoke [finish_test ; return] in callers context. +# +proc test_cli_invocation {} { + set prog [test_find_binary sqlite3] + if {$prog==""} { return -code return } + set vgrun [expr {[permutation]=="valgrind"}] + if {$vgrun || [info exists ::env(SQLITE_CLI_VALGRIND_OPT)]} { + if {$vgrun} { + set vgo "--quiet" + } else { + set vgo $::env(SQLITE_CLI_VALGRIND_OPT) + } + if {$vgo == 0 || $vgo eq ""} { + return $prog + } elseif {$vgo == 1} { + return "valgrind --quiet --leak-check=yes $prog" + } else { + return "valgrind $vgo $prog" + } + } else { + return $prog + } +} + # Find the name of the 'sqldiff' executable (e.g. "sqlite3.exe") to use for # the tests in sqldiff tests. If no such executable can be found, invoke # [finish_test ; return] in the callers context. diff --git a/test/tkt3935.test b/test/tkt3935.test index abbeb3f86..a4f1f61da 100644 --- a/test/tkt3935.test +++ b/test/tkt3935.test @@ -34,13 +34,13 @@ do_test tkt3935.3 { do_test tkt3935.4 { catchsql { SELECT a FROM (t1) AS t ON b USING(a) } -} {1 {a JOIN clause is required before ON}} +} {1 {near "USING": syntax error}} do_test tkt3935.5 { catchsql { SELECT a FROM (t1) AS t ON b } } {1 {a JOIN clause is required before ON}} do_test tkt3935.6 { catchsql { SELECT a FROM (SELECT * FROM t1) AS t ON b USING(a) } -} {1 {a JOIN clause is required before ON}} +} {1 {near "USING": syntax error}} do_test tkt3935.7 { catchsql { SELECT a FROM (SELECT * FROM t1) AS t ON b } } {1 {a JOIN clause is required before ON}} @@ -49,7 +49,7 @@ do_test tkt3935.8 { } {1 {a JOIN clause is required before ON}} do_test tkt3935.9 { catchsql { SELECT a FROM t1 AS t ON b USING(a) } -} {1 {a JOIN clause is required before ON}} +} {1 {near "USING": syntax error}} do_test tkt3935.10 { catchsql { SELECT a FROM t1 AS t USING(a) } } {1 {a JOIN clause is required before USING}} diff --git a/test/upfrom4.test b/test/upfrom4.test new file mode 100644 index 000000000..b20e9638a --- /dev/null +++ b/test/upfrom4.test @@ -0,0 +1,130 @@ +# 2022-05-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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix upfrom4 + +do_execsql_test 100 { + DROP TABLE IF EXISTS t5; + DROP TABLE IF EXISTS m1; + DROP TABLE IF EXISTS m2; + CREATE TABLE t5(a INTEGER PRIMARY KEY, b TEXT, c TEXT); + CREATE TABLE m1(x INTEGER PRIMARY KEY, y TEXT); + CREATE TABLE m2(u INTEGER PRIMARY KEY, v TEXT); + + INSERT INTO t5 VALUES(1, 'one', 'ONE'); + INSERT INTO t5 VALUES(2, 'two', 'TWO'); + INSERT INTO t5 VALUES(3, 'three', 'THREE'); + INSERT INTO t5 VALUES(4, 'four', 'FOUR'); + + INSERT INTO m1 VALUES(1, 'i'); + INSERT INTO m1 VALUES(2, 'ii'); + INSERT INTO m1 VALUES(3, 'iii'); + + INSERT INTO m2 VALUES(1, 'I'); + INSERT INTO m2 VALUES(3, 'II'); + INSERT INTO m2 VALUES(4, 'III'); + SELECT * FROM t5; +} {1 one ONE 2 two TWO 3 three THREE 4 four FOUR} + +do_execsql_test 110 { + BEGIN; + UPDATE t5 SET b=y, c=v FROM m1 LEFT JOIN m2 ON (x=u) WHERE x=a; + SELECT * FROM t5 ORDER BY a; + ROLLBACK; +} {1 i I 2 ii {} 3 iii II 4 four FOUR} + +do_execsql_test 120 { + BEGIN; + UPDATE t5 SET b=y, c=v FROM m2 RIGHT JOIN m1 ON (x=u) WHERE x=a; + SELECT * FROM t5 ORDER BY a; + ROLLBACK; +} {1 i I 2 ii {} 3 iii II 4 four FOUR} + + +reset_db +db null - +do_execsql_test 200 { + CREATE TABLE t1(a INT PRIMARY KEY, b INT, c INT); + INSERT INTO t1(a) VALUES(1),(2),(8),(19); + CREATE TABLE c1(x INTEGER PRIMARY KEY, b INT); + INSERT INTO c1(x,b) VALUES(1,1),(8,8),(17,17),(NULL,NULL); + CREATE TABLE c2(x INT,c INT); + INSERT INTO c2(x,c) VALUES(2,2),(8,8),(NULL,NULL); + CREATE TABLE dual(dummy TEXT); + INSERT INTO dual VALUES('X'); +} {} +do_execsql_test 210 { + BEGIN; + SELECT * FROM t1 ORDER BY a; + UPDATE t1 SET b=c1.b, c=c2.c + FROM dual, c1 NATURAL RIGHT JOIN c2 + WHERE x=a; + SELECT * FROM t1 ORDER BY a; + ROLLBACK; +} { + 1 - - + 2 - - + 8 - - + 19 - - + 1 - - + 2 - 2 + 8 8 8 + 19 - - +} +do_execsql_test 300 { + CREATE TABLE t2(x); + CREATE TRIGGER AFTER INSERT ON t2 BEGIN + UPDATE t1 SET b=c1.b, c=c2.c + FROM dual, c1 NATURAL RIGHT JOIN c2 + WHERE x=a; + END; +} {} +do_execsql_test 310 { + BEGIN; + SELECT * FROM t1 ORDER BY a; + INSERT INTO t2(x) VALUES(1); + SELECT * FROM t1 ORDER BY a; + ROLLBACK; +} { + 1 - - + 2 - - + 8 - - + 19 - - + 1 - - + 2 - 2 + 8 8 8 + 19 - - +} + +# 2022-05-26 dbsqlfuzz crash-9401d6ba699f1257d352a657de236286bf2b14da +# +reset_db +db null - +do_execsql_test 400 { + CREATE TABLE t2(x,y,z PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t2 VALUES(89,-89,6); + CREATE TABLE t1(a INT,b TEXT,c TEXT,d REAL) STRICT; + INSERT INTO t1 VALUES(1,'xyz','def',4.5); + CREATE TRIGGER t1tr BEFORE UPDATE ON t1 BEGIN + INSERT INTO t1(a,b) VALUES(1000,'uvw'); + UPDATE t1 SET b=NULL FROM (SELECT CAST(a AS varchar) FROM t1 ORDER BY b) NATURAL LEFT FULL JOIN t1 AS text; + END; + UPDATE t1 SET b=b|100; + SELECT * FROM t1 ORDER BY a; +} { + 1 100 def 4.5 + 1000 - - - +} + +finish_test diff --git a/test/vtab6.test b/test/vtab6.test index ffbe430c6..1c220e11f 100644 --- a/test/vtab6.test +++ b/test/vtab6.test @@ -223,11 +223,11 @@ do_test vtab6-2.2 { SELECT * FROM t2 NATURAL LEFT OUTER JOIN t1; } } {1 2 3 {} 2 3 4 1 3 4 5 2} -do_test vtab6-2.3 { - catchsql { - SELECT * FROM t1 NATURAL RIGHT OUTER JOIN t2; - } -} {1 {RIGHT and FULL OUTER JOINs are not currently supported}} +#do_test vtab6-2.3 { +# catchsql { +# SELECT * FROM t1 NATURAL RIGHT OUTER JOIN t2; +# } +#} {1 {RIGHT and FULL OUTER JOINs are not currently supported}} do_test vtab6-2.4 { execsql { SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.d @@ -263,7 +263,7 @@ do_test vtab6-3.3 { catchsql { SELECT * FROM t1 JOIN t2 ON t1.a=t2.b USING(b); } -} {1 {cannot have both ON and USING clauses in the same join}} +} {1 {near "USING": syntax error}} do_test vtab6-3.4 { catchsql { SELECT * FROM t1 JOIN t2 USING(a); @@ -277,16 +277,20 @@ do_test vtab6-3.6 { SELECT * FROM t1 JOIN t2 ON t3.a=t2.b; } } {1 {no such column: t3.a}} + +# EVIDENCE-OF: R-47973-48020 you cannot say "INNER OUTER JOIN", because +# that would be contradictory. do_test vtab6-3.7 { catchsql { SELECT * FROM t1 INNER OUTER JOIN t2; } -} {1 {unknown or unsupported join type: INNER OUTER}} +} {1 {unknown join type: INNER OUTER}} + do_test vtab6-3.7 { catchsql { SELECT * FROM t1 LEFT BOGUS JOIN t2; } -} {1 {unknown or unsupported join type: LEFT BOGUS}} +} {1 {unknown join type: LEFT BOGUS}} do_test vtab6-4.1 { execsql { diff --git a/test/where.test b/test/where.test index 8ee57b8b6..e28861bc1 100644 --- a/test/where.test +++ b/test/where.test @@ -11,7 +11,6 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing the use of indices in WHERE clases. # -# $Id: where.test,v 1.50 2008/11/03 09:06:06 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -1348,16 +1347,25 @@ do_execsql_test where-18.1 { INSERT INTO t181 VALUES(1); SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY c IS NULL; } {1} +do_execsql_test where-18.1rj { + SELECT DISTINCT a FROM t182 RIGHT JOIN t181 ON a=b ORDER BY c IS NULL; +} {1} do_execsql_test where-18.2 { SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY +c; } {1} do_execsql_test where-18.3 { SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY c; } {1} +do_execsql_test where-18.3rj { + SELECT DISTINCT a FROM t182 RIGHT JOIN t181 ON a=b ORDER BY c; +} {1} do_execsql_test where-18.4 { INSERT INTO t181 VALUES(1),(1),(1),(1); SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY +c; } {1} +do_execsql_test where-18.4rj { + SELECT DISTINCT a FROM t182 RIGHT JOIN t181 ON a=b ORDER BY +c; +} {1} do_execsql_test where-18.5 { INSERT INTO t181 VALUES(2); SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY c IS NULL, +a; @@ -1594,4 +1602,19 @@ if {[permutation]!="valgrind"} { } {0} } +# 2022-05-10 dbsqlfuzz 4c5e3e89bc251d28378be88233f531b84ec66901 +# +reset_db +do_execsql_test where-28.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); + CREATE INDEX t1b ON t1(b,b,b,b,b,b,b,b,b,b,b,b,b); + INSERT INTO t1(a,b) VALUES(1,1),(15,2),(19,5); + UPDATE t1 SET b=999 WHERE a IN (SELECT 15) AND b IN (1,2); + SELECT * FROM t1; +} { + 1 1 + 15 999 + 19 5 +} + finish_test diff --git a/test/where9.test b/test/where9.test index 429708f1f..475788153 100644 --- a/test/where9.test +++ b/test/where9.test @@ -378,9 +378,9 @@ ifcapable explain { |--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?) `--MULTI-INDEX OR |--INDEX 1 - | `--SEARCH t2 USING INDEX t2d (d=?) + | `--SEARCH t2 USING INDEX t2d (d=?) LEFT-JOIN `--INDEX 2 - `--SEARCH t2 USING COVERING INDEX t2f (f=?) + `--SEARCH t2 USING COVERING INDEX t2f (f=?) LEFT-JOIN }] } diff --git a/test/window1.test b/test/window1.test index 84331d115..fd71ed6bb 100644 --- a/test/window1.test +++ b/test/window1.test @@ -2203,5 +2203,10 @@ do_catchsql_test 71.0 { ORDER BY b; } {/1 {.*}/} +do_execsql_test 72.1 { + CREATE TABLE dual(dummy); INSERT INTO dual VALUES('X'); + CREATE VIEW v1(x,y) AS SELECT RANK() OVER (PARTITION BY 0), SUM(0) FROM dual; + SELECT * FROM v1 WHERE true; +} {1 0} finish_test diff --git a/test/windowD.test b/test/windowD.test new file mode 100644 index 000000000..f2ec36a44 --- /dev/null +++ b/test/windowD.test @@ -0,0 +1,94 @@ +# 2022 June 2 +# +# 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 windowD + + +do_execsql_test 1.0 { + CREATE TABLE t0(c0 TEXT); + CREATE VIEW v0(c0, c1) + AS SELECT CUME_DIST() OVER (PARTITION BY t0.c0), TRUE FROM t0; + INSERT INTO t0 VALUES ('x'); +} + +do_execsql_test 1.1 { + SELECT ('500') IS (v0.c1) FROM v0; +} { + 0 +} + +do_execsql_test 1.2 { + SELECT (('500') IS (v0.c1)) FROM v0, t0; +} { + 0 +} + +do_execsql_test 1.2 { + SELECT (('500') IS (v0.c1)) IS FALSE FROM v0; +} { + 1 +} + +do_execsql_test 1.3 { + SELECT * FROM v0; +} { + 1.0 1 +} + +do_execsql_test 1.4 { + SELECT * FROM v0 WHERE ('500' IS v0.c1) IS FALSE; +} { + 1.0 1 +} + +#------------------------------------------------------------------------- + +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('value'); + CREATE VIEW v1(a, b, c, d) AS SELECT 1, 2, TRUE, FALSE FROM t1; +} + +do_execsql_test 2.1 { + SELECT 500 IS a, 500 IS b, 500 IS c, 500 IS d FROM v1 +} {0 0 0 0} + +do_execsql_test 2.2 { + SELECT * FROM v1 WHERE 500 IS c; +} {} + +do_execsql_test 2.3 { + SELECT * FROM v1 WHERE 500 IS d; +} {} + +do_execsql_test 2.4 { + CREATE VIEW v2 AS SELECT max(x) OVER () AS a, TRUE AS c FROM t1; +} + +do_execsql_test 2.5 { + SELECT 500 IS c FROM v2; +} 0 + +do_execsql_test 2.6 { + SELECT * FROM v2 WHERE 500 IS c; +} {} + + + + + + +finish_test + diff --git a/test/with1.test b/test/with1.test index 5765399d1..91cbc7952 100644 --- a/test/with1.test +++ b/test/with1.test @@ -1075,10 +1075,10 @@ do_execsql_test 21.2 { # Make sure crazy nexted CTE joins terminate with an error quickly. # do_catchsql_test 22.1 { - WITH RECURSIVE c AS ( - WITH RECURSIVE c AS ( - WITH RECURSIVE c AS ( - WITH RECURSIVE c AS ( + WITH RECURSIVE c AS NOT MATERIALIZED ( + WITH RECURSIVE c AS NOT MATERIALIZED ( + WITH RECURSIVE c AS NOT MATERIALIZED ( + WITH RECURSIVE c AS NOT MATERIALIZED ( WITH c AS (VALUES(0)) SELECT 1 FROM c LEFT JOIN c ON ltrim(1) ) diff --git a/test/with6.test b/test/with6.test index 333147d8b..20e518b01 100644 --- a/test/with6.test +++ b/test/with6.test @@ -229,10 +229,12 @@ do_eqp_test 211 { } { QUERY PLAN |--MATERIALIZE c1 - | |--MATERIALIZE c + | |--CO-ROUTINE c | | `--SCAN 3 CONSTANT ROWS | `--SCAN c |--MATERIALIZE c2 + | |--CO-ROUTINE c + | | `--SCAN 3 CONSTANT ROWS | `--SCAN c |--SCAN c1 |--SCAN c2 @@ -250,6 +252,71 @@ do_execsql_test 220 { SELECT y FROM t2 ORDER BY y; } {40404 40405 40406 40504 40505 40506 40604 40605 40606} +# 2022-04-22: Do not allow flattening of a MATERIALIZED CTE into +# an outer query. +# +reset_db +db null - +do_execsql_test 300 { + CREATE TABLE t2(a INT,b INT,d INT); INSERT INTO t2 VALUES(4,5,6),(7,8,9); + CREATE TABLE t3(a INT,b INT,e INT); INSERT INTO t3 VALUES(3,3,3),(8,8,8); +} {} +do_execsql_test 310 { + WITH t23 AS MATERIALIZED (SELECT * FROM t2 FULL JOIN t3 USING(b)) + SELECT * FROM t23; +} { + 4 5 6 - - + 7 8 9 8 8 + - 3 - 3 3 +} +do_eqp_test 311 { + WITH t23 AS MATERIALIZED (SELECT * FROM t2 FULL JOIN t3 USING(b)) + SELECT * FROM t23; +} { + QUERY PLAN + |--MATERIALIZE t23 + | |--SCAN t2 + | |--SCAN t3 LEFT-JOIN + | `--RIGHT-JOIN t3 + | `--SCAN t3 + `--SCAN t23 +} +do_execsql_test 320 { + WITH t23 AS NOT MATERIALIZED (SELECT * FROM t2 FULL JOIN t3 USING(b)) + SELECT * FROM t23; +} { + 4 5 6 - - + 7 8 9 8 8 + - 3 - 3 3 +} +do_eqp_test 321 { + WITH t23 AS NOT MATERIALIZED (SELECT * FROM t2 FULL JOIN t3 USING(b)) + SELECT * FROM t23; +} { + QUERY PLAN + |--SCAN t2 + |--SCAN t3 LEFT-JOIN + `--RIGHT-JOIN t3 + `--SCAN t3 +} +do_execsql_test 330 { + WITH t23 AS (SELECT * FROM t2 FULL JOIN t3 USING(b)) + SELECT * FROM t23; +} { + 4 5 6 - - + 7 8 9 8 8 + - 3 - 3 3 +} +do_eqp_test 331 { + WITH t23 AS (SELECT * FROM t2 FULL JOIN t3 USING(b)) + SELECT * FROM t23; +} { + QUERY PLAN + |--SCAN t2 + |--SCAN t3 LEFT-JOIN + `--RIGHT-JOIN t3 + `--SCAN t3 +} finish_test diff --git a/tool/dbtotxt.c b/tool/dbtotxt.c index 8b0600fa5..a56416dcc 100644 --- a/tool/dbtotxt.c +++ b/tool/dbtotxt.c @@ -10,7 +10,11 @@ ** ** Usage: ** -** dbtotxt [--pagesize N] FILENAME +** dbtotxt [OPTIONS] FILENAME +** +** where OPTIONS are zero or more of: +** --pagesize N => setting the database page size for later readin +** --for-cli => prepending '.open --hexdb' to the output ** ** The translation of the database appears on standard output. If the ** --pagesize command-line option is omitted, then the page size is taken @@ -38,6 +42,7 @@ static int allZero(unsigned char *aLine){ int main(int argc, char **argv){ int pgsz = 0; /* page size */ + int forCli = 0; /* whether to prepend with .open */ long szFile; /* Size of the input file in bytes */ FILE *in; /* Input file */ int i, j; /* Loop counters */ @@ -67,6 +72,9 @@ int main(int argc, char **argv){ nErr++; } continue; + }else if( strcmp(z,"for-cli")==0 ){ + forCli = 1; + continue; } fprintf(stderr, "Unknown option: %s\n", argv[i]); nErr++; @@ -114,6 +122,9 @@ int main(int argc, char **argv){ for(i=0; zInputFile[i]; i++){ if( zInputFile[i]=='/' && zInputFile[i+1]!=0 ) zBaseName = zInputFile+i+1; } + if( forCli ){ + printf(".open --hexdb\n"); + } printf("| size %d pagesize %d filename %s\n",(int)szFile,pgsz,zBaseName); for(i=0; i=0} { if {[regexp $BEGIN $line]} break + if {$N>0} { + write_one_file $all + set N 0 + set all {} + } puts $out1 $line } } From 4fec0dc8ad0f48040f6145bed974f6bab45a9849 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 25 Jul 2022 13:00:03 -0400 Subject: [PATCH 071/340] remove redefines for PAGER_MJ_PGNO to PAGER_SJ_PGNO --- src/crypto.c | 2 +- src/crypto.h | 2 +- src/crypto_impl.c | 8 ++------ src/pager.c | 8 ++------ 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 37bb54f84..5dcda8492 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -978,7 +978,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { rc = sqlite3BtreeBeginTrans(pDb->pBt, 1, 0); /* begin write transaction */ sqlite3PagerPagecount(pPager, &page_count); for(pgno = 1; rc == SQLITE_OK && pgno <= (unsigned int)page_count; pgno++) { /* pgno's start at 1 see pager.c:pagerAcquire */ - if(!sqlite3pager_is_mj_pgno(pPager, pgno)) { /* skip this page (see pager.c:pagerAcquire for reasoning) */ + if(!sqlite3pager_is_sj_pgno(pPager, pgno)) { /* skip this page (see pager.c:pagerAcquire for reasoning) */ rc = sqlite3PagerGet(pPager, pgno, &page, 0); if(rc == SQLITE_OK) { /* write page see pager_incr_changecounter for example */ rc = sqlite3PagerWrite(page); diff --git a/src/crypto.h b/src/crypto.h index 0ecb9a66e..db9534bc9 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -66,7 +66,7 @@ /* extensions defined in pager.c */ void *sqlcipherPagerGetCodec(Pager*); void sqlcipherPagerSetCodec(Pager*, void *(*)(void*,void*,Pgno,int), void (*)(void*,int,int), void (*)(void*), void *); -int sqlite3pager_is_mj_pgno(Pager*, Pgno); +int sqlite3pager_is_sj_pgno(Pager*, Pgno); void sqlite3pager_error(Pager*, int); void sqlite3pager_reset(Pager *pPager); /* end extensions defined in pager.c */ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index f28c315da..9b580e7a2 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -34,10 +34,6 @@ #include "sqlcipher.h" #include "crypto.h" -#ifndef PAGER_MJ_PGNO -#define PAGER_MJ_PGNO(x) PAGER_SJ_PGNO(x) -#endif - #ifdef SQLCIPHER_TEST static volatile unsigned int cipher_test_flags = 0; unsigned int sqlcipher_get_test_flags() { @@ -1382,8 +1378,8 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *col int payload_sz = ctx->page_sz - ctx->reserve_sz + ctx->iv_sz; int read_sz = ctx->page_sz; - /* skip integrity check on PAGER_MJ_PGNO since it will have no valid content */ - if(sqlite3pager_is_mj_pgno(ctx->pBt->pBt->pPager, page)) continue; + /* skip integrity check on PAGER_SJ_PGNO since it will have no valid content */ + if(sqlite3pager_is_sj_pgno(ctx->pBt->pBt->pPager, page)) continue; if(page==1) { int page1_offset = ctx->plaintext_header_sz ? ctx->plaintext_header_sz : FILE_HEADER_SZ; diff --git a/src/pager.c b/src/pager.c index 34f037d8c..2545e6cc9 100644 --- a/src/pager.c +++ b/src/pager.c @@ -7906,12 +7906,8 @@ int sqlite3PagerWalFramesize(Pager *pPager){ /* BEGIN SQLCIPHER */ #ifdef SQLITE_HAS_CODEC -#ifndef PAGER_MJ_PGNO -#define PAGER_MJ_PGNO(x) PAGER_SJ_PGNO(x) -#endif - -int sqlite3pager_is_mj_pgno(Pager *pPager, Pgno pgno) { - return (PAGER_MJ_PGNO(pPager) == pgno) ? 1 : 0; +int sqlite3pager_is_sj_pgno(Pager *pPager, Pgno pgno) { + return (PAGER_SJ_PGNO(pPager) == pgno) ? 1 : 0; } void sqlite3pager_error(Pager *pPager, int error) { From 8ca640647e8678bf1b38a0fdd70e460165acbd76 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 25 Jul 2022 13:04:56 -0400 Subject: [PATCH 072/340] update changelog to reflect upstream SQLite version --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f0599774..8cd24b097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. ## [unreleased] - (? 2022 - [unreleased changes]) -- Updates source code baseline to upstream SQLite 3.38.3 +- Updates source code baseline to upstream SQLite 3.39.2 - Simplifies OpenSSL version conditional code - Fixes issue where PRAGMA cipher_memory_security could report OFF when it was actually ON - Fixes fix unfreed OpenSSL allocation when compiled against version 3 From 9245370c2591a649503fc5a7732f6df27b73f8d5 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 25 Jul 2022 13:34:34 -0400 Subject: [PATCH 073/340] update podspec version --- SQLCipher.podspec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 91ce7bc06..4c56637e9 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -18,7 +18,7 @@ "tag": "v4.5.1" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.1", + "version": "4.5.2", "subspecs": [ { "compiler_flags": [ From 8d571c8c5ab910ecbdae7d3ee9d232d868977a87 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 26 Jul 2022 10:35:50 -0400 Subject: [PATCH 074/340] adjust log file pointer --- src/crypto_impl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 9b580e7a2..7dd3b0f9a 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -78,7 +78,7 @@ static volatile sqlite3_mem_methods default_mem_methods; static sqlcipher_provider *default_provider = NULL; static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; -static volatile FILE* sqlcipher_log_file = NULL; +static FILE* sqlcipher_log_file = NULL; static volatile int sqlcipher_log_logcat = 0; static volatile unsigned int sqlcipher_log_level = SQLCIPHER_LOG_NONE; From c1f02fd9689da4c27b4ce98be3d4cfb34db7980e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 26 Jul 2022 10:45:55 -0400 Subject: [PATCH 075/340] changelog update --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd24b097..103f7aa6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## [unreleased] - (? 2022 - [unreleased changes]) + +## [4.5.2] - (August 2022 - [4.5.2 changes]) - Updates source code baseline to upstream SQLite 3.39.2 - Simplifies OpenSSL version conditional code - Fixes issue where PRAGMA cipher_memory_security could report OFF when it was actually ON @@ -208,7 +210,9 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 -[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.1...prerelease +[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.2...prerelease +[4.5.2]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.2 +[4.5.2 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.1...v4.5.2 [4.5.1]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.1 [4.5.1 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.0...v4.5.1 [4.5.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.0 From a621277b17f4892960e0010845c7bb050f924eeb Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 12 Aug 2022 15:23:50 -0400 Subject: [PATCH 076/340] version bump to 4.5.3 --- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index db9534bc9..12fc0aaf6 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -84,7 +84,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.2 +#define CIPHER_VERSION_NUMBER 4.5.3 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index 6c5f01388..0f1bf15b3 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.2 community}} +} {{4.5.3 community}} db close file delete -force test.db From f1a9283217607e898f8561d7d17ff81d850210b3 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 31 Aug 2022 12:03:13 -0400 Subject: [PATCH 077/340] avoid merge conflict on upstream distclean change --- Makefile.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index e685d642b..2a1d6baeb 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1531,7 +1531,8 @@ clean: rm -f threadtest5 distclean: clean - rm -f config.h config.log config.status libtool Makefile sqlcipher.pc + rm -f config.h config.log config.status libtool Makefile sqlite3.pc \ + $(TESTPROGS) # # Windows section From 3063f0cfc77aa9772901c8d92755fd6a64b20cc7 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 30 Sep 2022 11:36:35 -0400 Subject: [PATCH 078/340] Snapshot of upstream SQLite 3.39.4 --- VERSION | 2 +- configure | 18 +++++------ ext/fts3/fts3.c | 6 ++-- ext/fts3/fts3Int.h | 2 +- ext/fts3/fts3_porter.c | 2 +- ext/fts3/fts3_tokenizer1.c | 2 +- ext/fts3/fts3_write.c | 62 ++++++++++++++++++------------------ ext/icu/icu.c | 5 +-- ext/rtree/geopoly.c | 2 +- manifest | 64 +++++++++++++++++++------------------- manifest.uuid | 2 +- src/btree.c | 61 +++++++++++++++++------------------- src/malloc.c | 5 +++ src/os_unix.c | 31 +++++++++++------- src/os_win.c | 47 ++++++++++++++++++++++++---- src/pager.c | 3 +- src/pcache.c | 10 ++++++ src/pcache1.c | 17 +++++----- src/pragma.c | 6 ++++ src/select.c | 11 +++++-- src/sqliteInt.h | 5 +++ src/trigger.c | 17 ++++++++++ src/util.c | 2 +- src/vdbeaux.c | 2 ++ src/wherecode.c | 3 +- test/json101.test | 18 +++++++++++ test/offset1.test | 43 ++++++++++++++++++++++++- test/select3.test | 8 +++++ 28 files changed, 310 insertions(+), 146 deletions(-) diff --git a/VERSION b/VERSION index 5f7b534c9..bd5311787 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.39.2 +3.39.4 diff --git a/configure b/configure index 24135817d..367b1485f 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.69 for sqlite 3.39.2. +# Generated by GNU Autoconf 2.69 for sqlite 3.39.4. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.39.2' -PACKAGE_STRING='sqlite 3.39.2' +PACKAGE_VERSION='3.39.4' +PACKAGE_STRING='sqlite 3.39.4' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1468,7 +1468,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.39.2 to adapt to many kinds of systems. +\`configure' configures sqlite 3.39.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1533,7 +1533,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.39.2:";; + short | recursive ) echo "Configuration of sqlite 3.39.4:";; esac cat <<\_ACEOF @@ -1661,7 +1661,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.39.2 +sqlite configure 3.39.4 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2080,7 +2080,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.39.2, which was +It was created by sqlite $as_me 3.39.4, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12390,7 +12390,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.39.2, which was +This file was extended by sqlite $as_me 3.39.4, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12456,7 +12456,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.39.2 +sqlite config.status 3.39.4 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 1a1672512..c43eac491 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -2888,7 +2888,7 @@ static int fts3TermSelectMerge( ** ** Similar padding is added in the fts3DoclistOrMerge() function. */ - pTS->aaOutput[0] = sqlite3_malloc(nDoclist + FTS3_VARINT_MAX + 1); + pTS->aaOutput[0] = sqlite3_malloc64((i64)nDoclist + FTS3_VARINT_MAX + 1); pTS->anOutput[0] = nDoclist; if( pTS->aaOutput[0] ){ memcpy(pTS->aaOutput[0], aDoclist, nDoclist); @@ -4376,7 +4376,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ nDistance = iPrev - nMaxUndeferred; } - aOut = (char *)sqlite3_malloc(nPoslist+8); + aOut = (char *)sqlite3Fts3MallocZero(nPoslist+FTS3_BUFFER_PADDING); if( !aOut ){ sqlite3_free(aPoslist); return SQLITE_NOMEM; @@ -4745,7 +4745,7 @@ static int fts3EvalIncrPhraseNext( if( bEof==0 ){ int nList = 0; int nByte = a[p->nToken-1].nList; - char *aDoclist = sqlite3_malloc(nByte+FTS3_BUFFER_PADDING); + char *aDoclist = sqlite3_malloc64((i64)nByte+FTS3_BUFFER_PADDING); if( !aDoclist ) return SQLITE_NOMEM; memcpy(aDoclist, a[p->nToken-1].pList, nByte+1); memset(&aDoclist[nByte], 0, FTS3_BUFFER_PADDING); diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index 0626486ed..e821d6be3 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -558,7 +558,7 @@ struct Fts3MultiSegReader { int nAdvance; /* How many seg-readers to advance */ Fts3SegFilter *pFilter; /* Pointer to filter object */ char *aBuffer; /* Buffer to merge doclists in */ - int nBuffer; /* Allocated size of aBuffer[] in bytes */ + i64 nBuffer; /* Allocated size of aBuffer[] in bytes */ int iColFilter; /* If >=0, filter for this column */ int bRestart; diff --git a/ext/fts3/fts3_porter.c b/ext/fts3/fts3_porter.c index 8fb4c25da..fbe791302 100644 --- a/ext/fts3/fts3_porter.c +++ b/ext/fts3/fts3_porter.c @@ -621,7 +621,7 @@ static int porterNext( if( n>c->nAllocated ){ char *pNew; c->nAllocated = n+20; - pNew = sqlite3_realloc(c->zToken, c->nAllocated); + pNew = sqlite3_realloc64(c->zToken, c->nAllocated); if( !pNew ) return SQLITE_NOMEM; c->zToken = pNew; } diff --git a/ext/fts3/fts3_tokenizer1.c b/ext/fts3/fts3_tokenizer1.c index deea06d92..78e5889da 100644 --- a/ext/fts3/fts3_tokenizer1.c +++ b/ext/fts3/fts3_tokenizer1.c @@ -185,7 +185,7 @@ static int simpleNext( if( n>c->nTokenAllocated ){ char *pNew; c->nTokenAllocated = n+20; - pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated); + pNew = sqlite3_realloc64(c->pToken, c->nTokenAllocated); if( !pNew ) return SQLITE_NOMEM; c->pToken = pNew; } diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index f1a4bd6bb..6a727eaf5 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -649,7 +649,7 @@ static int fts3PendingListAppendVarint( /* Allocate or grow the PendingList as required. */ if( !p ){ - p = sqlite3_malloc(sizeof(*p) + 100); + p = sqlite3_malloc64(sizeof(*p) + 100); if( !p ){ return SQLITE_NOMEM; } @@ -658,14 +658,14 @@ static int fts3PendingListAppendVarint( p->nData = 0; } else if( p->nData+FTS3_VARINT_MAX+1>p->nSpace ){ - int nNew = p->nSpace * 2; - p = sqlite3_realloc(p, sizeof(*p) + nNew); + i64 nNew = p->nSpace * 2; + p = sqlite3_realloc64(p, sizeof(*p) + nNew); if( !p ){ sqlite3_free(*pp); *pp = 0; return SQLITE_NOMEM; } - p->nSpace = nNew; + p->nSpace = (int)nNew; p->aData = (char *)&p[1]; } @@ -1222,7 +1222,7 @@ int sqlite3Fts3ReadBlock( int nByte = sqlite3_blob_bytes(p->pSegments); *pnBlob = nByte; if( paBlob ){ - char *aByte = sqlite3_malloc(nByte + FTS3_NODE_PADDING); + char *aByte = sqlite3_malloc64((i64)nByte + FTS3_NODE_PADDING); if( !aByte ){ rc = SQLITE_NOMEM; }else{ @@ -1339,7 +1339,7 @@ static int fts3SegReaderNext( int nTerm = fts3HashKeysize(pElem); if( (nTerm+1)>pReader->nTermAlloc ){ sqlite3_free(pReader->zTerm); - pReader->zTerm = (char*)sqlite3_malloc((nTerm+1)*2); + pReader->zTerm = (char*)sqlite3_malloc64(((i64)nTerm+1)*2); if( !pReader->zTerm ) return SQLITE_NOMEM; pReader->nTermAlloc = (nTerm+1)*2; } @@ -1347,7 +1347,7 @@ static int fts3SegReaderNext( pReader->zTerm[nTerm] = '\0'; pReader->nTerm = nTerm; - aCopy = (char*)sqlite3_malloc(nCopy); + aCopy = (char*)sqlite3_malloc64(nCopy); if( !aCopy ) return SQLITE_NOMEM; memcpy(aCopy, pList->aData, nCopy); pReader->nNode = pReader->nDoclist = nCopy; @@ -1634,7 +1634,7 @@ int sqlite3Fts3SegReaderNew( nExtra = nRoot + FTS3_NODE_PADDING; } - pReader = (Fts3SegReader *)sqlite3_malloc(sizeof(Fts3SegReader) + nExtra); + pReader = (Fts3SegReader *)sqlite3_malloc64(sizeof(Fts3SegReader) + nExtra); if( !pReader ){ return SQLITE_NOMEM; } @@ -1726,7 +1726,7 @@ int sqlite3Fts3SegReaderPending( if( nElem==nAlloc ){ Fts3HashElem **aElem2; nAlloc += 16; - aElem2 = (Fts3HashElem **)sqlite3_realloc( + aElem2 = (Fts3HashElem **)sqlite3_realloc64( aElem, nAlloc*sizeof(Fts3HashElem *) ); if( !aElem2 ){ @@ -2060,7 +2060,7 @@ static int fts3NodeAddTerm( ** this is not expected to be a serious problem. */ assert( pTree->aData==(char *)&pTree[1] ); - pTree->aData = (char *)sqlite3_malloc(nReq); + pTree->aData = (char *)sqlite3_malloc64(nReq); if( !pTree->aData ){ return SQLITE_NOMEM; } @@ -2078,7 +2078,7 @@ static int fts3NodeAddTerm( if( isCopyTerm ){ if( pTree->nMalloczMalloc, nTerm*2); + char *zNew = sqlite3_realloc64(pTree->zMalloc, (i64)nTerm*2); if( !zNew ){ return SQLITE_NOMEM; } @@ -2104,7 +2104,7 @@ static int fts3NodeAddTerm( ** now. Instead, the term is inserted into the parent of pTree. If pTree ** has no parent, one is created here. */ - pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize); + pNew = (SegmentNode *)sqlite3_malloc64(sizeof(SegmentNode) + p->nNodeSize); if( !pNew ){ return SQLITE_NOMEM; } @@ -2242,7 +2242,7 @@ static int fts3SegWriterAdd( ){ int nPrefix; /* Size of term prefix in bytes */ int nSuffix; /* Size of term suffix in bytes */ - int nReq; /* Number of bytes required on leaf page */ + i64 nReq; /* Number of bytes required on leaf page */ int nData; SegmentWriter *pWriter = *ppWriter; @@ -2251,13 +2251,13 @@ static int fts3SegWriterAdd( sqlite3_stmt *pStmt; /* Allocate the SegmentWriter structure */ - pWriter = (SegmentWriter *)sqlite3_malloc(sizeof(SegmentWriter)); + pWriter = (SegmentWriter *)sqlite3_malloc64(sizeof(SegmentWriter)); if( !pWriter ) return SQLITE_NOMEM; memset(pWriter, 0, sizeof(SegmentWriter)); *ppWriter = pWriter; /* Allocate a buffer in which to accumulate data */ - pWriter->aData = (char *)sqlite3_malloc(p->nNodeSize); + pWriter->aData = (char *)sqlite3_malloc64(p->nNodeSize); if( !pWriter->aData ) return SQLITE_NOMEM; pWriter->nSize = p->nNodeSize; @@ -2332,7 +2332,7 @@ static int fts3SegWriterAdd( ** the buffer to make it large enough. */ if( nReq>pWriter->nSize ){ - char *aNew = sqlite3_realloc(pWriter->aData, nReq); + char *aNew = sqlite3_realloc64(pWriter->aData, nReq); if( !aNew ) return SQLITE_NOMEM; pWriter->aData = aNew; pWriter->nSize = nReq; @@ -2357,7 +2357,7 @@ static int fts3SegWriterAdd( */ if( isCopyTerm ){ if( nTerm>pWriter->nMalloc ){ - char *zNew = sqlite3_realloc(pWriter->zMalloc, nTerm*2); + char *zNew = sqlite3_realloc64(pWriter->zMalloc, (i64)nTerm*2); if( !zNew ){ return SQLITE_NOMEM; } @@ -2665,12 +2665,12 @@ static void fts3ColumnFilter( static int fts3MsrBufferData( Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */ char *pList, - int nList + i64 nList ){ if( nList>pMsr->nBuffer ){ char *pNew; pMsr->nBuffer = nList*2; - pNew = (char *)sqlite3_realloc(pMsr->aBuffer, pMsr->nBuffer); + pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, pMsr->nBuffer); if( !pNew ) return SQLITE_NOMEM; pMsr->aBuffer = pNew; } @@ -2726,7 +2726,7 @@ int sqlite3Fts3MsrIncrNext( fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp); if( nList>0 && fts3SegReaderIsPending(apSegment[0]) ){ - rc = fts3MsrBufferData(pMsr, pList, nList+1); + rc = fts3MsrBufferData(pMsr, pList, (i64)nList+1); if( rc!=SQLITE_OK ) return rc; assert( (pMsr->aBuffer[nList] & 0xFE)==0x00 ); pList = pMsr->aBuffer; @@ -2863,11 +2863,11 @@ int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){ return SQLITE_OK; } -static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, int nReq){ +static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, i64 nReq){ if( nReq>pCsr->nBuffer ){ char *aNew; pCsr->nBuffer = nReq*2; - aNew = sqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer); + aNew = sqlite3_realloc64(pCsr->aBuffer, pCsr->nBuffer); if( !aNew ){ return SQLITE_NOMEM; } @@ -2958,7 +2958,8 @@ int sqlite3Fts3SegReaderStep( ){ pCsr->nDoclist = apSegment[0]->nDoclist; if( fts3SegReaderIsPending(apSegment[0]) ){ - rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist); + rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, + (i64)pCsr->nDoclist); pCsr->aDoclist = pCsr->aBuffer; }else{ pCsr->aDoclist = apSegment[0]->aDoclist; @@ -3011,7 +3012,8 @@ int sqlite3Fts3SegReaderStep( nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0); - rc = fts3GrowSegReaderBuffer(pCsr, nByte+nDoclist+FTS3_NODE_PADDING); + rc = fts3GrowSegReaderBuffer(pCsr, + (i64)nByte+nDoclist+FTS3_NODE_PADDING); if( rc ) return rc; if( isFirst ){ @@ -3037,7 +3039,7 @@ int sqlite3Fts3SegReaderStep( fts3SegReaderSort(apSegment, nMerge, j, xCmp); } if( nDoclist>0 ){ - rc = fts3GrowSegReaderBuffer(pCsr, nDoclist+FTS3_NODE_PADDING); + rc = fts3GrowSegReaderBuffer(pCsr, (i64)nDoclist+FTS3_NODE_PADDING); if( rc ) return rc; memset(&pCsr->aBuffer[nDoclist], 0, FTS3_NODE_PADDING); pCsr->aDoclist = pCsr->aBuffer; @@ -3750,7 +3752,7 @@ struct NodeReader { static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){ if( *pRc==SQLITE_OK && nMin>pBlob->nAlloc ){ int nAlloc = nMin; - char *a = (char *)sqlite3_realloc(pBlob->a, nAlloc); + char *a = (char *)sqlite3_realloc64(pBlob->a, nAlloc); if( a ){ pBlob->nAlloc = nAlloc; pBlob->a = a; @@ -4547,7 +4549,7 @@ static int fts3RepackSegdirLevel( if( nIdx>=nAlloc ){ int *aNew; nAlloc += 16; - aNew = sqlite3_realloc(aIdx, nAlloc*sizeof(int)); + aNew = sqlite3_realloc64(aIdx, nAlloc*sizeof(int)); if( !aNew ){ rc = SQLITE_NOMEM; break; @@ -4921,7 +4923,7 @@ int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ /* Allocate space for the cursor, filter and writer objects */ const int nAlloc = sizeof(*pCsr) + sizeof(*pFilter) + sizeof(*pWriter); - pWriter = (IncrmergeWriter *)sqlite3_malloc(nAlloc); + pWriter = (IncrmergeWriter *)sqlite3_malloc64(nAlloc); if( !pWriter ) return SQLITE_NOMEM; pFilter = (Fts3SegFilter *)&pWriter[1]; pCsr = (Fts3MultiSegReader *)&pFilter[1]; @@ -5557,7 +5559,7 @@ int sqlite3Fts3DeferredTokenList( return SQLITE_OK; } - pRet = (char *)sqlite3_malloc(p->pList->nData); + pRet = (char *)sqlite3_malloc64(p->pList->nData); if( !pRet ) return SQLITE_NOMEM; nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy); @@ -5577,7 +5579,7 @@ int sqlite3Fts3DeferToken( int iCol /* Column that token must appear in (or -1) */ ){ Fts3DeferredToken *pDeferred; - pDeferred = sqlite3_malloc(sizeof(*pDeferred)); + pDeferred = sqlite3_malloc64(sizeof(*pDeferred)); if( !pDeferred ){ return SQLITE_NOMEM; } diff --git a/ext/icu/icu.c b/ext/icu/icu.c index 92d7c5438..e745ab025 100644 --- a/ext/icu/icu.c +++ b/ext/icu/icu.c @@ -299,8 +299,9 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ if( U_SUCCESS(status) ){ sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete); - }else{ - assert(!pExpr); + pExpr = sqlite3_get_auxdata(p, 0); + } + if( !pExpr ){ icuFunctionError(p, "uregex_open", status); return; } diff --git a/ext/rtree/geopoly.c b/ext/rtree/geopoly.c index 68cde8734..7b41e7909 100644 --- a/ext/rtree/geopoly.c +++ b/ext/rtree/geopoly.c @@ -1695,7 +1695,7 @@ static int geopolyUpdate( sqlite3_free(p); nChange = 1; } - for(jj=1; jjnAux; jj++){ + for(jj=1; jj usableSize ) return SQLITE_CORRUPT_PAGE(pPage); memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz)); sz += sz2; - }else if( NEVER(iFree+sz>usableSize) ){ + }else if( iFree+sz>usableSize ){ return SQLITE_CORRUPT_PAGE(pPage); } @@ -7904,8 +7904,6 @@ static int balance_nonroot( Pgno pgno; /* Temp var to store a page number in */ u8 abDone[NB+2]; /* True after i'th new page is populated */ Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */ - Pgno aPgOrder[NB+2]; /* Copy of aPgno[] used for sorting pages */ - u16 aPgFlags[NB+2]; /* flags field of new pages before shuffling */ CellArray b; /* Parsed information on cells being balanced */ memset(abDone, 0, sizeof(abDone)); @@ -8329,42 +8327,39 @@ static int balance_nonroot( ** of the table is closer to a linear scan through the file. That in turn ** helps the operating system to deliver pages from the disk more rapidly. ** - ** An O(n^2) insertion sort algorithm is used, but since n is never more - ** than (NB+2) (a small constant), that should not be a problem. + ** An O(N*N) sort algorithm is used, but since N is never more than NB+2 + ** (5), that is not a performance concern. ** ** When NB==3, this one optimization makes the database about 25% faster ** for large insertions and deletions. */ for(i=0; ipgno; - aPgFlags[i] = apNew[i]->pDbPage->flags; - for(j=0; jpgno; + assert( apNew[i]->pDbPage->flags & PGHDR_WRITEABLE ); + assert( apNew[i]->pDbPage->flags & PGHDR_DIRTY ); } - for(i=0; ii ){ - sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0); - } - sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]); - apNew[i]->pgno = pgno; + for(i=0; ipgno < apNew[iB]->pgno ) iB = j; + } + + /* If apNew[i] has a page number that is bigger than any of the + ** subsequence apNew[i] entries, then swap apNew[i] with the subsequent + ** entry that has the smallest page number (which we know to be + ** entry apNew[iB]). + */ + if( iB!=i ){ + Pgno pgnoA = apNew[i]->pgno; + Pgno pgnoB = apNew[iB]->pgno; + Pgno pgnoTemp = (PENDING_BYTE/pBt->pageSize)+1; + u16 fgA = apNew[i]->pDbPage->flags; + u16 fgB = apNew[iB]->pDbPage->flags; + sqlite3PagerRekey(apNew[i]->pDbPage, pgnoTemp, fgB); + sqlite3PagerRekey(apNew[iB]->pDbPage, pgnoA, fgA); + sqlite3PagerRekey(apNew[i]->pDbPage, pgnoB, fgB); + apNew[i]->pgno = pgnoB; + apNew[iB]->pgno = pgnoA; } } diff --git a/src/malloc.c b/src/malloc.c index cfda60a0b..c508bf752 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -776,8 +776,13 @@ void *sqlite3OomFault(sqlite3 *db){ } DisableLookaside; if( db->pParse ){ + Parse *pParse; sqlite3ErrorMsg(db->pParse, "out of memory"); db->pParse->rc = SQLITE_NOMEM_BKPT; + for(pParse=db->pParse->pOuterParse; pParse; pParse = pParse->pOuterParse){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } } } return 0; diff --git a/src/os_unix.c b/src/os_unix.c index b933de376..81a41263b 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5855,6 +5855,7 @@ static const char *unixTempFileDir(void){ static int unixGetTempname(int nBuf, char *zBuf){ const char *zDir; int iLimit = 0; + int rc = SQLITE_OK; /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this @@ -5863,18 +5864,26 @@ static int unixGetTempname(int nBuf, char *zBuf){ zBuf[0] = 0; SimulateIOError( return SQLITE_IOERR ); + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); zDir = unixTempFileDir(); - if( zDir==0 ) return SQLITE_IOERR_GETTEMPPATH; - do{ - u64 r; - sqlite3_randomness(sizeof(r), &r); - assert( nBuf>2 ); - zBuf[nBuf-2] = 0; - sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c", - zDir, r, 0); - if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ) return SQLITE_ERROR; - }while( osAccess(zBuf,0)==0 ); - return SQLITE_OK; + if( zDir==0 ){ + rc = SQLITE_IOERR_GETTEMPPATH; + }else{ + do{ + u64 r; + sqlite3_randomness(sizeof(r), &r); + assert( nBuf>2 ); + zBuf[nBuf-2] = 0; + sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c", + zDir, r, 0); + if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ){ + rc = SQLITE_ERROR; + break; + } + }while( osAccess(zBuf,0)==0 ); + } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; } #if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) diff --git a/src/os_win.c b/src/os_win.c index 8832c8012..2827b0b49 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -1918,10 +1918,12 @@ int sqlite3_win32_set_directory8( const char *zValue /* New value for directory being set or reset */ ){ char **ppDirectory = 0; + int rc; #ifndef SQLITE_OMIT_AUTOINIT - int rc = sqlite3_initialize(); + rc = sqlite3_initialize(); if( rc ) return rc; #endif + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( type==SQLITE_WIN32_DATA_DIRECTORY_TYPE ){ ppDirectory = &sqlite3_data_directory; }else if( type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE ){ @@ -1936,14 +1938,19 @@ int sqlite3_win32_set_directory8( if( zValue && zValue[0] ){ zCopy = sqlite3_mprintf("%s", zValue); if ( zCopy==0 ){ - return SQLITE_NOMEM_BKPT; + rc = SQLITE_NOMEM_BKPT; + goto set_directory8_done; } } sqlite3_free(*ppDirectory); *ppDirectory = zCopy; - return SQLITE_OK; + rc = SQLITE_OK; + }else{ + rc = SQLITE_ERROR; } - return SQLITE_ERROR; +set_directory8_done: + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; } /* @@ -4717,6 +4724,18 @@ static int winMakeEndInDirSep(int nBuf, char *zBuf){ return 0; } +/* +** If sqlite3_temp_directory is not, take the mutex and return true. +** +** If sqlite3_temp_directory is NULL, omit the mutex and return false. +*/ +static int winTempDirDefined(void){ + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + if( sqlite3_temp_directory!=0 ) return 1; + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return 0; +} + /* ** Create a temporary file name and store the resulting pointer into pzBuf. ** The pointer returned in pzBuf must be freed via sqlite3_free(). @@ -4753,20 +4772,23 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ */ nDir = nMax - (nPre + 15); assert( nDir>0 ); - if( sqlite3_temp_directory ){ + if( winTempDirDefined() ){ int nDirLen = sqlite3Strlen30(sqlite3_temp_directory); if( nDirLen>0 ){ if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){ nDirLen++; } if( nDirLen>nDir ){ + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0); } sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory); } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); } + #if defined(__CYGWIN__) else{ static const char *azDirs[] = { @@ -5555,7 +5577,7 @@ static BOOL winIsVerbatimPathname( ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname ** bytes in size. */ -static int winFullPathname( +static int winFullPathnameNoMutex( sqlite3_vfs *pVfs, /* Pointer to vfs object */ const char *zRelative, /* Possibly relative input path */ int nFull, /* Size of output buffer in bytes */ @@ -5734,6 +5756,19 @@ static int winFullPathname( } #endif } +static int winFullPathname( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + const char *zRelative, /* Possibly relative input path */ + int nFull, /* Size of output buffer in bytes */ + char *zFull /* Output buffer */ +){ + int rc; + sqlite3_mutex *pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR); + sqlite3_mutex_enter(pMutex); + rc = winFullPathnameNoMutex(pVfs, zRelative, nFull, zFull); + sqlite3_mutex_leave(pMutex); + return rc; +} #ifndef SQLITE_OMIT_LOAD_EXTENSION /* diff --git a/src/pager.c b/src/pager.c index 95e6eb8af..dcb72857b 100644 --- a/src/pager.c +++ b/src/pager.c @@ -5821,6 +5821,7 @@ static int pager_open_journal(Pager *pPager){ if( rc!=SQLITE_OK ){ sqlite3BitvecDestroy(pPager->pInJournal); pPager->pInJournal = 0; + pPager->journalOff = 0; }else{ assert( pPager->eState==PAGER_WRITER_LOCKED ); pPager->eState = PAGER_WRITER_CACHEMOD; @@ -7376,7 +7377,7 @@ int sqlite3PagerGetJournalMode(Pager *pPager){ int sqlite3PagerOkToChangeJournalMode(Pager *pPager){ assert( assert_pager_state(pPager) ); if( pPager->eState>=PAGER_WRITER_CACHEMOD ) return 0; - if( NEVER(isOpen(pPager->jfd) && pPager->journalOff>0) ) return 0; + if( isOpen(pPager->jfd) && pPager->journalOff>0 ) return 0; return 1; } diff --git a/src/pcache.c b/src/pcache.c index 14d1e7cde..e130affd2 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -622,14 +622,24 @@ void sqlite3PcacheClearSyncFlags(PCache *pCache){ */ void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){ PCache *pCache = p->pCache; + sqlite3_pcache_page *pOther; assert( p->nRef>0 ); assert( newPgno>0 ); assert( sqlite3PcachePageSanity(p) ); pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno)); + pOther = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, newPgno, 0); + if( pOther ){ + PgHdr *pXPage = (PgHdr*)pOther->pExtra; + assert( pXPage->nRef==0 ); + pXPage->nRef++; + pCache->nRefSum++; + sqlite3PcacheDrop(pXPage); + } sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno); p->pgno = newPgno; if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){ pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); + assert( sqlite3PcachePageSanity(p) ); } } diff --git a/src/pcache1.c b/src/pcache1.c index a93b14689..ce595f2c2 100644 --- a/src/pcache1.c +++ b/src/pcache1.c @@ -1123,23 +1123,26 @@ static void pcache1Rekey( PCache1 *pCache = (PCache1 *)p; PgHdr1 *pPage = (PgHdr1 *)pPg; PgHdr1 **pp; - unsigned int h; + unsigned int hOld, hNew; assert( pPage->iKey==iOld ); assert( pPage->pCache==pCache ); + assert( iOld!=iNew ); /* The page number really is changing */ pcache1EnterMutex(pCache->pGroup); - - h = iOld%pCache->nHash; - pp = &pCache->apHash[h]; + + assert( pcache1FetchNoMutex(p, iOld, 0)==pPage ); /* pPg really is iOld */ + hOld = iOld%pCache->nHash; + pp = &pCache->apHash[hOld]; while( (*pp)!=pPage ){ pp = &(*pp)->pNext; } *pp = pPage->pNext; - h = iNew%pCache->nHash; + assert( pcache1FetchNoMutex(p, iNew, 0)==0 ); /* iNew not in cache */ + hNew = iNew%pCache->nHash; pPage->iKey = iNew; - pPage->pNext = pCache->apHash[h]; - pCache->apHash[h] = pPage; + pPage->pNext = pCache->apHash[hNew]; + pCache->apHash[hNew] = pPage; if( iNew>pCache->iMaxKey ){ pCache->iMaxKey = iNew; } diff --git a/src/pragma.c b/src/pragma.c index 9860da86d..e2f2577ae 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -965,6 +965,7 @@ void sqlite3Pragma( ** */ case PragTyp_TEMP_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( !zRight ){ returnSingleText(v, sqlite3_temp_directory); }else{ @@ -974,6 +975,7 @@ void sqlite3Pragma( rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); if( rc!=SQLITE_OK || res==0 ){ sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); goto pragma_out; } } @@ -991,6 +993,7 @@ void sqlite3Pragma( } #endif /* SQLITE_OMIT_WSD */ } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); break; } @@ -1009,6 +1012,7 @@ void sqlite3Pragma( ** */ case PragTyp_DATA_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( !zRight ){ returnSingleText(v, sqlite3_data_directory); }else{ @@ -1018,6 +1022,7 @@ void sqlite3Pragma( rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); if( rc!=SQLITE_OK || res==0 ){ sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); goto pragma_out; } } @@ -1029,6 +1034,7 @@ void sqlite3Pragma( } #endif /* SQLITE_OMIT_WSD */ } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); break; } #endif diff --git a/src/select.c b/src/select.c index 7d0c5f8e5..4242730e2 100644 --- a/src/select.c +++ b/src/select.c @@ -1688,7 +1688,7 @@ static void generateSortTail( if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak); VdbeCoverage(v); - codeOffset(v, p->iOffset, addrContinue); + assert( p->iLimit==0 && p->iOffset==0 ); sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab); bSeq = 0; }else{ @@ -1696,6 +1696,9 @@ static void generateSortTail( codeOffset(v, p->iOffset, addrContinue); iSortTab = iTab; bSeq = 1; + if( p->iOffset>0 ){ + sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1); + } } for(i=0, iCol=nKey+bSeq-1; ipPrior ){ - sqlite3SelectDelete(db, pSplit->pPrior); + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior); } pSplit->pPrior = pPrior; pPrior->pNext = pSplit; @@ -5210,6 +5214,7 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ || p->pSrc->nSrc!=1 || p->pSrc->a[0].pSelect || pAggInfo->nFunc!=1 + || p->pHaving ){ return 0; } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 106a5d147..1c9633990 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -198,6 +198,11 @@ */ #include "sqlite3.h" +/* +** Reuse the STATIC_LRU for mutex access to sqlite3_temp_directory. +*/ +#define SQLITE_MUTEX_STATIC_TEMPDIR SQLITE_MUTEX_STATIC_VFS1 + /* ** Include the configuration header output by 'configure' if we're using the ** autoconf-based build diff --git a/src/trigger.c b/src/trigger.c index 3b7d0d9e8..1c62fc231 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -351,6 +351,23 @@ void sqlite3FinishTrigger( Vdbe *v; char *z; + /* If this is a new CREATE TABLE statement, and if shadow tables + ** are read-only, and the trigger makes a change to a shadow table, + ** then raise an error - do not allow the trigger to be created. */ + if( sqlite3ReadOnlyShadowTables(db) ){ + TriggerStep *pStep; + for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){ + if( pStep->zTarget!=0 + && sqlite3ShadowTableName(db, pStep->zTarget) + ){ + sqlite3ErrorMsg(pParse, + "trigger \"%s\" may not write to shadow table \"%s\"", + pTrig->zName, pStep->zTarget); + goto triggerfinish_cleanup; + } + } + } + /* Make an entry in the sqlite_schema table */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto triggerfinish_cleanup; diff --git a/src/util.c b/src/util.c index 84608739a..32e9c2778 100644 --- a/src/util.c +++ b/src/util.c @@ -190,7 +190,7 @@ void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){ va_list ap; sqlite3 *db = pParse->db; assert( db!=0 ); - assert( db->pParse==pParse ); + assert( db->pParse==pParse || db->pParse->pToplevel==pParse ); db->errByteOffset = -2; va_start(ap, zFormat); zMsg = sqlite3VMPrintf(db, zFormat, ap); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 7c3be404e..3ef2bac31 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -381,6 +381,7 @@ int sqlite3VdbeAddFunctionCall( addr = sqlite3VdbeAddOp4(v, eCallCtx ? OP_PureFunc : OP_Function, p1, p2, p3, (char*)pCtx, P4_FUNCCTX); sqlite3VdbeChangeP5(v, eCallCtx & NC_SelfRef); + sqlite3MayAbort(pParse); return addr; } @@ -716,6 +717,7 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ || opcode==OP_VDestroy || opcode==OP_VCreate || opcode==OP_ParseSchema + || opcode==OP_Function || opcode==OP_PureFunc || ((opcode==OP_Halt || opcode==OP_HaltIfNull) && ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort)) ){ diff --git a/src/wherecode.c b/src/wherecode.c index 04f1c374d..c582ec657 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -622,7 +622,8 @@ static int codeEqualityTerm( } sqlite3ExprDelete(db, pX); }else{ - aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); + int n = sqlite3ExprVectorSize(pX->pLeft); + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n)); eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab); } pX = pExpr; diff --git a/test/json101.test b/test/json101.test index b84846d88..135c988e9 100644 --- a/test/json101.test +++ b/test/json101.test @@ -867,4 +867,22 @@ do_catchsql_test json-18.5 { SELECT json_extract('{"":8}', '$.'); } {1 {JSON path error near ''}} +# 2022-08-29 https://sqlite.org/forum/forumpost/9b9e4716c0d7bbd1 +# This is not a problem specifically with JSON functions. It is +# a problem with transaction control. But the json() function makes +# the problem more easily accessible, so it is tested here. +# +do_execsql_test json-19.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x); +} {} +do_catchsql_test json-19.2 { + BEGIN; + INSERT INTO t1 VALUES(0), (json('not-valid-json')); +} {1 {malformed JSON}} +do_execsql_test json-19.3 { + COMMIT; + SELECT * FROM t1; +} {} + finish_test diff --git a/test/offset1.test b/test/offset1.test index 91dc0b00a..5b04bd836 100644 --- a/test/offset1.test +++ b/test/offset1.test @@ -156,6 +156,47 @@ do_execsql_test offset1-1.4.9 { LIMIT 9 OFFSET 1; } {2 b 3 c 4 d 5 e 6 w 7 x 8 y 9 z} - +# 2022-08-04 +# https://sqlite.org/forum/forumpost/6b5e9188f0657616 +# +do_execsql_test offset1-2.0 { + CREATE TABLE employees ( + id integer primary key, + name text, + city text, + department text, + salary integer + ); + INSERT INTO employees VALUES + (11,'Diane','London','hr',70), + (12,'Bob','London','hr',78), + (21,'Emma','London','it',84), + (22,'Grace','Berlin','it',90), + (23,'Henry','London','it',104), + (24,'Irene','Berlin','it',104), + (25,'Frank','Berlin','it',120), + (31,'Cindy','Berlin','sales',96), + (32,'Dave','London','sales',96), + (33,'Alice','Berlin','sales',100); + CREATE VIEW v AS + SELECT * FROM ( + SELECT * FROM employees + WHERE salary < 100 + ORDER BY salary desc) + UNION ALL + SELECT * FROM ( + SELECT * FROM employees + WHERE salary >= 100 + ORDER BY salary asc); +} {} +do_execsql_test offset1-2.1 { + SELECT * FROM v LIMIT 5 OFFSET 2; +} { + 22 Grace Berlin it 90 + 21 Emma London it 84 + 12 Bob London hr 78 + 11 Diane London hr 70 + 33 Alice Berlin sales 100 +} finish_test diff --git a/test/select3.test b/test/select3.test index 690514b49..809b54902 100644 --- a/test/select3.test +++ b/test/select3.test @@ -118,10 +118,18 @@ do_test select3-2.14 { } {1 {near ";": syntax error}} # Cannot have a HAVING without a GROUP BY +# +# Update: As of 3.39.0, you can. # do_execsql_test select3-3.1 { SELECT log, count(*) FROM t1 HAVING log>=4 } {} +do_execsql_test select3-3.2 { + SELECT count(*) FROM t1 HAVING log>=4 +} {} +do_execsql_test select3-3.3 { + SELECT count(*) FROM t1 HAVING log!=400 +} {31} # Toss in some HAVING clauses # From f03453b13d69719a3970b9642f6ca2b5e355f50d Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 5 Oct 2022 16:28:37 -0400 Subject: [PATCH 079/340] resolve upstream merge conflict in distclean --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 2a1d6baeb..da215bb6a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1531,7 +1531,7 @@ clean: rm -f threadtest5 distclean: clean - rm -f config.h config.log config.status libtool Makefile sqlite3.pc \ + rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \ $(TESTPROGS) # From c6c357d5016ef26e9bb5ac83b1fb60953814fd07 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 8 Nov 2022 10:12:48 -0500 Subject: [PATCH 080/340] Revert "resolve upstream merge conflict in distclean" This reverts commit f03453b13d69719a3970b9642f6ca2b5e355f50d. --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index da215bb6a..2a1d6baeb 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1531,7 +1531,7 @@ clean: rm -f threadtest5 distclean: clean - rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \ + rm -f config.h config.log config.status libtool Makefile sqlite3.pc \ $(TESTPROGS) # From aaef569e21326f27384ed6ac612201c425d71616 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 8 Nov 2022 10:13:51 -0500 Subject: [PATCH 081/340] correct distclean to match sqlcipher.pc in current tree --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 2a1d6baeb..a77b3bb55 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1531,7 +1531,7 @@ clean: rm -f threadtest5 distclean: clean - rm -f config.h config.log config.status libtool Makefile sqlite3.pc \ + rm -f config.h config.log config.status libtool Makefile sqlcipher.pc \ $(TESTPROGS) # From deb4ab777339302da37dac698076195d679279e6 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 12 Dec 2022 15:34:52 -0500 Subject: [PATCH 082/340] update podspec version number --- SQLCipher.podspec.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 4c56637e9..a467be774 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.1" + "tag": "v4.5.3" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.2", + "version": "4.5.3", "subspecs": [ { "compiler_flags": [ From 8763afaf13231cb1fc835b52c94ada23f8e47b3d Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 12 Dec 2022 15:46:23 -0500 Subject: [PATCH 083/340] changelog update --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 103f7aa6c..f381d01cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. -## [unreleased] - (? 2022 - [unreleased changes]) +## [unreleased] - (? 2023 - [unreleased changes]) + +## [4.5.3] - (December 2022 - [4.5.3 changes]) +- Updates baseline to upstream SQLite 3.39.4 ## [4.5.2] - (August 2022 - [4.5.2 changes]) - Updates source code baseline to upstream SQLite 3.39.2 @@ -210,7 +213,9 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 -[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.2...prerelease +[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.3...prerelease +[4.5.3]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.3 +[4.5.3 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.2...v4.5.3 [4.5.2]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.2 [4.5.2 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.1...v4.5.2 [4.5.1]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.1 From c867151ba5e2302be078b63db444581c46bcad72 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 21 Dec 2022 13:36:41 -0500 Subject: [PATCH 084/340] update sdk versions for podspec --- SQLCipher.podspec.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index a467be774..c42806385 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -6,10 +6,10 @@ "license": "BSD", "name": "SQLCipher", "platforms": { - "ios": "8.0", - "osx": "10.9", - "tvos": "9.0", - "watchos": "2.0" + "ios": "11.0", + "osx": "10.13", + "tvos": "11.0", + "watchos": "7.0" }, "prepare_command": "./configure --enable-tempstore=yes --with-crypto-lib=commoncrypto CFLAGS=\"-DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLITE_SOUNDEX -DSQLITE_THREADSAFE -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_STAT3 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_LOAD_EXTENSION -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS4_UNICODE61 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DHAVE_USLEEP=1 -DSQLITE_MAX_VARIABLE_NUMBER=99999\"; make sqlite3.c", "requires_arc": false, From 93c24ef16c2114d91a0bca6acdc32d1cd6eefb4a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 30 Dec 2022 11:52:28 -0500 Subject: [PATCH 085/340] Snapshot of upstream SQLite 3.40.1 --- Makefile.in | 179 +- Makefile.msc | 32 +- VERSION | 2 +- autoconf/tea/Makefile.in | 189 +- autoconf/tea/configure.ac | 94 +- autoconf/tea/pkgIndex.tcl.in | 15 +- autoconf/tea/tclconfig/tcl.m4 | 1153 +++---- autoconf/tea/win/makefile.vc | 13 +- configure | 22 +- configure.ac | 2 +- ext/expert/expert1.test | 10 + ext/expert/sqlite3expert.c | 4 + ext/fiddle/EXPORTED_RUNTIME_METHODS | 14 - ext/fiddle/Makefile | 34 - ext/fiddle/SqliteTestUtil.js | 144 - ext/fiddle/fiddle-worker.js | 301 -- ext/fiddle/fiddle.js | 809 ----- ext/fiddle/index.md | 94 - ext/fiddle/sqlite3-api.js | 1781 ---------- ext/fiddle/sqlite3-worker.js | 44 - ext/fiddle/testing.css | 31 - ext/fiddle/testing1.js | 185 -- ext/fiddle/testing2.js | 235 -- ext/fts5/fts5Int.h | 2 +- ext/fts5/fts5_index.c | 23 +- ext/fts5/fts5_main.c | 10 +- ext/fts5/test/fts5ah.test | 1 + ext/fts5/test/fts5misc.test | 91 + ext/fts5/test/fts5ubsan.test | 60 + ext/misc/cksumvfs.c | 2 +- ext/misc/csv.c | 9 +- ext/misc/regexp.c | 133 +- ext/rbu/rburename.test | 54 + ext/rbu/rbuvacuum2.test | 9 +- ext/rbu/sqlite3rbu.c | 80 +- ext/rbu/sqlite3rbu.h | 28 + ext/rbu/test_rbu.c | 57 +- ext/{misc => recover}/dbdata.c | 175 +- ext/recover/recover1.test | 320 ++ ext/recover/recover_common.tcl | 14 + ext/recover/recoverclobber.test | 50 + ext/recover/recovercorrupt.test | 67 + ext/recover/recovercorrupt2.test | 289 ++ ext/recover/recoverfault.test | 84 + ext/recover/recoverfault2.test | 102 + ext/recover/recoverold.test | 189 ++ ext/recover/recoverpgsz.test | 100 + ext/recover/recoverrowid.test | 50 + ext/recover/recoverslowidx.test | 87 + ext/recover/recoversql.test | 52 + ext/recover/sqlite3recover.c | 2861 +++++++++++++++++ ext/recover/sqlite3recover.h | 249 ++ ext/recover/test_recover.c | 311 ++ ext/rtree/rtree.c | 2 +- ext/session/sqlite3session.c | 18 +- ext/session/test_session.c | 112 + .../EXPORTED_FUNCTIONS.fiddle.in} | 11 +- ext/wasm/GNUmakefile | 655 ++++ ext/wasm/README-dist.txt | 23 + ext/wasm/README.md | 105 + .../api}/EXPORTED_FUNCTIONS.sqlite3-api | 47 +- .../api/EXPORTED_RUNTIME_METHODS.sqlite3-api | 3 + ext/wasm/api/README.md | 133 + ext/wasm/api/extern-post-js.js | 103 + ext/wasm/api/extern-pre-js.js | 7 + ext/wasm/api/post-js-footer.js | 4 + ext/wasm/api/post-js-header.js | 25 + ext/wasm/api/pre-js.js | 100 + ext/wasm/api/sqlite3-api-cleanup.js | 70 + ext/wasm/api/sqlite3-api-glue.js | 720 +++++ ext/wasm/api/sqlite3-api-oo1.js | 1800 +++++++++++ ext/wasm/api/sqlite3-api-opfs.js | 1311 ++++++++ ext/wasm/api/sqlite3-api-prologue.js | 1602 +++++++++ ext/wasm/api/sqlite3-api-worker1.js | 654 ++++ .../api/sqlite3-license-version-header.js | 25 + ext/wasm/api/sqlite3-opfs-async-proxy.js | 830 +++++ ext/wasm/api/sqlite3-wasi.h | 69 + ext/wasm/api/sqlite3-wasm.c | 1181 +++++++ ext/wasm/api/sqlite3-worker1-promiser.js | 259 ++ ext/wasm/api/sqlite3-worker1.js | 49 + ext/wasm/batch-runner.html | 90 + ext/wasm/batch-runner.js | 588 ++++ ext/wasm/common/SqliteTestUtil.js | 236 ++ ext/{fiddle => wasm/common}/emscripten.css | 0 ext/wasm/common/testing.css | 63 + ext/wasm/common/whwasmutil.js | 1706 ++++++++++ ext/wasm/demo-123-worker.html | 44 + ext/wasm/demo-123.html | 24 + ext/wasm/demo-123.js | 289 ++ ext/wasm/demo-jsstorage.html | 49 + ext/wasm/demo-jsstorage.js | 114 + .../demo-worker1-promiser.html} | 18 +- ext/wasm/demo-worker1-promiser.js | 270 ++ .../testing1.html => wasm/demo-worker1.html} | 19 +- ext/wasm/demo-worker1.js | 345 ++ ext/wasm/dist.make | 101 + ext/wasm/fiddle.make | 194 ++ ext/wasm/fiddle/emscripten.css | 24 + ext/wasm/fiddle/fiddle-worker.js | 379 +++ ext/wasm/fiddle/fiddle.js | 815 +++++ .../fiddle.html => wasm/fiddle/index.html} | 5 - ext/wasm/index-dist.html | 90 + ext/wasm/index.html | 115 + ext/wasm/jaccwabyt/jaccwabyt.js | 746 +++++ ext/wasm/jaccwabyt/jaccwabyt.md | 1076 +++++++ ext/wasm/module-symbols.html | 333 ++ ext/wasm/scratchpad-wasmfs-main.html | 40 + ext/wasm/scratchpad-wasmfs-main.js | 70 + ext/wasm/speedtest1-wasmfs.html | 149 + ext/wasm/speedtest1-worker.html | 372 +++ ext/wasm/speedtest1-worker.js | 99 + ext/wasm/speedtest1.html | 174 + ext/wasm/split-speedtest1-script.sh | 17 + ext/wasm/sql/000-mandelbrot.sql | 17 + ext/wasm/sql/001-sudoku.sql | 28 + ext/wasm/test-opfs-vfs.html | 26 + ext/wasm/test-opfs-vfs.js | 85 + ext/wasm/tester1-worker.html | 63 + ext/wasm/tester1.html | 28 + ext/wasm/tester1.js | 1864 +++++++++++ ext/wasm/version-info.c | 106 + ext/wasm/wasmfs.make | 113 + magic.txt | 9 +- main.mk | 29 +- manifest | 443 +-- manifest.uuid | 2 +- config.h.in => sqlite_cfg.h.in | 25 +- src/analyze.c | 1 + src/btmutex.c | 1 + src/btree.c | 116 +- src/btree.h | 2 + src/build.c | 169 +- src/callback.c | 6 +- src/ctime.c | 8 +- src/date.c | 4 +- src/dbpage.c | 36 +- src/delete.c | 45 +- src/expr.c | 193 +- src/fkey.c | 3 +- src/func.c | 18 +- src/global.c | 12 - src/insert.c | 59 +- src/loadext.c | 4 +- src/main.c | 43 +- src/malloc.c | 93 +- src/mem5.c | 8 +- src/memdb.c | 102 +- src/memjournal.c | 2 + src/os.c | 3 + src/os_kv.c | 972 ++++++ src/os_setup.h | 86 +- src/os_unix.c | 19 +- src/os_win.c | 8 +- src/pager.c | 3 +- src/pcache.c | 55 +- src/pcache1.c | 73 +- src/pragma.c | 180 +- src/prepare.c | 12 +- src/random.c | 94 +- src/resolve.c | 4 +- src/select.c | 171 +- src/shell.c.in | 1679 ++++------ src/sqlite.h.in | 118 +- src/sqlite3ext.h | 8 +- src/sqliteInt.h | 107 +- src/status.c | 8 +- src/test1.c | 22 +- src/test2.c | 8 + src/test_demovfs.c | 22 +- src/test_multiplex.c | 2 +- src/test_tclsh.c | 2 + src/tokenize.c | 2 +- src/trigger.c | 4 +- src/update.c | 9 +- src/upsert.c | 1 + src/vacuum.c | 8 +- src/vdbe.c | 271 +- src/vdbe.h | 2 + src/vdbeInt.h | 2 +- src/vdbeapi.c | 29 +- src/vdbeaux.c | 142 +- src/vdbemem.c | 16 +- src/vtab.c | 5 +- src/where.c | 427 ++- src/whereInt.h | 22 +- src/wherecode.c | 183 +- src/whereexpr.c | 21 +- src/window.c | 8 +- test/attachmalloc.test | 23 +- test/bigmmap.test | 6 +- test/bigsort.test | 7 + test/bloom1.test | 70 + test/btreefault.test | 49 + test/busy2.test | 1 + test/cast.test | 2 +- test/collate5.test | 36 + test/corruptL.test | 25 +- test/corruptN.test | 15 +- test/csv01.test | 39 + test/dbpagefault.test | 88 + test/e_uri.test | 2 + test/e_wal.test | 1 + test/enc3.test | 1 - test/fts4merge4.test | 2 +- test/fuzzcheck.c | 75 +- test/fuzzdata8.db | Bin 3283968 -> 3298304 bytes test/fuzzinvariants.c | 198 +- test/join.test | 106 + test/joinD.test | 2 + test/joinH.test | 21 + test/like2.test | 8 + test/malloctraceviewer.tcl | 2 +- test/memjournal2.test | 1 + test/memsubsys1.test | 2 +- test/permutations.test | 8 + test/pragma.test | 18 +- test/recover.test | 49 +- test/regexp1.test | 71 + test/releasetest_data.tcl | 1 + test/returning1.test | 24 + test/round1.test | 1 + test/savepoint.test | 2 + test/seekscan1.test | 63 + test/select3.test | 104 + test/selectA.test | 24 + test/shared9.test | 2 +- test/sharedA.test | 5 + test/sharedB.test | 5 + test/shell2.test | 12 + test/speedtest1.c | 156 +- test/sqllimits1.test | 13 + test/temptable2.test | 3 + test/tester.tcl | 45 +- test/testrunner.tcl | 705 ++++ test/thread001.test | 1 + test/thread002.test | 2 +- test/tkt-80e031a00f.test | 7 +- test/tkt-f7b4edec.test | 1 + test/unionall.test | 77 + test/uri.test | 2 + test/vacuum-into.test | 54 + test/vacuum6.test | 1 + test/vacuummem.test | 1 + test/view.test | 35 - test/view3.test | 60 + test/vt02.c | 1019 ++++++ test/wal3.test | 1 + test/walsetlk.test | 4 +- test/walshared.test | 2 +- test/walvfs.test | 1 + test/wapptest.tcl | 17 +- test/where.test | 17 + test/widetab1.test | 156 + test/win32lock.test | 2 + test/window1.test | 5 +- test/windowE.test | 58 + test/with3.test | 4 +- tool/GetTclKit.bat | 2 + tool/build-all-msvc.bat | 5 + tool/dbtotxt.c | 71 +- tool/mkctimec.tcl | 4 +- tool/mkopcodeh.tcl | 1 + tool/mkshellc.tcl | 8 +- tool/mksqlite3c.tcl | 1 + tool/omittest-msvc.tcl | 99 + tool/speed-check.sh | 12 +- tool/stripccomments.c | 228 ++ 267 files changed, 34846 insertions(+), 7294 deletions(-) delete mode 100644 ext/fiddle/EXPORTED_RUNTIME_METHODS delete mode 100644 ext/fiddle/Makefile delete mode 100644 ext/fiddle/SqliteTestUtil.js delete mode 100644 ext/fiddle/fiddle-worker.js delete mode 100644 ext/fiddle/fiddle.js delete mode 100644 ext/fiddle/index.md delete mode 100644 ext/fiddle/sqlite3-api.js delete mode 100644 ext/fiddle/sqlite3-worker.js delete mode 100644 ext/fiddle/testing.css delete mode 100644 ext/fiddle/testing1.js delete mode 100644 ext/fiddle/testing2.js create mode 100644 ext/fts5/test/fts5ubsan.test create mode 100644 ext/rbu/rburename.test rename ext/{misc => recover}/dbdata.c (84%) create mode 100644 ext/recover/recover1.test create mode 100644 ext/recover/recover_common.tcl create mode 100644 ext/recover/recoverclobber.test create mode 100644 ext/recover/recovercorrupt.test create mode 100644 ext/recover/recovercorrupt2.test create mode 100644 ext/recover/recoverfault.test create mode 100644 ext/recover/recoverfault2.test create mode 100644 ext/recover/recoverold.test create mode 100644 ext/recover/recoverpgsz.test create mode 100644 ext/recover/recoverrowid.test create mode 100644 ext/recover/recoverslowidx.test create mode 100644 ext/recover/recoversql.test create mode 100644 ext/recover/sqlite3recover.c create mode 100644 ext/recover/sqlite3recover.h create mode 100644 ext/recover/test_recover.c rename ext/{fiddle/EXPORTED_FUNCTIONS.fiddle => wasm/EXPORTED_FUNCTIONS.fiddle.in} (61%) create mode 100644 ext/wasm/GNUmakefile create mode 100644 ext/wasm/README-dist.txt create mode 100644 ext/wasm/README.md rename ext/{fiddle => wasm/api}/EXPORTED_FUNCTIONS.sqlite3-api (51%) create mode 100644 ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api create mode 100644 ext/wasm/api/README.md create mode 100644 ext/wasm/api/extern-post-js.js create mode 100644 ext/wasm/api/extern-pre-js.js create mode 100644 ext/wasm/api/post-js-footer.js create mode 100644 ext/wasm/api/post-js-header.js create mode 100644 ext/wasm/api/pre-js.js create mode 100644 ext/wasm/api/sqlite3-api-cleanup.js create mode 100644 ext/wasm/api/sqlite3-api-glue.js create mode 100644 ext/wasm/api/sqlite3-api-oo1.js create mode 100644 ext/wasm/api/sqlite3-api-opfs.js create mode 100644 ext/wasm/api/sqlite3-api-prologue.js create mode 100644 ext/wasm/api/sqlite3-api-worker1.js create mode 100644 ext/wasm/api/sqlite3-license-version-header.js create mode 100644 ext/wasm/api/sqlite3-opfs-async-proxy.js create mode 100644 ext/wasm/api/sqlite3-wasi.h create mode 100644 ext/wasm/api/sqlite3-wasm.c create mode 100644 ext/wasm/api/sqlite3-worker1-promiser.js create mode 100644 ext/wasm/api/sqlite3-worker1.js create mode 100644 ext/wasm/batch-runner.html create mode 100644 ext/wasm/batch-runner.js create mode 100644 ext/wasm/common/SqliteTestUtil.js rename ext/{fiddle => wasm/common}/emscripten.css (100%) create mode 100644 ext/wasm/common/testing.css create mode 100644 ext/wasm/common/whwasmutil.js create mode 100644 ext/wasm/demo-123-worker.html create mode 100644 ext/wasm/demo-123.html create mode 100644 ext/wasm/demo-123.js create mode 100644 ext/wasm/demo-jsstorage.html create mode 100644 ext/wasm/demo-jsstorage.js rename ext/{fiddle/testing2.html => wasm/demo-worker1-promiser.html} (64%) create mode 100644 ext/wasm/demo-worker1-promiser.js rename ext/{fiddle/testing1.html => wasm/demo-worker1.html} (67%) create mode 100644 ext/wasm/demo-worker1.js create mode 100644 ext/wasm/dist.make create mode 100644 ext/wasm/fiddle.make create mode 100644 ext/wasm/fiddle/emscripten.css create mode 100644 ext/wasm/fiddle/fiddle-worker.js create mode 100644 ext/wasm/fiddle/fiddle.js rename ext/{fiddle/fiddle.html => wasm/fiddle/index.html} (99%) create mode 100644 ext/wasm/index-dist.html create mode 100644 ext/wasm/index.html create mode 100644 ext/wasm/jaccwabyt/jaccwabyt.js create mode 100644 ext/wasm/jaccwabyt/jaccwabyt.md create mode 100644 ext/wasm/module-symbols.html create mode 100644 ext/wasm/scratchpad-wasmfs-main.html create mode 100644 ext/wasm/scratchpad-wasmfs-main.js create mode 100644 ext/wasm/speedtest1-wasmfs.html create mode 100644 ext/wasm/speedtest1-worker.html create mode 100644 ext/wasm/speedtest1-worker.js create mode 100644 ext/wasm/speedtest1.html create mode 100755 ext/wasm/split-speedtest1-script.sh create mode 100644 ext/wasm/sql/000-mandelbrot.sql create mode 100644 ext/wasm/sql/001-sudoku.sql create mode 100644 ext/wasm/test-opfs-vfs.html create mode 100644 ext/wasm/test-opfs-vfs.js create mode 100644 ext/wasm/tester1-worker.html create mode 100644 ext/wasm/tester1.html create mode 100644 ext/wasm/tester1.js create mode 100644 ext/wasm/version-info.c create mode 100644 ext/wasm/wasmfs.make rename config.h.in => sqlite_cfg.h.in (82%) create mode 100644 src/os_kv.c create mode 100644 test/bloom1.test create mode 100644 test/dbpagefault.test create mode 100644 test/seekscan1.test create mode 100644 test/testrunner.tcl create mode 100644 test/view3.test create mode 100644 test/vt02.c create mode 100644 test/widetab1.test create mode 100644 test/windowE.test create mode 100644 tool/omittest-msvc.tcl create mode 100644 tool/stripccomments.c diff --git a/Makefile.in b/Makefile.in index 63120ffb2..09e75bb88 100644 --- a/Makefile.in +++ b/Makefile.in @@ -35,7 +35,7 @@ TCC += -I${TOP}/ext/fts3 -I${TOP}/ext/async -I${TOP}/ext/session TCC += -I${TOP}/ext/userauth # Define this for the autoconf-based build, so that the code knows it can -# include the generated config.h +# include the generated sqlite_cfg.h # TCC += -D_HAVE_SQLITE_CONFIG_H -DBUILD_sqlite @@ -184,7 +184,7 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \ main.lo malloc.lo mem0.lo mem1.lo mem2.lo mem3.lo mem5.lo \ memdb.lo memjournal.lo \ mutex.lo mutex_noop.lo mutex_unix.lo mutex_w32.lo \ - notify.lo opcodes.lo os.lo os_unix.lo os_win.lo \ + notify.lo opcodes.lo os.lo os_kv.lo os_unix.lo os_win.lo \ pager.lo parse.lo pcache.lo pcache1.lo pragma.lo prepare.lo printf.lo \ random.lo resolve.lo rowset.lo rtree.lo \ sqlite3session.lo select.lo sqlite3rbu.lo status.lo stmt.lo \ @@ -257,6 +257,7 @@ SRC = \ $(TOP)/src/os.h \ $(TOP)/src/os_common.h \ $(TOP)/src/os_setup.h \ + $(TOP)/src/os_kv.c \ $(TOP)/src/os_unix.c \ $(TOP)/src/os_win.c \ $(TOP)/src/os_win.h \ @@ -377,7 +378,7 @@ SRC += \ opcodes.h \ parse.c \ parse.h \ - config.h \ + sqlite_cfg.h \ shell.c \ sqlite3.h @@ -433,6 +434,9 @@ TESTSRC = \ $(TOP)/ext/fts3/fts3_term.c \ $(TOP)/ext/fts3/fts3_test.c \ $(TOP)/ext/session/test_session.c \ + $(TOP)/ext/recover/sqlite3recover.c \ + $(TOP)/ext/recover/dbdata.c \ + $(TOP)/ext/recover/test_recover.c \ $(TOP)/ext/rbu/test_rbu.c # Statically linked extensions @@ -492,6 +496,7 @@ TESTSRC2 = \ $(TOP)/src/main.c \ $(TOP)/src/mem5.c \ $(TOP)/src/os.c \ + $(TOP)/src/os_kv.c \ $(TOP)/src/os_unix.c \ $(TOP)/src/os_win.c \ $(TOP)/src/pager.c \ @@ -555,7 +560,7 @@ HDR = \ $(TOP)/src/vdbeInt.h \ $(TOP)/src/vxworks.h \ $(TOP)/src/whereInt.h \ - config.h + sqlite_cfg.h # Header files used by extensions # @@ -621,7 +626,10 @@ SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC FUZZERSHELL_OPT = -FUZZCHECK_OPT = -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ +FUZZCHECK_OPT += -I$(TOP)/test +FUZZCHECK_OPT += -I$(TOP)/ext/recover +FUZZCHECK_OPT += -DSQLITE_OMIT_LOAD_EXTENSION +FUZZCHECK_OPT += -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4 @@ -631,8 +639,14 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB -FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c $(TOP)/test/fuzzinvariants.c +FUZZCHECK_SRC += $(TOP)/test/fuzzcheck.c +FUZZCHECK_SRC += $(TOP)/test/ossfuzz.c +FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c +FUZZCHECK_SRC += $(TOP)/ext/recover/dbdata.c +FUZZCHECK_SRC += $(TOP)/ext/recover/sqlite3recover.c +FUZZCHECK_SRC += $(TOP)/test/vt02.c DBFUZZ_OPT = +ST_OPT = -DSQLITE_OS_KV_OPTIONAL # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. @@ -681,7 +695,7 @@ fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZERSHELL_OPT) \ $(TOP)/tool/fuzzershell.c sqlite3.c $(TLIBS) -fuzzcheck$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h +fuzzcheck$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h @@ -938,6 +952,9 @@ pcache1.lo: $(TOP)/src/pcache1.c $(HDR) $(TOP)/src/pcache.h os.lo: $(TOP)/src/os.c $(HDR) $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/os.c +os_kv.lo: $(TOP)/src/os_kv.c $(HDR) + $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/os_kv.c + os_unix.lo: $(TOP)/src/os_unix.c $(HDR) $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/os_unix.c @@ -1102,6 +1119,9 @@ SHELL_SRC = \ $(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/misc/memtrace.c \ + $(TOP)/ext/recover/dbdata.c \ + $(TOP)/ext/recover/sqlite3recover.c \ + $(TOP)/ext/recover/sqlite3recover.h \ $(TOP)/src/test_windirent.c shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl @@ -1281,6 +1301,12 @@ valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessio tcltest: ./testfixture$(TEXE) ./testfixture$(TEXE) $(TOP)/test/veryquick.test $(TESTOPTS) +# Runs all the same tests cases as the "tcltest" target but uses +# the testrunner.tcl script to run them in multiple cores +# concurrently. +testrunner: testfixture$(TEXE) + ./testfixture$(TEXE) $(TOP)/test/testrunner.tcl + # Minimal testing that runs in less than 3 minutes # quicktest: ./testfixture$(TEXE) @@ -1381,7 +1407,7 @@ LogEst$(TEXE): $(TOP)/tool/logest.c sqlite3.h wordcount$(TEXE): $(TOP)/test/wordcount.c sqlite3.lo $(LTLINK) -o $@ $(TOP)/test/wordcount.c sqlite3.lo $(TLIBS) -speedtest1$(TEXE): $(TOP)/test/speedtest1.c sqlite3.c +speedtest1$(TEXE): $(TOP)/test/speedtest1.c sqlite3.c Makefile $(LTLINK) $(ST_OPT) -o $@ $(TOP)/test/speedtest1.c sqlite3.c $(TLIBS) startup$(TEXE): $(TOP)/test/startup.c sqlite3.c @@ -1470,6 +1496,7 @@ clean: rm -rf .libs .deps rm -f lemon$(BEXE) lempar.c parse.* sqlite*.tar.gz rm -f mkkeywordhash$(BEXE) keywordhash.h + rm -f mksourceid$(BEXE) rm -f *.da *.bb *.bbg gmon.out rm -rf tsrc .target_source rm -f tclsqlite3$(TEXE) @@ -1494,7 +1521,8 @@ clean: rm -f threadtest5 distclean: clean - rm -f config.h config.log config.status libtool Makefile sqlite3.pc + rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \ + $(TESTPROGS) # # Windows section @@ -1514,133 +1542,8 @@ sqlite3.dll: $(REAL_LIBOBJ) sqlite3.def $(TCC) -shared -o $@ sqlite3.def \ -Wl,"--strip-all" $(REAL_LIBOBJ) - # -# fiddle/wasm section -# -fiddle_dir = ext/fiddle -fiddle_dir_abs = $(TOP)/$(fiddle_dir) -# ^^^ some emcc opts require absolute paths -fiddle_html = $(fiddle_dir)/fiddle.html -fiddle_module_js = $(fiddle_dir)/fiddle-module.js -sqlite3_wasm_js = $(fiddle_dir)/sqlite3.js -sqlite3_wasm = $(fiddle_dir)/sqlite3.wasm -#emcc_opt = -O0 -#emcc_opt = -O1 -#emcc_opt = -O2 -#emcc_opt = -O3 -emcc_opt = -Oz -emcc_flags = $(emcc_opt) -sALLOW_TABLE_GROWTH -sSTRICT_JS \ - -sENVIRONMENT=web -sMODULARIZE \ - -sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \ - -sDYNAMIC_EXECUTION=0 \ - -I. $(SHELL_OPT) -$(fiddle_module_js): Makefile sqlite3.c shell.c \ - $(fiddle_dir)/EXPORTED_RUNTIME_METHODS \ - $(fiddle_dir)/EXPORTED_FUNCTIONS.fiddle - emcc -o $@ $(emcc_flags) \ - -sEXPORT_NAME=initFiddleModule \ - -sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.fiddle \ - sqlite3.c shell.c - gzip < $@ > $@.gz - gzip < $(fiddle_dir)/fiddle-module.wasm > $(fiddle_dir)/fiddle-module.wasm.gz -$(sqlite3_wasm_js): Makefile sqlite3.c \ - $(fiddle_dir)/sqlite3-api.js \ - $(fiddle_dir)/EXPORTED_RUNTIME_METHODS \ - $(fiddle_dir)/EXPORTED_FUNCTIONS.sqlite3-api - emcc -o $@ $(emcc_flags) \ - -sEXPORT_NAME=initSqlite3Module \ - -sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.sqlite3-api \ - --post-js=$(fiddle_dir)/sqlite3-api.js \ - --no-entry \ - sqlite3.c - gzip < $@ > $@.gz - gzip < $(sqlite3_wasm) > $(sqlite3_wasm).gz - gzip < $(fiddle_dir)/sqlite3-api.js > $(fiddle_dir)/sqlite3-api.js.gz -$(fiddle_dir)/fiddle.js.gz: $(fiddle_dir)/fiddle.js - gzip < $< > $@ -$(fiddle_dir)/sqlite3-api.js.gz: $(fiddle_dir)/sqlite3-api.js - gzip < $< > $@ - -fiddle_generated = $(fiddle_module_js) $(fiddle_module_js).gz \ - $(fiddle_dir)/fiddle-module.wasm \ - $(fiddle_dir)/fiddle-module.wasm.gz \ - $(fiddle_dir)/fiddle.js.gz -sqlite3_wasm_generated = \ - $(sqlite3_wasm) $(sqlite3_wasm).gz \ - $(sqlite3_wasm_js) $(sqlite3_wasm_js).gz \ - $(fiddle_dir)/sqlite3.js.gz \ - $(fiddle_dir)/sqlite3-api.js.gz - -clean-wasm: - rm -f $(fiddle_generated) $(sqlite3_wasm_generated) -clean: clean-wasm -fiddle: $(fiddle_module_js) $(fiddle_dir)/fiddle.js.gz -sqlite3-wasm: $(sqlite3_wasm_js) -wasm: fiddle sqlite3-wasm -######################################################################## -# Explanation of the emcc build flags follows. Full docs for these can -# be found at: -# -# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js -# -# -sENVIRONMENT=web: elides bootstrap code related to non-web JS -# environments like node.js. Removing this makes the output a tiny -# tick larger but hypothetically makes it more portable to -# non-browser JS environments. -# -# -sMODULARIZE: changes how the generated code is structured to avoid -# declaring a global Module object and instead installing a function -# which loads and initialized the module. The function is named... -# -# -sEXPORT_NAME=jsFunctionName (see -sMODULARIZE) -# -# -sEXPORTED_RUNTIME_METHODS=@/absolute/path/to/file: a file -# containing a list of emscripten-supplied APIs, one per line, which -# must be exported into the generated JS. Must be an absolute path! -# -# -sEXPORTED_FUNCTIONS=@/absolute/path/to/file: a file containing a -# list of C functions, one per line, which must be exported via wasm -# so they're visible to JS. C symbols names in that file must all -# start with an underscore for reasons known only to the emcc -# developers. e.g., _sqlite3_open_v2 and _sqlite3_finalize. Must be -# an absolute path! -# -# -sSTRICT_JS ensures that the emitted JS code includes the 'use -# strict' option. Note that -sSTRICT is more broadly-scoped and -# results in build errors. -# -# -sALLOW_TABLE_GROWTH is required for (at a minimum) the UDF-binding -# feature. -# -# -sDYNAMIC_EXECUTION=0 disables eval() and the Function constructor. -# If the build runs without these, it's preferable to use this flag -# because certain execution environments disallow those constructs. -# This flag is not strictly necessary, however. -# -# -sWASM_BIGINT is UNTESTED but "should" allow the int64-using C APIs -# to work with JS/wasm, insofar as the JS environment supports the -# BigInt type. That support requires an extremely recent browser: -# Safari didn't get that support until late 2020. -# -# --no-entry: for compiling library code with no main(). If this is -# not supplied and the code has a main(), it is called as part of the -# module init process. Note that main() is #if'd out of shell.c -# (renamed) when building in wasm mode. -# -# --pre-js/--post-js=FILE relative or absolute paths to JS files to -# prepend/append to the emcc-generated bootstrapping JS. It's -# easier/faster to develop with separate JS files (reduces rebuilding -# requirements) but certain configurations, namely -sMODULARIZE, may -# require using at least a --pre-js file. They can be used -# individually and need not be paired. -# -# -O0..-O3 and -Oz: optimization levels affect not only C-style -# optimization but whether or not the resulting generated JS code -# gets minified. -O0 compiles _much_ more quickly than -O3 or -Oz, -# and doesn't minimize any JS code, so is recommended for -# development. -O3 or -Oz are recommended for deployment, but -# primarily because -Oz will shrink the wasm file notably. JS-side -# minification makes little difference in terms of overall -# distributable size. -######################################################################## +# Fiddle app +# +fiddle: sqlite3.c shell.c + make -C ext/wasm fiddle emcc_opt=-Os diff --git a/Makefile.msc b/Makefile.msc index 42da9b9e6..191701cdb 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1251,7 +1251,7 @@ LIBOBJS0 = vdbe.lo parse.lo alter.lo analyze.lo attach.lo auth.lo \ main.lo malloc.lo mem0.lo mem1.lo mem2.lo mem3.lo mem5.lo \ memdb.lo memjournal.lo \ mutex.lo mutex_noop.lo mutex_unix.lo mutex_w32.lo \ - notify.lo opcodes.lo os.lo os_unix.lo os_win.lo \ + notify.lo opcodes.lo os.lo os_kv.lo os_unix.lo os_win.lo \ pager.lo pcache.lo pcache1.lo pragma.lo prepare.lo printf.lo \ random.lo resolve.lo rowset.lo rtree.lo \ sqlite3session.lo select.lo sqlite3rbu.lo status.lo stmt.lo \ @@ -1332,6 +1332,7 @@ SRC00 = \ $(TOP)\src\mutex_w32.c \ $(TOP)\src\notify.c \ $(TOP)\src\os.c \ + $(TOP)\src\os_kv.c \ $(TOP)\src\os_unix.c \ $(TOP)\src\os_win.c @@ -1587,6 +1588,9 @@ TESTEXT = \ $(TOP)\ext\misc\unionvtab.c \ $(TOP)\ext\misc\wholenumber.c \ $(TOP)\ext\rtree\test_rtreedoc.c \ + $(TOP)\ext\recover\sqlite3recover.c \ + $(TOP)\ext\recover\test_recover.c \ + $(TOP)\ext\recover\dbdata.c \ fts5.c # If use of zlib is enabled, add the "zipfile.c" source file. @@ -1697,15 +1701,25 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 # MPTESTER_COMPILE_OPTS = -DSQLITE_ENABLE_FTS5 FUZZERSHELL_COMPILE_OPTS = -FUZZCHECK_OPTS = -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -DSQLITE_MAX_MEMORY=50000000 -DSQLITE_PRINTF_PRECISION_LIMIT=1000 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -I$(TOP)\test -I$(TOP)\ext\recover +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MEMSYS5 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OSS_FUZZ +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MEMORY=50000000 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS4 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS5 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_RTREE FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBSTAT_VTAB FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\fuzzcheck.c +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\ossfuzz.c +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\fuzzinvariants.c +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\vt02.c +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\dbdata.c +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\sqlite3recover.c -FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c $(TOP)\test\fuzzinvariants.c OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION KV_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ @@ -2053,6 +2067,9 @@ pcache1.lo: $(TOP)\src\pcache1.c $(HDR) $(TOP)\src\pcache.h os.lo: $(TOP)\src\os.c $(HDR) $(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\os.c +os_kv.lo: $(TOP)\src\os_kv.c $(HDR) + $(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\os_kv.c + os_unix.lo: $(TOP)\src\os_unix.c $(HDR) $(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\os_unix.c @@ -2224,6 +2241,9 @@ SHELL_SRC = \ $(TOP)\ext\expert\sqlite3expert.c \ $(TOP)\ext\expert\sqlite3expert.h \ $(TOP)\ext\misc\memtrace.c \ + $(TOP)/ext/recover/dbdata.c \ + $(TOP)/ext/recover/sqlite3recover.c \ + $(TOP)/ext/recover/sqlite3recover.h \ $(TOP)\src\test_windirent.c # If use of zlib is enabled, add the "zipfile.c" source file. @@ -2475,6 +2495,12 @@ tcltest: testfixture.exe @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\veryquick.test $(TESTOPTS) +# Runs all the same tests cases as the "tcltest" target but uses +# the testrunner.tcl script to run them in multiple cores +# concurrently. +testrunner: testfixture.exe + .\testfixture.exe $(TOP)\test\testrunner.tcl + smoketest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\main.test $(TESTOPTS) diff --git a/VERSION b/VERSION index bd5311787..e49080f12 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.39.4 +3.40.1 diff --git a/autoconf/tea/Makefile.in b/autoconf/tea/Makefile.in index 3e481dadf..5264f89f3 100644 --- a/autoconf/tea/Makefile.in +++ b/autoconf/tea/Makefile.in @@ -11,8 +11,6 @@ # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. -# -# RCS: @(#) $Id: Makefile.in,v 1.59 2005/07/26 19:17:02 mdejong Exp $ #======================================================================== # Add additional lines to handle any additional AC_SUBST cases that @@ -60,6 +58,8 @@ PKG_HEADERS = @PKG_HEADERS@ #======================================================================== PKG_LIB_FILE = @PKG_LIB_FILE@ +PKG_LIB_FILE8 = @PKG_LIB_FILE8@ +PKG_LIB_FILE9 = @PKG_LIB_FILE9@ PKG_STUB_LIB_FILE = @PKG_STUB_LIB_FILE@ lib_BINARIES = $(PKG_LIB_FILE) @@ -73,10 +73,11 @@ exec_prefix = @exec_prefix@ bindir = @bindir@ libdir = @libdir@ +includedir = @includedir@ datarootdir = @datarootdir@ +runstatedir = @runstatedir@ datadir = @datadir@ mandir = @mandir@ -includedir = @includedir@ DESTDIR = @@ -85,24 +86,25 @@ pkgdatadir = $(datadir)/$(PKG_DIR) pkglibdir = $(libdir)/$(PKG_DIR) pkgincludedir = $(includedir)/$(PKG_DIR) -top_builddir = . +top_builddir = @abs_top_builddir@ -INSTALL = @INSTALL@ -INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_OPTIONS = +INSTALL = @INSTALL@ $(INSTALL_OPTIONS) +INSTALL_DATA_DIR = @INSTALL_DATA_DIR@ INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_LIBRARY = @INSTALL_LIBRARY@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ CC = @CC@ +CCLD = @CCLD@ CFLAGS_DEFAULT = @CFLAGS_DEFAULT@ CFLAGS_WARNING = @CFLAGS_WARNING@ -CLEANFILES = @CLEANFILES@ EXEEXT = @EXEEXT@ LDFLAGS_DEFAULT = @LDFLAGS_DEFAULT@ MAKE_LIB = @MAKE_LIB@ -MAKE_SHARED_LIB = @MAKE_SHARED_LIB@ -MAKE_STATIC_LIB = @MAKE_STATIC_LIB@ MAKE_STUB_LIB = @MAKE_STUB_LIB@ OBJEXT = @OBJEXT@ RANLIB = @RANLIB@ @@ -117,8 +119,6 @@ TCL_SRC_DIR = @TCL_SRC_DIR@ #TK_BIN_DIR = @TK_BIN_DIR@ #TK_SRC_DIR = @TK_SRC_DIR@ -# This is no longer necessary even for packages that use private Tcl headers -#TCL_TOP_DIR_NATIVE = @TCL_TOP_DIR_NATIVE@ # Not used, but retained for reference of what libs Tcl required #TCL_LIBS = @TCL_LIBS@ @@ -132,41 +132,52 @@ TCL_SRC_DIR = @TCL_SRC_DIR@ EXTRA_PATH = $(top_builddir):$(TCL_BIN_DIR) #EXTRA_PATH = $(top_builddir):$(TCL_BIN_DIR):$(TK_BIN_DIR) TCLLIBPATH = $(top_builddir) -TCLSH_ENV = TCL_LIBRARY=`@CYGPATH@ $(TCL_SRC_DIR)/library` \ - @LD_LIBRARY_PATH_VAR@="$(EXTRA_PATH):$(@LD_LIBRARY_PATH_VAR@)" \ +TCLSH_ENV = TCL_LIBRARY=`@CYGPATH@ $(TCL_SRC_DIR)/library` +PKG_ENV = @LD_LIBRARY_PATH_VAR@="$(EXTRA_PATH):$(@LD_LIBRARY_PATH_VAR@)" \ PATH="$(EXTRA_PATH):$(PATH)" \ TCLLIBPATH="$(TCLLIBPATH)" -# TK_LIBRARY=`@CYGPATH@ $(TK_SRC_DIR)/library` TCLSH_PROG = @TCLSH_PROG@ -TCLSH = $(TCLSH_ENV) $(TCLSH_PROG) +TCLSH = $(TCLSH_ENV) $(PKG_ENV) $(TCLSH_PROG) +#WISH_ENV = TK_LIBRARY=`@CYGPATH@ $(TK_SRC_DIR)/library` #WISH_PROG = @WISH_PROG@ -#WISH = $(TCLSH_ENV) $(WISH_PROG) - +#WISH = $(TCLSH_ENV) $(WISH_ENV) $(PKG_ENV) $(WISH_PROG) SHARED_BUILD = @SHARED_BUILD@ -INCLUDES = @PKG_INCLUDES@ @TCL_INCLUDES@ -I$(srcdir)/.. +INCLUDES = @PKG_INCLUDES@ @TCL_INCLUDES@ -I. -I$(srcdir)/.. #INCLUDES = @PKG_INCLUDES@ @TCL_INCLUDES@ @TK_INCLUDES@ @TK_XINCLUDES@ PKG_CFLAGS = @PKG_CFLAGS@ # TCL_DEFS is not strictly need here, but if you remove it, then you -# must make sure that configure.in checks for the necessary components +# must make sure that configure.ac checks for the necessary components # that your library may use. TCL_DEFS can actually be a problem if # you do not compile with a similar machine setup as the Tcl core was # compiled with. #DEFS = $(TCL_DEFS) @DEFS@ $(PKG_CFLAGS) DEFS = @DEFS@ $(PKG_CFLAGS) +# Move pkgIndex.tcl to 'BINARIES' var if it is generated in the Makefile CONFIG_CLEAN_FILES = Makefile pkgIndex.tcl +CLEANFILES = @CLEANFILES@ CPPFLAGS = @CPPFLAGS@ LIBS = @PKG_LIBS@ @LIBS@ AR = @AR@ CFLAGS = @CFLAGS@ -COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LDFLAGS = @LDFLAGS@ +LDFLAGS_DEFAULT = @LDFLAGS_DEFAULT@ +COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) \ + $(CFLAGS_DEFAULT) $(CFLAGS_WARNING) $(SHLIB_CFLAGS) $(CFLAGS) + +GDB = gdb +VALGRIND = valgrind +VALGRINDARGS = --tool=memcheck --num-callers=8 --leak-resolution=high \ + --leak-check=yes --show-reachable=yes -v + +.SUFFIXES: .c .$(OBJEXT) #======================================================================== # Start of user-definable TARGETS section @@ -174,7 +185,7 @@ COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(C #======================================================================== # TEA TARGETS. Please note that the "libraries:" target refers to platform -# independent files, and the "binaries:" target inclues executable programs and +# independent files, and the "binaries:" target includes executable programs and # platform-dependent libraries. Modify these targets so that they install # the various pieces of your package. The make and install rules # for the BINARIES that you specified above have already been done. @@ -193,7 +204,6 @@ binaries: $(BINARIES) libraries: - #======================================================================== # Your doc target should differentiate from doc builds (by the developer) # and doc installs (see install-doc), which just install the docs on the @@ -216,11 +226,11 @@ install-binaries: binaries install-lib-binaries install-bin-binaries #======================================================================== install-libraries: libraries - @mkdir -p $(DESTDIR)$(includedir) + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(includedir)" @echo "Installing header files in $(DESTDIR)$(includedir)" @list='$(PKG_HEADERS)'; for i in $$list; do \ echo "Installing $(srcdir)/$$i" ; \ - $(INSTALL_DATA) $(srcdir)/$$i $(DESTDIR)$(includedir) ; \ + $(INSTALL_DATA) $(srcdir)/$$i "$(DESTDIR)$(includedir)" ; \ done; #======================================================================== @@ -229,12 +239,11 @@ install-libraries: libraries #======================================================================== install-doc: doc - @mkdir -p $(DESTDIR)$(mandir)/mann + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(mandir)/mann" @echo "Installing documentation in $(DESTDIR)$(mandir)" @list='$(srcdir)/doc/*.n'; for i in $$list; do \ echo "Installing $$i"; \ - rm -f $(DESTDIR)$(mandir)/mann/`basename $$i`; \ - $(INSTALL_DATA) $$i $(DESTDIR)$(mandir)/mann ; \ + $(INSTALL_DATA) $$i "$(DESTDIR)$(mandir)/mann" ; \ done test: binaries libraries @@ -244,7 +253,21 @@ shell: binaries libraries @$(TCLSH) $(SCRIPT) gdb: - $(TCLSH_ENV) gdb $(TCLSH_PROG) $(SCRIPT) + $(TCLSH_ENV) $(PKG_ENV) $(GDB) $(TCLSH_PROG) $(SCRIPT) + +gdb-test: binaries libraries + $(TCLSH_ENV) $(PKG_ENV) $(GDB) \ + --args $(TCLSH_PROG) `@CYGPATH@ $(srcdir)/tests/all.tcl` \ + $(TESTFLAGS) -singleproc 1 \ + -load "package ifneeded $(PACKAGE_NAME) $(PACKAGE_VERSION) \ + [list load `@CYGPATH@ $(PKG_LIB_FILE)` [string totitle $(PACKAGE_NAME)]]" + +valgrind: binaries libraries + $(TCLSH_ENV) $(PKG_ENV) $(VALGRIND) $(VALGRINDARGS) $(TCLSH_PROG) \ + `@CYGPATH@ $(srcdir)/tests/all.tcl` $(TESTFLAGS) + +valgrindshell: binaries libraries + $(TCLSH_ENV) $(PKG_ENV) $(VALGRIND) $(VALGRINDARGS) $(TCLSH_PROG) $(SCRIPT) depend: @@ -283,49 +306,70 @@ $(PKG_STUB_LIB_FILE): $(PKG_STUB_OBJECTS) # As necessary, add $(srcdir):$(srcdir)/compat:.... #======================================================================== -VPATH = $(srcdir):$(srcdir)/generic:$(srcdir)/unix:$(srcdir)/win +VPATH = $(srcdir):$(srcdir)/generic:$(srcdir)/unix:$(srcdir)/win:$(srcdir)/macosx .c.@OBJEXT@: $(COMPILE) -c `@CYGPATH@ $<` -o $@ +tclsample.@OBJEXT@: sampleUuid.h + +$(srcdir)/manifest.uuid: + printf "git-" >$(srcdir)/manifest.uuid + (cd $(srcdir); git rev-parse HEAD >>$(srcdir)/manifest.uuid || \ + (printf "svn-r" >$(srcdir)/manifest.uuid ; \ + svn info --show-item last-changed-revision >>$(srcdir)/manifest.uuid) || \ + printf "unknown" >$(srcdir)/manifest.uuid) + +sampleUuid.h: $(srcdir)/manifest.uuid + echo "#define SAMPLE_VERSION_UUID \\" >$@ + cat $(srcdir)/manifest.uuid >>$@ + echo "" >>$@ + #======================================================================== # Distribution creation # You may need to tweak this target to make it work correctly. #======================================================================== #COMPRESS = tar cvf $(PKG_DIR).tar $(PKG_DIR); compress $(PKG_DIR).tar -COMPRESS = gtar zcvf $(PKG_DIR).tar.gz $(PKG_DIR) +COMPRESS = tar zcvf $(PKG_DIR).tar.gz $(PKG_DIR) DIST_ROOT = /tmp/dist DIST_DIR = $(DIST_ROOT)/$(PKG_DIR) +DIST_INSTALL_DATA = CPPROG='cp -p' $(INSTALL) -m 644 +DIST_INSTALL_SCRIPT = CPPROG='cp -p' $(INSTALL) -m 755 + dist-clean: rm -rf $(DIST_DIR) $(DIST_ROOT)/$(PKG_DIR).tar.* -dist: dist-clean - mkdir -p $(DIST_DIR) - cp -p $(srcdir)/README* $(srcdir)/license* \ - $(srcdir)/aclocal.m4 $(srcdir)/configure $(srcdir)/*.in \ - $(DIST_DIR)/ - chmod 664 $(DIST_DIR)/Makefile.in $(DIST_DIR)/aclocal.m4 - chmod 775 $(DIST_DIR)/configure $(DIST_DIR)/configure.in - - for i in $(srcdir)/*.[ch]; do \ - if [ -f $$i ]; then \ - cp -p $$i $(DIST_DIR)/ ; \ - fi; \ - done; - - mkdir $(DIST_DIR)/tclconfig - cp $(srcdir)/tclconfig/install-sh $(srcdir)/tclconfig/tcl.m4 \ - $(DIST_DIR)/tclconfig/ - chmod 664 $(DIST_DIR)/tclconfig/tcl.m4 - chmod +x $(DIST_DIR)/tclconfig/install-sh - - list='demos doc generic library mac tests unix win'; \ +dist: dist-clean $(srcdir)/manifest.uuid + $(INSTALL_DATA_DIR) $(DIST_DIR) + + # TEA files + $(DIST_INSTALL_DATA) $(srcdir)/Makefile.in \ + $(srcdir)/aclocal.m4 $(srcdir)/configure.ac \ + $(DIST_DIR)/ + $(DIST_INSTALL_SCRIPT) $(srcdir)/configure $(DIST_DIR)/ + + $(INSTALL_DATA_DIR) $(DIST_DIR)/tclconfig + $(DIST_INSTALL_DATA) $(srcdir)/tclconfig/README.txt \ + $(srcdir)/manifest.uuid \ + $(srcdir)/tclconfig/tcl.m4 $(srcdir)/tclconfig/install-sh \ + $(DIST_DIR)/tclconfig/ + + # Extension files + $(DIST_INSTALL_DATA) \ + $(srcdir)/ChangeLog \ + $(srcdir)/README.sha \ + $(srcdir)/license.terms \ + $(srcdir)/README \ + $(srcdir)/pkgIndex.tcl.in \ + $(DIST_DIR)/ + + list='demos doc generic library macosx tests unix win'; \ for p in $$list; do \ if test -d $(srcdir)/$$p ; then \ - mkdir $(DIST_DIR)/$$p; \ - cp -p $(srcdir)/$$p/*.* $(DIST_DIR)/$$p/; \ + $(INSTALL_DATA_DIR) $(DIST_DIR)/$$p; \ + $(DIST_INSTALL_DATA) $(srcdir)/$$p/* $(DIST_DIR)/$$p/; \ fi; \ done @@ -337,10 +381,10 @@ dist: dist-clean #======================================================================== # Don't modify the file to clean here. Instead, set the "CLEANFILES" -# variable in configure.in +# variable in configure.ac #======================================================================== -clean: +clean: -test -z "$(BINARIES)" || rm -f $(BINARIES) -rm -f *.$(OBJEXT) core *.core -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) @@ -348,7 +392,7 @@ clean: distclean: clean -rm -f *.tab.c -rm -f $(CONFIG_CLEAN_FILES) - -rm -f config.h config.cache config.log config.status + -rm -f config.cache config.log config.status #======================================================================== # Install binary object libraries. On Windows this includes both .dll and @@ -362,25 +406,17 @@ distclean: clean #======================================================================== install-lib-binaries: binaries - @mkdir -p $(DESTDIR)$(pkglibdir) + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(pkglibdir)" @list='$(lib_BINARIES)'; for p in $$list; do \ if test -f $$p; then \ - echo " $(INSTALL_PROGRAM) $$p $(DESTDIR)$(pkglibdir)/$$p"; \ - $(INSTALL_PROGRAM) $$p $(DESTDIR)$(pkglibdir)/$$p; \ - stub=`echo $$p|sed -e "s/.*\(stub\).*/\1/"`; \ - if test "x$$stub" = "xstub"; then \ - echo " $(RANLIB_STUB) $(DESTDIR)$(pkglibdir)/$$p"; \ - $(RANLIB_STUB) $(DESTDIR)$(pkglibdir)/$$p; \ - else \ - echo " $(RANLIB) $(DESTDIR)$(pkglibdir)/$$p"; \ - $(RANLIB) $(DESTDIR)$(pkglibdir)/$$p; \ - fi; \ + echo " $(INSTALL_LIBRARY) $$p $(DESTDIR)$(pkglibdir)/$$p"; \ + $(INSTALL_LIBRARY) $$p "$(DESTDIR)$(pkglibdir)/$$p"; \ ext=`echo $$p|sed -e "s/.*\.//"`; \ if test "x$$ext" = "xdll"; then \ lib=`basename $$p|sed -e 's/.[^.]*$$//'`.lib; \ if test -f $$lib; then \ echo " $(INSTALL_DATA) $$lib $(DESTDIR)$(pkglibdir)/$$lib"; \ - $(INSTALL_DATA) $$lib $(DESTDIR)$(pkglibdir)/$$lib; \ + $(INSTALL_DATA) $$lib "$(DESTDIR)$(pkglibdir)/$$lib"; \ fi; \ fi; \ fi; \ @@ -389,12 +425,12 @@ install-lib-binaries: binaries if test -f $(srcdir)/$$p; then \ destp=`basename $$p`; \ echo " Install $$destp $(DESTDIR)$(pkglibdir)/$$destp"; \ - $(INSTALL_DATA) $(srcdir)/$$p $(DESTDIR)$(pkglibdir)/$$destp; \ + $(INSTALL_DATA) $(srcdir)/$$p "$(DESTDIR)$(pkglibdir)/$$destp"; \ fi; \ done @if test "x$(SHARED_BUILD)" = "x1"; then \ echo " Install pkgIndex.tcl $(DESTDIR)$(pkglibdir)"; \ - $(INSTALL_DATA) pkgIndex.tcl $(DESTDIR)$(pkglibdir); \ + $(INSTALL_DATA) pkgIndex.tcl "$(DESTDIR)$(pkglibdir)"; \ fi #======================================================================== @@ -407,33 +443,32 @@ install-lib-binaries: binaries #======================================================================== install-bin-binaries: binaries - @mkdir -p $(DESTDIR)$(bindir) + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(bindir)" @list='$(bin_BINARIES)'; for p in $$list; do \ if test -f $$p; then \ echo " $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/$$p"; \ - $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/$$p; \ + $(INSTALL_PROGRAM) $$p "$(DESTDIR)$(bindir)/$$p"; \ fi; \ done -.SUFFIXES: .c .$(OBJEXT) - Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status cd $(top_builddir) \ && CONFIG_FILES=$@ CONFIG_HEADERS= $(SHELL) ./config.status uninstall-binaries: list='$(lib_BINARIES)'; for p in $$list; do \ - rm -f $(DESTDIR)$(pkglibdir)/$$p; \ + rm -f "$(DESTDIR)$(pkglibdir)/$$p"; \ done list='$(PKG_TCL_SOURCES)'; for p in $$list; do \ p=`basename $$p`; \ - rm -f $(DESTDIR)$(pkglibdir)/$$p; \ + rm -f "$(DESTDIR)$(pkglibdir)/$$p"; \ done list='$(bin_BINARIES)'; for p in $$list; do \ - rm -f $(DESTDIR)$(bindir)/$$p; \ + rm -f "$(DESTDIR)$(bindir)/$$p"; \ done .PHONY: all binaries clean depend distclean doc install libraries test +.PHONY: gdb gdb-test valgrind valgrindshell # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index dae94f512..3e17b8b0b 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -2,11 +2,9 @@ dnl This file is an input file used by the GNU "autoconf" program to dnl generate the file "configure", which is run during Tcl installation dnl to configure the system for the local environment. -# -# RCS: @(#) $Id: configure.in,v 1.43 2005/07/26 19:17:05 mdejong Exp $ #----------------------------------------------------------------------- -# Sample configure.in for Tcl Extensions. The only places you should +# Sample configure.ac for Tcl Extensions. The only places you should # need to modify this file are marked by the string __CHANGE__ #----------------------------------------------------------------------- @@ -17,9 +15,11 @@ dnl to configure the system for the local environment. # This initializes the environment with PACKAGE_NAME and PACKAGE_VERSION # set as provided. These will also be added as -D defs in your Makefile # so you can encode the package version directly into the source files. +# This will also define a special symbol for Windows (BUILD_ +# so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite], [3.32.0]) +AC_INIT([sqlite],[3.40.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. @@ -27,7 +27,7 @@ AC_INIT([sqlite], [3.32.0]) # as well as PKG_LIB_FILE and PKG_STUB_LIB_FILE. #-------------------------------------------------------------------- -TEA_INIT([3.9]) +TEA_INIT() AC_CONFIG_AUX_DIR(tclconfig) @@ -55,8 +55,8 @@ TEA_PREFIX #----------------------------------------------------------------------- # Standard compiler checks. # This sets up CC by using the CC env var, or looks for gcc otherwise. -# This also calls AC_PROG_CC, AC_PROG_INSTALL and a few others to create -# the basic setup necessary to compile executables. +# This also calls AC_PROG_CC and a few others to create the basic setup +# necessary to compile executables. #----------------------------------------------------------------------- TEA_SETUP_COMPILER @@ -73,11 +73,19 @@ TEA_SETUP_COMPILER TEA_ADD_SOURCES([tclsqlite3.c]) TEA_ADD_HEADERS([]) -TEA_ADD_INCLUDES([-I\"`\${CYGPATH} \${srcdir}/generic`\"]) +TEA_ADD_INCLUDES([]) TEA_ADD_LIBS([]) TEA_ADD_CFLAGS([-DSQLITE_ENABLE_FTS3=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_FTS4=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_FTS5=1]) TEA_ADD_CFLAGS([-DSQLITE_3_SUFFIX_ONLY=1]) TEA_ADD_CFLAGS([-DSQLITE_ENABLE_RTREE=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_GEOPOLY=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_MATH_FUNCTIONS=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_DESERIALIZE=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_DBPAGE_VTAB=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_BYTECODE_VTAB=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_DBSTAT_VTAB=1]) TEA_ADD_STUB_SOURCES([]) TEA_ADD_TCL_SOURCES([]) @@ -99,6 +107,31 @@ if test x$with_system_sqlite != xno; then LIBS="$LIBS -lsqlite3"])]) fi +#-------------------------------------------------------------------- +# __CHANGE__ +# +# You can add more files to clean if your extension creates any extra +# files by extending CLEANFILES. +# Add pkgIndex.tcl if it is generated in the Makefile instead of ./configure +# and change Makefile.in to move it from CONFIG_CLEAN_FILES to BINARIES var. +# +# A few miscellaneous platform-specific items: +# TEA_ADD_* any platform specific compiler/build info here. +#-------------------------------------------------------------------- + +#CLEANFILES="$CLEANFILES pkgIndex.tcl" +if test "${TEA_PLATFORM}" = "windows" ; then + # Ensure no empty if clauses + : + #TEA_ADD_SOURCES([win/winFile.c]) + #TEA_ADD_INCLUDES([-I\"$(${CYGPATH} ${srcdir}/win)\"]) +else + # Ensure no empty else clauses + : + #TEA_ADD_SOURCES([unix/unixFile.c]) + #TEA_ADD_LIBS([-lsuperfly]) +fi + #-------------------------------------------------------------------- # __CHANGE__ # Choose which headers you need. Extension authors should try very @@ -151,28 +184,6 @@ TEA_CONFIG_CFLAGS TEA_ENABLE_SYMBOLS -#-------------------------------------------------------------------- -# Everyone should be linking against the Tcl stub library. If you -# can't for some reason, remove this definition. If you aren't using -# stubs, you also need to modify the SHLIB_LD_LIBS setting below to -# link against the non-stubbed Tcl library. Add Tk too if necessary. -#-------------------------------------------------------------------- - -AC_DEFINE(USE_TCL_STUBS, 1, [Use Tcl stubs]) -#AC_DEFINE(USE_TK_STUBS, 1, [Use Tk stubs]) - - -#-------------------------------------------------------------------- -# Redefine fdatasync as fsync on systems that lack fdatasync -#-------------------------------------------------------------------- -# -#AC_CHECK_FUNC(fdatasync, , AC_DEFINE(fdatasync, fsync)) -# Check for library functions that SQLite can optionally use. -AC_CHECK_FUNCS([fdatasync usleep fullfsync localtime_r gmtime_r]) - -AC_FUNC_STRERROR_R - - #-------------------------------------------------------------------- # This macro generates a line to use when building a library. It # depends on values set by the TEA_ENABLE_SHARED, TEA_ENABLE_SYMBOLS, @@ -193,9 +204,24 @@ TEA_PROG_TCLSH #TEA_PROG_WISH #-------------------------------------------------------------------- -# Finally, substitute all of the various values into the Makefile. -# You may alternatively have a special pkgIndex.tcl.in or other files -# which require substituting th AC variables in. Include these here. +# Setup a *Config.sh.in configuration file. +#-------------------------------------------------------------------- + +#TEA_EXPORT_CONFIG([sample]) +#AC_SUBST(SAMPLE_VAR) + +#-------------------------------------------------------------------- +# Specify files to substitute AC variables in. You may alternatively +# have a special pkgIndex.tcl.in or other files which require +# substituting the AC variables in. Include these here. +#-------------------------------------------------------------------- + +AC_CONFIG_FILES([Makefile pkgIndex.tcl]) +#AC_CONFIG_FILES([sampleConfig.sh]) + +#-------------------------------------------------------------------- +# Finally, substitute all of the various values into the files +# specified with AC_CONFIG_FILES. #-------------------------------------------------------------------- -AC_OUTPUT([Makefile pkgIndex.tcl]) +AC_OUTPUT diff --git a/autoconf/tea/pkgIndex.tcl.in b/autoconf/tea/pkgIndex.tcl.in index bc585f73b..f95f7d389 100644 --- a/autoconf/tea/pkgIndex.tcl.in +++ b/autoconf/tea/pkgIndex.tcl.in @@ -1,7 +1,10 @@ +# -*- tcl -*- +# Tcl package index file, version 1.1 # -# Tcl package index file -# -# Note sqlite*3* init specifically -# -package ifneeded sqlite3 @PACKAGE_VERSION@ \ - [list load [file join $dir @PKG_LIB_FILE@] Sqlite3] +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded sqlite3 @PACKAGE_VERSION@ \ + [list load [file join $dir @PKG_LIB_FILE9@] sqlite3] +} else { + package ifneeded sqlite3 @PACKAGE_VERSION@ \ + [list load [file join $dir @PKG_LIB_FILE8@] sqlite3] +} diff --git a/autoconf/tea/tclconfig/tcl.m4 b/autoconf/tea/tclconfig/tcl.m4 index 4b4bd1e88..c83d660e1 100644 --- a/autoconf/tea/tclconfig/tcl.m4 +++ b/autoconf/tea/tclconfig/tcl.m4 @@ -9,16 +9,13 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. -AC_PREREQ(2.57) - -dnl TEA extensions pass us the version of TEA they think they -dnl are compatible with (must be set in TEA_INIT below) -dnl TEA_VERSION="3.9" +AC_PREREQ([2.69]) # Possible values for key variables defined: # # TEA_WINDOWINGSYSTEM - win32 aqua x11 (mirrors 'tk windowingsystem') # TEA_PLATFORM - windows unix +# TEA_TK_EXTENSION - True if this is a Tk extension # #------------------------------------------------------------------------ @@ -53,9 +50,9 @@ AC_DEFUN([TEA_PATH_TCLCONFIG], [ # we reset no_tcl in case something fails here no_tcl=true AC_ARG_WITH(tcl, - AC_HELP_STRING([--with-tcl], + AS_HELP_STRING([--with-tcl], [directory containing tcl configuration (tclConfig.sh)]), - with_tclconfig="${withval}") + [with_tclconfig="${withval}"]) AC_MSG_CHECKING([for Tcl configuration]) AC_CACHE_VAL(ac_cv_c_tclconfig,[ @@ -107,7 +104,9 @@ AC_DEFUN([TEA_PATH_TCLCONFIG], [ for i in `ls -d ~/Library/Frameworks 2>/dev/null` \ `ls -d /Library/Frameworks 2>/dev/null` \ `ls -d /Network/Library/Frameworks 2>/dev/null` \ - `ls -d /System/Library/Frameworks 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/Library/Frameworks/Tcl.framework 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/Network/Library/Frameworks/Tcl.framework 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Tcl.framework 2>/dev/null` \ ; do if test -f "$i/Tcl.framework/tclConfig.sh" ; then ac_cv_c_tclconfig="`(cd $i/Tcl.framework; pwd)`" @@ -136,10 +135,15 @@ AC_DEFUN([TEA_PATH_TCLCONFIG], [ `ls -d ${prefix}/lib 2>/dev/null` \ `ls -d /usr/local/lib 2>/dev/null` \ `ls -d /usr/contrib/lib 2>/dev/null` \ + `ls -d /usr/pkg/lib 2>/dev/null` \ `ls -d /usr/lib 2>/dev/null` \ `ls -d /usr/lib64 2>/dev/null` \ `ls -d /usr/lib/tcl8.6 2>/dev/null` \ `ls -d /usr/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl8.5 2>/dev/null` \ ; do if test -f "$i/tclConfig.sh" ; then ac_cv_c_tclconfig="`(cd $i; pwd)`" @@ -208,9 +212,9 @@ AC_DEFUN([TEA_PATH_TKCONFIG], [ # we reset no_tk in case something fails here no_tk=true AC_ARG_WITH(tk, - AC_HELP_STRING([--with-tk], + AS_HELP_STRING([--with-tk], [directory containing tk configuration (tkConfig.sh)]), - with_tkconfig="${withval}") + [with_tkconfig="${withval}"]) AC_MSG_CHECKING([for Tk configuration]) AC_CACHE_VAL(ac_cv_c_tkconfig,[ @@ -262,7 +266,6 @@ AC_DEFUN([TEA_PATH_TKCONFIG], [ for i in `ls -d ~/Library/Frameworks 2>/dev/null` \ `ls -d /Library/Frameworks 2>/dev/null` \ `ls -d /Network/Library/Frameworks 2>/dev/null` \ - `ls -d /System/Library/Frameworks 2>/dev/null` \ ; do if test -f "$i/Tk.framework/tkConfig.sh" ; then ac_cv_c_tkconfig="`(cd $i/Tk.framework; pwd)`" @@ -278,8 +281,15 @@ AC_DEFUN([TEA_PATH_TKCONFIG], [ `ls -d ${prefix}/lib 2>/dev/null` \ `ls -d /usr/local/lib 2>/dev/null` \ `ls -d /usr/contrib/lib 2>/dev/null` \ + `ls -d /usr/pkg/lib 2>/dev/null` \ + `ls -d /usr/lib/tk8.6 2>/dev/null` \ + `ls -d /usr/lib/tk8.5 2>/dev/null` \ `ls -d /usr/lib 2>/dev/null` \ `ls -d /usr/lib64 2>/dev/null` \ + `ls -d /usr/local/lib/tk8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tk8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tk8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tk8.5 2>/dev/null` \ ; do if test -f "$i/tkConfig.sh" ; then ac_cv_c_tkconfig="`(cd $i; pwd)`" @@ -348,6 +358,8 @@ AC_DEFUN([TEA_PATH_TKCONFIG], [ # TCL_BIN_DIR # TCL_SRC_DIR # TCL_LIB_FILE +# TCL_ZIP_FILE +# TCL_ZIPFS_SUPPORT #------------------------------------------------------------------------ AC_DEFUN([TEA_LOAD_TCLCONFIG], [ @@ -360,10 +372,6 @@ AC_DEFUN([TEA_LOAD_TCLCONFIG], [ AC_MSG_RESULT([could not find ${TCL_BIN_DIR}/tclConfig.sh]) fi - # eval is required to do the TCL_DBGX substitution - eval "TCL_LIB_FILE=\"${TCL_LIB_FILE}\"" - eval "TCL_STUB_LIB_FILE=\"${TCL_STUB_LIB_FILE}\"" - # If the TCL_BIN_DIR is the build directory (not the install directory), # then set the common variable name to the value of the build variables. # For example, the variable TCL_LIB_SPEC will be set to the value @@ -397,12 +405,6 @@ AC_DEFUN([TEA_LOAD_TCLCONFIG], [ esac fi - # eval is required to do the TCL_DBGX substitution - eval "TCL_LIB_FLAG=\"${TCL_LIB_FLAG}\"" - eval "TCL_LIB_SPEC=\"${TCL_LIB_SPEC}\"" - eval "TCL_STUB_LIB_FLAG=\"${TCL_STUB_LIB_FLAG}\"" - eval "TCL_STUB_LIB_SPEC=\"${TCL_STUB_LIB_SPEC}\"" - AC_SUBST(TCL_VERSION) AC_SUBST(TCL_PATCH_LEVEL) AC_SUBST(TCL_BIN_DIR) @@ -418,13 +420,18 @@ AC_DEFUN([TEA_LOAD_TCLCONFIG], [ AC_MSG_CHECKING([platform]) hold_cc=$CC; CC="$TCL_CC" - AC_TRY_COMPILE(,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ #ifdef _WIN32 #error win32 #endif - ], TEA_PLATFORM="unix", + ]])],[ + # first test we've already retrieved platform (cross-compile), fallback to unix otherwise: + TEA_PLATFORM="${TEA_PLATFORM-unix}" + CYGPATH=echo + ],[ TEA_PLATFORM="windows" - ) + AC_CHECK_PROG(CYGPATH, cygpath, cygpath -m, echo) + ]) CC=$hold_cc AC_MSG_RESULT($TEA_PLATFORM) @@ -473,10 +480,6 @@ AC_DEFUN([TEA_LOAD_TKCONFIG], [ AC_MSG_RESULT([could not find ${TK_BIN_DIR}/tkConfig.sh]) fi - # eval is required to do the TK_DBGX substitution - eval "TK_LIB_FILE=\"${TK_LIB_FILE}\"" - eval "TK_STUB_LIB_FILE=\"${TK_STUB_LIB_FILE}\"" - # If the TK_BIN_DIR is the build directory (not the install directory), # then set the common variable name to the value of the build variables. # For example, the variable TK_LIB_SPEC will be set to the value @@ -510,12 +513,6 @@ AC_DEFUN([TEA_LOAD_TKCONFIG], [ esac fi - # eval is required to do the TK_DBGX substitution - eval "TK_LIB_FLAG=\"${TK_LIB_FLAG}\"" - eval "TK_LIB_SPEC=\"${TK_LIB_SPEC}\"" - eval "TK_STUB_LIB_FLAG=\"${TK_STUB_LIB_FLAG}\"" - eval "TK_STUB_LIB_SPEC=\"${TK_STUB_LIB_SPEC}\"" - # TEA specific: Ensure windowingsystem is defined if test "${TEA_PLATFORM}" = "unix" ; then case ${TK_DEFS} in @@ -572,16 +569,24 @@ AC_DEFUN([TEA_PROG_TCLSH], [ if test -f "${TCL_BIN_DIR}/Makefile" ; then # tclConfig.sh is in Tcl build directory if test "${TEA_PLATFORM}" = "windows"; then - TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${TCL_DBGX}${EXEEXT}" + if test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" + fi else TCLSH_PROG="${TCL_BIN_DIR}/tclsh" fi else # tclConfig.sh is in install location if test "${TEA_PLATFORM}" = "windows"; then - TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${TCL_DBGX}${EXEEXT}" + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" else - TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}${TCL_DBGX}" + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}" fi list="`ls -d ${TCL_BIN_DIR}/../bin 2>/dev/null` \ `ls -d ${TCL_BIN_DIR}/.. 2>/dev/null` \ @@ -622,16 +627,24 @@ AC_DEFUN([TEA_PROG_WISH], [ if test -f "${TK_BIN_DIR}/Makefile" ; then # tkConfig.sh is in Tk build directory if test "${TEA_PLATFORM}" = "windows"; then - WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${TK_DBGX}${EXEEXT}" + if test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}s${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}$s{EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" + fi else WISH_PROG="${TK_BIN_DIR}/wish" fi else # tkConfig.sh is in install location if test "${TEA_PLATFORM}" = "windows"; then - WISH_PROG="wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${TK_DBGX}${EXEEXT}" + WISH_PROG="wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" else - WISH_PROG="wish${TK_MAJOR_VERSION}.${TK_MINOR_VERSION}${TK_DBGX}" + WISH_PROG="wish${TK_MAJOR_VERSION}.${TK_MINOR_VERSION}" fi list="`ls -d ${TK_BIN_DIR}/../bin 2>/dev/null` \ `ls -d ${TK_BIN_DIR}/.. 2>/dev/null` \ @@ -660,6 +673,7 @@ AC_DEFUN([TEA_PROG_WISH], [ # # Adds the following arguments to configure: # --enable-shared=yes|no +# --enable-stubs=yes|no # # Defines the following vars: # STATIC_BUILD Used for building import/export libraries @@ -667,31 +681,63 @@ AC_DEFUN([TEA_PROG_WISH], [ # # Sets the following vars: # SHARED_BUILD Value of 1 or 0 +# STUBS_BUILD Value if 1 or 0 +# USE_TCL_STUBS Value true: if SHARED_BUILD or --enable-stubs +# USE_TCLOO_STUBS Value true: if SHARED_BUILD or --enable-stubs +# USE_TK_STUBS Value true: if SHARED_BUILD or --enable-stubs +# AND TEA_WINDOWING_SYSTEM != "" #------------------------------------------------------------------------ - AC_DEFUN([TEA_ENABLE_SHARED], [ AC_MSG_CHECKING([how to build libraries]) AC_ARG_ENABLE(shared, - AC_HELP_STRING([--enable-shared], + AS_HELP_STRING([--enable-shared], [build and link with shared libraries (default: on)]), - [tcl_ok=$enableval], [tcl_ok=yes]) + [shared_ok=$enableval], [shared_ok=yes]) if test "${enable_shared+set}" = set; then enableval="$enable_shared" - tcl_ok=$enableval + shared_ok=$enableval else - tcl_ok=yes + shared_ok=yes + fi + + AC_ARG_ENABLE(stubs, + AS_HELP_STRING([--enable-stubs], + [build and link with stub libraries. Always true for shared builds (default: on)]), + [stubs_ok=$enableval], [stubs_ok=yes]) + + if test "${enable_stubs+set}" = set; then + enableval="$enable_stubs" + stubs_ok=$enableval + else + stubs_ok=yes fi - if test "$tcl_ok" = "yes" ; then + # Stubs are always enabled for shared builds + if test "$shared_ok" = "yes" ; then AC_MSG_RESULT([shared]) SHARED_BUILD=1 + STUBS_BUILD=1 else AC_MSG_RESULT([static]) SHARED_BUILD=0 - AC_DEFINE(STATIC_BUILD, 1, [Is this a static build?]) + AC_DEFINE(STATIC_BUILD, 1, [This a static build]) + if test "$stubs_ok" = "yes" ; then + STUBS_BUILD=1 + else + STUBS_BUILD=0 + fi + fi + if test "${STUBS_BUILD}" = "1" ; then + AC_DEFINE(USE_TCL_STUBS, 1, [Use Tcl stubs]) + AC_DEFINE(USE_TCLOO_STUBS, 1, [Use TclOO stubs]) + if test "${TEA_WINDOWINGSYSTEM}" != ""; then + AC_DEFINE(USE_TK_STUBS, 1, [Use Tk stubs]) + fi fi + AC_SUBST(SHARED_BUILD) + AC_SUBST(STUBS_BUILD) ]) #------------------------------------------------------------------------ @@ -728,8 +774,8 @@ AC_DEFUN([TEA_ENABLE_SHARED], [ AC_DEFUN([TEA_ENABLE_THREADS], [ AC_ARG_ENABLE(threads, - AC_HELP_STRING([--enable-threads], - [build with threads]), + AS_HELP_STRING([--enable-threads], + [build with threads (default: on)]), [tcl_ok=$enableval], [tcl_ok=yes]) if test "${enable_threads+set}" = set; then @@ -813,14 +859,6 @@ AC_DEFUN([TEA_ENABLE_THREADS], [ that IS thread-enabled. It is recommended to use --enable-threads.]) fi ;; - *) - if test "${TCL_THREADS}" = "1"; then - AC_MSG_WARN([ - --enable-threads requested, but building against a Tcl that is NOT - thread-enabled. This is an OK configuration that will also run in - a thread-enabled core.]) - fi - ;; esac AC_SUBST(TCL_THREADS) ]) @@ -850,8 +888,6 @@ AC_DEFUN([TEA_ENABLE_THREADS], [ # Sets to "$(CFLAGS_OPTIMIZE) -DNDEBUG" if false # LDFLAGS_DEFAULT Sets to $(LDFLAGS_DEBUG) if true # Sets to $(LDFLAGS_OPTIMIZE) if false -# DBGX Formerly used as debug library extension; -# always blank now. #------------------------------------------------------------------------ AC_DEFUN([TEA_ENABLE_SYMBOLS], [ @@ -859,14 +895,14 @@ AC_DEFUN([TEA_ENABLE_SYMBOLS], [ AC_REQUIRE([TEA_CONFIG_CFLAGS]) AC_MSG_CHECKING([for build with symbols]) AC_ARG_ENABLE(symbols, - AC_HELP_STRING([--enable-symbols], + AS_HELP_STRING([--enable-symbols], [build with debugging symbols (default: off)]), [tcl_ok=$enableval], [tcl_ok=no]) - DBGX="" if test "$tcl_ok" = "no"; then CFLAGS_DEFAULT="${CFLAGS_OPTIMIZE} -DNDEBUG" LDFLAGS_DEFAULT="${LDFLAGS_OPTIMIZE}" AC_MSG_RESULT([no]) + AC_DEFINE(TCL_CFG_OPTIMIZED, 1, [Is this an optimized build?]) else CFLAGS_DEFAULT="${CFLAGS_DEBUG}" LDFLAGS_DEFAULT="${LDFLAGS_DEBUG}" @@ -874,13 +910,8 @@ AC_DEFUN([TEA_ENABLE_SYMBOLS], [ AC_MSG_RESULT([yes (standard debugging)]) fi fi - # TEA specific: - if test "${TEA_PLATFORM}" != "windows" ; then - LDFLAGS_DEFAULT="${LDFLAGS}" - fi AC_SUBST(CFLAGS_DEFAULT) AC_SUBST(LDFLAGS_DEFAULT) - AC_SUBST(TCL_DBGX) if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then AC_DEFINE(TCL_MEM_DEBUG, 1, [Is memory debugging enabled?]) @@ -915,7 +946,7 @@ AC_DEFUN([TEA_ENABLE_SYMBOLS], [ AC_DEFUN([TEA_ENABLE_LANGINFO], [ AC_ARG_ENABLE(langinfo, - AC_HELP_STRING([--enable-langinfo], + AS_HELP_STRING([--enable-langinfo], [use nl_langinfo if possible to determine encoding at startup, otherwise use old heuristic (default: on)]), [langinfo_ok=$enableval], [langinfo_ok=yes]) @@ -926,7 +957,7 @@ AC_DEFUN([TEA_ENABLE_LANGINFO], [ AC_MSG_CHECKING([whether to use nl_langinfo]) if test "$langinfo_ok" = "yes"; then AC_CACHE_VAL(tcl_cv_langinfo_h, [ - AC_TRY_COMPILE([#include ], [nl_langinfo(CODESET);], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[nl_langinfo(CODESET);]])], [tcl_cv_langinfo_h=yes],[tcl_cv_langinfo_h=no])]) AC_MSG_RESULT([$tcl_cv_langinfo_h]) if test $tcl_cv_langinfo_h = yes; then @@ -951,6 +982,7 @@ AC_DEFUN([TEA_ENABLE_LANGINFO], [ # Defines the following var: # # system - System/platform/version identification code. +# #-------------------------------------------------------------------- AC_DEFUN([TEA_CONFIG_SYSTEM], [ @@ -967,6 +999,9 @@ AC_DEFUN([TEA_CONFIG_SYSTEM], [ if test "`uname -s`" = "AIX" ; then tcl_cv_sys_version=AIX-`uname -v`.`uname -r` fi + if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then + tcl_cv_sys_version=NetBSD-Debian + fi fi fi ]) @@ -1043,7 +1078,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_MSG_CHECKING([if 64bit support is requested]) AC_ARG_ENABLE(64bit, - AC_HELP_STRING([--enable-64bit], + AS_HELP_STRING([--enable-64bit], [enable 64bit support (default: off)]), [do64bit=$enableval], [do64bit=no]) AC_MSG_RESULT([$do64bit]) @@ -1052,7 +1087,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_MSG_CHECKING([if 64bit Sparc VIS support is requested]) AC_ARG_ENABLE(64bit-vis, - AC_HELP_STRING([--enable-64bit-vis], + AS_HELP_STRING([--enable-64bit-vis], [enable 64bit Sparc VIS support (default: off)]), [do64bitVIS=$enableval], [do64bitVIS=no]) AC_MSG_RESULT([$do64bitVIS]) @@ -1065,10 +1100,10 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_CACHE_CHECK([if compiler supports visibility "hidden"], tcl_cv_cc_visibility_hidden, [ hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" - AC_TRY_LINK([ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ extern __attribute__((__visibility__("hidden"))) void f(void); - void f(void) {}], [f();], tcl_cv_cc_visibility_hidden=yes, - tcl_cv_cc_visibility_hidden=no) + void f(void) {}]], [[f();]])],[tcl_cv_cc_visibility_hidden=yes], + [tcl_cv_cc_visibility_hidden=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_visibility_hidden = yes], [ AC_DEFINE(MODULE_SCOPE, @@ -1081,22 +1116,11 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_MSG_CHECKING([if rpath support is requested]) AC_ARG_ENABLE(rpath, - AC_HELP_STRING([--disable-rpath], + AS_HELP_STRING([--disable-rpath], [disable rpath support (default: on)]), [doRpath=$enableval], [doRpath=yes]) AC_MSG_RESULT([$doRpath]) - # TEA specific: Cross-compiling options for Windows/CE builds? - - AS_IF([test "${TEA_PLATFORM}" = windows], [ - AC_MSG_CHECKING([if Windows/CE build is requested]) - AC_ARG_ENABLE(wince, - AC_HELP_STRING([--enable-wince], - [enable Win/CE support (where applicable)]), - [doWince=$enableval], [doWince=no]) - AC_MSG_RESULT([$doWince]) - ]) - # Set the variable "system" to hold the name and version number # for the system. @@ -1133,99 +1157,23 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_CHECK_TOOL(AR, ar) STLIB_LD='${AR} cr' LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH" - AS_IF([test "x$SHLIB_VERSION" = x],[SHLIB_VERSION="1.0"]) + AS_IF([test "x$SHLIB_VERSION" = x],[SHLIB_VERSION=""],[SHLIB_VERSION=".$SHLIB_VERSION"]) case $system in # TEA specific: windows) - # This is a 2-stage check to make sure we have the 64-bit SDK - # We have to know where the SDK is installed. - # This magic is based on MS Platform SDK for Win2003 SP1 - hobbs - # MACHINE is IX86 for LINK, but this is used by the manifest, - # which requires x86|amd64|ia64. MACHINE="X86" if test "$do64bit" != "no" ; then - if test "x${MSSDK}x" = "xx" ; then - MSSDK="C:/Progra~1/Microsoft Platform SDK" - fi - MSSDK=`echo "$MSSDK" | sed -e 's!\\\!/!g'` - PATH64="" case "$do64bit" in amd64|x64|yes) MACHINE="AMD64" ; # default to AMD64 64-bit build - PATH64="${MSSDK}/Bin/Win64/x86/AMD64" + ;; + arm64|aarch64) + MACHINE="ARM64" ;; ia64) MACHINE="IA64" - PATH64="${MSSDK}/Bin/Win64" ;; esac - if test "$GCC" != "yes" -a ! -d "${PATH64}" ; then - AC_MSG_WARN([Could not find 64-bit $MACHINE SDK to enable 64bit mode]) - AC_MSG_WARN([Ensure latest Platform SDK is installed]) - do64bit="no" - else - AC_MSG_RESULT([ Using 64-bit $MACHINE mode]) - do64bit_ok="yes" - fi - fi - - if test "$doWince" != "no" ; then - if test "$do64bit" != "no" ; then - AC_MSG_ERROR([Windows/CE and 64-bit builds incompatible]) - fi - if test "$GCC" = "yes" ; then - AC_MSG_ERROR([Windows/CE and GCC builds incompatible]) - fi - TEA_PATH_CELIB - # Set defaults for common evc4/PPC2003 setup - # Currently Tcl requires 300+, possibly 420+ for sockets - CEVERSION=420; # could be 211 300 301 400 420 ... - TARGETCPU=ARMV4; # could be ARMV4 ARM MIPS SH3 X86 ... - ARCH=ARM; # could be ARM MIPS X86EM ... - PLATFORM="Pocket PC 2003"; # or "Pocket PC 2002" - if test "$doWince" != "yes"; then - # If !yes then the user specified something - # Reset ARCH to allow user to skip specifying it - ARCH= - eval `echo $doWince | awk -F, '{ \ - if (length([$]1)) { printf "CEVERSION=\"%s\"\n", [$]1; \ - if ([$]1 < 400) { printf "PLATFORM=\"Pocket PC 2002\"\n" } }; \ - if (length([$]2)) { printf "TARGETCPU=\"%s\"\n", toupper([$]2) }; \ - if (length([$]3)) { printf "ARCH=\"%s\"\n", toupper([$]3) }; \ - if (length([$]4)) { printf "PLATFORM=\"%s\"\n", [$]4 }; \ - }'` - if test "x${ARCH}" = "x" ; then - ARCH=$TARGETCPU; - fi - fi - OSVERSION=WCE$CEVERSION; - if test "x${WCEROOT}" = "x" ; then - WCEROOT="C:/Program Files/Microsoft eMbedded C++ 4.0" - if test ! -d "${WCEROOT}" ; then - WCEROOT="C:/Program Files/Microsoft eMbedded Tools" - fi - fi - if test "x${SDKROOT}" = "x" ; then - SDKROOT="C:/Program Files/Windows CE Tools" - if test ! -d "${SDKROOT}" ; then - SDKROOT="C:/Windows CE Tools" - fi - fi - WCEROOT=`echo "$WCEROOT" | sed -e 's!\\\!/!g'` - SDKROOT=`echo "$SDKROOT" | sed -e 's!\\\!/!g'` - if test ! -d "${SDKROOT}/${OSVERSION}/${PLATFORM}/Lib/${TARGETCPU}" \ - -o ! -d "${WCEROOT}/EVC/${OSVERSION}/bin"; then - AC_MSG_ERROR([could not find PocketPC SDK or target compiler to enable WinCE mode [$CEVERSION,$TARGETCPU,$ARCH,$PLATFORM]]) - doWince="no" - else - # We could PATH_NOSPACE these, but that's not important, - # as long as we quote them when used. - CEINCLUDE="${SDKROOT}/${OSVERSION}/${PLATFORM}/include" - if test -d "${CEINCLUDE}/${TARGETCPU}" ; then - CEINCLUDE="${CEINCLUDE}/${TARGETCPU}" - fi - CELIBPATH="${SDKROOT}/${OSVERSION}/${PLATFORM}/Lib/${TARGETCPU}" - fi fi if test "$GCC" != "yes" ; then @@ -1234,48 +1182,28 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ else runtime=-MD fi + case "x`echo \${VisualStudioVersion}`" in + x1[[4-9]]*) + lflags="${lflags} -nodefaultlib:libucrt.lib" + TEA_ADD_LIBS([ucrt.lib]) + ;; + *) + ;; + esac if test "$do64bit" != "no" ; then - # All this magic is necessary for the Win64 SDK RC1 - hobbs - CC="\"${PATH64}/cl.exe\"" - CFLAGS="${CFLAGS} -I\"${MSSDK}/Include\" -I\"${MSSDK}/Include/crt\" -I\"${MSSDK}/Include/crt/sys\"" - RC="\"${MSSDK}/bin/rc.exe\"" - lflags="-nologo -MACHINE:${MACHINE} -LIBPATH:\"${MSSDK}/Lib/${MACHINE}\"" - LINKBIN="\"${PATH64}/link.exe\"" + CC="cl.exe" + RC="rc.exe" + lflags="${lflags} -nologo -MACHINE:${MACHINE} " + LINKBIN="link.exe" CFLAGS_DEBUG="-nologo -Zi -Od -W3 ${runtime}d" CFLAGS_OPTIMIZE="-nologo -O2 -W2 ${runtime}" # Avoid 'unresolved external symbol __security_cookie' # errors, c.f. http://support.microsoft.com/?id=894573 TEA_ADD_LIBS([bufferoverflowU.lib]) - elif test "$doWince" != "no" ; then - CEBINROOT="${WCEROOT}/EVC/${OSVERSION}/bin" - if test "${TARGETCPU}" = "X86"; then - CC="\"${CEBINROOT}/cl.exe\"" - else - CC="\"${CEBINROOT}/cl${ARCH}.exe\"" - fi - CFLAGS="$CFLAGS -I\"${CELIB_DIR}/inc\" -I\"${CEINCLUDE}\"" - RC="\"${WCEROOT}/Common/EVC/bin/rc.exe\"" - arch=`echo ${ARCH} | awk '{print tolower([$]0)}'` - defs="${ARCH} _${ARCH}_ ${arch} PALM_SIZE _MT _WINDOWS" - if test "${SHARED_BUILD}" = "1" ; then - # Static CE builds require static celib as well - defs="${defs} _DLL" - fi - for i in $defs ; do - AC_DEFINE_UNQUOTED($i, 1, [WinCE def ]$i) - done - AC_DEFINE_UNQUOTED(_WIN32_WCE, $CEVERSION, [_WIN32_WCE version]) - AC_DEFINE_UNQUOTED(UNDER_CE, $CEVERSION, [UNDER_CE version]) - CFLAGS_DEBUG="-nologo -Zi -Od" - CFLAGS_OPTIMIZE="-nologo -Ox" - lversion=`echo ${CEVERSION} | sed -e 's/\(.\)\(..\)/\1\.\2/'` - lflags="-MACHINE:${ARCH} -LIBPATH:\"${CELIBPATH}\" -subsystem:windowsce,${lversion} -nologo" - LINKBIN="\"${CEBINROOT}/link.exe\"" - AC_SUBST(CELIB_DIR) else RC="rc" - lflags="-nologo" + lflags="${lflags} -nologo" LINKBIN="link" CFLAGS_DEBUG="-nologo -Z7 -Od -W3 -WX ${runtime}d" CFLAGS_OPTIMIZE="-nologo -O2 -W2 ${runtime}" @@ -1294,25 +1222,32 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_CACHE_CHECK(for cross-compile version of gcc, ac_cv_cross, - AC_TRY_COMPILE([ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #ifdef _WIN32 #error cross-compiler #endif - ], [], - ac_cv_cross=yes, - ac_cv_cross=no) + ]], [[]])], + [ac_cv_cross=yes], + [ac_cv_cross=no]) ) if test "$ac_cv_cross" = "yes"; then case "$do64bit" in amd64|x64|yes) - CC="x86_64-w64-mingw32-gcc" + CC="x86_64-w64-mingw32-${CC}" LD="x86_64-w64-mingw32-ld" AR="x86_64-w64-mingw32-ar" RANLIB="x86_64-w64-mingw32-ranlib" RC="x86_64-w64-mingw32-windres" ;; + arm64|aarch64) + CC="aarch64-w64-mingw32-clang" + LD="aarch64-w64-mingw32-ld" + AR="aarch64-w64-mingw32-ar" + RANLIB="aarch64-w64-mingw32-ranlib" + RC="aarch64-w64-mingw32-windres" + ;; *) - CC="i686-w64-mingw32-gcc" + CC="i686-w64-mingw32-${CC}" LD="i686-w64-mingw32-ld" AR="i686-w64-mingw32-ar" RANLIB="i686-w64-mingw32-ranlib" @@ -1334,13 +1269,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ # This essentially turns it all on. LDFLAGS_DEBUG="-debug -debugtype:cv" LDFLAGS_OPTIMIZE="-release" - if test "$doWince" != "no" ; then - LDFLAGS_CONSOLE="-link ${lflags}" - LDFLAGS_WINDOW=${LDFLAGS_CONSOLE} - else - LDFLAGS_CONSOLE="-link -subsystem:console ${lflags}" - LDFLAGS_WINDOW="-link -subsystem:windows ${lflags}" - fi + LDFLAGS_CONSOLE="-link -subsystem:console ${lflags}" + LDFLAGS_WINDOW="-link -subsystem:windows ${lflags}" fi SHLIB_SUFFIX=".dll" @@ -1349,7 +1279,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ TCL_LIB_VERSIONS_OK=nodots ;; AIX-*) - AS_IF([test "${TCL_THREADS}" = "1" -a "$GCC" != "yes"], [ + AS_IF([test "$GCC" != "yes"], [ # AIX requires the _r compiler when gcc isn't being used case "${CC}" in *_r|*_r\ *) @@ -1386,11 +1316,11 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ # AIX-5 uses ELF style dynamic libraries on IA-64, but not PPC SHLIB_LD="/usr/ccs/bin/ld -G -z text" AS_IF([test "$GCC" = yes], [ - CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' ], [ - CC_SEARCH_FLAGS='-R${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='"-R${LIB_RUNTIME_DIR}"' ]) - LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' + LD_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' ], [ AS_IF([test "$GCC" = yes], [ SHLIB_LD='${CC} -shared -Wl,-bexpall' @@ -1399,7 +1329,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ LDFLAGS="$LDFLAGS -brtl" ]) SHLIB_LD="${SHLIB_LD} ${SHLIB_LD_FLAGS}" - CC_SEARCH_FLAGS='-L${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='"-L${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ]) ;; @@ -1415,6 +1345,13 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ #----------------------------------------------------------- AC_CHECK_LIB(bind, inet_ntoa, [LIBS="$LIBS -lbind -lsocket"]) ;; + BSD/OS-2.1*|BSD/OS-3*) + SHLIB_CFLAGS="" + SHLIB_LD="shlicc -r" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; BSD/OS-4.*) SHLIB_CFLAGS="-export-dynamic -fPIC" SHLIB_LD='${CC} -shared' @@ -1427,16 +1364,25 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ SHLIB_CFLAGS="" SHLIB_LD='${CC} -shared' SHLIB_SUFFIX=".dll" + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} -Wl,--out-implib,\$[@].a" EXEEXT=".exe" do64bit_ok=yes CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; + dgux*) + SHLIB_CFLAGS="-K PIC" + SHLIB_LD='${CC} -G' + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; Haiku*) LDFLAGS="$LDFLAGS -Wl,--export-dynamic" SHLIB_CFLAGS="-fPIC" SHLIB_SUFFIX=".so" - SHLIB_LD='${CC} -shared ${CFLAGS} ${LDFLAGS}' + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' AC_CHECK_LIB(network, inet_ntoa, [LIBS="$LIBS -lnetwork"]) ;; HP-UX-*.11.*) @@ -1448,18 +1394,16 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AS_IF([test "`uname -m`" = ia64], [ SHLIB_SUFFIX=".so" - # Use newer C++ library for C++ extensions - #if test "$GCC" != "yes" ; then - # CPPFLAGS="-AA" - #fi ], [ SHLIB_SUFFIX=".sl" ]) AC_CHECK_LIB(dld, shl_load, tcl_ok=yes, tcl_ok=no) AS_IF([test "$tcl_ok" = yes], [ + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" LDFLAGS="$LDFLAGS -Wl,-E" - CC_SEARCH_FLAGS='-Wl,+s,+b,${LIB_RUNTIME_DIR}:.' - LD_SEARCH_FLAGS='+s +b ${LIB_RUNTIME_DIR}:.' + CC_SEARCH_FLAGS='"-Wl,+s,+b,${LIB_RUNTIME_DIR}:."' + LD_SEARCH_FLAGS='+s +b "${LIB_RUNTIME_DIR}:."' LD_LIBRARY_PATH_VAR="SHLIB_PATH" ]) AS_IF([test "$GCC" = yes], [ @@ -1467,10 +1411,6 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ], [ CFLAGS="$CFLAGS -z" - # Users may want PA-RISC 1.1/2.0 portable code - needs HP cc - #CFLAGS="$CFLAGS +DAportable" - SHLIB_CFLAGS="+z" - SHLIB_LD="ld -b" ]) # Check to enable 64-bit flags for compiler/linker @@ -1482,7 +1422,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ do64bit_ok=yes SHLIB_LD='${CC} -shared' AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}']) + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ;; *) @@ -1495,13 +1435,34 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ LDFLAGS_ARCH="+DD64" ]) ]) ;; + HP-UX-*.08.*|HP-UX-*.09.*|HP-UX-*.10.*) + SHLIB_SUFFIX=".sl" + AC_CHECK_LIB(dld, shl_load, tcl_ok=yes, tcl_ok=no) + AS_IF([test "$tcl_ok" = yes], [ + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" + SHLIB_LD_LIBS="" + LDFLAGS="$LDFLAGS -Wl,-E" + CC_SEARCH_FLAGS='"-Wl,+s,+b,${LIB_RUNTIME_DIR}:."' + LD_SEARCH_FLAGS='+s +b "${LIB_RUNTIME_DIR}:."' + LD_LIBRARY_PATH_VAR="SHLIB_PATH" + ]) ;; + IRIX-5.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -shared -rdata_shared" + SHLIB_SUFFIX=".so" + AC_LIBOBJ(mkstemp) + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"']) + ;; IRIX-6.*) SHLIB_CFLAGS="" SHLIB_LD="ld -n32 -shared -rdata_shared" SHLIB_SUFFIX=".so" AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}' - LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"']) AS_IF([test "$GCC" = yes], [ CFLAGS="$CFLAGS -mabi=n32" LDFLAGS="$LDFLAGS -mabi=n32" @@ -1523,8 +1484,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ SHLIB_LD="ld -n32 -shared -rdata_shared" SHLIB_SUFFIX=".so" AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}' - LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"']) # Check to enable 64-bit flags for compiler/linker @@ -1539,7 +1500,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ ]) ]) ;; - Linux*|GNU*|NetBSD-Debian) + Linux*|GNU*|NetBSD-Debian|DragonFly-*|FreeBSD-*) SHLIB_CFLAGS="-fPIC" SHLIB_SUFFIX=".so" @@ -1547,17 +1508,29 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ CFLAGS_OPTIMIZE="-O2 -fomit-frame-pointer" # TEA specific: use LDFLAGS_DEFAULT instead of LDFLAGS - SHLIB_LD='${CC} -shared ${CFLAGS} ${LDFLAGS_DEFAULT}' + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS_DEFAULT} -shared' LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + + case $system in + DragonFly-*|FreeBSD-*) + AS_IF([test "${TCL_THREADS}" = "1"], [ + # The -pthread needs to go in the LDFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LDFLAGS="$LDFLAGS $PTHREAD_LIBS"]) + ;; + esac + AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}']) + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} AS_IF([test "`uname -m`" = "alpha"], [CFLAGS="$CFLAGS -mieee"]) AS_IF([test $do64bit = yes], [ AC_CACHE_CHECK([if compiler accepts -m64 flag], tcl_cv_cc_m64, [ hold_cflags=$CFLAGS CFLAGS="$CFLAGS -m64" - AC_TRY_LINK(,, tcl_cv_cc_m64=yes, tcl_cv_cc_m64=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], + [tcl_cv_cc_m64=yes],[tcl_cv_cc_m64=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_m64 = yes], [ CFLAGS="$CFLAGS -m64" @@ -1580,42 +1553,31 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ SHLIB_LD='${CC} -shared' LD_FLAGS="-Wl,--export-dynamic" AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}' - LD_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}']) + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) ;; OpenBSD-*) arch=`arch -s` case "$arch" in - vax) - SHLIB_SUFFIX="" - SHARED_LIB_SUFFIX="" - LDFLAGS="" - ;; - *) + alpha|sparc64) SHLIB_CFLAGS="-fPIC" - SHLIB_LD='${CC} -shared ${SHLIB_CFLAGS}' - SHLIB_SUFFIX=".so" - AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}']) - LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} - SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so.${SHLIB_VERSION}' - LDFLAGS="-Wl,-export-dynamic" - ;; - esac - case "$arch" in - vax) - CFLAGS_OPTIMIZE="-O1" ;; *) - CFLAGS_OPTIMIZE="-O2" + SHLIB_CFLAGS="-fpic" ;; esac - AS_IF([test "${TCL_THREADS}" = "1"], [ - # On OpenBSD: Compile with -pthread - # Don't link with -lpthread - LIBS=`echo $LIBS | sed s/-lpthread//` - CFLAGS="$CFLAGS -pthread" - ]) + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' + SHLIB_SUFFIX=".so" + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so${SHLIB_VERSION}' + LDFLAGS="$LDFLAGS -Wl,-export-dynamic" + CFLAGS_OPTIMIZE="-O2" + # On OpenBSD: Compile with -pthread + # Don't link with -lpthread + LIBS=`echo $LIBS | sed s/-lpthread//` + CFLAGS="$CFLAGS -pthread" # OpenBSD doesn't do version numbers with dots. UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' TCL_LIB_VERSIONS_OK=nodots @@ -1623,44 +1585,16 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ NetBSD-*) # NetBSD has ELF and can use 'cc -shared' to build shared libs SHLIB_CFLAGS="-fPIC" - SHLIB_LD='${CC} -shared ${SHLIB_CFLAGS}' + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' SHLIB_SUFFIX=".so" LDFLAGS="$LDFLAGS -export-dynamic" AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}']) + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} - AS_IF([test "${TCL_THREADS}" = "1"], [ - # The -pthread needs to go in the CFLAGS, not LIBS - LIBS=`echo $LIBS | sed s/-pthread//` - CFLAGS="$CFLAGS -pthread" - LDFLAGS="$LDFLAGS -pthread" - ]) - ;; - FreeBSD-*) - # This configuration from FreeBSD Ports. - SHLIB_CFLAGS="-fPIC" - SHLIB_LD="${CC} -shared" - TCL_SHLIB_LD_EXTRAS="-Wl,-soname=\$[@]" - TK_SHLIB_LD_EXTRAS="-Wl,-soname,\$[@]" - SHLIB_SUFFIX=".so" - LDFLAGS="" - AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}' - LD_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}']) - AS_IF([test "${TCL_THREADS}" = "1"], [ - # The -pthread needs to go in the LDFLAGS, not LIBS - LIBS=`echo $LIBS | sed s/-pthread//` - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - LDFLAGS="$LDFLAGS $PTHREAD_LIBS"]) - case $system in - FreeBSD-3.*) - # Version numbers are dot-stripped by system policy. - TCL_TRIM_DOTS=`echo ${VERSION} | tr -d .` - UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' - SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so' - TCL_LIB_VERSIONS_OK=nodots - ;; - esac + # The -pthread needs to go in the CFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" ;; Darwin-*) CFLAGS_OPTIMIZE="-Os" @@ -1681,8 +1615,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ tcl_cv_cc_arch_ppc64, [ hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" - AC_TRY_LINK(,, tcl_cv_cc_arch_ppc64=yes, - tcl_cv_cc_arch_ppc64=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], + [tcl_cv_cc_arch_ppc64=yes],[tcl_cv_cc_arch_ppc64=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_arch_ppc64 = yes], [ CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" @@ -1693,8 +1627,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ tcl_cv_cc_arch_x86_64, [ hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch x86_64" - AC_TRY_LINK(,, tcl_cv_cc_arch_x86_64=yes, - tcl_cv_cc_arch_x86_64=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], + [tcl_cv_cc_arch_x86_64=yes],[tcl_cv_cc_arch_x86_64=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_arch_x86_64 = yes], [ CFLAGS="$CFLAGS -arch x86_64" @@ -1714,7 +1648,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_CACHE_CHECK([if ld accepts -single_module flag], tcl_cv_ld_single_module, [ hold_ldflags=$LDFLAGS LDFLAGS="$LDFLAGS -dynamiclib -Wl,-single_module" - AC_TRY_LINK(, [int i;], tcl_cv_ld_single_module=yes, tcl_cv_ld_single_module=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], + [tcl_cv_ld_single_module=yes],[tcl_cv_ld_single_module=no]) LDFLAGS=$hold_ldflags]) AS_IF([test $tcl_cv_ld_single_module = yes], [ SHLIB_LD="${SHLIB_LD} -Wl,-single_module" @@ -1723,17 +1658,13 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ vers=`echo ${PACKAGE_VERSION} | sed -e 's/^\([[0-9]]\{1,5\}\)\(\(\.[[0-9]]\{1,3\}\)\{0,2\}\).*$/\1\2/p' -e d` SHLIB_LD="${SHLIB_LD} -current_version ${vers:-0} -compatibility_version ${vers:-0}" SHLIB_SUFFIX=".dylib" - # Don't use -prebind when building for Mac OS X 10.4 or later only: - AS_IF([test "`echo "${MACOSX_DEPLOYMENT_TARGET}" | awk -F '10\\.' '{print int([$]2)}'`" -lt 4 -a \ - "`echo "${CPPFLAGS}" | awk -F '-mmacosx-version-min=10\\.' '{print int([$]2)}'`" -lt 4], [ - LDFLAGS="$LDFLAGS -prebind"]) LDFLAGS="$LDFLAGS -headerpad_max_install_names" AC_CACHE_CHECK([if ld accepts -search_paths_first flag], tcl_cv_ld_search_paths_first, [ hold_ldflags=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-search_paths_first" - AC_TRY_LINK(, [int i;], tcl_cv_ld_search_paths_first=yes, - tcl_cv_ld_search_paths_first=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], + [tcl_cv_ld_search_paths_first=yes],[tcl_cv_ld_search_paths_first=no]) LDFLAGS=$hold_ldflags]) AS_IF([test $tcl_cv_ld_search_paths_first = yes], [ LDFLAGS="$LDFLAGS -Wl,-search_paths_first" @@ -1756,8 +1687,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ done CPPFLAGS="$CPPFLAGS -I/usr/X11R6/include" LDFLAGS="$LDFLAGS -L/usr/X11R6/lib -lX11" - AC_TRY_LINK([#include ], [XrmInitialize();], - tcl_cv_lib_x11_64=yes, tcl_cv_lib_x11_64=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[XrmInitialize();]])], + [tcl_cv_lib_x11_64=yes],[tcl_cv_lib_x11_64=no]) for v in CFLAGS CPPFLAGS LDFLAGS; do eval $v'="$hold_'$v'"' done]) @@ -1769,8 +1700,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ done CPPFLAGS="$CPPFLAGS -DUSE_TCL_STUBS=1 -DUSE_TK_STUBS=1 ${TCL_INCLUDES} ${TK_INCLUDES}" LDFLAGS="$LDFLAGS ${TCL_STUB_LIB_SPEC} ${TK_STUB_LIB_SPEC}" - AC_TRY_LINK([#include ], [Tk_InitStubs(NULL, "", 0);], - tcl_cv_lib_tk_64=yes, tcl_cv_lib_tk_64=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[Tk_InitStubs(NULL, "", 0);]])], + [tcl_cv_lib_tk_64=yes],[tcl_cv_lib_tk_64=no]) for v in CFLAGS CPPFLAGS LDFLAGS; do eval $v'="$hold_'$v'"' done]) @@ -1799,21 +1730,19 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ ]) SHLIB_SUFFIX=".so" AS_IF([test $doRpath = yes], [ - CC_SEARCH_FLAGS='-Wl,-rpath,${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) AS_IF([test "$GCC" = yes], [CFLAGS="$CFLAGS -mieee"], [ CFLAGS="$CFLAGS -DHAVE_TZSET -std1 -ieee"]) # see pthread_intro(3) for pthread support on osf1, k.furukawa - AS_IF([test "${TCL_THREADS}" = 1], [ - CFLAGS="$CFLAGS -DHAVE_PTHREAD_ATTR_SETSTACKSIZE" - CFLAGS="$CFLAGS -DTCL_THREAD_STACK_MIN=PTHREAD_STACK_MIN*64" - LIBS=`echo $LIBS | sed s/-lpthreads//` - AS_IF([test "$GCC" = yes], [ - LIBS="$LIBS -lpthread -lmach -lexc" - ], [ - CFLAGS="$CFLAGS -pthread" - LDFLAGS="$LDFLAGS -pthread" - ]) + CFLAGS="$CFLAGS -DHAVE_PTHREAD_ATTR_SETSTACKSIZE" + CFLAGS="$CFLAGS -DTCL_THREAD_STACK_MIN=PTHREAD_STACK_MIN*64" + LIBS=`echo $LIBS | sed s/-lpthreads//` + AS_IF([test "$GCC" = yes], [ + LIBS="$LIBS -lpthread -lmach -lexc" + ], [ + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" ]) ;; QNX-6*) @@ -1854,11 +1783,11 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ SHLIB_SUFFIX=".so" AS_IF([test "$GCC" = yes], [ SHLIB_LD='${CC} -shared' - CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ], [ SHLIB_LD="/usr/ccs/bin/ld -G -z text" - CC_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ]) ;; @@ -1924,7 +1853,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ SHLIB_SUFFIX=".so" AS_IF([test "$GCC" = yes], [ SHLIB_LD='${CC} -shared' - CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} AS_IF([test "$do64bit_ok" = yes], [ AS_IF([test "$arch" = "sparcv9 sparc"], [ @@ -1951,8 +1880,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ *) SHLIB_LD='/usr/ccs/bin/ld -G -z text';; esac - CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' - LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' ]) ;; UNIX_SV* | UnixWare-5*) @@ -1965,7 +1894,8 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ AC_CACHE_CHECK([for ld accepts -Bexport flag], tcl_cv_ld_Bexport, [ hold_ldflags=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-Bexport" - AC_TRY_LINK(, [int i;], tcl_cv_ld_Bexport=yes, tcl_cv_ld_Bexport=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], + [tcl_cv_ld_Bexport=yes],[tcl_cv_ld_Bexport=no]) LDFLAGS=$hold_ldflags]) AS_IF([test $tcl_cv_ld_Bexport = yes], [ LDFLAGS="$LDFLAGS -Wl,-Bexport" @@ -1997,9 +1927,9 @@ dnl # preprocessing tests use only CPPFLAGS. case $system in AIX-*) ;; BSD/OS*) ;; - CYGWIN_*|MINGW32_*) ;; + CYGWIN_*|MINGW32_*|MINGW64_*|MSYS_*) ;; IRIX*) ;; - NetBSD-*|FreeBSD-*|OpenBSD-*) ;; + NetBSD-*|DragonFly-*|FreeBSD-*|OpenBSD-*) ;; Darwin-*) ;; SCO_SV-3.2*) ;; windows) ;; @@ -2021,7 +1951,7 @@ dnl # preprocessing tests use only CPPFLAGS. if test "${GCC}" = "yes" -a ${SHLIB_SUFFIX} = ".dll"; then AC_CACHE_CHECK(for SEH support in compiler, tcl_cv_seh, - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ #define WIN32_LEAN_AND_MEAN #include #undef WIN32_LEAN_AND_MEAN @@ -2036,10 +1966,10 @@ dnl # preprocessing tests use only CPPFLAGS. } return 1; } - ], - tcl_cv_seh=yes, - tcl_cv_seh=no, - tcl_cv_seh=no) + ]])], + [tcl_cv_seh=yes], + [tcl_cv_seh=no], + [tcl_cv_seh=no]) ) if test "$tcl_cv_seh" = "no" ; then AC_DEFINE(HAVE_NO_SEH, 1, @@ -2054,15 +1984,15 @@ dnl # preprocessing tests use only CPPFLAGS. # AC_CACHE_CHECK(for EXCEPTION_DISPOSITION support in include files, tcl_cv_eh_disposition, - AC_TRY_COMPILE([ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ # define WIN32_LEAN_AND_MEAN # include # undef WIN32_LEAN_AND_MEAN - ],[ + ]], [[ EXCEPTION_DISPOSITION x; - ], - tcl_cv_eh_disposition=yes, - tcl_cv_eh_disposition=no) + ]])], + [tcl_cv_eh_disposition=yes], + [tcl_cv_eh_disposition=no]) ) if test "$tcl_cv_eh_disposition" = "no" ; then AC_DEFINE(EXCEPTION_DISPOSITION, int, @@ -2075,18 +2005,18 @@ dnl # preprocessing tests use only CPPFLAGS. AC_CACHE_CHECK(for winnt.h that ignores VOID define, tcl_cv_winnt_ignore_void, - AC_TRY_COMPILE([ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define VOID void #define WIN32_LEAN_AND_MEAN #include #undef WIN32_LEAN_AND_MEAN - ], [ + ]], [[ CHAR c; SHORT s; LONG l; - ], - tcl_cv_winnt_ignore_void=yes, - tcl_cv_winnt_ignore_void=no) + ]])], + [tcl_cv_winnt_ignore_void=yes], + [tcl_cv_winnt_ignore_void=no]) ) if test "$tcl_cv_winnt_ignore_void" = "yes" ; then AC_DEFINE(HAVE_WINNT_IGNORE_VOID, 1, @@ -2100,22 +2030,25 @@ dnl # preprocessing tests use only CPPFLAGS. AC_CACHE_CHECK(for cast to union support, tcl_cv_cast_to_union, - AC_TRY_COMPILE([], - [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ union foo { int i; double d; }; union foo f = (union foo) (int) 0; - ], - tcl_cv_cast_to_union=yes, - tcl_cv_cast_to_union=no) + ]])], + [tcl_cv_cast_to_union=yes], + [tcl_cv_cast_to_union=no]) ) if test "$tcl_cv_cast_to_union" = "yes"; then AC_DEFINE(HAVE_CAST_TO_UNION, 1, [Defined when compiler supports casting to union type.]) fi + AC_CHECK_HEADER(stdbool.h, [AC_DEFINE(HAVE_STDBOOL_H, 1, [Do we have ?])],) + AC_SUBST(CFLAGS_DEBUG) AC_SUBST(CFLAGS_OPTIMIZE) AC_SUBST(CFLAGS_WARNING) + AC_SUBST(LDFLAGS_DEBUG) + AC_SUBST(LDFLAGS_OPTIMIZE) AC_SUBST(STLIB_LD) AC_SUBST(SHLIB_LD) @@ -2155,7 +2088,7 @@ dnl # preprocessing tests use only CPPFLAGS. AC_DEFUN([TEA_SERIAL_PORT], [ AC_CHECK_HEADERS(sys/modem.h) AC_CACHE_CHECK([termios vs. termio vs. sgtty], tcl_cv_api_serial, [ - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include int main() { @@ -2166,9 +2099,9 @@ int main() { return 0; } return 1; -}], tcl_cv_api_serial=termios, tcl_cv_api_serial=no, tcl_cv_api_serial=no) +}]])],[tcl_cv_api_serial=termios],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) if test $tcl_cv_api_serial = no ; then - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include int main() { @@ -2178,10 +2111,10 @@ int main() { return 0; } return 1; -}], tcl_cv_api_serial=termio, tcl_cv_api_serial=no, tcl_cv_api_serial=no) +}]])],[tcl_cv_api_serial=termio],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) fi if test $tcl_cv_api_serial = no ; then - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include int main() { @@ -2192,10 +2125,10 @@ int main() { return 0; } return 1; -}], tcl_cv_api_serial=sgtty, tcl_cv_api_serial=no, tcl_cv_api_serial=no) +}]])],[tcl_cv_api_serial=sgtty],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) fi if test $tcl_cv_api_serial = no ; then - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include @@ -2208,10 +2141,10 @@ int main() { return 0; } return 1; -}], tcl_cv_api_serial=termios, tcl_cv_api_serial=no, tcl_cv_api_serial=no) +}]])],[tcl_cv_api_serial=termios],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) fi if test $tcl_cv_api_serial = no; then - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include @@ -2223,10 +2156,10 @@ int main() { return 0; } return 1; - }], tcl_cv_api_serial=termio, tcl_cv_api_serial=no, tcl_cv_api_serial=no) + }]])],[tcl_cv_api_serial=termio],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) fi if test $tcl_cv_api_serial = no; then - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include @@ -2239,7 +2172,7 @@ int main() { return 0; } return 1; -}], tcl_cv_api_serial=sgtty, tcl_cv_api_serial=none, tcl_cv_api_serial=none) +}]])],[tcl_cv_api_serial=sgtty],[tcl_cv_api_serial=none],[tcl_cv_api_serial=none]) fi]) case $tcl_cv_api_serial in termios) AC_DEFINE(USE_TERMIOS, 1, [Use the termios API for serial lines]);; @@ -2248,97 +2181,6 @@ int main() { esac ]) -#-------------------------------------------------------------------- -# TEA_MISSING_POSIX_HEADERS -# -# Supply substitutes for missing POSIX header files. Special -# notes: -# - stdlib.h doesn't define strtol, strtoul, or -# strtod in some versions of SunOS -# - some versions of string.h don't declare procedures such -# as strstr -# -# Arguments: -# none -# -# Results: -# -# Defines some of the following vars: -# NO_DIRENT_H -# NO_ERRNO_H -# NO_VALUES_H -# HAVE_LIMITS_H or NO_LIMITS_H -# NO_STDLIB_H -# NO_STRING_H -# NO_SYS_WAIT_H -# NO_DLFCN_H -# HAVE_SYS_PARAM_H -# -# HAVE_STRING_H ? -# -# tkUnixPort.h checks for HAVE_LIMITS_H, so do both HAVE and -# CHECK on limits.h -#-------------------------------------------------------------------- - -AC_DEFUN([TEA_MISSING_POSIX_HEADERS], [ - AC_CACHE_CHECK([dirent.h], tcl_cv_dirent_h, [ - AC_TRY_LINK([#include -#include ], [ -#ifndef _POSIX_SOURCE -# ifdef __Lynx__ - /* - * Generate compilation error to make the test fail: Lynx headers - * are only valid if really in the POSIX environment. - */ - - missing_procedure(); -# endif -#endif -DIR *d; -struct dirent *entryPtr; -char *p; -d = opendir("foobar"); -entryPtr = readdir(d); -p = entryPtr->d_name; -closedir(d); -], tcl_cv_dirent_h=yes, tcl_cv_dirent_h=no)]) - - if test $tcl_cv_dirent_h = no; then - AC_DEFINE(NO_DIRENT_H, 1, [Do we have ?]) - fi - - # TEA specific: - AC_CHECK_HEADER(errno.h, , [AC_DEFINE(NO_ERRNO_H, 1, [Do we have ?])]) - AC_CHECK_HEADER(float.h, , [AC_DEFINE(NO_FLOAT_H, 1, [Do we have ?])]) - AC_CHECK_HEADER(values.h, , [AC_DEFINE(NO_VALUES_H, 1, [Do we have ?])]) - AC_CHECK_HEADER(limits.h, - [AC_DEFINE(HAVE_LIMITS_H, 1, [Do we have ?])], - [AC_DEFINE(NO_LIMITS_H, 1, [Do we have ?])]) - AC_CHECK_HEADER(stdlib.h, tcl_ok=1, tcl_ok=0) - AC_EGREP_HEADER(strtol, stdlib.h, , tcl_ok=0) - AC_EGREP_HEADER(strtoul, stdlib.h, , tcl_ok=0) - AC_EGREP_HEADER(strtod, stdlib.h, , tcl_ok=0) - if test $tcl_ok = 0; then - AC_DEFINE(NO_STDLIB_H, 1, [Do we have ?]) - fi - AC_CHECK_HEADER(string.h, tcl_ok=1, tcl_ok=0) - AC_EGREP_HEADER(strstr, string.h, , tcl_ok=0) - AC_EGREP_HEADER(strerror, string.h, , tcl_ok=0) - - # See also memmove check below for a place where NO_STRING_H can be - # set and why. - - if test $tcl_ok = 0; then - AC_DEFINE(NO_STRING_H, 1, [Do we have ?]) - fi - - AC_CHECK_HEADER(sys/wait.h, , [AC_DEFINE(NO_SYS_WAIT_H, 1, [Do we have ?])]) - AC_CHECK_HEADER(dlfcn.h, , [AC_DEFINE(NO_DLFCN_H, 1, [Do we have ?])]) - - # OS/390 lacks sys/param.h (and doesn't need it, by chance). - AC_HAVE_HEADERS(sys/param.h) -]) - #-------------------------------------------------------------------- # TEA_PATH_X # @@ -2374,7 +2216,7 @@ AC_DEFUN([TEA_PATH_UNIX_X], [ not_really_there="" if test "$no_x" = ""; then if test "$x_includes" = ""; then - AC_TRY_CPP([#include ], , not_really_there="yes") + AC_PREPROC_IFELSE([AC_LANG_SOURCE([[#include ]])],[],[not_really_there="yes"]) else if test ! -r $x_includes/X11/Xlib.h; then not_really_there="yes" @@ -2384,7 +2226,7 @@ AC_DEFUN([TEA_PATH_UNIX_X], [ if test "$no_x" = "yes" -o "$not_really_there" = "yes"; then AC_MSG_CHECKING([for X11 header files]) found_xincludes="no" - AC_TRY_CPP([#include ], found_xincludes="yes", found_xincludes="no") + AC_PREPROC_IFELSE([AC_LANG_SOURCE([[#include ]])],[found_xincludes="yes"],[found_xincludes="no"]) if test "$found_xincludes" = "no"; then dirs="/usr/unsupported/include /usr/local/include /usr/X386/include /usr/X11R6/include /usr/X11R5/include /usr/include/X11R5 /usr/include/X11R4 /usr/openwin/include /usr/X11/include /usr/sww/include" for i in $dirs ; do @@ -2490,6 +2332,7 @@ AC_DEFUN([TEA_BLOCKING_STYLE], [ # HAVE_TM_GMTOFF # HAVE_TM_TZADJ # HAVE_TIMEZONE_VAR +# #-------------------------------------------------------------------- AC_DEFUN([TEA_TIME_HANDLER], [ @@ -2497,18 +2340,20 @@ AC_DEFUN([TEA_TIME_HANDLER], [ AC_HEADER_TIME AC_STRUCT_TIMEZONE - AC_CHECK_FUNCS(gmtime_r localtime_r) + AC_CHECK_FUNCS(gmtime_r localtime_r mktime) AC_CACHE_CHECK([tm_tzadj in struct tm], tcl_cv_member_tm_tzadj, [ - AC_TRY_COMPILE([#include ], [struct tm tm; tm.tm_tzadj;], - tcl_cv_member_tm_tzadj=yes, tcl_cv_member_tm_tzadj=no)]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[struct tm tm; (void)tm.tm_tzadj;]])], + [tcl_cv_member_tm_tzadj=yes], + [tcl_cv_member_tm_tzadj=no])]) if test $tcl_cv_member_tm_tzadj = yes ; then AC_DEFINE(HAVE_TM_TZADJ, 1, [Should we use the tm_tzadj field of struct tm?]) fi AC_CACHE_CHECK([tm_gmtoff in struct tm], tcl_cv_member_tm_gmtoff, [ - AC_TRY_COMPILE([#include ], [struct tm tm; tm.tm_gmtoff;], - tcl_cv_member_tm_gmtoff=yes, tcl_cv_member_tm_gmtoff=no)]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[struct tm tm; (void)tm.tm_gmtoff;]])], + [tcl_cv_member_tm_gmtoff=yes], + [tcl_cv_member_tm_gmtoff=no])]) if test $tcl_cv_member_tm_gmtoff = yes ; then AC_DEFINE(HAVE_TM_GMTOFF, 1, [Should we use the tm_gmtoff field of struct tm?]) fi @@ -2518,11 +2363,12 @@ AC_DEFUN([TEA_TIME_HANDLER], [ # (like convex) have timezone functions, etc. # AC_CACHE_CHECK([long timezone variable], tcl_cv_timezone_long, [ - AC_TRY_COMPILE([#include ], - [extern long timezone; + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +#include ]], + [[extern long timezone; timezone += 1; - exit (0);], - tcl_cv_timezone_long=yes, tcl_cv_timezone_long=no)]) + exit (0);]])], + [tcl_cv_timezone_long=yes], [tcl_cv_timezone_long=no])]) if test $tcl_cv_timezone_long = yes ; then AC_DEFINE(HAVE_TIMEZONE_VAR, 1, [Should we use the global timezone variable?]) else @@ -2530,11 +2376,12 @@ AC_DEFUN([TEA_TIME_HANDLER], [ # On some systems (eg IRIX 6.2), timezone is a time_t and not a long. # AC_CACHE_CHECK([time_t timezone variable], tcl_cv_timezone_time, [ - AC_TRY_COMPILE([#include ], - [extern time_t timezone; + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +#include ]], + [[extern time_t timezone; timezone += 1; - exit (0);], - tcl_cv_timezone_time=yes, tcl_cv_timezone_time=no)]) + exit (0);]])], + [tcl_cv_timezone_time=yes], [tcl_cv_timezone_time=no])]) if test $tcl_cv_timezone_time = yes ; then AC_DEFINE(HAVE_TIMEZONE_VAR, 1, [Should we use the global timezone variable?]) fi @@ -2564,7 +2411,8 @@ AC_DEFUN([TEA_BUGGY_STRTOD], [ AC_CHECK_FUNC(strtod, tcl_strtod=1, tcl_strtod=0) if test "$tcl_strtod" = 1; then AC_CACHE_CHECK([for Solaris2.4/Tru64 strtod bugs], tcl_cv_strtod_buggy,[ - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ + #include extern double strtod(); int main() { char *infString="Inf", *nanString="NaN", *spaceString=" "; @@ -2583,8 +2431,8 @@ AC_DEFUN([TEA_BUGGY_STRTOD], [ exit(1); } exit(0); - }], tcl_cv_strtod_buggy=ok, tcl_cv_strtod_buggy=buggy, - tcl_cv_strtod_buggy=buggy)]) + }]])], [tcl_cv_strtod_buggy=ok], [tcl_cv_strtod_buggy=buggy], + [tcl_cv_strtod_buggy=buggy])]) if test "$tcl_cv_strtod_buggy" = buggy; then AC_LIBOBJ([fixstrtod]) USE_COMPAT=1 @@ -2597,38 +2445,30 @@ AC_DEFUN([TEA_BUGGY_STRTOD], [ # TEA_TCL_LINK_LIBS # # Search for the libraries needed to link the Tcl shell. -# Things like the math library (-lm) and socket stuff (-lsocket vs. -# -lnsl) are dealt with here. +# Things like the math library (-lm), socket stuff (-lsocket vs. +# -lnsl), zlib (-lz) and libtommath (-ltommath) are dealt with here. # # Arguments: -# Requires the following vars to be set in the Makefile: -# DL_LIBS (not in TEA, only needed in core) -# LIBS -# MATH_LIBS +# None. # # Results: # -# Substitutes the following vars: -# TCL_LIBS -# MATH_LIBS -# # Might append to the following vars: # LIBS +# MATH_LIBS # # Might define the following vars: # HAVE_NET_ERRNO_H +# #-------------------------------------------------------------------- AC_DEFUN([TEA_TCL_LINK_LIBS], [ #-------------------------------------------------------------------- # On a few very rare systems, all of the libm.a stuff is # already in libc.a. Set compiler flags accordingly. - # Also, Linux requires the "ieee" library for math to work - # right (and it must appear before "-lm"). #-------------------------------------------------------------------- AC_CHECK_FUNC(sin, MATH_LIBS="", MATH_LIBS="-lm") - AC_CHECK_LIB(ieee, main, [MATH_LIBS="-lieee $MATH_LIBS"]) #-------------------------------------------------------------------- # Interactive UNIX requires -linet instead of -lsocket, plus it @@ -2670,13 +2510,10 @@ AC_DEFUN([TEA_TCL_LINK_LIBS], [ fi AC_CHECK_FUNC(gethostbyname, , [AC_CHECK_LIB(nsl, gethostbyname, [LIBS="$LIBS -lnsl"])]) - - # TEA specific: Don't perform the eval of the libraries here because - # DL_LIBS won't be set until we call TEA_CONFIG_CFLAGS - - TCL_LIBS='${DL_LIBS} ${LIBS} ${MATH_LIBS}' - AC_SUBST(TCL_LIBS) - AC_SUBST(MATH_LIBS) + AC_CHECK_FUNC(mp_log_u32, , [AC_CHECK_LIB(tommath, mp_log_u32, + [LIBS="$LIBS -ltommath"])]) + AC_CHECK_FUNC(deflateSetHeader, , [AC_CHECK_LIB(z, deflateSetHeader, + [LIBS="$LIBS -lz"])]) ]) #-------------------------------------------------------------------- @@ -2694,15 +2531,16 @@ AC_DEFUN([TEA_TCL_LINK_LIBS], [ # _ISOC99_SOURCE # _LARGEFILE64_SOURCE # _LARGEFILE_SOURCE64 +# #-------------------------------------------------------------------- AC_DEFUN([TEA_TCL_EARLY_FLAG],[ AC_CACHE_VAL([tcl_cv_flag_]translit($1,[A-Z],[a-z]), - AC_TRY_COMPILE([$2], $3, [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no, - AC_TRY_COMPILE([[#define ]$1[ 1 -]$2], $3, - [tcl_cv_flag_]translit($1,[A-Z],[a-z])=yes, - [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no))) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$2]], [[$3]])], + [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no,[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[[#define ]$1[ 1 +]$2]], [[$3]])], + [tcl_cv_flag_]translit($1,[A-Z],[a-z])=yes, + [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no)])) if test ["x${tcl_cv_flag_]translit($1,[A-Z],[a-z])[}" = "xyes"] ; then AC_DEFINE($1, 1, [Add the ]$1[ flag when building]) tcl_flags="$tcl_flags $1" @@ -2738,9 +2576,10 @@ AC_DEFUN([TEA_TCL_EARLY_FLAGS],[ # Might define the following vars: # TCL_WIDE_INT_IS_LONG # TCL_WIDE_INT_TYPE -# HAVE_STRUCT_DIRENT64 +# HAVE_STRUCT_DIRENT64, HAVE_DIR64 # HAVE_STRUCT_STAT64 # HAVE_TYPE_OFF64_T +# #-------------------------------------------------------------------- AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ @@ -2748,17 +2587,17 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ AC_CACHE_VAL(tcl_cv_type_64bit,[ tcl_cv_type_64bit=none # See if the compiler knows natively about __int64 - AC_TRY_COMPILE(,[__int64 value = (__int64) 0;], - tcl_type_64bit=__int64, tcl_type_64bit="long long") - # See if we should use long anyway Note that we substitute in the + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[__int64 value = (__int64) 0;]])], + [tcl_type_64bit=__int64],[tcl_type_64bit="long long"]) + # See if we could use long anyway Note that we substitute in the # type that is our current guess for a 64-bit type inside this check # program, so it should be modified only carefully... - AC_TRY_COMPILE(,[switch (0) { - case 1: case (sizeof(]${tcl_type_64bit}[)==sizeof(long)): ; - }],tcl_cv_type_64bit=${tcl_type_64bit})]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[switch (0) { + case 1: case (sizeof(${tcl_type_64bit})==sizeof(long)): ; + }]])],[tcl_cv_type_64bit=${tcl_type_64bit}],[])]) if test "${tcl_cv_type_64bit}" = none ; then - AC_DEFINE(TCL_WIDE_INT_IS_LONG, 1, [Are wide integers to be implemented with C 'long's?]) - AC_MSG_RESULT([using long]) + AC_DEFINE(TCL_WIDE_INT_IS_LONG, 1, [Do 'long' and 'long long' have the same size (64-bit)?]) + AC_MSG_RESULT([yes]) elif test "${tcl_cv_type_64bit}" = "__int64" \ -a "${TEA_PLATFORM}" = "windows" ; then # TEA specific: We actually want to use the default tcl.h checks in @@ -2771,17 +2610,26 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ # Now check for auxiliary declarations AC_CACHE_CHECK([for struct dirent64], tcl_cv_struct_dirent64,[ - AC_TRY_COMPILE([#include -#include ],[struct dirent64 p;], - tcl_cv_struct_dirent64=yes,tcl_cv_struct_dirent64=no)]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +#include ]], [[struct dirent64 p;]])], + [tcl_cv_struct_dirent64=yes],[tcl_cv_struct_dirent64=no])]) if test "x${tcl_cv_struct_dirent64}" = "xyes" ; then AC_DEFINE(HAVE_STRUCT_DIRENT64, 1, [Is 'struct dirent64' in ?]) fi + AC_CACHE_CHECK([for DIR64], tcl_cv_DIR64,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +#include ]], [[struct dirent64 *p; DIR64 d = opendir64("."); + p = readdir64(d); rewinddir64(d); closedir64(d);]])], + [tcl_cv_DIR64=yes], [tcl_cv_DIR64=no])]) + if test "x${tcl_cv_DIR64}" = "xyes" ; then + AC_DEFINE(HAVE_DIR64, 1, [Is 'DIR64' in ?]) + fi + AC_CACHE_CHECK([for struct stat64], tcl_cv_struct_stat64,[ - AC_TRY_COMPILE([#include ],[struct stat64 p; -], - tcl_cv_struct_stat64=yes,tcl_cv_struct_stat64=no)]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[struct stat64 p; +]])], + [tcl_cv_struct_stat64=yes], [tcl_cv_struct_stat64=no])]) if test "x${tcl_cv_struct_stat64}" = "xyes" ; then AC_DEFINE(HAVE_STRUCT_STAT64, 1, [Is 'struct stat64' in ?]) fi @@ -2789,9 +2637,9 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ AC_CHECK_FUNCS(open64 lseek64) AC_MSG_CHECKING([for off64_t]) AC_CACHE_VAL(tcl_cv_type_off64_t,[ - AC_TRY_COMPILE([#include ],[off64_t offset; -], - tcl_cv_type_off64_t=yes,tcl_cv_type_off64_t=no)]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[off64_t offset; +]])], + [tcl_cv_type_off64_t=yes], [tcl_cv_type_off64_t=no])]) dnl Define HAVE_TYPE_OFF64_T only when the off64_t type and the dnl functions lseek64 and open64 are defined. if test "x${tcl_cv_type_off64_t}" = "xyes" && \ @@ -2840,23 +2688,14 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ #------------------------------------------------------------------------ AC_DEFUN([TEA_INIT], [ - # TEA extensions pass this us the version of TEA they think they - # are compatible with. - TEA_VERSION="3.9" + TEA_VERSION="3.13" - AC_MSG_CHECKING([for correct TEA configuration]) + AC_MSG_CHECKING([TEA configuration]) if test x"${PACKAGE_NAME}" = x ; then AC_MSG_ERROR([ -The PACKAGE_NAME variable must be defined by your TEA configure.in]) - fi - if test x"$1" = x ; then - AC_MSG_ERROR([ -TEA version not specified.]) - elif test "$1" != "${TEA_VERSION}" ; then - AC_MSG_RESULT([warning: requested TEA version "$1", have "${TEA_VERSION}"]) - else - AC_MSG_RESULT([ok (TEA ${TEA_VERSION})]) +The PACKAGE_NAME variable must be defined by your TEA configure.ac]) fi + AC_MSG_RESULT([ok (TEA ${TEA_VERSION})]) # If the user did not set CFLAGS, set it now to keep macros # like AC_PROG_CC and AC_TRY_COMPILE from adding "-g -O2". @@ -2865,15 +2704,14 @@ TEA version not specified.]) fi case "`uname -s`" in - *win32*|*WIN32*|*MINGW32_*) - AC_CHECK_PROG(CYGPATH, cygpath, cygpath -w, echo) + *win32*|*WIN32*|*MINGW32_*|*MINGW64_*|*MSYS_*) + AC_CHECK_PROG(CYGPATH, cygpath, cygpath -m, echo) EXEEXT=".exe" TEA_PLATFORM="windows" ;; *CYGWIN_*) - CYGPATH=echo EXEEXT=".exe" - # TEA_PLATFORM is determined later in LOAD_TCLCONFIG + # CYGPATH and TEA_PLATFORM are determined later in LOAD_TCLCONFIG ;; *) CYGPATH=echo @@ -2907,6 +2745,8 @@ TEA version not specified.]) # This package name must be replaced statically for AC_SUBST to work AC_SUBST(PKG_LIB_FILE) + AC_SUBST(PKG_LIB_FILE8) + AC_SUBST(PKG_LIB_FILE9) # Substitute STUB_LIB_FILE in case package creates a stub library too. AC_SUBST(PKG_STUB_LIB_FILE) @@ -2919,6 +2759,9 @@ TEA version not specified.]) AC_SUBST(PKG_INCLUDES) AC_SUBST(PKG_LIBS) AC_SUBST(PKG_CFLAGS) + + # Configure the installer. + TEA_INSTALLER ]) #------------------------------------------------------------------------ @@ -3111,7 +2954,7 @@ AC_DEFUN([TEA_ADD_LIBS], [ for i in $vars; do if test "${TEA_PLATFORM}" = "windows" -a "$GCC" = "yes" ; then # Convert foo.lib to -lfoo for GCC. No-op if not *.lib - i=`echo "$i" | sed -e 's/^\([[^-]].*\)\.lib[$]/-l\1/i'` + i=`echo "$i" | sed -e 's/^\([[^-]].*\)\.[[lL]][[iI]][[bB]][$]/-l\1/'` fi PKG_LIBS="$PKG_LIBS $i" done @@ -3194,7 +3037,7 @@ AC_DEFUN([TEA_PREFIX], [ # TEA_SETUP_COMPILER_CC -- # # Do compiler checks the way we want. This is just a replacement -# for AC_PROG_CC in TEA configure.in files to make them cleaner. +# for AC_PROG_CC in TEA configure.ac files to make them cleaner. # # Arguments: # none @@ -3210,15 +3053,6 @@ AC_DEFUN([TEA_SETUP_COMPILER_CC], [ AC_PROG_CC AC_PROG_CPP - INSTALL="\$(SHELL) \$(srcdir)/tclconfig/install-sh -c" - AC_SUBST(INSTALL) - INSTALL_DATA="\${INSTALL} -m 644" - AC_SUBST(INSTALL_DATA) - INSTALL_PROGRAM="\${INSTALL}" - AC_SUBST(INSTALL_PROGRAM) - INSTALL_SCRIPT="\${INSTALL}" - AC_SUBST(INSTALL_SCRIPT) - #-------------------------------------------------------------------- # Checks to see if the make program sets the $MAKE variable. #-------------------------------------------------------------------- @@ -3265,7 +3099,7 @@ AC_DEFUN([TEA_SETUP_COMPILER], [ AC_CACHE_CHECK([if the compiler understands -pipe], tcl_cv_cc_pipe, [ hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -pipe" - AC_TRY_COMPILE(,, tcl_cv_cc_pipe=yes, tcl_cv_cc_pipe=no) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[tcl_cv_cc_pipe=yes],[tcl_cv_cc_pipe=no]) CFLAGS=$hold_cflags]) if test $tcl_cv_cc_pipe = yes; then CFLAGS="$CFLAGS -pipe" @@ -3277,14 +3111,6 @@ AC_DEFUN([TEA_SETUP_COMPILER], [ #-------------------------------------------------------------------- AC_C_BIGENDIAN - if test "${TEA_PLATFORM}" = "unix" ; then - TEA_TCL_LINK_LIBS - TEA_MISSING_POSIX_HEADERS - # Let the user call this, because if it triggers, they will - # need a compat/strtod.c that is correct. Users can also - # use Tcl_GetDouble(FromObj) instead. - #TEA_BUGGY_STRTOD - fi ]) #------------------------------------------------------------------------ @@ -3315,7 +3141,7 @@ AC_DEFUN([TEA_SETUP_COMPILER], [ AC_DEFUN([TEA_MAKE_LIB], [ if test "${TEA_PLATFORM}" = "windows" -a "$GCC" != "yes"; then MAKE_STATIC_LIB="\${STLIB_LD} -out:\[$]@ \$(PKG_OBJECTS)" - MAKE_SHARED_LIB="\${SHLIB_LD} \${SHLIB_LD_LIBS} \${LDFLAGS_DEFAULT} -out:\[$]@ \$(PKG_OBJECTS)" + MAKE_SHARED_LIB="\${SHLIB_LD} \${LDFLAGS} \${LDFLAGS_DEFAULT} -out:\[$]@ \$(PKG_OBJECTS) \${SHLIB_LD_LIBS}" AC_EGREP_CPP([manifest needed], [ #if defined(_MSC_VER) && _MSC_VER >= 1400 print("manifest needed") @@ -3330,7 +3156,7 @@ print("manifest needed") MAKE_STUB_LIB="\${STLIB_LD} -nodefaultlib -out:\[$]@ \$(PKG_STUB_OBJECTS)" else MAKE_STATIC_LIB="\${STLIB_LD} \[$]@ \$(PKG_OBJECTS)" - MAKE_SHARED_LIB="\${SHLIB_LD} -o \[$]@ \$(PKG_OBJECTS) \${SHLIB_LD_LIBS}" + MAKE_SHARED_LIB="\${SHLIB_LD} \${LDFLAGS} \${LDFLAGS_DEFAULT} -o \[$]@ \$(PKG_OBJECTS) \${SHLIB_LD_LIBS}" MAKE_STUB_LIB="\${STLIB_LD} \[$]@ \$(PKG_STUB_OBJECTS)" fi @@ -3346,6 +3172,13 @@ print("manifest needed") # substituted. (@@@ Might not be necessary anymore) #-------------------------------------------------------------------- + PACKAGE_LIB_PREFIX8="${PACKAGE_LIB_PREFIX}" + PACKAGE_LIB_PREFIX9="${PACKAGE_LIB_PREFIX}tcl9" + if test "${TCL_MAJOR_VERSION}" -gt 8 ; then + PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX9}" + else + PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX8}" + fi if test "${TEA_PLATFORM}" = "windows" ; then if test "${SHARED_BUILD}" = "1" ; then # We force the unresolved linking of symbols that are really in @@ -3357,15 +3190,19 @@ print("manifest needed") if test "$GCC" = "yes"; then SHLIB_LD_LIBS="${SHLIB_LD_LIBS} -static-libgcc" fi - eval eval "PKG_LIB_FILE=${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE8=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" else - eval eval "PKG_LIB_FILE=${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" if test "$GCC" = "yes"; then - PKG_LIB_FILE=lib${PKG_LIB_FILE} + PACKAGE_LIB_PREFIX=lib${PACKAGE_LIB_PREFIX} fi + eval eval "PKG_LIB_FILE8=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" fi # Some packages build their own stubs libraries - eval eval "PKG_STUB_LIB_FILE=${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_STUB_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" if test "$GCC" = "yes"; then PKG_STUB_LIB_FILE=lib${PKG_STUB_LIB_FILE} fi @@ -3379,13 +3216,17 @@ print("manifest needed") if test x"${TK_BIN_DIR}" != x ; then SHLIB_LD_LIBS="${SHLIB_LD_LIBS} ${TK_STUB_LIB_SPEC}" fi - eval eval "PKG_LIB_FILE=lib${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE8=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" RANLIB=: else - eval eval "PKG_LIB_FILE=lib${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" fi # Some packages build their own stubs libraries - eval eval "PKG_STUB_LIB_FILE=lib${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_STUB_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" fi # These are escaped so that only CFLAGS is picked up at configure time. @@ -3930,6 +3771,7 @@ AC_DEFUN([TEA_PATH_CONFIG], [ `ls -d ${prefix}/lib 2>/dev/null` \ `ls -d /usr/local/lib 2>/dev/null` \ `ls -d /usr/contrib/lib 2>/dev/null` \ + `ls -d /usr/pkg/lib 2>/dev/null` \ `ls -d /usr/lib 2>/dev/null` \ `ls -d /usr/lib64 2>/dev/null` \ ; do @@ -4071,18 +3913,18 @@ AC_DEFUN([TEA_EXPORT_CONFIG], [ # pkglibdir must be a fully qualified path and (not ${exec_prefix}/lib) eval pkglibdir="[$]{libdir}/$1${PACKAGE_VERSION}" if test "${TCL_LIB_VERSIONS_OK}" = "ok"; then - eval $1_LIB_FLAG="-l$1${PACKAGE_VERSION}${DBGX}" - eval $1_STUB_LIB_FLAG="-l$1stub${PACKAGE_VERSION}${DBGX}" + eval $1_LIB_FLAG="-l$1${PACKAGE_VERSION}" + eval $1_STUB_LIB_FLAG="-l$1stub${PACKAGE_VERSION}" else - eval $1_LIB_FLAG="-l$1`echo ${PACKAGE_VERSION} | tr -d .`${DBGX}" - eval $1_STUB_LIB_FLAG="-l$1stub`echo ${PACKAGE_VERSION} | tr -d .`${DBGX}" + eval $1_LIB_FLAG="-l$1`echo ${PACKAGE_VERSION} | tr -d .`" + eval $1_STUB_LIB_FLAG="-l$1stub`echo ${PACKAGE_VERSION} | tr -d .`" fi - $1_BUILD_LIB_SPEC="-L`pwd` ${$1_LIB_FLAG}" - $1_LIB_SPEC="-L${pkglibdir} ${$1_LIB_FLAG}" - $1_BUILD_STUB_LIB_SPEC="-L`pwd` [$]{$1_STUB_LIB_FLAG}" - $1_STUB_LIB_SPEC="-L${pkglibdir} [$]{$1_STUB_LIB_FLAG}" - $1_BUILD_STUB_LIB_PATH="`pwd`/[$]{PKG_STUB_LIB_FILE}" - $1_STUB_LIB_PATH="${pkglibdir}/[$]{PKG_STUB_LIB_FILE}" + $1_BUILD_LIB_SPEC="-L`$CYGPATH $(pwd)` ${$1_LIB_FLAG}" + $1_LIB_SPEC="-L`$CYGPATH ${pkglibdir}` ${$1_LIB_FLAG}" + $1_BUILD_STUB_LIB_SPEC="-L`$CYGPATH $(pwd)` [$]{$1_STUB_LIB_FLAG}" + $1_STUB_LIB_SPEC="-L`$CYGPATH ${pkglibdir}` [$]{$1_STUB_LIB_FLAG}" + $1_BUILD_STUB_LIB_PATH="`$CYGPATH $(pwd)`/[$]{PKG_STUB_LIB_FILE}" + $1_STUB_LIB_PATH="`$CYGPATH ${pkglibdir}`/[$]{PKG_STUB_LIB_FILE}" AC_SUBST($1_BUILD_LIB_SPEC) AC_SUBST($1_LIB_SPEC) @@ -4098,71 +3940,128 @@ AC_DEFUN([TEA_EXPORT_CONFIG], [ #------------------------------------------------------------------------ -# TEA_PATH_CELIB -- +# TEA_INSTALLER -- # -# Locate Keuchel's celib emulation layer for targeting Win/CE +# Configure the installer. # # Arguments: # none # # Results: -# -# Adds the following arguments to configure: -# --with-celib=... -# -# Defines the following vars: -# CELIB_DIR Full path to the directory containing -# the include and platform lib files +# Substitutes the following vars: +# INSTALL +# INSTALL_DATA_DIR +# INSTALL_DATA +# INSTALL_PROGRAM +# INSTALL_SCRIPT +# INSTALL_LIBRARY #------------------------------------------------------------------------ -AC_DEFUN([TEA_PATH_CELIB], [ - # First, look for one uninstalled. - # the alternative search directory is invoked by --with-celib - - if test x"${no_celib}" = x ; then - # we reset no_celib in case something fails here - no_celib=true - AC_ARG_WITH(celib,[ --with-celib=DIR use Windows/CE support library from DIR], with_celibconfig=${withval}) - AC_MSG_CHECKING([for Windows/CE celib directory]) - AC_CACHE_VAL(ac_cv_c_celibconfig,[ - # First check to see if --with-celibconfig was specified. - if test x"${with_celibconfig}" != x ; then - if test -d "${with_celibconfig}/inc" ; then - ac_cv_c_celibconfig=`(cd ${with_celibconfig}; pwd)` - else - AC_MSG_ERROR([${with_celibconfig} directory doesn't contain inc directory]) - fi - fi +AC_DEFUN([TEA_INSTALLER], [ + INSTALL='$(SHELL) $(srcdir)/tclconfig/install-sh -c' + INSTALL_DATA_DIR='${INSTALL} -d -m 755' + INSTALL_DATA='${INSTALL} -m 644' + INSTALL_PROGRAM='${INSTALL} -m 755' + INSTALL_SCRIPT='${INSTALL} -m 755' - # then check for a celib library - if test x"${ac_cv_c_celibconfig}" = x ; then - for i in \ - ../celib-palm-3.0 \ - ../celib \ - ../../celib-palm-3.0 \ - ../../celib \ - `ls -dr ../celib-*3.[[0-9]]* 2>/dev/null` \ - ${srcdir}/../celib-palm-3.0 \ - ${srcdir}/../celib \ - `ls -dr ${srcdir}/../celib-*3.[[0-9]]* 2>/dev/null` \ - ; do - if test -d "$i/inc" ; then - ac_cv_c_celibconfig=`(cd $i; pwd)` - break - fi - done - fi - ]) - if test x"${ac_cv_c_celibconfig}" = x ; then - AC_MSG_ERROR([Cannot find celib support library directory]) - else - no_celib= - CELIB_DIR=${ac_cv_c_celibconfig} - CELIB_DIR=`echo "$CELIB_DIR" | sed -e 's!\\\!/!g'` - AC_MSG_RESULT([found $CELIB_DIR]) - fi - fi + TEA_CONFIG_SYSTEM + case $system in + HP-UX-*) INSTALL_LIBRARY='${INSTALL} -m 755' ;; + *) INSTALL_LIBRARY='${INSTALL} -m 644' ;; + esac + + AC_SUBST(INSTALL) + AC_SUBST(INSTALL_DATA_DIR) + AC_SUBST(INSTALL_DATA) + AC_SUBST(INSTALL_PROGRAM) + AC_SUBST(INSTALL_SCRIPT) + AC_SUBST(INSTALL_LIBRARY) ]) + +### +# Tip 430 - ZipFS Modifications +### +#------------------------------------------------------------------------ +# TEA_ZIPFS_SUPPORT +# Locate a zip encoder installed on the system path, or none. +# +# Arguments: +# none +# +# Results: +# Substitutes the following vars: +# MACHER_PROG +# ZIP_PROG +# ZIP_PROG_OPTIONS +# ZIP_PROG_VFSSEARCH +# ZIP_INSTALL_OBJS +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_ZIPFS_SUPPORT], [ + MACHER_PROG="" + ZIP_PROG="" + ZIP_PROG_OPTIONS="" + ZIP_PROG_VFSSEARCH="" + ZIP_INSTALL_OBJS="" + + AC_MSG_CHECKING([for macher]) + AC_CACHE_VAL(ac_cv_path_macher, [ + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/macher 2> /dev/null` \ + `ls -r $dir/macher 2> /dev/null` ; do + if test x"$ac_cv_path_macher" = x ; then + if test -f "$j" ; then + ac_cv_path_macher=$j + break + fi + fi + done + done + ]) + if test -f "$ac_cv_path_macher" ; then + MACHER_PROG="$ac_cv_path_macher" + AC_MSG_RESULT([$MACHER_PROG]) + AC_MSG_RESULT([Found macher in environment]) + fi + AC_MSG_CHECKING([for zip]) + AC_CACHE_VAL(ac_cv_path_zip, [ + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/zip 2> /dev/null` \ + `ls -r $dir/zip 2> /dev/null` ; do + if test x"$ac_cv_path_zip" = x ; then + if test -f "$j" ; then + ac_cv_path_zip=$j + break + fi + fi + done + done + ]) + if test -f "$ac_cv_path_zip" ; then + ZIP_PROG="$ac_cv_path_zip" + AC_MSG_RESULT([$ZIP_PROG]) + ZIP_PROG_OPTIONS="-rq" + ZIP_PROG_VFSSEARCH="*" + AC_MSG_RESULT([Found INFO Zip in environment]) + # Use standard arguments for zip + else + # It is not an error if an installed version of Zip can't be located. + # We can use the locally distributed minizip instead + ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" + ZIP_PROG_OPTIONS="-o -r" + ZIP_PROG_VFSSEARCH="*" + ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" + AC_MSG_RESULT([No zip found on PATH. Building minizip]) + fi + AC_SUBST(MACHER_PROG) + AC_SUBST(ZIP_PROG) + AC_SUBST(ZIP_PROG_OPTIONS) + AC_SUBST(ZIP_PROG_VFSSEARCH) + AC_SUBST(ZIP_INSTALL_OBJS) +]) + # Local Variables: # mode: autoconf -# End: +# End: \ No newline at end of file diff --git a/autoconf/tea/win/makefile.vc b/autoconf/tea/win/makefile.vc index d92a8428b..da56e811f 100644 --- a/autoconf/tea/win/makefile.vc +++ b/autoconf/tea/win/makefile.vc @@ -251,7 +251,18 @@ INCLUDES = $(SQL_INCLUDES) $(TCL_INCLUDES) -I"$(WINDIR)" \ -I"$(GENERICDIR)" -I"$(ROOT)\.." BASE_CLFAGS = $(cflags) $(cdebug) $(crt) $(INCLUDES) \ -DSQLITE_3_SUFFIX_ONLY=1 -DSQLITE_ENABLE_RTREE=1 \ - -DSQLITE_ENABLE_FTS3=1 -DSQLITE_OMIT_DEPRECATED=1 + -DSQLITE_ENABLE_FTS3=1 -DSQLITE_OMIT_DEPRECATED=1 \ + -DSQLITE_ENABLE_FTS4=1 \ + -DSQLITE_ENABLE_FTS5=1 \ + -DSQLITE_3_SUFFIX_ONLY=1 \ + -DSQLITE_ENABLE_RTREE=1 \ + -DSQLITE_ENABLE_GEOPOLY=1 \ + -DSQLITE_ENABLE_MATH_FUNCTIONS=1 \ + -DSQLITE_ENABLE_DESERIALIZE=1 \ + -DSQLITE_ENABLE_DBPAGE_VTAB=1 \ + -DSQLITE_ENABLE_BYTECODE_VTAB=1 \ + -DSQLITE_ENABLE_DBSTAT_VTAB=1 + CON_CFLAGS = $(cflags) $(cdebug) $(crt) -DCONSOLE -DSQLITE_ENABLE_FTS3=1 TCL_CFLAGS = -DBUILD_sqlite -DUSE_TCL_STUBS \ -DPACKAGE_VERSION="\"$(DOTVERSION)\"" $(BASE_CLFAGS) \ diff --git a/configure b/configure index 367b1485f..a3bd73206 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.69 for sqlite 3.39.4. +# Generated by GNU Autoconf 2.69 for sqlite 3.40.1. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.39.4' -PACKAGE_STRING='sqlite 3.39.4' +PACKAGE_VERSION='3.40.1' +PACKAGE_STRING='sqlite 3.40.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1468,7 +1468,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.39.4 to adapt to many kinds of systems. +\`configure' configures sqlite 3.40.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1533,7 +1533,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.39.4:";; + short | recursive ) echo "Configuration of sqlite 3.40.1:";; esac cat <<\_ACEOF @@ -1661,7 +1661,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.39.4 +sqlite configure 3.40.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2080,7 +2080,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.39.4, which was +It was created by sqlite $as_me 3.40.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -11875,7 +11875,7 @@ fi ######### # Output the config header -ac_config_headers="$ac_config_headers config.h" +ac_config_headers="$ac_config_headers sqlite_cfg.h" ######### @@ -12390,7 +12390,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.39.4, which was +This file was extended by sqlite $as_me 3.40.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12456,7 +12456,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.39.4 +sqlite config.status 3.40.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" @@ -12838,7 +12838,7 @@ for ac_config_target in $ac_config_targets do case $ac_config_target in "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; - "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; + "sqlite_cfg.h") CONFIG_HEADERS="$CONFIG_HEADERS sqlite_cfg.h" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "sqlite3.pc") CONFIG_FILES="$CONFIG_FILES sqlite3.pc" ;; diff --git a/configure.ac b/configure.ac index cc805a00d..5f905db40 100644 --- a/configure.ac +++ b/configure.ac @@ -806,7 +806,7 @@ AC_SUBST(AMALGAMATION_LINE_MACROS) ######### # Output the config header -AC_CONFIG_HEADERS(config.h) +AC_CONFIG_HEADERS(sqlite_cfg.h) ######### # Generate the output files. diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index 73541122d..dee4eb9ec 100644 --- a/ext/expert/expert1.test +++ b/ext/expert/expert1.test @@ -391,6 +391,16 @@ do_setup_rec_test $tn.18.1 { SEARCH SomeObject USING COVERING INDEX SomeObject_idx_00000078 (x=?) } + +do_setup_rec_test $tn.19.0 { + CREATE TABLE t1("index"); +} { + SELECT * FROM t1 ORDER BY "index"; +} { + CREATE INDEX t1_idx_01a7214e ON t1('index'); + SCAN t1 USING COVERING INDEX t1_idx_01a7214e +} + } proc do_candidates_test {tn sql res} { diff --git a/ext/expert/sqlite3expert.c b/ext/expert/sqlite3expert.c index f29b8a9e0..c01feff58 100644 --- a/ext/expert/sqlite3expert.c +++ b/ext/expert/sqlite3expert.c @@ -820,6 +820,10 @@ static char *idxAppendText(int *pRc, char *zIn, const char *zFmt, ...){ */ static int idxIdentifierRequiresQuotes(const char *zId){ int i; + int nId = STRLEN(zId); + + if( sqlite3_keyword_check(zId, nId) ) return 1; + for(i=0; zId[i]; i++){ if( !(zId[i]=='_') && !(zId[i]>='0' && zId[i]<='9') diff --git a/ext/fiddle/EXPORTED_RUNTIME_METHODS b/ext/fiddle/EXPORTED_RUNTIME_METHODS deleted file mode 100644 index 763722d23..000000000 --- a/ext/fiddle/EXPORTED_RUNTIME_METHODS +++ /dev/null @@ -1,14 +0,0 @@ -ALLOC_NORMAL -FS -UTF8ToString -addFunction -allocate -allocateUTF8OnStack -ccall -cwrap -getValue -removeFunction -setValue -stackAlloc -stackRestore -stackSave diff --git a/ext/fiddle/Makefile b/ext/fiddle/Makefile deleted file mode 100644 index 093f9260f..000000000 --- a/ext/fiddle/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# This GNU makefile exists primarily to simplify/speed up development -# from emacs. It is not part of the canonical build process. -default: - $(MAKE) -C ../.. wasm -e emcc_opt=-O0 - -clean: - $(MAKE) -C ../../ clean-wasm - -fiddle_files = emscripten.css fiddle.html \ - fiddle.js fiddle-module.js \ - fiddle-module.wasm fiddle-worker.js \ - $(wildcard *.wasm.gz) $(wildcard *.js.gz) - -# fiddle_remote is the remote destination for the fiddle app. It -# must be a [user@]HOST:/path for rsync. -# Note that the target "should probably" contain a symlink of -# index.html -> fiddle.html. -fiddle_remote ?= -ifeq (,$(fiddle_remote)) -ifneq (,$(wildcard /home/stephan)) - fiddle_remote = wh2:www/wh/sqlite3/. -else ifneq (,$(wildcard /home/drh)) - #fiddle_remote = if appropriate, add that user@host:/path here -endif -endif - -$(fiddle_files): default - -push-fiddle: $(fiddle_files) - @if [ x = "x$(fiddle_remote)" ]; then \ - echo "fiddle_remote must be a [user@]HOST:/path for rsync"; \ - exit 1; \ - fi - rsync -va $(fiddle_files) $(fiddle_remote) diff --git a/ext/fiddle/SqliteTestUtil.js b/ext/fiddle/SqliteTestUtil.js deleted file mode 100644 index cf78946dc..000000000 --- a/ext/fiddle/SqliteTestUtil.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - 2022-05-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 bootstrapping code used by various test scripts - which live in this file's directory. -*/ -(function(){ - /* querySelectorAll() proxy */ - const EAll = function(/*[element=document,] cssSelector*/){ - return (arguments.length>1 ? arguments[0] : document) - .querySelectorAll(arguments[arguments.length-1]); - }; - /* querySelector() proxy */ - const E = function(/*[element=document,] cssSelector*/){ - return (arguments.length>1 ? arguments[0] : document) - .querySelector(arguments[arguments.length-1]); - }; - - /** - Helpers for writing sqlite3-specific tests. - */ - self/*window or worker*/.SqliteTestUtil = { - /** Running total of the number of tests run via - this API. */ - counter: 0, - /** - If expr is a function, it is called and its result - is returned, coerced to a bool, else expr, coerced to - a bool, is returned. - */ - toBool: function(expr){ - return (expr instanceof Function) ? !!expr() : !!expr; - }, - /** abort() if expr is false. If expr is a function, it - is called and its result is evaluated. - */ - assert: function f(expr, msg){ - if(!f._){ - f._ = ('undefined'===typeof abort - ? (msg)=>{throw new Error(msg)} - : abort); - } - ++this.counter; - if(!this.toBool(expr)){ - f._(msg || "Assertion failed."); - } - return this; - }, - /** Identical to assert() but throws instead of calling - abort(). */ - affirm: function(expr, msg){ - ++this.counter; - if(!this.toBool(expr)) throw new Error(msg || "Affirmation failed."); - return this; - }, - /** Calls f() and squelches any exception it throws. If it - does not throw, this function throws. */ - mustThrow: function(f, msg){ - ++this.counter; - let err; - try{ f(); } catch(e){err=e;} - if(!err) throw new Error(msg || "Expected exception."); - return this; - }, - /** Throws if expr is truthy or expr is a function and expr() - returns truthy. */ - throwIf: function(expr, msg){ - ++this.counter; - if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); - return this; - }, - /** Throws if expr is falsy or expr is a function and expr() - returns falsy. */ - throwUnless: function(expr, msg){ - ++this.counter; - if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); - return this; - } - }; - - - /** - This is a module object for use with the emscripten-installed - initSqlite3Module() factory function. - */ - self.sqlite3TestModule = { - postRun: [ - /* function(theModule){...} */ - ], - //onRuntimeInitialized: function(){}, - /* Proxy for C-side stdout output. */ - print: function(){ - console.log.apply(console, Array.prototype.slice.call(arguments)); - }, - /* Proxy for C-side stderr output. */ - printErr: function(){ - console.error.apply(console, Array.prototype.slice.call(arguments)); - }, - /** - Called by the module init bits to report loading - progress. It gets passed an empty argument when loading is - done (after onRuntimeInitialized() and any this.postRun - callbacks have been run). - */ - setStatus: function f(text){ - if(!f.last){ - f.last = { text: '', step: 0 }; - f.ui = { - status: E('#module-status'), - progress: E('#module-progress'), - spinner: E('#module-spinner') - }; - } - if(text === f.last.text) return; - f.last.text = text; - if(f.ui.progress){ - f.ui.progress.value = f.last.step; - f.ui.progress.max = f.last.step + 1; - } - ++f.last.step; - if(text) { - f.ui.status.classList.remove('hidden'); - f.ui.status.innerText = text; - }else{ - if(f.ui.progress){ - f.ui.progress.remove(); - f.ui.spinner.remove(); - delete f.ui.progress; - delete f.ui.spinner; - } - f.ui.status.classList.add('hidden'); - } - } - }; -})(self/*window or worker*/); diff --git a/ext/fiddle/fiddle-worker.js b/ext/fiddle/fiddle-worker.js deleted file mode 100644 index ca562323c..000000000 --- a/ext/fiddle/fiddle-worker.js +++ /dev/null @@ -1,301 +0,0 @@ -/* - 2022-05-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 is the JS Worker file for the sqlite3 fiddle app. It loads the - sqlite3 wasm module and offers access to the db via the Worker - message-passing interface. - - Forewarning: this API is still very much Under Construction and - subject to any number of changes as experience reveals what those - need to be. - - Because we can have only a single message handler, as opposed to an - arbitrary number of discrete event listeners like with DOM elements, - we have to define a lower-level message API. Messages abstractly - look like: - - { type: string, data: type-specific value } - - Where 'type' is used for dispatching and 'data' is a - 'type'-dependent value. - - The 'type' values expected by each side of the main/worker - connection vary. The types are described below but subject to - change at any time as this experiment evolves. - - Workers-to-Main types - - - stdout, stderr: indicate stdout/stderr output from the wasm - layer. The data property is the string of the output, noting - that the emscripten binding emits these one line at a time. Thus, - if a C-side puts() emits multiple lines in a single call, the JS - side will see that as multiple calls. Example: - - {type:'stdout', data: 'Hi, world.'} - - - module: Status text. This is intended to alert the main thread - about module loading status so that, e.g., the main thread can - update a progress widget and DTRT when the module is finished - loading and available for work. Status messages come in the form - - {type:'module', data:{ - type:'status', - data: {text:string|null, step:1-based-integer} - } - - with an incrementing step value for each subsequent message. When - the module loading is complete, a message with a text value of - null is posted. - - - working: data='start'|'end'. Indicates that work is about to be - sent to the module or has just completed. This can be used, e.g., - to disable UI elements which should not be activated while work - is pending. Example: - - {type:'working', data:'start'} - - Main-to-Worker types: - - - shellExec: data=text to execute as if it had been entered in the - sqlite3 CLI shell app (as opposed to sqlite3_exec()). This event - causes the worker to emit a 'working' event (data='start') before - it starts and a 'working' event (data='end') when it finished. If - called while work is currently being executed it emits stderr - message instead of doing actual work, as the underlying db cannot - handle concurrent tasks. Example: - - {type:'shellExec', data: 'select * from sqlite_master'} - - - More TBD as the higher-level db layer develops. -*/ - -/* - Apparent browser(s) bug: console messages emitted may be duplicated - in the console, even though they're provably only run once. See: - - https://stackoverflow.com/questions/49659464 - - Noting that it happens in Firefox as well as Chrome. Harmless but - annoying. -*/ -"use strict"; -(function(){ - /** - Posts a message in the form {type,data} unless passed more than 2 - args, in which case it posts {type, data:[arg1...argN]}. - */ - const wMsg = function(type,data){ - postMessage({ - type, - data: arguments.length<3 - ? data - : Array.prototype.slice.call(arguments,1) - }); - }; - - const stdout = function(){wMsg('stdout', Array.prototype.slice.call(arguments));}; - const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));}; - - self.onerror = function(/*message, source, lineno, colno, error*/) { - const err = arguments[4]; - if(err && 'ExitStatus'==err.name){ - /* This is relevant for the sqlite3 shell binding but not the - lower-level binding. */ - fiddleModule.isDead = true; - stderr("FATAL ERROR:", err.message); - stderr("Restarting the app requires reloading the page."); - wMsg('error', err); - } - console.error(err); - fiddleModule.setStatus('Exception thrown, see JavaScript console: '+err); - }; - - const Sqlite3Shell = { - /** Returns the name of the currently-opened db. */ - dbFilename: function f(){ - if(!f._) f._ = fiddleModule.cwrap('fiddle_db_filename', "string", ['string']); - return f._(); - }, - /** - Runs the given text through the shell as if it had been typed - in by a user. Fires a working/start event before it starts and - working/end event when it finishes. - */ - exec: function f(sql){ - if(!f._) f._ = fiddleModule.cwrap('fiddle_exec', null, ['string']); - if(fiddleModule.isDead){ - stderr("shell module has exit()ed. Cannot run SQL."); - return; - } - wMsg('working','start'); - try { - if(f._running){ - stderr('Cannot run multiple commands concurrently.'); - }else{ - f._running = true; - f._(sql); - } - } finally { - delete f._running; - wMsg('working','end'); - } - }, - resetDb: function f(){ - if(!f._) f._ = fiddleModule.cwrap('fiddle_reset_db', null); - stdout("Resetting database."); - f._(); - stdout("Reset",this.dbFilename()); - }, - /* Interrupt can't work: this Worker is tied up working, so won't get the - interrupt event which would be needed to perform the interrupt. */ - interrupt: function f(){ - if(!f._) f._ = fiddleModule.cwrap('fiddle_interrupt', null); - stdout("Requesting interrupt."); - f._(); - } - }; - - self.onmessage = function f(ev){ - ev = ev.data; - if(!f.cache){ - f.cache = { - prevFilename: null - }; - } - //console.debug("worker: onmessage.data",ev); - switch(ev.type){ - case 'shellExec': Sqlite3Shell.exec(ev.data); return; - case 'db-reset': Sqlite3Shell.resetDb(); return; - case 'interrupt': Sqlite3Shell.interrupt(); return; - /** Triggers the export of the current db. Fires an - event in the form: - - {type:'db-export', - data:{ - filename: name of db, - buffer: contents of the db file (Uint8Array), - error: on error, a message string and no buffer property. - } - } - */ - case 'db-export': { - const fn = Sqlite3Shell.dbFilename(); - stdout("Exporting",fn+"."); - const fn2 = fn ? fn.split(/[/\\]/).pop() : null; - try{ - if(!fn2) throw new Error("DB appears to be closed."); - wMsg('db-export',{ - filename: fn2, - buffer: fiddleModule.FS.readFile(fn, {encoding:"binary"}) - }); - }catch(e){ - /* Post a failure message so that UI elements disabled - during the export can be re-enabled. */ - wMsg('db-export',{ - filename: fn, - error: e.message - }); - } - return; - } - case 'open': { - /* Expects: { - buffer: ArrayBuffer | Uint8Array, - filename: for logging/informational purposes only - } */ - const opt = ev.data; - let buffer = opt.buffer; - if(buffer instanceof Uint8Array){ - }else if(buffer instanceof ArrayBuffer){ - buffer = new Uint8Array(buffer); - }else{ - stderr("'open' expects {buffer:Uint8Array} containing an uploaded db."); - return; - } - const fn = ( - opt.filename - ? opt.filename.split(/[/\\]/).pop().replace('"','_') - : ("db-"+((Math.random() * 10000000) | 0)+ - "-"+((Math.random() * 10000000) | 0)+".sqlite3") - ); - /* We cannot delete the existing db file until the new one - is installed, which means that we risk overflowing our - quota (if any) by having both the previous and current - db briefly installed in the virtual filesystem. */ - fiddleModule.FS.createDataFile("/", fn, buffer, true, true); - const oldName = Sqlite3Shell.dbFilename(); - Sqlite3Shell.exec('.open "/'+fn+'"'); - if(oldName && oldName !== fn){ - try{fiddleModule.FS.unlink(oldName);} - catch(e){/*ignored*/} - } - stdout("Replaced DB with",fn+"."); - return; - } - }; - console.warn("Unknown fiddle-worker message type:",ev); - }; - - /** - emscripten module for use with build mode -sMODULARIZE. - */ - const fiddleModule = { - print: stdout, - printErr: stderr, - /** - Intercepts status updates from the emscripting module init - and fires worker events with a type of 'status' and a - payload of: - - { - text: string | null, // null at end of load process - step: integer // starts at 1, increments 1 per call - } - - We have no way of knowing in advance how many steps will - be processed/posted, so creating a "percentage done" view is - not really practical. One can be approximated by giving it a - current value of message.step and max value of message.step+1, - though. - - When work is finished, a message with a text value of null is - submitted. - - After a message with text==null is posted, the module may later - post messages about fatal problems, e.g. an exit() being - triggered, so it is recommended that UI elements for posting - status messages not be outright removed from the DOM when - text==null, and that they instead be hidden until/unless - text!=null. - */ - setStatus: function f(text){ - if(!f.last) f.last = { step: 0, text: '' }; - else if(text === f.last.text) return; - f.last.text = text; - wMsg('module',{ - type:'status', - data:{step: ++f.last.step, text: text||null} - }); - } - }; - - importScripts('fiddle-module.js'); - /** - initFiddleModule() is installed via fiddle-module.js due to - building with: - - emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initFiddleModule - */ - initFiddleModule(fiddleModule).then(function(thisModule){ - wMsg('fiddle-ready'); - }); -})(); diff --git a/ext/fiddle/fiddle.js b/ext/fiddle/fiddle.js deleted file mode 100644 index 619ce4eca..000000000 --- a/ext/fiddle/fiddle.js +++ /dev/null @@ -1,809 +0,0 @@ -/* - 2022-05-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 is the main entry point for the sqlite3 fiddle app. It sets up the - various UI bits, loads a Worker for the db connection, and manages the - communication between the UI and worker. -*/ -(function(){ - 'use strict'; - /* Recall that the 'self' symbol, except where locally - overwritten, refers to the global window or worker object. */ - - const storage = (function(NS/*namespace object in which to store this module*/){ - /* Pedantic licensing note: this code originated in the Fossil SCM - source tree, where it has a different license, but the person who - ported it into sqlite is the same one who wrote it for fossil. */ - 'use strict'; - NS = NS||{}; - - /** - This module provides a basic wrapper around localStorage - or sessionStorage or a dummy proxy object if neither - of those are available. - */ - const tryStorage = function f(obj){ - if(!f.key) f.key = 'storage.access.check'; - try{ - obj.setItem(f.key, 'f'); - const x = obj.getItem(f.key); - obj.removeItem(f.key); - if(x!=='f') throw new Error(f.key+" failed") - return obj; - }catch(e){ - return undefined; - } - }; - - /** Internal storage impl for this module. */ - const $storage = - tryStorage(window.localStorage) - || tryStorage(window.sessionStorage) - || tryStorage({ - // A basic dummy xyzStorage stand-in - $$$:{}, - setItem: function(k,v){this.$$$[k]=v}, - getItem: function(k){ - return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined; - }, - removeItem: function(k){delete this.$$$[k]}, - clear: function(){this.$$$={}} - }); - - /** - For the dummy storage we need to differentiate between - $storage and its real property storage for hasOwnProperty() - to work properly... - */ - const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage; - - /** - A prefix which gets internally applied to all storage module - property keys so that localStorage and sessionStorage across the - same browser profile instance do not "leak" across multiple apps - being hosted by the same origin server. Such cross-polination is - still there but, with this key prefix applied, it won't be - immediately visible via the storage API. - - With this in place we can justify using localStorage instead of - sessionStorage. - - One implication of using localStorage and sessionStorage is that - their scope (the same "origin" and client application/profile) - allows multiple apps on the same origin to use the same - storage. Thus /appA/foo could then see changes made via - /appB/foo. The data do not cross user- or browser boundaries, - though, so it "might" arguably be called a - feature. storageKeyPrefix was added so that we can sandbox that - state for each separate app which shares an origin. - - See: https://fossil-scm.org/forum/forumpost/4afc4d34de - - Sidebar: it might seem odd to provide a key prefix and stick all - properties in the topmost level of the storage object. We do that - because adding a layer of object to sandbox each app would mean - (de)serializing that whole tree on every storage property change. - e.g. instead of storageObject.projectName.foo we have - storageObject[storageKeyPrefix+'foo']. That's soley for - efficiency's sake (in terms of battery life and - environment-internal storage-level effort). - */ - const storageKeyPrefix = ( - $storageHolder===$storage/*localStorage or sessionStorage*/ - ? ( - (NS.config ? - (NS.config.projectCode || NS.config.projectName - || NS.config.shortProjectName) - : false) - || window.location.pathname - )+'::' : ( - '' /* transient storage */ - ) - ); - - /** - A proxy for localStorage or sessionStorage or a - page-instance-local proxy, if neither one is availble. - - Which exact storage implementation is uses is unspecified, and - apps must not rely on it. - */ - NS.storage = { - storageKeyPrefix: storageKeyPrefix, - /** Sets the storage key k to value v, implicitly converting - it to a string. */ - set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v), - /** Sets storage key k to JSON.stringify(v). */ - setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)), - /** Returns the value for the given storage key, or - dflt if the key is not found in the storage. */ - get: (k,dflt)=>$storageHolder.hasOwnProperty( - storageKeyPrefix+k - ) ? $storage.getItem(storageKeyPrefix+k) : dflt, - /** Returns true if the given key has a value of "true". If the - key is not found, it returns true if the boolean value of dflt - is "true". (Remember that JS persistent storage values are all - strings.) */ - getBool: function(k,dflt){ - return 'true'===this.get(k,''+(!!dflt)); - }, - /** Returns the JSON.parse()'d value of the given - storage key's value, or dflt is the key is not - found or JSON.parse() fails. */ - getJSON: function f(k,dflt){ - try { - const x = this.get(k,f); - return x===f ? dflt : JSON.parse(x); - } - catch(e){return dflt} - }, - /** Returns true if the storage contains the given key, - else false. */ - contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k), - /** Removes the given key from the storage. Returns this. */ - remove: function(k){ - $storage.removeItem(storageKeyPrefix+k); - return this; - }, - /** Clears ALL keys from the storage. Returns this. */ - clear: function(){ - this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k)); - return this; - }, - /** Returns an array of all keys currently in the storage. */ - keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)), - /** Returns true if this storage is transient (only available - until the page is reloaded), indicating that fileStorage - and sessionStorage are unavailable. */ - isTransient: ()=>$storageHolder!==$storage, - /** Returns a symbolic name for the current storage mechanism. */ - storageImplName: function(){ - if($storage===window.localStorage) return 'localStorage'; - else if($storage===window.sessionStorage) return 'sessionStorage'; - else return 'transient'; - }, - - /** - Returns a brief help text string for the currently-selected - storage type. - */ - storageHelpDescription: function(){ - return { - localStorage: "Browser-local persistent storage with an "+ - "unspecified long-term lifetime (survives closing the browser, "+ - "but maybe not a browser upgrade).", - sessionStorage: "Storage local to this browser tab, "+ - "lost if this tab is closed.", - "transient": "Transient storage local to this invocation of this page." - }[this.storageImplName()]; - } - }; - return NS.storage; - })({})/*storage API setup*/; - - - /** Name of the stored copy of SqliteFiddle.config. */ - const configStorageKey = 'sqlite3-fiddle-config'; - - /** - The SqliteFiddle object is intended to be the primary - app-level object for the main-thread side of the sqlite - fiddle application. It uses a worker thread to load the - sqlite WASM module and communicate with it. - */ - const SF/*local convenience alias*/ - = window.SqliteFiddle/*canonical name*/ = { - /* Config options. */ - config: { - /* If true, SqliteFiddle.echo() will auto-scroll the - output widget to the bottom when it receives output, - else it won't. */ - autoScrollOutput: true, - /* If true, the output area will be cleared before each - command is run, else it will not. */ - autoClearOutput: false, - /* If true, SqliteFiddle.echo() will echo its output to - the console, in addition to its normal output widget. - That slows it down but is useful for testing. */ - echoToConsole: false, - /* If true, display input/output areas side-by-side. */ - sideBySide: true, - /* If true, swap positions of the input/output areas. */ - swapInOut: false - }, - /** - Emits the given text, followed by a line break, to the - output widget. If given more than one argument, they are - join()'d together with a space between each. As a special - case, if passed a single array, that array is used in place - of the arguments array (this is to facilitate receiving - lists of arguments via worker events). - */ - echo: function f(text) { - /* Maintenance reminder: we currently require/expect a textarea - output element. It might be nice to extend this to behave - differently if the output element is a non-textarea element, - in which case it would need to append the given text as a TEXT - node and add a line break. */ - if(!f._){ - f._ = document.getElementById('output'); - f._.value = ''; // clear browser cache - } - if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); - else if(1===arguments.length && Array.isArray(text)) text = text.join(' '); - // These replacements are necessary if you render to raw HTML - //text = text.replace(/&/g, "&"); - //text = text.replace(//g, ">"); - //text = text.replace('\n', '
    ', 'g'); - if(null===text){/*special case: clear output*/ - f._.value = ''; - return; - }else if(this.echo._clearPending){ - delete this.echo._clearPending; - f._.value = ''; - } - if(this.config.echoToConsole) console.log(text); - if(this.jqTerm) this.jqTerm.echo(text); - f._.value += text + "\n"; - if(this.config.autoScrollOutput){ - f._.scrollTop = f._.scrollHeight; - } - }, - _msgMap: {}, - /** Adds a worker message handler for messages of the given - type. */ - addMsgHandler: function f(type,callback){ - if(Array.isArray(type)){ - type.forEach((t)=>this.addMsgHandler(t, callback)); - return this; - } - (this._msgMap.hasOwnProperty(type) - ? this._msgMap[type] - : (this._msgMap[type] = [])).push(callback); - return this; - }, - /** Given a worker message, runs all handlers for msg.type. */ - runMsgHandlers: function(msg){ - const list = (this._msgMap.hasOwnProperty(msg.type) - ? this._msgMap[msg.type] : false); - if(!list){ - console.warn("No handlers found for message type:",msg); - return false; - } - //console.debug("runMsgHandlers",msg); - list.forEach((f)=>f(msg)); - return true; - }, - /** Removes all message handlers for the given message type. */ - clearMsgHandlers: function(type){ - delete this._msgMap[type]; - return this; - }, - /* Posts a message in the form {type, data} to the db worker. Returns this. */ - wMsg: function(type,data){ - this.worker.postMessage({type, data}); - return this; - }, - /** - Prompts for confirmation and, if accepted, deletes - all content and tables in the (transient) database. - */ - resetDb: function(){ - if(window.confirm("Really destroy all content and tables " - +"in the (transient) db?")){ - this.wMsg('db-reset'); - } - return this; - }, - /** Stores this object's config in the browser's storage. */ - storeConfig: function(){ - storage.setJSON(configStorageKey,this.config); - } - }; - - if(1){ /* Restore SF.config */ - const storedConfig = storage.getJSON(configStorageKey); - if(storedConfig){ - /* Copy all properties to SF.config which are currently in - storedConfig. We don't bother copying any other - properties: those have been removed from the app in the - meantime. */ - Object.keys(SF.config).forEach(function(k){ - if(storedConfig.hasOwnProperty(k)){ - SF.config[k] = storedConfig[k]; - } - }); - } - } - - SF.worker = new Worker('fiddle-worker.js'); - SF.worker.onmessage = (ev)=>SF.runMsgHandlers(ev.data); - SF.addMsgHandler(['stdout', 'stderr'], (ev)=>SF.echo(ev.data)); - - /* querySelectorAll() proxy */ - const EAll = function(/*[element=document,] cssSelector*/){ - return (arguments.length>1 ? arguments[0] : document) - .querySelectorAll(arguments[arguments.length-1]); - }; - /* querySelector() proxy */ - const E = function(/*[element=document,] cssSelector*/){ - return (arguments.length>1 ? arguments[0] : document) - .querySelector(arguments[arguments.length-1]); - }; - - /** Handles status updates from the Module object. */ - SF.addMsgHandler('module', function f(ev){ - ev = ev.data; - if('status'!==ev.type){ - console.warn("Unexpected module-type message:",ev); - return; - } - if(!f.ui){ - f.ui = { - status: E('#module-status'), - progress: E('#module-progress'), - spinner: E('#module-spinner') - }; - } - const msg = ev.data; - if(f.ui.progres){ - progress.value = msg.step; - progress.max = msg.step + 1/*we don't know how many steps to expect*/; - } - if(1==msg.step){ - f.ui.progress.classList.remove('hidden'); - f.ui.spinner.classList.remove('hidden'); - } - if(msg.text){ - f.ui.status.classList.remove('hidden'); - f.ui.status.innerText = msg.text; - }else{ - if(f.ui.progress){ - f.ui.progress.remove(); - f.ui.spinner.remove(); - delete f.ui.progress; - delete f.ui.spinner; - } - f.ui.status.classList.add('hidden'); - /* The module can post messages about fatal problems, - e.g. an exit() being triggered or assertion failure, - after the last "load" message has arrived, so - leave f.ui.status and message listener intact. */ - } - }); - - /** - The 'fiddle-ready' event is fired (with no payload) when the - wasm module has finished loading. Interestingly, that happens - _before_ the final module:status event */ - SF.addMsgHandler('fiddle-ready', function(){ - SF.clearMsgHandlers('fiddle-ready'); - self.onSFLoaded(); - }); - - /** - Performs all app initialization which must wait until after the - worker module is loaded. This function removes itself when it's - called. - */ - self.onSFLoaded = function(){ - delete this.onSFLoaded; - // Unhide all elements which start out hidden - EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); - E('#btn-reset').addEventListener('click',()=>SF.resetDb()); - const taInput = E('#input'); - const btnClearIn = E('#btn-clear'); - btnClearIn.addEventListener('click',function(){ - taInput.value = ''; - },false); - // Ctrl-enter and shift-enter both run the current SQL. - taInput.addEventListener('keydown',function(ev){ - if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){ - ev.preventDefault(); - ev.stopPropagation(); - btnShellExec.click(); - } - }, false); - const taOutput = E('#output'); - const btnClearOut = E('#btn-clear-output'); - btnClearOut.addEventListener('click',function(){ - taOutput.value = ''; - if(SF.jqTerm) SF.jqTerm.clear(); - },false); - const btnShellExec = E('#btn-shell-exec'); - btnShellExec.addEventListener('click',function(ev){ - let sql; - ev.preventDefault(); - if(taInput.selectionStart e.addEventListener('click', cmdClick, false) - ); - - btnInterrupt.addEventListener('click',function(){ - SF.wMsg('interrupt'); - }); - - /** Initiate a download of the db. */ - const btnExport = E('#btn-export'); - const eLoadDb = E('#load-db'); - const btnLoadDb = E('#btn-load-db'); - btnLoadDb.addEventListener('click', ()=>eLoadDb.click()); - /** - Enables (if passed true) or disables all UI elements which - "might," if timed "just right," interfere with an - in-progress db import/export/exec operation. - */ - const enableMutatingElements = function f(enable){ - if(!f._elems){ - f._elems = [ - /* UI elements to disable while import/export are - running. Normally the export is fast enough - that this won't matter, but we really don't - want to be reading (from outside of sqlite) the - db when the user taps btnShellExec. */ - btnShellExec, btnExport, eLoadDb - ]; - } - f._elems.forEach( enable - ? (e)=>e.removeAttribute('disabled') - : (e)=>e.setAttribute('disabled','disabled') ); - }; - btnExport.addEventListener('click',function(){ - enableMutatingElements(false); - SF.wMsg('db-export'); - }); - SF.addMsgHandler('db-export', function(ev){ - enableMutatingElements(true); - ev = ev.data; - if(ev.error){ - SF.echo("Export failed:",ev.error); - return; - } - const blob = new Blob([ev.buffer], {type:"application/x-sqlite3"}); - const a = document.createElement('a'); - document.body.appendChild(a); - a.href = window.URL.createObjectURL(blob); - a.download = ev.filename; - a.addEventListener('click',function(){ - setTimeout(function(){ - SF.echo("Exported (possibly auto-downloaded):",ev.filename); - window.URL.revokeObjectURL(a.href); - a.remove(); - },500); - }); - a.click(); - }); - /** - Handle load/import of an external db file. - */ - eLoadDb.addEventListener('change',function(){ - const f = this.files[0]; - const r = new FileReader(); - const status = {loaded: 0, total: 0}; - enableMutatingElements(false); - r.addEventListener('loadstart', function(){ - SF.echo("Loading",f.name,"..."); - }); - r.addEventListener('progress', function(ev){ - SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes."); - }); - const that = this; - r.addEventListener('load', function(){ - enableMutatingElements(true); - SF.echo("Loaded",f.name+". Opening db..."); - SF.wMsg('open',{ - filename: f.name, - buffer: this.result - }); - }); - r.addEventListener('error',function(){ - enableMutatingElements(true); - SF.echo("Loading",f.name,"failed for unknown reasons."); - }); - r.addEventListener('abort',function(){ - enableMutatingElements(true); - SF.echo("Cancelled loading of",f.name+"."); - }); - r.readAsArrayBuffer(f); - }); - - EAll('fieldset.collapsible').forEach(function(fs){ - const btnToggle = E(fs,'legend > .fieldset-toggle'), - content = EAll(fs,':scope > div'); - btnToggle.addEventListener('click', function(){ - fs.classList.toggle('collapsed'); - content.forEach((d)=>d.classList.toggle('hidden')); - }, false); - }); - - /** - Given a DOM element, this routine measures its "effective - height", which is the bounding top/bottom range of this element - and all of its children, recursively. For some DOM structure - cases, a parent may have a reported height of 0 even though - children have non-0 sizes. - - Returns 0 if !e or if the element really has no height. - */ - const effectiveHeight = function f(e){ - if(!e) return 0; - if(!f.measure){ - f.measure = function callee(e, depth){ - if(!e) return; - const m = e.getBoundingClientRect(); - if(0===depth){ - callee.top = m.top; - callee.bottom = m.bottom; - }else{ - callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; - callee.bottom = Math.max(callee.bottom, m.bottom); - } - Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); - if(0===depth){ - //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); - f.extra += callee.bottom - callee.top; - } - return f.extra; - }; - } - f.extra = 0; - f.measure(e,0); - return f.extra; - }; - - /** - Returns a function, that, as long as it continues to be invoked, - will not be triggered. The function will be called after it stops - being called for N milliseconds. If `immediate` is passed, call - the callback immediately and hinder future invocations until at - least the given time has passed. - - If passed only 1 argument, or passed a falsy 2nd argument, - the default wait time set in this function's $defaultDelay - property is used. - - Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function - */ - const debounce = function f(func, wait, immediate) { - var timeout; - if(!wait) wait = f.$defaultDelay; - return function() { - const context = this, args = Array.prototype.slice.call(arguments); - const later = function() { - timeout = undefined; - if(!immediate) func.apply(context, args); - }; - const callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if(callNow) func.apply(context, args); - }; - }; - debounce.$defaultDelay = 500 /*arbitrary*/; - - const ForceResizeKludge = (function(){ - /* Workaround for Safari mayhem regarding use of vh CSS - units.... We cannot use vh units to set the main view - size because Safari chokes on that, so we calculate - that height here. Larger than ~95% is too big for - Firefox on Android, causing the input area to move - off-screen. */ - const appViews = EAll('.app-view'); - const elemsToCount = [ - /* Elements which we need to always count in the - visible body size. */ - E('body > header'), - E('body > footer') - ]; - const resized = function f(){ - if(f.$disabled) return; - const wh = window.innerHeight; - var ht; - var extra = 0; - elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false); - ht = wh - extra; - appViews.forEach(function(e){ - e.style.height = - e.style.maxHeight = [ - "calc(", (ht>=100 ? ht : 100), "px", - " - 2em"/*fudge value*/,")" - /* ^^^^ hypothetically not needed, but both - Chrome/FF on Linux will force scrollbars on the - body if this value is too small. */ - ].join(''); - }); - }; - resized.$disabled = true/*gets deleted when setup is finished*/; - window.addEventListener('resize', debounce(resized, 250), false); - return resized; - })(); - - /** Set up a selection list of examples */ - (function(){ - const xElem = E('#select-examples'); - const examples = [ - {name: "Help", sql: -`-- ================================================ --- Use ctrl-enter or shift-enter to execute sqlite3 --- shell commands and SQL. --- If a subset of the text is currently selected, --- only that part is executed. --- ================================================ -.help`}, - {name: "Timer on", sql: ".timer on"}, - {name: "Setup table T", sql:`.nullvalue NULL -CREATE TABLE t(a,b); -INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012); -SELECT * FROM t;`}, - {name: "Table list", sql: ".tables"}, - {name: "Box Mode", sql: ".mode box"}, - {name: "JSON Mode", sql: ".mode json"}, - {name: "Mandlebrot", sql: `WITH RECURSIVE - xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2), - yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0), - m(iter, cx, cy, x, y) AS ( - SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis - UNION ALL - SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m - WHERE (x*x + y*y) < 4.0 AND iter<28 - ), - m2(iter, cx, cy) AS ( - SELECT max(iter), cx, cy FROM m GROUP BY cx, cy - ), - a(t) AS ( - SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') - FROM m2 GROUP BY cy - ) -SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`} - ]; - const newOpt = function(lbl,val){ - const o = document.createElement('option'); - o.value = val; - if(!val) o.setAttribute('disabled',true); - o.appendChild(document.createTextNode(lbl)); - xElem.appendChild(o); - }; - newOpt("Examples (replaces input!)"); - examples.forEach((o)=>newOpt(o.name, o.sql)); - //xElem.setAttribute('disabled',true); - xElem.selectedIndex = 0; - xElem.addEventListener('change', function(){ - taInput.value = '-- ' + - this.selectedOptions[0].innerText + - '\n' + this.value; - SF.dbExec(this.value); - }); - })()/* example queries */; - - SF.echo(null/*clear any output generated by the init process*/); - if(window.jQuery && window.jQuery.terminal){ - /* Set up the terminal-style view... */ - const eTerm = window.jQuery('#view-terminal').empty(); - SF.jqTerm = eTerm.terminal(SF.dbExec.bind(SF),{ - prompt: 'sqlite> ', - greetings: false /* note that the docs incorrectly call this 'greeting' */ - }); - /* Set up a button to toggle the views... */ - const head = E('header#titlebar'); - const btnToggleView = document.createElement('button'); - btnToggleView.appendChild(document.createTextNode("Toggle View")); - head.appendChild(btnToggleView); - btnToggleView.addEventListener('click',function f(){ - EAll('.app-view').forEach(e=>e.classList.toggle('hidden')); - if(document.body.classList.toggle('terminal-mode')){ - ForceResizeKludge(); - } - }, false); - btnToggleView.click()/*default to terminal view*/; - } - SF.dbExec(null/*init the db and output the header*/); - SF.echo('This experimental app is provided in the hope that it', - 'may prove interesting or useful but is not an officially', - 'supported deliverable of the sqlite project. It is subject to', - 'any number of changes or outright removal at any time.\n'); - delete ForceResizeKludge.$disabled; - ForceResizeKludge(); - - btnShellExec.click(); - }/*onSFLoaded()*/; -})(); diff --git a/ext/fiddle/index.md b/ext/fiddle/index.md deleted file mode 100644 index 9d1f8d83e..000000000 --- a/ext/fiddle/index.md +++ /dev/null @@ -1,94 +0,0 @@ -This directory houses a "fiddle"-style application which embeds a -[Web Assembly (WASM)](https://en.wikipedia.org/wiki/WebAssembly) -build of the sqlite3 shell app into an HTML page, effectively running -the shell in a client-side browser. - -It requires [emscripten][] and that the build environment be set up for -emscripten. A mini-HOWTO for setting that up follows... - -First, install the Emscripten SDK, as documented -[here](https://emscripten.org/docs/getting_started/downloads.html) and summarized -below for Linux environments: - -``` -# Clone the emscripten repository: -$ git clone https://github.com/emscripten-core/emsdk.git -$ cd emsdk - -# Download and install the latest SDK tools: -$ ./emsdk install latest - -# Make the "latest" SDK "active" for the current user: -$ ./emsdk activate latest -``` - -Those parts only need to be run once. The following needs to be run for each -shell instance which needs the `emcc` compiler: - -``` -# Activate PATH and other environment variables in the current terminal: -$ source ./emsdk_env.sh - -$ which emcc -/path/to/emsdk/upstream/emscripten/emcc -``` - -That `env` script needs to be sourced for building this application from the -top of the sqlite3 build tree: - -``` -$ make fiddle -``` - -Or: - -``` -$ cd ext/fiddle -$ make -``` - -That will generate the fiddle application under -[ext/fiddle](/dir/ext/fiddle), as `fiddle.html`. That application -cannot, due to XMLHttpRequest security limitations, run if the HTML -file is opened directly in the browser (i.e. if it is opened using a -`file://` URL), so it needs to be served via an HTTP server. For -example, using [althttpd][]: - -``` -$ cd ext/fiddle -$ althttpd -debug 1 -jail 0 -port 9090 -root . -``` - -Then browse to `http://localhost:9090/fiddle.html`. - -Note that when serving this app via [althttpd][], it must be a version -from 2022-05-17 or newer so that it recognizes the `.wasm` file -extension and responds with the mimetype `application/wasm`, as the -WASM loader is pedantic about that detail. - -# Known Quirks and Limitations - -Some "impedence mismatch" between C and WASM/JavaScript is to be -expected. - -## No I/O - -sqlite3 shell commands which require file I/O or pipes are disabled in -the WASM build. - -## `exit()` Triggered from C - -When C code calls `exit()`, as happens (for example) when running an -"unsafe" command when safe mode is active, WASM's connection to the -sqlite3 shell environment has no sensible choice but to shut down -because `exit()` leaves it in a state we can no longer recover -from. The JavaScript-side application attempts to recognize this and -warn the user that restarting the application is necessary. Currently -the only way to restart it is to reload the page. Restructuring the -shell code such that it could be "rebooted" without restarting the -JS app would require some invasive changes which are not currently -on any TODO list but have not been entirely ruled out long-term. - - -[emscripten]: https://emscripten.org -[althttpd]: https://sqlite.org/althttpd diff --git a/ext/fiddle/sqlite3-api.js b/ext/fiddle/sqlite3-api.js deleted file mode 100644 index 5057eb53f..000000000 --- a/ext/fiddle/sqlite3-api.js +++ /dev/null @@ -1,1781 +0,0 @@ -/* - 2022-05-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 is intended to be appended to the emcc-generated - sqlite3.js via emcc: - - emcc ... -sMODULARIZE -sEXPORT_NAME=initSqlite3Module --post-js=THIS_FILE - - It is loaded by importing the emcc-generated sqlite3.js, then: - - initSqlite3Module({module object}).then( - function(theModule){ - theModule.sqlite3 == an object containing this file's - deliverables: - { - api: bindings for much of the core sqlite3 APIs, - SQLite3: high-level OO API wrapper - } - }); - - It is up to the caller to provide a module object compatible with - emcc, but it can be a plain empty object. The object passed to - initSqlite3Module() will get populated by the emscripten-generated - bits and, in part, by the code from this file. Specifically, this file - installs the `theModule.sqlite3` part shown above. - - The resulting sqlite3.api object wraps the standard sqlite3 C API in - a way as close to its native form as JS allows for. The - sqlite3.SQLite3 object provides a higher-level wrapper more - appropriate for general client-side use in JS. - - Because using certain parts of the low-level API properly requires - some degree of WASM-related magic, it is not recommended that that - API be used as-is in client-level code. Rather, client code should - use the higher-level OO API or write a custom wrapper on top of the - lower-level API. In short, most of the C-style API is used in an - intuitive manner from JS but any C-style APIs which take - pointers-to-pointer arguments require WASM-specific interfaces - installed by emcscripten-generated code. Those which take or return - only integers, doubles, strings, or "plain" pointers to db or - statement objects can be used in "as normal," noting that "pointers" - in wasm are simply 32-bit integers. - - # Goals and Non-goals of this API - - Goals: - - - Except where noted in the non-goals, provide a more-or-less - complete wrapper to the sqlite3 C API, insofar as WASM feature - parity with C allows for. In fact, provide at least 3... - - - (1) The aforementioned C-style API. (2) An OO-style API on - top of that, designed to run in the same thread (main window or - Web Worker) as the C API. (3) A less-capable wrapper which can - work across the main window/worker boundary, where the sqlite3 API - is one of those and this wrapper is in the other. That - constellation places some considerable limitations on how the API - can be interacted with, but keeping the DB operations out of the - UI thread is generally desirable. - - - Insofar as possible, support client-side storage using JS - filesystem APIs. As of this writing, such things are still very - much TODO. - - Non-goals: - - - As WASM is a web-centric technology and UTF-8 is the King of - Encodings in that realm, there are no current plans to support the - UTF16-related APIs. They would add a complication to the bindings - for no appreciable benefit. - - - Supporting old or niche-market platforms. WASM is built for a - modern web and requires modern platforms. - -*/ -if(!Module.postRun) Module.postRun = []; -/* ^^^^ the name Module is, in this setup, scope-local in the generated - file sqlite3.js, with which this file gets combined at build-time. */ -Module.postRun.push(function(namespace/*the module object, the target for - installed features*/){ - 'use strict'; - /* For reference: sql.js does essentially everything we want and - it solves much of the wasm-related voodoo, but we'll need a - different structure because we want the db connection to run in - a worker thread and feed data back into the main - thread. Regardless of those differences, it makes a great point - of reference: - - https://github.com/sql-js/sql.js - - Some of the specific design goals here: - - - Bind a low-level sqlite3 API which is close to the native one - in terms of usage. - - - Create a higher-level one, more akin to sql.js and - node.js-style implementations. This one would speak directly - to the low-level API. This API could be used by clients who - import the low-level API directly into their main thread - (which we don't want to recommend but also don't want to - outright forbid). - - - Create a second higher-level one which speaks to the - low-level API via worker messages. This one would be intended - for use in the main thread, talking to the low-level UI via - worker messages. Because workers have only a single message - channel, some acrobatics will be needed here to feed async - work results back into client-side callbacks (as those - callbacks cannot simply be passed to the worker). Exactly - what those acrobatics should look like is not yet entirely - clear and much experimentation is pending. - */ - - const SQM = namespace/*the sqlite module object */; - - /** - Set up the main sqlite3 binding API here, mimicking the C API as - closely as we can. - - Attribution: though not a direct copy/paste, much of what - follows is strongly influenced by the sql.js implementation. - */ - const api = { - /* It is important that the following integer values match - those from the C code. Ideally we could fetch them from the - C API, e.g., in the form of a JSON object, but getting that - JSON string constructed within our current confines is - currently not worth the effort. - - Reminder to self: we could probably do so by adding the - proverbial level of indirection, calling in to C to get it, - and having that C func call an - emscripten-installed/JS-implemented library function which - builds the result object: - - const obj = {}; - sqlite3__get_enum(function(key,val){ - obj[key] = val; - }); - - but whether or not we can pass a function that way, via a - (void*) is as yet unknown. - */ - /* Minimum subset of sqlite result codes we'll need. */ - SQLITE_OK: 0, - SQLITE_ROW: 100, - SQLITE_DONE: 101, - /* sqlite data types */ - SQLITE_INTEGER: 1, - SQLITE_FLOAT: 2, - SQLITE_TEXT: 3, - SQLITE_BLOB: 4, - SQLITE_NULL: 5, - /* create_function() flags */ - SQLITE_DETERMINISTIC: 0x000000800, - SQLITE_DIRECTONLY: 0x000080000, - SQLITE_INNOCUOUS: 0x000200000, - /* sqlite encodings, used for creating UDFs, noting that we - will only support UTF8. */ - SQLITE_UTF8: 1 - }; - const cwrap = SQM.cwrap; - [/* C-side functions to bind. Each entry is an array with 3 or 4 - elements: - - ["c-side name", - "result type" (cwrap() syntax), - [arg types in cwrap() syntax] - ] - - If it has 4 elements, the first one is an alternate name to - use for the JS-side binding. That's required when overloading - a binding for two different uses. - */ - ["sqlite3_bind_blob","number",["number", "number", "number", "number", "number"]], - ["sqlite3_bind_double","number",["number", "number", "number"]], - ["sqlite3_bind_int","number",["number", "number", "number"]], - /*Noting that JS/wasm combo does not currently support 64-bit integers: - ["sqlite3_bind_int64","number",["number", "number", "number"]],*/ - ["sqlite3_bind_null","void",["number"]], - ["sqlite3_bind_parameter_count", "number", ["number"]], - ["sqlite3_bind_parameter_index","number",["number", "string"]], - ["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]], - ["sqlite3_changes", "number", ["number"]], - ["sqlite3_clear_bindings","number",["number"]], - ["sqlite3_close_v2", "number", ["number"]], - ["sqlite3_column_blob","number", ["number", "number"]], - ["sqlite3_column_bytes","number",["number", "number"]], - ["sqlite3_column_count", "number", ["number"]], - ["sqlite3_column_count","number",["number"]], - ["sqlite3_column_double","number",["number", "number"]], - ["sqlite3_column_int","number",["number", "number"]], - /*Noting that JS/wasm combo does not currently support 64-bit integers: - ["sqlite3_column_int64","number",["number", "number"]],*/ - ["sqlite3_column_name","string",["number", "number"]], - ["sqlite3_column_text","string",["number", "number"]], - ["sqlite3_column_type","number",["number", "number"]], - ["sqlite3_compileoption_get", "string", ["number"]], - ["sqlite3_compileoption_used", "number", ["string"]], - ["sqlite3_create_function_v2", "number", - ["number", "string", "number", "number","number", - "number", "number", "number", "number"]], - ["sqlite3_data_count", "number", ["number"]], - ["sqlite3_db_filename", "string", ["number", "string"]], - ["sqlite3_errmsg", "string", ["number"]], - ["sqlite3_exec", "number", ["number", "string", "number", "number", "number"]], - ["sqlite3_finalize", "number", ["number"]], - ["sqlite3_interrupt", "void", ["number"]], - ["sqlite3_libversion", "string", []], - ["sqlite3_open", "number", ["string", "number"]], - //["sqlite3_open_v2", "number", ["string", "number", "number", "string"]], - //^^^^ TODO: add the flags needed for the 3rd arg - ["sqlite3_prepare_v2", "number", ["number", "string", "number", "number", "number"]], - ["sqlite3_prepare_v2_sqlptr", "sqlite3_prepare_v2", - /* Impl which requires that the 2nd argument be a pointer to - the SQL string, instead of being converted to a - string. This is used for cases where we require a non-NULL - value for the final argument (exec()'ing multiple - statements from one input string). */ - "number", ["number", "number", "number", "number", "number"]], - ["sqlite3_reset", "number", ["number"]], - ["sqlite3_result_blob",null,["number", "number", "number", "number"]], - ["sqlite3_result_double",null,["number", "number"]], - ["sqlite3_result_error",null,["number", "string", "number"]], - ["sqlite3_result_int",null,["number", "number"]], - ["sqlite3_result_null",null,["number"]], - ["sqlite3_result_text",null,["number", "string", "number", "number"]], - ["sqlite3_sourceid", "string", []], - ["sqlite3_sql", "string", ["number"]], - ["sqlite3_step", "number", ["number"]], - ["sqlite3_value_blob", "number", ["number"]], - ["sqlite3_value_bytes","number",["number"]], - ["sqlite3_value_double","number",["number"]], - ["sqlite3_value_text", "string", ["number"]], - ["sqlite3_value_type", "number", ["number"]] - //["sqlite3_normalized_sql", "string", ["number"]] - ].forEach(function(a){ - const k = (4==a.length) ? a.shift() : a[0]; - api[k] = cwrap.apply(this, a); - }); - - /* What follows is colloquially known as "OO API #1". It is a - binding of the sqlite3 API which is designed to be run within - the same thread (main or worker) as the one in which the - sqlite3 WASM binding was initialized. This wrapper cannot use - the sqlite3 binding if, e.g., the wrapper is in the main thread - and the sqlite3 API is in a worker. */ - - /** Memory for use in some pointer-to-pointer-passing routines. */ - const pPtrArg = stackAlloc(4); - /** Throws a new error, concatenating all args with a space between - each. */ - const toss = function(){ - throw new Error(Array.prototype.join.call(arguments, ' ')); - }; - - /** - The DB class wraps a sqlite3 db handle. - - It accepts the following argument signatures: - - - () - - (undefined) (same effect as ()) - - (filename[,buffer]) - - (buffer) - - Where a buffer indicates a Uint8Array holding an sqlite3 db - image. - - If the filename is provided, only the last component of the - path is used - any path prefix is stripped and certain - "special" characters are replaced with `_`. If no name is - provided, a random name is generated. The resulting filename is - the one used for accessing the db file within root directory of - the emscripten-supplied virtual filesystem, and is set (with no - path part) as the DB object's `filename` property. - - Note that the special sqlite3 db names ":memory:" and "" - (temporary db) have no special meanings here. We can apparently - only export images of DBs which are stored in the - pseudo-filesystem provided by the JS APIs. Since exporting and - importing images is an important usability feature for this - class, ":memory:" DBs are not supported (until/unless we can - find a way to export those as well). The naming semantics will - certainly evolve as this API does. - */ - const DB = function(arg){ - let buffer, fn; - if(arg instanceof Uint8Array){ - buffer = arg; - arg = undefined; - }else if(arguments.length){ /*(filename[,buffer])*/ - if('string'===typeof arg){ - const p = arg.split('/').pop().replace(':','_'); - if(p) fn = p; - if(arguments.length>1){ - buffer = arguments[1]; - } - }else if(undefined!==arg){ - toss("Invalid arguments to DB constructor.", - "Expecting (), (undefined), (name,buffer),", - "or (buffer), where buffer an sqlite3 db ", - "as a Uint8Array."); - } - } - if(!fn){ - fn = "db-"+((Math.random() * 10000000) | 0)+ - "-"+((Math.random() * 10000000) | 0)+".sqlite3"; - } - if(buffer){ - if(!(buffer instanceof Uint8Array)){ - toss("Expecting Uint8Array image of db contents."); - } - FS.createDataFile("/", fn, buffer, true, true); - } - setValue(pPtrArg, 0, "i32"); - this.checkRc(api.sqlite3_open(fn, pPtrArg)); - this._pDb = getValue(pPtrArg, "i32"); - this.filename = fn; - this._statements = {/*map of open Stmt _pointers_ to Stmt*/}; - this._udfs = {/*map of UDF names to wasm function _pointers_*/}; - }; - - /** - Internal-use enum for mapping JS types to DB-bindable types. - These do not (and need not) line up with the SQLITE_type - values. All values in this enum must be truthy and distinct - but they need not be numbers. - */ - const BindTypes = { - null: 1, - number: 2, - string: 3, - boolean: 4, - blob: 5 - }; - BindTypes['undefined'] == BindTypes.null; - - /** - This class wraps sqlite3_stmt. Calling this constructor - directly will trigger an exception. Use DB.prepare() to create - new instances. - */ - const Stmt = function(){ - if(BindTypes!==arguments[2]){ - toss("Do not call the Stmt constructor directly. Use DB.prepare()."); - } - this.db = arguments[0]; - this._pStmt = arguments[1]; - this.columnCount = api.sqlite3_column_count(this._pStmt); - this.parameterCount = api.sqlite3_bind_parameter_count(this._pStmt); - this._allocs = [/*list of alloc'd memory blocks for bind() values*/] - }; - - /** Throws if the given DB has been closed, else it is returned. */ - const affirmDbOpen = function(db){ - if(!db._pDb) toss("DB has been closed."); - return db; - }; - - /** Returns true if n is a 32-bit (signed) integer, - else false. */ - const isInt32 = function(n){ - return (n===n|0 && n<0xFFFFFFFF) ? true : undefined; - }; - - /** - Expects to be passed (arguments) from DB.exec() and - DB.execMulti(). Does the argument processing/validation, throws - on error, and returns a new object on success: - - { sql: the SQL, obt: optionsObj, cbArg: function} - - cbArg is only set if the opt.callback is set, in which case - it's a function which expects to be passed the current Stmt - and returns the callback argument of the type indicated by - the input arguments. - */ - const parseExecArgs = function(args){ - const out = {}; - switch(args.length){ - case 1: - if('string'===typeof args[0]){ - out.sql = args[0]; - out.opt = {}; - }else if(args[0] && 'object'===typeof args[0]){ - out.opt = args[0]; - out.sql = out.opt.sql; - } - break; - case 2: - out.sql = args[0]; - out.opt = args[1]; - break; - default: toss("Invalid argument count for exec()."); - }; - if('string'!==typeof out.sql) toss("Missing SQL argument."); - if(out.opt.callback || out.opt.resultRows){ - switch((undefined===out.opt.rowMode) - ? 'stmt' : out.opt.rowMode) { - case 'object': out.cbArg = (stmt)=>stmt.get({}); break; - case 'array': out.cbArg = (stmt)=>stmt.get([]); break; - case 'stmt': out.cbArg = (stmt)=>stmt; break; - default: toss("Invalid rowMode:",out.opt.rowMode); - } - } - return out; - }; - - /** If object opts has _its own_ property named p then that - property's value is returned, else dflt is returned. */ - const getOwnOption = (opts, p, dflt)=> - opts.hasOwnProperty(p) ? opts[p] : dflt; - - DB.prototype = { - /** - Expects to be given an sqlite3 API result code. If it is - falsy, this function returns this object, else it throws an - exception with an error message from sqlite3_errmsg(), - using this object's db handle. Note that if it's passed a - non-error code like SQLITE_ROW or SQLITE_DONE, it will - still throw but the error string might be "Not an error." - The various non-0 non-error codes need to be checked for in - client code where they are expected. - */ - checkRc: function(sqliteResultCode){ - if(!sqliteResultCode) return this; - toss("sqlite result code",sqliteResultCode+":", - api.sqlite3_errmsg(this._pDb) || "Unknown db error."); - }, - /** - Finalizes all open statements and closes this database - connection. This is a no-op if the db has already been - closed. If the db is open and alsoUnlink is truthy then the - this.filename entry in the pseudo-filesystem will also be - removed (and any error in that attempt is silently - ignored). - */ - close: function(alsoUnlink){ - if(this._pDb){ - let s; - const that = this; - Object.keys(this._statements).forEach(function(k,s){ - delete that._statements[k]; - if(s && s._pStmt) s.finalize(); - }); - Object.values(this._udfs).forEach(SQM.removeFunction); - delete this._udfs; - delete this._statements; - api.sqlite3_close_v2(this._pDb); - delete this._pDb; - if(this.filename){ - if(alsoUnlink){ - try{SQM.FS.unlink('/'+this.filename);} - catch(e){/*ignored*/} - } - delete this.filename; - } - } - }, - /** - Similar to this.filename but will return NULL for - special names like ":memory:". Not of much use until - we have filesystem support. Throws if the DB has - been closed. If passed an argument it then it will return - the filename of the ATTACHEd db with that name, else it assumes - a name of `main`. - */ - fileName: function(dbName){ - return api.sqlite3_db_filename(affirmDbOpen(this)._pDb, dbName||"main"); - }, - /** - Compiles the given SQL and returns a prepared Stmt. This is - the only way to create new Stmt objects. Throws on error. - */ - prepare: function(sql){ - affirmDbOpen(this); - setValue(pPtrArg,0,"i32"); - this.checkRc(api.sqlite3_prepare_v2(this._pDb, sql, -1, pPtrArg, null)); - const pStmt = getValue(pPtrArg, "i32"); - if(!pStmt) toss("Empty SQL is not permitted."); - const stmt = new Stmt(this, pStmt, BindTypes); - this._statements[pStmt] = stmt; - return stmt; - }, - /** - This function works like execMulti(), and takes most of the - same arguments, but is more efficient (performs much less - work) when the input SQL is only a single statement. If - passed a multi-statement SQL, it only processes the first - one. - - This function supports the following additional options not - supported by execMulti(): - - - .multi: if true, this function acts as a proxy for - execMulti() and behaves identically to that function. - - - .resultRows: if this is an array, each row of the result - set (if any) is appended to it in the format specified - for the `rowMode` property, with the exception that the - only legal values for `rowMode` in this case are 'array' - or 'object', neither of which is the default. It is legal - to use both `resultRows` and `callback`, but `resultRows` - is likely much simpler to use for small data sets and can - be used over a WebWorker-style message interface. - - - .columnNames: if this is an array and the query has - result columns, the array is passed to - Stmt.getColumnNames() to append the column names to it - (regardless of whether the query produces any result - rows). If the query has no result columns, this value is - unchanged. - - The following options to execMulti() are _not_ supported by - this method (they are simply ignored): - - - .saveSql - */ - exec: function(/*(sql [,optionsObj]) or (optionsObj)*/){ - affirmDbOpen(this); - const arg = parseExecArgs(arguments); - if(!arg.sql) return this; - else if(arg.opt.multi){ - return this.execMulti(arg, undefined, BindTypes); - } - const opt = arg.opt; - let stmt, rowTarget; - try { - if(Array.isArray(opt.resultRows)){ - if(opt.rowMode!=='array' && opt.rowMode!=='object'){ - toss("Invalid rowMode for resultRows array: must", - "be one of 'array' or 'object'."); - } - rowTarget = opt.resultRows; - } - stmt = this.prepare(arg.sql); - if(stmt.columnCount && Array.isArray(opt.columnNames)){ - stmt.getColumnNames(opt.columnNames); - } - if(opt.bind) stmt.bind(opt.bind); - if(opt.callback || rowTarget){ - while(stmt.step()){ - const row = arg.cbArg(stmt); - if(rowTarget) rowTarget.push(row); - if(opt.callback){ - stmt._isLocked = true; - opt.callback(row, stmt); - stmt._isLocked = false; - } - } - }else{ - stmt.step(); - } - }finally{ - if(stmt){ - delete stmt._isLocked; - stmt.finalize(); - } - } - return this; - - }/*exec()*/, - /** - Executes one or more SQL statements in the form of a single - string. Its arguments must be either (sql,optionsObject) or - (optionsObject). In the latter case, optionsObject.sql - must contain the SQL to execute. Returns this - object. Throws on error. - - If no SQL is provided, or a non-string is provided, an - exception is triggered. Empty SQL, on the other hand, is - simply a no-op. - - The optional options object may contain any of the following - properties: - - - .sql = the SQL to run (unless it's provided as the first - argument). - - - .bind = a single value valid as an argument for - Stmt.bind(). This is ONLY applied to the FIRST non-empty - statement in the SQL which has any bindable - parameters. (Empty statements are skipped entirely.) - - - .callback = a function which gets called for each row of - the FIRST statement in the SQL (if it has any result - rows). The second argument passed to the callback is - always the current Stmt object (so that the caller - may collect column names, or similar). The first - argument passed to the callback defaults to the current - Stmt object but may be changed with ... - - - .rowMode = a string describing what type of argument - should be passed as the first argument to the callback. A - value of 'object' causes the results of `stmt.get({})` to - be passed to the object. A value of 'array' causes the - results of `stmt.get([])` to be passed to the callback. - A value of 'stmt' is equivalent to the default, passing - the current Stmt to the callback (noting that it's always - passed as the 2nd argument). Any other value triggers an - exception. - - - saveSql = an optional array. If set, the SQL of each - executed statement is appended to this array before the - statement is executed (but after it is prepared - we - don't have the string until after that). Empty SQL - statements are elided. - - See also the exec() method, which is a close cousin of this - one. - - ACHTUNG #1: The callback MUST NOT modify the Stmt - object. Calling any of the Stmt.get() variants, - Stmt.getColumnName(), or similar, is legal, but calling - step() or finalize() is not. Routines which are illegal - in this context will trigger an exception. - - ACHTUNG #2: The semantics of the `bind` and `callback` - options may well change or those options may be removed - altogether for this function (but retained for exec()). - Generally speaking, neither bind parameters nor a callback - are generically useful when executing multi-statement SQL. - */ - execMulti: function(/*(sql [,obj]) || (obj)*/){ - affirmDbOpen(this); - const arg = (BindTypes===arguments[2] - /* ^^^ Being passed on from exec() */ - ? arguments[0] : parseExecArgs(arguments)); - if(!arg.sql) return this; - const opt = arg.opt; - const stack = stackSave(); - let stmt; - let bind = opt.bind; - let rowMode = ( - (opt.callback && opt.rowMode) - ? opt.rowMode : false); - try{ - let pSql = SQM.allocateUTF8OnStack(arg.sql) - const pzTail = stackAlloc(4); - while(getValue(pSql, "i8")){ - setValue(pPtrArg, 0, "i32"); - setValue(pzTail, 0, "i32"); - this.checkRc(api.sqlite3_prepare_v2_sqlptr( - this._pDb, pSql, -1, pPtrArg, pzTail - )); - const pStmt = getValue(pPtrArg, "i32"); - pSql = getValue(pzTail, "i32"); - if(!pStmt) continue; - if(opt.saveSql){ - opt.saveSql.push(api.sqlite3_sql(pStmt).trim()); - } - stmt = new Stmt(this, pStmt, BindTypes); - if(bind && stmt.parameterCount){ - stmt.bind(bind); - bind = null; - } - if(opt.callback && null!==rowMode){ - while(stmt.step()){ - stmt._isLocked = true; - callback(arg.cbArg(stmt), stmt); - stmt._isLocked = false; - } - rowMode = null; - }else{ - // Do we need to while(stmt.step()){} here? - stmt.step(); - } - stmt.finalize(); - stmt = null; - } - }finally{ - if(stmt){ - delete stmt._isLocked; - stmt.finalize(); - } - stackRestore(stack); - } - return this; - }/*execMulti()*/, - /** - Creates a new scalar UDF (User-Defined Function) which is - accessible via SQL code. This function may be called in any - of the following forms: - - - (name, function) - - (name, function, optionsObject) - - (name, optionsObject) - - (optionsObject) - - In the final two cases, the function must be defined as the - 'callback' property of the options object. In the final - case, the function's name must be the 'name' property. - - This can only be used to create scalar functions, not - aggregate or window functions. UDFs cannot be removed from - a DB handle after they're added. - - On success, returns this object. Throws on error. - - When called from SQL, arguments to the UDF, and its result, - will be converted between JS and SQL with as much fidelity - as is feasible, triggering an exception if a type - conversion cannot be determined. Some freedom is afforded - to numeric conversions due to friction between the JS and C - worlds: integers which are larger than 32 bits will be - treated as doubles, as JS does not support 64-bit integers - and it is (as of this writing) illegal to use WASM - functions which take or return 64-bit integers from JS. - - The optional options object may contain flags to modify how - the function is defined: - - - .arity: the number of arguments which SQL calls to this - function expect or require. The default value is the - callback's length property (i.e. the number of declared - parameters it has). A value of -1 means that the function - is variadic and may accept any number of arguments, up to - sqlite3's compile-time limits. sqlite3 will enforce the - argument count if is zero or greater. - - The following properties correspond to flags documented at: - - https://sqlite.org/c3ref/create_function.html - - - .deterministic = SQLITE_DETERMINISTIC - - .directOnly = SQLITE_DIRECTONLY - - .innocuous = SQLITE_INNOCUOUS - - - Maintenance reminder: the ability to add new - WASM-accessible functions to the runtime requires that the - WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH` - flag. - */ - createFunction: function f(name, callback,opt){ - switch(arguments.length){ - case 1: /* (optionsObject) */ - opt = name; - name = opt.name; - callback = opt.callback; - break; - case 2: /* (name, callback|optionsObject) */ - if(!(callback instanceof Function)){ - opt = callback; - callback = opt.callback; - } - break; - default: break; - } - if(!opt) opt = {}; - if(!(callback instanceof Function)){ - toss("Invalid arguments: expecting a callback function."); - }else if('string' !== typeof name){ - toss("Invalid arguments: missing function name."); - } - if(!f._extractArgs){ - /* Static init */ - f._extractArgs = function(argc, pArgv){ - let i, pVal, valType, arg; - const tgt = []; - for(i = 0; i < argc; ++i){ - pVal = getValue(pArgv + (4 * i), "i32"); - valType = api.sqlite3_value_type(pVal); - switch(valType){ - case api.SQLITE_INTEGER: - case api.SQLITE_FLOAT: - arg = api.sqlite3_value_double(pVal); - break; - case SQLITE_TEXT: - arg = api.sqlite3_value_text(pVal); - break; - case SQLITE_BLOB:{ - const n = api.sqlite3_value_bytes(ptr); - const pBlob = api.sqlite3_value_blob(ptr); - arg = new Uint8Array(n); - let i; - for(i = 0; i < n; ++i) arg[i] = HEAP8[pBlob+i]; - break; - } - default: - arg = null; break; - } - tgt.push(arg); - } - return tgt; - }/*_extractArgs()*/; - f._setResult = function(pCx, val){ - switch(typeof val) { - case 'boolean': - api.sqlite3_result_int(pCx, val ? 1 : 0); - break; - case 'number': { - (isInt32(val) - ? api.sqlite3_result_int - : api.sqlite3_result_double)(pCx, val); - break; - } - case 'string': - api.sqlite3_result_text(pCx, val, -1, - -1/*==SQLITE_TRANSIENT*/); - break; - case 'object': - if(null===val) { - api.sqlite3_result_null(pCx); - break; - }else if(undefined!==val.length){ - const pBlob = - SQM.allocate(val, SQM.ALLOC_NORMAL); - api.sqlite3_result_blob(pCx, pBlob, val.length, -1/*==SQLITE_TRANSIENT*/); - SQM._free(blobptr); - break; - } - // else fall through - default: - toss("Don't not how to handle this UDF result value:",val); - }; - }/*_setResult()*/; - }/*static init*/ - const wrapper = function(pCx, argc, pArgv){ - try{ - f._setResult(pCx, callback.apply(null, f._extractArgs(argc, pArgv))); - }catch(e){ - api.sqlite3_result_error(pCx, e.message, -1); - } - }; - const pUdf = SQM.addFunction(wrapper, "viii"); - let fFlags = 0; - if(getOwnOption(opt, 'deterministic')) fFlags |= api.SQLITE_DETERMINISTIC; - if(getOwnOption(opt, 'directOnly')) fFlags |= api.SQLITE_DIRECTONLY; - if(getOwnOption(opt, 'innocuous')) fFlags |= api.SQLITE_INNOCUOUS; - name = name.toLowerCase(); - try { - this.checkRc(api.sqlite3_create_function_v2( - this._pDb, name, - (opt.hasOwnProperty('arity') ? +opt.arity : callback.length), - api.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf, - null/*xStep*/, null/*xFinal*/, null/*xDestroy*/)); - }catch(e){ - SQM.removeFunction(pUdf); - throw e; - } - if(this._udfs.hasOwnProperty(name)){ - SQM.removeFunction(this._udfs[name]); - } - this._udfs[name] = pUdf; - return this; - }/*createFunction()*/, - /** - Prepares the given SQL, step()s it one time, and returns - the value of the first result column. If it has no results, - undefined is returned. If passed a second argument, it is - treated like an argument to Stmt.bind(), so may be any type - supported by that function. Throws on error (e.g. malformed - SQL). - */ - selectValue: function(sql,bind){ - let stmt, rc; - try { - stmt = this.prepare(sql).bind(bind); - if(stmt.step()) rc = stmt.get(0); - }finally{ - if(stmt) stmt.finalize(); - } - return rc; - }, - - /** - Exports a copy of this db's file as a Uint8Array and - returns it. It is technically not legal to call this while - any prepared statement are currently active because, - depending on the platform, it might not be legal to read - the db while a statement is locking it. Throws if this db - is not open or has any opened statements. - - The resulting buffer can be passed to this class's - constructor to restore the DB. - - Maintenance reminder: the corresponding sql.js impl of this - feature closes the current db, finalizing any active - statements and (seemingly unnecessarily) destroys any UDFs, - copies the file, and then re-opens it (without restoring - the UDFs). Those gymnastics are not necessary on the tested - platform but might be necessary on others. Because of that - eventuality, this interface currently enforces that no - statements are active when this is run. It will throw if - any are. - */ - exportBinaryImage: function(){ - affirmDbOpen(this); - if(Object.keys(this._statements).length){ - toss("Cannot export with prepared statements active!", - "finalize() all statements and try again."); - } - return FS.readFile(this.filename, {encoding:"binary"}); - } - }/*DB.prototype*/; - - - /** Throws if the given Stmt has been finalized, else stmt is - returned. */ - const affirmStmtOpen = function(stmt){ - if(!stmt._pStmt) toss("Stmt has been closed."); - return stmt; - }; - - /** Returns an opaque truthy value from the BindTypes - enum if v's type is a valid bindable type, else - returns a falsy value. As a special case, a value of - undefined is treated as a bind type of null. */ - const isSupportedBindType = function(v){ - let t = BindTypes[(null===v||undefined===v) ? 'null' : typeof v]; - switch(t){ - case BindTypes.boolean: - case BindTypes.null: - case BindTypes.number: - case BindTypes.string: - return t; - default: - if(v instanceof Uint8Array) return BindTypes.blob; - return undefined; - } - }; - - /** - If isSupportedBindType(v) returns a truthy value, this - function returns that value, else it throws. - */ - const affirmSupportedBindType = function(v){ - return isSupportedBindType(v) || toss("Unsupport bind() argument type."); - }; - - /** - If key is a number and within range of stmt's bound parameter - count, key is returned. - - If key is not a number then it is checked against named - parameters. If a match is found, its index is returned. - - Else it throws. - */ - const affirmParamIndex = function(stmt,key){ - const n = ('number'===typeof key) - ? key : api.sqlite3_bind_parameter_index(stmt._pStmt, key); - if(0===n || (n===key && (n!==(n|0)/*floating point*/))){ - toss("Invalid bind() parameter name: "+key); - } - else if(n<1 || n>stmt.parameterCount) toss("Bind index",key,"is out of range."); - return n; - }; - - /** Throws if ndx is not an integer or if it is out of range - for stmt.columnCount, else returns stmt. - - Reminder: this will also fail after the statement is finalized - but the resulting error will be about an out-of-bounds column - index. - */ - const affirmColIndex = function(stmt,ndx){ - if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){ - toss("Column index",ndx,"is out of range."); - } - return stmt; - }; - - /** - If stmt._isLocked is truthy, this throws an exception - complaining that the 2nd argument (an operation name, - e.g. "bind()") is not legal while the statement is "locked". - Locking happens before an exec()-like callback is passed a - statement, to ensure that the callback does not mutate or - finalize the statement. If it does not throw, it returns stmt. - */ - const affirmUnlocked = function(stmt,currentOpName){ - if(stmt._isLocked){ - toss("Operation is illegal when statement is locked:",currentOpName); - } - return stmt; - }; - - /** - Binds a single bound parameter value on the given stmt at the - given index (numeric or named) using the given bindType (see - the BindTypes enum) and value. Throws on error. Returns stmt on - success. - */ - const bindOne = function f(stmt,ndx,bindType,val){ - affirmUnlocked(stmt, 'bind()'); - if(!f._){ - f._ = { - string: function(stmt, ndx, val, asBlob){ - const bytes = intArrayFromString(val,true); - const pStr = SQM.allocate(bytes, ALLOC_NORMAL); - stmt._allocs.push(pStr); - const func = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text; - return func(stmt._pStmt, ndx, pStr, bytes.length, 0); - } - }; - } - affirmSupportedBindType(val); - ndx = affirmParamIndex(stmt,ndx); - let rc = 0; - switch((null===val || undefined===val) ? BindTypes.null : bindType){ - case BindTypes.null: - rc = api.sqlite3_bind_null(stmt._pStmt, ndx); - break; - case BindTypes.string:{ - rc = f._.string(stmt, ndx, val, false); - break; - } - case BindTypes.number: { - const m = (isInt32(val) - ? api.sqlite3_bind_int - /*It's illegal to bind a 64-bit int - from here*/ - : api.sqlite3_bind_double); - rc = m(stmt._pStmt, ndx, val); - break; - } - case BindTypes.boolean: - rc = api.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0); - break; - case BindTypes.blob: { - if('string'===typeof val){ - rc = f._.string(stmt, ndx, val, true); - }else{ - const len = val.length; - if(undefined===len){ - toss("Binding a value as a blob requires", - "that it have a length member."); - } - const pBlob = SQM.allocate(val, ALLOC_NORMAL); - stmt._allocs.push(pBlob); - rc = api.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0); - } - } - default: toss("Unsupported bind() argument type."); - } - if(rc) stmt.db.checkRc(rc); - return stmt; - }; - - /** Frees any memory explicitly allocated for the given - Stmt object. Returns stmt. */ - const freeBindMemory = function(stmt){ - let m; - while(undefined !== (m = stmt._allocs.pop())){ - SQM._free(m); - } - return stmt; - }; - - Stmt.prototype = { - /** - "Finalizes" this statement. This is a no-op if the - statement has already been finalizes. Returns - undefined. Most methods in this class will throw if called - after this is. - */ - finalize: function(){ - if(this._pStmt){ - affirmUnlocked(this,'finalize()'); - freeBindMemory(this); - delete this.db._statements[this._pStmt]; - api.sqlite3_finalize(this._pStmt); - delete this.columnCount; - delete this.parameterCount; - delete this._pStmt; - delete this.db; - delete this._isLocked; - } - }, - /** Clears all bound values. Returns this object. - Throws if this statement has been finalized. */ - clearBindings: function(){ - freeBindMemory( - affirmUnlocked(affirmStmtOpen(this), 'clearBindings()') - ); - api.sqlite3_clear_bindings(this._pStmt); - this._mayGet = false; - return this; - }, - /** - Resets this statement so that it may be step()ed again - from the beginning. Returns this object. Throws if this - statement has been finalized. - - If passed a truthy argument then this.clearBindings() is - also called, otherwise any existing bindings, along with - any memory allocated for them, are retained. - */ - reset: function(alsoClearBinds){ - affirmUnlocked(this,'reset()'); - if(alsoClearBinds) this.clearBindings(); - api.sqlite3_reset(affirmStmtOpen(this)._pStmt); - this._mayGet = false; - return this; - }, - /** - Binds one or more values to its bindable parameters. It - accepts 1 or 2 arguments: - - If passed a single argument, it must be either an array, an - object, or a value of a bindable type (see below). - - If passed 2 arguments, the first one is the 1-based bind - index or bindable parameter name and the second one must be - a value of a bindable type. - - Bindable value types: - - - null is bound as NULL. - - - undefined as a standalone value is a no-op intended to - simplify certain client-side use cases: passing undefined - as a value to this function will not actually bind - anything and this function will skip confirmation that - binding is even legal. (Those semantics simplify certain - client-side uses.) Conversely, a value of undefined as an - array or object property when binding an array/object - (see below) is treated the same as null. - - - Numbers are bound as either doubles or integers: doubles - if they are larger than 32 bits, else double or int32, - depending on whether they have a fractional part. (It is, - as of this writing, illegal to call (from JS) a WASM - function which either takes or returns an int64.) - Booleans are bound as integer 0 or 1. It is not expected - the distinction of binding doubles which have no - fractional parts is integers is significant for the - majority of clients due to sqlite3's data typing - model. This API does not currently support the BigInt - type. - - - Strings are bound as strings (use bindAsBlob() to force - blob binding). - - - Uint8Array instances are bound as blobs. - - If passed an array, each element of the array is bound at - the parameter index equal to the array index plus 1 - (because arrays are 0-based but binding is 1-based). - - If passed an object, each object key is treated as a - bindable parameter name. The object keys _must_ match any - bindable parameter names, including any `$`, `@`, or `:` - prefix. Because `$` is a legal identifier chararacter in - JavaScript, that is the suggested prefix for bindable - parameters. - - It returns this object on success and throws on - error. Errors include: - - - Any bind index is out of range, a named bind parameter - does not match, or this statement has no bindable - parameters. - - - Any value to bind is of an unsupported type. - - - Passed no arguments or more than two. - - - The statement has been finalized. - */ - bind: function(/*[ndx,] arg*/){ - affirmStmtOpen(this); - let ndx, arg; - switch(arguments.length){ - case 1: ndx = 1; arg = arguments[0]; break; - case 2: ndx = arguments[0]; arg = arguments[1]; break; - default: toss("Invalid bind() arguments."); - } - if(undefined===arg){ - /* It might seem intuitive to bind undefined as NULL - but this approach simplifies certain client-side - uses when passing on arguments between 2+ levels of - functions. */ - return this; - }else if(!this.parameterCount){ - toss("This statement has no bindable parameters."); - } - this._mayGet = false; - if(null===arg){ - /* bind NULL */ - return bindOne(this, ndx, BindTypes.null, arg); - } - else if(Array.isArray(arg)){ - /* bind each entry by index */ - if(1!==arguments.length){ - toss("When binding an array, an index argument is not permitted."); - } - arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); - return this; - } - else if('object'===typeof arg/*null was checked above*/){ - /* bind by name */ - if(1!==arguments.length){ - toss("When binding an object, an index argument is not permitted."); - } - Object.keys(arg) - .forEach(k=>bindOne(this, k, - affirmSupportedBindType(arg[k]), - arg[k])); - return this; - }else{ - return bindOne(this, ndx, - affirmSupportedBindType(arg), arg); - } - toss("Should not reach this point."); - }, - /** - Special case of bind() which binds the given value - using the BLOB binding mechanism instead of the default - selected one for the value. The ndx may be a numbered - or named bind index. The value must be of type string, - Uint8Array, or null/undefined (both treated as null). - - If passed a single argument, a bind index of 1 is assumed. - */ - bindAsBlob: function(ndx,arg){ - affirmStmtOpen(this); - if(1===arguments.length){ - ndx = 1; - arg = arguments[0]; - } - const t = affirmSupportedBindType(arg); - if(BindTypes.string !== t && BindTypes.blob !== t - && BindTypes.null !== t){ - toss("Invalid value type for bindAsBlob()"); - } - this._mayGet = false; - return bindOne(this, ndx, BindTypes.blob, arg); - }, - /** - Steps the statement one time. If the result indicates that - a row of data is available, true is returned. If no row of - data is available, false is returned. Throws on error. - */ - step: function(){ - affirmUnlocked(this, 'step()'); - const rc = api.sqlite3_step(affirmStmtOpen(this)._pStmt); - switch(rc){ - case api.SQLITE_DONE: return this._mayGet = false; - case api.SQLITE_ROW: return this._mayGet = true; - default: - this._mayGet = false; - console.warn("sqlite3_step() rc=",rc,"SQL =", - api.sqlite3_sql(this._pStmt)); - this.db.checkRc(rc); - }; - }, - /** - Fetches the value from the given 0-based column index of - the current data row, throwing if index is out of range. - - Requires that step() has just returned a truthy value, else - an exception is thrown. - - By default it will determine the data type of the result - automatically. If passed a second arugment, it must be one - of the enumeration values for sqlite3 types, which are - defined as members of the sqlite3 module: SQLITE_INTEGER, - SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value, - except for undefined, will trigger an exception. Passing - undefined is the same as not passing a value. It is legal - to, e.g., fetch an integer value as a string, in which case - sqlite3 will convert the value to a string. - - If ndx is an array, this function behaves a differently: it - assigns the indexes of the array, from 0 to the number of - result columns, to the values of the corresponding column, - and returns that array. - - If ndx is a plain object, this function behaves even - differentlier: it assigns the properties of the object to - the values of their corresponding result columns. - - Blobs are returned as Uint8Array instances. - - Potential TODO: add type ID SQLITE_JSON, which fetches the - result as a string and passes it (if it's not null) to - JSON.parse(), returning the result of that. Until then, - getJSON() can be used for that. - */ - get: function(ndx,asType){ - if(!affirmStmtOpen(this)._mayGet){ - toss("Stmt.step() has not (recently) returned true."); - } - if(Array.isArray(ndx)){ - let i = 0; - while(i32bits */ - } - case api.SQLITE_FLOAT: - return api.sqlite3_column_double(this._pStmt, ndx); - case api.SQLITE_TEXT: - return api.sqlite3_column_text(this._pStmt, ndx); - case api.SQLITE_BLOB: { - const n = api.sqlite3_column_bytes(this._pStmt, ndx); - const ptr = api.sqlite3_column_blob(this._pStmt, ndx); - const rc = new Uint8Array(n); - for(let i = 0; i < n; ++i) rc[i] = HEAP8[ptr + i]; - if(n && this.db._blobXfer instanceof Array){ - /* This is an optimization soley for the - Worker-based API. These values will be - transfered to the main thread directly - instead of being copied. */ - this.db._blobXfer.push(rc.buffer); - } - return rc; - } - default: toss("Don't know how to translate", - "type of result column #"+ndx+"."); - } - abort("Not reached."); - }, - /** Equivalent to get(ndx) but coerces the result to an - integer. */ - getInt: function(ndx){return this.get(ndx,api.SQLITE_INTEGER)}, - /** Equivalent to get(ndx) but coerces the result to a - float. */ - getFloat: function(ndx){return this.get(ndx,api.SQLITE_FLOAT)}, - /** Equivalent to get(ndx) but coerces the result to a - string. */ - getString: function(ndx){return this.get(ndx,api.SQLITE_TEXT)}, - /** Equivalent to get(ndx) but coerces the result to a - Uint8Array. */ - getBlob: function(ndx){return this.get(ndx,api.SQLITE_BLOB)}, - /** - A convenience wrapper around get() which fetches the value - as a string and then, if it is not null, passes it to - JSON.parse(), returning that result. Throws if parsing - fails. If the result is null, null is returned. An empty - string, on the other hand, will trigger an exception. - */ - getJSON: function(ndx){ - const s = this.get(ndx, api.SQLITE_STRING); - return null===s ? s : JSON.parse(s); - }, - /** - Returns the result column name of the given index, or - throws if index is out of bounds or this statement has been - finalized. This can be used without having run step() - first. - */ - getColumnName: function(ndx){ - return api.sqlite3_column_name( - affirmColIndex(affirmStmtOpen(this),ndx)._pStmt, ndx - ); - }, - /** - If this statement potentially has result columns, this - function returns an array of all such names. If passed an - array, it is used as the target and all names are appended - to it. Returns the target array. Throws if this statement - cannot have result columns. This object's columnCount member - holds the number of columns. - */ - getColumnNames: function(tgt){ - affirmColIndex(affirmStmtOpen(this),0); - if(!tgt) tgt = []; - for(let i = 0; i < this.columnCount; ++i){ - tgt.push(api.sqlite3_column_name(this._pStmt, i)); - } - return tgt; - }, - /** - If this statement has named bindable parameters and the - given name matches one, its 1-based bind index is - returned. If no match is found, 0 is returned. If it has no - bindable parameters, the undefined value is returned. - */ - getParamIndex: function(name){ - return (affirmStmtOpen(this).parameterCount - ? api.sqlite3_bind_parameter_index(this._pStmt, name) - : undefined); - } - }/*Stmt.prototype*/; - - /** OO binding's namespace. */ - const SQLite3 = { - version: { - lib: api.sqlite3_libversion(), - ooApi: "0.0.1" - }, - DB, - Stmt, - /** - Reports info about compile-time options. It has several - distinct uses: - - If optName is an array then it is expected to be a list of - compilation options and this function returns an object - which maps each such option to true or false, indicating - whether or not the given option was included in this - build. That object is returned. - - If optName is an object, its keys are expected to be - compilation options and this function sets each entry to - true or false. That object is returned. - - If passed no arguments then it returns an object mapping - all known compilation options to their compile-time values, - or boolean true if they are defined with no value. - - In all other cases it returns true if the given option was - active when when compiling the sqlite3 module, else false. - - Compile-time option names may optionally include their - "SQLITE_" prefix. When it returns an object of all options, - the prefix is elided. - */ - compileOptionUsed: function f(optName){ - if(!arguments.length){ - if(!f._opt){ - f._rx = /^([^=]+)=(.+)/; - f._rxInt = /^-?\d+$/; - f._opt = function(opt, rv){ - const m = f._rx.exec(opt); - rv[0] = (m ? m[1] : opt); - rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; - }; - } - const rc = {}, ov = [0,0]; - let i = 0, k; - while((k = api.sqlite3_compileoption_get(i++))){ - f._opt(k,ov); - rc[ov[0]] = ov[1]; - } - return rc; - } - else if(Array.isArray(optName)){ - const rc = {}; - optName.forEach((v)=>{ - rc[v] = api.sqlite3_compileoption_used(v); - }); - return rc; - } - else if('object' === typeof optName){ - Object.keys(optName).forEach((k)=> { - optName[k] = api.sqlite3_compileoption_used(k); - }); - return optName; - } - return ( - 'string'===typeof optName - ) ? !!api.sqlite3_compileoption_used(optName) : false; - } - }/*SQLite3 object*/; - - namespace.sqlite3 = { - api: api, - SQLite3 - }; - - if(self === self.window){ - /* This is running in the main window thread, so we're done. */ - setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0); - return; - } - /****************************************************************** - End of main window thread. What follows is only intended for use - in Worker threads. - ******************************************************************/ - - /* - UNDER CONSTRUCTION - - We need an API which can proxy the DB API via a Worker message - interface. The primary quirky factor in such an API is that we - cannot pass callback functions between the window thread and a - worker thread, so we have to receive all db results via - asynchronous message-passing. - - Certain important considerations here include: - - - Support only one db connection or multiple? The former is far - easier, but there's always going to be a user out there who - wants to juggle six database handles at once. Do we add that - complexity or tell such users to write their own code using - the provided lower-level APIs? - - - Fetching multiple results: do we pass them on as a series of - messages, with start/end messages on either end, or do we - collect all results and bundle them back in a single message? - The former is, generically speaking, more memory-efficient but - the latter far easier to implement in this environment. The - latter is untennable for large data sets. Despite a web page - hypothetically being a relatively limited environment, there - will always be those users who feel that they should/need to - be able to work with multi-hundred-meg (or larger) blobs, and - passing around arrays of those may quickly exhaust the JS - engine's memory. - - TODOs include, but are not limited to: - - - The ability to manage multiple DB handles. This can - potentially be done via a simple mapping of DB.filename or - DB._pDb (`sqlite3*` handle) to DB objects. The open() - interface would need to provide an ID (probably DB._pDb) back - to the user which can optionally be passed as an argument to - the other APIs (they'd default to the first-opened DB, for - ease of use). Client-side usability of this feature would - benefit from making another wrapper class (or a singleton) - available to the main thread, with that object proxying all(?) - communication with the worker. - - - Revisit how virtual files are managed. We currently delete DBs - from the virtual filesystem when we close them, for the sake - of saving memory (the VFS lives in RAM). Supporting multiple - DBs may require that we give up that habit. Similarly, fully - supporting ATTACH, where a user can upload multiple DBs and - ATTACH them, also requires the that we manage the VFS entries - better. As of this writing, ATTACH will fail fatally in the - fiddle app (but not the lower-level APIs) because it runs in - safe mode, where ATTACH is disabled. - */ - - /** - Helper for managing Worker-level state. - */ - const wState = { - db: undefined, - open: function(arg){ - if(!arg && this.db) return this.db; - else if(this.db) this.db.close(); - return this.db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg)); - }, - close: function(alsoUnlink){ - if(this.db){ - this.db.close(alsoUnlink); - this.db = undefined; - } - }, - affirmOpen: function(){ - return this.db || toss("DB is not opened."); - }, - post: function(type,data,xferList){ - if(xferList){ - self.postMessage({type, data},xferList); - xferList.length = 0; - }else{ - self.postMessage({type, data}); - } - } - }; - - /** - A level of "organizational abstraction" for the Worker - API. Each method in this object must map directly to a Worker - message type key. The onmessage() dispatcher attempts to - dispatch all inbound messages to a method of this object, - passing it the event.data part of the inbound event object. All - methods must return a plain Object containing any response - state, which the dispatcher may amend. All methods must throw - on error. - */ - const wMsgHandler = { - xfer: [/*Temp holder for "transferable" postMessage() state.*/], - /** - Proxy for DB.exec() which expects a single argument of type - string (SQL to execute) or an options object in the form - expected by exec(). The notable differences from exec() - include: - - - The default value for options.rowMode is 'array' because - the normal default cannot cross the window/Worker boundary. - - - A function-type options.callback property cannot cross - the window/Worker boundary, so is not useful here. If - options.callback is a string then it is assumed to be a - message type key, in which case a callback function will be - applied which posts each row result via: - - postMessage({type: thatKeyType, data: theRow}) - - And, at the end of the result set (whether or not any - result rows were produced), it will post an identical - message with data:null to alert the caller than the result - set is completed. - - The callback proxy must not recurse into this interface, or - results are undefined. (It hypothetically cannot recurse - because an exec() call will be tying up the Worker thread, - causing any recursion attempt to wait until the first - exec() is completed.) - - The response is the input options object (or a synthesized - one if passed only a string), noting that - options.resultRows and options.columnNames may be populated - by the call to exec(). - - This opens/creates the Worker's db if needed. - */ - exec: function(ev){ - const opt = ( - 'string'===typeof ev.data - ) ? {sql: ev.data} : (ev.data || {}); - if(!opt.rowMode){ - /* Since the default rowMode of 'stmt' is not useful - for the Worker interface, we'll default to - something else. */ - opt.rowMode = 'array'; - }else if('stmt'===opt.rowMode){ - toss("Invalid rowMode for exec(): stmt mode", - "does not work in the Worker API."); - } - const db = wState.open(); - if(opt.callback || opt.resultRows instanceof Array){ - // Part of a copy-avoidance optimization for blobs - db._blobXfer = this.xfer; - } - const callbackMsgType = opt.callback; - if('string' === typeof callbackMsgType){ - const that = this; - opt.callback = - (row)=>wState.post(callbackMsgType,row,this.xfer); - } - try { - db.exec(opt); - if(opt.callback instanceof Function){ - opt.callback = callbackMsgType; - wState.post(callbackMsgType, null); - } - }finally{ - delete db._blobXfer; - if('string'===typeof callbackMsgType){ - opt.callback = callbackMsgType; - } - } - return opt; - }/*exec()*/, - /** - Proxy for DB.exportBinaryImage(). Throws if the db has not - been opened. Response is an object: - - { - buffer: Uint8Array (db file contents), - filename: the current db filename, - mimetype: string - } - */ - export: function(ev){ - const db = wState.affirmOpen(); - const response = { - buffer: db.exportBinaryImage(), - filename: db.filename, - mimetype: 'application/x-sqlite3' - }; - this.xfer.push(response.buffer.buffer); - return response; - }/*export()*/, - /** - Proxy for the DB constructor. Expects to be passed a single - object or a falsy value to use defaults. The object may - have a filename property to name the db file (see the DB - constructor for peculiarities and transformations) and/or a - buffer property (a Uint8Array holding a complete database - file's contents). The response is an object: - - { - filename: db filename (possibly differing from the input) - } - - If the Worker's db is currently opened, this call closes it - before proceeding. - */ - open: function(ev){ - wState.close(/*true???*/); - const args = [], data = (ev.data || {}); - if(data.filename) args.push(data.filename); - if(data.buffer){ - args.push(data.buffer); - this.xfer.push(data.buffer.buffer); - } - const db = wState.open(args); - return {filename: db.filename}; - }, - /** - Proxy for DB.close(). If ev.data may either be a boolean or - an object with an `unlink` property. If that value is - truthy then the db file (if the db is currently open) will - be unlinked from the virtual filesystem, else it will be - kept intact. The response object is: - - {filename: db filename _if_ the db is is opened when this - is called, else the undefined value - } - */ - close: function(ev){ - const response = { - filename: wState.db && wState.db.filename - }; - if(wState.db){ - wState.close(!!(ev.data && 'object'===typeof ev.data) - ? ev.data.unlink : ev.data); - } - return response; - } - }/*wMsgHandler*/; - - /** - UNDER CONSTRUCTION! - - A subset of the DB API is accessible via Worker messages in the form: - - { type: apiCommand, - data: apiArguments } - - As a rule, these commands respond with a postMessage() of their - own in the same form, but will, if needed, transform the `data` - member to an object and may add state to it. The responses - always have an object-format `data` part. If the inbound `data` - is an object which has a `messageId` property, that property is - always mirrored in the result object, for use in client-side - dispatching of these asynchronous results. Exceptions thrown - during processing result in an `error`-type event with a - payload in the form: - - { - message: error string, - errorClass: class name of the error type, - input: ev.data, - [messageId: if set in the inbound message] - } - - The individual APIs are documented in the wMsgHandler object. - */ - self.onmessage = function(ev){ - ev = ev.data; - let response, evType = ev.type; - try { - if(wMsgHandler.hasOwnProperty(evType) && - wMsgHandler[evType] instanceof Function){ - response = wMsgHandler[evType](ev); - }else{ - toss("Unknown db worker message type:",ev.type); - } - }catch(err){ - evType = 'error'; - response = { - message: err.message, - errorClass: err.name, - input: ev - }; - } - if(!response.messageId && ev.data - && 'object'===typeof ev.data && ev.data.messageId){ - response.messageId = ev.data.messageId; - } - wState.post(evType, response, wMsgHandler.xfer); - }; - - setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0); -}); diff --git a/ext/fiddle/sqlite3-worker.js b/ext/fiddle/sqlite3-worker.js deleted file mode 100644 index fe7423f4c..000000000 --- a/ext/fiddle/sqlite3-worker.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - 2022-05-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. - - *********************************************************************** - - This is a JS Worker file for the main sqlite3 api. It loads - sqlite3.js, initializes the module, and postMessage()'s a message - after the module is initialized: - - {type: 'sqlite3-api', data: 'ready'} - - This seemingly superfluous level of indirection is necessary when - loading sqlite3.js via a Worker. Loading sqlite3.js from the main - window thread elides the Worker-specific API. Instantiating a worker - with new Worker("sqlite.js") will not (cannot) call - initSqlite3Module() to initialize the module due to a - timing/order-of-operations conflict (and that symbol is not exported - in a way that a Worker loading it that way can see it). Thus JS - code wanting to load the sqlite3 Worker-specific API needs to pass - _this_ file (or equivalent) to the Worker constructor and then - listen for an event in the form shown above in order to know when - the module has completed initialization. sqlite3.js will fire a - similar event, with data:'loaded' as the final step in its loading - process. Whether or not we _really_ need both 'loaded' and 'ready' - events is unclear, but they are currently separate events primarily - for the sake of clarity in the timing of when it's okay to use the - loaded module. At the time the 'loaded' event is fired, it's - possible (but unknown and unknowable) that the emscripten-generated - module-setup infrastructure still has work to do. Thus it is - hypothesized that client code is better off waiting for the 'ready' - even before using the API. -*/ -"use strict"; -importScripts('sqlite3.js'); -initSqlite3Module().then(function(){ - setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'ready'}), 0); -}); diff --git a/ext/fiddle/testing.css b/ext/fiddle/testing.css deleted file mode 100644 index f87dbd2cf..000000000 --- a/ext/fiddle/testing.css +++ /dev/null @@ -1,31 +0,0 @@ -textarea { - font-family: monospace; -} -header { - font-size: 130%; - font-weight: bold; -} -.hidden, .initially-hidden { - position: absolute !important; - opacity: 0 !important; - pointer-events: none !important; - display: none !important; -} -fieldset.options { - font-size: 75%; -} -fieldset > legend { - padding: 0 0.5em; -} -span.labeled-input { - padding: 0.25em; - margin: 0.25em 0.5em; - border-radius: 0.25em; - white-space: nowrap; - background: #0002; -} -.center { text-align: center; } -.error { - color: red; - background-color: yellow; -} diff --git a/ext/fiddle/testing1.js b/ext/fiddle/testing1.js deleted file mode 100644 index a6f0062dc..000000000 --- a/ext/fiddle/testing1.js +++ /dev/null @@ -1,185 +0,0 @@ -/* - 2022-05-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. - - *********************************************************************** - - A basic test script for sqlite3-api.js. This file must be run in - main JS thread and sqlite3.js must have been loaded before it. -*/ -(function(){ - const T = self.SqliteTestUtil; - const log = console.log.bind(console); - - const assert = function(condition, text) { - if (!condition) { - throw new Error('Assertion failed' + (text ? ': ' + text : '')); - } - }; - - const test1 = function(db,sqlite3){ - const api = sqlite3.api; - log("Basic sanity tests..."); - T.assert(db._pDb); - let st = db.prepare("select 3 as a"); - //log("statement =",st); - T.assert(st._pStmt) - .assert(!st._mayGet) - .assert('a' === st.getColumnName(0)) - .assert(st === db._statements[st._pStmt]) - .assert(1===st.columnCount) - .assert(0===st.parameterCount) - .mustThrow(()=>st.bind(1,null)) - .assert(true===st.step()) - .assert(3 === st.get(0)) - .mustThrow(()=>st.get(1)) - .mustThrow(()=>st.get(0,~api.SQLITE_INTEGER)) - .assert(3 === st.get(0,api.SQLITE_INTEGER)) - .assert(3 === st.getInt(0)) - .assert('3' === st.get(0,api.SQLITE_TEXT)) - .assert('3' === st.getString(0)) - .assert(3.0 === st.get(0,api.SQLITE_FLOAT)) - .assert(3.0 === st.getFloat(0)) - .assert(st.get(0,api.SQLITE_BLOB) instanceof Uint8Array) - .assert(st.getBlob(0) instanceof Uint8Array) - .assert(3 === st.get([])[0]) - .assert(3 === st.get({}).a) - .assert(3 === st.getJSON(0)) - .assert(st._mayGet) - .assert(false===st.step()) - .assert(!st._mayGet) - ; - let pId = st._pStmt; - st.finalize(); - T.assert(!st._pStmt) - .assert(!db._statements[pId]); - - let list = []; - db.exec({ - sql:`CREATE TABLE t(a,b); -INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`, - multi: true, - saveSql: list, - bind: [5,6] - }); - T.assert(2 === list.length); - //log("Exec'd SQL:", list); - let counter = 0, colNames = []; - list.length = 0; - db.exec("SELECT a a, b b FROM t",{ - rowMode: 'object', - resultRows: list, - columnNames: colNames, - callback: function(row,stmt){ - ++counter; - T.assert(row.a%2 && row.a<6); - } - }); - T.assert(2 === colNames.length) - .assert('a' === colNames[0]) - .assert(3 === counter) - .assert(3 === list.length); - list.length = 0; - db.exec("SELECT a a, b b FROM t",{ - rowMode: 'array', - callback: function(row,stmt){ - ++counter; - T.assert(Array.isArray(row)) - .assert(0===row[1]%2 && row[1]<7); - } - }); - T.assert(6 === counter); - }; - - const testUDF = function(db){ - log("Testing UDF..."); - db.createFunction("foo",function(a,b){return a+b}); - T.assert(7===db.selectValue("select foo(3,4)")). - assert(5===db.selectValue("select foo(3,?)",2)). - assert(5===db.selectValue("select foo(?,?)",[1,4])). - assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5})); - db.createFunction("bar", { - arity: -1, - callback: function(){ - var rc = 0; - for(let i = 0; i < arguments.length; ++i) rc += arguments[i]; - return rc; - } - }); - - log("Testing DB::selectValue() w/ UDF..."); - T.assert(0===db.selectValue("select bar()")). - assert(1===db.selectValue("select bar(1)")). - assert(3===db.selectValue("select bar(1,2)")). - assert(-1===db.selectValue("select bar(1,2,-4)")); - - const eqApprox = function(v1,v2,factor=0.05){ - return v1>=(v2-factor) && v1<=(v2+factor); - }; - - T.assert('hi' === db.selectValue("select ?",'hi')). - assert(null===db.selectValue("select null")). - assert(null === db.selectValue("select ?",null)). - assert(null === db.selectValue("select ?",[null])). - assert(null === db.selectValue("select $a",{$a:null})). - assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))) - ; - }; - - const testAttach = function(db){ - log("Testing ATTACH..."); - db.exec({ - sql:[ - "attach 'foo.db' as foo", - "create table foo.bar(a)", - "insert into foo.bar(a) values(1),(2),(3)" - ].join(';'), - multi: true - }); - T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a')); - db.exec("detach foo"); - T.mustThrow(()=>db.exec("select * from foo.bar")); - }; - - const runTests = function(Module){ - T.assert(Module._free instanceof Function). - assert(Module.allocate instanceof Function). - assert(Module.addFunction instanceof Function). - assert(Module.removeFunction instanceof Function); - const sqlite3 = Module.sqlite3; - const api = sqlite3.api; - const oo = sqlite3.SQLite3; - console.log("Loaded module:",api.sqlite3_libversion(), - api.sqlite3_sourceid()); - log("Build options:",oo.compileOptionUsed()); - const db = new oo.DB(); - try { - log("DB:",db.filename); - [ - test1, testUDF, testAttach - ].forEach((f)=>{ - const t = T.counter; - f(db, sqlite3); - log("Test count:",T.counter - t); - }); - }finally{ - db.close(); - } - log("Total Test count:",T.counter); - }; - - initSqlite3Module(self.sqlite3TestModule).then(function(theModule){ - /** Use a timeout so that we are (hopefully) out from - under the module init stack when our setup gets - run. Just on principle, not because we _need_ to - be. */ - //console.debug("theModule =",theModule); - setTimeout(()=>runTests(theModule), 0); - }); -})(); diff --git a/ext/fiddle/testing2.js b/ext/fiddle/testing2.js deleted file mode 100644 index 4bb21b95f..000000000 --- a/ext/fiddle/testing2.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - 2022-05-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. - - *********************************************************************** - - A basic test script for sqlite3-worker.js. -*/ -(function(){ - const T = self.SqliteTestUtil; - const SW = new Worker("sqlite3-worker.js"); - /** Posts a worker message as {type:type, data:data}. */ - const wMsg = function(type,data){ - SW.postMessage({type, data}); - return SW; - }; - const log = console.log.bind(console); - const warn = console.warn.bind(console); - const error = console.error.bind(console); - - SW.onerror = function(event){ - error("onerror",event); - }; - - /** - A queue for callbacks which are to be run in response to async - DB commands. See the notes in runTests() for why we need - this. The event-handling plumbing of this file requires that - any DB command which includes a `messageId` property also have - a queued callback entry, as the existence of that property in - response payloads is how it knows whether or not to shift an - entry off of the queue. - */ - const MsgHandlerQueue = { - queue: [], - id: 0, - push: function(type,callback){ - this.queue.push(callback); - return type + '-' + (++this.id); - }, - shift: function(){ - return this.queue.shift(); - } - }; - - const testCount = ()=>log("Total test count:",T.counter); - - const runOneTest = function(eventType, eventData, callback){ - T.assert(eventData && 'object'===typeof eventData); - /* ^^^ that is for the testing and messageId-related code, not - a hard requirement of all of the Worker-exposed APIs. */ - eventData.messageId = MsgHandlerQueue.push(eventType,function(ev){ - log("runOneTest",eventType,"result",ev.data); - if(callback instanceof Function){ - callback(ev); - testCount(); - } - }); - wMsg(eventType, eventData); - }; - - /** Methods which map directly to onmessage() event.type keys. - They get passed the inbound event.data. */ - const dbMsgHandler = { - open: function(ev){ - log("open result",ev.data); - }, - exec: function(ev){ - log("exec result",ev.data); - }, - export: function(ev){ - log("exec result",ev.data); - }, - error: function(ev){ - error("ERROR from the worker:",ev.data); - }, - resultRowTest1: function f(ev){ - if(undefined === f.counter) f.counter = 0; - if(ev.data) ++f.counter; - //log("exec() result row:",ev.data); - T.assert(null===ev.data || 'number' === typeof ev.data.b); - } - }; - - const runTests = function(){ - const mustNotReach = ()=>{ - throw new Error("This is not supposed to be reached."); - }; - /** - "The problem" now is that the test results are async. We - know, however, that the messages posted to the worker will - be processed in the order they are passed to it, so we can - create a queue of callbacks to handle them. The problem - with that approach is that it's not error-handling - friendly, in that an error can cause us to bypass a result - handler queue entry. We have to perform some extra - acrobatics to account for that. - */ - runOneTest('open', {filename:'testing2.sqlite3'}, function(ev){ - //log("open result",ev); - T.assert('testing2.sqlite3'===ev.data.filename) - .assert(ev.data.messageId); - }); - runOneTest('exec',{ - sql: ["create table t(a,b)", - "insert into t(a,b) values(1,2),(3,4),(5,6)" - ].join(';'), - multi: true, - resultRows: [], columnNames: [] - }, function(ev){ - ev = ev.data; - T.assert(0===ev.resultRows.length) - .assert(0===ev.columnNames.length); - }); - runOneTest('exec',{ - sql: 'select a a, b b from t order by a', - resultRows: [], columnNames: [], - }, function(ev){ - ev = ev.data; - T.assert(3===ev.resultRows.length) - .assert(1===ev.resultRows[0][0]) - .assert(6===ev.resultRows[2][1]) - .assert(2===ev.columnNames.length) - .assert('b'===ev.columnNames[1]); - }); - runOneTest('exec',{ - sql: 'select a a, b b from t order by a', - resultRows: [], columnNames: [], - rowMode: 'object' - }, function(ev){ - ev = ev.data; - T.assert(3===ev.resultRows.length) - .assert(1===ev.resultRows[0].a) - .assert(6===ev.resultRows[2].b) - }); - runOneTest('exec',{sql:'intentional_error'}, mustNotReach); - // Ensure that the message-handler queue survives ^^^ that error... - runOneTest('exec',{ - sql:'select 1', - resultRows: [], - //rowMode: 'array', // array is the default in the Worker interface - }, function(ev){ - ev = ev.data; - T.assert(1 === ev.resultRows.length) - .assert(1 === ev.resultRows[0][0]); - }); - runOneTest('exec',{ - sql: 'select a a, b b from t order by a', - callback: 'resultRowTest1', - rowMode: 'object' - }, function(ev){ - T.assert(3===dbMsgHandler.resultRowTest1.counter); - dbMsgHandler.resultRowTest1.counter = 0; - }); - runOneTest('exec',{sql: 'delete from t where a>3'}); - runOneTest('exec',{ - sql: 'select count(a) from t', - resultRows: [] - },function(ev){ - ev = ev.data; - T.assert(1===ev.resultRows.length) - .assert(2===ev.resultRows[0][0]); - }); - runOneTest('export',{}, function(ev){ - ev = ev.data; - T.assert('string' === typeof ev.filename) - .assert(ev.buffer instanceof Uint8Array) - .assert(ev.buffer.length > 1024) - .assert('application/x-sqlite3' === ev.mimetype); - }); - - /***** close() tests must come last. *****/ - runOneTest('close',{unlink:true},function(ev){ - ev = ev.data; - T.assert('string' === typeof ev.filename); - }); - runOneTest('close',{unlink:true},function(ev){ - ev = ev.data; - T.assert(undefined === ev.filename); - }); - }; - - SW.onmessage = function(ev){ - if(!ev.data || 'object'!==typeof ev.data){ - warn("Unknown sqlite3-worker message type:",ev); - return; - } - ev = ev.data/*expecting a nested object*/; - //log("main window onmessage:",ev); - if(ev.data && ev.data.messageId){ - /* We're expecting a queued-up callback handler. */ - const f = MsgHandlerQueue.shift(); - if('error'===ev.type){ - dbMsgHandler.error(ev); - return; - } - T.assert(f instanceof Function); - f(ev); - return; - } - switch(ev.type){ - case 'sqlite3-api': - switch(ev.data){ - case 'loaded': - log("Message:",ev); return; - case 'ready': - log("Message:",ev); - self.sqlite3TestModule.setStatus(null); - setTimeout(runTests, 0); - return; - default: - warn("Unknown sqlite3-api message type:",ev); - return; - } - default: - if(dbMsgHandler.hasOwnProperty(ev.type)){ - try{dbMsgHandler[ev.type](ev);} - catch(err){ - error("Exception while handling db result message", - ev,":",err); - } - return; - } - warn("Unknown sqlite3-api message type:",ev); - } - }; - - log("Init complete, but async init bits may still be running."); -})(); diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 754f28c67..e7e7043c6 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -286,7 +286,7 @@ void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...); char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...); #define fts5BufferZero(x) sqlite3Fts5BufferZero(x) -#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,c) +#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,(i64)c) #define fts5BufferFree(a) sqlite3Fts5BufferFree(a) #define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d) #define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d) diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index d2ec9ad5a..7eca9b132 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -4087,7 +4087,9 @@ static void fts5WriteAppendRowid( fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid); }else{ assert_nc( p->rc || iRowid>pWriter->iPrevRowid ); - fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid - pWriter->iPrevRowid); + fts5BufferAppendVarint(&p->rc, &pPage->buf, + (u64)iRowid - (u64)pWriter->iPrevRowid + ); } pWriter->iPrevRowid = iRowid; pWriter->bFirstRowidInDoclist = 0; @@ -4851,7 +4853,7 @@ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ static void fts5AppendRowid( Fts5Index *p, - i64 iDelta, + u64 iDelta, Fts5Iter *pUnused, Fts5Buffer *pBuf ){ @@ -4861,7 +4863,7 @@ static void fts5AppendRowid( static void fts5AppendPoslist( Fts5Index *p, - i64 iDelta, + u64 iDelta, Fts5Iter *pMulti, Fts5Buffer *pBuf ){ @@ -4936,10 +4938,10 @@ static void fts5MergeAppendDocid( } #endif -#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \ - assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \ - fts5BufferSafeAppendVarint((pBuf), (iRowid) - (iLastRowid)); \ - (iLastRowid) = (iRowid); \ +#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \ + assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \ + fts5BufferSafeAppendVarint((pBuf), (u64)(iRowid) - (u64)(iLastRowid)); \ + (iLastRowid) = (iRowid); \ } /* @@ -5210,7 +5212,7 @@ static void fts5SetupPrefixIter( int nMerge = 1; void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); - void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*); + void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*); if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ xMerge = fts5MergeRowidLists; xAppend = fts5AppendRowid; @@ -5249,7 +5251,7 @@ static void fts5SetupPrefixIter( Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; p1->xSetOutputs(p1, pSeg); if( p1->base.nData ){ - xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist); + xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); iLastRowid = p1->base.iRowid; } } @@ -5297,7 +5299,7 @@ static void fts5SetupPrefixIter( iLastRowid = 0; } - xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist); + xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); iLastRowid = p1->base.iRowid; } @@ -6276,6 +6278,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ /* If this is a new term, query for it. Update cksum3 with the results. */ fts5TestTerm(p, &term, z, n, cksum2, &cksum3); + if( p->rc ) break; if( eDetail==FTS5_DETAIL_NONE ){ if( 0==fts5MultiIterIsEmpty(p, pIter) ){ diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 9a8b2fadb..ccdcf7b57 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -260,7 +260,7 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){ break; case FTS5_SYNC: - assert( p->ts.eState==1 ); + assert( p->ts.eState==1 || p->ts.eState==2 ); p->ts.eState = 2; break; @@ -275,21 +275,21 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){ break; case FTS5_SAVEPOINT: - assert( p->ts.eState==1 ); + assert( p->ts.eState>=1 ); assert( iSavepoint>=0 ); assert( iSavepoint>=p->ts.iSavepoint ); p->ts.iSavepoint = iSavepoint; break; case FTS5_RELEASE: - assert( p->ts.eState==1 ); + assert( p->ts.eState>=1 ); assert( iSavepoint>=0 ); assert( iSavepoint<=p->ts.iSavepoint ); p->ts.iSavepoint = iSavepoint-1; break; case FTS5_ROLLBACKTO: - assert( p->ts.eState==1 ); + assert( p->ts.eState>=1 ); assert( iSavepoint>=-1 ); /* The following assert() can fail if another vtab strikes an error ** within an xSavepoint() call then SQLite calls xRollbackTo() - without @@ -1625,7 +1625,7 @@ static int fts5UpdateMethod( int rc = SQLITE_OK; /* Return code */ /* A transaction must be open when this is called. */ - assert( pTab->ts.eState==1 ); + assert( pTab->ts.eState==1 || pTab->ts.eState==2 ); assert( pVtab->zErrMsg==0 ); assert( nArg==1 || nArg==(2+pConfig->nCol+2) ); diff --git a/ext/fts5/test/fts5ah.test b/ext/fts5/test/fts5ah.test index 24613f5c4..000435137 100644 --- a/ext/fts5/test/fts5ah.test +++ b/ext/fts5/test/fts5ah.test @@ -11,6 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this script is testing the FTS5 module. # +# TESTRUNNER: slow source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ah diff --git a/ext/fts5/test/fts5misc.test b/ext/fts5/test/fts5misc.test index e354d20e2..416b4c808 100644 --- a/ext/fts5/test/fts5misc.test +++ b/ext/fts5/test/fts5misc.test @@ -352,5 +352,96 @@ do_test 13.3 { sqlite3_errmsg db } {not an error} +#------------------------------------------------------------------------- +reset_db +db close +sqlite3 db test.db -uri 1 + +do_execsql_test 14.0 { + PRAGMA locking_mode=EXCLUSIVE; + BEGIN; + ATTACH 'file:/one?vfs=memdb' AS aux1; + ATTACH 'file:/one?vfs=memdb' AS aux2; + CREATE VIRTUAL TABLE t1 USING fts5(x); +} {exclusive} +do_catchsql_test 14.1 { + ANALYZE; +} {1 {database is locked}} +do_catchsql_test 14.2 { + COMMIT; +} {1 {database is locked}} +do_catchsql_test 14.3 { + COMMIT; +} {1 {database is locked}} +do_catchsql_test 14.4 { + ROLLBACK; +} {0 {}} + +#------------------------------------------------------------------------- +reset_db +sqlite3 db2 test.db + +do_execsql_test 15.0 { + CREATE TABLE t1(a, b); + BEGIN; + SELECT * FROM t1; +} + +do_execsql_test -db db2 15.1 { + BEGIN; + CREATE VIRTUAL TABLE x1 USING fts5(y); +} +do_test 15.2 { + list [catch { db2 eval COMMIT } msg] $msg +} {1 {database is locked}} +do_execsql_test -db db2 15.3 { + SAVEPOINT one; +} {} +do_execsql_test 15.4 END +do_test 15.4 { + list [catch { db2 eval COMMIT } msg] $msg +} {0 {}} + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +sqlite3 db2 test.db +do_execsql_test 16.0 { + + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t2(x,y); + INSERT INTO t2 VALUES(1, 2); + CREATE VIRTUAL TABLE x1 USING fts5(a); + BEGIN; + INSERT INTO x1 VALUES('abc'); + INSERT INTO t2 VALUES(3, 4); +} + +do_execsql_test -db db2 16.1 { + ATTACH 'test.db2' AS aux; + BEGIN; + SELECT * FROM t2 +} {1 2} + +do_catchsql_test 16.2 { + COMMIT; +} {1 {database is locked}} + +do_execsql_test 16.3 { + INSERT INTO x1 VALUES('def'); +} + +do_execsql_test -db db2 16.4 { + END +} + +do_execsql_test 16.5 { + COMMIT +} + +do_execsql_test -db db2 16.6 { + SELECT * FROM x1 +} {abc def} + finish_test diff --git a/ext/fts5/test/fts5ubsan.test b/ext/fts5/test/fts5ubsan.test new file mode 100644 index 000000000..2dc0aa7bd --- /dev/null +++ b/ext/fts5/test/fts5ubsan.test @@ -0,0 +1,60 @@ +# 2022 August 9 +# +# 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 test is focused on edge cases that cause ubsan errors. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5ubsan + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x); +} + +set BIG 9000000000000000000 +set SMALL -9000000000000000000 + +do_execsql_test 1.1 { + BEGIN; + INSERT INTO x1 (rowid, x) VALUES($BIG, 'aaa aba acc'); + INSERT INTO x1 (rowid, x) VALUES($SMALL, 'aaa abc acb'); + COMMIT; +} + +do_execsql_test 1.2 { + SELECT rowid, x FROM x1('ab*'); +} [list $SMALL {aaa abc acb} $BIG {aaa aba acc}] + +do_execsql_test 1.3 { + SELECT rowid, x FROM x1('ac*'); +} [list $SMALL {aaa abc acb} $BIG {aaa aba acc}] + +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x); +} + +do_execsql_test 2.1 { + INSERT INTO x1 (rowid, x) VALUES($BIG, 'aaa aba acc'); + INSERT INTO x1 (rowid, x) VALUES($SMALL, 'aaa abc acb'); +} + +do_execsql_test 2.2 { + INSERT INTO x1 (x1) VALUES('optimize'); +} + +finish_test diff --git a/ext/misc/cksumvfs.c b/ext/misc/cksumvfs.c index 8c340889f..e7c2c9d5c 100644 --- a/ext/misc/cksumvfs.c +++ b/ext/misc/cksumvfs.c @@ -47,7 +47,7 @@ ** ** sqlite3 *db; ** sqlite3_open(":memory:", &db); -** sqlite3_load_extention(db, "./cksumvfs"); +** sqlite3_load_extension(db, "./cksumvfs"); ** sqlite3_close(db); ** ** If this extension is compiled with -DSQLITE_CKSUMVFS_STATIC and diff --git a/ext/misc/csv.c b/ext/misc/csv.c index b51fbad30..870a0cf60 100644 --- a/ext/misc/csv.c +++ b/ext/misc/csv.c @@ -280,6 +280,7 @@ static char *csv_read_one_field(CsvReader *p){ } p->cTerm = (char)c; } + assert( p->z==0 || p->nnAlloc ); if( p->z ) p->z[p->n] = 0; p->bNotFirst = 1; return p->z; @@ -750,7 +751,7 @@ static int csvtabNext(sqlite3_vtab_cursor *cur){ i++; } }while( pCur->rdr.cTerm==',' ); - if( z==0 || (pCur->rdr.cTerm==EOF && inCol) ){ + if( z==0 && i==0 ){ pCur->iRowid = -1; }else{ pCur->iRowid++; @@ -811,6 +812,12 @@ static int csvtabFilter( CsvCursor *pCur = (CsvCursor*)pVtabCursor; CsvTable *pTab = (CsvTable*)pVtabCursor->pVtab; pCur->iRowid = 0; + + /* Ensure the field buffer is always allocated. Otherwise, if the + ** first field is zero bytes in size, this may be mistaken for an OOM + ** error in csvtabNext(). */ + if( csv_append(&pCur->rdr, 0) ) return SQLITE_NOMEM; + if( pCur->rdr.in==0 ){ assert( pCur->rdr.zIn==pTab->zData ); assert( pTab->iStart>=0 ); diff --git a/ext/misc/regexp.c b/ext/misc/regexp.c index 52973cc73..086ef564c 100644 --- a/ext/misc/regexp.c +++ b/ext/misc/regexp.c @@ -72,6 +72,7 @@ SQLITE_EXTENSION_INIT1 /* The end-of-input character */ #define RE_EOF 0 /* End of input */ +#define RE_START 0xfffffff /* Start of input - larger than an UTF-8 */ /* The NFA is implemented as sequence of opcodes taken from the following ** set. Each opcode has a single integer argument. @@ -93,6 +94,33 @@ SQLITE_EXTENSION_INIT1 #define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ #define RE_OP_NOTSPACE 16 /* Not a digit */ #define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ +#define RE_OP_ATSTART 18 /* Currently at the start of the string */ + +#if defined(SQLITE_DEBUG) +/* Opcode names used for symbolic debugging */ +static const char *ReOpName[] = { + "EOF", + "MATCH", + "ANY", + "ANYSTAR", + "FORK", + "GOTO", + "ACCEPT", + "CC_INC", + "CC_EXC", + "CC_VALUE", + "CC_RANGE", + "WORD", + "NOTWORD", + "DIGIT", + "NOTDIGIT", + "SPACE", + "NOTSPACE", + "BOUNDARY", + "ATSTART", +}; +#endif /* SQLITE_DEBUG */ + /* Each opcode is a "state" in the NFA */ typedef unsigned short ReStateNumber; @@ -127,7 +155,7 @@ struct ReCompiled { int *aArg; /* Arguments to each operator */ unsigned (*xNextChar)(ReInput*); /* Next character function */ unsigned char zInit[12]; /* Initial text to match */ - int nInit; /* Number of characters in zInit */ + int nInit; /* Number of bytes in zInit */ unsigned nState; /* Number of entries in aOp[] and aArg[] */ unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ }; @@ -157,7 +185,7 @@ static unsigned re_next_char(ReInput *p){ c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); p->i += 2; if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; - }else if( (c&0xf8)==0xf0 && p->i+3mx && (p->z[p->i]&0xc0)==0x80 + }else if( (c&0xf8)==0xf0 && p->i+2mx && (p->z[p->i]&0xc0)==0x80 && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) | (p->z[p->i+2]&0x3f); @@ -200,7 +228,7 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ ReStateNumber *pToFree; unsigned int i = 0; unsigned int iSwap = 0; - int c = RE_EOF+1; + int c = RE_START; int cPrev = 0; int rc = 0; ReInput in; @@ -219,6 +247,7 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ in.i++; } if( in.i+pRe->nInit>in.mx ) return 0; + c = RE_START-1; } if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ @@ -247,6 +276,10 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); break; } + case RE_OP_ATSTART: { + if( cPrev==RE_START ) re_add_state(pThis, x+1); + break; + } case RE_OP_ANY: { if( c!=0 ) re_add_state(pNext, x+1); break; @@ -328,7 +361,9 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ } } for(i=0; inState; i++){ - if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; } + int x = pNext->aState[i]; + while( pRe->aOp[x]==RE_OP_GOTO ) x += pRe->aArg[x]; + if( pRe->aOp[x]==RE_OP_ACCEPT ){ rc = 1; break; } } re_match_end: sqlite3_free(pToFree); @@ -483,7 +518,6 @@ static const char *re_subcompile_string(ReCompiled *p){ iStart = p->nState; switch( c ){ case '|': - case '$': case ')': { p->sIn.i--; return 0; @@ -520,6 +554,14 @@ static const char *re_subcompile_string(ReCompiled *p){ re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); break; } + case '$': { + re_append(p, RE_OP_MATCH, RE_EOF); + break; + } + case '^': { + re_append(p, RE_OP_ATSTART, 0); + break; + } case '{': { int m = 0, n = 0; int sz, j; @@ -538,6 +580,7 @@ static const char *re_subcompile_string(ReCompiled *p){ if( m==0 ){ if( n==0 ) return "both m and n are zero in '{m,n}'"; re_insert(p, iPrev, RE_OP_FORK, sz+1); + iPrev++; n--; }else{ for(j=1; jsIn.i+1>=pRe->sIn.mx ){ - re_append(pRe, RE_OP_MATCH, RE_EOF); - re_append(pRe, RE_OP_ACCEPT, 0); - *ppRe = pRe; - }else if( pRe->sIn.i>=pRe->sIn.mx ){ + if( pRe->sIn.i>=pRe->sIn.mx ){ re_append(pRe, RE_OP_ACCEPT, 0); *ppRe = pRe; }else{ @@ -673,15 +712,15 @@ static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ ** one or more matching characters, enter those matching characters into ** zInit[]. The re_match() routine can then search ahead in the input ** string looking for the initial match without having to run the whole - ** regex engine over the string. Do not worry able trying to match + ** regex engine over the string. Do not worry about trying to match ** unicode characters beyond plane 0 - those are very rare and this is ** just an optimization. */ if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ unsigned x = pRe->aArg[i]; - if( x<=127 ){ + if( x<=0x7f ){ pRe->zInit[j++] = (unsigned char)x; - }else if( x<=0xfff ){ + }else if( x<=0x7ff ){ pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); pRe->zInit[j++] = 0x80 | (x&0x3f); }else if( x<=0xffff ){ @@ -744,6 +783,67 @@ static void re_sql_func( } } +#if defined(SQLITE_DEBUG) +/* +** This function is used for testing and debugging only. It is only available +** if the SQLITE_DEBUG compile-time option is used. +** +** Compile a regular expression and then convert the compiled expression into +** text and return that text. +*/ +static void re_bytecode_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zPattern; + const char *zErr; + ReCompiled *pRe; + sqlite3_str *pStr; + int i; + int n; + char *z; + + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); + if( zErr ){ + re_free(pRe); + sqlite3_result_error(context, zErr, -1); + return; + } + if( pRe==0 ){ + sqlite3_result_error_nomem(context); + return; + } + pStr = sqlite3_str_new(0); + if( pStr==0 ) goto re_bytecode_func_err; + if( pRe->nInit>0 ){ + sqlite3_str_appendf(pStr, "INIT "); + for(i=0; inInit; i++){ + sqlite3_str_appendf(pStr, "%02x", pRe->zInit[i]); + } + sqlite3_str_appendf(pStr, "\n"); + } + for(i=0; (unsigned)inState; i++){ + sqlite3_str_appendf(pStr, "%-8s %4d\n", + ReOpName[(unsigned char)pRe->aOp[i]], pRe->aArg[i]); + } + n = sqlite3_str_length(pStr); + z = sqlite3_str_finish(pStr); + if( n==0 ){ + sqlite3_free(z); + }else{ + sqlite3_result_text(context, z, n-1, sqlite3_free); + } + +re_bytecode_func_err: + re_free(pRe); +} + +#endif /* SQLITE_DEBUG */ + + /* ** Invoke this routine to register the regexp() function with the ** SQLite database connection. @@ -768,6 +868,13 @@ int sqlite3_regexp_init( rc = sqlite3_create_function(db, "regexpi", 2, SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, (void*)db, re_sql_func, 0, 0); +#if defined(SQLITE_DEBUG) + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "regexp_bytecode", 1, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + 0, re_bytecode_func, 0, 0); + } +#endif /* SQLITE_DEBUG */ } return rc; } diff --git a/ext/rbu/rburename.test b/ext/rbu/rburename.test new file mode 100644 index 000000000..2275396bc --- /dev/null +++ b/ext/rbu/rburename.test @@ -0,0 +1,54 @@ +# 2022 November 07 +# +# 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. +# +#*********************************************************************** +# +# + +source [file join [file dirname [info script]] rbu_common.tcl] +set ::testprefix rburename + + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); +} + +forcedelete test.db-vacuum + +proc my_rename {old new} { + lappend ::my_rename_calls [list [file tail $old] [file tail $new]] + file rename $old $new +} + +do_test 1.1 { + sqlite3rbu_vacuum rbu test.db + rbu rename_handler my_rename + while {[rbu step]=="SQLITE_OK"} {} + rbu close +} SQLITE_DONE + +do_test 1.2 { + set ::my_rename_calls +} {{test.db-oal test.db-wal}} + +proc my_rename {old new} { + error "something went wrong" +} + +do_test 1.3 { + sqlite3rbu_vacuum rbu test.db + rbu rename_handler my_rename + while {[rbu step]=="SQLITE_OK"} {} + list [catch { rbu close } msg] $msg +} {1 SQLITE_IOERR} + +finish_test diff --git a/ext/rbu/rbuvacuum2.test b/ext/rbu/rbuvacuum2.test index c8fba6a22..d4f7c5232 100644 --- a/ext/rbu/rbuvacuum2.test +++ b/ext/rbu/rbuvacuum2.test @@ -227,10 +227,11 @@ do_test 6.1 { rbu close } {SQLITE_OK} -do_execsql_test 6.2 { - SELECT 1 FROM sqlite_master LIMIT 1; - PRAGMA wal_checkpoint; -} {1 0 4 4} +do_test 6.2 { + execsql { SELECT 1 FROM sqlite_master LIMIT 1 } + execsql { PRAGMA wal_checkpoint } + execsql { SELECT 1 FROM sqlite_master LIMIT 1 } +} {1} do_test 6.3 { sqlite3rbu_vacuum rbu test.db test.db2 diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c index 27a3720da..482952006 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -393,6 +393,8 @@ struct sqlite3rbu { int nPagePerSector; /* Pages per sector for pTargetFd */ i64 iOalSz; i64 nPhaseOneStep; + void *pRenameArg; + int (*xRename)(void*, const char*, const char*); /* The following state variables are used as part of the incremental ** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding @@ -2781,7 +2783,7 @@ static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){ sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); if( p->zState==0 ){ const char *zFile = sqlite3_db_filename(p->dbRbu, "main"); - p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile); + p->zState = rbuMPrintf(p, "file:///%s-vacuum?modeof=%s", zFile, zFile); } } @@ -3241,32 +3243,7 @@ static void rbuMoveOalFile(sqlite3rbu *p){ } if( p->rc==SQLITE_OK ){ -#if defined(_WIN32_WCE) - { - LPWSTR zWideOal; - LPWSTR zWideWal; - - zWideOal = rbuWinUtf8ToUnicode(zOal); - if( zWideOal ){ - zWideWal = rbuWinUtf8ToUnicode(zWal); - if( zWideWal ){ - if( MoveFileW(zWideOal, zWideWal) ){ - p->rc = SQLITE_OK; - }else{ - p->rc = SQLITE_IOERR; - } - sqlite3_free(zWideWal); - }else{ - p->rc = SQLITE_IOERR_NOMEM; - } - sqlite3_free(zWideOal); - }else{ - p->rc = SQLITE_IOERR_NOMEM; - } - } -#else - p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK; -#endif + p->rc = p->xRename(p->pRenameArg, zOal, zWal); } if( p->rc!=SQLITE_OK @@ -4005,6 +3982,7 @@ static sqlite3rbu *openRbuHandle( /* Create the custom VFS. */ memset(p, 0, sizeof(sqlite3rbu)); + sqlite3rbu_rename_handler(p, 0, 0); rbuCreateVfs(p); /* Open the target, RBU and state databases */ @@ -4396,6 +4374,54 @@ int sqlite3rbu_savestate(sqlite3rbu *p){ return rc; } +/* +** Default xRename callback for RBU. +*/ +static int xDefaultRename(void *pArg, const char *zOld, const char *zNew){ + int rc = SQLITE_OK; +#if defined(_WIN32_WCE) + { + LPWSTR zWideOld; + LPWSTR zWideNew; + + zWideOld = rbuWinUtf8ToUnicode(zOld); + if( zWideOld ){ + zWideNew = rbuWinUtf8ToUnicode(zNew); + if( zWideNew ){ + if( MoveFileW(zWideOld, zWideNew) ){ + rc = SQLITE_OK; + }else{ + rc = SQLITE_IOERR; + } + sqlite3_free(zWideNew); + }else{ + rc = SQLITE_IOERR_NOMEM; + } + sqlite3_free(zWideOld); + }else{ + rc = SQLITE_IOERR_NOMEM; + } + } +#else + rc = rename(zOld, zNew) ? SQLITE_IOERR : SQLITE_OK; +#endif + return rc; +} + +void sqlite3rbu_rename_handler( + sqlite3rbu *pRbu, + void *pArg, + int (*xRename)(void *pArg, const char *zOld, const char *zNew) +){ + if( xRename ){ + pRbu->xRename = xRename; + pRbu->pRenameArg = pArg; + }else{ + pRbu->xRename = xDefaultRename; + pRbu->pRenameArg = 0; + } +} + /************************************************************************** ** Beginning of RBU VFS shim methods. The VFS shim modifies the behaviour ** of a standard VFS in the following ways: diff --git a/ext/rbu/sqlite3rbu.h b/ext/rbu/sqlite3rbu.h index 69d89500a..c819cd3f2 100644 --- a/ext/rbu/sqlite3rbu.h +++ b/ext/rbu/sqlite3rbu.h @@ -544,6 +544,34 @@ SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int*pnTwo); SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu); +/* +** As part of applying an RBU update or performing an RBU vacuum operation, +** the system must at one point move the *-oal file to the equivalent *-wal +** path. Normally, it does this by invoking POSIX function rename(2) directly. +** Except on WINCE platforms, where it uses win32 API MoveFileW(). This +** function may be used to register a callback that the RBU module will invoke +** instead of one of these APIs. +** +** If a callback is registered with an RBU handle, it invokes it instead +** of rename(2) when it needs to move a file within the file-system. The +** first argument passed to the xRename() callback is a copy of the second +** argument (pArg) passed to this function. The second is the full path +** to the file to move and the third the full path to which it should be +** moved. The callback function should return SQLITE_OK to indicate +** success. If an error occurs, it should return an SQLite error code. +** In this case the RBU operation will be abandoned and the error returned +** to the RBU user. +** +** Passing a NULL pointer in place of the xRename argument to this function +** restores the default behaviour. +*/ +SQLITE_API void sqlite3rbu_rename_handler( + sqlite3rbu *pRbu, + void *pArg, + int (*xRename)(void *pArg, const char *zOld, const char *zNew) +); + + /* ** Create an RBU VFS named zName that accesses the underlying file-system ** via existing VFS zParent. Or, if the zParent parameter is passed NULL, diff --git a/ext/rbu/test_rbu.c b/ext/rbu/test_rbu.c index 6d04bfe8c..af794d80f 100644 --- a/ext/rbu/test_rbu.c +++ b/ext/rbu/test_rbu.c @@ -26,6 +26,14 @@ # endif #endif #include +#include + +typedef struct TestRbu TestRbu; +struct TestRbu { + sqlite3rbu *pRbu; + Tcl_Interp *interp; + Tcl_Obj *xRename; +}; /* From main.c */ extern const char *sqlite3ErrName(int); @@ -55,6 +63,20 @@ void test_rbu_delta(sqlite3_context *pCtx, int nArg, sqlite3_value **apVal){ Tcl_DecrRefCount(pScript); } +static int xRenameCallback(void *pArg, const char *zOld, const char *zNew){ + int rc = SQLITE_OK; + TestRbu *pTest = (TestRbu*)pArg; + Tcl_Obj *pEval = Tcl_DuplicateObj(pTest->xRename); + + Tcl_IncrRefCount(pEval); + Tcl_ListObjAppendElement(pTest->interp, pEval, Tcl_NewStringObj(zOld, -1)); + Tcl_ListObjAppendElement(pTest->interp, pEval, Tcl_NewStringObj(zNew, -1)); + + rc = Tcl_EvalObjEx(pTest->interp, pEval, TCL_GLOBAL_ONLY); + Tcl_DecrRefCount(pEval); + + return rc ? SQLITE_IOERR : SQLITE_OK; +} static int SQLITE_TCLAPI test_sqlite3rbu_cmd( ClientData clientData, @@ -63,7 +85,8 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd( Tcl_Obj *CONST objv[] ){ int ret = TCL_OK; - sqlite3rbu *pRbu = (sqlite3rbu*)clientData; + TestRbu *pTest = (TestRbu*)clientData; + sqlite3rbu *pRbu = pTest->pRbu; struct RbuCmd { const char *zName; int nArg; @@ -82,6 +105,7 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd( {"temp_size_limit", 3, "LIMIT"}, /* 10 */ {"temp_size", 2, ""}, /* 11 */ {"dbRbu_eval", 3, "SQL"}, /* 12 */ + {"rename_handler", 3, "SCRIPT"},/* 13 */ {0,0,0} }; int iCmd; @@ -127,6 +151,8 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd( } ret = TCL_ERROR; } + if( pTest->xRename ) Tcl_DecrRefCount(pTest->xRename); + ckfree(pTest); break; } @@ -214,6 +240,19 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd( break; } + case 13: /* rename_handler */ { + Tcl_Obj *pScript = objv[2]; + assert( !sqlite3_stricmp(aCmd[13].zName, "rename_handler") ); + if( Tcl_GetCharLength(pScript)==0 ){ + sqlite3rbu_rename_handler(pRbu, 0, 0); + }else{ + pTest->xRename = Tcl_DuplicateObj(pScript); + Tcl_IncrRefCount(pTest->xRename); + sqlite3rbu_rename_handler(pRbu, pTest, xRenameCallback); + } + break; + } + default: /* seems unlikely */ assert( !"cannot happen" ); break; @@ -222,6 +261,18 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd( return ret; } +static void createRbuWrapper( + Tcl_Interp *interp, + const char *zCmd, + sqlite3rbu *pRbu +){ + TestRbu *pTest = (TestRbu*)ckalloc(sizeof(TestRbu)); + memset(pTest, 0, sizeof(TestRbu)); + pTest->pRbu = pRbu; + pTest->interp = interp; + Tcl_CreateObjCommand(interp, zCmd, test_sqlite3rbu_cmd, (ClientData)pTest, 0); +} + /* ** Tclcmd: sqlite3rbu CMD ?? */ @@ -247,7 +298,7 @@ static int SQLITE_TCLAPI test_sqlite3rbu( if( objc==5 ) zStateDb = Tcl_GetString(objv[4]); pRbu = sqlite3rbu_open(zTarget, zRbu, zStateDb); - Tcl_CreateObjCommand(interp, zCmd, test_sqlite3rbu_cmd, (ClientData)pRbu, 0); + createRbuWrapper(interp, zCmd, pRbu); Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } @@ -276,7 +327,7 @@ static int SQLITE_TCLAPI test_sqlite3rbu_vacuum( if( zStateDb && zStateDb[0]=='\0' ) zStateDb = 0; pRbu = sqlite3rbu_vacuum(zTarget, zStateDb); - Tcl_CreateObjCommand(interp, zCmd, test_sqlite3rbu_cmd, (ClientData)pRbu, 0); + createRbuWrapper(interp, zCmd, pRbu); Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } diff --git a/ext/misc/dbdata.c b/ext/recover/dbdata.c similarity index 84% rename from ext/misc/dbdata.c rename to ext/recover/dbdata.c index 7405e7c89..9563ab502 100644 --- a/ext/misc/dbdata.c +++ b/ext/recover/dbdata.c @@ -71,16 +71,20 @@ ** It contains one entry for each b-tree pointer between a parent and ** child page in the database. */ + #if !defined(SQLITEINT_H) #include "sqlite3ext.h" typedef unsigned char u8; +typedef unsigned int u32; #endif SQLITE_EXTENSION_INIT1 #include #include +#ifndef SQLITE_OMIT_VIRTUALTABLE + #define DBDATA_PADDING_BYTES 100 typedef struct DbdataTable DbdataTable; @@ -102,11 +106,12 @@ struct DbdataCursor { /* Only for the sqlite_dbdata table */ u8 *pRec; /* Buffer containing current record */ - int nRec; /* Size of pRec[] in bytes */ - int nHdr; /* Size of header in bytes */ + sqlite3_int64 nRec; /* Size of pRec[] in bytes */ + sqlite3_int64 nHdr; /* Size of header in bytes */ int iField; /* Current field number */ u8 *pHdrPtr; u8 *pPtr; + u32 enc; /* Text encoding */ sqlite3_int64 iIntkey; /* Integer key value */ }; @@ -299,14 +304,14 @@ static int dbdataClose(sqlite3_vtab_cursor *pCursor){ /* ** Utility methods to decode 16 and 32-bit big-endian unsigned integers. */ -static unsigned int get_uint16(unsigned char *a){ +static u32 get_uint16(unsigned char *a){ return (a[0]<<8)|a[1]; } -static unsigned int get_uint32(unsigned char *a){ - return ((unsigned int)a[0]<<24) - | ((unsigned int)a[1]<<16) - | ((unsigned int)a[2]<<8) - | ((unsigned int)a[3]); +static u32 get_uint32(unsigned char *a){ + return ((u32)a[0]<<24) + | ((u32)a[1]<<16) + | ((u32)a[2]<<8) + | ((u32)a[3]); } /* @@ -321,7 +326,7 @@ static unsigned int get_uint32(unsigned char *a){ */ static int dbdataLoadPage( DbdataCursor *pCsr, /* Cursor object */ - unsigned int pgno, /* Page number of page to load */ + u32 pgno, /* Page number of page to load */ u8 **ppPage, /* OUT: pointer to page buffer */ int *pnPage /* OUT: Size of (*ppPage) in bytes */ ){ @@ -331,25 +336,27 @@ static int dbdataLoadPage( *ppPage = 0; *pnPage = 0; - sqlite3_bind_int64(pStmt, 2, pgno); - if( SQLITE_ROW==sqlite3_step(pStmt) ){ - int nCopy = sqlite3_column_bytes(pStmt, 0); - if( nCopy>0 ){ - u8 *pPage; - pPage = (u8*)sqlite3_malloc64(nCopy + DBDATA_PADDING_BYTES); - if( pPage==0 ){ - rc = SQLITE_NOMEM; - }else{ - const u8 *pCopy = sqlite3_column_blob(pStmt, 0); - memcpy(pPage, pCopy, nCopy); - memset(&pPage[nCopy], 0, DBDATA_PADDING_BYTES); + if( pgno>0 ){ + sqlite3_bind_int64(pStmt, 2, pgno); + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + int nCopy = sqlite3_column_bytes(pStmt, 0); + if( nCopy>0 ){ + u8 *pPage; + pPage = (u8*)sqlite3_malloc64(nCopy + DBDATA_PADDING_BYTES); + if( pPage==0 ){ + rc = SQLITE_NOMEM; + }else{ + const u8 *pCopy = sqlite3_column_blob(pStmt, 0); + memcpy(pPage, pCopy, nCopy); + memset(&pPage[nCopy], 0, DBDATA_PADDING_BYTES); + } + *ppPage = pPage; + *pnPage = nCopy; } - *ppPage = pPage; - *pnPage = nCopy; } + rc2 = sqlite3_reset(pStmt); + if( rc==SQLITE_OK ) rc = rc2; } - rc2 = sqlite3_reset(pStmt); - if( rc==SQLITE_OK ) rc = rc2; return rc; } @@ -358,17 +365,30 @@ static int dbdataLoadPage( ** Read a varint. Put the value in *pVal and return the number of bytes. */ static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){ - sqlite3_int64 v = 0; + sqlite3_uint64 u = 0; int i; for(i=0; i<8; i++){ - v = (v<<7) + (z[i]&0x7f); - if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; } + u = (u<<7) + (z[i]&0x7f); + if( (z[i]&0x80)==0 ){ *pVal = (sqlite3_int64)u; return i+1; } } - v = (v<<8) + (z[i]&0xff); - *pVal = v; + u = (u<<8) + (z[i]&0xff); + *pVal = (sqlite3_int64)u; return 9; } +/* +** Like dbdataGetVarint(), but set the output to 0 if it is less than 0 +** or greater than 0xFFFFFFFF. This can be used for all varints in an +** SQLite database except for key values in intkey tables. +*/ +static int dbdataGetVarintU32(const u8 *z, sqlite3_int64 *pVal){ + sqlite3_int64 val; + int nRet = dbdataGetVarint(z, &val); + if( val<0 || val>0xFFFFFFFF ) val = 0; + *pVal = val; + return nRet; +} + /* ** Return the number of bytes of space used by an SQLite value of type ** eType. @@ -405,9 +425,10 @@ static int dbdataValueBytes(int eType){ */ static void dbdataValue( sqlite3_context *pCtx, + u32 enc, int eType, u8 *pData, - int nData + sqlite3_int64 nData ){ if( eType>=0 && dbdataValueBytes(eType)<=nData ){ switch( eType ){ @@ -449,7 +470,19 @@ static void dbdataValue( default: { int n = ((eType-12) / 2); if( eType % 2 ){ - sqlite3_result_text(pCtx, (const char*)pData, n, SQLITE_TRANSIENT); + switch( enc ){ +#ifndef SQLITE_OMIT_UTF16 + case SQLITE_UTF16BE: + sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; + case SQLITE_UTF16LE: + sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; +#endif + default: + sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT); + break; + } }else{ sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); } @@ -477,6 +510,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage); if( rc!=SQLITE_OK ) return rc; if( pCsr->aPage ) break; + if( pCsr->bOnePage ) return SQLITE_OK; pCsr->iPgno++; } pCsr->iCell = pTab->bPtr ? -2 : 0; @@ -540,7 +574,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ if( bNextPage || iOff>pCsr->nPage ){ bNextPage = 1; }else{ - iOff += dbdataGetVarint(&pCsr->aPage[iOff], &nPayload); + iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); } /* If this is a leaf intkey cell, load the rowid */ @@ -587,7 +621,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ /* Load content from overflow pages */ if( nPayload>nLocal ){ sqlite3_int64 nRem = nPayload - nLocal; - unsigned int pgnoOvfl = get_uint32(&pCsr->aPage[iOff]); + u32 pgnoOvfl = get_uint32(&pCsr->aPage[iOff]); while( nRem>0 ){ u8 *aOvfl = 0; int nOvfl = 0; @@ -607,7 +641,8 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ } } - iHdr = dbdataGetVarint(pCsr->pRec, &nHdr); + iHdr = dbdataGetVarintU32(pCsr->pRec, &nHdr); + if( nHdr>nPayload ) nHdr = 0; pCsr->nHdr = nHdr; pCsr->pHdrPtr = &pCsr->pRec[iHdr]; pCsr->pPtr = &pCsr->pRec[pCsr->nHdr]; @@ -621,7 +656,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){ bNextPage = 1; }else{ - pCsr->pHdrPtr += dbdataGetVarint(pCsr->pHdrPtr, &iType); + pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType); pCsr->pPtr += dbdataValueBytes(iType); } } @@ -660,6 +695,18 @@ static int dbdataEof(sqlite3_vtab_cursor *pCursor){ return pCsr->aPage==0; } +/* +** Return true if nul-terminated string zSchema ends in "()". Or false +** otherwise. +*/ +static int dbdataIsFunction(const char *zSchema){ + size_t n = strlen(zSchema); + if( n>2 && zSchema[n-2]=='(' && zSchema[n-1]==')' ){ + return (int)n-2; + } + return 0; +} + /* ** Determine the size in pages of database zSchema (where zSchema is ** "main", "temp" or the name of an attached database) and set @@ -670,10 +717,16 @@ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){ DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab; char *zSql = 0; int rc, rc2; + int nFunc = 0; sqlite3_stmt *pStmt = 0; - zSql = sqlite3_mprintf("PRAGMA %Q.page_count", zSchema); + if( (nFunc = dbdataIsFunction(zSchema))>0 ){ + zSql = sqlite3_mprintf("SELECT %.*s(0)", nFunc, zSchema); + }else{ + zSql = sqlite3_mprintf("PRAGMA %Q.page_count", zSchema); + } if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ @@ -684,6 +737,25 @@ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){ return rc; } +/* +** Attempt to figure out the encoding of the database by retrieving page 1 +** and inspecting the header field. If successful, set the pCsr->enc variable +** and return SQLITE_OK. Otherwise, return an SQLite error code. +*/ +static int dbdataGetEncoding(DbdataCursor *pCsr){ + int rc = SQLITE_OK; + int nPg1 = 0; + u8 *aPg1 = 0; + rc = dbdataLoadPage(pCsr, 1, &aPg1, &nPg1); + assert( rc!=SQLITE_OK || nPg1==0 || nPg1>=512 ); + if( rc==SQLITE_OK && nPg1>0 ){ + pCsr->enc = get_uint32(&aPg1[56]); + } + sqlite3_free(aPg1); + return rc; +} + + /* ** xFilter method for sqlite_dbdata and sqlite_dbptr. */ @@ -701,19 +773,28 @@ static int dbdataFilter( assert( pCsr->iPgno==1 ); if( idxNum & 0x01 ){ zSchema = (const char*)sqlite3_value_text(argv[0]); + if( zSchema==0 ) zSchema = ""; } if( idxNum & 0x02 ){ pCsr->iPgno = sqlite3_value_int(argv[(idxNum & 0x01)]); pCsr->bOnePage = 1; }else{ - pCsr->nPage = dbdataDbsize(pCsr, zSchema); rc = dbdataDbsize(pCsr, zSchema); } if( rc==SQLITE_OK ){ + int nFunc = 0; if( pTab->pStmt ){ pCsr->pStmt = pTab->pStmt; pTab->pStmt = 0; + }else if( (nFunc = dbdataIsFunction(zSchema))>0 ){ + char *zSql = sqlite3_mprintf("SELECT %.*s(?2)", nFunc, zSchema); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0); + sqlite3_free(zSql); + } }else{ rc = sqlite3_prepare_v2(pTab->db, "SELECT data FROM sqlite_dbpage(?) WHERE pgno=?", -1, @@ -726,13 +807,20 @@ static int dbdataFilter( }else{ pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); } + + /* Try to determine the encoding of the db by inspecting the header + ** field on page 1. */ + if( rc==SQLITE_OK ){ + rc = dbdataGetEncoding(pCsr); + } + if( rc==SQLITE_OK ){ rc = dbdataNext(pCursor); } return rc; } -/* +/* ** Return a column for the sqlite_dbdata or sqlite_dbptr table. */ static int dbdataColumn( @@ -776,11 +864,12 @@ static int dbdataColumn( case DBDATA_COLUMN_VALUE: { if( pCsr->iField<0 ){ sqlite3_result_int64(ctx, pCsr->iIntkey); - }else{ + }else if( &pCsr->pRec[pCsr->nRec] >= pCsr->pPtr ){ sqlite3_int64 iType; - dbdataGetVarint(pCsr->pHdrPtr, &iType); + dbdataGetVarintU32(pCsr->pHdrPtr, &iType); dbdataValue( - ctx, iType, pCsr->pPtr, &pCsr->pRec[pCsr->nRec] - pCsr->pPtr + ctx, pCsr->enc, iType, pCsr->pPtr, + &pCsr->pRec[pCsr->nRec] - pCsr->pPtr ); } break; @@ -849,3 +938,5 @@ int sqlite3_dbdata_init( SQLITE_EXTENSION_INIT2(pApi); return sqlite3DbdataRegister(db); } + +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/recover/recover1.test b/ext/recover/recover1.test new file mode 100644 index 000000000..dba96f519 --- /dev/null +++ b/ext/recover/recover1.test @@ -0,0 +1,320 @@ +# 2022 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recover1 + +proc compare_result {db1 db2 sql} { + set r1 [$db1 eval $sql] + set r2 [$db2 eval $sql] + if {$r1 != $r2} { + puts "r1: $r1" + puts "r2: $r2" + error "mismatch for $sql" + } + return "" +} + +proc compare_dbs {db1 db2} { + compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1" + foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] { + compare_result $db1 $db2 "SELECT * FROM $tbl" + } + + compare_result $db1 $db2 "PRAGMA page_size" + compare_result $db1 $db2 "PRAGMA auto_vacuum" + compare_result $db1 $db2 "PRAGMA encoding" + compare_result $db1 $db2 "PRAGMA user_version" + compare_result $db1 $db2 "PRAGMA application_id" +} + +proc do_recover_test {tn} { + forcedelete test.db2 + forcedelete rstate.db + + uplevel [list do_test $tn.1 { + set R [sqlite3_recover_init db main test.db2] + $R config testdb rstate.db + $R run + $R finish + } {}] + + sqlite3 db2 test.db2 + uplevel [list do_test $tn.2 [list compare_dbs db db2] {}] + db2 close + + forcedelete test.db2 + forcedelete rstate.db + + uplevel [list do_test $tn.3 { + set ::sqlhook [list] + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R config testdb rstate.db + $R config rowids 1 + $R run + $R finish + } {}] + + sqlite3 db2 test.db2 + execsql [join $::sqlhook ";"] db2 + db2 close + sqlite3 db2 test.db2 + uplevel [list do_test $tn.4 [list compare_dbs db db2] {}] + db2 close +} + +proc my_sql_hook {sql} { + lappend ::sqlhook $sql + return 0 +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + CREATE TABLE t2(a INTEGER PRIMARY KEY, b) WITHOUT ROWID; + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10 + ) + INSERT INTO t1 SELECT i*2, hex(randomblob(250)) FROM s; + INSERT INTO t2 SELECT * FROM t1; +} + +do_recover_test 1 + +do_execsql_test 2.0 { + ALTER TABLE t1 ADD COLUMN c DEFAULT 'xyz' +} +do_recover_test 2 + +do_execsql_test 3.0 { + CREATE INDEX i1 ON t1(c); +} +do_recover_test 3 + +do_execsql_test 4.0 { + CREATE VIEW v1 AS SELECT * FROM t2; +} +do_recover_test 4 + +do_execsql_test 5.0 { + CREATE UNIQUE INDEX i2 ON t1(c, b); +} +do_recover_test 5 + +#-------------------------------------------------------------------------- +# +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1( + a INTEGER PRIMARY KEY, + b INT, + c TEXT, + d INT GENERATED ALWAYS AS (a*abs(b)) VIRTUAL, + e TEXT GENERATED ALWAYS AS (substr(c,b,b+1)) STORED, + f TEXT GENERATED ALWAYS AS (substr(c,b,b+1)) STORED + ); + + INSERT INTO t1 VALUES(1, 2, 'hello world'); +} +do_recover_test 6 + +do_execsql_test 7.0 { + CREATE TABLE t2(i, j GENERATED ALWAYS AS (i+1) STORED, k); + INSERT INTO t2 VALUES(10, 'ten'); +} +do_execsql_test 7.1 { + SELECT * FROM t2 +} {10 11 ten} + +do_recover_test 7.2 + +#-------------------------------------------------------------------------- +# +reset_db +do_execsql_test 8.0 { + CREATE TABLE x1(a INTEGER PRIMARY KEY AUTOINCREMENT, b, c); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<2 + ) + INSERT INTO x1(b, c) SELECT hex(randomblob(100)), hex(randomblob(100)) FROM s; + + CREATE INDEX x1b ON x1(b); + CREATE INDEX x1cb ON x1(c, b); + DELETE FROM x1 WHERE a>50; + + ANALYZE; +} + +do_recover_test 8 + +#------------------------------------------------------------------------- +reset_db +ifcapable fts5 { + do_execsql_test 9.1 { + CREATE VIRTUAL TABLE ft5 USING fts5(a, b); + INSERT INTO ft5 VALUES('hello', 'world'); + } + do_recover_test 9 +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 10.1 { + CREATE TABLE x1(a PRIMARY KEY, str TEXT) WITHOUT ROWID; + INSERT INTO x1 VALUES(1, ' + \nhello\012world(\n0)(\n1) + '); + INSERT INTO x1 VALUES(2, ' + \nhello + '); +} +do_execsql_test 10.2 " + INSERT INTO x1 VALUES(3, '\012hello there\015world'); + INSERT INTO x1 VALUES(4, '\015hello there\015world'); +" +do_recover_test 10 + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 11.1 { + PRAGMA page_size = 4096; + PRAGMA encoding='utf16'; + PRAGMA auto_vacuum = 2; + PRAGMA user_version = 45; + PRAGMA application_id = 22; + + CREATE TABLE u1(u, v); + INSERT INTO u1 VALUES('edvin marton', 'bond'); + INSERT INTO u1 VALUES(1, 4.0); +} +do_execsql_test 11.1a { + PRAGMA auto_vacuum; +} {2} + +do_recover_test 11 + +do_test 12.1 { + set R [sqlite3_recover_init db "" test.db2] + $R config lostandfound "" + $R config invalid xyz +} {12} +do_test 12.2 { + $R run + $R run +} {0} + +do_test 12.3 { + $R finish +} {} + + + +#------------------------------------------------------------------------- +reset_db +file_control_reservebytes db 16 +do_execsql_test 12.1 { + PRAGMA auto_vacuum = 2; + PRAGMA user_version = 45; + PRAGMA application_id = 22; + + CREATE TABLE u1(u, v); + CREATE UNIQUE INDEX i1 ON u1(u, v); + INSERT INTO u1 VALUES(1, 2), (3, 4); + + CREATE TABLE u2(u, v); + CREATE UNIQUE INDEX i2 ON u1(u, v); + INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000))); + INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000))); + INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000))); + INSERT INTO u2 VALUES(hex(randomblob(50000)), hex(randomblob(20000))); +} + +do_recover_test 12 + +#------------------------------------------------------------------------- +reset_db +sqlite3 db "" +do_recover_test 13 + +do_execsql_test 14.1 { + PRAGMA auto_vacuum = 2; + PRAGMA user_version = 45; + PRAGMA application_id = 22; + + CREATE TABLE u1(u, v); + CREATE UNIQUE INDEX i1 ON u1(u, v); + INSERT INTO u1 VALUES(1, 2), (3, 4); + + CREATE TABLE u2(u, v); + CREATE UNIQUE INDEX i2 ON u1(u, v); + INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000))); + INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000))); + INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000))); + INSERT INTO u2 VALUES(hex(randomblob(50000)), hex(randomblob(20000))); +} +do_recover_test 14 + +#------------------------------------------------------------------------- +reset_db +execsql { + PRAGMA journal_mode=OFF; + PRAGMA mmap_size=10; +} +do_execsql_test 15.1 { + CREATE TABLE t1(x); +} {} +do_recover_test 15 + +#------------------------------------------------------------------------- +reset_db +if {[wal_is_capable]} { + do_execsql_test 16.1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1), (2), (3); + } {wal} + do_test 16.2 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish + } {} + do_execsql_test 16.3 { + SELECT * FROM t1; + } {1 2 3} + + do_execsql_test 16.4 { + BEGIN; + SELECT * FROM t1; + } {1 2 3} + do_test 16.5 { + set R [sqlite3_recover_init db main test.db2] + $R run + list [catch { $R finish } msg] $msg + } {1 {cannot start a transaction within a transaction}} + do_execsql_test 16.6 { + SELECT * FROM t1; + } {1 2 3} + do_execsql_test 16.7 { + INSERT INTO t1 VALUES(4); + } + do_test 16.8 { + set R [sqlite3_recover_init db main test.db2] + $R run + list [catch { $R finish } msg] $msg + } {1 {cannot start a transaction within a transaction}} + do_execsql_test 16.9 { + SELECT * FROM t1; + COMMIT; + } {1 2 3 4} +} + +finish_test + diff --git a/ext/recover/recover_common.tcl b/ext/recover/recover_common.tcl new file mode 100644 index 000000000..fdf735ee7 --- /dev/null +++ b/ext/recover/recover_common.tcl @@ -0,0 +1,14 @@ + + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source $testdir/tester.tcl + +if {[info commands sqlite3_recover_init]==""} { + finish_test + return -code return +} + + + diff --git a/ext/recover/recoverclobber.test b/ext/recover/recoverclobber.test new file mode 100644 index 000000000..e096b2e21 --- /dev/null +++ b/ext/recover/recoverclobber.test @@ -0,0 +1,50 @@ +# 2019 April 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. +# +#*********************************************************************** +# +# Tests for the SQLITE_RECOVER_ROWIDS option. +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoverclobber + +proc recover {db output} { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} + +forcedelete test.db2 +do_execsql_test 1.0 { + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.x1(x, one); + INSERT INTO x1 VALUES(1, 'one'), (2, 'two'), (3, 'three'); + + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3), (4, 4); + + DETACH aux; +} + +breakpoint +do_test 1.1 { + recover db test.db2 +} {} + +do_execsql_test 1.2 { + ATTACH 'test.db2' AS aux; + SELECT * FROM aux.t1; +} {1 1 2 2 3 3 4 4} + +do_catchsql_test 1.3 { + SELECT * FROM aux.x1; +} {1 {no such table: aux.x1}} + +finish_test diff --git a/ext/recover/recovercorrupt.test b/ext/recover/recovercorrupt.test new file mode 100644 index 000000000..eb6fe53ad --- /dev/null +++ b/ext/recover/recovercorrupt.test @@ -0,0 +1,67 @@ +# 2022 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recovercorrupt + +database_may_be_corrupt + +do_execsql_test 1.0 { + PRAGMA page_size = 512; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(2, hex(randomblob(100)), randomblob(200)); + CREATE INDEX i1 ON t1(b, c); + CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID; + INSERT INTO t2 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(2, hex(randomblob(100)), randomblob(200)); + ANALYZE; + PRAGMA writable_schema = 1; + DELETE FROM sqlite_schema WHERE name='t2'; +} + +do_test 1.1 { + expr [file size test.db]>3072 +} {1} + +proc toggle_bit {blob bit} { + set byte [expr {$bit / 8}] + set bit [expr {$bit & 0x0F}] + binary scan $blob a${byte}ca* A x B + set x [expr {$x ^ (1 << $bit)}] + binary format a*ca* $A $x $B +} + + +db_save_and_close +for {set ii 0} {$ii < 10000} {incr ii} { + db_restore_and_reopen + db func toggle_bit toggle_bit + set bitsperpage [expr 512*8] + + set pg [expr {($ii / $bitsperpage)+1}] + set byte [expr {$ii % $bitsperpage}] + db eval { + UPDATE sqlite_dbpage SET data = toggle_bit(data, $byte) WHERE pgno=$pg + } + + set R [sqlite3_recover_init db main test.db2] + $R config lostandfound lost_and_found + $R run + do_test 1.2.$ii { + $R finish + } {} +} + + +finish_test + diff --git a/ext/recover/recovercorrupt2.test b/ext/recover/recovercorrupt2.test new file mode 100644 index 000000000..7147c67e9 --- /dev/null +++ b/ext/recover/recovercorrupt2.test @@ -0,0 +1,289 @@ +# 2022 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recovercorrupt2 + +do_execsql_test 1.0 { + PRAGMA page_size = 512; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(2, hex(randomblob(100)), randomblob(200)); + CREATE INDEX i1 ON t1(b, c); + CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID; + INSERT INTO t2 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(2, hex(randomblob(100)), randomblob(200)); + ANALYZE; + PRAGMA writable_schema = 1; + UPDATE sqlite_schema SET sql = 'CREATE INDEX i1 ON o(world)' WHERE name='i1'; + DELETE FROM sqlite_schema WHERE name='sqlite_stat4'; +} + +do_test 1.1 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +sqlite3 db2 test.db2 +do_execsql_test -db db2 1.2 { + SELECT sql FROM sqlite_schema +} { + {CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c)} + {CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID} + {CREATE TABLE sqlite_stat1(tbl,idx,stat)} +} +db2 close + +do_execsql_test 1.3 { + PRAGMA writable_schema = 1; + UPDATE sqlite_schema SET sql = 'CREATE TABLE t2 syntax error!' WHERE name='t2'; +} + +do_test 1.4 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +sqlite3 db2 test.db2 +do_execsql_test -db db2 1.5 { + SELECT sql FROM sqlite_schema +} { + {CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c)} + {CREATE TABLE sqlite_stat1(tbl,idx,stat)} +} +db2 close + +#------------------------------------------------------------------------- +# +reset_db +do_test 2.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 8192 pagesize 4096 filename x3.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................ +| 96: 00 2e 63 00 0d 00 00 00 01 0f d8 00 0f d8 00 00 ..c............. +| 4048: 00 00 00 00 00 00 00 00 26 01 06 17 11 11 01 39 ........&......9 +| 4064: 74 61 62 6c 65 74 31 74 31 02 43 52 45 41 54 45 tablet1t1.CREATE +| 4080: 20 54 41 42 4c 45 20 74 31 28 61 2c 62 2c 63 29 TABLE t1(a,b,c) +| page 2 offset 4096 +| 0: 0d 00 00 00 01 0f ce 00 0f ce 00 00 00 00 00 00 ................ +| 4032: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ..............(. +| 4048: ff ff ff ff ff ff ff 28 04 27 25 23 61 61 61 61 .........'%#aaaa +| 4064: 61 61 61 61 61 61 61 61 61 62 62 62 62 62 62 62 aaaaaaaaabbbbbbb +| 4080: 62 62 62 62 62 63 63 63 63 63 63 63 63 63 63 63 bbbbbccccccccccc +| end x3.db +}]} {} + +do_test 2.1 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +sqlite3 db2 test.db2 +do_execsql_test -db db2 2.2 { + SELECT sql FROM sqlite_schema +} { + {CREATE TABLE t1(a,b,c)} +} +do_execsql_test -db db2 2.3 { + SELECT * FROM t1 +} {} +db2 close + +#------------------------------------------------------------------------- +# +reset_db +do_test 3.0 { + sqlite3 db {} + db deserialize [decode_hexdb { + .open --hexdb + | size 4096 pagesize 1024 filename corrupt032.txt.db + | page 1 offset 0 + | 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. + | 16: 04 00 01 01 08 40 20 20 00 00 00 02 00 00 00 03 .....@ ........ + | 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ + | 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ + | 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................ + | 96: 00 2e 24 80 0d 00 00 00 01 03 d4 00 03 d4 00 00 ..$............. + | 976: 00 00 00 00 22 01 06 17 11 11 01 31 74 61 62 6c ...........1tabl + | 992: 65 74 31 74 31 02 43 52 45 41 54 45 20 54 41 42 et1t1.CREATE TAB + | 1008: 4c 45 20 74 31 28 78 29 00 00 00 00 00 00 00 00 LE t1(x)........ + | page 2 offset 1024 + | 0: 0d 00 00 00 01 02 06 00 02 06 00 00 00 00 00 00 ................ + | 512: 00 00 00 00 00 00 8b 60 01 03 97 46 00 00 00 00 .......`...F.... + | 1008: 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 00 ................ + | end corrupt032.txt.db +}]} {} + +do_test 3.1 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +#------------------------------------------------------------------------- +# +reset_db +do_test 4.0 { + sqlite3 db {} + db deserialize [decode_hexdb { + .open --hexdb + | size 4096 pagesize 4096 filename crash-00f2d3627f1b43.db + | page 1 offset 0 + | 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. + | 16: 00 01 01 02 00 40 20 20 01 00 ff 00 42 01 10 01 .....@ ....B... + | 32: ef 00 00 87 00 ff ff ff f0 01 01 10 ff ff 00 00 ................ + | end crash-00f2d3627f1b43.db +}]} {} + +do_test 4.1 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {1 {unable to open database file}} + +#------------------------------------------------------------------------- +# +reset_db +do_test 5.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 16384 pagesize 4096 filename crash-7b75760a4c5f15.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 04 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 ................ +| 96: 00 00 00 00 0d 00 00 00 03 0f 4e 00 0f bc 0f 90 ..........N..... +| 112: 0f 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .N.............. +| 3904: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 03 ..............@. +| 3920: 06 17 11 11 01 6d 74 61 62 6c 65 74 32 74 32 04 .....mtablet2t2. +| 3936: 43 52 45 41 54 45 20 54 41 42 4c 45 20 74 32 28 CREATE TABLE t2( +| 3952: 78 2c 79 2c 7a 20 50 52 49 4d 41 52 59 20 4b 45 x,y,z PRIMARY KE +| 3968: 59 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 Y) WITHOUT ROWID +| 3984: 2a 02 06 17 13 11 01 3f 69 6e 64 65 78 74 31 61 *......?indext1a +| 4000: 74 31 03 43 52 45 41 54 45 20 49 4e 44 45 58 20 t1.CREATE INDEX +| 4016: 74 31 61 20 4f 4e 20 74 31 28 61 29 42 01 06 17 t1a ON t1(a)B... +| 4032: 11 11 01 71 74 61 62 6c 65 74 31 74 31 02 43 52 ...qtablet1t1.CR +| 4048: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 61 20 EATE TABLE t1(a +| 4064: 49 4e 54 2c 62 20 54 45 58 54 2c 63 20 42 4c 4f INT,b TEXT,c BLO +| 4080: 42 2c 64 20 52 45 41 4c 29 20 53 54 52 49 43 54 B,d REAL) STRICT +| page 2 offset 4096 +| 0: 0d 00 00 00 14 0c ae 00 0f df 0f bd 0f 9a 0f 76 ...............v +| 16: 0f 51 0f 2b 0f 04 0e dc 0e b3 0e 89 0e 5e 0e 32 .Q.+.........^.2 +| 32: 0e 05 0d 1a 0d a8 0d 78 0d 47 0d 15 0c e2 00 00 .......x.G...... +| 3232: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 14 ..............2. +| 3248: 05 06 3f 34 07 15 f4 c9 23 af e2 b3 b6 61 62 63 ..?4....#....abc +| 3264: 30 32 30 78 79 7a 01 00 00 00 00 00 00 00 00 00 020xyz.......... +| 3280: 00 00 00 00 00 00 00 00 00 00 c3 b0 96 7e fb 4e .............~.N +| 3296: c5 4c 31 13 05 06 1f 32 07 dd f2 2a a5 7e b2 4d .L1....2...*.~.M +| 3312: 82 61 62 63 30 31 39 78 79 7a 01 00 00 00 00 00 .abc019xyz...... +| 3328: 00 00 00 00 00 00 00 00 00 00 00 00 00 c3 a3 d6 ................ +| 3344: e9 f1 c2 fd f3 30 12 05 06 1f 30 07 8f 8f f5 c4 .....0....0..... +| 3360: 35 b6 7f 8d 61 62 63 30 31 38 00 00 00 00 00 00 5...abc018...... +| 3376: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 43 ...............C +| 3392: b2 13 1f 9d 56 8a 47 21 b1 05 06 1f 2e 07 7f 46 ....V.G!.......F +| 3408: 91 03 3f 97 fb f7 61 62 63 30 00 00 00 00 00 00 ..?...abc0...... +| 3440: c3 bb d8 96 86 c2 e8 2b 2e 10 05 06 1f 2c 07 6d .......+.....,.m +| 3456: 85 7b ce d0 32 d2 54 61 62 63 30 00 00 00 00 00 ....2.Tabc0..... +| 3488: 43 a1 eb 44 14 dc 03 7b 2d 0f 05 06 1f 2a 07 d9 C..D....-....*.. +| 3504: ab ec bf 34 51 70 f3 61 62 63 30 31 35 78 79 7a ...4Qp.abc015xyz +| 3520: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c3 ................ +| 3536: b6 3d f4 46 b1 6a af 2c 0e 05 06 1f 28 07 36 75 .=.F.j.,....(.6u +| 3552: e9 a2 bd 05 04 ea 61 62 63 30 31 34 78 79 7a 00 ......abc014xyz. +| 3568: 00 00 00 00 00 00 00 00 00 00 00 00 00 c3 ab 23 ...............# +| 3584: a7 6a 34 ca f8 2b 0d 05 06 1f 26 07 48 45 ab e0 .j4..+....&.HE.. +| 3600: 8c 7c ff 0c 61 62 63 30 31 33 78 79 7a 00 00 00 .|..abc013xyz... +| 3616: 00 00 00 00 0d d0 00 00 00 00 43 b8 d3 93 f4 92 ..........C..... +| 3632: 5b 7a 2a 0c 05 06 1f 24 07 be 6d 1e db 61 5d 80 [z*....$..m..a]. +| 3648: 9f 61 62 63 30 31 32 78 79 7a 00 00 00 00 00 00 .abc012xyz...... +| 3664: 00 00 00 00 00 00 43 b5 a1 a4 af 7b c6 60 29 0b ......C......`). +| 3680: 05 06 1f 22 07 6e a2 a3 64 68 d4 a6 bd 61 62 63 .....n..dh...abc +| 3696: 30 31 31 78 79 7a 00 00 00 00 00 00 00 00 00 00 011xyz.......... +| 3712: 00 c3 c4 1e ff 0f fc e6 ff 28 0a 05 06 1f 20 07 .........(.... . +| 3728: 50 f9 4a bb a5 7a 1e ca 61 62 63 30 31 30 78 79 P.J..z..abc010xy +| 3744: 7a 00 00 00 00 00 00 00 00 00 00 c3 a7 90 ed d9 z............... +| 3760: 5c 2c d5 27 09 05 06 1f 1e 07 90 8e 1d d9 1c 3a .,.'...........: +| 3776: e8 c1 61 62 63 30 30 39 78 79 7a 00 00 00 00 00 ..abc009xyz..... +| 3792: 00 00 00 00 43 a7 97 87 cf b0 ff 79 26 08 05 06 ....C......y&... +| 3808: 1f 1c 07 86 65 f6 7c 50 7a 2c 76 61 62 63 30 30 ....e.|Pz,vabc00 +| 3824: 38 78 79 7a 00 00 00 00 00 00 00 00 c3 b0 e3 4c 8xyz...........L +| 3840: 4f d3 41 b5 25 07 05 06 1f 1a 07 8b 20 e5 68 11 O.A.%....... .h. +| 3856: 13 55 87 61 62 63 30 30 37 78 79 7a 00 00 00 00 .U.abc007xyz.... +| 3872: 00 00 00 c3 b6 a3 74 f1 9c 33 f8 24 06 05 06 1f ......t..3.$.... +| 3888: 18 07 97 3c bc 34 49 94 54 ab 61 62 63 30 30 36 ...<.4I.T.abc006 +| 3904: 78 79 7a 00 00 00 00 00 00 c3 88 00 c2 ca 4c 4d xyz...........LM +| 3920: d3 23 05 05 06 1f 16 07 59 37 11 10 e9 e5 3d d5 .#......Y7....=. +| 3936: 61 62 63 30 30 35 78 79 7a 00 00 00 00 00 c3 c0 abc005xyz....... +| 3952: 15 12 67 ed 4b 79 22 04 05 06 1f 14 07 93 39 01 ..g.Ky........9. +| 3968: 7f b8 c7 99 58 61 62 63 30 30 34 78 79 7a 00 00 ....Xabc004xyz.. +| 3984: 09 c0 43 bf e0 e7 6d 70 fd 61 21 03 05 06 1f 12 ..C...mp.a!..... +| 4000: 07 b6 df 8d 8b 27 08 22 5a 61 62 63 30 30 33 78 .....'..Zabc003x +| 4016: 79 7a 00 00 00 c3 c7 ea 0f dc dd 32 22 20 02 05 yz.........2. .. +| 4032: 06 1f 10 07 2f a6 da 71 df 66 b3 b5 61 62 63 30 ..../..q.f..abc0 +| 4048: 30 32 78 79 7a 00 00 c3 ce d9 8d e9 ec 20 45 1f 02xyz........ E. +| 4064: 01 05 06 1f 0e 07 5a 47 53 20 3b 48 8f c0 61 62 ......ZGS ;H..ab +| 4080: 63 30 30 31 78 79 7a 00 c3 c9 e6 81 f8 d9 24 04 c001xyz.......$. +| page 3 offset 8192 +| 0: 0a 00 00 00 14 0e fd 00 0f f3 0f e6 0f d9 0f cc ................ +| 16: 0f bf 0f b2 0f a5 0f 98 0f 8b 0f 7e 0f 71 0f 64 ...........~.q.d +| 32: 0f 57 0f 4a 0f 3d 0f 30 0f 24 00 00 00 00 00 00 .W.J.=.0.$...... +| 3824: 00 00 00 00 00 00 00 00 00 00 00 00 00 0c 03 06 ................ +| 3840: 01 7f 46 91 03 3f 97 fb f7 11 0c 03 06 01 6e a2 ..F..?........n. +| 3856: a3 64 68 d4 a6 bd 0b 0c 03 06 01 6d 85 7b ce d0 .dh........m.... +| 3872: 32 d2 54 10 0b 03 06 09 5a 47 53 20 3b 48 8f c0 2.T.....ZGS ;H.. +| 3888: 0c 03 06 01 59 37 11 10 e9 e5 3d d5 05 0c 03 06 ....Y7....=..... +| 3904: 01 50 f9 4a bb a5 7a 1e ca 0a 0c 03 06 01 48 45 .P.J..z.......HE +| 3920: ab e0 8c 7c ff 0c 0d 0c 03 06 01 36 75 e9 a2 bd ...|.......6u... +| 3936: 05 04 ea 0e 0c 03 06 01 2f a6 da 71 df 66 b3 b5 ......../..q.f.. +| 3952: 02 0c 03 06 01 15 f4 c9 23 af e2 b3 b6 14 0c 03 ........#....... +| 3968: 06 01 dd f2 2a a5 7e b2 4d 82 13 0c 03 06 01 d9 ....*.~.M....... +| 3984: ab ec bf 34 51 70 f3 0f 0c 03 06 01 be 6d 1e db ...4Qp.......m.. +| 4000: 61 5d 80 9f 0c 0c 03 06 01 b6 df 8d 8b 27 08 22 a]...........'.. +| 4016: 5a 03 0c 03 06 01 97 3c bc 34 49 94 54 ab 06 0c Z......<.4I.T... +| 4032: 03 06 01 93 39 01 7f b8 c7 99 58 04 0c 03 06 01 ....9.....X..... +| 4048: 90 8e 1d d9 1c 3a e8 c1 09 0c 03 06 01 8f 8f f5 .....:.......... +| 4064: c4 35 b6 7f 8d 12 0c 03 06 01 8b 20 e5 68 11 13 .5......... .h.. +| 4080: 55 87 07 0c 03 06 01 86 65 f6 7c 50 7a 2b 06 08 U.......e.|Pz+.. +| page 4 offset 12288 +| 0: 0a 00 00 00 14 0f 62 00 0f 7a 0f a1 0f c9 0f d9 ......b..z...... +| 16: 0f 81 0f d1 0f f1 0f f9 0f e1 0f 89 0e 6a 0f c1 .............j.. +| 32: 0f 91 0f 99 0f b9 0f 72 0f 62 0f e9 0f b1 0f a9 .......r.b...... +| 3936: 00 00 07 04 01 01 01 11 0e 9e 07 04 01 01 01 0b ................ +| 3952: 31 16 07 04 01 01 01 10 37 36 06 04 09 01 01 ab 1.......76...... +| 3968: 58 07 04 01 01 01 05 1c 28 07 04 01 01 01 0a 10 X.......(....... +| 3984: cf 07 04 01 01 01 0d b2 e3 07 04 01 01 01 0e d3 ................ +| 4000: f2 07 04 01 01 01 02 41 ad 07 04 01 01 01 14 3e .......A.......> +| 4016: 22 07 04 01 01 01 13 27 45 07 04 01 01 01 0f ad .......'E....... +| 4032: dd 07 04 01 01 01 0c 2e a1 07 04 01 01 01 03 df ................ +| 4048: e1 07 04 01 01 01 06 59 a7 07 04 01 01 01 04 27 .......Y.......' +| 4064: bd 07 04 01 01 01 09 d0 e0 07 04 01 01 01 12 39 ...............9 +| 4080: 4f 07 04 01 01 01 07 c4 11 06 04 00 00 00 00 00 O............... +| end crash-7b75760a4c5f15.db +}]} {} + +do_test 5.1 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {0 {}} + +finish_test + diff --git a/ext/recover/recoverfault.test b/ext/recover/recoverfault.test new file mode 100644 index 000000000..30bb65527 --- /dev/null +++ b/ext/recover/recoverfault.test @@ -0,0 +1,84 @@ +# 2022 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoverfault + + +#-------------------------------------------------------------------------- +proc compare_result {db1 db2 sql} { + set r1 [$db1 eval $sql] + set r2 [$db2 eval $sql] + if {$r1 != $r2} { + puts "r1: $r1" + puts "r2: $r2" + error "mismatch for $sql" + } + return "" +} + +proc compare_dbs {db1 db2} { + compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1" + foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] { + compare_result $db1 $db2 "SELECT * FROM $tbl" + } +} +#-------------------------------------------------------------------------- + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(2, hex(randomblob(1000)), randomblob(2000)); + CREATE INDEX i1 ON t1(b, c); + ANALYZE; +} +faultsim_save_and_close + +do_faultsim_test 1 -faults oom* -prep { + catch { db2 close } + faultsim_restore_and_reopen +} -body { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} -test { + faultsim_test_result {0 {}} {1 {}} + if {$testrc==0} { + sqlite3 db2 test.db2 + compare_dbs db db2 + db2 close + } +} + +faultsim_restore_and_reopen +do_execsql_test 2.0 { + CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t2 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(2, hex(randomblob(1000)), hex(randomblob(2000))); + PRAGMA writable_schema = 1; + DELETE FROM sqlite_schema WHERE name='t2'; +} +faultsim_save_and_close + +do_faultsim_test 2 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + set R [sqlite3_recover_init db main test.db2] + $R config lostandfound lost_and_found + $R run + $R finish +} -test { + faultsim_test_result {0 {}} {1 {}} +} + +finish_test + diff --git a/ext/recover/recoverfault2.test b/ext/recover/recoverfault2.test new file mode 100644 index 000000000..e80d480ce --- /dev/null +++ b/ext/recover/recoverfault2.test @@ -0,0 +1,102 @@ +# 2022 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoverfault2 + + +#-------------------------------------------------------------------------- +proc compare_result {db1 db2 sql} { + set r1 [$db1 eval $sql] + set r2 [$db2 eval $sql] + if {$r1 != $r2} { + puts "r1: $r1" + puts "r2: $r2" + error "mismatch for $sql" + } + return "" +} + +proc compare_dbs {db1 db2} { + compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1" + foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] { + compare_result $db1 $db2 "SELECT * FROM $tbl" + } +} +#-------------------------------------------------------------------------- + +do_execsql_test 1.0 " + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(2, '\012hello\015world\012today\n'); +" +faultsim_save_and_close + +proc my_sql_hook {sql} { + lappend ::lSql $sql + return 0 +} + +do_faultsim_test 1 -faults oom* -prep { + catch { db2 close } + faultsim_restore_and_reopen + set ::lSql [list] +} -body { + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R run + $R finish +} -test { + faultsim_test_result {0 {}} {1 {}} + if {$testrc==0} { + sqlite3 db2 "" + db2 eval [join $::lSql ";"] + compare_dbs db db2 + db2 close + } +} + +ifcapable utf16 { + reset_db + do_execsql_test 2.0 " + PRAGMA encoding='utf-16'; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(2, '\012hello\015world\012today\n'); + " + faultsim_save_and_close + + proc my_sql_hook {sql} { + lappend ::lSql $sql + return 0 + } + + do_faultsim_test 2 -faults oom-t* -prep { + catch { db2 close } + faultsim_restore_and_reopen + set ::lSql [list] + } -body { + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R run + $R finish + } -test { + faultsim_test_result {0 {}} {1 {}} + if {$testrc==0} { + sqlite3 db2 "" + db2 eval [join $::lSql ";"] + compare_dbs db db2 + db2 close + } + } +} + + + +finish_test + diff --git a/ext/recover/recoverold.test b/ext/recover/recoverold.test new file mode 100644 index 000000000..c6acbb2f4 --- /dev/null +++ b/ext/recover/recoverold.test @@ -0,0 +1,189 @@ +# 2019 April 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. +# +#*********************************************************************** +# +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoverold + +proc compare_result {db1 db2 sql} { + set r1 [$db1 eval $sql] + set r2 [$db2 eval $sql] + if {$r1 != $r2} { + puts "sql: $sql" + puts "r1: $r1" + puts "r2: $r2" + error "mismatch for $sql" + } + return "" +} + +proc compare_dbs {db1 db2} { + compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1" + foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] { + compare_result $db1 $db2 "SELECT * FROM $tbl" + } +} + +proc do_recover_test {tn {tsql {}} {res {}}} { + forcedelete test.db2 + forcedelete rstate.db + + set R [sqlite3_recover_init db main test.db2] + $R config lostandfound lost_and_found + $R run + $R finish + + sqlite3 db2 test.db2 + + if {$tsql==""} { + uplevel [list do_test $tn.1 [list compare_dbs db db2] {}] + } else { + uplevel [list do_execsql_test -db db2 $tn.1 $tsql $res] + } + db2 close + + forcedelete test.db2 + forcedelete rstate.db + + set ::sqlhook [list] + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R config lostandfound lost_and_found + $R run + $R finish + + sqlite3 db2 test.db2 + db2 eval [join $::sqlhook ";"] + + + db cache flush + if {$tsql==""} { + compare_dbs db db2 + uplevel [list do_test $tn.sql [list compare_dbs db db2] {}] + } else { + uplevel [list do_execsql_test -db db2 $tn.sql $tsql $res] + } + db2 close +} + +proc my_sql_hook {sql} { + lappend ::sqlhook $sql + return 0 +} + + +set doc { + hello + world +} +do_execsql_test 1.1.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 4, X'1234567800'); + INSERT INTO t1 VALUES(2, 'test', 8.1); + INSERT INTO t1 VALUES(3, $doc, 8.4); +} +do_recover_test 1.1.2 + +do_execsql_test 1.2.1 " + DELETE FROM t1; + INSERT INTO t1 VALUES(13, 'hello\r\nworld', 13); +" +do_recover_test 1.2.2 + +do_execsql_test 1.3.1 " + CREATE TABLE t2(i INTEGER PRIMARY KEY AUTOINCREMENT, b, c); + INSERT INTO t2 VALUES(NULL, 1, 2); + INSERT INTO t2 VALUES(NULL, 3, 4); + INSERT INTO t2 VALUES(NULL, 5, 6); + CREATE TABLE t3(i INTEGER PRIMARY KEY AUTOINCREMENT, b, c); + INSERT INTO t3 VALUES(NULL, 1, 2); + INSERT INTO t3 VALUES(NULL, 3, 4); + INSERT INTO t3 VALUES(NULL, 5, 6); + DELETE FROM t2; +" +do_recover_test 1.3.2 + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.1.0 { + PRAGMA auto_vacuum = 0; + CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID; + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); + INSERT INTO t1 VALUES(7, 8, 9); +} + +do_recover_test 2.1.1 + +do_execsql_test 2.2.0 { + PRAGMA writable_schema = 1; + DELETE FROM sqlite_master WHERE name='t1'; +} +do_recover_test 2.2.1 { + SELECT name FROM sqlite_master +} {lost_and_found} + +do_execsql_test 2.3.0 { + CREATE TABLE lost_and_found(a, b, c); +} +do_recover_test 2.3.1 { + SELECT name FROM sqlite_master +} {lost_and_found lost_and_found_0} + +do_execsql_test 2.4.0 { + CREATE TABLE lost_and_found_0(a, b, c); +} +do_recover_test 2.4.1 { + SELECT name FROM sqlite_master; + SELECT * FROM lost_and_found_1; +} {lost_and_found lost_and_found_0 lost_and_found_1 + 2 2 3 {} 2 3 1 + 2 2 3 {} 5 6 4 + 2 2 3 {} 8 9 7 +} + +do_execsql_test 2.5 { + CREATE TABLE x1(a, b, c); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO x1 SELECT i, i, hex(randomblob(500)) FROM s; + DROP TABLE x1; +} +do_recover_test 2.5.1 { + SELECT name FROM sqlite_master; + SELECT * FROM lost_and_found_1; +} {lost_and_found lost_and_found_0 lost_and_found_1 + 2 2 3 {} 2 3 1 + 2 2 3 {} 5 6 4 + 2 2 3 {} 8 9 7 +} + +ifcapable !secure_delete { + do_test 2.6 { + forcedelete test.db2 + set R [sqlite3_recover_init db main test.db2] + $R config lostandfound lost_and_found + $R config freelistcorrupt 1 + $R run + $R finish + sqlite3 db2 test.db2 + execsql { SELECT count(*) FROM lost_and_found_1; } db2 + } {103} + db2 close +} + +#------------------------------------------------------------------------- +breakpoint +reset_db +do_recover_test 3.0 + +finish_test diff --git a/ext/recover/recoverpgsz.test b/ext/recover/recoverpgsz.test new file mode 100644 index 000000000..1a91f0845 --- /dev/null +++ b/ext/recover/recoverpgsz.test @@ -0,0 +1,100 @@ +# 2022 October 14 +# +# 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] + +db close +sqlite3_test_control_pending_byte 0x1000000 + +set testprefix recoverpgsz + +foreach {pgsz bOverflow} { + 512 0 1024 0 2048 0 4096 0 8192 0 16384 0 32768 0 65536 0 + 512 1 1024 1 2048 1 4096 1 8192 1 16384 1 32768 1 65536 1 +} { + reset_db + execsql "PRAGMA page_size = $pgsz" + execsql "PRAGMA auto_vacuum = 0" + do_execsql_test 1.$pgsz.$bOverflow.1 { + CREATE TABLE t1(a, b, c); + CREATE INDEX i1 ON t1(b, a, c); + INSERT INTO t1(a, b) VALUES(1, 2), (3, 4), (5, 6); + DELETE FROM t1 WHERE a=3; + } + if {$bOverflow} { + do_execsql_test 1.$pgsz.$bOverflow.1a { + UPDATE t1 SET c = randomblob(100000); + } + } + db close + + + set fd [open test.db] + fconfigure $fd -encoding binary -translation binary + seek $fd $pgsz + set pg1 [read $fd $pgsz] + set pg2 [read $fd $pgsz] + close $fd + + set fd2 [open test.db2 w] + fconfigure $fd2 -encoding binary -translation binary + seek $fd2 $pgsz + puts -nonewline $fd2 $pg1 + close $fd2 + + sqlite3 db2 test.db2 + do_test 1.$pgsz.$bOverflow.2 { + set R [sqlite3_recover_init db2 main test.db3] + $R run + $R finish + } {} + + sqlite3 db3 test.db3 + do_test 1.$pgsz.$bOverflow.3 { + db3 eval { SELECT * FROM sqlite_schema } + db3 eval { PRAGMA page_size } + } $pgsz + + db2 close + db3 close + + forcedelete test.db3 + forcedelete test.db2 + + set fd2 [open test.db2 w] + fconfigure $fd2 -encoding binary -translation binary + seek $fd2 $pgsz + puts -nonewline $fd2 $pg2 + close $fd2 + + sqlite3 db2 test.db2 + do_test 1.$pgsz.$bOverflow.4 { + set R [sqlite3_recover_init db2 main test.db3] + $R run + $R finish + } {} + + sqlite3 db3 test.db3 + do_test 1.$pgsz.$bOverflow.5 { + db3 eval { SELECT * FROM sqlite_schema } + db3 eval { PRAGMA page_size } + } $pgsz + + db2 close + db3 close +} + + +finish_test + + + diff --git a/ext/recover/recoverrowid.test b/ext/recover/recoverrowid.test new file mode 100644 index 000000000..5855e84fa --- /dev/null +++ b/ext/recover/recoverrowid.test @@ -0,0 +1,50 @@ +# 2022 September 07 +# +# 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. +# +#*********************************************************************** +# +# Tests for the SQLITE_RECOVER_ROWIDS option. +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoverrowid + +proc recover {db bRowids output} { + forcedelete $output + + set R [sqlite3_recover_init db main test.db2] + $R config rowids $bRowids + $R run + $R finish +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3), (4, 4); + DELETE FROM t1 WHERE a IN (1, 3); +} + +do_test 1.1 { + recover db 0 test.db2 + sqlite3 db2 test.db2 + execsql { SELECT rowid, a, b FROM t1 ORDER BY rowid} db2 +} {1 2 2 2 4 4} + +do_test 1.2 { + db2 close + recover db 1 test.db2 + sqlite3 db2 test.db2 + execsql { SELECT rowid, a, b FROM t1 ORDER BY rowid} db2 +} {2 2 2 4 4 4} +db2 close + + + + +finish_test diff --git a/ext/recover/recoverslowidx.test b/ext/recover/recoverslowidx.test new file mode 100644 index 000000000..269105113 --- /dev/null +++ b/ext/recover/recoverslowidx.test @@ -0,0 +1,87 @@ +# 2022 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. +# +#*********************************************************************** +# +# Tests for the SQLITE_RECOVER_SLOWINDEXES option. +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoverslowidx + +do_execsql_test 1.0 { + PRAGMA auto_vacuum = 0; + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a); + INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3), (4, 4); +} + +proc my_sql_hook {sql} { + lappend ::lSql $sql + return 0 +} + +do_test 1.1 { + set lSql [list] + set R [sqlite3_recover_init_sql db main my_sql_hook] + while {[$R step]==0} { } + $R finish +} {} + +do_test 1.2 { + set lSql +} [list {*}{ + {BEGIN} + {PRAGMA writable_schema = on} + {PRAGMA encoding = 'UTF-8'} + {PRAGMA page_size = '1024'} + {PRAGMA auto_vacuum = '0'} + {PRAGMA user_version = '0'} + {PRAGMA application_id = '0'} + {CREATE TABLE t1(a, b)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (1, 1, 1)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (2, 2, 2)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (3, 3, 3)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (4, 4, 4)} + {CREATE INDEX i1 ON t1(a)} + {PRAGMA writable_schema = off} + {COMMIT} +}] + +do_test 1.3 { + set lSql [list] + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R config slowindexes 1 + while {[$R step]==0} { } + $R finish +} {} + +do_test 1.4 { + set lSql +} [list {*}{ + {BEGIN} + {PRAGMA writable_schema = on} + {PRAGMA encoding = 'UTF-8'} + {PRAGMA page_size = '1024'} + {PRAGMA auto_vacuum = '0'} + {PRAGMA user_version = '0'} + {PRAGMA application_id = '0'} + {CREATE TABLE t1(a, b)} + {CREATE INDEX i1 ON t1(a)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (1, 1, 1)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (2, 2, 2)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (3, 3, 3)} + {INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (4, 4, 4)} + {PRAGMA writable_schema = off} + {COMMIT} +}] + + +finish_test + diff --git a/ext/recover/recoversql.test b/ext/recover/recoversql.test new file mode 100644 index 000000000..0a6726727 --- /dev/null +++ b/ext/recover/recoversql.test @@ -0,0 +1,52 @@ +# 2022 September 13 +# +# 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. +# +#*********************************************************************** +# +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoversql + +do_execsql_test 1.0 { + CREATE TABLE "x.1" (x, y); + INSERT INTO "x.1" VALUES(1, 1), (2, 2), (3, 3); + CREATE INDEX "i.1" ON "x.1"(y, x); +} + +proc sql_hook {sql} { + incr ::iSqlHook + if {$::iSqlHook==$::sql_hook_cnt} { return 4 } + return 0 +} + +do_test 1.1 { + set ::sql_hook_cnt -1 + set ::iSqlHook 0 + set R [sqlite3_recover_init_sql db main sql_hook] + $R run + $R finish +} {} + +set nSqlCall $iSqlHook + +for {set ii 1} {$ii<$nSqlCall} {incr ii} { + set iSqlHook 0 + set sql_hook_cnt $ii + do_test 1.$ii.a { + set R [sqlite3_recover_init_sql db main sql_hook] + $R run + } {1} + do_test 1.$ii.b { + list [catch { $R finish } msg] $msg + } {1 {callback returned an error - 4}} +} + + +finish_test diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c new file mode 100644 index 000000000..e62aba752 --- /dev/null +++ b/ext/recover/sqlite3recover.c @@ -0,0 +1,2861 @@ +/* +** 2022-08-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. +** +************************************************************************* +** +*/ + + +#include "sqlite3recover.h" +#include +#include + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Declaration for public API function in file dbdata.c. This may be called +** with NULL as the final two arguments to register the sqlite_dbptr and +** sqlite_dbdata virtual tables with a database handle. +*/ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_dbdata_init(sqlite3*, char**, const sqlite3_api_routines*); + +typedef unsigned int u32; +typedef unsigned char u8; +typedef sqlite3_int64 i64; + +typedef struct RecoverTable RecoverTable; +typedef struct RecoverColumn RecoverColumn; + +/* +** When recovering rows of data that can be associated with table +** definitions recovered from the sqlite_schema table, each table is +** represented by an instance of the following object. +** +** iRoot: +** The root page in the original database. Not necessarily (and usually +** not) the same in the recovered database. +** +** zTab: +** Name of the table. +** +** nCol/aCol[]: +** aCol[] is an array of nCol columns. In the order in which they appear +** in the table. +** +** bIntkey: +** Set to true for intkey tables, false for WITHOUT ROWID. +** +** iRowidBind: +** Each column in the aCol[] array has associated with it the index of +** the bind parameter its values will be bound to in the INSERT statement +** used to construct the output database. If the table does has a rowid +** but not an INTEGER PRIMARY KEY column, then iRowidBind contains the +** index of the bind paramater to which the rowid value should be bound. +** Otherwise, it contains -1. If the table does contain an INTEGER PRIMARY +** KEY column, then the rowid value should be bound to the index associated +** with the column. +** +** pNext: +** All RecoverTable objects used by the recovery operation are allocated +** and populated as part of creating the recovered database schema in +** the output database, before any non-schema data are recovered. They +** are then stored in a singly-linked list linked by this variable beginning +** at sqlite3_recover.pTblList. +*/ +struct RecoverTable { + u32 iRoot; /* Root page in original database */ + char *zTab; /* Name of table */ + int nCol; /* Number of columns in table */ + RecoverColumn *aCol; /* Array of columns */ + int bIntkey; /* True for intkey, false for without rowid */ + int iRowidBind; /* If >0, bind rowid to INSERT here */ + RecoverTable *pNext; +}; + +/* +** Each database column is represented by an instance of the following object +** stored in the RecoverTable.aCol[] array of the associated table. +** +** iField: +** The index of the associated field within database records. Or -1 if +** there is no associated field (e.g. for virtual generated columns). +** +** iBind: +** The bind index of the INSERT statement to bind this columns values +** to. Or 0 if there is no such index (iff (iField<0)). +** +** bIPK: +** True if this is the INTEGER PRIMARY KEY column. +** +** zCol: +** Name of column. +** +** eHidden: +** A RECOVER_EHIDDEN_* constant value (see below for interpretation of each). +*/ +struct RecoverColumn { + int iField; /* Field in record on disk */ + int iBind; /* Binding to use in INSERT */ + int bIPK; /* True for IPK column */ + char *zCol; + int eHidden; +}; + +#define RECOVER_EHIDDEN_NONE 0 /* Normal database column */ +#define RECOVER_EHIDDEN_HIDDEN 1 /* Column is __HIDDEN__ */ +#define RECOVER_EHIDDEN_VIRTUAL 2 /* Virtual generated column */ +#define RECOVER_EHIDDEN_STORED 3 /* Stored generated column */ + +/* +** Bitmap object used to track pages in the input database. Allocated +** and manipulated only by the following functions: +** +** recoverBitmapAlloc() +** recoverBitmapFree() +** recoverBitmapSet() +** recoverBitmapQuery() +** +** nPg: +** Largest page number that may be stored in the bitmap. The range +** of valid keys is 1 to nPg, inclusive. +** +** aElem[]: +** Array large enough to contain a bit for each key. For key value +** iKey, the associated bit is the bit (iKey%32) of aElem[iKey/32]. +** In other words, the following is true if bit iKey is set, or +** false if it is clear: +** +** (aElem[iKey/32] & (1 << (iKey%32))) ? 1 : 0 +*/ +typedef struct RecoverBitmap RecoverBitmap; +struct RecoverBitmap { + i64 nPg; /* Size of bitmap */ + u32 aElem[1]; /* Array of 32-bit bitmasks */ +}; + +/* +** State variables (part of the sqlite3_recover structure) used while +** recovering data for tables identified in the recovered schema (state +** RECOVER_STATE_WRITING). +*/ +typedef struct RecoverStateW1 RecoverStateW1; +struct RecoverStateW1 { + sqlite3_stmt *pTbls; + sqlite3_stmt *pSel; + sqlite3_stmt *pInsert; + int nInsert; + + RecoverTable *pTab; /* Table currently being written */ + int nMax; /* Max column count in any schema table */ + sqlite3_value **apVal; /* Array of nMax values */ + int nVal; /* Number of valid entries in apVal[] */ + int bHaveRowid; + i64 iRowid; + i64 iPrevPage; + int iPrevCell; +}; + +/* +** State variables (part of the sqlite3_recover structure) used while +** recovering data destined for the lost and found table (states +** RECOVER_STATE_LOSTANDFOUND[123]). +*/ +typedef struct RecoverStateLAF RecoverStateLAF; +struct RecoverStateLAF { + RecoverBitmap *pUsed; + i64 nPg; /* Size of db in pages */ + sqlite3_stmt *pAllAndParent; + sqlite3_stmt *pMapInsert; + sqlite3_stmt *pMaxField; + sqlite3_stmt *pUsedPages; + sqlite3_stmt *pFindRoot; + sqlite3_stmt *pInsert; /* INSERT INTO lost_and_found ... */ + sqlite3_stmt *pAllPage; + sqlite3_stmt *pPageData; + sqlite3_value **apVal; + int nMaxField; +}; + +/* +** Main recover handle structure. +*/ +struct sqlite3_recover { + /* Copies of sqlite3_recover_init[_sql]() parameters */ + sqlite3 *dbIn; /* Input database */ + char *zDb; /* Name of input db ("main" etc.) */ + char *zUri; /* URI for output database */ + void *pSqlCtx; /* SQL callback context */ + int (*xSql)(void*,const char*); /* Pointer to SQL callback function */ + + /* Values configured by sqlite3_recover_config() */ + char *zStateDb; /* State database to use (or NULL) */ + char *zLostAndFound; /* Name of lost-and-found table (or NULL) */ + int bFreelistCorrupt; /* SQLITE_RECOVER_FREELIST_CORRUPT setting */ + int bRecoverRowid; /* SQLITE_RECOVER_ROWIDS setting */ + int bSlowIndexes; /* SQLITE_RECOVER_SLOWINDEXES setting */ + + int pgsz; + int detected_pgsz; + int nReserve; + u8 *pPage1Disk; + u8 *pPage1Cache; + + /* Error code and error message */ + int errCode; /* For sqlite3_recover_errcode() */ + char *zErrMsg; /* For sqlite3_recover_errmsg() */ + + int eState; + int bCloseTransaction; + + /* Variables used with eState==RECOVER_STATE_WRITING */ + RecoverStateW1 w1; + + /* Variables used with states RECOVER_STATE_LOSTANDFOUND[123] */ + RecoverStateLAF laf; + + /* Fields used within sqlite3_recover_run() */ + sqlite3 *dbOut; /* Output database */ + sqlite3_stmt *pGetPage; /* SELECT against input db sqlite_dbdata */ + RecoverTable *pTblList; /* List of tables recovered from schema */ +}; + +/* +** The various states in which an sqlite3_recover object may exist: +** +** RECOVER_STATE_INIT: +** The object is initially created in this state. sqlite3_recover_step() +** has yet to be called. This is the only state in which it is permitted +** to call sqlite3_recover_config(). +** +** RECOVER_STATE_WRITING: +** +** RECOVER_STATE_LOSTANDFOUND1: +** State to populate the bitmap of pages used by other tables or the +** database freelist. +** +** RECOVER_STATE_LOSTANDFOUND2: +** Populate the recovery.map table - used to figure out a "root" page +** for each lost page from in the database from which records are +** extracted. +** +** RECOVER_STATE_LOSTANDFOUND3: +** Populate the lost-and-found table itself. +*/ +#define RECOVER_STATE_INIT 0 +#define RECOVER_STATE_WRITING 1 +#define RECOVER_STATE_LOSTANDFOUND1 2 +#define RECOVER_STATE_LOSTANDFOUND2 3 +#define RECOVER_STATE_LOSTANDFOUND3 4 +#define RECOVER_STATE_SCHEMA2 5 +#define RECOVER_STATE_DONE 6 + + +/* +** Global variables used by this extension. +*/ +typedef struct RecoverGlobal RecoverGlobal; +struct RecoverGlobal { + const sqlite3_io_methods *pMethods; + sqlite3_recover *p; +}; +static RecoverGlobal recover_g; + +/* +** Use this static SQLite mutex to protect the globals during the +** first call to sqlite3_recover_step(). +*/ +#define RECOVER_MUTEX_ID SQLITE_MUTEX_STATIC_APP2 + + +/* +** Default value for SQLITE_RECOVER_ROWIDS (sqlite3_recover.bRecoverRowid). +*/ +#define RECOVER_ROWID_DEFAULT 1 + +/* +** Mutex handling: +** +** recoverEnterMutex() - Enter the recovery mutex +** recoverLeaveMutex() - Leave the recovery mutex +** recoverAssertMutexHeld() - Assert that the recovery mutex is held +*/ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 +# define recoverEnterMutex() +# define recoverLeaveMutex() +#else +static void recoverEnterMutex(void){ + sqlite3_mutex_enter(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)); +} +static void recoverLeaveMutex(void){ + sqlite3_mutex_leave(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)); +} +#endif +#if SQLITE_THREADSAFE+0>=1 && defined(SQLITE_DEBUG) +static void recoverAssertMutexHeld(void){ + assert( sqlite3_mutex_held(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)) ); +} +#else +# define recoverAssertMutexHeld() +#endif + + +/* +** Like strlen(). But handles NULL pointer arguments. +*/ +static int recoverStrlen(const char *zStr){ + if( zStr==0 ) return 0; + return (int)(strlen(zStr)&0x7fffffff); +} + +/* +** This function is a no-op if the recover handle passed as the first +** argument already contains an error (if p->errCode!=SQLITE_OK). +** +** Otherwise, an attempt is made to allocate, zero and return a buffer nByte +** bytes in size. If successful, a pointer to the new buffer is returned. Or, +** if an OOM error occurs, NULL is returned and the handle error code +** (p->errCode) set to SQLITE_NOMEM. +*/ +static void *recoverMalloc(sqlite3_recover *p, i64 nByte){ + void *pRet = 0; + assert( nByte>0 ); + if( p->errCode==SQLITE_OK ){ + pRet = sqlite3_malloc64(nByte); + if( pRet ){ + memset(pRet, 0, nByte); + }else{ + p->errCode = SQLITE_NOMEM; + } + } + return pRet; +} + +/* +** Set the error code and error message for the recover handle passed as +** the first argument. The error code is set to the value of parameter +** errCode. +** +** Parameter zFmt must be a printf() style formatting string. The handle +** error message is set to the result of using any trailing arguments for +** parameter substitutions in the formatting string. +** +** For example: +** +** recoverError(p, SQLITE_ERROR, "no such table: %s", zTablename); +*/ +static int recoverError( + sqlite3_recover *p, + int errCode, + const char *zFmt, ... +){ + char *z = 0; + va_list ap; + va_start(ap, zFmt); + if( zFmt ){ + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + } + sqlite3_free(p->zErrMsg); + p->zErrMsg = z; + p->errCode = errCode; + return errCode; +} + + +/* +** This function is a no-op if p->errCode is initially other than SQLITE_OK. +** In this case it returns NULL. +** +** Otherwise, an attempt is made to allocate and return a bitmap object +** large enough to store a bit for all page numbers between 1 and nPg, +** inclusive. The bitmap is initially zeroed. +*/ +static RecoverBitmap *recoverBitmapAlloc(sqlite3_recover *p, i64 nPg){ + int nElem = (nPg+1+31) / 32; + int nByte = sizeof(RecoverBitmap) + nElem*sizeof(u32); + RecoverBitmap *pRet = (RecoverBitmap*)recoverMalloc(p, nByte); + + if( pRet ){ + pRet->nPg = nPg; + } + return pRet; +} + +/* +** Free a bitmap object allocated by recoverBitmapAlloc(). +*/ +static void recoverBitmapFree(RecoverBitmap *pMap){ + sqlite3_free(pMap); +} + +/* +** Set the bit associated with page iPg in bitvec pMap. +*/ +static void recoverBitmapSet(RecoverBitmap *pMap, i64 iPg){ + if( iPg<=pMap->nPg ){ + int iElem = (iPg / 32); + int iBit = (iPg % 32); + pMap->aElem[iElem] |= (((u32)1) << iBit); + } +} + +/* +** Query bitmap object pMap for the state of the bit associated with page +** iPg. Return 1 if it is set, or 0 otherwise. +*/ +static int recoverBitmapQuery(RecoverBitmap *pMap, i64 iPg){ + int ret = 1; + if( iPg<=pMap->nPg && iPg>0 ){ + int iElem = (iPg / 32); + int iBit = (iPg % 32); + ret = (pMap->aElem[iElem] & (((u32)1) << iBit)) ? 1 : 0; + } + return ret; +} + +/* +** Set the recover handle error to the error code and message returned by +** calling sqlite3_errcode() and sqlite3_errmsg(), respectively, on database +** handle db. +*/ +static int recoverDbError(sqlite3_recover *p, sqlite3 *db){ + return recoverError(p, sqlite3_errcode(db), "%s", sqlite3_errmsg(db)); +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). +** +** Otherwise, it attempts to prepare the SQL statement in zSql against +** database handle db. If successful, the statement handle is returned. +** Or, if an error occurs, NULL is returned and an error left in the +** recover handle. +*/ +static sqlite3_stmt *recoverPrepare( + sqlite3_recover *p, + sqlite3 *db, + const char *zSql +){ + sqlite3_stmt *pStmt = 0; + if( p->errCode==SQLITE_OK ){ + if( sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) ){ + recoverDbError(p, db); + } + } + return pStmt; +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). +** +** Otherwise, argument zFmt is used as a printf() style format string, +** along with any trailing arguments, to create an SQL statement. This +** SQL statement is prepared against database handle db and, if successful, +** the statment handle returned. Or, if an error occurs - either during +** the printf() formatting or when preparing the resulting SQL - an +** error code and message are left in the recover handle. +*/ +static sqlite3_stmt *recoverPreparePrintf( + sqlite3_recover *p, + sqlite3 *db, + const char *zFmt, ... +){ + sqlite3_stmt *pStmt = 0; + if( p->errCode==SQLITE_OK ){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( z==0 ){ + p->errCode = SQLITE_NOMEM; + }else{ + pStmt = recoverPrepare(p, db, z); + sqlite3_free(z); + } + } + return pStmt; +} + +/* +** Reset SQLite statement handle pStmt. If the call to sqlite3_reset() +** indicates that an error occurred, and there is not already an error +** in the recover handle passed as the first argument, set the error +** code and error message appropriately. +** +** This function returns a copy of the statement handle pointer passed +** as the second argument. +*/ +static sqlite3_stmt *recoverReset(sqlite3_recover *p, sqlite3_stmt *pStmt){ + int rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT && p->errCode==SQLITE_OK ){ + recoverDbError(p, sqlite3_db_handle(pStmt)); + } + return pStmt; +} + +/* +** Finalize SQLite statement handle pStmt. If the call to sqlite3_reset() +** indicates that an error occurred, and there is not already an error +** in the recover handle passed as the first argument, set the error +** code and error message appropriately. +*/ +static void recoverFinalize(sqlite3_recover *p, sqlite3_stmt *pStmt){ + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc = sqlite3_finalize(pStmt); + if( rc!=SQLITE_OK && p->errCode==SQLITE_OK ){ + recoverDbError(p, db); + } +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). A copy of p->errCode is returned in this +** case. +** +** Otherwise, execute SQL script zSql. If successful, return SQLITE_OK. +** Or, if an error occurs, leave an error code and message in the recover +** handle and return a copy of the error code. +*/ +static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){ + if( p->errCode==SQLITE_OK ){ + int rc = sqlite3_exec(db, zSql, 0, 0, 0); + if( rc ){ + recoverDbError(p, db); + } + } + return p->errCode; +} + +/* +** Bind the value pVal to parameter iBind of statement pStmt. Leave an +** error in the recover handle passed as the first argument if an error +** (e.g. an OOM) occurs. +*/ +static void recoverBindValue( + sqlite3_recover *p, + sqlite3_stmt *pStmt, + int iBind, + sqlite3_value *pVal +){ + if( p->errCode==SQLITE_OK ){ + int rc = sqlite3_bind_value(pStmt, iBind, pVal); + if( rc ) recoverError(p, rc, 0); + } +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). NULL is returned in this case. +** +** Otherwise, an attempt is made to interpret zFmt as a printf() style +** formatting string and the result of using the trailing arguments for +** parameter substitution with it written into a buffer obtained from +** sqlite3_malloc(). If successful, a pointer to the buffer is returned. +** It is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +** +** Or, if an error occurs, an error code and message is left in the recover +** handle and NULL returned. +*/ +static char *recoverMPrintf(sqlite3_recover *p, const char *zFmt, ...){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( p->errCode==SQLITE_OK ){ + if( z==0 ) p->errCode = SQLITE_NOMEM; + }else{ + sqlite3_free(z); + z = 0; + } + return z; +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). Zero is returned in this case. +** +** Otherwise, execute "PRAGMA page_count" against the input database. If +** successful, return the integer result. Or, if an error occurs, leave an +** error code and error message in the sqlite3_recover handle and return +** zero. +*/ +static i64 recoverPageCount(sqlite3_recover *p){ + i64 nPg = 0; + if( p->errCode==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + pStmt = recoverPreparePrintf(p, p->dbIn, "PRAGMA %Q.page_count", p->zDb); + if( pStmt ){ + sqlite3_step(pStmt); + nPg = sqlite3_column_int64(pStmt, 0); + } + recoverFinalize(p, pStmt); + } + return nPg; +} + +/* +** Implementation of SQL scalar function "read_i32". The first argument to +** this function must be a blob. The second a non-negative integer. This +** function reads and returns a 32-bit big-endian integer from byte +** offset (4*) of the blob. +** +** SELECT read_i32(, ) +*/ +static void recoverReadI32( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *pBlob; + int nBlob; + int iInt; + + assert( argc==2 ); + nBlob = sqlite3_value_bytes(argv[0]); + pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]); + iInt = sqlite3_value_int(argv[1]) & 0xFFFF; + + if( (iInt+1)*4<=nBlob ){ + const unsigned char *a = &pBlob[iInt*4]; + i64 iVal = ((i64)a[0]<<24) + + ((i64)a[1]<<16) + + ((i64)a[2]<< 8) + + ((i64)a[3]<< 0); + sqlite3_result_int64(context, iVal); + } +} + +/* +** Implementation of SQL scalar function "page_is_used". This function +** is used as part of the procedure for locating orphan rows for the +** lost-and-found table, and it depends on those routines having populated +** the sqlite3_recover.laf.pUsed variable. +** +** The only argument to this function is a page-number. It returns true +** if the page has already been used somehow during data recovery, or false +** otherwise. +** +** SELECT page_is_used(); +*/ +static void recoverPageIsUsed( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + sqlite3_recover *p = (sqlite3_recover*)sqlite3_user_data(pCtx); + i64 pgno = sqlite3_value_int64(apArg[0]); + assert( nArg==1 ); + sqlite3_result_int(pCtx, recoverBitmapQuery(p->laf.pUsed, pgno)); +} + +/* +** The implementation of a user-defined SQL function invoked by the +** sqlite_dbdata and sqlite_dbptr virtual table modules to access pages +** of the database being recovered. +** +** This function always takes a single integer argument. If the argument +** is zero, then the value returned is the number of pages in the db being +** recovered. If the argument is greater than zero, it is a page number. +** The value returned in this case is an SQL blob containing the data for +** the identified page of the db being recovered. e.g. +** +** SELECT getpage(0); -- return number of pages in db +** SELECT getpage(4); -- return page 4 of db as a blob of data +*/ +static void recoverGetPage( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + sqlite3_recover *p = (sqlite3_recover*)sqlite3_user_data(pCtx); + i64 pgno = sqlite3_value_int64(apArg[0]); + sqlite3_stmt *pStmt = 0; + + assert( nArg==1 ); + if( pgno==0 ){ + i64 nPg = recoverPageCount(p); + sqlite3_result_int64(pCtx, nPg); + return; + }else{ + if( p->pGetPage==0 ){ + pStmt = p->pGetPage = recoverPreparePrintf( + p, p->dbIn, "SELECT data FROM sqlite_dbpage(%Q) WHERE pgno=?", p->zDb + ); + }else if( p->errCode==SQLITE_OK ){ + pStmt = p->pGetPage; + } + + if( pStmt ){ + sqlite3_bind_int64(pStmt, 1, pgno); + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + const u8 *aPg; + int nPg; + assert( p->errCode==SQLITE_OK ); + aPg = sqlite3_column_blob(pStmt, 0); + nPg = sqlite3_column_bytes(pStmt, 0); + if( pgno==1 && nPg==p->pgsz && 0==memcmp(p->pPage1Cache, aPg, nPg) ){ + aPg = p->pPage1Disk; + } + sqlite3_result_blob(pCtx, aPg, nPg-p->nReserve, SQLITE_TRANSIENT); + } + recoverReset(p, pStmt); + } + } + + if( p->errCode ){ + if( p->zErrMsg ) sqlite3_result_error(pCtx, p->zErrMsg, -1); + sqlite3_result_error_code(pCtx, p->errCode); + } +} + +/* +** Find a string that is not found anywhere in z[]. Return a pointer +** to that string. +** +** Try to use zA and zB first. If both of those are already found in z[] +** then make up some string and store it in the buffer zBuf. +*/ +static const char *recoverUnusedString( + const char *z, /* Result must not appear anywhere in z */ + const char *zA, const char *zB, /* Try these first */ + char *zBuf /* Space to store a generated string */ +){ + unsigned i = 0; + if( strstr(z, zA)==0 ) return zA; + if( strstr(z, zB)==0 ) return zB; + do{ + sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++); + }while( strstr(z,zBuf)!=0 ); + return zBuf; +} + +/* +** Implementation of scalar SQL function "escape_crnl". The argument passed to +** this function is the output of built-in function quote(). If the first +** character of the input is "'", indicating that the value passed to quote() +** was a text value, then this function searches the input for "\n" and "\r" +** characters and adds a wrapper similar to the following: +** +** replace(replace(, '\n', char(10), '\r', char(13)); +** +** Or, if the first character of the input is not "'", then a copy of the input +** is returned. +*/ +static void recoverEscapeCrnl( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zText = (const char*)sqlite3_value_text(argv[0]); + if( zText && zText[0]=='\'' ){ + int nText = sqlite3_value_bytes(argv[0]); + int i; + char zBuf1[20]; + char zBuf2[20]; + const char *zNL = 0; + const char *zCR = 0; + int nCR = 0; + int nNL = 0; + + for(i=0; zText[i]; i++){ + if( zNL==0 && zText[i]=='\n' ){ + zNL = recoverUnusedString(zText, "\\n", "\\012", zBuf1); + nNL = (int)strlen(zNL); + } + if( zCR==0 && zText[i]=='\r' ){ + zCR = recoverUnusedString(zText, "\\r", "\\015", zBuf2); + nCR = (int)strlen(zCR); + } + } + + if( zNL || zCR ){ + int iOut = 0; + i64 nMax = (nNL > nCR) ? nNL : nCR; + i64 nAlloc = nMax * nText + (nMax+64)*2; + char *zOut = (char*)sqlite3_malloc64(nAlloc); + if( zOut==0 ){ + sqlite3_result_error_nomem(context); + return; + } + + if( zNL && zCR ){ + memcpy(&zOut[iOut], "replace(replace(", 16); + iOut += 16; + }else{ + memcpy(&zOut[iOut], "replace(", 8); + iOut += 8; + } + for(i=0; zText[i]; i++){ + if( zText[i]=='\n' ){ + memcpy(&zOut[iOut], zNL, nNL); + iOut += nNL; + }else if( zText[i]=='\r' ){ + memcpy(&zOut[iOut], zCR, nCR); + iOut += nCR; + }else{ + zOut[iOut] = zText[i]; + iOut++; + } + } + + if( zNL ){ + memcpy(&zOut[iOut], ",'", 2); iOut += 2; + memcpy(&zOut[iOut], zNL, nNL); iOut += nNL; + memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12; + } + if( zCR ){ + memcpy(&zOut[iOut], ",'", 2); iOut += 2; + memcpy(&zOut[iOut], zCR, nCR); iOut += nCR; + memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12; + } + + sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT); + sqlite3_free(zOut); + return; + } + } + + sqlite3_result_value(context, argv[0]); +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in +** this case. +** +** Otherwise, attempt to populate temporary table "recovery.schema" with the +** parts of the database schema that can be extracted from the input database. +** +** If no error occurs, SQLITE_OK is returned. Otherwise, an error code +** and error message are left in the recover handle and a copy of the +** error code returned. It is not considered an error if part of all of +** the database schema cannot be recovered due to corruption. +*/ +static int recoverCacheSchema(sqlite3_recover *p){ + return recoverExec(p, p->dbOut, + "WITH RECURSIVE pages(p) AS (" + " SELECT 1" + " UNION" + " SELECT child FROM sqlite_dbptr('getpage()'), pages WHERE pgno=p" + ")" + "INSERT INTO recovery.schema SELECT" + " max(CASE WHEN field=0 THEN value ELSE NULL END)," + " max(CASE WHEN field=1 THEN value ELSE NULL END)," + " max(CASE WHEN field=2 THEN value ELSE NULL END)," + " max(CASE WHEN field=3 THEN value ELSE NULL END)," + " max(CASE WHEN field=4 THEN value ELSE NULL END)" + "FROM sqlite_dbdata('getpage()') WHERE pgno IN (" + " SELECT p FROM pages" + ") GROUP BY pgno, cell" + ); +} + +/* +** If this recover handle is not in SQL callback mode (i.e. was not created +** using sqlite3_recover_init_sql()) of if an error has already occurred, +** this function is a no-op. Otherwise, issue a callback with SQL statement +** zSql as the parameter. +** +** If the callback returns non-zero, set the recover handle error code to +** the value returned (so that the caller will abandon processing). +*/ +static void recoverSqlCallback(sqlite3_recover *p, const char *zSql){ + if( p->errCode==SQLITE_OK && p->xSql ){ + int res = p->xSql(p->pSqlCtx, zSql); + if( res ){ + recoverError(p, SQLITE_ERROR, "callback returned an error - %d", res); + } + } +} + +/* +** Transfer the following settings from the input database to the output +** database: +** +** + page-size, +** + auto-vacuum settings, +** + database encoding, +** + user-version (PRAGMA user_version), and +** + application-id (PRAGMA application_id), and +*/ +static void recoverTransferSettings(sqlite3_recover *p){ + const char *aPragma[] = { + "encoding", + "page_size", + "auto_vacuum", + "user_version", + "application_id" + }; + int ii; + + /* Truncate the output database to 0 pages in size. This is done by + ** opening a new, empty, temp db, then using the backup API to clobber + ** any existing output db with a copy of it. */ + if( p->errCode==SQLITE_OK ){ + sqlite3 *db2 = 0; + int rc = sqlite3_open("", &db2); + if( rc!=SQLITE_OK ){ + recoverDbError(p, db2); + return; + } + + for(ii=0; iidbIn, "PRAGMA %Q.%s", p->zDb, zPrag); + if( p->errCode==SQLITE_OK && sqlite3_step(p1)==SQLITE_ROW ){ + const char *zArg = (const char*)sqlite3_column_text(p1, 0); + char *z2 = recoverMPrintf(p, "PRAGMA %s = %Q", zPrag, zArg); + recoverSqlCallback(p, z2); + recoverExec(p, db2, z2); + sqlite3_free(z2); + if( zArg==0 ){ + recoverError(p, SQLITE_NOMEM, 0); + } + } + recoverFinalize(p, p1); + } + recoverExec(p, db2, "CREATE TABLE t1(a); DROP TABLE t1;"); + + if( p->errCode==SQLITE_OK ){ + sqlite3 *db = p->dbOut; + sqlite3_backup *pBackup = sqlite3_backup_init(db, "main", db2, "main"); + if( pBackup ){ + sqlite3_backup_step(pBackup, -1); + p->errCode = sqlite3_backup_finish(pBackup); + }else{ + recoverDbError(p, db); + } + } + + sqlite3_close(db2); + } +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in +** this case. +** +** Otherwise, an attempt is made to open the output database, attach +** and create the schema of the temporary database used to store +** intermediate data, and to register all required user functions and +** virtual table modules with the output handle. +** +** If no error occurs, SQLITE_OK is returned. Otherwise, an error code +** and error message are left in the recover handle and a copy of the +** error code returned. +*/ +static int recoverOpenOutput(sqlite3_recover *p){ + struct Func { + const char *zName; + int nArg; + void (*xFunc)(sqlite3_context*,int,sqlite3_value **); + } aFunc[] = { + { "getpage", 1, recoverGetPage }, + { "page_is_used", 1, recoverPageIsUsed }, + { "read_i32", 2, recoverReadI32 }, + { "escape_crnl", 1, recoverEscapeCrnl }, + }; + + const int flags = SQLITE_OPEN_URI|SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE; + sqlite3 *db = 0; /* New database handle */ + int ii; /* For iterating through aFunc[] */ + + assert( p->dbOut==0 ); + + if( sqlite3_open_v2(p->zUri, &db, flags, 0) ){ + recoverDbError(p, db); + } + + /* Register the sqlite_dbdata and sqlite_dbptr virtual table modules. + ** These two are registered with the output database handle - this + ** module depends on the input handle supporting the sqlite_dbpage + ** virtual table only. */ + if( p->errCode==SQLITE_OK ){ + p->errCode = sqlite3_dbdata_init(db, 0, 0); + } + + /* Register the custom user-functions with the output handle. */ + for(ii=0; p->errCode==SQLITE_OK && iierrCode = sqlite3_create_function(db, aFunc[ii].zName, + aFunc[ii].nArg, SQLITE_UTF8, (void*)p, aFunc[ii].xFunc, 0, 0 + ); + } + + p->dbOut = db; + return p->errCode; +} + +/* +** Attach the auxiliary database 'recovery' to the output database handle. +** This temporary database is used during the recovery process and then +** discarded. +*/ +static void recoverOpenRecovery(sqlite3_recover *p){ + char *zSql = recoverMPrintf(p, "ATTACH %Q AS recovery;", p->zStateDb); + recoverExec(p, p->dbOut, zSql); + recoverExec(p, p->dbOut, + "PRAGMA writable_schema = 1;" + "CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT);" + "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" + ); + sqlite3_free(zSql); +} + + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). +** +** Otherwise, argument zName must be the name of a table that has just been +** created in the output database. This function queries the output db +** for the schema of said table, and creates a RecoverTable object to +** store the schema in memory. The new RecoverTable object is linked into +** the list at sqlite3_recover.pTblList. +** +** Parameter iRoot must be the root page of table zName in the INPUT +** database. +*/ +static void recoverAddTable( + sqlite3_recover *p, + const char *zName, /* Name of table created in output db */ + i64 iRoot /* Root page of same table in INPUT db */ +){ + sqlite3_stmt *pStmt = recoverPreparePrintf(p, p->dbOut, + "PRAGMA table_xinfo(%Q)", zName + ); + + if( pStmt ){ + int iPk = -1; + int iBind = 1; + RecoverTable *pNew = 0; + int nCol = 0; + int nName = recoverStrlen(zName); + int nByte = 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + nCol++; + nByte += (sqlite3_column_bytes(pStmt, 1)+1); + } + nByte += sizeof(RecoverTable) + nCol*sizeof(RecoverColumn) + nName+1; + recoverReset(p, pStmt); + + pNew = recoverMalloc(p, nByte); + if( pNew ){ + int i = 0; + int iField = 0; + char *csr = 0; + pNew->aCol = (RecoverColumn*)&pNew[1]; + pNew->zTab = csr = (char*)&pNew->aCol[nCol]; + pNew->nCol = nCol; + pNew->iRoot = iRoot; + memcpy(csr, zName, nName); + csr += nName+1; + + for(i=0; sqlite3_step(pStmt)==SQLITE_ROW; i++){ + int iPKF = sqlite3_column_int(pStmt, 5); + int n = sqlite3_column_bytes(pStmt, 1); + const char *z = (const char*)sqlite3_column_text(pStmt, 1); + const char *zType = (const char*)sqlite3_column_text(pStmt, 2); + int eHidden = sqlite3_column_int(pStmt, 6); + + if( iPk==-1 && iPKF==1 && !sqlite3_stricmp("integer", zType) ) iPk = i; + if( iPKF>1 ) iPk = -2; + pNew->aCol[i].zCol = csr; + pNew->aCol[i].eHidden = eHidden; + if( eHidden==RECOVER_EHIDDEN_VIRTUAL ){ + pNew->aCol[i].iField = -1; + }else{ + pNew->aCol[i].iField = iField++; + } + if( eHidden!=RECOVER_EHIDDEN_VIRTUAL + && eHidden!=RECOVER_EHIDDEN_STORED + ){ + pNew->aCol[i].iBind = iBind++; + } + memcpy(csr, z, n); + csr += (n+1); + } + + pNew->pNext = p->pTblList; + p->pTblList = pNew; + pNew->bIntkey = 1; + } + + recoverFinalize(p, pStmt); + + pStmt = recoverPreparePrintf(p, p->dbOut, "PRAGMA index_xinfo(%Q)", zName); + while( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ + int iField = sqlite3_column_int(pStmt, 0); + int iCol = sqlite3_column_int(pStmt, 1); + + assert( iFieldnCol && iColnCol ); + pNew->aCol[iCol].iField = iField; + + pNew->bIntkey = 0; + iPk = -2; + } + recoverFinalize(p, pStmt); + + if( p->errCode==SQLITE_OK ){ + if( iPk>=0 ){ + pNew->aCol[iPk].bIPK = 1; + }else if( pNew->bIntkey ){ + pNew->iRowidBind = iBind++; + } + } + } +} + +/* +** This function is called after recoverCacheSchema() has cached those parts +** of the input database schema that could be recovered in temporary table +** "recovery.schema". This function creates in the output database copies +** of all parts of that schema that must be created before the tables can +** be populated. Specifically, this means: +** +** * all tables that are not VIRTUAL, and +** * UNIQUE indexes. +** +** If the recovery handle uses SQL callbacks, then callbacks containing +** the associated "CREATE TABLE" and "CREATE INDEX" statements are made. +** +** Additionally, records are added to the sqlite_schema table of the +** output database for any VIRTUAL tables. The CREATE VIRTUAL TABLE +** records are written directly to sqlite_schema, not actually executed. +** If the handle is in SQL callback mode, then callbacks are invoked +** with equivalent SQL statements. +*/ +static int recoverWriteSchema1(sqlite3_recover *p){ + sqlite3_stmt *pSelect = 0; + sqlite3_stmt *pTblname = 0; + + pSelect = recoverPrepare(p, p->dbOut, + "WITH dbschema(rootpage, name, sql, tbl, isVirtual, isIndex) AS (" + " SELECT rootpage, name, sql, " + " type='table', " + " sql LIKE 'create virtual%'," + " (type='index' AND (sql LIKE '%unique%' OR ?1))" + " FROM recovery.schema" + ")" + "SELECT rootpage, tbl, isVirtual, name, sql" + " FROM dbschema " + " WHERE tbl OR isIndex" + " ORDER BY tbl DESC, name=='sqlite_sequence' DESC" + ); + + pTblname = recoverPrepare(p, p->dbOut, + "SELECT name FROM sqlite_schema " + "WHERE type='table' ORDER BY rowid DESC LIMIT 1" + ); + + if( pSelect ){ + sqlite3_bind_int(pSelect, 1, p->bSlowIndexes); + while( sqlite3_step(pSelect)==SQLITE_ROW ){ + i64 iRoot = sqlite3_column_int64(pSelect, 0); + int bTable = sqlite3_column_int(pSelect, 1); + int bVirtual = sqlite3_column_int(pSelect, 2); + const char *zName = (const char*)sqlite3_column_text(pSelect, 3); + const char *zSql = (const char*)sqlite3_column_text(pSelect, 4); + char *zFree = 0; + int rc = SQLITE_OK; + + if( bVirtual ){ + zSql = (const char*)(zFree = recoverMPrintf(p, + "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", + zName, zName, zSql + )); + } + rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); + if( rc==SQLITE_OK ){ + recoverSqlCallback(p, zSql); + if( bTable && !bVirtual ){ + if( SQLITE_ROW==sqlite3_step(pTblname) ){ + const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0); + recoverAddTable(p, zTbl, iRoot); + } + recoverReset(p, pTblname); + } + }else if( rc!=SQLITE_ERROR ){ + recoverDbError(p, p->dbOut); + } + sqlite3_free(zFree); + } + } + recoverFinalize(p, pSelect); + recoverFinalize(p, pTblname); + + return p->errCode; +} + +/* +** This function is called after the output database has been populated. It +** adds all recovered schema elements that were not created in the output +** database by recoverWriteSchema1() - everything except for tables and +** UNIQUE indexes. Specifically: +** +** * views, +** * triggers, +** * non-UNIQUE indexes. +** +** If the recover handle is in SQL callback mode, then equivalent callbacks +** are issued to create the schema elements. +*/ +static int recoverWriteSchema2(sqlite3_recover *p){ + sqlite3_stmt *pSelect = 0; + + pSelect = recoverPrepare(p, p->dbOut, + p->bSlowIndexes ? + "SELECT rootpage, sql FROM recovery.schema " + " WHERE type!='table' AND type!='index'" + : + "SELECT rootpage, sql FROM recovery.schema " + " WHERE type!='table' AND (type!='index' OR sql NOT LIKE '%unique%')" + ); + + if( pSelect ){ + while( sqlite3_step(pSelect)==SQLITE_ROW ){ + const char *zSql = (const char*)sqlite3_column_text(pSelect, 1); + int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); + if( rc==SQLITE_OK ){ + recoverSqlCallback(p, zSql); + }else if( rc!=SQLITE_ERROR ){ + recoverDbError(p, p->dbOut); + } + } + } + recoverFinalize(p, pSelect); + + return p->errCode; +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). In this case it returns NULL. +** +** Otherwise, if the recover handle is configured to create an output +** database (was created by sqlite3_recover_init()), then this function +** prepares and returns an SQL statement to INSERT a new record into table +** pTab, assuming the first nField fields of a record extracted from disk +** are valid. +** +** For example, if table pTab is: +** +** CREATE TABLE name(a, b GENERATED ALWAYS AS (a+1) STORED, c, d, e); +** +** And nField is 4, then the SQL statement prepared and returned is: +** +** INSERT INTO (a, c, d) VALUES (?1, ?2, ?3); +** +** In this case even though 4 values were extracted from the input db, +** only 3 are written to the output, as the generated STORED column +** cannot be written. +** +** If the recover handle is in SQL callback mode, then the SQL statement +** prepared is such that evaluating it returns a single row containing +** a single text value - itself an SQL statement similar to the above, +** except with SQL literals in place of the variables. For example: +** +** SELECT 'INSERT INTO (a, c, d) VALUES (' +** || quote(?1) || ', ' +** || quote(?2) || ', ' +** || quote(?3) || ')'; +** +** In either case, it is the responsibility of the caller to eventually +** free the statement handle using sqlite3_finalize(). +*/ +static sqlite3_stmt *recoverInsertStmt( + sqlite3_recover *p, + RecoverTable *pTab, + int nField +){ + sqlite3_stmt *pRet = 0; + const char *zSep = ""; + const char *zSqlSep = ""; + char *zSql = 0; + char *zFinal = 0; + char *zBind = 0; + int ii; + int bSql = p->xSql ? 1 : 0; + + if( nField<=0 ) return 0; + + assert( nField<=pTab->nCol ); + + zSql = recoverMPrintf(p, "INSERT OR IGNORE INTO %Q(", pTab->zTab); + + if( pTab->iRowidBind ){ + assert( pTab->bIntkey ); + zSql = recoverMPrintf(p, "%z_rowid_", zSql); + if( bSql ){ + zBind = recoverMPrintf(p, "%zquote(?%d)", zBind, pTab->iRowidBind); + }else{ + zBind = recoverMPrintf(p, "%z?%d", zBind, pTab->iRowidBind); + } + zSqlSep = "||', '||"; + zSep = ", "; + } + + for(ii=0; iiaCol[ii].eHidden; + if( eHidden!=RECOVER_EHIDDEN_VIRTUAL + && eHidden!=RECOVER_EHIDDEN_STORED + ){ + assert( pTab->aCol[ii].iField>=0 && pTab->aCol[ii].iBind>=1 ); + zSql = recoverMPrintf(p, "%z%s%Q", zSql, zSep, pTab->aCol[ii].zCol); + + if( bSql ){ + zBind = recoverMPrintf(p, + "%z%sescape_crnl(quote(?%d))", zBind, zSqlSep, pTab->aCol[ii].iBind + ); + zSqlSep = "||', '||"; + }else{ + zBind = recoverMPrintf(p, "%z%s?%d", zBind, zSep, pTab->aCol[ii].iBind); + } + zSep = ", "; + } + } + + if( bSql ){ + zFinal = recoverMPrintf(p, "SELECT %Q || ') VALUES (' || %s || ')'", + zSql, zBind + ); + }else{ + zFinal = recoverMPrintf(p, "%s) VALUES (%s)", zSql, zBind); + } + + pRet = recoverPrepare(p, p->dbOut, zFinal); + sqlite3_free(zSql); + sqlite3_free(zBind); + sqlite3_free(zFinal); + + return pRet; +} + + +/* +** Search the list of RecoverTable objects at p->pTblList for one that +** has root page iRoot in the input database. If such an object is found, +** return a pointer to it. Otherwise, return NULL. +*/ +static RecoverTable *recoverFindTable(sqlite3_recover *p, u32 iRoot){ + RecoverTable *pRet = 0; + for(pRet=p->pTblList; pRet && pRet->iRoot!=iRoot; pRet=pRet->pNext); + return pRet; +} + +/* +** This function attempts to create a lost and found table within the +** output db. If successful, it returns a pointer to a buffer containing +** the name of the new table. It is the responsibility of the caller to +** eventually free this buffer using sqlite3_free(). +** +** If an error occurs, NULL is returned and an error code and error +** message left in the recover handle. +*/ +static char *recoverLostAndFoundCreate( + sqlite3_recover *p, /* Recover object */ + int nField /* Number of column fields in new table */ +){ + char *zTbl = 0; + sqlite3_stmt *pProbe = 0; + int ii = 0; + + pProbe = recoverPrepare(p, p->dbOut, + "SELECT 1 FROM sqlite_schema WHERE name=?" + ); + for(ii=-1; zTbl==0 && p->errCode==SQLITE_OK && ii<1000; ii++){ + int bFail = 0; + if( ii<0 ){ + zTbl = recoverMPrintf(p, "%s", p->zLostAndFound); + }else{ + zTbl = recoverMPrintf(p, "%s_%d", p->zLostAndFound, ii); + } + + if( p->errCode==SQLITE_OK ){ + sqlite3_bind_text(pProbe, 1, zTbl, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pProbe) ){ + bFail = 1; + } + recoverReset(p, pProbe); + } + + if( bFail ){ + sqlite3_clear_bindings(pProbe); + sqlite3_free(zTbl); + zTbl = 0; + } + } + recoverFinalize(p, pProbe); + + if( zTbl ){ + const char *zSep = 0; + char *zField = 0; + char *zSql = 0; + + zSep = "rootpgno INTEGER, pgno INTEGER, nfield INTEGER, id INTEGER, "; + for(ii=0; p->errCode==SQLITE_OK && iidbOut, zSql); + recoverSqlCallback(p, zSql); + sqlite3_free(zSql); + }else if( p->errCode==SQLITE_OK ){ + recoverError( + p, SQLITE_ERROR, "failed to create %s output table", p->zLostAndFound + ); + } + + return zTbl; +} + +/* +** Synthesize and prepare an INSERT statement to write to the lost_and_found +** table in the output database. The name of the table is zTab, and it has +** nField c* fields. +*/ +static sqlite3_stmt *recoverLostAndFoundInsert( + sqlite3_recover *p, + const char *zTab, + int nField +){ + int nTotal = nField + 4; + int ii; + char *zBind = 0; + sqlite3_stmt *pRet = 0; + + if( p->xSql==0 ){ + for(ii=0; iidbOut, "INSERT INTO %s VALUES(%s)", zTab, zBind + ); + }else{ + const char *zSep = ""; + for(ii=0; iidbOut, "SELECT 'INSERT INTO %s VALUES(' || %s || ')'", zTab, zBind + ); + } + + sqlite3_free(zBind); + return pRet; +} + +/* +** Input database page iPg contains data that will be written to the +** lost-and-found table of the output database. This function attempts +** to identify the root page of the tree that page iPg belonged to. +** If successful, it sets output variable (*piRoot) to the page number +** of the root page and returns SQLITE_OK. Otherwise, if an error occurs, +** an SQLite error code is returned and the final value of *piRoot +** undefined. +*/ +static int recoverLostAndFoundFindRoot( + sqlite3_recover *p, + i64 iPg, + i64 *piRoot +){ + RecoverStateLAF *pLaf = &p->laf; + + if( pLaf->pFindRoot==0 ){ + pLaf->pFindRoot = recoverPrepare(p, p->dbOut, + "WITH RECURSIVE p(pgno) AS (" + " SELECT ?" + " UNION" + " SELECT parent FROM recovery.map AS m, p WHERE m.pgno=p.pgno" + ") " + "SELECT p.pgno FROM p, recovery.map m WHERE m.pgno=p.pgno " + " AND m.parent IS NULL" + ); + } + if( p->errCode==SQLITE_OK ){ + sqlite3_bind_int64(pLaf->pFindRoot, 1, iPg); + if( sqlite3_step(pLaf->pFindRoot)==SQLITE_ROW ){ + *piRoot = sqlite3_column_int64(pLaf->pFindRoot, 0); + }else{ + *piRoot = iPg; + } + recoverReset(p, pLaf->pFindRoot); + } + return p->errCode; +} + +/* +** Recover data from page iPage of the input database and write it to +** the lost-and-found table in the output database. +*/ +static void recoverLostAndFoundOnePage(sqlite3_recover *p, i64 iPage){ + RecoverStateLAF *pLaf = &p->laf; + sqlite3_value **apVal = pLaf->apVal; + sqlite3_stmt *pPageData = pLaf->pPageData; + sqlite3_stmt *pInsert = pLaf->pInsert; + + int nVal = -1; + int iPrevCell = 0; + i64 iRoot = 0; + int bHaveRowid = 0; + i64 iRowid = 0; + int ii = 0; + + if( recoverLostAndFoundFindRoot(p, iPage, &iRoot) ) return; + sqlite3_bind_int64(pPageData, 1, iPage); + while( p->errCode==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPageData) ){ + int iCell = sqlite3_column_int64(pPageData, 0); + int iField = sqlite3_column_int64(pPageData, 1); + + if( iPrevCell!=iCell && nVal>=0 ){ + /* Insert the new row */ + sqlite3_bind_int64(pInsert, 1, iRoot); /* rootpgno */ + sqlite3_bind_int64(pInsert, 2, iPage); /* pgno */ + sqlite3_bind_int(pInsert, 3, nVal); /* nfield */ + if( bHaveRowid ){ + sqlite3_bind_int64(pInsert, 4, iRowid); /* id */ + } + for(ii=0; iinMaxField ){ + sqlite3_value *pVal = sqlite3_column_value(pPageData, 2); + apVal[iField] = sqlite3_value_dup(pVal); + assert( iField==nVal || (nVal==-1 && iField==0) ); + nVal = iField+1; + if( apVal[iField]==0 ){ + recoverError(p, SQLITE_NOMEM, 0); + } + } + + iPrevCell = iCell; + } + recoverReset(p, pPageData); + + for(ii=0; iilaf; + if( p->errCode==SQLITE_OK ){ + if( pLaf->pInsert==0 ){ + return SQLITE_DONE; + }else{ + if( p->errCode==SQLITE_OK ){ + int res = sqlite3_step(pLaf->pAllPage); + if( res==SQLITE_ROW ){ + i64 iPage = sqlite3_column_int64(pLaf->pAllPage, 0); + if( recoverBitmapQuery(pLaf->pUsed, iPage)==0 ){ + recoverLostAndFoundOnePage(p, iPage); + } + }else{ + recoverReset(p, pLaf->pAllPage); + return SQLITE_DONE; + } + } + } + } + return SQLITE_OK; +} + +/* +** Initialize resources required in RECOVER_STATE_LOSTANDFOUND3 +** state - during which the lost-and-found table of the output database +** is populated with recovered data that can not be assigned to any +** recovered schema object. +*/ +static void recoverLostAndFound3Init(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + + if( pLaf->nMaxField>0 ){ + char *zTab = 0; /* Name of lost_and_found table */ + + zTab = recoverLostAndFoundCreate(p, pLaf->nMaxField); + pLaf->pInsert = recoverLostAndFoundInsert(p, zTab, pLaf->nMaxField); + sqlite3_free(zTab); + + pLaf->pAllPage = recoverPreparePrintf(p, p->dbOut, + "WITH RECURSIVE seq(ii) AS (" + " SELECT 1 UNION ALL SELECT ii+1 FROM seq WHERE ii<%lld" + ")" + "SELECT ii FROM seq" , p->laf.nPg + ); + pLaf->pPageData = recoverPrepare(p, p->dbOut, + "SELECT cell, field, value " + "FROM sqlite_dbdata('getpage()') d WHERE d.pgno=? " + "UNION ALL " + "SELECT -1, -1, -1" + ); + + pLaf->apVal = (sqlite3_value**)recoverMalloc(p, + pLaf->nMaxField*sizeof(sqlite3_value*) + ); + } +} + +/* +** Initialize resources required in RECOVER_STATE_WRITING state - during which +** tables recovered from the schema of the input database are populated with +** recovered data. +*/ +static int recoverWriteDataInit(sqlite3_recover *p){ + RecoverStateW1 *p1 = &p->w1; + RecoverTable *pTbl = 0; + int nByte = 0; + + /* Figure out the maximum number of columns for any table in the schema */ + assert( p1->nMax==0 ); + for(pTbl=p->pTblList; pTbl; pTbl=pTbl->pNext){ + if( pTbl->nCol>p1->nMax ) p1->nMax = pTbl->nCol; + } + + /* Allocate an array of (sqlite3_value*) in which to accumulate the values + ** that will be written to the output database in a single row. */ + nByte = sizeof(sqlite3_value*) * (p1->nMax+1); + p1->apVal = (sqlite3_value**)recoverMalloc(p, nByte); + if( p1->apVal==0 ) return p->errCode; + + /* Prepare the SELECT to loop through schema tables (pTbls) and the SELECT + ** to loop through cells that appear to belong to a single table (pSel). */ + p1->pTbls = recoverPrepare(p, p->dbOut, + "SELECT rootpage FROM recovery.schema " + " WHERE type='table' AND (sql NOT LIKE 'create virtual%')" + " ORDER BY (tbl_name='sqlite_sequence') ASC" + ); + p1->pSel = recoverPrepare(p, p->dbOut, + "WITH RECURSIVE pages(page) AS (" + " SELECT ?1" + " UNION" + " SELECT child FROM sqlite_dbptr('getpage()'), pages " + " WHERE pgno=page" + ") " + "SELECT page, cell, field, value " + "FROM sqlite_dbdata('getpage()') d, pages p WHERE p.page=d.pgno " + "UNION ALL " + "SELECT 0, 0, 0, 0" + ); + + return p->errCode; +} + +/* +** Clean up resources allocated by recoverWriteDataInit() (stuff in +** sqlite3_recover.w1). +*/ +static void recoverWriteDataCleanup(sqlite3_recover *p){ + RecoverStateW1 *p1 = &p->w1; + int ii; + for(ii=0; iinVal; ii++){ + sqlite3_value_free(p1->apVal[ii]); + } + sqlite3_free(p1->apVal); + recoverFinalize(p, p1->pInsert); + recoverFinalize(p, p1->pTbls); + recoverFinalize(p, p1->pSel); + memset(p1, 0, sizeof(*p1)); +} + +/* +** Perform one step (sqlite3_recover_step()) of work for the connection +** passed as the only argument, which is guaranteed to be in +** RECOVER_STATE_WRITING state - during which tables recovered from the +** schema of the input database are populated with recovered data. +*/ +static int recoverWriteDataStep(sqlite3_recover *p){ + RecoverStateW1 *p1 = &p->w1; + sqlite3_stmt *pSel = p1->pSel; + sqlite3_value **apVal = p1->apVal; + + if( p->errCode==SQLITE_OK && p1->pTab==0 ){ + if( sqlite3_step(p1->pTbls)==SQLITE_ROW ){ + i64 iRoot = sqlite3_column_int64(p1->pTbls, 0); + p1->pTab = recoverFindTable(p, iRoot); + + recoverFinalize(p, p1->pInsert); + p1->pInsert = 0; + + /* If this table is unknown, return early. The caller will invoke this + ** function again and it will move on to the next table. */ + if( p1->pTab==0 ) return p->errCode; + + /* If this is the sqlite_sequence table, delete any rows added by + ** earlier INSERT statements on tables with AUTOINCREMENT primary + ** keys before recovering its contents. The p1->pTbls SELECT statement + ** is rigged to deliver "sqlite_sequence" last of all, so we don't + ** worry about it being modified after it is recovered. */ + if( sqlite3_stricmp("sqlite_sequence", p1->pTab->zTab)==0 ){ + recoverExec(p, p->dbOut, "DELETE FROM sqlite_sequence"); + recoverSqlCallback(p, "DELETE FROM sqlite_sequence"); + } + + /* Bind the root page of this table within the original database to + ** SELECT statement p1->pSel. The SELECT statement will then iterate + ** through cells that look like they belong to table pTab. */ + sqlite3_bind_int64(pSel, 1, iRoot); + + p1->nVal = 0; + p1->bHaveRowid = 0; + p1->iPrevPage = -1; + p1->iPrevCell = -1; + }else{ + return SQLITE_DONE; + } + } + assert( p->errCode!=SQLITE_OK || p1->pTab ); + + if( p->errCode==SQLITE_OK && sqlite3_step(pSel)==SQLITE_ROW ){ + RecoverTable *pTab = p1->pTab; + + i64 iPage = sqlite3_column_int64(pSel, 0); + int iCell = sqlite3_column_int(pSel, 1); + int iField = sqlite3_column_int(pSel, 2); + sqlite3_value *pVal = sqlite3_column_value(pSel, 3); + int bNewCell = (p1->iPrevPage!=iPage || p1->iPrevCell!=iCell); + + assert( bNewCell==0 || (iField==-1 || iField==0) ); + assert( bNewCell || iField==p1->nVal || p1->nVal==pTab->nCol ); + + if( bNewCell ){ + int ii = 0; + if( p1->nVal>=0 ){ + if( p1->pInsert==0 || p1->nVal!=p1->nInsert ){ + recoverFinalize(p, p1->pInsert); + p1->pInsert = recoverInsertStmt(p, pTab, p1->nVal); + p1->nInsert = p1->nVal; + } + if( p1->nVal>0 ){ + sqlite3_stmt *pInsert = p1->pInsert; + for(ii=0; iinCol; ii++){ + RecoverColumn *pCol = &pTab->aCol[ii]; + int iBind = pCol->iBind; + if( iBind>0 ){ + if( pCol->bIPK ){ + sqlite3_bind_int64(pInsert, iBind, p1->iRowid); + }else if( pCol->iFieldnVal ){ + recoverBindValue(p, pInsert, iBind, apVal[pCol->iField]); + } + } + } + if( p->bRecoverRowid && pTab->iRowidBind>0 && p1->bHaveRowid ){ + sqlite3_bind_int64(pInsert, pTab->iRowidBind, p1->iRowid); + } + if( SQLITE_ROW==sqlite3_step(pInsert) ){ + const char *z = (const char*)sqlite3_column_text(pInsert, 0); + recoverSqlCallback(p, z); + } + recoverReset(p, pInsert); + assert( p->errCode || pInsert ); + if( pInsert ) sqlite3_clear_bindings(pInsert); + } + } + + for(ii=0; iinVal; ii++){ + sqlite3_value_free(apVal[ii]); + apVal[ii] = 0; + } + p1->nVal = -1; + p1->bHaveRowid = 0; + } + + if( iPage!=0 ){ + if( iField<0 ){ + p1->iRowid = sqlite3_column_int64(pSel, 3); + assert( p1->nVal==-1 ); + p1->nVal = 0; + p1->bHaveRowid = 1; + }else if( iFieldnCol ){ + assert( apVal[iField]==0 ); + apVal[iField] = sqlite3_value_dup( pVal ); + if( apVal[iField]==0 ){ + recoverError(p, SQLITE_NOMEM, 0); + } + p1->nVal = iField+1; + } + p1->iPrevCell = iCell; + p1->iPrevPage = iPage; + } + }else{ + recoverReset(p, pSel); + p1->pTab = 0; + } + + return p->errCode; +} + +/* +** Initialize resources required by sqlite3_recover_step() in +** RECOVER_STATE_LOSTANDFOUND1 state - during which the set of pages not +** already allocated to a recovered schema element is determined. +*/ +static void recoverLostAndFound1Init(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + sqlite3_stmt *pStmt = 0; + + assert( p->laf.pUsed==0 ); + pLaf->nPg = recoverPageCount(p); + pLaf->pUsed = recoverBitmapAlloc(p, pLaf->nPg); + + /* Prepare a statement to iterate through all pages that are part of any tree + ** in the recoverable part of the input database schema to the bitmap. And, + ** if !p->bFreelistCorrupt, add all pages that appear to be part of the + ** freelist. */ + pStmt = recoverPrepare( + p, p->dbOut, + "WITH trunk(pgno) AS (" + " SELECT read_i32(getpage(1), 8) AS x WHERE x>0" + " UNION" + " SELECT read_i32(getpage(trunk.pgno), 0) AS x FROM trunk WHERE x>0" + ")," + "trunkdata(pgno, data) AS (" + " SELECT pgno, getpage(pgno) FROM trunk" + ")," + "freelist(data, n, freepgno) AS (" + " SELECT data, min(16384, read_i32(data, 1)-1), pgno FROM trunkdata" + " UNION ALL" + " SELECT data, n-1, read_i32(data, 2+n) FROM freelist WHERE n>=0" + ")," + "" + "roots(r) AS (" + " SELECT 1 UNION ALL" + " SELECT rootpage FROM recovery.schema WHERE rootpage>0" + ")," + "used(page) AS (" + " SELECT r FROM roots" + " UNION" + " SELECT child FROM sqlite_dbptr('getpage()'), used " + " WHERE pgno=page" + ") " + "SELECT page FROM used" + " UNION ALL " + "SELECT freepgno FROM freelist WHERE NOT ?" + ); + if( pStmt ) sqlite3_bind_int(pStmt, 1, p->bFreelistCorrupt); + pLaf->pUsedPages = pStmt; +} + +/* +** Perform one step (sqlite3_recover_step()) of work for the connection +** passed as the only argument, which is guaranteed to be in +** RECOVER_STATE_LOSTANDFOUND1 state - during which the set of pages not +** already allocated to a recovered schema element is determined. +*/ +static int recoverLostAndFound1Step(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + int rc = p->errCode; + if( rc==SQLITE_OK ){ + rc = sqlite3_step(pLaf->pUsedPages); + if( rc==SQLITE_ROW ){ + i64 iPg = sqlite3_column_int64(pLaf->pUsedPages, 0); + recoverBitmapSet(pLaf->pUsed, iPg); + rc = SQLITE_OK; + }else{ + recoverFinalize(p, pLaf->pUsedPages); + pLaf->pUsedPages = 0; + } + } + return rc; +} + +/* +** Initialize resources required by RECOVER_STATE_LOSTANDFOUND2 +** state - during which the pages identified in RECOVER_STATE_LOSTANDFOUND1 +** are sorted into sets that likely belonged to the same database tree. +*/ +static void recoverLostAndFound2Init(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + + assert( p->laf.pAllAndParent==0 ); + assert( p->laf.pMapInsert==0 ); + assert( p->laf.pMaxField==0 ); + assert( p->laf.nMaxField==0 ); + + pLaf->pMapInsert = recoverPrepare(p, p->dbOut, + "INSERT OR IGNORE INTO recovery.map(pgno, parent) VALUES(?, ?)" + ); + pLaf->pAllAndParent = recoverPreparePrintf(p, p->dbOut, + "WITH RECURSIVE seq(ii) AS (" + " SELECT 1 UNION ALL SELECT ii+1 FROM seq WHERE ii<%lld" + ")" + "SELECT pgno, child FROM sqlite_dbptr('getpage()') " + " UNION ALL " + "SELECT NULL, ii FROM seq", p->laf.nPg + ); + pLaf->pMaxField = recoverPreparePrintf(p, p->dbOut, + "SELECT max(field)+1 FROM sqlite_dbdata('getpage') WHERE pgno = ?" + ); +} + +/* +** Perform one step (sqlite3_recover_step()) of work for the connection +** passed as the only argument, which is guaranteed to be in +** RECOVER_STATE_LOSTANDFOUND2 state - during which the pages identified +** in RECOVER_STATE_LOSTANDFOUND1 are sorted into sets that likely belonged +** to the same database tree. +*/ +static int recoverLostAndFound2Step(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + if( p->errCode==SQLITE_OK ){ + int res = sqlite3_step(pLaf->pAllAndParent); + if( res==SQLITE_ROW ){ + i64 iChild = sqlite3_column_int(pLaf->pAllAndParent, 1); + if( recoverBitmapQuery(pLaf->pUsed, iChild)==0 ){ + sqlite3_bind_int64(pLaf->pMapInsert, 1, iChild); + sqlite3_bind_value(pLaf->pMapInsert, 2, + sqlite3_column_value(pLaf->pAllAndParent, 0) + ); + sqlite3_step(pLaf->pMapInsert); + recoverReset(p, pLaf->pMapInsert); + sqlite3_bind_int64(pLaf->pMaxField, 1, iChild); + if( SQLITE_ROW==sqlite3_step(pLaf->pMaxField) ){ + int nMax = sqlite3_column_int(pLaf->pMaxField, 0); + if( nMax>pLaf->nMaxField ) pLaf->nMaxField = nMax; + } + recoverReset(p, pLaf->pMaxField); + } + }else{ + recoverFinalize(p, pLaf->pAllAndParent); + pLaf->pAllAndParent =0; + return SQLITE_DONE; + } + } + return p->errCode; +} + +/* +** Free all resources allocated as part of sqlite3_recover_step() calls +** in one of the RECOVER_STATE_LOSTANDFOUND[123] states. +*/ +static void recoverLostAndFoundCleanup(sqlite3_recover *p){ + recoverBitmapFree(p->laf.pUsed); + p->laf.pUsed = 0; + sqlite3_finalize(p->laf.pUsedPages); + sqlite3_finalize(p->laf.pAllAndParent); + sqlite3_finalize(p->laf.pMapInsert); + sqlite3_finalize(p->laf.pMaxField); + sqlite3_finalize(p->laf.pFindRoot); + sqlite3_finalize(p->laf.pInsert); + sqlite3_finalize(p->laf.pAllPage); + sqlite3_finalize(p->laf.pPageData); + p->laf.pUsedPages = 0; + p->laf.pAllAndParent = 0; + p->laf.pMapInsert = 0; + p->laf.pMaxField = 0; + p->laf.pFindRoot = 0; + p->laf.pInsert = 0; + p->laf.pAllPage = 0; + p->laf.pPageData = 0; + sqlite3_free(p->laf.apVal); + p->laf.apVal = 0; +} + +/* +** Free all resources allocated as part of sqlite3_recover_step() calls. +*/ +static void recoverFinalCleanup(sqlite3_recover *p){ + RecoverTable *pTab = 0; + RecoverTable *pNext = 0; + + recoverWriteDataCleanup(p); + recoverLostAndFoundCleanup(p); + + for(pTab=p->pTblList; pTab; pTab=pNext){ + pNext = pTab->pNext; + sqlite3_free(pTab); + } + p->pTblList = 0; + sqlite3_finalize(p->pGetPage); + p->pGetPage = 0; + sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0); + + { +#ifndef NDEBUG + int res = +#endif + sqlite3_close(p->dbOut); + assert( res==SQLITE_OK ); + } + p->dbOut = 0; +} + +/* +** Decode and return an unsigned 16-bit big-endian integer value from +** buffer a[]. +*/ +static u32 recoverGetU16(const u8 *a){ + return (((u32)a[0])<<8) + ((u32)a[1]); +} + +/* +** Decode and return an unsigned 32-bit big-endian integer value from +** buffer a[]. +*/ +static u32 recoverGetU32(const u8 *a){ + return (((u32)a[0])<<24) + (((u32)a[1])<<16) + (((u32)a[2])<<8) + ((u32)a[3]); +} + +/* +** Decode an SQLite varint from buffer a[]. Write the decoded value to (*pVal) +** and return the number of bytes consumed. +*/ +static int recoverGetVarint(const u8 *a, i64 *pVal){ + sqlite3_uint64 u = 0; + int i; + for(i=0; i<8; i++){ + u = (u<<7) + (a[i]&0x7f); + if( (a[i]&0x80)==0 ){ *pVal = (sqlite3_int64)u; return i+1; } + } + u = (u<<8) + (a[i]&0xff); + *pVal = (sqlite3_int64)u; + return 9; +} + +/* +** The second argument points to a buffer n bytes in size. If this buffer +** or a prefix thereof appears to contain a well-formed SQLite b-tree page, +** return the page-size in bytes. Otherwise, if the buffer does not +** appear to contain a well-formed b-tree page, return 0. +*/ +static int recoverIsValidPage(u8 *aTmp, const u8 *a, int n){ + u8 *aUsed = aTmp; + int nFrag = 0; + int nActual = 0; + int iFree = 0; + int nCell = 0; /* Number of cells on page */ + int iCellOff = 0; /* Offset of cell array in page */ + int iContent = 0; + int eType = 0; + int ii = 0; + + eType = (int)a[0]; + if( eType!=0x02 && eType!=0x05 && eType!=0x0A && eType!=0x0D ) return 0; + + iFree = (int)recoverGetU16(&a[1]); + nCell = (int)recoverGetU16(&a[3]); + iContent = (int)recoverGetU16(&a[5]); + if( iContent==0 ) iContent = 65536; + nFrag = (int)a[7]; + + if( iContent>n ) return 0; + + memset(aUsed, 0, n); + memset(aUsed, 0xFF, iContent); + + /* Follow the free-list. This is the same format for all b-tree pages. */ + if( iFree && iFree<=iContent ) return 0; + while( iFree ){ + int iNext = 0; + int nByte = 0; + if( iFree>(n-4) ) return 0; + iNext = recoverGetU16(&a[iFree]); + nByte = recoverGetU16(&a[iFree+2]); + if( iFree+nByte>n ) return 0; + if( iNext && iNextiContent ) return 0; + for(ii=0; iin ){ + return 0; + } + if( eType==0x05 || eType==0x02 ) nByte += 4; + nByte += recoverGetVarint(&a[iOff+nByte], &nPayload); + if( eType==0x0D ){ + i64 dummy = 0; + nByte += recoverGetVarint(&a[iOff+nByte], &dummy); + } + if( eType!=0x05 ){ + int X = (eType==0x0D) ? n-35 : (((n-12)*64/255)-23); + int M = ((n-12)*32/255)-23; + int K = M+((nPayload-M)%(n-4)); + + if( nPayloadn ){ + return 0; + } + for(iByte=iOff; iByte<(iOff+nByte); iByte++){ + if( aUsed[iByte]!=0 ){ + return 0; + } + aUsed[iByte] = 0xFF; + } + } + + nActual = 0; + for(ii=0; iipMethods!=&recover_methods ); + return pFd->pMethods->xClose(pFd); +} + +/* +** Write value v to buffer a[] as a 16-bit big-endian unsigned integer. +*/ +static void recoverPutU16(u8 *a, u32 v){ + a[0] = (v>>8) & 0x00FF; + a[1] = (v>>0) & 0x00FF; +} + +/* +** Write value v to buffer a[] as a 32-bit big-endian unsigned integer. +*/ +static void recoverPutU32(u8 *a, u32 v){ + a[0] = (v>>24) & 0x00FF; + a[1] = (v>>16) & 0x00FF; + a[2] = (v>>8) & 0x00FF; + a[3] = (v>>0) & 0x00FF; +} + +/* +** Detect the page-size of the database opened by file-handle pFd by +** searching the first part of the file for a well-formed SQLite b-tree +** page. If parameter nReserve is non-zero, then as well as searching for +** a b-tree page with zero reserved bytes, this function searches for one +** with nReserve reserved bytes at the end of it. +** +** If successful, set variable p->detected_pgsz to the detected page-size +** in bytes and return SQLITE_OK. Or, if no error occurs but no valid page +** can be found, return SQLITE_OK but leave p->detected_pgsz set to 0. Or, +** if an error occurs (e.g. an IO or OOM error), then an SQLite error code +** is returned. The final value of p->detected_pgsz is undefined in this +** case. +*/ +static int recoverVfsDetectPagesize( + sqlite3_recover *p, /* Recover handle */ + sqlite3_file *pFd, /* File-handle open on input database */ + u32 nReserve, /* Possible nReserve value */ + i64 nSz /* Size of database file in bytes */ +){ + int rc = SQLITE_OK; + const int nMin = 512; + const int nMax = 65536; + const int nMaxBlk = 4; + u32 pgsz = 0; + int iBlk = 0; + u8 *aPg = 0; + u8 *aTmp = 0; + int nBlk = 0; + + aPg = (u8*)sqlite3_malloc(2*nMax); + if( aPg==0 ) return SQLITE_NOMEM; + aTmp = &aPg[nMax]; + + nBlk = (nSz+nMax-1)/nMax; + if( nBlk>nMaxBlk ) nBlk = nMaxBlk; + + do { + for(iBlk=0; rc==SQLITE_OK && iBlk=((iBlk+1)*nMax)) ? nMax : (nSz % nMax); + memset(aPg, 0, nMax); + rc = pFd->pMethods->xRead(pFd, aPg, nByte, iBlk*nMax); + if( rc==SQLITE_OK ){ + int pgsz2; + for(pgsz2=(pgsz ? pgsz*2 : nMin); pgsz2<=nMax; pgsz2=pgsz2*2){ + int iOff; + for(iOff=0; iOff(u32)p->detected_pgsz ){ + p->detected_pgsz = pgsz; + p->nReserve = nReserve; + } + if( nReserve==0 ) break; + nReserve = 0; + }while( 1 ); + + p->detected_pgsz = pgsz; + sqlite3_free(aPg); + return rc; +} + +/* +** The xRead() method of the wrapper VFS. This is used to intercept calls +** to read page 1 of the input database. +*/ +static int recoverVfsRead(sqlite3_file *pFd, void *aBuf, int nByte, i64 iOff){ + int rc = SQLITE_OK; + if( pFd->pMethods==&recover_methods ){ + pFd->pMethods = recover_g.pMethods; + rc = pFd->pMethods->xRead(pFd, aBuf, nByte, iOff); + if( nByte==16 ){ + sqlite3_randomness(16, aBuf); + }else + if( rc==SQLITE_OK && iOff==0 && nByte>=108 ){ + /* Ensure that the database has a valid header file. The only fields + ** that really matter to recovery are: + ** + ** + Database page size (16-bits at offset 16) + ** + Size of db in pages (32-bits at offset 28) + ** + Database encoding (32-bits at offset 56) + ** + ** Also preserved are: + ** + ** + first freelist page (32-bits at offset 32) + ** + size of freelist (32-bits at offset 36) + ** + the wal-mode flags (16-bits at offset 18) + ** + ** We also try to preserve the auto-vacuum, incr-value, user-version + ** and application-id fields - all 32 bit quantities at offsets + ** 52, 60, 64 and 68. All other fields are set to known good values. + ** + ** Byte offset 105 should also contain the page-size as a 16-bit + ** integer. + */ + const int aPreserve[] = {32, 36, 52, 60, 64, 68}; + u8 aHdr[108] = { + 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00, + 0xFF, 0xFF, 0x01, 0x01, 0x00, 0x40, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x10, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2e, 0x5b, 0x30, + + 0x0D, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00 + }; + u8 *a = (u8*)aBuf; + + u32 pgsz = recoverGetU16(&a[16]); + u32 nReserve = a[20]; + u32 enc = recoverGetU32(&a[56]); + u32 dbsz = 0; + i64 dbFileSize = 0; + int ii; + sqlite3_recover *p = recover_g.p; + + if( pgsz==0x01 ) pgsz = 65536; + rc = pFd->pMethods->xFileSize(pFd, &dbFileSize); + + if( rc==SQLITE_OK && p->detected_pgsz==0 ){ + rc = recoverVfsDetectPagesize(p, pFd, nReserve, dbFileSize); + } + if( p->detected_pgsz ){ + pgsz = p->detected_pgsz; + nReserve = p->nReserve; + } + + if( pgsz ){ + dbsz = dbFileSize / pgsz; + } + if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF16BE && enc!=SQLITE_UTF16LE ){ + enc = SQLITE_UTF8; + } + + sqlite3_free(p->pPage1Cache); + p->pPage1Cache = 0; + p->pPage1Disk = 0; + + p->pgsz = nByte; + p->pPage1Cache = (u8*)recoverMalloc(p, nByte*2); + if( p->pPage1Cache ){ + p->pPage1Disk = &p->pPage1Cache[nByte]; + memcpy(p->pPage1Disk, aBuf, nByte); + aHdr[18] = a[18]; + aHdr[19] = a[19]; + recoverPutU32(&aHdr[28], dbsz); + recoverPutU32(&aHdr[56], enc); + recoverPutU16(&aHdr[105], pgsz-nReserve); + if( pgsz==65536 ) pgsz = 1; + recoverPutU16(&aHdr[16], pgsz); + aHdr[20] = nReserve; + for(ii=0; iipPage1Cache, aBuf, nByte); + }else{ + rc = p->errCode; + } + + } + pFd->pMethods = &recover_methods; + }else{ + rc = pFd->pMethods->xRead(pFd, aBuf, nByte, iOff); + } + return rc; +} + +/* +** Used to make sqlite3_io_methods wrapper methods less verbose. +*/ +#define RECOVER_VFS_WRAPPER(code) \ + int rc = SQLITE_OK; \ + if( pFd->pMethods==&recover_methods ){ \ + pFd->pMethods = recover_g.pMethods; \ + rc = code; \ + pFd->pMethods = &recover_methods; \ + }else{ \ + rc = code; \ + } \ + return rc; + +/* +** Methods of the wrapper VFS. All methods except for xRead() and xClose() +** simply uninstall the sqlite3_io_methods wrapper, invoke the equivalent +** method on the lower level VFS, then reinstall the wrapper before returning. +** Those that return an integer value use the RECOVER_VFS_WRAPPER macro. +*/ +static int recoverVfsWrite( + sqlite3_file *pFd, const void *aBuf, int nByte, i64 iOff +){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xWrite(pFd, aBuf, nByte, iOff) + ); +} +static int recoverVfsTruncate(sqlite3_file *pFd, sqlite3_int64 size){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xTruncate(pFd, size) + ); +} +static int recoverVfsSync(sqlite3_file *pFd, int flags){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xSync(pFd, flags) + ); +} +static int recoverVfsFileSize(sqlite3_file *pFd, sqlite3_int64 *pSize){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xFileSize(pFd, pSize) + ); +} +static int recoverVfsLock(sqlite3_file *pFd, int eLock){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xLock(pFd, eLock) + ); +} +static int recoverVfsUnlock(sqlite3_file *pFd, int eLock){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xUnlock(pFd, eLock) + ); +} +static int recoverVfsCheckReservedLock(sqlite3_file *pFd, int *pResOut){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xCheckReservedLock(pFd, pResOut) + ); +} +static int recoverVfsFileControl(sqlite3_file *pFd, int op, void *pArg){ + RECOVER_VFS_WRAPPER ( + (pFd->pMethods ? pFd->pMethods->xFileControl(pFd, op, pArg) : SQLITE_NOTFOUND) + ); +} +static int recoverVfsSectorSize(sqlite3_file *pFd){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xSectorSize(pFd) + ); +} +static int recoverVfsDeviceCharacteristics(sqlite3_file *pFd){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xDeviceCharacteristics(pFd) + ); +} +static int recoverVfsShmMap( + sqlite3_file *pFd, int iPg, int pgsz, int bExtend, void volatile **pp +){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xShmMap(pFd, iPg, pgsz, bExtend, pp) + ); +} +static int recoverVfsShmLock(sqlite3_file *pFd, int offset, int n, int flags){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xShmLock(pFd, offset, n, flags) + ); +} +static void recoverVfsShmBarrier(sqlite3_file *pFd){ + if( pFd->pMethods==&recover_methods ){ + pFd->pMethods = recover_g.pMethods; + pFd->pMethods->xShmBarrier(pFd); + pFd->pMethods = &recover_methods; + }else{ + pFd->pMethods->xShmBarrier(pFd); + } +} +static int recoverVfsShmUnmap(sqlite3_file *pFd, int deleteFlag){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xShmUnmap(pFd, deleteFlag) + ); +} + +static int recoverVfsFetch( + sqlite3_file *pFd, + sqlite3_int64 iOff, + int iAmt, + void **pp +){ + *pp = 0; + return SQLITE_OK; +} +static int recoverVfsUnfetch(sqlite3_file *pFd, sqlite3_int64 iOff, void *p){ + return SQLITE_OK; +} + +/* +** Install the VFS wrapper around the file-descriptor open on the input +** database for recover handle p. Mutex RECOVER_MUTEX_ID must be held +** when this function is called. +*/ +static void recoverInstallWrapper(sqlite3_recover *p){ + sqlite3_file *pFd = 0; + assert( recover_g.pMethods==0 ); + recoverAssertMutexHeld(); + sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_FILE_POINTER, (void*)&pFd); + assert( pFd==0 || pFd->pMethods!=&recover_methods ); + if( pFd && pFd->pMethods ){ + int iVersion = 1 + (pFd->pMethods->iVersion>1 && pFd->pMethods->xShmMap!=0); + recover_g.pMethods = pFd->pMethods; + recover_g.p = p; + recover_methods.iVersion = iVersion; + pFd->pMethods = &recover_methods; + } +} + +/* +** Uninstall the VFS wrapper that was installed around the file-descriptor open +** on the input database for recover handle p. Mutex RECOVER_MUTEX_ID must be +** held when this function is called. +*/ +static void recoverUninstallWrapper(sqlite3_recover *p){ + sqlite3_file *pFd = 0; + recoverAssertMutexHeld(); + sqlite3_file_control(p->dbIn, p->zDb,SQLITE_FCNTL_FILE_POINTER,(void*)&pFd); + if( pFd && pFd->pMethods ){ + pFd->pMethods = recover_g.pMethods; + recover_g.pMethods = 0; + recover_g.p = 0; + } +} + +/* +** This function does the work of a single sqlite3_recover_step() call. It +** is guaranteed that the handle is not in an error state when this +** function is called. +*/ +static void recoverStep(sqlite3_recover *p){ + assert( p && p->errCode==SQLITE_OK ); + switch( p->eState ){ + case RECOVER_STATE_INIT: + /* This is the very first call to sqlite3_recover_step() on this object. + */ + recoverSqlCallback(p, "BEGIN"); + recoverSqlCallback(p, "PRAGMA writable_schema = on"); + + recoverEnterMutex(); + recoverInstallWrapper(p); + + /* Open the output database. And register required virtual tables and + ** user functions with the new handle. */ + recoverOpenOutput(p); + + /* Open transactions on both the input and output databases. */ + sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0); + recoverExec(p, p->dbIn, "PRAGMA writable_schema = on"); + recoverExec(p, p->dbIn, "BEGIN"); + if( p->errCode==SQLITE_OK ) p->bCloseTransaction = 1; + recoverExec(p, p->dbIn, "SELECT 1 FROM sqlite_schema"); + recoverTransferSettings(p); + recoverOpenRecovery(p); + recoverCacheSchema(p); + + recoverUninstallWrapper(p); + recoverLeaveMutex(); + + recoverExec(p, p->dbOut, "BEGIN"); + + recoverWriteSchema1(p); + p->eState = RECOVER_STATE_WRITING; + break; + + case RECOVER_STATE_WRITING: { + if( p->w1.pTbls==0 ){ + recoverWriteDataInit(p); + } + if( SQLITE_DONE==recoverWriteDataStep(p) ){ + recoverWriteDataCleanup(p); + if( p->zLostAndFound ){ + p->eState = RECOVER_STATE_LOSTANDFOUND1; + }else{ + p->eState = RECOVER_STATE_SCHEMA2; + } + } + break; + } + + case RECOVER_STATE_LOSTANDFOUND1: { + if( p->laf.pUsed==0 ){ + recoverLostAndFound1Init(p); + } + if( SQLITE_DONE==recoverLostAndFound1Step(p) ){ + p->eState = RECOVER_STATE_LOSTANDFOUND2; + } + break; + } + case RECOVER_STATE_LOSTANDFOUND2: { + if( p->laf.pAllAndParent==0 ){ + recoverLostAndFound2Init(p); + } + if( SQLITE_DONE==recoverLostAndFound2Step(p) ){ + p->eState = RECOVER_STATE_LOSTANDFOUND3; + } + break; + } + + case RECOVER_STATE_LOSTANDFOUND3: { + if( p->laf.pInsert==0 ){ + recoverLostAndFound3Init(p); + } + if( SQLITE_DONE==recoverLostAndFound3Step(p) ){ + p->eState = RECOVER_STATE_SCHEMA2; + } + break; + } + + case RECOVER_STATE_SCHEMA2: { + int rc = SQLITE_OK; + + recoverWriteSchema2(p); + p->eState = RECOVER_STATE_DONE; + + /* If no error has occurred, commit the write transaction on the output + ** database. Regardless of whether or not an error has occurred, make + ** an attempt to end the read transaction on the input database. */ + recoverExec(p, p->dbOut, "COMMIT"); + rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0); + if( p->errCode==SQLITE_OK ) p->errCode = rc; + + recoverSqlCallback(p, "PRAGMA writable_schema = off"); + recoverSqlCallback(p, "COMMIT"); + p->eState = RECOVER_STATE_DONE; + recoverFinalCleanup(p); + break; + }; + + case RECOVER_STATE_DONE: { + /* no-op */ + break; + }; + } +} + + +/* +** This is a worker function that does the heavy lifting for both init +** functions: +** +** sqlite3_recover_init() +** sqlite3_recover_init_sql() +** +** All this function does is allocate space for the recover handle and +** take copies of the input parameters. All the real work is done within +** sqlite3_recover_run(). +*/ +sqlite3_recover *recoverInit( + sqlite3* db, + const char *zDb, + const char *zUri, /* Output URI for _recover_init() */ + int (*xSql)(void*, const char*),/* SQL callback for _recover_init_sql() */ + void *pSqlCtx /* Context arg for _recover_init_sql() */ +){ + sqlite3_recover *pRet = 0; + int nDb = 0; + int nUri = 0; + int nByte = 0; + + if( zDb==0 ){ zDb = "main"; } + + nDb = recoverStrlen(zDb); + nUri = recoverStrlen(zUri); + + nByte = sizeof(sqlite3_recover) + nDb+1 + nUri+1; + pRet = (sqlite3_recover*)sqlite3_malloc(nByte); + if( pRet ){ + memset(pRet, 0, nByte); + pRet->dbIn = db; + pRet->zDb = (char*)&pRet[1]; + pRet->zUri = &pRet->zDb[nDb+1]; + memcpy(pRet->zDb, zDb, nDb); + if( nUri>0 && zUri ) memcpy(pRet->zUri, zUri, nUri); + pRet->xSql = xSql; + pRet->pSqlCtx = pSqlCtx; + pRet->bRecoverRowid = RECOVER_ROWID_DEFAULT; + } + + return pRet; +} + +/* +** Initialize a recovery handle that creates a new database containing +** the recovered data. +*/ +sqlite3_recover *sqlite3_recover_init( + sqlite3* db, + const char *zDb, + const char *zUri +){ + return recoverInit(db, zDb, zUri, 0, 0); +} + +/* +** Initialize a recovery handle that returns recovered data in the +** form of SQL statements via a callback. +*/ +sqlite3_recover *sqlite3_recover_init_sql( + sqlite3* db, + const char *zDb, + int (*xSql)(void*, const char*), + void *pSqlCtx +){ + return recoverInit(db, zDb, 0, xSql, pSqlCtx); +} + +/* +** Return the handle error message, if any. +*/ +const char *sqlite3_recover_errmsg(sqlite3_recover *p){ + return (p && p->errCode!=SQLITE_NOMEM) ? p->zErrMsg : "out of memory"; +} + +/* +** Return the handle error code. +*/ +int sqlite3_recover_errcode(sqlite3_recover *p){ + return p ? p->errCode : SQLITE_NOMEM; +} + +/* +** Configure the handle. +*/ +int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){ + int rc = SQLITE_OK; + if( p==0 ){ + rc = SQLITE_NOMEM; + }else if( p->eState!=RECOVER_STATE_INIT ){ + rc = SQLITE_MISUSE; + }else{ + switch( op ){ + case 789: + /* This undocumented magic configuration option is used to set the + ** name of the auxiliary database that is ATTACH-ed to the database + ** connection and used to hold state information during the + ** recovery process. This option is for debugging use only and + ** is subject to change or removal at any time. */ + sqlite3_free(p->zStateDb); + p->zStateDb = recoverMPrintf(p, "%s", (char*)pArg); + break; + + case SQLITE_RECOVER_LOST_AND_FOUND: { + const char *zArg = (const char*)pArg; + sqlite3_free(p->zLostAndFound); + if( zArg ){ + p->zLostAndFound = recoverMPrintf(p, "%s", zArg); + }else{ + p->zLostAndFound = 0; + } + break; + } + + case SQLITE_RECOVER_FREELIST_CORRUPT: + p->bFreelistCorrupt = *(int*)pArg; + break; + + case SQLITE_RECOVER_ROWIDS: + p->bRecoverRowid = *(int*)pArg; + break; + + case SQLITE_RECOVER_SLOWINDEXES: + p->bSlowIndexes = *(int*)pArg; + break; + + default: + rc = SQLITE_NOTFOUND; + break; + } + } + + return rc; +} + +/* +** Do a unit of work towards the recovery job. Return SQLITE_OK if +** no error has occurred but database recovery is not finished, SQLITE_DONE +** if database recovery has been successfully completed, or an SQLite +** error code if an error has occurred. +*/ +int sqlite3_recover_step(sqlite3_recover *p){ + if( p==0 ) return SQLITE_NOMEM; + if( p->errCode==SQLITE_OK ) recoverStep(p); + if( p->eState==RECOVER_STATE_DONE && p->errCode==SQLITE_OK ){ + return SQLITE_DONE; + } + return p->errCode; +} + +/* +** Do the configured recovery operation. Return SQLITE_OK if successful, or +** else an SQLite error code. +*/ +int sqlite3_recover_run(sqlite3_recover *p){ + while( SQLITE_OK==sqlite3_recover_step(p) ); + return sqlite3_recover_errcode(p); +} + + +/* +** Free all resources associated with the recover handle passed as the only +** argument. The results of using a handle with any sqlite3_recover_** +** API function after it has been passed to this function are undefined. +** +** A copy of the value returned by the first call made to sqlite3_recover_run() +** on this handle is returned, or SQLITE_OK if sqlite3_recover_run() has +** not been called on this handle. +*/ +int sqlite3_recover_finish(sqlite3_recover *p){ + int rc; + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + recoverFinalCleanup(p); + if( p->bCloseTransaction && sqlite3_get_autocommit(p->dbIn)==0 ){ + rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0); + if( p->errCode==SQLITE_OK ) p->errCode = rc; + } + rc = p->errCode; + sqlite3_free(p->zErrMsg); + sqlite3_free(p->zStateDb); + sqlite3_free(p->zLostAndFound); + sqlite3_free(p->pPage1Cache); + sqlite3_free(p); + } + return rc; +} + +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/recover/sqlite3recover.h b/ext/recover/sqlite3recover.h new file mode 100644 index 000000000..7a1cd1cd8 --- /dev/null +++ b/ext/recover/sqlite3recover.h @@ -0,0 +1,249 @@ +/* +** 2022-08-27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the public interface to the "recover" extension - +** an SQLite extension designed to recover data from corrupted database +** files. +*/ + +/* +** OVERVIEW: +** +** To use the API to recover data from a corrupted database, an +** application: +** +** 1) Creates an sqlite3_recover handle by calling either +** sqlite3_recover_init() or sqlite3_recover_init_sql(). +** +** 2) Configures the new handle using one or more calls to +** sqlite3_recover_config(). +** +** 3) Executes the recovery by repeatedly calling sqlite3_recover_step() on +** the handle until it returns something other than SQLITE_OK. If it +** returns SQLITE_DONE, then the recovery operation completed without +** error. If it returns some other non-SQLITE_OK value, then an error +** has occurred. +** +** 4) Retrieves any error code and English language error message using the +** sqlite3_recover_errcode() and sqlite3_recover_errmsg() APIs, +** respectively. +** +** 5) Destroys the sqlite3_recover handle and frees all resources +** using sqlite3_recover_finish(). +** +** The application may abandon the recovery operation at any point +** before it is finished by passing the sqlite3_recover handle to +** sqlite3_recover_finish(). This is not an error, but the final state +** of the output database, or the results of running the partial script +** delivered to the SQL callback, are undefined. +*/ + +#ifndef _SQLITE_RECOVER_H +#define _SQLITE_RECOVER_H + +#include "sqlite3.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** An instance of the sqlite3_recover object represents a recovery +** operation in progress. +** +** Constructors: +** +** sqlite3_recover_init() +** sqlite3_recover_init_sql() +** +** Destructor: +** +** sqlite3_recover_finish() +** +** Methods: +** +** sqlite3_recover_config() +** sqlite3_recover_errcode() +** sqlite3_recover_errmsg() +** sqlite3_recover_run() +** sqlite3_recover_step() +*/ +typedef struct sqlite3_recover sqlite3_recover; + +/* +** These two APIs attempt to create and return a new sqlite3_recover object. +** In both cases the first two arguments identify the (possibly +** corrupt) database to recover data from. The first argument is an open +** database handle and the second the name of a database attached to that +** handle (i.e. "main", "temp" or the name of an attached database). +** +** If sqlite3_recover_init() is used to create the new sqlite3_recover +** handle, then data is recovered into a new database, identified by +** string parameter zUri. zUri may be an absolute or relative file path, +** or may be an SQLite URI. If the identified database file already exists, +** it is overwritten. +** +** If sqlite3_recover_init_sql() is invoked, then any recovered data will +** be returned to the user as a series of SQL statements. Executing these +** SQL statements results in the same database as would have been created +** had sqlite3_recover_init() been used. For each SQL statement in the +** output, the callback function passed as the third argument (xSql) is +** invoked once. The first parameter is a passed a copy of the fourth argument +** to this function (pCtx) as its first parameter, and a pointer to a +** nul-terminated buffer containing the SQL statement formated as UTF-8 as +** the second. If the xSql callback returns any value other than SQLITE_OK, +** then processing is immediately abandoned and the value returned used as +** the recover handle error code (see below). +** +** If an out-of-memory error occurs, NULL may be returned instead of +** a valid handle. In all other cases, it is the responsibility of the +** application to avoid resource leaks by ensuring that +** sqlite3_recover_finish() is called on all allocated handles. +*/ +sqlite3_recover *sqlite3_recover_init( + sqlite3* db, + const char *zDb, + const char *zUri +); +sqlite3_recover *sqlite3_recover_init_sql( + sqlite3* db, + const char *zDb, + int (*xSql)(void*, const char*), + void *pCtx +); + +/* +** Configure an sqlite3_recover object that has just been created using +** sqlite3_recover_init() or sqlite3_recover_init_sql(). This function +** may only be called before the first call to sqlite3_recover_step() +** or sqlite3_recover_run() on the object. +** +** The second argument passed to this function must be one of the +** SQLITE_RECOVER_* symbols defined below. Valid values for the third argument +** depend on the specific SQLITE_RECOVER_* symbol in use. +** +** SQLITE_OK is returned if the configuration operation was successful, +** or an SQLite error code otherwise. +*/ +int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg); + +/* +** SQLITE_RECOVER_LOST_AND_FOUND: +** The pArg argument points to a string buffer containing the name +** of a "lost-and-found" table in the output database, or NULL. If +** the argument is non-NULL and the database contains seemingly +** valid pages that cannot be associated with any table in the +** recovered part of the schema, data is extracted from these +** pages to add to the lost-and-found table. +** +** SQLITE_RECOVER_FREELIST_CORRUPT: +** The pArg value must actually be a pointer to a value of type +** int containing value 0 or 1 cast as a (void*). If this option is set +** (argument is 1) and a lost-and-found table has been configured using +** SQLITE_RECOVER_LOST_AND_FOUND, then is assumed that the freelist is +** corrupt and an attempt is made to recover records from pages that +** appear to be linked into the freelist. Otherwise, pages on the freelist +** are ignored. Setting this option can recover more data from the +** database, but often ends up "recovering" deleted records. The default +** value is 0 (clear). +** +** SQLITE_RECOVER_ROWIDS: +** The pArg value must actually be a pointer to a value of type +** int containing value 0 or 1 cast as a (void*). If this option is set +** (argument is 1), then an attempt is made to recover rowid values +** that are not also INTEGER PRIMARY KEY values. If this option is +** clear, then new rowids are assigned to all recovered rows. The +** default value is 1 (set). +** +** SQLITE_RECOVER_SLOWINDEXES: +** The pArg value must actually be a pointer to a value of type +** int containing value 0 or 1 cast as a (void*). If this option is clear +** (argument is 0), then when creating an output database, the recover +** module creates and populates non-UNIQUE indexes right at the end of the +** recovery operation - after all recoverable data has been inserted +** into the new database. This is faster overall, but means that the +** final call to sqlite3_recover_step() for a recovery operation may +** be need to create a large number of indexes, which may be very slow. +** +** Or, if this option is set (argument is 1), then non-UNIQUE indexes +** are created in the output database before it is populated with +** recovered data. This is slower overall, but avoids the slow call +** to sqlite3_recover_step() at the end of the recovery operation. +** +** The default option value is 0. +*/ +#define SQLITE_RECOVER_LOST_AND_FOUND 1 +#define SQLITE_RECOVER_FREELIST_CORRUPT 2 +#define SQLITE_RECOVER_ROWIDS 3 +#define SQLITE_RECOVER_SLOWINDEXES 4 + +/* +** Perform a unit of work towards the recovery operation. This function +** must normally be called multiple times to complete database recovery. +** +** If no error occurs but the recovery operation is not completed, this +** function returns SQLITE_OK. If recovery has been completed successfully +** then SQLITE_DONE is returned. If an error has occurred, then an SQLite +** error code (e.g. SQLITE_IOERR or SQLITE_NOMEM) is returned. It is not +** considered an error if some or all of the data cannot be recovered +** due to database corruption. +** +** Once sqlite3_recover_step() has returned a value other than SQLITE_OK, +** all further such calls on the same recover handle are no-ops that return +** the same non-SQLITE_OK value. +*/ +int sqlite3_recover_step(sqlite3_recover*); + +/* +** Run the recovery operation to completion. Return SQLITE_OK if successful, +** or an SQLite error code otherwise. Calling this function is the same +** as executing: +** +** while( SQLITE_OK==sqlite3_recover_step(p) ); +** return sqlite3_recover_errcode(p); +*/ +int sqlite3_recover_run(sqlite3_recover*); + +/* +** If an error has been encountered during a prior call to +** sqlite3_recover_step(), then this function attempts to return a +** pointer to a buffer containing an English language explanation of +** the error. If no error message is available, or if an out-of memory +** error occurs while attempting to allocate a buffer in which to format +** the error message, NULL is returned. +** +** The returned buffer remains valid until the sqlite3_recover handle is +** destroyed using sqlite3_recover_finish(). +*/ +const char *sqlite3_recover_errmsg(sqlite3_recover*); + +/* +** If this function is called on an sqlite3_recover handle after +** an error occurs, an SQLite error code is returned. Otherwise, SQLITE_OK. +*/ +int sqlite3_recover_errcode(sqlite3_recover*); + +/* +** Clean up a recovery object created by a call to sqlite3_recover_init(). +** The results of using a recovery object with any API after it has been +** passed to this function are undefined. +** +** This function returns the same value as sqlite3_recover_errcode(). +*/ +int sqlite3_recover_finish(sqlite3_recover*); + + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* ifndef _SQLITE_RECOVER_H */ diff --git a/ext/recover/test_recover.c b/ext/recover/test_recover.c new file mode 100644 index 000000000..1c333df8e --- /dev/null +++ b/ext/recover/test_recover.c @@ -0,0 +1,311 @@ +/* +** 2022-08-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. +** +************************************************************************* +** +*/ + +#include "sqlite3recover.h" +#include "sqliteInt.h" + +#include +#include + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +typedef struct TestRecover TestRecover; +struct TestRecover { + sqlite3_recover *p; + Tcl_Interp *interp; + Tcl_Obj *pScript; +}; + +static int xSqlCallback(void *pSqlArg, const char *zSql){ + TestRecover *p = (TestRecover*)pSqlArg; + Tcl_Obj *pEval = 0; + int res = 0; + + pEval = Tcl_DuplicateObj(p->pScript); + Tcl_IncrRefCount(pEval); + + res = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zSql, -1)); + if( res==TCL_OK ){ + res = Tcl_EvalObjEx(p->interp, pEval, 0); + } + + Tcl_DecrRefCount(pEval); + if( res ){ + Tcl_BackgroundError(p->interp); + return TCL_ERROR; + }else{ + Tcl_Obj *pObj = Tcl_GetObjResult(p->interp); + if( Tcl_GetCharLength(pObj)==0 ){ + res = 0; + }else if( Tcl_GetIntFromObj(p->interp, pObj, &res) ){ + Tcl_BackgroundError(p->interp); + return TCL_ERROR; + } + } + return res; +} + +static int getDbPointer(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){ + Tcl_CmdInfo info; + if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){ + Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0); + return TCL_ERROR; + } + *pDb = *(sqlite3 **)info.objClientData; + return TCL_OK; +} + +/* +** Implementation of the command created by [sqlite3_recover_init]: +** +** $cmd config OP ARG +** $cmd run +** $cmd errmsg +** $cmd errcode +** $cmd finalize +*/ +static int testRecoverCmd( + void *clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + static struct RecoverSub { + const char *zSub; + int nArg; + const char *zMsg; + } aSub[] = { + { "config", 2, "ARG" }, /* 0 */ + { "run", 0, "" }, /* 1 */ + { "errmsg", 0, "" }, /* 2 */ + { "errcode", 0, "" }, /* 3 */ + { "finish", 0, "" }, /* 4 */ + { "step", 0, "" }, /* 5 */ + { 0 } + }; + int rc = TCL_OK; + int iSub = 0; + TestRecover *pTest = (TestRecover*)clientData; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); + return TCL_ERROR; + } + rc = Tcl_GetIndexFromObjStruct(interp, + objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub + ); + if( rc!=TCL_OK ) return rc; + if( (objc-2)!=aSub[iSub].nArg ){ + Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); + return TCL_ERROR; + } + + switch( iSub ){ + case 0: assert( sqlite3_stricmp("config", aSub[iSub].zSub)==0 ); { + const char *aOp[] = { + "testdb", /* 0 */ + "lostandfound", /* 1 */ + "freelistcorrupt", /* 2 */ + "rowids", /* 3 */ + "slowindexes", /* 4 */ + "invalid", /* 5 */ + 0 + }; + int iOp = 0; + int res = 0; + if( Tcl_GetIndexFromObj(interp, objv[2], aOp, "option", 0, &iOp) ){ + return TCL_ERROR; + } + switch( iOp ){ + case 0: + res = sqlite3_recover_config(pTest->p, + 789, (void*)Tcl_GetString(objv[3]) /* MAGIC NUMBER! */ + ); + break; + case 1: { + const char *zStr = Tcl_GetString(objv[3]); + res = sqlite3_recover_config(pTest->p, + SQLITE_RECOVER_LOST_AND_FOUND, (void*)(zStr[0] ? zStr : 0) + ); + break; + } + case 2: { + int iVal = 0; + if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR; + res = sqlite3_recover_config(pTest->p, + SQLITE_RECOVER_FREELIST_CORRUPT, (void*)&iVal + ); + break; + } + case 3: { + int iVal = 0; + if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR; + res = sqlite3_recover_config(pTest->p, + SQLITE_RECOVER_ROWIDS, (void*)&iVal + ); + break; + } + case 4: { + int iVal = 0; + if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR; + res = sqlite3_recover_config(pTest->p, + SQLITE_RECOVER_SLOWINDEXES, (void*)&iVal + ); + break; + } + case 5: { + res = sqlite3_recover_config(pTest->p, 12345, 0); + break; + } + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(res)); + break; + } + case 1: assert( sqlite3_stricmp("run", aSub[iSub].zSub)==0 ); { + int res = sqlite3_recover_run(pTest->p); + Tcl_SetObjResult(interp, Tcl_NewIntObj(res)); + break; + } + case 2: assert( sqlite3_stricmp("errmsg", aSub[iSub].zSub)==0 ); { + const char *zErr = sqlite3_recover_errmsg(pTest->p); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1)); + break; + } + case 3: assert( sqlite3_stricmp("errcode", aSub[iSub].zSub)==0 ); { + int errCode = sqlite3_recover_errcode(pTest->p); + Tcl_SetObjResult(interp, Tcl_NewIntObj(errCode)); + break; + } + case 4: assert( sqlite3_stricmp("finish", aSub[iSub].zSub)==0 ); { + int res = sqlite3_recover_errcode(pTest->p); + int res2; + if( res!=SQLITE_OK ){ + const char *zErr = sqlite3_recover_errmsg(pTest->p); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1)); + } + res2 = sqlite3_recover_finish(pTest->p); + assert( res2==res ); + if( res ) return TCL_ERROR; + break; + } + case 5: assert( sqlite3_stricmp("step", aSub[iSub].zSub)==0 ); { + int res = sqlite3_recover_step(pTest->p); + Tcl_SetObjResult(interp, Tcl_NewIntObj(res)); + break; + } + } + + return TCL_OK; +} + +/* +** sqlite3_recover_init DB DBNAME URI +*/ +static int test_sqlite3_recover_init( + void *clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + static int iTestRecoverCmd = 1; + + TestRecover *pNew = 0; + sqlite3 *db = 0; + const char *zDb = 0; + const char *zUri = 0; + char zCmd[128]; + int bSql = clientData ? 1 : 0; + + if( objc!=4 ){ + const char *zErr = (bSql ? "DB DBNAME SCRIPT" : "DB DBNAME URI"); + Tcl_WrongNumArgs(interp, 1, objv, zErr); + return TCL_ERROR; + } + if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR; + zDb = Tcl_GetString(objv[2]); + if( zDb[0]=='\0' ) zDb = 0; + + pNew = ckalloc(sizeof(TestRecover)); + if( bSql==0 ){ + zUri = Tcl_GetString(objv[3]); + pNew->p = sqlite3_recover_init(db, zDb, zUri); + }else{ + pNew->interp = interp; + pNew->pScript = objv[3]; + Tcl_IncrRefCount(pNew->pScript); + pNew->p = sqlite3_recover_init_sql(db, zDb, xSqlCallback, (void*)pNew); + } + + sprintf(zCmd, "sqlite_recover%d", iTestRecoverCmd++); + Tcl_CreateObjCommand(interp, zCmd, testRecoverCmd, (void*)pNew, 0); + + Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1)); + return TCL_OK; +} + +/* +** Declaration for public API function in file dbdata.c. This may be called +** with NULL as the final two arguments to register the sqlite_dbptr and +** sqlite_dbdata virtual tables with a database handle. +*/ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_dbdata_init(sqlite3*, char**, const sqlite3_api_routines*); + +/* +** sqlite3_recover_init DB DBNAME URI +*/ +static int test_sqlite3_dbdata_init( + void *clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3 *db = 0; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR; + sqlite3_dbdata_init(db, 0, 0); + + Tcl_ResetResult(interp); + return TCL_OK; +} + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +int TestRecover_Init(Tcl_Interp *interp){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + struct Cmd { + const char *zCmd; + Tcl_ObjCmdProc *xProc; + void *pArg; + } aCmd[] = { + { "sqlite3_recover_init", test_sqlite3_recover_init, 0 }, + { "sqlite3_recover_init_sql", test_sqlite3_recover_init, (void*)1 }, + { "sqlite3_dbdata_init", test_sqlite3_dbdata_init, (void*)1 }, + }; + int i; + + for(i=0; izCmd, p->xProc, p->pArg, 0); + } +#endif + return TCL_OK; +} + diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 49053a2bc..7daae9b1c 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -3235,7 +3235,7 @@ static int rtreeUpdate( rtreeReference(pRtree); assert(nData>=1); - cell.iRowid = 0; /* Used only to suppress a compiler warning */ + memset(&cell, 0, sizeof(cell)); /* Constraint handling. A write operation on an r-tree table may return ** SQLITE_CONSTRAINT for two reasons: diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index a892804b4..fd06f3b4d 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -3327,6 +3327,22 @@ static int sessionChangesetNextOne( if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE; else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT; } + + /* If this is an UPDATE that is part of a changeset, then check that + ** there are no fields in the old.* record that are not (a) PK fields, + ** or (b) also present in the new.* record. + ** + ** Such records are technically corrupt, but the rebaser was at one + ** point generating them. Under most circumstances this is benign, but + ** can cause spurious SQLITE_RANGE errors when applying the changeset. */ + if( p->bPatchset==0 && p->op==SQLITE_UPDATE){ + for(i=0; inCol; i++){ + if( p->abPK[i]==0 && p->apValue[i+p->nCol]==0 ){ + sqlite3ValueFree(p->apValue[i]); + p->apValue[i] = 0; + } + } + } } return SQLITE_ROW; @@ -5523,7 +5539,7 @@ static void sessionAppendPartialUpdate( if( !pIter->abPK[i] && a1[0] ) bData = 1; memcpy(pOut, a1, n1); pOut += n1; - }else if( a2[0]!=0xFF ){ + }else if( a2[0]!=0xFF && a1[0] ){ bData = 1; memcpy(pOut, a2, n2); pOut += n2; diff --git a/ext/session/test_session.c b/ext/session/test_session.c index f2fc77eee..c1feb7802 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -98,6 +98,19 @@ int sql_exec_changeset( } /************************************************************************/ + +#ifdef SQLITE_DEBUG +static int sqlite3_test_changeset(int, void *, char **); +static void assert_changeset_is_ok(int n, void *p){ + int rc = 0; + char *z = 0; + rc = sqlite3_test_changeset(n, p, &z); + assert( z==0 ); +} +#else +# define assert_changeset_is_ok(n,p) +#endif + /* ** Tclcmd: sql_exec_changeset DB SQL */ @@ -127,6 +140,7 @@ static int SQLITE_TCLAPI test_sql_exec_changeset( return TCL_ERROR; } + assert_changeset_is_ok(nChangeset, pChangeset); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset)); sqlite3_free(pChangeset); return TCL_OK; @@ -295,6 +309,7 @@ static int SQLITE_TCLAPI test_session_cmd( } } if( rc==SQLITE_OK ){ + assert_changeset_is_ok(o.n, o.p); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); } sqlite3_free(o.p); @@ -953,6 +968,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_invert( if( rc!=SQLITE_OK ){ rc = test_session_error(interp, rc, 0); }else{ + assert_changeset_is_ok(sOut.n, sOut.p); Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); } sqlite3_free(sOut.p); @@ -1001,6 +1017,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat( if( rc!=SQLITE_OK ){ rc = test_session_error(interp, rc, 0); }else{ + assert_changeset_is_ok(sOut.n, sOut.p); Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); } sqlite3_free(sOut.p); @@ -1236,6 +1253,7 @@ static int SQLITE_TCLAPI test_rebaser_cmd( } if( rc==SQLITE_OK ){ + assert_changeset_is_ok(sOut.n, sOut.p); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n)); } sqlite3_free(sOut.p); @@ -1282,6 +1300,99 @@ static int SQLITE_TCLAPI test_sqlite3rebaser_create( return TCL_OK; } +/* +** +*/ +static int sqlite3_test_changeset( + int nChangeset, + void *pChangeset, + char **pzErr +){ + sqlite3_changeset_iter *pIter = 0; + char *zErr = 0; + int rc = SQLITE_OK; + int bPatch = (nChangeset>0 && ((char*)pChangeset)[0]=='P'); + + rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); + if( rc==SQLITE_OK ){ + int rc2; + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ + unsigned char *aPk = 0; + int nCol = 0; + int op = 0; + const char *zTab = 0; + + sqlite3changeset_pk(pIter, &aPk, &nCol); + sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); + + if( op==SQLITE_UPDATE ){ + int iCol; + for(iCol=0; iCol/dev/null) +MAKEFILE := $(lastword $(MAKEFILE_LIST)) +CLEAN_FILES := +DISTCLEAN_FILES := ./--dummy-- +default: all +release: oz + +# Emscripten SDK home dir and related binaries... +EMSDK_HOME ?= $(word 1,$(wildcard $(HOME)/emsdk $(HOME)/src/emsdk)) +emcc.bin ?= $(word 1,$(wildcard $(EMSDK_HOME)/upstream/emscripten/emcc) $(shell which emcc)) +ifeq (,$(emcc.bin)) + $(error Cannot find emcc.) +endif + +wasm-strip ?= $(shell which wasm-strip 2>/dev/null) +ifeq (,$(filter clean,$(MAKECMDGOALS))) +ifeq (,$(wasm-strip)) + $(info WARNING: *******************************************************************) + $(info WARNING: builds using -O2/-O3/-Os/-Oz will minify WASM-exported names,) + $(info WARNING: breaking _All The Things_. The workaround for that is to build) + $(info WARNING: with -g3 (which explodes the file size) and then strip the debug) + $(info WARNING: info after compilation, using wasm-strip, to shrink the wasm file.) + $(info WARNING: wasm-strip was not found in the PATH so we cannot strip those.) + $(info WARNING: If this build uses any optimization level higher than -O1 then) + $(info WARNING: the ***resulting JS code WILL NOT BE USABLE***.) + $(info WARNING: wasm-strip is part of the wabt package:) + $(info WARNING: https://github.com/WebAssembly/wabt) + $(info WARNING: on Ubuntu-like systems it can be installed with:) + $(info WARNING: sudo apt install wabt) + $(info WARNING: *******************************************************************) +endif +endif # 'make clean' check + +ifeq (,$(wasm-strip)) + maybe-wasm-strip = echo "not wasm-stripping" +else + maybe-wasm-strip = $(wasm-strip) +endif + +dir.top := ../.. +# Reminder: some Emscripten flags require absolute paths but we want +# relative paths for most stuff simply to reduce noise. The +# $(abspath...) GNU make function can transform relative paths to +# absolute. +dir.wasm := $(patsubst %/,%,$(dir $(MAKEFILE))) +dir.api := api +dir.jacc := jaccwabyt +dir.common := common +dir.fiddle := fiddle +dir.tool := $(dir.top)/tool +######################################################################## +# dir.dout = output dir for deliverables. +# +# MAINTENANCE REMINDER: the output .js and .wasm files of emcc must be +# in _this_ dir, rather than a subdir, or else parts of the generated +# code get confused and cannot load property. Specifically, when X.js +# loads X.wasm, whether or not X.js uses the correct path for X.wasm +# depends on how it's loaded: an HTML script tag will resolve it +# intuitively, whereas a Worker's call to importScripts() will not. +# That's a fundamental incompatibility with how URL resolution in +# JS happens between those two contexts. See: +# +# https://zzz.buzz/2017/03/14/relative-uris-in-web-development/ +# +# We unfortunately have no way, from Worker-initiated code, to +# automatically resolve the path from X.js to X.wasm. +# +# We have an "only slightly unsightly" solution for our main builds +# but it does not work for the WASMFS builds, so those builds have to +# be built to _this_ directory and can only run when the client app is +# loaded from the same directory. +dir.dout := $(dir.wasm)/jswasm +# dir.tmp = output dir for intermediary build files, as opposed to +# end-user deliverables. +dir.tmp := $(dir.wasm)/bld +CLEAN_FILES += $(dir.tmp)/* $(dir.dout)/* +ifeq (,$(wildcard $(dir.dout))) + dir._tmp := $(shell mkdir -p $(dir.dout)) +endif +ifeq (,$(wildcard $(dir.tmp))) + dir._tmp := $(shell mkdir -p $(dir.tmp)) +endif + +cflags.common := -I. -I.. -I$(dir.top) +CLEAN_FILES += *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~ +emcc.WASM_BIGINT ?= 1 +sqlite3.c := $(dir.top)/sqlite3.c +sqlite3.h := $(dir.top)/sqlite3.h +SQLITE_OPT = \ + -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_BYTECODE_VTAB \ + -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_OMIT_UTF16 \ + -DSQLITE_OMIT_SHARED_CACHE \ + -DSQLITE_OMIT_WAL \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_TEMP_STORE=3 \ + -DSQLITE_OS_KV_OPTIONAL=1 \ + '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \ + -DSQLITE_USE_URI=1 \ + -DSQLITE_WASM_ENABLE_C_TESTS +# ^^^ most flags are set in sqlite3-wasm.c but we need them +# made explicit here for building speedtest1.c. + +ifneq (,$(filter release,$(MAKECMDGOALS))) +emcc_opt ?= -Oz -flto +else +emcc_opt ?= -O0 +# ^^^^ build times for -O levels higher than 0 are painful at +# dev-time. +endif +# When passing emcc_opt from the CLI, += and re-assignment have no +# effect, so emcc_opt+=-g3 doesn't work. So... +emcc_opt_full := $(emcc_opt) -g3 +# ^^^ ALWAYS use -g3. See below for why. +# +# ^^^ -flto improves runtime speed at -O0 considerably but doubles +# build time. +# +# ^^^^ -O3, -Oz, -Os minify symbol names and there appears to be no +# way around that except to use -g3, but -g3 causes the binary file +# size to absolutely explode (approx. 5x larger). This minification +# utterly breaks the resulting module, making it unsable except as +# self-contained/self-referential-only code, as ALL of the exported +# symbols get minified names. +# +# However, we have an option for using -Oz or -Os: +# +# Build with (-Os -g3) or (-Oz -g3) then use wasm-strip, from the wabt +# tools package (https://github.com/WebAssembly/wabt), to strip the +# debugging symbols. That results in a small build with unmangled +# symbol names. -Oz gives ever-so-slightly better compression than +# -Os: not quite 1% in some completely unscientific tests. Runtime +# speed for the unit tests is all over the place either way so it's +# difficult to say whether -Os gives any speed benefit over -Oz. +# +# (Much later: -O2 consistently gives the best speeds.) +######################################################################## + + +$(sqlite3.c) $(sqlite3.h): + $(MAKE) -C $(dir.top) sqlite3.c + +.PHONY: clean distclean +clean: + -rm -f $(CLEAN_FILES) +distclean: clean + -rm -f $(DISTCLEAN_FILES) + +ifeq (release,$(filter release,$(MAKECMDGOALS))) + ifeq (,$(wasm-strip)) + $(error Cannot make release-quality binary because wasm-strip is not available. \ + See notes in the warning above) + endif +else + $(info Development build. Use '$(MAKE) release' for a smaller release build.) +endif + +bin.version-info := $(dir.wasm)/version-info +# ^^^^ NOT in $(dir.tmp) because we need it to survive the cleanup +# process for the dist build to work properly. +$(bin.version-info): $(dir.wasm)/version-info.c $(sqlite3.h) $(MAKEFILE) + $(CC) -O0 -I$(dir.top) -o $@ $< +DISTCLEAN_FILES += $(bin.version-info) + +bin.stripccomments := $(dir.tool)/stripccomments +$(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE) + $(CC) -o $@ $< +DISTCLEAN_FILES += $(bin.stripccomments) + +EXPORTED_FUNCTIONS.api.in := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) +EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api +$(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE) + cat $(EXPORTED_FUNCTIONS.api.in) > $@ + +sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js +sqlite3-license-version-header.js := $(dir.api)/sqlite3-license-version-header.js +sqlite3-api-build-version.js := $(dir.tmp)/sqlite3-api-build-version.js +# sqlite3-api.jses = the list of JS files which make up $(sqlite3-api.js), in +# the order they need to be assembled. +sqlite3-api.jses := $(sqlite3-license-version.js) +sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js +sqlite3-api.jses += $(dir.common)/whwasmutil.js +sqlite3-api.jses += $(dir.jacc)/jaccwabyt.js +sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js +sqlite3-api.jses += $(sqlite3-api-build-version.js) +sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js +sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js +sqlite3-api.jses += $(dir.api)/sqlite3-api-opfs.js +sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js + +# "External" API files which are part of our distribution +# but not part of the sqlite3-api.js amalgamation. +SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js +sqlite3-worker1.js := $(dir.api)/sqlite3-worker1.js +sqlite3-worker1-promiser.js := $(dir.api)/sqlite3-worker1-promiser.js +define CP_XAPI +sqlite3-api.ext.jses += $$(dir.dout)/$$(notdir $(1)) +$$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE) + cp $$< $$@ +endef +$(foreach X,$(SOAP.js) $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js),\ + $(eval $(call CP_XAPI,$(X)))) +all: $(sqlite3-api.ext.jses) + +sqlite3-api.js := $(dir.tmp)/sqlite3-api.js +$(sqlite3-api.js): $(sqlite3-api.jses) $(MAKEFILE) + @echo "Making $@..." + @for i in $(sqlite3-api.jses); do \ + echo "/* BEGIN FILE: $$i */"; \ + cat $$i; \ + echo "/* END FILE: $$i */"; \ + done > $@ + +$(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) + @echo "Making $@..." + @{ \ + echo 'self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ + echo -n ' sqlite3.version = '; \ + $(bin.version-info) --json; \ + echo ';'; \ + echo '});'; \ + } > $@ + +######################################################################## +# --post-js and --pre-js are emcc flags we use to append/prepend JS to +# the generated emscripten module file. +pre-js.js := $(dir.api)/pre-js.js +post-js.js := $(dir.tmp)/post-js.js +post-jses := \ + $(dir.api)/post-js-header.js \ + $(sqlite3-api.js) \ + $(dir.api)/post-js-footer.js +$(post-js.js): $(post-jses) $(MAKEFILE) + @echo "Making $@..." + @for i in $(post-jses); do \ + echo "/* BEGIN FILE: $$i */"; \ + cat $$i; \ + echo "/* END FILE: $$i */"; \ + done > $@ +extern-post-js.js := $(dir.api)/extern-post-js.js +extern-pre-js.js := $(dir.api)/extern-pre-js.js +pre-post-common.flags := \ + --post-js=$(post-js.js) \ + --extern-post-js=$(extern-post-js.js) \ + --extern-pre-js=$(sqlite3-license-version.js) +pre-post-jses.deps := $(post-js.js) \ + $(extern-post-js.js) $(extern-pre-js.js) $(sqlite3-license-version.js) +$(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) $(MAKEFILE) + @echo "Making $@..."; { \ + cat $(sqlite3-license-version-header.js); \ + echo '/*'; \ + echo '** This code was built from sqlite3 version...'; \ + echo "** "; \ + awk -e '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' \ + -e '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ + echo '*/'; \ + } > $@ + +######################################################################## +# call-make-pre-js creates rules for pre-js-$(1).js. $1 = the base +# name of the JS file on whose behalf this pre-js is for. +define call-make-pre-js +pre-post-$(1).flags ?= +$$(dir.tmp)/pre-js-$(1).js: $$(pre-js.js) $$(MAKEFILE) + cp $$(pre-js.js) $$@ + @if [ sqlite3-wasmfs = $(1) ]; then \ + echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \ + elif [ sqlite3 != $(1) ]; then \ + echo "Module[xNameOfInstantiateWasm].uri = '$(1).wasm';"; \ + fi >> $$@ +pre-post-$(1).deps := $$(pre-post-jses.deps) $$(dir.tmp)/pre-js-$(1).js +pre-post-$(1).flags += --pre-js=$$(dir.tmp)/pre-js-$(1).js +endef +#$(error $(call call-make-pre-js,sqlite3-wasmfs)) +# /post-js and pre-js +######################################################################## + +######################################################################## +# emcc flags for .c/.o/.wasm/.js. +emcc.flags := +#emcc.flags += -v # _very_ loud but also informative about what it's doing +# -g3 is needed to keep -O2 and higher from creating broken JS via +# minification. + +######################################################################## +# emcc flags for .c/.o. +emcc.cflags := +emcc.cflags += -std=c99 -fPIC +# -------------^^^^^^^^ we currently need c99 for WASM-specific sqlite3 APIs. +emcc.cflags += -I. -I$(dir.top) + +######################################################################## +# emcc flags specific to building the final .js/.wasm file... +emcc.jsflags := -fPIC +emcc.jsflags += --minify 0 +emcc.jsflags += --no-entry +emcc.jsflags += -sMODULARIZE +emcc.jsflags += -sSTRICT_JS +emcc.jsflags += -sDYNAMIC_EXECUTION=0 +emcc.jsflags += -sNO_POLYFILL +emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.api) +emcc.exportedRuntimeMethods := \ + -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory + # FS ==> stdio/POSIX I/O proxies + # wasmMemory ==> required by our code for use with -sIMPORTED_MEMORY +emcc.jsflags += $(emcc.exportedRuntimeMethods) +emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 +emcc.jsflags += -sIMPORTED_MEMORY +emcc.environment := -sENVIRONMENT=web,worker +######################################################################## +# -sINITIAL_MEMORY: How much memory we need to start with is governed +# at least in part by whether -sALLOW_MEMORY_GROWTH is enabled. If so, +# we can start with less. If not, we need as much as we'll ever +# possibly use (which, of course, we can't know for sure). Note, +# however, that speedtest1 shows that performance for even moderate +# workloads MAY suffer considerably if we start small and have to grow +# at runtime. e.g. OPFS-backed (speedtest1 --size 75) take MAY take X +# time with 16mb+ memory and 3X time when starting with 8MB. However, +# such test results are inconsistent due to browser internals which +# are opaque to us. +emcc.jsflags += -sALLOW_MEMORY_GROWTH +emcc.INITIAL_MEMORY.128 := 13107200 +emcc.INITIAL_MEMORY.96 := 100663296 +emcc.INITIAL_MEMORY.64 := 64225280 +emcc.INITIAL_MEMORY.32 := 33554432 +emcc.INITIAL_MEMORY.16 := 16777216 +emcc.INITIAL_MEMORY.8 := 8388608 +emcc.INITIAL_MEMORY ?= 16 +ifeq (,$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY))) +$(error emcc.INITIAL_MEMORY must be one of: 8, 16, 32, 64, 96, 128 (megabytes)) +endif +emcc.jsflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) +# /INITIAL_MEMORY +######################################################################## + +emcc.jsflags += $(emcc.environment) +#emcc.jsflags += -sTOTAL_STACK=4194304 + +sqlite3.js.init-func := sqlite3InitModule +# ^^^^ $(sqlite3.js.init-func) symbol name is hard-coded in +# $(extern-post-js.js) as well as in numerous docs. If changed, it +# needs to be globally modified in *.js and all related documentation. + +emcc.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) +emcc.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. +#emcc.jsflags += -sSTRICT # fails due to missing __syscall_...() +#emcc.jsflags += -sALLOW_UNIMPLEMENTED_SYSCALLS +#emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API +#emcc.jsflags += -sABORTING_MALLOC +emcc.jsflags += -sALLOW_TABLE_GROWTH +# -sALLOW_TABLE_GROWTH is required for installing new SQL UDFs +emcc.jsflags += -Wno-limited-postlink-optimizations +# ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag. +#emcc.jsflags += -sSTANDALONE_WASM # causes OOM errors, not sure why +# https://lld.llvm.org/WebAssembly.html +emcc.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=0 +emcc.jsflags += -sLLD_REPORT_UNDEFINED +#emcc.jsflags += --allow-undefined +#emcc.jsflags += --import-undefined +#emcc.jsflags += --unresolved-symbols=import-dynamic --experimental-pic +#emcc.jsflags += --experimental-pic --unresolved-symbols=ingore-all --import-undefined +#emcc.jsflags += --unresolved-symbols=ignore-all +emcc.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) + +######################################################################## +# -sMEMORY64=1 fails to load, erroring with: +# invalid memory limits flags 0x5 +# (enable via --experimental-wasm-memory64) +# +# ^^^^ MEMORY64=2 builds and loads but dies when we do things like: +# +# new Uint8Array(wasm.heap8u().buffer, ptr, n) +# +# because ptr is now a BigInt, so is invalid for passing to arguments +# which have strict must-be-a-Number requirements. +######################################################################## + + +######################################################################## +# -sSINGLE_FILE: +# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js#L1704 +# -sSINGLE_FILE=1 would be really nice but we have to build with -g3 +# for -O2 and higher to work (else minification breaks the code) and +# cannot wasm-strip the binary before it gets encoded into the JS +# file. The result is that the generated JS file is, because of the -g3 +# debugging info, _huge_. +######################################################################## + +######################################################################## +# AN EXPERIMENT: undocumented Emscripten feature: if the target file +# extension is "mjs", it defaults to ES6 module builds: +# https://github.com/emscripten-core/emscripten/issues/14383 +ifeq (,$(filter esm,$(MAKECMDGOALS))) +sqlite3.js.ext := js +else +esm.deps := $(filter-out esm,$(MAKECMDGOALS)) +esm: $(if $(esm.deps),$(esm.deps),all) +sqlite3.js.ext := mjs +endif +# /esm +######################################################################## +sqlite3.js := $(dir.dout)/sqlite3.$(sqlite3.js.ext) +sqlite3.wasm := $(dir.dout)/sqlite3.wasm +sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c +# sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter +# (predictably) results in a slightly faster binary, but we're close +# enough to the target speed requirements that the 500ms makes a +# difference. Thus we build all binaries against sqlite3-wasm.c +# instead of building a shared copy of sqlite3-wasm.o. +$(eval $(call call-make-pre-js,sqlite3)) +$(sqlite3.js): +$(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \ + $(EXPORTED_FUNCTIONS.api) \ + $(pre-post-sqlite3.deps) + @echo "Building $@ ..." + $(emcc.bin) -o $@ $(emcc_opt_full) $(emcc.flags) \ + $(emcc.jsflags) $(pre-post-common.flags) $(pre-post-sqlite3.flags) \ + $(cflags.common) $(SQLITE_OPT) $(sqlite3-wasm.c) + chmod -x $(sqlite3.wasm) + $(maybe-wasm-strip) $(sqlite3.wasm) + @ls -la $@ $(sqlite3.wasm) +$(sqlite3.wasm): $(sqlite3.js) +CLEAN_FILES += $(sqlite3.js) $(sqlite3.wasm) +all: $(sqlite3.js) +wasm: $(sqlite3.js) +# End main Emscripten-based module build +######################################################################## + +######################################################################## +# batch-runner.js... +dir.sql := sql +speedtest1 := ../../speedtest1 +speedtest1.c := ../../test/speedtest1.c +speedtest1.sql := $(dir.sql)/speedtest1.sql +speedtest1.cliflags := --size 25 --big-transactions +$(speedtest1): + $(MAKE) -C ../.. speedtest1 +$(speedtest1.sql): $(speedtest1) $(MAKEFILE) + $(speedtest1) $(speedtest1.cliflags) --script $@ +batch-runner.list: $(MAKEFILE) $(speedtest1.sql) $(dir.sql)/000-mandelbrot.sql + bash split-speedtest1-script.sh $(dir.sql)/speedtest1.sql + ls -1 $(dir.sql)/*.sql | grep -v speedtest1.sql | sort > $@ +clean-batch: + rm -f batch-runner.list $(dir.sql)/speedtest1*.sql +# ^^^ we don't do this along with 'clean' because we clean/rebuild on +# a regular basis with different -Ox flags and rebuilding the batch +# pieces each time is an unnecessary time sink. +batch: batch-runner.list +all: batch +# end batch-runner.js +######################################################################## +# speedtest1.js... +# speedtest1-common.eflags = emcc flags used by multiple builds of speedtest1 +# speedtest1.eflags = emcc flags used by main build of speedtest1 +speedtest1-common.eflags := $(emcc_opt_full) +speedtest1.eflags := +speedtest1.eflags += -sENVIRONMENT=web +speedtest1.eflags += -sALLOW_MEMORY_GROWTH +speedtest1.eflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) +speedtest1-common.eflags += -sINVOKE_RUN=0 +speedtest1-common.eflags += --no-entry +#speedtest1-common.eflags += -flto +speedtest1-common.eflags += -sABORTING_MALLOC +speedtest1-common.eflags += -sSTRICT_JS +speedtest1-common.eflags += -sMODULARIZE +speedtest1-common.eflags += -Wno-limited-postlink-optimizations +EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) +speedtest1-common.eflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) +speedtest1-common.eflags += $(emcc.exportedRuntimeMethods) +speedtest1-common.eflags += -sALLOW_TABLE_GROWTH +speedtest1-common.eflags += -sDYNAMIC_EXECUTION=0 +speedtest1-common.eflags += --minify 0 +speedtest1-common.eflags += -sEXPORT_NAME=$(sqlite3.js.init-func) +speedtest1-common.eflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) +speedtest1-common.eflags += $(pre-post-common.flags) +speedtest1.exit-runtime0 := -sEXIT_RUNTIME=0 +speedtest1.exit-runtime1 := -sEXIT_RUNTIME=1 +# Re -sEXIT_RUNTIME=1 vs 0: if it's 1 and speedtest1 crashes, we get +# this error from emscripten: +# +# > native function `free` called after runtime exit (use +# NO_EXIT_RUNTIME to keep it alive after main() exits)) +# +# If it's 0 and it crashes, we get: +# +# > stdio streams had content in them that was not flushed. you should +# set EXIT_RUNTIME to 1 (see the FAQ), or make sure to emit a newline +# when you printf etc. +# +# and pending output is not flushed because it didn't end with a +# newline (by design). The lesser of the two evils seems to be +# -sEXIT_RUNTIME=1 but we need EXIT_RUNTIME=0 for the worker-based app +# which runs speedtest1 multiple times. + +$(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api) + @echo "Making $@ ..." + @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ +speedtest1.js := $(dir.dout)/speedtest1.js +speedtest1.wasm := $(subst .js,.wasm,$(speedtest1.js)) +speedtest1.cflags := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM +speedtest1.cses := $(speedtest1.c) $(sqlite3-wasm.c) +$(eval $(call call-make-pre-js,speedtest1)) +$(speedtest1.js): $(MAKEFILE) $(speedtest1.cses) \ + $(pre-post-speedtest1.deps) \ + $(EXPORTED_FUNCTIONS.speedtest1) + @echo "Building $@ ..." + $(emcc.bin) \ + $(speedtest1.eflags) $(speedtest1-common.eflags) $(speedtest1.cflags) \ + $(pre-post-speedtest1.flags) \ + $(SQLITE_OPT) \ + $(speedtest1.exit-runtime0) \ + -o $@ $(speedtest1.cses) -lm + $(maybe-wasm-strip) $(speedtest1.wasm) + ls -la $@ $(speedtest1.wasm) + +speedtest1: $(speedtest1.js) +all: speedtest1 +CLEAN_FILES += $(speedtest1.js) $(speedtest1.wasm) +# end speedtest1.js +######################################################################## + +######################################################################## +# Convenience rules to rebuild with various -Ox levels. Much +# experimentation shows -O2 to be the clear winner in terms of speed. +# Note that build times with anything higher than -O0 are somewhat +# painful. + +.PHONY: o0 o1 o2 o3 os oz +o-xtra := -flto +# ^^^^ -flto can have a considerably performance boost at -O0 but +# doubles the build time and seems to have negligible effect on +# higher optimization levels. +o0: clean + $(MAKE) -e "emcc_opt=-O0" +o1: clean + $(MAKE) -e "emcc_opt=-O1 $(o-xtra)" +o2: clean + $(MAKE) -e "emcc_opt=-O2 $(o-xtra)" +o3: clean + $(MAKE) -e "emcc_opt=-O3 $(o-xtra)" +os: clean + @echo "WARNING: -Os can result in a build with mysteriously missing pieces!" + $(MAKE) -e "emcc_opt=-Os $(o-xtra)" +oz: clean + $(MAKE) -e "emcc_opt=-Oz $(o-xtra)" + +######################################################################## +# Sub-makes... + +include fiddle.make + +# Only add wasmfs if wasmfs.enable=1 or we're running (dist)clean +wasmfs.enable ?= $(if $(filter %clean,$(MAKECMDGOALS)),1,0) +ifeq (1,$(wasmfs.enable)) +# wasmfs build disabled 2022-10-19 per /chat discussion. +# OPFS-over-wasmfs was initially a stopgap measure and a convenient +# point of comparison for the OPFS sqlite3_vfs's performance, but it +# currently doubles our deliverables and build maintenance burden for +# little, if any, benefit. +# +######################################################################## +# Some platforms do not support the WASMFS build. Raspberry Pi OS is one +# of them. As such platforms are discovered, add their (uname -m) name +# to PLATFORMS_WITH_NO_WASMFS to exclude the wasmfs build parts. +PLATFORMS_WITH_NO_WASMFS := aarch64 # add any others here +THIS_ARCH := $(shell /usr/bin/uname -m) +ifneq (,$(filter $(THIS_ARCH),$(PLATFORMS_WITH_NO_WASMFS))) +$(info This platform does not support the WASMFS build.) +HAVE_WASMFS := 0 +else +HAVE_WASMFS := 1 +include wasmfs.make +endif +endif +# /wasmfs +######################################################################## + +######################################################################## +# Create deliverables: +ifneq (,$(filter dist,$(MAKECMDGOALS))) +include dist.make +endif + +######################################################################## +# Push files to public wasm-testing.sqlite.org server +wasm-testing.include = $(dir.dout) *.js *.html \ + batch-runner.list $(dir.sql) $(dir.common) $(dir.fiddle) $(dir.jacc) +wasm-testing.exclude = sql/speedtest1.sql +wasm-testing.dir = /jail/sites/wasm-testing +wasm-testing.dest ?= wasm-testing:$(wasm-testing.dir) +# ---------------------^^^^^^^^^^^^ ssh alias +.PHONY: push-testing +push-testing: + rsync -z -e ssh --ignore-times --chown=stephan:www-data --group -r \ + $(patsubst %,--exclude=%,$(wasm-testing.exclude)) \ + $(wasm-testing.include) $(wasm-testing.dest) + @echo "Updating gzipped copies..."; \ + ssh wasm-testing 'cd $(wasm-testing.dir) && bash .gzip' || \ + echo "SSH failed: it's likely that stale content will be served via old gzip files." + +######################################################################## +# If we find a copy of the sqlite.org/wasm docs checked out, copy +# certain files over to it, noting that some need automatable edits... +WDOCS.home ?= ../../../wdoc +.PHONY: update-docs +ifneq (,$(wildcard $(WDOCS.home)/api-index.md)) +WDOCS.jswasm := $(WDOCS.home)/jswasm +update-docs: $(bin.stripccomments) $(sqlite3.js) $(sqlite3.wasm) + @echo "Copying files to the /wasm docs. Be sure to use an -Oz build for this!" + cp $(sqlite3.wasm) $(WDOCS.jswasm)/. + $(bin.stripccomments) -k -k < $(sqlite3.js) \ + | sed -e '/^[ \t]*$$/d' > $(WDOCS.jswasm)/sqlite3.js + cp demo-123.js demo-123.html demo-123-worker.html $(WDOCS.home) + sed -n -e '/EXTRACT_BEGIN/,/EXTRACT_END/p' \ + module-symbols.html > $(WDOCS.home)/module-symbols.html +else +update-docs: + @echo "Cannot find wasm docs checkout."; \ + echo "Pass WDOCS.home=/path/to/wasm/docs/checkout or edit this makefile to suit."; \ + exit 127 +endif +# end /wasm docs +######################################################################## diff --git a/ext/wasm/README-dist.txt b/ext/wasm/README-dist.txt new file mode 100644 index 000000000..ca6bef93e --- /dev/null +++ b/ext/wasm/README-dist.txt @@ -0,0 +1,23 @@ +This is the README for the sqlite3 WASM/JS distribution. + +Main project page: https://sqlite.org + +Documentation: https://sqlite.org/wasm + +This archive contains the sqlite3.js and sqlite3.wasm file which make +up the sqlite3 WASM/JS build. + +The jswasm directory contains the core sqlite3 deliverables and the +top-level directory contains demonstration and test apps. Browsers +will not serve WASM files from file:// URLs, so the demo/test apps +require a web server and that server must include the following +headers in its response when serving the files: + + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp + +One simple way to get the demo apps up and running on Unix-style +systems is to install althttpd (https://sqlite.org/althttpd) and run: + + althttpd --enable-sab --page index.html + diff --git a/ext/wasm/README.md b/ext/wasm/README.md new file mode 100644 index 000000000..e8d66865d --- /dev/null +++ b/ext/wasm/README.md @@ -0,0 +1,105 @@ +This directory houses the [Web Assembly (WASM)](https://en.wikipedia.org/wiki/WebAssembly) +parts of the sqlite3 build. + +It requires [emscripten][] and that the build environment be set up for +emscripten. A mini-HOWTO for setting that up follows... + +First, install the Emscripten SDK, as documented +[here](https://emscripten.org/docs/getting_started/downloads.html) and summarized +below for Linux environments: + +``` +# Clone the emscripten repository: +$ sudo apt install git +$ git clone https://github.com/emscripten-core/emsdk.git +$ cd emsdk + +# Download and install the latest SDK tools: +$ ./emsdk install latest + +# Make the "latest" SDK "active" for the current user: +$ ./emsdk activate latest +``` + +Those parts only need to be run once, but the SDK can be updated using: + +``` +$ git pull +$ ./emsdk install latest +$ ./emsdk activate latest +``` + +The following needs to be run for each shell instance which needs the +`emcc` compiler: + +``` +# Activate PATH and other environment variables in the current terminal: +$ source ./emsdk_env.sh + +$ which emcc +/path/to/emsdk/upstream/emscripten/emcc +``` + +Optionally, add that to your login shell's resource file (`~/.bashrc` +or equivalent). + +That `env` script needs to be sourced for building this application +from the top of the sqlite3 build tree: + +``` +$ make fiddle +``` + +Or: + +``` +$ cd ext/wasm +$ make +``` + +That will generate the a number of files required for a handful of +test and demo applications which can be accessed via +`index.html`. WASM content cannot, due to XMLHttpRequest security +limitations, be loaded if the containing HTML file is opened directly +in the browser (i.e. if it is opened using a `file://` URL), so it +needs to be served via an HTTP server. For example, using +[althttpd][]: + +``` +$ cd ext/wasm +$ althttpd --enable-sab --max-age 1 --page index.html +``` + +That will open the system's browser and run the index page, from which +all of the test and demo applications can be accessed. + +Note that when serving this app via [althttpd][], it must be a version +from 2022-09-26 or newer so that it recognizes the `--enable-sab` +flag, which causes althttpd to emit two HTTP response headers which +are required to enable JavaScript's `SharedArrayBuffer` and `Atomics` +APIs. Those APIs are required in order to enable the OPFS-related +features in the apps which use them. + +# Testing on a remote machine that is accessed via SSH + +*NB: The following are developer notes, last validated on 2022-08-18* + + * Remote: Install git, emsdk, and althttpd + * Use a [version of althttpd][althttpd] from + September 26, 2022 or newer. + * Remote: Install the SQLite source tree. CD to ext/wasm + * Remote: "`make`" to build WASM + * Remote: `althttpd --enable-sab --port 8080 --popup` + * Local: `ssh -L 8180:localhost:8080 remote` + * Local: Point your web-browser at http://localhost:8180/index.html + +In order to enable [SharedArrayBuffers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer), +the web-browser requires that the two extra Cross-Origin lines be present +in HTTP reply headers and that the request must come from "localhost". +Since the web-server is on a different machine from +the web-broser, the localhost requirement means that the connection must be tunneled +using SSH. + + +[emscripten]: https://emscripten.org +[althttpd]: https://sqlite.org/althttpd diff --git a/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api similarity index 51% rename from ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api rename to ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api index 3127b294d..b903bedee 100644 --- a/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -1,3 +1,4 @@ +_sqlite3_aggregate_context _sqlite3_bind_blob _sqlite3_bind_double _sqlite3_bind_int @@ -7,6 +8,7 @@ _sqlite3_bind_parameter_count _sqlite3_bind_parameter_index _sqlite3_bind_text _sqlite3_changes +_sqlite3_changes64 _sqlite3_clear_bindings _sqlite3_close_v2 _sqlite3_column_blob @@ -21,31 +23,72 @@ _sqlite3_column_text _sqlite3_column_type _sqlite3_compileoption_get _sqlite3_compileoption_used +_sqlite3_create_function _sqlite3_create_function_v2 +_sqlite3_create_window_function _sqlite3_data_count _sqlite3_db_filename +_sqlite3_db_handle +_sqlite3_db_name +_sqlite3_deserialize _sqlite3_errmsg +_sqlite3_error_offset +_sqlite3_errstr _sqlite3_exec +_sqlite3_expanded_sql +_sqlite3_extended_errcode +_sqlite3_extended_result_codes +_sqlite3_file_control _sqlite3_finalize -_sqlite3_interrupt +_sqlite3_free +_sqlite3_initialize _sqlite3_libversion +_sqlite3_libversion_number +_sqlite3_malloc +_sqlite3_malloc64 +_sqlite3_msize _sqlite3_open _sqlite3_open_v2 _sqlite3_prepare_v2 -_sqlite3_prepare_v2 +_sqlite3_prepare_v3 +_sqlite3_randomness +_sqlite3_realloc +_sqlite3_realloc64 _sqlite3_reset _sqlite3_result_blob _sqlite3_result_double _sqlite3_result_error +_sqlite3_result_error_code +_sqlite3_result_error_nomem +_sqlite3_result_error_toobig _sqlite3_result_int +_sqlite3_result_int64 _sqlite3_result_null _sqlite3_result_text +_sqlite3_serialize +_sqlite3_shutdown _sqlite3_sourceid _sqlite3_sql _sqlite3_step +_sqlite3_strglob +_sqlite3_strlike +_sqlite3_total_changes +_sqlite3_total_changes64 +_sqlite3_trace_v2 +_sqlite3_uri_boolean +_sqlite3_uri_int64 +_sqlite3_uri_key +_sqlite3_uri_parameter +_sqlite3_user_data _sqlite3_value_blob _sqlite3_value_bytes _sqlite3_value_double +_sqlite3_value_int +_sqlite3_value_int64 _sqlite3_value_text _sqlite3_value_type +_sqlite3_vfs_find +_sqlite3_vfs_register +_sqlite3_vfs_unregister +_malloc _free diff --git a/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api b/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api new file mode 100644 index 000000000..aab1d8bd3 --- /dev/null +++ b/ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api @@ -0,0 +1,3 @@ +FS +wasmMemory + diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md new file mode 100644 index 000000000..6440eba99 --- /dev/null +++ b/ext/wasm/api/README.md @@ -0,0 +1,133 @@ +# sqlite3-api.js And Friends + +This is the README for the files `sqlite3-*.js` and +`sqlite3-wasm.c`. This collection of files is used to build a +single-file distribution of the sqlite3 WASM API. It is broken into +multiple JS files because: + +1. To facilitate including or excluding certain components for + specific use cases. e.g. by removing `sqlite3-api-oo1.js` if the + OO#1 API is not needed. + +2. To facilitate modularizing the pieces for use in different WASM + build environments. e.g. the files `post-js-*.js` are for use with + Emscripten's `--post-js` feature, and nowhere else. + +3. Certain components must be in their own standalone files in order + to be loaded as JS Workers. + +Note that the structure described here is the current state of things, +not necessarily the "final" state. + +The overall idea is that the following files get concatenated +together, in the listed order, the resulting file is loaded by a +browser client: + +- `sqlite3-api-prologue.js`\ + Contains the initial bootstrap setup of the sqlite3 API + objects. This is exposed as a function, rather than objects, so that + the next step can pass in a config object which abstracts away parts + of the WASM environment, to facilitate plugging it in to arbitrary + WASM toolchains. +- `../common/whwasmutil.js`\ + A semi-third-party collection of JS/WASM utility code intended to + replace much of the Emscripten glue. The sqlite3 APIs internally use + these APIs instead of their Emscripten counterparts, in order to be + more portable to arbitrary WASM toolchains. This API is + configurable, in principle, for use with arbitrary WASM + toolchains. It is "semi-third-party" in that it was created in order + to support this tree but is standalone and maintained together + with... +- `../jaccwabyt/jaccwabyt.js`\ + Another semi-third-party API which creates bindings between JS + and C structs, such that changes to the struct state from either JS + or C are visible to the other end of the connection. This is also an + independent spinoff project, conceived for the sqlite3 project but + maintained separately. +- `sqlite3-api-glue.js`\ + Invokes functionality exposed by the previous two files to + flesh out low-level parts of `sqlite3-api-prologue.js`. Most of + these pieces related to the `sqlite3.capi.wasm` object. +- `sqlite3-api-build-version.js`\ + Gets created by the build process and populates the + `sqlite3.version` object. This part is not critical, but records the + version of the library against which this module was built. +- `sqlite3-api-oo1.js`\ + Provides a high-level object-oriented wrapper to the lower-level C + API, colloquially known as OO API #1. Its API is similar to other + high-level sqlite3 JS wrappers and should feel relatively familiar + to anyone familiar with such APIs. That said, it is not a "required + component" and can be elided from builds which do not want it. +- `sqlite3-api-worker1.js`\ + A Worker-thread-based API which uses OO API #1 to provide an + interface to a database which can be driven from the main Window + thread via the Worker message-passing interface. Like OO API #1, + this is an optional component, offering one of any number of + potential implementations for such an API. + - `sqlite3-worker1.js`\ + Is not part of the amalgamated sources and is intended to be + loaded by a client Worker thread. It loads the sqlite3 module + and runs the Worker #1 API which is implemented in + `sqlite3-api-worker1.js`. + - `sqlite3-worker1-promiser.js`\ + Is likewise not part of the amalgamated sources and provides + a Promise-based interface into the Worker #1 API. This is + a far user-friendlier way to interface with databases running + in a Worker thread. +- `sqlite3-api-opfs.js`\ + is an sqlite3 VFS implementation which supports Google Chrome's + Origin-Private FileSystem (OPFS) as a storage layer to provide + persistent storage for database files in a browser. It requires... + - `sqlite3-opfs-async-proxy.js`\ + is the asynchronous backend part of the OPFS proxy. It speaks + directly to the (async) OPFS API and channels those results back + to its synchronous counterpart. This file, because it must be + started in its own Worker, is not part of the amalgamation. +- **`api/sqlite3-api-cleanup.js`**\ + The previous files do not immediately extend the library. Instead + they add callback functions to be called during its + bootstrapping. Some also temporarily create global objects in order + to communicate their state to the files which follow them. This file + cleans up any dangling globals and runs the API bootstrapping + process, which is what finally executes the initialization code + installed by the previous files. As of this writing, this code + ensures that the previous files leave no more than a single global + symbol installed. When adapting the API for non-Emscripten + toolchains, this "should" be the only file where changes are needed. + +The build process glues those files together, resulting in +`sqlite3-api.js`, which is everything except for the `post-js-*.js` +files, and `sqlite3.js`, which is the Emscripten-generated amalgamated +output and includes the `post-js-*.js` parts, as well as the +Emscripten-provided module loading pieces. + +The non-JS outlier file is `sqlite3-wasm.c`: it is a proxy for +`sqlite3.c` which `#include`'s that file and adds a couple more +WASM-specific helper functions, at least one of which requires access +to private/static `sqlite3.c` internals. `sqlite3.wasm` is compiled +from this file rather than `sqlite3.c`. + +The following files are part of the build process but are injected +into the build-generated `sqlite3.js` along with `sqlite3-api.js`. + +- `extern-pre-js.js`\ + Emscripten-specific header for Emscripten's `--extern-pre-js` + flag. As of this writing, that file is only used for experimentation + purposes and holds no code relevant to the production deliverables. +- `pre-js.js`\ + Emscripten-specific header for Emscripten's `--pre-js` flag. This + file is intended as a place to override certain Emscripten behavior + before it starts up, but corner-case Emscripten bugs keep that from + being a reality. +- `post-js-header.js`\ + Emscripten-specific header for the `--post-js` input. It opens up + a lexical scope by starting a post-run handler for Emscripten. +- `post-js-footer.js`\ + Emscripten-specific footer for the `--post-js` input. This closes + off the lexical scope opened by `post-js-header.js`. +- `extern-post-js.js`\ + Emscripten-specific header for Emscripten's `--extern-post-js` + flag. This file overwrites the Emscripten-installed + `sqlite3InitModule()` function with one which, after the module is + loaded, also initializes the asynchronous parts of the sqlite3 + module. For example, the OPFS VFS support. diff --git a/ext/wasm/api/extern-post-js.js b/ext/wasm/api/extern-post-js.js new file mode 100644 index 000000000..84b99b53a --- /dev/null +++ b/ext/wasm/api/extern-post-js.js @@ -0,0 +1,103 @@ +/* extern-post-js.js must be appended to the resulting sqlite3.js + file. It gets its name from being used as the value for the + --extern-post-js=... Emscripten flag. Note that this code, unlike + most of the associated JS code, runs outside of the + Emscripten-generated module init scope, in the current + global scope. */ +(function(){ + /** + In order to hide the sqlite3InitModule()'s resulting Emscripten + module from downstream clients (and simplify our documentation by + being able to elide those details), we rewrite + sqlite3InitModule() to return the sqlite3 object. + + Unfortunately, we cannot modify the module-loader/exporter-based + impls which Emscripten installs at some point in the file above + this. + */ + const originalInit = self.sqlite3InitModule; + if(!originalInit){ + throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build."); + } + /** + We need to add some state which our custom Module.locateFile() + can see, but an Emscripten limitation currently prevents us from + attaching it to the sqlite3InitModule function object: + + https://github.com/emscripten-core/emscripten/issues/18071 + + The only(?) current workaround is to temporarily stash this state + into the global scope and delete it when sqlite3InitModule() + is called. + */ + const initModuleState = self.sqlite3InitModuleState = Object.assign(Object.create(null),{ + moduleScript: self?.document?.currentScript, + isWorker: ('undefined' !== typeof WorkerGlobalScope), + location: self.location, + urlParams: new URL(self.location.href).searchParams + }); + initModuleState.debugModule = + (new URL(self.location.href).searchParams).has('sqlite3.debugModule') + ? (...args)=>console.warn('sqlite3.debugModule:',...args) + : ()=>{}; + + if(initModuleState.urlParams.has('sqlite3.dir')){ + initModuleState.sqlite3Dir = initModuleState.urlParams.get('sqlite3.dir') +'/'; + }else if(initModuleState.moduleScript){ + const li = initModuleState.moduleScript.src.split('/'); + li.pop(); + initModuleState.sqlite3Dir = li.join('/') + '/'; + } + + self.sqlite3InitModule = (...args)=>{ + //console.warn("Using replaced sqlite3InitModule()",self.location); + return originalInit(...args).then((EmscriptenModule)=>{ + if(self.window!==self && + (EmscriptenModule['ENVIRONMENT_IS_PTHREAD'] + || EmscriptenModule['_pthread_self'] + || 'function'===typeof threadAlert + || self.location.pathname.endsWith('.worker.js') + )){ + /** Workaround for wasmfs-generated worker, which calls this + routine from each individual thread and requires that its + argument be returned. All of the criteria above are fragile, + based solely on inspection of the offending code, not public + Emscripten details. */ + return EmscriptenModule; + } + EmscriptenModule.sqlite3.scriptInfo = initModuleState; + //console.warn("sqlite3.scriptInfo =",EmscriptenModule.sqlite3.scriptInfo); + const f = EmscriptenModule.sqlite3.asyncPostInit; + delete EmscriptenModule.sqlite3.asyncPostInit; + return f(); + }).catch((e)=>{ + console.error("Exception loading sqlite3 module:",e); + throw e; + }); + }; + self.sqlite3InitModule.ready = originalInit.ready; + + if(self.sqlite3InitModuleState.moduleScript){ + const sim = self.sqlite3InitModuleState; + let src = sim.moduleScript.src.split('/'); + src.pop(); + sim.scriptDir = src.join('/') + '/'; + } + initModuleState.debugModule('sqlite3InitModuleState =',initModuleState); + if(0){ + console.warn("Replaced sqlite3InitModule()"); + console.warn("self.location.href =",self.location.href); + if('undefined' !== typeof document){ + console.warn("document.currentScript.src =", + document?.currentScript?.src); + } + } + /* Replace the various module exports performed by the Emscripten + glue... */ + if (typeof exports === 'object' && typeof module === 'object') + module.exports = sqlite3InitModule; + else if (typeof exports === 'object') + exports["sqlite3InitModule"] = sqlite3InitModule; + /* AMD modules get injected in a way we cannot override, + so we can't handle those here. */ +})(); diff --git a/ext/wasm/api/extern-pre-js.js b/ext/wasm/api/extern-pre-js.js new file mode 100644 index 000000000..7d47d33e3 --- /dev/null +++ b/ext/wasm/api/extern-pre-js.js @@ -0,0 +1,7 @@ +/* extern-pre-js.js must be prepended to the resulting sqlite3.js + file. This file is currently only used for holding snippets during + test and development. + + It gets its name from being used as the value for the + --extern-pre-js=... Emscripten flag. +*/ diff --git a/ext/wasm/api/post-js-footer.js b/ext/wasm/api/post-js-footer.js new file mode 100644 index 000000000..58882cbd9 --- /dev/null +++ b/ext/wasm/api/post-js-footer.js @@ -0,0 +1,4 @@ +/* The current function scope was opened via post-js-header.js, which + gets prepended to this at build-time. This file closes that + scope. */ +})/*postRun.push(...)*/; diff --git a/ext/wasm/api/post-js-header.js b/ext/wasm/api/post-js-header.js new file mode 100644 index 000000000..82a80e5a1 --- /dev/null +++ b/ext/wasm/api/post-js-header.js @@ -0,0 +1,25 @@ +/** + post-js-header.js is to be prepended to other code to create + post-js.js for use with Emscripten's --post-js flag. This code + requires that it be running in that context. The Emscripten + environment must have been set up already but it will not have + loaded its WASM when the code in this file is run. The function it + installs will be run after the WASM module is loaded, at which + point the sqlite3 JS API bits will get set up. +*/ +if(!Module.postRun) Module.postRun = []; +Module.postRun.push(function(Module/*the Emscripten-style module object*/){ + 'use strict'; + /* This function will contain at least the following: + + - post-js-header.js (this file) + - sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to + - common/whwasmutil.js => Replacements for much of Emscripten's glue + - jaccwaby/jaccwabyt.js => Jaccwabyt (C/JS struct binding) + - sqlite3-api-glue.js => glues previous parts together + - sqlite3-api-oo.js => SQLite3 OO API #1 + - sqlite3-api-worker1.js => Worker-based API + - sqlite3-api-opfs.js => OPFS VFS + - sqlite3-api-cleanup.js => final API cleanup + - post-js-footer.js => closes this postRun() function + */ diff --git a/ext/wasm/api/pre-js.js b/ext/wasm/api/pre-js.js new file mode 100644 index 000000000..f31dea179 --- /dev/null +++ b/ext/wasm/api/pre-js.js @@ -0,0 +1,100 @@ +/** + BEGIN FILE: api/pre-js.js + + This file is intended to be prepended to the sqlite3.js build using + Emscripten's --pre-js=THIS_FILE flag (or equivalent). +*/ + +// See notes in extern-post-js.js +const sqlite3InitModuleState = self.sqlite3InitModuleState || Object.create(null); +delete self.sqlite3InitModuleState; +sqlite3InitModuleState.debugModule('self.location =',self.location); + +/** + This custom locateFile() tries to figure out where to load `path` + from. The intent is to provide a way for foo/bar/X.js loaded from a + Worker constructor or importScripts() to be able to resolve + foo/bar/X.wasm (in the latter case, with some help): + + 1) If URL param named the same as `path` is set, it is returned. + + 2) If sqlite3InitModuleState.sqlite3Dir is set, then (thatName + path) + is returned (note that it's assumed to end with '/'). + + 3) If this code is running in the main UI thread AND it was loaded + from a SCRIPT tag, the directory part of that URL is used + as the prefix. (This form of resolution unfortunately does not + function for scripts loaded via importScripts().) + + 4) If none of the above apply, (prefix+path) is returned. +*/ +Module['locateFile'] = function(path, prefix) { + let theFile; + const up = this.urlParams; + if(up.has(path)){ + theFile = up.get(path); + }else if(this.sqlite3Dir){ + theFile = this.sqlite3Dir + path; + }else if(this.scriptDir){ + theFile = this.scriptDir + path; + }else{ + theFile = prefix + path; + } + sqlite3InitModuleState.debugModule( + "locateFile(",arguments[0], ',', arguments[1],")", + 'sqlite3InitModuleState.scriptDir =',this.scriptDir, + 'up.entries() =',Array.from(up.entries()), + "result =", theFile + ); + return theFile; +}.bind(sqlite3InitModuleState); + +/** + Bug warning: a custom Module.instantiateWasm() does not work + in WASMFS builds: + + https://github.com/emscripten-core/emscripten/issues/17951 + + In such builds we must disable this. +*/ +const xNameOfInstantiateWasm = true + ? 'instantiateWasm' + : 'emscripten-bug-17951'; +Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){ + imports.env.foo = function(){}; + const uri = Module.locateFile( + callee.uri, ( + ('undefined'===typeof scriptDirectory/*var defined by Emscripten glue*/) + ? '' : scriptDirectory) + ); + sqlite3InitModuleState.debugModule( + "instantiateWasm() uri =", uri + ); + const wfetch = ()=>fetch(uri, {credentials: 'same-origin'}); + const loadWasm = WebAssembly.instantiateStreaming + ? async ()=>{ + return WebAssembly.instantiateStreaming(wfetch(), imports) + .then((arg)=>onSuccess(arg.instance, arg.module)); + } + : async ()=>{ // Safari < v15 + return wfetch() + .then(response => response.arrayBuffer()) + .then(bytes => WebAssembly.instantiate(bytes, imports)) + .then((arg)=>onSuccess(arg.instance, arg.module)); + }; + loadWasm(); + return {}; +}; +/* + It is literally impossible to reliably get the name of _this_ script + at runtime, so impossible to derive X.wasm from script name + X.js. Thus we need, at build-time, to redefine + Module[xNameOfInstantiateWasm].uri by appending it to a build-specific + copy of this file with the name of the wasm file. This is apparently + why Emscripten hard-codes the name of the wasm file into their glue + scripts. +*/ +Module[xNameOfInstantiateWasm].uri = 'sqlite3.wasm'; +/* END FILE: api/pre-js.js, noting that the build process may add a + line after this one to change the above .uri to a build-specific + one. */ diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js new file mode 100644 index 000000000..bef4d91d7 --- /dev/null +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -0,0 +1,70 @@ +/* + 2022-07-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 is the tail end of the sqlite3-api.js constellation, + intended to be appended after all other sqlite3-api-*.js files so + that it can finalize any setup and clean up any global symbols + temporarily used for setting up the API's various subsystems. +*/ +'use strict'; +if('undefined' !== typeof Module){ // presumably an Emscripten build + /** + Install a suitable default configuration for sqlite3ApiBootstrap(). + */ + const SABC = Object.assign( + Object.create(null), { + Module: Module /* ==> Currently needs to be exposed here for + test code. NOT part of the public API. */, + exports: Module['asm'], + memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */ + }, + self.sqlite3ApiConfig || Object.create(null) + ); + + /** + For current (2022-08-22) purposes, automatically call + sqlite3ApiBootstrap(). That decision will be revisited at some + point, as we really want client code to be able to call this to + configure certain parts. Clients may modify + self.sqlite3ApiBootstrap.defaultConfig to tweak the default + configuration used by a no-args call to sqlite3ApiBootstrap(), + but must have first loaded their WASM module in order to be + able to provide the necessary configuration state. + */ + //console.warn("self.sqlite3ApiConfig = ",self.sqlite3ApiConfig); + self.sqlite3ApiConfig = SABC; + let sqlite3; + try{ + sqlite3 = self.sqlite3ApiBootstrap(); + }catch(e){ + console.error("sqlite3ApiBootstrap() error:",e); + throw e; + }finally{ + delete self.sqlite3ApiBootstrap; + delete self.sqlite3ApiConfig; + } + + if(self.location && +self.location.port > 1024){ + console.warn("Installing sqlite3 bits as global S for local dev/test purposes."); + self.S = sqlite3; + } + + /* Clean up temporary references to our APIs... */ + delete sqlite3.util /* arguable, but these are (currently) internal-use APIs */; + Module.sqlite3 = sqlite3 /* Needed for customized sqlite3InitModule() to be able to + pass the sqlite3 object off to the client. */; +}else{ + console.warn("This is not running in an Emscripten module context, so", + "self.sqlite3ApiBootstrap() is _not_ being called due to lack", + "of config info for the WASM environment.", + "It must be called manually."); +} diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js new file mode 100644 index 000000000..86aa1d181 --- /dev/null +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -0,0 +1,720 @@ +/* + 2022-07-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 glues together disparate pieces of JS which are loaded in + previous steps of the sqlite3-api.js bootstrapping process: + sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It + initializes the main API pieces so that the downstream components + (e.g. sqlite3-api-oo1.js) have all that they need. +*/ +self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + 'use strict'; + const toss = (...args)=>{throw new Error(args.join(' '))}; + const toss3 = sqlite3.SQLite3Error.toss; + const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; + self.WhWasmUtilInstaller(wasm); + delete self.WhWasmUtilInstaller; + + /** + Install JS<->C struct bindings for the non-opaque struct types we + need... */ + sqlite3.StructBinder = self.Jaccwabyt({ + heap: 0 ? wasm.memory : wasm.heap8u, + alloc: wasm.alloc, + dealloc: wasm.dealloc, + functionTable: wasm.functionTable, + bigIntEnabled: wasm.bigIntEnabled, + memberPrefix: '$' + }); + delete self.Jaccwabyt; + + if(0){ + /* "The problem" is that the following isn't even remotely + type-safe. OTOH, nothing about WASM pointers is. */ + const argPointer = wasm.xWrap.argAdapter('*'); + wasm.xWrap.argAdapter('StructType', (v)=>{ + if(v && v.constructor && v instanceof StructBinder.StructType){ + v = v.pointer; + } + return wasm.isPtr(v) + ? argPointer(v) + : toss("Invalid (object) type for StructType-type argument."); + }); + } + + {/* Convert Arrays and certain TypedArrays to strings for + 'flexible-string'-type arguments */ + const xString = wasm.xWrap.argAdapter('string'); + wasm.xWrap.argAdapter( + 'flexible-string', (v)=>xString(util.flexibleString(v)) + ); + } + + if(1){// WhWasmUtil.xWrap() bindings... + /** + Add some descriptive xWrap() aliases for '*' intended to (A) + initially improve readability/correctness of capi.signatures + and (B) eventually perhaps provide automatic conversion from + higher-level representations, e.g. capi.sqlite3_vfs to + `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. + */ + const aPtr = wasm.xWrap.argAdapter('*'); + wasm.xWrap.argAdapter('sqlite3*', aPtr) + ('sqlite3_stmt*', aPtr) + ('sqlite3_context*', aPtr) + ('sqlite3_value*', aPtr) + ('sqlite3_vfs*', aPtr) + ('void*', aPtr); + wasm.xWrap.resultAdapter('sqlite3*', aPtr) + ('sqlite3_context*', aPtr) + ('sqlite3_stmt*', aPtr) + ('sqlite3_vfs*', aPtr) + ('void*', aPtr); + + /** + Populate api object with sqlite3_...() by binding the "raw" wasm + exports into type-converting proxies using wasm.xWrap(). + */ + for(const e of wasm.bindingSignatures){ + capi[e[0]] = wasm.xWrap.apply(null, e); + } + for(const e of wasm.bindingSignatures.wasm){ + wasm[e[0]] = wasm.xWrap.apply(null, e); + } + + /* For C API functions which cannot work properly unless + wasm.bigIntEnabled is true, install a bogus impl which + throws if called when bigIntEnabled is false. */ + const fI64Disabled = function(fname){ + return ()=>toss(fname+"() disabled due to lack", + "of BigInt support in this build."); + }; + for(const e of wasm.bindingSignatures.int64){ + capi[e[0]] = wasm.bigIntEnabled + ? wasm.xWrap.apply(null, e) + : fI64Disabled(e[0]); + } + + /* There's no(?) need to expose bindingSignatures to clients, + implicitly making it part of the public interface. */ + delete wasm.bindingSignatures; + + if(wasm.exports.sqlite3_wasm_db_error){ + util.sqlite3_wasm_db_error = wasm.xWrap( + 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' + ); + }else{ + util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ + console.warn("sqlite3_wasm_db_error() is not exported.",arguments); + return errCode; + }; + } + + }/*xWrap() bindings*/; + + /** + When registering a VFS and its related components it may be + necessary to ensure that JS keeps a reference to them to keep + them from getting garbage collected. Simply pass each such value + to this function and a reference will be held to it for the life + of the app. + */ + capi.sqlite3_vfs_register.addReference = function f(...args){ + if(!f._) f._ = []; + f._.push(...args); + }; + + /** + Internal helper to assist in validating call argument counts in + the hand-written sqlite3_xyz() wrappers. We do this only for + consistency with non-special-case wrappings. + */ + const __dbArgcMismatch = (pDb,f,n)=>{ + return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, + f+"() requires "+n+" argument"+ + (1===n?"":'s')+"."); + }; + + /** + Helper for flexible-string conversions which require a + byte-length counterpart argument. Passed a value and its + ostensible length, this function returns [V,N], where V + is either v or a transformed copy of v and N is either n, + -1, or the byte length of v (if it's a byte array). + */ + const __flexiString = function(v,n){ + if('string'===typeof v){ + n = -1; + }else if(util.isSQLableTypedArray(v)){ + n = v.byteLength; + v = util.typedArrayToString(v); + }else if(Array.isArray(v)){ + v = v.join(""); + n = -1; + } + return [v, n]; + }; + + if(1){/* Special-case handling of sqlite3_exec() */ + const __exec = wasm.xWrap("sqlite3_exec", "int", + ["sqlite3*", "flexible-string", "*", "*", "**"]); + /* Documented in the api object's initializer. */ + capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ + if(f.length!==arguments.length){ + return __dbArgcMismatch(pDb,"sqlite3_exec",f.length); + }else if('function' !== typeof callback){ + return __exec(pDb, sql, callback, pVoid, pErrMsg); + } + /* Wrap the callback in a WASM-bound function and convert the callback's + `(char**)` arguments to arrays of strings... */ + const cbwrap = function(pVoid, nCols, pColVals, pColNames){ + let rc = capi.SQLITE_ERROR; + try { + let aVals = [], aNames = [], i = 0, offset = 0; + for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ + aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); + aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); + } + rc = callback(pVoid, nCols, aVals, aNames) | 0; + /* The first 2 args of the callback are useless for JS but + we want the JS mapping of the C API to be as close to the + C API as possible. */ + }catch(e){ + /* If we set the db error state here, the higher-level exec() call + replaces it with its own, so we have no way of reporting the + exception message except the console. We must not propagate + exceptions through the C API. */ + } + return rc; + }; + let pFunc, rc; + try{ + pFunc = wasm.installFunction("ipipp", cbwrap); + rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg); + }catch(e){ + rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, + "Error running exec(): "+e.message); + }finally{ + if(pFunc) wasm.uninstallFunction(pFunc); + } + return rc; + }; + }/*sqlite3_exec() proxy*/; + + if(1){/* Special-case handling of sqlite3_create_function_v2() + and sqlite3_create_window_function() */ + const sqlite3CreateFunction = wasm.xWrap( + "sqlite3_create_function_v2", "int", + ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, + "int"/*eTextRep*/, "*"/*pApp*/, + "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/] + ); + const sqlite3CreateWindowFunction = wasm.xWrap( + "sqlite3_create_window_function", "int", + ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, + "int"/*eTextRep*/, "*"/*pApp*/, + "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, + "*"/*xInverse*/, "*"/*xDestroy*/] + ); + + const __udfSetResult = function(pCtx, val){ + //console.warn("udfSetResult",typeof val, val); + switch(typeof val) { + case 'undefined': + /* Assume that the client already called sqlite3_result_xxx(). */ + break; + case 'boolean': + capi.sqlite3_result_int(pCtx, val ? 1 : 0); + break; + case 'bigint': + if(wasm.bigIntEnabled){ + if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val); + else toss3("BigInt value",val.toString(),"is too BigInt for int64."); + }else if(util.bigIntFits32(val)){ + capi.sqlite3_result_int(pCtx, Number(val)); + }else if(util.bigIntFitsDouble(val)){ + capi.sqlite3_result_double(pCtx, Number(val)); + }else{ + toss3("BigInt value",val.toString(),"is too BigInt."); + } + break; + case 'number': { + (util.isInt32(val) + ? capi.sqlite3_result_int + : capi.sqlite3_result_double)(pCtx, val); + break; + } + case 'string': + capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT); + break; + case 'object': + if(null===val/*yes, typeof null === 'object'*/) { + capi.sqlite3_result_null(pCtx); + break; + }else if(util.isBindableTypedArray(val)){ + const pBlob = wasm.allocFromTypedArray(val); + capi.sqlite3_result_blob( + pCtx, pBlob, val.byteLength, + wasm.exports[sqlite3.config.deallocExportName] + ); + break; + } + // else fall through + default: + toss3("Don't not how to handle this UDF result value:",(typeof val), val); + }; + }/*__udfSetResult()*/; + + const __udfConvertArgs = function(argc, pArgv){ + let i, pVal, valType, arg; + const tgt = []; + for(i = 0; i < argc; ++i){ + pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i)); + /** + Curiously: despite ostensibly requiring 8-byte + alignment, the pArgv array is parcelled into chunks of + 4 bytes (1 pointer each). The values those point to + have 8-byte alignment but the individual argv entries + do not. + */ + valType = capi.sqlite3_value_type(pVal); + switch(valType){ + case capi.SQLITE_INTEGER: + if(wasm.bigIntEnabled){ + arg = capi.sqlite3_value_int64(pVal); + if(util.bigIntFitsDouble(arg)) arg = Number(arg); + } + else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/; + break; + case capi.SQLITE_FLOAT: + arg = capi.sqlite3_value_double(pVal); + break; + case capi.SQLITE_TEXT: + arg = capi.sqlite3_value_text(pVal); + break; + case capi.SQLITE_BLOB:{ + const n = capi.sqlite3_value_bytes(pVal); + const pBlob = capi.sqlite3_value_blob(pVal); + if(n && !pBlob) sqlite3.WasmAllocError.toss( + "Cannot allocate memory for blob argument of",n,"byte(s)" + ); + arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null; + break; + } + case capi.SQLITE_NULL: + arg = null; break; + default: + toss3("Unhandled sqlite3_value_type()",valType, + "is possibly indicative of incorrect", + "pointer size assumption."); + } + tgt.push(arg); + } + return tgt; + }/*__udfConvertArgs()*/; + + const __udfSetError = (pCtx, e)=>{ + if(e instanceof sqlite3.WasmAllocError){ + capi.sqlite3_result_error_nomem(pCtx); + }else{ + const msg = ('string'===typeof e) ? e : e.message; + capi.sqlite3_result_error(pCtx, msg, -1); + } + }; + + const __xFunc = function(callback){ + return function(pCtx, argc, pArgv){ + try{ __udfSetResult(pCtx, callback(pCtx, ...__udfConvertArgs(argc, pArgv))) } + catch(e){ + //console.error('xFunc() caught:',e); + __udfSetError(pCtx, e); + } + }; + }; + + const __xInverseAndStep = function(callback){ + return function(pCtx, argc, pArgv){ + try{ callback(pCtx, ...__udfConvertArgs(argc, pArgv)) } + catch(e){ __udfSetError(pCtx, e) } + }; + }; + + const __xFinalAndValue = function(callback){ + return function(pCtx){ + try{ __udfSetResult(pCtx, callback(pCtx)) } + catch(e){ __udfSetError(pCtx, e) } + }; + }; + + const __xDestroy = function(callback){ + return function(pVoid){ + try{ callback(pVoid) } + catch(e){ console.error("UDF xDestroy method threw:",e) } + }; + }; + + const __xMap = Object.assign(Object.create(null), { + xFunc: {sig:'v(pip)', f:__xFunc}, + xStep: {sig:'v(pip)', f:__xInverseAndStep}, + xInverse: {sig:'v(pip)', f:__xInverseAndStep}, + xFinal: {sig:'v(p)', f:__xFinalAndValue}, + xValue: {sig:'v(p)', f:__xFinalAndValue}, + xDestroy: {sig:'v(p)', f:__xDestroy} + }); + + const __xWrapFuncs = function(theFuncs, tgtUninst){ + const rc = [] + let k; + for(k in theFuncs){ + let fArg = theFuncs[k]; + if('function'===typeof fArg){ + const w = __xMap[k]; + fArg = wasm.installFunction(w.sig, w.f(fArg)); + tgtUninst.push(fArg); + } + rc.push(fArg); + } + return rc; + }; + + /* Documented in the api object's initializer. */ + capi.sqlite3_create_function_v2 = function f( + pDb, funcName, nArg, eTextRep, pApp, + xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**) + xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) + xFinal, //void (*xFinal)(sqlite3_context*) + xDestroy //void (*xDestroy)(void*) + ){ + if(f.length!==arguments.length){ + return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length); + } + /* Wrap the callbacks in a WASM-bound functions... */ + const uninstall = [/*funcs to uninstall on error*/]; + let rc; + try{ + const funcArgs = __xWrapFuncs({xFunc, xStep, xFinal, xDestroy}, + uninstall); + rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, + pApp, ...funcArgs); + }catch(e){ + console.error("sqlite3_create_function_v2() setup threw:",e); + for(let v of uninstall){ + wasm.uninstallFunction(v); + } + rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, + "Creation of UDF threw: "+e.message); + } + return rc; + }; + + capi.sqlite3_create_function = function f( + pDb, funcName, nArg, eTextRep, pApp, + xFunc, xStep, xFinal + ){ + return (f.length===arguments.length) + ? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep, + pApp, xFunc, xStep, xFinal, 0) + : __dbArgcMismatch(pDb,"sqlite3_create_function",f.length); + }; + + /* Documented in the api object's initializer. */ + capi.sqlite3_create_window_function = function f( + pDb, funcName, nArg, eTextRep, pApp, + xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) + xFinal, //void (*xFinal)(sqlite3_context*) + xValue, //void (*xFinal)(sqlite3_context*) + xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**) + xDestroy //void (*xDestroy)(void*) + ){ + if(f.length!==arguments.length){ + return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length); + } + /* Wrap the callbacks in a WASM-bound functions... */ + const uninstall = [/*funcs to uninstall on error*/]; + let rc; + try{ + const funcArgs = __xWrapFuncs({xStep, xFinal, xValue, xInverse, xDestroy}, + uninstall); + rc = sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, + pApp, ...funcArgs); + }catch(e){ + console.error("sqlite3_create_window_function() setup threw:",e); + for(let v of uninstall){ + wasm.uninstallFunction(v); + } + rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, + "Creation of UDF threw: "+e.message); + } + return rc; + }; + /** + A helper for UDFs implemented in JS and bound to WASM by the + client. Given a JS value, udfSetResult(pCtx,X) calls one of the + sqlite3_result_xyz(pCtx,...) routines, depending on X's data + type: + + - `null`: sqlite3_result_null() + - `boolean`: sqlite3_result_int() + - `number`: sqlite3_result_int() or sqlite3_result_double() + - `string`: sqlite3_result_text() + - Uint8Array or Int8Array: sqlite3_result_blob() + - `undefined`: indicates that the UDF called one of the + `sqlite3_result_xyz()` routines on its own, making this + function a no-op. Results are _undefined_ if this function is + passed the `undefined` value but did _not_ call one of the + `sqlite3_result_xyz()` routines. + + Anything else triggers sqlite3_result_error(). + */ + capi.sqlite3_create_function_v2.udfSetResult = + capi.sqlite3_create_function.udfSetResult = + capi.sqlite3_create_window_function.udfSetResult = __udfSetResult; + + /** + A helper for UDFs implemented in JS and bound to WASM by the + client. When passed the + (argc,argv) values from the UDF-related functions which receive + them (xFunc, xStep, xInverse), it creates a JS array + representing those arguments, converting each to JS in a manner + appropriate to its data type: numeric, text, blob + (Uint8Array), or null. + + Results are undefined if it's passed anything other than those + two arguments from those specific contexts. + + Thus an argc of 4 will result in a length-4 array containing + the converted values from the corresponding argv. + + The conversion will throw only on allocation error or an internal + error. + */ + capi.sqlite3_create_function_v2.udfConvertArgs = + capi.sqlite3_create_function.udfConvertArgs = + capi.sqlite3_create_window_function.udfConvertArgs = __udfConvertArgs; + + /** + A helper for UDFs implemented in JS and bound to WASM by the + client. It expects to be a passed `(sqlite3_context*, Error)` + (an exception object or message string). And it sets the + current UDF's result to sqlite3_result_error_nomem() or + sqlite3_result_error(), depending on whether the 2nd argument + is a sqlite3.WasmAllocError object or not. + */ + capi.sqlite3_create_function_v2.udfSetError = + capi.sqlite3_create_function.udfSetError = + capi.sqlite3_create_window_function.udfSetError = __udfSetError; + + }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; + + if(1){/* Special-case handling of sqlite3_prepare_v2() and + sqlite3_prepare_v3() */ + /** + Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). + */ + const __prepare = Object.create(null); + /** + This binding expects a JS string as its 2nd argument and + null as its final argument. In order to compile multiple + statements from a single string, the "full" impl (see + below) must be used. + */ + __prepare.basic = wasm.xWrap('sqlite3_prepare_v3', + "int", ["sqlite3*", "string", + "int"/*ignored for this impl!*/, + "int", "**", + "**"/*MUST be 0 or null or undefined!*/]); + /** + Impl which requires that the 2nd argument be a pointer + to the SQL string, instead of being converted to a + string. This variant is necessary for cases where we + require a non-NULL value for the final argument + (exec()'ing multiple statements from one input + string). For simpler cases, where only the first + statement in the SQL string is required, the wrapper + named sqlite3_prepare_v2() is sufficient and easier to + use because it doesn't require dealing with pointers. + */ + __prepare.full = wasm.xWrap('sqlite3_prepare_v3', + "int", ["sqlite3*", "*", "int", "int", + "**", "**"]); + + /* Documented in the api object's initializer. */ + capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ + if(f.length!==arguments.length){ + return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); + } + const [xSql, xSqlLen] = __flexiString(sql, sqlLen); + switch(typeof xSql){ + case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); + case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); + default: + return util.sqlite3_wasm_db_error( + pDb, capi.SQLITE_MISUSE, + "Invalid SQL argument type for sqlite3_prepare_v2/v3()." + ); + } + }; + + /* Documented in the api object's initializer. */ + capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ + return (f.length===arguments.length) + ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) + : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length); + }; + }/*sqlite3_prepare_v2/v3()*/; + + {/* Import C-level constants and structs... */ + const cJson = wasm.xCall('sqlite3_wasm_enum_json'); + if(!cJson){ + toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", + "static buffer size!"); + } + wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); + //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); + for(const t of ['access', 'blobFinalizers', 'dataTypes', + 'encodings', 'fcntl', 'flock', 'ioCap', + 'openFlags', 'prepareFlags', 'resultCodes', + 'serialize', 'syncFlags', 'trace', 'udfFlags', + 'version' + ]){ + for(const e of Object.entries(wasm.ctype[t])){ + // ^^^ [k,v] there triggers a buggy code transormation via one + // of the Emscripten-driven optimizers. + capi[e[0]] = e[1]; + } + } + const __rcMap = Object.create(null); + for(const t of ['resultCodes']){ + for(const e of Object.entries(wasm.ctype[t])){ + __rcMap[e[1]] = e[0]; + } + } + /** + For the given integer, returns the SQLITE_xxx result code as a + string, or undefined if no such mapping is found. + */ + capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; + /* Bind all registered C-side structs... */ + const notThese = Object.assign(Object.create(null),{ + // Structs NOT to register + WasmTestStruct: true + }); + if(!util.isUIThread()){ + /* We remove the kvvfs VFS from Worker threads below. */ + notThese.sqlite3_kvvfs_methods = true; + } + for(const s of wasm.ctype.structs){ + if(!notThese[s.name]){ + capi[s.name] = sqlite3.StructBinder(s); + } + } + }/*end C constant imports*/ + + const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); + if( pKvvfs ){/* kvvfs-specific glue */ + if(util.isUIThread()){ + const kvvfsMethods = new capi.sqlite3_kvvfs_methods( + wasm.exports.sqlite3_wasm_kvvfs_methods() + ); + delete capi.sqlite3_kvvfs_methods; + + const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, + pstack = wasm.pstack, + pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc; + + const kvvfsStorage = (zClass)=> + ((115/*=='s'*/===wasm.getMemValue(zClass)) + ? sessionStorage : localStorage); + + /** + Implementations for members of the object referred to by + sqlite3_wasm_kvvfs_methods(). We swap out the native + implementations with these, which use localStorage or + sessionStorage for their backing store. + */ + const kvvfsImpls = { + xRead: (zClass, zKey, zBuf, nBuf)=>{ + const stack = pstack.pointer, + astack = wasm.scopedAllocPush(); + try { + const zXKey = kvvfsMakeKey(zClass,zKey); + if(!zXKey) return -3/*OOM*/; + const jKey = wasm.cstringToJs(zXKey); + const jV = kvvfsStorage(zClass).getItem(jKey); + if(!jV) return -1; + const nV = jV.length /* Note that we are relying 100% on v being + ASCII so that jV.length is equal to the + C-string's byte length. */; + if(nBuf<=0) return nV; + else if(1===nBuf){ + wasm.setMemValue(zBuf, 0); + return nV; + } + const zV = wasm.scopedAllocCString(jV); + if(nBuf > nV + 1) nBuf = nV + 1; + wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1); + wasm.setMemValue(zBuf + nBuf - 1, 0); + return nBuf - 1; + }catch(e){ + console.error("kvstorageRead()",e); + return -2; + }finally{ + pstack.restore(stack); + wasm.scopedAllocPop(astack); + } + }, + xWrite: (zClass, zKey, zData)=>{ + const stack = pstack.pointer; + try { + const zXKey = kvvfsMakeKey(zClass,zKey); + if(!zXKey) return 1/*OOM*/; + const jKey = wasm.cstringToJs(zXKey); + kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData)); + return 0; + }catch(e){ + console.error("kvstorageWrite()",e); + return capi.SQLITE_IOERR; + }finally{ + pstack.restore(stack); + } + }, + xDelete: (zClass, zKey)=>{ + const stack = pstack.pointer; + try { + const zXKey = kvvfsMakeKey(zClass,zKey); + if(!zXKey) return 1/*OOM*/; + kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey)); + return 0; + }catch(e){ + console.error("kvstorageDelete()",e); + return capi.SQLITE_IOERR; + }finally{ + pstack.restore(stack); + } + } + }/*kvvfsImpls*/; + for(const k of Object.keys(kvvfsImpls)){ + kvvfsMethods[kvvfsMethods.memberKey(k)] = + wasm.installFunction( + kvvfsMethods.memberSignature(k), + kvvfsImpls[k] + ); + } + }else{ + /* Worker thread: unregister kvvfs to avoid it being used + for anything other than local/sessionStorage. It "can" + be used that way but it's not really intended to be. */ + capi.sqlite3_vfs_unregister(pKvvfs); + } + }/*pKvvfs*/ + +}); diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js new file mode 100644 index 000000000..02ce9c0ce --- /dev/null +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -0,0 +1,1800 @@ +/* + 2022-07-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 the so-called OO #1 API wrapper for the sqlite3 + WASM build. It requires that sqlite3-api-glue.js has already run + and it installs its deliverable as self.sqlite3.oo1. +*/ +self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + const toss = (...args)=>{throw new Error(args.join(' '))}; + const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)}; + + const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; + /* What follows is colloquially known as "OO API #1". It is a + binding of the sqlite3 API which is designed to be run within + the same thread (main or worker) as the one in which the + sqlite3 WASM binding was initialized. This wrapper cannot use + the sqlite3 binding if, e.g., the wrapper is in the main thread + and the sqlite3 API is in a worker. */ + + /** + In order to keep clients from manipulating, perhaps + inadvertently, the underlying pointer values of DB and Stmt + instances, we'll gate access to them via the `pointer` property + accessor and store their real values in this map. Keys = DB/Stmt + objects, values = pointer values. This also unifies how those are + accessed, for potential use downstream via custom + wasm.xWrap() function signatures which know how to extract + it. + */ + const __ptrMap = new WeakMap(); + /** + Map of DB instances to objects, each object being a map of Stmt + wasm pointers to Stmt objects. + */ + const __stmtMap = new WeakMap(); + + /** If object opts has _its own_ property named p then that + property's value is returned, else dflt is returned. */ + const getOwnOption = (opts, p, dflt)=>{ + const d = Object.getOwnPropertyDescriptor(opts,p); + return d ? d.value : dflt; + }; + + // Documented in DB.checkRc() + const checkSqlite3Rc = function(dbPtr, sqliteResultCode){ + if(sqliteResultCode){ + if(dbPtr instanceof DB) dbPtr = dbPtr.pointer; + toss3( + "sqlite result code",sqliteResultCode+":", + (dbPtr + ? capi.sqlite3_errmsg(dbPtr) + : capi.sqlite3_errstr(sqliteResultCode)) + ); + } + }; + + /** + sqlite3_trace_v2() callback which gets installed by the DB ctor + if its open-flags contain "t". + */ + const __dbTraceToConsole = + wasm.installFunction('i(ippp)', function(t,c,p,x){ + if(capi.SQLITE_TRACE_STMT===t){ + // x == SQL, p == sqlite3_stmt* + console.log("SQL TRACE #"+(++this.counter), + wasm.cstringToJs(x)); + } + }.bind({counter: 0})); + + /** + A map of sqlite3_vfs pointers to SQL code to run when the DB + constructor opens a database with the given VFS. + */ + const __vfsPostOpenSql = Object.create(null); + + /** + A proxy for DB class constructors. It must be called with the + being-construct DB object as its "this". See the DB constructor + for the argument docs. This is split into a separate function + in order to enable simple creation of special-case DB constructors, + e.g. JsStorageDb and OpfsDb. + + Expects to be passed a configuration object with the following + properties: + + - `.filename`: the db filename. It may be a special name like ":memory:" + or "". + + - `.flags`: as documented in the DB constructor. + + - `.vfs`: as documented in the DB constructor. + + It also accepts those as the first 3 arguments. + */ + const dbCtorHelper = function ctor(...args){ + if(!ctor._name2vfs){ + /** + Map special filenames which we handle here (instead of in C) + to some helpful metadata... + + As of 2022-09-20, the C API supports the names :localStorage: + and :sessionStorage: for kvvfs. However, C code cannot + determine (without embedded JS code, e.g. via Emscripten's + EM_JS()) whether the kvvfs is legal in the current browser + context (namely the main UI thread). In order to help client + code fail early on, instead of it being delayed until they + try to read or write a kvvfs-backed db, we'll check for those + names here and throw if they're not legal in the current + context. + */ + ctor._name2vfs = Object.create(null); + const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/) + ? (n)=>toss3("The VFS for",n,"is only available in the main window thread.") + : false; + ctor._name2vfs[':localStorage:'] = { + vfs: 'kvvfs', filename: isWorkerThread || (()=>'local') + }; + ctor._name2vfs[':sessionStorage:'] = { + vfs: 'kvvfs', filename: isWorkerThread || (()=>'session') + }; + } + const opt = ctor.normalizeArgs(...args); + let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags; + if(('string'!==typeof fn && 'number'!==typeof fn) + || 'string'!==typeof flagsStr + || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){ + console.error("Invalid DB ctor args",opt,arguments); + toss3("Invalid arguments for DB constructor."); + } + let fnJs = ('number'===typeof fn) ? wasm.cstringToJs(fn) : fn; + const vfsCheck = ctor._name2vfs[fnJs]; + if(vfsCheck){ + vfsName = vfsCheck.vfs; + fn = fnJs = vfsCheck.filename(fnJs); + } + let pDb, oflags = 0; + if( flagsStr.indexOf('c')>=0 ){ + oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; + } + if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE; + if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY; + oflags |= capi.SQLITE_OPEN_EXRESCODE; + const stack = wasm.pstack.pointer; + try { + const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */; + let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0); + pDb = wasm.getPtrValue(pPtr); + checkSqlite3Rc(pDb, rc); + if(flagsStr.indexOf('t')>=0){ + capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT, + __dbTraceToConsole, 0); + } + // Check for per-VFS post-open SQL... + const pVfs = capi.sqlite3_js_db_vfs(pDb); + //console.warn("Opened db",fn,"with vfs",vfsName,pVfs); + if(!pVfs) toss3("Internal error: cannot get VFS for new db handle."); + const postInitSql = __vfsPostOpenSql[pVfs]; + if(postInitSql){ + rc = capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0); + checkSqlite3Rc(pDb, rc); + } + }catch( e ){ + if( pDb ) capi.sqlite3_close_v2(pDb); + throw e; + }finally{ + wasm.pstack.restore(stack); + } + this.filename = fnJs; + __ptrMap.set(this, pDb); + __stmtMap.set(this, Object.create(null)); + }; + + /** + Sets SQL which should be exec()'d on a DB instance after it is + opened with the given VFS pointer. This is intended only for use + by DB subclasses or sqlite3_vfs implementations. + */ + dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){ + __vfsPostOpenSql[pVfs] = sql; + }; + + /** + A helper for DB constructors. It accepts either a single + config-style object or up to 3 arguments (filename, dbOpenFlags, + dbVfsName). It returns a new object containing: + + { filename: ..., flags: ..., vfs: ... } + + If passed an object, any additional properties it has are copied + as-is into the new object. + */ + dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){ + const arg = {}; + if(1===arguments.length && 'object'===typeof arguments[0]){ + const x = arguments[0]; + Object.keys(x).forEach((k)=>arg[k] = x[k]); + if(undefined===arg.flags) arg.flags = 'c'; + if(undefined===arg.vfs) arg.vfs = null; + if(undefined===arg.filename) arg.filename = ':memory:'; + }else{ + arg.filename = filename; + arg.flags = flags; + arg.vfs = vfs; + } + return arg; + }; + /** + The DB class provides a high-level OO wrapper around an sqlite3 + db handle. + + The given db filename must be resolvable using whatever + filesystem layer (virtual or otherwise) is set up for the default + sqlite3 VFS. + + Note that the special sqlite3 db names ":memory:" and "" + (temporary db) have their normal special meanings here and need + not resolve to real filenames, but "" uses an on-storage + temporary database and requires that the VFS support that. + + The second argument specifies the open/create mode for the + database. It must be string containing a sequence of letters (in + any order, but case sensitive) specifying the mode: + + - "c": create if it does not exist, else fail if it does not + exist. Implies the "w" flag. + + - "w": write. Implies "r": a db cannot be write-only. + + - "r": read-only if neither "w" nor "c" are provided, else it + is ignored. + + - "t": enable tracing of SQL executed on this database handle, + sending it to `console.log()`. To disable it later, call + `sqlite3.capi.sqlite3_trace_v2(thisDb.pointer, 0, 0, 0)`. + + If "w" is not provided, the db is implicitly read-only, noting + that "rc" is meaningless + + Any other letters are currently ignored. The default is + "c". These modes are ignored for the special ":memory:" and "" + names and _may_ be ignored altogether for certain VFSes. + + The final argument is analogous to the final argument of + sqlite3_open_v2(): the name of an sqlite3 VFS. Pass a falsy value, + or none at all, to use the default. If passed a value, it must + be the string name of a VFS. + + The constructor optionally (and preferably) takes its arguments + in the form of a single configuration object with the following + properties: + + - `filename`: database file name + - `flags`: open-mode flags + - `vfs`: the VFS fname + + The `filename` and `vfs` arguments may be either JS strings or + C-strings allocated via WASM. `flags` is required to be a JS + string (because it's specific to this API, which is specific + to JS). + + For purposes of passing a DB instance to C-style sqlite3 + functions, the DB object's read-only `pointer` property holds its + `sqlite3*` pointer value. That property can also be used to check + whether this DB instance is still open. + + In the main window thread, the filenames `":localStorage:"` and + `":sessionStorage:"` are special: they cause the db to use either + localStorage or sessionStorage for storing the database using + the kvvfs. If one of these names are used, they trump + any vfs name set in the arguments. + */ + const DB = function(...args){ + dbCtorHelper.apply(this, args); + }; + DB.dbCtorHelper = dbCtorHelper; + + /** + Internal-use enum for mapping JS types to DB-bindable types. + These do not (and need not) line up with the SQLITE_type + values. All values in this enum must be truthy and distinct + but they need not be numbers. + */ + const BindTypes = { + null: 1, + number: 2, + string: 3, + boolean: 4, + blob: 5 + }; + BindTypes['undefined'] == BindTypes.null; + if(wasm.bigIntEnabled){ + BindTypes.bigint = BindTypes.number; + } + + /** + This class wraps sqlite3_stmt. Calling this constructor + directly will trigger an exception. Use DB.prepare() to create + new instances. + + For purposes of passing a Stmt instance to C-style sqlite3 + functions, its read-only `pointer` property holds its `sqlite3_stmt*` + pointer value. + + Other non-function properties include: + + - `db`: the DB object which created the statement. + + - `columnCount`: the number of result columns in the query, or 0 for + queries which cannot return results. + + - `parameterCount`: the number of bindable paramters in the query. + */ + const Stmt = function(){ + if(BindTypes!==arguments[2]){ + toss3("Do not call the Stmt constructor directly. Use DB.prepare()."); + } + this.db = arguments[0]; + __ptrMap.set(this, arguments[1]); + this.columnCount = capi.sqlite3_column_count(this.pointer); + this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer); + }; + + /** Throws if the given DB has been closed, else it is returned. */ + const affirmDbOpen = function(db){ + if(!db.pointer) toss3("DB has been closed."); + return db; + }; + + /** Throws if ndx is not an integer or if it is out of range + for stmt.columnCount, else returns stmt. + + Reminder: this will also fail after the statement is finalized + but the resulting error will be about an out-of-bounds column + index rather than a statement-is-finalized error. + */ + const affirmColIndex = function(stmt,ndx){ + if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){ + toss3("Column index",ndx,"is out of range."); + } + return stmt; + }; + + /** + Expects to be passed the `arguments` object from DB.exec(). Does + the argument processing/validation, throws on error, and returns + a new object on success: + + { sql: the SQL, opt: optionsObj, cbArg: function} + + The opt object is a normalized copy of any passed to this + function. The sql will be converted to a string if it is provided + in one of the supported non-string formats. + + cbArg is only set if the opt.callback or opt.resultRows are set, + in which case it's a function which expects to be passed the + current Stmt and returns the callback argument of the type + indicated by the input arguments. + */ + const parseExecArgs = function(db, args){ + const out = Object.create(null); + out.opt = Object.create(null); + switch(args.length){ + case 1: + if('string'===typeof args[0] || util.isSQLableTypedArray(args[0])){ + out.sql = args[0]; + }else if(Array.isArray(args[0])){ + out.sql = args[0]; + }else if(args[0] && 'object'===typeof args[0]){ + out.opt = args[0]; + out.sql = out.opt.sql; + } + break; + case 2: + out.sql = args[0]; + out.opt = args[1]; + break; + default: toss3("Invalid argument count for exec()."); + }; + out.sql = util.flexibleString(out.sql); + if('string'!==typeof out.sql){ + toss3("Missing SQL argument or unsupported SQL value type."); + } + const opt = out.opt; + switch(opt.returnValue){ + case 'resultRows': + if(!opt.resultRows) opt.resultRows = []; + out.returnVal = ()=>opt.resultRows; + break; + case 'saveSql': + if(!opt.saveSql) opt.saveSql = []; + out.returnVal = ()=>opt.saveSql; + break; + case undefined: + case 'this': + out.returnVal = ()=>db; + break; + default: + toss3("Invalid returnValue value:",opt.returnValue); + } + if(opt.callback || opt.resultRows){ + switch((undefined===opt.rowMode) + ? 'array' : opt.rowMode) { + case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break; + case 'array': out.cbArg = (stmt)=>stmt.get([]); break; + case 'stmt': + if(Array.isArray(opt.resultRows)){ + toss3("exec(): invalid rowMode for a resultRows array: must", + "be one of 'array', 'object',", + "a result column number, or column name reference."); + } + out.cbArg = (stmt)=>stmt; + break; + default: + if(util.isInt32(opt.rowMode)){ + out.cbArg = (stmt)=>stmt.get(opt.rowMode); + break; + }else if('string'===typeof opt.rowMode && opt.rowMode.length>1){ + /* "$X", ":X", and "@X" fetch column named "X" (case-sensitive!) */ + const prefix = opt.rowMode[0]; + if(':'===prefix || '@'===prefix || '$'===prefix){ + out.cbArg = function(stmt){ + const rc = stmt.get(this.obj)[this.colName]; + return (undefined===rc) ? toss3("exec(): unknown result column:",this.colName) : rc; + }.bind({ + obj:Object.create(null), + colName: opt.rowMode.substr(1) + }); + break; + } + } + toss3("Invalid rowMode:",opt.rowMode); + } + } + return out; + }; + + /** + Internal impl of the DB.selectArray() and + selectObject() methods. + */ + const __selectFirstRow = (db, sql, bind, getArg)=>{ + let stmt, rc; + try { + stmt = db.prepare(sql).bind(bind); + if(stmt.step()) rc = stmt.get(getArg); + }finally{ + if(stmt) stmt.finalize(); + } + return rc; + }; + + /** + Expects to be given a DB instance or an `sqlite3*` pointer (may + be null) and an sqlite3 API result code. If the result code is + not falsy, this function throws an SQLite3Error with an error + message from sqlite3_errmsg(), using dbPtr as the db handle, or + sqlite3_errstr() if dbPtr is falsy. Note that if it's passed a + non-error code like SQLITE_ROW or SQLITE_DONE, it will still + throw but the error string might be "Not an error." The various + non-0 non-error codes need to be checked for in + client code where they are expected. + */ + DB.checkRc = checkSqlite3Rc; + + DB.prototype = { + /** Returns true if this db handle is open, else false. */ + isOpen: function(){ + return !!this.pointer; + }, + /** Throws if this given DB has been closed, else returns `this`. */ + affirmOpen: function(){ + return affirmDbOpen(this); + }, + /** + Finalizes all open statements and closes this database + connection. This is a no-op if the db has already been + closed. After calling close(), `this.pointer` will resolve to + `undefined`, so that can be used to check whether the db + instance is still opened. + + If this.onclose.before is a function then it is called before + any close-related cleanup. + + If this.onclose.after is a function then it is called after the + db is closed but before auxiliary state like this.filename is + cleared. + + Both onclose handlers are passed this object. If this db is not + opened, neither of the handlers are called. Any exceptions the + handlers throw are ignored because "destructors must not + throw." + + Note that garbage collection of a db handle, if it happens at + all, will never trigger close(), so onclose handlers are not a + reliable way to implement close-time cleanup or maintenance of + a db. + */ + close: function(){ + if(this.pointer){ + if(this.onclose && (this.onclose.before instanceof Function)){ + try{this.onclose.before(this)} + catch(e){/*ignore*/} + } + const pDb = this.pointer; + Object.keys(__stmtMap.get(this)).forEach((k,s)=>{ + if(s && s.pointer) s.finalize(); + }); + __ptrMap.delete(this); + __stmtMap.delete(this); + capi.sqlite3_close_v2(pDb); + if(this.onclose && (this.onclose.after instanceof Function)){ + try{this.onclose.after(this)} + catch(e){/*ignore*/} + } + delete this.filename; + } + }, + /** + Returns the number of changes, as per sqlite3_changes() + (if the first argument is false) or sqlite3_total_changes() + (if it's true). If the 2nd argument is true, it uses + sqlite3_changes64() or sqlite3_total_changes64(), which + will trigger an exception if this build does not have + BigInt support enabled. + */ + changes: function(total=false,sixtyFour=false){ + const p = affirmDbOpen(this).pointer; + if(total){ + return sixtyFour + ? capi.sqlite3_total_changes64(p) + : capi.sqlite3_total_changes(p); + }else{ + return sixtyFour + ? capi.sqlite3_changes64(p) + : capi.sqlite3_changes(p); + } + }, + /** + Similar to the this.filename but returns the + sqlite3_db_filename() value for the given database name, + defaulting to "main". The argument may be either a JS string + or a pointer to a WASM-allocated C-string. + */ + dbFilename: function(dbName='main'){ + return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName); + }, + /** + Returns the name of the given 0-based db number, as documented + for sqlite3_db_name(). + */ + dbName: function(dbNumber=0){ + return capi.sqlite3_db_name(affirmDbOpen(this).pointer, dbNumber); + }, + /** + Returns the name of the sqlite3_vfs used by the given database + of this connection (defaulting to 'main'). The argument may be + either a JS string or a WASM C-string. Returns undefined if the + given db name is invalid. Throws if this object has been + close()d. + */ + dbVfsName: function(dbName=0){ + let rc; + const pVfs = capi.sqlite3_js_db_vfs( + affirmDbOpen(this).pointer, dbName + ); + if(pVfs){ + const v = new capi.sqlite3_vfs(pVfs); + try{ rc = wasm.cstringToJs(v.$zName) } + finally { v.dispose() } + } + return rc; + }, + /** + Compiles the given SQL and returns a prepared Stmt. This is + the only way to create new Stmt objects. Throws on error. + + The given SQL must be a string, a Uint8Array holding SQL, a + WASM pointer to memory holding the NUL-terminated SQL string, + or an array of strings. In the latter case, the array is + concatenated together, with no separators, to form the SQL + string (arrays are often a convenient way to formulate long + statements). If the SQL contains no statements, an + SQLite3Error is thrown. + + Design note: the C API permits empty SQL, reporting it as a 0 + result code and a NULL stmt pointer. Supporting that case here + would cause extra work for all clients: any use of the Stmt API + on such a statement will necessarily throw, so clients would be + required to check `stmt.pointer` after calling `prepare()` in + order to determine whether the Stmt instance is empty or not. + Long-time practice (with other sqlite3 script bindings) + suggests that the empty-prepare case is sufficiently rare that + supporting it here would simply hurt overall usability. + */ + prepare: function(sql){ + affirmDbOpen(this); + const stack = wasm.pstack.pointer; + let ppStmt, pStmt; + try{ + ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */; + DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null)); + pStmt = wasm.getPtrValue(ppStmt); + } + finally { + wasm.pstack.restore(stack); + } + if(!pStmt) toss3("Cannot prepare empty SQL."); + const stmt = new Stmt(this, pStmt, BindTypes); + __stmtMap.get(this)[pStmt] = stmt; + return stmt; + }, + /** + Executes one or more SQL statements in the form of a single + string. Its arguments must be either (sql,optionsObject) or + (optionsObject). In the latter case, optionsObject.sql must + contain the SQL to execute. By default it returns this object + but that can be changed via the `returnValue` option as + described below. Throws on error. + + If no SQL is provided, or a non-string is provided, an + exception is triggered. Empty SQL, on the other hand, is + simply a no-op. + + The optional options object may contain any of the following + properties: + + - `sql` = the SQL to run (unless it's provided as the first + argument). This must be of type string, Uint8Array, or an array + of strings. In the latter case they're concatenated together + as-is, _with no separator_ between elements, before evaluation. + The array form is often simpler for long hand-written queries. + + - `bind` = a single value valid as an argument for + Stmt.bind(). This is _only_ applied to the _first_ non-empty + statement in the SQL which has any bindable parameters. (Empty + statements are skipped entirely.) + + - `saveSql` = an optional array. If set, the SQL of each + executed statement is appended to this array before the + statement is executed (but after it is prepared - we don't have + the string until after that). Empty SQL statements are elided + but can have odd effects in the output. e.g. SQL of: `"select + 1; -- empty\n; select 2"` will result in an array containing + `["select 1;", "--empty \n; select 2"]`. That's simply how + sqlite3 records the SQL for the 2nd statement. + + ================================================================== + The following options apply _only_ to the _first_ statement + which has a non-zero result column count, regardless of whether + the statement actually produces any result rows. + ================================================================== + + - `columnNames`: if this is an array, the column names of the + result set are stored in this array before the callback (if + any) is triggered (regardless of whether the query produces any + result rows). If no statement has result columns, this value is + unchanged. Achtung: an SQL result may have multiple columns + with identical names. + + - `callback` = a function which gets called for each row of + the result set, but only if that statement has any result + _rows_. The callback's "this" is the options object, noting + that this function synthesizes one if the caller does not pass + one to exec(). The second argument passed to the callback is + always the current Stmt object, as it's needed if the caller + wants to fetch the column names or some such (noting that they + could also be fetched via `this.columnNames`, if the client + provides the `columnNames` option). + + ACHTUNG: The callback MUST NOT modify the Stmt object. Calling + any of the Stmt.get() variants, Stmt.getColumnName(), or + similar, is legal, but calling step() or finalize() is + not. Member methods which are illegal in this context will + trigger an exception. + + The first argument passed to the callback defaults to an array of + values from the current result row but may be changed with ... + + - `rowMode` = specifies the type of he callback's first argument. + It may be any of... + + A) A string describing what type of argument should be passed + as the first argument to the callback: + + A.1) `'array'` (the default) causes the results of + `stmt.get([])` to be passed to the `callback` and/or appended + to `resultRows` + + A.2) `'object'` causes the results of + `stmt.get(Object.create(null))` to be passed to the + `callback` and/or appended to `resultRows`. Achtung: an SQL + result may have multiple columns with identical names. In + that case, the right-most column will be the one set in this + object! + + A.3) `'stmt'` causes the current Stmt to be passed to the + callback, but this mode will trigger an exception if + `resultRows` is an array because appending the statement to + the array would be downright unhelpful. + + B) An integer, indicating a zero-based column in the result + row. Only that one single value will be passed on. + + C) A string with a minimum length of 2 and leading character of + ':', '$', or '@' will fetch the row as an object, extract that + one field, and pass that field's value to the callback. Note + that these keys are case-sensitive so must match the case used + in the SQL. e.g. `"select a A from t"` with a `rowMode` of + `'$A'` would work but `'$a'` would not. A reference to a column + not in the result set will trigger an exception on the first + row (as the check is not performed until rows are fetched). + Note also that `$` is a legal identifier character in JS so + need not be quoted. (Design note: those 3 characters were + chosen because they are the characters support for naming bound + parameters.) + + Any other `rowMode` value triggers an exception. + + - `resultRows`: if this is an array, it functions similarly to + the `callback` option: each row of the result set (if any), + with the exception that the `rowMode` 'stmt' is not legal. It + is legal to use both `resultRows` and `callback`, but + `resultRows` is likely much simpler to use for small data sets + and can be used over a WebWorker-style message interface. + exec() throws if `resultRows` is set and `rowMode` is 'stmt'. + + - `returnValue`: is a string specifying what this function + should return: + + A) The default value is `"this"`, meaning that the + DB object itself should be returned. + + B) `"resultRows"` means to return the value of the + `resultRows` option. If `resultRows` is not set, this + function behaves as if it were set to an empty array. + + C) `"saveSql"` means to return the value of the + `saveSql` option. If `saveSql` is not set, this + function behaves as if it were set to an empty array. + + Potential TODOs: + + - `bind`: permit an array of arrays/objects to bind. The first + sub-array would act on the first statement which has bindable + parameters (as it does now). The 2nd would act on the next such + statement, etc. + + - `callback` and `resultRows`: permit an array entries with + semantics similar to those described for `bind` above. + + */ + exec: function(/*(sql [,obj]) || (obj)*/){ + affirmDbOpen(this); + const arg = parseExecArgs(this, arguments); + if(!arg.sql){ + return toss3("exec() requires an SQL string."); + } + const opt = arg.opt; + const callback = opt.callback; + const resultRows = + Array.isArray(opt.resultRows) ? opt.resultRows : undefined; + let stmt; + let bind = opt.bind; + let evalFirstResult = !!(arg.cbArg || opt.columnNames) /* true to evaluate the first result-returning query */; + const stack = wasm.scopedAllocPush(); + try{ + const isTA = util.isSQLableTypedArray(arg.sql) + /* Optimization: if the SQL is a TypedArray we can save some string + conversion costs. */; + /* Allocate the two output pointers (ppStmt, pzTail) and heap + space for the SQL (pSql). When prepare_v2() returns, pzTail + will point to somewhere in pSql. */ + let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql); + const ppStmt = wasm.scopedAlloc(/* output (sqlite3_stmt**) arg and pzTail */ + (2 * wasm.ptrSizeof) + + (sqlByteLen + 1/* SQL + NUL */)); + const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */; + let pSql = pzTail + wasm.ptrSizeof; + const pSqlEnd = pSql + sqlByteLen; + if(isTA) wasm.heap8().set(arg.sql, pSql); + else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false); + wasm.setMemValue(pSql + sqlByteLen, 0/*NUL terminator*/); + while(pSql && wasm.getMemValue(pSql, 'i8') + /* Maintenance reminder:^^^ _must_ be 'i8' or else we + will very likely cause an endless loop. What that's + doing is checking for a terminating NUL byte. If we + use i32 or similar then we read 4 bytes, read stuff + around the NUL terminator, and get stuck in and + endless loop at the end of the SQL, endlessly + re-preparing an empty statement. */ ){ + wasm.setPtrValue(ppStmt, 0); + wasm.setPtrValue(pzTail, 0); + DB.checkRc(this, capi.sqlite3_prepare_v3( + this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail + )); + const pStmt = wasm.getPtrValue(ppStmt); + pSql = wasm.getPtrValue(pzTail); + sqlByteLen = pSqlEnd - pSql; + if(!pStmt) continue; + if(Array.isArray(opt.saveSql)){ + opt.saveSql.push(capi.sqlite3_sql(pStmt).trim()); + } + stmt = new Stmt(this, pStmt, BindTypes); + if(bind && stmt.parameterCount){ + stmt.bind(bind); + bind = null; + } + if(evalFirstResult && stmt.columnCount){ + /* Only forward SELECT results for the FIRST query + in the SQL which potentially has them. */ + evalFirstResult = false; + if(Array.isArray(opt.columnNames)){ + stmt.getColumnNames(opt.columnNames); + } + while(!!arg.cbArg && stmt.step()){ + stmt._isLocked = true; + const row = arg.cbArg(stmt); + if(resultRows) resultRows.push(row); + if(callback) callback.call(opt, row, stmt); + stmt._isLocked = false; + } + }else{ + stmt.step(); + } + stmt.finalize(); + stmt = null; + } + }/*catch(e){ + console.warn("DB.exec() is propagating exception",opt,e); + throw e; + }*/finally{ + if(stmt){ + delete stmt._isLocked; + stmt.finalize(); + } + wasm.scopedAllocPop(stack); + } + return arg.returnVal(); + }/*exec()*/, + /** + Creates a new scalar UDF (User-Defined Function) which is + accessible via SQL code. This function may be called in any + of the following forms: + + - (name, function) + - (name, function, optionsObject) + - (name, optionsObject) + - (optionsObject) + + In the final two cases, the function must be defined as the + `callback` property of the options object (optionally called + `xFunc` to align with the C API documentation). In the final + case, the function's name must be the 'name' property. + + The first two call forms can only be used for creating scalar + functions. Creating an aggregate or window function requires + the options-object form (see below for details). + + UDFs cannot currently be removed from a DB handle after they're + added. More correctly, they can be removed as documented for + sqlite3_create_function_v2(), but doing so will "leak" the + JS-created WASM binding of those functions. + + On success, returns this object. Throws on error. + + When called from SQL arguments to the UDF, and its result, + will be converted between JS and SQL with as much fidelity as + is feasible, triggering an exception if a type conversion + cannot be determined. The docs for sqlite3_create_function_v2() + describe the conversions in more detail. + + The values set in the options object differ for scalar and + aggregate functions: + + - Scalar: set the `xFunc` function-type property to the UDF + function. + + - Aggregate: set the `xStep` and `xFinal` function-type + properties to the "step" and "final" callbacks for the + aggregate. Do not set the `xFunc` property. + + - Window: set the `xStep`, `xFinal`, `xValue`, and `xInverse` + function-type properties. Do not set the `xFunc` property. + + The options object may optionally have an `xDestroy` + function-type property, as per sqlite3_create_function_v2(). + Its argument will be the WASM-pointer-type value of the `pApp` + property, and this function will throw if `pApp` is defined but + is not null, undefined, or a numeric (WASM pointer) + value. i.e. `pApp`, if set, must be value suitable for use as a + WASM pointer argument, noting that `null` or `undefined` will + translate to 0 for that purpose. + + The options object may contain flags to modify how + the function is defined: + + - `arity`: the number of arguments which SQL calls to this + function expect or require. The default value is `xFunc.length` + or `xStep.length` (i.e. the number of declared parameters it + has) **MINUS 1** (see below for why). As a special case, if the + `length` is 0, its arity is also 0 instead of -1. A negative + arity value means that the function is variadic and may accept + any number of arguments, up to sqlite3's compile-time + limits. sqlite3 will enforce the argument count if is zero or + greater. The callback always receives a pointer to an + `sqlite3_context` object as its first argument. Any arguments + after that are from SQL code. The leading context argument does + _not_ count towards the function's arity. See the docs for + sqlite3.capi.sqlite3_create_function_v2() for why that argument + is needed in the interface. + + The following options-object properties correspond to flags + documented at: + + https://sqlite.org/c3ref/create_function.html + + - `deterministic` = sqlite3.capi.SQLITE_DETERMINISTIC + - `directOnly` = sqlite3.capi.SQLITE_DIRECTONLY + - `innocuous` = sqlite3.capi.SQLITE_INNOCUOUS + + Sidebar: the ability to add new WASM-accessible functions to + the runtime requires that the WASM build is compiled with the + equivalent functionality as that provided by Emscripten's + `-sALLOW_TABLE_GROWTH` flag. + */ + createFunction: function f(name, xFunc, opt){ + const isFunc = (f)=>(f instanceof Function); + switch(arguments.length){ + case 1: /* (optionsObject) */ + opt = name; + name = opt.name; + xFunc = opt.xFunc || 0; + break; + case 2: /* (name, callback|optionsObject) */ + if(!isFunc(xFunc)){ + opt = xFunc; + xFunc = opt.xFunc || 0; + } + break; + case 3: /* name, xFunc, opt */ + break; + default: break; + } + if(!opt) opt = {}; + if('string' !== typeof name){ + toss3("Invalid arguments: missing function name."); + } + let xStep = opt.xStep || 0; + let xFinal = opt.xFinal || 0; + const xValue = opt.xValue || 0; + const xInverse = opt.xInverse || 0; + let isWindow = undefined; + if(isFunc(xFunc)){ + isWindow = false; + if(isFunc(xStep) || isFunc(xFinal)){ + toss3("Ambiguous arguments: scalar or aggregate?"); + } + xStep = xFinal = null; + }else if(isFunc(xStep)){ + if(!isFunc(xFinal)){ + toss3("Missing xFinal() callback for aggregate or window UDF."); + } + xFunc = null; + }else if(isFunc(xFinal)){ + toss3("Missing xStep() callback for aggregate or window UDF."); + }else{ + toss3("Missing function-type properties."); + } + if(false === isWindow){ + if(isFunc(xValue) || isFunc(xInverse)){ + toss3("xValue and xInverse are not permitted for non-window UDFs."); + } + }else if(isFunc(xValue)){ + if(!isFunc(xInverse)){ + toss3("xInverse must be provided if xValue is."); + } + isWindow = true; + }else if(isFunc(xInverse)){ + toss3("xValue must be provided if xInverse is."); + } + const pApp = opt.pApp; + if(undefined!==pApp && + null!==pApp && + (('number'!==typeof pApp) || !util.isInt32(pApp))){ + toss3("Invalid value for pApp property. Must be a legal WASM pointer value."); + } + const xDestroy = opt.xDestroy || 0; + if(xDestroy && !isFunc(xDestroy)){ + toss3("xDestroy property must be a function."); + } + let fFlags = 0 /*flags for sqlite3_create_function_v2()*/; + if(getOwnOption(opt, 'deterministic')) fFlags |= capi.SQLITE_DETERMINISTIC; + if(getOwnOption(opt, 'directOnly')) fFlags |= capi.SQLITE_DIRECTONLY; + if(getOwnOption(opt, 'innocuous')) fFlags |= capi.SQLITE_INNOCUOUS; + name = name.toLowerCase(); + const xArity = xFunc || xStep; + const arity = getOwnOption(opt, 'arity'); + const arityArg = ('number'===typeof arity + ? arity + : (xArity.length ? xArity.length-1/*for pCtx arg*/ : 0)); + let rc; + if( isWindow ){ + rc = capi.sqlite3_create_window_function( + this.pointer, name, arityArg, + capi.SQLITE_UTF8 | fFlags, pApp || 0, + xStep, xFinal, xValue, xInverse, xDestroy); + }else{ + rc = capi.sqlite3_create_function_v2( + this.pointer, name, arityArg, + capi.SQLITE_UTF8 | fFlags, pApp || 0, + xFunc, xStep, xFinal, xDestroy); + } + DB.checkRc(this, rc); + return this; + }/*createFunction()*/, + /** + Prepares the given SQL, step()s it one time, and returns + the value of the first result column. If it has no results, + undefined is returned. + + If passed a second argument, it is treated like an argument + to Stmt.bind(), so may be any type supported by that + function. Passing the undefined value is the same as passing + no value, which is useful when... + + If passed a 3rd argument, it is expected to be one of the + SQLITE_{typename} constants. Passing the undefined value is + the same as not passing a value. + + Throws on error (e.g. malformed SQL). + */ + selectValue: function(sql,bind,asType){ + let stmt, rc; + try { + stmt = this.prepare(sql).bind(bind); + if(stmt.step()) rc = stmt.get(0,asType); + }finally{ + if(stmt) stmt.finalize(); + } + return rc; + }, + /** + Prepares the given SQL, step()s it one time, and returns an + array containing the values of the first result row. If it has + no results, `undefined` is returned. + + If passed a second argument other than `undefined`, it is + treated like an argument to Stmt.bind(), so may be any type + supported by that function. + + Throws on error (e.g. malformed SQL). + */ + selectArray: function(sql,bind){ + return __selectFirstRow(this, sql, bind, []); + }, + + /** + Prepares the given SQL, step()s it one time, and returns an + object containing the key/value pairs of the first result + row. If it has no results, `undefined` is returned. + + Note that the order of returned object's keys is not guaranteed + to be the same as the order of the fields in the query string. + + If passed a second argument other than `undefined`, it is + treated like an argument to Stmt.bind(), so may be any type + supported by that function. + + Throws on error (e.g. malformed SQL). + */ + selectObject: function(sql,bind){ + return __selectFirstRow(this, sql, bind, {}); + }, + + /** + Returns the number of currently-opened Stmt handles for this db + handle, or 0 if this DB instance is closed. + */ + openStatementCount: function(){ + return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0; + }, + + /** + Starts a transaction, calls the given callback, and then either + rolls back or commits the savepoint, depending on whether the + callback throws. The callback is passed this db object as its + only argument. On success, returns the result of the + callback. Throws on error. + + Note that transactions may not be nested, so this will throw if + it is called recursively. For nested transactions, use the + savepoint() method or manually manage SAVEPOINTs using exec(). + */ + transaction: function(callback){ + affirmDbOpen(this).exec("BEGIN"); + try { + const rc = callback(this); + this.exec("COMMIT"); + return rc; + }catch(e){ + this.exec("ROLLBACK"); + throw e; + } + }, + + /** + This works similarly to transaction() but uses sqlite3's SAVEPOINT + feature. This function starts a savepoint (with an unspecified name) + and calls the given callback function, passing it this db object. + If the callback returns, the savepoint is released (committed). If + the callback throws, the savepoint is rolled back. If it does not + throw, it returns the result of the callback. + */ + savepoint: function(callback){ + affirmDbOpen(this).exec("SAVEPOINT oo1"); + try { + const rc = callback(this); + this.exec("RELEASE oo1"); + return rc; + }catch(e){ + this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1"); + throw e; + } + } + }/*DB.prototype*/; + + + /** Throws if the given Stmt has been finalized, else stmt is + returned. */ + const affirmStmtOpen = function(stmt){ + if(!stmt.pointer) toss3("Stmt has been closed."); + return stmt; + }; + + /** Returns an opaque truthy value from the BindTypes + enum if v's type is a valid bindable type, else + returns a falsy value. As a special case, a value of + undefined is treated as a bind type of null. */ + const isSupportedBindType = function(v){ + let t = BindTypes[(null===v||undefined===v) ? 'null' : typeof v]; + switch(t){ + case BindTypes.boolean: + case BindTypes.null: + case BindTypes.number: + case BindTypes.string: + return t; + case BindTypes.bigint: + if(wasm.bigIntEnabled) return t; + /* else fall through */ + default: + //console.log("isSupportedBindType",t,v); + return util.isBindableTypedArray(v) ? BindTypes.blob : undefined; + } + }; + + /** + If isSupportedBindType(v) returns a truthy value, this + function returns that value, else it throws. + */ + const affirmSupportedBindType = function(v){ + //console.log('affirmSupportedBindType',v); + return isSupportedBindType(v) || toss3("Unsupported bind() argument type:",typeof v); + }; + + /** + If key is a number and within range of stmt's bound parameter + count, key is returned. + + If key is not a number then it is checked against named + parameters. If a match is found, its index is returned. + + Else it throws. + */ + const affirmParamIndex = function(stmt,key){ + const n = ('number'===typeof key) + ? key : capi.sqlite3_bind_parameter_index(stmt.pointer, key); + if(0===n || !util.isInt32(n)){ + toss3("Invalid bind() parameter name: "+key); + } + else if(n<1 || n>stmt.parameterCount) toss3("Bind index",key,"is out of range."); + return n; + }; + + /** + If stmt._isLocked is truthy, this throws an exception + complaining that the 2nd argument (an operation name, + e.g. "bind()") is not legal while the statement is "locked". + Locking happens before an exec()-like callback is passed a + statement, to ensure that the callback does not mutate or + finalize the statement. If it does not throw, it returns stmt. + */ + const affirmUnlocked = function(stmt,currentOpName){ + if(stmt._isLocked){ + toss3("Operation is illegal when statement is locked:",currentOpName); + } + return stmt; + }; + + /** + Binds a single bound parameter value on the given stmt at the + given index (numeric or named) using the given bindType (see + the BindTypes enum) and value. Throws on error. Returns stmt on + success. + */ + const bindOne = function f(stmt,ndx,bindType,val){ + affirmUnlocked(stmt, 'bind()'); + if(!f._){ + f._tooBigInt = (v)=>toss3( + "BigInt value is too big to store without precision loss:", v + ); + /* Reminder: when not in BigInt mode, it's impossible for + JS to represent a number out of the range we can bind, + so we have no range checking. */ + f._ = { + string: function(stmt, ndx, val, asBlob){ + if(1){ + /* _Hypothetically_ more efficient than the impl in the 'else' block. */ + const stack = wasm.scopedAllocPush(); + try{ + const n = wasm.jstrlen(val); + const pStr = wasm.scopedAlloc(n); + wasm.jstrcpy(val, wasm.heap8u(), pStr, n, false); + const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text; + return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_TRANSIENT); + }finally{ + wasm.scopedAllocPop(stack); + } + }else{ + const bytes = wasm.jstrToUintArray(val,false); + const pStr = wasm.alloc(bytes.length || 1); + wasm.heap8u().set(bytes.length ? bytes : [0], pStr); + try{ + const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text; + return f(stmt.pointer, ndx, pStr, bytes.length, capi.SQLITE_TRANSIENT); + }finally{ + wasm.dealloc(pStr); + } + } + } + }; + }/* static init */ + affirmSupportedBindType(val); + ndx = affirmParamIndex(stmt,ndx); + let rc = 0; + switch((null===val || undefined===val) ? BindTypes.null : bindType){ + case BindTypes.null: + rc = capi.sqlite3_bind_null(stmt.pointer, ndx); + break; + case BindTypes.string: + rc = f._.string(stmt, ndx, val, false); + break; + case BindTypes.number: { + let m; + if(util.isInt32(val)) m = capi.sqlite3_bind_int; + else if('bigint'===typeof val){ + if(!util.bigIntFits64(val)){ + f._tooBigInt(val); + }else if(wasm.bigIntEnabled){ + m = capi.sqlite3_bind_int64; + }else if(util.bigIntFitsDouble(val)){ + val = Number(val); + m = capi.sqlite3_bind_double; + }else{ + f._tooBigInt(val); + } + }else{ // !int32, !bigint + val = Number(val); + if(wasm.bigIntEnabled && Number.isInteger(val)){ + m = capi.sqlite3_bind_int64; + }else{ + m = capi.sqlite3_bind_double; + } + } + rc = m(stmt.pointer, ndx, val); + break; + } + case BindTypes.boolean: + rc = capi.sqlite3_bind_int(stmt.pointer, ndx, val ? 1 : 0); + break; + case BindTypes.blob: { + if('string'===typeof val){ + rc = f._.string(stmt, ndx, val, true); + }else if(!util.isBindableTypedArray(val)){ + toss3("Binding a value as a blob requires", + "that it be a string, Uint8Array, or Int8Array."); + }else if(1){ + /* _Hypothetically_ more efficient than the impl in the 'else' block. */ + const stack = wasm.scopedAllocPush(); + try{ + const pBlob = wasm.scopedAlloc(val.byteLength || 1); + wasm.heap8().set(val.byteLength ? val : [0], pBlob) + rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength, + capi.SQLITE_TRANSIENT); + }finally{ + wasm.scopedAllocPop(stack); + } + }else{ + const pBlob = wasm.allocFromTypedArray(val); + try{ + rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength, + capi.SQLITE_TRANSIENT); + }finally{ + wasm.dealloc(pBlob); + } + } + break; + } + default: + console.warn("Unsupported bind() argument type:",val); + toss3("Unsupported bind() argument type: "+(typeof val)); + } + if(rc) DB.checkRc(stmt.db.pointer, rc); + return stmt; + }; + + Stmt.prototype = { + /** + "Finalizes" this statement. This is a no-op if the + statement has already been finalizes. Returns + undefined. Most methods in this class will throw if called + after this is. + */ + finalize: function(){ + if(this.pointer){ + affirmUnlocked(this,'finalize()'); + delete __stmtMap.get(this.db)[this.pointer]; + capi.sqlite3_finalize(this.pointer); + __ptrMap.delete(this); + delete this._mayGet; + delete this.columnCount; + delete this.parameterCount; + delete this.db; + delete this._isLocked; + } + }, + /** Clears all bound values. Returns this object. + Throws if this statement has been finalized. */ + clearBindings: function(){ + affirmUnlocked(affirmStmtOpen(this), 'clearBindings()') + capi.sqlite3_clear_bindings(this.pointer); + this._mayGet = false; + return this; + }, + /** + Resets this statement so that it may be step()ed again + from the beginning. Returns this object. Throws if this + statement has been finalized. + + If passed a truthy argument then this.clearBindings() is + also called, otherwise any existing bindings, along with + any memory allocated for them, are retained. + */ + reset: function(alsoClearBinds){ + affirmUnlocked(this,'reset()'); + if(alsoClearBinds) this.clearBindings(); + capi.sqlite3_reset(affirmStmtOpen(this).pointer); + this._mayGet = false; + return this; + }, + /** + Binds one or more values to its bindable parameters. It + accepts 1 or 2 arguments: + + If passed a single argument, it must be either an array, an + object, or a value of a bindable type (see below). + + If passed 2 arguments, the first one is the 1-based bind + index or bindable parameter name and the second one must be + a value of a bindable type. + + Bindable value types: + + - null is bound as NULL. + + - undefined as a standalone value is a no-op intended to + simplify certain client-side use cases: passing undefined as + a value to this function will not actually bind anything and + this function will skip confirmation that binding is even + legal. (Those semantics simplify certain client-side uses.) + Conversely, a value of undefined as an array or object + property when binding an array/object (see below) is treated + the same as null. + + - Numbers are bound as either doubles or integers: doubles if + they are larger than 32 bits, else double or int32, depending + on whether they have a fractional part. Booleans are bound as + integer 0 or 1. It is not expected the distinction of binding + doubles which have no fractional parts is integers is + significant for the majority of clients due to sqlite3's data + typing model. If [BigInt] support is enabled then this + routine will bind BigInt values as 64-bit integers if they'll + fit in 64 bits. If that support disabled, it will store the + BigInt as an int32 or a double if it can do so without loss + of precision. If the BigInt is _too BigInt_ then it will + throw. + + - Strings are bound as strings (use bindAsBlob() to force + blob binding). + + - Uint8Array and Int8Array instances are bound as blobs. + (TODO: binding the other TypedArray types.) + + If passed an array, each element of the array is bound at + the parameter index equal to the array index plus 1 + (because arrays are 0-based but binding is 1-based). + + If passed an object, each object key is treated as a + bindable parameter name. The object keys _must_ match any + bindable parameter names, including any `$`, `@`, or `:` + prefix. Because `$` is a legal identifier chararacter in + JavaScript, that is the suggested prefix for bindable + parameters: `stmt.bind({$a: 1, $b: 2})`. + + It returns this object on success and throws on + error. Errors include: + + - Any bind index is out of range, a named bind parameter + does not match, or this statement has no bindable + parameters. + + - Any value to bind is of an unsupported type. + + - Passed no arguments or more than two. + + - The statement has been finalized. + */ + bind: function(/*[ndx,] arg*/){ + affirmStmtOpen(this); + let ndx, arg; + switch(arguments.length){ + case 1: ndx = 1; arg = arguments[0]; break; + case 2: ndx = arguments[0]; arg = arguments[1]; break; + default: toss3("Invalid bind() arguments."); + } + if(undefined===arg){ + /* It might seem intuitive to bind undefined as NULL + but this approach simplifies certain client-side + uses when passing on arguments between 2+ levels of + functions. */ + return this; + }else if(!this.parameterCount){ + toss3("This statement has no bindable parameters."); + } + this._mayGet = false; + if(null===arg){ + /* bind NULL */ + return bindOne(this, ndx, BindTypes.null, arg); + } + else if(Array.isArray(arg)){ + /* bind each entry by index */ + if(1!==arguments.length){ + toss3("When binding an array, an index argument is not permitted."); + } + arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); + return this; + } + else if('object'===typeof arg/*null was checked above*/ + && !util.isBindableTypedArray(arg)){ + /* Treat each property of arg as a named bound parameter. */ + if(1!==arguments.length){ + toss3("When binding an object, an index argument is not permitted."); + } + Object.keys(arg) + .forEach(k=>bindOne(this, k, + affirmSupportedBindType(arg[k]), + arg[k])); + return this; + }else{ + return bindOne(this, ndx, affirmSupportedBindType(arg), arg); + } + toss3("Should not reach this point."); + }, + /** + Special case of bind() which binds the given value using the + BLOB binding mechanism instead of the default selected one for + the value. The ndx may be a numbered or named bind index. The + value must be of type string, null/undefined (both get treated + as null), or a TypedArray of a type supported by the bind() + API. + + If passed a single argument, a bind index of 1 is assumed and + the first argument is the value. + */ + bindAsBlob: function(ndx,arg){ + affirmStmtOpen(this); + if(1===arguments.length){ + arg = ndx; + ndx = 1; + } + const t = affirmSupportedBindType(arg); + if(BindTypes.string !== t && BindTypes.blob !== t + && BindTypes.null !== t){ + toss3("Invalid value type for bindAsBlob()"); + } + bindOne(this, ndx, BindTypes.blob, arg); + this._mayGet = false; + return this; + }, + /** + Steps the statement one time. If the result indicates that a + row of data is available, a truthy value is returned. + If no row of data is available, a falsy + value is returned. Throws on error. + */ + step: function(){ + affirmUnlocked(this, 'step()'); + const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer); + switch(rc){ + case capi.SQLITE_DONE: return this._mayGet = false; + case capi.SQLITE_ROW: return this._mayGet = true; + default: + this._mayGet = false; + console.warn("sqlite3_step() rc=",rc, + capi.sqlite3_js_rc_str(rc), + "SQL =", capi.sqlite3_sql(this.pointer)); + DB.checkRc(this.db.pointer, rc); + } + }, + /** + Functions exactly like step() except that... + + 1) On success, it calls this.reset() and returns this object. + 2) On error, it throws and does not call reset(). + + This is intended to simplify constructs like: + + ``` + for(...) { + stmt.bind(...).stepReset(); + } + ``` + + Note that the reset() call makes it illegal to call this.get() + after the step. + */ + stepReset: function(){ + this.step(); + return this.reset(); + }, + /** + Functions like step() except that it finalizes this statement + immediately after stepping unless the step cannot be performed + because the statement is locked. Throws on error, but any error + other than the statement-is-locked case will also trigger + finalization of this statement. + + On success, it returns true if the step indicated that a row of + data was available, else it returns false. + + This is intended to simplify use cases such as: + + ``` + aDb.prepare("insert into foo(a) values(?)").bind(123).stepFinalize(); + ``` + */ + stepFinalize: function(){ + const rc = this.step(); + this.finalize(); + return rc; + }, + /** + Fetches the value from the given 0-based column index of + the current data row, throwing if index is out of range. + + Requires that step() has just returned a truthy value, else + an exception is thrown. + + By default it will determine the data type of the result + automatically. If passed a second arugment, it must be one + of the enumeration values for sqlite3 types, which are + defined as members of the sqlite3 module: SQLITE_INTEGER, + SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value, + except for undefined, will trigger an exception. Passing + undefined is the same as not passing a value. It is legal + to, e.g., fetch an integer value as a string, in which case + sqlite3 will convert the value to a string. + + If ndx is an array, this function behaves a differently: it + assigns the indexes of the array, from 0 to the number of + result columns, to the values of the corresponding column, + and returns that array. + + If ndx is a plain object, this function behaves even + differentlier: it assigns the properties of the object to + the values of their corresponding result columns. + + Blobs are returned as Uint8Array instances. + + Potential TODO: add type ID SQLITE_JSON, which fetches the + result as a string and passes it (if it's not null) to + JSON.parse(), returning the result of that. Until then, + getJSON() can be used for that. + */ + get: function(ndx,asType){ + if(!affirmStmtOpen(this)._mayGet){ + toss3("Stmt.step() has not (recently) returned true."); + } + if(Array.isArray(ndx)){ + let i = 0; + while(i=Number.MIN_SAFE_INTEGER && rc<=Number.MAX_SAFE_INTEGER){ + /* Coerce "normal" number ranges to normal number values, + and only return BigInt-type values for numbers out of this + range. */ + return Number(rc).valueOf(); + } + return rc; + }else{ + const rc = capi.sqlite3_column_double(this.pointer, ndx); + if(rc>Number.MAX_SAFE_INTEGER || rctoss3("The pointer property is read-only.") + } + Object.defineProperty(Stmt.prototype, 'pointer', prop); + Object.defineProperty(DB.prototype, 'pointer', prop); + } + + /** The OO API's public namespace. */ + sqlite3.oo1 = { + version: { + lib: capi.sqlite3_libversion(), + ooApi: "0.1" + }, + DB, + Stmt + }/*oo1 object*/; + + if(util.isUIThread()){ + /** + Functionally equivalent to DB(storageName,'c','kvvfs') except + that it throws if the given storage name is not one of 'local' + or 'session'. + */ + sqlite3.oo1.JsStorageDb = function(storageName='session'){ + if('session'!==storageName && 'local'!==storageName){ + toss3("JsStorageDb db name must be one of 'session' or 'local'."); + } + dbCtorHelper.call(this, { + filename: storageName, + flags: 'c', + vfs: "kvvfs" + }); + }; + const jdb = sqlite3.oo1.JsStorageDb; + jdb.prototype = Object.create(DB.prototype); + /** Equivalent to sqlite3_js_kvvfs_clear(). */ + jdb.clearStorage = capi.sqlite3_js_kvvfs_clear; + /** + Clears this database instance's storage or throws if this + instance has been closed. Returns the number of + database blocks which were cleaned up. + */ + jdb.prototype.clearStorage = function(){ + return jdb.clearStorage(affirmDbOpen(this).filename); + }; + /** Equivalent to sqlite3_js_kvvfs_size(). */ + jdb.storageSize = capi.sqlite3_js_kvvfs_size; + /** + Returns the _approximate_ number of bytes this database takes + up in its storage or throws if this instance has been closed. + */ + jdb.prototype.storageSize = function(){ + return jdb.storageSize(affirmDbOpen(this).filename); + }; + }/*main-window-only bits*/ + +}); + diff --git a/ext/wasm/api/sqlite3-api-opfs.js b/ext/wasm/api/sqlite3-api-opfs.js new file mode 100644 index 000000000..da5496f65 --- /dev/null +++ b/ext/wasm/api/sqlite3-api-opfs.js @@ -0,0 +1,1311 @@ +/* + 2022-09-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 holds the synchronous half of an sqlite3_vfs + implementation which proxies, in a synchronous fashion, the + asynchronous Origin-Private FileSystem (OPFS) APIs using a second + Worker, implemented in sqlite3-opfs-async-proxy.js. This file is + intended to be appended to the main sqlite3 JS deliverable somewhere + after sqlite3-api-oo1.js and before sqlite3-api-cleanup.js. +*/ +'use strict'; +self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +/** + installOpfsVfs() returns a Promise which, on success, installs an + sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs + which accept a VFS. It is intended to be called via + sqlite3ApiBootstrap.initializersAsync or an equivalent mechanism. + + The installed VFS uses the Origin-Private FileSystem API for + all file storage. On error it is rejected with an exception + explaining the problem. Reasons for rejection include, but are + not limited to: + + - The counterpart Worker (see below) could not be loaded. + + - The environment does not support OPFS. That includes when + this function is called from the main window thread. + + Significant notes and limitations: + + - As of this writing, OPFS is still very much in flux and only + available in bleeding-edge versions of Chrome (v102+, noting that + that number will increase as the OPFS API matures). + + - The OPFS features used here are only available in dedicated Worker + threads. This file tries to detect that case, resulting in a + rejected Promise if those features do not seem to be available. + + - It requires the SharedArrayBuffer and Atomics classes, and the + former is only available if the HTTP server emits the so-called + COOP and COEP response headers. These features are required for + proxying OPFS's synchronous API via the synchronous interface + required by the sqlite3_vfs API. + + - This function may only be called a single time. When called, this + function removes itself from the sqlite3 object. + + All arguments to this function are for internal/development purposes + only. They do not constitute a public API and may change at any + time. + + The argument may optionally be a plain object with the following + configuration options: + + - proxyUri: as described above + + - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables + logging of errors. 2 enables logging of warnings and errors. 3 + additionally enables debugging info. + + - sanityChecks (=false): if true, some basic sanity tests are + run on the OPFS VFS API after it's initialized, before the + returned Promise resolves. + + On success, the Promise resolves to the top-most sqlite3 namespace + object and that object gets a new object installed in its + `opfs` property, containing several OPFS-specific utilities. +*/ +const installOpfsVfs = function callee(options){ + if(!self.SharedArrayBuffer || + !self.Atomics || + !self.FileSystemHandle || + !self.FileSystemDirectoryHandle || + !self.FileSystemFileHandle || + !self.FileSystemFileHandle.prototype.createSyncAccessHandle || + !navigator.storage.getDirectory){ + return Promise.reject( + new Error("This environment does not have OPFS support.") + ); + } + if(!options || 'object'!==typeof options){ + options = Object.create(null); + } + const urlParams = new URL(self.location.href).searchParams; + if(undefined===options.verbose){ + options.verbose = urlParams.has('opfs-verbose') ? 3 : 2; + } + if(undefined===options.sanityChecks){ + options.sanityChecks = urlParams.has('opfs-sanity-check'); + } + if(undefined===options.proxyUri){ + options.proxyUri = callee.defaultProxyUri; + } + + if('function' === typeof options.proxyUri){ + options.proxyUri = options.proxyUri(); + } + const thePromise = new Promise(function(promiseResolve, promiseReject_){ + const loggers = { + 0:console.error.bind(console), + 1:console.warn.bind(console), + 2:console.log.bind(console) + }; + const logImpl = (level,...args)=>{ + if(options.verbose>level) loggers[level]("OPFS syncer:",...args); + }; + const log = (...args)=>logImpl(2, ...args); + const warn = (...args)=>logImpl(1, ...args); + const error = (...args)=>logImpl(0, ...args); + const toss = function(...args){throw new Error(args.join(' '))}; + const capi = sqlite3.capi; + const wasm = sqlite3.wasm; + const sqlite3_vfs = capi.sqlite3_vfs; + const sqlite3_file = capi.sqlite3_file; + const sqlite3_io_methods = capi.sqlite3_io_methods; + /** + Generic utilities for working with OPFS. This will get filled out + by the Promise setup and, on success, installed as sqlite3.opfs. + */ + const opfsUtil = Object.create(null); + /** + Not part of the public API. Solely for internal/development + use. + */ + opfsUtil.metrics = { + dump: function(){ + let k, n = 0, t = 0, w = 0; + for(k in state.opIds){ + const m = metrics[k]; + n += m.count; + t += m.time; + w += m.wait; + m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; + m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0; + } + console.log(self.location.href, + "metrics for",self.location.href,":",metrics, + "\nTotal of",n,"op(s) for",t, + "ms (incl. "+w+" ms of waiting on the async side)"); + console.log("Serialization metrics:",metrics.s11n); + W.postMessage({type:'opfs-async-metrics'}); + }, + reset: function(){ + let k; + const r = (m)=>(m.count = m.time = m.wait = 0); + for(k in state.opIds){ + r(metrics[k] = Object.create(null)); + } + let s = metrics.s11n = Object.create(null); + s = s.serialize = Object.create(null); + s.count = s.time = 0; + s = metrics.s11n.deserialize = Object.create(null); + s.count = s.time = 0; + } + }/*metrics*/; + const promiseReject = function(err){ + opfsVfs.dispose(); + return promiseReject_(err); + }; + const W = new Worker(options.proxyUri); + W._originalOnError = W.onerror /* will be restored later */; + W.onerror = function(err){ + // The error object doesn't contain any useful info when the + // failure is, e.g., that the remote script is 404. + error("Error initializing OPFS asyncer:",err); + promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons.")); + }; + const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/; + const dVfs = pDVfs + ? new sqlite3_vfs(pDVfs) + : null /* dVfs will be null when sqlite3 is built with + SQLITE_OS_OTHER. Though we cannot currently handle + that case, the hope is to eventually be able to. */; + const opfsVfs = new sqlite3_vfs(); + const opfsIoMethods = new sqlite3_io_methods(); + opfsVfs.$iVersion = 2/*yes, two*/; + opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof; + opfsVfs.$mxPathname = 1024/*sure, why not?*/; + opfsVfs.$zName = wasm.allocCString("opfs"); + // All C-side memory of opfsVfs is zeroed out, but just to be explicit: + opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null; + opfsVfs.ondispose = [ + '$zName', opfsVfs.$zName, + 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null), + 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose() + ]; + /** + Pedantic sidebar about opfsVfs.ondispose: the entries in that array + are items to clean up when opfsVfs.dispose() is called, but in this + environment it will never be called. The VFS instance simply + hangs around until the WASM module instance is cleaned up. We + "could" _hypothetically_ clean it up by "importing" an + sqlite3_os_end() impl into the wasm build, but the shutdown order + of the wasm engine and the JS one are undefined so there is no + guaranty that the opfsVfs instance would be available in one + environment or the other when sqlite3_os_end() is called (_if_ it + gets called at all in a wasm build, which is undefined). + */ + /** + State which we send to the async-api Worker or share with it. + This object must initially contain only cloneable or sharable + objects. After the worker's "inited" message arrives, other types + of data may be added to it. + + For purposes of Atomics.wait() and Atomics.notify(), we use a + SharedArrayBuffer with one slot reserved for each of the API + proxy's methods. The sync side of the API uses Atomics.wait() + on the corresponding slot and the async side uses + Atomics.notify() on that slot. + + The approach of using a single SAB to serialize comms for all + instances might(?) lead to deadlock situations in multi-db + cases. We should probably have one SAB here with a single slot + for locking a per-file initialization step and then allocate a + separate SAB like the above one for each file. That will + require a bit of acrobatics but should be feasible. The most + problematic part is that xOpen() would have to use + postMessage() to communicate its SharedArrayBuffer, and mixing + that approach with Atomics.wait/notify() gets a bit messy. + */ + const state = Object.create(null); + state.verbose = options.verbose; + state.littleEndian = (()=>{ + const buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, 256, true /* ==>littleEndian */); + // Int16Array uses the platform's endianness. + return new Int16Array(buffer)[0] === 256; + })(); + /** + Whether the async counterpart should log exceptions to + the serialization channel. That produces a great deal of + noise for seemingly innocuous things like xAccess() checks + for missing files, so this option may have one of 3 values: + + 0 = no exception logging + + 1 = only log exceptions for "significant" ops like xOpen(), + xRead(), and xWrite(). + + 2 = log all exceptions. + */ + state.asyncS11nExceptions = 1; + /* Size of file I/O buffer block. 64k = max sqlite3 page size, and + xRead/xWrite() will never deal in blocks larger than that. */ + state.fileBufferSize = 1024 * 64; + state.sabS11nOffset = state.fileBufferSize; + /** + The size of the block in our SAB for serializing arguments and + result values. Needs to be large enough to hold serialized + values of any of the proxied APIs. Filenames are the largest + part but are limited to opfsVfs.$mxPathname bytes. + */ + state.sabS11nSize = opfsVfs.$mxPathname * 2; + /** + The SAB used for all data I/O between the synchronous and + async halves (file i/o and arg/result s11n). + */ + state.sabIO = new SharedArrayBuffer( + state.fileBufferSize/* file i/o block */ + + state.sabS11nSize/* argument/result serialization block */ + ); + state.opIds = Object.create(null); + const metrics = Object.create(null); + { + /* Indexes for use in our SharedArrayBuffer... */ + let i = 0; + /* SAB slot used to communicate which operation is desired + between both workers. This worker writes to it and the other + listens for changes. */ + state.opIds.whichOp = i++; + /* Slot for storing return values. This worker listens to that + slot and the other worker writes to it. */ + state.opIds.rc = i++; + /* Each function gets an ID which this worker writes to + the whichOp slot. The async-api worker uses Atomic.wait() + on the whichOp slot to figure out which operation to run + next. */ + state.opIds.xAccess = i++; + state.opIds.xClose = i++; + state.opIds.xDelete = i++; + state.opIds.xDeleteNoWait = i++; + state.opIds.xFileControl = i++; + state.opIds.xFileSize = i++; + state.opIds.xLock = i++; + state.opIds.xOpen = i++; + state.opIds.xRead = i++; + state.opIds.xSleep = i++; + state.opIds.xSync = i++; + state.opIds.xTruncate = i++; + state.opIds.xUnlock = i++; + state.opIds.xWrite = i++; + state.opIds.mkdir = i++; + state.opIds['opfs-async-metrics'] = i++; + state.opIds['opfs-async-shutdown'] = i++; + /* The retry slot is used by the async part for wait-and-retry + semantics. Though we could hypothetically use the xSleep slot + for that, doing so might lead to undesired side effects. */ + state.opIds.retry = i++; + state.sabOP = new SharedArrayBuffer( + i * 4/* ==sizeof int32, noting that Atomics.wait() and friends + can only function on Int32Array views of an SAB. */); + opfsUtil.metrics.reset(); + } + /** + SQLITE_xxx constants to export to the async worker + counterpart... + */ + state.sq3Codes = Object.create(null); + [ + 'SQLITE_ACCESS_EXISTS', + 'SQLITE_ACCESS_READWRITE', + 'SQLITE_ERROR', + 'SQLITE_IOERR', + 'SQLITE_IOERR_ACCESS', + 'SQLITE_IOERR_CLOSE', + 'SQLITE_IOERR_DELETE', + 'SQLITE_IOERR_FSYNC', + 'SQLITE_IOERR_LOCK', + 'SQLITE_IOERR_READ', + 'SQLITE_IOERR_SHORT_READ', + 'SQLITE_IOERR_TRUNCATE', + 'SQLITE_IOERR_UNLOCK', + 'SQLITE_IOERR_WRITE', + 'SQLITE_LOCK_EXCLUSIVE', + 'SQLITE_LOCK_NONE', + 'SQLITE_LOCK_PENDING', + 'SQLITE_LOCK_RESERVED', + 'SQLITE_LOCK_SHARED', + 'SQLITE_MISUSE', + 'SQLITE_NOTFOUND', + 'SQLITE_OPEN_CREATE', + 'SQLITE_OPEN_DELETEONCLOSE', + 'SQLITE_OPEN_READONLY' + ].forEach((k)=>{ + if(undefined === (state.sq3Codes[k] = capi[k])){ + toss("Maintenance required: not found:",k); + } + }); + + /** + Runs the given operation (by name) in the async worker + counterpart, waits for its response, and returns the result + which the async worker writes to SAB[state.opIds.rc]. The + 2nd and subsequent arguments must be the aruguments for the + async op. + */ + const opRun = (op,...args)=>{ + const opNdx = state.opIds[op] || toss("Invalid op ID:",op); + state.s11n.serialize(...args); + Atomics.store(state.sabOPView, state.opIds.rc, -1); + Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx); + Atomics.notify(state.sabOPView, state.opIds.whichOp) + /* async thread will take over here */; + const t = performance.now(); + Atomics.wait(state.sabOPView, state.opIds.rc, -1) + /* When this wait() call returns, the async half will have + completed the operation and reported its results. */; + const rc = Atomics.load(state.sabOPView, state.opIds.rc); + metrics[op].wait += performance.now() - t; + if(rc && state.asyncS11nExceptions){ + const err = state.s11n.deserialize(); + if(err) error(op+"() async error:",...err); + } + return rc; + }; + + /** + Not part of the public API. Only for test/development use. + */ + opfsUtil.debug = { + asyncShutdown: ()=>{ + warn("Shutting down OPFS async listener. The OPFS VFS will no longer work."); + opRun('opfs-async-shutdown'); + }, + asyncRestart: ()=>{ + warn("Attempting to restart OPFS VFS async listener. Might work, might not."); + W.postMessage({type: 'opfs-async-restart'}); + } + }; + + const initS11n = ()=>{ + /** + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ACHTUNG: this code is 100% duplicated in the other half of + this proxy! The documentation is maintained in the + "synchronous half". + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + This proxy de/serializes cross-thread function arguments and + output-pointer values via the state.sabIO SharedArrayBuffer, + using the region defined by (state.sabS11nOffset, + state.sabS11nOffset]. Only one dataset is recorded at a time. + + This is not a general-purpose format. It only supports the + range of operations, and data sizes, needed by the + sqlite3_vfs and sqlite3_io_methods operations. Serialized + data are transient and this serialization algorithm may + change at any time. + + The data format can be succinctly summarized as: + + Nt...Td...D + + Where: + + - N = number of entries (1 byte) + + - t = type ID of first argument (1 byte) + + - ...T = type IDs of the 2nd and subsequent arguments (1 byte + each). + + - d = raw bytes of first argument (per-type size). + + - ...D = raw bytes of the 2nd and subsequent arguments (per-type + size). + + All types except strings have fixed sizes. Strings are stored + using their TextEncoder/TextDecoder representations. It would + arguably make more sense to store them as Int16Arrays of + their JS character values, but how best/fastest to get that + in and out of string form is an open point. Initial + experimentation with that approach did not gain us any speed. + + Historical note: this impl was initially about 1% this size by + using using JSON.stringify/parse(), but using fit-to-purpose + serialization saves considerable runtime. + */ + if(state.s11n) return state.s11n; + const textDecoder = new TextDecoder(), + textEncoder = new TextEncoder('utf-8'), + viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), + viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); + state.s11n = Object.create(null); + /* Only arguments and return values of these types may be + serialized. This covers the whole range of types needed by the + sqlite3_vfs API. */ + const TypeIds = Object.create(null); + TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; + TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; + TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; + TypeIds.string = { id: 4 }; + + const getTypeId = (v)=>( + TypeIds[typeof v] + || toss("Maintenance required: this value type cannot be serialized.",v) + ); + const getTypeIdById = (tid)=>{ + switch(tid){ + case TypeIds.number.id: return TypeIds.number; + case TypeIds.bigint.id: return TypeIds.bigint; + case TypeIds.boolean.id: return TypeIds.boolean; + case TypeIds.string.id: return TypeIds.string; + default: toss("Invalid type ID:",tid); + } + }; + + /** + Returns an array of the deserialized state stored by the most + recent serialize() operation (from from this thread or the + counterpart thread), or null if the serialization buffer is + empty. If passed a truthy argument, the serialization buffer + is cleared after deserialization. + */ + state.s11n.deserialize = function(clear=false){ + ++metrics.s11n.deserialize.count; + const t = performance.now(); + const argc = viewU8[0]; + const rc = argc ? [] : null; + if(argc){ + const typeIds = []; + let offset = 1, i, n, v; + for(i = 0; i < argc; ++i, ++offset){ + typeIds.push(getTypeIdById(viewU8[offset])); + } + for(i = 0; i < argc; ++i){ + const t = typeIds[i]; + if(t.getter){ + v = viewDV[t.getter](offset, state.littleEndian); + offset += t.size; + }else{/*String*/ + n = viewDV.getInt32(offset, state.littleEndian); + offset += 4; + v = textDecoder.decode(viewU8.slice(offset, offset+n)); + offset += n; + } + rc.push(v); + } + } + if(clear) viewU8[0] = 0; + //log("deserialize:",argc, rc); + metrics.s11n.deserialize.time += performance.now() - t; + return rc; + }; + + /** + Serializes all arguments to the shared buffer for consumption + by the counterpart thread. + + This routine is only intended for serializing OPFS VFS + arguments and (in at least one special case) result values, + and the buffer is sized to be able to comfortably handle + those. + + If passed no arguments then it zeroes out the serialization + state. + */ + state.s11n.serialize = function(...args){ + const t = performance.now(); + ++metrics.s11n.serialize.count; + if(args.length){ + //log("serialize():",args); + const typeIds = []; + let i = 0, offset = 1; + viewU8[0] = args.length & 0xff /* header = # of args */; + for(; i < args.length; ++i, ++offset){ + /* Write the TypeIds.id value into the next args.length + bytes. */ + typeIds.push(getTypeId(args[i])); + viewU8[offset] = typeIds[i].id; + } + for(i = 0; i < args.length; ++i) { + /* Deserialize the following bytes based on their + corresponding TypeIds.id from the header. */ + const t = typeIds[i]; + if(t.setter){ + viewDV[t.setter](offset, args[i], state.littleEndian); + offset += t.size; + }else{/*String*/ + const s = textEncoder.encode(args[i]); + viewDV.setInt32(offset, s.byteLength, state.littleEndian); + offset += 4; + viewU8.set(s, offset); + offset += s.byteLength; + } + } + //log("serialize() result:",viewU8.slice(0,offset)); + }else{ + viewU8[0] = 0; + } + metrics.s11n.serialize.time += performance.now() - t; + }; + return state.s11n; + }/*initS11n()*/; + + /** + Generates a random ASCII string len characters long, intended for + use as a temporary file name. + */ + const randomFilename = function f(len=16){ + if(!f._chars){ + f._chars = "abcdefghijklmnopqrstuvwxyz"+ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ + "012346789"; + f._n = f._chars.length; + } + const a = []; + let i = 0; + for( ; i < len; ++i){ + const ndx = Math.random() * (f._n * 64) % f._n | 0; + a[i] = f._chars[ndx]; + } + return a.join(''); + }; + + /** + Map of sqlite3_file pointers to objects constructed by xOpen(). + */ + const __openFiles = Object.create(null); + + /** + Installs a StructBinder-bound function pointer member of the + given name and function in the given StructType target object. + It creates a WASM proxy for the given function and arranges for + that proxy to be cleaned up when tgt.dispose() is called. Throws + on the slightest hint of error (e.g. tgt is-not-a StructType, + name does not map to a struct-bound member, etc.). + + Returns a proxy for this function which is bound to tgt and takes + 2 args (name,func). That function returns the same thing, + permitting calls to be chained. + + If called with only 1 arg, it has no side effects but returns a + func with the same signature as described above. + */ + const installMethod = function callee(tgt, name, func){ + if(!(tgt instanceof sqlite3.StructBinder.StructType)){ + toss("Usage error: target object is-not-a StructType."); + } + if(1===arguments.length){ + return (n,f)=>callee(tgt,n,f); + } + if(!callee.argcProxy){ + callee.argcProxy = function(func,sig){ + return function(...args){ + if(func.length!==arguments.length){ + toss("Argument mismatch. Native signature is:",sig); + } + return func.apply(this, args); + } + }; + callee.removeFuncList = function(){ + if(this.ondispose.__removeFuncList){ + this.ondispose.__removeFuncList.forEach( + (v,ndx)=>{ + if('number'===typeof v){ + try{wasm.uninstallFunction(v)} + catch(e){/*ignore*/} + } + /* else it's a descriptive label for the next number in + the list. */ + } + ); + delete this.ondispose.__removeFuncList; + } + }; + }/*static init*/ + const sigN = tgt.memberSignature(name); + if(sigN.length<2){ + toss("Member",name," is not a function pointer. Signature =",sigN); + } + const memKey = tgt.memberKey(name); + const fProxy = 0 + /** This middle-man proxy is only for use during development, to + confirm that we always pass the proper number of + arguments. We know that the C-level code will always use the + correct argument count. */ + ? callee.argcProxy(func, sigN) + : func; + const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); + tgt[memKey] = pFunc; + if(!tgt.ondispose) tgt.ondispose = []; + if(!tgt.ondispose.__removeFuncList){ + tgt.ondispose.push('ondispose.__removeFuncList handler', + callee.removeFuncList); + tgt.ondispose.__removeFuncList = []; + } + tgt.ondispose.__removeFuncList.push(memKey, pFunc); + return (n,f)=>callee(tgt, n, f); + }/*installMethod*/; + + const opTimer = Object.create(null); + opTimer.op = undefined; + opTimer.start = undefined; + const mTimeStart = (op)=>{ + opTimer.start = performance.now(); + opTimer.op = op; + ++metrics[op].count; + }; + const mTimeEnd = ()=>( + metrics[opTimer.op].time += performance.now() - opTimer.start + ); + + /** + Impls for the sqlite3_io_methods methods. Maintenance reminder: + members are in alphabetical order to simplify finding them. + */ + const ioSyncWrappers = { + xCheckReservedLock: function(pFile,pOut){ + /** + As of late 2022, only a single lock can be held on an OPFS + file. We have no way of checking whether any _other_ db + connection has a lock except by trying to obtain and (on + success) release a sync-handle for it, but doing so would + involve an inherent race condition. For the time being, + pending a better solution, we simply report whether the + given pFile instance has a lock. + */ + const f = __openFiles[pFile]; + wasm.setMemValue(pOut, f.lockMode ? 1 : 0, 'i32'); + return 0; + }, + xClose: function(pFile){ + mTimeStart('xClose'); + let rc = 0; + const f = __openFiles[pFile]; + if(f){ + delete __openFiles[pFile]; + rc = opRun('xClose', pFile); + if(f.sq3File) f.sq3File.dispose(); + } + mTimeEnd(); + return rc; + }, + xDeviceCharacteristics: function(pFile){ + //debug("xDeviceCharacteristics(",pFile,")"); + return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN; + }, + xFileControl: function(pFile, opId, pArg){ + mTimeStart('xFileControl'); + const rc = (capi.SQLITE_FCNTL_SYNC===opId) + ? opRun('xSync', pFile, 0) + : capi.SQLITE_NOTFOUND; + mTimeEnd(); + return rc; + }, + xFileSize: function(pFile,pSz64){ + mTimeStart('xFileSize'); + const rc = opRun('xFileSize', pFile); + if(0==rc){ + const sz = state.s11n.deserialize()[0]; + wasm.setMemValue(pSz64, sz, 'i64'); + } + mTimeEnd(); + return rc; + }, + xLock: function(pFile,lockType){ + mTimeStart('xLock'); + const f = __openFiles[pFile]; + let rc = 0; + if( capi.SQLITE_LOCK_NONE === f.lockType ) { + rc = opRun('xLock', pFile, lockType); + if( 0===rc ) f.lockType = lockType; + }else{ + f.lockType = lockType; + } + mTimeEnd(); + return rc; + }, + xRead: function(pFile,pDest,n,offset64){ + mTimeStart('xRead'); + const f = __openFiles[pFile]; + let rc; + try { + rc = opRun('xRead',pFile, n, Number(offset64)); + if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){ + /** + Results get written to the SharedArrayBuffer f.sabView. + Because the heap is _not_ a SharedArrayBuffer, we have + to copy the results. TypedArray.set() seems to be the + fastest way to copy this. */ + wasm.heap8u().set(f.sabView.subarray(0, n), pDest); + } + }catch(e){ + error("xRead(",arguments,") failed:",e,f); + rc = capi.SQLITE_IOERR_READ; + } + mTimeEnd(); + return rc; + }, + xSync: function(pFile,flags){ + ++metrics.xSync.count; + return 0; // impl'd in xFileControl() + }, + xTruncate: function(pFile,sz64){ + mTimeStart('xTruncate'); + const rc = opRun('xTruncate', pFile, Number(sz64)); + mTimeEnd(); + return rc; + }, + xUnlock: function(pFile,lockType){ + mTimeStart('xUnlock'); + const f = __openFiles[pFile]; + let rc = 0; + if( capi.SQLITE_LOCK_NONE === lockType + && f.lockType ){ + rc = opRun('xUnlock', pFile, lockType); + } + if( 0===rc ) f.lockType = lockType; + mTimeEnd(); + return rc; + }, + xWrite: function(pFile,pSrc,n,offset64){ + mTimeStart('xWrite'); + const f = __openFiles[pFile]; + let rc; + try { + f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n)); + rc = opRun('xWrite', pFile, n, Number(offset64)); + }catch(e){ + error("xWrite(",arguments,") failed:",e,f); + rc = capi.SQLITE_IOERR_WRITE; + } + mTimeEnd(); + return rc; + } + }/*ioSyncWrappers*/; + + /** + Impls for the sqlite3_vfs methods. Maintenance reminder: members + are in alphabetical order to simplify finding them. + */ + const vfsSyncWrappers = { + xAccess: function(pVfs,zName,flags,pOut){ + mTimeStart('xAccess'); + const rc = opRun('xAccess', wasm.cstringToJs(zName)); + wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' ); + mTimeEnd(); + return 0; + }, + xCurrentTime: function(pVfs,pOut){ + /* If it turns out that we need to adjust for timezone, see: + https://stackoverflow.com/a/11760121/1458521 */ + wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000), + 'double'); + return 0; + }, + xCurrentTimeInt64: function(pVfs,pOut){ + // TODO: confirm that this calculation is correct + wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(), + 'i64'); + return 0; + }, + xDelete: function(pVfs, zName, doSyncDir){ + mTimeStart('xDelete'); + opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false); + /* We're ignoring errors because we cannot yet differentiate + between harmless and non-harmless failures. */ + mTimeEnd(); + return 0; + }, + xFullPathname: function(pVfs,zName,nOut,pOut){ + /* Until/unless we have some notion of "current dir" + in OPFS, simply copy zName to pOut... */ + const i = wasm.cstrncpy(pOut, zName, nOut); + return ipMethods is NULL. */ + if(fh.readOnly){ + wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32'); + } + __openFiles[pFile] = fh; + fh.sabView = state.sabFileBufView; + fh.sq3File = new sqlite3_file(pFile); + fh.sq3File.$pMethods = opfsIoMethods.pointer; + fh.lockType = capi.SQLITE_LOCK_NONE; + } + mTimeEnd(); + return rc; + }/*xOpen()*/ + }/*vfsSyncWrappers*/; + + if(dVfs){ + opfsVfs.$xRandomness = dVfs.$xRandomness; + opfsVfs.$xSleep = dVfs.$xSleep; + } + if(!opfsVfs.$xRandomness){ + /* If the default VFS has no xRandomness(), add a basic JS impl... */ + vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){ + const heap = wasm.heap8u(); + let i = 0; + for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF; + return i; + }; + } + if(!opfsVfs.$xSleep){ + /* If we can inherit an xSleep() impl from the default VFS then + assume it's sane and use it, otherwise install a JS-based + one. */ + vfsSyncWrappers.xSleep = function(pVfs,ms){ + Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms); + return 0; + }; + } + + /* Install the vfs/io_methods into their C-level shared instances... */ + for(let k of Object.keys(ioSyncWrappers)){ + installMethod(opfsIoMethods, k, ioSyncWrappers[k]); + } + for(let k of Object.keys(vfsSyncWrappers)){ + installMethod(opfsVfs, k, vfsSyncWrappers[k]); + } + + /** + Expects an OPFS file path. It gets resolved, such that ".." + components are properly expanded, and returned. If the 2nd arg + is true, the result is returned as an array of path elements, + else an absolute path string is returned. + */ + opfsUtil.getResolvedPath = function(filename,splitIt){ + const p = new URL(filename, "file://irrelevant").pathname; + return splitIt ? p.split('/').filter((v)=>!!v) : p; + }; + + /** + Takes the absolute path to a filesystem element. Returns an + array of [handleOfContainingDir, filename]. If the 2nd argument + is truthy then each directory element leading to the file is + created along the way. Throws if any creation or resolution + fails. + */ + opfsUtil.getDirForFilename = async function f(absFilename, createDirs = false){ + const path = opfsUtil.getResolvedPath(absFilename, true); + const filename = path.pop(); + let dh = opfsUtil.rootDirectory; + for(const dirName of path){ + if(dirName){ + dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); + } + } + return [dh, filename]; + }; + + /** + Creates the given directory name, recursively, in + the OPFS filesystem. Returns true if it succeeds or the + directory already exists, else false. + */ + opfsUtil.mkdir = async function(absDirName){ + try { + await opfsUtil.getDirForFilename(absDirName+"/filepart", true); + return true; + }catch(e){ + //console.warn("mkdir(",absDirName,") failed:",e); + return false; + } + }; + /** + Checks whether the given OPFS filesystem entry exists, + returning true if it does, false if it doesn't. + */ + opfsUtil.entryExists = async function(fsEntryName){ + try { + const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName); + await dh.getFileHandle(fn); + return true; + }catch(e){ + return false; + } + }; + + /** + Generates a random ASCII string, intended for use as a + temporary file name. Its argument is the length of the string, + defaulting to 16. + */ + opfsUtil.randomFilename = randomFilename; + + /** + Re-registers the OPFS VFS. This is intended only for odd use + cases which have to call sqlite3_shutdown() as part of their + initialization process, which will unregister the VFS + registered by installOpfsVfs(). If passed a truthy value, the + OPFS VFS is registered as the default VFS, else it is not made + the default. Returns the result of the the + sqlite3_vfs_register() call. + + Design note: the problem of having to re-register things after + a shutdown/initialize pair is more general. How to best plug + that in to the library is unclear. In particular, we cannot + hook in to any C-side calls to sqlite3_initialize(), so we + cannot add an after-initialize callback mechanism. + */ + opfsUtil.registerVfs = (asDefault=false)=>{ + return wasm.exports.sqlite3_vfs_register( + opfsVfs.pointer, asDefault ? 1 : 0 + ); + }; + + /** + Returns a promise which resolves to an object which represents + all files and directories in the OPFS tree. The top-most object + has two properties: `dirs` is an array of directory entries + (described below) and `files` is a list of file names for all + files in that directory. + + Traversal starts at sqlite3.opfs.rootDirectory. + + Each `dirs` entry is an object in this form: + + ``` + { name: directoryName, + dirs: [...subdirs], + files: [...file names] + } + ``` + + The `files` and `subdirs` entries are always set but may be + empty arrays. + + The returned object has the same structure but its `name` is + an empty string. All returned objects are created with + Object.create(null), so have no prototype. + + Design note: the entries do not contain more information, + e.g. file sizes, because getting such info is not only + expensive but is subject to locking-related errors. + */ + opfsUtil.treeList = async function(){ + const doDir = async function callee(dirHandle,tgt){ + tgt.name = dirHandle.name; + tgt.dirs = []; + tgt.files = []; + for await (const handle of dirHandle.values()){ + if('directory' === handle.kind){ + const subDir = Object.create(null); + tgt.dirs.push(subDir); + await callee(handle, subDir); + }else{ + tgt.files.push(handle.name); + } + } + }; + const root = Object.create(null); + await doDir(opfsUtil.rootDirectory, root); + return root; + }; + + /** + Irrevocably deletes _all_ files in the current origin's OPFS. + Obviously, this must be used with great caution. It may throw + an exception if removal of anything fails (e.g. a file is + locked), but the precise conditions under which it will throw + are not documented (so we cannot tell you what they are). + */ + opfsUtil.rmfr = async function(){ + const dir = opfsUtil.rootDirectory, opt = {recurse: true}; + for await (const handle of dir.values()){ + dir.removeEntry(handle.name, opt); + } + }; + + /** + Deletes the given OPFS filesystem entry. As this environment + has no notion of "current directory", the given name must be an + absolute path. If the 2nd argument is truthy, deletion is + recursive (use with caution!). + + The returned Promise resolves to true if the deletion was + successful, else false (but...). The OPFS API reports the + reason for the failure only in human-readable form, not + exceptions which can be type-checked to determine the + failure. Because of that... + + If the final argument is truthy then this function will + propagate any exception on error, rather than returning false. + */ + opfsUtil.unlink = async function(fsEntryName, recursive = false, + throwOnError = false){ + try { + const [hDir, filenamePart] = + await opfsUtil.getDirForFilename(fsEntryName, false); + await hDir.removeEntry(filenamePart, {recursive}); + return true; + }catch(e){ + if(throwOnError){ + throw new Error("unlink(",arguments[0],") failed: "+e.message,{ + cause: e + }); + } + return false; + } + }; + + /** + Traverses the OPFS filesystem, calling a callback for each one. + The argument may be either a callback function or an options object + with any of the following properties: + + - `callback`: function which gets called for each filesystem + entry. It gets passed 3 arguments: 1) the + FileSystemFileHandle or FileSystemDirectoryHandle of each + entry (noting that both are instanceof FileSystemHandle). 2) + the FileSystemDirectoryHandle of the parent directory. 3) the + current depth level, with 0 being at the top of the tree + relative to the starting directory. If the callback returns a + literal false, as opposed to any other falsy value, traversal + stops without an error. Any exceptions it throws are + propagated. Results are undefined if the callback manipulate + the filesystem (e.g. removing or adding entries) because the + how OPFS iterators behave in the face of such changes is + undocumented. + + - `recursive` [bool=true]: specifies whether to recurse into + subdirectories or not. Whether recursion is depth-first or + breadth-first is unspecified! + + - `directory` [FileSystemDirectoryEntry=sqlite3.opfs.rootDirectory] + specifies the starting directory. + + If this function is passed a function, it is assumed to be the + callback. + + Returns a promise because it has to (by virtue of being async) + but that promise has no specific meaning: the traversal it + performs is synchronous. The promise must be used to catch any + exceptions propagated by the callback, however. + + TODO: add an option which specifies whether to traverse + depth-first or breadth-first. We currently do depth-first but + an incremental file browsing widget would benefit more from + breadth-first. + */ + opfsUtil.traverse = async function(opt){ + const defaultOpt = { + recursive: true, + directory: opfsUtil.rootDirectory + }; + if('function'===typeof opt){ + opt = {callback:opt}; + } + opt = Object.assign(defaultOpt, opt||{}); + const doDir = async function callee(dirHandle, depth){ + for await (const handle of dirHandle.values()){ + if(false === opt.callback(handle, dirHandle, depth)) return false; + else if(opt.recursive && 'directory' === handle.kind){ + if(false === await callee(handle, depth + 1)) break; + } + } + }; + doDir(opt.directory, 0); + }; + + //TODO to support fiddle and worker1 db upload: + //opfsUtil.createFile = function(absName, content=undefined){...} + + if(sqlite3.oo1){ + opfsUtil.OpfsDb = function(...args){ + const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args); + opt.vfs = opfsVfs.$zName; + sqlite3.oo1.DB.dbCtorHelper.call(this, opt); + }; + opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); + sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( + opfsVfs.pointer, + [ + /* Truncate journal mode is faster than delete or wal for + this vfs, per speedtest1. */ + "pragma journal_mode=truncate;" + /* + This vfs benefits hugely from cache on moderate/large + speedtest1 --size 50 and --size 100 workloads. We currently + rely on setting a non-default cache size when building + sqlite3.wasm. If that policy changes, the cache can + be set here. + */ + //"pragma cache_size=-8388608;" + ].join('') + ); + } + + /** + Potential TODOs: + + - Expose one or both of the Worker objects via opfsUtil and + publish an interface for proxying the higher-level OPFS + features like getting a directory listing. + */ + const sanityCheck = function(){ + const scope = wasm.scopedAllocPush(); + const sq3File = new sqlite3_file(); + try{ + const fid = sq3File.pointer; + const openFlags = capi.SQLITE_OPEN_CREATE + | capi.SQLITE_OPEN_READWRITE + //| capi.SQLITE_OPEN_DELETEONCLOSE + | capi.SQLITE_OPEN_MAIN_DB; + const pOut = wasm.scopedAlloc(8); + const dbFile = "/sanity/check/file"+randomFilename(8); + const zDbFile = wasm.scopedAllocCString(dbFile); + let rc; + state.s11n.serialize("This is ä string."); + rc = state.s11n.deserialize(); + log("deserialize() says:",rc); + if("This is ä string."!==rc[0]) toss("String d13n error."); + vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); + rc = wasm.getMemValue(pOut,'i32'); + log("xAccess(",dbFile,") exists ?=",rc); + rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile, + fid, openFlags, pOut); + log("open rc =",rc,"state.sabOPView[xOpen] =", + state.sabOPView[state.opIds.xOpen]); + if(0!==rc){ + error("open failed with code",rc); + return; + } + vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); + rc = wasm.getMemValue(pOut,'i32'); + if(!rc) toss("xAccess() failed to detect file."); + rc = ioSyncWrappers.xSync(sq3File.pointer, 0); + if(rc) toss('sync failed w/ rc',rc); + rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024); + if(rc) toss('truncate failed w/ rc',rc); + wasm.setMemValue(pOut,0,'i64'); + rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut); + if(rc) toss('xFileSize failed w/ rc',rc); + log("xFileSize says:",wasm.getMemValue(pOut, 'i64')); + rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1); + if(rc) toss("xWrite() failed!"); + const readBuf = wasm.scopedAlloc(16); + rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2); + wasm.setMemValue(readBuf+6,0); + let jRead = wasm.cstringToJs(readBuf); + log("xRead() got:",jRead); + if("sanity"!==jRead) toss("Unexpected xRead() value."); + if(vfsSyncWrappers.xSleep){ + log("xSleep()ing before close()ing..."); + vfsSyncWrappers.xSleep(opfsVfs.pointer,2000); + log("waking up from xSleep()"); + } + rc = ioSyncWrappers.xClose(fid); + log("xClose rc =",rc,"sabOPView =",state.sabOPView); + log("Deleting file:",dbFile); + vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234); + vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); + rc = wasm.getMemValue(pOut,'i32'); + if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete()."); + warn("End of OPFS sanity checks."); + }finally{ + sq3File.dispose(); + wasm.scopedAllocPop(scope); + } + }/*sanityCheck()*/; + + W.onmessage = function({data}){ + //log("Worker.onmessage:",data); + switch(data.type){ + case 'opfs-async-loaded': + /*Arrives as soon as the asyc proxy finishes loading. + Pass our config and shared state on to the async worker.*/ + W.postMessage({type: 'opfs-async-init',args: state}); + break; + case 'opfs-async-inited':{ + /*Indicates that the async partner has received the 'init' + and has finished initializing, so the real work can + begin...*/ + try { + const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0); + if(rc){ + toss("sqlite3_vfs_register(OPFS) failed with rc",rc); + } + if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){ + toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS"); + } + capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods); + state.sabOPView = new Int32Array(state.sabOP); + state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); + state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); + initS11n(); + if(options.sanityChecks){ + warn("Running sanity checks because of opfs-sanity-check URL arg..."); + sanityCheck(); + } + navigator.storage.getDirectory().then((d)=>{ + W.onerror = W._originalOnError; + delete W._originalOnError; + sqlite3.opfs = opfsUtil; + opfsUtil.rootDirectory = d; + log("End of OPFS sqlite3_vfs setup.", opfsVfs); + promiseResolve(sqlite3); + }); + }catch(e){ + error(e); + promiseReject(e); + } + break; + } + default: + promiseReject(e); + error("Unexpected message from the async worker:",data); + break; + }/*switch(data.type)*/ + }/*W.onmessage()*/; + })/*thePromise*/; + return thePromise; +}/*installOpfsVfs()*/; +installOpfsVfs.defaultProxyUri = + "sqlite3-opfs-async-proxy.js"; +self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ + if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){ + return; + } + try{ + let proxyJs = installOpfsVfs.defaultProxyUri; + if(sqlite3.scriptInfo.sqlite3Dir){ + installOpfsVfs.defaultProxyUri = + sqlite3.scriptInfo.sqlite3Dir + proxyJs; + //console.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri); + } + return installOpfsVfs().catch((e)=>{ + console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message); + }); + }catch(e){ + console.error("installOpfsVfs() exception:",e); + throw e; + } +}); +}/*sqlite3ApiBootstrap.initializers.push()*/); diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js new file mode 100644 index 000000000..fed1c5666 --- /dev/null +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -0,0 +1,1602 @@ +/* + 2022-05-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 is intended to be combined at build-time with other + related code, most notably a header and footer which wraps this whole + file into an Emscripten Module.postRun() handler which has a parameter + named "Module" (the Emscripten Module object). The exact requirements, + conventions, and build process are very much under construction and + will be (re)documented once they've stopped fluctuating so much. + + Project home page: https://sqlite.org + + Documentation home page: https://sqlite.org/wasm + + Specific goals of this subproject: + + - Except where noted in the non-goals, provide a more-or-less + feature-complete wrapper to the sqlite3 C API, insofar as WASM + feature parity with C allows for. In fact, provide at least 4 + APIs... + + 1) 1-to-1 bindings as exported from WASM, with no automatic + type conversions between JS and C. + + 2) A binding of (1) which provides certain JS/C type conversions + to greatly simplify its use. + + 3) A higher-level API, more akin to sql.js and node.js-style + implementations. This one speaks directly to the low-level + API. This API must be used from the same thread as the + low-level API. + + 4) A second higher-level API which speaks to the previous APIs via + worker messages. This one is intended for use in the main + thread, with the lower-level APIs installed in a Worker thread, + and talking to them via Worker messages. Because Workers are + asynchronouns and have only a single message channel, some + acrobatics are needed here to feed async work results back to + the client (as we cannot simply pass around callbacks between + the main and Worker threads). + + - Insofar as possible, support client-side storage using JS + filesystem APIs. As of this writing, such things are still very + much under development. + + Specific non-goals of this project: + + - As WASM is a web-centric technology and UTF-8 is the King of + Encodings in that realm, there are no currently plans to support + the UTF16-related sqlite3 APIs. They would add a complication to + the bindings for no appreciable benefit. Though web-related + implementation details take priority, and the JavaScript + components of the API specifically focus on browser clients, the + lower-level WASM module "should" work in non-web WASM + environments. + + - Supporting old or niche-market platforms. WASM is built for a + modern web and requires modern platforms. + + - Though scalar User-Defined Functions (UDFs) may be created in + JavaScript, there are currently no plans to add support for + aggregate and window functions. + + Attribution: + + This project is endebted to the work of sql.js: + + https://github.com/sql-js/sql.js + + sql.js was an essential stepping stone in this code's development as + it demonstrated how to handle some of the WASM-related voodoo (like + handling pointers-to-pointers and adding JS implementations of + C-bound callback functions). These APIs have a considerably + different shape than sql.js's, however. +*/ + +/** + sqlite3ApiBootstrap() is the only global symbol persistently + exposed by this API. It is intended to be called one time at the + end of the API amalgamation process, passed configuration details + for the current environment, and then optionally be removed from + the global object using `delete self.sqlite3ApiBootstrap`. + + This function expects a configuration object, intended to abstract + away details specific to any given WASM environment, primarily so + that it can be used without any _direct_ dependency on + Emscripten. (Note the default values for the config object!) The + config object is only honored the first time this is + called. Subsequent calls ignore the argument and return the same + (configured) object which gets initialized by the first call. + This function will throw if any of the required config options are + missing. + + The config object properties include: + + - `exports`[^1]: the "exports" object for the current WASM + environment. In an Emscripten-based build, this should be set to + `Module['asm']`. + + - `memory`[^1]: optional WebAssembly.Memory object, defaulting to + `exports.memory`. In Emscripten environments this should be set + to `Module.wasmMemory` if the build uses `-sIMPORT_MEMORY`, or be + left undefined/falsy to default to `exports.memory` when using + WASM-exported memory. + + - `bigIntEnabled`: true if BigInt support is enabled. Defaults to + true if `self.BigInt64Array` is available, else false. Some APIs + will throw exceptions if called without BigInt support, as BigInt + is required for marshalling C-side int64 into and out of JS. + + - `allocExportName`: the name of the function, in `exports`, of the + `malloc(3)`-compatible routine for the WASM environment. Defaults + to `"malloc"`. + + - `deallocExportName`: the name of the function, in `exports`, of + the `free(3)`-compatible routine for the WASM + environment. Defaults to `"free"`. + + - `wasmfsOpfsDir`[^1]: if the environment supports persistent + storage, this directory names the "mount point" for that + directory. It must be prefixed by `/` and may contain only a + single directory-name part. Using the root directory name is not + supported by any current persistent backend. This setting is + only used in WASMFS-enabled builds. + + + [^1] = This property may optionally be a function, in which case this + function re-assigns it to the value returned from that function, + enabling delayed evaluation. + +*/ +'use strict'; +self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( + apiConfig = (self.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig) +){ + if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */ + console.warn("sqlite3ApiBootstrap() called multiple times.", + "Config and external initializers are ignored on calls after the first."); + return sqlite3ApiBootstrap.sqlite3; + } + const config = Object.assign(Object.create(null),{ + exports: undefined, + memory: undefined, + bigIntEnabled: (()=>{ + if('undefined'!==typeof Module){ + /* Emscripten module will contain HEAPU64 when built with + -sWASM_BIGINT=1, else it will not. */ + return !!Module.HEAPU64; + } + return !!self.BigInt64Array; + })(), + allocExportName: 'malloc', + deallocExportName: 'free', + wasmfsOpfsDir: '/opfs' + }, apiConfig || {}); + + [ + // If any of these config options are functions, replace them with + // the result of calling that function... + 'exports', 'memory', 'wasmfsOpfsDir' + ].forEach((k)=>{ + if('function' === typeof config[k]){ + config[k] = config[k](); + } + }); + + /** + The main sqlite3 binding API gets installed into this object, + mimicking the C API as closely as we can. The numerous members + names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as + possible, identically to the C-native counterparts, as documented at: + + https://www.sqlite.org/c3ref/intro.html + + A very few exceptions require an additional level of proxy + function or may otherwise require special attention in the WASM + environment, and all such cases are documented somewhere below + in this file or in sqlite3-api-glue.js. capi members which are + not documented are installed as 1-to-1 proxies for their + C-side counterparts. + */ + const capi = Object.create(null); + /** + Holds state which are specific to the WASM-related + infrastructure and glue code. It is not expected that client + code will normally need these, but they're exposed here in case + it does. These APIs are _not_ to be considered an + official/stable part of the sqlite3 WASM API. They may change + as the developers' experience suggests appropriate changes. + + Note that a number of members of this object are injected + dynamically after the api object is fully constructed, so + not all are documented in this file. + */ + const wasm = Object.create(null); + + /** Internal helper for SQLite3Error ctor. */ + const __rcStr = (rc)=>{ + return (capi.sqlite3_js_rc_str && capi.sqlite3_js_rc_str(rc)) + || ("Unknown result code #"+rc); + }; + + /** Internal helper for SQLite3Error ctor. */ + const __isInt = (n)=>'number'===typeof n && n===(n | 0); + + /** + An Error subclass specifically for reporting DB-level errors and + enabling clients to unambiguously identify such exceptions. + The C-level APIs never throw, but some of the higher-level + C-style APIs do and the object-oriented APIs use exceptions + exclusively to report errors. + */ + class SQLite3Error extends Error { + /** + Constructs this object with a message depending on its arguments: + + - If it's passed only a single integer argument, it is assumed + to be an sqlite3 C API result code. The message becomes the + result of sqlite3.capi.sqlite3_js_rc_str() or (if that returns + falsy) a synthesized string which contains that integer. + + - If passed 2 arguments and the 2nd is a object, it bevaves + like the Error(string,object) constructor except that the first + argument is subject to the is-integer semantics from the + previous point. + + - Else all arguments are concatenated with a space between each + one, using args.join(' '), to create the error message. + */ + constructor(...args){ + if(1===args.length && __isInt(args[0])){ + super(__rcStr(args[0])); + }else if(2===args.length && 'object'===typeof args){ + if(__isInt(args[0])) super(__rcStr(args[0]), args[1]); + else super(...args); + }else{ + super(args.join(' ')); + } + this.name = 'SQLite3Error'; + } + }; + + /** + Functionally equivalent to the SQLite3Error constructor but may + be used as part of an expression, e.g.: + + ``` + return someFunction(x) || SQLite3Error.toss(...); + ``` + */ + SQLite3Error.toss = (...args)=>{ + throw new SQLite3Error(...args); + }; + const toss3 = SQLite3Error.toss; + + if(config.wasmfsOpfsDir && !/^\/[^/]+$/.test(config.wasmfsOpfsDir)){ + toss3("config.wasmfsOpfsDir must be falsy or in the form '/dir-name'."); + } + + /** + Returns true if n is a 32-bit (signed) integer, else + false. This is used for determining when we need to switch to + double-type DB operations for integer values in order to keep + more precision. + */ + const isInt32 = (n)=>{ + return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/) + && !!(n===(n|0) && n<=2147483647 && n>=-2147483648); + }; + /** + Returns true if the given BigInt value is small enough to fit + into an int64 value, else false. + */ + const bigIntFits64 = function f(b){ + if(!f._max){ + f._max = BigInt("0x7fffffffffffffff"); + f._min = ~f._max; + } + return b >= f._min && b <= f._max; + }; + + /** + Returns true if the given BigInt value is small enough to fit + into an int32, else false. + */ + const bigIntFits32 = (b)=>(b >= (-0x7fffffffn - 1n) && b <= 0x7fffffffn); + + /** + Returns true if the given BigInt value is small enough to fit + into a double value without loss of precision, else false. + */ + const bigIntFitsDouble = function f(b){ + if(!f._min){ + f._min = Number.MIN_SAFE_INTEGER; + f._max = Number.MAX_SAFE_INTEGER; + } + return b >= f._min && b <= f._max; + }; + + /** Returns v if v appears to be a TypedArray, else false. */ + const isTypedArray = (v)=>{ + return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false; + }; + + + /** Internal helper to use in operations which need to distinguish + between TypedArrays which are backed by a SharedArrayBuffer + from those which are not. */ + const __SAB = ('undefined'===typeof SharedArrayBuffer) + ? function(){} : SharedArrayBuffer; + /** Returns true if the given TypedArray object is backed by a + SharedArrayBuffer, else false. */ + const isSharedTypedArray = (aTypedArray)=>(aTypedArray.buffer instanceof __SAB); + + /** + Returns either aTypedArray.slice(begin,end) (if + aTypedArray.buffer is a SharedArrayBuffer) or + aTypedArray.subarray(begin,end) (if it's not). + + This distinction is important for APIs which don't like to + work on SABs, e.g. TextDecoder, and possibly for our + own APIs which work on memory ranges which "might" be + modified by other threads while they're working. + */ + const typedArrayPart = (aTypedArray, begin, end)=>{ + return isSharedTypedArray(aTypedArray) + ? aTypedArray.slice(begin, end) + : aTypedArray.subarray(begin, end); + }; + + /** + Returns true if v appears to be one of our bind()-able + TypedArray types: Uint8Array or Int8Array. Support for + TypedArrays with element sizes >1 is TODO. + */ + const isBindableTypedArray = (v)=>{ + return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); + }; + + /** + Returns true if v appears to be one of the TypedArray types + which is legal for holding SQL code (as opposed to binary blobs). + + Currently this is the same as isBindableTypedArray() but it + seems likely that we'll eventually want to add Uint32Array + and friends to the isBindableTypedArray() list but not to the + isSQLableTypedArray() list. + */ + const isSQLableTypedArray = (v)=>{ + return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); + }; + + /** Returns true if isBindableTypedArray(v) does, else throws with a message + that v is not a supported TypedArray value. */ + const affirmBindableTypedArray = (v)=>{ + return isBindableTypedArray(v) + || toss3("Value is not of a supported TypedArray type."); + }; + + const utf8Decoder = new TextDecoder('utf-8'); + + /** + Uses TextDecoder to decode the given half-open range of the + given TypedArray to a string. This differs from a simple + call to TextDecoder in that it accounts for whether the + first argument is backed by a SharedArrayBuffer or not, + and can work more efficiently if it's not (TextDecoder + refuses to act upon an SAB). + */ + const typedArrayToString = function(typedArray, begin, end){ + return utf8Decoder.decode(typedArrayPart(typedArray, begin,end)); + }; + + /** + If v is-a Array, its join("") result is returned. If + isSQLableTypedArray(v) is true then typedArrayToString(v) is + returned. If it looks like a WASM pointer, wasm.cstringToJs(v) is + returned. Else v is returned as-is. + */ + const flexibleString = function(v){ + if(isSQLableTypedArray(v)) return typedArrayToString(v); + else if(Array.isArray(v)) return v.join(""); + else if(wasm.isPtr(v)) v = wasm.cstringToJs(v); + return v; + }; + + /** + An Error subclass specifically for reporting Wasm-level malloc() + failure and enabling clients to unambiguously identify such + exceptions. + */ + class WasmAllocError extends Error { + /** + If called with 2 arguments and the 2nd one is an object, it + behaves like the Error constructor, else it concatenates all + arguments together with a single space between each to + construct an error message string. As a special case, if + called with no arguments then it uses a default error + message. + */ + constructor(...args){ + if(2===args.length && 'object'===typeof args){ + super(...args); + }else if(args.length){ + super(args.join(' ')); + }else{ + super("Allocation failed."); + } + this.name = 'WasmAllocError'; + } + }; + /** + Functionally equivalent to the WasmAllocError constructor but may + be used as part of an expression, e.g.: + + ``` + return someAllocatingFunction(x) || WasmAllocError.toss(...); + ``` + */ + WasmAllocError.toss = (...args)=>{ + throw new WasmAllocError(...args); + }; + + Object.assign(capi, { + /** + sqlite3_create_function_v2() differs from its native + counterpart only in the following ways: + + 1) The fourth argument (`eTextRep`) argument must not specify + any encoding other than sqlite3.SQLITE_UTF8. The JS API does not + currently support any other encoding and likely never + will. This function does not replace that argument on its own + because it may contain other flags. + + 2) Any of the four final arguments may be either WASM pointers + (assumed to be function pointers) or JS Functions. In the + latter case, each gets bound to WASM using + sqlite3.capi.wasm.installFunction() and that wrapper is passed + on to the native implementation. + + The semantics of JS functions are: + + xFunc: is passed `(pCtx, ...values)`. Its return value becomes + the new SQL function's result. + + xStep: is passed `(pCtx, ...values)`. Its return value is + ignored. + + xFinal: is passed `(pCtx)`. Its return value becomes the new + aggregate SQL function's result. + + xDestroy: is passed `(void*)`. Its return value is ignored. The + pointer passed to it is the one from the 5th argument to + sqlite3_create_function_v2(). + + Note that: + + - `pCtx` in the above descriptions is a `sqlite3_context*`. At + least 99 times out of a hundred, that initial argument will + be irrelevant for JS UDF bindings, but it needs to be there + so that the cases where it _is_ relevant, in particular with + window and aggregate functions, have full access to the + lower-level sqlite3 APIs. + + - When wrapping JS functions, the remaining arguments are passd + to them as positional arguments, not as an array of + arguments, because that allows callback definitions to be + more JS-idiomatic than C-like. For example `(pCtx,a,b)=>a+b` + is more intuitive and legible than + `(pCtx,args)=>args[0]+args[1]`. For cases where an array of + arguments would be more convenient, the callbacks simply need + to be declared like `(pCtx,...args)=>{...}`, in which case + `args` will be an array. + + - If a JS wrapper throws, it gets translated to + sqlite3_result_error() or sqlite3_result_error_nomem(), + depending on whether the exception is an + sqlite3.WasmAllocError object or not. + + - When passing on WASM function pointers, arguments are _not_ + converted or reformulated. They are passed on as-is in raw + pointer form using their native C signatures. Only JS + functions passed in to this routine, and thus wrapped by this + routine, get automatic conversions of arguments and result + values. The routines which perform those conversions are + exposed for client-side use as + sqlite3_create_function_v2.convertUdfArgs() and + sqlite3_create_function_v2.setUdfResult(). sqlite3_create_function() + and sqlite3_create_window_function() have those same methods. + + For xFunc(), xStep(), and xFinal(): + + - When called from SQL, arguments to the UDF, and its result, + will be converted between JS and SQL with as much fidelity as + is feasible, triggering an exception if a type conversion + cannot be determined. Some freedom is afforded to numeric + conversions due to friction between the JS and C worlds: + integers which are larger than 32 bits may be treated as + doubles or BigInts. + + If any JS-side bound functions throw, those exceptions are + intercepted and converted to database-side errors with the + exception of xDestroy(): any exception from it is ignored, + possibly generating a console.error() message. Destructors + must not throw. + + Once installed, there is currently no way to uninstall the + automatically-converted WASM-bound JS functions from WASM. They + can be uninstalled from the database as documented in the C + API, but this wrapper currently has no infrastructure in place + to also free the WASM-bound JS wrappers, effectively resulting + in a memory leak if the client uninstalls the UDF. Improving that + is a potential TODO, but removing client-installed UDFs is rare + in practice. If this factor is relevant for a given client, + they can create WASM-bound JS functions themselves, hold on to their + pointers, and pass the pointers in to here. Later on, they can + free those pointers (using `wasm.uninstallFunction()` or + equivalent). + + C reference: https://www.sqlite.org/c3ref/create_function.html + + Maintenance reminder: the ability to add new + WASM-accessible functions to the runtime requires that the + WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH` + flag. + */ + sqlite3_create_function_v2: function( + pDb, funcName, nArg, eTextRep, pApp, + xFunc, xStep, xFinal, xDestroy + ){/*installed later*/}, + /** + Equivalent to passing the same arguments to + sqlite3_create_function_v2(), with 0 as the final argument. + */ + sqlite3_create_function:function( + pDb, funcName, nArg, eTextRep, pApp, + xFunc, xStep, xFinal + ){/*installed later*/}, + /** + The sqlite3_create_window_function() JS wrapper differs from + its native implementation in the exact same way that + sqlite3_create_function_v2() does. The additional function, + xInverse(), is treated identically to xStep() by the wrapping + layer. + */ + sqlite3_create_window_function: function( + pDb, funcName, nArg, eTextRep, pApp, + xStep, xFinal, xValue, xInverse, xDestroy + ){/*installed later*/}, + /** + The sqlite3_prepare_v3() binding handles two different uses + with differing JS/WASM semantics: + + 1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt , null) + + 2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer) + + Note that the SQL length argument (the 3rd argument) must, for + usage (1), always be negative because it must be a byte length + and that value is expensive to calculate from JS (where only + the character length of strings is readily available). It is + retained in this API's interface for code/documentation + compatibility reasons but is currently _always_ ignored. With + usage (2), the 3rd argument is used as-is but is is still + critical that the C-style input string (2nd argument) be + terminated with a 0 byte. + + In usage (1), the 2nd argument must be of type string, + Uint8Array, or Int8Array (either of which is assumed to + hold SQL). If it is, this function assumes case (1) and + calls the underyling C function with the equivalent of: + + (pDb, sqlAsString, -1, prepFlags, ppStmt, null) + + The `pzTail` argument is ignored in this case because its + result is meaningless when a string-type value is passed + through: the string goes through another level of internal + conversion for WASM's sake and the result pointer would refer + to that transient conversion's memory, not the passed-in + string. + + If the sql argument is not a string, it must be a _pointer_ to + a NUL-terminated string which was allocated in the WASM memory + (e.g. using capi.wasm.alloc() or equivalent). In that case, + the final argument may be 0/null/undefined or must be a pointer + to which the "tail" of the compiled SQL is written, as + documented for the C-side sqlite3_prepare_v3(). In case (2), + the underlying C function is called with the equivalent of: + + (pDb, sqlAsPointer, sqlByteLen, prepFlags, ppStmt, pzTail) + + It returns its result and compiled statement as documented in + the C API. Fetching the output pointers (5th and 6th + parameters) requires using `capi.wasm.getMemValue()` (or + equivalent) and the `pzTail` will point to an address relative to + the `sqlAsPointer` value. + + If passed an invalid 2nd argument type, this function will + return SQLITE_MISUSE and sqlite3_errmsg() will contain a string + describing the problem. + + Side-note: if given an empty string, or one which contains only + comments or an empty SQL expression, 0 is returned but the result + output pointer will be NULL. + */ + sqlite3_prepare_v3: (dbPtr, sql, sqlByteLen, prepFlags, + stmtPtrPtr, strPtrPtr)=>{}/*installed later*/, + + /** + Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument. + */ + sqlite3_prepare_v2: (dbPtr, sql, sqlByteLen, + stmtPtrPtr,strPtrPtr)=>{}/*installed later*/, + + /** + This binding enables the callback argument to be a JavaScript. + + If the callback is a function, then for the duration of the + sqlite3_exec() call, it installs a WASM-bound function which + acts as a proxy for the given callback. That proxy will also + perform a conversion of the callback's arguments from + `(char**)` to JS arrays of strings. However, for API + consistency's sake it will still honor the C-level callback + parameter order and will call it like: + + `callback(pVoid, colCount, listOfValues, listOfColNames)` + + If the callback is not a JS function then this binding performs + no translation of the callback, but the sql argument is still + converted to a WASM string for the call using the + "flexible-string" argument converter. + */ + sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, + + /** + If passed a single argument which appears to be a byte-oriented + TypedArray (Int8Array or Uint8Array), this function treats that + TypedArray as an output target, fetches `theArray.byteLength` + bytes of randomness, and populates the whole array with it. As + a special case, if the array's length is 0, this function + behaves as if it were passed (0,0). When called this way, it + returns its argument, else it returns the `undefined` value. + + If called with any other arguments, they are passed on as-is + to the C API. Results are undefined if passed any incompatible + values. + */ + sqlite3_randomness: (n, outPtr)=>{/*installed later*/}, + }/*capi*/); + + /** + Various internal-use utilities are added here as needed. They + are bound to an object only so that we have access to them in + the differently-scoped steps of the API bootstrapping + process. At the end of the API setup process, this object gets + removed. These are NOT part of the public API. + */ + const util = { + affirmBindableTypedArray, flexibleString, + bigIntFits32, bigIntFits64, bigIntFitsDouble, + isBindableTypedArray, + isInt32, isSQLableTypedArray, isTypedArray, + typedArrayToString, + isUIThread: ()=>'undefined'===typeof WorkerGlobalScope, + isSharedTypedArray, + typedArrayPart + }; + + Object.assign(wasm, { + /** + Emscripten APIs have a deep-seated assumption that all pointers + are 32 bits. We'll remain optimistic that that won't always be + the case and will use this constant in places where we might + otherwise use a hard-coded 4. + */ + ptrSizeof: config.wasmPtrSizeof || 4, + /** + The WASM IR (Intermediate Representation) value for + pointer-type values. It MUST refer to a value type of the + size described by this.ptrSizeof _or_ it may be any value + which ends in '*', which Emscripten's glue code internally + translates to i32. + */ + ptrIR: config.wasmPtrIR || "i32", + /** + True if BigInt support was enabled via (e.g.) the + Emscripten -sWASM_BIGINT flag, else false. When + enabled, certain 64-bit sqlite3 APIs are enabled which + are not otherwise enabled due to JS/WASM int64 + impedence mismatches. + */ + bigIntEnabled: !!config.bigIntEnabled, + /** + The symbols exported by the WASM environment. + */ + exports: config.exports + || toss3("Missing API config.exports (WASM module exports)."), + + /** + When Emscripten compiles with `-sIMPORT_MEMORY`, it + initalizes the heap and imports it into wasm, as opposed to + the other way around. In this case, the memory is not + available via this.exports.memory. + */ + memory: config.memory || config.exports['memory'] + || toss3("API config object requires a WebAssembly.Memory object", + "in either config.exports.memory (exported)", + "or config.memory (imported)."), + + /** + The API's one single point of access to the WASM-side memory + allocator. Works like malloc(3) (and is likely bound to + malloc()) but throws an WasmAllocError if allocation fails. It is + important that any code which might pass through the sqlite3 C + API NOT throw and must instead return SQLITE_NOMEM (or + equivalent, depending on the context). + + Very few cases in the sqlite3 JS APIs can result in + client-defined functions propagating exceptions via the C-style + API. Most notably, this applies to WASM-bound JS functions + which are created directly by clients and passed on _as WASM + function pointers_ to functions such as + sqlite3_create_function_v2(). Such bindings created + transparently by this API will automatically use wrappers which + catch exceptions and convert them to appropriate error codes. + + For cases where non-throwing allocation is required, use + sqlite3.wasm.alloc.impl(), which is direct binding of the + underlying C-level allocator. + + Design note: this function is not named "malloc" primarily + because Emscripten uses that name and we wanted to avoid any + confusion early on in this code's development, when it still + had close ties to Emscripten's glue code. + */ + alloc: undefined/*installed later*/, + + /** + The API's one single point of access to the WASM-side memory + deallocator. Works like free(3) (and is likely bound to + free()). + + Design note: this function is not named "free" for the same + reason that this.alloc() is not called this.malloc(). + */ + dealloc: undefined/*installed later*/ + + /* Many more wasm-related APIs get installed later on. */ + }/*wasm*/); + + /** + wasm.alloc()'s srcTypedArray.byteLength bytes, + populates them with the values from the source + TypedArray, and returns the pointer to that memory. The + returned pointer must eventually be passed to + wasm.dealloc() to clean it up. + + As a special case, to avoid further special cases where + this is used, if srcTypedArray.byteLength is 0, it + allocates a single byte and sets it to the value + 0. Even in such cases, calls must behave as if the + allocated memory has exactly srcTypedArray.byteLength + bytes. + + ACHTUNG: this currently only works for Uint8Array and + Int8Array types and will throw if srcTypedArray is of + any other type. + */ + wasm.allocFromTypedArray = function(srcTypedArray){ + affirmBindableTypedArray(srcTypedArray); + const pRet = wasm.alloc(srcTypedArray.byteLength || 1); + wasm.heapForSize(srcTypedArray.constructor).set( + srcTypedArray.byteLength ? srcTypedArray : [0], pRet + ); + return pRet; + }; + + const keyAlloc = config.allocExportName || 'malloc', + keyDealloc = config.deallocExportName || 'free'; + for(const key of [keyAlloc, keyDealloc]){ + const f = wasm.exports[key]; + if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function."); + } + + wasm.alloc = function f(n){ + const m = f.impl(n); + if(!m) throw new WasmAllocError("Failed to allocate",n," bytes."); + return m; + }; + wasm.alloc.impl = wasm.exports[keyAlloc]; + wasm.dealloc = wasm.exports[keyDealloc]; + + /** + Reports info about compile-time options using + sqlite_compileoption_get() and sqlite3_compileoption_used(). It + has several distinct uses: + + If optName is an array then it is expected to be a list of + compilation options and this function returns an object + which maps each such option to true or false, indicating + whether or not the given option was included in this + build. That object is returned. + + If optName is an object, its keys are expected to be compilation + options and this function sets each entry to true or false, + indicating whether the compilation option was used or not. That + object is returned. + + If passed no arguments then it returns an object mapping + all known compilation options to their compile-time values, + or boolean true if they are defined with no value. This + result, which is relatively expensive to compute, is cached + and returned for future no-argument calls. + + In all other cases it returns true if the given option was + active when when compiling the sqlite3 module, else false. + + Compile-time option names may optionally include their + "SQLITE_" prefix. When it returns an object of all options, + the prefix is elided. + */ + wasm.compileOptionUsed = function f(optName){ + if(!arguments.length){ + if(f._result) return f._result; + else if(!f._opt){ + f._rx = /^([^=]+)=(.+)/; + f._rxInt = /^-?\d+$/; + f._opt = function(opt, rv){ + const m = f._rx.exec(opt); + rv[0] = (m ? m[1] : opt); + rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; + }; + } + const rc = {}, ov = [0,0]; + let i = 0, k; + while((k = capi.sqlite3_compileoption_get(i++))){ + f._opt(k,ov); + rc[ov[0]] = ov[1]; + } + return f._result = rc; + }else if(Array.isArray(optName)){ + const rc = {}; + optName.forEach((v)=>{ + rc[v] = capi.sqlite3_compileoption_used(v); + }); + return rc; + }else if('object' === typeof optName){ + Object.keys(optName).forEach((k)=> { + optName[k] = capi.sqlite3_compileoption_used(k); + }); + return optName; + } + return ( + 'string'===typeof optName + ) ? !!capi.sqlite3_compileoption_used(optName) : false; + }/*compileOptionUsed()*/; + + /** + Signatures for the WASM-exported C-side functions. Each entry + is an array with 2+ elements: + + [ "c-side name", + "result type" (wasm.xWrap() syntax), + [arg types in xWrap() syntax] + // ^^^ this needn't strictly be an array: it can be subsequent + // elements instead: [x,y,z] is equivalent to x,y,z + ] + + Note that support for the API-specific data types in the + result/argument type strings gets plugged in at a later phase in + the API initialization process. + */ + wasm.bindingSignatures = [ + // Please keep these sorted by function name! + ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], + ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*" + /* TODO: we should arguably write a custom wrapper which knows + how to handle Blob, TypedArrays, and JS strings. */ + ], + ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], + ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"], + ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], + ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], + ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], + ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int" + /* We should arguably create a hand-written binding of + bind_text() which does more flexible text conversion, along + the lines of sqlite3_prepare_v3(). The slightly problematic + part is the final argument (text destructor). */ + ], + ["sqlite3_close_v2", "int", "sqlite3*"], + ["sqlite3_changes", "int", "sqlite3*"], + ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], + ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], + ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], + ["sqlite3_column_count", "int", "sqlite3_stmt*"], + ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"], + ["sqlite3_column_int","int", "sqlite3_stmt*", "int"], + ["sqlite3_column_name","string", "sqlite3_stmt*", "int"], + ["sqlite3_column_text","string", "sqlite3_stmt*", "int"], + ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], + ["sqlite3_compileoption_get", "string", "int"], + ["sqlite3_compileoption_used", "int", "string"], + /* sqlite3_create_function(), sqlite3_create_function_v2(), and + sqlite3_create_window_function() use hand-written bindings to + simplify handling of their function-type arguments. */ + ["sqlite3_data_count", "int", "sqlite3_stmt*"], + ["sqlite3_db_filename", "string", "sqlite3*", "string"], + ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], + ["sqlite3_db_name", "string", "sqlite3*", "int"], + ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"] + /* Careful! Short version: de/serialize() are problematic because they + might use a different allocator than the user for managing the + deserialized block. de/serialize() are ONLY safe to use with + sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, + ["sqlite3_errmsg", "string", "sqlite3*"], + ["sqlite3_error_offset", "int", "sqlite3*"], + ["sqlite3_errstr", "string", "int"], + /*["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**" + Handled seperately to perform translation of the callback + into a WASM-usable one. ],*/ + ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], + ["sqlite3_extended_errcode", "int", "sqlite3*"], + ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], + ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], + ["sqlite3_finalize", "int", "sqlite3_stmt*"], + ["sqlite3_free", undefined,"*"], + ["sqlite3_initialize", undefined], + /*["sqlite3_interrupt", undefined, "sqlite3*" + ^^^ we cannot actually currently support this because JS is + single-threaded and we don't have a portable way to access a DB + from 2 SharedWorkers concurrently. ],*/ + ["sqlite3_libversion", "string"], + ["sqlite3_libversion_number", "int"], + ["sqlite3_malloc", "*","int"], + ["sqlite3_open", "int", "string", "*"], + ["sqlite3_open_v2", "int", "string", "*", "int", "string"], + /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled + separately due to us requiring two different sets of semantics + for those, depending on how their SQL argument is provided. */ + /* sqlite3_randomness() uses a hand-written wrapper to extend + the range of supported argument types. */ + ["sqlite3_realloc", "*","*","int"], + ["sqlite3_reset", "int", "sqlite3_stmt*"], + ["sqlite3_result_blob",undefined, "*", "*", "int", "*"], + ["sqlite3_result_double",undefined, "*", "f64"], + ["sqlite3_result_error",undefined, "*", "string", "int"], + ["sqlite3_result_error_code", undefined, "*", "int"], + ["sqlite3_result_error_nomem", undefined, "*"], + ["sqlite3_result_error_toobig", undefined, "*"], + ["sqlite3_result_int",undefined, "*", "int"], + ["sqlite3_result_null",undefined, "*"], + ["sqlite3_result_text",undefined, "*", "string", "int", "*"], + ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], + ["sqlite3_shutdown", undefined], + ["sqlite3_sourceid", "string"], + ["sqlite3_sql", "string", "sqlite3_stmt*"], + ["sqlite3_step", "int", "sqlite3_stmt*"], + ["sqlite3_strglob", "int", "string","string"], + ["sqlite3_strlike", "int", "string","string","int"], + ["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"], + ["sqlite3_total_changes", "int", "sqlite3*"], + ["sqlite3_uri_boolean", "int", "string", "string", "int"], + ["sqlite3_uri_key", "string", "string", "int"], + ["sqlite3_uri_parameter", "string", "string", "string"], + ["sqlite3_user_data","void*", "sqlite3_context*"], + ["sqlite3_value_blob", "*", "sqlite3_value*"], + ["sqlite3_value_bytes","int", "sqlite3_value*"], + ["sqlite3_value_double","f64", "sqlite3_value*"], + ["sqlite3_value_int","int", "sqlite3_value*"], + ["sqlite3_value_text", "string", "sqlite3_value*"], + ["sqlite3_value_type", "int", "sqlite3_value*"], + ["sqlite3_vfs_find", "*", "string"], + ["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"], + ["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"] + ]/*wasm.bindingSignatures*/; + + if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ + /* ^^^ "the problem" is that this is an option feature and the + build-time function-export list does not currently take + optional features into account. */ + wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); + } + + /** + Functions which require BigInt (int64) support are separated from + the others because we need to conditionally bind them or apply + dummy impls, depending on the capabilities of the environment. + */ + wasm.bindingSignatures.int64 = [ + ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], + ["sqlite3_changes64","i64", ["sqlite3*"]], + ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], + ["sqlite3_malloc64", "*","i64"], + ["sqlite3_msize", "i64", "*"], + ["sqlite3_realloc64", "*","*", "i64"], + ["sqlite3_result_int64",undefined, "*", "i64"], + ["sqlite3_total_changes64", "i64", ["sqlite3*"]], + ["sqlite3_uri_int64", "i64", ["string", "string", "i64"]], + ["sqlite3_value_int64","i64", "sqlite3_value*"], + ]; + + /** + Functions which are intended solely for API-internal use by the + WASM components, not client code. These get installed into + sqlite3.wasm. + */ + wasm.bindingSignatures.wasm = [ + ["sqlite3_wasm_db_reset", "int", "sqlite3*"], + ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], + ["sqlite3_wasm_vfs_create_file", "int", + "sqlite3_vfs*","string","*", "int"], + ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] + ]; + + + /** + sqlite3.wasm.pstack (pseudo-stack) holds a special-case + stack-style allocator intended only for use with _small_ data of + not more than (in total) a few kb in size, managed as if it were + stack-based. + + It has only a single intended usage: + + ``` + const stackPos = pstack.pointer; + try{ + const ptr = pstack.alloc(8); + // ==> pstack.pointer === ptr + const otherPtr = pstack.alloc(8); + // ==> pstack.pointer === otherPtr + ... + }finally{ + pstack.restore(stackPos); + // ==> pstack.pointer === stackPos + } + ``` + + This allocator is much faster than a general-purpose one but is + limited to usage patterns like the one shown above. + + It operates from a static range of memory which lives outside of + space managed by Emscripten's stack-management, so does not + collide with Emscripten-provided stack allocation APIs. The + memory lives in the WASM heap and can be used with routines such + as wasm.setMemValue() and any wasm.heap8u().slice(). + */ + wasm.pstack = Object.assign(Object.create(null),{ + /** + Sets the current pstack position to the given pointer. Results + are undefined if the passed-in value did not come from + this.pointer. + */ + restore: wasm.exports.sqlite3_wasm_pstack_restore, + /** + Attempts to allocate the given number of bytes from the + pstack. On success, it zeroes out a block of memory of the + given size, adjusts the pstack pointer, and returns a pointer + to the memory. On error, returns throws a WasmAllocError. The + memory must eventually be released using restore(). + + This method always adjusts the given value to be a multiple + of 8 bytes because failing to do so can lead to incorrect + results when reading and writing 64-bit values from/to the WASM + heap. Similarly, the returned address is always 8-byte aligned. + */ + alloc: (n)=>{ + return wasm.exports.sqlite3_wasm_pstack_alloc(n) + || WasmAllocError.toss("Could not allocate",n, + "bytes from the pstack."); + }, + /** + alloc()'s n chunks, each sz bytes, as a single memory block and + returns the addresses as an array of n element, each holding + the address of one chunk. + + Throws a WasmAllocError if allocation fails. + + Example: + + ``` + const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); + ``` + */ + allocChunks: (n,sz)=>{ + const mem = wasm.pstack.alloc(n * sz); + const rc = []; + let i = 0, offset = 0; + for(; i < n; offset = (sz * ++i)){ + rc.push(mem + offset); + } + return rc; + }, + /** + A convenience wrapper for allocChunks() which sizes each chunk + as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if + safePtrSize is falsy). + + How it returns its result differs depending on its first + argument: if it's 1, it returns a single pointer value. If it's + more than 1, it returns the same as allocChunks(). + + When a returned pointers will refer to a 64-bit value, e.g. a + double or int64, and that value must be written or fetched, + e.g. using wasm.setMemValue() or wasm.getMemValue(), it is + important that the pointer in question be aligned to an 8-byte + boundary or else it will not be fetched or written properly and + will corrupt or read neighboring memory. + + However, when all pointers involved point to "small" data, it + is safe to pass a falsy value to save a tiny bit of memory. + */ + allocPtr: (n=1,safePtrSize=true)=>{ + return 1===n + ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof) + : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof); + } + })/*wasm.pstack*/; + Object.defineProperties(wasm.pstack, { + /** + sqlite3.wasm.pstack.pointer resolves to the current pstack + position pointer. This value is intended _only_ to be saved + for passing to restore(). Writing to this memory, without + first reserving it via wasm.pstack.alloc() and friends, leads + to undefined results. + */ + pointer: { + configurable: false, iterable: true, writeable: false, + get: wasm.exports.sqlite3_wasm_pstack_ptr + //Whether or not a setter as an alternative to restore() is + //clearer or would just lead to confusion is unclear. + //set: wasm.exports.sqlite3_wasm_pstack_restore + }, + /** + sqlite3.wasm.pstack.quota to the total number of bytes + available in the pstack, including any space which is currently + allocated. This value is a compile-time constant. + */ + quota: { + configurable: false, iterable: true, writeable: false, + get: wasm.exports.sqlite3_wasm_pstack_quota + }, + /** + sqlite3.wasm.pstack.remaining resolves to the amount of space + remaining in the pstack. + */ + remaining: { + configurable: false, iterable: true, writeable: false, + get: wasm.exports.sqlite3_wasm_pstack_remaining + } + })/*wasm.pstack properties*/; + + capi.sqlite3_randomness = (...args)=>{ + if(1===args.length && util.isTypedArray(args[0]) + && 1===args[0].BYTES_PER_ELEMENT){ + const ta = args[0]; + if(0===ta.byteLength){ + wasm.exports.sqlite3_randomness(0,0); + return ta; + } + const stack = wasm.pstack.pointer; + try { + let n = ta.byteLength, offset = 0; + const r = wasm.exports.sqlite3_randomness; + const heap = wasm.heap8u(); + const nAlloc = n < 512 ? n : 512; + const ptr = wasm.pstack.alloc(nAlloc); + do{ + const j = (n>nAlloc ? nAlloc : n); + r(j, ptr); + ta.set(typedArrayPart(heap, ptr, ptr+j), offset); + n -= j; + offset += j; + } while(n > 0); + }catch(e){ + console.error("Highly unexpected (and ignored!) "+ + "exception in sqlite3_randomness():",e); + }finally{ + wasm.pstack.restore(stack); + } + return ta; + } + wasm.exports.sqlite3_randomness(...args); + }; + + /** State for sqlite3_wasmfs_opfs_dir(). */ + let __wasmfsOpfsDir = undefined; + /** + If the wasm environment has a WASMFS/OPFS-backed persistent + storage directory, its path is returned by this function. If it + does not then it returns "" (noting that "" is a falsy value). + + The first time this is called, this function inspects the current + environment to determine whether persistence support is available + and, if it is, enables it (if needed). + + This function currently only recognizes the WASMFS/OPFS storage + combination and its path refers to storage rooted in the + Emscripten-managed virtual filesystem. + */ + capi.sqlite3_wasmfs_opfs_dir = function(){ + if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; + // If we have no OPFS, there is no persistent dir + const pdir = config.wasmfsOpfsDir; + if(!pdir + || !self.FileSystemHandle + || !self.FileSystemDirectoryHandle + || !self.FileSystemFileHandle){ + return __wasmfsOpfsDir = ""; + } + try{ + if(pdir && 0===wasm.xCallWrapped( + 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir + )){ + return __wasmfsOpfsDir = pdir; + }else{ + return __wasmfsOpfsDir = ""; + } + }catch(e){ + // sqlite3_wasm_init_wasmfs() is not available + return __wasmfsOpfsDir = ""; + } + }; + + /** + Experimental and subject to change or removal. + + Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a + non-empty string and the given name starts with (that string + + '/'), else returns false. + */ + capi.sqlite3_wasmfs_filename_is_persistent = function(name){ + const p = capi.sqlite3_wasmfs_opfs_dir(); + return (p && name) ? name.startsWith(p+'/') : false; + }; + + // This bit is highly arguable and is incompatible with the fiddle shell. + if(false && 0===wasm.exports.sqlite3_vfs_find(0)){ + /* Assume that sqlite3_initialize() has not yet been called. + This will be the case in an SQLITE_OS_KV build. */ + wasm.exports.sqlite3_initialize(); + } + + /** + Given an `sqlite3*`, an sqlite3_vfs name, and an optional db name + (defaulting to "main"), returns a truthy value (see below) if + that db uses that VFS, else returns false. If pDb is falsy then + the 3rd argument is ignored and this function returns a truthy + value if the default VFS name matches that of the 2nd + argument. Results are undefined if pDb is truthy but refers to an + invalid pointer. The 3rd argument specifies the database name of + the given database connection to check, defaulting to the main + db. + + The 2nd and 3rd arguments may either be a JS string or a WASM + C-string. If the 2nd argument is a NULL WASM pointer, the default + VFS is assumed. If the 3rd is a NULL WASM pointer, "main" is + assumed. + + The truthy value it returns is a pointer to the `sqlite3_vfs` + object. + + To permit safe use of this function from APIs which may be called + via the C stack (like SQL UDFs), this function does not throw: if + bad arguments cause a conversion error when passing into + wasm-space, false is returned. + */ + capi.sqlite3_js_db_uses_vfs = function(pDb,vfsName,dbName=0){ + try{ + const pK = capi.sqlite3_vfs_find(vfsName); + if(!pK) return false; + else if(!pDb){ + return pK===capi.sqlite3_vfs_find(0) ? pK : false; + }else{ + return pK===capi.sqlite3_js_db_vfs(pDb,dbName) ? pK : false; + } + }catch(e){ + /* Ignore - probably bad args to a wasm-bound function. */ + return false; + } + }; + + /** + Returns an array of the names of all currently-registered sqlite3 + VFSes. + */ + capi.sqlite3_js_vfs_list = function(){ + const rc = []; + let pVfs = capi.sqlite3_vfs_find(0); + while(pVfs){ + const oVfs = new capi.sqlite3_vfs(pVfs); + rc.push(wasm.cstringToJs(oVfs.$zName)); + pVfs = oVfs.$pNext; + oVfs.dispose(); + } + return rc; + }; + + /** + Serializes the given `sqlite3*` pointer to a Uint8Array, as per + sqlite3_serialize(). On success it returns a Uint8Array. On + error it throws with a description of the problem. + */ + capi.sqlite3_js_db_export = function(pDb){ + if(!pDb) toss3('Invalid sqlite3* argument.'); + if(!wasm.bigIntEnabled) toss3('BigInt64 support is not enabled.'); + const stack = wasm.pstack.pointer; + let pOut; + try{ + const pSize = wasm.pstack.alloc(8/*i64*/ + wasm.ptrSizeof); + const ppOut = pSize + 8; + /** + Maintenance reminder, since this cost a full hour of grief + and confusion: if the order of pSize/ppOut are reversed in + that memory block, fetching the value of pSize after the + export reads a garbage size because it's not on an 8-byte + memory boundary! + */ + let rc = wasm.exports.sqlite3_wasm_db_serialize( + pDb, ppOut, pSize, 0 + ); + if(rc){ + toss3("Database serialization failed with code", + sqlite3.capi.sqlite3_js_rc_str(rc)); + } + pOut = wasm.getPtrValue(ppOut); + const nOut = wasm.getMemValue(pSize, 'i64'); + rc = nOut + ? wasm.heap8u().slice(pOut, pOut + Number(nOut)) + : new Uint8Array(); + return rc; + }finally{ + if(pOut) wasm.exports.sqlite3_free(pOut); + wasm.pstack.restore(stack); + } + }; + + /** + Given a `sqlite3*` and a database name (JS string or WASM + C-string pointer, which may be 0), returns a pointer to the + sqlite3_vfs responsible for it. If the given db name is null/0, + or not provided, then "main" is assumed. + */ + capi.sqlite3_js_db_vfs = + (dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName); + + /** + A thin wrapper around capi.sqlite3_aggregate_context() which + behaves the same except that it throws a WasmAllocError if that + function returns 0. As a special case, if n is falsy it does + _not_ throw if that function returns 0. That special case is + intended for use with xFinal() implementations. + */ + capi.sqlite3_js_aggregate_context = (pCtx, n)=>{ + return capi.sqlite3_aggregate_context(pCtx, n) + || (n ? WasmAllocError.toss("Cannot allocate",n, + "bytes for sqlite3_aggregate_context()") + : 0); + }; + + if( util.isUIThread() ){ + /* Features specific to the main window thread... */ + + /** + Internal helper for sqlite3_js_kvvfs_clear() and friends. + Its argument should be one of ('local','session',""). + */ + const __kvvfsInfo = function(which){ + const rc = Object.create(null); + rc.prefix = 'kvvfs-'+which; + rc.stores = []; + if('session'===which || ""===which) rc.stores.push(self.sessionStorage); + if('local'===which || ""===which) rc.stores.push(self.localStorage); + return rc; + }; + + /** + Clears all storage used by the kvvfs DB backend, deleting any + DB(s) stored there. Its argument must be either 'session', + 'local', or "". In the first two cases, only sessionStorage + resp. localStorage is cleared. If it's an empty string (the + default) then both are cleared. Only storage keys which match + the pattern used by kvvfs are cleared: any other client-side + data are retained. + + This function is only available in the main window thread. + + Returns the number of entries cleared. + */ + capi.sqlite3_js_kvvfs_clear = function(which=""){ + let rc = 0; + const kvinfo = __kvvfsInfo(which); + kvinfo.stores.forEach((s)=>{ + const toRm = [] /* keys to remove */; + let i; + for( i = 0; i < s.length; ++i ){ + const k = s.key(i); + if(k.startsWith(kvinfo.prefix)) toRm.push(k); + } + toRm.forEach((kk)=>s.removeItem(kk)); + rc += toRm.length; + }); + return rc; + }; + + /** + This routine guesses the approximate amount of + window.localStorage and/or window.sessionStorage in use by the + kvvfs database backend. Its argument must be one of + ('session', 'local', ""). In the first two cases, only + sessionStorage resp. localStorage is counted. If it's an empty + string (the default) then both are counted. Only storage keys + which match the pattern used by kvvfs are counted. The returned + value is the "length" value of every matching key and value, + noting that JavaScript stores each character in 2 bytes. + + Note that the returned size is not authoritative from the + perspective of how much data can fit into localStorage and + sessionStorage, as the precise algorithms for determining + those limits are unspecified and may include per-entry + overhead invisible to clients. + */ + capi.sqlite3_js_kvvfs_size = function(which=""){ + let sz = 0; + const kvinfo = __kvvfsInfo(which); + kvinfo.stores.forEach((s)=>{ + let i; + for(i = 0; i < s.length; ++i){ + const k = s.key(i); + if(k.startsWith(kvinfo.prefix)){ + sz += k.length; + sz += s.getItem(k).length; + } + } + }); + return sz * 2 /* because JS uses 2-byte char encoding */; + }; + + }/* main-window-only bits */ + + + /* The remainder of the API will be set up in later steps. */ + const sqlite3 = { + WasmAllocError: WasmAllocError, + SQLite3Error: SQLite3Error, + capi, + util, + wasm, + config, + /** + Holds the version info of the sqlite3 source tree from which + the generated sqlite3-api.js gets built. Note that its version + may well differ from that reported by sqlite3_libversion(), but + that should be considered a source file mismatch, as the JS and + WASM files are intended to be built and distributed together. + + This object is initially a placeholder which gets replaced by a + build-generated object. + */ + version: Object.create(null), + /** + Performs any optional asynchronous library-level initialization + which might be required. This function returns a Promise which + resolves to the sqlite3 namespace object. Any error in the + async init will be fatal to the init as a whole, but init + routines are themselves welcome to install dummy catch() + handlers which are not fatal if their failure should be + considered non-fatal. If called more than once, the second and + subsequent calls are no-ops which return a pre-resolved + Promise. + + Ideally this function is called as part of the Promise chain + which handles the loading and bootstrapping of the API. If not + then it must be called by client-level code, which must not use + the library until the returned promise resolves. + + Bug: if called while a prior call is still resolving, the 2nd + call will resolve prematurely, before the 1st call has finished + resolving. The current build setup precludes that possibility, + so it's only a hypothetical problem if/when this function + ever needs to be invoked by clients. + + In Emscripten-based builds, this function is called + automatically and deleted from this object. + */ + asyncPostInit: async function(){ + let lip = sqlite3ApiBootstrap.initializersAsync; + delete sqlite3ApiBootstrap.initializersAsync; + if(!lip || !lip.length) return Promise.resolve(sqlite3); + // Is it okay to resolve these in parallel or do we need them + // to resolve in order? We currently only have 1, so it + // makes no difference. + lip = lip.map((f)=>{ + const p = (f instanceof Promise) ? f : f(sqlite3); + return p.catch((e)=>{ + console.error("an async sqlite3 initializer failed:",e); + throw e; + }); + }); + //let p = lip.shift(); + //while(lip.length) p = p.then(lip.shift()); + //return p.then(()=>sqlite3); + return Promise.all(lip).then(()=>sqlite3); + }, + /** + scriptInfo ideally gets injected into this object by the + infrastructure which assembles the JS/WASM module. It contains + state which must be collected before sqlite3ApiBootstrap() can + be declared. It is not necessarily available to any + sqlite3ApiBootstrap.initializers but "should" be in place (if + it's added at all) by the time that + sqlite3ApiBootstrap.initializersAsync is processed. + + This state is not part of the public API, only intended for use + with the sqlite3 API bootstrapping and wasm-loading process. + */ + scriptInfo: undefined + }; + try{ + sqlite3ApiBootstrap.initializers.forEach((f)=>{ + f(sqlite3); + }); + }catch(e){ + /* If we don't report this here, it can get completely swallowed + up and disappear into the abyss of Promises and Workers. */ + console.error("sqlite3 bootstrap initializer threw:",e); + throw e; + } + delete sqlite3ApiBootstrap.initializers; + sqlite3ApiBootstrap.sqlite3 = sqlite3; + return sqlite3; +}/*sqlite3ApiBootstrap()*/; +/** + self.sqlite3ApiBootstrap.initializers is an internal detail used by + the various pieces of the sqlite3 API's amalgamation process. It + must not be modified by client code except when plugging such code + into the amalgamation process. + + Each component of the amalgamation is expected to append a function + to this array. When sqlite3ApiBootstrap() is called for the first + time, each such function will be called (in their appended order) + and passed the sqlite3 namespace object, into which they can install + their features (noting that most will also require that certain + features alread have been installed). At the end of that process, + this array is deleted. + + Note that the order of insertion into this array is significant for + some pieces. e.g. sqlite3.capi and sqlite3.wasm cannot be fully + utilized until the whwasmutil.js part is plugged in via + sqlite3-api-glue.js. +*/ +self.sqlite3ApiBootstrap.initializers = []; +/** + self.sqlite3ApiBootstrap.initializersAsync is an internal detail + used by the sqlite3 API's amalgamation process. It must not be + modified by client code except when plugging such code into the + amalgamation process. + + The counterpart of self.sqlite3ApiBootstrap.initializers, + specifically for initializers which are asynchronous. All entries in + this list must be either async functions, non-async functions which + return a Promise, or a Promise. Each function in the list is called + with the sqlite3 ojbect as its only argument. + + The resolved value of any Promise is ignored and rejection will kill + the asyncPostInit() process (at an indeterminate point because all + of them are run asynchronously in parallel). + + This list is not processed until the client calls + sqlite3.asyncPostInit(). This means, for example, that intializers + added to self.sqlite3ApiBootstrap.initializers may push entries to + this list. +*/ +self.sqlite3ApiBootstrap.initializersAsync = []; +/** + Client code may assign sqlite3ApiBootstrap.defaultConfig an + object-type value before calling sqlite3ApiBootstrap() (without + arguments) in order to tell that call to use this object as its + default config value. The intention of this is to provide + downstream clients with a reasonably flexible approach for plugging in + an environment-suitable configuration without having to define a new + global-scope symbol. +*/ +self.sqlite3ApiBootstrap.defaultConfig = Object.create(null); +/** + Placeholder: gets installed by the first call to + self.sqlite3ApiBootstrap(). However, it is recommended that the + caller of sqlite3ApiBootstrap() capture its return value and delete + self.sqlite3ApiBootstrap after calling it. It returns the same + value which will be stored here. +*/ +self.sqlite3ApiBootstrap.sqlite3 = undefined; + diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js new file mode 100644 index 000000000..62e2bb9bd --- /dev/null +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -0,0 +1,654 @@ +/* + 2022-07-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 implements the initializer for the sqlite3 "Worker API + #1", a very basic DB access API intended to be scripted from a main + window thread via Worker-style messages. Because of limitations in + that type of communication, this API is minimalistic and only + capable of serving relatively basic DB requests (e.g. it cannot + process nested query loops concurrently). + + This file requires that the core C-style sqlite3 API and OO API #1 + have been loaded. +*/ + +/** + sqlite3.initWorker1API() implements a Worker-based wrapper around + SQLite3 OO API #1, colloquially known as "Worker API #1". + + In order to permit this API to be loaded in worker threads without + automatically registering onmessage handlers, initializing the + worker API requires calling initWorker1API(). If this function is + called from a non-worker thread then it throws an exception. It + must only be called once per Worker. + + When initialized, it installs message listeners to receive Worker + messages and then it posts a message in the form: + + ``` + {type:'sqlite3-api', result:'worker1-ready'} + ``` + + to let the client know that it has been initialized. Clients may + optionally depend on this function not returning until + initialization is complete, as the initialization is synchronous. + In some contexts, however, listening for the above message is + a better fit. + + Note that the worker-based interface can be slightly quirky because + of its async nature. In particular, any number of messages may be posted + to the worker before it starts handling any of them. If, e.g., an + "open" operation fails, any subsequent messages will fail. The + Promise-based wrapper for this API (`sqlite3-worker1-promiser.js`) + is more comfortable to use in that regard. + + The documentation for the input and output worker messages for + this API follows... + + ==================================================================== + Common message format... + + Each message posted to the worker has an operation-independent + envelope and operation-dependent arguments: + + ``` + { + type: string, // one of: 'open', 'close', 'exec', 'config-get' + + messageId: OPTIONAL arbitrary value. The worker will copy it as-is + into response messages to assist in client-side dispatching. + + dbId: a db identifier string (returned by 'open') which tells the + operation which database instance to work on. If not provided, the + first-opened db is used. This is an "opaque" value, with no + inherently useful syntax or information. Its value is subject to + change with any given build of this API and cannot be used as a + basis for anything useful beyond its one intended purpose. + + args: ...operation-dependent arguments... + + // the framework may add other properties for testing or debugging + // purposes. + + } + ``` + + Response messages, posted back to the main thread, look like: + + ``` + { + type: string. Same as above except for error responses, which have the type + 'error', + + messageId: same value, if any, provided by the inbound message + + dbId: the id of the db which was operated on, if any, as returned + by the corresponding 'open' operation. + + result: ...operation-dependent result... + + } + ``` + + ==================================================================== + Error responses + + Errors are reported messages in an operation-independent format: + + ``` + { + type: "error", + + messageId: ...as above..., + + dbId: ...as above... + + result: { + + operation: type of the triggering operation: 'open', 'close', ... + + message: ...error message text... + + errorClass: string. The ErrorClass.name property from the thrown exception. + + input: the message object which triggered the error. + + stack: _if available_, a stack trace array. + + } + + } + ``` + + + ==================================================================== + "config-get" + + This operation fetches the serializable parts of the sqlite3 API + configuration. + + Message format: + + ``` + { + type: "config-get", + messageId: ...as above..., + args: currently ignored and may be elided. + } + ``` + + Response: + + ``` + { + type: "config-get", + messageId: ...as above..., + result: { + + version: sqlite3.version object + + bigIntEnabled: bool. True if BigInt support is enabled. + + wasmfsOpfsDir: path prefix, if any, _intended_ for use with + WASMFS OPFS persistent storage. + + wasmfsOpfsEnabled: true if persistent storage is enabled in the + current environment. Only files stored under wasmfsOpfsDir + will persist using that mechanism, however. It is legal to use + the non-WASMFS OPFS VFS to open a database via a URI-style + db filename. + + vfsList: result of sqlite3.capi.sqlite3_js_vfs_list() + } + } + ``` + + + ==================================================================== + "open" a database + + Message format: + + ``` + { + type: "open", + messageId: ...as above..., + args:{ + + filename [=":memory:" or "" (unspecified)]: the db filename. + See the sqlite3.oo1.DB constructor for peculiarities and + transformations, + + vfs: sqlite3_vfs name. Ignored if filename is ":memory:" or "". + This may change how the given filename is resolved. + } + } + ``` + + Response: + + ``` + { + type: "open", + messageId: ...as above..., + result: { + filename: db filename, possibly differing from the input. + + dbId: an opaque ID value which must be passed in the message + envelope to other calls in this API to tell them which db to + use. If it is not provided to future calls, they will default to + operating on the least-recently-opened db. This property is, for + API consistency's sake, also part of the containing message + envelope. Only the `open` operation includes it in the `result` + property. + + persistent: true if the given filename resides in the + known-persistent storage, else false. + + vfs: name of the VFS the "main" db is using. + } + } + ``` + + ==================================================================== + "close" a database + + Message format: + + ``` + { + type: "close", + messageId: ...as above... + dbId: ...as above... + args: OPTIONAL {unlink: boolean} + } + ``` + + If the `dbId` does not refer to an opened ID, this is a no-op. If + the `args` object contains a truthy `unlink` value then the database + will be unlinked (deleted) after closing it. The inability to close a + db (because it's not opened) or delete its file does not trigger an + error. + + Response: + + ``` + { + type: "close", + messageId: ...as above..., + result: { + + filename: filename of closed db, or undefined if no db was closed + + } + } + ``` + + ==================================================================== + "exec" SQL + + All SQL execution is processed through the exec operation. It offers + most of the features of the oo1.DB.exec() method, with a few limitations + imposed by the state having to cross thread boundaries. + + Message format: + + ``` + { + type: "exec", + messageId: ...as above... + dbId: ...as above... + args: string (SQL) or {... see below ...} + } + ``` + + Response: + + ``` + { + type: "exec", + messageId: ...as above..., + dbId: ...as above... + result: { + input arguments, possibly modified. See below. + } + } + ``` + + The arguments are in the same form accepted by oo1.DB.exec(), with + the exceptions noted below. + + A function-type args.callback property cannot cross + the window/Worker boundary, so is not useful here. If + args.callback is a string then it is assumed to be a + message type key, in which case a callback function will be + applied which posts each row result via: + + postMessage({type: thatKeyType, + rowNumber: 1-based-#, + row: theRow, + columnNames: anArray + }) + + And, at the end of the result set (whether or not any result rows + were produced), it will post an identical message with + (row=undefined, rowNumber=null) to alert the caller than the result + set is completed. Note that a row value of `null` is a legal row + result for certain arg.rowMode values. + + (Design note: we don't use (row=undefined, rowNumber=undefined) to + indicate end-of-results because fetching those would be + indistinguishable from fetching from an empty object unless the + client used hasOwnProperty() (or similar) to distinguish "missing + property" from "property with the undefined value". Similarly, + `null` is a legal value for `row` in some case , whereas the db + layer won't emit a result value of `undefined`.) + + The callback proxy must not recurse into this interface. An exec() + call will tie up the Worker thread, causing any recursion attempt + to wait until the first exec() is completed. + + The response is the input options object (or a synthesized one if + passed only a string), noting that options.resultRows and + options.columnNames may be populated by the call to db.exec(). + +*/ +self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +sqlite3.initWorker1API = function(){ + 'use strict'; + const toss = (...args)=>{throw new Error(args.join(' '))}; + if('function' !== typeof importScripts){ + toss("initWorker1API() must be run from a Worker thread."); + } + const self = this.self; + const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object."); + const DB = sqlite3.oo1.DB; + + /** + Returns the app-wide unique ID for the given db, creating one if + needed. + */ + const getDbId = function(db){ + let id = wState.idMap.get(db); + if(id) return id; + id = 'db#'+(++wState.idSeq)+'@'+db.pointer; + /** ^^^ can't simply use db.pointer b/c closing/opening may re-use + the same address, which could map pending messages to a wrong + instance. */ + wState.idMap.set(db, id); + return id; + }; + + /** + Internal helper for managing Worker-level state. + */ + const wState = { + /** + Each opened DB is added to this.dbList, and the first entry in + that list is the default db. As each db is closed, its entry is + removed from the list. + */ + dbList: [], + /** Sequence number of dbId generation. */ + idSeq: 0, + /** Map of DB instances to dbId. */ + idMap: new WeakMap, + /** Temp holder for "transferable" postMessage() state. */ + xfer: [], + open: function(opt){ + const db = new DB(opt); + this.dbs[getDbId(db)] = db; + if(this.dbList.indexOf(db)<0) this.dbList.push(db); + return db; + }, + close: function(db,alsoUnlink){ + if(db){ + delete this.dbs[getDbId(db)]; + const filename = db.filename; + const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0); + db.close(); + const ddNdx = this.dbList.indexOf(db); + if(ddNdx>=0) this.dbList.splice(ddNdx, 1); + if(alsoUnlink && filename && pVfs){ + sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); + } + } + }, + /** + Posts the given worker message value. If xferList is provided, + it must be an array, in which case a copy of it passed as + postMessage()'s second argument and xferList.length is set to + 0. + */ + post: function(msg,xferList){ + if(xferList && xferList.length){ + self.postMessage( msg, Array.from(xferList) ); + xferList.length = 0; + }else{ + self.postMessage(msg); + } + }, + /** Map of DB IDs to DBs. */ + dbs: Object.create(null), + /** Fetch the DB for the given id. Throw if require=true and the + id is not valid, else return the db or undefined. */ + getDb: function(id,require=true){ + return this.dbs[id] + || (require ? toss("Unknown (or closed) DB ID:",id) : undefined); + } + }; + + /** Throws if the given db is falsy or not opened, else returns its + argument. */ + const affirmDbOpen = function(db = wState.dbList[0]){ + return (db && db.pointer) ? db : toss("DB is not opened."); + }; + + /** Extract dbId from the given message payload. */ + const getMsgDb = function(msgData,affirmExists=true){ + const db = wState.getDb(msgData.dbId,false) || wState.dbList[0]; + return affirmExists ? affirmDbOpen(db) : db; + }; + + const getDefaultDbId = function(){ + return wState.dbList[0] && getDbId(wState.dbList[0]); + }; + + const guessVfs = function(filename){ + const m = /^file:.+(vfs=(\w+))/.exec(filename); + return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0); + }; + + const isSpecialDbFilename = (n)=>{ + return ""===n || ':'===n[0]; + }; + + /** + A level of "organizational abstraction" for the Worker1 + API. Each method in this object must map directly to a Worker1 + message type key. The onmessage() dispatcher attempts to + dispatch all inbound messages to a method of this object, + passing it the event.data part of the inbound event object. All + methods must return a plain Object containing any result + state, which the dispatcher may amend. All methods must throw + on error. + */ + const wMsgHandler = { + open: function(ev){ + const oargs = Object.create(null), args = (ev.args || Object.create(null)); + if(args.simulateError){ // undocumented internal testing option + toss("Throwing because of simulateError flag."); + } + const rc = Object.create(null); + const pDir = sqlite3.capi.sqlite3_wasmfs_opfs_dir(); + let byteArray, pVfs; + oargs.vfs = args.vfs; + if(isSpecialDbFilename(args.filename)){ + oargs.filename = args.filename || ""; + }else{ + oargs.filename = args.filename; + byteArray = args.byteArray; + if(byteArray) pVfs = guessVfs(args.filename); + } + if(pVfs){ + /* 2022-11-02: this feature is as-yet untested except that + sqlite3_wasm_vfs_create_file() has been tested from the + browser dev console. */ + let pMem; + try{ + pMem = sqlite3.wasm.allocFromTypedArray(byteArray); + const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file( + pVfs, oargs.filename, pMem, byteArray.byteLength + ); + if(rc) sqlite3.SQLite3Error.toss(rc); + }catch(e){ + throw new sqlite3.SQLite3Error( + e.name+' creating '+args.filename+": "+e.message, { + cause: e + } + ); + }finally{ + if(pMem) sqlite3.wasm.dealloc(pMem); + } + } + const db = wState.open(oargs); + rc.filename = db.filename; + rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/')) + || !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); + rc.dbId = getDbId(db); + rc.vfs = db.dbVfsName(); + return rc; + }, + + close: function(ev){ + const db = getMsgDb(ev,false); + const response = { + filename: db && db.filename + }; + if(db){ + const doUnlink = ((ev.args && 'object'===typeof ev.args) + ? !!ev.args.unlink : false); + wState.close(db, doUnlink); + } + return response; + }, + + exec: function(ev){ + const rc = ( + 'string'===typeof ev.args + ) ? {sql: ev.args} : (ev.args || Object.create(null)); + if('stmt'===rc.rowMode){ + toss("Invalid rowMode for 'exec': stmt mode", + "does not work in the Worker API."); + }else if(!rc.sql){ + toss("'exec' requires input SQL."); + } + const db = getMsgDb(ev); + if(rc.callback || Array.isArray(rc.resultRows)){ + // Part of a copy-avoidance optimization for blobs + db._blobXfer = wState.xfer; + } + const theCallback = rc.callback; + let rowNumber = 0; + const hadColNames = !!rc.columnNames; + if('string' === typeof theCallback){ + if(!hadColNames) rc.columnNames = []; + /* Treat this as a worker message type and post each + row as a message of that type. */ + rc.callback = function(row,stmt){ + wState.post({ + type: theCallback, + columnNames: rc.columnNames, + rowNumber: ++rowNumber, + row: row + }, wState.xfer); + } + } + try { + db.exec(rc); + if(rc.callback instanceof Function){ + rc.callback = theCallback; + /* Post a sentinel message to tell the client that the end + of the result set has been reached (possibly with zero + rows). */ + wState.post({ + type: theCallback, + columnNames: rc.columnNames, + rowNumber: null /*null to distinguish from "property not set"*/, + row: undefined /*undefined because null is a legal row value + for some rowType values, but undefined is not*/ + }); + } + }finally{ + delete db._blobXfer; + if(rc.callback) rc.callback = theCallback; + } + return rc; + }/*exec()*/, + + 'config-get': function(){ + const rc = Object.create(null), src = sqlite3.config; + [ + 'wasmfsOpfsDir', 'bigIntEnabled' + ].forEach(function(k){ + if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k]; + }); + rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir(); + rc.version = sqlite3.version; + rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list(); + rc.opfsEnabled = !!sqlite3.opfs; + return rc; + }, + + /** + Exports the database to a byte array, as per + sqlite3_serialize(). Response is an object: + + { + byteArray: Uint8Array (db file contents), + filename: the current db filename, + mimetype: 'application/x-sqlite3' + } + */ + export: function(ev){ + const db = getMsgDb(ev); + const response = { + byteArray: sqlite3.capi.sqlite3_js_db_export(db.pointer), + filename: db.filename, + mimetype: 'application/x-sqlite3' + }; + wState.xfer.push(response.byteArray.buffer); + return response; + }/*export()*/, + + toss: function(ev){ + toss("Testing worker exception"); + }, + + 'opfs-tree': async function(ev){ + if(!sqlite3.opfs) toss("OPFS support is unavailable."); + const response = await sqlite3.opfs.treeList(); + return response; + } + }/*wMsgHandler*/; + + self.onmessage = async function(ev){ + ev = ev.data; + let result, dbId = ev.dbId, evType = ev.type; + const arrivalTime = performance.now(); + try { + if(wMsgHandler.hasOwnProperty(evType) && + wMsgHandler[evType] instanceof Function){ + result = await wMsgHandler[evType](ev); + }else{ + toss("Unknown db worker message type:",ev.type); + } + }catch(err){ + evType = 'error'; + result = { + operation: ev.type, + message: err.message, + errorClass: err.name, + input: ev + }; + if(err.stack){ + result.stack = ('string'===typeof err.stack) + ? err.stack.split(/\n\s*/) : err.stack; + } + if(0) console.warn("Worker is propagating an exception to main thread.", + "Reporting it _here_ for the stack trace:",err,result); + } + if(!dbId){ + dbId = result.dbId/*from 'open' cmd*/ + || getDefaultDbId(); + } + // Timing info is primarily for use in testing this API. It's not part of + // the public API. arrivalTime = when the worker got the message. + wState.post({ + type: evType, + dbId: dbId, + messageId: ev.messageId, + workerReceivedTime: arrivalTime, + workerRespondTime: performance.now(), + departureTime: ev.departureTime, + // TODO: move the timing bits into... + //timing:{ + // departure: ev.departureTime, + // workerReceived: arrivalTime, + // workerResponse: performance.now(); + //}, + result: result + }, wState.xfer); + }; + self.postMessage({type:'sqlite3-api',result:'worker1-ready'}); +}.bind({self, sqlite3}); +}); diff --git a/ext/wasm/api/sqlite3-license-version-header.js b/ext/wasm/api/sqlite3-license-version-header.js new file mode 100644 index 000000000..f8b3eddc5 --- /dev/null +++ b/ext/wasm/api/sqlite3-license-version-header.js @@ -0,0 +1,25 @@ +/* +** LICENSE for the sqlite3 WebAssembly/JavaScript APIs. +** +** This bundle (typically released as sqlite3.js or sqlite3-wasmfs.js) +** is an amalgamation of JavaScript source code from two projects: +** +** 1) https://emscripten.org: the Emscripten "glue code" is covered by +** the terms of the MIT license and University of Illinois/NCSA +** Open Source License, as described at: +** +** https://emscripten.org/docs/introducing_emscripten/emscripten_license.html +** +** 2) https://sqlite.org: all code and documentation labeled as being +** from this source are released under the same terms as the sqlite3 +** C library: +** +** 2022-10-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. +*/ diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js new file mode 100644 index 000000000..e4657484e --- /dev/null +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -0,0 +1,830 @@ +/* + 2022-09-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. + + *********************************************************************** + + A Worker which manages asynchronous OPFS handles on behalf of a + synchronous API which controls it via a combination of Worker + messages, SharedArrayBuffer, and Atomics. It is the asynchronous + counterpart of the API defined in sqlite3-api-opfs.js. + + Highly indebted to: + + https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js + + for demonstrating how to use the OPFS APIs. + + This file is to be loaded as a Worker. It does not have any direct + access to the sqlite3 JS/WASM bits, so any bits which it needs (most + notably SQLITE_xxx integer codes) have to be imported into it via an + initialization process. + + This file represents an implementation detail of a larger piece of + code, and not a public interface. Its details may change at any time + and are not intended to be used by any client-level code. +*/ +"use strict"; +const toss = function(...args){throw new Error(args.join(' '))}; +if(self.window === self){ + toss("This code cannot run from the main thread.", + "Load it as a Worker from a separate Worker."); +}else if(!navigator.storage.getDirectory){ + toss("This API requires navigator.storage.getDirectory."); +} + +/** + Will hold state copied to this object from the syncronous side of + this API. +*/ +const state = Object.create(null); + +/** + verbose: + + 0 = no logging output + 1 = only errors + 2 = warnings and errors + 3 = debug, warnings, and errors +*/ +state.verbose = 2; + +const loggers = { + 0:console.error.bind(console), + 1:console.warn.bind(console), + 2:console.log.bind(console) +}; +const logImpl = (level,...args)=>{ + if(state.verbose>level) loggers[level]("OPFS asyncer:",...args); +}; +const log = (...args)=>logImpl(2, ...args); +const warn = (...args)=>logImpl(1, ...args); +const error = (...args)=>logImpl(0, ...args); +const metrics = Object.create(null); +metrics.reset = ()=>{ + let k; + const r = (m)=>(m.count = m.time = m.wait = 0); + for(k in state.opIds){ + r(metrics[k] = Object.create(null)); + } + let s = metrics.s11n = Object.create(null); + s = s.serialize = Object.create(null); + s.count = s.time = 0; + s = metrics.s11n.deserialize = Object.create(null); + s.count = s.time = 0; +}; +metrics.dump = ()=>{ + let k, n = 0, t = 0, w = 0; + for(k in state.opIds){ + const m = metrics[k]; + n += m.count; + t += m.time; + w += m.wait; + m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; + } + console.log(self.location.href, + "metrics for",self.location.href,":\n", + metrics, + "\nTotal of",n,"op(s) for",t,"ms", + "approx",w,"ms spent waiting on OPFS APIs."); + console.log("Serialization metrics:",metrics.s11n); +}; + +/** + __openFiles is a map of sqlite3_file pointers (integers) to + metadata related to a given OPFS file handles. The pointers are, in + this side of the interface, opaque file handle IDs provided by the + synchronous part of this constellation. Each value is an object + with a structure demonstrated in the xOpen() impl. +*/ +const __openFiles = Object.create(null); +/** + __autoLocks is a Set of sqlite3_file pointers (integers) which were + "auto-locked". i.e. those for which we obtained a sync access + handle without an explicit xLock() call. Such locks will be + released during db connection idle time, whereas a sync access + handle obtained via xLock(), or subsequently xLock()'d after + auto-acquisition, will not be released until xUnlock() is called. + + Maintenance reminder: if we relinquish auto-locks at the end of the + operation which acquires them, we pay a massive performance + penalty: speedtest1 benchmarks take up to 4x as long. By delaying + the lock release until idle time, the hit is negligible. +*/ +const __autoLocks = new Set(); + +/** + Expects an OPFS file path. It gets resolved, such that ".." + components are properly expanded, and returned. If the 2nd arg is + true, the result is returned as an array of path elements, else an + absolute path string is returned. +*/ +const getResolvedPath = function(filename,splitIt){ + const p = new URL( + filename, 'file://irrelevant' + ).pathname; + return splitIt ? p.split('/').filter((v)=>!!v) : p; +}; + +/** + Takes the absolute path to a filesystem element. Returns an array + of [handleOfContainingDir, filename]. If the 2nd argument is truthy + then each directory element leading to the file is created along + the way. Throws if any creation or resolution fails. +*/ +const getDirForFilename = async function f(absFilename, createDirs = false){ + const path = getResolvedPath(absFilename, true); + const filename = path.pop(); + let dh = state.rootDir; + for(const dirName of path){ + if(dirName){ + dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); + } + } + return [dh, filename]; +}; + +/** + An error class specifically for use with getSyncHandle(), the goal + of which is to eventually be able to distinguish unambiguously + between locking-related failures and other types, noting that we + cannot currently do so because createSyncAccessHandle() does not + define its exceptions in the required level of detail. +*/ +class GetSyncHandleError extends Error { + constructor(errorObject, ...msg){ + super(); + this.error = errorObject; + this.message = [ + ...msg, ': Original exception ['+errorObject.name+']:', + errorObject.message + ].join(' '); + this.name = 'GetSyncHandleError'; + } +}; + +/** + Returns the sync access handle associated with the given file + handle object (which must be a valid handle object, as created by + xOpen()), lazily opening it if needed. + + In order to help alleviate cross-tab contention for a dabase, + if an exception is thrown while acquiring the handle, this routine + will wait briefly and try again, up to 3 times. If acquisition + still fails at that point it will give up and propagate the + exception. +*/ +const getSyncHandle = async (fh)=>{ + if(!fh.syncHandle){ + const t = performance.now(); + log("Acquiring sync handle for",fh.filenameAbs); + const maxTries = 4, msBase = 300; + let i = 1, ms = msBase; + for(; true; ms = msBase * ++i){ + try { + //if(i<3) toss("Just testing getSyncHandle() wait-and-retry."); + //TODO? A config option which tells it to throw here + //randomly every now and then, for testing purposes. + fh.syncHandle = await fh.fileHandle.createSyncAccessHandle(); + break; + }catch(e){ + if(i === maxTries){ + throw new GetSyncHandleError( + e, "Error getting sync handle.",maxTries, + "attempts failed.",fh.filenameAbs + ); + } + warn("Error getting sync handle. Waiting",ms, + "ms and trying again.",fh.filenameAbs,e); + Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms); + } + } + log("Got sync handle for",fh.filenameAbs,'in',performance.now() - t,'ms'); + if(!fh.xLock){ + __autoLocks.add(fh.fid); + log("Auto-locked",fh.fid,fh.filenameAbs); + } + } + return fh.syncHandle; +}; + +/** + If the given file-holding object has a sync handle attached to it, + that handle is remove and asynchronously closed. Though it may + sound sensible to continue work as soon as the close() returns + (noting that it's asynchronous), doing so can cause operations + performed soon afterwards, e.g. a call to getSyncHandle() to fail + because they may happen out of order from the close(). OPFS does + not guaranty that the actual order of operations is retained in + such cases. i.e. always "await" on the result of this function. +*/ +const closeSyncHandle = async (fh)=>{ + if(fh.syncHandle){ + log("Closing sync handle for",fh.filenameAbs); + const h = fh.syncHandle; + delete fh.syncHandle; + delete fh.xLock; + __autoLocks.delete(fh.fid); + return h.close(); + } +}; + +/** + A proxy for closeSyncHandle() which is guaranteed to not throw. + + This function is part of a lock/unlock step in functions which + require a sync access handle but may be called without xLock() + having been called first. Such calls need to release that + handle to avoid locking the file for all of time. This is an + _attempt_ at reducing cross-tab contention but it may prove + to be more of a problem than a solution and may need to be + removed. +*/ +const closeSyncHandleNoThrow = async (fh)=>{ + try{await closeSyncHandle(fh)} + catch(e){ + warn("closeSyncHandleNoThrow() ignoring:",e,fh); + } +}; + +/** + Stores the given value at state.sabOPView[state.opIds.rc] and then + Atomics.notify()'s it. +*/ +const storeAndNotify = (opName, value)=>{ + log(opName+"() => notify(",value,")"); + Atomics.store(state.sabOPView, state.opIds.rc, value); + Atomics.notify(state.sabOPView, state.opIds.rc); +}; + +/** + Throws if fh is a file-holding object which is flagged as read-only. +*/ +const affirmNotRO = function(opName,fh){ + if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); +}; +const affirmLocked = function(opName,fh){ + //if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs); + /** + Currently a no-op, as speedtest1 triggers xRead() without a + lock (that seems like a bug but it's currently uninvestigated). + This means, however, that some OPFS VFS routines may trigger + acquisition of a lock but never let it go until xUnlock() is + called (which it likely won't be if xLock() was not called). + */ +}; + +/** + We track 2 different timers: the "metrics" timer records how much + time we spend performing work. The "wait" timer records how much + time we spend waiting on the underlying OPFS timer. See the calls + to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd() + throughout this file to see how they're used. +*/ +const __mTimer = Object.create(null); +__mTimer.op = undefined; +__mTimer.start = undefined; +const mTimeStart = (op)=>{ + __mTimer.start = performance.now(); + __mTimer.op = op; + //metrics[op] || toss("Maintenance required: missing metrics for",op); + ++metrics[op].count; +}; +const mTimeEnd = ()=>( + metrics[__mTimer.op].time += performance.now() - __mTimer.start +); +const __wTimer = Object.create(null); +__wTimer.op = undefined; +__wTimer.start = undefined; +const wTimeStart = (op)=>{ + __wTimer.start = performance.now(); + __wTimer.op = op; + //metrics[op] || toss("Maintenance required: missing metrics for",op); +}; +const wTimeEnd = ()=>( + metrics[__wTimer.op].wait += performance.now() - __wTimer.start +); + +/** + Gets set to true by the 'opfs-async-shutdown' command to quit the + wait loop. This is only intended for debugging purposes: we cannot + inspect this file's state while the tight waitLoop() is running and + need a way to stop that loop for introspection purposes. +*/ +let flagAsyncShutdown = false; + + +/** + Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods + methods, as well as helpers like mkdir(). Maintenance reminder: + members are in alphabetical order to simplify finding them. +*/ +const vfsAsyncImpls = { + 'opfs-async-metrics': async ()=>{ + mTimeStart('opfs-async-metrics'); + metrics.dump(); + storeAndNotify('opfs-async-metrics', 0); + mTimeEnd(); + }, + 'opfs-async-shutdown': async ()=>{ + flagAsyncShutdown = true; + storeAndNotify('opfs-async-shutdown', 0); + }, + mkdir: async (dirname)=>{ + mTimeStart('mkdir'); + let rc = 0; + wTimeStart('mkdir'); + try { + await getDirForFilename(dirname+"/filepart", true); + }catch(e){ + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR; + }finally{ + wTimeEnd(); + } + storeAndNotify('mkdir', rc); + mTimeEnd(); + }, + xAccess: async (filename)=>{ + mTimeStart('xAccess'); + /* OPFS cannot support the full range of xAccess() queries sqlite3 + calls for. We can essentially just tell if the file is + accessible, but if it is it's automatically writable (unless + it's locked, which we cannot(?) know without trying to open + it). OPFS does not have the notion of read-only. + + The return semantics of this function differ from sqlite3's + xAccess semantics because we are limited in what we can + communicate back to our synchronous communication partner: 0 = + accessible, non-0 means not accessible. + */ + let rc = 0; + wTimeStart('xAccess'); + try{ + const [dh, fn] = await getDirForFilename(filename); + await dh.getFileHandle(fn); + }catch(e){ + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR; + }finally{ + wTimeEnd(); + } + storeAndNotify('xAccess', rc); + mTimeEnd(); + }, + xClose: async function(fid/*sqlite3_file pointer*/){ + const opName = 'xClose'; + mTimeStart(opName); + __autoLocks.delete(fid); + const fh = __openFiles[fid]; + let rc = 0; + wTimeStart(opName); + if(fh){ + delete __openFiles[fid]; + await closeSyncHandle(fh); + if(fh.deleteOnClose){ + try{ await fh.dirHandle.removeEntry(fh.filenamePart) } + catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) } + } + }else{ + state.s11n.serialize(); + rc = state.sq3Codes.SQLITE_NOTFOUND; + } + wTimeEnd(); + storeAndNotify(opName, rc); + mTimeEnd(); + }, + xDelete: async function(...args){ + mTimeStart('xDelete'); + const rc = await vfsAsyncImpls.xDeleteNoWait(...args); + storeAndNotify('xDelete', rc); + mTimeEnd(); + }, + xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ + /* The syncDir flag is, for purposes of the VFS API's semantics, + ignored here. However, if it has the value 0x1234 then: after + deleting the given file, recursively try to delete any empty + directories left behind in its wake (ignoring any errors and + stopping at the first failure). + + That said: we don't know for sure that removeEntry() fails if + the dir is not empty because the API is not documented. It has, + however, a "recursive" flag which defaults to false, so + presumably it will fail if the dir is not empty and that flag + is false. + */ + let rc = 0; + wTimeStart('xDelete'); + try { + while(filename){ + const [hDir, filenamePart] = await getDirForFilename(filename, false); + if(!filenamePart) break; + await hDir.removeEntry(filenamePart, {recursive}); + if(0x1234 !== syncDir) break; + recursive = false; + filename = getResolvedPath(filename, true); + filename.pop(); + filename = filename.join('/'); + } + }catch(e){ + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR_DELETE; + } + wTimeEnd(); + return rc; + }, + xFileSize: async function(fid/*sqlite3_file pointer*/){ + mTimeStart('xFileSize'); + const fh = __openFiles[fid]; + let rc; + wTimeStart('xFileSize'); + try{ + affirmLocked('xFileSize',fh); + rc = await (await getSyncHandle(fh)).getSize(); + state.s11n.serialize(Number(rc)); + rc = 0; + }catch(e){ + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR; + } + wTimeEnd(); + storeAndNotify('xFileSize', rc); + mTimeEnd(); + }, + xLock: async function(fid/*sqlite3_file pointer*/, + lockType/*SQLITE_LOCK_...*/){ + mTimeStart('xLock'); + const fh = __openFiles[fid]; + let rc = 0; + const oldLockType = fh.xLock; + fh.xLock = lockType; + if( !fh.syncHandle ){ + wTimeStart('xLock'); + try { + await getSyncHandle(fh); + __autoLocks.delete(fid); + }catch(e){ + state.s11n.storeException(1,e); + rc = state.sq3Codes.SQLITE_IOERR_LOCK; + fh.xLock = oldLockType; + } + wTimeEnd(); + } + storeAndNotify('xLock',rc); + mTimeEnd(); + }, + xOpen: async function(fid/*sqlite3_file pointer*/, filename, + flags/*SQLITE_OPEN_...*/){ + const opName = 'xOpen'; + mTimeStart(opName); + const deleteOnClose = (state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags); + const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags); + wTimeStart('xOpen'); + try{ + let hDir, filenamePart; + try { + [hDir, filenamePart] = await getDirForFilename(filename, !!create); + }catch(e){ + state.s11n.storeException(1,e); + storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); + mTimeEnd(); + wTimeEnd(); + return; + } + const hFile = await hDir.getFileHandle(filenamePart, {create}); + /** + wa-sqlite, at this point, grabs a SyncAccessHandle and + assigns it to the syncHandle prop of the file state + object, but only for certain cases and it's unclear why it + places that limitation on it. + */ + wTimeEnd(); + __openFiles[fid] = Object.assign(Object.create(null),{ + fid: fid, + filenameAbs: filename, + filenamePart: filenamePart, + dirHandle: hDir, + fileHandle: hFile, + sabView: state.sabFileBufView, + readOnly: create + ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags), + deleteOnClose: deleteOnClose + }); + storeAndNotify(opName, 0); + }catch(e){ + wTimeEnd(); + error(opName,e); + state.s11n.storeException(1,e); + storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); + } + mTimeEnd(); + }, + xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){ + mTimeStart('xRead'); + let rc = 0, nRead; + const fh = __openFiles[fid]; + try{ + affirmLocked('xRead',fh); + wTimeStart('xRead'); + nRead = (await getSyncHandle(fh)).read( + fh.sabView.subarray(0, n), + {at: Number(offset64)} + ); + wTimeEnd(); + if(nRead < n){/* Zero-fill remaining bytes */ + fh.sabView.fill(0, nRead, n); + rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; + } + }catch(e){ + if(undefined===nRead) wTimeEnd(); + error("xRead() failed",e,fh); + state.s11n.storeException(1,e); + rc = state.sq3Codes.SQLITE_IOERR_READ; + } + storeAndNotify('xRead',rc); + mTimeEnd(); + }, + xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){ + mTimeStart('xSync'); + const fh = __openFiles[fid]; + let rc = 0; + if(!fh.readOnly && fh.syncHandle){ + try { + wTimeStart('xSync'); + await fh.syncHandle.flush(); + }catch(e){ + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR_FSYNC; + } + wTimeEnd(); + } + storeAndNotify('xSync',rc); + mTimeEnd(); + }, + xTruncate: async function(fid/*sqlite3_file pointer*/,size){ + mTimeStart('xTruncate'); + let rc = 0; + const fh = __openFiles[fid]; + wTimeStart('xTruncate'); + try{ + affirmLocked('xTruncate',fh); + affirmNotRO('xTruncate', fh); + await (await getSyncHandle(fh)).truncate(size); + }catch(e){ + error("xTruncate():",e,fh); + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE; + } + wTimeEnd(); + storeAndNotify('xTruncate',rc); + mTimeEnd(); + }, + xUnlock: async function(fid/*sqlite3_file pointer*/, + lockType/*SQLITE_LOCK_...*/){ + mTimeStart('xUnlock'); + let rc = 0; + const fh = __openFiles[fid]; + if( state.sq3Codes.SQLITE_LOCK_NONE===lockType + && fh.syncHandle ){ + wTimeStart('xUnlock'); + try { await closeSyncHandle(fh) } + catch(e){ + state.s11n.storeException(1,e); + rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; + } + wTimeEnd(); + } + storeAndNotify('xUnlock',rc); + mTimeEnd(); + }, + xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){ + mTimeStart('xWrite'); + let rc; + const fh = __openFiles[fid]; + wTimeStart('xWrite'); + try{ + affirmLocked('xWrite',fh); + affirmNotRO('xWrite', fh); + rc = ( + n === (await getSyncHandle(fh)) + .write(fh.sabView.subarray(0, n), + {at: Number(offset64)}) + ) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE; + }catch(e){ + error("xWrite():",e,fh); + state.s11n.storeException(1,e); + rc = state.sq3Codes.SQLITE_IOERR_WRITE; + } + wTimeEnd(); + storeAndNotify('xWrite',rc); + mTimeEnd(); + } +}/*vfsAsyncImpls*/; + +const initS11n = ()=>{ + /** + ACHTUNG: this code is 100% duplicated in the other half of this + proxy! The documentation is maintained in the "synchronous half". + */ + if(state.s11n) return state.s11n; + const textDecoder = new TextDecoder(), + textEncoder = new TextEncoder('utf-8'), + viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), + viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); + state.s11n = Object.create(null); + const TypeIds = Object.create(null); + TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; + TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; + TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; + TypeIds.string = { id: 4 }; + const getTypeId = (v)=>( + TypeIds[typeof v] + || toss("Maintenance required: this value type cannot be serialized.",v) + ); + const getTypeIdById = (tid)=>{ + switch(tid){ + case TypeIds.number.id: return TypeIds.number; + case TypeIds.bigint.id: return TypeIds.bigint; + case TypeIds.boolean.id: return TypeIds.boolean; + case TypeIds.string.id: return TypeIds.string; + default: toss("Invalid type ID:",tid); + } + }; + state.s11n.deserialize = function(clear=false){ + ++metrics.s11n.deserialize.count; + const t = performance.now(); + const argc = viewU8[0]; + const rc = argc ? [] : null; + if(argc){ + const typeIds = []; + let offset = 1, i, n, v; + for(i = 0; i < argc; ++i, ++offset){ + typeIds.push(getTypeIdById(viewU8[offset])); + } + for(i = 0; i < argc; ++i){ + const t = typeIds[i]; + if(t.getter){ + v = viewDV[t.getter](offset, state.littleEndian); + offset += t.size; + }else{/*String*/ + n = viewDV.getInt32(offset, state.littleEndian); + offset += 4; + v = textDecoder.decode(viewU8.slice(offset, offset+n)); + offset += n; + } + rc.push(v); + } + } + if(clear) viewU8[0] = 0; + //log("deserialize:",argc, rc); + metrics.s11n.deserialize.time += performance.now() - t; + return rc; + }; + state.s11n.serialize = function(...args){ + const t = performance.now(); + ++metrics.s11n.serialize.count; + if(args.length){ + //log("serialize():",args); + const typeIds = []; + let i = 0, offset = 1; + viewU8[0] = args.length & 0xff /* header = # of args */; + for(; i < args.length; ++i, ++offset){ + /* Write the TypeIds.id value into the next args.length + bytes. */ + typeIds.push(getTypeId(args[i])); + viewU8[offset] = typeIds[i].id; + } + for(i = 0; i < args.length; ++i) { + /* Deserialize the following bytes based on their + corresponding TypeIds.id from the header. */ + const t = typeIds[i]; + if(t.setter){ + viewDV[t.setter](offset, args[i], state.littleEndian); + offset += t.size; + }else{/*String*/ + const s = textEncoder.encode(args[i]); + viewDV.setInt32(offset, s.byteLength, state.littleEndian); + offset += 4; + viewU8.set(s, offset); + offset += s.byteLength; + } + } + //log("serialize() result:",viewU8.slice(0,offset)); + }else{ + viewU8[0] = 0; + } + metrics.s11n.serialize.time += performance.now() - t; + }; + + state.s11n.storeException = state.asyncS11nExceptions + ? ((priority,e)=>{ + if(priority<=state.asyncS11nExceptions){ + state.s11n.serialize([e.name,': ',e.message].join("")); + } + }) + : ()=>{}; + + return state.s11n; +}/*initS11n()*/; + +const waitLoop = async function f(){ + const opHandlers = Object.create(null); + for(let k of Object.keys(state.opIds)){ + const vi = vfsAsyncImpls[k]; + if(!vi) continue; + const o = Object.create(null); + opHandlers[state.opIds[k]] = o; + o.key = k; + o.f = vi; + } + /** + waitTime is how long (ms) to wait for each Atomics.wait(). + We need to wake up periodically to give the thread a chance + to do other things. + */ + const waitTime = 500; + while(!flagAsyncShutdown){ + try { + if('timed-out'===Atomics.wait( + state.sabOPView, state.opIds.whichOp, 0, waitTime + )){ + if(__autoLocks.size){ + /* Release all auto-locks. */ + for(const fid of __autoLocks){ + const fh = __openFiles[fid]; + await closeSyncHandleNoThrow(fh); + log("Auto-unlocked",fid,fh.filenameAbs); + } + } + continue; + } + const opId = Atomics.load(state.sabOPView, state.opIds.whichOp); + Atomics.store(state.sabOPView, state.opIds.whichOp, 0); + const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId); + const args = state.s11n.deserialize( + true /* clear s11n to keep the caller from confusing this with + an exception string written by the upcoming + operation */ + ) || []; + //warn("waitLoop() whichOp =",opId, hnd, args); + if(hnd.f) await hnd.f(...args); + else error("Missing callback for opId",opId); + }catch(e){ + error('in waitLoop():',e); + } + } +}; + +navigator.storage.getDirectory().then(function(d){ + const wMsg = (type)=>postMessage({type}); + state.rootDir = d; + self.onmessage = function({data}){ + switch(data.type){ + case 'opfs-async-init':{ + /* Receive shared state from synchronous partner */ + const opt = data.args; + state.littleEndian = opt.littleEndian; + state.asyncS11nExceptions = opt.asyncS11nExceptions; + state.verbose = opt.verbose ?? 2; + state.fileBufferSize = opt.fileBufferSize; + state.sabS11nOffset = opt.sabS11nOffset; + state.sabS11nSize = opt.sabS11nSize; + state.sabOP = opt.sabOP; + state.sabOPView = new Int32Array(state.sabOP); + state.sabIO = opt.sabIO; + state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); + state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); + state.opIds = opt.opIds; + state.sq3Codes = opt.sq3Codes; + Object.keys(vfsAsyncImpls).forEach((k)=>{ + if(!Number.isFinite(state.opIds[k])){ + toss("Maintenance required: missing state.opIds[",k,"]"); + } + }); + initS11n(); + metrics.reset(); + log("init state",state); + wMsg('opfs-async-inited'); + waitLoop(); + break; + } + case 'opfs-async-restart': + if(flagAsyncShutdown){ + warn("Restarting after opfs-async-shutdown. Might or might not work."); + flagAsyncShutdown = false; + waitLoop(); + } + break; + case 'opfs-async-metrics': + metrics.dump(); + break; + } + }; + wMsg('opfs-async-loaded'); +}).catch((e)=>error("error initializing OPFS asyncer:",e)); diff --git a/ext/wasm/api/sqlite3-wasi.h b/ext/wasm/api/sqlite3-wasi.h new file mode 100644 index 000000000..096f45dfe --- /dev/null +++ b/ext/wasm/api/sqlite3-wasi.h @@ -0,0 +1,69 @@ +/** + Dummy function stubs to get sqlite3.c compiling with + wasi-sdk. This requires, in addition: + + -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID + + -lwasi-emulated-getpid +*/ +typedef unsigned mode_t; +int fchmod(int fd, mode_t mode); +int fchmod(int fd, mode_t mode){ + return (fd && mode) ? 0 : 0; +} +typedef unsigned uid_t; +typedef uid_t gid_t; +int fchown(int fd, uid_t owner, gid_t group); +int fchown(int fd, uid_t owner, gid_t group){ + return (fd && owner && group) ? 0 : 0; +} +uid_t geteuid(void); +uid_t geteuid(void){return 0;} +#if !defined(F_WRLCK) +enum { +F_WRLCK, +F_RDLCK, +F_GETLK, +F_SETLK, +F_UNLCK +}; +#endif + +#undef HAVE_PREAD + +#include +#define WASM__KEEP __attribute__((used)) + +#if 0 +/** + wasi-sdk cannot build sqlite3's default VFS without at least the following + functions. They are apparently syscalls which clients have to implement or + otherwise obtain. + + https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md +*/ +environ_get +environ_sizes_get +clock_time_get +fd_close +fd_fdstat_get +fd_fdstat_set_flags +fd_filestat_get +fd_filestat_set_size +fd_pread +fd_prestat_get +fd_prestat_dir_name +fd_read +fd_seek +fd_sync +fd_write +path_create_directory +path_filestat_get +path_filestat_set_times +path_open +path_readlink +path_remove_directory +path_unlink_file +poll_oneoff +proc_exit +#endif diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c new file mode 100644 index 000000000..af5ed6bf7 --- /dev/null +++ b/ext/wasm/api/sqlite3-wasm.c @@ -0,0 +1,1181 @@ +/* +** This file requires access to sqlite3.c static state in order to +** implement certain WASM-specific features, and thus directly +** includes that file. Unlike the rest of sqlite3.c, this file +** requires compiling with -std=c99 (or equivalent, or a later C +** version) because it makes use of features not available in C89. +** +** At its simplest, to build sqlite3.wasm either place this file +** in the same directory as sqlite3.c/h before compilation or use the +** -I/path flag to tell the compiler where to find both of those +** files, then compile this file. For example: +** +** emcc -o sqlite3.wasm ... -I/path/to/sqlite3-c-and-h sqlite3-wasm.c +*/ +#define SQLITE_WASM +#ifdef SQLITE_WASM_ENABLE_C_TESTS +/* +** Code blocked off by SQLITE_WASM_TESTS is intended solely for use in +** unit/regression testing. They may be safely omitted from +** client-side builds. The main unit test script, tester1.js, will +** skip related tests if it doesn't find the corresponding functions +** in the WASM exports. +*/ +# define SQLITE_WASM_TESTS 1 +#else +# define SQLITE_WASM_TESTS 0 +#endif + +/* +** Threading and file locking: JS is single-threaded. Each Worker +** thread is a separate instance of the JS engine so can never access +** the same db handle as another thread, thus multi-threading support +** is unnecessary in the library. Because the filesystems are virtual +** and local to a given wasm runtime instance, two Workers can never +** access the same db file at once, with the exception of OPFS. As of +** this writing (2022-09-30), OPFS exclusively locks a file when +** opening it, so two Workers can never open the same OPFS-backed file +** at once. That situation will change if and when lower-level locking +** features are added to OPFS (as is currently planned, per folks +** involved with its development). +** +** Summary: except for the case of future OPFS, which supports +** locking, and any similar future filesystems, threading and file +** locking support are unnecessary in the wasm build. +*/ + +/* +** Undefine any SQLITE_... config flags which we specifically do not +** want undefined. Please keep these alphabetized. +*/ +#undef SQLITE_OMIT_DESERIALIZE +#undef SQLITE_OMIT_MEMORYDB + +/* +** Define any SQLITE_... config defaults we want if they aren't +** overridden by the builder. Please keep these alphabetized. +*/ + +/**********************************************************************/ +/* SQLITE_D... */ +#ifndef SQLITE_DEFAULT_CACHE_SIZE +/* +** The OPFS impls benefit tremendously from an increased cache size +** when working on large workloads, e.g. speedtest1 --size 50 or +** higher. On smaller workloads, e.g. speedtest1 --size 25, they +** clearly benefit from having 4mb of cache, but not as much as a +** larger cache benefits the larger workloads. Speed differences +** between 2x and nearly 3x have been measured with ample page cache. +*/ +# define SQLITE_DEFAULT_CACHE_SIZE -16384 +#endif +#if 0 && !defined(SQLITE_DEFAULT_PAGE_SIZE) +/* TODO: experiment with this. */ +# define SQLITE_DEFAULT_PAGE_SIZE 8192 /*4096*/ +#endif +#ifndef SQLITE_DEFAULT_UNIX_VFS +# define SQLITE_DEFAULT_UNIX_VFS "unix-none" +#endif +#undef SQLITE_DQS +#define SQLITE_DQS 0 + +/**********************************************************************/ +/* SQLITE_ENABLE_... */ +#ifndef SQLITE_ENABLE_BYTECODE_VTAB +# define SQLITE_ENABLE_BYTECODE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBPAGE_VTAB +# define SQLITE_ENABLE_DBPAGE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBSTAT_VTAB +# define SQLITE_ENABLE_DBSTAT_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS +# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 +#endif +#ifndef SQLITE_ENABLE_FTS4 +# define SQLITE_ENABLE_FTS4 1 +#endif +#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC +# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 +#endif +#ifndef SQLITE_ENABLE_RTREE +# define SQLITE_ENABLE_RTREE 1 +#endif +#ifndef SQLITE_ENABLE_STMTVTAB +# define SQLITE_ENABLE_STMTVTAB 1 +#endif +#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +#endif + +/**********************************************************************/ +/* SQLITE_O... */ +#ifndef SQLITE_OMIT_DEPRECATED +# define SQLITE_OMIT_DEPRECATED 1 +#endif +#ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE +# define SQLITE_OMIT_SHARED_CACHE 1 +#endif +#ifndef SQLITE_OMIT_UTF16 +# define SQLITE_OMIT_UTF16 1 +#endif +#ifndef SQLITE_OMIT_WAL +# define SQLITE_OMIT_WAL 1 +#endif +#ifndef SQLITE_OS_KV_OPTIONAL +# define SQLITE_OS_KV_OPTIONAL 1 +#endif + +/**********************************************************************/ +/* SQLITE_T... */ +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 3 +#endif +#ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +#endif + +/**********************************************************************/ +/* SQLITE_USE_... */ +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 1 +#endif + +#include +#include "sqlite3.c" /* yes, .c instead of .h. */ + +#if defined(__EMSCRIPTEN__) +# include +#endif + +/* +** SQLITE_WASM_KEEP is functionally identical to EMSCRIPTEN_KEEPALIVE +** but is not Emscripten-specific. It explicitly marks functions for +** export into the target wasm file without requiring explicit listing +** of those functions in Emscripten's -sEXPORTED_FUNCTIONS=... list +** (or equivalent in other build platforms). Any function with neither +** this attribute nor which is listed as an explicit export will not +** be exported from the wasm file (but may still be used internally +** within the wasm file). +** +** The functions in this file (sqlite3-wasm.c) which require exporting +** are marked with this flag. They may also be added to any explicit +** build-time export list but need not be. All of these APIs are +** intended for use only within the project's own JS/WASM code, and +** not by client code, so an argument can be made for reducing their +** visibility by not including them in any build-time export lists. +** +** 2022-09-11: it's not yet _proven_ that this approach works in +** non-Emscripten builds. If not, such builds will need to export +** those using the --export=... wasm-ld flag (or equivalent). As of +** this writing we are tied to Emscripten for various reasons +** and cannot test the library with other build environments. +*/ +#define SQLITE_WASM_KEEP __attribute__((used,visibility("default"))) +// See also: +//__attribute__((export_name("theExportedName"), used, visibility("default"))) + + +#if 0 +/* +** An EXPERIMENT in implementing a stack-based allocator analog to +** Emscripten's stackSave(), stackAlloc(), stackRestore(). +** Unfortunately, this cannot work together with Emscripten because +** Emscripten defines its own native one and we'd stomp on each +** other's memory. Other than that complication, basic tests show it +** to work just fine. +** +** Another option is to malloc() a chunk of our own and call that our +** "stack". +*/ +SQLITE_WASM_KEEP void * sqlite3_wasm_stack_end(void){ + extern void __heap_base + /* see https://stackoverflow.com/questions/10038964 */; + return &__heap_base; +} +SQLITE_WASM_KEEP void * sqlite3_wasm_stack_begin(void){ + extern void __data_end; + return &__data_end; +} +static void * pWasmStackPtr = 0; +SQLITE_WASM_KEEP void * sqlite3_wasm_stack_ptr(void){ + if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end(); + return pWasmStackPtr; +} +SQLITE_WASM_KEEP void sqlite3_wasm_stack_restore(void * p){ + pWasmStackPtr = p; +} +SQLITE_WASM_KEEP void * sqlite3_wasm_stack_alloc(int n){ + if(n<=0) return 0; + n = (n + 7) & ~7 /* align to 8-byte boundary */; + unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr(); + unsigned const char * const b = (unsigned const char *)sqlite3_wasm_stack_begin(); + if(b + n >= p || b + n < b/*overflow*/) return 0; + return pWasmStackPtr = p - n; +} +#endif /* stack allocator experiment */ + +/* +** State for the "pseudo-stack" allocator implemented in +** sqlite3_wasm_pstack_xyz(). In order to avoid colliding with +** Emscripten-controled stack space, it carves out a bit of stack +** memory to use for that purpose. This memory ends up in the +** WASM-managed memory, such that routines which manipulate the wasm +** heap can also be used to manipulate this memory. +** +** This particular allocator is intended for small allocations such as +** storage for output pointers. We cannot reasonably size it large +** enough for general-purpose string conversions because some of our +** tests use input files (strings) of 16MB+. +*/ +static unsigned char PStack_mem[512 * 8] = {0}; +static struct { + unsigned const char * const pBegin;/* Start (inclusive) of memory */ + unsigned const char * const pEnd; /* One-after-the-end of memory */ + unsigned char * pPos; /* Current stack pointer */ +} PStack = { + &PStack_mem[0], + &PStack_mem[0] + sizeof(PStack_mem), + &PStack_mem[0] + sizeof(PStack_mem) +}; +/* +** Returns the current pstack position. +*/ +SQLITE_WASM_KEEP void * sqlite3_wasm_pstack_ptr(void){ + return PStack.pPos; +} +/* +** Sets the pstack position poitner to p. Results are undefined if the +** given value did not come from sqlite3_wasm_pstack_ptr(). +*/ +SQLITE_WASM_KEEP void sqlite3_wasm_pstack_restore(unsigned char * p){ + assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos); + assert(0==(p & 0x7)); + if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){ + PStack.pPos = p; + } +} +/* +** Allocate and zero out n bytes from the pstack. Returns a pointer to +** the memory on success, 0 on error (including a negative n value). n +** is always adjusted to be a multiple of 8 and returned memory is +** always zeroed out before returning (because this keeps the client +** JS code from having to do so, and most uses of the pstack will +** call for doing so). +*/ +SQLITE_WASM_KEEP void * sqlite3_wasm_pstack_alloc(int n){ + if( n<=0 ) return 0; + //if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */; + n = (n + 7) & ~7 /* align to 8-byte boundary */; + if( PStack.pBegin + n > PStack.pPos /*not enough space left*/ + || PStack.pBegin + n <= PStack.pBegin /*overflow*/ ) return 0; + memset((PStack.pPos = PStack.pPos - n), 0, (unsigned int)n); + return PStack.pPos; +} +/* +** Return the number of bytes left which can be +** sqlite3_wasm_pstack_alloc()'d. +*/ +SQLITE_WASM_KEEP int sqlite3_wasm_pstack_remaining(void){ + assert(PStack.pPos >= PStack.pBegin); + assert(PStack.pPos <= PStack.pEnd); + return (int)(PStack.pPos - PStack.pBegin); +} + +/* +** Return the total number of bytes available in the pstack, including +** any space which is currently allocated. This value is a +** compile-time constant. +*/ +SQLITE_WASM_KEEP int sqlite3_wasm_pstack_quota(void){ + return (int)(PStack.pEnd - PStack.pBegin); +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** For purposes of certain hand-crafted C/Wasm function bindings, we +** need a way of reporting errors which is consistent with the rest of +** the C API, as opposed to throwing JS exceptions. To that end, this +** internal-use-only function is a thin proxy around +** sqlite3ErrorWithMessage(). The intent is that it only be used from +** Wasm bindings such as sqlite3_prepare_v2/v3(), and definitely not +** from client code. +** +** Returns err_code. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ + if( 0!=zMsg ){ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + }else{ + sqlite3ErrorWithMsg(db, err_code, NULL); + } + return err_code; +} + +#if SQLITE_WASM_TESTS +struct WasmTestStruct { + int v4; + void * ppV; + const char * cstr; + int64_t v8; + void (*xFunc)(void*); +}; +typedef struct WasmTestStruct WasmTestStruct; +SQLITE_WASM_KEEP +void sqlite3_wasm_test_struct(WasmTestStruct * s){ + if(s){ + s->v4 *= 2; + s->v8 = s->v4 * 2; + s->ppV = s; + s->cstr = __FILE__; + if(s->xFunc) s->xFunc(s); + } + return; +} +#endif /* SQLITE_WASM_TESTS */ + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. Unlike the +** rest of the sqlite3 API, this part requires C99 for snprintf() and +** variadic macros. +** +** Returns a string containing a JSON-format "enum" of C-level +** constants and struct-related metadata intended to be imported into +** the JS environment. The JSON is initialized the first time this +** function is called and that result is reused for all future calls. +** +** If this function returns NULL then it means that the internal +** buffer is not large enough for the generated JSON and needs to be +** increased. In debug builds that will trigger an assert(). +*/ +SQLITE_WASM_KEEP +const char * sqlite3_wasm_enum_json(void){ + static char aBuffer[1024 * 12] = {0} /* where the JSON goes */; + int n = 0, nChildren = 0, nStruct = 0 + /* output counters for figuring out where commas go */; + char * zPos = &aBuffer[1] /* skip first byte for now to help protect + ** against a small race condition */; + char const * const zEnd = &aBuffer[0] + sizeof(aBuffer) /* one-past-the-end */; + if(aBuffer[0]) return aBuffer; + /* Leave aBuffer[0] at 0 until the end to help guard against a tiny + ** race condition. If this is called twice concurrently, they might + ** end up both writing to aBuffer, but they'll both write the same + ** thing, so that's okay. If we set byte 0 up front then the 2nd + ** instance might return and use the string before the 1st instance + ** is done filling it. */ + +/* Core output macros... */ +#define lenCheck assert(zPos < zEnd - 128 \ + && "sqlite3_wasm_enum_json() buffer is too small."); \ + if( zPos >= zEnd - 128 ) return 0 +#define outf(format,...) \ + zPos += snprintf(zPos, ((size_t)(zEnd - zPos)), format, __VA_ARGS__); \ + lenCheck +#define out(TXT) outf("%s",TXT) +#define CloseBrace(LEVEL) \ + assert(LEVEL<5); memset(zPos, '}', LEVEL); zPos+=LEVEL; lenCheck + +/* Macros for emitting maps of integer- and string-type macros to +** their values. */ +#define DefGroup(KEY) n = 0; \ + outf("%s\"" #KEY "\": {",(nChildren++ ? "," : "")); +#define DefInt(KEY) \ + outf("%s\"%s\": %d", (n++ ? ", " : ""), #KEY, (int)KEY) +#define DefStr(KEY) \ + outf("%s\"%s\": \"%s\"", (n++ ? ", " : ""), #KEY, KEY) +#define _DefGroup CloseBrace(1) + + /* The following groups are sorted alphabetic by group name. */ + DefGroup(access){ + DefInt(SQLITE_ACCESS_EXISTS); + DefInt(SQLITE_ACCESS_READWRITE); + DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; + } _DefGroup; + + DefGroup(blobFinalizers) { + /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as + ** integers to avoid casting-related warnings. */ + out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1"); + } _DefGroup; + + DefGroup(dataTypes) { + DefInt(SQLITE_INTEGER); + DefInt(SQLITE_FLOAT); + DefInt(SQLITE_TEXT); + DefInt(SQLITE_BLOB); + DefInt(SQLITE_NULL); + } _DefGroup; + + DefGroup(encodings) { + /* Noting that the wasm binding only aims to support UTF-8. */ + DefInt(SQLITE_UTF8); + DefInt(SQLITE_UTF16LE); + DefInt(SQLITE_UTF16BE); + DefInt(SQLITE_UTF16); + /*deprecated DefInt(SQLITE_ANY); */ + DefInt(SQLITE_UTF16_ALIGNED); + } _DefGroup; + + DefGroup(fcntl) { + DefInt(SQLITE_FCNTL_LOCKSTATE); + DefInt(SQLITE_FCNTL_GET_LOCKPROXYFILE); + DefInt(SQLITE_FCNTL_SET_LOCKPROXYFILE); + DefInt(SQLITE_FCNTL_LAST_ERRNO); + DefInt(SQLITE_FCNTL_SIZE_HINT); + DefInt(SQLITE_FCNTL_CHUNK_SIZE); + DefInt(SQLITE_FCNTL_FILE_POINTER); + DefInt(SQLITE_FCNTL_SYNC_OMITTED); + DefInt(SQLITE_FCNTL_WIN32_AV_RETRY); + DefInt(SQLITE_FCNTL_PERSIST_WAL); + DefInt(SQLITE_FCNTL_OVERWRITE); + DefInt(SQLITE_FCNTL_VFSNAME); + DefInt(SQLITE_FCNTL_POWERSAFE_OVERWRITE); + DefInt(SQLITE_FCNTL_PRAGMA); + DefInt(SQLITE_FCNTL_BUSYHANDLER); + DefInt(SQLITE_FCNTL_TEMPFILENAME); + DefInt(SQLITE_FCNTL_MMAP_SIZE); + DefInt(SQLITE_FCNTL_TRACE); + DefInt(SQLITE_FCNTL_HAS_MOVED); + DefInt(SQLITE_FCNTL_SYNC); + DefInt(SQLITE_FCNTL_COMMIT_PHASETWO); + DefInt(SQLITE_FCNTL_WIN32_SET_HANDLE); + DefInt(SQLITE_FCNTL_WAL_BLOCK); + DefInt(SQLITE_FCNTL_ZIPVFS); + DefInt(SQLITE_FCNTL_RBU); + DefInt(SQLITE_FCNTL_VFS_POINTER); + DefInt(SQLITE_FCNTL_JOURNAL_POINTER); + DefInt(SQLITE_FCNTL_WIN32_GET_HANDLE); + DefInt(SQLITE_FCNTL_PDB); + DefInt(SQLITE_FCNTL_BEGIN_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_COMMIT_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_LOCK_TIMEOUT); + DefInt(SQLITE_FCNTL_DATA_VERSION); + DefInt(SQLITE_FCNTL_SIZE_LIMIT); + DefInt(SQLITE_FCNTL_CKPT_DONE); + DefInt(SQLITE_FCNTL_RESERVE_BYTES); + DefInt(SQLITE_FCNTL_CKPT_START); + DefInt(SQLITE_FCNTL_EXTERNAL_READER); + DefInt(SQLITE_FCNTL_CKSM_FILE); + } _DefGroup; + + DefGroup(flock) { + DefInt(SQLITE_LOCK_NONE); + DefInt(SQLITE_LOCK_SHARED); + DefInt(SQLITE_LOCK_RESERVED); + DefInt(SQLITE_LOCK_PENDING); + DefInt(SQLITE_LOCK_EXCLUSIVE); + } _DefGroup; + + DefGroup(ioCap) { + DefInt(SQLITE_IOCAP_ATOMIC); + DefInt(SQLITE_IOCAP_ATOMIC512); + DefInt(SQLITE_IOCAP_ATOMIC1K); + DefInt(SQLITE_IOCAP_ATOMIC2K); + DefInt(SQLITE_IOCAP_ATOMIC4K); + DefInt(SQLITE_IOCAP_ATOMIC8K); + DefInt(SQLITE_IOCAP_ATOMIC16K); + DefInt(SQLITE_IOCAP_ATOMIC32K); + DefInt(SQLITE_IOCAP_ATOMIC64K); + DefInt(SQLITE_IOCAP_SAFE_APPEND); + DefInt(SQLITE_IOCAP_SEQUENTIAL); + DefInt(SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN); + DefInt(SQLITE_IOCAP_POWERSAFE_OVERWRITE); + DefInt(SQLITE_IOCAP_IMMUTABLE); + DefInt(SQLITE_IOCAP_BATCH_ATOMIC); + } _DefGroup; + + DefGroup(openFlags) { + /* Noting that not all of these will have any effect in + ** WASM-space. */ + DefInt(SQLITE_OPEN_READONLY); + DefInt(SQLITE_OPEN_READWRITE); + DefInt(SQLITE_OPEN_CREATE); + DefInt(SQLITE_OPEN_URI); + DefInt(SQLITE_OPEN_MEMORY); + DefInt(SQLITE_OPEN_NOMUTEX); + DefInt(SQLITE_OPEN_FULLMUTEX); + DefInt(SQLITE_OPEN_SHAREDCACHE); + DefInt(SQLITE_OPEN_PRIVATECACHE); + DefInt(SQLITE_OPEN_EXRESCODE); + DefInt(SQLITE_OPEN_NOFOLLOW); + /* OPEN flags for use with VFSes... */ + DefInt(SQLITE_OPEN_MAIN_DB); + DefInt(SQLITE_OPEN_MAIN_JOURNAL); + DefInt(SQLITE_OPEN_TEMP_DB); + DefInt(SQLITE_OPEN_TEMP_JOURNAL); + DefInt(SQLITE_OPEN_TRANSIENT_DB); + DefInt(SQLITE_OPEN_SUBJOURNAL); + DefInt(SQLITE_OPEN_SUPER_JOURNAL); + DefInt(SQLITE_OPEN_WAL); + DefInt(SQLITE_OPEN_DELETEONCLOSE); + DefInt(SQLITE_OPEN_EXCLUSIVE); + } _DefGroup; + + DefGroup(prepareFlags) { + DefInt(SQLITE_PREPARE_PERSISTENT); + DefInt(SQLITE_PREPARE_NORMALIZE); + DefInt(SQLITE_PREPARE_NO_VTAB); + } _DefGroup; + + DefGroup(resultCodes) { + DefInt(SQLITE_OK); + DefInt(SQLITE_ERROR); + DefInt(SQLITE_INTERNAL); + DefInt(SQLITE_PERM); + DefInt(SQLITE_ABORT); + DefInt(SQLITE_BUSY); + DefInt(SQLITE_LOCKED); + DefInt(SQLITE_NOMEM); + DefInt(SQLITE_READONLY); + DefInt(SQLITE_INTERRUPT); + DefInt(SQLITE_IOERR); + DefInt(SQLITE_CORRUPT); + DefInt(SQLITE_NOTFOUND); + DefInt(SQLITE_FULL); + DefInt(SQLITE_CANTOPEN); + DefInt(SQLITE_PROTOCOL); + DefInt(SQLITE_EMPTY); + DefInt(SQLITE_SCHEMA); + DefInt(SQLITE_TOOBIG); + DefInt(SQLITE_CONSTRAINT); + DefInt(SQLITE_MISMATCH); + DefInt(SQLITE_MISUSE); + DefInt(SQLITE_NOLFS); + DefInt(SQLITE_AUTH); + DefInt(SQLITE_FORMAT); + DefInt(SQLITE_RANGE); + DefInt(SQLITE_NOTADB); + DefInt(SQLITE_NOTICE); + DefInt(SQLITE_WARNING); + DefInt(SQLITE_ROW); + DefInt(SQLITE_DONE); + // Extended Result Codes + DefInt(SQLITE_ERROR_MISSING_COLLSEQ); + DefInt(SQLITE_ERROR_RETRY); + DefInt(SQLITE_ERROR_SNAPSHOT); + DefInt(SQLITE_IOERR_READ); + DefInt(SQLITE_IOERR_SHORT_READ); + DefInt(SQLITE_IOERR_WRITE); + DefInt(SQLITE_IOERR_FSYNC); + DefInt(SQLITE_IOERR_DIR_FSYNC); + DefInt(SQLITE_IOERR_TRUNCATE); + DefInt(SQLITE_IOERR_FSTAT); + DefInt(SQLITE_IOERR_UNLOCK); + DefInt(SQLITE_IOERR_RDLOCK); + DefInt(SQLITE_IOERR_DELETE); + DefInt(SQLITE_IOERR_BLOCKED); + DefInt(SQLITE_IOERR_NOMEM); + DefInt(SQLITE_IOERR_ACCESS); + DefInt(SQLITE_IOERR_CHECKRESERVEDLOCK); + DefInt(SQLITE_IOERR_LOCK); + DefInt(SQLITE_IOERR_CLOSE); + DefInt(SQLITE_IOERR_DIR_CLOSE); + DefInt(SQLITE_IOERR_SHMOPEN); + DefInt(SQLITE_IOERR_SHMSIZE); + DefInt(SQLITE_IOERR_SHMLOCK); + DefInt(SQLITE_IOERR_SHMMAP); + DefInt(SQLITE_IOERR_SEEK); + DefInt(SQLITE_IOERR_DELETE_NOENT); + DefInt(SQLITE_IOERR_MMAP); + DefInt(SQLITE_IOERR_GETTEMPPATH); + DefInt(SQLITE_IOERR_CONVPATH); + DefInt(SQLITE_IOERR_VNODE); + DefInt(SQLITE_IOERR_AUTH); + DefInt(SQLITE_IOERR_BEGIN_ATOMIC); + DefInt(SQLITE_IOERR_COMMIT_ATOMIC); + DefInt(SQLITE_IOERR_ROLLBACK_ATOMIC); + DefInt(SQLITE_IOERR_DATA); + DefInt(SQLITE_IOERR_CORRUPTFS); + DefInt(SQLITE_LOCKED_SHAREDCACHE); + DefInt(SQLITE_LOCKED_VTAB); + DefInt(SQLITE_BUSY_RECOVERY); + DefInt(SQLITE_BUSY_SNAPSHOT); + DefInt(SQLITE_BUSY_TIMEOUT); + DefInt(SQLITE_CANTOPEN_NOTEMPDIR); + DefInt(SQLITE_CANTOPEN_ISDIR); + DefInt(SQLITE_CANTOPEN_FULLPATH); + DefInt(SQLITE_CANTOPEN_CONVPATH); + //DefInt(SQLITE_CANTOPEN_DIRTYWAL)/*docs say not used*/; + DefInt(SQLITE_CANTOPEN_SYMLINK); + DefInt(SQLITE_CORRUPT_VTAB); + DefInt(SQLITE_CORRUPT_SEQUENCE); + DefInt(SQLITE_CORRUPT_INDEX); + DefInt(SQLITE_READONLY_RECOVERY); + DefInt(SQLITE_READONLY_CANTLOCK); + DefInt(SQLITE_READONLY_ROLLBACK); + DefInt(SQLITE_READONLY_DBMOVED); + DefInt(SQLITE_READONLY_CANTINIT); + DefInt(SQLITE_READONLY_DIRECTORY); + DefInt(SQLITE_ABORT_ROLLBACK); + DefInt(SQLITE_CONSTRAINT_CHECK); + DefInt(SQLITE_CONSTRAINT_COMMITHOOK); + DefInt(SQLITE_CONSTRAINT_FOREIGNKEY); + DefInt(SQLITE_CONSTRAINT_FUNCTION); + DefInt(SQLITE_CONSTRAINT_NOTNULL); + DefInt(SQLITE_CONSTRAINT_PRIMARYKEY); + DefInt(SQLITE_CONSTRAINT_TRIGGER); + DefInt(SQLITE_CONSTRAINT_UNIQUE); + DefInt(SQLITE_CONSTRAINT_VTAB); + DefInt(SQLITE_CONSTRAINT_ROWID); + DefInt(SQLITE_CONSTRAINT_PINNED); + DefInt(SQLITE_CONSTRAINT_DATATYPE); + DefInt(SQLITE_NOTICE_RECOVER_WAL); + DefInt(SQLITE_NOTICE_RECOVER_ROLLBACK); + DefInt(SQLITE_WARNING_AUTOINDEX); + DefInt(SQLITE_AUTH_USER); + DefInt(SQLITE_OK_LOAD_PERMANENTLY); + //DefInt(SQLITE_OK_SYMLINK) /* internal use only */; + } _DefGroup; + + DefGroup(serialize){ + DefInt(SQLITE_SERIALIZE_NOCOPY); + DefInt(SQLITE_DESERIALIZE_FREEONCLOSE); + DefInt(SQLITE_DESERIALIZE_READONLY); + DefInt(SQLITE_DESERIALIZE_RESIZEABLE); + } _DefGroup; + + DefGroup(syncFlags) { + DefInt(SQLITE_SYNC_NORMAL); + DefInt(SQLITE_SYNC_FULL); + DefInt(SQLITE_SYNC_DATAONLY); + } _DefGroup; + + DefGroup(trace) { + DefInt(SQLITE_TRACE_STMT); + DefInt(SQLITE_TRACE_PROFILE); + DefInt(SQLITE_TRACE_ROW); + DefInt(SQLITE_TRACE_CLOSE); + } _DefGroup; + + DefGroup(udfFlags) { + DefInt(SQLITE_DETERMINISTIC); + DefInt(SQLITE_DIRECTONLY); + DefInt(SQLITE_INNOCUOUS); + } _DefGroup; + + DefGroup(version) { + DefInt(SQLITE_VERSION_NUMBER); + DefStr(SQLITE_VERSION); + DefStr(SQLITE_SOURCE_ID); + } _DefGroup; + +#undef DefGroup +#undef DefStr +#undef DefInt +#undef _DefGroup + + /* + ** Emit an array of "StructBinder" struct descripions, which look + ** like: + ** + ** { + ** "name": "MyStruct", + ** "sizeof": 16, + ** "members": { + ** "member1": {"offset": 0,"sizeof": 4,"signature": "i"}, + ** "member2": {"offset": 4,"sizeof": 4,"signature": "p"}, + ** "member3": {"offset": 8,"sizeof": 8,"signature": "j"} + ** } + ** } + ** + ** Detailed documentation for those bits are in the docs for the + ** Jaccwabyt JS-side component. + */ + + /** Macros for emitting StructBinder description. */ +#define StructBinder__(TYPE) \ + n = 0; \ + outf("%s{", (nStruct++ ? ", " : "")); \ + out("\"name\": \"" # TYPE "\","); \ + outf("\"sizeof\": %d", (int)sizeof(TYPE)); \ + out(",\"members\": {"); +#define StructBinder_(T) StructBinder__(T) + /** ^^^ indirection needed to expand CurrentStruct */ +#define StructBinder StructBinder_(CurrentStruct) +#define _StructBinder CloseBrace(2) +#define M(MEMBER,SIG) \ + outf("%s\"%s\": " \ + "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \ + (n++ ? ", " : ""), #MEMBER, \ + (int)offsetof(CurrentStruct,MEMBER), \ + (int)sizeof(((CurrentStruct*)0)->MEMBER), \ + SIG) + + nStruct = 0; + out(", \"structs\": ["); { + +#define CurrentStruct sqlite3_vfs + StructBinder { + M(iVersion,"i"); + M(szOsFile,"i"); + M(mxPathname,"i"); + M(pNext,"p"); + M(zName,"s"); + M(pAppData,"p"); + M(xOpen,"i(pppip)"); + M(xDelete,"i(ppi)"); + M(xAccess,"i(ppip)"); + M(xFullPathname,"i(ppip)"); + M(xDlOpen,"p(pp)"); + M(xDlError,"p(pip)"); + M(xDlSym,"p()"); + M(xDlClose,"v(pp)"); + M(xRandomness,"i(pip)"); + M(xSleep,"i(pi)"); + M(xCurrentTime,"i(pp)"); + M(xGetLastError,"i(pip)"); + M(xCurrentTimeInt64,"i(pp)"); + M(xSetSystemCall,"i(ppp)"); + M(xGetSystemCall,"p(pp)"); + M(xNextSystemCall,"p(pp)"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_io_methods + StructBinder { + M(iVersion,"i"); + M(xClose,"i(p)"); + M(xRead,"i(ppij)"); + M(xWrite,"i(ppij)"); + M(xTruncate,"i(pj)"); + M(xSync,"i(pi)"); + M(xFileSize,"i(pp)"); + M(xLock,"i(pi)"); + M(xUnlock,"i(pi)"); + M(xCheckReservedLock,"i(pp)"); + M(xFileControl,"i(pip)"); + M(xSectorSize,"i(p)"); + M(xDeviceCharacteristics,"i(p)"); + M(xShmMap,"i(piiip)"); + M(xShmLock,"i(piii)"); + M(xShmBarrier,"v(p)"); + M(xShmUnmap,"i(pi)"); + M(xFetch,"i(pjip)"); + M(xUnfetch,"i(pjp)"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_file + StructBinder { + M(pMethods,"p"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_kvvfs_methods + StructBinder { + M(xRead,"i(sspi)"); + M(xWrite,"i(sss)"); + M(xDelete,"i(ss)"); + M(nKeySize,"i"); + } _StructBinder; +#undef CurrentStruct + +#if SQLITE_WASM_TESTS +#define CurrentStruct WasmTestStruct + StructBinder { + M(v4,"i"); + M(cstr,"s"); + M(ppV,"p"); + M(v8,"j"); + M(xFunc,"v(p)"); + } _StructBinder; +#undef CurrentStruct +#endif + + } out( "]"/*structs*/); + + out("}"/*top-level object*/); + *zPos = 0; + aBuffer[0] = '{'/*end of the race-condition workaround*/; + return aBuffer; +#undef StructBinder +#undef StructBinder_ +#undef StructBinder__ +#undef M +#undef _StructBinder +#undef CloseBrace +#undef out +#undef outf +#undef lenCheck +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** This function invokes the xDelete method of the given VFS (or the +** default VFS if pVfs is NULL), passing on the given filename. If +** zName is NULL, no default VFS is found, or it has no xDelete +** method, SQLITE_MISUSE is returned, else the result of the xDelete() +** call is returned. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char * zName){ + int rc = SQLITE_MISUSE /* ??? */; + if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0); + if( zName && pVfs && pVfs->xDelete ){ + rc = pVfs->xDelete(pVfs, zName, 1); + } + return rc; +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Returns a pointer to the given DB's VFS for the given DB name, +** defaulting to "main" if zDbName is 0. Returns 0 if no db with the +** given name is open. +*/ +SQLITE_WASM_KEEP +sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ + sqlite3_vfs * pVfs = 0; + sqlite3_file_control(pDb, zDbName ? zDbName : "main", + SQLITE_FCNTL_VFS_POINTER, &pVfs); + return pVfs; +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** This function resets the given db pointer's database as described at +** +** https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase +** +** Returns 0 on success, an SQLITE_xxx code on error. Returns +** SQLITE_MISUSE if pDb is NULL. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_db_reset(sqlite3*pDb){ + int rc = SQLITE_MISUSE; + if( pDb ){ + rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); + if( 0==rc ) rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0); + sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); + } + return rc; +} + +/* +** Uses the given database's VFS xRead to stream the db file's +** contents out to the given callback. The callback gets a single +** chunk of size n (its 2nd argument) on each call and must return 0 +** on success, non-0 on error. This function returns 0 on success, +** SQLITE_NOTFOUND if no db is open, or propagates any other non-0 +** code from the callback. Note that this is not thread-friendly: it +** expects that it will be the only thread reading the db file and +** takes no measures to ensure that is the case. +** +** This implementation appears to work fine, but +** sqlite3_wasm_db_serialize() is arguably the better way to achieve +** this. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_db_export_chunked( sqlite3* pDb, + int (*xCallback)(unsigned const char *zOut, int n) ){ + sqlite3_int64 nSize = 0; + sqlite3_int64 nPos = 0; + sqlite3_file * pFile = 0; + unsigned char buf[1024 * 8]; + int nBuf = (int)sizeof(buf); + int rc = pDb + ? sqlite3_file_control(pDb, "main", + SQLITE_FCNTL_FILE_POINTER, &pFile) + : SQLITE_NOTFOUND; + if( rc ) return rc; + rc = pFile->pMethods->xFileSize(pFile, &nSize); + if( rc ) return rc; + if(nSize % nBuf){ + /* DB size is not an even multiple of the buffer size. Reduce + ** buffer size so that we do not unduly inflate the db size + ** with zero-padding when exporting. */ + if(0 == nSize % 4096) nBuf = 4096; + else if(0 == nSize % 2048) nBuf = 2048; + else if(0 == nSize % 1024) nBuf = 1024; + else nBuf = 512; + } + for( ; 0==rc && nPospMethods->xRead(pFile, buf, nBuf, nPos); + if(SQLITE_IOERR_SHORT_READ == rc){ + rc = (nPos + nBuf) < nSize ? rc : 0/*assume EOF*/; + } + if( 0==rc ) rc = xCallback(buf, nBuf); + } + return rc; +} + +/* +** A proxy for sqlite3_serialize() which serializes the "main" schema +** of pDb, placing the serialized output in pOut and nOut. nOut may be +** NULL. If pDb or pOut are NULL then SQLITE_MISUSE is returned. If +** allocation of the serialized copy fails, SQLITE_NOMEM is returned. +** On success, 0 is returned and `*pOut` will contain a pointer to the +** memory unless mFlags includes SQLITE_SERIALIZE_NOCOPY and the +** database has no contiguous memory representation, in which case +** `*pOut` will be NULL but 0 will be returned. +** +** If `*pOut` is not NULL, the caller is responsible for passing it to +** sqlite3_free() to free it. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_db_serialize( sqlite3 *pDb, unsigned char **pOut, + sqlite3_int64 *nOut, unsigned int mFlags ){ + unsigned char * z; + if( !pDb || !pOut ) return SQLITE_MISUSE; + if(nOut) *nOut = 0; + z = sqlite3_serialize(pDb, "main", nOut, mFlags); + if( z || (SQLITE_SERIALIZE_NOCOPY & mFlags) ){ + *pOut = z; + return 0; + }else{ + return SQLITE_NOMEM; + } +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Creates a new file using the I/O API of the given VFS, containing +** the given number of bytes of the given data. If the file exists, +** it is truncated to the given length and populated with the given +** data. +** +** This function exists so that we can implement the equivalent of +** Emscripten's FS.createDataFile() in a VFS-agnostic way. This +** functionality is intended for use in uploading database files. +** +** If pVfs is NULL, sqlite3_vfs_find(0) is used. +** +** If zFile is NULL, pVfs is NULL (and sqlite3_vfs_find(0) returns +** NULL), or nData is negative, SQLITE_MISUSE are returned. +** +** On success, it creates a new file with the given name, populated +** with the fist nData bytes of pData. If pData is NULL, the file is +** created and/or truncated to nData bytes. +** +** Whether or not directory components of zFilename are created +** automatically or not is unspecified: that detail is left to the +** VFS. The "opfs" VFS, for example, create them. +** +** Not all VFSes support this functionality, e.g. the "kvvfs" does +** not. +** +** If an error happens while populating or truncating the file, the +** target file will be deleted (if needed) if this function created +** it. If this function did not create it, it is not deleted but may +** be left in an undefined state. +** +** Returns 0 on success. On error, it returns a code described above +** or propagates a code from one of the I/O methods. +** +** Design note: nData is an integer, instead of int64, for WASM +** portability, so that the API can still work in builds where BigInt +** support is disabled or unavailable. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, + const char *zFilename, + const unsigned char * pData, + int nData ){ + int rc; + sqlite3_file *pFile = 0; + sqlite3_io_methods const *pIo; + const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + int flagsOut = 0; + int fileExisted = 0; + int doUnlock = 0; + const unsigned char *pPos = pData; + const int blockSize = 512 + /* Because we are using pFile->pMethods->xWrite() for writing, and + ** it may have a buffer limit related to sqlite3's pager size, we + ** conservatively write in 512-byte blocks (smallest page + ** size). */; + + if( !pVfs ) pVfs = sqlite3_vfs_find(0); + if( !pVfs || !zFilename || nData<0 ) return SQLITE_MISUSE; + pVfs->xAccess(pVfs, zFilename, SQLITE_ACCESS_EXISTS, &fileExisted); + rc = sqlite3OsOpenMalloc(pVfs, zFilename, &pFile, openFlags, &flagsOut); + if(rc) return rc; + pIo = pFile->pMethods; + if( pIo->xLock ) { + /* We need xLock() in order to accommodate the OPFS VFS, as it + ** obtains a writeable handle via the lock operation and releases + ** it in xUnlock(). If we don't do those here, we have to add code + ** to the VFS to account check whether it was locked before + ** xFileSize(), xTruncate(), and the like, and release the lock + ** only if it was unlocked when the op was started. */ + rc = pIo->xLock(pFile, SQLITE_LOCK_EXCLUSIVE); + doUnlock = 0==rc; + } + if( 0==rc) rc = pIo->xTruncate(pFile, nData); + if( 0==rc && 0!=pData && nData>0 ){ + while( 0==rc && nData>0 ){ + const int n = nData>=blockSize ? blockSize : nData; + rc = pIo->xWrite(pFile, pPos, n, (sqlite3_int64)(pPos - pData)); + nData -= n; + pPos += n; + } + if( 0==rc && nData>0 ){ + assert( nDataxWrite(pFile, pPos, nData, (sqlite3_int64)(pPos - pData)); + } + } + if( pIo->xUnlock && doUnlock!=0 ) pIo->xUnlock(pFile, SQLITE_LOCK_NONE); + pIo->xClose(pFile); + if( rc!=0 && 0==fileExisted ){ + pVfs->xDelete(pVfs, zFilename, 1); + } + return rc; +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Allocates sqlite3KvvfsMethods.nKeySize bytes from +** sqlite3_wasm_pstack_alloc() and returns 0 if that allocation fails, +** else it passes that string to kvstorageMakeKey() and returns a +** NUL-terminated pointer to that string. It is up to the caller to +** use sqlite3_wasm_pstack_restore() to free the returned pointer. +*/ +SQLITE_WASM_KEEP +char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass, + const char *zKeyIn){ + assert(sqlite3KvvfsMethods.nKeySize>24); + char *zKeyOut = + (char *)sqlite3_wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize); + if(zKeyOut){ + kvstorageMakeKey(zClass, zKeyIn, zKeyOut); + } + return zKeyOut; +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Returns the pointer to the singleton object which holds the kvvfs +** I/O methods and associated state. +*/ +SQLITE_WASM_KEEP +sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ + return &sqlite3KvvfsMethods; +} + +#if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) +#include + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings, specifically +** only when building with Emscripten's WASMFS support. +** +** This function should only be called if the JS side detects the +** existence of the Origin-Private FileSystem (OPFS) APIs in the +** client. The first time it is called, this function instantiates a +** WASMFS backend impl for OPFS. On success, subsequent calls are +** no-ops. +** +** This function may be passed a "mount point" name, which must have a +** leading "/" and is currently restricted to a single path component, +** e.g. "/foo" is legal but "/foo/" and "/foo/bar" are not. If it is +** NULL or empty, it defaults to "/opfs". +** +** Returns 0 on success, SQLITE_NOMEM if instantiation of the backend +** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in +** the virtual FS fails. In builds compiled without SQLITE_ENABLE_WASMFS +** defined, SQLITE_NOTFOUND is returned without side effects. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_init_wasmfs(const char *zMountPoint){ + static backend_t pOpfs = 0; + if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs"; + if( !pOpfs ){ + pOpfs = wasmfs_create_opfs_backend(); + } + /** It's not enough to instantiate the backend. We have to create a + mountpoint in the VFS and attach the backend to it. */ + if( pOpfs && 0!=access(zMountPoint, F_OK) ){ + /* Note that this check and is not robust but it will + hypothetically suffice for the transient wasm-based virtual + filesystem we're currently running in. */ + const int rc = wasmfs_create_directory(zMountPoint, 0777, pOpfs); + /*emscripten_console_logf("OPFS mkdir(%s) rc=%d", zMountPoint, rc);*/ + if(rc) return SQLITE_IOERR; + } + return pOpfs ? 0 : SQLITE_NOMEM; +} +#else +SQLITE_WASM_KEEP +int sqlite3_wasm_init_wasmfs(const char *zUnused){ + //emscripten_console_warn("WASMFS OPFS is not compiled in."); + if(zUnused){/*unused*/} + return SQLITE_NOTFOUND; +} +#endif /* __EMSCRIPTEN__ && SQLITE_ENABLE_WASMFS */ + +#if SQLITE_WASM_TESTS + +SQLITE_WASM_KEEP +int sqlite3_wasm_test_intptr(int * p){ + return *p = *p * 2; +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64_max(void){ + return (int64_t)0x7fffffffffffffff; +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64_min(void){ + return ~sqlite3_wasm_test_int64_max(); +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64_times2(int64_t x){ + return x * 2; +} + +SQLITE_WASM_KEEP +void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){ + *max = sqlite3_wasm_test_int64_max(); + *min = sqlite3_wasm_test_int64_min(); + /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/ +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64ptr(int64_t * p){ + /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/ + return *p = *p * 2; +} + +SQLITE_WASM_KEEP +void sqlite3_wasm_test_stack_overflow(int recurse){ + if(recurse) sqlite3_wasm_test_stack_overflow(recurse); +} + +/* For testing the 'string-free' whwasmutil.xWrap() conversion. */ +SQLITE_WASM_KEEP +char * sqlite3_wasm_test_str_hello(int fail){ + char * s = fail ? 0 : (char *)malloc(6); + if(s){ + memcpy(s, "hello", 5); + s[5] = 0; + } + return s; +} +#endif /* SQLITE_WASM_TESTS */ + +#undef SQLITE_WASM_KEEP diff --git a/ext/wasm/api/sqlite3-worker1-promiser.js b/ext/wasm/api/sqlite3-worker1-promiser.js new file mode 100644 index 000000000..7360512d4 --- /dev/null +++ b/ext/wasm/api/sqlite3-worker1-promiser.js @@ -0,0 +1,259 @@ +/* + 2022-08-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 a Promise-based proxy for the sqlite3 Worker + API #1. It is intended to be included either from the main thread or + a Worker, but only if (A) the environment supports nested Workers + and (B) it's _not_ a Worker which loads the sqlite3 WASM/JS + module. This file's features will load that module and provide a + slightly simpler client-side interface than the slightly-lower-level + Worker API does. + + This script necessarily exposes one global symbol, but clients may + freely `delete` that symbol after calling it. +*/ +'use strict'; +/** + Configures an sqlite3 Worker API #1 Worker such that it can be + manipulated via a Promise-based interface and returns a factory + function which returns Promises for communicating with the worker. + This proxy has an _almost_ identical interface to the normal + worker API, with any exceptions documented below. + + It requires a configuration object with the following properties: + + - `worker` (required): a Worker instance which loads + `sqlite3-worker1.js` or a functional equivalent. Note that the + promiser factory replaces the worker.onmessage property. This + config option may alternately be a function, in which case this + function re-assigns this property with the result of calling that + function, enabling delayed instantiation of a Worker. + + - `onready` (optional, but...): this callback is called with no + arguments when the worker fires its initial + 'sqlite3-api'/'worker1-ready' message, which it does when + sqlite3.initWorker1API() completes its initialization. This is + the simplest way to tell the worker to kick off work at the + earliest opportunity. + + - `onunhandled` (optional): a callback which gets passed the + message event object for any worker.onmessage() events which + are not handled by this proxy. Ideally that "should" never + happen, as this proxy aims to handle all known message types. + + - `generateMessageId` (optional): a function which, when passed an + about-to-be-posted message object, generates a _unique_ message ID + for the message, which this API then assigns as the messageId + property of the message. It _must_ generate unique IDs on each call + so that dispatching can work. If not defined, a default generator + is used (which should be sufficient for most or all cases). + + - `debug` (optional): a console.debug()-style function for logging + information about messages. + + This function returns a stateful factory function with the + following interfaces: + + - Promise function(messageType, messageArgs) + - Promise function({message object}) + + The first form expects the "type" and "args" values for a Worker + message. The second expects an object in the form {type:..., + args:...} plus any other properties the client cares to set. This + function will always set the `messageId` property on the object, + even if it's already set, and will set the `dbId` property to the + current database ID if it is _not_ set in the message object. + + The function throws on error. + + The function installs a temporary message listener, posts a + message to the configured Worker, and handles the message's + response via the temporary message listener. The then() callback + of the returned Promise is passed the `message.data` property from + the resulting message, i.e. the payload from the worker, stripped + of the lower-level event state which the onmessage() handler + receives. + + Example usage: + + ``` + const config = {...}; + const sq3Promiser = sqlite3Worker1Promiser(config); + sq3Promiser('open', {filename:"/foo.db"}).then(function(msg){ + console.log("open response",msg); // => {type:'open', result: {filename:'/foo.db'}, ...} + }); + sq3Promiser({type:'close'}).then((msg)=>{ + console.log("close response",msg); // => {type:'close', result: {filename:'/foo.db'}, ...} + }); + ``` + + Differences from Worker API #1: + + - exec's {callback: STRING} option does not work via this + interface (it triggers an exception), but {callback: function} + does and works exactly like the STRING form does in the Worker: + the callback is called one time for each row of the result set, + passed the same worker message format as the worker API emits: + + {type:typeString, + row:VALUE, + rowNumber:1-based-#, + columnNames: array} + + Where `typeString` is an internally-synthesized message type string + used temporarily for worker message dispatching. It can be ignored + by all client code except that which tests this API. The `row` + property contains the row result in the form implied by the + `rowMode` option (defaulting to `'array'`). The `rowNumber` is a + 1-based integer value incremented by 1 on each call into th + callback. + + At the end of the result set, the same event is fired with + (row=undefined, rowNumber=null) to indicate that + the end of the result set has been reached. Note that the rows + arrive via worker-posted messages, with all the implications + of that. +*/ +self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ + // Inspired by: https://stackoverflow.com/a/52439530 + if(1===arguments.length && 'function'===typeof arguments[0]){ + const f = config; + config = Object.assign(Object.create(null), callee.defaultConfig); + config.onready = f; + }else{ + config = Object.assign(Object.create(null), callee.defaultConfig, config); + } + const handlerMap = Object.create(null); + const noop = function(){}; + const err = config.onerror + || noop /* config.onerror is intentionally undocumented + pending finding a less ambiguous name */; + const debug = config.debug || noop; + const idTypeMap = config.generateMessageId ? undefined : Object.create(null); + const genMsgId = config.generateMessageId || function(msg){ + return msg.type+'#'+(idTypeMap[msg.type] = (idTypeMap[msg.type]||0) + 1); + }; + const toss = (...args)=>{throw new Error(args.join(' '))}; + if(!config.worker) config.worker = callee.defaultConfig.worker; + if('function'===typeof config.worker) config.worker = config.worker(); + let dbId; + config.worker.onmessage = function(ev){ + ev = ev.data; + debug('worker1.onmessage',ev); + let msgHandler = handlerMap[ev.messageId]; + if(!msgHandler){ + if(ev && 'sqlite3-api'===ev.type && 'worker1-ready'===ev.result) { + /*fired one time when the Worker1 API initializes*/ + if(config.onready) config.onready(); + return; + } + msgHandler = handlerMap[ev.type] /* check for exec per-row callback */; + if(msgHandler && msgHandler.onrow){ + msgHandler.onrow(ev); + return; + } + if(config.onunhandled) config.onunhandled(arguments[0]); + else err("sqlite3Worker1Promiser() unhandled worker message:",ev); + return; + } + delete handlerMap[ev.messageId]; + switch(ev.type){ + case 'error': + msgHandler.reject(ev); + return; + case 'open': + if(!dbId) dbId = ev.dbId; + break; + case 'close': + if(ev.dbId===dbId) dbId = undefined; + break; + default: + break; + } + try {msgHandler.resolve(ev)} + catch(e){msgHandler.reject(e)} + }/*worker.onmessage()*/; + return function(/*(msgType, msgArgs) || (msgEnvelope)*/){ + let msg; + if(1===arguments.length){ + msg = arguments[0]; + }else if(2===arguments.length){ + msg = { + type: arguments[0], + args: arguments[1] + }; + }else{ + toss("Invalid arugments for sqlite3Worker1Promiser()-created factory."); + } + if(!msg.dbId) msg.dbId = dbId; + msg.messageId = genMsgId(msg); + msg.departureTime = performance.now(); + const proxy = Object.create(null); + proxy.message = msg; + let rowCallbackId /* message handler ID for exec on-row callback proxy */; + if('exec'===msg.type && msg.args){ + if('function'===typeof msg.args.callback){ + rowCallbackId = msg.messageId+':row'; + proxy.onrow = msg.args.callback; + msg.args.callback = rowCallbackId; + handlerMap[rowCallbackId] = proxy; + }else if('string' === typeof msg.args.callback){ + toss("exec callback may not be a string when using the Promise interface."); + /** + Design note: the reason for this limitation is that this + API takes over worker.onmessage() and the client has no way + of adding their own message-type handlers to it. Per-row + callbacks are implemented as short-lived message.type + mappings for worker.onmessage(). + + We "could" work around this by providing a new + config.fallbackMessageHandler (or some such) which contains + a map of event type names to callbacks. Seems like overkill + for now, seeing as the client can pass callback functions + to this interface (whereas the string-form "callback" is + needed for the over-the-Worker interface). + */ + } + } + //debug("requestWork", msg); + let p = new Promise(function(resolve, reject){ + proxy.resolve = resolve; + proxy.reject = reject; + handlerMap[msg.messageId] = proxy; + debug("Posting",msg.type,"message to Worker dbId="+(dbId||'default')+':',msg); + config.worker.postMessage(msg); + }); + if(rowCallbackId) p = p.finally(()=>delete handlerMap[rowCallbackId]); + return p; + }; +}/*sqlite3Worker1Promiser()*/; +self.sqlite3Worker1Promiser.defaultConfig = { + worker: function(){ + let theJs = "sqlite3-worker1.js"; + if(this.currentScript){ + const src = this.currentScript.src.split('/'); + src.pop(); + theJs = src.join('/')+'/' + theJs; + //console.warn("promiser currentScript, theJs =",this.currentScript,theJs); + }else{ + //console.warn("promiser self.location =",self.location); + const urlParams = new URL(self.location.href).searchParams; + if(urlParams.has('sqlite3.dir')){ + theJs = urlParams.get('sqlite3.dir') + '/' + theJs; + } + } + return new Worker(theJs + self.location.search); + }.bind({ + currentScript: self?.document?.currentScript + }), + onerror: (...args)=>console.error('worker1 promiser error',...args) +}; diff --git a/ext/wasm/api/sqlite3-worker1.js b/ext/wasm/api/sqlite3-worker1.js new file mode 100644 index 000000000..942437908 --- /dev/null +++ b/ext/wasm/api/sqlite3-worker1.js @@ -0,0 +1,49 @@ +/* + 2022-05-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. + + *********************************************************************** + + This is a JS Worker file for the main sqlite3 api. It loads + sqlite3.js, initializes the module, and postMessage()'s a message + after the module is initialized: + + {type: 'sqlite3-api', result: 'worker1-ready'} + + This seemingly superfluous level of indirection is necessary when + loading sqlite3.js via a Worker. Instantiating a worker with new + Worker("sqlite.js") will not (cannot) call sqlite3InitModule() to + initialize the module due to a timing/order-of-operations conflict + (and that symbol is not exported in a way that a Worker loading it + that way can see it). Thus JS code wanting to load the sqlite3 + Worker-specific API needs to pass _this_ file (or equivalent) to the + Worker constructor and then listen for an event in the form shown + above in order to know when the module has completed initialization. + + This file accepts a URL arguments to adjust how it loads sqlite3.js: + + - `sqlite3.dir`, if set, treats the given directory name as the + directory from which `sqlite3.js` will be loaded. +*/ +"use strict"; +(()=>{ + const urlParams = new URL(self.location.href).searchParams; + let theJs = 'sqlite3.js'; + if(urlParams.has('sqlite3.dir')){ + theJs = urlParams.get('sqlite3.dir') + '/' + theJs; + } + //console.warn("worker1 theJs =",theJs); + importScripts(theJs); + sqlite3InitModule().then((sqlite3)=>{ + if(sqlite3.capi.sqlite3_wasmfs_opfs_dir){ + sqlite3.capi.sqlite3_wasmfs_opfs_dir(); + } + sqlite3.initWorker1API(); + }); +})(); diff --git a/ext/wasm/batch-runner.html b/ext/wasm/batch-runner.html new file mode 100644 index 000000000..5258f9597 --- /dev/null +++ b/ext/wasm/batch-runner.html @@ -0,0 +1,90 @@ + + + + + + + + + sqlite3-api batch SQL runner + + +

    sqlite3-api batch SQL runner
    + +
    +
    +
    Initializing app...
    +
    + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
    +
    +
    Downloading...
    +
    + +
    +

    + This page is for batch-running extracts from the output + of speedtest1 --script, as well as other standalone SQL + scripts. +

    +

    ACHTUNG: this file requires a generated input list + file. Run "make batch" from this directory to generate it. +

    + + +
    +
    +
    + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + diff --git a/ext/wasm/batch-runner.js b/ext/wasm/batch-runner.js new file mode 100644 index 000000000..11c43217f --- /dev/null +++ b/ext/wasm/batch-runner.js @@ -0,0 +1,588 @@ +/* + 2022-08-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. + + *********************************************************************** + + A basic batch SQL runner for sqlite3-api.js. This file must be run in + main JS thread and sqlite3.js must have been loaded before it. +*/ +'use strict'; +(function(){ + const toss = function(...args){throw new Error(args.join(' '))}; + const warn = console.warn.bind(console); + let sqlite3; + const urlParams = new URL(self.location.href).searchParams; + const cacheSize = (()=>{ + if(urlParams.has('cachesize')) return +urlParams.get('cachesize'); + return 200; + })(); + + /** Throws if the given sqlite3 result code is not 0. */ + const checkSqliteRc = (dbh,rc)=>{ + if(rc) toss("Prepare failed:",sqlite3.capi.sqlite3_errmsg(dbh)); + }; + + const sqlToDrop = [ + "SELECT type,name FROM sqlite_schema ", + "WHERE name NOT LIKE 'sqlite\\_%' escape '\\' ", + "AND name NOT LIKE '\\_%' escape '\\'" + ].join(''); + + const clearDbWebSQL = function(db){ + db.handle.transaction(function(tx){ + const onErr = (e)=>console.error(e); + const callback = function(tx, result){ + const rows = result.rows; + let i, n; + i = n = rows.length; + while(i--){ + const row = rows.item(i); + const name = JSON.stringify(row.name); + const type = row.type; + switch(type){ + case 'index': case 'table': + case 'trigger': case 'view': { + const sql2 = 'DROP '+type+' '+name; + tx.executeSql(sql2, [], ()=>{}, onErr); + break; + } + default: + warn("Unhandled db entry type:",type,'name =',name); + break; + } + } + }; + tx.executeSql(sqlToDrop, [], callback, onErr); + db.handle.changeVersion(db.handle.version, "", ()=>{}, onErr, ()=>{}); + }); + }; + + const clearDbSqlite = function(db){ + // This would be SO much easier with the oo1 API, but we specifically want to + // inject metrics we can't get via that API, and we cannot reliably (OPFS) + // open the same DB twice to clear it using that API, so... + const rc = sqlite3.wasm.exports.sqlite3_wasm_db_reset(db.handle); + App.logHtml("reset db rc =",rc,db.id, db.filename); + }; + + + const E = (s)=>document.querySelector(s); + const App = { + e: { + output: E('#test-output'), + selSql: E('#sql-select'), + btnRun: E('#sql-run'), + btnRunNext: E('#sql-run-next'), + btnRunRemaining: E('#sql-run-remaining'), + btnExportMetrics: E('#export-metrics'), + btnClear: E('#output-clear'), + btnReset: E('#db-reset'), + cbReverseLog: E('#cb-reverse-log-order'), + selImpl: E('#select-impl'), + fsToolbar: E('#toolbar') + }, + db: Object.create(null), + dbs: Object.create(null), + cache:{}, + log: console.log.bind(console), + warn: console.warn.bind(console), + cls: function(){this.e.output.innerHTML = ''}, + logHtml2: function(cssClass,...args){ + const ln = document.createElement('div'); + if(cssClass) ln.classList.add(cssClass); + ln.append(document.createTextNode(args.join(' '))); + this.e.output.append(ln); + //this.e.output.lastElementChild.scrollIntoViewIfNeeded(); + }, + logHtml: function(...args){ + console.log(...args); + if(1) this.logHtml2('', ...args); + }, + logErr: function(...args){ + console.error(...args); + if(1) this.logHtml2('error', ...args); + }, + + execSql: async function(name,sql){ + const db = this.getSelectedDb(); + const banner = "========================================"; + this.logHtml(banner, + "Running",name,'('+sql.length,'bytes) using',db.id); + const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; + let pStmt = 0, pSqlBegin; + const stack = wasm.scopedAllocPush(); + const metrics = db.metrics = Object.create(null); + metrics.prepTotal = metrics.stepTotal = 0; + metrics.stmtCount = 0; + metrics.malloc = 0; + metrics.strcpy = 0; + this.blockControls(true); + if(this.gotErr){ + this.logErr("Cannot run SQL: error cleanup is pending."); + return; + } + // Run this async so that the UI can be updated for the above header... + const endRun = ()=>{ + metrics.evalSqlEnd = performance.now(); + metrics.evalTimeTotal = (metrics.evalSqlEnd - metrics.evalSqlStart); + this.logHtml(db.id,"metrics:",JSON.stringify(metrics, undefined, ' ')); + this.logHtml("prepare() count:",metrics.stmtCount); + this.logHtml("Time in prepare_v2():",metrics.prepTotal,"ms", + "("+(metrics.prepTotal / metrics.stmtCount),"ms per prepare())"); + this.logHtml("Time in step():",metrics.stepTotal,"ms", + "("+(metrics.stepTotal / metrics.stmtCount),"ms per step())"); + this.logHtml("Total runtime:",metrics.evalTimeTotal,"ms"); + this.logHtml("Overhead (time - prep - step):", + (metrics.evalTimeTotal - metrics.prepTotal - metrics.stepTotal)+"ms"); + this.logHtml(banner,"End of",name); + }; + + let runner; + if('websql'===db.id){ + const who = this; + runner = function(resolve, reject){ + /* WebSQL cannot execute multiple statements, nor can it execute SQL without + an explicit transaction. Thus we have to do some fragile surgery on the + input SQL. Since we're only expecting carefully curated inputs, the hope is + that this will suffice. PS: it also can't run most SQL functions, e.g. even + instr() results in "not authorized". */ + if('string'!==typeof sql){ // assume TypedArray + sql = new TextDecoder().decode(sql); + } + sql = sql.replace(/-- [^\n]+\n/g,''); // comment lines interfere with our split() + const sqls = sql.split(/;+\n/); + const rxBegin = /^BEGIN/i, rxCommit = /^COMMIT/i; + try { + const nextSql = ()=>{ + let x = sqls.shift(); + while(sqls.length && !x) x = sqls.shift(); + return x && x.trim(); + }; + const who = this; + const transaction = function(tx){ + try { + let s; + /* Try to approximate the spirit of the input scripts + by running batches bound by BEGIN/COMMIT statements. */ + for(s = nextSql(); !!s; s = nextSql()){ + if(rxBegin.test(s)) continue; + else if(rxCommit.test(s)) break; + //console.log("websql sql again",sqls.length, s); + ++metrics.stmtCount; + const t = performance.now(); + tx.executeSql(s,[], ()=>{}, (t,e)=>{ + console.error("WebSQL error",e,"SQL =",s); + who.logErr(e.message); + //throw e; + return false; + }); + metrics.stepTotal += performance.now() - t; + } + }catch(e){ + who.logErr("transaction():",e.message); + throw e; + } + }; + const n = sqls.length; + const nextBatch = function(){ + if(sqls.length){ + console.log("websql sqls.length",sqls.length,'of',n); + db.handle.transaction(transaction, (e)=>{ + who.logErr("Ignoring and contiuing:",e.message) + //reject(e); + return false; + }, nextBatch); + }else{ + resolve(who); + } + }; + metrics.evalSqlStart = performance.now(); + nextBatch(); + }catch(e){ + //this.gotErr = e; + console.error("websql error:",e); + who.logErr(e.message); + //reject(e); + } + }.bind(this); + }else{/*sqlite3 db...*/ + runner = function(resolve, reject){ + metrics.evalSqlStart = performance.now(); + try { + let t; + let sqlByteLen = sql.byteLength; + const [ppStmt, pzTail] = wasm.scopedAllocPtr(2); + t = performance.now(); + pSqlBegin = wasm.scopedAlloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed"); + metrics.malloc = performance.now() - t; + metrics.byteLength = sqlByteLen; + let pSql = pSqlBegin; + const pSqlEnd = pSqlBegin + sqlByteLen; + t = performance.now(); + wasm.heap8().set(sql, pSql); + wasm.setMemValue(pSql + sqlByteLen, 0); + metrics.strcpy = performance.now() - t; + let breaker = 0; + while(pSql && wasm.getMemValue(pSql,'i8')){ + wasm.setPtrValue(ppStmt, 0); + wasm.setPtrValue(pzTail, 0); + t = performance.now(); + let rc = capi.sqlite3_prepare_v3( + db.handle, pSql, sqlByteLen, 0, ppStmt, pzTail + ); + metrics.prepTotal += performance.now() - t; + checkSqliteRc(db.handle, rc); + pStmt = wasm.getPtrValue(ppStmt); + pSql = wasm.getPtrValue(pzTail); + sqlByteLen = pSqlEnd - pSql; + if(!pStmt) continue/*empty statement*/; + ++metrics.stmtCount; + t = performance.now(); + rc = capi.sqlite3_step(pStmt); + capi.sqlite3_finalize(pStmt); + pStmt = 0; + metrics.stepTotal += performance.now() - t; + switch(rc){ + case capi.SQLITE_ROW: + case capi.SQLITE_DONE: break; + default: checkSqliteRc(db.handle, rc); toss("Not reached."); + } + } + resolve(this); + }catch(e){ + if(pStmt) capi.sqlite3_finalize(pStmt); + //this.gotErr = e; + reject(e); + }finally{ + capi.sqlite3_exec(db.handle,"rollback;",0,0,0); + wasm.scopedAllocPop(stack); + } + }.bind(this); + } + let p; + if(1){ + p = new Promise(function(res,rej){ + setTimeout(()=>runner(res, rej), 50)/*give UI a chance to output the "running" banner*/; + }); + }else{ + p = new Promise(runner); + } + return p.catch( + (e)=>this.logErr("Error via execSql("+name+",...):",e.message) + ).finally(()=>{ + endRun(); + this.blockControls(false); + }); + }, + + clearDb: function(){ + const db = this.getSelectedDb(); + if('websql'===db.id){ + this.logErr("TODO: clear websql db."); + return; + } + if(!db.handle) return; + const capi = this.sqlite3, wasm = this.sqlite3.wasm; + //const scope = wasm.scopedAllocPush( + this.logErr("TODO: clear db"); + }, + + /** + Loads batch-runner.list and populates the selection list from + it. Returns a promise which resolves to nothing in particular + when it completes. Only intended to be run once at the start + of the app. + */ + loadSqlList: async function(){ + const sel = this.e.selSql; + sel.innerHTML = ''; + this.blockControls(true); + const infile = 'batch-runner.list'; + this.logHtml("Loading list of SQL files:", infile); + let txt; + try{ + const r = await fetch(infile); + if(404 === r.status){ + toss("Missing file '"+infile+"'."); + } + if(!r.ok) toss("Loading",infile,"failed:",r.statusText); + txt = await r.text(); + const warning = E('#warn-list'); + if(warning) warning.remove(); + }catch(e){ + this.logErr(e.message); + throw e; + }finally{ + this.blockControls(false); + } + const list = txt.split(/\n+/); + let opt; + if(0){ + opt = document.createElement('option'); + opt.innerText = "Select file to evaluate..."; + opt.value = ''; + opt.disabled = true; + opt.selected = true; + sel.appendChild(opt); + } + list.forEach(function(fn){ + if(!fn) return; + opt = document.createElement('option'); + opt.value = fn; + opt.innerText = fn.split('/').pop(); + sel.appendChild(opt); + }); + this.logHtml("Loaded",infile); + }, + + /** Fetch ./fn and return its contents as a Uint8Array. */ + fetchFile: async function(fn, cacheIt=false){ + if(cacheIt && this.cache[fn]) return this.cache[fn]; + this.logHtml("Fetching",fn,"..."); + let sql; + try { + const r = await fetch(fn); + if(!r.ok) toss("Fetch failed:",r.statusText); + sql = new Uint8Array(await r.arrayBuffer()); + }catch(e){ + this.logErr(e.message); + throw e; + } + this.logHtml("Fetched",sql.length,"bytes from",fn); + if(cacheIt) this.cache[fn] = sql; + return sql; + }/*fetchFile()*/, + + /** Disable or enable certain UI controls. */ + blockControls: function(disable){ + //document.querySelectorAll('.disable-during-eval').forEach((e)=>e.disabled = disable); + this.e.fsToolbar.disabled = disable; + }, + + /** + Converts this.metrics() to a form which is suitable for easy conversion to + CSV. It returns an array of arrays. The first sub-array is the column names. + The 2nd and subsequent are the values, one per test file (only the most recent + metrics are kept for any given file). + */ + metricsToArrays: function(){ + const rc = []; + Object.keys(this.dbs).sort().forEach((k)=>{ + const d = this.dbs[k]; + const m = d.metrics; + delete m.evalSqlStart; + delete m.evalSqlEnd; + const mk = Object.keys(m).sort(); + if(!rc.length){ + rc.push(['db', ...mk]); + } + const row = [k.split('/').pop()/*remove dir prefix from filename*/]; + rc.push(row); + row.push(...mk.map((kk)=>m[kk])); + }); + return rc; + }, + + metricsToBlob: function(colSeparator='\t'){ + const ar = [], ma = this.metricsToArrays(); + if(!ma.length){ + this.logErr("Metrics are empty. Run something."); + return; + } + ma.forEach(function(row){ + ar.push(row.join(colSeparator),'\n'); + }); + return new Blob(ar); + }, + + downloadMetrics: function(){ + const b = this.metricsToBlob(); + if(!b) return; + const url = URL.createObjectURL(b); + const a = document.createElement('a'); + a.href = url; + a.download = 'batch-runner-js-'+((new Date().getTime()/1000) | 0)+'.csv'; + this.logHtml("Triggering download of",a.download); + document.body.appendChild(a); + a.click(); + setTimeout(()=>{ + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 500); + }, + + /** + Fetch file fn and eval it as an SQL blob. This is an async + operation and returns a Promise which resolves to this + object on success. + */ + evalFile: async function(fn){ + const sql = await this.fetchFile(fn); + return this.execSql(fn,sql); + }/*evalFile()*/, + + /** + Clears all DB tables in all _opened_ databases. Because of + disparities between backends, we cannot simply "unlink" the + databases to clean them up. + */ + clearStorage: function(onlySelectedDb=false){ + const list = onlySelectedDb + ? [('boolean'===typeof onlySelectedDb) + ? this.dbs[this.e.selImpl.value] + : onlySelectedDb] + : Object.values(this.dbs); + for(let db of list){ + if(db && db.handle){ + this.logHtml("Clearing db",db.id); + db.clear(); + } + } + }, + + /** + Fetches the handle of the db associated with + this.e.selImpl.value, opening it if needed. + */ + getSelectedDb: function(){ + if(!this.dbs.memdb){ + for(let opt of this.e.selImpl.options){ + const d = this.dbs[opt.value] = Object.create(null); + d.id = opt.value; + switch(d.id){ + case 'virtualfs': + d.filename = 'file:/virtualfs.sqlite3?vfs=unix-none'; + break; + case 'memdb': + d.filename = ':memory:'; + break; + case 'wasmfs-opfs': + d.filename = 'file:'+( + this.sqlite3.capi.sqlite3_wasmfs_opfs_dir() + )+'/wasmfs-opfs.sqlite3b'; + break; + case 'websql': + d.filename = 'websql.db'; + break; + default: + this.logErr("Unhandled db selection option (see details in the console).",opt); + toss("Unhandled db init option"); + } + } + }/*first-time init*/ + const dbId = this.e.selImpl.value; + const d = this.dbs[dbId]; + if(d.handle) return d; + if('websql' === dbId){ + d.handle = self.openDatabase('batch-runner', '0.1', 'foo', 1024 * 1024 * 50); + d.clear = ()=>clearDbWebSQL(d); + d.handle.transaction(function(tx){ + tx.executeSql("PRAGMA cache_size="+cacheSize); + App.logHtml(dbId,"cache_size =",cacheSize); + }); + }else{ + const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; + const stack = wasm.scopedAllocPush(); + let pDb = 0; + try{ + const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; + const ppDb = wasm.scopedAllocPtr(); + const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null); + pDb = wasm.getPtrValue(ppDb) + if(rc) toss("sqlite3_open_v2() failed with code",rc); + capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0); + this.logHtml(dbId,"cache_size =",cacheSize); + }catch(e){ + if(pDb) capi.sqlite3_close_v2(pDb); + }finally{ + wasm.scopedAllocPop(stack); + } + d.handle = pDb; + d.clear = ()=>clearDbSqlite(d); + } + d.clear(); + this.logHtml("Opened db:",dbId,d.filename); + console.log("db =",d); + return d; + }, + + run: function(sqlite3){ + delete this.run; + this.sqlite3 = sqlite3; + const capi = sqlite3.capi, wasm = sqlite3.wasm; + this.logHtml("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); + this.logHtml("WASM heap size =",wasm.heap8().length); + this.loadSqlList(); + if(capi.sqlite3_wasmfs_opfs_dir()){ + E('#warn-opfs').classList.remove('hidden'); + }else{ + E('#warn-opfs').remove(); + E('option[value=wasmfs-opfs]').disabled = true; + } + if('function' === typeof self.openDatabase){ + E('#warn-websql').classList.remove('hidden'); + }else{ + E('option[value=websql]').disabled = true; + E('#warn-websql').remove(); + } + const who = this; + if(this.e.cbReverseLog.checked){ + this.e.output.classList.add('reverse'); + } + this.e.cbReverseLog.addEventListener('change', function(){ + who.e.output.classList[this.checked ? 'add' : 'remove']('reverse'); + }, false); + this.e.btnClear.addEventListener('click', ()=>this.cls(), false); + this.e.btnRun.addEventListener('click', function(){ + if(!who.e.selSql.value) return; + who.evalFile(who.e.selSql.value); + }, false); + this.e.btnRunNext.addEventListener('click', function(){ + ++who.e.selSql.selectedIndex; + if(!who.e.selSql.value) return; + who.evalFile(who.e.selSql.value); + }, false); + this.e.btnReset.addEventListener('click', function(){ + who.clearStorage(true); + }, false); + this.e.btnExportMetrics.addEventListener('click', function(){ + who.logHtml2('warning',"Triggering download of metrics CSV. Check your downloads folder."); + who.downloadMetrics(); + //const m = who.metricsToArrays(); + //console.log("Metrics:",who.metrics, m); + }); + this.e.selImpl.addEventListener('change', function(){ + who.getSelectedDb(); + }); + this.e.btnRunRemaining.addEventListener('click', async function(){ + let v = who.e.selSql.value; + const timeStart = performance.now(); + while(v){ + await who.evalFile(v); + if(who.gotError){ + who.logErr("Error handling script",v,":",who.gotError.message); + break; + } + ++who.e.selSql.selectedIndex; + v = who.e.selSql.value; + } + const timeTotal = performance.now() - timeStart; + who.logHtml("Run-remaining time:",timeTotal,"ms ("+(timeTotal/1000/60)+" minute(s))"); + who.clearStorage(); + }, false); + }/*run()*/ + }/*App*/; + + self.sqlite3TestModule.initSqlite3().then(function(sqlite3_){ + sqlite3 = sqlite3_; + self.App = App /* only to facilitate dev console access */; + App.run(sqlite3); + }); +})(); diff --git a/ext/wasm/common/SqliteTestUtil.js b/ext/wasm/common/SqliteTestUtil.js new file mode 100644 index 000000000..5ed423785 --- /dev/null +++ b/ext/wasm/common/SqliteTestUtil.js @@ -0,0 +1,236 @@ +/* + 2022-05-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 bootstrapping code used by various test scripts + which live in this file's directory. +*/ +'use strict'; +(function(self){ + /* querySelectorAll() proxy */ + const EAll = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelectorAll(arguments[arguments.length-1]); + }; + /* querySelector() proxy */ + const E = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelector(arguments[arguments.length-1]); + }; + + /** + Helpers for writing sqlite3-specific tests. + */ + self.SqliteTestUtil = { + /** Running total of the number of tests run via + this API. */ + counter: 0, + /** + If expr is a function, it is called and its result + is returned, coerced to a bool, else expr, coerced to + a bool, is returned. + */ + toBool: function(expr){ + return (expr instanceof Function) ? !!expr() : !!expr; + }, + /** abort() if expr is false. If expr is a function, it + is called and its result is evaluated. + */ + assert: function f(expr, msg){ + if(!f._){ + f._ = ('undefined'===typeof abort + ? (msg)=>{throw new Error(msg)} + : abort); + } + ++this.counter; + if(!this.toBool(expr)){ + f._(msg || "Assertion failed."); + } + return this; + }, + /** Identical to assert() but throws instead of calling + abort(). */ + affirm: function(expr, msg){ + ++this.counter; + if(!this.toBool(expr)) throw new Error(msg || "Affirmation failed."); + return this; + }, + /** Calls f() and squelches any exception it throws. If it + does not throw, this function throws. */ + mustThrow: function(f, msg){ + ++this.counter; + let err; + try{ f(); } catch(e){err=e;} + if(!err) throw new Error(msg || "Expected exception."); + return this; + }, + /** + Works like mustThrow() but expects filter to be a regex, + function, or string to match/filter the resulting exception + against. If f() does not throw, this test fails and an Error is + thrown. If filter is a regex, the test passes if + filter.test(error.message) passes. If it's a function, the test + passes if filter(error) returns truthy. If it's a string, the + test passes if the filter matches the exception message + precisely. In all other cases the test fails, throwing an + Error. + + If it throws, msg is used as the error report unless it's falsy, + in which case a default is used. + */ + mustThrowMatching: function(f, filter, msg){ + ++this.counter; + let err; + try{ f(); } catch(e){err=e;} + if(!err) throw new Error(msg || "Expected exception."); + let pass = false; + if(filter instanceof RegExp) pass = filter.test(err.message); + else if(filter instanceof Function) pass = filter(err); + else if('string' === typeof filter) pass = (err.message === filter); + if(!pass){ + throw new Error(msg || ("Filter rejected this exception: "+err.message)); + } + return this; + }, + /** Throws if expr is truthy or expr is a function and expr() + returns truthy. */ + throwIf: function(expr, msg){ + ++this.counter; + if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); + return this; + }, + /** Throws if expr is falsy or expr is a function and expr() + returns falsy. */ + throwUnless: function(expr, msg){ + ++this.counter; + if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); + return this; + }, + + /** + Parses window.location.search-style string into an object + containing key/value pairs of URL arguments (already + urldecoded). The object is created using Object.create(null), + so contains only parsed-out properties and has no prototype + (and thus no inherited properties). + + If the str argument is not passed (arguments.length==0) then + window.location.search.substring(1) is used by default. If + neither str is passed in nor window exists then false is returned. + + On success it returns an Object containing the key/value pairs + parsed from the string. Keys which have no value are treated + has having the boolean true value. + + Pedantic licensing note: this code has appeared in other source + trees, but was originally written by the same person who pasted + it into those trees. + */ + processUrlArgs: function(str) { + if( 0 === arguments.length ) { + if( ('undefined' === typeof window) || + !window.location || + !window.location.search ) return false; + else str = (''+window.location.search).substring(1); + } + if( ! str ) return false; + str = (''+str).split(/#/,2)[0]; // remove #... to avoid it being added as part of the last value. + const args = Object.create(null); + const sp = str.split(/&+/); + const rx = /^([^=]+)(=(.+))?/; + var i, m; + for( i in sp ) { + m = rx.exec( sp[i] ); + if( ! m ) continue; + args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true); + } + return args; + } + }; + + + /** + This is a module object for use with the emscripten-installed + sqlite3InitModule() factory function. + */ + self.sqlite3TestModule = { + /** + Array of functions to call after Emscripten has initialized the + wasm module. Each gets passed the Emscripten module object + (which is _this_ object). + */ + postRun: [ + /* function(theModule){...} */ + ], + //onRuntimeInitialized: function(){}, + /* Proxy for C-side stdout output. */ + print: (...args)=>{console.log(...args)}, + /* Proxy for C-side stderr output. */ + printErr: (...args)=>{console.error(...args)}, + /** + Called by the Emscripten module init bits to report loading + progress. It gets passed an empty argument when loading is done + (after onRuntimeInitialized() and any this.postRun callbacks + have been run). + */ + setStatus: function f(text){ + if(!f.last){ + f.last = { text: '', step: 0 }; + f.ui = { + status: E('#module-status'), + progress: E('#module-progress'), + spinner: E('#module-spinner') + }; + } + if(text === f.last.text) return; + f.last.text = text; + if(f.ui.progress){ + f.ui.progress.value = f.last.step; + f.ui.progress.max = f.last.step + 1; + } + ++f.last.step; + if(text) { + f.ui.status.classList.remove('hidden'); + f.ui.status.innerText = text; + }else{ + if(f.ui.progress){ + f.ui.progress.remove(); + f.ui.spinner.remove(); + delete f.ui.progress; + delete f.ui.spinner; + } + f.ui.status.classList.add('hidden'); + } + }, + /** + Config options used by the Emscripten-dependent initialization + which happens via this.initSqlite3(). This object gets + (indirectly) passed to sqlite3ApiBootstrap() to configure the + sqlite3 API. + */ + sqlite3ApiConfig: { + wasmfsOpfsDir: "/opfs" + }, + /** + Intended to be called by apps which need to call the + Emscripten-installed sqlite3InitModule() routine. This function + temporarily installs this.sqlite3ApiConfig into the self + object, calls it sqlite3InitModule(), and removes + self.sqlite3ApiConfig after initialization is done. Returns the + promise from sqlite3InitModule(), and the next then() handler + will get the sqlite3 API object as its argument. + */ + initSqlite3: function(){ + self.sqlite3ApiConfig = this.sqlite3ApiConfig; + return self.sqlite3InitModule(this).finally(()=>delete self.sqlite3ApiConfig); + } + }; +})(self/*window or worker*/); diff --git a/ext/fiddle/emscripten.css b/ext/wasm/common/emscripten.css similarity index 100% rename from ext/fiddle/emscripten.css rename to ext/wasm/common/emscripten.css diff --git a/ext/wasm/common/testing.css b/ext/wasm/common/testing.css new file mode 100644 index 000000000..9438b330c --- /dev/null +++ b/ext/wasm/common/testing.css @@ -0,0 +1,63 @@ +body { + display: flex; + flex-direction: column; + flex-wrap: wrap; +} +textarea { + font-family: monospace; +} +header { + font-size: 130%; + font-weight: bold; +} +.hidden, .initially-hidden { + position: absolute !important; + opacity: 0 !important; + pointer-events: none !important; + display: none !important; +} +fieldset.options { + font-size: 75%; +} +fieldset > legend { + padding: 0 0.5em; +} +span.labeled-input { + padding: 0.25em; + margin: 0.25em 0.5em; + border-radius: 0.25em; + white-space: nowrap; + background: #0002; +} +.center { text-align: center; } +.error { + color: red; + background-color: yellow; +} +.strong { font-weight: 700 } +.warning { color: firebrick; } +.green { color: darkgreen; } +.tests-pass { background-color: green; color: white } +.tests-fail { background-color: red; color: yellow } +.faded { opacity: 0.5; } +.group-start { color: blue; } +.group-end { color: blue; } +.input-wrapper { + white-space: nowrap; + display: flex; + align-items: center; +} +#test-output { + border: 1px inset; + border-radius: 0.25em; + padding: 0.25em; + /*max-height: 30em;*/ + overflow: auto; + white-space: break-spaces; + display: flex; flex-direction: column; + font-family: monospace; +} +#test-output.reverse { + flex-direction: column-reverse; +} +label[for] { cursor: pointer } diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js new file mode 100644 index 000000000..7e5e7981f --- /dev/null +++ b/ext/wasm/common/whwasmutil.js @@ -0,0 +1,1706 @@ +/** + 2022-07-08 + + 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 whwasmutil is developed in conjunction with the Jaccwabyt + project: + + https://fossil.wanderinghorse.net/r/jaccwabyt + + and sqlite3: + + https://sqlite.org + + This file is kept in sync between both of those trees. + + Maintenance reminder: If you're reading this in a tree other than + one of those listed above, note that this copy may be replaced with + upstream copies of that one from time to time. Thus the code + installed by this function "should not" be edited outside of those + projects, else it risks getting overwritten. +*/ +/** + This function is intended to simplify porting around various bits + of WASM-related utility code from project to project. + + The primary goal of this code is to replace, where possible, + Emscripten-generated glue code with equivalent utility code which + can be used in arbitrary WASM environments built with toolchains + other than Emscripten. As of this writing, this code is capable of + acting as a replacement for Emscripten's generated glue code + _except_ that the latter installs handlers for Emscripten-provided + APIs such as its "FS" (virtual filesystem) API. Loading of such + things still requires using Emscripten's glue, but the post-load + utility APIs provided by this code are still usable as replacements + for their sub-optimally-documented Emscripten counterparts. + + Intended usage: + + ``` + self.WhWasmUtilInstaller(appObject); + delete self.WhWasmUtilInstaller; + ``` + + Its global-scope symbol is intended only to provide an easy way to + make it available to 3rd-party scripts and "should" be deleted + after calling it. That symbols is _not_ used within the library. + + Forewarning: this API explicitly targets only browser + environments. If a given non-browser environment has the + capabilities needed for a given feature (e.g. TextEncoder), great, + but it does not go out of its way to account for them and does not + provide compatibility crutches for them. + + It currently offers alternatives to the following + Emscripten-generated APIs: + + - OPTIONALLY memory allocation, but how this gets imported is + environment-specific. Most of the following features only work + if allocation is available. + + - WASM-exported "indirect function table" access and + manipulation. e.g. creating new WASM-side functions using JS + functions, analog to Emscripten's addFunction() and + uninstallFunction() but slightly different. + + - Get/set specific heap memory values, analog to Emscripten's + getValue() and setValue(). + + - String length counting in UTF-8 bytes (C-style and JS strings). + + - JS string to C-string conversion and vice versa, analog to + Emscripten's stringToUTF8Array() and friends, but with slighter + different interfaces. + + - JS string to Uint8Array conversion, noting that browsers actually + already have this built in via TextEncoder. + + - "Scoped" allocation, such that allocations made inside of a given + explicit scope will be automatically cleaned up when the scope is + closed. This is fundamentally similar to Emscripten's + stackAlloc() and friends but uses the heap instead of the stack + because access to the stack requires C code. + + - Create JS wrappers for WASM functions, analog to Emscripten's + ccall() and cwrap() functions, except that the automatic + conversions for function arguments and return values can be + easily customized by the client by assigning custom function + signature type names to conversion functions. Essentially, + it's ccall() and cwrap() on steroids. + + How to install... + + Passing an object to this function will install the functionality + into that object. Afterwards, client code "should" delete the global + symbol. + + This code requires that the target object have the following + properties, noting that they needn't be available until the first + time one of the installed APIs is used (as opposed to when this + function is called) except where explicitly noted: + + - `exports` must be a property of the target object OR a property + of `target.instance` (a WebAssembly.Module instance) and it must + contain the symbols exported by the WASM module associated with + this code. In an Enscripten environment it must be set to + `Module['asm']`. The exports object must contain a minimum of the + following symbols: + + - `memory`: a WebAssembly.Memory object representing the WASM + memory. _Alternately_, the `memory` property can be set as + `target.memory`, in particular if the WASM heap memory is + initialized in JS an _imported_ into WASM, as opposed to being + initialized in WASM and exported to JS. + + - `__indirect_function_table`: the WebAssembly.Table object which + holds WASM-exported functions. This API does not strictly + require that the table be able to grow but it will throw if its + `installFunction()` is called and the table cannot grow. + + In order to simplify downstream usage, if `target.exports` is not + set when this is called then a property access interceptor + (read-only, configurable, enumerable) gets installed as `exports` + which resolves to `target.instance.exports`, noting that the latter + property need not exist until the first time `target.exports` is + accessed. + + Some APIs _optionally_ make use of the `bigIntEnabled` property of + the target object. It "should" be set to true if the WASM + environment is compiled with BigInt support, else it must be + false. If it is false, certain BigInt-related features will trigger + an exception if invoked. This property, if not set when this is + called, will get a default value of true only if the BigInt64Array + constructor is available, else it will default to false. Note that + having the BigInt type is not sufficient for full int64 integration + with WASM: the target WASM file must also have been built with + that support. In Emscripten that's done using the `-sWASM_BIGINT` + flag. + + Some optional APIs require that the target have the following + methods: + + - 'alloc()` must behave like C's `malloc()`, allocating N bytes of + memory and returning its pointer. In Emscripten this is + conventionally made available via `Module['_malloc']`. This API + requires that the alloc routine throw on allocation error, as + opposed to returning null or 0. + + - 'dealloc()` must behave like C's `free()`, accepting either a + pointer returned from its allocation counterpart or the values + null/0 (for which it must be a no-op). allocating N bytes of + memory and returning its pointer. In Emscripten this is + conventionally made available via `Module['_free']`. + + APIs which require allocation routines are explicitly documented as + such and/or have "alloc" in their names. + + This code is developed and maintained in conjunction with the + Jaccwabyt project: + + https://fossil.wanderinghorse.net/r/jaccwabbyt + + More specifically: + + https://fossil.wanderinghorse.net/r/jaccwabbyt/file/common/whwasmutil.js +*/ +self.WhWasmUtilInstaller = function(target){ + 'use strict'; + if(undefined===target.bigIntEnabled){ + target.bigIntEnabled = !!self['BigInt64Array']; + } + + /** Throws a new Error, the message of which is the concatenation of + all args with a space between each. */ + const toss = (...args)=>{throw new Error(args.join(' '))}; + + if(!target.exports){ + Object.defineProperty(target, 'exports', { + enumerable: true, configurable: true, + get: ()=>(target.instance && target.instance.exports) + }); + } + + /********* + alloc()/dealloc() auto-install... + + This would be convenient but it can also cause us to pick up + malloc() even when the client code is using a different exported + allocator (who, me?), which is bad. malloc() may be exported even + if we're not explicitly using it and overriding the malloc() + function, linking ours first, is not always feasible when using a + malloc() proxy, as it can lead to recursion and stack overflow + (who, me?). So... we really need the downstream code to set up + target.alloc/dealloc() itself. + ******/ + /****** + if(target.exports){ + //Maybe auto-install alloc()/dealloc()... + if(!target.alloc && target.exports.malloc){ + target.alloc = function(n){ + const m = this(n); + return m || toss("Allocation of",n,"byte(s) failed."); + }.bind(target.exports.malloc); + } + + if(!target.dealloc && target.exports.free){ + target.dealloc = function(ptr){ + if(ptr) this(ptr); + }.bind(target.exports.free); + } + }*******/ + + /** + Pointers in WASM are currently assumed to be 32-bit, but someday + that will certainly change. + */ + const ptrIR = target.pointerIR || 'i32'; + const ptrSizeof = target.ptrSizeof = + ('i32'===ptrIR ? 4 + : ('i64'===ptrIR + ? 8 : toss("Unhandled ptrSizeof:",ptrIR))); + /** Stores various cached state. */ + const cache = Object.create(null); + /** Previously-recorded size of cache.memory.buffer, noted so that + we can recreate the view objects if the heap grows. */ + cache.heapSize = 0; + /** WebAssembly.Memory object extracted from target.memory or + target.exports.memory the first time heapWrappers() is + called. */ + cache.memory = null; + /** uninstallFunction() puts table indexes in here for reuse and + installFunction() extracts them. */ + cache.freeFuncIndexes = []; + /** + Used by scopedAlloc() and friends. + */ + cache.scopedAlloc = []; + + cache.utf8Decoder = new TextDecoder(); + cache.utf8Encoder = new TextEncoder('utf-8'); + + /** + If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if + the heap has grown since the last call, updates cache.HEAPxyz. + Returns the cache object. + */ + const heapWrappers = function(){ + if(!cache.memory){ + cache.memory = (target.memory instanceof WebAssembly.Memory) + ? target.memory : target.exports.memory; + }else if(cache.heapSize === cache.memory.buffer.byteLength){ + return cache; + } + // heap is newly-acquired or has been resized.... + const b = cache.memory.buffer; + cache.HEAP8 = new Int8Array(b); cache.HEAP8U = new Uint8Array(b); + cache.HEAP16 = new Int16Array(b); cache.HEAP16U = new Uint16Array(b); + cache.HEAP32 = new Int32Array(b); cache.HEAP32U = new Uint32Array(b); + if(target.bigIntEnabled){ + cache.HEAP64 = new BigInt64Array(b); cache.HEAP64U = new BigUint64Array(b); + } + cache.HEAP32F = new Float32Array(b); cache.HEAP64F = new Float64Array(b); + cache.heapSize = b.byteLength; + return cache; + }; + + /** Convenience equivalent of this.heapForSize(8,false). */ + target.heap8 = ()=>heapWrappers().HEAP8; + + /** Convenience equivalent of this.heapForSize(8,true). */ + target.heap8u = ()=>heapWrappers().HEAP8U; + + /** Convenience equivalent of this.heapForSize(16,false). */ + target.heap16 = ()=>heapWrappers().HEAP16; + + /** Convenience equivalent of this.heapForSize(16,true). */ + target.heap16u = ()=>heapWrappers().HEAP16U; + + /** Convenience equivalent of this.heapForSize(32,false). */ + target.heap32 = ()=>heapWrappers().HEAP32; + + /** Convenience equivalent of this.heapForSize(32,true). */ + target.heap32u = ()=>heapWrappers().HEAP32U; + + /** + Requires n to be one of: + + - integer 8, 16, or 32. + - A integer-type TypedArray constructor: Int8Array, Int16Array, + Int32Array, or their Uint counterparts. + + If this.bigIntEnabled is true, it also accepts the value 64 or a + BigInt64Array/BigUint64Array, else it throws if passed 64 or one + of those constructors. + + Returns an integer-based TypedArray view of the WASM heap + memory buffer associated with the given block size. If passed + an integer as the first argument and unsigned is truthy then + the "U" (unsigned) variant of that view is returned, else the + signed variant is returned. If passed a TypedArray value, the + 2nd argument is ignored. Note that Float32Array and + Float64Array views are not supported by this function. + + Note that growth of the heap will invalidate any references to + this heap, so do not hold a reference longer than needed and do + not use a reference after any operation which may + allocate. Instead, re-fetch the reference by calling this + function again. + + Throws if passed an invalid n. + + Pedantic side note: the name "heap" is a bit of a misnomer. In an + Emscripten environment, the memory managed via the stack + allocation API is in the same Memory object as the heap (which + makes sense because otherwise arbitrary pointer X would be + ambiguous: is it in the heap or the stack?). + */ + target.heapForSize = function(n,unsigned = false){ + let ctor; + const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) + ? cache : heapWrappers(); + switch(n){ + case Int8Array: return c.HEAP8; case Uint8Array: return c.HEAP8U; + case Int16Array: return c.HEAP16; case Uint16Array: return c.HEAP16U; + case Int32Array: return c.HEAP32; case Uint32Array: return c.HEAP32U; + case 8: return unsigned ? c.HEAP8U : c.HEAP8; + case 16: return unsigned ? c.HEAP16U : c.HEAP16; + case 32: return unsigned ? c.HEAP32U : c.HEAP32; + case 64: + if(c.HEAP64) return unsigned ? c.HEAP64U : c.HEAP64; + break; + default: + if(target.bigIntEnabled){ + if(n===self['BigUint64Array']) return c.HEAP64U; + else if(n===self['BigInt64Array']) return c.HEAP64; + break; + } + } + toss("Invalid heapForSize() size: expecting 8, 16, 32,", + "or (if BigInt is enabled) 64."); + }; + + /** + Returns the WASM-exported "indirect function table." + */ + target.functionTable = function(){ + return target.exports.__indirect_function_table; + /** -----------------^^^^^ "seems" to be a standardized export name. + From Emscripten release notes from 2020-09-10: + - Use `__indirect_function_table` as the import name for the + table, which is what LLVM does. + */ + }; + + /** + Given a function pointer, returns the WASM function table entry + if found, else returns a falsy value. + */ + target.functionEntry = function(fptr){ + const ft = target.functionTable(); + return fptr < ft.length ? ft.get(fptr) : undefined; + }; + + /** + Creates a WASM function which wraps the given JS function and + returns the JS binding of that WASM function. The signature + string must be the Jaccwabyt-format or Emscripten + addFunction()-format function signature string. In short: in may + have one of the following formats: + + - Emscripten: `"x..."`, where the first x is a letter representing + the result type and subsequent letters represent the argument + types. Functions with no arguments have only a single + letter. See below. + + - Jaccwabyt: `"x(...)"` where `x` is the letter representing the + result type and letters in the parens (if any) represent the + argument types. Functions with no arguments use `x()`. See + below. + + Supported letters: + + - `i` = int32 + - `p` = int32 ("pointer") + - `j` = int64 + - `f` = float32 + - `d` = float64 + - `v` = void, only legal for use as the result type + + It throws if an invalid signature letter is used. + + Jaccwabyt-format signatures support some additional letters which + have no special meaning here but (in this context) act as aliases + for other letters: + + - `s`, `P`: same as `p` + + Sidebar: this code is developed together with Jaccwabyt, thus the + support for its signature format. + + The arguments may be supplied in either order: (func,sig) or + (sig,func). + */ + target.jsFuncToWasm = function f(func, sig){ + /** Attribution: adapted up from Emscripten-generated glue code, + refactored primarily for efficiency's sake, eliminating + call-local functions and superfluous temporary arrays. */ + if(!f._){/*static init...*/ + f._ = { + // Map of signature letters to type IR values + sigTypes: Object.assign(Object.create(null),{ + i: 'i32', p: 'i32', P: 'i32', s: 'i32', + j: 'i64', f: 'f32', d: 'f64' + }), + // Map of type IR values to WASM type code values + typeCodes: Object.assign(Object.create(null),{ + f64: 0x7c, f32: 0x7d, i64: 0x7e, i32: 0x7f + }), + /** Encodes n, which must be <2^14 (16384), into target array + tgt, as a little-endian value, using the given method + ('push' or 'unshift'). */ + uleb128Encode: function(tgt, method, n){ + if(n<128) tgt[method](n); + else tgt[method]( (n % 128) | 128, n>>7); + }, + /** Intentionally-lax pattern for Jaccwabyt-format function + pointer signatures, the intent of which is simply to + distinguish them from Emscripten-format signatures. The + downstream checks are less lax. */ + rxJSig: /^(\w)\((\w*)\)$/, + /** Returns the parameter-value part of the given signature + string. */ + sigParams: function(sig){ + const m = f._.rxJSig.exec(sig); + return m ? m[2] : sig.substr(1); + }, + /** Returns the IR value for the given letter or throws + if the letter is invalid. */ + letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x), + /** Returns an object describing the result type and parameter + type(s) of the given function signature, or throws if the + signature is invalid. */ + /******** // only valid for use with the WebAssembly.Function ctor, which + // is not yet documented on MDN. + sigToWasm: function(sig){ + const rc = {parameters:[], results: []}; + if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0])); + for(const x of f._.sigParams(sig)){ + rc.parameters.push(f._.typeCodes(x)); + } + return rc; + },************/ + /** Pushes the WASM data type code for the given signature + letter to the given target array. Throws if letter is + invalid. */ + pushSigType: (dest, letter)=>dest.push(f._.typeCodes[f._.letterType(letter)]) + }; + }/*static init*/ + if('string'===typeof func){ + const x = sig; + sig = func; + func = x; + } + const sigParams = f._.sigParams(sig); + const wasmCode = [0x01/*count: 1*/, 0x60/*function*/]; + f._.uleb128Encode(wasmCode, 'push', sigParams.length); + for(const x of sigParams) f._.pushSigType(wasmCode, x); + if('v'===sig[0]) wasmCode.push(0); + else{ + wasmCode.push(1); + f._.pushSigType(wasmCode, sig[0]); + } + f._.uleb128Encode(wasmCode, 'unshift', wasmCode.length)/* type section length */; + wasmCode.unshift( + 0x00, 0x61, 0x73, 0x6d, /* magic: "\0asm" */ + 0x01, 0x00, 0x00, 0x00, /* version: 1 */ + 0x01 /* type section code */ + ); + wasmCode.push( + /* import section: */ 0x02, 0x07, + /* (import "e" "f" (func 0 (type 0))): */ + 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00, + /* export section: */ 0x07, 0x05, + /* (export "f" (func 0 (type 0))): */ + 0x01, 0x01, 0x66, 0x00, 0x00 + ); + return (new WebAssembly.Instance( + new WebAssembly.Module(new Uint8Array(wasmCode)), { + e: { f: func } + })).exports['f']; + }/*jsFuncToWasm()*/; + + /** + Expects a JS function and signature, exactly as for + this.jsFuncToWasm(). It uses that function to create a + WASM-exported function, installs that function to the next + available slot of this.functionTable(), and returns the + function's index in that table (which acts as a pointer to that + function). The returned pointer can be passed to + uninstallFunction() to uninstall it and free up the table slot for + reuse. + + If passed (string,function) arguments then it treats the first + argument as the signature and second as the function. + + As a special case, if the passed-in function is a WASM-exported + function then the signature argument is ignored and func is + installed as-is, without requiring re-compilation/re-wrapping. + + This function will propagate an exception if + WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws. + The former case can happen in an Emscripten-compiled + environment when building without Emscripten's + `-sALLOW_TABLE_GROWTH` flag. + + Sidebar: this function differs from Emscripten's addFunction() + _primarily_ in that it does not share that function's + undocumented behavior of reusing a function if it's passed to + addFunction() more than once, which leads to uninstallFunction() + breaking clients which do not take care to avoid that case: + + https://github.com/emscripten-core/emscripten/issues/17323 + */ + target.installFunction = function f(func, sig){ + if(2!==arguments.length){ + toss("installFunction() requires exactly 2 arguments"); + } + if('string'===typeof func){ + const x = sig; + sig = func; + func = x; + } + const ft = target.functionTable(); + const oldLen = ft.length; + let ptr; + while(cache.freeFuncIndexes.length){ + ptr = cache.freeFuncIndexes.pop(); + if(ft.get(ptr)){ /* Table was modified via a different API */ + ptr = null; + continue; + }else{ + break; + } + } + if(!ptr){ + ptr = oldLen; + ft.grow(1); + } + try{ + /*this will only work if func is a WASM-exported function*/ + ft.set(ptr, func); + return ptr; + }catch(e){ + if(!(e instanceof TypeError)){ + if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); + throw e; + } + } + // It's not a WASM-exported function, so compile one... + try { + ft.set(ptr, target.jsFuncToWasm(func, sig)); + }catch(e){ + if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); + throw e; + } + return ptr; + }; + + /** + Requires a pointer value previously returned from + this.installFunction(). Removes that function from the WASM + function table, marks its table slot as free for re-use, and + returns that function. It is illegal to call this before + installFunction() has been called and results are undefined if + ptr was not returned by that function. The returned function + may be passed back to installFunction() to reinstall it. + */ + target.uninstallFunction = function(ptr){ + const fi = cache.freeFuncIndexes; + const ft = target.functionTable(); + fi.push(ptr); + const rc = ft.get(ptr); + ft.set(ptr, null); + return rc; + }; + + /** + Given a WASM heap memory address and a data type name in the form + (i8, i16, i32, i64, float (or f32), double (or f64)), this + fetches the numeric value from that address and returns it as a + number or, for the case of type='i64', a BigInt (noting that that + type triggers an exception if this.bigIntEnabled is + falsy). Throws if given an invalid type. + + As a special case, if type ends with a `*`, it is considered to + be a pointer type and is treated as the WASM numeric type + appropriate for the pointer size (`i32`). + + While likely not obvious, this routine and its setMemValue() + counterpart are how pointer-to-value _output_ parameters + in WASM-compiled C code can be interacted with: + + ``` + const ptr = alloc(4); + setMemValue(ptr, 0, 'i32'); // clear the ptr's value + aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x); + const result = getMemValue(ptr, 'i32'); // fetch ptr's value + dealloc(ptr); + ``` + + scopedAlloc() and friends can be used to make handling of + `ptr` safe against leaks in the case of an exception: + + ``` + let result; + const scope = scopedAllocPush(); + try{ + const ptr = scopedAlloc(4); + setMemValue(ptr, 0, 'i32'); + aCFuncWithOutputPtrArg( ptr ); + result = getMemValue(ptr, 'i32'); + }finally{ + scopedAllocPop(scope); + } + ``` + + As a rule setMemValue() must be called to set (typically zero + out) the pointer's value, else it will contain an essentially + random value. + + ACHTUNG: calling this often, e.g. in a loop, can have a noticably + painful impact on performance. Rather than doing so, use + heapForSize() to fetch the heap object and read directly from it. + + See: setMemValue() + */ + target.getMemValue = function(ptr, type='i8'){ + if(type.endsWith('*')) type = ptrIR; + const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) + ? cache : heapWrappers(); + switch(type){ + case 'i1': + case 'i8': return c.HEAP8[ptr>>0]; + case 'i16': return c.HEAP16[ptr>>1]; + case 'i32': return c.HEAP32[ptr>>2]; + case 'i64': + if(target.bigIntEnabled) return BigInt(c.HEAP64[ptr>>3]); + break; + case 'float': case 'f32': return c.HEAP32F[ptr>>2]; + case 'double': case 'f64': return Number(c.HEAP64F[ptr>>3]); + default: break; + } + toss('Invalid type for getMemValue():',type); + }; + + /** + The counterpart of getMemValue(), this sets a numeric value at + the given WASM heap address, using the type to define how many + bytes are written. Throws if given an invalid type. See + getMemValue() for details about the type argument. If the 3rd + argument ends with `*` then it is treated as a pointer type and + this function behaves as if the 3rd argument were `i32`. + + This function returns itself. + + ACHTUNG: calling this often, e.g. in a loop, can have a noticably + painful impact on performance. Rather than doing so, use + heapForSize() to fetch the heap object and assign directly to it. + */ + target.setMemValue = function f(ptr, value, type='i8'){ + if (type.endsWith('*')) type = ptrIR; + const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) + ? cache : heapWrappers(); + switch (type) { + case 'i1': + case 'i8': c.HEAP8[ptr>>0] = value; return f; + case 'i16': c.HEAP16[ptr>>1] = value; return f; + case 'i32': c.HEAP32[ptr>>2] = value; return f; + case 'i64': + if(c.HEAP64){ + c.HEAP64[ptr>>3] = BigInt(value); + return f; + } + break; + case 'float': case 'f32': c.HEAP32F[ptr>>2] = value; return f; + case 'double': case 'f64': c.HEAP64F[ptr>>3] = value; return f; + } + toss('Invalid type for setMemValue(): ' + type); + }; + + + /** Convenience form of getMemValue() intended for fetching + pointer-to-pointer values. */ + target.getPtrValue = (ptr)=>target.getMemValue(ptr, ptrIR); + + /** Convenience form of setMemValue() intended for setting + pointer-to-pointer values. */ + target.setPtrValue = (ptr, value)=>target.setMemValue(ptr, value, ptrIR); + + /** + Returns true if the given value appears to be legal for use as + a WASM pointer value. Its _range_ of values is not (cannot be) + validated except to ensure that it is a 32-bit integer with a + value of 0 or greater. Likewise, it cannot verify whether the + value actually refers to allocated memory in the WASM heap. + */ + target.isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0); + + /** + isPtr() is an alias for isPtr32(). If/when 64-bit WASM pointer + support becomes widespread, it will become an alias for either + isPtr32() or the as-yet-hypothetical isPtr64(), depending on a + configuration option. + */ + target.isPtr = target.isPtr32; + + /** + Expects ptr to be a pointer into the WASM heap memory which + refers to a NUL-terminated C-style string encoded as UTF-8. + Returns the length, in bytes, of the string, as for `strlen(3)`. + As a special case, if !ptr then it it returns `null`. Throws if + ptr is out of range for target.heap8u(). + */ + target.cstrlen = function(ptr){ + if(!ptr) return null; + const h = heapWrappers().HEAP8U; + let pos = ptr; + for( ; h[pos] !== 0; ++pos ){} + return pos - ptr; + }; + + /** Internal helper to use in operations which need to distinguish + between SharedArrayBuffer heap memory and non-shared heap. */ + const __SAB = ('undefined'===typeof SharedArrayBuffer) + ? function(){} : SharedArrayBuffer; + const __utf8Decode = function(arrayBuffer, begin, end){ + return cache.utf8Decoder.decode( + (arrayBuffer.buffer instanceof __SAB) + ? arrayBuffer.slice(begin, end) + : arrayBuffer.subarray(begin, end) + ); + }; + + /** + Expects ptr to be a pointer into the WASM heap memory which + refers to a NUL-terminated C-style string encoded as UTF-8. This + function counts its byte length using cstrlen() then returns a + JS-format string representing its contents. As a special case, if + ptr is falsy, `null` is returned. + */ + target.cstringToJs = function(ptr){ + const n = target.cstrlen(ptr); + return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : ""); + }; + + /** + Given a JS string, this function returns its UTF-8 length in + bytes. Returns null if str is not a string. + */ + target.jstrlen = function(str){ + /** Attribution: derived from Emscripten's lengthBytesUTF8() */ + if('string'!==typeof str) return null; + const n = str.length; + let len = 0; + for(let i = 0; i < n; ++i){ + let u = str.charCodeAt(i); + if(u>=0xd800 && u<=0xdfff){ + u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF); + } + if(u<=0x7f) ++len; + else if(u<=0x7ff) len += 2; + else if(u<=0xffff) len += 3; + else len += 4; + } + return len; + }; + + /** + Encodes the given JS string as UTF8 into the given TypedArray + tgt, starting at the given offset and writing, at most, maxBytes + bytes (including the NUL terminator if addNul is true, else no + NUL is added). If it writes any bytes at all and addNul is true, + it always NUL-terminates the output, even if doing so means that + the NUL byte is all that it writes. + + If maxBytes is negative (the default) then it is treated as the + remaining length of tgt, starting at the given offset. + + If writing the last character would surpass the maxBytes count + because the character is multi-byte, that character will not be + written (as opposed to writing a truncated multi-byte character). + This can lead to it writing as many as 3 fewer bytes than + maxBytes specifies. + + Returns the number of bytes written to the target, _including_ + the NUL terminator (if any). If it returns 0, it wrote nothing at + all, which can happen if: + + - str is empty and addNul is false. + - offset < 0. + - maxBytes == 0. + - maxBytes is less than the byte length of a multi-byte str[0]. + + Throws if tgt is not an Int8Array or Uint8Array. + + Design notes: + + - In C's strcpy(), the destination pointer is the first + argument. That is not the case here primarily because the 3rd+ + arguments are all referring to the destination, so it seems to + make sense to have them grouped with it. + + - Emscripten's counterpart of this function (stringToUTF8Array()) + returns the number of bytes written sans NUL terminator. That + is, however, ambiguous: str.length===0 or maxBytes===(0 or 1) + all cause 0 to be returned. + */ + target.jstrcpy = function(jstr, tgt, offset = 0, maxBytes = -1, addNul = true){ + /** Attribution: the encoding bits are taken from Emscripten's + stringToUTF8Array(). */ + if(!tgt || (!(tgt instanceof Int8Array) && !(tgt instanceof Uint8Array))){ + toss("jstrcpy() target must be an Int8Array or Uint8Array."); + } + if(maxBytes<0) maxBytes = tgt.length - offset; + if(!(maxBytes>0) || !(offset>=0)) return 0; + let i = 0, max = jstr.length; + const begin = offset, end = offset + maxBytes - (addNul ? 1 : 0); + for(; i < max && offset < end; ++i){ + let u = jstr.charCodeAt(i); + if(u>=0xd800 && u<=0xdfff){ + u = 0x10000 + ((u & 0x3FF) << 10) | (jstr.charCodeAt(++i) & 0x3FF); + } + if(u<=0x7f){ + if(offset >= end) break; + tgt[offset++] = u; + }else if(u<=0x7ff){ + if(offset + 1 >= end) break; + tgt[offset++] = 0xC0 | (u >> 6); + tgt[offset++] = 0x80 | (u & 0x3f); + }else if(u<=0xffff){ + if(offset + 2 >= end) break; + tgt[offset++] = 0xe0 | (u >> 12); + tgt[offset++] = 0x80 | ((u >> 6) & 0x3f); + tgt[offset++] = 0x80 | (u & 0x3f); + }else{ + if(offset + 3 >= end) break; + tgt[offset++] = 0xf0 | (u >> 18); + tgt[offset++] = 0x80 | ((u >> 12) & 0x3f); + tgt[offset++] = 0x80 | ((u >> 6) & 0x3f); + tgt[offset++] = 0x80 | (u & 0x3f); + } + } + if(addNul) tgt[offset++] = 0; + return offset - begin; + }; + + /** + Works similarly to C's strncpy(), copying, at most, n bytes (not + characters) from srcPtr to tgtPtr. It copies until n bytes have + been copied or a 0 byte is reached in src. _Unlike_ strncpy(), it + returns the number of bytes it assigns in tgtPtr, _including_ the + NUL byte (if any). If n is reached before a NUL byte in srcPtr, + tgtPtr will _not_ be NULL-terminated. If a NUL byte is reached + before n bytes are copied, tgtPtr will be NUL-terminated. + + If n is negative, cstrlen(srcPtr)+1 is used to calculate it, the + +1 being for the NUL byte. + + Throws if tgtPtr or srcPtr are falsy. Results are undefined if: + + - either is not a pointer into the WASM heap or + + - srcPtr is not NUL-terminated AND n is less than srcPtr's + logical length. + + ACHTUNG: it is possible to copy partial multi-byte characters + this way, and converting such strings back to JS strings will + have undefined results. + */ + target.cstrncpy = function(tgtPtr, srcPtr, n){ + if(!tgtPtr || !srcPtr) toss("cstrncpy() does not accept NULL strings."); + if(n<0) n = target.cstrlen(strPtr)+1; + else if(!(n>0)) return 0; + const heap = target.heap8u(); + let i = 0, ch; + for(; i < n && (ch = heap[srcPtr+i]); ++i){ + heap[tgtPtr+i] = ch; + } + if(i{ + return cache.utf8Encoder.encode(addNul ? (str+"\0") : str); + // Or the hard way... + /** Attribution: derived from Emscripten's stringToUTF8Array() */ + //const a = [], max = str.length; + //let i = 0, pos = 0; + //for(; i < max; ++i){ + // let u = str.charCodeAt(i); + // if(u>=0xd800 && u<=0xdfff){ + // u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF); + // } + // if(u<=0x7f) a[pos++] = u; + // else if(u<=0x7ff){ + // a[pos++] = 0xC0 | (u >> 6); + // a[pos++] = 0x80 | (u & 63); + // }else if(u<=0xffff){ + // a[pos++] = 0xe0 | (u >> 12); + // a[pos++] = 0x80 | ((u >> 6) & 63); + // a[pos++] = 0x80 | (u & 63); + // }else{ + // a[pos++] = 0xf0 | (u >> 18); + // a[pos++] = 0x80 | ((u >> 12) & 63); + // a[pos++] = 0x80 | ((u >> 6) & 63); + // a[pos++] = 0x80 | (u & 63); + // } + // } + // return new Uint8Array(a); + }; + + const __affirmAlloc = (obj,funcName)=>{ + if(!(obj.alloc instanceof Function) || + !(obj.dealloc instanceof Function)){ + toss("Object is missing alloc() and/or dealloc() function(s)", + "required by",funcName+"()."); + } + }; + + const __allocCStr = function(jstr, returnWithLength, allocator, funcName){ + __affirmAlloc(target, funcName); + if('string'!==typeof jstr) return null; + const n = target.jstrlen(jstr), + ptr = allocator(n+1); + target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true); + return returnWithLength ? [ptr, n] : ptr; + }; + + /** + Uses target.alloc() to allocate enough memory for jstrlen(jstr)+1 + bytes of memory, copies jstr to that memory using jstrcpy(), + NUL-terminates it, and returns the pointer to that C-string. + Ownership of the pointer is transfered to the caller, who must + eventually pass the pointer to dealloc() to free it. + + If passed a truthy 2nd argument then its return semantics change: + it returns [ptr,n], where ptr is the C-string's pointer and n is + its cstrlen(). + + Throws if `target.alloc` or `target.dealloc` are not functions. + */ + target.allocCString = + (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength, + target.alloc, 'allocCString()'); + + /** + Starts an "allocation scope." All allocations made using + scopedAlloc() are recorded in this scope and are freed when the + value returned from this function is passed to + scopedAllocPop(). + + This family of functions requires that the API's object have both + `alloc()` and `dealloc()` methods, else this function will throw. + + Intended usage: + + ``` + const scope = scopedAllocPush(); + try { + const ptr1 = scopedAlloc(100); + const ptr2 = scopedAlloc(200); + const ptr3 = scopedAlloc(300); + ... + // Note that only allocations made via scopedAlloc() + // are managed by this allocation scope. + }finally{ + scopedAllocPop(scope); + } + ``` + + The value returned by this function must be treated as opaque by + the caller, suitable _only_ for passing to scopedAllocPop(). + Its type and value are not part of this function's API and may + change in any given version of this code. + + `scopedAlloc.level` can be used to determine how many scoped + alloc levels are currently active. + */ + target.scopedAllocPush = function(){ + __affirmAlloc(target, 'scopedAllocPush'); + const a = []; + cache.scopedAlloc.push(a); + return a; + }; + + /** + Cleans up all allocations made using scopedAlloc() in the context + of the given opaque state object, which must be a value returned + by scopedAllocPush(). See that function for an example of how to + use this function. + + Though scoped allocations are managed like a stack, this API + behaves properly if allocation scopes are popped in an order + other than the order they were pushed. + + If called with no arguments, it pops the most recent + scopedAllocPush() result: + + ``` + scopedAllocPush(); + try{ ... } finally { scopedAllocPop(); } + ``` + + It's generally recommended that it be passed an explicit argument + to help ensure that push/push are used in matching pairs, but in + trivial code that may be a non-issue. + */ + target.scopedAllocPop = function(state){ + __affirmAlloc(target, 'scopedAllocPop'); + const n = arguments.length + ? cache.scopedAlloc.indexOf(state) + : cache.scopedAlloc.length-1; + if(n<0) toss("Invalid state object for scopedAllocPop()."); + if(0===arguments.length) state = cache.scopedAlloc[n]; + cache.scopedAlloc.splice(n,1); + for(let p; (p = state.pop()); ) target.dealloc(p); + }; + + /** + Allocates n bytes of memory using this.alloc() and records that + fact in the state for the most recent call of scopedAllocPush(). + Ownership of the memory is given to scopedAllocPop(), which + will clean it up when it is called. The memory _must not_ be + passed to this.dealloc(). Throws if this API object is missing + the required `alloc()` or `dealloc()` functions or no scoped + alloc is active. + + See scopedAllocPush() for an example of how to use this function. + + The `level` property of this function can be queried to query how + many scoped allocation levels are currently active. + + See also: scopedAllocPtr(), scopedAllocCString() + */ + target.scopedAlloc = function(n){ + if(!cache.scopedAlloc.length){ + toss("No scopedAllocPush() scope is active."); + } + const p = target.alloc(n); + cache.scopedAlloc[cache.scopedAlloc.length-1].push(p); + return p; + }; + + Object.defineProperty(target.scopedAlloc, 'level', { + configurable: false, enumerable: false, + get: ()=>cache.scopedAlloc.length, + set: ()=>toss("The 'active' property is read-only.") + }); + + /** + Works identically to allocCString() except that it allocates the + memory using scopedAlloc(). + + Will throw if no scopedAllocPush() call is active. + */ + target.scopedAllocCString = + (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength, + target.scopedAlloc, 'scopedAllocCString()'); + + // impl for allocMainArgv() and scopedAllocMainArgv(). + const __allocMainArgv = function(isScoped, list){ + if(!list.length) toss("Cannot allocate empty array."); + const pList = target[ + isScoped ? 'scopedAlloc' : 'alloc' + ](list.length * target.ptrSizeof); + let i = 0; + list.forEach((e)=>{ + target.setPtrValue(pList + (target.ptrSizeof * i++), + target[ + isScoped ? 'scopedAllocCString' : 'allocCString' + ](""+e)); + }); + return pList; + }; + + /** + Creates an array, using scopedAlloc(), suitable for passing to a + C-level main() routine. The input is a collection with a length + property and a forEach() method. A block of memory list.length + entries long is allocated and each pointer-sized block of that + memory is populated with a scopedAllocCString() conversion of the + (""+value) of each element. Returns a pointer to the start of the + list, suitable for passing as the 2nd argument to a C-style + main() function. + + Throws if list.length is falsy or scopedAllocPush() is not active. + */ + target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list); + + /** + Identical to scopedAllocMainArgv() but uses alloc() instead of + scopedAllocMainArgv + */ + target.allocMainArgv = (list)=>__allocMainArgv(false, list); + + /** + Wraps function call func() in a scopedAllocPush() and + scopedAllocPop() block, such that all calls to scopedAlloc() and + friends from within that call will have their memory freed + automatically when func() returns. If func throws or propagates + an exception, the scope is still popped, otherwise it returns the + result of calling func(). + */ + target.scopedAllocCall = function(func){ + target.scopedAllocPush(); + try{ return func() } finally{ target.scopedAllocPop() } + }; + + /** Internal impl for allocPtr() and scopedAllocPtr(). */ + const __allocPtr = function(howMany, safePtrSize, method){ + __affirmAlloc(target, method); + const pIr = safePtrSize ? 'i64' : ptrIR; + let m = target[method](howMany * (safePtrSize ? 8 : ptrSizeof)); + target.setMemValue(m, 0, pIr) + if(1===howMany){ + return m; + } + const a = [m]; + for(let i = 1; i < howMany; ++i){ + m += (safePtrSize ? 8 : ptrSizeof); + a[i] = m; + target.setMemValue(m, 0, pIr); + } + return a; + }; + + /** + Allocates one or more pointers as a single chunk of memory and + zeroes them out. + + The first argument is the number of pointers to allocate. The + second specifies whether they should use a "safe" pointer size (8 + bytes) or whether they may use the default pointer size + (typically 4 but also possibly 8). + + How the result is returned depends on its first argument: if + passed 1, it returns the allocated memory address. If passed more + than one then an array of pointer addresses is returned, which + can optionally be used with "destructuring assignment" like this: + + ``` + const [p1, p2, p3] = allocPtr(3); + ``` + + ACHTUNG: when freeing the memory, pass only the _first_ result + value to dealloc(). The others are part of the same memory chunk + and must not be freed separately. + + The reason for the 2nd argument is.. + + When one of the returned pointers will refer to a 64-bit value, + e.g. a double or int64, an that value must be written or fetched, + e.g. using setMemValue() or getMemValue(), it is important that + the pointer in question be aligned to an 8-byte boundary or else + it will not be fetched or written properly and will corrupt or + read neighboring memory. It is only safe to pass false when the + client code is certain that it will only get/fetch 4-byte values + (or smaller). + */ + target.allocPtr = + (howMany=1, safePtrSize=true)=>__allocPtr(howMany, safePtrSize, 'alloc'); + + /** + Identical to allocPtr() except that it allocates using scopedAlloc() + instead of alloc(). + */ + target.scopedAllocPtr = + (howMany=1, safePtrSize=true)=>__allocPtr(howMany, safePtrSize, 'scopedAlloc'); + + /** + If target.exports[name] exists, it is returned, else an + exception is thrown. + */ + target.xGet = function(name){ + return target.exports[name] || toss("Cannot find exported symbol:",name); + }; + + const __argcMismatch = + (f,n)=>toss(f+"() requires",n,"argument(s)."); + + /** + Looks up a WASM-exported function named fname from + target.exports. If found, it is called, passed all remaining + arguments, and its return value is returned to xCall's caller. If + not found, an exception is thrown. This function does no + conversion of argument or return types, but see xWrap() and + xCallWrapped() for variants which do. + + As a special case, if passed only 1 argument after the name and + that argument in an Array, that array's entries become the + function arguments. (This is not an ambiguous case because it's + not legal to pass an Array object to a WASM function.) + */ + target.xCall = function(fname, ...args){ + const f = target.xGet(fname); + if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function."); + if(f.length!==args.length) __argcMismatch(fname,f.length) + /* This is arguably over-pedantic but we want to help clients keep + from shooting themselves in the foot when calling C APIs. */; + return (2===arguments.length && Array.isArray(arguments[1])) + ? f.apply(null, arguments[1]) + : f.apply(null, args); + }; + + /** + State for use with xWrap() + */ + cache.xWrap = Object.create(null); + const xcv = cache.xWrap.convert = Object.create(null); + /** Map of type names to argument conversion functions. */ + cache.xWrap.convert.arg = Object.create(null); + /** Map of type names to return result conversion functions. */ + cache.xWrap.convert.result = Object.create(null); + + if(target.bigIntEnabled){ + xcv.arg.i64 = (i)=>BigInt(i); + } + xcv.arg.i32 = (i)=>(i | 0); + xcv.arg.i16 = (i)=>((i | 0) & 0xFFFF); + xcv.arg.i8 = (i)=>((i | 0) & 0xFF); + xcv.arg.f32 = xcv.arg.float = (i)=>Number(i).valueOf(); + xcv.arg.f64 = xcv.arg.double = xcv.arg.f32; + xcv.arg.int = xcv.arg.i32; + xcv.result['*'] = xcv.result['pointer'] = xcv.arg['**'] = xcv.arg[ptrIR]; + xcv.result['number'] = (v)=>Number(v); + + { /* Copy certain xcv.arg[...] handlers to xcv.result[...] and + add pointer-style variants of them. */ + const copyToResult = ['i8', 'i16', 'i32', 'int', + 'f32', 'float', 'f64', 'double']; + if(target.bigIntEnabled) copyToResult.push('i64'); + for(const t of copyToResult){ + xcv.arg[t+'*'] = xcv.result[t+'*'] = xcv.arg[ptrIR]; + xcv.result[t] = xcv.arg[t] || toss("Missing arg converter:",t); + } + } + + /** + In order for args of type string to work in various contexts in + the sqlite3 API, we need to pass them on as, variably, a C-string + or a pointer value. Thus for ARGs of type 'string' and + '*'/'pointer' we behave differently depending on whether the + argument is a string or not: + + - If v is a string, scopeAlloc() a new C-string from it and return + that temp string's pointer. + + - Else return the value from the arg adaptor defined for ptrIR. + + TODO? Permit an Int8Array/Uint8Array and convert it to a string? + Would that be too much magic concentrated in one place, ready to + backfire? + */ + xcv.arg.string = xcv.arg.utf8 = xcv.arg['pointer'] = xcv.arg['*'] + = function(v){ + if('string'===typeof v) return target.scopedAllocCString(v); + return v ? xcv.arg[ptrIR](v) : null; + }; + xcv.result.string = xcv.result.utf8 = (i)=>target.cstringToJs(i); + xcv.result['string:free'] = xcv.result['utf8:free'] = (i)=>{ + try { return i ? target.cstringToJs(i) : null } + finally{ target.dealloc(i) } + }; + xcv.result.json = (i)=>JSON.parse(target.cstringToJs(i)); + xcv.result['json:free'] = (i)=>{ + try{ return i ? JSON.parse(target.cstringToJs(i)) : null } + finally{ target.dealloc(i) } + } + xcv.result['void'] = (v)=>undefined; + xcv.result['null'] = (v)=>v; + + if(0){ + /*** + This idea can't currently work because we don't know the + signature for the func and don't have a way for the user to + convey it. To do this we likely need to be able to match + arg/result handlers by a regex, but that would incur an O(N) + cost as we check the regex one at a time. Another use case for + such a thing would be pseudotypes like "int:-1" to say that + the value will always be treated like -1 (which has a useful + case in the sqlite3 bindings). + */ + xcv.arg['func-ptr'] = function(v){ + if(!(v instanceof Function)) return xcv.arg[ptrIR]; + const f = target.jsFuncToWasm(v, WHAT_SIGNATURE); + }; + } + + const __xArgAdapterCheck = + (t)=>xcv.arg[t] || toss("Argument adapter not found:",t); + + const __xResultAdapterCheck = + (t)=>xcv.result[t] || toss("Result adapter not found:",t); + + cache.xWrap.convertArg = (t,v)=>__xArgAdapterCheck(t)(v); + cache.xWrap.convertResult = + (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); + + /** + Creates a wrapper for the WASM-exported function fname. Uses + xGet() to fetch the exported function (which throws on + error) and returns either that function or a wrapper for that + function which converts the JS-side argument types into WASM-side + types and converts the result type. If the function takes no + arguments and resultType is `null` then the function is returned + as-is, else a wrapper is created for it to adapt its arguments + and result value, as described below. + + (If you're familiar with Emscripten's ccall() and cwrap(), this + function is essentially cwrap() on steroids.) + + This function's arguments are: + + - fname: the exported function's name. xGet() is used to fetch + this, so will throw if no exported function is found with that + name. + + - resultType: the name of the result type. A literal `null` means + to return the original function's value as-is (mnemonic: there + is "null" conversion going on). Literal `undefined` or the + string `"void"` mean to ignore the function's result and return + `undefined`. Aside from those two special cases, it may be one + of the values described below or any mapping installed by the + client using xWrap.resultAdapter(). + + If passed 3 arguments and the final one is an array, that array + must contain a list of type names (see below) for adapting the + arguments from JS to WASM. If passed 2 arguments, more than 3, + or the 3rd is not an array, all arguments after the 2nd (if any) + are treated as type names. i.e.: + + ``` + xWrap('funcname', 'i32', 'string', 'f64'); + // is equivalent to: + xWrap('funcname', 'i32', ['string', 'f64']); + ``` + + Type names are symbolic names which map the arguments to an + adapter function to convert, if needed, the value before passing + it on to WASM or to convert a return result from WASM. The list + of built-in names: + + - `i8`, `i16`, `i32` (args and results): all integer conversions + which convert their argument to an integer and truncate it to + the given bit length. + + - `N*` (args): a type name in the form `N*`, where N is a numeric + type name, is treated the same as WASM pointer. + + - `*` and `pointer` (args): have multple semantics. They + behave exactly as described below for `string` args. + + - `*` and `pointer` (results): are aliases for the current + WASM pointer numeric type. + + - `**` (args): is simply a descriptive alias for the WASM pointer + type. It's primarily intended to mark output-pointer arguments. + + - `i64` (args and results): passes the value to BigInt() to + convert it to an int64. Only available if bigIntEnabled is + true. + + - `f32` (`float`), `f64` (`double`) (args and results): pass + their argument to Number(). i.e. the adaptor does not currently + distinguish between the two types of floating-point numbers. + + - `number` (results): converts the result to a JS Number using + Number(theValue).valueOf(). Note that this is for result + conversions only, as it's not possible to generically know + which type of number to convert arguments to. + + Non-numeric conversions include: + + - `string` or `utf8` (args): has two different semantics in order + to accommodate various uses of certain C APIs + (e.g. output-style strings)... + + - If the arg is a string, it creates a _temporary_ + UTF-8-encoded C-string to pass to the exported function, + cleaning it up before the wrapper returns. If a long-lived + C-string pointer is required, that requires client-side code + to create the string, then pass its pointer to the function. + + - Else the arg is assumed to be a pointer to a string the + client has already allocated and it's passed on as + a WASM pointer. + + - `string` or `utf8` (results): treats the result value as a + const C-string, encoded as UTF-8, copies it to a JS string, + and returns that JS string. + + - `string:free` or `utf8:free) (results): treats the result value + as a non-const UTF-8 C-string, ownership of which has just been + transfered to the caller. It copies the C-string to a JS + string, frees the C-string, and returns the JS string. If such + a result value is NULL, the JS result is `null`. Achtung: when + using an API which returns results from a specific allocator, + e.g. `my_malloc()`, this conversion _is not legal_. Instead, an + equivalent conversion which uses the appropriate deallocator is + required. For example: + +```js + target.xWrap.resultAdaptor('string:my_free',(i)=>{ + try { return i ? target.cstringToJs(i) : null } + finally{ target.exports.my_free(i) } + }; +``` + + - `json` (results): treats the result as a const C-string and + returns the result of passing the converted-to-JS string to + JSON.parse(). Returns `null` if the C-string is a NULL pointer. + + - `json:free` (results): works exactly like `string:free` but + returns the same thing as the `json` adapter. Note the + warning in `string:free` regarding maching allocators and + deallocators. + + The type names for results and arguments are validated when + xWrap() is called and any unknown names will trigger an + exception. + + Clients may map their own result and argument adapters using + xWrap.resultAdapter() and xWrap.argAdaptor(), noting that not all + type conversions are valid for both arguments _and_ result types + as they often have different memory ownership requirements. + + TODOs: + + - Figure out how/whether we can (semi-)transparently handle + pointer-type _output_ arguments. Those currently require + explicit handling by allocating pointers, assigning them before + the call using setMemValue(), and fetching them with + getMemValue() after the call. We may be able to automate some + or all of that. + + - Figure out whether it makes sense to extend the arg adapter + interface such that each arg adapter gets an array containing + the results of the previous arguments in the current call. That + might allow some interesting type-conversion feature. Use case: + handling of the final argument to sqlite3_prepare_v2() depends + on the type (pointer vs JS string) of its 2nd + argument. Currently that distinction requires hand-writing a + wrapper for that function. That case is unusual enough that + abstracting it into this API (and taking on the associated + costs) may well not make good sense. + */ + target.xWrap = function(fname, resultType, ...argTypes){ + if(3===arguments.length && Array.isArray(arguments[2])){ + argTypes = arguments[2]; + } + const xf = target.xGet(fname); + if(argTypes.length!==xf.length) __argcMismatch(fname, xf.length); + if((null===resultType) && 0===xf.length){ + /* Func taking no args with an as-is return. We don't need a wrapper. */ + return xf; + } + /*Verify the arg type conversions are valid...*/; + if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType); + argTypes.forEach(__xArgAdapterCheck); + if(0===xf.length){ + // No args to convert, so we can create a simpler wrapper... + return (...args)=>(args.length + ? __argcMismatch(fname, xf.length) + : cache.xWrap.convertResult(resultType, xf.call(null))); + } + return function(...args){ + if(args.length!==xf.length) __argcMismatch(fname, xf.length); + const scope = target.scopedAllocPush(); + try{ + const rc = xf.apply(null,args.map((v,i)=>cache.xWrap.convertArg(argTypes[i], v))); + return cache.xWrap.convertResult(resultType, rc); + }finally{ + target.scopedAllocPop(scope); + } + }; + }/*xWrap()*/; + + /** Internal impl for xWrap.resultAdapter() and argAdaptor(). */ + const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){ + if('string'===typeof typeName){ + if(1===argc) return xcvPart[typeName]; + else if(2===argc){ + if(!adapter){ + delete xcvPart[typeName]; + return func; + }else if(!(adapter instanceof Function)){ + toss(modeName,"requires a function argument."); + } + xcvPart[typeName] = adapter; + return func; + } + } + toss("Invalid arguments to",modeName); + }; + + /** + Gets, sets, or removes a result value adapter for use with + xWrap(). If passed only 1 argument, the adapter function for the + given type name is returned. If the second argument is explicit + falsy (as opposed to defaulted), the adapter named by the first + argument is removed. If the 2nd argument is not falsy, it must be + a function which takes one value and returns a value appropriate + for the given type name. The adapter may throw if its argument is + not of a type it can work with. This function throws for invalid + arguments. + + Example: + + ``` + xWrap.resultAdapter('twice',(v)=>v+v); + ``` + + xWrap.resultAdapter() MUST NOT use the scopedAlloc() family of + APIs to allocate a result value. xWrap()-generated wrappers run + in the context of scopedAllocPush() so that argument adapters can + easily convert, e.g., to C-strings, and have them cleaned up + automatically before the wrapper returns to the caller. Likewise, + if a _result_ adapter uses scoped allocation, the result will be + freed before because they would be freed before the wrapper + returns, leading to chaos and undefined behavior. + + Except when called as a getter, this function returns itself. + */ + target.xWrap.resultAdapter = function f(typeName, adapter){ + return __xAdapter(f, arguments.length, typeName, adapter, + 'resultAdaptor()', xcv.result); + }; + + /** + Functions identically to xWrap.resultAdapter() but applies to + call argument conversions instead of result value conversions. + + xWrap()-generated wrappers perform argument conversion in the + context of a scopedAllocPush(), so any memory allocation + performed by argument adapters really, really, really should be + made using the scopedAlloc() family of functions unless + specifically necessary. For example: + + ``` + xWrap.argAdapter('my-string', function(v){ + return ('string'===typeof v) + ? myWasmObj.scopedAllocCString(v) : null; + }; + ``` + + Contrariwise, xWrap.resultAdapter() must _not_ use scopedAlloc() + to allocate its results because they would be freed before the + xWrap()-created wrapper returns. + + Note that it is perfectly legitimate to use these adapters to + perform argument validation, as opposed (or in addition) to + conversion. + */ + target.xWrap.argAdapter = function f(typeName, adapter){ + return __xAdapter(f, arguments.length, typeName, adapter, + 'argAdaptor()', xcv.arg); + }; + + /** + Functions like xCall() but performs argument and result type + conversions as for xWrap(). The first argument is the name of the + exported function to call. The 2nd its the name of its result + type, as documented for xWrap(). The 3rd is an array of argument + type name, as documented for xWrap() (use a falsy value or an + empty array for nullary functions). The 4th+ arguments are + arguments for the call, with the special case that if the 4th + argument is an array, it is used as the arguments for the + call. Returns the converted result of the call. + + This is just a thin wrapper around xWrap(). If the given function + is to be called more than once, it's more efficient to use + xWrap() to create a wrapper, then to call that wrapper as many + times as needed. For one-shot calls, however, this variant is + arguably more efficient because it will hypothetically free the + wrapper function quickly. + */ + target.xCallWrapped = function(fname, resultType, argTypes, ...args){ + if(Array.isArray(arguments[3])) args = arguments[3]; + return target.xWrap(fname, resultType, argTypes||[]).apply(null, args||[]); + }; + + return target; +}; + +/** + yawl (Yet Another Wasm Loader) provides very basic wasm loader. + It requires a config object: + + - `uri`: required URI of the WASM file to load. + + - `onload(loadResult,config)`: optional callback. The first + argument is the result object from + WebAssembly.instantiate[Streaming](). The 2nd is the config + object passed to this function. Described in more detail below. + + - `imports`: optional imports object for + WebAssembly.instantiate[Streaming](). The default is an empty set + of imports. If the module requires any imports, this object + must include them. + + - `wasmUtilTarget`: optional object suitable for passing to + WhWasmUtilInstaller(). If set, it gets passed to that function + after the promise resolves. This function sets several properties + on it before passing it on to that function (which sets many + more): + + - `module`, `instance`: the properties from the + instantiate[Streaming]() result. + + - If `instance.exports.memory` is _not_ set then it requires that + `config.imports.env.memory` be set (else it throws), and + assigns that to `target.memory`. + + - If `wasmUtilTarget.alloc` is not set and + `instance.exports.malloc` is, it installs + `wasmUtilTarget.alloc()` and `wasmUtilTarget.dealloc()` + wrappers for the exports `malloc` and `free` functions. + + It returns a function which, when called, initiates loading of the + module and returns a Promise. When that Promise resolves, it calls + the `config.onload` callback (if set) and passes it + `(loadResult,config)`, where `loadResult` is the result of + WebAssembly.instantiate[Streaming](): an object in the form: + + ``` + { + module: a WebAssembly.Module, + instance: a WebAssembly.Instance + } + ``` + + (Note that the initial `then()` attached to the promise gets only + that object, and not the `config` one.) + + Error handling is up to the caller, who may attach a `catch()` call + to the promise. +*/ +self.WhWasmUtilInstaller.yawl = function(config){ + const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'}); + const wui = this; + const finalThen = function(arg){ + //log("finalThen()",arg); + if(config.wasmUtilTarget){ + const toss = (...args)=>{throw new Error(args.join(' '))}; + const tgt = config.wasmUtilTarget; + tgt.module = arg.module; + tgt.instance = arg.instance; + //tgt.exports = tgt.instance.exports; + if(!tgt.instance.exports.memory){ + /** + WhWasmUtilInstaller requires either tgt.exports.memory + (exported from WASM) or tgt.memory (JS-provided memory + imported into WASM). + */ + tgt.memory = (config.imports && config.imports.env + && config.imports.env.memory) + || toss("Missing 'memory' object!"); + } + if(!tgt.alloc && arg.instance.exports.malloc){ + const exports = arg.instance.exports; + tgt.alloc = function(n){ + return exports.malloc(n) || toss("Allocation of",n,"bytes failed."); + }; + tgt.dealloc = function(m){exports.free(m)}; + } + wui(tgt); + } + if(config.onload) config.onload(arg,config); + return arg /* for any then() handler attached to + yetAnotherWasmLoader()'s return value */; + }; + const loadWasm = WebAssembly.instantiateStreaming + ? function loadWasmStreaming(){ + return WebAssembly.instantiateStreaming(wfetch(), config.imports||{}) + .then(finalThen); + } + : function loadWasmOldSchool(){ // Safari < v15 + return wfetch() + .then(response => response.arrayBuffer()) + .then(bytes => WebAssembly.instantiate(bytes, config.imports||{})) + .then(finalThen); + }; + return loadWasm; +}.bind(self.WhWasmUtilInstaller)/*yawl()*/; diff --git a/ext/wasm/demo-123-worker.html b/ext/wasm/demo-123-worker.html new file mode 100644 index 000000000..692203d71 --- /dev/null +++ b/ext/wasm/demo-123-worker.html @@ -0,0 +1,44 @@ + + + + + + + Hello, sqlite3 + + + +

    1-2-sqlite3 worker demo

    + + + diff --git a/ext/wasm/demo-123.html b/ext/wasm/demo-123.html new file mode 100644 index 000000000..2046b076d --- /dev/null +++ b/ext/wasm/demo-123.html @@ -0,0 +1,24 @@ + + + + + + + Hello, sqlite3 + + + +

    1-2-sqlite3 demo

    + + + + diff --git a/ext/wasm/demo-123.js b/ext/wasm/demo-123.js new file mode 100644 index 000000000..311afcc82 --- /dev/null +++ b/ext/wasm/demo-123.js @@ -0,0 +1,289 @@ +/* + 2022-09-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. + + *********************************************************************** + + A basic demonstration of the SQLite3 "OO#1" API. +*/ +'use strict'; +(function(){ + /** + Set up our output channel differently depending + on whether we are running in a worker thread or + the main (UI) thread. + */ + let logHtml; + if(self.window === self /* UI thread */){ + console.log("Running demo from main UI thread."); + logHtml = function(cssClass,...args){ + const ln = document.createElement('div'); + if(cssClass) ln.classList.add(cssClass); + ln.append(document.createTextNode(args.join(' '))); + document.body.append(ln); + }; + }else{ /* Worker thread */ + console.log("Running demo from Worker thread."); + logHtml = function(cssClass,...args){ + postMessage({ + type:'log', + payload:{cssClass, args} + }); + }; + } + const log = (...args)=>logHtml('',...args); + const warn = (...args)=>logHtml('warning',...args); + const error = (...args)=>logHtml('error',...args); + + const demo1 = function(sqlite3){ + const capi = sqlite3.capi/*C-style API*/, + oo = sqlite3.oo1/*high-level OO API*/; + log("sqlite3 version",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); + const db = new oo.DB("/mydb.sqlite3",'ct'); + log("transient db =",db.filename); + /** + Never(!) rely on garbage collection to clean up DBs and + (especially) prepared statements. Always wrap their lifetimes + in a try/finally construct, as demonstrated below. By and + large, client code can entirely avoid lifetime-related + complications of prepared statement objects by using the + DB.exec() method for SQL execution. + */ + try { + log("Create a table..."); + db.exec("CREATE TABLE IF NOT EXISTS t(a,b)"); + //Equivalent: + db.exec({ + sql:"CREATE TABLE IF NOT EXISTS t(a,b)" + // ... numerous other options ... + }); + // SQL can be either a string or a byte array + // or an array of strings which get concatenated + // together as-is (so be sure to end each statement + // with a semicolon). + + log("Insert some data using exec()..."); + let i; + for( i = 20; i <= 25; ++i ){ + db.exec({ + sql: "insert into t(a,b) values (?,?)", + // bind by parameter index... + bind: [i, i*2] + }); + db.exec({ + sql: "insert into t(a,b) values ($a,$b)", + // bind by parameter name... + bind: {$a: i * 10, $b: i * 20} + }); + } + + log("Insert using a prepared statement..."); + let q = db.prepare([ + // SQL may be a string or array of strings + // (concatenated w/o separators). + "insert into t(a,b) ", + "values(?,?)" + ]); + try { + for( i = 100; i < 103; ++i ){ + q.bind( [i, i*2] ).step(); + q.reset(); + } + // Equivalent... + for( i = 103; i <= 105; ++i ){ + q.bind(1, i).bind(2, i*2).stepReset(); + } + }finally{ + q.finalize(); + } + + log("Query data with exec() using rowMode 'array'..."); + db.exec({ + sql: "select a from t order by a limit 3", + rowMode: 'array', // 'array' (default), 'object', or 'stmt' + callback: function(row){ + log("row ",++this.counter,"=",row); + }.bind({counter: 0}) + }); + + log("Query data with exec() using rowMode 'object'..."); + db.exec({ + sql: "select a as aa, b as bb from t order by aa limit 3", + rowMode: 'object', + callback: function(row){ + log("row ",++this.counter,"=",JSON.stringify(row)); + }.bind({counter: 0}) + }); + + log("Query data with exec() using rowMode 'stmt'..."); + db.exec({ + sql: "select a from t order by a limit 3", + rowMode: 'stmt', + callback: function(row){ + log("row ",++this.counter,"get(0) =",row.get(0)); + }.bind({counter: 0}) + }); + + log("Query data with exec() using rowMode INTEGER (result column index)..."); + db.exec({ + sql: "select a, b from t order by a limit 3", + rowMode: 1, // === result column 1 + callback: function(row){ + log("row ",++this.counter,"b =",row); + }.bind({counter: 0}) + }); + + log("Query data with exec() using rowMode $COLNAME (result column name)..."); + db.exec({ + sql: "select a a, b from t order by a limit 3", + rowMode: '$a', + callback: function(value){ + log("row ",++this.counter,"a =",value); + }.bind({counter: 0}) + }); + + log("Query data with exec() without a callback..."); + let resultRows = []; + db.exec({ + sql: "select a, b from t order by a limit 3", + rowMode: 'object', + resultRows: resultRows + }); + log("Result rows:",JSON.stringify(resultRows,undefined,2)); + + log("Create a scalar UDF..."); + db.createFunction({ + name: 'twice', + xFunc: function(pCx, arg){ // note the call arg count + return arg + arg; + } + }); + log("Run scalar UDF and collect result column names..."); + let columnNames = []; + db.exec({ + sql: "select a, twice(a), twice(''||a) from t order by a desc limit 3", + columnNames: columnNames, + rowMode: 'stmt', + callback: function(row){ + log("a =",row.get(0), "twice(a) =", row.get(1), + "twice(''||a) =",row.get(2)); + } + }); + log("Result column names:",columnNames); + + try{ + log("The following use of the twice() UDF will", + "fail because of incorrect arg count..."); + db.exec("select twice(1,2,3)"); + }catch(e){ + warn("Got expected exception:",e.message); + } + + try { + db.transaction( function(D) { + D.exec("delete from t"); + log("In transaction: count(*) from t =",db.selectValue("select count(*) from t")); + throw new sqlite3.SQLite3Error("Demonstrating transaction() rollback"); + }); + }catch(e){ + if(e instanceof sqlite3.SQLite3Error){ + log("Got expected exception from db.transaction():",e.message); + log("count(*) from t =",db.selectValue("select count(*) from t")); + }else{ + throw e; + } + } + + try { + db.savepoint( function(D) { + D.exec("delete from t"); + log("In savepoint: count(*) from t =",db.selectValue("select count(*) from t")); + D.savepoint(function(DD){ + const rows = []; + DD.exec({ + sql: ["insert into t(a,b) values(99,100);", + "select count(*) from t"], + rowMode: 0, + resultRows: rows + }); + log("In nested savepoint. Row count =",rows[0]); + throw new sqlite3.SQLite3Error("Demonstrating nested savepoint() rollback"); + }) + }); + }catch(e){ + if(e instanceof sqlite3.SQLite3Error){ + log("Got expected exception from nested db.savepoint():",e.message); + log("count(*) from t =",db.selectValue("select count(*) from t")); + }else{ + throw e; + } + } + }finally{ + db.close(); + } + + log("That's all, folks!"); + + /** + Some of the features of the OO API not demonstrated above... + + - get change count (total or statement-local, 32- or 64-bit) + - get a DB's file name + + Misc. Stmt features: + + - Various forms of bind() + - clearBindings() + - reset() + - Various forms of step() + - Variants of get() for explicit type treatment/conversion, + e.g. getInt(), getFloat(), getBlob(), getJSON() + - getColumnName(ndx), getColumnNames() + - getParamIndex(name) + */ + }/*demo1()*/; + + log("Loading and initializing sqlite3 module..."); + if(self.window!==self) /*worker thread*/{ + /* + If sqlite3.js is in a directory other than this script, in order + to get sqlite3.js to resolve sqlite3.wasm properly, we have to + explicitly tell it where sqlite3.js is being loaded from. We do + that by passing the `sqlite3.dir=theDirName` URL argument to + _this_ script. That URL argument will be seen by the JS/WASM + loader and it will adjust the sqlite3.wasm path accordingly. If + sqlite3.js/.wasm are in the same directory as this script then + that's not needed. + + URL arguments passed as part of the filename via importScripts() + are simply lost, and such scripts see the self.location of + _this_ script. + */ + let sqlite3Js = 'sqlite3.js'; + const urlParams = new URL(self.location.href).searchParams; + if(urlParams.has('sqlite3.dir')){ + sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; + } + importScripts(sqlite3Js); + } + self.sqlite3InitModule({ + // We can redirect any stdout/stderr from the module + // like so... + print: log, + printErr: error + }).then(function(sqlite3){ + //console.log('sqlite3 =',sqlite3); + log("Done initializing. Running demo..."); + try { + demo1(sqlite3); + }catch(e){ + error("Exception:",e.message); + } + }); +})(); diff --git a/ext/wasm/demo-jsstorage.html b/ext/wasm/demo-jsstorage.html new file mode 100644 index 000000000..79f4a3b4b --- /dev/null +++ b/ext/wasm/demo-jsstorage.html @@ -0,0 +1,49 @@ + + + + + + + + + sqlite3-kvvfs.js tests + + +
    sqlite3-kvvfs.js tests
    + +
    +
    +
    Initializing app...
    +
    + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
    +
    +
    Downloading...
    +
    + +
    +
    + Options +
    + + + + + +
    +
    +
    + + + + + + diff --git a/ext/wasm/demo-jsstorage.js b/ext/wasm/demo-jsstorage.js new file mode 100644 index 000000000..cf820e403 --- /dev/null +++ b/ext/wasm/demo-jsstorage.js @@ -0,0 +1,114 @@ +/* + 2022-09-12 + + The author disclaims copyright to this source code. In place of a + legal notice, here is a blessing: + + * May you do good and not evil. + * May you find forgiveness for yourself and forgive others. + * May you share freely, never taking more than you give. + + *********************************************************************** + + A basic test script for sqlite3.wasm with kvvfs support. This file + must be run in main JS thread and sqlite3.js must have been loaded + before it. +*/ +'use strict'; +(function(){ + const T = self.SqliteTestUtil; + const toss = function(...args){throw new Error(args.join(' '))}; + const debug = console.debug.bind(console); + const eOutput = document.querySelector('#test-output'); + const logC = console.log.bind(console) + const logE = function(domElement){ + eOutput.append(domElement); + }; + const logHtml = function(cssClass,...args){ + const ln = document.createElement('div'); + if(cssClass) ln.classList.add(cssClass); + ln.append(document.createTextNode(args.join(' '))); + logE(ln); + } + const log = function(...args){ + logC(...args); + logHtml('',...args); + }; + const warn = function(...args){ + logHtml('warning',...args); + }; + const error = function(...args){ + logHtml('error',...args); + }; + + const runTests = function(sqlite3){ + const capi = sqlite3.capi, + oo = sqlite3.oo1, + wasm = sqlite3.wasm; + log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); + T.assert( 0 !== capi.sqlite3_vfs_find(null) ); + if(!capi.sqlite3_vfs_find('kvvfs')){ + error("This build is not kvvfs-capable."); + return; + } + + const dbStorage = 0 ? 'session' : 'local'; + const theStore = 's'===dbStorage[0] ? sessionStorage : localStorage; + const db = new oo.JsStorageDb( dbStorage ); + // Or: oo.DB(dbStorage, 'c', 'kvvfs') + log("db.storageSize():",db.storageSize()); + document.querySelector('#btn-clear-storage').addEventListener('click',function(){ + const sz = db.clearStorage(); + log("kvvfs",db.filename+"Storage cleared:",sz,"entries."); + }); + document.querySelector('#btn-clear-log').addEventListener('click',function(){ + eOutput.innerText = ''; + }); + document.querySelector('#btn-init-db').addEventListener('click',function(){ + try{ + const saveSql = []; + db.exec({ + sql: ["drop table if exists t;", + "create table if not exists t(a);", + "insert into t(a) values(?),(?),(?)"], + bind: [performance.now() >> 0, + (performance.now() * 2) >> 0, + (performance.now() / 2) >> 0], + saveSql + }); + console.log("saveSql =",saveSql,theStore); + log("DB (re)initialized."); + }catch(e){ + error(e.message); + } + }); + const btnSelect = document.querySelector('#btn-select1'); + btnSelect.addEventListener('click',function(){ + log("DB rows:"); + try{ + db.exec({ + sql: "select * from t order by a", + rowMode: 0, + callback: (v)=>log(v) + }); + }catch(e){ + error(e.message); + } + }); + document.querySelector('#btn-storage-size').addEventListener('click',function(){ + log("size.storageSize(",dbStorage,") says", db.storageSize(), + "bytes"); + }); + log("Storage backend:",db.filename); + if(0===db.selectValue('select count(*) from sqlite_master')){ + log("DB is empty. Use the init button to populate it."); + }else{ + log("DB contains data from a previous session. Use the Clear Ctorage button to delete it."); + btnSelect.click(); + } + }; + + sqlite3InitModule(self.sqlite3TestModule).then((sqlite3)=>{ + runTests(sqlite3); + }); +})(); diff --git a/ext/fiddle/testing2.html b/ext/wasm/demo-worker1-promiser.html similarity index 64% rename from ext/fiddle/testing2.html rename to ext/wasm/demo-worker1-promiser.html index b773c4aa4..e99131e6c 100644 --- a/ext/fiddle/testing2.html +++ b/ext/wasm/demo-worker1-promiser.html @@ -4,13 +4,12 @@ - - - sqlite3-worker.js tests - + + + worker-promise tests -
    sqlite3-worker.js tests
    +
    worker-promise tests
    @@ -25,8 +24,11 @@
    -
    Everything on this page happens in the dev console.
    - - +
    Most stuff on this page happens in the dev console.
    +
    +
    + + + diff --git a/ext/wasm/demo-worker1-promiser.js b/ext/wasm/demo-worker1-promiser.js new file mode 100644 index 000000000..a65cc31b6 --- /dev/null +++ b/ext/wasm/demo-worker1-promiser.js @@ -0,0 +1,270 @@ +/* + 2022-08-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. + + *********************************************************************** + + Demonstration of the sqlite3 Worker API #1 Promiser: a Promise-based + proxy for for the sqlite3 Worker #1 API. +*/ +'use strict'; +(function(){ + const T = self.SqliteTestUtil; + const eOutput = document.querySelector('#test-output'); + const warn = console.warn.bind(console); + const error = console.error.bind(console); + const log = console.log.bind(console); + const logHtml = async function(cssClass,...args){ + log.apply(this, args); + const ln = document.createElement('div'); + if(cssClass) ln.classList.add(cssClass); + ln.append(document.createTextNode(args.join(' '))); + eOutput.append(ln); + }; + + let startTime; + const testCount = async ()=>{ + logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms"); + }; + + //why is this triggered even when we catch() a Promise? + //window.addEventListener('unhandledrejection', function(event) { + // warn('unhandledrejection',event); + //}); + + const promiserConfig = { + worker: ()=>{ + const w = new Worker("jswasm/sqlite3-worker1.js"); + w.onerror = (event)=>error("worker.onerror",event); + return w; + }, + debug: 1 ? undefined : (...args)=>console.debug('worker debug',...args), + onunhandled: function(ev){ + error("Unhandled worker message:",ev.data); + }, + onready: function(){ + self.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/; + runTests(); + }, + onerror: function(ev){ + error("worker1 error:",ev); + } + }; + const workerPromise = self.sqlite3Worker1Promiser(promiserConfig); + delete self.sqlite3Worker1Promiser; + + const wtest = async function(msgType, msgArgs, callback){ + if(2===arguments.length && 'function'===typeof msgArgs){ + callback = msgArgs; + msgArgs = undefined; + } + const p = workerPromise({type: msgType, args:msgArgs}); + return callback ? p.then(callback).finally(testCount) : p; + }; + + const runTests = async function(){ + const dbFilename = '/testing2.sqlite3'; + startTime = performance.now(); + + let sqConfig; + await wtest('config-get', (ev)=>{ + const r = ev.result; + log('sqlite3.config subset:', r); + T.assert('boolean' === typeof r.bigIntEnabled) + .assert('string'===typeof r.wasmfsOpfsDir) + .assert('boolean' === typeof r.wasmfsOpfsEnabled); + sqConfig = r; + }); + logHtml('', + "Sending 'open' message and waiting for its response before continuing..."); + + await wtest('open', { + filename: dbFilename, + simulateError: 0 /* if true, fail the 'open' */, + }, function(ev){ + const r = ev.result; + log("then open result",r); + T.assert(ev.dbId === r.dbId) + .assert(ev.messageId) + .assert('string' === typeof r.vfs); + promiserConfig.dbId = ev.dbId; + }).then(runTests2); + }; + + const runTests2 = async function(){ + const mustNotReach = ()=>toss("This is not supposed to be reached."); + + await wtest('exec',{ + sql: ["create table t(a,b)", + "insert into t(a,b) values(1,2),(3,4),(5,6)" + ].join(';'), + multi: true, + resultRows: [], columnNames: [] + }, function(ev){ + ev = ev.result; + T.assert(0===ev.resultRows.length) + .assert(0===ev.columnNames.length); + }); + + await wtest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], + }, function(ev){ + ev = ev.result; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0][0]) + .assert(6===ev.resultRows[2][1]) + .assert(2===ev.columnNames.length) + .assert('b'===ev.columnNames[1]); + }); + + await wtest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], + rowMode: 'object' + }, function(ev){ + ev = ev.result; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0].a) + .assert(6===ev.resultRows[2].b) + }); + + await wtest( + 'exec', + {sql:'intentional_error'}, + mustNotReach + ).catch((e)=>{ + warn("Intentional error:",e); + }); + + await wtest('exec',{ + sql:'select 1 union all select 3', + resultRows: [], + }, function(ev){ + ev = ev.result; + T.assert(2 === ev.resultRows.length) + .assert(1 === ev.resultRows[0][0]) + .assert(3 === ev.resultRows[1][0]); + }); + + const resultRowTest1 = function f(ev){ + if(undefined === f.counter) f.counter = 0; + if(null === ev.rowNumber){ + /* End of result set. */ + T.assert(undefined === ev.row) + .assert(2===ev.columnNames.length) + .assert('a'===ev.columnNames[0]) + .assert('B'===ev.columnNames[1]); + }else{ + T.assert(ev.rowNumber > 0); + ++f.counter; + } + log("exec() result row:",ev); + T.assert(null === ev.rowNumber || 'number' === typeof ev.row.B); + }; + await wtest('exec',{ + sql: 'select a a, b B from t order by a limit 3', + callback: resultRowTest1, + rowMode: 'object' + }, function(ev){ + T.assert(3===resultRowTest1.counter); + resultRowTest1.counter = 0; + }); + + const resultRowTest2 = function f(ev){ + if(null === ev.rowNumber){ + /* End of result set. */ + T.assert(undefined === ev.row) + .assert(1===ev.columnNames.length) + .assert('a'===ev.columnNames[0]) + }else{ + T.assert(ev.rowNumber > 0); + f.counter = ev.rowNumber; + } + log("exec() result row:",ev); + T.assert(null === ev.rowNumber || 'number' === typeof ev.row); + }; + await wtest('exec',{ + sql: 'select a a from t limit 3', + callback: resultRowTest2, + rowMode: 0 + }, function(ev){ + T.assert(3===resultRowTest2.counter); + }); + + const resultRowTest3 = function f(ev){ + if(null === ev.rowNumber){ + T.assert(3===ev.columnNames.length) + .assert('foo'===ev.columnNames[0]) + .assert('bar'===ev.columnNames[1]) + .assert('baz'===ev.columnNames[2]); + }else{ + f.counter = ev.rowNumber; + T.assert('number' === typeof ev.row); + } + }; + await wtest('exec',{ + sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2", + callback: resultRowTest3, + columnNames: [], + rowMode: ':bar' + }, function(ev){ + log("exec() result row:",ev); + T.assert(2===resultRowTest3.counter); + }); + + await wtest('exec',{ + multi: true, + sql:[ + 'pragma foreign_keys=0;', + // ^^^ arbitrary query with no result columns + 'select a, b from t order by a desc; select a from t;' + // multi-exec only honors results from the first + // statement with result columns (regardless of whether) + // it has any rows). + ], + rowMode: 1, + resultRows: [] + },function(ev){ + const rows = ev.result.resultRows; + T.assert(3===rows.length). + assert(6===rows[0]); + }); + + await wtest('exec',{sql: 'delete from t where a>3'}); + + await wtest('exec',{ + sql: 'select count(a) from t', + resultRows: [] + },function(ev){ + ev = ev.result; + T.assert(1===ev.resultRows.length) + .assert(2===ev.resultRows[0][0]); + }); + + await wtest('export', function(ev){ + ev = ev.result; + T.assert('string' === typeof ev.filename) + .assert(ev.byteArray instanceof Uint8Array) + .assert(ev.byteArray.length > 1024) + .assert('application/x-sqlite3' === ev.mimetype); + }); + + /***** close() tests must come last. *****/ + await wtest('close',{},function(ev){ + T.assert('string' === typeof ev.result.filename); + }); + + await wtest('close', (ev)=>{ + T.assert(undefined === ev.result.filename); + }).finally(()=>logHtml('',"That's all, folks!")); + }/*runTests2()*/; + + log("Init complete, but async init bits may still be running."); +})(); diff --git a/ext/fiddle/testing1.html b/ext/wasm/demo-worker1.html similarity index 67% rename from ext/fiddle/testing1.html rename to ext/wasm/demo-worker1.html index bf22f30ff..c766ffd44 100644 --- a/ext/fiddle/testing1.html +++ b/ext/wasm/demo-worker1.html @@ -1,16 +1,16 @@ + - - - sqlite3-api.js tests - + + + sqlite3-worker1.js tests -
    sqlite3-api.js tests
    +
    sqlite3-worker1.js tests
    @@ -25,9 +25,10 @@
    -
    Everything on this page happens in the dev console.
    - - - +
    Most stuff on this page happens in the dev console.
    +
    +
    + + diff --git a/ext/wasm/demo-worker1.js b/ext/wasm/demo-worker1.js new file mode 100644 index 000000000..cc63f3a7c --- /dev/null +++ b/ext/wasm/demo-worker1.js @@ -0,0 +1,345 @@ +/* + 2022-05-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. + + *********************************************************************** + + A basic test script for sqlite3-worker1.js. + + Note that the wrapper interface demonstrated in + demo-worker1-promiser.js is much easier to use from client code, as it + lacks the message-passing acrobatics demonstrated in this file. +*/ +'use strict'; +(function(){ + const T = self.SqliteTestUtil; + const SW = new Worker("jswasm/sqlite3-worker1.js"); + const DbState = { + id: undefined + }; + const eOutput = document.querySelector('#test-output'); + const log = console.log.bind(console); + const logHtml = function(cssClass,...args){ + log.apply(this, args); + const ln = document.createElement('div'); + if(cssClass) ln.classList.add(cssClass); + ln.append(document.createTextNode(args.join(' '))); + eOutput.append(ln); + }; + const warn = console.warn.bind(console); + const error = console.error.bind(console); + const toss = (...args)=>{throw new Error(args.join(' '))}; + + SW.onerror = function(event){ + error("onerror",event); + }; + + let startTime; + + /** + A queue for callbacks which are to be run in response to async + DB commands. See the notes in runTests() for why we need + this. The event-handling plumbing of this file requires that + any DB command which includes a `messageId` property also have + a queued callback entry, as the existence of that property in + response payloads is how it knows whether or not to shift an + entry off of the queue. + */ + const MsgHandlerQueue = { + queue: [], + id: 0, + push: function(type,callback){ + this.queue.push(callback); + return type + '-' + (++this.id); + }, + shift: function(){ + return this.queue.shift(); + } + }; + + const testCount = ()=>{ + logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms"); + }; + + const logEventResult = function(ev){ + const evd = ev.result; + logHtml(evd.errorClass ? 'error' : '', + "runOneTest",ev.messageId,"Worker time =", + (ev.workerRespondTime - ev.workerReceivedTime),"ms.", + "Round-trip event time =", + (performance.now() - ev.departureTime),"ms.", + (evd.errorClass ? ev.message : "")//, JSON.stringify(evd) + ); + }; + + const runOneTest = function(eventType, eventArgs, callback){ + T.assert(eventArgs && 'object'===typeof eventArgs); + /* ^^^ that is for the testing and messageId-related code, not + a hard requirement of all of the Worker-exposed APIs. */ + const messageId = MsgHandlerQueue.push(eventType,function(ev){ + logEventResult(ev); + if(callback instanceof Function){ + callback(ev); + testCount(); + } + }); + const msg = { + type: eventType, + args: eventArgs, + dbId: DbState.id, + messageId: messageId, + departureTime: performance.now() + }; + log("Posting",eventType,"message to worker dbId="+(DbState.id||'default')+':',msg); + SW.postMessage(msg); + }; + + /** Methods which map directly to onmessage() event.type keys. + They get passed the inbound event.data. */ + const dbMsgHandler = { + open: function(ev){ + DbState.id = ev.dbId; + log("open result",ev); + }, + exec: function(ev){ + log("exec result",ev); + }, + export: function(ev){ + log("export result",ev); + }, + error: function(ev){ + error("ERROR from the worker:",ev); + logEventResult(ev); + }, + resultRowTest1: function f(ev){ + if(undefined === f.counter) f.counter = 0; + if(null === ev.rowNumber){ + /* End of result set. */ + T.assert(undefined === ev.row) + .assert(Array.isArray(ev.columnNames)) + .assert(ev.columnNames.length); + }else{ + T.assert(ev.rowNumber > 0); + ++f.counter; + } + //log("exec() result row:",ev); + T.assert(null === ev.rowNumber || 'number' === typeof ev.row.b); + } + }; + + /** + "The problem" now is that the test results are async. We + know, however, that the messages posted to the worker will + be processed in the order they are passed to it, so we can + create a queue of callbacks to handle them. The problem + with that approach is that it's not error-handling + friendly, in that an error can cause us to bypass a result + handler queue entry. We have to perform some extra + acrobatics to account for that. + + Problem #2 is that we cannot simply start posting events: we + first have to post an 'open' event, wait for it to respond, and + collect its db ID before continuing. If we don't wait, we may + well fire off 10+ messages before the open actually responds. + */ + const runTests2 = function(){ + const mustNotReach = ()=>{ + throw new Error("This is not supposed to be reached."); + }; + runOneTest('exec',{ + sql: ["create table t(a,b);", + "insert into t(a,b) values(1,2),(3,4),(5,6)" + ], + resultRows: [], columnNames: [] + }, function(ev){ + ev = ev.result; + T.assert(0===ev.resultRows.length) + .assert(0===ev.columnNames.length); + }); + runOneTest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], saveSql:[] + }, function(ev){ + ev = ev.result; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0][0]) + .assert(6===ev.resultRows[2][1]) + .assert(2===ev.columnNames.length) + .assert('b'===ev.columnNames[1]); + }); + //if(1){ error("Returning prematurely for testing."); return; } + runOneTest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], + rowMode: 'object' + }, function(ev){ + ev = ev.result; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0].a) + .assert(6===ev.resultRows[2].b) + }); + runOneTest('exec',{sql:'intentional_error'}, mustNotReach); + // Ensure that the message-handler queue survives ^^^ that error... + runOneTest('exec',{ + sql:'select 1', + resultRows: [], + //rowMode: 'array', // array is the default in the Worker interface + }, function(ev){ + ev = ev.result; + T.assert(1 === ev.resultRows.length) + .assert(1 === ev.resultRows[0][0]); + }); + runOneTest('exec',{ + sql: 'select a a, b b from t order by a', + callback: 'resultRowTest1', + rowMode: 'object' + }, function(ev){ + T.assert(3===dbMsgHandler.resultRowTest1.counter); + dbMsgHandler.resultRowTest1.counter = 0; + }); + runOneTest('exec',{ + sql:[ + "pragma foreign_keys=0;", + // ^^^ arbitrary query with no result columns + "select a, b from t order by a desc;", + "select a from t;" + // multi-statement exec only honors results from the first + // statement with result columns (regardless of whether) + // it has any rows). + ], + rowMode: 1, + resultRows: [] + },function(ev){ + const rows = ev.result.resultRows; + T.assert(3===rows.length). + assert(6===rows[0]); + }); + runOneTest('exec',{sql: 'delete from t where a>3'}); + runOneTest('exec',{ + sql: 'select count(a) from t', + resultRows: [] + },function(ev){ + ev = ev.result; + T.assert(1===ev.resultRows.length) + .assert(2===ev.resultRows[0][0]); + }); + runOneTest('export',{}, function(ev){ + ev = ev.result; + log("export result:",ev); + T.assert('string' === typeof ev.filename) + .assert(ev.byteArray instanceof Uint8Array) + .assert(ev.byteArray.length > 1024) + .assert('application/x-sqlite3' === ev.mimetype); + }); + /***** close() tests must come last. *****/ + runOneTest('close',{unlink:true},function(ev){ + ev = ev.result; + T.assert('string' === typeof ev.filename); + }); + runOneTest('close',{unlink:true},function(ev){ + ev = ev.result; + T.assert(undefined === ev.filename); + logHtml('warning',"This is the final test."); + }); + logHtml('warning',"Finished posting tests. Waiting on async results."); + }; + + const runTests = function(){ + /** + Design decision time: all remaining tests depend on the 'open' + command having succeeded. In order to support multiple DBs, the + upcoming commands ostensibly have to know the ID of the DB they + want to talk to. We have two choices: + + 1) We run 'open' and wait for its response, which contains the + db id. + + 2) We have the Worker automatically use the current "default + db" (the one which was most recently opened) if no db id is + provided in the message. When we do this, the main thread may + well fire off _all_ of the test messages before the 'open' + actually responds, but because the messages are handled on a + FIFO basis, those after the initial 'open' will pick up the + "default" db. However, if the open fails, then all pending + messages (until next next 'open', at least) except for 'close' + will fail and we have no way of cancelling them once they've + been posted to the worker. + + Which approach we use below depends on the boolean value of + waitForOpen. + */ + const waitForOpen = 1, + simulateOpenError = 0 /* if true, the remaining tests will + all barf if waitForOpen is + false. */; + logHtml('', + "Sending 'open' message and",(waitForOpen ? "" : "NOT ")+ + "waiting for its response before continuing."); + startTime = performance.now(); + runOneTest('open', { + filename:'testing2.sqlite3', + simulateError: simulateOpenError + }, function(ev){ + log("open result",ev); + T.assert('testing2.sqlite3'===ev.result.filename) + .assert(ev.dbId) + .assert(ev.messageId) + .assert('string' === typeof ev.result.vfs); + DbState.id = ev.dbId; + if(waitForOpen) setTimeout(runTests2, 0); + }); + if(!waitForOpen) runTests2(); + }; + + SW.onmessage = function(ev){ + if(!ev.data || 'object'!==typeof ev.data){ + warn("Unknown sqlite3-worker message type:",ev); + return; + } + ev = ev.data/*expecting a nested object*/; + //log("main window onmessage:",ev); + if(ev.result && ev.messageId){ + /* We're expecting a queued-up callback handler. */ + const f = MsgHandlerQueue.shift(); + if('error'===ev.type){ + dbMsgHandler.error(ev); + return; + } + T.assert(f instanceof Function); + f(ev); + return; + } + switch(ev.type){ + case 'sqlite3-api': + switch(ev.result){ + case 'worker1-ready': + log("Message:",ev); + self.sqlite3TestModule.setStatus(null); + runTests(); + return; + default: + warn("Unknown sqlite3-api message type:",ev); + return; + } + default: + if(dbMsgHandler.hasOwnProperty(ev.type)){ + try{dbMsgHandler[ev.type](ev);} + catch(err){ + error("Exception while handling db result message", + ev,":",err); + } + return; + } + warn("Unknown sqlite3-api message type:",ev); + } + }; + log("Init complete, but async init bits may still be running."); + log("Installing Worker into global scope SW for dev purposes."); + self.SW = SW; +})(); diff --git a/ext/wasm/dist.make b/ext/wasm/dist.make new file mode 100644 index 000000000..5aee8af77 --- /dev/null +++ b/ext/wasm/dist.make @@ -0,0 +1,101 @@ +#!/do/not/make +#^^^ help emacs select edit mode +# +# Intended to include'd by ./GNUmakefile. +# +# 'make dist' rules for creating a distribution archive of the WASM/JS +# pieces, noting that we only build a dist of the built files, not the +# numerous pieces required to build them. +####################################################################### +MAKEFILE.dist := $(lastword $(MAKEFILE_LIST)) + +######################################################################## +# Chicken/egg situation: we need $(bin.version-info) to get the version +# info for the archive name, but that binary may not yet be built, and +# won't be built until we expand the dependencies. We have to use a +# temporary name for the archive. +dist-name = sqlite-wasm-TEMP +#ifeq (0,1) +# $(info WARNING *******************************************************************) +# $(info ** Be sure to create the desired build configuration before creating the) +# $(info ** distribution archive. Use one of the following targets to do so:) +# $(info **) +# $(info ** o2: builds with -O2, resulting in the fastest builds) +# $(info ** oz: builds with -Oz, resulting in the smallest builds) +# $(info /WARNING *******************************************************************) +#endif + +######################################################################## +# dist.build must be the name of a target which triggers the +# build of the files to be packed into the dist archive. The +# intention is that it be one of (o0, o1, o2, o3, os, oz), each of +# which uses like-named -Ox optimization level flags. The o2 target +# provides the best overall runtime speeds. The oz target provides +# slightly slower speeds (roughly 10%) with significantly smaller WASM +# file sizes. Note that -O2 (the o2 target) results in faster binaries +# than both -O3 and -Os (the o3 and os targets) in all tests run to +# date. +dist.build ?= oz + +dist-dir.top := $(dist-name) +dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout)) +dist-dir.common := $(dist-dir.top)/common +dist.top.extras := \ + demo-123.html demo-123-worker.html demo-123.js \ + tester1.html tester1-worker.html tester1.js \ + demo-jsstorage.html demo-jsstorage.js \ + demo-worker1.html demo-worker1.js \ + demo-worker1-promiser.html demo-worker1-promiser.js +dist.jswasm.extras := $(sqlite3-api.ext.jses) $(sqlite3.wasm) +dist.common.extras := \ + $(wildcard $(dir.common)/*.css) \ + $(dir.common)/SqliteTestUtil.js + +.PHONY: dist +######################################################################## +# dist: create the end-user deliverable archive. +# +# Maintenance reminder: because dist depends on $(dist.build), and +# $(dist.build) will depend on clean, having any deps on +# $(dist-archive) which themselves may be cleaned up by the clean +# target will lead to grief in parallel builds (-j #). Thus +# $(dist-target)'s deps must be trimmed to non-generated files or +# files which are _not_ cleaned up by the clean target. +# +# Note that we require $(bin.version-info) in order to figure out the +# dist file's name, so cannot (without a recursive make) have the +# target name equal to the archive name. +dist: \ + $(bin.stripccomments) $(bin.version-info) \ + $(dist.build) \ + $(MAKEFILE) $(MAKEFILE.dist) + @echo "Making end-user deliverables..." + @rm -fr $(dist-dir.top) + @mkdir -p $(dist-dir.jswasm) $(dist-dir.common) + @cp -p $(dist.top.extras) $(dist-dir.top) + @cp -p README-dist.txt $(dist-dir.top)/README.txt + @cp -p index-dist.html $(dist-dir.top)/index.html + @cp -p $(dist.jswasm.extras) $(dist-dir.jswasm) + @$(bin.stripccomments) -k -k < $(sqlite3.js) \ + > $(dist-dir.jswasm)/$(notdir $(sqlite3.js)) + @cp -p $(dist.common.extras) $(dist-dir.common) + @set -e; \ + vnum=$$($(bin.version-info) --download-version); \ + vdir=sqlite-wasm-$$vnum; \ + arczip=$$vdir.zip; \ + echo "Making $$arczip ..."; \ + rm -fr $$arczip $$vdir; \ + mv $(dist-dir.top) $$vdir; \ + zip -qr $$arczip $$vdir; \ + rm -fr $$vdir; \ + ls -la $$arczip; \ + set +e; \ + unzip -lv $$arczip || echo "Missing unzip app? Not fatal." + +# We need a separate `clean` rule to account for weirdness in +# a sub-make, where we get a copy of the $(dist-name) dir +# copied into the new $(dist-name) dir. +.PHONY: dist-clean +clean: dist-clean +dist-clean: + rm -fr $(dist-name) $(wildcard sqlite-wasm-*.zip) diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make new file mode 100644 index 000000000..43e6941f5 --- /dev/null +++ b/ext/wasm/fiddle.make @@ -0,0 +1,194 @@ +#!/do/not/make +#^^^ help emacs select edit mode +# +# Intended to include'd by ./GNUmakefile. +####################################################################### +MAKEFILE.fiddle := $(lastword $(MAKEFILE_LIST)) + +######################################################################## +# shell.c and its build flags... +make-np-0 := make -C $(dir.top) -n -p +make-np-1 := sed -e 's/(TOP)/(dir.top)/g' +$(eval $(shell $(make-np-0) | grep -e '^SHELL_OPT ' | $(make-np-1))) +$(eval $(shell $(make-np-0) | grep -e '^SHELL_SRC ' | $(make-np-1))) +# ^^^ can't do that in 1 invocation b/c newlines get stripped +ifeq (,$(SHELL_OPT)) +$(error Could not parse SHELL_OPT from $(dir.top)/Makefile.) +endif +ifeq (,$(SHELL_SRC)) +$(error Could not parse SHELL_SRC from $(dir.top)/Makefile.) +endif +$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl + $(MAKE) -C $(dir.top) shell.c +# /shell.c +######################################################################## + +EXPORTED_FUNCTIONS.fiddle := $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle +fiddle.emcc-flags = \ + $(emcc.cflags) $(emcc_opt_full) \ + --minify 0 \ + -sALLOW_TABLE_GROWTH \ + -sABORTING_MALLOC \ + -sSTRICT_JS \ + -sENVIRONMENT=web,worker \ + -sMODULARIZE \ + -sDYNAMIC_EXECUTION=0 \ + -sWASM_BIGINT=$(emcc.WASM_BIGINT) \ + -sEXPORT_NAME=$(sqlite3.js.init-func) \ + -Wno-limited-postlink-optimizations \ + $(sqlite3.js.flags.--post-js) \ + $(emcc.exportedRuntimeMethods) \ + -sEXPORTED_FUNCTIONS=@$(abspath $(EXPORTED_FUNCTIONS.fiddle)) \ + $(SQLITE_OPT) $(SHELL_OPT) \ + -DSQLITE_SHELL_FIDDLE +# -D_POSIX_C_SOURCE is needed for strdup() with emcc + +fiddle.EXPORTED_FUNCTIONS.in := \ + EXPORTED_FUNCTIONS.fiddle.in \ + $(EXPORTED_FUNCTIONS.api) + +$(EXPORTED_FUNCTIONS.fiddle): $(fiddle.EXPORTED_FUNCTIONS.in) $(MAKEFILE.fiddle) + sort -u $(fiddle.EXPORTED_FUNCTIONS.in) > $@ + +fiddle-module.js := $(dir.fiddle)/fiddle-module.js +fiddle-module.wasm := $(subst .js,.wasm,$(fiddle-module.js)) +fiddle.cses := $(dir.top)/shell.c $(sqlite3-wasm.c) + +fiddle.SOAP.js := $(dir.fiddle)/$(notdir $(SOAP.js)) +$(fiddle.SOAP.js): $(SOAP.js) + cp $< $@ + +$(eval $(call call-make-pre-js,fiddle-module)) +$(fiddle-module.js): $(MAKEFILE) $(MAKEFILE.fiddle) \ + $(EXPORTED_FUNCTIONS.fiddle) \ + $(fiddle.cses) $(pre-post-fiddle-module.deps) $(fiddle.SOAP.js) + $(emcc.bin) -o $@ $(fiddle.emcc-flags) \ + $(pre-post-common.flags) $(pre-post-fiddle-module.flags) \ + $(fiddle.cses) + $(maybe-wasm-strip) $(fiddle-module.wasm) + gzip < $@ > $@.gz + gzip < $(fiddle-module.wasm) > $(fiddle-module.wasm).gz + +$(dir.fiddle)/fiddle.js.gz: $(dir.fiddle)/fiddle.js + gzip < $< > $@ + +clean: clean-fiddle +clean-fiddle: + rm -f $(fiddle-module.js) $(fiddle-module.js).gz \ + $(fiddle-module.wasm) $(fiddle-module.wasm).gz \ + $(dir.fiddle)/$(SOAP.js) \ + $(dir.fiddle)/fiddle-module.worker.js \ + EXPORTED_FUNCTIONS.fiddle +.PHONY: fiddle +fiddle: $(fiddle-module.js) $(dir.fiddle)/fiddle.js.gz +all: fiddle + +######################################################################## +# fiddle_remote is the remote destination for the fiddle app. It +# must be a [user@]HOST:/path for rsync. +# Note that the target "should probably" contain a symlink of +# index.html -> fiddle.html. +fiddle_remote ?= +ifeq (,$(fiddle_remote)) +ifneq (,$(wildcard /home/stephan)) + fiddle_remote = wh:www/wh/sqlite3/. +else ifneq (,$(wildcard /home/drh)) + #fiddle_remote = if appropriate, add that user@host:/path here +endif +endif +push-fiddle: fiddle + @if [ x = "x$(fiddle_remote)" ]; then \ + echo "fiddle_remote must be a [user@]HOST:/path for rsync"; \ + exit 1; \ + fi + rsync -va fiddle/ $(fiddle_remote) +# end fiddle remote push +######################################################################## + + +######################################################################## +# Explanation of the emcc build flags follows. Full docs for these can +# be found at: +# +# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js +# +# -sENVIRONMENT=web: elides bootstrap code related to non-web JS +# environments like node.js. Removing this makes the output a tiny +# tick larger but hypothetically makes it more portable to +# non-browser JS environments. +# +# -sMODULARIZE: changes how the generated code is structured to avoid +# declaring a global Module object and instead installing a function +# which loads and initializes the module. The function is named... +# +# -sEXPORT_NAME=jsFunctionName (see -sMODULARIZE) +# +# -sEXPORTED_RUNTIME_METHODS=@/absolute/path/to/file: a file +# containing a list of emscripten-supplied APIs, one per line, which +# must be exported into the generated JS. Must be an absolute path! +# +# -sEXPORTED_FUNCTIONS=@/absolute/path/to/file: a file containing a +# list of C functions, one per line, which must be exported via wasm +# so they're visible to JS. C symbols names in that file must all +# start with an underscore for reasons known only to the emcc +# developers. e.g., _sqlite3_open_v2 and _sqlite3_finalize. Must be +# an absolute path! +# +# -sSTRICT_JS ensures that the emitted JS code includes the 'use +# strict' option. Note that -sSTRICT is more broadly-scoped and +# results in build errors. +# +# -sALLOW_TABLE_GROWTH is required for (at a minimum) the UDF-binding +# feature. Without it, JS functions cannot be made to proxy C-side +# callbacks. +# +# -sABORTING_MALLOC causes the JS-bound _malloc() to abort rather than +# return 0 on OOM. If set to 0 then all code which uses _malloc() +# must, just like in C, check the result before using it, else +# they're likely to corrupt the JS/WASM heap by writing to its +# address of 0. It is, as of this writing, enabled in Emscripten by +# default but we enable it explicitly in case that default changes. +# +# -sDYNAMIC_EXECUTION=0 disables eval() and the Function constructor. +# If the build runs without these, it's preferable to use this flag +# because certain execution environments disallow those constructs. +# This flag is not strictly necessary, however. +# +# -sWASM_BIGINT is UNTESTED but "should" allow the int64-using C APIs +# to work with JS/wasm, insofar as the JS environment supports the +# BigInt type. That support requires an extremely recent browser: +# Safari didn't get that support until late 2020. +# +# --no-entry: for compiling library code with no main(). If this is +# not supplied and the code has a main(), it is called as part of the +# module init process. Note that main() is #if'd out of shell.c +# (renamed) when building in wasm mode. +# +# --pre-js/--post-js=FILE relative or absolute paths to JS files to +# prepend/append to the emcc-generated bootstrapping JS. It's +# easier/faster to develop with separate JS files (reduces rebuilding +# requirements) but certain configurations, namely -sMODULARIZE, may +# require using at least a --pre-js file. They can be used +# individually and need not be paired. +# +# -O0..-O3 and -Oz: optimization levels affect not only C-style +# optimization but whether or not the resulting generated JS code +# gets minified. -O0 compiles _much_ more quickly than -O3 or -Oz, +# and doesn't minimize any JS code, so is recommended for +# development. -O3 or -Oz are recommended for deployment, but +# primarily because -Oz will shrink the wasm file notably. JS-side +# minification makes little difference in terms of overall +# distributable size. +# +# --minify 0: disables minification of the generated JS code, +# regardless of optimization level. Minification of the JS has +# minimal overall effect in the larger scheme of things and results +# in JS files which can neither be edited nor viewed as text files in +# Fossil (which flags them as binary because of their extreme line +# lengths). Interestingly, whether or not the comments in the +# generated JS file get stripped is unaffected by this setting and +# depends entirely on the optimization level. Higher optimization +# levels reduce the size of the JS considerably even without +# minification. +# +######################################################################## diff --git a/ext/wasm/fiddle/emscripten.css b/ext/wasm/fiddle/emscripten.css new file mode 100644 index 000000000..7e3dc811d --- /dev/null +++ b/ext/wasm/fiddle/emscripten.css @@ -0,0 +1,24 @@ +/* emcscript-related styling, used during the module load/intialization processes... */ +.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } +div.emscripten { text-align: center; } +div.emscripten_border { border: 1px solid black; } +#module-spinner { overflow: visible; } +#module-spinner > * { + margin-top: 1em; +} +.spinner { + height: 50px; + width: 50px; + margin: 0px auto; + animation: rotation 0.8s linear infinite; + border-left: 10px solid rgb(0,150,240); + border-right: 10px solid rgb(0,150,240); + border-bottom: 10px solid rgb(0,150,240); + border-top: 10px solid rgb(100,0,200); + border-radius: 100%; + background-color: rgb(200,100,250); +} +@keyframes rotation { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js new file mode 100644 index 000000000..a60b79ab2 --- /dev/null +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -0,0 +1,379 @@ +/* + 2022-05-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 is the JS Worker file for the sqlite3 fiddle app. It loads the + sqlite3 wasm module and offers access to the db via the Worker + message-passing interface. + + Forewarning: this API is still very much Under Construction and + subject to any number of changes as experience reveals what those + need to be. + + Because we can have only a single message handler, as opposed to an + arbitrary number of discrete event listeners like with DOM elements, + we have to define a lower-level message API. Messages abstractly + look like: + + { type: string, data: type-specific value } + + Where 'type' is used for dispatching and 'data' is a + 'type'-dependent value. + + The 'type' values expected by each side of the main/worker + connection vary. The types are described below but subject to + change at any time as this experiment evolves. + + Workers-to-Main types + + - stdout, stderr: indicate stdout/stderr output from the wasm + layer. The data property is the string of the output, noting + that the emscripten binding emits these one line at a time. Thus, + if a C-side puts() emits multiple lines in a single call, the JS + side will see that as multiple calls. Example: + + {type:'stdout', data: 'Hi, world.'} + + - module: Status text. This is intended to alert the main thread + about module loading status so that, e.g., the main thread can + update a progress widget and DTRT when the module is finished + loading and available for work. Status messages come in the form + + {type:'module', data:{ + type:'status', + data: {text:string|null, step:1-based-integer} + } + + with an incrementing step value for each subsequent message. When + the module loading is complete, a message with a text value of + null is posted. + + - working: data='start'|'end'. Indicates that work is about to be + sent to the module or has just completed. This can be used, e.g., + to disable UI elements which should not be activated while work + is pending. Example: + + {type:'working', data:'start'} + + Main-to-Worker types: + + - shellExec: data=text to execute as if it had been entered in the + sqlite3 CLI shell app (as opposed to sqlite3_exec()). This event + causes the worker to emit a 'working' event (data='start') before + it starts and a 'working' event (data='end') when it finished. If + called while work is currently being executed it emits stderr + message instead of doing actual work, as the underlying db cannot + handle concurrent tasks. Example: + + {type:'shellExec', data: 'select * from sqlite_master'} + + - More TBD as the higher-level db layer develops. +*/ + +/* + Apparent browser(s) bug: console messages emitted may be duplicated + in the console, even though they're provably only run once. See: + + https://stackoverflow.com/questions/49659464 + + Noting that it happens in Firefox as well as Chrome. Harmless but + annoying. +*/ +"use strict"; +(function(){ + /** + Posts a message in the form {type,data}. If passed more than 2 + args, the 3rd must be an array of "transferable" values to pass + as the 2nd argument to postMessage(). */ + const wMsg = + (type,data,transferables)=>{ + postMessage({type, data}, transferables || []); + }; + const stdout = (...args)=>wMsg('stdout', args); + const stderr = (...args)=>wMsg('stderr', args); + const toss = (...args)=>{ + throw new Error(args.join(' ')); + }; + const fixmeOPFS = "(FIXME: won't work with OPFS-over-sqlite3_vfs.)"; + let sqlite3 /* gets assigned when the wasm module is loaded */; + + self.onerror = function(/*message, source, lineno, colno, error*/) { + const err = arguments[4]; + if(err && 'ExitStatus'==err.name){ + /* This is relevant for the sqlite3 shell binding but not the + lower-level binding. */ + fiddleModule.isDead = true; + stderr("FATAL ERROR:", err.message); + stderr("Restarting the app requires reloading the page."); + wMsg('error', err); + } + console.error(err); + fiddleModule.setStatus('Exception thrown, see JavaScript console: '+err); + }; + + const Sqlite3Shell = { + /** Returns the name of the currently-opened db. */ + dbFilename: function f(){ + if(!f._) f._ = sqlite3.wasm.xWrap('fiddle_db_filename', "string", ['string']); + return f._(0); + }, + dbHandle: function f(){ + if(!f._) f._ = sqlite3.wasm.xWrap("fiddle_db_handle", "sqlite3*"); + return f._(); + }, + dbIsOpfs: function f(){ + return sqlite3.opfs && sqlite3.capi.sqlite3_js_db_uses_vfs( + this.dbHandle(), "opfs" + ); + }, + runMain: function f(){ + if(f.argv) return 0===f.argv.rc; + const dbName = "/fiddle.sqlite3"; + f.argv = [ + 'sqlite3-fiddle.wasm', + '-bail', '-safe', + dbName + /* Reminder: because of how we run fiddle, we have to ensure + that any argv strings passed to its main() are valid until + the wasm environment shuts down. */ + ]; + const capi = sqlite3.capi, wasm = sqlite3.wasm; + /* We need to call sqlite3_shutdown() in order to avoid numerous + legitimate warnings from the shell about it being initialized + after sqlite3_initialize() has been called. This means, + however, that any initialization done by the JS code may need + to be re-done (e.g. re-registration of dynamically-loaded + VFSes). We need a more generic approach to running such + init-level code. */ + capi.sqlite3_shutdown(); + f.argv.pArgv = wasm.allocMainArgv(f.argv); + f.argv.rc = wasm.exports.fiddle_main( + f.argv.length, f.argv.pArgv + ); + if(f.argv.rc){ + stderr("Fatal error initializing sqlite3 shell."); + fiddleModule.isDead = true; + return false; + } + stdout("SQLite version", capi.sqlite3_libversion(), + capi.sqlite3_sourceid().substr(0,19)); + stdout('Welcome to the "fiddle" shell.'); + if(sqlite3.opfs){ + stdout("\nOPFS is available. To open a persistent db, use:\n\n", + " .open file:name?vfs=opfs\n\nbut note that some", + "features (e.g. upload) do not yet work with OPFS."); + sqlite3.opfs.registerVfs(); + } + stdout('\nEnter ".help" for usage hints.'); + this.exec([ // initialization commands... + '.nullvalue NULL', + '.headers on' + ].join('\n')); + return true; + }, + /** + Runs the given text through the shell as if it had been typed + in by a user. Fires a working/start event before it starts and + working/end event when it finishes. + */ + exec: function f(sql){ + if(!f._){ + if(!this.runMain()) return; + f._ = sqlite3.wasm.xWrap('fiddle_exec', null, ['string']); + } + if(fiddleModule.isDead){ + stderr("shell module has exit()ed. Cannot run SQL."); + return; + } + wMsg('working','start'); + try { + if(f._running){ + stderr('Cannot run multiple commands concurrently.'); + }else if(sql){ + if(Array.isArray(sql)) sql = sql.join(''); + f._running = true; + f._(sql); + } + }finally{ + delete f._running; + wMsg('working','end'); + } + }, + resetDb: function f(){ + if(!f._) f._ = sqlite3.wasm.xWrap('fiddle_reset_db', null); + stdout("Resetting database."); + f._(); + stdout("Reset",this.dbFilename()); + }, + /* Interrupt can't work: this Worker is tied up working, so won't get the + interrupt event which would be needed to perform the interrupt. */ + interrupt: function f(){ + if(!f._) f._ = sqlite3.wasm.xWrap('fiddle_interrupt', null); + stdout("Requesting interrupt."); + f._(); + } + }; + + self.onmessage = function f(ev){ + ev = ev.data; + if(!f.cache){ + f.cache = { + prevFilename: null + }; + } + //console.debug("worker: onmessage.data",ev); + switch(ev.type){ + case 'shellExec': Sqlite3Shell.exec(ev.data); return; + case 'db-reset': Sqlite3Shell.resetDb(); return; + case 'interrupt': Sqlite3Shell.interrupt(); return; + /** Triggers the export of the current db. Fires an + event in the form: + + {type:'db-export', + data:{ + filename: name of db, + buffer: contents of the db file (Uint8Array), + error: on error, a message string and no buffer property. + } + } + */ + case 'db-export': { + const fn = Sqlite3Shell.dbFilename(); + stdout("Exporting",fn+"."); + const fn2 = fn ? fn.split(/[/\\]/).pop() : null; + try{ + if(!fn2) toss("DB appears to be closed."); + const buffer = sqlite3.capi.sqlite3_js_db_export( + Sqlite3Shell.dbHandle() + ); + wMsg('db-export',{filename: fn2, buffer: buffer.buffer}, [buffer.buffer]); + }catch(e){ + console.error("Export failed:",e); + /* Post a failure message so that UI elements disabled + during the export can be re-enabled. */ + wMsg('db-export',{ + filename: fn, + error: e.message + }); + } + return; + } + case 'open': { + /* Expects: { + buffer: ArrayBuffer | Uint8Array, + filename: the filename for the db. Any dir part is + stripped. + } + */ + const opt = ev.data; + let buffer = opt.buffer; + stderr('open():',fixmeOPFS); + if(buffer instanceof ArrayBuffer){ + buffer = new Uint8Array(buffer); + }else if(!(buffer instanceof Uint8Array)){ + stderr("'open' expects {buffer:Uint8Array} containing an uploaded db."); + return; + } + const fn = ( + opt.filename + ? opt.filename.split(/[/\\]/).pop().replace('"','_') + : ("db-"+((Math.random() * 10000000) | 0)+ + "-"+((Math.random() * 10000000) | 0)+".sqlite3") + ); + try { + /* We cannot delete the existing db file until the new one + is installed, which means that we risk overflowing our + quota (if any) by having both the previous and current + db briefly installed in the virtual filesystem. */ + const fnAbs = '/'+fn; + const oldName = Sqlite3Shell.dbFilename(); + if(oldName && oldName===fnAbs){ + /* We cannot create the replacement file while the current file + is opened, nor does the shell have a .close command, so we + must temporarily switch to another db... */ + Sqlite3Shell.exec('.open :memory:'); + fiddleModule.FS.unlink(fnAbs); + } + fiddleModule.FS.createDataFile("/", fn, buffer, true, true); + Sqlite3Shell.exec('.open "'+fnAbs+'"'); + if(oldName && oldName!==fnAbs){ + try{fiddleModule.fsUnlink(oldName)} + catch(e){/*ignored*/} + } + stdout("Replaced DB with",fn+"."); + }catch(e){ + stderr("Error installing db",fn+":",e.message); + } + return; + } + }; + console.warn("Unknown fiddle-worker message type:",ev); + }; + + /** + emscripten module for use with build mode -sMODULARIZE. + */ + const fiddleModule = { + print: stdout, + printErr: stderr, + /** + Intercepts status updates from the emscripting module init + and fires worker events with a type of 'status' and a + payload of: + + { + text: string | null, // null at end of load process + step: integer // starts at 1, increments 1 per call + } + + We have no way of knowing in advance how many steps will + be processed/posted, so creating a "percentage done" view is + not really practical. One can be approximated by giving it a + current value of message.step and max value of message.step+1, + though. + + When work is finished, a message with a text value of null is + submitted. + + After a message with text==null is posted, the module may later + post messages about fatal problems, e.g. an exit() being + triggered, so it is recommended that UI elements for posting + status messages not be outright removed from the DOM when + text==null, and that they instead be hidden until/unless + text!=null. + */ + setStatus: function f(text){ + if(!f.last) f.last = { step: 0, text: '' }; + else if(text === f.last.text) return; + f.last.text = text; + wMsg('module',{ + type:'status', + data:{step: ++f.last.step, text: text||null} + }); + } + }; + + importScripts('fiddle-module.js'+self.location.search); + /** + initFiddleModule() is installed via fiddle-module.js due to + building with: + + emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initFiddleModule + */ + sqlite3InitModule(fiddleModule).then((_sqlite3)=>{ + sqlite3 = _sqlite3; + const dbVfs = sqlite3.wasm.xWrap('fiddle_db_vfs', "*", ['string']); + fiddleModule.fsUnlink = (fn)=>{ + return sqlite3.wasm.sqlite3_wasm_vfs_unlink(dbVfs(0), fn); + }; + wMsg('fiddle-ready'); + })/*then()*/; +})(); diff --git a/ext/wasm/fiddle/fiddle.js b/ext/wasm/fiddle/fiddle.js new file mode 100644 index 000000000..2a3d1746f --- /dev/null +++ b/ext/wasm/fiddle/fiddle.js @@ -0,0 +1,815 @@ +/* + 2022-05-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 is the main entry point for the sqlite3 fiddle app. It sets up the + various UI bits, loads a Worker for the db connection, and manages the + communication between the UI and worker. +*/ +(function(){ + 'use strict'; + /* Recall that the 'self' symbol, except where locally + overwritten, refers to the global window or worker object. */ + + const storage = (function(NS/*namespace object in which to store this module*/){ + /* Pedantic licensing note: this code originated in the Fossil SCM + source tree, where it has a different license, but the person who + ported it into sqlite is the same one who wrote it for fossil. */ + 'use strict'; + NS = NS||{}; + + /** + This module provides a basic wrapper around localStorage + or sessionStorage or a dummy proxy object if neither + of those are available. + */ + const tryStorage = function f(obj){ + if(!f.key) f.key = 'storage.access.check'; + try{ + obj.setItem(f.key, 'f'); + const x = obj.getItem(f.key); + obj.removeItem(f.key); + if(x!=='f') throw new Error(f.key+" failed") + return obj; + }catch(e){ + return undefined; + } + }; + + /** Internal storage impl for this module. */ + const $storage = + tryStorage(window.localStorage) + || tryStorage(window.sessionStorage) + || tryStorage({ + // A basic dummy xyzStorage stand-in + $$$:{}, + setItem: function(k,v){this.$$$[k]=v}, + getItem: function(k){ + return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined; + }, + removeItem: function(k){delete this.$$$[k]}, + clear: function(){this.$$$={}} + }); + + /** + For the dummy storage we need to differentiate between + $storage and its real property storage for hasOwnProperty() + to work properly... + */ + const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage; + + /** + A prefix which gets internally applied to all storage module + property keys so that localStorage and sessionStorage across the + same browser profile instance do not "leak" across multiple apps + being hosted by the same origin server. Such cross-polination is + still there but, with this key prefix applied, it won't be + immediately visible via the storage API. + + With this in place we can justify using localStorage instead of + sessionStorage. + + One implication of using localStorage and sessionStorage is that + their scope (the same "origin" and client application/profile) + allows multiple apps on the same origin to use the same + storage. Thus /appA/foo could then see changes made via + /appB/foo. The data do not cross user- or browser boundaries, + though, so it "might" arguably be called a + feature. storageKeyPrefix was added so that we can sandbox that + state for each separate app which shares an origin. + + See: https://fossil-scm.org/forum/forumpost/4afc4d34de + + Sidebar: it might seem odd to provide a key prefix and stick all + properties in the topmost level of the storage object. We do that + because adding a layer of object to sandbox each app would mean + (de)serializing that whole tree on every storage property change. + e.g. instead of storageObject.projectName.foo we have + storageObject[storageKeyPrefix+'foo']. That's soley for + efficiency's sake (in terms of battery life and + environment-internal storage-level effort). + */ + const storageKeyPrefix = ( + $storageHolder===$storage/*localStorage or sessionStorage*/ + ? ( + (NS.config ? + (NS.config.projectCode || NS.config.projectName + || NS.config.shortProjectName) + : false) + || window.location.pathname + )+'::' : ( + '' /* transient storage */ + ) + ); + + /** + A proxy for localStorage or sessionStorage or a + page-instance-local proxy, if neither one is availble. + + Which exact storage implementation is uses is unspecified, and + apps must not rely on it. + */ + NS.storage = { + storageKeyPrefix: storageKeyPrefix, + /** Sets the storage key k to value v, implicitly converting + it to a string. */ + set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v), + /** Sets storage key k to JSON.stringify(v). */ + setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)), + /** Returns the value for the given storage key, or + dflt if the key is not found in the storage. */ + get: (k,dflt)=>$storageHolder.hasOwnProperty( + storageKeyPrefix+k + ) ? $storage.getItem(storageKeyPrefix+k) : dflt, + /** Returns true if the given key has a value of "true". If the + key is not found, it returns true if the boolean value of dflt + is "true". (Remember that JS persistent storage values are all + strings.) */ + getBool: function(k,dflt){ + return 'true'===this.get(k,''+(!!dflt)); + }, + /** Returns the JSON.parse()'d value of the given + storage key's value, or dflt is the key is not + found or JSON.parse() fails. */ + getJSON: function f(k,dflt){ + try { + const x = this.get(k,f); + return x===f ? dflt : JSON.parse(x); + } + catch(e){return dflt} + }, + /** Returns true if the storage contains the given key, + else false. */ + contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k), + /** Removes the given key from the storage. Returns this. */ + remove: function(k){ + $storage.removeItem(storageKeyPrefix+k); + return this; + }, + /** Clears ALL keys from the storage. Returns this. */ + clear: function(){ + this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k)); + return this; + }, + /** Returns an array of all keys currently in the storage. */ + keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)), + /** Returns true if this storage is transient (only available + until the page is reloaded), indicating that fileStorage + and sessionStorage are unavailable. */ + isTransient: ()=>$storageHolder!==$storage, + /** Returns a symbolic name for the current storage mechanism. */ + storageImplName: function(){ + if($storage===window.localStorage) return 'localStorage'; + else if($storage===window.sessionStorage) return 'sessionStorage'; + else return 'transient'; + }, + + /** + Returns a brief help text string for the currently-selected + storage type. + */ + storageHelpDescription: function(){ + return { + localStorage: "Browser-local persistent storage with an "+ + "unspecified long-term lifetime (survives closing the browser, "+ + "but maybe not a browser upgrade).", + sessionStorage: "Storage local to this browser tab, "+ + "lost if this tab is closed.", + "transient": "Transient storage local to this invocation of this page." + }[this.storageImplName()]; + } + }; + return NS.storage; + })({})/*storage API setup*/; + + + /** Name of the stored copy of SqliteFiddle.config. */ + const configStorageKey = 'sqlite3-fiddle-config'; + + /** + The SqliteFiddle object is intended to be the primary + app-level object for the main-thread side of the sqlite + fiddle application. It uses a worker thread to load the + sqlite WASM module and communicate with it. + */ + const SF/*local convenience alias*/ + = window.SqliteFiddle/*canonical name*/ = { + /* Config options. */ + config: { + /* If true, SqliteFiddle.echo() will auto-scroll the + output widget to the bottom when it receives output, + else it won't. */ + autoScrollOutput: true, + /* If true, the output area will be cleared before each + command is run, else it will not. */ + autoClearOutput: false, + /* If true, SqliteFiddle.echo() will echo its output to + the console, in addition to its normal output widget. + That slows it down but is useful for testing. */ + echoToConsole: false, + /* If true, display input/output areas side-by-side. */ + sideBySide: true, + /* If true, swap positions of the input/output areas. */ + swapInOut: false + }, + /** + Emits the given text, followed by a line break, to the + output widget. If given more than one argument, they are + join()'d together with a space between each. As a special + case, if passed a single array, that array is used in place + of the arguments array (this is to facilitate receiving + lists of arguments via worker events). + */ + echo: function f(text) { + /* Maintenance reminder: we currently require/expect a textarea + output element. It might be nice to extend this to behave + differently if the output element is a non-textarea element, + in which case it would need to append the given text as a TEXT + node and add a line break. */ + if(!f._){ + f._ = document.getElementById('output'); + f._.value = ''; // clear browser cache + } + if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); + else if(1===arguments.length && Array.isArray(text)) text = text.join(' '); + // These replacements are necessary if you render to raw HTML + //text = text.replace(/&/g, "&"); + //text = text.replace(//g, ">"); + //text = text.replace('\n', '
    ', 'g'); + if(null===text){/*special case: clear output*/ + f._.value = ''; + return; + }else if(this.echo._clearPending){ + delete this.echo._clearPending; + f._.value = ''; + } + if(this.config.echoToConsole) console.log(text); + if(this.jqTerm) this.jqTerm.echo(text); + f._.value += text + "\n"; + if(this.config.autoScrollOutput){ + f._.scrollTop = f._.scrollHeight; + } + }, + _msgMap: {}, + /** Adds a worker message handler for messages of the given + type. */ + addMsgHandler: function f(type,callback){ + if(Array.isArray(type)){ + type.forEach((t)=>this.addMsgHandler(t, callback)); + return this; + } + (this._msgMap.hasOwnProperty(type) + ? this._msgMap[type] + : (this._msgMap[type] = [])).push(callback); + return this; + }, + /** Given a worker message, runs all handlers for msg.type. */ + runMsgHandlers: function(msg){ + const list = (this._msgMap.hasOwnProperty(msg.type) + ? this._msgMap[msg.type] : false); + if(!list){ + console.warn("No handlers found for message type:",msg); + return false; + } + //console.debug("runMsgHandlers",msg); + list.forEach((f)=>f(msg)); + return true; + }, + /** Removes all message handlers for the given message type. */ + clearMsgHandlers: function(type){ + delete this._msgMap[type]; + return this; + }, + /* Posts a message in the form {type, data} to the db worker. Returns this. */ + wMsg: function(type,data,transferables){ + this.worker.postMessage({type, data}, transferables || []); + return this; + }, + /** + Prompts for confirmation and, if accepted, deletes + all content and tables in the (transient) database. + */ + resetDb: function(){ + if(window.confirm("Really destroy all content and tables " + +"in the (transient) db?")){ + this.wMsg('db-reset'); + } + return this; + }, + /** Stores this object's config in the browser's storage. */ + storeConfig: function(){ + storage.setJSON(configStorageKey,this.config); + } + }; + + if(1){ /* Restore SF.config */ + const storedConfig = storage.getJSON(configStorageKey); + if(storedConfig){ + /* Copy all properties to SF.config which are currently in + storedConfig. We don't bother copying any other + properties: those have been removed from the app in the + meantime. */ + Object.keys(SF.config).forEach(function(k){ + if(storedConfig.hasOwnProperty(k)){ + SF.config[k] = storedConfig[k]; + } + }); + } + } + + SF.worker = new Worker('fiddle-worker.js'+self.location.search); + SF.worker.onmessage = (ev)=>SF.runMsgHandlers(ev.data); + SF.addMsgHandler(['stdout', 'stderr'], (ev)=>SF.echo(ev.data)); + + /* querySelectorAll() proxy */ + const EAll = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelectorAll(arguments[arguments.length-1]); + }; + /* querySelector() proxy */ + const E = function(/*[element=document,] cssSelector*/){ + return (arguments.length>1 ? arguments[0] : document) + .querySelector(arguments[arguments.length-1]); + }; + + /** Handles status updates from the Emscripten Module object. */ + SF.addMsgHandler('module', function f(ev){ + ev = ev.data; + if('status'!==ev.type){ + console.warn("Unexpected module-type message:",ev); + return; + } + if(!f.ui){ + f.ui = { + status: E('#module-status'), + progress: E('#module-progress'), + spinner: E('#module-spinner') + }; + } + const msg = ev.data; + if(f.ui.progres){ + progress.value = msg.step; + progress.max = msg.step + 1/*we don't know how many steps to expect*/; + } + if(1==msg.step){ + f.ui.progress.classList.remove('hidden'); + f.ui.spinner.classList.remove('hidden'); + } + if(msg.text){ + f.ui.status.classList.remove('hidden'); + f.ui.status.innerText = msg.text; + }else{ + if(f.ui.progress){ + f.ui.progress.remove(); + f.ui.spinner.remove(); + delete f.ui.progress; + delete f.ui.spinner; + } + f.ui.status.classList.add('hidden'); + /* The module can post messages about fatal problems, + e.g. an exit() being triggered or assertion failure, + after the last "load" message has arrived, so + leave f.ui.status and message listener intact. */ + } + }); + + /** + The 'fiddle-ready' event is fired (with no payload) when the + wasm module has finished loading. Interestingly, that happens + _before_ the final module:status event */ + SF.addMsgHandler('fiddle-ready', function(){ + SF.clearMsgHandlers('fiddle-ready'); + self.onSFLoaded(); + }); + + /** + Performs all app initialization which must wait until after the + worker module is loaded. This function removes itself when it's + called. + */ + self.onSFLoaded = function(){ + delete this.onSFLoaded; + // Unhide all elements which start out hidden + EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); + E('#btn-reset').addEventListener('click',()=>SF.resetDb()); + const taInput = E('#input'); + const btnClearIn = E('#btn-clear'); + btnClearIn.addEventListener('click',function(){ + taInput.value = ''; + },false); + // Ctrl-enter and shift-enter both run the current SQL. + taInput.addEventListener('keydown',function(ev){ + if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){ + ev.preventDefault(); + ev.stopPropagation(); + btnShellExec.click(); + } + }, false); + const taOutput = E('#output'); + const btnClearOut = E('#btn-clear-output'); + btnClearOut.addEventListener('click',function(){ + taOutput.value = ''; + if(SF.jqTerm) SF.jqTerm.clear(); + },false); + const btnShellExec = E('#btn-shell-exec'); + btnShellExec.addEventListener('click',function(ev){ + let sql; + ev.preventDefault(); + if(taInput.selectionStart e.addEventListener('click', cmdClick, false) + ); + + btnInterrupt.addEventListener('click',function(){ + SF.wMsg('interrupt'); + }); + + /** Initiate a download of the db. */ + const btnExport = E('#btn-export'); + const eLoadDb = E('#load-db'); + const btnLoadDb = E('#btn-load-db'); + btnLoadDb.addEventListener('click', ()=>eLoadDb.click()); + /** + Enables (if passed true) or disables all UI elements which + "might," if timed "just right," interfere with an + in-progress db import/export/exec operation. + */ + const enableMutatingElements = function f(enable){ + if(!f._elems){ + f._elems = [ + /* UI elements to disable while import/export are + running. Normally the export is fast enough + that this won't matter, but we really don't + want to be reading (from outside of sqlite) the + db when the user taps btnShellExec. */ + btnShellExec, btnExport, eLoadDb + ]; + } + f._elems.forEach( enable + ? (e)=>e.removeAttribute('disabled') + : (e)=>e.setAttribute('disabled','disabled') ); + }; + btnExport.addEventListener('click',function(){ + enableMutatingElements(false); + SF.wMsg('db-export'); + }); + SF.addMsgHandler('db-export', function(ev){ + enableMutatingElements(true); + ev = ev.data; + if(ev.error){ + SF.echo("Export failed:",ev.error); + return; + } + const blob = new Blob([ev.buffer], + {type:"application/x-sqlite3"}); + const a = document.createElement('a'); + document.body.appendChild(a); + a.href = window.URL.createObjectURL(blob); + a.download = ev.filename; + a.addEventListener('click',function(){ + setTimeout(function(){ + SF.echo("Exported (possibly auto-downloaded):",ev.filename); + window.URL.revokeObjectURL(a.href); + a.remove(); + },500); + }); + a.click(); + }); + /** + Handle load/import of an external db file. + */ + eLoadDb.addEventListener('change',function(){ + const f = this.files[0]; + const r = new FileReader(); + const status = {loaded: 0, total: 0}; + enableMutatingElements(false); + r.addEventListener('loadstart', function(){ + SF.echo("Loading",f.name,"..."); + }); + r.addEventListener('progress', function(ev){ + SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes."); + }); + const that = this; + r.addEventListener('load', function(){ + enableMutatingElements(true); + SF.echo("Loaded",f.name+". Opening db..."); + SF.wMsg('open',{ + filename: f.name, + buffer: this.result + }, [this.result]); + }); + r.addEventListener('error',function(){ + enableMutatingElements(true); + SF.echo("Loading",f.name,"failed for unknown reasons."); + }); + r.addEventListener('abort',function(){ + enableMutatingElements(true); + SF.echo("Cancelled loading of",f.name+"."); + }); + r.readAsArrayBuffer(f); + }); + + EAll('fieldset.collapsible').forEach(function(fs){ + const btnToggle = E(fs,'legend > .fieldset-toggle'), + content = EAll(fs,':scope > div'); + btnToggle.addEventListener('click', function(){ + fs.classList.toggle('collapsed'); + content.forEach((d)=>d.classList.toggle('hidden')); + }, false); + }); + + /** + Given a DOM element, this routine measures its "effective + height", which is the bounding top/bottom range of this element + and all of its children, recursively. For some DOM structure + cases, a parent may have a reported height of 0 even though + children have non-0 sizes. + + Returns 0 if !e or if the element really has no height. + */ + const effectiveHeight = function f(e){ + if(!e) return 0; + if(!f.measure){ + f.measure = function callee(e, depth){ + if(!e) return; + const m = e.getBoundingClientRect(); + if(0===depth){ + callee.top = m.top; + callee.bottom = m.bottom; + }else{ + callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; + callee.bottom = Math.max(callee.bottom, m.bottom); + } + Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); + if(0===depth){ + //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); + f.extra += callee.bottom - callee.top; + } + return f.extra; + }; + } + f.extra = 0; + f.measure(e,0); + return f.extra; + }; + + /** + Returns a function, that, as long as it continues to be invoked, + will not be triggered. The function will be called after it stops + being called for N milliseconds. If `immediate` is passed, call + the callback immediately and hinder future invocations until at + least the given time has passed. + + If passed only 1 argument, or passed a falsy 2nd argument, + the default wait time set in this function's $defaultDelay + property is used. + + Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function + */ + const debounce = function f(func, wait, immediate) { + var timeout; + if(!wait) wait = f.$defaultDelay; + return function() { + const context = this, args = Array.prototype.slice.call(arguments); + const later = function() { + timeout = undefined; + if(!immediate) func.apply(context, args); + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if(callNow) func.apply(context, args); + }; + }; + debounce.$defaultDelay = 500 /*arbitrary*/; + + const ForceResizeKludge = (function(){ + /* Workaround for Safari mayhem regarding use of vh CSS + units.... We cannot use vh units to set the main view + size because Safari chokes on that, so we calculate + that height here. Larger than ~95% is too big for + Firefox on Android, causing the input area to move + off-screen. */ + const appViews = EAll('.app-view'); + const elemsToCount = [ + /* Elements which we need to always count in the + visible body size. */ + E('body > header'), + E('body > footer') + ]; + const resized = function f(){ + if(f.$disabled) return; + const wh = window.innerHeight; + var ht; + var extra = 0; + elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false); + ht = wh - extra; + appViews.forEach(function(e){ + e.style.height = + e.style.maxHeight = [ + "calc(", (ht>=100 ? ht : 100), "px", + " - 2em"/*fudge value*/,")" + /* ^^^^ hypothetically not needed, but both + Chrome/FF on Linux will force scrollbars on the + body if this value is too small. */ + ].join(''); + }); + }; + resized.$disabled = true/*gets deleted when setup is finished*/; + window.addEventListener('resize', debounce(resized, 250), false); + return resized; + })(); + + /** Set up a selection list of examples */ + (function(){ + const xElem = E('#select-examples'); + const examples = [ + {name: "Help", sql: [ + "-- ================================================\n", + "-- Use ctrl-enter or shift-enter to execute sqlite3\n", + "-- shell commands and SQL.\n", + "-- If a subset of the text is currently selected,\n", + "-- only that part is executed.\n", + "-- ================================================\n", + ".help\n" + ]}, + //{name: "Timer on", sql: ".timer on"}, + // ^^^ re-enable if emscripten re-enables getrusage() + {name: "Setup table T", sql:[ + ".nullvalue NULL\n", + "CREATE TABLE t(a,b);\n", + "INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);\n", + "SELECT * FROM t;\n" + ]}, + {name: "Table list", sql: ".tables"}, + {name: "Box Mode", sql: ".mode box"}, + {name: "JSON Mode", sql: ".mode json"}, + {name: "Mandlebrot", sql:[ + "WITH RECURSIVE", + " xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),\n", + " yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),\n", + " m(iter, cx, cy, x, y) AS (\n", + " SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis\n", + " UNION ALL\n", + " SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m \n", + " WHERE (x*x + y*y) < 4.0 AND iter<28\n", + " ),\n", + " m2(iter, cx, cy) AS (\n", + " SELECT max(iter), cx, cy FROM m GROUP BY cx, cy\n", + " ),\n", + " a(t) AS (\n", + " SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') \n", + " FROM m2 GROUP BY cy\n", + " )\n", + "SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n", + ]} + ]; + const newOpt = function(lbl,val){ + const o = document.createElement('option'); + if(Array.isArray(val)) val = val.join(''); + o.value = val; + if(!val) o.setAttribute('disabled',true); + o.appendChild(document.createTextNode(lbl)); + xElem.appendChild(o); + }; + newOpt("Examples (replaces input!)"); + examples.forEach((o)=>newOpt(o.name, o.sql)); + //xElem.setAttribute('disabled',true); + xElem.selectedIndex = 0; + xElem.addEventListener('change', function(){ + taInput.value = '-- ' + + this.selectedOptions[0].innerText + + '\n' + this.value; + SF.dbExec(this.value); + }); + })()/* example queries */; + + //SF.echo(null/*clear any output generated by the init process*/); + if(window.jQuery && window.jQuery.terminal){ + /* Set up the terminal-style view... */ + const eTerm = window.jQuery('#view-terminal').empty(); + SF.jqTerm = eTerm.terminal(SF.dbExec.bind(SF),{ + prompt: 'sqlite> ', + greetings: false /* note that the docs incorrectly call this 'greeting' */ + }); + /* Set up a button to toggle the views... */ + const head = E('header#titlebar'); + const btnToggleView = document.createElement('button'); + btnToggleView.appendChild(document.createTextNode("Toggle View")); + head.appendChild(btnToggleView); + btnToggleView.addEventListener('click',function f(){ + EAll('.app-view').forEach(e=>e.classList.toggle('hidden')); + if(document.body.classList.toggle('terminal-mode')){ + ForceResizeKludge(); + } + }, false); + btnToggleView.click()/*default to terminal view*/; + } + SF.echo('This experimental app is provided in the hope that it', + 'may prove interesting or useful but is not an officially', + 'supported deliverable of the sqlite project. It is subject to', + 'any number of changes or outright removal at any time.\n'); + const urlParams = new URL(self.location.href).searchParams; + SF.dbExec(urlParams.get('sql') || null); + delete ForceResizeKludge.$disabled; + ForceResizeKludge(); + }/*onSFLoaded()*/; +})(); diff --git a/ext/fiddle/fiddle.html b/ext/wasm/fiddle/index.html similarity index 99% rename from ext/fiddle/fiddle.html rename to ext/wasm/fiddle/index.html index 3570b283e..272f1aca7 100644 --- a/ext/fiddle/fiddle.html +++ b/ext/wasm/fiddle/index.html @@ -273,11 +273,6 @@ - diff --git a/ext/wasm/index-dist.html b/ext/wasm/index-dist.html new file mode 100644 index 000000000..6b038c82a --- /dev/null +++ b/ext/wasm/index-dist.html @@ -0,0 +1,90 @@ + + + + + + + sqlite3 WASM Demo Page Index + + + +
    sqlite3 WASM demo pages
    +
    +
    Below is the list of demo pages for the sqlite3 WASM + builds. The intent is that this page be run + using the functional equivalent of:
    +
    althttpd -enable-sab -page index.html
    +
    and the individual pages be started in their own tab. + Warnings and Caveats: +
      +
    • Some of these pages require that the web server emit the + so-called + COOP + and + COEP + headers. althttpd requires the + -enable-sab flag for that. +
    • +
    +
    +
    The tests and demos... +
      +
    • Core-most tests +
        +
      • tester1: Core unit and + regression tests for the various APIs and surrounding + utility code.
      • +
      • tester1-worker: same thing + but running in a Worker.
      • +
      +
    • +
    • Higher-level apps and demos... +
        +
      • demo-123 provides a + no-nonsense example of adding sqlite3 support to a web + page in the UI thread.
      • +
      • demo-123-worker is + the same as demo-123 but loads and runs + sqlite3 from a Worker thread.
      • +
      • demo-jsstorage: very basic + demo of using the key-value VFS for storing a persistent db + in JS localStorage or sessionStorage.
      • +
      • demo-worker1: + Worker-based wrapper of the OO API #1. Its Promise-based + wrapper is significantly easier to use, however.
      • +
      • demo-worker1-promiser: + a demo of the Promise-based wrapper of the Worker1 API.
      • +
      +
    • +
    +
    + + + + diff --git a/ext/wasm/index.html b/ext/wasm/index.html new file mode 100644 index 000000000..044cd1360 --- /dev/null +++ b/ext/wasm/index.html @@ -0,0 +1,115 @@ + + + + + + + + sqlite3 WASM Testing Page Index + + +
    sqlite3 WASM test pages
    +
    +
    Below is the list of test pages for the sqlite3 WASM + builds. All of them require that this directory have been + "make"d first. The intent is that this page be run + using:
    +
    althttpd -enable-sab -page index.html
    +
    and the individual tests be started in their own tab. + Warnings and Caveats: +
      +
    • Some of these pages require that + the web server emit the so-called + COOP + and + COEP + headers. althttpd requires the + -enable-sab flag for that. +
    • +
    • Any OPFS-related pages require very recent version of + Chrome or Chromium (v102 at least, possibly newer). OPFS + support in the other major browsers is pending. Development + and testing is currently done against a dev-channel release + of Chrome (v107 as of 2022-09-26). +
    • +
    • Whether or not WASMFS/OPFS support is enabled on any given + page may depend on build-time options which are off by + default. +
    • +
    +
    +
    The tests and demos... +
      +
    • Core-most tests +
        +
      • tester1: Core unit and + regression tests for the various APIs and surrounding + utility code.
      • +
      • tester1-worker: same thing + but running in a Worker.
      • +
      +
    • +
    • High-level apps and demos... +
        +
      • fiddle is an HTML front-end + to a wasm build of the sqlite3 shell.
      • +
      • demo-123 provides a + no-nonsense example of adding sqlite3 support to a web + page in the UI thread.
      • +
      • demo-123-worker is + the same as demo-123 but loads and runs + sqlite3 from a Worker thread.
      • +
      • demo-jsstorage: very basic + demo of using the key-value VFS for storing a persistent db + in JS localStorage or sessionStorage.
      • +
      • demo-worker1: + Worker-based wrapper of the OO API #1. Its Promise-based + wrapper is significantly easier to use, however.
      • +
      • demo-worker1-promiser: + a demo of the Promise-based wrapper of the Worker1 API.
      • +
      +
    • +
    • speedtest1 ports (sqlite3's primary benchmarking tool)... + +
    • +
    • The obligatory "misc." category... + +
    • + +
    +
    + + + + diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js new file mode 100644 index 000000000..dee7258c5 --- /dev/null +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -0,0 +1,746 @@ +/** + 2022-06-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. + + *********************************************************************** + + The Jaccwabyt API is documented in detail in an external file. + + Project home: https://fossil.wanderinghorse.net/r/jaccwabyt + +*/ +'use strict'; +self.Jaccwabyt = function StructBinderFactory(config){ +/* ^^^^ it is recommended that clients move that object into wherever + they'd like to have it and delete the self-held copy ("self" being + the global window or worker object). This API does not require the + global reference - it is simply installed as a convenience for + connecting these bits to other co-developed code before it gets + removed from the global namespace. +*/ + + /** Throws a new Error, the message of which is the concatenation + all args with a space between each. */ + const toss = (...args)=>{throw new Error(args.join(' '))}; + + /** + Implementing function bindings revealed significant + shortcomings in Emscripten's addFunction()/removeFunction() + interfaces: + + https://github.com/emscripten-core/emscripten/issues/17323 + + Until those are resolved, or a suitable replacement can be + implemented, our function-binding API will be more limited + and/or clumsier to use than initially hoped. + */ + if(!(config.heap instanceof WebAssembly.Memory) + && !(config.heap instanceof Function)){ + toss("config.heap must be WebAssembly.Memory instance or a function."); + } + ['alloc','dealloc'].forEach(function(k){ + (config[k] instanceof Function) || + toss("Config option '"+k+"' must be a function."); + }); + const SBF = StructBinderFactory; + const heap = (config.heap instanceof Function) + ? config.heap : (()=>new Uint8Array(config.heap.buffer)), + alloc = config.alloc, + dealloc = config.dealloc, + log = config.log || console.log.bind(console), + memberPrefix = (config.memberPrefix || ""), + memberSuffix = (config.memberSuffix || ""), + bigIntEnabled = (undefined===config.bigIntEnabled + ? !!self['BigInt64Array'] : !!config.bigIntEnabled), + BigInt = self['BigInt'], + BigInt64Array = self['BigInt64Array'], + /* Undocumented (on purpose) config options: */ + functionTable = config.functionTable/*EXPERIMENTAL, undocumented*/, + ptrSizeof = config.ptrSizeof || 4, + ptrIR = config.ptrIR || 'i32' + ; + + if(!SBF.debugFlags){ + SBF.__makeDebugFlags = function(deriveFrom=null){ + /* This is disgustingly overengineered. :/ */ + if(deriveFrom && deriveFrom.__flags) deriveFrom = deriveFrom.__flags; + const f = function f(flags){ + if(0===arguments.length){ + return f.__flags; + } + if(flags<0){ + delete f.__flags.getter; delete f.__flags.setter; + delete f.__flags.alloc; delete f.__flags.dealloc; + }else{ + f.__flags.getter = 0!==(0x01 & flags); + f.__flags.setter = 0!==(0x02 & flags); + f.__flags.alloc = 0!==(0x04 & flags); + f.__flags.dealloc = 0!==(0x08 & flags); + } + return f._flags; + }; + Object.defineProperty(f,'__flags', { + iterable: false, writable: false, + value: Object.create(deriveFrom) + }); + if(!deriveFrom) f(0); + return f; + }; + SBF.debugFlags = SBF.__makeDebugFlags(); + }/*static init*/ + + const isLittleEndian = (function() { + const buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, 256, true /* littleEndian */); + // Int16Array uses the platform's endianness. + return new Int16Array(buffer)[0] === 256; + })(); + /** + Some terms used in the internal docs: + + StructType: a struct-wrapping class generated by this + framework. + DEF: struct description object. + SIG: struct member signature string. + */ + + /** True if SIG s looks like a function signature, else + false. */ + const isFuncSig = (s)=>'('===s[1]; + /** True if SIG s is-a pointer signature. */ + const isPtrSig = (s)=>'p'===s || 'P'===s; + const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/; + const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0]; + /** Returns the WASM IR form of the Emscripten-conventional letter + at SIG s[0]. Throws for an unknown SIG. */ + const sigIR = function(s){ + switch(sigLetter(s)){ + case 'i': return 'i32'; + case 'p': case 'P': case 's': return ptrIR; + case 'j': return 'i64'; + case 'f': return 'float'; + case 'd': return 'double'; + } + toss("Unhandled signature IR:",s); + }; + /** Returns the sizeof value for the given SIG. Throws for an + unknown SIG. */ + const sigSizeof = function(s){ + switch(sigLetter(s)){ + case 'i': return 4; + case 'p': case 'P': case 's': return ptrSizeof; + case 'j': return 8; + case 'f': return 4 /* C-side floats, not JS-side */; + case 'd': return 8; + } + toss("Unhandled signature sizeof:",s); + }; + const affirmBigIntArray = BigInt64Array + ? ()=>true : ()=>toss('BigInt64Array is not available.'); + /** Returns the (signed) TypedArray associated with the type + described by the given SIG. Throws for an unknown SIG. */ + /********** + const sigTypedArray = function(s){ + switch(sigIR(s)) { + case 'i32': return Int32Array; + case 'i64': return affirmBigIntArray() && BigInt64Array; + case 'float': return Float32Array; + case 'double': return Float64Array; + } + toss("Unhandled signature TypedArray:",s); + }; + **************/ + /** Returns the name of a DataView getter method corresponding + to the given SIG. */ + const sigDVGetter = function(s){ + switch(sigLetter(s)) { + case 'p': case 'P': case 's': { + switch(ptrSizeof){ + case 4: return 'getInt32'; + case 8: return affirmBigIntArray() && 'getBigInt64'; + } + break; + } + case 'i': return 'getInt32'; + case 'j': return affirmBigIntArray() && 'getBigInt64'; + case 'f': return 'getFloat32'; + case 'd': return 'getFloat64'; + } + toss("Unhandled DataView getter for signature:",s); + }; + /** Returns the name of a DataView setter method corresponding + to the given SIG. */ + const sigDVSetter = function(s){ + switch(sigLetter(s)){ + case 'p': case 'P': case 's': { + switch(ptrSizeof){ + case 4: return 'setInt32'; + case 8: return affirmBigIntArray() && 'setBigInt64'; + } + break; + } + case 'i': return 'setInt32'; + case 'j': return affirmBigIntArray() && 'setBigInt64'; + case 'f': return 'setFloat32'; + case 'd': return 'setFloat64'; + } + toss("Unhandled DataView setter for signature:",s); + }; + /** + Returns either Number of BigInt, depending on the given + SIG. This constructor is used in property setters to coerce + the being-set value to the correct size. + */ + const sigDVSetWrapper = function(s){ + switch(sigLetter(s)) { + case 'i': case 'f': case 'd': return Number; + case 'j': return affirmBigIntArray() && BigInt; + case 'p': case 'P': case 's': + switch(ptrSizeof){ + case 4: return Number; + case 8: return affirmBigIntArray() && BigInt; + } + break; + } + toss("Unhandled DataView set wrapper for signature:",s); + }; + + const sPropName = (s,k)=>s+'::'+k; + + const __propThrowOnSet = function(structName,propName){ + return ()=>toss(sPropName(structName,propName),"is read-only."); + }; + + /** + When C code passes a pointer of a bound struct to back into + a JS function via a function pointer struct member, it + arrives in JS as a number (pointer). + StructType.instanceForPointer(ptr) can be used to get the + instance associated with that pointer, and __ptrBacklinks + holds that mapping. WeakMap keys must be objects, so we + cannot use a weak map to map pointers to instances. We use + the StructType constructor as the WeakMap key, mapped to a + plain, prototype-less Object which maps the pointers to + struct instances. That arrangement gives us a + per-StructType type-safe way to resolve pointers. + */ + const __ptrBacklinks = new WeakMap(); + /** + Similar to __ptrBacklinks but is scoped at the StructBinder + level and holds pointer-to-object mappings for all struct + instances created by any struct from any StructFactory + which this specific StructBinder has created. The intention + of this is to help implement more transparent handling of + pointer-type property resolution. + */ + const __ptrBacklinksGlobal = Object.create(null); + + /** + In order to completely hide StructBinder-bound struct + pointers from JS code, we store them in a scope-local + WeakMap which maps the struct-bound objects to their WASM + pointers. The pointers are accessible via + boundObject.pointer, which is gated behind an accessor + function, but are not exposed anywhere else in the + object. The main intention of that is to make it impossible + for stale copies to be made. + */ + const __instancePointerMap = new WeakMap(); + + /** Property name for the pointer-is-external marker. */ + const xPtrPropName = '(pointer-is-external)'; + + /** Frees the obj.pointer memory and clears the pointer + property. */ + const __freeStruct = function(ctor, obj, m){ + if(!m) m = __instancePointerMap.get(obj); + if(m) { + if(obj.ondispose instanceof Function){ + try{obj.ondispose()} + catch(e){ + /*do not rethrow: destructors must not throw*/ + console.warn("ondispose() for",ctor.structName,'@', + m,'threw. NOT propagating it.',e); + } + }else if(Array.isArray(obj.ondispose)){ + obj.ondispose.forEach(function(x){ + try{ + if(x instanceof Function) x.call(obj); + else if('number' === typeof x) dealloc(x); + // else ignore. Strings are permitted to annotate entries + // to assist in debugging. + }catch(e){ + console.warn("ondispose() for",ctor.structName,'@', + m,'threw. NOT propagating it.',e); + } + }); + } + delete obj.ondispose; + delete __ptrBacklinks.get(ctor)[m]; + delete __ptrBacklinksGlobal[m]; + __instancePointerMap.delete(obj); + if(ctor.debugFlags.__flags.dealloc){ + log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), + ctor.structName,"instance:", + ctor.structInfo.sizeof,"bytes @"+m); + } + if(!obj[xPtrPropName]) dealloc(m); + } + }; + + /** Returns a skeleton for a read-only property accessor wrapping + value v. */ + const rop = (v)=>{return {configurable: false, writable: false, + iterable: false, value: v}}; + + /** Allocates obj's memory buffer based on the size defined in + DEF.sizeof. */ + const __allocStruct = function(ctor, obj, m){ + let fill = !m; + if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); + else{ + m = alloc(ctor.structInfo.sizeof); + if(!m) toss("Allocation of",ctor.structName,"structure failed."); + } + try { + if(ctor.debugFlags.__flags.alloc){ + log("debug.alloc:",(fill?"":"EXTERNAL"), + ctor.structName,"instance:", + ctor.structInfo.sizeof,"bytes @"+m); + } + if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof); + __instancePointerMap.set(obj, m); + __ptrBacklinks.get(ctor)[m] = obj; + __ptrBacklinksGlobal[m] = obj; + }catch(e){ + __freeStruct(ctor, obj, m); + throw e; + } + }; + /** Gets installed as the memoryDump() method of all structs. */ + const __memoryDump = function(){ + const p = this.pointer; + return p + ? new Uint8Array(heap().slice(p, p+this.structInfo.sizeof)) + : null; + }; + + const __memberKey = (k)=>memberPrefix + k + memberSuffix; + const __memberKeyProp = rop(__memberKey); + + /** + Looks up a struct member in structInfo.members. Throws if found + if tossIfNotFound is true, else returns undefined if not + found. The given name may be either the name of the + structInfo.members key (faster) or the key as modified by the + memberPrefix/memberSuffix settings. + */ + const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){ + let m = structInfo.members[memberName]; + if(!m && (memberPrefix || memberSuffix)){ + // Check for a match on members[X].key + for(const v of Object.values(structInfo.members)){ + if(v.key===memberName){ m = v; break; } + } + if(!m && tossIfNotFound){ + toss(sPropName(structInfo.name,memberName),'is not a mapped struct member.'); + } + } + return m; + }; + + /** + Uses __lookupMember(obj.structInfo,memberName) to find a member, + throwing if not found. Returns its signature, either in this + framework's native format or in Emscripten format. + */ + const __memberSignature = function f(obj,memberName,emscriptenFormat=false){ + if(!f._) f._ = (x)=>x.replace(/[^vipPsjrd]/g,"").replace(/[pPs]/g,'i'); + const m = __lookupMember(obj.structInfo, memberName, true); + return emscriptenFormat ? f._(m.signature) : m.signature; + }; + + /** + Returns the instanceForPointer() impl for the given + StructType constructor. + */ + const __instanceBacklinkFactory = function(ctor){ + const b = Object.create(null); + __ptrBacklinks.set(ctor, b); + return (ptr)=>b[ptr]; + }; + + const __ptrPropDescriptor = { + configurable: false, enumerable: false, + get: function(){return __instancePointerMap.get(this)}, + set: ()=>toss("Cannot assign the 'pointer' property of a struct.") + // Reminder: leaving `set` undefined makes assignments + // to the property _silently_ do nothing. Current unit tests + // rely on it throwing, though. + }; + + /** Impl of X.memberKeys() for StructType and struct ctors. */ + const __structMemberKeys = rop(function(){ + const a = []; + Object.keys(this.structInfo.members).forEach((k)=>a.push(this.memberKey(k))); + return a; + }); + + const __utf8Decoder = new TextDecoder('utf-8'); + const __utf8Encoder = new TextEncoder(); + /** Internal helper to use in operations which need to distinguish + between SharedArrayBuffer heap memory and non-shared heap. */ + const __SAB = ('undefined'===typeof SharedArrayBuffer) + ? function(){} : SharedArrayBuffer; + const __utf8Decode = function(arrayBuffer, begin, end){ + return __utf8Decoder.decode( + (arrayBuffer.buffer instanceof __SAB) + ? arrayBuffer.slice(begin, end) + : arrayBuffer.subarray(begin, end) + ); + }; + /** + Uses __lookupMember() to find the given obj.structInfo key. + Returns that member if it is a string, else returns false. If the + member is not found, throws if tossIfNotFound is true, else + returns false. + */ + const __memberIsString = function(obj,memberName, tossIfNotFound=false){ + const m = __lookupMember(obj.structInfo, memberName, tossIfNotFound); + return (m && 1===m.signature.length && 's'===m.signature[0]) ? m : false; + }; + + /** + Given a member description object, throws if member.signature is + not valid for assigning to or interpretation as a C-style string. + It optimistically assumes that any signature of (i,p,s) is + C-string compatible. + */ + const __affirmCStringSignature = function(member){ + if('s'===member.signature) return; + toss("Invalid member type signature for C-string value:", + JSON.stringify(member)); + }; + + /** + Looks up the given member in obj.structInfo. If it has a + signature of 's' then it is assumed to be a C-style UTF-8 string + and a decoded copy of the string at its address is returned. If + the signature is of any other type, it throws. If an s-type + member's address is 0, `null` is returned. + */ + const __memberToJsString = function f(obj,memberName){ + const m = __lookupMember(obj.structInfo, memberName, true); + __affirmCStringSignature(m); + const addr = obj[m.key]; + //log("addr =",addr,memberName,"m =",m); + if(!addr) return null; + let pos = addr; + const mem = heap(); + for( ; mem[pos]!==0; ++pos ) { + //log("mem[",pos,"]",mem[pos]); + }; + //log("addr =",addr,"pos =",pos); + return (addr===pos) ? "" : __utf8Decode(mem, addr, pos); + }; + + /** + Adds value v to obj.ondispose, creating ondispose, + or converting it to an array, if needed. + */ + const __addOnDispose = function(obj, v){ + if(obj.ondispose){ + if(obj.ondispose instanceof Function){ + obj.ondispose = [obj.ondispose]; + }/*else assume it's an array*/ + }else{ + obj.ondispose = []; + } + obj.ondispose.push(v); + }; + + /** + Allocates a new UTF-8-encoded, NUL-terminated copy of the given + JS string and returns its address relative to heap(). If + allocation returns 0 this function throws. Ownership of the + memory is transfered to the caller, who must eventually pass it + to the configured dealloc() function. + */ + const __allocCString = function(str){ + const u = __utf8Encoder.encode(str); + const mem = alloc(u.length+1); + if(!mem) toss("Allocation error while duplicating string:",str); + const h = heap(); + let i = 0; + for( ; i < u.length; ++i ) h[mem + i] = u[i]; + h[mem + u.length] = 0; + //log("allocCString @",mem," =",u); + return mem; + }; + + /** + Sets the given struct member of obj to a dynamically-allocated, + UTF-8-encoded, NUL-terminated copy of str. It is up to the caller + to free any prior memory, if appropriate. The newly-allocated + string is added to obj.ondispose so will be freed when the object + is disposed. + */ + const __setMemberCString = function(obj, memberName, str){ + const m = __lookupMember(obj.structInfo, memberName, true); + __affirmCStringSignature(m); + /* Potential TODO: if obj.ondispose contains obj[m.key] then + dealloc that value and clear that ondispose entry */ + const mem = __allocCString(str); + obj[m.key] = mem; + __addOnDispose(obj, mem); + return obj; + }; + + /** + Prototype for all StructFactory instances (the constructors + returned from StructBinder). + */ + const StructType = function ctor(structName, structInfo){ + if(arguments[2]!==rop){ + toss("Do not call the StructType constructor", + "from client-level code."); + } + Object.defineProperties(this,{ + //isA: rop((v)=>v instanceof ctor), + structName: rop(structName), + structInfo: rop(structInfo) + }); + }; + + /** + Properties inherited by struct-type-specific StructType instances + and (indirectly) concrete struct-type instances. + */ + StructType.prototype = Object.create(null, { + dispose: rop(function(){__freeStruct(this.constructor, this)}), + lookupMember: rop(function(memberName, tossIfNotFound=true){ + return __lookupMember(this.structInfo, memberName, tossIfNotFound); + }), + memberToJsString: rop(function(memberName){ + return __memberToJsString(this, memberName); + }), + memberIsString: rop(function(memberName, tossIfNotFound=true){ + return __memberIsString(this, memberName, tossIfNotFound); + }), + memberKey: __memberKeyProp, + memberKeys: __structMemberKeys, + memberSignature: rop(function(memberName, emscriptenFormat=false){ + return __memberSignature(this, memberName, emscriptenFormat); + }), + memoryDump: rop(__memoryDump), + pointer: __ptrPropDescriptor, + setMemberCString: rop(function(memberName, str){ + return __setMemberCString(this, memberName, str); + }) + }); + + /** + "Static" properties for StructType. + */ + Object.defineProperties(StructType, { + allocCString: rop(__allocCString), + instanceForPointer: rop((ptr)=>__ptrBacklinksGlobal[ptr]), + isA: rop((v)=>v instanceof StructType), + hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), + memberKey: __memberKeyProp + }); + + const isNumericValue = (v)=>Number.isFinite(v) || (v instanceof (BigInt || Number)); + + /** + Pass this a StructBinder-generated prototype, and the struct + member description object. It will define property accessors for + proto[memberKey] which read from/write to memory in + this.pointer. It modifies descr to make certain downstream + operations much simpler. + */ + const makeMemberWrapper = function f(ctor,name, descr){ + if(!f._){ + /*cache all available getters/setters/set-wrappers for + direct reuse in each accessor function. */ + f._ = {getters: {}, setters: {}, sw:{}}; + const a = ['i','p','P','s','f','d','v()']; + if(bigIntEnabled) a.push('j'); + a.forEach(function(v){ + //const ir = sigIR(v); + f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */; + f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */; + f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values + for conversion */; + }); + const rxSig1 = /^[ipPsjfd]$/, + rxSig2 = /^[vipPsjfd]\([ipPsjfd]*\)$/; + f.sigCheck = function(obj, name, key,sig){ + if(Object.prototype.hasOwnProperty.call(obj, key)){ + toss(obj.structName,'already has a property named',key+'.'); + } + rxSig1.test(sig) || rxSig2.test(sig) + || toss("Malformed signature for", + sPropName(obj.structName,name)+":",sig); + }; + } + const key = ctor.memberKey(name); + f.sigCheck(ctor.prototype, name, key, descr.signature); + descr.key = key; + descr.name = name; + const sizeOf = sigSizeof(descr.signature); + const sigGlyph = sigLetter(descr.signature); + const xPropName = sPropName(ctor.prototype.structName,key); + const dbg = ctor.prototype.debugFlags.__flags; + /* + TODO?: set prototype of descr to an object which can set/fetch + its prefered representation, e.g. conversion to string or mapped + function. Advantage: we can avoid doing that via if/else if/else + in the get/set methods. + */ + const prop = Object.create(null); + prop.configurable = false; + prop.enumerable = false; + prop.get = function(){ + if(dbg.getter){ + log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), + xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf); + } + let rc = ( + new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) + )[f._.getters[sigGlyph]](0, isLittleEndian); + if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); + if(rc && isAutoPtrSig(descr.signature)){ + rc = StructType.instanceForPointer(rc) || rc; + if(dbg.getter) log("debug.getter:",xPropName,"resolved =",rc); + } + return rc; + }; + if(descr.readOnly){ + prop.set = __propThrowOnSet(ctor.prototype.structName,key); + }else{ + prop.set = function(v){ + if(dbg.setter){ + log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), + xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf, v); + } + if(!this.pointer){ + toss("Cannot set struct property on disposed instance."); + } + if(null===v) v = 0; + else while(!isNumericValue(v)){ + if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){ + // It's a struct instance: let's store its pointer value! + v = v.pointer || 0; + if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v); + break; + } + toss("Invalid value for pointer-type",xPropName+'.'); + } + ( + new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) + )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); + }; + } + Object.defineProperty(ctor.prototype, key, prop); + }/*makeMemberWrapper*/; + + /** + The main factory function which will be returned to the + caller. + */ + const StructBinder = function StructBinder(structName, structInfo){ + if(1===arguments.length){ + structInfo = structName; + structName = structInfo.name; + }else if(!structInfo.name){ + structInfo.name = structName; + } + if(!structName) toss("Struct name is required."); + let lastMember = false; + Object.keys(structInfo.members).forEach((k)=>{ + const m = structInfo.members[k]; + if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); + else if(0!==(m.sizeof%4)){ + toss(structName,"member",k,"sizeof is not aligned."); + } + else if(0!==(m.offset%4)){ + toss(structName,"member",k,"offset is not aligned."); + } + if(!lastMember || lastMember.offset < m.offset) lastMember = m; + }); + if(!lastMember) toss("No member property descriptions found."); + else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){ + toss("Invalid struct config:",structName, + "max member offset ("+lastMember.offset+") ", + "extends past end of struct (sizeof="+structInfo.sizeof+")."); + } + const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags)); + /** Constructor for the StructCtor. */ + const StructCtor = function StructCtor(externalMemory){ + if(!(this instanceof StructCtor)){ + toss("The",structName,"constructor may only be called via 'new'."); + }else if(arguments.length){ + if(externalMemory!==(externalMemory|0) || externalMemory<=0){ + toss("Invalid pointer value for",structName,"constructor."); + } + __allocStruct(StructCtor, this, externalMemory); + }else{ + __allocStruct(StructCtor, this); + } + }; + Object.defineProperties(StructCtor,{ + debugFlags: debugFlags, + disposeAll: rop(function(){ + const map = __ptrBacklinks.get(StructCtor); + Object.keys(map).forEach(function(ptr){ + const b = map[ptr]; + if(b) __freeStruct(StructCtor, b, ptr); + }); + __ptrBacklinks.set(StructCtor, Object.create(null)); + return StructCtor; + }), + instanceForPointer: rop(__instanceBacklinkFactory(StructCtor)), + isA: rop((v)=>v instanceof StructCtor), + memberKey: __memberKeyProp, + memberKeys: __structMemberKeys, + resolveToInstance: rop(function(v, throwIfNot=false){ + if(!(v instanceof StructCtor)){ + v = Number.isSafeInteger(v) + ? StructCtor.instanceForPointer(v) : undefined; + } + if(!v && throwIfNot) toss("Value is-not-a",StructCtor.structName); + return v; + }), + methodInfoForKey: rop(function(mKey){ + }), + structInfo: rop(structInfo), + structName: rop(structName) + }); + StructCtor.prototype = new StructType(structName, structInfo, rop); + Object.defineProperties(StructCtor.prototype,{ + debugFlags: debugFlags, + constructor: rop(StructCtor) + /*if we assign StructCtor.prototype and don't do + this then StructCtor!==instance.constructor!*/ + }); + Object.keys(structInfo.members).forEach( + (name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name]) + ); + return StructCtor; + }; + StructBinder.instanceForPointer = StructType.instanceForPointer; + StructBinder.StructType = StructType; + StructBinder.config = config; + StructBinder.allocCString = __allocCString; + if(!StructBinder.debugFlags){ + StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags); + } + return StructBinder; +}/*StructBinderFactory*/; diff --git a/ext/wasm/jaccwabyt/jaccwabyt.md b/ext/wasm/jaccwabyt/jaccwabyt.md new file mode 100644 index 000000000..edcba260a --- /dev/null +++ b/ext/wasm/jaccwabyt/jaccwabyt.md @@ -0,0 +1,1076 @@ +Jaccwabyt 🐇 +============================================================ + +**Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte +Arrays_ + + +Welcome to Jaccwabyt, a JavaScript API which creates bindings for +WASM-compiled C structs, defining them in such a way that changes to +their state in JS are visible in C/WASM, and vice versa, permitting +two-way interchange of struct state with very little user-side +friction. + +(If that means nothing to you, neither will the rest of this page!) + +**Browser compatibility**: this library requires a _recent_ browser +and makes no attempt whatsoever to accommodate "older" or +lesser-capable ones, where "recent," _very roughly_, means released in +mid-2018 or later, with late 2021 releases required for some optional +features in some browsers (e.g. [BigInt64Array][] in Safari). It also +relies on a couple non-standard, but widespread, features, namely +[TextEncoder][] and [TextDecoder][]. It is developed primarily on +Firefox and Chrome on Linux and all claims of Safari compatibility +are based solely on feature compatibility tables provided at +[MDN][]. + +**Formalities:** + +- Author: [Stephan Beal][sgb] +- License: Public Domain +- Project Home: + + +Table of Contents +============================================================ + +- [Overview](#overview) + - [Architecture](#architecture) +- [Creating and Binding Structs](#creating-binding) + - [Step 1: Configure Jaccwabyt](#step-1) + - [Step 2: Struct Description](#step-2) + - [`P` vs `p`](#step-2-pvsp) + - [Step 3: Binding a Struct](#step-3) + - [Step 4: Creating, Using, and Destroying Instances](#step-4) +- APIs + - [Struct Binder Factory](#api-binderfactory) + - [Struct Binder](#api-structbinder) + - [Struct Type](#api-structtype) + - [Struct Constructors](#api-structctor) + - [Struct Protypes](#api-structprototype) + - [Struct Instances](#api-structinstance) +- Appendices + - [Appendix A: Limitations, TODOs, etc.](#appendix-a) + - [Appendix D: Debug Info](#appendix-d) + - [Appendix G: Generating Struct Descriptions](#appendix-g) + + +Overview +============================================================ + +Management summary: this JavaScript-only framework provides limited +two-way bindings between C structs and JavaScript objects, such that +changes to the struct in one environment are visible in the other. + +Details... + +It works by creating JavaScript proxies for C structs. Reads and +writes of the JS-side members are marshaled through a flat byte array +allocated from the WASM heap. As that heap is shared with the C-side +code, and the memory block is written using the same approach C does, +that byte array can be used to access and manipulate a given struct +instance from both JS and C. + +Motivating use case: this API was initially developed as an +experiment to determine whether it would be feasible to implement, +completely in JS, custom "VFS" and "virtual table" objects for the +WASM build of [sqlite3][]. Doing so was going to require some form of +two-way binding of several structs. Once the proof of concept was +demonstrated, a rabbit hole appeared and _down we went_... It has +since grown beyond its humble proof-of-concept origins and is believed +to be a useful (or at least interesting) tool for mixed JS/C +applications. + +Portability notes: + +- These docs sometimes use [Emscripten][] as a point of reference + because it is the most widespread WASM toolchain, but this code is + specifically designed to be usable in arbitrary WASM environments. + It abstracts away a few Emscripten-specific features into + configurable options. Similarly, the build tree requires Emscripten + but Jaccwabyt does not have any hard Emscripten dependencies. +- This code is encapsulated into a single JavaScript function. It + should be trivial to copy/paste into arbitrary WASM/JS-using + projects. +- The source tree includes C code, but only for testing and + demonstration purposes. It is not part of the core distributable. + + +Architecture +------------------------------------------------------------ + + + +```pikchr +BSBF: box rad 0.3*boxht "StructBinderFactory" fit fill lightblue +BSB: box same "StructBinder" fit at 0.75 e of 0.7 s of BSBF.c +BST: box same "StructType" fit at 1.5 e of BSBF +BSC: box same "Struct" "Ctor" fit at 1.5 s of BST +BSI: box same "Struct" "Instances" fit at 1 right of BSB.e +BC: box same at 0.25 right of 1.6 e of BST "C Structs" fit fill lightgrey + +arrow -> from BSBF.s to BSB.w "Generates" aligned above +arrow -> from BSB.n to BST.sw "Contains" aligned above +arrow -> from BSB.s to BSC.nw "Generates" aligned below +arrow -> from BSC.ne to BSI.s "Constructs" aligned below +arrow <- from BST.se to BSI.n "Inherits" aligned above +arrow <-> from BSI.e to BC.s dotted "Shared" aligned above "Memory" aligned below +arrow -> from BST.e to BC.w dotted "Mirrors Struct" aligned above "Model From" aligned below +arrow -> from BST.s to BSC.n "Prototype of" aligned above +``` + +Its major classes and functions are: + +- **[StructBinderFactory][StructBinderFactory]** is a factory function which + accepts a configuration object to customize it for a given WASM + environment. A client will typically call this only one time, with + an appropriate configuration, to generate a single... +- **[StructBinder][]** is a factory function which converts an + arbitrary number struct descriptions into... +- **[StructTypes][StructCtors]** are constructors, one per struct + description, which inherit from + **[`StructBinder.StructType`][StructType]** and are used to instantiate... +- **[Struct instances][StructInstance]** are objects representing + individual instances of generated struct types. + +An app may have any number of StructBinders, but will typically +need only one. Each StructBinder is effectively a separate +namespace for struct creation. + + + +Creating and Binding Structs +============================================================ + +From the amount of documentation provided, it may seem that +creating and using struct bindings is a daunting task, but it +essentially boils down to: + +1. [Confire Jaccwabyt for your WASM environment](#step-1). This is a + one-time task per project and results is a factory function which + can create new struct bindings. +2. [Create a JSON-format description of your C structs](#step-2). This is + required once for each struct and required updating if the C + structs change. +3. [Feed (2) to the function generated by (1)](#step-3) to create JS + constuctor functions for each struct. This is done at runtime, as + opposed to during a build-process step, and can be set up in such a + way that it does not require any maintenace after its initial + setup. +4. [Create and use instances of those structs](#step-4). + +Detailed instructions for each of those steps follows... + + +Step 1: Configure Jaccwabyt for the Environment +------------------------------------------------------------ + +Jaccwabyt's highest-level API is a single function. It creates a +factory for processing struct descriptions, but does not process any +descriptions itself. This level of abstraction exist primarily so that +the struct-specific factories can be configured for a given WASM +environment. Its usage looks like: + +> +```javascript +const MyBinder = StructBinderFactory({ + // These config options are all required: + heap: WebAssembly.Memory instance or a function which returns + a Uint8Array or Int8Array view of the WASM memory, + alloc: function(howMuchMemory){...}, + dealloc: function(pointerToFree){...} +}); +``` + +It also offers a number of other settings, but all are optional except +for the ones shown above. Those three config options abstract away +details which are specific to a given WASM environment. They provide +the WASM "heap" memory (a byte array), the memory allocator, and the +deallocator. In a conventional Emscripten setup, that config might +simply look like: + +> +```javascript +{ + heap: Module['asm']['memory'], + //Or: + // heap: ()=>Module['HEAP8'], + alloc: (n)=>Module['_malloc'](n), + dealloc: (m)=>Module['_free'](m) +} +``` + +The StructBinder factory function returns a function which can then be +used to create bindings for our structs. + + + +Step 2: Create a Struct Description +------------------------------------------------------------ + +The primary input for this framework is a JSON-compatible construct +which describes a struct we want to bind. For example, given this C +struct: + +> +```c +// C-side: +struct Foo { + int member1; + void * member2; + int64_t member3; +}; +``` + +Its JSON description looks like: + +> +```json +{ + "name": "Foo", + "sizeof": 16, + "members": { + "member1": {"offset": 0,"sizeof": 4,"signature": "i"}, + "member2": {"offset": 4,"sizeof": 4,"signature": "p"}, + "member3": {"offset": 8,"sizeof": 8,"signature": "j"} + } +} +``` + +These data _must_ match up with the C-side definition of the struct +(if any). See [Appendix G][appendix-g] for one way to easily generate +these from C code. + +Each entry in the `members` object maps the member's name to +its low-level layout: + +- `offset`: the byte offset from the start of the struct, as reported + by C's `offsetof()` feature. +- `sizeof`: as reported by C's `sizeof()`. +- `signature`: described below. +- `readOnly`: optional. If set to true, the binding layer will + throw if JS code tries to set that property. + +The order of the `members` entries is not important: their memory +layout is determined by their `offset` and `sizeof` members. The +`name` property is technically optional, but one of the steps in the +binding process requires that either it be passed an explicit name or +there be one in the struct description. The names of the `members` +entries need not match their C counterparts. Project conventions may +call for giving them different names in the JS side and the +[StructBinderFactory][] can be configured to automatically add a +prefix and/or suffix to their names. + +Nested structs are as-yet unsupported by this tool. + +Struct member "signatures" describe the data types of the members and +are an extended variant of the format used by Emscripten's +`addFunction()`. A signature for a non-function-pointer member, or +function pointer member which is to be modelled as an opaque pointer, +is a single letter. A signature for a function pointer may also be +modelled as a series of letters describing the call signature. The +supported letters are: + +- **`v`** = `void` (only used as return type for function pointer members) +- **`i`** = `int32` (4 bytes) +- **`j`** = `int64` (8 bytes) is only really usable if this code is built + with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build + flag). Without that, this API may throw when encountering the `j` + signature entry. +- **`f`** = `float` (4 bytes) +- **`d`** = `double` (8 bytes) +- **`p`** = `int32` (but see below!) +- **`P`** = Like `p` but with extra handling. Described below. +- **`s`** = like `int32` but is a _hint_ that it's a pointer to a string + so that _some_ (very limited) contexts may treat it as such, noting + such algorithms must, for lack of information to the contrary, + assume both that the encoding is UTF-8 and that the pointer's member + is NUL-terminated. If that is _not_ the case for a given string + member, do not use `s`: use `i` or `p` instead and do any string + handling yourself. + +Noting that: + +- All of these types are numeric. Attempting to set any struct-bound + property to a non-numeric value will trigger an exception except in + cases explicitly noted otherwise. + +> Sidebar: Emscripten's public docs do not mention `p`, but their +generated code includes `p` as an alias for `i`, presumably to mean +"pointer". Though `i` is legal for pointer types in the signature, `p` +is more descriptive, so this framework encourages the use of `p` for +pointer-type members. Using `p` for pointers also helps future-proof +the signatures against the eventuality that WASM eventually supports +64-bit pointers. Note that sometimes `p` really means +pointer-to-pointer, but the Emscripten JS/WASM glue does not offer +that level of expressiveness in these signatures. We simply have to be +aware of when we need to deal with pointers and pointers-to-pointers +in JS code. + +> Trivia: this API treates `p` as distinctly different from `i` in +some contexts, so its use is encouraged for pointer types. + +Signatures in the form `x(...)` denote function-pointer members and +`x` denotes non-function members. Functions with no arguments use the +form `x()`. For function-type signatures, the strings are formulated +such that they can be passed to Emscripten's `addFunction()` after +stripping out the `(` and `)` characters. For good measure, to match +the public Emscripten docs, `p` should also be replaced with `i`. In +JavaScript that might look like: + +> +``` +signature.replace(/[^vipPsjfd]/g,'').replace(/[pPs]/g,'i'); +``` + + +### `P` vs `p` in Method Signatures + +*This support is experimental and subject to change.* + +The method signature letter `p` means "pointer," which, in WASM, means +"integer." `p` is treated as an integer for most contexts, while still +also being a separate type (analog to how pointers in C are just a +special use of unsigned numbers). A capital `P` changes the semantics +of plain member pointers (but not, as of this writing, function +pointer members) as follows: + +- When a `P`-type member is **fetched** via `myStruct.x` and its value is + a non-0 integer, [`StructBinder.instanceForPointer()`][StructBinder] + is used to try to map that pointer to a struct instance. If a match + is found, the "get" operation returns that instance instead of the + integer. If no match is found, it behaves exactly as for `p`, returning + the integer value. +- When a `P`-type member is **set** via `myStruct.x=y`, if + [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is + stored in `myStruct.x`. If `y` is neither a number nor + a [StructType][], an exception is triggered (regardless of whether + `p` or `P` is used). + + + +Step 3: Binding the Struct +------------------------------------------------------------ + +We can now use the results of steps 1 and 2: + +> +```javascript +const MyStruct = MyBinder(myStructDescription); +``` + +That creates a new constructor function, `MyStruct`, which can be used +to instantiate new instances. The binder will throw if it encounters +any problems. + +That's all there is to it. + +> Sidebar: that function may modify the struct description object +and/or its sub-objects, or may even replace sub-objects, in order to +simplify certain later operations. If that is not desired, then feed +it a copy of the original, e.g. by passing it +`JSON.parse(JSON.stringify(structDefinition))`. + + +Step 4: Creating, Using, and Destroying Struct Instances +------------------------------------------------------------ + +Now that we have our constructor... + +> +```javascript +const my = new MyStruct(); +``` + +It is important to understand that creating a new instance allocates +memory on the WASM heap. We must not simply rely on garbage collection +to clean up the instances because doing so will not free up the WASM +heap memory. The correct way to free up that memory is to use the +object's `dispose()` method. Alternately, there is a "nuclear option": +`MyBinder.disposeAll()` will free the memory allocated for _all_ +instances which have not been manually disposed. + +The following usage pattern offers one way to easily ensure proper +cleanup of struct instances: + + +> +```javascript +const my = new MyStruct(); +try { + console.log(my.member1, my.member2, my.member3); + my.member1 = 12; + assert(12 === my.member1); + /* ^^^ it may seem silly to test that, but recall that assigning that + property encodes the value into a byte array in heap memory, not + a normal JS property. Similarly, fetching the property decodes it + from the byte array. */ + // Pass the struct to C code which takes a MyStruct pointer: + aCFunction( my.pointer ); + // Type-safely check if a pointer returned from C is a MyStruct: + const x = MyStruct.instanceForPointer( anotherCFunction() ); + // If it is a MyStruct, x now refers to that object. Note, however, + // that this only works for instances created in JS, as the + // pointer mapping only exists in JS space. +} finally { + my.dispose(); +} +``` + +> Sidebar: the `finally` block will be run no matter how the `try` +exits, whether it runs to completion, propagates an exception, or uses +flow-control keywords like `return` or `break`. It is perfectly legal +to use `try`/`finally` without a `catch`, and doing so is an ideal +match for the memory management requirements of Jaccwaby-bound struct +instances. + +Now that we have struct instances, there are a number of things we +can do with them, as covered in the rest of this document. + + + +API Reference +============================================================ + + +API: Binder Factory +------------------------------------------------------------ + +This is the top-most function of the API, from which all other +functions and types are generated. The binder factory's signature is: + +> +``` +Function StructBinderFactory(object configOptions); +``` + +It returns a function which these docs refer to as a [StructBinder][] +(covered in the next section). It throws on error. + +The binder factory supports the following options in its +configuration object argument: + + +- `heap` + Must be either a `WebAssembly.Memory` instance representing the WASM + heap memory OR a function which returns an Int8Array or Uint8Array + view of the WASM heap. In the latter case the function should, if + appropriate for the environment, account for the heap being able to + grow. Jaccwabyt uses this property in such a way that it "should" be + okay for the WASM heap to grow at runtime (that case is, however, + untested). + +- `alloc` + Must be a function semantically compatible with Emscripten's + `Module._malloc()`. That is, it is passed the number of bytes to + allocate and it returns a pointer. On allocation failure it may + either return 0 or throw an exception. This API will throw an + exception if allocation fails or will propagate whatever exception + the allocator throws. The allocator _must_ use the same heap as the + `heap` config option. + +- `dealloc` + Must be a function semantically compatible with Emscripten's + `Module._free()`. That is, it takes a pointer returned from + `alloc()` and releases that memory. It must never throw and must + accept a value of 0/null to mean "do nothing" (noting that 0 is + _technically_ a legal memory address in WASM, but that seems like a + design flaw). + +- `bigIntEnabled` (bool=true if BigInt64Array is available, else false) + If true, the WASM bits this code is used with must have been + compiled with int64 support (e.g. using Emscripten's `-sWASM_BIGINT` + flag). If that's not the case, this flag should be set to false. If + it's enabled, BigInt support is assumed to work and certain extra + features are enabled. Trying to use features which requires BigInt + when it is disabled (e.g. using 64-bit integer types) will trigger + an exception. + +- `memberPrefix` and `memberSuffix` (string="") + If set, struct-defined properties get bound to JS with this string + as a prefix resp. suffix. This can be used to avoid symbol name + collisions between the struct-side members and the JS-side ones + and/or to make more explicit which object-level properties belong to + the struct mapping and which to the JS side. This does not modify + the values in the struct description objects, just the property + names through which they are accessed via property access operations + and the various a [StructInstance][] APIs (noting that the latter + tend to permit both the original names and the names as modified by + these settings). + +- `log` + Optional function used for debugging output. By default + `console.log` is used but by default no debug output is generated. + This API assumes that the function will space-separate each argument + (like `console.log` does). See [Appendix D](#appendix-d) for info + about enabling debugging output. + + + +API: Struct Binder +------------------------------------------------------------ + +Struct Binders are factories which are created by the +[StructBinderFactory][]. A given Struct Binder can process any number +of distinct structs. In a typical setup, an app will have ony one +shared Binder Factory and one Struct Binder. Struct Binders which are +created via different [StructBinderFactory][] calls are unrelated to each +other, sharing no state except, perhaps, indirectly via +[StructBinderFactory][] configuration (e.g. the memory heap). + +These factories have two call signatures: + +> +```javascript +Function StructBinder([string structName,] object structDescription) +``` + +If the struct description argument has a `name` property then the name +argument is optional, otherwise it is required. + +The returned object is a constructor for instances of the struct +described by its argument(s), each of which derives from +a separate [StructType][] instance. + +The Struct Binder has the following members: + +- `allocCString(str)` + Allocates a new UTF-8-encoded, NUL-terminated copy of the given JS + string and returns its address relative to `config.heap()`. If + allocation returns 0 this function throws. Ownership of the memory + is transfered to the caller, who must eventually pass it to the + configured `config.dealloc()` function. + +- `config` + The configuration object passed to the [StructBinderFactory][], + primarily for accessing the memory (de)allocator and memory. Modifying + any of its "significant" configuration values may have undefined + results. + +- `instanceForPointer(pointer)` + Given a pointer value relative to `config.memory`, if that pointer + resolves to a struct of _any type_ generated via the same Struct + Binder, this returns the struct instance associated with it, or + `undefined` if no struct object is mapped to that pointer. This + differs from the struct-type-specific member of the same name in + that this one is not "type-safe": it does not know the type of the + returned object (if any) and may return a struct of any + [StructType][] for which this Struct Binder has created a + constructor. It cannot return instances created via a different + [StructBinderFactory][] because each factory can hypothetically have + a different memory heap. + + + +API: Struct Type +------------------------------------------------------------ + +The StructType class is a property of the [StructBinder][] function. + +Each constructor created by a [StructBinder][] inherits from _its own +instance_ of the StructType class, which contains state specific to +that struct type (e.g. the struct name and description metadata). +StructTypes which are created via different [StructBinder][] instances +are unrelated to each other, sharing no state except [StructBinderFactory][] +config options. + +The StructType constructor cannot be called from client code. It is +only called by the [StructBinder][]-generated +[constructors][StructCtors]. The `StructBinder.StructType` object +has the following "static" properties (^Which are accessible from +individual instances via `theInstance.constructor`.): + +- `allocCString(str)` + Identical to the [StructBinder][] method of the same name. + +- `hasExternalPointer(object)` + Returns true if the given object's `pointer` member refers to an + "external" object. That is the case when a pointer is passed to a + [struct's constructor][StructCtors]. If true, the memory is owned by + someone other than the object and must outlive the object. + +- `instanceForPointer(pointer)` + Works identically to the [StructBinder][] method of the same name. + +- `isA(value)` + Returns true if its argument is a StructType instance _from the same + [StructBinder][]_ as this StructType. + +- `memberKey(string)` + Returns the given string wrapped in the configured `memberPrefix` + and `memberSuffix` values. e.g. if passed `"x"` and `memberPrefix` + is `"$"` then it returns `"$x"`. This does not verify that the + property is actually a struct a member, it simply transforms the + given string. TODO(?): add a 2nd parameter indicating whether it + should validate that it's a known member name. + +The base StructType prototype has the following members, all of which +are inherited by [struct instances](#api-structinstance) and may only +legally be called on concrete struct instances unless noted otherwise: + +- `dispose()` + Frees, if appropriate, the WASM-allocated memory which is allocated + by the constructor. If this is not called before the JS engine + cleans up the object, a leak in the WASM heap memory pool will result. + When `dispose()` is called, if the object has a property named `ondispose` + then it is treated as follows: + - If it is a function, it is called with the struct object as its `this`. + That method must not throw - if it does, the exception will be + ignored. + - If it is an array, it may contain functions, pointers, and/or JS + strings. If an entry is a function, it is called as described + above. If it's a number, it's assumed to be a pointer and is + passed to the `dealloc()` function configured for the parent + [StructBinder][]. If it's a JS string, it's assumed to be a + helpful description of the next entry in the list and is simply + ignored. Strings are supported primarily for use as debugging + information. + - Some struct APIs will manipulate the `ondispose` member, creating + it as an array or converting it from a function to array as + needed. + +- `lookupMember(memberName,throwIfNotFound=true)` + Given the name of a mapped struct member, it returns the member + description object. If not found, it either throws (if the 2nd + argument is true) or returns `undefined` (if the second argument is + false). The first argument may be either the member name as it is + mapped in the struct description or that same name with the + configured `memberPrefix` and `memberSuffix` applied, noting that + the lookup in the former case is faster.\ + This method may be called directly on the prototype, without a + struct instance. + +- `memberToJsString(memberName)` + Uses `this.lookupMember(memberName,true)` to look up the given + member. If its signature is `s` then it is assumed to refer to a + NUL-terminated, UTF-8-encoded string and its memory is decoded as + such. If its signature is not one of those then an exception is + thrown. If its address is 0, `null` is returned. See also: + `setMemberCString()`. + +- `memberIsString(memberName [,throwIfNotFound=true])` + Uses `this.lookupMember(memberName,throwIfNotFound)` to look up the + given member. Returns the member description object if the member + has a signature of `s`, else returns false. If the given member is + not found, it throws if the 2nd argument is true, else it returns + false. + +- `memberKey(string)` + Works identically to `StructBinder.StructType.memberKey()`. + +- `memberKeys()` + Returns an array of the names of the properties of this object + which refer to C-side struct counterparts. + +- `memberSignature(memberName [,emscriptenFormat=false])` + Returns the signature for a given a member property, either in this + framework's format or, if passed a truthy 2nd argument, in a format + suitable for the 2nd argument to Emscripten's `addFunction()`. + Throws if the first argument does not resolve to a struct-bound + member name. The member name is resolved using `this.lookupMember()` + and throws if the member is found mapped. + +- `memoryDump()` + Returns a Uint8Array which contains the current state of this + object's raw memory buffer. Potentially useful for debugging, but + not much else. Note that the memory is necessarily, for + compatibility with C, written in the host platform's endianness and + is thus not useful as a persistent/portable serialization format. + +- `setMemberCString(memberName,str)` + Uses `StructType.allocCString()` to allocate a new C-style string, + assign it to the given member, and add the new string to this + object's `ondispose` list for cleanup when `this.dispose()` is + called. This function throws if `lookupMember()` fails for the given + member name, if allocation of the string fails, or if the member has + a signature value of anything other than `s`. Returns `this`. + *Achtung*: calling this repeatedly will not immediately free the + previous values because this code cannot know whether they are in + use in other places, namely C. Instead, each time this is called, + the prior value is retained in the `ondispose` list for cleanup when + the struct is disposed of. Because of the complexities and general + uncertainties of memory ownership and lifetime in such + constellations, it is recommended that the use of C-string members + from JS be kept to a minimum or that the relationship be one-way: + let C manage the strings and only fetch them from JS using, e.g., + `memberToJsString()`. + + + +API: Struct Constructors +------------------------------------------------------------ + +Struct constructors (the functions returned from [StructBinder][]) +are used for, intuitively enough, creating new instances of a given +struct type: + +> +``` +const x = new MyStruct; +``` + +Normally they should be passed no arguments, but they optionally +accept a single argument: a WASM heap pointer address of memory +which the object will use for storage. It does _not_ take over +ownership of that memory and that memory must be valid at +for least as long as this struct instance. This is used, for example, +to proxy static/shared C-side instances: + +> +``` +const x = new MyStruct( someCFuncWhichReturnsAMyStructPointer() ); +... +x.dispose(); // does NOT free the memory +``` + +The JS-side construct does not own the memory in that case and has no +way of knowing when the C-side struct is destroyed. Results are +specifically undefined if the JS-side struct is used after the C-side +struct's member is freed. + +> Potential TODO: add a way of passing ownership of the C-side struct +to the JS-side object. e.g. maybe simply pass `true` as the second +argument to tell the constructor to take over ownership. Currently the +pointer can be taken over using something like +`myStruct.ondispose=[myStruct.pointer]` immediately after creation. + +These constructors have the following "static" members: + +- `disposeAll()` + For each instance of this struct, the equivalent of its `dispose()` + method is called. This frees all WASM-allocated memory associated + with _all_ instances and clears the `instanceForPointer()` + mappings. Returns `this`. + +- `instanceForPointer(pointer)` + Given a pointer value (accessible via the `pointer` property of all + struct instances) which ostensibly refers to an instance of this + class, this returns the instance associated with it, or `undefined` + if no object _of this specific struct type_ is mapped to that + pointer. When C-side code calls back into JS code and passes a + pointer to an object, this function can be used to type-safely + "cast" that pointer back to its original object. + +- `isA(value)` + Returns true if its argument was created by this constructor. + +- `memberKey(string)` + Works exactly as documented for [StructType][]. + +- `memberKeys(string)` + Works exactly as documented for [StructType][]. + +- `resolveToInstance(value [,throwIfNot=false])` + Works like `instanceForPointer()` but accepts either an instance + of this struct type or a pointer which resolves to one. + It returns an instance of this struct type on success. + By default it returns a falsy value if its argument is not, + or does not resolve to, an instance of this struct type, + but if passed a truthy second argument then it will throw + instead. + +- `structInfo` + The structure description passed to [StructBinder][] when this + constructor was generated. + +- `structName` + The structure name passed to [StructBinder][] when this constructor + was generated. + + + +API: Struct Prototypes +------------------------------------------------------------ + +The prototypes of structs created via [the constructors described in +the previous section][StructCtors] are each a struct-type-specific +instance of [StructType][] and add the following struct-type-specific +properties to the mix: + +- `structInfo` + The struct description metadata, as it was given to the + [StructBinder][] which created this class. + +- `structName` + The name of the struct, as it was given to the [StructBinder][] which + created this class. + + +API: Struct Instances +------------------------------------------------------------------------ + +Instances of structs created via [the constructors described +above][StructCtors] each have the following instance-specific state in +common: + +- `pointer` + A read-only numeric property which is the "pointer" returned by the + configured allocator when this object is constructed. After + `dispose()` (inherited from [StructType][]) is called, this property + has the `undefined` value. When calling C-side code which takes a + pointer to a struct of this type, simply pass it `myStruct.pointer`. + + +Appendices +============================================================ + + +Appendix A: Limitations, TODOs, and Non-TODOs +------------------------------------------------------------ + +- This library only supports the basic set of member types supported + by WASM: numbers (which includes pointers). Nested structs are not + handled except that a member may be a _pointer_ to such a + struct. Whether or not it ever will depends entirely on whether its + developer ever needs that support. Conversion of strings between + JS and C requires infrastructure specific to each WASM environment + and is not directly supported by this library. + +- Binding functions to struct instances, such that C can see and call + JS-defined functions, is not as transparent as it really could be, + due to [shortcomings in the Emscripten + `addFunction()`/`removeFunction()` + interfaces](https://github.com/emscripten-core/emscripten/issues/17323). Until + a replacement for that API can be written, this support will be + quite limited. It _is_ possible to bind a JS-defined function to a + C-side function pointer and call that function from C. What's + missing is easier-to-use/more transparent support for doing so. + - In the meantime, a [standalone + subproject](/file/common/whwasmutil.js) of Jaccwabyt provides such a + binding mechanism, but integrating it directly with Jaccwabyt would + not only more than double its size but somehow feels inappropriate, so + experimentation is in order for how to offer that capability via + completely optional [StructBinderFactory][] config options. + +- It "might be interesting" to move access of the C-bound members into + a sub-object. e.g., from JS they might be accessed via + `myStructInstance.s.structMember`. The main advantage is that it would + eliminate any potential confusion about which members are part of + the C struct and which exist purely in JS. "The problem" with that + is that it requires internally mapping the `s` member back to the + object which contains it, which makes the whole thing more costly + and adds one more moving part which can break. Even so, it's + something to try out one rainy day. Maybe even make it optional and + make the `s` name configurable via the [StructBinderFactory][] + options. (Over-engineering is an arguably bad habit of mine.) + +- It "might be interesting" to offer (de)serialization support. It + would be very limited, e.g. we can't serialize arbitrary pointers in + any meaningful way, but "might" be useful for structs which contain + only numeric or C-string state. As it is, it's easy enough for + client code to write wrappers for that and handle the members in + ways appropriate to their apps. Any impl provided in this library + would have the shortcoming that it may inadvertently serialize + pointers (since they're just integers), resulting in potential chaos + after deserialization. Perhaps the struct description can be + extended to tag specific members as serializable and how to + serialize them. + + +Appendix D: Debug Info +------------------------------------------------------------ + +The [StructBinderFactory][], [StructBinder][], and [StructType][] classes +all have the following "unsupported" method intended primarily +to assist in their own development, as opposed to being for use in +client code: + +- `debugFlags(flags)` (integer) + An "unsupported" debugging option which may change or be removed at + any time. Its argument is a set of flags to enable/disable certain + debug/tracing output for property accessors: 0x01 for getters, 0x02 + for setters, 0x04 for allocations, 0x08 for deallocations. Pass 0 to + disable all flags and pass a negative value to _completely_ clear + all flags. The latter has the side effect of telling the flags to be + inherited from the next-higher-up class in the hierarchy, with + [StructBinderFactory][] being top-most, followed by [StructBinder][], then + [StructType][]. + + + +Appendix G: Generating Struct Descriptions From C +------------------------------------------------------------ + +Struct definitions are _ideally_ generated from WASM-compiled C, as +opposed to simply guessing the sizeofs and offsets, so that the sizeof +and offset information can be collected using C's `sizeof()` and +`offsetof()` features (noting that struct padding may impact offsets +in ways which might not be immediately obvious, so writing them by +hand is _most certainly not recommended_). + +How exactly the desciption is generated is necessarily +project-dependent. It's tempting say, "oh, that's easy! We'll just +write it by hand!" but that would be folly. The struct sizes and byte +offsets into the struct _must_ be precisely how C-side code sees the +struct or the runtime results are completely undefined. + +The approach used in developing and testing _this_ software is... + +Below is a complete copy/pastable example of how we can use a small +set of macros to generate struct descriptions from C99 or later into +static string memory. Simply add such a file to your WASM build, +arrange for its function to be exported[^export-func], and call it +from JS (noting that it requires environment-specific JS glue to +convert the returned pointer to a JS-side string). Use `JSON.parse()` +to process it, then feed the included struct descriptions into the +binder factory at your leisure. + +------------------------------------------------------------ + +```c +#include /* memset() */ +#include /* offsetof() */ +#include /* snprintf() */ +#include /* int64_t */ +#include + +struct ExampleStruct { + int v4; + void * ppV; + int64_t v8; + void (*xFunc)(void*); +}; +typedef struct ExampleStruct ExampleStruct; + +const char * wasm__ctype_json(void){ + static char strBuf[512 * 8] = {0} + /* Static buffer which must be sized large enough for + our JSON. The string-generation macros try very + hard to assert() if this buffer is too small. */; + int n = 0, structCount = 0 /* counters for the macros */; + char * pos = &strBuf[1] + /* Write-position cursor. Skip the first byte for now to help + protect against a small race condition */; + char const * const zEnd = pos + sizeof(strBuf) + /* one-past-the-end cursor (virtual EOF) */; + if(strBuf[0]) return strBuf; // Was set up in a previous call. + + //////////////////////////////////////////////////////////////////// + // First we need to build up our macro framework... + + //////////////////////////////////////////////////////////////////// + // Core output-generating macros... +#define lenCheck assert(pos < zEnd - 100) +#define outf(format,...) \ + pos += snprintf(pos, ((size_t)(zEnd - pos)), format, __VA_ARGS__); \ + lenCheck +#define out(TXT) outf("%s",TXT) +#define CloseBrace(LEVEL) \ + assert(LEVEL<5); memset(pos, '}', LEVEL); pos+=LEVEL; lenCheck + + //////////////////////////////////////////////////////////////////// + // Macros for emiting StructBinders... +#define StructBinder__(TYPE) \ + n = 0; \ + outf("%s{", (structCount++ ? ", " : "")); \ + out("\"name\": \"" # TYPE "\","); \ + outf("\"sizeof\": %d", (int)sizeof(TYPE)); \ + out(",\"members\": {"); +#define StructBinder_(T) StructBinder__(T) +// ^^^ extra indirection needed to expand CurrentStruct +#define StructBinder StructBinder_(CurrentStruct) +#define _StructBinder CloseBrace(2) +#define M(MEMBER,SIG) \ + outf("%s\"%s\": " \ + "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \ + (n++ ? ", " : ""), #MEMBER, \ + (int)offsetof(CurrentStruct,MEMBER), \ + (int)sizeof(((CurrentStruct*)0)->MEMBER), \ + SIG) + // End of macros. + //////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////// + // With that out of the way, we can do what we came here to do. + out("\"structs\": ["); { + +// For each struct description, do... +#define CurrentStruct ExampleStruct + StructBinder { + M(v4,"i"); + M(ppV,"p"); + M(v8,"j"); + M(xFunc,"v(p)"); + } _StructBinder; +#undef CurrentStruct + + } out( "]"/*structs*/); + //////////////////////////////////////////////////////////////////// + // Done! Finalize the output... + out("}"/*top-level wrapper*/); + *pos = 0; + strBuf[0] = '{'/*end of the race-condition workaround*/; + return strBuf; + +// If this file will ever be concatenated or #included with others, +// it's good practice to clean up our macros: +#undef StructBinder +#undef StructBinder_ +#undef StructBinder__ +#undef M +#undef _StructBinder +#undef CloseBrace +#undef out +#undef outf +#undef lenCheck +} +``` + +------------------------------------------------------------ + + + +[sqlite3]: https://sqlite.org +[emscripten]: https://emscripten.org +[sgb]: https://wanderinghorse.net/home/stephan/ +[appendix-g]: #appendix-g +[StructBinderFactory]: #api-binderfactory +[StructCtors]: #api-structctor +[StructType]: #api-structtype +[StructBinder]: #api-structbinder +[StructInstance]: #api-structinstance +[^export-func]: In Emscripten, add its name, prefixed with `_`, to the + project's `EXPORT_FUNCTIONS` list. +[BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array +[TextDecoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder +[TextEncoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder +[MDN]: https://developer.mozilla.org/docs/Web/API diff --git a/ext/wasm/module-symbols.html b/ext/wasm/module-symbols.html new file mode 100644 index 000000000..427d2dc8c --- /dev/null +++ b/ext/wasm/module-symbols.html @@ -0,0 +1,333 @@ + + + + + + + sqlite3 Module Symbols + + + +
    + + +

    Loading WASM module... + If this takes "a long time" it may have failed and the browser's + dev console may contain hints as to why. +

    + +

    + This page lists the SQLite3 APIs exported + by sqlite3.wasm and exposed to clients + by sqlite3.js. These lists are generated dynamically + by loading the JS/WASM module and introspecting it, with the following + caveats: +

    + +
      +
    • Some APIs are explicitly filtered out of these lists because + they are strictly for internal use within the JS/WASM APIs and + its own test code. +
    • +
    • This page runs in the main UI thread so cannot see features + which are only available in a Worker thread. If this page were + to function via a Worker, it would not be able to see + functionality only available in the main thread. Starting a + Worker here to fetch those symbols requires loading a second + copy of the sqlite3 WASM module and JS code. +
    • +
    + +
    + +

    This page exposes a global symbol named sqlite3 + which can be inspected using the browser's dev tools. +

    + +

    Jump to...

    + + + +

    sqlite3 Namespace

    +

    + The sqlite3 namespace object exposes the following... +

    +
    + + +

    sqlite3.version Object

    +

    + The sqlite3.version object exposes the following... +

    +
    + + +

    sqlite3_...() Function List

    + +

    The sqlite3.capi namespace exposes the following + sqlite3_...() + functions... +

    +
    +

    + = function is specific to the JS/WASM + bindings, not part of the C API. +

    + + +

    SQLITE_... Constants

    + +

    The sqlite3.capi namespace exposes the following + SQLITE_... + constants... +

    +
    + + +

    sqlite3.oo1 Namespace

    +

    + The sqlite3.oo1 namespace exposes the following... +

    +
    + + +

    sqlite3.wasm Namespace

    +

    + The sqlite3.wasm namespace exposes the + following... +

    +
    + + +

    sqlite3.wasm.pstack Namespace

    +

    + The sqlite3.wasm.pstack namespace exposes the + following... +

    +
    + + +

    Compilation Options

    +

    + SQLITE_... compilation options used in this build + of sqlite3.wasm... +

    +
    + +
    + + +
    + diff --git a/ext/wasm/scratchpad-wasmfs-main.html b/ext/wasm/scratchpad-wasmfs-main.html new file mode 100644 index 000000000..91f61526c --- /dev/null +++ b/ext/wasm/scratchpad-wasmfs-main.html @@ -0,0 +1,40 @@ + + + + + + + + + sqlite3 WASMFS/OPFS Main-thread Scratchpad + + +
    sqlite3 WASMFS/OPFS Main-thread Scratchpad
    + +
    +
    +
    Initializing app...
    +
    + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
    +
    +
    Downloading...
    +
    + +
    +

    Scratchpad/test app for the WASMF/OPFS integration in the + main window thread. This page requires that the sqlite3 API have + been built with WASMFS support. If OPFS support is available then + it "should" persist a database across reloads (watch the dev console + output), otherwise it will not. +

    +

    All stuff on this page happens in the dev console.

    +
    +
    + + + + + diff --git a/ext/wasm/scratchpad-wasmfs-main.js b/ext/wasm/scratchpad-wasmfs-main.js new file mode 100644 index 000000000..56f9325de --- /dev/null +++ b/ext/wasm/scratchpad-wasmfs-main.js @@ -0,0 +1,70 @@ +/* + 2022-05-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. + + *********************************************************************** + + A basic test script for sqlite3-api.js. This file must be run in + main JS thread and sqlite3.js must have been loaded before it. +*/ +'use strict'; +(function(){ + const toss = function(...args){throw new Error(args.join(' '))}; + const log = console.log.bind(console), + warn = console.warn.bind(console), + error = console.error.bind(console); + + const stdout = log; + const stderr = error; + + const test1 = function(db){ + db.exec("create table if not exists t(a);") + .transaction(function(db){ + db.prepare("insert into t(a) values(?)") + .bind(new Date().getTime()) + .stepFinalize(); + stdout("Number of values in table t:", + db.selectValue("select count(*) from t")); + }); + }; + + const runTests = function(sqlite3){ + const capi = sqlite3.capi, + oo = sqlite3.oo1, + wasm = sqlite3.wasm; + stdout("Loaded sqlite3:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); + const persistentDir = capi.sqlite3_wasmfs_opfs_dir(); + if(persistentDir){ + stdout("Persistent storage dir:",persistentDir); + }else{ + stderr("No persistent storage available."); + } + const startTime = performance.now(); + let db; + try { + db = new oo.DB(persistentDir+'/foo.db'); + stdout("DB filename:",db.filename); + const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', + banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + [ + test1 + ].forEach((f)=>{ + const n = performance.now(); + stdout(banner1,"Running",f.name+"()..."); + f(db, sqlite3); + stdout(banner2,f.name+"() took ",(performance.now() - n),"ms"); + }); + }finally{ + if(db) db.close(); + } + stdout("Total test time:",(performance.now() - startTime),"ms"); + }; + + sqlite3InitModule(self.sqlite3TestModule).then(runTests); +})(); diff --git a/ext/wasm/speedtest1-wasmfs.html b/ext/wasm/speedtest1-wasmfs.html new file mode 100644 index 000000000..e35546702 --- /dev/null +++ b/ext/wasm/speedtest1-wasmfs.html @@ -0,0 +1,149 @@ + + + + + + + + + speedtest1-wasmfs.wasm + + +
    speedtest1-wasmfs.wasm
    + + +
    +
    +
    Initializing app...
    +
    + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
    +
    +
    Downloading...
    +
    + +
    +
    This page starts running the main exe when it loads, which will + block the UI until it finishes! Adding UI controls to manually configure and start it + are TODO.
    + +
    Achtung: running it with the dev tools open may + drastically slow it down. For faster results, keep the dev + tools closed when running it! +
    +
    Output is delayed/buffered because we cannot update the UI while the + speedtest is running. Output will appear below when ready... +
    + + + + + diff --git a/ext/wasm/speedtest1-worker.html b/ext/wasm/speedtest1-worker.html new file mode 100644 index 000000000..5638ce3b5 --- /dev/null +++ b/ext/wasm/speedtest1-worker.html @@ -0,0 +1,372 @@ + + + + + + + + + speedtest1.wasm Worker + + +
    speedtest1.wasm Worker
    + + +
    +
    +
    Initializing app...
    +
    + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
    +
    +
    Downloading...
    +
    + +
    + +
    + + + + +
    +
    +
    +
    + Tips: +
      +
    • Control-click the flags to (de)select multiple flags.
    • +
    • The --big-transactions flag is important for two + of the bigger tests. Without it, those tests create a + combined total of 140k implicit transactions, reducing their + speed to an absolute crawl, especially when WASMFS is + activated. +
    • +
    • The easiest way to try different optimization levels is, + from this directory: +
      $ rm -f speedtest1.js; make -e emcc_opt='-O2' speedtest1.js
      + Then reload this page. -O2 seems to consistently produce the fastest results. +
    • +
    +
    + + + + diff --git a/ext/wasm/speedtest1-worker.js b/ext/wasm/speedtest1-worker.js new file mode 100644 index 000000000..c61cab919 --- /dev/null +++ b/ext/wasm/speedtest1-worker.js @@ -0,0 +1,99 @@ +'use strict'; +(function(){ + let speedtestJs = 'speedtest1.js'; + const urlParams = new URL(self.location.href).searchParams; + if(urlParams.has('sqlite3.dir')){ + speedtestJs = urlParams.get('sqlite3.dir') + '/' + speedtestJs; + } + importScripts('common/whwasmutil.js', speedtestJs); + /** + If this environment contains OPFS, this function initializes it and + returns the name of the dir on which OPFS is mounted, else it returns + an empty string. + */ + const wasmfsDir = function f(wasmUtil){ + if(undefined !== f._) return f._; + const pdir = '/opfs'; + if( !self.FileSystemHandle + || !self.FileSystemDirectoryHandle + || !self.FileSystemFileHandle){ + return f._ = ""; + } + try{ + if(0===wasmUtil.xCallWrapped( + 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir + )){ + return f._ = pdir; + }else{ + return f._ = ""; + } + }catch(e){ + // sqlite3_wasm_init_wasmfs() is not available + return f._ = ""; + } + }; + wasmfsDir._ = undefined; + + const mPost = function(msgType,payload){ + postMessage({type: msgType, data: payload}); + }; + + const App = Object.create(null); + App.logBuffer = []; + const logMsg = (type,msgArgs)=>{ + const msg = msgArgs.join(' '); + App.logBuffer.push(msg); + mPost(type,msg); + }; + const log = (...args)=>logMsg('stdout',args); + const logErr = (...args)=>logMsg('stderr',args); + + const runSpeedtest = function(cliFlagsArray){ + const scope = App.wasm.scopedAllocPush(); + const dbFile = App.pDir+"/speedtest1.sqlite3"; + try{ + const argv = [ + "speedtest1.wasm", ...cliFlagsArray, dbFile + ]; + App.logBuffer.length = 0; + mPost('run-start', [...argv]); + App.wasm.xCall('wasm_main', argv.length, + App.wasm.scopedAllocMainArgv(argv)); + }catch(e){ + mPost('error',e.message); + }finally{ + App.wasm.scopedAllocPop(scope); + mPost('run-end', App.logBuffer.join('\n')); + App.logBuffer.length = 0; + } + }; + + self.onmessage = function(msg){ + msg = msg.data; + switch(msg.type){ + case 'run': runSpeedtest(msg.data || []); break; + default: + logErr("Unhandled worker message type:",msg.type); + break; + } + }; + + const EmscriptenModule = { + print: log, + printErr: logErr, + setStatus: (text)=>mPost('load-status',text) + }; + self.sqlite3InitModule(EmscriptenModule).then((sqlite3)=>{ + const S = sqlite3; + App.vfsUnlink = function(pDb, fname){ + const pVfs = S.wasm.sqlite3_wasm_db_vfs(pDb, 0); + if(pVfs) S.wasm.sqlite3_wasm_vfs_unlink(pVfs, fname||0); + }; + App.pDir = wasmfsDir(S.wasm); + App.wasm = S.wasm; + //if(App.pDir) log("Persistent storage:",pDir); + //else log("Using transient storage."); + mPost('ready',true); + log("Registered VFSes:", ...S.capi.sqlite3_js_vfs_list()); + }); +})(); diff --git a/ext/wasm/speedtest1.html b/ext/wasm/speedtest1.html new file mode 100644 index 000000000..5286b9e48 --- /dev/null +++ b/ext/wasm/speedtest1.html @@ -0,0 +1,174 @@ + + + + + + + + + speedtest1.wasm + + +
    speedtest1.wasm
    + + +
    +
    +
    Initializing app...
    +
    + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
    +
    +
    Downloading...
    +
    + +
    +
    This page starts running the main exe when it loads, which will + block the UI until it finishes! Adding UI controls to manually configure and start it + are TODO.
    +
    +
    Achtung: running it with the dev tools open may + drastically slow it down. For faster results, keep the dev + tools closed when running it! +
    +
    Output is delayed/buffered because we cannot update the UI while the + speedtest is running. Output will appear below when ready... +
    + + + + + diff --git a/ext/wasm/split-speedtest1-script.sh b/ext/wasm/split-speedtest1-script.sh new file mode 100755 index 000000000..e072d08a1 --- /dev/null +++ b/ext/wasm/split-speedtest1-script.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Expects $1 to be a (speedtest1 --script) output file. Output is a +# series of SQL files extracted from that file. +infile=${1:?arg = speedtest1 --script output file} +testnums=$(grep -e '^-- begin test' "$infile" | cut -d' ' -f4) +if [ x = "x${testnums}" ]; then + echo "Could not parse any begin/end blocks out of $infile" 1>&2 + exit 1 +fi +odir=${infile%%/*} +if [ "$odir" = "$infile" ]; then odir="."; fi +#echo testnums=$testnums +for n in $testnums; do + ofile=$odir/$(printf "speedtest1-%03d.sql" $n) + sed -n -e "/^-- begin test $n /,/^-- end test $n\$/p" $infile > $ofile + echo -e "$n\t$ofile" +done diff --git a/ext/wasm/sql/000-mandelbrot.sql b/ext/wasm/sql/000-mandelbrot.sql new file mode 100644 index 000000000..3aa5f5715 --- /dev/null +++ b/ext/wasm/sql/000-mandelbrot.sql @@ -0,0 +1,17 @@ +WITH RECURSIVE + xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2), + yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0), + m(iter, cx, cy, x, y) AS ( + SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis + UNION ALL + SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m + WHERE (x*x + y*y) < 4.0 AND iter<28 + ), + m2(iter, cx, cy) AS ( + SELECT max(iter), cx, cy FROM m GROUP BY cx, cy + ), + a(t) AS ( + SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') + FROM m2 GROUP BY cy + ) +SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a; diff --git a/ext/wasm/sql/001-sudoku.sql b/ext/wasm/sql/001-sudoku.sql new file mode 100644 index 000000000..53661b1c3 --- /dev/null +++ b/ext/wasm/sql/001-sudoku.sql @@ -0,0 +1,28 @@ +WITH RECURSIVE + input(sud) AS ( + VALUES('53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79') + ), + digits(z, lp) AS ( + VALUES('1', 1) + UNION ALL SELECT + CAST(lp+1 AS TEXT), lp+1 FROM digits WHERE lp<9 + ), + x(s, ind) AS ( + SELECT sud, instr(sud, '.') FROM input + UNION ALL + SELECT + substr(s, 1, ind-1) || z || substr(s, ind+1), + instr( substr(s, 1, ind-1) || z || substr(s, ind+1), '.' ) + FROM x, digits AS z + WHERE ind>0 + AND NOT EXISTS ( + SELECT 1 + FROM digits AS lp + WHERE z.z = substr(s, ((ind-1)/9)*9 + lp, 1) + OR z.z = substr(s, ((ind-1)%9) + (lp-1)*9 + 1, 1) + OR z.z = substr(s, (((ind-1)/3) % 3) * 3 + + ((ind-1)/27) * 27 + lp + + ((lp-1) / 3) * 6, 1) + ) + ) +SELECT s FROM x WHERE ind=0; diff --git a/ext/wasm/test-opfs-vfs.html b/ext/wasm/test-opfs-vfs.html new file mode 100644 index 000000000..235ef51e9 --- /dev/null +++ b/ext/wasm/test-opfs-vfs.html @@ -0,0 +1,26 @@ + + + + + + + + + Async-behind-Sync experiment + + +
    Async-behind-Sync sqlite3_vfs
    +
    This performs a sanity test of the "opfs" sqlite3_vfs. + See the dev console for all output. +
    +
    + Use this link to delete the persistent OPFS-side db (if any). +
    +
    + + + diff --git a/ext/wasm/test-opfs-vfs.js b/ext/wasm/test-opfs-vfs.js new file mode 100644 index 000000000..bba31bb9c --- /dev/null +++ b/ext/wasm/test-opfs-vfs.js @@ -0,0 +1,85 @@ +/* + 2022-09-17 + + 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. + + *********************************************************************** + + A testing ground for the OPFS VFS. +*/ +'use strict'; +const tryOpfsVfs = async function(sqlite3){ + const toss = function(...args){throw new Error(args.join(' '))}; + const logPrefix = "OPFS tester:"; + const log = (...args)=>console.log(logPrefix,...args); + const warn = (...args)=>console.warn(logPrefix,...args); + const error = (...args)=>console.error(logPrefix,...args); + const opfs = sqlite3.opfs; + log("tryOpfsVfs()"); + if(!sqlite3.opfs){ + const e = toss("OPFS is not available."); + error(e); + throw e; + } + const capi = sqlite3.capi; + const pVfs = capi.sqlite3_vfs_find("opfs") || toss("Missing 'opfs' VFS."); + const oVfs = capi.sqlite3_vfs.instanceForPointer(pVfs) || toss("Unexpected instanceForPointer() result.");; + log("OPFS VFS:",pVfs, oVfs); + + const wait = async (ms)=>{ + return new Promise((resolve)=>setTimeout(resolve, ms)); + }; + + const urlArgs = new URL(self.location.href).searchParams; + const dbFile = "my-persistent.db"; + if(urlArgs.has('delete')) sqlite3.opfs.unlink(dbFile); + + const db = new opfs.OpfsDb(dbFile,'ct'); + log("db file:",db.filename); + try{ + if(opfs.entryExists(dbFile)){ + let n = db.selectValue("select count(*) from sqlite_schema"); + log("Persistent data found. sqlite_schema entry count =",n); + } + db.transaction((db)=>{ + db.exec({ + sql:[ + "create table if not exists t(a);", + "insert into t(a) values(?),(?),(?);", + ], + bind: [performance.now() | 0, + (performance.now() |0) / 2, + (performance.now() |0) / 4] + }); + }); + log("count(*) from t =",db.selectValue("select count(*) from t")); + + // Some sanity checks of the opfs utility functions... + const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); + const aDir = testDir+'/test/dir'; + await opfs.mkdir(aDir) || toss("mkdir failed"); + await opfs.mkdir(aDir) || toss("mkdir must pass if the dir exists"); + await opfs.unlink(testDir+'/test') && toss("delete 1 should have failed (dir not empty)"); + //await opfs.entryExists(testDir) + await opfs.unlink(testDir+'/test/dir') || toss("delete 2 failed"); + await opfs.unlink(testDir+'/test/dir') && toss("delete 2b should have failed (dir already deleted)"); + await opfs.unlink(testDir, true) || toss("delete 3 failed"); + await opfs.entryExists(testDir) && toss("entryExists(",testDir,") should have failed"); + }finally{ + db.close(); + } + + log("Done!"); +}/*tryOpfsVfs()*/; + +importScripts('jswasm/sqlite3.js'); +self.sqlite3InitModule() + .then((sqlite3)=>tryOpfsVfs(sqlite3)) + .catch((e)=>{ + console.error("Error initializing module:",e); + }); diff --git a/ext/wasm/tester1-worker.html b/ext/wasm/tester1-worker.html new file mode 100644 index 000000000..4d2df0c8d --- /dev/null +++ b/ext/wasm/tester1-worker.html @@ -0,0 +1,63 @@ + + + + + + + + + sqlite3 tester #1 (Worker thread) + + + +

    sqlite3 WASM/JS tester #1 (Worker thread)

    +
    See tester1.html + for the UI-thread variant.
    +
    + + +
    +
    + + + diff --git a/ext/wasm/tester1.html b/ext/wasm/tester1.html new file mode 100644 index 000000000..f7a2fba4a --- /dev/null +++ b/ext/wasm/tester1.html @@ -0,0 +1,28 @@ + + + + + + + + + sqlite3 tester #1 (UI thread) + + + +

    sqlite3 WASM/JS tester #1 (UI thread)

    +
    See tester1-worker.html + for the Worker-thread variant.
    +
    + + +
    +
    + + + + diff --git a/ext/wasm/tester1.js b/ext/wasm/tester1.js new file mode 100644 index 000000000..99fb5b318 --- /dev/null +++ b/ext/wasm/tester1.js @@ -0,0 +1,1864 @@ +/* + 2022-10-12 + + The author disclaims copyright to this source code. In place of a + legal notice, here is a blessing: + + * May you do good and not evil. + * May you find forgiveness for yourself and forgive others. + * May you share freely, never taking more than you give. + + *********************************************************************** + + Main functional and regression tests for the sqlite3 WASM API. + + This mini-framework works like so: + + This script adds a series of test groups, each of which contains an + arbitrary number of tests, into a queue. After loading of the + sqlite3 WASM/JS module is complete, that queue is processed. If any + given test fails, the whole thing fails. This script is built such + that it can run from the main UI thread or worker thread. Test + groups and individual tests can be assigned a predicate function + which determines whether to run them or not, and this is + specifically intended to be used to toggle certain tests on or off + for the main/worker threads. + + Each test group defines a state object which gets applied as each + test function's `this`. Test functions can use that to, e.g., set up + a db in an early test and close it in a later test. Each test gets + passed the sqlite3 namespace object as its only argument. +*/ +'use strict'; +(function(){ + /** + Set up our output channel differently depending + on whether we are running in a worker thread or + the main (UI) thread. + */ + let logClass; + /* Predicate for tests/groups. */ + const isUIThread = ()=>(self.window===self && self.document); + /* Predicate for tests/groups. */ + const isWorker = ()=>!isUIThread(); + /* Predicate for tests/groups. */ + const testIsTodo = ()=>false; + const haveWasmCTests = ()=>{ + return !!wasm.exports.sqlite3_wasm_test_intptr; + }; + { + const mapToString = (v)=>{ + switch(typeof v){ + case 'number': case 'string': case 'boolean': + case 'undefined': case 'bigint': + return ''+v; + default: break; + } + if(null===v) return 'null'; + if(v instanceof Error){ + v = { + message: v.message, + stack: v.stack, + errorClass: v.name + }; + } + return JSON.stringify(v,undefined,2); + }; + const normalizeArgs = (args)=>args.map(mapToString); + if( isUIThread() ){ + console.log("Running in the UI thread."); + const logTarget = document.querySelector('#test-output'); + logClass = function(cssClass,...args){ + const ln = document.createElement('div'); + if(cssClass){ + for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){ + ln.classList.add(c); + } + } + ln.append(document.createTextNode(normalizeArgs(args).join(' '))); + logTarget.append(ln); + }; + const cbReverse = document.querySelector('#cb-log-reverse'); + const cbReverseKey = 'tester1:cb-log-reverse'; + const cbReverseIt = ()=>{ + logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); + //localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0); + }; + cbReverse.addEventListener('change', cbReverseIt, true); + /*if(localStorage.getItem(cbReverseKey)){ + cbReverse.checked = !!(+localStorage.getItem(cbReverseKey)); + }*/ + cbReverseIt(); + }else{ /* Worker thread */ + console.log("Running in a Worker thread."); + logClass = function(cssClass,...args){ + postMessage({ + type:'log', + payload:{cssClass, args: normalizeArgs(args)} + }); + }; + } + } + const reportFinalTestStatus = function(pass){ + if(isUIThread()){ + const e = document.querySelector('#color-target'); + e.classList.add(pass ? 'tests-pass' : 'tests-fail'); + }else{ + postMessage({type:'test-result', payload:{pass}}); + } + }; + const log = (...args)=>{ + //console.log(...args); + logClass('',...args); + } + const warn = (...args)=>{ + console.warn(...args); + logClass('warning',...args); + } + const error = (...args)=>{ + console.error(...args); + logClass('error',...args); + }; + + const toss = (...args)=>{ + error(...args); + throw new Error(args.join(' ')); + }; + const tossQuietly = (...args)=>{ + throw new Error(args.join(' ')); + }; + + const roundMs = (ms)=>Math.round(ms*100)/100; + + /** + Helpers for writing sqlite3-specific tests. + */ + const TestUtil = { + /** Running total of the number of tests run via + this API. */ + counter: 0, + /* Separator line for log messages. */ + separator: '------------------------------------------------------------', + /** + If expr is a function, it is called and its result + is returned, coerced to a bool, else expr, coerced to + a bool, is returned. + */ + toBool: function(expr){ + return (expr instanceof Function) ? !!expr() : !!expr; + }, + /** Throws if expr is false. If expr is a function, it is called + and its result is evaluated. If passed multiple arguments, + those after the first are a message string which get applied + as an exception message if the assertion fails. The message + arguments are concatenated together with a space between each. + */ + assert: function f(expr, ...msg){ + ++this.counter; + if(!this.toBool(expr)){ + throw new Error(msg.length ? msg.join(' ') : "Assertion failed."); + } + return this; + }, + /** Calls f() and squelches any exception it throws. If it + does not throw, this function throws. */ + mustThrow: function(f, msg){ + ++this.counter; + let err; + try{ f(); } catch(e){err=e;} + if(!err) throw new Error(msg || "Expected exception."); + return this; + }, + /** + Works like mustThrow() but expects filter to be a regex, + function, or string to match/filter the resulting exception + against. If f() does not throw, this test fails and an Error is + thrown. If filter is a regex, the test passes if + filter.test(error.message) passes. If it's a function, the test + passes if filter(error) returns truthy. If it's a string, the + test passes if the filter matches the exception message + precisely. In all other cases the test fails, throwing an + Error. + + If it throws, msg is used as the error report unless it's falsy, + in which case a default is used. + */ + mustThrowMatching: function(f, filter, msg){ + ++this.counter; + let err; + try{ f(); } catch(e){err=e;} + if(!err) throw new Error(msg || "Expected exception."); + let pass = false; + if(filter instanceof RegExp) pass = filter.test(err.message); + else if(filter instanceof Function) pass = filter(err); + else if('string' === typeof filter) pass = (err.message === filter); + if(!pass){ + throw new Error(msg || ("Filter rejected this exception: "+err.message)); + } + return this; + }, + /** Throws if expr is truthy or expr is a function and expr() + returns truthy. */ + throwIf: function(expr, msg){ + ++this.counter; + if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); + return this; + }, + /** Throws if expr is falsy or expr is a function and expr() + returns falsy. */ + throwUnless: function(expr, msg){ + ++this.counter; + if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); + return this; + }, + eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)), + TestGroup: (function(){ + let groupCounter = 0; + const TestGroup = function(name, predicate){ + this.number = ++groupCounter; + this.name = name; + this.predicate = predicate; + this.tests = []; + }; + TestGroup.prototype = { + addTest: function(testObj){ + this.tests.push(testObj); + return this; + }, + run: async function(sqlite3){ + log(TestUtil.separator); + logClass('group-start',"Group #"+this.number+':',this.name); + const indent = ' '; + if(this.predicate && !this.predicate(sqlite3)){ + logClass('warning',indent, + "SKIPPING group because predicate says to."); + return; + } + const assertCount = TestUtil.counter; + const groupState = Object.create(null); + const skipped = []; + let runtime = 0, i = 0; + for(const t of this.tests){ + ++i; + const n = this.number+"."+i; + log(indent, n+":", t.name); + if(t.predicate && !t.predicate(sqlite3)){ + logClass('warning', indent, indent, + 'SKIPPING because predicate says to'); + skipped.push( n+': '+t.name ); + }else{ + const tc = TestUtil.counter, now = performance.now(); + await t.test.call(groupState, sqlite3); + const then = performance.now(); + runtime += then - now; + logClass('faded',indent, indent, + TestUtil.counter - tc, 'assertion(s) in', + roundMs(then-now),'ms'); + } + } + logClass('green', + "Group #"+this.number+":",(TestUtil.counter - assertCount), + "assertion(s) in",roundMs(runtime),"ms"); + if(skipped.length){ + logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped); + } + } + }; + return TestGroup; + })()/*TestGroup*/, + testGroups: [], + currentTestGroup: undefined, + addGroup: function(name, predicate){ + this.testGroups.push( this.currentTestGroup = + new this.TestGroup(name, predicate) ); + return this; + }, + addTest: function(name, callback){ + let predicate; + if(1===arguments.length){ + const opt = arguments[0]; + predicate = opt.predicate; + name = opt.name; + callback = opt.test; + } + this.currentTestGroup.addTest({ + name, predicate, test: callback + }); + return this; + }, + runTests: async function(sqlite3){ + return new Promise(async function(pok,pnok){ + try { + let runtime = 0; + for(let g of this.testGroups){ + const now = performance.now(); + await g.run(sqlite3); + runtime += performance.now() - now; + } + log(TestUtil.separator); + logClass(['strong','green'], + "Done running tests.",TestUtil.counter,"assertions in", + roundMs(runtime),'ms'); + pok(); + reportFinalTestStatus(true); + }catch(e){ + error(e); + pnok(e); + reportFinalTestStatus(false); + } + }.bind(this)); + } + }/*TestUtil*/; + const T = TestUtil; + T.g = T.addGroup; + T.t = T.addTest; + let capi, wasm/*assigned after module init*/; + //////////////////////////////////////////////////////////////////////// + // End of infrastructure setup. Now define the tests... + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////// + T.g('Basic sanity checks') + .t('Namespace object checks', function(sqlite3){ + const wasmCtypes = wasm.ctype; + T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs'). + assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4). + assert(wasmCtypes.structs[1/*sqlite3_io_methods*/ + ].members.xFileSize.offset>0); + [ /* Spot-check a handful of constants to make sure they got installed... */ + 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8', + 'SQLITE_STATIC', 'SQLITE_DIRECTONLY', + 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE' + ].forEach((k)=>T.assert('number' === typeof capi[k])); + [/* Spot-check a few of the WASM API methods. */ + 'alloc', 'dealloc', 'installFunction' + ].forEach((k)=>T.assert(wasm[k] instanceof Function)); + + T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0). + assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0). + assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error'); + + try { + throw new sqlite3.WasmAllocError; + }catch(e){ + T.assert(e instanceof Error) + .assert(e instanceof sqlite3.WasmAllocError) + .assert("Allocation failed." === e.message); + } + try { + throw new sqlite3.WasmAllocError("test",{ + cause: 3 + }); + }catch(e){ + T.assert(3 === e.cause) + .assert("test" === e.message); + } + try {throw new sqlite3.WasmAllocError("test","ing",".")} + catch(e){T.assert("test ing ." === e.message)} + + try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) } + catch(e){ T.assert('SQLITE_SCHEMA' === e.message) } + try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) } + catch(e){ + T.assert('SQLITE_CORRUPT'===e.message) + .assert(true===e.cause); + } + }) + //////////////////////////////////////////////////////////////////// + .t('strglob/strlike', function(sqlite3){ + T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). + assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). + assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). + assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); + }) + //////////////////////////////////////////////////////////////////// + ;/*end of basic sanity checks*/ + + //////////////////////////////////////////////////////////////////// + T.g('C/WASM Utilities') + .t('sqlite3.wasm namespace', function(sqlite3){ + const w = wasm; + const chr = (x)=>x.charCodeAt(0); + //log("heap getters..."); + { + const li = [8, 16, 32]; + if(w.bigIntEnabled) li.push(64); + for(const n of li){ + const bpe = n/8; + const s = w.heapForSize(n,false); + T.assert(bpe===s.BYTES_PER_ELEMENT). + assert(w.heapForSize(s.constructor) === s); + const u = w.heapForSize(n,true); + T.assert(bpe===u.BYTES_PER_ELEMENT). + assert(s!==u). + assert(w.heapForSize(u.constructor) === u); + } + } + + // isPtr32() + { + const ip = w.isPtr32; + T.assert(ip(0)) + .assert(!ip(-1)) + .assert(!ip(1.1)) + .assert(!ip(0xffffffff)) + .assert(ip(0x7fffffff)) + .assert(!ip()) + .assert(!ip(null)/*might change: under consideration*/) + ; + } + + //log("jstrlen()..."); + { + T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc")); + } + + //log("jstrcpy()..."); + { + const fillChar = 10; + let ua = new Uint8Array(8), rc, + refill = ()=>ua.fill(fillChar); + refill(); + rc = w.jstrcpy("hello", ua); + T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]); + refill(); + ua[5] = chr('!'); + rc = w.jstrcpy("HELLO", ua, 0, -1, false); + T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]); + refill(); + rc = w.jstrcpy("the end", ua, 4); + //log("rc,ua",rc,ua); + T.assert(4===rc).assert(0===ua[7]). + assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); + refill(); + rc = w.jstrcpy("the end", ua, 4, -1, false); + T.assert(4===rc).assert(chr(' ')===ua[7]). + assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); + refill(); + rc = w.jstrcpy("", ua, 0, 1, true); + //log("rc,ua",rc,ua); + T.assert(1===rc).assert(0===ua[0]); + refill(); + rc = w.jstrcpy("x", ua, 0, 1, true); + //log("rc,ua",rc,ua); + T.assert(1===rc).assert(0===ua[0]); + refill(); + rc = w.jstrcpy('äbä', ua, 0, 1, true); + T.assert(1===rc, 'Must not write partial multi-byte char.') + .assert(0===ua[0]); + refill(); + rc = w.jstrcpy('äbä', ua, 0, 2, true); + T.assert(1===rc, 'Must not write partial multi-byte char.') + .assert(0===ua[0]); + refill(); + rc = w.jstrcpy('äbä', ua, 0, 2, false); + T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]); + }/*jstrcpy()*/ + + //log("cstrncpy()..."); + { + const scope = w.scopedAllocPush(); + try { + let cStr = w.scopedAllocCString("hello"); + const n = w.cstrlen(cStr); + let cpy = w.scopedAlloc(n+10); + let rc = w.cstrncpy(cpy, cStr, n+10); + T.assert(n+1 === rc). + assert("hello" === w.cstringToJs(cpy)). + assert(chr('o') === w.getMemValue(cpy+n-1)). + assert(0 === w.getMemValue(cpy+n)); + let cStr2 = w.scopedAllocCString("HI!!!"); + rc = w.cstrncpy(cpy, cStr2, 3); + T.assert(3===rc). + assert("HI!lo" === w.cstringToJs(cpy)). + assert(chr('!') === w.getMemValue(cpy+2)). + assert(chr('l') === w.getMemValue(cpy+3)); + }finally{ + w.scopedAllocPop(scope); + } + } + + //log("jstrToUintArray()..."); + { + let a = w.jstrToUintArray("hello", false); + T.assert(5===a.byteLength).assert(chr('o')===a[4]); + a = w.jstrToUintArray("hello", true); + T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]); + a = w.jstrToUintArray("äbä", false); + T.assert(5===a.byteLength).assert(chr('b')===a[2]); + a = w.jstrToUintArray("äbä", true); + T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]); + } + + //log("allocCString()..."); + { + const cstr = w.allocCString("hällo, world"); + const n = w.cstrlen(cstr); + T.assert(13 === n) + .assert(0===w.getMemValue(cstr+n)) + .assert(chr('d')===w.getMemValue(cstr+n-1)); + } + + //log("scopedAlloc() and friends..."); + { + const alloc = w.alloc, dealloc = w.dealloc; + w.alloc = w.dealloc = null; + T.assert(!w.scopedAlloc.level) + .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) + .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); + w.alloc = alloc; + T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); + w.dealloc = dealloc; + T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/) + .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) + .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/); + const asc = w.scopedAllocPush(); + let asc2; + try { + const p1 = w.scopedAlloc(16), + p2 = w.scopedAlloc(16); + T.assert(1===w.scopedAlloc.level) + .assert(Number.isFinite(p1)) + .assert(Number.isFinite(p2)) + .assert(asc[0] === p1) + .assert(asc[1]===p2); + asc2 = w.scopedAllocPush(); + const p3 = w.scopedAlloc(16); + T.assert(2===w.scopedAlloc.level) + .assert(Number.isFinite(p3)) + .assert(2===asc.length) + .assert(p3===asc2[0]); + + const [z1, z2, z3] = w.scopedAllocPtr(3); + T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2) + .assert(0===w.getMemValue(z1,'i32'), 'allocPtr() must zero the targets') + .assert(0===w.getMemValue(z3,'i32')); + }finally{ + // Pop them in "incorrect" order to make sure they behave: + w.scopedAllocPop(asc); + T.assert(0===asc.length); + T.mustThrowMatching(()=>w.scopedAllocPop(asc), + /^Invalid state object/); + if(asc2){ + T.assert(2===asc2.length,'Should be p3 and z1'); + w.scopedAllocPop(asc2); + T.assert(0===asc2.length); + T.mustThrowMatching(()=>w.scopedAllocPop(asc2), + /^Invalid state object/); + } + } + T.assert(0===w.scopedAlloc.level); + w.scopedAllocCall(function(){ + T.assert(1===w.scopedAlloc.level); + const [cstr, n] = w.scopedAllocCString("hello, world", true); + T.assert(12 === n) + .assert(0===w.getMemValue(cstr+n)) + .assert(chr('d')===w.getMemValue(cstr+n-1)); + }); + }/*scopedAlloc()*/ + + //log("xCall()..."); + { + const pJson = w.xCall('sqlite3_wasm_enum_json'); + T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300); + } + + //log("xWrap()..."); + { + T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'), + /requires 0 arg/). + assert(w.xWrap.resultAdapter('i32') instanceof Function). + assert(w.xWrap.argAdapter('i32') instanceof Function); + let fw = w.xWrap('sqlite3_libversion','utf8'); + T.mustThrowMatching(()=>fw(1), /requires 0 arg/); + let rc = fw(); + T.assert('string'===typeof rc).assert(rc.length>5); + rc = w.xCallWrapped('sqlite3_wasm_enum_json','*'); + T.assert(rc>0 && Number.isFinite(rc)); + rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8'); + T.assert('string'===typeof rc).assert(rc.length>300); + if(haveWasmCTests()){ + fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:free',['i32']); + rc = fw(0); + T.assert('hello'===rc); + rc = fw(1); + T.assert(null===rc); + + if(w.bigIntEnabled){ + w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v)); + w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v)); + fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice'); + rc = fw(1); + T.assert(12n===rc); + + w.scopedAllocCall(function(){ + let pI1 = w.scopedAlloc(8), pI2 = pI1+4; + w.setMemValue(pI1, 0,'*')(pI2, 0, '*'); + let f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']); + let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64'); + T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2)); + }); + } + } + } + }/*WhWasmUtil*/) + + //////////////////////////////////////////////////////////////////// + .t('sqlite3.StructBinder (jaccwabyt)', function(sqlite3){ + const S = sqlite3, W = S.wasm; + const MyStructDef = { + sizeof: 16, + members: { + p4: {offset: 0, sizeof: 4, signature: "i"}, + pP: {offset: 4, sizeof: 4, signature: "P"}, + ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true}, + cstr: {offset: 12, sizeof: 4, signature: "s"} + } + }; + if(W.bigIntEnabled){ + const m = MyStructDef; + m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"}; + m.sizeof += m.members.p8.sizeof; + } + const StructType = S.StructBinder.StructType; + const K = S.StructBinder('my_struct',MyStructDef); + T.mustThrowMatching(()=>K(), /via 'new'/). + mustThrowMatching(()=>new K('hi'), /^Invalid pointer/); + const k1 = new K(), k2 = new K(); + try { + T.assert(k1.constructor === K). + assert(K.isA(k1)). + assert(k1 instanceof K). + assert(K.prototype.lookupMember('p4').key === '$p4'). + assert(K.prototype.lookupMember('$p4').name === 'p4'). + mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/). + assert(undefined === K.prototype.lookupMember('nope',false)). + assert(k1 instanceof StructType). + assert(StructType.isA(k1)). + assert(K.resolveToInstance(k1.pointer)===k1). + mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/). + assert(k1 === StructType.instanceForPointer(k1.pointer)). + mustThrowMatching(()=>k1.$ro = 1, /read-only/); + Object.keys(MyStructDef.members).forEach(function(key){ + key = K.memberKey(key); + T.assert(0 == k1[key], + "Expecting allocation to zero the memory "+ + "for "+key+" but got: "+k1[key]+ + " from "+k1.memoryDump()); + }); + T.assert('number' === typeof k1.pointer). + mustThrowMatching(()=>k1.pointer = 1, /pointer/). + assert(K.instanceForPointer(k1.pointer) === k1); + k1.$p4 = 1; k1.$pP = 2; + T.assert(1 === k1.$p4).assert(2 === k1.$pP); + if(MyStructDef.members.$p8){ + k1.$p8 = 1/*must not throw despite not being a BigInt*/; + k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2); + T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8); + } + T.assert(!k1.ondispose); + k1.setMemberCString('cstr', "A C-string."); + T.assert(Array.isArray(k1.ondispose)). + assert(k1.ondispose[0] === k1.$cstr). + assert('number' === typeof k1.$cstr). + assert('A C-string.' === k1.memberToJsString('cstr')); + k1.$pP = k2; + T.assert(k1.$pP === k2); + k1.$pP = null/*null is special-cased to 0.*/; + T.assert(0===k1.$pP); + let ptr = k1.pointer; + k1.dispose(); + T.assert(undefined === k1.pointer). + assert(undefined === K.instanceForPointer(ptr)). + mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); + const k3 = new K(); + ptr = k3.pointer; + T.assert(k3 === K.instanceForPointer(ptr)); + K.disposeAll(); + T.assert(ptr). + assert(undefined === k2.pointer). + assert(undefined === k3.pointer). + assert(undefined === K.instanceForPointer(ptr)); + }finally{ + k1.dispose(); + k2.dispose(); + } + + if(!W.bigIntEnabled){ + log("Skipping WasmTestStruct tests: BigInt not enabled."); + return; + } + + const WTStructDesc = + W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0]; + const autoResolvePtr = true /* EXPERIMENTAL */; + if(autoResolvePtr){ + WTStructDesc.members.ppV.signature = 'P'; + } + const WTStruct = S.StructBinder(WTStructDesc); + //log(WTStruct.structName, WTStruct.structInfo); + const wts = new WTStruct(); + //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype)); + try{ + T.assert(wts.constructor === WTStruct). + assert(WTStruct.memberKeys().indexOf('$ppV')>=0). + assert(wts.memberKeys().indexOf('$v8')>=0). + assert(!K.isA(wts)). + assert(WTStruct.isA(wts)). + assert(wts instanceof WTStruct). + assert(wts instanceof StructType). + assert(StructType.isA(wts)). + assert(wts === StructType.instanceForPointer(wts.pointer)); + T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). + assert(0===wts.$ppV).assert(0===wts.$xFunc). + assert(WTStruct.instanceForPointer(wts.pointer) === wts); + const testFunc = + W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/); + let counter = 0; + //log("wts.pointer =",wts.pointer); + const wtsFunc = function(arg){ + /*log("This from a JS function called from C, "+ + "which itself was called from JS. arg =",arg);*/ + ++counter; + T.assert(WTStruct.instanceForPointer(arg) === wts); + if(3===counter){ + tossQuietly("Testing exception propagation."); + } + } + wts.$v4 = 10; wts.$v8 = 20; + wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc')) + T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8) + .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc) + .assert(0 === wts.$cstr) + .assert(wts.memberIsString('$cstr')) + .assert(!wts.memberIsString('$v4')) + .assert(null === wts.memberToJsString('$cstr')) + .assert(W.functionEntry(wts.$xFunc) instanceof Function); + /* It might seem silly to assert that the values match + what we just set, but recall that all of those property + reads and writes are, via property interceptors, + actually marshaling their data to/from a raw memory + buffer, so merely reading them back is actually part of + testing the struct-wrapping API. */ + + testFunc(wts.pointer); + //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV); + T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8) + .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)) + .assert('string' === typeof wts.memberToJsString('cstr')) + .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr')) + .mustThrowMatching(()=>wts.memberToJsString('xFunc'), + /Invalid member type signature for C-string/) + ; + testFunc(wts.pointer); + T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8) + .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)); + /** The 3rd call to wtsFunc throw from JS, which is called + from C, which is called from JS. Let's ensure that + that exception propagates back here... */ + T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/); + W.uninstallFunction(wts.$xFunc); + wts.$xFunc = 0; + if(autoResolvePtr){ + wts.$ppV = 0; + T.assert(!wts.$ppV); + //WTStruct.debugFlags(0x03); + wts.$ppV = wts; + T.assert(wts === wts.$ppV) + //WTStruct.debugFlags(0); + } + wts.setMemberCString('cstr', "A C-string."); + T.assert(Array.isArray(wts.ondispose)). + assert(wts.ondispose[0] === wts.$cstr). + assert('A C-string.' === wts.memberToJsString('cstr')); + const ptr = wts.pointer; + wts.dispose(); + T.assert(ptr).assert(undefined === wts.pointer). + assert(undefined === WTStruct.instanceForPointer(ptr)) + }finally{ + wts.dispose(); + } + }/*StructBinder*/) + + //////////////////////////////////////////////////////////////////// + .t('sqlite3.StructBinder part 2', function(sqlite3){ + // https://www.sqlite.org/c3ref/vfs.html + // https://www.sqlite.org/c3ref/io_methods.html + const sqlite3_io_methods = capi.sqlite3_io_methods, + sqlite3_vfs = capi.sqlite3_vfs, + sqlite3_file = capi.sqlite3_file; + //log("struct sqlite3_file", sqlite3_file.memberKeys()); + //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys()); + //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys()); + const installMethod = function callee(tgt, name, func){ + if(1===arguments.length){ + return (n,f)=>callee(tgt,n,f); + } + if(!callee.argcProxy){ + callee.argcProxy = function(func,sig){ + return function(...args){ + if(func.length!==arguments.length){ + toss("Argument mismatch. Native signature is:",sig); + } + return func.apply(this, args); + } + }; + callee.ondisposeRemoveFunc = function(){ + if(this.__ondispose){ + const who = this; + this.__ondispose.forEach( + (v)=>{ + if('number'===typeof v){ + try{wasm.uninstallFunction(v)} + catch(e){/*ignore*/} + }else{/*wasm function wrapper property*/ + delete who[v]; + } + } + ); + delete this.__ondispose; + } + }; + }/*static init*/ + const sigN = tgt.memberSignature(name), + memKey = tgt.memberKey(name); + //log("installMethod",tgt, name, sigN); + if(!tgt.__ondispose){ + T.assert(undefined === tgt.ondispose); + tgt.ondispose = [callee.ondisposeRemoveFunc]; + tgt.__ondispose = []; + } + const fProxy = callee.argcProxy(func, sigN); + const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); + tgt[memKey] = pFunc; + /** + ACHTUNG: function pointer IDs are from a different pool than + allocation IDs, starting at 1 and incrementing in steps of 1, + so if we set tgt[memKey] to those values, we'd very likely + later misinterpret them as plain old pointer addresses unless + unless we use some silly heuristic like "all values <5k are + presumably function pointers," or actually perform a function + lookup on every pointer to first see if it's a function. That + would likely work just fine, but would be kludgy. + + It turns out that "all values less than X are functions" is + essentially how it works in wasm: a function pointer is + reported to the client as its index into the + __indirect_function_table. + + So... once jaccwabyt can be told how to access the + function table, it could consider all pointer values less + than that table's size to be functions. As "real" pointer + values start much, much higher than the function table size, + that would likely work reasonably well. e.g. the object + pointer address for sqlite3's default VFS is (in this local + setup) 65104, whereas the function table has fewer than 600 + entries. + */ + const wrapperKey = '$'+memKey; + tgt[wrapperKey] = fProxy; + tgt.__ondispose.push(pFunc, wrapperKey); + //log("tgt.__ondispose =",tgt.__ondispose); + return (n,f)=>callee(tgt, n, f); + }/*installMethod*/; + + const installIOMethods = function instm(iom){ + (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type."); + if(!instm._requireFileArg){ + instm._requireFileArg = function(arg,methodName){ + arg = capi.sqlite3_file.resolveToInstance(arg); + if(!arg){ + err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file."); + } + return arg; + }; + instm._methods = { + // https://sqlite.org/c3ref/io_methods.html + xClose: /*i(P)*/function(f){ + /* int (*xClose)(sqlite3_file*) */ + log("xClose(",f,")"); + if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE; + f.dispose(/*noting that f has externally-owned memory*/); + return 0; + }, + xRead: /*i(Ppij)*/function(f,dest,n,offset){ + /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */ + log("xRead(",arguments,")"); + if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE; + wasm.heap8().fill(0, dest + offset, n); + return 0; + }, + xWrite: /*i(Ppij)*/function(f,dest,n,offset){ + /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */ + log("xWrite(",arguments,")"); + if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE; + return 0; + }, + xTruncate: /*i(Pj)*/function(f){ + /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */ + log("xTruncate(",arguments,")"); + if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE; + return 0; + }, + xSync: /*i(Pi)*/function(f){ + /* int (*xSync)(sqlite3_file*, int flags) */ + log("xSync(",arguments,")"); + if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE; + return 0; + }, + xFileSize: /*i(Pp)*/function(f,pSz){ + /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */ + log("xFileSize(",arguments,")"); + if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE; + wasm.setMemValue(pSz, 0/*file size*/); + return 0; + }, + xLock: /*i(Pi)*/function(f){ + /* int (*xLock)(sqlite3_file*, int) */ + log("xLock(",arguments,")"); + if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE; + return 0; + }, + xUnlock: /*i(Pi)*/function(f){ + /* int (*xUnlock)(sqlite3_file*, int) */ + log("xUnlock(",arguments,")"); + if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE; + return 0; + }, + xCheckReservedLock: /*i(Pp)*/function(){ + /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */ + log("xCheckReservedLock(",arguments,")"); + return 0; + }, + xFileControl: /*i(Pip)*/function(){ + /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */ + log("xFileControl(",arguments,")"); + return capi.SQLITE_NOTFOUND; + }, + xSectorSize: /*i(P)*/function(){ + /* int (*xSectorSize)(sqlite3_file*) */ + log("xSectorSize(",arguments,")"); + return 0/*???*/; + }, + xDeviceCharacteristics:/*i(P)*/function(){ + /* int (*xDeviceCharacteristics)(sqlite3_file*) */ + log("xDeviceCharacteristics(",arguments,")"); + return 0; + } + }; + }/*static init*/ + iom.$iVersion = 1; + Object.keys(instm._methods).forEach( + (k)=>installMethod(iom, k, instm._methods[k]) + ); + }/*installIOMethods()*/; + + const iom = new sqlite3_io_methods, sfile = new sqlite3_file; + const err = console.error.bind(console); + try { + const IOM = sqlite3_io_methods, S3F = sqlite3_file; + //log("iom proto",iom,iom.constructor.prototype); + //log("sfile",sfile,sfile.constructor.prototype); + T.assert(0===sfile.$pMethods).assert(iom.pointer > 0); + //log("iom",iom); + sfile.$pMethods = iom.pointer; + T.assert(iom.pointer === sfile.$pMethods) + .assert(IOM.resolveToInstance(iom)) + .assert(undefined ===IOM.resolveToInstance(sfile)) + .mustThrow(()=>IOM.resolveToInstance(0,true)) + .assert(S3F.resolveToInstance(sfile.pointer)) + .assert(undefined===S3F.resolveToInstance(iom)) + .assert(iom===IOM.resolveToInstance(sfile.$pMethods)); + T.assert(0===iom.$iVersion); + installIOMethods(iom); + T.assert(1===iom.$iVersion); + //log("iom.__ondispose",iom.__ondispose); + T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10); + }finally{ + iom.dispose(); + T.assert(undefined === iom.__ondispose); + } + + const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null)); + try { + const SB = sqlite3.StructBinder; + T.assert(dVfs instanceof SB.StructType) + .assert(dVfs.pointer) + .assert('sqlite3_vfs' === dVfs.structName) + .assert(!!dVfs.structInfo) + .assert(SB.StructType.hasExternalPointer(dVfs)) + .assert(dVfs.$iVersion>0) + .assert('number'===typeof dVfs.$zName) + .assert('number'===typeof dVfs.$xSleep) + .assert(wasm.functionEntry(dVfs.$xOpen)) + .assert(dVfs.memberIsString('zName')) + .assert(dVfs.memberIsString('$zName')) + .assert(!dVfs.memberIsString('pAppData')) + .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'), + /Invalid member type signature for C-string/) + .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/) + .assert('string' === typeof dVfs.memberToJsString('zName')) + .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName')) + ; + //log("Default VFS: @",dVfs.pointer); + Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){ + const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname], + addr = dVfs[mk], prefix = 'defaultVfs.'+mname; + if(1===mbr.signature.length){ + let sep = '?', val = undefined; + switch(mbr.signature[0]){ + // TODO: move this into an accessor, e.g. getPreferredValue(member) + case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break + case 'p': case 'P': sep = '@'; val = dVfs[mk]; break; + case 's': sep = '='; + val = dVfs.memberToJsString(mname); + break; + } + //log(prefix, sep, val); + }else{ + //log(prefix," = funcptr @",addr, wasm.functionEntry(addr)); + } + }); + }finally{ + dVfs.dispose(); + T.assert(undefined===dVfs.pointer); + } + }/*StructBinder part 2*/) + + //////////////////////////////////////////////////////////////////// + .t('sqlite3.wasm.pstack', function(sqlite3){ + const P = wasm.pstack; + const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError; + const stack = P.pointer; + T.assert(0===stack % 8 /* must be 8-byte aligned */); + try{ + const remaining = P.remaining; + T.assert(P.quota >= 4096) + .assert(remaining === P.quota) + .mustThrowMatching(()=>P.alloc(0), isAllocErr) + .mustThrowMatching(()=>P.alloc(-1), isAllocErr); + let p1 = P.alloc(12); + T.assert(p1 === stack - 16/*8-byte aligned*/) + .assert(P.pointer === p1); + let p2 = P.alloc(7); + T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/) + .mustThrowMatching(()=>P.alloc(remaining), isAllocErr) + .assert(24 === stack - p2) + .assert(P.pointer === p2); + let n = remaining - (stack - p2); + let p3 = P.alloc(n); + T.assert(p3 === stack-remaining) + .mustThrowMatching(()=>P.alloc(1), isAllocErr); + }finally{ + P.restore(stack); + } + + T.assert(P.pointer === stack); + try { + const [p1, p2, p3] = P.allocChunks(3,4); + T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/) + .assert(p2 === p1 + 4) + .assert(p3 === p2 + 4); + T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16), + (e)=>e instanceof sqlite3.WasmAllocError) + }finally{ + P.restore(stack); + } + + T.assert(P.pointer === stack); + try { + let [p1, p2, p3] = P.allocPtr(3,false); + let sPos = stack-16/*always rounded to multiple of 8*/; + T.assert(P.pointer === sPos) + .assert(p2 === p1 + 4) + .assert(p3 === p2 + 4); + [p1, p2, p3] = P.allocPtr(3); + T.assert(P.pointer === sPos-24/*3 x 8 bytes*/) + .assert(p2 === p1 + 8) + .assert(p3 === p2 + 8); + p1 = P.allocPtr(); + T.assert('number'===typeof p1); + }finally{ + P.restore(stack); + } + }/*pstack tests*/) + + //////////////////////////////////////////////////////////////////// + ;/*end of C/WASM utils checks*/ + + T.g('sqlite3_randomness()') + .t('To memory buffer', function(sqlite3){ + const stack = wasm.pstack.pointer; + try{ + const n = 520; + const p = wasm.pstack.alloc(n); + T.assert(0===wasm.getMemValue(p)) + .assert(0===wasm.getMemValue(p+n-1)); + T.assert(undefined === capi.sqlite3_randomness(n - 10, p)); + let j, check = 0; + const heap = wasm.heap8u(); + for(j = 0; j < 10 && 0===check; ++j){ + check += heap[p + j]; + } + T.assert(check > 0); + check = 0; + // Ensure that the trailing bytes were not modified... + for(j = n - 10; j < n && 0===check; ++j){ + check += heap[p + j]; + } + T.assert(0===check); + }finally{ + wasm.pstack.restore(stack); + } + }) + .t('To byte array', function(sqlite3){ + const ta = new Uint8Array(117); + let i, n = 0; + for(i=0; i0); + const t0 = new Uint8Array(0); + T.assert(t0 === capi.sqlite3_randomness(t0), + "0-length array is a special case"); + }) + ;/*end sqlite3_randomness() checks*/ + + //////////////////////////////////////////////////////////////////////// + T.g('sqlite3.oo1') + .t('Create db', function(sqlite3){ + const dbFile = '/tester1.db'; + wasm.sqlite3_wasm_vfs_unlink(0, dbFile); + const db = this.db = new sqlite3.oo1.DB(dbFile); + T.assert(Number.isInteger(db.pointer)) + .mustThrowMatching(()=>db.pointer=1, /read-only/) + .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)) + .assert('main'===db.dbName(0)) + .assert('string' === typeof db.dbVfsName()); + // Custom db error message handling via sqlite3_prepare_v2/v3() + let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null); + T.assert(capi.SQLITE_MISUSE === rc) + .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL")) + .assert(dbFile === db.dbFilename()) + .assert(!db.dbFilename('nope')); + }) + + //////////////////////////////////////////////////////////////////// + .t('DB.Stmt', function(S){ + let st = this.db.prepare( + new TextEncoder('utf-8').encode("select 3 as a") + ); + //debug("statement =",st); + try { + T.assert(Number.isInteger(st.pointer)) + .mustThrowMatching(()=>st.pointer=1, /read-only/) + .assert(1===this.db.openStatementCount()) + .assert(!st._mayGet) + .assert('a' === st.getColumnName(0)) + .assert(1===st.columnCount) + .assert(0===st.parameterCount) + .mustThrow(()=>st.bind(1,null)) + .assert(true===st.step()) + .assert(3 === st.get(0)) + .mustThrow(()=>st.get(1)) + .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER)) + .assert(3 === st.get(0,capi.SQLITE_INTEGER)) + .assert(3 === st.getInt(0)) + .assert('3' === st.get(0,capi.SQLITE_TEXT)) + .assert('3' === st.getString(0)) + .assert(3.0 === st.get(0,capi.SQLITE_FLOAT)) + .assert(3.0 === st.getFloat(0)) + .assert(3 === st.get({}).a) + .assert(3 === st.get([])[0]) + .assert(3 === st.getJSON(0)) + .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array) + .assert(1===st.get(0,capi.SQLITE_BLOB).length) + .assert(st.getBlob(0) instanceof Uint8Array) + .assert('3'.charCodeAt(0) === st.getBlob(0)[0]) + .assert(st._mayGet) + .assert(false===st.step()) + .assert(!st._mayGet) + ; + T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). + assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). + assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). + assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); + }finally{ + st.finalize(); + } + T.assert(!st.pointer) + .assert(0===this.db.openStatementCount()); + }) + + //////////////////////////////////////////////////////////////////////// + .t('sqlite3_js_...()', function(){ + const db = this.db; + if(1){ + const vfsList = capi.sqlite3_js_vfs_list(); + T.assert(vfsList.length>1); + T.assert('string'===typeof vfsList[0]); + //log("vfsList =",vfsList); + for(const v of vfsList){ + T.assert('string' === typeof v) + .assert(capi.sqlite3_vfs_find(v) > 0); + } + } + /** + Trivia: the magic db name ":memory:" does not actually use the + "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS + name. Instead, it uses the default VFS with an in-memory btree. + Thus this.db's VFS may not be memdb even though it's an in-memory + db. + */ + const pVfsMem = capi.sqlite3_vfs_find('memdb'), + pVfsDflt = capi.sqlite3_vfs_find(0), + pVfsDb = capi.sqlite3_js_db_vfs(db.pointer); + T.assert(pVfsMem > 0) + .assert(pVfsDflt > 0) + .assert(pVfsDb > 0) + .assert(pVfsMem !== pVfsDflt + /* memdb lives on top of the default vfs */) + .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem) + ; + /*const vMem = new capi.sqlite3_vfs(pVfsMem), + vDflt = new capi.sqlite3_vfs(pVfsDflt), + vDb = new capi.sqlite3_vfs(pVfsDb);*/ + const duv = capi.sqlite3_js_db_uses_vfs; + T.assert(pVfsDflt === duv(db.pointer, 0) + || pVfsMem === duv(db.pointer,0)) + .assert(!duv(db.pointer, "foo")) + ; + }/*sqlite3_js_...()*/) + + //////////////////////////////////////////////////////////////////// + .t('Table t', function(sqlite3){ + const db = this.db; + let list = []; + let rc = db.exec({ + sql:['CREATE TABLE t(a,b);', + // ^^^ using TEMP TABLE breaks the db export test + "INSERT INTO t(a,b) VALUES(1,2),(3,4),", + "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for + off-by-one bug in string-to-WASM conversion*/], + saveSql: list, + bind: [5,6] + }); + //debug("Exec'd SQL:", list); + T.assert(rc === db) + .assert(2 === list.length) + .assert('string'===typeof list[1]) + .assert(4===db.changes()); + if(wasm.bigIntEnabled){ + T.assert(4n===db.changes(false,true)); + } + let blob = db.selectValue("select b from t where a='blob'"); + T.assert(blob instanceof Uint8Array). + assert(0x68===blob[0] && 0x69===blob[1]); + blob = null; + let counter = 0, colNames = []; + list.length = 0; + db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{ + rowMode: 'object', + resultRows: list, + columnNames: colNames, + callback: function(row,stmt){ + ++counter; + T.assert((row.a%2 && row.a<6) || 'blob'===row.a); + } + }); + T.assert(2 === colNames.length) + .assert('a' === colNames[0]) + .assert(4 === counter) + .assert(4 === list.length); + list.length = 0; + db.exec("SELECT a a, b b FROM t",{ + rowMode: 'array', + callback: function(row,stmt){ + ++counter; + T.assert(Array.isArray(row)) + .assert((0===row[1]%2 && row[1]<7) + || (row[1] instanceof Uint8Array)); + } + }); + T.assert(8 === counter); + T.assert(Number.MIN_SAFE_INTEGER === + db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)). + assert(Number.MAX_SAFE_INTEGER === + db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER)); + if(wasm.bigIntEnabled && haveWasmCTests()){ + const mI = wasm.xCall('sqlite3_wasm_test_int64_max'); + const b = BigInt(Number.MAX_SAFE_INTEGER * 2); + T.assert(b === db.selectValue("SELECT "+b)). + assert(b === db.selectValue("SELECT ?", b)). + assert(mI == db.selectValue("SELECT $x", {$x:mI})); + }else{ + /* Curiously, the JS spec seems to be off by one with the definitions + of MIN/MAX_SAFE_INTEGER: + + https://github.com/emscripten-core/emscripten/issues/17391 */ + T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))). + mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1))); + } + + let st = db.prepare("update t set b=:b where a='blob'"); + try { + const ndx = st.getParamIndex(':b'); + T.assert(1===ndx); + st.bindAsBlob(ndx, "ima blob").reset(true); + } finally { + st.finalize(); + } + + try { + db.prepare("/*empty SQL*/"); + toss("Must not be reached."); + }catch(e){ + T.assert(e instanceof sqlite3.SQLite3Error) + .assert(0==e.message.indexOf('Cannot prepare empty')); + } + }) + + //////////////////////////////////////////////////////////////////////// + .t('selectArray/Object()', function(sqlite3){ + const db = this.db; + let rc = db.selectArray('select a, b from t where a=?', 5); + T.assert(Array.isArray(rc)) + .assert(2===rc.length) + .assert(5===rc[0] && 6===rc[1]); + rc = db.selectArray('select a, b from t where b=-1'); + T.assert(undefined === rc); + rc = db.selectObject('select a A, b b from t where b=?', 6); + T.assert(rc && 'object'===typeof rc) + .assert(5===rc.A) + .assert(6===rc.b); + rc = db.selectArray('select a, b from t where b=-1'); + T.assert(undefined === rc); + }) + + //////////////////////////////////////////////////////////////////////// + .t('sqlite3_js_db_export()', function(){ + const db = this.db; + const xp = capi.sqlite3_js_db_export(db.pointer); + T.assert(xp instanceof Uint8Array) + .assert(xp.byteLength>0) + .assert(0 === xp.byteLength % 512); + }/*sqlite3_js_db_export()*/) + + //////////////////////////////////////////////////////////////////// + .t('Scalar UDFs', function(sqlite3){ + const db = this.db; + db.createFunction("foo",(pCx,a,b)=>a+b); + T.assert(7===db.selectValue("select foo(3,4)")). + assert(5===db.selectValue("select foo(3,?)",2)). + assert(5===db.selectValue("select foo(?,?2)",[1,4])). + assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5})); + db.createFunction("bar", { + arity: -1, + xFunc: (pCx,...args)=>{ + let rc = 0; + for(const v of args) rc += v; + return rc; + } + }).createFunction({ + name: "asis", + xFunc: (pCx,arg)=>arg + }); + T.assert(0===db.selectValue("select bar()")). + assert(1===db.selectValue("select bar(1)")). + assert(3===db.selectValue("select bar(1,2)")). + assert(-1===db.selectValue("select bar(1,2,-4)")). + assert('hi' === db.selectValue("select asis('hi')")). + assert('hi' === db.selectValue("select ?",'hi')). + assert(null === db.selectValue("select null")). + assert(null === db.selectValue("select asis(null)")). + assert(1 === db.selectValue("select ?",1)). + assert(2 === db.selectValue("select ?",[2])). + assert(3 === db.selectValue("select $a",{$a:3})). + assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))). + assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)"))); + + let blobArg = new Uint8Array(2); + blobArg.set([0x68, 0x69], 0); + let blobRc = db.selectValue("select asis(?1)", blobArg); + T.assert(blobRc instanceof Uint8Array). + assert(2 === blobRc.length). + assert(0x68==blobRc[0] && 0x69==blobRc[1]); + blobRc = db.selectValue("select asis(X'6869')"); + T.assert(blobRc instanceof Uint8Array). + assert(2 === blobRc.length). + assert(0x68==blobRc[0] && 0x69==blobRc[1]); + + blobArg = new Int8Array(2); + blobArg.set([0x68, 0x69]); + //debug("blobArg=",blobArg); + blobRc = db.selectValue("select asis(?1)", blobArg); + T.assert(blobRc instanceof Uint8Array). + assert(2 === blobRc.length); + //debug("blobRc=",blobRc); + T.assert(0x68==blobRc[0] && 0x69==blobRc[1]); + }) + + //////////////////////////////////////////////////////////////////// + .t({ + name: 'Aggregate UDFs', + test: function(sqlite3){ + const db = this.db; + const sjac = capi.sqlite3_js_aggregate_context; + db.createFunction({ + name: 'summer', + xStep: (pCtx, n)=>{ + const ac = sjac(pCtx, 4); + wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32'); + }, + xFinal: (pCtx)=>{ + const ac = sjac(pCtx, 0); + return ac ? wasm.getMemValue(ac,'i32') : 0; + } + }); + let v = db.selectValue([ + "with cte(v) as (", + "select 3 union all select 5 union all select 7", + ") select summer(v), summer(v+1) from cte" + /* ------------------^^^^^^^^^^^ ensures that we're handling + sqlite3_aggregate_context() properly. */ + ]); + T.assert(15===v); + T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"), + /wrong number of arguments/); + + db.createFunction({ + name: 'summerN', + arity: -1, + xStep: (pCtx, ...args)=>{ + const ac = sjac(pCtx, 4); + let sum = wasm.getMemValue(ac, 'i32'); + for(const v of args) sum += Number(v); + wasm.setMemValue(ac, sum, 'i32'); + }, + xFinal: (pCtx)=>{ + const ac = sjac(pCtx, 0); + capi.sqlite3_result_int( pCtx, ac ? wasm.getMemValue(ac,'i32') : 0 ); + // xFinal() may either return its value directly or call + // sqlite3_result_xyz() and return undefined. Both are + // functionally equivalent. + } + }); + T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)')); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{ + xFunc: ()=>{}, xStep: ()=>{} + }); + }, /scalar or aggregate\?/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xStep: ()=>{}}); + }, /Missing xFinal/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xFinal: ()=>{}}); + }, /Missing xStep/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{}); + }, /Missing function-type properties/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'}); + }, /xDestroy property must be a function/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'}); + }, /Invalid value for pApp/); + } + }/*aggregate UDFs*/) + + //////////////////////////////////////////////////////////////////////// + .t({ + name: 'Aggregate UDFs (64-bit)', + predicate: ()=>wasm.bigIntEnabled, + test: function(sqlite3){ + const db = this.db; + const sjac = capi.sqlite3_js_aggregate_context; + db.createFunction({ + name: 'summer64', + xStep: (pCtx, n)=>{ + const ac = sjac(pCtx, 8); + wasm.setMemValue(ac, wasm.getMemValue(ac,'i64') + BigInt(n), 'i64'); + }, + xFinal: (pCtx)=>{ + const ac = sjac(pCtx, 0); + return ac ? wasm.getMemValue(ac,'i64') : 0n; + } + }); + let v = db.selectValue([ + "with cte(v) as (", + "select 9007199254740991 union all select 1 union all select 2", + ") select summer64(v), summer64(v+1) from cte" + ]); + T.assert(9007199254740994n===v); + } + }/*aggregate UDFs*/) + + //////////////////////////////////////////////////////////////////// + .t({ + name: 'Window UDFs', + test: function(){ + /* Example window function, table, and results taken from: + https://sqlite.org/windowfunctions.html#udfwinfunc */ + const db = this.db; + const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n); + const xValueFinal = (pCtx)=>{ + const ac = sjac(pCtx, 0); + return ac ? wasm.getMemValue(ac,'i32') : 0; + }; + const xStepInverse = (pCtx, n)=>{ + const ac = sjac(pCtx); + wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32'); + }; + db.createFunction({ + name: 'winsumint', + xStep: (pCtx, n)=>xStepInverse(pCtx, n), + xInverse: (pCtx, n)=>xStepInverse(pCtx, -n), + xFinal: xValueFinal, + xValue: xValueFinal + }); + db.exec([ + "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", + "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" + ]); + let rc = db.exec({ + returnValue: 'resultRows', + sql:[ + "SELECT x, winsumint(y) OVER (", + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", + ") AS sum_y ", + "FROM twin ORDER BY x;" + ] + }); + T.assert(Array.isArray(rc)) + .assert(5 === rc.length); + let count = 0; + for(const row of rc){ + switch(++count){ + case 1: T.assert('a'===row[0] && 9===row[1]); break; + case 2: T.assert('b'===row[0] && 12===row[1]); break; + case 3: T.assert('c'===row[0] && 16===row[1]); break; + case 4: T.assert('d'===row[0] && 12===row[1]); break; + case 5: T.assert('e'===row[0] && 9===row[1]); break; + default: toss("Too many rows to window function."); + } + } + const resultRows = []; + rc = db.exec({ + resultRows, + returnValue: 'resultRows', + sql:[ + "SELECT x, winsumint(y) OVER (", + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", + ") AS sum_y ", + "FROM twin ORDER BY x;" + ] + }); + T.assert(rc === resultRows) + .assert(5 === rc.length); + + rc = db.exec({ + returnValue: 'saveSql', + sql: "select 1; select 2; -- empty\n; select 3" + }); + T.assert(Array.isArray(rc)) + .assert(3===rc.length) + .assert('select 1;' === rc[0]) + .assert('select 2;' === rc[1]) + .assert('-- empty\n; select 3' === rc[2] + /* Strange but true. */); + + T.mustThrowMatching(()=>{ + db.exec({sql:'', returnValue: 'nope'}); + }, /^Invalid returnValue/); + + db.exec("DROP TABLE twin"); + } + }/*window UDFs*/) + + //////////////////////////////////////////////////////////////////// + .t("ATTACH", function(){ + const db = this.db; + const resultRows = []; + db.exec({ + sql:new TextEncoder('utf-8').encode([ + // ^^^ testing string-vs-typedarray handling in exec() + "attach 'session' as foo;", + "create table foo.bar(a);", + "insert into foo.bar(a) values(1),(2),(3);", + "select a from foo.bar order by a;" + ].join('')), + rowMode: 0, + resultRows + }); + T.assert(3===resultRows.length) + .assert(2===resultRows[1]); + T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a')); + let colCount = 0, rowCount = 0; + const execCallback = function(pVoid, nCols, aVals, aNames){ + colCount = nCols; + ++rowCount; + T.assert(2===aVals.length) + .assert(2===aNames.length) + .assert(+(aVals[1]) === 2 * +(aVals[0])); + }; + let rc = capi.sqlite3_exec( + db.pointer, "select a, a*2 from foo.bar", execCallback, + 0, 0 + ); + T.assert(0===rc).assert(3===rowCount).assert(2===colCount); + rc = capi.sqlite3_exec( + db.pointer, "select a from foo.bar", ()=>{ + tossQuietly("Testing throwing from exec() callback."); + }, 0, 0 + ); + T.assert(capi.SQLITE_ABORT === rc); + db.exec("detach foo"); + T.mustThrow(()=>db.exec("select * from foo.bar")); + }) + + //////////////////////////////////////////////////////////////////// + .t({ + name: 'C-side WASM tests (if compiled in)', + predicate: haveWasmCTests, + test: function(){ + const w = wasm, db = this.db; + const stack = w.scopedAllocPush(); + let ptrInt; + const origValue = 512; + const ptrValType = 'i32'; + try{ + ptrInt = w.scopedAlloc(4); + w.setMemValue(ptrInt,origValue, ptrValType); + const cf = w.xGet('sqlite3_wasm_test_intptr'); + const oldPtrInt = ptrInt; + //log('ptrInt',ptrInt); + //log('getMemValue(ptrInt)',w.getMemValue(ptrInt)); + T.assert(origValue === w.getMemValue(ptrInt, ptrValType)); + const rc = cf(ptrInt); + //log('cf(ptrInt)',rc); + //log('ptrInt',ptrInt); + //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType)); + T.assert(2*origValue === rc). + assert(rc === w.getMemValue(ptrInt,ptrValType)). + assert(oldPtrInt === ptrInt); + const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/; + const o64 = 0x010203040506/*>32-bit integer*/; + const ptrType64 = 'i64'; + if(w.bigIntEnabled){ + w.setMemValue(pi64, o64, ptrType64); + //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64); + const v64 = ()=>w.getMemValue(pi64,ptrType64) + //log("getMemValue(pi64)",v64()); + T.assert(v64() == o64); + //T.assert(o64 === w.getMemValue(pi64, ptrType64)); + const cf64w = w.xGet('sqlite3_wasm_test_int64ptr'); + cf64w(pi64); + //log("getMemValue(pi64)",v64()); + T.assert(v64() == BigInt(2 * o64)); + cf64w(pi64); + T.assert(v64() == BigInt(4 * o64)); + + const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2'); + T.assert(BigInt(2 * o64) === + biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError + in the call :/ */)); + + const pMin = w.scopedAlloc(16); + const pMax = pMin + 8; + const g64 = (p)=>w.getMemValue(p,ptrType64); + w.setMemValue(pMin, 0, ptrType64); + w.setMemValue(pMax, 0, ptrType64); + const minMaxI64 = [ + w.xCall('sqlite3_wasm_test_int64_min'), + w.xCall('sqlite3_wasm_test_int64_max') + ]; + T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)). + assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER)); + //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]); + w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax); + T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch"). + assert(g64(pMax) === minMaxI64[1], "int64 mismatch"); + //log("pMin",g64(pMin), "pMax",g64(pMax)); + w.setMemValue(pMin, minMaxI64[0], ptrType64); + T.assert(g64(pMin) === minMaxI64[0]). + assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))). + assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax))); + const rxRange = /too big/; + T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))}, + rxRange). + mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))}, + (e)=>rxRange.test(e.message)); + }else{ + log("No BigInt support. Skipping related tests."); + log("\"The problem\" here is that we can manipulate, at the byte level,", + "heap memory to set 64-bit values, but we can't get those values", + "back into JS because of the lack of 64-bit integer support."); + } + }finally{ + const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1); + //log("x=",x,"y=",y,"z=",z); // just looking at the alignment + w.scopedAllocPop(stack); + } + } + }/* jaccwabyt-specific tests */) + + .t('Close db', function(){ + T.assert(this.db).assert(Number.isInteger(this.db.pointer)); + wasm.exports.sqlite3_wasm_db_reset(this.db.pointer); + this.db.close(); + T.assert(!this.db.pointer); + }) + ;/* end of oo1 checks */ + + //////////////////////////////////////////////////////////////////////// + T.g('kvvfs') + .t('kvvfs sanity checks', function(sqlite3){ + if(isWorker()){ + T.assert( + !capi.sqlite3_vfs_find('kvvfs'), + "Expecting kvvfs to be unregistered." + ); + log("kvvfs is (correctly) unavailable in a Worker."); + return; + } + const filename = 'session'; + const pVfs = capi.sqlite3_vfs_find('kvvfs'); + T.assert(pVfs); + const JDb = sqlite3.oo1.JsStorageDb; + const unlink = ()=>JDb.clearStorage(filename); + unlink(); + let db = new JDb(filename); + try { + db.exec([ + 'create table kvvfs(a);', + 'insert into kvvfs(a) values(1),(2),(3)' + ]); + T.assert(3 === db.selectValue('select count(*) from kvvfs')); + db.close(); + db = new JDb(filename); + db.exec('insert into kvvfs(a) values(4),(5),(6)'); + T.assert(6 === db.selectValue('select count(*) from kvvfs')); + }finally{ + db.close(); + unlink(); + } + }/*kvvfs sanity checks*/) + ;/* end kvvfs tests */ + + //////////////////////////////////////////////////////////////////////// + T.g('OPFS (Worker thread only and only in supported browsers)', + (sqlite3)=>{return !!sqlite3.opfs}) + .t({ + name: 'OPFS sanity checks', + test: async function(sqlite3){ + const opfs = sqlite3.opfs; + const filename = 'sqlite3-tester1.db'; + const pVfs = capi.sqlite3_vfs_find('opfs'); + T.assert(pVfs); + const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn); + unlink(); + let db = new opfs.OpfsDb(filename); + try { + db.exec([ + 'create table p(a);', + 'insert into p(a) values(1),(2),(3)' + ]); + T.assert(3 === db.selectValue('select count(*) from p')); + db.close(); + db = new opfs.OpfsDb(filename); + db.exec('insert into p(a) values(4),(5),(6)'); + T.assert(6 === db.selectValue('select count(*) from p')); + }finally{ + db.close(); + unlink(); + } + + if(1){ + // Sanity-test sqlite3_wasm_vfs_create_file()... + const fSize = 1379; + let sh; + try{ + T.assert(!(await opfs.entryExists(filename))); + let rc = wasm.sqlite3_wasm_vfs_create_file( + pVfs, filename, null, fSize + ); + T.assert(0===rc) + .assert(await opfs.entryExists(filename)); + const fh = await opfs.rootDirectory.getFileHandle(filename); + sh = await fh.createSyncAccessHandle(); + T.assert(fSize === await sh.getSize()); + }finally{ + if(sh) await sh.close(); + unlink(); + } + } + + // Some sanity checks of the opfs utility functions... + const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); + const aDir = testDir+'/test/dir'; + T.assert(await opfs.mkdir(aDir), "mkdir failed") + .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists") + .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)") + .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed") + .assert(!(await opfs.unlink(testDir+'/test/dir')), + "delete 2b should have failed (dir already deleted)") + .assert((await opfs.unlink(testDir, true)), "delete 3 failed") + .assert(!(await opfs.entryExists(testDir)), + "entryExists(",testDir,") should have failed"); + } + }/*OPFS sanity checks*/) + ;/* end OPFS tests */ + + //////////////////////////////////////////////////////////////////////// + log("Loading and initializing sqlite3 WASM module..."); + if(!isUIThread()){ + /* + If sqlite3.js is in a directory other than this script, in order + to get sqlite3.js to resolve sqlite3.wasm properly, we have to + explicitly tell it where sqlite3.js is being loaded from. We do + that by passing the `sqlite3.dir=theDirName` URL argument to + _this_ script. That URL argument will be seen by the JS/WASM + loader and it will adjust the sqlite3.wasm path accordingly. If + sqlite3.js/.wasm are in the same directory as this script then + that's not needed. + + URL arguments passed as part of the filename via importScripts() + are simply lost, and such scripts see the self.location of + _this_ script. + */ + let sqlite3Js = 'sqlite3.js'; + const urlParams = new URL(self.location.href).searchParams; + if(urlParams.has('sqlite3.dir')){ + sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; + } + importScripts(sqlite3Js); + } + self.sqlite3InitModule({ + print: log, + printErr: error + }).then(function(sqlite3){ + //console.log('sqlite3 =',sqlite3); + log("Done initializing WASM/JS bits. Running tests..."); + capi = sqlite3.capi; + wasm = sqlite3.wasm; + log("sqlite3 version:",capi.sqlite3_libversion(), + capi.sqlite3_sourceid()); + if(wasm.bigIntEnabled){ + log("BigInt/int64 support is enabled."); + }else{ + logClass('warning',"BigInt/int64 support is disabled."); + } + if(haveWasmCTests()){ + log("sqlite3_wasm_test_...() APIs are available."); + }else{ + logClass('warning',"sqlite3_wasm_test_...() APIs unavailable."); + } + TestUtil.runTests(sqlite3); + }); +})(); diff --git a/ext/wasm/version-info.c b/ext/wasm/version-info.c new file mode 100644 index 000000000..62fcd633c --- /dev/null +++ b/ext/wasm/version-info.c @@ -0,0 +1,106 @@ +/* +** 2022-10-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 simply outputs sqlite3 version information in JSON form, +** intended for embedding in the sqlite3 JS API build. +*/ +#ifdef TEST_VERSION +/*3029003 3039012*/ +#define SQLITE_VERSION "X.Y.Z" +#define SQLITE_VERSION_NUMBER TEST_VERSION +#define SQLITE_SOURCE_ID "dummy" +#else +#include "sqlite3.h" +#endif +#include +#include +static void usage(const char *zAppName){ + puts("Emits version info about the sqlite3 it is built against."); + printf("Usage: %s [--quote] --INFO-FLAG:\n\n", zAppName); + puts(" --version Emit SQLITE_VERSION (3.X.Y)"); + puts(" --version-number Emit SQLITE_VERSION_NUMBER (30XXYYZZ)"); + puts(" --download-version Emit /download.html version number (3XXYYZZ)"); + puts(" --source-id Emit SQLITE_SOURCE_ID"); + puts(" --json Emit all info in JSON form"); + puts("\nThe non-JSON formats may be modified by:\n"); + puts(" --quote Add double quotes around output."); +} + +int main(int argc, char const * const * argv){ + int fJson = 0; + int fVersion = 0; + int fVersionNumber = 0; + int fDlVersion = 0; + int dlVersion = 0; + int fSourceInfo = 0; + int fQuote = 0; + int nFlags = 0; + int i; + + for( i = 1; i < argc; ++i ){ + const char * zArg = argv[i]; + while('-'==*zArg) ++zArg; + if( 0==strcmp("version", zArg) ){ + fVersion = 1; + }else if( 0==strcmp("version-number", zArg) ){ + fVersionNumber = 1; + }else if( 0==strcmp("download-version", zArg) ){ + fDlVersion = 1; + }else if( 0==strcmp("source-id", zArg) ){ + fSourceInfo = 1; + }else if( 0==strcmp("json", zArg) ){ + fJson = 1; + }else if( 0==strcmp("quote", zArg) ){ + fQuote = 1; + --nFlags; + }else{ + printf("Unhandled flag: %s\n", argv[i]); + usage(argv[0]); + return 1; + } + ++nFlags; + } + + if( 0==nFlags ) fJson = 1; + + { + const int v = SQLITE_VERSION_NUMBER; + int ver[4] = {0,0,0,0}; + ver[0] = (v / 1000000) * 1000000; + ver[1] = v % 1000000 / 100 * 1000; + ver[2] = v % 100 * 100; + dlVersion = ver[0] + ver[1] + ver[2] + ver[3]; + } + if( fJson ){ + printf("{\"libVersion\": \"%s\", " + "\"libVersionNumber\": %d, " + "\"sourceId\": \"%s\"," + "\"downloadVersion\": %d}"/*missing newline is intentional*/, + SQLITE_VERSION, + SQLITE_VERSION_NUMBER, + SQLITE_SOURCE_ID, + dlVersion); + }else{ + if(fQuote) printf("%c", '"'); + if( fVersion ){ + printf("%s", SQLITE_VERSION); + }else if( fVersionNumber ){ + printf("%d", SQLITE_VERSION_NUMBER); + }else if( fSourceInfo ){ + printf("%s", SQLITE_SOURCE_ID); + }else if( fDlVersion ){ + printf("%d", dlVersion); + } + if(fQuote) printf("%c", '"'); + puts(""); + } + return 0; +} diff --git a/ext/wasm/wasmfs.make b/ext/wasm/wasmfs.make new file mode 100644 index 000000000..81b41870c --- /dev/null +++ b/ext/wasm/wasmfs.make @@ -0,0 +1,113 @@ +#!/usr/bin/make +#^^^^ help emacs select makefile mode +# +# This is a sub-make for building a standalone wasmfs-based +# sqlite3.wasm. It is intended to be "include"d from the main +# GNUMakefile. +######################################################################## +MAKEFILE.wasmfs := $(lastword $(MAKEFILE_LIST)) + +# Maintenance reminder: these particular files cannot be built into a +# subdirectory because loading of the auxiliary +# sqlite3-wasmfs.worker.js file it creates fails if sqlite3-wasmfs.js +# is loaded from any directory other than the one in which the +# containing HTML lives. Similarly, they cannot be loaded from a +# Worker to an Emscripten quirk regarding loading nested Workers. +dir.wasmfs := $(dir.wasm) +sqlite3-wasmfs.js := $(dir.wasmfs)/sqlite3-wasmfs.js +sqlite3-wasmfs.wasm := $(dir.wasmfs)/sqlite3-wasmfs.wasm + +CLEAN_FILES += $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.wasm) \ + $(subst .js,.worker.js,$(sqlite3-wasmfs.js)) + +######################################################################## +# emcc flags for .c/.o. +sqlite3-wasmfs.cflags := +sqlite3-wasmfs.cflags += -std=c99 -fPIC +sqlite3-wasmfs.cflags += -pthread +sqlite3-wasmfs.cflags += $(cflags.common) +sqlite3-wasmfs.cflags += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS + +######################################################################## +# emcc flags specific to building the final .js/.wasm file... +sqlite3-wasmfs.jsflags := -fPIC +sqlite3-wasmfs.jsflags += --no-entry +sqlite3-wasmfs.jsflags += --minify 0 +sqlite3-wasmfs.jsflags += -sMODULARIZE +sqlite3-wasmfs.jsflags += -sSTRICT_JS +sqlite3-wasmfs.jsflags += -sDYNAMIC_EXECUTION=0 +sqlite3-wasmfs.jsflags += -sNO_POLYFILL +sqlite3-wasmfs.jsflags += -sEXPORTED_FUNCTIONS=@$(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) +sqlite3-wasmfs.jsflags += -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory,allocateUTF8OnStack + # wasmMemory ==> for -sIMPORTED_MEMORY + # allocateUTF8OnStack ==> wasmfs internals +sqlite3-wasmfs.jsflags += -sUSE_CLOSURE_COMPILER=0 +sqlite3-wasmfs.jsflags += -sIMPORTED_MEMORY +#sqlite3-wasmfs.jsflags += -sINITIAL_MEMORY=13107200 +#sqlite3-wasmfs.jsflags += -sTOTAL_STACK=4194304 +sqlite3-wasmfs.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) +sqlite3-wasmfs.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. +#sqlite3-wasmfs.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API +# Perhaps the wasmfs build doesn't? +#sqlite3-wasmfs.jsflags += -sABORTING_MALLOC +sqlite3-wasmfs.jsflags += -sALLOW_TABLE_GROWTH +sqlite3-wasmfs.jsflags += -Wno-limited-postlink-optimizations +# ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag. +sqlite3-wasmfs.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=0 +sqlite3-wasmfs.jsflags += -sLLD_REPORT_UNDEFINED +#sqlite3-wasmfs.jsflags += --import-undefined +sqlite3-wasmfs.jsflags += -sMEMORY64=0 +sqlite3-wasmfs.jsflags += -sINITIAL_MEMORY=128450560 +# ^^^^ 64MB is not enough for WASMFS/OPFS test runs using batch-runner.js +sqlite3-wasmfs.fsflags := -pthread -sWASMFS -sPTHREAD_POOL_SIZE=2 -sENVIRONMENT=web,worker +# -sPTHREAD_POOL_SIZE values of 2 or higher trigger that bug. +sqlite3-wasmfs.jsflags += $(sqlite3-wasmfs.fsflags) +#sqlite3-wasmfs.jsflags += -sALLOW_MEMORY_GROWTH +#^^^ using ALLOW_MEMORY_GROWTH produces a warning from emcc: +# USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, +# see https://github.com/WebAssembly/design/issues/1271 [-Wpthreads-mem-growth] +sqlite3-wasmfs.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) +$(eval $(call call-make-pre-js,sqlite3-wasmfs)) +sqlite3-wasmfs.jsflags += $(pre-post-common.flags) $(pre-post-sqlite3-wasmfs.flags) +$(sqlite3-wasmfs.js): $(sqlite3-wasm.c) \ + $(EXPORTED_FUNCTIONS.api) $(MAKEFILE) $(MAKEFILE.wasmfs) \ + $(pre-post-sqlite3-wasmfs.deps) + @echo "Building $@ ..." + $(emcc.bin) -o $@ $(emcc_opt_full) $(emcc.flags) \ + $(sqlite3-wasmfs.cflags) $(sqlite3-wasmfs.jsflags) \ + $(sqlite3-wasm.c) + chmod -x $(sqlite3-wasmfs.wasm) + $(maybe-wasm-strip) $(sqlite3-wasmfs.wasm) + @ls -la $@ $(sqlite3-wasmfs.wasm) +$(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.js) +wasmfs: $(sqlite3-wasmfs.js) +all: wasmfs + +######################################################################## +# speedtest1 for wasmfs. +speedtest1-wasmfs.js := $(dir.wasmfs)/speedtest1-wasmfs.js +speedtest1-wasmfs.wasm := $(subst .js,.wasm,$(speedtest1-wasmfs.js)) +speedtest1-wasmfs.eflags := $(sqlite3-wasmfs.fsflags) +speedtest1-wasmfs.eflags += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS +speedtest1-wasmfs.eflags += -sALLOW_MEMORY_GROWTH=0 +speedtest1-wasmfs.eflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) +$(eval $(call call-make-pre-js,speedtest1-wasmfs)) +$(speedtest1-wasmfs.js): $(speedtest1.cses) $(sqlite3-wasmfs.js) \ + $(MAKEFILE) $(MAKEFILE.wasmfs) \ + $(pre-post-speedtest1-wasmfs.deps) \ + $(EXPORTED_FUNCTIONS.speedtest1) + @echo "Building $@ ..." + $(emcc.bin) \ + $(speedtest1-wasmfs.eflags) $(speedtest1-common.eflags) \ + $(pre-post-speedtest1-wasmfs.flags) \ + $(speedtest1.cflags) \ + $(sqlite3-wasmfs.cflags) \ + -o $@ $(speedtest1.cses) -lm + $(maybe-wasm-strip) $(speedtest1-wasmfs.wasm) + ls -la $@ $(speedtest1-wasmfs.wasm) + +speedtest1: $(speedtest1-wasmfs.js) +CLEAN_FILES += $(speedtest1-wasmfs.js) $(speedtest1-wasmfs.wasm) \ + $(subst .js,.worker.js,$(speedtest1-wasmfs.js)) +# end speedtest1.js +######################################################################## diff --git a/magic.txt b/magic.txt index 65622687c..f52a7a90e 100644 --- a/magic.txt +++ b/magic.txt @@ -9,18 +9,18 @@ # PRAGMA application_id = INTEGER; # # INTEGER can be any signed 32-bit integer. That integer is written as -# a 4-byte big-endian integer into offset 68 of the database header. +# a 4-byte big-endian integer into offset 68 of the database header. # # The Monotone application used "PRAGMA user_version=1598903374;" to set # its identifier long before "PRAGMA application_id" became available. # The user_version is very similar to application_id except that it is -# stored at offset 68 instead of offset 60. The application_id pragma +# stored at offset 60 instead of offset 68. The application_id pragma # is preferred. The rule using offset 60 for Monotone is for historical # compatibility only. # 0 string =SQLite\ format\ 3 ->68 belong =0x0f055112 Fossil checkout - ->68 belong =0x0f055113 Fossil global configuration - +>68 belong =0x0f055112 Fossil checkout - +>68 belong =0x0f055113 Fossil global configuration - >68 belong =0x0f055111 Fossil repository - >68 belong =0x42654462 Bentley Systems BeSQLite Database - >68 belong =0x42654c6e Bentley Systems Localization File - @@ -29,4 +29,5 @@ >68 belong =0x47503130 OGC GeoPackage version 1.0 file - >68 belong =0x45737269 Esri Spatially-Enabled Database - >68 belong =0x4d504258 MBTiles tileset - +>68 belong =0x6a035744 TeXnicard card database >0 string =SQLite SQLite3 database diff --git a/main.mk b/main.mk index 35e42d6ca..76c62fcec 100644 --- a/main.mk +++ b/main.mk @@ -68,7 +68,7 @@ LIBOBJ+= vdbe.o parse.o \ main.o malloc.o mem0.o mem1.o mem2.o mem3.o mem5.o \ memdb.o memjournal.o \ mutex.o mutex_noop.o mutex_unix.o mutex_w32.o \ - notify.o opcodes.o os.o os_unix.o os_win.o \ + notify.o opcodes.o os.o os_kv.o os_unix.o os_win.o \ pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \ random.o resolve.o rowset.o rtree.o \ select.o sqlite3rbu.o status.o stmt.o \ @@ -134,6 +134,7 @@ SRC = \ $(TOP)/src/os.h \ $(TOP)/src/os_common.h \ $(TOP)/src/os_setup.h \ + $(TOP)/src/os_kv.c \ $(TOP)/src/os_unix.c \ $(TOP)/src/os_win.c \ $(TOP)/src/os_win.h \ @@ -389,7 +390,10 @@ TESTSRC += \ $(TOP)/ext/fts5/fts5_tcl.c \ $(TOP)/ext/fts5/fts5_test_mi.c \ $(TOP)/ext/fts5/fts5_test_tok.c \ - $(TOP)/ext/rtree/test_rtreedoc.c + $(TOP)/ext/rtree/test_rtreedoc.c \ + $(TOP)/ext/recover/sqlite3recover.c \ + $(TOP)/ext/recover/dbdata.c \ + $(TOP)/ext/recover/test_recover.c #TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c @@ -411,6 +415,7 @@ TESTSRC2 = \ $(TOP)/src/main.c \ $(TOP)/src/mem5.c \ $(TOP)/src/os.c \ + $(TOP)/src/os_kv.c \ $(TOP)/src/os_unix.c \ $(TOP)/src/os_win.c \ $(TOP)/src/pager.c \ @@ -539,7 +544,9 @@ SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC -FUZZCHECK_OPT = -DSQLITE_ENABLE_MEMSYS5 +FUZZCHECK_OPT += -I$(TOP)/test +FUZZCHECK_OPT += -I$(TOP)/ext/recover +FUZZCHECK_OPT += -DSQLITE_ENABLE_MEMSYS5 FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4 @@ -549,7 +556,10 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB FUZZSRC += $(TOP)/test/fuzzcheck.c FUZZSRC += $(TOP)/test/ossfuzz.c +FUZZSRC += $(TOP)/test/vt02.c FUZZSRC += $(TOP)/test/fuzzinvariants.c +FUZZSRC += $(TOP)/ext/recover/dbdata.c +FUZZSRC += $(TOP)/ext/recover/sqlite3recover.c DBFUZZ_OPT = KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ ST_OPT = -DSQLITE_THREADSAFE=0 @@ -608,7 +618,7 @@ dbfuzz2$(EXE): $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h $(TCCX) -I. -g -O0 -DSTANDALONE -o dbfuzz2$(EXE) \ $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c $(TLIBS) $(THREADLIB) -fuzzcheck$(EXE): $(FUZZSRC) sqlite3.c sqlite3.h +fuzzcheck$(EXE): $(FUZZSRC) sqlite3.c sqlite3.h $(FUZZDEP) $(TCCX) -o fuzzcheck$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \ $(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB) @@ -759,7 +769,9 @@ SHELL_SRC = \ $(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/misc/memtrace.c \ - $(TOP)/ext/misc/dbdata.c \ + $(TOP)/ext/recover/dbdata.c \ + $(TOP)/ext/recover/sqlite3recover.c \ + $(TOP)/ext/recover/sqlite3recover.h \ $(TOP)/src/test_windirent.c shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl @@ -958,6 +970,12 @@ valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionf tcltest: ./testfixture$(EXE) ./testfixture$(EXE) $(TOP)/test/veryquick.test $(TESTOPTS) +# Runs all the same tests cases as the "tcltest" target but uses +# the testrunner.tcl script to run them in multiple cores +# concurrently. +testrunner: testfixture$(EXE) + ./testfixture$(EXE) $(TOP)/test/testrunner.tcl + # A very quick test using only testfixture and omitting all the slower # tests. Designed to run in under 3 minutes on a workstation. # @@ -968,6 +986,7 @@ quicktest: ./testfixture$(EXE) # and fuzz tests, and sqlite3_analyzer and sqldiff tests. test: fuzztest sourcetest $(TESTPROGS) tcltest + # Run a test using valgrind. This can take a really long time # because valgrind is so much slower than a native machine. # diff --git a/manifest b/manifest index 229149d08..1d8427952 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.39.4 -D 2022-09-29T15:55:41.801 +C Version\s3.40.1 +D 2022-12-28T14:03:47.023 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 4e1cacdce04497567464116cf5798674e8a21e15b4d44838ceaa8a49c527028c +F Makefile.in 78e4c4916f2c3993a8a454018745ff02094a8029d449d0c22db98f1cf8260420 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 -F Makefile.msc de7cb3e095ce2fdc33513ccd76ebdaeda1483d0ddab0410fe65cbdeadd4c0ee1 +F Makefile.msc e7a564ceec71f0d9666031d5638cf0d4f88b050b44e8df5d32125137cd259ac0 F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e -F VERSION ad74f073c8910c112b5db75d9a7be486305a91d8a352d53f2a4c7dda9e9e5efa +F VERSION abec048a7ac74ad5559a336fe048d624207ac1e21be7be4356f3fa6b1ad4bec8 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -19,23 +19,22 @@ F autoconf/Makefile.msc 8401a514e4e70add3c6448348ae31322d5cb7db427b05a20828f943c F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 -F autoconf/tea/Makefile.in b438a7020446c8a8156e8d97c8914a04833da6fd +F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac ea61e07340d97e4a79a081f0b8977198a6073edd060738dbb3ae5cb8d5e96f1c +F autoconf/tea/configure.ac 2bbcc0f449361c5dfbcfc51ebadc5118e4c4e8c670674a10b6443356ecdb493e F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 -F autoconf/tea/pkgIndex.tcl.in 3ef61715cf1c7bdcff56947ffadb26bc991ca39d +F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce F autoconf/tea/tclconfig/install-sh bdd5e293591621ae60d9824d86a4b1c5f22c3d00 -F autoconf/tea/tclconfig/tcl.m4 66ddf0a5d5e4b1d29bff472c0985fd7fa89d0fb5 -F autoconf/tea/win/makefile.vc a5ff708245260c2794c6aaa0151efe5403d5896566eaf096747be0d9075284e4 +F autoconf/tea/tclconfig/tcl.m4 debe13280bd5a9d76dc34e7919cd9ed3a1408c7320400900357128c2d1abb723 +F autoconf/tea/win/makefile.vc 2c478a9a962e48b2bf9062734e04d7c63c556e217095419173f9d7938d7d78f7 F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea00342d1a1a7eaa19cb F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 -F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 01ce1a8ad8aa8450bc4e7bef937588a116ed724aa49802537ec9399ee18a8488 x -F configure.ac 3ef6eeff4387585bfcab76b0c3f6e15a0618587bb90245dd5d44e4378141bb35 +F configure 291769d08ff3a3091f420ccf7d2e1010153f778d6372217d4cc6fcbd2d14bb2d x +F configure.ac 48dc6bfee293eef05910faf085760f2fd79b680aa47b50e8e6a22ca40bb026bb F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f @@ -51,27 +50,10 @@ F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test 3c642a4e7bbb14f21ddab595436fb465a4733f47a0fe5b2855e1d5ff900ef08e -F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0ff5d9cdfac204 +F ext/expert/expert1.test 95b00567ce0775126a1b788af2d055255014714ecfddc97913864d2f9266e583 +F ext/expert/sqlite3expert.c a912efbad597eafdb0ce934ebc11039f3190b2d479685d89184e107f65d856e1 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 -F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3 -F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api 540b9dec63a3a62a256e2f030827848a92e9b9d9b6fa5c0188295a4a1c5382cd -F ext/fiddle/EXPORTED_RUNTIME_METHODS b831017ba67ba993b34a27400cef2f6095bd6789c0fc4eba7e7a251c207be31c -F ext/fiddle/Makefile e25d34a0e1324f771d64c09c592601b97219282011587e6ce410fa8acdedb913 -F ext/fiddle/SqliteTestUtil.js 559731c3e8e0de330ec7d292e6c1846566408caee6637acc8a119ac338a8781c -F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f -F ext/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba833865ab64a1b4ae -F ext/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08 -F ext/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8 -F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf -F ext/fiddle/sqlite3-api.js ccf4bd0c1c5bbb3be3469573423d6c53991941bec497eac63e9f17ea13bf8952 -F ext/fiddle/sqlite3-worker.js a9c2b614beca187dbdd8c053ec2770cc61ec1ac9c0ec6398ceb49a79f705a421 -F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a -F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a -F ext/fiddle/testing1.js e2fa02ac8adbd21c69bc50cfcb79bfc26af0d30a8d6b95ac473a17e0dc9de733 -F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4 -F ext/fiddle/testing2.js 7b45b4e7fddbd51dbaf89b6722c02758051b34bac5a98c11b569a7e7572f88ee F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea @@ -130,14 +112,14 @@ F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6d F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 F ext/fts5/fts5.h c132a9323f22a972c4c93a8d5a3d901113a6e612faf30ca8e695788438c5ca2a -F ext/fts5/fts5Int.h 36fd4a05e6e6307e3bac359a589d5f090b903afe0e7ae15db84f0ff90c79676a +F ext/fts5/fts5Int.h c0d46e399e345e35985b72a1c1af025973bfaa5b1e3563b0ce3bb0ce144a7ca3 F ext/fts5/fts5_aux.c f558e1fb9f0f86a4f7489e258c162e1f947de5ff2709087fbb465fddb7092f98 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 501e7d3566bc92766b0e11c0109a7c5a6146bc41144195459af5422f6c2078aa F ext/fts5/fts5_expr.c 40174a64829d30cc86e8266306ad24980f6911edd5ca0b8c1ce7821ea1341b88 F ext/fts5/fts5_hash.c d4fb70940359f2120ccd1de7ffe64cc3efe65de9e8995b822cd536ff64c96982 -F ext/fts5/fts5_index.c 3e47d9c56e4e9a6dee78bc32e006d6a28a3b5ec9ff84f3b8c381c78323201720 -F ext/fts5/fts5_main.c 6078ae86d3b813753a4f1201054550aff21a3f660e97b30f200d2b1472874151 +F ext/fts5/fts5_index.c a8ee270724ae1f958d0ce9897bcd60a5b760ecbeaa058fc8632805a283f1c20a +F ext/fts5/fts5_main.c 2bf44b9efdecc7fd936f320064666235b91bba8a6c4f3dc6e2d7eae6662570ed F ext/fts5/fts5_storage.c 76c6085239eb44424004c022e9da17a5ecd5aaec859fba90ad47d3b08f4c8082 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee @@ -156,7 +138,7 @@ F ext/fts5/test/fts5ad.test e8cf959dfcd57c8e46d6f5f25665686f3b6627130a9a981371da F ext/fts5/test/fts5ae.test 1142d16d9cc193894dc13cc8f9c7a8a21411ac61b5567a878514df6f9f0d7bb7 F ext/fts5/test/fts5af.test bea75184c0e63631b552c20ebe4a631699f357e00a2059c92538f7aeece8291e F ext/fts5/test/fts5ag.test 7816f25a0707578f08145ab539fc0ca025f8951e788b28a6a18a06b2099469dd -F ext/fts5/test/fts5ah.test 27b5a33bfd0363ca8a4dc659e6e2a5df3dea1c3c5b04bc51ca6aeb1277bd9b21 +F ext/fts5/test/fts5ah.test 2f047dfe89dc8611fa53e3d8bfc453b79cff037aa423c8d171e91e645745aa2c F ext/fts5/test/fts5ai.test bc97e4758cc93e06bf851d61c98fdf4e8b8f8315ee28a84fb15f916360856414 F ext/fts5/test/fts5aj.test 745020852d85f5dd49d11cb7ad11d3cc6dafc4fe6d6d24bc0875ac8f43ee4149 F ext/fts5/test/fts5ak.test fc3595f8e6873bb86d70c9bd4b67d0413ce577bd4793c39a2b60a7b8825b60a6 @@ -212,7 +194,7 @@ F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc27826807405 F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2 -F ext/fts5/test/fts5misc.test 4d7d20372242cc618688de029b87d897d09fa1640302c254abee63cf3ffa2c10 +F ext/fts5/test/fts5misc.test d6d4fdd7ec164e69e50af539137c0565362a4124547bf841ba474f092298637b F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd @@ -239,6 +221,7 @@ F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0 F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 F ext/fts5/test/fts5trigram.test 5b4feb53a4d5aca70c841f6919c8719b5a9c805474727dda99285fccdd2e9cce +F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602 F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9 F ext/fts5/test/fts5unicode2.test 9b3df486de05fb4bde4aa7ee8de2e6dae1df6eb90e3f2e242c9383b95d314e3e @@ -310,12 +293,11 @@ F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0e F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9 F ext/misc/carray.c b752f46411e4e47e34dce6f0c88bc8e51bb821ba9e49bfcd882506451c928f69 F ext/misc/carray.h d2b1b12486d531367c37832d3d0dad34eea4bdd83ed839d445521ef01f0bc4e3 -F ext/misc/cksumvfs.c b42ef52eaaa510d54ec320c87bea149e934a3b06cd232be2093562bf669bd572 +F ext/misc/cksumvfs.c 9224e33cc0cb6aa61ff1d7d7b8fd6fe56beca9f9c47954fa4ae0a69bef608f69 F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c85c243 F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9 F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 -F ext/misc/csv.c d14709096280dc0e20c533f184568952bf4b8022ea80afc4aa9fec5ab3637bb3 -F ext/misc/dbdata.c e316fba936571584e55abd5b974a32a191727a6b746053a0c9d439bd2cf93940 +F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 F ext/misc/decimal.c 09f967dcf4a1ee35a76309829308ec278d3648168733f4a1147820e11ebefd12 F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 @@ -334,7 +316,7 @@ F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196 F ext/misc/qpvtab.c 09738419e25f603a35c0ac8bd0a04daab794f48d08a9bc07a6085b9057b99009 -F ext/misc/regexp.c 03e483711534c437b2e29648d2a4b7730f5cb781a434ac8150907376bc4489f6 +F ext/misc/regexp.c 064838f7b31e90d312cce089cfafbb992034e75d359009c48886ca06c0a794b2 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 @@ -392,17 +374,34 @@ F ext/rbu/rbumisc.test 329986cf5dd51890c4eb906c2f960ebb773a79a64bed90f506b7c4178 F ext/rbu/rbumulti.test 5fb139058f37ddc5a113c5b93238de915b769b7792de41b44c983bc7c18cf5b9 F ext/rbu/rbupartial.test f25df014b8dbe3c5345851fba6e66f79ab237f57dc201b2d5f0dbae658ae5a4c F ext/rbu/rbuprogress.test 857cf1f8166c83ef977edb9ef4fc42d80f71fbd798652b46ae2f3a7031870f8d +F ext/rbu/rburename.test a9b4aea612352b74c45de1757edd2ecb2079348b1d4cc734572dc29e55b1b376 F ext/rbu/rburesume.test dbdc4ca504e9c76375a69e5f0d91205db967dcc509a5166ca80231f8fda49eb1 F ext/rbu/rbusave.test f4190a1a86fccf84f723af5c93813365ae33feda35845ba107b59683d1cdd926 F ext/rbu/rbusplit.test b37e7b40b38760881dc9c854bd40b4744c6b6cd74990754eca3bda0f407051e8 F ext/rbu/rbutemplimit.test 05ceefa90a2e26a99f40dd48282ed63a00df5e59c1f2bfd479c143e201a1b0ba F ext/rbu/rbuvacuum.test 55e101e90168c2b31df6c9638fe73dc7f7cc666b6142266d1563697d79f73534 -F ext/rbu/rbuvacuum2.test 886add83fd74bcb02e6dd016ae5b585367bd58c5d0694c9d9ca7bdb1d1f578c2 +F ext/rbu/rbuvacuum2.test 2643b58f4d8d3573db0f93faae18805a35ab162b4c55ff6b656062ff432ed55b F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10 -F ext/rbu/sqlite3rbu.c 8737cabdfbee84bb25a7851ecef8b1312be332761238da9be6ddb10c62ad4291 -F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812 -F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a +F ext/rbu/sqlite3rbu.c c4ba7901b2d3e0c7845f30840e3ffb35c6f999d6da0d80f121866491f982794c +F ext/rbu/sqlite3rbu.h 02d981e2d39c151391759e1a400e29c7388730812957ac3db8dad7f6c9f9cfc8 +F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 +F ext/recover/dbdata.c 8f1f75d636431de69d7977ec50fc41bfdd0c48c510d5ee7eae0cbd4164e1429a +F ext/recover/recover1.test 6a12f73330a2ca4d1428730c09272e294c5082c2806ae9e56db846374e03090d +F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a +F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e +F ext/recover/recovercorrupt.test 64c081ad1200ae77b447da99eb724785d6bf71715f394543dc7689642e92bf49 +F ext/recover/recovercorrupt2.test 74bef7dd2d7dd4856f3da21be6e213d27da44827e0f5f0946ca0325b46d163ed +F ext/recover/recoverfault.test 9d9f88eeb222615a25e7514f234c950d46bee20d24cd8db49d8fff8d650dcfe1 +F ext/recover/recoverfault2.test 730e7371bcda769554d15460cb23126abba1be8eca9539ccabf63623e7bb7e09 +F ext/recover/recoverold.test 68db3d6f85dd2b98e785b6c4da4f5eea4bbe52ccf6674d9a94c7506dc92596aa +F ext/recover/recoverpgsz.test 3658ab8e68475b1bb87d6af88baa04551c84b73280a566a1be847182410ffc58 +F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f +F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a3a21030057bfd81411 +F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486 +F ext/recover/sqlite3recover.c 6d37a422c8917b793548a7b2c6e7ff8e1af15ba5453eef37bb69331828b73186 +F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 +F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890 @@ -414,7 +413,7 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c bb0dcd013dbb3fe0335b6bbae0d9c29fcfffda93d5ef6c31daa47e802132e435 -F ext/rtree/rtree.c d7b4b8b81d8d54376a7f81de5be85ec58b37c11604bcf42984a8418b34158d93 +F ext/rtree/rtree.c b963ebced19f249d8523396a779351481d13a67c36c3c1f034b982886cd39a42 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test d47f58832145fcfed9067bc457ca8664962196c4566c17a1ebd679367db55d11 F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d @@ -482,16 +481,82 @@ F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2 F ext/session/sessionsize.test 6f644aff31c7f1e4871e9ff3542766e18da68fc7e587b83a347ea9820a002dd8 F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5 F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c 1d019c5caf51936ef24c761db63552b06e0e0d951c8740bba9639b17fa0cb107 +F ext/session/sqlite3session.c b4254dd6e785cdd206c9ca7118796cf82273627fe2d4fd647597f08c2f821f96 F ext/session/sqlite3session.h 0907de79bc13a2e3af30a6dc29acc60792a3eaf7d33d44cf52500d0f3c2b2171 -F ext/session/test_session.c f433f68a8a8c64b0f5bc74dc725078f12483301ad4ae8375205eef790274a787 +F ext/session/test_session.c 6aa7e97407987e81431b26730889db4719efebebbb6fe7502802d19d58b35798 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb +F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c +F ext/wasm/GNUmakefile 3aa8c160705ab9d9d4d552a1f4e630925a65a27df216befe2e9a956904434c1d +F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20 +F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9 +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 9120c2f8f51fa85f46dcf4dcb6b12f4a807d428f6089b99cdb08d8ddfcfd88b2 +F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 +F ext/wasm/api/README.md 1350088aee90e959ad9a94fab1bb6bcb5e99d4d27f976db389050f54f2640c78 +F ext/wasm/api/extern-post-js.js f3dc4219a2a1f183d98452dcbd55a0c5351b759eccca75480a92473974d8b047 +F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 +F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1 +F ext/wasm/api/post-js-header.js d6ab3dfef4a06960d28a7eaa338d4e2a1a5981e9b38718168bbde8fdb2a439b8 +F ext/wasm/api/pre-js.js 287e462f969342b032c03900e668099fa1471d852df7a472de5bc349161d9c04 +F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34 +F ext/wasm/api/sqlite3-api-glue.js 056f44b82c126358a0175e08a892d56fadfce177b0d7a0012502a6acf67ea6d5 +F ext/wasm/api/sqlite3-api-oo1.js e9a83489bbb4838ce0aee46eaaa9350e0e25a5b926b565e4f5ae8e840e4fbaed +F ext/wasm/api/sqlite3-api-opfs.js 5c6d0903e100b2918920d8c596ab037cd9a67df19e388ebde4fe085218780af0 +F ext/wasm/api/sqlite3-api-prologue.js fd526fa017fa2578673ca18158354515c719e719a5d93f2f6d0e43f39170430e +F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f +F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 +F ext/wasm/api/sqlite3-opfs-async-proxy.js 24d1c1982a012d998907105a4ff1ff6881bf462395e90c06326817701e69f093 +F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 +F ext/wasm/api/sqlite3-wasm.c 8fc8f47680df0e9a6c0f2f03cb004148645ecc983aa216daba09cb21f7e092a2 +F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b +F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54 +F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 +F ext/wasm/batch-runner.js 49609e89aaac9989d6c1ad3fae268e4878e1ad7bc5fd3e5c2f44959660780b2e +F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b +F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f +F ext/wasm/common/testing.css 35889709547d89a6109ff83b25c11bbc91d8dd43aab8722e428655ca98880a06 +F ext/wasm/common/whwasmutil.js 16a592d5c304a2d268ca1c28e08a5b029a2f3cbe10af78dbc3456cfc9e3559d1 +F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed +F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 +F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 +F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e +F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8 +F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f +F ext/wasm/demo-worker1-promiser.js b85a2bb1b918db4f09dfa24419241cb3edad7791389425c2505092e9b715017d +F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d +F ext/wasm/demo-worker1.js a619adffc98b75b66c633b00f747b856449a134a9a0357909287d80a182d70fa +F ext/wasm/dist.make 481289899a07958439d07ee4302ff86235fa0fbb72f17ea05db2be90a94abf90 +F ext/wasm/fiddle.make e570ec1bfc7d803507a2e514fe32f673fe001b2114b85c73c3964a462ba8bcfc +F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f +F ext/wasm/fiddle/fiddle-worker.js b4a0c8ab6c0983218543ca771c45f6075449f63a1dcf290ae5a681b2cba8800d +F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715 +F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 +F ext/wasm/index-dist.html cb0da16cba0f21cda2c25724c5869102d48eb0af04446acd3cd0ca031f80ed19 +F ext/wasm/index.html ce6a68a75532b47e3c0adb83381a06d15de8c0ac0331fb7bf31d33f8e7c77dc4 +F ext/wasm/jaccwabyt/jaccwabyt.js 95f573de1826474c9605dda620ee622fcb1673ae74f191eb324c0853aa4dcb66 +F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e +F ext/wasm/module-symbols.html b8eebafef8e536624bbe5f7a3da40c07a9062b843dfd3161a0bb72cbb6763dc5 +F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06 +F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18 +F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0 +F ext/wasm/speedtest1-worker.html d75d9de9347c6f2f3278b73ccb6bceb559080644aef385fe496fa389f58bcd66 +F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1 +F ext/wasm/speedtest1.html e4c4e5c1c8ec1ad13c995e346e4216a1df152fd2c5cd17e0793b865b2f3c5000 +F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x +F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 +F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 +F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f +F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac +F ext/wasm/tester1-worker.html 51bf39e2b87f974ae3d5bc3086e2fb36d258f3698c54f6e21ba4b3b99636fa27 +F ext/wasm/tester1.html 624ec41cd9f78a1f2b6d7df70aaa7a6394396b1f2455ecbd6de5775c1275b121 +F ext/wasm/tester1.js bff806de454de115922d78c056f11d523ec7ed9ed3839d4e21433a9f72558b88 +F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 +F ext/wasm/wasmfs.make edfd60691d10fd19ada4c061280fd7fbe4cf5f6bf6b913268e8ebedfccea6ab5 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 -F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 50e98928657b6d5aeb9445f6130964bd644861cfa049d689db41da317d449517 +F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 +F main.mk fd90f1bd90bd4070c9af7e9c8396bd0cf4208a0186c8d18fa6e2609dff447d5d F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -503,46 +568,47 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a +F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 F src/alter.c 0390ca1d69ec3626cfa9f153114b7ab233e6b2bada6a9eb91361ed385fe90deb -F src/analyze.c aabdf3769c7fd9954a8ec508eb7041ae174b66f88d12c47199fabbea9a646467 +F src/analyze.c d2fce73f6a024897593012c6ca25368629fa4aeb49960d88a52fac664582e483 F src/attach.c 4431f82f0247bf3aaf91589acafdff77d1882235c95407b36da1585c765fbbc8 F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7 F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d -F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6 -F src/btree.c 98e30d582239e1c940390f6487aa0d231691989a142fa863a689c021517ede08 -F src/btree.h 74d64b8f28cfa4a894d14d4ed64fa432cd697b98b61708d4351482ae15913e22 +F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca +F src/btree.c 0d2aa1331a63eefc054faa8701cfe3ba1cbe27540cb02f65c61b0605bf6b5004 +F src/btree.h edfb08230b6fd6b0a165ba82defd3471b0e71d577d0c66807d4994d4aade4332 F src/btreeInt.h 8ce1332edd89dfd2461d561ac10a0ab5601c8e06200cb5230596c3caaf54482e -F src/build.c 29fcc97af5197511788a571ed35a001eea472cbe3bcdbae88178e17fcafd4341 -F src/callback.c 4c19af69835787bfe790ac560f3071a824eb629f34e41f97b52ce5235c77de1c +F src/build.c d3e43e950e4e377c1d451a4862556792acdef1faba14a03f899d30d09731c48b +F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c 026dbdcdbd8c3cde98a88483ee88310ff43150ab164ad768f12cc700a11495ad -F src/date.c 15082566229d4b1e5f24fdb490bf9bcc68824b911d70e3573ef075a1b9e2d26f -F src/dbpage.c 90661a87e1db8bfbc8d2ebbdcd3749651ddb287c555c07a28fb17c7c591ffb68 +F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d +F src/date.c 94ce83b4cd848a387680a5f920c9018c16655db778c4d36525af0a0f34679ac5 +F src/dbpage.c f1a87f4ebcf22284e0aaf0697862f4ccfc120dcd6db3d8dfa3b049b2580c01d8 F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d -F src/delete.c a8e844af211a48b13b5b358be77a12c860c6a557c21990ad51a548e2536500ce -F src/expr.c 4907afcb86d72b5525d8767515ce425ec53c7a2d3664441b46cef5b376ee0cba +F src/delete.c 86573edae75e3d3e9a8b590d87db8e47222103029df4f3e11fa56044459b514e +F src/expr.c 847f87d9df3ede2b2b0a8db088af0b9c1923b21009f8ea1b9b7b28cb0a383170 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c d965ede15d8360c09ed59348940649ee647b192e784466837d7aefa836d1d91e -F src/func.c 8f72e88cccdee22185133c10f96ccd61dc34c5ea4b1fa9a73c237ef59b2e64f1 -F src/global.c e83ee571b79ee3adc32e380cf554cf1254bc43763d23786c71721fbcdfbbb965 +F src/fkey.c 722f20779f5342a787922deded3628d8c74b5249cab04098cf17ee2f2aaff002 +F src/func.c d25f3c667d59dbac195e65f3539fdbbd8a36c142ce7bcdb45d1baf07446ad13a +F src/global.c e06ff8e0acd85aec13563c9ecb44fbbf38232ccf73594998fd880b92d619594b F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 173845e5a6bac96ae937409e4f876b631f26b31dabb9df8fd0eb3b130b2bb3a7 +F src/insert.c 90a32bc7faa755cd5292ade21d2b3c6edba8fd1d70754a364caccabfde2c3bb2 F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 853385cc7a604157e137585097949252d5d0c731768e16b044608e5c95c3614b -F src/main.c b91c7e71af6f33640c35b8239a285040aad8dfcfdaaf979152e743c0f8017ea8 -F src/malloc.c 4a3785323104678a8b4b0a482fe0c2a80900e7468ddf76ab0f2ea1c79a8ca8cd +F src/loadext.c 25663175950c5c4404b9377840b7b4c6fe5c53b415caf43634c62f442c02a9a7 +F src/main.c dcb6d30c31dcfd6c901e753d6618ef27838b2895a1a35d4ac9ac1c10c1bee128 +F src/malloc.c dfddca1e163496c0a10250cedeafaf56dff47673e0f15888fb0925340a8e3f90 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 -F src/mem5.c 5a3dbd8ac8a6501152a4fc1fcae9b0900c2d7eb0589c4ec7456fdde15725a26c -F src/memdb.c c2dc88f97c410eb68a24468344b65526685e18354ddfd15906750c1eaf9dc2dd -F src/memjournal.c 8bd50ae6d9c6d34b3a96cc3b4f567f9935dc358444d872ab48901a8c11ad82a6 +F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff +F src/memdb.c 559c42e61eb70cd6d4bc692b042497133c6d96c09a3d514d92f3dac72268e223 +F src/memjournal.c c283c6c95d940eb9dc70f1863eef3ee40382dbd35e5a1108026e7817c206e8a0 F src/msvc.h 3a15918220367a8876be3fa4f2abe423a861491e84b864fb2b7426bf022a28f8 F src/mutex.c 5e3409715552348732e97b9194abe92fdfcd934cfb681df4ba0ab87ac6c18d25 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a @@ -550,38 +616,39 @@ F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 -F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab +F src/os.c 81c9c1c52eab711e27e33fd51fe5788488d3a02bc1a71439857abbee5d0d2c97 F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 -F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 -F src/os_unix.c e020676e334a0a78c3affb7dd1789a240071ed6af98309f1bef76948cb8382bd -F src/os_win.c e9454cb141908e8eef2102180bad353a36480612d5b736e4c2bd5777d9b25a34 +F src/os_kv.c 0e59600d25b72034c7666b8b7dcc527f039b5d9c16f24a7eca4c08c66f63c364 +F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 +F src/os_unix.c c710dfac1b86a87c82053fa397ae31628217a8a86630904c5496ce649e5ada2b +F src/os_win.c 295fe45f18bd86f2477f4cd79f3377c6f883ceb941b1f46808665c73747f2345 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 8c3853cad320c24904a2f970777bbdda2deb9572f53e6a1d73b28b85a7fd30cc +F src/pager.c 6176d9752eb580419e8fef4592dc417a6b00ddfd43ee22f818819bf8840ceee8 F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 F src/parse.y 8e67d820030d2655b9942ffe61c1e7e6b96cea2f2f72183533299393907d0564 -F src/pcache.c 5df59c993907b742dfac6aa9038d2515856bede56040a7640d39d06e1c93b494 +F src/pcache.c f4268f7f73c6a3db12ce22fd25bc68dc42315d19599414ab1207d7cf32f79197 F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 -F src/pcache1.c 3e6267b17bae4cbaf02dfa08c4507794452991206862e3fc5c94d28313d32c18 -F src/pragma.c 404c0fe37b51f6def2c9ec6b07c80f0355673f0e74d720dc9a58a78725502417 +F src/pcache1.c dee95e3cd2b61e6512dc814c5ab76d5eb36f0bfc9441dbb4260fccc0d12bbddc +F src/pragma.c 894c2621d35edd4beea9b331cfdb1b42032394420074d2294c8febe548eea8a1 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c c62820c15dcb63013519c8e41d9f928d7478672cc902cfd0581c733c271dbf45 +F src/prepare.c 1b02be0441eda4579471fea097f678effcbb77ef0c39ab3f703c837822bcd674 F src/printf.c e99ee9741e79ae3873458146f59644276657340385ade4e76a5f5d1c25793764 -F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c -F src/resolve.c f0d663c9b1ceeb3e7d262ede872dd3b24b323a7cc11d84c05a39d962e7d64b07 +F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c +F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c fea5ae26259dc995b2144a28bbb5b013196dbcc86527cd992ed109b00af7f9e8 -F src/shell.c.in 2b85128ca8ea13fc2dc32f971d628d9f688a324a30f469619b817ce490764fcb -F src/sqlite.h.in 01573eae96721f2a8ee2a9e3b7140ceeba2e9c44350911890b89b8ff0dcf6781 +F src/select.c 9886d6669f5787471aab6ae52af76fad90b53edb1c218fc9ed9d953363bc5184 +F src/shell.c.in f2736e84caf751e94477776df8ab0e4ebc8311de00594d997abd81533debc85c +F src/sqlite.h.in b729158b4c018117fe318f0be60a8c0da10e53cb2153f8349d05fde7ceb0d15b F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d -F src/sqliteInt.h 9491831b5faa77584f0f7f52c087b48ba573714fa20a448a1550180ce42ef5aa +F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f +F src/sqliteInt.h 2c24ba38f78e32fe5d7ec136321a6ad827698b33ca98664970a8b7274d69ef7c F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 -F src/status.c 4a3da6d77eeb3531cb0dbdf7047772a2a1b99f98c69e90ce009c75fe6328b2c0 +F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c 4e64ba300a5a26e0f1170e09032429faeb65e45e8f3d1a7833e8edb69fc2979e -F src/test1.c 1356984e97bff07e4a8cc3863e892f05b3348678a74783bb6f350b76316736f1 -F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 +F src/test1.c 40c9a40975512985762f87b83d0c63e4904833a9fe78cbcca664a37095301b1d +F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d @@ -597,7 +664,7 @@ F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31 F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c 8264637b06a3c1f0727c88d1ea32dcf7986b9e7e358a970cae87cdac8a5b2708 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f -F src/test_demovfs.c 86142ba864d4297d54c5b2e972e74f3141ae4b30f05b3a95824184ed2d3d7f91 +F src/test_demovfs.c 7cc7623d1025d1e92c51da20fd25060759733b7a356a121545a3b7d2faa8a0f1 F src/test_devsym.c aff2255ea290d7718da08af30cdf18e470ff7325a5eff63e0057b1496ed66593 F src/test_fs.c ba1e1dc18fd3159fdba0b9c4256f14032159785320dfbd6776eb9973cb75d480 F src/test_func.c 24df3a346c012b1fc9e1001d346db6054deb426db0a7437e92490630e71c9b0a @@ -609,7 +676,7 @@ F src/test_journal.c a0b9709b2f12b1ec819eea8a1176f283bca6d688a6d4a502bd6fd79786f F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd F src/test_malloc.c 21121ea85b49ec0bdb69995847cef9036ef9beca3ce63bbb776e4ea2ecc44b97 F src/test_md5.c 7268e1e8c399d4a5e181b64ac20e1e6f3bc4dd9fc87abac02db145a3d951fa8c -F src/test_multiplex.c 1b23782212a01349fac382913ef82b8de4ae8a4cb46556602c2ee733edabbbc9 +F src/test_multiplex.c d8bc260a57c2028946a9bce9918d24e69e44cebd71ac6be240aa5b6b75e2b369 F src/test_multiplex.h 5436d03f2d0501d04f3ed50a75819e190495b635 F src/test_mutex.c abf486e91bd65e2448027d4bb505e7cce6ba110e1afb9bd348d1996961cadf0d F src/test_onefile.c f31e52e891c5fef6709b9fcef54ce660648a34172423a9cbdf4cbce3ba0049f4 @@ -623,7 +690,7 @@ F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939 -F src/test_tclsh.c c4065ced25126e25c40122c5ff62dc89902ea617d72cdd27765151cdd7fcc477 +F src/test_tclsh.c 7dd98be675a1dc0d1fd302b8247bab992c909db384df054381a2279ad76f9b0e F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc F src/test_thread.c 269ea9e1fa5828dba550eb26f619aa18aedbc29fd92f8a5f6b93521fbb74a61c F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43 @@ -634,34 +701,34 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 36eb0799e487759bbe73e5742b82ee676f06cea2515ff578d03c59a74ccf2d6c +F src/tokenize.c 1305797eab3542a0896b552c6e7669c972c1468e11e92b370533c1f37a37082b F src/treeview.c 07787f67cd297a6d09d04b8d70c06769c60c9c1d9080378f93929c16f8fd3298 -F src/trigger.c bc70c58e713dcfb6cabe5cc0bed71aedb02c3e9e128c6089a78aca945ba4d720 -F src/update.c c52a7991bece0453d22c77c08469512ee2f1391c12503fd347d1c939220c5877 -F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937 +F src/trigger.c 5e68b790f022b8dafbfb0eb244786512a95c9575fc198719d2557d73e5795858 +F src/update.c 5b0302c47cf31b533d5dff04c497ca1d8b9d89c39727e633fbe7b882fd5ac5aa +F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 0be191521ff6d2805995f4910f0b6231b42843678b2efdc1abecaf39929a673f -F src/vacuum.c bb346170b0b54c6683bba4a5983aea40485597fdf605c87ec8bc2e199fe88cd8 -F src/vdbe.c 3df15f0f2cd9d8faeeea18398a62f16c07fda2bfd8d726186f07508ad69b40e2 -F src/vdbe.h 07641758ca8b4f4c6d81ea667ea167c541e6ece21f5574da11e3d21ec37e2662 -F src/vdbeInt.h 2cad0aeeb106371ed0e0946bab89f60627087068847afc2451c05056961c18da -F src/vdbeapi.c 602610f1252d59cd69742f78a1e2f6fbae40a4b407f5506a6a7b869b0df08ff2 -F src/vdbeaux.c dbdfa27cdaf91f18fbc960902fc45e79db3dbfcc05edd9f1f878c144437ce9f6 +F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd +F src/vdbe.c 0c7cb1b934ad8611e14e7efaf2c3a95df7dd3f7964d63ea07fef42a23df86131 +F src/vdbe.h 58675f47dcf3105bab182c3ad3726efd60ffd003e954386904ac9107d0d2b743 +F src/vdbeInt.h 17b7461ffcf9ee760d1341731715a419f6b8c763089a7ece25c2e8098d702b3f +F src/vdbeapi.c 1e8713d0b653acb43cd1bdf579c40e005c4844ea90f414f065946a83db3c27fb +F src/vdbeaux.c 8ebe337e82d99cf3b01cd4fd67103a5b0054d53fee8456b90dbeba46ebf97ceb F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd -F src/vdbemem.c 5ebf05c0182addedb1607ade848e1c83cef40981df94d1abfab0c59288c6064f +F src/vdbemem.c 6cfed43758d57b6e3b99d9cdedfeccd86e45a07e427b22d8487cbdbebb6c522a F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c -F src/vtab.c 3d72c780d1ea08906a198e4f033921a658a54590e3ed72c544995d84f3f9464a +F src/vtab.c b2f993aa954078985bc40317bb2140fe0880a08a7440f3a428b60fce74636808 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c e7b7c9d37f4f8d14d4d05b20e57ae43712a51ffed7d45f547a23f9c42ee483dd -F src/whereInt.h b48ca529ffe293c18cbfa8326af18a09e39910de66fb3e96ef788c7cbf8ef3a7 -F src/wherecode.c f8acceb057286ed95b66689d1981985228fd925001fdc49dbd3b112f4d739174 -F src/whereexpr.c 55a39f42aaf982574fbf52906371a84cceed98a994422198dfd03db4fce4cc46 -F src/window.c fff1b51757438c664e471d5184634e48dcdf8ea34b640f3b1b0810b1e06de18c +F src/where.c 42f0433beedec9ba0ac0945732e1a8c10b5081f1e82caf4507ac493d79f552af +F src/whereInt.h df0c79388c0b71b4a91f480d02791679fe0345d40410435c541c8893e95a4d3f +F src/wherecode.c 133a94f82858787217d073143617df19e4a6a7d0b771a1519f957608109ad5a5 +F src/whereexpr.c 05295b44b54eea76d1ba766f0908928d0e20e990c249344c9521454d3d09c7ae +F src/window.c 928e215840e2f2d9a2746e018c9643ef42c66c4ab6630ef0df7fa388fa145e86 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test b5c19d504dec222c0dc66642673d23dce915d35737b68e74d9f237b80493eb53 @@ -717,7 +784,7 @@ F test/attach.test 54f8e49e88d0de48f6428267a678465863d2b8f72320612f35bd5c02e240b F test/attach2.test 256bd240da1835fb8408dd59fb7ef71f8358c7a756c46662434d11d07ba3a0ce F test/attach3.test c59d92791070c59272e00183b7353eeb94915976 F test/attach4.test 00e754484859998d124d144de6d114d920f2ed6ca2f961e6a7f4183c714f885e -F test/attachmalloc.test 12c4f028e570acf9e0a4b0b7fe6f536e21f3d5ebddcece423603d0569beaf438 +F test/attachmalloc.test 67309af95c6b765c13e7d2279d7fccbef78e6eb0565d75d51cefd5dc88784549 F test/auth.test 0f246deec5cb2f6f893f8fbb76628f182c08fe40f178b254dd72467ca012f657 F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab @@ -755,14 +822,15 @@ F test/bestindex9.test bf2eb8556e8d5c00ef3ee18c521751cd03c1b55454b6e7683b4c6742e F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc -F test/bigmmap.test b820c234daa56d24bc3bf006e3ac7aa9d9623c8ac656a38f59063b444a2d65d1 +F test/bigmmap.test 6021e205487347c6d7e5a541aa472a4b8efc4e9f4a3799a823b61a8e6616105d F test/bigrow.test f0aeb7573dcb8caaafea76454be3ade29b7fc747 -F test/bigsort.test 8299fa9298f4f1e02fc7d2712e8b77d6cd60e5a2 +F test/bigsort.test 997e172009905873c06426145e4b3794c7dfe2d563724cb2fd39d45f319cf3d2 F test/bind.test 1e136709b306f7ed3192d349c2930d89df6ab621654ad6f1a72381d3fe76f483 F test/bind2.test 918bc35135f4141809ead7585909cde57d44db90a7a62aef540127148f91aab7 F test/bindxfer.test efecd12c580c14df5f4ad3b3e83c667744a4f7e0 F test/bitvec.test 75894a880520164d73b1305c1c3f96882615e142 F test/blob.test e7ac6c7d3a985cc4678c64f325292529a69ae252 +F test/bloom1.test 5318eb0648dff073ca9b9a54387ec2c0a7a07ed3490461fe2db0d074b2eb0e7f F test/boundary1.tcl 6421b2d920d8b09539503a8673339d32f7609eb1 F test/boundary1.test 66d7f4706ccdb42d58eafdb081de07b0eb42d77b F test/boundary2.tcl e34ef4e930cf1083150d4d2c603e146bd3b76bcb @@ -773,9 +841,9 @@ F test/boundary4.tcl 0bb4b1a94f4fc5ae59b79b9a2b7a140c405e2983 F test/boundary4.test 89e02fa66397b8a325d5eb102b5806f961f8ec4b F test/btree01.test fef17d9e999ac4f04095948e3438fbe674f4e07bb2c63bb1cad41d87baee077f F test/btree02.test 7555a5440453d900410160a52554fe6478af4faf53098f7235f1f443d5a1d6cc -F test/btreefault.test c2bcb542685eea44621275cfedbd8a13f65201e3 +F test/btreefault.test a82a23b0578bc587afbf9a622c8f54a54f63762f062ba8a35613cfee38ab42f9 F test/busy.test 510dc6daaad18bcbbc085bcc6217d6dc418def5e73f72ce1475eea0cb7834727 -F test/busy2.test dbfb61b3265e7a962d3bcd32cd542bbe3d7801edbda6438d35af5aa707cae981 +F test/busy2.test 20823a5d7c42fb257d9f108c66312d90b1bb4ec3d80ba6b4e371073727560f98 F test/cache.test 13bc046b26210471ca6f2889aceb1ea52dc717de F test/cacheflush.test af25bb1509df04c1da10e38d8f322d66eceedf61 F test/cachespill.test 895997f84a25b323b166aecb69baab2d6380ea98f9e0bcc688c4493c535cfab9 @@ -786,7 +854,7 @@ F test/capi3c.test 54e2dc0c8fd7c34ad1590d1be6864397da2438c95a9f5aee2f8fbc60c112e F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212a2634 -F test/cast.test 336fa21989b5170ebcaf90c24266be22dd97b3e23d1fad5ecf6ad4efb04c4423 +F test/cast.test 6064022ba9af31a8a2ff7bb345e5bd0e74172ffad85bdab5898a42d8227c7585 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9 @@ -800,7 +868,7 @@ F test/collate1.test 71a6f27fdc93a92f14d8ab80c05e1937656a5a03197e1a10157314554d6 F test/collate2.test 471c6f74573382b89b0f8b88a05256faa52f7964f9e4799e76708a3b1ece6ba4 F test/collate3.test 89defc49983ddfbf0a0555aca8c0521a676f56a5 F test/collate4.test c953715fb498b87163e3e73dd94356bff1f317bd -F test/collate5.test 65d928034d30d2d263a80f6359f7549ee1598ec6 +F test/collate5.test b1dfeff239ea69ee9225832553f423d37a6184eb730cee06f6846ab4e3c6dbef F test/collate6.test 8be65a182abaac8011a622131486dafb8076e907 F test/collate7.test 8ec29d98f3ee4ccebce6e16ce3863fb6b8c7b868 F test/collate8.test cd9b3d3f999b8520ffaa7cc1647061fc5bab1334 @@ -834,9 +902,9 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test a17bbf54fdde78d43cf3cc34b0057719fd4a173a3d824285b67dc5257c064c7b F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test ecce40d7b9b909a670a42a45d86e30d927735d7e7f09041af438b19529d35532 +F test/corruptL.test 9d1a0055c8db19baccd12f22ac36a33ec7d63afb59e82eb30835aea8f89b94df F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 -F test/corruptN.test 60b5a62944b4f0029ba07edaa5fd8e670539d6b0a8d99db26c068d435675cbfe +F test/corruptN.test 7c099d153a554001b4fb829c799b01f2ea6276cbc32479131e0db0da4efd9cc4 F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 F test/count.test cd4bd531066e8d77ef8fe1e3fc8253d042072e117ccab214b290cf83f1602249 F test/countofview.test e17d6e6688cf74f22783c9ec6e788c0790ee4fbbaee713affd00b1ac0bb39b86 @@ -853,7 +921,7 @@ F test/crashM.test d95f59046fa749b0d0822edf18a717788c8f318d F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2 F test/createtab.test 85cdfdae5c3de331cd888d6c66e1aba575b47c2e3c3cc4a1d6f54140699f5165 F test/cse.test 00b3aea44b16828833c94fbe92475fd6977583fcb064ae0bc590986812b38d0c -F test/csv01.test c9c3af0d58c34e9ac970c5875a77939edb958762c8aafb95409e19a3f088b6cd +F test/csv01.test 2ab5514005fd308995c8910bc313e47f0368b94213b9d6c27f9a2da78796a091 F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c4773 F test/cursorhint.test 0175e4404181ace3ceca8b114eb0a98eae600d565aa4e2705abbe6614c7fe201 F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f @@ -867,6 +935,7 @@ F test/dbfuzz001.test 55e1a3504f8dea84155e09912fe3b1c3ad77e0b1a938ec42ca03b8e51b F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23 F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e887d +F test/dbpagefault.test b893e9e43d55edc0cdf3f71ae093802f80f0fb517d839eefeae5647c49a41f36 F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test fcf403fd5585f47342234e153c4a4338cd737b8e0884ac66fc484df47dbcf1a7 @@ -902,16 +971,16 @@ F test/e_select.test 89fa483f68d868f1be3d6f56180ed42d979979c266e051bf8b5e66a251e F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f3b30e9b41e4 F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 -F test/e_uri.test a3d30c7d6d7700150055b6660ee42da7f05dee2c150ce08bf6a9ededc26468cc +F test/e_uri.test 86564382132d9c453845eeb5293c7e375487b625900ab56c181a0464908417d8 F test/e_vacuum.test 89fc48e8beee2f9dfd6de1fbb2edea6542dae9121dc0fbe6313764169e742104 -F test/e_wal.test ae9a593207a77d711443ee69ffe081fda9243625 +F test/e_wal.test db7c33642711cf3c7959714b5f012aca08cacfa78da0382f95e849eb3ba66aa4 F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8 F test/e_walckpt.test 28c371a6bb5e5fe7f31679c1df1763a19d19e8a0 F test/e_walhook.test 01b494287ba9e60b70f6ebf3c6c62e0ffe01788e344a4846b08e5de0b344cb66 F test/emptytable.test a38110becbdfa6325cd65cb588dca658cd885f62 F test/enc.test 9a7be5479da985381d740b15f432800f65e2c87029ee57a318f42cb2eb43763a F test/enc2.test 848bf05f15b011719f478dddb7b5e9aea35e39e457493cba4c4eef75d849a5ec -F test/enc3.test 6807f7a7740a00361ca8d0ccd66bc60c8dc5f2b6 +F test/enc3.test 55ef64416d72975c66167310a51dc9fc544ba3ae4858b8d5ab22f4cb6500b087 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 F test/eqp.test 473aea9599b4b7af46614b55198cd78167e4eccd48e60812a40db47c5c41dea9 F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 @@ -1059,7 +1128,7 @@ F test/fts4lastrowid.test 185835895948d5325c7710649824042373b2203149abe8024a9319 F test/fts4merge.test e2b2ec21e287d54ec09824ccfb41e66896eeca568fc818ba0e0eb2efd94c35d2 F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891 F test/fts4merge3.test 8d9ccb4a3d41c4c617a149d6c4b13ad02de797d0 -F test/fts4merge4.test d895b1057a7798b67e03455d0fa50e9ea836c47b +F test/fts4merge4.test 66fce89934cd9508cbdc67de486558c34912ffb2e8ffe5c9a1bbb9b8a4408ba7 F test/fts4merge5.test 69932d85cda8a1c4dcfb742865900ed8fbda51724b8cf9a45bbe226dfd06c596 F test/fts4min.test 1c11e4bde16674a0c795953509cbc3731a7d9cbd1ddc7f35467bf39d632d749f F test/fts4noti.test d5d933705b1b1516b67a5e3f8e514ecb19c6522fb3357bb744776d48427c2292 @@ -1085,7 +1154,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 7b501d55631c2d759e0bed02ed329904a35690fc6563d7b6cc69b7788a024f26 +F test/fuzzcheck.c 30475c820dc5ab8a87fa3be1fe8ba8199ebfe2544508a759d653688d8d168766 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1093,11 +1162,11 @@ F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e4 F test/fuzzdata5.db e35f64af17ec48926481cfaf3b3855e436bd40d1cfe2d59a9474cb4b748a52a5 F test/fuzzdata6.db 92a80e4afc172c24f662a10a612d188fb272de4a9bd19e017927c95f737de6d7 F test/fuzzdata7.db 0166b56fd7a6b9636a1d60ef0a060f86ddaecf99400a666bb6e5bbd7199ad1f2 -F test/fuzzdata8.db ca9a97f401b06b0d5376139ec7e1f9e773e13345a9a2d9ccc0032cdbfedea230 +F test/fuzzdata8.db 653423800b7671e67caa740e977d80e1360f0d69e9992851f3ea5c4a69a2724a F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc -F test/fuzzinvariants.c c1c7ae6be4c5fe4e5b845ce2b166c17a3d0f8ef545ab0248c9990505010070da +F test/fuzzinvariants.c 7877178eaa10eb3ea986f81a7010efc371ccd3e13ee5b14fa290b0459002a36a F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c F test/gencol1.test cc0dbb0ee116e5602e18ea7d47f2a0f76b26e09a823b7c36ef254370c2b0f3c1 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 @@ -1161,7 +1230,7 @@ F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9 -F test/join.test 5c7f917aa219a125d1df517d3df384c79f74860fdfb2e48c0a599f1e2b3e9ed8 +F test/join.test e32cb9b1491eed682489e2cde33a22a4eb7611fe5aa3b0aa4b275fe27ab3f3ac F test/join2.test 466b07233820f5deee66a6c3bf6e4500c8bbf7b83649e67606f5f649c07928c0 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 @@ -1173,10 +1242,10 @@ F test/join9.test 9056ddd3b0c0f4f9d658f4521038d9a37dc23ead8ca9a505d0b0db2b6a471e F test/joinA.test 7eab225dc1c1ab258a5e62513a4ed7cabbd3db971d59d5d92f4fb6fa14c12f6a F test/joinB.test 1b2ba3fc8568b49411787fccbf540570c148e9b6a53a30f80691cb6268098ded F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f207 -F test/joinD.test 1a430af8dac5b68663f13df534ffe98775e582bac2305b80f1e8eb4ab778672a +F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28 F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 -F test/joinH.test e67d1d6a8c7141caf981a07386caa7fda0362baa09e03669f9a4fbee812806d0 +F test/joinH.test 15f501b33d848521964afde9865a92aeca79c8c41fa84dc4dc3f865c9ed8c868 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e @@ -1194,7 +1263,7 @@ F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 F test/lemon-test01.y 58b764610fd934e189ffbb0bbfa33d171b9cb06019b55bdc04d090d6767e11d7 F test/like.test 5fe0bc37f307aef0a453ce2de4632bdfc0759448f0421c39f6d53caefe905fac -F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da +F test/like2.test d3be15fefee3e02fc88942a9b98f26c5339bbdef7783c90023c092c4955fe3d3 F test/like3.test a76e5938fadbe6d32807284c796bafd869974a961057bc5fc5a28e06de98745c F test/limit.test 350f5d03c29e7dff9a2cde016f84f8d368d40bcd02fa2b2a52fa10c4bf3cbfaf F test/limit2.test 9409b033284642a859fafc95f29a5a6a557bd57c1f0d7c3f554bd64ed69df77e @@ -1234,15 +1303,15 @@ F test/mallocK.test 25897506da0098cea09b302ff432b0fb6d8002773c1e0fc9732aa8b444bf F test/mallocL.test fb311ff80afddf3b1a75e52289081f4754d901dc F test/mallocM.test 78bbe9d3da84a5c679123cdb40d7b2010b18fc46e13897e4f253c6ba6fbff134 F test/malloc_common.tcl 806c50379cf4fa65008cd4d5af18273e5dac8ab62d1d4316c76aa2ecd2e54018 -F test/malloctraceviewer.tcl b7a54595270c1d201abf1c3f3d461f27eaf24cdef623ad08a0fe5e411264c8a9 +F test/malloctraceviewer.tcl 3e3ddf11e30d2b20f53aa16aa6615082fb24a100bea61cca7214c927b742eba6 F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7 F test/memdb1.test 2c4e9cc10d21c6bf4e217d72b7f6b8ba9b2605971bb2c5e6df76018e189f98f5 F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c -F test/memjournal2.test 89a4e0d1084170a281efa4d54c2677599f986f44227f98f7dfae282802737b65 +F test/memjournal2.test 6b9083cfaab9a3281ec545c3da2487999e8025fb7501bbae10f713f80c56454c F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 -F test/memsubsys1.test 9e7555a22173b8f1c96c281ce289b338fcba2abe8b157f8798ca195bbf1d347e +F test/memsubsys1.test 86b8158752af9188ed5b32a30674a1ef71183e6bc4e6808e815cd658ca9058a6 F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08 F test/merge1.test 2de6d6ef8d25402764b1aab49d8f9d7f89208c89a6674e437f76de4c812157b8 F test/minmax.test fe638b55d77d2375531a8f549b338eafcd9adfbd2f72df37ed77d9b26ca0a71a @@ -1317,9 +1386,9 @@ F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035c F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test cf5f31bab83a452288b2a050880152cdf99d62e9aab71948268d549debcc6942 +F test/permutations.test 3e0d6eea70e5087f3240b1a2fe621b0c73445f38a262029f0a1d2d89564026f7 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f -F test/pragma.test cae534c12a033a5c319ccc94f50b32811acdef9f67bf19a82ff42697caccd69f +F test/pragma.test 620622fb0815f1cbea8e26e1d8abad38e0cbcbed8927fd84048fe9fd6239e323 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31 F test/pragma4.test ca5e4dfc46adfe490f75d73734f70349d95a199e6510973899e502eef2c8b1f8 @@ -1341,19 +1410,19 @@ F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459 F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 -F test/recover.test ccb8c2623902a92ebb76770edd075cb4f75a4760bb7afde38026572c6e79070d -F test/regexp1.test 4a44e014664a109bbb1c37d29d9b61ca5aa5a7f49cc564c95208a80f818e3377 +F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0471e +F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest_data.tcl 11ba48a21ed1c808147b0e77c6e93d204577f4327ffe6d7c3b34cd3c01eac3a2 +F test/releasetest_data.tcl 0db8aee0c348090fd06da47020ab4ed8ec692e0723427b2f3947d4dfb806f3b0 F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test c43b8370a351f77aec6d71f4a2cde59b849369ed1933261a2c2c69e23e34ff5e +F test/returning1.test 1366e04566cfe1a082d17b1e0f195ec64473c79374b3a5d4ae00c43d885dea31 F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a -F test/round1.test 768018b04522ca420b1aba8a24bd76091d269f3bce3902af3ec6ebcee41ab21e +F test/round1.test 1bb32cf3fc505eed9e86b5e523d07e15d4428189665524587512fbcc85d114bb F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test e29025be95baf6b32f0d5edef59a7633028325896a98f1caa8019559ca910350 @@ -1371,7 +1440,7 @@ F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972 F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 -F test/savepoint.test 1f8a6b1aea9a0d05837adc463d4bf47bd9d0f1c842f1c2a9caccd639baf34bf9 +F test/savepoint.test 6e9804a17767f08432c7a5e738b9a8f4b891d243110b63d3a41d270d3d1378ec F test/savepoint2.test 9b8543940572a2f01a18298c3135ad0c9f4f67d7 F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0 F test/savepoint5.test 0735db177e0ebbaedc39812c8d065075d563c4fd @@ -1388,16 +1457,17 @@ F test/schema6.test e4bd1f23d368695eb9e7b51ef6e02ca0642ea2ab4a52579959826b5e7dce F test/schemafault.test 1936bceca55ac82c5efbcc9fc91a1933e45c8d1e1d106b9a7e56c972a5a2a51e F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 +F test/seekscan1.test d79c97de5bb1dd1fd466687f3014add514fddf8248c57baf51d749c7dfd573d8 F test/select1.test 692e84cfa29c405854c69e8a4027183d64c22952866a123fabbce741a379e889 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 -F test/select3.test ce4f78bbc809b0513f960f1ee84cdbc5af50ba112c343d5266558a8b2468f656 +F test/select3.test 8d04b66df7475275a65f7e4a786d6a724c30bd9929f8ae5bd59c8d3d6e75e6cd F test/select4.test f0684d3da3bccacbe2a1ebadf6fb49d9df6f53acb4c6ebc228a88d0d6054cc7b F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f390ae F test/select6.test 9b2fb4ffedf52e1b5703cfcae1212e7a4a063f014c0458d78d29aca3db766d1f F test/select7.test f659f231489349e8c5734e610803d7654207318f F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d F test/select9.test f7586b207ce2304ab80dc93d3146469a28fd4403621dd3a82d06644563d3c812 -F test/selectA.test 985df11dfc58dedd6f8124dea32754566346b93e294f26291b5c96d10c8cc889 +F test/selectA.test 6aef8b2136a4ac7a3e2e4161d2b8ca7bc6ebe2779de084f9bb66ca9e2323a937 F test/selectB.test 954e4e49cf1f896d61794e440669e03a27ceea25 F test/selectC.test fec14c9015ed4ec941508bbc144f30b42e40ac34a4bb33001450369865dd0b75 F test/selectD.test 6d1909b49970bf92f45ce657505befcef5fc7cbc13544e18103a316d32189bfb @@ -1415,13 +1485,13 @@ F test/shared4.test c75f476804e76e26bf6fa0e7b421fb0ca7d07558 F test/shared6.test 866bb4982c45ce216c61ded5e8fde4e7e2f3ffa9 F test/shared7.test a81e99f83e6c51b02ac99c96fb3a2a7b5978c956 F test/shared8.test 933ed7d71f598bb6c7a8c192a3cd30f2562fdccf514df383798599c34ffa672f -F test/shared9.test 5f2a8f79b4d6c7d107a01ffa1ed05ae7e6333e21 -F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69e281 -F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e +F test/shared9.test 600a257fe9d8b0272746b230e761aa1bd8802ca4cf3ba5b2136b9204f3d51efa +F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e212700 +F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 F test/shell1.test e4b4de56f454708e0747b52915135baa2cbfec4965406d6eaf02a4a5c22a9880 -F test/shell2.test c536c2aab4852608f8a606262330797abc4d964a4c2c782a7760f54ea1f17a6a +F test/shell2.test 1190b951373fdfe719bc6ac16962bc743dfa4355db8ae546c0bb9bf559a28d4a F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a F test/shell4.test 7dc8a515705bc093d8ffe381670e8fa7a969661e8ed177c35c847e3c6dfc35e2 F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b @@ -1461,13 +1531,13 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest1.c 61f8a72bbcc80edb0b95e957f108feb4013ff3d08721cc87ae1865fd4d20652d +F test/speedtest1.c 645dbf022337116fcef60ac2579e8d25c94e4475109e2c6f48005f302efe7b09 F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb F test/sqldiff1.test 182058e09c7082de5c6a470ff9c291337bbeb650052c2cc68fbb3d7e25861d91 -F test/sqllimits1.test f46f405d754e702227dcdf5b73f94fffcd48c676e845afa37329e4ce2e32ccbf +F test/sqllimits1.test 69d110987dbdb4bea9dbc3f151c93b4697ae6cd7e6c3a519119dc1140c8607fd F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805 F test/stat.test 123212a20ceb496893d5254a5f6c76442ce549fdc08d1702d8288a2bbaac8408 @@ -1502,12 +1572,13 @@ F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cb F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900 F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 -F test/temptable2.test d2940417496e2b9548e01d09990763fbe88c316504033256d51493e1f1a5ce6a +F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 76771269dcc20b2c2d1d6f1175dd50d1eebddc004aebac865483f1829a5cd398 -F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef -F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 +F test/tester.tcl 65c29b6f1dbf71b0e59a7b221d7e849dfa5a55fa7d0a2902811e8abdecdb1d44 +F test/testrunner.tcl 86b57135754ab2160aeb04b4829d321fb285a5cfa7a505fe61d69aed605854cc +F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 +F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 F test/thread004.test f51dfc3936184aaf73ee85f315224baad272a87f F test/thread005.test 50d10b5684399676174bd96c94ad4250b1a2c8b6 @@ -1547,7 +1618,7 @@ F test/tkt-78e04e52ea.test b731f2ab7d1c2482ac5152097da02ef4805a45147ba9498d3cd9d F test/tkt-7a31705a7e6.test 9e9c057b6a9497c8f7ba7b16871029414ccf6550e7345d9085d6d71c9a56bb6f F test/tkt-7bbfb7d442.test e87b59e620700b5a52ecd92f05d56686c1cad9e1aa17456eada55e0bb821b698 F test/tkt-80ba201079.test 105a721e6aad0ae3c5946d7615d1e4d03f6145b8 -F test/tkt-80e031a00f.test 9ee36348b761bf7c14261e002b75a4c0d5a04d4c +F test/tkt-80e031a00f.test 7c93af53f43527f50020983a4bcc39b077e77c7362d7af8e04a5fd155fe5e829 F test/tkt-8454a207b9.test aff2e76143cfa443ddce6f7d85968a2e9b57a3deb0b881b730120740555f9e2f F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed @@ -1575,7 +1646,7 @@ F test/tkt-d82e3f3721.test bcc0dfba658d15bab30fd4a9320c9e35d214ce30 F test/tkt-f3e5abed55.test d5a0126118142d13e27f6ce9f4c47096e9321c00 F test/tkt-f67b41381a.test 9120eab5e949969a29087e01bf57ac6a52b6c06c16be41091a74c2a863ffc660 F test/tkt-f777251dc7a.test d1a8fc3eefb7a9e64d19ff24d5c8c94c34a632fb -F test/tkt-f7b4edec.test d998a08ff2b18b7f62edce8e3044317c45efe6c7 +F test/tkt-f7b4edec.test a0d9cf5023af8bfc066e71128f325fd4831c6c6761cad35e451d35c8492f5cf1 F test/tkt-f973c7ac31.test 28ef85c7f015477916795246d8286aeda39d4ead F test/tkt-fa7bf5ec.test 9102dfea58aa371d78969da735f9392c57e2e035 F test/tkt-fc62af4523.test 72825d3febdedcd5593a27989fc05accdbfc2bb4 @@ -1699,7 +1770,7 @@ F test/tt3_vacuum.c 71b254cde1fc49d6c8c44efd54f4668f3e57d7b3a8f4601ade069f75a999 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a -F test/unionall.test fee6e6adebe5446938475432ae8b042835a286c7e2efdc1b186d2182dfb93ffb +F test/unionall.test bfeeea6c18c09a46f7e8bed69173e31c71336152e6253fb37c7257c76509f0e4 F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1 F test/unionallfault.test 652bfbb630e6c43135965dc1e8f0a9a791da83aec885d626a632fe1909c56f73 F test/unionvtab.test e1704ab1b4c1bb3ffc9da4681f8e85a0b909fd80b937984fc94b27415ac8e5a4 @@ -1722,22 +1793,24 @@ F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3 F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 F test/upsert5.test fff0dcfce73c649204543088d8e5bde01172676063ec9b8f8fc7f195abc386fe F test/upsertfault.test f21ca47740841fdb4d61acfa7b17646d773e67724fe8c185b71c018db8a94b35 -F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568 +F test/uri.test c1abaaaa28e9422d61e5f3f9cbc8ef993ec49fe802f581520731708561d49384 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 F test/utf16align.test 9fde0bb5d3a821594aa68c6829ab9c5453a084384137ebb9f6153e2d678039da -F test/vacuum-into.test f0b8c091df5305728b6973e9cce4166c861955b650dd3c599cb045d7160d3971 +F test/vacuum-into.test e0e3406845be4cf1b44db354179e5d9437e38bc267e4ac8e8dc617f9c3c903ab F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d F test/vacuum2.test 9fd45ce6ce29f5614c249e03938d3567c06a9e772d4f155949f8eafe2d8af520 F test/vacuum3.test d9d9a04ee58c485b94694fd4f68cffaba49c32234fdefe1ac1a622c5e17d4ce3 F test/vacuum4.test 7ea76b769fffeb41f925303b04cbcf5a5bbeabe55e4c60ae754ff24eeeb7c010 F test/vacuum5.test 263b144d537e92ad8e9ca8a73cc6e1583f41cfd0dda9432b87f7806174a2f48c -F test/vacuum6.test d3173a54edc81d13d99e4cf4972232b3cbb52f1d56ed48c3a939ef4e751c1ee8 -F test/vacuummem.test 7b42abb3208bd82dd23a7536588396f295a314f2 +F test/vacuum6.test b137b04bf3392d3f5c3b8fda0ce85a6775a70ca112f6559f74ff52dc9ce042fd +F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 -F test/view.test d654fbadae82f936c2a820bbc892592085467548ff59e88acef201416e9fe48a +F test/view.test d16e49e89ada6137d1447777ef2a74574526a3b024e6733bf53ae2960da8c17c F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 +F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 +F test/vt02.c 33ecddc0832d4cd24e9e9fa83d868981b1e049462f4ec9080710353f6479a534 F test/vtab1.test 09a72330d0f31eda2ffaa828b06a6b917fb86250ee72de0301570af725774c07 F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e @@ -1765,7 +1838,7 @@ F test/vtabdrop.test 65d4cf6722972e5499bdaf0c0d70ee3b8133944a4e4bc31862563f32a7e F test/vtabrhs1.test 9b5ecbc74a689500c33a4b2b36761f9bcc22fcc4e3f9d21066ee0c9c74cf5f6c F test/wal.test b7cc6984709f54afbf8441747ced1f646af120bf0c1b1d847bfa39306fbea089 F test/wal2.test 31f6e2c404b9f2cdf9ca19b105a1742fdc19653c2c936da39e3658c617524046 -F test/wal3.test 2a93004bc0fb2b5c29888964024695bade278ab2 +F test/wal3.test 5de023bb862fd1eb9d2ad26fa8d9c43abb5370582e5b08b2ae0d6f93661bc310 F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c F test/wal5.test 9c11da7aeccd83a46d79a556ad11a18d3cb15aa9 F test/wal6.test b602704e4b066199bc89d91ca9000f335dcf4572 @@ -1794,14 +1867,14 @@ F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db8 F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20 F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 -F test/walsetlk.test 3185bebc90557e0d611442c8d64f7a0cb7b06f8e156eea37a4a7358f722715be -F test/walshared.test 0befc811dcf0b287efae21612304d15576e35417 +F test/walsetlk.test 34c901443b31ab720afc463f5b236c86ca5c4134402573dce91aa0761de8db5a +F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370dbc7a3 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747 -F test/walvfs.test bccb3e0d235ef85e276f491d34db32c9ada1ea67be8d9f10aabe7b30319ec656 +F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1 F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec -F test/wapptest.tcl 899594e25684861d5b0c0880fb012364def50ef8097041b8ddf74be5ba7fa270 x -F test/where.test d13cd7c24e80009d2b54e2f7a8893c457afa49c64f99359c9eb3fe668ba1d9d4 +F test/wapptest.tcl 1bea58a6a8e68a73f542ee4fca28b771b84ed803bd0c9e385087070b3d747b3c x +F test/where.test c033c4c156e463fe1074a56dc914dfaa91de82b0dcf8a02f5fbc67cebecfdd17 F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6 F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 @@ -1827,11 +1900,12 @@ F test/wherefault.test 6cf2a9c5712952d463d3f45ebee7f6caf400984df51a195d884cfb7eb F test/wherelfault.test 9012e4ef5259058b771606616bd007af5d154e64cc25fa9fd4170f6411db44e3 F test/wherelimit.test afb46397c6d7e964e6e294ba3569864a0c570fe3807afc634236c2b752372f31 F test/wherelimit2.test 657a3f24aadee62d058c5091ea682dc4af4b95ffe32f137155be49799a58e721 +F test/widetab1.test c296a98e123762de79917350e45fa33fdf88577a2571eb3a64c8bf7e44ef74d1 F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2aeee74 -F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972 +F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test ae87c4ea4e689725c7e1c826ab2b10704d5b6d31f9b9e5abadded996ba53a1d4 +F test/window1.test 08bac934238ff66987635ea3367c5aed5b4c088c414441beaba426e68a8257c1 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -1849,13 +1923,14 @@ F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af F test/windowB.test f2fb42b864b0cf431c956407583e9478a74c3642bdf8737fdcb6ff4a40298b07 F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2 F test/windowD.test 65cf5a765fb8072450e8a0de2979ce7f09a38d87724fe1280c6444073e3da49b +F test/windowE.test 6ba0c8048e4cc02b942e56640f8fcd50fd7ca72c876656c40f6baf42e316684c F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df F test/with1.test 9ad67fdeb2bbd808a5763c9060e214ea232f9b18d846ea3a59756dc38bdc3880 F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df -F test/with3.test ad32d13ad50661e6fa305f62a0717649c348792e7b658bf2644976227a9e0373 +F test/with3.test 1e2e8d5e7b1d955342d0d18c250aaaa6e6bcf36ef2a818477bd01cb74f9a5d66 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8 F test/with6.test c18592592b5a1c5802fd4e933d506f7b34ebbe8fdd585229793e960ae58d433f @@ -1876,14 +1951,14 @@ F test/zipfile.test 0d8758d8c0d63f16644f959689f78969d223789d998964276554039f067b F test/zipfile2.test 9903388a602a3834189857a985106ff95c3bba6a3969e0134127df991889db5d F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb44 -F tool/GetTclKit.bat e95747c0f7a9fe279a9979178b71f6431a21f945b390fc3120244897ff3f5135 +F tool/GetTclKit.bat d84033c6a93dfe735d247f48ba00292a1cc284dcf69963e5e672444e04534bbf F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91 -F tool/build-all-msvc.bat c12328d06c45fec8baada5949e3d5af54bf8c887 x +F tool/build-all-msvc.bat c817b716e0edeecaf265a6775b63e5f45c34a6544f1d4114a222701ed5ac79ab x F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367 F tool/cg_anno.tcl c1f875f5a4c9caca3d59937b16aff716f8b1883935f1b4c9ae23124705bc8099 x F tool/checkSpacing.c 810e51703529a204fc4e1eb060e9ab663e3c06d2 F tool/dbhash.c 5da0c61032d23d74f2ab84ffc5740f0e8abec94f2c45c0b4306be7eb3ae96df0 -F tool/dbtotxt.c 2620a354d109b70d71bf758d2a64e042d74e0aaa5bd6e447900ed3542410d7f9 +F tool/dbtotxt.c ca48d34eaca6d6b6e4bd6a7be2b72caf34475869054240244c60fa7e69a518d6 F tool/dbtotxt.md c9a57af8739957ef36d2cfad5c4b1443ff3688ed33e4901ee200c8b651f43f3c F tool/enlargedb.c 3e8b2612b985cfa7e3e8800031ee191b43ae80de96abb5abbd5eada62651ee21 F tool/extract-sqlite3h.tcl 069ceab0cee26cba99952bfa08c0b23e35941c837acabe143f0c355d96c9e2eb x @@ -1905,22 +1980,23 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl ac96a74f5e6d9dac672d5229f79c583d3357a50e7d098e473e6b2ce2f8ae1704 x +F tool/mkctimec.tcl c185cf1bdcd3d9bd3c06f77a2fd2df8a4a0d07266f992ecda75286965ba3574c x F tool/mkkeywordhash.c 35bfc41adacc4aa6ef6fca7fd0c63e0ec0534b78daf4d0cfdebe398216bbffc3 F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a895ab33 F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef -F tool/mkopcodeh.tcl 5dab48c49a25452257494e9601702ab63adaba6bd54a9b382615fa52661c8f8c +F tool/mkopcodeh.tcl bcb2bd5affb545fd219ef0304c7978e2a356407ab723f45ec8569235892c1c3f F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d -F tool/mkshellc.tcl df5d249617f9cc94d5c48eb0401673eb3f31f383ecbc54e8a13ca3dd97e89450 +F tool/mkshellc.tcl 02d0de8349ef830c0fb20d29680320bde2466e2ec422e5bd94c4317a7a7e8cc9 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f -F tool/mksqlite3c.tcl eee7e9d9c58abb1045f6ed74ad95ad26e8d22be29fdc431deea5267fb3fa049c +F tool/mksqlite3c.tcl 4fc26a9bfa0c4505b203d7ca0cf086e75ebcd4d63eb719c82f37e3fecdf23d37 F tool/mksqlite3h.tcl 1f5e4a1dbbbc43c83cc6e74fe32c6c620502240b66c7c0f33a51378e78fc4edf F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845 +F tool/omittest-msvc.tcl d6b8f501ac1d7798c4126065030f89812379012cad98a1735d6d7221492abc08 F tool/omittest.tcl 3bc9609aceea871e1ca6ed6749df9ce79b89369d22b492f6ce6078f40647cc3f F tool/opcodesum.tcl 740ed206ba8c5040018988129abbf3089a0ccf4a F tool/pagesig.c ff0ca355fd3c2398e933da5e22439bbff89b803b @@ -1936,7 +2012,7 @@ F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c F tool/showwal.c 0253c187ae16fdae9cde89e63e1dfcd3bb35e5416d066415f99e2f8cac6ab03d F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe F tool/spaceanal.tcl 1b5be34c6223cb1af06da2a10fb77863eb869b1962d055820b0a11cf2336ab45 -F tool/speed-check.sh ff74a68bb95a0341275f4d3c9a7d8a3800bd278aceecf1913295a1f0175bc3e6 +F tool/speed-check.sh 9b27e158330a6587e92214b2344cc6fadde514996c0648fc0de7955ef7a79d77 F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff @@ -1949,6 +2025,7 @@ F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaa F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 +F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37 F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d F tool/symbols.sh 1612bd947750e21e7b47befad5f6b3825b06cce0705441f903bf35ced65ae9b9 F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003 @@ -1978,10 +2055,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 6607dd010359d0f2e07e49ce69d4bfd1e57c3505df5d667cf7735facf34112d7 -R 7ddc9e480f1c6dbefb10e119ae3343ea +P ff1d456161e57314b0bb4e2e720eb1cd00f71b4bf4c459fad9fef4f11d38c19f +R 3583cb8fddea8cad93eaac66eb583e8a T +sym-release * -T +sym-version-3.39.4 * +T +sym-version-3.40.1 * U drh -Z 191c67622a74f691de0b6643916bb1a3 +Z dd3aba06a636bceff5c4750b57949095 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b5b70ff5b..8b119aa1d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a29f9949895322123f7c38fbe94c649a9d6e6c9cd0c3b41c96d694552f26b309 +df5c253c0b3dd24916e4ec7cf77d3db5294cc9fd45ae7b9c5e82ad8197f38a24 diff --git a/config.h.in b/sqlite_cfg.h.in similarity index 82% rename from config.h.in rename to sqlite_cfg.h.in index f2ba7d4fc..3adea0993 100644 --- a/config.h.in +++ b/sqlite_cfg.h.in @@ -1,4 +1,4 @@ -/* config.h.in. Generated from configure.ac by autoheader. */ +/* sqlite_cfg.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H @@ -45,16 +45,16 @@ /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H -/* Define to 1 if you have the pread() function. */ +/* Define to 1 if you have the `pread' function. */ #undef HAVE_PREAD -/* Define to 1 if you have the pread64() function. */ +/* Define to 1 if you have the `pread64' function. */ #undef HAVE_PREAD64 -/* Define to 1 if you have the pwrite() function. */ +/* Define to 1 if you have the `pwrite' function. */ #undef HAVE_PWRITE -/* Define to 1 if you have the pwrite64() function. */ +/* Define to 1 if you have the `pwrite64' function. */ #undef HAVE_PWRITE64 /* Define to 1 if you have the header file. */ @@ -63,7 +63,7 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H -/* Define to 1 if you have the strchrnul() function */ +/* Define to 1 if you have the `strchrnul' function. */ #undef HAVE_STRCHRNUL /* Define to 1 if you have the header file. */ @@ -99,9 +99,12 @@ /* Define to 1 if you have the `usleep' function. */ #undef HAVE_USLEEP -/* Define to 1 if you have the utime() library function. */ +/* Define to 1 if you have the `utime' function. */ #undef HAVE_UTIME +/* Define to 1 if you have the header file. */ +#undef HAVE_ZLIB_H + /* Define to the sub-directory in which libtool stores uninstalled libraries. */ #undef LT_OBJDIR @@ -118,12 +121,20 @@ /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME +/* Define to the home page for this package. */ +#undef PACKAGE_URL + /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS diff --git a/src/analyze.c b/src/analyze.c index 39009899a..8562b9d7f 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -953,6 +953,7 @@ static void analyzeVdbeCommentIndexWithColumnName( if( NEVER(i==XN_ROWID) ){ VdbeComment((v,"%s.rowid",pIdx->zName)); }else if( i==XN_EXPR ){ + assert( pIdx->bHasExpr ); VdbeComment((v,"%s.expr(%d)",pIdx->zName, k)); }else{ VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zCnName)); diff --git a/src/btmutex.c b/src/btmutex.c index 275a93ff2..45745b001 100644 --- a/src/btmutex.c +++ b/src/btmutex.c @@ -252,6 +252,7 @@ int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){ int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema){ Btree *p; assert( db!=0 ); + if( db->pVfs==0 && db->nDb==0 ) return 1; if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema); assert( iDb>=0 && iDbnDb ); if( !sqlite3_mutex_held(db->mutex) ) return 0; diff --git a/src/btree.c b/src/btree.c index ebfca451b..9f1bc56f6 100644 --- a/src/btree.c +++ b/src/btree.c @@ -1513,8 +1513,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE ); assert( pPage->nOverflow==0 ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - temp = 0; - src = data = pPage->aData; + data = pPage->aData; hdr = pPage->hdrOffset; cellOffset = pPage->cellOffset; nCell = pPage->nCell; @@ -1568,39 +1567,38 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ cbrk = usableSize; iCellLast = usableSize - 4; iCellStart = get2byte(&data[hdr+5]); - for(i=0; iiCellLast ){ - return SQLITE_CORRUPT_PAGE(pPage); - } - assert( pc>=iCellStart && pc<=iCellLast ); - size = pPage->xCellSize(pPage, &src[pc]); - cbrk -= size; - if( cbrkusableSize ){ - return SQLITE_CORRUPT_PAGE(pPage); - } - assert( cbrk+size<=usableSize && cbrk>=iCellStart ); - testcase( cbrk+size==usableSize ); - testcase( pc+size==usableSize ); - put2byte(pAddr, cbrk); - if( temp==0 ){ - if( cbrk==pc ) continue; - temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart); - src = temp; + if( nCell>0 ){ + temp = sqlite3PagerTempSpace(pPage->pBt->pPager); + memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart); + src = temp; + for(i=0; iiCellLast ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( pc>=iCellStart && pc<=iCellLast ); + size = pPage->xCellSize(pPage, &src[pc]); + cbrk -= size; + if( cbrkusableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( cbrk+size<=usableSize && cbrk>=iCellStart ); + testcase( cbrk+size==usableSize ); + testcase( pc+size==usableSize ); + put2byte(pAddr, cbrk); + memcpy(&data[cbrk], &src[pc], size); } - memcpy(&data[cbrk], &src[pc], size); } data[hdr+7] = 0; - defragment_out: +defragment_out: assert( pPage->nFree>=0 ); if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){ return SQLITE_CORRUPT_PAGE(pPage); @@ -1657,7 +1655,6 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ ** fragmented bytes within the page. */ memcpy(&aData[iAddr], &aData[pc], 2); aData[hdr+7] += (u8)x; - testcase( pc+x>maxPC ); return &aData[pc]; }else if( x+pc > maxPC ){ /* This slot extends off the end of the usable part of the page */ @@ -1673,9 +1670,9 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ iAddr = pc; pTmp = &aData[pc]; pc = get2byte(pTmp); - if( pc<=iAddr+size ){ + if( pc<=iAddr ){ if( pc ){ - /* The next slot in the chain is not past the end of the current slot */ + /* The next slot in the chain comes before the current slot */ *pRc = SQLITE_CORRUPT_PAGE(pPg); } return 0; @@ -1827,7 +1824,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ iFreeBlk = 0; /* Shortcut for the case when the freelist is empty */ }else{ while( (iFreeBlk = get2byte(&data[iPtr]))pPage = pCur->apPage[pCur->iPage]; } testcase( pgno==0 ); - assert( pgno!=0 || rc==SQLITE_CORRUPT - || rc==SQLITE_IOERR_NOMEM - || rc==SQLITE_NOMEM ); + assert( pgno!=0 || rc!=SQLITE_OK ); return rc; } @@ -3747,6 +3742,9 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ } } }else{ + if( pCell+4 > pPage->aData+pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } if( get4byte(pCell)==iFrom ){ put4byte(pCell, iTo); break; @@ -5253,8 +5251,6 @@ const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){ ** vice-versa). */ static int moveToChild(BtCursor *pCur, u32 newPgno){ - BtShared *pBt = pCur->pBt; - assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPageapPage[pCur->iPage] = pCur->pPage; pCur->ix = 0; pCur->iPage++; - return getAndInitPage(pBt, newPgno, &pCur->pPage, pCur, pCur->curPagerFlags); + return getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur, + pCur->curPagerFlags); } #ifdef SQLITE_DEBUG @@ -5374,7 +5371,7 @@ static int moveToRoot(BtCursor *pCur){ } sqlite3BtreeClearCursor(pCur); } - rc = getAndInitPage(pCur->pBtree->pBt, pCur->pgnoRoot, &pCur->pPage, + rc = getAndInitPage(pCur->pBt, pCur->pgnoRoot, &pCur->pPage, 0, pCur->curPagerFlags); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; @@ -6082,14 +6079,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ pPage = pCur->pPage; idx = ++pCur->ix; - if( !pPage->isInit || sqlite3FaultSim(412) ){ - /* The only known way for this to happen is for there to be a - ** recursive SQL function that does a DELETE operation as part of a - ** SELECT which deletes content out from under an active cursor - ** in a corrupt database file where the table being DELETE-ed from - ** has pages in common with the table being queried. See TH3 - ** module cov1/btree78.test testcase 220 (2018-06-08) for an - ** example. */ + if( NEVER(!pPage->isInit) || sqlite3FaultSim(412) ){ return SQLITE_CORRUPT_BKPT; } @@ -6265,8 +6255,8 @@ static int allocateBtreePage( assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) ); pPage1 = pBt->pPage1; mxPage = btreePagecount(pBt); - /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36 - ** stores stores the total number of pages on the freelist. */ + /* EVIDENCE-OF: R-21003-45125 The 4-byte big-endian integer at offset 36 + ** stores the total number of pages on the freelist. */ n = get4byte(&pPage1->aData[36]); testcase( n==mxPage-1 ); if( n>=mxPage ){ @@ -7015,12 +7005,6 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ assert( pPage->pBt->usableSize > (u32)(ptr-data) ); pc = get2byte(ptr); hdr = pPage->hdrOffset; -#if 0 /* Not required. Omit for efficiency */ - if( pcnCell*2 ){ - *pRC = SQLITE_CORRUPT_BKPT; - return; - } -#endif testcase( pc==(u32)get2byte(&data[hdr+5]) ); testcase( pc+sz==pPage->pBt->usableSize ); if( pc+sz > pPage->pBt->usableSize ){ @@ -8785,6 +8769,11 @@ static int balance(BtCursor *pCur){ }else{ break; } + }else if( sqlite3PagerPageRefcount(pPage->pDbPage)>1 ){ + /* The page being written is not a root page, and there is currently + ** more than one reference to it. This only happens if the page is one + ** of its own ancestor pages. Corruption. */ + rc = SQLITE_CORRUPT_BKPT; }else{ MemPage * const pParent = pCur->apPage[iPage-1]; int const iIdx = pCur->aiIdx[iPage-1]; @@ -11083,6 +11072,17 @@ int sqlite3BtreeIsReadonly(Btree *p){ */ int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } +/* +** If no transaction is active and the database is not a temp-db, clear +** the in-memory pager cache. +*/ +void sqlite3BtreeClearCache(Btree *p){ + BtShared *pBt = p->pBt; + if( pBt->inTransaction==TRANS_NONE ){ + sqlite3PagerClearCache(pBt->pPager); + } +} + #if !defined(SQLITE_OMIT_SHARED_CACHE) /* ** Return true if the Btree passed as the only argument is sharable. diff --git a/src/btree.h b/src/btree.h index f80ba4a97..bb29870d2 100644 --- a/src/btree.h +++ b/src/btree.h @@ -368,6 +368,8 @@ void sqlite3BtreeCursorList(Btree*); int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64); +void sqlite3BtreeClearCache(Btree*); + /* ** If we are not using shared cache, then there is no need to ** use mutexes to access the BtShared structures. So make the diff --git a/src/build.c b/src/build.c index 31ab81b09..effaeb733 100644 --- a/src/build.c +++ b/src/build.c @@ -140,6 +140,7 @@ int sqlite3DbMaskAllZero(yDbMask m){ void sqlite3FinishCoding(Parse *pParse){ sqlite3 *db; Vdbe *v; + int iDb, i; assert( pParse->pToplevel==0 ); db = pParse->db; @@ -169,7 +170,6 @@ void sqlite3FinishCoding(Parse *pParse){ if( pParse->bReturning ){ Returning *pReturning = pParse->u1.pReturning; int addrRewind; - int i; int reg; if( pReturning->nRetCol ){ @@ -206,76 +206,69 @@ void sqlite3FinishCoding(Parse *pParse){ ** transaction on each used database and to verify the schema cookie ** on each used database. */ - if( db->mallocFailed==0 - && (DbMaskNonZero(pParse->cookieMask) || pParse->pConstExpr) - ){ - int iDb, i; - assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); - sqlite3VdbeJumpHere(v, 0); - assert( db->nDb>0 ); - iDb = 0; - do{ - Schema *pSchema; - if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; - sqlite3VdbeUsesBtree(v, iDb); - pSchema = db->aDb[iDb].pSchema; - sqlite3VdbeAddOp4Int(v, - OP_Transaction, /* Opcode */ - iDb, /* P1 */ - DbMaskTest(pParse->writeMask,iDb), /* P2 */ - pSchema->schema_cookie, /* P3 */ - pSchema->iGeneration /* P4 */ - ); - if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); - VdbeComment((v, - "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); - }while( ++iDbnDb ); + assert( pParse->nErr>0 || sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); + sqlite3VdbeJumpHere(v, 0); + assert( db->nDb>0 ); + iDb = 0; + do{ + Schema *pSchema; + if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; + sqlite3VdbeUsesBtree(v, iDb); + pSchema = db->aDb[iDb].pSchema; + sqlite3VdbeAddOp4Int(v, + OP_Transaction, /* Opcode */ + iDb, /* P1 */ + DbMaskTest(pParse->writeMask,iDb), /* P2 */ + pSchema->schema_cookie, /* P3 */ + pSchema->iGeneration /* P4 */ + ); + if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); + VdbeComment((v, + "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); + }while( ++iDbnDb ); #ifndef SQLITE_OMIT_VIRTUALTABLE - for(i=0; inVtabLock; i++){ - char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); - sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); - } - pParse->nVtabLock = 0; + for(i=0; inVtabLock; i++){ + char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); + sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); + } + pParse->nVtabLock = 0; #endif - /* Once all the cookies have been verified and transactions opened, - ** obtain the required table-locks. This is a no-op unless the - ** shared-cache feature is enabled. - */ - codeTableLocks(pParse); + /* Once all the cookies have been verified and transactions opened, + ** obtain the required table-locks. This is a no-op unless the + ** shared-cache feature is enabled. + */ + codeTableLocks(pParse); - /* Initialize any AUTOINCREMENT data structures required. - */ - sqlite3AutoincrementBegin(pParse); - - /* Code constant expressions that where factored out of inner loops. - ** - ** The pConstExpr list might also contain expressions that we simply - ** want to keep around until the Parse object is deleted. Such - ** expressions have iConstExprReg==0. Do not generate code for - ** those expressions, of course. - */ - if( pParse->pConstExpr ){ - ExprList *pEL = pParse->pConstExpr; - pParse->okConstFactor = 0; - for(i=0; inExpr; i++){ - int iReg = pEL->a[i].u.iConstExprReg; - if( iReg>0 ){ - sqlite3ExprCode(pParse, pEL->a[i].pExpr, iReg); - } - } - } + /* Initialize any AUTOINCREMENT data structures required. + */ + sqlite3AutoincrementBegin(pParse); - if( pParse->bReturning ){ - Returning *pRet = pParse->u1.pReturning; - if( pRet->nRetCol ){ - sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); - } + /* Code constant expressions that where factored out of inner loops. + ** + ** The pConstExpr list might also contain expressions that we simply + ** want to keep around until the Parse object is deleted. Such + ** expressions have iConstExprReg==0. Do not generate code for + ** those expressions, of course. + */ + if( pParse->pConstExpr ){ + ExprList *pEL = pParse->pConstExpr; + pParse->okConstFactor = 0; + for(i=0; inExpr; i++){ + int iReg = pEL->a[i].u.iConstExprReg; + sqlite3ExprCode(pParse, pEL->a[i].pExpr, iReg); } + } - /* Finally, jump back to the beginning of the executable code. */ - sqlite3VdbeGoto(v, 1); + if( pParse->bReturning ){ + Returning *pRet = pParse->u1.pReturning; + if( pRet->nRetCol ){ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + } } + + /* Finally, jump back to the beginning of the executable code. */ + sqlite3VdbeGoto(v, 1); } /* Get the VDBE program ready for execution @@ -460,7 +453,7 @@ Table *sqlite3LocateTable( /* If zName is the not the name of a table in the schema created using ** CREATE, then check to see if it is the name of an virtual table that ** can be an eponymous virtual table. */ - if( pParse->disableVtab==0 && db->init.busy==0 ){ + if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){ Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ pMod = sqlite3PragmaVtabRegister(db, zName); @@ -473,7 +466,7 @@ Table *sqlite3LocateTable( #endif if( flags & LOCATE_NOERR ) return 0; pParse->checkSchema = 1; - }else if( IsVirtual(p) && pParse->disableVtab ){ + }else if( IsVirtual(p) && (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)!=0 ){ p = 0; } @@ -782,16 +775,17 @@ void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ int i; Column *pCol; assert( pTable!=0 ); + assert( db!=0 ); if( (pCol = pTable->aCol)!=0 ){ for(i=0; inCol; i++, pCol++){ assert( pCol->zCnName==0 || pCol->hName==sqlite3StrIHash(pCol->zCnName) ); sqlite3DbFree(db, pCol->zCnName); } - sqlite3DbFree(db, pTable->aCol); + sqlite3DbNNFreeNN(db, pTable->aCol); if( IsOrdinaryTable(pTable) ){ sqlite3ExprListDelete(db, pTable->u.tab.pDfltList); } - if( db==0 || db->pnBytesFreed==0 ){ + if( db->pnBytesFreed==0 ){ pTable->aCol = 0; pTable->nCol = 0; if( IsOrdinaryTable(pTable) ){ @@ -828,7 +822,8 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ ** a Table object that was going to be marked ephemeral. So do not check ** that no lookaside memory is used in this case either. */ int nLookaside = 0; - if( db && !db->mallocFailed && (pTable->tabFlags & TF_Ephemeral)==0 ){ + assert( db!=0 ); + if( !db->mallocFailed && (pTable->tabFlags & TF_Ephemeral)==0 ){ nLookaside = sqlite3LookasideUsed(db, 0); } #endif @@ -838,7 +833,7 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ pNext = pIndex->pNext; assert( pIndex->pSchema==pTable->pSchema || (IsVirtual(pTable) && pIndex->idxType!=SQLITE_IDXTYPE_APPDEF) ); - if( (db==0 || db->pnBytesFreed==0) && !IsVirtual(pTable) ){ + if( db->pnBytesFreed==0 && !IsVirtual(pTable) ){ char *zName = pIndex->zName; TESTONLY ( Index *pOld = ) sqlite3HashInsert( &pIndex->pSchema->idxHash, zName, 0 @@ -875,8 +870,9 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ } void sqlite3DeleteTable(sqlite3 *db, Table *pTable){ /* Do not delete the table until the reference count reaches zero. */ + assert( db!=0 ); if( !pTable ) return; - if( ((!db || db->pnBytesFreed==0) && (--pTable->nTabRef)>0) ) return; + if( db->pnBytesFreed==0 && (--pTable->nTabRef)>0 ) return; deleteTable(db, pTable); } @@ -2280,7 +2276,8 @@ static int isDupColumn(Index *pIdx, int nKey, Index *pPk, int iCol){ /* Recompute the colNotIdxed field of the Index. ** ** colNotIdxed is a bitmask that has a 0 bit representing each indexed -** columns that are within the first 63 columns of the table. The +** columns that are within the first 63 columns of the table and a 1 for +** all other bits (all columns that are not in the index). The ** high-order bit of colNotIdxed is always 1. All unindexed columns ** of the table have a 1. ** @@ -2308,7 +2305,7 @@ static void recomputeColumnsNotIndexed(Index *pIdx){ } } pIdx->colNotIdxed = ~m; - assert( (pIdx->colNotIdxed>>63)==1 ); + assert( (pIdx->colNotIdxed>>63)==1 ); /* See note-20221022-a */ } /* @@ -3049,7 +3046,7 @@ void sqlite3CreateView( ** the columns of the view in the pTable structure. Return the number ** of errors. If an error is seen leave an error message in pParse->zErrMsg. */ -int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ +static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ Table *pSelTab; /* A fake table from which we get the result set */ Select *pSel; /* Copy of the SELECT that implements the view */ int nErr = 0; /* Number of errors encountered */ @@ -3074,9 +3071,10 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ #ifndef SQLITE_OMIT_VIEW /* A positive nCol means the columns names for this view are - ** already known. + ** already known. This routine is not called unless either the + ** table is virtual or nCol is zero. */ - if( pTable->nCol>0 ) return 0; + assert( pTable->nCol<=0 ); /* A negative nCol is a special marker meaning that we are currently ** trying to compute the column names. If we enter this routine with @@ -3172,6 +3170,11 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ #endif /* SQLITE_OMIT_VIEW */ return nErr; } +int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ + assert( pTable!=0 ); + if( !IsVirtual(pTable) && pTable->nCol>0 ) return 0; + return viewGetColumnNames(pParse, pTable); +} #endif /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */ #ifndef SQLITE_OMIT_VIEW @@ -4037,7 +4040,7 @@ void sqlite3CreateIndex( } if( !IN_RENAME_OBJECT ){ if( !db->init.busy ){ - if( sqlite3FindTable(db, zName, 0)!=0 ){ + if( sqlite3FindTable(db, zName, pDb->zDbSName)!=0 ){ sqlite3ErrorMsg(pParse, "there is already a table named %s", zName); goto exit_create_index; } @@ -4190,6 +4193,7 @@ void sqlite3CreateIndex( j = XN_EXPR; pIndex->aiColumn[i] = XN_EXPR; pIndex->uniqNotNull = 0; + pIndex->bHasExpr = 1; }else{ j = pCExpr->iColumn; assert( j<=0x7fff ); @@ -4201,6 +4205,7 @@ void sqlite3CreateIndex( } if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){ pIndex->bHasVCol = 1; + pIndex->bHasExpr = 1; } } pIndex->aiColumn[i] = (i16)j; @@ -4690,12 +4695,13 @@ IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token *pToken){ */ void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ int i; + assert( db!=0 ); if( pList==0 ) return; assert( pList->eU4!=EU4_EXPR ); /* EU4_EXPR mode is not currently used */ for(i=0; inId; i++){ sqlite3DbFree(db, pList->a[i].zName); } - sqlite3DbFreeNN(db, pList); + sqlite3DbNNFreeNN(db, pList); } /* @@ -4898,11 +4904,12 @@ void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){ void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ int i; SrcItem *pItem; + assert( db!=0 ); if( pList==0 ) return; for(pItem=pList->a, i=0; inSrc; i++, pItem++){ - if( pItem->zDatabase ) sqlite3DbFreeNN(db, pItem->zDatabase); - sqlite3DbFree(db, pItem->zName); - if( pItem->zAlias ) sqlite3DbFreeNN(db, pItem->zAlias); + if( pItem->zDatabase ) sqlite3DbNNFreeNN(db, pItem->zDatabase); + if( pItem->zName ) sqlite3DbNNFreeNN(db, pItem->zName); + if( pItem->zAlias ) sqlite3DbNNFreeNN(db, pItem->zAlias); if( pItem->fg.isIndexedBy ) sqlite3DbFree(db, pItem->u1.zIndexedBy); if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg); sqlite3DeleteTable(db, pItem->pTab); @@ -4913,7 +4920,7 @@ void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ sqlite3ExprDelete(db, pItem->u3.pOn); } } - sqlite3DbFreeNN(db, pList); + sqlite3DbNNFreeNN(db, pList); } /* diff --git a/src/callback.c b/src/callback.c index 7d8f9dcbc..6cbe8e584 100644 --- a/src/callback.c +++ b/src/callback.c @@ -490,19 +490,21 @@ void sqlite3SchemaClear(void *p){ Hash temp2; HashElem *pElem; Schema *pSchema = (Schema *)p; + sqlite3 xdb; + memset(&xdb, 0, sizeof(xdb)); temp1 = pSchema->tblHash; temp2 = pSchema->trigHash; sqlite3HashInit(&pSchema->trigHash); sqlite3HashClear(&pSchema->idxHash); for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){ - sqlite3DeleteTrigger(0, (Trigger*)sqliteHashData(pElem)); + sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem)); } sqlite3HashClear(&temp2); sqlite3HashInit(&pSchema->tblHash); for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){ Table *pTab = sqliteHashData(pElem); - sqlite3DeleteTable(0, pTab); + sqlite3DeleteTable(&xdb, pTab); } sqlite3HashClear(&temp1); sqlite3HashClear(&pSchema->fkeyHash); diff --git a/src/ctime.c b/src/ctime.c index a9391213d..c853e5ba4 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -28,7 +28,7 @@ ** autoconf-based build */ #if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) -#include "config.h" +#include "sqlite_cfg.h" #define SQLITECONFIG_H 1 #endif @@ -193,6 +193,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_DISABLE_SKIPAHEAD_DISTINCT "DISABLE_SKIPAHEAD_DISTINCT", #endif +#ifdef SQLITE_DQS + "DQS=" CTIMEOPT_VAL(SQLITE_DQS), +#endif #ifdef SQLITE_ENABLE_8_3_NAMES "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), #endif @@ -683,9 +686,6 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_OMIT_XFER_OPT "OMIT_XFER_OPT", #endif -#ifdef SQLITE_PCACHE_SEPARATE_HEADER - "PCACHE_SEPARATE_HEADER", -#endif #ifdef SQLITE_PERFORMANCE_TRACE "PERFORMANCE_TRACE", #endif diff --git a/src/date.c b/src/date.c index 6e2ba202b..a3e58bc4e 100644 --- a/src/date.c +++ b/src/date.c @@ -276,7 +276,7 @@ static void computeJD(DateTime *p){ p->iJD = (sqlite3_int64)((X1 + X2 + D + B - 1524.5 ) * 86400000); p->validJD = 1; if( p->validHMS ){ - p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000); + p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000 + 0.5); if( p->validTZ ){ p->iJD -= p->tz*60000; p->validYMD = 0; @@ -785,7 +785,7 @@ static int parseModifier( */ if( sqlite3_strnicmp(z, "weekday ", 8)==0 && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)>0 - && (n=(int)r)==r && n>=0 && r<7 ){ + && r>=0.0 && r<7.0 && (n=(int)r)==r ){ sqlite3_int64 Z; computeYMD_HMS(p); p->validTZ = 0; diff --git a/src/dbpage.c b/src/dbpage.c index 003997b95..458f2bd50 100644 --- a/src/dbpage.c +++ b/src/dbpage.c @@ -274,12 +274,18 @@ static int dbpageColumn( } case 1: { /* data */ DbPage *pDbPage = 0; - rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); - if( rc==SQLITE_OK ){ - sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage, - SQLITE_TRANSIENT); + if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){ + /* The pending byte page. Assume it is zeroed out. Attempting to + ** request this page from the page is an SQLITE_CORRUPT error. */ + sqlite3_result_zeroblob(ctx, pCsr->szPage); + }else{ + rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); + if( rc==SQLITE_OK ){ + sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage, + SQLITE_TRANSIENT); + } + sqlite3PagerUnref(pDbPage); } - sqlite3PagerUnref(pDbPage); break; } default: { /* schema */ @@ -288,7 +294,7 @@ static int dbpageColumn( break; } } - return SQLITE_OK; + return rc; } static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ @@ -348,11 +354,12 @@ static int dbpageUpdate( pPager = sqlite3BtreePager(pBt); rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0); if( rc==SQLITE_OK ){ - rc = sqlite3PagerWrite(pDbPage); - if( rc==SQLITE_OK ){ - memcpy(sqlite3PagerGetData(pDbPage), - sqlite3_value_blob(argv[3]), - szPage); + const void *pData = sqlite3_value_blob(argv[3]); + assert( pData!=0 || pTab->db->mallocFailed ); + if( pData + && (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK + ){ + memcpy(sqlite3PagerGetData(pDbPage), pData, szPage); } } sqlite3PagerUnref(pDbPage); @@ -372,11 +379,12 @@ static int dbpageBegin(sqlite3_vtab *pVtab){ DbpageTable *pTab = (DbpageTable *)pVtab; sqlite3 *db = pTab->db; int i; - for(i=0; inDb; i++){ + int rc = SQLITE_OK; + for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; - if( pBt ) sqlite3BtreeBeginTrans(pBt, 1, 0); + if( pBt ) rc = sqlite3BtreeBeginTrans(pBt, 1, 0); } - return SQLITE_OK; + return rc; } diff --git a/src/delete.c b/src/delete.c index df378d2d5..704a3c711 100644 --- a/src/delete.c +++ b/src/delete.c @@ -61,18 +61,42 @@ void sqlite3CodeChangeCount(Vdbe *v, int regCounter, const char *zColName){ ** 1) It is a virtual table and no implementation of the xUpdate method ** has been provided ** -** 2) It is a system table (i.e. sqlite_schema), this call is not +** 2) A trigger is currently being coded and the table is a virtual table +** that is SQLITE_VTAB_DIRECTONLY or if PRAGMA trusted_schema=OFF and +** the table is not SQLITE_VTAB_INNOCUOUS. +** +** 3) It is a system table (i.e. sqlite_schema), this call is not ** part of a nested parse and writable_schema pragma has not ** been specified ** -** 3) The table is a shadow table, the database connection is in +** 4) The table is a shadow table, the database connection is in ** defensive mode, and the current sqlite3_prepare() ** is for a top-level SQL statement. */ +static int vtabIsReadOnly(Parse *pParse, Table *pTab){ + if( sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0 ){ + return 1; + } + + /* Within triggers: + ** * Do not allow DELETE, INSERT, or UPDATE of SQLITE_VTAB_DIRECTONLY + ** virtual tables + ** * Only allow DELETE, INSERT, or UPDATE of non-SQLITE_VTAB_INNOCUOUS + ** virtual tables if PRAGMA trusted_schema=ON. + */ + if( pParse->pToplevel!=0 + && pTab->u.vtab.p->eVtabRisk > + ((pParse->db->flags & SQLITE_TrustedSchema)!=0) + ){ + sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", + pTab->zName); + } + return 0; +} static int tabIsReadOnly(Parse *pParse, Table *pTab){ sqlite3 *db; if( IsVirtual(pTab) ){ - return sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0; + return vtabIsReadOnly(pParse, pTab); } if( (pTab->tabFlags & (TF_Readonly|TF_Shadow))==0 ) return 0; db = pParse->db; @@ -84,9 +108,11 @@ static int tabIsReadOnly(Parse *pParse, Table *pTab){ } /* -** Check to make sure the given table is writable. If it is not -** writable, generate an error message and return 1. If it is -** writable return 0; +** Check to make sure the given table is writable. +** +** If pTab is not writable -> generate an error message and return 1. +** If pTab is writable but other errors have occurred -> return 1. +** If pTab is writable and no prior errors -> return 0; */ int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){ if( tabIsReadOnly(pParse, pTab) ){ @@ -447,9 +473,10 @@ void sqlite3DeleteFrom( } for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->pSchema==pTab->pSchema ); - sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - sqlite3VdbeChangeP3(v, -1, memCnt ? memCnt : -1); + sqlite3VdbeAddOp3(v, OP_Clear, pIdx->tnum, iDb, memCnt ? memCnt : -1); + }else{ + sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); } } }else @@ -649,7 +676,7 @@ void sqlite3DeleteFrom( sqlite3ExprListDelete(db, pOrderBy); sqlite3ExprDelete(db, pLimit); #endif - sqlite3DbFree(db, aToOpen); + if( aToOpen ) sqlite3DbNNFreeNN(db, aToOpen); return; } /* Make sure "isView" and other macros defined above are undefined. Otherwise diff --git a/src/expr.c b/src/expr.c index c0b2bee94..7a4e59f28 100644 --- a/src/expr.c +++ b/src/expr.c @@ -55,9 +55,8 @@ char sqlite3ExprAffinity(const Expr *pExpr){ if( op==TK_REGISTER ) op = pExpr->op2; if( op==TK_COLUMN || op==TK_AGG_COLUMN ){ assert( ExprUseYTab(pExpr) ); - if( pExpr->y.pTab ){ - return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); - } + assert( pExpr->y.pTab!=0 ); + return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); } if( op==TK_SELECT ){ assert( ExprUseXSelect(pExpr) ); @@ -175,17 +174,14 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ int op = p->op; if( op==TK_REGISTER ) op = p->op2; if( op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER ){ + int j; assert( ExprUseYTab(p) ); - if( p->y.pTab!=0 ){ - /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally - ** a TK_COLUMN but was previously evaluated and cached in a register */ - int j = p->iColumn; - if( j>=0 ){ - const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]); - pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); - } - break; + assert( p->y.pTab!=0 ); + if( (j = p->iColumn)>=0 ){ + const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]); + pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); } + break; } if( op==TK_CAST || op==TK_UPLUS ){ p = p->pLeft; @@ -770,7 +766,9 @@ static void heightOfSelect(const Select *pSelect, int *pnHeight){ */ static void exprSetHeight(Expr *p){ int nHeight = p->pLeft ? p->pLeft->nHeight : 0; - if( p->pRight && p->pRight->nHeight>nHeight ) nHeight = p->pRight->nHeight; + if( NEVER(p->pRight) && p->pRight->nHeight>nHeight ){ + nHeight = p->pRight->nHeight; + } if( ExprUseXSelect(p) ){ heightOfSelect(p->x.pSelect, &nHeight); }else if( p->x.pList ){ @@ -913,15 +911,26 @@ void sqlite3ExprAttachSubtrees( sqlite3ExprDelete(db, pLeft); sqlite3ExprDelete(db, pRight); }else{ + assert( ExprUseXList(pRoot) ); + assert( pRoot->x.pSelect==0 ); if( pRight ){ pRoot->pRight = pRight; pRoot->flags |= EP_Propagate & pRight->flags; +#if SQLITE_MAX_EXPR_DEPTH>0 + pRoot->nHeight = pRight->nHeight+1; + }else{ + pRoot->nHeight = 1; +#endif } if( pLeft ){ pRoot->pLeft = pLeft; pRoot->flags |= EP_Propagate & pLeft->flags; +#if SQLITE_MAX_EXPR_DEPTH>0 + if( pLeft->nHeight>=pRoot->nHeight ){ + pRoot->nHeight = pLeft->nHeight+1; + } +#endif } - exprSetHeight(pRoot); } } @@ -1207,6 +1216,7 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){ */ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); + assert( db!=0 ); assert( !ExprUseUValue(p) || p->u.iValue>=0 ); assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); @@ -1238,12 +1248,8 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ #endif } } - if( ExprHasProperty(p, EP_MemToken) ){ - assert( !ExprHasProperty(p, EP_IntValue) ); - sqlite3DbFree(db, p->u.zToken); - } if( !ExprHasProperty(p, EP_Static) ){ - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } } void sqlite3ExprDelete(sqlite3 *db, Expr *p){ @@ -1274,8 +1280,9 @@ void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ ** pExpr to the pParse->pConstExpr list with a register number of 0. */ void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ - pParse->pConstExpr = - sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr); + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprDelete, + pExpr); } /* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the @@ -1349,7 +1356,6 @@ static int dupedExprStructSize(const Expr *p, int flags){ }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); assert( !ExprHasProperty(p, EP_OuterON) ); - assert( !ExprHasProperty(p, EP_MemToken) ); assert( !ExprHasVVAProperty(p, EP_NoReduce) ); if( p->pLeft || p->x.pList ){ nSize = EXPR_REDUCEDSIZE | EP_Reduced; @@ -1453,7 +1459,7 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ } /* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */ - pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken); + pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static); pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly); pNew->flags |= staticFlag; ExprClearVVAProperties(pNew); @@ -2029,12 +2035,13 @@ static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){ int i = pList->nExpr; struct ExprList_item *pItem = pList->a; assert( pList->nExpr>0 ); + assert( db!=0 ); do{ sqlite3ExprDelete(db, pItem->pExpr); - sqlite3DbFree(db, pItem->zEName); + if( pItem->zEName ) sqlite3DbNNFreeNN(db, pItem->zEName); pItem++; }while( --i>0 ); - sqlite3DbFreeNN(db, pList); + sqlite3DbNNFreeNN(db, pList); } void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ if( pList ) exprListDeleteNN(db, pList); @@ -3212,6 +3219,7 @@ void sqlite3CodeRhsOfIN( sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO); } if( addrOnce ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iTab); sqlite3VdbeJumpHere(v, addrOnce); /* Subroutine return */ assert( ExprUseYSub(pExpr) ); @@ -3325,7 +3333,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ pLimit = sqlite3PExpr(pParse, TK_NE, sqlite3ExprDup(db, pSel->pLimit->pLeft, 0), pLimit); } - sqlite3ExprDelete(db, pSel->pLimit->pLeft); + sqlite3ExprDeferredDelete(pParse, pSel->pLimit->pLeft); pSel->pLimit->pLeft = pLimit; }else{ /* If there is no pre-existing limit add a limit of 1 */ @@ -3778,10 +3786,7 @@ void sqlite3ExprCodeGetColumnOfTable( ){ Column *pCol; assert( v!=0 ); - if( pTab==0 ){ - sqlite3VdbeAddOp3(v, OP_Column, iTabCur, iCol, regOut); - return; - } + assert( pTab!=0 ); if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); VdbeComment((v, "%s.rowid", pTab->zName)); @@ -3839,7 +3844,7 @@ int sqlite3ExprCodeGetColumn( assert( pParse->pVdbe!=0 ); sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pTab, iTable, iColumn, iReg); if( p5 ){ - VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1); + VdbeOp *pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); if( pOp->opcode==OP_Column ) pOp->p5 = p5; } return iReg; @@ -3908,7 +3913,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ ** so that a subsequent copy will not be merged into this one. */ static void setDoNotMergeFlagOnCopy(Vdbe *v){ - if( sqlite3VdbeGetOp(v, -1)->opcode==OP_Copy ){ + if( sqlite3VdbeGetLastOp(v)->opcode==OP_Copy ){ sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergable */ } } @@ -4031,6 +4036,53 @@ static int exprCodeInlineFunction( return target; } +/* +** Check to see if pExpr is one of the indexed expressions on pParse->pIdxExpr. +** If it is, then resolve the expression by reading from the index and +** return the register into which the value has been read. If pExpr is +** not an indexed expression, then return negative. +*/ +static SQLITE_NOINLINE int sqlite3IndexedExprLookup( + Parse *pParse, /* The parsing context */ + Expr *pExpr, /* The expression to potentially bypass */ + int target /* Where to store the result of the expression */ +){ + IndexedExpr *p; + Vdbe *v; + for(p=pParse->pIdxExpr; p; p=p->pIENext){ + int iDataCur = p->iDataCur; + if( iDataCur<0 ) continue; + if( pParse->iSelfTab ){ + if( p->iDataCur!=pParse->iSelfTab-1 ) continue; + iDataCur = -1; + } + if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue; + v = pParse->pVdbe; + assert( v!=0 ); + if( p->bMaybeNullRow ){ + /* If the index is on a NULL row due to an outer join, then we + ** cannot extract the value from the index. The value must be + ** computed using the original expression. */ + int addr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_IfNullRow, p->iIdxCur, addr+3, target); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); + VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); + sqlite3VdbeGoto(v, 0); + p = pParse->pIdxExpr; + pParse->pIdxExpr = 0; + sqlite3ExprCode(pParse, pExpr, target); + pParse->pIdxExpr = p; + sqlite3VdbeJumpHere(v, addr+2); + }else{ + sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); + VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); + } + return target; + } + return -1; /* Not found */ +} + /* ** Generate code into the current Vdbe to evaluate the given @@ -4059,6 +4111,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ expr_code_doover: if( pExpr==0 ){ op = TK_NULL; + }else if( pParse->pIdxExpr!=0 + && !ExprHasProperty(pExpr, EP_Leaf) + && (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0 + ){ + return r1; }else{ assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); op = pExpr->op; @@ -4079,7 +4136,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ pCol->iSorterColumn, target); if( pCol->iColumn<0 ){ VdbeComment((v,"%s.rowid",pTab->zName)); - }else{ + }else if( ALWAYS(pTab!=0) ){ VdbeComment((v,"%s.%s", pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ @@ -4104,11 +4161,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ int aff; iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target); assert( ExprUseYTab(pExpr) ); - if( pExpr->y.pTab ){ - aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); - }else{ - aff = pExpr->affExpr; - } + assert( pExpr->y.pTab!=0 ); + aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); if( aff>SQLITE_AFF_BLOB ){ static const char zAff[] = "B\000C\000D\000E"; assert( SQLITE_AFF_BLOB=='A' ); @@ -4170,12 +4224,10 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ } } assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab, pExpr->iColumn, iTab, target, pExpr->op2); - if( pExpr->y.pTab==0 && pExpr->affExpr==SQLITE_AFF_REAL ){ - sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); - } return iReg; } case TK_INTEGER: { @@ -4674,6 +4726,21 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ case TK_IF_NULL_ROW: { int addrINR; u8 okConstFactor = pParse->okConstFactor; + AggInfo *pAggInfo = pExpr->pAggInfo; + if( pAggInfo ){ + assert( pExpr->iAgg>=0 && pExpr->iAggnColumn ); + if( !pAggInfo->directMode ){ + inReg = pAggInfo->aCol[pExpr->iAgg].iMem; + break; + } + if( pExpr->pAggInfo->useSortingIdx ){ + sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, + pAggInfo->aCol[pExpr->iAgg].iSorterColumn, + target); + inReg = target; + break; + } + } addrINR = sqlite3VdbeAddOp1(v, OP_IfNullRow, pExpr->iTable); /* Temporarily disable factoring of constant expressions, since ** even though expressions may appear to be constant, they are not @@ -5015,7 +5082,7 @@ int sqlite3ExprCodeExprList( if( inReg!=target+i ){ VdbeOp *pOp; if( copyOp==OP_Copy - && (pOp=sqlite3VdbeGetOp(v, -1))->opcode==OP_Copy + && (pOp=sqlite3VdbeGetLastOp(v))->opcode==OP_Copy && pOp->p1+pOp->p3+1==inReg && pOp->p2+pOp->p3+1==target+i && pOp->p5==0 /* The do-not-merge flag must be clear */ @@ -5214,6 +5281,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL ); assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + sqlite3VdbeTypeofColumn(v, r1); sqlite3VdbeAddOp2(v, op, r1, dest); VdbeCoverageIf(v, op==TK_ISNULL); VdbeCoverageIf(v, op==TK_NOTNULL); @@ -5388,6 +5456,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ case TK_ISNULL: case TK_NOTNULL: { r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + sqlite3VdbeTypeofColumn(v, r1); sqlite3VdbeAddOp2(v, op, r1, dest); testcase( op==TK_ISNULL ); VdbeCoverageIf(v, op==TK_ISNULL); testcase( op==TK_NOTNULL ); VdbeCoverageIf(v, op==TK_NOTNULL); @@ -5541,7 +5610,13 @@ int sqlite3ExprCompare( if( pB->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA,pB->pLeft,iTab)<2 ){ return 1; } - return 2; + if( pA->op==TK_AGG_COLUMN && pB->op==TK_COLUMN + && pB->iTable<0 && pA->iTable==iTab + ){ + /* fall through */ + }else{ + return 2; + } } assert( !ExprHasProperty(pA, EP_IntValue) ); assert( !ExprHasProperty(pB, EP_IntValue) ); @@ -5843,10 +5918,10 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); assert( pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); if( (pLeft->op==TK_COLUMN - && pLeft->y.pTab!=0 + && ALWAYS(pLeft->y.pTab!=0) && IsVirtual(pLeft->y.pTab)) || (pRight->op==TK_COLUMN - && pRight->y.pTab!=0 + && ALWAYS(pRight->y.pTab!=0) && IsVirtual(pRight->y.pTab)) ){ return WRC_Prune; @@ -6051,6 +6126,7 @@ static int exprRefToSrcList(Walker *pWalker, Expr *pExpr){ int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ Walker w; struct RefSrcList x; + assert( pParse->db!=0 ); memset(&w, 0, sizeof(w)); memset(&x, 0, sizeof(x)); w.xExprCallback = exprRefToSrcList; @@ -6067,7 +6143,7 @@ int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter); } #endif - sqlite3DbFree(pParse->db, x.aiExclude); + if( x.aiExclude ) sqlite3DbNNFreeNN(pParse->db, x.aiExclude); if( w.eCode & 0x01 ){ return 1; }else if( w.eCode ){ @@ -6098,8 +6174,8 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ int iAgg = pExpr->iAgg; Parse *pParse = pWalker->pParse; sqlite3 *db = pParse->db; - assert( pExpr->op==TK_AGG_COLUMN || pExpr->op==TK_AGG_FUNCTION ); - if( pExpr->op==TK_AGG_COLUMN ){ + if( pExpr->op!=TK_AGG_FUNCTION ){ + assert( pExpr->op==TK_AGG_COLUMN || pExpr->op==TK_IF_NULL_ROW ); assert( iAgg>=0 && iAggnColumn ); if( pAggInfo->aCol[iAgg].pCExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); @@ -6109,6 +6185,7 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ } } }else{ + assert( pExpr->op==TK_AGG_FUNCTION ); assert( iAgg>=0 && iAggnFunc ); if( pAggInfo->aFunc[iAgg].pFExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); @@ -6179,10 +6256,12 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ assert( pNC->ncFlags & NC_UAggInfo ); switch( pExpr->op ){ + case TK_IF_NULL_ROW: case TK_AGG_COLUMN: case TK_COLUMN: { testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_COLUMN ); + testcase( pExpr->op==TK_IF_NULL_ROW ); /* Check to see if the column is in one of the tables in the FROM ** clause of the aggregate query */ if( ALWAYS(pSrcList!=0) ){ @@ -6200,8 +6279,10 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ int k; pCol = pAggInfo->aCol; for(k=0; knColumn; k++, pCol++){ - if( pCol->iTable==pExpr->iTable && - pCol->iColumn==pExpr->iColumn ){ + if( pCol->iTable==pExpr->iTable + && pCol->iColumn==pExpr->iColumn + && pExpr->op!=TK_IF_NULL_ROW + ){ break; } } @@ -6216,15 +6297,17 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ pCol->iMem = ++pParse->nMem; pCol->iSorterColumn = -1; pCol->pCExpr = pExpr; - if( pAggInfo->pGroupBy ){ + if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){ int j, n; ExprList *pGB = pAggInfo->pGroupBy; struct ExprList_item *pTerm = pGB->a; n = pGB->nExpr; for(j=0; jpExpr; - if( pE->op==TK_COLUMN && pE->iTable==pExpr->iTable && - pE->iColumn==pExpr->iColumn ){ + if( pE->op==TK_COLUMN + && pE->iTable==pExpr->iTable + && pE->iColumn==pExpr->iColumn + ){ pCol->iSorterColumn = j; break; } @@ -6241,7 +6324,9 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ */ ExprSetVVAProperty(pExpr, EP_NoReduce); pExpr->pAggInfo = pAggInfo; - pExpr->op = TK_AGG_COLUMN; + if( pExpr->op==TK_COLUMN ){ + pExpr->op = TK_AGG_COLUMN; + } pExpr->iAgg = (i16)k; break; } /* endif pExpr->iTable==pItem->iCursor */ diff --git a/src/fkey.c b/src/fkey.c index a05792514..cae6bb19d 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -1439,11 +1439,12 @@ void sqlite3FkDelete(sqlite3 *db, Table *pTab){ FKey *pNext; /* Copy of pFKey->pNextFrom */ assert( IsOrdinaryTable(pTab) ); + assert( db!=0 ); for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pNext){ assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); /* Remove the FK from the fkeyHash hash table. */ - if( !db || db->pnBytesFreed==0 ){ + if( db->pnBytesFreed==0 ){ if( pFKey->pPrevTo ){ pFKey->pPrevTo->pNextTo = pFKey->pNextTo; }else{ diff --git a/src/func.c b/src/func.c index ec50a17a3..b42b2588a 100644 --- a/src/func.c +++ b/src/func.c @@ -740,7 +740,7 @@ static int patternCompare( ** c but in the other case and search the input string for either ** c or cx. */ - if( c<=0x80 ){ + if( c<0x80 ){ char zStop[3]; int bMatch; if( noCase ){ @@ -823,7 +823,13 @@ static int patternCompare( ** non-zero if there is no match. */ int sqlite3_strglob(const char *zGlobPattern, const char *zString){ - return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '['); + if( zString==0 ){ + return zGlobPattern!=0; + }else if( zGlobPattern==0 ){ + return 1; + }else { + return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '['); + } } /* @@ -831,7 +837,13 @@ int sqlite3_strglob(const char *zGlobPattern, const char *zString){ ** a miss - like strcmp(). */ int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){ - return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc); + if( zStr==0 ){ + return zPattern!=0; + }else if( zPattern==0 ){ + return 1; + }else{ + return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc); + } } /* diff --git a/src/global.c b/src/global.c index 9e3c07a51..799b1e47c 100644 --- a/src/global.c +++ b/src/global.c @@ -375,10 +375,6 @@ const char sqlite3StrBINARY[] = "BINARY"; ** ** sqlite3StdTypeAffinity[] The affinity associated with each entry ** in sqlite3StdType[]. -** -** sqlite3StdTypeMap[] The type value (as returned from -** sqlite3_column_type() or sqlite3_value_type()) -** for each entry in sqlite3StdType[]. */ const unsigned char sqlite3StdTypeLen[] = { 3, 4, 3, 7, 4, 4 }; const char sqlite3StdTypeAffinity[] = { @@ -389,14 +385,6 @@ const char sqlite3StdTypeAffinity[] = { SQLITE_AFF_REAL, SQLITE_AFF_TEXT }; -const char sqlite3StdTypeMap[] = { - 0, - SQLITE_BLOB, - SQLITE_INTEGER, - SQLITE_INTEGER, - SQLITE_FLOAT, - SQLITE_TEXT -}; const char *sqlite3StdType[] = { "ANY", "BLOB", diff --git a/src/insert.c b/src/insert.c index 9b97b99a3..1f6cdffcc 100644 --- a/src/insert.c +++ b/src/insert.c @@ -96,6 +96,7 @@ const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ aff = SQLITE_AFF_INTEGER; }else{ assert( x==XN_EXPR ); + assert( pIdx->bHasExpr ); assert( pIdx->aColExpr!=0 ); aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr); } @@ -109,6 +110,28 @@ const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ return pIdx->zColAff; } +/* +** Compute an affinity string for a table. Space is obtained +** from sqlite3DbMalloc(). The caller is responsible for freeing +** the space when done. +*/ +char *sqlite3TableAffinityStr(sqlite3 *db, const Table *pTab){ + char *zColAff; + zColAff = (char *)sqlite3DbMallocRaw(db, pTab->nCol+1); + if( zColAff ){ + int i, j; + for(i=j=0; inCol; i++){ + if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ + zColAff[j++] = pTab->aCol[i].affinity; + } + } + do{ + zColAff[j--] = 0; + }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB ); + } + return zColAff; +} + /* ** Make changes to the evolving bytecode to do affinity transformations ** of values that are about to be gathered into a row for table pTab. @@ -150,7 +173,7 @@ const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ ** Apply the type checking to that array of registers. */ void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ - int i, j; + int i; char *zColAff; if( pTab->tabFlags & TF_Strict ){ if( iReg==0 ){ @@ -159,7 +182,7 @@ void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ ** OP_MakeRecord is found */ VdbeOp *pPrev; sqlite3VdbeAppendP4(v, pTab, P4_TABLE); - pPrev = sqlite3VdbeGetOp(v, -1); + pPrev = sqlite3VdbeGetLastOp(v); assert( pPrev!=0 ); assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed ); pPrev->opcode = OP_TypeCheck; @@ -173,22 +196,11 @@ void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ } zColAff = pTab->zColAff; if( zColAff==0 ){ - sqlite3 *db = sqlite3VdbeDb(v); - zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1); + zColAff = sqlite3TableAffinityStr(0, pTab); if( !zColAff ){ - sqlite3OomFault(db); + sqlite3OomFault(sqlite3VdbeDb(v)); return; } - - for(i=j=0; inCol; i++){ - assert( pTab->aCol[i].affinity!=0 || sqlite3VdbeParser(v)->nErr>0 ); - if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ - zColAff[j++] = pTab->aCol[i].affinity; - } - } - do{ - zColAff[j--] = 0; - }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB ); pTab->zColAff = zColAff; } assert( zColAff!=0 ); @@ -197,7 +209,7 @@ void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ if( iReg ){ sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i); }else{ - assert( sqlite3VdbeGetOp(v, -1)->opcode==OP_MakeRecord + assert( sqlite3VdbeGetLastOp(v)->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed ); sqlite3VdbeChangeP4(v, -1, zColAff, i); } @@ -283,7 +295,7 @@ void sqlite3ComputeGeneratedColumns( */ sqlite3TableAffinity(pParse->pVdbe, pTab, iRegStore); if( (pTab->tabFlags & TF_HasStored)!=0 ){ - pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1); + pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); if( pOp->opcode==OP_Affinity ){ /* Change the OP_Affinity argument to '@' (NONE) for all stored ** columns. '@' is the no-op affinity and those columns have not @@ -1189,7 +1201,12 @@ void sqlite3Insert( sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+k, iRegStore); } }else{ - sqlite3ExprCode(pParse, pList->a[k].pExpr, iRegStore); + Expr *pX = pList->a[k].pExpr; + int y = sqlite3ExprCodeTarget(pParse, pX, iRegStore); + if( y!=iRegStore ){ + sqlite3VdbeAddOp2(v, + ExprHasProperty(pX, EP_Subquery) ? OP_Copy : OP_SCopy, y, iRegStore); + } } } @@ -1326,7 +1343,9 @@ void sqlite3Insert( sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert ); - sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + if( db->flags & SQLITE_ForeignKeys ){ + sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + } /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE ** constraints or (b) there are no triggers and this table is not a @@ -1410,7 +1429,7 @@ void sqlite3Insert( sqlite3UpsertDelete(db, pUpsert); sqlite3SelectDelete(db, pSelect); sqlite3IdListDelete(db, pColumn); - sqlite3DbFree(db, aRegIdx); + if( aRegIdx ) sqlite3DbNNFreeNN(db, aRegIdx); } /* Make sure "isView" and other macros defined above are undefined. Otherwise diff --git a/src/loadext.c b/src/loadext.c index dd15d9a4d..c14338f8b 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -508,7 +508,9 @@ static const sqlite3_api_routines sqlite3Apis = { 0, 0, #endif - sqlite3_db_name + sqlite3_db_name, + /* Version 3.40.0 and later */ + sqlite3_value_encoding }; /* True if x is the directory separator character diff --git a/src/main.c b/src/main.c index d905ac8ea..4008d71d6 100644 --- a/src/main.c +++ b/src/main.c @@ -824,18 +824,19 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){ db->lookaside.bMalloced = pBuf==0 ?1:0; db->lookaside.nSlot = nBig+nSm; }else{ - db->lookaside.pStart = db; + db->lookaside.pStart = 0; #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE db->lookaside.pSmallInit = 0; db->lookaside.pSmallFree = 0; - db->lookaside.pMiddle = db; + db->lookaside.pMiddle = 0; #endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ - db->lookaside.pEnd = db; + db->lookaside.pEnd = 0; db->lookaside.bDisable = 1; db->lookaside.sz = 0; db->lookaside.bMalloced = 0; db->lookaside.nSlot = 0; } + db->lookaside.pTrueEnd = db->lookaside.pEnd; assert( sqlite3LookasideUsed(db,0)==0 ); #endif /* SQLITE_OMIT_LOOKASIDE */ return SQLITE_OK; @@ -914,6 +915,7 @@ int sqlite3_db_cacheflush(sqlite3 *db){ int sqlite3_db_config(sqlite3 *db, int op, ...){ va_list ap; int rc; + sqlite3_mutex_enter(db->mutex); va_start(ap, op); switch( op ){ case SQLITE_DBCONFIG_MAINDBNAME: { @@ -979,6 +981,7 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ } } va_end(ap); + sqlite3_mutex_leave(db->mutex); return rc; } @@ -2115,7 +2118,7 @@ int sqlite3_overload_function( rc = sqlite3FindFunction(db, zName, nArg, SQLITE_UTF8, 0)!=0; sqlite3_mutex_leave(db->mutex); if( rc ) return SQLITE_OK; - zCopy = sqlite3_mprintf(zName); + zCopy = sqlite3_mprintf("%s", zName); if( zCopy==0 ) return SQLITE_NOMEM; return sqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8, zCopy, sqlite3InvalidFunction, 0, 0, sqlite3_free); @@ -3349,6 +3352,19 @@ static int openDatabase( goto opendb_out; } +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) + /* Process magic filenames ":localStorage:" and ":sessionStorage:" */ + if( zFilename && zFilename[0]==':' ){ + if( strcmp(zFilename, ":localStorage:")==0 ){ + zFilename = "file:local?vfs=kvvfs"; + flags |= SQLITE_OPEN_URI; + }else if( strcmp(zFilename, ":sessionStorage:")==0 ){ + zFilename = "file:session?vfs=kvvfs"; + flags |= SQLITE_OPEN_URI; + } + } +#endif /* SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) */ + /* Parse the filename/URI argument ** ** Only allow sensible combinations of bits in the flags argument. @@ -3379,6 +3395,12 @@ static int openDatabase( sqlite3_free(zErrMsg); goto opendb_out; } + assert( db->pVfs!=0 ); +#if SQLITE_OS_KV || defined(SQLITE_OS_KV_OPTIONAL) + if( sqlite3_stricmp(db->pVfs->zName, "kvvfs")==0 ){ + db->temp_store = 2; + } +#endif /* Open the backend database driver */ rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0, @@ -3928,6 +3950,9 @@ int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){ sqlite3BtreeSetPageSize(pBtree, 0, iNew, 0); } rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_RESET_CACHE ){ + sqlite3BtreeClearCache(pBtree); + rc = SQLITE_OK; }else{ int nSave = db->busyHandler.nBusy; rc = sqlite3OsFileControl(fd, op, pArg); @@ -4488,7 +4513,7 @@ static char *appendText(char *p, const char *z){ ** Memory layout must be compatible with that generated by the pager ** and expected by sqlite3_uri_parameter() and databaseName(). */ -char *sqlite3_create_filename( +const char *sqlite3_create_filename( const char *zDatabase, const char *zJournal, const char *zWal, @@ -4524,10 +4549,10 @@ char *sqlite3_create_filename( ** error to call this routine with any parameter other than a pointer ** previously obtained from sqlite3_create_filename() or a NULL pointer. */ -void sqlite3_free_filename(char *p){ +void sqlite3_free_filename(const char *p){ if( p==0 ) return; - p = (char*)databaseName(p); - sqlite3_free(p - 4); + p = databaseName(p); + sqlite3_free((char*)p - 4); } @@ -4778,8 +4803,8 @@ int sqlite3_snapshot_open( */ int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){ int rc = SQLITE_ERROR; - int iDb; #ifndef SQLITE_OMIT_WAL + int iDb; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ diff --git a/src/malloc.c b/src/malloc.c index c508bf752..af83743fc 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -270,18 +270,34 @@ static void mallocWithAlarm(int n, void **pp){ *pp = p; } +/* +** Maximum size of any single memory allocation. +** +** This is not a limit on the total amount of memory used. This is +** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc(). +** +** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 +** This provides a 256-byte safety margin for defense against 32-bit +** signed integer overflow bugs when computing memory allocation sizes. +** Parnoid applications might want to reduce the maximum allocation size +** further for an even larger safety margin. 0x3fffffff or 0x0fffffff +** or even smaller would be reasonable upper bounds on the size of a memory +** allocations for most applications. +*/ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 2147483391 +#endif +#if SQLITE_MAX_ALLOCATION_SIZE>2147483391 +# error Maximum size for SQLITE_MAX_ALLOCATION_SIZE is 2147483391 +#endif + /* ** Allocate memory. This routine is like sqlite3_malloc() except that it ** assumes the memory subsystem has already been initialized. */ void *sqlite3Malloc(u64 n){ void *p; - if( n==0 || n>=0x7fffff00 ){ - /* A memory allocation of a number of bytes which is near the maximum - ** signed integer value might cause an integer overflow inside of the - ** xMalloc(). Hence we limit the maximum size to 0x7fffff00, giving - ** 255 bytes of overhead. SQLite itself will never use anything near - ** this amount. The only way to reach the limit is with sqlite3_malloc() */ + if( n==0 || n>SQLITE_MAX_ALLOCATION_SIZE ){ p = 0; }else if( sqlite3GlobalConfig.bMemstat ){ sqlite3_mutex_enter(mem0.mutex); @@ -317,7 +333,7 @@ void *sqlite3_malloc64(sqlite3_uint64 n){ */ #ifndef SQLITE_OMIT_LOOKASIDE static int isLookaside(sqlite3 *db, const void *p){ - return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd); + return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pTrueEnd); } #else #define isLookaside(A,B) 0 @@ -341,18 +357,16 @@ static int lookasideMallocSize(sqlite3 *db, const void *p){ int sqlite3DbMallocSize(sqlite3 *db, const void *p){ assert( p!=0 ); #ifdef SQLITE_DEBUG - if( db==0 || !isLookaside(db,p) ){ - if( db==0 ){ - assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); - assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); - }else{ - assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); - assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); - } + if( db==0 ){ + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + }else if( !isLookaside(db,p) ){ + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); } #endif if( db ){ - if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ + if( ((uptr)p)<(uptr)(db->lookaside.pTrueEnd) ){ #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ assert( sqlite3_mutex_held(db->mutex) ); @@ -408,14 +422,11 @@ void sqlite3DbFreeNN(sqlite3 *db, void *p){ assert( db==0 || sqlite3_mutex_held(db->mutex) ); assert( p!=0 ); if( db ){ - if( db->pnBytesFreed ){ - measureAllocationSize(db, p); - return; - } if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); #ifdef SQLITE_DEBUG memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */ #endif @@ -426,6 +437,7 @@ void sqlite3DbFreeNN(sqlite3 *db, void *p){ #endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); #ifdef SQLITE_DEBUG memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */ #endif @@ -434,6 +446,10 @@ void sqlite3DbFreeNN(sqlite3 *db, void *p){ return; } } + if( db->pnBytesFreed ){ + measureAllocationSize(db, p); + return; + } } assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); @@ -441,6 +457,43 @@ void sqlite3DbFreeNN(sqlite3 *db, void *p){ sqlite3MemdebugSetType(p, MEMTYPE_HEAP); sqlite3_free(p); } +void sqlite3DbNNFreeNN(sqlite3 *db, void *p){ + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( p!=0 ); + if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pSmallFree; + db->lookaside.pSmallFree = pBuf; + return; + } +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pFree; + db->lookaside.pFree = pBuf; + return; + } + } + if( db->pnBytesFreed ){ + measureAllocationSize(db, p); + return; + } + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + sqlite3_free(p); +} void sqlite3DbFree(sqlite3 *db, void *p){ assert( db==0 || sqlite3_mutex_held(db->mutex) ); if( p ) sqlite3DbFreeNN(db, p); diff --git a/src/mem5.c b/src/mem5.c index b61b93e11..02f4c2744 100644 --- a/src/mem5.c +++ b/src/mem5.c @@ -424,9 +424,13 @@ static int memsys5Roundup(int n){ if( n<=mem5.szAtom ) return mem5.szAtom; return mem5.szAtom*2; } - if( n>0x40000000 ) return 0; + if( n>0x10000000 ){ + if( n>0x40000000 ) return 0; + if( n>0x20000000 ) return 0x40000000; + return 0x20000000; + } for(iFullSz=mem5.szAtom*8; iFullSz=n ) return iFullSz/2; + if( (iFullSz/2)>=(i64)n ) return iFullSz/2; return iFullSz; } diff --git a/src/memdb.c b/src/memdb.c index 31b2324b9..657cb9ca6 100644 --- a/src/memdb.c +++ b/src/memdb.c @@ -109,6 +109,7 @@ static int memdbTruncate(sqlite3_file*, sqlite3_int64 size); static int memdbSync(sqlite3_file*, int flags); static int memdbFileSize(sqlite3_file*, sqlite3_int64 *pSize); static int memdbLock(sqlite3_file*, int); +static int memdbUnlock(sqlite3_file*, int); /* static int memdbCheckReservedLock(sqlite3_file*, int *pResOut);// not used */ static int memdbFileControl(sqlite3_file*, int op, void *pArg); /* static int memdbSectorSize(sqlite3_file*); // not used */ @@ -167,7 +168,7 @@ static const sqlite3_io_methods memdb_io_methods = { memdbSync, /* xSync */ memdbFileSize, /* xFileSize */ memdbLock, /* xLock */ - memdbLock, /* xUnlock - same as xLock in this case */ + memdbUnlock, /* xUnlock */ 0, /* memdbCheckReservedLock, */ /* xCheckReservedLock */ memdbFileControl, /* xFileControl */ 0, /* memdbSectorSize,*/ /* xSectorSize */ @@ -368,39 +369,81 @@ static int memdbLock(sqlite3_file *pFile, int eLock){ MemFile *pThis = (MemFile*)pFile; MemStore *p = pThis->pStore; int rc = SQLITE_OK; - if( eLock==pThis->eLock ) return SQLITE_OK; + if( eLock<=pThis->eLock ) return SQLITE_OK; memdbEnter(p); - if( eLock>SQLITE_LOCK_SHARED ){ - if( p->mFlags & SQLITE_DESERIALIZE_READONLY ){ - rc = SQLITE_READONLY; - }else if( pThis->eLock<=SQLITE_LOCK_SHARED ){ - if( p->nWrLock ){ - rc = SQLITE_BUSY; - }else{ - p->nWrLock = 1; + + assert( p->nWrLock==0 || p->nWrLock==1 ); + assert( pThis->eLock<=SQLITE_LOCK_SHARED || p->nWrLock==1 ); + assert( pThis->eLock==SQLITE_LOCK_NONE || p->nRdLock>=1 ); + + if( eLock>SQLITE_LOCK_SHARED && (p->mFlags & SQLITE_DESERIALIZE_READONLY) ){ + rc = SQLITE_READONLY; + }else{ + switch( eLock ){ + case SQLITE_LOCK_SHARED: { + assert( pThis->eLock==SQLITE_LOCK_NONE ); + if( p->nWrLock>0 ){ + rc = SQLITE_BUSY; + }else{ + p->nRdLock++; + } + break; + }; + + case SQLITE_LOCK_RESERVED: + case SQLITE_LOCK_PENDING: { + assert( pThis->eLock>=SQLITE_LOCK_SHARED ); + if( ALWAYS(pThis->eLock==SQLITE_LOCK_SHARED) ){ + if( p->nWrLock>0 ){ + rc = SQLITE_BUSY; + }else{ + p->nWrLock = 1; + } + } + break; + } + + default: { + assert( eLock==SQLITE_LOCK_EXCLUSIVE ); + assert( pThis->eLock>=SQLITE_LOCK_SHARED ); + if( p->nRdLock>1 ){ + rc = SQLITE_BUSY; + }else if( pThis->eLock==SQLITE_LOCK_SHARED ){ + p->nWrLock = 1; + } + break; } } - }else if( eLock==SQLITE_LOCK_SHARED ){ - if( pThis->eLock > SQLITE_LOCK_SHARED ){ - assert( p->nWrLock==1 ); - p->nWrLock = 0; - }else if( p->nWrLock ){ - rc = SQLITE_BUSY; - }else{ - p->nRdLock++; + } + if( rc==SQLITE_OK ) pThis->eLock = eLock; + memdbLeave(p); + return rc; +} + +/* +** Unlock an memdb-file. +*/ +static int memdbUnlock(sqlite3_file *pFile, int eLock){ + MemFile *pThis = (MemFile*)pFile; + MemStore *p = pThis->pStore; + if( eLock>=pThis->eLock ) return SQLITE_OK; + memdbEnter(p); + + assert( eLock==SQLITE_LOCK_SHARED || eLock==SQLITE_LOCK_NONE ); + if( eLock==SQLITE_LOCK_SHARED ){ + if( ALWAYS(pThis->eLock>SQLITE_LOCK_SHARED) ){ + p->nWrLock--; } }else{ - assert( eLock==SQLITE_LOCK_NONE ); - if( pThis->eLock>SQLITE_LOCK_SHARED ){ - assert( p->nWrLock==1 ); - p->nWrLock = 0; + if( pThis->eLock>SQLITE_LOCK_SHARED ){ + p->nWrLock--; } - assert( p->nRdLock>0 ); p->nRdLock--; } - if( rc==SQLITE_OK ) pThis->eLock = eLock; + + pThis->eLock = eLock; memdbLeave(p); - return rc; + return SQLITE_OK; } #if 0 @@ -510,7 +553,7 @@ static int memdbOpen( memset(pFile, 0, sizeof(*pFile)); szName = sqlite3Strlen30(zName); - if( szName>1 && zName[0]=='/' ){ + if( szName>1 && (zName[0]=='/' || zName[0]=='\\') ){ int i; #ifndef SQLITE_MUTEX_OMIT sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); @@ -857,6 +900,13 @@ int sqlite3_deserialize( return rc; } +/* +** Return true if the VFS is the memvfs. +*/ +int sqlite3IsMemdb(const sqlite3_vfs *pVfs){ + return pVfs==&memdb_vfs; +} + /* ** This routine is called when the extension is loaded. ** Register the new VFS. diff --git a/src/memjournal.c b/src/memjournal.c index a4c17eedd..934380107 100644 --- a/src/memjournal.c +++ b/src/memjournal.c @@ -359,6 +359,8 @@ int sqlite3JournalOpen( ){ MemJournal *p = (MemJournal*)pJfd; + assert( zName || nSpill<0 || (flags & SQLITE_OPEN_EXCLUSIVE) ); + /* Zero the file-handle object. If nSpill was passed zero, initialize ** it using the sqlite3OsOpen() function of the underlying VFS. In this ** case none of the code in this module is executed as a result of calls diff --git a/src/os.c b/src/os.c index 0c3c9d898..e2914e03c 100644 --- a/src/os.c +++ b/src/os.c @@ -106,9 +106,11 @@ int sqlite3OsFileSize(sqlite3_file *id, i64 *pSize){ } int sqlite3OsLock(sqlite3_file *id, int lockType){ DO_OS_MALLOC_TEST(id); + assert( lockType>=SQLITE_LOCK_SHARED && lockType<=SQLITE_LOCK_EXCLUSIVE ); return id->pMethods->xLock(id, lockType); } int sqlite3OsUnlock(sqlite3_file *id, int lockType){ + assert( lockType==SQLITE_LOCK_NONE || lockType==SQLITE_LOCK_SHARED ); return id->pMethods->xUnlock(id, lockType); } int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut){ @@ -223,6 +225,7 @@ int sqlite3OsOpen( ** down into the VFS layer. Some SQLITE_OPEN_ flags (for example, ** SQLITE_OPEN_FULLMUTEX or SQLITE_OPEN_SHAREDCACHE) are blocked before ** reaching the VFS. */ + assert( zPath || (flags & SQLITE_OPEN_EXCLUSIVE) ); rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x1087f7f, pFlagsOut); assert( rc==SQLITE_OK || pFile->pMethods==0 ); return rc; diff --git a/src/os_kv.c b/src/os_kv.c new file mode 100644 index 000000000..322588be9 --- /dev/null +++ b/src/os_kv.c @@ -0,0 +1,972 @@ +/* +** 2022-09-06 +** +** 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 an experimental VFS layer that operates on a +** Key/Value storage engine where both keys and values must be pure +** text. +*/ +#include +#if SQLITE_OS_KV || (SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)) + +/***************************************************************************** +** Debugging logic +*/ + +/* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */ +#if 0 +#define SQLITE_KV_TRACE(X) printf X +#else +#define SQLITE_KV_TRACE(X) +#endif + +/* SQLITE_KV_LOG() is used for tracing calls to the VFS interface */ +#if 0 +#define SQLITE_KV_LOG(X) printf X +#else +#define SQLITE_KV_LOG(X) +#endif + + +/* +** Forward declaration of objects used by this VFS implementation +*/ +typedef struct KVVfsFile KVVfsFile; + +/* A single open file. There are only two files represented by this +** VFS - the database and the rollback journal. +*/ +struct KVVfsFile { + sqlite3_file base; /* IO methods */ + const char *zClass; /* Storage class */ + int isJournal; /* True if this is a journal file */ + unsigned int nJrnl; /* Space allocated for aJrnl[] */ + char *aJrnl; /* Journal content */ + int szPage; /* Last known page size */ + sqlite3_int64 szDb; /* Database file size. -1 means unknown */ +}; + +/* +** Methods for KVVfsFile +*/ +static int kvvfsClose(sqlite3_file*); +static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int kvvfsWriteDb(sqlite3_file*,const void*,int iAmt, sqlite3_int64); +static int kvvfsWriteJrnl(sqlite3_file*,const void*,int iAmt, sqlite3_int64); +static int kvvfsTruncateDb(sqlite3_file*, sqlite3_int64 size); +static int kvvfsTruncateJrnl(sqlite3_file*, sqlite3_int64 size); +static int kvvfsSyncDb(sqlite3_file*, int flags); +static int kvvfsSyncJrnl(sqlite3_file*, int flags); +static int kvvfsFileSizeDb(sqlite3_file*, sqlite3_int64 *pSize); +static int kvvfsFileSizeJrnl(sqlite3_file*, sqlite3_int64 *pSize); +static int kvvfsLock(sqlite3_file*, int); +static int kvvfsUnlock(sqlite3_file*, int); +static int kvvfsCheckReservedLock(sqlite3_file*, int *pResOut); +static int kvvfsFileControlDb(sqlite3_file*, int op, void *pArg); +static int kvvfsFileControlJrnl(sqlite3_file*, int op, void *pArg); +static int kvvfsSectorSize(sqlite3_file*); +static int kvvfsDeviceCharacteristics(sqlite3_file*); + +/* +** Methods for sqlite3_vfs +*/ +static int kvvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int kvvfsDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int kvvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int kvvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *kvvfsDlOpen(sqlite3_vfs*, const char *zFilename); +static int kvvfsRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int kvvfsSleep(sqlite3_vfs*, int microseconds); +static int kvvfsCurrentTime(sqlite3_vfs*, double*); +static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); + +static sqlite3_vfs sqlite3OsKvvfsObject = { + 1, /* iVersion */ + sizeof(KVVfsFile), /* szOsFile */ + 1024, /* mxPathname */ + 0, /* pNext */ + "kvvfs", /* zName */ + 0, /* pAppData */ + kvvfsOpen, /* xOpen */ + kvvfsDelete, /* xDelete */ + kvvfsAccess, /* xAccess */ + kvvfsFullPathname, /* xFullPathname */ + kvvfsDlOpen, /* xDlOpen */ + 0, /* xDlError */ + 0, /* xDlSym */ + 0, /* xDlClose */ + kvvfsRandomness, /* xRandomness */ + kvvfsSleep, /* xSleep */ + kvvfsCurrentTime, /* xCurrentTime */ + 0, /* xGetLastError */ + kvvfsCurrentTimeInt64 /* xCurrentTimeInt64 */ +}; + +/* Methods for sqlite3_file objects referencing a database file +*/ +static sqlite3_io_methods kvvfs_db_io_methods = { + 1, /* iVersion */ + kvvfsClose, /* xClose */ + kvvfsReadDb, /* xRead */ + kvvfsWriteDb, /* xWrite */ + kvvfsTruncateDb, /* xTruncate */ + kvvfsSyncDb, /* xSync */ + kvvfsFileSizeDb, /* xFileSize */ + kvvfsLock, /* xLock */ + kvvfsUnlock, /* xUnlock */ + kvvfsCheckReservedLock, /* xCheckReservedLock */ + kvvfsFileControlDb, /* xFileControl */ + kvvfsSectorSize, /* xSectorSize */ + kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ +}; + +/* Methods for sqlite3_file objects referencing a rollback journal +*/ +static sqlite3_io_methods kvvfs_jrnl_io_methods = { + 1, /* iVersion */ + kvvfsClose, /* xClose */ + kvvfsReadJrnl, /* xRead */ + kvvfsWriteJrnl, /* xWrite */ + kvvfsTruncateJrnl, /* xTruncate */ + kvvfsSyncJrnl, /* xSync */ + kvvfsFileSizeJrnl, /* xFileSize */ + kvvfsLock, /* xLock */ + kvvfsUnlock, /* xUnlock */ + kvvfsCheckReservedLock, /* xCheckReservedLock */ + kvvfsFileControlJrnl, /* xFileControl */ + kvvfsSectorSize, /* xSectorSize */ + kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ +}; + +/****** Storage subsystem **************************************************/ +#include +#include +#include + +/* Forward declarations for the low-level storage engine +*/ +static int kvstorageWrite(const char*, const char *zKey, const char *zData); +static int kvstorageDelete(const char*, const char *zKey); +static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); +#define KVSTORAGE_KEY_SZ 32 + +/* Expand the key name with an appropriate prefix and put the result +** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least +** KVSTORAGE_KEY_SZ bytes. +*/ +static void kvstorageMakeKey( + const char *zClass, + const char *zKeyIn, + char *zKeyOut +){ + sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn); +} + +/* Write content into a key. zClass is the particular namespace of the +** underlying key/value store to use - either "local" or "session". +** +** Both zKey and zData are zero-terminated pure text strings. +** +** Return the number of errors. +*/ +static int kvstorageWrite( + const char *zClass, + const char *zKey, + const char *zData +){ + FILE *fd; + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); + fd = fopen(zXKey, "wb"); + if( fd ){ + SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey, + (int)strlen(zData), zData, + strlen(zData)>50 ? "..." : "")); + fputs(zData, fd); + fclose(fd); + return 0; + }else{ + return 1; + } +} + +/* Delete a key (with its corresponding data) from the key/value +** namespace given by zClass. If the key does not previously exist, +** this routine is a no-op. +*/ +static int kvstorageDelete(const char *zClass, const char *zKey){ + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); + unlink(zXKey); + SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey)); + return 0; +} + +/* Read the value associated with a zKey from the key/value namespace given +** by zClass and put the text data associated with that key in the first +** nBuf bytes of zBuf[]. The value might be truncated if zBuf is not large +** enough to hold it all. The value put into zBuf must always be zero +** terminated, even if it gets truncated because nBuf is not large enough. +** +** Return the total number of bytes in the data, without truncation, and +** not counting the final zero terminator. Return -1 if the key does +** not exist. +** +** If nBuf<=0 then this routine simply returns the size of the data without +** actually reading it. +*/ +static int kvstorageRead( + const char *zClass, + const char *zKey, + char *zBuf, + int nBuf +){ + FILE *fd; + struct stat buf; + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); + if( access(zXKey, R_OK)!=0 + || stat(zXKey, &buf)!=0 + || !S_ISREG(buf.st_mode) + ){ + SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); + return -1; + } + if( nBuf<=0 ){ + return (int)buf.st_size; + }else if( nBuf==1 ){ + zBuf[0] = 0; + SQLITE_KV_TRACE(("KVVFS-READ %-15s (%d)\n", zXKey, + (int)buf.st_size)); + return (int)buf.st_size; + } + if( nBuf > buf.st_size + 1 ){ + nBuf = buf.st_size + 1; + } + fd = fopen(zXKey, "rb"); + if( fd==0 ){ + SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); + return -1; + }else{ + sqlite3_int64 n = fread(zBuf, 1, nBuf-1, fd); + fclose(fd); + zBuf[n] = 0; + SQLITE_KV_TRACE(("KVVFS-READ %-15s (%lld) %.50s%s\n", zXKey, + n, zBuf, n>50 ? "..." : "")); + return (int)n; + } +} + +/* +** An internal level of indirection which enables us to replace the +** kvvfs i/o methods with JavaScript implementations in WASM builds. +** Maintenance reminder: if this struct changes in any way, the JSON +** rendering of its structure must be updated in +** sqlite3_wasm_enum_json(). There are no binary compatibility +** concerns, so it does not need an iVersion member. This file is +** necessarily always compiled together with sqlite3_wasm_enum_json(), +** and JS code dynamically creates the mapping of members based on +** that JSON description. +*/ +typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; +struct sqlite3_kvvfs_methods { + int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); + int (*xWrite)(const char *zClass, const char *zKey, const char *zData); + int (*xDelete)(const char *zClass, const char *zKey); + const int nKeySize; +}; + +/* +** This object holds the kvvfs I/O methods which may be swapped out +** for JavaScript-side implementations in WASM builds. In such builds +** it cannot be const, but in native builds it should be so that +** the compiler can hopefully optimize this level of indirection out. +** That said, kvvfs is intended primarily for use in WASM builds. +** +** Note that this is not explicitly flagged as static because the +** amalgamation build will tag it with SQLITE_PRIVATE. +*/ +#ifndef SQLITE_WASM +const +#endif +sqlite3_kvvfs_methods sqlite3KvvfsMethods = { +kvstorageRead, +kvstorageWrite, +kvstorageDelete, +KVSTORAGE_KEY_SZ +}; + +/****** Utility subroutines ************************************************/ + +/* +** Encode binary into the text encoded used to persist on disk. +** The output text is stored in aOut[], which must be at least +** nData+1 bytes in length. +** +** Return the actual length of the encoded text, not counting the +** zero terminator at the end. +** +** Encoding format +** --------------- +** +** * Non-zero bytes are encoded as upper-case hexadecimal +** +** * A sequence of one or more zero-bytes that are not at the +** beginning of the buffer are encoded as a little-endian +** base-26 number using a..z. "a" means 0. "b" means 1, +** "z" means 25. "ab" means 26. "ac" means 52. And so forth. +** +** * Because there is no overlap between the encoding characters +** of hexadecimal and base-26 numbers, it is always clear where +** one stops and the next begins. +*/ +static int kvvfsEncode(const char *aData, int nData, char *aOut){ + int i, j; + const unsigned char *a = (const unsigned char*)aData; + for(i=j=0; i>4]; + aOut[j++] = "0123456789ABCDEF"[c&0xf]; + }else{ + /* A sequence of 1 or more zeros is stored as a little-endian + ** base-26 number using a..z as the digits. So one zero is "b". + ** Two zeros is "c". 25 zeros is "z", 26 zeros is "ab", 27 is "bb", + ** and so forth. + */ + int k; + for(k=1; i+k0 ){ + aOut[j++] = 'a'+(k%26); + k /= 26; + } + } + } + aOut[j] = 0; + return j; +} + +static const signed char kvvfsHexValue[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* +** Decode the text encoding back to binary. The binary content is +** written into pOut, which must be at least nOut bytes in length. +** +** The return value is the number of bytes actually written into aOut[]. +*/ +static int kvvfsDecode(const char *a, char *aOut, int nOut){ + int i, j; + int c; + const unsigned char *aIn = (const unsigned char*)a; + i = 0; + j = 0; + while( 1 ){ + c = kvvfsHexValue[aIn[i]]; + if( c<0 ){ + int n = 0; + int mult = 1; + c = aIn[i]; + if( c==0 ) break; + while( c>='a' && c<='z' ){ + n += (c - 'a')*mult; + mult *= 26; + c = aIn[++i]; + } + if( j+n>nOut ) return -1; + memset(&aOut[j], 0, n); + j += n; + c = aIn[i]; + if( c==0 ) break; + }else{ + aOut[j] = c<<4; + c = kvvfsHexValue[aIn[++i]]; + if( c<0 ) break; + aOut[j++] += c; + i++; + } + } + return j; +} + +/* +** Decode a complete journal file. Allocate space in pFile->aJrnl +** and store the decoding there. Or leave pFile->aJrnl set to NULL +** if an error is encountered. +** +** The first few characters of the text encoding will be a little-endian +** base-26 number (digits a..z) that is the total number of bytes +** in the decoded journal file image. This base-26 number is followed +** by a single space, then the encoding of the journal. The space +** separator is required to act as a terminator for the base-26 number. +*/ +static void kvvfsDecodeJournal( + KVVfsFile *pFile, /* Store decoding in pFile->aJrnl */ + const char *zTxt, /* Text encoding. Zero-terminated */ + int nTxt /* Bytes in zTxt, excluding zero terminator */ +){ + unsigned int n = 0; + int c, i, mult; + i = 0; + mult = 1; + while( (c = zTxt[i++])>='a' && c<='z' ){ + n += (zTxt[i] - 'a')*mult; + mult *= 26; + } + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = sqlite3_malloc64( n ); + if( pFile->aJrnl==0 ){ + pFile->nJrnl = 0; + return; + } + pFile->nJrnl = n; + n = kvvfsDecode(zTxt+i, pFile->aJrnl, pFile->nJrnl); + if( nnJrnl ){ + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = 0; + pFile->nJrnl = 0; + } +} + +/* +** Read or write the "sz" element, containing the database file size. +*/ +static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){ + char zData[50]; + zData[0] = 0; + sqlite3KvvfsMethods.xRead(pFile->zClass, "sz", zData, sizeof(zData)-1); + return strtoll(zData, 0, 0); +} +static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ + char zData[50]; + sqlite3_snprintf(sizeof(zData), zData, "%lld", sz); + return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData); +} + +/****** sqlite3_io_methods methods ******************************************/ + +/* +** Close an kvvfs-file. +*/ +static int kvvfsClose(sqlite3_file *pProtoFile){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + + SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, + pFile->isJournal ? "journal" : "db")); + sqlite3_free(pFile->aJrnl); + return SQLITE_OK; +} + +/* +** Read from the -journal file. +*/ +static int kvvfsReadJrnl( + sqlite3_file *pProtoFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + assert( pFile->isJournal ); + SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + if( pFile->aJrnl==0 ){ + int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0); + char *aTxt; + if( szTxt<=4 ){ + return SQLITE_IOERR; + } + aTxt = sqlite3_malloc64( szTxt+1 ); + if( aTxt==0 ) return SQLITE_NOMEM; + kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1); + kvvfsDecodeJournal(pFile, aTxt, szTxt); + sqlite3_free(aTxt); + if( pFile->aJrnl==0 ) return SQLITE_IOERR; + } + if( iOfst+iAmt>pFile->nJrnl ){ + return SQLITE_IOERR_SHORT_READ; + } + memcpy(zBuf, pFile->aJrnl+iOfst, iAmt); + return SQLITE_OK; +} + +/* +** Read from the database file. +*/ +static int kvvfsReadDb( + sqlite3_file *pProtoFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + unsigned int pgno; + int got, n; + char zKey[30]; + char aData[133073]; + assert( iOfst>=0 ); + assert( iAmt>=0 ); + SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + if( iOfst+iAmt>=512 ){ + if( (iOfst % iAmt)!=0 ){ + return SQLITE_IOERR_READ; + } + if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){ + return SQLITE_IOERR_READ; + } + pFile->szPage = iAmt; + pgno = 1 + iOfst/iAmt; + }else{ + pgno = 1; + } + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, aData, sizeof(aData)-1); + if( got<0 ){ + n = 0; + }else{ + aData[got] = 0; + if( iOfst+iAmt<512 ){ + int k = iOfst+iAmt; + aData[k*2] = 0; + n = kvvfsDecode(aData, &aData[2000], sizeof(aData)-2000); + if( n>=iOfst+iAmt ){ + memcpy(zBuf, &aData[2000+iOfst], iAmt); + n = iAmt; + }else{ + n = 0; + } + }else{ + n = kvvfsDecode(aData, zBuf, iAmt); + } + } + if( nzClass, iAmt, iOfst)); + if( iEnd>=0x10000000 ) return SQLITE_FULL; + if( pFile->aJrnl==0 || pFile->nJrnlaJrnl, iEnd); + if( aNew==0 ){ + return SQLITE_IOERR_NOMEM; + } + pFile->aJrnl = aNew; + if( pFile->nJrnlaJrnl+pFile->nJrnl, 0, iOfst-pFile->nJrnl); + } + pFile->nJrnl = iEnd; + } + memcpy(pFile->aJrnl+iOfst, zBuf, iAmt); + return SQLITE_OK; +} + +/* +** Write into the database file. +*/ +static int kvvfsWriteDb( + sqlite3_file *pProtoFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + unsigned int pgno; + char zKey[30]; + char aData[131073]; + SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + assert( iAmt>=512 && iAmt<=65536 ); + assert( (iAmt & (iAmt-1))==0 ); + assert( pFile->szPage<0 || pFile->szPage==iAmt ); + pFile->szPage = iAmt; + pgno = 1 + iOfst/iAmt; + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + kvvfsEncode(zBuf, iAmt, aData); + if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){ + return SQLITE_IOERR; + } + if( iOfst+iAmt > pFile->szDb ){ + pFile->szDb = iOfst + iAmt; + } + return SQLITE_OK; +} + +/* +** Truncate an kvvfs-file. +*/ +static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size)); + assert( size==0 ); + sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl"); + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = 0; + pFile->nJrnl = 0; + return SQLITE_OK; +} +static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + if( pFile->szDb>size + && pFile->szPage>0 + && (size % pFile->szPage)==0 + ){ + char zKey[50]; + unsigned int pgno, pgnoMax; + SQLITE_KV_LOG(("xTruncate('%s-db',%lld)\n", pFile->zClass, size)); + pgno = 1 + size/pFile->szPage; + pgnoMax = 2 + pFile->szDb/pFile->szPage; + while( pgno<=pgnoMax ){ + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey); + pgno++; + } + pFile->szDb = size; + return kvvfsWriteFileSize(pFile, size) ? SQLITE_IOERR : SQLITE_OK; + } + return SQLITE_IOERR; +} + +/* +** Sync an kvvfs-file. +*/ +static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){ + int i, n; + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + char *zOut; + SQLITE_KV_LOG(("xSync('%s-journal')\n", pFile->zClass)); + if( pFile->nJrnl<=0 ){ + return kvvfsTruncateJrnl(pProtoFile, 0); + } + zOut = sqlite3_malloc64( pFile->nJrnl*2 + 50 ); + if( zOut==0 ){ + return SQLITE_IOERR_NOMEM; + } + n = pFile->nJrnl; + i = 0; + do{ + zOut[i++] = 'a' + (n%26); + n /= 26; + }while( n>0 ); + zOut[i++] = ' '; + kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]); + i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut); + sqlite3_free(zOut); + return i ? SQLITE_IOERR : SQLITE_OK; +} +static int kvvfsSyncDb(sqlite3_file *pProtoFile, int flags){ + return SQLITE_OK; +} + +/* +** Return the current file-size of an kvvfs-file. +*/ +static int kvvfsFileSizeJrnl(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xFileSize('%s-journal')\n", pFile->zClass)); + *pSize = pFile->nJrnl; + return SQLITE_OK; +} +static int kvvfsFileSizeDb(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xFileSize('%s-db')\n", pFile->zClass)); + if( pFile->szDb>=0 ){ + *pSize = pFile->szDb; + }else{ + *pSize = kvvfsReadFileSize(pFile); + } + return SQLITE_OK; +} + +/* +** Lock an kvvfs-file. +*/ +static int kvvfsLock(sqlite3_file *pProtoFile, int eLock){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + assert( !pFile->isJournal ); + SQLITE_KV_LOG(("xLock(%s,%d)\n", pFile->zClass, eLock)); + + if( eLock!=SQLITE_LOCK_NONE ){ + pFile->szDb = kvvfsReadFileSize(pFile); + } + return SQLITE_OK; +} + +/* +** Unlock an kvvfs-file. +*/ +static int kvvfsUnlock(sqlite3_file *pProtoFile, int eLock){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + assert( !pFile->isJournal ); + SQLITE_KV_LOG(("xUnlock(%s,%d)\n", pFile->zClass, eLock)); + if( eLock==SQLITE_LOCK_NONE ){ + pFile->szDb = -1; + } + return SQLITE_OK; +} + +/* +** Check if another file-handle holds a RESERVED lock on an kvvfs-file. +*/ +static int kvvfsCheckReservedLock(sqlite3_file *pProtoFile, int *pResOut){ + SQLITE_KV_LOG(("xCheckReservedLock\n")); + *pResOut = 0; + return SQLITE_OK; +} + +/* +** File control method. For custom operations on an kvvfs-file. +*/ +static int kvvfsFileControlJrnl(sqlite3_file *pProtoFile, int op, void *pArg){ + SQLITE_KV_LOG(("xFileControl(%d) on journal\n", op)); + return SQLITE_NOTFOUND; +} +static int kvvfsFileControlDb(sqlite3_file *pProtoFile, int op, void *pArg){ + SQLITE_KV_LOG(("xFileControl(%d) on database\n", op)); + if( op==SQLITE_FCNTL_SYNC ){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + int rc = SQLITE_OK; + SQLITE_KV_LOG(("xSync('%s-db')\n", pFile->zClass)); + if( pFile->szDb>0 && 0!=kvvfsWriteFileSize(pFile, pFile->szDb) ){ + rc = SQLITE_IOERR; + } + return rc; + } + return SQLITE_NOTFOUND; +} + +/* +** Return the sector-size in bytes for an kvvfs-file. +*/ +static int kvvfsSectorSize(sqlite3_file *pFile){ + return 512; +} + +/* +** Return the device characteristic flags supported by an kvvfs-file. +*/ +static int kvvfsDeviceCharacteristics(sqlite3_file *pProtoFile){ + return 0; +} + +/****** sqlite3_vfs methods *************************************************/ + +/* +** Open an kvvfs file handle. +*/ +static int kvvfsOpen( + sqlite3_vfs *pProtoVfs, + const char *zName, + sqlite3_file *pProtoFile, + int flags, + int *pOutFlags +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + if( zName==0 ) zName = ""; + SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName)); + if( strcmp(zName, "local")==0 + || strcmp(zName, "session")==0 + ){ + pFile->isJournal = 0; + pFile->base.pMethods = &kvvfs_db_io_methods; + }else + if( strcmp(zName, "local-journal")==0 + || strcmp(zName, "session-journal")==0 + ){ + pFile->isJournal = 1; + pFile->base.pMethods = &kvvfs_jrnl_io_methods; + }else{ + return SQLITE_CANTOPEN; + } + if( zName[0]=='s' ){ + pFile->zClass = "session"; + }else{ + pFile->zClass = "local"; + } + pFile->aJrnl = 0; + pFile->nJrnl = 0; + pFile->szPage = -1; + pFile->szDb = -1; + return SQLITE_OK; +} + +/* +** Delete the file located at zPath. If the dirSync argument is true, +** ensure the file-system modifications are synced to disk before +** returning. +*/ +static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + if( strcmp(zPath, "local-journal")==0 ){ + sqlite3KvvfsMethods.xDelete("local", "jrnl"); + }else + if( strcmp(zPath, "session-journal")==0 ){ + sqlite3KvvfsMethods.xDelete("session", "jrnl"); + } + return SQLITE_OK; +} + +/* +** Test for access permissions. Return true if the requested permission +** is available, or false otherwise. +*/ +static int kvvfsAccess( + sqlite3_vfs *pProtoVfs, + const char *zPath, + int flags, + int *pResOut +){ + SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath)); + if( strcmp(zPath, "local-journal")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0; + }else + if( strcmp(zPath, "session-journal")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0; + }else + if( strcmp(zPath, "local")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0; + }else + if( strcmp(zPath, "session")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0; + }else + { + *pResOut = 0; + } + SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut)); + return SQLITE_OK; +} + +/* +** Populate buffer zOut with the full canonical pathname corresponding +** to the pathname in zPath. zOut is guaranteed to point to a buffer +** of at least (INST_MAX_PATHNAME+1) bytes. +*/ +static int kvvfsFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + size_t nPath; +#ifdef SQLITE_OS_KV_ALWAYS_LOCAL + zPath = "local"; +#endif + nPath = strlen(zPath); + SQLITE_KV_LOG(("xFullPathname(\"%s\")\n", zPath)); + if( nOut +static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ + static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; + struct timeval sNow; + (void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */ + *pTimeOut = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + return SQLITE_OK; +} +#endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */ + +#if SQLITE_OS_KV +/* +** This routine is called initialize the KV-vfs as the default VFS. +*/ +int sqlite3_os_init(void){ + return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 1); +} +int sqlite3_os_end(void){ + return SQLITE_OK; +} +#endif /* SQLITE_OS_KV */ + +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) +int sqlite3KvvfsInit(void){ + return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 0); +} +#endif diff --git a/src/os_setup.h b/src/os_setup.h index 08aaa1195..a82f86fd9 100644 --- a/src/os_setup.h +++ b/src/os_setup.h @@ -20,38 +20,72 @@ ** Figure out if we are dealing with Unix, Windows, or some other operating ** system. ** -** After the following block of preprocess macros, all of SQLITE_OS_UNIX, -** SQLITE_OS_WIN, and SQLITE_OS_OTHER will defined to either 1 or 0. One of -** the three will be 1. The other two will be 0. +** After the following block of preprocess macros, all of +** +** SQLITE_OS_KV +** SQLITE_OS_OTHER +** SQLITE_OS_UNIX +** SQLITE_OS_WIN +** +** will defined to either 1 or 0. One of them will be 1. The others will be 0. +** If none of the macros are initially defined, then select either +** SQLITE_OS_UNIX or SQLITE_OS_WIN depending on the target platform. +** +** If SQLITE_OS_OTHER=1 is specified at compile-time, then the application +** must provide its own VFS implementation together with sqlite3_os_init() +** and sqlite3_os_end() routines. */ -#if defined(SQLITE_OS_OTHER) -# if SQLITE_OS_OTHER==1 -# undef SQLITE_OS_UNIX +#if !defined(SQLITE_OS_KV) && !defined(SQLITE_OS_OTHER) && \ + !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_WIN) +# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ + defined(__MINGW32__) || defined(__BORLANDC__) +# define SQLITE_OS_WIN 1 # define SQLITE_OS_UNIX 0 -# undef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 # else -# undef SQLITE_OS_OTHER +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 1 # endif #endif -#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) +#if SQLITE_OS_OTHER+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +#endif +#if SQLITE_OS_KV+1>1 +# undef SQLITE_OS_OTHER # define SQLITE_OS_OTHER 0 -# ifndef SQLITE_OS_WIN -# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ - defined(__MINGW32__) || defined(__BORLANDC__) -# define SQLITE_OS_WIN 1 -# define SQLITE_OS_UNIX 0 -# else -# define SQLITE_OS_WIN 0 -# define SQLITE_OS_UNIX 1 -# endif -# else -# define SQLITE_OS_UNIX 0 -# endif -#else -# ifndef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 -# endif +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# define SQLITE_OMIT_LOAD_EXTENSION 1 +# define SQLITE_OMIT_WAL 1 +# define SQLITE_OMIT_DEPRECATED 1 +# undef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 3 /* Always use memory for temporary storage */ +# define SQLITE_DQS 0 +# define SQLITE_OMIT_SHARED_CACHE 1 +# define SQLITE_OMIT_AUTOINIT 1 +#endif +#if SQLITE_OS_UNIX+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +#endif +#if SQLITE_OS_WIN+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 #endif + #endif /* SQLITE_OS_SETUP_H */ diff --git a/src/os_unix.c b/src/os_unix.c index 81a41263b..722e4b2c3 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -87,13 +87,13 @@ /* ** standard include files. */ -#include -#include +#include /* amalgamator: keep */ +#include /* amalgamator: keep */ #include #include -#include +#include /* amalgamator: keep */ #include -#include +#include /* amalgamator: keep */ #include #if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 # include @@ -686,6 +686,9 @@ static int robust_open(const char *z, int f, mode_t m){ break; } if( fd>=SQLITE_MINIMUM_FILE_DESCRIPTOR ) break; + if( (f & (O_EXCL|O_CREAT))==(O_EXCL|O_CREAT) ){ + (void)osUnlink(z); + } osClose(fd); sqlite3_log(SQLITE_WARNING, "attempt to open \"%s\" as file descriptor %d", z, fd); @@ -8058,8 +8061,16 @@ int sqlite3_os_init(void){ /* Register all VFSes defined in the aVfs[] array */ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ +#ifdef SQLITE_DEFAULT_UNIX_VFS + sqlite3_vfs_register(&aVfs[i], + 0==strcmp(aVfs[i].zName,SQLITE_DEFAULT_UNIX_VFS)); +#else sqlite3_vfs_register(&aVfs[i], i==0); +#endif } +#ifdef SQLITE_OS_KV_OPTIONAL + sqlite3KvvfsInit(); +#endif unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); #ifndef SQLITE_OMIT_WAL diff --git a/src/os_win.c b/src/os_win.c index 2827b0b49..abecf1b93 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -4725,9 +4725,10 @@ static int winMakeEndInDirSep(int nBuf, char *zBuf){ } /* -** If sqlite3_temp_directory is not, take the mutex and return true. +** If sqlite3_temp_directory is defined, take the mutex and return true. ** -** If sqlite3_temp_directory is NULL, omit the mutex and return false. +** If sqlite3_temp_directory is NULL (undefined), omit the mutex and +** return false. */ static int winTempDirDefined(void){ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); @@ -5763,7 +5764,8 @@ static int winFullPathname( char *zFull /* Output buffer */ ){ int rc; - sqlite3_mutex *pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR); + MUTEX_LOGIC( sqlite3_mutex *pMutex; ) + MUTEX_LOGIC( pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR); ) sqlite3_mutex_enter(pMutex); rc = winFullPathnameNoMutex(pVfs, zRelative, nFull, zFull); sqlite3_mutex_leave(pMutex); diff --git a/src/pager.c b/src/pager.c index dcb72857b..883e6532c 100644 --- a/src/pager.c +++ b/src/pager.c @@ -5786,6 +5786,7 @@ static int pager_open_journal(Pager *pPager){ if( pPager->tempFile ){ flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL); + flags |= SQLITE_OPEN_EXCLUSIVE; nSpill = sqlite3Config.nStmtSpill; }else{ flags |= SQLITE_OPEN_MAIN_JOURNAL; @@ -7377,7 +7378,7 @@ int sqlite3PagerGetJournalMode(Pager *pPager){ int sqlite3PagerOkToChangeJournalMode(Pager *pPager){ assert( assert_pager_state(pPager) ); if( pPager->eState>=PAGER_WRITER_CACHEMOD ) return 0; - if( isOpen(pPager->jfd) && pPager->journalOff>0 ) return 0; + if( NEVER(isOpen(pPager->jfd) && pPager->journalOff>0) ) return 0; return 1; } diff --git a/src/pcache.c b/src/pcache.c index e130affd2..0407e06b2 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -66,12 +66,20 @@ struct PCache { int sqlite3PcacheTrace = 2; /* 0: off 1: simple 2: cache dumps */ int sqlite3PcacheMxDump = 9999; /* Max cache entries for pcacheDump() */ # define pcacheTrace(X) if(sqlite3PcacheTrace){sqlite3DebugPrintf X;} - void pcacheDump(PCache *pCache){ - int N; - int i, j; - sqlite3_pcache_page *pLower; + static void pcachePageTrace(int i, sqlite3_pcache_page *pLower){ PgHdr *pPg; unsigned char *a; + int j; + pPg = (PgHdr*)pLower->pExtra; + printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags); + a = (unsigned char *)pLower->pBuf; + for(j=0; j<12; j++) printf("%02x", a[j]); + printf(" ptr %p\n", pPg); + } + static void pcacheDump(PCache *pCache){ + int N; + int i; + sqlite3_pcache_page *pLower; if( sqlite3PcacheTrace<2 ) return; if( pCache->pCache==0 ) return; @@ -80,21 +88,32 @@ struct PCache { for(i=1; i<=N; i++){ pLower = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0); if( pLower==0 ) continue; - pPg = (PgHdr*)pLower->pExtra; - printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags); - a = (unsigned char *)pLower->pBuf; - for(j=0; j<12; j++) printf("%02x", a[j]); - printf("\n"); - if( pPg->pPage==0 ){ + pcachePageTrace(i, pLower); + if( ((PgHdr*)pLower)->pPage==0 ){ sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0); } } } - #else +#else # define pcacheTrace(X) +# define pcachePageTrace(PGNO, X) # define pcacheDump(X) #endif +/* +** Return 1 if pPg is on the dirty list for pCache. Return 0 if not. +** This routine runs inside of assert() statements only. +*/ +#ifdef SQLITE_DEBUG +static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 1; + } + return 0; +} +#endif + /* ** Check invariants on a PgHdr entry. Return true if everything is OK. ** Return false if any invariant is violated. @@ -113,8 +132,13 @@ int sqlite3PcachePageSanity(PgHdr *pPg){ assert( pCache!=0 ); /* Every page has an associated PCache */ if( pPg->flags & PGHDR_CLEAN ){ assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */ - assert( pCache->pDirty!=pPg ); /* CLEAN pages not on dirty list */ - assert( pCache->pDirtyTail!=pPg ); + assert( !pageOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirty list */ + }else{ + assert( (pPg->flags & PGHDR_DIRTY)!=0 );/* If not CLEAN must be DIRTY */ + assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg ); + assert( pPg->pDirtyPrev==0 || pPg->pDirtyPrev->pDirtyNext==pPg ); + assert( pPg->pDirtyPrev!=0 || pCache->pDirty==pPg ); + assert( pageOnDirtyList(pCache, pPg) ); } /* WRITEABLE pages must also be DIRTY */ if( pPg->flags & PGHDR_WRITEABLE ){ @@ -388,8 +412,9 @@ sqlite3_pcache_page *sqlite3PcacheFetch( assert( createFlag==0 || pCache->eCreate==eCreate ); assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) ); pRes = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate); - pcacheTrace(("%p.FETCH %d%s (result: %p)\n",pCache,pgno, + pcacheTrace(("%p.FETCH %d%s (result: %p) ",pCache,pgno, createFlag?" create":"",pRes)); + pcachePageTrace(pgno, pRes); return pRes; } @@ -517,6 +542,7 @@ void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){ pcacheUnpin(p); }else{ pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); + assert( sqlite3PcachePageSanity(p) ); } } } @@ -560,6 +586,7 @@ void sqlite3PcacheMakeDirty(PgHdr *p){ pcacheTrace(("%p.DIRTY %d\n",p->pCache,p->pgno)); assert( (p->flags & (PGHDR_DIRTY|PGHDR_CLEAN))==PGHDR_DIRTY ); pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD); + assert( sqlite3PcachePageSanity(p) ); } assert( sqlite3PcachePageSanity(p) ); } diff --git a/src/pcache1.c b/src/pcache1.c index ce595f2c2..adbe95395 100644 --- a/src/pcache1.c +++ b/src/pcache1.c @@ -39,12 +39,13 @@ ** size can vary according to architecture, compile-time options, and ** SQLite library version number. ** -** If SQLITE_PCACHE_SEPARATE_HEADER is defined, then the extension is obtained -** using a separate memory allocation from the database page content. This -** seeks to overcome the "clownshoe" problem (also called "internal -** fragmentation" in academic literature) of allocating a few bytes more -** than a power of two with the memory allocator rounding up to the next -** power of two, and leaving the rounded-up space unused. +** Historical note: It used to be that if the SQLITE_PCACHE_SEPARATE_HEADER +** was defined, then the page content would be held in a separate memory +** allocation from the PgHdr1. This was intended to avoid clownshoe memory +** allocations. However, the btree layer needs a small (16-byte) overrun +** area after the page content buffer. The header serves as that overrun +** area. Therefore SQLITE_PCACHE_SEPARATE_HEADER was discontinued to avoid +** any possibility of a memory error. ** ** This module tracks pointers to PgHdr1 objects. Only pcache.c communicates ** with this module. Information is passed back and forth as PgHdr1 pointers. @@ -89,30 +90,40 @@ typedef struct PGroup PGroup; /* ** Each cache entry is represented by an instance of the following -** structure. Unless SQLITE_PCACHE_SEPARATE_HEADER is defined, a buffer of -** PgHdr1.pCache->szPage bytes is allocated directly before this structure -** in memory. +** structure. A buffer of PgHdr1.pCache->szPage bytes is allocated +** directly before this structure and is used to cache the page content. ** -** Note: Variables isBulkLocal and isAnchor were once type "u8". That works, +** When reading a corrupt database file, it is possible that SQLite might +** read a few bytes (no more than 16 bytes) past the end of the page buffer. +** It will only read past the end of the page buffer, never write. This +** object is positioned immediately after the page buffer to serve as an +** overrun area, so that overreads are harmless. +** +** Variables isBulkLocal and isAnchor were once type "u8". That works, ** but causes a 2-byte gap in the structure for most architectures (since ** pointers must be either 4 or 8-byte aligned). As this structure is located ** in memory directly after the associated page data, if the database is ** corrupt, code at the b-tree layer may overread the page buffer and ** read part of this structure before the corruption is detected. This ** can cause a valgrind error if the unitialized gap is accessed. Using u16 -** ensures there is no such gap, and therefore no bytes of unitialized memory -** in the structure. +** ensures there is no such gap, and therefore no bytes of uninitialized +** memory in the structure. +** +** The pLruNext and pLruPrev pointers form a double-linked circular list +** of all pages that are unpinned. The PGroup.lru element (which should be +** the only element on the list with PgHdr1.isAnchor set to 1) forms the +** beginning and the end of the list. */ struct PgHdr1 { - sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */ - unsigned int iKey; /* Key value (page number) */ - u16 isBulkLocal; /* This page from bulk local storage */ - u16 isAnchor; /* This is the PGroup.lru element */ - PgHdr1 *pNext; /* Next in hash table chain */ - PCache1 *pCache; /* Cache that currently owns this page */ - PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */ - PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */ - /* NB: pLruPrev is only valid if pLruNext!=0 */ + sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */ + unsigned int iKey; /* Key value (page number) */ + u16 isBulkLocal; /* This page from bulk local storage */ + u16 isAnchor; /* This is the PGroup.lru element */ + PgHdr1 *pNext; /* Next in hash table chain */ + PCache1 *pCache; /* Cache that currently owns this page */ + PgHdr1 *pLruNext; /* Next in circular LRU list of unpinned pages */ + PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */ + /* NB: pLruPrev is only valid if pLruNext!=0 */ }; /* @@ -438,25 +449,13 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){ pcache1LeaveMutex(pCache->pGroup); #endif if( benignMalloc ){ sqlite3BeginBenignMalloc(); } -#ifdef SQLITE_PCACHE_SEPARATE_HEADER - pPg = pcache1Alloc(pCache->szPage); - p = sqlite3Malloc(sizeof(PgHdr1) + pCache->szExtra); - if( !pPg || !p ){ - pcache1Free(pPg); - sqlite3_free(p); - pPg = 0; - } -#else pPg = pcache1Alloc(pCache->szAlloc); -#endif if( benignMalloc ){ sqlite3EndBenignMalloc(); } #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT pcache1EnterMutex(pCache->pGroup); #endif if( pPg==0 ) return 0; -#ifndef SQLITE_PCACHE_SEPARATE_HEADER p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage]; -#endif p->page.pBuf = pPg; p->page.pExtra = &p[1]; p->isBulkLocal = 0; @@ -480,9 +479,6 @@ static void pcache1FreePage(PgHdr1 *p){ pCache->pFree = p; }else{ pcache1Free(p->page.pBuf); -#ifdef SQLITE_PCACHE_SEPARATE_HEADER - sqlite3_free(p); -#endif } (*pCache->pnPurgeable)--; } @@ -1129,7 +1125,7 @@ static void pcache1Rekey( assert( iOld!=iNew ); /* The page number really is changing */ pcache1EnterMutex(pCache->pGroup); - + assert( pcache1FetchNoMutex(p, iOld, 0)==pPage ); /* pPg really is iOld */ hOld = iOld%pCache->nHash; pp = &pCache->apHash[hOld]; @@ -1249,9 +1245,6 @@ int sqlite3PcacheReleaseMemory(int nReq){ && p->isAnchor==0 ){ nFree += pcache1MemSize(p->page.pBuf); -#ifdef SQLITE_PCACHE_SEPARATE_HEADER - nFree += sqlite3MemSize(p); -#endif assert( PAGE_IS_UNPINNED(p) ); pcache1PinPage(p); pcache1RemoveFromHash(p, 1); diff --git a/src/pragma.c b/src/pragma.c index e2f2577ae..a0aa123fc 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1748,15 +1748,24 @@ void sqlite3Pragma( for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx, *pPk; - Index *pPrior = 0; + Index *pPrior = 0; /* Previous index */ int loopTop; int iDataCur, iIdxCur; int r1 = -1; - int bStrict; + int bStrict; /* True for a STRICT table */ + int r2; /* Previous key for WITHOUT ROWID tables */ + int mxCol; /* Maximum non-virtual column number */ if( !IsOrdinaryTable(pTab) ) continue; if( pObjTab && pObjTab!=pTab ) continue; - pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + if( isQuick || HasRowid(pTab) ){ + pPk = 0; + r2 = 0; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + r2 = sqlite3GetTempRange(pParse, pPk->nKeyCol); + sqlite3VdbeAddOp3(v, OP_Null, 1, r2, r2+pPk->nKeyCol-1); + } sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, 1, 0, &iDataCur, &iIdxCur); /* reg[7] counts the number of entries in the table. @@ -1770,52 +1779,157 @@ void sqlite3Pragma( assert( sqlite3NoTempsInRange(pParse,1,7+j) ); sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v); loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1); + + /* Fetch the right-most column from the table. This will cause + ** the entire record header to be parsed and sanity checked. It + ** will also prepopulate the cursor column cache that is used + ** by the OP_IsType code, so it is a required step. + */ + mxCol = pTab->nCol-1; + while( mxCol>=0 + && ((pTab->aCol[mxCol].colFlags & COLFLAG_VIRTUAL)!=0 + || pTab->iPKey==mxCol) ) mxCol--; + if( mxCol>=0 ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, mxCol, 3); + sqlite3VdbeTypeofColumn(v, 3); + } + if( !isQuick ){ - /* Sanity check on record header decoding */ - sqlite3VdbeAddOp3(v, OP_Column, iDataCur, pTab->nNVCol-1,3); - sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); - VdbeComment((v, "(right-most column)")); + if( pPk ){ + /* Verify WITHOUT ROWID keys are in ascending order */ + int a1; + char *zErr; + a1 = sqlite3VdbeAddOp4Int(v, OP_IdxGT, iDataCur, 0,r2,pPk->nKeyCol); + VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_IsNull, r2); VdbeCoverage(v); + zErr = sqlite3MPrintf(db, + "row not in PRIMARY KEY order for %s", + pTab->zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); + sqlite3VdbeJumpHere(v, a1+1); + for(j=0; jnKeyCol; j++){ + sqlite3ExprCodeLoadIndexColumn(pParse, pPk, iDataCur, j, r2+j); + } + } } - /* Verify that all NOT NULL columns really are NOT NULL. At the - ** same time verify the type of the content of STRICT tables */ + /* Verify datatypes for all columns: + ** + ** (1) NOT NULL columns may not contain a NULL + ** (2) Datatype must be exact for non-ANY columns in STRICT tables + ** (3) Datatype for TEXT columns in non-STRICT tables must be + ** NULL, TEXT, or BLOB. + ** (4) Datatype for numeric columns in non-STRICT tables must not + ** be a TEXT value that can be losslessly converted to numeric. + */ bStrict = (pTab->tabFlags & TF_Strict)!=0; for(j=0; jnCol; j++){ char *zErr; - Column *pCol = pTab->aCol + j; - int doError, jmp2; + Column *pCol = pTab->aCol + j; /* The column to be checked */ + int labelError; /* Jump here to report an error */ + int labelOk; /* Jump here if all looks ok */ + int p1, p3, p4; /* Operands to the OP_IsType opcode */ + int doTypeCheck; /* Check datatypes (besides NOT NULL) */ + if( j==pTab->iPKey ) continue; - if( pCol->notNull==0 && !bStrict ) continue; - doError = bStrict ? sqlite3VdbeMakeLabel(pParse) : 0; - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); - if( sqlite3VdbeGetOp(v,-1)->opcode==OP_Column ){ - sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + if( bStrict ){ + doTypeCheck = pCol->eCType>COLTYPE_ANY; + }else{ + doTypeCheck = pCol->affinity>SQLITE_AFF_BLOB; } + if( pCol->notNull==0 && !doTypeCheck ) continue; + + /* Compute the operands that will be needed for OP_IsType */ + p4 = SQLITE_NULL; + if( pCol->colFlags & COLFLAG_VIRTUAL ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + p1 = -1; + p3 = 3; + }else{ + if( pCol->iDflt ){ + sqlite3_value *pDfltValue = 0; + sqlite3ValueFromExpr(db, sqlite3ColumnExpr(pTab,pCol), ENC(db), + pCol->affinity, &pDfltValue); + if( pDfltValue ){ + p4 = sqlite3_value_type(pDfltValue); + sqlite3ValueFree(pDfltValue); + } + } + p1 = iDataCur; + if( !HasRowid(pTab) ){ + testcase( j!=sqlite3TableColumnToStorage(pTab, j) ); + p3 = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), j); + }else{ + p3 = sqlite3TableColumnToStorage(pTab,j); + testcase( p3!=j); + } + } + + labelError = sqlite3VdbeMakeLabel(pParse); + labelOk = sqlite3VdbeMakeLabel(pParse); if( pCol->notNull ){ - jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); + /* (1) NOT NULL columns may not contain a NULL */ + int jmp2 = sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + sqlite3VdbeChangeP5(v, 0x0f); + VdbeCoverage(v); zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, pCol->zCnName); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); - if( bStrict && pCol->eCType!=COLTYPE_ANY ){ - sqlite3VdbeGoto(v, doError); + if( doTypeCheck ){ + sqlite3VdbeGoto(v, labelError); + sqlite3VdbeJumpHere(v, jmp2); }else{ - integrityCheckResultRow(v); + /* VDBE byte code will fall thru */ } - sqlite3VdbeJumpHere(v, jmp2); } - if( (pTab->tabFlags & TF_Strict)!=0 - && pCol->eCType!=COLTYPE_ANY - ){ - jmp2 = sqlite3VdbeAddOp3(v, OP_IsNullOrType, 3, 0, - sqlite3StdTypeMap[pCol->eCType-1]); + if( bStrict && doTypeCheck ){ + /* (2) Datatype must be exact for non-ANY columns in STRICT tables*/ + static unsigned char aStdTypeMask[] = { + 0x1f, /* ANY */ + 0x18, /* BLOB */ + 0x11, /* INT */ + 0x11, /* INTEGER */ + 0x13, /* REAL */ + 0x14 /* TEXT */ + }; + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + assert( pCol->eCType>=1 && pCol->eCType<=sizeof(aStdTypeMask) ); + sqlite3VdbeChangeP5(v, aStdTypeMask[pCol->eCType-1]); VdbeCoverage(v); zErr = sqlite3MPrintf(db, "non-%s value in %s.%s", sqlite3StdType[pCol->eCType-1], pTab->zName, pTab->aCol[j].zCnName); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); - sqlite3VdbeResolveLabel(v, doError); - integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, jmp2); + }else if( !bStrict && pCol->affinity==SQLITE_AFF_TEXT ){ + /* (3) Datatype for TEXT columns in non-STRICT tables must be + ** NULL, TEXT, or BLOB. */ + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */ + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "NUMERIC value in %s.%s", + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + }else if( !bStrict && pCol->affinity>=SQLITE_AFF_NUMERIC ){ + /* (4) Datatype for numeric columns in non-STRICT tables must not + ** be a TEXT value that can be converted to numeric. */ + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + sqlite3VdbeChangeP5(v, 0x1b); /* NULL, INT, FLOAT, or BLOB */ + VdbeCoverage(v); + if( p1>=0 ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + } + sqlite3VdbeAddOp4(v, OP_Affinity, 3, 1, 0, "C", P4_STATIC); + sqlite3VdbeAddOp4Int(v, OP_IsType, -1, labelOk, 3, p4); + sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */ + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "TEXT value in %s.%s", + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); } + sqlite3VdbeResolveLabel(v, labelError); + integrityCheckResultRow(v); + sqlite3VdbeResolveLabel(v, labelOk); } /* Verify CHECK constraints */ if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ @@ -1903,6 +2017,9 @@ void sqlite3Pragma( integrityCheckResultRow(v); sqlite3VdbeJumpHere(v, addr); } + if( pPk ){ + sqlite3ReleaseTempRange(pParse, r2, pPk->nKeyCol); + } } } } @@ -2053,6 +2170,11 @@ void sqlite3Pragma( aOp[1].p2 = iCookie; aOp[1].p3 = sqlite3Atoi(zRight); aOp[1].p5 = 1; + if( iCookie==BTREE_SCHEMA_VERSION && (db->flags & SQLITE_Defensive)!=0 ){ + /* Do not allow the use of PRAGMA schema_version=VALUE in defensive + ** mode. Change the OP_SetCookie opcode into a no-op. */ + aOp[1].opcode = OP_Noop; + } }else{ /* Read the specified cookie value */ static const VdbeOpList readCookie[] = { diff --git a/src/prepare.c b/src/prepare.c index f55ff72fc..1e7a1222b 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -574,15 +574,15 @@ void sqlite3ParseObjectReset(Parse *pParse){ assert( db->pParse==pParse ); assert( pParse->nested==0 ); #ifndef SQLITE_OMIT_SHARED_CACHE - sqlite3DbFree(db, pParse->aTableLock); + if( pParse->aTableLock ) sqlite3DbNNFreeNN(db, pParse->aTableLock); #endif while( pParse->pCleanup ){ ParseCleanup *pCleanup = pParse->pCleanup; pParse->pCleanup = pCleanup->pNext; pCleanup->xCleanup(db, pCleanup->pPtr); - sqlite3DbFreeNN(db, pCleanup); + sqlite3DbNNFreeNN(db, pCleanup); } - sqlite3DbFree(db, pParse->aLabel); + if( pParse->aLabel ) sqlite3DbNNFreeNN(db, pParse->aLabel); if( pParse->pConstExpr ){ sqlite3ExprListDelete(db, pParse->pConstExpr); } @@ -705,7 +705,7 @@ static int sqlite3Prepare( sParse.disableLookaside++; DisableLookaside; } - sParse.disableVtab = (prepFlags & SQLITE_PREPARE_NO_VTAB)!=0; + sParse.prepFlags = prepFlags & 0xff; /* Check to verify that it is possible to get a read lock on all ** database schemas. The inability to get a read lock indicates that @@ -746,7 +746,9 @@ static int sqlite3Prepare( } } - sqlite3VtabUnlockList(db); +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( db->pDisconnect ) sqlite3VtabUnlockList(db); +#endif if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){ char *zSqlCopy; diff --git a/src/random.c b/src/random.c index 87f9e2cec..ea8431ba9 100644 --- a/src/random.c +++ b/src/random.c @@ -22,16 +22,41 @@ ** This structure is the current state of the generator. */ static SQLITE_WSD struct sqlite3PrngType { - unsigned char isInit; /* True if initialized */ - unsigned char i, j; /* State variables */ - unsigned char s[256]; /* State variables */ + u32 s[16]; /* 64 bytes of chacha20 state */ + u8 out[64]; /* Output bytes */ + u8 n; /* Output bytes remaining */ } sqlite3Prng; + +/* The RFC-7539 ChaCha20 block function +*/ +#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) +#define QR(a, b, c, d) ( \ + a += b, d ^= a, d = ROTL(d,16), \ + c += d, b ^= c, b = ROTL(b,12), \ + a += b, d ^= a, d = ROTL(d, 8), \ + c += d, b ^= c, b = ROTL(b, 7)) +static void chacha_block(u32 *out, const u32 *in){ + int i; + u32 x[16]; + memcpy(x, in, 64); + for(i=0; i<10; i++){ + QR(x[0], x[4], x[ 8], x[12]); + QR(x[1], x[5], x[ 9], x[13]); + QR(x[2], x[6], x[10], x[14]); + QR(x[3], x[7], x[11], x[15]); + QR(x[0], x[5], x[10], x[15]); + QR(x[1], x[6], x[11], x[12]); + QR(x[2], x[7], x[ 8], x[13]); + QR(x[3], x[4], x[ 9], x[14]); + } + for(i=0; i<16; i++) out[i] = x[i]+in[i]; +} + /* ** Return N random bytes. */ void sqlite3_randomness(int N, void *pBuf){ - unsigned char t; unsigned char *zBuf = pBuf; /* The "wsdPrng" macro will resolve to the pseudo-random number generator @@ -61,53 +86,46 @@ void sqlite3_randomness(int N, void *pBuf){ sqlite3_mutex_enter(mutex); if( N<=0 || pBuf==0 ){ - wsdPrng.isInit = 0; + wsdPrng.s[0] = 0; sqlite3_mutex_leave(mutex); return; } /* Initialize the state of the random number generator once, - ** the first time this routine is called. The seed value does - ** not need to contain a lot of randomness since we are not - ** trying to do secure encryption or anything like that... - ** - ** Nothing in this file or anywhere else in SQLite does any kind of - ** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random - ** number generator) not as an encryption device. + ** the first time this routine is called. */ - if( !wsdPrng.isInit ){ + if( wsdPrng.s[0]==0 ){ sqlite3_vfs *pVfs = sqlite3_vfs_find(0); - int i; - char k[256]; - wsdPrng.j = 0; - wsdPrng.i = 0; + static const u32 chacha20_init[] = { + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 + }; + memcpy(&wsdPrng.s[0], chacha20_init, 16); if( NEVER(pVfs==0) ){ - memset(k, 0, sizeof(k)); + memset(&wsdPrng.s[4], 0, 44); }else{ - sqlite3OsRandomness(pVfs, 256, k); + sqlite3OsRandomness(pVfs, 44, (char*)&wsdPrng.s[4]); } - for(i=0; i<256; i++){ - wsdPrng.s[i] = (u8)i; - } - for(i=0; i<256; i++){ - wsdPrng.j += wsdPrng.s[i] + k[i]; - t = wsdPrng.s[wsdPrng.j]; - wsdPrng.s[wsdPrng.j] = wsdPrng.s[i]; - wsdPrng.s[i] = t; - } - wsdPrng.isInit = 1; + wsdPrng.s[15] = wsdPrng.s[12]; + wsdPrng.s[12] = 0; + wsdPrng.n = 0; } assert( N>0 ); - do{ - wsdPrng.i++; - t = wsdPrng.s[wsdPrng.i]; - wsdPrng.j += t; - wsdPrng.s[wsdPrng.i] = wsdPrng.s[wsdPrng.j]; - wsdPrng.s[wsdPrng.j] = t; - t += wsdPrng.s[wsdPrng.i]; - *(zBuf++) = wsdPrng.s[t]; - }while( --N ); + while( 1 /* exit by break */ ){ + if( N<=wsdPrng.n ){ + memcpy(zBuf, &wsdPrng.out[wsdPrng.n-N], N); + wsdPrng.n -= N; + break; + } + if( wsdPrng.n>0 ){ + memcpy(zBuf, wsdPrng.out, wsdPrng.n); + N -= wsdPrng.n; + zBuf += wsdPrng.n; + } + wsdPrng.s[12]++; + chacha_block((u32*)wsdPrng.out, wsdPrng.s); + wsdPrng.n = 64; + } sqlite3_mutex_leave(mutex); } diff --git a/src/resolve.c b/src/resolve.c index 9512e3a42..1c3a9d909 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -99,9 +99,7 @@ static void resolveAlias( pExpr->y.pWin->pOwner = pExpr; } } - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3ExprDelete, - pDup); + sqlite3ExprDeferredDelete(pParse, pDup); } } diff --git a/src/select.c b/src/select.c index 4242730e2..b0f79f840 100644 --- a/src/select.c +++ b/src/select.c @@ -76,6 +76,7 @@ struct SortCtx { ** If bFree==0, Leave the first Select object unfreed */ static void clearSelect(sqlite3 *db, Select *p, int bFree){ + assert( db!=0 ); while( p ){ Select *pPrior = p->pPrior; sqlite3ExprListDelete(db, p->pEList); @@ -95,7 +96,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ sqlite3WindowUnlinkFromSelect(p->pWin); } #endif - if( bFree ) sqlite3DbFreeNN(db, p); + if( bFree ) sqlite3DbNNFreeNN(db, p); p = pPrior; bFree = 1; } @@ -1286,6 +1287,9 @@ static void selectInnerLoop( testcase( eDest==SRT_Fifo ); testcase( eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); + if( pDest->zAffSdst ){ + sqlite3VdbeChangeP4(v, -1, pDest->zAffSdst, nResultCol); + } #ifndef SQLITE_OMIT_CTE if( eDest==SRT_DistFifo ){ /* If the destination is DistFifo, then cursor (iParm+1) is open @@ -1501,9 +1505,10 @@ KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ */ void sqlite3KeyInfoUnref(KeyInfo *p){ if( p ){ + assert( p->db!=0 ); assert( p->nRef>0 ); p->nRef--; - if( p->nRef==0 ) sqlite3DbFreeNN(p->db, p); + if( p->nRef==0 ) sqlite3DbNNFreeNN(p->db, p); } } @@ -1823,9 +1828,6 @@ static void generateSortTail( ** Return a pointer to a string containing the 'declaration type' of the ** expression pExpr. The string may be treated as static by the caller. ** -** Also try to estimate the size of the returned value and return that -** result in *pEstWidth. -** ** The declaration type is the exact datatype definition extracted from the ** original CREATE TABLE statement if the expression is a column. The ** declaration type for a ROWID field is INTEGER. Exactly when an expression @@ -3691,8 +3693,8 @@ static int multiSelectOrderBy( */ sqlite3VdbeResolveLabel(v, labelEnd); - /* Reassemble the compound query so that it will be freed correctly - ** by the calling function */ + /* Make arrangements to free the 2nd and subsequent arms of the compound + ** after the parse has finished */ if( pSplit->pPrior ){ sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior); @@ -3725,7 +3727,7 @@ static int multiSelectOrderBy( ** the left operands of a RIGHT JOIN. In either case, we need to potentially ** bypass the substituted expression with OP_IfNullRow. ** -** Suppose the original expression integer constant. Even though the table +** Suppose the original expression is an integer constant. Even though the table ** has the nullRow flag set, because the expression is an integer constant, ** it will not be NULLed out. So instead, we insert an OP_IfNullRow opcode ** that checks to see if the nullRow flag is set on the table. If the nullRow @@ -3751,6 +3753,7 @@ typedef struct SubstContext { int iNewTable; /* New table number */ int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ ExprList *pEList; /* Replacement expressions */ + ExprList *pCList; /* Collation sequences for replacement expr */ } SubstContext; /* Forward Declarations */ @@ -3792,9 +3795,10 @@ static Expr *substExpr( #endif { Expr *pNew; - Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr; + int iColumn = pExpr->iColumn; + Expr *pCopy = pSubst->pEList->a[iColumn].pExpr; Expr ifNullRow; - assert( pSubst->pEList!=0 && pExpr->iColumnpEList->nExpr ); + assert( pSubst->pEList!=0 && iColumnpEList->nExpr ); assert( pExpr->pRight==0 ); if( sqlite3ExprIsVector(pCopy) ){ sqlite3VectorErrorMsg(pSubst->pParse, pCopy); @@ -3805,6 +3809,7 @@ static Expr *substExpr( ifNullRow.op = TK_IF_NULL_ROW; ifNullRow.pLeft = pCopy; ifNullRow.iTable = pSubst->iNewTable; + ifNullRow.iColumn = -99; ifNullRow.flags = EP_IfNullRow; pCopy = &ifNullRow; } @@ -3831,11 +3836,16 @@ static Expr *substExpr( /* Ensure that the expression now has an implicit collation sequence, ** just as it did when it was a column of a view or sub-query. */ - if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){ - CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr); - pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, - (pColl ? pColl->zName : "BINARY") + { + CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr); + CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, + pSubst->pCList->a[iColumn].pExpr ); + if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){ + pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, + (pColl ? pColl->zName : "BINARY") + ); + } } ExprClearProperty(pExpr, EP_Collate); } @@ -4028,6 +4038,18 @@ static void renumberCursors( } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ +/* +** If pSel is not part of a compound SELECT, return a pointer to its +** expression list. Otherwise, return a pointer to the expression list +** of the leftmost SELECT in the compound. +*/ +static ExprList *findLeftmostExprlist(Select *pSel){ + while( pSel->pPrior ){ + pSel = pSel->pPrior; + } + return pSel->pEList; +} + #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** This routine attempts to flatten subqueries as a performance optimization. @@ -4072,7 +4094,8 @@ static void renumberCursors( ** (3a) the subquery may not be a join and ** (3b) the FROM clause of the subquery may not contain a virtual ** table and -** (3c) the outer query may not be an aggregate. +** (**) Was: "The outer query may not have a GROUP BY." This case +** is now managed correctly ** (3d) the outer query may not be DISTINCT. ** See also (26) for restrictions on RIGHT JOIN. ** @@ -4129,6 +4152,8 @@ static void renumberCursors( ** (17g) either the subquery is the first element of the outer ** query or there are no RIGHT or FULL JOINs in any arm ** of the subquery. (This is a duplicate of condition (27b).) +** (17h) The corresponding result set expressions in all arms of the +** compound must have the same affinity. ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, @@ -4180,19 +4205,13 @@ static void renumberCursors( ** See also (3) for restrictions on LEFT JOIN. ** ** (27) The subquery may not contain a FULL or RIGHT JOIN unless it -** is the first element of the parent query. This must be the -** the case if: -** (27a) the subquery is not compound query, and +** is the first element of the parent query. Two subcases: +** (27a) the subquery is not a compound query. ** (27b) the subquery is a compound query and the RIGHT JOIN occurs ** in any arm of the compound query. (See also (17g).) ** ** (28) The subquery is not a MATERIALIZED CTE. ** -** (29) Either the subquery is not the right-hand operand of a join with an -** ON or USING clause nor the right-hand operand of a NATURAL JOIN, or -** the right-most table within the FROM clause of the subquery -** is not part of an outer join. -** ** ** In this routine, the "p" parameter is a pointer to the outer query. ** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query @@ -4284,16 +4303,10 @@ static int flattenSubquery( ** ** which is not at all the same thing. ** - ** If the subquery is the right operand of a LEFT JOIN, then the outer - ** query cannot be an aggregate. (3c) This is an artifact of the way - ** aggregates are processed - there is no mechanism to determine if - ** the LEFT JOIN table should be all-NULL. - ** ** See also tickets #306, #350, and #3300. */ if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){ if( pSubSrc->nSrc>1 /* (3a) */ - || isAgg /* (3c) */ || IsVirtual(pSubSrc->a[0].pTab) /* (3b) */ || (p->selFlags & SF_Distinct)!=0 /* (3d) */ || (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */ @@ -4302,15 +4315,6 @@ static int flattenSubquery( } isOuterJoin = 1; } -#ifdef SQLITE_EXTRA_IFNULLROW - else if( iFrom>0 && !isAgg ){ - /* Setting isOuterJoin to -1 causes OP_IfNullRow opcodes to be generated for - ** every reference to any result column from subquery in a join, even - ** though they are not necessary. This will stress-test the OP_IfNullRow - ** opcode. */ - isOuterJoin = -1; - } -#endif assert( pSubSrc->nSrc>0 ); /* True by restriction (7) */ if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ @@ -4320,41 +4324,13 @@ static int flattenSubquery( return 0; /* (28) */ } - /* Restriction (29): - ** - ** We do not want two constraints on the same term of the flattened - ** query where one constraint has EP_InnerON and the other is EP_OuterON. - ** To prevent this, one or the other of the following conditions must be - ** false: - ** - ** (29a) The right-most entry in the FROM clause of the subquery - ** must not be part of an outer join. - ** - ** (29b) The subquery itself must not be the right operand of a - ** NATURAL join or a join that as an ON or USING clause. - ** - ** These conditions are sufficient to keep an EP_OuterON from being - ** flattened into an EP_InnerON. Restrictions (3a) and (27a) prevent - ** an EP_InnerON from being flattened into an EP_OuterON. - */ - if( pSubSrc->nSrc>=2 - && (pSubSrc->a[pSubSrc->nSrc-1].fg.jointype & JT_OUTER)!=0 - ){ - if( (pSubitem->fg.jointype & JT_NATURAL)!=0 - || pSubitem->fg.isUsing - || NEVER(pSubitem->u3.pOn!=0) /* ON clause already shifted into WHERE */ - || pSubitem->fg.isOn - ){ - return 0; - } - } - /* Restriction (17): If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries ** that make up the compound SELECT are allowed to be aggregate or distinct ** queries. */ if( pSub->pPrior ){ + int ii; if( pSub->pOrderBy ){ return 0; /* Restriction (20) */ } @@ -4387,7 +4363,6 @@ static int flattenSubquery( /* Restriction (18). */ if( p->pOrderBy ){ - int ii; for(ii=0; iipOrderBy->nExpr; ii++){ if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0; } @@ -4396,6 +4371,21 @@ static int flattenSubquery( /* Restriction (23) */ if( (p->selFlags & SF_Recursive) ) return 0; + /* Restriction (17h) */ + for(ii=0; iipEList->nExpr; ii++){ + char aff; + assert( pSub->pEList->a[ii].pExpr!=0 ); + aff = sqlite3ExprAffinity(pSub->pEList->a[ii].pExpr); + for(pSub1=pSub->pPrior; pSub1; pSub1=pSub1->pPrior){ + assert( pSub1->pEList!=0 ); + assert( pSub1->pEList->nExpr>ii ); + assert( pSub1->pEList->a[ii].pExpr!=0 ); + if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){ + return 0; + } + } + } + if( pSrc->nSrc>1 ){ if( pParse->nSelect>500 ) return 0; if( OptimizationDisabled(db, SQLITE_FlttnUnionAll) ) return 0; @@ -4629,6 +4619,7 @@ static int flattenSubquery( x.iNewTable = iNewParent; x.isOuterJoin = isOuterJoin; x.pEList = pSub->pEList; + x.pCList = findLeftmostExprlist(pSub); substSelect(&x, pParent, 0); } @@ -4648,7 +4639,7 @@ static int flattenSubquery( pSub->pLimit = 0; } - /* Recompute the SrcList_item.colUsed masks for the flattened + /* Recompute the SrcItem.colUsed masks for the flattened ** tables. */ for(i=0; ia[i+iFrom]); @@ -5038,6 +5029,13 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** be materialized. (This restriction is implemented in the calling ** routine.) ** +** (8) The subquery may not be a compound that uses UNION, INTERSECT, +** or EXCEPT. (We could, perhaps, relax this restriction to allow +** this case if none of the comparisons operators between left and +** right arms of the compound use a collation other than BINARY. +** But it is a lot of work to check that case for an obscure and +** minor optimization, so we omit it for now.) +** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. */ @@ -5057,6 +5055,10 @@ static int pushDownWhereTerms( if( pSubq->pPrior ){ Select *pSel; for(pSel=pSubq; pSel; pSel=pSel->pPrior){ + u8 op = pSel->op; + assert( op==TK_ALL || op==TK_SELECT + || op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT ); + if( op!=TK_ALL && op!=TK_SELECT ) return 0; /* restriction (8) */ if( pSel->pWin ) return 0; /* restriction (6b) */ } }else{ @@ -5111,6 +5113,7 @@ static int pushDownWhereTerms( x.iNewTable = pSrc->iCursor; x.isOuterJoin = 0; x.pEList = pSubq->pEList; + x.pCList = findLeftmostExprlist(pSubq); pNew = substExpr(&x, pNew); #ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){ @@ -5635,9 +5638,9 @@ void sqlite3SelectPopWith(Walker *pWalker, Select *p){ #endif /* -** The SrcList_item structure passed as the second argument represents a +** The SrcItem structure passed as the second argument represents a ** sub-query in the FROM clause of a SELECT statement. This function -** allocates and populates the SrcList_item.pTab object. If successful, +** allocates and populates the SrcItem.pTab object. If successful, ** SQLITE_OK is returned. Otherwise, if an OOM error is encountered, ** SQLITE_NOMEM. */ @@ -6470,7 +6473,7 @@ static void havingToWhere(Parse *pParse, Select *p){ /* ** Check to see if the pThis entry of pTabList is a self-join of a prior view. -** If it is, then return the SrcList_item for the prior view. If it is not, +** If it is, then return the SrcItem for the prior view. If it is not, ** then return 0. */ static SrcItem *isSelfJoinView( @@ -7088,7 +7091,10 @@ int sqlite3Select( } sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); ExplainQueryPlan((pParse, 1, "MATERIALIZE %!S", pItem)); + dest.zAffSdst = sqlite3TableAffinityStr(db, pItem->pTab); sqlite3Select(pParse, pSub, &dest); + sqlite3DbFree(db, dest.zAffSdst); + dest.zAffSdst = 0; pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1); @@ -7208,7 +7214,7 @@ int sqlite3Select( if( (p->selFlags & SF_FixedLimit)==0 ){ p->nSelectRow = 320; /* 4 billion rows */ } - computeLimitRegisters(pParse, p, iEnd); + if( p->pLimit ) computeLimitRegisters(pParse, p, iEnd); if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen); sSort.sortFlags |= SORTFLAG_UseSorter; @@ -7430,8 +7436,13 @@ int sqlite3Select( sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY"); } for(ii=0; iinColumn; ii++){ - sqlite3DebugPrintf("agg-column[%d] iMem=%d\n", - ii, pAggInfo->aCol[ii].iMem); + struct AggInfo_col *pCol = &pAggInfo->aCol[ii]; + sqlite3DebugPrintf( + "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d" + " iSorterColumn=%d\n", + ii, pCol->pTab ? pCol->pTab->zName : "NULL", + pCol->iTable, pCol->iColumn, pCol->iMem, + pCol->iSorterColumn); sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0); } for(ii=0; iinFunc; ii++){ @@ -7509,7 +7520,7 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); SELECTTRACE(1,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, - 0, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) + p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 ); if( pWInfo==0 ){ @@ -7552,15 +7563,15 @@ int sqlite3Select( regBase = sqlite3GetTempRange(pParse, nCol); sqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0, 0); j = nGroupBy; + pAggInfo->directMode = 1; for(i=0; inColumn; i++){ struct AggInfo_col *pCol = &pAggInfo->aCol[i]; if( pCol->iSorterColumn>=j ){ - int r1 = j + regBase; - sqlite3ExprCodeGetColumnOfTable(v, - pCol->pTab, pCol->iTable, pCol->iColumn, r1); + sqlite3ExprCode(pParse, pCol->pCExpr, j + regBase); j++; } } + pAggInfo->directMode = 0; regRecord = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord); sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); @@ -7808,7 +7819,7 @@ int sqlite3Select( SELECTTRACE(1,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy, - pDistinct, 0, minMaxFlag|distFlag, 0); + pDistinct, p, minMaxFlag|distFlag, 0); if( pWInfo==0 ){ goto select_end; } diff --git a/src/shell.c.in b/src/shell.c.in index 4e921cb6e..2b4cede76 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -16,6 +16,8 @@ /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif +typedef unsigned int u32; +typedef unsigned short int u16; /* ** Optionally #include a user-defined header, whereby compilation options @@ -37,6 +39,15 @@ # define SQLITE_OS_WINRT 0 #endif +/* +** If SQLITE_SHELL_FIDDLE is defined then the shell is modified +** somewhat for use as a WASM module in a web browser. This flag +** should only be used when building the "fiddle" web application, as +** the browser-mode build has much different user input requirements +** and this build mode rewires the user input subsystem to account for +** that. +*/ + /* ** Warning pragmas copied from msvc.h in the core. */ @@ -76,6 +87,14 @@ # define _LARGEFILE_SOURCE 1 #endif +#if defined(SQLITE_SHELL_FIDDLE) && !defined(_POSIX_SOURCE) +/* +** emcc requires _POSIX_SOURCE (or one of several similar defines) +** to expose strdup(). +*/ +# define _POSIX_SOURCE +#endif + #include #include #include @@ -229,20 +248,21 @@ static void setTextMode(FILE *file, int isOutput){ # define setTextMode(X,Y) #endif -/* -** When compiling with emcc (a.k.a. emscripten), we're building a -** WebAssembly (WASM) bundle and need to disable and rewire a few -** things. -*/ -#ifdef __EMSCRIPTEN__ -#define SQLITE_SHELL_WASM_MODE -#else -#undef SQLITE_SHELL_WASM_MODE -#endif - /* True if the timer is enabled */ static int enableTimer = 0; +/* A version of strcmp() that works with NULL values */ +static int cli_strcmp(const char *a, const char *b){ + if( a==0 ) a = ""; + if( b==0 ) b = ""; + return strcmp(a,b); +} +static int cli_strncmp(const char *a, const char *b, size_t n){ + if( a==0 ) a = ""; + if( b==0 ) b = ""; + return strncmp(a,b,n); +} + /* Return the current wall-clock time */ static sqlite3_int64 timeOfDay(void){ static sqlite3_vfs *clockVfs = 0; @@ -531,6 +551,7 @@ static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ int i; int n; int aw = w<0 ? -w : w; + if( zUtf==0 ) zUtf = ""; for(i=n=0; zUtf[i]; i++){ if( (zUtf[i]&0xc0)!=0x80 ){ n++; @@ -674,7 +695,7 @@ static char *local_getline(char *zLine, FILE *in){ if( stdin_is_interactive && in==stdin ){ char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); if( zTrans ){ - int nTrans = strlen30(zTrans)+1; + i64 nTrans = strlen(zTrans)+1; if( nTrans>nLine ){ zLine = realloc(zLine, nTrans); shell_check_oom(zLine); @@ -701,7 +722,7 @@ static char *local_getline(char *zLine, FILE *in){ ** be freed by the caller or else passed back into this routine via the ** zPrior argument for reuse. */ -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ char *zPrompt; char *zResult; @@ -721,7 +742,7 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ } return zResult; } -#endif /* !SQLITE_SHELL_WASM_MODE */ +#endif /* !SQLITE_SHELL_FIDDLE */ /* ** Return the value of a hexadecimal digit. Return -1 if the input @@ -810,9 +831,9 @@ static void freeText(ShellText *p){ ** quote character for zAppend. */ static void appendText(ShellText *p, const char *zAppend, char quote){ - int len; - int i; - int nAppend = strlen30(zAppend); + i64 len; + i64 i; + i64 nAppend = strlen30(zAppend); len = nAppend+p->n+1; if( quote ){ @@ -971,10 +992,10 @@ static void shellAddSchemaName( const char *zName = (const char*)sqlite3_value_text(apVal[2]); sqlite3 *db = sqlite3_context_db_handle(pCtx); UNUSED_PARAMETER(nVal); - if( zIn!=0 && strncmp(zIn, "CREATE ", 7)==0 ){ + if( zIn!=0 && cli_strncmp(zIn, "CREATE ", 7)==0 ){ for(i=0; i> 4) ]; + zStr[i*2+1] = aHex[ (aBlob[i] & 0x0F) ]; + } + zStr[i*2] = '\0'; + + raw_printf(out,"X'%s'", zStr); + sqlite3_free(zStr); } /* @@ -1665,9 +1707,9 @@ static void output_c_string(FILE *out, const char *z){ /* ** Output the given string as a quoted according to JSON quoting rules. */ -static void output_json_string(FILE *out, const char *z, int n){ +static void output_json_string(FILE *out, const char *z, i64 n){ unsigned int c; - if( n<0 ) n = (int)strlen(z); + if( n<0 ) n = strlen(z); fputc('"', out); while( n-- ){ c = *(z++); @@ -1833,12 +1875,12 @@ static int safeModeAuth( "zipfile", "zipfile_cds", }; - UNUSED_PARAMETER(zA2); + UNUSED_PARAMETER(zA1); UNUSED_PARAMETER(zA3); UNUSED_PARAMETER(zA4); switch( op ){ case SQLITE_ATTACH: { -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE /* In WASM builds the filesystem is a virtual sandbox, so ** there's no harm in using ATTACH. */ failIfSafeMode(p, "cannot run ATTACH in safe mode"); @@ -1848,7 +1890,7 @@ static int safeModeAuth( case SQLITE_FUNCTION: { int i; for(i=0; iautoEQPtest ){ utf8_printf(p->out, "%d,%d,%s\n", iEqpId, p2, zText); } @@ -1993,14 +2059,14 @@ static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){ */ static void eqp_render_level(ShellState *p, int iEqpId){ EQPGraphRow *pRow, *pNext; - int n = strlen30(p->sGraph.zPrefix); + i64 n = strlen(p->sGraph.zPrefix); char *z; for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){ pNext = eqp_next_row(p, iEqpId, pRow); z = pRow->zText; utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix, pNext ? "|--" : "`--", z); - if( n<(int)sizeof(p->sGraph.zPrefix)-7 ){ + if( n<(i64)sizeof(p->sGraph.zPrefix)-7 ){ memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4); eqp_render_level(p, pRow->iEqpId); p->sGraph.zPrefix[n] = 0; @@ -2600,6 +2666,7 @@ static char *shell_error_context(const char *zSql, sqlite3 *db){ while( (zSql[len]&0xc0)==0x80 ) len--; } zCode = sqlite3_mprintf("%.*s", len, zSql); + shell_check_oom(zCode); for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } if( iOffset<25 ){ zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode, iOffset, ""); @@ -2716,7 +2783,7 @@ static void displayLinuxIoStats(FILE *out){ int i; for(i=0; icMode = p->mode; sqlite3_reset(pSql); return; @@ -3754,10 +3821,10 @@ static int expertDotCommand( int n; if( z[0]=='-' && z[1]=='-' ) z++; n = strlen30(z); - if( n>=2 && 0==strncmp(z, "-verbose", n) ){ + if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){ pState->expert.bVerbose = 1; } - else if( n>=2 && 0==strncmp(z, "-sample", n) ){ + else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){ if( i==(nArg-1) ){ raw_printf(stderr, "option requires an argument: %s\n", z); rc = SQLITE_ERROR; @@ -4105,18 +4172,20 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ zTable = azArg[0]; zType = azArg[1]; zSql = azArg[2]; + if( zTable==0 ) return 0; + if( zType==0 ) return 0; dataOnly = (p->shellFlgs & SHFLG_DumpDataOnly)!=0; noSys = (p->shellFlgs & SHFLG_DumpNoSys)!=0; - if( strcmp(zTable, "sqlite_sequence")==0 && !noSys ){ + if( cli_strcmp(zTable, "sqlite_sequence")==0 && !noSys ){ if( !dataOnly ) raw_printf(p->out, "DELETE FROM sqlite_sequence;\n"); }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){ if( !dataOnly ) raw_printf(p->out, "ANALYZE sqlite_schema;\n"); - }else if( strncmp(zTable, "sqlite_", 7)==0 ){ + }else if( cli_strncmp(zTable, "sqlite_", 7)==0 ){ return 0; }else if( dataOnly ){ /* no-op */ - }else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ + }else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ char *zIns; if( !p->writableSchema ){ raw_printf(p->out, "PRAGMA writable_schema=ON;\n"); @@ -4134,7 +4203,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ printSchemaLine(p->out, zSql, ";\n"); } - if( strcmp(zType, "table")==0 ){ + if( cli_strcmp(zType, "table")==0 ){ ShellText sSelect; ShellText sTable; char **azCol; @@ -4252,7 +4321,7 @@ static int run_schema_dump_query( */ static const char *(azHelp[]) = { #if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \ - && !defined(SQLITE_SHELL_WASM_MODE) + && !defined(SQLITE_SHELL_FIDDLE) ".archive ... Manage SQL archives", " Each command must have exactly one of the following options:", " -c, --create Create a new archive", @@ -4278,7 +4347,7 @@ static const char *(azHelp[]) = { #ifndef SQLITE_OMIT_AUTHORIZATION ".auth ON|OFF Show authorizer callbacks", #endif -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".backup ?DB? FILE Backup DB (default \"main\") to FILE", " Options:", " --append Use the appendvfs", @@ -4286,18 +4355,18 @@ static const char *(azHelp[]) = { #endif ".bail on|off Stop after hitting an error. Default OFF", ".binary on|off Turn binary output on or off. Default OFF", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".cd DIRECTORY Change the working directory to DIRECTORY", #endif ".changes on|off Show number of rows changed by SQL", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".check GLOB Fail if output since .testcase does not match", ".clone NEWDB Clone data into NEWDB from the existing database", #endif ".connection [close] [#] Open or close an auxiliary database connection", ".databases List names and files of attached databases", ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) +#if SQLITE_SHELL_HAVE_RECOVER ".dbinfo ?DB? Show status information about the database", #endif ".dump ?OBJECTS? Render database content as SQL", @@ -4316,11 +4385,11 @@ static const char *(azHelp[]) = { " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", #endif " trigger Like \"full\" but also show trigger bytecode", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".excel Display the output of next command in spreadsheet", " --bom Put a UTF8 byte-order mark on intermediate file", #endif -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".exit ?CODE? Exit this program with return-code CODE", #endif ".expert EXPERIMENTAL. Suggest indexes for queries", @@ -4331,7 +4400,7 @@ static const char *(azHelp[]) = { ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", ".headers on|off Turn display of headers on or off", ".help ?-all? ?PATTERN? Show help text for PATTERN", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".import FILE TABLE Import data from FILE into TABLE", " Options:", " --ascii Use \\037 and \\036 as column and row separators", @@ -4360,10 +4429,10 @@ static const char *(azHelp[]) = { ".lint OPTIONS Report potential schema issues.", " Options:", " fkey-indexes Find missing foreign key indexes", -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_WASM_MODE) +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE) ".load FILE ?ENTRY? Load an extension library", #endif -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", #endif ".mode MODE ?OPTIONS? Set output mode", @@ -4378,7 +4447,7 @@ static const char *(azHelp[]) = { " line One value per line", " list Values delimited by \"|\"", " markdown Markdown table format", - " qbox Shorthand for \"box --width 60 --quote\"", + " qbox Shorthand for \"box --wrap 60 --quote\"", " quote Escape answers as for SQL", " table ASCII-art table", " tabs Tab-separated values", @@ -4390,11 +4459,11 @@ static const char *(azHelp[]) = { " --quote Quote output text as SQL literals", " --noquote Do not quote output text", " TABLE The name of SQL table used for \"insert\" mode", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".nonce STRING Suspend safe mode for one command if nonce matches", #endif ".nullvalue STRING Use STRING in place of NULL values", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", " If FILE begins with '|' then open as a pipe", " --bom Put a UTF8 byte-order mark at the beginning", @@ -4416,7 +4485,7 @@ static const char *(azHelp[]) = { " --nofollow Do not follow symbolic links", " --readonly Open FILE readonly", " --zip FILE is a ZIP archive", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".output ?FILE? Send output to FILE or stdout if FILE is omitted", " If FILE begins with '|' then open it as a pipe.", " Options:", @@ -4440,20 +4509,19 @@ static const char *(azHelp[]) = { " --reset Reset the count for each input and interrupt", #endif ".prompt MAIN CONTINUE Replace the standard prompts", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".quit Exit this program", ".read FILE Read input from FILE or command output", " If FILE begins with \"|\", it is a command that generates the input.", #endif -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) +#if SQLITE_SHELL_HAVE_RECOVER ".recover Recover as much data as possible from corrupt db.", - " --freelist-corrupt Assume the freelist is corrupt", - " --recovery-db NAME Store recovery metadata in database file NAME", + " --ignore-freelist Ignore pages that appear to be on db freelist", " --lost-and-found TABLE Alternative name for the lost-and-found table", " --no-rowids Do not attempt to recover rowid values", " that are not also INTEGER PRIMARY KEYs", #endif -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", #endif @@ -4490,7 +4558,7 @@ static const char *(azHelp[]) = { " --sha3-384 Use the sha3-384 algorithm", " --sha3-512 Use the sha3-512 algorithm", " Any other argument is a LIKE pattern for tables to hash", -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) ".shell CMD ARGS... Run CMD ARGS... in a system shell", #endif ".show Show the current values for various settings", @@ -4499,11 +4567,11 @@ static const char *(azHelp[]) = { " on Turn on automatic stat display", " stmt Show statement stats", " vmstep Show the virtual machine step count only", -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) ".system CMD ARGS... Run CMD ARGS... in a system shell", #endif ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE ".testcase NAME Begin redirecting output to 'testcase-out.txt'", #endif ".testctrl CMD ... Run various sqlite3_test_control() operations", @@ -4553,9 +4621,9 @@ static int showHelp(FILE *out, const char *zPattern){ char *zPat; if( zPattern==0 || zPattern[0]=='0' - || strcmp(zPattern,"-a")==0 - || strcmp(zPattern,"-all")==0 - || strcmp(zPattern,"--all")==0 + || cli_strcmp(zPattern,"-a")==0 + || cli_strcmp(zPattern,"-all")==0 + || cli_strcmp(zPattern,"--all")==0 ){ /* Show all commands, but only one line per command */ if( zPattern==0 ) zPattern = ""; @@ -4792,7 +4860,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){ iOffset = k; continue; } - if( strncmp(zLine, "| end ", 6)==0 ){ + if( cli_strncmp(zLine, "| end ", 6)==0 ){ break; } rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", @@ -4820,7 +4888,7 @@ readHexDb_error: }else{ while( fgets(zLine, sizeof(zLine), p->in)!=0 ){ nLine++; - if(strncmp(zLine, "| end ", 6)==0 ) break; + if(cli_strncmp(zLine, "| end ", 6)==0 ) break; } p->lineno = nLine; } @@ -4912,28 +4980,28 @@ static void shellEscapeCrnl( const char *zText = (const char*)sqlite3_value_text(argv[0]); UNUSED_PARAMETER(argc); if( zText && zText[0]=='\'' ){ - int nText = sqlite3_value_bytes(argv[0]); - int i; + i64 nText = sqlite3_value_bytes(argv[0]); + i64 i; char zBuf1[20]; char zBuf2[20]; const char *zNL = 0; const char *zCR = 0; - int nCR = 0; - int nNL = 0; + i64 nCR = 0; + i64 nNL = 0; for(i=0; zText[i]; i++){ if( zNL==0 && zText[i]=='\n' ){ zNL = unused_string(zText, "\\n", "\\012", zBuf1); - nNL = (int)strlen(zNL); + nNL = strlen(zNL); } if( zCR==0 && zText[i]=='\r' ){ zCR = unused_string(zText, "\\r", "\\015", zBuf2); - nCR = (int)strlen(zCR); + nCR = strlen(zCR); } } if( zNL || zCR ){ - int iOut = 0; + i64 iOut = 0; i64 nMax = (nNL > nCR) ? nNL : nCR; i64 nAlloc = nMax * nText + (nMax+64)*2; char *zOut = (char*)sqlite3_malloc64(nAlloc); @@ -5056,11 +5124,11 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_regexp_init(p->db, 0, 0); sqlite3_ieee_init(p->db, 0, 0); sqlite3_series_init(p->db, 0, 0); -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE sqlite3_fileio_init(p->db, 0, 0); sqlite3_completion_init(p->db, 0, 0); #endif -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) +#if SQLITE_SHELL_HAVE_RECOVER sqlite3_dbdata_init(p->db, 0, 0); #endif #ifdef SQLITE_HAVE_ZLIB @@ -5174,8 +5242,8 @@ static char **readline_completion(const char *zText, int iStart, int iEnd){ ** Linenoise completion callback */ static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ - int nLine = strlen30(zLine); - int i, iStart; + i64 nLine = strlen(zLine); + i64 i, iStart; sqlite3_stmt *pStmt = 0; char *zSql; char zBuf[1000]; @@ -5313,11 +5381,11 @@ static void output_file_close(FILE *f){ */ static FILE *output_file_open(const char *zFile, int bTextMode){ FILE *f; - if( strcmp(zFile,"stdout")==0 ){ + if( cli_strcmp(zFile,"stdout")==0 ){ f = stdout; - }else if( strcmp(zFile, "stderr")==0 ){ + }else if( cli_strcmp(zFile, "stderr")==0 ){ f = stderr; - }else if( strcmp(zFile, "off")==0 ){ + }else if( cli_strcmp(zFile, "off")==0 ){ f = 0; }else{ f = fopen(zFile, bTextMode ? "w" : "wb"); @@ -5341,7 +5409,7 @@ static int sql_trace_callback( ShellState *p = (ShellState*)pArg; sqlite3_stmt *pStmt; const char *zSql; - int nSql; + i64 nSql; if( p->traceOut==0 ) return 0; if( mType==SQLITE_TRACE_CLOSE ){ utf8_printf(p->traceOut, "-- closing database connection\n"); @@ -5369,17 +5437,18 @@ static int sql_trace_callback( } } if( zSql==0 ) return 0; - nSql = strlen30(zSql); + nSql = strlen(zSql); + if( nSql>1000000000 ) nSql = 1000000000; while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } switch( mType ){ case SQLITE_TRACE_ROW: case SQLITE_TRACE_STMT: { - utf8_printf(p->traceOut, "%.*s;\n", nSql, zSql); + utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql); break; } case SQLITE_TRACE_PROFILE: { sqlite3_int64 nNanosec = *(sqlite3_int64*)pX; - utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", nSql, zSql, nNanosec); + utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); break; } } @@ -5928,7 +5997,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ } if( zDb==0 ){ zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); - }else if( strcmp(zDb,"temp")==0 ){ + }else if( cli_strcmp(zDb,"temp")==0 ){ zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema"); }else{ zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb); @@ -5944,8 +6013,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion); return 0; } -#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) - && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +#endif /* SQLITE_SHELL_HAVE_RECOVER */ /* ** Print the current sqlite3_errmsg() value to stderr and return 1. @@ -6062,7 +6130,7 @@ static int optionMatch(const char *zStr, const char *zOpt){ if( zStr[0]!='-' ) return 0; zStr++; if( zStr[0]=='-' ) zStr++; - return strcmp(zStr, zOpt)==0; + return cli_strcmp(zStr, zOpt)==0; } /* @@ -7218,364 +7286,16 @@ end_ar_command: *******************************************************************************/ #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) -/* -** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op. -** Otherwise, the SQL statement or statements in zSql are executed using -** database connection db and the error code written to *pRc before -** this function returns. -*/ -static void shellExec(sqlite3 *db, int *pRc, const char *zSql){ - int rc = *pRc; - if( rc==SQLITE_OK ){ - char *zErr = 0; - rc = sqlite3_exec(db, zSql, 0, 0, &zErr); - if( rc!=SQLITE_OK ){ - raw_printf(stderr, "SQL error: %s\n", zErr); - } - sqlite3_free(zErr); - *pRc = rc; - } -} - -/* -** Like shellExec(), except that zFmt is a printf() style format string. -*/ -static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){ - char *z = 0; - if( *pRc==SQLITE_OK ){ - va_list ap; - va_start(ap, zFmt); - z = sqlite3_vmprintf(zFmt, ap); - va_end(ap); - if( z==0 ){ - *pRc = SQLITE_NOMEM; - }else{ - shellExec(db, pRc, z); - } - sqlite3_free(z); - } -} - -/* -** If *pRc is not SQLITE_OK when this function is called, it is a no-op. -** Otherwise, an attempt is made to allocate, zero and return a pointer -** to a buffer nByte bytes in size. If an OOM error occurs, *pRc is set -** to SQLITE_NOMEM and NULL returned. -*/ -static void *shellMalloc(int *pRc, sqlite3_int64 nByte){ - void *pRet = 0; - if( *pRc==SQLITE_OK ){ - pRet = sqlite3_malloc64(nByte); - if( pRet==0 ){ - *pRc = SQLITE_NOMEM; - }else{ - memset(pRet, 0, nByte); - } - } - return pRet; -} +#if SQLITE_SHELL_HAVE_RECOVER /* -** If *pRc is not SQLITE_OK when this function is called, it is a no-op. -** Otherwise, zFmt is treated as a printf() style string. The result of -** formatting it along with any trailing arguments is written into a -** buffer obtained from sqlite3_malloc(), and pointer to which is returned. -** It is the responsibility of the caller to eventually free this buffer -** using a call to sqlite3_free(). -** -** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL -** pointer returned. +** This function is used as a callback by the recover extension. Simply +** print the supplied SQL statement to stdout. */ -static char *shellMPrintf(int *pRc, const char *zFmt, ...){ - char *z = 0; - if( *pRc==SQLITE_OK ){ - va_list ap; - va_start(ap, zFmt); - z = sqlite3_vmprintf(zFmt, ap); - va_end(ap); - if( z==0 ){ - *pRc = SQLITE_NOMEM; - } - } - return z; -} - - -/* -** When running the ".recover" command, each output table, and the special -** orphaned row table if it is required, is represented by an instance -** of the following struct. -*/ -typedef struct RecoverTable RecoverTable; -struct RecoverTable { - char *zQuoted; /* Quoted version of table name */ - int nCol; /* Number of columns in table */ - char **azlCol; /* Array of column lists */ - int iPk; /* Index of IPK column */ -}; - -/* -** Free a RecoverTable object allocated by recoverFindTable() or -** recoverOrphanTable(). -*/ -static void recoverFreeTable(RecoverTable *pTab){ - if( pTab ){ - sqlite3_free(pTab->zQuoted); - if( pTab->azlCol ){ - int i; - for(i=0; i<=pTab->nCol; i++){ - sqlite3_free(pTab->azlCol[i]); - } - sqlite3_free(pTab->azlCol); - } - sqlite3_free(pTab); - } -} - -/* -** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. -** Otherwise, it allocates and returns a RecoverTable object based on the -** final four arguments passed to this function. It is the responsibility -** of the caller to eventually free the returned object using -** recoverFreeTable(). -*/ -static RecoverTable *recoverNewTable( - int *pRc, /* IN/OUT: Error code */ - const char *zName, /* Name of table */ - const char *zSql, /* CREATE TABLE statement */ - int bIntkey, - int nCol -){ - sqlite3 *dbtmp = 0; /* sqlite3 handle for testing CREATE TABLE */ - int rc = *pRc; - RecoverTable *pTab = 0; - - pTab = (RecoverTable*)shellMalloc(&rc, sizeof(RecoverTable)); - if( rc==SQLITE_OK ){ - int nSqlCol = 0; - int bSqlIntkey = 0; - sqlite3_stmt *pStmt = 0; - - rc = sqlite3_open("", &dbtmp); - if( rc==SQLITE_OK ){ - sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0, - shellIdQuote, 0, 0); - } - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(dbtmp, "PRAGMA writable_schema = on", 0, 0, 0); - } - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0); - if( rc==SQLITE_ERROR ){ - rc = SQLITE_OK; - goto finished; - } - } - shellPreparePrintf(dbtmp, &rc, &pStmt, - "SELECT count(*) FROM pragma_table_info(%Q)", zName - ); - if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - nSqlCol = sqlite3_column_int(pStmt, 0); - } - shellFinalize(&rc, pStmt); - - if( rc!=SQLITE_OK || nSqlColiPk to the index - ** of the column, where columns are 0-numbered from left to right. - ** Or, if this is a WITHOUT ROWID table or if there is no IPK column, - ** leave zPk as "_rowid_" and pTab->iPk at -2. */ - pTab->iPk = -2; - if( bIntkey ){ - shellPreparePrintf(dbtmp, &rc, &pPkFinder, - "SELECT cid, name FROM pragma_table_info(%Q) " - " WHERE pk=1 AND type='integer' COLLATE nocase" - " AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)" - , zName, zName - ); - if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){ - pTab->iPk = sqlite3_column_int(pPkFinder, 0); - zPk = (const char*)sqlite3_column_text(pPkFinder, 1); - if( zPk==0 ){ zPk = "_"; /* Defensive. Should never happen */ } - } - } - - pTab->zQuoted = shellMPrintf(&rc, "\"%w\"", zName); - pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1)); - pTab->nCol = nSqlCol; - - if( bIntkey ){ - pTab->azlCol[0] = shellMPrintf(&rc, "\"%w\"", zPk); - }else{ - pTab->azlCol[0] = shellMPrintf(&rc, ""); - } - i = 1; - shellPreparePrintf(dbtmp, &rc, &pStmt, - "SELECT %Q || group_concat(shell_idquote(name), ', ') " - " FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) " - "FROM pragma_table_info(%Q)", - bIntkey ? ", " : "", pTab->iPk, - bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ", - zName - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zText = (const char*)sqlite3_column_text(pStmt, 0); - pTab->azlCol[i] = shellMPrintf(&rc, "%s%s", pTab->azlCol[0], zText); - i++; - } - shellFinalize(&rc, pStmt); - - shellFinalize(&rc, pPkFinder); - } - } - - finished: - sqlite3_close(dbtmp); - *pRc = rc; - if( rc!=SQLITE_OK || (pTab && pTab->zQuoted==0) ){ - recoverFreeTable(pTab); - pTab = 0; - } - return pTab; -} - -/* -** This function is called to search the schema recovered from the -** sqlite_schema table of the (possibly) corrupt database as part -** of a ".recover" command. Specifically, for a table with root page -** iRoot and at least nCol columns. Additionally, if bIntkey is 0, the -** table must be a WITHOUT ROWID table, or if non-zero, not one of -** those. -** -** If a table is found, a (RecoverTable*) object is returned. Or, if -** no such table is found, but bIntkey is false and iRoot is the -** root page of an index in the recovered schema, then (*pbNoop) is -** set to true and NULL returned. Or, if there is no such table or -** index, NULL is returned and (*pbNoop) set to 0, indicating that -** the caller should write data to the orphans table. -*/ -static RecoverTable *recoverFindTable( - ShellState *pState, /* Shell state object */ - int *pRc, /* IN/OUT: Error code */ - int iRoot, /* Root page of table */ - int bIntkey, /* True for an intkey table */ - int nCol, /* Number of columns in table */ - int *pbNoop /* OUT: True if iRoot is root of index */ -){ - sqlite3_stmt *pStmt = 0; - RecoverTable *pRet = 0; - int bNoop = 0; - const char *zSql = 0; - const char *zName = 0; - - /* Search the recovered schema for an object with root page iRoot. */ - shellPreparePrintf(pState->db, pRc, &pStmt, - "SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot - ); - while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zType = (const char*)sqlite3_column_text(pStmt, 0); - if( bIntkey==0 && sqlite3_stricmp(zType, "index")==0 ){ - bNoop = 1; - break; - } - if( sqlite3_stricmp(zType, "table")==0 ){ - zName = (const char*)sqlite3_column_text(pStmt, 1); - zSql = (const char*)sqlite3_column_text(pStmt, 2); - if( zName!=0 && zSql!=0 ){ - pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol); - break; - } - } - } - - shellFinalize(pRc, pStmt); - *pbNoop = bNoop; - return pRet; -} - -/* -** Return a RecoverTable object representing the orphans table. -*/ -static RecoverTable *recoverOrphanTable( - ShellState *pState, /* Shell state object */ - int *pRc, /* IN/OUT: Error code */ - const char *zLostAndFound, /* Base name for orphans table */ - int nCol /* Number of user data columns */ -){ - RecoverTable *pTab = 0; - if( nCol>=0 && *pRc==SQLITE_OK ){ - int i; - - /* This block determines the name of the orphan table. The prefered - ** name is zLostAndFound. But if that clashes with another name - ** in the recovered schema, try zLostAndFound_0, zLostAndFound_1 - ** and so on until a non-clashing name is found. */ - int iTab = 0; - char *zTab = shellMPrintf(pRc, "%s", zLostAndFound); - sqlite3_stmt *pTest = 0; - shellPrepare(pState->db, pRc, - "SELECT 1 FROM recovery.schema WHERE name=?", &pTest - ); - if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); - while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pTest) ){ - shellReset(pRc, pTest); - sqlite3_free(zTab); - zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++); - sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); - } - shellFinalize(pRc, pTest); - - pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable)); - if( pTab ){ - pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab); - pTab->nCol = nCol; - pTab->iPk = -2; - if( nCol>0 ){ - pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1)); - if( pTab->azlCol ){ - pTab->azlCol[nCol] = shellMPrintf(pRc, ""); - for(i=nCol-1; i>=0; i--){ - pTab->azlCol[i] = shellMPrintf(pRc, "%s, NULL", pTab->azlCol[i+1]); - } - } - } - - if( *pRc!=SQLITE_OK ){ - recoverFreeTable(pTab); - pTab = 0; - }else{ - raw_printf(pState->out, - "CREATE TABLE %s(rootpgno INTEGER, " - "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted - ); - for(i=0; iout, ", c%d", i); - } - raw_printf(pState->out, ");\n"); - } - } - sqlite3_free(zTab); - } - return pTab; +static int recoverSqlCb(void *pCtx, const char *zSql){ + ShellState *pState = (ShellState*)pCtx; + utf8_printf(pState->out, "%s;\n", zSql); + return SQLITE_OK; } /* @@ -7585,32 +7305,33 @@ static RecoverTable *recoverOrphanTable( */ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ int rc = SQLITE_OK; - sqlite3_stmt *pLoop = 0; /* Loop through all root pages */ - sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */ - sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */ - const char *zRecoveryDb = ""; /* Name of "recovery" database */ - const char *zLostAndFound = "lost_and_found"; - int i; - int nOrphan = -1; - RecoverTable *pOrphan = 0; - - int bFreelist = 1; /* 0 if --freelist-corrupt is specified */ + const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */ + const char *zLAF = "lost_and_found"; + int bFreelist = 1; /* 0 if --ignore-freelist is specified */ int bRowids = 1; /* 0 if --no-rowids */ + sqlite3_recover *p = 0; + int i = 0; + for(i=1; idb, &rc, - /* Attach an in-memory database named 'recovery'. Create an indexed - ** cache of the sqlite_dbptr virtual table. */ - "PRAGMA writable_schema = on;" - "ATTACH %Q AS recovery;" - "DROP TABLE IF EXISTS recovery.dbptr;" - "DROP TABLE IF EXISTS recovery.freelist;" - "DROP TABLE IF EXISTS recovery.map;" - "DROP TABLE IF EXISTS recovery.schema;" - "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb - ); - - if( bFreelist ){ - shellExec(pState->db, &rc, - "WITH trunk(pgno) AS (" - " SELECT shell_int32(" - " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x " - " WHERE x>0" - " UNION" - " SELECT shell_int32(" - " (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x " - " FROM trunk WHERE x>0" - ")," - "freelist(data, n, freepgno) AS (" - " SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno " - " FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno" - " UNION ALL" - " SELECT data, n-1, shell_int32(data, 2+n) " - " FROM freelist WHERE n>=0" - ")" - "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;" - ); - } - - /* If this is an auto-vacuum database, add all pointer-map pages to - ** the freelist table. Do this regardless of whether or not - ** --freelist-corrupt was specified. */ - shellExec(pState->db, &rc, - "WITH ptrmap(pgno) AS (" - " SELECT 2 WHERE shell_int32(" - " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13" - " )" - " UNION ALL " - " SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp " - " FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)" - ")" - "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap" + p = sqlite3_recover_init_sql( + pState->db, "main", recoverSqlCb, (void*)pState ); - shellExec(pState->db, &rc, - "CREATE TABLE recovery.dbptr(" - " pgno, child, PRIMARY KEY(child, pgno)" - ") WITHOUT ROWID;" - "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) " - " SELECT * FROM sqlite_dbptr" - " WHERE pgno NOT IN freelist AND child NOT IN freelist;" - - /* Delete any pointer to page 1. This ensures that page 1 is considered - ** a root page, regardless of how corrupt the db is. */ - "DELETE FROM recovery.dbptr WHERE child = 1;" - - /* Delete all pointers to any pages that have more than one pointer - ** to them. Such pages will be treated as root pages when recovering - ** data. */ - "DELETE FROM recovery.dbptr WHERE child IN (" - " SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1" - ");" - - /* Create the "map" table that will (eventually) contain instructions - ** for dealing with each page in the db that contains one or more - ** records. */ - "CREATE TABLE recovery.map(" - "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT" - ");" + sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ + sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); + sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); + sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); - /* Populate table [map]. If there are circular loops of pages in the - ** database, the following adds all pages in such a loop to the map - ** as individual root pages. This could be handled better. */ - "WITH pages(i, maxlen) AS (" - " SELECT page_count, (" - " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count" - " ) FROM pragma_page_count WHERE page_count>0" - " UNION ALL" - " SELECT i-1, (" - " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1" - " ) FROM pages WHERE i>=2" - ")" - "INSERT INTO recovery.map(pgno, maxlen, intkey, root) " - " SELECT i, maxlen, NULL, (" - " WITH p(orig, pgno, parent) AS (" - " SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)" - " UNION " - " SELECT i, p.parent, " - " (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p" - " )" - " SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)" - ") " - "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;" - "UPDATE recovery.map AS o SET intkey = (" - " SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno" - ");" - - /* Extract data from page 1 and any linked pages into table - ** recovery.schema. With the same schema as an sqlite_schema table. */ - "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" - "INSERT INTO recovery.schema SELECT " - " max(CASE WHEN field=0 THEN value ELSE NULL END)," - " max(CASE WHEN field=1 THEN value ELSE NULL END)," - " max(CASE WHEN field=2 THEN value ELSE NULL END)," - " max(CASE WHEN field=3 THEN value ELSE NULL END)," - " max(CASE WHEN field=4 THEN value ELSE NULL END)" - "FROM sqlite_dbdata WHERE pgno IN (" - " SELECT pgno FROM recovery.map WHERE root=1" - ")" - "GROUP BY pgno, cell;" - "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);" - ); - - /* Open a transaction, then print out all non-virtual, non-"sqlite_%" - ** CREATE TABLE statements that extracted from the existing schema. */ - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt = 0; - /* ".recover" might output content in an order which causes immediate - ** foreign key constraints to be violated. So disable foreign-key - ** constraint enforcement to prevent problems when running the output - ** script. */ - raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n"); - raw_printf(pState->out, "BEGIN;\n"); - raw_printf(pState->out, "PRAGMA writable_schema = on;\n"); - shellPrepare(pState->db, &rc, - "SELECT sql FROM recovery.schema " - "WHERE type='table' AND sql LIKE 'create table%'", &pStmt - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0); - raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n", - &zCreateTable[12] - ); - } - shellFinalize(&rc, pStmt); + sqlite3_recover_run(p); + if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ + const char *zErr = sqlite3_recover_errmsg(p); + int errCode = sqlite3_recover_errcode(p); + raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode); } - - /* Figure out if an orphan table will be required. And if so, how many - ** user columns it should contain */ - shellPrepare(pState->db, &rc, - "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1" - , &pLoop - ); - if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ - nOrphan = sqlite3_column_int(pLoop, 0); - } - shellFinalize(&rc, pLoop); - pLoop = 0; - - shellPrepare(pState->db, &rc, - "SELECT pgno FROM recovery.map WHERE root=?", &pPages - ); - - shellPrepare(pState->db, &rc, - "SELECT max(field), group_concat(shell_escape_crnl(quote" - "(case when (? AND field<0) then NULL else value end)" - "), ', ')" - ", min(field) " - "FROM sqlite_dbdata WHERE pgno = ? AND field != ?" - "GROUP BY cell", &pCells - ); - - /* Loop through each root page. */ - shellPrepare(pState->db, &rc, - "SELECT root, intkey, max(maxlen) FROM recovery.map" - " WHERE root>1 GROUP BY root, intkey ORDER BY root=(" - " SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'" - ")", &pLoop - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ - int iRoot = sqlite3_column_int(pLoop, 0); - int bIntkey = sqlite3_column_int(pLoop, 1); - int nCol = sqlite3_column_int(pLoop, 2); - int bNoop = 0; - RecoverTable *pTab; - - assert( bIntkey==0 || bIntkey==1 ); - pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop); - if( bNoop || rc ) continue; - if( pTab==0 ){ - if( pOrphan==0 ){ - pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan); - } - pTab = pOrphan; - if( pTab==0 ) break; - } - - if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){ - raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n"); - } - sqlite3_bind_int(pPages, 1, iRoot); - if( bRowids==0 && pTab->iPk<0 ){ - sqlite3_bind_int(pCells, 1, 1); - }else{ - sqlite3_bind_int(pCells, 1, 0); - } - sqlite3_bind_int(pCells, 3, pTab->iPk); - - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){ - int iPgno = sqlite3_column_int(pPages, 0); - sqlite3_bind_int(pCells, 2, iPgno); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){ - int nField = sqlite3_column_int(pCells, 0); - int iMin = sqlite3_column_int(pCells, 2); - const char *zVal = (const char*)sqlite3_column_text(pCells, 1); - - RecoverTable *pTab2 = pTab; - if( pTab!=pOrphan && (iMin<0)!=bIntkey ){ - if( pOrphan==0 ){ - pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan); - } - pTab2 = pOrphan; - if( pTab2==0 ) break; - } - - nField = nField+1; - if( pTab2==pOrphan ){ - raw_printf(pState->out, - "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n", - pTab2->zQuoted, iRoot, iPgno, nField, - iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField] - ); - }else{ - raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", - pTab2->zQuoted, pTab2->azlCol[nField], zVal - ); - } - } - shellReset(&rc, pCells); - } - shellReset(&rc, pPages); - if( pTab!=pOrphan ) recoverFreeTable(pTab); - } - shellFinalize(&rc, pLoop); - shellFinalize(&rc, pPages); - shellFinalize(&rc, pCells); - recoverFreeTable(pOrphan); - - /* The rest of the schema */ - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt = 0; - shellPrepare(pState->db, &rc, - "SELECT sql, name FROM recovery.schema " - "WHERE sql NOT LIKE 'create table%'", &pStmt - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zSql = (const char*)sqlite3_column_text(pStmt, 0); - if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){ - const char *zName = (const char*)sqlite3_column_text(pStmt, 1); - char *zPrint = shellMPrintf(&rc, - "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", - zName, zName, zSql - ); - raw_printf(pState->out, "%s;\n", zPrint); - sqlite3_free(zPrint); - }else{ - raw_printf(pState->out, "%s;\n", zSql); - } - } - shellFinalize(&rc, pStmt); - } - - if( rc==SQLITE_OK ){ - raw_printf(pState->out, "PRAGMA writable_schema = off;\n"); - raw_printf(pState->out, "COMMIT;\n"); - } - sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0); + rc = sqlite3_recover_finish(p); return rc; } -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +#endif /* SQLITE_SHELL_HAVE_RECOVER */ /* @@ -8168,7 +7633,7 @@ static int do_meta_command(char *zLine, ShellState *p){ clearTempFile(p); #ifndef SQLITE_OMIT_AUTHORIZATION - if( c=='a' && strncmp(azArg[0], "auth", n)==0 ){ + if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){ if( nArg!=2 ){ raw_printf(stderr, "Usage: .auth ON|OFF\n"); rc = 1; @@ -8186,17 +7651,17 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \ - && !defined(SQLITE_SHELL_WASM_MODE) - if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){ + && !defined(SQLITE_SHELL_FIDDLE) + if( c=='a' && cli_strncmp(azArg[0], "archive", n)==0 ){ open_db(p, 0); failIfSafeMode(p, "cannot run .archive in safe mode"); rc = arDotCommand(p, 0, azArg, nArg); }else #endif -#ifndef SQLITE_SHELL_WASM_MODE - if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) - || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) +#ifndef SQLITE_SHELL_FIDDLE + if( (c=='b' && n>=3 && cli_strncmp(azArg[0], "backup", n)==0) + || (c=='s' && n>=3 && cli_strncmp(azArg[0], "save", n)==0) ){ const char *zDestFile = 0; const char *zDb = 0; @@ -8210,10 +7675,10 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *z = azArg[j]; if( z[0]=='-' ){ if( z[1]=='-' ) z++; - if( strcmp(z, "-append")==0 ){ + if( cli_strcmp(z, "-append")==0 ){ zVfs = "apndvfs"; }else - if( strcmp(z, "-async")==0 ){ + if( cli_strcmp(z, "-async")==0 ){ bAsync = 1; }else { @@ -8263,9 +7728,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } close_db(pDest); }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){ + if( c=='b' && n>=3 && cli_strncmp(azArg[0], "bail", n)==0 ){ if( nArg==2 ){ bail_on_error = booleanValue(azArg[1]); }else{ @@ -8274,7 +7739,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){ + if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){ if( nArg==2 ){ if( booleanValue(azArg[1]) ){ setBinaryMode(p->out, 1); @@ -8290,12 +7755,12 @@ static int do_meta_command(char *zLine, ShellState *p){ /* The undocumented ".breakpoint" command causes a call to the no-op ** routine named test_breakpoint(). */ - if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ + if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){ test_breakpoint(); }else -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='c' && strcmp(azArg[0],"cd")==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){ failIfSafeMode(p, "cannot run .cd in safe mode"); if( nArg==2 ){ #if defined(_WIN32) || defined(WIN32) @@ -8314,9 +7779,9 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; } }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){ + if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){ if( nArg==2 ){ setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); }else{ @@ -8325,12 +7790,12 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE /* Cancel output redirection, if it is currently set (by .testcase) ** Then read the content of the testcase-out.txt file and compare against ** azArg[1]. If there are differences, report an error and exit. */ - if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){ + if( c=='c' && n>=3 && cli_strncmp(azArg[0], "check", n)==0 ){ char *zRes = 0; output_reset(p); if( nArg!=2 ){ @@ -8350,10 +7815,10 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_free(zRes); }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){ failIfSafeMode(p, "cannot run .clone in safe mode"); if( nArg==2 ){ tryToClone(p, azArg[1]); @@ -8362,9 +7827,9 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; } }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){ + if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){ if( nArg==1 ){ /* List available connections */ int i; @@ -8391,7 +7856,7 @@ static int do_meta_command(char *zLine, ShellState *p){ globalDb = p->db = p->pAuxDb->db; p->pAuxDb->db = 0; } - }else if( nArg==3 && strcmp(azArg[1], "close")==0 + }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0 && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){ int i = azArg[2][0] - '0'; if( i<0 || i>=ArraySize(p->aAuxDb) ){ @@ -8410,7 +7875,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){ + if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ char **azName = 0; int nName = 0; sqlite3_stmt *pStmt; @@ -8449,7 +7914,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(azName); }else - if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){ + if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbconfig", n)==0 ){ static const struct DbConfigChoices { const char *zName; int op; @@ -8474,7 +7939,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int ii, v; open_db(p, 0); for(ii=0; ii1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; + if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; if( nArg>=3 ){ sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); } @@ -8488,18 +7953,18 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) - if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ +#if SQLITE_SHELL_HAVE_RECOVER + if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){ rc = shell_dbinfo_command(p, nArg, azArg); }else - if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){ + if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){ open_db(p, 0); rc = recoverDatabaseCmd(p, nArg, azArg); }else -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +#endif /* SQLITE_SHELL_HAVE_RECOVER */ - if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ + if( c=='d' && cli_strncmp(azArg[0], "dump", n)==0 ){ char *zLike = 0; char *zSql; int i; @@ -8512,7 +7977,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( azArg[i][0]=='-' ){ const char *z = azArg[i]+1; if( z[0]=='-' ) z++; - if( strcmp(z,"preserve-rowids")==0 ){ + if( cli_strcmp(z,"preserve-rowids")==0 ){ #ifdef SQLITE_OMIT_VIRTUALTABLE raw_printf(stderr, "The --preserve-rowids option is not compatible" " with SQLITE_OMIT_VIRTUALTABLE\n"); @@ -8523,13 +7988,13 @@ static int do_meta_command(char *zLine, ShellState *p){ ShellSetFlag(p, SHFLG_PreserveRowid); #endif }else - if( strcmp(z,"newlines")==0 ){ + if( cli_strcmp(z,"newlines")==0 ){ ShellSetFlag(p, SHFLG_Newlines); }else - if( strcmp(z,"data-only")==0 ){ + if( cli_strcmp(z,"data-only")==0 ){ ShellSetFlag(p, SHFLG_DumpDataOnly); }else - if( strcmp(z,"nosys")==0 ){ + if( cli_strcmp(z,"nosys")==0 ){ ShellSetFlag(p, SHFLG_DumpNoSys); }else { @@ -8611,7 +8076,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->shellFlgs = savedShellFlags; }else - if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){ + if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){ if( nArg==2 ){ setOrClearFlag(p, SHFLG_Echo, azArg[1]); }else{ @@ -8620,22 +8085,22 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){ + if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ if( nArg==2 ){ p->autoEQPtest = 0; if( p->autoEQPtrace ){ if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); p->autoEQPtrace = 0; } - if( strcmp(azArg[1],"full")==0 ){ + if( cli_strcmp(azArg[1],"full")==0 ){ p->autoEQP = AUTOEQP_full; - }else if( strcmp(azArg[1],"trigger")==0 ){ + }else if( cli_strcmp(azArg[1],"trigger")==0 ){ p->autoEQP = AUTOEQP_trigger; #ifdef SQLITE_DEBUG - }else if( strcmp(azArg[1],"test")==0 ){ + }else if( cli_strcmp(azArg[1],"test")==0 ){ p->autoEQP = AUTOEQP_on; p->autoEQPtest = 1; - }else if( strcmp(azArg[1],"trace")==0 ){ + }else if( cli_strcmp(azArg[1],"trace")==0 ){ p->autoEQP = AUTOEQP_full; p->autoEQPtrace = 1; open_db(p, 0); @@ -8651,8 +8116,8 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); rc = 2; }else @@ -8660,10 +8125,10 @@ static int do_meta_command(char *zLine, ShellState *p){ /* The ".explain" command is automatic now. It is largely pointless. It ** retained purely for backwards compatibility */ - if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ + if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){ int val = 1; if( nArg>=2 ){ - if( strcmp(azArg[1],"auto")==0 ){ + if( cli_strcmp(azArg[1],"auto")==0 ){ val = 99; }else{ val = booleanValue(azArg[1]); @@ -8683,7 +8148,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #ifndef SQLITE_OMIT_VIRTUALTABLE - if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){ + if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ raw_printf(stderr, "Cannot run experimental commands such as \"%s\" in safe mode\n", @@ -8696,7 +8161,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif - if( c=='f' && strncmp(azArg[0], "filectrl", n)==0 ){ + if( c=='f' && cli_strncmp(azArg[0], "filectrl", n)==0 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ @@ -8726,7 +8191,7 @@ static int do_meta_command(char *zLine, ShellState *p){ zCmd = nArg>=2 ? azArg[1] : "help"; if( zCmd[0]=='-' - && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0) + && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0) && nArg>=4 ){ zSchema = azArg[2]; @@ -8742,7 +8207,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } /* --help lists all file-controls */ - if( strcmp(zCmd,"help")==0 ){ + if( cli_strcmp(zCmd,"help")==0 ){ utf8_printf(p->out, "Available file-controls:\n"); for(i=0; iout, " .filectrl %s %s\n", @@ -8756,7 +8221,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** of the option name, or a numerical value. */ n2 = strlen30(zCmd); for(i=0; ishowHeader = booleanValue(azArg[1]); p->shellFlgs |= SHFLG_HeaderSet; @@ -8900,7 +8365,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ + if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){ if( nArg>=2 ){ n = showHelp(p->out, azArg[1]); if( n==0 ){ @@ -8911,8 +8376,8 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='i' && strncmp(azArg[0], "import", n)==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ char *zTable = 0; /* Insert data into this table */ char *zSchema = 0; /* within this schema (may default to "main") */ char *zFile = 0; /* Name of file to extra content from */ @@ -8952,18 +8417,18 @@ static int do_meta_command(char *zLine, ShellState *p){ showHelp(p->out, "import"); goto meta_command_exit; } - }else if( strcmp(z,"-v")==0 ){ + }else if( cli_strcmp(z,"-v")==0 ){ eVerbose++; - }else if( strcmp(z,"-schema")==0 && imode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){ + if( nSep==2 && p->mode==MODE_Csv + && cli_strcmp(p->rowSeparator,SEP_CrLf)==0 + ){ /* When importing CSV (only), if the row separator is set to the ** default output row separator, change it to the default input ** row separator. This avoids having to maintain different input @@ -9202,10 +8669,10 @@ static int do_meta_command(char *zLine, ShellState *p){ sCtx.nRow, sCtx.nErr, sCtx.nLine-1); } }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ #ifndef SQLITE_UNTESTABLE - if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){ + if( c=='i' && cli_strncmp(azArg[0], "imposter", n)==0 ){ char *zSql; char *zCollist = 0; sqlite3_stmt *pStmt; @@ -9306,13 +8773,13 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ #ifdef SQLITE_ENABLE_IOTRACE - if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){ + if( c=='i' && cli_strncmp(azArg[0], "iotrace", n)==0 ){ SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); if( iotrace && iotrace!=stdout ) fclose(iotrace); iotrace = 0; if( nArg<2 ){ sqlite3IoTrace = 0; - }else if( strcmp(azArg[1], "-")==0 ){ + }else if( cli_strcmp(azArg[1], "-")==0 ){ sqlite3IoTrace = iotracePrintf; iotrace = stdout; }else{ @@ -9328,7 +8795,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif - if( c=='l' && n>=5 && strncmp(azArg[0], "limits", n)==0 ){ + if( c=='l' && n>=5 && cli_strncmp(azArg[0], "limits", n)==0 ){ static const struct { const char *zLimitName; /* Name of a limit */ int limitCode; /* Integer code for that limit */ @@ -9387,13 +8854,13 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){ + if( c=='l' && n>2 && cli_strncmp(azArg[0], "lint", n)==0 ){ open_db(p, 0); lintDotCommand(p, azArg, nArg); }else -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_WASM_MODE) - if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE) + if( c=='l' && cli_strncmp(azArg[0], "load", n)==0 ){ const char *zFile, *zProc; char *zErrMsg = 0; failIfSafeMode(p, "cannot run .load in safe mode"); @@ -9414,8 +8881,8 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='l' && strncmp(azArg[0], "log", n)==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){ failIfSafeMode(p, "cannot run .log in safe mode"); if( nArg!=2 ){ raw_printf(stderr, "Usage: .log FILENAME\n"); @@ -9428,7 +8895,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif - if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){ + if( c=='m' && cli_strncmp(azArg[0], "mode", n)==0 ){ const char *zMode = 0; const char *zTabname = 0; int i, n2; @@ -9447,10 +8914,10 @@ static int do_meta_command(char *zLine, ShellState *p){ cmOpts.bQuote = 0; }else if( zMode==0 ){ zMode = z; - /* Apply defaults for qbox pseudo-mods. If that + /* Apply defaults for qbox pseudo-mode. If that * overwrites already-set values, user was informed of this. */ - if( strcmp(z, "qbox")==0 ){ + if( cli_strcmp(z, "qbox")==0 ){ ColModeOpts cmo = ColModeOpts_default_qbox; zMode = "box"; cmOpts = cmo; @@ -9489,58 +8956,58 @@ static int do_meta_command(char *zLine, ShellState *p){ zMode = modeDescr[p->mode]; } n2 = strlen30(zMode); - if( strncmp(zMode,"lines",n2)==0 ){ + if( cli_strncmp(zMode,"lines",n2)==0 ){ p->mode = MODE_Line; sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"columns",n2)==0 ){ + }else if( cli_strncmp(zMode,"columns",n2)==0 ){ p->mode = MODE_Column; if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ p->showHeader = 1; } sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); p->cmOpts = cmOpts; - }else if( strncmp(zMode,"list",n2)==0 ){ + }else if( cli_strncmp(zMode,"list",n2)==0 ){ p->mode = MODE_List; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"html",n2)==0 ){ + }else if( cli_strncmp(zMode,"html",n2)==0 ){ p->mode = MODE_Html; - }else if( strncmp(zMode,"tcl",n2)==0 ){ + }else if( cli_strncmp(zMode,"tcl",n2)==0 ){ p->mode = MODE_Tcl; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"csv",n2)==0 ){ + }else if( cli_strncmp(zMode,"csv",n2)==0 ){ p->mode = MODE_Csv; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); - }else if( strncmp(zMode,"tabs",n2)==0 ){ + }else if( cli_strncmp(zMode,"tabs",n2)==0 ){ p->mode = MODE_List; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); - }else if( strncmp(zMode,"insert",n2)==0 ){ + }else if( cli_strncmp(zMode,"insert",n2)==0 ){ p->mode = MODE_Insert; set_table_name(p, zTabname ? zTabname : "table"); - }else if( strncmp(zMode,"quote",n2)==0 ){ + }else if( cli_strncmp(zMode,"quote",n2)==0 ){ p->mode = MODE_Quote; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"ascii",n2)==0 ){ + }else if( cli_strncmp(zMode,"ascii",n2)==0 ){ p->mode = MODE_Ascii; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); - }else if( strncmp(zMode,"markdown",n2)==0 ){ + }else if( cli_strncmp(zMode,"markdown",n2)==0 ){ p->mode = MODE_Markdown; p->cmOpts = cmOpts; - }else if( strncmp(zMode,"table",n2)==0 ){ + }else if( cli_strncmp(zMode,"table",n2)==0 ){ p->mode = MODE_Table; p->cmOpts = cmOpts; - }else if( strncmp(zMode,"box",n2)==0 ){ + }else if( cli_strncmp(zMode,"box",n2)==0 ){ p->mode = MODE_Box; p->cmOpts = cmOpts; - }else if( strncmp(zMode,"count",n2)==0 ){ + }else if( cli_strncmp(zMode,"count",n2)==0 ){ p->mode = MODE_Count; - }else if( strncmp(zMode,"off",n2)==0 ){ + }else if( cli_strncmp(zMode,"off",n2)==0 ){ p->mode = MODE_Off; - }else if( strncmp(zMode,"json",n2)==0 ){ + }else if( cli_strncmp(zMode,"json",n2)==0 ){ p->mode = MODE_Json; }else{ raw_printf(stderr, "Error: mode should be one of: " @@ -9551,12 +9018,12 @@ static int do_meta_command(char *zLine, ShellState *p){ p->cMode = p->mode; }else -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='n' && strcmp(azArg[0], "nonce")==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='n' && cli_strcmp(azArg[0], "nonce")==0 ){ if( nArg!=2 ){ raw_printf(stderr, "Usage: .nonce NONCE\n"); rc = 1; - }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){ + }else if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){ raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", p->lineno, azArg[1]); exit(1); @@ -9566,9 +9033,9 @@ static int do_meta_command(char *zLine, ShellState *p){ ** at the end of this procedure */ } }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ + if( c=='n' && cli_strncmp(azArg[0], "nullvalue", n)==0 ){ if( nArg==2 ){ sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); @@ -9578,7 +9045,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ + if( c=='o' && cli_strncmp(azArg[0], "open", n)==0 && n>=2 ){ const char *zFN = 0; /* Pointer to constant filename */ char *zNewFilename = 0; /* Name of the database file to open */ int iName = 1; /* Index in azArg[] of the filename */ @@ -9588,7 +9055,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* Check for command-line arguments */ for(iName=1; iNameszMax = integerValue(azArg[++iName]); #endif /* SQLITE_OMIT_DESERIALIZE */ }else -#endif /* !SQLITE_SHELL_WASM_MODE */ +#endif /* !SQLITE_SHELL_FIDDLE */ if( z[0]=='-' ){ utf8_printf(stderr, "unknown option: %s\n", z); rc = 1; @@ -9638,11 +9105,11 @@ static int do_meta_command(char *zLine, ShellState *p){ /* If a filename is specified, try to open it first */ if( zFN || p->openMode==SHELL_OPEN_HEXDB ){ if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN); -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE if( p->bSafeMode && p->openMode!=SHELL_OPEN_HEXDB && zFN - && strcmp(zFN,":memory:")!=0 + && cli_strcmp(zFN,":memory:")!=0 ){ failIfSafeMode(p, "cannot open disk-based database files in safe mode"); } @@ -9671,10 +9138,11 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE if( (c=='o' - && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0)) - || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0) + && (cli_strncmp(azArg[0], "output", n)==0 + || cli_strncmp(azArg[0], "once", n)==0)) + || (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0) ){ char *zFile = 0; int bTxtMode = 0; @@ -9688,21 +9156,21 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='e' ){ eMode = 'x'; bOnce = 2; - }else if( strncmp(azArg[0],"once",n)==0 ){ + }else if( cli_strncmp(azArg[0],"once",n)==0 ){ bOnce = 1; } for(i=1; iout, "ERROR: unknown option: \"%s\". Usage:\n", @@ -9775,7 +9243,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ p->out = output_file_open(zFile, bTxtMode); if( p->out==0 ){ - if( strcmp(zFile,"off")!=0 ){ + if( cli_strcmp(zFile,"off")!=0 ){ utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); } p->out = stdout; @@ -9787,16 +9255,16 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_free(zFile); }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){ + if( c=='p' && n>=3 && cli_strncmp(azArg[0], "parameter", n)==0 ){ open_db(p,0); if( nArg<=1 ) goto parameter_syntax_error; /* .parameter clear ** Clear all bind parameters by dropping the TEMP table that holds them. */ - if( nArg==2 && strcmp(azArg[1],"clear")==0 ){ + if( nArg==2 && cli_strcmp(azArg[1],"clear")==0 ){ sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;", 0, 0, 0); }else @@ -9804,7 +9272,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* .parameter list ** List all bind parameters. */ - if( nArg==2 && strcmp(azArg[1],"list")==0 ){ + if( nArg==2 && cli_strcmp(azArg[1],"list")==0 ){ sqlite3_stmt *pStmt = 0; int rx; int len = 0; @@ -9833,7 +9301,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** Make sure the TEMP table used to hold bind parameters exists. ** Create it if necessary. */ - if( nArg==2 && strcmp(azArg[1],"init")==0 ){ + if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){ bind_table_init(p); }else @@ -9843,7 +9311,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** VALUE can be in either SQL literal notation, or if not it will be ** understood to be a text string. */ - if( nArg==4 && strcmp(azArg[1],"set")==0 ){ + if( nArg==4 && cli_strcmp(azArg[1],"set")==0 ){ int rx; char *zSql; sqlite3_stmt *pStmt; @@ -9881,7 +9349,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** Remove the NAME binding from the parameter binding table, if it ** exists. */ - if( nArg==3 && strcmp(azArg[1],"unset")==0 ){ + if( nArg==3 && cli_strcmp(azArg[1],"unset")==0 ){ char *zSql = sqlite3_mprintf( "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]); shell_check_oom(zSql); @@ -9893,7 +9361,7 @@ static int do_meta_command(char *zLine, ShellState *p){ showHelp(p->out, "parameter"); }else - if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){ + if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){ int i; for(i=1; i1 ) raw_printf(p->out, " "); @@ -9903,7 +9371,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #ifndef SQLITE_OMIT_PROGRESS_CALLBACK - if( c=='p' && n>=3 && strncmp(azArg[0], "progress", n)==0 ){ + if( c=='p' && n>=3 && cli_strncmp(azArg[0], "progress", n)==0 ){ int i; int nn = 0; p->flgProgress = 0; @@ -9914,19 +9382,19 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ){ z++; if( z[0]=='-' ) z++; - if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){ + if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){ p->flgProgress |= SHELL_PROGRESS_QUIET; continue; } - if( strcmp(z,"reset")==0 ){ + if( cli_strcmp(z,"reset")==0 ){ p->flgProgress |= SHELL_PROGRESS_RESET; continue; } - if( strcmp(z,"once")==0 ){ + if( cli_strcmp(z,"once")==0 ){ p->flgProgress |= SHELL_PROGRESS_ONCE; continue; } - if( strcmp(z,"limit")==0 ){ + if( cli_strcmp(z,"limit")==0 ){ if( i+1>=nArg ){ utf8_printf(stderr, "Error: missing argument on --limit\n"); rc = 1; @@ -9948,7 +9416,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ - if( c=='p' && strncmp(azArg[0], "prompt", n)==0 ){ + if( c=='p' && cli_strncmp(azArg[0], "prompt", n)==0 ){ if( nArg >= 2) { strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); } @@ -9957,14 +9425,14 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='q' && cli_strncmp(azArg[0], "quit", n)==0 ){ rc = 2; }else #endif -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='r' && n>=3 && cli_strncmp(azArg[0], "read", n)==0 ){ FILE *inSaved = p->in; int savedLineno = p->lineno; failIfSafeMode(p, "cannot run .read in safe mode"); @@ -9998,10 +9466,10 @@ static int do_meta_command(char *zLine, ShellState *p){ p->in = inSaved; p->lineno = savedLineno; }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ -#ifndef SQLITE_SHELL_WASM_MODE - if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){ +#ifndef SQLITE_SHELL_FIDDLE + if( c=='r' && n>=3 && cli_strncmp(azArg[0], "restore", n)==0 ){ const char *zSrcFile; const char *zDb; sqlite3 *pSrc; @@ -10052,9 +9520,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } close_db(pSrc); }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ + if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ p->scanstatsOn = (u8)booleanValue(azArg[1]); #ifndef SQLITE_ENABLE_STMT_SCANSTATUS @@ -10066,7 +9534,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){ + if( c=='s' && cli_strncmp(azArg[0], "schema", n)==0 ){ ShellText sSelect; ShellState data; char *zErrMsg = 0; @@ -10209,15 +9677,15 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( (c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0) - || (c=='t' && n==9 && strncmp(azArg[0], "treetrace", n)==0) + if( (c=='s' && n==11 && cli_strncmp(azArg[0], "selecttrace", n)==0) + || (c=='t' && n==9 && cli_strncmp(azArg[0], "treetrace", n)==0) ){ unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); }else #if defined(SQLITE_ENABLE_SESSION) - if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){ + if( c=='s' && cli_strncmp(azArg[0],"session",n)==0 && n>=3 ){ struct AuxDb *pAuxDb = p->pAuxDb; OpenSession *pSession = &pAuxDb->aSession[0]; char **azCmd = &azArg[1]; @@ -10228,7 +9696,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); if( nArg>=3 ){ for(iSes=0; iSesnSession; iSes++){ - if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; + if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; } if( iSesnSession ){ pSession = &pAuxDb->aSession[iSes]; @@ -10244,7 +9712,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** Invoke the sqlite3session_attach() interface to attach a particular ** table so that it is never filtered. */ - if( strcmp(azCmd[0],"attach")==0 ){ + if( cli_strcmp(azCmd[0],"attach")==0 ){ if( nCmd!=2 ) goto session_syntax_error; if( pSession->p==0 ){ session_not_open: @@ -10262,7 +9730,9 @@ static int do_meta_command(char *zLine, ShellState *p){ ** .session patchset FILE ** Write a changeset or patchset into a file. The file is overwritten. */ - if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){ + if( cli_strcmp(azCmd[0],"changeset")==0 + || cli_strcmp(azCmd[0],"patchset")==0 + ){ FILE *out = 0; failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]); if( nCmd!=2 ) goto session_syntax_error; @@ -10296,7 +9766,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* .session close ** Close the identified session */ - if( strcmp(azCmd[0], "close")==0 ){ + if( cli_strcmp(azCmd[0], "close")==0 ){ if( nCmd!=1 ) goto session_syntax_error; if( pAuxDb->nSession ){ session_close(pSession); @@ -10307,7 +9777,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* .session enable ?BOOLEAN? ** Query or set the enable flag */ - if( strcmp(azCmd[0], "enable")==0 ){ + if( cli_strcmp(azCmd[0], "enable")==0 ){ int ii; if( nCmd>2 ) goto session_syntax_error; ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); @@ -10321,7 +9791,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* .session filter GLOB .... ** Set a list of GLOB patterns of table names to be excluded. */ - if( strcmp(azCmd[0], "filter")==0 ){ + if( cli_strcmp(azCmd[0], "filter")==0 ){ int ii, nByte; if( nCmd<2 ) goto session_syntax_error; if( pAuxDb->nSession ){ @@ -10346,7 +9816,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* .session indirect ?BOOLEAN? ** Query or set the indirect flag */ - if( strcmp(azCmd[0], "indirect")==0 ){ + if( cli_strcmp(azCmd[0], "indirect")==0 ){ int ii; if( nCmd>2 ) goto session_syntax_error; ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); @@ -10360,7 +9830,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* .session isempty ** Determine if the session is empty */ - if( strcmp(azCmd[0], "isempty")==0 ){ + if( cli_strcmp(azCmd[0], "isempty")==0 ){ int ii; if( nCmd!=1 ) goto session_syntax_error; if( pAuxDb->nSession ){ @@ -10373,7 +9843,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* .session list ** List all currently open sessions */ - if( strcmp(azCmd[0],"list")==0 ){ + if( cli_strcmp(azCmd[0],"list")==0 ){ for(i=0; inSession; i++){ utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); } @@ -10383,13 +9853,13 @@ static int do_meta_command(char *zLine, ShellState *p){ ** Open a new session called NAME on the attached database DB. ** DB is normally "main". */ - if( strcmp(azCmd[0],"open")==0 ){ + if( cli_strcmp(azCmd[0],"open")==0 ){ char *zName; if( nCmd!=3 ) goto session_syntax_error; zName = azCmd[2]; if( zName[0]==0 ) goto session_syntax_error; for(i=0; inSession; i++){ - if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ + if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ utf8_printf(stderr, "Session \"%s\" already exists\n", zName); goto meta_command_exit; } @@ -10420,15 +9890,15 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifdef SQLITE_DEBUG /* Undocumented commands for internal testing. Subject to change ** without notice. */ - if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){ - if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){ + if( c=='s' && n>=10 && cli_strncmp(azArg[0], "selftest-", 9)==0 ){ + if( cli_strncmp(azArg[0]+9, "boolean", n-9)==0 ){ int i, v; for(i=1; iout, "%s: %d 0x%x\n", azArg[i], v, v); } } - if( strncmp(azArg[0]+9, "integer", n-9)==0 ){ + if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){ int i; sqlite3_int64 v; for(i=1; i=4 && strncmp(azArg[0],"selftest",n)==0 ){ + if( c=='s' && n>=4 && cli_strncmp(azArg[0],"selftest",n)==0 ){ int bIsInit = 0; /* True to initialize the SELFTEST table */ int bVerbose = 0; /* Verbose output */ int bSelftestExists; /* True if SELFTEST already exists */ @@ -10454,10 +9924,10 @@ static int do_meta_command(char *zLine, ShellState *p){ for(i=1; i0 ){ printf("%d: %s %s\n", tno, zOp, zSql); } - if( strcmp(zOp,"memo")==0 ){ + if( cli_strcmp(zOp,"memo")==0 ){ utf8_printf(p->out, "%s\n", zSql); }else - if( strcmp(zOp,"run")==0 ){ + if( cli_strcmp(zOp,"run")==0 ){ char *zErrMsg = 0; str.n = 0; str.z[0] = 0; @@ -10527,7 +9997,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg); sqlite3_free(zErrMsg); - }else if( strcmp(zAns,str.z)!=0 ){ + }else if( cli_strcmp(zAns,str.z)!=0 ){ nErr++; rc = 1; utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); @@ -10547,7 +10017,7 @@ static int do_meta_command(char *zLine, ShellState *p){ utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); }else - if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){ + if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){ if( nArg<2 || nArg>3 ){ raw_printf(stderr, "Usage: .separator COL ?ROW?\n"); rc = 1; @@ -10562,7 +10032,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='s' && n>=4 && strncmp(azArg[0],"sha3sum",n)==0 ){ + if( c=='s' && n>=4 && cli_strncmp(azArg[0],"sha3sum",n)==0 ){ const char *zLike = 0; /* Which table to checksum. 0 means everything */ int i; /* Loop counter */ int bSchema = 0; /* Also hash the schema */ @@ -10580,15 +10050,15 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ){ z++; if( z[0]=='-' ) z++; - if( strcmp(z,"schema")==0 ){ + if( cli_strcmp(z,"schema")==0 ){ bSchema = 1; }else - if( strcmp(z,"sha3-224")==0 || strcmp(z,"sha3-256")==0 - || strcmp(z,"sha3-384")==0 || strcmp(z,"sha3-512")==0 + if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0 + || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0 ){ iSize = atoi(&z[5]); }else - if( strcmp(z,"debug")==0 ){ + if( cli_strcmp(z,"debug")==0 ){ bDebug = 1; }else { @@ -10628,20 +10098,20 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *zTab = (const char*)sqlite3_column_text(pStmt,0); if( zTab==0 ) continue; if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue; - if( strncmp(zTab, "sqlite_",7)!=0 ){ + if( cli_strncmp(zTab, "sqlite_",7)!=0 ){ appendText(&sQuery,"SELECT * FROM ", 0); appendText(&sQuery,zTab,'"'); appendText(&sQuery," NOT INDEXED;", 0); - }else if( strcmp(zTab, "sqlite_schema")==0 ){ + }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){ appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema" " ORDER BY name;", 0); - }else if( strcmp(zTab, "sqlite_sequence")==0 ){ + }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){ appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence" " ORDER BY name;", 0); - }else if( strcmp(zTab, "sqlite_stat1")==0 ){ + }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){ appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1" " ORDER BY tbl,idx;", 0); - }else if( strcmp(zTab, "sqlite_stat4")==0 ){ + }else if( cli_strcmp(zTab, "sqlite_stat4")==0 ){ appendText(&sQuery, "SELECT * FROM ", 0); appendText(&sQuery, zTab, 0); appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0); @@ -10678,9 +10148,10 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(zSql); }else -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) if( c=='s' - && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0) + && (cli_strncmp(azArg[0], "shell", n)==0 + || cli_strncmp(azArg[0],"system",n)==0) ){ char *zCmd; int i, x; @@ -10699,9 +10170,9 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(zCmd); if( x ) raw_printf(stderr, "System command returns %d\n", x); }else -#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */ - if( c=='s' && strncmp(azArg[0], "show", n)==0 ){ + if( c=='s' && cli_strncmp(azArg[0], "show", n)==0 ){ static const char *azBool[] = { "off", "on", "trigger", "full"}; const char *zOut; int i; @@ -10754,11 +10225,11 @@ static int do_meta_command(char *zLine, ShellState *p){ p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); }else - if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ + if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){ if( nArg==2 ){ - if( strcmp(azArg[1],"stmt")==0 ){ + if( cli_strcmp(azArg[1],"stmt")==0 ){ p->statsOn = 2; - }else if( strcmp(azArg[1],"vmstep")==0 ){ + }else if( cli_strcmp(azArg[1],"vmstep")==0 ){ p->statsOn = 3; }else{ p->statsOn = (u8)booleanValue(azArg[1]); @@ -10771,9 +10242,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0) - || (c=='i' && (strncmp(azArg[0], "indices", n)==0 - || strncmp(azArg[0], "indexes", n)==0) ) + if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0) + || (c=='i' && (cli_strncmp(azArg[0], "indices", n)==0 + || cli_strncmp(azArg[0], "indexes", n)==0) ) ){ sqlite3_stmt *pStmt; char **azResult; @@ -10879,9 +10350,9 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(azResult); }else -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE /* Begin redirecting output to the file "testcase-out.txt" */ - if( c=='t' && strcmp(azArg[0],"testcase")==0 ){ + if( c=='t' && cli_strcmp(azArg[0],"testcase")==0 ){ output_reset(p); p->out = output_file_open("testcase-out.txt", 0); if( p->out==0 ){ @@ -10893,10 +10364,10 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); } }else -#endif /* !defined(SQLITE_SHELL_WASM_MODE) */ +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ #ifndef SQLITE_UNTESTABLE - if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){ + if( c=='t' && n>=8 && cli_strncmp(azArg[0], "testctrl", n)==0 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ @@ -10943,7 +10414,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } /* --help lists all test-controls */ - if( strcmp(zCmd,"help")==0 ){ + if( cli_strcmp(zCmd,"help")==0 ){ utf8_printf(p->out, "Available test-controls:\n"); for(i=0; iout, " .testctrl %s %s\n", @@ -10957,7 +10428,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** of the option name, or a numerical value. */ n2 = strlen30(zCmd); for(i=0; i4 && strncmp(azArg[0], "timeout", n)==0 ){ + if( c=='t' && n>4 && cli_strncmp(azArg[0], "timeout", n)==0 ){ open_db(p, 0); sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0); }else - if( c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 ){ + if( c=='t' && n>=5 && cli_strncmp(azArg[0], "timer", n)==0 ){ if( nArg==2 ){ enableTimer = booleanValue(azArg[1]); if( enableTimer && !HAS_TIMER ){ @@ -11148,7 +10619,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #ifndef SQLITE_OMIT_TRACE - if( c=='t' && strncmp(azArg[0], "trace", n)==0 ){ + if( c=='t' && cli_strncmp(azArg[0], "trace", n)==0 ){ int mType = 0; int jj; open_db(p, 0); @@ -11198,7 +10669,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif /* !defined(SQLITE_OMIT_TRACE) */ #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) - if( c=='u' && strncmp(azArg[0], "unmodule", n)==0 ){ + if( c=='u' && cli_strncmp(azArg[0], "unmodule", n)==0 ){ int ii; int lenOpt; char *zOpt; @@ -11211,7 +10682,7 @@ static int do_meta_command(char *zLine, ShellState *p){ zOpt = azArg[1]; if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++; lenOpt = (int)strlen(zOpt); - if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){ + if( lenOpt>=3 && cli_strncmp(zOpt, "-allexcept",lenOpt)==0 ){ assert( azArg[nArg]==0 ); sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0); }else{ @@ -11223,14 +10694,14 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif #if SQLITE_USER_AUTHENTICATION - if( c=='u' && strncmp(azArg[0], "user", n)==0 ){ + if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){ if( nArg<2 ){ raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n"); rc = 1; goto meta_command_exit; } open_db(p, 0); - if( strcmp(azArg[1],"login")==0 ){ + if( cli_strcmp(azArg[1],"login")==0 ){ if( nArg!=4 ){ raw_printf(stderr, "Usage: .user login USER PASSWORD\n"); rc = 1; @@ -11242,7 +10713,7 @@ static int do_meta_command(char *zLine, ShellState *p){ utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]); rc = 1; } - }else if( strcmp(azArg[1],"add")==0 ){ + }else if( cli_strcmp(azArg[1],"add")==0 ){ if( nArg!=5 ){ raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n"); rc = 1; @@ -11254,7 +10725,7 @@ static int do_meta_command(char *zLine, ShellState *p){ raw_printf(stderr, "User-Add failed: %d\n", rc); rc = 1; } - }else if( strcmp(azArg[1],"edit")==0 ){ + }else if( cli_strcmp(azArg[1],"edit")==0 ){ if( nArg!=5 ){ raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n"); rc = 1; @@ -11266,7 +10737,7 @@ static int do_meta_command(char *zLine, ShellState *p){ raw_printf(stderr, "User-Edit failed: %d\n", rc); rc = 1; } - }else if( strcmp(azArg[1],"delete")==0 ){ + }else if( cli_strcmp(azArg[1],"delete")==0 ){ if( nArg!=3 ){ raw_printf(stderr, "Usage: .user delete USER\n"); rc = 1; @@ -11285,7 +10756,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif /* SQLITE_USER_AUTHENTICATION */ - if( c=='v' && strncmp(azArg[0], "version", n)==0 ){ + if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB @@ -11304,7 +10775,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif }else - if( c=='v' && strncmp(azArg[0], "vfsinfo", n)==0 ){ + if( c=='v' && cli_strncmp(azArg[0], "vfsinfo", n)==0 ){ const char *zDbName = nArg==2 ? azArg[1] : "main"; sqlite3_vfs *pVfs = 0; if( p->db ){ @@ -11318,7 +10789,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='v' && strncmp(azArg[0], "vfslist", n)==0 ){ + if( c=='v' && cli_strncmp(azArg[0], "vfslist", n)==0 ){ sqlite3_vfs *pVfs; sqlite3_vfs *pCurrent = 0; if( p->db ){ @@ -11336,7 +10807,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){ + if( c=='v' && cli_strncmp(azArg[0], "vfsname", n)==0 ){ const char *zDbName = nArg==2 ? azArg[1] : "main"; char *zVfsName = 0; if( p->db ){ @@ -11348,12 +10819,12 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){ + if( c=='w' && cli_strncmp(azArg[0], "wheretrace", n)==0 ){ unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); }else - if( c=='w' && strncmp(azArg[0], "width", n)==0 ){ + if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){ int j; assert( nArg<=ArraySize(azArg) ); p->nWidth = nArg-1; @@ -11531,10 +11002,10 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ if( zErrMsg==0 ){ zErrorType = "Error"; zErrorTail = sqlite3_errmsg(p->db); - }else if( strncmp(zErrMsg, "in prepare, ",12)==0 ){ + }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){ zErrorType = "Parse error"; zErrorTail = &zErrMsg[12]; - }else if( strncmp(zErrMsg, "stepping, ", 10)==0 ){ + }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){ zErrorType = "Runtime error"; zErrorTail = &zErrMsg[10]; }else{ @@ -11565,7 +11036,7 @@ static void echo_group_input(ShellState *p, const char *zDo){ if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); } -#ifdef SQLITE_SHELL_WASM_MODE +#ifdef SQLITE_SHELL_FIDDLE /* ** Alternate one_input_line() impl for wasm mode. This is not in the primary impl ** because we need the global shellState and cannot access it from that function @@ -11576,7 +11047,7 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ const char *zBegin = shellState.wasm.zPos; const char *z = zBegin; char *zLine = 0; - int nZ = 0; + i64 nZ = 0; UNUSED_PARAMETER(in); UNUSED_PARAMETER(isContinuation); @@ -11592,11 +11063,11 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ shellState.wasm.zPos = z; zLine = realloc(zPrior, nZ+1); shell_check_oom(zLine); - memcpy(zLine, zBegin, (size_t)nZ); + memcpy(zLine, zBegin, nZ); zLine[nZ] = 0; return zLine; } -#endif /* SQLITE_SHELL_WASM_MODE */ +#endif /* SQLITE_SHELL_FIDDLE */ /* ** Read input from *in and process it. If *in==0 then input @@ -11610,12 +11081,12 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ static int process_input(ShellState *p){ char *zLine = 0; /* A single input line */ char *zSql = 0; /* Accumulated SQL text */ - int nLine; /* Length of current line */ - int nSql = 0; /* Bytes of zSql[] used */ - int nAlloc = 0; /* Allocated zSql[] space */ + i64 nLine; /* Length of current line */ + i64 nSql = 0; /* Bytes of zSql[] used */ + i64 nAlloc = 0; /* Allocated zSql[] space */ int rc; /* Error code */ int errCnt = 0; /* Number of errors seen */ - int startline = 0; /* Line number for start of current input */ + i64 startline = 0; /* Line number for start of current input */ QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ if( p->inputNesting==MAX_INPUT_NESTING ){ @@ -11665,7 +11136,7 @@ static int process_input(ShellState *p){ continue; } /* No single-line dispositions remain; accumulate line(s). */ - nLine = strlen30(zLine); + nLine = strlen(zLine); if( nSql+nLine+2>=nAlloc ){ /* Grow buffer by half-again increments when big. */ nAlloc = nSql+(nSql>>1)+nLine+100; @@ -11673,7 +11144,7 @@ static int process_input(ShellState *p){ shell_check_oom(zSql); } if( nSql==0 ){ - int i; + i64 i; for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} assert( nAlloc>0 && zSql!=0 ); memcpy(zSql, zLine+i, nLine+1-i); @@ -11773,7 +11244,7 @@ static char *find_home_dir(int clearFlag){ #endif /* !_WIN32_WCE */ if( home_dir ){ - int n = strlen30(home_dir) + 1; + i64 n = strlen(home_dir) + 1; char *z = malloc( n ); if( z ) memcpy(z, home_dir, n); home_dir = z; @@ -11979,7 +11450,7 @@ static char *cmdline_option_value(int argc, char **argv, int i){ # endif #endif -#ifdef SQLITE_SHELL_WASM_MODE +#ifdef SQLITE_SHELL_FIDDLE # define main fiddle_main #endif @@ -11993,7 +11464,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ sqlite3_int64 mem_main_enter = sqlite3_memory_used(); #endif char *zErrMsg = 0; -#ifdef SQLITE_SHELL_WASM_MODE +#ifdef SQLITE_SHELL_FIDDLE # define data shellState #else ShellState data; @@ -12013,9 +11484,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ setBinaryMode(stdin, 0); setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ -#ifdef SQLITE_SHELL_WASM_MODE +#ifdef SQLITE_SHELL_FIDDLE stdin_is_interactive = 0; stdout_is_console = 1; + data.wasm.zDefaultDbName = "/fiddle.sqlite3"; #else stdin_is_interactive = isatty(0); stdout_is_console = isatty(1); @@ -12043,7 +11515,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif #if USE_SYSTEM_SQLITE+0!=1 - if( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ + if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); @@ -12065,9 +11537,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ argv = argvToFree + argc; for(i=0; i70000 ) sz = 70000; @@ -12162,7 +11634,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ sqlite3_config(SQLITE_CONFIG_PAGECACHE, (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n); data.shellFlgs |= SHFLG_Pagecache; - }else if( strcmp(z,"-lookaside")==0 ){ + }else if( cli_strcmp(z,"-lookaside")==0 ){ int n, sz; sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( sz<0 ) sz = 0; @@ -12170,7 +11642,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( n<0 ) n = 0; sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; - }else if( strcmp(z,"-threadsafe")==0 ){ + }else if( cli_strcmp(z,"-threadsafe")==0 ){ int n; n = (int)integerValue(cmdline_option_value(argc,argv,++i)); switch( n ){ @@ -12179,7 +11651,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; } #ifdef SQLITE_ENABLE_VFSTRACE - }else if( strcmp(z,"-vfstrace")==0 ){ + }else if( cli_strcmp(z,"-vfstrace")==0 ){ extern int vfstrace_register( const char *zTraceName, const char *zOldVfsName, @@ -12190,50 +11662,50 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); #endif #ifdef SQLITE_ENABLE_MULTIPLEX - }else if( strcmp(z,"-multiplex")==0 ){ + }else if( cli_strcmp(z,"-multiplex")==0 ){ extern int sqlite3_multiple_initialize(const char*,int); sqlite3_multiplex_initialize(0, 1); #endif - }else if( strcmp(z,"-mmap")==0 ){ + }else if( cli_strcmp(z,"-mmap")==0 ){ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz); #ifdef SQLITE_ENABLE_SORTER_REFERENCES - }else if( strcmp(z,"-sorterref")==0 ){ + }else if( cli_strcmp(z,"-sorterref")==0 ){ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz); #endif - }else if( strcmp(z,"-vfs")==0 ){ + }else if( cli_strcmp(z,"-vfs")==0 ){ zVfs = cmdline_option_value(argc, argv, ++i); #ifdef SQLITE_HAVE_ZLIB - }else if( strcmp(z,"-zip")==0 ){ + }else if( cli_strcmp(z,"-zip")==0 ){ data.openMode = SHELL_OPEN_ZIPFILE; #endif - }else if( strcmp(z,"-append")==0 ){ + }else if( cli_strcmp(z,"-append")==0 ){ data.openMode = SHELL_OPEN_APPENDVFS; #ifndef SQLITE_OMIT_DESERIALIZE - }else if( strcmp(z,"-deserialize")==0 ){ + }else if( cli_strcmp(z,"-deserialize")==0 ){ data.openMode = SHELL_OPEN_DESERIALIZE; - }else if( strcmp(z,"-maxsize")==0 && i+10 ){ utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" " with \"%s\"\n", z); @@ -12461,7 +11933,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ readStdin = 0; break; #endif - }else if( strcmp(z,"-safe")==0 ){ + }else if( cli_strcmp(z,"-safe")==0 ){ data.bSafeMode = data.bSafeModePersist = 1; }else{ utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); @@ -12543,7 +12015,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ rc = process_input(&data); } } -#ifndef SQLITE_SHELL_WASM_MODE +#ifndef SQLITE_SHELL_FIDDLE /* In WASM mode we have to leave the db state in place so that ** client code can "push" SQL into it after this call returns. */ free(azCmd); @@ -12578,38 +12050,38 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ (unsigned int)(sqlite3_memory_used()-mem_main_enter)); } #endif -#endif /* !SQLITE_SHELL_WASM_MODE */ +#endif /* !SQLITE_SHELL_FIDDLE */ return rc; } -#ifdef SQLITE_SHELL_WASM_MODE +#ifdef SQLITE_SHELL_FIDDLE /* Only for emcc experimentation purposes. */ int fiddle_experiment(int a,int b){ - return a + b; + return a + b; } -/* Only for emcc experimentation purposes. - - Define this function in JS using: - - emcc ... --js-library somefile.js - - containing: +/* +** Returns a pointer to the current DB handle. +*/ +sqlite3 * fiddle_db_handle(){ + return globalDb; +} -mergeInto(LibraryManager.library, { - my_foo: function(){ - console.debug("my_foo()",arguments); - } -}); +/* +** Returns a pointer to the given DB name's VFS. If zDbName is 0 then +** "main" is assumed. Returns 0 if no db with the given name is +** open. */ -/*extern void my_foo(sqlite3 *);*/ -/* Only for emcc experimentation purposes. */ -sqlite3 * fiddle_the_db(){ - printf("fiddle_the_db(%p)\n", (const void*)globalDb); - /*my_foo(globalDb);*/ - return globalDb; +sqlite3_vfs * fiddle_db_vfs(const char *zDbName){ + sqlite3_vfs * pVfs = 0; + if(globalDb){ + sqlite3_file_control(globalDb, zDbName ? zDbName : "main", + SQLITE_FCNTL_VFS_POINTER, &pVfs); + } + return pVfs; } + /* Only for emcc experimentation purposes. */ sqlite3 * fiddle_db_arg(sqlite3 *arg){ printf("fiddle_db_arg(%p)\n", (const void*)arg); @@ -12623,7 +12095,7 @@ sqlite3 * fiddle_db_arg(sqlite3 *arg){ ** portable enough to make real use of. */ void fiddle_interrupt(void){ - if(globalDb) sqlite3_interrupt(globalDb); + if( globalDb ) sqlite3_interrupt(globalDb); } /* @@ -12637,71 +12109,72 @@ const char * fiddle_db_filename(const char * zDbName){ } /* -** Closes, unlinks, and reopens the db using its current filename (or -** the default if the db is currently closed). It is assumed, for -** purposes of the fiddle build, that the file is in a transient -** virtual filesystem within the browser. +** Completely wipes out the contents of the currently-opened database +** but leaves its storage intact for reuse. */ void fiddle_reset_db(void){ - char *zFilename = 0; - if(0==globalDb){ - shellState.pAuxDb->zDbFilename = "/fiddle.sqlite3"; - }else{ - zFilename = - sqlite3_mprintf("%s", sqlite3_db_filename(globalDb, "main")); - shell_check_oom(zFilename); - close_db(globalDb); - shellDeleteFile(zFilename); - shellState.db = 0; - shellState.pAuxDb->zDbFilename = zFilename; + if( globalDb ){ + int rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); + if( 0==rc ) rc = sqlite3_exec(globalDb, "VACUUM", 0, 0, 0); + sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); + } +} + +/* +** Uses the current database's VFS xRead to stream the db file's +** contents out to the given callback. The callback gets a single +** chunk of size n (its 2nd argument) on each call and must return 0 +** on success, non-0 on error. This function returns 0 on success, +** SQLITE_NOTFOUND if no db is open, or propagates any other non-0 +** code from the callback. Note that this is not thread-friendly: it +** expects that it will be the only thread reading the db file and +** takes no measures to ensure that is the case. +*/ +int fiddle_export_db( int (*xCallback)(unsigned const char *zOut, int n) ){ + sqlite3_int64 nSize = 0; + sqlite3_int64 nPos = 0; + sqlite3_file * pFile = 0; + unsigned char buf[1024 * 8]; + int nBuf = (int)sizeof(buf); + int rc = shellState.db + ? sqlite3_file_control(shellState.db, "main", + SQLITE_FCNTL_FILE_POINTER, &pFile) + : SQLITE_NOTFOUND; + if( rc ) return rc; + rc = pFile->pMethods->xFileSize(pFile, &nSize); + if( rc ) return rc; + if(nSize % nBuf){ + /* DB size is not an even multiple of the buffer size. Reduce + ** buffer size so that we do not unduly inflate the db size when + ** exporting. */ + if(0 == nSize % 4096) nBuf = 4096; + else if(0 == nSize % 2048) nBuf = 2048; + else if(0 == nSize % 1024) nBuf = 1024; + else nBuf = 512; + } + for( ; 0==rc && nPospMethods->xRead(pFile, buf, nBuf, nPos); + if(SQLITE_IOERR_SHORT_READ == rc){ + rc = (nPos + nBuf) < nSize ? rc : 0/*assume EOF*/; + } + if( 0==rc ) rc = xCallback(buf, nBuf); } - open_db(&shellState, 0); - sqlite3_free(zFilename); + return rc; } /* -** Trivial exportable function for emscripten. Needs to be exported using: -** -** emcc ..flags... -sEXPORTED_FUNCTIONS=_fiddle_exec -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -** -** (Note the underscore before the function name.) It processes zSql -** as if it were input to the sqlite3 shell and redirects all output -** to the wasm binding. +** Trivial exportable function for emscripten. It processes zSql as if +** it were input to the sqlite3 shell and redirects all output to the +** wasm binding. fiddle_main() must have been called before this +** is called, or results are undefined. */ void fiddle_exec(const char * zSql){ - static int once = 0; - int rc = 0; - if(!once){ - /* Simulate an argv array for main() */ - static char * argv[] = {"fiddle", - "-bail", - "-safe"}; - rc = fiddle_main((int)(sizeof(argv)/sizeof(argv[0])), argv); - once = rc ? -1 : 1; - memset(&shellState.wasm, 0, sizeof(shellState.wasm)); - printf( - "SQLite version %s %.19s\n" /*extra-version-info*/, - sqlite3_libversion(), sqlite3_sourceid() - ); - puts("WASM shell"); - puts("Enter \".help\" for usage hints."); - if(once>0){ - fiddle_reset_db(); - } - if(shellState.db){ - printf("Connected to %s.\n", fiddle_db_filename(NULL)); - }else{ - fprintf(stderr,"ERROR initializing db!\n"); - return; - } - } - if(once<0){ - puts("DB init failed. Not executing SQL."); - }else if(zSql && *zSql){ + if(zSql && *zSql){ + if('.'==*zSql) puts(zSql); shellState.wasm.zInput = zSql; shellState.wasm.zPos = zSql; process_input(&shellState); - memset(&shellState.wasm, 0, sizeof(shellState.wasm)); + shellState.wasm.zInput = shellState.wasm.zPos = 0; } } -#endif /* SQLITE_SHELL_WASM_MODE */ +#endif /* SQLITE_SHELL_FIDDLE */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index e2281e497..0d3040a4b 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -670,13 +670,17 @@ int sqlite3_exec( ** ** SQLite uses one of these integer values as the second ** argument to calls it makes to the xLock() and xUnlock() methods -** of an [sqlite3_io_methods] object. +** of an [sqlite3_io_methods] object. These values are ordered from +** lest restrictive to most restrictive. +** +** The argument to xLock() is always SHARED or higher. The argument to +** xUnlock is either SHARED or NONE. */ -#define SQLITE_LOCK_NONE 0 -#define SQLITE_LOCK_SHARED 1 -#define SQLITE_LOCK_RESERVED 2 -#define SQLITE_LOCK_PENDING 3 -#define SQLITE_LOCK_EXCLUSIVE 4 +#define SQLITE_LOCK_NONE 0 /* xUnlock() only */ +#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */ +#define SQLITE_LOCK_RESERVED 2 /* xLock() only */ +#define SQLITE_LOCK_PENDING 3 /* xLock() only */ +#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */ /* ** CAPI3REF: Synchronization Type Flags @@ -754,7 +758,14 @@ struct sqlite3_file { **
  • [SQLITE_LOCK_PENDING], or **
  • [SQLITE_LOCK_EXCLUSIVE]. ** -** xLock() increases the lock. xUnlock() decreases the lock. +** xLock() upgrades the database file lock. In other words, xLock() moves the +** database file lock in the direction NONE toward EXCLUSIVE. The argument to +** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never +** SQLITE_LOCK_NONE. If the database file lock is already at or above the +** requested lock, then the call to xLock() is a no-op. +** xUnlock() downgrades the database file lock to either SHARED or NONE. +* If the lock is already at or below the requested lock state, then the call +** to xUnlock() is a no-op. ** The xCheckReservedLock() method checks whether any database connection, ** either in this process or in some other process, is holding a RESERVED, ** PENDING, or EXCLUSIVE lock on the file. It returns true @@ -859,9 +870,8 @@ struct sqlite3_io_methods { ** opcode causes the xFileControl method to write the current state of ** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], ** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) -** into an integer that the pArg argument points to. This capability -** is used during testing and is only available when the SQLITE_TEST -** compile-time option is used. +** into an integer that the pArg argument points to. +** This capability is only available if SQLite is compiled with [SQLITE_DEBUG]. ** **
  • [[SQLITE_FCNTL_SIZE_HINT]] ** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS @@ -1182,6 +1192,12 @@ struct sqlite3_io_methods { ** **
  • [[SQLITE_FCNTL_CKSM_FILE]] ** Used by the cksmvfs VFS module only. +** +**
  • [[SQLITE_FCNTL_RESET_CACHE]] +** If there is currently no transaction open on the database, and the +** database is not a temp db, then this file-control purges the contents +** of the in-memory page cache. If there is an open transaction, or if +** the db is a temp-db, it is a no-op, not an error. ** */ #define SQLITE_FCNTL_LOCKSTATE 1 @@ -1224,6 +1240,7 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_CKPT_START 39 #define SQLITE_FCNTL_EXTERNAL_READER 40 #define SQLITE_FCNTL_CKSM_FILE 41 +#define SQLITE_FCNTL_RESET_CACHE 42 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -1253,6 +1270,26 @@ typedef struct sqlite3_mutex sqlite3_mutex; */ typedef struct sqlite3_api_routines sqlite3_api_routines; +/* +** CAPI3REF: File Name +** +** Type [sqlite3_filename] is used by SQLite to pass filenames to the +** xOpen method of a [VFS]. It may be cast to (const char*) and treated +** as a normal, nul-terminated, UTF-8 buffer containing the filename, but +** may also be passed to special APIs such as: +** +**
      +**
    • sqlite3_filename_database() +**
    • sqlite3_filename_journal() +**
    • sqlite3_filename_wal() +**
    • sqlite3_uri_parameter() +**
    • sqlite3_uri_boolean() +**
    • sqlite3_uri_int64() +**
    • sqlite3_uri_key() +**
    +*/ +typedef const char *sqlite3_filename; + /* ** CAPI3REF: OS Interface Object ** @@ -1431,7 +1468,7 @@ struct sqlite3_vfs { sqlite3_vfs *pNext; /* Next registered VFS */ const char *zName; /* Name of this virtual file system */ void *pAppData; /* Pointer to application-specific data */ - int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, + int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*, int flags, int *pOutFlags); int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); @@ -2309,6 +2346,7 @@ struct sqlite3_mem_methods { **
      **
    • The [PRAGMA writable_schema=ON] statement. **
    • The [PRAGMA journal_mode=OFF] statement. +**
    • The [PRAGMA schema_version=N] statement. **
    • Writes to the [sqlite_dbpage] virtual table. **
    • Direct writes to [shadow tables]. **
    @@ -3424,6 +3462,9 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); **
    The database is opened [shared cache] enabled, overriding ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ +** The [use of shared cache mode is discouraged] and hence shared cache +** capabilities may be omitted from many builds of SQLite. In such cases, +** this option is a no-op. ** ** ^(
    [SQLITE_OPEN_PRIVATECACHE]
    **
    The database is opened [shared cache] disabled, overriding @@ -3439,7 +3480,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** to return an extended result code.
    ** ** [[OPEN_NOFOLLOW]] ^(
    [SQLITE_OPEN_NOFOLLOW]
    -**
    The database filename is not allowed to be a symbolic link
    +**
    The database filename is not allowed to contain a symbolic link
    ** )^ ** ** If the 3rd parameter to sqlite3_open_v2() is not one of the @@ -3698,10 +3739,10 @@ int sqlite3_open_v2( ** ** See the [URI filename] documentation for additional information. */ -const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam); -int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); -sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); -const char *sqlite3_uri_key(const char *zFilename, int N); +const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam); +int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault); +sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64); +const char *sqlite3_uri_key(sqlite3_filename z, int N); /* ** CAPI3REF: Translate filenames @@ -3730,9 +3771,9 @@ const char *sqlite3_uri_key(const char *zFilename, int N); ** return value from [sqlite3_db_filename()], then the result is ** undefined and is likely a memory access violation. */ -const char *sqlite3_filename_database(const char*); -const char *sqlite3_filename_journal(const char*); -const char *sqlite3_filename_wal(const char*); +const char *sqlite3_filename_database(sqlite3_filename); +const char *sqlite3_filename_journal(sqlite3_filename); +const char *sqlite3_filename_wal(sqlite3_filename); /* ** CAPI3REF: Database File Corresponding To A Journal @@ -3798,14 +3839,14 @@ sqlite3_file *sqlite3_database_file_object(const char*); ** then the corresponding [sqlite3_module.xClose() method should also be ** invoked prior to calling sqlite3_free_filename(Y). */ -char *sqlite3_create_filename( +sqlite3_filename sqlite3_create_filename( const char *zDatabase, const char *zJournal, const char *zWal, int nParam, const char **azParam ); -void sqlite3_free_filename(char*); +void sqlite3_free_filename(sqlite3_filename); /* ** CAPI3REF: Error Codes And Messages @@ -5508,6 +5549,16 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), ** then the conversion is performed. Otherwise no conversion occurs. ** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ ** +** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], +** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current encoding +** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) +** returns something other than SQLITE_TEXT, then the return value from +** sqlite3_value_encoding(X) is meaningless. ^Calls to +** sqlite3_value_text(X), sqlite3_value_text16(X), sqlite3_value_text16be(X), +** sqlite3_value_text16le(X), sqlite3_value_bytes(X), or +** sqlite3_value_bytes16(X) might change the encoding of the value X and +** thus change the return from subsequent calls to sqlite3_value_encoding(X). +** ** ^Within the [xUpdate] method of a [virtual table], the ** sqlite3_value_nochange(X) interface returns true if and only if ** the column corresponding to X is unchanged by the UPDATE operation @@ -5572,6 +5623,7 @@ int sqlite3_value_type(sqlite3_value*); int sqlite3_value_numeric_type(sqlite3_value*); int sqlite3_value_nochange(sqlite3_value*); int sqlite3_value_frombind(sqlite3_value*); +int sqlite3_value_encoding(sqlite3_value*); /* ** CAPI3REF: Finding The Subtype Of SQL Values @@ -5625,7 +5677,7 @@ void sqlite3_value_free(sqlite3_value*); ** ** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer ** when first called if N is less than or equal to zero or if a memory -** allocate error occurs. +** allocation error occurs. ** ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is ** determined by the N parameter on first successful call. Changing the @@ -5830,9 +5882,10 @@ typedef void (*sqlite3_destructor_type)(void*); ** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. -** ^If the 3rd parameter to the sqlite3_result_text* interfaces -** is negative, then SQLite takes result text from the 2nd parameter -** through the first zero character. +** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces +** other than sqlite3_result_text64() is negative, then SQLite computes +** the string length itself by searching the 2nd parameter for the first +** zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined @@ -6328,7 +6381,7 @@ const char *sqlite3_db_name(sqlite3 *db, int N); **
  • [sqlite3_filename_wal()] ** */ -const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); +sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Determine if a database is read-only @@ -6465,7 +6518,7 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** function C that is invoked prior to each autovacuum of the database ** file. ^The callback is passed a copy of the generic data pointer (P), ** the schema-name of the attached database that is being autovacuumed, -** the the size of the database file in pages, the number of free pages, +** the size of the database file in pages, the number of free pages, ** and the number of bytes per page, respectively. The callback should ** return the number of free pages that should be removed by the ** autovacuum. ^If the callback returns zero, then no autovacuum happens. @@ -6586,6 +6639,11 @@ void *sqlite3_update_hook( ** to the same database. Sharing is enabled if the argument is true ** and disabled if the argument is false.)^ ** +** This interface is omitted if SQLite is compiled with +** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE] +** compile-time option is recommended because the +** [use of shared cache mode is discouraged]. +** ** ^Cache sharing is enabled and disabled for an entire process. ** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). ** In prior versions of SQLite, @@ -6684,7 +6742,7 @@ int sqlite3_db_release_memory(sqlite3*); ** ^The soft heap limit may not be greater than the hard heap limit. ** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N) ** is invoked with a value of N that is greater than the hard heap limit, -** the the soft heap limit is set to the value of the hard heap limit. +** the soft heap limit is set to the value of the hard heap limit. ** ^The soft heap limit is automatically enabled whenever the hard heap ** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and ** the soft heap limit is outside the range of 1..N, then the soft heap @@ -8979,7 +9037,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** if the application incorrectly accesses the destination [database connection] ** and so no error code is reported, but the operations may malfunction ** nevertheless. Use of the destination database connection while a -** backup is in progress might also also cause a mutex deadlock. +** backup is in progress might also cause a mutex deadlock. ** ** If running in [shared cache mode], the application must ** guarantee that the shared cache used by the destination database @@ -9407,7 +9465,7 @@ int sqlite3_wal_checkpoint_v2( */ #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ -#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ /* diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 2cdd0e429..79702d7b2 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -331,9 +331,9 @@ struct sqlite3_api_routines { const char *(*filename_journal)(const char*); const char *(*filename_wal)(const char*); /* Version 3.32.0 and later */ - char *(*create_filename)(const char*,const char*,const char*, + const char *(*create_filename)(const char*,const char*,const char*, int,const char**); - void (*free_filename)(char*); + void (*free_filename)(const char*); sqlite3_file *(*database_file_object)(const char*); /* Version 3.34.0 and later */ int (*txn_state)(sqlite3*,const char*); @@ -357,6 +357,8 @@ struct sqlite3_api_routines { unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, unsigned int); const char *(*db_name)(sqlite3*,int); + /* Version 3.40.0 and later */ + int (*value_encoding)(sqlite3_value*); }; /* @@ -681,6 +683,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_serialize sqlite3_api->serialize #endif #define sqlite3_db_name sqlite3_api->db_name +/* Version 3.40.0 and later */ +#define sqlite3_value_encoding sqlite3_api->value_encoding #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 1c9633990..e4b74f6d0 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -208,7 +208,7 @@ ** autoconf-based build */ #if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) -#include "config.h" +#include "sqlite_cfg.h" #define SQLITECONFIG_H 1 #endif @@ -1189,6 +1189,7 @@ typedef struct FuncDef FuncDef; typedef struct FuncDefHash FuncDefHash; typedef struct IdList IdList; typedef struct Index Index; +typedef struct IndexedExpr IndexedExpr; typedef struct IndexSample IndexSample; typedef struct KeyClass KeyClass; typedef struct KeyInfo KeyInfo; @@ -1254,6 +1255,7 @@ typedef struct With With; #define MASKBIT32(n) (((unsigned int)1)<<(n)) #define SMASKBIT32(n) ((n)<=31?((unsigned int)1)<<(n):0) #define ALLBITS ((Bitmask)-1) +#define TOPBIT (((Bitmask)1)<<(BMS-1)) /* A VList object records a mapping between parameters/variables/wildcards ** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer @@ -1268,11 +1270,11 @@ typedef int VList; ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque ** pointer types (i.e. FuncDef) defined above. */ +#include "os.h" #include "pager.h" #include "btree.h" #include "vdbe.h" #include "pcache.h" -#include "os.h" #include "mutex.h" /* The SQLITE_EXTRA_DURABLE compile-time option used to set the default @@ -1443,6 +1445,7 @@ struct Lookaside { #endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ void *pStart; /* First byte of available memory space */ void *pEnd; /* First byte past end of available space */ + void *pTrueEnd; /* True value of pEnd, when db->pnBytesFreed!=0 */ }; struct LookasideSlot { LookasideSlot *pNext; /* Next buffer in the list of free buffers */ @@ -1787,6 +1790,7 @@ struct sqlite3 { #define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */ #define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ +#define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -2359,7 +2363,7 @@ struct Table { #ifndef SQLITE_OMIT_VIRTUALTABLE # define IsVirtual(X) ((X)->eTabType==TABTYP_VTAB) # define ExprIsVtab(X) \ - ((X)->op==TK_COLUMN && (X)->y.pTab!=0 && (X)->y.pTab->eTabType==TABTYP_VTAB) + ((X)->op==TK_COLUMN && (X)->y.pTab->eTabType==TABTYP_VTAB) #else # define IsVirtual(X) 0 # define ExprIsVtab(X) 0 @@ -2576,10 +2580,22 @@ struct UnpackedRecord { ** The Index.onError field determines whether or not the indexed columns ** must be unique and what to do if they are not. When Index.onError=OE_None, ** it means this is not a unique index. Otherwise it is a unique index -** and the value of Index.onError indicate the which conflict resolution -** algorithm to employ whenever an attempt is made to insert a non-unique +** and the value of Index.onError indicates which conflict resolution +** algorithm to employ when an attempt is made to insert a non-unique ** element. ** +** The colNotIdxed bitmask is used in combination with SrcItem.colUsed +** for a fast test to see if an index can serve as a covering index. +** colNotIdxed has a 1 bit for every column of the original table that +** is *not* available in the index. Thus the expression +** "colUsed & colNotIdxed" will be non-zero if the index is not a +** covering index. The most significant bit of of colNotIdxed will always +** be true (note-20221022-a). If a column beyond the 63rd column of the +** table is used, the "colUsed & colNotIdxed" test will always be non-zero +** and we have to assume either that the index is not covering, or use +** an alternative (slower) algorithm to determine whether or not +** the index is covering. +** ** While parsing a CREATE TABLE or CREATE INDEX statement in order to ** generate VDBE code (as opposed to parsing one read from an sqlite_schema ** table as part of parsing an existing database schema), transient instances @@ -2615,6 +2631,8 @@ struct Index { unsigned bNoQuery:1; /* Do not use this index to optimize queries */ unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */ unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */ + unsigned bHasExpr:1; /* Index contains an expression, either a literal + ** expression, or a reference to a VIRTUAL column */ #ifdef SQLITE_ENABLE_STAT4 int nSample; /* Number of elements in aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ @@ -2623,7 +2641,7 @@ struct Index { tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */ tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */ #endif - Bitmask colNotIdxed; /* 0 for unindexed columns in pTab */ + Bitmask colNotIdxed; /* Unindexed columns in pTab */ }; /* @@ -2891,7 +2909,7 @@ struct Expr { #define EP_Reduced 0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ #define EP_Win 0x008000 /* Contains window functions */ #define EP_TokenOnly 0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ -#define EP_MemToken 0x020000 /* Need to sqlite3DbFree() Expr.zToken */ + /* 0x020000 // Available for reuse */ #define EP_IfNullRow 0x040000 /* The TK_IF_NULL_ROW opcode */ #define EP_Unlikely 0x080000 /* unlikely() or likelihood() function */ #define EP_ConstFunc 0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ @@ -3076,6 +3094,14 @@ struct IdList { ** The SrcItem object represents a single term in the FROM clause of a query. ** The SrcList object is mostly an array of SrcItems. ** +** The jointype starts out showing the join type between the current table +** and the next table on the list. The parser builds the list this way. +** But sqlite3SrcListShiftJoinType() later shifts the jointypes so that each +** jointype expresses the join between the table and the previous table. +** +** In the colUsed field, the high-order bit (bit 63) is set if the table +** contains more than 63 columns and the 64-th or later column is used. +** ** Union member validity: ** ** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc @@ -3115,14 +3141,14 @@ struct SrcItem { Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */ IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */ } u3; - Bitmask colUsed; /* Bit N (1<62 */ union { char *zIndexedBy; /* Identifier from "INDEXED BY " clause */ ExprList *pFuncArg; /* Arguments to table-valued-function */ } u1; union { Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ - CteUse *pCteUse; /* CTE Usage info info fg.isCte is true */ + CteUse *pCteUse; /* CTE Usage info when fg.isCte is true */ } u2; }; @@ -3136,23 +3162,11 @@ struct OnOrUsing { }; /* -** The following structure describes the FROM clause of a SELECT statement. -** Each table or subquery in the FROM clause is a separate element of -** the SrcList.a[] array. +** This object represents one or more tables that are the source of +** content for an SQL statement. For example, a single SrcList object +** is used to hold the FROM clause of a SELECT statement. SrcList also +** represents the target tables for DELETE, INSERT, and UPDATE statements. ** -** With the addition of multiple database support, the following structure -** can also be used to describe a particular table such as the table that -** is modified by an INSERT, DELETE, or UPDATE statement. In standard SQL, -** such a table must be a simple name: ID. But in SQLite, the table can -** now be identified by a database name, a dot, then the table name: ID.ID. -** -** The jointype starts out showing the join type between the current table -** and the next table on the list. The parser builds the list this way. -** But sqlite3SrcListShiftJoinType() later shifts the jointypes so that each -** jointype expresses the join between the table and the previous table. -** -** In the colUsed field, the high-order bit (bit 63) is set if the table -** contains more than 63 columns and the 64-th or later column is used. */ struct SrcList { int nSrc; /* Number of tables or subqueries in the FROM clause */ @@ -3497,7 +3511,7 @@ struct SelectDest { int iSDParm2; /* A second parameter for the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ - char *zAffSdst; /* Affinity used when eDest==SRT_Set */ + char *zAffSdst; /* Affinity used for SRT_Set, SRT_Table, and similar */ ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ }; @@ -3562,6 +3576,28 @@ struct TriggerPrg { # define DbMaskNonZero(M) (M)!=0 #endif +/* +** For each index X that has as one of its arguments either an expression +** or the name of a virtual generated column, and if X is in scope such that +** the value of the expression can simply be read from the index, then +** there is an instance of this object on the Parse.pIdxExpr list. +** +** During code generation, while generating code to evaluate expressions, +** this list is consulted and if a matching expression is found, the value +** is read from the index rather than being recomputed. +*/ +struct IndexedExpr { + Expr *pExpr; /* The expression contained in the index */ + int iDataCur; /* The data cursor associated with the index */ + int iIdxCur; /* The index cursor */ + int iIdxCol; /* The index column that contains value of pExpr */ + u8 bMaybeNullRow; /* True if we need an OP_IfNullRow check */ + IndexedExpr *pIENext; /* Next in a list of all indexed expressions */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + const char *zIdxName; /* Name of index, used only for bytecode comments */ +#endif +}; + /* ** An instance of the ParseCleanup object specifies an operation that ** should be performed after parsing to deallocation resources obtained @@ -3603,7 +3639,7 @@ struct Parse { u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ - u8 disableVtab; /* Disable all virtual tables for this parse */ + u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ @@ -3620,6 +3656,7 @@ struct Parse { int nLabelAlloc; /* Number of slots in aLabel */ int *aLabel; /* Space to hold the labels */ ExprList *pConstExpr;/* Constant expressions */ + IndexedExpr *pIdxExpr;/* List of expressions used by active indexes */ Token constraintName;/* Name of the constraint currently being parsed */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ @@ -4055,15 +4092,15 @@ struct Walker { struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ int *aiCol; /* array of column indexes */ struct IdxCover *pIdxCover; /* Check for index coverage */ - struct IdxExprTrans *pIdxTrans; /* Convert idxed expr to column */ ExprList *pGroupBy; /* GROUP BY clause */ Select *pSelect; /* HAVING to WHERE clause ctx */ struct WindowRewrite *pRewrite; /* Window rewrite context */ struct WhereConst *pConst; /* WHERE clause constants */ struct RenameCtx *pRename; /* RENAME COLUMN context */ struct Table *pTab; /* Table of generated column */ + struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */ SrcItem *pSrcItem; /* A single FROM clause item */ - DbFixer *pFix; + DbFixer *pFix; /* See sqlite3FixSelect() */ } u; }; @@ -4369,6 +4406,7 @@ void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); void *sqlite3DbRealloc(sqlite3 *, void *, u64); void sqlite3DbFree(sqlite3*, void*); void sqlite3DbFreeNN(sqlite3*, void*); +void sqlite3DbNNFreeNN(sqlite3*, void*); int sqlite3MallocSize(const void*); int sqlite3DbMallocSize(sqlite3*, const void*); void *sqlite3PageMalloc(int); @@ -4389,12 +4427,16 @@ int sqlite3HeapNearlyFull(void); */ #ifdef SQLITE_USE_ALLOCA # define sqlite3StackAllocRaw(D,N) alloca(N) +# define sqlite3StackAllocRawNN(D,N) alloca(N) # define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N) # define sqlite3StackFree(D,P) +# define sqlite3StackFreeNN(D,P) #else # define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N) +# define sqlite3StackAllocRawNN(D,N) sqlite3DbMallocRawNN(D,N) # define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N) # define sqlite3StackFree(D,P) sqlite3DbFree(D,P) +# define sqlite3StackFreeNN(D,P) sqlite3DbFreeNN(D,P) #endif /* Do not allow both MEMSYS5 and MEMSYS3 to be defined together. If they @@ -4893,6 +4935,7 @@ int sqlite3FixSelect(DbFixer*, Select*); int sqlite3FixExpr(DbFixer*, Expr*); int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); int sqlite3RealSameAsInt(double,sqlite3_int64); +i64 sqlite3RealToI64(double); void sqlite3Int64ToText(i64,char*); int sqlite3AtoF(const char *z, double*, int, u8); int sqlite3GetInt32(const char *, int*); @@ -4938,6 +4981,7 @@ int sqlite3VarintLen(u64 v); const char *sqlite3IndexAffinityStr(sqlite3*, Index*); +char *sqlite3TableAffinityStr(sqlite3*,const Table*); void sqlite3TableAffinity(Vdbe*, Table*, int); char sqlite3CompareAffinity(const Expr *pExpr, char aff2); int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); @@ -5009,7 +5053,6 @@ extern const unsigned char sqlite3OpcodeProperty[]; extern const char sqlite3StrBINARY[]; extern const unsigned char sqlite3StdTypeLen[]; extern const char sqlite3StdTypeAffinity[]; -extern const char sqlite3StdTypeMap[]; extern const char *sqlite3StdType[]; extern const unsigned char sqlite3UpperToLower[]; extern const unsigned char *sqlite3aLTb; @@ -5453,4 +5496,8 @@ void sqlite3VectorErrorMsg(Parse*, Expr*); const char **sqlite3CompileOptions(int *pnOpt); #endif +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) +int sqlite3KvvfsInit(void); +#endif + #endif /* SQLITEINT_H */ diff --git a/src/status.c b/src/status.c index e7f215844..784016700 100644 --- a/src/status.c +++ b/src/status.c @@ -291,6 +291,8 @@ int sqlite3_db_status( sqlite3BtreeEnterAll(db); db->pnBytesFreed = &nByte; + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; for(i=0; inDb; i++){ Schema *pSchema = db->aDb[i].pSchema; if( ALWAYS(pSchema!=0) ){ @@ -316,6 +318,7 @@ int sqlite3_db_status( } } db->pnBytesFreed = 0; + db->lookaside.pEnd = db->lookaside.pTrueEnd; sqlite3BtreeLeaveAll(db); *pHighwater = 0; @@ -333,9 +336,12 @@ int sqlite3_db_status( int nByte = 0; /* Used to accumulate return value */ db->pnBytesFreed = &nByte; - for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){ + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; + for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pVNext){ sqlite3VdbeDelete(pVdbe); } + db->lookaside.pEnd = db->lookaside.pTrueEnd; db->pnBytesFreed = 0; *pHighwater = 0; /* IMP: R-64479-57858 */ diff --git a/src/test1.c b/src/test1.c index 6ccd4512f..ba1a2252f 100644 --- a/src/test1.c +++ b/src/test1.c @@ -1852,7 +1852,8 @@ static int SQLITE_TCLAPI test_create_function_v2( {"utf16le", SQLITE_UTF16LE }, {"utf16be", SQLITE_UTF16BE }, {"any", SQLITE_ANY }, - {"0", 0 } + {"0", 0 }, + {0, 0 } }; if( objc<5 || (objc%2)==0 ){ @@ -7273,6 +7274,7 @@ static int SQLITE_TCLAPI test_test_control( { "SQLITE_TESTCTRL_SORTER_MMAP", SQLITE_TESTCTRL_SORTER_MMAP }, { "SQLITE_TESTCTRL_IMPOSTER", SQLITE_TESTCTRL_IMPOSTER }, { "SQLITE_TESTCTRL_INTERNAL_FUNCTIONS", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS}, + { 0, 0 } }; int iVerb; int iFlag; @@ -7627,7 +7629,12 @@ static int SQLITE_TCLAPI win32_rmdir( ** ** 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. +** OPT is the name of the optimization to be disabled. OPT can also be a +** list or optimizations names, in which case all optimizations named are +** enabled or disabled. +** +** Each invocation of this control overrides all prior invocations. The +** changes are not cumulative. */ static int SQLITE_TCLAPI optimization_control( void * clientData, @@ -7640,6 +7647,7 @@ static int SQLITE_TCLAPI optimization_control( const char *zOpt; int onoff; int mask = 0; + int cnt = 0; static const struct { const char *zOptName; int mask; @@ -7658,6 +7666,7 @@ static int SQLITE_TCLAPI optimization_control( { "skip-scan", SQLITE_SkipScan }, { "push-down", SQLITE_PushDown }, { "balanced-merge", SQLITE_BalancedMerge }, + { "propagate-const", SQLITE_PropagateConst }, }; if( objc!=4 ){ @@ -7668,13 +7677,13 @@ static int SQLITE_TCLAPI optimization_control( if( Tcl_GetBooleanFromObj(interp, objv[3], &onoff) ) return TCL_ERROR; zOpt = Tcl_GetString(objv[2]); for(i=0; i=sizeof(aOpt)/sizeof(aOpt[0]) ){ + if( cnt==0 ){ Tcl_AppendResult(interp, "unknown optimization - should be one of:", (char*)0); for(i=0; i2 ){ + Tcl_AppendResult(interp, "cannot create ", argv[1], + "MB file because Windows " + "does not support sparse files", (void*)0); + return TCL_ERROR; + } +#endif pVfs = sqlite3_vfs_find(0); nFile = (int)strlen(argv[2]); diff --git a/src/test_demovfs.c b/src/test_demovfs.c index eaba20873..29307675b 100644 --- a/src/test_demovfs.c +++ b/src/test_demovfs.c @@ -462,21 +462,23 @@ static int demoDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ if( rc==0 && dirSync ){ int dfd; /* File descriptor open on directory */ int i; /* Iterator variable */ + char *zSlash; char zDir[MAXPATHNAME+1]; /* Name of directory containing file zPath */ /* Figure out the directory name from the path of the file deleted. */ sqlite3_snprintf(MAXPATHNAME, zDir, "%s", zPath); zDir[MAXPATHNAME] = '\0'; - for(i=strlen(zDir); i>1 && zDir[i]!='/'; i++); - zDir[i] = '\0'; - - /* Open a file-descriptor on the directory. Sync. Close. */ - dfd = open(zDir, O_RDONLY, 0); - if( dfd<0 ){ - rc = -1; - }else{ - rc = fsync(dfd); - close(dfd); + zSlash = strrchr(zDir,'/'); + if( zSlash ){ + /* Open a file-descriptor on the directory. Sync. Close. */ + zSlash[0] = 0; + dfd = open(zDir, O_RDONLY, 0); + if( dfd<0 ){ + rc = -1; + }else{ + rc = fsync(dfd); + close(dfd); + } } } return (rc==0 ? SQLITE_OK : SQLITE_IOERR_DELETE); diff --git a/src/test_multiplex.c b/src/test_multiplex.c index ff1281715..226131f75 100644 --- a/src/test_multiplex.c +++ b/src/test_multiplex.c @@ -272,7 +272,7 @@ static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){ return SQLITE_NOMEM; } multiplexFilename(pGroup->zName, pGroup->nName, pGroup->flags, iChunk, z); - pGroup->aReal[iChunk].z = sqlite3_create_filename(z,"","",0,0); + pGroup->aReal[iChunk].z = (char*)sqlite3_create_filename(z,"","",0,0); sqlite3_free(z); if( pGroup->aReal[iChunk].z==0 ) return SQLITE_NOMEM; } diff --git a/src/test_tclsh.c b/src/test_tclsh.c index 707c16812..c133deca2 100644 --- a/src/test_tclsh.c +++ b/src/test_tclsh.c @@ -108,6 +108,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ extern int TestExpert_Init(Tcl_Interp*); extern int Sqlitetest_window_Init(Tcl_Interp *); extern int Sqlitetestvdbecov_Init(Tcl_Interp *); + extern int TestRecover_Init(Tcl_Interp*); Tcl_CmdInfo cmdInfo; @@ -175,6 +176,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ TestExpert_Init(interp); Sqlitetest_window_Init(interp); Sqlitetestvdbecov_Init(interp); + TestRecover_Init(interp); Tcl_CreateObjCommand( interp, "load_testfixture_extensions", load_testfixture_extensions,0,0 diff --git a/src/tokenize.c b/src/tokenize.c index b147cdb40..8dac9ece5 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -711,7 +711,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){ if( pParse->pNewTrigger && !IN_RENAME_OBJECT ){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); } - if( pParse->pVList ) sqlite3DbFreeNN(db, pParse->pVList); + if( pParse->pVList ) sqlite3DbNNFreeNN(db, pParse->pVList); db->pParse = pParentParse; assert( nErr==0 || pParse->rc!=SQLITE_OK ); return nErr; diff --git a/src/trigger.c b/src/trigger.c index 1c62fc231..02d854023 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -61,7 +61,7 @@ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){ if( pTrig->pTabSchema==pTab->pSchema && pTrig->table && 0==sqlite3StrICmp(pTrig->table, pTab->zName) - && pTrig->pTabSchema!=pTmpSchema + && (pTrig->pTabSchema!=pTmpSchema || pTrig->bReturning) ){ pTrig->pNext = pList; pList = pTrig; @@ -1191,7 +1191,7 @@ static TriggerPrg *codeRowTrigger( sSubParse.zAuthContext = pTrigger->zName; sSubParse.eTriggerOp = pTrigger->op; sSubParse.nQueryLoop = pParse->nQueryLoop; - sSubParse.disableVtab = pParse->disableVtab; + sSubParse.prepFlags = pParse->prepFlags; v = sqlite3GetVdbe(&sSubParse); if( v ){ diff --git a/src/update.c b/src/update.c index 3fd78a0b5..809b30653 100644 --- a/src/update.c +++ b/src/update.c @@ -59,11 +59,14 @@ static void updateVirtualTable( ** it has been converted into REAL. */ void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ + Column *pCol; assert( pTab!=0 ); - if( !IsView(pTab) ){ + assert( pTab->nCol>i ); + pCol = &pTab->aCol[i]; + if( pCol->iDflt ){ sqlite3_value *pValue = 0; u8 enc = ENC(sqlite3VdbeDb(v)); - Column *pCol = &pTab->aCol[i]; + assert( !IsView(pTab) ); VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName)); assert( inCol ); sqlite3ValueFromExpr(sqlite3VdbeDb(v), @@ -74,7 +77,7 @@ void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ } } #ifndef SQLITE_OMIT_FLOATING_POINT - if( pTab->aCol[i].affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){ + if( pCol->affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); } #endif diff --git a/src/upsert.c b/src/upsert.c index fb6c7c0c0..85994020c 100644 --- a/src/upsert.c +++ b/src/upsert.c @@ -166,6 +166,7 @@ int sqlite3UpsertAnalyzeTarget( if( pIdx->aiColumn[ii]==XN_EXPR ){ assert( pIdx->aColExpr!=0 ); assert( pIdx->aColExpr->nExpr>ii ); + assert( pIdx->bHasExpr ); pExpr = pIdx->aColExpr->a[ii].pExpr; if( pExpr->op!=TK_COLLATE ){ sCol[0].pLeft = pExpr; diff --git a/src/vacuum.c b/src/vacuum.c index 9899b63cf..9908cf142 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -161,6 +161,7 @@ SQLITE_NOINLINE int sqlite3RunVacuum( int nDb; /* Number of attached databases */ const char *zDbMain; /* Schema name of database to vacuum */ const char *zOut; /* Name of output file */ + u32 pgflags = PAGER_SYNCHRONOUS_OFF; /* sync flags for output db */ if( !db->autoCommit ){ sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction"); @@ -232,12 +233,17 @@ SQLITE_NOINLINE int sqlite3RunVacuum( goto end_of_vacuum; } db->mDbFlags |= DBFLAG_VacuumInto; + + /* For a VACUUM INTO, the pager-flags are set to the same values as + ** they are for the database being vacuumed, except that PAGER_CACHESPILL + ** is always set. */ + pgflags = db->aDb[iDb].safety_level | (db->flags & PAGER_FLAGS_MASK); } nRes = sqlite3BtreeGetRequestedReserve(pMain); sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); - sqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF|PAGER_CACHESPILL); + sqlite3BtreeSetPagerFlags(pTemp, pgflags|PAGER_CACHESPILL); /* Begin a transaction and take an exclusive lock on the main database ** file. This is done before the sqlite3BtreeGetPageSize(pMain) call below, diff --git a/src/vdbe.c b/src/vdbe.c index ef9a23f64..4eb1c0327 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -314,7 +314,8 @@ static VdbeCursor *allocateCursor( ** return false. */ static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){ - i64 iValue = (double)rValue; + i64 iValue; + iValue = sqlite3RealToI64(rValue); if( sqlite3RealSameAsInt(rValue,iValue) ){ *piValue = iValue; return 1; @@ -476,17 +477,18 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ ** But it does set pMem->u.r and pMem->u.i appropriately. */ static u16 numericType(Mem *pMem){ - if( pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal) ){ + assert( (pMem->flags & MEM_Null)==0 + || pMem->db==0 || pMem->db->mallocFailed ); + if( pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null) ){ testcase( pMem->flags & MEM_Int ); testcase( pMem->flags & MEM_Real ); testcase( pMem->flags & MEM_IntReal ); - return pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal); - } - if( pMem->flags & (MEM_Str|MEM_Blob) ){ - testcase( pMem->flags & MEM_Str ); - testcase( pMem->flags & MEM_Blob ); - return computeNumericType(pMem); + return pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null); } + assert( pMem->flags & (MEM_Str|MEM_Blob) ); + testcase( pMem->flags & MEM_Str ); + testcase( pMem->flags & MEM_Blob ); + return computeNumericType(pMem); return 0; } @@ -1731,7 +1733,6 @@ case OP_Subtract: /* same as TK_MINUS, in1, in2, out3 */ case OP_Multiply: /* same as TK_STAR, in1, in2, out3 */ case OP_Divide: /* same as TK_SLASH, in1, in2, out3 */ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ - u16 flags; /* Combined MEM_* flags from both inputs */ u16 type1; /* Numeric type of left operand */ u16 type2; /* Numeric type of right operand */ i64 iA; /* Integer value of left operand */ @@ -1740,12 +1741,12 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ double rB; /* Real value of right operand */ pIn1 = &aMem[pOp->p1]; - type1 = numericType(pIn1); + type1 = pIn1->flags; pIn2 = &aMem[pOp->p2]; - type2 = numericType(pIn2); + type2 = pIn2->flags; pOut = &aMem[pOp->p3]; - flags = pIn1->flags | pIn2->flags; if( (type1 & type2 & MEM_Int)!=0 ){ +int_math: iA = pIn1->u.i; iB = pIn2->u.i; switch( pOp->opcode ){ @@ -1767,9 +1768,12 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ } pOut->u.i = iB; MemSetTypeFlag(pOut, MEM_Int); - }else if( (flags & MEM_Null)!=0 ){ + }else if( ((type1 | type2) & MEM_Null)!=0 ){ goto arithmetic_result_is_null; }else{ + type1 = numericType(pIn1); + type2 = numericType(pIn2); + if( (type1 & type2 & MEM_Int)!=0 ) goto int_math; fp_math: rA = sqlite3VdbeRealValue(pIn1); rB = sqlite3VdbeRealValue(pIn2); @@ -2584,19 +2588,90 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ break; } -/* Opcode: IsNullOrType P1 P2 P3 * * -** Synopsis: if typeof(r[P1]) IN (P3,5) goto P2 +/* Opcode: IsType P1 P2 P3 P4 P5 +** Synopsis: if typeof(P1.P3) in P5 goto P2 +** +** Jump to P2 if the type of a column in a btree is one of the types specified +** by the P5 bitmask. +** +** P1 is normally a cursor on a btree for which the row decode cache is +** valid through at least column P3. In other words, there should have been +** a prior OP_Column for column P3 or greater. If the cursor is not valid, +** then this opcode might give spurious results. +** The the btree row has fewer than P3 columns, then use P4 as the +** datatype. +** +** If P1 is -1, then P3 is a register number and the datatype is taken +** from the value in that register. +** +** P5 is a bitmask of data types. SQLITE_INTEGER is the least significant +** (0x01) bit. SQLITE_FLOAT is the 0x02 bit. SQLITE_TEXT is 0x04. +** SQLITE_BLOB is 0x08. SQLITE_NULL is 0x10. +** +** Take the jump to address P2 if and only if the datatype of the +** value determined by P1 and P3 corresponds to one of the bits in the +** P5 bitmask. ** -** Jump to P2 if the value in register P1 is NULL or has a datatype P3. -** P3 is an integer which should be one of SQLITE_INTEGER, SQLITE_FLOAT, -** SQLITE_BLOB, SQLITE_NULL, or SQLITE_TEXT. */ -case OP_IsNullOrType: { /* jump, in1 */ - int doTheJump; - pIn1 = &aMem[pOp->p1]; - doTheJump = (pIn1->flags & MEM_Null)!=0 || sqlite3_value_type(pIn1)==pOp->p3; - VdbeBranchTaken( doTheJump, 2); - if( doTheJump ) goto jump_to_p2; +case OP_IsType: { /* jump */ + VdbeCursor *pC; + u16 typeMask; + u32 serialType; + + assert( pOp->p1>=(-1) && pOp->p1nCursor ); + assert( pOp->p1>=0 || (pOp->p3>=0 && pOp->p3<=(p->nMem+1 - p->nCursor)) ); + if( pOp->p1>=0 ){ + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pOp->p3>=0 ); + if( pOp->p3nHdrParsed ){ + serialType = pC->aType[pOp->p3]; + if( serialType>=12 ){ + if( serialType&1 ){ + typeMask = 0x04; /* SQLITE_TEXT */ + }else{ + typeMask = 0x08; /* SQLITE_BLOB */ + } + }else{ + static const unsigned char aMask[] = { + 0x10, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2, + 0x01, 0x01, 0x10, 0x10 + }; + testcase( serialType==0 ); + testcase( serialType==1 ); + testcase( serialType==2 ); + testcase( serialType==3 ); + testcase( serialType==4 ); + testcase( serialType==5 ); + testcase( serialType==6 ); + testcase( serialType==7 ); + testcase( serialType==8 ); + testcase( serialType==9 ); + testcase( serialType==10 ); + testcase( serialType==11 ); + typeMask = aMask[serialType]; + } + }else{ + typeMask = 1 << (pOp->p4.i - 1); + testcase( typeMask==0x01 ); + testcase( typeMask==0x02 ); + testcase( typeMask==0x04 ); + testcase( typeMask==0x08 ); + testcase( typeMask==0x10 ); + } + }else{ + assert( memIsValid(&aMem[pOp->p3]) ); + typeMask = 1 << (sqlite3_value_type((sqlite3_value*)&aMem[pOp->p3])-1); + testcase( typeMask==0x01 ); + testcase( typeMask==0x02 ); + testcase( typeMask==0x04 ); + testcase( typeMask==0x08 ); + testcase( typeMask==0x10 ); + } + VdbeBranchTaken( (typeMask & pOp->p5)!=0, 2); + if( typeMask & pOp->p5 ){ + goto jump_to_p2; + } break; } @@ -2639,11 +2714,14 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ ** If it is, then set register P3 to NULL and jump immediately to P2. ** If P1 is not on a NULL row, then fall through without making any ** changes. +** +** If P1 is not an open cursor, then this opcode is a no-op. */ case OP_IfNullRow: { /* jump */ + VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); - assert( p->apCsr[pOp->p1]!=0 ); - if( p->apCsr[pOp->p1]->nullRow ){ + pC = p->apCsr[pOp->p1]; + if( ALWAYS(pC) && pC->nullRow ){ sqlite3VdbeMemSetNull(aMem + pOp->p3); goto jump_to_p2; } @@ -2694,7 +2772,7 @@ case OP_Offset: { /* out3 */ ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional ** information about the format of the data.) Extract the P2-th column -** from this record. If there are less that (P2+1) +** from this record. If there are less than (P2+1) ** values in the record, extract a NULL. ** ** The value extracted is stored in register P3. @@ -2703,10 +2781,12 @@ case OP_Offset: { /* out3 */ ** if the P4 argument is a P4_MEM use the value of the P4 argument as ** the result. ** -** If the OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG bits are set on P5 then -** the result is guaranteed to only be used as the argument of a length() -** or typeof() function, respectively. The loading of large blobs can be -** skipped for length() and all content loading can be skipped for typeof(). +** If the OPFLAG_LENGTHARG bit is set in P5 then the result is guaranteed +** to only be used by the length() function or the equivalent. The content +** of large blobs is not loaded, thus saving CPU cycles. If the +** OPFLAG_TYPEOFARG bit is set then the result will only be used by the +** typeof() function or the IS NULL or IS NOT NULL operators or the +** equivalent. In this case, all content loading can be omitted. */ case OP_Column: { u32 p2; /* column number to retrieve */ @@ -4645,7 +4725,13 @@ case OP_SeekGT: { /* jump, in3, group */ r.aMem = &aMem[pOp->p3]; #ifdef SQLITE_DEBUG - { int i; for(i=0; i0 ) REGISTER_TRACE(pOp->p3+i, &r.aMem[i]); + } + } #endif r.eqSeen = 0; rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &res); @@ -4708,7 +4794,7 @@ case OP_SeekGT: { /* jump, in3, group */ } -/* Opcode: SeekScan P1 P2 * * * +/* Opcode: SeekScan P1 P2 * * P5 ** Synopsis: Scan-ahead up to P1 rows ** ** This opcode is a prefix opcode to OP_SeekGE. In other words, this @@ -4718,8 +4804,8 @@ case OP_SeekGT: { /* jump, in3, group */ ** This opcode uses the P1 through P4 operands of the subsequent ** OP_SeekGE. In the text that follows, the operands of the subsequent ** OP_SeekGE opcode are denoted as SeekOP.P1 through SeekOP.P4. Only -** the P1 and P2 operands of this opcode are also used, and are called -** This.P1 and This.P2. +** the P1, P2 and P5 operands of this opcode are also used, and are called +** This.P1, This.P2 and This.P5. ** ** This opcode helps to optimize IN operators on a multi-column index ** where the IN operator is on the later terms of the index by avoiding @@ -4729,29 +4815,51 @@ case OP_SeekGT: { /* jump, in3, group */ ** ** The SeekGE.P3 and SeekGE.P4 operands identify an unpacked key which ** is the desired entry that we want the cursor SeekGE.P1 to be pointing -** to. Call this SeekGE.P4/P5 row the "target". +** to. Call this SeekGE.P3/P4 row the "target". ** ** If the SeekGE.P1 cursor is not currently pointing to a valid row, ** then this opcode is a no-op and control passes through into the OP_SeekGE. ** ** If the SeekGE.P1 cursor is pointing to a valid row, then that row ** might be the target row, or it might be near and slightly before the -** target row. This opcode attempts to position the cursor on the target -** row by, perhaps by invoking sqlite3BtreeStep() on the cursor -** between 0 and This.P1 times. -** -** There are three possible outcomes from this opcode:
      -** -**
    1. If after This.P1 steps, the cursor is still pointing to a place that -** is earlier in the btree than the target row, then fall through -** into the subsquence OP_SeekGE opcode. -** -**
    2. If the cursor is successfully moved to the target row by 0 or more -** sqlite3BtreeNext() calls, then jump to This.P2, which will land just -** past the OP_IdxGT or OP_IdxGE opcode that follows the OP_SeekGE. -** -**
    3. If the cursor ends up past the target row (indicating the the target -** row does not exist in the btree) then jump to SeekOP.P2. +** target row, or it might be after the target row. If the cursor is +** currently before the target row, then this opcode attempts to position +** the cursor on or after the target row by invoking sqlite3BtreeStep() +** on the cursor between 1 and This.P1 times. +** +** The This.P5 parameter is a flag that indicates what to do if the +** cursor ends up pointing at a valid row that is past the target +** row. If This.P5 is false (0) then a jump is made to SeekGE.P2. If +** This.P5 is true (non-zero) then a jump is made to This.P2. The P5==0 +** case occurs when there are no inequality constraints to the right of +** the IN constraing. The jump to SeekGE.P2 ends the loop. The P5!=0 case +** occurs when there are inequality constraints to the right of the IN +** operator. In that case, the This.P2 will point either directly to or +** to setup code prior to the OP_IdxGT or OP_IdxGE opcode that checks for +** loop terminate. +** +** Possible outcomes from this opcode:
        +** +**
      1. If the cursor is initally not pointed to any valid row, then +** fall through into the subsequent OP_SeekGE opcode. +** +**
      2. If the cursor is left pointing to a row that is before the target +** row, even after making as many as This.P1 calls to +** sqlite3BtreeNext(), then also fall through into OP_SeekGE. +** +**
      3. If the cursor is left pointing at the target row, either because it +** was at the target row to begin with or because one or more +** sqlite3BtreeNext() calls moved the cursor to the target row, +** then jump to This.P2.., +** +**
      4. If the cursor started out before the target row and a call to +** to sqlite3BtreeNext() moved the cursor off the end of the index +** (indicating that the target row definitely does not exist in the +** btree) then jump to SeekGE.P2, ending the loop. +** +**
      5. If the cursor ends up on a valid row that is past the target row +** (indicating that the target row does not exist in the btree) then +** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0. **
      */ case OP_SeekScan: { @@ -4762,14 +4870,25 @@ case OP_SeekScan: { assert( pOp[1].opcode==OP_SeekGE ); - /* pOp->p2 points to the first instruction past the OP_IdxGT that - ** follows the OP_SeekGE. */ + /* If pOp->p5 is clear, then pOp->p2 points to the first instruction past the + ** OP_IdxGT that follows the OP_SeekGE. Otherwise, it points to the first + ** opcode past the OP_SeekGE itself. */ assert( pOp->p2>=(int)(pOp-aOp)+2 ); - assert( aOp[pOp->p2-1].opcode==OP_IdxGT || aOp[pOp->p2-1].opcode==OP_IdxGE ); - testcase( aOp[pOp->p2-1].opcode==OP_IdxGE ); - assert( pOp[1].p1==aOp[pOp->p2-1].p1 ); - assert( pOp[1].p2==aOp[pOp->p2-1].p2 ); - assert( pOp[1].p3==aOp[pOp->p2-1].p3 ); +#ifdef SQLITE_DEBUG + if( pOp->p5==0 ){ + /* There are no inequality constraints following the IN constraint. */ + assert( pOp[1].p1==aOp[pOp->p2-1].p1 ); + assert( pOp[1].p2==aOp[pOp->p2-1].p2 ); + assert( pOp[1].p3==aOp[pOp->p2-1].p3 ); + assert( aOp[pOp->p2-1].opcode==OP_IdxGT + || aOp[pOp->p2-1].opcode==OP_IdxGE ); + testcase( aOp[pOp->p2-1].opcode==OP_IdxGE ); + }else{ + /* There are inequality constraints. */ + assert( pOp->p2==(int)(pOp-aOp)+2 ); + assert( aOp[pOp->p2-1].opcode==OP_SeekGE ); + } +#endif assert( pOp->p1>0 ); pC = p->apCsr[pOp[1].p1]; @@ -4803,8 +4922,9 @@ case OP_SeekScan: { while(1){ rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res); if( rc ) goto abort_due_to_error; - if( res>0 ){ + if( res>0 && pOp->p5==0 ){ seekscan_search_fail: + /* Jump to SeekGE.P2, ending the loop */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("... %d steps and then skip\n", pOp->p1 - nStep); @@ -4814,7 +4934,8 @@ case OP_SeekScan: { pOp++; goto jump_to_p2; } - if( res==0 ){ + if( res>=0 ){ + /* Jump to This.P2, bypassing the OP_SeekGE opcode */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("... %d steps and then success\n", pOp->p1 - nStep); @@ -4890,12 +5011,16 @@ case OP_SeekHit: { /* Opcode: IfNotOpen P1 P2 * * * ** Synopsis: if( !csr[P1] ) goto P2 ** -** If cursor P1 is not open, jump to instruction P2. Otherwise, fall through. +** If cursor P1 is not open or if P1 is set to a NULL row using the +** OP_NullRow opcode, then jump to instruction P2. Otherwise, fall through. */ case OP_IfNotOpen: { /* jump */ + VdbeCursor *pCur; + assert( pOp->p1>=0 && pOp->p1nCursor ); - VdbeBranchTaken(p->apCsr[pOp->p1]==0, 2); - if( !p->apCsr[pOp->p1] ){ + pCur = p->apCsr[pOp->p1]; + VdbeBranchTaken(pCur==0 || pCur->nullRow, 2); + if( pCur==0 || pCur->nullRow ){ goto jump_to_p2_and_check_for_interrupt; } break; @@ -6086,7 +6211,9 @@ case OP_SorterNext: { /* jump */ case OP_Prev: /* jump */ assert( pOp->p1>=0 && pOp->p1nCursor ); - assert( pOp->p5aCounter) ); + assert( pOp->p5==0 + || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP + || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); @@ -6099,7 +6226,9 @@ case OP_Prev: /* jump */ case OP_Next: /* jump */ assert( pOp->p1>=0 && pOp->p1nCursor ); - assert( pOp->p5aCounter) ); + assert( pOp->p5==0 + || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP + || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); @@ -6306,10 +6435,10 @@ case OP_IdxRowid: { /* out2 */ ** of sqlite3VdbeCursorRestore() and sqlite3VdbeIdxRowid(). */ rc = sqlite3VdbeCursorRestore(pC); - /* sqlite3VbeCursorRestore() can only fail if the record has been deleted - ** out from under the cursor. That will never happens for an IdxRowid - ** or Seek opcode */ - if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; + /* sqlite3VdbeCursorRestore() may fail if the cursor has been disturbed + ** since it was last positioned and an error (e.g. OOM or an IO error) + ** occurs while trying to reposition it. */ + if( rc!=SQLITE_OK ) goto abort_due_to_error; if( !pC->nullRow ){ rowid = 0; /* Not needed. Only used to silence a warning. */ @@ -7211,7 +7340,7 @@ case OP_IfPos: { /* jump, in1 */ ** Synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) ** ** This opcode performs a commonly used computation associated with -** LIMIT and OFFSET process. r[P1] holds the limit counter. r[P3] +** LIMIT and OFFSET processing. r[P1] holds the limit counter. r[P3] ** holds the offset counter. The opcode computes the combined value ** of the LIMIT and OFFSET and stores that value in r[P2]. The r[P2] ** value computed is the total number of rows that will need to be diff --git a/src/vdbe.h b/src/vdbe.h index 5909d3995..a244468b5 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -228,6 +228,7 @@ void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1); void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2); void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3); void sqlite3VdbeChangeP5(Vdbe*, u16 P5); +void sqlite3VdbeTypeofColumn(Vdbe*, int); void sqlite3VdbeJumpHere(Vdbe*, int addr); void sqlite3VdbeJumpHereOrPopInst(Vdbe*, int addr); int sqlite3VdbeChangeToNoop(Vdbe*, int addr); @@ -242,6 +243,7 @@ void sqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type); void sqlite3VdbeSetP4KeyInfo(Parse*, Index*); void sqlite3VdbeUsesBtree(Vdbe*, int); VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); +VdbeOp *sqlite3VdbeGetLastOp(Vdbe*); int sqlite3VdbeMakeLabel(Parse*); void sqlite3VdbeRunOnlyOnce(Vdbe*); void sqlite3VdbeReusable(Vdbe*); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 0e17c7d91..0c6301c8f 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -420,7 +420,7 @@ struct DblquoteStr { */ struct Vdbe { sqlite3 *db; /* The database connection that owns this statement */ - Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ + Vdbe **ppVPrev,*pVNext; /* Linked list of VDBEs with the same Vdbe.db */ Parse *pParse; /* Parsing context used to create this Vdbe */ ynVar nVar; /* Number of entries in aVar[] */ int nMem; /* Number of memory locations currently allocated */ diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 4e719ede3..04295342b 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -108,7 +108,9 @@ int sqlite3_finalize(sqlite3_stmt *pStmt){ if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT; sqlite3_mutex_enter(db->mutex); checkProfileCallback(db, v); - rc = sqlite3VdbeFinalize(v); + assert( v->eVdbeState>=VDBE_READY_STATE ); + rc = sqlite3VdbeReset(v); + sqlite3VdbeDelete(v); rc = sqlite3ApiExit(db, rc); sqlite3LeaveMutexAndCloseZombie(db); } @@ -316,6 +318,9 @@ int sqlite3_value_type(sqlite3_value* pVal){ #endif return aType[pVal->flags&MEM_AffMask]; } +int sqlite3_value_encoding(sqlite3_value *pVal){ + return pVal->enc; +} /* Return true if a parameter to xUpdate represents an unchanged column */ int sqlite3_value_nochange(sqlite3_value *pVal){ @@ -1430,7 +1435,7 @@ const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ ** The error code stored in database p->db is overwritten with the return ** value in any case. */ -static int vdbeUnbind(Vdbe *p, int i){ +static int vdbeUnbind(Vdbe *p, unsigned int i){ Mem *pVar; if( vdbeSafetyNotNull(p) ){ return SQLITE_MISUSE_BKPT; @@ -1443,12 +1448,11 @@ static int vdbeUnbind(Vdbe *p, int i){ "bind on a busy prepared statement: [%s]", p->zSql); return SQLITE_MISUSE_BKPT; } - if( i<1 || i>p->nVar ){ + if( i>=(unsigned int)p->nVar ){ sqlite3Error(p->db, SQLITE_RANGE); sqlite3_mutex_leave(p->db->mutex); return SQLITE_RANGE; } - i--; pVar = &p->aVar[i]; sqlite3VdbeMemRelease(pVar); pVar->flags = MEM_Null; @@ -1485,7 +1489,7 @@ static int bindText( Mem *pVar; int rc; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ if( zData!=0 ){ pVar = &p->aVar[i-1]; @@ -1534,7 +1538,7 @@ int sqlite3_bind_blob64( int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ int rc; Vdbe *p = (Vdbe *)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue); sqlite3_mutex_leave(p->db->mutex); @@ -1547,7 +1551,7 @@ int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){ int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ int rc; Vdbe *p = (Vdbe *)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue); sqlite3_mutex_leave(p->db->mutex); @@ -1557,7 +1561,7 @@ int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){ int rc; Vdbe *p = (Vdbe*)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ sqlite3_mutex_leave(p->db->mutex); } @@ -1572,7 +1576,7 @@ int sqlite3_bind_pointer( ){ int rc; Vdbe *p = (Vdbe*)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetPointer(&p->aVar[i-1], pPtr, zPTtype, xDestructor); sqlite3_mutex_leave(p->db->mutex); @@ -1650,7 +1654,7 @@ int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ int rc; Vdbe *p = (Vdbe *)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ #ifndef SQLITE_OMIT_INCRBLOB sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); @@ -1810,7 +1814,7 @@ sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){ if( pStmt==0 ){ pNext = (sqlite3_stmt*)pDb->pVdbe; }else{ - pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pNext; + pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pVNext; } sqlite3_mutex_leave(pDb->mutex); return pNext; @@ -1835,8 +1839,11 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ sqlite3_mutex_enter(db->mutex); v = 0; db->pnBytesFreed = (int*)&v; + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; sqlite3VdbeDelete(pVdbe); db->pnBytesFreed = 0; + db->lookaside.pEnd = db->lookaside.pTrueEnd; sqlite3_mutex_leave(db->mutex); }else{ v = pVdbe->aCounter[op]; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 3ef2bac31..fd196f37b 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -30,10 +30,10 @@ Vdbe *sqlite3VdbeCreate(Parse *pParse){ memset(&p->aOp, 0, sizeof(Vdbe)-offsetof(Vdbe,aOp)); p->db = db; if( db->pVdbe ){ - db->pVdbe->pPrev = p; + db->pVdbe->ppVPrev = &p->pVNext; } - p->pNext = db->pVdbe; - p->pPrev = 0; + p->pVNext = db->pVdbe; + p->ppVPrev = &db->pVdbe; db->pVdbe = p; assert( p->eVdbeState==VDBE_INIT_STATE ); p->pParse = pParse; @@ -115,21 +115,28 @@ int sqlite3VdbeUsesDoubleQuotedString( #endif /* -** Swap all content between two VDBE structures. +** Swap byte-code between two VDBE structures. +** +** This happens after pB was previously run and returned +** SQLITE_SCHEMA. The statement was then reprepared in pA. +** This routine transfers the new bytecode in pA over to pB +** so that pB can be run again. The old pB byte code is +** moved back to pA so that it will be cleaned up when pA is +** finalized. */ void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ - Vdbe tmp, *pTmp; + Vdbe tmp, *pTmp, **ppTmp; char *zTmp; assert( pA->db==pB->db ); tmp = *pA; *pA = *pB; *pB = tmp; - pTmp = pA->pNext; - pA->pNext = pB->pNext; - pB->pNext = pTmp; - pTmp = pA->pPrev; - pA->pPrev = pB->pPrev; - pB->pPrev = pTmp; + pTmp = pA->pVNext; + pA->pVNext = pB->pVNext; + pB->pVNext = pTmp; + ppTmp = pA->ppVPrev; + pA->ppVPrev = pB->ppVPrev; + pB->ppVPrev = ppTmp; zTmp = pA->zSql; pA->zSql = pB->zSql; pB->zSql = zTmp; @@ -450,7 +457,7 @@ void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ iThis = v->nOp; sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0, zMsg, P4_DYNAMIC); - sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetOp(v,-1)->p4.z); + sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetLastOp(v)->p4.z); if( bPush){ pParse->addrExplain = iThis; } @@ -808,8 +815,8 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ p->readOnly = 1; p->bIsReader = 0; pOp = &p->aOp[p->nOp-1]; - while(1){ - + assert( p->aOp[0].opcode==OP_Init ); + while( 1 /* Loop termates when it reaches the OP_Init opcode */ ){ /* Only JUMP opcodes and the short list of special opcodes in the switch ** below need to be considered. The mkopcodeh.tcl generator script groups ** all these opcodes together near the front of the opcode list. Skip @@ -838,6 +845,10 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ p->bIsReader = 1; break; } + case OP_Init: { + assert( pOp->p2>=0 ); + goto resolve_p2_values_loop_exit; + } #ifndef SQLITE_OMIT_VIRTUALTABLE case OP_VUpdate: { if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2; @@ -870,11 +881,12 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ ** have non-negative values for P2. */ assert( (sqlite3OpcodeProperty[pOp->opcode]&OPFLG_JUMP)==0 || pOp->p2>=0); } - if( pOp==p->aOp ) break; + assert( pOp>p->aOp ); pOp--; } +resolve_p2_values_loop_exit: if( aLabel ){ - sqlite3DbFreeNN(p->db, pParse->aLabel); + sqlite3DbNNFreeNN(p->db, pParse->aLabel); pParse->aLabel = 0; } pParse->nLabel = 0; @@ -1123,15 +1135,19 @@ void sqlite3VdbeScanStatus( ** for a specific instruction. */ void sqlite3VdbeChangeOpcode(Vdbe *p, int addr, u8 iNewOpcode){ + assert( addr>=0 ); sqlite3VdbeGetOp(p,addr)->opcode = iNewOpcode; } void sqlite3VdbeChangeP1(Vdbe *p, int addr, int val){ + assert( addr>=0 ); sqlite3VdbeGetOp(p,addr)->p1 = val; } void sqlite3VdbeChangeP2(Vdbe *p, int addr, int val){ + assert( addr>=0 || p->db->mallocFailed ); sqlite3VdbeGetOp(p,addr)->p2 = val; } void sqlite3VdbeChangeP3(Vdbe *p, int addr, int val){ + assert( addr>=0 ); sqlite3VdbeGetOp(p,addr)->p3 = val; } void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){ @@ -1139,6 +1155,18 @@ void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){ if( p->nOp>0 ) p->aOp[p->nOp-1].p5 = p5; } +/* +** If the previous opcode is an OP_Column that delivers results +** into register iDest, then add the OPFLAG_TYPEOFARG flag to that +** opcode. +*/ +void sqlite3VdbeTypeofColumn(Vdbe *p, int iDest){ + VdbeOp *pOp = sqlite3VdbeGetLastOp(p); + if( pOp->p3==iDest && pOp->opcode==OP_Column ){ + pOp->p5 |= OPFLAG_TYPEOFARG; + } +} + /* ** Change the P2 operand of instruction addr so that it points to ** the address of the next instruction to be coded. @@ -1167,7 +1195,7 @@ void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){ || p->aOp[addr].opcode==OP_FkIfZero ); assert( p->aOp[addr].p4type==0 ); #ifdef SQLITE_VDBE_COVERAGE - sqlite3VdbeGetOp(p,-1)->iSrcLine = 0; /* Erase VdbeCoverage() macros */ + sqlite3VdbeGetLastOp(p)->iSrcLine = 0; /* Erase VdbeCoverage() macros */ #endif p->nOp--; }else{ @@ -1181,8 +1209,9 @@ void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){ ** the FuncDef is not ephermal, then do nothing. */ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ + assert( db!=0 ); if( (pDef->funcFlags & SQLITE_FUNC_EPHEM)!=0 ){ - sqlite3DbFreeNN(db, pDef); + sqlite3DbNNFreeNN(db, pDef); } } @@ -1191,11 +1220,12 @@ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ */ static SQLITE_NOINLINE void freeP4Mem(sqlite3 *db, Mem *p){ if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } static SQLITE_NOINLINE void freeP4FuncCtx(sqlite3 *db, sqlite3_context *p){ + assert( db!=0 ); freeEphemeralFunction(db, p->pFunc); - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } static void freeP4(sqlite3 *db, int p4type, void *p4){ assert( db ); @@ -1208,7 +1238,7 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ case P4_INT64: case P4_DYNAMIC: case P4_INTARRAY: { - sqlite3DbFree(db, p4); + if( p4 ) sqlite3DbNNFreeNN(db, p4); break; } case P4_KEYINFO: { @@ -1247,6 +1277,7 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ */ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ assert( nOp>=0 ); + assert( db!=0 ); if( aOp ){ Op *pOp = &aOp[nOp-1]; while(1){ /* Exit via break */ @@ -1257,7 +1288,7 @@ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ if( pOp==aOp ) break; pOp--; } - sqlite3DbFreeNN(db, aOp); + sqlite3DbNNFreeNN(db, aOp); } } @@ -1426,7 +1457,7 @@ void sqlite3VdbeAppendP4(Vdbe *p, void *pP4, int n){ if( p->db->mallocFailed ){ freeP4(p->db, n, pP4); }else{ - assert( pP4!=0 ); + assert( pP4!=0 || n==P4_DYNAMIC ); assert( p->nOp>0 ); pOp = &p->aOp[p->nOp-1]; assert( pOp->p4type==P4_NOTUSED ); @@ -1488,13 +1519,13 @@ void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){ ** Set the value if the iSrcLine field for the previously coded instruction. */ void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){ - sqlite3VdbeGetOp(v,-1)->iSrcLine = iLine; + sqlite3VdbeGetLastOp(v)->iSrcLine = iLine; } #endif /* SQLITE_VDBE_COVERAGE */ /* -** Return the opcode for a given address. If the address is -1, then -** return the most recently inserted opcode. +** Return the opcode for a given address. The address must be non-negative. +** See sqlite3VdbeGetLastOp() to get the most recently added opcode. ** ** If a memory allocation error has occurred prior to the calling of this ** routine, then a pointer to a dummy VdbeOp will be returned. That opcode @@ -1510,9 +1541,6 @@ VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ ** zeros, which is correct. MSVC generates a warning, nevertheless. */ static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */ assert( p->eVdbeState==VDBE_INIT_STATE ); - if( addr<0 ){ - addr = p->nOp - 1; - } assert( (addr>=0 && addrnOp) || p->db->mallocFailed ); if( p->db->mallocFailed ){ return (VdbeOp*)&dummy; @@ -1521,6 +1549,12 @@ VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ } } +/* Return the most recently added opcode +*/ +VdbeOp * sqlite3VdbeGetLastOp(Vdbe *p){ + return sqlite3VdbeGetOp(p, p->nOp - 1); +} + #if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) /* ** Return an integer value for one of the parameters to the opcode pOp @@ -2008,7 +2042,7 @@ static void releaseMemArray(Mem *p, int N){ sqlite3VdbeMemRelease(p); p->flags = MEM_Undefined; }else if( p->szMalloc ){ - sqlite3DbFreeNN(db, p->zMalloc); + sqlite3DbNNFreeNN(db, p->zMalloc); p->szMalloc = 0; p->flags = MEM_Undefined; } @@ -3000,7 +3034,7 @@ static void checkActiveVdbeCnt(sqlite3 *db){ if( p->readOnly==0 ) nWrite++; if( p->bIsReader ) nRead++; } - p = p->pNext; + p = p->pVNext; } assert( cnt==db->nVdbeActive ); assert( nWrite==db->nVdbeWrite ); @@ -3529,10 +3563,11 @@ void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, int mask){ */ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; + assert( db!=0 ); assert( p->db==0 || p->db==db ); if( p->aColName ){ releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); - sqlite3DbFreeNN(db, p->aColName); + sqlite3DbNNFreeNN(db, p->aColName); } for(pSub=p->pProgram; pSub; pSub=pNext){ pNext = pSub->pNext; @@ -3541,11 +3576,11 @@ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ } if( p->eVdbeState!=VDBE_INIT_STATE ){ releaseMemArray(p->aVar, p->nVar); - if( p->pVList ) sqlite3DbFreeNN(db, p->pVList); - if( p->pFree ) sqlite3DbFreeNN(db, p->pFree); + if( p->pVList ) sqlite3DbNNFreeNN(db, p->pVList); + if( p->pFree ) sqlite3DbNNFreeNN(db, p->pFree); } vdbeFreeOpArray(db, p->aOp, p->nOp); - sqlite3DbFree(db, p->zSql); + if( p->zSql ) sqlite3DbNNFreeNN(db, p->zSql); #ifdef SQLITE_ENABLE_NORMALIZE sqlite3DbFree(db, p->zNormSql); { @@ -3575,20 +3610,17 @@ void sqlite3VdbeDelete(Vdbe *p){ assert( p!=0 ); db = p->db; + assert( db!=0 ); assert( sqlite3_mutex_held(db->mutex) ); sqlite3VdbeClearObject(db, p); if( db->pnBytesFreed==0 ){ - if( p->pPrev ){ - p->pPrev->pNext = p->pNext; - }else{ - assert( db->pVdbe==p ); - db->pVdbe = p->pNext; - } - if( p->pNext ){ - p->pNext->pPrev = p->pPrev; + assert( p->ppVPrev!=0 ); + *p->ppVPrev = p->pVNext; + if( p->pVNext ){ + p->pVNext->ppVPrev = p->ppVPrev; } } - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } /* @@ -4543,7 +4575,7 @@ int sqlite3VdbeRecordCompareWithSkip( assert( pPKey2->pKeyInfo->aSortFlags!=0 ); assert( pPKey2->pKeyInfo->nKeyField>0 ); assert( idx1<=szHdr1 || CORRUPT_DB ); - do{ + while( 1 /*exit-by-break*/ ){ u32 serial_type; /* RHS is an integer */ @@ -4553,7 +4585,7 @@ int sqlite3VdbeRecordCompareWithSkip( serial_type = aKey1[idx1]; testcase( serial_type==12 ); if( serial_type>=10 ){ - rc = +1; + rc = serial_type==10 ? -1 : +1; }else if( serial_type==0 ){ rc = -1; }else if( serial_type==7 ){ @@ -4578,7 +4610,7 @@ int sqlite3VdbeRecordCompareWithSkip( ** numbers). Types 10 and 11 are currently "reserved for future ** use", so it doesn't really matter what the results of comparing ** them to numberic values are. */ - rc = +1; + rc = serial_type==10 ? -1 : +1; }else if( serial_type==0 ){ rc = -1; }else{ @@ -4659,7 +4691,7 @@ int sqlite3VdbeRecordCompareWithSkip( /* RHS is null */ else{ serial_type = aKey1[idx1]; - rc = (serial_type!=0); + rc = (serial_type!=0 && serial_type!=10); } if( rc!=0 ){ @@ -4681,8 +4713,13 @@ int sqlite3VdbeRecordCompareWithSkip( if( i==pPKey2->nField ) break; pRhs++; d1 += sqlite3VdbeSerialTypeLen(serial_type); + if( d1>(unsigned)nKey1 ) break; idx1 += sqlite3VarintLen(serial_type); - }while( idx1<(unsigned)szHdr1 && d1<=(unsigned)nKey1 ); + if( idx1>=(unsigned)szHdr1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corrupt index */ + } + } /* No memory allocation is ever used on mem1. Prove this using ** the following assert(). If the assert() fails, it indicates a @@ -5083,7 +5120,7 @@ void sqlite3VdbeCountChanges(Vdbe *v){ */ void sqlite3ExpirePreparedStatements(sqlite3 *db, int iCode){ Vdbe *p; - for(p = db->pVdbe; p; p=p->pNext){ + for(p = db->pVdbe; p; p=p->pVNext){ p->expired = iCode+1; } } @@ -5204,13 +5241,14 @@ void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){ ** the vdbeUnpackRecord() function found in vdbeapi.c. */ static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ + assert( db!=0 ); if( p ){ int i; for(i=0; iaMem[i]; if( pMem->zMalloc ) sqlite3VdbeMemReleaseMalloc(pMem); } - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -5281,7 +5319,7 @@ void sqlite3VdbePreUpdateHook( for(i=0; inField; i++){ sqlite3VdbeMemRelease(&preupdate.aNew[i]); } - sqlite3DbFreeNN(db, preupdate.aNew); + sqlite3DbNNFreeNN(db, preupdate.aNew); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ diff --git a/src/vdbemem.c b/src/vdbemem.c index fd4ab0ccc..f14599def 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -749,6 +749,16 @@ int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ && i >= -2251799813685248LL && i < 2251799813685248LL); } +/* Convert a floating point value to its closest integer. Do so in +** a way that avoids 'outside the range of representable values' warnings +** from UBSAN. +*/ +i64 sqlite3RealToI64(double r){ + if( r<=(double)SMALLEST_INT64 ) return SMALLEST_INT64; + if( r>=(double)LARGEST_INT64) return LARGEST_INT64; + return (i64)r; +} + /* ** Convert pMem so that it has type MEM_Real or MEM_Int. ** Invalidate any prior representations. @@ -770,7 +780,7 @@ int sqlite3VdbeMemNumerify(Mem *pMem){ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); if( ((rc==0 || rc==1) && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1) - || sqlite3RealSameAsInt(pMem->u.r, (ix = (i64)pMem->u.r)) + || sqlite3RealSameAsInt(pMem->u.r, (ix = sqlite3RealToI64(pMem->u.r))) ){ pMem->u.i = ix; MemSetTypeFlag(pMem, MEM_Int); @@ -822,6 +832,7 @@ int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal|MEM_Blob|MEM_Zero); + if( encoding!=SQLITE_UTF8 ) pMem->n &= ~1; return sqlite3VdbeChangeEncoding(pMem, encoding); } } @@ -1957,6 +1968,9 @@ int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){ if( (p->flags & MEM_Str)!=0 && pVal->enc==enc ){ return p->n; } + if( (p->flags & MEM_Str)!=0 && enc!=SQLITE_UTF8 && pVal->enc!=SQLITE_UTF8 ){ + return p->n; + } if( (p->flags & MEM_Blob)!=0 ){ if( p->flags & MEM_Zero ){ return p->n + p->u.nZero; diff --git a/src/vtab.c b/src/vtab.c index b50ccd24a..3814b0355 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -340,7 +340,8 @@ void sqlite3VtabUnlockList(sqlite3 *db){ */ void sqlite3VtabClear(sqlite3 *db, Table *p){ assert( IsVirtual(p) ); - if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p); + assert( db!=0 ); + if( db->pnBytesFreed==0 ) vtabDisconnectAll(0, p); if( p->u.vtab.azArg ){ int i; for(i=0; iu.vtab.nArg; i++){ @@ -1140,7 +1141,7 @@ FuncDef *sqlite3VtabOverloadFunction( if( pExpr->op!=TK_COLUMN ) return pDef; assert( ExprUseYTab(pExpr) ); pTab = pExpr->y.pTab; - if( pTab==0 ) return pDef; + if( NEVER(pTab==0) ) return pDef; if( !IsVirtual(pTab) ) return pDef; pVtab = sqlite3GetVTable(db, pTab)->pVtab; assert( pVtab!=0 ); diff --git a/src/where.c b/src/where.c index 6c2c64d18..0c083484f 100644 --- a/src/where.c +++ b/src/where.c @@ -67,7 +67,7 @@ int sqlite3WhereIsDistinct(WhereInfo *pWInfo){ ** block sorting is required. */ int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ - return pWInfo->nOBSat; + return pWInfo->nOBSat<0 ? 0 : pWInfo->nOBSat; } /* @@ -743,6 +743,43 @@ static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ #define whereTraceIndexInfoOutputs(A) #endif +/* +** We know that pSrc is an operand of an outer join. Return true if +** pTerm is a constraint that is compatible with that join. +** +** pTerm must be EP_OuterON if pSrc is the right operand of an +** outer join. pTerm can be either EP_OuterON or EP_InnerON if pSrc +** is the left operand of a RIGHT join. +** +** See https://sqlite.org/forum/forumpost/206d99a16dd9212f +** for an example of a WHERE clause constraints that may not be used on +** the right table of a RIGHT JOIN because the constraint implies a +** not-NULL condition on the left table of the RIGHT JOIN. +*/ +static int constraintCompatibleWithOuterJoin( + const WhereTerm *pTerm, /* WHERE clause term to check */ + const SrcItem *pSrc /* Table we are trying to access */ +){ + assert( (pSrc->fg.jointype&(JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ); /* By caller */ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + return 0; + } + if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0 + && ExprHasProperty(pTerm->pExpr, EP_InnerON) + ){ + return 0; + } + return 1; +} + + + #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* ** Return TRUE if the WHERE clause term pTerm is of a form where it @@ -758,16 +795,10 @@ static int termCanDriveIndex( if( pTerm->leftCursor!=pSrc->iCursor ) return 0; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0; assert( (pSrc->fg.jointype & JT_RIGHT)==0 ); - if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); - testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) - testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); - if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) - || pTerm->pExpr->w.iJoin != pSrc->iCursor - ){ - return 0; /* See tag-20191211-001 */ - } + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) + ){ + return 0; /* See https://sqlite.org/forum/forumpost/51e6959f61 */ } if( (pTerm->prereqRight & notReady)!=0 ) return 0; assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); @@ -1179,22 +1210,10 @@ static sqlite3_index_info *allocateIndexInfo( assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pTerm->u.x.leftColumn>=XN_ROWID ); assert( pTerm->u.x.leftColumnnCol ); - - /* tag-20191211-002: WHERE-clause constraints are not useful to the - ** right-hand table of a LEFT JOIN nor to the either table of a - ** RIGHT JOIN. See tag-20191211-001 for the - ** equivalent restriction for ordinary tables. */ - if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT ); - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); - testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ); - testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); - if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) - || pTerm->pExpr->w.iJoin != pSrc->iCursor - ){ - continue; - } + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) + ){ + continue; } nTerm++; pTerm->wtFlags |= TERM_OK; @@ -1567,7 +1586,7 @@ static int whereKeyStats( ** is larger than all samples in the array. */ tRowcnt iUpper, iGap; if( i>=pIdx->nSample ){ - iUpper = sqlite3LogEstToInt(pIdx->aiRowLogEst[0]); + iUpper = pIdx->nRowEst0; }else{ iUpper = aSample[i].anLt[iCol]; } @@ -2196,12 +2215,18 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){ } /* -** Deallocate internal memory used by a WhereLoop object +** Deallocate internal memory used by a WhereLoop object. Leave the +** object in an initialized state, as if it had been newly allocated. */ static void whereLoopClear(sqlite3 *db, WhereLoop *p){ - if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFreeNN(db, p->aLTerm); + if( p->aLTerm!=p->aLTermSpace ){ + sqlite3DbFreeNN(db, p->aLTerm); + p->aLTerm = p->aLTermSpace; + p->nLSlot = ArraySize(p->aLTermSpace); + } whereLoopClearUnion(db, p); - whereLoopInit(p); + p->nLTerm = 0; + p->wsFlags = 0; } /* @@ -2225,7 +2250,9 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ */ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ whereLoopClearUnion(db, pTo); - if( whereLoopResize(db, pTo, pFrom->nLTerm) ){ + if( pFrom->nLTerm > pTo->nLSlot + && whereLoopResize(db, pTo, pFrom->nLTerm) + ){ memset(pTo, 0, WHERE_LOOP_XFER_SZ); return SQLITE_NOMEM_BKPT; } @@ -2243,8 +2270,9 @@ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ ** Delete a WhereLoop object */ static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ + assert( db!=0 ); whereLoopClear(db, p); - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } /* @@ -2252,30 +2280,19 @@ static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ */ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ assert( pWInfo!=0 ); + assert( db!=0 ); sqlite3WhereClauseClear(&pWInfo->sWC); while( pWInfo->pLoops ){ WhereLoop *p = pWInfo->pLoops; pWInfo->pLoops = p->pNextLoop; whereLoopDelete(db, p); } - assert( pWInfo->pExprMods==0 ); while( pWInfo->pMemToFree ){ WhereMemBlock *pNext = pWInfo->pMemToFree->pNext; - sqlite3DbFreeNN(db, pWInfo->pMemToFree); + sqlite3DbNNFreeNN(db, pWInfo->pMemToFree); pWInfo->pMemToFree = pNext; } - sqlite3DbFreeNN(db, pWInfo); -} - -/* Undo all Expr node modifications -*/ -static void whereUndoExprMods(WhereInfo *pWInfo){ - while( pWInfo->pExprMods ){ - WhereExprMod *p = pWInfo->pExprMods; - pWInfo->pExprMods = p->pNext; - memcpy(p->pExpr, &p->orig, sizeof(p->orig)); - sqlite3DbFree(pWInfo->pParse->db, p); - } + sqlite3DbNNFreeNN(db, pWInfo); } /* @@ -2842,32 +2859,11 @@ static int whereLoopAddBtreeIndex( ** to mix with a lower range bound from some other source */ if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue; - /* tag-20191211-001: Do not allow constraints from the WHERE clause to - ** be used by the right table of a LEFT JOIN nor by the left table of a - ** RIGHT JOIN. Only constraints in the ON clause are allowed. - ** See tag-20191211-002 for the vtab equivalent. - ** - ** 2022-06-06: See https://sqlite.org/forum/forumpost/206d99a16dd9212f - ** for an example of a WHERE clause constraints that may not be used on - ** the right table of a RIGHT JOIN because the constraint implies a - ** not-NULL condition on the left table of the RIGHT JOIN. - ** - ** 2022-06-10: The same condition applies to termCanDriveIndex() above. - ** https://sqlite.org/forum/forumpost/51e6959f61 - */ - if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT ); - testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); - testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) - testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); - if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) - || pTerm->pExpr->w.iJoin != pSrc->iCursor - ){ - continue; - } + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) + ){ + continue; } - if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){ pBuilder->bldFlags1 |= SQLITE_BLDF1_UNIQUE; }else{ @@ -2878,7 +2874,11 @@ static int whereLoopAddBtreeIndex( pNew->u.btree.nBtm = saved_nBtm; pNew->u.btree.nTop = saved_nTop; pNew->nLTerm = saved_nLTerm; - if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ + if( pNew->nLTerm>=pNew->nLSlot + && whereLoopResize(db, pNew, pNew->nLTerm+1) + ){ + break; /* OOM while trying to enlarge the pNew->aLTerm array */ + } pNew->aLTerm[pNew->nLTerm++] = pTerm; pNew->prereq = (saved_prereq | pTerm->prereqRight) & ~pNew->maskSelf; @@ -2971,38 +2971,39 @@ static int whereLoopAddBtreeIndex( if( scan.iEquiv>1 ) pNew->wsFlags |= WHERE_TRANSCONS; }else if( eOp & WO_ISNULL ){ pNew->wsFlags |= WHERE_COLUMN_NULL; - }else if( eOp & (WO_GT|WO_GE) ){ - testcase( eOp & WO_GT ); - testcase( eOp & WO_GE ); - pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; - pNew->u.btree.nBtm = whereRangeVectorLen( - pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm - ); - pBtm = pTerm; - pTop = 0; - if( pTerm->wtFlags & TERM_LIKEOPT ){ - /* Range constraints that come from the LIKE optimization are - ** always used in pairs. */ - pTop = &pTerm[1]; - assert( (pTop-(pTerm->pWC->a))pWC->nTerm ); - assert( pTop->wtFlags & TERM_LIKEOPT ); - assert( pTop->eOperator==WO_LT ); - if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ - pNew->aLTerm[pNew->nLTerm++] = pTop; - pNew->wsFlags |= WHERE_TOP_LIMIT; - pNew->u.btree.nTop = 1; - } }else{ - assert( eOp & (WO_LT|WO_LE) ); - testcase( eOp & WO_LT ); - testcase( eOp & WO_LE ); - pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; - pNew->u.btree.nTop = whereRangeVectorLen( + int nVecLen = whereRangeVectorLen( pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm ); - pTop = pTerm; - pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? - pNew->aLTerm[pNew->nLTerm-2] : 0; + if( eOp & (WO_GT|WO_GE) ){ + testcase( eOp & WO_GT ); + testcase( eOp & WO_GE ); + pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; + pNew->u.btree.nBtm = nVecLen; + pBtm = pTerm; + pTop = 0; + if( pTerm->wtFlags & TERM_LIKEOPT ){ + /* Range constraints that come from the LIKE optimization are + ** always used in pairs. */ + pTop = &pTerm[1]; + assert( (pTop-(pTerm->pWC->a))pWC->nTerm ); + assert( pTop->wtFlags & TERM_LIKEOPT ); + assert( pTop->eOperator==WO_LT ); + if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ + pNew->aLTerm[pNew->nLTerm++] = pTop; + pNew->wsFlags |= WHERE_TOP_LIMIT; + pNew->u.btree.nTop = 1; + } + }else{ + assert( eOp & (WO_LT|WO_LE) ); + testcase( eOp & WO_LT ); + testcase( eOp & WO_LE ); + pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; + pNew->u.btree.nTop = nVecLen; + pTop = pTerm; + pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? + pNew->aLTerm[pNew->nLTerm-2] : 0; + } } /* At this point pNew->nOut is set to the number of rows expected to @@ -3246,6 +3247,94 @@ static int whereUsablePartialIndex( return 0; } +/* +** Structure passed to the whereIsCoveringIndex Walker callback. +*/ +struct CoveringIndexCheck { + Index *pIdx; /* The index */ + int iTabCur; /* Cursor number for the corresponding table */ +}; + +/* +** Information passed in is pWalk->u.pCovIdxCk. Call is pCk. +** +** If the Expr node references the table with cursor pCk->iTabCur, then +** make sure that column is covered by the index pCk->pIdx. We know that +** all columns less than 63 (really BMS-1) are covered, so we don't need +** to check them. But we do need to check any column at 63 or greater. +** +** If the index does not cover the column, then set pWalk->eCode to +** non-zero and return WRC_Abort to stop the search. +** +** If this node does not disprove that the index can be a covering index, +** then just return WRC_Continue, to continue the search. +*/ +static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){ + int i; /* Loop counter */ + const Index *pIdx; /* The index of interest */ + const i16 *aiColumn; /* Columns contained in the index */ + u16 nColumn; /* Number of columns in the index */ + if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_AGG_COLUMN ) return WRC_Continue; + if( pExpr->iColumn<(BMS-1) ) return WRC_Continue; + if( pExpr->iTable!=pWalk->u.pCovIdxCk->iTabCur ) return WRC_Continue; + pIdx = pWalk->u.pCovIdxCk->pIdx; + aiColumn = pIdx->aiColumn; + nColumn = pIdx->nColumn; + for(i=0; iiColumn ) return WRC_Continue; + } + pWalk->eCode = 1; + return WRC_Abort; +} + + +/* +** pIdx is an index that covers all of the low-number columns used by +** pWInfo->pSelect (columns from 0 through 62). But there are columns +** in pWInfo->pSelect beyond 62. This routine tries to answer the question +** of whether pIdx covers *all* columns in the query. +** +** Return 0 if pIdx is a covering index. Return non-zero if pIdx is +** not a covering index or if we are unable to determine if pIdx is a +** covering index. +** +** This routine is an optimization. It is always safe to return non-zero. +** But returning zero when non-zero should have been returned can lead to +** incorrect bytecode and assertion faults. +*/ +static SQLITE_NOINLINE u32 whereIsCoveringIndex( + WhereInfo *pWInfo, /* The WHERE clause context */ + Index *pIdx, /* Index that is being tested */ + int iTabCur /* Cursor for the table being indexed */ +){ + int i; + struct CoveringIndexCheck ck; + Walker w; + if( pWInfo->pSelect==0 ){ + /* We don't have access to the full query, so we cannot check to see + ** if pIdx is covering. Assume it is not. */ + return 1; + } + for(i=0; inColumn; i++){ + if( pIdx->aiColumn[i]>=BMS-1 ) break; + } + if( i>=pIdx->nColumn ){ + /* pIdx does not index any columns greater than 62, but we know from + ** colMask that columns greater than 62 are used, so this is not a + ** covering index */ + return 1; + } + ck.pIdx = pIdx; + ck.iTabCur = iTabCur; + memset(&w, 0, sizeof(w)); + w.xExprCallback = whereIsCoveringIndexWalkCallback; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.u.pCovIdxCk = &ck; + w.eCode = 0; + sqlite3WalkSelect(&w, pWInfo->pSelect); + return w.eCode; +} + /* ** Add all WhereLoop objects for a single table of the join where the table ** is identified by pBuilder->pNew->iTab. That table is guaranteed to be @@ -3448,6 +3537,9 @@ static int whereLoopAddBtree( #else pNew->rRun = rSize + 16; #endif + if( IsView(pTab) || (pTab->tabFlags & TF_Ephemeral)!=0 ){ + pNew->wsFlags |= WHERE_VIEWSCAN; + } ApplyCostMultiplier(pNew->rRun, pTab->costMult); whereLoopOutputAdjust(pWC, pNew, rSize); rc = whereLoopInsert(pBuilder, pNew); @@ -3460,6 +3552,9 @@ static int whereLoopAddBtree( m = 0; }else{ m = pSrc->colUsed & pProbe->colNotIdxed; + if( m==TOPBIT ){ + m = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor); + } pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; } @@ -4174,7 +4269,13 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ /* Loop over the tables in the join, from left to right */ pNew = pBuilder->pNew; - whereLoopInit(pNew); + + /* Verify that pNew has already been initialized */ + assert( pNew->nLTerm==0 ); + assert( pNew->wsFlags==0 ); + assert( pNew->nLSlot>=ArraySize(pNew->aLTermSpace) ); + assert( pNew->aLTerm!=0 ); + pBuilder->iPlanLimit = SQLITE_QUERY_PLANNER_LIMIT; for(iTab=0, pItem=pTabList->a; pItempParse; - db = pParse->db; nLoop = pWInfo->nLevel; /* TUNING: For simple queries, only the best path is tracked. ** For 2-way joins, the 5 best paths are followed. @@ -4721,7 +4820,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ /* Allocate and initialize space for aTo, aFrom and aSortCost[] */ nSpace = (sizeof(WherePath)+sizeof(WhereLoop*)*nLoop)*mxChoice*2; nSpace += sizeof(LogEst) * nOrderBy; - pSpace = sqlite3DbMallocRawNN(db, nSpace); + pSpace = sqlite3StackAllocRawNN(pParse->db, nSpace); if( pSpace==0 ) return SQLITE_NOMEM_BKPT; aTo = (WherePath*)pSpace; aFrom = aTo+mxChoice; @@ -4771,9 +4870,9 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ LogEst nOut; /* Rows visited by (pFrom+pWLoop) */ LogEst rCost; /* Cost of path (pFrom+pWLoop) */ LogEst rUnsorted; /* Unsorted cost of (pFrom+pWLoop) */ - i8 isOrdered = pFrom->isOrdered; /* isOrdered for (pFrom+pWLoop) */ + i8 isOrdered; /* isOrdered for (pFrom+pWLoop) */ Bitmask maskNew; /* Mask of src visited by (..) */ - Bitmask revMask = 0; /* Mask of rev-order loops for (..) */ + Bitmask revMask; /* Mask of rev-order loops for (..) */ if( (pWLoop->prereq & ~pFrom->maskLoop)!=0 ) continue; if( (pWLoop->maskSelf & pFrom->maskLoop)!=0 ) continue; @@ -4792,7 +4891,9 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted); nOut = pFrom->nRow + pWLoop->nOut; maskNew = pFrom->maskLoop | pWLoop->maskSelf; + isOrdered = pFrom->isOrdered; if( isOrdered<0 ){ + revMask = 0; isOrdered = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, pWInfo->wctrlFlags, iLoop, pWLoop, &revMask); @@ -4820,6 +4921,13 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */ } + /* TUNING: A full-scan of a VIEW or subquery in the outer loop + ** is not so bad. */ + if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 ){ + rCost += -10; + nOut += -30; + } + /* Check to see if pWLoop should be added to the set of ** mxChoice best-so-far paths. ** @@ -4970,7 +5078,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ if( nFrom==0 ){ sqlite3ErrorMsg(pParse, "no query solution"); - sqlite3DbFreeNN(db, pSpace); + sqlite3StackFreeNN(pParse->db, pSpace); return SQLITE_ERROR; } @@ -5052,7 +5160,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ pWInfo->nRowOut = pFrom->nRow; /* Free temporary memory and return success */ - sqlite3DbFreeNN(db, pSpace); + sqlite3StackFreeNN(pParse->db, pSpace); return SQLITE_OK; } @@ -5351,6 +5459,77 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( } } +/* +** This is an sqlite3ParserAddCleanup() callback that is invoked to +** free the Parse->pIdxExpr list when the Parse object is destroyed. +*/ +static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ + Parse *pParse = (Parse*)pObject; + while( pParse->pIdxExpr!=0 ){ + IndexedExpr *p = pParse->pIdxExpr; + pParse->pIdxExpr = p->pIENext; + sqlite3ExprDelete(db, p->pExpr); + sqlite3DbFreeNN(db, p); + } +} + +/* +** The index pIdx is used by a query and contains one or more expressions. +** In other words pIdx is an index on an expression. iIdxCur is the cursor +** number for the index and iDataCur is the cursor number for the corresponding +** table. +** +** This routine adds IndexedExpr entries to the Parse->pIdxExpr field for +** each of the expressions in the index so that the expression code generator +** will know to replace occurrences of the indexed expression with +** references to the corresponding column of the index. +*/ +static SQLITE_NOINLINE void whereAddIndexedExpr( + Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxExpr */ + Index *pIdx, /* The index-on-expression that contains the expressions */ + int iIdxCur, /* Cursor number for pIdx */ + SrcItem *pTabItem /* The FROM clause entry for the table */ +){ + int i; + IndexedExpr *p; + Table *pTab; + assert( pIdx->bHasExpr ); + pTab = pIdx->pTable; + for(i=0; inColumn; i++){ + Expr *pExpr; + int j = pIdx->aiColumn[i]; + int bMaybeNullRow; + if( j==XN_EXPR ){ + pExpr = pIdx->aColExpr->a[i].pExpr; + testcase( pTabItem->fg.jointype & JT_LEFT ); + testcase( pTabItem->fg.jointype & JT_RIGHT ); + testcase( pTabItem->fg.jointype & JT_LTORJ ); + bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; + }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){ + pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); + bMaybeNullRow = 0; + }else{ + continue; + } + if( sqlite3ExprIsConstant(pExpr) ) continue; + p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); + if( p==0 ) break; + p->pIENext = pParse->pIdxExpr; + p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); + p->iDataCur = pTabItem->iCursor; + p->iIdxCur = iIdxCur; + p->iIdxCol = i; + p->bMaybeNullRow = bMaybeNullRow; +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + p->zIdxName = pIdx->zName; +#endif + pParse->pIdxExpr = p; + if( p->pIENext==0 ){ + sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse); + } + } +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -5445,7 +5624,7 @@ WhereInfo *sqlite3WhereBegin( Expr *pWhere, /* The WHERE clause */ ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */ ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */ - Select *pLimit, /* Use this LIMIT/OFFSET clause, if any */ + Select *pSelect, /* The entire SELECT statement */ u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */ int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number ** If WHERE_USE_LIMIT, then the limit amount */ @@ -5514,7 +5693,9 @@ WhereInfo *sqlite3WhereBegin( pWInfo->pParse = pParse; pWInfo->pTabList = pTabList; pWInfo->pOrderBy = pOrderBy; +#if WHERETRACE_ENABLED pWInfo->pWhere = pWhere; +#endif pWInfo->pResultSet = pResultSet; pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; pWInfo->nLevel = nTabList; @@ -5522,9 +5703,7 @@ WhereInfo *sqlite3WhereBegin( pWInfo->wctrlFlags = wctrlFlags; pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; -#ifndef SQLITE_OMIT_VIRTUALTABLE - pWInfo->pLimit = pLimit; -#endif + pWInfo->pSelect = pSelect; memset(&pWInfo->nOBSat, 0, offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); @@ -5593,7 +5772,9 @@ WhereInfo *sqlite3WhereBegin( /* Analyze all of the subexpressions. */ sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC); - sqlite3WhereAddLimit(&pWInfo->sWC, pLimit); + if( pSelect && pSelect->pLimit ){ + sqlite3WhereAddLimit(&pWInfo->sWC, pSelect); + } if( pParse->nErr ) goto whereBeginError; /* Special case: WHERE terms that do not refer to any tables in the join @@ -5896,6 +6077,9 @@ WhereInfo *sqlite3WhereBegin( op = OP_ReopenIdx; }else{ iIndexCur = pParse->nTab++; + if( pIx->bHasExpr && OptimizationEnabled(db, SQLITE_IndexedExpr) ){ + whereAddIndexedExpr(pParse, pIx, iIndexCur, pTabItem); + } } pLevel->iIdxCur = iIndexCur; assert( pIx!=0 ); @@ -6018,8 +6202,6 @@ WhereInfo *sqlite3WhereBegin( /* Jump here if malloc fails */ whereBeginError: if( pWInfo ){ - testcase( pWInfo->pExprMods!=0 ); - whereUndoExprMods(pWInfo); pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); } @@ -6238,7 +6420,6 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } assert( pWInfo->nLevel<=pTabList->nSrc ); - if( pWInfo->pExprMods ) whereUndoExprMods(pWInfo); for(i=0, pLevel=pWInfo->a; inLevel; i++, pLevel++){ int k, last; VdbeOp *pOp, *pLastOp; @@ -6292,6 +6473,16 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ }else{ last = pWInfo->iEndWhere; } + if( pIdx->bHasExpr ){ + IndexedExpr *p = pParse->pIdxExpr; + while( p ){ + if( p->iIdxCur==pLevel->iIdxCur ){ + p->iDataCur = -1; + p->iIdxCur = -1; + } + p = p->pIENext; + } + } k = pLevel->addrBody + 1; #ifdef SQLITE_DEBUG if( db->flags & SQLITE_VdbeAddopTrace ){ diff --git a/src/whereInt.h b/src/whereInt.h index bc96ff7e6..28ede5be6 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -378,7 +378,7 @@ struct WhereAndInfo { ** between VDBE cursor numbers and bits of the bitmasks in WhereTerm. ** ** The VDBE cursor numbers are small integers contained in -** SrcList_item.iCursor and Expr.iTable fields. For any given WHERE +** SrcItem.iCursor and Expr.iTable fields. For any given WHERE ** clause, the cursor numbers might not begin with 0 and they might ** contain gaps in the numbering sequence. But we want to make maximum ** use of the bits in our bitmasks. This structure provides a mapping @@ -449,20 +449,6 @@ struct WhereLoopBuilder { # define SQLITE_QUERY_PLANNER_LIMIT_INCR 1000 #endif -/* -** Each instance of this object records a change to a single node -** in an expression tree to cause that node to point to a column -** of an index rather than an expression or a virtual column. All -** such transformations need to be undone at the end of WHERE clause -** processing. -*/ -typedef struct WhereExprMod WhereExprMod; -struct WhereExprMod { - WhereExprMod *pNext; /* Next translation on a list of them all */ - Expr *pExpr; /* The Expr node that was transformed */ - Expr orig; /* Original value of the Expr node */ -}; - /* ** The WHERE clause processing routine has two halves. The ** first part does the start of the WHERE loop and the second @@ -478,10 +464,10 @@ struct WhereInfo { SrcList *pTabList; /* List of tables in the join */ ExprList *pOrderBy; /* The ORDER BY clause or NULL */ ExprList *pResultSet; /* Result set of the query */ +#if WHERETRACE_ENABLED Expr *pWhere; /* The complete WHERE clause */ -#ifndef SQLITE_OMIT_VIRTUALTABLE - Select *pLimit; /* Used to access LIMIT expr/registers for vtabs */ #endif + Select *pSelect; /* The entire SELECT statement containing WHERE */ int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ int iContinue; /* Jump here to continue with next record */ int iBreak; /* Jump here to break out of the loop */ @@ -500,7 +486,6 @@ struct WhereInfo { int iTop; /* The very beginning of the WHERE loop */ int iEndWhere; /* End of the WHERE clause itself */ WhereLoop *pLoops; /* List of all WhereLoop objects */ - WhereExprMod *pExprMods; /* Expression modifications */ WhereMemBlock *pMemToFree;/* Memory to free when this object destroyed */ Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ WhereClause sWC; /* Decomposition of the WHERE clause */ @@ -648,5 +633,6 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); #define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ #define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ #define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ +#define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */ #endif /* !defined(SQLITE_WHEREINT_H) */ diff --git a/src/wherecode.c b/src/wherecode.c index c582ec657..e36d1c996 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -893,7 +893,7 @@ static void whereLikeOptimizationStringFixup( if( pTerm->wtFlags & TERM_LIKEOPT ){ VdbeOp *pOp; assert( pLevel->iLikeRepCntr>0 ); - pOp = sqlite3VdbeGetOp(v, -1); + pOp = sqlite3VdbeGetLastOp(v); assert( pOp!=0 ); assert( pOp->opcode==OP_String8 || pTerm->pWC->pWInfo->pParse->db->mallocFailed ); @@ -1217,143 +1217,6 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ } } -/* An instance of the IdxExprTrans object carries information about a -** mapping from an expression on table columns into a column in an index -** down through the Walker. -*/ -typedef struct IdxExprTrans { - Expr *pIdxExpr; /* The index expression */ - int iTabCur; /* The cursor of the corresponding table */ - int iIdxCur; /* The cursor for the index */ - int iIdxCol; /* The column for the index */ - int iTabCol; /* The column for the table */ - WhereInfo *pWInfo; /* Complete WHERE clause information */ - sqlite3 *db; /* Database connection (for malloc()) */ -} IdxExprTrans; - -/* -** Preserve pExpr on the WhereETrans list of the WhereInfo. -*/ -static void preserveExpr(IdxExprTrans *pTrans, Expr *pExpr){ - WhereExprMod *pNew; - pNew = sqlite3DbMallocRaw(pTrans->db, sizeof(*pNew)); - if( pNew==0 ) return; - pNew->pNext = pTrans->pWInfo->pExprMods; - pTrans->pWInfo->pExprMods = pNew; - pNew->pExpr = pExpr; - memcpy(&pNew->orig, pExpr, sizeof(*pExpr)); -} - -/* The walker node callback used to transform matching expressions into -** a reference to an index column for an index on an expression. -** -** If pExpr matches, then transform it into a reference to the index column -** that contains the value of pExpr. -*/ -static int whereIndexExprTransNode(Walker *p, Expr *pExpr){ - IdxExprTrans *pX = p->u.pIdxTrans; - if( sqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){ - pExpr = sqlite3ExprSkipCollate(pExpr); - preserveExpr(pX, pExpr); - pExpr->affExpr = sqlite3ExprAffinity(pExpr); - pExpr->op = TK_COLUMN; - pExpr->iTable = pX->iIdxCur; - pExpr->iColumn = pX->iIdxCol; - testcase( ExprHasProperty(pExpr, EP_Unlikely) ); - ExprClearProperty(pExpr, EP_Skip|EP_Unlikely|EP_WinFunc|EP_Subrtn); - pExpr->y.pTab = 0; - return WRC_Prune; - }else{ - return WRC_Continue; - } -} - -#ifndef SQLITE_OMIT_GENERATED_COLUMNS -/* A walker node callback that translates a column reference to a table -** into a corresponding column reference of an index. -*/ -static int whereIndexExprTransColumn(Walker *p, Expr *pExpr){ - if( pExpr->op==TK_COLUMN ){ - IdxExprTrans *pX = p->u.pIdxTrans; - if( pExpr->iTable==pX->iTabCur && pExpr->iColumn==pX->iTabCol ){ - assert( ExprUseYTab(pExpr) && pExpr->y.pTab!=0 ); - preserveExpr(pX, pExpr); - pExpr->affExpr = sqlite3TableColumnAffinity(pExpr->y.pTab,pExpr->iColumn); - pExpr->iTable = pX->iIdxCur; - pExpr->iColumn = pX->iIdxCol; - pExpr->y.pTab = 0; - } - } - return WRC_Continue; -} -#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ - -/* -** For an indexes on expression X, locate every instance of expression X -** in pExpr and change that subexpression into a reference to the appropriate -** column of the index. -** -** 2019-10-24: Updated to also translate references to a VIRTUAL column in -** the table into references to the corresponding (stored) column of the -** index. -*/ -static void whereIndexExprTrans( - Index *pIdx, /* The Index */ - int iTabCur, /* Cursor of the table that is being indexed */ - int iIdxCur, /* Cursor of the index itself */ - WhereInfo *pWInfo /* Transform expressions in this WHERE clause */ -){ - int iIdxCol; /* Column number of the index */ - ExprList *aColExpr; /* Expressions that are indexed */ - Table *pTab; - Walker w; - IdxExprTrans x; - aColExpr = pIdx->aColExpr; - if( aColExpr==0 && !pIdx->bHasVCol ){ - /* The index does not reference any expressions or virtual columns - ** so no translations are needed. */ - return; - } - pTab = pIdx->pTable; - memset(&w, 0, sizeof(w)); - w.u.pIdxTrans = &x; - x.iTabCur = iTabCur; - x.iIdxCur = iIdxCur; - x.pWInfo = pWInfo; - x.db = pWInfo->pParse->db; - for(iIdxCol=0; iIdxColnColumn; iIdxCol++){ - i16 iRef = pIdx->aiColumn[iIdxCol]; - if( iRef==XN_EXPR ){ - assert( aColExpr!=0 && aColExpr->a[iIdxCol].pExpr!=0 ); - x.pIdxExpr = aColExpr->a[iIdxCol].pExpr; - if( sqlite3ExprIsConstant(x.pIdxExpr) ) continue; - w.xExprCallback = whereIndexExprTransNode; -#ifndef SQLITE_OMIT_GENERATED_COLUMNS - }else if( iRef>=0 - && (pTab->aCol[iRef].colFlags & COLFLAG_VIRTUAL)!=0 - && ((pTab->aCol[iRef].colFlags & COLFLAG_HASCOLL)==0 - || sqlite3StrICmp(sqlite3ColumnColl(&pTab->aCol[iRef]), - sqlite3StrBINARY)==0) - ){ - /* Check to see if there are direct references to generated columns - ** that are contained in the index. Pulling the generated column - ** out of the index is an optimization only - the main table is always - ** available if the index cannot be used. To avoid unnecessary - ** complication, omit this optimization if the collating sequence for - ** the column is non-standard */ - x.iTabCol = iRef; - w.xExprCallback = whereIndexExprTransColumn; -#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ - }else{ - continue; - } - x.iIdxCol = iIdxCol; - sqlite3WalkExpr(&w, pWInfo->pWhere); - sqlite3WalkExprList(&w, pWInfo->pOrderBy); - sqlite3WalkExprList(&w, pWInfo->pResultSet); - } -} - /* ** The pTruth expression is always true because it is the WHERE clause ** a partial index that is driving a query loop. Look through all of the @@ -1422,6 +1285,8 @@ static SQLITE_NOINLINE void filterPullDown( testcase( pTerm->wtFlags & TERM_VIRTUAL ); regRowid = sqlite3GetTempReg(pParse); regRowid = codeEqualityTerm(pParse, pTerm, pLevel, 0, 0, regRowid); + sqlite3VdbeAddOp2(pParse->pVdbe, OP_MustBeInt, regRowid, addrNxt); + VdbeCoverage(pParse->pVdbe); sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, addrNxt, regRowid, 1); VdbeCoverage(pParse->pVdbe); @@ -1573,9 +1438,9 @@ Bitmask sqlite3WhereCodeOneLoopStart( && pLoop->u.vtab.bOmitOffset ){ assert( pTerm->eOperator==WO_AUX ); - assert( pWInfo->pLimit!=0 ); - assert( pWInfo->pLimit->iOffset>0 ); - sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pLimit->iOffset); + assert( pWInfo->pSelect!=0 ); + assert( pWInfo->pSelect->iOffset>0 ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pSelect->iOffset); VdbeComment((v,"Zero OFFSET counter")); } } @@ -1683,6 +1548,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg); addrNxt = pLevel->addrNxt; if( pLevel->regFilter ){ + sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrNxt); + VdbeCoverage(v); sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, iRowidReg, 1); VdbeCoverage(v); @@ -2034,6 +1901,11 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** guess. */ addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan, (pIdx->aiRowLogEst[0]+9)/10); + if( pRangeStart ){ + sqlite3VdbeChangeP5(v, 1); + sqlite3VdbeChangeP2(v, addrSeekScan, sqlite3VdbeCurrentAddr(v)+1); + addrSeekScan = 0; + } VdbeCoverage(v); } sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint); @@ -2109,8 +1981,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( } nConstraint++; } - sqlite3DbFree(db, zStartAff); - sqlite3DbFree(db, zEndAff); + if( zStartAff ) sqlite3DbNNFreeNN(db, zStartAff); + if( zEndAff ) sqlite3DbNNFreeNN(db, zEndAff); /* Top of the loop body */ if( pLevel->p2==0 ) pLevel->p2 = sqlite3VdbeCurrentAddr(v); @@ -2172,27 +2044,6 @@ Bitmask sqlite3WhereCodeOneLoopStart( } if( pLevel->iLeftJoin==0 ){ - /* If pIdx is an index on one or more expressions, then look through - ** all the expressions in pWInfo and try to transform matching expressions - ** into reference to index columns. Also attempt to translate references - ** to virtual columns in the table into references to (stored) columns - ** of the index. - ** - ** Do not do this for the RHS of a LEFT JOIN. This is because the - ** expression may be evaluated after OP_NullRow has been executed on - ** the cursor. In this case it is important to do the full evaluation, - ** as the result of the expression may not be NULL, even if all table - ** column values are. https://www.sqlite.org/src/info/7fa8049685b50b5a - ** - ** Also, do not do this when processing one index an a multi-index - ** OR clause, since the transformation will become invalid once we - ** move forward to the next index. - ** https://sqlite.org/src/info/4e8e4857d32d401f - */ - if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ){ - whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo); - } - /* If a partial index is driving the loop, try to eliminate WHERE clause ** terms from the query that must be true due to the WHERE clause of ** the partial index. @@ -2305,7 +2156,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( int nNotReady; /* The number of notReady tables */ SrcItem *origSrc; /* Original list of tables */ nNotReady = pWInfo->nLevel - iLevel - 1; - pOrTab = sqlite3StackAllocRaw(db, + pOrTab = sqlite3DbMallocRawNN(db, sizeof(*pOrTab)+ nNotReady*sizeof(pOrTab->a[0])); if( pOrTab==0 ) return notReady; pOrTab->nAlloc = (u8)(nNotReady + 1); @@ -2558,7 +2409,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( assert( pLevel->op==OP_Return ); pLevel->p2 = sqlite3VdbeCurrentAddr(v); - if( pWInfo->nLevel>1 ){ sqlite3StackFree(db, pOrTab); } + if( pWInfo->nLevel>1 ){ sqlite3DbFreeNN(db, pOrTab); } if( !untestedTerms ) disableTerm(pLevel, pTerm); }else #endif /* SQLITE_OMIT_OR_OPTIMIZATION */ diff --git a/src/whereexpr.c b/src/whereexpr.c index 8c9233ebd..24246c1aa 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -266,7 +266,7 @@ static int isLikeOrGlob( if( pLeft->op!=TK_COLUMN || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT || (ALWAYS( ExprUseYTab(pLeft) ) - && pLeft->y.pTab + && ALWAYS(pLeft->y.pTab) && IsVirtual(pLeft->y.pTab)) /* Might be numeric */ ){ int isNum; @@ -383,8 +383,7 @@ static int isAuxiliaryVtabOperator( ** MATCH(expression,vtab_column) */ pCol = pList->a[1].pExpr; - assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); - testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 ); + assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); if( ExprIsVtab(pCol) ){ for(i=0; ia[0].pExpr; assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); - testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 ); + assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); if( ExprIsVtab(pCol) ){ sqlite3_vtab *pVtab; sqlite3_module *pMod; @@ -434,13 +433,12 @@ static int isAuxiliaryVtabOperator( int res = 0; Expr *pLeft = pExpr->pLeft; Expr *pRight = pExpr->pRight; - assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); - testcase( pLeft->op==TK_COLUMN && pLeft->y.pTab==0 ); + assert( pLeft->op!=TK_COLUMN || (ExprUseYTab(pLeft) && pLeft->y.pTab!=0) ); if( ExprIsVtab(pLeft) ){ res++; } - assert( pRight==0 || pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); - testcase( pRight && pRight->op==TK_COLUMN && pRight->y.pTab==0 ); + assert( pRight==0 || pRight->op!=TK_COLUMN + || (ExprUseYTab(pRight) && pRight->y.pTab!=0) ); if( pRight && ExprIsVtab(pRight) ){ res++; SWAP(Expr*, pLeft, pRight); @@ -989,6 +987,7 @@ static SQLITE_NOINLINE int exprMightBeIndexed2( if( pIdx->aColExpr==0 ) continue; for(i=0; inKeyCol; i++){ if( pIdx->aiColumn[i]!=XN_EXPR ) continue; + assert( pIdx->bHasExpr ); if( sqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ aiCurCol[0] = iCur; aiCurCol[1] = XN_EXPR; @@ -1602,9 +1601,9 @@ static void whereAddLimitExpr( ** exist only so that they may be passed to the xBestIndex method of the ** single virtual table in the FROM clause of the SELECT. */ -void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ - assert( p==0 || (p->pGroupBy==0 && (p->selFlags & SF_Aggregate)==0) ); - if( (p && p->pLimit) /* 1 */ +void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ + assert( p!=0 && p->pLimit!=0 ); /* 1 -- checked by caller */ + if( p->pGroupBy==0 && (p->selFlags & (SF_Distinct|SF_Aggregate))==0 /* 2 */ && (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pTab)) /* 3 */ ){ diff --git a/src/window.c b/src/window.c index 893668664..f13ea8b02 100644 --- a/src/window.c +++ b/src/window.c @@ -900,7 +900,6 @@ static ExprList *exprListAppendList( for(i=0; inExpr; i++){ sqlite3 *db = pParse->db; Expr *pDup = sqlite3ExprDup(db, pAppend->a[i].pExpr, 0); - assert( pDup==0 || !ExprHasProperty(pDup, EP_MemToken) ); if( db->mallocFailed ){ sqlite3ExprDelete(db, pDup); break; @@ -2171,10 +2170,9 @@ static void windowCodeRangeTest( /* This block runs if reg1 is not NULL, but reg2 is. */ sqlite3VdbeJumpHere(v, addr); - sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); VdbeCoverage(v); - if( op==OP_Gt || op==OP_Ge ){ - sqlite3VdbeChangeP2(v, -1, addrDone); - } + sqlite3VdbeAddOp2(v, OP_IsNull, reg2, + (op==OP_Gt || op==OP_Ge) ? addrDone : lbl); + VdbeCoverage(v); } /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1). diff --git a/test/attachmalloc.test b/test/attachmalloc.test index 7a82f41f6..3c6a075b4 100644 --- a/test/attachmalloc.test +++ b/test/attachmalloc.test @@ -61,17 +61,18 @@ do_malloc_test attachmalloc-2 -tclprep { ATTACH 'test2.db' AS db1; } -set enable_shared_cache [sqlite3_enable_shared_cache 1] -sqlite3 dbaux test3.db -dbaux eval {SELECT * FROM sqlite_master} -do_malloc_test attachmalloc-3 -sqlbody { - SELECT * FROM sqlite_master; - ATTACH 'test3.db' AS three; -} -cleanup { - db eval { DETACH three } +ifcapable shared_cache { + set enable_shared_cache [sqlite3_enable_shared_cache 1] + sqlite3 dbaux test3.db + dbaux eval {SELECT * FROM sqlite_master} + do_malloc_test attachmalloc-3 -sqlbody { + SELECT * FROM sqlite_master; + ATTACH 'test3.db' AS three; + } -cleanup { + db eval { DETACH three } + } + dbaux close + sqlite3_enable_shared_cache $enable_shared_cache } -dbaux close -sqlite3_enable_shared_cache $enable_shared_cache - finish_test diff --git a/test/bigmmap.test b/test/bigmmap.test index 7802fd58e..ba9f84252 100644 --- a/test/bigmmap.test +++ b/test/bigmmap.test @@ -52,7 +52,11 @@ do_execsql_test 1.0 { } for {set i 1} {$i < 8} {incr i} { - fake_big_file [expr $i*1024] [get_pwd]/test.db + if {[catch {fake_big_file [expr $i*1024] [get_pwd]/test.db}]} { + puts "Cannot create ${i}MB sparse file" + finish_test + return + } hexio_write test.db 28 [format %.8x [expr ($i*1024*1024*1024/4096) - 5]] do_execsql_test 1.$i " diff --git a/test/bigsort.test b/test/bigsort.test index c71151597..61c334425 100644 --- a/test/bigsort.test +++ b/test/bigsort.test @@ -24,10 +24,17 @@ set testprefix bigsort # memory. Make sure the host has at least 8GB available before running # this test. # +# Update: https://sqlite.org/src/info/7c96a56 adds assert() statements +# that make this test too slow to run with SQLITE_DEBUG builds. +# if {[catch {exec free | grep Mem:} out] || [lindex $out 1]<8000000} { finish_test return } +ifcapable debug { + finish_test + return +} do_execsql_test 1.0 { PRAGMA page_size = 1024; diff --git a/test/bloom1.test b/test/bloom1.test new file mode 100644 index 000000000..12b53ddf1 --- /dev/null +++ b/test/bloom1.test @@ -0,0 +1,70 @@ +# 2022 October 06 +# +# 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. +# +#*********************************************************************** +# +# Tests for queries that use bloom filters + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +set testprefix bloom1 + +# Tests 1.* verify that the bloom filter code correctly handles the +# case where the RHS of an ( = ?) expression must be coerced +# to an integer before the comparison made. +# +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c INTEGER PRIMARY KEY, d); +} + +do_execsql_test 1.1 { + INSERT INTO t1 VALUES('hello', 'world'); + INSERT INTO t2 VALUES(14, 'fourteen'); +} + +do_execsql_test 1.2 { + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t2','idx1','6 6'); + ANALYZE sqlite_schema; +} + +do_execsql_test 1.3 { + SELECT 'affinity!' FROM t1 CROSS JOIN t2 WHERE t2.c = '14'; +} {affinity!} + + +reset_db +do_execsql_test 1.4 { + CREATE TABLE t1(a, b TEXT); + CREATE TABLE t2(c INTEGER PRIMARY KEY, d); + CREATE TABLE t3(e INTEGER PRIMARY KEY, f); + + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t1','idx1','600 6'); + INSERT INTO sqlite_stat1 VALUES('t2','idx1','6 6'); + INSERT INTO sqlite_stat1 VALUES('t3','idx2','6 6'); + ANALYZE sqlite_schema; + + INSERT INTO t1 VALUES(1, '123'); + INSERT INTO t2 VALUES(123, 'one'); + INSERT INTO t3 VALUES(123, 'two'); +} + +do_execsql_test 1.5 { + SELECT 'result' FROM t1, t2, t3 + WHERE t2.c=t1.b AND t2.d!='silly' + AND t3.e=t1.b AND t3.f!='silly' +} {result} + +finish_test + diff --git a/test/btreefault.test b/test/btreefault.test index 61104c5a7..d0ba05961 100644 --- a/test/btreefault.test +++ b/test/btreefault.test @@ -54,4 +54,53 @@ do_faultsim_test 1 -prep { faultsim_integrity_check } +#------------------------------------------------------------------------- +# dbsqlfuzz crash-6ef3cd3b18ccc5de86120950a0498641acd90a33.txt +# +reset_db + +do_execsql_test 2.0 { + CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b); + CREATE INDEX i1 ON t1(b); + CREATE TABLE t2(x, y); +} + +do_execsql_test 2.1 { + INSERT INTO t1 VALUES(25, 25, 25); + INSERT INTO t2 VALUES(25, 'a'), (25, 'b'), (25, 'c'); +} + +faultsim_save +do_test 2.2 { + set res [list] + db eval { + SELECT x, y FROM t1 CROSS JOIN t2 WHERE t2.x=t1.i AND +t1.i=25 ORDER BY b + } { + lappend res $x $y + if {$y=="b"} { + db eval { DELETE FROM t1 WHERE i=25 } + } + } + set res +} {25 a 25 b} + +do_faultsim_test 2 -faults oom-t* -prep { + faultsim_restore_and_reopen + db eval {SELECT * FROM sqlite_master} +} -body { + set ::myres [list] + db eval { + SELECT x, y FROM t1 CROSS JOIN t2 WHERE t2.x=t1.i AND +t1.i=25 ORDER BY b + } { + lappend ::myres $x $y + if {$y=="b"} { + db eval { DELETE FROM t1 WHERE i=25 } + } + } + set ::myres +} -test { + faultsim_test_result {0 {25 a 25 b}} +} + + finish_test diff --git a/test/busy2.test b/test/busy2.test index 61fb8496a..ec420c8b2 100644 --- a/test/busy2.test +++ b/test/busy2.test @@ -10,6 +10,7 @@ #*********************************************************************** # This file test the busy handler # +# TESTRUNNER: slow set testdir [file dirname $argv0] diff --git a/test/cast.test b/test/cast.test index 6ac9bc7ef..cbeec47c9 100644 --- a/test/cast.test +++ b/test/cast.test @@ -481,7 +481,7 @@ do_execsql_test cast-9.0 { CREATE VIEW v1(c0, c1) AS SELECT CAST(0.0 AS NUMERIC), COUNT(*) OVER () FROM t0; SELECT v1.c0 FROM v1, t0 WHERE v1.c0=0; -} {0.0} +} {0} finish_test diff --git a/test/collate5.test b/test/collate5.test index 5f8697ea5..71d4efe25 100644 --- a/test/collate5.test +++ b/test/collate5.test @@ -19,6 +19,8 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix collate5 + # # Tests are organised as follows: @@ -26,6 +28,7 @@ source $testdir/tester.tcl # collate5-2.* - Compound SELECT # collate5-3.* - ORDER BY on compound SELECT # collate5-4.* - GROUP BY +# collate5-5.* - Collation sequence cases # Create the collation sequence 'TEXT', purely for asthetic reasons. The # test cases in this script could just as easily use BINARY. @@ -289,4 +292,37 @@ do_test collate5-4.3 { } } {} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 5.0 { + CREATE TABLE t1(a, b COLLATE nocase); + CREATE TABLE t2(c, d); + INSERT INTO t2 VALUES(1, 'bbb'); +} +do_execsql_test 5.1 { + SELECT * FROM ( + SELECT a, b FROM t1 UNION ALL SELECT c, d FROM t2 + ) WHERE b='BbB'; +} {1 bbb} + +reset_db +do_execsql_test 5.2 { + CREATE TABLE t1(a,b,c COLLATE NOCASE); + INSERT INTO t1 VALUES(NULL,'C','c'); + CREATE VIEW v2 AS + SELECT a,b,c FROM t1 INTERSECT SELECT a,b,b FROM t1 + WHERE 'eT"3qRkL+oJMJjQ9z0'>=b + ORDER BY a,b,c; +} + +do_execsql_test 5.3 { + SELECT * FROM v2; +} { {} C c } + +do_execsql_test 5.4 { + SELECT * FROM v2 WHERE c='c'; +} { {} C c } + + finish_test diff --git a/test/corruptL.test b/test/corruptL.test index 7361a0b35..3a841a819 100644 --- a/test/corruptL.test +++ b/test/corruptL.test @@ -1479,13 +1479,26 @@ do_test 19.0 { do_execsql_test 19.1 { PRAGMA writable_schema=ON; } - -set err "UNIQUE constraint failed: index 'a'" -ifcapable oversize_cell_check { - set err "database disk image is malformed" -} do_catchsql_test 19.2 { UPDATE t1 SET a=1; -} [list 1 $err] +} {1 {database disk image is malformed}} + +reset_db +do_execsql_test 19.3 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c INTEGER, d TEXT); + CREATE INDEX i1 ON t1((NULL)); + INSERT INTO t1 VALUES(1, NULL, 1, 'text value'); + PRAGMA writable_schema = on; + UPDATE sqlite_schema SET + sql = 'CREATE INDEX i1 ON t1(b, c, d)', + tbl_name = 't1', + type='index' + WHERE name='i1'; +} +db close +sqlite3 db test.db +do_catchsql_test 19.4 { + PRAGMA integrity_check; +} {1 {database disk image is malformed}} finish_test diff --git a/test/corruptN.test b/test/corruptN.test index 468578822..376325f5c 100644 --- a/test/corruptN.test +++ b/test/corruptN.test @@ -141,12 +141,15 @@ do_test 2.0 { | end c-b92b.txt.db }]} {} -prng_seed 0 db -do_catchsql_test 2.1 { -SELECT count(*) FROM sqlite_schema; -WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<1000) -INSERT INTO t1(a) SELECT randomblob(null) FROM c; -} {1 {database disk image is malformed}} +# This test only works with the legacy RC4 PRNG +if 0 { + prng_seed 0 db + do_catchsql_test 2.1 { + SELECT count(*) FROM sqlite_schema; + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<1000) + INSERT INTO t1(a) SELECT randomblob(null) FROM c; + } {1 {database disk image is malformed}} +} reset_db if {![info exists ::G(perm:presql)]} { diff --git a/test/csv01.test b/test/csv01.test index 9ba04206b..ecb1a968d 100644 --- a/test/csv01.test +++ b/test/csv01.test @@ -237,4 +237,43 @@ do_execsql_test 5.4 { SELECT *, '|' FROM t5_1; } {1 2 3 4 | one two three four | 5 6 7 8 |} +#------------------------------------------------------------------------- + +proc randomtext {n} { + string range [db one {SELECT hex(randomblob($n))}] 1 $n +} + +for {set ii 0} {$ii < 200} {incr ii} { + reset_db + load_static_extension db csv + set fd [open csv.data w] + puts $fd "a,b" + puts $fd "[randomtext $ii],abcd" + close $fd + do_execsql_test 6.$ii.1 { + CREATE VIRTUAL TABLE abc USING csv(filename='csv.data', header=true); + } + do_execsql_test 6.$ii.2 { + SELECT count(*) FROM abc + } 1 +} + +for {set ii 0} {$ii < 20} {incr ii} { + reset_db + load_static_extension db csv + set T [randomtext $ii] + set fd [open csv.data w] + puts $fd "a,b" + puts -nonewline $fd "abcd,$T" + close $fd + do_execsql_test 7.$ii.1 { + CREATE VIRTUAL TABLE abc USING csv(filename='csv.data', header=true); + } + breakpoint + do_execsql_test 7.$ii.2 { + SELECT * FROM abc + } [list abcd $T] +} + + finish_test diff --git a/test/dbpagefault.test b/test/dbpagefault.test new file mode 100644 index 000000000..b41ac6623 --- /dev/null +++ b/test/dbpagefault.test @@ -0,0 +1,88 @@ +# 2022 July 06 +# +# 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 +} + +ifcapable !vtab { + finish_test + return +} + +set testprefix dbpagefault + +faultsim_save_and_close +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen + execsql { ATTACH 'test.db2' AS aux; } +} -body { + execsql { + CREATE VIRTUAL TABLE t1 USING sqlite_dbpage(); + } +} -test { + execsql { PRAGMA journal_mode = off } + faultsim_test_result {0 {}} +} + +do_faultsim_test 2 -prep { + sqlite3 db "xyz.db" -vfs memdb + execsql { ATTACH 'test.db2' AS aux; } +} -body { + execsql { + CREATE VIRTUAL TABLE t1 USING sqlite_dbpage(); + INSERT INTO t1 DEFAULT VALUES; + } +} -test { + execsql { PRAGMA journal_mode = off } + faultsim_test_result {1 {no such schema}} {1 {SQL logic error}} +} + +reset_db +do_execsql_test 3.0 { + CREATE TABLE x1(z, b); + CREATE TRIGGER BEFORE INSERT ON x1 BEGIN + DELETE FROM sqlite_dbpage WHERE pgno=100; + UPDATE sqlite_dbpage SET data=null WHERE pgno=100; + END; +} + +# This test case no longer works, as it is no longer possible to use +# virtual table sqlite_dbpage from within a trigger. +# +do_execsql_test 3.1 { + PRAGMA trusted_schema = 1; +} +do_catchsql_test 3.2 { + PRAGMA trusted_schema = 1; + INSERT INTO x1 DEFAULT VALUES; +} {1 {unsafe use of virtual table "sqlite_dbpage"}} +#do_faultsim_test 3 -prep { +# catch { db close } +# sqlite3 db test.db +# execsql { PRAGMA trusted_schema = 1 } +#} -body { +# execsql { INSERT INTO x1 DEFAULT VALUES; } +#} -test { +# faultsim_test_result {0 {}} +#} + + +finish_test + + diff --git a/test/e_uri.test b/test/e_uri.test index 92a3df78d..b1fb47989 100644 --- a/test/e_uri.test +++ b/test/e_uri.test @@ -358,6 +358,7 @@ foreach {tn uri error} " do_test 10.$tn { open_uri_error $uri } $error } +ifcapable shared_cache { # EVIDENCE-OF: R-23027-03515 Setting it to "shared" is equivalent to # setting the SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed # to sqlite3_open_v2(). @@ -430,6 +431,7 @@ foreach {tn uri flags shared_default isshared} { db close } sqlite3_enable_shared_cache $orig +} ;# End ifcapable shared_chache # EVIDENCE-OF: R-63472-46769 Specifying an unknown parameter in the # query component of a URI is not an error. diff --git a/test/e_wal.test b/test/e_wal.test index 77ac83a0a..c9c5e9643 100644 --- a/test/e_wal.test +++ b/test/e_wal.test @@ -15,6 +15,7 @@ source $testdir/tester.tcl set testprefix e_wal db close +forcedelete test.db-shm testvfs oldvfs -iversion 1 diff --git a/test/enc3.test b/test/enc3.test index 7ede2b716..c9d39cbf3 100644 --- a/test/enc3.test +++ b/test/enc3.test @@ -13,7 +13,6 @@ # The focus of this file is testing of the proper handling of conversions # to the native text representation. # -# $Id: enc3.test,v 1.8 2008/01/22 01:48:09 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/fts4merge4.test b/test/fts4merge4.test index 038e460d0..12328c23d 100644 --- a/test/fts4merge4.test +++ b/test/fts4merge4.test @@ -16,7 +16,7 @@ source $testdir/tester.tcl source $testdir/fts3_common.tcl set ::testprefix fts4merge4 -ifcapable !fts3 { +ifcapable !fts3||!shared_cache { finish_test return } diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index fe5626221..a0bcc3bf0 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -85,6 +85,7 @@ #include #include #include "sqlite3.h" +#include "sqlite3recover.h" #define ISSPACE(X) isspace((unsigned char)(X)) #define ISDIGIT(X) isdigit((unsigned char)(X)) @@ -158,12 +159,10 @@ static struct GlobalVars { } g; /* -** Include the external vt02.c module, if requested by compile-time -** options. +** Include the external vt02.c module. */ -#ifdef VT02_SOURCES -# include "vt02.c" -#endif +extern int sqlite3_vt02_init(sqlite3*,char***,void*); + /* ** Print an error message and quit. @@ -629,6 +628,9 @@ static unsigned int oomCounter = 0; /* Simulate OOM when equals 1 */ static unsigned int oomRepeat = 0; /* Number of OOMs in a row */ static void*(*defaultMalloc)(int) = 0; /* The low-level malloc routine */ +/* Enable recovery */ +static int bNoRecover = 0; + /* This routine is called when a simulated OOM occurs. It is broken ** out as a separate routine to make it easy to set a breakpoint on ** the OOM @@ -969,7 +971,7 @@ static int block_troublesome_sql( } /* Implementation found in fuzzinvariant.c */ -int fuzz_invariant( +extern int fuzz_invariant( sqlite3 *db, /* The database connection */ sqlite3_stmt *pStmt, /* Test statement stopped on an SQLITE_ROW */ int iCnt, /* Invariant sequence number, starting at 0 */ @@ -979,6 +981,50 @@ int fuzz_invariant( int eVerbosity /* How much debugging output */ ); +/* Implementation of sqlite_dbdata and sqlite_dbptr */ +extern int sqlite3_dbdata_init(sqlite3*,const char**,void*); + + +/* +** This function is used as a callback by the recover extension. Simply +** print the supplied SQL statement to stdout. +*/ +static int recoverSqlCb(void *pCtx, const char *zSql){ + if( eVerbosity>=2 ){ + printf("%s\n", zSql); + } + return SQLITE_OK; +} + +/* +** This function is called to recover data from the database. +*/ +static int recoverDatabase(sqlite3 *db){ + int rc; /* Return code from this routine */ + const char *zLAF = "lost_and_found"; /* Name of "lost_and_found" table */ + int bFreelist = 1; /* True to scan the freelist */ + int bRowids = 1; /* True to restore ROWID values */ + sqlite3_recover *p; /* The recovery object */ + + p = sqlite3_recover_init_sql(db, "main", recoverSqlCb, 0); + sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); + sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); + sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); + sqlite3_recover_run(p); + if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ + const char *zErr = sqlite3_recover_errmsg(p); + int errCode = sqlite3_recover_errcode(p); + if( eVerbosity>0 ){ + printf("recovery error: %s (%d)\n", zErr, errCode); + } + } + rc = sqlite3_recover_finish(p); + if( eVerbosity>0 && rc ){ + printf("recovery returns error code %d\n", rc); + } + return rc; +} + /* ** Run the SQL text */ @@ -1189,9 +1235,12 @@ int runCombinedDbSqlInput( ** deserialize to do this because deserialize depends on ATTACH */ sqlite3_set_authorizer(cx.db, block_troublesome_sql, &btsFlags); -#ifdef VT02_SOURCES + /* Add the vt02 virtual table */ sqlite3_vt02_init(cx.db, 0, 0); -#endif + + /* Add support for sqlite_dbdata and sqlite_dbptr virtual tables used + ** by the recovery API */ + sqlite3_dbdata_init(cx.db, 0, 0); /* Consistent PRNG seed */ #ifdef SQLITE_TESTCTRL_PRNG_SEED @@ -1201,6 +1250,12 @@ int runCombinedDbSqlInput( sqlite3_randomness(0,0); #endif + /* Run recovery on the initial database, just to make sure recovery + ** works. */ + if( !bNoRecover ){ + recoverDatabase(cx.db); + } + zSql = sqlite3_malloc( nSql + 1 ); if( zSql==0 ){ fprintf(stderr, "Out of memory!\n"); @@ -1700,6 +1755,7 @@ static void showHelp(void){ " -m TEXT Add a description to the database\n" " --native-vfs Use the native VFS for initially empty database files\n" " --native-malloc Turn off MEMSYS3/5 and Lookaside\n" +" --no-recover Do not run recovery on dbsqlfuzz databases\n" " --oss-fuzz Enable OSS-FUZZ testing\n" " --prng-seed N Seed value for the PRGN inside of SQLite\n" " -q|--quiet Reduced output\n" @@ -1851,6 +1907,9 @@ int main(int argc, char **argv){ if( strcmp(z,"native-vfs")==0 ){ nativeFlag = 1; }else + if( strcmp(z,"no-recover")==0 ){ + bNoRecover = 1; + }else if( strcmp(z,"oss-fuzz")==0 ){ ossFuzz = 1; }else diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db index de7799f945f521444c9788aa2f933692ca84a6ba..aff7e27340cd560731ada86f167851c766694516 100644 GIT binary patch delta 10092 zcmZ`<349bq)}QKQ?vPB*$vuHU61sZ2XL17}2@r+l$U!*d%#edyG7u1uBQe7&h!Czk z50FF=L2v;%N(D4oMMbXQ!LEuJT)b9+g#FfQ1-{n>ec_fdOfu- zze8%b9VHFXwm6;6x@hUVF|_(f+ZA77?yA>5m@Y4BC$kaiDqk0#?1P{g7B6QD@yAIu*M0-D z#Zfy5J9 z*9+x+zHF5D__9#;@nxWN`_fS+`_fRx`BG8ZeQuN%p9?#`?o&|y?2}Rc$460i_)<__ z^d+PG%9n)lystaTkA2-xF8!x30l)s?bD;dt7mxC^4^?kD>5D=6mMP)_rqVk{Lt6Us6l>f18PXFxf^C!#F%2`GpA zc$CB9d>noa^bwS~^-EFqs$Y!KRlf*j_lK{sSS)c^;;}feBw*=^r5lz+ELxi$Sdy?L zV@bi%lU-wy!EPWCI`S2X*y}B@5My&zeYK$bdkvf)Kv(dp%M9cvDTKJ7p}*7(0h_dMYy~Ro(n-ctQ3;rz+)xTt>q}T&nqI&%b`TV9yFE z$yAl5ruiR%^DCsS!~@lVQk0mXRl(_Z=)Df}*mN~33r9(c5UKY+3~B5~;hOjRdWuB^esL79(65E#<=bFQhPS3O~X5NP8+=P7V>l z`-wCV1_^SHI6pR*d92Ho=C0BvR*|Ku?weC92M3dAOTl=*JVi7gcC41d8|x6_*zI%>6g5bCt}+X} zp;8E!RspTsX&6kdqLi4SwLwbYQn8DkGzyot`ka(##T7_R%Sa#ZR z^{Wd`KO-gG&_YX?lw?m=D_t%Xm8li&QBALES~?W}TS|iFLsB$qMyG?~a48%no|3{N zGEm*_%v5*Q_$)VGWoK95LS&esZmkp^UR08Q@6dclP36q$iF0aZ&92OGVDHdcXNZRA zt~M*Y^)H7l-sMy)C3Vr{lz#U)JdRNV3rY$cH7>`{{L=md9Z5;xdD0RFo2F1R9Q;-v zG?Y4eI_@bc9O|e^b0l3%N}(x^-sXZ~1A3cF3I+`uP%!%Uci2^LbN`Zp{L%u4|7S;~ zqi`73CYR$qv<(S%@RRyF;BT(WhY>qG{zgmyl;y}qF10VLiIc<8 z?h_J&yOR`X3X@~HA;g>7O-;r5k0=_TO%T;Kyr9%EDJN-4WfBy9CxmBe(@)OtS6EVt zP)ZB^6Q|`V*ltKc|I(DZmuP6g(87}YV9znZJ}Tv=F8%7g@q}ZhXFhezp7p@giewKh zr;}2sW9HQRlRZkB*6odz!7*!6{ZPvL^eOi%Z)AS|5hI57Hsj9vAGl6;iH4Thiu#OMWuE{UZ`XFw_VVSfVp6j3!nC&C!vfhl8tRq~8j>}-SK7?|@ zQyKMMme3|0A>z;2@pbUtBfY`iZkKxVoJ9xCYw73g%L`Hr-VtzoDwSya+yrI)jRJdZ zm=sK)A%^C#m%fpTIL^X>Cnu&5R(}*ELj1l6BP;t(dIlfmp>qY-gE_vJ+6h!&mS(Vd zSEQ?=I9t$NtYApO7LL{bEG;79HKcky*cAI^Xgbavfn8Q<3pgiIx$$qjfi+x{<`80F zog*a!f!!ZTMreqjA*=_XpK`YPXE2nvea?N#h**vnoM$o+uo^;;t3A&??x0s*H zuAd|5-kqm`Kf-B#P4j0KR`;{Al(3*U={o}L7wH)Qu(C z=~)8_{H^^Cf{wzwK#GA5kOaJLy-0U!PX}pF$B8%iyY;G+3jZmUVmUPx^7qh@;5=xK z)aKX)P&wT>PX^RYq?4iDK=cr_hK?nv;O&CsSTsm_pOiywH{_kT^~*jz;iL;sWXRcH@G zgq|lUQSL~A=GK^aqqakEd!S!^KG^OYHe$$- z-sZGH!-f~&4y{VAN^y+LA2Ol<86|0EnY8A>f@wA8nMRe(1L8N^rbES3}-$E{Oe- zKn)xT1$COV5Q_Fo0(&Tt*72q=zpZG_z36#b3}yRi0PH?bhcjH7lO}PVpgS@^v)dUY z2v<57i~WW(b-SQAUfQ7U@29r zVRME$hHc5F6FCwEwmDrRps*2*6L(=ZP@5&`_q%W!%gCWy4Z;J0?l^e2#^f@Lu-Z9M z3nyct=uN7y)Pb~(Cvh8W^a-|U2(98tJfM@$ZyYAoGsoA`T7=c(E=5^M;AGMgZD|o8u_w46!)Wnl+VCy5LFbtK>@FWpBdeH{9zJk7G5Ecr$@1Zj& z(2rxBb}F#+cy@aI0!Hqq*NCV^#Yte>-lZ4oTTPGfBpKEuQkA9E&{I6Flb6{-q41X_gIQq%-N!@o6?*c95<4D~dO-(AC6>E^erymI3A(rY zX>|1w`(YCXK%%>OZ2f~^+e|NsTIin&u0|=Ib!(#SJnmL>3pr4GT`FVETj(U7q%&a~ zy>2jMY9^4L3C(Zfk_DZS#z4>sE}pR?R3M;cPzv@>>AYLK(v!XRDfJP}46<0W8{=Ul zXfw3fDTCcZq*<{0OX-?s1le%#IL*-7WwXxXw1`0M_jEDDACkgZ{1-HmJ8+1O6vV}X z?xa(j#Ul_`92E$GKS_O9?iqR*9gHj;hNY7w1MGT(7KwQ9vGf5~Fnv?I9|{deB#}iO zq2F*Mhq=yC2Ra6~G@30sN+0G(UufMx$FZ{G^l_uORM4HO(grElI6Ut;p?(Q+xb+W) zZ6Tz0<1ff&mw%LOe}rfrd;ViOj*xyDJ3Cn7S^61I`oo%`bOx(#r|Wn!0QOW%VQ}&U z^|BQg=mG<|2i9Dr@$lWlQqXS>JCIGjNH=oiUO4@>q`;6&I-8-x{@KX+2sHmhr-_4} zD)Z@B(9iTT5epFaUjUsvNh-RBiA31N?KaGMd(aQi#if zo+QRF2~Sh*N;i0O&~BS^&`ztr(Wr7i?!;b`X)U}d(LT_UW-s7SNqe4^dIU_GgmHEy z#@YTzJV8_wVEq)D9i8cSG}MK@Mq_lZpV9r^#RHyWabfW9^#P86l7eCRLkrLwVJJ8W zI*$k2;hw3K_9`jpKcZy#ppn{7S~z>d2_+|GdMJk2$w{;veTDX2QHd4)t0_~bX_xS~ zsr0@cH)G#hA^5F}Ujxi^e^h<{S0OpDWg49X+*35J%T2d;GcbjwT~T&EDcJ!D*Q_yd z7({BJw&V6tJ2@GHT`jCGg@%V?5}bJ3i%E3@lg9U1}*Zd$?-SB zO>#j;F5tQQV`AJQ_;^A0j&FN zCr+GzKqdh>W=Leo1LaS6483*?meH>bkz>uo#EM7CeP-0*CsyK6GvJ!f@-91BEvNHjCd*xJ!9e$^d9n>I zJ!!FkV z#QwTSUd+R`)t1q0Y^}VFCtmO@l`+co$!B>o8`>K!Q`n40WZ=mhXn)d@2fdzDLZSK< zO8~1}DL>7dY6>uB!X1TYv=Jf6EUiI4#dC8Fn7zF;5^u!G(%;|cR@lAqzo zdYQg>S40cY)Gxj@^Fqk#K^_y8H80ImDlNs4_x=@ z^RZu4T6`4>MspFwRL?2_Uww5pzNAFg8ACzr94@V@lUJ?6J1|MJ)*(! z5~lx3&M}BxFkb4T*_x)ihlp5@kFVT1F&bmC!06FP&T&C(MyCuMc?`V4(GF-hYbb@< z^5|OF9W9zLHi%gZ6aFDTqbaXDv@MU`r@{U&S@nCBJxgu@o#x-btC?4hSw|q}4Y@`G zK7t|KX9;Owr_ReR4w|k<#j)X>GfgiB;?dHN5fGyho|2p{_OsC`^5Ub2b{!)NVhai6 zRYf{LZHj)91$1$KPsn=M^I4DrK?kD^x26$7*m#?BD@Qi4&NFf#`90{{R$0?ECJl9# zM(F%lu7*CZ%WvMAz)JMNItxJOIk^~WpO16=eiBW;*+7hcs*khI&$YpDN@Z|rQ_OLe z+trz(7efTyx*@20H*8LJ?l(2RG}{*}=)TalzMHL1agHLUryj2G*#zBctywxG;>?Xt zzM}U93A!uL92J~#OCuFz?E&iK1+o#&pD@mWj(>-rW&>T$gPdhkmYRh$qq?)pp=okh z8Rj!XWoQYE$b+SCnycC1bmz;Q;pxmQH5E^ysw{uSIb!0s&qL?jo*}St6Z)eEHy8uqldBOX)@z*esDW%} zryFA>B<9CsYj9s5j1FVFjq&jWIzr=8v_*JevDwVB(D@@FyI|>m zrDj-k%;|$Ni)ewPGdVnFUyKc76Bj#&8c76uahbD(6Sv{;FM;QJpp&)rGE-di;Rf7@ ziDuk8@i~UE5cH_?KV&a>mc+iu_N;W4@MIr&9*WhD0nU9q+0Q(GjonO`+TtAVH>d;5 zvm$m5VdlBc<^Bo=o>j4HS>s0MOrCo=2@>A2IN{h)r?Eb$yHxRBhjU}9Qtm2_? zt7)dT8h;1xO2<2XZ31dD7#h9_DP_}-I{O<)B-_>IEaI()u-%c&j8vC9D+}^I7rHa` z1LrO?!Cd<}=QQ!{HQjwtQ23d1xA-Qudj~d za3Dq*t`{AGu2I{ZDtn}>@&F-c4kRj}c+M1bkB;$I59+RbqZhm33y5)epTs6mC5SVA z@~G(RijQh;t0!x6DiewL5sq}0395p#`V`LODm|GqP5IEkeU=XS*UTZ%5yVSurmEy& zK5A7^0Bgum=5VB4Yc++i8TrZ-gV-J4g=hoyfa?R54W`dGj`Af6x>wxV*qB;XOzgxU z#m$N5aIk-|{H?r^FzZm|2aa5T*5@O7L(zT8BkNl1MJ%FN`HmxBKx?sirO;Y-qzFt1p*8@51{$Hyw;pMRkFt}XVCw_?od;`tL3}G-~M}X3pZ_Hw~ z6-vE0`WE(sELJ8PMcn=`#ApLEd|(zQUc#S|6U)V}tj8nDLq?Gbx-FU}9PC7c5<*Nr zJYn)lg6^eotq!ox06}Y&nWq0+JKKj|^Er)n6WI%El@TPM6Fd8FW%=~vq#75d3-(8Z zVBn%$;K}!}C`^lg9#_D?{g?utZ;fG4+g~w(ew}1vX-x`e6=hVD9(#*qr8|`!y!aFL z_6y|yGcFh8iwP~y^LrGYTxG&OZKbclrbcTNi+fqgGZKYOY*D`F%vN2q&F93W?19~c z!0b1a$Hf2P+du+q_Qp+-pB1!LgrH-5)7#3!*6p{+hP307TDK4ujsl&3k)DzNis5|Chn%!b$4 z`O``Q*5NCkZiYvpY+-yREZu49xJ7S~42S=x$XN~Gh1f^`2+}yhS&dLUAs!Fz?FuH4 zttRjch+n~Q&>FlmGw)P~71ONWC@Zy;n!B1~A+c&b@NcBw)y@C*<&NS)Wg6^lXbg)Ib`$2m{ z@L+iHUE42>N$PdchSP{*o+qRL!fYw(6%HxS>!IJW%Tx{JNetwts;@(By1fdj@f9BP zxKxadW1-m)5e4np(h#kBJbOD$ebHb}6?C6YOTUgE@OBzpY*!z(h!^pFuGIma9ue~1 z-Jv50Ywu7a$bkcqq?qY2N)l|%^tvg-V z4V)+K-Pq6?buVwoaATInnh3U9zJg6DQt>>~9nOaarLnA1^%zfjfa3?HFBD1k09H6! zMaPx|#VcYQP&7Gm3wvjr>NAowHndWGi4*ZXw6*8Dy3h%1(iF8>%cIrpj2r}xF-lhr z7W!(+TK^K6OIY3@^|0mxt@673Zar?{Ilyaqwfx8jp(9@%2pa|W6RfOQeU`%v+i#e_ zCfSGF1cPbN-@y3PD@J`f9*Ny*X0_4uLPyt!c2wtygc;8f%)SmJ@t&cqhUP zK!hK$Paju%@Feq2bXj+yYu3!GX9pp~+KSp*i#WjEvwZ8%M-CGyx z`)w&b{-w+X?>X0-Y;&9I9l{!3Q}Ino9^_wAPXeyuDpuC24Vr%^xPEtm^Q*4^?cfI7 z4$i-v1-FCyK%HwKS*-6_bv`lQdpy>cd8anDKRgTs7gLYztqomv6L{l+$PLM6}8C;81_Fa-U>)dBKJd!lBS zU;8VEKtdrVe0uWkGoa2_0h<19uVg=Ts#XpYXSNWG{@a#dCbQ_$Y z$2a{hd^2vxr)@KWrx-szjRt1@3BKyP@OkgVH~UU}bN(0~{z>IG;+%UsK0gi2y9-~R zoA~ZWd<*`R*caYS><`{W?2G=G*cabU>`QLPxAb;=ekQ2B9bb#9!>G^Ec7NUtF11u- zYJXnBe@9vC*`6&CM+ifb4&tC_E2q7OwEBRIQSvpS@5-^s@3}T&{sYdxZI5r97dZ?* zBkpW%zhj#D)vGQSX8u?3ePQ=MT*LXlx>BI{ANsC0JQB2Rk2FHw^2k`N(}$t%m>u8l z&sR&?(z7lfPac8d|5HC@pBUZon4r{Im$Tv@)tQ7%4{)#c)5eNh(@0{2zjpOAk-ltI zr_0NU4`K7i*15tyxaB#wp{r5xrj uYzu;-G&kypd6+OR*ew|Jxv2A}5&`|!CZ=9-9Y=(R4f=JvL+i9YR{lRERa$uf delta 7640 zcmZ`;30PER_n-GJvoM1&GVBZtim1%pW)#F7P0Lafaw*M)N!&FVw=y+BF1e;C%Bj=@ zQ`6MMjZ>+mUz+BMZC|Dp_VrbMWCaEda;c3NV^SX13iFEdSaBX#oW~dE3B`G0ah|z2&r+OcEzYwQ z=YlmBh!YD}}4gFrgwgJX~TMw-cF7kUSGu1By;qRq^If~NRQLolOCaG zk{+nHBi&oiAf2hFlUDRLq*HW1={Vg-I$YOChv+IT@tdxYuG1yb-|Ggbf9tJDf2F6A z{!(v2`kbCZdhUmMGx|EIdq^MG6G^|XyGb9_6G*?S$CEy&$C7?SCzsgw>QSV(>yf0_ z>n_r(^a#?6^>ETfIvK-0Tep**q=%3ms#{49(#@nF(M8e^>a=0|13E|g{zRS8S5KX6 zX75@wmvlza9MZm`S)^OdzlCm+M3N*D4@ojfGm_>cDI_gOQb}5p827g(NyA&{wZwQ# zZWH^KRO8R;jG}zORFw}G$4ciR_9yQ*)@y6TqRE2k4M<+#=n6^Wr28=c1Mf-(zOzl@ z;oyhfPEb4F>%e}A-WCkL+~v)~+JAc^ICcXL&GL3F>mcRBMwdJsotL~<&9uXin-IL; z`z{{+ueT%5h5P~)Rnk}Z%?)n?UHuB4E8a&SYbnim;9+kBgAz^ZjJtmGW^-J~ZxHC5 z&<~cK3l|}2PqcM!uC$&Wx&?KMxK#9nNHq-dd1)ffFiXFQVo)$$$wS(Tox`Fg(rhOF zOtxd-RAa6}`AO~w>~MHDfKnvMP}7IEU`bPHDq|eh4f0wTR9^C0p~NdiU}~iFImdV? zEB7{o?6;(W5L)4VxE>`C${cvTf_~8Yc^6=Htkl+QbA{9qD0!o5+j+wvmKs z^&h_va+17Tp~fj!Ky7r41u6%4?}Muj?@I7wc$*kz%qK^1avuYDIApt|Phf7ImJjQm z)P~@OVbTbWIbrz@DH{5{B@HsHNt5)I9)bLAQVe9B@(#d^fV9aXOczWiA-E}_D^ldu zaAFgh&;{AwNh-FPC{^<;5}YpSMO>RNjpkVt1j@bL${zL>q32ic3c@g6YX|Eh>Du*_ zWHE~~1k;HWV^bIEEpFszECg0c*YIY6WaC*JI6HY;qpVAx@GKr`hf5{6a)I=oMR-~; zU4y#tuzDavnc*+@D-(*ILbgJ>#fX1R_C#=QY0?3^tdx%MED4tPk~D1lf^>#w9tfh- z1^rGtlc1!((+s)mrLQr0t@IVgl1nS1A~3pK`krUaAh^~W1;4GO2-B`dgyY-|lFqZ{ zP+M((5c_VHsw}Jp)FsNEIuQcj&=vy_&L0ONQz`)>x##VW`grw=?5`|u&05q%$tPG4@)&X^BS4k844n# zT&y@IJ;5^xg_F{4i$yU4AiW(dZ!c4vJ>U&Tyd((>v>}oNF-)ETH5O)u@V!zRzRhHv z83CYTh2J}Z@@Zxih1p-w8SZZn`)Z{O<3!X%p<%T?7#g9LGw zU^=ZBJ6HgTIdNeS7AkeZt~aFvL=m$00L&djF|^}bDO;3jsy5(Dl@1%%eWB!tSH!qW z(j|_yMIR@72#VkP7|y;T&F5G;9C}k4in+DY3s!NCU^+9}SY)J;qwO{dMYAbK9rsxF zAQS0`v1cOXtIQb?X5g#0q@j$pHxkveH%0!EXB}X9kmx91kV|>i5q9Q#BjI$N6vRbV zd8UPRhULj}5_~_O40(q?!*GmKUc<32aQ3KIgs0ou6oWAy`7&?ou4x&XU( z3nU+RcEQW(vWsK)mGzN(h~%oJ&y?;Qor#5g2&%~Uuu7&vgSa}A|`azc4m5l37hXfepKCKLe-0G0Y;GL;#D|1K3V$@{$iokL( zWLH=ML(g>iHH#<;rfq}jftcsWKXYO>0T}@7ge3)A70Bm#HW0QJ$wU#l><(dO%vmbG z&cyz7*JIG_0aq&I+mukKOpEp5*b;d)&jv$I&zM}yTPd&O*$}AM7L$r;FUaS3+v8+T zvg#9l$l+q&!uQt5^DJhoXe!(_mH?!~>C18mJikGHSsb=KQ@5bDQ9i?okJ3`Z;g)Vc zj;A-v={y^OT^HI9aj^Pz*$F?qWVeH7iM<(A>O^3z3n}sQgPa0aUtyt;wOE-3ubq;k zjg-g*XPiJBR4Gs9SpbF?ar5xGJ@Oo$Jpo&m*&oHn-;g)+Y$OCwrt@IGe1T_qP*Y|f zixUsZz_U?M^O8LaI((qIApcE!D2_TTzs%c4_w)oj1NwOakUTJ=6}CMlpW(SNk3r$f z_Bc#BDSvHY<8WWF$uNe2kLC3o8xP?}DPFS3L%2&x=})8;G9%tE(6cU=GI0r^yKC?I)!94 zET0{cVOY~Aj-}zrrOxp&nDD*)Ezk0CNSg8r!;_&(TMo|uB)4Kg2zE+zLDs?eHBhi9 zUVtX5@+0KeShhg+Yw;mC?3%oZu_+MDN{oOm513nH)(!bxo=wFe8OkO)TGQjrSoxdW zpJUUIU5TH_FrO)=WCseXftd#kBLUf{OqO?@pQh0#XkmWzr=8y z*jfRO&4pmocn_3Zund5Lk?{pk886zP@|%PeFg#3IZy2uyR4^a!^Ww;9A`{FHkwh_T_*n`R@j?Krq zCQ2ClE9eGZHf&9msmZY6I~(CNB-_|68F)E7+)p)N|vfr3{PJ%8Oq`JXY6P`}kh zSQlq3MqA2-Giwu0V%H%`YqJ%X~~Hz$+CdL66Lmqy$E&F(;{Ha+BiGRT4fD` z^S?#gu)|8_eG6NSXUp7PqUIOe(SXQT` zVA^`+9Aj(2*)DMcPTrubH0p$@+e2JFd0D;?K3YIss z45b6+nWT0wcblyik~&)+hwyimpV&qSJnP<$I}a<5@N5$V=DDe&I;K!X^(qFQbFX8l z-B5Tp&DMq8fmn-L z56{&4Fs+2(Y|pP%bqde3oe(VW{0p)-TWnbSUnPQPyD+GGjWv9x%%)IsDPjOlVCsDq7KJ;kYBuk9o$kYS8Et(2%uLAo zN@#(aL){U=VxT5U9WTDQyoFAMd$d|9R?(er!P3pHSjdgwn_<@&l{UQ(g#`6B&-SC> zF}@F=kfKr)y#rg@gl5BQT|=AVXi42^6=Ma{R)aM(?31qU=ES#YzJI_WpX!F(W}F2^ z#Bvr?Gu2U6F0#nUS3uE*PRd#n`c`p{k3CiGpeIkow^VhpCs$qK95{hthRR9G<6! zbJkOfMLmfgHO{RxEFZ0oVB!f{>NE;tjeVVgPo7fKP?@NHY~jwPL$_NY5l|b>dvVfO zHH$&b((q6$nXXRd*aya4V;N4IsgAXX%>>gwjD=F+c7eLe_R*S0^<=^Hrr%f_Xl)yw zDpdWPSWSz4jNLZzR)&sZ^*YBsfkUrGcZBTa>VndPG1(ZsQ2m}`|Aa%?vDNs|67`)> zF@;il8qJgf`(IMK+df-3U2je#_T>MD5}-EV5TN&Jb&vQdEpQ$xPFNyg_|{OhteZ6x z3)ZMb7WO$b85B1Jhi_0f^6U!~HmftNVhh1^;P-h-4ybXMzcX$L2L&b30*n|Kx5IcR zxl6oWiqeMAA@hq_Fp}}VP`Kay z80N-kFVmniBpjQ>X+IK6-TwXe|AUJ2?h#O3^C82coMw_#c0%0vHt>r9!ZOk z7M(^{zhZEl$H6c>SzBUZ3ao9VjWgSCZ6B)3=xD9|$eHaoXgjx86*|4>vBs2hct+9M zGG>CD8Df8YRMWQcjA3E2oI*v*Dv6RhUHgb*928cB^}*~+ZMX5JQ9U^90qmWny}>gP z@~?Bz;HV3m4M}e~zrg7|wWS=rTLwM}kH+|3+IKu>@c{c)i7p+U7=`{m+F9OiZP&Ja z+qUT==_rNevHmpJ;^8Nhg~Y7Et~uIaj@eKcpiQ!wRl)RgZaO$~{N16Zq-k&1e%$$M z*$C~n$WyP-fq@sjp$we?+8R~l!D8t$Ud_{7JZl2oCTQ9PSmzrLNvkj`S|qP^nqZV#fLklY1zC;Wv`_~&*9T5f3?U1zH>GdX%2+*Bq47G0m~> zHf=X=$?zL3&?s;g@Oe0Pfky8%(Qwff-WD^LXeW50ZjY*UfoyL~DE2GS=xmFHoJ9#9 z$Q~2B0gtWJbSv{=-?iE3nj6mE-s5Iif?W(F)etrPLkL$O^M zW)*4&jAPJ|RNAe1>K!~)Jas(ky2Z|d+L>A}SkuY>BIYjCUZG}3Lrl%?!qmJWrv2KF zR1J@g8W+h$;#T`{wvyjoJiechqk-WrT^*EK)p|W5RCF4#&uTz zF@`1AG-@*Xp|)&V(!nzvTYi}YkL>GdK;!UgqP6}UcF!2HH7EzO}DUg?Beo0 z%|hCrbn9&!+mpKDjt=0vs-4(f;41zDcv)5KD#NWE;m|^DGi2`%qrTN5>OR)*0B8pr zjGsHfAzkfF)wNatS*gA=MsPk+9}k@&Jj`DU^68iqBS62aAM66j^R;A1>*)Ukv`P)2 z{2wu+@Oo>XgJWI689^@s)z4BVGW&993Q*Xdg36CLKjz9lvQ8E(AI48JVs{9`4}3l! z$GSnzv#~Rv{6%ddPy`QvWfNjun6-*xxGK{}W%hl46DK|Hij$s=1N#2P&UoP7&gj+9 z8I&V`B$*W-8sK}AXAd=W#$&#pthn!r)`JtfllA)k4Z4T#Lf5Y`y6nd2a_)`pk%s8{ z-$D033K-BB9c%YTB=;eFkNyolY7#V}gTamQ4QY(;@q6PN+7REcyYLNfh_61=GXKCg zi0}pe2Hz8R;Tzc)UtVK;qwbB5T7iF3=$MB1>X~3{V|=(T;3Mzm61oY0OYjr#CiqEr z5&V<)Ciuw>37-C?`4e5RA-Z}Zn9>m44PUL*+`&lxtl!Dhp4YA|!5mLA9d~YsKEja5 zI-G;-O&k?kCdUk5E7WcV;Wy)OOo)FCbwc3c(U_#N8L9Tv)KOi; zcY(?vu0MFU#^-O&u|nKgpjFkQevo7HpzcbXyAe_wc5b4*8NiS_|+*x6{{IEqoJ(Y7+&(bGSGqfUl%W_X-&oYuoUz>s^$FIdc- d$mXk4fDW*=sHOQ&62b!(b7`fk(3s=d{|DnSTG{{r diff --git a/test/fuzzinvariants.c b/test/fuzzinvariants.c index a601e7aa4..c0ed2dde5 100644 --- a/test/fuzzinvariants.c +++ b/test/fuzzinvariants.c @@ -29,7 +29,7 @@ /* Forward references */ static char *fuzz_invariant_sql(sqlite3_stmt*, int); -static int sameValue(sqlite3_stmt*,int,sqlite3_stmt*,int); +static int sameValue(sqlite3_stmt*,int,sqlite3_stmt*,int,sqlite3_stmt*); static void reportInvariantFailed(sqlite3_stmt*,sqlite3_stmt*,int); /* @@ -46,11 +46,14 @@ static void reportInvariantFailed(sqlite3_stmt*,sqlite3_stmt*,int); ** ** SQLITE_OK This check was successful. ** -** SQLITE_DONE iCnt is out of range. +** SQLITE_DONE iCnt is out of range. The caller typically sets +** up a loop on iCnt starting with zero, and increments +** iCnt until this code is returned. ** ** SQLITE_CORRUPT The invariant failed, but the underlying database ** file is indicating that it is corrupt, which might -** be the cause of the malfunction. +** be the cause of the malfunction. The *pCorrupt +** value will also be set. ** ** SQLITE_INTERNAL The invariant failed, and the database file is not ** corrupt. (This never happens because this function @@ -105,13 +108,16 @@ int fuzz_invariant( } while( (rc = sqlite3_step(pTestStmt))==SQLITE_ROW ){ for(i=0; i=nCol ) break; } if( rc==SQLITE_DONE ){ /* No matching output row found */ sqlite3_stmt *pCk = 0; + + /* This is not a fault if the database file is corrupt, because anything + ** can happen with a corrupt database file */ rc = sqlite3_prepare_v2(db, "PRAGMA integrity_check", -1, &pCk, 0); if( rc ){ sqlite3_finalize(pCk); @@ -129,6 +135,60 @@ int fuzz_invariant( return SQLITE_CORRUPT; } sqlite3_finalize(pCk); + + if( sqlite3_strlike("%group%by%order%by%desc%",sqlite3_sql(pStmt),0)==0 ){ + /* dbsqlfuzz crash-647c162051c9b23ce091b7bbbe5125ce5f00e922 + ** Original statement is: + ** + ** SELECT a,c,d,b,'' FROM t1 GROUP BY 1 HAVING d<>345 ORDER BY a DESC; + ** + ** The values of c, d, and b are indeterminate and change when the + ** enclosed in the test query because the DESC is dropped. + ** + ** SELECT * FROM (...) WHERE "a"==0 + */ + goto not_a_fault; + } + + if( sqlite3_strlike("%limit%)%order%by%", sqlite3_sql(pTestStmt),0)==0 ){ + /* crash-89bd6a6f8c6166e9a4c5f47b3e70b225f69b76c6 + ** Original statement is: + ** + ** SELECT a,b,c* FROM t1 LIMIT 1%5<4 + ** + ** When running: + ** + ** SELECT * FROM (...) ORDER BY 1 + ** + ** A different subset of the rows come out + */ + goto not_a_fault; + } + + /* The original sameValue() comparison assumed a collating sequence + ** of "binary". It can sometimes get an incorrect result for different + ** collating sequences. So rerun the test with no assumptions about + ** collations. + */ + rc = sqlite3_prepare_v2(db, + "SELECT ?1=?2 OR ?1=?2 COLLATE nocase OR ?1=?2 COLLATE rtrim", + -1, &pCk, 0); + if( rc==SQLITE_OK ){ + sqlite3_reset(pTestStmt); + while( (rc = sqlite3_step(pTestStmt))==SQLITE_ROW ){ + for(i=0; i=nCol ){ + sqlite3_finalize(pCk); + goto not_a_fault; + } + } + } + sqlite3_finalize(pCk); + + /* Invariants do not necessarily work if there are virtual tables + ** involved in the query */ rc = sqlite3_prepare_v2(db, "SELECT 1 FROM bytecode(?1) WHERE opcode='VOpen'", -1, &pCk, 0); if( rc==SQLITE_OK ){ @@ -143,6 +203,7 @@ int fuzz_invariant( printf("invariant-error ignored due to the use of virtual tables\n"); } } +not_a_fault: sqlite3_finalize(pTestStmt); return SQLITE_OK; } @@ -152,6 +213,24 @@ int fuzz_invariant( ** Generate SQL used to test a statement invariant. ** ** Return 0 if the iCnt is out of range. +** +** iCnt meanings: +** +** 0 SELECT * FROM () +** 1 SELECT DISTINCT * FROM () +** 2 SELECT * FROM () WHERE ORDER BY 1 +** 3 SELECT DISTINCT * FROM () ORDER BY 1 +** 4 SELECT * FROM () WHERE = +** 5 SELECT DISTINCT * FROM () WHERE ) WHERE = ORDER BY 1 +** 7 SELECT DISTINCT * FROM () WHERE = +** ORDER BY 1 +** N+0 SELECT * FROM () WHERE = +** N+1 SELECT DISTINCT * FROM () WHERE = +** N+2 SELECT * FROM () WHERE = ORDER BY 1 +** N+3 SELECT DISTINCT * FROM () WHERE = +** ORDER BY N +** */ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ const char *zIn; @@ -168,7 +247,6 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ int bOrderBy = 0; int nParam = sqlite3_bind_parameter_count(pStmt); - iCnt++; switch( iCnt % 4 ){ case 1: bDistinct = 1; break; case 2: bOrderBy = 1; break; @@ -183,9 +261,10 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ while( nIn>0 && (isspace(zIn[nIn-1]) || zIn[nIn-1]==';') ) nIn--; if( strchr(zIn, '?') ) return 0; pTest = sqlite3_str_new(0); - sqlite3_str_appendf(pTest, "SELECT %s* FROM (%s", - bDistinct ? "DISTINCT " : "", zIn); - sqlite3_str_appendf(pTest, ")"); + sqlite3_str_appendf(pTest, "SELECT %s* FROM (", + bDistinct ? "DISTINCT " : ""); + sqlite3_str_append(pTest, zIn, (int)nIn); + sqlite3_str_append(pTest, ")", 1); rc = sqlite3_prepare_v2(db, sqlite3_str_value(pTest), -1, &pBase, 0); if( rc ){ sqlite3_finalize(pBase); @@ -202,7 +281,8 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ ** WHERE clause. */ continue; } - if( i+1!=iCnt ) continue; + if( iCnt==0 ) continue; + if( iCnt>1 && i+2!=iCnt ) continue; if( zColName==0 ) continue; if( sqlite3_column_type(pStmt, i)==SQLITE_NULL ){ sqlite3_str_appendf(pTest, " %s \"%w\" ISNULL", zAnd, zColName); @@ -214,7 +294,7 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ } if( pBase!=pStmt ) sqlite3_finalize(pBase); if( bOrderBy ){ - sqlite3_str_appendf(pTest, " ORDER BY 1"); + sqlite3_str_appendf(pTest, " ORDER BY %d", iCnt>2 ? iCnt-1 : 1); } return sqlite3_str_finish(pTest); } @@ -222,7 +302,11 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ /* ** Return true if and only if v1 and is the same as v2. */ -static int sameValue(sqlite3_stmt *pS1, int i1, sqlite3_stmt *pS2, int i2){ +static int sameValue( + sqlite3_stmt *pS1, int i1, /* Value to text on the left */ + sqlite3_stmt *pS2, int i2, /* Value to test on the right */ + sqlite3_stmt *pTestCompare /* COLLATE comparison statement or NULL */ +){ int x = 1; int t1 = sqlite3_column_type(pS1,i1); int t2 = sqlite3_column_type(pS2,i2); @@ -245,10 +329,38 @@ static int sameValue(sqlite3_stmt *pS1, int i1, sqlite3_stmt *pS2, int i2){ break; } case SQLITE_TEXT: { - const char *z1 = (const char*)sqlite3_column_text(pS1,i1); - const char *z2 = (const char*)sqlite3_column_text(pS2,i2); - x = ((z1==0 && z2==0) || (z1!=0 && z2!=0 && strcmp(z1,z1)==0)); - break; + int e1 = sqlite3_value_encoding(sqlite3_column_value(pS1,i1)); + int e2 = sqlite3_value_encoding(sqlite3_column_value(pS2,i2)); + if( e1!=e2 ){ + const char *z1 = (const char*)sqlite3_column_text(pS1,i1); + const char *z2 = (const char*)sqlite3_column_text(pS2,i2); + x = ((z1==0 && z2==0) || (z1!=0 && z2!=0 && strcmp(z1,z1)==0)); + printf("Encodings differ. %d on left and %d on right\n", e1, e2); + abort(); + } + if( pTestCompare ){ + sqlite3_bind_value(pTestCompare, 1, sqlite3_column_value(pS1,i1)); + sqlite3_bind_value(pTestCompare, 2, sqlite3_column_value(pS2,i2)); + x = sqlite3_step(pTestCompare)==SQLITE_ROW + && sqlite3_column_int(pTestCompare,0)!=0; + sqlite3_reset(pTestCompare); + break; + } + if( e1!=SQLITE_UTF8 ){ + int len1 = sqlite3_column_bytes16(pS1,i1); + const unsigned char *b1 = sqlite3_column_blob(pS1,i1); + int len2 = sqlite3_column_bytes16(pS2,i2); + const unsigned char *b2 = sqlite3_column_blob(pS2,i2); + if( len1!=len2 ){ + x = 0; + }else if( len1==0 ){ + x = 1; + }else{ + x = (b1!=0 && b2!=0 && memcmp(b1,b2,len1)==0); + } + break; + } + /* Fall through into the SQLITE_BLOB case */ } case SQLITE_BLOB: { int len1 = sqlite3_column_bytes(pS1,i1); @@ -268,11 +380,23 @@ static int sameValue(sqlite3_stmt *pS1, int i1, sqlite3_stmt *pS2, int i2){ return x; } +/* +** Print binary data as hex +*/ +static void printHex(const unsigned char *a, int n, int mx){ + int j; + for(j=0; j file line set content [$db one "SELECT content FROM file WHERE name = '$file'"] $::O(text) delete 0.0 end diff --git a/test/memjournal2.test b/test/memjournal2.test index 97d35a98d..ec5ba56da 100644 --- a/test/memjournal2.test +++ b/test/memjournal2.test @@ -10,6 +10,7 @@ #*********************************************************************** # Tests focused on the in-memory journal. # +# TESTRUNNER: slow set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/memsubsys1.test b/test/memsubsys1.test index 41bc11526..fda35a918 100644 --- a/test/memsubsys1.test +++ b/test/memsubsys1.test @@ -174,7 +174,7 @@ do_test memsubsys1-4.4 { } 0 do_test memsubsys1-4.5 { set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] - expr {$maxreq<7000} + expr {$maxreq<9000} } 1 db close diff --git a/test/permutations.test b/test/permutations.test index f2710da0f..add02d27c 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -91,6 +91,7 @@ foreach f [glob -nocomplain \ $testdir/../ext/fts5/test/*.test \ $testdir/../ext/expert/*.test \ $testdir/../ext/lsm1/test/*.test \ + $testdir/../ext/recover/*.test \ ] { lappend alltests $f } @@ -129,9 +130,14 @@ set allquicktests [test_set $alltests -exclude { fts4merge5.test fts3cov.test fts3snippet.test fts3corrupt2.test fts3an.test fts3defer.test fts4langid.test fts3sort.test fts5unicode.test + recovercorrupt.test rtree4.test sessionbig.test + + writecrash.test view3.test + fts5dlidx.test fts5ac.test fts4merge3.test fts5prefix.test + sessionB.test }] if {[info exists ::env(QUICKTEST_INCLUDE)]} { set allquicktests [concat $allquicktests $::env(QUICKTEST_INCLUDE)] @@ -800,6 +806,8 @@ test_suite "inmemory_journal" -description { # This test depends on a successful recovery from the pager error # state. Which is not possible with an in-memory journal fts5fault1.test + + recoverpgsz.test }] ifcapable mem3 { diff --git a/test/pragma.test b/test/pragma.test index ba61882d5..4fc97648b 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -904,15 +904,15 @@ do_test pragma-8.1.2 { PRAGMA schema_version; } } {schema_version 105} -do_test pragma-8.1.3 { - execsql { - PRAGMA schema_version = 106; - } -} {} -do_test pragma-8.1.4 { - execsql { - PRAGMA schema_version; - } +sqlite3_db_config db DEFENSIVE 1 +do_execsql_test pragma-8.1.3 { + PRAGMA schema_version = 106; + PRAGMA schema_version; +} 105 +sqlite3_db_config db DEFENSIVE 0 +do_execsql_test pragma-8.1.4 { + PRAGMA schema_version = 106; + PRAGMA schema_version; } 106 # Check that creating a table modifies the schema-version (this is really diff --git a/test/recover.test b/test/recover.test index 469c43b11..8d9ad013c 100644 --- a/test/recover.test +++ b/test/recover.test @@ -39,8 +39,9 @@ proc compare_dbs {db1 db2} { } } -proc do_recover_test {tn {tsql {}} {res {}}} { - set fd [open "|$::CLI test.db .recover"] +proc recover_with_opts {opts} { + set cmd ".recover $opts" + set fd [open [list |$::CLI test.db $cmd]] fconfigure $fd -encoding binary fconfigure $fd -translation binary set sql [read $fd] @@ -49,6 +50,13 @@ proc do_recover_test {tn {tsql {}} {res {}}} { forcedelete test.db2 sqlite3 db2 test.db2 execsql $sql db2 + db2 close +} + +proc do_recover_test {tn {tsql {}} {res {}}} { + recover_with_opts "" + + sqlite3 db2 test.db2 if {$tsql==""} { uplevel [list do_test $tn [list compare_dbs db db2] {}] } else { @@ -131,4 +139,41 @@ do_recover_test 2.4.1 { reset_db do_recover_test 3.0 +#------------------------------------------------------------------------- +reset_db +execsql { PRAGMA secure_delete = 0 } +execsql { PRAGMA auto_vacuum = 0 } +do_execsql_test 4.0 { + CREATE TABLE t1(a, b, c); + CREATE TABLE t2(d, e, f); + CREATE TABLE t3(g, h, i); + + INSERT INTO t2 VALUES(1, 2, 3); + INSERT INTO t2 VALUES('a', 'b', 'c'); + + INSERT INTO t3 VALUES('one', 'two', 'three'); + DROP TABLE t1; + DROP TABLE t2; +} + +recover_with_opts "" +sqlite3 db2 test.db2 +do_execsql_test -db db2 4.1.1 { + SELECT name FROM sqlite_schema +} {t3 lost_and_found} +do_execsql_test -db db2 4.1.2 { + SELECT id, c0, c1, c2 FROM lost_and_found +} {1 1 2 3 2 a b c} +db2 close + +recover_with_opts -ignore-freelist +sqlite3 db2 test.db2 +do_execsql_test -db db2 4.2.1 { + SELECT name FROM sqlite_schema +} {t3} +do_execsql_test -db db2 4.2.2 { + SELECT * FROM t3 +} {one two three} +db2 close + finish_test diff --git a/test/regexp1.test b/test/regexp1.test index 569dd66c2..0401b13d7 100644 --- a/test/regexp1.test +++ b/test/regexp1.test @@ -261,4 +261,75 @@ do_execsql_test regexp1-3.4 { SELECT * FROM t1 wHERE a REGEXP '日本語'; } {1 日本語} +# 2022-07-03 +# https://sqlite.org/forum/forumpost/96692f8ba5 Issue #2 +# The '$' token in REGEXP contained within other elements. +# +do_execsql_test regexp1-4.1 {SELECT 'xab' REGEXP 'a(b$|cd)';} {1} +do_execsql_test regexp1-4.1b {SELECT 'xab' REGEXP '(b$|cd)';} {1} +do_execsql_test regexp1-4.2 {SELECT 'xaby' REGEXP 'a(b$|cd)';} {0} +do_execsql_test regexp1-4.3 {SELECT 'xacd' REGEXP 'a(b$|cd)';} {1} +do_execsql_test regexp1-4.4 {SELECT 'xacdy' REGEXP 'a(b$|cd)';} {1} +do_execsql_test regexp1-4.5 {SELECT 'xab' REGEXP 'a(cd|b$)';} {1} +do_execsql_test regexp1-4.6 {SELECT 'xaby' REGEXP 'a(cd|b$)';} {0} +do_execsql_test regexp1-4.7 {SELECT 'xacd' REGEXP 'a(cd|b$)';} {1} +do_execsql_test regexp1-4.8 {SELECT 'xacdy' REGEXP 'a(cd|b$)';} {1} +do_execsql_test regexp1-4.9 {SELECT 'xab' REGEXP 'a(cd|b$|e)';} {1} +do_execsql_test regexp1-4.10 {SELECT 'xaby' REGEXP 'a(cd|b$|e)';} {0} +do_execsql_test regexp1-4.11 {SELECT 'xacd' REGEXP 'a(cd|b$|e)';} {1} +do_execsql_test regexp1-4.12 {SELECT 'xacdy' REGEXP 'a(cd|b$|e)';} {1} + +# 2022-07-18 +# https://sqlite.org/forum/forumpost/57cbaf1d0e +# Incorrect bytecode for {M,N} when M is zero. +# +do_execsql_test regexp1-5.1 {SELECT 'fooX' REGEXP '^[a-z][a-z0-9]{0,30}$';} {0} +do_execsql_test regexp1-5.2 {SELECT 'fooX' REGEXP '^[a-z][a-z0-9]{0,30}X$';} {1} +do_execsql_test regexp1-5.3 {SELECT 'fooX' REGEXP '^[a-z][a-z0-9]{0,2}X$';} {1} +do_execsql_test regexp1-5.4 {SELECT 'foooX' REGEXP '^[a-z][a-z0-9]{0,2}X$';} {0} +do_execsql_test regexp1-5.5 {SELECT 'foooX' REGEXP '^[a-z][a-z0-9]{0,3}X$';} {1} + +# 2022-07-18 +# https://sqlite.org/forum/forumpost/18f87fdcdf +# Allow "^" to occur inside of "(..)" +# +do_execsql_test regexp1-6.1 {SELECT 'foo' REGEXP '[a-z]';} {1} +do_execsql_test regexp1-6.2 {SELECT 'foo' REGEXP '^[a-z]+$';} {1} +do_execsql_test regexp1-6.3 {SELECT 'foo' REGEXP '^([a-z]+)$';} {1} +do_execsql_test regexp1-6.4 {SELECT 'foo' REGEXP '(^[a-z]+)$';} {1} +do_execsql_test regexp1-6.5 {SELECT 'foo' REGEXP '(^[a-z]+$)';} {1} +do_execsql_test regexp1-6.6 {SELECT 'abc' REGEXP '(^abc|def)';} {1} +do_execsql_test regexp1-6.7 {SELECT 'xabc' REGEXP '(^abc|def)';} {0} +do_execsql_test regexp1-6.8 {SELECT 'def' REGEXP '(^abc|def)';} {1} +do_execsql_test regexp1-6.9 {SELECT 'xdef' REGEXP '(^abc|def)';} {1} + +# 2022-11-17 +# https://sqlite.org/forum/forumpost/3ffe058b04 +# +do_execsql_test regexp1-7.1 { + SELECT char(0x61,0x7ff,0x62) REGEXP char(0x7ff); +} 1 +do_execsql_test regexp1-7.2 { + SELECT char(0x61,0x800,0x62) REGEXP char(0x800); +} 1 +do_execsql_test regexp1-7.3 { + SELECT char(0x61,0xabc,0x62) REGEXP char(0xabc); +} 1 +do_execsql_test regexp1-7.4 { + SELECT char(0x61,0xfff,0x62) REGEXP char(0xfff); +} 1 +do_execsql_test regexp1-7.5 { + SELECT char(0x61,0x1000,0x62) REGEXP char(0x1000); +} 1 +do_execsql_test regexp1-7.10 { + SELECT char(0x61,0xffff,0x62) REGEXP char(0xffff); +} 1 +do_execsql_test regexp1-7.11 { + SELECT char(0x61,0x10000,0x62) REGEXP char(0x10000); +} 1 +do_execsql_test regexp1-7.12 { + SELECT char(0x61,0x10ffff,0x62) REGEXP char(0x10ffff); +} 1 + + finish_test diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl index 314331aee..6efc44b92 100644 --- a/test/releasetest_data.tcl +++ b/test/releasetest_data.tcl @@ -51,6 +51,7 @@ array set ::Configs [strip_comments { -O2 --disable-amalgamation --disable-shared --enable-session + -DSQLITE_ENABLE_RBU } "Sanitize" { CC=clang -fsanitize=address,undefined diff --git a/test/returning1.test b/test/returning1.test index fb058a643..92e10ee9f 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -376,4 +376,28 @@ do_execsql_test 16.1 { SELECT * FROM t2; } {1 2 3 a b c} + +foreach {tn temp} { + 1 "" + 2 TEMP +} { + reset_db + do_execsql_test 17.$tn.0 " + CREATE $temp TABLE foo ( + fooid INTEGER PRIMARY KEY, + fooval INTEGER NOT NULL UNIQUE, + refcnt INTEGER NOT NULL DEFAULT 1 + ); + " + do_execsql_test 17.$tn.1 { + INSERT INTO foo (fooval) VALUES (17), (4711), (17) + ON CONFLICT DO + UPDATE SET refcnt = refcnt+1 + RETURNING fooid; + } { + 1 2 1 + } +} + + finish_test diff --git a/test/round1.test b/test/round1.test index ba2c79eab..2244a399a 100644 --- a/test/round1.test +++ b/test/round1.test @@ -10,6 +10,7 @@ #*********************************************************************** # Test cases for rounding behavior of floating point values. # +# TESTRUNNER: slow set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/savepoint.test b/test/savepoint.test index eed8a9e70..3952981da 100644 --- a/test/savepoint.test +++ b/test/savepoint.test @@ -16,6 +16,8 @@ source $testdir/tester.tcl source $testdir/lock_common.tcl source $testdir/malloc_common.tcl +forcedelete test2.db + #---------------------------------------------------------------------- # The following tests - savepoint-1.* - test that the SAVEPOINT, RELEASE # and ROLLBACK TO comands are correctly parsed, and that the auto-commit diff --git a/test/seekscan1.test b/test/seekscan1.test new file mode 100644 index 000000000..e68d71449 --- /dev/null +++ b/test/seekscan1.test @@ -0,0 +1,63 @@ +# 2022 October 7 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix seekscan1 + +do_execsql_test 1.0 { + CREATE TABLE t1(a TEXT, b INT, c INT NOT NULL, PRIMARY KEY(a,b,c)); + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<1997) + INSERT INTO t1(a,b,c) SELECT printf('xyz%d',x/10),x/6,x FROM c; + INSERT INTO t1 VALUES('abc',234,6); + INSERT INTO t1 VALUES('abc',345,7); + ANALYZE; +} + +do_execsql_test 1.1 { + SELECT a,b,c FROM t1 + WHERE b IN (234, 345) AND c BETWEEN 6 AND 6.5 AND a='abc' + ORDER BY a, b; +} { + abc 234 6 +} + +do_execsql_test 1.2 { + SELECT a,b,c FROM t1 + WHERE b IN (234, 345) AND c BETWEEN 6 AND 7 AND a='abc' + ORDER BY a, b; +} { + abc 234 6 + abc 345 7 +} + +do_execsql_test 1.3 { + SELECT a,b,c FROM t1 + WHERE b IN (234, 345) AND c >=6 AND a='abc' + ORDER BY a, b; +} { + abc 234 6 + abc 345 7 +} + +do_execsql_test 1.4 { + SELECT a,b,c FROM t1 + WHERE b IN (234, 345) AND c<=7 AND a='abc' + ORDER BY a, b; +} { + abc 234 6 + abc 345 7 +} + + +finish_test diff --git a/test/select3.test b/test/select3.test index 809b54902..4c9d71b4f 100644 --- a/test/select3.test +++ b/test/select3.test @@ -330,4 +330,108 @@ do_execsql_test select3.10.100 { FROM t1; } {{} {}} +#------------------------------------------------------------------------- +# dbsqlfuzz crash-8e17857db2c5a9294c975123ac807156a6559f13.txt +# Associated with the flatten-left-join branch circa 2022-06-23. +# +foreach {tn sql} { + 1 { + CREATE TABLE t1(a TEXT); + CREATE TABLE t2(x INT); + CREATE INDEX t2x ON t2(x); + INSERT INTO t1 VALUES('abc'); + } + 2 { + CREATE TABLE t1(a TEXT); + CREATE TABLE t2(x INT); + INSERT INTO t1 VALUES('abc'); + } + 3 { + CREATE TABLE t1(a TEXT); + CREATE TABLE t2(x INT); + INSERT INTO t1 VALUES('abc'); + PRAGMA automatic_index=OFF; + } +} { + reset_db + do_execsql_test select3-11.$tn.1 $sql + do_execsql_test select3.11.$tn.2 { + SELECT max(a), val FROM t1 LEFT JOIN ( + SELECT 'constant' AS val FROM t2 WHERE x=1234 + ) + } {abc {}} + do_execsql_test select3.11.$tn.3 { + INSERT INTO t2 VALUES(123); + SELECT max(a), val FROM t1 LEFT JOIN ( + SELECT 'constant' AS val FROM t2 WHERE x=1234 + ) + } {abc {}} + do_execsql_test select3.11.$tn.4 { + INSERT INTO t2 VALUES(1234); + SELECT max(a), val FROM t1 LEFT JOIN ( + SELECT 'constant' AS val FROM t2 WHERE x=1234 + ) + } {abc constant} +} + +reset_db +do_execsql_test 12.0 { + CREATE TABLE t1(a); + CREATE TABLE t2(x); +} +do_execsql_test 12.1 { + SELECT count(x), m FROM t1 LEFT JOIN (SELECT x, 59 AS m FROM t2) GROUP BY a; +} +do_execsql_test 12.2 { + INSERT INTO t1 VALUES(1), (1), (2), (3); + SELECT count(x), m FROM t1 LEFT JOIN (SELECT x, 59 AS m FROM t2) GROUP BY a; +} { + 0 {} + 0 {} + 0 {} +} +do_execsql_test 12.3 { + INSERT INTO t2 VALUES(45); + SELECT count(x), m FROM t1 LEFT JOIN (SELECT x, 59 AS m FROM t2) GROUP BY a; +} { + 2 59 + 1 59 + 1 59 +} +do_execsql_test 12.4 { + INSERT INTO t2 VALUES(210); + SELECT count(x), m FROM t1 LEFT JOIN (SELECT x, 59 AS m FROM t2) GROUP BY a; +} { + 4 59 + 2 59 + 2 59 +} +do_execsql_test 12.5 { + INSERT INTO t2 VALUES(NULL); + SELECT count(x), m FROM t1 LEFT JOIN (SELECT x, 59 AS m FROM t2) GROUP BY a; +} { + 4 59 + 2 59 + 2 59 +} +do_execsql_test 12.6 { + DELETE FROM t2; + DELETE FROM t1; + INSERT INTO t1 VALUES('value'); + INSERT INTO t2 VALUES('hello'); +} {} +do_execsql_test 12.7 { + SELECT group_concat(x), m FROM t1 + LEFT JOIN (SELECT x, 59 AS m FROM t2) GROUP BY a; +} { + hello 59 +} +do_execsql_test 12.8 { + SELECT group_concat(x), m, n FROM t1 + LEFT JOIN (SELECT x, 59 AS m, 60 AS n FROM t2) GROUP BY a; +} { + hello 59 60 +} + finish_test + diff --git a/test/selectA.test b/test/selectA.test index ce7f8eba9..7d72bb3fa 100644 --- a/test/selectA.test +++ b/test/selectA.test @@ -1483,4 +1483,28 @@ do_execsql_test 8.1 { AND (0 OR (SELECT 'xyz' INTERSECT SELECT a ORDER BY 1)) } {} +#------------------------------------------------------------------------- +# dbsqlfuzz a34f455c91ad75a0cf8cd9476841903f42930a7a +# +reset_db +do_execsql_test 9.0 { + CREATE TABLE t1(a COLLATE nocase); + CREATE TABLE t2(b COLLATE nocase); + + INSERT INTO t1 VALUES('ABC'); + INSERT INTO t2 VALUES('abc'); +} + +do_execsql_test 9.1 { + SELECT a FROM t1 INTERSECT SELECT b FROM t2; +} {ABC} + +do_execsql_test 9.2 { + SELECT * FROM ( + SELECT a FROM t1 INTERSECT SELECT b FROM t2 + ) WHERE a||'' = 'ABC'; +} {ABC} + + + finish_test diff --git a/test/shared9.test b/test/shared9.test index 1982a593e..5bb46c268 100644 --- a/test/shared9.test +++ b/test/shared9.test @@ -19,7 +19,7 @@ source $testdir/tester.tcl source $testdir/lock_common.tcl set testprefix shared9 -ifcapable !view||!trigger { +ifcapable !view||!trigger||!shared_cache { finish_test return } diff --git a/test/sharedA.test b/test/sharedA.test index 55ed5749b..ef57c89cd 100644 --- a/test/sharedA.test +++ b/test/sharedA.test @@ -19,6 +19,11 @@ if {[run_thread_tests]==0} { finish_test ; return } db close set ::testprefix sharedA +ifcapable !shared_cache { + finish_test + return +} + if {[atomic_batch_write test.db]} { finish_test return diff --git a/test/sharedB.test b/test/sharedB.test index 1b1575507..2a7e49cd9 100644 --- a/test/sharedB.test +++ b/test/sharedB.test @@ -24,6 +24,11 @@ if {[run_thread_tests]==0} { finish_test ; return } db close set ::testprefix sharedB +ifcapable !shared_cache { + finish_test + return +} + set ::enable_shared_cache [sqlite3_enable_shared_cache 1] #------------------------------------------------------------------------- diff --git a/test/shell2.test b/test/shell2.test index 2237404e5..6b5c2bc10 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -191,4 +191,16 @@ do_test shell2-1.4.7 { SELECT 'unclosed; ^--- error here}} +# Verify that safe mode rejects certain UDFs +# Reported at https://sqlite.org/forum/forumpost/07beac8056151b2f +do_test shell2-1.4.8 { + catchcmd "-safe :memory:" { + SELECT edit('DoNotCare');} +} {1 {line 2: cannot use the edit() function in safe mode}} +do_test shell2-1.4.9 { + catchcmd "-safe :memory:" { + SELECT writefile('DoNotCare', x'');} +} {1 {line 2: cannot use the writefile() function in safe mode}} + + finish_test diff --git a/test/speedtest1.c b/test/speedtest1.c index 83c503c8c..12c8d69b5 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -7,7 +7,8 @@ static const char zHelp[] = "Usage: %s [--options] DATABASE\n" "Options:\n" " --autovacuum Enable AUTOVACUUM mode\n" - " --cachesize N Set the cache size to N\n" + " --big-transactions Add BEGIN/END around all large tests\n" + " --cachesize N Set PRAGMA cache_size=N. Note: N is pages, not bytes\n" " --checkpoint Run PRAGMA wal_checkpoint after each test case\n" " --exclusive Enable locking_mode=EXCLUSIVE\n" " --explain Like --sqlonly but with added EXPLAIN keywords\n" @@ -20,6 +21,7 @@ static const char zHelp[] = " --mmap SZ MMAP the first SZ bytes of the database file\n" " --multithread Set multithreaded mode\n" " --nomemstat Disable memory statistics\n" + " --nomutex Open db with SQLITE_OPEN_NOMUTEX\n" " --nosync Set PRAGMA synchronous=OFF\n" " --notnull Add NOT NULL constraints to table columns\n" " --output FILE Store SQL output in FILE\n" @@ -29,6 +31,7 @@ static const char zHelp[] = " --repeat N Repeat each SELECT N times (default: 1)\n" " --reprepare Reprepare each statement upon every invocation\n" " --reserve N Reserve N bytes on each database page\n" + " --script FILE Write an SQL script for the test into FILE\n" " --serialized Set serialized threading mode\n" " --singlethread Set single-threaded mode - disables all mutexing\n" " --sqlonly No-op. Only show the SQL that would have been run.\n" @@ -42,7 +45,8 @@ static const char zHelp[] = " --threads N Use up to N threads for sorting\n" " --utf16be Set text encoding to UTF-16BE\n" " --utf16le Set text encoding to UTF-16LE\n" - " --verify Run additional verification steps.\n" + " --verify Run additional verification steps\n" + " --vfs NAME Use the given (preinstalled) VFS\n" " --without-rowid Use WITHOUT ROWID where appropriate\n" ; @@ -96,6 +100,7 @@ static struct Global { int nRepeat; /* Repeat selects this many times */ int doCheckpoint; /* Run PRAGMA wal_checkpoint after each trans */ int nReserve; /* Reserve bytes */ + int doBigTransactions; /* Enable transactions on tests 410 and 510 */ const char *zWR; /* Might be WITHOUT ROWID */ const char *zNN; /* Might be NOT NULL */ const char *zPK; /* Might be UNIQUE or PRIMARY KEY */ @@ -103,6 +108,7 @@ static struct Global { u64 nResByte; /* Total number of result bytes */ int nResult; /* Size of the current result */ char zResult[3000]; /* Text of the current result */ + FILE *pScript; /* Write an SQL script into this file */ #ifndef SPEEDTEST_OMIT_HASH FILE *hashFile; /* Store all hash results in this file */ HashContext hash; /* Hash of all output */ @@ -370,10 +376,12 @@ int speedtest1_numbername(unsigned int n, char *zOut, int nOut){ #define NAMEWIDTH 60 static const char zDots[] = "......................................................................."; +static int iTestNumber = 0; /* Current test # for begin/end_test(). */ void speedtest1_begin_test(int iTestNum, const char *zTestName, ...){ int n = (int)strlen(zTestName); char *zName; va_list ap; + iTestNumber = iTestNum; va_start(ap, zTestName); zName = sqlite3_vmprintf(zTestName, ap); va_end(ap); @@ -382,6 +390,11 @@ void speedtest1_begin_test(int iTestNum, const char *zTestName, ...){ zName[NAMEWIDTH] = 0; n = NAMEWIDTH; } + if( g.pScript ){ + fprintf(g.pScript,"-- begin test %d %.*s\n", iTestNumber, n, zName) + /* maintenance reminder: ^^^ code in ext/wasm expects %d to be + ** field #4 (as in: cut -d' ' -f4). */; + } if( g.bSqlOnly ){ printf("/* %4d - %s%.*s */\n", iTestNum, zName, NAMEWIDTH-n, zDots); }else{ @@ -402,6 +415,10 @@ void speedtest1_exec(const char*,...); void speedtest1_end_test(void){ sqlite3_int64 iElapseTime = speedtest1_timestamp() - g.iStart; if( g.doCheckpoint ) speedtest1_exec("PRAGMA wal_checkpoint;"); + assert( iTestNumber > 0 ); + if( g.pScript ){ + fprintf(g.pScript,"-- end test %d\n", iTestNumber); + } if( !g.bSqlOnly ){ g.iTotal += iElapseTime; printf("%4d.%03ds\n", (int)(iElapseTime/1000), (int)(iElapseTime%1000)); @@ -410,6 +427,7 @@ void speedtest1_end_test(void){ sqlite3_finalize(g.pStmt); g.pStmt = 0; } + iTestNumber = 0; } /* Report end of testing */ @@ -473,7 +491,11 @@ void speedtest1_exec(const char *zFormat, ...){ printSql(zSql); }else{ char *zErrMsg = 0; - int rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg); + int rc; + if( g.pScript ){ + fprintf(g.pScript,"%s;\n",zSql); + } + rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg); if( zErrMsg ) fatal_error("SQL error: %s\n%s\n", zErrMsg, zSql); if( rc!=SQLITE_OK ) fatal_error("exec error: %s\n", sqlite3_errmsg(g.db)); } @@ -500,6 +522,11 @@ char *speedtest1_once(const char *zFormat, ...){ if( rc ){ fatal_error("SQL error: %s\n", sqlite3_errmsg(g.db)); } + if( g.pScript ){ + char *z = sqlite3_expanded_sql(pStmt); + fprintf(g.pScript,"%s\n",z); + sqlite3_free(z); + } if( sqlite3_step(pStmt)==SQLITE_ROW ){ const char *z = (const char*)sqlite3_column_text(pStmt, 0); if( z ) zResult = sqlite3_mprintf("%s", z); @@ -537,6 +564,11 @@ void speedtest1_run(void){ if( g.bSqlOnly ) return; assert( g.pStmt ); g.nResult = 0; + if( g.pScript ){ + char *z = sqlite3_expanded_sql(g.pStmt); + fprintf(g.pScript,"%s\n",z); + sqlite3_free(z); + } while( sqlite3_step(g.pStmt)==SQLITE_ROW ){ n = sqlite3_column_count(g.pStmt); for(i=0; i=argc-(N) ) fatal_error("missing argument on %s\n", argv[i]) /* Display the version of SQLite being tested */ printf("-- Speedtest1 for SQLite %s %.48s\n", sqlite3_libversion(), sqlite3_sourceid()); @@ -2196,10 +2258,11 @@ int main(int argc, char **argv){ do{ z++; }while( z[0]=='-' ); if( strcmp(z,"autovacuum")==0 ){ doAutovac = 1; + }else if( strcmp(z,"big-transactions")==0 ){ + g.doBigTransactions = 1; }else if( strcmp(z,"cachesize")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); - i++; - cacheSize = integerValue(argv[i]); + ARGC_VALUE_CHECK(1); + cacheSize = integerValue(argv[++i]); }else if( strcmp(z,"exclusive")==0 ){ doExclusive = 1; }else if( strcmp(z,"checkpoint")==0 ){ @@ -2208,20 +2271,20 @@ int main(int argc, char **argv){ g.bSqlOnly = 1; g.bExplain = 1; }else if( strcmp(z,"heap")==0 ){ - if( i>=argc-2 ) fatal_error("missing arguments on %s\n", argv[i]); + ARGC_VALUE_CHECK(2); nHeap = integerValue(argv[i+1]); mnHeap = integerValue(argv[i+2]); i += 2; }else if( strcmp(z,"incrvacuum")==0 ){ doIncrvac = 1; }else if( strcmp(z,"journal")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); zJMode = argv[++i]; }else if( strcmp(z,"key")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); zKey = argv[++i]; }else if( strcmp(z,"lookaside")==0 ){ - if( i>=argc-2 ) fatal_error("missing arguments on %s\n", argv[i]); + ARGC_VALUE_CHECK(2); nLook = integerValue(argv[i+1]); szLook = integerValue(argv[i+2]); i += 2; @@ -2235,9 +2298,11 @@ int main(int argc, char **argv){ #endif #if SQLITE_VERSION_NUMBER>=3007017 }else if( strcmp(z, "mmap")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); mmapSize = integerValue(argv[++i]); #endif + }else if( strcmp(z,"nomutex")==0 ){ + openFlags |= SQLITE_OPEN_NOMUTEX; }else if( strcmp(z,"nosync")==0 ){ noSync = 1; }else if( strcmp(z,"notnull")==0 ){ @@ -2247,7 +2312,7 @@ int main(int argc, char **argv){ fatal_error("The --output option is not supported with" " -DSPEEDTEST_OMIT_HASH\n"); #else - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); i++; if( strcmp(argv[i],"-")==0 ){ g.hashFile = stdout; @@ -2259,10 +2324,10 @@ int main(int argc, char **argv){ } #endif }else if( strcmp(z,"pagesize")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); pageSize = integerValue(argv[++i]); }else if( strcmp(z,"pcache")==0 ){ - if( i>=argc-2 ) fatal_error("missing arguments on %s\n", argv[i]); + ARGC_VALUE_CHECK(2); nPCache = integerValue(argv[i+1]); szPCache = integerValue(argv[i+2]); doPCache = 1; @@ -2270,9 +2335,8 @@ int main(int argc, char **argv){ }else if( strcmp(z,"primarykey")==0 ){ g.zPK = "PRIMARY KEY"; }else if( strcmp(z,"repeat")==0 ){ - if( i>=argc-1 ) fatal_error("missing arguments on %s\n", argv[i]); - g.nRepeat = integerValue(argv[i+1]); - i += 1; + ARGC_VALUE_CHECK(1); + g.nRepeat = integerValue(argv[++i]); }else if( strcmp(z,"reprepare")==0 ){ g.bReprepare = 1; #if SQLITE_VERSION_NUMBER>=3006000 @@ -2281,29 +2345,36 @@ int main(int argc, char **argv){ }else if( strcmp(z,"singlethread")==0 ){ sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); #endif + }else if( strcmp(z,"script")==0 ){ + ARGC_VALUE_CHECK(1); + if( g.pScript ) fclose(g.pScript); + g.pScript = fopen(argv[++i], "wb"); + if( g.pScript==0 ){ + fatal_error("unable to open output file \"%s\"\n", argv[i]); + } }else if( strcmp(z,"sqlonly")==0 ){ g.bSqlOnly = 1; }else if( strcmp(z,"shrink-memory")==0 ){ g.bMemShrink = 1; }else if( strcmp(z,"size")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); g.szTest = integerValue(argv[++i]); }else if( strcmp(z,"stats")==0 ){ showStats = 1; }else if( strcmp(z,"temp")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); i++; if( argv[i][0]<'0' || argv[i][0]>'9' || argv[i][1]!=0 ){ fatal_error("argument to --temp should be integer between 0 and 9"); } g.eTemp = argv[i][0] - '0'; }else if( strcmp(z,"testset")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); zTSet = argv[++i]; }else if( strcmp(z,"trace")==0 ){ doTrace = 1; }else if( strcmp(z,"threads")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); nThread = integerValue(argv[++i]); }else if( strcmp(z,"utf16le")==0 ){ zEncoding = "utf16le"; @@ -2314,8 +2385,11 @@ int main(int argc, char **argv){ #ifndef SPEEDTEST_OMIT_HASH HashInit(); #endif + }else if( strcmp(z,"vfs")==0 ){ + ARGC_VALUE_CHECK(1); + zVfs = argv[++i]; }else if( strcmp(z,"reserve")==0 ){ - if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + ARGC_VALUE_CHECK(1); g.nReserve = atoi(argv[++i]); }else if( strcmp(z,"without-rowid")==0 ){ if( strstr(g.zWR,"WITHOUT")!=0 ){ @@ -2348,7 +2422,7 @@ int main(int argc, char **argv){ argv[i], argv[0]); } } - if( zDbName!=0 ) unlink(zDbName); +#undef ARGC_VALUE_CHECK #if SQLITE_VERSION_NUMBER>=3006001 if( nHeap>0 ){ pHeap = malloc( nHeap ); @@ -2371,8 +2445,23 @@ int main(int argc, char **argv){ #endif sqlite3_initialize(); + if( zDbName!=0 ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs); + /* For some VFSes, e.g. opfs, unlink() is not sufficient. Use the + ** selected (or default) VFS's xDelete method to delete the + ** database. This is specifically important for the "opfs" VFS + ** when running from a WASM build of speedtest1, so that the db + ** can be cleaned up properly. For historical compatibility, we'll + ** also simply unlink(). */ + if( pVfs!=0 ){ + pVfs->xDelete(pVfs, zDbName, 1); + } + unlink(zDbName); + } + /* Open the database and the input file */ - if( sqlite3_open(memDb ? ":memory:" : zDbName, &g.db) ){ + if( sqlite3_open_v2(memDb ? ":memory:" : zDbName, &g.db, + openFlags, zVfs) ){ fatal_error("Cannot open database file: %s\n", zDbName); } #if SQLITE_VERSION_NUMBER>=3006001 @@ -2547,6 +2636,9 @@ int main(int argc, char **argv){ displayLinuxIoStats(stdout); } #endif + if( g.pScript ){ + fclose(g.pScript); + } /* Release memory */ free( pLook ); @@ -2554,3 +2646,13 @@ int main(int argc, char **argv){ free( pHeap ); return 0; } + +#ifdef SQLITE_SPEEDTEST1_WASM +/* +** A workaround for some inconsistent behaviour with how +** main() does (or does not) get exported to WASM. +*/ +int wasm_main(int argc, char **argv){ + return main(argc, argv); +} +#endif diff --git a/test/sqllimits1.test b/test/sqllimits1.test index 6b7d7b609..1c3f66b7f 100644 --- a/test/sqllimits1.test +++ b/test/sqllimits1.test @@ -910,4 +910,17 @@ do_catchsql_test sqllimits1-17.1 " CREATE TABLE $nm (x PRIMARY KEY) " {1 {string or blob too big}} +#------------------------------------------------------------------------- +# +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 10 +do_catchsql_test sqllimits1-18.1 { + CREATE TABLE b1(x); + INSERT INTO b1 VALUES(1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11); +} {0 {}} + +do_catchsql_test sqllimits1-18.2 { + INSERT INTO b1 VALUES(1), (2), (3), (4), (5), (6), (7), (8), (9), (10) + UNION VALUES(11); +} {1 {too many terms in compound SELECT}} + finish_test diff --git a/test/temptable2.test b/test/temptable2.test index d94021449..2ee4adb9f 100644 --- a/test/temptable2.test +++ b/test/temptable2.test @@ -8,6 +8,9 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# +# TESTRUNNER: slow +# set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/tester.tcl b/test/tester.tcl index f65182885..9353b9124 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -1548,6 +1548,47 @@ proc explain_i {sql {db db}} { output2 "---- ------------ ------ ------ ------ ---------------- -- -" } +proc execsql_pp {sql {db db}} { + set nCol 0 + $db eval $sql A { + if {$nCol==0} { + set nCol [llength $A(*)] + foreach c $A(*) { + set aWidth($c) [string length $c] + lappend data $c + } + } + foreach c $A(*) { + set n [string length $A($c)] + if {$n > $aWidth($c)} { + set aWidth($c) $n + } + lappend data $A($c) + } + } + if {$nCol>0} { + set nTotal 0 + foreach e [array names aWidth] { incr nTotal $aWidth($e) } + incr nTotal [expr ($nCol-1) * 3] + incr nTotal 4 + + set fmt "" + foreach c $A(*) { + lappend fmt "% -$aWidth($c)s" + } + set fmt "| [join $fmt { | }] |" + + puts [string repeat - $nTotal] + for {set i 0} {$i < [llength $data]} {incr i $nCol} { + set vals [lrange $data $i [expr $i+$nCol-1]] + puts [format $fmt {*}$vals] + if {$i==0} { puts [string repeat - $nTotal] } + } + puts [string repeat - $nTotal] + } +} + + # Show the VDBE program for an SQL statement but omit the Trace # opcode at the beginning. This procedure can be used to prove # that different SQL statements generate exactly the same VDBE code. @@ -2171,13 +2212,13 @@ proc memdebug_log_sql {filename} { } set escaped "BEGIN; ${tbl}${tbl2}${tbl3}${sql} ; COMMIT;" - set escaped [string map [list "{" "\\{" "}" "\\}"] $escaped] + set escaped [string map [list "{" "\\{" "}" "\\}" "\\" "\\\\"] $escaped] set fd [open $filename w] puts $fd "set BUILTIN {" puts $fd $escaped puts $fd "}" - puts $fd {set BUILTIN [string map [list "\\{" "{" "\\}" "}"] $BUILTIN]} + puts $fd {set BUILTIN [string map [list "\\{" "{" "\\}" "}" "\\\\" "\\"] $BUILTIN]} set mtv [open $::testdir/malloctraceviewer.tcl] set txt [read $mtv] close $mtv diff --git a/test/testrunner.tcl b/test/testrunner.tcl new file mode 100644 index 000000000..da4efde26 --- /dev/null +++ b/test/testrunner.tcl @@ -0,0 +1,705 @@ + +#------------------------------------------------------------------------- +# Usage: +# +proc usage {} { + set a0 testrunner.tcl + + set ::argv [list] + uplevel [list source $::testdir/permutations.test] + + puts stderr "Usage: $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?" + puts stderr "" + puts stderr "where SWITCHES are:" + puts stderr " --jobs NUMBER-OF-JOBS" + puts stderr "" + puts stderr "available PERMUTATION values are:" + set ii 0 + foreach name [lsort [array names ::testspec]] { + if {($ii % 3)==0} { puts -nonewline stderr " " } + puts -nonewline stderr [format "% -22s" $name] + if {($ii % 3)==2} { puts stderr "" } + incr ii + } + puts stderr "" + puts stderr "" + puts stderr "Examples:" + puts stderr " 1) Run the veryquick tests:" + puts stderr " $a0" + puts stderr " 2) Run all test scripts in the source tree:" + puts stderr " $a0 full" + puts stderr " 2) Run the 'memsubsys1' permutation:" + puts stderr " $a0 memsubsys1" + puts stderr " 3) Run all permutations usually run by \[make fulltest\]" + puts stderr " $a0 release" + puts stderr " 4) Run all scripts that match the pattern 'select%':" + puts stderr " $a0 select%" + puts stderr " $a0 all select%" + puts stderr " $a0 full select%" + puts stderr " 5) Run all scripts that are part of the veryquick permutation and match the pattern 'select%':" + puts stderr " $a0 veryquick select%" + puts stderr " 6) Run the 'memsubsys1' permutation, but just those scripts that match 'window%':" + puts stderr " $a0 memsubsys1 window%" + puts stderr " 7) Run all the permutations, but only the scripts that match either 'fts5%' or 'rtree%':" + puts stderr " $a0 release fts5% rtree%" + + exit 1 +} +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------- +# The database schema used by the testrunner.db database. +# +set R(schema) { + DROP TABLE IF EXISTS script; + DROP TABLE IF EXISTS msg; + DROP TABLE IF EXISTS malloc; + + CREATE TABLE script( + config TEXT, + filename TEXT, -- full path to test script + slow BOOLEAN, -- true if script is "slow" + state TEXT CHECK( state IN ('ready', 'running', 'done') ), + testfixtureid, -- Id of process that ran script + time INTEGER, -- Time in ms + nerr INTEGER, -- if 'done', the number of errors + ntest INTEGER, -- if 'done', the number of tests + output TEXT, -- full output of test script + PRIMARY KEY(config, filename) + ); + + CREATE TABLE malloc( + id INTEGER PRIMARY KEY, + nmalloc INTEGER, + nbyte INTEGER, + leaker TEXT + ); + + CREATE TABLE msg( + id INTEGER PRIMARY KEY, + msg TEXT + ); +} +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------- +# Try to estimate a the number of processes to use. +# +# Command [guess_number_of_cores] attempts to glean the number of logical +# cores. Command [default_njob] returns the default value for the --jobs +# switch. +# +proc guess_number_of_cores {} { + set ret 4 + + if {$::tcl_platform(os)=="Darwin"} { + set cmd "sysctl -n hw.logicalcpu" + } else { + set cmd "nproc" + } + catch { + set fd [open "|$cmd" r] + set ret [gets $fd] + close $fd + set ret [expr $ret] + } + return $ret +} + +proc default_njob {} { + set nCore [guess_number_of_cores] + set nHelper [expr int($nCore*0.75)] + expr $nHelper>0 ? $nHelper : 1 +} +#------------------------------------------------------------------------- + + +set R(dbname) [file normalize testrunner.db] +set R(logname) [file normalize testrunner.log] +set R(info_script) [file normalize [info script]] +set R(timeout) 10000 ;# Default busy-timeout for testrunner. +set R(nJob) [default_njob] ;# Default number of helper processes +set R(leaker) "" ;# Name of first script to leak memory + +set R(patternlist) [list] + +set testdir [file dirname $argv0] + +# Parse the command line options. There are two ways to invoke this +# script - to create a helper or coordinator process. If there are +# no helper processes, the coordinator runs test scripts. +# +# To create a helper process: +# +# testrunner.tcl helper ID +# +# where ID is an integer greater than 0. The process will create and +# run tests in the "testdir$ID" directory. Helper processes are only +# created by coordinators - there is no need for a user to create +# helper processes manually. +# +# If the first argument is anything other than "helper", then a coordinator +# process is started. See the implementation of the [usage] proc above for +# details. +# +switch -- [lindex $argv 0] { + helper { + set R(helper) 1 + set R(helper_id) [lindex $argv 1] + set argv [list --testdir=testdir$R(helper_id)] + } + + default { + set R(helper) 0 + set R(helper_id) 0 + + } +} +if {$R(helper)==0} { + for {set ii 0} {$ii < [llength $argv]} {incr ii} { + set a [lindex $argv $ii] + set n [string length $a] + + if {[string range $a 0 0]=="-"} { + if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} { + incr ii + set R(nJob) [lindex $argv $ii] + } else { + usage + } + } else { + lappend R(patternlist) [string map {% *} $a] + } + } + + set argv [list] +} +source $testdir/permutations.test + +#------------------------------------------------------------------------- +# Return a list of tests to run. Each element of the list is itself a +# list of two elements - the name of a permuations.test configuration +# followed by the full path to a test script. i.e.: +# +# {CONFIG FILENAME} {CONFIG FILENAME} ... +# +proc testset_patternlist {patternlist} { + + set first [lindex $patternlist 0] + if {$first=="all"} { set first "full" } + + if {$first=="release"} { + + # The following mirrors the set of test suites invoked by "all.test". + # + set clist { + full + no_optimization memsubsys1 memsubsys2 singlethread + multithread onefile utf16 exclusive persistent_journal + persistent_journal_error no_journal no_journal_error + autovacuum_ioerr no_mutex_try fullmutex journaltest + inmemory_journal pcache0 pcache10 pcache50 pcache90 + pcache100 prepare mmap + } + ifcapable rbu { lappend clist rbu } + if {$::tcl_platform(platform)=="unix"} { + ifcapable !default_autovacuum { + lappend clist autovacuum_crash + } + } + set patternlist [lrange $patternlist 1 end] + + } elseif {[info exists ::testspec($first)]} { + set clist $first + set patternlist [lrange $patternlist 1 end] + } elseif { [llength $patternlist]==0 } { + set clist veryquick + } else { + set clist full + } + + set testset [list] + + foreach config $clist { + catch { array unset O } + array set O $::testspec($config) + foreach f $O(-files) { + if {[file pathtype $f]!="absolute"} { + set f [file join $::testdir $f] + } + lappend testset [list $config [file normalize $f]] + } + } + + if {[llength $patternlist]>0} { + foreach t $testset { + set tail [file tail [lindex $t 1]] + foreach p $patternlist { + if {[string match $p $tail]} { + lappend ret $t + break; + } + } + } + } else { + set ret $testset + } + + set ret +} +#-------------------------------------------------------------------------- + + +proc r_write_db {tcl} { + global R + + sqlite3_test_control_pending_byte 0x010000 + sqlite3 db $R(dbname) + db timeout $R(timeout) + db eval { BEGIN EXCLUSIVE } + + uplevel $tcl + + db eval { COMMIT } + db close +} + +proc make_new_testset {} { + global R + + set tests [testset_patternlist $R(patternlist)] + r_write_db { + db eval $R(schema) + foreach t $tests { + foreach {c s} $t {} + set slow 0 + + set fd [open $s] + for {set ii 0} {$ii<100 && ![eof $fd]} {incr ii} { + set line [gets $fd] + if {[string match -nocase *testrunner:* $line]} { + regexp -nocase {.*testrunner:(.*)} $line -> properties + foreach p $properties { + if {$p=="slow"} { set slow 1 } + } + } + } + close $fd + + db eval { + INSERT INTO script(config, filename, slow, state) + VALUES ($c, $s, $slow, 'ready') + } + } + } +} + +# Find the next job in the database and mark it as 'running'. Then return +# a list consisting of the +# +# CONFIG FILENAME +# +# pair for the test. +# +proc get_next_test {} { + global R + set myid $R(helper_id) + + r_write_db { + set f "" + set c "" + db eval { + SELECT config, filename FROM script WHERE state='ready' + ORDER BY + (slow * (($myid+1) % 2)) DESC, + config!='full', + config, + filename + LIMIT 1 + } { + set c $config + set f $filename + } + if {$f!=""} { + db eval { + UPDATE script SET state='running', testfixtureid=$myid + WHERE (config, filename) = ($c, $f) + } + } + } + + if {$f==""} { return "" } + list $c $f +} + +proc r_testname {config filename} { + set name [file tail $filename] + if {$config!="" && $config!="full" && $config!="veryquick"} { + set name "$config-$name" + } + return $name +} + +proc r_set_test_result {config filename ms nerr ntest output} { + global R + + set f [r_testname $config $filename] + if {$nerr==0} { + set msg "$f... Ok" + } else { + set msg "$f... FAILED - $nerr errors of $ntest tests" + } + append msg " (${ms}ms)" + if {$R(helper)} { + append msg " (helper $R(helper_id))" + } + + sqlite3_shutdown + set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1] + set nByte [sqlite3_memory_used] + if {($nByte>0 || $nMalloc>0) && $R(leaker)==""} { + set R(leaker) $f + } + + r_write_db { + db eval { + UPDATE script + SET state='done', output=$output, nerr=$nerr, ntest=$ntest, time=$ms + WHERE (config, filename)=($config, $filename); + + INSERT INTO msg(msg) VALUES ($msg); + } + } +} + +set R(iNextMsg) 1 +proc r_get_messages {{db ""}} { + global R + + sqlite3_test_control_pending_byte 0x010000 + + if {$db==""} { + sqlite3 rgmhandle $R(dbname) + set dbhandle rgmhandle + $dbhandle timeout $R(timeout) + } else { + set dbhandle $db + } + + $dbhandle transaction { + set next $R(iNextMsg) + set ret [$dbhandle eval {SELECT msg FROM msg WHERE id>=$next}] + set R(iNextMsg) [$dbhandle one {SELECT COALESCE(max(id), 0)+1 FROM msg}] + } + + if {$db==""} { + rgmhandle close + } + + set ret +} + +# This is called after all tests have been run to write the leaked memory +# report into the malloc table of testrunner.db. +# +proc r_memory_report {} { + global R + + sqlite3_shutdown + + set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1] + set nByte [sqlite3_memory_used] + set id $R(helper_id) + set leaker $R(leaker) + + r_write_db { + db eval { + INSERT INTO malloc(id, nMalloc, nByte, leaker) + VALUES($id, $nMalloc, $nByte, $leaker) + } + } +} + + +#-------------------------------------------------------------------------- +# +set ::R_INSTALL_PUTS_WRAPPER { + proc puts_sts_wrapper {args} { + set n [llength $args] + if {$n==1 || ($n==2 && [string first [lindex $args 0] -nonewline]==0)} { + uplevel puts_into_caller $args + } else { + # A channel was explicitly specified. + uplevel puts_sts_original $args + } + } + rename puts puts_sts_original + proc puts {args} { uplevel puts_sts_wrapper $args } +} + +proc r_install_puts_wrapper {} $::R_INSTALL_PUTS_WRAPPER +proc r_uninstall_puts_wrapper {} { + rename puts "" + rename puts_sts_original puts +} + +proc slave_test_script {script} { + + # Create the interpreter used to run the test script. + interp create tinterp + + # Populate some global variables that tester.tcl expects to see. + foreach {var value} [list \ + ::argv0 $::argv0 \ + ::argv {} \ + ::SLAVE 1 \ + ] { + interp eval tinterp [list set $var $value] + } + + # The alias used to access the global test counters. + tinterp alias set_test_counter set_test_counter + + # Set up an empty ::cmdlinearg array in the slave. + interp eval tinterp [list array set ::cmdlinearg [array get ::cmdlinearg]] + + # Set up the ::G array in the slave. + interp eval tinterp [list array set ::G [array get ::G]] + interp eval tinterp [list set ::G(runner.tcl) 1] + + interp eval tinterp $::R_INSTALL_PUTS_WRAPPER + tinterp alias puts_into_caller puts_into_caller + + # Load the various test interfaces implemented in C. + load_testfixture_extensions tinterp + + # Run the test script. + set rc [catch { interp eval tinterp $script } msg opt] + if {$rc} { + puts_into_caller $msg + puts_into_caller [dict get $opt -errorinfo] + incr ::TC(errors) + } + + # Check if the interpreter call [run_thread_tests] + if { [interp eval tinterp {info exists ::run_thread_tests_called}] } { + set ::run_thread_tests_called 1 + } + + # Delete the interpreter used to run the test script. + interp delete tinterp +} + +proc slave_test_file {zFile} { + set tail [file tail $zFile] + + # Remember the value of the shared-cache setting. So that it is possible + # to check afterwards that it was not modified by the test script. + # + ifcapable shared_cache { set scs [sqlite3_enable_shared_cache] } + + # Run the test script in a slave interpreter. + # + unset -nocomplain ::run_thread_tests_called + reset_prng_state + set ::sqlite_open_file_count 0 + set time [time { slave_test_script [list source $zFile] }] + set ms [expr [lindex $time 0] / 1000] + + r_install_puts_wrapper + + # Test that all files opened by the test script were closed. Omit this + # if the test script has "thread" in its name. The open file counter + # is not thread-safe. + # + if {[info exists ::run_thread_tests_called]==0} { + do_test ${tail}-closeallfiles { expr {$::sqlite_open_file_count>0} } {0} + } + set ::sqlite_open_file_count 0 + + # Test that the global "shared-cache" setting was not altered by + # the test script. + # + ifcapable shared_cache { + set res [expr {[sqlite3_enable_shared_cache] == $scs}] + do_test ${tail}-sharedcachesetting [list set {} $res] 1 + } + + # Add some info to the output. + # + output2 "Time: $tail $ms ms" + show_memstats + + r_uninstall_puts_wrapper + return $ms +} + +proc puts_into_caller {args} { + global R + if {[llength $args]==1} { + append R(output) [lindex $args 0] + append R(output) "\n" + } else { + append R(output) [lindex $args 1] + } +} + +#------------------------------------------------------------------------- +# +proc r_final_report {} { + global R + + sqlite3_test_control_pending_byte 0x010000 + sqlite3 db $R(dbname) + + db timeout $R(timeout) + + set errcode 0 + + # Create the text log file. This is just the concatenation of the + # 'output' column of the database for every script that was run. + set fd [open $R(logname) w] + db eval {SELECT output FROM script ORDER BY config!='full',config,filename} { + puts $fd $output + } + close $fd + + # Check if any scripts reported errors. If so, print one line noting + # how many errors, and another identifying the scripts in which they + # occured. Or, if no errors occurred, print out "no errors at all!". + sqlite3 db $R(dbname) + db timeout $R(timeout) + db eval { SELECT sum(nerr) AS nerr, sum(ntest) AS ntest FROM script } { } + puts "$nerr errors from $ntest tests." + if {$nerr>0} { + db eval { SELECT config, filename FROM script WHERE nerr>0 } { + lappend errlist [r_testname $config $filename] + } + puts "Errors in: $errlist" + set errcode 1 + } + + # Check if any scripts were not run or did not finish. Print out a + # line identifying them if there are any. + set errlist [list] + db eval { SELECT config, filename FROM script WHERE state!='done' } { + lappend errlist [r_testname $config $filename] + } + if {$errlist!=[list]} { + puts "Tests DID NOT FINISH (crashed?): $errlist" + set errcode 1 + } + + set bLeak 0 + db eval { + SELECT id, nmalloc, nbyte, leaker FROM malloc + WHERE nmalloc>0 OR nbyte>0 + } { + if {$id==0} { + set line "This process " + } else { + set line "Helper $id " + } + append line "leaked $nbyte byte in $nmalloc allocations" + if {$leaker!=""} { append line " (perhaps in [file tail $leaker])" } + puts $line + set bLeak 1 + } + if {$bLeak==0} { + puts "No leaks - all allocations freed." + } + + db close + + puts "Test database is $R(dbname)" + puts "Test log file is $R(logname)" + if {$errcode} { + puts "This test has FAILED." + } + return $errcode +} + + +if {$R(helper)==0} { + make_new_testset +} + +set R(nHelperRunning) 0 +if {$R(helper)==0 && $R(nJob)>1} { + cd $cmdlinearg(TESTFIXTURE_HOME) + for {set ii 1} {$ii <= $R(nJob)} {incr ii} { + set cmd "[info nameofexec] $R(info_script) helper $ii 2>@1" + puts "Launching helper $ii ($cmd)" + set chan [open "|$cmd" r] + fconfigure $chan -blocking false + fileevent $chan readable [list r_helper_readable $ii $chan] + incr R(nHelperRunning) + } + cd $cmdlinearg(testdir) +} + +proc r_helper_readable {id chan} { + set data [gets $chan] + if {$data!=""} { puts "helper $id:$data" } + if {[eof $chan]} { + puts "helper $id is finished" + incr ::R(nHelperRunning) -1 + close $chan + } +} + +if {$R(nHelperRunning)==0} { + while { ""!=[set t [get_next_test]] } { + set R(output) "" + set TC(count) 0 + set TC(errors) 0 + + foreach {config filename} $t {} + + array set O $::testspec($config) + set ::G(perm:name) $config + set ::G(perm:prefix) $O(-prefix) + set ::G(isquick) 1 + set ::G(perm:dbconfig) $O(-dbconfig) + set ::G(perm:presql) $O(-presql) + + eval $O(-initialize) + set ms [slave_test_file $filename] + eval $O(-shutdown) + + unset -nocomplain ::G(perm:sqlite3_args) + unset ::G(perm:name) + unset ::G(perm:prefix) + unset ::G(perm:dbconfig) + unset ::G(perm:presql) + + r_set_test_result $config $filename $ms $TC(errors) $TC(count) $R(output) + + if {$R(helper)==0} { + foreach msg [r_get_messages] { puts $msg } + } + } + + # Tests are finished - write a record into testrunner.db describing + # any memory leaks. + r_memory_report + +} else { + set TTT 0 + sqlite3 db $R(dbname) + db timeout $R(timeout) + while {$R(nHelperRunning)>0} { + after 250 { incr TTT } + vwait TTT + foreach msg [r_get_messages db] { puts $msg } + } + db close +} + +set errcode 0 +if {$R(helper)==0} { + set errcode [r_final_report] +} + +exit $errcode + diff --git a/test/thread001.test b/test/thread001.test index 7f21fb073..1390f0d99 100644 --- a/test/thread001.test +++ b/test/thread001.test @@ -15,6 +15,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl if {[run_thread_tests]==0} { finish_test ; return } +ifcapable !shared_cache { finish_test ; return } set ::enable_shared_cache [sqlite3_enable_shared_cache] diff --git a/test/thread002.test b/test/thread002.test index b39c9ae7c..3e9439e21 100644 --- a/test/thread002.test +++ b/test/thread002.test @@ -19,7 +19,7 @@ set testdir [file dirname $argv0] set do_not_use_codec 1 source $testdir/tester.tcl if {[run_thread_tests]==0} { finish_test ; return } - +ifcapable !shared_cache { finish_test ; return } db close set ::enable_shared_cache [sqlite3_enable_shared_cache 1] diff --git a/test/tkt-80e031a00f.test b/test/tkt-80e031a00f.test index 4ad0a772a..56449dba2 100644 --- a/test/tkt-80e031a00f.test +++ b/test/tkt-80e031a00f.test @@ -24,11 +24,10 @@ source $testdir/malloc_common.tcl # result of IN is false and the result of NOT IN is true, regardless of # the left operand and even if the left operand is NULL. # -# EVIDENCE-OF: R-13595-45863 Note that SQLite allows the parenthesized +# EVIDENCE-OF: R-64309-54027 Note that SQLite allows the parenthesized # list of scalar values on the right-hand side of an IN or NOT IN -# operator to be an empty list but most other SQL database database -# engines and the SQL92 standard require the list to contain at least -# one element. +# operator to be an empty list but most other SQL database engines and +# the SQL92 standard require the list to contain at least one element. # do_execsql_test tkt-80e031a00f.1 {SELECT 1 IN ()} 0 do_execsql_test tkt-80e031a00f.1b {SELECT 1 IN (2)} 0 diff --git a/test/tkt-f7b4edec.test b/test/tkt-f7b4edec.test index f6d3d5b95..d21da3191 100644 --- a/test/tkt-f7b4edec.test +++ b/test/tkt-f7b4edec.test @@ -16,6 +16,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +ifcapable !shared_cache { finish_test ; return } # Open two database connections to the same database file in # shared cache mode. Create update hooks that will fire on diff --git a/test/unionall.test b/test/unionall.test index 7554a3b30..7783c04a6 100644 --- a/test/unionall.test +++ b/test/unionall.test @@ -55,6 +55,27 @@ do_execsql_test 1.3 { } {1 one 2 two 5 five 6 six} +# 2022-10-31 part of ticket 57c47526c34f01e8 +# The queries below were causing an assertion fault in +# the comparison operators of the VDBE. +# +reset_db +database_never_corrupt +optimization_control db all 0 +do_execsql_test 1.10 { + CREATE TABLE t0(c0 INT); + INSERT INTO t0 VALUES(0); + CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1_a VALUES(1,'one'); + CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT); + INSERT INTO t1_b VALUES(2,'two'); + CREATE VIEW t1 AS SELECT a, b FROM t1_a UNION ALL SELECT c, c FROM t1_b; + SELECT * FROM (SELECT t1.a, t1.b AS b, t0.c0 FROM t0, t1); +} {1 one 0 2 2 0} +do_execsql_test 1.11 { + SELECT * FROM (SELECT t1.a, t1.b AS b, t0.c0 FROM t0, t1) WHERE b=2; +} {2 2 0} + #------------------------------------------------------------------------- reset_db @@ -364,4 +385,60 @@ do_execsql_test 7.1 { ORDER BY x, y; } {0 1 | 0 101 | 0 102 | 1 1 | 1 101 | 1 102 | 100 1 | 100 101 | 100 102 | 101 1 | 101 101 | 101 102 |} +# 2022-10-31 ticket https://sqlite.org/src/info/57c47526c34f01e8 +# dbsqlfuzz 37230460b46b3b6049f0d768eb801f3428189382 +# UNION ALL subqueries or views which have arms with different +# affinities should not be flattened. +# +reset_db +do_execsql_test 8.1 { + CREATE TABLE t0(c0 INT); + INSERT INTO t0 VALUES(0); + CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1_a VALUES(1,'one'); + INSERT INTO t1_a VALUES(4,'four'); + CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT); + INSERT INTO t1_b VALUES(2,'two'); + INSERT INTO t1_b VALUES(5,'five'); + CREATE TABLE t1_c(e INTEGER PRIMARY KEY, f TEXT); + INSERT INTO t1_c VALUES(3,'three'); + INSERT INTO t1_c VALUES(6,'six'); + CREATE VIEW v0(c0) AS SELECT CAST(t0.c0 AS INTEGER) FROM t0; + CREATE VIEW t1 AS + SELECT a, b FROM t1_a UNION ALL + SELECT c, c FROM t1_b UNION ALL + SELECT e, f FROM t1_c; +} +optimization_control db all 1 +do_execsql_test 8.2 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; +} {2 2 0 {}} +do_execsql_test 8.3 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; +} {} +do_execsql_test 8.4 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; +} {2 2 0 {}} +optimization_control db query-flattener,push-down 0 +do_execsql_test 8.5 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; +} {2 2 0 {}} +do_execsql_test 8.6 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; +} {} +do_execsql_test 8.7 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; +} {2 2 0 {}} +optimization_control db all 0 +do_execsql_test 8.8 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; +} {2 2 0 {}} +do_execsql_test 8.9 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; +} {} +do_execsql_test 8.10 { + SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; +} {2 2 0 {}} + + finish_test diff --git a/test/uri.test b/test/uri.test index 0931a25e5..2b388c400 100644 --- a/test/uri.test +++ b/test/uri.test @@ -194,6 +194,7 @@ foreach {tn mode create_ok write_ok readonly_ok} { } $A($readonly_ok) } +ifcapable shared_cache { set orig [sqlite3_enable_shared_cache] foreach {tn options sc_default is_shared} { 1 "" 1 1 @@ -223,6 +224,7 @@ foreach {tn options sc_default is_shared} { db2 close } +} ;# end ifcapable shared_cache do_test 4.3.1 { list [catch {sqlite3 db "file:test.db?mode=rc"} msg] $msg diff --git a/test/vacuum-into.test b/test/vacuum-into.test index a46d95cf1..98692a108 100644 --- a/test/vacuum-into.test +++ b/test/vacuum-into.test @@ -133,4 +133,58 @@ if {[wal_is_capable]} { } {1024 ok} } +#------------------------------------------------------------------------- + +testvfs tvfs -default 1 +tvfs filter xSync +tvfs script xSyncCb +proc xSyncCb {method file fileid flags} { + incr ::sync($flags) +} + +reset_db + +do_execsql_test vacuum-into-700 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); +} + +foreach {tn pragma res} { + 710 { + PRAGMA synchronous = normal + } {normal 2} + 720 { + PRAGMA synchronous = full + } {normal 3} + 730 { + PRAGMA synchronous = off + } {} + 740 { + PRAGMA synchronous = extra; + } {normal 3} + 750 { + PRAGMA fullfsync = 1; + PRAGMA synchronous = full; + } {full|dataonly 1 full 2} +} { + + forcedelete test.db2 + array unset ::sync + do_execsql_test vacuum-into-$tn.1 " + $pragma ; + VACUUM INTO 'test.db2' + " + + do_test vacuum-into-$tn.2 { + array get ::sync + } $res +} + +db close +tvfs delete + + finish_test + + + diff --git a/test/vacuum6.test b/test/vacuum6.test index e9a1542f2..f80ff7546 100644 --- a/test/vacuum6.test +++ b/test/vacuum6.test @@ -11,6 +11,7 @@ # # This file implements a test for VACUUM on attached databases. # +# TESTRUNNER: slow set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/vacuummem.test b/test/vacuummem.test index 0f77c1b40..9668b0ea9 100644 --- a/test/vacuummem.test +++ b/test/vacuummem.test @@ -12,6 +12,7 @@ # focus of this file is testing that the VACUUM statement correctly # frees any memory used for a temporary cache. # +# TESTRUNNER: slow set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/view.test b/test/view.test index 85202d7d0..b0b6da268 100644 --- a/test/view.test +++ b/test/view.test @@ -649,41 +649,6 @@ do_test view-20.1 { } } {} -# Ticket [d58ccbb3f1b]: Prevent Table.nRef overflow. -db close -sqlite3 db :memory: -do_test view-21.1 { - catchsql { - CREATE TABLE t1(x); - INSERT INTO t1 VALUES(5); - CREATE VIEW v1 AS SELECT x*2 FROM t1; - CREATE VIEW v2 AS SELECT * FROM v1 UNION SELECT * FROM v1; - CREATE VIEW v4 AS SELECT * FROM v2 UNION SELECT * FROM v2; - CREATE VIEW v8 AS SELECT * FROM v4 UNION SELECT * FROM v4; - CREATE VIEW v16 AS SELECT * FROM v8 UNION SELECT * FROM v8; - CREATE VIEW v32 AS SELECT * FROM v16 UNION SELECT * FROM v16; - CREATE VIEW v64 AS SELECT * FROM v32 UNION SELECT * FROM v32; - CREATE VIEW v128 AS SELECT * FROM v64 UNION SELECT * FROM v64; - CREATE VIEW v256 AS SELECT * FROM v128 UNION SELECT * FROM v128; - CREATE VIEW v512 AS SELECT * FROM v256 UNION SELECT * FROM v256; - CREATE VIEW v1024 AS SELECT * FROM v512 UNION SELECT * FROM v512; - CREATE VIEW v2048 AS SELECT * FROM v1024 UNION SELECT * FROM v1024; - CREATE VIEW v4096 AS SELECT * FROM v2048 UNION SELECT * FROM v2048; - CREATE VIEW v8192 AS SELECT * FROM v4096 UNION SELECT * FROM v4096; - CREATE VIEW v16384 AS SELECT * FROM v8192 UNION SELECT * FROM v8192; - CREATE VIEW v32768 AS SELECT * FROM v16384 UNION SELECT * FROM v16384; - SELECT * FROM v32768 UNION SELECT * FROM v32768; - } -} {1 {too many references to "v1": max 65535}} -ifcapable progress { - do_test view-21.2 { - db progress 1000 {expr 1} - catchsql { - SELECT * FROM v32768; - } - } {1 interrupted} -} - db close sqlite3 db :memory: do_execsql_test view-22.1 { diff --git a/test/view3.test b/test/view3.test new file mode 100644 index 000000000..60dd694bf --- /dev/null +++ b/test/view3.test @@ -0,0 +1,60 @@ +# 2022 July 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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing VIEW statements. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Omit this entire file if the library is not configured with views enabled. +ifcapable !view { + finish_test + return +} +set testprefix view3 + +# Ticket [d58ccbb3f1b]: Prevent Table.nRef overflow. +db close +sqlite3 db :memory: +do_test 1.1 { + catchsql { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(5); + CREATE VIEW v1 AS SELECT x*2 FROM t1; + CREATE VIEW v2 AS SELECT * FROM v1 UNION SELECT * FROM v1; + CREATE VIEW v4 AS SELECT * FROM v2 UNION SELECT * FROM v2; + CREATE VIEW v8 AS SELECT * FROM v4 UNION SELECT * FROM v4; + CREATE VIEW v16 AS SELECT * FROM v8 UNION SELECT * FROM v8; + CREATE VIEW v32 AS SELECT * FROM v16 UNION SELECT * FROM v16; + CREATE VIEW v64 AS SELECT * FROM v32 UNION SELECT * FROM v32; + CREATE VIEW v128 AS SELECT * FROM v64 UNION SELECT * FROM v64; + CREATE VIEW v256 AS SELECT * FROM v128 UNION SELECT * FROM v128; + CREATE VIEW v512 AS SELECT * FROM v256 UNION SELECT * FROM v256; + CREATE VIEW v1024 AS SELECT * FROM v512 UNION SELECT * FROM v512; + CREATE VIEW v2048 AS SELECT * FROM v1024 UNION SELECT * FROM v1024; + CREATE VIEW v4096 AS SELECT * FROM v2048 UNION SELECT * FROM v2048; + CREATE VIEW v8192 AS SELECT * FROM v4096 UNION SELECT * FROM v4096; + CREATE VIEW v16384 AS SELECT * FROM v8192 UNION SELECT * FROM v8192; + CREATE VIEW v32768 AS SELECT * FROM v16384 UNION SELECT * FROM v16384; + SELECT * FROM v32768 UNION SELECT * FROM v32768; + } +} {1 {too many references to "v1": max 65535}} +ifcapable progress { + do_test 1.2 { + db progress 1000 {expr 1} + catchsql { + SELECT * FROM v32768; + } + } {1 interrupted} +} + + +finish_test diff --git a/test/vt02.c b/test/vt02.c new file mode 100644 index 000000000..40bd6fcb3 --- /dev/null +++ b/test/vt02.c @@ -0,0 +1,1019 @@ +/* +** This file implements an eponymous, read-only table-valued function +** (a virtual table) designed to be used for testing. We are not aware +** of any practical real-world use case for the virtual table. +** +** This virtual table originated in the TH3 test suite. It is still used +** there, but has now been copied into the public SQLite source tree and +** reused for a variety of testing purpose. The name "vt02" comes from the +** fact that there are many different testing virtual tables in TH3, of which +** this one is the second. +** +** ## SUBJECT TO CHANGE +** +** Because this virtual table is intended for testing, its interface is not +** guaranteed to be stable across releases. Future releases may contain +** changes in the vt02 design and interface. +** +** ## OVERVIEW +** +** The vt02 table-valued function has 10000 rows with 5 data columns. +** Column X contains all integer values between 0 and 9999 inclusive. +** Columns A, B, C, and D contain the individual base-10 digits associated +** with each X value: +** +** X A B C D +** ---- - - - - +** 0 0 0 0 0 +** 1 0 0 0 1 +** 2 0 0 0 2 +** ... +** 4998 4 9 9 8 +** 4999 4 9 9 9 +** 5000 5 0 0 0 +** ... +** 9995 9 9 9 5 +** 9996 9 9 9 6 +** 9997 9 9 9 7 +** +** The xBestIndex method recognizes a variety of equality constraints +** and attempts to optimize its output accordingly. +** +** x=... +** a=... +** a=... AND b=... +** a=... AND b=... AND c=... +** a=... AND b=... AND c=... AND d=... +** +** Various ORDER BY constraints are also recognized and consumed. The +** OFFSET constraint is recognized and consumed. +** +** ## TABLE-VALUED FUNCTION +** +** The vt02 virtual table is eponymous and has two hidden columns, meaning +** that it can functions a table-valued function. The two hidden columns +** are "flags" and "logtab", in that order. The "flags" column can be set +** to an integer where various bits enable or disable behaviors of the +** virtual table. The "logtab" can set to the name of an ordinary SQLite +** table into which is written information about each call to xBestIndex. +** +** The bits of "flags" are as follows: +** +** 0x01 Ignore the aConstraint[].usable flag. This might +** result in the xBestIndex method incorrectly using +** unusable entries in the aConstraint[] array, which +** should result in the SQLite core detecting and +** reporting that the virtual table is not behaving +** to spec. +** +** 0x02 Do not set the orderByConsumed flag, even if it +** could be set. +** +** 0x04 Do not consume the OFFSET constraint, if there is +** one. Instead, let the generated byte-code visit +** and ignore the first few columns of output. +** +** 0x08 Use sqlite3_mprintf() to allocate an idxStr string. +** The string is never used, but allocating it does +** test the idxStr deallocation logic inside of the +** SQLite core. +** +** 0x10 Cause the xBestIndex method to generate an idxNum +** that xFilter does not understand, thus causing +** the OP_VFilter opcode to raise an error. +** +** 0x20 Set the omit flag for all equality constraints on +** columns X, A, B, C, and D that are used to limit +** the search. +** +** 0x40 Add all constraints against X,A,B,C,D to the +** vector of results sent to xFilter. Only the first +** few are used, as required by idxNum. +** +** Because these flags take effect during xBestIndex, the RHS of the +** flag= constraint must be accessible. In other words, the RHS of flag= +** needs to be an integer literal, not another column of a join or a +** bound parameter. +** +** ## LOGGING OUTPUT +** +** If the "logtab" columns is set, then each call to the xBestIndex method +** inserts multiple rows into the table identified by "logtab". These +** rows collectively show the content of the sqlite3_index_info object and +** other context associated with the xBestIndex call. +** +** If the table named by "logtab" does not previously exist, it is created +** automatically. The schema for the logtab table is like this: +** +** CREATE TEMP TABLE vt02_log( +** bi INT, -- BestIndex call counter +** vn TEXT, -- Variable Name +** ix INT, -- Index or value +** cn TEXT, -- Column Name +** op INT, -- Opcode or "DESC" value +** ux INT, -- "Usable" flag +** ra BOOLEAN, -- Right-hand side Available. +** rhs ANY, -- Right-Hand Side value +** cs TEXT -- Collating Sequence for this constraint +** ); +** +** Because logging happens during xBestIindex, the RHS value of "logtab" must +** be known to xBestIndex, which means it must be a string literal, not a +** column in a join, or a bound parameter. +** +** ## VIRTUAL TABLE SCHEMA +** +** CREATE TABLE vt02( +** x INT, -- integer between 0 and 9999 inclusive +** a INT, -- The 1000s digit +** b INT, -- The 100s digit +** c INT, -- The 10s digit +** d INT, -- The 1s digit +** flags INT HIDDEN, -- Option flags +** logtab TEXT HIDDEN, -- Name of table into which to log xBestIndex +** ); +** +** ## COMPILING AND RUNNING +** +** This file can also be compiled separately as a loadable extension +** for SQLite (as long as the -DTH3_VERSION is not defined). To compile as a +** loadable extension do his: +** +** gcc -Wall -g -shared -fPIC -I. -DSQLITE_DEBUG vt02.c -o vt02.so +** +** Or on Windows: +** +** cl vt02.c -link -dll -out:vt02.dll +** +** Then load into the CLI using: +** +** .load ./vt02 sqlite3_vt02_init +** +** ## IDXNUM SUMMARY +** +** The xBestIndex method communicates the query plan to xFilter using +** the idxNum value, as follows: +** +** 0 unconstrained +** 1 X=argv[0] +** 2 A=argv[0] +** 3 A=argv[0], B=argv[1] +** 4 A=argv[0], B=argv[1], C=argv[2] +** 5 A=argv[0], B=argv[1], C=argv[2], D=argv[3] +** 6 A=argv[0], D IN argv[2] +** 7 A=argv[0], B=argv[2], D IN argv[3] +** 8 A=argv[0], B=argv[2], C=argv[3], D IN argv[4] +** 1x increment by 10 +** 2x increment by 100 +** 3x increment by 1000 +** 1xx Use offset provided by argv[N] +*/ +#ifndef TH3_VERSION + /* These bits for separate compilation as a loadable extension, only */ + #include "sqlite3ext.h" + SQLITE_EXTENSION_INIT1 + #include + #include + #include +#endif + +/* Forward declarations */ +typedef struct vt02_vtab vt02_vtab; +typedef struct vt02_cur vt02_cur; + +/* +** The complete virtual table +*/ +struct vt02_vtab { + sqlite3_vtab parent; /* Base clase. Must be first. */ + sqlite3 *db; /* Database connection */ + int busy; /* Currently running xBestIndex */ +}; + +#define VT02_IGNORE_USABLE 0x0001 /* Ignore usable flags */ +#define VT02_NO_SORT_OPT 0x0002 /* Do not do any sorting optimizations */ +#define VT02_NO_OFFSET 0x0004 /* Omit the offset optimization */ +#define VT02_ALLOC_IDXSTR 0x0008 /* Alloate an idxStr */ +#define VT02_BAD_IDXNUM 0x0010 /* Generate an invalid idxNum */ + +/* +** A cursor +*/ +struct vt02_cur { + sqlite3_vtab_cursor parent; /* Base class. Must be first */ + sqlite3_int64 i; /* Current entry */ + sqlite3_int64 iEof; /* Indicate EOF when reaching this value */ + int iIncr; /* Amount by which to increment */ + unsigned int mD; /* Mask of allowed D-column values */ +}; + +/* The xConnect method */ +int vt02Connect( + sqlite3 *db, /* The database connection */ + void *pAux, /* Pointer to an alternative schema */ + int argc, /* Number of arguments */ + const char *const*argv, /* Text of the arguments */ + sqlite3_vtab **ppVTab, /* Write the new vtab here */ + char **pzErr /* Error message written here */ +){ + vt02_vtab *pVtab; + int rc; + const char *zSchema = (const char*)pAux; + static const char zDefaultSchema[] = + "CREATE TABLE x(x INT, a INT, b INT, c INT, d INT," + " flags INT HIDDEN, logtab TEXT HIDDEN);"; +#define VT02_COL_X 0 +#define VT02_COL_A 1 +#define VT02_COL_B 2 +#define VT02_COL_C 3 +#define VT02_COL_D 4 +#define VT02_COL_FLAGS 5 +#define VT02_COL_LOGTAB 6 +#define VT02_COL_NONE 7 + + pVtab = sqlite3_malloc( sizeof(*pVtab) ); + if( pVtab==0 ){ + *pzErr = sqlite3_mprintf("out of memory"); + return SQLITE_NOMEM; + } + memset(pVtab, 0, sizeof(*pVtab)); + pVtab->db = db; + rc = sqlite3_declare_vtab(db, zSchema ? zSchema : zDefaultSchema); + if( rc ){ + sqlite3_free(pVtab); + }else{ + *ppVTab = &pVtab->parent; + } + return rc; +} + +/* the xDisconnect method +*/ +int vt02Disconnect(sqlite3_vtab *pVTab){ + sqlite3_free(pVTab); + return SQLITE_OK; +} + +/* Put an error message into the zErrMsg string of the virtual table. +*/ +static void vt02ErrMsg(sqlite3_vtab *pVtab, const char *zFormat, ...){ + va_list ap; + sqlite3_free(pVtab->zErrMsg); + va_start(ap, zFormat); + pVtab->zErrMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); +} + + +/* Open a cursor for scanning +*/ +static int vt02Open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + vt02_cur *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ){ + vt02ErrMsg(pVTab, "out of memory"); + return SQLITE_NOMEM; + } + *ppCursor = &pCur->parent; + pCur->i = -1; + return SQLITE_OK; +} + +/* Close a cursor +*/ +static int vt02Close(sqlite3_vtab_cursor *pCursor){ + vt02_cur *pCur = (vt02_cur*)pCursor; + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* Return TRUE if we are at the end of the BVS and there are +** no more entries. +*/ +static int vt02Eof(sqlite3_vtab_cursor *pCursor){ + vt02_cur *pCur = (vt02_cur*)pCursor; + return pCur->i<0 || pCur->i>=pCur->iEof; +} + +/* Advance the cursor to the next row in the table +*/ +static int vt02Next(sqlite3_vtab_cursor *pCursor){ + vt02_cur *pCur = (vt02_cur*)pCursor; + do{ + pCur->i += pCur->iIncr; + if( pCur->i<0 ) pCur->i = pCur->iEof; + }while( (pCur->mD & (1<<(pCur->i%10)))==0 && pCur->iiEof ); + return SQLITE_OK; +} + +/* Rewind a cursor back to the beginning of its scan. +** +** Scanning is always increasing. +** +** idxNum +** 0 unconstrained +** 1 X=argv[0] +** 2 A=argv[0] +** 3 A=argv[0], B=argv[1] +** 4 A=argv[0], B=argv[1], C=argv[2] +** 5 A=argv[0], B=argv[1], C=argv[2], D=argv[3] +** 6 A=argv[0], D IN argv[2] +** 7 A=argv[0], B=argv[2], D IN argv[3] +** 8 A=argv[0], B=argv[2], C=argv[3], D IN argv[4] +** 1x increment by 10 +** 2x increment by 100 +** 3x increment by 1000 +** 1xx Use offset provided by argv[N] +*/ +static int vt02Filter( + sqlite3_vtab_cursor *pCursor, /* The cursor to rewind */ + int idxNum, /* Search strategy */ + const char *idxStr, /* Not used */ + int argc, /* Not used */ + sqlite3_value **argv /* Not used */ +){ + vt02_cur *pCur = (vt02_cur*)pCursor; /* The vt02 cursor */ + int bUseOffset = 0; /* True to use OFFSET value */ + int iArg = 0; /* argv[] values used so far */ + int iOrigIdxNum = idxNum; /* Original value for idxNum */ + + pCur->iIncr = 1; + pCur->mD = 0x3ff; + if( idxNum>=100 ){ + bUseOffset = 1; + idxNum -= 100; + } + if( idxNum<0 || idxNum>38 ) goto vt02_bad_idxnum; + while( idxNum>=10 ){ + pCur->iIncr *= 10; + idxNum -= 10; + } + if( idxNum==0 ){ + pCur->i = 0; + pCur->iEof = 10000; + }else if( idxNum==1 ){ + pCur->i = sqlite3_value_int64(argv[0]); + if( pCur->i<0 ) pCur->i = -1; + if( pCur->i>9999 ) pCur->i = 10000; + pCur->iEof = pCur->i+1; + if( pCur->i<0 || pCur->i>9999 ) pCur->i = pCur->iEof; + }else if( idxNum>=2 && idxNum<=5 ){ + int i, e, m; + e = idxNum - 2; + assert( e<=argc-1 ); + pCur->i = 0; + for(m=1000, i=0; i<=e; i++, m /= 10){ + sqlite3_int64 v = sqlite3_value_int64(argv[iArg++]); + if( v<0 ) v = 0; + if( v>9 ) v = 9; + pCur->i += m*v; + pCur->iEof = pCur->i+m; + } + }else if( idxNum>=6 && idxNum<=8 ){ + int i, e, m, rc; + sqlite3_value *pIn, *pVal; + e = idxNum - 6; + assert( e<=argc-2 ); + pCur->i = 0; + for(m=1000, i=0; i<=e; i++, m /= 10){ + sqlite3_int64 v; + pVal = 0; + if( sqlite3_vtab_in_first(0, &pVal)!=SQLITE_MISUSE + || sqlite3_vtab_in_first(argv[iArg], &pVal)!=SQLITE_MISUSE + ){ + vt02ErrMsg(pCursor->pVtab, + "unexpected success from sqlite3_vtab_in_first()"); + return SQLITE_ERROR; + } + v = sqlite3_value_int64(argv[iArg++]); + if( v<0 ) v = 0; + if( v>9 ) v = 9; + pCur->i += m*v; + pCur->iEof = pCur->i+m; + } + pCur->mD = 0; + pIn = argv[iArg++]; + assert( sqlite3_value_type(pIn)==SQLITE_NULL ); + for( rc = sqlite3_vtab_in_first(pIn, &pVal); + rc==SQLITE_OK && pVal!=0; + rc = sqlite3_vtab_in_next(pIn, &pVal) + ){ + int eType = sqlite3_value_numeric_type(pVal); + if( eType==SQLITE_FLOAT ){ + double r = sqlite3_value_double(pVal); + if( r<0.0 || r>9.0 || r!=(int)r ) continue; + }else if( eType!=SQLITE_INTEGER ){ + continue; + } + i = sqlite3_value_int(pVal); + if( i<0 || i>9 ) continue; + pCur->mD |= 1<pVtab, "Error from sqlite3_vtab_in_first/next()"); + return rc; + } + }else{ + goto vt02_bad_idxnum; + } + if( bUseOffset ){ + int nSkip = sqlite3_value_int(argv[iArg]); + while( nSkip-- > 0 ) vt02Next(pCursor); + } + return SQLITE_OK; + +vt02_bad_idxnum: + vt02ErrMsg(pCursor->pVtab, "invalid idxNum for vt02: %d", iOrigIdxNum); + return SQLITE_ERROR; +} + +/* Return the Nth column of the current row. +*/ +static int vt02Column( + sqlite3_vtab_cursor *pCursor, + sqlite3_context *context, + int N +){ + vt02_cur *pCur = (vt02_cur*)pCursor; + int v = pCur->i; + if( N==VT02_COL_X ){ + sqlite3_result_int(context, v); + }else if( N>=VT02_COL_A && N<=VT02_COL_D ){ + static const int iDivisor[] = { 1, 1000, 100, 10, 1 }; + v = (v/iDivisor[N])%10; + sqlite3_result_int(context, v); + } + return SQLITE_OK; +} + +/* Return the rowid of the current row +*/ +static int vt02Rowid(sqlite3_vtab_cursor *pCursor, sqlite3_int64 *pRowid){ + vt02_cur *pCur = (vt02_cur*)pCursor; + *pRowid = pCur->i+1; + return SQLITE_OK; +} + +/************************************************************************* +** Logging Subsystem +** +** The sqlite3BestIndexLog() routine implements a logging system for +** xBestIndex calls. This code is portable to any virtual table. +** +** sqlite3BestIndexLog() is the main routine, sqlite3RunSql() is a +** helper routine used for running various SQL statements as part of +** creating the log. +** +** These two routines should be portable to other virtual tables. Simply +** extract this code and call sqlite3BestIndexLog() near the end of the +** xBestIndex method in cases where logging is desired. +*/ +/* +** Run SQL on behalf of sqlite3BestIndexLog. +** +** Construct the SQL using the zFormat string and subsequent arguments. +** Or if zFormat is NULL, take the SQL as the first argument after the +** zFormat. In either case, the dynamically allocated SQL string is +** freed after it has been run. If something goes wrong with the SQL, +** then an error is left in pVTab->zErrMsg. +*/ +static void sqlite3RunSql( + sqlite3 *db, /* Run the SQL on this database connection */ + sqlite3_vtab *pVTab, /* Report errors to this virtual table */ + const char *zFormat, /* Format string for SQL, or NULL */ + ... /* Arguments, according to the format string */ +){ + char *zSql; + + va_list ap; + va_start(ap, zFormat); + if( zFormat==0 ){ + zSql = va_arg(ap, char*); + }else{ + zSql = sqlite3_vmprintf(zFormat, ap); + } + va_end(ap); + if( zSql ){ + char *zErrMsg = 0; + (void)sqlite3_exec(db, zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + if( pVTab->zErrMsg==0 ){ + pVTab->zErrMsg = sqlite3_mprintf("%s in [%s]", zErrMsg, zSql); + } + sqlite3_free(zErrMsg); + } + sqlite3_free(zSql); + } +} + +/* +** Record information about each xBestIndex method call in a separate +** table: +** +** CREATE TEMP TABLE [log-table-name] ( +** bi INT, -- BestIndex call number +** vn TEXT, -- Variable Name +** ix INT, -- Index or value +** cn TEXT, -- Column Name +** op INT, -- Opcode or argvIndex +** ux INT, -- "usable" or "omit" flag +** rx BOOLEAN, -- True if has a RHS value +** rhs ANY, -- The RHS value +** cs TEXT, -- Collating Sequence +** inop BOOLEAN -- True if this is a batchable IN operator +** ); +** +** If an error occurs, leave an error message in pVTab->zErrMsg. +*/ +static void sqlite3BestIndexLog( + sqlite3_index_info *pInfo, /* The sqlite3_index_info object */ + const char *zLogTab, /* Log into this table */ + sqlite3 *db, /* Database connection containing zLogTab */ + const char **azColname, /* Names of columns in the virtual table */ + sqlite3_vtab *pVTab /* Record errors into this object */ +){ + int i, rc; + sqlite3_str *pStr; + int iBI; + + if( sqlite3_table_column_metadata(db,0,zLogTab,0,0,0,0,0,0) ){ + /* The log table does not previously exist. Create it. */ + sqlite3RunSql(db,pVTab, + "CREATE TABLE IF NOT EXISTS temp.\"%w\"(\n" + " bi INT, -- BestIndex call number\n" + " vn TEXT, -- Variable Name\n" + " ix INT, -- Index or value\n" + " cn TEXT, -- Column Name\n" + " op INT, -- Opcode or argvIndex\n" + " ux INT, -- usable for omit flag\n" + " rx BOOLEAN, -- Right-hand side value is available\n" + " rhs ANY, -- RHS value\n" + " cs TEXT, -- Collating Sequence\n" + " inop BOOLEAN -- IN operator capable of batch reads\n" + ");", zLogTab + ); + iBI = 1; + }else{ + /* The log table does already exist. We assume that it has the + ** correct schema and proceed to find the largest prior "bi" value. + ** If the schema is wrong, errors might result. The code is able + ** to deal with this. */ + sqlite3_stmt *pStmt; + char *zSql; + zSql = sqlite3_mprintf("SELECT max(bi) FROM temp.\"%w\"",zLogTab); + if( zSql==0 ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf("out of memory"); + return; + } + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + iBI = 0; + }else if( sqlite3_step(pStmt)==SQLITE_ROW ){ + iBI = sqlite3_column_int(pStmt, 0)+1; + }else{ + iBI = 1; + } + sqlite3_finalize(pStmt); + } + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'nConstraint',%d)", + zLogTab, iBI, pInfo->nConstraint + ); + for(i=0; inConstraint; i++){ + sqlite3_value *pVal; + char *zSql; + int iCol = pInfo->aConstraint[i].iColumn; + int op = pInfo->aConstraint[i].op; + const char *zCol; + if( op==SQLITE_INDEX_CONSTRAINT_LIMIT + || op==SQLITE_INDEX_CONSTRAINT_OFFSET + ){ + zCol = ""; + }else if( iCol<0 ){ + zCol = "rowid"; + }else{ + zCol = azColname[iCol]; + } + pStr = sqlite3_str_new(0); + sqlite3_str_appendf(pStr, + "INSERT INTO temp.\"%w\"(bi,vn,ix,cn,op,ux,rx,rhs,cs,inop)" + "VALUES(%d,'aConstraint',%d,%Q,%d,%d", + zLogTab, iBI, + i, + zCol, + op, + pInfo->aConstraint[i].usable); + pVal = 0; + rc = sqlite3_vtab_rhs_value(pInfo, i, &pVal); + assert( pVal!=0 || rc!=SQLITE_OK ); + if( rc==SQLITE_OK ){ + sqlite3_str_appendf(pStr,",1,?1"); + }else{ + sqlite3_str_appendf(pStr,",0,NULL"); + } + sqlite3_str_appendf(pStr,",%Q,%d)", + sqlite3_vtab_collation(pInfo,i), + sqlite3_vtab_in(pInfo,i,-1)); + zSql = sqlite3_str_finish(pStr); + if( zSql==0 ){ + if( pVTab->zErrMsg==0 ) pVTab->zErrMsg = sqlite3_mprintf("out of memory"); + }else{ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc ){ + if( pVTab->zErrMsg==0 ){ + pVTab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + }else{ + if( pVal ) sqlite3_bind_value(pStmt, 1, pVal); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc && pVTab->zErrMsg==0 ){ + pVTab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + } + sqlite3_finalize(pStmt); + sqlite3_free(zSql); + } + } + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'nOrderBy',%d)", + zLogTab, iBI, pInfo->nOrderBy + ); + for(i=0; inOrderBy; i++){ + int iCol = pInfo->aOrderBy[i].iColumn; + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix,cn,op)VALUES(%d,'aOrderBy',%d,%Q,%d)", + zLogTab, iBI, + i, + iCol>=0 ? azColname[iCol] : "rowid", + pInfo->aOrderBy[i].desc + ); + } + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'sqlite3_vtab_distinct',%d)", + zLogTab, iBI, sqlite3_vtab_distinct(pInfo) + ); + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'colUsed',%lld)", + zLogTab, iBI, pInfo->colUsed + ); + for(i=0; inConstraint; i++){ + int iCol = pInfo->aConstraint[i].iColumn; + int op = pInfo->aConstraint[i].op; + const char *zCol; + if( op==SQLITE_INDEX_CONSTRAINT_LIMIT + || op==SQLITE_INDEX_CONSTRAINT_OFFSET + ){ + zCol = ""; + }else if( iCol<0 ){ + zCol = "rowid"; + }else{ + zCol = azColname[iCol]; + } + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix,cn,op,ux)" + "VALUES(%d,'aConstraintUsage',%d,%Q,%d,%d)", + zLogTab, iBI, + i, + zCol, + pInfo->aConstraintUsage[i].argvIndex, + pInfo->aConstraintUsage[i].omit + ); + } + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix)VALUES(%d,'idxNum',%d)", + zLogTab, iBI, pInfo->idxNum + ); + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix)VALUES(%d,'estimatedCost',%f)", + zLogTab, iBI, pInfo->estimatedCost + ); + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix)VALUES(%d,'estimatedRows',%lld)", + zLogTab, iBI, pInfo->estimatedRows + ); + if( pInfo->idxStr ){ + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix)VALUES(%d,'idxStr',%Q)", + zLogTab, iBI, pInfo->idxStr + ); + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix)VALUES(%d,'needToFreeIdxStr',%d)", + zLogTab, iBI, pInfo->needToFreeIdxStr + ); + } + if( pInfo->nOrderBy ){ + sqlite3RunSql(db,pVTab, + "INSERT INTO temp.\"%w\"(bi,vn,ix)VALUES(%d,'orderByConsumed',%d)", + zLogTab, iBI, pInfo->orderByConsumed + ); + } +} +/* +** End of Logging Subsystem +*****************************************************************************/ + + +/* Find an estimated cost of running a query against vt02. +*/ +static int vt02BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ + int i; /* Loop counter */ + int isEq[5]; /* Equality constraints on X, A, B, C, and D */ + int isUsed[5]; /* Other non-== cosntraints X, A, B, C, and D */ + int argvIndex = 0; /* Next available argv[] slot */ + int iOffset = -1; /* Constraint for OFFSET */ + void *pX = 0; /* idxStr value */ + int flags = 0; /* RHS value for flags= */ + const char *zLogTab = 0; /* RHS value for logtab= */ + int iFlagTerm = -1; /* Constraint term for flags= */ + int iLogTerm = -1; /* Constraint term for logtab= */ + int iIn = -1; /* Index of the IN constraint */ + vt02_vtab *pSelf; /* This virtual table */ + + pSelf = (vt02_vtab*)pVTab; + if( pSelf->busy ){ + vt02ErrMsg(pVTab, "recursive use of vt02 prohibited"); + return SQLITE_CONSTRAINT; + } + pSelf->busy++; + + + /* Do an initial scan for flags=N and logtab=TAB constraints with + ** usable RHS values */ + for(i=0; inConstraint; i++){ + sqlite3_value *pVal; + if( !pInfo->aConstraint[i].usable ) continue; + if( pInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pInfo->aConstraint[i].iColumn ){ + case VT02_COL_FLAGS: + if( sqlite3_vtab_rhs_value(pInfo, i, &pVal)==SQLITE_OK + && sqlite3_value_type(pVal)==SQLITE_INTEGER + ){ + flags = sqlite3_value_int(pVal); + } + iFlagTerm = i; + break; + case VT02_COL_LOGTAB: + if( sqlite3_vtab_rhs_value(pInfo, i, &pVal)==SQLITE_OK + && sqlite3_value_type(pVal)==SQLITE_TEXT + ){ + zLogTab = (const char*)sqlite3_value_text(pVal); + } + iLogTerm = i; + break; + } + } + + /* Do a second scan to actually analyze the index information */ + memset(isEq, 0xff, sizeof(isEq)); + memset(isUsed, 0xff, sizeof(isUsed)); + for(i=0; inConstraint; i++){ + int j = pInfo->aConstraint[i].iColumn; + if( j>=VT02_COL_FLAGS ) continue; + if( pInfo->aConstraint[i].usable==0 + && (flags & VT02_IGNORE_USABLE)==0 ) continue; + if( j<0 ) j = VT02_COL_X; + switch( pInfo->aConstraint[i].op ){ + case SQLITE_INDEX_CONSTRAINT_FUNCTION: + case SQLITE_INDEX_CONSTRAINT_EQ: + isEq[j] = i; + break; + case SQLITE_INDEX_CONSTRAINT_LT: + case SQLITE_INDEX_CONSTRAINT_LE: + case SQLITE_INDEX_CONSTRAINT_GT: + case SQLITE_INDEX_CONSTRAINT_GE: + isUsed[j] = i; + break; + case SQLITE_INDEX_CONSTRAINT_OFFSET: + iOffset = i; + break; + } + } + + /* Use the analysis to find an appropriate query plan */ + if( isEq[0]>=0 ){ + /* A constraint of X= takes priority */ + pInfo->estimatedCost = 1; + pInfo->aConstraintUsage[isEq[0]].argvIndex = ++argvIndex; + if( flags & 0x20 ) pInfo->aConstraintUsage[isEq[0]].omit = 1; + pInfo->idxNum = 1; + }else if( isEq[1]<0 ){ + /* If there is no X= nor A= then we have to do a full scan */ + pInfo->idxNum = 0; + pInfo->estimatedCost = 10000; + }else{ + int v = 1000; + pInfo->aConstraintUsage[isEq[1]].argvIndex = ++argvIndex; + if( flags & 0x20 ) pInfo->aConstraintUsage[isEq[1]].omit = 1; + for(i=2; i<=4 && isEq[i]>=0; i++){ + if( i==4 && sqlite3_vtab_in(pInfo, isEq[4], 0) ) break; + pInfo->aConstraintUsage[isEq[i]].argvIndex = ++argvIndex; + if( flags & 0x20 ) pInfo->aConstraintUsage[isEq[i]].omit = 1; + v /= 10; + } + pInfo->idxNum = i; + if( isEq[4]>=0 && sqlite3_vtab_in(pInfo,isEq[4],1) ){ + iIn = isEq[4]; + pInfo->aConstraintUsage[iIn].argvIndex = ++argvIndex; + if( flags & 0x20 ) pInfo->aConstraintUsage[iIn].omit = 1; + v /= 5; + i++; + pInfo->idxNum += 4; + } + pInfo->estimatedCost = v; + } + pInfo->estimatedRows = (sqlite3_int64)pInfo->estimatedCost; + + /* Attempt to consume the ORDER BY clause. Except, always leave + ** orderByConsumed set to 0 for vt02_no_sort_opt. In this way, + ** we can compare vt02 and vt02_no_sort_opt to ensure they get + ** the same answer. + */ + if( pInfo->nOrderBy>0 && (flags & VT02_NO_SORT_OPT)==0 ){ + if( pInfo->idxNum==1 ){ + /* There will only be one row of output. So it is always sorted. */ + pInfo->orderByConsumed = 1; + }else + if( pInfo->aOrderBy[0].iColumn<=0 + && pInfo->aOrderBy[0].desc==0 + ){ + /* First column of order by is X ascending */ + pInfo->orderByConsumed = 1; + }else + if( sqlite3_vtab_distinct(pInfo)>=1 ){ + unsigned int x = 0; + for(i=0; inOrderBy; i++){ + int iCol = pInfo->aOrderBy[i].iColumn; + if( iCol<0 ) iCol = 0; + x |= 1<idxNum += 30; + pInfo->orderByConsumed = 1; + }else if( x==0x06 ){ + /* DISTINCT A,B */ + pInfo->idxNum += 20; + pInfo->orderByConsumed = 1; + }else if( x==0x0e ){ + /* DISTINCT A,B,C */ + pInfo->idxNum += 10; + pInfo->orderByConsumed = 1; + }else if( x & 0x01 ){ + /* DISTINCT X */ + pInfo->orderByConsumed = 1; + }else if( x==0x1e ){ + /* DISTINCT A,B,C,D */ + pInfo->orderByConsumed = 1; + } + }else{ + if( x==0x02 ){ + /* GROUP BY A */ + pInfo->orderByConsumed = 1; + }else if( x==0x06 ){ + /* GROUP BY A,B */ + pInfo->orderByConsumed = 1; + }else if( x==0x0e ){ + /* GROUP BY A,B,C */ + pInfo->orderByConsumed = 1; + }else if( x & 0x01 ){ + /* GROUP BY X */ + pInfo->orderByConsumed = 1; + }else if( x==0x1e ){ + /* GROUP BY A,B,C,D */ + pInfo->orderByConsumed = 1; + } + } + } + } + + if( flags & VT02_ALLOC_IDXSTR ){ + pInfo->idxStr = sqlite3_mprintf("test"); + pInfo->needToFreeIdxStr = 1; + } + if( flags & VT02_BAD_IDXNUM ){ + pInfo->idxNum += 1000; + } + + if( iOffset>=0 ){ + pInfo->aConstraintUsage[iOffset].argvIndex = ++argvIndex; + if( (flags & VT02_NO_OFFSET)==0 + && (pInfo->nOrderBy==0 || pInfo->orderByConsumed) + ){ + pInfo->aConstraintUsage[iOffset].omit = 1; + pInfo->idxNum += 100; + } + } + + + /* Always omit flags= and logtab= constraints to prevent them from + ** interfering with the bytecode. Put them at the end of the argv[] + ** array to keep them out of the way. + */ + if( iFlagTerm>=0 ){ + pInfo->aConstraintUsage[iFlagTerm].omit = 1; + pInfo->aConstraintUsage[iFlagTerm].argvIndex = ++argvIndex; + } + if( iLogTerm>=0 ){ + pInfo->aConstraintUsage[iLogTerm].omit = 1; + pInfo->aConstraintUsage[iLogTerm].argvIndex = ++argvIndex; + } + + /* The 0x40 flag means add all usable constraints to the output set */ + if( flags & 0x40 ){ + for(i=0; inConstraint; i++){ + if( pInfo->aConstraint[i].usable + && pInfo->aConstraintUsage[i].argvIndex==0 + ){ + pInfo->aConstraintUsage[i].argvIndex = ++argvIndex; + if( flags & 0x20 ) pInfo->aConstraintUsage[i].omit = 1; + } + } + } + + + /* Generate the log if requested */ + if( zLogTab ){ + static const char *azColname[] = { + "x", "a", "b", "c", "d", "flags", "logtab" + }; + sqlite3 *db = ((vt02_vtab*)pVTab)->db; + sqlite3BestIndexLog(pInfo, zLogTab, db, azColname, pVTab); + } + pSelf->busy--; + + /* Try to do a memory allocation solely for the purpose of causing + ** an error under OOM testing loops */ + pX = sqlite3_malloc(800); + if( pX==0 ) return SQLITE_NOMEM; + sqlite3_free(pX); + + return pVTab->zErrMsg!=0 ? SQLITE_ERROR : SQLITE_OK; +} + +/* This is the sqlite3_module definition for the the virtual table defined +** by this include file. +*/ +const sqlite3_module vt02Module = { + /* iVersion */ 2, + /* xCreate */ 0, /* This is an eponymous table */ + /* xConnect */ vt02Connect, + /* xBestIndex */ vt02BestIndex, + /* xDisconnect */ vt02Disconnect, + /* xDestroy */ vt02Disconnect, + /* xOpen */ vt02Open, + /* xClose */ vt02Close, + /* xFilter */ vt02Filter, + /* xNext */ vt02Next, + /* xEof */ vt02Eof, + /* xColumn */ vt02Column, + /* xRowid */ vt02Rowid, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindFunction */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0 +}; + +static void vt02CoreInit(sqlite3 *db){ + static const char zPkXSchema[] = + "CREATE TABLE x(x INT NOT NULL PRIMARY KEY, a INT, b INT, c INT, d INT," + " flags INT HIDDEN, logtab TEXT HIDDEN);"; + static const char zPkABCDSchema[] = + "CREATE TABLE x(x INT, a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, " + "d INT NOT NULL, flags INT HIDDEN, logtab TEXT HIDDEN, " + "PRIMARY KEY(a,b,c,d));"; + sqlite3_create_module(db, "vt02", &vt02Module, 0); + sqlite3_create_module(db, "vt02pkx", &vt02Module, (void*)zPkXSchema); + sqlite3_create_module(db, "vt02pkabcd", &vt02Module, (void*)zPkABCDSchema); +} + +#ifdef TH3_VERSION +static void vt02_init(th3state *p, int iDb, char *zArg){ + vt02CoreInit(th3dbPointer(p, iDb)); +} +#else +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_vt02_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi); + vt02CoreInit(db); + return SQLITE_OK; +} +#endif /* TH3_VERSION */ diff --git a/test/wal3.test b/test/wal3.test index 56f40ab53..cb28d0f0b 100644 --- a/test/wal3.test +++ b/test/wal3.test @@ -12,6 +12,7 @@ # focus of this file is testing the operation of the library in # "PRAGMA journal_mode=WAL" mode. # +# TESTRUNNER: slow set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/walsetlk.test b/test/walsetlk.test index 8c0a9659f..1e0923822 100644 --- a/test/walsetlk.test +++ b/test/walsetlk.test @@ -9,6 +9,8 @@ # #*********************************************************************** # +# TESTRUNNER: slow +# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -86,7 +88,7 @@ do_multiclient_test tn { INSERT INTO t1 VALUES(3, 4); INSERT INTO t1 VALUES(5, 6); } - code1 { db timeout 2000 } + code1 { db timeout 1100 } } {} do_test 2.$tn.2 { diff --git a/test/walshared.test b/test/walshared.test index fbbdeb4de..ccac9484c 100644 --- a/test/walshared.test +++ b/test/walshared.test @@ -16,7 +16,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -ifcapable !wal {finish_test ; return } +ifcapable !wal||!shared_cache {finish_test ; return } db close set ::enable_shared_cache [sqlite3_enable_shared_cache 1] diff --git a/test/walvfs.test b/test/walvfs.test index d6c58656f..d32cbd73f 100644 --- a/test/walvfs.test +++ b/test/walvfs.test @@ -12,6 +12,7 @@ # focus of this file is testing the operation of the library in # "PRAGMA journal_mode=WAL" mode. # +# TESTRUNNER: slow set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/wapptest.tcl b/test/wapptest.tcl index b7e16e722..d37b2e48c 100755 --- a/test/wapptest.tcl +++ b/test/wapptest.tcl @@ -8,6 +8,7 @@ source [file join [file dirname [info script]] wapp.tcl] # Variables set by the "control" form: # # G(platform) - User selected platform. +# G(cfgglob) - Glob pattern that all configurations must match # G(test) - Set to "Normal", "Veryquick", "Smoketest" or "Build-Only". # G(keep) - Boolean. True to delete no files after each test. # G(msvc) - Boolean. True to use MSVC as the compiler. @@ -15,6 +16,7 @@ source [file join [file dirname [info script]] wapp.tcl] # G(jobs) - How many sub-processes to run simultaneously. # set G(platform) $::tcl_platform(os)-$::tcl_platform(machine) +set G(cfgglob) * set G(test) Normal set G(keep) 1 set G(msvc) 0 @@ -29,7 +31,7 @@ set G(stdout) 0 proc wapptest_init {} { global G - set lSave [list platform test keep msvc tcl jobs debug noui stdout] + set lSave [list platform test keep msvc tcl jobs debug noui stdout cfgglob] foreach k $lSave { set A($k) $G($k) } array unset G foreach k $lSave { set G($k) $A($k) } @@ -117,6 +119,10 @@ proc set_test_array {} { if {$G(debug)==0} { set debug "-nodebug"} foreach {config target} [releasetest_data tests $debug $G(platform)] { + # All configuration names must match $g(cfgglob), which defaults to * + # + if {![string match -nocase $G(cfgglob) $config]} continue + # If using MSVC, do not run sanitize or valgrind tests. Or the # checksymbols test. if {$G(msvc) && ( @@ -470,7 +476,7 @@ proc generate_main_page {{extra {}}} { generate_select_widget Test control_test $lOpt $G(test) # Build the "jobs" select widget. Options are 1 to 8. - generate_select_widget Jobs control_jobs {1 2 3 4 5 6 7 8} $G(jobs) + generate_select_widget Jobs control_jobs {1 2 3 4 5 6 7 8 12 16} $G(jobs) switch $G(state) { config { @@ -785,6 +791,7 @@ default it uses "wapp" to provide an interactive interface. Supported command line options (all optional) are: --platform PLATFORM (which tests to run) + --config GLOB (only run configurations matching GLOB) --smoketest (run "make smoketest" only) --veryquick (run veryquick.test only) --buildonly (build executables, do not run tests) @@ -876,6 +883,12 @@ for {set i 0} {$i < [llength $lTestArg]} {incr i} { set G(stdout) 1 } + -config { + if {$i==[llength $lTestArg]-1} { wapptest_usage } + incr i + set G(cfgglob) [lindex $lTestArg $i] + } + -stdout { set G(stdout) 1 } diff --git a/test/where.test b/test/where.test index e28861bc1..b559f5ee9 100644 --- a/test/where.test +++ b/test/where.test @@ -1617,4 +1617,21 @@ do_execsql_test where-28.1 { 19 5 } +# 2022-12-07 Yong Heng [https://sqlite.org/forum/forumpost/dfe8084751] +# +ifcapable vtab { + do_execsql_test where-29.1 { + SELECT DISTINCT 'xyz' FROM pragma_cache_size + WHERE rowid OR abs(0) + ORDER BY + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1; + } {xyz} +} + finish_test diff --git a/test/widetab1.test b/test/widetab1.test new file mode 100644 index 000000000..39523ce88 --- /dev/null +++ b/test/widetab1.test @@ -0,0 +1,156 @@ +# 2022-10-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 test cases for wide table (tables with more than +# 64 columns) and indexes that reference columns beyond the 63rd or 64th +# column. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix widetab1 + + +# In order to pick the better index in the following query, SQLite needs to +# be able to detect when an index that references later columns in a wide +# table is a covering index. +# +do_execsql_test 100 { + CREATE TABLE a( + a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, + a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, + a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, + a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, + a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, + a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, + pd, bn, vb, bc, cn, ie, qm); + CREATE INDEX a1 on a(pd, bn, vb, bc, cn); -- preferred index + CREATE INDEX a2 on a(pd, bc, ie, qm); -- suboptimal index + CREATE TABLE b(bg, bc, bn, iv, ln, mg); + CREATE INDEX b1 on b(bn, iv, bg); +} +do_eqp_test 110 { + SELECT dc, count(cn) + FROM (SELECT coalesce(b.bg, a.bc) as dc, cn + FROM a LEFT JOIN b + ON a.bn = b.bn + AND CASE WHEN a.vb IS NOT NULL THEN 1 ELSE 0 END = b.iv + WHERE pd BETWEEN 0 AND 10) + GROUP BY dc; +} { + QUERY PLAN + |--SEARCH a USING COVERING INDEX a1 (pd>? AND pd diff --git a/tool/build-all-msvc.bat b/tool/build-all-msvc.bat index aaeb67bdf..8f9a1b7b0 100755 --- a/tool/build-all-msvc.bat +++ b/tool/build-all-msvc.bat @@ -129,6 +129,8 @@ REM SET __ECHO2=ECHO REM SET __ECHO3=ECHO IF NOT DEFINED _AECHO (SET _AECHO=REM) IF NOT DEFINED _CECHO (SET _CECHO=REM) +IF NOT DEFINED _CECHO2 (SET _CECHO2=REM) +IF NOT DEFINED _CECHO3 (SET _CECHO3=REM) IF NOT DEFINED _VECHO (SET _VECHO=REM) SET REDIRECT=^> @@ -177,6 +179,7 @@ REM REM NOTE: Change the current directory to the root of the source tree, saving REM the current directory on the directory stack. REM +%_CECHO2% PUSHD "%ROOT%" %__ECHO2% PUSHD "%ROOT%" IF ERRORLEVEL 1 ( @@ -524,6 +527,7 @@ FOR %%P IN (%PLATFORMS%) DO ( REM REM NOTE: Attempt to setup the MSVC environment for this platform. REM + %_CECHO3% CALL "%VCVARSALL%" %%P %__ECHO3% CALL "%VCVARSALL%" %%P IF ERRORLEVEL 1 ( @@ -749,6 +753,7 @@ FOR %%P IN (%PLATFORMS%) DO ( REM REM NOTE: Restore the saved current directory from the directory stack. REM +%_CECHO2% POPD %__ECHO2% POPD IF ERRORLEVEL 1 ( diff --git a/tool/dbtotxt.c b/tool/dbtotxt.c index a56416dcc..fbd6e3d51 100644 --- a/tool/dbtotxt.c +++ b/tool/dbtotxt.c @@ -13,8 +13,15 @@ ** dbtotxt [OPTIONS] FILENAME ** ** where OPTIONS are zero or more of: -** --pagesize N => setting the database page size for later readin -** --for-cli => prepending '.open --hexdb' to the output +** +** --for-cli prepending '.open --hexdb' to the output +** +** --script The input file is expected to start with a +** zero-terminated SQL string. Output the +** ".open --hexdb" header, then the database +** then the SQL. +** +** --pagesize N set the database page size for later reading ** ** The translation of the database appears on standard output. If the ** --pagesize command-line option is omitted, then the page size is taken @@ -43,17 +50,20 @@ static int allZero(unsigned char *aLine){ int main(int argc, char **argv){ int pgsz = 0; /* page size */ int forCli = 0; /* whether to prepend with .open */ + int bSQL = 0; /* Expect and SQL prefix */ long szFile; /* Size of the input file in bytes */ FILE *in; /* Input file */ + int nSQL; /* Number of bytes of script */ int i, j; /* Loop counters */ int nErr = 0; /* Number of errors */ const char *zInputFile = 0; /* Name of the input file */ const char *zBaseName = 0; /* Base name of the file */ int lastPage = 0; /* Last page number shown */ int iPage; /* Current page number */ - unsigned char aLine[16]; /* A single line of the file */ - unsigned char aHdr[100]; /* File header */ - unsigned char bShow[256]; /* Characters ok to display */ + unsigned char *aData = 0; /* All data */ + unsigned char *aLine; /* A single line of the file */ + unsigned char *aHdr; /* File header */ + unsigned char bShow[256]; /* Characters ok to display */ memset(bShow, '.', sizeof(bShow)); for(i=' '; i<='~'; i++){ if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = (unsigned char)i; @@ -75,6 +85,10 @@ int main(int argc, char **argv){ }else if( strcmp(z,"for-cli")==0 ){ forCli = 1; continue; + }else if( strcmp(z,"script")==0 ){ + forCli = 1; + bSQL = 1; + continue; } fprintf(stderr, "Unknown option: %s\n", argv[i]); nErr++; @@ -90,7 +104,8 @@ int main(int argc, char **argv){ nErr++; } if( nErr ){ - fprintf(stderr, "Usage: %s [--pagesize N] FILENAME\n", argv[0]); + fprintf(stderr, + "Usage: %s [--pagesize N] [--script] [--for-cli] FILENAME\n", argv[0]); exit(1); } in = fopen(zInputFile, "rb"); @@ -105,11 +120,32 @@ int main(int argc, char **argv){ fprintf(stderr, "File too short. Minimum size is 100 bytes.\n"); exit(1); } - if( fread(aHdr, 100, 1, in)!=1 ){ - fprintf(stderr, "Cannot read file header\n"); + aData = malloc( szFile+16 ); + if( aData==0 ){ + fprintf(stderr, "Failed to allocate %ld bytes\n", szFile); exit(1); } - rewind(in); + if( fread(aData, szFile, 1, in)!=1 ){ + fprintf(stderr, "Cannot read file info memory\n"); + exit(1); + } + memset(aData+szFile, 0, 16); + fclose(in); + if( bSQL ){ + for(i=0; i0 ){ + printf("%s\n", aData); + } + free( aData ); return 0; } diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl index bc723f456..0438b11b3 100755 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -43,7 +43,7 @@ set ::headCode " ** autoconf-based build */ #if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) -#include \"config.h\" +#include \"sqlite_cfg.h\" #define SQLITECONFIG_H 1 #endif @@ -254,7 +254,6 @@ set boolean_defnil_options { SQLITE_OMIT_WAL SQLITE_OMIT_WSD SQLITE_OMIT_XFER_OPT - SQLITE_PCACHE_SEPARATE_HEADER SQLITE_PERFORMANCE_TRACE SQLITE_PREFER_PROXY_LOCKING SQLITE_PROXY_DEBUG @@ -305,6 +304,7 @@ set value_options { SQLITE_DEFAULT_WAL_AUTOCHECKPOINT SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_WORKER_THREADS + SQLITE_DQS SQLITE_ENABLE_8_3_NAMES SQLITE_ENABLE_CEROD SQLITE_ENABLE_LOCKING_STYLE diff --git a/tool/mkopcodeh.tcl b/tool/mkopcodeh.tcl index 57c692011..8b4e345c6 100644 --- a/tool/mkopcodeh.tcl +++ b/tool/mkopcodeh.tcl @@ -158,6 +158,7 @@ set rp2v_ops { OP_JournalMode OP_VUpdate OP_VFilter + OP_Init } # Assign the smallest values to opcodes that are processed by resolveP2Values() diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 82ecd2f61..35b480071 100644 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -34,11 +34,11 @@ set in [open $topdir/src/shell.c.in] fconfigure $in -translation binary proc omit_redundant_typedefs {line} { global typedef_seen - if {[regexp {^typedef .*;} $line]} { - if {[info exists typedef_seen($line)]} { - return "/* $line */" + if {[regexp {^typedef .*\y([a-zA-Z0-9_]+);} $line all typename]} { + if {[info exists typedef_seen($typename)]} { + return "/* [string map {/* // */ //} $line] */" } - set typedef_seen($line) 1 + set typedef_seen($typename) 1 } return $line } diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 595fc4c60..0cdf514e4 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -355,6 +355,7 @@ foreach file { hash.c opcodes.c + os_kv.c os_unix.c os_win.c memdb.c diff --git a/tool/omittest-msvc.tcl b/tool/omittest-msvc.tcl new file mode 100644 index 000000000..28c4b9831 --- /dev/null +++ b/tool/omittest-msvc.tcl @@ -0,0 +1,99 @@ +# Run this TCL script in order to build using MSVC multiple times +# with various compile-time options. Use this to verify that the various +# compile-time options all work with MSVC. +# +set OPTIONS [list \ + SQLITE_ALLOW_ROWID_IN_VIEW \ + SQLITE_ENABLE_COLUMN_METADATA \ + SQLITE_ENABLE_EXPENSIVE_ASSERT \ + SQLITE_ENABLE_IOTRACE \ + SQLITE_ENABLE_MEMORY_MANAGEMENT \ + SQLITE_ENABLE_MEMSYS3 \ + SQLITE_ENABLE_MEMSYS5 \ + SQLITE_ENABLE_OVERSIZE_CELL_CHECK \ + SQLITE_ENABLE_UNLOCK_NOTIFY \ + SQLITE_ENABLE_UPDATE_DELETE_LIMIT \ + SQLITE_OMIT_ALTERTABLE-x \ + SQLITE_OMIT_ATTACH-x \ + SQLITE_OMIT_AUTHORIZATION \ + SQLITE_OMIT_AUTOINCREMENT \ + SQLITE_OMIT_AUTOINIT \ + SQLITE_OMIT_AUTOMATIC_INDEX \ + SQLITE_OMIT_AUTORESET \ + SQLITE_OMIT_AUTOVACUUM \ + SQLITE_OMIT_BETWEEN_OPTIMIZATION \ + SQLITE_OMIT_BLOB_LITERAL \ + SQLITE_OMIT_BTREECOUNT \ + SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA \ + SQLITE_OMIT_CAST \ + SQLITE_OMIT_CHECK \ + SQLITE_OMIT_COMPILEOPTION_DIAGS \ + SQLITE_OMIT_COMPLETE \ + SQLITE_OMIT_COMPOUND_SELECT \ + SQLITE_OMIT_CONFLICT_CLAUSE \ + SQLITE_OMIT_CTE \ + SQLITE_OMIT_DATETIME_FUNCS \ + SQLITE_OMIT_DECLTYPE \ + SQLITE_OMIT_DEPRECATED \ + SQLITE_OMIT_DESERIALIZE \ + SQLITE_OMIT_DISKIO-x \ + SQLITE_OMIT_EXPLAIN-x \ + SQLITE_OMIT_FLAG_PRAGMAS \ + SQLITE_OMIT_FLOATING_POINT \ + SQLITE_OMIT_FOREIGN_KEY \ + SQLITE_OMIT_GENERATED_COLUMNS \ + SQLITE_OMIT_GET_TABLE \ + SQLITE_OMIT_HEX_INTEGER \ + SQLITE_OMIT_INCRBLOB-x \ + SQLITE_OMIT_INTEGRITY_CHECK \ + SQLITE_OMIT_INTROSPECTION_PRAGMAS \ + SQLITE_OMIT_JSON \ + SQLITE_OMIT_LIKE_OPTIMIZATION \ + SQLITE_OMIT_LOAD_EXTENSION \ + SQLITE_OMIT_LOCALTIME \ + SQLITE_OMIT_LOOKASIDE \ + SQLITE_OMIT_MEMORYDB \ + SQLITE_OMIT_OR_OPTIMIZATION \ + SQLITE_OMIT_PAGER_PRAGMAS-x \ + SQLITE_OMIT_PARSER_TRACE \ + SQLITE_OMIT_POPEN \ + SQLITE_OMIT_PRAGMA-x \ + SQLITE_OMIT_PROGRESS_CALLBACK \ + SQLITE_OMIT_QUICKBALANCE \ + SQLITE_OMIT_RANDOMNESS \ + SQLITE_OMIT_REINDEX-x \ + SQLITE_OMIT_SCHEMA_PRAGMAS \ + SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS \ + SQLITE_OMIT_SHARED_CACHE \ + SQLITE_OMIT_SHUTDOWN_DIRECTORIES \ + SQLITE_OMIT_SUBQUERY-x \ + SQLITE_OMIT_TCL_VARIABLE \ + SQLITE_OMIT_TEMPDB \ + SQLITE_OMIT_TEST_CONTROL \ + SQLITE_OMIT_TRACE \ + SQLITE_OMIT_TRIGGER \ + SQLITE_OMIT_TRUNCATE_OPTIMIZATION \ + SQLITE_OMIT_UPSERT \ + SQLITE_OMIT_UTF16 \ + SQLITE_OMIT_VACUUM-x \ + SQLITE_OMIT_VIEW-x \ + SQLITE_OMIT_VIRTUALTABLE-x \ + SQLITE_OMIT_WAL \ + SQLITE_OMIT_WINDOWFUNC-x \ + SQLITE_OMIT_WSD \ + SQLITE_OMIT_XFER_OPT \ +] +set start [lindex $argv 0] +foreach opt $OPTIONS { + if {[regexp x $opt]} continue + if {[string compare $opt $start]<0} continue + exec fossil clean -x + set cmd "nmake /f Makefile.msc USE_FULLWARN=0 OPTIMIZATIONS=0" + append cmd " CCOPTS=-D$opt" + puts $cmd + set res [catch {eval exec $cmd} outtxt] + if {[regexp {sqlite3.c.\d+.: error} $outtxt]} { + puts "FAILED:\n$outtxt" + exit + } +} diff --git a/tool/speed-check.sh b/tool/speed-check.sh index 6b0fbeb43..4e070565e 100644 --- a/tool/speed-check.sh +++ b/tool/speed-check.sh @@ -3,9 +3,9 @@ # This is a template for a script used for day-to-day size and # performance monitoring of SQLite. Typical usage: # -# sh run-speed-test.sh trunk # Baseline measurement of trunk -# sh run-speed-test.sh x1 # Measure some experimental change -# fossil test-diff --tk cout-trunk.txt cout-x1.txt # View chanages +# sh speed-check.sh trunk # Baseline measurement of trunk +# sh speed-check.sh x1 # Measure some experimental change +# fossil xdiff --tk cout-trunk.txt cout-x1.txt # View chanages # # There are multiple output files, all with a base name given by # the first argument: @@ -96,6 +96,9 @@ while test "$1" != ""; do --cachesize) shift; SPEEDTEST_OPTS="$SPEEDTEST_OPTS --cachesize $1" ;; + --stmtcache) + shift; SPEEDTEST_OPTS="$SPEEDTEST_OPTS --stmtcache $1" + ;; --checkpoint) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --checkpoint" ;; @@ -143,6 +146,9 @@ while test "$1" != ""; do SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset rtree" CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_RTREE" ;; + --persist) + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --persist" + ;; --orm) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset orm" ;; diff --git a/tool/stripccomments.c b/tool/stripccomments.c new file mode 100644 index 000000000..53933c013 --- /dev/null +++ b/tool/stripccomments.c @@ -0,0 +1,228 @@ +/** + Strips C- and C++-style comments from stdin, sending the results to + stdout. It assumes that its input is legal C-like code, and does + only little error handling. + + It treats string literals as anything starting and ending with + matching double OR single quotes OR backticks (for use with + scripting languages which use those). It assumes that a quote + character within a string which uses the same quote type is escaped + by a backslash. It should not be used on any code which might + contain C/C++ comments inside heredocs, and similar constructs, as + it will strip those out. + + Usage: $0 [--keep-first|-k] < input > output + + The --keep-first (-k) flag tells it to retain the first comment in the + input stream (which is often a license or attribution block). It + may be given repeatedly, each one incrementing the number of + retained comments by one. + + License: Public Domain + Author: Stephan Beal (stephan@wanderinghorse.net) +*/ +#include +#include +#include + +#if 1 +#define MARKER(pfexp) \ + do{ printf("MARKER: %s:%d:\t",__FILE__,__LINE__); \ + printf pfexp; \ + } while(0) +#else +#define MARKER(exp) if(0) printf +#endif + +struct { + FILE * input; + FILE * output; + int rc; + int keepFirst; +} App = { + 0/*input*/, + 0/*output*/, + 0/*rc*/, + 0/*keepFirst*/ +}; + +void do_it_all(void){ + enum states { + S_NONE = 0 /* not in comment */, + S_SLASH1 = 1 /* slash - possibly comment prefix */, + S_CPP = 2 /* in C++ comment */, + S_C = 3 /* in C comment */ + }; + int ch, prev = EOF; + FILE * out = App.output; + int const slash = '/'; + int const star = '*'; + int line = 1; + int col = 0; + enum states state = S_NONE /* current state */; + int elide = 0 /* true if currently eliding output */; + int state3Col = -99 + /* huge kludge for odd corner case: */ + /*/ <--- here. state3Col marks the source column in which a C-style + comment starts, so that it can tell if star-slash inside a + C-style comment is the end of the comment or is the weird corner + case marked at the start of _this_ comment block. */; + for( ; EOF != (ch = fgetc(App.input)); prev = ch, + ++col){ + switch(state){ + case S_NONE: + if('\''==ch || '"'==ch || '`'==ch){ + /* Read string literal... + needed to properly catch comments in strings. */ + int const quote = ch, + startLine = line, startCol = col; + int ch2, escaped = 0, endOfString = 0; + fputc(ch, out); + for( ++col; !endOfString && EOF != (ch2 = fgetc(App.input)); + ++col ){ + switch(ch2){ + case '\\': escaped = !escaped; + break; + case '`': + case '\'': + case '"': + if(!escaped && quote == ch2) endOfString = 1; + escaped = 0; + break; + default: + escaped = 0; + break; + } + if('\n'==ch2){ + ++line; + col = 0; + } + fputc(ch2, out); + } + if(EOF == ch2){ + fprintf(stderr, "Unexpected EOF while reading %s literal " + "on line %d column %d.\n", + ('\''==ch) ? "char" : "string", + startLine, startCol); + App.rc = 1; + return; + } + break; + } + else if(slash == ch){ + /* MARKER(("state 0 ==> 1 @ %d:%d\n", line, col)); */ + state = S_SLASH1; + break; + } + fputc(ch, out); + break; + case S_SLASH1: /* 1 slash */ + /* MARKER(("SLASH1 @ %d:%d App.keepFirst=%d\n", + line, col, App.keepFirst)); */ + switch(ch){ + case '*': + /* Enter C comment */ + if(App.keepFirst>0){ + elide = 0; + --App.keepFirst; + }else{ + elide = 1; + } + /*MARKER(("state 1 ==> 3 @ %d:%d\n", line, col));*/ + state = S_C; + state3Col = col-1; + if(!elide){ + fputc(prev, out); + fputc(ch, out); + } + break; + case '/': + /* Enter C++ comment */ + if(App.keepFirst>0){ + elide = 0; + --App.keepFirst; + }else{ + elide = 1; + } + /*MARKER(("state 1 ==> 2 @ %d:%d\n", line, col));*/ + state = S_CPP; + if(!elide){ + fputc(prev, out); + fputc(ch, out); + } + break; + default: + /* It wasn't a comment after all. */ + state = S_NONE; + if(!elide){ + fputc(prev, out); + fputc(ch, out); + } + } + break; + case S_CPP: /* C++ comment */ + if('\n' == ch){ + /* MARKER(("state 2 ==> 0 @ %d:%d\n", line, col)); */ + state = S_NONE; + elide = 0; + } + if(!elide){ + fputc(ch, out); + } + break; + case S_C: /* C comment */ + if(!elide){ + fputc(ch, out); + } + if(slash == ch){ + if(star == prev){ + /* MARKER(("state 3 ==> 0 @ %d:%d\n", line, col)); */ + /* Corner case which breaks this: */ + /*/ <-- slash there */ + /* That shows up twice in a piece of 3rd-party + code i use. */ + /* And thus state3Col was introduced :/ */ + if(col!=state3Col+2){ + state = S_NONE; + elide = 0; + state3Col = -99; + } + } + } + break; + default: + assert(!"impossible!"); + break; + } + if('\n' == ch){ + ++line; + col = 0; + state3Col = -99; + } + } +} + +static void usage(char const *zAppName){ + fprintf(stderr, "Strips C- and C++-style comments from stdin and sends " + "the results to stdout.\n"); + fprintf(stderr, "Usage: %s [--keep-first|-k] < input > output\n", zAppName); +} + +int main( int argc, char const * const * argv ){ + int i; + for(i = 1; i < argc; ++i){ + char const * zArg = argv[i]; + while( '-'==*zArg ) ++zArg; + if( 0==strcmp(zArg,"k") + || 0==strcmp(zArg,"keep-first") ){ + ++App.keepFirst; + }else{ + usage(argv[0]); + return 1; + } + } + App.input = stdin; + App.output = stdout; + do_it_all(); + return App.rc ? 1 : 0; +} From b3fbd46a2f6b58fe980758c3f4580cd116c3e1f5 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 30 Dec 2022 16:38:42 -0500 Subject: [PATCH 086/340] bump version number to 4.5.4 --- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 12fc0aaf6..4f4b42529 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -84,7 +84,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.3 +#define CIPHER_VERSION_NUMBER 4.5.4 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index 0f1bf15b3..0adaa53f2 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.3 community}} +} {{4.5.4 community}} db close file delete -force test.db From af3d7770090784a427e995f578577ac4652d1b22 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 10 Jan 2023 16:50:55 -0500 Subject: [PATCH 087/340] return runtime openssl version instead of compile time for provider version --- src/crypto_openssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index 0ac53dfb0..4482375c8 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -147,7 +147,7 @@ static const char* sqlcipher_openssl_get_provider_name(void *ctx) { } static const char* sqlcipher_openssl_get_provider_version(void *ctx) { - return OPENSSL_VERSION_TEXT; + return OpenSSL_version(OPENSSL_VERSION); } /* generate a defined number of random bytes */ From d3d785cb93cc63da18d5023845e63f4d1a51d682 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 10 Jan 2023 16:51:24 -0500 Subject: [PATCH 088/340] guard against zero block size if provider initialization fails --- src/crypto_impl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 7dd3b0f9a..1451aa351 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -467,8 +467,10 @@ static int sqlcipher_codec_ctx_reserve_setup(codec_ctx *ctx) { reserve += ctx->hmac_sz; /* if reserve will include hmac, update that size */ /* calculate the amount of reserve needed in even increments of the cipher block size */ - reserve = ((reserve % ctx->block_sz) == 0) ? reserve : + if(ctx->block_sz > 0) { + reserve = ((reserve % ctx->block_sz) == 0) ? reserve : ((reserve / ctx->block_sz) + 1) * ctx->block_sz; + } sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d", base_reserve, ctx->block_sz, ctx->hmac_sz, reserve); @@ -704,7 +706,7 @@ int sqlcipher_set_default_plaintext_header_size(int size) { } int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size) { - if(size >= 0 && (size % ctx->block_sz) == 0 && size < (ctx->page_sz - ctx->reserve_sz)) { + if(size >= 0 && ctx->block_sz > 0 && (size % ctx->block_sz) == 0 && size < (ctx->page_sz - ctx->reserve_sz)) { ctx->plaintext_header_sz = size; return SQLITE_OK; } From b942636547ad68b1c38053099e7e877b2495d4cc Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 27 Jan 2023 15:25:46 -0500 Subject: [PATCH 089/340] update license file copyright --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index bebe1e7eb..7167addca 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2008, ZETETIC LLC +Copyright (c) 2008-2023, ZETETIC LLC All rights reserved. Redistribution and use in source and binary forms, with or without From 8690034ed2e0f056821e6b21aebda5a78add60dd Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 1 Mar 2023 13:43:37 -0500 Subject: [PATCH 090/340] Snapshot of upstream SQLite 3.41.0 --- Makefile.in | 85 +- Makefile.msc | 71 +- VERSION | 2 +- autoconf/Makefile.msc | 1 + autoconf/tea/configure.ac | 2 +- configure | 113 +- configure.ac | 120 +- ext/fts1/README.txt | 2 - ext/fts1/ft_hash.c | 404 - ext/fts1/ft_hash.h | 111 - ext/fts1/fts1.c | 3348 -------- ext/fts1/fts1.h | 11 - ext/fts1/fts1_hash.c | 369 - ext/fts1/fts1_hash.h | 112 - ext/fts1/fts1_porter.c | 643 -- ext/fts1/fts1_tokenizer.h | 90 - ext/fts1/fts1_tokenizer1.c | 221 - ext/fts1/fulltext.c | 1511 ---- ext/fts1/fulltext.h | 11 - ext/fts1/simple_tokenizer.c | 174 - ext/fts1/tokenizer.h | 89 - ext/fts2/README.tokenizers | 133 - ext/fts2/README.txt | 4 - ext/fts2/fts2.c | 6860 ----------------- ext/fts2/fts2.h | 26 - ext/fts2/fts2_hash.c | 376 - ext/fts2/fts2_hash.h | 110 - ext/fts2/fts2_icu.c | 260 - ext/fts2/fts2_porter.c | 644 -- ext/fts2/fts2_tokenizer.c | 375 - ext/fts2/fts2_tokenizer.h | 145 - ext/fts2/fts2_tokenizer1.c | 233 - ext/fts2/mkfts2amal.tcl | 116 - ext/fts3/fts3.c | 56 +- ext/fts3/fts3Int.h | 2 + ext/fts3/fts3_snippet.c | 48 +- ext/fts5/fts5_expr.c | 16 +- ext/fts5/fts5_index.c | 8 +- ext/fts5/fts5_main.c | 4 +- ext/fts5/test/fts5optimize2.test | 64 + ext/fts5/test/fts5trigram.test | 18 + ext/lsm1/lsmInt.h | 6 +- ext/lsm1/lsm_ckpt.c | 2 +- ext/lsm1/lsm_file.c | 13 +- ext/lsm1/lsm_main.c | 2 +- ext/lsm1/lsm_shared.c | 18 + ext/lsm1/lsm_sorted.c | 125 +- ext/lsm1/lsm_varint.c | 5 + ext/misc/base64.c | 277 + ext/misc/base85.c | 436 ++ ext/misc/basexx.c | 86 + ext/misc/carray.c | 51 +- ext/misc/carray.h | 2 +- ext/misc/decimal.c | 2 +- ext/misc/regexp.c | 1 + ext/misc/shathree.c | 8 +- ext/misc/sqlar.c | 5 +- ext/misc/stmt.c | 9 + ext/misc/zipfile.c | 11 +- ext/rbu/rbu1.test | 3 +- ext/rbu/rbu10.test | 7 +- ext/rbu/rbu11.test | 6 +- ext/rbu/rbu12.test | 6 +- ext/rbu/rbu13.test | 6 +- ext/rbu/rbu14.test | 6 +- ext/rbu/rbu3.test | 9 +- ext/rbu/rbu5.test | 1 + ext/rbu/rbu6.test | 6 +- ext/rbu/rbu7.test | 6 +- ext/rbu/rbu8.test | 6 +- ext/rbu/rbu9.test | 6 +- ext/rbu/rbuA.test | 6 +- ext/rbu/rbuB.test | 3 +- ext/rbu/rbuC.test | 1 + ext/rbu/rbu_common.tcl | 10 + ext/rbu/rbubusy.test | 2 + ext/rbu/rbucollate.test | 6 +- ext/rbu/rbucrash.test | 12 +- ext/rbu/rbucrash2.test | 12 +- ext/rbu/rbudiff.test | 7 +- ext/rbu/rbudor.test | 6 +- ext/rbu/rbuexlock.test | 20 +- ext/rbu/rbuexpr.test | 7 +- ext/rbu/rbufault.test | 6 +- ext/rbu/rbufault2.test | 6 +- ext/rbu/rbufault3.test | 1 + ext/rbu/rbufault4.test | 9 +- ext/rbu/rbufts.test | 6 +- ext/rbu/rbumisc.test | 6 +- ext/rbu/rbumulti.test | 5 +- ext/rbu/rbupartial.test | 3 +- ext/rbu/rbupass.test | 81 + ext/rbu/rbuprogress.test | 1 + ext/rbu/rburename.test | 1 + ext/rbu/rburesume.test | 9 +- ext/rbu/rbusave.test | 6 +- ext/rbu/rbusplit.test | 3 +- ext/rbu/rbutemplimit.test | 4 +- ext/rbu/rbuvacuum.test | 1 + ext/rbu/rbuvacuum2.test | 1 + ext/rbu/rbuvacuum3.test | 1 + ext/rbu/rbuvacuum4.test | 2 + ext/rbu/sqlite3rbu.c | 46 +- ext/rbu/sqlite3rbu.h | 86 +- ext/recover/dbdata.c | 6 + ext/recover/recover1.test | 70 +- ext/recover/sqlite3recover.c | 15 +- ext/rtree/geopoly.c | 25 +- ext/rtree/rtree.c | 8 +- ext/rtree/rtreecheck.test | 24 + ext/session/sessionat.test | 54 + ext/session/sqlite3session.c | 11 +- ext/session/test_session.c | 11 +- ext/wasm/GNUmakefile | 808 +- ext/wasm/README-dist.txt | 39 +- ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api | 113 +- ext/wasm/api/README.md | 60 +- ...tern-post-js.js => extern-post-js.c-pp.js} | 51 +- ext/wasm/api/post-js-header.js | 3 +- ext/wasm/api/{pre-js.js => pre-js.c-pp.js} | 16 +- ext/wasm/api/sqlite3-api-cleanup.js | 11 +- ext/wasm/api/sqlite3-api-glue.js | 1777 ++++- ext/wasm/api/sqlite3-api-oo1.js | 414 +- ext/wasm/api/sqlite3-api-prologue.js | 1112 ++- ext/wasm/api/sqlite3-api-worker1.js | 36 +- .../api/sqlite3-license-version-header.js | 2 +- ext/wasm/api/sqlite3-opfs-async-proxy.js | 1563 ++-- ext/wasm/api/sqlite3-v-helper.js | 714 ++ ...3-api-opfs.js => sqlite3-vfs-opfs.c-pp.js} | 363 +- ext/wasm/api/sqlite3-wasm.c | 758 +- ...er.js => sqlite3-worker1-promiser.c-pp.js} | 8 +- ...te3-worker1.js => sqlite3-worker1.c-pp.js} | 7 +- ext/wasm/batch-runner.js | 14 +- ext/wasm/c-pp.c | 1530 ++++ ext/wasm/common/emscripten.css | 2 +- ext/wasm/common/testing.css | 6 + ext/wasm/common/whwasmutil.js | 1003 ++- ext/wasm/demo-worker1-promiser.js | 6 +- ext/wasm/dist.make | 93 +- ext/wasm/fiddle.make | 8 +- ext/wasm/fiddle/fiddle-worker.js | 5 +- ext/wasm/index-dist.html | 40 +- ext/wasm/index.html | 83 +- ext/wasm/jaccwabyt/jaccwabyt.js | 200 +- ext/wasm/jaccwabyt/jaccwabyt.md | 154 +- ext/wasm/module-symbols.html | 231 +- ext/wasm/speedtest1-wasmfs.html | 5 +- ext/wasm/speedtest1-worker.html | 4 +- ext/wasm/speedtest1.html | 6 +- ext/wasm/test-opfs-vfs.js | 4 +- ext/wasm/tester1-worker.html | 48 +- ext/wasm/tester1.c-pp.html | 41 + ext/wasm/tester1.c-pp.js | 3034 ++++++++ ext/wasm/tester1.html | 28 - ext/wasm/tester1.js | 1864 ----- ext/wasm/tests/opfs/concurrency/index.html | 49 + ext/wasm/tests/opfs/concurrency/test.js | 136 + ext/wasm/tests/opfs/concurrency/worker.js | 113 + ext/wasm/wasmfs.make | 135 +- main.mk | 57 +- manifest | 564 +- manifest.uuid | 2 +- src/alter.c | 5 +- src/analyze.c | 4 +- src/attach.c | 29 +- src/btree.c | 337 +- src/btree.h | 12 +- src/btreeInt.h | 11 +- src/build.c | 19 +- src/date.c | 2 +- src/dbpage.c | 27 +- src/dbstat.c | 14 +- src/expr.c | 358 +- src/func.c | 106 +- src/hash.c | 3 +- src/hwtime.h | 6 +- src/insert.c | 1 + src/json.c | 11 +- src/loadext.c | 4 +- src/main.c | 26 +- src/malloc.c | 9 +- src/os_common.h | 6 - src/os_kv.c | 19 +- src/os_unix.c | 98 +- src/pager.c | 27 +- src/parse.y | 5 + src/pragma.c | 43 +- src/prepare.c | 9 +- src/printf.c | 27 +- src/resolve.c | 39 +- src/select.c | 710 +- src/shell.c.in | 671 +- src/sqlite.h.in | 348 +- src/sqlite3ext.h | 4 + src/sqliteInt.h | 138 +- src/tclsqlite.c | 3 + src/test1.c | 180 +- src/test7.c | 718 -- src/test_bestindex.c | 102 +- src/test_demovfs.c | 1 - src/test_server.c | 516 -- src/test_tclsh.c | 2 - src/test_thread.c | 46 +- src/test_vfs.c | 5 +- src/treeview.c | 12 +- src/update.c | 3 +- src/util.c | 37 +- src/vdbe.c | 220 +- src/vdbe.h | 22 +- src/vdbeInt.h | 15 +- src/vdbeapi.c | 158 +- src/vdbeaux.c | 110 +- src/vdbemem.c | 59 +- src/vdbevtab.c | 4 + src/vtab.c | 2 +- src/where.c | 427 +- src/whereInt.h | 1 + src/wherecode.c | 160 +- src/whereexpr.c | 63 +- src/window.c | 15 +- test/affinity3.test | 14 +- test/attach2.test | 60 + test/auth.test | 15 + test/basexx1.test | 155 + test/bestindexA.test | 138 + test/bloom1.test | 36 +- test/capi3c.test | 1 + test/carray01.test | 81 +- test/cast.test | 75 +- test/conflict.test | 31 + test/dbpagefault.test | 4 +- test/distinctagg.test | 1 + test/e_createtable.test | 13 +- test/e_expr.test | 9 +- test/eqp.test | 18 +- test/exprfault.test | 10 + test/filter1.test | 14 +- test/fts3defer2.test | 28 + test/fts3expr4.test | 11 +- test/fuzzcheck.c | 30 +- test/fuzzdata8.db | Bin 3298304 -> 4235264 bytes test/fuzzinvariants.c | 25 +- test/hook.test | 1 + test/in.test | 25 + test/indexexpr1.test | 109 +- test/interrupt.test | 18 +- test/intreal.test | 18 + test/join5.test | 4 + test/join8.test | 2 +- test/memdb2.test | 77 + test/memsubsys2.test | 29 +- test/misc1.test | 11 +- test/multiplex3.test | 2 + test/mutex1.test | 1 + test/notify3.test | 4 +- test/orderby1.test | 3 + test/pager2.test | 2 + test/pendingrace.test | 126 + test/permutations.test | 11 +- test/pragma.test | 57 + test/pushdown.test | 37 +- test/quickcheck.test | 34 + test/releasetest_data.tcl | 170 + test/resetdb.test | 42 + test/returning1.test | 8 + test/rowvalue9.test | 40 + test/scanstatus.test | 48 +- test/scanstatus2.test | 237 + test/server1.test | 180 - test/sessionfuzz.c | 2 +- test/shell1.test | 12 + test/shell2.test | 14 + test/shell4.test | 6 + test/shrink.test | 7 + test/sort.test | 32 + test/sort4.test | 2 + test/speed1.test | 2 + test/speed1p.test | 2 + test/sqllimits1.test | 54 +- test/symlink.test | 28 + test/tester.tcl | 26 +- test/testrunner.tcl | 1225 +-- test/testrunner_data.tcl | 143 + test/tkt-99378177930f87bd.test | 179 + test/trustschema1.test | 11 + test/unhex.test | 102 + test/unionall.test | 16 +- test/view.test | 25 + test/vt02.c | 6 + test/where.test | 88 +- test/whereL.test | 19 + test/windowB.test | 73 + test/with3.test | 6 +- test/with6.test | 59 +- test/zipfile.test | 10 +- tool/mkctimec.tcl | 1 - tool/mkopcodeh.tcl | 5 + tool/mkshellc.tcl | 2 +- tool/mksqlite3c.tcl | 18 +- tool/mksqlite3h.tcl | 10 + tool/showdb.c | 14 + tool/showwal.c | 14 + tool/vdbe_profile.tcl | 6 + tool/warnings.sh | 8 +- 304 files changed, 20803 insertions(+), 25533 deletions(-) delete mode 100644 ext/fts1/README.txt delete mode 100644 ext/fts1/ft_hash.c delete mode 100644 ext/fts1/ft_hash.h delete mode 100644 ext/fts1/fts1.c delete mode 100644 ext/fts1/fts1.h delete mode 100644 ext/fts1/fts1_hash.c delete mode 100644 ext/fts1/fts1_hash.h delete mode 100644 ext/fts1/fts1_porter.c delete mode 100644 ext/fts1/fts1_tokenizer.h delete mode 100644 ext/fts1/fts1_tokenizer1.c delete mode 100644 ext/fts1/fulltext.c delete mode 100644 ext/fts1/fulltext.h delete mode 100644 ext/fts1/simple_tokenizer.c delete mode 100644 ext/fts1/tokenizer.h delete mode 100644 ext/fts2/README.tokenizers delete mode 100644 ext/fts2/README.txt delete mode 100644 ext/fts2/fts2.c delete mode 100644 ext/fts2/fts2.h delete mode 100644 ext/fts2/fts2_hash.c delete mode 100644 ext/fts2/fts2_hash.h delete mode 100644 ext/fts2/fts2_icu.c delete mode 100644 ext/fts2/fts2_porter.c delete mode 100644 ext/fts2/fts2_tokenizer.c delete mode 100644 ext/fts2/fts2_tokenizer.h delete mode 100644 ext/fts2/fts2_tokenizer1.c delete mode 100644 ext/fts2/mkfts2amal.tcl create mode 100644 ext/fts5/test/fts5optimize2.test create mode 100755 ext/misc/base64.c create mode 100644 ext/misc/base85.c create mode 100644 ext/misc/basexx.c create mode 100644 ext/rbu/rbupass.test rename ext/wasm/api/{extern-post-js.js => extern-post-js.c-pp.js} (68%) rename ext/wasm/api/{pre-js.js => pre-js.c-pp.js} (89%) create mode 100644 ext/wasm/api/sqlite3-v-helper.js rename ext/wasm/api/{sqlite3-api-opfs.js => sqlite3-vfs-opfs.c-pp.js} (84%) rename ext/wasm/api/{sqlite3-worker1-promiser.js => sqlite3-worker1-promiser.c-pp.js} (97%) rename ext/wasm/api/{sqlite3-worker1.js => sqlite3-worker1.c-pp.js} (94%) create mode 100644 ext/wasm/c-pp.c create mode 100644 ext/wasm/tester1.c-pp.html create mode 100644 ext/wasm/tester1.c-pp.js delete mode 100644 ext/wasm/tester1.html delete mode 100644 ext/wasm/tester1.js create mode 100644 ext/wasm/tests/opfs/concurrency/index.html create mode 100644 ext/wasm/tests/opfs/concurrency/test.js create mode 100644 ext/wasm/tests/opfs/concurrency/worker.js delete mode 100644 src/test7.c delete mode 100644 src/test_server.c create mode 100644 test/basexx1.test create mode 100644 test/bestindexA.test create mode 100644 test/memdb2.test create mode 100644 test/pendingrace.test create mode 100644 test/quickcheck.test create mode 100644 test/scanstatus2.test delete mode 100644 test/server1.test create mode 100644 test/testrunner_data.tcl create mode 100644 test/tkt-99378177930f87bd.test create mode 100644 test/unhex.test diff --git a/Makefile.in b/Makefile.in index 09e75bb88..2a71bd2c5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -315,24 +315,6 @@ SRC = \ # Source code for extensions # -SRC += \ - $(TOP)/ext/fts1/fts1.c \ - $(TOP)/ext/fts1/fts1.h \ - $(TOP)/ext/fts1/fts1_hash.c \ - $(TOP)/ext/fts1/fts1_hash.h \ - $(TOP)/ext/fts1/fts1_porter.c \ - $(TOP)/ext/fts1/fts1_tokenizer.h \ - $(TOP)/ext/fts1/fts1_tokenizer1.c -SRC += \ - $(TOP)/ext/fts2/fts2.c \ - $(TOP)/ext/fts2/fts2.h \ - $(TOP)/ext/fts2/fts2_hash.c \ - $(TOP)/ext/fts2/fts2_hash.h \ - $(TOP)/ext/fts2/fts2_icu.c \ - $(TOP)/ext/fts2/fts2_porter.c \ - $(TOP)/ext/fts2/fts2_tokenizer.h \ - $(TOP)/ext/fts2/fts2_tokenizer.c \ - $(TOP)/ext/fts2/fts2_tokenizer1.c SRC += \ $(TOP)/ext/fts3/fts3.c \ $(TOP)/ext/fts3/fts3.h \ @@ -391,7 +373,6 @@ TESTSRC = \ $(TOP)/src/test4.c \ $(TOP)/src/test5.c \ $(TOP)/src/test6.c \ - $(TOP)/src/test7.c \ $(TOP)/src/test8.c \ $(TOP)/src/test9.c \ $(TOP)/src/test_autoext.c \ @@ -420,7 +401,6 @@ TESTSRC = \ $(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_syscall.c \ $(TOP)/src/test_tclsh.c \ @@ -437,7 +417,7 @@ TESTSRC = \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/test_recover.c \ - $(TOP)/ext/rbu/test_rbu.c + $(TOP)/ext/rbu/test_rbu.c # Statically linked extensions # @@ -446,6 +426,7 @@ TESTSRC += \ $(TOP)/ext/expert/test_expert.c \ $(TOP)/ext/misc/amatch.c \ $(TOP)/ext/misc/appendvfs.c \ + $(TOP)/ext/misc/basexx.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/cksumvfs.c \ $(TOP)/ext/misc/closure.c \ @@ -564,14 +545,6 @@ HDR = \ # Header files used by extensions # -EXTHDR += \ - $(TOP)/ext/fts1/fts1.h \ - $(TOP)/ext/fts1/fts1_hash.h \ - $(TOP)/ext/fts1/fts1_tokenizer.h -EXTHDR += \ - $(TOP)/ext/fts2/fts2.h \ - $(TOP)/ext/fts2/fts2_hash.h \ - $(TOP)/ext/fts2/fts2_tokenizer.h EXTHDR += \ $(TOP)/ext/fts3/fts3.h \ $(TOP)/ext/fts3/fts3Int.h \ @@ -615,7 +588,8 @@ TESTOPTS = --verbose=file --output=test-out.txt # Extra compiler options for various shell tools # -SHELL_OPT = -DSQLITE_ENABLE_FTS4 +SHELL_OPT += -DSQLITE_DQS=0 +SHELL_OPT += -DSQLITE_ENABLE_FTS4 #SHELL_OPT += -DSQLITE_ENABLE_FTS5 SHELL_OPT += -DSQLITE_ENABLE_RTREE SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS @@ -648,10 +622,17 @@ FUZZCHECK_SRC += $(TOP)/test/vt02.c DBFUZZ_OPT = ST_OPT = -DSQLITE_OS_KV_OPTIONAL + +# In wasi-sdk builds, disable the CLI shell build in the "all" target. +SQLITE3_SHELL_TARGET_ = sqlite3$(TEXE) +SQLITE3_SHELL_TARGET_1 = +SQLITE3_SHELL_TARGET = $(SQLITE3_SHELL_TARGET_@HAVE_WASI_SDK@) + # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. # -all: sqlite3.h libsqlite3.la sqlite3$(TEXE) $(HAVE_TCL:1=libtclsqlite3.la) +all: sqlite3.h libsqlite3.la $(SQLITE3_SHELL_TARGET) \ + $(HAVE_TCL:1=libtclsqlite3.la) Makefile: $(TOP)/Makefile.in ./config.status @@ -778,6 +759,15 @@ sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . +sqlite3r.h: sqlite3.h + $(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h + +sqlite3r.c: sqlite3.c sqlite3r.h + cp $(TOP)/ext/recover/sqlite3recover.c tsrc/ + cp $(TOP)/ext/recover/sqlite3recover.h tsrc/ + cp $(TOP)/ext/recover/dbdata.c tsrc/ + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) + sqlite3ext.h: .target_source cp tsrc/sqlite3ext.h . @@ -1108,6 +1098,9 @@ SHELL_SRC = \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ $(TOP)/ext/misc/decimal.c \ + $(TOP)/ext/misc/basexx.c \ + $(TOP)/ext/misc/base64.c \ + $(TOP)/ext/misc/base85.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/regexp.c \ @@ -1135,24 +1128,6 @@ shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl icu.lo: $(TOP)/ext/icu/icu.c $(HDR) $(EXTHDR) $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/icu/icu.c -fts2.lo: $(TOP)/ext/fts2/fts2.c $(HDR) $(EXTHDR) - $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2.c - -fts2_hash.lo: $(TOP)/ext/fts2/fts2_hash.c $(HDR) $(EXTHDR) - $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_hash.c - -fts2_icu.lo: $(TOP)/ext/fts2/fts2_icu.c $(HDR) $(EXTHDR) - $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_icu.c - -fts2_porter.lo: $(TOP)/ext/fts2/fts2_porter.c $(HDR) $(EXTHDR) - $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_porter.c - -fts2_tokenizer.lo: $(TOP)/ext/fts2/fts2_tokenizer.c $(HDR) $(EXTHDR) - $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_tokenizer.c - -fts2_tokenizer1.lo: $(TOP)/ext/fts2/fts2_tokenizer1.c $(HDR) $(EXTHDR) - $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_tokenizer1.c - fts3.lo: $(TOP)/ext/fts3/fts3.c $(HDR) $(EXTHDR) $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3.c @@ -1307,6 +1282,15 @@ tcltest: ./testfixture$(TEXE) testrunner: testfixture$(TEXE) ./testfixture$(TEXE) $(TOP)/test/testrunner.tcl +# Runs both fuzztest and testrunner, consecutively. +# +devtest: testfixture$(TEXE) fuzztest testrunner + +# Testing for a release +# +releasetest: testfixture$(TEXE) + ./testfixture$(TEXE) $(TOP)/test/testrunner.tcl release + # Minimal testing that runs in less than 3 minutes # quicktest: ./testfixture$(TEXE) @@ -1464,9 +1448,6 @@ threadtest: threadtest3$(TEXE) threadtest5: sqlite3.c $(TOP)/test/threadtest5.c $(LTLINK) $(TOP)/test/threadtest5.c sqlite3.c -o $@ $(TLIBS) -releasetest: - $(TCLSH_CMD) $(TOP)/test/releasetest.tcl - # Standard install and cleanup targets # lib_install: libsqlite3.la diff --git a/Makefile.msc b/Makefile.msc index 191701cdb..5528b8e72 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1412,20 +1412,6 @@ SRC05 = \ $(TOP)\src\wal.h \ $(TOP)\src\whereInt.h -# Extension source code files, part 1. -# -SRC06 = \ - $(TOP)\ext\fts1\fts1.c \ - $(TOP)\ext\fts1\fts1_hash.c \ - $(TOP)\ext\fts1\fts1_porter.c \ - $(TOP)\ext\fts1\fts1_tokenizer1.c \ - $(TOP)\ext\fts2\fts2.c \ - $(TOP)\ext\fts2\fts2_hash.c \ - $(TOP)\ext\fts2\fts2_icu.c \ - $(TOP)\ext\fts2\fts2_porter.c \ - $(TOP)\ext\fts2\fts2_tokenizer.c \ - $(TOP)\ext\fts2\fts2_tokenizer1.c - # Extension source code files, part 2. # SRC07 = \ @@ -1448,16 +1434,6 @@ SRC07 = \ $(TOP)\ext\rbu\sqlite3rbu.c \ $(TOP)\ext\misc\stmt.c -# Extension header files, part 1. -# -SRC08 = \ - $(TOP)\ext\fts1\fts1.h \ - $(TOP)\ext\fts1\fts1_hash.h \ - $(TOP)\ext\fts1\fts1_tokenizer.h \ - $(TOP)\ext\fts2\fts2.h \ - $(TOP)\ext\fts2\fts2_hash.h \ - $(TOP)\ext\fts2\fts2_tokenizer.h - # Extension header files, part 2. # SRC09 = \ @@ -1498,7 +1474,7 @@ SRC12 = # All source code files. # -SRC = $(SRC00) $(SRC01) $(SRC03) $(SRC04) $(SRC05) $(SRC06) $(SRC07) $(SRC08) $(SRC09) $(SRC10) $(SRC11) $(SRC12) +SRC = $(SRC00) $(SRC01) $(SRC03) $(SRC04) $(SRC05) $(SRC07) $(SRC09) $(SRC10) $(SRC11) $(SRC12) # Source code to the test files. # @@ -1509,7 +1485,6 @@ TESTSRC = \ $(TOP)\src\test4.c \ $(TOP)\src\test5.c \ $(TOP)\src\test6.c \ - $(TOP)\src\test7.c \ $(TOP)\src\test8.c \ $(TOP)\src\test9.c \ $(TOP)\src\test_autoext.c \ @@ -1538,7 +1513,6 @@ TESTSRC = \ $(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_syscall.c \ $(TOP)\src\test_tclsh.c \ @@ -1561,6 +1535,7 @@ TESTEXT = \ $(TOP)\ext\expert\test_expert.c \ $(TOP)\ext\misc\amatch.c \ $(TOP)\ext\misc\appendvfs.c \ + $(TOP)\ext\misc\basexx.c \ $(TOP)\ext\misc\carray.c \ $(TOP)\ext\misc\cksumvfs.c \ $(TOP)\ext\misc\closure.c \ @@ -1605,7 +1580,6 @@ TESTEXT = $(TESTEXT) $(TOP)\ext\misc\zipfile.c TESTSRC2 = \ $(SRC00) \ $(SRC01) \ - $(SRC06) \ $(SRC07) \ $(SRC10) \ $(TOP)\ext\async\sqlite3async.c @@ -1640,14 +1614,6 @@ HDR = \ # Header files used by extensions # -EXTHDR = $(EXTHDR) \ - $(TOP)\ext\fts1\fts1.h \ - $(TOP)\ext\fts1\fts1_hash.h \ - $(TOP)\ext\fts1\fts1_tokenizer.h -EXTHDR = $(EXTHDR) \ - $(TOP)\ext\fts2\fts2.h \ - $(TOP)\ext\fts2\fts2_hash.h \ - $(TOP)\ext\fts2\fts2_tokenizer.h EXTHDR = $(EXTHDR) \ $(TOP)\ext\fts3\fts3.h \ $(TOP)\ext\fts3\fts3Int.h \ @@ -1691,6 +1657,7 @@ FUZZDATA = \ # when the shell is not being dynamically linked. # !IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_DQS=0 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 @@ -1859,9 +1826,7 @@ mptest: mptester.exe for %i in ($(SRC03)) do copy /Y %i tsrc for %i in ($(SRC04)) do copy /Y %i tsrc for %i in ($(SRC05)) do copy /Y %i tsrc - for %i in ($(SRC06)) do copy /Y %i tsrc for %i in ($(SRC07)) do copy /Y %i tsrc - for %i in ($(SRC08)) do copy /Y %i tsrc for %i in ($(SRC09)) do copy /Y %i tsrc for %i in ($(SRC10)) do copy /Y %i tsrc for %i in ($(SRC11)) do copy /Y %i tsrc @@ -2231,6 +2196,8 @@ SHELL_SRC = \ $(TOP)\src\shell.c.in \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\completion.c \ + $(TOP)\ext\misc\base64.c \ + $(TOP)\ext\misc\base85.c \ $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\fileio.c \ $(TOP)\ext\misc\ieee754.c \ @@ -2264,24 +2231,6 @@ zlib: icu.lo: $(TOP)\ext\icu\icu.c $(HDR) $(EXTHDR) $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\icu\icu.c -fts2.lo: $(TOP)\ext\fts2\fts2.c $(HDR) $(EXTHDR) - $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\fts2\fts2.c - -fts2_hash.lo: $(TOP)\ext\fts2\fts2_hash.c $(HDR) $(EXTHDR) - $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\fts2\fts2_hash.c - -fts2_icu.lo: $(TOP)\ext\fts2\fts2_icu.c $(HDR) $(EXTHDR) - $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\fts2\fts2_icu.c - -fts2_porter.lo: $(TOP)\ext\fts2\fts2_porter.c $(HDR) $(EXTHDR) - $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\fts2\fts2_porter.c - -fts2_tokenizer.lo: $(TOP)\ext\fts2\fts2_tokenizer.c $(HDR) $(EXTHDR) - $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\fts2\fts2_tokenizer.c - -fts2_tokenizer1.lo: $(TOP)\ext\fts2\fts2_tokenizer1.c $(HDR) $(EXTHDR) - $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\fts2\fts2_tokenizer1.c - fts3.lo: $(TOP)\ext\fts3\fts3.c $(HDR) $(EXTHDR) $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\fts3\fts3.c @@ -2501,6 +2450,16 @@ tcltest: testfixture.exe testrunner: testfixture.exe .\testfixture.exe $(TOP)\test\testrunner.tcl +# Runs both fuzztest and testrunner, consecutively. +# +devtest: testfixture.exe fuzztest testrunner + +# Testing for a release +# +releasetest: testfixture.exe fuzztest + testfixture.exe $(TOP)/test/testrunner.tcl release + + smoketest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\main.test $(TESTOPTS) diff --git a/VERSION b/VERSION index e49080f12..371986f6d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.40.1 +3.41.0 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index e36eb21ea..09daa867e 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -955,6 +955,7 @@ LIBRESOBJS = # when the shell is not being dynamically linked. # !IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_DQS=0 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index 3e17b8b0b..f6291d255 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.40.0]) +AC_INIT([sqlite],[3.41.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index a3bd73206..56e74f5ec 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.69 for sqlite 3.40.1. +# Generated by GNU Autoconf 2.69 for sqlite 3.41.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.40.1' -PACKAGE_STRING='sqlite 3.40.1' +PACKAGE_VERSION='3.41.0' +PACKAGE_STRING='sqlite 3.41.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -800,6 +800,7 @@ TEMP_STORE ALLOWRELEASE SQLITE_THREADSAFE BUILD_CC +HAVE_WASI_SDK RELEASE VERSION program_prefix @@ -892,6 +893,7 @@ enable_fast_install with_gnu_ld enable_libtool_lock enable_largefile +with_wasi_sdk enable_threadsafe enable_releasemode enable_tempstore @@ -1468,7 +1470,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.40.1 to adapt to many kinds of systems. +\`configure' configures sqlite 3.41.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1533,7 +1535,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.40.1:";; + short | recursive ) echo "Configuration of sqlite 3.41.0:";; esac cat <<\_ACEOF @@ -1579,6 +1581,8 @@ Optional Packages: --with-pic try to use only PIC/non-PIC objects [default=use both] --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-wasi-sdk=DIR directory containing the WASI SDK. Triggers + cross-compile to WASM. --with-tcl=DIR directory containing tcl configuration (tclConfig.sh) --with-readline-lib specify readline library @@ -1661,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.40.1 +sqlite configure 3.41.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2080,7 +2084,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.40.1, which was +It was created by sqlite $as_me 3.41.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3938,13 +3942,13 @@ if ${lt_cv_nm_interface+:} false; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3941: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3945: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3944: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3948: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3947: output\"" >&5) + (eval echo "\"\$as_me:3951: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -5150,7 +5154,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5153 "configure"' > conftest.$ac_ext + echo '#line 5157 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6675,11 +6679,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:6678: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6682: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6682: \$? = $ac_status" >&5 + echo "$as_me:6686: \$? = $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. @@ -7014,11 +7018,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:7017: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7021: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7021: \$? = $ac_status" >&5 + echo "$as_me:7025: \$? = $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. @@ -7119,11 +7123,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:7122: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7126: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7126: \$? = $ac_status" >&5 + echo "$as_me:7130: \$? = $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 @@ -7174,11 +7178,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:7181: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7181: \$? = $ac_status" >&5 + echo "$as_me:7185: \$? = $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 @@ -9554,7 +9558,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9557 "configure" +#line 9561 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -9650,7 +9654,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9653 "configure" +#line 9657 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -10393,6 +10397,68 @@ RELEASE=`cat $srcdir/VERSION` $as_echo "$as_me: Release set to $RELEASE" >&6;} +########## +# Handle --with-wasi-sdk=DIR +# +# This must be early because it changes the toolchain. +# + +# Check whether --with-wasi-sdk was given. +if test "${with_wasi_sdk+set}" = set; then : + withval=$with_wasi_sdk; with_wasisdk=${withval} +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for WASI SDK directory" >&5 +$as_echo_n "checking for WASI SDK directory... " >&6; } +if ${ac_cv_c_wasi_sdk+:} false; then : + $as_echo_n "(cached) " >&6 +else + + # First check to see if --with-tcl was specified. + if test x"${with_wasi_sdk}" != x ; then + if ! test -d "${with_wasi_sdk}" ; then + as_fn_error $? "${with_wasi_sdk} directory doesn't exist" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${with_wasi_sdk}: using wasi-sdk clang, disabling: tcl, CLI shell, DLL" >&5 +$as_echo "${with_wasi_sdk}: using wasi-sdk clang, disabling: tcl, CLI shell, DLL" >&6; } + use_wasi_sdk=yes + else + use_wasi_sdk=no + fi + +fi + +if test "${use_wasi_sdk}" = "no" ; then + HAVE_WASI_SDK="" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +else + HAVE_WASI_SDK=1 +# Changing --host and --target have no effect here except to possibly +# cause confusion. autoconf has finished processing them by this +# point. +# +# host_alias=wasm32-wasi +# target=wasm32-wasi +# +# Merely changing CC and LD to the wasi-sdk's is enough to get +# sqlite3.o building in WASM format. + CC="${with_wasi_sdk}/bin/clang" + LD="${with_wasi_sdk}/bin/wasm-ld" + RANLIB="${with_wasi_sdk}/bin/llvm-ranlib" + cross_compiling=yes + enable_threadsafe=no + use_tcl=no + enable_tcl=no + # libtool is apparently hard-coded to use gcc for linking DLLs, so + # we disable the DLL build... + enable_shared=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi + + + ######### # Locate a compiler for the build machine. This compiler should # generate command-line programs that run on the build machine. @@ -11267,6 +11333,7 @@ fi ######### # See whether we should use the amalgamation to build + # Check whether --enable-amalgamation was given. if test "${enable_amalgamation+set}" = set; then : enableval=$enable_amalgamation; @@ -12390,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.40.1, which was +This file was extended by sqlite $as_me 3.41.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12456,7 +12523,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.40.1 +sqlite config.status 3.41.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 5f905db40..53be0a686 100644 --- a/configure.ac +++ b/configure.ac @@ -70,11 +70,11 @@ # target platform. "" for Unix and ".exe" for windows. # # This configure.in file is easy to reuse on other projects. Just -# change the argument to AC_INIT(). And disable any features that +# change the argument to AC_INIT. And disable any features that # you don't need (for example BLT) by erasing or commenting out # the corresponding code. # -AC_INIT(sqlite, m4_esyscmd([cat VERSION | tr -d '\n'])) +AC_INIT([sqlite],[m4_esyscmd(cat VERSION | tr -d '\n')]) dnl Make sure the local VERSION file matches this configure script sqlite_version_sanity_check=`cat $srcdir/VERSION | tr -d '\n'` @@ -88,7 +88,7 @@ fi ######### # Programs needed # -AC_PROG_LIBTOOL +LT_INIT AC_PROG_INSTALL ######### @@ -158,6 +158,56 @@ RELEASE=`cat $srcdir/VERSION` AC_MSG_NOTICE(Release set to $RELEASE) AC_SUBST(RELEASE) +########## +# Handle --with-wasi-sdk=DIR +# +# This must be early because it changes the toolchain. +# +AC_ARG_WITH(wasi-sdk, +AS_HELP_STRING([--with-wasi-sdk=DIR], + [directory containing the WASI SDK. Triggers cross-compile to WASM.]), with_wasisdk=${withval}) +AC_MSG_CHECKING([for WASI SDK directory]) +AC_CACHE_VAL(ac_cv_c_wasi_sdk,[ + # First check to see if --with-tcl was specified. + if test x"${with_wasi_sdk}" != x ; then + if ! test -d "${with_wasi_sdk}" ; then + AC_MSG_ERROR([${with_wasi_sdk} directory doesn't exist]) + fi + AC_MSG_RESULT([${with_wasi_sdk}: using wasi-sdk clang, disabling: tcl, CLI shell, DLL]) + use_wasi_sdk=yes + else + use_wasi_sdk=no + fi +]) +if test "${use_wasi_sdk}" = "no" ; then + HAVE_WASI_SDK="" + AC_MSG_RESULT([no]) +else + HAVE_WASI_SDK=1 +# Changing --host and --target have no effect here except to possibly +# cause confusion. autoconf has finished processing them by this +# point. +# +# host_alias=wasm32-wasi +# target=wasm32-wasi +# +# Merely changing CC and LD to the wasi-sdk's is enough to get +# sqlite3.o building in WASM format. + CC="${with_wasi_sdk}/bin/clang" + LD="${with_wasi_sdk}/bin/wasm-ld" + RANLIB="${with_wasi_sdk}/bin/llvm-ranlib" + cross_compiling=yes + enable_threadsafe=no + use_tcl=no + enable_tcl=no + # libtool is apparently hard-coded to use gcc for linking DLLs, so + # we disable the DLL build... + enable_shared=no + AC_MSG_RESULT([yes]) +fi +AC_SUBST(HAVE_WASI_SDK) + + ######### # Locate a compiler for the build machine. This compiler should # generate command-line programs that run on the build machine. @@ -179,7 +229,7 @@ AC_SUBST(BUILD_CC) # Do we want to support multithreaded use of sqlite # AC_ARG_ENABLE(threadsafe, -AC_HELP_STRING([--disable-threadsafe],[Disable mutexing])) +AS_HELP_STRING([--disable-threadsafe],[Disable mutexing])) AC_MSG_CHECKING([whether to support threadsafe operation]) if test "$enable_threadsafe" = "no"; then SQLITE_THREADSAFE=0 @@ -199,7 +249,7 @@ fi # Do we want to support release # AC_ARG_ENABLE(releasemode, -AC_HELP_STRING([--enable-releasemode],[Support libtool link to release mode]),,enable_releasemode=no) +AS_HELP_STRING([--enable-releasemode],[Support libtool link to release mode]),,enable_releasemode=no) AC_MSG_CHECKING([whether to support shared library linked as release mode or not]) if test "$enable_releasemode" = "no"; then ALLOWRELEASE="" @@ -214,7 +264,7 @@ AC_SUBST(ALLOWRELEASE) # Do we want temporary databases in memory # AC_ARG_ENABLE(tempstore, -AC_HELP_STRING([--enable-tempstore],[Use an in-ram database for temporary tables (never,no,yes,always)]),,enable_tempstore=no) +AS_HELP_STRING([--enable-tempstore],[Use an in-ram database for temporary tables (never,no,yes,always)]),,enable_tempstore=no) AC_MSG_CHECKING([whether to use an in-ram database for temporary tables]) case "$enable_tempstore" in never ) @@ -254,7 +304,15 @@ else AC_MSG_RESULT(unknown) fi if test "$CYGWIN" != "yes"; then - AC_CYGWIN + m4_warn([obsolete], +[AC_CYGWIN is obsolete: use AC_CANONICAL_HOST and check if $host_os +matches *cygwin*])dnl +AC_CANONICAL_HOST +case $host_os in + *cygwin* ) CYGWIN=yes;; + * ) CYGWIN=no;; +esac + fi if test "$CYGWIN" = "yes"; then BUILD_EXEEXT=.exe @@ -289,10 +347,10 @@ AC_SUBST(TARGET_EXEEXT) # Those macros could not be used directly since we have to make some # minor changes to accomodate systems that do not have TCL installed. # -AC_ARG_ENABLE(tcl, AC_HELP_STRING([--disable-tcl],[do not build TCL extension]), +AC_ARG_ENABLE(tcl, AS_HELP_STRING([--disable-tcl],[do not build TCL extension]), [use_tcl=$enableval],[use_tcl=yes]) if test "${use_tcl}" = "yes" ; then - AC_ARG_WITH(tcl, AC_HELP_STRING([--with-tcl=DIR],[directory containing tcl configuration (tclConfig.sh)]), with_tclconfig=${withval}) + AC_ARG_WITH(tcl, AS_HELP_STRING([--with-tcl=DIR],[directory containing tcl configuration (tclConfig.sh)]), with_tclconfig=${withval}) AC_MSG_CHECKING([for Tcl configuration]) AC_CACHE_VAL(ac_cv_c_tclconfig,[ # First check to see if --with-tcl was specified. @@ -474,11 +532,11 @@ TARGET_READLINE_INC="" TARGET_HAVE_READLINE=0 TARGET_HAVE_EDITLINE=0 AC_ARG_ENABLE([editline], - [AC_HELP_STRING([--enable-editline],[enable BSD editline support])], + [AS_HELP_STRING([--enable-editline],[enable BSD editline support])], [with_editline=$enableval], [with_editline=auto]) AC_ARG_ENABLE([readline], - [AC_HELP_STRING([--disable-readline],[disable readline support])], + [AS_HELP_STRING([--disable-readline],[disable readline support])], [with_readline=$enableval], [with_readline=auto]) @@ -494,7 +552,7 @@ if test x"$with_readline" != xno; then found="yes" AC_ARG_WITH([readline-lib], - [AC_HELP_STRING([--with-readline-lib],[specify readline library])], + [AS_HELP_STRING([--with-readline-lib],[specify readline library])], [with_readline_lib=$withval], [with_readline_lib="auto"]) if test "x$with_readline_lib" = xauto; then @@ -509,7 +567,7 @@ if test x"$with_readline" != xno; then fi AC_ARG_WITH([readline-inc], - [AC_HELP_STRING([--with-readline-inc],[specify readline include paths])], + [AS_HELP_STRING([--with-readline-inc],[specify readline include paths])], [with_readline_inc=$withval], [with_readline_inc="auto"]) if test "x$with_readline_inc" = xauto; then @@ -554,7 +612,7 @@ AC_SEARCH_LIBS(fdatasync, [rt]) ######### # check for debug enabled -AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug],[enable debugging & verbose explain])) +AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[enable debugging & verbose explain])) AC_MSG_CHECKING([build type]) if test "${enable_debug}" = "yes" ; then TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0" @@ -567,7 +625,8 @@ AC_SUBST(TARGET_DEBUG) ######### # See whether we should use the amalgamation to build -AC_ARG_ENABLE(amalgamation, AC_HELP_STRING([--disable-amalgamation], + +AC_ARG_ENABLE(amalgamation, AS_HELP_STRING([--disable-amalgamation], [Disable the amalgamation and instead build all files separately])) if test "${enable_amalgamation}" = "no" ; then USE_AMALGAMATION=0 @@ -582,7 +641,7 @@ AC_SUBST(HAVE_ZLIB) ######### # See whether we should allow loadable extensions -AC_ARG_ENABLE(load-extension, AC_HELP_STRING([--disable-load-extension], +AC_ARG_ENABLE(load-extension, AS_HELP_STRING([--disable-load-extension], [Disable loading of external extensions]),,[enable_load_extension=yes]) if test "${enable_load_extension}" = "yes" ; then OPT_FEATURE_FLAGS="" @@ -595,7 +654,7 @@ fi # Do we want to support math functions # AC_ARG_ENABLE(math, -AC_HELP_STRING([--disable-math],[Disable math functions])) +AS_HELP_STRING([--disable-math],[Disable math functions])) AC_MSG_CHECKING([whether to support math functions]) if test "$enable_math" = "no"; then AC_MSG_RESULT([no]) @@ -609,7 +668,7 @@ fi # Do we want to support JSON functions # AC_ARG_ENABLE(json, -AC_HELP_STRING([--disable-json],[Disable JSON functions])) +AS_HELP_STRING([--disable-json],[Disable JSON functions])) AC_MSG_CHECKING([whether to support JSON functions]) if test "$enable_json" = "no"; then AC_MSG_RESULT([no]) @@ -621,14 +680,14 @@ fi ######## # The --enable-all argument is short-hand to enable # multiple extensions. -AC_ARG_ENABLE(all, AC_HELP_STRING([--enable-all], +AC_ARG_ENABLE(all, AS_HELP_STRING([--enable-all], [Enable FTS4, FTS5, Geopoly, RTree, Sessions])) ########## # Do we want to support memsys3 and/or memsys5 # AC_ARG_ENABLE(memsys5, - AC_HELP_STRING([--enable-memsys5],[Enable MEMSYS5])) + AS_HELP_STRING([--enable-memsys5],[Enable MEMSYS5])) AC_MSG_CHECKING([whether to support MEMSYS5]) if test "${enable_memsys5}" = "yes"; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_MEMSYS5" @@ -637,7 +696,7 @@ else AC_MSG_RESULT([no]) fi AC_ARG_ENABLE(memsys3, - AC_HELP_STRING([--enable-memsys3],[Enable MEMSYS3])) + AS_HELP_STRING([--enable-memsys3],[Enable MEMSYS3])) AC_MSG_CHECKING([whether to support MEMSYS3]) if test "${enable_memsys3}" = "yes" -a "${enable_memsys5}" = "no"; then OPT_FEATURE_FLAGS="${OPT_FEATURE_FLAGS} -DSQLITE_ENABLE_MEMSYS3" @@ -648,7 +707,7 @@ fi ######### # See whether we should enable Full Text Search extensions -AC_ARG_ENABLE(fts3, AC_HELP_STRING([--enable-fts3], +AC_ARG_ENABLE(fts3, AS_HELP_STRING([--enable-fts3], [Enable the FTS3 extension])) AC_MSG_CHECKING([whether to support FTS3]) if test "${enable_fts3}" = "yes" ; then @@ -657,7 +716,7 @@ if test "${enable_fts3}" = "yes" ; then else AC_MSG_RESULT([no]) fi -AC_ARG_ENABLE(fts4, AC_HELP_STRING([--enable-fts4], +AC_ARG_ENABLE(fts4, AS_HELP_STRING([--enable-fts4], [Enable the FTS4 extension])) AC_MSG_CHECKING([whether to support FTS4]) if test "${enable_fts4}" = "yes" -o "${enable_all}" = "yes" ; then @@ -667,7 +726,7 @@ if test "${enable_fts4}" = "yes" -o "${enable_all}" = "yes" ; then else AC_MSG_RESULT([no]) fi -AC_ARG_ENABLE(fts5, AC_HELP_STRING([--enable-fts5], +AC_ARG_ENABLE(fts5, AS_HELP_STRING([--enable-fts5], [Enable the FTS5 extension])) AC_MSG_CHECKING([whether to support FTS5]) if test "${enable_fts5}" = "yes" -o "${enable_all}" = "yes" ; then @@ -681,7 +740,7 @@ fi ######### # See whether we should enable the LIMIT clause on UPDATE and DELETE # statements. -AC_ARG_ENABLE(update-limit, AC_HELP_STRING([--enable-update-limit], +AC_ARG_ENABLE(update-limit, AS_HELP_STRING([--enable-update-limit], [Enable the UPDATE/DELETE LIMIT clause])) AC_MSG_CHECKING([whether to support LIMIT on UPDATE and DELETE statements]) if test "${enable_update_limit}" = "yes" ; then @@ -693,7 +752,7 @@ fi ######### # See whether we should enable GEOPOLY -AC_ARG_ENABLE(geopoly, AC_HELP_STRING([--enable-geopoly], +AC_ARG_ENABLE(geopoly, AS_HELP_STRING([--enable-geopoly], [Enable the GEOPOLY extension]), [enable_geopoly=yes],[enable_geopoly=no]) AC_MSG_CHECKING([whether to support GEOPOLY]) @@ -707,7 +766,7 @@ fi ######### # See whether we should enable RTREE -AC_ARG_ENABLE(rtree, AC_HELP_STRING([--enable-rtree], +AC_ARG_ENABLE(rtree, AS_HELP_STRING([--enable-rtree], [Enable the RTREE extension])) AC_MSG_CHECKING([whether to support RTREE]) if test "${enable_rtree}" = "yes" ; then @@ -719,7 +778,7 @@ fi ######### # See whether we should enable the SESSION extension -AC_ARG_ENABLE(session, AC_HELP_STRING([--enable-session], +AC_ARG_ENABLE(session, AS_HELP_STRING([--enable-session], [Enable the SESSION extension])) AC_MSG_CHECKING([whether to support SESSION]) if test "${enable_session}" = "yes" -o "${enable_all}" = "yes" ; then @@ -783,7 +842,7 @@ BUILD_CFLAGS=$ac_temp_BUILD_CFLAGS ######### # See whether we should use GCOV -AC_ARG_ENABLE(gcov, AC_HELP_STRING([--enable-gcov], +AC_ARG_ENABLE(gcov, AS_HELP_STRING([--enable-gcov], [Enable coverage testing using gcov])) if test "${use_gcov}" = "yes" ; then USE_GCOV=1 @@ -812,7 +871,8 @@ AC_CONFIG_HEADERS(sqlite_cfg.h) # Generate the output files. # AC_SUBST(BUILD_CFLAGS) -AC_OUTPUT([ +AC_CONFIG_FILES([ Makefile sqlite3.pc ]) +AC_OUTPUT diff --git a/ext/fts1/README.txt b/ext/fts1/README.txt deleted file mode 100644 index 292b7daa0..000000000 --- a/ext/fts1/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -This folder contains source code to the first full-text search -extension for SQLite. diff --git a/ext/fts1/ft_hash.c b/ext/fts1/ft_hash.c deleted file mode 100644 index 8b3a7064e..000000000 --- a/ext/fts1/ft_hash.c +++ /dev/null @@ -1,404 +0,0 @@ -/* -** 2001 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 is the implementation of generic hash-tables used in SQLite. -** We've modified it slightly to serve as a standalone hash table -** implementation for the full-text indexing module. -*/ -#include -#include -#include - -#include "ft_hash.h" - -void *malloc_and_zero(int n){ - void *p = malloc(n); - if( p ){ - memset(p, 0, n); - } - return p; -} - -/* Turn bulk memory into a hash table object by initializing the -** fields of the Hash structure. -** -** "pNew" is a pointer to the hash table that is to be initialized. -** keyClass is one of the constants HASH_INT, HASH_POINTER, -** HASH_BINARY, or HASH_STRING. The value of keyClass -** determines what kind of key the hash table will use. "copyKey" is -** true if the hash table should make its own private copy of keys and -** false if it should just use the supplied pointer. CopyKey only makes -** sense for HASH_STRING and HASH_BINARY and is ignored -** for other key classes. -*/ -void HashInit(Hash *pNew, int keyClass, int copyKey){ - assert( pNew!=0 ); - assert( keyClass>=HASH_STRING && keyClass<=HASH_BINARY ); - pNew->keyClass = keyClass; -#if 0 - if( keyClass==HASH_POINTER || keyClass==HASH_INT ) copyKey = 0; -#endif - pNew->copyKey = copyKey; - pNew->first = 0; - pNew->count = 0; - pNew->htsize = 0; - pNew->ht = 0; - pNew->xMalloc = malloc_and_zero; - pNew->xFree = free; -} - -/* Remove all entries from a hash table. Reclaim all memory. -** Call this routine to delete a hash table or to reset a hash table -** to the empty state. -*/ -void HashClear(Hash *pH){ - HashElem *elem; /* For looping over all elements of the table */ - - assert( pH!=0 ); - elem = pH->first; - pH->first = 0; - if( pH->ht ) pH->xFree(pH->ht); - pH->ht = 0; - pH->htsize = 0; - while( elem ){ - HashElem *next_elem = elem->next; - if( pH->copyKey && elem->pKey ){ - pH->xFree(elem->pKey); - } - pH->xFree(elem); - elem = next_elem; - } - pH->count = 0; -} - -#if 0 /* NOT USED */ -/* -** Hash and comparison functions when the mode is HASH_INT -*/ -static int intHash(const void *pKey, int nKey){ - return nKey ^ (nKey<<8) ^ (nKey>>8); -} -static int intCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - return n2 - n1; -} -#endif - -#if 0 /* NOT USED */ -/* -** Hash and comparison functions when the mode is HASH_POINTER -*/ -static int ptrHash(const void *pKey, int nKey){ - uptr x = Addr(pKey); - return x ^ (x<<8) ^ (x>>8); -} -static int ptrCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - if( pKey1==pKey2 ) return 0; - if( pKey1 0 ){ - h = (h<<3) ^ h ^ *z++; - nKey--; - } - return h & 0x7fffffff; -} -static int strCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - if( n1!=n2 ) return 1; - return strncmp((const char*)pKey1,(const char*)pKey2,n1); -} - -/* -** Hash and comparison functions when the mode is HASH_BINARY -*/ -static int binHash(const void *pKey, int nKey){ - int h = 0; - const char *z = (const char *)pKey; - while( nKey-- > 0 ){ - h = (h<<3) ^ h ^ *(z++); - } - return h & 0x7fffffff; -} -static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - if( n1!=n2 ) return 1; - return memcmp(pKey1,pKey2,n1); -} - -/* -** Return a pointer to the appropriate hash function given the key class. -** -** The C syntax in this function definition may be unfamilar to some -** programmers, so we provide the following additional explanation: -** -** The name of the function is "hashFunction". The function takes a -** single parameter "keyClass". The return value of hashFunction() -** is a pointer to another function. Specifically, the return value -** of hashFunction() is a pointer to a function that takes two parameters -** with types "const void*" and "int" and returns an "int". -*/ -static int (*hashFunction(int keyClass))(const void*,int){ -#if 0 /* HASH_INT and HASH_POINTER are never used */ - switch( keyClass ){ - case HASH_INT: return &intHash; - case HASH_POINTER: return &ptrHash; - case HASH_STRING: return &strHash; - case HASH_BINARY: return &binHash;; - default: break; - } - return 0; -#else - if( keyClass==HASH_STRING ){ - return &strHash; - }else{ - assert( keyClass==HASH_BINARY ); - return &binHash; - } -#endif -} - -/* -** Return a pointer to the appropriate hash function given the key class. -** -** For help in interpreted the obscure C code in the function definition, -** see the header comment on the previous function. -*/ -static int (*compareFunction(int keyClass))(const void*,int,const void*,int){ -#if 0 /* HASH_INT and HASH_POINTER are never used */ - switch( keyClass ){ - case HASH_INT: return &intCompare; - case HASH_POINTER: return &ptrCompare; - case HASH_STRING: return &strCompare; - case HASH_BINARY: return &binCompare; - default: break; - } - return 0; -#else - if( keyClass==HASH_STRING ){ - return &strCompare; - }else{ - assert( keyClass==HASH_BINARY ); - return &binCompare; - } -#endif -} - -/* Link an element into the hash table -*/ -static void insertElement( - Hash *pH, /* The complete hash table */ - struct _ht *pEntry, /* The entry into which pNew is inserted */ - HashElem *pNew /* The element to be inserted */ -){ - HashElem *pHead; /* First element already in pEntry */ - pHead = pEntry->chain; - if( pHead ){ - pNew->next = pHead; - pNew->prev = pHead->prev; - if( pHead->prev ){ pHead->prev->next = pNew; } - else { pH->first = pNew; } - pHead->prev = pNew; - }else{ - pNew->next = pH->first; - if( pH->first ){ pH->first->prev = pNew; } - pNew->prev = 0; - pH->first = pNew; - } - pEntry->count++; - pEntry->chain = pNew; -} - - -/* Resize the hash table so that it cantains "new_size" buckets. -** "new_size" must be a power of 2. The hash table might fail -** to resize if sqliteMalloc() fails. -*/ -static void rehash(Hash *pH, int new_size){ - struct _ht *new_ht; /* The new hash table */ - HashElem *elem, *next_elem; /* For looping over existing elements */ - int (*xHash)(const void*,int); /* The hash function */ - - assert( (new_size & (new_size-1))==0 ); - new_ht = (struct _ht *)pH->xMalloc( new_size*sizeof(struct _ht) ); - if( new_ht==0 ) return; - if( pH->ht ) pH->xFree(pH->ht); - pH->ht = new_ht; - pH->htsize = new_size; - xHash = hashFunction(pH->keyClass); - for(elem=pH->first, pH->first=0; elem; elem = next_elem){ - int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); - next_elem = elem->next; - insertElement(pH, &new_ht[h], elem); - } -} - -/* This function (for internal use only) locates an element in an -** hash table that matches the given key. The hash for this key has -** already been computed and is passed as the 4th parameter. -*/ -static HashElem *findElementGivenHash( - const Hash *pH, /* The pH to be searched */ - const void *pKey, /* The key we are searching for */ - int nKey, - int h /* The hash for this key. */ -){ - HashElem *elem; /* Used to loop thru the element list */ - int count; /* Number of elements left to test */ - int (*xCompare)(const void*,int,const void*,int); /* comparison function */ - - if( pH->ht ){ - struct _ht *pEntry = &pH->ht[h]; - elem = pEntry->chain; - count = pEntry->count; - xCompare = compareFunction(pH->keyClass); - while( count-- && elem ){ - if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ - return elem; - } - elem = elem->next; - } - } - return 0; -} - -/* Remove a single entry from the hash table given a pointer to that -** element and a hash on the element's key. -*/ -static void removeElementGivenHash( - Hash *pH, /* The pH containing "elem" */ - HashElem* elem, /* The element to be removed from the pH */ - int h /* Hash value for the element */ -){ - struct _ht *pEntry; - if( elem->prev ){ - elem->prev->next = elem->next; - }else{ - pH->first = elem->next; - } - if( elem->next ){ - elem->next->prev = elem->prev; - } - pEntry = &pH->ht[h]; - if( pEntry->chain==elem ){ - pEntry->chain = elem->next; - } - pEntry->count--; - if( pEntry->count<=0 ){ - pEntry->chain = 0; - } - if( pH->copyKey && elem->pKey ){ - pH->xFree(elem->pKey); - } - pH->xFree( elem ); - pH->count--; - if( pH->count<=0 ){ - assert( pH->first==0 ); - assert( pH->count==0 ); - HashClear(pH); - } -} - -/* Attempt to locate an element of the hash table pH with a key -** that matches pKey,nKey. Return the data for this element if it is -** found, or NULL if there is no match. -*/ -void *HashFind(const Hash *pH, const void *pKey, int nKey){ - int h; /* A hash on key */ - HashElem *elem; /* The element that matches key */ - int (*xHash)(const void*,int); /* The hash function */ - - if( pH==0 || pH->ht==0 ) return 0; - xHash = hashFunction(pH->keyClass); - assert( xHash!=0 ); - h = (*xHash)(pKey,nKey); - assert( (pH->htsize & (pH->htsize-1))==0 ); - elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1)); - return elem ? elem->data : 0; -} - -/* Insert an element into the hash table pH. The key is pKey,nKey -** and the data is "data". -** -** If no element exists with a matching key, then a new -** element is created. A copy of the key is made if the copyKey -** flag is set. NULL is returned. -** -** If another element already exists with the same key, then the -** new data replaces the old data and the old data is returned. -** The key is not copied in this instance. If a malloc fails, then -** the new data is returned and the hash table is unchanged. -** -** If the "data" parameter to this function is NULL, then the -** element corresponding to "key" is removed from the hash table. -*/ -void *HashInsert(Hash *pH, const void *pKey, int nKey, void *data){ - int hraw; /* Raw hash value of the key */ - int h; /* the hash of the key modulo hash table size */ - HashElem *elem; /* Used to loop thru the element list */ - HashElem *new_elem; /* New element added to the pH */ - int (*xHash)(const void*,int); /* The hash function */ - - assert( pH!=0 ); - xHash = hashFunction(pH->keyClass); - assert( xHash!=0 ); - hraw = (*xHash)(pKey, nKey); - assert( (pH->htsize & (pH->htsize-1))==0 ); - h = hraw & (pH->htsize-1); - elem = findElementGivenHash(pH,pKey,nKey,h); - if( elem ){ - void *old_data = elem->data; - if( data==0 ){ - removeElementGivenHash(pH,elem,h); - }else{ - elem->data = data; - } - return old_data; - } - if( data==0 ) return 0; - new_elem = (HashElem*)pH->xMalloc( sizeof(HashElem) ); - if( new_elem==0 ) return data; - if( pH->copyKey && pKey!=0 ){ - new_elem->pKey = pH->xMalloc( nKey ); - if( new_elem->pKey==0 ){ - pH->xFree(new_elem); - return data; - } - memcpy((void*)new_elem->pKey, pKey, nKey); - }else{ - new_elem->pKey = (void*)pKey; - } - new_elem->nKey = nKey; - pH->count++; - if( pH->htsize==0 ){ - rehash(pH,8); - if( pH->htsize==0 ){ - pH->count = 0; - pH->xFree(new_elem); - return data; - } - } - if( pH->count > pH->htsize ){ - rehash(pH,pH->htsize*2); - } - assert( pH->htsize>0 ); - assert( (pH->htsize & (pH->htsize-1))==0 ); - h = hraw & (pH->htsize-1); - insertElement(pH, &pH->ht[h], new_elem); - new_elem->data = data; - return 0; -} diff --git a/ext/fts1/ft_hash.h b/ext/fts1/ft_hash.h deleted file mode 100644 index 95871a459..000000000 --- a/ext/fts1/ft_hash.h +++ /dev/null @@ -1,111 +0,0 @@ -/* -** 2001 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 is the header file for the generic hash-table implementation -** used in SQLite. We've modified it slightly to serve as a standalone -** hash table implementation for the full-text indexing module. -** -*/ -#ifndef _HASH_H_ -#define _HASH_H_ - -/* Forward declarations of structures. */ -typedef struct Hash Hash; -typedef struct HashElem HashElem; - -/* A complete hash table is an instance of the following structure. -** The internals of this structure are intended to be opaque -- client -** code should not attempt to access or modify the fields of this structure -** directly. Change this structure only by using the routines below. -** However, many of the "procedures" and "functions" for modifying and -** accessing this structure are really macros, so we can't really make -** this structure opaque. -*/ -struct Hash { - char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */ - char copyKey; /* True if copy of key made on insert */ - int count; /* Number of entries in this table */ - HashElem *first; /* The first element of the array */ - void *(*xMalloc)(int); /* malloc() function to use */ - void (*xFree)(void *); /* free() function to use */ - int htsize; /* Number of buckets in the hash table */ - struct _ht { /* the hash table */ - int count; /* Number of entries with this hash */ - HashElem *chain; /* Pointer to first entry with this hash */ - } *ht; -}; - -/* Each element in the hash table is an instance of the following -** structure. All elements are stored on a single doubly-linked list. -** -** Again, this structure is intended to be opaque, but it can't really -** be opaque because it is used by macros. -*/ -struct HashElem { - HashElem *next, *prev; /* Next and previous elements in the table */ - void *data; /* Data associated with this element */ - void *pKey; int nKey; /* Key associated with this element */ -}; - -/* -** There are 4 different modes of operation for a hash table: -** -** HASH_INT nKey is used as the key and pKey is ignored. -** -** HASH_POINTER pKey is used as the key and nKey is ignored. -** -** HASH_STRING pKey points to a string that is nKey bytes long -** (including the null-terminator, if any). Case -** is respected in comparisons. -** -** HASH_BINARY pKey points to binary data nKey bytes long. -** memcmp() is used to compare keys. -** -** A copy of the key is made for HASH_STRING and HASH_BINARY -** if the copyKey parameter to HashInit is 1. -*/ -/* #define HASH_INT 1 // NOT USED */ -/* #define HASH_POINTER 2 // NOT USED */ -#define HASH_STRING 3 -#define HASH_BINARY 4 - -/* -** Access routines. To delete, insert a NULL pointer. -*/ -void HashInit(Hash*, int keytype, int copyKey); -void *HashInsert(Hash*, const void *pKey, int nKey, void *pData); -void *HashFind(const Hash*, const void *pKey, int nKey); -void HashClear(Hash*); - -/* -** Macros for looping over all elements of a hash table. The idiom is -** like this: -** -** Hash h; -** HashElem *p; -** ... -** for(p=HashFirst(&h); p; p=HashNext(p)){ -** SomeStructure *pData = HashData(p); -** // do something with pData -** } -*/ -#define HashFirst(H) ((H)->first) -#define HashNext(E) ((E)->next) -#define HashData(E) ((E)->data) -#define HashKey(E) ((E)->pKey) -#define HashKeysize(E) ((E)->nKey) - -/* -** Number of entries in a hash table -*/ -#define HashCount(H) ((H)->count) - -#endif /* _HASH_H_ */ diff --git a/ext/fts1/fts1.c b/ext/fts1/fts1.c deleted file mode 100644 index 77fa9e23f..000000000 --- a/ext/fts1/fts1.c +++ /dev/null @@ -1,3348 +0,0 @@ -/* fts1 has a design flaw which can lead to database corruption (see -** below). It is recommended not to use it any longer, instead use -** fts3 (or higher). If you believe that your use of fts1 is safe, -** add -DSQLITE_ENABLE_BROKEN_FTS1=1 to your CFLAGS. -*/ -#if (!defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1)) \ - && !defined(SQLITE_ENABLE_BROKEN_FTS1) -#error fts1 has a design flaw and has been deprecated. -#endif -/* The flaw is that fts1 uses the content table's unaliased rowid as -** the unique docid. fts1 embeds the rowid in the index it builds, -** and expects the rowid to not change. The SQLite VACUUM operation -** will renumber such rowids, thereby breaking fts1. If you are using -** fts1 in a system which has disabled VACUUM, then you can continue -** to use it safely. Note that PRAGMA auto_vacuum does NOT disable -** VACUUM, though systems using auto_vacuum are unlikely to invoke -** VACUUM. -** -** fts1 should be safe even across VACUUM if you only insert documents -** and never delete. -*/ - -/* The author disclaims copyright to this source code. - * - * This is an SQLite module implementing full-text search. - */ - -/* -** The code in this file is only compiled if: -** -** * The FTS1 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS1 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS1 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) - -#if defined(SQLITE_ENABLE_FTS1) && !defined(SQLITE_CORE) -# define SQLITE_CORE 1 -#endif - -#include -#include -#include -#include -#include - -#include "fts1.h" -#include "fts1_hash.h" -#include "fts1_tokenizer.h" -#include "sqlite3.h" -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT1 - - -#if 0 -# define TRACE(A) printf A; fflush(stdout) -#else -# define TRACE(A) -#endif - -/* utility functions */ - -typedef struct StringBuffer { - int len; /* length, not including null terminator */ - int alloced; /* Space allocated for s[] */ - char *s; /* Content of the string */ -} StringBuffer; - -static void initStringBuffer(StringBuffer *sb){ - sb->len = 0; - sb->alloced = 100; - sb->s = malloc(100); - sb->s[0] = '\0'; -} - -static void nappend(StringBuffer *sb, const char *zFrom, int nFrom){ - if( sb->len + nFrom >= sb->alloced ){ - sb->alloced = sb->len + nFrom + 100; - sb->s = realloc(sb->s, sb->alloced+1); - if( sb->s==0 ){ - initStringBuffer(sb); - return; - } - } - memcpy(sb->s + sb->len, zFrom, nFrom); - sb->len += nFrom; - sb->s[sb->len] = 0; -} -static void append(StringBuffer *sb, const char *zFrom){ - nappend(sb, zFrom, strlen(zFrom)); -} - -/* We encode variable-length integers in little-endian order using seven bits - * per byte as follows: -** -** KEY: -** A = 0xxxxxxx 7 bits of data and one flag bit -** B = 1xxxxxxx 7 bits of data and one flag bit -** -** 7 bits - A -** 14 bits - BA -** 21 bits - BBA -** and so on. -*/ - -/* We may need up to VARINT_MAX bytes to store an encoded 64-bit integer. */ -#define VARINT_MAX 10 - -/* Write a 64-bit variable-length integer to memory starting at p[0]. - * The length of data written will be between 1 and VARINT_MAX bytes. - * The number of bytes written is returned. */ -static int putVarint(char *p, sqlite_int64 v){ - unsigned char *q = (unsigned char *) p; - sqlite_uint64 vu = v; - do{ - *q++ = (unsigned char) ((vu & 0x7f) | 0x80); - vu >>= 7; - }while( vu!=0 ); - q[-1] &= 0x7f; /* turn off high bit in final byte */ - assert( q - (unsigned char *)p <= VARINT_MAX ); - return (int) (q - (unsigned char *)p); -} - -/* Read a 64-bit variable-length integer from memory starting at p[0]. - * Return the number of bytes read, or 0 on error. - * The value is stored in *v. */ -static int getVarint(const char *p, sqlite_int64 *v){ - const unsigned char *q = (const unsigned char *) p; - sqlite_uint64 x = 0, y = 1; - while( (*q & 0x80) == 0x80 ){ - x += y * (*q++ & 0x7f); - y <<= 7; - if( q - (unsigned char *)p >= VARINT_MAX ){ /* bad data */ - assert( 0 ); - return 0; - } - } - x += y * (*q++); - *v = (sqlite_int64) x; - return (int) (q - (unsigned char *)p); -} - -static int getVarint32(const char *p, int *pi){ - sqlite_int64 i; - int ret = getVarint(p, &i); - *pi = (int) i; - assert( *pi==i ); - return ret; -} - -/*** Document lists *** - * - * A document list holds a sorted list of varint-encoded document IDs. - * - * A doclist with type DL_POSITIONS_OFFSETS is stored like this: - * - * array { - * varint docid; - * array { - * varint position; (delta from previous position plus POS_BASE) - * varint startOffset; (delta from previous startOffset) - * varint endOffset; (delta from startOffset) - * } - * } - * - * Here, array { X } means zero or more occurrences of X, adjacent in memory. - * - * A position list may hold positions for text in multiple columns. A position - * POS_COLUMN is followed by a varint containing the index of the column for - * following positions in the list. Any positions appearing before any - * occurrences of POS_COLUMN are for column 0. - * - * A doclist with type DL_POSITIONS is like the above, but holds only docids - * and positions without offset information. - * - * A doclist with type DL_DOCIDS is like the above, but holds only docids - * without positions or offset information. - * - * On disk, every document list has positions and offsets, so we don't bother - * to serialize a doclist's type. - * - * We don't yet delta-encode document IDs; doing so will probably be a - * modest win. - * - * NOTE(shess) I've thought of a slightly (1%) better offset encoding. - * After the first offset, estimate the next offset by using the - * current token position and the previous token position and offset, - * offset to handle some variance. So the estimate would be - * (iPosition*w->iStartOffset/w->iPosition-64), which is delta-encoded - * as normal. Offsets more than 64 chars from the estimate are - * encoded as the delta to the previous start offset + 128. An - * additional tiny increment can be gained by using the end offset of - * the previous token to make the estimate a tiny bit more precise. -*/ - -/* It is not safe to call isspace(), tolower(), or isalnum() on -** hi-bit-set characters. This is the same solution used in the -** tokenizer. -*/ -/* TODO(shess) The snippet-generation code should be using the -** tokenizer-generated tokens rather than doing its own local -** tokenization. -*/ -/* TODO(shess) Is __isascii() a portable version of (c&0x80)==0? */ -static int safe_isspace(char c){ - return (c&0x80)==0 ? isspace((unsigned char)c) : 0; -} -static int safe_tolower(char c){ - return (c&0x80)==0 ? tolower((unsigned char)c) : c; -} -static int safe_isalnum(char c){ - return (c&0x80)==0 ? isalnum((unsigned char)c) : 0; -} - -typedef enum DocListType { - DL_DOCIDS, /* docids only */ - DL_POSITIONS, /* docids + positions */ - DL_POSITIONS_OFFSETS /* docids + positions + offsets */ -} DocListType; - -/* -** By default, only positions and not offsets are stored in the doclists. -** To change this so that offsets are stored too, compile with -** -** -DDL_DEFAULT=DL_POSITIONS_OFFSETS -** -*/ -#ifndef DL_DEFAULT -# define DL_DEFAULT DL_POSITIONS -#endif - -typedef struct DocList { - char *pData; - int nData; - DocListType iType; - int iLastColumn; /* the last column written */ - int iLastPos; /* the last position written */ - int iLastOffset; /* the last start offset written */ -} DocList; - -enum { - POS_END = 0, /* end of this position list */ - POS_COLUMN, /* followed by new column number */ - POS_BASE -}; - -/* Initialize a new DocList to hold the given data. */ -static void docListInit(DocList *d, DocListType iType, - const char *pData, int nData){ - d->nData = nData; - if( nData>0 ){ - d->pData = malloc(nData); - memcpy(d->pData, pData, nData); - } else { - d->pData = NULL; - } - d->iType = iType; - d->iLastColumn = 0; - d->iLastPos = d->iLastOffset = 0; -} - -/* Create a new dynamically-allocated DocList. */ -static DocList *docListNew(DocListType iType){ - DocList *d = (DocList *) malloc(sizeof(DocList)); - docListInit(d, iType, 0, 0); - return d; -} - -static void docListDestroy(DocList *d){ - free(d->pData); -#ifndef NDEBUG - memset(d, 0x55, sizeof(*d)); -#endif -} - -static void docListDelete(DocList *d){ - docListDestroy(d); - free(d); -} - -static char *docListEnd(DocList *d){ - return d->pData + d->nData; -} - -/* Append a varint to a DocList's data. */ -static void appendVarint(DocList *d, sqlite_int64 i){ - char c[VARINT_MAX]; - int n = putVarint(c, i); - d->pData = realloc(d->pData, d->nData + n); - memcpy(d->pData + d->nData, c, n); - d->nData += n; -} - -static void docListAddDocid(DocList *d, sqlite_int64 iDocid){ - appendVarint(d, iDocid); - if( d->iType>=DL_POSITIONS ){ - appendVarint(d, POS_END); /* initially empty position list */ - d->iLastColumn = 0; - d->iLastPos = d->iLastOffset = 0; - } -} - -/* helper function for docListAddPos and docListAddPosOffset */ -static void addPos(DocList *d, int iColumn, int iPos){ - assert( d->nData>0 ); - --d->nData; /* remove previous terminator */ - if( iColumn!=d->iLastColumn ){ - assert( iColumn>d->iLastColumn ); - appendVarint(d, POS_COLUMN); - appendVarint(d, iColumn); - d->iLastColumn = iColumn; - d->iLastPos = d->iLastOffset = 0; - } - assert( iPos>=d->iLastPos ); - appendVarint(d, iPos-d->iLastPos+POS_BASE); - d->iLastPos = iPos; -} - -/* Add a position to the last position list in a doclist. */ -static void docListAddPos(DocList *d, int iColumn, int iPos){ - assert( d->iType==DL_POSITIONS ); - addPos(d, iColumn, iPos); - appendVarint(d, POS_END); /* add new terminator */ -} - -/* -** Add a position and starting and ending offsets to a doclist. -** -** If the doclist is setup to handle only positions, then insert -** the position only and ignore the offsets. -*/ -static void docListAddPosOffset( - DocList *d, /* Doclist under construction */ - int iColumn, /* Column the inserted term is part of */ - int iPos, /* Position of the inserted term */ - int iStartOffset, /* Starting offset of inserted term */ - int iEndOffset /* Ending offset of inserted term */ -){ - assert( d->iType>=DL_POSITIONS ); - addPos(d, iColumn, iPos); - if( d->iType==DL_POSITIONS_OFFSETS ){ - assert( iStartOffset>=d->iLastOffset ); - appendVarint(d, iStartOffset-d->iLastOffset); - d->iLastOffset = iStartOffset; - assert( iEndOffset>=iStartOffset ); - appendVarint(d, iEndOffset-iStartOffset); - } - appendVarint(d, POS_END); /* add new terminator */ -} - -/* -** A DocListReader object is a cursor into a doclist. Initialize -** the cursor to the beginning of the doclist by calling readerInit(). -** Then use routines -** -** peekDocid() -** readDocid() -** readPosition() -** skipPositionList() -** and so forth... -** -** to read information out of the doclist. When we reach the end -** of the doclist, atEnd() returns TRUE. -*/ -typedef struct DocListReader { - DocList *pDoclist; /* The document list we are stepping through */ - char *p; /* Pointer to next unread byte in the doclist */ - int iLastColumn; - int iLastPos; /* the last position read, or -1 when not in a position list */ -} DocListReader; - -/* -** Initialize the DocListReader r to point to the beginning of pDoclist. -*/ -static void readerInit(DocListReader *r, DocList *pDoclist){ - r->pDoclist = pDoclist; - if( pDoclist!=NULL ){ - r->p = pDoclist->pData; - } - r->iLastColumn = -1; - r->iLastPos = -1; -} - -/* -** Return TRUE if we have reached then end of pReader and there is -** nothing else left to read. -*/ -static int atEnd(DocListReader *pReader){ - return pReader->pDoclist==0 || (pReader->p >= docListEnd(pReader->pDoclist)); -} - -/* Peek at the next docid without advancing the read pointer. -*/ -static sqlite_int64 peekDocid(DocListReader *pReader){ - sqlite_int64 ret; - assert( !atEnd(pReader) ); - assert( pReader->iLastPos==-1 ); - getVarint(pReader->p, &ret); - return ret; -} - -/* Read the next docid. See also nextDocid(). -*/ -static sqlite_int64 readDocid(DocListReader *pReader){ - sqlite_int64 ret; - assert( !atEnd(pReader) ); - assert( pReader->iLastPos==-1 ); - pReader->p += getVarint(pReader->p, &ret); - if( pReader->pDoclist->iType>=DL_POSITIONS ){ - pReader->iLastColumn = 0; - pReader->iLastPos = 0; - } - return ret; -} - -/* Read the next position and column index from a position list. - * Returns the position, or -1 at the end of the list. */ -static int readPosition(DocListReader *pReader, int *iColumn){ - int i; - int iType = pReader->pDoclist->iType; - - if( pReader->iLastPos==-1 ){ - return -1; - } - assert( !atEnd(pReader) ); - - if( iTypep += getVarint32(pReader->p, &i); - if( i==POS_END ){ - pReader->iLastColumn = pReader->iLastPos = -1; - *iColumn = -1; - return -1; - } - if( i==POS_COLUMN ){ - pReader->p += getVarint32(pReader->p, &pReader->iLastColumn); - pReader->iLastPos = 0; - pReader->p += getVarint32(pReader->p, &i); - assert( i>=POS_BASE ); - } - pReader->iLastPos += ((int) i)-POS_BASE; - if( iType>=DL_POSITIONS_OFFSETS ){ - /* Skip over offsets, ignoring them for now. */ - int iStart, iEnd; - pReader->p += getVarint32(pReader->p, &iStart); - pReader->p += getVarint32(pReader->p, &iEnd); - } - *iColumn = pReader->iLastColumn; - return pReader->iLastPos; -} - -/* Skip past the end of a position list. */ -static void skipPositionList(DocListReader *pReader){ - DocList *p = pReader->pDoclist; - if( p && p->iType>=DL_POSITIONS ){ - int iColumn; - while( readPosition(pReader, &iColumn)!=-1 ){} - } -} - -/* Skip over a docid, including its position list if the doclist has - * positions. */ -static void skipDocument(DocListReader *pReader){ - readDocid(pReader); - skipPositionList(pReader); -} - -/* Skip past all docids which are less than [iDocid]. Returns 1 if a docid - * matching [iDocid] was found. */ -static int skipToDocid(DocListReader *pReader, sqlite_int64 iDocid){ - sqlite_int64 d = 0; - while( !atEnd(pReader) && (d=peekDocid(pReader))iType>=DL_POSITIONS ){ - int iPos, iCol; - const char *zDiv = ""; - printf("("); - while( (iPos = readPosition(&r, &iCol))>=0 ){ - printf("%s%d:%d", zDiv, iCol, iPos); - zDiv = ":"; - } - printf(")"); - } - } - printf("\n"); - fflush(stdout); -} -#endif /* SQLITE_DEBUG */ - -/* Trim the given doclist to contain only positions in column - * [iRestrictColumn]. */ -static void docListRestrictColumn(DocList *in, int iRestrictColumn){ - DocListReader r; - DocList out; - - assert( in->iType>=DL_POSITIONS ); - readerInit(&r, in); - docListInit(&out, DL_POSITIONS, NULL, 0); - - while( !atEnd(&r) ){ - sqlite_int64 iDocid = readDocid(&r); - int iPos, iColumn; - - docListAddDocid(&out, iDocid); - while( (iPos = readPosition(&r, &iColumn)) != -1 ){ - if( iColumn==iRestrictColumn ){ - docListAddPos(&out, iColumn, iPos); - } - } - } - - docListDestroy(in); - *in = out; -} - -/* Trim the given doclist by discarding any docids without any remaining - * positions. */ -static void docListDiscardEmpty(DocList *in) { - DocListReader r; - DocList out; - - /* TODO: It would be nice to implement this operation in place; that - * could save a significant amount of memory in queries with long doclists. */ - assert( in->iType>=DL_POSITIONS ); - readerInit(&r, in); - docListInit(&out, DL_POSITIONS, NULL, 0); - - while( !atEnd(&r) ){ - sqlite_int64 iDocid = readDocid(&r); - int match = 0; - int iPos, iColumn; - while( (iPos = readPosition(&r, &iColumn)) != -1 ){ - if( !match ){ - docListAddDocid(&out, iDocid); - match = 1; - } - docListAddPos(&out, iColumn, iPos); - } - } - - docListDestroy(in); - *in = out; -} - -/* Helper function for docListUpdate() and docListAccumulate(). -** Splices a doclist element into the doclist represented by r, -** leaving r pointing after the newly spliced element. -*/ -static void docListSpliceElement(DocListReader *r, sqlite_int64 iDocid, - const char *pSource, int nSource){ - DocList *d = r->pDoclist; - char *pTarget; - int nTarget, found; - - found = skipToDocid(r, iDocid); - - /* Describe slice in d to place pSource/nSource. */ - pTarget = r->p; - if( found ){ - skipDocument(r); - nTarget = r->p-pTarget; - }else{ - nTarget = 0; - } - - /* The sense of the following is that there are three possibilities. - ** If nTarget==nSource, we should not move any memory nor realloc. - ** If nTarget>nSource, trim target and realloc. - ** If nTargetnSource ){ - memmove(pTarget+nSource, pTarget+nTarget, docListEnd(d)-(pTarget+nTarget)); - } - if( nTarget!=nSource ){ - int iDoclist = pTarget-d->pData; - d->pData = realloc(d->pData, d->nData+nSource-nTarget); - pTarget = d->pData+iDoclist; - } - if( nTargetnData += nSource-nTarget; - r->p = pTarget+nSource; -} - -/* Insert/update pUpdate into the doclist. */ -static void docListUpdate(DocList *d, DocList *pUpdate){ - DocListReader reader; - - assert( d!=NULL && pUpdate!=NULL ); - assert( d->iType==pUpdate->iType); - - readerInit(&reader, d); - docListSpliceElement(&reader, firstDocid(pUpdate), - pUpdate->pData, pUpdate->nData); -} - -/* Propagate elements from pUpdate to pAcc, overwriting elements with -** matching docids. -*/ -static void docListAccumulate(DocList *pAcc, DocList *pUpdate){ - DocListReader accReader, updateReader; - - /* Handle edge cases where one doclist is empty. */ - assert( pAcc!=NULL ); - if( pUpdate==NULL || pUpdate->nData==0 ) return; - if( pAcc->nData==0 ){ - pAcc->pData = malloc(pUpdate->nData); - memcpy(pAcc->pData, pUpdate->pData, pUpdate->nData); - pAcc->nData = pUpdate->nData; - return; - } - - readerInit(&accReader, pAcc); - readerInit(&updateReader, pUpdate); - - while( !atEnd(&updateReader) ){ - char *pSource = updateReader.p; - sqlite_int64 iDocid = readDocid(&updateReader); - skipPositionList(&updateReader); - docListSpliceElement(&accReader, iDocid, pSource, updateReader.p-pSource); - } -} - -/* -** Read the next docid off of pIn. Return 0 if we reach the end. -* -* TODO: This assumes that docids are never 0, but they may actually be 0 since -* users can choose docids when inserting into a full-text table. Fix this. -*/ -static sqlite_int64 nextDocid(DocListReader *pIn){ - skipPositionList(pIn); - return atEnd(pIn) ? 0 : readDocid(pIn); -} - -/* -** pLeft and pRight are two DocListReaders that are pointing to -** positions lists of the same document: iDocid. -** -** If there are no instances in pLeft or pRight where the position -** of pLeft is one less than the position of pRight, then this -** routine adds nothing to pOut. -** -** If there are one or more instances where positions from pLeft -** are exactly one less than positions from pRight, then add a new -** document record to pOut. If pOut wants to hold positions, then -** include the positions from pRight that are one more than a -** position in pLeft. In other words: pRight.iPos==pLeft.iPos+1. -** -** pLeft and pRight are left pointing at the next document record. -*/ -static void mergePosList( - DocListReader *pLeft, /* Left position list */ - DocListReader *pRight, /* Right position list */ - sqlite_int64 iDocid, /* The docid from pLeft and pRight */ - DocList *pOut /* Write the merged document record here */ -){ - int iLeftCol, iLeftPos = readPosition(pLeft, &iLeftCol); - int iRightCol, iRightPos = readPosition(pRight, &iRightCol); - int match = 0; - - /* Loop until we've reached the end of both position lists. */ - while( iLeftPos!=-1 && iRightPos!=-1 ){ - if( iLeftCol==iRightCol && iLeftPos+1==iRightPos ){ - if( !match ){ - docListAddDocid(pOut, iDocid); - match = 1; - } - if( pOut->iType>=DL_POSITIONS ){ - docListAddPos(pOut, iRightCol, iRightPos); - } - iLeftPos = readPosition(pLeft, &iLeftCol); - iRightPos = readPosition(pRight, &iRightCol); - }else if( iRightCol=0 ) skipPositionList(pLeft); - if( iRightPos>=0 ) skipPositionList(pRight); -} - -/* We have two doclists: pLeft and pRight. -** Write the phrase intersection of these two doclists into pOut. -** -** A phrase intersection means that two documents only match -** if pLeft.iPos+1==pRight.iPos. -** -** The output pOut may or may not contain positions. If pOut -** does contain positions, they are the positions of pRight. -*/ -static void docListPhraseMerge( - DocList *pLeft, /* Doclist resulting from the words on the left */ - DocList *pRight, /* Doclist for the next word to the right */ - DocList *pOut /* Write the combined doclist here */ -){ - DocListReader left, right; - sqlite_int64 docidLeft, docidRight; - - readerInit(&left, pLeft); - readerInit(&right, pRight); - docidLeft = nextDocid(&left); - docidRight = nextDocid(&right); - - while( docidLeft>0 && docidRight>0 ){ - if( docidLeftiType0 && docidRight>0 ){ - if( docidLeft0 && docidRight>0 ){ - if( docidLeft<=docidRight ){ - docListAddDocid(pOut, docidLeft); - }else{ - docListAddDocid(pOut, docidRight); - } - priorLeft = docidLeft; - if( docidLeft<=docidRight ){ - docidLeft = nextDocid(&left); - } - if( docidRight>0 && docidRight<=priorLeft ){ - docidRight = nextDocid(&right); - } - } - while( docidLeft>0 ){ - docListAddDocid(pOut, docidLeft); - docidLeft = nextDocid(&left); - } - while( docidRight>0 ){ - docListAddDocid(pOut, docidRight); - docidRight = nextDocid(&right); - } -} - -/* We have two doclists: pLeft and pRight. -** Write into pOut all documents that occur in pLeft but not -** in pRight. -** -** Only docids are matched. Position information is ignored. -** -** The output pOut never holds positions. -*/ -static void docListExceptMerge( - DocList *pLeft, /* Doclist resulting from the words on the left */ - DocList *pRight, /* Doclist for the next word to the right */ - DocList *pOut /* Write the combined doclist here */ -){ - DocListReader left, right; - sqlite_int64 docidLeft, docidRight, priorLeft; - - readerInit(&left, pLeft); - readerInit(&right, pRight); - docidLeft = nextDocid(&left); - docidRight = nextDocid(&right); - - while( docidLeft>0 && docidRight>0 ){ - priorLeft = docidLeft; - if( docidLeft0 && docidRight<=priorLeft ){ - docidRight = nextDocid(&right); - } - } - while( docidLeft>0 ){ - docListAddDocid(pOut, docidLeft); - docidLeft = nextDocid(&left); - } -} - -static char *string_dup_n(const char *s, int n){ - char *str = malloc(n + 1); - memcpy(str, s, n); - str[n] = '\0'; - return str; -} - -/* Duplicate a string; the caller must free() the returned string. - * (We don't use strdup() since it is not part of the standard C library and - * may not be available everywhere.) */ -static char *string_dup(const char *s){ - return string_dup_n(s, strlen(s)); -} - -/* Format a string, replacing each occurrence of the % character with - * zDb.zName. This may be more convenient than sqlite_mprintf() - * when one string is used repeatedly in a format string. - * The caller must free() the returned string. */ -static char *string_format(const char *zFormat, - const char *zDb, const char *zName){ - const char *p; - size_t len = 0; - size_t nDb = strlen(zDb); - size_t nName = strlen(zName); - size_t nFullTableName = nDb+1+nName; - char *result; - char *r; - - /* first compute length needed */ - for(p = zFormat ; *p ; ++p){ - len += (*p=='%' ? nFullTableName : 1); - } - len += 1; /* for null terminator */ - - r = result = malloc(len); - for(p = zFormat; *p; ++p){ - if( *p=='%' ){ - memcpy(r, zDb, nDb); - r += nDb; - *r++ = '.'; - memcpy(r, zName, nName); - r += nName; - } else { - *r++ = *p; - } - } - *r++ = '\0'; - assert( r == result + len ); - return result; -} - -static int sql_exec(sqlite3 *db, const char *zDb, const char *zName, - const char *zFormat){ - char *zCommand = string_format(zFormat, zDb, zName); - int rc; - TRACE(("FTS1 sql: %s\n", zCommand)); - rc = sqlite3_exec(db, zCommand, NULL, 0, NULL); - free(zCommand); - return rc; -} - -static int sql_prepare(sqlite3 *db, const char *zDb, const char *zName, - sqlite3_stmt **ppStmt, const char *zFormat){ - char *zCommand = string_format(zFormat, zDb, zName); - int rc; - TRACE(("FTS1 prepare: %s\n", zCommand)); - rc = sqlite3_prepare(db, zCommand, -1, ppStmt, NULL); - free(zCommand); - return rc; -} - -/* end utility functions */ - -/* Forward reference */ -typedef struct fulltext_vtab fulltext_vtab; - -/* A single term in a query is represented by an instances of -** the following structure. -*/ -typedef struct QueryTerm { - short int nPhrase; /* How many following terms are part of the same phrase */ - short int iPhrase; /* This is the i-th term of a phrase. */ - short int iColumn; /* Column of the index that must match this term */ - signed char isOr; /* this term is preceded by "OR" */ - signed char isNot; /* this term is preceded by "-" */ - char *pTerm; /* text of the term. '\000' terminated. malloced */ - int nTerm; /* Number of bytes in pTerm[] */ -} QueryTerm; - - -/* A query string is parsed into a Query structure. - * - * We could, in theory, allow query strings to be complicated - * nested expressions with precedence determined by parentheses. - * But none of the major search engines do this. (Perhaps the - * feeling is that an parenthesized expression is two complex of - * an idea for the average user to grasp.) Taking our lead from - * the major search engines, we will allow queries to be a list - * of terms (with an implied AND operator) or phrases in double-quotes, - * with a single optional "-" before each non-phrase term to designate - * negation and an optional OR connector. - * - * OR binds more tightly than the implied AND, which is what the - * major search engines seem to do. So, for example: - * - * [one two OR three] ==> one AND (two OR three) - * [one OR two three] ==> (one OR two) AND three - * - * A "-" before a term matches all entries that lack that term. - * The "-" must occur immediately before the term with in intervening - * space. This is how the search engines do it. - * - * A NOT term cannot be the right-hand operand of an OR. If this - * occurs in the query string, the NOT is ignored: - * - * [one OR -two] ==> one OR two - * - */ -typedef struct Query { - fulltext_vtab *pFts; /* The full text index */ - int nTerms; /* Number of terms in the query */ - QueryTerm *pTerms; /* Array of terms. Space obtained from malloc() */ - int nextIsOr; /* Set the isOr flag on the next inserted term */ - int nextColumn; /* Next word parsed must be in this column */ - int dfltColumn; /* The default column */ -} Query; - - -/* -** An instance of the following structure keeps track of generated -** matching-word offset information and snippets. -*/ -typedef struct Snippet { - int nMatch; /* Total number of matches */ - int nAlloc; /* Space allocated for aMatch[] */ - struct snippetMatch { /* One entry for each matching term */ - char snStatus; /* Status flag for use while constructing snippets */ - short int iCol; /* The column that contains the match */ - short int iTerm; /* The index in Query.pTerms[] of the matching term */ - short int nByte; /* Number of bytes in the term */ - int iStart; /* The offset to the first character of the term */ - } *aMatch; /* Points to space obtained from malloc */ - char *zOffset; /* Text rendering of aMatch[] */ - int nOffset; /* strlen(zOffset) */ - char *zSnippet; /* Snippet text */ - int nSnippet; /* strlen(zSnippet) */ -} Snippet; - - -typedef enum QueryType { - QUERY_GENERIC, /* table scan */ - QUERY_ROWID, /* lookup by rowid */ - QUERY_FULLTEXT /* QUERY_FULLTEXT + [i] is a full-text search for column i*/ -} QueryType; - -/* TODO(shess) CHUNK_MAX controls how much data we allow in segment 0 -** before we start aggregating into larger segments. Lower CHUNK_MAX -** means that for a given input we have more individual segments per -** term, which means more rows in the table and a bigger index (due to -** both more rows and bigger rowids). But it also reduces the average -** cost of adding new elements to the segment 0 doclist, and it seems -** to reduce the number of pages read and written during inserts. 256 -** was chosen by measuring insertion times for a certain input (first -** 10k documents of Enron corpus), though including query performance -** in the decision may argue for a larger value. -*/ -#define CHUNK_MAX 256 - -typedef enum fulltext_statement { - CONTENT_INSERT_STMT, - CONTENT_SELECT_STMT, - CONTENT_UPDATE_STMT, - CONTENT_DELETE_STMT, - - TERM_SELECT_STMT, - TERM_SELECT_ALL_STMT, - TERM_INSERT_STMT, - TERM_UPDATE_STMT, - TERM_DELETE_STMT, - - MAX_STMT /* Always at end! */ -} fulltext_statement; - -/* These must exactly match the enum above. */ -/* TODO(adam): Is there some risk that a statement (in particular, -** pTermSelectStmt) will be used in two cursors at once, e.g. if a -** query joins a virtual table to itself? If so perhaps we should -** move some of these to the cursor object. -*/ -static const char *const fulltext_zStatement[MAX_STMT] = { - /* CONTENT_INSERT */ NULL, /* generated in contentInsertStatement() */ - /* CONTENT_SELECT */ "select * from %_content where rowid = ?", - /* CONTENT_UPDATE */ NULL, /* generated in contentUpdateStatement() */ - /* CONTENT_DELETE */ "delete from %_content where rowid = ?", - - /* TERM_SELECT */ - "select rowid, doclist from %_term where term = ? and segment = ?", - /* TERM_SELECT_ALL */ - "select doclist from %_term where term = ? order by segment", - /* TERM_INSERT */ - "insert into %_term (rowid, term, segment, doclist) values (?, ?, ?, ?)", - /* TERM_UPDATE */ "update %_term set doclist = ? where rowid = ?", - /* TERM_DELETE */ "delete from %_term where rowid = ?", -}; - -/* -** A connection to a fulltext index is an instance of the following -** structure. The xCreate and xConnect methods create an instance -** of this structure and xDestroy and xDisconnect free that instance. -** All other methods receive a pointer to the structure as one of their -** arguments. -*/ -struct fulltext_vtab { - sqlite3_vtab base; /* Base class used by SQLite core */ - sqlite3 *db; /* The database connection */ - const char *zDb; /* logical database name */ - const char *zName; /* virtual table name */ - int nColumn; /* number of columns in virtual table */ - char **azColumn; /* column names. malloced */ - char **azContentColumn; /* column names in content table; malloced */ - sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ - - /* Precompiled statements which we keep as long as the table is - ** open. - */ - sqlite3_stmt *pFulltextStatements[MAX_STMT]; -}; - -/* -** When the core wants to do a query, it create a cursor using a -** call to xOpen. This structure is an instance of a cursor. It -** is destroyed by xClose. -*/ -typedef struct fulltext_cursor { - sqlite3_vtab_cursor base; /* Base class used by SQLite core */ - QueryType iCursorType; /* Copy of sqlite3_index_info.idxNum */ - sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */ - int eof; /* True if at End Of Results */ - Query q; /* Parsed query string */ - Snippet snippet; /* Cached snippet for the current row */ - int iColumn; /* Column being searched */ - DocListReader result; /* used when iCursorType == QUERY_FULLTEXT */ -} fulltext_cursor; - -static struct fulltext_vtab *cursor_vtab(fulltext_cursor *c){ - return (fulltext_vtab *) c->base.pVtab; -} - -static const sqlite3_module fulltextModule; /* forward declaration */ - -/* Append a list of strings separated by commas to a StringBuffer. */ -static void appendList(StringBuffer *sb, int nString, char **azString){ - int i; - for(i=0; i0 ) append(sb, ", "); - append(sb, azString[i]); - } -} - -/* Return a dynamically generated statement of the form - * insert into %_content (rowid, ...) values (?, ...) - */ -static const char *contentInsertStatement(fulltext_vtab *v){ - StringBuffer sb; - int i; - - initStringBuffer(&sb); - append(&sb, "insert into %_content (rowid, "); - appendList(&sb, v->nColumn, v->azContentColumn); - append(&sb, ") values (?"); - for(i=0; inColumn; ++i) - append(&sb, ", ?"); - append(&sb, ")"); - return sb.s; -} - -/* Return a dynamically generated statement of the form - * update %_content set [col_0] = ?, [col_1] = ?, ... - * where rowid = ? - */ -static const char *contentUpdateStatement(fulltext_vtab *v){ - StringBuffer sb; - int i; - - initStringBuffer(&sb); - append(&sb, "update %_content set "); - for(i=0; inColumn; ++i) { - if( i>0 ){ - append(&sb, ", "); - } - append(&sb, v->azContentColumn[i]); - append(&sb, " = ?"); - } - append(&sb, " where rowid = ?"); - return sb.s; -} - -/* Puts a freshly-prepared statement determined by iStmt in *ppStmt. -** If the indicated statement has never been prepared, it is prepared -** and cached, otherwise the cached version is reset. -*/ -static int sql_get_statement(fulltext_vtab *v, fulltext_statement iStmt, - sqlite3_stmt **ppStmt){ - assert( iStmtpFulltextStatements[iStmt]==NULL ){ - const char *zStmt; - int rc; - switch( iStmt ){ - case CONTENT_INSERT_STMT: - zStmt = contentInsertStatement(v); break; - case CONTENT_UPDATE_STMT: - zStmt = contentUpdateStatement(v); break; - default: - zStmt = fulltext_zStatement[iStmt]; - } - rc = sql_prepare(v->db, v->zDb, v->zName, &v->pFulltextStatements[iStmt], - zStmt); - if( zStmt != fulltext_zStatement[iStmt]) free((void *) zStmt); - if( rc!=SQLITE_OK ) return rc; - } else { - int rc = sqlite3_reset(v->pFulltextStatements[iStmt]); - if( rc!=SQLITE_OK ) return rc; - } - - *ppStmt = v->pFulltextStatements[iStmt]; - return SQLITE_OK; -} - -/* Step the indicated statement, handling errors SQLITE_BUSY (by -** retrying) and SQLITE_SCHEMA (by re-preparing and transferring -** bindings to the new statement). -** TODO(adam): We should extend this function so that it can work with -** statements declared locally, not only globally cached statements. -*/ -static int sql_step_statement(fulltext_vtab *v, fulltext_statement iStmt, - sqlite3_stmt **ppStmt){ - int rc; - sqlite3_stmt *s = *ppStmt; - assert( iStmtpFulltextStatements[iStmt] ); - - while( (rc=sqlite3_step(s))!=SQLITE_DONE && rc!=SQLITE_ROW ){ - if( rc==SQLITE_BUSY ) continue; - if( rc!=SQLITE_ERROR ) return rc; - - /* If an SQLITE_SCHEMA error has occurred, then finalizing this - * statement is going to delete the fulltext_vtab structure. If - * the statement just executed is in the pFulltextStatements[] - * array, it will be finalized twice. So remove it before - * calling sqlite3_finalize(). - */ - v->pFulltextStatements[iStmt] = NULL; - rc = sqlite3_finalize(s); - break; - } - return rc; - - err: - sqlite3_finalize(s); - return rc; -} - -/* Like sql_step_statement(), but convert SQLITE_DONE to SQLITE_OK. -** Useful for statements like UPDATE, where we expect no results. -*/ -static int sql_single_step_statement(fulltext_vtab *v, - fulltext_statement iStmt, - sqlite3_stmt **ppStmt){ - int rc = sql_step_statement(v, iStmt, ppStmt); - return (rc==SQLITE_DONE) ? SQLITE_OK : rc; -} - -/* insert into %_content (rowid, ...) values ([rowid], [pValues]) */ -static int content_insert(fulltext_vtab *v, sqlite3_value *rowid, - sqlite3_value **pValues){ - sqlite3_stmt *s; - int i; - int rc = sql_get_statement(v, CONTENT_INSERT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_value(s, 1, rowid); - if( rc!=SQLITE_OK ) return rc; - - for(i=0; inColumn; ++i){ - rc = sqlite3_bind_value(s, 2+i, pValues[i]); - if( rc!=SQLITE_OK ) return rc; - } - - return sql_single_step_statement(v, CONTENT_INSERT_STMT, &s); -} - -/* update %_content set col0 = pValues[0], col1 = pValues[1], ... - * where rowid = [iRowid] */ -static int content_update(fulltext_vtab *v, sqlite3_value **pValues, - sqlite_int64 iRowid){ - sqlite3_stmt *s; - int i; - int rc = sql_get_statement(v, CONTENT_UPDATE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - for(i=0; inColumn; ++i){ - rc = sqlite3_bind_value(s, 1+i, pValues[i]); - if( rc!=SQLITE_OK ) return rc; - } - - rc = sqlite3_bind_int64(s, 1+v->nColumn, iRowid); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, CONTENT_UPDATE_STMT, &s); -} - -static void freeStringArray(int nString, const char **pString){ - int i; - - for (i=0 ; i < nString ; ++i) { - if( pString[i]!=NULL ) free((void *) pString[i]); - } - free((void *) pString); -} - -/* select * from %_content where rowid = [iRow] - * The caller must delete the returned array and all strings in it. - * null fields will be NULL in the returned array. - * - * TODO: Perhaps we should return pointer/length strings here for consistency - * with other code which uses pointer/length. */ -static int content_select(fulltext_vtab *v, sqlite_int64 iRow, - const char ***pValues){ - sqlite3_stmt *s; - const char **values; - int i; - int rc; - - *pValues = NULL; - - rc = sql_get_statement(v, CONTENT_SELECT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iRow); - if( rc!=SQLITE_OK ) return rc; - - rc = sql_step_statement(v, CONTENT_SELECT_STMT, &s); - if( rc!=SQLITE_ROW ) return rc; - - values = (const char **) malloc(v->nColumn * sizeof(const char *)); - for(i=0; inColumn; ++i){ - if( sqlite3_column_type(s, i)==SQLITE_NULL ){ - values[i] = NULL; - }else{ - values[i] = string_dup((char*)sqlite3_column_text(s, i)); - } - } - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ){ - *pValues = values; - return SQLITE_OK; - } - - freeStringArray(v->nColumn, values); - return rc; -} - -/* delete from %_content where rowid = [iRow ] */ -static int content_delete(fulltext_vtab *v, sqlite_int64 iRow){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, CONTENT_DELETE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iRow); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, CONTENT_DELETE_STMT, &s); -} - -/* select rowid, doclist from %_term - * where term = [pTerm] and segment = [iSegment] - * If found, returns SQLITE_ROW; the caller must free the - * returned doclist. If no rows found, returns SQLITE_DONE. */ -static int term_select(fulltext_vtab *v, const char *pTerm, int nTerm, - int iSegment, - sqlite_int64 *rowid, DocList *out){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_SELECT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_text(s, 1, pTerm, nTerm, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int(s, 2, iSegment); - if( rc!=SQLITE_OK ) return rc; - - rc = sql_step_statement(v, TERM_SELECT_STMT, &s); - if( rc!=SQLITE_ROW ) return rc; - - *rowid = sqlite3_column_int64(s, 0); - docListInit(out, DL_DEFAULT, - sqlite3_column_blob(s, 1), sqlite3_column_bytes(s, 1)); - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - return rc==SQLITE_DONE ? SQLITE_ROW : rc; -} - -/* Load the segment doclists for term pTerm and merge them in -** appropriate order into out. Returns SQLITE_OK if successful. If -** there are no segments for pTerm, successfully returns an empty -** doclist in out. -** -** Each document consists of 1 or more "columns". The number of -** columns is v->nColumn. If iColumn==v->nColumn, then return -** position information about all columns. If iColumnnColumn, -** then only return position information about the iColumn-th column -** (where the first column is 0). -*/ -static int term_select_all( - fulltext_vtab *v, /* The fulltext index we are querying against */ - int iColumn, /* If nColumn ){ /* querying a single column */ - docListRestrictColumn(&old, iColumn); - } - - /* doclist contains the newer data, so write it over old. Then - ** steal accumulated result for doclist. - */ - docListAccumulate(&old, &doclist); - docListDestroy(&doclist); - doclist = old; - } - if( rc!=SQLITE_DONE ){ - docListDestroy(&doclist); - return rc; - } - - docListDiscardEmpty(&doclist); - *out = doclist; - return SQLITE_OK; -} - -/* insert into %_term (rowid, term, segment, doclist) - values ([piRowid], [pTerm], [iSegment], [doclist]) -** Lets sqlite select rowid if piRowid is NULL, else uses *piRowid. -** -** NOTE(shess) piRowid is IN, with values of "space of int64" plus -** null, it is not used to pass data back to the caller. -*/ -static int term_insert(fulltext_vtab *v, sqlite_int64 *piRowid, - const char *pTerm, int nTerm, - int iSegment, DocList *doclist){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_INSERT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - if( piRowid==NULL ){ - rc = sqlite3_bind_null(s, 1); - }else{ - rc = sqlite3_bind_int64(s, 1, *piRowid); - } - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_text(s, 2, pTerm, nTerm, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int(s, 3, iSegment); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_blob(s, 4, doclist->pData, doclist->nData, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, TERM_INSERT_STMT, &s); -} - -/* update %_term set doclist = [doclist] where rowid = [rowid] */ -static int term_update(fulltext_vtab *v, sqlite_int64 rowid, - DocList *doclist){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_UPDATE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_blob(s, 1, doclist->pData, doclist->nData, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 2, rowid); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, TERM_UPDATE_STMT, &s); -} - -static int term_delete(fulltext_vtab *v, sqlite_int64 rowid){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_DELETE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, rowid); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, TERM_DELETE_STMT, &s); -} - -/* -** Free the memory used to contain a fulltext_vtab structure. -*/ -static void fulltext_vtab_destroy(fulltext_vtab *v){ - int iStmt, i; - - TRACE(("FTS1 Destroy %p\n", v)); - for( iStmt=0; iStmtpFulltextStatements[iStmt]!=NULL ){ - sqlite3_finalize(v->pFulltextStatements[iStmt]); - v->pFulltextStatements[iStmt] = NULL; - } - } - - if( v->pTokenizer!=NULL ){ - v->pTokenizer->pModule->xDestroy(v->pTokenizer); - v->pTokenizer = NULL; - } - - free(v->azColumn); - for(i = 0; i < v->nColumn; ++i) { - sqlite3_free(v->azContentColumn[i]); - } - free(v->azContentColumn); - free(v); -} - -/* -** Token types for parsing the arguments to xConnect or xCreate. -*/ -#define TOKEN_EOF 0 /* End of file */ -#define TOKEN_SPACE 1 /* Any kind of whitespace */ -#define TOKEN_ID 2 /* An identifier */ -#define TOKEN_STRING 3 /* A string literal */ -#define TOKEN_PUNCT 4 /* A single punctuation character */ - -/* -** If X is a character that can be used in an identifier then -** IdChar(X) will be true. Otherwise it is false. -** -** For ASCII, any character with the high-order bit set is -** allowed in an identifier. For 7-bit characters, -** sqlite3IsIdChar[X] must be 1. -** -** Ticket #1066. the SQL standard does not allow '$' in the -** middle of identfiers. But many SQL implementations do. -** SQLite will allow '$' in identifiers for compatibility. -** But the feature is undocumented. -*/ -static const char isIdChar[] = { -/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ - 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ -}; -#define IdChar(C) (((c=C)&0x80)!=0 || (c>0x1f && isIdChar[c-0x20])) - - -/* -** Return the length of the token that begins at z[0]. -** Store the token type in *tokenType before returning. -*/ -static int getToken(const char *z, int *tokenType){ - int i, c; - switch( *z ){ - case 0: { - *tokenType = TOKEN_EOF; - return 0; - } - case ' ': case '\t': case '\n': case '\f': case '\r': { - for(i=1; safe_isspace(z[i]); i++){} - *tokenType = TOKEN_SPACE; - return i; - } - case '`': - case '\'': - case '"': { - int delim = z[0]; - for(i=1; (c=z[i])!=0; i++){ - if( c==delim ){ - if( z[i+1]==delim ){ - i++; - }else{ - break; - } - } - } - *tokenType = TOKEN_STRING; - return i + (c!=0); - } - case '[': { - for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){} - *tokenType = TOKEN_ID; - return i; - } - default: { - if( !IdChar(*z) ){ - break; - } - for(i=1; IdChar(z[i]); i++){} - *tokenType = TOKEN_ID; - return i; - } - } - *tokenType = TOKEN_PUNCT; - return 1; -} - -/* -** A token extracted from a string is an instance of the following -** structure. -*/ -typedef struct Token { - const char *z; /* Pointer to token text. Not '\000' terminated */ - short int n; /* Length of the token text in bytes. */ -} Token; - -/* -** Given a input string (which is really one of the argv[] parameters -** passed into xConnect or xCreate) split the string up into tokens. -** Return an array of pointers to '\000' terminated strings, one string -** for each non-whitespace token. -** -** The returned array is terminated by a single NULL pointer. -** -** Space to hold the returned array is obtained from a single -** malloc and should be freed by passing the return value to free(). -** The individual strings within the token list are all a part of -** the single memory allocation and will all be freed at once. -*/ -static char **tokenizeString(const char *z, int *pnToken){ - int nToken = 0; - Token *aToken = malloc( strlen(z) * sizeof(aToken[0]) ); - int n = 1; - int e, i; - int totalSize = 0; - char **azToken; - char *zCopy; - while( n>0 ){ - n = getToken(z, &e); - if( e!=TOKEN_SPACE ){ - aToken[nToken].z = z; - aToken[nToken].n = n; - nToken++; - totalSize += n+1; - } - z += n; - } - azToken = (char**)malloc( nToken*sizeof(char*) + totalSize ); - zCopy = (char*)&azToken[nToken]; - nToken--; - for(i=0; i=0 ){ - azIn[j] = azIn[i]; - } - j++; - } - } - azIn[j] = 0; - } -} - - -/* -** Find the first alphanumeric token in the string zIn. Null-terminate -** this token. Remove any quotation marks. And return a pointer to -** the result. -*/ -static char *firstToken(char *zIn, char **pzTail){ - int n, ttype; - while(1){ - n = getToken(zIn, &ttype); - if( ttype==TOKEN_SPACE ){ - zIn += n; - }else if( ttype==TOKEN_EOF ){ - *pzTail = zIn; - return 0; - }else{ - zIn[n] = 0; - *pzTail = &zIn[1]; - dequoteString(zIn); - return zIn; - } - } - /*NOTREACHED*/ -} - -/* Return true if... -** -** * s begins with the string t, ignoring case -** * s is longer than t -** * The first character of s beyond t is not a alphanumeric -** -** Ignore leading space in *s. -** -** To put it another way, return true if the first token of -** s[] is t[]. -*/ -static int startsWith(const char *s, const char *t){ - while( safe_isspace(*s) ){ s++; } - while( *t ){ - if( safe_tolower(*s++)!=safe_tolower(*t++) ) return 0; - } - return *s!='_' && !safe_isalnum(*s); -} - -/* -** An instance of this structure defines the "spec" of a -** full text index. This structure is populated by parseSpec -** and use by fulltextConnect and fulltextCreate. -*/ -typedef struct TableSpec { - const char *zDb; /* Logical database name */ - const char *zName; /* Name of the full-text index */ - int nColumn; /* Number of columns to be indexed */ - char **azColumn; /* Original names of columns to be indexed */ - char **azContentColumn; /* Column names for %_content */ - char **azTokenizer; /* Name of tokenizer and its arguments */ -} TableSpec; - -/* -** Reclaim all of the memory used by a TableSpec -*/ -static void clearTableSpec(TableSpec *p) { - free(p->azColumn); - free(p->azContentColumn); - free(p->azTokenizer); -} - -/* Parse a CREATE VIRTUAL TABLE statement, which looks like this: - * - * CREATE VIRTUAL TABLE email - * USING fts1(subject, body, tokenize mytokenizer(myarg)) - * - * We return parsed information in a TableSpec structure. - * - */ -static int parseSpec(TableSpec *pSpec, int argc, const char *const*argv, - char**pzErr){ - int i, n; - char *z, *zDummy; - char **azArg; - const char *zTokenizer = 0; /* argv[] entry describing the tokenizer */ - - assert( argc>=3 ); - /* Current interface: - ** argv[0] - module name - ** argv[1] - database name - ** argv[2] - table name - ** argv[3..] - columns, optionally followed by tokenizer specification - ** and snippet delimiters specification. - */ - - /* Make a copy of the complete argv[][] array in a single allocation. - ** The argv[][] array is read-only and transient. We can write to the - ** copy in order to modify things and the copy is persistent. - */ - memset(pSpec, 0, sizeof(*pSpec)); - for(i=n=0; izDb = azArg[1]; - pSpec->zName = azArg[2]; - pSpec->nColumn = 0; - pSpec->azColumn = azArg; - zTokenizer = "tokenize simple"; - for(i=3; inColumn] = firstToken(azArg[i], &zDummy); - pSpec->nColumn++; - } - } - if( pSpec->nColumn==0 ){ - azArg[0] = "content"; - pSpec->nColumn = 1; - } - - /* - ** Construct the list of content column names. - ** - ** Each content column name will be of the form cNNAAAA - ** where NN is the column number and AAAA is the sanitized - ** column name. "sanitized" means that special characters are - ** converted to "_". The cNN prefix guarantees that all column - ** names are unique. - ** - ** The AAAA suffix is not strictly necessary. It is included - ** for the convenience of people who might examine the generated - ** %_content table and wonder what the columns are used for. - */ - pSpec->azContentColumn = malloc( pSpec->nColumn * sizeof(char *) ); - if( pSpec->azContentColumn==0 ){ - clearTableSpec(pSpec); - return SQLITE_NOMEM; - } - for(i=0; inColumn; i++){ - char *p; - pSpec->azContentColumn[i] = sqlite3_mprintf("c%d%s", i, azArg[i]); - for (p = pSpec->azContentColumn[i]; *p ; ++p) { - if( !safe_isalnum(*p) ) *p = '_'; - } - } - - /* - ** Parse the tokenizer specification string. - */ - pSpec->azTokenizer = tokenizeString(zTokenizer, &n); - tokenListToIdList(pSpec->azTokenizer); - - return SQLITE_OK; -} - -/* -** Generate a CREATE TABLE statement that describes the schema of -** the virtual table. Return a pointer to this schema string. -** -** Space is obtained from sqlite3_mprintf() and should be freed -** using sqlite3_free(). -*/ -static char *fulltextSchema( - int nColumn, /* Number of columns */ - const char *const* azColumn, /* List of columns */ - const char *zTableName /* Name of the table */ -){ - int i; - char *zSchema, *zNext; - const char *zSep = "("; - zSchema = sqlite3_mprintf("CREATE TABLE x"); - for(i=0; ibase */ - v->db = db; - v->zDb = spec->zDb; /* Freed when azColumn is freed */ - v->zName = spec->zName; /* Freed when azColumn is freed */ - v->nColumn = spec->nColumn; - v->azContentColumn = spec->azContentColumn; - spec->azContentColumn = 0; - v->azColumn = spec->azColumn; - spec->azColumn = 0; - - if( spec->azTokenizer==0 ){ - return SQLITE_NOMEM; - } - /* TODO(shess) For now, add new tokenizers as else if clauses. */ - if( spec->azTokenizer[0]==0 || startsWith(spec->azTokenizer[0], "simple") ){ - sqlite3Fts1SimpleTokenizerModule(&m); - }else if( startsWith(spec->azTokenizer[0], "porter") ){ - sqlite3Fts1PorterTokenizerModule(&m); - }else{ - *pzErr = sqlite3_mprintf("unknown tokenizer: %s", spec->azTokenizer[0]); - rc = SQLITE_ERROR; - goto err; - } - for(n=0; spec->azTokenizer[n]; n++){} - if( n ){ - rc = m->xCreate(n-1, (const char*const*)&spec->azTokenizer[1], - &v->pTokenizer); - }else{ - rc = m->xCreate(0, 0, &v->pTokenizer); - } - if( rc!=SQLITE_OK ) goto err; - v->pTokenizer->pModule = m; - - /* TODO: verify the existence of backing tables foo_content, foo_term */ - - schema = fulltextSchema(v->nColumn, (const char*const*)v->azColumn, - spec->zName); - rc = sqlite3_declare_vtab(db, schema); - sqlite3_free(schema); - if( rc!=SQLITE_OK ) goto err; - - memset(v->pFulltextStatements, 0, sizeof(v->pFulltextStatements)); - - *ppVTab = &v->base; - TRACE(("FTS1 Connect %p\n", v)); - - return rc; - -err: - fulltext_vtab_destroy(v); - return rc; -} - -static int fulltextConnect( - sqlite3 *db, - void *pAux, - int argc, const char *const*argv, - sqlite3_vtab **ppVTab, - char **pzErr -){ - TableSpec spec; - int rc = parseSpec(&spec, argc, argv, pzErr); - if( rc!=SQLITE_OK ) return rc; - - rc = constructVtab(db, &spec, ppVTab, pzErr); - clearTableSpec(&spec); - return rc; -} - - /* The %_content table holds the text of each document, with - ** the rowid used as the docid. - ** - ** The %_term table maps each term to a document list blob - ** containing elements sorted by ascending docid, each element - ** encoded as: - ** - ** docid varint-encoded - ** token elements: - ** position+1 varint-encoded as delta from previous position - ** start offset varint-encoded as delta from previous start offset - ** end offset varint-encoded as delta from start offset - ** - ** The sentinel position of 0 indicates the end of the token list. - ** - ** Additionally, doclist blobs are chunked into multiple segments, - ** using segment to order the segments. New elements are added to - ** the segment at segment 0, until it exceeds CHUNK_MAX. Then - ** segment 0 is deleted, and the doclist is inserted at segment 1. - ** If there is already a doclist at segment 1, the segment 0 doclist - ** is merged with it, the segment 1 doclist is deleted, and the - ** merged doclist is inserted at segment 2, repeating those - ** operations until an insert succeeds. - ** - ** Since this structure doesn't allow us to update elements in place - ** in case of deletion or update, these are simply written to - ** segment 0 (with an empty token list in case of deletion), with - ** docListAccumulate() taking care to retain lower-segment - ** information in preference to higher-segment information. - */ - /* TODO(shess) Provide a VACUUM type operation which both removes - ** deleted elements which are no longer necessary, and duplicated - ** elements. I suspect this will probably not be necessary in - ** practice, though. - */ -static int fulltextCreate(sqlite3 *db, void *pAux, - int argc, const char * const *argv, - sqlite3_vtab **ppVTab, char **pzErr){ - int rc; - TableSpec spec; - StringBuffer schema; - TRACE(("FTS1 Create\n")); - - rc = parseSpec(&spec, argc, argv, pzErr); - if( rc!=SQLITE_OK ) return rc; - - initStringBuffer(&schema); - append(&schema, "CREATE TABLE %_content("); - appendList(&schema, spec.nColumn, spec.azContentColumn); - append(&schema, ")"); - rc = sql_exec(db, spec.zDb, spec.zName, schema.s); - free(schema.s); - if( rc!=SQLITE_OK ) goto out; - - rc = sql_exec(db, spec.zDb, spec.zName, - "create table %_term(term text, segment integer, doclist blob, " - "primary key(term, segment));"); - if( rc!=SQLITE_OK ) goto out; - - rc = constructVtab(db, &spec, ppVTab, pzErr); - -out: - clearTableSpec(&spec); - return rc; -} - -/* Decide how to handle an SQL query. */ -static int fulltextBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ - int i; - TRACE(("FTS1 BestIndex\n")); - - for(i=0; inConstraint; ++i){ - const struct sqlite3_index_constraint *pConstraint; - pConstraint = &pInfo->aConstraint[i]; - if( pConstraint->usable ) { - if( pConstraint->iColumn==-1 && - pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ - pInfo->idxNum = QUERY_ROWID; /* lookup by rowid */ - TRACE(("FTS1 QUERY_ROWID\n")); - } else if( pConstraint->iColumn>=0 && - pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH ){ - /* full-text search */ - pInfo->idxNum = QUERY_FULLTEXT + pConstraint->iColumn; - TRACE(("FTS1 QUERY_FULLTEXT %d\n", pConstraint->iColumn)); - } else continue; - - pInfo->aConstraintUsage[i].argvIndex = 1; - pInfo->aConstraintUsage[i].omit = 1; - - /* An arbitrary value for now. - * TODO: Perhaps rowid matches should be considered cheaper than - * full-text searches. */ - pInfo->estimatedCost = 1.0; - - return SQLITE_OK; - } - } - pInfo->idxNum = QUERY_GENERIC; - return SQLITE_OK; -} - -static int fulltextDisconnect(sqlite3_vtab *pVTab){ - TRACE(("FTS1 Disconnect %p\n", pVTab)); - fulltext_vtab_destroy((fulltext_vtab *)pVTab); - return SQLITE_OK; -} - -static int fulltextDestroy(sqlite3_vtab *pVTab){ - fulltext_vtab *v = (fulltext_vtab *)pVTab; - int rc; - - TRACE(("FTS1 Destroy %p\n", pVTab)); - rc = sql_exec(v->db, v->zDb, v->zName, - "drop table if exists %_content;" - "drop table if exists %_term;" - ); - if( rc!=SQLITE_OK ) return rc; - - fulltext_vtab_destroy((fulltext_vtab *)pVTab); - return SQLITE_OK; -} - -static int fulltextOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ - fulltext_cursor *c; - - c = (fulltext_cursor *) calloc(sizeof(fulltext_cursor), 1); - /* sqlite will initialize c->base */ - *ppCursor = &c->base; - TRACE(("FTS1 Open %p: %p\n", pVTab, c)); - - return SQLITE_OK; -} - - -/* Free all of the dynamically allocated memory held by *q -*/ -static void queryClear(Query *q){ - int i; - for(i = 0; i < q->nTerms; ++i){ - free(q->pTerms[i].pTerm); - } - free(q->pTerms); - memset(q, 0, sizeof(*q)); -} - -/* Free all of the dynamically allocated memory held by the -** Snippet -*/ -static void snippetClear(Snippet *p){ - free(p->aMatch); - free(p->zOffset); - free(p->zSnippet); - memset(p, 0, sizeof(*p)); -} -/* -** Append a single entry to the p->aMatch[] log. -*/ -static void snippetAppendMatch( - Snippet *p, /* Append the entry to this snippet */ - int iCol, int iTerm, /* The column and query term */ - int iStart, int nByte /* Offset and size of the match */ -){ - int i; - struct snippetMatch *pMatch; - if( p->nMatch+1>=p->nAlloc ){ - p->nAlloc = p->nAlloc*2 + 10; - p->aMatch = realloc(p->aMatch, p->nAlloc*sizeof(p->aMatch[0]) ); - if( p->aMatch==0 ){ - p->nMatch = 0; - p->nAlloc = 0; - return; - } - } - i = p->nMatch++; - pMatch = &p->aMatch[i]; - pMatch->iCol = iCol; - pMatch->iTerm = iTerm; - pMatch->iStart = iStart; - pMatch->nByte = nByte; -} - -/* -** Sizing information for the circular buffer used in snippetOffsetsOfColumn() -*/ -#define FTS1_ROTOR_SZ (32) -#define FTS1_ROTOR_MASK (FTS1_ROTOR_SZ-1) - -/* -** Add entries to pSnippet->aMatch[] for every match that occurs against -** document zDoc[0..nDoc-1] which is stored in column iColumn. -*/ -static void snippetOffsetsOfColumn( - Query *pQuery, - Snippet *pSnippet, - int iColumn, - const char *zDoc, - int nDoc -){ - const sqlite3_tokenizer_module *pTModule; /* The tokenizer module */ - sqlite3_tokenizer *pTokenizer; /* The specific tokenizer */ - sqlite3_tokenizer_cursor *pTCursor; /* Tokenizer cursor */ - fulltext_vtab *pVtab; /* The full text index */ - int nColumn; /* Number of columns in the index */ - const QueryTerm *aTerm; /* Query string terms */ - int nTerm; /* Number of query string terms */ - int i, j; /* Loop counters */ - int rc; /* Return code */ - unsigned int match, prevMatch; /* Phrase search bitmasks */ - const char *zToken; /* Next token from the tokenizer */ - int nToken; /* Size of zToken */ - int iBegin, iEnd, iPos; /* Offsets of beginning and end */ - - /* The following variables keep a circular buffer of the last - ** few tokens */ - unsigned int iRotor = 0; /* Index of current token */ - int iRotorBegin[FTS1_ROTOR_SZ]; /* Beginning offset of token */ - int iRotorLen[FTS1_ROTOR_SZ]; /* Length of token */ - - pVtab = pQuery->pFts; - nColumn = pVtab->nColumn; - pTokenizer = pVtab->pTokenizer; - pTModule = pTokenizer->pModule; - rc = pTModule->xOpen(pTokenizer, zDoc, nDoc, &pTCursor); - if( rc ) return; - pTCursor->pTokenizer = pTokenizer; - aTerm = pQuery->pTerms; - nTerm = pQuery->nTerms; - if( nTerm>=FTS1_ROTOR_SZ ){ - nTerm = FTS1_ROTOR_SZ - 1; - } - prevMatch = 0; - while(1){ - rc = pTModule->xNext(pTCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos); - if( rc ) break; - iRotorBegin[iRotor&FTS1_ROTOR_MASK] = iBegin; - iRotorLen[iRotor&FTS1_ROTOR_MASK] = iEnd-iBegin; - match = 0; - for(i=0; i=0 && iCol1 && (prevMatch & (1<=0; j--){ - int k = (iRotor-j) & FTS1_ROTOR_MASK; - snippetAppendMatch(pSnippet, iColumn, i-j, - iRotorBegin[k], iRotorLen[k]); - } - } - } - prevMatch = match<<1; - iRotor++; - } - pTModule->xClose(pTCursor); -} - - -/* -** Compute all offsets for the current row of the query. -** If the offsets have already been computed, this routine is a no-op. -*/ -static void snippetAllOffsets(fulltext_cursor *p){ - int nColumn; - int iColumn, i; - int iFirst, iLast; - fulltext_vtab *pFts; - - if( p->snippet.nMatch ) return; - if( p->q.nTerms==0 ) return; - pFts = p->q.pFts; - nColumn = pFts->nColumn; - iColumn = p->iCursorType - QUERY_FULLTEXT; - if( iColumn<0 || iColumn>=nColumn ){ - iFirst = 0; - iLast = nColumn-1; - }else{ - iFirst = iColumn; - iLast = iColumn; - } - for(i=iFirst; i<=iLast; i++){ - const char *zDoc; - int nDoc; - zDoc = (const char*)sqlite3_column_text(p->pStmt, i+1); - nDoc = sqlite3_column_bytes(p->pStmt, i+1); - snippetOffsetsOfColumn(&p->q, &p->snippet, i, zDoc, nDoc); - } -} - -/* -** Convert the information in the aMatch[] array of the snippet -** into the string zOffset[0..nOffset-1]. -*/ -static void snippetOffsetText(Snippet *p){ - int i; - int cnt = 0; - StringBuffer sb; - char zBuf[200]; - if( p->zOffset ) return; - initStringBuffer(&sb); - for(i=0; inMatch; i++){ - struct snippetMatch *pMatch = &p->aMatch[i]; - zBuf[0] = ' '; - sqlite3_snprintf(sizeof(zBuf)-1, &zBuf[cnt>0], "%d %d %d %d", - pMatch->iCol, pMatch->iTerm, pMatch->iStart, pMatch->nByte); - append(&sb, zBuf); - cnt++; - } - p->zOffset = sb.s; - p->nOffset = sb.len; -} - -/* -** zDoc[0..nDoc-1] is phrase of text. aMatch[0..nMatch-1] are a set -** of matching words some of which might be in zDoc. zDoc is column -** number iCol. -** -** iBreak is suggested spot in zDoc where we could begin or end an -** excerpt. Return a value similar to iBreak but possibly adjusted -** to be a little left or right so that the break point is better. -*/ -static int wordBoundary( - int iBreak, /* The suggested break point */ - const char *zDoc, /* Document text */ - int nDoc, /* Number of bytes in zDoc[] */ - struct snippetMatch *aMatch, /* Matching words */ - int nMatch, /* Number of entries in aMatch[] */ - int iCol /* The column number for zDoc[] */ -){ - int i; - if( iBreak<=10 ){ - return 0; - } - if( iBreak>=nDoc-10 ){ - return nDoc; - } - for(i=0; i0 && aMatch[i-1].iStart+aMatch[i-1].nByte>=iBreak ){ - return aMatch[i-1].iStart; - } - } - for(i=1; i<=10; i++){ - if( safe_isspace(zDoc[iBreak-i]) ){ - return iBreak - i + 1; - } - if( safe_isspace(zDoc[iBreak+i]) ){ - return iBreak + i + 1; - } - } - return iBreak; -} - -/* -** If the StringBuffer does not end in white space, add a single -** space character to the end. -*/ -static void appendWhiteSpace(StringBuffer *p){ - if( p->len==0 ) return; - if( safe_isspace(p->s[p->len-1]) ) return; - append(p, " "); -} - -/* -** Remove white space from teh end of the StringBuffer -*/ -static void trimWhiteSpace(StringBuffer *p){ - while( p->len>0 && safe_isspace(p->s[p->len-1]) ){ - p->len--; - } -} - - - -/* -** Allowed values for Snippet.aMatch[].snStatus -*/ -#define SNIPPET_IGNORE 0 /* It is ok to omit this match from the snippet */ -#define SNIPPET_DESIRED 1 /* We want to include this match in the snippet */ - -/* -** Generate the text of a snippet. -*/ -static void snippetText( - fulltext_cursor *pCursor, /* The cursor we need the snippet for */ - const char *zStartMark, /* Markup to appear before each match */ - const char *zEndMark, /* Markup to appear after each match */ - const char *zEllipsis /* Ellipsis mark */ -){ - int i, j; - struct snippetMatch *aMatch; - int nMatch; - int nDesired; - StringBuffer sb; - int tailCol; - int tailOffset; - int iCol; - int nDoc; - const char *zDoc; - int iStart, iEnd; - int tailEllipsis = 0; - int iMatch; - - - free(pCursor->snippet.zSnippet); - pCursor->snippet.zSnippet = 0; - aMatch = pCursor->snippet.aMatch; - nMatch = pCursor->snippet.nMatch; - initStringBuffer(&sb); - - for(i=0; iq.nTerms; i++){ - for(j=0; j0; i++){ - if( aMatch[i].snStatus!=SNIPPET_DESIRED ) continue; - nDesired--; - iCol = aMatch[i].iCol; - zDoc = (const char*)sqlite3_column_text(pCursor->pStmt, iCol+1); - nDoc = sqlite3_column_bytes(pCursor->pStmt, iCol+1); - iStart = aMatch[i].iStart - 40; - iStart = wordBoundary(iStart, zDoc, nDoc, aMatch, nMatch, iCol); - if( iStart<=10 ){ - iStart = 0; - } - if( iCol==tailCol && iStart<=tailOffset+20 ){ - iStart = tailOffset; - } - if( (iCol!=tailCol && tailCol>=0) || iStart!=tailOffset ){ - trimWhiteSpace(&sb); - appendWhiteSpace(&sb); - append(&sb, zEllipsis); - appendWhiteSpace(&sb); - } - iEnd = aMatch[i].iStart + aMatch[i].nByte + 40; - iEnd = wordBoundary(iEnd, zDoc, nDoc, aMatch, nMatch, iCol); - if( iEnd>=nDoc-10 ){ - iEnd = nDoc; - tailEllipsis = 0; - }else{ - tailEllipsis = 1; - } - while( iMatchsnippet.zSnippet = sb.s; - pCursor->snippet.nSnippet = sb.len; -} - - -/* -** 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){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - TRACE(("FTS1 Close %p\n", c)); - sqlite3_finalize(c->pStmt); - queryClear(&c->q); - snippetClear(&c->snippet); - if( c->result.pDoclist!=NULL ){ - docListDelete(c->result.pDoclist); - } - free(c); - return SQLITE_OK; -} - -static int fulltextNext(sqlite3_vtab_cursor *pCursor){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - sqlite_int64 iDocid; - int rc; - - TRACE(("FTS1 Next %p\n", pCursor)); - snippetClear(&c->snippet); - if( c->iCursorType < QUERY_FULLTEXT ){ - /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */ - rc = sqlite3_step(c->pStmt); - switch( rc ){ - case SQLITE_ROW: - c->eof = 0; - return SQLITE_OK; - case SQLITE_DONE: - c->eof = 1; - return SQLITE_OK; - default: - c->eof = 1; - return rc; - } - } else { /* full-text query */ - rc = sqlite3_reset(c->pStmt); - if( rc!=SQLITE_OK ) return rc; - - iDocid = nextDocid(&c->result); - if( iDocid==0 ){ - c->eof = 1; - return SQLITE_OK; - } - rc = sqlite3_bind_int64(c->pStmt, 1, iDocid); - if( rc!=SQLITE_OK ) return rc; - /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */ - rc = sqlite3_step(c->pStmt); - if( rc==SQLITE_ROW ){ /* the case we expect */ - c->eof = 0; - return SQLITE_OK; - } - /* an error occurred; abort */ - return rc==SQLITE_DONE ? SQLITE_ERROR : rc; - } -} - - -/* Return a DocList corresponding to the query term *pTerm. If *pTerm -** is the first term of a phrase query, go ahead and evaluate the phrase -** query and return the doclist for the entire phrase query. -** -** The result is stored in pTerm->doclist. -*/ -static int docListOfTerm( - fulltext_vtab *v, /* The full text index */ - int iColumn, /* column to restrict to. No restrition if >=nColumn */ - QueryTerm *pQTerm, /* Term we are looking for, or 1st term of a phrase */ - DocList **ppResult /* Write the result here */ -){ - DocList *pLeft, *pRight, *pNew; - int i, rc; - - pLeft = docListNew(DL_POSITIONS); - rc = term_select_all(v, iColumn, pQTerm->pTerm, pQTerm->nTerm, pLeft); - if( rc ){ - docListDelete(pLeft); - return rc; - } - for(i=1; i<=pQTerm->nPhrase; i++){ - pRight = docListNew(DL_POSITIONS); - rc = term_select_all(v, iColumn, pQTerm[i].pTerm, pQTerm[i].nTerm, pRight); - if( rc ){ - docListDelete(pLeft); - return rc; - } - pNew = docListNew(inPhrase ? DL_POSITIONS : DL_DOCIDS); - docListPhraseMerge(pLeft, pRight, pNew); - docListDelete(pLeft); - docListDelete(pRight); - pLeft = pNew; - } - *ppResult = pLeft; - return SQLITE_OK; -} - -/* Add a new term pTerm[0..nTerm-1] to the query *q. -*/ -static void queryAdd(Query *q, const char *pTerm, int nTerm){ - QueryTerm *t; - ++q->nTerms; - q->pTerms = realloc(q->pTerms, q->nTerms * sizeof(q->pTerms[0])); - if( q->pTerms==0 ){ - q->nTerms = 0; - return; - } - t = &q->pTerms[q->nTerms - 1]; - memset(t, 0, sizeof(*t)); - t->pTerm = malloc(nTerm+1); - memcpy(t->pTerm, pTerm, nTerm); - t->pTerm[nTerm] = 0; - t->nTerm = nTerm; - t->isOr = q->nextIsOr; - q->nextIsOr = 0; - t->iColumn = q->nextColumn; - q->nextColumn = q->dfltColumn; -} - -/* -** Check to see if the string zToken[0...nToken-1] matches any -** column name in the virtual table. If it does, -** return the zero-indexed column number. If not, return -1. -*/ -static int checkColumnSpecifier( - fulltext_vtab *pVtab, /* The virtual table */ - const char *zToken, /* Text of the token */ - int nToken /* Number of characters in the token */ -){ - int i; - for(i=0; inColumn; i++){ - if( memcmp(pVtab->azColumn[i], zToken, nToken)==0 - && pVtab->azColumn[i][nToken]==0 ){ - return i; - } - } - return -1; -} - -/* -** Parse the text at pSegment[0..nSegment-1]. Add additional terms -** to the query being assemblied in pQuery. -** -** inPhrase is true if pSegment[0..nSegement-1] is contained within -** double-quotes. If inPhrase is true, then the first term -** is marked with the number of terms in the phrase less one and -** OR and "-" syntax is ignored. If inPhrase is false, then every -** term found is marked with nPhrase=0 and OR and "-" syntax is significant. -*/ -static int tokenizeSegment( - sqlite3_tokenizer *pTokenizer, /* The tokenizer to use */ - const char *pSegment, int nSegment, /* Query expression being parsed */ - int inPhrase, /* True if within "..." */ - Query *pQuery /* Append results here */ -){ - const sqlite3_tokenizer_module *pModule = pTokenizer->pModule; - sqlite3_tokenizer_cursor *pCursor; - int firstIndex = pQuery->nTerms; - int iCol; - int nTerm = 1; - - int rc = pModule->xOpen(pTokenizer, pSegment, nSegment, &pCursor); - if( rc!=SQLITE_OK ) return rc; - pCursor->pTokenizer = pTokenizer; - - while( 1 ){ - const char *pToken; - int nToken, iBegin, iEnd, iPos; - - rc = pModule->xNext(pCursor, - &pToken, &nToken, - &iBegin, &iEnd, &iPos); - if( rc!=SQLITE_OK ) break; - if( !inPhrase && - pSegment[iEnd]==':' && - (iCol = checkColumnSpecifier(pQuery->pFts, pToken, nToken))>=0 ){ - pQuery->nextColumn = iCol; - continue; - } - if( !inPhrase && pQuery->nTerms>0 && nToken==2 - && pSegment[iBegin]=='O' && pSegment[iBegin+1]=='R' ){ - pQuery->nextIsOr = 1; - continue; - } - queryAdd(pQuery, pToken, nToken); - if( !inPhrase && iBegin>0 && pSegment[iBegin-1]=='-' ){ - pQuery->pTerms[pQuery->nTerms-1].isNot = 1; - } - pQuery->pTerms[pQuery->nTerms-1].iPhrase = nTerm; - if( inPhrase ){ - nTerm++; - } - } - - if( inPhrase && pQuery->nTerms>firstIndex ){ - pQuery->pTerms[firstIndex].nPhrase = pQuery->nTerms - firstIndex - 1; - } - - return pModule->xClose(pCursor); -} - -/* Parse a query string, yielding a Query object pQuery. -** -** The calling function will need to queryClear() to clean up -** the dynamically allocated memory held by pQuery. -*/ -static int parseQuery( - fulltext_vtab *v, /* The fulltext index */ - const char *zInput, /* Input text of the query string */ - int nInput, /* Size of the input text */ - int dfltColumn, /* Default column of the index to match against */ - Query *pQuery /* Write the parse results here. */ -){ - int iInput, inPhrase = 0; - - if( zInput==0 ) nInput = 0; - if( nInput<0 ) nInput = strlen(zInput); - pQuery->nTerms = 0; - pQuery->pTerms = NULL; - pQuery->nextIsOr = 0; - pQuery->nextColumn = dfltColumn; - pQuery->dfltColumn = dfltColumn; - pQuery->pFts = v; - - for(iInput=0; iInputiInput ){ - tokenizeSegment(v->pTokenizer, zInput+iInput, i-iInput, inPhrase, - pQuery); - } - iInput = i; - if( i=nColumn -** they are allowed to match against any column. -*/ -static int fulltextQuery( - fulltext_vtab *v, /* The full text index */ - int iColumn, /* Match against this column by default */ - const char *zInput, /* The query string */ - int nInput, /* Number of bytes in zInput[] */ - DocList **pResult, /* Write the result doclist here */ - Query *pQuery /* Put parsed query string here */ -){ - int i, iNext, rc; - DocList *pLeft = NULL; - DocList *pRight, *pNew, *pOr; - int nNot = 0; - QueryTerm *aTerm; - - rc = parseQuery(v, zInput, nInput, iColumn, pQuery); - if( rc!=SQLITE_OK ) return rc; - - /* Merge AND terms. */ - aTerm = pQuery->pTerms; - for(i = 0; inTerms; i=iNext){ - if( aTerm[i].isNot ){ - /* Handle all NOT terms in a separate pass */ - nNot++; - iNext = i + aTerm[i].nPhrase+1; - continue; - } - iNext = i + aTerm[i].nPhrase + 1; - rc = docListOfTerm(v, aTerm[i].iColumn, &aTerm[i], &pRight); - if( rc ){ - queryClear(pQuery); - return rc; - } - while( iNextnTerms && aTerm[iNext].isOr ){ - rc = docListOfTerm(v, aTerm[iNext].iColumn, &aTerm[iNext], &pOr); - iNext += aTerm[iNext].nPhrase + 1; - if( rc ){ - queryClear(pQuery); - return rc; - } - pNew = docListNew(DL_DOCIDS); - docListOrMerge(pRight, pOr, pNew); - docListDelete(pRight); - docListDelete(pOr); - pRight = pNew; - } - if( pLeft==0 ){ - pLeft = pRight; - }else{ - pNew = docListNew(DL_DOCIDS); - docListAndMerge(pLeft, pRight, pNew); - docListDelete(pRight); - docListDelete(pLeft); - pLeft = pNew; - } - } - - if( nNot && pLeft==0 ){ - /* We do not yet know how to handle a query of only NOT terms */ - return SQLITE_ERROR; - } - - /* Do the EXCEPT terms */ - for(i=0; inTerms; i += aTerm[i].nPhrase + 1){ - if( !aTerm[i].isNot ) continue; - rc = docListOfTerm(v, aTerm[i].iColumn, &aTerm[i], &pRight); - if( rc ){ - queryClear(pQuery); - docListDelete(pLeft); - return rc; - } - pNew = docListNew(DL_DOCIDS); - docListExceptMerge(pLeft, pRight, pNew); - docListDelete(pRight); - docListDelete(pLeft); - pLeft = pNew; - } - - *pResult = pLeft; - return rc; -} - -/* -** This is the xFilter interface for the virtual table. See -** the virtual table xFilter method documentation for additional -** information. -** -** If idxNum==QUERY_GENERIC then do a full table scan against -** the %_content table. -** -** If idxNum==QUERY_ROWID then do a rowid lookup for a single entry -** in the %_content table. -** -** If idxNum>=QUERY_FULLTEXT then use the full text index. The -** column on the left-hand side of the MATCH operator is column -** number idxNum-QUERY_FULLTEXT, 0 indexed. argv[0] is the right-hand -** side of the MATCH operator. -*/ -/* TODO(shess) Upgrade the cursor initialization and destruction to -** account for fulltextFilter() being called multiple times on the -** same cursor. The current solution is very fragile. Apply fix to -** fts2 as appropriate. -*/ -static int fulltextFilter( - sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ - int idxNum, const char *idxStr, /* Which indexing scheme to use */ - int argc, sqlite3_value **argv /* Arguments for the indexing scheme */ -){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - fulltext_vtab *v = cursor_vtab(c); - int rc; - char *zSql; - - TRACE(("FTS1 Filter %p\n",pCursor)); - - zSql = sqlite3_mprintf("select rowid, * from %%_content %s", - idxNum==QUERY_GENERIC ? "" : "where rowid=?"); - sqlite3_finalize(c->pStmt); - rc = sql_prepare(v->db, v->zDb, v->zName, &c->pStmt, zSql); - sqlite3_free(zSql); - if( rc!=SQLITE_OK ) return rc; - - c->iCursorType = idxNum; - switch( idxNum ){ - case QUERY_GENERIC: - break; - - case QUERY_ROWID: - rc = sqlite3_bind_int64(c->pStmt, 1, sqlite3_value_int64(argv[0])); - if( rc!=SQLITE_OK ) return rc; - break; - - default: /* full-text search */ - { - const char *zQuery = (const char *)sqlite3_value_text(argv[0]); - DocList *pResult; - assert( idxNum<=QUERY_FULLTEXT+v->nColumn); - assert( argc==1 ); - queryClear(&c->q); - rc = fulltextQuery(v, idxNum-QUERY_FULLTEXT, zQuery, -1, &pResult, &c->q); - if( rc!=SQLITE_OK ) return rc; - if( c->result.pDoclist!=NULL ) docListDelete(c->result.pDoclist); - readerInit(&c->result, pResult); - break; - } - } - - return fulltextNext(pCursor); -} - -/* This is the xEof method of the virtual table. The SQLite core -** calls this routine to find out if it has reached the end of -** a query's results set. -*/ -static int fulltextEof(sqlite3_vtab_cursor *pCursor){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - return c->eof; -} - -/* This is the xColumn method of the virtual table. The SQLite -** core calls this method during a query when it needs the value -** of a column from the virtual table. This method needs to use -** one of the sqlite3_result_*() routines to store the requested -** value back in the pContext. -*/ -static int fulltextColumn(sqlite3_vtab_cursor *pCursor, - sqlite3_context *pContext, int idxCol){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - fulltext_vtab *v = cursor_vtab(c); - - if( idxColnColumn ){ - sqlite3_value *pVal = sqlite3_column_value(c->pStmt, idxCol+1); - sqlite3_result_value(pContext, pVal); - }else if( idxCol==v->nColumn ){ - /* The extra column whose name is the same as the table. - ** Return a blob which is a pointer to the cursor - */ - sqlite3_result_blob(pContext, &c, sizeof(c), SQLITE_TRANSIENT); - } - return SQLITE_OK; -} - -/* This is the xRowid method. The SQLite core calls this routine to -** retrive the rowid for the current row of the result set. The -** rowid should be written to *pRowid. -*/ -static int fulltextRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - - *pRowid = sqlite3_column_int64(c->pStmt, 0); - return SQLITE_OK; -} - -/* Add all terms in [zText] to the given hash table. If [iColumn] > 0, - * we also store positions and offsets in the hash table using the given - * column number. */ -static int buildTerms(fulltext_vtab *v, fts1Hash *terms, sqlite_int64 iDocid, - const char *zText, int iColumn){ - sqlite3_tokenizer *pTokenizer = v->pTokenizer; - sqlite3_tokenizer_cursor *pCursor; - const char *pToken; - int nTokenBytes; - int iStartOffset, iEndOffset, iPosition; - int rc; - - rc = pTokenizer->pModule->xOpen(pTokenizer, zText, -1, &pCursor); - if( rc!=SQLITE_OK ) return rc; - - pCursor->pTokenizer = pTokenizer; - while( SQLITE_OK==pTokenizer->pModule->xNext(pCursor, - &pToken, &nTokenBytes, - &iStartOffset, &iEndOffset, - &iPosition) ){ - DocList *p; - - /* Positions can't be negative; we use -1 as a terminator internally. */ - if( iPosition<0 ){ - pTokenizer->pModule->xClose(pCursor); - return SQLITE_ERROR; - } - - p = fts1HashFind(terms, pToken, nTokenBytes); - if( p==NULL ){ - p = docListNew(DL_DEFAULT); - docListAddDocid(p, iDocid); - fts1HashInsert(terms, pToken, nTokenBytes, p); - } - if( iColumn>=0 ){ - docListAddPosOffset(p, iColumn, iPosition, iStartOffset, iEndOffset); - } - } - - /* TODO(shess) Check return? Should this be able to cause errors at - ** this point? Actually, same question about sqlite3_finalize(), - ** though one could argue that failure there means that the data is - ** not durable. *ponder* - */ - pTokenizer->pModule->xClose(pCursor); - return rc; -} - -/* Update the %_terms table to map the term [pTerm] to the given rowid. */ -static int index_insert_term(fulltext_vtab *v, const char *pTerm, int nTerm, - DocList *d){ - sqlite_int64 iIndexRow; - DocList doclist; - int iSegment = 0, rc; - - rc = term_select(v, pTerm, nTerm, iSegment, &iIndexRow, &doclist); - if( rc==SQLITE_DONE ){ - docListInit(&doclist, DL_DEFAULT, 0, 0); - docListUpdate(&doclist, d); - /* TODO(shess) Consider length(doclist)>CHUNK_MAX? */ - rc = term_insert(v, NULL, pTerm, nTerm, iSegment, &doclist); - goto err; - } - if( rc!=SQLITE_ROW ) return SQLITE_ERROR; - - docListUpdate(&doclist, d); - if( doclist.nData<=CHUNK_MAX ){ - rc = term_update(v, iIndexRow, &doclist); - goto err; - } - - /* Doclist doesn't fit, delete what's there, and accumulate - ** forward. - */ - rc = term_delete(v, iIndexRow); - if( rc!=SQLITE_OK ) goto err; - - /* Try to insert the doclist into a higher segment bucket. On - ** failure, accumulate existing doclist with the doclist from that - ** bucket, and put results in the next bucket. - */ - iSegment++; - while( (rc=term_insert(v, &iIndexRow, pTerm, nTerm, iSegment, - &doclist))!=SQLITE_OK ){ - sqlite_int64 iSegmentRow; - DocList old; - int rc2; - - /* Retain old error in case the term_insert() error was really an - ** error rather than a bounced insert. - */ - rc2 = term_select(v, pTerm, nTerm, iSegment, &iSegmentRow, &old); - if( rc2!=SQLITE_ROW ) goto err; - - rc = term_delete(v, iSegmentRow); - if( rc!=SQLITE_OK ) goto err; - - /* Reusing lowest-number deleted row keeps the index smaller. */ - if( iSegmentRownColumn ; ++i){ - char *zText = (char*)sqlite3_value_text(pValues[i]); - int rc = buildTerms(v, terms, iRowid, zText, i); - if( rc!=SQLITE_OK ) return rc; - } - return SQLITE_OK; -} - -/* Add empty doclists for all terms in the given row's content to the hash - * table [pTerms]. */ -static int deleteTerms(fulltext_vtab *v, fts1Hash *pTerms, sqlite_int64 iRowid){ - const char **pValues; - int i; - - int rc = content_select(v, iRowid, &pValues); - if( rc!=SQLITE_OK ) return rc; - - for(i = 0 ; i < v->nColumn; ++i) { - rc = buildTerms(v, pTerms, iRowid, pValues[i], -1); - if( rc!=SQLITE_OK ) break; - } - - freeStringArray(v->nColumn, pValues); - return SQLITE_OK; -} - -/* Insert a row into the %_content table; set *piRowid to be the ID of the - * new row. Fill [pTerms] with new doclists for the %_term table. */ -static int index_insert(fulltext_vtab *v, sqlite3_value *pRequestRowid, - sqlite3_value **pValues, - sqlite_int64 *piRowid, fts1Hash *pTerms){ - int rc; - - rc = content_insert(v, pRequestRowid, pValues); /* execute an SQL INSERT */ - if( rc!=SQLITE_OK ) return rc; - *piRowid = sqlite3_last_insert_rowid(v->db); - return insertTerms(v, pTerms, *piRowid, pValues); -} - -/* Delete a row from the %_content table; fill [pTerms] with empty doclists - * to be written to the %_term table. */ -static int index_delete(fulltext_vtab *v, sqlite_int64 iRow, fts1Hash *pTerms){ - int rc = deleteTerms(v, pTerms, iRow); - if( rc!=SQLITE_OK ) return rc; - return content_delete(v, iRow); /* execute an SQL DELETE */ -} - -/* Update a row in the %_content table; fill [pTerms] with new doclists for the - * %_term table. */ -static int index_update(fulltext_vtab *v, sqlite_int64 iRow, - sqlite3_value **pValues, fts1Hash *pTerms){ - /* Generate an empty doclist for each term that previously appeared in this - * row. */ - int rc = deleteTerms(v, pTerms, iRow); - if( rc!=SQLITE_OK ) return rc; - - rc = content_update(v, pValues, iRow); /* execute an SQL UPDATE */ - if( rc!=SQLITE_OK ) return rc; - - /* Now add positions for terms which appear in the updated row. */ - return insertTerms(v, pTerms, iRow, pValues); -} - -/* This function implements the xUpdate callback; it is the top-level entry - * point for inserting, deleting or updating a row in a full-text table. */ -static int fulltextUpdate(sqlite3_vtab *pVtab, int nArg, sqlite3_value **ppArg, - sqlite_int64 *pRowid){ - fulltext_vtab *v = (fulltext_vtab *) pVtab; - fts1Hash terms; /* maps term string -> PosList */ - int rc; - fts1HashElem *e; - - TRACE(("FTS1 Update %p\n", pVtab)); - - fts1HashInit(&terms, FTS1_HASH_STRING, 1); - - if( nArg<2 ){ - rc = index_delete(v, sqlite3_value_int64(ppArg[0]), &terms); - } else if( sqlite3_value_type(ppArg[0]) != SQLITE_NULL ){ - /* An update: - * ppArg[0] = old rowid - * ppArg[1] = new rowid - * ppArg[2..2+v->nColumn-1] = values - * ppArg[2+v->nColumn] = value for magic column (we ignore this) - */ - sqlite_int64 rowid = sqlite3_value_int64(ppArg[0]); - if( sqlite3_value_type(ppArg[1]) != SQLITE_INTEGER || - sqlite3_value_int64(ppArg[1]) != rowid ){ - rc = SQLITE_ERROR; /* we don't allow changing the rowid */ - } else { - assert( nArg==2+v->nColumn+1); - rc = index_update(v, rowid, &ppArg[2], &terms); - } - } else { - /* An insert: - * ppArg[1] = requested rowid - * ppArg[2..2+v->nColumn-1] = values - * ppArg[2+v->nColumn] = value for magic column (we ignore this) - */ - assert( nArg==2+v->nColumn+1); - rc = index_insert(v, ppArg[1], &ppArg[2], pRowid, &terms); - } - - if( rc==SQLITE_OK ){ - /* Write updated doclists to disk. */ - for(e=fts1HashFirst(&terms); e; e=fts1HashNext(e)){ - DocList *p = fts1HashData(e); - rc = index_insert_term(v, fts1HashKey(e), fts1HashKeysize(e), p); - if( rc!=SQLITE_OK ) break; - } - } - - /* clean up */ - for(e=fts1HashFirst(&terms); e; e=fts1HashNext(e)){ - DocList *p = fts1HashData(e); - docListDelete(p); - } - fts1HashClear(&terms); - - return rc; -} - -/* -** Implementation of the snippet() function for FTS1 -*/ -static void snippetFunc( - sqlite3_context *pContext, - int argc, - sqlite3_value **argv -){ - fulltext_cursor *pCursor; - if( argc<1 ) return; - if( sqlite3_value_type(argv[0])!=SQLITE_BLOB || - sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){ - sqlite3_result_error(pContext, "illegal first argument to html_snippet",-1); - }else{ - const char *zStart = ""; - const char *zEnd = ""; - const char *zEllipsis = "..."; - memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor)); - if( argc>=2 ){ - zStart = (const char*)sqlite3_value_text(argv[1]); - if( argc>=3 ){ - zEnd = (const char*)sqlite3_value_text(argv[2]); - if( argc>=4 ){ - zEllipsis = (const char*)sqlite3_value_text(argv[3]); - } - } - } - snippetAllOffsets(pCursor); - snippetText(pCursor, zStart, zEnd, zEllipsis); - sqlite3_result_text(pContext, pCursor->snippet.zSnippet, - pCursor->snippet.nSnippet, SQLITE_STATIC); - } -} - -/* -** Implementation of the offsets() function for FTS1 -*/ -static void snippetOffsetsFunc( - sqlite3_context *pContext, - int argc, - sqlite3_value **argv -){ - fulltext_cursor *pCursor; - if( argc<1 ) return; - if( sqlite3_value_type(argv[0])!=SQLITE_BLOB || - sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){ - sqlite3_result_error(pContext, "illegal first argument to offsets",-1); - }else{ - memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor)); - snippetAllOffsets(pCursor); - snippetOffsetText(&pCursor->snippet); - sqlite3_result_text(pContext, - pCursor->snippet.zOffset, pCursor->snippet.nOffset, - SQLITE_STATIC); - } -} - -/* -** This routine implements the xFindFunction method for the FTS1 -** virtual table. -*/ -static int fulltextFindFunction( - sqlite3_vtab *pVtab, - int nArg, - const char *zName, - void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), - void **ppArg -){ - if( strcmp(zName,"snippet")==0 ){ - *pxFunc = snippetFunc; - return 1; - }else if( strcmp(zName,"offsets")==0 ){ - *pxFunc = snippetOffsetsFunc; - return 1; - } - return 0; -} - -/* -** Rename an fts1 table. -*/ -static int fulltextRename( - sqlite3_vtab *pVtab, - const char *zName -){ - fulltext_vtab *p = (fulltext_vtab *)pVtab; - int rc = SQLITE_NOMEM; - char *zSql = sqlite3_mprintf( - "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';" - "ALTER TABLE %Q.'%q_term' RENAME TO '%q_term';" - , p->zDb, p->zName, zName - , p->zDb, p->zName, zName - ); - if( zSql ){ - rc = sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_free(zSql); - } - return rc; -} - -static const sqlite3_module fulltextModule = { - /* iVersion */ 0, - /* xCreate */ fulltextCreate, - /* xConnect */ fulltextConnect, - /* xBestIndex */ fulltextBestIndex, - /* xDisconnect */ fulltextDisconnect, - /* xDestroy */ fulltextDestroy, - /* xOpen */ fulltextOpen, - /* xClose */ fulltextClose, - /* xFilter */ fulltextFilter, - /* xNext */ fulltextNext, - /* xEof */ fulltextEof, - /* xColumn */ fulltextColumn, - /* xRowid */ fulltextRowid, - /* xUpdate */ fulltextUpdate, - /* xBegin */ 0, - /* xSync */ 0, - /* xCommit */ 0, - /* xRollback */ 0, - /* xFindFunction */ fulltextFindFunction, - /* xRename */ fulltextRename, -}; - -int sqlite3Fts1Init(sqlite3 *db){ - sqlite3_overload_function(db, "snippet", -1); - sqlite3_overload_function(db, "offsets", -1); - return sqlite3_create_module(db, "fts1", &fulltextModule, 0); -} - -#if !SQLITE_CORE -#ifdef _WIN32 -__declspec(dllexport) -#endif -int sqlite3_fts1_init(sqlite3 *db, char **pzErrMsg, - const sqlite3_api_routines *pApi){ - SQLITE_EXTENSION_INIT2(pApi) - return sqlite3Fts1Init(db); -} -#endif - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) */ diff --git a/ext/fts1/fts1.h b/ext/fts1/fts1.h deleted file mode 100644 index d55e68973..000000000 --- a/ext/fts1/fts1.h +++ /dev/null @@ -1,11 +0,0 @@ -#include "sqlite3.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -int sqlite3Fts1Init(sqlite3 *db); - -#ifdef __cplusplus -} /* extern "C" */ -#endif /* __cplusplus */ diff --git a/ext/fts1/fts1_hash.c b/ext/fts1/fts1_hash.c deleted file mode 100644 index 463a52b64..000000000 --- a/ext/fts1/fts1_hash.c +++ /dev/null @@ -1,369 +0,0 @@ -/* -** 2001 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 is the implementation of generic hash-tables used in SQLite. -** We've modified it slightly to serve as a standalone hash table -** implementation for the full-text indexing module. -*/ -#include -#include -#include - -/* -** The code in this file is only compiled if: -** -** * The FTS1 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS1 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS1 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) - - -#include "fts1_hash.h" - -static void *malloc_and_zero(int n){ - void *p = malloc(n); - if( p ){ - memset(p, 0, n); - } - return p; -} - -/* Turn bulk memory into a hash table object by initializing the -** fields of the Hash structure. -** -** "pNew" is a pointer to the hash table that is to be initialized. -** keyClass is one of the constants -** FTS1_HASH_BINARY or FTS1_HASH_STRING. The value of keyClass -** determines what kind of key the hash table will use. "copyKey" is -** true if the hash table should make its own private copy of keys and -** false if it should just use the supplied pointer. -*/ -void sqlite3Fts1HashInit(fts1Hash *pNew, int keyClass, int copyKey){ - assert( pNew!=0 ); - assert( keyClass>=FTS1_HASH_STRING && keyClass<=FTS1_HASH_BINARY ); - pNew->keyClass = keyClass; - pNew->copyKey = copyKey; - pNew->first = 0; - pNew->count = 0; - pNew->htsize = 0; - pNew->ht = 0; - pNew->xMalloc = malloc_and_zero; - pNew->xFree = free; -} - -/* Remove all entries from a hash table. Reclaim all memory. -** Call this routine to delete a hash table or to reset a hash table -** to the empty state. -*/ -void sqlite3Fts1HashClear(fts1Hash *pH){ - fts1HashElem *elem; /* For looping over all elements of the table */ - - assert( pH!=0 ); - elem = pH->first; - pH->first = 0; - if( pH->ht ) pH->xFree(pH->ht); - pH->ht = 0; - pH->htsize = 0; - while( elem ){ - fts1HashElem *next_elem = elem->next; - if( pH->copyKey && elem->pKey ){ - pH->xFree(elem->pKey); - } - pH->xFree(elem); - elem = next_elem; - } - pH->count = 0; -} - -/* -** Hash and comparison functions when the mode is FTS1_HASH_STRING -*/ -static int strHash(const void *pKey, int nKey){ - const char *z = (const char *)pKey; - int h = 0; - if( nKey<=0 ) nKey = (int) strlen(z); - while( nKey > 0 ){ - h = (h<<3) ^ h ^ *z++; - nKey--; - } - return h & 0x7fffffff; -} -static int strCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - if( n1!=n2 ) return 1; - return strncmp((const char*)pKey1,(const char*)pKey2,n1); -} - -/* -** Hash and comparison functions when the mode is FTS1_HASH_BINARY -*/ -static int binHash(const void *pKey, int nKey){ - int h = 0; - const char *z = (const char *)pKey; - while( nKey-- > 0 ){ - h = (h<<3) ^ h ^ *(z++); - } - return h & 0x7fffffff; -} -static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - if( n1!=n2 ) return 1; - return memcmp(pKey1,pKey2,n1); -} - -/* -** Return a pointer to the appropriate hash function given the key class. -** -** The C syntax in this function definition may be unfamilar to some -** programmers, so we provide the following additional explanation: -** -** The name of the function is "hashFunction". The function takes a -** single parameter "keyClass". The return value of hashFunction() -** is a pointer to another function. Specifically, the return value -** of hashFunction() is a pointer to a function that takes two parameters -** with types "const void*" and "int" and returns an "int". -*/ -static int (*hashFunction(int keyClass))(const void*,int){ - if( keyClass==FTS1_HASH_STRING ){ - return &strHash; - }else{ - assert( keyClass==FTS1_HASH_BINARY ); - return &binHash; - } -} - -/* -** Return a pointer to the appropriate hash function given the key class. -** -** For help in interpreted the obscure C code in the function definition, -** see the header comment on the previous function. -*/ -static int (*compareFunction(int keyClass))(const void*,int,const void*,int){ - if( keyClass==FTS1_HASH_STRING ){ - return &strCompare; - }else{ - assert( keyClass==FTS1_HASH_BINARY ); - return &binCompare; - } -} - -/* Link an element into the hash table -*/ -static void insertElement( - fts1Hash *pH, /* The complete hash table */ - struct _fts1ht *pEntry, /* The entry into which pNew is inserted */ - fts1HashElem *pNew /* The element to be inserted */ -){ - fts1HashElem *pHead; /* First element already in pEntry */ - pHead = pEntry->chain; - if( pHead ){ - pNew->next = pHead; - pNew->prev = pHead->prev; - if( pHead->prev ){ pHead->prev->next = pNew; } - else { pH->first = pNew; } - pHead->prev = pNew; - }else{ - pNew->next = pH->first; - if( pH->first ){ pH->first->prev = pNew; } - pNew->prev = 0; - pH->first = pNew; - } - pEntry->count++; - pEntry->chain = pNew; -} - - -/* Resize the hash table so that it cantains "new_size" buckets. -** "new_size" must be a power of 2. The hash table might fail -** to resize if sqliteMalloc() fails. -*/ -static void rehash(fts1Hash *pH, int new_size){ - struct _fts1ht *new_ht; /* The new hash table */ - fts1HashElem *elem, *next_elem; /* For looping over existing elements */ - int (*xHash)(const void*,int); /* The hash function */ - - assert( (new_size & (new_size-1))==0 ); - new_ht = (struct _fts1ht *)pH->xMalloc( new_size*sizeof(struct _fts1ht) ); - if( new_ht==0 ) return; - if( pH->ht ) pH->xFree(pH->ht); - pH->ht = new_ht; - pH->htsize = new_size; - xHash = hashFunction(pH->keyClass); - for(elem=pH->first, pH->first=0; elem; elem = next_elem){ - int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); - next_elem = elem->next; - insertElement(pH, &new_ht[h], elem); - } -} - -/* This function (for internal use only) locates an element in an -** hash table that matches the given key. The hash for this key has -** already been computed and is passed as the 4th parameter. -*/ -static fts1HashElem *findElementGivenHash( - const fts1Hash *pH, /* The pH to be searched */ - const void *pKey, /* The key we are searching for */ - int nKey, - int h /* The hash for this key. */ -){ - fts1HashElem *elem; /* Used to loop thru the element list */ - int count; /* Number of elements left to test */ - int (*xCompare)(const void*,int,const void*,int); /* comparison function */ - - if( pH->ht ){ - struct _fts1ht *pEntry = &pH->ht[h]; - elem = pEntry->chain; - count = pEntry->count; - xCompare = compareFunction(pH->keyClass); - while( count-- && elem ){ - if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ - return elem; - } - elem = elem->next; - } - } - return 0; -} - -/* Remove a single entry from the hash table given a pointer to that -** element and a hash on the element's key. -*/ -static void removeElementGivenHash( - fts1Hash *pH, /* The pH containing "elem" */ - fts1HashElem* elem, /* The element to be removed from the pH */ - int h /* Hash value for the element */ -){ - struct _fts1ht *pEntry; - if( elem->prev ){ - elem->prev->next = elem->next; - }else{ - pH->first = elem->next; - } - if( elem->next ){ - elem->next->prev = elem->prev; - } - pEntry = &pH->ht[h]; - if( pEntry->chain==elem ){ - pEntry->chain = elem->next; - } - pEntry->count--; - if( pEntry->count<=0 ){ - pEntry->chain = 0; - } - if( pH->copyKey && elem->pKey ){ - pH->xFree(elem->pKey); - } - pH->xFree( elem ); - pH->count--; - if( pH->count<=0 ){ - assert( pH->first==0 ); - assert( pH->count==0 ); - fts1HashClear(pH); - } -} - -/* Attempt to locate an element of the hash table pH with a key -** that matches pKey,nKey. Return the data for this element if it is -** found, or NULL if there is no match. -*/ -void *sqlite3Fts1HashFind(const fts1Hash *pH, const void *pKey, int nKey){ - int h; /* A hash on key */ - fts1HashElem *elem; /* The element that matches key */ - int (*xHash)(const void*,int); /* The hash function */ - - if( pH==0 || pH->ht==0 ) return 0; - xHash = hashFunction(pH->keyClass); - assert( xHash!=0 ); - h = (*xHash)(pKey,nKey); - assert( (pH->htsize & (pH->htsize-1))==0 ); - elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1)); - return elem ? elem->data : 0; -} - -/* Insert an element into the hash table pH. The key is pKey,nKey -** and the data is "data". -** -** If no element exists with a matching key, then a new -** element is created. A copy of the key is made if the copyKey -** flag is set. NULL is returned. -** -** If another element already exists with the same key, then the -** new data replaces the old data and the old data is returned. -** The key is not copied in this instance. If a malloc fails, then -** the new data is returned and the hash table is unchanged. -** -** If the "data" parameter to this function is NULL, then the -** element corresponding to "key" is removed from the hash table. -*/ -void *sqlite3Fts1HashInsert( - fts1Hash *pH, /* The hash table to insert into */ - const void *pKey, /* The key */ - int nKey, /* Number of bytes in the key */ - void *data /* The data */ -){ - int hraw; /* Raw hash value of the key */ - int h; /* the hash of the key modulo hash table size */ - fts1HashElem *elem; /* Used to loop thru the element list */ - fts1HashElem *new_elem; /* New element added to the pH */ - int (*xHash)(const void*,int); /* The hash function */ - - assert( pH!=0 ); - xHash = hashFunction(pH->keyClass); - assert( xHash!=0 ); - hraw = (*xHash)(pKey, nKey); - assert( (pH->htsize & (pH->htsize-1))==0 ); - h = hraw & (pH->htsize-1); - elem = findElementGivenHash(pH,pKey,nKey,h); - if( elem ){ - void *old_data = elem->data; - if( data==0 ){ - removeElementGivenHash(pH,elem,h); - }else{ - elem->data = data; - } - return old_data; - } - if( data==0 ) return 0; - new_elem = (fts1HashElem*)pH->xMalloc( sizeof(fts1HashElem) ); - if( new_elem==0 ) return data; - if( pH->copyKey && pKey!=0 ){ - new_elem->pKey = pH->xMalloc( nKey ); - if( new_elem->pKey==0 ){ - pH->xFree(new_elem); - return data; - } - memcpy((void*)new_elem->pKey, pKey, nKey); - }else{ - new_elem->pKey = (void*)pKey; - } - new_elem->nKey = nKey; - pH->count++; - if( pH->htsize==0 ){ - rehash(pH,8); - if( pH->htsize==0 ){ - pH->count = 0; - pH->xFree(new_elem); - return data; - } - } - if( pH->count > pH->htsize ){ - rehash(pH,pH->htsize*2); - } - assert( pH->htsize>0 ); - assert( (pH->htsize & (pH->htsize-1))==0 ); - h = hraw & (pH->htsize-1); - insertElement(pH, &pH->ht[h], new_elem); - new_elem->data = data; - return 0; -} - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) */ diff --git a/ext/fts1/fts1_hash.h b/ext/fts1/fts1_hash.h deleted file mode 100644 index 900115293..000000000 --- a/ext/fts1/fts1_hash.h +++ /dev/null @@ -1,112 +0,0 @@ -/* -** 2001 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 is the header file for the generic hash-table implementation -** used in SQLite. We've modified it slightly to serve as a standalone -** hash table implementation for the full-text indexing module. -** -*/ -#ifndef _FTS1_HASH_H_ -#define _FTS1_HASH_H_ - -/* Forward declarations of structures. */ -typedef struct fts1Hash fts1Hash; -typedef struct fts1HashElem fts1HashElem; - -/* A complete hash table is an instance of the following structure. -** The internals of this structure are intended to be opaque -- client -** code should not attempt to access or modify the fields of this structure -** directly. Change this structure only by using the routines below. -** However, many of the "procedures" and "functions" for modifying and -** accessing this structure are really macros, so we can't really make -** this structure opaque. -*/ -struct fts1Hash { - char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */ - char copyKey; /* True if copy of key made on insert */ - int count; /* Number of entries in this table */ - fts1HashElem *first; /* The first element of the array */ - void *(*xMalloc)(int); /* malloc() function to use */ - void (*xFree)(void *); /* free() function to use */ - int htsize; /* Number of buckets in the hash table */ - struct _fts1ht { /* the hash table */ - int count; /* Number of entries with this hash */ - fts1HashElem *chain; /* Pointer to first entry with this hash */ - } *ht; -}; - -/* Each element in the hash table is an instance of the following -** structure. All elements are stored on a single doubly-linked list. -** -** Again, this structure is intended to be opaque, but it can't really -** be opaque because it is used by macros. -*/ -struct fts1HashElem { - fts1HashElem *next, *prev; /* Next and previous elements in the table */ - void *data; /* Data associated with this element */ - void *pKey; int nKey; /* Key associated with this element */ -}; - -/* -** There are 2 different modes of operation for a hash table: -** -** FTS1_HASH_STRING pKey points to a string that is nKey bytes long -** (including the null-terminator, if any). Case -** is respected in comparisons. -** -** FTS1_HASH_BINARY pKey points to binary data nKey bytes long. -** memcmp() is used to compare keys. -** -** A copy of the key is made if the copyKey parameter to fts1HashInit is 1. -*/ -#define FTS1_HASH_STRING 1 -#define FTS1_HASH_BINARY 2 - -/* -** Access routines. To delete, insert a NULL pointer. -*/ -void sqlite3Fts1HashInit(fts1Hash*, int keytype, int copyKey); -void *sqlite3Fts1HashInsert(fts1Hash*, const void *pKey, int nKey, void *pData); -void *sqlite3Fts1HashFind(const fts1Hash*, const void *pKey, int nKey); -void sqlite3Fts1HashClear(fts1Hash*); - -/* -** Shorthand for the functions above -*/ -#define fts1HashInit sqlite3Fts1HashInit -#define fts1HashInsert sqlite3Fts1HashInsert -#define fts1HashFind sqlite3Fts1HashFind -#define fts1HashClear sqlite3Fts1HashClear - -/* -** Macros for looping over all elements of a hash table. The idiom is -** like this: -** -** fts1Hash h; -** fts1HashElem *p; -** ... -** for(p=fts1HashFirst(&h); p; p=fts1HashNext(p)){ -** SomeStructure *pData = fts1HashData(p); -** // do something with pData -** } -*/ -#define fts1HashFirst(H) ((H)->first) -#define fts1HashNext(E) ((E)->next) -#define fts1HashData(E) ((E)->data) -#define fts1HashKey(E) ((E)->pKey) -#define fts1HashKeysize(E) ((E)->nKey) - -/* -** Number of entries in a hash table -*/ -#define fts1HashCount(H) ((H)->count) - -#endif /* _FTS1_HASH_H_ */ diff --git a/ext/fts1/fts1_porter.c b/ext/fts1/fts1_porter.c deleted file mode 100644 index 1d2623668..000000000 --- a/ext/fts1/fts1_porter.c +++ /dev/null @@ -1,643 +0,0 @@ -/* -** 2006 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. -** -************************************************************************* -** Implementation of the full-text-search tokenizer that implements -** a Porter stemmer. -*/ - -/* -** The code in this file is only compiled if: -** -** * The FTS1 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS1 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS1 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) - - -#include -#include -#include -#include -#include - -#include "fts1_tokenizer.h" - -/* -** Class derived from sqlite3_tokenizer -*/ -typedef struct porter_tokenizer { - sqlite3_tokenizer base; /* Base class */ -} porter_tokenizer; - -/* -** Class derived from sqlit3_tokenizer_cursor -*/ -typedef struct porter_tokenizer_cursor { - sqlite3_tokenizer_cursor base; - const char *zInput; /* input we are tokenizing */ - int nInput; /* size of the input */ - int iOffset; /* current position in zInput */ - int iToken; /* index of next token to be returned */ - char *zToken; /* storage for current token */ - int nAllocated; /* space allocated to zToken buffer */ -} porter_tokenizer_cursor; - - -/* Forward declaration */ -static const sqlite3_tokenizer_module porterTokenizerModule; - - -/* -** Create a new tokenizer instance. -*/ -static int porterCreate( - int argc, const char * const *argv, - sqlite3_tokenizer **ppTokenizer -){ - porter_tokenizer *t; - t = (porter_tokenizer *) calloc(sizeof(*t), 1); - if( t==NULL ) return SQLITE_NOMEM; - - *ppTokenizer = &t->base; - return SQLITE_OK; -} - -/* -** Destroy a tokenizer -*/ -static int porterDestroy(sqlite3_tokenizer *pTokenizer){ - free(pTokenizer); - return SQLITE_OK; -} - -/* -** Prepare to begin tokenizing a particular string. The input -** string to be tokenized is zInput[0..nInput-1]. A cursor -** used to incrementally tokenize this string is returned in -** *ppCursor. -*/ -static int porterOpen( - sqlite3_tokenizer *pTokenizer, /* The tokenizer */ - const char *zInput, int nInput, /* String to be tokenized */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ -){ - porter_tokenizer_cursor *c; - - c = (porter_tokenizer_cursor *) malloc(sizeof(*c)); - if( c==NULL ) return SQLITE_NOMEM; - - c->zInput = zInput; - if( zInput==0 ){ - c->nInput = 0; - }else if( nInput<0 ){ - c->nInput = (int)strlen(zInput); - }else{ - c->nInput = nInput; - } - c->iOffset = 0; /* start tokenizing at the beginning */ - c->iToken = 0; - c->zToken = NULL; /* no space allocated, yet. */ - c->nAllocated = 0; - - *ppCursor = &c->base; - return SQLITE_OK; -} - -/* -** Close a tokenization cursor previously opened by a call to -** porterOpen() above. -*/ -static int porterClose(sqlite3_tokenizer_cursor *pCursor){ - porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; - free(c->zToken); - free(c); - return SQLITE_OK; -} -/* -** Vowel or consonant -*/ -static const char cType[] = { - 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 2, 1 -}; - -/* -** isConsonant() and isVowel() determine if their first character in -** the string they point to is a consonant or a vowel, according -** to Porter ruls. -** -** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'. -** 'Y' is a consonant unless it follows another consonant, -** in which case it is a vowel. -** -** In these routine, the letters are in reverse order. So the 'y' rule -** is that 'y' is a consonant unless it is followed by another -** consonent. -*/ -static int isVowel(const char*); -static int isConsonant(const char *z){ - int j; - char x = *z; - if( x==0 ) return 0; - assert( x>='a' && x<='z' ); - j = cType[x-'a']; - if( j<2 ) return j; - return z[1]==0 || isVowel(z + 1); -} -static int isVowel(const char *z){ - int j; - char x = *z; - if( x==0 ) return 0; - assert( x>='a' && x<='z' ); - j = cType[x-'a']; - if( j<2 ) return 1-j; - return isConsonant(z + 1); -} - -/* -** Let any sequence of one or more vowels be represented by V and let -** C be sequence of one or more consonants. Then every word can be -** represented as: -** -** [C] (VC){m} [V] -** -** In prose: A word is an optional consonant followed by zero or -** vowel-consonant pairs followed by an optional vowel. "m" is the -** number of vowel consonant pairs. This routine computes the value -** of m for the first i bytes of a word. -** -** Return true if the m-value for z is 1 or more. In other words, -** return true if z contains at least one vowel that is followed -** by a consonant. -** -** In this routine z[] is in reverse order. So we are really looking -** for an instance of of a consonant followed by a vowel. -*/ -static int m_gt_0(const char *z){ - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - return *z!=0; -} - -/* Like mgt0 above except we are looking for a value of m which is -** exactly 1 -*/ -static int m_eq_1(const char *z){ - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - if( *z==0 ) return 0; - while( isVowel(z) ){ z++; } - if( *z==0 ) return 1; - while( isConsonant(z) ){ z++; } - return *z==0; -} - -/* Like mgt0 above except we are looking for a value of m>1 instead -** or m>0 -*/ -static int m_gt_1(const char *z){ - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - if( *z==0 ) return 0; - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - return *z!=0; -} - -/* -** Return TRUE if there is a vowel anywhere within z[0..n-1] -*/ -static int hasVowel(const char *z){ - while( isConsonant(z) ){ z++; } - return *z!=0; -} - -/* -** Return TRUE if the word ends in a double consonant. -** -** The text is reversed here. So we are really looking at -** the first two characters of z[]. -*/ -static int doubleConsonant(const char *z){ - return isConsonant(z) && z[0]==z[1] && isConsonant(z+1); -} - -/* -** Return TRUE if the word ends with three letters which -** are consonant-vowel-consonent and where the final consonant -** is not 'w', 'x', or 'y'. -** -** The word is reversed here. So we are really checking the -** first three letters and the first one cannot be in [wxy]. -*/ -static int star_oh(const char *z){ - return - z[0]!=0 && isConsonant(z) && - z[0]!='w' && z[0]!='x' && z[0]!='y' && - z[1]!=0 && isVowel(z+1) && - z[2]!=0 && isConsonant(z+2); -} - -/* -** If the word ends with zFrom and xCond() is true for the stem -** of the word that preceeds the zFrom ending, then change the -** ending to zTo. -** -** The input word *pz and zFrom are both in reverse order. zTo -** is in normal order. -** -** Return TRUE if zFrom matches. Return FALSE if zFrom does not -** match. Not that TRUE is returned even if xCond() fails and -** no substitution occurs. -*/ -static int stem( - char **pz, /* The word being stemmed (Reversed) */ - const char *zFrom, /* If the ending matches this... (Reversed) */ - const char *zTo, /* ... change the ending to this (not reversed) */ - int (*xCond)(const char*) /* Condition that must be true */ -){ - char *z = *pz; - while( *zFrom && *zFrom==*z ){ z++; zFrom++; } - if( *zFrom!=0 ) return 0; - if( xCond && !xCond(z) ) return 1; - while( *zTo ){ - *(--z) = *(zTo++); - } - *pz = z; - return 1; -} - -/* -** This is the fallback stemmer used when the porter stemmer is -** inappropriate. The input word is copied into the output with -** US-ASCII case folding. If the input word is too long (more -** than 20 bytes if it contains no digits or more than 6 bytes if -** it contains digits) then word is truncated to 20 or 6 bytes -** by taking 10 or 3 bytes from the beginning and end. -*/ -static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ - int i, mx, j; - int hasDigit = 0; - for(i=0; i='A' && c<='Z' ){ - zOut[i] = c - 'A' + 'a'; - }else{ - if( c>='0' && c<='9' ) hasDigit = 1; - zOut[i] = c; - } - } - mx = hasDigit ? 3 : 10; - if( nIn>mx*2 ){ - for(j=mx, i=nIn-mx; i=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); - return; - } - for(i=0, j=sizeof(zReverse)-6; i='A' && c<='Z' ){ - zReverse[j] = c + 'a' - 'A'; - }else if( c>='a' && c<='z' ){ - zReverse[j] = c; - }else{ - /* The use of a character not in [a-zA-Z] means that we fallback - ** to the copy stemmer */ - copy_stemmer(zIn, nIn, zOut, pnOut); - return; - } - } - memset(&zReverse[sizeof(zReverse)-5], 0, 5); - z = &zReverse[j+1]; - - - /* Step 1a */ - if( z[0]=='s' ){ - if( - !stem(&z, "sess", "ss", 0) && - !stem(&z, "sei", "i", 0) && - !stem(&z, "ss", "ss", 0) - ){ - z++; - } - } - - /* Step 1b */ - z2 = z; - if( stem(&z, "dee", "ee", m_gt_0) ){ - /* Do nothing. The work was all in the test */ - }else if( - (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel)) - && z!=z2 - ){ - if( stem(&z, "ta", "ate", 0) || - stem(&z, "lb", "ble", 0) || - stem(&z, "zi", "ize", 0) ){ - /* Do nothing. The work was all in the test */ - }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){ - z++; - }else if( m_eq_1(z) && star_oh(z) ){ - *(--z) = 'e'; - } - } - - /* Step 1c */ - if( z[0]=='y' && hasVowel(z+1) ){ - z[0] = 'i'; - } - - /* Step 2 */ - switch( z[1] ){ - case 'a': - stem(&z, "lanoita", "ate", m_gt_0) || - stem(&z, "lanoit", "tion", m_gt_0); - break; - case 'c': - stem(&z, "icne", "ence", m_gt_0) || - stem(&z, "icna", "ance", m_gt_0); - break; - case 'e': - stem(&z, "rezi", "ize", m_gt_0); - break; - case 'g': - stem(&z, "igol", "log", m_gt_0); - break; - case 'l': - stem(&z, "ilb", "ble", m_gt_0) || - stem(&z, "illa", "al", m_gt_0) || - stem(&z, "iltne", "ent", m_gt_0) || - stem(&z, "ile", "e", m_gt_0) || - stem(&z, "ilsuo", "ous", m_gt_0); - break; - case 'o': - stem(&z, "noitazi", "ize", m_gt_0) || - stem(&z, "noita", "ate", m_gt_0) || - stem(&z, "rota", "ate", m_gt_0); - break; - case 's': - stem(&z, "msila", "al", m_gt_0) || - stem(&z, "ssenevi", "ive", m_gt_0) || - stem(&z, "ssenluf", "ful", m_gt_0) || - stem(&z, "ssensuo", "ous", m_gt_0); - break; - case 't': - stem(&z, "itila", "al", m_gt_0) || - stem(&z, "itivi", "ive", m_gt_0) || - stem(&z, "itilib", "ble", m_gt_0); - break; - } - - /* Step 3 */ - switch( z[0] ){ - case 'e': - stem(&z, "etaci", "ic", m_gt_0) || - stem(&z, "evita", "", m_gt_0) || - stem(&z, "ezila", "al", m_gt_0); - break; - case 'i': - stem(&z, "itici", "ic", m_gt_0); - break; - case 'l': - stem(&z, "laci", "ic", m_gt_0) || - stem(&z, "luf", "", m_gt_0); - break; - case 's': - stem(&z, "ssen", "", m_gt_0); - break; - } - - /* Step 4 */ - switch( z[1] ){ - case 'a': - if( z[0]=='l' && m_gt_1(z+2) ){ - z += 2; - } - break; - case 'c': - if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){ - z += 4; - } - break; - case 'e': - if( z[0]=='r' && m_gt_1(z+2) ){ - z += 2; - } - break; - case 'i': - if( z[0]=='c' && m_gt_1(z+2) ){ - z += 2; - } - break; - case 'l': - if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){ - z += 4; - } - break; - case 'n': - if( z[0]=='t' ){ - if( z[2]=='a' ){ - if( m_gt_1(z+3) ){ - z += 3; - } - }else if( z[2]=='e' ){ - stem(&z, "tneme", "", m_gt_1) || - stem(&z, "tnem", "", m_gt_1) || - stem(&z, "tne", "", m_gt_1); - } - } - break; - case 'o': - if( z[0]=='u' ){ - if( m_gt_1(z+2) ){ - z += 2; - } - }else if( z[3]=='s' || z[3]=='t' ){ - stem(&z, "noi", "", m_gt_1); - } - break; - case 's': - if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){ - z += 3; - } - break; - case 't': - stem(&z, "eta", "", m_gt_1) || - stem(&z, "iti", "", m_gt_1); - break; - case 'u': - if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){ - z += 3; - } - break; - case 'v': - case 'z': - if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){ - z += 3; - } - break; - } - - /* Step 5a */ - if( z[0]=='e' ){ - if( m_gt_1(z+1) ){ - z++; - }else if( m_eq_1(z+1) && !star_oh(z+1) ){ - z++; - } - } - - /* Step 5b */ - if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){ - z++; - } - - /* z[] is now the stemmed word in reverse order. Flip it back - ** around into forward order and return. - */ - *pnOut = i = strlen(z); - zOut[i] = 0; - while( *z ){ - zOut[--i] = *(z++); - } -} - -/* -** Characters that can be part of a token. We assume any character -** whose value is greater than 0x80 (any UTF character) can be -** part of a token. In other words, delimiters all must have -** values of 0x7f or lower. -*/ -static const char isIdChar[] = { -/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ -}; -#define idChar(C) (((ch=C)&0x80)!=0 || (ch>0x2f && isIdChar[ch-0x30])) -#define isDelim(C) (((ch=C)&0x80)==0 && (ch<0x30 || !isIdChar[ch-0x30])) - -/* -** Extract the next token from a tokenization cursor. The cursor must -** have been opened by a prior call to porterOpen(). -*/ -static int porterNext( - sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */ - const char **pzToken, /* OUT: *pzToken is the token text */ - int *pnBytes, /* OUT: Number of bytes in token */ - int *piStartOffset, /* OUT: Starting offset of token */ - int *piEndOffset, /* OUT: Ending offset of token */ - int *piPosition /* OUT: Position integer of token */ -){ - porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; - const char *z = c->zInput; - - while( c->iOffsetnInput ){ - int iStartOffset, ch; - - /* Scan past delimiter characters */ - while( c->iOffsetnInput && isDelim(z[c->iOffset]) ){ - c->iOffset++; - } - - /* Count non-delimiter characters. */ - iStartOffset = c->iOffset; - while( c->iOffsetnInput && !isDelim(z[c->iOffset]) ){ - c->iOffset++; - } - - if( c->iOffset>iStartOffset ){ - int n = c->iOffset-iStartOffset; - if( n>c->nAllocated ){ - c->nAllocated = n+20; - c->zToken = realloc(c->zToken, c->nAllocated); - if( c->zToken==NULL ) return SQLITE_NOMEM; - } - porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes); - *pzToken = c->zToken; - *piStartOffset = iStartOffset; - *piEndOffset = c->iOffset; - *piPosition = c->iToken++; - return SQLITE_OK; - } - } - return SQLITE_DONE; -} - -/* -** The set of routines that implement the porter-stemmer tokenizer -*/ -static const sqlite3_tokenizer_module porterTokenizerModule = { - 0, - porterCreate, - porterDestroy, - porterOpen, - porterClose, - porterNext, -}; - -/* -** Allocate a new porter tokenizer. Return a pointer to the new -** tokenizer in *ppModule -*/ -void sqlite3Fts1PorterTokenizerModule( - sqlite3_tokenizer_module const**ppModule -){ - *ppModule = &porterTokenizerModule; -} - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) */ diff --git a/ext/fts1/fts1_tokenizer.h b/ext/fts1/fts1_tokenizer.h deleted file mode 100644 index a48cb7451..000000000 --- a/ext/fts1/fts1_tokenizer.h +++ /dev/null @@ -1,90 +0,0 @@ -/* -** 2006 July 10 -** -** The author disclaims copyright to this source code. -** -************************************************************************* -** Defines the interface to tokenizers used by fulltext-search. There -** are three basic components: -** -** sqlite3_tokenizer_module is a singleton defining the tokenizer -** interface functions. This is essentially the class structure for -** tokenizers. -** -** sqlite3_tokenizer is used to define a particular tokenizer, perhaps -** including customization information defined at creation time. -** -** sqlite3_tokenizer_cursor is generated by a tokenizer to generate -** tokens from a particular input. -*/ -#ifndef _FTS1_TOKENIZER_H_ -#define _FTS1_TOKENIZER_H_ - -/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. -** If tokenizers are to be allowed to call sqlite3_*() functions, then -** we will need a way to register the API consistently. -*/ -#include "sqlite3.h" - -/* -** Structures used by the tokenizer interface. -*/ -typedef struct sqlite3_tokenizer sqlite3_tokenizer; -typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; -typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; - -struct sqlite3_tokenizer_module { - int iVersion; /* currently 0 */ - - /* - ** Create and destroy a tokenizer. argc/argv are passed down from - ** the fulltext virtual table creation to allow customization. - */ - int (*xCreate)(int argc, const char *const*argv, - sqlite3_tokenizer **ppTokenizer); - int (*xDestroy)(sqlite3_tokenizer *pTokenizer); - - /* - ** Tokenize a particular input. Call xOpen() to prepare to - ** tokenize, xNext() repeatedly until it returns SQLITE_DONE, then - ** xClose() to free any internal state. The pInput passed to - ** xOpen() must exist until the cursor is closed. The ppToken - ** result from xNext() is only valid until the next call to xNext() - ** or until xClose() is called. - */ - /* TODO(shess) current implementation requires pInput to be - ** nul-terminated. This should either be fixed, or pInput/nBytes - ** should be converted to zInput. - */ - int (*xOpen)(sqlite3_tokenizer *pTokenizer, - const char *pInput, int nBytes, - sqlite3_tokenizer_cursor **ppCursor); - int (*xClose)(sqlite3_tokenizer_cursor *pCursor); - int (*xNext)(sqlite3_tokenizer_cursor *pCursor, - const char **ppToken, int *pnBytes, - int *piStartOffset, int *piEndOffset, int *piPosition); -}; - -struct sqlite3_tokenizer { - const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ - /* Tokenizer implementations will typically add additional fields */ -}; - -struct sqlite3_tokenizer_cursor { - sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ - /* Tokenizer implementations will typically add additional fields */ -}; - -/* -** Get the module for a tokenizer which generates tokens based on a -** set of non-token characters. The default is to break tokens at any -** non-alnum character, though the set of delimiters can also be -** specified by the first argv argument to xCreate(). -*/ -/* TODO(shess) This doesn't belong here. Need some sort of -** registration process. -*/ -void sqlite3Fts1SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); -void sqlite3Fts1PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule); - -#endif /* _FTS1_TOKENIZER_H_ */ diff --git a/ext/fts1/fts1_tokenizer1.c b/ext/fts1/fts1_tokenizer1.c deleted file mode 100644 index f58fba8f8..000000000 --- a/ext/fts1/fts1_tokenizer1.c +++ /dev/null @@ -1,221 +0,0 @@ -/* -** The author disclaims copyright to this source code. -** -************************************************************************* -** Implementation of the "simple" full-text-search tokenizer. -*/ - -/* -** The code in this file is only compiled if: -** -** * The FTS1 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS1 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS1 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) - - -#include -#include -#include -#include -#include - -#include "fts1_tokenizer.h" - -typedef struct simple_tokenizer { - sqlite3_tokenizer base; - char delim[128]; /* flag ASCII delimiters */ -} simple_tokenizer; - -typedef struct simple_tokenizer_cursor { - sqlite3_tokenizer_cursor base; - const char *pInput; /* input we are tokenizing */ - int nBytes; /* size of the input */ - int iOffset; /* current position in pInput */ - int iToken; /* index of next token to be returned */ - char *pToken; /* storage for current token */ - int nTokenAllocated; /* space allocated to zToken buffer */ -} simple_tokenizer_cursor; - - -/* Forward declaration */ -static const sqlite3_tokenizer_module simpleTokenizerModule; - -static int isDelim(simple_tokenizer *t, unsigned char c){ - return c<0x80 && t->delim[c]; -} - -/* -** Create a new tokenizer instance. -*/ -static int simpleCreate( - int argc, const char * const *argv, - sqlite3_tokenizer **ppTokenizer -){ - simple_tokenizer *t; - - t = (simple_tokenizer *) calloc(sizeof(*t), 1); - if( t==NULL ) return SQLITE_NOMEM; - - /* TODO(shess) Delimiters need to remain the same from run to run, - ** else we need to reindex. One solution would be a meta-table to - ** track such information in the database, then we'd only want this - ** information on the initial create. - */ - if( argc>1 ){ - int i, n = strlen(argv[1]); - for(i=0; i=0x80 ){ - free(t); - return SQLITE_ERROR; - } - t->delim[ch] = 1; - } - } else { - /* Mark non-alphanumeric ASCII characters as delimiters */ - int i; - for(i=1; i<0x80; i++){ - t->delim[i] = !isalnum(i); - } - } - - *ppTokenizer = &t->base; - return SQLITE_OK; -} - -/* -** Destroy a tokenizer -*/ -static int simpleDestroy(sqlite3_tokenizer *pTokenizer){ - free(pTokenizer); - return SQLITE_OK; -} - -/* -** Prepare to begin tokenizing a particular string. The input -** string to be tokenized is pInput[0..nBytes-1]. A cursor -** used to incrementally tokenize this string is returned in -** *ppCursor. -*/ -static int simpleOpen( - sqlite3_tokenizer *pTokenizer, /* The tokenizer */ - const char *pInput, int nBytes, /* String to be tokenized */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ -){ - simple_tokenizer_cursor *c; - - c = (simple_tokenizer_cursor *) malloc(sizeof(*c)); - if( c==NULL ) return SQLITE_NOMEM; - - c->pInput = pInput; - if( pInput==0 ){ - c->nBytes = 0; - }else if( nBytes<0 ){ - c->nBytes = (int)strlen(pInput); - }else{ - c->nBytes = nBytes; - } - c->iOffset = 0; /* start tokenizing at the beginning */ - c->iToken = 0; - c->pToken = NULL; /* no space allocated, yet. */ - c->nTokenAllocated = 0; - - *ppCursor = &c->base; - return SQLITE_OK; -} - -/* -** Close a tokenization cursor previously opened by a call to -** simpleOpen() above. -*/ -static int simpleClose(sqlite3_tokenizer_cursor *pCursor){ - simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; - free(c->pToken); - free(c); - return SQLITE_OK; -} - -/* -** Extract the next token from a tokenization cursor. The cursor must -** have been opened by a prior call to simpleOpen(). -*/ -static int simpleNext( - sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */ - const char **ppToken, /* OUT: *ppToken is the token text */ - int *pnBytes, /* OUT: Number of bytes in token */ - int *piStartOffset, /* OUT: Starting offset of token */ - int *piEndOffset, /* OUT: Ending offset of token */ - int *piPosition /* OUT: Position integer of token */ -){ - simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; - simple_tokenizer *t = (simple_tokenizer *) pCursor->pTokenizer; - unsigned char *p = (unsigned char *)c->pInput; - - while( c->iOffsetnBytes ){ - int iStartOffset; - - /* Scan past delimiter characters */ - while( c->iOffsetnBytes && isDelim(t, p[c->iOffset]) ){ - c->iOffset++; - } - - /* Count non-delimiter characters. */ - iStartOffset = c->iOffset; - while( c->iOffsetnBytes && !isDelim(t, p[c->iOffset]) ){ - c->iOffset++; - } - - if( c->iOffset>iStartOffset ){ - int i, n = c->iOffset-iStartOffset; - if( n>c->nTokenAllocated ){ - c->nTokenAllocated = n+20; - c->pToken = realloc(c->pToken, c->nTokenAllocated); - if( c->pToken==NULL ) return SQLITE_NOMEM; - } - for(i=0; ipToken[i] = ch<0x80 ? tolower(ch) : ch; - } - *ppToken = c->pToken; - *pnBytes = n; - *piStartOffset = iStartOffset; - *piEndOffset = c->iOffset; - *piPosition = c->iToken++; - - return SQLITE_OK; - } - } - return SQLITE_DONE; -} - -/* -** The set of routines that implement the simple tokenizer -*/ -static const sqlite3_tokenizer_module simpleTokenizerModule = { - 0, - simpleCreate, - simpleDestroy, - simpleOpen, - simpleClose, - simpleNext, -}; - -/* -** Allocate a new simple tokenizer. Return a pointer to the new -** tokenizer in *ppModule -*/ -void sqlite3Fts1SimpleTokenizerModule( - sqlite3_tokenizer_module const**ppModule -){ - *ppModule = &simpleTokenizerModule; -} - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS1) */ diff --git a/ext/fts1/fulltext.c b/ext/fts1/fulltext.c deleted file mode 100644 index 313ff303e..000000000 --- a/ext/fts1/fulltext.c +++ /dev/null @@ -1,1511 +0,0 @@ -/* The author disclaims copyright to this source code. - * - * This is an SQLite module implementing full-text search. - */ - -#include -#if !defined(__APPLE__) -#include -#else -#include -#endif -#include -#include -#include - -#include "fulltext.h" -#include "ft_hash.h" -#include "tokenizer.h" -#include "sqlite3.h" -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT1 - -/* utility functions */ - -/* We encode variable-length integers in little-endian order using seven bits - * per byte as follows: -** -** KEY: -** A = 0xxxxxxx 7 bits of data and one flag bit -** B = 1xxxxxxx 7 bits of data and one flag bit -** -** 7 bits - A -** 14 bits - BA -** 21 bits - BBA -** and so on. -*/ - -/* We may need up to VARINT_MAX bytes to store an encoded 64-bit integer. */ -#define VARINT_MAX 10 - -/* Write a 64-bit variable-length integer to memory starting at p[0]. - * The length of data written will be between 1 and VARINT_MAX bytes. - * The number of bytes written is returned. */ -static int putVarint(char *p, sqlite_int64 v){ - unsigned char *q = (unsigned char *) p; - sqlite_uint64 vu = v; - do{ - *q++ = (unsigned char) ((vu & 0x7f) | 0x80); - vu >>= 7; - }while( vu!=0 ); - q[-1] &= 0x7f; /* turn off high bit in final byte */ - assert( q - (unsigned char *)p <= VARINT_MAX ); - return (int) (q - (unsigned char *)p); -} - -/* Read a 64-bit variable-length integer from memory starting at p[0]. - * Return the number of bytes read, or 0 on error. - * The value is stored in *v. */ -static int getVarint(const char *p, sqlite_int64 *v){ - const unsigned char *q = (const unsigned char *) p; - sqlite_uint64 x = 0, y = 1; - while( (*q & 0x80) == 0x80 ){ - x += y * (*q++ & 0x7f); - y <<= 7; - if( q - (unsigned char *)p >= VARINT_MAX ){ /* bad data */ - assert( 0 ); - return 0; - } - } - x += y * (*q++); - *v = (sqlite_int64) x; - return (int) (q - (unsigned char *)p); -} - -static int getVarint32(const char *p, int *pi){ - sqlite_int64 i; - int ret = getVarint(p, &i); - *pi = (int) i; - assert( *pi==i ); - return ret; -} - -/*** Document lists *** - * - * A document list holds a sorted list of varint-encoded document IDs. - * - * A doclist with type DL_POSITIONS_OFFSETS is stored like this: - * - * array { - * varint docid; - * array { - * varint position; (delta from previous position plus 1, or 0 for end) - * varint startOffset; (delta from previous startOffset) - * varint endOffset; (delta from startOffset) - * } - * } - * - * Here, array { X } means zero or more occurrences of X, adjacent in memory. - * - * A doclist with type DL_POSITIONS is like the above, but holds only docids - * and positions without offset information. - * - * A doclist with type DL_DOCIDS is like the above, but holds only docids - * without positions or offset information. - * - * On disk, every document list has positions and offsets, so we don't bother - * to serialize a doclist's type. - * - * We don't yet delta-encode document IDs; doing so will probably be a - * modest win. - * - * NOTE(shess) I've thought of a slightly (1%) better offset encoding. - * After the first offset, estimate the next offset by using the - * current token position and the previous token position and offset, - * offset to handle some variance. So the estimate would be - * (iPosition*w->iStartOffset/w->iPosition-64), which is delta-encoded - * as normal. Offsets more than 64 chars from the estimate are - * encoded as the delta to the previous start offset + 128. An - * additional tiny increment can be gained by using the end offset of - * the previous token to make the estimate a tiny bit more precise. -*/ - -typedef enum DocListType { - DL_DOCIDS, /* docids only */ - DL_POSITIONS, /* docids + positions */ - DL_POSITIONS_OFFSETS /* docids + positions + offsets */ -} DocListType; - -typedef struct DocList { - char *pData; - int nData; - DocListType iType; - int iLastPos; /* the last position written */ - int iLastOffset; /* the last start offset written */ -} DocList; - -/* Initialize a new DocList to hold the given data. */ -static void docListInit(DocList *d, DocListType iType, - const char *pData, int nData){ - d->nData = nData; - if( nData>0 ){ - d->pData = malloc(nData); - memcpy(d->pData, pData, nData); - } else { - d->pData = NULL; - } - d->iType = iType; - d->iLastPos = 0; - d->iLastOffset = 0; -} - -/* Create a new dynamically-allocated DocList. */ -static DocList *docListNew(DocListType iType){ - DocList *d = (DocList *) malloc(sizeof(DocList)); - docListInit(d, iType, 0, 0); - return d; -} - -static void docListDestroy(DocList *d){ - free(d->pData); -#ifndef NDEBUG - memset(d, 0x55, sizeof(*d)); -#endif -} - -static void docListDelete(DocList *d){ - docListDestroy(d); - free(d); -} - -static char *docListEnd(DocList *d){ - return d->pData + d->nData; -} - -/* Append a varint to a DocList's data. */ -static void appendVarint(DocList *d, sqlite_int64 i){ - char c[VARINT_MAX]; - int n = putVarint(c, i); - d->pData = realloc(d->pData, d->nData + n); - memcpy(d->pData + d->nData, c, n); - d->nData += n; -} - -static void docListAddDocid(DocList *d, sqlite_int64 iDocid){ - appendVarint(d, iDocid); - d->iLastPos = 0; -} - -/* Add a position to the last position list in a doclist. */ -static void docListAddPos(DocList *d, int iPos){ - assert( d->iType>=DL_POSITIONS ); - appendVarint(d, iPos-d->iLastPos+1); - d->iLastPos = iPos; -} - -static void docListAddPosOffset(DocList *d, int iPos, - int iStartOffset, int iEndOffset){ - assert( d->iType==DL_POSITIONS_OFFSETS ); - docListAddPos(d, iPos); - appendVarint(d, iStartOffset-d->iLastOffset); - d->iLastOffset = iStartOffset; - appendVarint(d, iEndOffset-iStartOffset); -} - -/* Terminate the last position list in the given doclist. */ -static void docListAddEndPos(DocList *d){ - appendVarint(d, 0); -} - -typedef struct DocListReader { - DocList *pDoclist; - char *p; - int iLastPos; /* the last position read */ -} DocListReader; - -static void readerInit(DocListReader *r, DocList *pDoclist){ - r->pDoclist = pDoclist; - if( pDoclist!=NULL ){ - r->p = pDoclist->pData; - } - r->iLastPos = 0; -} - -static int readerAtEnd(DocListReader *pReader){ - return pReader->p >= docListEnd(pReader->pDoclist); -} - -/* Peek at the next docid without advancing the read pointer. */ -static sqlite_int64 peekDocid(DocListReader *pReader){ - sqlite_int64 ret; - assert( !readerAtEnd(pReader) ); - getVarint(pReader->p, &ret); - return ret; -} - -/* Read the next docid. */ -static sqlite_int64 readDocid(DocListReader *pReader){ - sqlite_int64 ret; - assert( !readerAtEnd(pReader) ); - pReader->p += getVarint(pReader->p, &ret); - pReader->iLastPos = 0; - return ret; -} - -/* Read the next position from a position list. - * Returns the position, or -1 at the end of the list. */ -static int readPosition(DocListReader *pReader){ - int i; - int iType = pReader->pDoclist->iType; - assert( iType>=DL_POSITIONS ); - assert( !readerAtEnd(pReader) ); - - pReader->p += getVarint32(pReader->p, &i); - if( i==0 ){ - pReader->iLastPos = -1; - return -1; - } - pReader->iLastPos += ((int) i)-1; - if( iType>=DL_POSITIONS_OFFSETS ){ - /* Skip over offsets, ignoring them for now. */ - int iStart, iEnd; - pReader->p += getVarint32(pReader->p, &iStart); - pReader->p += getVarint32(pReader->p, &iEnd); - } - return pReader->iLastPos; -} - -/* Skip past the end of a position list. */ -static void skipPositionList(DocListReader *pReader){ - while( readPosition(pReader)!=-1 ) - ; -} - -/* Skip over a docid, including its position list if the doclist has - * positions. */ -static void skipDocument(DocListReader *pReader){ - readDocid(pReader); - if( pReader->pDoclist->iType >= DL_POSITIONS ){ - skipPositionList(pReader); - } -} - -static sqlite_int64 firstDocid(DocList *d){ - DocListReader r; - readerInit(&r, d); - return readDocid(&r); -} - -/* Doclist multi-tool. Pass pUpdate==NULL to delete the indicated docid; - * otherwise pUpdate, which must contain only the single docid [iDocid], is - * inserted (if not present) or updated (if already present). */ -static int docListUpdate(DocList *d, sqlite_int64 iDocid, DocList *pUpdate){ - int modified = 0; - DocListReader reader; - char *p; - - if( pUpdate!=NULL ){ - assert( d->iType==pUpdate->iType); - assert( iDocid==firstDocid(pUpdate) ); - } - - readerInit(&reader, d); - while( !readerAtEnd(&reader) && peekDocid(&reader)nData -= (reader.p - p); - modified = 1; - } - - /* Insert if indicated. */ - if( pUpdate!=NULL ){ - int iDoclist = p-d->pData; - docListAddEndPos(pUpdate); - - d->pData = realloc(d->pData, d->nData+pUpdate->nData); - p = d->pData + iDoclist; - - memmove(p+pUpdate->nData, p, docListEnd(d) - p); - memcpy(p, pUpdate->pData, pUpdate->nData); - d->nData += pUpdate->nData; - modified = 1; - } - - return modified; -} - -/* Split the second half of doclist d into a separate doclist d2. Returns 1 - * if successful, or 0 if d contains a single document and hence can't be - * split. */ -static int docListSplit(DocList *d, DocList *d2){ - const char *pSplitPoint = d->pData + d->nData / 2; - DocListReader reader; - - readerInit(&reader, d); - while( reader.piType, reader.p, docListEnd(d) - reader.p); - d->nData = reader.p - d->pData; - d->pData = realloc(d->pData, d->nData); - return 1; -} - -/* A DocListMerge computes the AND of an in-memory DocList [in] and a chunked - * on-disk doclist, resulting in another in-memory DocList [out]. [in] - * and [out] may or may not store position information according to the - * caller's wishes. The on-disk doclist always comes with positions. - * - * The caller must read each chunk of the on-disk doclist in succession and - * pass it to mergeBlock(). - * - * If [in] has positions, then the merge output contains only documents with - * matching positions in the two input doclists. If [in] does not have - * positions, then the merge output contains all documents common to the two - * input doclists. - * - * If [in] is NULL, then the on-disk doclist is copied to [out] directly. - * - * A merge is performed using an integer [iOffset] provided by the caller. - * [iOffset] is subtracted from each position in the on-disk doclist for the - * purpose of position comparison; this is helpful in implementing phrase - * searches. - * - * A DocListMerge is not yet able to propagate offsets through query - * processing; we should add that capability soon. -*/ -typedef struct DocListMerge { - DocListReader in; - DocList *pOut; - int iOffset; -} DocListMerge; - -static void mergeInit(DocListMerge *m, - DocList *pIn, int iOffset, DocList *pOut){ - readerInit(&m->in, pIn); - m->pOut = pOut; - m->iOffset = iOffset; - - /* can't handle offsets yet */ - assert( pIn==NULL || pIn->iType <= DL_POSITIONS ); - assert( pOut->iType <= DL_POSITIONS ); -} - -/* A helper function for mergeBlock(), below. Merge the position lists - * pointed to by m->in and pBlockReader. - * If the merge matches, write [iDocid] to m->pOut; if m->pOut - * has positions then write all matching positions as well. */ -static void mergePosList(DocListMerge *m, sqlite_int64 iDocid, - DocListReader *pBlockReader){ - int block_pos = readPosition(pBlockReader); - int in_pos = readPosition(&m->in); - int match = 0; - while( block_pos!=-1 || in_pos!=-1 ){ - if( block_pos-m->iOffset==in_pos ){ - if( !match ){ - docListAddDocid(m->pOut, iDocid); - match = 1; - } - if( m->pOut->iType >= DL_POSITIONS ){ - docListAddPos(m->pOut, in_pos); - } - block_pos = readPosition(pBlockReader); - in_pos = readPosition(&m->in); - } else if( in_pos==-1 || (block_pos!=-1 && block_pos-m->iOffsetin); - } - } - if( m->pOut->iType >= DL_POSITIONS && match ){ - docListAddEndPos(m->pOut); - } -} - -/* Merge one block of an on-disk doclist into a DocListMerge. */ -static void mergeBlock(DocListMerge *m, DocList *pBlock){ - DocListReader blockReader; - assert( pBlock->iType >= DL_POSITIONS ); - readerInit(&blockReader, pBlock); - while( !readerAtEnd(&blockReader) ){ - sqlite_int64 iDocid = readDocid(&blockReader); - if( m->in.pDoclist!=NULL ){ - while( 1 ){ - if( readerAtEnd(&m->in) ) return; /* nothing more to merge */ - if( peekDocid(&m->in)>=iDocid ) break; - skipDocument(&m->in); - } - if( peekDocid(&m->in)>iDocid ){ /* [pIn] has no match with iDocid */ - skipPositionList(&blockReader); /* skip this docid in the block */ - continue; - } - readDocid(&m->in); - } - /* We have a document match. */ - if( m->in.pDoclist==NULL || m->in.pDoclist->iType < DL_POSITIONS ){ - /* We don't need to do a poslist merge. */ - docListAddDocid(m->pOut, iDocid); - if( m->pOut->iType >= DL_POSITIONS ){ - /* Copy all positions to the output doclist. */ - while( 1 ){ - int pos = readPosition(&blockReader); - if( pos==-1 ) break; - docListAddPos(m->pOut, pos); - } - docListAddEndPos(m->pOut); - } else skipPositionList(&blockReader); - continue; - } - mergePosList(m, iDocid, &blockReader); - } -} - -static char *string_dup_n(const char *s, int n){ - char *str = malloc(n + 1); - memcpy(str, s, n); - str[n] = '\0'; - return str; -} - -/* Duplicate a string; the caller must free() the returned string. - * (We don't use strdup() since it's not part of the standard C library and - * may not be available everywhere.) */ -static char *string_dup(const char *s){ - return string_dup_n(s, strlen(s)); -} - -/* Format a string, replacing each occurrence of the % character with - * zName. This may be more convenient than sqlite_mprintf() - * when one string is used repeatedly in a format string. - * The caller must free() the returned string. */ -static char *string_format(const char *zFormat, const char *zName){ - const char *p; - size_t len = 0; - size_t nName = strlen(zName); - char *result; - char *r; - - /* first compute length needed */ - for(p = zFormat ; *p ; ++p){ - len += (*p=='%' ? nName : 1); - } - len += 1; /* for null terminator */ - - r = result = malloc(len); - for(p = zFormat; *p; ++p){ - if( *p=='%' ){ - memcpy(r, zName, nName); - r += nName; - } else { - *r++ = *p; - } - } - *r++ = '\0'; - assert( r == result + len ); - return result; -} - -static int sql_exec(sqlite3 *db, const char *zName, const char *zFormat){ - char *zCommand = string_format(zFormat, zName); - int rc = sqlite3_exec(db, zCommand, NULL, 0, NULL); - free(zCommand); - return rc; -} - -static int sql_prepare(sqlite3 *db, const char *zName, sqlite3_stmt **ppStmt, - const char *zFormat){ - char *zCommand = string_format(zFormat, zName); - int rc = sqlite3_prepare(db, zCommand, -1, ppStmt, NULL); - free(zCommand); - return rc; -} - -/* end utility functions */ - -#define QUERY_GENERIC 0 -#define QUERY_FULLTEXT 1 - -#define CHUNK_MAX 1024 - -typedef enum fulltext_statement { - CONTENT_INSERT_STMT, - CONTENT_SELECT_STMT, - CONTENT_DELETE_STMT, - - TERM_SELECT_STMT, - TERM_CHUNK_SELECT_STMT, - TERM_INSERT_STMT, - TERM_UPDATE_STMT, - TERM_DELETE_STMT, - - MAX_STMT /* Always at end! */ -} fulltext_statement; - -/* These must exactly match the enum above. */ -/* TODO(adam): Is there some risk that a statement (in particular, -** pTermSelectStmt) will be used in two cursors at once, e.g. if a -** query joins a virtual table to itself? If so perhaps we should -** move some of these to the cursor object. -*/ -static const char *fulltext_zStatement[MAX_STMT] = { - /* CONTENT_INSERT */ "insert into %_content (rowid, content) values (?, ?)", - /* CONTENT_SELECT */ "select content from %_content where rowid = ?", - /* CONTENT_DELETE */ "delete from %_content where rowid = ?", - - /* TERM_SELECT */ - "select rowid, doclist from %_term where term = ? and first = ?", - /* TERM_CHUNK_SELECT */ - "select max(first) from %_term where term = ? and first <= ?", - /* TERM_INSERT */ - "insert into %_term (term, first, doclist) values (?, ?, ?)", - /* TERM_UPDATE */ "update %_term set doclist = ? where rowid = ?", - /* TERM_DELETE */ "delete from %_term where rowid = ?", -}; - -typedef struct fulltext_vtab { - sqlite3_vtab base; - sqlite3 *db; - const char *zName; /* virtual table name */ - sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ - - /* Precompiled statements which we keep as long as the table is - ** open. - */ - sqlite3_stmt *pFulltextStatements[MAX_STMT]; -} fulltext_vtab; - -typedef struct fulltext_cursor { - sqlite3_vtab_cursor base; - int iCursorType; /* QUERY_GENERIC or QUERY_FULLTEXT */ - - sqlite3_stmt *pStmt; - - int eof; - - /* The following is used only when iCursorType == QUERY_FULLTEXT. */ - DocListReader result; -} fulltext_cursor; - -static struct fulltext_vtab *cursor_vtab(fulltext_cursor *c){ - return (fulltext_vtab *) c->base.pVtab; -} - -static sqlite3_module fulltextModule; /* forward declaration */ - -/* Puts a freshly-prepared statement determined by iStmt in *ppStmt. -** If the indicated statement has never been prepared, it is prepared -** and cached, otherwise the cached version is reset. -*/ -static int sql_get_statement(fulltext_vtab *v, fulltext_statement iStmt, - sqlite3_stmt **ppStmt){ - assert( iStmtpFulltextStatements[iStmt]==NULL ){ - int rc = sql_prepare(v->db, v->zName, &v->pFulltextStatements[iStmt], - fulltext_zStatement[iStmt]); - if( rc!=SQLITE_OK ) return rc; - } else { - int rc = sqlite3_reset(v->pFulltextStatements[iStmt]); - if( rc!=SQLITE_OK ) return rc; - } - - *ppStmt = v->pFulltextStatements[iStmt]; - return SQLITE_OK; -} - -/* Step the indicated statement, handling errors SQLITE_BUSY (by -** retrying) and SQLITE_SCHEMA (by re-preparing and transferring -** bindings to the new statement). -** TODO(adam): We should extend this function so that it can work with -** statements declared locally, not only globally cached statements. -*/ -static int sql_step_statement(fulltext_vtab *v, fulltext_statement iStmt, - sqlite3_stmt **ppStmt){ - int rc; - sqlite3_stmt *s = *ppStmt; - assert( iStmtpFulltextStatements[iStmt] ); - - while( (rc=sqlite3_step(s))!=SQLITE_DONE && rc!=SQLITE_ROW ){ - sqlite3_stmt *pNewStmt; - - if( rc==SQLITE_BUSY ) continue; - if( rc!=SQLITE_ERROR ) return rc; - - rc = sqlite3_reset(s); - if( rc!=SQLITE_SCHEMA ) return SQLITE_ERROR; - - v->pFulltextStatements[iStmt] = NULL; /* Still in s */ - rc = sql_get_statement(v, iStmt, &pNewStmt); - if( rc!=SQLITE_OK ) goto err; - *ppStmt = pNewStmt; - - rc = sqlite3_transfer_bindings(s, pNewStmt); - if( rc!=SQLITE_OK ) goto err; - - rc = sqlite3_finalize(s); - if( rc!=SQLITE_OK ) return rc; - s = pNewStmt; - } - return rc; - - err: - sqlite3_finalize(s); - return rc; -} - -/* Like sql_step_statement(), but convert SQLITE_DONE to SQLITE_OK. -** Useful for statements like UPDATE, where we expect no results. -*/ -static int sql_single_step_statement(fulltext_vtab *v, - fulltext_statement iStmt, - sqlite3_stmt **ppStmt){ - int rc = sql_step_statement(v, iStmt, ppStmt); - return (rc==SQLITE_DONE) ? SQLITE_OK : rc; -} - -/* insert into %_content (rowid, content) values ([rowid], [zContent]) */ -static int content_insert(fulltext_vtab *v, sqlite3_value *rowid, - const char *zContent, int nContent){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, CONTENT_INSERT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_value(s, 1, rowid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_text(s, 2, zContent, nContent, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, CONTENT_INSERT_STMT, &s); -} - -/* select content from %_content where rowid = [iRow] - * The caller must delete the returned string. */ -static int content_select(fulltext_vtab *v, sqlite_int64 iRow, - char **pzContent){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, CONTENT_SELECT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iRow); - if( rc!=SQLITE_OK ) return rc; - - rc = sql_step_statement(v, CONTENT_SELECT_STMT, &s); - if( rc!=SQLITE_ROW ) return rc; - - *pzContent = string_dup((const char *)sqlite3_column_text(s, 0)); - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ) return SQLITE_OK; - - free(*pzContent); - return rc; -} - -/* delete from %_content where rowid = [iRow ] */ -static int content_delete(fulltext_vtab *v, sqlite_int64 iRow){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, CONTENT_DELETE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iRow); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, CONTENT_DELETE_STMT, &s); -} - -/* select rowid, doclist from %_term where term = [zTerm] and first = [iFirst] - * If found, returns SQLITE_OK; the caller must free the returned doclist. - * If no rows found, returns SQLITE_ERROR. */ -static int term_select(fulltext_vtab *v, const char *zTerm, int nTerm, - sqlite_int64 iFirst, - sqlite_int64 *rowid, - DocList *out){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_SELECT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_text(s, 1, zTerm, nTerm, SQLITE_TRANSIENT); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 2, iFirst); - if( rc!=SQLITE_OK ) return rc; - - rc = sql_step_statement(v, TERM_SELECT_STMT, &s); - if( rc!=SQLITE_ROW ) return rc==SQLITE_DONE ? SQLITE_ERROR : rc; - - *rowid = sqlite3_column_int64(s, 0); - docListInit(out, DL_POSITIONS_OFFSETS, - sqlite3_column_blob(s, 1), sqlite3_column_bytes(s, 1)); - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - return rc==SQLITE_DONE ? SQLITE_OK : rc; -} - -/* select max(first) from %_term where term = [zTerm] and first <= [iFirst] - * If found, returns SQLITE_ROW and result in *piResult; if the query returns - * NULL (meaning no row found) returns SQLITE_DONE. - */ -static int term_chunk_select(fulltext_vtab *v, const char *zTerm, int nTerm, - sqlite_int64 iFirst, sqlite_int64 *piResult){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_CHUNK_SELECT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_text(s, 1, zTerm, nTerm, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 2, iFirst); - if( rc!=SQLITE_OK ) return rc; - - rc = sql_step_statement(v, TERM_CHUNK_SELECT_STMT, &s); - if( rc!=SQLITE_ROW ) return rc==SQLITE_DONE ? SQLITE_ERROR : rc; - - switch( sqlite3_column_type(s, 0) ){ - case SQLITE_NULL: - rc = SQLITE_DONE; - break; - case SQLITE_INTEGER: - *piResult = sqlite3_column_int64(s, 0); - break; - default: - return SQLITE_ERROR; - } - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - if( sqlite3_step(s) != SQLITE_DONE ) return SQLITE_ERROR; - return rc; -} - -/* insert into %_term (term, first, doclist) - values ([zTerm], [iFirst], [doclist]) */ -static int term_insert(fulltext_vtab *v, const char *zTerm, int nTerm, - sqlite_int64 iFirst, DocList *doclist){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_INSERT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_text(s, 1, zTerm, nTerm, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 2, iFirst); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_blob(s, 3, doclist->pData, doclist->nData, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, TERM_INSERT_STMT, &s); -} - -/* update %_term set doclist = [doclist] where rowid = [rowid] */ -static int term_update(fulltext_vtab *v, sqlite_int64 rowid, - DocList *doclist){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_UPDATE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_blob(s, 1, doclist->pData, doclist->nData, - SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 2, rowid); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, TERM_UPDATE_STMT, &s); -} - -static int term_delete(fulltext_vtab *v, sqlite_int64 rowid){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, TERM_DELETE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, rowid); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step_statement(v, TERM_DELETE_STMT, &s); -} - -static void fulltext_vtab_destroy(fulltext_vtab *v){ - int iStmt; - - for( iStmt=0; iStmtpFulltextStatements[iStmt]!=NULL ){ - sqlite3_finalize(v->pFulltextStatements[iStmt]); - v->pFulltextStatements[iStmt] = NULL; - } - } - - if( v->pTokenizer!=NULL ){ - v->pTokenizer->pModule->xDestroy(v->pTokenizer); - v->pTokenizer = NULL; - } - - free((void *) v->zName); - free(v); -} - -/* Current interface: -** argv[0] - module name -** argv[1] - database name -** argv[2] - table name -** argv[3] - tokenizer name (optional, a sensible default is provided) -** argv[4..] - passed to tokenizer (optional based on tokenizer) -**/ -static int fulltextConnect( - sqlite3 *db, - void *pAux, - int argc, - const char * const *argv, - sqlite3_vtab **ppVTab, - char **pzErr -){ - int rc; - fulltext_vtab *v; - sqlite3_tokenizer_module *m = NULL; - - assert( argc>=3 ); - v = (fulltext_vtab *) malloc(sizeof(fulltext_vtab)); - /* sqlite will initialize v->base */ - v->db = db; - v->zName = string_dup(argv[2]); - v->pTokenizer = NULL; - - if( argc==3 ){ - get_simple_tokenizer_module(&m); - } else { - /* TODO(shess) For now, add new tokenizers as else if clauses. */ - if( !strcmp(argv[3], "simple") ){ - get_simple_tokenizer_module(&m); - } else { - assert( "unrecognized tokenizer"==NULL ); - } - } - - /* TODO(shess) Since tokenization impacts the index, the parameters - ** to the tokenizer need to be identical when a persistent virtual - ** table is re-created. One solution would be a meta-table to track - ** such information in the database. Then we could verify that the - ** information is identical on subsequent creates. - */ - /* TODO(shess) Why isn't argv already (const char **)? */ - rc = m->xCreate(argc-3, (const char **) (argv+3), &v->pTokenizer); - if( rc!=SQLITE_OK ) return rc; - v->pTokenizer->pModule = m; - - /* TODO: verify the existence of backing tables foo_content, foo_term */ - - rc = sqlite3_declare_vtab(db, "create table x(content text)"); - if( rc!=SQLITE_OK ) return rc; - - memset(v->pFulltextStatements, 0, sizeof(v->pFulltextStatements)); - - *ppVTab = &v->base; - return SQLITE_OK; -} - -static int fulltextCreate( - sqlite3 *db, - void *pAux, - int argc, - const char * const *argv, - sqlite3_vtab **ppVTab, - char **pzErr -){ - int rc; - assert( argc>=3 ); - - /* The %_content table holds the text of each full-text item, with - ** the rowid used as the docid. - ** - ** The %_term table maps each term to a document list blob - ** containing elements sorted by ascending docid, each element - ** encoded as: - ** - ** docid varint-encoded - ** token count varint-encoded - ** "count" token elements (poslist): - ** position varint-encoded as delta from previous position - ** start offset varint-encoded as delta from previous start offset - ** end offset varint-encoded as delta from start offset - ** - ** Additionally, doclist blobs can be chunked into multiple rows, - ** using "first" to order the blobs. "first" is simply the first - ** docid in the blob. - */ - /* - ** NOTE(shess) That last sentence is incorrect in the face of - ** deletion, which can leave a doclist that doesn't contain the - ** first from that row. I _believe_ this does not matter to the - ** operation of the system, but it might be reasonable to update - ** appropriately in case this assumption becomes more important. - */ - rc = sql_exec(db, argv[2], - "create table %_content(content text);" - "create table %_term(term text, first integer, doclist blob);" - "create index %_index on %_term(term, first)"); - if( rc!=SQLITE_OK ) return rc; - - return fulltextConnect(db, pAux, argc, argv, ppVTab, pzErr); -} - -/* Decide how to handle an SQL query. - * At the moment, MATCH queries can include implicit boolean ANDs; we - * haven't implemented phrase searches or OR yet. */ -static int fulltextBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ - int i; - - for(i=0; inConstraint; ++i){ - const struct sqlite3_index_constraint *pConstraint; - pConstraint = &pInfo->aConstraint[i]; - if( pConstraint->iColumn==0 && - pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH && - pConstraint->usable ){ /* a full-text search */ - pInfo->aConstraintUsage[i].argvIndex = 1; - pInfo->aConstraintUsage[i].omit = 1; - pInfo->idxNum = QUERY_FULLTEXT; - pInfo->estimatedCost = 1.0; /* an arbitrary value for now */ - return SQLITE_OK; - } - } - pInfo->idxNum = QUERY_GENERIC; - return SQLITE_OK; -} - -static int fulltextDisconnect(sqlite3_vtab *pVTab){ - fulltext_vtab_destroy((fulltext_vtab *)pVTab); - return SQLITE_OK; -} - -static int fulltextDestroy(sqlite3_vtab *pVTab){ - fulltext_vtab *v = (fulltext_vtab *)pVTab; - - int rc = sql_exec(v->db, v->zName, - "drop table %_content; drop table %_term"); - if( rc!=SQLITE_OK ) return rc; - - fulltext_vtab_destroy((fulltext_vtab *)pVTab); - return SQLITE_OK; -} - -static int fulltextOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ - fulltext_cursor *c; - - c = (fulltext_cursor *) calloc(sizeof(fulltext_cursor), 1); - /* sqlite will initialize c->base */ - *ppCursor = &c->base; - - return SQLITE_OK; -} - -static int fulltextClose(sqlite3_vtab_cursor *pCursor){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - sqlite3_finalize(c->pStmt); - if( c->result.pDoclist!=NULL ){ - docListDelete(c->result.pDoclist); - } - free(c); - return SQLITE_OK; -} - -static int fulltextNext(sqlite3_vtab_cursor *pCursor){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - sqlite_int64 iDocid; - int rc; - - switch( c->iCursorType ){ - case QUERY_GENERIC: - /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */ - rc = sqlite3_step(c->pStmt); - switch( rc ){ - case SQLITE_ROW: - c->eof = 0; - return SQLITE_OK; - case SQLITE_DONE: - c->eof = 1; - return SQLITE_OK; - default: - c->eof = 1; - return rc; - } - case QUERY_FULLTEXT: - rc = sqlite3_reset(c->pStmt); - if( rc!=SQLITE_OK ) return rc; - - if( readerAtEnd(&c->result)){ - c->eof = 1; - return SQLITE_OK; - } - iDocid = readDocid(&c->result); - rc = sqlite3_bind_int64(c->pStmt, 1, iDocid); - if( rc!=SQLITE_OK ) return rc; - /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */ - rc = sqlite3_step(c->pStmt); - if( rc==SQLITE_ROW ){ /* the case we expect */ - c->eof = 0; - return SQLITE_OK; - } - /* an error occurred; abort */ - return rc==SQLITE_DONE ? SQLITE_ERROR : rc; - default: - assert( 0 ); - return SQLITE_ERROR; /* not reached */ - } -} - -static int term_select_doclist(fulltext_vtab *v, const char *pTerm, int nTerm, - sqlite3_stmt **ppStmt){ - int rc; - if( *ppStmt ){ - rc = sqlite3_reset(*ppStmt); - } else { - rc = sql_prepare(v->db, v->zName, ppStmt, - "select doclist from %_term where term = ? order by first"); - } - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_text(*ppStmt, 1, pTerm, nTerm, SQLITE_TRANSIENT); - if( rc!=SQLITE_OK ) return rc; - - return sqlite3_step(*ppStmt); /* TODO(adamd): handle schema error */ -} - -/* Read the posting list for [zTerm]; AND it with the doclist [in] to - * produce the doclist [out], using the given offset [iOffset] for phrase - * matching. - * (*pSelect) is used to hold an SQLite statement used inside this function; - * the caller should initialize *pSelect to NULL before the first call. - */ -static int query_merge(fulltext_vtab *v, sqlite3_stmt **pSelect, - const char *zTerm, - DocList *pIn, int iOffset, DocList *out){ - int rc; - DocListMerge merge; - - if( pIn!=NULL && !pIn->nData ){ - /* If [pIn] is already empty, there's no point in reading the - * posting list to AND it in; return immediately. */ - return SQLITE_OK; - } - - rc = term_select_doclist(v, zTerm, -1, pSelect); - if( rc!=SQLITE_ROW && rc!=SQLITE_DONE ) return rc; - - mergeInit(&merge, pIn, iOffset, out); - while( rc==SQLITE_ROW ){ - DocList block; - docListInit(&block, DL_POSITIONS_OFFSETS, - sqlite3_column_blob(*pSelect, 0), - sqlite3_column_bytes(*pSelect, 0)); - mergeBlock(&merge, &block); - docListDestroy(&block); - - rc = sqlite3_step(*pSelect); - if( rc!=SQLITE_ROW && rc!=SQLITE_DONE ){ - return rc; - } - } - - return SQLITE_OK; -} - -typedef struct QueryTerm { - int is_phrase; /* true if this term begins a new phrase */ - const char *zTerm; -} QueryTerm; - -/* A parsed query. - * - * As an example, parsing the query ["four score" years "new nation"] will - * yield a Query with 5 terms: - * "four", is_phrase = 1 - * "score", is_phrase = 0 - * "years", is_phrase = 1 - * "new", is_phrase = 1 - * "nation", is_phrase = 0 - */ -typedef struct Query { - int nTerms; - QueryTerm *pTerm; -} Query; - -static void query_add(Query *q, int is_phrase, const char *zTerm){ - QueryTerm *t; - ++q->nTerms; - q->pTerm = realloc(q->pTerm, q->nTerms * sizeof(q->pTerm[0])); - t = &q->pTerm[q->nTerms - 1]; - t->is_phrase = is_phrase; - t->zTerm = zTerm; -} - -static void query_free(Query *q){ - int i; - for(i = 0; i < q->nTerms; ++i){ - free((void *) q->pTerm[i].zTerm); - } - free(q->pTerm); -} - -static int tokenize_segment(sqlite3_tokenizer *pTokenizer, - const char *zQuery, int in_phrase, - Query *pQuery){ - sqlite3_tokenizer_module *pModule = pTokenizer->pModule; - sqlite3_tokenizer_cursor *pCursor; - int is_first = 1; - - int rc = pModule->xOpen(pTokenizer, zQuery, -1, &pCursor); - if( rc!=SQLITE_OK ) return rc; - pCursor->pTokenizer = pTokenizer; - - while( 1 ){ - const char *zToken; - int nToken, iStartOffset, iEndOffset, dummy_pos; - - rc = pModule->xNext(pCursor, - &zToken, &nToken, - &iStartOffset, &iEndOffset, - &dummy_pos); - if( rc!=SQLITE_OK ) break; - query_add(pQuery, !in_phrase || is_first, string_dup_n(zToken, nToken)); - is_first = 0; - } - - return pModule->xClose(pCursor); -} - -/* Parse a query string, yielding a Query object. */ -static int parse_query(fulltext_vtab *v, const char *zQuery, Query *pQuery){ - char *zQuery1 = string_dup(zQuery); - int in_phrase = 0; - char *s = zQuery1; - pQuery->nTerms = 0; - pQuery->pTerm = NULL; - - while( *s ){ - char *t = s; - while( *t ){ - if( *t=='"' ){ - *t++ = '\0'; - break; - } - ++t; - } - if( *s ){ - tokenize_segment(v->pTokenizer, s, in_phrase, pQuery); - } - s = t; - in_phrase = !in_phrase; - } - - free(zQuery1); - return SQLITE_OK; -} - -/* Perform a full-text query; return a list of documents in [pResult]. */ -static int fulltext_query(fulltext_vtab *v, const char *zQuery, - DocList **pResult){ - Query q; - int phrase_start = -1; - int i; - sqlite3_stmt *pSelect = NULL; - DocList *d = NULL; - - int rc = parse_query(v, zQuery, &q); - if( rc!=SQLITE_OK ) return rc; - - /* Merge terms. */ - for(i = 0 ; i < q.nTerms ; ++i){ - /* In each merge step, we need to generate positions whenever we're - * processing a phrase which hasn't ended yet. */ - int need_positions = iiCursorType = idxNum; - switch( idxNum ){ - case QUERY_GENERIC: - zStatement = "select rowid, content from %_content"; - break; - - case QUERY_FULLTEXT: /* full-text search */ - { - const char *zQuery = (const char *)sqlite3_value_text(argv[0]); - DocList *pResult; - assert( argc==1 ); - rc = fulltext_query(v, zQuery, &pResult); - if( rc!=SQLITE_OK ) return rc; - readerInit(&c->result, pResult); - zStatement = "select rowid, content from %_content where rowid = ?"; - break; - } - - default: - assert( 0 ); - } - - rc = sql_prepare(v->db, v->zName, &c->pStmt, zStatement); - if( rc!=SQLITE_OK ) return rc; - - return fulltextNext(pCursor); -} - -static int fulltextEof(sqlite3_vtab_cursor *pCursor){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - return c->eof; -} - -static int fulltextColumn(sqlite3_vtab_cursor *pCursor, - sqlite3_context *pContext, int idxCol){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - const char *s; - - assert( idxCol==0 ); - s = (const char *) sqlite3_column_text(c->pStmt, 1); - sqlite3_result_text(pContext, s, -1, SQLITE_TRANSIENT); - - return SQLITE_OK; -} - -static int fulltextRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - - *pRowid = sqlite3_column_int64(c->pStmt, 0); - return SQLITE_OK; -} - -/* Build a hash table containing all terms in zText. */ -static int build_terms(Hash *terms, sqlite3_tokenizer *pTokenizer, - const char *zText, sqlite_int64 iDocid){ - sqlite3_tokenizer_cursor *pCursor; - const char *pToken; - int nTokenBytes; - int iStartOffset, iEndOffset, iPosition; - - int rc = pTokenizer->pModule->xOpen(pTokenizer, zText, -1, &pCursor); - if( rc!=SQLITE_OK ) return rc; - - pCursor->pTokenizer = pTokenizer; - HashInit(terms, HASH_STRING, 1); - while( SQLITE_OK==pTokenizer->pModule->xNext(pCursor, - &pToken, &nTokenBytes, - &iStartOffset, &iEndOffset, - &iPosition) ){ - DocList *p; - - /* Positions can't be negative; we use -1 as a terminator internally. */ - if( iPosition<0 ) { - rc = SQLITE_ERROR; - goto err; - } - - p = HashFind(terms, pToken, nTokenBytes); - if( p==NULL ){ - p = docListNew(DL_POSITIONS_OFFSETS); - docListAddDocid(p, iDocid); - HashInsert(terms, pToken, nTokenBytes, p); - } - docListAddPosOffset(p, iPosition, iStartOffset, iEndOffset); - } - -err: - /* TODO(shess) Check return? Should this be able to cause errors at - ** this point? Actually, same question about sqlite3_finalize(), - ** though one could argue that failure there means that the data is - ** not durable. *ponder* - */ - pTokenizer->pModule->xClose(pCursor); - return rc; -} -/* Update the %_terms table to map the term [zTerm] to the given rowid. */ -static int index_insert_term(fulltext_vtab *v, const char *zTerm, int nTerm, - sqlite_int64 iDocid, DocList *p){ - sqlite_int64 iFirst; - sqlite_int64 iIndexRow; - DocList doclist; - - int rc = term_chunk_select(v, zTerm, nTerm, iDocid, &iFirst); - if( rc==SQLITE_DONE ){ - docListInit(&doclist, DL_POSITIONS_OFFSETS, 0, 0); - if( docListUpdate(&doclist, iDocid, p) ){ - rc = term_insert(v, zTerm, nTerm, iDocid, &doclist); - docListDestroy(&doclist); - return rc; - } - return SQLITE_OK; - } - if( rc!=SQLITE_ROW ) return SQLITE_ERROR; - - /* This word is in the index; add this document ID to its blob. */ - - rc = term_select(v, zTerm, nTerm, iFirst, &iIndexRow, &doclist); - if( rc!=SQLITE_OK ) return rc; - - if( docListUpdate(&doclist, iDocid, p) ){ - /* If the blob is too big, split it in half. */ - if( doclist.nData>CHUNK_MAX ){ - DocList half; - if( docListSplit(&doclist, &half) ){ - rc = term_insert(v, zTerm, nTerm, firstDocid(&half), &half); - docListDestroy(&half); - if( rc!=SQLITE_OK ) goto err; - } - } - rc = term_update(v, iIndexRow, &doclist); - } - -err: - docListDestroy(&doclist); - return rc; -} - -/* Insert a row into the full-text index; set *piRowid to be the ID of the - * new row. */ -static int index_insert(fulltext_vtab *v, - sqlite3_value *pRequestRowid, const char *zText, - sqlite_int64 *piRowid){ - Hash terms; /* maps term string -> PosList */ - HashElem *e; - - int rc = content_insert(v, pRequestRowid, zText, -1); - if( rc!=SQLITE_OK ) return rc; - *piRowid = sqlite3_last_insert_rowid(v->db); - - if( !zText ) return SQLITE_OK; /* nothing to index */ - - rc = build_terms(&terms, v->pTokenizer, zText, *piRowid); - if( rc!=SQLITE_OK ) return rc; - - for(e=HashFirst(&terms); e; e=HashNext(e)){ - DocList *p = HashData(e); - rc = index_insert_term(v, HashKey(e), HashKeysize(e), *piRowid, p); - if( rc!=SQLITE_OK ) break; - } - - for(e=HashFirst(&terms); e; e=HashNext(e)){ - DocList *p = HashData(e); - docListDelete(p); - } - HashClear(&terms); - return rc; -} - -static int index_delete_term(fulltext_vtab *v, const char *zTerm, int nTerm, - sqlite_int64 iDocid){ - sqlite_int64 iFirst; - sqlite_int64 iIndexRow; - DocList doclist; - - int rc = term_chunk_select(v, zTerm, nTerm, iDocid, &iFirst); - if( rc!=SQLITE_ROW ) return SQLITE_ERROR; - - rc = term_select(v, zTerm, nTerm, iFirst, &iIndexRow, &doclist); - if( rc!=SQLITE_OK ) return rc; - - if( docListUpdate(&doclist, iDocid, NULL) ){ - if( doclist.nData>0 ){ - rc = term_update(v, iIndexRow, &doclist); - } else { /* empty posting list */ - rc = term_delete(v, iIndexRow); - } - } - docListDestroy(&doclist); - return rc; -} - -/* Delete a row from the full-text index. */ -static int index_delete(fulltext_vtab *v, sqlite_int64 iRow){ - char *zText; - Hash terms; - HashElem *e; - - int rc = content_select(v, iRow, &zText); - if( rc!=SQLITE_OK ) return rc; - - rc = build_terms(&terms, v->pTokenizer, zText, iRow); - free(zText); - if( rc!=SQLITE_OK ) return rc; - - for(e=HashFirst(&terms); e; e=HashNext(e)){ - rc = index_delete_term(v, HashKey(e), HashKeysize(e), iRow); - if( rc!=SQLITE_OK ) break; - } - for(e=HashFirst(&terms); e; e=HashNext(e)){ - DocList *p = HashData(e); - docListDelete(p); - } - HashClear(&terms); - - return content_delete(v, iRow); -} - -static int fulltextUpdate(sqlite3_vtab *pVtab, int nArg, sqlite3_value **ppArg, - sqlite_int64 *pRowid){ - fulltext_vtab *v = (fulltext_vtab *) pVtab; - - if( nArg<2 ){ - return index_delete(v, sqlite3_value_int64(ppArg[0])); - } - - if( sqlite3_value_type(ppArg[0]) != SQLITE_NULL ){ - return SQLITE_ERROR; /* an update; not yet supported */ - } - - assert( nArg==3 ); /* ppArg[1] = rowid, ppArg[2] = content */ - return index_insert(v, ppArg[1], - (const char *)sqlite3_value_text(ppArg[2]), pRowid); -} - -static sqlite3_module fulltextModule = { - 0, - fulltextCreate, - fulltextConnect, - fulltextBestIndex, - fulltextDisconnect, - fulltextDestroy, - fulltextOpen, - fulltextClose, - fulltextFilter, - fulltextNext, - fulltextEof, - fulltextColumn, - fulltextRowid, - fulltextUpdate -}; - -int fulltext_init(sqlite3 *db){ - return sqlite3_create_module(db, "fulltext", &fulltextModule, 0); -} - -#if !SQLITE_CORE -#ifdef _WIN32 -__declspec(dllexport) -#endif -int sqlite3_fulltext_init(sqlite3 *db, char **pzErrMsg, - const sqlite3_api_routines *pApi){ - SQLITE_EXTENSION_INIT2(pApi) - return fulltext_init(db); -} -#endif diff --git a/ext/fts1/fulltext.h b/ext/fts1/fulltext.h deleted file mode 100644 index 477dcab2a..000000000 --- a/ext/fts1/fulltext.h +++ /dev/null @@ -1,11 +0,0 @@ -#include "sqlite3.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -int fulltext_init(sqlite3 *db); - -#ifdef __cplusplus -} /* extern "C" */ -#endif /* __cplusplus */ diff --git a/ext/fts1/simple_tokenizer.c b/ext/fts1/simple_tokenizer.c deleted file mode 100644 index 0ddc7055a..000000000 --- a/ext/fts1/simple_tokenizer.c +++ /dev/null @@ -1,174 +0,0 @@ -/* -** The author disclaims copyright to this source code. -** -************************************************************************* -** Implementation of the "simple" full-text-search tokenizer. -*/ - -#include -#if !defined(__APPLE__) -#include -#else -#include -#endif -#include -#include -#include - -#include "tokenizer.h" - -/* Duplicate a string; the caller must free() the returned string. - * (We don't use strdup() since it's not part of the standard C library and - * may not be available everywhere.) */ -/* TODO(shess) Copied from fulltext.c, consider util.c for such -** things. */ -static char *string_dup(const char *s){ - char *str = malloc(strlen(s) + 1); - strcpy(str, s); - return str; -} - -typedef struct simple_tokenizer { - sqlite3_tokenizer base; - const char *zDelim; /* token delimiters */ -} simple_tokenizer; - -typedef struct simple_tokenizer_cursor { - sqlite3_tokenizer_cursor base; - const char *pInput; /* input we are tokenizing */ - int nBytes; /* size of the input */ - const char *pCurrent; /* current position in pInput */ - int iToken; /* index of next token to be returned */ - char *zToken; /* storage for current token */ - int nTokenBytes; /* actual size of current token */ - int nTokenAllocated; /* space allocated to zToken buffer */ -} simple_tokenizer_cursor; - -static sqlite3_tokenizer_module simpleTokenizerModule;/* forward declaration */ - -static int simpleCreate( - int argc, const char **argv, - sqlite3_tokenizer **ppTokenizer -){ - simple_tokenizer *t; - - t = (simple_tokenizer *) malloc(sizeof(simple_tokenizer)); - /* TODO(shess) Delimiters need to remain the same from run to run, - ** else we need to reindex. One solution would be a meta-table to - ** track such information in the database, then we'd only want this - ** information on the initial create. - */ - if( argc>1 ){ - t->zDelim = string_dup(argv[1]); - } else { - /* Build a string excluding alphanumeric ASCII characters */ - char zDelim[0x80]; /* nul-terminated, so nul not a member */ - int i, j; - for(i=1, j=0; i<0x80; i++){ - if( !isalnum(i) ){ - zDelim[j++] = i; - } - } - zDelim[j++] = '\0'; - assert( j<=sizeof(zDelim) ); - t->zDelim = string_dup(zDelim); - } - - *ppTokenizer = &t->base; - return SQLITE_OK; -} - -static int simpleDestroy(sqlite3_tokenizer *pTokenizer){ - simple_tokenizer *t = (simple_tokenizer *) pTokenizer; - - free((void *) t->zDelim); - free(t); - - return SQLITE_OK; -} - -static int simpleOpen( - sqlite3_tokenizer *pTokenizer, - const char *pInput, int nBytes, - sqlite3_tokenizer_cursor **ppCursor -){ - simple_tokenizer_cursor *c; - - c = (simple_tokenizer_cursor *) malloc(sizeof(simple_tokenizer_cursor)); - c->pInput = pInput; - c->nBytes = nBytes<0 ? (int) strlen(pInput) : nBytes; - c->pCurrent = c->pInput; /* start tokenizing at the beginning */ - c->iToken = 0; - c->zToken = NULL; /* no space allocated, yet. */ - c->nTokenBytes = 0; - c->nTokenAllocated = 0; - - *ppCursor = &c->base; - return SQLITE_OK; -} - -static int simpleClose(sqlite3_tokenizer_cursor *pCursor){ - simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; - - if( NULL!=c->zToken ){ - free(c->zToken); - } - free(c); - - return SQLITE_OK; -} - -static int simpleNext( - sqlite3_tokenizer_cursor *pCursor, - const char **ppToken, int *pnBytes, - int *piStartOffset, int *piEndOffset, int *piPosition -){ - simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; - simple_tokenizer *t = (simple_tokenizer *) pCursor->pTokenizer; - int ii; - - while( c->pCurrent-c->pInputnBytes ){ - int n = (int) strcspn(c->pCurrent, t->zDelim); - if( n>0 ){ - if( n+1>c->nTokenAllocated ){ - c->zToken = realloc(c->zToken, n+1); - } - for(ii=0; iipCurrent[ii]; - c->zToken[ii] = (unsigned char)ch<0x80 ? tolower((unsigned char)ch):ch; - } - c->zToken[n] = '\0'; - *ppToken = c->zToken; - *pnBytes = n; - *piStartOffset = (int) (c->pCurrent-c->pInput); - *piEndOffset = *piStartOffset+n; - *piPosition = c->iToken++; - c->pCurrent += n + 1; - - return SQLITE_OK; - } - c->pCurrent += n + 1; - /* TODO(shess) could strspn() to skip delimiters en masse. Needs - ** to happen in two places, though, which is annoying. - */ - } - return SQLITE_DONE; -} - -static sqlite3_tokenizer_module simpleTokenizerModule = { - 0, - simpleCreate, - simpleDestroy, - simpleOpen, - simpleClose, - simpleNext, -}; - -void get_simple_tokenizer_module( - sqlite3_tokenizer_module **ppModule -){ - *ppModule = &simpleTokenizerModule; -} diff --git a/ext/fts1/tokenizer.h b/ext/fts1/tokenizer.h deleted file mode 100644 index 1d7bd1f67..000000000 --- a/ext/fts1/tokenizer.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -** 2006 July 10 -** -** The author disclaims copyright to this source code. -** -************************************************************************* -** Defines the interface to tokenizers used by fulltext-search. There -** are three basic components: -** -** sqlite3_tokenizer_module is a singleton defining the tokenizer -** interface functions. This is essentially the class structure for -** tokenizers. -** -** sqlite3_tokenizer is used to define a particular tokenizer, perhaps -** including customization information defined at creation time. -** -** sqlite3_tokenizer_cursor is generated by a tokenizer to generate -** tokens from a particular input. -*/ -#ifndef _TOKENIZER_H_ -#define _TOKENIZER_H_ - -/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. -** If tokenizers are to be allowed to call sqlite3_*() functions, then -** we will need a way to register the API consistently. -*/ -#include "sqlite3.h" - -/* -** Structures used by the tokenizer interface. -*/ -typedef struct sqlite3_tokenizer sqlite3_tokenizer; -typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; -typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; - -struct sqlite3_tokenizer_module { - int iVersion; /* currently 0 */ - - /* - ** Create and destroy a tokenizer. argc/argv are passed down from - ** the fulltext virtual table creation to allow customization. - */ - int (*xCreate)(int argc, const char **argv, - sqlite3_tokenizer **ppTokenizer); - int (*xDestroy)(sqlite3_tokenizer *pTokenizer); - - /* - ** Tokenize a particular input. Call xOpen() to prepare to - ** tokenize, xNext() repeatedly until it returns SQLITE_DONE, then - ** xClose() to free any internal state. The pInput passed to - ** xOpen() must exist until the cursor is closed. The ppToken - ** result from xNext() is only valid until the next call to xNext() - ** or until xClose() is called. - */ - /* TODO(shess) current implementation requires pInput to be - ** nul-terminated. This should either be fixed, or pInput/nBytes - ** should be converted to zInput. - */ - int (*xOpen)(sqlite3_tokenizer *pTokenizer, - const char *pInput, int nBytes, - sqlite3_tokenizer_cursor **ppCursor); - int (*xClose)(sqlite3_tokenizer_cursor *pCursor); - int (*xNext)(sqlite3_tokenizer_cursor *pCursor, - const char **ppToken, int *pnBytes, - int *piStartOffset, int *piEndOffset, int *piPosition); -}; - -struct sqlite3_tokenizer { - sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ - /* Tokenizer implementations will typically add additional fields */ -}; - -struct sqlite3_tokenizer_cursor { - sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ - /* Tokenizer implementations will typically add additional fields */ -}; - -/* -** Get the module for a tokenizer which generates tokens based on a -** set of non-token characters. The default is to break tokens at any -** non-alnum character, though the set of delimiters can also be -** specified by the first argv argument to xCreate(). -*/ -/* TODO(shess) This doesn't belong here. Need some sort of -** registration process. -*/ -void get_simple_tokenizer_module(sqlite3_tokenizer_module **ppModule); - -#endif /* _TOKENIZER_H_ */ diff --git a/ext/fts2/README.tokenizers b/ext/fts2/README.tokenizers deleted file mode 100644 index 98d2021ba..000000000 --- a/ext/fts2/README.tokenizers +++ /dev/null @@ -1,133 +0,0 @@ - -1. FTS2 Tokenizers - - When creating a new full-text table, FTS2 allows the user to select - the text tokenizer implementation to be used when indexing text - by specifying a "tokenizer" clause as part of the CREATE VIRTUAL TABLE - statement: - - CREATE VIRTUAL TABLE USING fts2( - [, tokenizer []] - ); - - The built-in tokenizers (valid values to pass as ) are - "simple" and "porter". - - should consist of zero or more white-space separated - arguments to pass to the selected tokenizer implementation. The - interpretation of the arguments, if any, depends on the individual - tokenizer. - -2. Custom Tokenizers - - FTS2 allows users to provide custom tokenizer implementations. The - interface used to create a new tokenizer is defined and described in - the fts2_tokenizer.h source file. - - Registering a new FTS2 tokenizer is similar to registering a new - virtual table module with SQLite. The user passes a pointer to a - structure containing pointers to various callback functions that - make up the implementation of the new tokenizer type. For tokenizers, - the structure (defined in fts2_tokenizer.h) is called - "sqlite3_tokenizer_module". - - FTS2 does not expose a C-function that users call to register new - tokenizer types with a database handle. Instead, the pointer must - be encoded as an SQL blob value and passed to FTS2 through the SQL - engine by evaluating a special scalar function, "fts2_tokenizer()". - The fts2_tokenizer() function may be called with one or two arguments, - as follows: - - SELECT fts2_tokenizer(); - SELECT fts2_tokenizer(, ); - - Where is a string identifying the tokenizer and - is a pointer to an sqlite3_tokenizer_module - structure encoded as an SQL blob. If the second argument is present, - it is registered as tokenizer and a copy of it - returned. If only one argument is passed, a pointer to the tokenizer - implementation currently registered as is returned, - encoded as a blob. Or, if no such tokenizer exists, an SQL exception - (error) is raised. - - SECURITY: If the fts2 extension is used in an environment where potentially - malicious users may execute arbitrary SQL (i.e. gears), they should be - prevented from invoking the fts2_tokenizer() function, possibly using the - authorisation callback. - - See "Sample code" below for an example of calling the fts2_tokenizer() - function from C code. - -3. ICU Library Tokenizers - - If this extension is compiled with the SQLITE_ENABLE_ICU pre-processor - symbol defined, then there exists a built-in tokenizer named "icu" - implemented using the ICU library. The first argument passed to the - xCreate() method (see fts2_tokenizer.h) of this tokenizer may be - an ICU locale identifier. For example "tr_TR" for Turkish as used - in Turkey, or "en_AU" for English as used in Australia. For example: - - "CREATE VIRTUAL TABLE thai_text USING fts2(text, tokenizer icu th_TH)" - - The ICU tokenizer implementation is very simple. It splits the input - text according to the ICU rules for finding word boundaries and discards - any tokens that consist entirely of white-space. This may be suitable - for some applications in some locales, but not all. If more complex - processing is required, for example to implement stemming or - discard punctuation, this can be done by creating a tokenizer - implementation that uses the ICU tokenizer as part of its implementation. - - When using the ICU tokenizer this way, it is safe to overwrite the - contents of the strings returned by the xNext() method (see - fts2_tokenizer.h). - -4. Sample code. - - The following two code samples illustrate the way C code should invoke - the fts2_tokenizer() scalar function: - - int registerTokenizer( - sqlite3 *db, - char *zName, - const sqlite3_tokenizer_module *p - ){ - int rc; - sqlite3_stmt *pStmt; - const char zSql[] = "SELECT fts2_tokenizer(?, ?)"; - - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - if( rc!=SQLITE_OK ){ - return rc; - } - - sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); - sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC); - sqlite3_step(pStmt); - - return sqlite3_finalize(pStmt); - } - - int queryTokenizer( - sqlite3 *db, - char *zName, - const sqlite3_tokenizer_module **pp - ){ - int rc; - sqlite3_stmt *pStmt; - const char zSql[] = "SELECT fts2_tokenizer(?)"; - - *pp = 0; - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - if( rc!=SQLITE_OK ){ - return rc; - } - - sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); - if( SQLITE_ROW==sqlite3_step(pStmt) ){ - if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ - memcpy(pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); - } - } - - return sqlite3_finalize(pStmt); - } diff --git a/ext/fts2/README.txt b/ext/fts2/README.txt deleted file mode 100644 index 517a2a043..000000000 --- a/ext/fts2/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -This folder contains source code to the second full-text search -extension for SQLite. While the API is the same, this version uses a -substantially different storage schema from fts1, so tables will need -to be rebuilt. diff --git a/ext/fts2/fts2.c b/ext/fts2/fts2.c deleted file mode 100644 index 0405fb7b1..000000000 --- a/ext/fts2/fts2.c +++ /dev/null @@ -1,6860 +0,0 @@ -/* fts2 has a design flaw which can lead to database corruption (see -** below). It is recommended not to use it any longer, instead use -** fts3 (or higher). If you believe that your use of fts2 is safe, -** add -DSQLITE_ENABLE_BROKEN_FTS2=1 to your CFLAGS. -*/ -#if (!defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2)) \ - && !defined(SQLITE_ENABLE_BROKEN_FTS2) -#error fts2 has a design flaw and has been deprecated. -#endif -/* The flaw is that fts2 uses the content table's unaliased rowid as -** the unique docid. fts2 embeds the rowid in the index it builds, -** and expects the rowid to not change. The SQLite VACUUM operation -** will renumber such rowids, thereby breaking fts2. If you are using -** fts2 in a system which has disabled VACUUM, then you can continue -** to use it safely. Note that PRAGMA auto_vacuum does NOT disable -** VACUUM, though systems using auto_vacuum are unlikely to invoke -** VACUUM. -** -** Unlike fts1, which is safe across VACUUM if you never delete -** documents, fts2 has a second exposure to this flaw, in the segments -** table. So fts2 should be considered unsafe across VACUUM in all -** cases. -*/ - -/* -** 2006 Oct 10 -** -** 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 is an SQLite module implementing full-text search. -*/ - -/* -** The code in this file is only compiled if: -** -** * The FTS2 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS2 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS2 is defined). -*/ - -/* TODO(shess) Consider exporting this comment to an HTML file or the -** wiki. -*/ -/* The full-text index is stored in a series of b+tree (-like) -** structures called segments which map terms to doclists. The -** structures are like b+trees in layout, but are constructed from the -** bottom up in optimal fashion and are not updatable. Since trees -** are built from the bottom up, things will be described from the -** bottom up. -** -** -**** Varints **** -** The basic unit of encoding is a variable-length integer called a -** varint. We encode variable-length integers in little-endian order -** using seven bits * per byte as follows: -** -** KEY: -** A = 0xxxxxxx 7 bits of data and one flag bit -** B = 1xxxxxxx 7 bits of data and one flag bit -** -** 7 bits - A -** 14 bits - BA -** 21 bits - BBA -** and so on. -** -** This is identical to how sqlite encodes varints (see util.c). -** -** -**** Document lists **** -** A doclist (document list) holds a docid-sorted list of hits for a -** given term. Doclists hold docids, and can optionally associate -** token positions and offsets with docids. -** -** A DL_POSITIONS_OFFSETS doclist is stored like this: -** -** array { -** varint docid; -** array { (position list for column 0) -** varint position; (delta from previous position plus POS_BASE) -** varint startOffset; (delta from previous startOffset) -** varint endOffset; (delta from startOffset) -** } -** array { -** varint POS_COLUMN; (marks start of position list for new column) -** varint column; (index of new column) -** array { -** varint position; (delta from previous position plus POS_BASE) -** varint startOffset;(delta from previous startOffset) -** varint endOffset; (delta from startOffset) -** } -** } -** varint POS_END; (marks end of positions for this document. -** } -** -** Here, array { X } means zero or more occurrences of X, adjacent in -** memory. A "position" is an index of a token in the token stream -** generated by the tokenizer, while an "offset" is a byte offset, -** both based at 0. Note that POS_END and POS_COLUMN occur in the -** same logical place as the position element, and act as sentinals -** ending a position list array. -** -** A DL_POSITIONS doclist omits the startOffset and endOffset -** information. A DL_DOCIDS doclist omits both the position and -** offset information, becoming an array of varint-encoded docids. -** -** On-disk data is stored as type DL_DEFAULT, so we don't serialize -** the type. Due to how deletion is implemented in the segmentation -** system, on-disk doclists MUST store at least positions. -** -** -**** Segment leaf nodes **** -** Segment leaf nodes store terms and doclists, ordered by term. Leaf -** nodes are written using LeafWriter, and read using LeafReader (to -** iterate through a single leaf node's data) and LeavesReader (to -** iterate through a segment's entire leaf layer). Leaf nodes have -** the format: -** -** varint iHeight; (height from leaf level, always 0) -** varint nTerm; (length of first term) -** char pTerm[nTerm]; (content of first term) -** varint nDoclist; (length of term's associated doclist) -** char pDoclist[nDoclist]; (content of doclist) -** array { -** (further terms are delta-encoded) -** varint nPrefix; (length of prefix shared with previous term) -** varint nSuffix; (length of unshared suffix) -** char pTermSuffix[nSuffix];(unshared suffix of next term) -** varint nDoclist; (length of term's associated doclist) -** char pDoclist[nDoclist]; (content of doclist) -** } -** -** Here, array { X } means zero or more occurrences of X, adjacent in -** memory. -** -** Leaf nodes are broken into blocks which are stored contiguously in -** the %_segments table in sorted order. This means that when the end -** of a node is reached, the next term is in the node with the next -** greater node id. -** -** New data is spilled to a new leaf node when the current node -** exceeds LEAF_MAX bytes (default 2048). New data which itself is -** larger than STANDALONE_MIN (default 1024) is placed in a standalone -** node (a leaf node with a single term and doclist). The goal of -** these settings is to pack together groups of small doclists while -** making it efficient to directly access large doclists. The -** assumption is that large doclists represent terms which are more -** likely to be query targets. -** -** TODO(shess) It may be useful for blocking decisions to be more -** dynamic. For instance, it may make more sense to have a 2.5k leaf -** node rather than splitting into 2k and .5k nodes. My intuition is -** that this might extend through 2x or 4x the pagesize. -** -** -**** Segment interior nodes **** -** Segment interior nodes store blockids for subtree nodes and terms -** to describe what data is stored by the each subtree. Interior -** nodes are written using InteriorWriter, and read using -** InteriorReader. InteriorWriters are created as needed when -** SegmentWriter creates new leaf nodes, or when an interior node -** itself grows too big and must be split. The format of interior -** nodes: -** -** varint iHeight; (height from leaf level, always >0) -** varint iBlockid; (block id of node's leftmost subtree) -** optional { -** varint nTerm; (length of first term) -** char pTerm[nTerm]; (content of first term) -** array { -** (further terms are delta-encoded) -** varint nPrefix; (length of shared prefix with previous term) -** varint nSuffix; (length of unshared suffix) -** char pTermSuffix[nSuffix]; (unshared suffix of next term) -** } -** } -** -** Here, optional { X } means an optional element, while array { X } -** means zero or more occurrences of X, adjacent in memory. -** -** An interior node encodes n terms separating n+1 subtrees. The -** subtree blocks are contiguous, so only the first subtree's blockid -** is encoded. The subtree at iBlockid will contain all terms less -** than the first term encoded (or all terms if no term is encoded). -** Otherwise, for terms greater than or equal to pTerm[i] but less -** than pTerm[i+1], the subtree for that term will be rooted at -** iBlockid+i. Interior nodes only store enough term data to -** distinguish adjacent children (if the rightmost term of the left -** child is "something", and the leftmost term of the right child is -** "wicked", only "w" is stored). -** -** New data is spilled to a new interior node at the same height when -** the current node exceeds INTERIOR_MAX bytes (default 2048). -** INTERIOR_MIN_TERMS (default 7) keeps large terms from monopolizing -** interior nodes and making the tree too skinny. The interior nodes -** at a given height are naturally tracked by interior nodes at -** height+1, and so on. -** -** -**** Segment directory **** -** The segment directory in table %_segdir stores meta-information for -** merging and deleting segments, and also the root node of the -** segment's tree. -** -** The root node is the top node of the segment's tree after encoding -** the entire segment, restricted to ROOT_MAX bytes (default 1024). -** This could be either a leaf node or an interior node. If the top -** node requires more than ROOT_MAX bytes, it is flushed to %_segments -** and a new root interior node is generated (which should always fit -** within ROOT_MAX because it only needs space for 2 varints, the -** height and the blockid of the previous root). -** -** The meta-information in the segment directory is: -** level - segment level (see below) -** idx - index within level -** - (level,idx uniquely identify a segment) -** start_block - first leaf node -** leaves_end_block - last leaf node -** end_block - last block (including interior nodes) -** root - contents of root node -** -** If the root node is a leaf node, then start_block, -** leaves_end_block, and end_block are all 0. -** -** -**** Segment merging **** -** To amortize update costs, segments are groups into levels and -** merged in matches. Each increase in level represents exponentially -** more documents. -** -** New documents (actually, document updates) are tokenized and -** written individually (using LeafWriter) to a level 0 segment, with -** incrementing idx. When idx reaches MERGE_COUNT (default 16), all -** level 0 segments are merged into a single level 1 segment. Level 1 -** is populated like level 0, and eventually MERGE_COUNT level 1 -** segments are merged to a single level 2 segment (representing -** MERGE_COUNT^2 updates), and so on. -** -** A segment merge traverses all segments at a given level in -** parallel, performing a straightforward sorted merge. Since segment -** leaf nodes are written in to the %_segments table in order, this -** merge traverses the underlying sqlite disk structures efficiently. -** After the merge, all segment blocks from the merged level are -** deleted. -** -** MERGE_COUNT controls how often we merge segments. 16 seems to be -** somewhat of a sweet spot for insertion performance. 32 and 64 show -** very similar performance numbers to 16 on insertion, though they're -** a tiny bit slower (perhaps due to more overhead in merge-time -** sorting). 8 is about 20% slower than 16, 4 about 50% slower than -** 16, 2 about 66% slower than 16. -** -** At query time, high MERGE_COUNT increases the number of segments -** which need to be scanned and merged. For instance, with 100k docs -** inserted: -** -** MERGE_COUNT segments -** 16 25 -** 8 12 -** 4 10 -** 2 6 -** -** This appears to have only a moderate impact on queries for very -** frequent terms (which are somewhat dominated by segment merge -** costs), and infrequent and non-existent terms still seem to be fast -** even with many segments. -** -** TODO(shess) That said, it would be nice to have a better query-side -** argument for MERGE_COUNT of 16. Also, it is possible/likely that -** optimizations to things like doclist merging will swing the sweet -** spot around. -** -** -** -**** Handling of deletions and updates **** -** Since we're using a segmented structure, with no docid-oriented -** index into the term index, we clearly cannot simply update the term -** index when a document is deleted or updated. For deletions, we -** write an empty doclist (varint(docid) varint(POS_END)), for updates -** we simply write the new doclist. Segment merges overwrite older -** data for a particular docid with newer data, so deletes or updates -** will eventually overtake the earlier data and knock it out. The -** query logic likewise merges doclists so that newer data knocks out -** older data. -** -** TODO(shess) Provide a VACUUM type operation to clear out all -** deletions and duplications. This would basically be a forced merge -** into a single segment. -*/ - -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) - -#if defined(SQLITE_ENABLE_FTS2) && !defined(SQLITE_CORE) -# define SQLITE_CORE 1 -#endif - -#include -#include -#include -#include -#include "fts2.h" -#include "fts2_hash.h" -#include "fts2_tokenizer.h" -#include "sqlite3.h" -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT1 - - -/* TODO(shess) MAN, this thing needs some refactoring. At minimum, it -** would be nice to order the file better, perhaps something along the -** lines of: -** -** - utility functions -** - table setup functions -** - table update functions -** - table query functions -** -** Put the query functions last because they're likely to reference -** typedefs or functions from the table update section. -*/ - -#if 0 -# define TRACE(A) printf A; fflush(stdout) -#else -# define TRACE(A) -#endif - -/* It is not safe to call isspace(), tolower(), or isalnum() on -** hi-bit-set characters. This is the same solution used in the -** tokenizer. -*/ -/* TODO(shess) The snippet-generation code should be using the -** tokenizer-generated tokens rather than doing its own local -** tokenization. -*/ -/* TODO(shess) Is __isascii() a portable version of (c&0x80)==0? */ -static int safe_isspace(char c){ - return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; -} -static int safe_tolower(char c){ - return (c>='A' && c<='Z') ? (c - 'A' + 'a') : c; -} -static int safe_isalnum(char c){ - return (c>='0' && c<='9') || (c>='A' && c<='Z') || (c>='a' && c<='z'); -} - -typedef enum DocListType { - DL_DOCIDS, /* docids only */ - DL_POSITIONS, /* docids + positions */ - DL_POSITIONS_OFFSETS /* docids + positions + offsets */ -} DocListType; - -/* -** By default, only positions and not offsets are stored in the doclists. -** To change this so that offsets are stored too, compile with -** -** -DDL_DEFAULT=DL_POSITIONS_OFFSETS -** -** If DL_DEFAULT is set to DL_DOCIDS, your table can only be inserted -** into (no deletes or updates). -*/ -#ifndef DL_DEFAULT -# define DL_DEFAULT DL_POSITIONS -#endif - -enum { - POS_END = 0, /* end of this position list */ - POS_COLUMN, /* followed by new column number */ - POS_BASE -}; - -/* MERGE_COUNT controls how often we merge segments (see comment at -** top of file). -*/ -#define MERGE_COUNT 16 - -/* utility functions */ - -/* CLEAR() and SCRAMBLE() abstract memset() on a pointer to a single -** record to prevent errors of the form: -** -** my_function(SomeType *b){ -** memset(b, '\0', sizeof(b)); // sizeof(b)!=sizeof(*b) -** } -*/ -/* TODO(shess) Obvious candidates for a header file. */ -#define CLEAR(b) memset(b, '\0', sizeof(*(b))) - -#ifndef NDEBUG -# define SCRAMBLE(b) memset(b, 0x55, sizeof(*(b))) -#else -# define SCRAMBLE(b) -#endif - -/* We may need up to VARINT_MAX bytes to store an encoded 64-bit integer. */ -#define VARINT_MAX 10 - -/* Write a 64-bit variable-length integer to memory starting at p[0]. - * The length of data written will be between 1 and VARINT_MAX bytes. - * The number of bytes written is returned. */ -static int putVarint(char *p, sqlite_int64 v){ - unsigned char *q = (unsigned char *) p; - sqlite_uint64 vu = v; - do{ - *q++ = (unsigned char) ((vu & 0x7f) | 0x80); - vu >>= 7; - }while( vu!=0 ); - q[-1] &= 0x7f; /* turn off high bit in final byte */ - assert( q - (unsigned char *)p <= VARINT_MAX ); - return (int) (q - (unsigned char *)p); -} - -/* Read a 64-bit variable-length integer from memory starting at p[0]. - * Return the number of bytes read, or 0 on error. - * The value is stored in *v. */ -static int getVarint(const char *p, sqlite_int64 *v){ - const unsigned char *q = (const unsigned char *) p; - sqlite_uint64 x = 0, y = 1; - while( (*q & 0x80) == 0x80 ){ - x += y * (*q++ & 0x7f); - y <<= 7; - if( q - (unsigned char *)p >= VARINT_MAX ){ /* bad data */ - assert( 0 ); - return 0; - } - } - x += y * (*q++); - *v = (sqlite_int64) x; - return (int) (q - (unsigned char *)p); -} - -static int getVarint32(const char *p, int *pi){ - sqlite_int64 i; - int ret = getVarint(p, &i); - *pi = (int) i; - assert( *pi==i ); - return ret; -} - -/*******************************************************************/ -/* DataBuffer is used to collect data into a buffer in piecemeal -** fashion. It implements the usual distinction between amount of -** data currently stored (nData) and buffer capacity (nCapacity). -** -** dataBufferInit - create a buffer with given initial capacity. -** dataBufferReset - forget buffer's data, retaining capacity. -** dataBufferDestroy - free buffer's data. -** dataBufferSwap - swap contents of two buffers. -** dataBufferExpand - expand capacity without adding data. -** dataBufferAppend - append data. -** dataBufferAppend2 - append two pieces of data at once. -** dataBufferReplace - replace buffer's data. -*/ -typedef struct DataBuffer { - char *pData; /* Pointer to malloc'ed buffer. */ - int nCapacity; /* Size of pData buffer. */ - int nData; /* End of data loaded into pData. */ -} DataBuffer; - -static void dataBufferInit(DataBuffer *pBuffer, int nCapacity){ - assert( nCapacity>=0 ); - pBuffer->nData = 0; - pBuffer->nCapacity = nCapacity; - pBuffer->pData = nCapacity==0 ? NULL : sqlite3_malloc(nCapacity); -} -static void dataBufferReset(DataBuffer *pBuffer){ - pBuffer->nData = 0; -} -static void dataBufferDestroy(DataBuffer *pBuffer){ - if( pBuffer->pData!=NULL ) sqlite3_free(pBuffer->pData); - SCRAMBLE(pBuffer); -} -static void dataBufferSwap(DataBuffer *pBuffer1, DataBuffer *pBuffer2){ - DataBuffer tmp = *pBuffer1; - *pBuffer1 = *pBuffer2; - *pBuffer2 = tmp; -} -static void dataBufferExpand(DataBuffer *pBuffer, int nAddCapacity){ - assert( nAddCapacity>0 ); - /* TODO(shess) Consider expanding more aggressively. Note that the - ** underlying malloc implementation may take care of such things for - ** us already. - */ - if( pBuffer->nData+nAddCapacity>pBuffer->nCapacity ){ - pBuffer->nCapacity = pBuffer->nData+nAddCapacity; - pBuffer->pData = sqlite3_realloc(pBuffer->pData, pBuffer->nCapacity); - } -} -static void dataBufferAppend(DataBuffer *pBuffer, - const char *pSource, int nSource){ - assert( nSource>0 && pSource!=NULL ); - dataBufferExpand(pBuffer, nSource); - memcpy(pBuffer->pData+pBuffer->nData, pSource, nSource); - pBuffer->nData += nSource; -} -static void dataBufferAppend2(DataBuffer *pBuffer, - const char *pSource1, int nSource1, - const char *pSource2, int nSource2){ - assert( nSource1>0 && pSource1!=NULL ); - assert( nSource2>0 && pSource2!=NULL ); - dataBufferExpand(pBuffer, nSource1+nSource2); - memcpy(pBuffer->pData+pBuffer->nData, pSource1, nSource1); - memcpy(pBuffer->pData+pBuffer->nData+nSource1, pSource2, nSource2); - pBuffer->nData += nSource1+nSource2; -} -static void dataBufferReplace(DataBuffer *pBuffer, - const char *pSource, int nSource){ - dataBufferReset(pBuffer); - dataBufferAppend(pBuffer, pSource, nSource); -} - -/* StringBuffer is a null-terminated version of DataBuffer. */ -typedef struct StringBuffer { - DataBuffer b; /* Includes null terminator. */ -} StringBuffer; - -static void initStringBuffer(StringBuffer *sb){ - dataBufferInit(&sb->b, 100); - dataBufferReplace(&sb->b, "", 1); -} -static int stringBufferLength(StringBuffer *sb){ - return sb->b.nData-1; -} -static char *stringBufferData(StringBuffer *sb){ - return sb->b.pData; -} -static void stringBufferDestroy(StringBuffer *sb){ - dataBufferDestroy(&sb->b); -} - -static void nappend(StringBuffer *sb, const char *zFrom, int nFrom){ - assert( sb->b.nData>0 ); - if( nFrom>0 ){ - sb->b.nData--; - dataBufferAppend2(&sb->b, zFrom, nFrom, "", 1); - } -} -static void append(StringBuffer *sb, const char *zFrom){ - nappend(sb, zFrom, strlen(zFrom)); -} - -/* Append a list of strings separated by commas. */ -static void appendList(StringBuffer *sb, int nString, char **azString){ - int i; - for(i=0; i0 ) append(sb, ", "); - append(sb, azString[i]); - } -} - -static int endsInWhiteSpace(StringBuffer *p){ - return stringBufferLength(p)>0 && - safe_isspace(stringBufferData(p)[stringBufferLength(p)-1]); -} - -/* If the StringBuffer ends in something other than white space, add a -** single space character to the end. -*/ -static void appendWhiteSpace(StringBuffer *p){ - if( stringBufferLength(p)==0 ) return; - if( !endsInWhiteSpace(p) ) append(p, " "); -} - -/* Remove white space from the end of the StringBuffer */ -static void trimWhiteSpace(StringBuffer *p){ - while( endsInWhiteSpace(p) ){ - p->b.pData[--p->b.nData-1] = '\0'; - } -} - -/*******************************************************************/ -/* DLReader is used to read document elements from a doclist. The -** current docid is cached, so dlrDocid() is fast. DLReader does not -** own the doclist buffer. -** -** dlrAtEnd - true if there's no more data to read. -** dlrDocid - docid of current document. -** dlrDocData - doclist data for current document (including docid). -** dlrDocDataBytes - length of same. -** dlrAllDataBytes - length of all remaining data. -** dlrPosData - position data for current document. -** dlrPosDataLen - length of pos data for current document (incl POS_END). -** dlrStep - step to current document. -** dlrInit - initial for doclist of given type against given data. -** dlrDestroy - clean up. -** -** Expected usage is something like: -** -** DLReader reader; -** dlrInit(&reader, pData, nData); -** while( !dlrAtEnd(&reader) ){ -** // calls to dlrDocid() and kin. -** dlrStep(&reader); -** } -** dlrDestroy(&reader); -*/ -typedef struct DLReader { - DocListType iType; - const char *pData; - int nData; - - sqlite_int64 iDocid; - int nElement; -} DLReader; - -static int dlrAtEnd(DLReader *pReader){ - assert( pReader->nData>=0 ); - return pReader->nData==0; -} -static sqlite_int64 dlrDocid(DLReader *pReader){ - assert( !dlrAtEnd(pReader) ); - return pReader->iDocid; -} -static const char *dlrDocData(DLReader *pReader){ - assert( !dlrAtEnd(pReader) ); - return pReader->pData; -} -static int dlrDocDataBytes(DLReader *pReader){ - assert( !dlrAtEnd(pReader) ); - return pReader->nElement; -} -static int dlrAllDataBytes(DLReader *pReader){ - assert( !dlrAtEnd(pReader) ); - return pReader->nData; -} -/* TODO(shess) Consider adding a field to track iDocid varint length -** to make these two functions faster. This might matter (a tiny bit) -** for queries. -*/ -static const char *dlrPosData(DLReader *pReader){ - sqlite_int64 iDummy; - int n = getVarint(pReader->pData, &iDummy); - assert( !dlrAtEnd(pReader) ); - return pReader->pData+n; -} -static int dlrPosDataLen(DLReader *pReader){ - sqlite_int64 iDummy; - int n = getVarint(pReader->pData, &iDummy); - assert( !dlrAtEnd(pReader) ); - return pReader->nElement-n; -} -static void dlrStep(DLReader *pReader){ - assert( !dlrAtEnd(pReader) ); - - /* Skip past current doclist element. */ - assert( pReader->nElement<=pReader->nData ); - pReader->pData += pReader->nElement; - pReader->nData -= pReader->nElement; - - /* If there is more data, read the next doclist element. */ - if( pReader->nData!=0 ){ - sqlite_int64 iDocidDelta; - int iDummy, n = getVarint(pReader->pData, &iDocidDelta); - pReader->iDocid += iDocidDelta; - if( pReader->iType>=DL_POSITIONS ){ - assert( nnData ); - while( 1 ){ - n += getVarint32(pReader->pData+n, &iDummy); - assert( n<=pReader->nData ); - if( iDummy==POS_END ) break; - if( iDummy==POS_COLUMN ){ - n += getVarint32(pReader->pData+n, &iDummy); - assert( nnData ); - }else if( pReader->iType==DL_POSITIONS_OFFSETS ){ - n += getVarint32(pReader->pData+n, &iDummy); - n += getVarint32(pReader->pData+n, &iDummy); - assert( nnData ); - } - } - } - pReader->nElement = n; - assert( pReader->nElement<=pReader->nData ); - } -} -static void dlrInit(DLReader *pReader, DocListType iType, - const char *pData, int nData){ - assert( pData!=NULL && nData!=0 ); - pReader->iType = iType; - pReader->pData = pData; - pReader->nData = nData; - pReader->nElement = 0; - pReader->iDocid = 0; - - /* Load the first element's data. There must be a first element. */ - dlrStep(pReader); -} -static void dlrDestroy(DLReader *pReader){ - SCRAMBLE(pReader); -} - -#ifndef NDEBUG -/* Verify that the doclist can be validly decoded. Also returns the -** last docid found because it is convenient in other assertions for -** DLWriter. -*/ -static void docListValidate(DocListType iType, const char *pData, int nData, - sqlite_int64 *pLastDocid){ - sqlite_int64 iPrevDocid = 0; - assert( nData>0 ); - assert( pData!=0 ); - assert( pData+nData>pData ); - while( nData!=0 ){ - sqlite_int64 iDocidDelta; - int n = getVarint(pData, &iDocidDelta); - iPrevDocid += iDocidDelta; - if( iType>DL_DOCIDS ){ - int iDummy; - while( 1 ){ - n += getVarint32(pData+n, &iDummy); - if( iDummy==POS_END ) break; - if( iDummy==POS_COLUMN ){ - n += getVarint32(pData+n, &iDummy); - }else if( iType>DL_POSITIONS ){ - n += getVarint32(pData+n, &iDummy); - n += getVarint32(pData+n, &iDummy); - } - assert( n<=nData ); - } - } - assert( n<=nData ); - pData += n; - nData -= n; - } - if( pLastDocid ) *pLastDocid = iPrevDocid; -} -#define ASSERT_VALID_DOCLIST(i, p, n, o) docListValidate(i, p, n, o) -#else -#define ASSERT_VALID_DOCLIST(i, p, n, o) assert( 1 ) -#endif - -/*******************************************************************/ -/* DLWriter is used to write doclist data to a DataBuffer. DLWriter -** always appends to the buffer and does not own it. -** -** dlwInit - initialize to write a given type doclistto a buffer. -** dlwDestroy - clear the writer's memory. Does not free buffer. -** dlwAppend - append raw doclist data to buffer. -** dlwCopy - copy next doclist from reader to writer. -** dlwAdd - construct doclist element and append to buffer. -** Only apply dlwAdd() to DL_DOCIDS doclists (else use PLWriter). -*/ -typedef struct DLWriter { - DocListType iType; - DataBuffer *b; - sqlite_int64 iPrevDocid; -#ifndef NDEBUG - int has_iPrevDocid; -#endif -} DLWriter; - -static void dlwInit(DLWriter *pWriter, DocListType iType, DataBuffer *b){ - pWriter->b = b; - pWriter->iType = iType; - pWriter->iPrevDocid = 0; -#ifndef NDEBUG - pWriter->has_iPrevDocid = 0; -#endif -} -static void dlwDestroy(DLWriter *pWriter){ - SCRAMBLE(pWriter); -} -/* iFirstDocid is the first docid in the doclist in pData. It is -** needed because pData may point within a larger doclist, in which -** case the first item would be delta-encoded. -** -** iLastDocid is the final docid in the doclist in pData. It is -** needed to create the new iPrevDocid for future delta-encoding. The -** code could decode the passed doclist to recreate iLastDocid, but -** the only current user (docListMerge) already has decoded this -** information. -*/ -/* TODO(shess) This has become just a helper for docListMerge. -** Consider a refactor to make this cleaner. -*/ -static void dlwAppend(DLWriter *pWriter, - const char *pData, int nData, - sqlite_int64 iFirstDocid, sqlite_int64 iLastDocid){ - sqlite_int64 iDocid = 0; - char c[VARINT_MAX]; - int nFirstOld, nFirstNew; /* Old and new varint len of first docid. */ -#ifndef NDEBUG - sqlite_int64 iLastDocidDelta; -#endif - - /* Recode the initial docid as delta from iPrevDocid. */ - nFirstOld = getVarint(pData, &iDocid); - assert( nFirstOldiType==DL_DOCIDS) ); - nFirstNew = putVarint(c, iFirstDocid-pWriter->iPrevDocid); - - /* Verify that the incoming doclist is valid AND that it ends with - ** the expected docid. This is essential because we'll trust this - ** docid in future delta-encoding. - */ - ASSERT_VALID_DOCLIST(pWriter->iType, pData, nData, &iLastDocidDelta); - assert( iLastDocid==iFirstDocid-iDocid+iLastDocidDelta ); - - /* Append recoded initial docid and everything else. Rest of docids - ** should have been delta-encoded from previous initial docid. - */ - if( nFirstOldb, c, nFirstNew, - pData+nFirstOld, nData-nFirstOld); - }else{ - dataBufferAppend(pWriter->b, c, nFirstNew); - } - pWriter->iPrevDocid = iLastDocid; -} -static void dlwCopy(DLWriter *pWriter, DLReader *pReader){ - dlwAppend(pWriter, dlrDocData(pReader), dlrDocDataBytes(pReader), - dlrDocid(pReader), dlrDocid(pReader)); -} -static void dlwAdd(DLWriter *pWriter, sqlite_int64 iDocid){ - char c[VARINT_MAX]; - int n = putVarint(c, iDocid-pWriter->iPrevDocid); - - /* Docids must ascend. */ - assert( !pWriter->has_iPrevDocid || iDocid>pWriter->iPrevDocid ); - assert( pWriter->iType==DL_DOCIDS ); - - dataBufferAppend(pWriter->b, c, n); - pWriter->iPrevDocid = iDocid; -#ifndef NDEBUG - pWriter->has_iPrevDocid = 1; -#endif -} - -/*******************************************************************/ -/* PLReader is used to read data from a document's position list. As -** the caller steps through the list, data is cached so that varints -** only need to be decoded once. -** -** plrInit, plrDestroy - create/destroy a reader. -** plrColumn, plrPosition, plrStartOffset, plrEndOffset - accessors -** plrAtEnd - at end of stream, only call plrDestroy once true. -** plrStep - step to the next element. -*/ -typedef struct PLReader { - /* These refer to the next position's data. nData will reach 0 when - ** reading the last position, so plrStep() signals EOF by setting - ** pData to NULL. - */ - const char *pData; - int nData; - - DocListType iType; - int iColumn; /* the last column read */ - int iPosition; /* the last position read */ - int iStartOffset; /* the last start offset read */ - int iEndOffset; /* the last end offset read */ -} PLReader; - -static int plrAtEnd(PLReader *pReader){ - return pReader->pData==NULL; -} -static int plrColumn(PLReader *pReader){ - assert( !plrAtEnd(pReader) ); - return pReader->iColumn; -} -static int plrPosition(PLReader *pReader){ - assert( !plrAtEnd(pReader) ); - return pReader->iPosition; -} -static int plrStartOffset(PLReader *pReader){ - assert( !plrAtEnd(pReader) ); - return pReader->iStartOffset; -} -static int plrEndOffset(PLReader *pReader){ - assert( !plrAtEnd(pReader) ); - return pReader->iEndOffset; -} -static void plrStep(PLReader *pReader){ - int i, n; - - assert( !plrAtEnd(pReader) ); - - if( pReader->nData==0 ){ - pReader->pData = NULL; - return; - } - - n = getVarint32(pReader->pData, &i); - if( i==POS_COLUMN ){ - n += getVarint32(pReader->pData+n, &pReader->iColumn); - pReader->iPosition = 0; - pReader->iStartOffset = 0; - n += getVarint32(pReader->pData+n, &i); - } - /* Should never see adjacent column changes. */ - assert( i!=POS_COLUMN ); - - if( i==POS_END ){ - pReader->nData = 0; - pReader->pData = NULL; - return; - } - - pReader->iPosition += i-POS_BASE; - if( pReader->iType==DL_POSITIONS_OFFSETS ){ - n += getVarint32(pReader->pData+n, &i); - pReader->iStartOffset += i; - n += getVarint32(pReader->pData+n, &i); - pReader->iEndOffset = pReader->iStartOffset+i; - } - assert( n<=pReader->nData ); - pReader->pData += n; - pReader->nData -= n; -} - -static void plrInit(PLReader *pReader, DLReader *pDLReader){ - pReader->pData = dlrPosData(pDLReader); - pReader->nData = dlrPosDataLen(pDLReader); - pReader->iType = pDLReader->iType; - pReader->iColumn = 0; - pReader->iPosition = 0; - pReader->iStartOffset = 0; - pReader->iEndOffset = 0; - plrStep(pReader); -} -static void plrDestroy(PLReader *pReader){ - SCRAMBLE(pReader); -} - -/*******************************************************************/ -/* PLWriter is used in constructing a document's position list. As a -** convenience, if iType is DL_DOCIDS, PLWriter becomes a no-op. -** PLWriter writes to the associated DLWriter's buffer. -** -** plwInit - init for writing a document's poslist. -** plwDestroy - clear a writer. -** plwAdd - append position and offset information. -** plwCopy - copy next position's data from reader to writer. -** plwTerminate - add any necessary doclist terminator. -** -** Calling plwAdd() after plwTerminate() may result in a corrupt -** doclist. -*/ -/* TODO(shess) Until we've written the second item, we can cache the -** first item's information. Then we'd have three states: -** -** - initialized with docid, no positions. -** - docid and one position. -** - docid and multiple positions. -** -** Only the last state needs to actually write to dlw->b, which would -** be an improvement in the DLCollector case. -*/ -typedef struct PLWriter { - DLWriter *dlw; - - int iColumn; /* the last column written */ - int iPos; /* the last position written */ - int iOffset; /* the last start offset written */ -} PLWriter; - -/* TODO(shess) In the case where the parent is reading these values -** from a PLReader, we could optimize to a copy if that PLReader has -** the same type as pWriter. -*/ -static void plwAdd(PLWriter *pWriter, int iColumn, int iPos, - int iStartOffset, int iEndOffset){ - /* Worst-case space for POS_COLUMN, iColumn, iPosDelta, - ** iStartOffsetDelta, and iEndOffsetDelta. - */ - char c[5*VARINT_MAX]; - int n = 0; - - /* Ban plwAdd() after plwTerminate(). */ - assert( pWriter->iPos!=-1 ); - - if( pWriter->dlw->iType==DL_DOCIDS ) return; - - if( iColumn!=pWriter->iColumn ){ - n += putVarint(c+n, POS_COLUMN); - n += putVarint(c+n, iColumn); - pWriter->iColumn = iColumn; - pWriter->iPos = 0; - pWriter->iOffset = 0; - } - assert( iPos>=pWriter->iPos ); - n += putVarint(c+n, POS_BASE+(iPos-pWriter->iPos)); - pWriter->iPos = iPos; - if( pWriter->dlw->iType==DL_POSITIONS_OFFSETS ){ - assert( iStartOffset>=pWriter->iOffset ); - n += putVarint(c+n, iStartOffset-pWriter->iOffset); - pWriter->iOffset = iStartOffset; - assert( iEndOffset>=iStartOffset ); - n += putVarint(c+n, iEndOffset-iStartOffset); - } - dataBufferAppend(pWriter->dlw->b, c, n); -} -static void plwCopy(PLWriter *pWriter, PLReader *pReader){ - plwAdd(pWriter, plrColumn(pReader), plrPosition(pReader), - plrStartOffset(pReader), plrEndOffset(pReader)); -} -static void plwInit(PLWriter *pWriter, DLWriter *dlw, sqlite_int64 iDocid){ - char c[VARINT_MAX]; - int n; - - pWriter->dlw = dlw; - - /* Docids must ascend. */ - assert( !pWriter->dlw->has_iPrevDocid || iDocid>pWriter->dlw->iPrevDocid ); - n = putVarint(c, iDocid-pWriter->dlw->iPrevDocid); - dataBufferAppend(pWriter->dlw->b, c, n); - pWriter->dlw->iPrevDocid = iDocid; -#ifndef NDEBUG - pWriter->dlw->has_iPrevDocid = 1; -#endif - - pWriter->iColumn = 0; - pWriter->iPos = 0; - pWriter->iOffset = 0; -} -/* TODO(shess) Should plwDestroy() also terminate the doclist? But -** then plwDestroy() would no longer be just a destructor, it would -** also be doing work, which isn't consistent with the overall idiom. -** Another option would be for plwAdd() to always append any necessary -** terminator, so that the output is always correct. But that would -** add incremental work to the common case with the only benefit being -** API elegance. Punt for now. -*/ -static void plwTerminate(PLWriter *pWriter){ - if( pWriter->dlw->iType>DL_DOCIDS ){ - char c[VARINT_MAX]; - int n = putVarint(c, POS_END); - dataBufferAppend(pWriter->dlw->b, c, n); - } -#ifndef NDEBUG - /* Mark as terminated for assert in plwAdd(). */ - pWriter->iPos = -1; -#endif -} -static void plwDestroy(PLWriter *pWriter){ - SCRAMBLE(pWriter); -} - -/*******************************************************************/ -/* DLCollector wraps PLWriter and DLWriter to provide a -** dynamically-allocated doclist area to use during tokenization. -** -** dlcNew - malloc up and initialize a collector. -** dlcDelete - destroy a collector and all contained items. -** dlcAddPos - append position and offset information. -** dlcAddDoclist - add the collected doclist to the given buffer. -** dlcNext - terminate the current document and open another. -*/ -typedef struct DLCollector { - DataBuffer b; - DLWriter dlw; - PLWriter plw; -} DLCollector; - -/* TODO(shess) This could also be done by calling plwTerminate() and -** dataBufferAppend(). I tried that, expecting nominal performance -** differences, but it seemed to pretty reliably be worth 1% to code -** it this way. I suspect it is the incremental malloc overhead (some -** percentage of the plwTerminate() calls will cause a realloc), so -** this might be worth revisiting if the DataBuffer implementation -** changes. -*/ -static void dlcAddDoclist(DLCollector *pCollector, DataBuffer *b){ - if( pCollector->dlw.iType>DL_DOCIDS ){ - char c[VARINT_MAX]; - int n = putVarint(c, POS_END); - dataBufferAppend2(b, pCollector->b.pData, pCollector->b.nData, c, n); - }else{ - dataBufferAppend(b, pCollector->b.pData, pCollector->b.nData); - } -} -static void dlcNext(DLCollector *pCollector, sqlite_int64 iDocid){ - plwTerminate(&pCollector->plw); - plwDestroy(&pCollector->plw); - plwInit(&pCollector->plw, &pCollector->dlw, iDocid); -} -static void dlcAddPos(DLCollector *pCollector, int iColumn, int iPos, - int iStartOffset, int iEndOffset){ - plwAdd(&pCollector->plw, iColumn, iPos, iStartOffset, iEndOffset); -} - -static DLCollector *dlcNew(sqlite_int64 iDocid, DocListType iType){ - DLCollector *pCollector = sqlite3_malloc(sizeof(DLCollector)); - dataBufferInit(&pCollector->b, 0); - dlwInit(&pCollector->dlw, iType, &pCollector->b); - plwInit(&pCollector->plw, &pCollector->dlw, iDocid); - return pCollector; -} -static void dlcDelete(DLCollector *pCollector){ - plwDestroy(&pCollector->plw); - dlwDestroy(&pCollector->dlw); - dataBufferDestroy(&pCollector->b); - SCRAMBLE(pCollector); - sqlite3_free(pCollector); -} - - -/* Copy the doclist data of iType in pData/nData into *out, trimming -** unnecessary data as we go. Only columns matching iColumn are -** copied, all columns copied if iColumn is -1. Elements with no -** matching columns are dropped. The output is an iOutType doclist. -*/ -/* NOTE(shess) This code is only valid after all doclists are merged. -** If this is run before merges, then doclist items which represent -** deletion will be trimmed, and will thus not effect a deletion -** during the merge. -*/ -static void docListTrim(DocListType iType, const char *pData, int nData, - int iColumn, DocListType iOutType, DataBuffer *out){ - DLReader dlReader; - DLWriter dlWriter; - - assert( iOutType<=iType ); - - dlrInit(&dlReader, iType, pData, nData); - dlwInit(&dlWriter, iOutType, out); - - while( !dlrAtEnd(&dlReader) ){ - PLReader plReader; - PLWriter plWriter; - int match = 0; - - plrInit(&plReader, &dlReader); - - while( !plrAtEnd(&plReader) ){ - if( iColumn==-1 || plrColumn(&plReader)==iColumn ){ - if( !match ){ - plwInit(&plWriter, &dlWriter, dlrDocid(&dlReader)); - match = 1; - } - plwAdd(&plWriter, plrColumn(&plReader), plrPosition(&plReader), - plrStartOffset(&plReader), plrEndOffset(&plReader)); - } - plrStep(&plReader); - } - if( match ){ - plwTerminate(&plWriter); - plwDestroy(&plWriter); - } - - plrDestroy(&plReader); - dlrStep(&dlReader); - } - dlwDestroy(&dlWriter); - dlrDestroy(&dlReader); -} - -/* Used by docListMerge() to keep doclists in the ascending order by -** docid, then ascending order by age (so the newest comes first). -*/ -typedef struct OrderedDLReader { - DLReader *pReader; - - /* TODO(shess) If we assume that docListMerge pReaders is ordered by - ** age (which we do), then we could use pReader comparisons to break - ** ties. - */ - int idx; -} OrderedDLReader; - -/* Order eof to end, then by docid asc, idx desc. */ -static int orderedDLReaderCmp(OrderedDLReader *r1, OrderedDLReader *r2){ - if( dlrAtEnd(r1->pReader) ){ - if( dlrAtEnd(r2->pReader) ) return 0; /* Both atEnd(). */ - return 1; /* Only r1 atEnd(). */ - } - if( dlrAtEnd(r2->pReader) ) return -1; /* Only r2 atEnd(). */ - - if( dlrDocid(r1->pReader)pReader) ) return -1; - if( dlrDocid(r1->pReader)>dlrDocid(r2->pReader) ) return 1; - - /* Descending on idx. */ - return r2->idx-r1->idx; -} - -/* Bubble p[0] to appropriate place in p[1..n-1]. Assumes that -** p[1..n-1] is already sorted. -*/ -/* TODO(shess) Is this frequent enough to warrant a binary search? -** Before implementing that, instrument the code to check. In most -** current usage, I expect that p[0] will be less than p[1] a very -** high proportion of the time. -*/ -static void orderedDLReaderReorder(OrderedDLReader *p, int n){ - while( n>1 && orderedDLReaderCmp(p, p+1)>0 ){ - OrderedDLReader tmp = p[0]; - p[0] = p[1]; - p[1] = tmp; - n--; - p++; - } -} - -/* Given an array of doclist readers, merge their doclist elements -** into out in sorted order (by docid), dropping elements from older -** readers when there is a duplicate docid. pReaders is assumed to be -** ordered by age, oldest first. -*/ -/* TODO(shess) nReaders must be <= MERGE_COUNT. This should probably -** be fixed. -*/ -static void docListMerge(DataBuffer *out, - DLReader *pReaders, int nReaders){ - OrderedDLReader readers[MERGE_COUNT]; - DLWriter writer; - int i, n; - const char *pStart = 0; - int nStart = 0; - sqlite_int64 iFirstDocid = 0, iLastDocid = 0; - - assert( nReaders>0 ); - if( nReaders==1 ){ - dataBufferAppend(out, dlrDocData(pReaders), dlrAllDataBytes(pReaders)); - return; - } - - assert( nReaders<=MERGE_COUNT ); - n = 0; - for(i=0; i0 ){ - orderedDLReaderReorder(readers+i, nReaders-i); - } - - dlwInit(&writer, pReaders[0].iType, out); - while( !dlrAtEnd(readers[0].pReader) ){ - sqlite_int64 iDocid = dlrDocid(readers[0].pReader); - - /* If this is a continuation of the current buffer to copy, extend - ** that buffer. memcpy() seems to be more efficient if it has a - ** lots of data to copy. - */ - if( dlrDocData(readers[0].pReader)==pStart+nStart ){ - nStart += dlrDocDataBytes(readers[0].pReader); - }else{ - if( pStart!=0 ){ - dlwAppend(&writer, pStart, nStart, iFirstDocid, iLastDocid); - } - pStart = dlrDocData(readers[0].pReader); - nStart = dlrDocDataBytes(readers[0].pReader); - iFirstDocid = iDocid; - } - iLastDocid = iDocid; - dlrStep(readers[0].pReader); - - /* Drop all of the older elements with the same docid. */ - for(i=1; i0 ){ - orderedDLReaderReorder(readers+i, nReaders-i); - } - } - - /* Copy over any remaining elements. */ - if( nStart>0 ) dlwAppend(&writer, pStart, nStart, iFirstDocid, iLastDocid); - dlwDestroy(&writer); -} - -/* Helper function for posListUnion(). Compares the current position -** between left and right, returning as standard C idiom of <0 if -** left0 if left>right, and 0 if left==right. "End" always -** compares greater. -*/ -static int posListCmp(PLReader *pLeft, PLReader *pRight){ - assert( pLeft->iType==pRight->iType ); - if( pLeft->iType==DL_DOCIDS ) return 0; - - if( plrAtEnd(pLeft) ) return plrAtEnd(pRight) ? 0 : 1; - if( plrAtEnd(pRight) ) return -1; - - if( plrColumn(pLeft)plrColumn(pRight) ) return 1; - - if( plrPosition(pLeft)plrPosition(pRight) ) return 1; - if( pLeft->iType==DL_POSITIONS ) return 0; - - if( plrStartOffset(pLeft)plrStartOffset(pRight) ) return 1; - - if( plrEndOffset(pLeft)plrEndOffset(pRight) ) return 1; - - return 0; -} - -/* Write the union of position lists in pLeft and pRight to pOut. -** "Union" in this case meaning "All unique position tuples". Should -** work with any doclist type, though both inputs and the output -** should be the same type. -*/ -static void posListUnion(DLReader *pLeft, DLReader *pRight, DLWriter *pOut){ - PLReader left, right; - PLWriter writer; - - assert( dlrDocid(pLeft)==dlrDocid(pRight) ); - assert( pLeft->iType==pRight->iType ); - assert( pLeft->iType==pOut->iType ); - - plrInit(&left, pLeft); - plrInit(&right, pRight); - plwInit(&writer, pOut, dlrDocid(pLeft)); - - while( !plrAtEnd(&left) || !plrAtEnd(&right) ){ - int c = posListCmp(&left, &right); - if( c<0 ){ - plwCopy(&writer, &left); - plrStep(&left); - }else if( c>0 ){ - plwCopy(&writer, &right); - plrStep(&right); - }else{ - plwCopy(&writer, &left); - plrStep(&left); - plrStep(&right); - } - } - - plwTerminate(&writer); - plwDestroy(&writer); - plrDestroy(&left); - plrDestroy(&right); -} - -/* Write the union of doclists in pLeft and pRight to pOut. For -** docids in common between the inputs, the union of the position -** lists is written. Inputs and outputs are always type DL_DEFAULT. -*/ -static void docListUnion( - const char *pLeft, int nLeft, - const char *pRight, int nRight, - DataBuffer *pOut /* Write the combined doclist here */ -){ - DLReader left, right; - DLWriter writer; - - if( nLeft==0 ){ - if( nRight!=0) dataBufferAppend(pOut, pRight, nRight); - return; - } - if( nRight==0 ){ - dataBufferAppend(pOut, pLeft, nLeft); - return; - } - - dlrInit(&left, DL_DEFAULT, pLeft, nLeft); - dlrInit(&right, DL_DEFAULT, pRight, nRight); - dlwInit(&writer, DL_DEFAULT, pOut); - - while( !dlrAtEnd(&left) || !dlrAtEnd(&right) ){ - if( dlrAtEnd(&right) ){ - dlwCopy(&writer, &left); - dlrStep(&left); - }else if( dlrAtEnd(&left) ){ - dlwCopy(&writer, &right); - dlrStep(&right); - }else if( dlrDocid(&left)dlrDocid(&right) ){ - dlwCopy(&writer, &right); - dlrStep(&right); - }else{ - posListUnion(&left, &right, &writer); - dlrStep(&left); - dlrStep(&right); - } - } - - dlrDestroy(&left); - dlrDestroy(&right); - dlwDestroy(&writer); -} - -/* pLeft and pRight are DLReaders positioned to the same docid. -** -** If there are no instances in pLeft or pRight where the position -** of pLeft is one less than the position of pRight, then this -** routine adds nothing to pOut. -** -** If there are one or more instances where positions from pLeft -** are exactly one less than positions from pRight, then add a new -** document record to pOut. If pOut wants to hold positions, then -** include the positions from pRight that are one more than a -** position in pLeft. In other words: pRight.iPos==pLeft.iPos+1. -*/ -static void posListPhraseMerge(DLReader *pLeft, DLReader *pRight, - DLWriter *pOut){ - PLReader left, right; - PLWriter writer; - int match = 0; - - assert( dlrDocid(pLeft)==dlrDocid(pRight) ); - assert( pOut->iType!=DL_POSITIONS_OFFSETS ); - - plrInit(&left, pLeft); - plrInit(&right, pRight); - - while( !plrAtEnd(&left) && !plrAtEnd(&right) ){ - if( plrColumn(&left)plrColumn(&right) ){ - plrStep(&right); - }else if( plrPosition(&left)+1plrPosition(&right) ){ - plrStep(&right); - }else{ - if( !match ){ - plwInit(&writer, pOut, dlrDocid(pLeft)); - match = 1; - } - plwAdd(&writer, plrColumn(&right), plrPosition(&right), 0, 0); - plrStep(&left); - plrStep(&right); - } - } - - if( match ){ - plwTerminate(&writer); - plwDestroy(&writer); - } - - plrDestroy(&left); - plrDestroy(&right); -} - -/* We have two doclists with positions: pLeft and pRight. -** Write the phrase intersection of these two doclists into pOut. -** -** A phrase intersection means that two documents only match -** if pLeft.iPos+1==pRight.iPos. -** -** iType controls the type of data written to pOut. If iType is -** DL_POSITIONS, the positions are those from pRight. -*/ -static void docListPhraseMerge( - const char *pLeft, int nLeft, - const char *pRight, int nRight, - DocListType iType, - DataBuffer *pOut /* Write the combined doclist here */ -){ - DLReader left, right; - DLWriter writer; - - if( nLeft==0 || nRight==0 ) return; - - assert( iType!=DL_POSITIONS_OFFSETS ); - - dlrInit(&left, DL_POSITIONS, pLeft, nLeft); - dlrInit(&right, DL_POSITIONS, pRight, nRight); - dlwInit(&writer, iType, pOut); - - while( !dlrAtEnd(&left) && !dlrAtEnd(&right) ){ - if( dlrDocid(&left) one AND (two OR three) - * [one OR two three] ==> (one OR two) AND three - * - * A "-" before a term matches all entries that lack that term. - * The "-" must occur immediately before the term with in intervening - * space. This is how the search engines do it. - * - * A NOT term cannot be the right-hand operand of an OR. If this - * occurs in the query string, the NOT is ignored: - * - * [one OR -two] ==> one OR two - * - */ -typedef struct Query { - fulltext_vtab *pFts; /* The full text index */ - int nTerms; /* Number of terms in the query */ - QueryTerm *pTerms; /* Array of terms. Space obtained from malloc() */ - int nextIsOr; /* Set the isOr flag on the next inserted term */ - int nextColumn; /* Next word parsed must be in this column */ - int dfltColumn; /* The default column */ -} Query; - - -/* -** An instance of the following structure keeps track of generated -** matching-word offset information and snippets. -*/ -typedef struct Snippet { - int nMatch; /* Total number of matches */ - int nAlloc; /* Space allocated for aMatch[] */ - struct snippetMatch { /* One entry for each matching term */ - char snStatus; /* Status flag for use while constructing snippets */ - short int iCol; /* The column that contains the match */ - short int iTerm; /* The index in Query.pTerms[] of the matching term */ - short int nByte; /* Number of bytes in the term */ - int iStart; /* The offset to the first character of the term */ - } *aMatch; /* Points to space obtained from malloc */ - char *zOffset; /* Text rendering of aMatch[] */ - int nOffset; /* strlen(zOffset) */ - char *zSnippet; /* Snippet text */ - int nSnippet; /* strlen(zSnippet) */ -} Snippet; - - -typedef enum QueryType { - QUERY_GENERIC, /* table scan */ - QUERY_ROWID, /* lookup by rowid */ - QUERY_FULLTEXT /* QUERY_FULLTEXT + [i] is a full-text search for column i*/ -} QueryType; - -typedef enum fulltext_statement { - CONTENT_INSERT_STMT, - CONTENT_SELECT_STMT, - CONTENT_UPDATE_STMT, - CONTENT_DELETE_STMT, - CONTENT_EXISTS_STMT, - - BLOCK_INSERT_STMT, - BLOCK_SELECT_STMT, - BLOCK_DELETE_STMT, - BLOCK_DELETE_ALL_STMT, - - SEGDIR_MAX_INDEX_STMT, - SEGDIR_SET_STMT, - SEGDIR_SELECT_LEVEL_STMT, - SEGDIR_SPAN_STMT, - SEGDIR_DELETE_STMT, - SEGDIR_SELECT_SEGMENT_STMT, - SEGDIR_SELECT_ALL_STMT, - SEGDIR_DELETE_ALL_STMT, - SEGDIR_COUNT_STMT, - - MAX_STMT /* Always at end! */ -} fulltext_statement; - -/* These must exactly match the enum above. */ -/* TODO(shess): Is there some risk that a statement will be used in two -** cursors at once, e.g. if a query joins a virtual table to itself? -** If so perhaps we should move some of these to the cursor object. -*/ -static const char *const fulltext_zStatement[MAX_STMT] = { - /* CONTENT_INSERT */ NULL, /* generated in contentInsertStatement() */ - /* CONTENT_SELECT */ "select * from %_content where rowid = ?", - /* CONTENT_UPDATE */ NULL, /* generated in contentUpdateStatement() */ - /* CONTENT_DELETE */ "delete from %_content where rowid = ?", - /* CONTENT_EXISTS */ "select rowid from %_content limit 1", - - /* BLOCK_INSERT */ "insert into %_segments values (?)", - /* BLOCK_SELECT */ "select block from %_segments where rowid = ?", - /* BLOCK_DELETE */ "delete from %_segments where rowid between ? and ?", - /* BLOCK_DELETE_ALL */ "delete from %_segments", - - /* SEGDIR_MAX_INDEX */ "select max(idx) from %_segdir where level = ?", - /* SEGDIR_SET */ "insert into %_segdir values (?, ?, ?, ?, ?, ?)", - /* SEGDIR_SELECT_LEVEL */ - "select start_block, leaves_end_block, root from %_segdir " - " where level = ? order by idx", - /* SEGDIR_SPAN */ - "select min(start_block), max(end_block) from %_segdir " - " where level = ? and start_block <> 0", - /* SEGDIR_DELETE */ "delete from %_segdir where level = ?", - - /* NOTE(shess): The first three results of the following two - ** statements must match. - */ - /* SEGDIR_SELECT_SEGMENT */ - "select start_block, leaves_end_block, root from %_segdir " - " where level = ? and idx = ?", - /* SEGDIR_SELECT_ALL */ - "select start_block, leaves_end_block, root from %_segdir " - " order by level desc, idx asc", - /* SEGDIR_DELETE_ALL */ "delete from %_segdir", - /* SEGDIR_COUNT */ "select count(*), ifnull(max(level),0) from %_segdir", -}; - -/* -** A connection to a fulltext index is an instance of the following -** structure. The xCreate and xConnect methods create an instance -** of this structure and xDestroy and xDisconnect free that instance. -** All other methods receive a pointer to the structure as one of their -** arguments. -*/ -struct fulltext_vtab { - sqlite3_vtab base; /* Base class used by SQLite core */ - sqlite3 *db; /* The database connection */ - const char *zDb; /* logical database name */ - const char *zName; /* virtual table name */ - int nColumn; /* number of columns in virtual table */ - char **azColumn; /* column names. malloced */ - char **azContentColumn; /* column names in content table; malloced */ - sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ - - /* Precompiled statements which we keep as long as the table is - ** open. - */ - sqlite3_stmt *pFulltextStatements[MAX_STMT]; - - /* Precompiled statements used for segment merges. We run a - ** separate select across the leaf level of each tree being merged. - */ - sqlite3_stmt *pLeafSelectStmts[MERGE_COUNT]; - /* The statement used to prepare pLeafSelectStmts. */ -#define LEAF_SELECT \ - "select block from %_segments where rowid between ? and ? order by rowid" - - /* These buffer pending index updates during transactions. - ** nPendingData estimates the memory size of the pending data. It - ** doesn't include the hash-bucket overhead, nor any malloc - ** overhead. When nPendingData exceeds kPendingThreshold, the - ** buffer is flushed even before the transaction closes. - ** pendingTerms stores the data, and is only valid when nPendingData - ** is >=0 (nPendingData<0 means pendingTerms has not been - ** initialized). iPrevDocid is the last docid written, used to make - ** certain we're inserting in sorted order. - */ - int nPendingData; -#define kPendingThreshold (1*1024*1024) - sqlite_int64 iPrevDocid; - fts2Hash pendingTerms; -}; - -/* -** When the core wants to do a query, it create a cursor using a -** call to xOpen. This structure is an instance of a cursor. It -** is destroyed by xClose. -*/ -typedef struct fulltext_cursor { - sqlite3_vtab_cursor base; /* Base class used by SQLite core */ - QueryType iCursorType; /* Copy of sqlite3_index_info.idxNum */ - sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */ - int eof; /* True if at End Of Results */ - Query q; /* Parsed query string */ - Snippet snippet; /* Cached snippet for the current row */ - int iColumn; /* Column being searched */ - DataBuffer result; /* Doclist results from fulltextQuery */ - DLReader reader; /* Result reader if result not empty */ -} fulltext_cursor; - -static struct fulltext_vtab *cursor_vtab(fulltext_cursor *c){ - return (fulltext_vtab *) c->base.pVtab; -} - -static const sqlite3_module fts2Module; /* forward declaration */ - -/* Return a dynamically generated statement of the form - * insert into %_content (rowid, ...) values (?, ...) - */ -static const char *contentInsertStatement(fulltext_vtab *v){ - StringBuffer sb; - int i; - - initStringBuffer(&sb); - append(&sb, "insert into %_content (rowid, "); - appendList(&sb, v->nColumn, v->azContentColumn); - append(&sb, ") values (?"); - for(i=0; inColumn; ++i) - append(&sb, ", ?"); - append(&sb, ")"); - return stringBufferData(&sb); -} - -/* Return a dynamically generated statement of the form - * update %_content set [col_0] = ?, [col_1] = ?, ... - * where rowid = ? - */ -static const char *contentUpdateStatement(fulltext_vtab *v){ - StringBuffer sb; - int i; - - initStringBuffer(&sb); - append(&sb, "update %_content set "); - for(i=0; inColumn; ++i) { - if( i>0 ){ - append(&sb, ", "); - } - append(&sb, v->azContentColumn[i]); - append(&sb, " = ?"); - } - append(&sb, " where rowid = ?"); - return stringBufferData(&sb); -} - -/* Puts a freshly-prepared statement determined by iStmt in *ppStmt. -** If the indicated statement has never been prepared, it is prepared -** and cached, otherwise the cached version is reset. -*/ -static int sql_get_statement(fulltext_vtab *v, fulltext_statement iStmt, - sqlite3_stmt **ppStmt){ - assert( iStmtpFulltextStatements[iStmt]==NULL ){ - const char *zStmt; - int rc; - switch( iStmt ){ - case CONTENT_INSERT_STMT: - zStmt = contentInsertStatement(v); break; - case CONTENT_UPDATE_STMT: - zStmt = contentUpdateStatement(v); break; - default: - zStmt = fulltext_zStatement[iStmt]; - } - rc = sql_prepare(v->db, v->zDb, v->zName, &v->pFulltextStatements[iStmt], - zStmt); - if( zStmt != fulltext_zStatement[iStmt]) sqlite3_free((void *) zStmt); - if( rc!=SQLITE_OK ) return rc; - } else { - int rc = sqlite3_reset(v->pFulltextStatements[iStmt]); - if( rc!=SQLITE_OK ) return rc; - } - - *ppStmt = v->pFulltextStatements[iStmt]; - return SQLITE_OK; -} - -/* Like sqlite3_step(), but convert SQLITE_DONE to SQLITE_OK and -** SQLITE_ROW to SQLITE_ERROR. Useful for statements like UPDATE, -** where we expect no results. -*/ -static int sql_single_step(sqlite3_stmt *s){ - int rc = sqlite3_step(s); - return (rc==SQLITE_DONE) ? SQLITE_OK : rc; -} - -/* Like sql_get_statement(), but for special replicated LEAF_SELECT -** statements. idx -1 is a special case for an uncached version of -** the statement (used in the optimize implementation). -*/ -/* TODO(shess) Write version for generic statements and then share -** that between the cached-statement functions. -*/ -static int sql_get_leaf_statement(fulltext_vtab *v, int idx, - sqlite3_stmt **ppStmt){ - assert( idx>=-1 && idxdb, v->zDb, v->zName, ppStmt, LEAF_SELECT); - }else if( v->pLeafSelectStmts[idx]==NULL ){ - int rc = sql_prepare(v->db, v->zDb, v->zName, &v->pLeafSelectStmts[idx], - LEAF_SELECT); - if( rc!=SQLITE_OK ) return rc; - }else{ - int rc = sqlite3_reset(v->pLeafSelectStmts[idx]); - if( rc!=SQLITE_OK ) return rc; - } - - *ppStmt = v->pLeafSelectStmts[idx]; - return SQLITE_OK; -} - -/* insert into %_content (rowid, ...) values ([rowid], [pValues]) */ -static int content_insert(fulltext_vtab *v, sqlite3_value *rowid, - sqlite3_value **pValues){ - sqlite3_stmt *s; - int i; - int rc = sql_get_statement(v, CONTENT_INSERT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_value(s, 1, rowid); - if( rc!=SQLITE_OK ) return rc; - - for(i=0; inColumn; ++i){ - rc = sqlite3_bind_value(s, 2+i, pValues[i]); - if( rc!=SQLITE_OK ) return rc; - } - - return sql_single_step(s); -} - -/* update %_content set col0 = pValues[0], col1 = pValues[1], ... - * where rowid = [iRowid] */ -static int content_update(fulltext_vtab *v, sqlite3_value **pValues, - sqlite_int64 iRowid){ - sqlite3_stmt *s; - int i; - int rc = sql_get_statement(v, CONTENT_UPDATE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - for(i=0; inColumn; ++i){ - rc = sqlite3_bind_value(s, 1+i, pValues[i]); - if( rc!=SQLITE_OK ) return rc; - } - - rc = sqlite3_bind_int64(s, 1+v->nColumn, iRowid); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step(s); -} - -static void freeStringArray(int nString, const char **pString){ - int i; - - for (i=0 ; i < nString ; ++i) { - if( pString[i]!=NULL ) sqlite3_free((void *) pString[i]); - } - sqlite3_free((void *) pString); -} - -/* select * from %_content where rowid = [iRow] - * The caller must delete the returned array and all strings in it. - * null fields will be NULL in the returned array. - * - * TODO: Perhaps we should return pointer/length strings here for consistency - * with other code which uses pointer/length. */ -static int content_select(fulltext_vtab *v, sqlite_int64 iRow, - const char ***pValues){ - sqlite3_stmt *s; - const char **values; - int i; - int rc; - - *pValues = NULL; - - rc = sql_get_statement(v, CONTENT_SELECT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iRow); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - if( rc!=SQLITE_ROW ) return rc; - - values = (const char **) sqlite3_malloc(v->nColumn * sizeof(const char *)); - for(i=0; inColumn; ++i){ - if( sqlite3_column_type(s, i)==SQLITE_NULL ){ - values[i] = NULL; - }else{ - values[i] = string_dup((char*)sqlite3_column_text(s, i)); - } - } - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ){ - *pValues = values; - return SQLITE_OK; - } - - freeStringArray(v->nColumn, values); - return rc; -} - -/* delete from %_content where rowid = [iRow ] */ -static int content_delete(fulltext_vtab *v, sqlite_int64 iRow){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, CONTENT_DELETE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iRow); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step(s); -} - -/* Returns SQLITE_ROW if any rows exist in %_content, SQLITE_DONE if -** no rows exist, and any error in case of failure. -*/ -static int content_exists(fulltext_vtab *v){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, CONTENT_EXISTS_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - if( rc!=SQLITE_ROW ) return rc; - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ) return SQLITE_ROW; - if( rc==SQLITE_ROW ) return SQLITE_ERROR; - return rc; -} - -/* insert into %_segments values ([pData]) -** returns assigned rowid in *piBlockid -*/ -static int block_insert(fulltext_vtab *v, const char *pData, int nData, - sqlite_int64 *piBlockid){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, BLOCK_INSERT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_blob(s, 1, pData, nData, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - if( rc==SQLITE_ROW ) return SQLITE_ERROR; - if( rc!=SQLITE_DONE ) return rc; - - *piBlockid = sqlite3_last_insert_rowid(v->db); - return SQLITE_OK; -} - -/* delete from %_segments -** where rowid between [iStartBlockid] and [iEndBlockid] -** -** Deletes the range of blocks, inclusive, used to delete the blocks -** which form a segment. -*/ -static int block_delete(fulltext_vtab *v, - sqlite_int64 iStartBlockid, sqlite_int64 iEndBlockid){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, BLOCK_DELETE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iStartBlockid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 2, iEndBlockid); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step(s); -} - -/* Returns SQLITE_ROW with *pidx set to the maximum segment idx found -** at iLevel. Returns SQLITE_DONE if there are no segments at -** iLevel. Otherwise returns an error. -*/ -static int segdir_max_index(fulltext_vtab *v, int iLevel, int *pidx){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, SEGDIR_MAX_INDEX_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int(s, 1, iLevel); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - /* Should always get at least one row due to how max() works. */ - if( rc==SQLITE_DONE ) return SQLITE_DONE; - if( rc!=SQLITE_ROW ) return rc; - - /* NULL means that there were no inputs to max(). */ - if( SQLITE_NULL==sqlite3_column_type(s, 0) ){ - rc = sqlite3_step(s); - if( rc==SQLITE_ROW ) return SQLITE_ERROR; - return rc; - } - - *pidx = sqlite3_column_int(s, 0); - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_ROW ) return SQLITE_ERROR; - if( rc!=SQLITE_DONE ) return rc; - return SQLITE_ROW; -} - -/* insert into %_segdir values ( -** [iLevel], [idx], -** [iStartBlockid], [iLeavesEndBlockid], [iEndBlockid], -** [pRootData] -** ) -*/ -static int segdir_set(fulltext_vtab *v, int iLevel, int idx, - sqlite_int64 iStartBlockid, - sqlite_int64 iLeavesEndBlockid, - sqlite_int64 iEndBlockid, - const char *pRootData, int nRootData){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, SEGDIR_SET_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int(s, 1, iLevel); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int(s, 2, idx); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 3, iStartBlockid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 4, iLeavesEndBlockid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 5, iEndBlockid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_blob(s, 6, pRootData, nRootData, SQLITE_STATIC); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step(s); -} - -/* Queries %_segdir for the block span of the segments in level -** iLevel. Returns SQLITE_DONE if there are no blocks for iLevel, -** SQLITE_ROW if there are blocks, else an error. -*/ -static int segdir_span(fulltext_vtab *v, int iLevel, - sqlite_int64 *piStartBlockid, - sqlite_int64 *piEndBlockid){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, SEGDIR_SPAN_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int(s, 1, iLevel); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ) return SQLITE_DONE; /* Should never happen */ - if( rc!=SQLITE_ROW ) return rc; - - /* This happens if all segments at this level are entirely inline. */ - if( SQLITE_NULL==sqlite3_column_type(s, 0) ){ - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - int rc2 = sqlite3_step(s); - if( rc2==SQLITE_ROW ) return SQLITE_ERROR; - return rc2; - } - - *piStartBlockid = sqlite3_column_int64(s, 0); - *piEndBlockid = sqlite3_column_int64(s, 1); - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_ROW ) return SQLITE_ERROR; - if( rc!=SQLITE_DONE ) return rc; - return SQLITE_ROW; -} - -/* Delete the segment blocks and segment directory records for all -** segments at iLevel. -*/ -static int segdir_delete(fulltext_vtab *v, int iLevel){ - sqlite3_stmt *s; - sqlite_int64 iStartBlockid, iEndBlockid; - int rc = segdir_span(v, iLevel, &iStartBlockid, &iEndBlockid); - if( rc!=SQLITE_ROW && rc!=SQLITE_DONE ) return rc; - - if( rc==SQLITE_ROW ){ - rc = block_delete(v, iStartBlockid, iEndBlockid); - if( rc!=SQLITE_OK ) return rc; - } - - /* Delete the segment directory itself. */ - rc = sql_get_statement(v, SEGDIR_DELETE_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iLevel); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step(s); -} - -/* Delete entire fts index, SQLITE_OK on success, relevant error on -** failure. -*/ -static int segdir_delete_all(fulltext_vtab *v){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, SEGDIR_DELETE_ALL_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sql_single_step(s); - if( rc!=SQLITE_OK ) return rc; - - rc = sql_get_statement(v, BLOCK_DELETE_ALL_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - return sql_single_step(s); -} - -/* Returns SQLITE_OK with *pnSegments set to the number of entries in -** %_segdir and *piMaxLevel set to the highest level which has a -** segment. Otherwise returns the SQLite error which caused failure. -*/ -static int segdir_count(fulltext_vtab *v, int *pnSegments, int *piMaxLevel){ - sqlite3_stmt *s; - int rc = sql_get_statement(v, SEGDIR_COUNT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - /* TODO(shess): This case should not be possible? Should stronger - ** measures be taken if it happens? - */ - if( rc==SQLITE_DONE ){ - *pnSegments = 0; - *piMaxLevel = 0; - return SQLITE_OK; - } - if( rc!=SQLITE_ROW ) return rc; - - *pnSegments = sqlite3_column_int(s, 0); - *piMaxLevel = sqlite3_column_int(s, 1); - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ) return SQLITE_OK; - if( rc==SQLITE_ROW ) return SQLITE_ERROR; - return rc; -} - -/* TODO(shess) clearPendingTerms() is far down the file because -** writeZeroSegment() is far down the file because LeafWriter is far -** down the file. Consider refactoring the code to move the non-vtab -** code above the vtab code so that we don't need this forward -** reference. -*/ -static int clearPendingTerms(fulltext_vtab *v); - -/* -** Free the memory used to contain a fulltext_vtab structure. -*/ -static void fulltext_vtab_destroy(fulltext_vtab *v){ - int iStmt, i; - - TRACE(("FTS2 Destroy %p\n", v)); - for( iStmt=0; iStmtpFulltextStatements[iStmt]!=NULL ){ - sqlite3_finalize(v->pFulltextStatements[iStmt]); - v->pFulltextStatements[iStmt] = NULL; - } - } - - for( i=0; ipLeafSelectStmts[i]!=NULL ){ - sqlite3_finalize(v->pLeafSelectStmts[i]); - v->pLeafSelectStmts[i] = NULL; - } - } - - if( v->pTokenizer!=NULL ){ - v->pTokenizer->pModule->xDestroy(v->pTokenizer); - v->pTokenizer = NULL; - } - - clearPendingTerms(v); - - sqlite3_free(v->azColumn); - for(i = 0; i < v->nColumn; ++i) { - sqlite3_free(v->azContentColumn[i]); - } - sqlite3_free(v->azContentColumn); - sqlite3_free(v); -} - -/* -** Token types for parsing the arguments to xConnect or xCreate. -*/ -#define TOKEN_EOF 0 /* End of file */ -#define TOKEN_SPACE 1 /* Any kind of whitespace */ -#define TOKEN_ID 2 /* An identifier */ -#define TOKEN_STRING 3 /* A string literal */ -#define TOKEN_PUNCT 4 /* A single punctuation character */ - -/* -** If X is a character that can be used in an identifier then -** IdChar(X) will be true. Otherwise it is false. -** -** For ASCII, any character with the high-order bit set is -** allowed in an identifier. For 7-bit characters, -** sqlite3IsIdChar[X] must be 1. -** -** Ticket #1066. the SQL standard does not allow '$' in the -** middle of identfiers. But many SQL implementations do. -** SQLite will allow '$' in identifiers for compatibility. -** But the feature is undocumented. -*/ -static const char isIdChar[] = { -/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ - 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ -}; -#define IdChar(C) (((c=C)&0x80)!=0 || (c>0x1f && isIdChar[c-0x20])) - - -/* -** Return the length of the token that begins at z[0]. -** Store the token type in *tokenType before returning. -*/ -static int getToken(const char *z, int *tokenType){ - int i, c; - switch( *z ){ - case 0: { - *tokenType = TOKEN_EOF; - return 0; - } - case ' ': case '\t': case '\n': case '\f': case '\r': { - for(i=1; safe_isspace(z[i]); i++){} - *tokenType = TOKEN_SPACE; - return i; - } - case '`': - case '\'': - case '"': { - int delim = z[0]; - for(i=1; (c=z[i])!=0; i++){ - if( c==delim ){ - if( z[i+1]==delim ){ - i++; - }else{ - break; - } - } - } - *tokenType = TOKEN_STRING; - return i + (c!=0); - } - case '[': { - for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){} - *tokenType = TOKEN_ID; - return i; - } - default: { - if( !IdChar(*z) ){ - break; - } - for(i=1; IdChar(z[i]); i++){} - *tokenType = TOKEN_ID; - return i; - } - } - *tokenType = TOKEN_PUNCT; - return 1; -} - -/* -** A token extracted from a string is an instance of the following -** structure. -*/ -typedef struct Token { - const char *z; /* Pointer to token text. Not '\000' terminated */ - short int n; /* Length of the token text in bytes. */ -} Token; - -/* -** Given a input string (which is really one of the argv[] parameters -** passed into xConnect or xCreate) split the string up into tokens. -** Return an array of pointers to '\000' terminated strings, one string -** for each non-whitespace token. -** -** The returned array is terminated by a single NULL pointer. -** -** Space to hold the returned array is obtained from a single -** malloc and should be freed by passing the return value to free(). -** The individual strings within the token list are all a part of -** the single memory allocation and will all be freed at once. -*/ -static char **tokenizeString(const char *z, int *pnToken){ - int nToken = 0; - Token *aToken = sqlite3_malloc( strlen(z) * sizeof(aToken[0]) ); - int n = 1; - int e, i; - int totalSize = 0; - char **azToken; - char *zCopy; - while( n>0 ){ - n = getToken(z, &e); - if( e!=TOKEN_SPACE ){ - aToken[nToken].z = z; - aToken[nToken].n = n; - nToken++; - totalSize += n+1; - } - z += n; - } - azToken = (char**)sqlite3_malloc( nToken*sizeof(char*) + totalSize ); - zCopy = (char*)&azToken[nToken]; - nToken--; - for(i=0; i=0 ){ - azIn[j] = azIn[i]; - } - j++; - } - } - azIn[j] = 0; - } -} - - -/* -** Find the first alphanumeric token in the string zIn. Null-terminate -** this token. Remove any quotation marks. And return a pointer to -** the result. -*/ -static char *firstToken(char *zIn, char **pzTail){ - int n, ttype; - while(1){ - n = getToken(zIn, &ttype); - if( ttype==TOKEN_SPACE ){ - zIn += n; - }else if( ttype==TOKEN_EOF ){ - *pzTail = zIn; - return 0; - }else{ - zIn[n] = 0; - *pzTail = &zIn[1]; - dequoteString(zIn); - return zIn; - } - } - /*NOTREACHED*/ -} - -/* Return true if... -** -** * s begins with the string t, ignoring case -** * s is longer than t -** * The first character of s beyond t is not a alphanumeric -** -** Ignore leading space in *s. -** -** To put it another way, return true if the first token of -** s[] is t[]. -*/ -static int startsWith(const char *s, const char *t){ - while( safe_isspace(*s) ){ s++; } - while( *t ){ - if( safe_tolower(*s++)!=safe_tolower(*t++) ) return 0; - } - return *s!='_' && !safe_isalnum(*s); -} - -/* -** An instance of this structure defines the "spec" of a -** full text index. This structure is populated by parseSpec -** and use by fulltextConnect and fulltextCreate. -*/ -typedef struct TableSpec { - const char *zDb; /* Logical database name */ - const char *zName; /* Name of the full-text index */ - int nColumn; /* Number of columns to be indexed */ - char **azColumn; /* Original names of columns to be indexed */ - char **azContentColumn; /* Column names for %_content */ - char **azTokenizer; /* Name of tokenizer and its arguments */ -} TableSpec; - -/* -** Reclaim all of the memory used by a TableSpec -*/ -static void clearTableSpec(TableSpec *p) { - sqlite3_free(p->azColumn); - sqlite3_free(p->azContentColumn); - sqlite3_free(p->azTokenizer); -} - -/* Parse a CREATE VIRTUAL TABLE statement, which looks like this: - * - * CREATE VIRTUAL TABLE email - * USING fts2(subject, body, tokenize mytokenizer(myarg)) - * - * We return parsed information in a TableSpec structure. - * - */ -static int parseSpec(TableSpec *pSpec, int argc, const char *const*argv, - char**pzErr){ - int i, n; - char *z, *zDummy; - char **azArg; - const char *zTokenizer = 0; /* argv[] entry describing the tokenizer */ - - assert( argc>=3 ); - /* Current interface: - ** argv[0] - module name - ** argv[1] - database name - ** argv[2] - table name - ** argv[3..] - columns, optionally followed by tokenizer specification - ** and snippet delimiters specification. - */ - - /* Make a copy of the complete argv[][] array in a single allocation. - ** The argv[][] array is read-only and transient. We can write to the - ** copy in order to modify things and the copy is persistent. - */ - CLEAR(pSpec); - for(i=n=0; izDb = azArg[1]; - pSpec->zName = azArg[2]; - pSpec->nColumn = 0; - pSpec->azColumn = azArg; - zTokenizer = "tokenize simple"; - for(i=3; inColumn] = firstToken(azArg[i], &zDummy); - pSpec->nColumn++; - } - } - if( pSpec->nColumn==0 ){ - azArg[0] = "content"; - pSpec->nColumn = 1; - } - - /* - ** Construct the list of content column names. - ** - ** Each content column name will be of the form cNNAAAA - ** where NN is the column number and AAAA is the sanitized - ** column name. "sanitized" means that special characters are - ** converted to "_". The cNN prefix guarantees that all column - ** names are unique. - ** - ** The AAAA suffix is not strictly necessary. It is included - ** for the convenience of people who might examine the generated - ** %_content table and wonder what the columns are used for. - */ - pSpec->azContentColumn = sqlite3_malloc( pSpec->nColumn * sizeof(char *) ); - if( pSpec->azContentColumn==0 ){ - clearTableSpec(pSpec); - return SQLITE_NOMEM; - } - for(i=0; inColumn; i++){ - char *p; - pSpec->azContentColumn[i] = sqlite3_mprintf("c%d%s", i, azArg[i]); - for (p = pSpec->azContentColumn[i]; *p ; ++p) { - if( !safe_isalnum(*p) ) *p = '_'; - } - } - - /* - ** Parse the tokenizer specification string. - */ - pSpec->azTokenizer = tokenizeString(zTokenizer, &n); - tokenListToIdList(pSpec->azTokenizer); - - return SQLITE_OK; -} - -/* -** Generate a CREATE TABLE statement that describes the schema of -** the virtual table. Return a pointer to this schema string. -** -** Space is obtained from sqlite3_mprintf() and should be freed -** using sqlite3_free(). -*/ -static char *fulltextSchema( - int nColumn, /* Number of columns */ - const char *const* azColumn, /* List of columns */ - const char *zTableName /* Name of the table */ -){ - int i; - char *zSchema, *zNext; - const char *zSep = "("; - zSchema = sqlite3_mprintf("CREATE TABLE x"); - for(i=0; ibase */ - v->db = db; - v->zDb = spec->zDb; /* Freed when azColumn is freed */ - v->zName = spec->zName; /* Freed when azColumn is freed */ - v->nColumn = spec->nColumn; - v->azContentColumn = spec->azContentColumn; - spec->azContentColumn = 0; - v->azColumn = spec->azColumn; - spec->azColumn = 0; - - if( spec->azTokenizer==0 ){ - return SQLITE_NOMEM; - } - - zTok = spec->azTokenizer[0]; - if( !zTok ){ - zTok = "simple"; - } - nTok = strlen(zTok)+1; - - m = (sqlite3_tokenizer_module *)sqlite3Fts2HashFind(pHash, zTok, nTok); - if( !m ){ - *pzErr = sqlite3_mprintf("unknown tokenizer: %s", spec->azTokenizer[0]); - rc = SQLITE_ERROR; - goto err; - } - - for(n=0; spec->azTokenizer[n]; n++){} - if( n ){ - rc = m->xCreate(n-1, (const char*const*)&spec->azTokenizer[1], - &v->pTokenizer); - }else{ - rc = m->xCreate(0, 0, &v->pTokenizer); - } - if( rc!=SQLITE_OK ) goto err; - v->pTokenizer->pModule = m; - - /* TODO: verify the existence of backing tables foo_content, foo_term */ - - schema = fulltextSchema(v->nColumn, (const char*const*)v->azColumn, - spec->zName); - rc = sqlite3_declare_vtab(db, schema); - sqlite3_free(schema); - if( rc!=SQLITE_OK ) goto err; - - memset(v->pFulltextStatements, 0, sizeof(v->pFulltextStatements)); - - /* Indicate that the buffer is not live. */ - v->nPendingData = -1; - - *ppVTab = &v->base; - TRACE(("FTS2 Connect %p\n", v)); - - return rc; - -err: - fulltext_vtab_destroy(v); - return rc; -} - -static int fulltextConnect( - sqlite3 *db, - void *pAux, - int argc, const char *const*argv, - sqlite3_vtab **ppVTab, - char **pzErr -){ - TableSpec spec; - int rc = parseSpec(&spec, argc, argv, pzErr); - if( rc!=SQLITE_OK ) return rc; - - rc = constructVtab(db, (fts2Hash *)pAux, &spec, ppVTab, pzErr); - clearTableSpec(&spec); - return rc; -} - -/* The %_content table holds the text of each document, with -** the rowid used as the docid. -*/ -/* TODO(shess) This comment needs elaboration to match the updated -** code. Work it into the top-of-file comment at that time. -*/ -static int fulltextCreate(sqlite3 *db, void *pAux, - int argc, const char * const *argv, - sqlite3_vtab **ppVTab, char **pzErr){ - int rc; - TableSpec spec; - StringBuffer schema; - TRACE(("FTS2 Create\n")); - - rc = parseSpec(&spec, argc, argv, pzErr); - if( rc!=SQLITE_OK ) return rc; - - initStringBuffer(&schema); - append(&schema, "CREATE TABLE %_content("); - appendList(&schema, spec.nColumn, spec.azContentColumn); - append(&schema, ")"); - rc = sql_exec(db, spec.zDb, spec.zName, stringBufferData(&schema)); - stringBufferDestroy(&schema); - if( rc!=SQLITE_OK ) goto out; - - rc = sql_exec(db, spec.zDb, spec.zName, - "create table %_segments(block blob);"); - if( rc!=SQLITE_OK ) goto out; - - rc = sql_exec(db, spec.zDb, spec.zName, - "create table %_segdir(" - " level integer," - " idx integer," - " start_block integer," - " leaves_end_block integer," - " end_block integer," - " root blob," - " primary key(level, idx)" - ");"); - if( rc!=SQLITE_OK ) goto out; - - rc = constructVtab(db, (fts2Hash *)pAux, &spec, ppVTab, pzErr); - -out: - clearTableSpec(&spec); - return rc; -} - -/* Decide how to handle an SQL query. */ -static int fulltextBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ - int i; - TRACE(("FTS2 BestIndex\n")); - - for(i=0; inConstraint; ++i){ - const struct sqlite3_index_constraint *pConstraint; - pConstraint = &pInfo->aConstraint[i]; - if( pConstraint->usable ) { - if( pConstraint->iColumn==-1 && - pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ - pInfo->idxNum = QUERY_ROWID; /* lookup by rowid */ - TRACE(("FTS2 QUERY_ROWID\n")); - } else if( pConstraint->iColumn>=0 && - pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH ){ - /* full-text search */ - pInfo->idxNum = QUERY_FULLTEXT + pConstraint->iColumn; - TRACE(("FTS2 QUERY_FULLTEXT %d\n", pConstraint->iColumn)); - } else continue; - - pInfo->aConstraintUsage[i].argvIndex = 1; - pInfo->aConstraintUsage[i].omit = 1; - - /* An arbitrary value for now. - * TODO: Perhaps rowid matches should be considered cheaper than - * full-text searches. */ - pInfo->estimatedCost = 1.0; - - return SQLITE_OK; - } - } - pInfo->idxNum = QUERY_GENERIC; - return SQLITE_OK; -} - -static int fulltextDisconnect(sqlite3_vtab *pVTab){ - TRACE(("FTS2 Disconnect %p\n", pVTab)); - fulltext_vtab_destroy((fulltext_vtab *)pVTab); - return SQLITE_OK; -} - -static int fulltextDestroy(sqlite3_vtab *pVTab){ - fulltext_vtab *v = (fulltext_vtab *)pVTab; - int rc; - - TRACE(("FTS2 Destroy %p\n", pVTab)); - rc = sql_exec(v->db, v->zDb, v->zName, - "drop table if exists %_content;" - "drop table if exists %_segments;" - "drop table if exists %_segdir;" - ); - if( rc!=SQLITE_OK ) return rc; - - fulltext_vtab_destroy((fulltext_vtab *)pVTab); - return SQLITE_OK; -} - -static int fulltextOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ - fulltext_cursor *c; - - c = (fulltext_cursor *) sqlite3_malloc(sizeof(fulltext_cursor)); - if( c ){ - memset(c, 0, sizeof(fulltext_cursor)); - /* sqlite will initialize c->base */ - *ppCursor = &c->base; - TRACE(("FTS2 Open %p: %p\n", pVTab, c)); - return SQLITE_OK; - }else{ - return SQLITE_NOMEM; - } -} - - -/* Free all of the dynamically allocated memory held by *q -*/ -static void queryClear(Query *q){ - int i; - for(i = 0; i < q->nTerms; ++i){ - sqlite3_free(q->pTerms[i].pTerm); - } - sqlite3_free(q->pTerms); - CLEAR(q); -} - -/* Free all of the dynamically allocated memory held by the -** Snippet -*/ -static void snippetClear(Snippet *p){ - sqlite3_free(p->aMatch); - sqlite3_free(p->zOffset); - sqlite3_free(p->zSnippet); - CLEAR(p); -} -/* -** Append a single entry to the p->aMatch[] log. -*/ -static void snippetAppendMatch( - Snippet *p, /* Append the entry to this snippet */ - int iCol, int iTerm, /* The column and query term */ - int iStart, int nByte /* Offset and size of the match */ -){ - int i; - struct snippetMatch *pMatch; - if( p->nMatch+1>=p->nAlloc ){ - p->nAlloc = p->nAlloc*2 + 10; - p->aMatch = sqlite3_realloc(p->aMatch, p->nAlloc*sizeof(p->aMatch[0]) ); - if( p->aMatch==0 ){ - p->nMatch = 0; - p->nAlloc = 0; - return; - } - } - i = p->nMatch++; - pMatch = &p->aMatch[i]; - pMatch->iCol = iCol; - pMatch->iTerm = iTerm; - pMatch->iStart = iStart; - pMatch->nByte = nByte; -} - -/* -** Sizing information for the circular buffer used in snippetOffsetsOfColumn() -*/ -#define FTS2_ROTOR_SZ (32) -#define FTS2_ROTOR_MASK (FTS2_ROTOR_SZ-1) - -/* -** Add entries to pSnippet->aMatch[] for every match that occurs against -** document zDoc[0..nDoc-1] which is stored in column iColumn. -*/ -static void snippetOffsetsOfColumn( - Query *pQuery, - Snippet *pSnippet, - int iColumn, - const char *zDoc, - int nDoc -){ - const sqlite3_tokenizer_module *pTModule; /* The tokenizer module */ - sqlite3_tokenizer *pTokenizer; /* The specific tokenizer */ - sqlite3_tokenizer_cursor *pTCursor; /* Tokenizer cursor */ - fulltext_vtab *pVtab; /* The full text index */ - int nColumn; /* Number of columns in the index */ - const QueryTerm *aTerm; /* Query string terms */ - int nTerm; /* Number of query string terms */ - int i, j; /* Loop counters */ - int rc; /* Return code */ - unsigned int match, prevMatch; /* Phrase search bitmasks */ - const char *zToken; /* Next token from the tokenizer */ - int nToken; /* Size of zToken */ - int iBegin, iEnd, iPos; /* Offsets of beginning and end */ - - /* The following variables keep a circular buffer of the last - ** few tokens */ - unsigned int iRotor = 0; /* Index of current token */ - int iRotorBegin[FTS2_ROTOR_SZ]; /* Beginning offset of token */ - int iRotorLen[FTS2_ROTOR_SZ]; /* Length of token */ - - pVtab = pQuery->pFts; - nColumn = pVtab->nColumn; - pTokenizer = pVtab->pTokenizer; - pTModule = pTokenizer->pModule; - rc = pTModule->xOpen(pTokenizer, zDoc, nDoc, &pTCursor); - if( rc ) return; - pTCursor->pTokenizer = pTokenizer; - aTerm = pQuery->pTerms; - nTerm = pQuery->nTerms; - if( nTerm>=FTS2_ROTOR_SZ ){ - nTerm = FTS2_ROTOR_SZ - 1; - } - prevMatch = 0; - while(1){ - rc = pTModule->xNext(pTCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos); - if( rc ) break; - iRotorBegin[iRotor&FTS2_ROTOR_MASK] = iBegin; - iRotorLen[iRotor&FTS2_ROTOR_MASK] = iEnd-iBegin; - match = 0; - for(i=0; i=0 && iColnToken ) continue; - if( !aTerm[i].isPrefix && aTerm[i].nTerm1 && (prevMatch & (1<=0; j--){ - int k = (iRotor-j) & FTS2_ROTOR_MASK; - snippetAppendMatch(pSnippet, iColumn, i-j, - iRotorBegin[k], iRotorLen[k]); - } - } - } - prevMatch = match<<1; - iRotor++; - } - pTModule->xClose(pTCursor); -} - - -/* -** Compute all offsets for the current row of the query. -** If the offsets have already been computed, this routine is a no-op. -*/ -static void snippetAllOffsets(fulltext_cursor *p){ - int nColumn; - int iColumn, i; - int iFirst, iLast; - fulltext_vtab *pFts; - - if( p->snippet.nMatch ) return; - if( p->q.nTerms==0 ) return; - pFts = p->q.pFts; - nColumn = pFts->nColumn; - iColumn = (p->iCursorType - QUERY_FULLTEXT); - if( iColumn<0 || iColumn>=nColumn ){ - iFirst = 0; - iLast = nColumn-1; - }else{ - iFirst = iColumn; - iLast = iColumn; - } - for(i=iFirst; i<=iLast; i++){ - const char *zDoc; - int nDoc; - zDoc = (const char*)sqlite3_column_text(p->pStmt, i+1); - nDoc = sqlite3_column_bytes(p->pStmt, i+1); - snippetOffsetsOfColumn(&p->q, &p->snippet, i, zDoc, nDoc); - } -} - -/* -** Convert the information in the aMatch[] array of the snippet -** into the string zOffset[0..nOffset-1]. -*/ -static void snippetOffsetText(Snippet *p){ - int i; - int cnt = 0; - StringBuffer sb; - char zBuf[200]; - if( p->zOffset ) return; - initStringBuffer(&sb); - for(i=0; inMatch; i++){ - struct snippetMatch *pMatch = &p->aMatch[i]; - zBuf[0] = ' '; - sqlite3_snprintf(sizeof(zBuf)-1, &zBuf[cnt>0], "%d %d %d %d", - pMatch->iCol, pMatch->iTerm, pMatch->iStart, pMatch->nByte); - append(&sb, zBuf); - cnt++; - } - p->zOffset = stringBufferData(&sb); - p->nOffset = stringBufferLength(&sb); -} - -/* -** zDoc[0..nDoc-1] is phrase of text. aMatch[0..nMatch-1] are a set -** of matching words some of which might be in zDoc. zDoc is column -** number iCol. -** -** iBreak is suggested spot in zDoc where we could begin or end an -** excerpt. Return a value similar to iBreak but possibly adjusted -** to be a little left or right so that the break point is better. -*/ -static int wordBoundary( - int iBreak, /* The suggested break point */ - const char *zDoc, /* Document text */ - int nDoc, /* Number of bytes in zDoc[] */ - struct snippetMatch *aMatch, /* Matching words */ - int nMatch, /* Number of entries in aMatch[] */ - int iCol /* The column number for zDoc[] */ -){ - int i; - if( iBreak<=10 ){ - return 0; - } - if( iBreak>=nDoc-10 ){ - return nDoc; - } - for(i=0; i0 && aMatch[i-1].iStart+aMatch[i-1].nByte>=iBreak ){ - return aMatch[i-1].iStart; - } - } - for(i=1; i<=10; i++){ - if( safe_isspace(zDoc[iBreak-i]) ){ - return iBreak - i + 1; - } - if( safe_isspace(zDoc[iBreak+i]) ){ - return iBreak + i + 1; - } - } - return iBreak; -} - - - -/* -** Allowed values for Snippet.aMatch[].snStatus -*/ -#define SNIPPET_IGNORE 0 /* It is ok to omit this match from the snippet */ -#define SNIPPET_DESIRED 1 /* We want to include this match in the snippet */ - -/* -** Generate the text of a snippet. -*/ -static void snippetText( - fulltext_cursor *pCursor, /* The cursor we need the snippet for */ - const char *zStartMark, /* Markup to appear before each match */ - const char *zEndMark, /* Markup to appear after each match */ - const char *zEllipsis /* Ellipsis mark */ -){ - int i, j; - struct snippetMatch *aMatch; - int nMatch; - int nDesired; - StringBuffer sb; - int tailCol; - int tailOffset; - int iCol; - int nDoc; - const char *zDoc; - int iStart, iEnd; - int tailEllipsis = 0; - int iMatch; - - - sqlite3_free(pCursor->snippet.zSnippet); - pCursor->snippet.zSnippet = 0; - aMatch = pCursor->snippet.aMatch; - nMatch = pCursor->snippet.nMatch; - initStringBuffer(&sb); - - for(i=0; iq.nTerms; i++){ - for(j=0; j0; i++){ - if( aMatch[i].snStatus!=SNIPPET_DESIRED ) continue; - nDesired--; - iCol = aMatch[i].iCol; - zDoc = (const char*)sqlite3_column_text(pCursor->pStmt, iCol+1); - nDoc = sqlite3_column_bytes(pCursor->pStmt, iCol+1); - iStart = aMatch[i].iStart - 40; - iStart = wordBoundary(iStart, zDoc, nDoc, aMatch, nMatch, iCol); - if( iStart<=10 ){ - iStart = 0; - } - if( iCol==tailCol && iStart<=tailOffset+20 ){ - iStart = tailOffset; - } - if( (iCol!=tailCol && tailCol>=0) || iStart!=tailOffset ){ - trimWhiteSpace(&sb); - appendWhiteSpace(&sb); - append(&sb, zEllipsis); - appendWhiteSpace(&sb); - } - iEnd = aMatch[i].iStart + aMatch[i].nByte + 40; - iEnd = wordBoundary(iEnd, zDoc, nDoc, aMatch, nMatch, iCol); - if( iEnd>=nDoc-10 ){ - iEnd = nDoc; - tailEllipsis = 0; - }else{ - tailEllipsis = 1; - } - while( iMatchsnippet.zSnippet = stringBufferData(&sb); - pCursor->snippet.nSnippet = stringBufferLength(&sb); -} - - -/* -** 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){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - TRACE(("FTS2 Close %p\n", c)); - sqlite3_finalize(c->pStmt); - queryClear(&c->q); - snippetClear(&c->snippet); - if( c->result.nData!=0 ) dlrDestroy(&c->reader); - dataBufferDestroy(&c->result); - sqlite3_free(c); - return SQLITE_OK; -} - -static int fulltextNext(sqlite3_vtab_cursor *pCursor){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - int rc; - - TRACE(("FTS2 Next %p\n", pCursor)); - snippetClear(&c->snippet); - if( c->iCursorType < QUERY_FULLTEXT ){ - /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */ - rc = sqlite3_step(c->pStmt); - switch( rc ){ - case SQLITE_ROW: - c->eof = 0; - return SQLITE_OK; - case SQLITE_DONE: - c->eof = 1; - return SQLITE_OK; - default: - c->eof = 1; - return rc; - } - } else { /* full-text query */ - rc = sqlite3_reset(c->pStmt); - if( rc!=SQLITE_OK ) return rc; - - if( c->result.nData==0 || dlrAtEnd(&c->reader) ){ - c->eof = 1; - return SQLITE_OK; - } - rc = sqlite3_bind_int64(c->pStmt, 1, dlrDocid(&c->reader)); - dlrStep(&c->reader); - if( rc!=SQLITE_OK ) return rc; - /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */ - rc = sqlite3_step(c->pStmt); - if( rc==SQLITE_ROW ){ /* the case we expect */ - c->eof = 0; - return SQLITE_OK; - } - /* an error occurred; abort */ - return rc==SQLITE_DONE ? SQLITE_ERROR : rc; - } -} - - -/* TODO(shess) If we pushed LeafReader to the top of the file, or to -** another file, term_select() could be pushed above -** docListOfTerm(). -*/ -static int termSelect(fulltext_vtab *v, int iColumn, - const char *pTerm, int nTerm, int isPrefix, - DocListType iType, DataBuffer *out); - -/* Return a DocList corresponding to the query term *pTerm. If *pTerm -** is the first term of a phrase query, go ahead and evaluate the phrase -** query and return the doclist for the entire phrase query. -** -** The resulting DL_DOCIDS doclist is stored in pResult, which is -** overwritten. -*/ -static int docListOfTerm( - fulltext_vtab *v, /* The full text index */ - int iColumn, /* column to restrict to. No restriction if >=nColumn */ - QueryTerm *pQTerm, /* Term we are looking for, or 1st term of a phrase */ - DataBuffer *pResult /* Write the result here */ -){ - DataBuffer left, right, new; - int i, rc; - - /* No phrase search if no position info. */ - assert( pQTerm->nPhrase==0 || DL_DEFAULT!=DL_DOCIDS ); - - /* This code should never be called with buffered updates. */ - assert( v->nPendingData<0 ); - - dataBufferInit(&left, 0); - rc = termSelect(v, iColumn, pQTerm->pTerm, pQTerm->nTerm, pQTerm->isPrefix, - 0nPhrase ? DL_POSITIONS : DL_DOCIDS, &left); - if( rc ) return rc; - for(i=1; i<=pQTerm->nPhrase && left.nData>0; i++){ - dataBufferInit(&right, 0); - rc = termSelect(v, iColumn, pQTerm[i].pTerm, pQTerm[i].nTerm, - pQTerm[i].isPrefix, DL_POSITIONS, &right); - if( rc ){ - dataBufferDestroy(&left); - return rc; - } - dataBufferInit(&new, 0); - docListPhraseMerge(left.pData, left.nData, right.pData, right.nData, - inPhrase ? DL_POSITIONS : DL_DOCIDS, &new); - dataBufferDestroy(&left); - dataBufferDestroy(&right); - left = new; - } - *pResult = left; - return SQLITE_OK; -} - -/* Add a new term pTerm[0..nTerm-1] to the query *q. -*/ -static void queryAdd(Query *q, const char *pTerm, int nTerm){ - QueryTerm *t; - ++q->nTerms; - q->pTerms = sqlite3_realloc(q->pTerms, q->nTerms * sizeof(q->pTerms[0])); - if( q->pTerms==0 ){ - q->nTerms = 0; - return; - } - t = &q->pTerms[q->nTerms - 1]; - CLEAR(t); - t->pTerm = sqlite3_malloc(nTerm+1); - memcpy(t->pTerm, pTerm, nTerm); - t->pTerm[nTerm] = 0; - t->nTerm = nTerm; - t->isOr = q->nextIsOr; - t->isPrefix = 0; - q->nextIsOr = 0; - t->iColumn = q->nextColumn; - q->nextColumn = q->dfltColumn; -} - -/* -** Check to see if the string zToken[0...nToken-1] matches any -** column name in the virtual table. If it does, -** return the zero-indexed column number. If not, return -1. -*/ -static int checkColumnSpecifier( - fulltext_vtab *pVtab, /* The virtual table */ - const char *zToken, /* Text of the token */ - int nToken /* Number of characters in the token */ -){ - int i; - for(i=0; inColumn; i++){ - if( memcmp(pVtab->azColumn[i], zToken, nToken)==0 - && pVtab->azColumn[i][nToken]==0 ){ - return i; - } - } - return -1; -} - -/* -** Parse the text at pSegment[0..nSegment-1]. Add additional terms -** to the query being assemblied in pQuery. -** -** inPhrase is true if pSegment[0..nSegement-1] is contained within -** double-quotes. If inPhrase is true, then the first term -** is marked with the number of terms in the phrase less one and -** OR and "-" syntax is ignored. If inPhrase is false, then every -** term found is marked with nPhrase=0 and OR and "-" syntax is significant. -*/ -static int tokenizeSegment( - sqlite3_tokenizer *pTokenizer, /* The tokenizer to use */ - const char *pSegment, int nSegment, /* Query expression being parsed */ - int inPhrase, /* True if within "..." */ - Query *pQuery /* Append results here */ -){ - const sqlite3_tokenizer_module *pModule = pTokenizer->pModule; - sqlite3_tokenizer_cursor *pCursor; - int firstIndex = pQuery->nTerms; - int iCol; - int nTerm = 1; - - int rc = pModule->xOpen(pTokenizer, pSegment, nSegment, &pCursor); - if( rc!=SQLITE_OK ) return rc; - pCursor->pTokenizer = pTokenizer; - - while( 1 ){ - const char *pToken; - int nToken, iBegin, iEnd, iPos; - - rc = pModule->xNext(pCursor, - &pToken, &nToken, - &iBegin, &iEnd, &iPos); - if( rc!=SQLITE_OK ) break; - if( !inPhrase && - pSegment[iEnd]==':' && - (iCol = checkColumnSpecifier(pQuery->pFts, pToken, nToken))>=0 ){ - pQuery->nextColumn = iCol; - continue; - } - if( !inPhrase && pQuery->nTerms>0 && nToken==2 - && pSegment[iBegin]=='O' && pSegment[iBegin+1]=='R' ){ - pQuery->nextIsOr = 1; - continue; - } - queryAdd(pQuery, pToken, nToken); - if( !inPhrase && iBegin>0 && pSegment[iBegin-1]=='-' ){ - pQuery->pTerms[pQuery->nTerms-1].isNot = 1; - } - if( iEndpTerms[pQuery->nTerms-1].isPrefix = 1; - } - pQuery->pTerms[pQuery->nTerms-1].iPhrase = nTerm; - if( inPhrase ){ - nTerm++; - } - } - - if( inPhrase && pQuery->nTerms>firstIndex ){ - pQuery->pTerms[firstIndex].nPhrase = pQuery->nTerms - firstIndex - 1; - } - - return pModule->xClose(pCursor); -} - -/* Parse a query string, yielding a Query object pQuery. -** -** The calling function will need to queryClear() to clean up -** the dynamically allocated memory held by pQuery. -*/ -static int parseQuery( - fulltext_vtab *v, /* The fulltext index */ - const char *zInput, /* Input text of the query string */ - int nInput, /* Size of the input text */ - int dfltColumn, /* Default column of the index to match against */ - Query *pQuery /* Write the parse results here. */ -){ - int iInput, inPhrase = 0; - - if( zInput==0 ) nInput = 0; - if( nInput<0 ) nInput = strlen(zInput); - pQuery->nTerms = 0; - pQuery->pTerms = NULL; - pQuery->nextIsOr = 0; - pQuery->nextColumn = dfltColumn; - pQuery->dfltColumn = dfltColumn; - pQuery->pFts = v; - - for(iInput=0; iInputiInput ){ - tokenizeSegment(v->pTokenizer, zInput+iInput, i-iInput, inPhrase, - pQuery); - } - iInput = i; - if( i=nColumn -** they are allowed to match against any column. -*/ -static int fulltextQuery( - fulltext_vtab *v, /* The full text index */ - int iColumn, /* Match against this column by default */ - const char *zInput, /* The query string */ - int nInput, /* Number of bytes in zInput[] */ - DataBuffer *pResult, /* Write the result doclist here */ - Query *pQuery /* Put parsed query string here */ -){ - int i, iNext, rc; - DataBuffer left, right, or, new; - int nNot = 0; - QueryTerm *aTerm; - - /* TODO(shess) Instead of flushing pendingTerms, we could query for - ** the relevant term and merge the doclist into what we receive from - ** the database. Wait and see if this is a common issue, first. - ** - ** A good reason not to flush is to not generate update-related - ** error codes from here. - */ - - /* Flush any buffered updates before executing the query. */ - rc = flushPendingTerms(v); - if( rc!=SQLITE_OK ) return rc; - - /* TODO(shess) I think that the queryClear() calls below are not - ** necessary, because fulltextClose() already clears the query. - */ - rc = parseQuery(v, zInput, nInput, iColumn, pQuery); - if( rc!=SQLITE_OK ) return rc; - - /* Empty or NULL queries return no results. */ - if( pQuery->nTerms==0 ){ - dataBufferInit(pResult, 0); - return SQLITE_OK; - } - - /* Merge AND terms. */ - /* TODO(shess) I think we can early-exit if( i>nNot && left.nData==0 ). */ - aTerm = pQuery->pTerms; - for(i = 0; inTerms; i=iNext){ - if( aTerm[i].isNot ){ - /* Handle all NOT terms in a separate pass */ - nNot++; - iNext = i + aTerm[i].nPhrase+1; - continue; - } - iNext = i + aTerm[i].nPhrase + 1; - rc = docListOfTerm(v, aTerm[i].iColumn, &aTerm[i], &right); - if( rc ){ - if( i!=nNot ) dataBufferDestroy(&left); - queryClear(pQuery); - return rc; - } - while( iNextnTerms && aTerm[iNext].isOr ){ - rc = docListOfTerm(v, aTerm[iNext].iColumn, &aTerm[iNext], &or); - iNext += aTerm[iNext].nPhrase + 1; - if( rc ){ - if( i!=nNot ) dataBufferDestroy(&left); - dataBufferDestroy(&right); - queryClear(pQuery); - return rc; - } - dataBufferInit(&new, 0); - docListOrMerge(right.pData, right.nData, or.pData, or.nData, &new); - dataBufferDestroy(&right); - dataBufferDestroy(&or); - right = new; - } - if( i==nNot ){ /* first term processed. */ - left = right; - }else{ - dataBufferInit(&new, 0); - docListAndMerge(left.pData, left.nData, right.pData, right.nData, &new); - dataBufferDestroy(&right); - dataBufferDestroy(&left); - left = new; - } - } - - if( nNot==pQuery->nTerms ){ - /* We do not yet know how to handle a query of only NOT terms */ - return SQLITE_ERROR; - } - - /* Do the EXCEPT terms */ - for(i=0; inTerms; i += aTerm[i].nPhrase + 1){ - if( !aTerm[i].isNot ) continue; - rc = docListOfTerm(v, aTerm[i].iColumn, &aTerm[i], &right); - if( rc ){ - queryClear(pQuery); - dataBufferDestroy(&left); - return rc; - } - dataBufferInit(&new, 0); - docListExceptMerge(left.pData, left.nData, right.pData, right.nData, &new); - dataBufferDestroy(&right); - dataBufferDestroy(&left); - left = new; - } - - *pResult = left; - return rc; -} - -/* -** This is the xFilter interface for the virtual table. See -** the virtual table xFilter method documentation for additional -** information. -** -** If idxNum==QUERY_GENERIC then do a full table scan against -** the %_content table. -** -** If idxNum==QUERY_ROWID then do a rowid lookup for a single entry -** in the %_content table. -** -** If idxNum>=QUERY_FULLTEXT then use the full text index. The -** column on the left-hand side of the MATCH operator is column -** number idxNum-QUERY_FULLTEXT, 0 indexed. argv[0] is the right-hand -** side of the MATCH operator. -*/ -/* TODO(shess) Upgrade the cursor initialization and destruction to -** account for fulltextFilter() being called multiple times on the -** same cursor. The current solution is very fragile. Apply fix to -** fts2 as appropriate. -*/ -static int fulltextFilter( - sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ - int idxNum, const char *idxStr, /* Which indexing scheme to use */ - int argc, sqlite3_value **argv /* Arguments for the indexing scheme */ -){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - fulltext_vtab *v = cursor_vtab(c); - int rc; - - TRACE(("FTS2 Filter %p\n",pCursor)); - - /* If the cursor has a statement that was not prepared according to - ** idxNum, clear it. I believe all calls to fulltextFilter with a - ** given cursor will have the same idxNum , but in this case it's - ** easy to be safe. - */ - if( c->pStmt && c->iCursorType!=idxNum ){ - sqlite3_finalize(c->pStmt); - c->pStmt = NULL; - } - - /* Get a fresh statement appropriate to idxNum. */ - /* TODO(shess): Add a prepared-statement cache in the vt structure. - ** The cache must handle multiple open cursors. Easier to cache the - ** statement variants at the vt to reduce malloc/realloc/free here. - ** Or we could have a StringBuffer variant which allowed stack - ** construction for small values. - */ - if( !c->pStmt ){ - char *zSql = sqlite3_mprintf("select rowid, * from %%_content %s", - idxNum==QUERY_GENERIC ? "" : "where rowid=?"); - rc = sql_prepare(v->db, v->zDb, v->zName, &c->pStmt, zSql); - sqlite3_free(zSql); - if( rc!=SQLITE_OK ) return rc; - c->iCursorType = idxNum; - }else{ - sqlite3_reset(c->pStmt); - assert( c->iCursorType==idxNum ); - } - - switch( idxNum ){ - case QUERY_GENERIC: - break; - - case QUERY_ROWID: - rc = sqlite3_bind_int64(c->pStmt, 1, sqlite3_value_int64(argv[0])); - if( rc!=SQLITE_OK ) return rc; - break; - - default: /* full-text search */ - { - const char *zQuery = (const char *)sqlite3_value_text(argv[0]); - assert( idxNum<=QUERY_FULLTEXT+v->nColumn); - assert( argc==1 ); - queryClear(&c->q); - if( c->result.nData!=0 ){ - /* This case happens if the same cursor is used repeatedly. */ - dlrDestroy(&c->reader); - dataBufferReset(&c->result); - }else{ - dataBufferInit(&c->result, 0); - } - rc = fulltextQuery(v, idxNum-QUERY_FULLTEXT, zQuery, -1, &c->result, &c->q); - if( rc!=SQLITE_OK ) return rc; - if( c->result.nData!=0 ){ - dlrInit(&c->reader, DL_DOCIDS, c->result.pData, c->result.nData); - } - break; - } - } - - return fulltextNext(pCursor); -} - -/* This is the xEof method of the virtual table. The SQLite core -** calls this routine to find out if it has reached the end of -** a query's results set. -*/ -static int fulltextEof(sqlite3_vtab_cursor *pCursor){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - return c->eof; -} - -/* This is the xColumn method of the virtual table. The SQLite -** core calls this method during a query when it needs the value -** of a column from the virtual table. This method needs to use -** one of the sqlite3_result_*() routines to store the requested -** value back in the pContext. -*/ -static int fulltextColumn(sqlite3_vtab_cursor *pCursor, - sqlite3_context *pContext, int idxCol){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - fulltext_vtab *v = cursor_vtab(c); - - if( idxColnColumn ){ - sqlite3_value *pVal = sqlite3_column_value(c->pStmt, idxCol+1); - sqlite3_result_value(pContext, pVal); - }else if( idxCol==v->nColumn ){ - /* The extra column whose name is the same as the table. - ** Return a blob which is a pointer to the cursor - */ - sqlite3_result_blob(pContext, &c, sizeof(c), SQLITE_TRANSIENT); - } - return SQLITE_OK; -} - -/* This is the xRowid method. The SQLite core calls this routine to -** retrive the rowid for the current row of the result set. The -** rowid should be written to *pRowid. -*/ -static int fulltextRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ - fulltext_cursor *c = (fulltext_cursor *) pCursor; - - *pRowid = sqlite3_column_int64(c->pStmt, 0); - return SQLITE_OK; -} - -/* Add all terms in [zText] to pendingTerms table. If [iColumn] > 0, -** we also store positions and offsets in the hash table using that -** column number. -*/ -static int buildTerms(fulltext_vtab *v, sqlite_int64 iDocid, - const char *zText, int iColumn){ - sqlite3_tokenizer *pTokenizer = v->pTokenizer; - sqlite3_tokenizer_cursor *pCursor; - const char *pToken; - int nTokenBytes; - int iStartOffset, iEndOffset, iPosition; - int rc; - - rc = pTokenizer->pModule->xOpen(pTokenizer, zText, -1, &pCursor); - if( rc!=SQLITE_OK ) return rc; - - pCursor->pTokenizer = pTokenizer; - while( SQLITE_OK==(rc=pTokenizer->pModule->xNext(pCursor, - &pToken, &nTokenBytes, - &iStartOffset, &iEndOffset, - &iPosition)) ){ - DLCollector *p; - int nData; /* Size of doclist before our update. */ - - /* Positions can't be negative; we use -1 as a terminator - * internally. Token can't be NULL or empty. */ - if( iPosition<0 || pToken == NULL || nTokenBytes == 0 ){ - rc = SQLITE_ERROR; - break; - } - - p = fts2HashFind(&v->pendingTerms, pToken, nTokenBytes); - if( p==NULL ){ - nData = 0; - p = dlcNew(iDocid, DL_DEFAULT); - fts2HashInsert(&v->pendingTerms, pToken, nTokenBytes, p); - - /* Overhead for our hash table entry, the key, and the value. */ - v->nPendingData += sizeof(struct fts2HashElem)+sizeof(*p)+nTokenBytes; - }else{ - nData = p->b.nData; - if( p->dlw.iPrevDocid!=iDocid ) dlcNext(p, iDocid); - } - if( iColumn>=0 ){ - dlcAddPos(p, iColumn, iPosition, iStartOffset, iEndOffset); - } - - /* Accumulate data added by dlcNew or dlcNext, and dlcAddPos. */ - v->nPendingData += p->b.nData-nData; - } - - /* TODO(shess) Check return? Should this be able to cause errors at - ** this point? Actually, same question about sqlite3_finalize(), - ** though one could argue that failure there means that the data is - ** not durable. *ponder* - */ - pTokenizer->pModule->xClose(pCursor); - if( SQLITE_DONE == rc ) return SQLITE_OK; - return rc; -} - -/* Add doclists for all terms in [pValues] to pendingTerms table. */ -static int insertTerms(fulltext_vtab *v, sqlite_int64 iRowid, - sqlite3_value **pValues){ - int i; - for(i = 0; i < v->nColumn ; ++i){ - char *zText = (char*)sqlite3_value_text(pValues[i]); - int rc = buildTerms(v, iRowid, zText, i); - if( rc!=SQLITE_OK ) return rc; - } - return SQLITE_OK; -} - -/* Add empty doclists for all terms in the given row's content to -** pendingTerms. -*/ -static int deleteTerms(fulltext_vtab *v, sqlite_int64 iRowid){ - const char **pValues; - int i, rc; - - /* TODO(shess) Should we allow such tables at all? */ - if( DL_DEFAULT==DL_DOCIDS ) return SQLITE_ERROR; - - rc = content_select(v, iRowid, &pValues); - if( rc!=SQLITE_OK ) return rc; - - for(i = 0 ; i < v->nColumn; ++i) { - rc = buildTerms(v, iRowid, pValues[i], -1); - if( rc!=SQLITE_OK ) break; - } - - freeStringArray(v->nColumn, pValues); - return SQLITE_OK; -} - -/* TODO(shess) Refactor the code to remove this forward decl. */ -static int initPendingTerms(fulltext_vtab *v, sqlite_int64 iDocid); - -/* Insert a row into the %_content table; set *piRowid to be the ID of the -** new row. Add doclists for terms to pendingTerms. -*/ -static int index_insert(fulltext_vtab *v, sqlite3_value *pRequestRowid, - sqlite3_value **pValues, sqlite_int64 *piRowid){ - int rc; - - rc = content_insert(v, pRequestRowid, pValues); /* execute an SQL INSERT */ - if( rc!=SQLITE_OK ) return rc; - - *piRowid = sqlite3_last_insert_rowid(v->db); - rc = initPendingTerms(v, *piRowid); - if( rc!=SQLITE_OK ) return rc; - - return insertTerms(v, *piRowid, pValues); -} - -/* Delete a row from the %_content table; add empty doclists for terms -** to pendingTerms. -*/ -static int index_delete(fulltext_vtab *v, sqlite_int64 iRow){ - int rc = initPendingTerms(v, iRow); - if( rc!=SQLITE_OK ) return rc; - - rc = deleteTerms(v, iRow); - if( rc!=SQLITE_OK ) return rc; - - return content_delete(v, iRow); /* execute an SQL DELETE */ -} - -/* Update a row in the %_content table; add delete doclists to -** pendingTerms for old terms not in the new data, add insert doclists -** to pendingTerms for terms in the new data. -*/ -static int index_update(fulltext_vtab *v, sqlite_int64 iRow, - sqlite3_value **pValues){ - int rc = initPendingTerms(v, iRow); - if( rc!=SQLITE_OK ) return rc; - - /* Generate an empty doclist for each term that previously appeared in this - * row. */ - rc = deleteTerms(v, iRow); - if( rc!=SQLITE_OK ) return rc; - - rc = content_update(v, pValues, iRow); /* execute an SQL UPDATE */ - if( rc!=SQLITE_OK ) return rc; - - /* Now add positions for terms which appear in the updated row. */ - return insertTerms(v, iRow, pValues); -} - -/*******************************************************************/ -/* InteriorWriter is used to collect terms and block references into -** interior nodes in %_segments. See commentary at top of file for -** format. -*/ - -/* How large interior nodes can grow. */ -#define INTERIOR_MAX 2048 - -/* Minimum number of terms per interior node (except the root). This -** prevents large terms from making the tree too skinny - must be >0 -** so that the tree always makes progress. Note that the min tree -** fanout will be INTERIOR_MIN_TERMS+1. -*/ -#define INTERIOR_MIN_TERMS 7 -#if INTERIOR_MIN_TERMS<1 -# error INTERIOR_MIN_TERMS must be greater than 0. -#endif - -/* ROOT_MAX controls how much data is stored inline in the segment -** directory. -*/ -/* TODO(shess) Push ROOT_MAX down to whoever is writing things. It's -** only here so that interiorWriterRootInfo() and leafWriterRootInfo() -** can both see it, but if the caller passed it in, we wouldn't even -** need a define. -*/ -#define ROOT_MAX 1024 -#if ROOT_MAXterm, 0); - dataBufferReplace(&block->term, pTerm, nTerm); - - n = putVarint(c, iHeight); - n += putVarint(c+n, iChildBlock); - dataBufferInit(&block->data, INTERIOR_MAX); - dataBufferReplace(&block->data, c, n); - } - return block; -} - -#ifndef NDEBUG -/* Verify that the data is readable as an interior node. */ -static void interiorBlockValidate(InteriorBlock *pBlock){ - const char *pData = pBlock->data.pData; - int nData = pBlock->data.nData; - int n, iDummy; - sqlite_int64 iBlockid; - - assert( nData>0 ); - assert( pData!=0 ); - assert( pData+nData>pData ); - - /* Must lead with height of node as a varint(n), n>0 */ - n = getVarint32(pData, &iDummy); - assert( n>0 ); - assert( iDummy>0 ); - assert( n0 ); - assert( n<=nData ); - pData += n; - nData -= n; - - /* Zero or more terms of positive length */ - if( nData!=0 ){ - /* First term is not delta-encoded. */ - n = getVarint32(pData, &iDummy); - assert( n>0 ); - assert( iDummy>0 ); - assert( n+iDummy>0); - assert( n+iDummy<=nData ); - pData += n+iDummy; - nData -= n+iDummy; - - /* Following terms delta-encoded. */ - while( nData!=0 ){ - /* Length of shared prefix. */ - n = getVarint32(pData, &iDummy); - assert( n>0 ); - assert( iDummy>=0 ); - assert( n0 ); - assert( iDummy>0 ); - assert( n+iDummy>0); - assert( n+iDummy<=nData ); - pData += n+iDummy; - nData -= n+iDummy; - } - } -} -#define ASSERT_VALID_INTERIOR_BLOCK(x) interiorBlockValidate(x) -#else -#define ASSERT_VALID_INTERIOR_BLOCK(x) assert( 1 ) -#endif - -typedef struct InteriorWriter { - int iHeight; /* from 0 at leaves. */ - InteriorBlock *first, *last; - struct InteriorWriter *parentWriter; - - DataBuffer term; /* Last term written to block "last". */ - sqlite_int64 iOpeningChildBlock; /* First child block in block "last". */ -#ifndef NDEBUG - sqlite_int64 iLastChildBlock; /* for consistency checks. */ -#endif -} InteriorWriter; - -/* Initialize an interior node where pTerm[nTerm] marks the leftmost -** term in the tree. iChildBlock is the leftmost child block at the -** next level down the tree. -*/ -static void interiorWriterInit(int iHeight, const char *pTerm, int nTerm, - sqlite_int64 iChildBlock, - InteriorWriter *pWriter){ - InteriorBlock *block; - assert( iHeight>0 ); - CLEAR(pWriter); - - pWriter->iHeight = iHeight; - pWriter->iOpeningChildBlock = iChildBlock; -#ifndef NDEBUG - pWriter->iLastChildBlock = iChildBlock; -#endif - block = interiorBlockNew(iHeight, iChildBlock, pTerm, nTerm); - pWriter->last = pWriter->first = block; - ASSERT_VALID_INTERIOR_BLOCK(pWriter->last); - dataBufferInit(&pWriter->term, 0); -} - -/* Append the child node rooted at iChildBlock to the interior node, -** with pTerm[nTerm] as the leftmost term in iChildBlock's subtree. -*/ -static void interiorWriterAppend(InteriorWriter *pWriter, - const char *pTerm, int nTerm, - sqlite_int64 iChildBlock){ - char c[VARINT_MAX+VARINT_MAX]; - int n, nPrefix = 0; - - ASSERT_VALID_INTERIOR_BLOCK(pWriter->last); - - /* The first term written into an interior node is actually - ** associated with the second child added (the first child was added - ** in interiorWriterInit, or in the if clause at the bottom of this - ** function). That term gets encoded straight up, with nPrefix left - ** at 0. - */ - if( pWriter->term.nData==0 ){ - n = putVarint(c, nTerm); - }else{ - while( nPrefixterm.nData && - pTerm[nPrefix]==pWriter->term.pData[nPrefix] ){ - nPrefix++; - } - - n = putVarint(c, nPrefix); - n += putVarint(c+n, nTerm-nPrefix); - } - -#ifndef NDEBUG - pWriter->iLastChildBlock++; -#endif - assert( pWriter->iLastChildBlock==iChildBlock ); - - /* Overflow to a new block if the new term makes the current block - ** too big, and the current block already has enough terms. - */ - if( pWriter->last->data.nData+n+nTerm-nPrefix>INTERIOR_MAX && - iChildBlock-pWriter->iOpeningChildBlock>INTERIOR_MIN_TERMS ){ - pWriter->last->next = interiorBlockNew(pWriter->iHeight, iChildBlock, - pTerm, nTerm); - pWriter->last = pWriter->last->next; - pWriter->iOpeningChildBlock = iChildBlock; - dataBufferReset(&pWriter->term); - }else{ - dataBufferAppend2(&pWriter->last->data, c, n, - pTerm+nPrefix, nTerm-nPrefix); - dataBufferReplace(&pWriter->term, pTerm, nTerm); - } - ASSERT_VALID_INTERIOR_BLOCK(pWriter->last); -} - -/* Free the space used by pWriter, including the linked-list of -** InteriorBlocks, and parentWriter, if present. -*/ -static int interiorWriterDestroy(InteriorWriter *pWriter){ - InteriorBlock *block = pWriter->first; - - while( block!=NULL ){ - InteriorBlock *b = block; - block = block->next; - dataBufferDestroy(&b->term); - dataBufferDestroy(&b->data); - sqlite3_free(b); - } - if( pWriter->parentWriter!=NULL ){ - interiorWriterDestroy(pWriter->parentWriter); - sqlite3_free(pWriter->parentWriter); - } - dataBufferDestroy(&pWriter->term); - SCRAMBLE(pWriter); - return SQLITE_OK; -} - -/* If pWriter can fit entirely in ROOT_MAX, return it as the root info -** directly, leaving *piEndBlockid unchanged. Otherwise, flush -** pWriter to %_segments, building a new layer of interior nodes, and -** recursively ask for their root into. -*/ -static int interiorWriterRootInfo(fulltext_vtab *v, InteriorWriter *pWriter, - char **ppRootInfo, int *pnRootInfo, - sqlite_int64 *piEndBlockid){ - InteriorBlock *block = pWriter->first; - sqlite_int64 iBlockid = 0; - int rc; - - /* If we can fit the segment inline */ - if( block==pWriter->last && block->data.nDatadata.pData; - *pnRootInfo = block->data.nData; - return SQLITE_OK; - } - - /* Flush the first block to %_segments, and create a new level of - ** interior node. - */ - ASSERT_VALID_INTERIOR_BLOCK(block); - rc = block_insert(v, block->data.pData, block->data.nData, &iBlockid); - if( rc!=SQLITE_OK ) return rc; - *piEndBlockid = iBlockid; - - pWriter->parentWriter = sqlite3_malloc(sizeof(*pWriter->parentWriter)); - interiorWriterInit(pWriter->iHeight+1, - block->term.pData, block->term.nData, - iBlockid, pWriter->parentWriter); - - /* Flush additional blocks and append to the higher interior - ** node. - */ - for(block=block->next; block!=NULL; block=block->next){ - ASSERT_VALID_INTERIOR_BLOCK(block); - rc = block_insert(v, block->data.pData, block->data.nData, &iBlockid); - if( rc!=SQLITE_OK ) return rc; - *piEndBlockid = iBlockid; - - interiorWriterAppend(pWriter->parentWriter, - block->term.pData, block->term.nData, iBlockid); - } - - /* Parent node gets the chance to be the root. */ - return interiorWriterRootInfo(v, pWriter->parentWriter, - ppRootInfo, pnRootInfo, piEndBlockid); -} - -/****************************************************************/ -/* InteriorReader is used to read off the data from an interior node -** (see comment at top of file for the format). -*/ -typedef struct InteriorReader { - const char *pData; - int nData; - - DataBuffer term; /* previous term, for decoding term delta. */ - - sqlite_int64 iBlockid; -} InteriorReader; - -static void interiorReaderDestroy(InteriorReader *pReader){ - dataBufferDestroy(&pReader->term); - SCRAMBLE(pReader); -} - -/* TODO(shess) The assertions are great, but what if we're in NDEBUG -** and the blob is empty or otherwise contains suspect data? -*/ -static void interiorReaderInit(const char *pData, int nData, - InteriorReader *pReader){ - int n, nTerm; - - /* Require at least the leading flag byte */ - assert( nData>0 ); - assert( pData[0]!='\0' ); - - CLEAR(pReader); - - /* Decode the base blockid, and set the cursor to the first term. */ - n = getVarint(pData+1, &pReader->iBlockid); - assert( 1+n<=nData ); - pReader->pData = pData+1+n; - pReader->nData = nData-(1+n); - - /* A single-child interior node (such as when a leaf node was too - ** large for the segment directory) won't have any terms. - ** Otherwise, decode the first term. - */ - if( pReader->nData==0 ){ - dataBufferInit(&pReader->term, 0); - }else{ - n = getVarint32(pReader->pData, &nTerm); - dataBufferInit(&pReader->term, nTerm); - dataBufferReplace(&pReader->term, pReader->pData+n, nTerm); - assert( n+nTerm<=pReader->nData ); - pReader->pData += n+nTerm; - pReader->nData -= n+nTerm; - } -} - -static int interiorReaderAtEnd(InteriorReader *pReader){ - return pReader->term.nData==0; -} - -static sqlite_int64 interiorReaderCurrentBlockid(InteriorReader *pReader){ - return pReader->iBlockid; -} - -static int interiorReaderTermBytes(InteriorReader *pReader){ - assert( !interiorReaderAtEnd(pReader) ); - return pReader->term.nData; -} -static const char *interiorReaderTerm(InteriorReader *pReader){ - assert( !interiorReaderAtEnd(pReader) ); - return pReader->term.pData; -} - -/* Step forward to the next term in the node. */ -static void interiorReaderStep(InteriorReader *pReader){ - assert( !interiorReaderAtEnd(pReader) ); - - /* If the last term has been read, signal eof, else construct the - ** next term. - */ - if( pReader->nData==0 ){ - dataBufferReset(&pReader->term); - }else{ - int n, nPrefix, nSuffix; - - n = getVarint32(pReader->pData, &nPrefix); - n += getVarint32(pReader->pData+n, &nSuffix); - - /* Truncate the current term and append suffix data. */ - pReader->term.nData = nPrefix; - dataBufferAppend(&pReader->term, pReader->pData+n, nSuffix); - - assert( n+nSuffix<=pReader->nData ); - pReader->pData += n+nSuffix; - pReader->nData -= n+nSuffix; - } - pReader->iBlockid++; -} - -/* Compare the current term to pTerm[nTerm], returning strcmp-style -** results. If isPrefix, equality means equal through nTerm bytes. -*/ -static int interiorReaderTermCmp(InteriorReader *pReader, - const char *pTerm, int nTerm, int isPrefix){ - const char *pReaderTerm = interiorReaderTerm(pReader); - int nReaderTerm = interiorReaderTermBytes(pReader); - int c, n = nReaderTerm0 ) return -1; - if( nTerm>0 ) return 1; - return 0; - } - - c = memcmp(pReaderTerm, pTerm, n); - if( c!=0 ) return c; - if( isPrefix && n==nTerm ) return 0; - return nReaderTerm - nTerm; -} - -/****************************************************************/ -/* LeafWriter is used to collect terms and associated doclist data -** into leaf blocks in %_segments (see top of file for format info). -** Expected usage is: -** -** LeafWriter writer; -** leafWriterInit(0, 0, &writer); -** while( sorted_terms_left_to_process ){ -** // data is doclist data for that term. -** rc = leafWriterStep(v, &writer, pTerm, nTerm, pData, nData); -** if( rc!=SQLITE_OK ) goto err; -** } -** rc = leafWriterFinalize(v, &writer); -**err: -** leafWriterDestroy(&writer); -** return rc; -** -** leafWriterStep() may write a collected leaf out to %_segments. -** leafWriterFinalize() finishes writing any buffered data and stores -** a root node in %_segdir. leafWriterDestroy() frees all buffers and -** InteriorWriters allocated as part of writing this segment. -** -** TODO(shess) Document leafWriterStepMerge(). -*/ - -/* Put terms with data this big in their own block. */ -#define STANDALONE_MIN 1024 - -/* Keep leaf blocks below this size. */ -#define LEAF_MAX 2048 - -typedef struct LeafWriter { - int iLevel; - int idx; - sqlite_int64 iStartBlockid; /* needed to create the root info */ - sqlite_int64 iEndBlockid; /* when we're done writing. */ - - DataBuffer term; /* previous encoded term */ - DataBuffer data; /* encoding buffer */ - - /* bytes of first term in the current node which distinguishes that - ** term from the last term of the previous node. - */ - int nTermDistinct; - - InteriorWriter parentWriter; /* if we overflow */ - int has_parent; -} LeafWriter; - -static void leafWriterInit(int iLevel, int idx, LeafWriter *pWriter){ - CLEAR(pWriter); - pWriter->iLevel = iLevel; - pWriter->idx = idx; - - dataBufferInit(&pWriter->term, 32); - - /* Start out with a reasonably sized block, though it can grow. */ - dataBufferInit(&pWriter->data, LEAF_MAX); -} - -#ifndef NDEBUG -/* Verify that the data is readable as a leaf node. */ -static void leafNodeValidate(const char *pData, int nData){ - int n, iDummy; - - if( nData==0 ) return; - assert( nData>0 ); - assert( pData!=0 ); - assert( pData+nData>pData ); - - /* Must lead with a varint(0) */ - n = getVarint32(pData, &iDummy); - assert( iDummy==0 ); - assert( n>0 ); - assert( n0 ); - assert( iDummy>0 ); - assert( n+iDummy>0 ); - assert( n+iDummy0 ); - assert( iDummy>0 ); - assert( n+iDummy>0 ); - assert( n+iDummy<=nData ); - ASSERT_VALID_DOCLIST(DL_DEFAULT, pData+n, iDummy, NULL); - pData += n+iDummy; - nData -= n+iDummy; - - /* Verify that trailing terms and doclists also are readable. */ - while( nData!=0 ){ - n = getVarint32(pData, &iDummy); - assert( n>0 ); - assert( iDummy>=0 ); - assert( n0 ); - assert( iDummy>0 ); - assert( n+iDummy>0 ); - assert( n+iDummy0 ); - assert( iDummy>0 ); - assert( n+iDummy>0 ); - assert( n+iDummy<=nData ); - ASSERT_VALID_DOCLIST(DL_DEFAULT, pData+n, iDummy, NULL); - pData += n+iDummy; - nData -= n+iDummy; - } -} -#define ASSERT_VALID_LEAF_NODE(p, n) leafNodeValidate(p, n) -#else -#define ASSERT_VALID_LEAF_NODE(p, n) assert( 1 ) -#endif - -/* Flush the current leaf node to %_segments, and adding the resulting -** blockid and the starting term to the interior node which will -** contain it. -*/ -static int leafWriterInternalFlush(fulltext_vtab *v, LeafWriter *pWriter, - int iData, int nData){ - sqlite_int64 iBlockid = 0; - const char *pStartingTerm; - int nStartingTerm, rc, n; - - /* Must have the leading varint(0) flag, plus at least some - ** valid-looking data. - */ - assert( nData>2 ); - assert( iData>=0 ); - assert( iData+nData<=pWriter->data.nData ); - ASSERT_VALID_LEAF_NODE(pWriter->data.pData+iData, nData); - - rc = block_insert(v, pWriter->data.pData+iData, nData, &iBlockid); - if( rc!=SQLITE_OK ) return rc; - assert( iBlockid!=0 ); - - /* Reconstruct the first term in the leaf for purposes of building - ** the interior node. - */ - n = getVarint32(pWriter->data.pData+iData+1, &nStartingTerm); - pStartingTerm = pWriter->data.pData+iData+1+n; - assert( pWriter->data.nData>iData+1+n+nStartingTerm ); - assert( pWriter->nTermDistinct>0 ); - assert( pWriter->nTermDistinct<=nStartingTerm ); - nStartingTerm = pWriter->nTermDistinct; - - if( pWriter->has_parent ){ - interiorWriterAppend(&pWriter->parentWriter, - pStartingTerm, nStartingTerm, iBlockid); - }else{ - interiorWriterInit(1, pStartingTerm, nStartingTerm, iBlockid, - &pWriter->parentWriter); - pWriter->has_parent = 1; - } - - /* Track the span of this segment's leaf nodes. */ - if( pWriter->iEndBlockid==0 ){ - pWriter->iEndBlockid = pWriter->iStartBlockid = iBlockid; - }else{ - pWriter->iEndBlockid++; - assert( iBlockid==pWriter->iEndBlockid ); - } - - return SQLITE_OK; -} -static int leafWriterFlush(fulltext_vtab *v, LeafWriter *pWriter){ - int rc = leafWriterInternalFlush(v, pWriter, 0, pWriter->data.nData); - if( rc!=SQLITE_OK ) return rc; - - /* Re-initialize the output buffer. */ - dataBufferReset(&pWriter->data); - - return SQLITE_OK; -} - -/* Fetch the root info for the segment. If the entire leaf fits -** within ROOT_MAX, then it will be returned directly, otherwise it -** will be flushed and the root info will be returned from the -** interior node. *piEndBlockid is set to the blockid of the last -** interior or leaf node written to disk (0 if none are written at -** all). -*/ -static int leafWriterRootInfo(fulltext_vtab *v, LeafWriter *pWriter, - char **ppRootInfo, int *pnRootInfo, - sqlite_int64 *piEndBlockid){ - /* we can fit the segment entirely inline */ - if( !pWriter->has_parent && pWriter->data.nDatadata.pData; - *pnRootInfo = pWriter->data.nData; - *piEndBlockid = 0; - return SQLITE_OK; - } - - /* Flush remaining leaf data. */ - if( pWriter->data.nData>0 ){ - int rc = leafWriterFlush(v, pWriter); - if( rc!=SQLITE_OK ) return rc; - } - - /* We must have flushed a leaf at some point. */ - assert( pWriter->has_parent ); - - /* Tenatively set the end leaf blockid as the end blockid. If the - ** interior node can be returned inline, this will be the final - ** blockid, otherwise it will be overwritten by - ** interiorWriterRootInfo(). - */ - *piEndBlockid = pWriter->iEndBlockid; - - return interiorWriterRootInfo(v, &pWriter->parentWriter, - ppRootInfo, pnRootInfo, piEndBlockid); -} - -/* Collect the rootInfo data and store it into the segment directory. -** This has the effect of flushing the segment's leaf data to -** %_segments, and also flushing any interior nodes to %_segments. -*/ -static int leafWriterFinalize(fulltext_vtab *v, LeafWriter *pWriter){ - sqlite_int64 iEndBlockid; - char *pRootInfo; - int rc, nRootInfo; - - rc = leafWriterRootInfo(v, pWriter, &pRootInfo, &nRootInfo, &iEndBlockid); - if( rc!=SQLITE_OK ) return rc; - - /* Don't bother storing an entirely empty segment. */ - if( iEndBlockid==0 && nRootInfo==0 ) return SQLITE_OK; - - return segdir_set(v, pWriter->iLevel, pWriter->idx, - pWriter->iStartBlockid, pWriter->iEndBlockid, - iEndBlockid, pRootInfo, nRootInfo); -} - -static void leafWriterDestroy(LeafWriter *pWriter){ - if( pWriter->has_parent ) interiorWriterDestroy(&pWriter->parentWriter); - dataBufferDestroy(&pWriter->term); - dataBufferDestroy(&pWriter->data); -} - -/* Encode a term into the leafWriter, delta-encoding as appropriate. -** Returns the length of the new term which distinguishes it from the -** previous term, which can be used to set nTermDistinct when a node -** boundary is crossed. -*/ -static int leafWriterEncodeTerm(LeafWriter *pWriter, - const char *pTerm, int nTerm){ - char c[VARINT_MAX+VARINT_MAX]; - int n, nPrefix = 0; - - assert( nTerm>0 ); - while( nPrefixterm.nData && - pTerm[nPrefix]==pWriter->term.pData[nPrefix] ){ - nPrefix++; - /* Failing this implies that the terms weren't in order. */ - assert( nPrefixdata.nData==0 ){ - /* Encode the node header and leading term as: - ** varint(0) - ** varint(nTerm) - ** char pTerm[nTerm] - */ - n = putVarint(c, '\0'); - n += putVarint(c+n, nTerm); - dataBufferAppend2(&pWriter->data, c, n, pTerm, nTerm); - }else{ - /* Delta-encode the term as: - ** varint(nPrefix) - ** varint(nSuffix) - ** char pTermSuffix[nSuffix] - */ - n = putVarint(c, nPrefix); - n += putVarint(c+n, nTerm-nPrefix); - dataBufferAppend2(&pWriter->data, c, n, pTerm+nPrefix, nTerm-nPrefix); - } - dataBufferReplace(&pWriter->term, pTerm, nTerm); - - return nPrefix+1; -} - -/* Used to avoid a memmove when a large amount of doclist data is in -** the buffer. This constructs a node and term header before -** iDoclistData and flushes the resulting complete node using -** leafWriterInternalFlush(). -*/ -static int leafWriterInlineFlush(fulltext_vtab *v, LeafWriter *pWriter, - const char *pTerm, int nTerm, - int iDoclistData){ - char c[VARINT_MAX+VARINT_MAX]; - int iData, n = putVarint(c, 0); - n += putVarint(c+n, nTerm); - - /* There should always be room for the header. Even if pTerm shared - ** a substantial prefix with the previous term, the entire prefix - ** could be constructed from earlier data in the doclist, so there - ** should be room. - */ - assert( iDoclistData>=n+nTerm ); - - iData = iDoclistData-(n+nTerm); - memcpy(pWriter->data.pData+iData, c, n); - memcpy(pWriter->data.pData+iData+n, pTerm, nTerm); - - return leafWriterInternalFlush(v, pWriter, iData, pWriter->data.nData-iData); -} - -/* Push pTerm[nTerm] along with the doclist data to the leaf layer of -** %_segments. -*/ -static int leafWriterStepMerge(fulltext_vtab *v, LeafWriter *pWriter, - const char *pTerm, int nTerm, - DLReader *pReaders, int nReaders){ - char c[VARINT_MAX+VARINT_MAX]; - int iTermData = pWriter->data.nData, iDoclistData; - int i, nData, n, nActualData, nActual, rc, nTermDistinct; - - ASSERT_VALID_LEAF_NODE(pWriter->data.pData, pWriter->data.nData); - nTermDistinct = leafWriterEncodeTerm(pWriter, pTerm, nTerm); - - /* Remember nTermDistinct if opening a new node. */ - if( iTermData==0 ) pWriter->nTermDistinct = nTermDistinct; - - iDoclistData = pWriter->data.nData; - - /* Estimate the length of the merged doclist so we can leave space - ** to encode it. - */ - for(i=0, nData=0; idata, c, n); - - docListMerge(&pWriter->data, pReaders, nReaders); - ASSERT_VALID_DOCLIST(DL_DEFAULT, - pWriter->data.pData+iDoclistData+n, - pWriter->data.nData-iDoclistData-n, NULL); - - /* The actual amount of doclist data at this point could be smaller - ** than the length we encoded. Additionally, the space required to - ** encode this length could be smaller. For small doclists, this is - ** not a big deal, we can just use memmove() to adjust things. - */ - nActualData = pWriter->data.nData-(iDoclistData+n); - nActual = putVarint(c, nActualData); - assert( nActualData<=nData ); - assert( nActual<=n ); - - /* If the new doclist is big enough for force a standalone leaf - ** node, we can immediately flush it inline without doing the - ** memmove(). - */ - /* TODO(shess) This test matches leafWriterStep(), which does this - ** test before it knows the cost to varint-encode the term and - ** doclist lengths. At some point, change to - ** pWriter->data.nData-iTermData>STANDALONE_MIN. - */ - if( nTerm+nActualData>STANDALONE_MIN ){ - /* Push leaf node from before this term. */ - if( iTermData>0 ){ - rc = leafWriterInternalFlush(v, pWriter, 0, iTermData); - if( rc!=SQLITE_OK ) return rc; - - pWriter->nTermDistinct = nTermDistinct; - } - - /* Fix the encoded doclist length. */ - iDoclistData += n - nActual; - memcpy(pWriter->data.pData+iDoclistData, c, nActual); - - /* Push the standalone leaf node. */ - rc = leafWriterInlineFlush(v, pWriter, pTerm, nTerm, iDoclistData); - if( rc!=SQLITE_OK ) return rc; - - /* Leave the node empty. */ - dataBufferReset(&pWriter->data); - - return rc; - } - - /* At this point, we know that the doclist was small, so do the - ** memmove if indicated. - */ - if( nActualdata.pData+iDoclistData+nActual, - pWriter->data.pData+iDoclistData+n, - pWriter->data.nData-(iDoclistData+n)); - pWriter->data.nData -= n-nActual; - } - - /* Replace written length with actual length. */ - memcpy(pWriter->data.pData+iDoclistData, c, nActual); - - /* If the node is too large, break things up. */ - /* TODO(shess) This test matches leafWriterStep(), which does this - ** test before it knows the cost to varint-encode the term and - ** doclist lengths. At some point, change to - ** pWriter->data.nData>LEAF_MAX. - */ - if( iTermData+nTerm+nActualData>LEAF_MAX ){ - /* Flush out the leading data as a node */ - rc = leafWriterInternalFlush(v, pWriter, 0, iTermData); - if( rc!=SQLITE_OK ) return rc; - - pWriter->nTermDistinct = nTermDistinct; - - /* Rebuild header using the current term */ - n = putVarint(pWriter->data.pData, 0); - n += putVarint(pWriter->data.pData+n, nTerm); - memcpy(pWriter->data.pData+n, pTerm, nTerm); - n += nTerm; - - /* There should always be room, because the previous encoding - ** included all data necessary to construct the term. - */ - assert( ndata.nData-iDoclistDatadata.pData+n, - pWriter->data.pData+iDoclistData, - pWriter->data.nData-iDoclistData); - pWriter->data.nData -= iDoclistData-n; - } - ASSERT_VALID_LEAF_NODE(pWriter->data.pData, pWriter->data.nData); - - return SQLITE_OK; -} - -/* Push pTerm[nTerm] along with the doclist data to the leaf layer of -** %_segments. -*/ -/* TODO(shess) Revise writeZeroSegment() so that doclists are -** constructed directly in pWriter->data. -*/ -static int leafWriterStep(fulltext_vtab *v, LeafWriter *pWriter, - const char *pTerm, int nTerm, - const char *pData, int nData){ - int rc; - DLReader reader; - - dlrInit(&reader, DL_DEFAULT, pData, nData); - rc = leafWriterStepMerge(v, pWriter, pTerm, nTerm, &reader, 1); - dlrDestroy(&reader); - - return rc; -} - - -/****************************************************************/ -/* LeafReader is used to iterate over an individual leaf node. */ -typedef struct LeafReader { - DataBuffer term; /* copy of current term. */ - - const char *pData; /* data for current term. */ - int nData; -} LeafReader; - -static void leafReaderDestroy(LeafReader *pReader){ - dataBufferDestroy(&pReader->term); - SCRAMBLE(pReader); -} - -static int leafReaderAtEnd(LeafReader *pReader){ - return pReader->nData<=0; -} - -/* Access the current term. */ -static int leafReaderTermBytes(LeafReader *pReader){ - return pReader->term.nData; -} -static const char *leafReaderTerm(LeafReader *pReader){ - assert( pReader->term.nData>0 ); - return pReader->term.pData; -} - -/* Access the doclist data for the current term. */ -static int leafReaderDataBytes(LeafReader *pReader){ - int nData; - assert( pReader->term.nData>0 ); - getVarint32(pReader->pData, &nData); - return nData; -} -static const char *leafReaderData(LeafReader *pReader){ - int n, nData; - assert( pReader->term.nData>0 ); - n = getVarint32(pReader->pData, &nData); - return pReader->pData+n; -} - -static void leafReaderInit(const char *pData, int nData, - LeafReader *pReader){ - int nTerm, n; - - assert( nData>0 ); - assert( pData[0]=='\0' ); - - CLEAR(pReader); - - /* Read the first term, skipping the header byte. */ - n = getVarint32(pData+1, &nTerm); - dataBufferInit(&pReader->term, nTerm); - dataBufferReplace(&pReader->term, pData+1+n, nTerm); - - /* Position after the first term. */ - assert( 1+n+nTermpData = pData+1+n+nTerm; - pReader->nData = nData-1-n-nTerm; -} - -/* Step the reader forward to the next term. */ -static void leafReaderStep(LeafReader *pReader){ - int n, nData, nPrefix, nSuffix; - assert( !leafReaderAtEnd(pReader) ); - - /* Skip previous entry's data block. */ - n = getVarint32(pReader->pData, &nData); - assert( n+nData<=pReader->nData ); - pReader->pData += n+nData; - pReader->nData -= n+nData; - - if( !leafReaderAtEnd(pReader) ){ - /* Construct the new term using a prefix from the old term plus a - ** suffix from the leaf data. - */ - n = getVarint32(pReader->pData, &nPrefix); - n += getVarint32(pReader->pData+n, &nSuffix); - assert( n+nSuffixnData ); - pReader->term.nData = nPrefix; - dataBufferAppend(&pReader->term, pReader->pData+n, nSuffix); - - pReader->pData += n+nSuffix; - pReader->nData -= n+nSuffix; - } -} - -/* strcmp-style comparison of pReader's current term against pTerm. -** If isPrefix, equality means equal through nTerm bytes. -*/ -static int leafReaderTermCmp(LeafReader *pReader, - const char *pTerm, int nTerm, int isPrefix){ - int c, n = pReader->term.nDataterm.nData : nTerm; - if( n==0 ){ - if( pReader->term.nData>0 ) return -1; - if(nTerm>0 ) return 1; - return 0; - } - - c = memcmp(pReader->term.pData, pTerm, n); - if( c!=0 ) return c; - if( isPrefix && n==nTerm ) return 0; - return pReader->term.nData - nTerm; -} - - -/****************************************************************/ -/* LeavesReader wraps LeafReader to allow iterating over the entire -** leaf layer of the tree. -*/ -typedef struct LeavesReader { - int idx; /* Index within the segment. */ - - sqlite3_stmt *pStmt; /* Statement we're streaming leaves from. */ - int eof; /* we've seen SQLITE_DONE from pStmt. */ - - LeafReader leafReader; /* reader for the current leaf. */ - DataBuffer rootData; /* root data for inline. */ -} LeavesReader; - -/* Access the current term. */ -static int leavesReaderTermBytes(LeavesReader *pReader){ - assert( !pReader->eof ); - return leafReaderTermBytes(&pReader->leafReader); -} -static const char *leavesReaderTerm(LeavesReader *pReader){ - assert( !pReader->eof ); - return leafReaderTerm(&pReader->leafReader); -} - -/* Access the doclist data for the current term. */ -static int leavesReaderDataBytes(LeavesReader *pReader){ - assert( !pReader->eof ); - return leafReaderDataBytes(&pReader->leafReader); -} -static const char *leavesReaderData(LeavesReader *pReader){ - assert( !pReader->eof ); - return leafReaderData(&pReader->leafReader); -} - -static int leavesReaderAtEnd(LeavesReader *pReader){ - return pReader->eof; -} - -/* loadSegmentLeaves() may not read all the way to SQLITE_DONE, thus -** leaving the statement handle open, which locks the table. -*/ -/* TODO(shess) This "solution" is not satisfactory. Really, there -** should be check-in function for all statement handles which -** arranges to call sqlite3_reset(). This most likely will require -** modification to control flow all over the place, though, so for now -** just punt. -** -** Note the current system assumes that segment merges will run to -** completion, which is why this particular probably hasn't arisen in -** this case. Probably a brittle assumption. -*/ -static int leavesReaderReset(LeavesReader *pReader){ - return sqlite3_reset(pReader->pStmt); -} - -static void leavesReaderDestroy(LeavesReader *pReader){ - /* If idx is -1, that means we're using a non-cached statement - ** handle in the optimize() case, so we need to release it. - */ - if( pReader->pStmt!=NULL && pReader->idx==-1 ){ - sqlite3_finalize(pReader->pStmt); - } - leafReaderDestroy(&pReader->leafReader); - dataBufferDestroy(&pReader->rootData); - SCRAMBLE(pReader); -} - -/* Initialize pReader with the given root data (if iStartBlockid==0 -** the leaf data was entirely contained in the root), or from the -** stream of blocks between iStartBlockid and iEndBlockid, inclusive. -*/ -static int leavesReaderInit(fulltext_vtab *v, - int idx, - sqlite_int64 iStartBlockid, - sqlite_int64 iEndBlockid, - const char *pRootData, int nRootData, - LeavesReader *pReader){ - CLEAR(pReader); - pReader->idx = idx; - - dataBufferInit(&pReader->rootData, 0); - if( iStartBlockid==0 ){ - /* Entire leaf level fit in root data. */ - dataBufferReplace(&pReader->rootData, pRootData, nRootData); - leafReaderInit(pReader->rootData.pData, pReader->rootData.nData, - &pReader->leafReader); - }else{ - sqlite3_stmt *s; - int rc = sql_get_leaf_statement(v, idx, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iStartBlockid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 2, iEndBlockid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ){ - pReader->eof = 1; - return SQLITE_OK; - } - if( rc!=SQLITE_ROW ) return rc; - - pReader->pStmt = s; - leafReaderInit(sqlite3_column_blob(pReader->pStmt, 0), - sqlite3_column_bytes(pReader->pStmt, 0), - &pReader->leafReader); - } - return SQLITE_OK; -} - -/* Step the current leaf forward to the next term. If we reach the -** end of the current leaf, step forward to the next leaf block. -*/ -static int leavesReaderStep(fulltext_vtab *v, LeavesReader *pReader){ - assert( !leavesReaderAtEnd(pReader) ); - leafReaderStep(&pReader->leafReader); - - if( leafReaderAtEnd(&pReader->leafReader) ){ - int rc; - if( pReader->rootData.pData ){ - pReader->eof = 1; - return SQLITE_OK; - } - rc = sqlite3_step(pReader->pStmt); - if( rc!=SQLITE_ROW ){ - pReader->eof = 1; - return rc==SQLITE_DONE ? SQLITE_OK : rc; - } - leafReaderDestroy(&pReader->leafReader); - leafReaderInit(sqlite3_column_blob(pReader->pStmt, 0), - sqlite3_column_bytes(pReader->pStmt, 0), - &pReader->leafReader); - } - return SQLITE_OK; -} - -/* Order LeavesReaders by their term, ignoring idx. Readers at eof -** always sort to the end. -*/ -static int leavesReaderTermCmp(LeavesReader *lr1, LeavesReader *lr2){ - if( leavesReaderAtEnd(lr1) ){ - if( leavesReaderAtEnd(lr2) ) return 0; - return 1; - } - if( leavesReaderAtEnd(lr2) ) return -1; - - return leafReaderTermCmp(&lr1->leafReader, - leavesReaderTerm(lr2), leavesReaderTermBytes(lr2), - 0); -} - -/* Similar to leavesReaderTermCmp(), with additional ordering by idx -** so that older segments sort before newer segments. -*/ -static int leavesReaderCmp(LeavesReader *lr1, LeavesReader *lr2){ - int c = leavesReaderTermCmp(lr1, lr2); - if( c!=0 ) return c; - return lr1->idx-lr2->idx; -} - -/* Assume that pLr[1]..pLr[nLr] are sorted. Bubble pLr[0] into its -** sorted position. -*/ -static void leavesReaderReorder(LeavesReader *pLr, int nLr){ - while( nLr>1 && leavesReaderCmp(pLr, pLr+1)>0 ){ - LeavesReader tmp = pLr[0]; - pLr[0] = pLr[1]; - pLr[1] = tmp; - nLr--; - pLr++; - } -} - -/* Initializes pReaders with the segments from level iLevel, returning -** the number of segments in *piReaders. Leaves pReaders in sorted -** order. -*/ -static int leavesReadersInit(fulltext_vtab *v, int iLevel, - LeavesReader *pReaders, int *piReaders){ - sqlite3_stmt *s; - int i, rc = sql_get_statement(v, SEGDIR_SELECT_LEVEL_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int(s, 1, iLevel); - if( rc!=SQLITE_OK ) return rc; - - i = 0; - while( (rc = sqlite3_step(s))==SQLITE_ROW ){ - sqlite_int64 iStart = sqlite3_column_int64(s, 0); - sqlite_int64 iEnd = sqlite3_column_int64(s, 1); - const char *pRootData = sqlite3_column_blob(s, 2); - int nRootData = sqlite3_column_bytes(s, 2); - - assert( i0 ){ - leavesReaderDestroy(&pReaders[i]); - } - return rc; - } - - *piReaders = i; - - /* Leave our results sorted by term, then age. */ - while( i-- ){ - leavesReaderReorder(pReaders+i, *piReaders-i); - } - return SQLITE_OK; -} - -/* Merge doclists from pReaders[nReaders] into a single doclist, which -** is written to pWriter. Assumes pReaders is ordered oldest to -** newest. -*/ -/* TODO(shess) Consider putting this inline in segmentMerge(). */ -static int leavesReadersMerge(fulltext_vtab *v, - LeavesReader *pReaders, int nReaders, - LeafWriter *pWriter){ - DLReader dlReaders[MERGE_COUNT]; - const char *pTerm = leavesReaderTerm(pReaders); - int i, nTerm = leavesReaderTermBytes(pReaders); - - assert( nReaders<=MERGE_COUNT ); - - for(i=0; i0 ){ - rc = leavesReaderStep(v, lrs+i); - if( rc!=SQLITE_OK ) goto err; - - /* Reorder by term, then by age. */ - leavesReaderReorder(lrs+i, MERGE_COUNT-i); - } - } - - for(i=0; i0 ); - - for(rc=SQLITE_OK; rc==SQLITE_OK && !leavesReaderAtEnd(pReader); - rc=leavesReaderStep(v, pReader)){ - /* TODO(shess) Really want leavesReaderTermCmp(), but that name is - ** already taken to compare the terms of two LeavesReaders. Think - ** on a better name. [Meanwhile, break encapsulation rather than - ** use a confusing name.] - */ - int c = leafReaderTermCmp(&pReader->leafReader, pTerm, nTerm, isPrefix); - if( c>0 ) break; /* Past any possible matches. */ - if( c==0 ){ - const char *pData = leavesReaderData(pReader); - int iBuffer, nData = leavesReaderDataBytes(pReader); - - /* Find the first empty buffer. */ - for(iBuffer=0; iBuffer0 ){ - assert(pBuffers!=NULL); - memcpy(p, pBuffers, nBuffers*sizeof(*pBuffers)); - sqlite3_free(pBuffers); - } - pBuffers = p; - } - dataBufferInit(&(pBuffers[nBuffers]), 0); - nBuffers++; - } - - /* At this point, must have an empty at iBuffer. */ - assert(iBufferpData, p->nData); - - /* dataBufferReset() could allow a large doclist to blow up - ** our memory requirements. - */ - if( p->nCapacity<1024 ){ - dataBufferReset(p); - }else{ - dataBufferDestroy(p); - dataBufferInit(p, 0); - } - } - } - } - } - - /* Union all the doclists together into *out. */ - /* TODO(shess) What if *out is big? Sigh. */ - if( rc==SQLITE_OK && nBuffers>0 ){ - int iBuffer; - for(iBuffer=0; iBuffer0 ){ - if( out->nData==0 ){ - dataBufferSwap(out, &(pBuffers[iBuffer])); - }else{ - docListAccumulateUnion(out, pBuffers[iBuffer].pData, - pBuffers[iBuffer].nData); - } - } - } - } - - while( nBuffers-- ){ - dataBufferDestroy(&(pBuffers[nBuffers])); - } - if( pBuffers!=NULL ) sqlite3_free(pBuffers); - - return rc; -} - -/* Call loadSegmentLeavesInt() with pData/nData as input. */ -static int loadSegmentLeaf(fulltext_vtab *v, const char *pData, int nData, - const char *pTerm, int nTerm, int isPrefix, - DataBuffer *out){ - LeavesReader reader; - int rc; - - assert( nData>1 ); - assert( *pData=='\0' ); - rc = leavesReaderInit(v, 0, 0, 0, pData, nData, &reader); - if( rc!=SQLITE_OK ) return rc; - - rc = loadSegmentLeavesInt(v, &reader, pTerm, nTerm, isPrefix, out); - leavesReaderReset(&reader); - leavesReaderDestroy(&reader); - return rc; -} - -/* Call loadSegmentLeavesInt() with the leaf nodes from iStartLeaf to -** iEndLeaf (inclusive) as input, and merge the resulting doclist into -** out. -*/ -static int loadSegmentLeaves(fulltext_vtab *v, - sqlite_int64 iStartLeaf, sqlite_int64 iEndLeaf, - const char *pTerm, int nTerm, int isPrefix, - DataBuffer *out){ - int rc; - LeavesReader reader; - - assert( iStartLeaf<=iEndLeaf ); - rc = leavesReaderInit(v, 0, iStartLeaf, iEndLeaf, NULL, 0, &reader); - if( rc!=SQLITE_OK ) return rc; - - rc = loadSegmentLeavesInt(v, &reader, pTerm, nTerm, isPrefix, out); - leavesReaderReset(&reader); - leavesReaderDestroy(&reader); - return rc; -} - -/* Taking pData/nData as an interior node, find the sequence of child -** nodes which could include pTerm/nTerm/isPrefix. Note that the -** interior node terms logically come between the blocks, so there is -** one more blockid than there are terms (that block contains terms >= -** the last interior-node term). -*/ -/* TODO(shess) The calling code may already know that the end child is -** not worth calculating, because the end may be in a later sibling -** node. Consider whether breaking symmetry is worthwhile. I suspect -** it is not worthwhile. -*/ -static void getChildrenContaining(const char *pData, int nData, - const char *pTerm, int nTerm, int isPrefix, - sqlite_int64 *piStartChild, - sqlite_int64 *piEndChild){ - InteriorReader reader; - - assert( nData>1 ); - assert( *pData!='\0' ); - interiorReaderInit(pData, nData, &reader); - - /* Scan for the first child which could contain pTerm/nTerm. */ - while( !interiorReaderAtEnd(&reader) ){ - if( interiorReaderTermCmp(&reader, pTerm, nTerm, 0)>0 ) break; - interiorReaderStep(&reader); - } - *piStartChild = interiorReaderCurrentBlockid(&reader); - - /* Keep scanning to find a term greater than our term, using prefix - ** comparison if indicated. If isPrefix is false, this will be the - ** same blockid as the starting block. - */ - while( !interiorReaderAtEnd(&reader) ){ - if( interiorReaderTermCmp(&reader, pTerm, nTerm, isPrefix)>0 ) break; - interiorReaderStep(&reader); - } - *piEndChild = interiorReaderCurrentBlockid(&reader); - - interiorReaderDestroy(&reader); - - /* Children must ascend, and if !prefix, both must be the same. */ - assert( *piEndChild>=*piStartChild ); - assert( isPrefix || *piStartChild==*piEndChild ); -} - -/* Read block at iBlockid and pass it with other params to -** getChildrenContaining(). -*/ -static int loadAndGetChildrenContaining( - fulltext_vtab *v, - sqlite_int64 iBlockid, - const char *pTerm, int nTerm, int isPrefix, - sqlite_int64 *piStartChild, sqlite_int64 *piEndChild -){ - sqlite3_stmt *s = NULL; - int rc; - - assert( iBlockid!=0 ); - assert( pTerm!=NULL ); - assert( nTerm!=0 ); /* TODO(shess) Why not allow this? */ - assert( piStartChild!=NULL ); - assert( piEndChild!=NULL ); - - rc = sql_get_statement(v, BLOCK_SELECT_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_bind_int64(s, 1, iBlockid); - if( rc!=SQLITE_OK ) return rc; - - rc = sqlite3_step(s); - if( rc==SQLITE_DONE ) return SQLITE_ERROR; - if( rc!=SQLITE_ROW ) return rc; - - getChildrenContaining(sqlite3_column_blob(s, 0), sqlite3_column_bytes(s, 0), - pTerm, nTerm, isPrefix, piStartChild, piEndChild); - - /* We expect only one row. We must execute another sqlite3_step() - * to complete the iteration; otherwise the table will remain - * locked. */ - rc = sqlite3_step(s); - if( rc==SQLITE_ROW ) return SQLITE_ERROR; - if( rc!=SQLITE_DONE ) return rc; - - return SQLITE_OK; -} - -/* Traverse the tree represented by pData[nData] looking for -** pTerm[nTerm], placing its doclist into *out. This is internal to -** loadSegment() to make error-handling cleaner. -*/ -static int loadSegmentInt(fulltext_vtab *v, const char *pData, int nData, - sqlite_int64 iLeavesEnd, - const char *pTerm, int nTerm, int isPrefix, - DataBuffer *out){ - /* Special case where root is a leaf. */ - if( *pData=='\0' ){ - return loadSegmentLeaf(v, pData, nData, pTerm, nTerm, isPrefix, out); - }else{ - int rc; - sqlite_int64 iStartChild, iEndChild; - - /* Process pData as an interior node, then loop down the tree - ** until we find the set of leaf nodes to scan for the term. - */ - getChildrenContaining(pData, nData, pTerm, nTerm, isPrefix, - &iStartChild, &iEndChild); - while( iStartChild>iLeavesEnd ){ - sqlite_int64 iNextStart, iNextEnd; - rc = loadAndGetChildrenContaining(v, iStartChild, pTerm, nTerm, isPrefix, - &iNextStart, &iNextEnd); - if( rc!=SQLITE_OK ) return rc; - - /* If we've branched, follow the end branch, too. */ - if( iStartChild!=iEndChild ){ - sqlite_int64 iDummy; - rc = loadAndGetChildrenContaining(v, iEndChild, pTerm, nTerm, isPrefix, - &iDummy, &iNextEnd); - if( rc!=SQLITE_OK ) return rc; - } - - assert( iNextStart<=iNextEnd ); - iStartChild = iNextStart; - iEndChild = iNextEnd; - } - assert( iStartChild<=iLeavesEnd ); - assert( iEndChild<=iLeavesEnd ); - - /* Scan through the leaf segments for doclists. */ - return loadSegmentLeaves(v, iStartChild, iEndChild, - pTerm, nTerm, isPrefix, out); - } -} - -/* Call loadSegmentInt() to collect the doclist for pTerm/nTerm, then -** merge its doclist over *out (any duplicate doclists read from the -** segment rooted at pData will overwrite those in *out). -*/ -/* TODO(shess) Consider changing this to determine the depth of the -** leaves using either the first characters of interior nodes (when -** ==1, we're one level above the leaves), or the first character of -** the root (which will describe the height of the tree directly). -** Either feels somewhat tricky to me. -*/ -/* TODO(shess) The current merge is likely to be slow for large -** doclists (though it should process from newest/smallest to -** oldest/largest, so it may not be that bad). It might be useful to -** modify things to allow for N-way merging. This could either be -** within a segment, with pairwise merges across segments, or across -** all segments at once. -*/ -static int loadSegment(fulltext_vtab *v, const char *pData, int nData, - sqlite_int64 iLeavesEnd, - const char *pTerm, int nTerm, int isPrefix, - DataBuffer *out){ - DataBuffer result; - int rc; - - assert( nData>1 ); - - /* This code should never be called with buffered updates. */ - assert( v->nPendingData<0 ); - - dataBufferInit(&result, 0); - rc = loadSegmentInt(v, pData, nData, iLeavesEnd, - pTerm, nTerm, isPrefix, &result); - if( rc==SQLITE_OK && result.nData>0 ){ - if( out->nData==0 ){ - DataBuffer tmp = *out; - *out = result; - result = tmp; - }else{ - DataBuffer merged; - DLReader readers[2]; - - dlrInit(&readers[0], DL_DEFAULT, out->pData, out->nData); - dlrInit(&readers[1], DL_DEFAULT, result.pData, result.nData); - dataBufferInit(&merged, out->nData+result.nData); - docListMerge(&merged, readers, 2); - dataBufferDestroy(out); - *out = merged; - dlrDestroy(&readers[0]); - dlrDestroy(&readers[1]); - } - } - dataBufferDestroy(&result); - return rc; -} - -/* Scan the database and merge together the posting lists for the term -** into *out. -*/ -static int termSelect(fulltext_vtab *v, int iColumn, - const char *pTerm, int nTerm, int isPrefix, - DocListType iType, DataBuffer *out){ - DataBuffer doclist; - sqlite3_stmt *s; - int rc = sql_get_statement(v, SEGDIR_SELECT_ALL_STMT, &s); - if( rc!=SQLITE_OK ) return rc; - - /* This code should never be called with buffered updates. */ - assert( v->nPendingData<0 ); - - dataBufferInit(&doclist, 0); - - /* Traverse the segments from oldest to newest so that newer doclist - ** elements for given docids overwrite older elements. - */ - while( (rc = sqlite3_step(s))==SQLITE_ROW ){ - const char *pData = sqlite3_column_blob(s, 2); - const int nData = sqlite3_column_bytes(s, 2); - const sqlite_int64 iLeavesEnd = sqlite3_column_int64(s, 1); - rc = loadSegment(v, pData, nData, iLeavesEnd, pTerm, nTerm, isPrefix, - &doclist); - if( rc!=SQLITE_OK ) goto err; - } - if( rc==SQLITE_DONE ){ - if( doclist.nData!=0 ){ - /* TODO(shess) The old term_select_all() code applied the column - ** restrict as we merged segments, leading to smaller buffers. - ** This is probably worthwhile to bring back, once the new storage - ** system is checked in. - */ - if( iColumn==v->nColumn) iColumn = -1; - docListTrim(DL_DEFAULT, doclist.pData, doclist.nData, - iColumn, iType, out); - } - rc = SQLITE_OK; - } - - err: - dataBufferDestroy(&doclist); - return rc; -} - -/****************************************************************/ -/* Used to hold hashtable data for sorting. */ -typedef struct TermData { - const char *pTerm; - int nTerm; - DLCollector *pCollector; -} TermData; - -/* Orders TermData elements in strcmp fashion ( <0 for less-than, 0 -** for equal, >0 for greater-than). -*/ -static int termDataCmp(const void *av, const void *bv){ - const TermData *a = (const TermData *)av; - const TermData *b = (const TermData *)bv; - int n = a->nTermnTerm ? a->nTerm : b->nTerm; - int c = memcmp(a->pTerm, b->pTerm, n); - if( c!=0 ) return c; - return a->nTerm-b->nTerm; -} - -/* Order pTerms data by term, then write a new level 0 segment using -** LeafWriter. -*/ -static int writeZeroSegment(fulltext_vtab *v, fts2Hash *pTerms){ - fts2HashElem *e; - int idx, rc, i, n; - TermData *pData; - LeafWriter writer; - DataBuffer dl; - - /* Determine the next index at level 0, merging as necessary. */ - rc = segdirNextIndex(v, 0, &idx); - if( rc!=SQLITE_OK ) return rc; - - n = fts2HashCount(pTerms); - pData = sqlite3_malloc(n*sizeof(TermData)); - - for(i = 0, e = fts2HashFirst(pTerms); e; i++, e = fts2HashNext(e)){ - assert( i1 ) qsort(pData, n, sizeof(*pData), termDataCmp); - - /* TODO(shess) Refactor so that we can write directly to the segment - ** DataBuffer, as happens for segment merges. - */ - leafWriterInit(0, idx, &writer); - dataBufferInit(&dl, 0); - for(i=0; inPendingData>=0 ){ - fts2HashElem *e; - for(e=fts2HashFirst(&v->pendingTerms); e; e=fts2HashNext(e)){ - dlcDelete(fts2HashData(e)); - } - fts2HashClear(&v->pendingTerms); - v->nPendingData = -1; - } - return SQLITE_OK; -} - -/* If pendingTerms has data, flush it to a level-zero segment, and -** free it. -*/ -static int flushPendingTerms(fulltext_vtab *v){ - if( v->nPendingData>=0 ){ - int rc = writeZeroSegment(v, &v->pendingTerms); - if( rc==SQLITE_OK ) clearPendingTerms(v); - return rc; - } - return SQLITE_OK; -} - -/* If pendingTerms is "too big", or docid is out of order, flush it. -** Regardless, be certain that pendingTerms is initialized for use. -*/ -static int initPendingTerms(fulltext_vtab *v, sqlite_int64 iDocid){ - /* TODO(shess) Explore whether partially flushing the buffer on - ** forced-flush would provide better performance. I suspect that if - ** we ordered the doclists by size and flushed the largest until the - ** buffer was half empty, that would let the less frequent terms - ** generate longer doclists. - */ - if( iDocid<=v->iPrevDocid || v->nPendingData>kPendingThreshold ){ - int rc = flushPendingTerms(v); - if( rc!=SQLITE_OK ) return rc; - } - if( v->nPendingData<0 ){ - fts2HashInit(&v->pendingTerms, FTS2_HASH_STRING, 1); - v->nPendingData = 0; - } - v->iPrevDocid = iDocid; - return SQLITE_OK; -} - -/* This function implements the xUpdate callback; it is the top-level entry - * point for inserting, deleting or updating a row in a full-text table. */ -static int fulltextUpdate(sqlite3_vtab *pVtab, int nArg, sqlite3_value **ppArg, - sqlite_int64 *pRowid){ - fulltext_vtab *v = (fulltext_vtab *) pVtab; - int rc; - - TRACE(("FTS2 Update %p\n", pVtab)); - - if( nArg<2 ){ - rc = index_delete(v, sqlite3_value_int64(ppArg[0])); - if( rc==SQLITE_OK ){ - /* If we just deleted the last row in the table, clear out the - ** index data. - */ - rc = content_exists(v); - if( rc==SQLITE_ROW ){ - rc = SQLITE_OK; - }else if( rc==SQLITE_DONE ){ - /* Clear the pending terms so we don't flush a useless level-0 - ** segment when the transaction closes. - */ - rc = clearPendingTerms(v); - if( rc==SQLITE_OK ){ - rc = segdir_delete_all(v); - } - } - } - } else if( sqlite3_value_type(ppArg[0]) != SQLITE_NULL ){ - /* An update: - * ppArg[0] = old rowid - * ppArg[1] = new rowid - * ppArg[2..2+v->nColumn-1] = values - * ppArg[2+v->nColumn] = value for magic column (we ignore this) - */ - sqlite_int64 rowid = sqlite3_value_int64(ppArg[0]); - if( sqlite3_value_type(ppArg[1]) != SQLITE_INTEGER || - sqlite3_value_int64(ppArg[1]) != rowid ){ - rc = SQLITE_ERROR; /* we don't allow changing the rowid */ - } else { - assert( nArg==2+v->nColumn+1); - rc = index_update(v, rowid, &ppArg[2]); - } - } else { - /* An insert: - * ppArg[1] = requested rowid - * ppArg[2..2+v->nColumn-1] = values - * ppArg[2+v->nColumn] = value for magic column (we ignore this) - */ - assert( nArg==2+v->nColumn+1); - rc = index_insert(v, ppArg[1], &ppArg[2], pRowid); - } - - return rc; -} - -static int fulltextSync(sqlite3_vtab *pVtab){ - TRACE(("FTS2 xSync()\n")); - return flushPendingTerms((fulltext_vtab *)pVtab); -} - -static int fulltextBegin(sqlite3_vtab *pVtab){ - fulltext_vtab *v = (fulltext_vtab *) pVtab; - TRACE(("FTS2 xBegin()\n")); - - /* Any buffered updates should have been cleared by the previous - ** transaction. - */ - assert( v->nPendingData<0 ); - return clearPendingTerms(v); -} - -static int fulltextCommit(sqlite3_vtab *pVtab){ - fulltext_vtab *v = (fulltext_vtab *) pVtab; - TRACE(("FTS2 xCommit()\n")); - - /* Buffered updates should have been cleared by fulltextSync(). */ - assert( v->nPendingData<0 ); - return clearPendingTerms(v); -} - -static int fulltextRollback(sqlite3_vtab *pVtab){ - TRACE(("FTS2 xRollback()\n")); - return clearPendingTerms((fulltext_vtab *)pVtab); -} - -/* -** Implementation of the snippet() function for FTS2 -*/ -static void snippetFunc( - sqlite3_context *pContext, - int argc, - sqlite3_value **argv -){ - fulltext_cursor *pCursor; - if( argc<1 ) return; - if( sqlite3_value_type(argv[0])!=SQLITE_BLOB || - sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){ - sqlite3_result_error(pContext, "illegal first argument to html_snippet",-1); - }else{ - const char *zStart = ""; - const char *zEnd = ""; - const char *zEllipsis = "..."; - memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor)); - if( argc>=2 ){ - zStart = (const char*)sqlite3_value_text(argv[1]); - if( argc>=3 ){ - zEnd = (const char*)sqlite3_value_text(argv[2]); - if( argc>=4 ){ - zEllipsis = (const char*)sqlite3_value_text(argv[3]); - } - } - } - snippetAllOffsets(pCursor); - snippetText(pCursor, zStart, zEnd, zEllipsis); - sqlite3_result_text(pContext, pCursor->snippet.zSnippet, - pCursor->snippet.nSnippet, SQLITE_STATIC); - } -} - -/* -** Implementation of the offsets() function for FTS2 -*/ -static void snippetOffsetsFunc( - sqlite3_context *pContext, - int argc, - sqlite3_value **argv -){ - fulltext_cursor *pCursor; - if( argc<1 ) return; - if( sqlite3_value_type(argv[0])!=SQLITE_BLOB || - sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){ - sqlite3_result_error(pContext, "illegal first argument to offsets",-1); - }else{ - memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor)); - snippetAllOffsets(pCursor); - snippetOffsetText(&pCursor->snippet); - sqlite3_result_text(pContext, - pCursor->snippet.zOffset, pCursor->snippet.nOffset, - SQLITE_STATIC); - } -} - -/* OptLeavesReader is nearly identical to LeavesReader, except that -** where LeavesReader is geared towards the merging of complete -** segment levels (with exactly MERGE_COUNT segments), OptLeavesReader -** is geared towards implementation of the optimize() function, and -** can merge all segments simultaneously. This version may be -** somewhat less efficient than LeavesReader because it merges into an -** accumulator rather than doing an N-way merge, but since segment -** size grows exponentially (so segment count logrithmically) this is -** probably not an immediate problem. -*/ -/* TODO(shess): Prove that assertion, or extend the merge code to -** merge tree fashion (like the prefix-searching code does). -*/ -/* TODO(shess): OptLeavesReader and LeavesReader could probably be -** merged with little or no loss of performance for LeavesReader. The -** merged code would need to handle >MERGE_COUNT segments, and would -** also need to be able to optionally optimize away deletes. -*/ -typedef struct OptLeavesReader { - /* Segment number, to order readers by age. */ - int segment; - LeavesReader reader; -} OptLeavesReader; - -static int optLeavesReaderAtEnd(OptLeavesReader *pReader){ - return leavesReaderAtEnd(&pReader->reader); -} -static int optLeavesReaderTermBytes(OptLeavesReader *pReader){ - return leavesReaderTermBytes(&pReader->reader); -} -static const char *optLeavesReaderData(OptLeavesReader *pReader){ - return leavesReaderData(&pReader->reader); -} -static int optLeavesReaderDataBytes(OptLeavesReader *pReader){ - return leavesReaderDataBytes(&pReader->reader); -} -static const char *optLeavesReaderTerm(OptLeavesReader *pReader){ - return leavesReaderTerm(&pReader->reader); -} -static int optLeavesReaderStep(fulltext_vtab *v, OptLeavesReader *pReader){ - return leavesReaderStep(v, &pReader->reader); -} -static int optLeavesReaderTermCmp(OptLeavesReader *lr1, OptLeavesReader *lr2){ - return leavesReaderTermCmp(&lr1->reader, &lr2->reader); -} -/* Order by term ascending, segment ascending (oldest to newest), with -** exhausted readers to the end. -*/ -static int optLeavesReaderCmp(OptLeavesReader *lr1, OptLeavesReader *lr2){ - int c = optLeavesReaderTermCmp(lr1, lr2); - if( c!=0 ) return c; - return lr1->segment-lr2->segment; -} -/* Bubble pLr[0] to appropriate place in pLr[1..nLr-1]. Assumes that -** pLr[1..nLr-1] is already sorted. -*/ -static void optLeavesReaderReorder(OptLeavesReader *pLr, int nLr){ - while( nLr>1 && optLeavesReaderCmp(pLr, pLr+1)>0 ){ - OptLeavesReader tmp = pLr[0]; - pLr[0] = pLr[1]; - pLr[1] = tmp; - nLr--; - pLr++; - } -} - -/* optimize() helper function. Put the readers in order and iterate -** through them, merging doclists for matching terms into pWriter. -** Returns SQLITE_OK on success, or the SQLite error code which -** prevented success. -*/ -static int optimizeInternal(fulltext_vtab *v, - OptLeavesReader *readers, int nReaders, - LeafWriter *pWriter){ - int i, rc = SQLITE_OK; - DataBuffer doclist, merged, tmp; - - /* Order the readers. */ - i = nReaders; - while( i-- > 0 ){ - optLeavesReaderReorder(&readers[i], nReaders-i); - } - - dataBufferInit(&doclist, LEAF_MAX); - dataBufferInit(&merged, LEAF_MAX); - - /* Exhausted readers bubble to the end, so when the first reader is - ** at eof, all are at eof. - */ - while( !optLeavesReaderAtEnd(&readers[0]) ){ - - /* Figure out how many readers share the next term. */ - for(i=1; i 0 ){ - dlrDestroy(&dlReaders[nReaders]); - } - - /* Accumulated doclist to reader 0 for next pass. */ - dlrInit(&dlReaders[0], DL_DEFAULT, doclist.pData, doclist.nData); - } - - /* Destroy reader that was left in the pipeline. */ - dlrDestroy(&dlReaders[0]); - - /* Trim deletions from the doclist. */ - dataBufferReset(&merged); - docListTrim(DL_DEFAULT, doclist.pData, doclist.nData, - -1, DL_DEFAULT, &merged); - } - - /* Only pass doclists with hits (skip if all hits deleted). */ - if( merged.nData>0 ){ - rc = leafWriterStep(v, pWriter, - optLeavesReaderTerm(&readers[0]), - optLeavesReaderTermBytes(&readers[0]), - merged.pData, merged.nData); - if( rc!=SQLITE_OK ) goto err; - } - - /* Step merged readers to next term and reorder. */ - while( i-- > 0 ){ - rc = optLeavesReaderStep(v, &readers[i]); - if( rc!=SQLITE_OK ) goto err; - - optLeavesReaderReorder(&readers[i], nReaders-i); - } - } - - err: - dataBufferDestroy(&doclist); - dataBufferDestroy(&merged); - return rc; -} - -/* Implement optimize() function for FTS3. optimize(t) merges all -** segments in the fts index into a single segment. 't' is the magic -** table-named column. -*/ -static void optimizeFunc(sqlite3_context *pContext, - int argc, sqlite3_value **argv){ - fulltext_cursor *pCursor; - if( argc>1 ){ - sqlite3_result_error(pContext, "excess arguments to optimize()",-1); - }else if( sqlite3_value_type(argv[0])!=SQLITE_BLOB || - sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){ - sqlite3_result_error(pContext, "illegal first argument to optimize",-1); - }else{ - fulltext_vtab *v; - int i, rc, iMaxLevel; - OptLeavesReader *readers; - int nReaders; - LeafWriter writer; - sqlite3_stmt *s; - - memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor)); - v = cursor_vtab(pCursor); - - /* Flush any buffered updates before optimizing. */ - rc = flushPendingTerms(v); - if( rc!=SQLITE_OK ) goto err; - - rc = segdir_count(v, &nReaders, &iMaxLevel); - if( rc!=SQLITE_OK ) goto err; - if( nReaders==0 || nReaders==1 ){ - sqlite3_result_text(pContext, "Index already optimal", -1, - SQLITE_STATIC); - return; - } - - rc = sql_get_statement(v, SEGDIR_SELECT_ALL_STMT, &s); - if( rc!=SQLITE_OK ) goto err; - - readers = sqlite3_malloc(nReaders*sizeof(readers[0])); - if( readers==NULL ) goto err; - - /* Note that there will already be a segment at this position - ** until we call segdir_delete() on iMaxLevel. - */ - leafWriterInit(iMaxLevel, 0, &writer); - - i = 0; - while( (rc = sqlite3_step(s))==SQLITE_ROW ){ - sqlite_int64 iStart = sqlite3_column_int64(s, 0); - sqlite_int64 iEnd = sqlite3_column_int64(s, 1); - const char *pRootData = sqlite3_column_blob(s, 2); - int nRootData = sqlite3_column_bytes(s, 2); - - assert( i 0 ){ - leavesReaderDestroy(&readers[i].reader); - } - sqlite3_free(readers); - - /* If we've successfully gotten to here, delete the old segments - ** and flush the interior structure of the new segment. - */ - if( rc==SQLITE_OK ){ - for( i=0; i<=iMaxLevel; i++ ){ - rc = segdir_delete(v, i); - if( rc!=SQLITE_OK ) break; - } - - if( rc==SQLITE_OK ) rc = leafWriterFinalize(v, &writer); - } - - leafWriterDestroy(&writer); - - if( rc!=SQLITE_OK ) goto err; - - sqlite3_result_text(pContext, "Index optimized", -1, SQLITE_STATIC); - return; - - /* TODO(shess): Error-handling needs to be improved along the - ** lines of the dump_ functions. - */ - err: - { - char buf[512]; - sqlite3_snprintf(sizeof(buf), buf, "Error in optimize: %s", - sqlite3_errmsg(sqlite3_context_db_handle(pContext))); - sqlite3_result_error(pContext, buf, -1); - } - } -} - -#ifdef SQLITE_TEST -/* Generate an error of the form ": ". If msg is NULL, -** pull the error from the context's db handle. -*/ -static void generateError(sqlite3_context *pContext, - const char *prefix, const char *msg){ - char buf[512]; - if( msg==NULL ) msg = sqlite3_errmsg(sqlite3_context_db_handle(pContext)); - sqlite3_snprintf(sizeof(buf), buf, "%s: %s", prefix, msg); - sqlite3_result_error(pContext, buf, -1); -} - -/* Helper function to collect the set of terms in the segment into -** pTerms. The segment is defined by the leaf nodes between -** iStartBlockid and iEndBlockid, inclusive, or by the contents of -** pRootData if iStartBlockid is 0 (in which case the entire segment -** fit in a leaf). -*/ -static int collectSegmentTerms(fulltext_vtab *v, sqlite3_stmt *s, - fts2Hash *pTerms){ - const sqlite_int64 iStartBlockid = sqlite3_column_int64(s, 0); - const sqlite_int64 iEndBlockid = sqlite3_column_int64(s, 1); - const char *pRootData = sqlite3_column_blob(s, 2); - const int nRootData = sqlite3_column_bytes(s, 2); - LeavesReader reader; - int rc = leavesReaderInit(v, 0, iStartBlockid, iEndBlockid, - pRootData, nRootData, &reader); - if( rc!=SQLITE_OK ) return rc; - - while( rc==SQLITE_OK && !leavesReaderAtEnd(&reader) ){ - const char *pTerm = leavesReaderTerm(&reader); - const int nTerm = leavesReaderTermBytes(&reader); - void *oldValue = sqlite3Fts2HashFind(pTerms, pTerm, nTerm); - void *newValue = (void *)((char *)oldValue+1); - - /* From the comment before sqlite3Fts2HashInsert in fts2_hash.c, - ** the data value passed is returned in case of malloc failure. - */ - if( newValue==sqlite3Fts2HashInsert(pTerms, pTerm, nTerm, newValue) ){ - rc = SQLITE_NOMEM; - }else{ - rc = leavesReaderStep(v, &reader); - } - } - - leavesReaderDestroy(&reader); - return rc; -} - -/* Helper function to build the result string for dump_terms(). */ -static int generateTermsResult(sqlite3_context *pContext, fts2Hash *pTerms){ - int iTerm, nTerms, nResultBytes, iByte; - char *result; - TermData *pData; - fts2HashElem *e; - - /* Iterate pTerms to generate an array of terms in pData for - ** sorting. - */ - nTerms = fts2HashCount(pTerms); - assert( nTerms>0 ); - pData = sqlite3_malloc(nTerms*sizeof(TermData)); - if( pData==NULL ) return SQLITE_NOMEM; - - nResultBytes = 0; - for(iTerm = 0, e = fts2HashFirst(pTerms); e; iTerm++, e = fts2HashNext(e)){ - nResultBytes += fts2HashKeysize(e)+1; /* Term plus trailing space */ - assert( iTerm0 ); /* nTerms>0, nResultsBytes must be, too. */ - result = sqlite3_malloc(nResultBytes); - if( result==NULL ){ - sqlite3_free(pData); - return SQLITE_NOMEM; - } - - if( nTerms>1 ) qsort(pData, nTerms, sizeof(*pData), termDataCmp); - - /* Read the terms in order to build the result. */ - iByte = 0; - for(iTerm=0; iTerm0 ){ - rc = generateTermsResult(pContext, &terms); - if( rc==SQLITE_NOMEM ){ - generateError(pContext, "dump_terms", "out of memory"); - }else{ - assert( rc==SQLITE_OK ); - } - }else if( argc==3 ){ - /* The specific segment asked for could not be found. */ - generateError(pContext, "dump_terms", "segment not found"); - }else{ - /* No segments found. */ - /* TODO(shess): It should be impossible to reach this. This - ** case can only happen for an empty table, in which case - ** SQLite has no rows to call this function on. - */ - sqlite3_result_null(pContext); - } - } - sqlite3Fts2HashClear(&terms); - } -} - -/* Expand the DL_DEFAULT doclist in pData into a text result in -** pContext. -*/ -static void createDoclistResult(sqlite3_context *pContext, - const char *pData, int nData){ - DataBuffer dump; - DLReader dlReader; - - assert( pData!=NULL && nData>0 ); - - dataBufferInit(&dump, 0); - dlrInit(&dlReader, DL_DEFAULT, pData, nData); - for( ; !dlrAtEnd(&dlReader); dlrStep(&dlReader) ){ - char buf[256]; - PLReader plReader; - - plrInit(&plReader, &dlReader); - if( DL_DEFAULT==DL_DOCIDS || plrAtEnd(&plReader) ){ - sqlite3_snprintf(sizeof(buf), buf, "[%lld] ", dlrDocid(&dlReader)); - dataBufferAppend(&dump, buf, strlen(buf)); - }else{ - int iColumn = plrColumn(&plReader); - - sqlite3_snprintf(sizeof(buf), buf, "[%lld %d[", - dlrDocid(&dlReader), iColumn); - dataBufferAppend(&dump, buf, strlen(buf)); - - for( ; !plrAtEnd(&plReader); plrStep(&plReader) ){ - if( plrColumn(&plReader)!=iColumn ){ - iColumn = plrColumn(&plReader); - sqlite3_snprintf(sizeof(buf), buf, "] %d[", iColumn); - assert( dump.nData>0 ); - dump.nData--; /* Overwrite trailing space. */ - assert( dump.pData[dump.nData]==' '); - dataBufferAppend(&dump, buf, strlen(buf)); - } - if( DL_DEFAULT==DL_POSITIONS_OFFSETS ){ - sqlite3_snprintf(sizeof(buf), buf, "%d,%d,%d ", - plrPosition(&plReader), - plrStartOffset(&plReader), plrEndOffset(&plReader)); - }else if( DL_DEFAULT==DL_POSITIONS ){ - sqlite3_snprintf(sizeof(buf), buf, "%d ", plrPosition(&plReader)); - }else{ - assert( NULL=="Unhandled DL_DEFAULT value"); - } - dataBufferAppend(&dump, buf, strlen(buf)); - } - plrDestroy(&plReader); - - assert( dump.nData>0 ); - dump.nData--; /* Overwrite trailing space. */ - assert( dump.pData[dump.nData]==' '); - dataBufferAppend(&dump, "]] ", 3); - } - } - dlrDestroy(&dlReader); - - assert( dump.nData>0 ); - dump.nData--; /* Overwrite trailing space. */ - assert( dump.pData[dump.nData]==' '); - dump.pData[dump.nData] = '\0'; - assert( dump.nData>0 ); - - /* Passes ownership of dump's buffer to pContext. */ - sqlite3_result_text(pContext, dump.pData, dump.nData, sqlite3_free); - dump.pData = NULL; - dump.nData = dump.nCapacity = 0; -} - -/* Implements dump_doclist() for use in inspecting the fts2 index from -** tests. TEXT result containing a string representation of the -** doclist for the indicated term. dump_doclist(t, term, level, idx) -** dumps the doclist for term from the segment specified by level, idx -** (in %_segdir), while dump_doclist(t, term) dumps the logical -** doclist for the term across all segments. The per-segment doclist -** can contain deletions, while the full-index doclist will not -** (deletions are omitted). -** -** Result formats differ with the setting of DL_DEFAULTS. Examples: -** -** DL_DOCIDS: [1] [3] [7] -** DL_POSITIONS: [1 0[0 4] 1[17]] [3 1[5]] -** DL_POSITIONS_OFFSETS: [1 0[0,0,3 4,23,26] 1[17,102,105]] [3 1[5,20,23]] -** -** In each case the number after the outer '[' is the docid. In the -** latter two cases, the number before the inner '[' is the column -** associated with the values within. For DL_POSITIONS the numbers -** within are the positions, for DL_POSITIONS_OFFSETS they are the -** position, the start offset, and the end offset. -*/ -static void dumpDoclistFunc( - sqlite3_context *pContext, - int argc, sqlite3_value **argv -){ - fulltext_cursor *pCursor; - if( argc!=2 && argc!=4 ){ - generateError(pContext, "dump_doclist", "incorrect arguments"); - }else if( sqlite3_value_type(argv[0])!=SQLITE_BLOB || - sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){ - generateError(pContext, "dump_doclist", "illegal first argument"); - }else if( sqlite3_value_text(argv[1])==NULL || - sqlite3_value_text(argv[1])[0]=='\0' ){ - generateError(pContext, "dump_doclist", "empty second argument"); - }else{ - const char *pTerm = (const char *)sqlite3_value_text(argv[1]); - const int nTerm = strlen(pTerm); - fulltext_vtab *v; - int rc; - DataBuffer doclist; - - memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor)); - v = cursor_vtab(pCursor); - - dataBufferInit(&doclist, 0); - - /* termSelect() yields the same logical doclist that queries are - ** run against. - */ - if( argc==2 ){ - rc = termSelect(v, v->nColumn, pTerm, nTerm, 0, DL_DEFAULT, &doclist); - }else{ - sqlite3_stmt *s = NULL; - - /* Get our specific segment's information. */ - rc = sql_get_statement(v, SEGDIR_SELECT_SEGMENT_STMT, &s); - if( rc==SQLITE_OK ){ - rc = sqlite3_bind_int(s, 1, sqlite3_value_int(argv[2])); - if( rc==SQLITE_OK ){ - rc = sqlite3_bind_int(s, 2, sqlite3_value_int(argv[3])); - } - } - - if( rc==SQLITE_OK ){ - rc = sqlite3_step(s); - - if( rc==SQLITE_DONE ){ - dataBufferDestroy(&doclist); - generateError(pContext, "dump_doclist", "segment not found"); - return; - } - - /* Found a segment, load it into doclist. */ - if( rc==SQLITE_ROW ){ - const sqlite_int64 iLeavesEnd = sqlite3_column_int64(s, 1); - const char *pData = sqlite3_column_blob(s, 2); - const int nData = sqlite3_column_bytes(s, 2); - - /* loadSegment() is used by termSelect() to load each - ** segment's data. - */ - rc = loadSegment(v, pData, nData, iLeavesEnd, pTerm, nTerm, 0, - &doclist); - if( rc==SQLITE_OK ){ - rc = sqlite3_step(s); - - /* Should not have more than one matching segment. */ - if( rc!=SQLITE_DONE ){ - sqlite3_reset(s); - dataBufferDestroy(&doclist); - generateError(pContext, "dump_doclist", "invalid segdir"); - return; - } - rc = SQLITE_OK; - } - } - } - - sqlite3_reset(s); - } - - if( rc==SQLITE_OK ){ - if( doclist.nData>0 ){ - createDoclistResult(pContext, doclist.pData, doclist.nData); - }else{ - /* TODO(shess): This can happen if the term is not present, or - ** if all instances of the term have been deleted and this is - ** an all-index dump. It may be interesting to distinguish - ** these cases. - */ - sqlite3_result_text(pContext, "", 0, SQLITE_STATIC); - } - }else if( rc==SQLITE_NOMEM ){ - /* Handle out-of-memory cases specially because if they are - ** generated in fts2 code they may not be reflected in the db - ** handle. - */ - /* TODO(shess): Handle this more comprehensively. - ** sqlite3ErrStr() has what I need, but is internal. - */ - generateError(pContext, "dump_doclist", "out of memory"); - }else{ - generateError(pContext, "dump_doclist", NULL); - } - - dataBufferDestroy(&doclist); - } -} -#endif - -/* -** This routine implements the xFindFunction method for the FTS2 -** virtual table. -*/ -static int fulltextFindFunction( - sqlite3_vtab *pVtab, - int nArg, - const char *zName, - void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), - void **ppArg -){ - if( strcmp(zName,"snippet")==0 ){ - *pxFunc = snippetFunc; - return 1; - }else if( strcmp(zName,"offsets")==0 ){ - *pxFunc = snippetOffsetsFunc; - return 1; - }else if( strcmp(zName,"optimize")==0 ){ - *pxFunc = optimizeFunc; - return 1; -#ifdef SQLITE_TEST - /* NOTE(shess): These functions are present only for testing - ** purposes. No particular effort is made to optimize their - ** execution or how they build their results. - */ - }else if( strcmp(zName,"dump_terms")==0 ){ - /* fprintf(stderr, "Found dump_terms\n"); */ - *pxFunc = dumpTermsFunc; - return 1; - }else if( strcmp(zName,"dump_doclist")==0 ){ - /* fprintf(stderr, "Found dump_doclist\n"); */ - *pxFunc = dumpDoclistFunc; - return 1; -#endif - } - return 0; -} - -/* -** Rename an fts2 table. -*/ -static int fulltextRename( - sqlite3_vtab *pVtab, - const char *zName -){ - fulltext_vtab *p = (fulltext_vtab *)pVtab; - int rc = SQLITE_NOMEM; - char *zSql = sqlite3_mprintf( - "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';" - "ALTER TABLE %Q.'%q_segments' RENAME TO '%q_segments';" - "ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';" - , p->zDb, p->zName, zName - , p->zDb, p->zName, zName - , p->zDb, p->zName, zName - ); - if( zSql ){ - rc = sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_free(zSql); - } - return rc; -} - -static const sqlite3_module fts2Module = { - /* iVersion */ 0, - /* xCreate */ fulltextCreate, - /* xConnect */ fulltextConnect, - /* xBestIndex */ fulltextBestIndex, - /* xDisconnect */ fulltextDisconnect, - /* xDestroy */ fulltextDestroy, - /* xOpen */ fulltextOpen, - /* xClose */ fulltextClose, - /* xFilter */ fulltextFilter, - /* xNext */ fulltextNext, - /* xEof */ fulltextEof, - /* xColumn */ fulltextColumn, - /* xRowid */ fulltextRowid, - /* xUpdate */ fulltextUpdate, - /* xBegin */ fulltextBegin, - /* xSync */ fulltextSync, - /* xCommit */ fulltextCommit, - /* xRollback */ fulltextRollback, - /* xFindFunction */ fulltextFindFunction, - /* xRename */ fulltextRename, -}; - -static void hashDestroy(void *p){ - fts2Hash *pHash = (fts2Hash *)p; - sqlite3Fts2HashClear(pHash); - sqlite3_free(pHash); -} - -/* -** The fts2 built-in tokenizers - "simple" and "porter" - are implemented -** in files fts2_tokenizer1.c and fts2_porter.c respectively. The following -** two forward declarations are for functions declared in these files -** used to retrieve the respective implementations. -** -** Calling sqlite3Fts2SimpleTokenizerModule() sets the value pointed -** to by the argument to point a the "simple" tokenizer implementation. -** Function ...PorterTokenizerModule() sets *pModule to point to the -** porter tokenizer/stemmer implementation. -*/ -void sqlite3Fts2SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); -void sqlite3Fts2PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule); -void sqlite3Fts2IcuTokenizerModule(sqlite3_tokenizer_module const**ppModule); - -int sqlite3Fts2InitHashTable(sqlite3 *, fts2Hash *, const char *); - -/* -** Initialize the fts2 extension. If this extension is built as part -** of the sqlite library, then this function is called directly by -** SQLite. If fts2 is built as a dynamically loadable extension, this -** function is called by the sqlite3_extension_init() entry point. -*/ -int sqlite3Fts2Init(sqlite3 *db){ - int rc = SQLITE_OK; - fts2Hash *pHash = 0; - const sqlite3_tokenizer_module *pSimple = 0; - const sqlite3_tokenizer_module *pPorter = 0; - const sqlite3_tokenizer_module *pIcu = 0; - - sqlite3Fts2SimpleTokenizerModule(&pSimple); - sqlite3Fts2PorterTokenizerModule(&pPorter); -#ifdef SQLITE_ENABLE_ICU - sqlite3Fts2IcuTokenizerModule(&pIcu); -#endif - - /* Allocate and initialize the hash-table used to store tokenizers. */ - pHash = sqlite3_malloc(sizeof(fts2Hash)); - if( !pHash ){ - rc = SQLITE_NOMEM; - }else{ - sqlite3Fts2HashInit(pHash, FTS2_HASH_STRING, 1); - } - - /* Load the built-in tokenizers into the hash table */ - if( rc==SQLITE_OK ){ - if( sqlite3Fts2HashInsert(pHash, "simple", 7, (void *)pSimple) - || sqlite3Fts2HashInsert(pHash, "porter", 7, (void *)pPorter) - || (pIcu && sqlite3Fts2HashInsert(pHash, "icu", 4, (void *)pIcu)) - ){ - rc = SQLITE_NOMEM; - } - } - - /* Create the virtual table wrapper around the hash-table and overload - ** the two scalar functions. If this is successful, register the - ** module with sqlite. - */ - if( SQLITE_OK==rc - && SQLITE_OK==(rc = sqlite3Fts2InitHashTable(db, pHash, "fts2_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, "optimize", -1)) -#ifdef SQLITE_TEST - && SQLITE_OK==(rc = sqlite3_overload_function(db, "dump_terms", -1)) - && SQLITE_OK==(rc = sqlite3_overload_function(db, "dump_doclist", -1)) -#endif - ){ - return sqlite3_create_module_v2( - db, "fts2", &fts2Module, (void *)pHash, hashDestroy - ); - } - - /* An error has occurred. Delete the hash table and return the error code. */ - assert( rc!=SQLITE_OK ); - if( pHash ){ - sqlite3Fts2HashClear(pHash); - sqlite3_free(pHash); - } - return rc; -} - -#if !SQLITE_CORE -#ifdef _WIN32 -__declspec(dllexport) -#endif -int sqlite3_fts2_init( - sqlite3 *db, - char **pzErrMsg, - const sqlite3_api_routines *pApi -){ - SQLITE_EXTENSION_INIT2(pApi) - return sqlite3Fts2Init(db); -} -#endif - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) */ diff --git a/ext/fts2/fts2.h b/ext/fts2/fts2.h deleted file mode 100644 index 4da4c3877..000000000 --- a/ext/fts2/fts2.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -** 2006 Oct 10 -** -** 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 header file is used by programs that want to link against the -** FTS2 library. All it does is declare the sqlite3Fts2Init() interface. -*/ -#include "sqlite3.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -int sqlite3Fts2Init(sqlite3 *db); - -#ifdef __cplusplus -} /* extern "C" */ -#endif /* __cplusplus */ diff --git a/ext/fts2/fts2_hash.c b/ext/fts2/fts2_hash.c deleted file mode 100644 index 3596dcf0b..000000000 --- a/ext/fts2/fts2_hash.c +++ /dev/null @@ -1,376 +0,0 @@ -/* -** 2001 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 is the implementation of generic hash-tables used in SQLite. -** We've modified it slightly to serve as a standalone hash table -** implementation for the full-text indexing module. -*/ - -/* -** The code in this file is only compiled if: -** -** * The FTS2 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS2 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS2 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) - -#include -#include -#include - -#include "sqlite3.h" -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT3 -#include "fts2_hash.h" - -/* -** Malloc and Free functions -*/ -static void *fts2HashMalloc(int n){ - void *p = sqlite3_malloc(n); - if( p ){ - memset(p, 0, n); - } - return p; -} -static void fts2HashFree(void *p){ - sqlite3_free(p); -} - -/* Turn bulk memory into a hash table object by initializing the -** fields of the Hash structure. -** -** "pNew" is a pointer to the hash table that is to be initialized. -** keyClass is one of the constants -** FTS2_HASH_BINARY or FTS2_HASH_STRING. The value of keyClass -** determines what kind of key the hash table will use. "copyKey" is -** true if the hash table should make its own private copy of keys and -** false if it should just use the supplied pointer. -*/ -void sqlite3Fts2HashInit(fts2Hash *pNew, int keyClass, int copyKey){ - assert( pNew!=0 ); - assert( keyClass>=FTS2_HASH_STRING && keyClass<=FTS2_HASH_BINARY ); - pNew->keyClass = keyClass; - pNew->copyKey = copyKey; - pNew->first = 0; - pNew->count = 0; - pNew->htsize = 0; - pNew->ht = 0; -} - -/* Remove all entries from a hash table. Reclaim all memory. -** Call this routine to delete a hash table or to reset a hash table -** to the empty state. -*/ -void sqlite3Fts2HashClear(fts2Hash *pH){ - fts2HashElem *elem; /* For looping over all elements of the table */ - - assert( pH!=0 ); - elem = pH->first; - pH->first = 0; - fts2HashFree(pH->ht); - pH->ht = 0; - pH->htsize = 0; - while( elem ){ - fts2HashElem *next_elem = elem->next; - if( pH->copyKey && elem->pKey ){ - fts2HashFree(elem->pKey); - } - fts2HashFree(elem); - elem = next_elem; - } - pH->count = 0; -} - -/* -** Hash and comparison functions when the mode is FTS2_HASH_STRING -*/ -static int strHash(const void *pKey, int nKey){ - const char *z = (const char *)pKey; - int h = 0; - if( nKey<=0 ) nKey = (int) strlen(z); - while( nKey > 0 ){ - h = (h<<3) ^ h ^ *z++; - nKey--; - } - return h & 0x7fffffff; -} -static int strCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - if( n1!=n2 ) return 1; - return strncmp((const char*)pKey1,(const char*)pKey2,n1); -} - -/* -** Hash and comparison functions when the mode is FTS2_HASH_BINARY -*/ -static int binHash(const void *pKey, int nKey){ - int h = 0; - const char *z = (const char *)pKey; - while( nKey-- > 0 ){ - h = (h<<3) ^ h ^ *(z++); - } - return h & 0x7fffffff; -} -static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){ - if( n1!=n2 ) return 1; - return memcmp(pKey1,pKey2,n1); -} - -/* -** Return a pointer to the appropriate hash function given the key class. -** -** The C syntax in this function definition may be unfamilar to some -** programmers, so we provide the following additional explanation: -** -** The name of the function is "hashFunction". The function takes a -** single parameter "keyClass". The return value of hashFunction() -** is a pointer to another function. Specifically, the return value -** of hashFunction() is a pointer to a function that takes two parameters -** with types "const void*" and "int" and returns an "int". -*/ -static int (*hashFunction(int keyClass))(const void*,int){ - if( keyClass==FTS2_HASH_STRING ){ - return &strHash; - }else{ - assert( keyClass==FTS2_HASH_BINARY ); - return &binHash; - } -} - -/* -** Return a pointer to the appropriate hash function given the key class. -** -** For help in interpreted the obscure C code in the function definition, -** see the header comment on the previous function. -*/ -static int (*compareFunction(int keyClass))(const void*,int,const void*,int){ - if( keyClass==FTS2_HASH_STRING ){ - return &strCompare; - }else{ - assert( keyClass==FTS2_HASH_BINARY ); - return &binCompare; - } -} - -/* Link an element into the hash table -*/ -static void insertElement( - fts2Hash *pH, /* The complete hash table */ - struct _fts2ht *pEntry, /* The entry into which pNew is inserted */ - fts2HashElem *pNew /* The element to be inserted */ -){ - fts2HashElem *pHead; /* First element already in pEntry */ - pHead = pEntry->chain; - if( pHead ){ - pNew->next = pHead; - pNew->prev = pHead->prev; - if( pHead->prev ){ pHead->prev->next = pNew; } - else { pH->first = pNew; } - pHead->prev = pNew; - }else{ - pNew->next = pH->first; - if( pH->first ){ pH->first->prev = pNew; } - pNew->prev = 0; - pH->first = pNew; - } - pEntry->count++; - pEntry->chain = pNew; -} - - -/* Resize the hash table so that it cantains "new_size" buckets. -** "new_size" must be a power of 2. The hash table might fail -** to resize if sqliteMalloc() fails. -*/ -static void rehash(fts2Hash *pH, int new_size){ - struct _fts2ht *new_ht; /* The new hash table */ - fts2HashElem *elem, *next_elem; /* For looping over existing elements */ - int (*xHash)(const void*,int); /* The hash function */ - - assert( (new_size & (new_size-1))==0 ); - new_ht = (struct _fts2ht *)fts2HashMalloc( new_size*sizeof(struct _fts2ht) ); - if( new_ht==0 ) return; - fts2HashFree(pH->ht); - pH->ht = new_ht; - pH->htsize = new_size; - xHash = hashFunction(pH->keyClass); - for(elem=pH->first, pH->first=0; elem; elem = next_elem){ - int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); - next_elem = elem->next; - insertElement(pH, &new_ht[h], elem); - } -} - -/* This function (for internal use only) locates an element in an -** hash table that matches the given key. The hash for this key has -** already been computed and is passed as the 4th parameter. -*/ -static fts2HashElem *findElementGivenHash( - const fts2Hash *pH, /* The pH to be searched */ - const void *pKey, /* The key we are searching for */ - int nKey, - int h /* The hash for this key. */ -){ - fts2HashElem *elem; /* Used to loop thru the element list */ - int count; /* Number of elements left to test */ - int (*xCompare)(const void*,int,const void*,int); /* comparison function */ - - if( pH->ht ){ - struct _fts2ht *pEntry = &pH->ht[h]; - elem = pEntry->chain; - count = pEntry->count; - xCompare = compareFunction(pH->keyClass); - while( count-- && elem ){ - if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ - return elem; - } - elem = elem->next; - } - } - return 0; -} - -/* Remove a single entry from the hash table given a pointer to that -** element and a hash on the element's key. -*/ -static void removeElementGivenHash( - fts2Hash *pH, /* The pH containing "elem" */ - fts2HashElem* elem, /* The element to be removed from the pH */ - int h /* Hash value for the element */ -){ - struct _fts2ht *pEntry; - if( elem->prev ){ - elem->prev->next = elem->next; - }else{ - pH->first = elem->next; - } - if( elem->next ){ - elem->next->prev = elem->prev; - } - pEntry = &pH->ht[h]; - if( pEntry->chain==elem ){ - pEntry->chain = elem->next; - } - pEntry->count--; - if( pEntry->count<=0 ){ - pEntry->chain = 0; - } - if( pH->copyKey && elem->pKey ){ - fts2HashFree(elem->pKey); - } - fts2HashFree( elem ); - pH->count--; - if( pH->count<=0 ){ - assert( pH->first==0 ); - assert( pH->count==0 ); - fts2HashClear(pH); - } -} - -/* Attempt to locate an element of the hash table pH with a key -** that matches pKey,nKey. Return the data for this element if it is -** found, or NULL if there is no match. -*/ -void *sqlite3Fts2HashFind(const fts2Hash *pH, const void *pKey, int nKey){ - int h; /* A hash on key */ - fts2HashElem *elem; /* The element that matches key */ - int (*xHash)(const void*,int); /* The hash function */ - - if( pH==0 || pH->ht==0 ) return 0; - xHash = hashFunction(pH->keyClass); - assert( xHash!=0 ); - h = (*xHash)(pKey,nKey); - assert( (pH->htsize & (pH->htsize-1))==0 ); - elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1)); - return elem ? elem->data : 0; -} - -/* Insert an element into the hash table pH. The key is pKey,nKey -** and the data is "data". -** -** If no element exists with a matching key, then a new -** element is created. A copy of the key is made if the copyKey -** flag is set. NULL is returned. -** -** If another element already exists with the same key, then the -** new data replaces the old data and the old data is returned. -** The key is not copied in this instance. If a malloc fails, then -** the new data is returned and the hash table is unchanged. -** -** If the "data" parameter to this function is NULL, then the -** element corresponding to "key" is removed from the hash table. -*/ -void *sqlite3Fts2HashInsert( - fts2Hash *pH, /* The hash table to insert into */ - const void *pKey, /* The key */ - int nKey, /* Number of bytes in the key */ - void *data /* The data */ -){ - int hraw; /* Raw hash value of the key */ - int h; /* the hash of the key modulo hash table size */ - fts2HashElem *elem; /* Used to loop thru the element list */ - fts2HashElem *new_elem; /* New element added to the pH */ - int (*xHash)(const void*,int); /* The hash function */ - - assert( pH!=0 ); - xHash = hashFunction(pH->keyClass); - assert( xHash!=0 ); - hraw = (*xHash)(pKey, nKey); - assert( (pH->htsize & (pH->htsize-1))==0 ); - h = hraw & (pH->htsize-1); - elem = findElementGivenHash(pH,pKey,nKey,h); - if( elem ){ - void *old_data = elem->data; - if( data==0 ){ - removeElementGivenHash(pH,elem,h); - }else{ - elem->data = data; - } - return old_data; - } - if( data==0 ) return 0; - new_elem = (fts2HashElem*)fts2HashMalloc( sizeof(fts2HashElem) ); - if( new_elem==0 ) return data; - if( pH->copyKey && pKey!=0 ){ - new_elem->pKey = fts2HashMalloc( nKey ); - if( new_elem->pKey==0 ){ - fts2HashFree(new_elem); - return data; - } - memcpy((void*)new_elem->pKey, pKey, nKey); - }else{ - new_elem->pKey = (void*)pKey; - } - new_elem->nKey = nKey; - pH->count++; - if( pH->htsize==0 ){ - rehash(pH,8); - if( pH->htsize==0 ){ - pH->count = 0; - fts2HashFree(new_elem); - return data; - } - } - if( pH->count > pH->htsize ){ - rehash(pH,pH->htsize*2); - } - assert( pH->htsize>0 ); - assert( (pH->htsize & (pH->htsize-1))==0 ); - h = hraw & (pH->htsize-1); - insertElement(pH, &pH->ht[h], new_elem); - new_elem->data = data; - return 0; -} - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) */ diff --git a/ext/fts2/fts2_hash.h b/ext/fts2/fts2_hash.h deleted file mode 100644 index 02936f18b..000000000 --- a/ext/fts2/fts2_hash.h +++ /dev/null @@ -1,110 +0,0 @@ -/* -** 2001 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 is the header file for the generic hash-table implementation -** used in SQLite. We've modified it slightly to serve as a standalone -** hash table implementation for the full-text indexing module. -** -*/ -#ifndef _FTS2_HASH_H_ -#define _FTS2_HASH_H_ - -/* Forward declarations of structures. */ -typedef struct fts2Hash fts2Hash; -typedef struct fts2HashElem fts2HashElem; - -/* A complete hash table is an instance of the following structure. -** The internals of this structure are intended to be opaque -- client -** code should not attempt to access or modify the fields of this structure -** directly. Change this structure only by using the routines below. -** However, many of the "procedures" and "functions" for modifying and -** accessing this structure are really macros, so we can't really make -** this structure opaque. -*/ -struct fts2Hash { - char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */ - char copyKey; /* True if copy of key made on insert */ - int count; /* Number of entries in this table */ - fts2HashElem *first; /* The first element of the array */ - int htsize; /* Number of buckets in the hash table */ - struct _fts2ht { /* the hash table */ - int count; /* Number of entries with this hash */ - fts2HashElem *chain; /* Pointer to first entry with this hash */ - } *ht; -}; - -/* Each element in the hash table is an instance of the following -** structure. All elements are stored on a single doubly-linked list. -** -** Again, this structure is intended to be opaque, but it can't really -** be opaque because it is used by macros. -*/ -struct fts2HashElem { - fts2HashElem *next, *prev; /* Next and previous elements in the table */ - void *data; /* Data associated with this element */ - void *pKey; int nKey; /* Key associated with this element */ -}; - -/* -** There are 2 different modes of operation for a hash table: -** -** FTS2_HASH_STRING pKey points to a string that is nKey bytes long -** (including the null-terminator, if any). Case -** is respected in comparisons. -** -** FTS2_HASH_BINARY pKey points to binary data nKey bytes long. -** memcmp() is used to compare keys. -** -** A copy of the key is made if the copyKey parameter to fts2HashInit is 1. -*/ -#define FTS2_HASH_STRING 1 -#define FTS2_HASH_BINARY 2 - -/* -** Access routines. To delete, insert a NULL pointer. -*/ -void sqlite3Fts2HashInit(fts2Hash*, int keytype, int copyKey); -void *sqlite3Fts2HashInsert(fts2Hash*, const void *pKey, int nKey, void *pData); -void *sqlite3Fts2HashFind(const fts2Hash*, const void *pKey, int nKey); -void sqlite3Fts2HashClear(fts2Hash*); - -/* -** Shorthand for the functions above -*/ -#define fts2HashInit sqlite3Fts2HashInit -#define fts2HashInsert sqlite3Fts2HashInsert -#define fts2HashFind sqlite3Fts2HashFind -#define fts2HashClear sqlite3Fts2HashClear - -/* -** Macros for looping over all elements of a hash table. The idiom is -** like this: -** -** fts2Hash h; -** fts2HashElem *p; -** ... -** for(p=fts2HashFirst(&h); p; p=fts2HashNext(p)){ -** SomeStructure *pData = fts2HashData(p); -** // do something with pData -** } -*/ -#define fts2HashFirst(H) ((H)->first) -#define fts2HashNext(E) ((E)->next) -#define fts2HashData(E) ((E)->data) -#define fts2HashKey(E) ((E)->pKey) -#define fts2HashKeysize(E) ((E)->nKey) - -/* -** Number of entries in a hash table -*/ -#define fts2HashCount(H) ((H)->count) - -#endif /* _FTS2_HASH_H_ */ diff --git a/ext/fts2/fts2_icu.c b/ext/fts2/fts2_icu.c deleted file mode 100644 index 2670301f5..000000000 --- a/ext/fts2/fts2_icu.c +++ /dev/null @@ -1,260 +0,0 @@ -/* -** 2007 June 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 implements a tokenizer for fts2 based on the ICU library. -** -** $Id: fts2_icu.c,v 1.3 2008/12/18 05:30:26 danielk1977 Exp $ -*/ - -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) -#ifdef SQLITE_ENABLE_ICU - -#include -#include -#include "fts2_tokenizer.h" - -#include -#include -#include -#include - -typedef struct IcuTokenizer IcuTokenizer; -typedef struct IcuCursor IcuCursor; - -struct IcuTokenizer { - sqlite3_tokenizer base; - char *zLocale; -}; - -struct IcuCursor { - sqlite3_tokenizer_cursor base; - - UBreakIterator *pIter; /* ICU break-iterator object */ - int nChar; /* Number of UChar elements in pInput */ - UChar *aChar; /* Copy of input using utf-16 encoding */ - int *aOffset; /* Offsets of each character in utf-8 input */ - - int nBuffer; - char *zBuffer; - - int iToken; -}; - -/* -** Create a new tokenizer instance. -*/ -static int icuCreate( - int argc, /* Number of entries in argv[] */ - const char * const *argv, /* Tokenizer creation arguments */ - sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ -){ - IcuTokenizer *p; - int n = 0; - - if( argc>0 ){ - n = strlen(argv[0])+1; - } - p = (IcuTokenizer *)sqlite3_malloc(sizeof(IcuTokenizer)+n); - if( !p ){ - return SQLITE_NOMEM; - } - memset(p, 0, sizeof(IcuTokenizer)); - - if( n ){ - p->zLocale = (char *)&p[1]; - memcpy(p->zLocale, argv[0], n); - } - - *ppTokenizer = (sqlite3_tokenizer *)p; - - return SQLITE_OK; -} - -/* -** Destroy a tokenizer -*/ -static int icuDestroy(sqlite3_tokenizer *pTokenizer){ - IcuTokenizer *p = (IcuTokenizer *)pTokenizer; - sqlite3_free(p); - return SQLITE_OK; -} - -/* -** Prepare to begin tokenizing a particular string. The input -** string to be tokenized is pInput[0..nBytes-1]. A cursor -** used to incrementally tokenize this string is returned in -** *ppCursor. -*/ -static int icuOpen( - sqlite3_tokenizer *pTokenizer, /* The tokenizer */ - const char *zInput, /* Input string */ - int nInput, /* Length of zInput in bytes */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ -){ - IcuTokenizer *p = (IcuTokenizer *)pTokenizer; - IcuCursor *pCsr; - - const int32_t opt = U_FOLD_CASE_DEFAULT; - UErrorCode status = U_ZERO_ERROR; - int nChar; - - UChar32 c; - int iInput = 0; - int iOut = 0; - - *ppCursor = 0; - - if( nInput<0 ){ - nInput = strlen(zInput); - } - nChar = nInput+1; - pCsr = (IcuCursor *)sqlite3_malloc( - sizeof(IcuCursor) + /* IcuCursor */ - ((nChar+3)&~3) * sizeof(UChar) + /* IcuCursor.aChar[] */ - (nChar+1) * sizeof(int) /* IcuCursor.aOffset[] */ - ); - if( !pCsr ){ - return SQLITE_NOMEM; - } - memset(pCsr, 0, sizeof(IcuCursor)); - pCsr->aChar = (UChar *)&pCsr[1]; - pCsr->aOffset = (int *)&pCsr->aChar[(nChar+3)&~3]; - - pCsr->aOffset[iOut] = iInput; - U8_NEXT(zInput, iInput, nInput, c); - while( c>0 ){ - int isError = 0; - c = u_foldCase(c, opt); - U16_APPEND(pCsr->aChar, iOut, nChar, c, isError); - if( isError ){ - sqlite3_free(pCsr); - return SQLITE_ERROR; - } - pCsr->aOffset[iOut] = iInput; - - if( iInputpIter = ubrk_open(UBRK_WORD, p->zLocale, pCsr->aChar, iOut, &status); - if( !U_SUCCESS(status) ){ - sqlite3_free(pCsr); - return SQLITE_ERROR; - } - pCsr->nChar = iOut; - - ubrk_first(pCsr->pIter); - *ppCursor = (sqlite3_tokenizer_cursor *)pCsr; - return SQLITE_OK; -} - -/* -** Close a tokenization cursor previously opened by a call to icuOpen(). -*/ -static int icuClose(sqlite3_tokenizer_cursor *pCursor){ - IcuCursor *pCsr = (IcuCursor *)pCursor; - ubrk_close(pCsr->pIter); - sqlite3_free(pCsr->zBuffer); - sqlite3_free(pCsr); - return SQLITE_OK; -} - -/* -** Extract the next token from a tokenization cursor. -*/ -static int icuNext( - sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */ - const char **ppToken, /* OUT: *ppToken is the token text */ - int *pnBytes, /* OUT: Number of bytes in token */ - int *piStartOffset, /* OUT: Starting offset of token */ - int *piEndOffset, /* OUT: Ending offset of token */ - int *piPosition /* OUT: Position integer of token */ -){ - IcuCursor *pCsr = (IcuCursor *)pCursor; - - int iStart = 0; - int iEnd = 0; - int nByte = 0; - - while( iStart==iEnd ){ - UChar32 c; - - iStart = ubrk_current(pCsr->pIter); - iEnd = ubrk_next(pCsr->pIter); - if( iEnd==UBRK_DONE ){ - return SQLITE_DONE; - } - - while( iStartaChar, iWhite, pCsr->nChar, c); - if( u_isspace(c) ){ - iStart = iWhite; - }else{ - break; - } - } - assert(iStart<=iEnd); - } - - do { - UErrorCode status = U_ZERO_ERROR; - if( nByte ){ - char *zNew = sqlite3_realloc(pCsr->zBuffer, nByte); - if( !zNew ){ - return SQLITE_NOMEM; - } - pCsr->zBuffer = zNew; - pCsr->nBuffer = nByte; - } - - u_strToUTF8( - pCsr->zBuffer, pCsr->nBuffer, &nByte, /* Output vars */ - &pCsr->aChar[iStart], iEnd-iStart, /* Input vars */ - &status /* Output success/failure */ - ); - } while( nByte>pCsr->nBuffer ); - - *ppToken = pCsr->zBuffer; - *pnBytes = nByte; - *piStartOffset = pCsr->aOffset[iStart]; - *piEndOffset = pCsr->aOffset[iEnd]; - *piPosition = pCsr->iToken++; - - return SQLITE_OK; -} - -/* -** The set of routines that implement the simple tokenizer -*/ -static const sqlite3_tokenizer_module icuTokenizerModule = { - 0, /* iVersion */ - icuCreate, /* xCreate */ - icuDestroy, /* xCreate */ - icuOpen, /* xOpen */ - icuClose, /* xClose */ - icuNext, /* xNext */ -}; - -/* -** Set *ppModule to point at the implementation of the ICU tokenizer. -*/ -void sqlite3Fts2IcuTokenizerModule( - sqlite3_tokenizer_module const**ppModule -){ - *ppModule = &icuTokenizerModule; -} - -#endif /* defined(SQLITE_ENABLE_ICU) */ -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) */ diff --git a/ext/fts2/fts2_porter.c b/ext/fts2/fts2_porter.c deleted file mode 100644 index 881baf710..000000000 --- a/ext/fts2/fts2_porter.c +++ /dev/null @@ -1,644 +0,0 @@ -/* -** 2006 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. -** -************************************************************************* -** Implementation of the full-text-search tokenizer that implements -** a Porter stemmer. -*/ - -/* -** The code in this file is only compiled if: -** -** * The FTS2 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS2 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS2 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) - - -#include -#include -#include -#include - -#include "sqlite3.h" -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT3 -#include "fts2_tokenizer.h" - -/* -** Class derived from sqlite3_tokenizer -*/ -typedef struct porter_tokenizer { - sqlite3_tokenizer base; /* Base class */ -} porter_tokenizer; - -/* -** Class derived from sqlit3_tokenizer_cursor -*/ -typedef struct porter_tokenizer_cursor { - sqlite3_tokenizer_cursor base; - const char *zInput; /* input we are tokenizing */ - int nInput; /* size of the input */ - int iOffset; /* current position in zInput */ - int iToken; /* index of next token to be returned */ - char *zToken; /* storage for current token */ - int nAllocated; /* space allocated to zToken buffer */ -} porter_tokenizer_cursor; - - -/* Forward declaration */ -static const sqlite3_tokenizer_module porterTokenizerModule; - - -/* -** Create a new tokenizer instance. -*/ -static int porterCreate( - int argc, const char * const *argv, - sqlite3_tokenizer **ppTokenizer -){ - porter_tokenizer *t; - t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t)); - if( t==NULL ) return SQLITE_NOMEM; - memset(t, 0, sizeof(*t)); - *ppTokenizer = &t->base; - return SQLITE_OK; -} - -/* -** Destroy a tokenizer -*/ -static int porterDestroy(sqlite3_tokenizer *pTokenizer){ - sqlite3_free(pTokenizer); - return SQLITE_OK; -} - -/* -** Prepare to begin tokenizing a particular string. The input -** string to be tokenized is zInput[0..nInput-1]. A cursor -** used to incrementally tokenize this string is returned in -** *ppCursor. -*/ -static int porterOpen( - sqlite3_tokenizer *pTokenizer, /* The tokenizer */ - const char *zInput, int nInput, /* String to be tokenized */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ -){ - porter_tokenizer_cursor *c; - - c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); - if( c==NULL ) return SQLITE_NOMEM; - - c->zInput = zInput; - if( zInput==0 ){ - c->nInput = 0; - }else if( nInput<0 ){ - c->nInput = (int)strlen(zInput); - }else{ - c->nInput = nInput; - } - c->iOffset = 0; /* start tokenizing at the beginning */ - c->iToken = 0; - c->zToken = NULL; /* no space allocated, yet. */ - c->nAllocated = 0; - - *ppCursor = &c->base; - return SQLITE_OK; -} - -/* -** Close a tokenization cursor previously opened by a call to -** porterOpen() above. -*/ -static int porterClose(sqlite3_tokenizer_cursor *pCursor){ - porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; - sqlite3_free(c->zToken); - sqlite3_free(c); - return SQLITE_OK; -} -/* -** Vowel or consonant -*/ -static const char cType[] = { - 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 2, 1 -}; - -/* -** isConsonant() and isVowel() determine if their first character in -** the string they point to is a consonant or a vowel, according -** to Porter ruls. -** -** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'. -** 'Y' is a consonant unless it follows another consonant, -** in which case it is a vowel. -** -** In these routine, the letters are in reverse order. So the 'y' rule -** is that 'y' is a consonant unless it is followed by another -** consonent. -*/ -static int isVowel(const char*); -static int isConsonant(const char *z){ - int j; - char x = *z; - if( x==0 ) return 0; - assert( x>='a' && x<='z' ); - j = cType[x-'a']; - if( j<2 ) return j; - return z[1]==0 || isVowel(z + 1); -} -static int isVowel(const char *z){ - int j; - char x = *z; - if( x==0 ) return 0; - assert( x>='a' && x<='z' ); - j = cType[x-'a']; - if( j<2 ) return 1-j; - return isConsonant(z + 1); -} - -/* -** Let any sequence of one or more vowels be represented by V and let -** C be sequence of one or more consonants. Then every word can be -** represented as: -** -** [C] (VC){m} [V] -** -** In prose: A word is an optional consonant followed by zero or -** vowel-consonant pairs followed by an optional vowel. "m" is the -** number of vowel consonant pairs. This routine computes the value -** of m for the first i bytes of a word. -** -** Return true if the m-value for z is 1 or more. In other words, -** return true if z contains at least one vowel that is followed -** by a consonant. -** -** In this routine z[] is in reverse order. So we are really looking -** for an instance of of a consonant followed by a vowel. -*/ -static int m_gt_0(const char *z){ - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - return *z!=0; -} - -/* Like mgt0 above except we are looking for a value of m which is -** exactly 1 -*/ -static int m_eq_1(const char *z){ - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - if( *z==0 ) return 0; - while( isVowel(z) ){ z++; } - if( *z==0 ) return 1; - while( isConsonant(z) ){ z++; } - return *z==0; -} - -/* Like mgt0 above except we are looking for a value of m>1 instead -** or m>0 -*/ -static int m_gt_1(const char *z){ - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - if( *z==0 ) return 0; - while( isVowel(z) ){ z++; } - if( *z==0 ) return 0; - while( isConsonant(z) ){ z++; } - return *z!=0; -} - -/* -** Return TRUE if there is a vowel anywhere within z[0..n-1] -*/ -static int hasVowel(const char *z){ - while( isConsonant(z) ){ z++; } - return *z!=0; -} - -/* -** Return TRUE if the word ends in a double consonant. -** -** The text is reversed here. So we are really looking at -** the first two characters of z[]. -*/ -static int doubleConsonant(const char *z){ - return isConsonant(z) && z[0]==z[1] && isConsonant(z+1); -} - -/* -** Return TRUE if the word ends with three letters which -** are consonant-vowel-consonent and where the final consonant -** is not 'w', 'x', or 'y'. -** -** The word is reversed here. So we are really checking the -** first three letters and the first one cannot be in [wxy]. -*/ -static int star_oh(const char *z){ - return - z[0]!=0 && isConsonant(z) && - z[0]!='w' && z[0]!='x' && z[0]!='y' && - z[1]!=0 && isVowel(z+1) && - z[2]!=0 && isConsonant(z+2); -} - -/* -** If the word ends with zFrom and xCond() is true for the stem -** of the word that preceeds the zFrom ending, then change the -** ending to zTo. -** -** The input word *pz and zFrom are both in reverse order. zTo -** is in normal order. -** -** Return TRUE if zFrom matches. Return FALSE if zFrom does not -** match. Not that TRUE is returned even if xCond() fails and -** no substitution occurs. -*/ -static int stem( - char **pz, /* The word being stemmed (Reversed) */ - const char *zFrom, /* If the ending matches this... (Reversed) */ - const char *zTo, /* ... change the ending to this (not reversed) */ - int (*xCond)(const char*) /* Condition that must be true */ -){ - char *z = *pz; - while( *zFrom && *zFrom==*z ){ z++; zFrom++; } - if( *zFrom!=0 ) return 0; - if( xCond && !xCond(z) ) return 1; - while( *zTo ){ - *(--z) = *(zTo++); - } - *pz = z; - return 1; -} - -/* -** This is the fallback stemmer used when the porter stemmer is -** inappropriate. The input word is copied into the output with -** US-ASCII case folding. If the input word is too long (more -** than 20 bytes if it contains no digits or more than 6 bytes if -** it contains digits) then word is truncated to 20 or 6 bytes -** by taking 10 or 3 bytes from the beginning and end. -*/ -static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ - int i, mx, j; - int hasDigit = 0; - for(i=0; i='A' && c<='Z' ){ - zOut[i] = c - 'A' + 'a'; - }else{ - if( c>='0' && c<='9' ) hasDigit = 1; - zOut[i] = c; - } - } - mx = hasDigit ? 3 : 10; - if( nIn>mx*2 ){ - for(j=mx, i=nIn-mx; i=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); - return; - } - for(i=0, j=sizeof(zReverse)-6; i='A' && c<='Z' ){ - zReverse[j] = c + 'a' - 'A'; - }else if( c>='a' && c<='z' ){ - zReverse[j] = c; - }else{ - /* The use of a character not in [a-zA-Z] means that we fallback - ** to the copy stemmer */ - copy_stemmer(zIn, nIn, zOut, pnOut); - return; - } - } - memset(&zReverse[sizeof(zReverse)-5], 0, 5); - z = &zReverse[j+1]; - - - /* Step 1a */ - if( z[0]=='s' ){ - if( - !stem(&z, "sess", "ss", 0) && - !stem(&z, "sei", "i", 0) && - !stem(&z, "ss", "ss", 0) - ){ - z++; - } - } - - /* Step 1b */ - z2 = z; - if( stem(&z, "dee", "ee", m_gt_0) ){ - /* Do nothing. The work was all in the test */ - }else if( - (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel)) - && z!=z2 - ){ - if( stem(&z, "ta", "ate", 0) || - stem(&z, "lb", "ble", 0) || - stem(&z, "zi", "ize", 0) ){ - /* Do nothing. The work was all in the test */ - }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){ - z++; - }else if( m_eq_1(z) && star_oh(z) ){ - *(--z) = 'e'; - } - } - - /* Step 1c */ - if( z[0]=='y' && hasVowel(z+1) ){ - z[0] = 'i'; - } - - /* Step 2 */ - switch( z[1] ){ - case 'a': - stem(&z, "lanoita", "ate", m_gt_0) || - stem(&z, "lanoit", "tion", m_gt_0); - break; - case 'c': - stem(&z, "icne", "ence", m_gt_0) || - stem(&z, "icna", "ance", m_gt_0); - break; - case 'e': - stem(&z, "rezi", "ize", m_gt_0); - break; - case 'g': - stem(&z, "igol", "log", m_gt_0); - break; - case 'l': - stem(&z, "ilb", "ble", m_gt_0) || - stem(&z, "illa", "al", m_gt_0) || - stem(&z, "iltne", "ent", m_gt_0) || - stem(&z, "ile", "e", m_gt_0) || - stem(&z, "ilsuo", "ous", m_gt_0); - break; - case 'o': - stem(&z, "noitazi", "ize", m_gt_0) || - stem(&z, "noita", "ate", m_gt_0) || - stem(&z, "rota", "ate", m_gt_0); - break; - case 's': - stem(&z, "msila", "al", m_gt_0) || - stem(&z, "ssenevi", "ive", m_gt_0) || - stem(&z, "ssenluf", "ful", m_gt_0) || - stem(&z, "ssensuo", "ous", m_gt_0); - break; - case 't': - stem(&z, "itila", "al", m_gt_0) || - stem(&z, "itivi", "ive", m_gt_0) || - stem(&z, "itilib", "ble", m_gt_0); - break; - } - - /* Step 3 */ - switch( z[0] ){ - case 'e': - stem(&z, "etaci", "ic", m_gt_0) || - stem(&z, "evita", "", m_gt_0) || - stem(&z, "ezila", "al", m_gt_0); - break; - case 'i': - stem(&z, "itici", "ic", m_gt_0); - break; - case 'l': - stem(&z, "laci", "ic", m_gt_0) || - stem(&z, "luf", "", m_gt_0); - break; - case 's': - stem(&z, "ssen", "", m_gt_0); - break; - } - - /* Step 4 */ - switch( z[1] ){ - case 'a': - if( z[0]=='l' && m_gt_1(z+2) ){ - z += 2; - } - break; - case 'c': - if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){ - z += 4; - } - break; - case 'e': - if( z[0]=='r' && m_gt_1(z+2) ){ - z += 2; - } - break; - case 'i': - if( z[0]=='c' && m_gt_1(z+2) ){ - z += 2; - } - break; - case 'l': - if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){ - z += 4; - } - break; - case 'n': - if( z[0]=='t' ){ - if( z[2]=='a' ){ - if( m_gt_1(z+3) ){ - z += 3; - } - }else if( z[2]=='e' ){ - stem(&z, "tneme", "", m_gt_1) || - stem(&z, "tnem", "", m_gt_1) || - stem(&z, "tne", "", m_gt_1); - } - } - break; - case 'o': - if( z[0]=='u' ){ - if( m_gt_1(z+2) ){ - z += 2; - } - }else if( z[3]=='s' || z[3]=='t' ){ - stem(&z, "noi", "", m_gt_1); - } - break; - case 's': - if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){ - z += 3; - } - break; - case 't': - stem(&z, "eta", "", m_gt_1) || - stem(&z, "iti", "", m_gt_1); - break; - case 'u': - if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){ - z += 3; - } - break; - case 'v': - case 'z': - if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){ - z += 3; - } - break; - } - - /* Step 5a */ - if( z[0]=='e' ){ - if( m_gt_1(z+1) ){ - z++; - }else if( m_eq_1(z+1) && !star_oh(z+1) ){ - z++; - } - } - - /* Step 5b */ - if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){ - z++; - } - - /* z[] is now the stemmed word in reverse order. Flip it back - ** around into forward order and return. - */ - *pnOut = i = strlen(z); - zOut[i] = 0; - while( *z ){ - zOut[--i] = *(z++); - } -} - -/* -** Characters that can be part of a token. We assume any character -** whose value is greater than 0x80 (any UTF character) can be -** part of a token. In other words, delimiters all must have -** values of 0x7f or lower. -*/ -static const char porterIdChar[] = { -/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ -}; -#define isDelim(C) (((ch=C)&0x80)==0 && (ch<0x30 || !porterIdChar[ch-0x30])) - -/* -** Extract the next token from a tokenization cursor. The cursor must -** have been opened by a prior call to porterOpen(). -*/ -static int porterNext( - sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */ - const char **pzToken, /* OUT: *pzToken is the token text */ - int *pnBytes, /* OUT: Number of bytes in token */ - int *piStartOffset, /* OUT: Starting offset of token */ - int *piEndOffset, /* OUT: Ending offset of token */ - int *piPosition /* OUT: Position integer of token */ -){ - porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; - const char *z = c->zInput; - - while( c->iOffsetnInput ){ - int iStartOffset, ch; - - /* Scan past delimiter characters */ - while( c->iOffsetnInput && isDelim(z[c->iOffset]) ){ - c->iOffset++; - } - - /* Count non-delimiter characters. */ - iStartOffset = c->iOffset; - while( c->iOffsetnInput && !isDelim(z[c->iOffset]) ){ - c->iOffset++; - } - - if( c->iOffset>iStartOffset ){ - int n = c->iOffset-iStartOffset; - if( n>c->nAllocated ){ - c->nAllocated = n+20; - c->zToken = sqlite3_realloc(c->zToken, c->nAllocated); - if( c->zToken==NULL ) return SQLITE_NOMEM; - } - porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes); - *pzToken = c->zToken; - *piStartOffset = iStartOffset; - *piEndOffset = c->iOffset; - *piPosition = c->iToken++; - return SQLITE_OK; - } - } - return SQLITE_DONE; -} - -/* -** The set of routines that implement the porter-stemmer tokenizer -*/ -static const sqlite3_tokenizer_module porterTokenizerModule = { - 0, - porterCreate, - porterDestroy, - porterOpen, - porterClose, - porterNext, -}; - -/* -** Allocate a new porter tokenizer. Return a pointer to the new -** tokenizer in *ppModule -*/ -void sqlite3Fts2PorterTokenizerModule( - sqlite3_tokenizer_module const**ppModule -){ - *ppModule = &porterTokenizerModule; -} - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) */ diff --git a/ext/fts2/fts2_tokenizer.c b/ext/fts2/fts2_tokenizer.c deleted file mode 100644 index dda33a72d..000000000 --- a/ext/fts2/fts2_tokenizer.c +++ /dev/null @@ -1,375 +0,0 @@ -/* -** 2007 June 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 is part of an SQLite module implementing full-text search. -** This particular file implements the generic tokenizer interface. -*/ - -/* -** The code in this file is only compiled if: -** -** * The FTS2 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS2 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS2 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) - - -#include "sqlite3.h" -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT3 - -#include "fts2_hash.h" -#include "fts2_tokenizer.h" -#include - -/* -** Implementation of the SQL scalar function for accessing the underlying -** hash table. This function may be called as follows: -** -** SELECT (); -** SELECT (, ); -** -** where is the name passed as the second argument -** to the sqlite3Fts2InitHashTable() function (e.g. 'fts2_tokenizer'). -** -** If the argument is specified, it must be a blob value -** containing a pointer to be stored as the hash data corresponding -** to the string . If is not specified, then -** the string must already exist in the has table. Otherwise, -** an error is returned. -** -** Whether or not the argument is specified, the value returned -** is a blob containing the pointer stored as the hash data corresponding -** to string (after the hash-table is updated, if applicable). -*/ -static void scalarFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - fts2Hash *pHash; - void *pPtr = 0; - const unsigned char *zName; - int nName; - - assert( argc==1 || argc==2 ); - - pHash = (fts2Hash *)sqlite3_user_data(context); - - zName = sqlite3_value_text(argv[0]); - nName = sqlite3_value_bytes(argv[0])+1; - - if( argc==2 ){ - void *pOld; - int n = sqlite3_value_bytes(argv[1]); - if( n!=sizeof(pPtr) ){ - sqlite3_result_error(context, "argument type mismatch", -1); - return; - } - pPtr = *(void **)sqlite3_value_blob(argv[1]); - pOld = sqlite3Fts2HashInsert(pHash, (void *)zName, nName, pPtr); - if( pOld==pPtr ){ - sqlite3_result_error(context, "out of memory", -1); - return; - } - }else{ - pPtr = sqlite3Fts2HashFind(pHash, zName, nName); - if( !pPtr ){ - char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName); - sqlite3_result_error(context, zErr, -1); - sqlite3_free(zErr); - return; - } - } - - sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT); -} - -#ifdef SQLITE_TEST - -#if defined(INCLUDE_SQLITE_TCL_H) -# include "sqlite_tcl.h" -#else -# include "tcl.h" -#endif -#include - -/* -** Implementation of a special SQL scalar function for testing tokenizers -** designed to be used in concert with the Tcl testing framework. This -** function must be called with two arguments: -** -** SELECT (, ); -** SELECT (, ); -** -** where is the name passed as the second argument -** to the sqlite3Fts2InitHashTable() function (e.g. 'fts2_tokenizer') -** concatenated with the string '_test' (e.g. 'fts2_tokenizer_test'). -** -** The return value is a string that may be interpreted as a Tcl -** list. For each token in the , three elements are -** added to the returned list. The first is the token position, the -** second is the token text (folded, stemmed, etc.) and the third is the -** substring of associated with the token. For example, -** using the built-in "simple" tokenizer: -** -** SELECT fts_tokenizer_test('simple', 'I don't see how'); -** -** will return the string: -** -** "{0 i I 1 dont don't 2 see see 3 how how}" -** -*/ -static void testFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - fts2Hash *pHash; - sqlite3_tokenizer_module *p; - sqlite3_tokenizer *pTokenizer = 0; - sqlite3_tokenizer_cursor *pCsr = 0; - - const char *zErr = 0; - - const char *zName; - int nName; - const char *zInput; - int nInput; - - const char *zArg = 0; - - const char *zToken; - int nToken; - int iStart; - int iEnd; - int iPos; - - Tcl_Obj *pRet; - - assert( argc==2 || argc==3 ); - - nName = sqlite3_value_bytes(argv[0]); - zName = (const char *)sqlite3_value_text(argv[0]); - nInput = sqlite3_value_bytes(argv[argc-1]); - zInput = (const char *)sqlite3_value_text(argv[argc-1]); - - if( argc==3 ){ - zArg = (const char *)sqlite3_value_text(argv[1]); - } - - pHash = (fts2Hash *)sqlite3_user_data(context); - p = (sqlite3_tokenizer_module *)sqlite3Fts2HashFind(pHash, zName, nName+1); - - if( !p ){ - char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName); - sqlite3_result_error(context, zErr, -1); - sqlite3_free(zErr); - return; - } - - pRet = Tcl_NewObj(); - Tcl_IncrRefCount(pRet); - - if( SQLITE_OK!=p->xCreate(zArg ? 1 : 0, &zArg, &pTokenizer) ){ - zErr = "error in xCreate()"; - goto finish; - } - pTokenizer->pModule = p; - if( SQLITE_OK!=p->xOpen(pTokenizer, zInput, nInput, &pCsr) ){ - zErr = "error in xOpen()"; - goto finish; - } - pCsr->pTokenizer = pTokenizer; - - while( SQLITE_OK==p->xNext(pCsr, &zToken, &nToken, &iStart, &iEnd, &iPos) ){ - Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(iPos)); - Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken)); - zToken = &zInput[iStart]; - nToken = iEnd-iStart; - Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken)); - } - - if( SQLITE_OK!=p->xClose(pCsr) ){ - zErr = "error in xClose()"; - goto finish; - } - if( SQLITE_OK!=p->xDestroy(pTokenizer) ){ - zErr = "error in xDestroy()"; - goto finish; - } - -finish: - if( zErr ){ - sqlite3_result_error(context, zErr, -1); - }else{ - sqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT); - } - Tcl_DecrRefCount(pRet); -} - -static -int registerTokenizer( - sqlite3 *db, - char *zName, - const sqlite3_tokenizer_module *p -){ - int rc; - sqlite3_stmt *pStmt; - const char zSql[] = "SELECT fts2_tokenizer(?, ?)"; - - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - if( rc!=SQLITE_OK ){ - return rc; - } - - sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); - sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC); - sqlite3_step(pStmt); - - return sqlite3_finalize(pStmt); -} - -static -int queryFts2Tokenizer( - sqlite3 *db, - char *zName, - const sqlite3_tokenizer_module **pp -){ - int rc; - sqlite3_stmt *pStmt; - const char zSql[] = "SELECT fts2_tokenizer(?)"; - - *pp = 0; - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - if( rc!=SQLITE_OK ){ - return rc; - } - - sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); - if( SQLITE_ROW==sqlite3_step(pStmt) ){ - if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ - memcpy(pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); - } - } - - return sqlite3_finalize(pStmt); -} - -void sqlite3Fts2SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); - -/* -** Implementation of the scalar function fts2_tokenizer_internal_test(). -** This function is used for testing only, it is not included in the -** build unless SQLITE_TEST is defined. -** -** The purpose of this is to test that the fts2_tokenizer() function -** can be used as designed by the C-code in the queryFts2Tokenizer and -** registerTokenizer() functions above. These two functions are repeated -** in the README.tokenizer file as an example, so it is important to -** test them. -** -** To run the tests, evaluate the fts2_tokenizer_internal_test() scalar -** function with no arguments. An assert() will fail if a problem is -** detected. i.e.: -** -** SELECT fts2_tokenizer_internal_test(); -** -*/ -static void intTestFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - int rc; - const sqlite3_tokenizer_module *p1; - const sqlite3_tokenizer_module *p2; - sqlite3 *db = (sqlite3 *)sqlite3_user_data(context); - - /* Test the query function */ - sqlite3Fts2SimpleTokenizerModule(&p1); - rc = queryFts2Tokenizer(db, "simple", &p2); - assert( rc==SQLITE_OK ); - assert( p1==p2 ); - rc = queryFts2Tokenizer(db, "nosuchtokenizer", &p2); - assert( rc==SQLITE_ERROR ); - assert( p2==0 ); - assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") ); - - /* Test the storage function */ - rc = registerTokenizer(db, "nosuchtokenizer", p1); - assert( rc==SQLITE_OK ); - rc = queryFts2Tokenizer(db, "nosuchtokenizer", &p2); - assert( rc==SQLITE_OK ); - assert( p2==p1 ); - - sqlite3_result_text(context, "ok", -1, SQLITE_STATIC); -} - -#endif - -/* -** Set up SQL objects in database db used to access the contents of -** the hash table pointed to by argument pHash. The hash table must -** been initialized to use string keys, and to take a private copy -** of the key when a value is inserted. i.e. by a call similar to: -** -** sqlite3Fts2HashInit(pHash, FTS2_HASH_STRING, 1); -** -** This function adds a scalar function (see header comment above -** scalarFunc() in this file for details) and, if ENABLE_TABLE is -** defined at compilation time, a temporary virtual table (see header -** comment above struct HashTableVtab) to the database schema. Both -** provide read/write access to the contents of *pHash. -** -** The third argument to this function, zName, is used as the name -** of both the scalar and, if created, the virtual table. -*/ -int sqlite3Fts2InitHashTable( - sqlite3 *db, - fts2Hash *pHash, - const char *zName -){ - int rc = SQLITE_OK; - void *p = (void *)pHash; - const int any = SQLITE_ANY; - char *zTest = 0; - char *zTest2 = 0; - -#ifdef SQLITE_TEST - void *pdb = (void *)db; - zTest = sqlite3_mprintf("%s_test", zName); - zTest2 = sqlite3_mprintf("%s_internal_test", zName); - if( !zTest || !zTest2 ){ - rc = SQLITE_NOMEM; - } -#endif - - if( rc!=SQLITE_OK - || (rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0)) - || (rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0)) -#ifdef SQLITE_TEST - || (rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0)) - || (rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0)) - || (rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0)) -#endif - ); - - sqlite3_free(zTest); - sqlite3_free(zTest2); - return rc; -} - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) */ diff --git a/ext/fts2/fts2_tokenizer.h b/ext/fts2/fts2_tokenizer.h deleted file mode 100644 index 8db2048d6..000000000 --- a/ext/fts2/fts2_tokenizer.h +++ /dev/null @@ -1,145 +0,0 @@ -/* -** 2006 July 10 -** -** The author disclaims copyright to this source code. -** -************************************************************************* -** Defines the interface to tokenizers used by fulltext-search. There -** are three basic components: -** -** sqlite3_tokenizer_module is a singleton defining the tokenizer -** interface functions. This is essentially the class structure for -** tokenizers. -** -** sqlite3_tokenizer is used to define a particular tokenizer, perhaps -** including customization information defined at creation time. -** -** sqlite3_tokenizer_cursor is generated by a tokenizer to generate -** tokens from a particular input. -*/ -#ifndef _FTS2_TOKENIZER_H_ -#define _FTS2_TOKENIZER_H_ - -/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. -** If tokenizers are to be allowed to call sqlite3_*() functions, then -** we will need a way to register the API consistently. -*/ -#include "sqlite3.h" - -/* -** Structures used by the tokenizer interface. When a new tokenizer -** implementation is registered, the caller provides a pointer to -** an sqlite3_tokenizer_module containing pointers to the callback -** functions that make up an implementation. -** -** When an fts2 table is created, it passes any arguments passed to -** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the -** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer -** implementation. The xCreate() function in turn returns an -** sqlite3_tokenizer structure representing the specific tokenizer to -** be used for the fts2 table (customized by the tokenizer clause arguments). -** -** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() -** method is called. It returns an sqlite3_tokenizer_cursor object -** that may be used to tokenize a specific input buffer based on -** the tokenization rules supplied by a specific sqlite3_tokenizer -** object. -*/ -typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; -typedef struct sqlite3_tokenizer sqlite3_tokenizer; -typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; - -struct sqlite3_tokenizer_module { - - /* - ** Structure version. Should always be set to 0. - */ - int iVersion; - - /* - ** Create a new tokenizer. The values in the argv[] array are the - ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL - ** TABLE statement that created the fts2 table. For example, if - ** the following SQL is executed: - ** - ** CREATE .. USING fts2( ... , tokenizer arg1 arg2) - ** - ** then argc is set to 2, and the argv[] array contains pointers - ** to the strings "arg1" and "arg2". - ** - ** This method should return either SQLITE_OK (0), or an SQLite error - ** code. If SQLITE_OK is returned, then *ppTokenizer should be set - ** to point at the newly created tokenizer structure. The generic - ** sqlite3_tokenizer.pModule variable should not be initialized by - ** this callback. The caller will do so. - */ - int (*xCreate)( - int argc, /* Size of argv array */ - const char *const*argv, /* Tokenizer argument strings */ - sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ - ); - - /* - ** Destroy an existing tokenizer. The fts2 module calls this method - ** exactly once for each successful call to xCreate(). - */ - int (*xDestroy)(sqlite3_tokenizer *pTokenizer); - - /* - ** Create a tokenizer cursor to tokenize an input buffer. The caller - ** is responsible for ensuring that the input buffer remains valid - ** until the cursor is closed (using the xClose() method). - */ - int (*xOpen)( - sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ - const char *pInput, int nBytes, /* Input buffer */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ - ); - - /* - ** Destroy an existing tokenizer cursor. The fts2 module calls this - ** method exactly once for each successful call to xOpen(). - */ - int (*xClose)(sqlite3_tokenizer_cursor *pCursor); - - /* - ** Retrieve the next token from the tokenizer cursor pCursor. This - ** method should either return SQLITE_OK and set the values of the - ** "OUT" variables identified below, or SQLITE_DONE to indicate that - ** the end of the buffer has been reached, or an SQLite error code. - ** - ** *ppToken should be set to point at a buffer containing the - ** normalized version of the token (i.e. after any case-folding and/or - ** stemming has been performed). *pnBytes should be set to the length - ** of this buffer in bytes. The input text that generated the token is - ** identified by the byte offsets returned in *piStartOffset and - ** *piEndOffset. - ** - ** The buffer *ppToken is set to point at is managed by the tokenizer - ** implementation. It is only required to be valid until the next call - ** to xNext() or xClose(). - */ - /* TODO(shess) current implementation requires pInput to be - ** nul-terminated. This should either be fixed, or pInput/nBytes - ** should be converted to zInput. - */ - int (*xNext)( - sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ - const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ - int *piStartOffset, /* OUT: Byte offset of token in input buffer */ - int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ - int *piPosition /* OUT: Number of tokens returned before this one */ - ); -}; - -struct sqlite3_tokenizer { - const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ - /* Tokenizer implementations will typically add additional fields */ -}; - -struct sqlite3_tokenizer_cursor { - sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ - /* Tokenizer implementations will typically add additional fields */ -}; - -#endif /* _FTS2_TOKENIZER_H_ */ diff --git a/ext/fts2/fts2_tokenizer1.c b/ext/fts2/fts2_tokenizer1.c deleted file mode 100644 index fe4f9eb4b..000000000 --- a/ext/fts2/fts2_tokenizer1.c +++ /dev/null @@ -1,233 +0,0 @@ -/* -** 2006 Oct 10 -** -** 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. -** -****************************************************************************** -** -** Implementation of the "simple" full-text-search tokenizer. -*/ - -/* -** The code in this file is only compiled if: -** -** * The FTS2 module is being built as an extension -** (in which case SQLITE_CORE is not defined), or -** -** * The FTS2 module is being built into the core of -** SQLite (in which case SQLITE_ENABLE_FTS2 is defined). -*/ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) - - -#include -#include -#include -#include - -#include "sqlite3.h" -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT3 -#include "fts2_tokenizer.h" - -typedef struct simple_tokenizer { - sqlite3_tokenizer base; - char delim[128]; /* flag ASCII delimiters */ -} simple_tokenizer; - -typedef struct simple_tokenizer_cursor { - sqlite3_tokenizer_cursor base; - const char *pInput; /* input we are tokenizing */ - int nBytes; /* size of the input */ - int iOffset; /* current position in pInput */ - int iToken; /* index of next token to be returned */ - char *pToken; /* storage for current token */ - int nTokenAllocated; /* space allocated to zToken buffer */ -} simple_tokenizer_cursor; - - -/* Forward declaration */ -static const sqlite3_tokenizer_module simpleTokenizerModule; - -static int simpleDelim(simple_tokenizer *t, unsigned char c){ - return c<0x80 && t->delim[c]; -} - -/* -** Create a new tokenizer instance. -*/ -static int simpleCreate( - int argc, const char * const *argv, - sqlite3_tokenizer **ppTokenizer -){ - simple_tokenizer *t; - - t = (simple_tokenizer *) sqlite3_malloc(sizeof(*t)); - if( t==NULL ) return SQLITE_NOMEM; - memset(t, 0, sizeof(*t)); - - /* TODO(shess) Delimiters need to remain the same from run to run, - ** else we need to reindex. One solution would be a meta-table to - ** track such information in the database, then we'd only want this - ** information on the initial create. - */ - if( argc>1 ){ - int i, n = strlen(argv[1]); - for(i=0; i=0x80 ){ - sqlite3_free(t); - return SQLITE_ERROR; - } - t->delim[ch] = 1; - } - } else { - /* Mark non-alphanumeric ASCII characters as delimiters */ - int i; - for(i=1; i<0x80; i++){ - t->delim[i] = !((i>='0' && i<='9') || (i>='A' && i<='Z') || - (i>='a' && i<='z')); - } - } - - *ppTokenizer = &t->base; - return SQLITE_OK; -} - -/* -** Destroy a tokenizer -*/ -static int simpleDestroy(sqlite3_tokenizer *pTokenizer){ - sqlite3_free(pTokenizer); - return SQLITE_OK; -} - -/* -** Prepare to begin tokenizing a particular string. The input -** string to be tokenized is pInput[0..nBytes-1]. A cursor -** used to incrementally tokenize this string is returned in -** *ppCursor. -*/ -static int simpleOpen( - sqlite3_tokenizer *pTokenizer, /* The tokenizer */ - const char *pInput, int nBytes, /* String to be tokenized */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ -){ - simple_tokenizer_cursor *c; - - c = (simple_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); - if( c==NULL ) return SQLITE_NOMEM; - - c->pInput = pInput; - if( pInput==0 ){ - c->nBytes = 0; - }else if( nBytes<0 ){ - c->nBytes = (int)strlen(pInput); - }else{ - c->nBytes = nBytes; - } - c->iOffset = 0; /* start tokenizing at the beginning */ - c->iToken = 0; - c->pToken = NULL; /* no space allocated, yet. */ - c->nTokenAllocated = 0; - - *ppCursor = &c->base; - return SQLITE_OK; -} - -/* -** Close a tokenization cursor previously opened by a call to -** simpleOpen() above. -*/ -static int simpleClose(sqlite3_tokenizer_cursor *pCursor){ - simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; - sqlite3_free(c->pToken); - sqlite3_free(c); - return SQLITE_OK; -} - -/* -** Extract the next token from a tokenization cursor. The cursor must -** have been opened by a prior call to simpleOpen(). -*/ -static int simpleNext( - sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */ - const char **ppToken, /* OUT: *ppToken is the token text */ - int *pnBytes, /* OUT: Number of bytes in token */ - int *piStartOffset, /* OUT: Starting offset of token */ - int *piEndOffset, /* OUT: Ending offset of token */ - int *piPosition /* OUT: Position integer of token */ -){ - simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; - simple_tokenizer *t = (simple_tokenizer *) pCursor->pTokenizer; - unsigned char *p = (unsigned char *)c->pInput; - - while( c->iOffsetnBytes ){ - int iStartOffset; - - /* Scan past delimiter characters */ - while( c->iOffsetnBytes && simpleDelim(t, p[c->iOffset]) ){ - c->iOffset++; - } - - /* Count non-delimiter characters. */ - iStartOffset = c->iOffset; - while( c->iOffsetnBytes && !simpleDelim(t, p[c->iOffset]) ){ - c->iOffset++; - } - - if( c->iOffset>iStartOffset ){ - int i, n = c->iOffset-iStartOffset; - if( n>c->nTokenAllocated ){ - c->nTokenAllocated = n+20; - c->pToken = sqlite3_realloc(c->pToken, c->nTokenAllocated); - if( c->pToken==NULL ) return SQLITE_NOMEM; - } - for(i=0; ipToken[i] = (ch>='A' && ch<='Z') ? (ch - 'A' + 'a') : ch; - } - *ppToken = c->pToken; - *pnBytes = n; - *piStartOffset = iStartOffset; - *piEndOffset = c->iOffset; - *piPosition = c->iToken++; - - return SQLITE_OK; - } - } - return SQLITE_DONE; -} - -/* -** The set of routines that implement the simple tokenizer -*/ -static const sqlite3_tokenizer_module simpleTokenizerModule = { - 0, - simpleCreate, - simpleDestroy, - simpleOpen, - simpleClose, - simpleNext, -}; - -/* -** Allocate a new simple tokenizer. Return a pointer to the new -** tokenizer in *ppModule -*/ -void sqlite3Fts2SimpleTokenizerModule( - sqlite3_tokenizer_module const**ppModule -){ - *ppModule = &simpleTokenizerModule; -} - -#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) */ diff --git a/ext/fts2/mkfts2amal.tcl b/ext/fts2/mkfts2amal.tcl deleted file mode 100644 index 5c8d1e93d..000000000 --- a/ext/fts2/mkfts2amal.tcl +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/tclsh -# -# This script builds a single C code file holding all of FTS2 code. -# The name of the output file is fts2amal.c. To build this file, -# first do: -# -# make target_source -# -# The make target above moves all of the source code files into -# a subdirectory named "tsrc". (This script expects to find the files -# there and will not work if they are not found.) -# -# After the "tsrc" directory has been created and populated, run -# this script: -# -# tclsh mkfts2amal.tcl -# -# The amalgamated FTS2 code will be written into fts2amal.c -# - -# Open the output file and write a header comment at the beginning -# of the file. -# -set out [open fts2amal.c w] -set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1] -puts $out [subst \ -{/****************************************************************************** -** This file is an amalgamation of separate C source files from the SQLite -** Full Text Search extension 2 (fts2). By combining all the individual C -** code files into this 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. It also -** makes the code easier to import into other projects. -** -** This amalgamation was generated on $today. -*/}] - -# These are the header files used by FTS2. The first time any of these -# files are seen in a #include statement in the C code, include the complete -# text of the file in-line. The file only needs to be included once. -# -foreach hdr { - fts2.h - fts2_hash.h - fts2_tokenizer.h - sqlite3.h - sqlite3ext.h -} { - set available_hdr($hdr) 1 -} - -# 78 stars used for comment formatting. -set s78 \ -{*****************************************************************************} - -# Insert a comment into the code -# -proc section_comment {text} { - global out s78 - set n [string length $text] - set nstar [expr {60 - $n}] - set stars [string range $s78 0 $nstar] - puts $out "/************** $text $stars/" -} - -# Read the source file named $filename and write it into the -# sqlite3.c output file. If any #include statements are seen, -# process them approprately. -# -proc copy_file {filename} { - global seen_hdr available_hdr out - set tail [file tail $filename] - section_comment "Begin file $tail" - set in [open $filename r] - while {![eof $in]} { - set line [gets $in] - if {[regexp {^#\s*include\s+["<]([^">]+)[">]} $line all hdr]} { - if {[info exists available_hdr($hdr)]} { - if {$available_hdr($hdr)} { - section_comment "Include $hdr in the middle of $tail" - copy_file tsrc/$hdr - section_comment "Continuing where we left off in $tail" - } - } elseif {![info exists seen_hdr($hdr)]} { - set seen_hdr($hdr) 1 - puts $out $line - } - } elseif {[regexp {^#ifdef __cplusplus} $line]} { - puts $out "#if 0" - } elseif {[regexp {^#line} $line]} { - # Skip #line directives. - } else { - puts $out $line - } - } - close $in - section_comment "End of $tail" -} - - -# Process the source files. Process files containing commonly -# used subroutines first in order to help the compiler find -# inlining opportunities. -# -foreach file { - fts2.c - fts2_hash.c - fts2_porter.c - fts2_tokenizer.c - fts2_tokenizer1.c - fts2_icu.c -} { - copy_file tsrc/$file -} - -close $out diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index c43eac491..43a9daf60 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -5287,9 +5287,8 @@ static void fts3EvalNextRow( Fts3Expr *pExpr, /* Expr. to advance to next matching row */ int *pRc /* IN/OUT: Error code */ ){ - if( *pRc==SQLITE_OK ){ + if( *pRc==SQLITE_OK && pExpr->bEof==0 ){ int bDescDoclist = pCsr->bDesc; /* Used by DOCID_CMP() macro */ - assert( pExpr->bEof==0 ); pExpr->bStart = 1; switch( pExpr->eType ){ @@ -5765,6 +5764,22 @@ static void fts3EvalUpdateCounts(Fts3Expr *pExpr, int nCol){ } } +/* +** This is an sqlite3Fts3ExprIterate() callback. If the Fts3Expr.aMI[] array +** has not yet been allocated, allocate and zero it. Otherwise, just zero +** it. +*/ +static int fts3AllocateMSI(Fts3Expr *pExpr, int iPhrase, void *pCtx){ + Fts3Table *pTab = (Fts3Table*)pCtx; + UNUSED_PARAMETER(iPhrase); + if( pExpr->aMI==0 ){ + pExpr->aMI = (u32 *)sqlite3_malloc64(pTab->nColumn * 3 * sizeof(u32)); + if( pExpr->aMI==0 ) return SQLITE_NOMEM; + } + memset(pExpr->aMI, 0, pTab->nColumn * 3 * sizeof(u32)); + return SQLITE_OK; +} + /* ** Expression pExpr must be of type FTSQUERY_PHRASE. ** @@ -5786,7 +5801,6 @@ static int fts3EvalGatherStats( if( pExpr->aMI==0 ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; Fts3Expr *pRoot; /* Root of NEAR expression */ - Fts3Expr *p; /* Iterator used for several purposes */ sqlite3_int64 iPrevId = pCsr->iPrevId; sqlite3_int64 iDocid; @@ -5794,7 +5808,9 @@ static int fts3EvalGatherStats( /* Find the root of the NEAR expression */ pRoot = pExpr; - while( pRoot->pParent && pRoot->pParent->eType==FTSQUERY_NEAR ){ + while( pRoot->pParent + && (pRoot->pParent->eType==FTSQUERY_NEAR || pRoot->bDeferred) + ){ pRoot = pRoot->pParent; } iDocid = pRoot->iDocid; @@ -5802,14 +5818,8 @@ static int fts3EvalGatherStats( assert( pRoot->bStart ); /* Allocate space for the aMSI[] array of each FTSQUERY_PHRASE node */ - for(p=pRoot; p; p=p->pLeft){ - Fts3Expr *pE = (p->eType==FTSQUERY_PHRASE?p:p->pRight); - assert( pE->aMI==0 ); - pE->aMI = (u32 *)sqlite3_malloc64(pTab->nColumn * 3 * sizeof(u32)); - if( !pE->aMI ) return SQLITE_NOMEM; - memset(pE->aMI, 0, pTab->nColumn * 3 * sizeof(u32)); - } - + rc = sqlite3Fts3ExprIterate(pRoot, fts3AllocateMSI, (void*)pTab); + if( rc!=SQLITE_OK ) return rc; fts3EvalRestart(pCsr, pRoot, &rc); while( pCsr->isEof==0 && rc==SQLITE_OK ){ @@ -5965,6 +5975,7 @@ int sqlite3Fts3EvalPhrasePoslist( u8 bTreeEof = 0; Fts3Expr *p; /* Used to iterate from pExpr to root */ Fts3Expr *pNear; /* Most senior NEAR ancestor (or pExpr) */ + Fts3Expr *pRun; /* Closest non-deferred ancestor of pNear */ int bMatch; /* Check if this phrase descends from an OR expression node. If not, @@ -5979,25 +5990,30 @@ int sqlite3Fts3EvalPhrasePoslist( if( p->bEof ) bTreeEof = 1; } if( bOr==0 ) return SQLITE_OK; + pRun = pNear; + while( pRun->bDeferred ){ + assert( pRun->pParent ); + pRun = pRun->pParent; + } /* This is the descendent of an OR node. In this case we cannot use ** an incremental phrase. Load the entire doclist for the phrase ** into memory in this case. */ if( pPhrase->bIncr ){ - int bEofSave = pNear->bEof; - fts3EvalRestart(pCsr, pNear, &rc); - while( rc==SQLITE_OK && !pNear->bEof ){ - fts3EvalNextRow(pCsr, pNear, &rc); - if( bEofSave==0 && pNear->iDocid==iDocid ) break; + int bEofSave = pRun->bEof; + fts3EvalRestart(pCsr, pRun, &rc); + while( rc==SQLITE_OK && !pRun->bEof ){ + fts3EvalNextRow(pCsr, pRun, &rc); + if( bEofSave==0 && pRun->iDocid==iDocid ) break; } assert( rc!=SQLITE_OK || pPhrase->bIncr==0 ); - if( rc==SQLITE_OK && pNear->bEof!=bEofSave ){ + if( rc==SQLITE_OK && pRun->bEof!=bEofSave ){ rc = FTS_CORRUPT_VTAB; } } if( bTreeEof ){ - while( rc==SQLITE_OK && !pNear->bEof ){ - fts3EvalNextRow(pCsr, pNear, &rc); + while( rc==SQLITE_OK && !pRun->bEof ){ + fts3EvalNextRow(pCsr, pRun, &rc); } } if( rc!=SQLITE_OK ) return rc; diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index e821d6be3..3a8a884f9 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -650,5 +650,7 @@ int sqlite3FtsUnicodeIsalnum(int); int sqlite3FtsUnicodeIsdiacritic(int); #endif +int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*); + #endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */ #endif /* _FTSINT_H */ diff --git a/ext/fts3/fts3_snippet.c b/ext/fts3/fts3_snippet.c index c5192c4eb..227c5f00f 100644 --- a/ext/fts3/fts3_snippet.c +++ b/ext/fts3/fts3_snippet.c @@ -41,7 +41,7 @@ typedef sqlite3_int64 i64; /* -** Used as an fts3ExprIterate() context when loading phrase doclists to +** Used as an sqlite3Fts3ExprIterate() context when loading phrase doclists to ** Fts3Expr.aDoclist[]/nDoclist. */ typedef struct LoadDoclistCtx LoadDoclistCtx; @@ -85,7 +85,7 @@ struct SnippetFragment { }; /* -** This type is used as an fts3ExprIterate() context object while +** This type is used as an sqlite3Fts3ExprIterate() context object while ** accumulating the data returned by the matchinfo() function. */ typedef struct MatchInfo MatchInfo; @@ -244,7 +244,7 @@ static void fts3GetDeltaPosition(char **pp, i64 *piPos){ } /* -** Helper function for fts3ExprIterate() (see below). +** Helper function for sqlite3Fts3ExprIterate() (see below). */ static int fts3ExprIterate2( Fts3Expr *pExpr, /* Expression to iterate phrases of */ @@ -278,7 +278,7 @@ static int fts3ExprIterate2( ** Otherwise, SQLITE_OK is returned after a callback has been made for ** all eligible phrase nodes. */ -static int fts3ExprIterate( +int sqlite3Fts3ExprIterate( Fts3Expr *pExpr, /* Expression to iterate phrases of */ int (*x)(Fts3Expr*,int,void*), /* Callback function to invoke for phrases */ void *pCtx /* Second argument to pass to callback */ @@ -287,10 +287,9 @@ static int fts3ExprIterate( return fts3ExprIterate2(pExpr, &iPhrase, x, pCtx); } - /* -** This is an fts3ExprIterate() callback used while loading the doclists -** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also +** This is an sqlite3Fts3ExprIterate() callback used while loading the +** doclists for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also ** fts3ExprLoadDoclists(). */ static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ @@ -322,9 +321,9 @@ static int fts3ExprLoadDoclists( int *pnToken /* OUT: Number of tokens in query */ ){ int rc; /* Return Code */ - LoadDoclistCtx sCtx = {0,0,0}; /* Context for fts3ExprIterate() */ + LoadDoclistCtx sCtx = {0,0,0}; /* Context for sqlite3Fts3ExprIterate() */ sCtx.pCsr = pCsr; - rc = fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb, (void *)&sCtx); + rc = sqlite3Fts3ExprIterate(pCsr->pExpr,fts3ExprLoadDoclistsCb,(void*)&sCtx); if( pnPhrase ) *pnPhrase = sCtx.nPhrase; if( pnToken ) *pnToken = sCtx.nToken; return rc; @@ -337,7 +336,7 @@ static int fts3ExprPhraseCountCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ } static int fts3ExprPhraseCount(Fts3Expr *pExpr){ int nPhrase = 0; - (void)fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase); + (void)sqlite3Fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase); return nPhrase; } @@ -465,8 +464,9 @@ static void fts3SnippetDetails( } /* -** This function is an fts3ExprIterate() callback used by fts3BestSnippet(). -** Each invocation populates an element of the SnippetIter.aPhrase[] array. +** This function is an sqlite3Fts3ExprIterate() callback used by +** fts3BestSnippet(). Each invocation populates an element of the +** SnippetIter.aPhrase[] array. */ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ SnippetIter *p = (SnippetIter *)ctx; @@ -556,7 +556,9 @@ static int fts3BestSnippet( sIter.nSnippet = nSnippet; sIter.nPhrase = nList; sIter.iCurrent = -1; - rc = fts3ExprIterate(pCsr->pExpr, fts3SnippetFindPositions, (void*)&sIter); + rc = sqlite3Fts3ExprIterate( + pCsr->pExpr, fts3SnippetFindPositions, (void*)&sIter + ); if( rc==SQLITE_OK ){ /* Set the *pmSeen output variable. */ @@ -917,10 +919,10 @@ static int fts3ExprLHitGather( } /* -** fts3ExprIterate() callback used to collect the "global" matchinfo stats -** for a single query. +** sqlite3Fts3ExprIterate() callback used to collect the "global" matchinfo +** stats for a single query. ** -** fts3ExprIterate() callback to load the 'global' elements of a +** sqlite3Fts3ExprIterate() 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. @@ -955,7 +957,7 @@ static int fts3ExprGlobalHitsCb( } /* -** fts3ExprIterate() callback used to collect the "local" part of the +** sqlite3Fts3ExprIterate() callback used to collect the "local" part of the ** FTS3_MATCHINFO_HITS array. The local stats are those elements of the ** array that are different for each row returned by the query. */ @@ -1151,7 +1153,7 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ **/ aIter = sqlite3Fts3MallocZero(sizeof(LcsIterator) * pCsr->nPhrase); if( !aIter ) return SQLITE_NOMEM; - (void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); + (void)sqlite3Fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); for(i=0; inPhrase; i++){ LcsIterator *pIter = &aIter[i]; @@ -1328,11 +1330,11 @@ static int fts3MatchinfoValues( rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc,0,0); if( rc!=SQLITE_OK ) break; } - rc = fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo); + rc = sqlite3Fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo); sqlite3Fts3EvalTestDeferred(pCsr, &rc); if( rc!=SQLITE_OK ) break; } - (void)fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo); + (void)sqlite3Fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo); break; } } @@ -1555,7 +1557,7 @@ struct TermOffsetCtx { }; /* -** This function is an fts3ExprIterate() callback used by sqlite3Fts3Offsets(). +** This function is an sqlite3Fts3ExprIterate() callback used by sqlite3Fts3Offsets(). */ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){ TermOffsetCtx *p = (TermOffsetCtx *)ctx; @@ -1637,7 +1639,9 @@ void sqlite3Fts3Offsets( */ sCtx.iCol = iCol; sCtx.iTerm = 0; - rc = fts3ExprIterate(pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx); + rc = sqlite3Fts3ExprIterate( + pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx + ); if( rc!=SQLITE_OK ) goto offsets_out; /* Retreive the text stored in column iCol. If an SQL NULL is stored diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 66bd304d4..e4072db7a 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -289,6 +289,19 @@ int sqlite3Fts5ExprNew( return sParse.rc; } +/* +** Assuming that buffer z is at least nByte bytes in size and contains a +** valid utf-8 string, return the number of characters in the string. +*/ +static int fts5ExprCountChar(const char *z, int nByte){ + int nRet = 0; + int ii; + for(ii=0; ii=3 ){ + + if( fts5ExprCountChar(&zText[iFirst], i-iFirst)>=3 ){ int jj; zExpr[iOut++] = '"'; for(jj=iFirst; jjnLevel = pStruct->nLevel+1; + pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL); pNew->nRef = 1; pNew->nWriteCounter = pStruct->nWriteCounter; - pLvl = &pNew->aLevel[pStruct->nLevel]; + pLvl = &pNew->aLevel[pNew->nLevel-1]; pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pLvl->aSeg ){ int iLvl, iSeg; @@ -5073,7 +5075,7 @@ static void fts5MergePrefixLists( /* Initialize a doclist-iterator for each input buffer. Arrange them in ** a linked-list starting at pHead in ascending order of rowid. Avoid ** linking any iterators already at EOF into the linked list at all. */ - assert( nBuf+1<=sizeof(aMerger)/sizeof(aMerger[0]) ); + assert( nBuf+1<=(int)(sizeof(aMerger)/sizeof(aMerger[0])) ); memset(aMerger, 0, sizeof(PrefixMerger)*(nBuf+1)); pHead = &aMerger[nBuf]; fts5DoclistIterInit(p1, &pHead->iter); diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index ccdcf7b57..5392b3ba0 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -2866,7 +2866,9 @@ static int fts5Init(sqlite3 *db){ } if( rc==SQLITE_OK ){ rc = sqlite3_create_function( - db, "fts5_source_id", 0, SQLITE_UTF8, p, fts5SourceIdFunc, 0, 0 + db, "fts5_source_id", 0, + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, + p, fts5SourceIdFunc, 0, 0 ); } } diff --git a/ext/fts5/test/fts5optimize2.test b/ext/fts5/test/fts5optimize2.test new file mode 100644 index 000000000..a0782ee79 --- /dev/null +++ b/ext/fts5/test/fts5optimize2.test @@ -0,0 +1,64 @@ +# 2014 Dec 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. +# +#*********************************************************************** +# +# TESTRUNNER: slow +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5optimize2 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +set nLoop 2500 + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('pgsz', 32); +} + +do_test 1.1 { + for {set ii 0} {$ii < $nLoop} {incr ii} { + execsql { + INSERT INTO t1 VALUES('abc def ghi'); + INSERT INTO t1 VALUES('jkl mno pqr'); + INSERT INTO t1(t1) VALUES('optimize'); + } + } +} {} + +do_execsql_test 1.2 { + SELECT count(*) FROM t1('mno') +} $nLoop + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t2 USING fts5(x); + INSERT INTO t2(t2, rank) VALUES('pgsz', 32); +} + +do_test 2.1 { + for {set ii 0} {$ii < $nLoop} {incr ii} { + execsql { + INSERT INTO t2 VALUES('abc def ghi'); + INSERT INTO t2 VALUES('jkl mno pqr'); + INSERT INTO t2(t2, rank) VALUES('merge', -1); + } + } +} {} + +do_execsql_test 2.2 { + SELECT count(*) FROM t2('mno') +} $nLoop + +finish_test diff --git a/ext/fts5/test/fts5trigram.test b/ext/fts5/test/fts5trigram.test index fb66efed6..951daf144 100644 --- a/ext/fts5/test/fts5trigram.test +++ b/ext/fts5/test/fts5trigram.test @@ -55,6 +55,7 @@ foreach {tn like res} { 6 {abc%klm} 1 7 {ABCDEFG%} 1 8 {%รุงเ%} 2 + 9 {%งเ%} 2 } { do_execsql_test 1.3.$tn { SELECT rowid FROM t1 WHERE y LIKE $like @@ -197,4 +198,21 @@ do_eqp_test 6.4 { SELECT * FROM ci1 WHERE x GLOB ? } {VIRTUAL TABLE INDEX 0:G0} +reset_db +do_execsql_test 7.0 { + CREATE VIRTUAL TABLE f USING FTS5(filename, tokenize="trigram"); + INSERT INTO f (rowid, filename) VALUES + (10, "giraffe.png"), + (20, "жираф.png"), + (30, "cat.png"), + (40, "кот.png"), + (50, "misic-🎵-.mp3"); +} +do_execsql_test 7.1 { + SELECT rowid FROM f WHERE +filename GLOB '*ир*'; +} {20} +do_execsql_test 7.2 { + SELECT rowid FROM f WHERE filename GLOB '*ир*'; +} {20} + finish_test diff --git a/ext/lsm1/lsmInt.h b/ext/lsm1/lsmInt.h index 0f822e479..5060366d0 100644 --- a/ext/lsm1/lsmInt.h +++ b/ext/lsm1/lsmInt.h @@ -44,6 +44,8 @@ # endif #endif +/* #define LSM_DEBUG_EXPENSIVE 1 */ + /* ** Default values for various data structure parameters. These may be ** overridden by calls to lsm_config(). @@ -405,7 +407,7 @@ struct Segment { LsmPgno iFirst; /* First page of this run */ LsmPgno iLastPg; /* Last page of this run */ LsmPgno iRoot; /* Root page number (if any) */ - int nSize; /* Size of this run in pages */ + LsmPgno nSize; /* Size of this run in pages */ Redirect *pRedirect; /* Block redirects (or NULL) */ }; @@ -853,6 +855,8 @@ int lsmVarintGet32(u8 *, int *); int lsmVarintPut64(u8 *aData, i64 iVal); int lsmVarintGet64(const u8 *aData, i64 *piVal); +int lsmVarintLen64(i64); + int lsmVarintLen32(int); int lsmVarintSize(u8 c); diff --git a/ext/lsm1/lsm_ckpt.c b/ext/lsm1/lsm_ckpt.c index ba92a823c..1c4f788ad 100644 --- a/ext/lsm1/lsm_ckpt.c +++ b/ext/lsm1/lsm_ckpt.c @@ -511,7 +511,7 @@ static void ckptNewSegment( pSegment->iFirst = ckptGobble64(aIn, piIn); pSegment->iLastPg = ckptGobble64(aIn, piIn); pSegment->iRoot = ckptGobble64(aIn, piIn); - pSegment->nSize = (int)ckptGobble64(aIn, piIn); + pSegment->nSize = ckptGobble64(aIn, piIn); assert( pSegment->iFirst ); } diff --git a/ext/lsm1/lsm_file.c b/ext/lsm1/lsm_file.c index 1dcdd05d9..fd78835bb 100644 --- a/ext/lsm1/lsm_file.c +++ b/ext/lsm1/lsm_file.c @@ -215,8 +215,8 @@ struct FileSystem { char *zLog; /* Database file name */ int nMetasize; /* Size of meta pages in bytes */ int nMetaRwSize; /* Read/written size of meta pages in bytes */ - int nPagesize; /* Database page-size in bytes */ - int nBlocksize; /* Database block-size in bytes */ + i64 nPagesize; /* Database page-size in bytes */ + i64 nBlocksize; /* Database block-size in bytes */ /* r/w file descriptors for both files. */ LsmFile *pLsmFile; /* Used after lsm_close() to link into list */ @@ -889,7 +889,7 @@ static LsmPgno fsFirstPageOnBlock(FileSystem *pFS, int iBlock){ iPg = pFS->nBlocksize * (LsmPgno)(iBlock-1) + 4; } }else{ - const int nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); + const i64 nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); if( iBlock==1 ){ iPg = 1 + ((pFS->nMetasize*2 + pFS->nPagesize - 1) / pFS->nPagesize); }else{ @@ -1131,7 +1131,6 @@ static void fsGrowMapping( i64 iSz, /* Minimum size to extend mapping to */ int *pRc /* IN/OUT: Error code */ ){ - assert( pFS->pCompress==0 ); assert( PAGE_HASPREV==4 ); if( *pRc==LSM_OK && iSz>pFS->nMap ){ @@ -1872,7 +1871,7 @@ void lsmFsGobble( assert( nPgno>0 && 0==fsPageRedirects(pFS, pRun, aPgno[0]) ); iBlk = fsPageToBlock(pFS, pRun->iFirst); - pRun->nSize += (int)(pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); + pRun->nSize += (pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); while( rc==LSM_OK ){ int iNext = 0; @@ -1883,13 +1882,13 @@ void lsmFsGobble( } rc = fsBlockNext(pFS, pRun, iBlk, &iNext); if( rc==LSM_OK ) rc = fsFreeBlock(pFS, pSnapshot, pRun, iBlk); - pRun->nSize -= (int)( + pRun->nSize -= ( 1 + fsLastPageOnBlock(pFS, iBlk) - fsFirstPageOnBlock(pFS, iBlk) ); iBlk = iNext; } - pRun->nSize -= (int)(pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); + pRun->nSize -= (pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); assert( pRun->nSize>0 ); } diff --git a/ext/lsm1/lsm_main.c b/ext/lsm1/lsm_main.c index a9c48e004..f2b353105 100644 --- a/ext/lsm1/lsm_main.c +++ b/ext/lsm1/lsm_main.c @@ -432,7 +432,7 @@ int lsm_config(lsm_db *pDb, int eParam, ...){ } void lsmAppendSegmentList(LsmString *pStr, char *zPre, Segment *pSeg){ - lsmStringAppendf(pStr, "%s{%d %d %d %d}", zPre, + lsmStringAppendf(pStr, "%s{%lld %lld %lld %lld}", zPre, pSeg->iFirst, pSeg->iLastPg, pSeg->iRoot, pSeg->nSize ); } diff --git a/ext/lsm1/lsm_shared.c b/ext/lsm1/lsm_shared.c index 2fdacf1ec..09f933848 100644 --- a/ext/lsm1/lsm_shared.c +++ b/ext/lsm1/lsm_shared.c @@ -1306,6 +1306,24 @@ int lsmBeginRoTrans(lsm_db *db){ } } + /* In 'lsm_open()' we don't update the page and block sizes in the + ** Filesystem for 'readonly' connection. Because member 'db->pShmhdr' is a + ** nullpointer, this prevents loading a checkpoint. Now that the system is + ** live this member should be set. So we can update both values in + ** the Filesystem. + ** + ** Configure the file-system connection with the page-size and block-size + ** of this database. Even if the database file is zero bytes in size + ** on disk, these values have been set in shared-memory by now, and so + ** are guaranteed not to change during the lifetime of this connection. */ + if( LSM_OK==rc + && 0==lsmCheckpointClientCacheOk(db) + && LSM_OK==(rc=lsmCheckpointLoad(db, 0)) + ){ + lsmFsSetPageSize(db->pFS, lsmCheckpointPgsz(db->aSnapshot)); + lsmFsSetBlockSize(db->pFS, lsmCheckpointBlksz(db->aSnapshot)); + } + if( rc==LSM_OK ){ rc = lsmBeginReadTrans(db); } diff --git a/ext/lsm1/lsm_sorted.c b/ext/lsm1/lsm_sorted.c index 4a24e4b82..6e5243e85 100644 --- a/ext/lsm1/lsm_sorted.c +++ b/ext/lsm1/lsm_sorted.c @@ -525,7 +525,7 @@ static u8 *pageGetKey( LsmBlob *pBlob /* If required, use this for dynamic memory */ ){ u8 *pKey; - int nDummy; + i64 nDummy; int eType; u8 *aData; int nData; @@ -537,10 +537,10 @@ static u8 *pageGetKey( pKey = pageGetCell(aData, nData, iCell); eType = *pKey++; - pKey += lsmVarintGet32(pKey, &nDummy); + pKey += lsmVarintGet64(pKey, &nDummy); pKey += lsmVarintGet32(pKey, pnKey); if( rtIsWrite(eType) ){ - pKey += lsmVarintGet32(pKey, &nDummy); + pKey += lsmVarintGet64(pKey, &nDummy); } *piTopic = rtTopic(eType); @@ -657,14 +657,14 @@ static int btreeCursorLoadKey(BtreeCursor *pCsr){ return rc; } -static int btreeCursorPtr(u8 *aData, int nData, int iCell){ +static LsmPgno btreeCursorPtr(u8 *aData, int nData, int iCell){ int nCell; nCell = pageGetNRec(aData, nData); if( iCell>=nCell ){ - return (int)pageGetPtr(aData, nData); + return pageGetPtr(aData, nData); } - return (int)pageGetRecordPtr(aData, nData, iCell); + return pageGetRecordPtr(aData, nData, iCell); } static int btreeCursorNext(BtreeCursor *pCsr){ @@ -751,7 +751,7 @@ static int btreeCursorFirst(BtreeCursor *pCsr){ Page *pPg = 0; FileSystem *pFS = pCsr->pFS; - int iPg = (int)pCsr->pSeg->iRoot; + LsmPgno iPg = pCsr->pSeg->iRoot; do { rc = lsmFsDbPageGet(pFS, pCsr->pSeg, iPg, &pPg); @@ -779,7 +779,7 @@ static int btreeCursorFirst(BtreeCursor *pCsr){ assert( pCsr->aPg[pCsr->nDepth].iCell==0 ); pCsr->aPg[pCsr->nDepth].pPage = pPg; pCsr->nDepth++; - iPg = (int)pageGetRecordPtr(aData, nData, 0); + iPg = pageGetRecordPtr(aData, nData, 0); } } }while( rc==LSM_OK ); @@ -871,7 +871,7 @@ static int btreeCursorRestore( int nSeek; int iTopicSeek; int iPg = 0; - int iLoad = (int)pSeg->iRoot; + LsmPgno iLoad = pSeg->iRoot; Page *pPg = pCsr->aPg[nDepth-1].pPage; if( pageObjGetNRec(pPg)==0 ){ @@ -903,7 +903,7 @@ static int btreeCursorRestore( aData = fsPageData(pPg2, &nData); assert( (pageGetFlags(aData, nData) & SEGMENT_BTREE_FLAG) ); - iLoad = (int)pageGetPtr(aData, nData); + iLoad = pageGetPtr(aData, nData); iCell2 = pageGetNRec(aData, nData); iMax = iCell2-1; iMin = 0; @@ -926,7 +926,7 @@ static int btreeCursorRestore( assert( res!=0 ); if( res<0 ){ - iLoad = (int)iPtr; + iLoad = iPtr; iCell2 = iTry; iMax = iTry-1; }else{ @@ -1013,7 +1013,7 @@ static void segmentPtrSetPage(SegmentPtr *pPtr, Page *pNext){ static int segmentPtrLoadPage( FileSystem *pFS, SegmentPtr *pPtr, /* Load page into this SegmentPtr object */ - int iNew /* Page number of new page */ + LsmPgno iNew /* Page number of new page */ ){ Page *pPg = 0; /* The new page */ int rc; /* Return Code */ @@ -1633,7 +1633,7 @@ static int segmentPtrSeek( int iTopic, /* Key topic to seek to */ void *pKey, int nKey, /* Key to seek to */ int eSeek, /* Search bias - see above */ - int *piPtr, /* OUT: FC pointer */ + LsmPgno *piPtr, /* OUT: FC pointer */ int *pbStop ){ int (*xCmp)(void *, int, void *, int) = pCsr->pDb->xCmp; @@ -1759,7 +1759,7 @@ static int segmentPtrSeek( } assert( rc!=LSM_OK || assertSeekResult(pCsr,pPtr,iTopic,pKey,nKey,eSeek) ); - *piPtr = (int)iPtrOut; + *piPtr = iPtrOut; return rc; } @@ -1773,11 +1773,11 @@ static int seekInBtree( ){ int i = 0; int rc; - int iPg; + LsmPgno iPg; Page *pPg = 0; LsmBlob blob = {0, 0, 0}; - iPg = (int)pSeg->iRoot; + iPg = pSeg->iRoot; do { LsmPgno *piFirst = 0; if( aPg ){ @@ -1799,7 +1799,7 @@ static int seekInBtree( flags = pageGetFlags(aData, nData); if( (flags & SEGMENT_BTREE_FLAG)==0 ) break; - iPg = (int)pageGetPtr(aData, nData); + iPg = pageGetPtr(aData, nData); nRec = pageGetNRec(aData, nData); iMin = 0; @@ -1825,7 +1825,7 @@ static int seekInBtree( pCsr->pDb->xCmp, iTopic, pKey, nKey, iTopicT, pKeyT, nKeyT ); if( res<0 ){ - iPg = (int)iPtr; + iPg = iPtr; iMax = iTry-1; }else{ iMin = iTry+1; @@ -1851,12 +1851,12 @@ static int seekInSegment( SegmentPtr *pPtr, int iTopic, void *pKey, int nKey, - int iPg, /* Page to search */ + LsmPgno iPg, /* Page to search */ int eSeek, /* Search bias - see above */ - int *piPtr, /* OUT: FC pointer */ + LsmPgno *piPtr, /* OUT: FC pointer */ int *pbStop /* OUT: Stop search flag */ ){ - int iPtr = iPg; + LsmPgno iPtr = iPg; int rc = LSM_OK; if( pPtr->pSeg->iRoot ){ @@ -1866,7 +1866,7 @@ static int seekInSegment( if( rc==LSM_OK ) segmentPtrSetPage(pPtr, pPg); }else{ if( iPtr==0 ){ - iPtr = (int)pPtr->pSeg->iFirst; + iPtr = pPtr->pSeg->iFirst; } if( rc==LSM_OK ){ rc = segmentPtrLoadPage(pCsr->pDb->pFS, pPtr, iPtr); @@ -1904,7 +1904,7 @@ static int seekInLevel( ){ Level *pLvl = aPtr[0].pLevel; /* Level to seek within */ int rc = LSM_OK; /* Return code */ - int iOut = 0; /* Pointer to return to caller */ + LsmPgno iOut = 0; /* Pointer to return to caller */ int res = -1; /* Result of xCmp(pKey, split) */ int nRhs = pLvl->nRight; /* Number of right-hand-side segments */ int bStop = 0; @@ -1923,8 +1923,8 @@ static int seekInLevel( ** left-hand-side of the level in this case. */ if( res<0 ){ int i; - int iPtr = 0; - if( nRhs==0 ) iPtr = (int)*piPgno; + LsmPgno iPtr = 0; + if( nRhs==0 ) iPtr = *piPgno; rc = seekInSegment( pCsr, &aPtr[0], iTopic, pKey, nKey, iPtr, eSeek, &iOut, &bStop @@ -1939,7 +1939,7 @@ static int seekInLevel( if( res>=0 ){ int bHit = 0; /* True if at least one rhs is not EOF */ - int iPtr = (int)*piPgno; + LsmPgno iPtr = *piPgno; int i; segmentPtrReset(&aPtr[0], LSM_SEGMENTPTR_FREE_THRESHOLD); for(i=1; rc==LSM_OK && i<=nRhs && bStop==0; i++){ @@ -3361,6 +3361,8 @@ static int mergeWorkerPageOffset(u8 *aData, int nData){ int iOff; int nKey; int eType; + i64 nDummy; + nRec = lsmGetU16(&aData[SEGMENT_NRECORD_OFFSET(nData)]); iOff = lsmGetU16(&aData[SEGMENT_CELLPTR_OFFSET(nData, nRec-1)]); @@ -3370,7 +3372,7 @@ static int mergeWorkerPageOffset(u8 *aData, int nData){ || eType==(LSM_SEPARATOR) ); - iOff += lsmVarintGet32(&aData[iOff], &nKey); + iOff += lsmVarintGet64(&aData[iOff], &nDummy); iOff += lsmVarintGet32(&aData[iOff], &nKey); return iOff + (eType ? nKey : 0); @@ -3480,7 +3482,7 @@ static int mergeWorkerLoadHierarchy(MergeWorker *pMW){ lsm_env *pEnv = pMW->pDb->pEnv; Page **apHier = 0; int nHier = 0; - int iPg = (int)pSeg->iRoot; + LsmPgno iPg = pSeg->iRoot; do { Page *pPg = 0; @@ -3506,7 +3508,7 @@ static int mergeWorkerLoadHierarchy(MergeWorker *pMW){ nHier++; apHier[0] = pPg; - iPg = (int)pageGetPtr(aData, nData); + iPg = pageGetPtr(aData, nData); }else{ lsmFsPageRelease(pPg); break; @@ -3625,10 +3627,11 @@ static int mergeWorkerBtreeWrite( assert( lsmFsPageWritable(pOld) ); aData = fsPageData(pOld, &nData); if( eType==0 ){ - nByte = 2 + 1 + lsmVarintLen32((int)iPtr) + lsmVarintLen32((int)iKeyPg); + nByte = 2 + 1 + lsmVarintLen64(iPtr) + lsmVarintLen64(iKeyPg); }else{ - nByte = 2 + 1 + lsmVarintLen32((int)iPtr) + lsmVarintLen32(nKey) + nKey; + nByte = 2 + 1 + lsmVarintLen64(iPtr) + lsmVarintLen32(nKey) + nKey; } + nRec = pageGetNRec(aData, nData); nFree = SEGMENT_EOF(nData, nRec) - mergeWorkerPageOffset(aData, nData); if( nByte<=nFree ) break; @@ -3672,11 +3675,11 @@ static int mergeWorkerBtreeWrite( lsmPutU16(&aData[SEGMENT_NRECORD_OFFSET(nData)], (u16)(nRec+1)); if( eType==0 ){ aData[iOff++] = 0x00; - iOff += lsmVarintPut32(&aData[iOff], (int)iPtr); - iOff += lsmVarintPut32(&aData[iOff], (int)iKeyPg); + iOff += lsmVarintPut64(&aData[iOff], iPtr); + iOff += lsmVarintPut64(&aData[iOff], iKeyPg); }else{ aData[iOff++] = eType; - iOff += lsmVarintPut32(&aData[iOff], (int)iPtr); + iOff += lsmVarintPut64(&aData[iOff], iPtr); iOff += lsmVarintPut32(&aData[iOff], nKey); memcpy(&aData[iOff], pKey, nKey); } @@ -3872,7 +3875,7 @@ static int mergeWorkerNextPage( static int mergeWorkerData( MergeWorker *pMW, /* Merge worker object */ int bSep, /* True to write to separators run */ - int iFPtr, /* Footer ptr for new pages */ + LsmPgno iFPtr, /* Footer ptr for new pages */ u8 *aWrite, /* Write data from this buffer */ int nWrite /* Size of aWrite[] in bytes */ ){ @@ -3916,14 +3919,14 @@ static int mergeWorkerData( static int mergeWorkerFirstPage(MergeWorker *pMW){ int rc = LSM_OK; /* Return code */ Page *pPg = 0; /* First page of run pSeg */ - int iFPtr = 0; /* Pointer value read from footer of pPg */ + LsmPgno iFPtr = 0; /* Pointer value read from footer of pPg */ MultiCursor *pCsr = pMW->pCsr; assert( pMW->pPage==0 ); if( pCsr->pBtCsr ){ rc = LSM_OK; - iFPtr = (int)pMW->pLevel->pNext->lhs.iFirst; + iFPtr = pMW->pLevel->pNext->lhs.iFirst; }else if( pCsr->nPtr>0 ){ Segment *pSeg; pSeg = pCsr->aPtr[pCsr->nPtr-1].pSeg; @@ -3932,7 +3935,7 @@ static int mergeWorkerFirstPage(MergeWorker *pMW){ u8 *aData; /* Buffer for page pPg */ int nData; /* Size of aData[] in bytes */ aData = fsPageData(pPg, &nData); - iFPtr = (int)pageGetPtr(aData, nData); + iFPtr = pageGetPtr(aData, nData); lsmFsPageRelease(pPg); } } @@ -3951,7 +3954,7 @@ static int mergeWorkerWrite( int eType, /* One of SORTED_SEPARATOR, WRITE or DELETE */ void *pKey, int nKey, /* Key value */ void *pVal, int nVal, /* Value value */ - int iPtr /* Absolute value of page pointer, or 0 */ + LsmPgno iPtr /* Absolute value of page pointer, or 0 */ ){ int rc = LSM_OK; /* Return code */ Merge *pMerge; /* Persistent part of level merge state */ @@ -3960,8 +3963,8 @@ static int mergeWorkerWrite( u8 *aData; /* Data buffer for page pWriter->pPage */ int nData = 0; /* Size of buffer aData[] in bytes */ int nRec = 0; /* Number of records on page pPg */ - int iFPtr = 0; /* Value of pointer in footer of pPg */ - int iRPtr = 0; /* Value of pointer written into record */ + LsmPgno iFPtr = 0; /* Value of pointer in footer of pPg */ + LsmPgno iRPtr = 0; /* Value of pointer written into record */ int iOff = 0; /* Current write offset within page pPg */ Segment *pSeg; /* Segment being written */ int flags = 0; /* If != 0, flags value for page footer */ @@ -3978,8 +3981,8 @@ static int mergeWorkerWrite( if( pPg ){ aData = fsPageData(pPg, &nData); nRec = pageGetNRec(aData, nData); - iFPtr = (int)pageGetPtr(aData, nData); - iRPtr = iPtr - iFPtr; + iFPtr = pageGetPtr(aData, nData); + iRPtr = iPtr ? (iPtr - iFPtr) : 0; } /* Figure out how much space is required by the new record. The space @@ -3997,7 +4000,7 @@ static int mergeWorkerWrite( ** 4) Value size - 1 varint (only if LSM_INSERT flag is set) */ if( rc==LSM_OK ){ - nHdr = 1 + lsmVarintLen32(iRPtr) + lsmVarintLen32(nKey); + nHdr = 1 + lsmVarintLen64(iRPtr) + lsmVarintLen32(nKey); if( rtIsWrite(eType) ) nHdr += lsmVarintLen32(nVal); /* If the entire header will not fit on page pPg, or if page pPg is @@ -4009,8 +4012,8 @@ static int mergeWorkerWrite( assert( aData ); memset(&aData[iOff], 0, SEGMENT_EOF(nData, nRec)-iOff); } - iFPtr = (int)*pMW->pCsr->pPrevMergePtr; - iRPtr = iPtr - iFPtr; + iFPtr = *pMW->pCsr->pPrevMergePtr; + iRPtr = iPtr ? (iPtr - iFPtr) : 0; iOff = 0; nRec = 0; rc = mergeWorkerNextPage(pMW, iFPtr); @@ -4049,7 +4052,7 @@ static int mergeWorkerWrite( /* Write the entry header into the current page. */ aData[iOff++] = (u8)eType; /* 1 */ - iOff += lsmVarintPut32(&aData[iOff], iRPtr); /* 2 */ + iOff += lsmVarintPut64(&aData[iOff], iRPtr); /* 2 */ iOff += lsmVarintPut32(&aData[iOff], nKey); /* 3 */ if( rtIsWrite(eType) ) iOff += lsmVarintPut32(&aData[iOff], nVal); /* 4 */ pMerge->iOutputOff = iOff; @@ -4283,7 +4286,7 @@ static int mergeWorkerStep(MergeWorker *pMW){ pVal = pCsr->val.pData; } if( rc==LSM_OK ){ - rc = mergeWorkerWrite(pMW, eType, pKey, nKey, pVal, nVal, (int)iPtr); + rc = mergeWorkerWrite(pMW, eType, pKey, nKey, pVal, nVal, iPtr); } } } @@ -4604,7 +4607,7 @@ static int mergeWorkerInit( SegmentPtr *pPtr; assert( pCsr->aPtr[i].pPg==0 ); pPtr = &pCsr->aPtr[i]; - rc = segmentPtrLoadPage(pDb->pFS, pPtr, (int)pInput->iPg); + rc = segmentPtrLoadPage(pDb->pFS, pPtr, pInput->iPg); if( rc==LSM_OK && pPtr->nCell>0 ){ rc = segmentPtrLoadCell(pPtr, pInput->iCell); } @@ -5469,7 +5472,7 @@ int lsmFlushTreeToDisk(lsm_db *pDb){ ** be freed by the caller using lsmFree(). */ static char *segToString(lsm_env *pEnv, Segment *pSeg, int nMin){ - int nSize = pSeg->nSize; + LsmPgno nSize = pSeg->nSize; LsmPgno iRoot = pSeg->iRoot; LsmPgno iFirst = pSeg->iFirst; LsmPgno iLast = pSeg->iLastPg; @@ -5481,9 +5484,9 @@ static char *segToString(lsm_env *pEnv, Segment *pSeg, int nMin){ z1 = lsmMallocPrintf(pEnv, "%d.%d", iFirst, iLast); if( iRoot ){ - z2 = lsmMallocPrintf(pEnv, "root=%d", iRoot); + z2 = lsmMallocPrintf(pEnv, "root=%lld", iRoot); }else{ - z2 = lsmMallocPrintf(pEnv, "size=%d", nSize); + z2 = lsmMallocPrintf(pEnv, "size=%lld", nSize); } nPad = nMin - 2 - strlen(z1) - 1 - strlen(z2); @@ -5536,7 +5539,7 @@ void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ int i; int nRec; - int iPtr; + LsmPgno iPtr; int flags; u8 *aData; int nData; @@ -5544,11 +5547,11 @@ void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ aData = fsPageData(pPg, &nData); nRec = pageGetNRec(aData, nData); - iPtr = (int)pageGetPtr(aData, nData); + iPtr = pageGetPtr(aData, nData); flags = pageGetFlags(aData, nData); lsmStringInit(&s, pDb->pEnv); - lsmStringAppendf(&s,"nCell=%d iPtr=%d flags=%d {", nRec, iPtr, flags); + lsmStringAppendf(&s,"nCell=%d iPtr=%lld flags=%d {", nRec, iPtr, flags); if( flags&SEGMENT_BTREE_FLAG ) iPtr = 0; for(i=0; ipEnv); lsmStringAppendf(&str, "Page : %lld (%d bytes)\n", iPg, nData); lsmStringAppendf(&str, "nRec : %d\n", nRec); - lsmStringAppendf(&str, "iPtr : %d\n", iPtr); + lsmStringAppendf(&str, "iPtr : %lld\n", iPtr); lsmStringAppendf(&str, "flags: %04x\n", flags2); lsmStringAppendf(&str, "\n"); diff --git a/ext/lsm1/lsm_varint.c b/ext/lsm1/lsm_varint.c index c550a6405..f690e3b06 100644 --- a/ext/lsm1/lsm_varint.c +++ b/ext/lsm1/lsm_varint.c @@ -185,6 +185,11 @@ int lsmVarintLen32(int n){ return lsmVarintPut32(aData, n); } +int lsmVarintLen64(i64 n){ + u8 aData[9]; + return lsmVarintPut64(aData, n); +} + /* ** The argument is the first byte of a varint. This function returns the ** total number of bytes in the entire varint (including the first byte). diff --git a/ext/misc/base64.c b/ext/misc/base64.c new file mode 100755 index 000000000..69eff6181 --- /dev/null +++ b/ext/misc/base64.c @@ -0,0 +1,277 @@ +/* +** 2022-11-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 is a SQLite extension for converting in either direction +** between a (binary) blob and base64 text. Base64 can transit a +** sane USASCII channel unmolested. It also plays nicely in CSV or +** written as TCL brace-enclosed literals or SQL string literals, +** and can be used unmodified in XML-like documents. +** +** This is an independent implementation of conversions specified in +** RFC 4648, done on the above date by the author (Larry Brasfield) +** who thereby has the right to put this into the public domain. +** +** The conversions meet RFC 4648 requirements, provided that this +** C source specifies that line-feeds are included in the encoded +** data to limit visible line lengths to 72 characters and to +** terminate any encoded blob having non-zero length. +** +** Length limitations are not imposed except that the runtime +** SQLite string or blob length limits are respected. Otherwise, +** any length binary sequence can be represented and recovered. +** Generated base64 sequences, with their line-feeds included, +** can be concatenated; the result converted back to binary will +** be the concatenation of the represented binary sequences. +** +** This SQLite3 extension creates a function, base64(x), which +** either: converts text x containing base64 to a returned blob; +** or converts a blob x to returned text containing base64. An +** error will be thrown for other input argument types. +** +** This code relies on UTF-8 encoding only with respect to the +** meaning of the first 128 (7-bit) codes matching that of USASCII. +** It will fail miserably if somehow made to try to convert EBCDIC. +** Because it is table-driven, it could be enhanced to handle that, +** but the world and SQLite have moved on from that anachronism. +** +** To build the extension: +** Set shell variable SQDIR= +** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c +** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c +** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c +** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll +*/ + +#include + +#include "sqlite3ext.h" + +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif + +SQLITE_EXTENSION_INIT1; + +#define PC 0x80 /* pad character */ +#define WS 0x81 /* whitespace */ +#define ND 0x82 /* Not above or digit-value */ +#define PAD_CHAR '=' + +#ifndef U8_TYPEDEF +typedef unsigned char u8; +#define U8_TYPEDEF +#endif + +static const u8 b64DigitValues[128] = { + /* HT LF VT FF CR */ + ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND, + /* US */ + ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, + /*sp + / */ + WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63, + /* 0 1 5 9 = */ + 52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND, + /* A O */ + ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + /* P Z */ + 15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND, + /* a o */ + ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + /* p z */ + 41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND +}; + +static const char b64Numerals[64+1] += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define BX_DV_PROTO(c) \ + ((((u8)(c))<0x80)? (u8)(b64DigitValues[(u8)(c)]) : 0x80) +#define IS_BX_DIGIT(bdp) (((u8)(bdp))<0x80) +#define IS_BX_WS(bdp) ((bdp)==WS) +#define IS_BX_PAD(bdp) ((bdp)==PC) +#define BX_NUMERAL(dv) (b64Numerals[(u8)(dv)]) +/* Width of base64 lines. Should be an integer multiple of 4. */ +#define B64_DARK_MAX 72 + +/* Encode a byte buffer into base64 text with linefeeds appended to limit +** encoded group lengths to B64_DARK_MAX or to terminate the last group. +*/ +static char* toBase64( u8 *pIn, int nbIn, char *pOut ){ + int nCol = 0; + while( nbIn >= 3 ){ + /* Do the bit-shuffle, exploiting unsigned input to avoid masking. */ + pOut[0] = BX_NUMERAL(pIn[0]>>2); + pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f); + pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6)); + pOut[3] = BX_NUMERAL(pIn[2]&0x3f); + pOut += 4; + nbIn -= 3; + pIn += 3; + if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){ + *pOut++ = '\n'; + nCol = 0; + } + } + if( nbIn > 0 ){ + signed char nco = nbIn+1; + int nbe; + unsigned long qv = *pIn++; + for( nbe=1; nbe<3; ++nbe ){ + qv <<= 8; + if( nbe=0; --nbe ){ + char ce = (nbe>= 6; + pOut[nbe] = ce; + } + pOut += 4; + *pOut++ = '\n'; + } + *pOut = 0; + return pOut; +} + +/* Skip over text which is not base64 numeral(s). */ +static char * skipNonB64( char *s ){ + char c; + while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; + return s; +} + +/* Decode base64 text into a byte buffer. */ +static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){ + if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; + while( ncIn>0 && *pIn!=PAD_CHAR ){ + static signed char nboi[] = { 0, 0, 1, 2, 3 }; + char *pUse = skipNonB64(pIn); + unsigned long qv = 0L; + int nti, nbo, nac; + ncIn -= (pUse - pIn); + pIn = pUse; + nti = (ncIn>4)? 4 : ncIn; + ncIn -= nti; + nbo = nboi[nti]; + if( nbo==0 ) break; + for( nac=0; nac<4; ++nac ){ + char c = (nac>8) & 0xff; + case 1: + pOut[0] = (qv>>16) & 0xff; + } + pOut += nbo; + } + return pOut; +} + +/* This function does the work for the SQLite base64(x) UDF. */ +static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ + int nb, nc, nv = sqlite3_value_bytes(av[0]); + int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), + SQLITE_LIMIT_LENGTH, -1); + char *cBuf; + u8 *bBuf; + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_BLOB: + nb = nv; + nc = 4*(nv+2/3); /* quads needed */ + nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ + if( nvMax < nc ){ + sqlite3_result_error(context, "blob expanded to base64 too big", -1); + return; + } + cBuf = sqlite3_malloc(nc); + if( !cBuf ) goto memFail; + bBuf = (u8*)sqlite3_value_blob(av[0]); + nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf); + sqlite3_result_text(context, cBuf, nc, sqlite3_free); + break; + case SQLITE_TEXT: + nc = nv; + nb = 3*((nv+3)/4); /* may overestimate due to LF and padding */ + if( nvMax < nb ){ + sqlite3_result_error(context, "blob from base64 may be too big", -1); + return; + }else if( nb<1 ){ + nb = 1; + } + bBuf = sqlite3_malloc(nb); + if( !bBuf ) goto memFail; + cBuf = (char *)sqlite3_value_text(av[0]); + nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf); + sqlite3_result_blob(context, bBuf, nb, sqlite3_free); + break; + default: + sqlite3_result_error(context, "base64 accepts only blob or text", -1); + return; + } + return; + memFail: + sqlite3_result_error(context, "base64 OOM", -1); +} + +/* +** Establish linkage to running SQLite library. +*/ +#ifndef SQLITE_SHELL_EXTFUNCS +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_base_init +#else +static int sqlite3_base64_init +#endif +(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErr; + return sqlite3_create_function + (db, "base64", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, + 0, base64, 0, 0); +} + +/* +** Define some macros to allow this extension to be built into the shell +** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This +** allows shell.c, as distributed, to have this extension built in. +*/ +#define BASE64_INIT(db) sqlite3_base64_init(db, 0, 0) +#define BASE64_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ diff --git a/ext/misc/base85.c b/ext/misc/base85.c new file mode 100644 index 000000000..5ec136dbc --- /dev/null +++ b/ext/misc/base85.c @@ -0,0 +1,436 @@ +/* +** 2022-11-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 is a utility for converting binary to base85 or vice-versa. +** It can be built as a standalone program or an SQLite3 extension. +** +** Much like base64 representations, base85 can be sent through a +** sane USASCII channel unmolested. It also plays nicely in CSV or +** written as TCL brace-enclosed literals or SQL string literals. +** It is not suited for unmodified use in XML-like documents. +** +** The encoding used resembles Ascii85, but was devised by the author +** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85 +** variant sources existed, in the 1984 timeframe on a VAX mainframe. +** Further, this is an independent implementation of a base85 system. +** Hence, the author has rightfully put this into the public domain. +** +** Base85 numerals are taken from the set of 7-bit USASCII codes, +** excluding control characters and Space ! " ' ( ) { | } ~ Del +** in code order representing digit values 0 to 84 (base 10.) +** +** Groups of 4 bytes, interpreted as big-endian 32-bit values, +** are represented as 5-digit base85 numbers with MS to LS digit +** order. Groups of 1-3 bytes are represented with 2-4 digits, +** still big-endian but 8-24 bit values. (Using big-endian yields +** the simplest transition to byte groups smaller than 4 bytes. +** These byte groups can also be considered base-256 numbers.) +** Groups of 0 bytes are represented with 0 digits and vice-versa. +** No pad characters are used; Encoded base85 numeral sequence +** (aka "group") length maps 1-to-1 to the decoded binary length. +** +** Any character not in the base85 numeral set delimits groups. +** When base85 is streamed or stored in containers of indefinite +** size, newline is used to separate it into sub-sequences of no +** more than 80 digits so that fgets() can be used to read it. +** +** Length limitations are not imposed except that the runtime +** SQLite string or blob length limits are respected. Otherwise, +** any length binary sequence can be represented and recovered. +** Base85 sequences can be concatenated by separating them with +** a non-base85 character; the conversion to binary will then +** be the concatenation of the represented binary sequences. + +** The standalone program either converts base85 on stdin to create +** a binary file or converts a binary file to base85 on stdout. +** Read or make it blurt its help for invocation details. +** +** The SQLite3 extension creates a function, base85(x), which will +** either convert text base85 to a blob or a blob to text base85 +** and return the result (or throw an error for other types.) +** Unless built with OMIT_BASE85_CHECKER defined, it also creates a +** function, is_base85(t), which returns 1 iff the text t contains +** nothing other than base85 numerals and whitespace, or 0 otherwise. +** +** To build the extension: +** Set shell variable SQDIR= +** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted. +** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c +** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c +** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c +** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll +** +** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg. +** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85 +** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c +** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c +*/ + +#include +#include +#include +#include +#ifndef OMIT_BASE85_CHECKER +# include +#endif + +#ifndef BASE85_STANDALONE + +# include "sqlite3ext.h" + +SQLITE_EXTENSION_INIT1; + +#else + +# ifdef _WIN32 +# include +# include +# else +# define setmode(fd,m) +# endif + +static char *zHelp = + "Usage: base85 \n" + " is either -r to read or -w to write ,\n" + " content to be converted to/from base85 on stdout/stdin.\n" + " names a binary file to be rendered or created.\n" + " Or, the name '-' refers to the stdin or stdout stream.\n" + ; + +static void sayHelp(){ + printf("%s", zHelp); +} +#endif + +#ifndef U8_TYPEDEF +typedef unsigned char u8; +#define U8_TYPEDEF +#endif + +/* Classify c according to interval within USASCII set w.r.t. base85 + * Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not. + */ +#define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z')) + +/* Provide digitValue to b85Numeral offset as a function of above class. */ +static u8 b85_cOffset[] = { 0, '#', 0, '*'-4, 0 }; +#define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)] + +/* Say whether c is a base85 numeral. */ +#define IS_B85( c ) (B85_CLASS(c) & 1) + +#if 0 /* Not used, */ +static u8 base85DigitValue( char c ){ + u8 dv = (u8)(c - '#'); + if( dv>87 ) return 0xff; + return (dv > 3)? dv-3 : dv; +} +#endif + +/* Width of base64 lines. Should be an integer multiple of 5. */ +#define B85_DARK_MAX 80 + + +static char * skipNonB85( char *s ){ + char c; + while( (c = *s) && !IS_B85(c) ) ++s; + return s; +} + +/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral. + * Do not use the macro form with argument expression having a side-effect.*/ +#if 0 +static char base85Numeral( u8 b ){ + return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*'); +} +#else +# define base85Numeral( dn )\ + ((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*'))) +#endif + +static char *putcs(char *pc, char *s){ + char c; + while( (c = *s++)!=0 ) *pc++ = c; + return pc; +} + +/* Encode a byte buffer into base85 text. If pSep!=0, it's a C string +** to be appended to encoded groups to limit their length to B85_DARK_MAX +** or to terminate the last group (to aid concatenation.) +*/ +static char* toBase85( u8 *pIn, int nbIn, char *pOut, char *pSep ){ + int nCol = 0; + while( nbIn >= 4 ){ + int nco = 5; + unsigned long qbv = (((unsigned long)pIn[0])<<24) | + (pIn[1]<<16) | (pIn[2]<<8) | pIn[3]; + while( nco > 0 ){ + unsigned nqv = (unsigned)(qbv/85UL); + unsigned char dv = qbv - 85UL*nqv; + qbv = nqv; + pOut[--nco] = base85Numeral(dv); + } + nbIn -= 4; + pIn += 4; + pOut += 5; + if( pSep && (nCol += 5)>=B85_DARK_MAX ){ + pOut = putcs(pOut, pSep); + nCol = 0; + } + } + if( nbIn > 0 ){ + int nco = nbIn + 1; + unsigned long qv = *pIn++; + int nbe = 1; + while( nbe++ < nbIn ){ + qv = (qv<<8) | *pIn++; + } + nCol += nco; + while( nco > 0 ){ + u8 dv = (u8)(qv % 85); + qv /= 85; + pOut[--nco] = base85Numeral(dv); + } + pOut += (nbIn+1); + } + if( pSep && nCol>0 ) pOut = putcs(pOut, pSep); + *pOut = 0; + return pOut; +} + +/* Decode base85 text into a byte buffer. */ +static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){ + if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; + while( ncIn>0 ){ + static signed char nboi[] = { 0, 0, 1, 2, 3, 4 }; + char *pUse = skipNonB85(pIn); + unsigned long qv = 0L; + int nti, nbo; + ncIn -= (pUse - pIn); + pIn = pUse; + nti = (ncIn>5)? 5 : ncIn; + nbo = nboi[nti]; + if( nbo==0 ) break; + while( nti>0 ){ + char c = *pIn++; + u8 cdo = B85_DNOS(c); + --ncIn; + if( cdo==0 ) break; + qv = 85 * qv + (c - cdo); + --nti; + } + nbo -= nti; /* Adjust for early (non-digit) end of group. */ + switch( nbo ){ + case 4: + *pOut++ = (qv >> 24)&0xff; + case 3: + *pOut++ = (qv >> 16)&0xff; + case 2: + *pOut++ = (qv >> 8)&0xff; + case 1: + *pOut++ = qv&0xff; + case 0: + break; + } + } + return pOut; +} + +#ifndef OMIT_BASE85_CHECKER +/* Say whether input char sequence is all (base85 and/or whitespace).*/ +static int allBase85( char *p, int len ){ + char c; + while( len-- > 0 && (c = *p++) != 0 ){ + if( !IS_B85(c) && !isspace(c) ) return 0; + } + return 1; +} +#endif + +#ifndef BASE85_STANDALONE + +# ifndef OMIT_BASE85_CHECKER +/* This function does the work for the SQLite is_base85(t) UDF. */ +static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){ + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_TEXT: + { + int rv = allBase85( (char *)sqlite3_value_text(av[0]), + sqlite3_value_bytes(av[0]) ); + sqlite3_result_int(context, rv); + } + break; + case SQLITE_NULL: + sqlite3_result_null(context); + break; + default: + sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1); + return; + } +} +# endif + +/* This function does the work for the SQLite base85(x) UDF. */ +static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ + int nb, nc, nv = sqlite3_value_bytes(av[0]); + int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), + SQLITE_LIMIT_LENGTH, -1); + char *cBuf; + u8 *bBuf; + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_BLOB: + nb = nv; + /* ulongs tail newlines tailenc+nul*/ + nc = 5*(nv/4) + nv%4 + nv/64+1 + 2; + if( nvMax < nc ){ + sqlite3_result_error(context, "blob expanded to base85 too big", -1); + return; + } + cBuf = sqlite3_malloc(nc); + if( !cBuf ) goto memFail; + bBuf = (u8*)sqlite3_value_blob(av[0]); + nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf); + sqlite3_result_text(context, cBuf, nc, sqlite3_free); + break; + case SQLITE_TEXT: + nc = nv; + nb = 4*(nv/5) + nv%5; /* may overestimate */ + if( nvMax < nb ){ + sqlite3_result_error(context, "blob from base85 may be too big", -1); + return; + }else if( nb<1 ){ + nb = 1; + } + bBuf = sqlite3_malloc(nb); + if( !bBuf ) goto memFail; + cBuf = (char *)sqlite3_value_text(av[0]); + nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf); + sqlite3_result_blob(context, bBuf, nb, sqlite3_free); + break; + default: + sqlite3_result_error(context, "base85 accepts only blob or text.", -1); + return; + } + return; + memFail: + sqlite3_result_error(context, "base85 OOM", -1); +} + +/* +** Establish linkage to running SQLite library. +*/ +#ifndef SQLITE_SHELL_EXTFUNCS +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_base_init +#else +static int sqlite3_base85_init +#endif +(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErr; +# ifndef OMIT_BASE85_CHECKER + { + int rc = sqlite3_create_function + (db, "is_base85", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8, + 0, is_base85, 0, 0); + if( rc!=SQLITE_OK ) return rc; + } +# endif + return sqlite3_create_function + (db, "base85", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, + 0, base85, 0, 0); +} + +/* +** Define some macros to allow this extension to be built into the shell +** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This +** allows shell.c, as distributed, to have this extension built in. +*/ +# define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0) +# define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ + +#else /* standalone program */ + +int main(int na, char *av[]){ + int cin; + int rc = 0; + u8 bBuf[4*(B85_DARK_MAX/5)]; + char cBuf[5*(sizeof(bBuf)/4)+2]; + size_t nio; +# ifndef OMIT_BASE85_CHECKER + int b85Clean = 1; +# endif + char rw; + FILE *fb = 0, *foc = 0; + char fmode[3] = "xb"; + if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){ + sayHelp(); + return 0; + } + fmode[0] = rw; + if( av[2][0]=='-' && av[2][1]==0 ){ + switch( rw ){ + case 'r': + fb = stdin; + setmode(fileno(stdin), O_BINARY); + break; + case 'w': + fb = stdout; + setmode(fileno(stdout), O_BINARY); + break; + } + }else{ + fb = fopen(av[2], fmode); + foc = fb; + } + if( !fb ){ + fprintf(stderr, "Cannot open %s for %c\n", av[2], rw); + rc = 1; + }else{ + switch( rw ){ + case 'r': + while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){ + toBase85( bBuf, (int)nio, cBuf, 0 ); + fprintf(stdout, "%s\n", cBuf); + } + break; + case 'w': + while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){ + int nc = strlen(cBuf); + size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf; + if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1; +# ifndef OMIT_BASE85_CHECKER + b85Clean &= allBase85( cBuf, nc ); +# endif + } + break; + default: + sayHelp(); + rc = 1; + } + if( foc ) fclose(foc); + } +# ifndef OMIT_BASE85_CHECKER + if( !b85Clean ){ + fprintf(stderr, "Base85 input had non-base85 dark or control content.\n"); + } +# endif + return rc; +} + +#endif diff --git a/ext/misc/basexx.c b/ext/misc/basexx.c new file mode 100644 index 000000000..c1808aa70 --- /dev/null +++ b/ext/misc/basexx.c @@ -0,0 +1,86 @@ +/* +** 2022-11-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 source allows multiple SQLite extensions to be either: combined +** into a single runtime-loadable library; or built into the SQLite shell +** using a preprocessing convention set by src/shell.c.in (and shell.c). +** +** Presently, it combines the base64.c and base85.c extensions. However, +** it can be used as a template for other combinations. +** +** Example usages: +** +** - Build a runtime-loadable extension from SQLite checkout directory: +** *Nix, OSX: gcc -O2 -shared -I. -fPIC -o basexx.so ext/misc/basexx.c +** Win32: cl /Os -I. ext/misc/basexx.c -link -dll -out:basexx.dll +** +** - Incorporate as built-in in sqlite3 shell: +** *Nix, OSX with gcc on a like platform: +** export mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c +** export mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX +** make sqlite3 "OPTS=$mop1 $mop2" +** Win32 with Microsoft toolset on Windows: +** set mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c +** set mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX +** set mops="OPTS=%mop1% %mop2%" +** nmake -f Makefile.msc sqlite3.exe %mops% +*/ + +#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */ +# include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1; +#endif + +static void init_api_ptr(const sqlite3_api_routines *pApi){ + SQLITE_EXTENSION_INIT2(pApi); +} + +#undef SQLITE_EXTENSION_INIT1 +#define SQLITE_EXTENSION_INIT1 /* */ +#undef SQLITE_EXTENSION_INIT2 +#define SQLITE_EXTENSION_INIT2(v) (void)v + +typedef unsigned char u8; +#define U8_TYPEDEF + +/* These next 2 undef's are only needed because the entry point names + * collide when formulated per the rules stated for loadable extension + * entry point names that will be deduced from the file basenames. + */ +#undef sqlite3_base_init +#define sqlite3_base_init sqlite3_base64_init +#include "base64.c" + +#undef sqlite3_base_init +#define sqlite3_base_init sqlite3_base85_init +#include "base85.c" + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_basexx_init(sqlite3 *db, char **pzErr, + const sqlite3_api_routines *pApi){ + init_api_ptr(pApi); + int rc1 = BASE64_INIT(db); + int rc2 = BASE85_INIT(db); + + if( rc1==SQLITE_OK && rc2==SQLITE_OK ){ + BASE64_EXPOSE(db, pzErr); + BASE64_EXPOSE(db, pzErr); + return SQLITE_OK; + }else{ + return SQLITE_ERROR; + } +} + +# define BASEXX_INIT(db) sqlite3_basexx_init(db, 0, 0) +# define BASEXX_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ diff --git a/ext/misc/carray.c b/ext/misc/carray.c index b6eb0453a..709c894f2 100644 --- a/ext/misc/carray.c +++ b/ext/misc/carray.c @@ -28,7 +28,7 @@ ** ** There is an optional third parameter to determine the datatype of ** the C-language array. Allowed values of the third parameter are -** 'int32', 'int64', 'double', 'char*'. Example: +** 'int32', 'int64', 'double', 'char*', 'struct iovec'. Example: ** ** SELECT * FROM carray($ptr,10,'char*'); ** @@ -56,6 +56,14 @@ SQLITE_EXTENSION_INIT1 #include #include +#ifdef _WIN32 + struct iovec { + void *iov_base; + size_t iov_len; + }; +#else +# include +#endif /* Allowed values for the mFlags parameter to sqlite3_carray_bind(). ** Must exactly match the definitions in carray.h. @@ -65,6 +73,7 @@ SQLITE_EXTENSION_INIT1 # define CARRAY_INT64 1 /* Data is 64-bit signed integers */ # define CARRAY_DOUBLE 2 /* Data is doubles */ # define CARRAY_TEXT 3 /* Data is char* */ +# define CARRAY_BLOB 4 /* Data is struct iovec* */ #endif #ifndef SQLITE_API @@ -80,7 +89,8 @@ SQLITE_EXTENSION_INIT1 /* ** Names of allowed datatypes */ -static const char *azType[] = { "int32", "int64", "double", "char*" }; +static const char *azType[] = { "int32", "int64", "double", "char*", + "struct iovec" }; /* ** Structure used to hold the sqlite3_carray_bind() information @@ -224,6 +234,12 @@ static int carrayColumn( sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT); return SQLITE_OK; } + case CARRAY_BLOB: { + const struct iovec *p = (struct iovec*)pCur->pPtr; + sqlite3_result_blob(ctx, p[pCur->iRowid-1].iov_base, + (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT); + return SQLITE_OK; + } } } } @@ -268,7 +284,7 @@ static int carrayFilter( if( pBind==0 ) break; pCur->pPtr = pBind->aData; pCur->iCnt = pBind->nData; - pCur->eType = pBind->mFlags & 0x03; + pCur->eType = pBind->mFlags & 0x07; break; } case 2: @@ -431,24 +447,29 @@ SQLITE_API int sqlite3_carray_bind( pNew->mFlags = mFlags; if( xDestroy==SQLITE_TRANSIENT ){ sqlite3_int64 sz = nData; - switch( mFlags & 0x03 ){ - case CARRAY_INT32: sz *= 4; break; - case CARRAY_INT64: sz *= 8; break; - case CARRAY_DOUBLE: sz *= 8; break; - case CARRAY_TEXT: sz *= sizeof(char*); break; + switch( mFlags & 0x07 ){ + case CARRAY_INT32: sz *= 4; break; + case CARRAY_INT64: sz *= 8; break; + case CARRAY_DOUBLE: sz *= 8; break; + case CARRAY_TEXT: sz *= sizeof(char*); break; + case CARRAY_BLOB: sz *= sizeof(struct iovec); break; } - if( (mFlags & 0x03)==CARRAY_TEXT ){ + if( (mFlags & 0x07)==CARRAY_TEXT ){ for(i=0; iaData = sqlite3_malloc64( sz ); if( pNew->aData==0 ){ sqlite3_free(pNew); return SQLITE_NOMEM; } - if( (mFlags & 0x03)==CARRAY_TEXT ){ + if( (mFlags & 0x07)==CARRAY_TEXT ){ char **az = (char**)pNew->aData; char *z = (char*)&az[nData]; for(i=0; iaData; + unsigned char *z = (unsigned char*)&p[nData]; + for(i=0; iaData, aData, sz); } diff --git a/ext/misc/carray.h b/ext/misc/carray.h index 63df0066b..ae0a9e8ab 100644 --- a/ext/misc/carray.h +++ b/ext/misc/carray.h @@ -41,10 +41,10 @@ SQLITE_API int sqlite3_carray_bind( #define CARRAY_INT64 1 /* Data is 64-bit signed integers */ #define CARRAY_DOUBLE 2 /* Data is doubles */ #define CARRAY_TEXT 3 /* Data is char* */ +#define CARRAY_BLOB 4 /* Data is struct iovec */ #ifdef __cplusplus } /* end of the 'extern "C"' block */ #endif #endif /* ifndef _CARRAY_H */ - diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index 37c6c2f52..4495cae46 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -616,7 +616,7 @@ int sqlite3_decimal_init( SQLITE_EXTENSION_INIT2(pApi); - for(i=0; ipRow; for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){ @@ -271,6 +279,7 @@ static int stmtBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ + (void)tab; pIdxInfo->estimatedCost = (double)500; pIdxInfo->estimatedRows = 500; return SQLITE_OK; diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index f818fbc11..480fbe399 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -352,6 +352,7 @@ static int zipfileConnect( const char *zFile = 0; ZipfileTab *pNew = 0; int rc; + (void)pAux; /* If the table name is not "zipfile", require that the argument be ** specified. This stops zipfile tables from being created as: @@ -808,6 +809,7 @@ static int zipfileGetEntry( u8 *aRead; char **pzErr = &pTab->base.zErrMsg; int rc = SQLITE_OK; + (void)nBlob; if( aBlob==0 ){ aRead = pTab->aBuffer; @@ -1254,6 +1256,9 @@ static int zipfileFilter( int rc = SQLITE_OK; /* Return Code */ int bInMemory = 0; /* True for an in-memory zipfile */ + (void)idxStr; + (void)argc; + zipfileResetCursor(pCsr); if( pTab->zFile ){ @@ -1280,7 +1285,7 @@ static int zipfileFilter( } if( 0==pTab->pWriteFd && 0==bInMemory ){ - pCsr->pFile = fopen(zFile, "rb"); + pCsr->pFile = zFile ? fopen(zFile, "rb") : 0; if( pCsr->pFile==0 ){ zipfileCursorErr(pCsr, "cannot open file: %s", zFile); rc = SQLITE_ERROR; @@ -1314,6 +1319,7 @@ static int zipfileBestIndex( int i; int idx = -1; int unusable = 0; + (void)tab; for(i=0; inConstraint; i++){ const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; @@ -1564,6 +1570,8 @@ static int zipfileUpdate( int bIsDir = 0; u32 iCrc32 = 0; + (void)pRowid; + if( pTab->pWriteFd==0 ){ rc = zipfileBegin(pVtab); if( rc!=SQLITE_OK ) return rc; @@ -1898,6 +1906,7 @@ static int zipfileFindFunction( void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ void **ppArg /* OUT: User data for *pxFunc */ ){ + (void)nArg; if( sqlite3_stricmp("zipfile_cds", zName)==0 ){ *pxFunc = zipfileFunctionCds; *ppArg = (void*)pVtab; diff --git a/ext/rbu/rbu1.test b/ext/rbu/rbu1.test index 9237dbcea..418857d8b 100644 --- a/ext/rbu/rbu1.test +++ b/ext/rbu/rbu1.test @@ -11,11 +11,10 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu1 db close -sqlite3_shutdown -sqlite3_config_uri 1 # Create a simple RBU database. That expects to write to a table: # diff --git a/ext/rbu/rbu10.test b/ext/rbu/rbu10.test index aa4db8a29..8705c7b2e 100644 --- a/ext/rbu/rbu10.test +++ b/ext/rbu/rbu10.test @@ -10,13 +10,10 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu10 - #-------------------------------------------------------------------- # Test that UPDATE commands work even if the input columns are in a # different order to the output columns. diff --git a/ext/rbu/rbu11.test b/ext/rbu/rbu11.test index 5a4219b00..a42163cce 100644 --- a/ext/rbu/rbu11.test +++ b/ext/rbu/rbu11.test @@ -10,10 +10,8 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu11 diff --git a/ext/rbu/rbu12.test b/ext/rbu/rbu12.test index 7816bfe89..a753ef249 100644 --- a/ext/rbu/rbu12.test +++ b/ext/rbu/rbu12.test @@ -10,10 +10,8 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } source $testdir/lock_common.tcl set ::testprefix rbu12 diff --git a/ext/rbu/rbu13.test b/ext/rbu/rbu13.test index 624c587cc..9bf2adc6f 100644 --- a/ext/rbu/rbu13.test +++ b/ext/rbu/rbu13.test @@ -13,10 +13,8 @@ # for UPDATE statements. This tests RBU's internal UPDATE statement cache. # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } source $testdir/lock_common.tcl set ::testprefix rbu13 diff --git a/ext/rbu/rbu14.test b/ext/rbu/rbu14.test index 07f678409..ac5b92c6c 100644 --- a/ext/rbu/rbu14.test +++ b/ext/rbu/rbu14.test @@ -13,10 +13,8 @@ # table. # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } source $testdir/lock_common.tcl set ::testprefix rbu14 diff --git a/ext/rbu/rbu3.test b/ext/rbu/rbu3.test index da87561fb..4ea6adbcd 100644 --- a/ext/rbu/rbu3.test +++ b/ext/rbu/rbu3.test @@ -10,10 +10,8 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu3 @@ -26,9 +24,6 @@ proc run_rbu {target rbu} { } forcedelete test.db-oal rbu.db -db close -sqlite3_shutdown -sqlite3_config_uri 1 reset_db #-------------------------------------------------------------------- diff --git a/ext/rbu/rbu5.test b/ext/rbu/rbu5.test index 3696dfb87..2ae69e965 100644 --- a/ext/rbu/rbu5.test +++ b/ext/rbu/rbu5.test @@ -13,6 +13,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu5 diff --git a/ext/rbu/rbu6.test b/ext/rbu/rbu6.test index bb5b17f42..29d4c5e1a 100644 --- a/ext/rbu/rbu6.test +++ b/ext/rbu/rbu6.test @@ -13,10 +13,8 @@ # outcome of some other client writing to the database while an RBU update # is being applied. -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu6 proc setup_test {} { diff --git a/ext/rbu/rbu7.test b/ext/rbu/rbu7.test index 1e3bc174d..ffe6ebe1b 100644 --- a/ext/rbu/rbu7.test +++ b/ext/rbu/rbu7.test @@ -13,10 +13,8 @@ # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu7 # Test index: diff --git a/ext/rbu/rbu8.test b/ext/rbu/rbu8.test index c0ccd8c5e..b73bde274 100644 --- a/ext/rbu/rbu8.test +++ b/ext/rbu/rbu8.test @@ -12,10 +12,8 @@ # Test the rbu_delta() feature. # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu8 do_execsql_test 1.0 { diff --git a/ext/rbu/rbu9.test b/ext/rbu/rbu9.test index 0efdc1dde..499ff0945 100644 --- a/ext/rbu/rbu9.test +++ b/ext/rbu/rbu9.test @@ -12,10 +12,8 @@ # Test RBU with virtual tables. And tables with no PRIMARY KEY declarations. # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbu9 ifcapable !fts3 { diff --git a/ext/rbu/rbuA.test b/ext/rbu/rbuA.test index 642caca19..b43d30d4f 100644 --- a/ext/rbu/rbuA.test +++ b/ext/rbu/rbuA.test @@ -14,10 +14,8 @@ # a wal mode database via RBU. # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbuA set db_sql { diff --git a/ext/rbu/rbuB.test b/ext/rbu/rbuB.test index ece51c39b..3e777631d 100644 --- a/ext/rbu/rbuB.test +++ b/ext/rbu/rbuB.test @@ -12,6 +12,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbuB db close @@ -48,7 +49,7 @@ do_test 1.2 { do_test 1.3 { set ::errlog -} {SQLITE_NOTICE_RECOVER_WAL SQLITE_INTERNAL} +} {SQLITE_NOTICE_RECOVER_WAL SQLITE_NOTICE_RBU} do_execsql_test 1.4 { SELECT * FROM t1 diff --git a/ext/rbu/rbuC.test b/ext/rbu/rbuC.test index ff3d4d507..a38a6918a 100644 --- a/ext/rbu/rbuC.test +++ b/ext/rbu/rbuC.test @@ -13,6 +13,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbuC #------------------------------------------------------------------------- diff --git a/ext/rbu/rbu_common.tcl b/ext/rbu/rbu_common.tcl index c4e98784a..57db5a561 100644 --- a/ext/rbu/rbu_common.tcl +++ b/ext/rbu/rbu_common.tcl @@ -15,6 +15,16 @@ if {![info exists testdir]} { } source $testdir/tester.tcl +proc if_no_rbu_support {tcl} { + set bOk 1 + ifcapable !rbu { set bOk 0 } + if {[permutation]=="journaltest"} { set bOk 0 } + if {$bOk==0} { + set c [catch {uplevel 1 $tcl} r] + return -code $c $r + } +} + proc check_prestep_state {target state} { set oal_exists [file exists $target-oal] set wal_exists [file exists $target-wal] diff --git a/ext/rbu/rbubusy.test b/ext/rbu/rbubusy.test index 8cc47db8f..b4b43219b 100644 --- a/ext/rbu/rbubusy.test +++ b/ext/rbu/rbubusy.test @@ -12,6 +12,8 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +ifcapable !rbu { finish_test ; return } +if_no_rbu_support { finish_test ; return } set ::testprefix rbubusy db close diff --git a/ext/rbu/rbucollate.test b/ext/rbu/rbucollate.test index ccc097624..beb54aa54 100644 --- a/ext/rbu/rbucollate.test +++ b/ext/rbu/rbucollate.test @@ -11,6 +11,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbucollate ifcapable !icu_collations { @@ -18,11 +19,6 @@ ifcapable !icu_collations { return } -db close -sqlite3_shutdown -sqlite3_config_uri 1 -reset_db - # Create a simple RBU database. That expects to write to a table: # # CREATE TABLE t1(a PRIMARY KEY, b, c); diff --git a/ext/rbu/rbucrash.test b/ext/rbu/rbucrash.test index 34ac2c86f..e187e8db3 100644 --- a/ext/rbu/rbucrash.test +++ b/ext/rbu/rbucrash.test @@ -10,18 +10,10 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbucrash -db close -forcedelete test.db-oal rbu.db -sqlite3_shutdown -sqlite3_config_uri 1 -reset_db - # Set up a target database and an rbu update database. The target # db is the usual "test.db", the rbu db is "test.db2". # diff --git a/ext/rbu/rbucrash2.test b/ext/rbu/rbucrash2.test index 9ff6eba4f..10721df70 100644 --- a/ext/rbu/rbucrash2.test +++ b/ext/rbu/rbucrash2.test @@ -10,18 +10,10 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbucrash2 -db close -forcedelete test.db-oal rbu.db -sqlite3_shutdown -sqlite3_config_uri 1 -reset_db - # Set up a target database and an rbu update database. The target # db is the usual "test.db", the rbu db is "test.db2". # diff --git a/ext/rbu/rbudiff.test b/ext/rbu/rbudiff.test index 5c2bf9bee..e5e3df12f 100644 --- a/ext/rbu/rbudiff.test +++ b/ext/rbu/rbudiff.test @@ -12,10 +12,9 @@ # Tests for the [sqldiff --rbu] command. # # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl + +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set testprefix rbudiff set PROG [test_find_sqldiff] diff --git a/ext/rbu/rbudor.test b/ext/rbu/rbudor.test index df4d934de..c456d6ab7 100644 --- a/ext/rbu/rbudor.test +++ b/ext/rbu/rbudor.test @@ -13,10 +13,8 @@ # enabled by SQLITE_DIRECT_OVERFLOW_READ - Direct Overflow Read. # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbudor set bigA [string repeat a 5000] diff --git a/ext/rbu/rbuexlock.test b/ext/rbu/rbuexlock.test index eddcdc115..27fd6c4ba 100644 --- a/ext/rbu/rbuexlock.test +++ b/ext/rbu/rbuexlock.test @@ -11,11 +11,15 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbuexlock db close -sqlite3_shutdown -sqlite3_config_uri 1 + +set journalmode delete +if {[permutation]=="inmemory_journal"} { + set journalmode memory +} # Create a simple RBU database. That expects to write to a table: # @@ -64,7 +68,7 @@ do_test 1.2.0 { do_catchsql_test 1.2.1 { SELECT * FROM t1 } {0 {1 2 3}} do_test 1.2.2 { db eval {PRAGMA journal_mode} -} {delete} +} $journalmode do_test 1.3.0 { while {[file exists test.db-wal]==0} { @@ -74,7 +78,7 @@ do_test 1.3.0 { do_catchsql_test 1.3.1 { SELECT * FROM t1 } {1 {database is locked}} do_test 1.3.2 { db eval {PRAGMA journal_mode} -} {delete} +} $journalmode do_test 1.4.0 { @@ -83,7 +87,7 @@ do_test 1.4.0 { do_catchsql_test 1.4.1 { SELECT * FROM t1 } {1 {database is locked}} do_test 1.4.2 { db eval {PRAGMA journal_mode} -} {delete} +} $journalmode rbu close @@ -98,7 +102,7 @@ do_test 1.5.1 { do_catchsql_test 1.5.2 { SELECT * FROM t1 } {1 {database is locked}} do_test 1.5.2 { db eval {PRAGMA journal_mode} -} {delete} +} $journalmode do_test 1.6.0 { @@ -107,7 +111,7 @@ do_test 1.6.0 { do_catchsql_test 1.6.1 { SELECT * FROM t1 } {1 {database is locked}} do_test 1.6.2 { db eval {PRAGMA journal_mode} -} {delete} +} $journalmode do_test 1.7.0 { while {[rbu step]=="SQLITE_OK"} {} @@ -116,7 +120,7 @@ do_test 1.7.0 { do_catchsql_test 1.7.2 { SELECT count(*) FROM t1 } {0 9} do_test 1.7.2 { db eval {PRAGMA journal_mode} -} {delete} +} $journalmode reset_db do_execsql_test 2.0 { diff --git a/ext/rbu/rbuexpr.test b/ext/rbu/rbuexpr.test index a392c4ed8..4635d9fef 100644 --- a/ext/rbu/rbuexpr.test +++ b/ext/rbu/rbuexpr.test @@ -11,14 +11,9 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbuexpr -db close -sqlite3_shutdown -sqlite3_config_uri 1 - -sqlite3 db test.db - do_execsql_test 1.0 { CREATE TABLE t1(a, b, c PRIMARY KEY); CREATE INDEX i1 ON t1(a, null, b+1); diff --git a/ext/rbu/rbufault.test b/ext/rbu/rbufault.test index 247a99189..d35c389d7 100644 --- a/ext/rbu/rbufault.test +++ b/ext/rbu/rbufault.test @@ -10,10 +10,8 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } source $testdir/malloc_common.tcl set ::testprefix rbufault diff --git a/ext/rbu/rbufault2.test b/ext/rbu/rbufault2.test index 36f2b6b6f..fafe0ede7 100644 --- a/ext/rbu/rbufault2.test +++ b/ext/rbu/rbufault2.test @@ -10,10 +10,8 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } source $testdir/malloc_common.tcl set ::testprefix rbufault2 diff --git a/ext/rbu/rbufault3.test b/ext/rbu/rbufault3.test index 4f690284a..543e23762 100644 --- a/ext/rbu/rbufault3.test +++ b/ext/rbu/rbufault3.test @@ -13,6 +13,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } source $testdir/malloc_common.tcl set ::testprefix rbufault3 diff --git a/ext/rbu/rbufault4.test b/ext/rbu/rbufault4.test index 551b8b2b6..b95612449 100644 --- a/ext/rbu/rbufault4.test +++ b/ext/rbu/rbufault4.test @@ -10,10 +10,8 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } source $testdir/malloc_common.tcl set ::testprefix rbufault4 @@ -26,6 +24,7 @@ for {set tn 1} {1} {incr tn} { INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t1 VALUES(4, 5, 6); } + db close forcedelete test.db2 sqlite3rbu_vacuum rbu test.db test.db2 @@ -55,8 +54,10 @@ for {set tn 1} {1} {incr tn} { set trc [rbu close] if {$trc!="SQLITE_DONE"} { error "Got $trc instead of SQLITE_DONE!" } + sqlite3 db test.db set rc [db one {PRAGMA integrity_check}] if {$rc!="ok"} { error "Got $rc instead of ok!" } + db close } } diff --git a/ext/rbu/rbufts.test b/ext/rbu/rbufts.test index 95d4e75c5..b46c32fdc 100644 --- a/ext/rbu/rbufts.test +++ b/ext/rbu/rbufts.test @@ -13,10 +13,8 @@ # contains tests to ensure that RBU works with FTS tables. # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbufts ifcapable !fts3 { diff --git a/ext/rbu/rbumisc.test b/ext/rbu/rbumisc.test index c2a3906c0..16293141b 100644 --- a/ext/rbu/rbumisc.test +++ b/ext/rbu/rbumisc.test @@ -11,13 +11,9 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbumisc -db close -sqlite3_shutdown -sqlite3_config_uri 1 -reset_db - proc populate_rbu_db {} { forcedelete rbu.db sqlite3 rbu rbu.db diff --git a/ext/rbu/rbumulti.test b/ext/rbu/rbumulti.test index 5579a5f5b..c24da3518 100644 --- a/ext/rbu/rbumulti.test +++ b/ext/rbu/rbumulti.test @@ -9,16 +9,17 @@ # #*********************************************************************** # +# TESTRUNNER: slow +# # This file contains tests of multiple RBU operations running # concurrently within the same process. # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbumulti db close -sqlite3_shutdown -sqlite3_config_uri 1 autoinstall_test_functions diff --git a/ext/rbu/rbupartial.test b/ext/rbu/rbupartial.test index d6cd7330f..0c8191d2b 100644 --- a/ext/rbu/rbupartial.test +++ b/ext/rbu/rbupartial.test @@ -11,11 +11,10 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbupartial db close -sqlite3_shutdown -sqlite3_config_uri 1 foreach {tn without_rowid a b c d} { 1 "" a b c d diff --git a/ext/rbu/rbupass.test b/ext/rbu/rbupass.test new file mode 100644 index 000000000..8b921cba4 --- /dev/null +++ b/ext/rbu/rbupass.test @@ -0,0 +1,81 @@ +# 2023 January 13 +# +# 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } +set ::testprefix rbupass + +if {[info commands register_demovfs]==""} { + finish_test + return +} + +db close + +register_demovfs +sqlite3rbu_create_vfs myvfs demo + +sqlite3 db file:test.db?vfs=myvfs -uri 1 +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, 2); + SELECT * FROM t1; +} {1 2} + +if {[permutation]!="inmemory_journal"} { + do_execsql_test 1.1 { + PRAGMA journal_mode = wal; + } {delete} +} + +do_execsql_test 1.2 { + SELECT * FROM t1; +} {1 2} + +do_test 1.3 { + forcedelete rbu.db + sqlite3 rbu rbu.db + rbu eval { + CREATE TABLE data_t1(a, b, rbu_control); + INSERT INTO data_t1 VALUES(2, 4, 0); + } + rbu close +} {} + +do_test 1.4 { + sqlite3rbu rbu test.db rbu.db +} {rbu} +do_test 1.5 { + rbu step +} {SQLITE_CANTOPEN} +do_test 1.6 { + list [catch { rbu close } msg] $msg +} {1 {SQLITE_CANTOPEN - unable to open database file}} + +do_test 1.7 { + sqlite3rbu_vacuum rbu test.db +} {rbu} +do_test 1.8 { + rbu step + catch { rbu close } +} {1} + +do_execsql_test 1.9 { + SELECT * FROM t1; +} {1 2} + +db close +sqlite3rbu_destroy_vfs myvfs +unregister_demovfs +sqlite3_shutdown +finish_test + diff --git a/ext/rbu/rbuprogress.test b/ext/rbu/rbuprogress.test index bf37b4e50..072683742 100644 --- a/ext/rbu/rbuprogress.test +++ b/ext/rbu/rbuprogress.test @@ -11,6 +11,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbuprogress diff --git a/ext/rbu/rburename.test b/ext/rbu/rburename.test index 2275396bc..e2bacaea5 100644 --- a/ext/rbu/rburename.test +++ b/ext/rbu/rburename.test @@ -12,6 +12,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rburename diff --git a/ext/rbu/rburesume.test b/ext/rbu/rburesume.test index a2cf9bc4a..cdd890843 100644 --- a/ext/rbu/rburesume.test +++ b/ext/rbu/rburesume.test @@ -9,11 +9,14 @@ # #*********************************************************************** # +# TESTRUNNER: slow +# # This file contains tests for resumption of RBU operations in the # case where the previous RBU process crashed. # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rburesume forcedelete test.db-shm test.db-oal @@ -97,7 +100,7 @@ for {set n 1} {$n < 5000} {incr n} { break } - foreach f {test.db test.db-oal test.db-wal test.db-shm test.db-vacuum} { + foreach f {test.db test.db-oal test.db-wal test.db-vacuum} { set f2 [string map [list test.db test.db2] $f] if {[file exists $f]} { forcecopy $f $f2 @@ -156,7 +159,7 @@ for {set n 1} {$n < 5000} {incr n} { break } - foreach f {test.db test.db-oal test.db-wal test.db-shm test.db-vacuum} { + foreach f {test.db test.db-oal test.db-wal test.db-vacuum} { set f2 [string map [list test.db test.db2] $f] if {[file exists $f]} { forcecopy $f $f2 @@ -223,7 +226,7 @@ for {set n 1} {$n < 5000} {incr n} { break } - foreach f {test.db test.db-oal test.db-wal test.db-shm test.db-vacuum} { + foreach f {test.db test.db-oal test.db-wal test.db-vacuum} { set f2 [string map [list test.db test.db2] $f] if {[file exists $f]} { forcecopy $f $f2 diff --git a/ext/rbu/rbusave.test b/ext/rbu/rbusave.test index 210d7b545..8e9708980 100644 --- a/ext/rbu/rbusave.test +++ b/ext/rbu/rbusave.test @@ -10,10 +10,8 @@ #*********************************************************************** # -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl +source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbusave do_execsql_test 1.0 { diff --git a/ext/rbu/rbusplit.test b/ext/rbu/rbusplit.test index 34fef3867..fb1d7238d 100644 --- a/ext/rbu/rbusplit.test +++ b/ext/rbu/rbusplit.test @@ -12,11 +12,10 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbusplit db close -sqlite3_shutdown -sqlite3_config_uri 1 autoinstall_test_functions diff --git a/ext/rbu/rbutemplimit.test b/ext/rbu/rbutemplimit.test index 958b2bfcb..88b5f4152 100644 --- a/ext/rbu/rbutemplimit.test +++ b/ext/rbu/rbutemplimit.test @@ -9,13 +9,13 @@ # #*********************************************************************** # +# TESTRUNNER: slow source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbutemplimit db close -sqlite3_shutdown -sqlite3_config_uri 1 proc setup_databases {} { forcedelete test.db2 diff --git a/ext/rbu/rbuvacuum.test b/ext/rbu/rbuvacuum.test index 1785f529b..ad377a4be 100644 --- a/ext/rbu/rbuvacuum.test +++ b/ext/rbu/rbuvacuum.test @@ -15,6 +15,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set ::testprefix rbuvacuum foreach step {0 1} { diff --git a/ext/rbu/rbuvacuum2.test b/ext/rbu/rbuvacuum2.test index d4f7c5232..34ec26188 100644 --- a/ext/rbu/rbuvacuum2.test +++ b/ext/rbu/rbuvacuum2.test @@ -15,6 +15,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } foreach {step} {0 1} { foreach {ttt state} { diff --git a/ext/rbu/rbuvacuum3.test b/ext/rbu/rbuvacuum3.test index 7e1e337f3..d70c094f2 100644 --- a/ext/rbu/rbuvacuum3.test +++ b/ext/rbu/rbuvacuum3.test @@ -15,6 +15,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set testprefix rbuvacuum3 do_execsql_test 1.0 { diff --git a/ext/rbu/rbuvacuum4.test b/ext/rbu/rbuvacuum4.test index 5cf33d676..7b0db974d 100644 --- a/ext/rbu/rbuvacuum4.test +++ b/ext/rbu/rbuvacuum4.test @@ -15,6 +15,7 @@ # source [file join [file dirname [info script]] rbu_common.tcl] +if_no_rbu_support { finish_test ; return } set testprefix rbuvacuum4 set step 1 @@ -87,6 +88,7 @@ do_execsql_test 4.2 { SELECT count(*) fROM x1 } 6 +db cache flush do_rbu_vacuum_test 4.1.2 0 #------------------------------------------------------------------------- diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c index 482952006..2db66f67a 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -3031,11 +3031,11 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ ** no-ops. These locks will not be released until the connection ** is closed. ** - ** * Attempting to xSync() the database file causes an SQLITE_INTERNAL + ** * Attempting to xSync() the database file causes an SQLITE_NOTICE ** error. ** ** As a result, unless an error (i.e. OOM or SQLITE_BUSY) occurs, the - ** checkpoint below fails with SQLITE_INTERNAL, and leaves the aFrame[] + ** checkpoint below fails with SQLITE_NOTICE, and leaves the aFrame[] ** array populated with a set of (frame -> page) mappings. Because the ** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy ** data from the wal file into the database file according to the @@ -3045,7 +3045,7 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ int rc2; p->eStage = RBU_STAGE_CAPTURE; rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0); - if( rc2!=SQLITE_INTERNAL ) p->rc = rc2; + if( rc2!=SQLITE_NOTICE ) p->rc = rc2; } if( p->rc==SQLITE_OK && p->nFrame>0 ){ @@ -3091,7 +3091,7 @@ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ if( pRbu->mLock!=mReq ){ pRbu->rc = SQLITE_BUSY; - return SQLITE_INTERNAL; + return SQLITE_NOTICE_RBU; } pRbu->pgsz = iAmt; @@ -3830,7 +3830,8 @@ static void rbuSetupOal(sqlite3rbu *p, RbuState *pState){ static void rbuDeleteOalFile(sqlite3rbu *p){ char *zOal = rbuMPrintf(p, "%s-oal", p->zTarget); if( zOal ){ - sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + sqlite3_vfs *pVfs = 0; + sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_VFS_POINTER, &pVfs); assert( pVfs && p->rc==SQLITE_OK && p->zErrmsg==0 ); pVfs->xDelete(pVfs, zOal, 0); sqlite3_free(zOal); @@ -4478,7 +4479,7 @@ void sqlite3rbu_rename_handler( ** database file are recorded. xShmLock() calls to unlock the same ** locks are no-ops (so that once obtained, these locks are never ** relinquished). Finally, calls to xSync() on the target database -** file fail with SQLITE_INTERNAL errors. +** file fail with SQLITE_NOTICE errors. */ static void rbuUnlockShm(rbu_file *p){ @@ -4587,9 +4588,12 @@ static int rbuVfsClose(sqlite3_file *pFile){ sqlite3_free(p->zDel); if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ + const sqlite3_io_methods *pMeth = p->pReal->pMethods; rbuMainlistRemove(p); rbuUnlockShm(p); - p->pReal->pMethods->xShmUnmap(p->pReal, 0); + if( pMeth->iVersion>1 && pMeth->xShmUnmap ){ + pMeth->xShmUnmap(p->pReal, 0); + } } else if( (p->openFlags & SQLITE_OPEN_DELETEONCLOSE) && p->pRbu ){ rbuUpdateTempSize(p, 0); @@ -4757,7 +4761,7 @@ static int rbuVfsSync(sqlite3_file *pFile, int flags){ rbu_file *p = (rbu_file *)pFile; if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){ if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ - return SQLITE_INTERNAL; + return SQLITE_NOTICE_RBU; } return SQLITE_OK; } @@ -5048,6 +5052,25 @@ static int rbuVfsOpen( rbuVfsShmUnmap, /* xShmUnmap */ 0, 0 /* xFetch, xUnfetch */ }; + static sqlite3_io_methods rbuvfs_io_methods1 = { + 1, /* iVersion */ + rbuVfsClose, /* xClose */ + rbuVfsRead, /* xRead */ + rbuVfsWrite, /* xWrite */ + rbuVfsTruncate, /* xTruncate */ + rbuVfsSync, /* xSync */ + rbuVfsFileSize, /* xFileSize */ + rbuVfsLock, /* xLock */ + rbuVfsUnlock, /* xUnlock */ + rbuVfsCheckReservedLock, /* xCheckReservedLock */ + rbuVfsFileControl, /* xFileControl */ + rbuVfsSectorSize, /* xSectorSize */ + rbuVfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, 0, 0, 0, 0, 0 + }; + + + rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs; sqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs; rbu_file *pFd = (rbu_file *)pFile; @@ -5102,10 +5125,15 @@ static int rbuVfsOpen( rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, oflags, pOutFlags); } if( pFd->pReal->pMethods ){ + const sqlite3_io_methods *pMeth = pFd->pReal->pMethods; /* The xOpen() operation has succeeded. Set the sqlite3_file.pMethods ** pointer and, if the file is a main database file, link it into the ** mutex protected linked list of all such files. */ - pFile->pMethods = &rbuvfs_io_methods; + if( pMeth->iVersion<2 || pMeth->xShmLock==0 ){ + pFile->pMethods = &rbuvfs_io_methods1; + }else{ + pFile->pMethods = &rbuvfs_io_methods; + } if( flags & SQLITE_OPEN_MAIN_DB ){ rbuMainlistAdd(pFd); } diff --git a/ext/rbu/sqlite3rbu.h b/ext/rbu/sqlite3rbu.h index c819cd3f2..d156b3178 100644 --- a/ext/rbu/sqlite3rbu.h +++ b/ext/rbu/sqlite3rbu.h @@ -10,42 +10,42 @@ ** ************************************************************************* ** -** This file contains the public interface for the RBU extension. +** This file contains the public interface for the RBU extension. */ /* ** SUMMARY ** -** Writing a transaction containing a large number of operations on +** Writing a transaction containing a large number of operations on ** b-tree indexes that are collectively larger than the available cache -** memory can be very inefficient. +** memory can be very inefficient. ** ** The problem is that in order to update a b-tree, the leaf page (at least) ** containing the entry being inserted or deleted must be modified. If the -** working set of leaves is larger than the available cache memory, then a -** single leaf that is modified more than once as part of the transaction +** working set of leaves is larger than the available cache memory, then a +** single leaf that is modified more than once as part of the transaction ** may be loaded from or written to the persistent media multiple times. ** Additionally, because the index updates are likely to be applied in -** random order, access to pages within the database is also likely to be in +** random order, access to pages within the database is also likely to be in ** random order, which is itself quite inefficient. ** ** One way to improve the situation is to sort the operations on each index ** by index key before applying them to the b-tree. This leads to an IO ** pattern that resembles a single linear scan through the index b-tree, -** and all but guarantees each modified leaf page is loaded and stored +** and all but guarantees each modified leaf page is loaded and stored ** exactly once. SQLite uses this trick to improve the performance of ** CREATE INDEX commands. This extension allows it to be used to improve ** the performance of large transactions on existing databases. ** -** Additionally, this extension allows the work involved in writing the -** large transaction to be broken down into sub-transactions performed -** sequentially by separate processes. This is useful if the system cannot -** guarantee that a single update process will run for long enough to apply -** the entire update, for example because the update is being applied on a -** mobile device that is frequently rebooted. Even after the writer process +** Additionally, this extension allows the work involved in writing the +** large transaction to be broken down into sub-transactions performed +** sequentially by separate processes. This is useful if the system cannot +** guarantee that a single update process will run for long enough to apply +** the entire update, for example because the update is being applied on a +** mobile device that is frequently rebooted. Even after the writer process ** has committed one or more sub-transactions, other database clients continue -** to read from the original database snapshot. In other words, partially -** applied transactions are not visible to other clients. +** to read from the original database snapshot. In other words, partially +** applied transactions are not visible to other clients. ** ** "RBU" stands for "Resumable Bulk Update". As in a large database update ** transmitted via a wireless network to a mobile device. A transaction @@ -61,9 +61,9 @@ ** ** * INSERT statements may not use any default values. ** -** * UPDATE and DELETE statements must identify their target rows by +** * UPDATE and DELETE statements must identify their target rows by ** non-NULL PRIMARY KEY values. Rows with NULL values stored in PRIMARY -** KEY fields may not be updated or deleted. If the table being written +** KEY fields may not be updated or deleted. If the table being written ** has no PRIMARY KEY, affected rows must be identified by rowid. ** ** * UPDATE statements may not modify PRIMARY KEY columns. @@ -80,10 +80,10 @@ ** PREPARATION ** ** An "RBU update" is stored as a separate SQLite database. A database -** containing an RBU update is an "RBU database". For each table in the +** containing an RBU update is an "RBU database". For each table in the ** target database to be updated, the RBU database should contain a table ** named "data_" containing the same set of columns as the -** target table, and one more - "rbu_control". The data_% table should +** target table, and one more - "rbu_control". The data_% table should ** have no PRIMARY KEY or UNIQUE constraints, but each column should have ** the same type as the corresponding column in the target database. ** The "rbu_control" column should have no type at all. For example, if @@ -98,22 +98,22 @@ ** The order of the columns in the data_% table does not matter. ** ** Instead of a regular table, the RBU database may also contain virtual -** tables or view named using the data_ naming scheme. +** tables or views named using the data_ naming scheme. ** -** Instead of the plain data_ naming scheme, RBU database tables +** Instead of the plain data_ naming scheme, RBU database tables ** may also be named data_, where is any sequence ** of zero or more numeric characters (0-9). This can be significant because -** tables within the RBU database are always processed in order sorted by +** tables within the RBU database are always processed in order sorted by ** name. By judicious selection of the portion of the names ** of the RBU tables the user can therefore control the order in which they ** are processed. This can be useful, for example, to ensure that "external ** content" FTS4 tables are updated before their underlying content tables. ** ** If the target database table is a virtual table or a table that has no -** PRIMARY KEY declaration, the data_% table must also contain a column -** named "rbu_rowid". This column is mapped to the tables implicit primary -** key column - "rowid". Virtual tables for which the "rowid" column does -** not function like a primary key value cannot be updated using RBU. For +** PRIMARY KEY declaration, the data_% table must also contain a column +** named "rbu_rowid". This column is mapped to the table's implicit primary +** key column - "rowid". Virtual tables for which the "rowid" column does +** not function like a primary key value cannot be updated using RBU. For ** example, if the target db contains either of the following: ** ** CREATE VIRTUAL TABLE x1 USING fts3(a, b); @@ -136,35 +136,35 @@ ** CREATE TABLE data_ft1(a, b, langid, rbu_rowid, rbu_control); ** CREATE TABLE data_ft1(a, b, rbu_rowid, rbu_control); ** -** For each row to INSERT into the target database as part of the RBU +** For each row to INSERT into the target database as part of the RBU ** update, the corresponding data_% table should contain a single record ** with the "rbu_control" column set to contain integer value 0. The -** other columns should be set to the values that make up the new record -** to insert. +** other columns should be set to the values that make up the new record +** to insert. ** -** If the target database table has an INTEGER PRIMARY KEY, it is not -** possible to insert a NULL value into the IPK column. Attempting to +** If the target database table has an INTEGER PRIMARY KEY, it is not +** possible to insert a NULL value into the IPK column. Attempting to ** do so results in an SQLITE_MISMATCH error. ** -** For each row to DELETE from the target database as part of the RBU +** For each row to DELETE from the target database as part of the RBU ** update, the corresponding data_% table should contain a single record ** with the "rbu_control" column set to contain integer value 1. The ** real primary key values of the row to delete should be stored in the ** corresponding columns of the data_% table. The values stored in the ** other columns are not used. ** -** For each row to UPDATE from the target database as part of the RBU +** For each row to UPDATE from the target database as part of the RBU ** update, the corresponding data_% table should contain a single record ** with the "rbu_control" column set to contain a value of type text. -** The real primary key values identifying the row to update should be +** The real primary key values identifying the row to update should be ** stored in the corresponding columns of the data_% table row, as should -** the new values of all columns being update. The text value in the +** the new values of all columns being update. The text value in the ** "rbu_control" column must contain the same number of characters as ** there are columns in the target database table, and must consist entirely -** of 'x' and '.' characters (or in some special cases 'd' - see below). For +** of 'x' and '.' characters (or in some special cases 'd' - see below). For ** each column that is being updated, the corresponding character is set to ** 'x'. For those that remain as they are, the corresponding character of the -** rbu_control value should be set to '.'. For example, given the tables +** rbu_control value should be set to '.'. For example, given the tables ** above, the update statement: ** ** UPDATE t1 SET c = 'usa' WHERE a = 4; @@ -178,30 +178,30 @@ ** target table with the value stored in the corresponding data_% column, the ** user-defined SQL function "rbu_delta()" is invoked and the result stored in ** the target table column. rbu_delta() is invoked with two arguments - the -** original value currently stored in the target table column and the +** original value currently stored in the target table column and the ** value specified in the data_xxx table. ** ** For example, this row: ** ** INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..d'); ** -** is similar to an UPDATE statement such as: +** is similar to an UPDATE statement such as: ** ** UPDATE t1 SET c = rbu_delta(c, 'usa') WHERE a = 4; ** -** Finally, if an 'f' character appears in place of a 'd' or 's' in an +** Finally, if an 'f' character appears in place of a 'd' or 's' in an ** ota_control string, the contents of the data_xxx table column is assumed ** to be a "fossil delta" - a patch to be applied to a blob value in the ** format used by the fossil source-code management system. In this case -** the existing value within the target database table must be of type BLOB. +** the existing value within the target database table must be of type BLOB. ** It is replaced by the result of applying the specified fossil delta to ** itself. ** ** If the target database table is a virtual table or a table with no PRIMARY -** KEY, the rbu_control value should not include a character corresponding +** KEY, the rbu_control value should not include a character corresponding ** to the rbu_rowid value. For example, this: ** -** INSERT INTO data_ft1(a, b, rbu_rowid, rbu_control) +** INSERT INTO data_ft1(a, b, rbu_rowid, rbu_control) ** VALUES(NULL, 'usa', 12, '.x'); ** ** causes a result similar to: diff --git a/ext/recover/dbdata.c b/ext/recover/dbdata.c index 9563ab502..da02b754b 100644 --- a/ext/recover/dbdata.c +++ b/ext/recover/dbdata.c @@ -164,6 +164,9 @@ static int dbdataConnect( DbdataTable *pTab = 0; int rc = sqlite3_declare_vtab(db, pAux ? DBPTR_SCHEMA : DBDATA_SCHEMA); + (void)argc; + (void)argv; + (void)pzErr; if( rc==SQLITE_OK ){ pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable)); if( pTab==0 ){ @@ -768,6 +771,8 @@ static int dbdataFilter( DbdataTable *pTab = (DbdataTable*)pCursor->pVtab; int rc = SQLITE_OK; const char *zSchema = "main"; + (void)idxStr; + (void)argc; dbdataResetCursor(pCsr); assert( pCsr->iPgno==1 ); @@ -936,6 +941,7 @@ int sqlite3_dbdata_init( const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; return sqlite3DbdataRegister(db); } diff --git a/ext/recover/recover1.test b/ext/recover/recover1.test index dba96f519..fef1bf90f 100644 --- a/ext/recover/recover1.test +++ b/ext/recover/recover1.test @@ -275,46 +275,46 @@ do_recover_test 15 #------------------------------------------------------------------------- reset_db -if {[wal_is_capable]} { - do_execsql_test 16.1 { - PRAGMA journal_mode = wal; +do_test 16.1 { + execsql { PRAGMA journal_mode = wal } + execsql { CREATE TABLE t1(x); INSERT INTO t1 VALUES(1), (2), (3); - } {wal} - do_test 16.2 { - set R [sqlite3_recover_init db main test.db2] - $R run - $R finish - } {} - do_execsql_test 16.3 { - SELECT * FROM t1; - } {1 2 3} - - do_execsql_test 16.4 { - BEGIN; - SELECT * FROM t1; - } {1 2 3} - do_test 16.5 { - set R [sqlite3_recover_init db main test.db2] - $R run - list [catch { $R finish } msg] $msg - } {1 {cannot start a transaction within a transaction}} - do_execsql_test 16.6 { - SELECT * FROM t1; - } {1 2 3} - do_execsql_test 16.7 { - INSERT INTO t1 VALUES(4); } - do_test 16.8 { - set R [sqlite3_recover_init db main test.db2] - $R run - list [catch { $R finish } msg] $msg - } {1 {cannot start a transaction within a transaction}} - do_execsql_test 16.9 { +} {} +do_test 16.2 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} +do_execsql_test 16.3 { + SELECT * FROM t1; +} {1 2 3} + +do_execsql_test 16.4 { + BEGIN; SELECT * FROM t1; - COMMIT; - } {1 2 3 4} +} {1 2 3} +do_test 16.5 { + set R [sqlite3_recover_init db main test.db2] + $R run + list [catch { $R finish } msg] $msg +} {1 {cannot start a transaction within a transaction}} +do_execsql_test 16.6 { + SELECT * FROM t1; +} {1 2 3} +do_execsql_test 16.7 { + INSERT INTO t1 VALUES(4); } +do_test 16.8 { + set R [sqlite3_recover_init db main test.db2] + $R run + list [catch { $R finish } msg] $msg +} {1 {cannot start a transaction within a transaction}} +do_execsql_test 16.9 { + SELECT * FROM t1; + COMMIT; +} {1 2 3 4} finish_test diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index e62aba752..8306e8ed8 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -761,6 +761,7 @@ static void recoverEscapeCrnl( sqlite3_value **argv ){ const char *zText = (const char*)sqlite3_value_text(argv[0]); + (void)argc; if( zText && zText[0]=='\'' ){ int nText = sqlite3_value_bytes(argv[0]); int i; @@ -913,7 +914,7 @@ static void recoverTransferSettings(sqlite3_recover *p){ return; } - for(ii=0; iidbIn, "PRAGMA %Q.%s", p->zDb, zPrag); @@ -991,7 +992,9 @@ static int recoverOpenOutput(sqlite3_recover *p){ } /* Register the custom user-functions with the output handle. */ - for(ii=0; p->errCode==SQLITE_OK && iierrCode==SQLITE_OK && ii<(int)(sizeof(aFunc)/sizeof(aFunc[0])); + ii++){ p->errCode = sqlite3_create_function(db, aFunc[ii].zName, aFunc[ii].nArg, SQLITE_UTF8, (void*)p, aFunc[ii].xFunc, 0, 0 ); @@ -2388,7 +2391,7 @@ static int recoverVfsRead(sqlite3_file *pFd, void *aBuf, int nByte, i64 iOff){ if( pgsz==65536 ) pgsz = 1; recoverPutU16(&aHdr[16], pgsz); aHdr[20] = nReserve; - for(ii=0; ii=(4+6*sizeof(GeoCoord)) + && (nByte = sqlite3_value_bytes(pVal))>=(int)(4+6*sizeof(GeoCoord)) ){ const unsigned char *a = sqlite3_value_blob(pVal); int nVertex; @@ -362,6 +362,7 @@ static void geopolyBlobFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); @@ -381,6 +382,7 @@ static void geopolyJsonFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ sqlite3 *db = sqlite3_context_db_handle(context); sqlite3_str *x = sqlite3_str_new(db); @@ -462,6 +464,7 @@ static void geopolyXformFunc( double F = sqlite3_value_double(argv[6]); GeoCoord x1, y1, x0, y0; int ii; + (void)argc; if( p ){ for(ii=0; iinVertex; ii++){ x0 = GeoX(p,ii); @@ -512,6 +515,7 @@ static void geopolyAreaFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ sqlite3_result_double(context, geopolyArea(p)); sqlite3_free(p); @@ -537,6 +541,7 @@ static void geopolyCcwFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ if( geopolyArea(p)<0.0 ){ int ii, jj; @@ -591,6 +596,7 @@ static void geopolyRegularFunc( int n = sqlite3_value_int(argv[3]); int i; GeoPoly *p; + (void)argc; if( n<3 || r<=0.0 ) return; if( n>1000 ) n = 1000; @@ -700,6 +706,7 @@ static void geopolyBBoxFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyBBox(context, argv[0], 0, 0); + (void)argc; if( p ){ sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); @@ -727,6 +734,7 @@ static void geopolyBBoxStep( ){ RtreeCoord a[4]; int rc = SQLITE_OK; + (void)argc; (void)geopolyBBox(context, argv[0], a, &rc); if( rc==SQLITE_OK ){ GeoBBox *pBBox; @@ -815,6 +823,8 @@ static void geopolyContainsPointFunc( int v = 0; int cnt = 0; int ii; + (void)argc; + if( p1==0 ) return; for(ii=0; iinVertex-1; ii++){ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii), @@ -854,6 +864,7 @@ static void geopolyWithinFunc( ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); + (void)argc; if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ @@ -1184,6 +1195,7 @@ static void geopolyOverlapFunc( ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); + (void)argc; if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ @@ -1204,8 +1216,12 @@ static void geopolyDebugFunc( int argc, sqlite3_value **argv ){ + (void)context; + (void)argc; #ifdef GEOPOLY_ENABLE_DEBUG geo_debug = sqlite3_value_int(argv[0]); +#else + (void)argv; #endif } @@ -1233,6 +1249,7 @@ static int geopolyInit( sqlite3_str *pSql; char *zSql; int ii; + (void)pAux; sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); @@ -1349,6 +1366,7 @@ static int geopolyFilter( RtreeNode *pRoot = 0; int rc = SQLITE_OK; int iCell = 0; + (void)idxStr; rtreeReference(pRtree); @@ -1475,6 +1493,7 @@ static int geopolyBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int iRowidTerm = -1; int iFuncTerm = -1; int idxNum = 0; + (void)tab; for(ii=0; iinConstraint; ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; @@ -1721,6 +1740,8 @@ static int geopolyFindFunction( void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg ){ + (void)pVtab; + (void)nArg; if( sqlite3_stricmp(zName, "geopoly_overlap")==0 ){ *pxFunc = geopolyOverlapFunc; *ppArg = 0; @@ -1790,7 +1811,7 @@ static int sqlite3_geopoly_init(sqlite3 *db){ } aAgg[] = { { geopolyBBoxStep, geopolyBBoxFinal, "geopoly_group_bbox" }, }; - int i; + unsigned int i; for(i=0; i=1300 pCoord->u = _byteswap_ulong(*(u32*)p); #elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -555,7 +555,7 @@ static void writeInt16(u8 *p, int i){ } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; - assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */ + assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */ assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); #if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -1283,7 +1283,7 @@ static void rtreeNonleafConstraint( assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); - assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */ + assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ case RTREE_FALSE: break; /* Never satisfied */ @@ -1336,7 +1336,7 @@ static void rtreeLeafConstraint( || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); pCellData += 8 + p->iCoord*4; - assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */ + assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ RTREE_DECODE_COORD(eInt, pCellData, xN); switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ diff --git a/ext/rtree/rtreecheck.test b/ext/rtree/rtreecheck.test index 17f359aa8..ff5397e1e 100644 --- a/ext/rtree/rtreecheck.test +++ b/ext/rtree/rtreecheck.test @@ -157,4 +157,28 @@ do_execsql_test 5.2 { SELECT rtreecheck('r3')=='ok' } 0 +#------------------------------------------------------------------------- +# dbsqlfuzz 4a1399d39bf9feccbf6b290da51d3b30103a4bf6 +# +reset_db +do_execsql_test 6.0 { + PRAGMA encoding = 'utf16'; + CREATE VIRTUAL TABLE t1 USING rtree(id, x, y); +} +db close +sqlite3 db test.db + +if {[permutation]=="inmemory_journal"} { + # This doesn't hit an SQLITE_LOCKED in this permutation as the schema + # has already been loaded. + do_catchsql_test 6.1.inmemory_journal { + SELECT ( 'elvis' IN(SELECT rtreecheck('t1')) ) FROM (SELECT 1) GROUP BY 1; + } {0 0} +} else { + do_catchsql_test 6.1 { + SELECT ( 'elvis' IN(SELECT rtreecheck('t1')) ) FROM (SELECT 1) GROUP BY 1; + } {1 {database table is locked}} +} + finish_test + diff --git a/ext/session/sessionat.test b/ext/session/sessionat.test index 8141d9232..e3f9e31ed 100644 --- a/ext/session/sessionat.test +++ b/ext/session/sessionat.test @@ -243,6 +243,60 @@ eval [string map [list %WR% $trailing] { sqlite3changeset_apply db $cinv xConflict execsql { SELECT * FROM t7 } } {1 1 ccc 2 2 ccc 3 3 ccc} + + #----------------------------------------------------------------------- + reset_test + do_execsql_test $tn.7.0 { + CREATE TABLE t8(a PRIMARY KEY, b, c); + } + do_execsql_test -db db2 $tn.7.1 { + CREATE TABLE t8(a PRIMARY KEY, b, c, d DEFAULT 'D', e DEFAULT 'E'); + } + + do_then_apply_sql { + INSERT INTO t8 VALUES(1, 2, 3); + INSERT INTO t8 VALUES(4, 5, 6); + } + do_execsql_test $tn.7.2.1 { + SELECT * FROM t8 + } {1 2 3 4 5 6} + do_execsql_test -db db2 $tn.7.2.2 { + SELECT * FROM t8 + } {1 2 3 D E 4 5 6 D E} + + do_then_apply_sql { + UPDATE t8 SET c=45 WHERE a=4; + } + do_execsql_test $tn.7.3.1 { + SELECT * FROM t8 + } {1 2 3 4 5 45} + do_execsql_test -db db2 $tn.7.3.2 { + SELECT * FROM t8 + } {1 2 3 D E 4 5 45 D E} + + #----------------------------------------------------------------------- + reset_test + do_execsql_test $tn.8.0 { + CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h); + } + do_execsql_test -db db2 $tn.8.1 { + CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h, i, j, k, l); + } + do_then_apply_sql { + INSERT INTO t9 VALUES(1, 2, 3, 4, 5, 6, 7, 8); + } + do_then_apply_sql { + UPDATE t9 SET h=450 WHERE a=1 + } + do_execsql_test -db db2 $tn.8.2 { + SELECT * FROM t9 + } {1 2 3 4 5 6 7 450 {} {} {} {}} + do_then_apply_sql { + UPDATE t9 SET h=NULL + } + do_execsql_test -db db2 $tn.8.2 { + SELECT * FROM t9 + } {1 2 3 4 5 6 7 {} {} {} {} {}} }] } diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index fd06f3b4d..a3f28abe9 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -1499,6 +1499,8 @@ static void xPreUpdate( int nDb = sqlite3Strlen30(zDb); assert( sqlite3_mutex_held(db->mutex) ); + (void)iKey1; + (void)iKey2; for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){ SessionTable *pTab; @@ -1575,6 +1577,7 @@ static int sessionDiffCount(void *pCtx){ return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt); } static int sessionDiffDepth(void *pCtx){ + (void)pCtx; return 0; } @@ -1648,7 +1651,6 @@ static char *sessionExprCompareOther( } static char *sessionSelectFindNew( - int nCol, const char *zDb1, /* Pick rows in this db only */ const char *zDb2, /* But not in this one */ const char *zTbl, /* Table name */ @@ -1672,7 +1674,7 @@ static int sessionDiffFindNew( char *zExpr ){ int rc = SQLITE_OK; - char *zStmt = sessionSelectFindNew(pTab->nCol, zDb1, zDb2, pTab->zName,zExpr); + char *zStmt = sessionSelectFindNew(zDb1, zDb2, pTab->zName,zExpr); if( zStmt==0 ){ rc = SQLITE_NOMEM; @@ -4189,7 +4191,6 @@ static int sessionBindRow( ** UPDATE, bind values from the old.* record. */ static int sessionSeekToRow( - sqlite3 *db, /* Database handle */ sqlite3_changeset_iter *pIter, /* Changeset iterator */ u8 *abPK, /* Primary key flags array */ sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */ @@ -4319,7 +4320,7 @@ static int sessionConflictHandler( /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ if( pbReplace ){ - rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); }else{ rc = SQLITE_OK; } @@ -4493,7 +4494,7 @@ static int sessionApplyOneOp( /* Check if there is a conflicting row. For sqlite_stat1, this needs ** to be done using a SELECT, as there is no PRIMARY KEY in the ** database schema to throw an exception if a duplicate is inserted. */ - rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); if( rc==SQLITE_ROW ){ rc = SQLITE_CONSTRAINT; sqlite3_reset(p->pSelect); diff --git a/ext/session/test_session.c b/ext/session/test_session.c index c1feb7802..242e0fb0f 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -102,9 +102,8 @@ int sql_exec_changeset( #ifdef SQLITE_DEBUG static int sqlite3_test_changeset(int, void *, char **); static void assert_changeset_is_ok(int n, void *p){ - int rc = 0; char *z = 0; - rc = sqlite3_test_changeset(n, p, &z); + (void)sqlite3_test_changeset(n, p, &z); assert( z==0 ); } #else @@ -1301,7 +1300,15 @@ static int SQLITE_TCLAPI test_sqlite3rebaser_create( } /* +** Run some sanity checks on the changeset in nChangeset byte buffer +** pChangeset. If any fail, return a non-zero value and, optionally, +** set output variable (*pzErr) to point to a buffer containing an +** English language error message describing the problem. In this +** case it is the responsibility of the caller to free the buffer +** using sqlite3_free(). ** +** Or, if the changeset appears to be well-formed, this function +** returns SQLITE_OK and sets (*pzErr) to NULL. */ static int sqlite3_test_changeset( int nChangeset, diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 039dff410..7ffd866f2 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -15,12 +15,37 @@ # by the target name. Rebuild is necessary for all components to get # the desired optimization level. # +# quick, q = do just a minimal build (sqlite3.js/wasm, tester1) for +# faster development-mode turnaround. +# +# qo2, qoz = a combination of quick+o2/oz. +# # dist = create end user deliverables. Add dist.build=oX to build # with a specific optimization level, where oX is one of the -# above-listed o? target names. +# above-listed o? or qo? target names. +# +# snapshot = like dist, but uses a zip file name which clearly +# marks it as a prerelease/snapshot build. # # clean = clean up +# +# Required tools beyond those needed for the canonical builds: +# +# - Emscripten SDK: https://emscripten.org/docs/getting_started/downloads.html +# - The bash shell +# - GNU make, GNU sed, GNU awk, GNU grep (all in the $PATH) +# - wasm-strip for release builds: https://github.com/WebAssembly/wabt +# - InfoZip for 'dist' zip file ######################################################################## +# +# Significant TODOs for this build include, but are not necessarily +# limited to: +# +# 1) Consolidate the code generation for sqlite3*.*js into a script +# which generates the makefile code, rather than using $(call) and +# $(eval), or at least centralize the setup of the numerous vars +# related to each build variant (vanilla, esm, bundler-friendly). +# SHELL := $(shell which bash 2>/dev/null) MAKEFILE := $(lastword $(MAKEFILE_LIST)) CLEAN_FILES := @@ -34,6 +59,20 @@ emcc.bin ?= $(word 1,$(wildcard $(EMSDK_HOME)/upstream/emscripten/emcc) $(shell ifeq (,$(emcc.bin)) $(error Cannot find emcc.) endif +emcc.version := $(shell "$(emcc.bin)" --version | sed -n 1p \ + | sed -e 's/^.* \([3-9][^ ]*\) .*$$/\1/;') +ifeq (,$(emcc.version)) + $(warning Cannot determine emcc version. This might unduly impact build flags.) +else + $(info using emcc version [$(emcc.version)]) +endif +emcc.version := $(shell "$(emcc.bin)" --version | sed -n 1p \ + | sed -e 's/^.* \([3-9][^ ]*\) .*$$/\1/;') +ifeq (,$(emcc.version)) + $(warning Cannot determine emcc version. This might unduly impact build flags.) +else + $(info using emcc version [$(emcc.version)]) +endif wasm-strip ?= $(shell which wasm-strip 2>/dev/null) ifeq (,$(filter clean,$(MAKECMDGOALS))) @@ -71,17 +110,19 @@ dir.jacc := jaccwabyt dir.common := common dir.fiddle := fiddle dir.tool := $(dir.top)/tool +CLEAN_FILES += *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~ $(dir.fiddle)/*~ + ######################################################################## # dir.dout = output dir for deliverables. # -# MAINTENANCE REMINDER: the output .js and .wasm files of emcc must be -# in _this_ dir, rather than a subdir, or else parts of the generated -# code get confused and cannot load property. Specifically, when X.js -# loads X.wasm, whether or not X.js uses the correct path for X.wasm -# depends on how it's loaded: an HTML script tag will resolve it -# intuitively, whereas a Worker's call to importScripts() will not. -# That's a fundamental incompatibility with how URL resolution in -# JS happens between those two contexts. See: +# MAINTENANCE REMINDER: the output .js and .wasm files of certain emcc +# buildables must be in _this_ dir, rather than a subdir, or else +# parts of the generated code get confused and cannot load +# property. Specifically, when X.js loads X.wasm, whether or not X.js +# uses the correct path for X.wasm depends on how it's loaded: an HTML +# script tag will resolve it intuitively, whereas a Worker's call to +# importScripts() will not. That's a fundamental incompatibility with +# how URL resolution in JS happens between those two contexts. See: # # https://zzz.buzz/2017/03/14/relative-uris-in-web-development/ # @@ -104,13 +145,12 @@ ifeq (,$(wildcard $(dir.tmp))) dir._tmp := $(shell mkdir -p $(dir.tmp)) endif -cflags.common := -I. -I.. -I$(dir.top) -CLEAN_FILES += *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~ -emcc.WASM_BIGINT ?= 1 sqlite3.c := $(dir.top)/sqlite3.c sqlite3.h := $(dir.top)/sqlite3.h +# Most SQLITE_OPT flags are set in sqlite3-wasm.c but we need them +# made explicit here for building speedtest1.c. SQLITE_OPT = \ - -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_FTS5 \ -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \ @@ -130,16 +170,100 @@ SQLITE_OPT = \ '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \ -DSQLITE_USE_URI=1 \ -DSQLITE_WASM_ENABLE_C_TESTS -# ^^^ most flags are set in sqlite3-wasm.c but we need them -# made explicit here for building speedtest1.c. -ifneq (,$(filter release,$(MAKECMDGOALS))) -emcc_opt ?= -Oz -flto +$(sqlite3.c) $(sqlite3.h): + $(MAKE) -C $(dir.top) sqlite3.c + +.PHONY: clean distclean +clean: + -rm -f $(CLEAN_FILES) +distclean: clean + -rm -f $(DISTCLEAN_FILES) + +ifeq (release,$(filter release,$(MAKECMDGOALS))) + ifeq (,$(wasm-strip)) + $(error Cannot make release-quality binary because wasm-strip is not available. \ + See notes in the warning above) + endif else -emcc_opt ?= -O0 -# ^^^^ build times for -O levels higher than 0 are painful at -# dev-time. + $(info Development build. Use '$(MAKE) release' for a smaller release build.) endif + +# bin.version-info = binary to output various sqlite3 version info for +# embedding in the JS files and in building the distribution zip file. +# It must NOT be in $(dir.tmp) because we need it to survive the +# cleanup process for the dist build to work properly. +bin.version-info := $(dir.wasm)/version-info +$(bin.version-info): $(dir.wasm)/version-info.c $(sqlite3.h) $(MAKEFILE) + $(CC) -O0 -I$(dir.top) -o $@ $< +DISTCLEAN_FILES += $(bin.version-info) + +# bin.stripcomments is used for stripping C/C++-style comments from JS +# files. The JS files contain large chunks of documentation which we +# don't need for all builds. That app's -k flag is of particular +# importance here, as it allows us to retain the opening comment +# blocks, which contain the license header and version info. +bin.stripccomments := $(dir.tool)/stripccomments +$(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE) + $(CC) -o $@ $< +DISTCLEAN_FILES += $(bin.stripccomments) + + +######################################################################## +# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via ./c-pp -f +# $(1) ... +# +# Historical notes: +# +# - We first attempted to use gcc and/or clang to preprocess JS files +# in the same way we would normally do C files, but C-specific quirks +# of each makes that untennable. +# +# - We implemented c-pp.c (the C-Minus Pre-processor) as a custom +# generic/file-format-agnostic preprocessor to enable us to pack +# code for different target builds into the same JS files. Most +# notably, some ES6 module (a.k.a. ESM) features cannot legally be +# referenced at all in non-ESM code, e.g. the "import" and "export" +# keywords. This preprocessing step permits us to swap out sections +# of code where necessary for ESM and non-ESM (a.k.a. vanilla JS) +# require different implementations. The alternative to such +# preprocessing, would be to have separate source files for ES6 +# builds, which would have a higher maintenance burden than c-pp.c +# seems likely to. +# +# c-pp.c was written specifically for the sqlite project's JavaScript +# builds but is maintained as a standalone project: +# https://fossil.wanderinghorse.net/r/c-pp +bin.c-pp := ./c-pp +$(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE) + $(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \ + -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \ + -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \ + -DSQLITE_TEMP_STORE=3 +define C-PP.FILTER +# Create $2 from $1 using $(bin.c-pp) +# $1 = Input file: c-pp -f $(1).js +# $2 = Output file: c-pp -o $(2).js +# $3 = optional c-pp -D... flags +$(2): $(1) $$(MAKEFILE) $$(bin.c-pp) + $$(bin.c-pp) -f $(1) -o $$@ $(3) +CLEAN_FILES += $(2) +endef +# /end C-PP.FILTER +######################################################################## + +# cflags.common = C compiler flags for all builds +cflags.common := -I. -I.. -I$(dir.top) +# emcc.WASM_BIGINT = 1 for BigInt (C int64) support, else 0. The API +# disables certain features if BigInt is not enabled and such builds +# _are not tested_ on any regular basis. +emcc.WASM_BIGINT ?= 1 + +# emcc_opt = optimization-related flags. These are primarily used by +# the various oX targets. build times for -O levels higher than 0 are +# painful at dev-time. +emcc_opt ?= -O0 + # When passing emcc_opt from the CLI, += and re-assignment have no # effect, so emcc_opt+=-g3 doesn't work. So... emcc_opt_full := $(emcc_opt) -g3 @@ -165,50 +289,29 @@ emcc_opt_full := $(emcc_opt) -g3 # speed for the unit tests is all over the place either way so it's # difficult to say whether -Os gives any speed benefit over -Oz. # -# (Much later: -O2 consistently gives the best speeds.) +# Much practice has demonstrated that -O2 consistently gives the best +# runtime speeds, but not by a large enough factor to rule out use of +# -Oz when small deliverable size is a priority. ######################################################################## - -$(sqlite3.c) $(sqlite3.h): - $(MAKE) -C $(dir.top) sqlite3.c - -.PHONY: clean distclean -clean: - -rm -f $(CLEAN_FILES) -distclean: clean - -rm -f $(DISTCLEAN_FILES) - -ifeq (release,$(filter release,$(MAKECMDGOALS))) - ifeq (,$(wasm-strip)) - $(error Cannot make release-quality binary because wasm-strip is not available. \ - See notes in the warning above) - endif -else - $(info Development build. Use '$(MAKE) release' for a smaller release build.) -endif - -bin.version-info := $(dir.wasm)/version-info -# ^^^^ NOT in $(dir.tmp) because we need it to survive the cleanup -# process for the dist build to work properly. -$(bin.version-info): $(dir.wasm)/version-info.c $(sqlite3.h) $(MAKEFILE) - $(CC) -O0 -I$(dir.top) -o $@ $< -DISTCLEAN_FILES += $(bin.version-info) - -bin.stripccomments := $(dir.tool)/stripccomments -$(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE) - $(CC) -o $@ $< -DISTCLEAN_FILES += $(bin.stripccomments) - +# EXPORTED_FUNCTIONS.* = files for use with Emscripten's +# -sEXPORTED_FUNCTION flag. EXPORTED_FUNCTIONS.api.in := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api $(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE) - cat $(EXPORTED_FUNCTIONS.api.in) > $@ + cp $(EXPORTED_FUNCTIONS.api.in) $@ +# sqlite3-license-version.js = generated JS file with the license +# header and version info. sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js +# sqlite3-license-version-header.js = JS file containing only the +# license header. sqlite3-license-version-header.js := $(dir.api)/sqlite3-license-version-header.js +# sqlite3-api-build-version.js = generated JS file which populates the +# sqlite3.version object using $(bin.version-info). sqlite3-api-build-version.js := $(dir.tmp)/sqlite3-api-build-version.js -# sqlite3-api.jses = the list of JS files which make up $(sqlite3-api.js), in -# the order they need to be assembled. +# sqlite3-api.jses = the list of JS files which make up +# $(sqlite3-api.js.in), in the order they need to be assembled. sqlite3-api.jses := $(sqlite3-license-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js sqlite3-api.jses += $(dir.common)/whwasmutil.js @@ -217,25 +320,51 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js sqlite3-api.jses += $(sqlite3-api-build-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js -sqlite3-api.jses += $(dir.api)/sqlite3-api-opfs.js +sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js +sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js # "External" API files which are part of our distribution # but not part of the sqlite3-api.js amalgamation. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js -sqlite3-worker1.js := $(dir.api)/sqlite3-worker1.js -sqlite3-worker1-promiser.js := $(dir.api)/sqlite3-worker1-promiser.js -define CP_XAPI +# COPY_XAPI = a $(call)able function to copy $1 to $(dir.dout), where +# $1 must be one of the "external" JS API files. +define COPY_XAPI sqlite3-api.ext.jses += $$(dir.dout)/$$(notdir $(1)) $$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE) cp $$< $$@ endef -$(foreach X,$(SOAP.js) $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js),\ - $(eval $(call CP_XAPI,$(X)))) -all: $(sqlite3-api.ext.jses) +$(foreach X,$(SOAP.js),\ + $(eval $(call COPY_XAPI,$(X)))) +all quick: $(sqlite3-api.ext.jses) +q: quick -sqlite3-api.js := $(dir.tmp)/sqlite3-api.js -$(sqlite3-api.js): $(sqlite3-api.jses) $(MAKEFILE) +######################################################################## +# $(sqlite3-api*.*js) contain the core library code but not the +# Emscripten-related glue which deals with loading sqlite3.wasm. In +# theory they can be used by arbitrary build environments and WASM +# loaders, but in practice that breaks down because the WASM loader +# has to be able to provide all of the necessary "imports" to +# sqlite3.wasm, and that list of imports is unknown until sqlite3.wasm +# is compiled, at which point Emscripten sets up the imports +# appropriately. Abstractly speaking, it's impossible for other build +# environments to know exactly which imports are needed and provide +# them. Tools like wasm-objdump can be used to find the list of +# imports but it's questionable whether a non-Emscripten tool could +# realistically use that info to provide proper implementations. +# Sidebar: some of the imports are used soley by the Emscripten glue, +# which the sqlite3 JS code does not rely on. +# +# We build $(sqlite3-api*.*) "because we can" and because it might be +# a useful point of experimentation for some clients, but the +# above-described caveat may well make them unusable for real-life +# clients. +# +# sqlite3-api.js.in = the generated sqlite3-api.js before it gets +# preprocessed. It contains all of $(sqlite3-api.jses) but none of the +# Emscripten-specific headers and footers. +sqlite3-api.js.in := $(dir.tmp)/sqlite3-api.c-pp.js +$(sqlite3-api.js.in): $(sqlite3-api.jses) $(MAKEFILE) @echo "Making $@..." @for i in $(sqlite3-api.jses); do \ echo "/* BEGIN FILE: $$i */"; \ @@ -243,101 +372,43 @@ $(sqlite3-api.js): $(sqlite3-api.jses) $(MAKEFILE) echo "/* END FILE: $$i */"; \ done > $@ -$(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) - @echo "Making $@..." - @{ \ - echo 'self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ - echo -n ' sqlite3.version = '; \ - $(bin.version-info) --json; \ - echo ';'; \ - echo '});'; \ - } > $@ - -######################################################################## -# --post-js and --pre-js are emcc flags we use to append/prepend JS to -# the generated emscripten module file. -pre-js.js := $(dir.api)/pre-js.js -post-js.js := $(dir.tmp)/post-js.js -post-jses := \ - $(dir.api)/post-js-header.js \ - $(sqlite3-api.js) \ - $(dir.api)/post-js-footer.js -$(post-js.js): $(post-jses) $(MAKEFILE) - @echo "Making $@..." - @for i in $(post-jses); do \ - echo "/* BEGIN FILE: $$i */"; \ - cat $$i; \ - echo "/* END FILE: $$i */"; \ - done > $@ -extern-post-js.js := $(dir.api)/extern-post-js.js -extern-pre-js.js := $(dir.api)/extern-pre-js.js -pre-post-common.flags := \ - --post-js=$(post-js.js) \ - --extern-post-js=$(extern-post-js.js) \ - --extern-pre-js=$(sqlite3-license-version.js) -pre-post-jses.deps := $(post-js.js) \ - $(extern-post-js.js) $(extern-pre-js.js) $(sqlite3-license-version.js) -$(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) $(MAKEFILE) - @echo "Making $@..."; { \ - cat $(sqlite3-license-version-header.js); \ - echo '/*'; \ - echo '** This code was built from sqlite3 version...'; \ - echo "** "; \ - awk -e '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' \ - -e '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ - echo '*/'; \ - } > $@ - -######################################################################## -# call-make-pre-js creates rules for pre-js-$(1).js. $1 = the base -# name of the JS file on whose behalf this pre-js is for. -define call-make-pre-js -pre-post-$(1).flags ?= -$$(dir.tmp)/pre-js-$(1).js: $$(pre-js.js) $$(MAKEFILE) - cp $$(pre-js.js) $$@ - @if [ sqlite3-wasmfs = $(1) ]; then \ - echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \ - elif [ sqlite3 != $(1) ]; then \ - echo "Module[xNameOfInstantiateWasm].uri = '$(1).wasm';"; \ - fi >> $$@ -pre-post-$(1).deps := $$(pre-post-jses.deps) $$(dir.tmp)/pre-js-$(1).js -pre-post-$(1).flags += --pre-js=$$(dir.tmp)/pre-js-$(1).js -endef -#$(error $(call call-make-pre-js,sqlite3-wasmfs)) -# /post-js and pre-js -######################################################################## - ######################################################################## # emcc flags for .c/.o/.wasm/.js. emcc.flags := -#emcc.flags += -v # _very_ loud but also informative about what it's doing -# -g3 is needed to keep -O2 and higher from creating broken JS via -# minification. +ifeq (1,$(emcc.verbose)) +emcc.flags += -v +# -v is _very_ loud but also informative about what it's doing +endif ######################################################################## # emcc flags for .c/.o. emcc.cflags := emcc.cflags += -std=c99 -fPIC -# -------------^^^^^^^^ we currently need c99 for WASM-specific sqlite3 APIs. +# -------------^^^^^^^^ we need c99 for $(sqlite3-wasm.c). emcc.cflags += -I. -I$(dir.top) - ######################################################################## -# emcc flags specific to building the final .js/.wasm file... +# emcc flags specific to building .js/.wasm files... emcc.jsflags := -fPIC emcc.jsflags += --minify 0 emcc.jsflags += --no-entry +emcc.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) emcc.jsflags += -sMODULARIZE -emcc.jsflags += -sSTRICT_JS emcc.jsflags += -sDYNAMIC_EXECUTION=0 emcc.jsflags += -sNO_POLYFILL emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.api) emcc.exportedRuntimeMethods := \ - -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory - # FS ==> stdio/POSIX I/O proxies + -sEXPORTED_RUNTIME_METHODS=wasmMemory # wasmMemory ==> required by our code for use with -sIMPORTED_MEMORY emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY +ifeq (3.1.31,$(emcc.version)) + emcc.jsflags += -sSTRICT_JS=0 + $(warning Disabling -sSTRICT_JS for emcc $(emcc.version): \ + https://github.com/emscripten-core/emscripten/issues/18610) +else + emcc.jsflags += -sSTRICT_JS=1 +endif emcc.environment := -sENVIRONMENT=web,worker ######################################################################## # -sINITIAL_MEMORY: How much memory we need to start with is governed @@ -366,33 +437,48 @@ emcc.jsflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) ######################################################################## emcc.jsflags += $(emcc.environment) -#emcc.jsflags += -sTOTAL_STACK=4194304 - +emcc.jsflags += -sSTACK_SIZE=512KB +# ^^^ ACHTUNG: emsdk 3.1.27 reduced the default stack size from 5MB to +# a mere 64KB, which leads to silent memory corruption via the kvvfs +# VFS, which requires twice that for its xRead() and xWrite() methods. +######################################################################## +# $(sqlite3.js.init-func) is the name Emscripten assigns our exported +# module init/load function. This symbol name is hard-coded in +# $(extern-post-js.js) as well as in numerous docs. +# +# "sqlite3InitModule" is the symbol we document for client use, so +# that's the symbol name which must be exported, whether it comes from +# Emscripten or our own code in extern-post-js.js. +# +# That said... we can change $(sqlite3.js.init-func) as long as the +# name "sqlite3InitModule" is the one which gets exposed via the +# resulting JS files. That can be accomplished via +# extern-post-js.js. However... using a temporary symbol name here +# and then adding sqlite3InitModule() ourselves results in 2 global +# symbols: we cannot "delete" the Emscripten-defined +# $(sqlite3.js.init-func) because it's declared with "var". sqlite3.js.init-func := sqlite3InitModule -# ^^^^ $(sqlite3.js.init-func) symbol name is hard-coded in -# $(extern-post-js.js) as well as in numerous docs. If changed, it -# needs to be globally modified in *.js and all related documentation. - emcc.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. #emcc.jsflags += -sSTRICT # fails due to missing __syscall_...() #emcc.jsflags += -sALLOW_UNIMPLEMENTED_SYSCALLS #emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API -#emcc.jsflags += -sABORTING_MALLOC +#emcc.jsflags += -sABORTING_MALLOC # only for experimentation emcc.jsflags += -sALLOW_TABLE_GROWTH -# -sALLOW_TABLE_GROWTH is required for installing new SQL UDFs +# ^^^^ -sALLOW_TABLE_GROWTH is required for installing new SQL UDFs emcc.jsflags += -Wno-limited-postlink-optimizations -# ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag. -#emcc.jsflags += -sSTANDALONE_WASM # causes OOM errors, not sure why -# https://lld.llvm.org/WebAssembly.html -emcc.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=0 +# ^^^^ emcc likes to warn when we have "limited optimizations" via the +# -g3 flag. +# emcc.jsflags += -sSTANDALONE_WASM # causes OOM errors, not sure why. + +# Re. undefined symbol handling, see: https://lld.llvm.org/WebAssembly.html +emcc.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=1 emcc.jsflags += -sLLD_REPORT_UNDEFINED #emcc.jsflags += --allow-undefined #emcc.jsflags += --import-undefined #emcc.jsflags += --unresolved-symbols=import-dynamic --experimental-pic #emcc.jsflags += --experimental-pic --unresolved-symbols=ingore-all --import-undefined #emcc.jsflags += --unresolved-symbols=ignore-all -emcc.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) ######################################################################## # -sMEMORY64=1 fails to load, erroring with: @@ -404,62 +490,255 @@ emcc.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) # new Uint8Array(wasm.heap8u().buffer, ptr, n) # # because ptr is now a BigInt, so is invalid for passing to arguments -# which have strict must-be-a-Number requirements. +# which have strict must-be-a-Number requirements. That aspect will +# make any eventual port to 64-bit address space extremely painful, as +# such constructs are found all over the place in the source code. ######################################################################## - ######################################################################## # -sSINGLE_FILE: -# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js#L1704 -# -sSINGLE_FILE=1 would be really nice but we have to build with -g3 +# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js +# +# -sSINGLE_FILE=1 would be _really_ nice but we have to build with -g3 # for -O2 and higher to work (else minification breaks the code) and # cannot wasm-strip the binary before it gets encoded into the JS -# file. The result is that the generated JS file is, because of the -g3 -# debugging info, _huge_. +# file. The result is that the generated JS file is, because of the +# -g3 debugging info, _huge_. ######################################################################## +$(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) + @echo "Making $@..." + @{ \ + echo 'self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ + echo -n ' sqlite3.version = '; \ + $(bin.version-info) --json; \ + echo ';'; \ + echo '});'; \ + } > $@ +$(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) \ + $(MAKEFILE) + @echo "Making $@..."; { \ + cat $(sqlite3-license-version-header.js); \ + echo '/*'; \ + echo '** This code was built from sqlite3 version...'; \ + echo "**"; \ + awk -e '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' \ + -e '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ + echo "**"; \ + echo "** Using the Emscripten SDK version $(emcc.version)."; \ + echo '*/'; \ + } > $@ + ######################################################################## -# AN EXPERIMENT: undocumented Emscripten feature: if the target file -# extension is "mjs", it defaults to ES6 module builds: -# https://github.com/emscripten-core/emscripten/issues/14383 -ifeq (,$(filter esm,$(MAKECMDGOALS))) -sqlite3.js.ext := js -else -esm.deps := $(filter-out esm,$(MAKECMDGOALS)) -esm: $(if $(esm.deps),$(esm.deps),all) -sqlite3.js.ext := mjs -endif -# /esm +# --post-js and --pre-js are emcc flags we use to append/prepend JS to +# the generated emscripten module file. These rules set up the core +# pre/post files for use by the various builds. +pre-js.js.in := $(dir.api)/pre-js.c-pp.js +post-js.js.in := $(dir.tmp)/post-js.c-pp.js +post-jses.js := \ + $(dir.api)/post-js-header.js \ + $(sqlite3-api.js.in) \ + $(dir.api)/post-js-footer.js +$(post-js.js.in): $(post-jses.js) $(MAKEFILE) + @echo "Making $@..." + @for i in $(post-jses.js); do \ + echo "/* BEGIN FILE: $$i */"; \ + cat $$i; \ + echo "/* END FILE: $$i */"; \ + done > $@ + + ######################################################################## -sqlite3.js := $(dir.dout)/sqlite3.$(sqlite3.js.ext) +# call-make-pre-post is a $(call)able which creates rules for +# pre-js-$(1).js. $1 = the base name of the JS file on whose behalf +# this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is the build +# mode: one of (vanilla, esm, bundler-friendly). This sets up +# --[extern-][pre/post]-js flags in $(pre-post-$(1).flags.$(2)) and +# dependencies in $(pre-post-$(1).deps.$(2)). +define call-make-pre-post +pre-post-$(1).flags.$(2) ?= +$$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(2)) $$(MAKEFILE) + cp $$(pre-js.js.$(2)) $$@ + @if [ sqlite3-wasmfs = $(1) ]; then \ + echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \ + elif [ sqlite3 != $(1) ]; then \ + echo "Module[xNameOfInstantiateWasm].uri = '$(1).wasm';"; \ + fi >> $$@ +pre-post-$(1).deps.$(2) := \ + $$(pre-post-jses.deps.$(2)) \ + $$(dir.tmp)/pre-js-$(1)-$(2).js +pre-post-$(1).flags.$(2) += \ + $$(pre-post-common.flags.$(2)) \ + --pre-js=$$(dir.tmp)/pre-js-$(1)-$(2).js +endef +# /post-js and pre-js +######################################################################## + +# Undocumented Emscripten feature: if the target file extension is +# "mjs", it defaults to ES6 module builds: +# https://github.com/emscripten-core/emscripten/issues/14383 sqlite3.wasm := $(dir.dout)/sqlite3.wasm sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c # sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter -# (predictably) results in a slightly faster binary, but we're close +# (predictably) results in a slightly faster binary. We're close # enough to the target speed requirements that the 500ms makes a -# difference. Thus we build all binaries against sqlite3-wasm.c -# instead of building a shared copy of sqlite3-wasm.o. -$(eval $(call call-make-pre-js,sqlite3)) -$(sqlite3.js): -$(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \ - $(EXPORTED_FUNCTIONS.api) \ - $(pre-post-sqlite3.deps) - @echo "Building $@ ..." - $(emcc.bin) -o $@ $(emcc_opt_full) $(emcc.flags) \ - $(emcc.jsflags) $(pre-post-common.flags) $(pre-post-sqlite3.flags) \ - $(cflags.common) $(SQLITE_OPT) $(sqlite3-wasm.c) - chmod -x $(sqlite3.wasm) - $(maybe-wasm-strip) $(sqlite3.wasm) - @ls -la $@ $(sqlite3.wasm) +# difference, so we build all binaries against sqlite3-wasm.c instead +# of building a shared copy of sqlite3-wasm.o to link against. +######################################################################## +# SQLITE3.xJS.EXPORT-DEFAULT is part of SQLITE3-WASMFS.xJS.RECIPE and +# SETUP_LIB_BUILD_MODE, factored into a separate piece to avoid code +# duplication. $1 is 1 if the build mode needs this workaround (esm, +# bundler-friendly) and 0 if not (vanilla). +# +# Reminder for ESM builds: even if we use -sEXPORT_ES6=0, emcc _still_ +# adds: +# +# export default $(sqlite3.js.init-func); +# +# when building *.mjs, which is bad because we need to export an +# overwritten version of that function and cannot "export default" +# twice. Because of this, we have to sed *.mjs to remove the _first_ +# instance (only) of /^export default/. +# +# Upstream RFE: +# https://github.com/emscripten-core/emscripten/issues/18237 +define SQLITE3.xJS.ESM-EXPORT-DEFAULT +if [ x1 = x$(1) ]; then \ + echo "Fragile workaround for an Emscripten annoyance. See SQLITE3.xJS.RECIPE."; \ + sed -i -e '0,/^export default/{/^export default/d;}' $@ || exit $$?; \ + if ! grep -q '^export default' $@; then \ + echo "Cannot find export default." 1>&2; \ + exit 1; \ + fi; \ +fi +endef + +# extern-post-js* and extern-pre-js* are files for use with +# Emscripten's --extern-pre-js and --extern-post-js flags. +extern-pre-js.js := $(dir.api)/extern-pre-js.js +extern-post-js.js.in := $(dir.api)/extern-post-js.c-pp.js +# Emscripten flags for --[extern-][pre|post]-js=... for the +# various builds. +pre-post-common.flags := \ + --extern-pre-js=$(sqlite3-license-version.js) +# pre-post-jses.deps.* = a list of dependencies for the +# --[extern-][pre/post]-js files. +pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) +######################################################################## +# SETUP_LIB_BUILD_MODE is a $(call)'able which sets up numerous pieces +# for one of the build modes (vanilla, esm, bundler-friendly). +# +# $1 = build mode name +# $2 = 1 for ESM build mode, else 0 +# $3 = resulting sqlite-api JS/MJS file +# $4 = resulting JS/MJS file +# $5 = -D... flags for $(bin.c-pp) +# $6 = emcc -sXYZ flags +define SETUP_LIB_BUILD_MODE +$(info Setting up build [$(1)]: $(4)) +c-pp.D.$(1) := $(5) +pre-js.js.$(1) := $$(dir.api)/pre-js.$(1).js +$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)),$$(c-pp.D.$(1)))) +post-js.js.$(1) := $$(dir.tmp)/post-js.$(1).js +$$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)),$$(c-pp.D.$(1)))) +extern-post-js.js.$(1) := $$(dir.tmp)/extern-post-js.$(1).js +$$(eval $$(call C-PP.FILTER,$$(extern-post-js.js.in),$$(extern-post-js.js.$(1)),$$(c-pp.D.$(1)))) +pre-post-common.flags.$(1) := \ + $$(pre-post-common.flags) \ + --post-js=$$(post-js.js.$(1)) \ + --extern-post-js=$$(extern-post-js.js.$(1)) +pre-post-jses.deps.$(1) := $$(pre-post-jses.deps.common) \ + $$(post-js.js.$(1)) $$(extern-post-js.js.$(1)) +$$(eval $$(call call-make-pre-post,sqlite3,$(1))) +emcc.flags.sqlite3.$(1) := $(6) +$$(eval $$(call C-PP.FILTER, $$(sqlite3-api.js.in), $(3), $(5))) +$(4): $(3) +$(4): $(3) $$(MAKEFILE) $$(sqlite3-wasm.c) $$(EXPORTED_FUNCTIONS.api) $$(pre-post-sqlite3.deps.$(1)) + @echo "Building $$@ ..." + $$(emcc.bin) -o $$@ $$(emcc_opt_full) $$(emcc.flags) \ + $$(emcc.jsflags) \ + $$(pre-post-sqlite3.flags.$(1)) $$(emcc.flags.sqlite3.$(1)) \ + $$(cflags.common) $$(SQLITE_OPT) $$(sqlite3-wasm.c) + @$$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(2)) + @if [ bundler-friendly = $(1) ]; then \ + echo "Patching $(3) for sqlite3.wasm..."; \ + rm -f $$(dir.dout)/sqlite3-bundler-friendly.wasm; \ + sed -i -e 's/sqlite3-bundler-friendly.wasm/sqlite3.wasm/g' $$@ || exit $$$$?; \ + fi + chmod -x $$(sqlite3.wasm) + $$(maybe-wasm-strip) $$(sqlite3.wasm) + @ls -la $@ $$(sqlite3.wasm) +all: $(4) +quick: $(4) +CLEAN_FILES += $(3) $(4) +endef +# ^^^ /SETUP_LIB_BUILD_MODE +######################################################################## +sqlite3-api.js := $(dir.dout)/sqlite3-api.js +sqlite3.js := $(dir.dout)/sqlite3.js +sqlite3-api.mjs := $(dir.dout)/sqlite3-api.mjs +sqlite3.mjs := $(dir.dout)/sqlite3.mjs +sqlite3-api-bundler-friendly.mjs := $(dir.dout)/sqlite3-api-bundler-friendly.mjs +sqlite3-bundler-friendly.mjs := $(dir.dout)/sqlite3-bundler-friendly.mjs +# Maintenance reminder: careful not to introduce spaces around args $1, $2 +#$(info $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) +$(eval $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) +$(eval $(call SETUP_LIB_BUILD_MODE,esm,1, $(sqlite3-api.mjs), $(sqlite3.mjs), \ + -Dtarget=es6-module, -sEXPORT_ES6 -sUSE_ES6_IMPORT_META)) +$(eval $(call SETUP_LIB_BUILD_MODE,bundler-friendly,1,\ + $(sqlite3-api-bundler-friendly.mjs),$(sqlite3-bundler-friendly.mjs),\ + $(c-pp.D.esm) -Dtarget=es6-bundler-friendly, $(emcc.flags.sqlite3.esm))) +# The various -D... values used by *.c-pp.js include: +# +# -Dtarget=es6-module: for all ESM module builds +# +# -Dtarget=es6-module -Dtarget=es6-bundler-friendly: intended for +# "bundler-friendly" ESM module build. These have some restrictions +# on how URL() objects are constructed in some contexts: URLs which +# refer to files which are part of this project must be references +# as string literals so that bundlers' static-analysis tools can +# find those files and include them in their bundles. +# +######################################################################## +######################################################################## +# We have to ensure that we do not build both $(sqlite3*.*js) in +# parallel because both result in the creation of $(sqlite3.wasm). We +# have no way to build just the .mjs file without also building the +# .wasm file because the generated .mjs file has to include info about +# the imports needed by the wasm file, so they have to be built +# together. i.e. we're building $(sqlite3.wasm) multiple times, but +# that's unavoidable (and harmless, just a waste of build time). $(sqlite3.wasm): $(sqlite3.js) -CLEAN_FILES += $(sqlite3.js) $(sqlite3.wasm) -all: $(sqlite3.js) -wasm: $(sqlite3.js) -# End main Emscripten-based module build +$(sqlite3.mjs): $(sqlite3.js) +$(sqlite3-bundler-friendly.mjs): $(sqlite3.mjs) +CLEAN_FILES += $(sqlite3.wasm) + ######################################################################## +# We need separate copies of certain supplementary JS files for the +# bundler-friendly build. Concretely, any supplemental JS files which +# themselves use importScripts() or Workers or URL() constructors +# which refer to other in-tree (m)JS files quire a bundler-friendly +# copy. +sqlite3-worker1.js.in := $(dir.api)/sqlite3-worker1.c-pp.js +sqlite3-worker1-promiser.js.in := $(dir.api)/sqlite3-worker1-promiser.c-pp.js +sqlite3-worker1.js := $(dir.dout)/sqlite3-worker1.js +sqlite3-worker1-promiser.js := $(dir.dout)/sqlite3-worker1-promiser.js +sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-friendly.js +sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js +$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js))) +$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\ + $(c-pp.D.bundler-friendly))) +$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js))) +$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\ + $(sqlite3-worker1-promiser-bundler-friendly.js),\ + $(c-pp.D.bundler-friendly))) +$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \ + $(sqlite3-worker1-promiser-bundler-friendly.js) +$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) ######################################################################## -# batch-runner.js... +# batch-runner.js is part of one of the test apps which reads in SQL +# dumps generated by $(speedtest1) and executes them. dir.sql := sql speedtest1 := ../../speedtest1 speedtest1.c := ../../test/speedtest1.c @@ -481,30 +760,30 @@ batch: batch-runner.list all: batch # end batch-runner.js ######################################################################## -# speedtest1.js... -# speedtest1-common.eflags = emcc flags used by multiple builds of speedtest1 -# speedtest1.eflags = emcc flags used by main build of speedtest1 -speedtest1-common.eflags := $(emcc_opt_full) -speedtest1.eflags := -speedtest1.eflags += -sENVIRONMENT=web -speedtest1.eflags += -sALLOW_MEMORY_GROWTH -speedtest1.eflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) -speedtest1-common.eflags += -sINVOKE_RUN=0 -speedtest1-common.eflags += --no-entry -#speedtest1-common.eflags += -flto -speedtest1-common.eflags += -sABORTING_MALLOC -speedtest1-common.eflags += -sSTRICT_JS -speedtest1-common.eflags += -sMODULARIZE -speedtest1-common.eflags += -Wno-limited-postlink-optimizations +# Wasmified speedtest1 is our primary benchmarking tool. +# +# emcc.speedtest1.common = emcc flags used by multiple builds of speedtest1 +# emcc.speedtest1 = emcc flags used by main build of speedtest1 +emcc.speedtest1.common := $(emcc_opt_full) +emcc.speedtest1 := +emcc.speedtest1 += -sENVIRONMENT=web +emcc.speedtest1 += -sALLOW_MEMORY_GROWTH +emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) +emcc.speedtest1.common += -sINVOKE_RUN=0 +emcc.speedtest1.common += --no-entry +emcc.speedtest1.common += -sABORTING_MALLOC +emcc.speedtest1.common += -sSTRICT_JS +emcc.speedtest1.common += -sMODULARIZE +emcc.speedtest1.common += -Wno-limited-postlink-optimizations EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) -speedtest1-common.eflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) -speedtest1-common.eflags += $(emcc.exportedRuntimeMethods) -speedtest1-common.eflags += -sALLOW_TABLE_GROWTH -speedtest1-common.eflags += -sDYNAMIC_EXECUTION=0 -speedtest1-common.eflags += --minify 0 -speedtest1-common.eflags += -sEXPORT_NAME=$(sqlite3.js.init-func) -speedtest1-common.eflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) -speedtest1-common.eflags += $(pre-post-common.flags) +emcc.speedtest1.common += -sSTACK_SIZE=512KB +emcc.speedtest1.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) +emcc.speedtest1.common += $(emcc.exportedRuntimeMethods) +emcc.speedtest1.common += -sALLOW_TABLE_GROWTH +emcc.speedtest1.common += -sDYNAMIC_EXECUTION=0 +emcc.speedtest1.common += --minify 0 +emcc.speedtest1.common += -sEXPORT_NAME=$(sqlite3.js.init-func) +emcc.speedtest1.common += -sWASM_BIGINT=$(emcc.WASM_BIGINT) speedtest1.exit-runtime0 := -sEXIT_RUNTIME=0 speedtest1.exit-runtime1 := -sEXIT_RUNTIME=1 # Re -sEXIT_RUNTIME=1 vs 0: if it's 1 and speedtest1 crashes, we get @@ -528,17 +807,17 @@ $(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api) @echo "Making $@ ..." @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ speedtest1.js := $(dir.dout)/speedtest1.js -speedtest1.wasm := $(subst .js,.wasm,$(speedtest1.js)) -speedtest1.cflags := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM +speedtest1.wasm := $(dir.dout)/speedtest1.wasm +cflags.speedtest1 := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM speedtest1.cses := $(speedtest1.c) $(sqlite3-wasm.c) -$(eval $(call call-make-pre-js,speedtest1)) +$(eval $(call call-make-pre-post,speedtest1,vanilla)) $(speedtest1.js): $(MAKEFILE) $(speedtest1.cses) \ - $(pre-post-speedtest1.deps) \ + $(pre-post-speedtest1.deps.vanilla) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." $(emcc.bin) \ - $(speedtest1.eflags) $(speedtest1-common.eflags) $(speedtest1.cflags) \ - $(pre-post-speedtest1.flags) \ + $(emcc.speedtest1) $(emcc.speedtest1.common) \ + $(cflags.speedtest1) $(pre-post-speedtest1.flags.vanilla) \ $(SQLITE_OPT) \ $(speedtest1.exit-runtime0) \ -o $@ $(speedtest1.cses) -lm @@ -551,6 +830,31 @@ CLEAN_FILES += $(speedtest1.js) $(speedtest1.wasm) # end speedtest1.js ######################################################################## +######################################################################## +# tester1 is the main unit and regression test application and needs +# to be able to run in 4 separate modes to cover the primary +# client-side use cases: +# +# 1) Load sqlite3 in the main UI thread of a conventional script. +# 2) Load sqlite3 in a conventional Worker thread. +# 3) Load sqlite3 as an ES6 module (ESM) in the main thread. +# 4) Load sqlite3 as an ESM worker. (Not all browsers support this.) +# +# To that end, we require two separate builds of tester1.js: +# +# tester1.js: cases 1 and 2 +# tester1.mjs: cases 3 and 4 +# +# To create those, we filter tester1.c-pp.js with $(bin.c-pp)... +$(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.js)) +$(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.esm))) +$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1.html)) +$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.esm))) +tester1: tester1.js tester1.mjs tester1.html tester1-esm.html +# Note that we do not include $(sqlite3-bundler-friendly.mjs) in this +# because bundlers are client-specific. +all quick: tester1 + ######################################################################## # Convenience rules to rebuild with various -Ox levels. Much # experimentation shows -O2 to be the clear winner in terms of speed. @@ -558,37 +862,47 @@ CLEAN_FILES += $(speedtest1.js) $(speedtest1.wasm) # painful. .PHONY: o0 o1 o2 o3 os oz -o-xtra := -flto +o-xtra := +#o-xtra ?= -flto # ^^^^ -flto can have a considerably performance boost at -O0 but -# doubles the build time and seems to have negligible effect on -# higher optimization levels. +# doubles the build time and seems to have negligible, if any, effect +# on higher optimization levels. o0: clean $(MAKE) -e "emcc_opt=-O0" o1: clean $(MAKE) -e "emcc_opt=-O1 $(o-xtra)" o2: clean - $(MAKE) -e "emcc_opt=-O2 $(o-xtra)" + $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" +qo2: clean + $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" quick o3: clean $(MAKE) -e "emcc_opt=-O3 $(o-xtra)" os: clean @echo "WARNING: -Os can result in a build with mysteriously missing pieces!" $(MAKE) -e "emcc_opt=-Os $(o-xtra)" oz: clean - $(MAKE) -e "emcc_opt=-Oz $(o-xtra)" + $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" +qoz: clean + $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" quick ######################################################################## # Sub-makes... +# sqlite.org/fiddle application... include fiddle.make # Only add wasmfs if wasmfs.enable=1 or we're running (dist)clean +ifneq (,$(filter wasmfs,$(MAKECMDGOALS))) +wasmfs.enable ?= 1 +else wasmfs.enable ?= $(if $(filter %clean,$(MAKECMDGOALS)),1,0) +endif ifeq (1,$(wasmfs.enable)) # wasmfs build disabled 2022-10-19 per /chat discussion. # OPFS-over-wasmfs was initially a stopgap measure and a convenient # point of comparison for the OPFS sqlite3_vfs's performance, but it # currently doubles our deliverables and build maintenance burden for -# little, if any, benefit. +# little benefit. # ######################################################################## # Some platforms do not support the WASMFS build. Raspberry Pi OS is one @@ -607,16 +921,12 @@ endif # /wasmfs ######################################################################## -######################################################################## -# Create deliverables: -ifneq (,$(filter dist,$(MAKECMDGOALS))) -include dist.make -endif - ######################################################################## # Push files to public wasm-testing.sqlite.org server -wasm-testing.include = $(dir.dout) *.js *.html \ - batch-runner.list $(dir.sql) $(dir.common) $(dir.fiddle) $(dir.jacc) +wasm-testing.include = *.js *.mjs *.html \ + ./tests \ + batch-runner.list \ + $(dir.dout) $(dir.sql) $(dir.common) $(dir.fiddle) $(dir.jacc) wasm-testing.exclude = sql/speedtest1.sql wasm-testing.dir = /jail/sites/wasm-testing wasm-testing.dest ?= wasm-testing:$(wasm-testing.dir) @@ -633,23 +943,35 @@ push-testing: ######################################################################## # If we find a copy of the sqlite.org/wasm docs checked out, copy # certain files over to it, noting that some need automatable edits... -WDOCS.home ?= ../../../wdoc +wasm.docs.home ?= ../../../wasm +wasm.docs.found = $(if $(wildcard $(wasm.docs.home)/api-index.md),\ + $(wildcard $(wasm.docs.home)),) .PHONY: update-docs -ifneq (,$(wildcard $(WDOCS.home)/api-index.md)) -WDOCS.jswasm := $(WDOCS.home)/jswasm +ifeq (,$(wasm.docs.found)) +update-docs: + @echo "Cannot find wasm docs checkout."; \ + echo "Pass wasm.docs.home=/path/to/wasm/docs/checkout or edit this makefile to suit."; \ + exit 127 +else +wasm.docs.jswasm := $(wasm.docs.home)/jswasm update-docs: $(bin.stripccomments) $(sqlite3.js) $(sqlite3.wasm) @echo "Copying files to the /wasm docs. Be sure to use an -Oz build for this!" - cp $(sqlite3.wasm) $(WDOCS.jswasm)/. + cp $(sqlite3.wasm) $(wasm.docs.jswasm)/. $(bin.stripccomments) -k -k < $(sqlite3.js) \ - | sed -e '/^[ \t]*$$/d' > $(WDOCS.jswasm)/sqlite3.js - cp demo-123.js demo-123.html demo-123-worker.html $(WDOCS.home) + | sed -e '/^[ \t]*$$/d' > $(wasm.docs.jswasm)/sqlite3.js + cp demo-123.js demo-123.html demo-123-worker.html $(wasm.docs.home) sed -n -e '/EXTRACT_BEGIN/,/EXTRACT_END/p' \ - module-symbols.html > $(WDOCS.home)/module-symbols.html -else -update-docs: - @echo "Cannot find wasm docs checkout."; \ - echo "Pass WDOCS.home=/path/to/wasm/docs/checkout or edit this makefile to suit."; \ - exit 127 + module-symbols.html > $(wasm.docs.home)/module-symbols.html endif # end /wasm docs ######################################################################## + +######################################################################## +# Create main client downloadable zip file: +ifneq (,$(filter dist snapshot,$(MAKECMDGOALS))) +include dist.make +endif + +# Run local web server for the test/demo pages. +httpd: + althttpd -max-age 1 -enable-sab -page index.html diff --git a/ext/wasm/README-dist.txt b/ext/wasm/README-dist.txt index ca6bef93e..6656a2072 100644 --- a/ext/wasm/README-dist.txt +++ b/ext/wasm/README-dist.txt @@ -4,20 +4,43 @@ Main project page: https://sqlite.org Documentation: https://sqlite.org/wasm -This archive contains the sqlite3.js and sqlite3.wasm file which make -up the sqlite3 WASM/JS build. +This archive contains the following deliverables for the WASM/JS +build: -The jswasm directory contains the core sqlite3 deliverables and the -top-level directory contains demonstration and test apps. Browsers -will not serve WASM files from file:// URLs, so the demo/test apps -require a web server and that server must include the following -headers in its response when serving the files: +- jswasm/sqlite3.js is the canonical "vanilla JS" version. + +- jswasm/sqlite3.mjs is the same but in ES6 module form + +- jswasm/*-bundler-friendly.js and .mjs are variants which are + intended to be compatible with "bundler" tools commonly seen in + node.js-based projects. Projects using such tools should use those + variants, where available, instead of files without the + "-bundler-friendly" suffix. Some files do not have separate + variants. + +- jswasm/sqlite3.wasm is the binary WASM file imported by all of the + above-listed JS files. + +- The jswasm directory additionally contains a number of supplemental + JS files which cannot be bundled directly with the main JS files + but are necessary for certain usages. + +- The top-level directory contains various demonstration and test + applications for sqlite3.js and sqlite3.mjs. + sqlite3-bundler-friendly.mjs requires client-side build tools to make + use of and is not demonstrated here. + +Browsers will not serve WASM files from file:// URLs, so the test and +demonstration apps require a web server and that server must include +the following headers in its response when serving the files: Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp +The core library will function without those headers but certain +features, most notably OPFS storage, will not be available. + One simple way to get the demo apps up and running on Unix-style systems is to install althttpd (https://sqlite.org/althttpd) and run: althttpd --enable-sab --page index.html - diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api index b903bedee..ad2872d83 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -1,4 +1,8 @@ +_malloc +_free +_realloc _sqlite3_aggregate_context +_sqlite3_auto_extension _sqlite3_bind_blob _sqlite3_bind_double _sqlite3_bind_int @@ -6,11 +10,16 @@ _sqlite3_bind_int64 _sqlite3_bind_null _sqlite3_bind_parameter_count _sqlite3_bind_parameter_index +_sqlite3_bind_pointer _sqlite3_bind_text +_sqlite3_busy_handler +_sqlite3_busy_timeout +_sqlite3_cancel_auto_extension _sqlite3_changes _sqlite3_changes64 _sqlite3_clear_bindings _sqlite3_close_v2 +_sqlite3_collation_needed _sqlite3_column_blob _sqlite3_column_bytes _sqlite3_column_count @@ -21,16 +30,28 @@ _sqlite3_column_int64 _sqlite3_column_name _sqlite3_column_text _sqlite3_column_type +_sqlite3_column_value +_sqlite3_commit_hook _sqlite3_compileoption_get _sqlite3_compileoption_used +_sqlite3_complete +_sqlite3_context_db_handle +_sqlite3_create_collation +_sqlite3_create_collation_v2 _sqlite3_create_function _sqlite3_create_function_v2 +_sqlite3_create_module +_sqlite3_create_module_v2 _sqlite3_create_window_function _sqlite3_data_count _sqlite3_db_filename _sqlite3_db_handle _sqlite3_db_name +_sqlite3_db_status +_sqlite3_declare_vtab _sqlite3_deserialize +_sqlite3_drop_modules +_sqlite3_errcode _sqlite3_errmsg _sqlite3_error_offset _sqlite3_errstr @@ -41,20 +62,35 @@ _sqlite3_extended_result_codes _sqlite3_file_control _sqlite3_finalize _sqlite3_free +_sqlite3_get_auxdata _sqlite3_initialize +_sqlite3_keyword_count +_sqlite3_keyword_name +_sqlite3_keyword_check +_sqlite3_last_insert_rowid _sqlite3_libversion _sqlite3_libversion_number +_sqlite3_limit _sqlite3_malloc _sqlite3_malloc64 _sqlite3_msize _sqlite3_open _sqlite3_open_v2 +_sqlite3_overload_function _sqlite3_prepare_v2 _sqlite3_prepare_v3 +_sqlite3_preupdate_blobwrite +_sqlite3_preupdate_count +_sqlite3_preupdate_depth +_sqlite3_preupdate_hook +_sqlite3_preupdate_new +_sqlite3_preupdate_old +_sqlite3_progress_handler _sqlite3_randomness _sqlite3_realloc _sqlite3_realloc64 _sqlite3_reset +_sqlite3_reset_auto_extension _sqlite3_result_blob _sqlite3_result_double _sqlite3_result_error @@ -64,17 +100,35 @@ _sqlite3_result_error_toobig _sqlite3_result_int _sqlite3_result_int64 _sqlite3_result_null +_sqlite3_result_pointer +_sqlite3_result_subtype _sqlite3_result_text +_sqlite3_result_zeroblob +_sqlite3_result_zeroblob64 +_sqlite3_rollback_hook _sqlite3_serialize +_sqlite3_set_authorizer +_sqlite3_set_auxdata +_sqlite3_set_last_insert_rowid _sqlite3_shutdown _sqlite3_sourceid _sqlite3_sql +_sqlite3_status +_sqlite3_status64 _sqlite3_step +_sqlite3_stmt_isexplain +_sqlite3_stmt_readonly +_sqlite3_stmt_status _sqlite3_strglob +_sqlite3_stricmp _sqlite3_strlike +_sqlite3_strnicmp +_sqlite3_table_column_metadata _sqlite3_total_changes _sqlite3_total_changes64 _sqlite3_trace_v2 +_sqlite3_txn_state +_sqlite3_update_hook _sqlite3_uri_boolean _sqlite3_uri_int64 _sqlite3_uri_key @@ -83,12 +137,67 @@ _sqlite3_user_data _sqlite3_value_blob _sqlite3_value_bytes _sqlite3_value_double +_sqlite3_value_dup +_sqlite3_value_free +_sqlite3_value_frombind _sqlite3_value_int _sqlite3_value_int64 +_sqlite3_value_nochange +_sqlite3_value_numeric_type +_sqlite3_value_pointer +_sqlite3_value_subtype _sqlite3_value_text _sqlite3_value_type _sqlite3_vfs_find _sqlite3_vfs_register _sqlite3_vfs_unregister -_malloc -_free +_sqlite3_vtab_collation +_sqlite3_vtab_distinct +_sqlite3_vtab_in +_sqlite3_vtab_in_first +_sqlite3_vtab_in_next +_sqlite3_vtab_nochange +_sqlite3_vtab_on_conflict +_sqlite3_vtab_rhs_value +_sqlite3changegroup_add +_sqlite3changegroup_add_strm +_sqlite3changegroup_delete +_sqlite3changegroup_new +_sqlite3changegroup_output +_sqlite3changegroup_output_strm +_sqlite3changeset_apply +_sqlite3changeset_apply_strm +_sqlite3changeset_apply_v2 +_sqlite3changeset_apply_v2_strm +_sqlite3changeset_concat +_sqlite3changeset_concat_strm +_sqlite3changeset_conflict +_sqlite3changeset_finalize +_sqlite3changeset_fk_conflicts +_sqlite3changeset_invert +_sqlite3changeset_invert_strm +_sqlite3changeset_new +_sqlite3changeset_next +_sqlite3changeset_old +_sqlite3changeset_op +_sqlite3changeset_pk +_sqlite3changeset_start +_sqlite3changeset_start_strm +_sqlite3changeset_start_v2 +_sqlite3changeset_start_v2_strm +_sqlite3session_attach +_sqlite3session_changeset +_sqlite3session_changeset_size +_sqlite3session_changeset_strm +_sqlite3session_config +_sqlite3session_create +_sqlite3session_delete +_sqlite3session_diff +_sqlite3session_enable +_sqlite3session_indirect +_sqlite3session_isempty +_sqlite3session_memory_used +_sqlite3session_object_config +_sqlite3session_patchset +_sqlite3session_patchset_strm +_sqlite3session_table_filter diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md index 6440eba99..6618666ae 100644 --- a/ext/wasm/api/README.md +++ b/ext/wasm/api/README.md @@ -17,19 +17,20 @@ multiple JS files because: to be loaded as JS Workers. Note that the structure described here is the current state of things, -not necessarily the "final" state. +as of this writing, but is not set in stone forever and may change +at any time. The overall idea is that the following files get concatenated together, in the listed order, the resulting file is loaded by a browser client: -- `sqlite3-api-prologue.js`\ +- **`sqlite3-api-prologue.js`**\ Contains the initial bootstrap setup of the sqlite3 API objects. This is exposed as a function, rather than objects, so that the next step can pass in a config object which abstracts away parts of the WASM environment, to facilitate plugging it in to arbitrary WASM toolchains. -- `../common/whwasmutil.js`\ +- **`../common/whwasmutil.js`**\ A semi-third-party collection of JS/WASM utility code intended to replace much of the Emscripten glue. The sqlite3 APIs internally use these APIs instead of their Emscripten counterparts, in order to be @@ -38,47 +39,54 @@ browser client: toolchains. It is "semi-third-party" in that it was created in order to support this tree but is standalone and maintained together with... -- `../jaccwabyt/jaccwabyt.js`\ +- **`../jaccwabyt/jaccwabyt.js`**\ Another semi-third-party API which creates bindings between JS and C structs, such that changes to the struct state from either JS or C are visible to the other end of the connection. This is also an independent spinoff project, conceived for the sqlite3 project but maintained separately. -- `sqlite3-api-glue.js`\ - Invokes functionality exposed by the previous two files to - flesh out low-level parts of `sqlite3-api-prologue.js`. Most of - these pieces related to the `sqlite3.capi.wasm` object. -- `sqlite3-api-build-version.js`\ +- **`sqlite3-api-glue.js`**\ + Invokes functionality exposed by the previous two files to flesh out + low-level parts of `sqlite3-api-prologue.js`. Most of these pieces + related to populating the `sqlite3.capi.wasm` object. This file + also deletes most global-scope symbols the above files create, + effectively moving them into the scope being used for initializing + the API. +- **`/sqlite3-api-build-version.js`**\ Gets created by the build process and populates the `sqlite3.version` object. This part is not critical, but records the version of the library against which this module was built. -- `sqlite3-api-oo1.js`\ +- **`sqlite3-api-oo1.js`**\ Provides a high-level object-oriented wrapper to the lower-level C API, colloquially known as OO API #1. Its API is similar to other high-level sqlite3 JS wrappers and should feel relatively familiar to anyone familiar with such APIs. That said, it is not a "required component" and can be elided from builds which do not want it. -- `sqlite3-api-worker1.js`\ +- **`sqlite3-api-worker1.js`**\ A Worker-thread-based API which uses OO API #1 to provide an interface to a database which can be driven from the main Window thread via the Worker message-passing interface. Like OO API #1, this is an optional component, offering one of any number of potential implementations for such an API. - - `sqlite3-worker1.js`\ + - **`sqlite3-worker1.js`**\ Is not part of the amalgamated sources and is intended to be loaded by a client Worker thread. It loads the sqlite3 module and runs the Worker #1 API which is implemented in `sqlite3-api-worker1.js`. - - `sqlite3-worker1-promiser.js`\ + - **`sqlite3-worker1-promiser.js`**\ Is likewise not part of the amalgamated sources and provides a Promise-based interface into the Worker #1 API. This is a far user-friendlier way to interface with databases running in a Worker thread. -- `sqlite3-api-opfs.js`\ +- **`sqlite3-v-helper.js`**\ + Installs `sqlite3.vfs` and `sqlite3.vtab`, namespaces which contain + helpers for use by downstream code which creates `sqlite3_vfs` + and `sqlite3_module` implementations. +- **`sqlite3-vfs-opfs.c-pp.js`**\ is an sqlite3 VFS implementation which supports Google Chrome's Origin-Private FileSystem (OPFS) as a storage layer to provide persistent storage for database files in a browser. It requires... - - `sqlite3-opfs-async-proxy.js`\ + - **`sqlite3-opfs-async-proxy.js`**\ is the asynchronous backend part of the OPFS proxy. It speaks directly to the (async) OPFS API and channels those results back to its synchronous counterpart. This file, because it must be @@ -95,6 +103,13 @@ browser client: symbol installed. When adapting the API for non-Emscripten toolchains, this "should" be the only file where changes are needed. + +**Files with the extension `.c-pp.js`** are intended [to be processed +with `c-pp`](#c-pp), noting that such preprocessing may be applied +after all of the relevant files are concatenated. That extension is +used primarily to keep the code maintainers cognisant of the fact that +those files contain constructs which will not run as-is in JavaScript. + The build process glues those files together, resulting in `sqlite3-api.js`, which is everything except for the `post-js-*.js` files, and `sqlite3.js`, which is the Emscripten-generated amalgamated @@ -114,7 +129,7 @@ into the build-generated `sqlite3.js` along with `sqlite3-api.js`. Emscripten-specific header for Emscripten's `--extern-pre-js` flag. As of this writing, that file is only used for experimentation purposes and holds no code relevant to the production deliverables. -- `pre-js.js`\ +- `pre-js.c-pp.js`\ Emscripten-specific header for Emscripten's `--pre-js` flag. This file is intended as a place to override certain Emscripten behavior before it starts up, but corner-case Emscripten bugs keep that from @@ -125,9 +140,20 @@ into the build-generated `sqlite3.js` along with `sqlite3-api.js`. - `post-js-footer.js`\ Emscripten-specific footer for the `--post-js` input. This closes off the lexical scope opened by `post-js-header.js`. -- `extern-post-js.js`\ +- `extern-post-js.c-pp.js`\ Emscripten-specific header for Emscripten's `--extern-post-js` flag. This file overwrites the Emscripten-installed `sqlite3InitModule()` function with one which, after the module is loaded, also initializes the asynchronous parts of the sqlite3 module. For example, the OPFS VFS support. + + +Preprocessing of Source Files +------------------------------------------------------------------------ + +Certain files in the build require preprocessing to filter in/out +parts which differ between vanilla JS builds and ES6 Module +(a.k.a. esm) builds. The preprocessor application itself is in +[`c-pp.c`](/file/ext/wasm/c-pp.c) and the complete technical details +of such preprocessing are maintained in +[`GNUMakefile`](/file/ext/wasm/GNUmakefile). diff --git a/ext/wasm/api/extern-post-js.js b/ext/wasm/api/extern-post-js.c-pp.js similarity index 68% rename from ext/wasm/api/extern-post-js.js rename to ext/wasm/api/extern-post-js.c-pp.js index 84b99b53a..a577a63e1 100644 --- a/ext/wasm/api/extern-post-js.js +++ b/ext/wasm/api/extern-post-js.c-pp.js @@ -1,21 +1,32 @@ + +/* ^^^^ ACHTUNG: blank line at the start is necessary because + Emscripten will not add a newline in some cases and we need + a blank line for a sed-based kludge for the ES6 build. */ /* extern-post-js.js must be appended to the resulting sqlite3.js file. It gets its name from being used as the value for the --extern-post-js=... Emscripten flag. Note that this code, unlike most of the associated JS code, runs outside of the Emscripten-generated module init scope, in the current global scope. */ +//#if target=es6-module +const toExportForESM = +//#endif (function(){ /** - In order to hide the sqlite3InitModule()'s resulting Emscripten - module from downstream clients (and simplify our documentation by - being able to elide those details), we rewrite - sqlite3InitModule() to return the sqlite3 object. + In order to hide the sqlite3InitModule()'s resulting + Emscripten module from downstream clients (and simplify our + documentation by being able to elide those details), we hide that + function and expose a hand-written sqlite3InitModule() to return + the sqlite3 object (most of the time). Unfortunately, we cannot modify the module-loader/exporter-based impls which Emscripten installs at some point in the file above this. */ - const originalInit = self.sqlite3InitModule; + const originalInit = + /* Maintenance reminder: DO NOT use `self.` here. It's correct + for non-ES6 Module cases but wrong for ES6 modules because those + resolve this symbol differently. */ sqlite3InitModule; if(!originalInit){ throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build."); } @@ -34,10 +45,10 @@ moduleScript: self?.document?.currentScript, isWorker: ('undefined' !== typeof WorkerGlobalScope), location: self.location, - urlParams: new URL(self.location.href).searchParams + urlParams: new URL(self.location.href).searchParams }); initModuleState.debugModule = - (new URL(self.location.href).searchParams).has('sqlite3.debugModule') + initModuleState.urlParams.has('sqlite3.debugModule') ? (...args)=>console.warn('sqlite3.debugModule:',...args) : ()=>{}; @@ -49,7 +60,7 @@ initModuleState.sqlite3Dir = li.join('/') + '/'; } - self.sqlite3InitModule = (...args)=>{ + self.sqlite3InitModule = function ff(...args){ //console.warn("Using replaced sqlite3InitModule()",self.location); return originalInit(...args).then((EmscriptenModule)=>{ if(self.window!==self && @@ -65,10 +76,12 @@ Emscripten details. */ return EmscriptenModule; } - EmscriptenModule.sqlite3.scriptInfo = initModuleState; - //console.warn("sqlite3.scriptInfo =",EmscriptenModule.sqlite3.scriptInfo); - const f = EmscriptenModule.sqlite3.asyncPostInit; - delete EmscriptenModule.sqlite3.asyncPostInit; + const s = EmscriptenModule.sqlite3; + s.scriptInfo = initModuleState; + //console.warn("sqlite3.scriptInfo =",s.scriptInfo); + if(ff.__isUnderTest) s.__isUnderTest = true; + const f = s.asyncPostInit; + delete s.asyncPostInit; return f(); }).catch((e)=>{ console.error("Exception loading sqlite3 module:",e); @@ -92,12 +105,22 @@ document?.currentScript?.src); } } +//#ifnot target=es6-module +// Emscripten does not inject these module-loader bits in ES6 module +// builds and including them here breaks JS bundlers, so elide them +// from ESM builds. /* Replace the various module exports performed by the Emscripten glue... */ - if (typeof exports === 'object' && typeof module === 'object') + if (typeof exports === 'object' && typeof module === 'object'){ module.exports = sqlite3InitModule; - else if (typeof exports === 'object') + }else if (typeof exports === 'object'){ exports["sqlite3InitModule"] = sqlite3InitModule; + } /* AMD modules get injected in a way we cannot override, so we can't handle those here. */ +//#endif // !target=es6-module + return self.sqlite3InitModule /* required for ESM */; })(); +//#if target=es6-module +export default toExportForESM; +//#endif diff --git a/ext/wasm/api/post-js-header.js b/ext/wasm/api/post-js-header.js index 82a80e5a1..0e27e1fd9 100644 --- a/ext/wasm/api/post-js-header.js +++ b/ext/wasm/api/post-js-header.js @@ -19,7 +19,8 @@ Module.postRun.push(function(Module/*the Emscripten-style module object*/){ - sqlite3-api-glue.js => glues previous parts together - sqlite3-api-oo.js => SQLite3 OO API #1 - sqlite3-api-worker1.js => Worker-based API - - sqlite3-api-opfs.js => OPFS VFS + - sqlite3-vfs-helper.js => Internal-use utilities for... + - sqlite3-vfs-opfs.js => OPFS VFS - sqlite3-api-cleanup.js => final API cleanup - post-js-footer.js => closes this postRun() function */ diff --git a/ext/wasm/api/pre-js.js b/ext/wasm/api/pre-js.c-pp.js similarity index 89% rename from ext/wasm/api/pre-js.js rename to ext/wasm/api/pre-js.c-pp.js index f31dea179..a25c7ce77 100644 --- a/ext/wasm/api/pre-js.js +++ b/ext/wasm/api/pre-js.c-pp.js @@ -6,10 +6,14 @@ */ // See notes in extern-post-js.js -const sqlite3InitModuleState = self.sqlite3InitModuleState || Object.create(null); +const sqlite3InitModuleState = self.sqlite3InitModuleState + || Object.assign(Object.create(null),{ + debugModule: ()=>{} + }); delete self.sqlite3InitModuleState; sqlite3InitModuleState.debugModule('self.location =',self.location); +//#ifnot target=es6-bundler-friendly /** This custom locateFile() tries to figure out where to load `path` from. The intent is to provide a way for foo/bar/X.js loaded from a @@ -29,6 +33,10 @@ sqlite3InitModuleState.debugModule('self.location =',self.location); 4) If none of the above apply, (prefix+path) is returned. */ Module['locateFile'] = function(path, prefix) { +//#if target=es6-module + return new URL(path, import.meta.url).href; +//#else + 'use strict'; let theFile; const up = this.urlParams; if(up.has(path)){ @@ -47,7 +55,9 @@ Module['locateFile'] = function(path, prefix) { "result =", theFile ); return theFile; +//#endif target=es6-module }.bind(sqlite3InitModuleState); +//#endif ifnot target=es6-bundler-friendly /** Bug warning: a custom Module.instantiateWasm() does not work @@ -57,7 +67,7 @@ Module['locateFile'] = function(path, prefix) { In such builds we must disable this. */ -const xNameOfInstantiateWasm = true +const xNameOfInstantiateWasm = false ? 'instantiateWasm' : 'emscripten-bug-17951'; Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){ @@ -65,7 +75,7 @@ Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){ const uri = Module.locateFile( callee.uri, ( ('undefined'===typeof scriptDirectory/*var defined by Emscripten glue*/) - ? '' : scriptDirectory) + ? "" : scriptDirectory) ); sqlite3InitModuleState.debugModule( "instantiateWasm() uri =", uri diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index bef4d91d7..7c23f8f89 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -22,12 +22,10 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build */ const SABC = Object.assign( Object.create(null), { - Module: Module /* ==> Currently needs to be exposed here for - test code. NOT part of the public API. */, exports: Module['asm'], memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */ }, - self.sqlite3ApiConfig || Object.create(null) + self.sqlite3ApiConfig || {} ); /** @@ -53,13 +51,6 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build delete self.sqlite3ApiConfig; } - if(self.location && +self.location.port > 1024){ - console.warn("Installing sqlite3 bits as global S for local dev/test purposes."); - self.S = sqlite3; - } - - /* Clean up temporary references to our APIs... */ - delete sqlite3.util /* arguable, but these are (currently) internal-use APIs */; Module.sqlite3 = sqlite3 /* Needed for customized sqlite3InitModule() to be able to pass the sqlite3 object off to the client. */; }else{ diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 86aa1d181..7db23bacc 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -24,6 +24,584 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ self.WhWasmUtilInstaller(wasm); delete self.WhWasmUtilInstaller; + if(0){ + /** + Please keep this block around as a maintenance reminder + that we cannot rely on this type of check. + + This block fails on Safari, per a report at + https://sqlite.org/forum/forumpost/e5b20e1feb. + + It turns out that what Safari serves from the indirect function + table (e.g. wasm.functionEntry(X)) is anonymous functions which + wrap the WASM functions, rather than returning the WASM + functions themselves. That means comparison of such functions + is useless for determining whether or not we have a specific + function from wasm.exports. i.e. if function X is indirection + function table entry N then wasm.exports.X is not equal to + wasm.functionEntry(N) in Safari, despite being so in the other + browsers. + */ + /** + Find a mapping for SQLITE_WASM_DEALLOC, which the API + guarantees is a WASM pointer to the same underlying function as + wasm.dealloc() (noting that wasm.dealloc() is permitted to be a + JS wrapper around the WASM function). There is unfortunately no + O(1) algorithm for finding this pointer: we have to walk the + WASM indirect function table to find it. However, experience + indicates that that particular function is always very close to + the front of the table (it's been entry #3 in all relevant + tests). + */ + const dealloc = wasm.exports[sqlite3.config.deallocExportName]; + const nFunc = wasm.functionTable().length; + let i; + for(i = 0; i < nFunc; ++i){ + const e = wasm.functionEntry(i); + if(dealloc === e){ + capi.SQLITE_WASM_DEALLOC = i; + break; + } + } + if(dealloc !== wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){ + toss("Internal error: cannot find function pointer for SQLITE_WASM_DEALLOC."); + } + } + + /** + Signatures for the WASM-exported C-side functions. Each entry + is an array with 2+ elements: + + [ "c-side name", + "result type" (wasm.xWrap() syntax), + [arg types in xWrap() syntax] + // ^^^ this needn't strictly be an array: it can be subsequent + // elements instead: [x,y,z] is equivalent to x,y,z + ] + + Note that support for the API-specific data types in the + result/argument type strings gets plugged in at a later phase in + the API initialization process. + */ + wasm.bindingSignatures = [ + // Please keep these sorted by function name! + ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], + /* sqlite3_auto_extension() has a hand-written binding. */ + /* sqlite3_bind_blob() and sqlite3_bind_text() have hand-written + bindings to permit more flexible inputs. */ + ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], + ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"], + ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], + ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], + ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], + ["sqlite3_bind_pointer", "int", + "sqlite3_stmt*", "int", "*", "string:static", "*"], + ["sqlite3_busy_handler","int", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + signature: 'i(pi)', + contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] + }), + "*" + ]], + ["sqlite3_busy_timeout","int", "sqlite3*", "int"], + /* sqlite3_cancel_auto_extension() has a hand-written binding. */ + /* sqlite3_close_v2() is implemented by hand to perform some + extra work. */ + ["sqlite3_changes", "int", "sqlite3*"], + ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], + ["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/], + ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], + ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], + ["sqlite3_column_count", "int", "sqlite3_stmt*"], + ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"], + ["sqlite3_column_int","int", "sqlite3_stmt*", "int"], + ["sqlite3_column_name","string", "sqlite3_stmt*", "int"], + ["sqlite3_column_text","string", "sqlite3_stmt*", "int"], + ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], + ["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"], + ["sqlite3_commit_hook", "void*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_commit_hook', + signature: 'i(p)', + contextKey: (argv)=>argv[0/* sqlite3* */] + }), + '*' + ]], + ["sqlite3_compileoption_get", "string", "int"], + ["sqlite3_compileoption_used", "int", "string"], + ["sqlite3_complete", "int", "string:flexible"], + ["sqlite3_context_db_handle", "sqlite3*", "sqlite3_context*"], + + /* sqlite3_create_function(), sqlite3_create_function_v2(), and + sqlite3_create_window_function() use hand-written bindings to + simplify handling of their function-type arguments. */ + /* sqlite3_create_collation() and sqlite3_create_collation_v2() + use hand-written bindings to simplify passing of the callback + function. + ["sqlite3_create_collation", "int", + "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value + "*", "*"], + ["sqlite3_create_collation_v2", "int", + "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value + "*", "*", "*"], + */ + ["sqlite3_data_count", "int", "sqlite3_stmt*"], + ["sqlite3_db_filename", "string", "sqlite3*", "string"], + ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], + ["sqlite3_db_name", "string", "sqlite3*", "int"], + ["sqlite3_db_status", "int", "sqlite3*", "int", "*", "*", "int"], + ["sqlite3_errcode", "int", "sqlite3*"], + ["sqlite3_errmsg", "string", "sqlite3*"], + ["sqlite3_error_offset", "int", "sqlite3*"], + ["sqlite3_errstr", "string", "int"], + ["sqlite3_exec", "int", [ + "sqlite3*", "string:flexible", + new wasm.xWrap.FuncPtrAdapter({ + signature: 'i(pipp)', + bindScope: 'transient', + callProxy: (callback)=>{ + let aNames; + return (pVoid, nCols, pColVals, pColNames)=>{ + try { + const aVals = wasm.cArgvToJs(nCols, pColVals); + if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames); + return callback(aVals, aNames) | 0; + }catch(e){ + /* If we set the db error state here, the higher-level + exec() call replaces it with its own, so we have no way + of reporting the exception message except the console. We + must not propagate exceptions through the C API. Though + we make an effort to report OOM here, sqlite3_exec() + translates that into SQLITE_ABORT as well. */ + return e.resultCode || capi.SQLITE_ERROR; + } + } + } + }), + "*", "**" + ]], + ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], + ["sqlite3_extended_errcode", "int", "sqlite3*"], + ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], + ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], + ["sqlite3_finalize", "int", "sqlite3_stmt*"], + ["sqlite3_free", undefined,"*"], + ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"], + ["sqlite3_initialize", undefined], + /*["sqlite3_interrupt", undefined, "sqlite3*" + ^^^ we cannot actually currently support this because JS is + single-threaded and we don't have a portable way to access a DB + from 2 SharedWorkers concurrently. ],*/ + ["sqlite3_keyword_count", "int"], + ["sqlite3_keyword_name", "int", ["int", "**", "*"]], + ["sqlite3_keyword_check", "int", ["string", "int"]], + ["sqlite3_libversion", "string"], + ["sqlite3_libversion_number", "int"], + ["sqlite3_limit", "int", ["sqlite3*", "int", "int"]], + ["sqlite3_malloc", "*","int"], + ["sqlite3_open", "int", "string", "*"], + ["sqlite3_open_v2", "int", "string", "*", "int", "string"], + /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled + separately due to us requiring two different sets of semantics + for those, depending on how their SQL argument is provided. */ + /* sqlite3_randomness() uses a hand-written wrapper to extend + the range of supported argument types. */ + ["sqlite3_progress_handler", undefined, [ + "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({ + name: 'xProgressHandler', + signature: 'i(p)', + bindScope: 'context', + contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] + }), "*" + ]], + ["sqlite3_realloc", "*","*","int"], + ["sqlite3_reset", "int", "sqlite3_stmt*"], + /* sqlite3_reset_auto_extension() has a hand-written binding. */ + ["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"], + ["sqlite3_result_double", undefined, "sqlite3_context*", "f64"], + ["sqlite3_result_error", undefined, "sqlite3_context*", "string", "int"], + ["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"], + ["sqlite3_result_error_nomem", undefined, "sqlite3_context*"], + ["sqlite3_result_error_toobig", undefined, "sqlite3_context*"], + ["sqlite3_result_int", undefined, "sqlite3_context*", "int"], + ["sqlite3_result_null", undefined, "sqlite3_context*"], + ["sqlite3_result_pointer", undefined, + "sqlite3_context*", "*", "string:static", "*"], + ["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"], + ["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"], + ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"], + ["sqlite3_rollback_hook", "void*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_rollback_hook', + signature: 'v(p)', + contextKey: (argv)=>argv[0/* sqlite3* */] + }), + '*' + ]], + ["sqlite3_set_authorizer", "int", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: "sqlite3_set_authorizer::xAuth", + signature: "i(pi"+"ssss)", + contextKey: (argv, argIndex)=>argv[0/*(sqlite3*)*/], + callProxy: (callback)=>{ + return (pV, iCode, s0, s1, s2, s3)=>{ + try{ + s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1); + s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3); + return callback(pV, iCode, s0, s1, s2, s3) || 0; + }catch(e){ + return e.resultCode || capi.SQLITE_ERROR; + } + } + } + }), + "*"/*pUserData*/ + ]], + ["sqlite3_set_auxdata", undefined, [ + "sqlite3_context*", "int", "*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'xDestroyAuxData', + signature: 'v(*)', + contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] + }) + ]], + ["sqlite3_shutdown", undefined], + ["sqlite3_sourceid", "string"], + ["sqlite3_sql", "string", "sqlite3_stmt*"], + ["sqlite3_status", "int", "int", "*", "*", "int"], + ["sqlite3_step", "int", "sqlite3_stmt*"], + ["sqlite3_stmt_isexplain", "int", ["sqlite3_stmt*"]], + ["sqlite3_stmt_readonly", "int", ["sqlite3_stmt*"]], + ["sqlite3_stmt_status", "int", "sqlite3_stmt*", "int", "int"], + ["sqlite3_strglob", "int", "string","string"], + ["sqlite3_stricmp", "int", "string", "string"], + ["sqlite3_strlike", "int", "string", "string","int"], + ["sqlite3_strnicmp", "int", "string", "string", "int"], + ["sqlite3_table_column_metadata", "int", + "sqlite3*", "string", "string", "string", + "**", "**", "*", "*", "*"], + ["sqlite3_total_changes", "int", "sqlite3*"], + ["sqlite3_trace_v2", "int", [ + "sqlite3*", "int", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_trace_v2::callback', + signature: 'i(ippp)', + contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] + }), + "*" + ]], + ["sqlite3_txn_state", "int", ["sqlite3*","string"]], + /* Note that sqlite3_uri_...() have very specific requirements for + their first C-string arguments, so we cannot perform any value + conversion on those. */ + ["sqlite3_uri_boolean", "int", "sqlite3_filename", "string", "int"], + ["sqlite3_uri_key", "string", "sqlite3_filename", "int"], + ["sqlite3_uri_parameter", "string", "sqlite3_filename", "string"], + ["sqlite3_user_data","void*", "sqlite3_context*"], + ["sqlite3_value_blob", "*", "sqlite3_value*"], + ["sqlite3_value_bytes","int", "sqlite3_value*"], + ["sqlite3_value_double","f64", "sqlite3_value*"], + ["sqlite3_value_dup", "sqlite3_value*", "sqlite3_value*"], + ["sqlite3_value_free", undefined, "sqlite3_value*"], + ["sqlite3_value_frombind", "int", "sqlite3_value*"], + ["sqlite3_value_int","int", "sqlite3_value*"], + ["sqlite3_value_nochange", "int", "sqlite3_value*"], + ["sqlite3_value_numeric_type", "int", "sqlite3_value*"], + ["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"], + ["sqlite3_value_subtype", "int", "sqlite3_value*"], + ["sqlite3_value_text", "string", "sqlite3_value*"], + ["sqlite3_value_type", "int", "sqlite3_value*"], + ["sqlite3_vfs_find", "*", "string"], + ["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"], + ["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"] + ]/*wasm.bindingSignatures*/; + + if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ + /* ^^^ "the problem" is that this is an option feature and the + build-time function-export list does not currently take + optional features into account. */ + wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); + } + + /** + Functions which require BigInt (int64) support are separated from + the others because we need to conditionally bind them or apply + dummy impls, depending on the capabilities of the environment. + + Note that not all of these functions directly require int64 + but are only for use with APIs which require int64. For example, + the vtab-related functions. + */ + wasm.bindingSignatures.int64 = [ + ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], + ["sqlite3_changes64","i64", ["sqlite3*"]], + ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], + ["sqlite3_create_module", "int", + ["sqlite3*","string","sqlite3_module*","*"]], + ["sqlite3_create_module_v2", "int", + ["sqlite3*","string","sqlite3_module*","*","*"]], + ["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]], + ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"] + /* Careful! Short version: de/serialize() are problematic because they + might use a different allocator than the user for managing the + deserialized block. de/serialize() are ONLY safe to use with + sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, + ["sqlite3_drop_modules", "int", ["sqlite3*", "**"]], + ["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]], + ["sqlite3_malloc64", "*","i64"], + ["sqlite3_msize", "i64", "*"], + ["sqlite3_overload_function", "int", ["sqlite3*","string","int"]], + ["sqlite3_preupdate_blobwrite", "int", "sqlite3*"], + ["sqlite3_preupdate_count", "int", "sqlite3*"], + ["sqlite3_preupdate_depth", "int", "sqlite3*"], + ["sqlite3_preupdate_hook", "*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_preupdate_hook', + signature: "v(ppippjj)", + contextKey: (argv)=>argv[0/* sqlite3* */], + callProxy: (callback)=>{ + return (p,db,op,zDb,zTbl,iKey1,iKey2)=>{ + callback(p, db, op, wasm.cstrToJs(zDb), wasm.cstrToJs(zTbl), + iKey1, iKey2); + }; + } + }), + "*" + ]], + ["sqlite3_preupdate_new", "int", ["sqlite3*", "int", "**"]], + ["sqlite3_preupdate_old", "int", ["sqlite3*", "int", "**"]], + ["sqlite3_realloc64", "*","*", "i64"], + ["sqlite3_result_int64", undefined, "*", "i64"], + ["sqlite3_result_zeroblob64", "int", "*", "i64"], + ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], + ["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]], + ["sqlite3_status64", "int", "int", "*", "*", "int"], + ["sqlite3_total_changes64", "i64", ["sqlite3*"]], + ["sqlite3_update_hook", "*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_update_hook', + signature: "v(iippj)", + contextKey: (argv)=>argv[0/* sqlite3* */], + callProxy: (callback)=>{ + return (p,op,z0,z1,rowid)=>{ + callback(p, op, wasm.cstrToJs(z0), wasm.cstrToJs(z1), rowid); + }; + } + }), + "*" + ]], + ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]], + ["sqlite3_value_int64","i64", "sqlite3_value*"], + ["sqlite3_vtab_collation","string","sqlite3_index_info*","int"], + ["sqlite3_vtab_distinct","int", "sqlite3_index_info*"], + ["sqlite3_vtab_in","int", "sqlite3_index_info*", "int", "int"], + ["sqlite3_vtab_in_first", "int", "sqlite3_value*", "**"], + ["sqlite3_vtab_in_next", "int", "sqlite3_value*", "**"], + /*["sqlite3_vtab_config" is variadic and requires a hand-written + proxy.] */ + ["sqlite3_vtab_nochange","int", "sqlite3_context*"], + ["sqlite3_vtab_on_conflict","int", "sqlite3*"], + ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] + ]; + + // Add session/changeset APIs... + if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){ + /* ACHTUNG: 2022-12-23: the session/changeset API bindings are + COMPLETELY UNTESTED. */ + /** + FuncPtrAdapter options for session-related callbacks with the + native signature "i(ps)". This proxy converts the 2nd argument + from a C string to a JS string before passing the arguments on + to the client-provided JS callback. + */ + const __ipsProxy = { + signature: 'i(ps)', + callProxy:(callback)=>{ + return (p,s)=>{ + try{return callback(p, wasm.cstrToJs(s)) | 0} + catch(e){return e.resultCode || capi.SQLITE_ERROR} + } + } + }; + + wasm.bindingSignatures.int64.push(...[ + ['sqlite3changegroup_add', 'int', ['sqlite3_changegroup*', 'int', 'void*']], + ['sqlite3changegroup_add_strm', 'int', [ + 'sqlite3_changegroup*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changegroup_delete', undefined, ['sqlite3_changegroup*']], + ['sqlite3changegroup_new', 'int', ['**']], + ['sqlite3changegroup_output', 'int', ['sqlite3_changegroup*', 'int*', '**']], + ['sqlite3changegroup_output_strm', 'int', [ + 'sqlite3_changegroup*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_apply', 'int', [ + 'sqlite3*', 'int', 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_apply_strm', 'int', [ + 'sqlite3*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_apply_v2', 'int', [ + 'sqlite3*', 'int', 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*', '**', 'int*', 'int' + + ]], + ['sqlite3changeset_apply_v2_strm', 'int', [ + 'sqlite3*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*', '**', 'int*', 'int' + ]], + ['sqlite3changeset_concat', 'int', ['int','void*', 'int', 'void*', 'int*', '**']], + ['sqlite3changeset_concat_strm', 'int', [ + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInputA', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInputB', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_conflict', 'int', ['sqlite3_changeset_iter*', 'int', '**']], + ['sqlite3changeset_finalize', 'int', ['sqlite3_changeset_iter*']], + ['sqlite3changeset_fk_conflicts', 'int', ['sqlite3_changeset_iter*', 'int*']], + ['sqlite3changeset_invert', 'int', ['int', 'void*', 'int*', '**']], + ['sqlite3changeset_invert_strm', 'int', [ + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_new', 'int', ['sqlite3_changeset_iter*', 'int', '**']], + ['sqlite3changeset_next', 'int', ['sqlite3_changeset_iter*']], + ['sqlite3changeset_old', 'int', ['sqlite3_changeset_iter*', 'int', '**']], + ['sqlite3changeset_op', 'int', [ + 'sqlite3_changeset_iter*', '**', 'int*', 'int*','int*' + ]], + ['sqlite3changeset_pk', 'int', ['sqlite3_changeset_iter*', '**', 'int*']], + ['sqlite3changeset_start', 'int', ['**', 'int', '*']], + ['sqlite3changeset_start_strm', 'int', [ + '**', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_start_v2', 'int', ['**', 'int', '*', 'int']], + ['sqlite3changeset_start_v2_strm', 'int', [ + '**', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', 'int' + ]], + ['sqlite3session_attach', 'int', ['sqlite3_session*', 'string']], + ['sqlite3session_changeset', 'int', ['sqlite3_session*', 'int*', '**']], + ['sqlite3session_changeset_size', 'i64', ['sqlite3_session*']], + ['sqlite3session_changeset_strm', 'int', [ + 'sqlite3_session*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3session_config', 'int', ['int', 'void*']], + ['sqlite3session_create', 'int', ['sqlite3*', 'string', '**']], + //sqlite3session_delete() is bound manually + ['sqlite3session_diff', 'int', ['sqlite3_session*', 'string', 'string', '**']], + ['sqlite3session_enable', 'int', ['sqlite3_session*', 'int']], + ['sqlite3session_indirect', 'int', ['sqlite3_session*', 'int']], + ['sqlite3session_isempty', 'int', ['sqlite3_session*']], + ['sqlite3session_memory_used', 'i64', ['sqlite3_session*']], + ['sqlite3session_object_config', 'int', ['sqlite3_session*', 'int', 'void*']], + ['sqlite3session_patchset', 'int', ['sqlite3_session*', '*', '**']], + ['sqlite3session_patchset_strm', 'int', [ + 'sqlite3_session*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3session_table_filter', undefined, [ + 'sqlite3_session*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', ...__ipsProxy, + contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */] + }), + '*' + ]] + ]); + }/*session/changeset APIs*/ + + /** + Functions which are intended solely for API-internal use by the + WASM components, not client code. These get installed into + sqlite3.wasm. Some of them get exposed to clients via variants + named sqlite3_js_...(). + */ + wasm.bindingSignatures.wasm = [ + ["sqlite3_wasm_db_reset", "int", "sqlite3*"], + ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], + ["sqlite3_wasm_vfs_create_file", "int", + "sqlite3_vfs*","string","*", "int"], + ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] + ]; + /** Install JS<->C struct bindings for the non-opaque struct types we need... */ @@ -31,54 +609,111 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ heap: 0 ? wasm.memory : wasm.heap8u, alloc: wasm.alloc, dealloc: wasm.dealloc, - functionTable: wasm.functionTable, bigIntEnabled: wasm.bigIntEnabled, - memberPrefix: '$' + memberPrefix: /* Never change this: this prefix is baked into any + amount of code and client-facing docs. */ '$' }); delete self.Jaccwabyt; - if(0){ - /* "The problem" is that the following isn't even remotely - type-safe. OTOH, nothing about WASM pointers is. */ - const argPointer = wasm.xWrap.argAdapter('*'); - wasm.xWrap.argAdapter('StructType', (v)=>{ - if(v && v.constructor && v instanceof StructBinder.StructType){ - v = v.pointer; - } - return wasm.isPtr(v) - ? argPointer(v) - : toss("Invalid (object) type for StructType-type argument."); - }); - } + {// wasm.xWrap() bindings... - {/* Convert Arrays and certain TypedArrays to strings for - 'flexible-string'-type arguments */ - const xString = wasm.xWrap.argAdapter('string'); + /* Convert Arrays and certain TypedArrays to strings for + 'string:flexible'-type arguments */ + const __xString = wasm.xWrap.argAdapter('string'); wasm.xWrap.argAdapter( - 'flexible-string', (v)=>xString(util.flexibleString(v)) + 'string:flexible', (v)=>__xString(util.flexibleString(v)) ); - } - - if(1){// WhWasmUtil.xWrap() bindings... + + /** + The 'string:static' argument adapter treats its argument as + either... + + - WASM pointer: assumed to be a long-lived C-string which gets + returned as-is. + + - Anything else: gets coerced to a JS string for use as a map + key. If a matching entry is found (as described next), it is + returned, else wasm.allocCString() is used to create a a new + string, map its pointer to (''+v) for the remainder of the + application's life, and returns that pointer value for this + call and all future calls which are passed a + string-equivalent argument. + + Use case: sqlite3_bind_pointer() and sqlite3_result_pointer() + call for "a static string and preferably a string + literal". This converter is used to ensure that the string + value seen by those functions is long-lived and behaves as they + need it to. + */ + wasm.xWrap.argAdapter( + 'string:static', + function(v){ + if(wasm.isPtr(v)) return v; + v = ''+v; + let rc = this[v]; + return rc || (this[v] = wasm.allocCString(v)); + }.bind(Object.create(null)) + ); + /** Add some descriptive xWrap() aliases for '*' intended to (A) - initially improve readability/correctness of capi.signatures - and (B) eventually perhaps provide automatic conversion from - higher-level representations, e.g. capi.sqlite3_vfs to + initially improve readability/correctness of + wasm.bindingSignatures and (B) provide automatic conversion + from higher-level representations, e.g. capi.sqlite3_vfs to `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. */ - const aPtr = wasm.xWrap.argAdapter('*'); - wasm.xWrap.argAdapter('sqlite3*', aPtr) - ('sqlite3_stmt*', aPtr) - ('sqlite3_context*', aPtr) - ('sqlite3_value*', aPtr) - ('sqlite3_vfs*', aPtr) - ('void*', aPtr); - wasm.xWrap.resultAdapter('sqlite3*', aPtr) - ('sqlite3_context*', aPtr) - ('sqlite3_stmt*', aPtr) - ('sqlite3_vfs*', aPtr) - ('void*', aPtr); + const __xArgPtr = wasm.xWrap.argAdapter('*'); + const nilType = function(){}/*a class no value can ever be an instance of*/; + wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr) + ('sqlite3_context*', __xArgPtr) + ('sqlite3_value*', __xArgPtr) + ('void*', __xArgPtr) + ('sqlite3_changegroup*', __xArgPtr) + ('sqlite3_changeset_iter*', __xArgPtr) + //('sqlite3_rebaser*', __xArgPtr) + ('sqlite3_session*', __xArgPtr) + ('sqlite3_stmt*', (v)=> + __xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) + ? v.pointer : v)) + ('sqlite3*', (v)=> + __xArgPtr((v instanceof (sqlite3?.oo1?.DB || nilType)) + ? v.pointer : v)) + ('sqlite3_index_info*', (v)=> + __xArgPtr((v instanceof (capi.sqlite3_index_info || nilType)) + ? v.pointer : v)) + ('sqlite3_module*', (v)=> + __xArgPtr((v instanceof (capi.sqlite3_module || nilType)) + ? v.pointer : v)) + /** + `sqlite3_vfs*`: + + - v is-a string: use the result of sqlite3_vfs_find(v) but + throw if it returns 0. + - v is-a capi.sqlite3_vfs: use v.pointer. + - Else return the same as the `'*'` argument conversion. + */ + ('sqlite3_vfs*', (v)=>{ + if('string'===typeof v){ + /* A NULL sqlite3_vfs pointer will be treated as the default + VFS in many contexts. We specifically do not want that + behavior here. */ + return capi.sqlite3_vfs_find(v) + || sqlite3.SQLite3Error.toss( + capi.SQLITE_NOTFOUND, + "Unknown sqlite3_vfs name:", v + ); + } + return __xArgPtr((v instanceof (capi.sqlite3_vfs || nilType)) + ? v.pointer : v); + }); + + const __xRcPtr = wasm.xWrap.resultAdapter('*'); + wasm.xWrap.resultAdapter('sqlite3*', __xRcPtr) + ('sqlite3_context*', __xRcPtr) + ('sqlite3_stmt*', __xRcPtr) + ('sqlite3_value*', __xRcPtr) + ('sqlite3_vfs*', __xRcPtr) + ('void*', __xRcPtr); /** Populate api object with sqlite3_...() by binding the "raw" wasm @@ -92,10 +727,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } /* For C API functions which cannot work properly unless - wasm.bigIntEnabled is true, install a bogus impl which - throws if called when bigIntEnabled is false. */ + wasm.bigIntEnabled is true, install a bogus impl which throws + if called when bigIntEnabled is false. The alternative would be + to elide these functions altogether, which seems likely to + cause more confusion. */ const fI64Disabled = function(fname){ - return ()=>toss(fname+"() disabled due to lack", + return ()=>toss(fname+"() is unavailable due to lack", "of BigInt support in this build."); }; for(const e of wasm.bindingSignatures.int64){ @@ -104,34 +741,127 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ : fI64Disabled(e[0]); } - /* There's no(?) need to expose bindingSignatures to clients, + /* There's no need to expose bindingSignatures to clients, implicitly making it part of the public interface. */ delete wasm.bindingSignatures; if(wasm.exports.sqlite3_wasm_db_error){ - util.sqlite3_wasm_db_error = wasm.xWrap( + const __db_err = wasm.xWrap( 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' ); + /** + Sets the given db's error state. Accepts: + + - (sqlite3*, int code, string msg) + - (sqlite3*, Error e [,string msg = ''+e]) + + If passed a WasmAllocError, the message is ignored and the + result code is SQLITE_NOMEM. If passed any other Error type, + the result code defaults to SQLITE_ERROR unless the Error + object has a resultCode property, in which case that is used + (e.g. SQLite3Error has that). If passed a non-WasmAllocError + exception, the message string defaults to theError.message. + + Returns the resulting code. Pass (pDb,0,0) to clear the error + state. + */ + util.sqlite3_wasm_db_error = function(pDb, resultCode, message){ + if(resultCode instanceof sqlite3.WasmAllocError){ + resultCode = capi.SQLITE_NOMEM; + message = 0 /*avoid allocating message string*/; + }else if(resultCode instanceof Error){ + message = message || ''+resultCode; + resultCode = (resultCode.resultCode || capi.SQLITE_ERROR); + } + return pDb ? __db_err(pDb, resultCode, message) : resultCode; + }; }else{ util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ console.warn("sqlite3_wasm_db_error() is not exported.",arguments); return errCode; }; } + }/*xWrap() bindings*/ - }/*xWrap() bindings*/; - - /** - When registering a VFS and its related components it may be - necessary to ensure that JS keeps a reference to them to keep - them from getting garbage collected. Simply pass each such value - to this function and a reference will be held to it for the life - of the app. - */ - capi.sqlite3_vfs_register.addReference = function f(...args){ - if(!f._) f._ = []; - f._.push(...args); - }; + {/* Import C-level constants and structs... */ + const cJson = wasm.xCall('sqlite3_wasm_enum_json'); + if(!cJson){ + toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", + "static buffer size!"); + } + //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); + wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); + // Groups of SQLITE_xyz macros... + const defineGroups = ['access', 'authorizer', + 'blobFinalizers', 'changeset', + 'config', 'dataTypes', + 'dbConfig', 'dbStatus', + 'encodings', 'fcntl', 'flock', 'ioCap', + 'limits', 'openFlags', + 'prepareFlags', 'resultCodes', + 'sqlite3Status', + 'stmtStatus', 'syncFlags', + 'trace', 'txnState', 'udfFlags', + 'version' ]; + if(wasm.bigIntEnabled){ + defineGroups.push('serialize', 'session', 'vtab'); + } + for(const t of defineGroups){ + for(const e of Object.entries(wasm.ctype[t])){ + // ^^^ [k,v] there triggers a buggy code transformation via + // one of the Emscripten-driven optimizers. + capi[e[0]] = e[1]; + } + } + if(!wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){ + toss("Internal error: cannot resolve exported function", + "entry SQLITE_WASM_DEALLOC (=="+capi.SQLITE_WASM_DEALLOC+")."); + } + const __rcMap = Object.create(null); + for(const t of ['resultCodes']){ + for(const e of Object.entries(wasm.ctype[t])){ + __rcMap[e[1]] = e[0]; + } + } + /** + For the given integer, returns the SQLITE_xxx result code as a + string, or undefined if no such mapping is found. + */ + capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; + /* Bind all registered C-side structs... */ + const notThese = Object.assign(Object.create(null),{ + // For each struct to NOT register, map its name to true: + WasmTestStruct: true, + /* We unregister the kvvfs VFS from Worker threads below. */ + sqlite3_kvvfs_methods: !util.isUIThread(), + /* sqlite3_index_info and friends require int64: */ + sqlite3_index_info: !wasm.bigIntEnabled, + sqlite3_index_constraint: !wasm.bigIntEnabled, + sqlite3_index_orderby: !wasm.bigIntEnabled, + sqlite3_index_constraint_usage: !wasm.bigIntEnabled + }); + for(const s of wasm.ctype.structs){ + if(!notThese[s.name]){ + capi[s.name] = sqlite3.StructBinder(s); + } + } + if(capi.sqlite3_index_info){ + /* Move these inner structs into sqlite3_index_info. Binding + ** them to WASM requires that we create global-scope structs to + ** model them with, but those are no longer needed after we've + ** passed them to StructBinder. */ + for(const k of ['sqlite3_index_constraint', + 'sqlite3_index_orderby', + 'sqlite3_index_constraint_usage']){ + capi.sqlite3_index_info[k] = capi[k]; + delete capi[k]; + } + capi.sqlite3_vtab_config = wasm.xWrap( + 'sqlite3_wasm_vtab_config','int',[ + 'sqlite3*', 'int', 'int'] + ); + }/* end vtab-related setup */ + }/*end C constant and struct imports*/ /** Internal helper to assist in validating call argument counts in @@ -144,247 +874,338 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ (1===n?"":'s')+"."); }; + /** Code duplication reducer for functions which take an encoding + argument and require SQLITE_UTF8. Sets the db error code to + SQLITE_FORMAT and returns that code. */ + const __errEncoding = (pDb)=>{ + return util.sqlite3_wasm_db_error( + pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding." + ); + }; + /** - Helper for flexible-string conversions which require a - byte-length counterpart argument. Passed a value and its - ostensible length, this function returns [V,N], where V - is either v or a transformed copy of v and N is either n, - -1, or the byte length of v (if it's a byte array). + __dbCleanupMap is infrastructure for recording registration of + UDFs and collations so that sqlite3_close_v2() can clean up any + automated JS-to-WASM function conversions installed by those. */ - const __flexiString = function(v,n){ - if('string'===typeof v){ - n = -1; - }else if(util.isSQLableTypedArray(v)){ - n = v.byteLength; - v = util.typedArrayToString(v); - }else if(Array.isArray(v)){ - v = v.join(""); - n = -1; + const __argPDb = (pDb)=>wasm.xWrap.argAdapter('sqlite3*')(pDb); + const __argStr = (str)=>wasm.isPtr(str) ? wasm.cstrToJs(str) : str; + const __dbCleanupMap = function( + pDb, mode/*0=remove, >0=create if needed, <0=do not create if missing*/ + ){ + pDb = __argPDb(pDb); + let m = this.dbMap.get(pDb); + if(!mode){ + this.dbMap.delete(pDb); + return m; + }else if(!m && mode>0){ + this.dbMap.set(pDb, (m = Object.create(null))); } - return [v, n]; + return m; + }.bind(Object.assign(Object.create(null),{ + dbMap: new Map + })); + + __dbCleanupMap.addCollation = function(pDb, name){ + const m = __dbCleanupMap(pDb, 1); + if(!m.collation) m.collation = new Set; + m.collation.add(__argStr(name).toLowerCase()); }; - if(1){/* Special-case handling of sqlite3_exec() */ - const __exec = wasm.xWrap("sqlite3_exec", "int", - ["sqlite3*", "flexible-string", "*", "*", "**"]); - /* Documented in the api object's initializer. */ - capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ - if(f.length!==arguments.length){ - return __dbArgcMismatch(pDb,"sqlite3_exec",f.length); - }else if('function' !== typeof callback){ - return __exec(pDb, sql, callback, pVoid, pErrMsg); + __dbCleanupMap._addUDF = function(pDb, name, arity, map){ + /* Map UDF name to a Set of arity values */ + name = __argStr(name).toLowerCase(); + let u = map.get(name); + if(!u) map.set(name, (u = new Set)); + u.add((arity<0) ? -1 : arity); + }; + + __dbCleanupMap.addFunction = function(pDb, name, arity){ + const m = __dbCleanupMap(pDb, 1); + if(!m.udf) m.udf = new Map; + this._addUDF(pDb, name, arity, m.udf); + }; + + __dbCleanupMap.addWindowFunc = function(pDb, name, arity){ + const m = __dbCleanupMap(pDb, 1); + if(!m.wudf) m.wudf = new Map; + this._addUDF(pDb, name, arity, m.wudf); + }; + + /** + Intended to be called _only_ from sqlite3_close_v2(), + passed its non-0 db argument. + + This function frees up certain automatically-installed WASM + function bindings which were installed on behalf of the given db, + as those may otherwise leak. + + Notable caveat: this is only ever run via + sqlite3.capi.sqlite3_close_v2(). If a client, for whatever + reason, uses sqlite3.wasm.exports.sqlite3_close_v2() (the + function directly exported from WASM), this cleanup will not + happen. + + This is not a silver bullet for avoiding automation-related + leaks but represents "an honest effort." + + The issue being addressed here is covered at: + + https://sqlite.org/wasm/doc/trunk/api-c-style.md#convert-func-ptr + */ + __dbCleanupMap.cleanup = function(pDb){ + pDb = __argPDb(pDb); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; + /** + Installing NULL functions in the C API will remove those + bindings. The FuncPtrAdapter which sits between us and the C + API will also treat that as an opportunity to + wasm.uninstallFunction() any WASM function bindings it has + installed for pDb. + */ + const closeArgs = [pDb]; + for(const name of [ + 'sqlite3_busy_handler', + 'sqlite3_commit_hook', + 'sqlite3_preupdate_hook', + 'sqlite3_progress_handler', + 'sqlite3_rollback_hook', + 'sqlite3_set_authorizer', + 'sqlite3_trace_v2', + 'sqlite3_update_hook' + ]) { + const x = wasm.exports[name]; + closeArgs.length = x.length/*==argument count*/ + /* recall that undefined entries translate to 0 when passed to + WASM. */; + try{ capi[name](...closeArgs) } + catch(e){ + console.warn("close-time call of",name+"(",closeArgs,") threw:",e); } - /* Wrap the callback in a WASM-bound function and convert the callback's - `(char**)` arguments to arrays of strings... */ - const cbwrap = function(pVoid, nCols, pColVals, pColNames){ - let rc = capi.SQLITE_ERROR; - try { - let aVals = [], aNames = [], i = 0, offset = 0; - for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ - aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); - aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); - } - rc = callback(pVoid, nCols, aVals, aNames) | 0; - /* The first 2 args of the callback are useless for JS but - we want the JS mapping of the C API to be as close to the - C API as possible. */ + } + const m = __dbCleanupMap(pDb, 0); + if(!m) return; + if(m.collation){ + for(const name of m.collation){ + try{ + capi.sqlite3_create_collation_v2( + pDb, name, capi.SQLITE_UTF8, 0, 0, 0 + ); }catch(e){ - /* If we set the db error state here, the higher-level exec() call - replaces it with its own, so we have no way of reporting the - exception message except the console. We must not propagate - exceptions through the C API. */ + /*ignored*/ } - return rc; - }; - let pFunc, rc; - try{ - pFunc = wasm.installFunction("ipipp", cbwrap); - rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg); - }catch(e){ - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Error running exec(): "+e.message); - }finally{ - if(pFunc) wasm.uninstallFunction(pFunc); } - return rc; - }; - }/*sqlite3_exec() proxy*/; - - if(1){/* Special-case handling of sqlite3_create_function_v2() - and sqlite3_create_window_function() */ - const sqlite3CreateFunction = wasm.xWrap( - "sqlite3_create_function_v2", "int", - ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, - "int"/*eTextRep*/, "*"/*pApp*/, - "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/] - ); - const sqlite3CreateWindowFunction = wasm.xWrap( - "sqlite3_create_window_function", "int", - ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, - "int"/*eTextRep*/, "*"/*pApp*/, - "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, - "*"/*xInverse*/, "*"/*xDestroy*/] - ); - - const __udfSetResult = function(pCtx, val){ - //console.warn("udfSetResult",typeof val, val); - switch(typeof val) { - case 'undefined': - /* Assume that the client already called sqlite3_result_xxx(). */ - break; - case 'boolean': - capi.sqlite3_result_int(pCtx, val ? 1 : 0); - break; - case 'bigint': - if(wasm.bigIntEnabled){ - if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val); - else toss3("BigInt value",val.toString(),"is too BigInt for int64."); - }else if(util.bigIntFits32(val)){ - capi.sqlite3_result_int(pCtx, Number(val)); - }else if(util.bigIntFitsDouble(val)){ - capi.sqlite3_result_double(pCtx, Number(val)); - }else{ - toss3("BigInt value",val.toString(),"is too BigInt."); - } - break; - case 'number': { - (util.isInt32(val) - ? capi.sqlite3_result_int - : capi.sqlite3_result_double)(pCtx, val); - break; - } - case 'string': - capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT); - break; - case 'object': - if(null===val/*yes, typeof null === 'object'*/) { - capi.sqlite3_result_null(pCtx); - break; - }else if(util.isBindableTypedArray(val)){ - const pBlob = wasm.allocFromTypedArray(val); - capi.sqlite3_result_blob( - pCtx, pBlob, val.byteLength, - wasm.exports[sqlite3.config.deallocExportName] - ); - break; - } - // else fall through - default: - toss3("Don't not how to handle this UDF result value:",(typeof val), val); - }; - }/*__udfSetResult()*/; - - const __udfConvertArgs = function(argc, pArgv){ - let i, pVal, valType, arg; - const tgt = []; - for(i = 0; i < argc; ++i){ - pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i)); - /** - Curiously: despite ostensibly requiring 8-byte - alignment, the pArgv array is parcelled into chunks of - 4 bytes (1 pointer each). The values those point to - have 8-byte alignment but the individual argv entries - do not. - */ - valType = capi.sqlite3_value_type(pVal); - switch(valType){ - case capi.SQLITE_INTEGER: - if(wasm.bigIntEnabled){ - arg = capi.sqlite3_value_int64(pVal); - if(util.bigIntFitsDouble(arg)) arg = Number(arg); - } - else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/; - break; - case capi.SQLITE_FLOAT: - arg = capi.sqlite3_value_double(pVal); - break; - case capi.SQLITE_TEXT: - arg = capi.sqlite3_value_text(pVal); - break; - case capi.SQLITE_BLOB:{ - const n = capi.sqlite3_value_bytes(pVal); - const pBlob = capi.sqlite3_value_blob(pVal); - if(n && !pBlob) sqlite3.WasmAllocError.toss( - "Cannot allocate memory for blob argument of",n,"byte(s)" - ); - arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null; - break; - } - case capi.SQLITE_NULL: - arg = null; break; - default: - toss3("Unhandled sqlite3_value_type()",valType, - "is possibly indicative of incorrect", - "pointer size assumption."); + delete m.collation; + } + let i; + for(i = 0; i < 2; ++i){ /* Clean up UDFs... */ + const fmap = i ? m.wudf : m.udf; + if(!fmap) continue; + const func = i + ? capi.sqlite3_create_window_function + : capi.sqlite3_create_function_v2; + for(const e of fmap){ + const name = e[0], arities = e[1]; + const fargs = [pDb, name, 0/*arity*/, capi.SQLITE_UTF8, 0, 0, 0, 0, 0]; + if(i) fargs.push(0); + for(const arity of arities){ + try{ fargs[2] = arity; func.apply(null, fargs); } + catch(e){/*ignored*/} } - tgt.push(arg); + arities.clear(); } - return tgt; - }/*__udfConvertArgs()*/; - - const __udfSetError = (pCtx, e)=>{ - if(e instanceof sqlite3.WasmAllocError){ - capi.sqlite3_result_error_nomem(pCtx); - }else{ - const msg = ('string'===typeof e) ? e : e.message; - capi.sqlite3_result_error(pCtx, msg, -1); + fmap.clear(); + } + delete m.udf; + delete m.wudf; + }/*__dbCleanupMap.cleanup()*/; + + {/* Binding of sqlite3_close_v2() */ + const __sqlite3CloseV2 = wasm.xWrap("sqlite3_close_v2", "int", "sqlite3*"); + capi.sqlite3_close_v2 = function(pDb){ + if(1!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_close_v2', 1); + if(pDb){ + try{__dbCleanupMap.cleanup(pDb)} catch(e){/*ignored*/} } + return __sqlite3CloseV2(pDb); }; + }/*sqlite3_close_v2()*/ - const __xFunc = function(callback){ - return function(pCtx, argc, pArgv){ - try{ __udfSetResult(pCtx, callback(pCtx, ...__udfConvertArgs(argc, pArgv))) } - catch(e){ - //console.error('xFunc() caught:',e); - __udfSetError(pCtx, e); - } - }; + if(capi.sqlite3session_table_filter){ + const __sqlite3SessionDelete = wasm.xWrap( + 'sqlite3session_delete', undefined, ['sqlite3_session*'] + ); + capi.sqlite3session_delete = function(pSession){ + if(1!==arguments.length){ + return __dbArgcMismatch(pDb, 'sqlite3session_delete', 1); + /* Yes, we're returning a value from a void function. That seems + like the lesser evil compared to not maintaining arg-count + consistency as we do with other similar bindings. */ + } + else if(pSession){ + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + capi.sqlite3session_table_filter(pSession, 0, 0); + } + __sqlite3SessionDelete(pSession); }; + } - const __xInverseAndStep = function(callback){ - return function(pCtx, argc, pArgv){ - try{ callback(pCtx, ...__udfConvertArgs(argc, pArgv)) } - catch(e){ __udfSetError(pCtx, e) } - }; + {/* Bindings for sqlite3_create_collation[_v2]() */ + // contextKey() impl for wasm.xWrap.FuncPtrAdapter + const contextKey = (argv,argIndex)=>{ + return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+ + ':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase() }; + const __sqlite3CreateCollationV2 = wasm.xWrap( + 'sqlite3_create_collation_v2', 'int', [ + 'sqlite3*', 'string', 'int', '*', + new wasm.xWrap.FuncPtrAdapter({ + /* int(*xCompare)(void*,int,const void*,int,const void*) */ + name: 'xCompare', signature: 'i(pipip)', contextKey + }), + new wasm.xWrap.FuncPtrAdapter({ + /* void(*xDestroy(void*) */ + name: 'xDestroy', signature: 'v(p)', contextKey + }) + ] + ); - const __xFinalAndValue = function(callback){ - return function(pCtx){ - try{ __udfSetResult(pCtx, callback(pCtx)) } - catch(e){ __udfSetError(pCtx, e) } - }; + /** + Works exactly like C's sqlite3_create_collation_v2() except that: + + 1) It returns capi.SQLITE_FORMAT if the 3rd argument contains + any encoding-related value other than capi.SQLITE_UTF8. No + other encodings are supported. As a special case, if the + bottom 4 bits of that argument are 0, SQLITE_UTF8 is + assumed. + + 2) It accepts JS functions for its function-pointer arguments, + for which it will install WASM-bound proxies. The bindings + are "permanent," in that they will stay in the WASM environment + until it shuts down unless the client calls this again with the + same collation name and a value of 0 or null for the + the function pointer(s). + + For consistency with the C API, it requires the same number of + arguments. It returns capi.SQLITE_MISUSE if passed any other + argument count. + + Returns 0 on success, non-0 on error, in which case the error + state of pDb (of type `sqlite3*` or argument-convertible to it) + may contain more information. + */ + capi.sqlite3_create_collation_v2 = function(pDb,zName,eTextRep,pArg,xCompare,xDestroy){ + if(6!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_create_collation_v2', 6); + else if( 0 === (eTextRep & 0xf) ){ + eTextRep |= capi.SQLITE_UTF8; + }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ + return __errEncoding(pDb); + } + try{ + const rc = __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy); + if(0===rc && xCompare instanceof Function){ + __dbCleanupMap.addCollation(pDb, zName); + } + return rc; + }catch(e){ + return util.sqlite3_wasm_db_error(pDb, e); + } }; - const __xDestroy = function(callback){ - return function(pVoid){ - try{ callback(pVoid) } - catch(e){ console.error("UDF xDestroy method threw:",e) } - }; + capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{ + return (5===arguments.length) + ? capi.sqlite3_create_collation_v2(pDb,zName,eTextRep,pArg,xCompare,0) + : __dbArgcMismatch(pDb, 'sqlite3_create_collation', 5); }; - const __xMap = Object.assign(Object.create(null), { - xFunc: {sig:'v(pip)', f:__xFunc}, - xStep: {sig:'v(pip)', f:__xInverseAndStep}, - xInverse: {sig:'v(pip)', f:__xInverseAndStep}, - xFinal: {sig:'v(p)', f:__xFinalAndValue}, - xValue: {sig:'v(p)', f:__xFinalAndValue}, - xDestroy: {sig:'v(p)', f:__xDestroy} - }); + }/*sqlite3_create_collation() and friends*/ + + {/* Special-case handling of sqlite3_create_function_v2() + and sqlite3_create_window_function(). */ + /** FuncPtrAdapter for contextKey() for sqlite3_create_function() + and friends. */ + const contextKey = function(argv,argIndex){ + return ( + argv[0/* sqlite3* */] + +':'+(argv[2/*number of UDF args*/] < 0 ? -1 : argv[2]) + +':'+argIndex/*distinct for each xAbc callback type*/ + +':'+wasm.cstrToJs(argv[1]).toLowerCase() + ) + }; - const __xWrapFuncs = function(theFuncs, tgtUninst){ - const rc = [] - let k; - for(k in theFuncs){ - let fArg = theFuncs[k]; - if('function'===typeof fArg){ - const w = __xMap[k]; - fArg = wasm.installFunction(w.sig, w.f(fArg)); - tgtUninst.push(fArg); + /** + JS proxies for the various sqlite3_create[_window]_function() + callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter. + */ + const __cfProxy = Object.assign(Object.create(null), { + xInverseAndStep: { + signature:'v(pip)', contextKey, + callProxy: (callback)=>{ + return (pCtx, argc, pArgv)=>{ + try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) } + catch(e){ capi.sqlite3_result_error_js(pCtx, e) } + }; + } + }, + xFinalAndValue: { + signature:'v(p)', contextKey, + callProxy: (callback)=>{ + return (pCtx)=>{ + try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) } + catch(e){ capi.sqlite3_result_error_js(pCtx, e) } + }; + } + }, + xFunc: { + signature:'v(pip)', contextKey, + callProxy: (callback)=>{ + return (pCtx, argc, pArgv)=>{ + try{ + capi.sqlite3_result_js( + pCtx, + callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) + ); + }catch(e){ + //console.error('xFunc() caught:',e); + capi.sqlite3_result_error_js(pCtx, e); + } + }; + } + }, + xDestroy: { + signature:'v(p)', contextKey, + //Arguable: a well-behaved destructor doesn't require a proxy. + callProxy: (callback)=>{ + return (pVoid)=>{ + try{ callback(pVoid) } + catch(e){ console.error("UDF xDestroy method threw:",e) } + }; } - rc.push(fArg); } - return rc; - }; + })/*__cfProxy*/; + + const __sqlite3CreateFunction = wasm.xWrap( + "sqlite3_create_function_v2", "int", [ + "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, + "int"/*eTextRep*/, "*"/*pApp*/, + new wasm.xWrap.FuncPtrAdapter({name: 'xFunc', ...__cfProxy.xFunc}), + new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), + new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), + new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) + ] + ); + + const __sqlite3CreateWindowFunction = wasm.xWrap( + "sqlite3_create_window_function", "int", [ + "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, + "int"/*eTextRep*/, "*"/*pApp*/, + new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), + new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), + new wasm.xWrap.FuncPtrAdapter({name: 'xValue', ...__cfProxy.xFinalAndValue}), + new wasm.xWrap.FuncPtrAdapter({name: 'xInverse', ...__cfProxy.xInverseAndStep}), + new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) + ] + ); /* Documented in the api object's initializer. */ capi.sqlite3_create_function_v2 = function f( @@ -394,28 +1215,30 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ xFinal, //void (*xFinal)(sqlite3_context*) xDestroy //void (*xDestroy)(void*) ){ - if(f.length!==arguments.length){ + if( f.length!==arguments.length ){ return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length); + }else if( 0 === (eTextRep & 0xf) ){ + eTextRep |= capi.SQLITE_UTF8; + }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ + return __errEncoding(pDb); } - /* Wrap the callbacks in a WASM-bound functions... */ - const uninstall = [/*funcs to uninstall on error*/]; - let rc; try{ - const funcArgs = __xWrapFuncs({xFunc, xStep, xFinal, xDestroy}, - uninstall); - rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, - pApp, ...funcArgs); + const rc = __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, + pApp, xFunc, xStep, xFinal, xDestroy); + if(0===rc && (xFunc instanceof Function + || xStep instanceof Function + || xFinal instanceof Function + || xDestroy instanceof Function)){ + __dbCleanupMap.addFunction(pDb, funcName, nArg); + } + return rc; }catch(e){ console.error("sqlite3_create_function_v2() setup threw:",e); - for(let v of uninstall){ - wasm.uninstallFunction(v); - } - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Creation of UDF threw: "+e.message); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } - return rc; }; + /* Documented in the api object's initializer. */ capi.sqlite3_create_function = function f( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal @@ -431,123 +1254,117 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ pDb, funcName, nArg, eTextRep, pApp, xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) xFinal, //void (*xFinal)(sqlite3_context*) - xValue, //void (*xFinal)(sqlite3_context*) - xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**) + xValue, //void (*xValue)(sqlite3_context*) + xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**) xDestroy //void (*xDestroy)(void*) ){ - if(f.length!==arguments.length){ + if( f.length!==arguments.length ){ return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length); + }else if( 0 === (eTextRep & 0xf) ){ + eTextRep |= capi.SQLITE_UTF8; + }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ + return __errEncoding(pDb); } - /* Wrap the callbacks in a WASM-bound functions... */ - const uninstall = [/*funcs to uninstall on error*/]; - let rc; try{ - const funcArgs = __xWrapFuncs({xStep, xFinal, xValue, xInverse, xDestroy}, - uninstall); - rc = sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, - pApp, ...funcArgs); + const rc = __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, + pApp, xStep, xFinal, xValue, + xInverse, xDestroy); + if(0===rc && (xStep instanceof Function + || xFinal instanceof Function + || xValue instanceof Function + || xInverse instanceof Function + || xDestroy instanceof Function)){ + __dbCleanupMap.addWindowFunc(pDb, funcName, nArg); + } + return rc; }catch(e){ console.error("sqlite3_create_window_function() setup threw:",e); - for(let v of uninstall){ - wasm.uninstallFunction(v); - } - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Creation of UDF threw: "+e.message); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } - return rc; }; /** - A helper for UDFs implemented in JS and bound to WASM by the - client. Given a JS value, udfSetResult(pCtx,X) calls one of the - sqlite3_result_xyz(pCtx,...) routines, depending on X's data - type: - - - `null`: sqlite3_result_null() - - `boolean`: sqlite3_result_int() - - `number`: sqlite3_result_int() or sqlite3_result_double() - - `string`: sqlite3_result_text() - - Uint8Array or Int8Array: sqlite3_result_blob() - - `undefined`: indicates that the UDF called one of the - `sqlite3_result_xyz()` routines on its own, making this - function a no-op. Results are _undefined_ if this function is - passed the `undefined` value but did _not_ call one of the - `sqlite3_result_xyz()` routines. - - Anything else triggers sqlite3_result_error(). + A _deprecated_ alias for capi.sqlite3_result_js() which + predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfSetResult = capi.sqlite3_create_function.udfSetResult = - capi.sqlite3_create_window_function.udfSetResult = __udfSetResult; + capi.sqlite3_create_window_function.udfSetResult = capi.sqlite3_result_js; /** - A helper for UDFs implemented in JS and bound to WASM by the - client. When passed the - (argc,argv) values from the UDF-related functions which receive - them (xFunc, xStep, xInverse), it creates a JS array - representing those arguments, converting each to JS in a manner - appropriate to its data type: numeric, text, blob - (Uint8Array), or null. - - Results are undefined if it's passed anything other than those - two arguments from those specific contexts. - - Thus an argc of 4 will result in a length-4 array containing - the converted values from the corresponding argv. - - The conversion will throw only on allocation error or an internal - error. + A _deprecated_ alias for capi.sqlite3_values_to_js() which + predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfConvertArgs = capi.sqlite3_create_function.udfConvertArgs = - capi.sqlite3_create_window_function.udfConvertArgs = __udfConvertArgs; + capi.sqlite3_create_window_function.udfConvertArgs = capi.sqlite3_values_to_js; /** - A helper for UDFs implemented in JS and bound to WASM by the - client. It expects to be a passed `(sqlite3_context*, Error)` - (an exception object or message string). And it sets the - current UDF's result to sqlite3_result_error_nomem() or - sqlite3_result_error(), depending on whether the 2nd argument - is a sqlite3.WasmAllocError object or not. + A _deprecated_ alias for capi.sqlite3_result_error_js() which + predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfSetError = capi.sqlite3_create_function.udfSetError = - capi.sqlite3_create_window_function.udfSetError = __udfSetError; + capi.sqlite3_create_window_function.udfSetError = capi.sqlite3_result_error_js; }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; - if(1){/* Special-case handling of sqlite3_prepare_v2() and - sqlite3_prepare_v3() */ - /** - Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). - */ - const __prepare = Object.create(null); + {/* Special-case handling of sqlite3_prepare_v2() and + sqlite3_prepare_v3() */ + /** - This binding expects a JS string as its 2nd argument and - null as its final argument. In order to compile multiple - statements from a single string, the "full" impl (see - below) must be used. + Helper for string:flexible conversions which require a + byte-length counterpart argument. Passed a value and its + ostensible length, this function returns [V,N], where V is + either v or a transformed copy of v and N is either n, -1, or + the byte length of v (if it's a byte array or ArrayBuffer). */ - __prepare.basic = wasm.xWrap('sqlite3_prepare_v3', - "int", ["sqlite3*", "string", - "int"/*ignored for this impl!*/, - "int", "**", - "**"/*MUST be 0 or null or undefined!*/]); + const __flexiString = (v,n)=>{ + if('string'===typeof v){ + n = -1; + }else if(util.isSQLableTypedArray(v)){ + n = v.byteLength; + v = util.typedArrayToString( + (v instanceof ArrayBuffer) ? new Uint8Array(v) : v + ); + }else if(Array.isArray(v)){ + v = v.join(""); + n = -1; + } + return [v, n]; + }; + /** - Impl which requires that the 2nd argument be a pointer - to the SQL string, instead of being converted to a - string. This variant is necessary for cases where we - require a non-NULL value for the final argument - (exec()'ing multiple statements from one input - string). For simpler cases, where only the first - statement in the SQL string is required, the wrapper - named sqlite3_prepare_v2() is sufficient and easier to - use because it doesn't require dealing with pointers. + Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). */ - __prepare.full = wasm.xWrap('sqlite3_prepare_v3', - "int", ["sqlite3*", "*", "int", "int", - "**", "**"]); + const __prepare = { + /** + This binding expects a JS string as its 2nd argument and + null as its final argument. In order to compile multiple + statements from a single string, the "full" impl (see + below) must be used. + */ + basic: wasm.xWrap('sqlite3_prepare_v3', + "int", ["sqlite3*", "string", + "int"/*ignored for this impl!*/, + "int", "**", + "**"/*MUST be 0 or null or undefined!*/]), + /** + Impl which requires that the 2nd argument be a pointer + to the SQL string, instead of being converted to a + string. This variant is necessary for cases where we + require a non-NULL value for the final argument + (exec()'ing multiple statements from one input + string). For simpler cases, where only the first + statement in the SQL string is required, the wrapper + named sqlite3_prepare_v2() is sufficient and easier to + use because it doesn't require dealing with pointers. + */ + full: wasm.xWrap('sqlite3_prepare_v3', + "int", ["sqlite3*", "*", "int", "int", + "**", "**"]) + }; - /* Documented in the api object's initializer. */ + /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ if(f.length!==arguments.length){ return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); @@ -564,60 +1381,174 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }; - /* Documented in the api object's initializer. */ + /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ return (f.length===arguments.length) ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length); }; - }/*sqlite3_prepare_v2/v3()*/; - {/* Import C-level constants and structs... */ - const cJson = wasm.xCall('sqlite3_wasm_enum_json'); - if(!cJson){ - toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", - "static buffer size!"); - } - wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); - //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); - for(const t of ['access', 'blobFinalizers', 'dataTypes', - 'encodings', 'fcntl', 'flock', 'ioCap', - 'openFlags', 'prepareFlags', 'resultCodes', - 'serialize', 'syncFlags', 'trace', 'udfFlags', - 'version' - ]){ - for(const e of Object.entries(wasm.ctype[t])){ - // ^^^ [k,v] there triggers a buggy code transormation via one - // of the Emscripten-driven optimizers. - capi[e[0]] = e[1]; + }/*sqlite3_prepare_v2/v3()*/ + + {/*sqlite3_bind_text/blob()*/ + const __bindText = wasm.xWrap("sqlite3_bind_text", "int", [ + "sqlite3_stmt*", "int", "string", "int", "*" + ]); + const __bindBlob = wasm.xWrap("sqlite3_bind_blob", "int", [ + "sqlite3_stmt*", "int", "*", "int", "*" + ]); + + /** Documented in the capi object's initializer. */ + capi.sqlite3_bind_text = function f(pStmt, iCol, text, nText, xDestroy){ + if(f.length!==arguments.length){ + return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt), + "sqlite3_bind_text", f.length); + }else if(wasm.isPtr(text) || null===text){ + return __bindText(pStmt, iCol, text, nText, xDestroy); + }else if(text instanceof ArrayBuffer){ + text = new Uint8Array(text); + }else if(Array.isArray(pMem)){ + text = pMem.join(''); } - } - const __rcMap = Object.create(null); - for(const t of ['resultCodes']){ - for(const e of Object.entries(wasm.ctype[t])){ - __rcMap[e[1]] = e[0]; + let p, n; + try{ + if(util.isSQLableTypedArray(text)){ + p = wasm.allocFromTypedArray(text); + n = text.byteLength; + }else if('string'===typeof text){ + [p, n] = wasm.allocCString(text); + }else{ + return util.sqlite3_wasm_db_error( + capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, + "Invalid 3rd argument type for sqlite3_bind_text()." + ); + } + return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); + }catch(e){ + wasm.dealloc(p); + return util.sqlite3_wasm_db_error( + capi.sqlite3_db_handle(pStmt), e + ); } - } + }/*sqlite3_bind_text()*/; + + /** Documented in the capi object's initializer. */ + capi.sqlite3_bind_blob = function f(pStmt, iCol, pMem, nMem, xDestroy){ + if(f.length!==arguments.length){ + return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt), + "sqlite3_bind_blob", f.length); + }else if(wasm.isPtr(pMem) || null===pMem){ + return __bindBlob(pStmt, iCol, pMem, nMem, xDestroy); + }else if(pMem instanceof ArrayBuffer){ + pMem = new Uint8Array(pMem); + }else if(Array.isArray(pMem)){ + pMem = pMem.join(''); + } + let p, n; + try{ + if(util.isBindableTypedArray(pMem)){ + p = wasm.allocFromTypedArray(pMem); + n = nMem>=0 ? nMem : pMem.byteLength; + }else if('string'===typeof pMem){ + [p, n] = wasm.allocCString(pMem); + }else{ + return util.sqlite3_wasm_db_error( + capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, + "Invalid 3rd argument type for sqlite3_bind_blob()." + ); + } + return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); + }catch(e){ + wasm.dealloc(p); + return util.sqlite3_wasm_db_error( + capi.sqlite3_db_handle(pStmt), e + ); + } + }/*sqlite3_bind_blob()*/; + + }/*sqlite3_bind_text/blob()*/ + + {/* sqlite3_config() */ /** - For the given integer, returns the SQLITE_xxx result code as a - string, or undefined if no such mapping is found. + Wraps a small subset of the C API's sqlite3_config() options. + Unsupported options trigger the return of capi.SQLITE_NOTFOUND. + Passing fewer than 2 arguments triggers return of + capi.SQLITE_MISUSE. */ - capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; - /* Bind all registered C-side structs... */ - const notThese = Object.assign(Object.create(null),{ - // Structs NOT to register - WasmTestStruct: true - }); - if(!util.isUIThread()){ - /* We remove the kvvfs VFS from Worker threads below. */ - notThese.sqlite3_kvvfs_methods = true; - } - for(const s of wasm.ctype.structs){ - if(!notThese[s.name]){ - capi[s.name] = sqlite3.StructBinder(s); + capi.sqlite3_config = function(op, ...args){ + if(arguments.length<2) return capi.SQLITE_MISUSE; + switch(op){ + case capi.SQLITE_CONFIG_COVERING_INDEX_SCAN: // 20 /* int */ + case capi.SQLITE_CONFIG_MEMSTATUS:// 9 /* boolean */ + case capi.SQLITE_CONFIG_SMALL_MALLOC: // 27 /* boolean */ + case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */ + case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */ + case capi.SQLITE_CONFIG_URI:// 17 /* int */ + return wasm.exports.sqlite3_wasm_config_i(op, args[0]); + case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */ + return wasm.exports.sqlite3_wasm_config_ii(op, args[0], args[1]); + case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */ + return wasm.exports.sqlite3_wasm_config_j(op, args[0]); + case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */ + case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */ + case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */ + case capi.SQLITE_CONFIG_GETPCACHE: // 15 /* no-op */ + case capi.SQLITE_CONFIG_HEAP: // 8 /* void*, int nByte, int min */ + case capi.SQLITE_CONFIG_LOG: // 16 /* xFunc, void* */ + case capi.SQLITE_CONFIG_MALLOC:// 4 /* sqlite3_mem_methods* */ + case capi.SQLITE_CONFIG_MMAP_SIZE: // 22 /* sqlite3_int64, sqlite3_int64 */ + case capi.SQLITE_CONFIG_MULTITHREAD: // 2 /* nil */ + case capi.SQLITE_CONFIG_MUTEX: // 10 /* sqlite3_mutex_methods* */ + case capi.SQLITE_CONFIG_PAGECACHE: // 7 /* void*, int sz, int N */ + case capi.SQLITE_CONFIG_PCACHE2: // 18 /* sqlite3_pcache_methods2* */ + case capi.SQLITE_CONFIG_PCACHE: // 14 /* no-op */ + case capi.SQLITE_CONFIG_PCACHE_HDRSZ: // 24 /* int *psz */ + case capi.SQLITE_CONFIG_PMASZ: // 25 /* unsigned int szPma */ + case capi.SQLITE_CONFIG_SERIALIZED: // 3 /* nil */ + case capi.SQLITE_CONFIG_SINGLETHREAD: // 1 /* nil */: + case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */ + case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */ + default: + return capi.SQLITE_NOTFOUND; } - } - }/*end C constant imports*/ + }; + }/* sqlite3_config() */ + + {/*auto-extension bindings.*/ + const __autoExtFptr = new Set; + + capi.sqlite3_auto_extension = function(fPtr){ + if( fPtr instanceof Function ){ + fPtr = wasm.installFunction('i(ppp)', fPtr); + }else if( 1!==arguments.length || !wasm.isPtr(fPtr) ){ + return capi.SQLITE_MISUSE; + } + const rc = wasm.exports.sqlite3_auto_extension(fPtr); + if( fPtr!==arguments[0] ){ + if(0===rc) __autoExtFptr.add(fPtr); + else wasm.uninstallFunction(fPtr); + } + return rc; + }; + + capi.sqlite3_cancel_auto_extension = function(fPtr){ + /* We do not do an automatic JS-to-WASM function conversion here + because it would be senseless: the converted pointer would + never possibly match an already-installed one. */; + if(!fPtr || 1!==arguments.length || !wasm.isPtr(fPtr)) return 0; + return wasm.exports.sqlite3_cancel_auto_extension(fPtr); + /* Note that it "cannot happen" that a client passes a pointer which + is in __autoExtFptr because __autoExtFptr only contains automatic + conversions created inside sqlite3_auto_extension() and + never exposed to the client. */ + }; + + capi.sqlite3_reset_auto_extension = function(){ + wasm.exports.sqlite3_reset_auto_extension(); + for(const fp of __autoExtFptr) wasm.uninstallFunction(fp); + __autoExtFptr.clear(); + }; + }/* auto-extension */ const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); if( pKvvfs ){/* kvvfs-specific glue */ @@ -628,11 +1559,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ delete capi.sqlite3_kvvfs_methods; const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, - pstack = wasm.pstack, - pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc; + pstack = wasm.pstack; const kvvfsStorage = (zClass)=> - ((115/*=='s'*/===wasm.getMemValue(zClass)) + ((115/*=='s'*/===wasm.peek(zClass)) ? sessionStorage : localStorage); /** @@ -648,7 +1578,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return -3/*OOM*/; - const jKey = wasm.cstringToJs(zXKey); + const jKey = wasm.cstrToJs(zXKey); const jV = kvvfsStorage(zClass).getItem(jKey); if(!jV) return -1; const nV = jV.length /* Note that we are relying 100% on v being @@ -656,13 +1586,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ C-string's byte length. */; if(nBuf<=0) return nV; else if(1===nBuf){ - wasm.setMemValue(zBuf, 0); + wasm.poke(zBuf, 0); return nV; } const zV = wasm.scopedAllocCString(jV); if(nBuf > nV + 1) nBuf = nV + 1; wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1); - wasm.setMemValue(zBuf + nBuf - 1, 0); + wasm.poke(zBuf + nBuf - 1, 0); return nBuf - 1; }catch(e){ console.error("kvstorageRead()",e); @@ -677,8 +1607,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return 1/*OOM*/; - const jKey = wasm.cstringToJs(zXKey); - kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData)); + const jKey = wasm.cstrToJs(zXKey); + kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData)); return 0; }catch(e){ console.error("kvstorageWrite()",e); @@ -692,7 +1622,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return 1/*OOM*/; - kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey)); + kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey)); return 0; }catch(e){ console.error("kvstorageDelete()",e); @@ -717,4 +1647,5 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }/*pKvvfs*/ + wasm.xWrap.FuncPtrAdapter.warnOnUse = true; }); diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index 02ce9c0ce..914497602 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -55,12 +55,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(sqliteResultCode){ if(dbPtr instanceof DB) dbPtr = dbPtr.pointer; toss3( - "sqlite result code",sqliteResultCode+":", + "sqlite3 result code",sqliteResultCode+":", (dbPtr ? capi.sqlite3_errmsg(dbPtr) : capi.sqlite3_errstr(sqliteResultCode)) ); } + return arguments[0]; }; /** @@ -71,14 +72,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ wasm.installFunction('i(ippp)', function(t,c,p,x){ if(capi.SQLITE_TRACE_STMT===t){ // x == SQL, p == sqlite3_stmt* - console.log("SQL TRACE #"+(++this.counter), - wasm.cstringToJs(x)); + console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':', + wasm.cstrToJs(x)); } }.bind({counter: 0})); /** - A map of sqlite3_vfs pointers to SQL code to run when the DB - constructor opens a database with the given VFS. + A map of sqlite3_vfs pointers to SQL code or a callback function + to run when the DB constructor opens a database with the given + VFS. In the latter case, the call signature is (theDbObject,sqlite3Namespace) + and the callback is expected to throw on error. */ const __vfsPostOpenSql = Object.create(null); @@ -133,10 +136,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(('string'!==typeof fn && 'number'!==typeof fn) || 'string'!==typeof flagsStr || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){ - console.error("Invalid DB ctor args",opt,arguments); + sqlite3.config.error("Invalid DB ctor args",opt,arguments); toss3("Invalid arguments for DB constructor."); } - let fnJs = ('number'===typeof fn) ? wasm.cstringToJs(fn) : fn; + let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn; const vfsCheck = ctor._name2vfs[fnJs]; if(vfsCheck){ vfsName = vfsCheck.vfs; @@ -153,21 +156,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */; let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0); - pDb = wasm.getPtrValue(pPtr); + pDb = wasm.peekPtr(pPtr); checkSqlite3Rc(pDb, rc); + capi.sqlite3_extended_result_codes(pDb, 1); if(flagsStr.indexOf('t')>=0){ capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT, - __dbTraceToConsole, 0); + __dbTraceToConsole, pDb); } - // Check for per-VFS post-open SQL... - const pVfs = capi.sqlite3_js_db_vfs(pDb); - //console.warn("Opened db",fn,"with vfs",vfsName,pVfs); - if(!pVfs) toss3("Internal error: cannot get VFS for new db handle."); - const postInitSql = __vfsPostOpenSql[pVfs]; - if(postInitSql){ - rc = capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0); - checkSqlite3Rc(pDb, rc); - } }catch( e ){ if( pDb ) capi.sqlite3_close_v2(pDb); throw e; @@ -177,12 +172,34 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ this.filename = fnJs; __ptrMap.set(this, pDb); __stmtMap.set(this, Object.create(null)); + try{ + // Check for per-VFS post-open SQL/callback... + const pVfs = capi.sqlite3_js_db_vfs(pDb); + if(!pVfs) toss3("Internal error: cannot get VFS for new db handle."); + const postInitSql = __vfsPostOpenSql[pVfs]; + if(postInitSql instanceof Function){ + postInitSql(this, sqlite3); + }else if(postInitSql){ + checkSqlite3Rc( + pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) + ); + } + }catch(e){ + this.close(); + throw e; + } }; /** Sets SQL which should be exec()'d on a DB instance after it is - opened with the given VFS pointer. This is intended only for use - by DB subclasses or sqlite3_vfs implementations. + opened with the given VFS pointer. The SQL may be any type + supported by the "string:flexible" function argument conversion. + Alternately, the 2nd argument may be a function, in which case it + is called with (theOo1DbObject,sqlite3Namespace) at the end of + the DB() constructor. The function must throw on error, in which + case the db is closed and the exception is propagated. This + function is intended only for use by DB subclasses or sqlite3_vfs + implementations. */ dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){ __vfsPostOpenSql[pVfs] = sql; @@ -200,9 +217,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){ const arg = {}; - if(1===arguments.length && 'object'===typeof arguments[0]){ - const x = arguments[0]; - Object.keys(x).forEach((k)=>arg[k] = x[k]); + if(1===arguments.length && arguments[0] && 'object'===typeof arguments[0]){ + Object.assign(arg, arguments[0]); if(undefined===arg.flags) arg.flags = 'c'; if(undefined===arg.vfs) arg.vfs = null; if(undefined===arg.filename) arg.filename = ':memory:'; @@ -321,7 +337,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ const Stmt = function(){ if(BindTypes!==arguments[2]){ - toss3("Do not call the Stmt constructor directly. Use DB.prepare()."); + toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare()."); } this.db = arguments[0]; __ptrMap.set(this, arguments[1]); @@ -406,6 +422,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ default: toss3("Invalid returnValue value:",opt.returnValue); } + if(!opt.callback && !opt.returnValue && undefined!==opt.rowMode){ + if(!opt.resultRows) opt.resultRows = []; + out.returnVal = ()=>opt.resultRows; + } if(opt.callback || opt.resultRows){ switch((undefined===opt.rowMode) ? 'array' : opt.rowMode) { @@ -423,19 +443,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(util.isInt32(opt.rowMode)){ out.cbArg = (stmt)=>stmt.get(opt.rowMode); break; - }else if('string'===typeof opt.rowMode && opt.rowMode.length>1){ - /* "$X", ":X", and "@X" fetch column named "X" (case-sensitive!) */ - const prefix = opt.rowMode[0]; - if(':'===prefix || '@'===prefix || '$'===prefix){ - out.cbArg = function(stmt){ - const rc = stmt.get(this.obj)[this.colName]; - return (undefined===rc) ? toss3("exec(): unknown result column:",this.colName) : rc; - }.bind({ - obj:Object.create(null), - colName: opt.rowMode.substr(1) - }); - break; - } + }else if('string'===typeof opt.rowMode + && opt.rowMode.length>1 + && '$'===opt.rowMode[0]){ + /* "$X": fetch column named "X" (case-sensitive!). Prior + to 2022-12-14 ":X" and "@X" were also permitted, but + having so many options is unnecessary and likely to + cause confusion. */ + const $colName = opt.rowMode.substr(1); + out.cbArg = (stmt)=>{ + const rc = stmt.get(Object.create(null))[$colName]; + return (undefined===rc) + ? toss3(capi.SQLITE_NOTFOUND, + "exec(): unknown result column:",$colName) + : rc; + }; + break; } toss3("Invalid rowMode:",opt.rowMode); } @@ -444,32 +467,41 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** - Internal impl of the DB.selectArray() and + Internal impl of the DB.selectValue(), selectArray(), and selectObject() methods. */ - const __selectFirstRow = (db, sql, bind, getArg)=>{ - let stmt, rc; + const __selectFirstRow = (db, sql, bind, ...getArgs)=>{ + const stmt = db.prepare(sql); try { - stmt = db.prepare(sql).bind(bind); - if(stmt.step()) rc = stmt.get(getArg); + return stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined; }finally{ - if(stmt) stmt.finalize(); + stmt.finalize(); } - return rc; }; + /** + Internal impl of the DB.selectArrays() and selectObjects() + methods. + */ + const __selectAll = + (db, sql, bind, rowMode)=>db.exec({ + sql, bind, rowMode, returnValue: 'resultRows' + }); + /** Expects to be given a DB instance or an `sqlite3*` pointer (may be null) and an sqlite3 API result code. If the result code is not falsy, this function throws an SQLite3Error with an error - message from sqlite3_errmsg(), using dbPtr as the db handle, or - sqlite3_errstr() if dbPtr is falsy. Note that if it's passed a - non-error code like SQLITE_ROW or SQLITE_DONE, it will still - throw but the error string might be "Not an error." The various - non-0 non-error codes need to be checked for in - client code where they are expected. + message from sqlite3_errmsg(), using db (or, if db is-a DB, + db.pointer) as the db handle, or sqlite3_errstr() if db is + falsy. Note that if it's passed a non-error code like SQLITE_ROW + or SQLITE_DONE, it will still throw but the error string might be + "Not an error." The various non-0 non-error codes need to be + checked for in client code where they are expected. + + If it does not throw, it returns its first argument. */ - DB.checkRc = checkSqlite3Rc; + DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode); DB.prototype = { /** Returns true if this db handle is open, else false. */ @@ -494,10 +526,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ db is closed but before auxiliary state like this.filename is cleared. - Both onclose handlers are passed this object. If this db is not - opened, neither of the handlers are called. Any exceptions the - handlers throw are ignored because "destructors must not - throw." + Both onclose handlers are passed this object, with the onclose + object as their "this," noting that the db will have been + closed when onclose.after is called. If this db is not opened + when close() is called, neither of the handlers are called. Any + exceptions the handlers throw are ignored because "destructors + must not throw." Note that garbage collection of a db handle, if it happens at all, will never trigger close(), so onclose handlers are not a @@ -574,10 +608,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ); if(pVfs){ const v = new capi.sqlite3_vfs(pVfs); - try{ rc = wasm.cstringToJs(v.$zName) } + try{ rc = wasm.cstrToJs(v.$zName) } finally { v.dispose() } } - return rc; + return rc; }, /** Compiles the given SQL and returns a prepared Stmt. This is @@ -608,7 +642,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try{ ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */; DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null)); - pStmt = wasm.getPtrValue(ppStmt); + pStmt = wasm.peekPtr(ppStmt); } finally { wasm.pstack.restore(stack); @@ -666,21 +700,27 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ unchanged. Achtung: an SQL result may have multiple columns with identical names. - - `callback` = a function which gets called for each row of - the result set, but only if that statement has any result + - `callback` = a function which gets called for each row of the + result set, but only if that statement has any result _rows_. The callback's "this" is the options object, noting that this function synthesizes one if the caller does not pass one to exec(). The second argument passed to the callback is always the current Stmt object, as it's needed if the caller wants to fetch the column names or some such (noting that they could also be fetched via `this.columnNames`, if the client - provides the `columnNames` option). + provides the `columnNames` option). If the callback returns a + literal `false` (as opposed to any other falsy value, e.g. an + implicit `undefined` return), any ongoing statement-`step()` + iteration stops without an error. The return value of the + callback is otherwise ignored. ACHTUNG: The callback MUST NOT modify the Stmt object. Calling any of the Stmt.get() variants, Stmt.getColumnName(), or similar, is legal, but calling step() or finalize() is not. Member methods which are illegal in this context will - trigger an exception. + trigger an exception, but clients must also refrain from using + any lower-level (C-style) APIs which might modify the + statement. The first argument passed to the callback defaults to an array of values from the current result row but may be changed with ... @@ -711,17 +751,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ row. Only that one single value will be passed on. C) A string with a minimum length of 2 and leading character of - ':', '$', or '@' will fetch the row as an object, extract that - one field, and pass that field's value to the callback. Note - that these keys are case-sensitive so must match the case used - in the SQL. e.g. `"select a A from t"` with a `rowMode` of - `'$A'` would work but `'$a'` would not. A reference to a column - not in the result set will trigger an exception on the first - row (as the check is not performed until rows are fetched). - Note also that `$` is a legal identifier character in JS so - need not be quoted. (Design note: those 3 characters were - chosen because they are the characters support for naming bound - parameters.) + '$' will fetch the row as an object, extract that one field, + and pass that field's value to the callback. Note that these + keys are case-sensitive so must match the case used in the + SQL. e.g. `"select a A from t"` with a `rowMode` of `'$A'` + would work but `'$a'` would not. A reference to a column not in + the result set will trigger an exception on the first row (as + the check is not performed until rows are fetched). Note also + that `$` is a legal identifier character in JS so need not be + quoted. Any other `rowMode` value triggers an exception. @@ -736,8 +774,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - `returnValue`: is a string specifying what this function should return: - A) The default value is `"this"`, meaning that the - DB object itself should be returned. + A) The default value is (usually) `"this"`, meaning that the + DB object itself should be returned. The exceptions is if + the caller passes neither of `callback` nor `returnValue` + but does pass an explicit `rowMode` then the default + `returnValue` is `"resultRows"`, described below. B) `"resultRows"` means to return the value of the `resultRows` option. If `resultRows` is not set, this @@ -770,8 +811,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Array.isArray(opt.resultRows) ? opt.resultRows : undefined; let stmt; let bind = opt.bind; - let evalFirstResult = !!(arg.cbArg || opt.columnNames) /* true to evaluate the first result-returning query */; + let evalFirstResult = !!( + arg.cbArg || opt.columnNames || resultRows + ) /* true to step through the first result-returning statement */; const stack = wasm.scopedAllocPush(); + const saveSql = Array.isArray(opt.saveSql) ? opt.saveSql : undefined; try{ const isTA = util.isSQLableTypedArray(arg.sql) /* Optimization: if the SQL is a TypedArray we can save some string @@ -780,16 +824,17 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ space for the SQL (pSql). When prepare_v2() returns, pzTail will point to somewhere in pSql. */ let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql); - const ppStmt = wasm.scopedAlloc(/* output (sqlite3_stmt**) arg and pzTail */ - (2 * wasm.ptrSizeof) - + (sqlByteLen + 1/* SQL + NUL */)); + const ppStmt = wasm.scopedAlloc( + /* output (sqlite3_stmt**) arg and pzTail */ + (2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */) + ); const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */; let pSql = pzTail + wasm.ptrSizeof; const pSqlEnd = pSql + sqlByteLen; if(isTA) wasm.heap8().set(arg.sql, pSql); else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false); - wasm.setMemValue(pSql + sqlByteLen, 0/*NUL terminator*/); - while(pSql && wasm.getMemValue(pSql, 'i8') + wasm.poke(pSql + sqlByteLen, 0/*NUL terminator*/); + while(pSql && wasm.peek(pSql, 'i8') /* Maintenance reminder:^^^ _must_ be 'i8' or else we will very likely cause an endless loop. What that's doing is checking for a terminating NUL byte. If we @@ -797,18 +842,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ around the NUL terminator, and get stuck in and endless loop at the end of the SQL, endlessly re-preparing an empty statement. */ ){ - wasm.setPtrValue(ppStmt, 0); - wasm.setPtrValue(pzTail, 0); + wasm.pokePtr([ppStmt, pzTail], 0); DB.checkRc(this, capi.sqlite3_prepare_v3( this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail )); - const pStmt = wasm.getPtrValue(ppStmt); - pSql = wasm.getPtrValue(pzTail); + const pStmt = wasm.peekPtr(ppStmt); + pSql = wasm.peekPtr(pzTail); sqlByteLen = pSqlEnd - pSql; if(!pStmt) continue; - if(Array.isArray(opt.saveSql)){ - opt.saveSql.push(capi.sqlite3_sql(pStmt).trim()); - } + if(saveSql) saveSql.push(capi.sqlite3_sql(pStmt).trim()); stmt = new Stmt(this, pStmt, BindTypes); if(bind && stmt.parameterCount){ stmt.bind(bind); @@ -821,11 +863,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(Array.isArray(opt.columnNames)){ stmt.getColumnNames(opt.columnNames); } - while(!!arg.cbArg && stmt.step()){ - stmt._isLocked = true; - const row = arg.cbArg(stmt); - if(resultRows) resultRows.push(row); - if(callback) callback.call(opt, row, stmt); + if(arg.cbArg || resultRows){ + for(; stmt.step(); stmt._isLocked = false){ + stmt._isLocked = true; + const row = arg.cbArg(stmt); + if(resultRows) resultRows.push(row); + if(callback && false === callback.call(opt, row, stmt)){ + break; + } + } stmt._isLocked = false; } }else{ @@ -835,7 +881,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ stmt = null; } }/*catch(e){ - console.warn("DB.exec() is propagating exception",opt,e); + sqlite3.config.warn("DB.exec() is propagating exception",opt,e); throw e; }*/finally{ if(stmt){ @@ -846,10 +892,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } return arg.returnVal(); }/*exec()*/, + /** - Creates a new scalar UDF (User-Defined Function) which is - accessible via SQL code. This function may be called in any - of the following forms: + Creates a new UDF (User-Defined Function) which is accessible + via SQL code. This function may be called in any of the + following forms: - (name, function) - (name, function, optionsObject) @@ -865,10 +912,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ functions. Creating an aggregate or window function requires the options-object form (see below for details). - UDFs cannot currently be removed from a DB handle after they're - added. More correctly, they can be removed as documented for - sqlite3_create_function_v2(), but doing so will "leak" the - JS-created WASM binding of those functions. + UDFs can be removed as documented for + sqlite3_create_function_v2() and + sqlite3_create_window_function(), but doing so will "leak" the + JS-created WASM binding of those functions (meaning that their + entries in the WASM indirect function table still + exist). Eliminating that potential leak is a pending TODO. On success, returns this object. Throws on error. @@ -1039,15 +1088,31 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Throws on error (e.g. malformed SQL). */ selectValue: function(sql,bind,asType){ - let stmt, rc; + return __selectFirstRow(this, sql, bind, 0, asType); + }, + + /** + Runs the given query and returns an array of the values from + the first result column of each row of the result set. The 2nd + argument is an optional value for use in a single-argument call + to Stmt.bind(). The 3rd argument may be any value suitable for + use as the 2nd argument to Stmt.get(). If a 3rd argument is + desired but no bind data are needed, pass `undefined` for the 2nd + argument. + + If there are no result rows, an empty array is returned. + */ + selectValues: function(sql,bind,asType){ + const stmt = this.prepare(sql), rc = []; try { - stmt = this.prepare(sql).bind(bind); - if(stmt.step()) rc = stmt.get(0,asType); + stmt.bind(bind); + while(stmt.step()) rc.push(stmt.get(0,asType)); }finally{ - if(stmt) stmt.finalize(); + stmt.finalize(); } return rc; }, + /** Prepares the given SQL, step()s it one time, and returns an array containing the values of the first result row. If it has @@ -1081,9 +1146,32 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return __selectFirstRow(this, sql, bind, {}); }, + /** + Runs the given SQL and returns an array of all results, with + each row represented as an array, as per the 'array' `rowMode` + option to `exec()`. An empty result set resolves + to an empty array. The second argument, if any, is treated as + the 'bind' option to a call to exec(). + */ + selectArrays: function(sql,bind){ + return __selectAll(this, sql, bind, 'array'); + }, + + /** + Works identically to selectArrays() except that each value + in the returned array is an object, as per the 'object' `rowMode` + option to `exec()`. + */ + selectObjects: function(sql,bind){ + return __selectAll(this, sql, bind, 'object'); + }, + /** Returns the number of currently-opened Stmt handles for this db - handle, or 0 if this DB instance is closed. + handle, or 0 if this DB instance is closed. Note that only + handles prepared via this.prepare() are counted, and not + handles prepared using capi.sqlite3_prepare_v3() (or + equivalent). */ openStatementCount: function(){ return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0; @@ -1099,9 +1187,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Note that transactions may not be nested, so this will throw if it is called recursively. For nested transactions, use the savepoint() method or manually manage SAVEPOINTs using exec(). + + If called with 2 arguments, the first must be a keyword which + is legal immediately after a BEGIN statement, e.g. one of + "DEFERRED", "IMMEDIATE", or "EXCLUSIVE". Though the exact list + of supported keywords is not hard-coded here, in order to be + future-compatible, if the argument does not look like a single + keyword then an exception is triggered with a description of + the problem. */ - transaction: function(callback){ - affirmDbOpen(this).exec("BEGIN"); + transaction: function(/* [beginQualifier,] */callback){ + let opener = 'BEGIN'; + if(arguments.length>1){ + if(/[^a-zA-Z]/.test(arguments[0])){ + toss3(capi.SQLITE_MISUSE, "Invalid argument for BEGIN qualifier."); + } + opener += ' '+arguments[0]; + callback = arguments[1]; + } + affirmDbOpen(this).exec(opener); try { const rc = callback(this); this.exec("COMMIT"); @@ -1130,6 +1234,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1"); throw e; } + }, + + /** + A convenience form of DB.checkRc(this,resultCode). If it does + not throw, it returns this object. + */ + checkRc: function(resultCode){ + return DB.checkRc(this, resultCode); } }/*DB.prototype*/; @@ -1157,7 +1269,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(wasm.bigIntEnabled) return t; /* else fall through */ default: - //console.log("isSupportedBindType",t,v); return util.isBindableTypedArray(v) ? BindTypes.blob : undefined; } }; @@ -1167,7 +1278,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ function returns that value, else it throws. */ const affirmSupportedBindType = function(v){ - //console.log('affirmSupportedBindType',v); + //sqlite3.config.log('affirmSupportedBindType',v); return isSupportedBindType(v) || toss3("Unsupported bind() argument type:",typeof v); }; @@ -1212,7 +1323,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ success. */ const bindOne = function f(stmt,ndx,bindType,val){ - affirmUnlocked(stmt, 'bind()'); + affirmUnlocked(affirmStmtOpen(stmt), 'bind()'); if(!f._){ f._tooBigInt = (v)=>toss3( "BigInt value is too big to store without precision loss:", v @@ -1222,29 +1333,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ so we have no range checking. */ f._ = { string: function(stmt, ndx, val, asBlob){ - if(1){ - /* _Hypothetically_ more efficient than the impl in the 'else' block. */ - const stack = wasm.scopedAllocPush(); - try{ - const n = wasm.jstrlen(val); - const pStr = wasm.scopedAlloc(n); - wasm.jstrcpy(val, wasm.heap8u(), pStr, n, false); - const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text; - return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_TRANSIENT); - }finally{ - wasm.scopedAllocPop(stack); - } - }else{ - const bytes = wasm.jstrToUintArray(val,false); - const pStr = wasm.alloc(bytes.length || 1); - wasm.heap8u().set(bytes.length ? bytes : [0], pStr); - try{ - const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text; - return f(stmt.pointer, ndx, pStr, bytes.length, capi.SQLITE_TRANSIENT); - }finally{ - wasm.dealloc(pStr); - } - } + const [pStr, n] = wasm.allocCString(val, true); + const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text; + return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_WASM_DEALLOC); } }; }/* static init */ @@ -1289,36 +1380,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ case BindTypes.blob: { if('string'===typeof val){ rc = f._.string(stmt, ndx, val, true); + break; + }else if(val instanceof ArrayBuffer){ + val = new Uint8Array(val); }else if(!util.isBindableTypedArray(val)){ toss3("Binding a value as a blob requires", - "that it be a string, Uint8Array, or Int8Array."); - }else if(1){ - /* _Hypothetically_ more efficient than the impl in the 'else' block. */ - const stack = wasm.scopedAllocPush(); - try{ - const pBlob = wasm.scopedAlloc(val.byteLength || 1); - wasm.heap8().set(val.byteLength ? val : [0], pBlob) - rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength, - capi.SQLITE_TRANSIENT); - }finally{ - wasm.scopedAllocPop(stack); - } - }else{ - const pBlob = wasm.allocFromTypedArray(val); - try{ - rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength, - capi.SQLITE_TRANSIENT); - }finally{ - wasm.dealloc(pBlob); - } + "that it be a string, Uint8Array, Int8Array, or ArrayBuffer."); } + const pBlob = wasm.alloc(val.byteLength || 1); + wasm.heap8().set(val.byteLength ? val : [0], pBlob) + rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength, + capi.SQLITE_WASM_DEALLOC); break; } default: - console.warn("Unsupported bind() argument type:",val); + sqlite3.config.warn("Unsupported bind() argument type:",val); toss3("Unsupported bind() argument type: "+(typeof val)); } if(rc) DB.checkRc(stmt.db.pointer, rc); + stmt._mayGet = false; return stmt; }; @@ -1406,8 +1486,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - Strings are bound as strings (use bindAsBlob() to force blob binding). - - Uint8Array and Int8Array instances are bound as blobs. - (TODO: binding the other TypedArray types.) + - Uint8Array, Int8Array, and ArrayBuffer instances are bound as + blobs. If passed an array, each element of the array is bound at the parameter index equal to the array index plus 1 @@ -1462,8 +1542,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); return this; + }else if(arg instanceof ArrayBuffer){ + arg = new Uint8Array(arg); } - else if('object'===typeof arg/*null was checked above*/ + if('object'===typeof arg/*null was checked above*/ && !util.isBindableTypedArray(arg)){ /* Treat each property of arg as a named bound parameter. */ if(1!==arguments.length){ @@ -1485,7 +1567,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ the value. The ndx may be a numbered or named bind index. The value must be of type string, null/undefined (both get treated as null), or a TypedArray of a type supported by the bind() - API. + API. This API cannot bind numbers as blobs. If passed a single argument, a bind index of 1 is assumed and the first argument is the value. @@ -1501,9 +1583,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ && BindTypes.null !== t){ toss3("Invalid value type for bindAsBlob()"); } - bindOne(this, ndx, BindTypes.blob, arg); - this._mayGet = false; - return this; + return bindOne(this, ndx, BindTypes.blob, arg); }, /** Steps the statement one time. If the result indicates that a @@ -1519,7 +1599,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ case capi.SQLITE_ROW: return this._mayGet = true; default: this._mayGet = false; - console.warn("sqlite3_step() rc=",rc, + sqlite3.config.warn("sqlite3_step() rc=",rc, capi.sqlite3_js_rc_str(rc), "SQL =", capi.sqlite3_sql(this.pointer)); DB.checkRc(this.db.pointer, rc); @@ -1569,7 +1649,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }, /** Fetches the value from the given 0-based column index of - the current data row, throwing if index is out of range. + the current data row, throwing if index is out of range. Requires that step() has just returned a truthy value, else an exception is thrown. @@ -1642,7 +1722,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ hope for the best, as the C API would do. */ toss3("Integer is out of range for JS integer range: "+rc); } - //console.log("get integer rc=",rc,isInt32(rc)); + //sqlite3.config.log("get integer rc=",rc,isInt32(rc)); return util.isInt32(rc) ? (rc | 0) : rc; } } @@ -1749,10 +1829,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** The OO API's public namespace. */ sqlite3.oo1 = { - version: { - lib: capi.sqlite3_libversion(), - ooApi: "0.1" - }, DB, Stmt }/*oo1 object*/; diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index fed1c5666..b08ad7a7c 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -11,76 +11,17 @@ *********************************************************************** This file is intended to be combined at build-time with other - related code, most notably a header and footer which wraps this whole - file into an Emscripten Module.postRun() handler which has a parameter - named "Module" (the Emscripten Module object). The exact requirements, - conventions, and build process are very much under construction and - will be (re)documented once they've stopped fluctuating so much. + related code, most notably a header and footer which wraps this + whole file into an Emscripten Module.postRun() handler. The sqlite3 + JS API has no hard requirements on Emscripten and does not expose + any Emscripten APIs to clients. It is structured such that its build + can be tweaked to include it in arbitrary WASM environments which + can supply the necessary underlying features (e.g. a POSIX file I/O + layer). - Project home page: https://sqlite.org + Main project home page: https://sqlite.org Documentation home page: https://sqlite.org/wasm - - Specific goals of this subproject: - - - Except where noted in the non-goals, provide a more-or-less - feature-complete wrapper to the sqlite3 C API, insofar as WASM - feature parity with C allows for. In fact, provide at least 4 - APIs... - - 1) 1-to-1 bindings as exported from WASM, with no automatic - type conversions between JS and C. - - 2) A binding of (1) which provides certain JS/C type conversions - to greatly simplify its use. - - 3) A higher-level API, more akin to sql.js and node.js-style - implementations. This one speaks directly to the low-level - API. This API must be used from the same thread as the - low-level API. - - 4) A second higher-level API which speaks to the previous APIs via - worker messages. This one is intended for use in the main - thread, with the lower-level APIs installed in a Worker thread, - and talking to them via Worker messages. Because Workers are - asynchronouns and have only a single message channel, some - acrobatics are needed here to feed async work results back to - the client (as we cannot simply pass around callbacks between - the main and Worker threads). - - - Insofar as possible, support client-side storage using JS - filesystem APIs. As of this writing, such things are still very - much under development. - - Specific non-goals of this project: - - - As WASM is a web-centric technology and UTF-8 is the King of - Encodings in that realm, there are no currently plans to support - the UTF16-related sqlite3 APIs. They would add a complication to - the bindings for no appreciable benefit. Though web-related - implementation details take priority, and the JavaScript - components of the API specifically focus on browser clients, the - lower-level WASM module "should" work in non-web WASM - environments. - - - Supporting old or niche-market platforms. WASM is built for a - modern web and requires modern platforms. - - - Though scalar User-Defined Functions (UDFs) may be created in - JavaScript, there are currently no plans to add support for - aggregate and window functions. - - Attribution: - - This project is endebted to the work of sql.js: - - https://github.com/sql-js/sql.js - - sql.js was an essential stepping stone in this code's development as - it demonstrated how to handle some of the WASM-related voodoo (like - handling pointers-to-pointers and adding JS implementations of - C-bound callback functions). These APIs have a considerably - different shape than sql.js's, however. */ /** @@ -90,14 +31,18 @@ for the current environment, and then optionally be removed from the global object using `delete self.sqlite3ApiBootstrap`. + This function is not intended for client-level use. It is intended + for use in creating bundles configured for specific WASM + environments. + This function expects a configuration object, intended to abstract away details specific to any given WASM environment, primarily so that it can be used without any _direct_ dependency on Emscripten. (Note the default values for the config object!) The config object is only honored the first time this is called. Subsequent calls ignore the argument and return the same - (configured) object which gets initialized by the first call. - This function will throw if any of the required config options are + (configured) object which gets initialized by the first call. This + function will throw if any of the required config options are missing. The config object properties include: @@ -116,27 +61,43 @@ true if `self.BigInt64Array` is available, else false. Some APIs will throw exceptions if called without BigInt support, as BigInt is required for marshalling C-side int64 into and out of JS. + (Sidebar: it is technically possible to add int64 support via + marshalling of int32 pairs, but doing so is unduly invasive.) - `allocExportName`: the name of the function, in `exports`, of the `malloc(3)`-compatible routine for the WASM environment. Defaults - to `"malloc"`. + to `"sqlite3_malloc"`. Beware that using any allocator other than + sqlite3_malloc() may require care in certain client-side code + regarding which allocator is uses. Notably, sqlite3_deserialize() + and sqlite3_serialize() can only safely use memory from different + allocators under very specific conditions. The canonical builds + of this API guaranty that `sqlite3_malloc()` is the JS-side + allocator implementation. - `deallocExportName`: the name of the function, in `exports`, of the `free(3)`-compatible routine for the WASM - environment. Defaults to `"free"`. + environment. Defaults to `"sqlite3_free"`. + + - `reallocExportName`: the name of the function, in `exports`, of + the `realloc(3)`-compatible routine for the WASM + environment. Defaults to `"sqlite3_realloc"`. - - `wasmfsOpfsDir`[^1]: if the environment supports persistent - storage, this directory names the "mount point" for that - directory. It must be prefixed by `/` and may contain only a - single directory-name part. Using the root directory name is not - supported by any current persistent backend. This setting is - only used in WASMFS-enabled builds. + - `debug`, `log`, `warn`, and `error` may be functions equivalent + to the like-named methods of the global `console` object. By + default, these map directly to their `console` counterparts, but + can be replaced with (e.g.) empty functions to squelch all such + output. + - `wasmfsOpfsDir`[^1]: As of 2022-12-17, this feature does not + currently work due to incompatible Emscripten-side changes made + in the WASMFS+OPFS combination. This option is currently ignored. [^1] = This property may optionally be a function, in which case this - function re-assigns it to the value returned from that function, + function re-assigns calls that function to fetch the value, enabling delayed evaluation. + The returned object is the top-level sqlite3 namespace object. + */ 'use strict'; self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( @@ -158,11 +119,27 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( } return !!self.BigInt64Array; })(), - allocExportName: 'malloc', - deallocExportName: 'free', - wasmfsOpfsDir: '/opfs' + debug: console.debug.bind(console), + warn: console.warn.bind(console), + error: console.error.bind(console), + log: console.log.bind(console), + wasmfsOpfsDir: '/opfs', + /** + useStdAlloc is just for testing an allocator discrepancy. The + docs guarantee that this is false in the canonical builds. For + 99% of purposes it doesn't matter which allocators we use, but + it becomes significant with, e.g., sqlite3_deserialize() + and certain wasm.xWrap.resultAdapter()s. + */ + useStdAlloc: false }, apiConfig || {}); + Object.assign(config, { + allocExportName: config.useStdAlloc ? 'malloc' : 'sqlite3_malloc', + deallocExportName: config.useStdAlloc ? 'free' : 'sqlite3_free', + reallocExportName: config.useStdAlloc ? 'realloc' : 'sqlite3_realloc' + }, config); + [ // If any of these config options are functions, replace them with // the result of calling that function... @@ -172,8 +149,12 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( config[k] = config[k](); } }); + config.wasmOpfsDir = + /* 2022-12-17: WASMFS+OPFS can no longer be activated from the + main thread (aborts via a failed assert() if it's attempted), + which eliminates any(?) benefit to supporting it. */ false; - /** + /** The main sqlite3 binding API gets installed into this object, mimicking the C API as closely as we can. The numerous members names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as @@ -191,11 +172,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const capi = Object.create(null); /** Holds state which are specific to the WASM-related - infrastructure and glue code. It is not expected that client - code will normally need these, but they're exposed here in case - it does. These APIs are _not_ to be considered an - official/stable part of the sqlite3 WASM API. They may change - as the developers' experience suggests appropriate changes. + infrastructure and glue code. Note that a number of members of this object are injected dynamically after the api object is fully constructed, so @@ -223,28 +200,49 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** Constructs this object with a message depending on its arguments: - - If it's passed only a single integer argument, it is assumed - to be an sqlite3 C API result code. The message becomes the - result of sqlite3.capi.sqlite3_js_rc_str() or (if that returns - falsy) a synthesized string which contains that integer. + If its first argument is an integer, it is assumed to be + an SQLITE_... result code and it is passed to + sqlite3.capi.sqlite3_js_rc_str() to stringify it. - - If passed 2 arguments and the 2nd is a object, it bevaves - like the Error(string,object) constructor except that the first - argument is subject to the is-integer semantics from the - previous point. + If called with exactly 2 arguments and the 2nd is an object, + that object is treated as the 2nd argument to the parent + constructor. - - Else all arguments are concatenated with a space between each - one, using args.join(' '), to create the error message. + The exception's message is created by concatenating its + arguments with a space between each, except for the + two-args-with-an-objec form and that the first argument will + get coerced to a string, as described above, if it's an + integer. + + If passed an integer first argument, the error object's + `resultCode` member will be set to the given integer value, + else it will be set to capi.SQLITE_ERROR. */ constructor(...args){ - if(1===args.length && __isInt(args[0])){ - super(__rcStr(args[0])); - }else if(2===args.length && 'object'===typeof args){ - if(__isInt(args[0])) super(__rcStr(args[0]), args[1]); - else super(...args); - }else{ - super(args.join(' ')); + let rc; + if(args.length){ + if(__isInt(args[0])){ + rc = args[0]; + if(1===args.length){ + super(__rcStr(args[0])); + }else{ + const rcStr = __rcStr(rc); + if('object'===typeof args[1]){ + super(rcStr,args[1]); + }else{ + args[0] = rcStr+':'; + super(args.join(' ')); + } + } + }else{ + if(2===args.length && 'object'===typeof args[1]){ + super(...args); + }else{ + super(args.join(' ')); + } + } } + this.resultCode = rc || capi.SQLITE_ERROR; this.name = 'SQLite3Error'; } }; @@ -338,12 +336,17 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** - Returns true if v appears to be one of our bind()-able - TypedArray types: Uint8Array or Int8Array. Support for - TypedArrays with element sizes >1 is TODO. + Returns true if v appears to be one of our bind()-able TypedArray + types: Uint8Array or Int8Array or ArrayBuffer. Support for + TypedArrays with element sizes >1 is a potential TODO just + waiting on a use case to justify them. Until then, their `buffer` + property can be used to pass them as an ArrayBuffer. If it's not + a bindable array type, a falsy value is returned. */ const isBindableTypedArray = (v)=>{ - return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); + return v && (v instanceof Uint8Array + || v instanceof Int8Array + || v instanceof ArrayBuffer); }; /** @@ -356,7 +359,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( isSQLableTypedArray() list. */ const isSQLableTypedArray = (v)=>{ - return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT); + return v && (v instanceof Uint8Array + || v instanceof Int8Array + || v instanceof ArrayBuffer); }; /** Returns true if isBindableTypedArray(v) does, else throws with a message @@ -383,13 +388,17 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** If v is-a Array, its join("") result is returned. If isSQLableTypedArray(v) is true then typedArrayToString(v) is - returned. If it looks like a WASM pointer, wasm.cstringToJs(v) is + returned. If it looks like a WASM pointer, wasm.cstrToJs(v) is returned. Else v is returned as-is. */ const flexibleString = function(v){ - if(isSQLableTypedArray(v)) return typedArrayToString(v); + if(isSQLableTypedArray(v)){ + return typedArrayToString( + (v instanceof ArrayBuffer) ? new Uint8Array(v) : v + ); + } else if(Array.isArray(v)) return v.join(""); - else if(wasm.isPtr(v)) v = wasm.cstringToJs(v); + else if(wasm.isPtr(v)) v = wasm.cstrToJs(v); return v; }; @@ -408,13 +417,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( message. */ constructor(...args){ - if(2===args.length && 'object'===typeof args){ + if(2===args.length && 'object'===typeof args[1]){ super(...args); }else if(args.length){ super(args.join(' ')); }else{ super("Allocation failed."); } + this.resultCode = capi.SQLITE_NOMEM; this.name = 'WasmAllocError'; } }; @@ -431,6 +441,92 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; Object.assign(capi, { + /** + sqlite3_bind_blob() works exactly like its C counterpart unless + its 3rd argument is one of: + + - JS string: the 3rd argument is converted to a C string, the + 4th argument is ignored, and the C-string's length is used + in its place. + + - Array: converted to a string as defined for "flexible + strings" and then it's treated as a JS string. + + - Int8Array or Uint8Array: wasm.allocFromTypedArray() is used to + conver the memory to the WASM heap. If the 4th argument is + 0 or greater, it is used as-is, otherwise the array's byteLength + value is used. This is an exception to the C API's undefined + behavior for a negative 4th argument, but results are undefined + if the given 4th argument value is greater than the byteLength + of the input array. + + - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and + treated as that type. + + In all of those cases, the final argument (destructor) is + ignored and capi.SQLITE_WASM_DEALLOC is assumed. + + A 3rd argument of `null` is treated as if it were a WASM pointer + of 0. + + If the 3rd argument is neither a WASM pointer nor one of the + above-described types, capi.SQLITE_MISUSE is returned. + + The first argument may be either an `sqlite3_stmt*` WASM + pointer or an sqlite3.oo1.Stmt instance. + + For consistency with the C API, it requires the same number of + arguments. It returns capi.SQLITE_MISUSE if passed any other + argument count. + */ + sqlite3_bind_blob: undefined/*installed later*/, + + /** + sqlite3_bind_text() works exactly like its C counterpart unless + its 3rd argument is one of: + + - JS string: the 3rd argument is converted to a C string, the + 4th argument is ignored, and the C-string's length is used + in its place. + + - Array: converted to a string as defined for "flexible + strings". The 4th argument is ignored and a value of -1 + is assumed. + + - Int8Array or Uint8Array: is assumed to contain UTF-8 text, is + converted to a string. The 4th argument is ignored, replaced + by the array's byteLength value. + + - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and + treated as that type. + + In each of those cases, the final argument (text destructor) is + ignored and capi.SQLITE_WASM_DEALLOC is assumed. + + A 3rd argument of `null` is treated as if it were a WASM pointer + of 0. + + If the 3rd argument is neither a WASM pointer nor one of the + above-described types, capi.SQLITE_MISUSE is returned. + + The first argument may be either an `sqlite3_stmt*` WASM + pointer or an sqlite3.oo1.Stmt instance. + + For consistency with the C API, it requires the same number of + arguments. It returns capi.SQLITE_MISUSE if passed any other + argument count. + + If client code needs to bind partial strings, it needs to + either parcel the string up before passing it in here or it + must pass in a WASM pointer for the 3rd argument and a valid + 4th-argument value, taking care not to pass a value which + truncates a multi-byte UTF-8 character. When passing + WASM-format strings, it is important that the final argument be + valid or unexpected content can result can result, or even a + crash if the application reads past the WASM heap bounds. + */ + sqlite3_bind_text: undefined/*installed later*/, + /** sqlite3_create_function_v2() differs from its native counterpart only in the following ways: @@ -439,7 +535,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( any encoding other than sqlite3.SQLITE_UTF8. The JS API does not currently support any other encoding and likely never will. This function does not replace that argument on its own - because it may contain other flags. + because it may contain other flags. As a special case, if + the bottom 4 bits of that argument are 0, SQLITE_UTF8 is + assumed. 2) Any of the four final arguments may be either WASM pointers (assumed to be function pointers) or JS Functions. In the @@ -447,6 +545,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( sqlite3.capi.wasm.installFunction() and that wrapper is passed on to the native implementation. + For consistency with the C API, it requires the same number of + arguments. It returns capi.SQLITE_MISUSE if passed any other + argument count. + The semantics of JS functions are: xFunc: is passed `(pCtx, ...values)`. Its return value becomes @@ -533,18 +635,18 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH` flag. */ - sqlite3_create_function_v2: function( + sqlite3_create_function_v2: ( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal, xDestroy - ){/*installed later*/}, + )=>{/*installed later*/}, /** Equivalent to passing the same arguments to sqlite3_create_function_v2(), with 0 as the final argument. */ - sqlite3_create_function:function( + sqlite3_create_function: ( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal - ){/*installed later*/}, + )=>{/*installed later*/}, /** The sqlite3_create_window_function() JS wrapper differs from its native implementation in the exact same way that @@ -552,10 +654,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( xInverse(), is treated identically to xStep() by the wrapping layer. */ - sqlite3_create_window_function: function( + sqlite3_create_window_function: ( pDb, funcName, nArg, eTextRep, pApp, xStep, xFinal, xValue, xInverse, xDestroy - ){/*installed later*/}, + )=>{/*installed later*/}, /** The sqlite3_prepare_v3() binding handles two different uses with differing JS/WASM semantics: @@ -575,8 +677,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( terminated with a 0 byte. In usage (1), the 2nd argument must be of type string, - Uint8Array, or Int8Array (either of which is assumed to - hold SQL). If it is, this function assumes case (1) and + Uint8Array, Int8Array, or ArrayBuffer (all of which are assumed + to hold SQL). If it is, this function assumes case (1) and calls the underyling C function with the equivalent of: (pDb, sqlAsString, -1, prepFlags, ppStmt, null) @@ -600,7 +702,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( It returns its result and compiled statement as documented in the C API. Fetching the output pointers (5th and 6th - parameters) requires using `capi.wasm.getMemValue()` (or + parameters) requires using `capi.wasm.peek()` (or equivalent) and the `pzTail` will point to an address relative to the `sqlAsPointer` value. @@ -637,7 +739,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( If the callback is not a JS function then this binding performs no translation of the callback, but the sql argument is still converted to a WASM string for the call using the - "flexible-string" argument converter. + "string:flexible" argument converter. */ sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, @@ -668,13 +770,16 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( affirmBindableTypedArray, flexibleString, bigIntFits32, bigIntFits64, bigIntFitsDouble, isBindableTypedArray, - isInt32, isSQLableTypedArray, isTypedArray, + isInt32, isSQLableTypedArray, isTypedArray, typedArrayToString, - isUIThread: ()=>'undefined'===typeof WorkerGlobalScope, + isUIThread: ()=>(self.window===self && !!self.document), + // is this true for ESM?: 'undefined'===typeof WorkerGlobalScope isSharedTypedArray, + toss: function(...args){throw new Error(args.join(' '))}, + toss3, typedArrayPart }; - + Object.assign(wasm, { /** Emscripten APIs have a deep-seated assumption that all pointers @@ -686,9 +791,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** The WASM IR (Intermediate Representation) value for pointer-type values. It MUST refer to a value type of the - size described by this.ptrSizeof _or_ it may be any value - which ends in '*', which Emscripten's glue code internally - translates to i32. + size described by this.ptrSizeof. */ ptrIR: config.wasmPtrIR || "i32", /** @@ -717,12 +820,12 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( "or config.memory (imported)."), /** - The API's one single point of access to the WASM-side memory - allocator. Works like malloc(3) (and is likely bound to - malloc()) but throws an WasmAllocError if allocation fails. It is - important that any code which might pass through the sqlite3 C - API NOT throw and must instead return SQLITE_NOMEM (or - equivalent, depending on the context). + The API's primary point of access to the WASM-side memory + allocator. Works like sqlite3_malloc() but throws a + WasmAllocError if allocation fails. It is important that any + code which might pass through the sqlite3 C API NOT throw and + must instead return SQLITE_NOMEM (or equivalent, depending on + the context). Very few cases in the sqlite3 JS APIs can result in client-defined functions propagating exceptions via the C-style @@ -734,7 +837,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( catch exceptions and convert them to appropriate error codes. For cases where non-throwing allocation is required, use - sqlite3.wasm.alloc.impl(), which is direct binding of the + this.alloc.impl(), which is direct binding of the underlying C-level allocator. Design note: this function is not named "malloc" primarily @@ -745,9 +848,27 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( alloc: undefined/*installed later*/, /** - The API's one single point of access to the WASM-side memory - deallocator. Works like free(3) (and is likely bound to - free()). + Rarely necessary in JS code, this routine works like + sqlite3_realloc(M,N), where M is either NULL or a pointer + obtained from this function or this.alloc() and N is the number + of bytes to reallocate the block to. Returns a pointer to the + reallocated block or 0 if allocation fails. + + If M is NULL and N is positive, this behaves like + this.alloc(N). If N is 0, it behaves like this.dealloc(). + Results are undefined if N is negative (sqlite3_realloc() + treats that as 0, but if this code is built with a different + allocator it may misbehave with negative values). + + Like this.alloc.impl(), this.realloc.impl() is a direct binding + to the underlying realloc() implementation which does not throw + exceptions, instead returning 0 on allocation error. + */ + realloc: undefined/*installed later*/, + + /** + The API's primary point of access to the WASM-side memory + deallocator. Works like sqlite3_free(). Design note: this function is not named "free" for the same reason that this.alloc() is not called this.malloc(). @@ -764,18 +885,20 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( returned pointer must eventually be passed to wasm.dealloc() to clean it up. + The argument may be a Uint8Array, Int8Array, or ArrayBuffer, + and it throws if passed any other type. + As a special case, to avoid further special cases where this is used, if srcTypedArray.byteLength is 0, it allocates a single byte and sets it to the value 0. Even in such cases, calls must behave as if the allocated memory has exactly srcTypedArray.byteLength bytes. - - ACHTUNG: this currently only works for Uint8Array and - Int8Array types and will throw if srcTypedArray is of - any other type. */ wasm.allocFromTypedArray = function(srcTypedArray){ + if(srcTypedArray instanceof ArrayBuffer){ + srcTypedArray = new Uint8Array(srcTypedArray); + } affirmBindableTypedArray(srcTypedArray); const pRet = wasm.alloc(srcTypedArray.byteLength || 1); wasm.heapForSize(srcTypedArray.constructor).set( @@ -784,24 +907,31 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return pRet; }; - const keyAlloc = config.allocExportName || 'malloc', - keyDealloc = config.deallocExportName || 'free'; - for(const key of [keyAlloc, keyDealloc]){ - const f = wasm.exports[key]; - if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function."); - } + { + // Set up allocators... + const keyAlloc = config.allocExportName, + keyDealloc = config.deallocExportName, + keyRealloc = config.reallocExportName; + for(const key of [keyAlloc, keyDealloc, keyRealloc]){ + const f = wasm.exports[key]; + if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function."); + } - wasm.alloc = function f(n){ - const m = f.impl(n); - if(!m) throw new WasmAllocError("Failed to allocate",n," bytes."); - return m; - }; - wasm.alloc.impl = wasm.exports[keyAlloc]; - wasm.dealloc = wasm.exports[keyDealloc]; + wasm.alloc = function f(n){ + return f.impl(n) || WasmAllocError.toss("Failed to allocate",n," bytes."); + }; + wasm.alloc.impl = wasm.exports[keyAlloc]; + wasm.realloc = function f(m,n){ + const m2 = f.impl(m,n); + return n ? (m2 || WasmAllocError.toss("Failed to reallocate",n," bytes.")) : 0; + }; + wasm.realloc.impl = wasm.exports[keyRealloc]; + wasm.dealloc = wasm.exports[keyDealloc]; + } /** Reports info about compile-time options using - sqlite_compileoption_get() and sqlite3_compileoption_used(). It + sqlite3_compileoption_get() and sqlite3_compileoption_used(). It has several distinct uses: If optName is an array then it is expected to be a list of @@ -838,7 +968,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const m = f._rx.exec(opt); rv[0] = (m ? m[1] : opt); rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; - }; + }; } const rc = {}, ov = [0,0]; let i = 0, k; @@ -864,165 +994,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ) ? !!capi.sqlite3_compileoption_used(optName) : false; }/*compileOptionUsed()*/; - /** - Signatures for the WASM-exported C-side functions. Each entry - is an array with 2+ elements: - - [ "c-side name", - "result type" (wasm.xWrap() syntax), - [arg types in xWrap() syntax] - // ^^^ this needn't strictly be an array: it can be subsequent - // elements instead: [x,y,z] is equivalent to x,y,z - ] - - Note that support for the API-specific data types in the - result/argument type strings gets plugged in at a later phase in - the API initialization process. - */ - wasm.bindingSignatures = [ - // Please keep these sorted by function name! - ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], - ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*" - /* TODO: we should arguably write a custom wrapper which knows - how to handle Blob, TypedArrays, and JS strings. */ - ], - ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], - ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"], - ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], - ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], - ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], - ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int" - /* We should arguably create a hand-written binding of - bind_text() which does more flexible text conversion, along - the lines of sqlite3_prepare_v3(). The slightly problematic - part is the final argument (text destructor). */ - ], - ["sqlite3_close_v2", "int", "sqlite3*"], - ["sqlite3_changes", "int", "sqlite3*"], - ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], - ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], - ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], - ["sqlite3_column_count", "int", "sqlite3_stmt*"], - ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"], - ["sqlite3_column_int","int", "sqlite3_stmt*", "int"], - ["sqlite3_column_name","string", "sqlite3_stmt*", "int"], - ["sqlite3_column_text","string", "sqlite3_stmt*", "int"], - ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], - ["sqlite3_compileoption_get", "string", "int"], - ["sqlite3_compileoption_used", "int", "string"], - /* sqlite3_create_function(), sqlite3_create_function_v2(), and - sqlite3_create_window_function() use hand-written bindings to - simplify handling of their function-type arguments. */ - ["sqlite3_data_count", "int", "sqlite3_stmt*"], - ["sqlite3_db_filename", "string", "sqlite3*", "string"], - ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], - ["sqlite3_db_name", "string", "sqlite3*", "int"], - ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"] - /* Careful! Short version: de/serialize() are problematic because they - might use a different allocator than the user for managing the - deserialized block. de/serialize() are ONLY safe to use with - sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, - ["sqlite3_errmsg", "string", "sqlite3*"], - ["sqlite3_error_offset", "int", "sqlite3*"], - ["sqlite3_errstr", "string", "int"], - /*["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**" - Handled seperately to perform translation of the callback - into a WASM-usable one. ],*/ - ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], - ["sqlite3_extended_errcode", "int", "sqlite3*"], - ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], - ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], - ["sqlite3_finalize", "int", "sqlite3_stmt*"], - ["sqlite3_free", undefined,"*"], - ["sqlite3_initialize", undefined], - /*["sqlite3_interrupt", undefined, "sqlite3*" - ^^^ we cannot actually currently support this because JS is - single-threaded and we don't have a portable way to access a DB - from 2 SharedWorkers concurrently. ],*/ - ["sqlite3_libversion", "string"], - ["sqlite3_libversion_number", "int"], - ["sqlite3_malloc", "*","int"], - ["sqlite3_open", "int", "string", "*"], - ["sqlite3_open_v2", "int", "string", "*", "int", "string"], - /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled - separately due to us requiring two different sets of semantics - for those, depending on how their SQL argument is provided. */ - /* sqlite3_randomness() uses a hand-written wrapper to extend - the range of supported argument types. */ - ["sqlite3_realloc", "*","*","int"], - ["sqlite3_reset", "int", "sqlite3_stmt*"], - ["sqlite3_result_blob",undefined, "*", "*", "int", "*"], - ["sqlite3_result_double",undefined, "*", "f64"], - ["sqlite3_result_error",undefined, "*", "string", "int"], - ["sqlite3_result_error_code", undefined, "*", "int"], - ["sqlite3_result_error_nomem", undefined, "*"], - ["sqlite3_result_error_toobig", undefined, "*"], - ["sqlite3_result_int",undefined, "*", "int"], - ["sqlite3_result_null",undefined, "*"], - ["sqlite3_result_text",undefined, "*", "string", "int", "*"], - ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], - ["sqlite3_shutdown", undefined], - ["sqlite3_sourceid", "string"], - ["sqlite3_sql", "string", "sqlite3_stmt*"], - ["sqlite3_step", "int", "sqlite3_stmt*"], - ["sqlite3_strglob", "int", "string","string"], - ["sqlite3_strlike", "int", "string","string","int"], - ["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"], - ["sqlite3_total_changes", "int", "sqlite3*"], - ["sqlite3_uri_boolean", "int", "string", "string", "int"], - ["sqlite3_uri_key", "string", "string", "int"], - ["sqlite3_uri_parameter", "string", "string", "string"], - ["sqlite3_user_data","void*", "sqlite3_context*"], - ["sqlite3_value_blob", "*", "sqlite3_value*"], - ["sqlite3_value_bytes","int", "sqlite3_value*"], - ["sqlite3_value_double","f64", "sqlite3_value*"], - ["sqlite3_value_int","int", "sqlite3_value*"], - ["sqlite3_value_text", "string", "sqlite3_value*"], - ["sqlite3_value_type", "int", "sqlite3_value*"], - ["sqlite3_vfs_find", "*", "string"], - ["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"], - ["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"] - ]/*wasm.bindingSignatures*/; - - if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ - /* ^^^ "the problem" is that this is an option feature and the - build-time function-export list does not currently take - optional features into account. */ - wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); - } - - /** - Functions which require BigInt (int64) support are separated from - the others because we need to conditionally bind them or apply - dummy impls, depending on the capabilities of the environment. - */ - wasm.bindingSignatures.int64 = [ - ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], - ["sqlite3_changes64","i64", ["sqlite3*"]], - ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], - ["sqlite3_malloc64", "*","i64"], - ["sqlite3_msize", "i64", "*"], - ["sqlite3_realloc64", "*","*", "i64"], - ["sqlite3_result_int64",undefined, "*", "i64"], - ["sqlite3_total_changes64", "i64", ["sqlite3*"]], - ["sqlite3_uri_int64", "i64", ["string", "string", "i64"]], - ["sqlite3_value_int64","i64", "sqlite3_value*"], - ]; - - /** - Functions which are intended solely for API-internal use by the - WASM components, not client code. These get installed into - sqlite3.wasm. - */ - wasm.bindingSignatures.wasm = [ - ["sqlite3_wasm_db_reset", "int", "sqlite3*"], - ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], - ["sqlite3_wasm_vfs_create_file", "int", - "sqlite3_vfs*","string","*", "int"], - ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] - ]; - - /** sqlite3.wasm.pstack (pseudo-stack) holds a special-case stack-style allocator intended only for use with _small_ data of @@ -1052,7 +1023,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( space managed by Emscripten's stack-management, so does not collide with Emscripten-provided stack allocation APIs. The memory lives in the WASM heap and can be used with routines such - as wasm.setMemValue() and any wasm.heap8u().slice(). + as wasm.poke() and wasm.heap8u().slice(). */ wasm.pstack = Object.assign(Object.create(null),{ /** @@ -1065,15 +1036,23 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Attempts to allocate the given number of bytes from the pstack. On success, it zeroes out a block of memory of the given size, adjusts the pstack pointer, and returns a pointer - to the memory. On error, returns throws a WasmAllocError. The + to the memory. On error, throws a WasmAllocError. The memory must eventually be released using restore(). + If n is a string, it must be a WASM "IR" value in the set + accepted by wasm.sizeofIR(), which is mapped to the size of + that data type. If passed a string not in that set, it throws a + WasmAllocError. + This method always adjusts the given value to be a multiple of 8 bytes because failing to do so can lead to incorrect results when reading and writing 64-bit values from/to the WASM heap. Similarly, the returned address is always 8-byte aligned. */ - alloc: (n)=>{ + alloc: function(n){ + if('string'===typeof n && !(n = wasm.sizeofIR(n))){ + WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")"); + } return wasm.exports.sqlite3_wasm_pstack_alloc(n) || WasmAllocError.toss("Could not allocate",n, "bytes from the pstack."); @@ -1083,6 +1062,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( returns the addresses as an array of n element, each holding the address of one chunk. + sz may optionally be an IR string accepted by wasm.sizeofIR(). + Throws a WasmAllocError if allocation fails. Example: @@ -1091,13 +1072,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); ``` */ - allocChunks: (n,sz)=>{ + allocChunks: function(n,sz){ + if('string'===typeof sz && !(sz = wasm.sizeofIR(sz))){ + WasmAllocError.toss("Invalid size value for allocChunks(",arguments[1],")"); + } const mem = wasm.pstack.alloc(n * sz); const rc = []; let i = 0, offset = 0; - for(; i < n; offset = (sz * ++i)){ - rc.push(mem + offset); - } + for(; i < n; ++i, offset += sz) rc.push(mem + offset); return rc; }, /** @@ -1111,7 +1093,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( When a returned pointers will refer to a 64-bit value, e.g. a double or int64, and that value must be written or fetched, - e.g. using wasm.setMemValue() or wasm.getMemValue(), it is + e.g. using wasm.poke() or wasm.peek(), it is important that the pointer in question be aligned to an 8-byte boundary or else it will not be fetched or written properly and will corrupt or read neighboring memory. @@ -1195,6 +1177,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** State for sqlite3_wasmfs_opfs_dir(). */ let __wasmfsOpfsDir = undefined; /** + 2022-12-17: incompatible WASMFS changes have made WASMFS+OPFS + unavailable from the main thread, which eliminates the most + significant benefit of supporting WASMFS. This function is now a + no-op which always returns a falsy value. Before that change, + this function behaved as documented below (and how it will again + if we can find a compelling reason to support it). + If the wasm environment has a WASMFS/OPFS-backed persistent storage directory, its path is returned by this function. If it does not then it returns "" (noting that "" is a falsy value). @@ -1211,6 +1200,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; // If we have no OPFS, there is no persistent dir const pdir = config.wasmfsOpfsDir; + console.error("sqlite3_wasmfs_opfs_dir() can no longer work due "+ + "to incompatible WASMFS changes. It will be removed."); if(!pdir || !self.FileSystemHandle || !self.FileSystemDirectoryHandle @@ -1298,7 +1289,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( let pVfs = capi.sqlite3_vfs_find(0); while(pVfs){ const oVfs = new capi.sqlite3_vfs(pVfs); - rc.push(wasm.cstringToJs(oVfs.$zName)); + rc.push(wasm.cstrToJs(oVfs.$zName)); pVfs = oVfs.$pNext; oVfs.dispose(); } @@ -1306,17 +1297,26 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** - Serializes the given `sqlite3*` pointer to a Uint8Array, as per - sqlite3_serialize(). On success it returns a Uint8Array. On - error it throws with a description of the problem. + A convenience wrapper around sqlite3_serialize() which serializes + the given `sqlite3*` pointer to a Uint8Array. The first argument + may be either an `sqlite3*` or an sqlite3.oo1.DB instance. + + On success it returns a Uint8Array. If the schema is empty, an + empty array is returned. + + `schema` is the schema to serialize. It may be a WASM C-string + pointer or a JS string. If it is falsy, it defaults to `"main"`. + + On error it throws with a description of the problem. */ - capi.sqlite3_js_db_export = function(pDb){ + capi.sqlite3_js_db_export = function(pDb, schema=0){ + pDb = wasm.xWrap.testConvertArg('sqlite3*', pDb); if(!pDb) toss3('Invalid sqlite3* argument.'); if(!wasm.bigIntEnabled) toss3('BigInt64 support is not enabled.'); - const stack = wasm.pstack.pointer; + const scope = wasm.scopedAllocPush(); let pOut; try{ - const pSize = wasm.pstack.alloc(8/*i64*/ + wasm.ptrSizeof); + const pSize = wasm.scopedAlloc(8/*i64*/ + wasm.ptrSizeof); const ppOut = pSize + 8; /** Maintenance reminder, since this cost a full hour of grief @@ -1325,22 +1325,25 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( export reads a garbage size because it's not on an 8-byte memory boundary! */ + const zSchema = schema + ? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema)) + : 0; let rc = wasm.exports.sqlite3_wasm_db_serialize( - pDb, ppOut, pSize, 0 + pDb, zSchema, ppOut, pSize, 0 ); if(rc){ toss3("Database serialization failed with code", sqlite3.capi.sqlite3_js_rc_str(rc)); } - pOut = wasm.getPtrValue(ppOut); - const nOut = wasm.getMemValue(pSize, 'i64'); + pOut = wasm.peekPtr(ppOut); + const nOut = wasm.peek(pSize, 'i64'); rc = nOut ? wasm.heap8u().slice(pOut, pOut + Number(nOut)) : new Uint8Array(); return rc; }finally{ if(pOut) wasm.exports.sqlite3_free(pOut); - wasm.pstack.restore(stack); + wasm.scopedAllocPop(scope); } }; @@ -1366,7 +1369,87 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( "bytes for sqlite3_aggregate_context()") : 0); }; - + + /** + Creates a file using the storage appropriate for the given + sqlite3_vfs. The first argument may be a VFS name (JS string + only, NOT a WASM C-string), WASM-managed `sqlite3_vfs*`, or + a capi.sqlite3_vfs instance. Pass 0 (a NULL pointer) to use the + default VFS. If passed a string which does not resolve using + sqlite3_vfs_find(), an exception is thrown. (Note that a WASM + C-string is not accepted because it is impossible to + distinguish from a C-level `sqlite3_vfs*`.) + + The second argument, the filename, must be a JS or WASM C-string. + + The 3rd may either be falsy, a valid WASM memory pointer, an + ArrayBuffer, or a Uint8Array. The 4th must be the length, in + bytes, of the data array to copy. If the 3rd argument is an + ArrayBuffer or Uint8Array and the 4th is not a positive integer + then the 4th defaults to the array's byteLength value. + + If data is falsy then a file is created with dataLen bytes filled + with uninitialized data (whatever truncate() leaves there). If + data is not falsy then a file is created or truncated and it is + filled with the first dataLen bytes of the data source. + + Throws if any arguments are invalid or if creating or writing to + the file fails. + + Note that most VFSes do _not_ automatically create directory + parts of filenames, nor do all VFSes have a concept of + directories. If the given filename is not valid for the given + VFS, an exception will be thrown. This function exists primarily + to assist in implementing file-upload capability, with the caveat + that clients must have some idea of the VFS into which they want + to upload and that VFS must support the operation. + + VFS-specific notes: + + - "memdb": results are undefined. + + - "kvvfs": will fail with an I/O error due to strict internal + requirments of that VFS's xTruncate(). + + - "unix" and related: will use the WASM build's equivalent of the + POSIX I/O APIs. This will work so long as neither a specific + VFS nor the WASM environment imposes requirements which break it. + + - "opfs": uses OPFS storage and creates directory parts of the + filename. + */ + capi.sqlite3_js_vfs_create_file = function(vfs, filename, data, dataLen){ + let pData; + if(data){ + if(wasm.isPtr(data)){ + pData = data; + }else if(data instanceof ArrayBuffer){ + data = new Uint8Array(data); + } + if(data instanceof Uint8Array){ + pData = wasm.allocFromTypedArray(data); + if(arguments.length<4 || !util.isInt32(dataLen) || dataLen<0){ + dataLen = data.byteLength; + } + }else{ + SQLite3Error.toss("Invalid 3rd argument type for sqlite3_js_vfs_create_file()."); + } + }else{ + pData = 0; + } + if(!util.isInt32(dataLen) || dataLen<0){ + wasm.dealloc(pData); + SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file()."); + } + try{ + const rc = wasm.sqlite3_wasm_vfs_create_file(vfs, filename, pData, dataLen); + if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", + capi.sqlite3_js_rc_str(rc)); + }finally{ + wasm.dealloc(pData); + } + }; + if( util.isUIThread() ){ /* Features specific to the main window thread... */ @@ -1447,6 +1530,317 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }/* main-window-only bits */ + /** + Wraps all known variants of the C-side variadic + sqlite3_db_config(). + + Full docs: https://sqlite.org/c3ref/db_config.html + + Returns capi.SQLITE_MISUSE if op is not a valid operation ID. + */ + capi.sqlite3_db_config = function f(pDb, op, ...args){ + if(!this.s){ + this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int', + ['sqlite3*', 'int', 'string:static'] + /* MAINDBNAME requires a static string */); + this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int', + ['sqlite3*', 'int', '*','int', 'int']); + this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int', + ['sqlite3*', 'int', 'int','*']); + } + const c = capi; + switch(op){ + case c.SQLITE_DBCONFIG_ENABLE_FKEY: + case c.SQLITE_DBCONFIG_ENABLE_TRIGGER: + case c.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case c.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case c.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case c.SQLITE_DBCONFIG_ENABLE_QPSG: + case c.SQLITE_DBCONFIG_TRIGGER_EQP: + case c.SQLITE_DBCONFIG_RESET_DATABASE: + case c.SQLITE_DBCONFIG_DEFENSIVE: + case c.SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case c.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case c.SQLITE_DBCONFIG_DQS_DML: + case c.SQLITE_DBCONFIG_DQS_DDL: + case c.SQLITE_DBCONFIG_ENABLE_VIEW: + case c.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case c.SQLITE_DBCONFIG_TRUSTED_SCHEMA: + return this.ip(pDb, op, args[0], args[1] || 0); + case c.SQLITE_DBCONFIG_LOOKASIDE: + return this.pii(pDb, op, args[0], args[1], args[2]); + case c.SQLITE_DBCONFIG_MAINDBNAME: + return this.s(pDb, op, args[0]); + default: + return c.SQLITE_MISUSE; + } + }.bind(Object.create(null)); + + /** + Given a (sqlite3_value*), this function attempts to convert it + to an equivalent JS value with as much fidelity as feasible and + return it. + + By default it throws if it cannot determine any sensible + conversion. If passed a falsy second argument, it instead returns + `undefined` if no suitable conversion is found. Note that there + is no conversion from SQL to JS which results in the `undefined` + value, so `undefined` has an unambiguous meaning here. It will + always throw a WasmAllocError if allocating memory for a + conversion fails. + + Caveats: + + - It does not support sqlite3_value_to_pointer() conversions + because those require a type name string which this function + does not have and cannot sensibly be given at the level of the + API where this is used (e.g. automatically converting UDF + arguments). Clients using sqlite3_value_to_pointer(), and its + related APIs, will need to manage those themselves. + */ + capi.sqlite3_value_to_js = function(pVal,throwIfCannotConvert=true){ + let arg; + const valType = capi.sqlite3_value_type(pVal); + switch(valType){ + case capi.SQLITE_INTEGER: + if(wasm.bigIntEnabled){ + arg = capi.sqlite3_value_int64(pVal); + if(util.bigIntFitsDouble(arg)) arg = Number(arg); + } + else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/; + break; + case capi.SQLITE_FLOAT: + arg = capi.sqlite3_value_double(pVal); + break; + case capi.SQLITE_TEXT: + arg = capi.sqlite3_value_text(pVal); + break; + case capi.SQLITE_BLOB:{ + const n = capi.sqlite3_value_bytes(pVal); + const pBlob = capi.sqlite3_value_blob(pVal); + if(n && !pBlob) sqlite3.WasmAllocError.toss( + "Cannot allocate memory for blob argument of",n,"byte(s)" + ); + arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null; + break; + } + case capi.SQLITE_NULL: + arg = null; break; + default: + if(throwIfCannotConvert){ + toss3(capi.SQLITE_MISMATCH, + "Unhandled sqlite3_value_type():",valType); + } + arg = undefined; + } + return arg; + }; + + /** + Requires a C-style array of `sqlite3_value*` objects and the + number of entries in that array. Returns a JS array containing + the results of passing each C array entry to + sqlite3_value_to_js(). The 3rd argument to this function is + passed on as the 2nd argument to that one. + */ + capi.sqlite3_values_to_js = function(argc,pArgv,throwIfCannotConvert=true){ + let i; + const tgt = []; + for(i = 0; i < argc; ++i){ + /** + Curiously: despite ostensibly requiring 8-byte + alignment, the pArgv array is parcelled into chunks of + 4 bytes (1 pointer each). The values those point to + have 8-byte alignment but the individual argv entries + do not. + */ + tgt.push(capi.sqlite3_value_to_js( + wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)) + )); + } + return tgt; + }; + + /** + Calls either sqlite3_result_error_nomem(), if e is-a + WasmAllocError, or sqlite3_result_error(). In the latter case, + the second arugment is coerced to a string to create the error + message. + + The first argument is a (sqlite3_context*). Returns void. + Does not throw. + */ + capi.sqlite3_result_error_js = function(pCtx,e){ + if(e instanceof WasmAllocError){ + capi.sqlite3_result_error_nomem(pCtx); + }else{ + /* Maintenance reminder: ''+e, rather than e.message, + will prefix e.message with e.name, so it includes + the exception's type name in the result. */; + capi.sqlite3_result_error(pCtx, ''+e, -1); + } + }; + + /** + This function passes its 2nd argument to one of the + sqlite3_result_xyz() routines, depending on the type of that + argument: + + - If (val instanceof Error), this function passes it to + sqlite3_result_error_js(). + - `null`: `sqlite3_result_null()` + - `boolean`: `sqlite3_result_int()` with a value of 0 or 1. + - `number`: `sqlite3_result_int()`, `sqlite3_result_int64()`, or + `sqlite3_result_double()`, depending on the range of the number + and whether or not int64 support is enabled. + - `bigint`: similar to `number` but will trigger an error if the + value is too big to store in an int64. + - `string`: `sqlite3_result_text()` + - Uint8Array or Int8Array or ArrayBuffer: `sqlite3_result_blob()` + - `undefined`: is a no-op provided to simplify certain use cases. + + Anything else triggers `sqlite3_result_error()` with a + description of the problem. + + The first argument to this function is a `(sqlite3_context*)`. + Returns void. Does not throw. + */ + capi.sqlite3_result_js = function(pCtx,val){ + if(val instanceof Error){ + capi.sqlite3_result_error_js(pCtx, val); + return; + } + try{ + switch(typeof val) { + case 'undefined': + /* This is a no-op. This routine originated in the create_function() + family of APIs and in that context, passing in undefined indicated + that the caller was responsible for calling sqlite3_result_xxx() + (if needed). */ + break; + case 'boolean': + capi.sqlite3_result_int(pCtx, val ? 1 : 0); + break; + case 'bigint': + if(util.bigIntFits32(val)){ + capi.sqlite3_result_int(pCtx, Number(val)); + }else if(util.bigIntFitsDouble(val)){ + capi.sqlite3_result_double(pCtx, Number(val)); + }else if(wasm.bigIntEnabled){ + if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val); + else toss3("BigInt value",val.toString(),"is too BigInt for int64."); + }else{ + toss3("BigInt value",val.toString(),"is too BigInt."); + } + break; + case 'number': { + let f; + if(util.isInt32(val)){ + f = capi.sqlite3_result_int; + }else if(wasm.bigIntEnabled + && Number.isInteger(val) + && util.bigIntFits64(BigInt(val))){ + f = capi.sqlite3_result_int64; + }else{ + f = capi.sqlite3_result_double; + } + f(pCtx, val); + break; + } + case 'string': { + const [p, n] = wasm.allocCString(val,true); + capi.sqlite3_result_text(pCtx, p, n, capi.SQLITE_WASM_DEALLOC); + break; + } + case 'object': + if(null===val/*yes, typeof null === 'object'*/) { + capi.sqlite3_result_null(pCtx); + break; + }else if(util.isBindableTypedArray(val)){ + const pBlob = wasm.allocFromTypedArray(val); + capi.sqlite3_result_blob( + pCtx, pBlob, val.byteLength, + capi.SQLITE_WASM_DEALLOC + ); + break; + } + // else fall through + default: + toss3("Don't not how to handle this UDF result value:",(typeof val), val); + } + }catch(e){ + capi.sqlite3_result_error_js(pCtx, e); + } + }; + + /** + Returns the result sqlite3_column_value(pStmt,iCol) passed to + sqlite3_value_to_js(). The 3rd argument of this function is + ignored by this function except to pass it on as the second + argument of sqlite3_value_to_js(). If the sqlite3_column_value() + returns NULL (e.g. because the column index is out of range), + this function returns `undefined`, regardless of the 3rd + argument. If the 3rd argument is falsy and conversion fails, + `undefined` will be returned. + + Note that sqlite3_column_value() returns an "unprotected" value + object, but in a single-threaded environment (like this one) + there is no distinction between protected and unprotected values. + */ + capi.sqlite3_column_js = function(pStmt, iCol, throwIfCannotConvert=true){ + const v = capi.sqlite3_column_value(pStmt, iCol); + return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert); + }; + + /** + Internal impl of sqlite3_preupdate_new/old_js() and + sqlite3changeset_new/old_js(). + */ + const __newOldValue = function(pObj, iCol, impl){ + impl = capi[impl]; + if(!this.ptr) this.ptr = wasm.allocPtr(); + else wasm.pokePtr(this.ptr, 0); + const rc = impl(pObj, iCol, this.ptr); + if(rc) return SQLite3Error.toss(rc,arguments[2]+"() failed with code "+rc); + const pv = wasm.peekPtr(this.ptr); + return pv ? capi.sqlite3_value_to_js( pv, true ) : undefined; + }.bind(Object.create(null)); + + /** + A wrapper around sqlite3_preupdate_new() which fetches the + sqlite3_value at the given index and returns the result of + passing it to sqlite3_value_to_js(). Throws on error. + */ + capi.sqlite3_preupdate_new_js = + (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_new'); + + /** + The sqlite3_preupdate_old() counterpart of + sqlite3_preupdate_new_js(), with an identical interface. + */ + capi.sqlite3_preupdate_old_js = + (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_old'); + + /** + A wrapper around sqlite3changeset_new() which fetches the + sqlite3_value at the given index and returns the result of + passing it to sqlite3_value_to_js(). Throws on error. + + If sqlite3changeset_new() succeeds but has no value to report, + this function returns the undefined value, noting that undefined + is a valid conversion from an `sqlite3_value`, so is unambiguous. + */ + capi.sqlite3changeset_new_js = + (pChangesetIter, iCol) => __newOldValue(pChangesetIter, iCol, + 'sqlite3changeset_new'); + + /** + The sqlite3changeset_old() counterpart of + sqlite3changeset_new_js(), with an identical interface. + */ + capi.sqlite3changeset_old_js = + (pChangesetIter, iCol)=>__newOldValue(pChangesetIter, iCol, + 'sqlite3changeset_old'); /* The remainder of the API will be set up in later steps. */ const sqlite3 = { @@ -1467,6 +1861,15 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( build-generated object. */ version: Object.create(null), + + /** + The library reserves the 'client' property for client-side use + and promises to never define a property with this name nor to + ever rely on specific contents of it. It makes no such guarantees + for other properties. + */ + client: undefined, + /** Performs any optional asynchronous library-level initialization which might be required. This function returns a Promise which @@ -1496,9 +1899,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( let lip = sqlite3ApiBootstrap.initializersAsync; delete sqlite3ApiBootstrap.initializersAsync; if(!lip || !lip.length) return Promise.resolve(sqlite3); - // Is it okay to resolve these in parallel or do we need them - // to resolve in order? We currently only have 1, so it - // makes no difference. lip = lip.map((f)=>{ const p = (f instanceof Promise) ? f : f(sqlite3); return p.catch((e)=>{ @@ -1506,10 +1906,32 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( throw e; }); }); - //let p = lip.shift(); - //while(lip.length) p = p.then(lip.shift()); - //return p.then(()=>sqlite3); - return Promise.all(lip).then(()=>sqlite3); + const postInit = ()=>{ + if(!sqlite3.__isUnderTest){ + /* Delete references to internal-only APIs which are used by + some initializers. Retain them when running in test mode + so that we can add tests for them. */ + delete sqlite3.util; + /* It's conceivable that we might want to expose + StructBinder to client-side code, but it's only useful if + clients build their own sqlite3.wasm which contains their + one C struct types. */ + delete sqlite3.StructBinder; + } + return sqlite3; + }; + if(1){ + /* Run all initializers in sequence. The advantage is that it + allows us to have post-init cleanup defined outside of this + routine at the end of the list and have it run at a + well-defined time. */ + let p = lip.shift(); + while(lip.length) p = p.then(lip.shift()); + return p.then(postInit); + }else{ + /* Run them in an arbitrary order. */ + return Promise.all(lip).then(postInit); + } }, /** scriptInfo ideally gets injected into this object by the diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index 62e2bb9bd..f82be6cd0 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -1,4 +1,4 @@ -/* +/** 2022-07-22 The author disclaims copyright to this source code. In place of a @@ -10,12 +10,12 @@ *********************************************************************** - This file implements the initializer for the sqlite3 "Worker API - #1", a very basic DB access API intended to be scripted from a main - window thread via Worker-style messages. Because of limitations in - that type of communication, this API is minimalistic and only - capable of serving relatively basic DB requests (e.g. it cannot - process nested query loops concurrently). + This file implements the initializer for SQLite's "Worker API #1", a + very basic DB access API intended to be scripted from a main window + thread via Worker-style messages. Because of limitations in that + type of communication, this API is minimalistic and only capable of + serving relatively basic DB requests (e.g. it cannot process nested + query loops concurrently). This file requires that the core C-style sqlite3 API and OO API #1 have been loaded. @@ -158,15 +158,6 @@ bigIntEnabled: bool. True if BigInt support is enabled. - wasmfsOpfsDir: path prefix, if any, _intended_ for use with - WASMFS OPFS persistent storage. - - wasmfsOpfsEnabled: true if persistent storage is enabled in the - current environment. Only files stored under wasmfsOpfsDir - will persist using that mechanism, however. It is legal to use - the non-WASMFS OPFS VFS to open a database via a URI-style - db filename. - vfsList: result of sqlite3.capi.sqlite3_js_vfs_list() } } @@ -449,7 +440,6 @@ sqlite3.initWorker1API = function(){ toss("Throwing because of simulateError flag."); } const rc = Object.create(null); - const pDir = sqlite3.capi.sqlite3_wasmfs_opfs_dir(); let byteArray, pVfs; oargs.vfs = args.vfs; if(isSpecialDbFilename(args.filename)){ @@ -475,15 +465,14 @@ sqlite3.initWorker1API = function(){ e.name+' creating '+args.filename+": "+e.message, { cause: e } - ); + ); }finally{ if(pMem) sqlite3.wasm.dealloc(pMem); } } const db = wState.open(oargs); rc.filename = db.filename; - rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/')) - || !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); + rc.persistent = !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); rc.dbId = getDbId(db); rc.vfs = db.dbVfsName(); return rc; @@ -558,11 +547,10 @@ sqlite3.initWorker1API = function(){ 'config-get': function(){ const rc = Object.create(null), src = sqlite3.config; [ - 'wasmfsOpfsDir', 'bigIntEnabled' + 'bigIntEnabled' ].forEach(function(k){ if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k]; }); - rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir(); rc.version = sqlite3.version; rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list(); rc.opfsEnabled = !!sqlite3.opfs; @@ -624,8 +612,8 @@ sqlite3.initWorker1API = function(){ result.stack = ('string'===typeof err.stack) ? err.stack.split(/\n\s*/) : err.stack; } - if(0) console.warn("Worker is propagating an exception to main thread.", - "Reporting it _here_ for the stack trace:",err,result); + if(0) sqlite3.config.warn("Worker is propagating an exception to main thread.", + "Reporting it _here_ for the stack trace:",err,result); } if(!dbId){ dbId = result.dbId/*from 'open' cmd*/ diff --git a/ext/wasm/api/sqlite3-license-version-header.js b/ext/wasm/api/sqlite3-license-version-header.js index f8b3eddc5..482989463 100644 --- a/ext/wasm/api/sqlite3-license-version-header.js +++ b/ext/wasm/api/sqlite3-license-version-header.js @@ -1,7 +1,7 @@ /* ** LICENSE for the sqlite3 WebAssembly/JavaScript APIs. ** -** This bundle (typically released as sqlite3.js or sqlite3-wasmfs.js) +** This bundle (typically released as sqlite3.js or sqlite3.mjs) ** is an amalgamation of JavaScript source code from two projects: ** ** 1) https://emscripten.org: the Emscripten "glue code" is covered by diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js index e4657484e..1456ae08d 100644 --- a/ext/wasm/api/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -13,7 +13,7 @@ A Worker which manages asynchronous OPFS handles on behalf of a synchronous API which controls it via a combination of Worker messages, SharedArrayBuffer, and Atomics. It is the asynchronous - counterpart of the API defined in sqlite3-api-opfs.js. + counterpart of the API defined in sqlite3-vfs-opfs.js. Highly indebted to: @@ -29,802 +29,869 @@ This file represents an implementation detail of a larger piece of code, and not a public interface. Its details may change at any time and are not intended to be used by any client-level code. + + 2022-11-27: Chrome v108 changes some async methods to synchronous, as + documented at: + + https://developer.chrome.com/blog/sync-methods-for-accesshandles/ + + We cannot change to the sync forms at this point without breaking + clients who use Chrome v104-ish or higher. truncate(), getSize(), + flush(), and close() are now (as of v108) synchronous. Calling them + with an "await", as we have to for the async forms, is still legal + with the sync forms but is superfluous. Calling the async forms with + theFunc().then(...) is not compatible with the change to + synchronous, but we do do not use those APIs that way. i.e. we don't + _need_ to change anything for this, but at some point (after Chrome + versions (approximately) 104-107 are extinct) should change our + usage of those methods to remove the "await". */ "use strict"; -const toss = function(...args){throw new Error(args.join(' '))}; -if(self.window === self){ - toss("This code cannot run from the main thread.", - "Load it as a Worker from a separate Worker."); -}else if(!navigator.storage.getDirectory){ - toss("This API requires navigator.storage.getDirectory."); -} +const wPost = (type,...args)=>postMessage({type, payload:args}); +const installAsyncProxy = function(self){ + const toss = function(...args){throw new Error(args.join(' '))}; + if(self.window === self){ + toss("This code cannot run from the main thread.", + "Load it as a Worker from a separate Worker."); + }else if(!navigator.storage.getDirectory){ + toss("This API requires navigator.storage.getDirectory."); + } -/** - Will hold state copied to this object from the syncronous side of - this API. -*/ -const state = Object.create(null); + /** + Will hold state copied to this object from the syncronous side of + this API. + */ + const state = Object.create(null); -/** - verbose: + /** + verbose: - 0 = no logging output - 1 = only errors - 2 = warnings and errors - 3 = debug, warnings, and errors -*/ -state.verbose = 2; - -const loggers = { - 0:console.error.bind(console), - 1:console.warn.bind(console), - 2:console.log.bind(console) -}; -const logImpl = (level,...args)=>{ - if(state.verbose>level) loggers[level]("OPFS asyncer:",...args); -}; -const log = (...args)=>logImpl(2, ...args); -const warn = (...args)=>logImpl(1, ...args); -const error = (...args)=>logImpl(0, ...args); -const metrics = Object.create(null); -metrics.reset = ()=>{ - let k; - const r = (m)=>(m.count = m.time = m.wait = 0); - for(k in state.opIds){ - r(metrics[k] = Object.create(null)); - } - let s = metrics.s11n = Object.create(null); - s = s.serialize = Object.create(null); - s.count = s.time = 0; - s = metrics.s11n.deserialize = Object.create(null); - s.count = s.time = 0; -}; -metrics.dump = ()=>{ - let k, n = 0, t = 0, w = 0; - for(k in state.opIds){ - const m = metrics[k]; - n += m.count; - t += m.time; - w += m.wait; - m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; - } - console.log(self.location.href, - "metrics for",self.location.href,":\n", - metrics, - "\nTotal of",n,"op(s) for",t,"ms", - "approx",w,"ms spent waiting on OPFS APIs."); - console.log("Serialization metrics:",metrics.s11n); -}; - -/** - __openFiles is a map of sqlite3_file pointers (integers) to - metadata related to a given OPFS file handles. The pointers are, in - this side of the interface, opaque file handle IDs provided by the - synchronous part of this constellation. Each value is an object - with a structure demonstrated in the xOpen() impl. -*/ -const __openFiles = Object.create(null); -/** - __autoLocks is a Set of sqlite3_file pointers (integers) which were - "auto-locked". i.e. those for which we obtained a sync access - handle without an explicit xLock() call. Such locks will be - released during db connection idle time, whereas a sync access - handle obtained via xLock(), or subsequently xLock()'d after - auto-acquisition, will not be released until xUnlock() is called. - - Maintenance reminder: if we relinquish auto-locks at the end of the - operation which acquires them, we pay a massive performance - penalty: speedtest1 benchmarks take up to 4x as long. By delaying - the lock release until idle time, the hit is negligible. -*/ -const __autoLocks = new Set(); + 0 = no logging output + 1 = only errors + 2 = warnings and errors + 3 = debug, warnings, and errors + */ + state.verbose = 1; -/** - Expects an OPFS file path. It gets resolved, such that ".." - components are properly expanded, and returned. If the 2nd arg is - true, the result is returned as an array of path elements, else an - absolute path string is returned. -*/ -const getResolvedPath = function(filename,splitIt){ - const p = new URL( - filename, 'file://irrelevant' - ).pathname; - return splitIt ? p.split('/').filter((v)=>!!v) : p; -}; - -/** - Takes the absolute path to a filesystem element. Returns an array - of [handleOfContainingDir, filename]. If the 2nd argument is truthy - then each directory element leading to the file is created along - the way. Throws if any creation or resolution fails. -*/ -const getDirForFilename = async function f(absFilename, createDirs = false){ - const path = getResolvedPath(absFilename, true); - const filename = path.pop(); - let dh = state.rootDir; - for(const dirName of path){ - if(dirName){ - dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); + const loggers = { + 0:console.error.bind(console), + 1:console.warn.bind(console), + 2:console.log.bind(console) + }; + const logImpl = (level,...args)=>{ + if(state.verbose>level) loggers[level]("OPFS asyncer:",...args); + }; + const log = (...args)=>logImpl(2, ...args); + const warn = (...args)=>logImpl(1, ...args); + const error = (...args)=>logImpl(0, ...args); + const metrics = Object.create(null); + metrics.reset = ()=>{ + let k; + const r = (m)=>(m.count = m.time = m.wait = 0); + for(k in state.opIds){ + r(metrics[k] = Object.create(null)); } - } - return [dh, filename]; -}; - -/** - An error class specifically for use with getSyncHandle(), the goal - of which is to eventually be able to distinguish unambiguously - between locking-related failures and other types, noting that we - cannot currently do so because createSyncAccessHandle() does not - define its exceptions in the required level of detail. -*/ -class GetSyncHandleError extends Error { - constructor(errorObject, ...msg){ - super(); - this.error = errorObject; - this.message = [ - ...msg, ': Original exception ['+errorObject.name+']:', - errorObject.message - ].join(' '); - this.name = 'GetSyncHandleError'; - } -}; - -/** - Returns the sync access handle associated with the given file - handle object (which must be a valid handle object, as created by - xOpen()), lazily opening it if needed. - - In order to help alleviate cross-tab contention for a dabase, - if an exception is thrown while acquiring the handle, this routine - will wait briefly and try again, up to 3 times. If acquisition - still fails at that point it will give up and propagate the - exception. -*/ -const getSyncHandle = async (fh)=>{ - if(!fh.syncHandle){ - const t = performance.now(); - log("Acquiring sync handle for",fh.filenameAbs); - const maxTries = 4, msBase = 300; - let i = 1, ms = msBase; - for(; true; ms = msBase * ++i){ - try { - //if(i<3) toss("Just testing getSyncHandle() wait-and-retry."); - //TODO? A config option which tells it to throw here - //randomly every now and then, for testing purposes. - fh.syncHandle = await fh.fileHandle.createSyncAccessHandle(); - break; - }catch(e){ - if(i === maxTries){ - throw new GetSyncHandleError( - e, "Error getting sync handle.",maxTries, - "attempts failed.",fh.filenameAbs - ); - } - warn("Error getting sync handle. Waiting",ms, - "ms and trying again.",fh.filenameAbs,e); - Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms); + let s = metrics.s11n = Object.create(null); + s = s.serialize = Object.create(null); + s.count = s.time = 0; + s = metrics.s11n.deserialize = Object.create(null); + s.count = s.time = 0; + }; + metrics.dump = ()=>{ + let k, n = 0, t = 0, w = 0; + for(k in state.opIds){ + const m = metrics[k]; + n += m.count; + t += m.time; + w += m.wait; + m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; + } + console.log(self.location.href, + "metrics for",self.location.href,":\n", + metrics, + "\nTotal of",n,"op(s) for",t,"ms", + "approx",w,"ms spent waiting on OPFS APIs."); + console.log("Serialization metrics:",metrics.s11n); + }; + + /** + __openFiles is a map of sqlite3_file pointers (integers) to + metadata related to a given OPFS file handles. The pointers are, in + this side of the interface, opaque file handle IDs provided by the + synchronous part of this constellation. Each value is an object + with a structure demonstrated in the xOpen() impl. + */ + const __openFiles = Object.create(null); + /** + __implicitLocks is a Set of sqlite3_file pointers (integers) which were + "auto-locked". i.e. those for which we obtained a sync access + handle without an explicit xLock() call. Such locks will be + released during db connection idle time, whereas a sync access + handle obtained via xLock(), or subsequently xLock()'d after + auto-acquisition, will not be released until xUnlock() is called. + + Maintenance reminder: if we relinquish auto-locks at the end of the + operation which acquires them, we pay a massive performance + penalty: speedtest1 benchmarks take up to 4x as long. By delaying + the lock release until idle time, the hit is negligible. + */ + const __implicitLocks = new Set(); + + /** + Expects an OPFS file path. It gets resolved, such that ".." + components are properly expanded, and returned. If the 2nd arg is + true, the result is returned as an array of path elements, else an + absolute path string is returned. + */ + const getResolvedPath = function(filename,splitIt){ + const p = new URL( + filename, 'file://irrelevant' + ).pathname; + return splitIt ? p.split('/').filter((v)=>!!v) : p; + }; + + /** + Takes the absolute path to a filesystem element. Returns an array + of [handleOfContainingDir, filename]. If the 2nd argument is truthy + then each directory element leading to the file is created along + the way. Throws if any creation or resolution fails. + */ + const getDirForFilename = async function f(absFilename, createDirs = false){ + const path = getResolvedPath(absFilename, true); + const filename = path.pop(); + let dh = state.rootDir; + for(const dirName of path){ + if(dirName){ + dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); } } - log("Got sync handle for",fh.filenameAbs,'in',performance.now() - t,'ms'); - if(!fh.xLock){ - __autoLocks.add(fh.fid); - log("Auto-locked",fh.fid,fh.filenameAbs); + return [dh, filename]; + }; + + /** + If the given file-holding object has a sync handle attached to it, + that handle is remove and asynchronously closed. Though it may + sound sensible to continue work as soon as the close() returns + (noting that it's asynchronous), doing so can cause operations + performed soon afterwards, e.g. a call to getSyncHandle() to fail + because they may happen out of order from the close(). OPFS does + not guaranty that the actual order of operations is retained in + such cases. i.e. always "await" on the result of this function. + */ + const closeSyncHandle = async (fh)=>{ + if(fh.syncHandle){ + log("Closing sync handle for",fh.filenameAbs); + const h = fh.syncHandle; + delete fh.syncHandle; + delete fh.xLock; + __implicitLocks.delete(fh.fid); + return h.close(); } - } - return fh.syncHandle; -}; - -/** - If the given file-holding object has a sync handle attached to it, - that handle is remove and asynchronously closed. Though it may - sound sensible to continue work as soon as the close() returns - (noting that it's asynchronous), doing so can cause operations - performed soon afterwards, e.g. a call to getSyncHandle() to fail - because they may happen out of order from the close(). OPFS does - not guaranty that the actual order of operations is retained in - such cases. i.e. always "await" on the result of this function. -*/ -const closeSyncHandle = async (fh)=>{ - if(fh.syncHandle){ - log("Closing sync handle for",fh.filenameAbs); - const h = fh.syncHandle; - delete fh.syncHandle; - delete fh.xLock; - __autoLocks.delete(fh.fid); - return h.close(); - } -}; - -/** - A proxy for closeSyncHandle() which is guaranteed to not throw. - - This function is part of a lock/unlock step in functions which - require a sync access handle but may be called without xLock() - having been called first. Such calls need to release that - handle to avoid locking the file for all of time. This is an - _attempt_ at reducing cross-tab contention but it may prove - to be more of a problem than a solution and may need to be - removed. -*/ -const closeSyncHandleNoThrow = async (fh)=>{ - try{await closeSyncHandle(fh)} - catch(e){ - warn("closeSyncHandleNoThrow() ignoring:",e,fh); - } -}; + }; -/** - Stores the given value at state.sabOPView[state.opIds.rc] and then - Atomics.notify()'s it. -*/ -const storeAndNotify = (opName, value)=>{ - log(opName+"() => notify(",value,")"); - Atomics.store(state.sabOPView, state.opIds.rc, value); - Atomics.notify(state.sabOPView, state.opIds.rc); -}; - -/** - Throws if fh is a file-holding object which is flagged as read-only. -*/ -const affirmNotRO = function(opName,fh){ - if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); -}; -const affirmLocked = function(opName,fh){ - //if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs); /** - Currently a no-op, as speedtest1 triggers xRead() without a - lock (that seems like a bug but it's currently uninvestigated). - This means, however, that some OPFS VFS routines may trigger - acquisition of a lock but never let it go until xUnlock() is - called (which it likely won't be if xLock() was not called). + A proxy for closeSyncHandle() which is guaranteed to not throw. + + This function is part of a lock/unlock step in functions which + require a sync access handle but may be called without xLock() + having been called first. Such calls need to release that + handle to avoid locking the file for all of time. This is an + _attempt_ at reducing cross-tab contention but it may prove + to be more of a problem than a solution and may need to be + removed. */ -}; - -/** - We track 2 different timers: the "metrics" timer records how much - time we spend performing work. The "wait" timer records how much - time we spend waiting on the underlying OPFS timer. See the calls - to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd() - throughout this file to see how they're used. -*/ -const __mTimer = Object.create(null); -__mTimer.op = undefined; -__mTimer.start = undefined; -const mTimeStart = (op)=>{ - __mTimer.start = performance.now(); - __mTimer.op = op; - //metrics[op] || toss("Maintenance required: missing metrics for",op); - ++metrics[op].count; -}; -const mTimeEnd = ()=>( - metrics[__mTimer.op].time += performance.now() - __mTimer.start -); -const __wTimer = Object.create(null); -__wTimer.op = undefined; -__wTimer.start = undefined; -const wTimeStart = (op)=>{ - __wTimer.start = performance.now(); - __wTimer.op = op; - //metrics[op] || toss("Maintenance required: missing metrics for",op); -}; -const wTimeEnd = ()=>( - metrics[__wTimer.op].wait += performance.now() - __wTimer.start -); - -/** - Gets set to true by the 'opfs-async-shutdown' command to quit the - wait loop. This is only intended for debugging purposes: we cannot - inspect this file's state while the tight waitLoop() is running and - need a way to stop that loop for introspection purposes. -*/ -let flagAsyncShutdown = false; + const closeSyncHandleNoThrow = async (fh)=>{ + try{await closeSyncHandle(fh)} + catch(e){ + warn("closeSyncHandleNoThrow() ignoring:",e,fh); + } + }; + /* Release all auto-locks. */ + const releaseImplicitLocks = async ()=>{ + if(__implicitLocks.size){ + /* Release all auto-locks. */ + for(const fid of __implicitLocks){ + const fh = __openFiles[fid]; + await closeSyncHandleNoThrow(fh); + log("Auto-unlocked",fid,fh.filenameAbs); + } + } + }; -/** - Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods - methods, as well as helpers like mkdir(). Maintenance reminder: - members are in alphabetical order to simplify finding them. -*/ -const vfsAsyncImpls = { - 'opfs-async-metrics': async ()=>{ - mTimeStart('opfs-async-metrics'); - metrics.dump(); - storeAndNotify('opfs-async-metrics', 0); - mTimeEnd(); - }, - 'opfs-async-shutdown': async ()=>{ - flagAsyncShutdown = true; - storeAndNotify('opfs-async-shutdown', 0); - }, - mkdir: async (dirname)=>{ - mTimeStart('mkdir'); - let rc = 0; - wTimeStart('mkdir'); - try { - await getDirForFilename(dirname+"/filepart", true); - }catch(e){ - state.s11n.storeException(2,e); - rc = state.sq3Codes.SQLITE_IOERR; - }finally{ - wTimeEnd(); + /** + An experiment in improving concurrency by freeing up implicit locks + sooner. This is known to impact performance dramatically but it has + also shown to improve concurrency considerably. + + If fh.releaseImplicitLocks is truthy and fh is in __implicitLocks, + this routine returns closeSyncHandleNoThrow(), else it is a no-op. + */ + const releaseImplicitLock = async (fh)=>{ + if(fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)){ + return closeSyncHandleNoThrow(fh); } - storeAndNotify('mkdir', rc); - mTimeEnd(); - }, - xAccess: async (filename)=>{ - mTimeStart('xAccess'); - /* OPFS cannot support the full range of xAccess() queries sqlite3 - calls for. We can essentially just tell if the file is - accessible, but if it is it's automatically writable (unless - it's locked, which we cannot(?) know without trying to open - it). OPFS does not have the notion of read-only. - - The return semantics of this function differ from sqlite3's - xAccess semantics because we are limited in what we can - communicate back to our synchronous communication partner: 0 = - accessible, non-0 means not accessible. - */ - let rc = 0; - wTimeStart('xAccess'); - try{ - const [dh, fn] = await getDirForFilename(filename); - await dh.getFileHandle(fn); - }catch(e){ - state.s11n.storeException(2,e); - rc = state.sq3Codes.SQLITE_IOERR; - }finally{ - wTimeEnd(); + }; + + /** + An error class specifically for use with getSyncHandle(), the goal + of which is to eventually be able to distinguish unambiguously + between locking-related failures and other types, noting that we + cannot currently do so because createSyncAccessHandle() does not + define its exceptions in the required level of detail. + + 2022-11-29: according to: + + https://github.com/whatwg/fs/pull/21 + + NoModificationAllowedError will be the standard exception thrown + when acquisition of a sync access handle fails due to a locking + error. As of this writing, that error type is not visible in the + dev console in Chrome v109, nor is it documented in MDN, but an + error with that "name" property is being thrown from the OPFS + layer. + */ + class GetSyncHandleError extends Error { + constructor(errorObject, ...msg){ + super([ + ...msg, ': '+errorObject.name+':', + errorObject.message + ].join(' '), { + cause: errorObject + }); + this.name = 'GetSyncHandleError'; } - storeAndNotify('xAccess', rc); - mTimeEnd(); - }, - xClose: async function(fid/*sqlite3_file pointer*/){ - const opName = 'xClose'; - mTimeStart(opName); - __autoLocks.delete(fid); - const fh = __openFiles[fid]; - let rc = 0; - wTimeStart(opName); - if(fh){ - delete __openFiles[fid]; - await closeSyncHandle(fh); - if(fh.deleteOnClose){ - try{ await fh.dirHandle.removeEntry(fh.filenamePart) } - catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) } - } + }; + GetSyncHandleError.convertRc = (e,rc)=>{ + if(1){ + return ( + e instanceof GetSyncHandleError + && ((e.cause.name==='NoModificationAllowedError') + /* Inconsistent exception.name from Chrome/ium with the + same exception.message text: */ + || (e.cause.name==='DOMException' + && 0===e.cause.message.indexOf('Access Handles cannot'))) + ) ? ( + /*console.warn("SQLITE_BUSY",e),*/ + state.sq3Codes.SQLITE_BUSY + ) : rc; }else{ - state.s11n.serialize(); - rc = state.sq3Codes.SQLITE_NOTFOUND; + return rc; } - wTimeEnd(); - storeAndNotify(opName, rc); - mTimeEnd(); - }, - xDelete: async function(...args){ - mTimeStart('xDelete'); - const rc = await vfsAsyncImpls.xDeleteNoWait(...args); - storeAndNotify('xDelete', rc); - mTimeEnd(); - }, - xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ - /* The syncDir flag is, for purposes of the VFS API's semantics, - ignored here. However, if it has the value 0x1234 then: after - deleting the given file, recursively try to delete any empty - directories left behind in its wake (ignoring any errors and - stopping at the first failure). - - That said: we don't know for sure that removeEntry() fails if - the dir is not empty because the API is not documented. It has, - however, a "recursive" flag which defaults to false, so - presumably it will fail if the dir is not empty and that flag - is false. - */ - let rc = 0; - wTimeStart('xDelete'); - try { - while(filename){ - const [hDir, filenamePart] = await getDirForFilename(filename, false); - if(!filenamePart) break; - await hDir.removeEntry(filenamePart, {recursive}); - if(0x1234 !== syncDir) break; - recursive = false; - filename = getResolvedPath(filename, true); - filename.pop(); - filename = filename.join('/'); + } + /** + Returns the sync access handle associated with the given file + handle object (which must be a valid handle object, as created by + xOpen()), lazily opening it if needed. + + In order to help alleviate cross-tab contention for a dabase, if + an exception is thrown while acquiring the handle, this routine + will wait briefly and try again, up to some fixed number of + times. If acquisition still fails at that point it will give up + and propagate the exception. Client-level code will see that as + an I/O error. + */ + const getSyncHandle = async (fh,opName)=>{ + if(!fh.syncHandle){ + const t = performance.now(); + log("Acquiring sync handle for",fh.filenameAbs); + const maxTries = 6, + msBase = state.asyncIdleWaitTime * 2; + let i = 1, ms = msBase; + for(; true; ms = msBase * ++i){ + try { + //if(i<3) toss("Just testing getSyncHandle() wait-and-retry."); + //TODO? A config option which tells it to throw here + //randomly every now and then, for testing purposes. + fh.syncHandle = await fh.fileHandle.createSyncAccessHandle(); + break; + }catch(e){ + if(i === maxTries){ + throw new GetSyncHandleError( + e, "Error getting sync handle for",opName+"().",maxTries, + "attempts failed.",fh.filenameAbs + ); + } + warn("Error getting sync handle for",opName+"(). Waiting",ms, + "ms and trying again.",fh.filenameAbs,e); + Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms); + } } - }catch(e){ - state.s11n.storeException(2,e); - rc = state.sq3Codes.SQLITE_IOERR_DELETE; - } - wTimeEnd(); - return rc; - }, - xFileSize: async function(fid/*sqlite3_file pointer*/){ - mTimeStart('xFileSize'); - const fh = __openFiles[fid]; - let rc; - wTimeStart('xFileSize'); - try{ - affirmLocked('xFileSize',fh); - rc = await (await getSyncHandle(fh)).getSize(); - state.s11n.serialize(Number(rc)); - rc = 0; - }catch(e){ - state.s11n.storeException(2,e); - rc = state.sq3Codes.SQLITE_IOERR; - } - wTimeEnd(); - storeAndNotify('xFileSize', rc); - mTimeEnd(); - }, - xLock: async function(fid/*sqlite3_file pointer*/, - lockType/*SQLITE_LOCK_...*/){ - mTimeStart('xLock'); - const fh = __openFiles[fid]; - let rc = 0; - const oldLockType = fh.xLock; - fh.xLock = lockType; - if( !fh.syncHandle ){ - wTimeStart('xLock'); - try { - await getSyncHandle(fh); - __autoLocks.delete(fid); - }catch(e){ - state.s11n.storeException(1,e); - rc = state.sq3Codes.SQLITE_IOERR_LOCK; - fh.xLock = oldLockType; + log("Got",opName+"() sync handle for",fh.filenameAbs, + 'in',performance.now() - t,'ms'); + if(!fh.xLock){ + __implicitLocks.add(fh.fid); + log("Acquired implicit lock for",opName+"()",fh.fid,fh.filenameAbs); } - wTimeEnd(); } - storeAndNotify('xLock',rc); - mTimeEnd(); - }, - xOpen: async function(fid/*sqlite3_file pointer*/, filename, - flags/*SQLITE_OPEN_...*/){ - const opName = 'xOpen'; - mTimeStart(opName); - const deleteOnClose = (state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags); - const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags); - wTimeStart('xOpen'); - try{ - let hDir, filenamePart; + return fh.syncHandle; + }; + + /** + Stores the given value at state.sabOPView[state.opIds.rc] and then + Atomics.notify()'s it. + */ + const storeAndNotify = (opName, value)=>{ + log(opName+"() => notify(",value,")"); + Atomics.store(state.sabOPView, state.opIds.rc, value); + Atomics.notify(state.sabOPView, state.opIds.rc); + }; + + /** + Throws if fh is a file-holding object which is flagged as read-only. + */ + const affirmNotRO = function(opName,fh){ + if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); + }; + + /** + We track 2 different timers: the "metrics" timer records how much + time we spend performing work. The "wait" timer records how much + time we spend waiting on the underlying OPFS timer. See the calls + to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd() + throughout this file to see how they're used. + */ + const __mTimer = Object.create(null); + __mTimer.op = undefined; + __mTimer.start = undefined; + const mTimeStart = (op)=>{ + __mTimer.start = performance.now(); + __mTimer.op = op; + //metrics[op] || toss("Maintenance required: missing metrics for",op); + ++metrics[op].count; + }; + const mTimeEnd = ()=>( + metrics[__mTimer.op].time += performance.now() - __mTimer.start + ); + const __wTimer = Object.create(null); + __wTimer.op = undefined; + __wTimer.start = undefined; + const wTimeStart = (op)=>{ + __wTimer.start = performance.now(); + __wTimer.op = op; + //metrics[op] || toss("Maintenance required: missing metrics for",op); + }; + const wTimeEnd = ()=>( + metrics[__wTimer.op].wait += performance.now() - __wTimer.start + ); + + /** + Gets set to true by the 'opfs-async-shutdown' command to quit the + wait loop. This is only intended for debugging purposes: we cannot + inspect this file's state while the tight waitLoop() is running and + need a way to stop that loop for introspection purposes. + */ + let flagAsyncShutdown = false; + + /** + Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods + methods, as well as helpers like mkdir(). Maintenance reminder: + members are in alphabetical order to simplify finding them. + */ + const vfsAsyncImpls = { + 'opfs-async-metrics': async ()=>{ + mTimeStart('opfs-async-metrics'); + metrics.dump(); + storeAndNotify('opfs-async-metrics', 0); + mTimeEnd(); + }, + 'opfs-async-shutdown': async ()=>{ + flagAsyncShutdown = true; + storeAndNotify('opfs-async-shutdown', 0); + }, + mkdir: async (dirname)=>{ + mTimeStart('mkdir'); + let rc = 0; + wTimeStart('mkdir'); try { - [hDir, filenamePart] = await getDirForFilename(filename, !!create); + await getDirForFilename(dirname+"/filepart", true); }catch(e){ - state.s11n.storeException(1,e); - storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); - mTimeEnd(); + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR; + }finally{ wTimeEnd(); - return; } - const hFile = await hDir.getFileHandle(filenamePart, {create}); - /** - wa-sqlite, at this point, grabs a SyncAccessHandle and - assigns it to the syncHandle prop of the file state - object, but only for certain cases and it's unclear why it - places that limitation on it. + storeAndNotify('mkdir', rc); + mTimeEnd(); + }, + xAccess: async (filename)=>{ + mTimeStart('xAccess'); + /* OPFS cannot support the full range of xAccess() queries + sqlite3 calls for. We can essentially just tell if the file + is accessible, but if it is then it's automatically writable + (unless it's locked, which we cannot(?) know without trying + to open it). OPFS does not have the notion of read-only. + + The return semantics of this function differ from sqlite3's + xAccess semantics because we are limited in what we can + communicate back to our synchronous communication partner: 0 = + accessible, non-0 means not accessible. */ - wTimeEnd(); - __openFiles[fid] = Object.assign(Object.create(null),{ - fid: fid, - filenameAbs: filename, - filenamePart: filenamePart, - dirHandle: hDir, - fileHandle: hFile, - sabView: state.sabFileBufView, - readOnly: create - ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags), - deleteOnClose: deleteOnClose - }); - storeAndNotify(opName, 0); - }catch(e){ - wTimeEnd(); - error(opName,e); - state.s11n.storeException(1,e); - storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); - } - mTimeEnd(); - }, - xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){ - mTimeStart('xRead'); - let rc = 0, nRead; - const fh = __openFiles[fid]; - try{ - affirmLocked('xRead',fh); - wTimeStart('xRead'); - nRead = (await getSyncHandle(fh)).read( - fh.sabView.subarray(0, n), - {at: Number(offset64)} - ); - wTimeEnd(); - if(nRead < n){/* Zero-fill remaining bytes */ - fh.sabView.fill(0, nRead, n); - rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; + let rc = 0; + wTimeStart('xAccess'); + try{ + const [dh, fn] = await getDirForFilename(filename); + await dh.getFileHandle(fn); + }catch(e){ + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR; + }finally{ + wTimeEnd(); } - }catch(e){ - if(undefined===nRead) wTimeEnd(); - error("xRead() failed",e,fh); - state.s11n.storeException(1,e); - rc = state.sq3Codes.SQLITE_IOERR_READ; - } - storeAndNotify('xRead',rc); - mTimeEnd(); - }, - xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){ - mTimeStart('xSync'); - const fh = __openFiles[fid]; - let rc = 0; - if(!fh.readOnly && fh.syncHandle){ + storeAndNotify('xAccess', rc); + mTimeEnd(); + }, + xClose: async function(fid/*sqlite3_file pointer*/){ + const opName = 'xClose'; + mTimeStart(opName); + __implicitLocks.delete(fid); + const fh = __openFiles[fid]; + let rc = 0; + wTimeStart(opName); + if(fh){ + delete __openFiles[fid]; + await closeSyncHandle(fh); + if(fh.deleteOnClose){ + try{ await fh.dirHandle.removeEntry(fh.filenamePart) } + catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) } + } + }else{ + state.s11n.serialize(); + rc = state.sq3Codes.SQLITE_NOTFOUND; + } + wTimeEnd(); + storeAndNotify(opName, rc); + mTimeEnd(); + }, + xDelete: async function(...args){ + mTimeStart('xDelete'); + const rc = await vfsAsyncImpls.xDeleteNoWait(...args); + storeAndNotify('xDelete', rc); + mTimeEnd(); + }, + xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ + /* The syncDir flag is, for purposes of the VFS API's semantics, + ignored here. However, if it has the value 0x1234 then: after + deleting the given file, recursively try to delete any empty + directories left behind in its wake (ignoring any errors and + stopping at the first failure). + + That said: we don't know for sure that removeEntry() fails if + the dir is not empty because the API is not documented. It has, + however, a "recursive" flag which defaults to false, so + presumably it will fail if the dir is not empty and that flag + is false. + */ + let rc = 0; + wTimeStart('xDelete'); try { - wTimeStart('xSync'); - await fh.syncHandle.flush(); + while(filename){ + const [hDir, filenamePart] = await getDirForFilename(filename, false); + if(!filenamePart) break; + await hDir.removeEntry(filenamePart, {recursive}); + if(0x1234 !== syncDir) break; + recursive = false; + filename = getResolvedPath(filename, true); + filename.pop(); + filename = filename.join('/'); + } }catch(e){ state.s11n.storeException(2,e); - rc = state.sq3Codes.SQLITE_IOERR_FSYNC; + rc = state.sq3Codes.SQLITE_IOERR_DELETE; } wTimeEnd(); - } - storeAndNotify('xSync',rc); - mTimeEnd(); - }, - xTruncate: async function(fid/*sqlite3_file pointer*/,size){ - mTimeStart('xTruncate'); - let rc = 0; - const fh = __openFiles[fid]; - wTimeStart('xTruncate'); - try{ - affirmLocked('xTruncate',fh); - affirmNotRO('xTruncate', fh); - await (await getSyncHandle(fh)).truncate(size); - }catch(e){ - error("xTruncate():",e,fh); - state.s11n.storeException(2,e); - rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE; - } - wTimeEnd(); - storeAndNotify('xTruncate',rc); - mTimeEnd(); - }, - xUnlock: async function(fid/*sqlite3_file pointer*/, - lockType/*SQLITE_LOCK_...*/){ - mTimeStart('xUnlock'); - let rc = 0; - const fh = __openFiles[fid]; - if( state.sq3Codes.SQLITE_LOCK_NONE===lockType - && fh.syncHandle ){ - wTimeStart('xUnlock'); - try { await closeSyncHandle(fh) } - catch(e){ + return rc; + }, + xFileSize: async function(fid/*sqlite3_file pointer*/){ + mTimeStart('xFileSize'); + const fh = __openFiles[fid]; + let rc = 0; + wTimeStart('xFileSize'); + try{ + const sz = await (await getSyncHandle(fh,'xFileSize')).getSize(); + state.s11n.serialize(Number(sz)); + }catch(e){ state.s11n.storeException(1,e); - rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; + rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR); } + await releaseImplicitLock(fh); wTimeEnd(); - } - storeAndNotify('xUnlock',rc); - mTimeEnd(); - }, - xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){ - mTimeStart('xWrite'); - let rc; - const fh = __openFiles[fid]; - wTimeStart('xWrite'); - try{ - affirmLocked('xWrite',fh); - affirmNotRO('xWrite', fh); - rc = ( - n === (await getSyncHandle(fh)) - .write(fh.sabView.subarray(0, n), - {at: Number(offset64)}) - ) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE; - }catch(e){ - error("xWrite():",e,fh); - state.s11n.storeException(1,e); - rc = state.sq3Codes.SQLITE_IOERR_WRITE; - } - wTimeEnd(); - storeAndNotify('xWrite',rc); - mTimeEnd(); - } -}/*vfsAsyncImpls*/; - -const initS11n = ()=>{ - /** - ACHTUNG: this code is 100% duplicated in the other half of this - proxy! The documentation is maintained in the "synchronous half". - */ - if(state.s11n) return state.s11n; - const textDecoder = new TextDecoder(), - textEncoder = new TextEncoder('utf-8'), - viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), - viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); - state.s11n = Object.create(null); - const TypeIds = Object.create(null); - TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; - TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; - TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; - TypeIds.string = { id: 4 }; - const getTypeId = (v)=>( - TypeIds[typeof v] - || toss("Maintenance required: this value type cannot be serialized.",v) - ); - const getTypeIdById = (tid)=>{ - switch(tid){ - case TypeIds.number.id: return TypeIds.number; - case TypeIds.bigint.id: return TypeIds.bigint; - case TypeIds.boolean.id: return TypeIds.boolean; - case TypeIds.string.id: return TypeIds.string; - default: toss("Invalid type ID:",tid); - } - }; - state.s11n.deserialize = function(clear=false){ - ++metrics.s11n.deserialize.count; - const t = performance.now(); - const argc = viewU8[0]; - const rc = argc ? [] : null; - if(argc){ - const typeIds = []; - let offset = 1, i, n, v; - for(i = 0; i < argc; ++i, ++offset){ - typeIds.push(getTypeIdById(viewU8[offset])); + storeAndNotify('xFileSize', rc); + mTimeEnd(); + }, + xLock: async function(fid/*sqlite3_file pointer*/, + lockType/*SQLITE_LOCK_...*/){ + mTimeStart('xLock'); + const fh = __openFiles[fid]; + let rc = 0; + const oldLockType = fh.xLock; + fh.xLock = lockType; + if( !fh.syncHandle ){ + wTimeStart('xLock'); + try { + await getSyncHandle(fh,'xLock'); + __implicitLocks.delete(fid); + }catch(e){ + state.s11n.storeException(1,e); + rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK); + fh.xLock = oldLockType; + } + wTimeEnd(); + } + storeAndNotify('xLock',rc); + mTimeEnd(); + }, + xOpen: async function(fid/*sqlite3_file pointer*/, filename, + flags/*SQLITE_OPEN_...*/, + opfsFlags/*OPFS_...*/){ + const opName = 'xOpen'; + mTimeStart(opName); + const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags); + wTimeStart('xOpen'); + try{ + let hDir, filenamePart; + try { + [hDir, filenamePart] = await getDirForFilename(filename, !!create); + }catch(e){ + state.s11n.storeException(1,e); + storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); + mTimeEnd(); + wTimeEnd(); + return; + } + const hFile = await hDir.getFileHandle(filenamePart, {create}); + wTimeEnd(); + const fh = Object.assign(Object.create(null),{ + fid: fid, + filenameAbs: filename, + filenamePart: filenamePart, + dirHandle: hDir, + fileHandle: hFile, + sabView: state.sabFileBufView, + readOnly: create + ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags), + deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags) + }); + fh.releaseImplicitLocks = + (opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP) + || state.opfsFlags.defaultUnlockAsap; + if(0 /* this block is modelled after something wa-sqlite + does but it leads to immediate contention on journal files. + Update: this approach reportedly only works for DELETE journal + mode. */ + && (0===(flags & state.sq3Codes.SQLITE_OPEN_MAIN_DB))){ + /* sqlite does not lock these files, so go ahead and grab an OPFS + lock. */ + fh.xLock = "xOpen"/* Truthy value to keep entry from getting + flagged as auto-locked. String value so + that we can easily distinguish is later + if needed. */; + await getSyncHandle(fh,'xOpen'); + } + __openFiles[fid] = fh; + storeAndNotify(opName, 0); + }catch(e){ + wTimeEnd(); + error(opName,e); + state.s11n.storeException(1,e); + storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); + } + mTimeEnd(); + }, + xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){ + mTimeStart('xRead'); + let rc = 0, nRead; + const fh = __openFiles[fid]; + try{ + wTimeStart('xRead'); + nRead = (await getSyncHandle(fh,'xRead')).read( + fh.sabView.subarray(0, n), + {at: Number(offset64)} + ); + wTimeEnd(); + if(nRead < n){/* Zero-fill remaining bytes */ + fh.sabView.fill(0, nRead, n); + rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; + } + }catch(e){ + if(undefined===nRead) wTimeEnd(); + error("xRead() failed",e,fh); + state.s11n.storeException(1,e); + rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ); } - for(i = 0; i < argc; ++i){ - const t = typeIds[i]; - if(t.getter){ - v = viewDV[t.getter](offset, state.littleEndian); - offset += t.size; - }else{/*String*/ - n = viewDV.getInt32(offset, state.littleEndian); - offset += 4; - v = textDecoder.decode(viewU8.slice(offset, offset+n)); - offset += n; + await releaseImplicitLock(fh); + storeAndNotify('xRead',rc); + mTimeEnd(); + }, + xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){ + mTimeStart('xSync'); + const fh = __openFiles[fid]; + let rc = 0; + if(!fh.readOnly && fh.syncHandle){ + try { + wTimeStart('xSync'); + await fh.syncHandle.flush(); + }catch(e){ + state.s11n.storeException(2,e); + rc = state.sq3Codes.SQLITE_IOERR_FSYNC; } - rc.push(v); + wTimeEnd(); } - } - if(clear) viewU8[0] = 0; - //log("deserialize:",argc, rc); - metrics.s11n.deserialize.time += performance.now() - t; - return rc; - }; - state.s11n.serialize = function(...args){ - const t = performance.now(); - ++metrics.s11n.serialize.count; - if(args.length){ - //log("serialize():",args); - const typeIds = []; - let i = 0, offset = 1; - viewU8[0] = args.length & 0xff /* header = # of args */; - for(; i < args.length; ++i, ++offset){ - /* Write the TypeIds.id value into the next args.length - bytes. */ - typeIds.push(getTypeId(args[i])); - viewU8[offset] = typeIds[i].id; + storeAndNotify('xSync',rc); + mTimeEnd(); + }, + xTruncate: async function(fid/*sqlite3_file pointer*/,size){ + mTimeStart('xTruncate'); + let rc = 0; + const fh = __openFiles[fid]; + wTimeStart('xTruncate'); + try{ + affirmNotRO('xTruncate', fh); + await (await getSyncHandle(fh,'xTruncate')).truncate(size); + }catch(e){ + error("xTruncate():",e,fh); + state.s11n.storeException(2,e); + rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE); } - for(i = 0; i < args.length; ++i) { - /* Deserialize the following bytes based on their - corresponding TypeIds.id from the header. */ - const t = typeIds[i]; - if(t.setter){ - viewDV[t.setter](offset, args[i], state.littleEndian); - offset += t.size; - }else{/*String*/ - const s = textEncoder.encode(args[i]); - viewDV.setInt32(offset, s.byteLength, state.littleEndian); - offset += 4; - viewU8.set(s, offset); - offset += s.byteLength; + await releaseImplicitLock(fh); + wTimeEnd(); + storeAndNotify('xTruncate',rc); + mTimeEnd(); + }, + xUnlock: async function(fid/*sqlite3_file pointer*/, + lockType/*SQLITE_LOCK_...*/){ + mTimeStart('xUnlock'); + let rc = 0; + const fh = __openFiles[fid]; + if( state.sq3Codes.SQLITE_LOCK_NONE===lockType + && fh.syncHandle ){ + wTimeStart('xUnlock'); + try { await closeSyncHandle(fh) } + catch(e){ + state.s11n.storeException(1,e); + rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; } + wTimeEnd(); } - //log("serialize() result:",viewU8.slice(0,offset)); - }else{ - viewU8[0] = 0; + storeAndNotify('xUnlock',rc); + mTimeEnd(); + }, + xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){ + mTimeStart('xWrite'); + let rc; + const fh = __openFiles[fid]; + wTimeStart('xWrite'); + try{ + affirmNotRO('xWrite', fh); + rc = ( + n === (await getSyncHandle(fh,'xWrite')) + .write(fh.sabView.subarray(0, n), + {at: Number(offset64)}) + ) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE; + }catch(e){ + error("xWrite():",e,fh); + state.s11n.storeException(1,e); + rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE); + } + await releaseImplicitLock(fh); + wTimeEnd(); + storeAndNotify('xWrite',rc); + mTimeEnd(); } - metrics.s11n.serialize.time += performance.now() - t; - }; + }/*vfsAsyncImpls*/; - state.s11n.storeException = state.asyncS11nExceptions - ? ((priority,e)=>{ - if(priority<=state.asyncS11nExceptions){ - state.s11n.serialize([e.name,': ',e.message].join("")); + const initS11n = ()=>{ + /** + ACHTUNG: this code is 100% duplicated in the other half of this + proxy! The documentation is maintained in the "synchronous half". + */ + if(state.s11n) return state.s11n; + const textDecoder = new TextDecoder(), + textEncoder = new TextEncoder('utf-8'), + viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), + viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); + state.s11n = Object.create(null); + const TypeIds = Object.create(null); + TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; + TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; + TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; + TypeIds.string = { id: 4 }; + const getTypeId = (v)=>( + TypeIds[typeof v] + || toss("Maintenance required: this value type cannot be serialized.",v) + ); + const getTypeIdById = (tid)=>{ + switch(tid){ + case TypeIds.number.id: return TypeIds.number; + case TypeIds.bigint.id: return TypeIds.bigint; + case TypeIds.boolean.id: return TypeIds.boolean; + case TypeIds.string.id: return TypeIds.string; + default: toss("Invalid type ID:",tid); } - }) - : ()=>{}; - - return state.s11n; -}/*initS11n()*/; - -const waitLoop = async function f(){ - const opHandlers = Object.create(null); - for(let k of Object.keys(state.opIds)){ - const vi = vfsAsyncImpls[k]; - if(!vi) continue; - const o = Object.create(null); - opHandlers[state.opIds[k]] = o; - o.key = k; - o.f = vi; - } - /** - waitTime is how long (ms) to wait for each Atomics.wait(). - We need to wake up periodically to give the thread a chance - to do other things. - */ - const waitTime = 500; - while(!flagAsyncShutdown){ - try { - if('timed-out'===Atomics.wait( - state.sabOPView, state.opIds.whichOp, 0, waitTime - )){ - if(__autoLocks.size){ - /* Release all auto-locks. */ - for(const fid of __autoLocks){ - const fh = __openFiles[fid]; - await closeSyncHandleNoThrow(fh); - log("Auto-unlocked",fid,fh.filenameAbs); + }; + state.s11n.deserialize = function(clear=false){ + ++metrics.s11n.deserialize.count; + const t = performance.now(); + const argc = viewU8[0]; + const rc = argc ? [] : null; + if(argc){ + const typeIds = []; + let offset = 1, i, n, v; + for(i = 0; i < argc; ++i, ++offset){ + typeIds.push(getTypeIdById(viewU8[offset])); + } + for(i = 0; i < argc; ++i){ + const t = typeIds[i]; + if(t.getter){ + v = viewDV[t.getter](offset, state.littleEndian); + offset += t.size; + }else{/*String*/ + n = viewDV.getInt32(offset, state.littleEndian); + offset += 4; + v = textDecoder.decode(viewU8.slice(offset, offset+n)); + offset += n; } + rc.push(v); } - continue; } - const opId = Atomics.load(state.sabOPView, state.opIds.whichOp); - Atomics.store(state.sabOPView, state.opIds.whichOp, 0); - const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId); - const args = state.s11n.deserialize( - true /* clear s11n to keep the caller from confusing this with - an exception string written by the upcoming - operation */ - ) || []; - //warn("waitLoop() whichOp =",opId, hnd, args); - if(hnd.f) await hnd.f(...args); - else error("Missing callback for opId",opId); - }catch(e){ - error('in waitLoop():',e); - } - } -}; - -navigator.storage.getDirectory().then(function(d){ - const wMsg = (type)=>postMessage({type}); - state.rootDir = d; - self.onmessage = function({data}){ - switch(data.type){ - case 'opfs-async-init':{ - /* Receive shared state from synchronous partner */ - const opt = data.args; - state.littleEndian = opt.littleEndian; - state.asyncS11nExceptions = opt.asyncS11nExceptions; - state.verbose = opt.verbose ?? 2; - state.fileBufferSize = opt.fileBufferSize; - state.sabS11nOffset = opt.sabS11nOffset; - state.sabS11nSize = opt.sabS11nSize; - state.sabOP = opt.sabOP; - state.sabOPView = new Int32Array(state.sabOP); - state.sabIO = opt.sabIO; - state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); - state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); - state.opIds = opt.opIds; - state.sq3Codes = opt.sq3Codes; - Object.keys(vfsAsyncImpls).forEach((k)=>{ - if(!Number.isFinite(state.opIds[k])){ - toss("Maintenance required: missing state.opIds[",k,"]"); - } - }); - initS11n(); - metrics.reset(); - log("init state",state); - wMsg('opfs-async-inited'); - waitLoop(); - break; + if(clear) viewU8[0] = 0; + //log("deserialize:",argc, rc); + metrics.s11n.deserialize.time += performance.now() - t; + return rc; + }; + state.s11n.serialize = function(...args){ + const t = performance.now(); + ++metrics.s11n.serialize.count; + if(args.length){ + //log("serialize():",args); + const typeIds = []; + let i = 0, offset = 1; + viewU8[0] = args.length & 0xff /* header = # of args */; + for(; i < args.length; ++i, ++offset){ + /* Write the TypeIds.id value into the next args.length + bytes. */ + typeIds.push(getTypeId(args[i])); + viewU8[offset] = typeIds[i].id; } - case 'opfs-async-restart': - if(flagAsyncShutdown){ - warn("Restarting after opfs-async-shutdown. Might or might not work."); - flagAsyncShutdown = false; - waitLoop(); + for(i = 0; i < args.length; ++i) { + /* Deserialize the following bytes based on their + corresponding TypeIds.id from the header. */ + const t = typeIds[i]; + if(t.setter){ + viewDV[t.setter](offset, args[i], state.littleEndian); + offset += t.size; + }else{/*String*/ + const s = textEncoder.encode(args[i]); + viewDV.setInt32(offset, s.byteLength, state.littleEndian); + offset += 4; + viewU8.set(s, offset); + offset += s.byteLength; } - break; - case 'opfs-async-metrics': - metrics.dump(); - break; + } + //log("serialize() result:",viewU8.slice(0,offset)); + }else{ + viewU8[0] = 0; + } + metrics.s11n.serialize.time += performance.now() - t; + }; + + state.s11n.storeException = state.asyncS11nExceptions + ? ((priority,e)=>{ + if(priority<=state.asyncS11nExceptions){ + state.s11n.serialize([e.name,': ',e.message].join("")); + } + }) + : ()=>{}; + + return state.s11n; + }/*initS11n()*/; + + const waitLoop = async function f(){ + const opHandlers = Object.create(null); + for(let k of Object.keys(state.opIds)){ + const vi = vfsAsyncImpls[k]; + if(!vi) continue; + const o = Object.create(null); + opHandlers[state.opIds[k]] = o; + o.key = k; + o.f = vi; + } + while(!flagAsyncShutdown){ + try { + if('timed-out'===Atomics.wait( + state.sabOPView, state.opIds.whichOp, 0, state.asyncIdleWaitTime + )){ + await releaseImplicitLocks(); + continue; + } + const opId = Atomics.load(state.sabOPView, state.opIds.whichOp); + Atomics.store(state.sabOPView, state.opIds.whichOp, 0); + const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId); + const args = state.s11n.deserialize( + true /* clear s11n to keep the caller from confusing this with + an exception string written by the upcoming + operation */ + ) || []; + //warn("waitLoop() whichOp =",opId, hnd, args); + if(hnd.f) await hnd.f(...args); + else error("Missing callback for opId",opId); + }catch(e){ + error('in waitLoop():',e); + } } }; - wMsg('opfs-async-loaded'); -}).catch((e)=>error("error initializing OPFS asyncer:",e)); + + navigator.storage.getDirectory().then(function(d){ + state.rootDir = d; + self.onmessage = function({data}){ + switch(data.type){ + case 'opfs-async-init':{ + /* Receive shared state from synchronous partner */ + const opt = data.args; + for(const k in opt) state[k] = opt[k]; + state.verbose = opt.verbose ?? 1; + state.sabOPView = new Int32Array(state.sabOP); + state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); + state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); + Object.keys(vfsAsyncImpls).forEach((k)=>{ + if(!Number.isFinite(state.opIds[k])){ + toss("Maintenance required: missing state.opIds[",k,"]"); + } + }); + initS11n(); + metrics.reset(); + log("init state",state); + wPost('opfs-async-inited'); + waitLoop(); + break; + } + case 'opfs-async-restart': + if(flagAsyncShutdown){ + warn("Restarting after opfs-async-shutdown. Might or might not work."); + flagAsyncShutdown = false; + waitLoop(); + } + break; + case 'opfs-async-metrics': + metrics.dump(); + break; + } + }; + wPost('opfs-async-loaded'); + }).catch((e)=>error("error initializing OPFS asyncer:",e)); +}/*installAsyncProxy()*/; +if(!self.SharedArrayBuffer){ + wPost('opfs-unavailable', "Missing SharedArrayBuffer API.", + "The server must emit the COOP/COEP response headers to enable that."); +}else if(!self.Atomics){ + wPost('opfs-unavailable', "Missing Atomics API.", + "The server must emit the COOP/COEP response headers to enable that."); +}else if(!self.FileSystemHandle || + !self.FileSystemDirectoryHandle || + !self.FileSystemFileHandle || + !self.FileSystemFileHandle.prototype.createSyncAccessHandle || + !navigator.storage.getDirectory){ + wPost('opfs-unavailable',"Missing required OPFS APIs."); +}else{ + installAsyncProxy(self); +} diff --git a/ext/wasm/api/sqlite3-v-helper.js b/ext/wasm/api/sqlite3-v-helper.js new file mode 100644 index 000000000..10be8ebce --- /dev/null +++ b/ext/wasm/api/sqlite3-v-helper.js @@ -0,0 +1,714 @@ +/* +** 2022-11-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 installs sqlite3.vfs, and object which exists to assist + in the creation of JavaScript implementations of sqlite3_vfs, along + with its virtual table counterpart, sqlite3.vtab. +*/ +'use strict'; +self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; + const vfs = Object.create(null), vtab = Object.create(null); + + sqlite3.vfs = vfs; + sqlite3.vtab = vtab; + + const sii = capi.sqlite3_index_info; + /** + If n is >=0 and less than this.$nConstraint, this function + returns either a WASM pointer to the 0-based nth entry of + this.$aConstraint (if passed a truthy 2nd argument) or an + sqlite3_index_info.sqlite3_index_constraint object wrapping that + address (if passed a falsy value or no 2nd argument). Returns a + falsy value if n is out of range. + */ + sii.prototype.nthConstraint = function(n, asPtr=false){ + if(n<0 || n>=this.$nConstraint) return false; + const ptr = this.$aConstraint + ( + sii.sqlite3_index_constraint.structInfo.sizeof * n + ); + return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr); + }; + + /** + Works identically to nthConstraint() but returns state from + this.$aConstraintUsage, so returns an + sqlite3_index_info.sqlite3_index_constraint_usage instance + if passed no 2nd argument or a falsy 2nd argument. + */ + sii.prototype.nthConstraintUsage = function(n, asPtr=false){ + if(n<0 || n>=this.$nConstraint) return false; + const ptr = this.$aConstraintUsage + ( + sii.sqlite3_index_constraint_usage.structInfo.sizeof * n + ); + return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr); + }; + + /** + If n is >=0 and less than this.$nOrderBy, this function + returns either a WASM pointer to the 0-based nth entry of + this.$aOrderBy (if passed a truthy 2nd argument) or an + sqlite3_index_info.sqlite3_index_orderby object wrapping that + address (if passed a falsy value or no 2nd argument). Returns a + falsy value if n is out of range. + */ + sii.prototype.nthOrderBy = function(n, asPtr=false){ + if(n<0 || n>=this.$nOrderBy) return false; + const ptr = this.$aOrderBy + ( + sii.sqlite3_index_orderby.structInfo.sizeof * n + ); + return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr); + }; + + /** + Installs a StructBinder-bound function pointer member of the + given name and function in the given StructType target object. + + It creates a WASM proxy for the given function and arranges for + that proxy to be cleaned up when tgt.dispose() is called. Throws + on the slightest hint of error, e.g. tgt is-not-a StructType, + name does not map to a struct-bound member, etc. + + As a special case, if the given function is a pointer, then + `wasm.functionEntry()` is used to validate that it is a known + function. If so, it is used as-is with no extra level of proxying + or cleanup, else an exception is thrown. It is legal to pass a + value of 0, indicating a NULL pointer, with the caveat that 0 + _is_ a legal function pointer in WASM but it will not be accepted + as such _here_. (Justification: the function at address zero must + be one which initially came from the WASM module, not a method we + want to bind to a virtual table or VFS.) + + This function returns a proxy for itself which is bound to tgt + and takes 2 args (name,func). That function returns the same + thing as this one, permitting calls to be chained. + + If called with only 1 arg, it has no side effects but returns a + func with the same signature as described above. + + ACHTUNG: because we cannot generically know how to transform JS + exceptions into result codes, the installed functions do no + automatic catching of exceptions. It is critical, to avoid + undefined behavior in the C layer, that methods mapped via + this function do not throw. The exception, as it were, to that + rule is... + + If applyArgcCheck is true then each JS function (as opposed to + function pointers) gets wrapped in a proxy which asserts that it + is passed the expected number of arguments, throwing if the + argument count does not match expectations. That is only intended + for dev-time usage for sanity checking, and will leave the C + environment in an undefined state. + */ + const installMethod = function callee( + tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck + ){ + if(!(tgt instanceof sqlite3.StructBinder.StructType)){ + toss("Usage error: target object is-not-a StructType."); + }else if(!(func instanceof Function) && !wasm.isPtr(func)){ + toss("Usage errror: expecting a Function or WASM pointer to one."); + } + if(1===arguments.length){ + return (n,f)=>callee(tgt, n, f, applyArgcCheck); + } + if(!callee.argcProxy){ + callee.argcProxy = function(tgt, funcName, func,sig){ + return function(...args){ + if(func.length!==arguments.length){ + toss("Argument mismatch for", + tgt.structInfo.name+"::"+funcName + +": Native signature is:",sig); + } + return func.apply(this, args); + } + }; + /* An ondispose() callback for use with + sqlite3.StructBinder-created types. */ + callee.removeFuncList = function(){ + if(this.ondispose.__removeFuncList){ + this.ondispose.__removeFuncList.forEach( + (v,ndx)=>{ + if('number'===typeof v){ + try{wasm.uninstallFunction(v)} + catch(e){/*ignore*/} + } + /* else it's a descriptive label for the next number in + the list. */ + } + ); + delete this.ondispose.__removeFuncList; + } + }; + }/*static init*/ + const sigN = tgt.memberSignature(name); + if(sigN.length<2){ + toss("Member",name,"does not have a function pointer signature:",sigN); + } + const memKey = tgt.memberKey(name); + const fProxy = (applyArgcCheck && !wasm.isPtr(func)) + /** This middle-man proxy is only for use during development, to + confirm that we always pass the proper number of + arguments. We know that the C-level code will always use the + correct argument count. */ + ? callee.argcProxy(tgt, memKey, func, sigN) + : func; + if(wasm.isPtr(fProxy)){ + if(fProxy && !wasm.functionEntry(fProxy)){ + toss("Pointer",fProxy,"is not a WASM function table entry."); + } + tgt[memKey] = fProxy; + }else{ + const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); + tgt[memKey] = pFunc; + if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){ + tgt.addOnDispose('ondispose.__removeFuncList handler', + callee.removeFuncList); + tgt.ondispose.__removeFuncList = []; + } + tgt.ondispose.__removeFuncList.push(memKey, pFunc); + } + return (n,f)=>callee(tgt, n, f, applyArgcCheck); + }/*installMethod*/; + installMethod.installMethodArgcCheck = false; + + /** + Installs methods into the given StructType-type instance. Each + entry in the given methods object must map to a known member of + the given StructType, else an exception will be triggered. See + installMethod() for more details, including the semantics of the + 3rd argument. + + As an exception to the above, if any two or more methods in the + 2nd argument are the exact same function, installMethod() is + _not_ called for the 2nd and subsequent instances, and instead + those instances get assigned the same method pointer which is + created for the first instance. This optimization is primarily to + accommodate special handling of sqlite3_module::xConnect and + xCreate methods. + + On success, returns its first argument. Throws on error. + */ + const installMethods = function( + structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + const seen = new Map /* map of */; + for(const k of Object.keys(methods)){ + const m = methods[k]; + const prior = seen.get(m); + if(prior){ + const mkey = structInstance.memberKey(k); + structInstance[mkey] = structInstance[structInstance.memberKey(prior)]; + }else{ + installMethod(structInstance, k, m, applyArgcCheck); + seen.set(m, k); + } + } + return structInstance; + }; + + /** + Equivalent to calling installMethod(this,...arguments) with a + first argument of this object. If called with 1 or 2 arguments + and the first is an object, it's instead equivalent to calling + installMethods(this,...arguments). + */ + sqlite3.StructBinder.StructType.prototype.installMethod = function callee( + name, func, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + return (arguments.length < 3 && name && 'object'===typeof name) + ? installMethods(this, ...arguments) + : installMethod(this, ...arguments); + }; + + /** + Equivalent to calling installMethods() with a first argument + of this object. + */ + sqlite3.StructBinder.StructType.prototype.installMethods = function( + methods, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + return installMethods(this, methods, applyArgcCheck); + }; + + /** + Uses sqlite3_vfs_register() to register this + sqlite3.capi.sqlite3_vfs. This object must have already been + filled out properly. If the first argument is truthy, the VFS is + registered as the default VFS, else it is not. + + On success, returns this object. Throws on error. + */ + capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){ + if(!(this instanceof sqlite3.capi.sqlite3_vfs)){ + toss("Expecting a sqlite3_vfs-type argument."); + } + const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0); + if(rc){ + toss("sqlite3_vfs_register(",this,") failed with rc",rc); + } + if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){ + toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", + this); + } + return this; + }; + + /** + A wrapper for installMethods() or registerVfs() to reduce + installation of a VFS and/or its I/O methods to a single + call. + + Accepts an object which contains the properties "io" and/or + "vfs", each of which is itself an object with following properties: + + - `struct`: an sqlite3.StructType-type struct. This must be a + populated (except for the methods) object of type + sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the + "vfs" entry). + + - `methods`: an object mapping sqlite3_io_methods method names + (e.g. 'xClose') to JS implementations of those methods. The JS + implementations must be call-compatible with their native + counterparts. + + For each of those object, this function passes its (`struct`, + `methods`, (optional) `applyArgcCheck`) properties to + installMethods(). + + If the `vfs` entry is set then: + + - Its `struct` property's registerVfs() is called. The + `vfs` entry may optionally have an `asDefault` property, which + gets passed as the argument to registerVfs(). + + - If `struct.$zName` is falsy and the entry has a string-type + `name` property, `struct.$zName` is set to the C-string form of + that `name` value before registerVfs() is called. + + On success returns this object. Throws on error. + */ + vfs.installVfs = function(opt){ + let count = 0; + const propList = ['io','vfs']; + for(const key of propList){ + const o = opt[key]; + if(o){ + ++count; + installMethods(o.struct, o.methods, !!o.applyArgcCheck); + if('vfs'===key){ + if(!o.struct.$zName && 'string'===typeof o.name){ + o.struct.addOnDispose( + o.struct.$zName = wasm.allocCString(o.name) + ); + } + o.struct.registerVfs(!!o.asDefault); + } + } + } + if(!count) toss("Misuse: installVfs() options object requires at least", + "one of:", propList); + return this; + }; + + /** + Internal factory function for xVtab and xCursor impls. + */ + const __xWrapFactory = function(methodName,StructType){ + return function(ptr,removeMapping=false){ + if(0===arguments.length) ptr = new StructType; + if(ptr instanceof StructType){ + //T.assert(!this.has(ptr.pointer)); + this.set(ptr.pointer, ptr); + return ptr; + }else if(!wasm.isPtr(ptr)){ + sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()"); + } + let rc = this.get(ptr); + if(removeMapping) this.delete(ptr); + return rc; + }.bind(new Map); + }; + + /** + A factory function which implements a simple lifetime manager for + mappings between C struct pointers and their JS-level wrappers. + The first argument must be the logical name of the manager + (e.g. 'xVtab' or 'xCursor'), which is only used for error + reporting. The second must be the capi.XYZ struct-type value, + e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor. + + Returns an object with 4 methods: create(), get(), unget(), and + dispose(), plus a StructType member with the value of the 2nd + argument. The methods are documented in the body of this + function. + */ + const StructPtrMapper = function(name, StructType){ + const __xWrap = __xWrapFactory(name,StructType); + /** + This object houses a small API for managing mappings of (`T*`) + to StructType objects, specifically within the lifetime + requirements of sqlite3_module methods. + */ + return Object.assign(Object.create(null),{ + /** The StructType object for this object's API. */ + StructType, + /** + Creates a new StructType object, writes its `pointer` + value to the given output pointer, and returns that + object. Its intended usage depends on StructType: + + sqlite3_vtab: to be called from sqlite3_module::xConnect() + or xCreate() implementations. + + sqlite3_vtab_cursor: to be called from xOpen(). + + This will throw if allocation of the StructType instance + fails or if ppOut is not a pointer-type value. + */ + create: (ppOut)=>{ + const rc = __xWrap(); + wasm.pokePtr(ppOut, rc.pointer); + return rc; + }, + /** + Returns the StructType object previously mapped to the + given pointer using create(). Its intended usage depends + on StructType: + + sqlite3_vtab: to be called from sqlite3_module methods which + take a (sqlite3_vtab*) pointer _except_ for + xDestroy()/xDisconnect(), in which case unget() or dispose(). + + sqlite3_vtab_cursor: to be called from any sqlite3_module methods + which take a `sqlite3_vtab_cursor*` argument except xClose(), + in which case use unget() or dispose(). + + Rule to remember: _never_ call dispose() on an instance + returned by this function. + */ + get: (pCObj)=>__xWrap(pCObj), + /** + Identical to get() but also disconnects the mapping between the + given pointer and the returned StructType object, such that + future calls to this function or get() with the same pointer + will return the undefined value. Its intended usage depends + on StructType: + + sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or + xDestroy() implementations or in error handling of a failed + xCreate() or xConnect(). + + sqlite3_vtab_cursor: to be called from xClose() or during + cleanup in a failed xOpen(). + + Calling this method obligates the caller to call dispose() on + the returned object when they're done with it. + */ + unget: (pCObj)=>__xWrap(pCObj,true), + /** + Works like unget() plus it calls dispose() on the + StructType object. + */ + dispose: (pCObj)=>{ + const o = __xWrap(pCObj,true); + if(o) o.dispose(); + } + }); + }; + + /** + A lifetime-management object for mapping `sqlite3_vtab*` + instances in sqlite3_module methods to capi.sqlite3_vtab + objects. + + The API docs are in the API-internal StructPtrMapper(). + */ + vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab); + + /** + A lifetime-management object for mapping `sqlite3_vtab_cursor*` + instances in sqlite3_module methods to capi.sqlite3_vtab_cursor + objects. + + The API docs are in the API-internal StructPtrMapper(). + */ + vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor); + + /** + Convenience form of creating an sqlite3_index_info wrapper, + intended for use in xBestIndex implementations. Note that the + caller is expected to call dispose() on the returned object + before returning. Though not _strictly_ required, as that object + does not own the pIdxInfo memory, it is nonetheless good form. + */ + vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo); + + /** + Given an error object, this function returns + sqlite3.capi.SQLITE_NOMEM if (e instanceof + sqlite3.WasmAllocError), else it returns its + second argument. Its intended usage is in the methods + of a sqlite3_vfs or sqlite3_module: + + ``` + try{ + let rc = ... + return rc; + }catch(e){ + return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ); + // where SQLITE_XYZ is some call-appropriate result code. + } + ``` + */ + /**vfs.exceptionToRc = vtab.exceptionToRc = + (e, defaultRc=capi.SQLITE_ERROR)=>( + (e instanceof sqlite3.WasmAllocError) + ? capi.SQLITE_NOMEM + : defaultRc + );*/ + + /** + Given an sqlite3_module method name and error object, this + function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof + sqlite3.WasmAllocError), else it returns its second argument. Its + intended usage is in the methods of a sqlite3_vfs or + sqlite3_module: + + ``` + try{ + let rc = ... + return rc; + }catch(e){ + return sqlite3.vtab.xError( + 'xColumn', e, sqlite3.capi.SQLITE_XYZ); + // where SQLITE_XYZ is some call-appropriate result code. + } + ``` + + If no 3rd argument is provided, its default depends on + the error type: + + - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM. + + - If err is an SQLite3Error then its `resultCode` property + is used. + + - If all else fails, capi.SQLITE_ERROR is used. + + If xError.errorReporter is a function, it is called in + order to report the error, else the error is not reported. + If that function throws, that exception is ignored. + */ + vtab.xError = function f(methodName, err, defaultRc){ + if(f.errorReporter instanceof Function){ + try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);} + catch(e){/*ignored*/} + } + let rc; + if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM; + else if(arguments.length>2) rc = defaultRc; + else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode; + return rc || capi.SQLITE_ERROR; + }; + vtab.xError.errorReporter = 1 ? console.error.bind(console) : false; + + /** + "The problem" with this is that it introduces an outer function with + a different arity than the passed-in method callback. That means we + cannot do argc validation on these. Additionally, some methods (namely + xConnect) may have call-specific error handling. It would be a shame to + hard-coded that per-method support in this function. + */ + /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){ + return function(...args){ + try { method(...args); } + }catch(e){ return vtab.xError(methodName, e, defaultRc) } + }; + */ + + /** + A helper for sqlite3_vtab::xRowid() and xUpdate() + implementations. It must be passed the final argument to one of + those methods (an output pointer to an int64 row ID) and the + value to store at the output pointer's address. Returns the same + as wasm.poke() and will throw if the 1st or 2nd arguments + are invalid for that function. + + Example xRowid impl: + + ``` + const xRowid = (pCursor, ppRowid64)=>{ + const c = vtab.xCursor(pCursor); + vtab.xRowid(ppRowid64, c.myRowId); + return 0; + }; + ``` + */ + vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64'); + + /** + A helper to initialize and set up an sqlite3_module object for + later installation into individual databases using + sqlite3_create_module(). Requires an object with the following + properties: + + - `methods`: an object containing a mapping of properties with + the C-side names of the sqlite3_module methods, e.g. xCreate, + xBestIndex, etc., to JS implementations for those functions. + Certain special-case handling is performed, as described below. + + - `catchExceptions` (default=false): if truthy, the given methods + are not mapped as-is, but are instead wrapped inside wrappers + which translate exceptions into result codes of SQLITE_ERROR or + SQLITE_NOMEM, depending on whether the exception is an + sqlite3.WasmAllocError. In the case of the xConnect and xCreate + methods, the exception handler also sets the output error + string to the exception's error string. + + - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If + not set, one will be created automatically. If the current + "this" is-a sqlite3_module then it is unconditionally used in + place of `struct`. + + - OPTIONAL `iVersion`: if set, it must be an integer value and it + gets assigned to the `$iVersion` member of the struct object. + If it's _not_ set, and the passed-in `struct` object's `$iVersion` + is 0 (the default) then this function attempts to define a value + for that property based on the list of methods it has. + + If `catchExceptions` is false, it is up to the client to ensure + that no exceptions escape the methods, as doing so would move + them through the C API, leading to undefined + behavior. (vtab.xError() is intended to assist in reporting + such exceptions.) + + Certain methods may refer to the same implementation. To simplify + the definition of such methods: + + - If `methods.xConnect` is `true` then the value of + `methods.xCreate` is used in its place, and vice versa. sqlite + treats xConnect/xCreate functions specially if they are exactly + the same function (same pointer value). + + - If `methods.xDisconnect` is true then the value of + `methods.xDestroy` is used in its place, and vice versa. + + This is to facilitate creation of those methods inline in the + passed-in object without requiring the client to explicitly get a + reference to one of them in order to assign it to the other + one. + + The `catchExceptions`-installed handlers will account for + identical references to the above functions and will install the + same wrapper function for both. + + The given methods are expected to return integer values, as + expected by the C API. If `catchExceptions` is truthy, the return + value of the wrapped function will be used as-is and will be + translated to 0 if the function returns a falsy value (e.g. if it + does not have an explicit return). If `catchExceptions` is _not_ + active, the method implementations must explicitly return integer + values. + + Throws on error. On success, returns the sqlite3_module object + (`this` or `opt.struct` or a new sqlite3_module instance, + depending on how it's called). + */ + vtab.setupModule = function(opt){ + let createdMod = false; + const mod = (this instanceof capi.sqlite3_module) + ? this : (opt.struct || (createdMod = new capi.sqlite3_module())); + try{ + const methods = opt.methods || toss("Missing 'methods' object."); + for(const e of Object.entries({ + // -----^ ==> [k,v] triggers a broken code transformation in + // some versions of the emsdk toolchain. + xConnect: 'xCreate', xDisconnect: 'xDestroy' + })){ + // Remap X=true to X=Y for certain X/Y combinations + const k = e[0], v = e[1]; + if(true === methods[k]) methods[k] = methods[v]; + else if(true === methods[v]) methods[v] = methods[k]; + } + if(opt.catchExceptions){ + const fwrap = function(methodName, func){ + if(['xConnect','xCreate'].indexOf(methodName) >= 0){ + return function(pDb, pAux, argc, argv, ppVtab, pzErr){ + try{return func(...arguments) || 0} + catch(e){ + if(!(e instanceof sqlite3.WasmAllocError)){ + wasm.dealloc(wasm.peekPtr(pzErr)); + wasm.pokePtr(pzErr, wasm.allocCString(e.message)); + } + return vtab.xError(methodName, e); + } + }; + }else{ + return function(...args){ + try{return func(...args) || 0} + catch(e){ + return vtab.xError(methodName, e); + } + }; + } + }; + const mnames = [ + 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect', + 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext', + 'xEof', 'xColumn', 'xRowid', 'xUpdate', + 'xBegin', 'xSync', 'xCommit', 'xRollback', + 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease', + 'xRollbackTo', 'xShadowName' + ]; + const remethods = Object.create(null); + for(const k of mnames){ + const m = methods[k]; + if(!(m instanceof Function)) continue; + else if('xConnect'===k && methods.xCreate===m){ + remethods[k] = methods.xCreate; + }else if('xCreate'===k && methods.xConnect===m){ + remethods[k] = methods.xConnect; + }else{ + remethods[k] = fwrap(k, m); + } + } + installMethods(mod, remethods, false); + }else{ + // No automatic exception handling. Trust the client + // to not throw. + installMethods( + mod, methods, !!opt.applyArgcCheck/*undocumented option*/ + ); + } + if(0===mod.$iVersion){ + let v; + if('number'===typeof opt.iVersion) v = opt.iVersion; + else if(mod.$xShadowName) v = 3; + else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2; + else v = 1; + mod.$iVersion = v; + } + }catch(e){ + if(createdMod) createdMod.dispose(); + throw e; + } + return mod; + }/*setupModule()*/; + + /** + Equivalent to calling vtab.setupModule() with this sqlite3_module + object as the call's `this`. + */ + capi.sqlite3_module.prototype.setupModule = function(opt){ + return vtab.setupModule.call(this, opt); + }; +}/*sqlite3ApiBootstrap.initializers.push()*/); diff --git a/ext/wasm/api/sqlite3-api-opfs.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js similarity index 84% rename from ext/wasm/api/sqlite3-api-opfs.js rename to ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index da5496f65..3e3255b0c 100644 --- a/ext/wasm/api/sqlite3-api-opfs.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -76,15 +76,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ `opfs` property, containing several OPFS-specific utilities. */ const installOpfsVfs = function callee(options){ - if(!self.SharedArrayBuffer || - !self.Atomics || - !self.FileSystemHandle || - !self.FileSystemDirectoryHandle || - !self.FileSystemFileHandle || - !self.FileSystemFileHandle.prototype.createSyncAccessHandle || - !navigator.storage.getDirectory){ + if(!self.SharedArrayBuffer + || !self.Atomics){ return Promise.reject( - new Error("This environment does not have OPFS support.") + new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+ + "The server must emit the COOP/COEP response headers to enable those. "+ + "See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep") + ); + }else if(self.window===self && self.document){ + return Promise.reject( + new Error("The OPFS sqlite3_vfs cannot run in the main thread "+ + "because it requires Atomics.wait().") + ); + }else if(!self.FileSystemHandle || + !self.FileSystemDirectoryHandle || + !self.FileSystemFileHandle || + !self.FileSystemFileHandle.prototype.createSyncAccessHandle || + !navigator.storage.getDirectory){ + return Promise.reject( + new Error("Missing required OPFS APIs.") ); } if(!options || 'object'!==typeof options){ @@ -92,7 +102,8 @@ const installOpfsVfs = function callee(options){ } const urlParams = new URL(self.location.href).searchParams; if(undefined===options.verbose){ - options.verbose = urlParams.has('opfs-verbose') ? 3 : 2; + options.verbose = urlParams.has('opfs-verbose') + ? (+urlParams.get('opfs-verbose') || 2) : 1; } if(undefined===options.sanityChecks){ options.sanityChecks = urlParams.has('opfs-sanity-check'); @@ -101,14 +112,16 @@ const installOpfsVfs = function callee(options){ options.proxyUri = callee.defaultProxyUri; } + //sqlite3.config.warn("OPFS options =",options,self.location); + if('function' === typeof options.proxyUri){ options.proxyUri = options.proxyUri(); } const thePromise = new Promise(function(promiseResolve, promiseReject_){ const loggers = { - 0:console.error.bind(console), - 1:console.warn.bind(console), - 2:console.log.bind(console) + 0:sqlite3.config.error.bind(console), + 1:sqlite3.config.warn.bind(console), + 2:sqlite3.config.log.bind(console) }; const logImpl = (level,...args)=>{ if(options.verbose>level) loggers[level]("OPFS syncer:",...args); @@ -116,7 +129,7 @@ const installOpfsVfs = function callee(options){ const log = (...args)=>logImpl(2, ...args); const warn = (...args)=>logImpl(1, ...args); const error = (...args)=>logImpl(0, ...args); - const toss = function(...args){throw new Error(args.join(' '))}; + const toss = sqlite3.util.toss; const capi = sqlite3.capi; const wasm = sqlite3.wasm; const sqlite3_vfs = capi.sqlite3_vfs; @@ -125,8 +138,24 @@ const installOpfsVfs = function callee(options){ /** Generic utilities for working with OPFS. This will get filled out by the Promise setup and, on success, installed as sqlite3.opfs. + + ACHTUNG: do not rely on these APIs in client code. They are + experimental and subject to change or removal as the + OPFS-specific sqlite3_vfs evolves. */ const opfsUtil = Object.create(null); + + /** + Returns true if _this_ thread has access to the OPFS APIs. + */ + const thisThreadHasOPFS = ()=>{ + return self.FileSystemHandle && + self.FileSystemDirectoryHandle && + self.FileSystemFileHandle && + self.FileSystemFileHandle.prototype.createSyncAccessHandle && + navigator.storage.getDirectory; + }; + /** Not part of the public API. Solely for internal/development use. @@ -142,11 +171,11 @@ const installOpfsVfs = function callee(options){ m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0; } - console.log(self.location.href, + sqlite3.config.log(self.location.href, "metrics for",self.location.href,":",metrics, "\nTotal of",n,"op(s) for",t, "ms (incl. "+w+" ms of waiting on the async side)"); - console.log("Serialization metrics:",metrics.s11n); + sqlite3.config.log("Serialization metrics:",metrics.s11n); W.postMessage({type:'opfs-async-metrics'}); }, reset: function(){ @@ -161,12 +190,21 @@ const installOpfsVfs = function callee(options){ s = metrics.s11n.deserialize = Object.create(null); s.count = s.time = 0; } - }/*metrics*/; + }/*metrics*/; + const opfsVfs = new sqlite3_vfs(); + const opfsIoMethods = new sqlite3_io_methods(); const promiseReject = function(err){ opfsVfs.dispose(); return promiseReject_(err); }; - const W = new Worker(options.proxyUri); + const W = +//#if target=es6-bundler-friendly + new Worker(new URL("sqlite3-opfs-async-proxy.js", import.meta.url)); +//#elif target=es6-module + new Worker(new URL(options.proxyUri, import.meta.url)); +//#else + new Worker(options.proxyUri); +//#endif W._originalOnError = W.onerror /* will be restored later */; W.onerror = function(err){ // The error object doesn't contain any useful info when the @@ -178,10 +216,7 @@ const installOpfsVfs = function callee(options){ const dVfs = pDVfs ? new sqlite3_vfs(pDVfs) : null /* dVfs will be null when sqlite3 is built with - SQLITE_OS_OTHER. Though we cannot currently handle - that case, the hope is to eventually be able to. */; - const opfsVfs = new sqlite3_vfs(); - const opfsIoMethods = new sqlite3_io_methods(); + SQLITE_OS_OTHER. */; opfsVfs.$iVersion = 2/*yes, two*/; opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof; opfsVfs.$mxPathname = 1024/*sure, why not?*/; @@ -235,13 +270,23 @@ const installOpfsVfs = function callee(options){ // Int16Array uses the platform's endianness. return new Int16Array(buffer)[0] === 256; })(); + /** + asyncIdleWaitTime is how long (ms) to wait, in the async proxy, + for each Atomics.wait() when waiting on inbound VFS API calls. + We need to wake up periodically to give the thread a chance to + do other things. If this is too high (e.g. 500ms) then even two + workers/tabs can easily run into locking errors. Some multiple + of this value is also used for determining how long to wait on + lock contention to free up. + */ + state.asyncIdleWaitTime = 150; /** Whether the async counterpart should log exceptions to the serialization channel. That produces a great deal of noise for seemingly innocuous things like xAccess() checks for missing files, so this option may have one of 3 values: - 0 = no exception logging + 0 = no exception logging. 1 = only log exceptions for "significant" ops like xOpen(), xRead(), and xWrite(). @@ -257,7 +302,9 @@ const installOpfsVfs = function callee(options){ The size of the block in our SAB for serializing arguments and result values. Needs to be large enough to hold serialized values of any of the proxied APIs. Filenames are the largest - part but are limited to opfsVfs.$mxPathname bytes. + part but are limited to opfsVfs.$mxPathname bytes. We also + store exceptions there, so it needs to be long enough to hold + a reasonably long exception string. */ state.sabS11nSize = opfsVfs.$mxPathname * 2; /** @@ -318,6 +365,7 @@ const installOpfsVfs = function callee(options){ [ 'SQLITE_ACCESS_EXISTS', 'SQLITE_ACCESS_READWRITE', + 'SQLITE_BUSY', 'SQLITE_ERROR', 'SQLITE_IOERR', 'SQLITE_IOERR_ACCESS', @@ -335,16 +383,37 @@ const installOpfsVfs = function callee(options){ 'SQLITE_LOCK_PENDING', 'SQLITE_LOCK_RESERVED', 'SQLITE_LOCK_SHARED', + 'SQLITE_LOCKED', 'SQLITE_MISUSE', 'SQLITE_NOTFOUND', 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE', + 'SQLITE_OPEN_MAIN_DB', 'SQLITE_OPEN_READONLY' ].forEach((k)=>{ if(undefined === (state.sq3Codes[k] = capi[k])){ toss("Maintenance required: not found:",k); } }); + state.opfsFlags = Object.assign(Object.create(null),{ + /** + Flag for use with xOpen(). "opfs-unlock-asap=1" enables + this. See defaultUnlockAsap, below. + */ + OPFS_UNLOCK_ASAP: 0x01, + /** + If true, any async routine which implicitly acquires a sync + access handle (i.e. an OPFS lock) will release that locks at + the end of the call which acquires it. If false, such + "autolocks" are not released until the VFS is idle for some + brief amount of time. + + The benefit of enabling this is much higher concurrency. The + down-side is much-reduced performance (as much as a 4x decrease + in speedtest1). + */ + defaultUnlockAsap: false + }); /** Runs the given operation (by name) in the async worker @@ -569,7 +638,13 @@ const installOpfsVfs = function callee(options){ const ndx = Math.random() * (f._n * 64) % f._n | 0; a[i] = f._chars[ndx]; } - return a.join(''); + return a.join(""); + /* + An alternative impl. with an unpredictable length + but much simpler: + + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36) + */ }; /** @@ -577,77 +652,6 @@ const installOpfsVfs = function callee(options){ */ const __openFiles = Object.create(null); - /** - Installs a StructBinder-bound function pointer member of the - given name and function in the given StructType target object. - It creates a WASM proxy for the given function and arranges for - that proxy to be cleaned up when tgt.dispose() is called. Throws - on the slightest hint of error (e.g. tgt is-not-a StructType, - name does not map to a struct-bound member, etc.). - - Returns a proxy for this function which is bound to tgt and takes - 2 args (name,func). That function returns the same thing, - permitting calls to be chained. - - If called with only 1 arg, it has no side effects but returns a - func with the same signature as described above. - */ - const installMethod = function callee(tgt, name, func){ - if(!(tgt instanceof sqlite3.StructBinder.StructType)){ - toss("Usage error: target object is-not-a StructType."); - } - if(1===arguments.length){ - return (n,f)=>callee(tgt,n,f); - } - if(!callee.argcProxy){ - callee.argcProxy = function(func,sig){ - return function(...args){ - if(func.length!==arguments.length){ - toss("Argument mismatch. Native signature is:",sig); - } - return func.apply(this, args); - } - }; - callee.removeFuncList = function(){ - if(this.ondispose.__removeFuncList){ - this.ondispose.__removeFuncList.forEach( - (v,ndx)=>{ - if('number'===typeof v){ - try{wasm.uninstallFunction(v)} - catch(e){/*ignore*/} - } - /* else it's a descriptive label for the next number in - the list. */ - } - ); - delete this.ondispose.__removeFuncList; - } - }; - }/*static init*/ - const sigN = tgt.memberSignature(name); - if(sigN.length<2){ - toss("Member",name," is not a function pointer. Signature =",sigN); - } - const memKey = tgt.memberKey(name); - const fProxy = 0 - /** This middle-man proxy is only for use during development, to - confirm that we always pass the proper number of - arguments. We know that the C-level code will always use the - correct argument count. */ - ? callee.argcProxy(func, sigN) - : func; - const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); - tgt[memKey] = pFunc; - if(!tgt.ondispose) tgt.ondispose = []; - if(!tgt.ondispose.__removeFuncList){ - tgt.ondispose.push('ondispose.__removeFuncList handler', - callee.removeFuncList); - tgt.ondispose.__removeFuncList = []; - } - tgt.ondispose.__removeFuncList.push(memKey, pFunc); - return (n,f)=>callee(tgt, n, f); - }/*installMethod*/; - const opTimer = Object.create(null); opTimer.op = undefined; opTimer.start = undefined; @@ -673,10 +677,10 @@ const installOpfsVfs = function callee(options){ success) release a sync-handle for it, but doing so would involve an inherent race condition. For the time being, pending a better solution, we simply report whether the - given pFile instance has a lock. + given pFile is open. */ const f = __openFiles[pFile]; - wasm.setMemValue(pOut, f.lockMode ? 1 : 0, 'i32'); + wasm.poke(pOut, f.lockType ? 1 : 0, 'i32'); return 0; }, xClose: function(pFile){ @@ -705,10 +709,15 @@ const installOpfsVfs = function callee(options){ }, xFileSize: function(pFile,pSz64){ mTimeStart('xFileSize'); - const rc = opRun('xFileSize', pFile); + let rc = opRun('xFileSize', pFile); if(0==rc){ - const sz = state.s11n.deserialize()[0]; - wasm.setMemValue(pSz64, sz, 'i64'); + try { + const sz = state.s11n.deserialize()[0]; + wasm.poke(pSz64, sz, 'i64'); + }catch(e){ + error("Unexpected error reading xFileSize() result:",e); + rc = state.sq3Codes.SQLITE_IOERR; + } } mTimeEnd(); return rc; @@ -717,7 +726,11 @@ const installOpfsVfs = function callee(options){ mTimeStart('xLock'); const f = __openFiles[pFile]; let rc = 0; - if( capi.SQLITE_LOCK_NONE === f.lockType ) { + /* All OPFS locks are exclusive locks. If xLock() has + previously succeeded, do nothing except record the lock + type. If no lock is active, have the async counterpart + lock the file. */ + if( !f.lockType ) { rc = opRun('xLock', pFile, lockType); if( 0===rc ) f.lockType = lockType; }else{ @@ -732,7 +745,7 @@ const installOpfsVfs = function callee(options){ let rc; try { rc = opRun('xRead',pFile, n, Number(offset64)); - if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){ + if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){ /** Results get written to the SharedArrayBuffer f.sabView. Because the heap is _not_ a SharedArrayBuffer, we have @@ -792,27 +805,27 @@ const installOpfsVfs = function callee(options){ const vfsSyncWrappers = { xAccess: function(pVfs,zName,flags,pOut){ mTimeStart('xAccess'); - const rc = opRun('xAccess', wasm.cstringToJs(zName)); - wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' ); + const rc = opRun('xAccess', wasm.cstrToJs(zName)); + wasm.poke( pOut, (rc ? 0 : 1), 'i32' ); mTimeEnd(); return 0; }, xCurrentTime: function(pVfs,pOut){ /* If it turns out that we need to adjust for timezone, see: https://stackoverflow.com/a/11760121/1458521 */ - wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000), + wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000), 'double'); return 0; }, xCurrentTimeInt64: function(pVfs,pOut){ // TODO: confirm that this calculation is correct - wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(), + wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(), 'i64'); return 0; }, xDelete: function(pVfs, zName, doSyncDir){ mTimeStart('xDelete'); - opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false); + opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false); /* We're ignoring errors because we cannot yet differentiate between harmless and non-harmless failures. */ mTimeEnd(); @@ -835,22 +848,28 @@ const installOpfsVfs = function callee(options){ //xSleep is optionally defined below xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){ mTimeStart('xOpen'); + let opfsFlags = 0; if(0===zName){ zName = randomFilename(); }else if('number'===typeof zName){ - zName = wasm.cstringToJs(zName); + if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){ + /* -----------------------^^^^^ MUST pass the untranslated + C-string here. */ + opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP; + } + zName = wasm.cstrToJs(zName); } const fh = Object.create(null); fh.fid = pFile; fh.filename = zName; fh.sab = new SharedArrayBuffer(state.fileBufferSize); fh.flags = flags; - const rc = opRun('xOpen', pFile, zName, flags); + const rc = opRun('xOpen', pFile, zName, flags, opfsFlags); if(!rc){ /* Recall that sqlite3_vfs::xClose() will be called, even on error, unless pFile->pMethods is NULL. */ if(fh.readOnly){ - wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32'); + wasm.poke(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32'); } __openFiles[pFile] = fh; fh.sabView = state.sabFileBufView; @@ -886,14 +905,6 @@ const installOpfsVfs = function callee(options){ }; } - /* Install the vfs/io_methods into their C-level shared instances... */ - for(let k of Object.keys(ioSyncWrappers)){ - installMethod(opfsIoMethods, k, ioSyncWrappers[k]); - } - for(let k of Object.keys(vfsSyncWrappers)){ - installMethod(opfsVfs, k, vfsSyncWrappers[k]); - } - /** Expects an OPFS file path. It gets resolved, such that ".." components are properly expanded, and returned. If the 2nd arg @@ -934,7 +945,7 @@ const installOpfsVfs = function callee(options){ await opfsUtil.getDirForFilename(absDirName+"/filepart", true); return true; }catch(e){ - //console.warn("mkdir(",absDirName,") failed:",e); + //sqlite3.config.warn("mkdir(",absDirName,") failed:",e); return false; } }; @@ -1033,8 +1044,9 @@ const installOpfsVfs = function callee(options){ Irrevocably deletes _all_ files in the current origin's OPFS. Obviously, this must be used with great caution. It may throw an exception if removal of anything fails (e.g. a file is - locked), but the precise conditions under which it will throw - are not documented (so we cannot tell you what they are). + locked), but the precise conditions under which the underlying + APIs will throw are not documented (so we cannot tell you what + they are). */ opfsUtil.rmfr = async function(){ const dir = opfsUtil.rootDirectory, opt = {recurse: true}; @@ -1136,39 +1148,44 @@ const installOpfsVfs = function callee(options){ //TODO to support fiddle and worker1 db upload: //opfsUtil.createFile = function(absName, content=undefined){...} + //We have sqlite3.wasm.sqlite3_wasm_vfs_create_file() for this + //purpose but its interface and name are still under + //consideration. if(sqlite3.oo1){ - opfsUtil.OpfsDb = function(...args){ + const OpfsDb = function(...args){ const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args); opt.vfs = opfsVfs.$zName; sqlite3.oo1.DB.dbCtorHelper.call(this, opt); }; - opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); + OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); + sqlite3.oo1.OpfsDb = OpfsDb; sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( opfsVfs.pointer, - [ - /* Truncate journal mode is faster than delete or wal for - this vfs, per speedtest1. */ - "pragma journal_mode=truncate;" - /* - This vfs benefits hugely from cache on moderate/large - speedtest1 --size 50 and --size 100 workloads. We currently - rely on setting a non-default cache size when building - sqlite3.wasm. If that policy changes, the cache can - be set here. - */ - //"pragma cache_size=-8388608;" - ].join('') + function(oo1Db, sqlite3){ + /* Set a relatively high default busy-timeout handler to + help OPFS dbs deal with multi-tab/multi-worker + contention. */ + sqlite3.capi.sqlite3_busy_timeout(oo1Db, 10000); + sqlite3.capi.sqlite3_exec(oo1Db, [ + /* Truncate journal mode is faster than delete for + this vfs, per speedtest1. That gap seems to have closed with + Chrome version 108 or 109, but "persist" is very roughly 5-6% + faster than truncate in initial tests. */ + "pragma journal_mode=persist;", + /* + This vfs benefits hugely from cache on moderate/large + speedtest1 --size 50 and --size 100 workloads. We + currently rely on setting a non-default cache size when + building sqlite3.wasm. If that policy changes, the cache + can be set here. + */ + "pragma cache_size=-16384;" + ], 0, 0, 0); + } ); - } - - /** - Potential TODOs: + }/*extend sqlite3.oo1*/ - - Expose one or both of the Worker objects via opfsUtil and - publish an interface for proxying the higher-level OPFS - features like getting a directory listing. - */ const sanityCheck = function(){ const scope = wasm.scopedAllocPush(); const sq3File = new sqlite3_file(); @@ -1187,7 +1204,7 @@ const installOpfsVfs = function callee(options){ log("deserialize() says:",rc); if("This is ä string."!==rc[0]) toss("String d13n error."); vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); - rc = wasm.getMemValue(pOut,'i32'); + rc = wasm.peek(pOut,'i32'); log("xAccess(",dbFile,") exists ?=",rc); rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile, fid, openFlags, pOut); @@ -1198,22 +1215,22 @@ const installOpfsVfs = function callee(options){ return; } vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); - rc = wasm.getMemValue(pOut,'i32'); + rc = wasm.peek(pOut,'i32'); if(!rc) toss("xAccess() failed to detect file."); rc = ioSyncWrappers.xSync(sq3File.pointer, 0); if(rc) toss('sync failed w/ rc',rc); rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024); if(rc) toss('truncate failed w/ rc',rc); - wasm.setMemValue(pOut,0,'i64'); + wasm.poke(pOut,0,'i64'); rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut); if(rc) toss('xFileSize failed w/ rc',rc); - log("xFileSize says:",wasm.getMemValue(pOut, 'i64')); + log("xFileSize says:",wasm.peek(pOut, 'i64')); rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1); if(rc) toss("xWrite() failed!"); const readBuf = wasm.scopedAlloc(16); rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2); - wasm.setMemValue(readBuf+6,0); - let jRead = wasm.cstringToJs(readBuf); + wasm.poke(readBuf+6,0); + let jRead = wasm.cstrToJs(readBuf); log("xRead() got:",jRead); if("sanity"!==jRead) toss("Unexpected xRead() value."); if(vfsSyncWrappers.xSleep){ @@ -1226,7 +1243,7 @@ const installOpfsVfs = function callee(options){ log("Deleting file:",dbFile); vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234); vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); - rc = wasm.getMemValue(pOut,'i32'); + rc = wasm.peek(pOut,'i32'); if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete()."); warn("End of OPFS sanity checks."); }finally{ @@ -1238,6 +1255,11 @@ const installOpfsVfs = function callee(options){ W.onmessage = function({data}){ //log("Worker.onmessage:",data); switch(data.type){ + case 'opfs-unavailable': + /* Async proxy has determined that OPFS is unavailable. There's + nothing more for us to do here. */ + promiseReject(new Error(data.payload.join(' '))); + break; case 'opfs-async-loaded': /*Arrives as soon as the asyc proxy finishes loading. Pass our config and shared state on to the async worker.*/ @@ -1248,14 +1270,10 @@ const installOpfsVfs = function callee(options){ and has finished initializing, so the real work can begin...*/ try { - const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0); - if(rc){ - toss("sqlite3_vfs_register(OPFS) failed with rc",rc); - } - if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){ - toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS"); - } - capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods); + sqlite3.vfs.installVfs({ + io: {struct: opfsIoMethods, methods: ioSyncWrappers}, + vfs: {struct: opfsVfs, methods: vfsSyncWrappers} + }); state.sabOPView = new Int32Array(state.sabOP); state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); @@ -1264,14 +1282,18 @@ const installOpfsVfs = function callee(options){ warn("Running sanity checks because of opfs-sanity-check URL arg..."); sanityCheck(); } - navigator.storage.getDirectory().then((d)=>{ - W.onerror = W._originalOnError; - delete W._originalOnError; - sqlite3.opfs = opfsUtil; - opfsUtil.rootDirectory = d; - log("End of OPFS sqlite3_vfs setup.", opfsVfs); + if(thisThreadHasOPFS()){ + navigator.storage.getDirectory().then((d)=>{ + W.onerror = W._originalOnError; + delete W._originalOnError; + sqlite3.opfs = opfsUtil; + opfsUtil.rootDirectory = d; + log("End of OPFS sqlite3_vfs setup.", opfsVfs); + promiseResolve(sqlite3); + }).catch(promiseReject); + }else{ promiseResolve(sqlite3); - }); + } }catch(e){ error(e); promiseReject(e); @@ -1290,21 +1312,18 @@ const installOpfsVfs = function callee(options){ installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js"; self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ - if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){ - return; - } try{ let proxyJs = installOpfsVfs.defaultProxyUri; if(sqlite3.scriptInfo.sqlite3Dir){ installOpfsVfs.defaultProxyUri = sqlite3.scriptInfo.sqlite3Dir + proxyJs; - //console.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri); + //sqlite3.config.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri); } return installOpfsVfs().catch((e)=>{ - console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message); + sqlite3.config.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message); }); }catch(e){ - console.error("installOpfsVfs() exception:",e); + sqlite3.config.error("installOpfsVfs() exception:",e); throw e; } }); diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index af5ed6bf7..e7513509c 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -32,21 +32,16 @@ ** the same db handle as another thread, thus multi-threading support ** is unnecessary in the library. Because the filesystems are virtual ** and local to a given wasm runtime instance, two Workers can never -** access the same db file at once, with the exception of OPFS. As of -** this writing (2022-09-30), OPFS exclusively locks a file when -** opening it, so two Workers can never open the same OPFS-backed file -** at once. That situation will change if and when lower-level locking -** features are added to OPFS (as is currently planned, per folks -** involved with its development). +** access the same db file at once, with the exception of OPFS. ** -** Summary: except for the case of future OPFS, which supports -** locking, and any similar future filesystems, threading and file -** locking support are unnecessary in the wasm build. +** Summary: except for the case of OPFS, which supports locking using +** its own API, threading and file locking support are unnecessary in +** the wasm build. */ /* ** Undefine any SQLITE_... config flags which we specifically do not -** want undefined. Please keep these alphabetized. +** want defined. Please keep these alphabetized. */ #undef SQLITE_OMIT_DESERIALIZE #undef SQLITE_OMIT_MEMORYDB @@ -69,9 +64,17 @@ */ # define SQLITE_DEFAULT_CACHE_SIZE -16384 #endif -#if 0 && !defined(SQLITE_DEFAULT_PAGE_SIZE) -/* TODO: experiment with this. */ -# define SQLITE_DEFAULT_PAGE_SIZE 8192 /*4096*/ +#if !defined(SQLITE_DEFAULT_PAGE_SIZE) +/* +** OPFS performance is improved by approx. 12% with a page size of 8kb +** instead of 4kb. Performance with 16kb is equivalent to 8kb. +** +** Performance difference of kvvfs with a page size of 8kb compared to +** 4kb, as measured by speedtest1 --size 4, is indeterminate: +** measurements are all over the place either way and not +** significantly different. +*/ +# define SQLITE_DEFAULT_PAGE_SIZE 8192 #endif #ifndef SQLITE_DEFAULT_UNIX_VFS # define SQLITE_DEFAULT_UNIX_VFS "unix-none" @@ -96,12 +99,21 @@ #ifndef SQLITE_ENABLE_FTS4 # define SQLITE_ENABLE_FTS4 1 #endif +#ifndef SQLITE_ENABLE_MATH_FUNCTIONS +# define SQLITE_ENABLE_MATH_FUNCTIONS 1 +#endif #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK +# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ +#endif #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif +#ifndef SQLITE_ENABLE_SESSION +# define SQLITE_ENABLE_SESSION 1 +#endif #ifndef SQLITE_ENABLE_STMTVTAB # define SQLITE_ENABLE_STMTVTAB 1 #endif @@ -109,6 +121,12 @@ # define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION #endif +/**********************************************************************/ +/* SQLITE_M... */ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff +#endif + /**********************************************************************/ /* SQLITE_O... */ #ifndef SQLITE_OMIT_DEPRECATED @@ -311,11 +329,13 @@ SQLITE_WASM_KEEP int sqlite3_wasm_pstack_quota(void){ */ SQLITE_WASM_KEEP int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ - if( 0!=zMsg ){ - const int nMsg = sqlite3Strlen30(zMsg); - sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); - }else{ - sqlite3ErrorWithMsg(db, err_code, NULL); + if( db!=0 ){ + if( 0!=zMsg ){ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + }else{ + sqlite3ErrorWithMsg(db, err_code, NULL); + } } return err_code; } @@ -359,11 +379,11 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){ */ SQLITE_WASM_KEEP const char * sqlite3_wasm_enum_json(void){ - static char aBuffer[1024 * 12] = {0} /* where the JSON goes */; + static char aBuffer[1024 * 20] = {0} /* where the JSON goes */; int n = 0, nChildren = 0, nStruct = 0 /* output counters for figuring out where commas go */; char * zPos = &aBuffer[1] /* skip first byte for now to help protect - ** against a small race condition */; + ** against a small race condition */; char const * const zEnd = &aBuffer[0] + sizeof(aBuffer) /* one-past-the-end */; if(aBuffer[0]) return aBuffer; /* Leave aBuffer[0] at 0 until the end to help guard against a tiny @@ -401,10 +421,99 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; } _DefGroup; + DefGroup(authorizer){ + DefInt(SQLITE_DENY); + DefInt(SQLITE_IGNORE); + DefInt(SQLITE_CREATE_INDEX); + DefInt(SQLITE_CREATE_TABLE); + DefInt(SQLITE_CREATE_TEMP_INDEX); + DefInt(SQLITE_CREATE_TEMP_TABLE); + DefInt(SQLITE_CREATE_TEMP_TRIGGER); + DefInt(SQLITE_CREATE_TEMP_VIEW); + DefInt(SQLITE_CREATE_TRIGGER); + DefInt(SQLITE_CREATE_VIEW); + DefInt(SQLITE_DELETE); + DefInt(SQLITE_DROP_INDEX); + DefInt(SQLITE_DROP_TABLE); + DefInt(SQLITE_DROP_TEMP_INDEX); + DefInt(SQLITE_DROP_TEMP_TABLE); + DefInt(SQLITE_DROP_TEMP_TRIGGER); + DefInt(SQLITE_DROP_TEMP_VIEW); + DefInt(SQLITE_DROP_TRIGGER); + DefInt(SQLITE_DROP_VIEW); + DefInt(SQLITE_INSERT); + DefInt(SQLITE_PRAGMA); + DefInt(SQLITE_READ); + DefInt(SQLITE_SELECT); + DefInt(SQLITE_TRANSACTION); + DefInt(SQLITE_UPDATE); + DefInt(SQLITE_ATTACH); + DefInt(SQLITE_DETACH); + DefInt(SQLITE_ALTER_TABLE); + DefInt(SQLITE_REINDEX); + DefInt(SQLITE_ANALYZE); + DefInt(SQLITE_CREATE_VTABLE); + DefInt(SQLITE_DROP_VTABLE); + DefInt(SQLITE_FUNCTION); + DefInt(SQLITE_SAVEPOINT); + //DefInt(SQLITE_COPY) /* No longer used */; + DefInt(SQLITE_RECURSIVE); + } _DefGroup; + DefGroup(blobFinalizers) { /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as ** integers to avoid casting-related warnings. */ out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1"); + outf(",\"SQLITE_WASM_DEALLOC\": %lld", + (sqlite3_int64)(sqlite3_free)); + } _DefGroup; + + DefGroup(changeset){ + DefInt(SQLITE_CHANGESETSTART_INVERT); + DefInt(SQLITE_CHANGESETAPPLY_NOSAVEPOINT); + DefInt(SQLITE_CHANGESETAPPLY_INVERT); + + DefInt(SQLITE_CHANGESET_DATA); + DefInt(SQLITE_CHANGESET_NOTFOUND); + DefInt(SQLITE_CHANGESET_CONFLICT); + DefInt(SQLITE_CHANGESET_CONSTRAINT); + DefInt(SQLITE_CHANGESET_FOREIGN_KEY); + + DefInt(SQLITE_CHANGESET_OMIT); + DefInt(SQLITE_CHANGESET_REPLACE); + DefInt(SQLITE_CHANGESET_ABORT); + } _DefGroup; + + DefGroup(config){ + DefInt(SQLITE_CONFIG_SINGLETHREAD); + DefInt(SQLITE_CONFIG_MULTITHREAD); + DefInt(SQLITE_CONFIG_SERIALIZED); + DefInt(SQLITE_CONFIG_MALLOC); + DefInt(SQLITE_CONFIG_GETMALLOC); + DefInt(SQLITE_CONFIG_SCRATCH); + DefInt(SQLITE_CONFIG_PAGECACHE); + DefInt(SQLITE_CONFIG_HEAP); + DefInt(SQLITE_CONFIG_MEMSTATUS); + DefInt(SQLITE_CONFIG_MUTEX); + DefInt(SQLITE_CONFIG_GETMUTEX); +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ + DefInt(SQLITE_CONFIG_LOOKASIDE); + DefInt(SQLITE_CONFIG_PCACHE); + DefInt(SQLITE_CONFIG_GETPCACHE); + DefInt(SQLITE_CONFIG_LOG); + DefInt(SQLITE_CONFIG_URI); + DefInt(SQLITE_CONFIG_PCACHE2); + DefInt(SQLITE_CONFIG_GETPCACHE2); + DefInt(SQLITE_CONFIG_COVERING_INDEX_SCAN); + DefInt(SQLITE_CONFIG_SQLLOG); + DefInt(SQLITE_CONFIG_MMAP_SIZE); + DefInt(SQLITE_CONFIG_WIN32_HEAPSIZE); + DefInt(SQLITE_CONFIG_PCACHE_HDRSZ); + DefInt(SQLITE_CONFIG_PMASZ); + DefInt(SQLITE_CONFIG_STMTJRNL_SPILL); + DefInt(SQLITE_CONFIG_SMALL_MALLOC); + DefInt(SQLITE_CONFIG_SORTERREF_SIZE); + DefInt(SQLITE_CONFIG_MEMDB_MAXSIZE); } _DefGroup; DefGroup(dataTypes) { @@ -415,6 +524,45 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_NULL); } _DefGroup; + DefGroup(dbConfig){ + DefInt(SQLITE_DBCONFIG_MAINDBNAME); + DefInt(SQLITE_DBCONFIG_LOOKASIDE); + DefInt(SQLITE_DBCONFIG_ENABLE_FKEY); + DefInt(SQLITE_DBCONFIG_ENABLE_TRIGGER); + DefInt(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER); + DefInt(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION); + DefInt(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE); + DefInt(SQLITE_DBCONFIG_ENABLE_QPSG); + DefInt(SQLITE_DBCONFIG_TRIGGER_EQP); + DefInt(SQLITE_DBCONFIG_RESET_DATABASE); + DefInt(SQLITE_DBCONFIG_DEFENSIVE); + DefInt(SQLITE_DBCONFIG_WRITABLE_SCHEMA); + DefInt(SQLITE_DBCONFIG_LEGACY_ALTER_TABLE); + DefInt(SQLITE_DBCONFIG_DQS_DML); + DefInt(SQLITE_DBCONFIG_DQS_DDL); + DefInt(SQLITE_DBCONFIG_ENABLE_VIEW); + DefInt(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT); + DefInt(SQLITE_DBCONFIG_TRUSTED_SCHEMA); + DefInt(SQLITE_DBCONFIG_MAX); + } _DefGroup; + + DefGroup(dbStatus){ + DefInt(SQLITE_DBSTATUS_LOOKASIDE_USED); + DefInt(SQLITE_DBSTATUS_CACHE_USED); + DefInt(SQLITE_DBSTATUS_SCHEMA_USED); + DefInt(SQLITE_DBSTATUS_STMT_USED); + DefInt(SQLITE_DBSTATUS_LOOKASIDE_HIT); + DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE); + DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL); + DefInt(SQLITE_DBSTATUS_CACHE_HIT); + DefInt(SQLITE_DBSTATUS_CACHE_MISS); + DefInt(SQLITE_DBSTATUS_CACHE_WRITE); + DefInt(SQLITE_DBSTATUS_DEFERRED_FKS); + DefInt(SQLITE_DBSTATUS_CACHE_USED_SHARED); + DefInt(SQLITE_DBSTATUS_CACHE_SPILL); + DefInt(SQLITE_DBSTATUS_MAX); + } _DefGroup; + DefGroup(encodings) { /* Noting that the wasm binding only aims to support UTF-8. */ DefInt(SQLITE_UTF8); @@ -494,6 +642,34 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_IOCAP_BATCH_ATOMIC); } _DefGroup; + DefGroup(limits) { + DefInt(SQLITE_MAX_ALLOCATION_SIZE); + DefInt(SQLITE_LIMIT_LENGTH); + DefInt(SQLITE_MAX_LENGTH); + DefInt(SQLITE_LIMIT_SQL_LENGTH); + DefInt(SQLITE_MAX_SQL_LENGTH); + DefInt(SQLITE_LIMIT_COLUMN); + DefInt(SQLITE_MAX_COLUMN); + DefInt(SQLITE_LIMIT_EXPR_DEPTH); + DefInt(SQLITE_MAX_EXPR_DEPTH); + DefInt(SQLITE_LIMIT_COMPOUND_SELECT); + DefInt(SQLITE_MAX_COMPOUND_SELECT); + DefInt(SQLITE_LIMIT_VDBE_OP); + DefInt(SQLITE_MAX_VDBE_OP); + DefInt(SQLITE_LIMIT_FUNCTION_ARG); + DefInt(SQLITE_MAX_FUNCTION_ARG); + DefInt(SQLITE_LIMIT_ATTACHED); + DefInt(SQLITE_MAX_ATTACHED); + DefInt(SQLITE_LIMIT_LIKE_PATTERN_LENGTH); + DefInt(SQLITE_MAX_LIKE_PATTERN_LENGTH); + DefInt(SQLITE_LIMIT_VARIABLE_NUMBER); + DefInt(SQLITE_MAX_VARIABLE_NUMBER); + DefInt(SQLITE_LIMIT_TRIGGER_DEPTH); + DefInt(SQLITE_MAX_TRIGGER_DEPTH); + DefInt(SQLITE_LIMIT_WORKER_THREADS); + DefInt(SQLITE_MAX_WORKER_THREADS); + } _DefGroup; + DefGroup(openFlags) { /* Noting that not all of these will have any effect in ** WASM-space. */ @@ -644,6 +820,36 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_DESERIALIZE_RESIZEABLE); } _DefGroup; + DefGroup(session){ + DefInt(SQLITE_SESSION_CONFIG_STRMSIZE); + DefInt(SQLITE_SESSION_OBJCONFIG_SIZE); + } _DefGroup; + + DefGroup(sqlite3Status){ + DefInt(SQLITE_STATUS_MEMORY_USED); + DefInt(SQLITE_STATUS_PAGECACHE_USED); + DefInt(SQLITE_STATUS_PAGECACHE_OVERFLOW); + //DefInt(SQLITE_STATUS_SCRATCH_USED) /* NOT USED */; + //DefInt(SQLITE_STATUS_SCRATCH_OVERFLOW) /* NOT USED */; + DefInt(SQLITE_STATUS_MALLOC_SIZE); + DefInt(SQLITE_STATUS_PARSER_STACK); + DefInt(SQLITE_STATUS_PAGECACHE_SIZE); + //DefInt(SQLITE_STATUS_SCRATCH_SIZE) /* NOT USED */; + DefInt(SQLITE_STATUS_MALLOC_COUNT); + } _DefGroup; + + DefGroup(stmtStatus){ + DefInt(SQLITE_STMTSTATUS_FULLSCAN_STEP); + DefInt(SQLITE_STMTSTATUS_SORT); + DefInt(SQLITE_STMTSTATUS_AUTOINDEX); + DefInt(SQLITE_STMTSTATUS_VM_STEP); + DefInt(SQLITE_STMTSTATUS_REPREPARE); + DefInt(SQLITE_STMTSTATUS_RUN); + DefInt(SQLITE_STMTSTATUS_FILTER_MISS); + DefInt(SQLITE_STMTSTATUS_FILTER_HIT); + DefInt(SQLITE_STMTSTATUS_MEMUSED); + } _DefGroup; + DefGroup(syncFlags) { DefInt(SQLITE_SYNC_NORMAL); DefInt(SQLITE_SYNC_FULL); @@ -657,6 +863,12 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_TRACE_CLOSE); } _DefGroup; + DefGroup(txnState){ + DefInt(SQLITE_TXN_NONE); + DefInt(SQLITE_TXN_READ); + DefInt(SQLITE_TXN_WRITE); + } _DefGroup; + DefGroup(udfFlags) { DefInt(SQLITE_DETERMINISTIC); DefInt(SQLITE_DIRECTONLY); @@ -668,7 +880,36 @@ const char * sqlite3_wasm_enum_json(void){ DefStr(SQLITE_VERSION); DefStr(SQLITE_SOURCE_ID); } _DefGroup; - + + DefGroup(vtab) { + DefInt(SQLITE_INDEX_SCAN_UNIQUE); + DefInt(SQLITE_INDEX_CONSTRAINT_EQ); + DefInt(SQLITE_INDEX_CONSTRAINT_GT); + DefInt(SQLITE_INDEX_CONSTRAINT_LE); + DefInt(SQLITE_INDEX_CONSTRAINT_LT); + DefInt(SQLITE_INDEX_CONSTRAINT_GE); + DefInt(SQLITE_INDEX_CONSTRAINT_MATCH); + DefInt(SQLITE_INDEX_CONSTRAINT_LIKE); + DefInt(SQLITE_INDEX_CONSTRAINT_GLOB); + DefInt(SQLITE_INDEX_CONSTRAINT_REGEXP); + DefInt(SQLITE_INDEX_CONSTRAINT_NE); + DefInt(SQLITE_INDEX_CONSTRAINT_ISNOT); + DefInt(SQLITE_INDEX_CONSTRAINT_ISNOTNULL); + DefInt(SQLITE_INDEX_CONSTRAINT_ISNULL); + DefInt(SQLITE_INDEX_CONSTRAINT_IS); + DefInt(SQLITE_INDEX_CONSTRAINT_LIMIT); + DefInt(SQLITE_INDEX_CONSTRAINT_OFFSET); + DefInt(SQLITE_INDEX_CONSTRAINT_FUNCTION); + DefInt(SQLITE_VTAB_CONSTRAINT_SUPPORT); + DefInt(SQLITE_VTAB_INNOCUOUS); + DefInt(SQLITE_VTAB_DIRECTONLY); + DefInt(SQLITE_ROLLBACK); + //DefInt(SQLITE_IGNORE); // Also used by sqlite3_authorizer() callback + DefInt(SQLITE_FAIL); + //DefInt(SQLITE_ABORT); // Also an error code + DefInt(SQLITE_REPLACE); + } _DefGroup; + #undef DefGroup #undef DefStr #undef DefInt @@ -695,8 +936,8 @@ const char * sqlite3_wasm_enum_json(void){ /** Macros for emitting StructBinder description. */ #define StructBinder__(TYPE) \ n = 0; \ - outf("%s{", (nStruct++ ? ", " : "")); \ - out("\"name\": \"" # TYPE "\","); \ + outf("%s{", (nStruct++ ? ", " : "")); \ + out("\"name\": \"" # TYPE "\","); \ outf("\"sizeof\": %d", (int)sizeof(TYPE)); \ out(",\"members\": {"); #define StructBinder_(T) StructBinder__(T) @@ -716,78 +957,200 @@ const char * sqlite3_wasm_enum_json(void){ #define CurrentStruct sqlite3_vfs StructBinder { - M(iVersion,"i"); - M(szOsFile,"i"); - M(mxPathname,"i"); - M(pNext,"p"); - M(zName,"s"); - M(pAppData,"p"); - M(xOpen,"i(pppip)"); - M(xDelete,"i(ppi)"); - M(xAccess,"i(ppip)"); - M(xFullPathname,"i(ppip)"); - M(xDlOpen,"p(pp)"); - M(xDlError,"p(pip)"); - M(xDlSym,"p()"); - M(xDlClose,"v(pp)"); - M(xRandomness,"i(pip)"); - M(xSleep,"i(pi)"); - M(xCurrentTime,"i(pp)"); - M(xGetLastError,"i(pip)"); - M(xCurrentTimeInt64,"i(pp)"); - M(xSetSystemCall,"i(ppp)"); - M(xGetSystemCall,"p(pp)"); - M(xNextSystemCall,"p(pp)"); + M(iVersion, "i"); + M(szOsFile, "i"); + M(mxPathname, "i"); + M(pNext, "p"); + M(zName, "s"); + M(pAppData, "p"); + M(xOpen, "i(pppip)"); + M(xDelete, "i(ppi)"); + M(xAccess, "i(ppip)"); + M(xFullPathname, "i(ppip)"); + M(xDlOpen, "p(pp)"); + M(xDlError, "p(pip)"); + M(xDlSym, "p()"); + M(xDlClose, "v(pp)"); + M(xRandomness, "i(pip)"); + M(xSleep, "i(pi)"); + M(xCurrentTime, "i(pp)"); + M(xGetLastError, "i(pip)"); + M(xCurrentTimeInt64, "i(pp)"); + M(xSetSystemCall, "i(ppp)"); + M(xGetSystemCall, "p(pp)"); + M(xNextSystemCall, "p(pp)"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_io_methods StructBinder { - M(iVersion,"i"); - M(xClose,"i(p)"); - M(xRead,"i(ppij)"); - M(xWrite,"i(ppij)"); - M(xTruncate,"i(pj)"); - M(xSync,"i(pi)"); - M(xFileSize,"i(pp)"); - M(xLock,"i(pi)"); - M(xUnlock,"i(pi)"); - M(xCheckReservedLock,"i(pp)"); - M(xFileControl,"i(pip)"); - M(xSectorSize,"i(p)"); - M(xDeviceCharacteristics,"i(p)"); - M(xShmMap,"i(piiip)"); - M(xShmLock,"i(piii)"); - M(xShmBarrier,"v(p)"); - M(xShmUnmap,"i(pi)"); - M(xFetch,"i(pjip)"); - M(xUnfetch,"i(pjp)"); + M(iVersion, "i"); + M(xClose, "i(p)"); + M(xRead, "i(ppij)"); + M(xWrite, "i(ppij)"); + M(xTruncate, "i(pj)"); + M(xSync, "i(pi)"); + M(xFileSize, "i(pp)"); + M(xLock, "i(pi)"); + M(xUnlock, "i(pi)"); + M(xCheckReservedLock, "i(pp)"); + M(xFileControl, "i(pip)"); + M(xSectorSize, "i(p)"); + M(xDeviceCharacteristics, "i(p)"); + M(xShmMap, "i(piiip)"); + M(xShmLock, "i(piii)"); + M(xShmBarrier, "v(p)"); + M(xShmUnmap, "i(pi)"); + M(xFetch, "i(pjip)"); + M(xUnfetch, "i(pjp)"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_file StructBinder { - M(pMethods,"p"); + M(pMethods, "p"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_kvvfs_methods StructBinder { - M(xRead,"i(sspi)"); - M(xWrite,"i(sss)"); - M(xDelete,"i(ss)"); - M(nKeySize,"i"); + M(xRead, "i(sspi)"); + M(xWrite, "i(sss)"); + M(xDelete, "i(ss)"); + M(nKeySize, "i"); + } _StructBinder; +#undef CurrentStruct + + +#define CurrentStruct sqlite3_vtab + StructBinder { + M(pModule, "p"); + M(nRef, "i"); + M(zErrMsg, "p"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_vtab_cursor + StructBinder { + M(pVtab, "p"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_module + StructBinder { + M(iVersion, "i"); + M(xCreate, "i(ppippp)"); + M(xConnect, "i(ppippp)"); + M(xBestIndex, "i(pp)"); + M(xDisconnect, "i(p)"); + M(xDestroy, "i(p)"); + M(xOpen, "i(pp)"); + M(xClose, "i(p)"); + M(xFilter, "i(pisip)"); + M(xNext, "i(p)"); + M(xEof, "i(p)"); + M(xColumn, "i(ppi)"); + M(xRowid, "i(pp)"); + M(xUpdate, "i(pipp)"); + M(xBegin, "i(p)"); + M(xSync, "i(p)"); + M(xCommit, "i(p)"); + M(xRollback, "i(p)"); + M(xFindFunction, "i(pispp)"); + M(xRename, "i(ps)"); + // ^^^ v1. v2+ follows... + M(xSavepoint, "i(pi)"); + M(xRelease, "i(pi)"); + M(xRollbackTo, "i(pi)"); + // ^^^ v2. v3+ follows... + M(xShadowName, "i(s)"); + } _StructBinder; +#undef CurrentStruct + + /** + ** Workaround: in order to map the various inner structs from + ** sqlite3_index_info, we have to uplift those into constructs we + ** can access by type name. These structs _must_ match their + ** in-sqlite3_index_info counterparts byte for byte. + */ + typedef struct { + int iColumn; + unsigned char op; + unsigned char usable; + int iTermOffset; + } sqlite3_index_constraint; + typedef struct { + int iColumn; + unsigned char desc; + } sqlite3_index_orderby; + typedef struct { + int argvIndex; + unsigned char omit; + } sqlite3_index_constraint_usage; + { /* Validate that the above struct sizeof()s match + ** expectations. We could improve upon this by + ** checking the offsetof() for each member. */ + const sqlite3_index_info siiCheck; +#define IndexSzCheck(T,M) \ + (sizeof(T) == sizeof(*siiCheck.M)) + if(!IndexSzCheck(sqlite3_index_constraint,aConstraint) + || !IndexSzCheck(sqlite3_index_orderby,aOrderBy) + || !IndexSzCheck(sqlite3_index_constraint_usage,aConstraintUsage)){ + assert(!"sizeof mismatch in sqlite3_index_... struct(s)"); + return 0; + } +#undef IndexSzCheck + } + +#define CurrentStruct sqlite3_index_constraint + StructBinder { + M(iColumn, "i"); + M(op, "C"); + M(usable, "C"); + M(iTermOffset, "i"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_index_orderby + StructBinder { + M(iColumn, "i"); + M(desc, "C"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_index_constraint_usage + StructBinder { + M(argvIndex, "i"); + M(omit, "C"); + } _StructBinder; +#undef CurrentStruct + +#define CurrentStruct sqlite3_index_info + StructBinder { + M(nConstraint, "i"); + M(aConstraint, "p"); + M(nOrderBy, "i"); + M(aOrderBy, "p"); + M(aConstraintUsage, "p"); + M(idxNum, "i"); + M(idxStr, "p"); + M(needToFreeIdxStr, "i"); + M(orderByConsumed, "i"); + M(estimatedCost, "d"); + M(estimatedRows, "j"); + M(idxFlags, "i"); + M(colUsed, "j"); } _StructBinder; #undef CurrentStruct #if SQLITE_WASM_TESTS #define CurrentStruct WasmTestStruct StructBinder { - M(v4,"i"); - M(cstr,"s"); - M(ppV,"p"); - M(v8,"j"); - M(xFunc,"v(p)"); + M(v4, "i"); + M(cstr, "s"); + M(ppV, "p"); + M(v8, "j"); + M(xFunc, "v(p)"); } _StructBinder; #undef CurrentStruct #endif @@ -820,7 +1183,7 @@ const char * sqlite3_wasm_enum_json(void){ ** call is returned. */ SQLITE_WASM_KEEP -int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char * zName){ +int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ int rc = SQLITE_MISUSE /* ??? */; if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0); if( zName && pVfs && pVfs->xDelete ){ @@ -851,23 +1214,33 @@ sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ ** ** This function resets the given db pointer's database as described at ** -** https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase +** https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase +** +** But beware: virtual tables destroyed that way do not have their +** xDestroy() called, so will leak if they require that function for +** proper cleanup. ** ** Returns 0 on success, an SQLITE_xxx code on error. Returns ** SQLITE_MISUSE if pDb is NULL. */ SQLITE_WASM_KEEP -int sqlite3_wasm_db_reset(sqlite3*pDb){ +int sqlite3_wasm_db_reset(sqlite3 *pDb){ int rc = SQLITE_MISUSE; if( pDb ){ + sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0); rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); - if( 0==rc ) rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0); - sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); + if( 0==rc ){ + rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0); + sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); + } } return rc; } /* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** ** Uses the given database's VFS xRead to stream the db file's ** contents out to the given callback. The callback gets a single ** chunk of size n (its 2nd argument) on each call and must return 0 @@ -907,7 +1280,7 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb, } for( ; 0==rc && nPospMethods->xRead(pFile, buf, nBuf, nPos); - if(SQLITE_IOERR_SHORT_READ == rc){ + if( SQLITE_IOERR_SHORT_READ == rc ){ rc = (nPos + nBuf) < nSize ? rc : 0/*assume EOF*/; } if( 0==rc ) rc = xCallback(buf, nBuf); @@ -916,25 +1289,30 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb, } /* -** A proxy for sqlite3_serialize() which serializes the "main" schema +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** A proxy for sqlite3_serialize() which serializes the schema zSchema ** of pDb, placing the serialized output in pOut and nOut. nOut may be -** NULL. If pDb or pOut are NULL then SQLITE_MISUSE is returned. If -** allocation of the serialized copy fails, SQLITE_NOMEM is returned. -** On success, 0 is returned and `*pOut` will contain a pointer to the -** memory unless mFlags includes SQLITE_SERIALIZE_NOCOPY and the -** database has no contiguous memory representation, in which case -** `*pOut` will be NULL but 0 will be returned. +** NULL. If zSchema is NULL then "main" is assumed. If pDb or pOut are +** NULL then SQLITE_MISUSE is returned. If allocation of the +** serialized copy fails, SQLITE_NOMEM is returned. On success, 0 is +** returned and `*pOut` will contain a pointer to the memory unless +** mFlags includes SQLITE_SERIALIZE_NOCOPY and the database has no +** contiguous memory representation, in which case `*pOut` will be +** NULL but 0 will be returned. ** ** If `*pOut` is not NULL, the caller is responsible for passing it to ** sqlite3_free() to free it. */ SQLITE_WASM_KEEP -int sqlite3_wasm_db_serialize( sqlite3 *pDb, unsigned char **pOut, +int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, + unsigned char **pOut, sqlite3_int64 *nOut, unsigned int mFlags ){ unsigned char * z; if( !pDb || !pOut ) return SQLITE_MISUSE; - if(nOut) *nOut = 0; - z = sqlite3_serialize(pDb, "main", nOut, mFlags); + if( nOut ) *nOut = 0; + z = sqlite3_serialize(pDb, zSchema ? zSchema : "main", nOut, mFlags); if( z || (SQLITE_SERIALIZE_NOCOPY & mFlags) ){ *pOut = z; return 0; @@ -948,14 +1326,17 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, unsigned char **pOut, ** for use by the sqlite project's own JS/WASM bindings. ** ** Creates a new file using the I/O API of the given VFS, containing -** the given number of bytes of the given data. If the file exists, -** it is truncated to the given length and populated with the given +** the given number of bytes of the given data. If the file exists, it +** is truncated to the given length and populated with the given ** data. ** ** This function exists so that we can implement the equivalent of ** Emscripten's FS.createDataFile() in a VFS-agnostic way. This ** functionality is intended for use in uploading database files. ** +** Not all VFSes support this functionality, e.g. the "kvvfs" does +** not. +** ** If pVfs is NULL, sqlite3_vfs_find(0) is used. ** ** If zFile is NULL, pVfs is NULL (and sqlite3_vfs_find(0) returns @@ -967,10 +1348,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, unsigned char **pOut, ** ** Whether or not directory components of zFilename are created ** automatically or not is unspecified: that detail is left to the -** VFS. The "opfs" VFS, for example, create them. -** -** Not all VFSes support this functionality, e.g. the "kvvfs" does -** not. +** VFS. The "opfs" VFS, for example, creates them. ** ** If an error happens while populating or truncating the file, the ** target file will be deleted (if needed) if this function created @@ -1002,11 +1380,18 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, ** it may have a buffer limit related to sqlite3's pager size, we ** conservatively write in 512-byte blocks (smallest page ** size). */; - + //fprintf(stderr, "pVfs=%p, zFilename=%s, nData=%d\n", pVfs, zFilename, nData); if( !pVfs ) pVfs = sqlite3_vfs_find(0); if( !pVfs || !zFilename || nData<0 ) return SQLITE_MISUSE; pVfs->xAccess(pVfs, zFilename, SQLITE_ACCESS_EXISTS, &fileExisted); rc = sqlite3OsOpenMalloc(pVfs, zFilename, &pFile, openFlags, &flagsOut); +#if 0 +# define RC fprintf(stderr,"create_file(%s,%s) @%d rc=%d\n", \ + pVfs->zName, zFilename, __LINE__, rc); +#else +# define RC +#endif + RC; if(rc) return rc; pIo = pFile->pMethods; if( pIo->xLock ) { @@ -1017,26 +1402,37 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, ** xFileSize(), xTruncate(), and the like, and release the lock ** only if it was unlocked when the op was started. */ rc = pIo->xLock(pFile, SQLITE_LOCK_EXCLUSIVE); + RC; doUnlock = 0==rc; } - if( 0==rc) rc = pIo->xTruncate(pFile, nData); + if( 0==rc ){ + rc = pIo->xTruncate(pFile, nData); + RC; + } if( 0==rc && 0!=pData && nData>0 ){ while( 0==rc && nData>0 ){ const int n = nData>=blockSize ? blockSize : nData; rc = pIo->xWrite(pFile, pPos, n, (sqlite3_int64)(pPos - pData)); + RC; nData -= n; pPos += n; } if( 0==rc && nData>0 ){ assert( nDataxWrite(pFile, pPos, nData, (sqlite3_int64)(pPos - pData)); + rc = pIo->xWrite(pFile, pPos, nData, + (sqlite3_int64)(pPos - pData)); + RC; } } - if( pIo->xUnlock && doUnlock!=0 ) pIo->xUnlock(pFile, SQLITE_LOCK_NONE); + if( pIo->xUnlock && doUnlock!=0 ){ + pIo->xUnlock(pFile, SQLITE_LOCK_NONE); + } pIo->xClose(pFile); if( rc!=0 && 0==fileExisted ){ pVfs->xDelete(pVfs, zFilename, 1); } + RC; +#undef RC return rc; } @@ -1074,6 +1470,159 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** This is a proxy for the variadic sqlite3_vtab_config() which passes +** its argument on, or not, to sqlite3_vtab_config(), depending on the +** value of its 2nd argument. Returns the result of +** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a +** valid value. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ + switch(op){ + case SQLITE_VTAB_DIRECTONLY: + case SQLITE_VTAB_INNOCUOUS: + return sqlite3_vtab_config(pDb, op); + case SQLITE_VTAB_CONSTRAINT_SUPPORT: + return sqlite3_vtab_config(pDb, op, arg); + default: + return SQLITE_MISUSE; + } +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Wrapper for the variants of sqlite3_db_config() which take +** (int,int*) variadic args. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ + switch(op){ + case SQLITE_DBCONFIG_ENABLE_FKEY: + case SQLITE_DBCONFIG_ENABLE_TRIGGER: + case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case SQLITE_DBCONFIG_ENABLE_QPSG: + case SQLITE_DBCONFIG_TRIGGER_EQP: + case SQLITE_DBCONFIG_RESET_DATABASE: + case SQLITE_DBCONFIG_DEFENSIVE: + case SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case SQLITE_DBCONFIG_DQS_DML: + case SQLITE_DBCONFIG_DQS_DDL: + case SQLITE_DBCONFIG_ENABLE_VIEW: + case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case SQLITE_DBCONFIG_TRUSTED_SCHEMA: + return sqlite3_db_config(pDb, op, arg1, pArg2); + default: return SQLITE_MISUSE; + } +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Wrapper for the variants of sqlite3_db_config() which take +** (void*,int,int) variadic args. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){ + switch(op){ + case SQLITE_DBCONFIG_LOOKASIDE: + return sqlite3_db_config(pDb, op, pArg1, arg2, arg3); + default: return SQLITE_MISUSE; + } +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Wrapper for the variants of sqlite3_db_config() which take +** (const char *) variadic args. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ + switch(op){ + case SQLITE_DBCONFIG_MAINDBNAME: + return sqlite3_db_config(pDb, op, zArg); + default: return SQLITE_MISUSE; + } +} + + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Binding for combinations of sqlite3_config() arguments which take +** a single integer argument. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_config_i(int op, int arg){ + return sqlite3_config(op, arg); +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Binding for combinations of sqlite3_config() arguments which take +** two int arguments. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_config_ii(int op, int arg1, int arg2){ + return sqlite3_config(op, arg1, arg2); +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Binding for combinations of sqlite3_config() arguments which take +** a single i64 argument. +*/ +SQLITE_WASM_KEEP +int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){ + return sqlite3_config(op, arg); +} + +#if 0 +// Pending removal after verification of a workaround discussed in the +// forum post linked to below. +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Returns a pointer to sqlite3_free(). In compliant browsers the +** return value, when passed to sqlite3.wasm.exports.functionEntry(), +** must resolve to the same function as +** sqlite3.wasm.exports.sqlite3_free. i.e. from a dev console where +** sqlite3 is exported globally, the following must be true: +** +** ``` +** sqlite3.wasm.functionEntry( +** sqlite3.wasm.exports.sqlite3_wasm_ptr_to_sqlite3_free() +** ) === sqlite3.wasm.exports.sqlite3_free +** ``` +** +** Using a function to return this pointer, as opposed to exporting it +** via sqlite3_wasm_enum_json(), is an attempt to work around a +** Safari-specific quirk covered at +** https://sqlite.org/forum/info/e5b20e1feb37a19a. +**/ +SQLITE_WASM_KEEP +void * sqlite3_wasm_ptr_to_sqlite3_free(void){ + return (void*)sqlite3_free; +} +#endif + #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #include @@ -1133,6 +1682,11 @@ int sqlite3_wasm_test_intptr(int * p){ return *p = *p * 2; } +SQLITE_WASM_KEEP +void * sqlite3_wasm_test_voidptr(void * p){ + return p; +} + SQLITE_WASM_KEEP int64_t sqlite3_wasm_test_int64_max(void){ return (int64_t)0x7fffffffffffffff; @@ -1166,10 +1720,10 @@ void sqlite3_wasm_test_stack_overflow(int recurse){ if(recurse) sqlite3_wasm_test_stack_overflow(recurse); } -/* For testing the 'string-free' whwasmutil.xWrap() conversion. */ +/* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */ SQLITE_WASM_KEEP char * sqlite3_wasm_test_str_hello(int fail){ - char * s = fail ? 0 : (char *)malloc(6); + char * s = fail ? 0 : (char *)sqlite3_malloc(6); if(s){ memcpy(s, "hello", 5); s[5] = 0; diff --git a/ext/wasm/api/sqlite3-worker1-promiser.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js similarity index 97% rename from ext/wasm/api/sqlite3-worker1-promiser.js rename to ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index 7360512d4..0f1ae39ea 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -238,20 +238,24 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ }/*sqlite3Worker1Promiser()*/; self.sqlite3Worker1Promiser.defaultConfig = { worker: function(){ +//#if target=es6-bundler-friendly + return new Worker("sqlite3-worker1.js"); +//#else let theJs = "sqlite3-worker1.js"; if(this.currentScript){ const src = this.currentScript.src.split('/'); src.pop(); theJs = src.join('/')+'/' + theJs; - //console.warn("promiser currentScript, theJs =",this.currentScript,theJs); + //sqlite3.config.warn("promiser currentScript, theJs =",this.currentScript,theJs); }else{ - //console.warn("promiser self.location =",self.location); + //sqlite3.config.warn("promiser self.location =",self.location); const urlParams = new URL(self.location.href).searchParams; if(urlParams.has('sqlite3.dir')){ theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } } return new Worker(theJs + self.location.search); +//#endif }.bind({ currentScript: self?.document?.currentScript }), diff --git a/ext/wasm/api/sqlite3-worker1.js b/ext/wasm/api/sqlite3-worker1.c-pp.js similarity index 94% rename from ext/wasm/api/sqlite3-worker1.js rename to ext/wasm/api/sqlite3-worker1.c-pp.js index 942437908..9e9c3ac42 100644 --- a/ext/wasm/api/sqlite3-worker1.js +++ b/ext/wasm/api/sqlite3-worker1.c-pp.js @@ -33,6 +33,9 @@ */ "use strict"; (()=>{ +//#if target=es6-bundler-friendly + importScripts('sqlite3.js'); +//#else const urlParams = new URL(self.location.href).searchParams; let theJs = 'sqlite3.js'; if(urlParams.has('sqlite3.dir')){ @@ -40,10 +43,8 @@ } //console.warn("worker1 theJs =",theJs); importScripts(theJs); +//#endif sqlite3InitModule().then((sqlite3)=>{ - if(sqlite3.capi.sqlite3_wasmfs_opfs_dir){ - sqlite3.capi.sqlite3_wasmfs_opfs_dir(); - } sqlite3.initWorker1API(); }); })(); diff --git a/ext/wasm/batch-runner.js b/ext/wasm/batch-runner.js index 11c43217f..ff287a66e 100644 --- a/ext/wasm/batch-runner.js +++ b/ext/wasm/batch-runner.js @@ -227,20 +227,20 @@ const pSqlEnd = pSqlBegin + sqlByteLen; t = performance.now(); wasm.heap8().set(sql, pSql); - wasm.setMemValue(pSql + sqlByteLen, 0); + wasm.poke(pSql + sqlByteLen, 0); metrics.strcpy = performance.now() - t; let breaker = 0; - while(pSql && wasm.getMemValue(pSql,'i8')){ - wasm.setPtrValue(ppStmt, 0); - wasm.setPtrValue(pzTail, 0); + while(pSql && wasm.peek(pSql,'i8')){ + wasm.pokePtr(ppStmt, 0); + wasm.pokePtr(pzTail, 0); t = performance.now(); let rc = capi.sqlite3_prepare_v3( db.handle, pSql, sqlByteLen, 0, ppStmt, pzTail ); metrics.prepTotal += performance.now() - t; checkSqliteRc(db.handle, rc); - pStmt = wasm.getPtrValue(ppStmt); - pSql = wasm.getPtrValue(pzTail); + pStmt = wasm.peekPtr(ppStmt); + pSql = wasm.peekPtr(pzTail); sqlByteLen = pSqlEnd - pSql; if(!pStmt) continue/*empty statement*/; ++metrics.stmtCount; @@ -495,7 +495,7 @@ const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; const ppDb = wasm.scopedAllocPtr(); const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null); - pDb = wasm.getPtrValue(ppDb) + pDb = wasm.peekPtr(ppDb) if(rc) toss("sqlite3_open_v2() failed with code",rc); capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0); this.logHtml(dbId,"cache_size =",cacheSize); diff --git a/ext/wasm/c-pp.c b/ext/wasm/c-pp.c new file mode 100644 index 000000000..c439a0d09 --- /dev/null +++ b/ext/wasm/c-pp.c @@ -0,0 +1,1530 @@ +/* +** 2022-11-12: +** +** 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 C-minus Preprocessor: a truly minimal C-like preprocessor. +** Why? Because C preprocessors _can_ process non-C code but generally make +** quite a mess of it. The purpose of this application is an extremely +** minimal preprocessor with only the most basic functionality of a C +** preprocessor, namely: +** +** - Limited `#if`, where its one argument is a macro name which +** resolves to true if it's defined, false if it's not. Likewise, +** `#ifnot` is the inverse. Includes `#else` and `#elif` and +** `#elifnot`. Such chains are terminated with `#endif`. +** +** - `#define` accepts one or more arguments, the names of +** macros. Each one is implicitly true. +** +** - `#undef` undefine one or more macros. +** +** - `#error` treats the rest of the line as a fatal error message. +** +** - `#include` treats its argument as a filename token (NOT quoted, +** though support for quoting may be added later). Some effort is +** made to prevent recursive inclusion, but that support is both +** somewhat fragile and possibly completely unnecessary. +** +** - `#pragma` is in place for adding "meta-commands", but it does not +** yet have any concrete list of documented commands. +** +* - `#stderr` outputs its file name, line number, and the remaininder +** of that line to stderr. +** +** - `#//` acts as a single-line comment, noting that there must be as +** space after the `//` part because `//` is (despite appearances) +** parsed like a keyword. +** +** Note that "#" above is symbolic. The keyword delimiter is +** configurable and defaults to "##". Define CMPP_DEFAULT_DELIM to a +** string when compiling to define the default at build-time. +** +** This preprocessor does no expansion of content except within the +** bounds of its `#keywords`. +** +** Design note: this code makes use of sqlite3. Though not _strictly_ +** needed in order to implement it, this tool was specifically created +** for use with the sqlite3 project's own JavaScript code, so there's +** no reason not to make use of it to do some of the heavy lifting. It +** does not require any cutting-edge sqlite3 features and should be +** usable with any version which supports `WITHOUT ROWID`. +** +** Author(s): +** +** - Stephan Beal +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "sqlite3.h" + +#if defined(_WIN32) || defined(WIN32) +# include +# include +# ifndef access +# define access(f,m) _access((f),(m)) +# endif +#else +# include +#endif + +#ifndef CMPP_DEFAULT_DELIM +#define CMPP_DEFAULT_DELIM "##" +#endif + +#if 1 +# define CMPP_NORETURN __attribute__((noreturn)) +#else +# define CMPP_NORETURN +#endif + +/* Fatally exits the app with the given printf-style message. */ +static CMPP_NORETURN void fatalv(char const *zFmt, va_list); +static CMPP_NORETURN void fatal(char const *zFmt, ...); + +/** Proxy for free(), for symmetry with cmpp_realloc(). */ +static void cmpp_free(void *p); +/** A realloc() proxy which dies fatally on allocation error. */ +static void * cmpp_realloc(void * p, unsigned n); +#if 0 +/** A malloc() proxy which dies fatally on allocation error. */ +static void * cmpp_malloc(unsigned n); +#endif + +/* +** If p is stdin or stderr then this is a no-op, else it is a +** proxy for fclose(). This is a no-op if p is NULL. +*/ +static void FILE_close(FILE *p); +/* +** Works like fopen() but accepts the special name "-" to mean either +** stdin (if zMode indicates a real-only mode) or stdout. Fails +** fatally on error. +*/ +static FILE * FILE_open(char const *zName, const char * zMode); +/* +** Reads the entire contents of the given file, allocating it in a +** buffer which gets assigned to `*pOut`. `*nOut` gets assigned the +** length of the output buffer. Fails fatally on error. +*/ +static void FILE_slurp(FILE *pFile, unsigned char **pOut, + unsigned * nOut); + +/* +** Intended to be passed an sqlite3 result code. If it's non-0 +** then it emits a fatal error message which contains both the +** given string and the sqlite3_errmsg() from the application's +** database instance. +*/ +static void db_affirm_rc(int rc, const char * zMsg); + +/* +** Proxy for sqlite3_str_finish() which fails fatally if that +** routine returns NULL. +*/ +static char * db_str_finish(sqlite3_str *s, int * n); +/* +** Proxy for sqlite3_str_new() which fails fatally if that +** routine returns NULL. +*/ +static sqlite3_str * db_str_new(void); + +/* Proxy for sqlite3_finalize(). */ +static void db_finalize(sqlite3_stmt *pStmt); +/* +** Proxy for sqlite3_step() which fails fatally if the result +** is anything other than SQLITE_ROW or SQLITE_DONE. +*/ +static int db_step(sqlite3_stmt *pStmt); +/* +** Proxy for sqlite3_bind_int() which fails fatally on error. +*/ +static void db_bind_int(sqlite3_stmt *pStmt, int col, int val); +#if 0 +/* +** Proxy for sqlite3_bind_null() which fails fatally on error. +*/ +static void db_bind_null(sqlite3_stmt *pStmt, int col); +#endif +/* +** Proxy for sqlite3_bind_text() which fails fatally on error. +*/ +static void db_bind_text(sqlite3_stmt *pStmt, int col, const char * zStr); +/* +** Proxy for sqlite3_bind_text() which fails fatally on error. +*/ +static void db_bind_textn(sqlite3_stmt *pStmt, int col, const char * zStr, int len); +#if 0 +/* +** Proxy for sqlite3_bind_text() which fails fatally on error. It uses +** sqlite3_str_vappendf() so supports all of its formatting options. +*/ +static void db_bind_textv(sqlite3_stmt *pStmt, int col, const char * zFmt, ...); +#endif +/* +** Proxy for sqlite3_free(), to be passed any memory which is allocated +** by sqlite3_malloc(). +*/ +static void db_free(void *m); +/* +** Adds the given `#define` macro name to the list of macros, ignoring +** any duplicates. Fails fatally on error. +*/ +static void db_define_add(const char * zKey); +/* +** Returns true if the given key is already in the `#define` list, +** else false. Fails fatally on db error. +*/ +static int db_define_has(const char * zName); +/* +** Removes the given `#define` macro name from the list of +** macros. Fails fatally on error. +*/ +static void db_define_rm(const char * zKey); +/* +** Adds the given filename to the list of being-`#include`d files, +** using the given source file name and line number of error reporting +** purposes. If recursion is later detected. +*/ +static void db_including_add(const char * zKey, const char * zSrc, int srcLine); +/* +** Adds the given dir to the list of includes. They are checked in the +** order they are added. +*/ +static void db_include_dir_add(const char * zKey); +/* +** Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of +** the `#include` dirs (db_include_dir_add()). If no file match is +** found, NULL is returned. Memory must eventually be passed to +** db_free() to free it. +*/ +static char * db_include_search(const char * zKey); +/* +** Removes the given key from the `#include` list. +*/ +static void db_include_rm(const char * zKey); +/* +** A proxy for sqlite3_prepare() which fails fatally on error. +*/ +static void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...); + +/* +** Opens the given file and processes its contents as c-pp, sending +** all output to the global c-pp output channel. Fails fatally on +** error. +*/ +static void cmpp_process_file(const char * zName); + +/* +** Returns the number newline characters between the given starting +** point and inclusive ending point. Results are undefined if zFrom is +** greater than zTo. +*/ +static unsigned count_lines(unsigned char const * zFrom, + unsigned char const *zTo); + +/* +** Wrapper around a FILE handle. +*/ +struct FileWrapper { + /* File's name. */ + char const *zName; + /* FILE handle. */ + FILE * pFile; + /* Where FileWrapper_slurp() stores the file's contents. */ + unsigned char * zContent; + /* Size of this->zContent, as set by FileWrapper_slurp(). */ + unsigned nContent; +}; +typedef struct FileWrapper FileWrapper; +#define FileWrapper_empty_m {0,0,0,0} +static const FileWrapper FileWrapper_empty = FileWrapper_empty_m; + +/* Proxy for FILE_close(). */ +static void FileWrapper_close(FileWrapper * p); +/* Proxy for FILE_open(). */ +static void FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode); +/* Proxy for FILE_slurp(). */ +static void FileWrapper_slurp(FileWrapper * p); + +/* +** Outputs a printf()-formatted message to stderr. +*/ +static void g_stderr(char const *zFmt, ...); +/* +** Outputs a printf()-formatted message to stderr. +*/ +static void g_stderrv(char const *zFmt, va_list); +#define g_debug(lvl,pfexpr) \ + if(lvl<=g.doDebug) g_stderr("%s @ %s:%d: ",g.zArgv0,__FILE__,__LINE__); \ + if(lvl<=g.doDebug) g_stderr pfexpr + +void fatalv(char const *zFmt, va_list va){ + if(zFmt && *zFmt){ + vfprintf(stderr, zFmt, va); + } + fputc('\n', stderr); + exit(1); +} + +void fatal(char const *zFmt, ...){ + va_list va; + va_start(va, zFmt); + fatalv(zFmt, va); + va_end(va); +} + +void cmpp_free(void *p){ + free(p); +} + +void * cmpp_realloc(void * p, unsigned n){ + void * const rc = realloc(p, n); + if(!rc) fatal("realloc(P,%u) failed", n); + return rc; +} + +#if 0 +void * cmpp_malloc(unsigned n){ + void * const rc = malloc(n); + if(!rc) fatal("malloc(%u) failed", n); + return rc; +} +#endif + +FILE * FILE_open(char const *zName, const char * zMode){ + FILE * p; + if('-'==zName[0] && 0==zName[1]){ + p = strstr(zMode,"w") ? stdout : stdin; + }else{ + p = fopen(zName, zMode); + if(!p) fatal("Cannot open file [%s] with mode [%s]", zName, zMode); + } + return p; +} + +void FILE_close(FILE *p){ + if(p && p!=stdout && p!=stderr){ + fclose(p); + } +} + +void FILE_slurp(FILE *pFile, unsigned char **pOut, + unsigned * nOut){ + unsigned char zBuf[1024 * 8]; + unsigned char * pDest = 0; + unsigned nAlloc = 0; + unsigned nOff = 0; + /* Note that this needs to be able to work on non-seekable streams, + ** thus we read in chunks instead of doing a single alloc and + ** filling it in one go. */ + while( !feof(pFile) ){ + size_t const n = fread(zBuf, 1, sizeof(zBuf), pFile); + if(n>0){ + if(nAlloc < nOff + n + 1){ + nAlloc = nOff + n + 1; + pDest = cmpp_realloc(pDest, nAlloc); + } + memcpy(pDest + nOff, zBuf, n); + nOff += n; + } + } + if(pDest) pDest[nOff] = 0; + *pOut = pDest; + *nOut = nOff; +} + +void FileWrapper_close(FileWrapper * p){ + if(p->pFile) FILE_close(p->pFile); + if(p->zContent) cmpp_free(p->zContent); + *p = FileWrapper_empty; +} + +void FileWrapper_open(FileWrapper * p, const char * zName, + const char * zMode){ + FileWrapper_close(p); + p->pFile = FILE_open(zName, zMode); + p->zName = zName; +} + +void FileWrapper_slurp(FileWrapper * p){ + assert(!p->zContent); + assert(p->pFile); + FILE_slurp(p->pFile, &p->zContent, &p->nContent); +} + +unsigned count_lines(unsigned char const * zFrom, unsigned char const *zTo){ + unsigned ln = 0; + unsigned char const *zPos = zFrom; + assert(zFrom && zTo); + assert(zFrom <= zTo); + for(; zPos < zTo; ++zPos){ + switch(*zPos){ + case (unsigned)'\n': ++ln; break; + default: break; + } + } + return ln; +} + +enum CmppParseState { +TS_Start = 1, +TS_If, +TS_IfPassed, +TS_Else, +TS_Error +}; +typedef enum CmppParseState CmppParseState; +enum CmppTokenType { +TT_Invalid = 0, +TT_Comment, +TT_Define, +TT_Elif, +TT_ElifNot, +TT_Else, +TT_EndIf, +TT_Error, +TT_If, +TT_IfNot, +TT_Include, +TT_Line, +TT_Pragma, +TT_Stderr, +TT_Undef +}; +typedef enum CmppTokenType CmppTokenType; + +struct CmppToken { + CmppTokenType ttype; + /* Line number of this token in the source file. */ + unsigned lineNo; + /* Start of the token. */ + unsigned char const * zBegin; + /* One-past-the-end byte of the token. */ + unsigned char const * zEnd; +}; +typedef struct CmppToken CmppToken; +#define CmppToken_empty_m {TT_Invalid,0,0,0} +static const CmppToken CmppToken_empty = CmppToken_empty_m; + +/* +** CmppLevel represents one "level" of tokenization, starting at the +** top of the main input, incrementing once for each level of `#if`, +** and decrementing for each `#endif`. +*/ +typedef struct CmppLevel CmppLevel; +struct CmppLevel { + unsigned short flags; + /* + ** Used for controlling which parts of an if/elif/...endif chain + ** should get output. + */ + unsigned short skipLevel; + /* The token which started this level (an 'if' or 'ifnot'). */ + CmppToken token; + CmppParseState pstate; +}; +#define CmppLevel_empty_m {0U,0U,CmppToken_empty_m,TS_Start} +static const CmppLevel CmppLevel_empty = CmppLevel_empty_m; +enum CmppLevel_Flags { +/* Max depth of nested `#if` constructs in a single tokenizer. */ +CmppLevel_Max = 10, +/* Max number of keyword arguments. */ +CmppArgs_Max = 10, +/* Flag indicating that output for a CmpLevel should be elided. */ +CmppLevel_F_ELIDE = 0x01, +/* +** Mask of CmppLevel::flags which are inherited when CmppLevel_push() +** is used. +*/ +CmppLevel_F_INHERIT_MASK = 0x01 +}; + +typedef struct CmppTokenizer CmppTokenizer; +typedef struct CmppKeyword CmppKeyword; +typedef void (*cmpp_keyword_f)(CmppKeyword const * pKw, CmppTokenizer * t); +struct CmppKeyword { + const char *zName; + unsigned nName; + int bTokenize; + CmppTokenType ttype; + cmpp_keyword_f xCall; +}; + +static CmppKeyword const * CmppKeyword_search(const char *zName); +static void cmpp_process_keyword(CmppTokenizer * const t); + +/* +** Tokenizer for c-pp input files. +*/ +struct CmppTokenizer { + const char * zName; /* Input (file) name for error reporting */ + unsigned const char * zBegin; /* start of input */ + unsigned const char * zEnd; /* one-after-the-end of input */ + unsigned const char * zAnchor; /* start of input or end point of + previous token */ + unsigned const char * zPos; /* current position */ + unsigned int lineNo; /* line # of current pos */ + CmppParseState pstate; + CmppToken token; /* current token result */ + struct { + unsigned ndx; + CmppLevel stack[CmppLevel_Max]; + } level; + /* Args for use in cmpp_keyword_f() impls. */ + struct { + CmppKeyword const * pKw; + int argc; + const unsigned char * argv[CmppArgs_Max]; + unsigned char lineBuf[1024]; + } args; +}; +#define CT_level(t) (t)->level.stack[(t)->level.ndx] +#define CT_pstate(t) CT_level(t).pstate +#define CT_skipLevel(t) CT_level(t).skipLevel +#define CLvl_skip(lvl) ((lvl)->skipLevel || ((lvl)->flags & CmppLevel_F_ELIDE)) +#define CT_skip(t) CLvl_skip(&CT_level(t)) +#define CmppTokenizer_empty_m { \ + 0,0,0,0,0,1U/*lineNo*/, \ + TS_Start, \ + CmppToken_empty_m, \ + {/*level*/0U,{CmppLevel_empty_m}}, \ + {/*args*/0,0,{0},{0}} \ + } +static const CmppTokenizer CmppTokenizer_empty = CmppTokenizer_empty_m; + +static void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n); +/*static void cmpp_t_outf(CmppTokenizer * t, char const *zFmt, ...);*/ + +/* +** Pushes a new level into the given tokenizer. Fails fatally if +** it's too deep. +*/ +static void CmppLevel_push(CmppTokenizer * const t); +/* +** Pops a level from the tokenizer. Fails fatally if the top +** level is popped. +*/ +static void CmppLevel_pop(CmppTokenizer * const t); +/* +** Returns the current level object. +*/ +static CmppLevel * CmppLevel_get(CmppTokenizer * const t); + +/* +** Global app state singleton. */ +static struct Global { + /* main()'s argv[0]. */ + const char * zArgv0; + /* + ** Bytes of the keyword delimiter/prefix. Owned + ** elsewhere. + */ + const char * zDelim; + /* Byte length of this->zDelim. */ + unsigned short nDelim; + /* If true, enables certain debugging output. */ + int doDebug; + /* App's db instance. */ + sqlite3 * db; + /* Output channel. */ + FileWrapper out; + struct { + sqlite3_stmt * defIns; + sqlite3_stmt * defDel; + sqlite3_stmt * defHas; + sqlite3_stmt * inclIns; + sqlite3_stmt * inclDel; + sqlite3_stmt * inclHas; + sqlite3_stmt * inclPathAdd; + sqlite3_stmt * inclSearch; + } stmt; +} g = { +"?", +CMPP_DEFAULT_DELIM/*zDelim*/, +(unsigned short) sizeof(CMPP_DEFAULT_DELIM)-1/*nDelim*/, +0/*doDebug*/, +0/*db*/, +FileWrapper_empty_m/*out*/, +{/*stmt*/ + 0/*defIns*/, 0/*defDel*/, 0/*defHas*/, + 0/*inclIns*/, 0/*inclDel*/, 0/*inclHas*/, + 0/*inclPathAdd*/ +} +}; + + +#if 0 +/* +** Outputs a printf()-formatted message to c-pp's global output +** channel. +*/ +static void g_outf(char const *zFmt, ...); +void g_outf(char const *zFmt, ...){ + va_list va; + va_start(va, zFmt); + vfprintf(g.out.pFile, zFmt, va); + va_end(va); +} +#endif + +#if 0 +/* Outputs n bytes from z to c-pp's global output channel. */ +static void g_out(void const *z, unsigned int n); +void g_out(void const *z, unsigned int n){ + if(1!=fwrite(z, n, 1, g.out.pFile)){ + int const err = errno; + fatal("fwrite() output failed with errno #%d", err); + } +} +#endif + +void g_stderrv(char const *zFmt, va_list va){ + vfprintf(stderr, zFmt, va); +} + +void g_stderr(char const *zFmt, ...){ + va_list va; + va_start(va, zFmt); + g_stderrv(zFmt, va); + va_end(va); +} + +void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n){ + g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t))); + g_debug(3,("CT_skip() ?= %d\n",CT_skip(t))); + if(!CT_skip(t)){ + if(1!=fwrite(z, n, 1, g.out.pFile)){ + int const err = errno; + fatal("fwrite() output failed with errno #%d", err); + } + } +} + +void CmppLevel_push(CmppTokenizer * const t){ + CmppLevel * pPrev; + CmppLevel * p; + if(t->level.ndx+1 == (unsigned)CmppLevel_Max){ + fatal("%sif nesting level is too deep. Max=%d\n", + g.zDelim, CmppLevel_Max); + } + pPrev = &CT_level(t); + g_debug(3,("push from tokenizer level=%u flags=%04x\n", t->level.ndx, pPrev->flags)); + p = &t->level.stack[++t->level.ndx]; + *p = CmppLevel_empty; + p->token = t->token; + p->flags = (CmppLevel_F_INHERIT_MASK & pPrev->flags); + if(CLvl_skip(pPrev)) p->flags |= CmppLevel_F_ELIDE; + g_debug(3,("push to tokenizer level=%u flags=%04x\n", t->level.ndx, p->flags)); +} + +void CmppLevel_pop(CmppTokenizer * const t){ + if(!t->level.ndx){ + fatal("Internal error: CmppLevel_pop() at the top of the stack"); + } + g_debug(3,("pop from tokenizer level=%u, flags=%04x skipLevel?=%d\n", t->level.ndx, + t->level.stack[t->level.ndx].flags, CT_skipLevel(t))); + g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t))); + g_debug(3,("CT_skip() ?= %d\n",CT_skip(t))); + t->level.stack[t->level.ndx--] = CmppLevel_empty; + g_debug(3,("pop to tokenizer level=%u, flags=%04x\n", t->level.ndx, + t->level.stack[t->level.ndx].flags)); + g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t))); + g_debug(3,("CT_skip() ?= %d\n",CT_skip(t))); +} + +CmppLevel * CmppLevel_get(CmppTokenizer * const t){ + return &t->level.stack[t->level.ndx]; +} + + +void db_affirm_rc(int rc, const char * zMsg){ + if(rc){ + fatal("Db error #%d %s: %s", rc, zMsg, sqlite3_errmsg(g.db)); + } +} + +void db_finalize(sqlite3_stmt *pStmt){ + sqlite3_finalize(pStmt); +} + +int db_step(sqlite3_stmt *pStmt){ + int const rc = sqlite3_step(pStmt); + if(SQLITE_ROW!=rc && SQLITE_DONE!=rc){ + db_affirm_rc(rc, "from db_step()"); + } + return rc; +} + +static sqlite3_str * db_str_new(void){ + sqlite3_str * rc = sqlite3_str_new(g.db); + if(!rc) fatal("Alloc failed for sqlite3_str_new()"); + return rc; +} + +static char * db_str_finish(sqlite3_str *s, int * n){ + int const rc = sqlite3_str_errcode(s); + if(rc) fatal("Error #%d from sqlite3_str_errcode()", rc); + if(n) *n = sqlite3_str_length(s); + char * z = sqlite3_str_finish(s); + if(!z) fatal("Alloc failed for sqlite3_str_new()"); + return z; +} + +void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...){ + int rc; + sqlite3_str * str = db_str_new(); + char * z = 0; + int n = 0; + va_list va; + if(!str) fatal("sqlite3_str_new() failed"); + va_start(va, zSql); + sqlite3_str_vappendf(str, zSql, va); + va_end(va); + rc = sqlite3_str_errcode(str); + if(rc) fatal("sqlite3_str_errcode() = %d", rc); + z = db_str_finish(str, &n); + rc = sqlite3_prepare_v2(g.db, z, n, pStmt, 0); + if(rc) fatal("Error #%d (%s) preparing: %s", + rc, sqlite3_errmsg(g.db), z); + sqlite3_free(z); +} + +void db_bind_int(sqlite3_stmt *pStmt, int col, int val){ + int const rc = sqlite3_bind_int(pStmt, col, val); + db_affirm_rc(rc,"from db_bind_int()"); +} + +#if 0 +void db_bind_null(sqlite3_stmt *pStmt, int col){ + int const rc = sqlite3_bind_null(pStmt, col); + db_affirm_rc(rc,"from db_bind_null()"); +} +#endif + +void db_bind_textn(sqlite3_stmt *pStmt, int col, + const char * zStr, int n){ + int const rc = zStr + ? sqlite3_bind_text(pStmt, col, zStr, n, SQLITE_TRANSIENT) + : sqlite3_bind_null(pStmt, col); + db_affirm_rc(rc,"from db_bind_textn()"); +} + +void db_bind_text(sqlite3_stmt *pStmt, int col, + const char * zStr){ + db_bind_textn(pStmt, col, zStr, -1); +} + +#if 0 +void db_bind_textv(sqlite3_stmt *pStmt, int col, + const char * zFmt, ...){ + int rc; + sqlite3_str * str = db_str_new(); + int n = 0; + char * z; + va_list va; + va_start(va,zFmt); + sqlite3_str_vappendf(str, zFmt, va); + va_end(va); + z = db_str_finish(str, &n); + rc = sqlite3_bind_text(pStmt, col, z, n, sqlite3_free); + db_affirm_rc(rc,"from db_bind_textv()"); +} +#endif + +void db_free(void *m){ + sqlite3_free(m); +} + +void db_define_add(const char * zKey){ + int rc; + if(!g.stmt.defIns){ + db_prepare(&g.stmt.defIns, + "INSERT OR REPLACE INTO def(k) VALUES(?)"); + } + db_bind_text(g.stmt.defIns, 1, zKey); + rc = db_step(g.stmt.defIns); + if(SQLITE_DONE != rc){ + db_affirm_rc(rc, "Stepping INSERT on def"); + } + g_debug(2,("define: %s\n",zKey)); + sqlite3_reset(g.stmt.defIns); +} + +int db_define_has(const char * zName){ + int rc; + if(!g.stmt.defHas){ + db_prepare(&g.stmt.defHas, "SELECT 1 FROM def WHERE k=?"); + } + db_bind_text(g.stmt.defHas, 1, zName); + rc = db_step(g.stmt.defHas); + if(SQLITE_ROW == rc){ + rc = 1; + }else{ + assert(SQLITE_DONE==rc); + rc = 0; + } + g_debug(1,("defined [%s] ?= %d\n",zName, rc)); + sqlite3_clear_bindings(g.stmt.defHas); + sqlite3_reset(g.stmt.defHas); + return rc; +} + + +void db_define_rm(const char * zKey){ + int rc; + int n = 0; + const char *zPos = zKey; + if(!g.stmt.defDel){ + db_prepare(&g.stmt.defDel, "DELETE FROM def WHERE k=?"); + } + for( ; *zPos && '='!=*zPos; ++n, ++zPos) {} + db_bind_text(g.stmt.defDel, 1, zKey); + rc = db_step(g.stmt.defDel); + if(SQLITE_DONE != rc){ + db_affirm_rc(rc, "Stepping DELETE on def"); + } + g_debug(2,("undefine: %.*s\n",n, zKey)); + sqlite3_clear_bindings(g.stmt.defDel); + sqlite3_reset(g.stmt.defDel); +} + +void db_including_add(const char * zKey, const char * zSrc, int srcLine){ + int rc; + if(!g.stmt.inclIns){ + db_prepare(&g.stmt.inclIns, + "INSERT OR FAIL INTO incl(file,srcFile,srcLine) VALUES(?,?,?)"); + } + db_bind_text(g.stmt.inclIns, 1, zKey); + db_bind_text(g.stmt.inclIns, 2, zSrc); + db_bind_int(g.stmt.inclIns, 3, srcLine); + rc = db_step(g.stmt.inclIns); + if(SQLITE_DONE != rc){ + db_affirm_rc(rc, "Stepping INSERT on incl"); + } + g_debug(2,("inclpath add [%s] from [%s]:%d\n", zKey, zSrc, srcLine)); + sqlite3_clear_bindings(g.stmt.inclIns); + sqlite3_reset(g.stmt.inclIns); +} + +void db_include_rm(const char * zKey){ + int rc; + if(!g.stmt.inclDel){ + db_prepare(&g.stmt.inclDel, "DELETE FROM incl WHERE file=?"); + } + db_bind_text(g.stmt.inclDel, 1, zKey); + rc = db_step(g.stmt.inclDel); + if(SQLITE_DONE != rc){ + db_affirm_rc(rc, "Stepping DELETE on incl"); + } + g_debug(2,("inclpath rm [%s]\n", zKey)); + sqlite3_clear_bindings(g.stmt.inclDel); + sqlite3_reset(g.stmt.inclDel); +} + +char * db_include_search(const char * zKey){ + char * zName = 0; + if(!g.stmt.inclSearch){ + db_prepare(&g.stmt.inclSearch, + "SELECT ?1 fn WHERE fileExists(fn) " + "UNION ALL SELECT * FROM (" + "SELECT replace(dir||'/'||?1, '//','/') AS fn " + "FROM inclpath WHERE fileExists(fn) ORDER BY seq" + ")"); + } + db_bind_text(g.stmt.inclSearch, 1, zKey); + if(SQLITE_ROW==db_step(g.stmt.inclSearch)){ + const unsigned char * z = sqlite3_column_text(g.stmt.inclSearch, 0); + zName = z ? sqlite3_mprintf("%s", z) : 0; + if(!zName) fatal("Alloc failed"); + } + sqlite3_clear_bindings(g.stmt.inclSearch); + sqlite3_reset(g.stmt.inclSearch); + return zName; +} + +static int db_including_has(const char * zName){ + int rc; + if(!g.stmt.inclHas){ + db_prepare(&g.stmt.inclHas, "SELECT 1 FROM incl WHERE file=?"); + } + db_bind_text(g.stmt.inclHas, 1, zName); + rc = db_step(g.stmt.inclHas); + if(SQLITE_ROW == rc){ + rc = 1; + }else{ + assert(SQLITE_DONE==rc); + rc = 0; + } + g_debug(2,("inclpath has [%s] = %d\n",zName, rc)); + sqlite3_clear_bindings(g.stmt.inclHas); + sqlite3_reset(g.stmt.inclHas); + return rc; +} + +#if 0 +/* +** Fails fatally if the `#include` list contains the given key. +*/ +static void db_including_check(const char * zKey); +void db_including_check(const char * zName){ + if(db_including_has(zName)){ + fatal("Recursive include detected: %s\n", zName); + } +} +#endif + +void db_include_dir_add(const char * zDir){ + static int seq = 0; + int rc; + if(!g.stmt.inclPathAdd){ + db_prepare(&g.stmt.inclPathAdd, + "INSERT OR FAIL INTO inclpath(seq,dir) VALUES(?,?)"); + } + db_bind_int(g.stmt.inclPathAdd, 1, ++seq); + db_bind_text(g.stmt.inclPathAdd, 2, zDir); + rc = db_step(g.stmt.inclPathAdd); + if(SQLITE_DONE != rc){ + db_affirm_rc(rc, "Stepping INSERT on inclpath"); + } + g_debug(2,("inclpath add #%d: %s\n",seq, zDir)); + sqlite3_clear_bindings(g.stmt.inclPathAdd); + sqlite3_reset(g.stmt.inclPathAdd); +} + +static void cmpp_atexit(void){ +#define FINI(M) if(g.stmt.M) sqlite3_finalize(g.stmt.M) + FINI(defIns); FINI(defDel); FINI(defHas); + FINI(inclIns); FINI(inclDel); FINI(inclHas); + FINI(inclPathAdd); FINI(inclSearch); +#undef FINI + FileWrapper_close(&g.out); + if(g.db) sqlite3_close(g.db); +} + +/* +** sqlite3 UDF which returns true if its argument refers to an +** accessible file, else false. +*/ +static void udf_file_exists( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName; + (void)(argc); /* Unused parameter */ + zName = (const char*)sqlite3_value_text(argv[0]); + if( zName==0 ) return; + sqlite3_result_int(context, 0==access(zName, 0)); +} + +/* Initialize g.db, failing fatally on error. */ +static void cmpp_initdb(void){ + int rc; + char * zErr = 0; + const char * zSchema = + "CREATE TABLE def(" + "k TEXT PRIMARY KEY NOT NULL" + /*"v INTEGER DEFAULT 1"*/ + ") WITHOUT ROWID;" + /* ^^^ defines */ + "CREATE TABLE incl(" + "file TEXT PRIMARY KEY NOT NULL," + "srcFile TEXT DEFAULT NULL," + "srcLine INTEGER DEFAULT 0" + ") WITHOUT ROWID;" + /* ^^^ files currently being included */ + "CREATE TABLE inclpath(" + "seq INTEGER UNIQUE, " + "dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE" + ")" + /* ^^^ include path */ + ; + assert(0==g.db); + if(g.db) return; + rc = sqlite3_open_v2(":memory:", &g.db, SQLITE_OPEN_READWRITE, 0); + if(rc) fatal("Error opening :memory: db."); + rc = sqlite3_exec(g.db, zSchema, 0, 0, &zErr); + if(rc) fatal("Error initializing database: %s", zErr); + rc = sqlite3_create_function(g.db, "fileExists", 1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + udf_file_exists, 0, 0); + db_affirm_rc(rc, "UDF registration failed."); +} + +/* +** For position zPos, which must be in the half-open range +** [zBegin,zEnd), returns g.nDelim if it is at the start of a line and +** starts with g.zDelim, else returns 0. +*/ +static unsigned short cmpp_is_delim(unsigned char const *zBegin, + unsigned char const *zEnd, + unsigned char const *zPos){ + assert(zEnd>zBegin); + assert(zPos=zBegin); + if(zPos>zBegin && + ('\n'!=*(zPos - 1) + || ((unsigned)(zEnd - zPos) <= g.nDelim))){ + return 0; + }else if(0==memcmp(zPos, g.zDelim, g.nDelim)){ + return g.nDelim; + }else{ + return 0; + } +} + +/* +** Scans t to the next keyword line, emitting all input before that +** which is _not_ a keyword line unless it's elided due to being +** inside a block which elides its content. Returns 0 if no keyword +** line was found, in which case the end of the input has been +** reached, else returns a truthy value and sets up t's state for use +** with cmpp_process_keyword(), which should then be called. +*/ +static int cmpp_next_keyword_line(CmppTokenizer * const t){ + unsigned char const * zStart; + unsigned char const * z; + CmppToken * const tok = &t->token; + unsigned short isDelim = 0; + + assert(t->zBegin); + assert(t->zEnd > t->zBegin); + if(!t->zPos) t->zPos = t->zBegin; + t->zAnchor = t->zPos; + zStart = z = t->zPos; + *tok = CmppToken_empty; + while(zzEnd + && 0==(isDelim = cmpp_is_delim(t->zBegin, t->zEnd, z))){ + ++z; + } + if(z>zStart){ + /* We passed up content */ + cmpp_t_out(t, zStart, (unsigned)(z - zStart)); + } + assert(isDelim==0 || isDelim==g.nDelim); + tok->lineNo = t->lineNo += count_lines(zStart, z); + if(isDelim){ + /* Handle backslash-escaped newlines */ + int isEsc = 0, atEol = 0; + tok->zBegin = z+isDelim; + for( ++z ; zzEnd && 0==atEol; ++z ){ + switch((int)*z){ + case (int)'\\': + isEsc = 0==isEsc; break; + case (int)'\n': + atEol = 0==isEsc; + isEsc = 0; + ++t->lineNo; + break; + default: + break; + } + } + tok->zEnd = atEol ? z-1 : z; + /* Strip leading spaces */ + while(tok->zBegin < tok->zEnd && isspace((char)(*tok->zBegin))){ + ++tok->zBegin; + } + tok->ttype = TT_Line; + g_debug(2,("Keyword @ line %u: [[[%.*s]]]\n", + tok->lineNo, + (int)(tok->zEnd-tok->zBegin), tok->zBegin)); + } + t->zPos = z; + if(isDelim){ + /* Split t->token into arguments for the line's keyword */ + int i, argc = 0, prevChar = 0; + const unsigned tokLen = (unsigned)(tok->zEnd - tok->zBegin); + unsigned char * zKwd; + unsigned char * zEsc; + unsigned char * zz; + + assert(TT_Line==tok->ttype); + if((unsigned)sizeof(t->args.lineBuf) < tokLen + 1){ + fatal("Keyword line is unreasonably long: %.*s", + tokLen, tok->zBegin); + }else if(!tokLen){ + fatal("Line #%u has no keyword after delimiter", tok->lineNo); + } + g_debug(2,("token @ line %u len=%u [[[%.*s]]]\n", + tok->lineNo, tokLen, tokLen, tok->zBegin)); + zKwd = &t->args.lineBuf[0]; + memcpy(zKwd, tok->zBegin, tokLen); + memset(zKwd + tokLen, 0, sizeof(t->args.lineBuf) - tokLen); + for( zEsc = 0, zz = zKwd; *zz; ++zz ){ + /* Convert backslash-escaped newlines to whitespace */ + switch((int)*zz){ + case (int)'\\': + if(zEsc) zEsc = 0; + else zEsc = zz; + break; + case (int)'\n': + assert(zEsc && "Should not have an unescaped newline?"); + if(zEsc==zz-1){ + *zEsc = (unsigned char)' '; + /* FIXME?: memmove() lnBuf content one byte to the left here + ** to collapse backslash and newline into a single + ** byte. Also consider collapsing all leading space on the + ** next line. */ + } + zEsc = 0; + *zz = (unsigned char)' '; + break; + default: + zEsc = 0; + break; + } + } + t->args.argv[argc++] = zKwd; + for( zz = zKwd; *zz; ++zz ){ + if(isspace(*zz)){ + *zz = 0; + break; + } + } + t->args.pKw = CmppKeyword_search((char const *)zKwd); + if(!t->args.pKw){ + fatal("Unknown keyword '%s' at line %u\n", (char const *)zKwd, + tok->lineNo); + } + for( ++zz ; *zz && isspace(*zz); ++zz ){} + if(t->args.pKw->bTokenize){ + for( ; *zz; prevChar = *zz, ++zz ){ + /* Split string into word-shaped tokens. + ** TODO ?= quoted strings, for the sake of the + ** #error keyword. */ + if(isspace(*zz)){ + assert(zz!=zKwd && "Leading space was stripped earlier."); + *zz = 0; + }else{ + if(argc == (int)CmppArgs_Max){ + fatal("Too many arguments @ line %u: %.*s", + tok->lineNo, tokLen, tok->zBegin); + }else if(zz>zKwd && !prevChar){ + t->args.argv[argc++] = zz; + } + } + } + }else{ + /* Treat rest of line as one token */ + if(*zz) t->args.argv[argc++] = zz; + } + tok->ttype = t->args.pKw->ttype; + if(g.doDebug>1){ + for(i = 0; i < argc; ++i){ + g_debug(0,("line %u arg #%d=%s\n", + tok->lineNo, i, + (char const *)t->args.argv[i])); + } + } + t->args.argc = argc; + }else{ + t->args.pKw = 0; + t->args.argc = 0; + } + return isDelim; +} + +static void cmpp_kwd__err_prefix(CmppKeyword const * pKw, CmppTokenizer *t, + char const *zPrefix){ + g_stderr("%s%s%s @ %s line %u: ", + zPrefix ? zPrefix : "", + zPrefix ? ": " : "", + pKw->zName, t->zName, t->token.lineNo); +} + +/* Internal error reporting helper for cmpp_keyword_f() impls. */ +static CMPP_NORETURN void cmpp_kwd__misuse(CmppKeyword const * pKw, + CmppTokenizer *t, + char const *zFmt, ...){ + va_list va; + cmpp_kwd__err_prefix(pKw, t, "Fatal error"); + va_start(va, zFmt); + fatalv(zFmt, va); + va_end(va); +} + +/* No-op cmpp_keyword_f() impl. */ +static void cmpp_kwd_noop(CmppKeyword const * pKw, CmppTokenizer *t){ + if(t || pKw){/*unused*/} +} + +/* #error impl. */ +static void cmpp_kwd_error(CmppKeyword const * pKw, CmppTokenizer *t){ + if(CT_skip(t)) return; + else{ + assert(t->args.argc < 3); + const char *zBegin = t->args.argc>1 + ? (const char *)t->args.argv[1] : 0; + cmpp_kwd__err_prefix(pKw, t, NULL); + fatal("%s", zBegin ? zBegin : "(no additional info)"); + } +} + +/* Impl. for #define, #undef */ +static void cmpp_kwd_define(CmppKeyword const * pKw, CmppTokenizer *t){ + if(CT_skip(t)) return; + if(t->args.argc<2){ + cmpp_kwd__misuse(pKw, t, "Expecting one or more arguments"); + }else{ + int i = 1; + void (*func)(const char *) = TT_Define==pKw->ttype + ? db_define_add : db_define_rm; + for( ; i < t->args.argc; ++i){ + func( (char const *)t->args.argv[i] ); + } + } +} + +/* Impl. for #if, #ifnot, #elif, #elifnot. */ +static void cmpp_kwd_if(CmppKeyword const * pKw, CmppTokenizer *t){ + int buul; + CmppParseState tmpState = TS_Start; + if(t->args.argc!=2){ + cmpp_kwd__misuse(pKw, t, "Expecting exactly 1 argument"); + } + /*g_debug(0,("%s %s level %u pstate=%d\n", pKw->zName, + (char const *)t->args.argv[1], + t->level.ndx, (int)CT_pstate(t)));*/ + switch(pKw->ttype){ + case TT_Elif: + case TT_ElifNot: + switch(CT_pstate(t)){ + case TS_If: break; + case TS_IfPassed: CT_level(t).flags |= CmppLevel_F_ELIDE; return; + default: goto misuse; + } + break; + case TT_If: + case TT_IfNot: + CmppLevel_push(t); + break; + default: + cmpp_kwd__misuse(pKw, t, "Unpexected keyword token type"); + break; + } + buul = db_define_has((char const *)t->args.argv[1]); + if(TT_IfNot==pKw->ttype || TT_ElifNot==pKw->ttype) buul = !buul; + if(buul){ + CT_pstate(t) = tmpState = TS_IfPassed; + CT_skipLevel(t) = 0; + }else{ + CT_pstate(t) = TS_If /* also for TT_IfNot, TT_Elif, TT_ElifNot */; + CT_skipLevel(t) = 1; + g_debug(3,("setting CT_skipLevel = 1 @ level %d\n", t->level.ndx)); + } + if(TT_If==pKw->ttype || TT_IfNot==pKw->ttype){ + unsigned const lvlIf = t->level.ndx; + CmppToken const lvlToken = CT_level(t).token; + while(cmpp_next_keyword_line(t)){ + cmpp_process_keyword(t); + if(lvlIf > t->level.ndx){ + assert(TT_EndIf == t->token.ttype); + break; + } +#if 0 + if(TS_IfPassed==tmpState){ + tmpState = TS_Start; + t->level.stack[lvlIf].flags |= CmppLevel_F_ELIDE; + g_debug(1,("Setting ELIDE for TS_IfPassed @ lv %d (lvlIf=%d)\n", t->level.ndx, lvlIf)); + } +#endif + } + if(lvlIf <= t->level.ndx){ + cmpp_kwd__err_prefix(pKw, t, NULL); + fatal("Input ended inside an unterminated %sif " + "opened at [%s] line %u", + g.zDelim, t->zName, lvlToken.lineNo); + } + } + return; + misuse: + cmpp_kwd__misuse(pKw, t, "'%s' used out of context", + pKw->zName); +} + +/* Impl. for #else. */ +static void cmpp_kwd_else(CmppKeyword const * pKw, CmppTokenizer *t){ + if(t->args.argc>1){ + cmpp_kwd__misuse(pKw, t, "Expecting no arguments"); + } + switch(CT_pstate(t)){ + case TS_IfPassed: CT_skipLevel(t) = 1; break; + case TS_If: CT_skipLevel(t) = 0; break; + default: + cmpp_kwd__misuse(pKw, t, "'%s' with no matching 'if'", + pKw->zName); + } + /*g_debug(0,("else flags=0x%02x skipLevel=%u\n", + CT_level(t).flags, CT_level(t).skipLevel));*/ + CT_pstate(t) = TS_Else; +} + +/* Impl. for #endif. */ +static void cmpp_kwd_endif(CmppKeyword const * pKw, CmppTokenizer *t){ + /* Maintenance reminder: we ignore all arguments after the endif + ** to allow for constructs like: + ** + ** #endif // foo + ** + ** in a manner which does not require a specific comment style */ + switch(CT_pstate(t)){ + case TS_Else: + case TS_If: + case TS_IfPassed: + break; + default: + cmpp_kwd__misuse(pKw, t, "'%s' with no matching 'if'", + pKw->zName); + } + CmppLevel_pop(t); +} + +/* Impl. for #include. */ +static void cmpp_kwd_include(CmppKeyword const * pKw, CmppTokenizer *t){ + char const * zFile; + char * zResolved; + if(CT_skip(t)) return; + else if(t->args.argc!=2){ + cmpp_kwd__misuse(pKw, t, "Expecting exactly 1 filename argument"); + } + zFile = (const char *)t->args.argv[1]; + if(db_including_has(zFile)){ + /* Note that different spellings of the same filename + ** will elude this check, but that seems okay, as different + ** spellings means that we're not re-running the exact same + ** invocation. We might want some other form of multi-include + ** protection, rather than this, however. There may well be + ** sensible uses for recursion. */ + cmpp_kwd__err_prefix(pKw, t, NULL); + fatal("Recursive include of file: %s", zFile); + } + zResolved = db_include_search(zFile); + if(zResolved){ + db_including_add(zFile, t->zName, t->token.lineNo); + cmpp_process_file(zResolved); + db_include_rm(zFile); + db_free(zResolved); + }else{ + cmpp_kwd__err_prefix(pKw, t, NULL); + fatal("file not found: %s", zFile); + } +} + +/* Impl. for #pragma. */ +static void cmpp_kwd_pragma(CmppKeyword const * pKw, CmppTokenizer *t){ + const char * zArg; + if(CT_skip(t)) return; + else if(t->args.argc!=2){ + cmpp_kwd__misuse(pKw, t, "Expecting one argument"); + } + zArg = (const char *)t->args.argv[1]; +#define M(X) 0==strcmp(zArg,X) + if(M("defines")){ + sqlite3_stmt * q = 0; + db_prepare(&q, "SELECT k FROM def ORDER BY k"); + g_stderr("cmpp defines:\n"); + while(SQLITE_ROW==db_step(q)){ + int const n = sqlite3_column_bytes(q, 0); + const char * z = (const char *)sqlite3_column_text(q, 0); + g_stderr("\t%.*s\n", n, z); + } + db_finalize(q); + }else{ + cmpp_kwd__misuse(pKw, t, "Unknown pragma"); + } +#undef M +} + +/* #stder impl. */ +static void cmpp_kwd_stderr(CmppKeyword const * pKw, CmppTokenizer *t){ + if(CT_skip(t)) return; + else{ + const char *zBegin = t->args.argc>1 + ? (const char *)t->args.argv[1] : 0; + if(zBegin){ + g_stderr("%s:%u: %s\n", t->zName, t->token.lineNo, zBegin); + }else{ + g_stderr("%s:%u: (no %.*s%s argument)\n", + t->zName, t->token.lineNo, + g.nDelim, g.zDelim, pKw->zName); + } + } +} + +#if 0 +/* Impl. for dummy placeholder. */ +static void cmpp_kwd_todo(CmppKeyword const * pKw, CmppTokenizer *t){ + if(t){/*unused*/} + g_debug(0,("TODO: keyword handler for %s\n", pKw->zName)); +} +#endif + +CmppKeyword aKeywords[] = { +/* Keep these sorted by zName */ + {"//", 2, 0, TT_Comment, cmpp_kwd_noop}, + {"define", 6, 1, TT_Define, cmpp_kwd_define}, + {"elif", 4, 1, TT_Elif, cmpp_kwd_if}, + {"elifnot", 7, 1, TT_ElifNot, cmpp_kwd_if}, + {"else", 4, 1, TT_Else, cmpp_kwd_else}, + {"endif", 5, 0, TT_EndIf, cmpp_kwd_endif}, + {"error", 4, 0, TT_Error, cmpp_kwd_error}, + {"if", 2, 1, TT_If, cmpp_kwd_if}, + {"ifnot", 5, 1, TT_IfNot, cmpp_kwd_if}, + {"include", 7, 0, TT_Include, cmpp_kwd_include}, + {"pragma", 6, 1, TT_Pragma, cmpp_kwd_pragma}, + {"stderr", 6, 0, TT_Stderr, cmpp_kwd_stderr}, + {"undef", 5, 1, TT_Undef, cmpp_kwd_define}, + {0,0,TT_Invalid, 0} +}; + +static int cmp_CmppKeyword(const void *p1, const void *p2){ + char const * zName = (const char *)p1; + CmppKeyword const * kw = (CmppKeyword const *)p2; + return strcmp(zName, kw->zName); +} + +CmppKeyword const * CmppKeyword_search(const char *zName){ + return (CmppKeyword const *)bsearch(zName, &aKeywords[0], + sizeof(aKeywords)/sizeof(aKeywords[0]) - 1, + sizeof(aKeywords[0]), + cmp_CmppKeyword); +} + +void cmpp_process_keyword(CmppTokenizer * const t){ + assert(t->args.pKw); + assert(t->args.argc); + t->args.pKw->xCall(t->args.pKw, t); + t->args.pKw = 0; + t->args.argc = 0; +} + +void cmpp_process_file(const char * zName){ + FileWrapper fw = FileWrapper_empty; + CmppTokenizer ct = CmppTokenizer_empty; + + FileWrapper_open(&fw, zName, "r"); + FileWrapper_slurp(&fw); + g_debug(1,("Read %u byte(s) from [%s]\n", fw.nContent, fw.zName)); + ct.zName = zName; + ct.zBegin = fw.zContent; + ct.zEnd = fw.zContent + fw.nContent; + while(cmpp_next_keyword_line(&ct)){ + cmpp_process_keyword(&ct); + } + FileWrapper_close(&fw); + if(0!=ct.level.ndx){ + CmppLevel * const lv = CmppLevel_get(&ct); + fatal("Input ended inside an unterminated nested construct" + "opened at [%s] line %u", zName, lv->token.lineNo); + } +} + +static void usage(int isErr){ + FILE * const fOut = isErr ? stderr : stdout; + fprintf(fOut, + "Usage: %s [flags] [infile]\n" + "Flags:\n", + g.zArgv0); +#define arg(F,D) fprintf(fOut," %s\n %s\n",F, D) + arg("-f|--file FILE","Read input from FILE (default=- (stdin)).\n" + " Alternately, the first non-flag argument is assumed to " + "be the input file."); + arg("-o|--outfile FILE","Send output to FILE (default=- (stdout))"); + arg("-DXYZ","Define XYZ to true"); + arg("-UXYZ","Undefine XYZ (equivalent to false)"); + arg("-IXYZ","Add dir XYZ to include path"); + arg("-d|--delimiter VALUE", "Set keyword delimiter to VALUE " + "(default=" CMPP_DEFAULT_DELIM ")"); +#undef arg + fputs("",fOut); +} + +int main(int argc, char const * const * argv){ + int rc = 0; + int i; + int inclCount = 0; + const char * zInfile = 0; +#define M(X) (0==strcmp(X,zArg)) +#define ISFLAG(X) else if(M(X)) +#define ISFLAG2(X,Y) else if(M(X) || M(Y)) +#define ARGVAL \ + if(i+1>=argc) fatal("Missing value for flag '%s'", zArg); \ + zArg = argv[++i] + g.zArgv0 = argv[0]; + atexit(cmpp_atexit); + cmpp_initdb(); + for(i = 1; i < argc; ++i){ + char const * zArg = argv[i]; + while('-'==*zArg) ++zArg; + if(M("?") || M("help")) { + usage(0); + goto end; + }else if('D'==*zArg){ + ++zArg; + if(!*zArg) fatal("Missing key for -D"); + db_define_add(zArg); + }else if('U'==*zArg){ + ++zArg; + if(!*zArg) fatal("Missing key for -U"); + db_define_rm(zArg); + }else if('I'==*zArg){ + ++zArg; + if(!*zArg) fatal("Missing directory for -I"); + db_include_dir_add(zArg); + ++inclCount; + } + ISFLAG2("o","outfile"){ + ARGVAL; + if(g.out.zName) fatal("Cannot use -o more than once."); + g.out.zName = zArg; + } + ISFLAG2("f","file"){ + ARGVAL; + do_infile: + if(zInfile) fatal("Cannot use -i more than once."); + zInfile = zArg; + } + ISFLAG2("d","delimiter"){ + ARGVAL; + g.zDelim = zArg; + g.nDelim = (unsigned short)strlen(zArg); + if(!g.nDelim) fatal("Keyword delimiter may not be empty."); + } + ISFLAG("debug"){ + ++g.doDebug; + }else if(!zInfile){ + goto do_infile; + }else{ + fatal("Unhandled flag: %s", argv[i]); + } + } + if(!zInfile) zInfile = "-"; + if(!g.out.zName) g.out.zName = "-"; + if(!inclCount) db_include_dir_add("."); + FileWrapper_open(&g.out, g.out.zName, "w"); + cmpp_process_file(zInfile); + FileWrapper_close(&g.out); + end: + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} + +#undef CT_level +#undef CT_pstate +#undef CT_skipLevel +#undef CT_skip +#undef CLvl_skip diff --git a/ext/wasm/common/emscripten.css b/ext/wasm/common/emscripten.css index 7e3dc811d..d8f82c73b 100644 --- a/ext/wasm/common/emscripten.css +++ b/ext/wasm/common/emscripten.css @@ -1,4 +1,4 @@ -/* emcscript-related styling, used during the module load/intialization processes... */ +/* emscripten-related styling, used during the module load/intialization processes... */ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten { text-align: center; } div.emscripten_border { border: 1px solid black; } diff --git a/ext/wasm/common/testing.css b/ext/wasm/common/testing.css index 9438b330c..fb44f1d61 100644 --- a/ext/wasm/common/testing.css +++ b/ext/wasm/common/testing.css @@ -61,3 +61,9 @@ span.labeled-input { flex-direction: column-reverse; } label[for] { cursor: pointer } + +h1 { + border-radius: 0.25em; + padding: 0.15em 0.25em; +} +h1:first-of-type {margin: 0 0 0.5em 0;} diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index 7e5e7981f..e50210206 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -246,6 +246,25 @@ self.WhWasmUtilInstaller = function(target){ cache.utf8Decoder = new TextDecoder(); cache.utf8Encoder = new TextEncoder('utf-8'); + /** + For the given IR-like string in the set ('i8', 'i16', 'i32', + 'f32', 'float', 'i64', 'f64', 'double', '*'), or any string value + ending in '*', returns the sizeof for that value + (target.ptrSizeof in the latter case). For any other value, it + returns the undefined value. + */ + target.sizeofIR = (n)=>{ + switch(n){ + case 'i8': return 1; + case 'i16': return 2; + case 'i32': case 'f32': case 'float': return 4; + case 'i64': case 'f64': case 'double': return 8; + case '*': return ptrSizeof; + default: + return (''+n).endsWith('*') ? ptrSizeof : undefined; + } + }; + /** If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if the heap has grown since the last call, updates cache.HEAPxyz. @@ -316,13 +335,11 @@ self.WhWasmUtilInstaller = function(target){ Throws if passed an invalid n. - Pedantic side note: the name "heap" is a bit of a misnomer. In an - Emscripten environment, the memory managed via the stack - allocation API is in the same Memory object as the heap (which - makes sense because otherwise arbitrary pointer X would be - ambiguous: is it in the heap or the stack?). + Pedantic side note: the name "heap" is a bit of a misnomer. In a + WASM environment, the stack and heap memory are all accessed via + the same view(s) of the memory. */ - target.heapForSize = function(n,unsigned = false){ + target.heapForSize = function(n,unsigned = true){ let ctor; const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) ? cache : heapWrappers(); @@ -361,7 +378,8 @@ self.WhWasmUtilInstaller = function(target){ /** Given a function pointer, returns the WASM function table entry - if found, else returns a falsy value. + if found, else returns a falsy value: undefined if fptr is out of + range or null if it's in range but the table entry is empty. */ target.functionEntry = function(fptr){ const ft = target.functionTable(); @@ -448,7 +466,7 @@ self.WhWasmUtilInstaller = function(target){ type(s) of the given function signature, or throws if the signature is invalid. */ /******** // only valid for use with the WebAssembly.Function ctor, which - // is not yet documented on MDN. + // is not yet documented on MDN. sigToWasm: function(sig){ const rc = {parameters:[], results: []}; if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0])); @@ -496,47 +514,27 @@ self.WhWasmUtilInstaller = function(target){ e: { f: func } })).exports['f']; }/*jsFuncToWasm()*/; - - /** - Expects a JS function and signature, exactly as for - this.jsFuncToWasm(). It uses that function to create a - WASM-exported function, installs that function to the next - available slot of this.functionTable(), and returns the - function's index in that table (which acts as a pointer to that - function). The returned pointer can be passed to - uninstallFunction() to uninstall it and free up the table slot for - reuse. - If passed (string,function) arguments then it treats the first - argument as the signature and second as the function. - - As a special case, if the passed-in function is a WASM-exported - function then the signature argument is ignored and func is - installed as-is, without requiring re-compilation/re-wrapping. - - This function will propagate an exception if - WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws. - The former case can happen in an Emscripten-compiled - environment when building without Emscripten's - `-sALLOW_TABLE_GROWTH` flag. - - Sidebar: this function differs from Emscripten's addFunction() - _primarily_ in that it does not share that function's - undocumented behavior of reusing a function if it's passed to - addFunction() more than once, which leads to uninstallFunction() - breaking clients which do not take care to avoid that case: - - https://github.com/emscripten-core/emscripten/issues/17323 - */ - target.installFunction = function f(func, sig){ - if(2!==arguments.length){ - toss("installFunction() requires exactly 2 arguments"); + /** + Documented as target.installFunction() except for the 3rd + argument: if truthy, the newly-created function pointer + is stashed in the current scoped-alloc scope and will be + cleaned up at the matching scopedAllocPop(), else it + is not stashed there. + */ + const __installFunction = function f(func, sig, scoped){ + if(scoped && !cache.scopedAlloc.length){ + toss("No scopedAllocPush() scope is active."); } if('string'===typeof func){ const x = sig; sig = func; func = x; } + if('string'!==typeof sig || !(func instanceof Function)){ + toss("Invalid arguments: expecting (function,signature) "+ + "or (signature,function)."); + } const ft = target.functionTable(); const oldLen = ft.length; let ptr; @@ -556,6 +554,9 @@ self.WhWasmUtilInstaller = function(target){ try{ /*this will only work if func is a WASM-exported function*/ ft.set(ptr, func); + if(scoped){ + cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); + } return ptr; }catch(e){ if(!(e instanceof TypeError)){ @@ -565,14 +566,63 @@ self.WhWasmUtilInstaller = function(target){ } // It's not a WASM-exported function, so compile one... try { - ft.set(ptr, target.jsFuncToWasm(func, sig)); + const fptr = target.jsFuncToWasm(func, sig); + ft.set(ptr, fptr); + if(scoped){ + cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); + } }catch(e){ if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); throw e; } - return ptr; + return ptr; }; + /** + Expects a JS function and signature, exactly as for + this.jsFuncToWasm(). It uses that function to create a + WASM-exported function, installs that function to the next + available slot of this.functionTable(), and returns the + function's index in that table (which acts as a pointer to that + function). The returned pointer can be passed to + uninstallFunction() to uninstall it and free up the table slot for + reuse. + + If passed (string,function) arguments then it treats the first + argument as the signature and second as the function. + + As a special case, if the passed-in function is a WASM-exported + function then the signature argument is ignored and func is + installed as-is, without requiring re-compilation/re-wrapping. + + This function will propagate an exception if + WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws. + The former case can happen in an Emscripten-compiled + environment when building without Emscripten's + `-sALLOW_TABLE_GROWTH` flag. + + Sidebar: this function differs from Emscripten's addFunction() + _primarily_ in that it does not share that function's + undocumented behavior of reusing a function if it's passed to + addFunction() more than once, which leads to uninstallFunction() + breaking clients which do not take care to avoid that case: + + https://github.com/emscripten-core/emscripten/issues/17323 + */ + target.installFunction = (func, sig)=>__installFunction(func, sig, false); + + /** + EXPERIMENTAL! DO NOT USE IN CLIENT CODE! + + Works exactly like installFunction() but requires that a + scopedAllocPush() is active and uninstalls the given function + when that alloc scope is popped via scopedAllocPop(). + This is used for implementing JS/WASM function bindings which + should only persist for the life of a call into a single + C-side function. + */ + target.scopedInstallFunction = (func, sig)=>__installFunction(func, sig, true); + /** Requires a pointer value previously returned from this.installFunction(). Removes that function from the WASM @@ -581,8 +631,13 @@ self.WhWasmUtilInstaller = function(target){ installFunction() has been called and results are undefined if ptr was not returned by that function. The returned function may be passed back to installFunction() to reinstall it. + + To simplify certain use cases, if passed a falsy non-0 value + (noting that 0 is a valid function table index), this function + has no side effects and returns undefined. */ target.uninstallFunction = function(ptr){ + if(!ptr && 0!==ptr) return undefined; const fi = cache.freeFuncIndexes; const ft = target.functionTable(); fi.push(ptr); @@ -599,19 +654,24 @@ self.WhWasmUtilInstaller = function(target){ type triggers an exception if this.bigIntEnabled is falsy). Throws if given an invalid type. + If the first argument is an array, it is treated as an array of + addresses and the result is an array of the values from each of + those address, using the same 2nd argument for determining the + value type to fetch. + As a special case, if type ends with a `*`, it is considered to be a pointer type and is treated as the WASM numeric type appropriate for the pointer size (`i32`). - While likely not obvious, this routine and its setMemValue() + While likely not obvious, this routine and its poke() counterpart are how pointer-to-value _output_ parameters in WASM-compiled C code can be interacted with: ``` const ptr = alloc(4); - setMemValue(ptr, 0, 'i32'); // clear the ptr's value + poke(ptr, 0, 'i32'); // clear the ptr's value aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x); - const result = getMemValue(ptr, 'i32'); // fetch ptr's value + const result = peek(ptr, 'i32'); // fetch ptr's value dealloc(ptr); ``` @@ -623,15 +683,15 @@ self.WhWasmUtilInstaller = function(target){ const scope = scopedAllocPush(); try{ const ptr = scopedAlloc(4); - setMemValue(ptr, 0, 'i32'); + poke(ptr, 0, 'i32'); aCFuncWithOutputPtrArg( ptr ); - result = getMemValue(ptr, 'i32'); + result = peek(ptr, 'i32'); }finally{ scopedAllocPop(scope); } ``` - As a rule setMemValue() must be called to set (typically zero + As a rule poke() must be called to set (typically zero out) the pointer's value, else it will contain an essentially random value. @@ -639,70 +699,142 @@ self.WhWasmUtilInstaller = function(target){ painful impact on performance. Rather than doing so, use heapForSize() to fetch the heap object and read directly from it. - See: setMemValue() + See: poke() */ - target.getMemValue = function(ptr, type='i8'){ + target.peek = function f(ptr, type='i8'){ if(type.endsWith('*')) type = ptrIR; const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) ? cache : heapWrappers(); - switch(type){ - case 'i1': - case 'i8': return c.HEAP8[ptr>>0]; - case 'i16': return c.HEAP16[ptr>>1]; - case 'i32': return c.HEAP32[ptr>>2]; - case 'i64': - if(target.bigIntEnabled) return BigInt(c.HEAP64[ptr>>3]); - break; - case 'float': case 'f32': return c.HEAP32F[ptr>>2]; - case 'double': case 'f64': return Number(c.HEAP64F[ptr>>3]); - default: break; - } - toss('Invalid type for getMemValue():',type); + const list = Array.isArray(ptr) ? [] : undefined; + let rc; + do{ + if(list) ptr = arguments[0].shift(); + switch(type){ + case 'i1': + case 'i8': rc = c.HEAP8[ptr>>0]; break; + case 'i16': rc = c.HEAP16[ptr>>1]; break; + case 'i32': rc = c.HEAP32[ptr>>2]; break; + case 'float': case 'f32': rc = c.HEAP32F[ptr>>2]; break; + case 'double': case 'f64': rc = Number(c.HEAP64F[ptr>>3]); break; + case 'i64': + if(target.bigIntEnabled){ + rc = BigInt(c.HEAP64[ptr>>3]); + break; + } + /* fallthru */ + default: + toss('Invalid type for peek():',type); + } + if(list) list.push(rc); + }while(list && arguments[0].length); + return list || rc; }; /** - The counterpart of getMemValue(), this sets a numeric value at + The counterpart of peek(), this sets a numeric value at the given WASM heap address, using the type to define how many bytes are written. Throws if given an invalid type. See - getMemValue() for details about the type argument. If the 3rd + peek() for details about the type argument. If the 3rd argument ends with `*` then it is treated as a pointer type and this function behaves as if the 3rd argument were `i32`. - This function returns itself. + If the first argument is an array, it is treated like a list + of pointers and the given value is written to each one. + + Returns `this`. (Prior to 2022-12-09 it returns this function.) ACHTUNG: calling this often, e.g. in a loop, can have a noticably painful impact on performance. Rather than doing so, use - heapForSize() to fetch the heap object and assign directly to it. + heapForSize() to fetch the heap object and assign directly to it + or use the heap's set() method. */ - target.setMemValue = function f(ptr, value, type='i8'){ + target.poke = function(ptr, value, type='i8'){ if (type.endsWith('*')) type = ptrIR; const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) ? cache : heapWrappers(); - switch (type) { - case 'i1': - case 'i8': c.HEAP8[ptr>>0] = value; return f; - case 'i16': c.HEAP16[ptr>>1] = value; return f; - case 'i32': c.HEAP32[ptr>>2] = value; return f; - case 'i64': - if(c.HEAP64){ - c.HEAP64[ptr>>3] = BigInt(value); - return f; - } - break; - case 'float': case 'f32': c.HEAP32F[ptr>>2] = value; return f; - case 'double': case 'f64': c.HEAP64F[ptr>>3] = value; return f; + for(const p of (Array.isArray(ptr) ? ptr : [ptr])){ + switch (type) { + case 'i1': + case 'i8': c.HEAP8[p>>0] = value; continue; + case 'i16': c.HEAP16[p>>1] = value; continue; + case 'i32': c.HEAP32[p>>2] = value; continue; + case 'float': case 'f32': c.HEAP32F[p>>2] = value; continue; + case 'double': case 'f64': c.HEAP64F[p>>3] = value; continue; + case 'i64': + if(c.HEAP64){ + c.HEAP64[p>>3] = BigInt(value); + continue; + } + /* fallthru */ + default: + toss('Invalid type for poke(): ' + type); + } } - toss('Invalid type for setMemValue(): ' + type); + return this; }; + /** + Convenience form of peek() intended for fetching + pointer-to-pointer values. If passed a single non-array argument + it returns the value of that one pointer address. If passed + multiple arguments, or a single array of arguments, it returns an + array of their values. + */ + target.peekPtr = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), ptrIR ); - /** Convenience form of getMemValue() intended for fetching - pointer-to-pointer values. */ - target.getPtrValue = (ptr)=>target.getMemValue(ptr, ptrIR); + /** + A variant of poke() intended for setting pointer-to-pointer + values. Its differences from poke() are that (1) it defaults to a + value of 0 and (2) it always writes to the pointer-sized heap + view. + */ + target.pokePtr = (ptr, value=0)=>target.poke(ptr, value, ptrIR); - /** Convenience form of setMemValue() intended for setting - pointer-to-pointer values. */ - target.setPtrValue = (ptr, value)=>target.setMemValue(ptr, value, ptrIR); + /** + Convenience form of peek() intended for fetching i8 values. If + passed a single non-array argument it returns the value of that + one pointer address. If passed multiple arguments, or a single + array of arguments, it returns an array of their values. + */ + target.peek8 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i8' ); + /** + Convience form of poke() intended for setting individual bytes. + Its difference from poke() is that it always writes to the + i8-sized heap view. + */ + target.poke8 = (ptr, value)=>target.poke(ptr, value, 'i8'); + /** i16 variant of peek8(). */ + target.peek16 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i16' ); + /** i16 variant of poke8(). */ + target.poke16 = (ptr, value)=>target.poke(ptr, value, 'i16'); + /** i32 variant of peek8(). */ + target.peek32 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i32' ); + /** i32 variant of poke8(). */ + target.poke32 = (ptr, value)=>target.poke(ptr, value, 'i32'); + /** i64 variant of peek8(). Will throw if this build is not + configured for BigInt support. */ + target.peek64 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i64' ); + /** i64 variant of poke8(). Will throw if this build is not + configured for BigInt support. Note that this returns + a BigInt-type value, not a Number-type value. */ + target.poke64 = (ptr, value)=>target.poke(ptr, value, 'i64'); + /** f32 variant of peek8(). */ + target.peek32f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f32' ); + /** f32 variant of poke8(). */ + target.poke32f = (ptr, value)=>target.poke(ptr, value, 'f32'); + /** f64 variant of peek8(). */ + target.peek64f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f64' ); + /** f64 variant of poke8(). */ + target.poke64f = (ptr, value)=>target.poke(ptr, value, 'f64'); + + /** Deprecated alias for getMemValue() */ + target.getMemValue = target.peek; + /** Deprecated alias for peekPtr() */ + target.getPtrValue = target.peekPtr; + /** Deprecated alias for poke() */ + target.setMemValue = target.poke; + /** Deprecated alias for pokePtr() */ + target.setPtrValue = target.pokePtr; /** Returns true if the given value appears to be legal for use as @@ -725,11 +857,12 @@ self.WhWasmUtilInstaller = function(target){ Expects ptr to be a pointer into the WASM heap memory which refers to a NUL-terminated C-style string encoded as UTF-8. Returns the length, in bytes, of the string, as for `strlen(3)`. - As a special case, if !ptr then it it returns `null`. Throws if - ptr is out of range for target.heap8u(). + As a special case, if !ptr or if it's not a pointer then it + returns `null`. Throws if ptr is out of range for + target.heap8u(). */ target.cstrlen = function(ptr){ - if(!ptr) return null; + if(!ptr || !target.isPtr(ptr)) return null; const h = heapWrappers().HEAP8U; let pos = ptr; for( ; h[pos] !== 0; ++pos ){} @@ -753,9 +886,9 @@ self.WhWasmUtilInstaller = function(target){ refers to a NUL-terminated C-style string encoded as UTF-8. This function counts its byte length using cstrlen() then returns a JS-format string representing its contents. As a special case, if - ptr is falsy, `null` is returned. + ptr is falsy or not a pointer, `null` is returned. */ - target.cstringToJs = function(ptr){ + target.cstrToJs = function(ptr){ const n = target.cstrlen(ptr); return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : ""); }; @@ -942,10 +1075,19 @@ self.WhWasmUtilInstaller = function(target){ const __allocCStr = function(jstr, returnWithLength, allocator, funcName){ __affirmAlloc(target, funcName); if('string'!==typeof jstr) return null; - const n = target.jstrlen(jstr), - ptr = allocator(n+1); - target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true); - return returnWithLength ? [ptr, n] : ptr; + if(0){/* older impl, possibly more widely compatible? */ + const n = target.jstrlen(jstr), + ptr = allocator(n+1); + target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true); + return returnWithLength ? [ptr, n] : ptr; + }else{/* newer, (probably) faster and (certainly) simpler impl */ + const u = cache.utf8Encoder.encode(jstr), + ptr = allocator(u.length+1), + heap = heapWrappers().HEAP8U; + heap.set(u, ptr); + heap[ptr + u.length] = 0; + return returnWithLength ? [ptr, u.length] : ptr; + } }; /** @@ -1035,7 +1177,13 @@ self.WhWasmUtilInstaller = function(target){ if(n<0) toss("Invalid state object for scopedAllocPop()."); if(0===arguments.length) state = cache.scopedAlloc[n]; cache.scopedAlloc.splice(n,1); - for(let p; (p = state.pop()); ) target.dealloc(p); + for(let p; (p = state.pop()); ){ + if(target.functionEntry(p)){ + //console.warn("scopedAllocPop() uninstalling transient function",p); + target.uninstallFunction(p); + } + else target.dealloc(p); + } }; /** @@ -1081,40 +1229,67 @@ self.WhWasmUtilInstaller = function(target){ // impl for allocMainArgv() and scopedAllocMainArgv(). const __allocMainArgv = function(isScoped, list){ - if(!list.length) toss("Cannot allocate empty array."); const pList = target[ isScoped ? 'scopedAlloc' : 'alloc' - ](list.length * target.ptrSizeof); + ]((list.length + 1) * target.ptrSizeof); let i = 0; list.forEach((e)=>{ - target.setPtrValue(pList + (target.ptrSizeof * i++), + target.pokePtr(pList + (target.ptrSizeof * i++), target[ isScoped ? 'scopedAllocCString' : 'allocCString' ](""+e)); }); + target.pokePtr(pList + (target.ptrSizeof * i), 0); return pList; }; /** Creates an array, using scopedAlloc(), suitable for passing to a C-level main() routine. The input is a collection with a length - property and a forEach() method. A block of memory list.length - entries long is allocated and each pointer-sized block of that - memory is populated with a scopedAllocCString() conversion of the - (""+value) of each element. Returns a pointer to the start of the - list, suitable for passing as the 2nd argument to a C-style - main() function. - - Throws if list.length is falsy or scopedAllocPush() is not active. + property and a forEach() method. A block of memory + (list.length+1) entries long is allocated and each pointer-sized + block of that memory is populated with a scopedAllocCString() + conversion of the (""+value) of each element, with the exception + that the final entry is a NULL pointer. Returns a pointer to the + start of the list, suitable for passing as the 2nd argument to a + C-style main() function. + + Throws if scopedAllocPush() is not active. + + Design note: the returned array is allocated with an extra NULL + pointer entry to accommodate certain APIs, but client code which + does not need that functionality should treat the returned array + as list.length entries long. */ target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list); /** Identical to scopedAllocMainArgv() but uses alloc() instead of - scopedAllocMainArgv + scopedAlloc(). */ target.allocMainArgv = (list)=>__allocMainArgv(false, list); + /** + Expects to be given a C-style string array and its length. It + returns a JS array of strings and/or nulls: any entry in the + pArgv array which is NULL results in a null entry in the result + array. If argc is 0 then an empty array is returned. + + Results are undefined if any entry in the first argc entries of + pArgv are neither 0 (NULL) nor legal UTF-format C strings. + + To be clear, the expected C-style arguments to be passed to this + function are `(int, char **)` (optionally const-qualified). + */ + target.cArgvToJs = (argc, pArgv)=>{ + const list = []; + for(let i = 0; i < argc; ++i){ + const arg = target.peekPtr(pArgv + (target.ptrSizeof * i)); + list.push( arg ? target.cstrToJs(arg) : null ); + } + return list; + }; + /** Wraps function call func() in a scopedAllocPush() and scopedAllocPop() block, such that all calls to scopedAlloc() and @@ -1133,7 +1308,7 @@ self.WhWasmUtilInstaller = function(target){ __affirmAlloc(target, method); const pIr = safePtrSize ? 'i64' : ptrIR; let m = target[method](howMany * (safePtrSize ? 8 : ptrSizeof)); - target.setMemValue(m, 0, pIr) + target.poke(m, 0, pIr) if(1===howMany){ return m; } @@ -1141,7 +1316,7 @@ self.WhWasmUtilInstaller = function(target){ for(let i = 1; i < howMany; ++i){ m += (safePtrSize ? 8 : ptrSizeof); a[i] = m; - target.setMemValue(m, 0, pIr); + target.poke(m, 0, pIr); } return a; }; @@ -1172,7 +1347,7 @@ self.WhWasmUtilInstaller = function(target){ When one of the returned pointers will refer to a 64-bit value, e.g. a double or int64, an that value must be written or fetched, - e.g. using setMemValue() or getMemValue(), it is important that + e.g. using poke() or peek(), it is important that the pointer in question be aligned to an 8-byte boundary or else it will not be fetched or written properly and will corrupt or read neighboring memory. It is only safe to pass false when the @@ -1199,7 +1374,7 @@ self.WhWasmUtilInstaller = function(target){ const __argcMismatch = (f,n)=>toss(f+"() requires",n,"argument(s)."); - + /** Looks up a WASM-exported function named fname from target.exports. If found, it is called, passed all remaining @@ -1228,32 +1403,47 @@ self.WhWasmUtilInstaller = function(target){ State for use with xWrap() */ cache.xWrap = Object.create(null); - const xcv = cache.xWrap.convert = Object.create(null); + cache.xWrap.convert = Object.create(null); /** Map of type names to argument conversion functions. */ - cache.xWrap.convert.arg = Object.create(null); + cache.xWrap.convert.arg = new Map; /** Map of type names to return result conversion functions. */ - cache.xWrap.convert.result = Object.create(null); + cache.xWrap.convert.result = new Map; + const xArg = cache.xWrap.convert.arg, xResult = cache.xWrap.convert.result; if(target.bigIntEnabled){ - xcv.arg.i64 = (i)=>BigInt(i); + xArg.set('i64', (i)=>BigInt(i)); } - xcv.arg.i32 = (i)=>(i | 0); - xcv.arg.i16 = (i)=>((i | 0) & 0xFFFF); - xcv.arg.i8 = (i)=>((i | 0) & 0xFF); - xcv.arg.f32 = xcv.arg.float = (i)=>Number(i).valueOf(); - xcv.arg.f64 = xcv.arg.double = xcv.arg.f32; - xcv.arg.int = xcv.arg.i32; - xcv.result['*'] = xcv.result['pointer'] = xcv.arg['**'] = xcv.arg[ptrIR]; - xcv.result['number'] = (v)=>Number(v); - - { /* Copy certain xcv.arg[...] handlers to xcv.result[...] and + const __xArgPtr = 'i32' === ptrIR + ? ((i)=>(i | 0)) : ((i)=>(BigInt(i) | BigInt(0))); + xArg.set('i32', __xArgPtr ) + .set('i16', (i)=>((i | 0) & 0xFFFF)) + .set('i8', (i)=>((i | 0) & 0xFF)) + .set('f32', (i)=>Number(i).valueOf()) + .set('float', xArg.get('f32')) + .set('f64', xArg.get('f32')) + .set('double', xArg.get('f64')) + .set('int', xArg.get('i32')) + .set('null', (i)=>i) + .set(null, xArg.get('null')) + .set('**', __xArgPtr) + .set('*', __xArgPtr); + xResult.set('*', __xArgPtr) + .set('pointer', __xArgPtr) + .set('number', (v)=>Number(v)) + .set('void', (v)=>undefined) + .set('null', (v)=>v) + .set(null, xResult.get('null')); + + { /* Copy certain xArg[...] handlers to xResult[...] and add pointer-style variants of them. */ const copyToResult = ['i8', 'i16', 'i32', 'int', 'f32', 'float', 'f64', 'double']; if(target.bigIntEnabled) copyToResult.push('i64'); + const adaptPtr = xArg.get(ptrIR); for(const t of copyToResult){ - xcv.arg[t+'*'] = xcv.result[t+'*'] = xcv.arg[ptrIR]; - xcv.result[t] = xcv.arg[t] || toss("Missing arg converter:",t); + xArg.set(t+'*', adaptPtr); + xResult.set(t+'*', adaptPtr); + xResult.set(t, (xArg.get(t) || toss("Missing arg converter:",t))); } } @@ -1267,83 +1457,352 @@ self.WhWasmUtilInstaller = function(target){ - If v is a string, scopeAlloc() a new C-string from it and return that temp string's pointer. - - Else return the value from the arg adaptor defined for ptrIR. + - Else return the value from the arg adapter defined for ptrIR. TODO? Permit an Int8Array/Uint8Array and convert it to a string? Would that be too much magic concentrated in one place, ready to - backfire? + backfire? We handle that at the client level in sqlite3 with a + custom argument converter. */ - xcv.arg.string = xcv.arg.utf8 = xcv.arg['pointer'] = xcv.arg['*'] - = function(v){ - if('string'===typeof v) return target.scopedAllocCString(v); - return v ? xcv.arg[ptrIR](v) : null; - }; - xcv.result.string = xcv.result.utf8 = (i)=>target.cstringToJs(i); - xcv.result['string:free'] = xcv.result['utf8:free'] = (i)=>{ - try { return i ? target.cstringToJs(i) : null } - finally{ target.dealloc(i) } + const __xArgString = function(v){ + if('string'===typeof v) return target.scopedAllocCString(v); + return v ? __xArgPtr(v) : null; }; - xcv.result.json = (i)=>JSON.parse(target.cstringToJs(i)); - xcv.result['json:free'] = (i)=>{ - try{ return i ? JSON.parse(target.cstringToJs(i)) : null } - finally{ target.dealloc(i) } - } - xcv.result['void'] = (v)=>undefined; - xcv.result['null'] = (v)=>v; - - if(0){ - /*** - This idea can't currently work because we don't know the - signature for the func and don't have a way for the user to - convey it. To do this we likely need to be able to match - arg/result handlers by a regex, but that would incur an O(N) - cost as we check the regex one at a time. Another use case for - such a thing would be pseudotypes like "int:-1" to say that - the value will always be treated like -1 (which has a useful - case in the sqlite3 bindings). + xArg.set('string', __xArgString) + .set('utf8', __xArgString) + .set('pointer', __xArgString); + //xArg.set('*', __xArgString); + + xResult.set('string', (i)=>target.cstrToJs(i)) + .set('utf8', xResult.get('string')) + .set('string:dealloc', (i)=>{ + try { return i ? target.cstrToJs(i) : null } + finally{ target.dealloc(i) } + }) + .set('utf8:dealloc', xResult.get('string:dealloc')) + .set('json', (i)=>JSON.parse(target.cstrToJs(i))) + .set('json:dealloc', (i)=>{ + try{ return i ? JSON.parse(target.cstrToJs(i)) : null } + finally{ target.dealloc(i) } + }); + + /** + Internal-use-only base class for FuncPtrAdapter and potentially + additional stateful argument adapter classes. + + Note that its main interface (convertArg()) is strictly + internal, not to be exposed to client code, as it may still + need re-shaping. Only the constructors of concrete subclasses + should be exposed to clients, and those in such a way that + does not hinder internal redesign of the convertArg() + interface. + */ + const AbstractArgAdapter = class { + constructor(opt){ + this.name = opt.name || 'unnamed adapter'; + } + /** + Gets called via xWrap() to "convert" v to whatever type + this specific class supports. + + argIndex is the argv index of _this_ argument in the + being-xWrap()'d call. argv is the current argument list + undergoing xWrap() argument conversion. argv entries to the + left of argIndex will have already undergone transformation and + those to the right will not have (they will have the values the + client-level code passed in, awaiting conversion). The RHS + indexes must never be relied upon for anything because their + types are indeterminate, whereas the LHS values will be + WASM-compatible values by the time this is called. */ - xcv.arg['func-ptr'] = function(v){ - if(!(v instanceof Function)) return xcv.arg[ptrIR]; - const f = target.jsFuncToWasm(v, WHAT_SIGNATURE); - }; - } + convertArg(v,argv,argIndex){ + toss("AbstractArgAdapter must be subclassed."); + } + }; + + /** + An attempt at adding function pointer conversion support to + xWrap(). This type is recognized by xWrap() as a proxy for + converting a JS function to a C-side function, either + permanently, for the duration of a single call into the C layer, + or semi-contextual, where it may keep track of a single binding + for a given context and uninstall the binding if it's replaced. + + The constructor requires an options object with these properties: + + - name (optional): string describing the function binding. This + is solely for debugging and error-reporting purposes. If not + provided, an empty string is assumed. + + - signature: a function signature string compatible with + jsFuncToWasm(). + + - bindScope (string): one of ('transient', 'context', + 'singleton'). Bind scopes are: + + - 'transient': it will convert JS functions to WASM only for + the duration of the xWrap()'d function call, using + scopedInstallFunction(). Before that call returns, the + WASM-side binding will be uninstalled. + + - 'singleton': holds one function-pointer binding for this + instance. If it's called with a different function pointer, + it uninstalls the previous one after converting the new + value. This is only useful for use with "global" functions + which do not rely on any state other than this function + pointer. If the being-converted function pointer is intended + to be mapped to some sort of state object (e.g. an + `sqlite3*`) then "context" (see below) is the proper mode. + + - 'context': similar to singleton mode but for a given + "context", where the context is a key provided by the user + and possibly dependent on a small amount of call-time + context. This mode is the default if bindScope is _not_ set + but a property named contextKey (described below) is. + + - 'permanent': the function is installed and left there + forever. There is no way to recover its pointer address + later on. + + - callProxy (function): if set, this must be a function which + will act as a proxy for any "converted" JS function. It is + passed the being-converted function value and must return + either that function or a function which acts on its + behalf. The returned function will be the one which gets + installed into the WASM function table. The proxy must perform + any required argument conversion (noting that it will be called + from C code, so will receive C-format arguments) before passing + them on to the being-converted function. Whether or not the + proxy itself must return a value depends on the context. If it + does, it must be a WASM-friendly value, as it will be returning + from a call made from native code. + + - contextKey (function): is only used if bindScope is 'context' + or if bindScope is not set and this function is, in which case + 'context' is assumed. This function gets bound to this object, + so its "this" is this object. It gets passed (argv,argIndex), + where argIndex is the index of _this_ function pointer in its + _wrapping_ function's arguments and argv is the _current_ + still-being-xWrap()-processed args array. All arguments to the + left of argIndex will have been processed by xWrap() by the + time this is called. argv[argIndex] will be the value the user + passed in to the xWrap()'d function for the argument this + FuncPtrAdapter is mapped to. Arguments to the right of + argv[argIndex] will not yet have been converted before this is + called. The function must return a key which uniquely + identifies this function mapping context for _this_ + FuncPtrAdapter instance (other instances are not considered), + taking into account that C functions often take some sort of + state object as one or more of their arguments. As an example, + if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and + this FuncPtrAdapter is the argv[2]nd arg, contextKey(argv,2) + might return 'T@'+argv[1], or even just argv[1]. Note, + however, that the (X*) argument will not yet have been + processed by the time this is called and should not be used as + part of that key because its pre-conversion data type might be + unpredictable. Similarly, care must be taken with C-string-type + arguments: those to the left in argv will, when this is called, + be WASM pointers, whereas those to the right might (and likely + do) have another data type. When using C-strings in keys, never + use their pointers in the key because most C-strings in this + constellation are transient. + + Yes, that ^^^ is quite awkward, but it's what we have. + + The constructor only saves the above state for later, and does + not actually bind any functions. Its convertArg() method is + called via xWrap() to perform any bindings. + + Shortcomings: + + - These "reverse" bindings, i.e. calling into a JS-defined + function from a WASM-defined function (the generated proxy + wrapper), lack all type conversion support. That means, for + example, that... + + - Function pointers which include C-string arguments may still + need a level of hand-written wrappers around them, depending on + how they're used, in order to provide the client with JS + strings. Alternately, clients will need to perform such conversions + on their own, e.g. using cstrtojs(). Or maybe we can find a way + to perform such conversions here, via addition of an xWrap()-style + function signature to the options argument. + */ + xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter { + constructor(opt) { + super(opt); + if(xArg.FuncPtrAdapter.warnOnUse){ + console.warn('xArg.FuncPtrAdapter is an internal-only API', + 'and is not intended to be invoked from', + 'client-level code. Invoked with:',opt); + } + this.signature = opt.signature; + if(opt.contextKey instanceof Function){ + this.contextKey = opt.contextKey; + if(!opt.bindScope) opt.bindScope = 'context'; + } + this.bindScope = opt.bindScope + || toss("FuncPtrAdapter options requires a bindScope (explicit or implied)."); + if(FuncPtrAdapter.bindScopes.indexOf(opt.bindScope)<0){ + toss("Invalid options.bindScope ("+opt.bindMod+") for FuncPtrAdapter. "+ + "Expecting one of: ("+FuncPtrAdapter.bindScopes.join(', ')+')'); + } + this.isTransient = 'transient'===this.bindScope; + this.isContext = 'context'===this.bindScope; + this.isPermanent = 'permanent'===this.bindScope; + this.singleton = ('singleton'===this.bindScope) ? [] : undefined; + //console.warn("FuncPtrAdapter()",opt,this); + this.callProxy = (opt.callProxy instanceof Function) + ? opt.callProxy : undefined; + } + + /** If true, the constructor emits a warning. The intent is that + this be set to true after bootstrapping of the higher-level + client library is complete, to warn downstream clients that + they shouldn't be relying on this implemenation detail which + does not have a stable interface. */ + static warnOnUse = false; + + /** If true, convertArg() will FuncPtrAdapter.debugOut() when it + (un)installs a function binding to/from WASM. Note that + deinstallation of bindScope=transient bindings happens + via scopedAllocPop() so will not be output. */ + static debugFuncInstall = false; + + /** Function used for debug output. */ + static debugOut = console.debug.bind(console); + + static bindScopes = [ + 'transient', 'context', 'singleton', 'permanent' + ]; + + /* Dummy impl. Overwritten per-instance as needed. */ + contextKey(argv,argIndex){ + return this; + } + + /* Returns this objects mapping for the given context key, in the + form of an an array, creating the mapping if needed. The key + may be anything suitable for use in a Map. */ + contextMap(key){ + const cm = (this.__cmap || (this.__cmap = new Map)); + let rc = cm.get(key); + if(undefined===rc) cm.set(key, (rc = [])); + return rc; + } + + /** + Gets called via xWrap() to "convert" v to a WASM-bound function + pointer. If v is one of (a pointer, null, undefined) then + (v||0) is returned and any earlier function installed by this + mapping _might_, depending on how it's bound, be uninstalled. + If v is not one of those types, it must be a Function, for + which it creates (if needed) a WASM function binding and + returns the WASM pointer to that binding. If this instance is + not in 'transient' mode, it will remember the binding for at + least the next call, to avoid recreating the function binding + unnecessarily. + + If it's passed a pointer(ish) value for v, it does _not_ + perform any function binding, so this object's bindMode is + irrelevant for such cases. + + See the parent class's convertArg() docs for details on what + exactly the 2nd and 3rd arguments are. + */ + convertArg(v,argv,argIndex){ + //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.signature,this.transient,v); + let pair = this.singleton; + if(!pair && this.isContext){ + pair = this.contextMap(this.contextKey(argv,argIndex)); + } + if(pair && pair[0]===v) return pair[1]; + if(v instanceof Function){ + /* Install a WASM binding and return its pointer. */ + if(this.callProxy) v = this.callProxy(v); + const fp = __installFunction(v, this.signature, this.isTransient); + if(FuncPtrAdapter.debugFuncInstall){ + FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this, + this.contextKey(argv,argIndex), '@'+fp, v); + } + if(pair){ + /* Replace existing stashed mapping */ + if(pair[1]){ + if(FuncPtrAdapter.debugFuncInstall){ + FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, + this.contextKey(argv,argIndex), '@'+pair[1], v); + } + try{target.uninstallFunction(pair[1])} + catch(e){/*ignored*/} + } + pair[0] = v; + pair[1] = fp; + } + return fp; + }else if(target.isPtr(v) || null===v || undefined===v){ + if(pair && pair[1] && pair[1]!==v){ + /* uninstall stashed mapping and replace stashed mapping with v. */ + if(FuncPtrAdapter.debugFuncInstall){ + FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, + this.contextKey(argv,argIndex), '@'+pair[1], v); + } + try{target.uninstallFunction(pair[1])} + catch(e){/*ignored*/} + pair[0] = pair[1] = (v | 0); + } + return v || 0; + }else{ + throw new TypeError("Invalid FuncPtrAdapter argument type. "+ + "Expecting a function pointer or a "+ + (this.name ? this.name+' ' : '')+ + "function matching signature "+ + this.signature+"."); + } + }/*convertArg()*/ + }/*FuncPtrAdapter*/; const __xArgAdapterCheck = - (t)=>xcv.arg[t] || toss("Argument adapter not found:",t); + (t)=>xArg.get(t) || toss("Argument adapter not found:",t); const __xResultAdapterCheck = - (t)=>xcv.result[t] || toss("Result adapter not found:",t); - - cache.xWrap.convertArg = (t,v)=>__xArgAdapterCheck(t)(v); + (t)=>xResult.get(t) || toss("Result adapter not found:",t); + + cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args); + cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args); + cache.xWrap.convertResult = (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); + cache.xWrap.convertResultNoCheck = + (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined)); /** - Creates a wrapper for the WASM-exported function fname. Uses - xGet() to fetch the exported function (which throws on - error) and returns either that function or a wrapper for that - function which converts the JS-side argument types into WASM-side - types and converts the result type. If the function takes no - arguments and resultType is `null` then the function is returned - as-is, else a wrapper is created for it to adapt its arguments - and result value, as described below. + Creates a wrapper for another function which converts the arguments + of the wrapper to argument types accepted by the wrapped function, + then converts the wrapped function's result to another form + for the wrapper. - (If you're familiar with Emscripten's ccall() and cwrap(), this - function is essentially cwrap() on steroids.) + The first argument must be one of: - This function's arguments are: + - A JavaScript function. + - The name of a WASM-exported function. In the latter case xGet() + is used to fetch the exported function, which throws if it's not + found. + - A pointer into the indirect function table. e.g. a pointer + returned from target.installFunction(). - - fname: the exported function's name. xGet() is used to fetch - this, so will throw if no exported function is found with that - name. - - - resultType: the name of the result type. A literal `null` means - to return the original function's value as-is (mnemonic: there - is "null" conversion going on). Literal `undefined` or the - string `"void"` mean to ignore the function's result and return - `undefined`. Aside from those two special cases, it may be one - of the values described below or any mapping installed by the - client using xWrap.resultAdapter(). + It returns either the passed-in function or a wrapper for that + function which converts the JS-side argument types into WASM-side + types and converts the result type. + + The second argument, `resultType`, describes the conversion for + the wrapped functions result. A literal `null` or the string + `'null'` both mean to return the original function's value as-is + (mnemonic: there is "null" conversion going on). Literal + `undefined` or the string `"void"` both mean to ignore the + function's result and return `undefined`. Aside from those two + special cases, the `resultType` value may be one of the values + described below or any mapping installed by the client using + xWrap.resultAdapter(). If passed 3 arguments and the final one is an array, that array must contain a list of type names (see below) for adapting the @@ -1357,6 +1816,12 @@ self.WhWasmUtilInstaller = function(target){ xWrap('funcname', 'i32', ['string', 'f64']); ``` + This function enforces that the given list of arguments has the + same arity as the being-wrapped function (as defined by its + `length` property) and it will throw if that is not the case. + Similarly, the created wrapper will throw if passed a differing + argument count. + Type names are symbolic names which map the arguments to an adapter function to convert, if needed, the value before passing it on to WASM or to convert a return result from WASM. The list @@ -1369,10 +1834,13 @@ self.WhWasmUtilInstaller = function(target){ - `N*` (args): a type name in the form `N*`, where N is a numeric type name, is treated the same as WASM pointer. - - `*` and `pointer` (args): have multple semantics. They - behave exactly as described below for `string` args. + - `*` and `pointer` (args): are assumed to be WASM pointer values + and are returned coerced to an appropriately-sized pointer + value (i32 or i64). Non-numeric values will coerce to 0 and + out-of-range values will have undefined results (just as with + any pointer misuse). - - `*` and `pointer` (results): are aliases for the current + - `*` and `pointer` (results): aliases for the current WASM pointer numeric type. - `**` (args): is simply a descriptive alias for the WASM pointer @@ -1383,7 +1851,7 @@ self.WhWasmUtilInstaller = function(target){ true. - `f32` (`float`), `f64` (`double`) (args and results): pass - their argument to Number(). i.e. the adaptor does not currently + their argument to Number(). i.e. the adapter does not currently distinguish between the two types of floating-point numbers. - `number` (results): converts the result to a JS Number using @@ -1393,6 +1861,10 @@ self.WhWasmUtilInstaller = function(target){ Non-numeric conversions include: + - `null` literal or `"null"` string (args and results): perform + no translation and pass the arg on as-is. This is primarily + useful for results but may have a use or two for arguments. + - `string` or `utf8` (args): has two different semantics in order to accommodate various uses of certain C APIs (e.g. output-style strings)... @@ -1407,11 +1879,11 @@ self.WhWasmUtilInstaller = function(target){ client has already allocated and it's passed on as a WASM pointer. - - `string` or `utf8` (results): treats the result value as a - const C-string, encoded as UTF-8, copies it to a JS string, - and returns that JS string. + - `string` or `utf8` (results): treats the result value as a + const C-string, encoded as UTF-8, copies it to a JS string, + and returns that JS string. - - `string:free` or `utf8:free) (results): treats the result value + - `string:dealloc` or `utf8:dealloc) (results): treats the result value as a non-const UTF-8 C-string, ownership of which has just been transfered to the caller. It copies the C-string to a JS string, frees the C-string, and returns the JS string. If such @@ -1422,8 +1894,8 @@ self.WhWasmUtilInstaller = function(target){ required. For example: ```js - target.xWrap.resultAdaptor('string:my_free',(i)=>{ - try { return i ? target.cstringToJs(i) : null } + target.xWrap.resultAdapter('string:my_free',(i)=>{ + try { return i ? target.cstrToJs(i) : null } finally{ target.exports.my_free(i) } }; ``` @@ -1432,9 +1904,9 @@ self.WhWasmUtilInstaller = function(target){ returns the result of passing the converted-to-JS string to JSON.parse(). Returns `null` if the C-string is a NULL pointer. - - `json:free` (results): works exactly like `string:free` but + - `json:dealloc` (results): works exactly like `string:dealloc` but returns the same thing as the `json` adapter. Note the - warning in `string:free` regarding maching allocators and + warning in `string:dealloc` regarding maching allocators and deallocators. The type names for results and arguments are validated when @@ -1442,17 +1914,22 @@ self.WhWasmUtilInstaller = function(target){ exception. Clients may map their own result and argument adapters using - xWrap.resultAdapter() and xWrap.argAdaptor(), noting that not all + xWrap.resultAdapter() and xWrap.argAdapter(), noting that not all type conversions are valid for both arguments _and_ result types as they often have different memory ownership requirements. + Design note: the ability to pass in a JS function as the first + argument is of relatively limited use, primarily for testing + argument and result converters. JS functions, by and large, will + not want to deal with C-type arguments. + TODOs: - Figure out how/whether we can (semi-)transparently handle pointer-type _output_ arguments. Those currently require explicit handling by allocating pointers, assigning them before - the call using setMemValue(), and fetching them with - getMemValue() after the call. We may be able to automate some + the call using poke(), and fetching them with + peek() after the call. We may be able to automate some or all of that. - Figure out whether it makes sense to extend the arg adapter @@ -1466,49 +1943,78 @@ self.WhWasmUtilInstaller = function(target){ abstracting it into this API (and taking on the associated costs) may well not make good sense. */ - target.xWrap = function(fname, resultType, ...argTypes){ + target.xWrap = function(fArg, resultType, ...argTypes){ if(3===arguments.length && Array.isArray(arguments[2])){ argTypes = arguments[2]; } - const xf = target.xGet(fname); - if(argTypes.length!==xf.length) __argcMismatch(fname, xf.length); + if(target.isPtr(fArg)){ + fArg = target.functionEntry(fArg) + || toss("Function pointer not found in WASM function table."); + } + const fIsFunc = (fArg instanceof Function); + const xf = fIsFunc ? fArg : target.xGet(fArg); + if(fIsFunc) fArg = xf.name || 'unnamed function'; + if(argTypes.length!==xf.length) __argcMismatch(fArg, xf.length); if((null===resultType) && 0===xf.length){ - /* Func taking no args with an as-is return. We don't need a wrapper. */ + /* Func taking no args with an as-is return. We don't need a wrapper. + We forego the argc check here, though. */ return xf; } /*Verify the arg type conversions are valid...*/; if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType); - argTypes.forEach(__xArgAdapterCheck); + for(const t of argTypes){ + if(t instanceof AbstractArgAdapter) xArg.set(t, (...args)=>t.convertArg(...args)); + else __xArgAdapterCheck(t); + } + const cxw = cache.xWrap; if(0===xf.length){ // No args to convert, so we can create a simpler wrapper... return (...args)=>(args.length - ? __argcMismatch(fname, xf.length) - : cache.xWrap.convertResult(resultType, xf.call(null))); + ? __argcMismatch(fArg, xf.length) + : cxw.convertResult(resultType, xf.call(null))); } return function(...args){ - if(args.length!==xf.length) __argcMismatch(fname, xf.length); + if(args.length!==xf.length) __argcMismatch(fArg, xf.length); const scope = target.scopedAllocPush(); try{ - const rc = xf.apply(null,args.map((v,i)=>cache.xWrap.convertArg(argTypes[i], v))); - return cache.xWrap.convertResult(resultType, rc); + /* + Maintenance reminder re. arguments passed to convertArg(): + The public interface of argument adapters is that they take + ONE argument and return a (possibly) converted result for + it. The passing-on of arguments after the first is an + internal implementation detail for the sake of + AbstractArgAdapter, and not to be relied on or documented + for other cases. The fact that this is how + AbstractArgAdapter.convertArgs() gets its 2nd+ arguments, + and how FuncPtrAdapter.contextKey() gets its args, is also + an implementation detail and subject to change. i.e. the + public interface of 1 argument is stable. The fact that any + arguments may be passed in after that one, and what those + arguments are, is _not_ part of the public interface and is + _not_ stable. + */ + for(const i in args) args[i] = cxw.convertArgNoCheck( + argTypes[i], args[i], args, i + ); + return cxw.convertResultNoCheck(resultType, xf.apply(null,args)); }finally{ target.scopedAllocPop(scope); } }; }/*xWrap()*/; - /** Internal impl for xWrap.resultAdapter() and argAdaptor(). */ + /** Internal impl for xWrap.resultAdapter() and argAdapter(). */ const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){ if('string'===typeof typeName){ - if(1===argc) return xcvPart[typeName]; + if(1===argc) return xcvPart.get(typeName); else if(2===argc){ if(!adapter){ - delete xcvPart[typeName]; + delete xcvPart.get(typeName); return func; }else if(!(adapter instanceof Function)){ toss(modeName,"requires a function argument."); } - xcvPart[typeName] = adapter; + xcvPart.set(typeName, adapter); return func; } } @@ -1545,7 +2051,7 @@ self.WhWasmUtilInstaller = function(target){ */ target.xWrap.resultAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, - 'resultAdaptor()', xcv.result); + 'resultAdapter()', xResult); }; /** @@ -1575,19 +2081,20 @@ self.WhWasmUtilInstaller = function(target){ */ target.xWrap.argAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, - 'argAdaptor()', xcv.arg); + 'argAdapter()', xArg); }; + target.xWrap.FuncPtrAdapter = xArg.FuncPtrAdapter; + /** Functions like xCall() but performs argument and result type - conversions as for xWrap(). The first argument is the name of the - exported function to call. The 2nd its the name of its result - type, as documented for xWrap(). The 3rd is an array of argument - type name, as documented for xWrap() (use a falsy value or an - empty array for nullary functions). The 4th+ arguments are - arguments for the call, with the special case that if the 4th - argument is an array, it is used as the arguments for the - call. Returns the converted result of the call. + conversions as for xWrap(). The first, second, and third + arguments are as documented for xWrap(), except that the 3rd + argument may be either a falsy value or empty array to represent + nullary functions. The 4th+ arguments are arguments for the call, + with the special case that if the 4th argument is an array, it is + used as the arguments for the call. Returns the converted result + of the call. This is just a thin wrapper around xWrap(). If the given function is to be called more than once, it's more efficient to use @@ -1596,11 +2103,41 @@ self.WhWasmUtilInstaller = function(target){ arguably more efficient because it will hypothetically free the wrapper function quickly. */ - target.xCallWrapped = function(fname, resultType, argTypes, ...args){ + target.xCallWrapped = function(fArg, resultType, argTypes, ...args){ if(Array.isArray(arguments[3])) args = arguments[3]; - return target.xWrap(fname, resultType, argTypes||[]).apply(null, args||[]); + return target.xWrap(fArg, resultType, argTypes||[]).apply(null, args||[]); }; + /** + This function is ONLY exposed in the public API to facilitate + testing. It should not be used in application-level code, only + in test code. + + Expects to be given (typeName, value) and returns a conversion + of that value as has been registered using argAdapter(). + It throws if no adapter is found. + + ACHTUNG: the adapter may require that a scopedAllocPush() is + active and it may allocate memory within that scope. It may also + require additional arguments, depending on the type of + conversion. + */ + target.xWrap.testConvertArg = cache.xWrap.convertArg; + + /** + This function is ONLY exposed in the public API to facilitate + testing. It should not be used in application-level code, only + in test code. + + Expects to be given (typeName, value) and returns a conversion + of that value as has been registered using resultAdapter(). + It throws if no adapter is found. + + ACHTUNG: the adapter may allocate memory which the caller may need + to know how to free. + */ + target.xWrap.testConvertResult = cache.xWrap.convertResult; + return target; }; diff --git a/ext/wasm/demo-worker1-promiser.js b/ext/wasm/demo-worker1-promiser.js index a65cc31b6..ef955403c 100644 --- a/ext/wasm/demo-worker1-promiser.js +++ b/ext/wasm/demo-worker1-promiser.js @@ -76,9 +76,7 @@ await wtest('config-get', (ev)=>{ const r = ev.result; log('sqlite3.config subset:', r); - T.assert('boolean' === typeof r.bigIntEnabled) - .assert('string'===typeof r.wasmfsOpfsDir) - .assert('boolean' === typeof r.wasmfsOpfsEnabled); + T.assert('boolean' === typeof r.bigIntEnabled); sqConfig = r; }); logHtml('', @@ -213,7 +211,7 @@ sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2", callback: resultRowTest3, columnNames: [], - rowMode: ':bar' + rowMode: '$bar' }, function(ev){ log("exec() result row:",ev); T.assert(2===resultRowTest3.counter); diff --git a/ext/wasm/dist.make b/ext/wasm/dist.make index 5aee8af77..7073c24b7 100644 --- a/ext/wasm/dist.make +++ b/ext/wasm/dist.make @@ -6,43 +6,47 @@ # 'make dist' rules for creating a distribution archive of the WASM/JS # pieces, noting that we only build a dist of the built files, not the # numerous pieces required to build them. +# +# Use 'make snapshot' to create "snapshot" releases. They use a +# distinctly different zip file and top directory name to distinguish +# them from release builds. ####################################################################### MAKEFILE.dist := $(lastword $(MAKEFILE_LIST)) ######################################################################## -# Chicken/egg situation: we need $(bin.version-info) to get the version -# info for the archive name, but that binary may not yet be built, and -# won't be built until we expand the dependencies. We have to use a -# temporary name for the archive. -dist-name = sqlite-wasm-TEMP -#ifeq (0,1) -# $(info WARNING *******************************************************************) -# $(info ** Be sure to create the desired build configuration before creating the) -# $(info ** distribution archive. Use one of the following targets to do so:) -# $(info **) -# $(info ** o2: builds with -O2, resulting in the fastest builds) -# $(info ** oz: builds with -Oz, resulting in the smallest builds) -# $(info /WARNING *******************************************************************) -#endif +# Chicken/egg situation: we need $(bin.version-info) to get the +# version info for the archive name, but that binary may not yet be +# built, and won't be built until we expand the dependencies. Thus we +# have to use a temporary name for the archive until we can get +# that binary built. +ifeq (,$(filter snapshot,$(MAKECMDGOALS))) +dist-name-prefix := sqlite-wasm +else +dist-name-prefix := sqlite-wasm-snapshot-$(shell /usr/bin/date +%Y%m%d) +endif +dist-name := $(dist-name-prefix)-TEMP ######################################################################## -# dist.build must be the name of a target which triggers the -# build of the files to be packed into the dist archive. The -# intention is that it be one of (o0, o1, o2, o3, os, oz), each of -# which uses like-named -Ox optimization level flags. The o2 target -# provides the best overall runtime speeds. The oz target provides -# slightly slower speeds (roughly 10%) with significantly smaller WASM -# file sizes. Note that -O2 (the o2 target) results in faster binaries -# than both -O3 and -Os (the o3 and os targets) in all tests run to -# date. -dist.build ?= oz +# dist.build must be the name of a target which triggers the build of +# the files to be packed into the dist archive. The intention is that +# it be one of (o0, o1, o2, o3, os, oz), each of which uses like-named +# -Ox optimization level flags. The o2 target provides the best +# overall runtime speeds. The oz target provides slightly slower +# speeds (roughly 10%) with significantly smaller WASM file +# sizes. Note that -O2 (the o2 target) results in faster binaries than +# both -O3 and -Os (the o3 and os targets) in all tests run to +# date. Our general policy is that we want the smallest binaries for +# dist zip files, so use the oz build unless there is a compelling +# reason not to. +dist.build ?= qoz dist-dir.top := $(dist-name) dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout)) dist-dir.common := $(dist-dir.top)/common dist.top.extras := \ demo-123.html demo-123-worker.html demo-123.js \ - tester1.html tester1-worker.html tester1.js \ + tester1.html tester1-worker.html tester1-esm.html \ + tester1.js tester1.mjs \ demo-jsstorage.html demo-jsstorage.js \ demo-worker1.html demo-worker1.js \ demo-worker1-promiser.html demo-worker1-promiser.js @@ -51,7 +55,22 @@ dist.common.extras := \ $(wildcard $(dir.common)/*.css) \ $(dir.common)/SqliteTestUtil.js -.PHONY: dist +.PHONY: dist snapshot +# DIST_STRIP_COMMENTS $(call)able to be used in stripping C-style +# from the dist copies of certain files. +# +# $1 = source js file +# $2 = flags for $(bin.stripcomments) +define DIST_STRIP_COMMENTS +$(bin.stripccomments) $(2) < $(1) > $(dist-dir.jswasm)/$(notdir $(1)) || exit; +endef +# STRIP_K1.js = list of JS files which need to be passed through +# $(bin.stripcomments) with a single -k flag. +STRIP_K1.js := $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) \ + $(sqlite3-worker1-bundler-friendly.js) $(sqlite3-worker1-promiser-bundler-friendly.js) +# STRIP_K2.js = list of JS files which need to be passed through +# $(bin.stripcomments) with two -k flags. +STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) $(sqlite3-bundler-friendly.mjs) ######################################################################## # dist: create the end-user deliverable archive. # @@ -59,7 +78,7 @@ dist.common.extras := \ # $(dist.build) will depend on clean, having any deps on # $(dist-archive) which themselves may be cleaned up by the clean # target will lead to grief in parallel builds (-j #). Thus -# $(dist-target)'s deps must be trimmed to non-generated files or +# dist's deps must be trimmed to non-generated files or # files which are _not_ cleaned up by the clean target. # # Note that we require $(bin.version-info) in order to figure out the @@ -67,7 +86,7 @@ dist.common.extras := \ # target name equal to the archive name. dist: \ $(bin.stripccomments) $(bin.version-info) \ - $(dist.build) \ + $(dist.build) $(STRIP_K1.js) $(STRIP_K2.js) \ $(MAKEFILE) $(MAKEFILE.dist) @echo "Making end-user deliverables..." @rm -fr $(dist-dir.top) @@ -76,12 +95,12 @@ dist: \ @cp -p README-dist.txt $(dist-dir.top)/README.txt @cp -p index-dist.html $(dist-dir.top)/index.html @cp -p $(dist.jswasm.extras) $(dist-dir.jswasm) - @$(bin.stripccomments) -k -k < $(sqlite3.js) \ - > $(dist-dir.jswasm)/$(notdir $(sqlite3.js)) + @$(foreach JS,$(STRIP_K1.js),$(call DIST_STRIP_COMMENTS,$(JS),-k)) + @$(foreach JS,$(STRIP_K2.js),$(call DIST_STRIP_COMMENTS,$(JS),-k -k)) @cp -p $(dist.common.extras) $(dist-dir.common) @set -e; \ vnum=$$($(bin.version-info) --download-version); \ - vdir=sqlite-wasm-$$vnum; \ + vdir=$(dist-name-prefix)-$$vnum; \ arczip=$$vdir.zip; \ echo "Making $$arczip ..."; \ rm -fr $$arczip $$vdir; \ @@ -91,7 +110,17 @@ dist: \ ls -la $$arczip; \ set +e; \ unzip -lv $$arczip || echo "Missing unzip app? Not fatal." - +ifeq (,$(wasm.docs.found)) +snapshot: dist + @echo "To upload the snapshot build to the wasm docs server:"; \ + echo "1) move $(dist-name-prefix)*.zip to the top of a wasm docs checkout."; \ + echo "2) run 'make uv-sync'" +else +snapshot: dist + @echo "Moving snapshot to [$(wasm.docs.found)]..."; \ + mv $(dist-name-prefix)*.zip $(wasm.docs.found)/. + @echo "Run 'make uv-sync' from $(wasm.docs.found) to upload it." +endif # We need a separate `clean` rule to account for weirdness in # a sub-make, where we get a copy of the $(dist-name) dir # copied into the new $(dist-name) dir. diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make index 43e6941f5..7facd7e9e 100644 --- a/ext/wasm/fiddle.make +++ b/ext/wasm/fiddle.make @@ -36,9 +36,9 @@ fiddle.emcc-flags = \ -sWASM_BIGINT=$(emcc.WASM_BIGINT) \ -sEXPORT_NAME=$(sqlite3.js.init-func) \ -Wno-limited-postlink-optimizations \ - $(sqlite3.js.flags.--post-js) \ $(emcc.exportedRuntimeMethods) \ -sEXPORTED_FUNCTIONS=@$(abspath $(EXPORTED_FUNCTIONS.fiddle)) \ + -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory \ $(SQLITE_OPT) $(SHELL_OPT) \ -DSQLITE_SHELL_FIDDLE # -D_POSIX_C_SOURCE is needed for strdup() with emcc @@ -58,12 +58,12 @@ fiddle.SOAP.js := $(dir.fiddle)/$(notdir $(SOAP.js)) $(fiddle.SOAP.js): $(SOAP.js) cp $< $@ -$(eval $(call call-make-pre-js,fiddle-module)) +$(eval $(call call-make-pre-post,fiddle-module,vanilla)) $(fiddle-module.js): $(MAKEFILE) $(MAKEFILE.fiddle) \ $(EXPORTED_FUNCTIONS.fiddle) \ - $(fiddle.cses) $(pre-post-fiddle-module.deps) $(fiddle.SOAP.js) + $(fiddle.cses) $(pre-post-fiddle-module.deps.vanilla) $(fiddle.SOAP.js) $(emcc.bin) -o $@ $(fiddle.emcc-flags) \ - $(pre-post-common.flags) $(pre-post-fiddle-module.flags) \ + $(pre-post-fiddle-module.flags.vanilla) \ $(fiddle.cses) $(maybe-wasm-strip) $(fiddle-module.wasm) gzip < $@ > $@.gz diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js index a60b79ab2..e239cbf51 100644 --- a/ext/wasm/fiddle/fiddle-worker.js +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -46,7 +46,7 @@ about module loading status so that, e.g., the main thread can update a progress widget and DTRT when the module is finished loading and available for work. Status messages come in the form - + {type:'module', data:{ type:'status', data: {text:string|null, step:1-based-integer} @@ -370,6 +370,9 @@ */ sqlite3InitModule(fiddleModule).then((_sqlite3)=>{ sqlite3 = _sqlite3; + console.warn("Installing sqlite3 module globally (in Worker)", + "for use in the dev console."); + self.sqlite3 = sqlite3; const dbVfs = sqlite3.wasm.xWrap('fiddle_db_vfs', "*", ['string']); fiddleModule.fsUnlink = (fn)=>{ return sqlite3.wasm.sqlite3_wasm_vfs_unlink(dbVfs(0), fn); diff --git a/ext/wasm/index-dist.html b/ext/wasm/index-dist.html index 6b038c82a..36e21117e 100644 --- a/ext/wasm/index-dist.html +++ b/ext/wasm/index-dist.html @@ -19,6 +19,10 @@ header { font-size: 130%; font-weight: bold; + background: #044a64; + color: white; + padding: 0.5em; + border-radius: 0.25em; } .hidden, .initially-hidden { position: absolute !important; @@ -37,13 +41,25 @@
      and the individual pages be started in their own tab. Warnings and Caveats:
        -
      • Some of these pages require that the web server emit the - so-called - COOP - and - COEP - headers. althttpd requires the - -enable-sab flag for that. +
      • All of these pages must be served via an HTTP + server. Browsers do not support loading WASM files via + file:// URLs.
      • +
      • Any OPFS-related pages or tests require: +
          +
        • That the web server emit the so-called + COOP + and + COEP + headers. althttpd requires the + -enable-sab flag for that. +
        • +
        • A very recent version of a Chromium-based browser + (v102 at least, possibly newer). OPFS support in the + other major browsers is pending. Development and testing + is currently done against a dev-channel release of + Chrome (v111 as of 2023-02-10). +
        • +
      @@ -56,6 +72,16 @@ utility code.
    4. tester1-worker: same thing but running in a Worker.
    5. +
    6. tester1-esm: same as + tester1 but loads sqlite3 in the main thread via + an ES6 module. +
    7. +
    8. tester1-worker?esm: + same as tester1-esm but loads a Worker Module which + then loads the sqlite3 API via an ES6 module. Note that + not all browsers permit loading modules in Worker + threads. +
    9. Higher-level apps and demos... diff --git a/ext/wasm/index.html b/ext/wasm/index.html index 044cd1360..a9872a9ea 100644 --- a/ext/wasm/index.html +++ b/ext/wasm/index.html @@ -8,6 +8,14 @@ sqlite3 WASM Testing Page Index +
      sqlite3 WASM test pages

      Below is the list of test pages for the sqlite3 WASM @@ -18,23 +26,25 @@
      and the individual tests be started in their own tab. Warnings and Caveats:
        -
      • Some of these pages require that - the web server emit the so-called - COOP - and - COEP - headers. althttpd requires the - -enable-sab flag for that. -
      • -
      • Any OPFS-related pages require very recent version of - Chrome or Chromium (v102 at least, possibly newer). OPFS - support in the other major browsers is pending. Development - and testing is currently done against a dev-channel release - of Chrome (v107 as of 2022-09-26). -
      • -
      • Whether or not WASMFS/OPFS support is enabled on any given - page may depend on build-time options which are off by - default. +
      • All of these pages must be served via an HTTP + server. Browsers do not support loading WASM files via + file:// URLs.
      • +
      • Any OPFS-related pages or tests require: +
          +
        • That the web server emit the so-called + COOP + and + COEP + headers. althttpd requires the + -enable-sab flag for that. +
        • +
        • A very recent version of a + Chromium-based browser (v102 at least, possibly newer). OPFS + support in the other major browsers is pending. Development + and testing is currently done against a dev-channel release + of Chrome (v111 as of 2023-02-10). +
        • +
      @@ -46,7 +56,17 @@ regression tests for the various APIs and surrounding utility code.
    10. tester1-worker: same thing - but running in a Worker.
    11. + but running in a Worker. +
    12. tester1-esm: same as + tester1 but loads sqlite3 in the main thread via + an ES6 module. +
    13. +
    14. tester1-worker?esm: + same as tester1-esm but loads a Worker Module which + then loads the sqlite3 API via an ES6 module. Note that + not all browsers permit loading modules in Worker + threads. +
    15. High-level apps and demos... @@ -72,11 +92,9 @@
    16. speedtest1 ports (sqlite3's primary benchmarking tool)...
    17. @@ -86,10 +104,6 @@ a high-level overview of the symbols exposed by the JS module.
    18. batch-runner: runs batches of SQL exported from speedtest1.
    19. -
    20. test-opfs-vfs (same with verbose output and sanity-checking tests) is an @@ -98,8 +112,23 @@ synchronous sqlite3_vfs interface and the async OPFS impl.
    21. +
    22. OPFS concurrency + tests using multiple workers. +
    23. +
  • @@ -108,7 +137,7 @@ diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index dee7258c5..d4ec719fb 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -10,9 +10,12 @@ *********************************************************************** - The Jaccwabyt API is documented in detail in an external file. + The Jaccwabyt API is documented in detail in an external file, + _possibly_ called jaccwabyt.md in the same directory as this file. - Project home: https://fossil.wanderinghorse.net/r/jaccwabyt + Project homes: + - https://fossil.wanderinghorse.net/r/jaccwabyt + - https://sqlite.org/src/dir/ext/wasm/jaccwabyt */ 'use strict'; @@ -61,7 +64,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ BigInt = self['BigInt'], BigInt64Array = self['BigInt64Array'], /* Undocumented (on purpose) config options: */ - functionTable = config.functionTable/*EXPERIMENTAL, undocumented*/, ptrSizeof = config.ptrSizeof || 4, ptrIR = config.ptrIR || 'i32' ; @@ -121,6 +123,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ at SIG s[0]. Throws for an unknown SIG. */ const sigIR = function(s){ switch(sigLetter(s)){ + case 'c': case 'C': return 'i8'; case 'i': return 'i32'; case 'p': case 'P': case 's': return ptrIR; case 'j': return 'i64'; @@ -129,33 +132,9 @@ self.Jaccwabyt = function StructBinderFactory(config){ } toss("Unhandled signature IR:",s); }; - /** Returns the sizeof value for the given SIG. Throws for an - unknown SIG. */ - const sigSizeof = function(s){ - switch(sigLetter(s)){ - case 'i': return 4; - case 'p': case 'P': case 's': return ptrSizeof; - case 'j': return 8; - case 'f': return 4 /* C-side floats, not JS-side */; - case 'd': return 8; - } - toss("Unhandled signature sizeof:",s); - }; + const affirmBigIntArray = BigInt64Array ? ()=>true : ()=>toss('BigInt64Array is not available.'); - /** Returns the (signed) TypedArray associated with the type - described by the given SIG. Throws for an unknown SIG. */ - /********** - const sigTypedArray = function(s){ - switch(sigIR(s)) { - case 'i32': return Int32Array; - case 'i64': return affirmBigIntArray() && BigInt64Array; - case 'float': return Float32Array; - case 'double': return Float64Array; - } - toss("Unhandled signature TypedArray:",s); - }; - **************/ /** Returns the name of a DataView getter method corresponding to the given SIG. */ const sigDVGetter = function(s){ @@ -168,6 +147,8 @@ self.Jaccwabyt = function StructBinderFactory(config){ break; } case 'i': return 'getInt32'; + case 'c': return 'getInt8'; + case 'C': return 'getUint8'; case 'j': return affirmBigIntArray() && 'getBigInt64'; case 'f': return 'getFloat32'; case 'd': return 'getFloat64'; @@ -186,6 +167,8 @@ self.Jaccwabyt = function StructBinderFactory(config){ break; } case 'i': return 'setInt32'; + case 'c': return 'setInt8'; + case 'C': return 'setUint8'; case 'j': return affirmBigIntArray() && 'setBigInt64'; case 'f': return 'setFloat32'; case 'd': return 'setFloat64'; @@ -199,7 +182,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ */ const sigDVSetWrapper = function(s){ switch(sigLetter(s)) { - case 'i': case 'f': case 'd': return Number; + case 'i': case 'f': case 'c': case 'C': case 'd': return Number; case 'j': return affirmBigIntArray() && BigInt; case 'p': case 'P': case 's': switch(ptrSizeof){ @@ -211,36 +194,14 @@ self.Jaccwabyt = function StructBinderFactory(config){ toss("Unhandled DataView set wrapper for signature:",s); }; + /** Returns the given struct and member name in a form suitable for + debugging and error output. */ const sPropName = (s,k)=>s+'::'+k; const __propThrowOnSet = function(structName,propName){ return ()=>toss(sPropName(structName,propName),"is read-only."); }; - /** - When C code passes a pointer of a bound struct to back into - a JS function via a function pointer struct member, it - arrives in JS as a number (pointer). - StructType.instanceForPointer(ptr) can be used to get the - instance associated with that pointer, and __ptrBacklinks - holds that mapping. WeakMap keys must be objects, so we - cannot use a weak map to map pointers to instances. We use - the StructType constructor as the WeakMap key, mapped to a - plain, prototype-less Object which maps the pointers to - struct instances. That arrangement gives us a - per-StructType type-safe way to resolve pointers. - */ - const __ptrBacklinks = new WeakMap(); - /** - Similar to __ptrBacklinks but is scoped at the StructBinder - level and holds pointer-to-object mappings for all struct - instances created by any struct from any StructFactory - which this specific StructBinder has created. The intention - of this is to help implement more transparent handling of - pointer-type property resolution. - */ - const __ptrBacklinksGlobal = Object.create(null); - /** In order to completely hide StructBinder-bound struct pointers from JS code, we store them in a scope-local @@ -261,17 +222,13 @@ self.Jaccwabyt = function StructBinderFactory(config){ const __freeStruct = function(ctor, obj, m){ if(!m) m = __instancePointerMap.get(obj); if(m) { - if(obj.ondispose instanceof Function){ - try{obj.ondispose()} - catch(e){ - /*do not rethrow: destructors must not throw*/ - console.warn("ondispose() for",ctor.structName,'@', - m,'threw. NOT propagating it.',e); - } - }else if(Array.isArray(obj.ondispose)){ - obj.ondispose.forEach(function(x){ + __instancePointerMap.delete(obj); + if(Array.isArray(obj.ondispose)){ + let x; + while((x = obj.ondispose.shift())){ try{ if(x instanceof Function) x.call(obj); + else if(x instanceof StructType) x.dispose(); else if('number' === typeof x) dealloc(x); // else ignore. Strings are permitted to annotate entries // to assist in debugging. @@ -279,12 +236,16 @@ self.Jaccwabyt = function StructBinderFactory(config){ console.warn("ondispose() for",ctor.structName,'@', m,'threw. NOT propagating it.',e); } - }); + } + }else if(obj.ondispose instanceof Function){ + try{obj.ondispose()} + catch(e){ + /*do not rethrow: destructors must not throw*/ + console.warn("ondispose() for",ctor.structName,'@', + m,'threw. NOT propagating it.',e); + } } delete obj.ondispose; - delete __ptrBacklinks.get(ctor)[m]; - delete __ptrBacklinksGlobal[m]; - __instancePointerMap.delete(obj); if(ctor.debugFlags.__flags.dealloc){ log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), ctor.structName,"instance:", @@ -300,7 +261,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ iterable: false, value: v}}; /** Allocates obj's memory buffer based on the size defined in - DEF.sizeof. */ + ctor.structInfo.sizeof. */ const __allocStruct = function(ctor, obj, m){ let fill = !m; if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); @@ -316,8 +277,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ } if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof); __instancePointerMap.set(obj, m); - __ptrBacklinks.get(ctor)[m] = obj; - __ptrBacklinksGlobal[m] = obj; }catch(e){ __freeStruct(ctor, obj, m); throw e; @@ -339,7 +298,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ if tossIfNotFound is true, else returns undefined if not found. The given name may be either the name of the structInfo.members key (faster) or the key as modified by the - memberPrefix/memberSuffix settings. + memberPrefix and memberSuffix settings. */ const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){ let m = structInfo.members[memberName]; @@ -361,21 +320,11 @@ self.Jaccwabyt = function StructBinderFactory(config){ framework's native format or in Emscripten format. */ const __memberSignature = function f(obj,memberName,emscriptenFormat=false){ - if(!f._) f._ = (x)=>x.replace(/[^vipPsjrd]/g,"").replace(/[pPs]/g,'i'); + if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i'); const m = __lookupMember(obj.structInfo, memberName, true); return emscriptenFormat ? f._(m.signature) : m.signature; }; - /** - Returns the instanceForPointer() impl for the given - StructType constructor. - */ - const __instanceBacklinkFactory = function(ctor){ - const b = Object.create(null); - __ptrBacklinks.set(ctor, b); - return (ptr)=>b[ptr]; - }; - const __ptrPropDescriptor = { configurable: false, enumerable: false, get: function(){return __instancePointerMap.get(this)}, @@ -388,7 +337,9 @@ self.Jaccwabyt = function StructBinderFactory(config){ /** Impl of X.memberKeys() for StructType and struct ctors. */ const __structMemberKeys = rop(function(){ const a = []; - Object.keys(this.structInfo.members).forEach((k)=>a.push(this.memberKey(k))); + for(const k of Object.keys(this.structInfo.members)){ + a.push(this.memberKey(k)); + } return a; }); @@ -454,15 +405,15 @@ self.Jaccwabyt = function StructBinderFactory(config){ Adds value v to obj.ondispose, creating ondispose, or converting it to an array, if needed. */ - const __addOnDispose = function(obj, v){ + const __addOnDispose = function(obj, ...v){ if(obj.ondispose){ - if(obj.ondispose instanceof Function){ + if(!Array.isArray(obj.ondispose)){ obj.ondispose = [obj.ondispose]; - }/*else assume it's an array*/ + } }else{ obj.ondispose = []; } - obj.ondispose.push(v); + obj.ondispose.push(...v); }; /** @@ -477,8 +428,9 @@ self.Jaccwabyt = function StructBinderFactory(config){ const mem = alloc(u.length+1); if(!mem) toss("Allocation error while duplicating string:",str); const h = heap(); - let i = 0; - for( ; i < u.length; ++i ) h[mem + i] = u[i]; + //let i = 0; + //for( ; i < u.length; ++i ) h[mem + i] = u[i]; + h.set(u, mem); h[mem + u.length] = 0; //log("allocCString @",mem," =",u); return mem; @@ -490,6 +442,10 @@ self.Jaccwabyt = function StructBinderFactory(config){ to free any prior memory, if appropriate. The newly-allocated string is added to obj.ondispose so will be freed when the object is disposed. + + The given name may be either the name of the structInfo.members + key (faster) or the key as modified by the memberPrefix and + memberSuffix settings. */ const __setMemberCString = function(obj, memberName, str){ const m = __lookupMember(obj.structInfo, memberName, true); @@ -544,13 +500,19 @@ self.Jaccwabyt = function StructBinderFactory(config){ return __setMemberCString(this, memberName, str); }) }); + // Function-type non-Property inherited members + Object.assign(StructType.prototype,{ + addOnDispose: function(...v){ + __addOnDispose(this,...v); + return this; + } + }); /** "Static" properties for StructType. */ Object.defineProperties(StructType, { allocCString: rop(__allocCString), - instanceForPointer: rop((ptr)=>__ptrBacklinksGlobal[ptr]), isA: rop((v)=>v instanceof StructType), hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), memberKey: __memberKeyProp @@ -570,7 +532,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ /*cache all available getters/setters/set-wrappers for direct reuse in each accessor function. */ f._ = {getters: {}, setters: {}, sw:{}}; - const a = ['i','p','P','s','f','d','v()']; + const a = ['i','c','C','p','P','s','f','d','v()']; if(bigIntEnabled) a.push('j'); a.forEach(function(v){ //const ir = sigIR(v); @@ -579,8 +541,8 @@ self.Jaccwabyt = function StructBinderFactory(config){ f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values for conversion */; }); - const rxSig1 = /^[ipPsjfd]$/, - rxSig2 = /^[vipPsjfd]\([ipPsjfd]*\)$/; + const rxSig1 = /^[ipPsjfdcC]$/, + rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; f.sigCheck = function(obj, name, key,sig){ if(Object.prototype.hasOwnProperty.call(obj, key)){ toss(obj.structName,'already has a property named',key+'.'); @@ -594,7 +556,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ f.sigCheck(ctor.prototype, name, key, descr.signature); descr.key = key; descr.name = name; - const sizeOf = sigSizeof(descr.signature); const sigGlyph = sigLetter(descr.signature); const xPropName = sPropName(ctor.prototype.structName,key); const dbg = ctor.prototype.debugFlags.__flags; @@ -610,16 +571,12 @@ self.Jaccwabyt = function StructBinderFactory(config){ prop.get = function(){ if(dbg.getter){ log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf); + xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof); } let rc = ( - new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) + new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) )[f._.getters[sigGlyph]](0, isLittleEndian); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); - if(rc && isAutoPtrSig(descr.signature)){ - rc = StructType.instanceForPointer(rc) || rc; - if(dbg.getter) log("debug.getter:",xPropName,"resolved =",rc); - } return rc; }; if(descr.readOnly){ @@ -628,7 +585,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ prop.set = function(v){ if(dbg.setter){ log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf, v); + xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v); } if(!this.pointer){ toss("Cannot set struct property on disposed instance."); @@ -644,7 +601,7 @@ self.Jaccwabyt = function StructBinderFactory(config){ toss("Invalid value for pointer-type",xPropName+'.'); } ( - new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) + new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); }; } @@ -665,13 +622,25 @@ self.Jaccwabyt = function StructBinderFactory(config){ if(!structName) toss("Struct name is required."); let lastMember = false; Object.keys(structInfo.members).forEach((k)=>{ + // Sanity checks of sizeof/offset info... const m = structInfo.members[k]; if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); - else if(0!==(m.sizeof%4)){ - toss(structName,"member",k,"sizeof is not aligned."); - } - else if(0!==(m.offset%4)){ - toss(structName,"member",k,"offset is not aligned."); + else if(m.sizeof===1){ + (m.signature === 'c' || m.signature === 'C') || + toss("Unexpected sizeof==1 member", + sPropName(structInfo.name,k), + "with signature",m.signature); + }else{ + // sizes and offsets of size-1 members may be odd values, but + // others may not. + if(0!==(m.sizeof%4)){ + console.warn("Invalid struct member description =",m,"from",structInfo); + toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof); + } + if(0!==(m.offset%4)){ + console.warn("Invalid struct member description =",m,"from",structInfo); + toss(structName,"member",k,"offset is not aligned. offset="+m.offset); + } } if(!lastMember || lastMember.offset < m.offset) lastMember = m; }); @@ -697,27 +666,9 @@ self.Jaccwabyt = function StructBinderFactory(config){ }; Object.defineProperties(StructCtor,{ debugFlags: debugFlags, - disposeAll: rop(function(){ - const map = __ptrBacklinks.get(StructCtor); - Object.keys(map).forEach(function(ptr){ - const b = map[ptr]; - if(b) __freeStruct(StructCtor, b, ptr); - }); - __ptrBacklinks.set(StructCtor, Object.create(null)); - return StructCtor; - }), - instanceForPointer: rop(__instanceBacklinkFactory(StructCtor)), isA: rop((v)=>v instanceof StructCtor), memberKey: __memberKeyProp, memberKeys: __structMemberKeys, - resolveToInstance: rop(function(v, throwIfNot=false){ - if(!(v instanceof StructCtor)){ - v = Number.isSafeInteger(v) - ? StructCtor.instanceForPointer(v) : undefined; - } - if(!v && throwIfNot) toss("Value is-not-a",StructCtor.structName); - return v; - }), methodInfoForKey: rop(function(mKey){ }), structInfo: rop(structInfo), @@ -735,7 +686,6 @@ self.Jaccwabyt = function StructBinderFactory(config){ ); return StructCtor; }; - StructBinder.instanceForPointer = StructType.instanceForPointer; StructBinder.StructType = StructType; StructBinder.config = config; StructBinder.allocCString = __allocCString; diff --git a/ext/wasm/jaccwabyt/jaccwabyt.md b/ext/wasm/jaccwabyt/jaccwabyt.md index edcba260a..dd80ed1c6 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.md +++ b/ext/wasm/jaccwabyt/jaccwabyt.md @@ -4,7 +4,6 @@ Jaccwabyt 🐇 **Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte Arrays_ - Welcome to Jaccwabyt, a JavaScript API which creates bindings for WASM-compiled C structs, defining them in such a way that changes to their state in JS are visible in C/WASM, and vice versa, permitting @@ -27,8 +26,28 @@ are based solely on feature compatibility tables provided at **Formalities:** - Author: [Stephan Beal][sgb] -- License: Public Domain -- Project Home: +- Project Homes: + - \ + Is the primary home but... + - \ + ... most development happens here. + +The license for both this documentation and the software it documents +is the same as [sqlite3][], the project from which this spinoff +project was spawned: + +----- + +> 2022-06-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. + +----- Table of Contents @@ -205,7 +224,6 @@ simply look like: The StructBinder factory function returns a function which can then be used to create bindings for our structs. - Step 2: Create a Struct Description ------------------------------------------------------------ @@ -281,21 +299,29 @@ supported letters are: signature entry. - **`f`** = `float` (4 bytes) - **`d`** = `double` (8 bytes) -- **`p`** = `int32` (but see below!) +- **`c`** = `int8` (1 byte) char - see notes below! +- **`C`** = `uint8` (1 byte) unsigned char - see notes below! +- **`p`** = `int32` (see notes below!) - **`P`** = Like `p` but with extra handling. Described below. -- **`s`** = like `int32` but is a _hint_ that it's a pointer to a string - so that _some_ (very limited) contexts may treat it as such, noting - such algorithms must, for lack of information to the contrary, - assume both that the encoding is UTF-8 and that the pointer's member - is NUL-terminated. If that is _not_ the case for a given string - member, do not use `s`: use `i` or `p` instead and do any string - handling yourself. +- **`s`** = like `int32` but is a _hint_ that it's a pointer to a + string so that _some_ (very limited) contexts may treat it as such, + noting that such algorithms must, for lack of information to the + contrary, assume both that the encoding is UTF-8 and that the + pointer's member is NUL-terminated. If that is _not_ the case for a + given string member, do not use `s`: use `i` or `p` instead and do + any string handling yourself. Noting that: -- All of these types are numeric. Attempting to set any struct-bound - property to a non-numeric value will trigger an exception except in - cases explicitly noted otherwise. +- **All of these types are numeric**. Attempting to set any + struct-bound property to a non-numeric value will trigger an + exception except in cases explicitly noted otherwise. +- **"Char" types**: WASM does not define an `int8` type, nor does it + distinguish between signed and unsigned. This API treats `c` as + `int8` and `C` as `uint8` for purposes of getting and setting values + when using the `DataView` class. It is _not_ recommended that client + code use these types in new WASM-capable code, but they were added + for the sake of binding some immutable legacy code to WASM. > Sidebar: Emscripten's public docs do not mention `p`, but their generated code includes `p` as an alias for `i`, presumably to mean @@ -317,12 +343,12 @@ Signatures in the form `x(...)` denote function-pointer members and form `x()`. For function-type signatures, the strings are formulated such that they can be passed to Emscripten's `addFunction()` after stripping out the `(` and `)` characters. For good measure, to match -the public Emscripten docs, `p` should also be replaced with `i`. In -JavaScript that might look like: +the public Emscripten docs, `p`, `c`, and `C`, should also be replaced +with `i`. In JavaScript that might look like: > ``` -signature.replace(/[^vipPsjfd]/g,'').replace(/[pPs]/g,'i'); +signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i'); ``` @@ -337,12 +363,6 @@ special use of unsigned numbers). A capital `P` changes the semantics of plain member pointers (but not, as of this writing, function pointer members) as follows: -- When a `P`-type member is **fetched** via `myStruct.x` and its value is - a non-0 integer, [`StructBinder.instanceForPointer()`][StructBinder] - is used to try to map that pointer to a struct instance. If a match - is found, the "get" operation returns that instance instead of the - integer. If no match is found, it behaves exactly as for `p`, returning - the integer value. - When a `P`-type member is **set** via `myStruct.x=y`, if [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is stored in `myStruct.x`. If `y` is neither a number nor @@ -388,14 +408,11 @@ It is important to understand that creating a new instance allocates memory on the WASM heap. We must not simply rely on garbage collection to clean up the instances because doing so will not free up the WASM heap memory. The correct way to free up that memory is to use the -object's `dispose()` method. Alternately, there is a "nuclear option": -`MyBinder.disposeAll()` will free the memory allocated for _all_ -instances which have not been manually disposed. +object's `dispose()` method. The following usage pattern offers one way to easily ensure proper cleanup of struct instances: - > ```javascript const my = new MyStruct(); @@ -409,11 +426,6 @@ try { from the byte array. */ // Pass the struct to C code which takes a MyStruct pointer: aCFunction( my.pointer ); - // Type-safely check if a pointer returned from C is a MyStruct: - const x = MyStruct.instanceForPointer( anotherCFunction() ); - // If it is a MyStruct, x now refers to that object. Note, however, - // that this only works for instances created in JS, as the - // pointer mapping only exists in JS space. } finally { my.dispose(); } @@ -426,6 +438,16 @@ to use `try`/`finally` without a `catch`, and doing so is an ideal match for the memory management requirements of Jaccwaby-bound struct instances. +It is often useful to wrap an existing instance of a C-side struct +without taking over ownership of its memory. That can be achieved by +simply passing a pointer to the constructor. For example: + +```js +const m = new MyStruct( functionReturningASharedPtr() ); +// calling m.dispose() will _not_ free the wrapped C-side instance +// but will trigger any ondispose handler. +``` + Now that we have struct instances, there are a number of things we can do with them, as covered in the rest of this document. @@ -549,20 +571,6 @@ The Struct Binder has the following members: any of its "significant" configuration values may have undefined results. -- `instanceForPointer(pointer)` - Given a pointer value relative to `config.memory`, if that pointer - resolves to a struct of _any type_ generated via the same Struct - Binder, this returns the struct instance associated with it, or - `undefined` if no struct object is mapped to that pointer. This - differs from the struct-type-specific member of the same name in - that this one is not "type-safe": it does not know the type of the - returned object (if any) and may return a struct of any - [StructType][] for which this Struct Binder has created a - constructor. It cannot return instances created via a different - [StructBinderFactory][] because each factory can hypothetically have - a different memory heap. - - API: Struct Type ------------------------------------------------------------ @@ -582,6 +590,14 @@ only called by the [StructBinder][]-generated has the following "static" properties (^Which are accessible from individual instances via `theInstance.constructor`.): +- `addOnDispose(...value)`\ + If this object has no `ondispose` property, this function creates it + as an array and pushes the given value(s) onto it. If the object has + a function-typed `ondispose` property, this call replaces it with an + array and moves that function into the array. In all other cases, + `ondispose` is assumed to be an array and the argument(s) is/are + appended to it. Returns `this`. + - `allocCString(str)` Identical to the [StructBinder][] method of the same name. @@ -591,9 +607,6 @@ individual instances via `theInstance.constructor`.): [struct's constructor][StructCtors]. If true, the memory is owned by someone other than the object and must outlive the object. -- `instanceForPointer(pointer)` - Works identically to the [StructBinder][] method of the same name. - - `isA(value)` Returns true if its argument is a StructType instance _from the same [StructBinder][]_ as this StructType. @@ -619,14 +632,15 @@ legally be called on concrete struct instances unless noted otherwise: - If it is a function, it is called with the struct object as its `this`. That method must not throw - if it does, the exception will be ignored. - - If it is an array, it may contain functions, pointers, and/or JS - strings. If an entry is a function, it is called as described - above. If it's a number, it's assumed to be a pointer and is - passed to the `dealloc()` function configured for the parent - [StructBinder][]. If it's a JS string, it's assumed to be a - helpful description of the next entry in the list and is simply - ignored. Strings are supported primarily for use as debugging - information. + - If it is an array, it may contain functions, pointers, other + [StructType] instances, and/or JS strings. If an entry is a + function, it is called as described above. If it's a number, it's + assumed to be a pointer and is passed to the `dealloc()` function + configured for the parent [StructBinder][]. If it's a + [StructType][] instance then its `dispose()` method is called. If + it's a JS string, it's assumed to be a helpful description of the + next entry in the list and is simply ignored. Strings are + supported primarily for use as debugging information. - Some struct APIs will manipulate the `ondispose` member, creating it as an array or converting it from a function to array as needed. @@ -738,21 +752,6 @@ pointer can be taken over using something like These constructors have the following "static" members: -- `disposeAll()` - For each instance of this struct, the equivalent of its `dispose()` - method is called. This frees all WASM-allocated memory associated - with _all_ instances and clears the `instanceForPointer()` - mappings. Returns `this`. - -- `instanceForPointer(pointer)` - Given a pointer value (accessible via the `pointer` property of all - struct instances) which ostensibly refers to an instance of this - class, this returns the instance associated with it, or `undefined` - if no object _of this specific struct type_ is mapped to that - pointer. When C-side code calls back into JS code and passes a - pointer to an object, this function can be used to type-safely - "cast" that pointer back to its original object. - - `isA(value)` Returns true if its argument was created by this constructor. @@ -762,15 +761,6 @@ These constructors have the following "static" members: - `memberKeys(string)` Works exactly as documented for [StructType][]. -- `resolveToInstance(value [,throwIfNot=false])` - Works like `instanceForPointer()` but accepts either an instance - of this struct type or a pointer which resolves to one. - It returns an instance of this struct type on success. - By default it returns a falsy value if its argument is not, - or does not resolve to, an instance of this struct type, - but if passed a truthy second argument then it will throw - instead. - - `structInfo` The structure description passed to [StructBinder][] when this constructor was generated. diff --git a/ext/wasm/module-symbols.html b/ext/wasm/module-symbols.html index 427d2dc8c..865f1b333 100644 --- a/ext/wasm/module-symbols.html +++ b/ext/wasm/module-symbols.html @@ -92,9 +92,8 @@
  • This page runs in the main UI thread so cannot see features which are only available in a Worker thread. If this page were to function via a Worker, it would not be able to see - functionality only available in the main thread. Starting a - Worker here to fetch those symbols requires loading a second - copy of the sqlite3 WASM module and JS code. + functionality only available in the main thread. Either way, it + would be missing certain APIs.
  • @@ -134,10 +133,10 @@

    sqlite3.version Object

    The sqlite3.version object exposes the following...

    - +

    sqlite3_...() Function List

    - +

    The sqlite3.capi namespace exposes the following sqlite3_...() functions... @@ -193,6 +192,186 @@

    Compilation Options

    fossil-doc block so that this part can work without modification in the wasm docs repo. */ +//#if target=es6-module + +//#else + + +//#endif + + diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js new file mode 100644 index 000000000..42d1167b6 --- /dev/null +++ b/ext/wasm/tester1.c-pp.js @@ -0,0 +1,3034 @@ +/* + 2022-10-12 + + The author disclaims copyright to this source code. In place of a + legal notice, here is a blessing: + + * May you do good and not evil. + * May you find forgiveness for yourself and forgive others. + * May you share freely, never taking more than you give. + + *********************************************************************** + + Main functional and regression tests for the sqlite3 WASM API. + + This mini-framework works like so: + + This script adds a series of test groups, each of which contains an + arbitrary number of tests, into a queue. After loading of the + sqlite3 WASM/JS module is complete, that queue is processed. If any + given test fails, the whole thing fails. This script is built such + that it can run from the main UI thread or worker thread. Test + groups and individual tests can be assigned a predicate function + which determines whether to run them or not, and this is + specifically intended to be used to toggle certain tests on or off + for the main/worker threads or the availability (or not) of + optional features such as int64 support. + + Each test group defines a single state object which gets applied as + the test functions' `this` for all tests in that group. Test + functions can use that to, e.g., set up a db in an early test and + close it in a later test. Each test gets passed the sqlite3 + namespace object as its only argument. +*/ +/* + This file is intended to be processed by c-pp to inject (or not) + code specific to ES6 modules which is illegal in non-module code. + + Non-ES6 module build and ES6 module for the main-thread: + + ./c-pp -f tester1.c-pp.js -o tester1.js + + ES6 worker module build: + + ./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module +*/ +//#if target=es6-module +import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs'; +self.sqlite3InitModule = sqlite3InitModule; +//#else +'use strict'; +//#endif +(function(self){ + /** + Set up our output channel differently depending + on whether we are running in a worker thread or + the main (UI) thread. + */ + let logClass; + /* Predicate for tests/groups. */ + const isUIThread = ()=>(self.window===self && self.document); + /* Predicate for tests/groups. */ + const isWorker = ()=>!isUIThread(); + /* Predicate for tests/groups. */ + const testIsTodo = ()=>false; + const haveWasmCTests = ()=>{ + return !!wasm.exports.sqlite3_wasm_test_intptr; + }; + { + const mapToString = (v)=>{ + switch(typeof v){ + case 'number': case 'string': case 'boolean': + case 'undefined': case 'bigint': + return ''+v; + default: break; + } + if(null===v) return 'null'; + if(v instanceof Error){ + v = { + message: v.message, + stack: v.stack, + errorClass: v.name + }; + } + return JSON.stringify(v,undefined,2); + }; + const normalizeArgs = (args)=>args.map(mapToString); + if( isUIThread() ){ + console.log("Running in the UI thread."); + const logTarget = document.querySelector('#test-output'); + logClass = function(cssClass,...args){ + const ln = document.createElement('div'); + if(cssClass){ + for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){ + ln.classList.add(c); + } + } + ln.append(document.createTextNode(normalizeArgs(args).join(' '))); + logTarget.append(ln); + }; + const cbReverse = document.querySelector('#cb-log-reverse'); + const cbReverseKey = 'tester1:cb-log-reverse'; + const cbReverseIt = ()=>{ + logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); + //localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0); + }; + cbReverse.addEventListener('change', cbReverseIt, true); + /*if(localStorage.getItem(cbReverseKey)){ + cbReverse.checked = !!(+localStorage.getItem(cbReverseKey)); + }*/ + cbReverseIt(); + }else{ /* Worker thread */ + console.log("Running in a Worker thread."); + logClass = function(cssClass,...args){ + postMessage({ + type:'log', + payload:{cssClass, args: normalizeArgs(args)} + }); + }; + } + } + const reportFinalTestStatus = function(pass){ + if(isUIThread()){ + let e = document.querySelector('#color-target'); + e.classList.add(pass ? 'tests-pass' : 'tests-fail'); + e = document.querySelector('title'); + e.innerText = (pass ? 'PASS' : 'FAIL') + ': ' + e.innerText; + }else{ + postMessage({type:'test-result', payload:{pass}}); + } + }; + const log = (...args)=>{ + //console.log(...args); + logClass('',...args); + } + const warn = (...args)=>{ + console.warn(...args); + logClass('warning',...args); + } + const error = (...args)=>{ + console.error(...args); + logClass('error',...args); + }; + + const toss = (...args)=>{ + error(...args); + throw new Error(args.join(' ')); + }; + const tossQuietly = (...args)=>{ + throw new Error(args.join(' ')); + }; + + const roundMs = (ms)=>Math.round(ms*100)/100; + + /** + Helpers for writing sqlite3-specific tests. + */ + const TestUtil = { + /** Running total of the number of tests run via + this API. */ + counter: 0, + /* Separator line for log messages. */ + separator: '------------------------------------------------------------', + /** + If expr is a function, it is called and its result + is returned, coerced to a bool, else expr, coerced to + a bool, is returned. + */ + toBool: function(expr){ + return (expr instanceof Function) ? !!expr() : !!expr; + }, + /** Throws if expr is false. If expr is a function, it is called + and its result is evaluated. If passed multiple arguments, + those after the first are a message string which get applied + as an exception message if the assertion fails. The message + arguments are concatenated together with a space between each. + */ + assert: function f(expr, ...msg){ + ++this.counter; + if(!this.toBool(expr)){ + throw new Error(msg.length ? msg.join(' ') : "Assertion failed."); + } + return this; + }, + /** Calls f() and squelches any exception it throws. If it + does not throw, this function throws. */ + mustThrow: function(f, msg){ + ++this.counter; + let err; + try{ f(); } catch(e){err=e;} + if(!err) throw new Error(msg || "Expected exception."); + return this; + }, + /** + Works like mustThrow() but expects filter to be a regex, + function, or string to match/filter the resulting exception + against. If f() does not throw, this test fails and an Error is + thrown. If filter is a regex, the test passes if + filter.test(error.message) passes. If it's a function, the test + passes if filter(error) returns truthy. If it's a string, the + test passes if the filter matches the exception message + precisely. In all other cases the test fails, throwing an + Error. + + If it throws, msg is used as the error report unless it's falsy, + in which case a default is used. + */ + mustThrowMatching: function(f, filter, msg){ + ++this.counter; + let err; + try{ f(); } catch(e){err=e;} + if(!err) throw new Error(msg || "Expected exception."); + let pass = false; + if(filter instanceof RegExp) pass = filter.test(err.message); + else if(filter instanceof Function) pass = filter(err); + else if('string' === typeof filter) pass = (err.message === filter); + if(!pass){ + throw new Error(msg || ("Filter rejected this exception: "+err.message)); + } + return this; + }, + /** Throws if expr is truthy or expr is a function and expr() + returns truthy. */ + throwIf: function(expr, msg){ + ++this.counter; + if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); + return this; + }, + /** Throws if expr is falsy or expr is a function and expr() + returns falsy. */ + throwUnless: function(expr, msg){ + ++this.counter; + if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); + return this; + }, + eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)), + TestGroup: (function(){ + let groupCounter = 0; + const TestGroup = function(name, predicate){ + this.number = ++groupCounter; + this.name = name; + this.predicate = predicate; + this.tests = []; + }; + TestGroup.prototype = { + addTest: function(testObj){ + this.tests.push(testObj); + return this; + }, + run: async function(sqlite3){ + log(TestUtil.separator); + logClass('group-start',"Group #"+this.number+':',this.name); + const indent = ' '; + if(this.predicate){ + const p = this.predicate(sqlite3); + if(!p || 'string'===typeof p){ + logClass('warning',indent, + "SKIPPING group:", p ? p : "predicate says to" ); + return; + } + } + const assertCount = TestUtil.counter; + const groupState = Object.create(null); + const skipped = []; + let runtime = 0, i = 0; + for(const t of this.tests){ + ++i; + const n = this.number+"."+i; + log(indent, n+":", t.name); + if(t.predicate){ + const p = t.predicate(sqlite3); + if(!p || 'string'===typeof p){ + logClass('warning',indent, + "SKIPPING:", p ? p : "predicate says to" ); + skipped.push( n+': '+t.name ); + continue; + } + } + const tc = TestUtil.counter, now = performance.now(); + await t.test.call(groupState, sqlite3); + const then = performance.now(); + runtime += then - now; + logClass('faded',indent, indent, + TestUtil.counter - tc, 'assertion(s) in', + roundMs(then-now),'ms'); + } + logClass('green', + "Group #"+this.number+":",(TestUtil.counter - assertCount), + "assertion(s) in",roundMs(runtime),"ms"); + if(0 && skipped.length){ + logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped); + } + } + }; + return TestGroup; + })()/*TestGroup*/, + testGroups: [], + currentTestGroup: undefined, + addGroup: function(name, predicate){ + this.testGroups.push( this.currentTestGroup = + new this.TestGroup(name, predicate) ); + return this; + }, + addTest: function(name, callback){ + let predicate; + if(1===arguments.length){ + this.currentTestGroup.addTest(arguments[0]); + }else{ + this.currentTestGroup.addTest({ + name, predicate, test: callback + }); + } + return this; + }, + runTests: async function(sqlite3){ + return new Promise(async function(pok,pnok){ + try { + let runtime = 0; + for(let g of this.testGroups){ + const now = performance.now(); + await g.run(sqlite3); + runtime += performance.now() - now; + } + log(TestUtil.separator); + logClass(['strong','green'], + "Done running tests.",TestUtil.counter,"assertions in", + roundMs(runtime),'ms'); + pok(); + reportFinalTestStatus(true); + }catch(e){ + error(e); + pnok(e); + reportFinalTestStatus(false); + } + }.bind(this)); + } + }/*TestUtil*/; + const T = TestUtil; + T.g = T.addGroup; + T.t = T.addTest; + let capi, wasm/*assigned after module init*/; + //////////////////////////////////////////////////////////////////////// + // End of infrastructure setup. Now define the tests... + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////// + T.g('Basic sanity checks') + .t({ + name:'sqlite3_config()', + test:function(sqlite3){ + for(const k of [ + 'SQLITE_CONFIG_GETMALLOC', 'SQLITE_CONFIG_URI' + ]){ + T.assert(capi[k] > 0); + } + T.assert(capi.SQLITE_MISUSE===capi.sqlite3_config( + capi.SQLITE_CONFIG_URI, 1 + ), "MISUSE because the library has already been initialized."); + T.assert(capi.SQLITE_MISUSE === capi.sqlite3_config( + // not enough args + capi.SQLITE_CONFIG_GETMALLOC + )); + T.assert(capi.SQLITE_NOTFOUND === capi.sqlite3_config( + // unhandled-in-JS config option + capi.SQLITE_CONFIG_GETMALLOC, 1 + )); + if(0){ + log("We cannot _fully_ test sqlite3_config() after the library", + "has been initialized (which it necessarily has been to", + "set up various bindings) and we cannot shut it down ", + "without losing the VFS registrations."); + T.assert(0 === capi.sqlite3_config( + capi.SQLITE_CONFIG_URI, 1 + )); + } + } + })/*sqlite3_config()*/ + + //////////////////////////////////////////////////////////////////// + .t({ + name: "JS wasm-side allocator", + test: function(sqlite3){ + if(sqlite3.config.useStdAlloc){ + warn("Using system allocator. This violates the docs and", + "may cause grief with certain APIs", + "(e.g. sqlite3_deserialize())."); + T.assert(wasm.alloc.impl === wasm.exports.malloc) + .assert(wasm.dealloc === wasm.exports.free) + .assert(wasm.realloc.impl === wasm.exports.realloc); + }else{ + T.assert(wasm.alloc.impl === wasm.exports.sqlite3_malloc) + .assert(wasm.dealloc === wasm.exports.sqlite3_free) + .assert(wasm.realloc.impl === wasm.exports.sqlite3_realloc); + } + } + }) + .t('Namespace object checks', function(sqlite3){ + const wasmCtypes = wasm.ctype; + T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs'). + assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4). + assert(wasmCtypes.structs[1/*sqlite3_io_methods*/ + ].members.xFileSize.offset>0); + [ /* Spot-check a handful of constants to make sure they got installed... */ + 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8', + 'SQLITE_STATIC', 'SQLITE_DIRECTONLY', + 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE' + ].forEach((k)=>T.assert('number' === typeof capi[k])); + [/* Spot-check a few of the WASM API methods. */ + 'alloc', 'dealloc', 'installFunction' + ].forEach((k)=>T.assert(wasm[k] instanceof Function)); + + T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0). + assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0). + assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error'); + + try { + throw new sqlite3.WasmAllocError; + }catch(e){ + T.assert(e instanceof Error) + .assert(e instanceof sqlite3.WasmAllocError) + .assert("Allocation failed." === e.message); + } + try { + throw new sqlite3.WasmAllocError("test",{ + cause: 3 + }); + }catch(e){ + T.assert(3 === e.cause) + .assert("test" === e.message); + } + try {throw new sqlite3.WasmAllocError("test","ing",".")} + catch(e){T.assert("test ing ." === e.message)} + + try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) } + catch(e){ + T.assert('SQLITE_SCHEMA' === e.message) + .assert(capi.SQLITE_SCHEMA === e.resultCode); + } + try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) } + catch(e){ + T.assert('SQLITE_CORRUPT' === e.message) + .assert(capi.SQLITE_CORRUPT === e.resultCode) + .assert(true===e.cause); + } + try{ sqlite3.SQLite3Error.toss("resultCode check") } + catch(e){ + T.assert(capi.SQLITE_ERROR === e.resultCode) + .assert('resultCode check' === e.message); + } + }) + //////////////////////////////////////////////////////////////////// + .t('strglob/strlike', function(sqlite3){ + T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). + assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). + assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). + assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); + }) + + //////////////////////////////////////////////////////////////////// + ;/*end of basic sanity checks*/ + + //////////////////////////////////////////////////////////////////// + T.g('C/WASM Utilities') + .t('sqlite3.wasm namespace', function(sqlite3){ + // TODO: break this into smaller individual test functions. + const w = wasm; + const chr = (x)=>x.charCodeAt(0); + //log("heap getters..."); + { + const li = [8, 16, 32]; + if(w.bigIntEnabled) li.push(64); + for(const n of li){ + const bpe = n/8; + const s = w.heapForSize(n,false); + T.assert(bpe===s.BYTES_PER_ELEMENT). + assert(w.heapForSize(s.constructor) === s); + const u = w.heapForSize(n,true); + T.assert(bpe===u.BYTES_PER_ELEMENT). + assert(s!==u). + assert(w.heapForSize(u.constructor) === u); + } + } + + // alloc(), realloc(), allocFromTypedArray() + { + let m = w.alloc(14); + let m2 = w.realloc(m, 16); + T.assert(m === m2/* because of alignment */); + T.assert(0 === w.realloc(m, 0)); + m = m2 = 0; + + // Check allocation limits and allocator's responses... + T.assert('number' === typeof sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE); + if(!sqlite3.config.useStdAlloc){ + const tooMuch = sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE + 1, + isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError; + T.mustThrowMatching(()=>w.alloc(tooMuch), isAllocErr) + .assert(0 === w.alloc.impl(tooMuch)) + .mustThrowMatching(()=>w.realloc(0, tooMuch), isAllocErr) + .assert(0 === w.realloc.impl(0, tooMuch)); + } + + // Check allocFromTypedArray()... + const byteList = [11,22,33] + const u = new Uint8Array(byteList); + m = w.allocFromTypedArray(u); + for(let i = 0; i < u.length; ++i){ + T.assert(u[i] === byteList[i]) + .assert(u[i] === w.peek8(m + i)); + } + w.dealloc(m); + m = w.allocFromTypedArray(u.buffer); + for(let i = 0; i < u.length; ++i){ + T.assert(u[i] === byteList[i]) + .assert(u[i] === w.peek8(m + i)); + } + + w.dealloc(m); + T.mustThrowMatching( + ()=>w.allocFromTypedArray(1), + 'Value is not of a supported TypedArray type.' + ); + } + + { // Test peekXYZ()/pokeXYZ()... + const m = w.alloc(8); + T.assert( 17 === w.poke8(m,17).peek8(m) ) + .assert( 31987 === w.poke16(m,31987).peek16(m) ) + .assert( 345678 === w.poke32(m,345678).peek32(m) ) + .assert( + T.eqApprox( 345678.9, w.poke32f(m,345678.9).peek32f(m) ) + ).assert( + T.eqApprox( 4567890123.4, w.poke64f(m, 4567890123.4).peek64f(m) ) + ); + if(w.bigIntEnabled){ + T.assert( + BigInt(Number.MAX_SAFE_INTEGER) === + w.poke64(m, Number.MAX_SAFE_INTEGER).peek64(m) + ); + } + w.dealloc(m); + } + + // isPtr32() + { + const ip = w.isPtr32; + T.assert(ip(0)) + .assert(!ip(-1)) + .assert(!ip(1.1)) + .assert(!ip(0xffffffff)) + .assert(ip(0x7fffffff)) + .assert(!ip()) + .assert(!ip(null)/*might change: under consideration*/) + ; + } + + //log("jstrlen()..."); + { + T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc")); + } + + //log("jstrcpy()..."); + { + const fillChar = 10; + let ua = new Uint8Array(8), rc, + refill = ()=>ua.fill(fillChar); + refill(); + rc = w.jstrcpy("hello", ua); + T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]); + refill(); + ua[5] = chr('!'); + rc = w.jstrcpy("HELLO", ua, 0, -1, false); + T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]); + refill(); + rc = w.jstrcpy("the end", ua, 4); + //log("rc,ua",rc,ua); + T.assert(4===rc).assert(0===ua[7]). + assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); + refill(); + rc = w.jstrcpy("the end", ua, 4, -1, false); + T.assert(4===rc).assert(chr(' ')===ua[7]). + assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); + refill(); + rc = w.jstrcpy("", ua, 0, 1, true); + //log("rc,ua",rc,ua); + T.assert(1===rc).assert(0===ua[0]); + refill(); + rc = w.jstrcpy("x", ua, 0, 1, true); + //log("rc,ua",rc,ua); + T.assert(1===rc).assert(0===ua[0]); + refill(); + rc = w.jstrcpy('äbä', ua, 0, 1, true); + T.assert(1===rc, 'Must not write partial multi-byte char.') + .assert(0===ua[0]); + refill(); + rc = w.jstrcpy('äbä', ua, 0, 2, true); + T.assert(1===rc, 'Must not write partial multi-byte char.') + .assert(0===ua[0]); + refill(); + rc = w.jstrcpy('äbä', ua, 0, 2, false); + T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]); + }/*jstrcpy()*/ + + //log("cstrncpy()..."); + { + const scope = w.scopedAllocPush(); + try { + let cStr = w.scopedAllocCString("hello"); + const n = w.cstrlen(cStr); + let cpy = w.scopedAlloc(n+10); + let rc = w.cstrncpy(cpy, cStr, n+10); + T.assert(n+1 === rc). + assert("hello" === w.cstrToJs(cpy)). + assert(chr('o') === w.peek8(cpy+n-1)). + assert(0 === w.peek8(cpy+n)); + let cStr2 = w.scopedAllocCString("HI!!!"); + rc = w.cstrncpy(cpy, cStr2, 3); + T.assert(3===rc). + assert("HI!lo" === w.cstrToJs(cpy)). + assert(chr('!') === w.peek8(cpy+2)). + assert(chr('l') === w.peek8(cpy+3)); + }finally{ + w.scopedAllocPop(scope); + } + } + + //log("jstrToUintArray()..."); + { + let a = w.jstrToUintArray("hello", false); + T.assert(5===a.byteLength).assert(chr('o')===a[4]); + a = w.jstrToUintArray("hello", true); + T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]); + a = w.jstrToUintArray("äbä", false); + T.assert(5===a.byteLength).assert(chr('b')===a[2]); + a = w.jstrToUintArray("äbä", true); + T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]); + } + + //log("allocCString()..."); + { + const jstr = "hällo, world!"; + const [cstr, n] = w.allocCString(jstr, true); + T.assert(14 === n) + .assert(0===w.peek8(cstr+n)) + .assert(chr('!')===w.peek8(cstr+n-1)); + w.dealloc(cstr); + } + + //log("scopedAlloc() and friends..."); + { + const alloc = w.alloc, dealloc = w.dealloc; + w.alloc = w.dealloc = null; + T.assert(!w.scopedAlloc.level) + .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) + .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); + w.alloc = alloc; + T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); + w.dealloc = dealloc; + T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/) + .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) + .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/); + const asc = w.scopedAllocPush(); + let asc2; + try { + const p1 = w.scopedAlloc(16), + p2 = w.scopedAlloc(16); + T.assert(1===w.scopedAlloc.level) + .assert(Number.isFinite(p1)) + .assert(Number.isFinite(p2)) + .assert(asc[0] === p1) + .assert(asc[1]===p2); + asc2 = w.scopedAllocPush(); + const p3 = w.scopedAlloc(16); + T.assert(2===w.scopedAlloc.level) + .assert(Number.isFinite(p3)) + .assert(2===asc.length) + .assert(p3===asc2[0]); + + const [z1, z2, z3] = w.scopedAllocPtr(3); + T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2) + .assert(0===w.peek32(z1), 'allocPtr() must zero the targets') + .assert(0===w.peek32(z3)); + }finally{ + // Pop them in "incorrect" order to make sure they behave: + w.scopedAllocPop(asc); + T.assert(0===asc.length); + T.mustThrowMatching(()=>w.scopedAllocPop(asc), + /^Invalid state object/); + if(asc2){ + T.assert(2===asc2.length,'Should be p3 and z1'); + w.scopedAllocPop(asc2); + T.assert(0===asc2.length); + T.mustThrowMatching(()=>w.scopedAllocPop(asc2), + /^Invalid state object/); + } + } + T.assert(0===w.scopedAlloc.level); + w.scopedAllocCall(function(){ + T.assert(1===w.scopedAlloc.level); + const [cstr, n] = w.scopedAllocCString("hello, world", true); + T.assert(12 === n) + .assert(0===w.peek8(cstr+n)) + .assert(chr('d')===w.peek8(cstr+n-1)); + }); + }/*scopedAlloc()*/ + + //log("xCall()..."); + { + const pJson = w.xCall('sqlite3_wasm_enum_json'); + T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300); + } + + //log("xWrap()..."); + { + T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'), + /requires 0 arg/). + assert(w.xWrap.resultAdapter('i32') instanceof Function). + assert(w.xWrap.argAdapter('i32') instanceof Function); + let fw = w.xWrap('sqlite3_libversion','utf8'); + T.mustThrowMatching(()=>fw(1), /requires 0 arg/); + let rc = fw(); + T.assert('string'===typeof rc).assert(rc.length>5); + rc = w.xCallWrapped('sqlite3_wasm_enum_json','*'); + T.assert(rc>0 && Number.isFinite(rc)); + rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8'); + T.assert('string'===typeof rc).assert(rc.length>300); + + + { // 'string:static' argAdapter() sanity checks... + let argAd = w.xWrap.argAdapter('string:static'); + let p0 = argAd('foo'), p1 = argAd('bar'); + T.assert(w.isPtr(p0) && w.isPtr(p1)) + .assert(p0 !== p1) + .assert(p0 === argAd('foo')) + .assert(p1 === argAd('bar')); + } + + // 'string:flexible' argAdapter() sanity checks... + w.scopedAllocCall(()=>{ + const argAd = w.xWrap.argAdapter('string:flexible'); + const cj = (v)=>w.cstrToJs(argAd(v)); + T.assert('Hi' === cj('Hi')) + .assert('hi' === cj(['h','i'])) + .assert('HI' === cj(new Uint8Array([72, 73]))); + }); + + // jsFuncToWasm() + { + const fsum3 = (x,y,z)=>x+y+z; + fw = w.jsFuncToWasm('i(iii)', fsum3); + T.assert(fw instanceof Function) + .assert( fsum3 !== fw ) + .assert( 3 === fw.length ) + .assert( 6 === fw(1,2,3) ); + T.mustThrowMatching( ()=>w.jsFuncToWasm('x()', function(){}), + 'Invalid signature letter: x'); + } + + // xWrap(Function,...) + { + let fp; + try { + const fmy = function fmy(i,s,d){ + if(fmy.debug) log("fmy(",...arguments,")"); + T.assert( 3 === i ) + .assert( w.isPtr(s) ) + .assert( w.cstrToJs(s) === 'a string' ) + .assert( T.eqApprox(1.2, d) ); + return w.allocCString("hi"); + }; + fmy.debug = false; + const xwArgs = ['string:dealloc', ['i32', 'string', 'f64']]; + fw = w.xWrap(fmy, ...xwArgs); + const fmyArgs = [3, 'a string', 1.2]; + let rc = fw(...fmyArgs); + T.assert( 'hi' === rc ); + if(0){ + /* Retain this as a "reminder to self"... + + This extra level of indirection does not work: the + string argument is ending up as a null in fmy() but + the numeric arguments are making their ways through + + What's happening is: installFunction() is creating a + WASM-compatible function instance. When we pass a JS string + into there it's getting coerced into `null` before being passed + on to the lower-level wrapper. + */ + fmy.debug = true; + fp = wasm.installFunction('i(isd)', fw); + fw = w.functionEntry(fp); + rc = fw(...fmyArgs); + log("rc =",rc); + T.assert( 'hi' === rc ); + // Similarly, this does not work: + //let fpw = w.xWrap(fp, null, [null,null,null]); + //rc = fpw(...fmyArgs); + //log("rc =",rc); + //T.assert( 'hi' === rc ); + } + }finally{ + wasm.uninstallFunction(fp); + } + } + + if(haveWasmCTests()){ + if(!sqlite3.config.useStdAlloc){ + fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']); + rc = fw(0); + T.assert('hello'===rc); + rc = fw(1); + T.assert(null===rc); + } + + if(w.bigIntEnabled){ + w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v)); + w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v)); + fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice'); + rc = fw(1); + T.assert(12n===rc); + + w.scopedAllocCall(function(){ + const pI1 = w.scopedAlloc(8), pI2 = pI1+4; + w.pokePtr([pI1, pI2], 0); + const f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']); + const [r1, r2] = w.peek64([pI1, pI2]); + T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2)); + }); + } + } + }/*xWrap()*/ + }/*WhWasmUtil*/) + + //////////////////////////////////////////////////////////////////// + .t('sqlite3.StructBinder (jaccwabyt🐇)', function(sqlite3){ + const S = sqlite3, W = S.wasm; + const MyStructDef = { + sizeof: 16, + members: { + p4: {offset: 0, sizeof: 4, signature: "i"}, + pP: {offset: 4, sizeof: 4, signature: "P"}, + ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true}, + cstr: {offset: 12, sizeof: 4, signature: "s"} + } + }; + if(W.bigIntEnabled){ + const m = MyStructDef; + m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"}; + m.sizeof += m.members.p8.sizeof; + } + const StructType = S.StructBinder.StructType; + const K = S.StructBinder('my_struct',MyStructDef); + T.mustThrowMatching(()=>K(), /via 'new'/). + mustThrowMatching(()=>new K('hi'), /^Invalid pointer/); + const k1 = new K(), k2 = new K(); + try { + T.assert(k1.constructor === K). + assert(K.isA(k1)). + assert(k1 instanceof K). + assert(K.prototype.lookupMember('p4').key === '$p4'). + assert(K.prototype.lookupMember('$p4').name === 'p4'). + mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/). + assert(undefined === K.prototype.lookupMember('nope',false)). + assert(k1 instanceof StructType). + assert(StructType.isA(k1)). + mustThrowMatching(()=>k1.$ro = 1, /read-only/); + Object.keys(MyStructDef.members).forEach(function(key){ + key = K.memberKey(key); + T.assert(0 == k1[key], + "Expecting allocation to zero the memory "+ + "for "+key+" but got: "+k1[key]+ + " from "+k1.memoryDump()); + }); + T.assert('number' === typeof k1.pointer). + mustThrowMatching(()=>k1.pointer = 1, /pointer/); + k1.$p4 = 1; k1.$pP = 2; + T.assert(1 === k1.$p4).assert(2 === k1.$pP); + if(MyStructDef.members.$p8){ + k1.$p8 = 1/*must not throw despite not being a BigInt*/; + k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2); + T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8); + } + T.assert(!k1.ondispose); + k1.setMemberCString('cstr', "A C-string."); + T.assert(Array.isArray(k1.ondispose)). + assert(k1.ondispose[0] === k1.$cstr). + assert('number' === typeof k1.$cstr). + assert('A C-string.' === k1.memberToJsString('cstr')); + k1.$pP = k2; + T.assert(k1.$pP === k2.pointer); + k1.$pP = null/*null is special-cased to 0.*/; + T.assert(0===k1.$pP); + let ptr = k1.pointer; + k1.dispose(); + T.assert(undefined === k1.pointer). + mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); + }finally{ + k1.dispose(); + k2.dispose(); + } + + if(!W.bigIntEnabled){ + log("Skipping WasmTestStruct tests: BigInt not enabled."); + return; + } + + const WTStructDesc = + W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0]; + const autoResolvePtr = true /* EXPERIMENTAL */; + if(autoResolvePtr){ + WTStructDesc.members.ppV.signature = 'P'; + } + const WTStruct = S.StructBinder(WTStructDesc); + //log(WTStruct.structName, WTStruct.structInfo); + const wts = new WTStruct(); + //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype)); + try{ + T.assert(wts.constructor === WTStruct). + assert(WTStruct.memberKeys().indexOf('$ppV')>=0). + assert(wts.memberKeys().indexOf('$v8')>=0). + assert(!K.isA(wts)). + assert(WTStruct.isA(wts)). + assert(wts instanceof WTStruct). + assert(wts instanceof StructType). + assert(StructType.isA(wts)). + assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). + assert(0===wts.$ppV).assert(0===wts.$xFunc); + const testFunc = + W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/); + let counter = 0; + //log("wts.pointer =",wts.pointer); + const wtsFunc = function(arg){ + /*log("This from a JS function called from C, "+ + "which itself was called from JS. arg =",arg);*/ + ++counter; + if(3===counter){ + tossQuietly("Testing exception propagation."); + } + } + wts.$v4 = 10; wts.$v8 = 20; + wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc')) + T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8) + .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc) + .assert(0 === wts.$cstr) + .assert(wts.memberIsString('$cstr')) + .assert(!wts.memberIsString('$v4')) + .assert(null === wts.memberToJsString('$cstr')) + .assert(W.functionEntry(wts.$xFunc) instanceof Function); + /* It might seem silly to assert that the values match + what we just set, but recall that all of those property + reads and writes are, via property interceptors, + actually marshaling their data to/from a raw memory + buffer, so merely reading them back is actually part of + testing the struct-wrapping API. */ + + testFunc(wts.pointer); + //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV); + T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8) + .assert(wts.$ppV === wts.pointer) + .assert('string' === typeof wts.memberToJsString('cstr')) + .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr')) + .mustThrowMatching(()=>wts.memberToJsString('xFunc'), + /Invalid member type signature for C-string/) + ; + testFunc(wts.pointer); + T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8) + .assert(wts.$ppV === wts.pointer); + /** The 3rd call to wtsFunc throw from JS, which is called + from C, which is called from JS. Let's ensure that + that exception propagates back here... */ + T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/); + W.uninstallFunction(wts.$xFunc); + wts.$xFunc = 0; + wts.$ppV = 0; + T.assert(!wts.$ppV); + //WTStruct.debugFlags(0x03); + wts.$ppV = wts; + T.assert(wts.pointer === wts.$ppV) + wts.setMemberCString('cstr', "A C-string."); + T.assert(Array.isArray(wts.ondispose)). + assert(wts.ondispose[0] === wts.$cstr). + assert('A C-string.' === wts.memberToJsString('cstr')); + const ptr = wts.pointer; + wts.dispose(); + T.assert(ptr).assert(undefined === wts.pointer); + }finally{ + wts.dispose(); + } + + if(1){ // ondispose of other struct instances + const s1 = new WTStruct, s2 = new WTStruct, s3 = new WTStruct; + T.assert(s1.lookupMember instanceof Function) + .assert(s1.addOnDispose instanceof Function); + s1.addOnDispose(s2,"testing variadic args"); + T.assert(2===s1.ondispose.length); + s2.addOnDispose(s3); + s1.dispose(); + T.assert(!s2.pointer,"Expecting s2 to be ondispose'd by s1."); + T.assert(!s3.pointer,"Expecting s3 to be ondispose'd by s2."); + } + }/*StructBinder*/) + + //////////////////////////////////////////////////////////////////// + .t('sqlite3.wasm.pstack', function(sqlite3){ + const P = wasm.pstack; + const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError; + const stack = P.pointer; + T.assert(0===stack % 8 /* must be 8-byte aligned */); + try{ + const remaining = P.remaining; + T.assert(P.quota >= 4096) + .assert(remaining === P.quota) + .mustThrowMatching(()=>P.alloc(0), isAllocErr) + .mustThrowMatching(()=>P.alloc(-1), isAllocErr) + .mustThrowMatching( + ()=>P.alloc('i33'), + (e)=>e instanceof sqlite3.WasmAllocError + ); + ; + let p1 = P.alloc(12); + T.assert(p1 === stack - 16/*8-byte aligned*/) + .assert(P.pointer === p1); + let p2 = P.alloc(7); + T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/) + .mustThrowMatching(()=>P.alloc(remaining), isAllocErr) + .assert(24 === stack - p2) + .assert(P.pointer === p2); + let n = remaining - (stack - p2); + let p3 = P.alloc(n); + T.assert(p3 === stack-remaining) + .mustThrowMatching(()=>P.alloc(1), isAllocErr); + }finally{ + P.restore(stack); + } + + T.assert(P.pointer === stack); + try { + const [p1, p2, p3] = P.allocChunks(3,'i32'); + T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/) + .assert(p2 === p1 + 4) + .assert(p3 === p2 + 4); + T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16), + (e)=>e instanceof sqlite3.WasmAllocError) + }finally{ + P.restore(stack); + } + + T.assert(P.pointer === stack); + try { + let [p1, p2, p3] = P.allocPtr(3,false); + let sPos = stack-16/*always rounded to multiple of 8*/; + T.assert(P.pointer === sPos) + .assert(p2 === p1 + 4) + .assert(p3 === p2 + 4); + [p1, p2, p3] = P.allocPtr(3); + T.assert(P.pointer === sPos-24/*3 x 8 bytes*/) + .assert(p2 === p1 + 8) + .assert(p3 === p2 + 8); + p1 = P.allocPtr(); + T.assert('number'===typeof p1); + }finally{ + P.restore(stack); + } + }/*pstack tests*/) + //////////////////////////////////////////////////////////////////// + ;/*end of C/WASM utils checks*/ + + T.g('sqlite3_randomness()') + .t('To memory buffer', function(sqlite3){ + const stack = wasm.pstack.pointer; + try{ + const n = 520; + const p = wasm.pstack.alloc(n); + T.assert(0===wasm.peek8(p)) + .assert(0===wasm.peek8(p+n-1)); + T.assert(undefined === capi.sqlite3_randomness(n - 10, p)); + let j, check = 0; + const heap = wasm.heap8u(); + for(j = 0; j < 10 && 0===check; ++j){ + check += heap[p + j]; + } + T.assert(check > 0); + check = 0; + // Ensure that the trailing bytes were not modified... + for(j = n - 10; j < n && 0===check; ++j){ + check += heap[p + j]; + } + T.assert(0===check); + }finally{ + wasm.pstack.restore(stack); + } + }) + .t('To byte array', function(sqlite3){ + const ta = new Uint8Array(117); + let i, n = 0; + for(i=0; i0); + const t0 = new Uint8Array(0); + T.assert(t0 === capi.sqlite3_randomness(t0), + "0-length array is a special case"); + }) + ;/*end sqlite3_randomness() checks*/ + + //////////////////////////////////////////////////////////////////////// + T.g('sqlite3.oo1') + .t('Create db', function(sqlite3){ + const dbFile = '/tester1.db'; + wasm.sqlite3_wasm_vfs_unlink(0, dbFile); + const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c'); + db.onclose = { + disposeAfter: [], + disposeBefore: [ + (db)=>{ + //console.debug("db.onclose.before dropping modules"); + //sqlite3.capi.sqlite3_drop_modules(db.pointer, 0); + } + ], + before: function(db){ + while(this.disposeBefore.length){ + const v = this.disposeBefore.shift(); + console.debug("db.onclose.before cleaning up:",v); + if(wasm.isPtr(v)) wasm.dealloc(v); + else if(v instanceof sqlite3.StructBinder.StructType){ + v.dispose(); + }else if(v instanceof Function){ + try{ v(db) } catch(e){ + console.warn("beforeDispose() callback threw:",e); + } + } + } + }, + after: function(){ + while(this.disposeAfter.length){ + const v = this.disposeAfter.shift(); + console.debug("db.onclose.after cleaning up:",v); + if(wasm.isPtr(v)) wasm.dealloc(v); + else if(v instanceof sqlite3.StructBinder.StructType){ + v.dispose(); + }else if(v instanceof Function){ + try{v()} catch(e){/*ignored*/} + } + } + } + }; + + T.assert(wasm.isPtr(db.pointer)) + .mustThrowMatching(()=>db.pointer=1, /read-only/) + .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)) + .assert('main'===db.dbName(0)) + .assert('string' === typeof db.dbVfsName()) + .assert(db.pointer === wasm.xWrap.testConvertArg('sqlite3*',db)); + // Custom db error message handling via sqlite3_prepare_v2/v3() + let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null); + T.assert(capi.SQLITE_MISUSE === rc) + .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL")) + .assert(dbFile === db.dbFilename()) + .assert(!db.dbFilename('nope')); + //Sanity check DB.checkRc()... + let ex; + try{db.checkRc(rc)} + catch(e){ex = e} + T.assert(ex instanceof sqlite3.SQLite3Error) + .assert(0===ex.message.indexOf("sqlite3 result code")) + .assert(ex.message.indexOf("Invalid SQL")>0); + T.assert(db === db.checkRc(0)) + .assert(db === sqlite3.oo1.DB.checkRc(db,0)) + .assert(null === sqlite3.oo1.DB.checkRc(null,0)); + + this.progressHandlerCount = 0; + capi.sqlite3_progress_handler(db, 5, (p)=>{ + ++this.progressHandlerCount; + return 0; + }, 0); + }) + //////////////////////////////////////////////////////////////////// + .t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){ + let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0); + T.assert(0===rc); + rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAX+1, 0); + T.assert(capi.SQLITE_MISUSE === rc); + rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAINDBNAME, "main"); + T.assert(0 === rc); + const stack = wasm.pstack.pointer; + try { + const [pCur, pHi] = wasm.pstack.allocChunks(2,'i64'); + rc = capi.sqlite3_db_status(this.db, capi.SQLITE_DBSTATUS_LOOKASIDE_USED, + pCur, pHi, 0); + T.assert(0===rc); + if(!wasm.peek32(pCur)){ + rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LOOKASIDE, + 0, 4096, 12); + T.assert(0 === rc); + }else{ + console.debug("Cannot test db_config(SQLITE_DBCONFIG_LOOKASIDE)", + "while lookaside memory is in use."); + } + wasm.poke32([pCur, pHi], 0); + let [vCur, vHi] = wasm.peek32(pCur, pHi); + T.assert(0===vCur).assert(0===vHi); + rc = capi.sqlite3_status(capi.SQLITE_STATUS_MEMORY_USED, + pCur, pHi, 0); + [vCur, vHi] = wasm.peek32(pCur, pHi); + //console.warn("i32 vCur,vHi",vCur,vHi); + T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur); + if(wasm.bigIntEnabled){ + // Again in 64-bit. Recall that pCur and pHi are allocated + // large enough to account for this re-use. + wasm.poke64([pCur, pHi], 0); + rc = capi.sqlite3_status64(capi.SQLITE_STATUS_MEMORY_USED, + pCur, pHi, 0); + [vCur, vHi] = wasm.peek64([pCur, pHi]); + //console.warn("i64 vCur,vHi",vCur,vHi); + T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur); + } + }finally{ + wasm.pstack.restore(stack); + } + }) + + //////////////////////////////////////////////////////////////////// + .t('DB.Stmt', function(sqlite3){ + let st = this.db.prepare( + new TextEncoder('utf-8').encode("select 3 as a") + ); + //debug("statement =",st); + this.progressHandlerCount = 0; + try { + T.assert(wasm.isPtr(st.pointer)) + .mustThrowMatching(()=>st.pointer=1, /read-only/) + .assert(1===this.db.openStatementCount()) + .assert( + capi.sqlite3_stmt_status( + st, capi.SQLITE_STMTSTATUS_RUN, 0 + ) === 0) + .assert(!st._mayGet) + .assert('a' === st.getColumnName(0)) + .assert(1===st.columnCount) + .assert(0===st.parameterCount) + .mustThrow(()=>st.bind(1,null)) + .assert(true===st.step()) + .assert(3 === st.get(0)) + .mustThrow(()=>st.get(1)) + .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER)) + .assert(3 === st.get(0,capi.SQLITE_INTEGER)) + .assert(3 === st.getInt(0)) + .assert('3' === st.get(0,capi.SQLITE_TEXT)) + .assert('3' === st.getString(0)) + .assert(3.0 === st.get(0,capi.SQLITE_FLOAT)) + .assert(3.0 === st.getFloat(0)) + .assert(3 === st.get({}).a) + .assert(3 === st.get([])[0]) + .assert(3 === st.getJSON(0)) + .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array) + .assert(1===st.get(0,capi.SQLITE_BLOB).length) + .assert(st.getBlob(0) instanceof Uint8Array) + .assert('3'.charCodeAt(0) === st.getBlob(0)[0]) + .assert(st._mayGet) + .assert(false===st.step()) + .assert(!st._mayGet) + .assert( + capi.sqlite3_stmt_status( + st, capi.SQLITE_STMTSTATUS_RUN, 0 + ) > 0); + + T.assert(this.progressHandlerCount > 0, + "Expecting progress callback."). + assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). + assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). + assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). + assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); + }finally{ + st.finalize(); + } + T.assert(!st.pointer) + .assert(0===this.db.openStatementCount()); + + T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){ + return (err instanceof sqlite3.SQLite3Error) + && capi.SQLITE_MISUSE === err.resultCode + && 0 < err.message.indexOf("Do not call the Stmt constructor directly.") + }); + }) + + //////////////////////////////////////////////////////////////////////// + .t('sqlite3_js_...()', function(){ + const db = this.db; + if(1){ + const vfsList = capi.sqlite3_js_vfs_list(); + T.assert(vfsList.length>1); + //log("vfsList =",vfsList); + wasm.scopedAllocCall(()=>{ + const vfsArg = (v)=>wasm.xWrap.testConvertArg('sqlite3_vfs*',v); + for(const v of vfsList){ + T.assert('string' === typeof v); + const pVfs = capi.sqlite3_vfs_find(v); + T.assert(wasm.isPtr(pVfs)) + .assert(pVfs===vfsArg(v)); + const vfs = new capi.sqlite3_vfs(pVfs); + try { T.assert(vfsArg(vfs)===pVfs) } + finally{ vfs.dispose() } + } + }); + } + /** + Trivia: the magic db name ":memory:" does not actually use the + "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS + name. Instead, it uses the default VFS with an in-memory btree. + Thus this.db's VFS may not be memdb even though it's an in-memory + db. + */ + const pVfsMem = capi.sqlite3_vfs_find('memdb'), + pVfsDflt = capi.sqlite3_vfs_find(0), + pVfsDb = capi.sqlite3_js_db_vfs(db.pointer); + T.assert(pVfsMem > 0) + .assert(pVfsDflt > 0) + .assert(pVfsDb > 0) + .assert(pVfsMem !== pVfsDflt + /* memdb lives on top of the default vfs */) + .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem) + ; + /*const vMem = new capi.sqlite3_vfs(pVfsMem), + vDflt = new capi.sqlite3_vfs(pVfsDflt), + vDb = new capi.sqlite3_vfs(pVfsDb);*/ + const duv = capi.sqlite3_js_db_uses_vfs; + T.assert(pVfsDflt === duv(db.pointer, 0) + || pVfsMem === duv(db.pointer,0)) + .assert(!duv(db.pointer, "foo")) + ; + }/*sqlite3_js_...()*/) + + //////////////////////////////////////////////////////////////////// + .t('Table t', function(sqlite3){ + const db = this.db; + let list = []; + this.progressHandlerCount = 0; + let rc = db.exec({ + sql:['CREATE TABLE t(a,b);', + // ^^^ using TEMP TABLE breaks the db export test + "INSERT INTO t(a,b) VALUES(1,2),(3,4),", + "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for + off-by-one bug in string-to-WASM conversion*/], + saveSql: list, + bind: [5,6] + }); + //debug("Exec'd SQL:", list); + T.assert(rc === db) + .assert(2 === list.length) + .assert('string'===typeof list[1]) + .assert(4===db.changes()) + .assert(this.progressHandlerCount > 0, + "Expecting progress callback.") + if(wasm.bigIntEnabled){ + T.assert(4n===db.changes(false,true)); + } + + let vals = db.selectValues('select a from t order by a limit 2'); + T.assert( 2 === vals.length ) + .assert( 1===vals[0] && 3===vals[1] ); + vals = db.selectValues('select a from t order by a limit $L', + {$L:2}, capi.SQLITE_TEXT); + T.assert( 2 === vals.length ) + .assert( '1'===vals[0] && '3'===vals[1] ); + vals = undefined; + + let blob = db.selectValue("select b from t where a='blob'"); + T.assert(blob instanceof Uint8Array). + assert(0x68===blob[0] && 0x69===blob[1]); + blob = null; + let counter = 0, colNames = []; + list.length = 0; + db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{ + rowMode: 'object', + resultRows: list, + columnNames: colNames, + _myState: 3 /* Accessible from the callback */, + callback: function(row,stmt){ + ++counter; + T.assert( + 3 === this._myState + /* Recall that "this" is the options object. */ + ).assert( + this.columnNames[0]==='a' && this.columnNames[1]==='b' + /* options.columnNames is filled out before the first + Stmt.step(). */ + ).assert( + (row.a%2 && row.a<6) || 'blob'===row.a + ); + } + }); + T.assert(2 === colNames.length) + .assert('a' === colNames[0]) + .assert(4 === counter) + .assert(4 === list.length); + list.length = 0; + db.exec("SELECT a a, b b FROM t",{ + rowMode: 'array', + callback: function(row,stmt){ + ++counter; + T.assert(Array.isArray(row)) + .assert((0===row[1]%2 && row[1]<7) + || (row[1] instanceof Uint8Array)); + } + }); + T.assert(8 === counter); + T.assert(Number.MIN_SAFE_INTEGER === + db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)). + assert(Number.MAX_SAFE_INTEGER === + db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER)); + counter = 0; + let rv = db.exec({ + sql: "SELECT a FROM t", + callback: ()=>(1===++counter), + }); + T.assert(db === rv) + .assert(2===counter, + "Expecting exec step() loop to stop if callback returns false."); + /** If exec() is passed neither callback nor returnValue but + is passed an explicit rowMode then the default returnValue + is the whole result set, as if an empty resultRows option + had been passed. */ + rv = db.exec({ + sql: "SELECT -1 UNION ALL SELECT -2 UNION ALL SELECT -3 ORDER BY 1 DESC", + rowMode: 0 + }); + T.assert(Array.isArray(rv)).assert(3===rv.length) + .assert(-1===rv[0]).assert(-3===rv[2]); + rv = db.exec("SELECT 1 WHERE 0",{rowMode: 0}); + T.assert(Array.isArray(rv)).assert(0===rv.length); + if(wasm.bigIntEnabled && haveWasmCTests()){ + const mI = wasm.xCall('sqlite3_wasm_test_int64_max'); + const b = BigInt(Number.MAX_SAFE_INTEGER * 2); + T.assert(b === db.selectValue("SELECT "+b)). + assert(b === db.selectValue("SELECT ?", b)). + assert(mI == db.selectValue("SELECT $x", {$x:mI})); + }else{ + /* Curiously, the JS spec seems to be off by one with the definitions + of MIN/MAX_SAFE_INTEGER: + + https://github.com/emscripten-core/emscripten/issues/17391 */ + T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))). + mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1))); + } + + let st = db.prepare("update t set b=:b where a='blob'"); + try { + const ndx = st.getParamIndex(':b'); + T.assert(1===ndx); + st.bindAsBlob(ndx, "ima blob").reset(true); + } finally { + st.finalize(); + } + + try { + db.prepare("/*empty SQL*/"); + toss("Must not be reached."); + }catch(e){ + T.assert(e instanceof sqlite3.SQLite3Error) + .assert(0==e.message.indexOf('Cannot prepare empty')); + } + })/*setup table T*/ + + //////////////////////////////////////////////////////////////////// + .t({ + name: "sqlite3_set_authorizer()", + test:function(sqlite3){ + T.assert(capi.SQLITE_IGNORE>0) + .assert(capi.SQLITE_DENY>0); + const db = this.db; + const ssa = capi.sqlite3_set_authorizer; + const n = db.selectValue('select count(*) from t'); + T.assert(n>0); + let authCount = 0; + let rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){ + ++authCount; + return capi.SQLITE_IGNORE; + }, 0); + T.assert(0===rc) + .assert( + undefined === db.selectValue('select count(*) from t') + /* Note that the count() never runs, so we get undefined + instead of 0. */ + ) + .assert(authCount>0); + authCount = 0; + db.exec("update t set a=-9999"); + T.assert(authCount>0); + /* Reminder: we don't use DELETE because, from the C API docs: + + "If the action code is [SQLITE_DELETE] and the callback + returns [SQLITE_IGNORE] then the [DELETE] operation proceeds + but the [truncate optimization] is disabled and all rows are + deleted individually." + */ + rc = ssa(db, null, 0); + authCount = 0; + T.assert(-9999 != db.selectValue('select a from t')) + .assert(0===authCount); + rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){ + ++authCount; + return capi.SQLITE_DENY; + }, 0); + T.assert(0===rc); + let err; + try{ db.exec("select 1 from t") } + catch(e){ err = e } + T.assert(err instanceof sqlite3.SQLite3Error) + .assert(err.message.indexOf('not authorized'>0)) + .assert(1===authCount); + authCount = 0; + rc = ssa(db, function(...args){ + ++authCount; + return capi.SQLITE_OK; + }, 0); + T.assert(0===rc); + T.assert(n === db.selectValue('select count(*) from t')) + .assert(authCount>0); + authCount = 0; + rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){ + ++authCount; + throw new Error("Testing catching of authorizer."); + }, 0); + T.assert(0===rc); + authCount = 0; + err = undefined; + try{ db.exec("select 1 from t") } + catch(e){err = e} + T.assert(err instanceof Error) + .assert(err.message.indexOf('not authorized')>0) + /* Note that the thrown message is trumped/overwritten + by the authorizer process. */ + .assert(1===authCount); + rc = ssa(db, 0, 0); + authCount = 0; + T.assert(0===rc); + T.assert(n === db.selectValue('select count(*) from t')) + .assert(0===authCount); + } + })/*sqlite3_set_authorizer()*/ + + //////////////////////////////////////////////////////////////////////// + .t("sqlite3_table_column_metadata()", function(sqlite3){ + const stack = wasm.pstack.pointer; + try{ + const [pzDT, pzColl, pNotNull, pPK, pAuto] = + wasm.pstack.allocPtr(5); + const rc = capi.sqlite3_table_column_metadata( + this.db, "main", "t", "rowid", + pzDT, pzColl, pNotNull, pPK, pAuto + ); + T.assert(0===rc) + .assert("INTEGER"===wasm.cstrToJs(wasm.peekPtr(pzDT))) + .assert("BINARY"===wasm.cstrToJs(wasm.peekPtr(pzColl))) + .assert(0===wasm.peek32(pNotNull)) + .assert(1===wasm.peek32(pPK)) + .assert(0===wasm.peek32(pAuto)) + }finally{ + wasm.pstack.restore(stack); + } + }) + + //////////////////////////////////////////////////////////////////////// + .t('selectArray/Object()', function(sqlite3){ + const db = this.db; + let rc = db.selectArray('select a, b from t where a=?', 5); + T.assert(Array.isArray(rc)) + .assert(2===rc.length) + .assert(5===rc[0] && 6===rc[1]); + rc = db.selectArray('select a, b from t where b=-1'); + T.assert(undefined === rc); + rc = db.selectObject('select a A, b b from t where b=?', 6); + T.assert(rc && 'object'===typeof rc) + .assert(5===rc.A) + .assert(6===rc.b); + rc = db.selectArray('select a, b from t where b=-1'); + T.assert(undefined === rc); + }) + //////////////////////////////////////////////////////////////////////// + .t('selectArrays/Objects()', function(sqlite3){ + const db = this.db; + const sql = 'select a, b from t where a=? or b=? order by a'; + let rc = db.selectArrays(sql, [1, 4]); + T.assert(Array.isArray(rc)) + .assert(2===rc.length) + .assert(2===rc[0].length) + .assert(1===rc[0][0]) + .assert(2===rc[0][1]) + .assert(3===rc[1][0]) + .assert(4===rc[1][1]) + rc = db.selectArrays(sql, [99,99]); + T.assert(Array.isArray(rc)).assert(0===rc.length); + rc = db.selectObjects(sql, [1,4]); + T.assert(Array.isArray(rc)) + .assert(2===rc.length) + .assert('object' === typeof rc[1]) + .assert(1===rc[0].a) + .assert(2===rc[0].b) + .assert(3===rc[1].a) + .assert(4===rc[1].b); + }) + + //////////////////////////////////////////////////////////////////////// + .t({ + name: 'sqlite3_js_db_export()', + predicate: ()=>true, + test: function(sqlite3){ + const db = this.db; + const xp = capi.sqlite3_js_db_export(db.pointer); + T.assert(xp instanceof Uint8Array) + .assert(xp.byteLength>0) + .assert(0 === xp.byteLength % 512); + this.dbExport = xp; + } + }/*sqlite3_js_db_export()*/) + .t({ + name: 'sqlite3_js_vfs_create_file() with db in default VFS', + predicate: ()=>true, + test: function(sqlite3){ + const db = this.db; + const pVfs = capi.sqlite3_js_db_vfs(db); + const filename = "sqlite3_js_vfs_create_file().db"; + capi.sqlite3_js_vfs_create_file(pVfs, filename, this.dbExport); + delete this.dbExport; + const db2 = new sqlite3.oo1.DB(filename,'r'); + try { + const sql = "select count(*) from t"; + const n = db.selectValue(sql); + T.assert(n>0 && db2.selectValue(sql) === n); + }finally{ + db2.close(); + wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); + } + } + }/*sqlite3_js_vfs_create_file()*/) + + //////////////////////////////////////////////////////////////////// + .t({ + name:'Scalar UDFs', + test: function(sqlite3){ + const db = this.db; + db.createFunction("foo",(pCx,a,b)=>a+b); + T.assert(7===db.selectValue("select foo(3,4)")). + assert(5===db.selectValue("select foo(3,?)",2)). + assert(5===db.selectValue("select foo(?,?2)",[1,4])). + assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5})); + db.createFunction("bar", { + arity: -1, + xFunc: (pCx,...args)=>{ + T.assert(db.pointer === capi.sqlite3_context_db_handle(pCx)); + let rc = 0; + for(const v of args) rc += v; + return rc; + } + }).createFunction({ + name: "asis", + xFunc: (pCx,arg)=>arg + }); + T.assert(0===db.selectValue("select bar()")). + assert(1===db.selectValue("select bar(1)")). + assert(3===db.selectValue("select bar(1,2)")). + assert(-1===db.selectValue("select bar(1,2,-4)")). + assert('hi' === db.selectValue("select asis('hi')")). + assert('hi' === db.selectValue("select ?",'hi')). + assert(null === db.selectValue("select null")). + assert(null === db.selectValue("select asis(null)")). + assert(1 === db.selectValue("select ?",1)). + assert(2 === db.selectValue("select ?",[2])). + assert(3 === db.selectValue("select $a",{$a:3})). + assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))). + assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)"))); + + let blobArg = new Uint8Array([0x68, 0x69]); + let blobRc = db.selectValue( + "select asis(?1)", + blobArg.buffer/*confirm that ArrayBuffer is handled as a Uint8Array*/ + ); + T.assert(blobRc instanceof Uint8Array). + assert(2 === blobRc.length). + assert(0x68==blobRc[0] && 0x69==blobRc[1]); + blobRc = db.selectValue("select asis(X'6869')"); + T.assert(blobRc instanceof Uint8Array). + assert(2 === blobRc.length). + assert(0x68==blobRc[0] && 0x69==blobRc[1]); + + blobArg = new Int8Array([0x68, 0x69]); + //debug("blobArg=",blobArg); + blobRc = db.selectValue("select asis(?1)", blobArg); + T.assert(blobRc instanceof Uint8Array). + assert(2 === blobRc.length); + //debug("blobRc=",blobRc); + T.assert(0x68==blobRc[0] && 0x69==blobRc[1]); + + let rc = sqlite3.capi.sqlite3_create_function_v2( + this.db, "foo", 0, -1, 0, 0, 0, 0, 0 + ); + T.assert( + sqlite3.capi.SQLITE_FORMAT === rc, + "For invalid eTextRep argument." + ); + rc = sqlite3.capi.sqlite3_create_function_v2(this.db, "foo", 0); + T.assert( + sqlite3.capi.SQLITE_MISUSE === rc, + "For invalid arg count." + ); + + /* Confirm that we can map and unmap the same function with + multiple arities... */ + const fCounts = [0,0]; + const fArityCheck = function(pCx){ + return ++fCounts[arguments.length-1]; + }; + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + rc = capi.sqlite3_create_function_v2( + db, "nary", 0, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0 + ); + T.assert( 0===rc ); + rc = capi.sqlite3_create_function_v2( + db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0 + ); + T.assert( 0===rc ); + const sqlFArity0 = "select nary()"; + const sqlFArity1 = "select nary(1)"; + T.assert( 1 === db.selectValue(sqlFArity0) ) + .assert( 1 === fCounts[0] ).assert( 0 === fCounts[1] ); + T.assert( 1 === db.selectValue(sqlFArity1) ) + .assert( 1 === fCounts[0] ).assert( 1 === fCounts[1] ); + capi.sqlite3_create_function_v2( + db, "nary", 0, capi.SQLITE_UTF8, 0, 0, 0, 0, 0 + ); + T.mustThrowMatching((()=>db.selectValue(sqlFArity0)), + (e)=>((e instanceof sqlite3.SQLite3Error) + && e.message.indexOf("wrong number of arguments")>0), + "0-arity variant was uninstalled."); + T.assert( 2 === db.selectValue(sqlFArity1) ) + .assert( 1 === fCounts[0] ).assert( 2 === fCounts[1] ); + capi.sqlite3_create_function_v2( + db, "nary", 1, capi.SQLITE_UTF8, 0, 0, 0, 0, 0 + ); + T.mustThrowMatching((()=>db.selectValue(sqlFArity1)), + (e)=>((e instanceof sqlite3.SQLite3Error) + && e.message.indexOf("no such function")>0), + "1-arity variant was uninstalled."); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; + } + }) + + //////////////////////////////////////////////////////////////////// + .t({ + name: 'Aggregate UDFs', + //predicate: ()=>false, + test: function(sqlite3){ + const db = this.db; + const sjac = capi.sqlite3_js_aggregate_context; + db.createFunction({ + name: 'summer', + xStep: (pCtx, n)=>{ + const ac = sjac(pCtx, 4); + wasm.poke32(ac, wasm.peek32(ac) + Number(n)); + }, + xFinal: (pCtx)=>{ + const ac = sjac(pCtx, 0); + return ac ? wasm.peek32(ac) : 0; + } + }); + let v = db.selectValue([ + "with cte(v) as (", + "select 3 union all select 5 union all select 7", + ") select summer(v), summer(v+1) from cte" + /* ------------------^^^^^^^^^^^ ensures that we're handling + sqlite3_aggregate_context() properly. */ + ]); + T.assert(15===v); + T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"), + /wrong number of arguments/); + + db.createFunction({ + name: 'summerN', + arity: -1, + xStep: (pCtx, ...args)=>{ + const ac = sjac(pCtx, 4); + let sum = wasm.peek32(ac); + for(const v of args) sum += Number(v); + wasm.poke32(ac, sum); + }, + xFinal: (pCtx)=>{ + const ac = sjac(pCtx, 0); + capi.sqlite3_result_int( pCtx, ac ? wasm.peek32(ac) : 0 ); + // xFinal() may either return its value directly or call + // sqlite3_result_xyz() and return undefined. Both are + // functionally equivalent. + } + }); + T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)')); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{ + xFunc: ()=>{}, xStep: ()=>{} + }); + }, /scalar or aggregate\?/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xStep: ()=>{}}); + }, /Missing xFinal/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xFinal: ()=>{}}); + }, /Missing xStep/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{}); + }, /Missing function-type properties/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'}); + }, /xDestroy property must be a function/); + T.mustThrowMatching(()=>{ + db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'}); + }, /Invalid value for pApp/); + } + }/*aggregate UDFs*/) + + //////////////////////////////////////////////////////////////////////// + .t({ + name: 'Aggregate UDFs (64-bit)', + predicate: ()=>wasm.bigIntEnabled, + //predicate: ()=>false, + test: function(sqlite3){ + const db = this.db; + const sjac = capi.sqlite3_js_aggregate_context; + db.createFunction({ + name: 'summer64', + xStep: (pCtx, n)=>{ + const ac = sjac(pCtx, 8); + wasm.poke64(ac, wasm.peek64(ac) + BigInt(n)); + }, + xFinal: (pCtx)=>{ + const ac = sjac(pCtx, 0); + return ac ? wasm.peek64(ac) : 0n; + } + }); + let v = db.selectValue([ + "with cte(v) as (", + "select 9007199254740991 union all select 1 union all select 2", + ") select summer64(v), summer64(v+1) from cte" + ]); + T.assert(9007199254740994n===v); + } + }/*aggregate UDFs*/) + + //////////////////////////////////////////////////////////////////// + .t({ + name: 'Window UDFs', + //predicate: ()=>false, + test: function(){ + /* Example window function, table, and results taken from: + https://sqlite.org/windowfunctions.html#udfwinfunc */ + const db = this.db; + const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n); + const xValueFinal = (pCtx)=>{ + const ac = sjac(pCtx, 0); + return ac ? wasm.peek32(ac) : 0; + }; + const xStepInverse = (pCtx, n)=>{ + const ac = sjac(pCtx); + wasm.poke32(ac, wasm.peek32(ac) + Number(n)); + }; + db.createFunction({ + name: 'winsumint', + xStep: (pCtx, n)=>xStepInverse(pCtx, n), + xInverse: (pCtx, n)=>xStepInverse(pCtx, -n), + xFinal: xValueFinal, + xValue: xValueFinal + }); + db.exec([ + "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", + "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" + ]); + let rc = db.exec({ + returnValue: 'resultRows', + sql:[ + "SELECT x, winsumint(y) OVER (", + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", + ") AS sum_y ", + "FROM twin ORDER BY x;" + ] + }); + T.assert(Array.isArray(rc)) + .assert(5 === rc.length); + let count = 0; + for(const row of rc){ + switch(++count){ + case 1: T.assert('a'===row[0] && 9===row[1]); break; + case 2: T.assert('b'===row[0] && 12===row[1]); break; + case 3: T.assert('c'===row[0] && 16===row[1]); break; + case 4: T.assert('d'===row[0] && 12===row[1]); break; + case 5: T.assert('e'===row[0] && 9===row[1]); break; + default: toss("Too many rows to window function."); + } + } + const resultRows = []; + rc = db.exec({ + resultRows, + returnValue: 'resultRows', + sql:[ + "SELECT x, winsumint(y) OVER (", + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", + ") AS sum_y ", + "FROM twin ORDER BY x;" + ] + }); + T.assert(rc === resultRows) + .assert(5 === rc.length); + + rc = db.exec({ + returnValue: 'saveSql', + sql: "select 1; select 2; -- empty\n; select 3" + }); + T.assert(Array.isArray(rc)) + .assert(3===rc.length) + .assert('select 1;' === rc[0]) + .assert('select 2;' === rc[1]) + .assert('-- empty\n; select 3' === rc[2] + /* Strange but true. */); + T.mustThrowMatching(()=>{ + db.exec({sql:'', returnValue: 'nope'}); + }, /^Invalid returnValue/); + + db.exec("DROP TABLE twin"); + } + }/*window UDFs*/) + + //////////////////////////////////////////////////////////////////// + .t("ATTACH", function(){ + const db = this.db; + const resultRows = []; + db.exec({ + sql:new TextEncoder('utf-8').encode([ + // ^^^ testing string-vs-typedarray handling in exec() + "attach 'session' as foo;", + "create table foo.bar(a);", + "insert into foo.bar(a) values(1),(2),(3);", + "select a from foo.bar order by a;" + ].join('')), + rowMode: 0, + resultRows + }); + T.assert(3===resultRows.length) + .assert(2===resultRows[1]); + T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a')); + + /** Demonstrate the JS-simplified form of the sqlite3_exec() callback... */ + let colCount = 0, rowCount = 0; + let rc = capi.sqlite3_exec( + db, "select a, a*2 from foo.bar", function(aVals, aNames){ + //console.warn("execCallback(",arguments,")"); + colCount = aVals.length; + ++rowCount; + T.assert(2===aVals.length) + .assert(2===aNames.length) + .assert(+(aVals[1]) === 2 * +(aVals[0])); + }, 0, 0 + ); + T.assert(0===rc).assert(3===rowCount).assert(2===colCount); + rc = capi.sqlite3_exec( + db.pointer, "select a from foo.bar", ()=>{ + tossQuietly("Testing throwing from exec() callback."); + }, 0, 0 + ); + T.assert(capi.SQLITE_ABORT === rc); + + /* Demonstrate how to get access to the "full" callback + signature, as opposed to the simplified JS-specific one... */ + rowCount = colCount = 0; + const pCb = wasm.installFunction('i(pipp)', function(pVoid,nCols,aVals,aCols){ + /* Tip: wasm.cArgvToJs() can be used to convert aVals and + aCols to arrays: const vals = wasm.cArgvToJs(nCols, + aVals); */ + ++rowCount; + colCount = nCols; + T.assert(2 === nCols) + .assert(wasm.isPtr(pVoid)) + .assert(wasm.isPtr(aVals)) + .assert(wasm.isPtr(aCols)) + .assert(+wasm.cstrToJs(wasm.peekPtr(aVals + wasm.ptrSizeof)) + === 2 * +wasm.cstrToJs(wasm.peekPtr(aVals))); + return 0; + }); + try { + T.assert(wasm.isPtr(pCb)); + rc = capi.sqlite3_exec( + db, new TextEncoder('utf-8').encode("select a, a*2 from foo.bar"), + pCb, 0, 0 + ); + T.assert(0===rc) + .assert(3===rowCount) + .assert(2===colCount); + }finally{ + wasm.uninstallFunction(pCb); + } + + // Demonstrate that an OOM result does not propagate through sqlite3_exec()... + rc = capi.sqlite3_exec( + db, ["select a,"," a*2 from foo.bar"], (aVals, aNames)=>{ + sqlite3.WasmAllocError.toss("just testing"); + }, 0, 0 + ); + T.assert(capi.SQLITE_ABORT === rc); + + db.exec("detach foo"); + T.mustThrow(()=>db.exec("select * from foo.bar"), + "Because foo is no longer attached."); + }) + + //////////////////////////////////////////////////////////////////// + .t({ + name: 'C-side WASM tests', + predicate: ()=>(haveWasmCTests() || "Not compiled in."), + test: function(){ + const w = wasm, db = this.db; + const stack = w.scopedAllocPush(); + let ptrInt; + const origValue = 512; + try{ + ptrInt = w.scopedAlloc(4); + w.poke32(ptrInt,origValue); + const cf = w.xGet('sqlite3_wasm_test_intptr'); + const oldPtrInt = ptrInt; + T.assert(origValue === w.peek32(ptrInt)); + const rc = cf(ptrInt); + T.assert(2*origValue === rc). + assert(rc === w.peek32(ptrInt)). + assert(oldPtrInt === ptrInt); + const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/; + const o64 = 0x010203040506/*>32-bit integer*/; + if(w.bigIntEnabled){ + w.poke64(pi64, o64); + //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64); + const v64 = ()=>w.peek64(pi64) + T.assert(v64() == o64); + //T.assert(o64 === w.peek64(pi64)); + const cf64w = w.xGet('sqlite3_wasm_test_int64ptr'); + cf64w(pi64); + T.assert(v64() == BigInt(2 * o64)); + cf64w(pi64); + T.assert(v64() == BigInt(4 * o64)); + + const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2'); + T.assert(BigInt(2 * o64) === + biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError + in the call :/ */)); + + const pMin = w.scopedAlloc(16); + const pMax = pMin + 8; + const g64 = (p)=>w.peek64(p); + w.poke64([pMin, pMax], 0); + const minMaxI64 = [ + w.xCall('sqlite3_wasm_test_int64_min'), + w.xCall('sqlite3_wasm_test_int64_max') + ]; + T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)). + assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER)); + //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]); + w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax); + T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch"). + assert(g64(pMax) === minMaxI64[1], "int64 mismatch"); + //log("pMin",g64(pMin), "pMax",g64(pMax)); + w.poke64(pMin, minMaxI64[0]); + T.assert(g64(pMin) === minMaxI64[0]). + assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))). + assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax))); + const rxRange = /too big/; + T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))}, + rxRange). + mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))}, + (e)=>rxRange.test(e.message)); + }else{ + log("No BigInt support. Skipping related tests."); + log("\"The problem\" here is that we can manipulate, at the byte level,", + "heap memory to set 64-bit values, but we can't get those values", + "back into JS because of the lack of 64-bit integer support."); + } + }finally{ + const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1); + //log("x=",x,"y=",y,"z=",z); // just looking at the alignment + w.scopedAllocPop(stack); + } + } + }/* jaccwabyt-specific tests */) + + //////////////////////////////////////////////////////////////////////// + .t({ + name: 'virtual table #1: eponymous w/ manual exception handling', + predicate: ()=>!!capi.sqlite3_index_info, + test: function(sqlite3){ + const VT = sqlite3.vtab; + const tmplCols = Object.assign(Object.create(null),{ + A: 0, B: 1 + }); + /** + The vtab demonstrated here is a JS-ification of + ext/misc/templatevtab.c. + */ + const tmplMod = new sqlite3.capi.sqlite3_module(); + T.assert(0===tmplMod.$xUpdate); + tmplMod.setupModule({ + catchExceptions: false, + methods: { + xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){ + try{ + const args = wasm.cArgvToJs(argc, argv); + T.assert(args.length>=3) + .assert(args[0] === 'testvtab') + .assert(args[1] === 'main') + .assert(args[2] === 'testvtab'); + //console.debug("xConnect() args =",args); + const rc = capi.sqlite3_declare_vtab( + pDb, "CREATE TABLE ignored(a,b)" + ); + if(0===rc){ + const t = VT.xVtab.create(ppVtab); + T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab))); + } + return rc; + }catch(e){ + if(!(e instanceof sqlite3.WasmAllocError)){ + wasm.dealloc(wasm.peekPtr, pzErr); + wasm.pokePtr(pzErr, wasm.allocCString(e.message)); + } + return VT.xError('xConnect',e); + } + }, + xCreate: true /* just for testing. Will be removed afterwards. */, + xDisconnect: function(pVtab){ + try { + VT.xVtab.unget(pVtab).dispose(); + return 0; + }catch(e){ + return VT.xError('xDisconnect',e); + } + }, + xOpen: function(pVtab, ppCursor){ + try{ + const t = VT.xVtab.get(pVtab), + c = VT.xCursor.create(ppCursor); + T.assert(t instanceof capi.sqlite3_vtab) + .assert(c instanceof capi.sqlite3_vtab_cursor); + c._rowId = 0; + return 0; + }catch(e){ + return VT.xError('xOpen',e); + } + }, + xClose: function(pCursor){ + try{ + const c = VT.xCursor.unget(pCursor); + T.assert(c instanceof capi.sqlite3_vtab_cursor) + .assert(!VT.xCursor.get(pCursor)); + c.dispose(); + return 0; + }catch(e){ + return VT.xError('xClose',e); + } + }, + xNext: function(pCursor){ + try{ + const c = VT.xCursor.get(pCursor); + ++c._rowId; + return 0; + }catch(e){ + return VT.xError('xNext',e); + } + }, + xColumn: function(pCursor, pCtx, iCol){ + try{ + const c = VT.xCursor.get(pCursor); + switch(iCol){ + case tmplCols.A: + capi.sqlite3_result_int(pCtx, 1000 + c._rowId); + break; + case tmplCols.B: + capi.sqlite3_result_int(pCtx, 2000 + c._rowId); + break; + default: sqlite3.SQLite3Error.toss("Invalid column id",iCol); + } + return 0; + }catch(e){ + return VT.xError('xColumn',e); + } + }, + xRowid: function(pCursor, ppRowid64){ + try{ + const c = VT.xCursor.get(pCursor); + VT.xRowid(ppRowid64, c._rowId); + return 0; + }catch(e){ + return VT.xError('xRowid',e); + } + }, + xEof: function(pCursor){ + const c = VT.xCursor.get(pCursor), + rc = c._rowId>=10; + return rc; + }, + xFilter: function(pCursor, idxNum, idxCStr, + argc, argv/* [sqlite3_value* ...] */){ + try{ + const c = VT.xCursor.get(pCursor); + c._rowId = 0; + const list = capi.sqlite3_values_to_js(argc, argv); + T.assert(argc === list.length); + //log(argc,"xFilter value(s):",list); + return 0; + }catch(e){ + return VT.xError('xFilter',e); + } + }, + xBestIndex: function(pVtab, pIdxInfo){ + try{ + //const t = VT.xVtab.get(pVtab); + const sii = capi.sqlite3_index_info; + const pii = new sii(pIdxInfo); + pii.$estimatedRows = 10; + pii.$estimatedCost = 10.0; + //log("xBestIndex $nConstraint =",pii.$nConstraint); + if(pii.$nConstraint>0){ + // Validate nthConstraint() and nthConstraintUsage() + const max = pii.$nConstraint; + for(let i=0; i < max; ++i ){ + let v = pii.nthConstraint(i,true); + T.assert(wasm.isPtr(v)); + v = pii.nthConstraint(i); + T.assert(v instanceof sii.sqlite3_index_constraint) + .assert(v.pointer >= pii.$aConstraint); + v.dispose(); + v = pii.nthConstraintUsage(i,true); + T.assert(wasm.isPtr(v)); + v = pii.nthConstraintUsage(i); + T.assert(v instanceof sii.sqlite3_index_constraint_usage) + .assert(v.pointer >= pii.$aConstraintUsage); + v.$argvIndex = i;//just to get some values into xFilter + v.dispose(); + } + } + //log("xBestIndex $nOrderBy =",pii.$nOrderBy); + if(pii.$nOrderBy>0){ + // Validate nthOrderBy() + const max = pii.$nOrderBy; + for(let i=0; i < max; ++i ){ + let v = pii.nthOrderBy(i,true); + T.assert(wasm.isPtr(v)); + v = pii.nthOrderBy(i); + T.assert(v instanceof sii.sqlite3_index_orderby) + .assert(v.pointer >= pii.$aOrderBy); + v.dispose(); + } + } + pii.dispose(); + return 0; + }catch(e){ + return VT.xError('xBestIndex',e); + } + } + } + }); + this.db.onclose.disposeAfter.push(tmplMod); + T.assert(0===tmplMod.$xUpdate) + .assert(tmplMod.$xCreate) + .assert(tmplMod.$xCreate === tmplMod.$xConnect, + "setup() must make these equivalent and "+ + "installMethods() must avoid re-compiling identical functions"); + tmplMod.$xCreate = 0 /* make tmplMod eponymous-only */; + let rc = capi.sqlite3_create_module( + this.db, "testvtab", tmplMod, 0 + ); + this.db.checkRc(rc); + const list = this.db.selectArrays( + "SELECT a,b FROM testvtab where a<9999 and b>1 order by a, b" + /* Query is shaped so that it will ensure that some constraints + end up in xBestIndex(). */ + ); + T.assert(10===list.length) + .assert(1000===list[0][0]) + .assert(2009===list[list.length-1][1]); + } + })/*custom vtab #1*/ + + //////////////////////////////////////////////////////////////////////// + .t({ + name: 'virtual table #2: non-eponymous w/ automated exception wrapping', + predicate: ()=>!!capi.sqlite3_index_info, + test: function(sqlite3){ + const VT = sqlite3.vtab; + const tmplCols = Object.assign(Object.create(null),{ + A: 0, B: 1 + }); + /** + The vtab demonstrated here is a JS-ification of + ext/misc/templatevtab.c. + */ + let throwOnCreate = 1 ? 0 : capi.SQLITE_CANTOPEN + /* ^^^ just for testing exception wrapping. Note that sqlite + always translates errors from a vtable to a generic + SQLITE_ERROR unless it's from xConnect()/xCreate() and that + callback sets an error string. */; + const vtabTrace = 1 + ? ()=>{} + : (methodName,...args)=>console.debug('sqlite3_module::'+methodName+'():',...args); + const modConfig = { + /* catchExceptions changes how the methods are wrapped */ + catchExceptions: true, + name: "vtab2test", + methods:{ + xCreate: function(pDb, pAux, argc, argv, ppVtab, pzErr){ + vtabTrace("xCreate",...arguments); + if(throwOnCreate){ + sqlite3.SQLite3Error.toss( + throwOnCreate, + "Throwing a test exception." + ); + } + const args = wasm.cArgvToJs(argc, argv); + vtabTrace("xCreate","argv:",args); + T.assert(args.length>=3); + const rc = capi.sqlite3_declare_vtab( + pDb, "CREATE TABLE ignored(a,b)" + ); + if(0===rc){ + const t = VT.xVtab.create(ppVtab); + T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab))); + vtabTrace("xCreate",...arguments," ppVtab =",t.pointer); + } + return rc; + }, + xConnect: true, + xDestroy: function(pVtab){ + vtabTrace("xDestroy/xDisconnect",pVtab); + VT.xVtab.dispose(pVtab); + }, + xDisconnect: true, + xOpen: function(pVtab, ppCursor){ + const t = VT.xVtab.get(pVtab), + c = VT.xCursor.create(ppCursor); + T.assert(t instanceof capi.sqlite3_vtab) + .assert(c instanceof capi.sqlite3_vtab_cursor); + vtabTrace("xOpen",...arguments," cursor =",c.pointer); + c._rowId = 0; + }, + xClose: function(pCursor){ + vtabTrace("xClose",...arguments); + const c = VT.xCursor.unget(pCursor); + T.assert(c instanceof capi.sqlite3_vtab_cursor) + .assert(!VT.xCursor.get(pCursor)); + c.dispose(); + }, + xNext: function(pCursor){ + vtabTrace("xNext",...arguments); + const c = VT.xCursor.get(pCursor); + ++c._rowId; + }, + xColumn: function(pCursor, pCtx, iCol){ + vtabTrace("xColumn",...arguments); + const c = VT.xCursor.get(pCursor); + switch(iCol){ + case tmplCols.A: + capi.sqlite3_result_int(pCtx, 1000 + c._rowId); + break; + case tmplCols.B: + capi.sqlite3_result_int(pCtx, 2000 + c._rowId); + break; + default: sqlite3.SQLite3Error.toss("Invalid column id",iCol); + } + }, + xRowid: function(pCursor, ppRowid64){ + vtabTrace("xRowid",...arguments); + const c = VT.xCursor.get(pCursor); + VT.xRowid(ppRowid64, c._rowId); + }, + xEof: function(pCursor){ + vtabTrace("xEof",...arguments); + return VT.xCursor.get(pCursor)._rowId>=10; + }, + xFilter: function(pCursor, idxNum, idxCStr, + argc, argv/* [sqlite3_value* ...] */){ + vtabTrace("xFilter",...arguments); + const c = VT.xCursor.get(pCursor); + c._rowId = 0; + const list = capi.sqlite3_values_to_js(argc, argv); + T.assert(argc === list.length); + }, + xBestIndex: function(pVtab, pIdxInfo){ + vtabTrace("xBestIndex",...arguments); + //const t = VT.xVtab.get(pVtab); + const pii = VT.xIndexInfo(pIdxInfo); + pii.$estimatedRows = 10; + pii.$estimatedCost = 10.0; + pii.dispose(); + } + }/*methods*/ + }; + const tmplMod = VT.setupModule(modConfig); + T.assert(1===tmplMod.$iVersion); + this.db.onclose.disposeAfter.push(tmplMod); + this.db.checkRc(capi.sqlite3_create_module( + this.db.pointer, modConfig.name, tmplMod.pointer, 0 + )); + this.db.exec([ + "create virtual table testvtab2 using ", + modConfig.name, + "(arg1 blah, arg2 bloop)" + ]); + if(0){ + /* If we DROP TABLE then xDestroy() is called. If the + vtab is instead destroyed when the db is closed, + xDisconnect() is called. */ + this.db.onclose.disposeBefore.push(function(db){ + console.debug("Explicitly dropping testvtab2 via disposeBefore handler..."); + db.exec( + /** DROP TABLE is the only way to get xDestroy() to be called. */ + "DROP TABLE testvtab2" + ); + }); + } + let list = this.db.selectArrays( + "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by a, b" + /* Query is shaped so that it will ensure that some + constraints end up in xBestIndex(). */ + ); + T.assert(10===list.length) + .assert(1000===list[0][0]) + .assert(2009===list[list.length-1][1]); + + list = this.db.selectArrays( + "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by b, a limit 5" + ); + T.assert(5===list.length) + .assert(1000===list[0][0]) + .assert(2004===list[list.length-1][1]); + + // Call it as a table-valued function... + list = this.db.selectArrays([ + "SELECT a,b FROM ", modConfig.name, + " where a<9999 and b>1 order by b, a limit 1" + ]); + T.assert(1===list.length) + .assert(1000===list[0][0]) + .assert(2000===list[0][1]); + } + })/*custom vtab #2*/ + //////////////////////////////////////////////////////////////////////// + .t('Custom collation', function(sqlite3){ + let collationCounter = 0; + let myCmp = function(pArg,n1,p1,n2,p2){ + //int (*)(void*,int,const void*,int,const void*) + ++collationCounter; + const rc = wasm.exports.sqlite3_strnicmp(p1,p2,(n1this.db.checkRc(rc), + /SQLITE_UTF8 is the only supported encoding./); + /* + We need to ensure that replacing that collation function does + the right thing. We don't have a handle to the underlying WASM + pointer from here, so cannot verify (without digging through + internal state) that the old one gets uninstalled, but we can + verify that a new one properly replaces it. (That said, + console.warn() output has shown that the uninstallation does + happen.) + */ + collationCounter = 0; + myCmp = function(pArg,n1,p1,n2,p2){ + --collationCounter; + return 0; + }; + rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8, + 0, myCmp, 0); + this.db.checkRc(rc); + rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation"); + T.assert(rc>0).assert(-1===collationCounter); + rc = this.db.selectValue("select 'a' = 'b' collate mycollation"); + T.assert(rc>0).assert(-2===collationCounter); + rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8, + 0, null, 0); + this.db.checkRc(rc); + rc = 0; + try { + this.db.selectValue("select 'a' = 'b' collate mycollation"); + }catch(e){ + /* Why is e.resultCode not automatically an extended result + code? The DB() class enables those automatically. */ + rc = sqlite3.capi.sqlite3_extended_errcode(this.db); + } + T.assert(capi.SQLITE_ERROR_MISSING_COLLSEQ === rc); + })/*custom collation*/ + + //////////////////////////////////////////////////////////////////////// + .t('Close db', function(){ + T.assert(this.db).assert(wasm.isPtr(this.db.pointer)); + //wasm.sqlite3_wasm_db_reset(this.db); // will leak virtual tables! + this.db.close(); + T.assert(!this.db.pointer); + }) + ;/* end of oo1 checks */ + + //////////////////////////////////////////////////////////////////////// + T.g('kvvfs') + .t({ + name: 'kvvfs is disabled in worker', + predicate: ()=>(isWorker() || "test is only valid in a Worker"), + test: function(sqlite3){ + T.assert( + !capi.sqlite3_vfs_find('kvvfs'), + "Expecting kvvfs to be unregistered." + ); + } + }) + .t({ + name: 'kvvfs in main thread', + predicate: ()=>(isUIThread() + || "local/sessionStorage are unavailable in a Worker"), + test: function(sqlite3){ + const filename = this.kvvfsDbFile = 'session'; + const pVfs = capi.sqlite3_vfs_find('kvvfs'); + T.assert(pVfs); + const JDb = this.JDb = sqlite3.oo1.JsStorageDb; + const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)}; + unlink(); + let db = new JDb(filename); + try { + db.exec([ + 'create table kvvfs(a);', + 'insert into kvvfs(a) values(1),(2),(3)' + ]); + T.assert(3 === db.selectValue('select count(*) from kvvfs')); + db.close(); + db = new JDb(filename); + db.exec('insert into kvvfs(a) values(4),(5),(6)'); + T.assert(6 === db.selectValue('select count(*) from kvvfs')); + }finally{ + db.close(); + } + } + }/*kvvfs sanity checks*/) + .t({ + name: 'kvvfs sqlite3_js_vfs_create_file()', + predicate: ()=>"kvvfs does not currently support this", + test: function(sqlite3){ + let db; + try { + db = new this.JDb(this.kvvfsDbFile); + const exp = capi.sqlite3_js_db_export(db); + db.close(); + this.kvvfsUnlink(); + capi.sqlite3_js_vfs_create_file("kvvfs", this.kvvfsDbFile, exp); + db = new this.JDb(filename); + T.assert(6 === db.selectValue('select count(*) from kvvfs')); + }finally{ + db.close(); + this.kvvfsUnlink(); + } + delete this.kvvfsDbFile; + delete this.kvvfsUnlink; + delete this.JDb; + } + }/*kvvfs sqlite3_js_vfs_create_file()*/) + ;/* end kvvfs tests */ + + //////////////////////////////////////////////////////////////////////// + T.g('OPFS: Origin-Private File System', + (sqlite3)=>(sqlite3.opfs + ? true : "requires Worker thread in a compatible browser")) + .t({ + name: 'OPFS db sanity checks', + test: async function(sqlite3){ + const filename = this.opfsDbFile = 'sqlite3-tester1.db'; + const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs'); + T.assert(pVfs); + const unlink = this.opfsUnlink = + (fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)}; + unlink(); + let db = new sqlite3.oo1.OpfsDb(filename); + try { + db.exec([ + 'create table p(a);', + 'insert into p(a) values(1),(2),(3)' + ]); + T.assert(3 === db.selectValue('select count(*) from p')); + db.close(); + db = new sqlite3.oo1.OpfsDb(filename); + db.exec('insert into p(a) values(4),(5),(6)'); + T.assert(6 === db.selectValue('select count(*) from p')); + this.opfsDbExport = capi.sqlite3_js_db_export(db); + T.assert(this.opfsDbExport instanceof Uint8Array) + .assert(this.opfsDbExport.byteLength>0 + && 0===this.opfsDbExport.byteLength % 512); + }finally{ + db.close(); + unlink(); + } + } + }/*OPFS db sanity checks*/) + .t({ + name: 'OPFS export/import', + test: async function(sqlite3){ + let db; + try { + const exp = this.opfsDbExport; + delete this.opfsDbExport; + capi.sqlite3_js_vfs_create_file("opfs", this.opfsDbFile, exp); + const db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); + T.assert(6 === db.selectValue('select count(*) from p')); + }finally{ + if(db) db.close(); + } + } + }/*OPFS export/import*/) + .t({ + name: 'OPFS utility APIs and sqlite3_js_vfs_create_file()', + test: async function(sqlite3){ + const filename = this.opfsDbFile; + const pVfs = this.opfsVfs; + const unlink = this.opfsUnlink; + T.assert(filename && pVfs && !!unlink); + delete this.opfsDbFile; + delete this.opfsVfs; + delete this.opfsUnlink; + unlink(); + // Sanity-test sqlite3_js_vfs_create_file()... + /************************************************************** + ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended + for client-side use. It is only for this project's own + internal use. Its APIs are subject to change or removal at + any time. + ***************************************************************/ + const opfs = sqlite3.opfs; + const fSize = 1379; + let sh; + try{ + T.assert(!(await opfs.entryExists(filename))); + capi.sqlite3_js_vfs_create_file( + pVfs, filename, null, fSize + ); + T.assert(await opfs.entryExists(filename)); + let fh = await opfs.rootDirectory.getFileHandle(filename); + sh = await fh.createSyncAccessHandle(); + T.assert(fSize === await sh.getSize()); + await sh.close(); + sh = undefined; + unlink(); + T.assert(!(await opfs.entryExists(filename))); + + const ba = new Uint8Array([1,2,3,4,5]); + capi.sqlite3_js_vfs_create_file( + "opfs", filename, ba + ); + T.assert(await opfs.entryExists(filename)); + fh = await opfs.rootDirectory.getFileHandle(filename); + sh = await fh.createSyncAccessHandle(); + T.assert(ba.byteLength === await sh.getSize()); + await sh.close(); + sh = undefined; + unlink(); + + T.mustThrowMatching(()=>{ + capi.sqlite3_js_vfs_create_file( + "no-such-vfs", filename, ba + ); + }, "SQLITE_NOTFOUND: Unknown sqlite3_vfs name: no-such-vfs"); + }finally{ + if(sh) await sh.close(); + unlink(); + } + + // Some sanity checks of the opfs utility functions... + const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); + const aDir = testDir+'/test/dir'; + T.assert(await opfs.mkdir(aDir), "mkdir failed") + .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists") + .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)") + .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed") + .assert(!(await opfs.unlink(testDir+'/test/dir')), + "delete 2b should have failed (dir already deleted)") + .assert((await opfs.unlink(testDir, true)), "delete 3 failed") + .assert(!(await opfs.entryExists(testDir)), + "entryExists(",testDir,") should have failed"); + } + }/*OPFS util sanity checks*/) + ;/* end OPFS tests */ + + //////////////////////////////////////////////////////////////////////// + T.g('Hook APIs') + .t({ + name: "sqlite3_commit/rollback/update_hook()", + predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64", + test: function(sqlite3){ + let countCommit = 0, countRollback = 0;; + const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct'); + let rc = capi.sqlite3_commit_hook(db, (p)=>{ + ++countCommit; + return (1 === p) ? 0 : capi.SQLITE_ERROR; + }, 1); + T.assert( 0 === rc /*void pointer*/ ); + + // Commit hook... + db.exec("BEGIN; SELECT 1; COMMIT"); + T.assert(0 === countCommit, + "No-op transactions (mostly) do not trigger commit hook."); + db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT"); + T.assert(1 === countCommit, + "But EXCLUSIVE transactions do."); + db.transaction((d)=>{d.exec("create table t(a)");}); + T.assert(2 === countCommit); + + // Rollback hook: + rc = capi.sqlite3_rollback_hook(db, (p)=>{ + ++countRollback; + T.assert( 2 === p ); + }, 2); + T.assert( 0 === rc /*void pointer*/ ); + T.mustThrowMatching(()=>{ + db.transaction('drop table t',()=>{}) + }, (e)=>{ + return (capi.SQLITE_MISUSE === e.resultCode) + && ( e.message.indexOf('Invalid argument') > 0 ); + }); + T.assert(0 === countRollback, "Transaction was not started."); + T.mustThrowMatching(()=>{ + db.transaction('immediate', ()=>{ + sqlite3.SQLite3Error.toss(capi.SQLITE_FULL,'testing rollback hook'); + }); + }, (e)=>{ + return capi.SQLITE_FULL === e.resultCode + }); + T.assert(1 === countRollback); + + // Update hook... + const countUpdate = Object.create(null); + capi.sqlite3_update_hook(db, (p,op,dbName,tbl,rowid)=>{ + T.assert('main' === dbName.toLowerCase()) + .assert('t' === tbl.toLowerCase()) + .assert(3===p) + .assert('bigint' === typeof rowid); + switch(op){ + case capi.SQLITE_INSERT: + case capi.SQLITE_UPDATE: + case capi.SQLITE_DELETE: + countUpdate[op] = (countUpdate[op]||0) + 1; + break; + default: toss("Unexpected hook operator:",op); + } + }, 3); + db.transaction((d)=>{ + d.exec([ + "insert into t(a) values(1);", + "update t set a=2;", + "update t set a=3;", + "delete from t where a=3" + // update hook is not called for an unqualified DELETE + ]); + }); + T.assert(1 === countRollback) + .assert(3 === countCommit) + .assert(1 === countUpdate[capi.SQLITE_INSERT]) + .assert(2 === countUpdate[capi.SQLITE_UPDATE]) + .assert(1 === countUpdate[capi.SQLITE_DELETE]); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + T.assert(1 === capi.sqlite3_commit_hook(db, 0, 0)); + T.assert(2 === capi.sqlite3_rollback_hook(db, 0, 0)); + T.assert(3 === capi.sqlite3_update_hook(db, 0, 0)); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; + db.close(); + } + })/* commit/rollback/update hooks */ + .t({ + name: "sqlite3_preupdate_hook()", + predicate: ()=>wasm.bigIntEnabled || "Pre-update hook requires int64", + test: function(sqlite3){ + const db = new sqlite3.oo1.DB(':memory:', 1 ? 'c' : 'ct'); + const countHook = Object.create(null); + let rc = capi.sqlite3_preupdate_hook( + db, function(p, pDb, op, zDb, zTbl, iKey1, iKey2){ + T.assert(9 === p) + .assert(db.pointer === pDb) + .assert(1 === capi.sqlite3_preupdate_count(pDb)) + .assert( 0 > capi.sqlite3_preupdate_blobwrite(pDb) ); + countHook[op] = (countHook[op]||0) + 1; + switch(op){ + case capi.SQLITE_INSERT: + case capi.SQLITE_UPDATE: + T.assert('number' === typeof capi.sqlite3_preupdate_new_js(pDb, 0)); + break; + case capi.SQLITE_DELETE: + T.assert('number' === typeof capi.sqlite3_preupdate_old_js(pDb, 0)); + break; + default: toss("Unexpected hook operator:",op); + } + }, + 9 + ); + db.transaction((d)=>{ + d.exec([ + "create table t(a);", + "insert into t(a) values(1);", + "update t set a=2;", + "update t set a=3;", + "delete from t where a=3" + ]); + }); + T.assert(1 === countHook[capi.SQLITE_INSERT]) + .assert(2 === countHook[capi.SQLITE_UPDATE]) + .assert(1 === countHook[capi.SQLITE_DELETE]); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + db.close(); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; + } + })/*pre-update hooks*/ + ;/*end hook API tests*/ + + //////////////////////////////////////////////////////////////////////// + T.g('Auto-extension API') + .t({ + name: "Auto-extension sanity checks.", + test: function(sqlite3){ + let counter = 0; + const fp = wasm.installFunction('i(ppp)', function(pDb,pzErr,pApi){ + ++counter; + return 0; + }); + (new sqlite3.oo1.DB()).close(); + T.assert( 0===counter ); + capi.sqlite3_auto_extension(fp); + (new sqlite3.oo1.DB()).close(); + T.assert( 1===counter ); + (new sqlite3.oo1.DB()).close(); + T.assert( 2===counter ); + capi.sqlite3_cancel_auto_extension(fp); + wasm.uninstallFunction(fp); + (new sqlite3.oo1.DB()).close(); + T.assert( 2===counter ); + } + }); + + //////////////////////////////////////////////////////////////////////// + T.g('Session API') + .t({ + name: 'Session API sanity checks', + predicate: ()=>!!capi.sqlite3changegroup_add, + test: function(sqlite3){ + warn("The session API tests could use some expansion."); + const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB(); + const sqlInit = [ + "create table t(rowid INTEGER PRIMARY KEY,a,b); ", + "insert into t(rowid,a,b) values", + "(1,'a1','b1'),", + "(2,'a2','b2'),", + "(3,'a3','b3');" + ].join(''); + db1.exec(sqlInit); + db2.exec(sqlInit); + T.assert(3 === db1.selectValue("select count(*) from t")) + .assert('b3' === db1.selectValue('select b from t where rowid=3')); + const stackPtr = wasm.pstack.pointer; + try{ + let ppOut = wasm.pstack.allocPtr(); + let rc = capi.sqlite3session_create(db1, "main", ppOut); + T.assert(0===rc); + let pSession = wasm.peekPtr(ppOut); + T.assert(pSession && wasm.isPtr(pSession)); + capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{ + T.assert('t' === tbl).assert( 99 === pCtx ); + return 1; + }, 99); + db1.exec([ + "update t set b='bTwo' where rowid=2;", + "update t set a='aThree' where rowid=3;", + "delete from t where rowid=1;", + "insert into t(rowid,a,b) values(4,'a4','b4')" + ]); + T.assert('bTwo' === db1.selectValue("select b from t where rowid=2")) + .assert(undefined === db1.selectValue('select a from t where rowid=1')) + .assert('b4' === db1.selectValue('select b from t where rowid=4')) + .assert(3 === db1.selectValue('select count(*) from t')); + + const testSessionEnable = false; + if(testSessionEnable){ + rc = capi.sqlite3session_enable(pSession, 0); + T.assert( 0 === rc ) + .assert( 0 === capi.sqlite3session_enable(pSession, -1) ); + db1.exec("delete from t where rowid=2;"); + rc = capi.sqlite3session_enable(pSession, 1); + T.assert( rc > 0 ) + .assert( capi.sqlite3session_enable(pSession, -1) > 0 ) + .assert(undefined === db1.selectValue('select a from t where rowid=2')); + }else{ + warn("sqlite3session_enable() tests disabled due to unexpected results.", + "(Possibly a tester misunderstanding, as opposed to a bug.)"); + } + let db1Count = db1.selectValue("select count(*) from t"); + T.assert( db1Count === (testSessionEnable ? 2 : 3) ); + + /* Capture changeset and destroy session. */ + let pnChanges = wasm.pstack.alloc('i32'), + ppChanges = wasm.pstack.allocPtr(); + rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges); + T.assert( 0 === rc ); + capi.sqlite3session_delete(pSession); + pSession = 0; + const pChanges = wasm.peekPtr(ppChanges), + nChanges = wasm.peek32(pnChanges); + T.assert( pChanges && wasm.isPtr( pChanges ) ) + .assert( nChanges > 0 ); + + /* Revert db1 via an inverted changeset, but keep pChanges + and nChanges for application to db2. */ + rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges ); + T.assert( 0 === rc ); + rc = capi.sqlite3changeset_apply( + db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{ + return 1; + }, 0 + ); + T.assert( 0 === rc ); + wasm.dealloc( wasm.peekPtr(ppChanges) ); + pnChanges = ppChanges = 0; + T.assert('b2' === db1.selectValue("select b from t where rowid=2")) + .assert('a1' === db1.selectValue('select a from t where rowid=1')) + .assert(undefined === db1.selectValue('select b from t where rowid=4')); + db1Count = db1.selectValue("select count(*) from t"); + T.assert(3 === db1Count); + + /* Apply pre-reverted changeset (pChanges, nChanges) to + db2... */ + rc = capi.sqlite3changeset_apply( + db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{ + return pCtx ? 1 : 0 + }, 1 + ); + wasm.dealloc( pChanges ); + T.assert( 0 === rc ) + .assert( 'b4' === db2.selectValue('select b from t where rowid=4') ) + .assert( 'aThree' === db2.selectValue('select a from t where rowid=3') ) + .assert( undefined === db2.selectValue('select b from t where rowid=1') ); + if(testSessionEnable){ + T.assert( (undefined === db2.selectValue('select b from t where rowid=2')), + "But... the session was disabled when rowid=2 was deleted?" ); + log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid')); + T.assert( 3 === db2.selectValue('select count(*) from t') ); + }else{ + T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') ) + .assert( 3 === db2.selectValue('select count(*) from t') ); + } + }finally{ + wasm.pstack.restore(stackPtr); + db1.close(); + db2.close(); + } + } + })/*session API sanity tests*/ + ;/*end of session API group*/; + + //////////////////////////////////////////////////////////////////////// + log("Loading and initializing sqlite3 WASM module..."); + if(0){ + self.sqlite3ApiConfig = { + debug: ()=>{}, + log: ()=>{}, + warn: ()=>{}, + error: ()=>{} + } + } + if(!self.sqlite3InitModule && !isUIThread()){ + /* Vanilla worker, as opposed to an ES6 module worker */ + /* + If sqlite3.js is in a directory other than this script, in order + to get sqlite3.js to resolve sqlite3.wasm properly, we have to + explicitly tell it where sqlite3.js is being loaded from. We do + that by passing the `sqlite3.dir=theDirName` URL argument to + _this_ script. That URL argument will be seen by the JS/WASM + loader and it will adjust the sqlite3.wasm path accordingly. If + sqlite3.js/.wasm are in the same directory as this script then + that's not needed. + + URL arguments passed as part of the filename via importScripts() + are simply lost, and such scripts see the self.location of + _this_ script. + */ + let sqlite3Js = 'sqlite3.js'; + const urlParams = new URL(self.location.href).searchParams; + if(urlParams.has('sqlite3.dir')){ + sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; + } + importScripts(sqlite3Js); + } + self.sqlite3InitModule.__isUnderTest = + true /* disables certain API-internal cleanup so that we can + test internal APIs from here */; + self.sqlite3InitModule({ + print: log, + printErr: error + }).then(function(sqlite3){ + //console.log('sqlite3 =',sqlite3); + log("Done initializing WASM/JS bits. Running tests..."); + sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes."); + self.S = sqlite3; + capi = sqlite3.capi; + wasm = sqlite3.wasm; + log("sqlite3 version:",capi.sqlite3_libversion(), + capi.sqlite3_sourceid()); + if(wasm.bigIntEnabled){ + log("BigInt/int64 support is enabled."); + }else{ + logClass('warning',"BigInt/int64 support is disabled."); + } + if(haveWasmCTests()){ + log("sqlite3_wasm_test_...() APIs are available."); + }else{ + logClass('warning',"sqlite3_wasm_test_...() APIs unavailable."); + } + TestUtil.runTests(sqlite3); + }); +})(self); + diff --git a/ext/wasm/tester1.html b/ext/wasm/tester1.html deleted file mode 100644 index f7a2fba4a..000000000 --- a/ext/wasm/tester1.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - sqlite3 tester #1 (UI thread) - - - -

    sqlite3 WASM/JS tester #1 (UI thread)

    -
    See tester1-worker.html - for the Worker-thread variant.
    -
    - - -
    -
    - - - - diff --git a/ext/wasm/tester1.js b/ext/wasm/tester1.js deleted file mode 100644 index 99fb5b318..000000000 --- a/ext/wasm/tester1.js +++ /dev/null @@ -1,1864 +0,0 @@ -/* - 2022-10-12 - - The author disclaims copyright to this source code. In place of a - legal notice, here is a blessing: - - * May you do good and not evil. - * May you find forgiveness for yourself and forgive others. - * May you share freely, never taking more than you give. - - *********************************************************************** - - Main functional and regression tests for the sqlite3 WASM API. - - This mini-framework works like so: - - This script adds a series of test groups, each of which contains an - arbitrary number of tests, into a queue. After loading of the - sqlite3 WASM/JS module is complete, that queue is processed. If any - given test fails, the whole thing fails. This script is built such - that it can run from the main UI thread or worker thread. Test - groups and individual tests can be assigned a predicate function - which determines whether to run them or not, and this is - specifically intended to be used to toggle certain tests on or off - for the main/worker threads. - - Each test group defines a state object which gets applied as each - test function's `this`. Test functions can use that to, e.g., set up - a db in an early test and close it in a later test. Each test gets - passed the sqlite3 namespace object as its only argument. -*/ -'use strict'; -(function(){ - /** - Set up our output channel differently depending - on whether we are running in a worker thread or - the main (UI) thread. - */ - let logClass; - /* Predicate for tests/groups. */ - const isUIThread = ()=>(self.window===self && self.document); - /* Predicate for tests/groups. */ - const isWorker = ()=>!isUIThread(); - /* Predicate for tests/groups. */ - const testIsTodo = ()=>false; - const haveWasmCTests = ()=>{ - return !!wasm.exports.sqlite3_wasm_test_intptr; - }; - { - const mapToString = (v)=>{ - switch(typeof v){ - case 'number': case 'string': case 'boolean': - case 'undefined': case 'bigint': - return ''+v; - default: break; - } - if(null===v) return 'null'; - if(v instanceof Error){ - v = { - message: v.message, - stack: v.stack, - errorClass: v.name - }; - } - return JSON.stringify(v,undefined,2); - }; - const normalizeArgs = (args)=>args.map(mapToString); - if( isUIThread() ){ - console.log("Running in the UI thread."); - const logTarget = document.querySelector('#test-output'); - logClass = function(cssClass,...args){ - const ln = document.createElement('div'); - if(cssClass){ - for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){ - ln.classList.add(c); - } - } - ln.append(document.createTextNode(normalizeArgs(args).join(' '))); - logTarget.append(ln); - }; - const cbReverse = document.querySelector('#cb-log-reverse'); - const cbReverseKey = 'tester1:cb-log-reverse'; - const cbReverseIt = ()=>{ - logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); - //localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0); - }; - cbReverse.addEventListener('change', cbReverseIt, true); - /*if(localStorage.getItem(cbReverseKey)){ - cbReverse.checked = !!(+localStorage.getItem(cbReverseKey)); - }*/ - cbReverseIt(); - }else{ /* Worker thread */ - console.log("Running in a Worker thread."); - logClass = function(cssClass,...args){ - postMessage({ - type:'log', - payload:{cssClass, args: normalizeArgs(args)} - }); - }; - } - } - const reportFinalTestStatus = function(pass){ - if(isUIThread()){ - const e = document.querySelector('#color-target'); - e.classList.add(pass ? 'tests-pass' : 'tests-fail'); - }else{ - postMessage({type:'test-result', payload:{pass}}); - } - }; - const log = (...args)=>{ - //console.log(...args); - logClass('',...args); - } - const warn = (...args)=>{ - console.warn(...args); - logClass('warning',...args); - } - const error = (...args)=>{ - console.error(...args); - logClass('error',...args); - }; - - const toss = (...args)=>{ - error(...args); - throw new Error(args.join(' ')); - }; - const tossQuietly = (...args)=>{ - throw new Error(args.join(' ')); - }; - - const roundMs = (ms)=>Math.round(ms*100)/100; - - /** - Helpers for writing sqlite3-specific tests. - */ - const TestUtil = { - /** Running total of the number of tests run via - this API. */ - counter: 0, - /* Separator line for log messages. */ - separator: '------------------------------------------------------------', - /** - If expr is a function, it is called and its result - is returned, coerced to a bool, else expr, coerced to - a bool, is returned. - */ - toBool: function(expr){ - return (expr instanceof Function) ? !!expr() : !!expr; - }, - /** Throws if expr is false. If expr is a function, it is called - and its result is evaluated. If passed multiple arguments, - those after the first are a message string which get applied - as an exception message if the assertion fails. The message - arguments are concatenated together with a space between each. - */ - assert: function f(expr, ...msg){ - ++this.counter; - if(!this.toBool(expr)){ - throw new Error(msg.length ? msg.join(' ') : "Assertion failed."); - } - return this; - }, - /** Calls f() and squelches any exception it throws. If it - does not throw, this function throws. */ - mustThrow: function(f, msg){ - ++this.counter; - let err; - try{ f(); } catch(e){err=e;} - if(!err) throw new Error(msg || "Expected exception."); - return this; - }, - /** - Works like mustThrow() but expects filter to be a regex, - function, or string to match/filter the resulting exception - against. If f() does not throw, this test fails and an Error is - thrown. If filter is a regex, the test passes if - filter.test(error.message) passes. If it's a function, the test - passes if filter(error) returns truthy. If it's a string, the - test passes if the filter matches the exception message - precisely. In all other cases the test fails, throwing an - Error. - - If it throws, msg is used as the error report unless it's falsy, - in which case a default is used. - */ - mustThrowMatching: function(f, filter, msg){ - ++this.counter; - let err; - try{ f(); } catch(e){err=e;} - if(!err) throw new Error(msg || "Expected exception."); - let pass = false; - if(filter instanceof RegExp) pass = filter.test(err.message); - else if(filter instanceof Function) pass = filter(err); - else if('string' === typeof filter) pass = (err.message === filter); - if(!pass){ - throw new Error(msg || ("Filter rejected this exception: "+err.message)); - } - return this; - }, - /** Throws if expr is truthy or expr is a function and expr() - returns truthy. */ - throwIf: function(expr, msg){ - ++this.counter; - if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); - return this; - }, - /** Throws if expr is falsy or expr is a function and expr() - returns falsy. */ - throwUnless: function(expr, msg){ - ++this.counter; - if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); - return this; - }, - eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)), - TestGroup: (function(){ - let groupCounter = 0; - const TestGroup = function(name, predicate){ - this.number = ++groupCounter; - this.name = name; - this.predicate = predicate; - this.tests = []; - }; - TestGroup.prototype = { - addTest: function(testObj){ - this.tests.push(testObj); - return this; - }, - run: async function(sqlite3){ - log(TestUtil.separator); - logClass('group-start',"Group #"+this.number+':',this.name); - const indent = ' '; - if(this.predicate && !this.predicate(sqlite3)){ - logClass('warning',indent, - "SKIPPING group because predicate says to."); - return; - } - const assertCount = TestUtil.counter; - const groupState = Object.create(null); - const skipped = []; - let runtime = 0, i = 0; - for(const t of this.tests){ - ++i; - const n = this.number+"."+i; - log(indent, n+":", t.name); - if(t.predicate && !t.predicate(sqlite3)){ - logClass('warning', indent, indent, - 'SKIPPING because predicate says to'); - skipped.push( n+': '+t.name ); - }else{ - const tc = TestUtil.counter, now = performance.now(); - await t.test.call(groupState, sqlite3); - const then = performance.now(); - runtime += then - now; - logClass('faded',indent, indent, - TestUtil.counter - tc, 'assertion(s) in', - roundMs(then-now),'ms'); - } - } - logClass('green', - "Group #"+this.number+":",(TestUtil.counter - assertCount), - "assertion(s) in",roundMs(runtime),"ms"); - if(skipped.length){ - logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped); - } - } - }; - return TestGroup; - })()/*TestGroup*/, - testGroups: [], - currentTestGroup: undefined, - addGroup: function(name, predicate){ - this.testGroups.push( this.currentTestGroup = - new this.TestGroup(name, predicate) ); - return this; - }, - addTest: function(name, callback){ - let predicate; - if(1===arguments.length){ - const opt = arguments[0]; - predicate = opt.predicate; - name = opt.name; - callback = opt.test; - } - this.currentTestGroup.addTest({ - name, predicate, test: callback - }); - return this; - }, - runTests: async function(sqlite3){ - return new Promise(async function(pok,pnok){ - try { - let runtime = 0; - for(let g of this.testGroups){ - const now = performance.now(); - await g.run(sqlite3); - runtime += performance.now() - now; - } - log(TestUtil.separator); - logClass(['strong','green'], - "Done running tests.",TestUtil.counter,"assertions in", - roundMs(runtime),'ms'); - pok(); - reportFinalTestStatus(true); - }catch(e){ - error(e); - pnok(e); - reportFinalTestStatus(false); - } - }.bind(this)); - } - }/*TestUtil*/; - const T = TestUtil; - T.g = T.addGroup; - T.t = T.addTest; - let capi, wasm/*assigned after module init*/; - //////////////////////////////////////////////////////////////////////// - // End of infrastructure setup. Now define the tests... - //////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////// - T.g('Basic sanity checks') - .t('Namespace object checks', function(sqlite3){ - const wasmCtypes = wasm.ctype; - T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs'). - assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4). - assert(wasmCtypes.structs[1/*sqlite3_io_methods*/ - ].members.xFileSize.offset>0); - [ /* Spot-check a handful of constants to make sure they got installed... */ - 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8', - 'SQLITE_STATIC', 'SQLITE_DIRECTONLY', - 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE' - ].forEach((k)=>T.assert('number' === typeof capi[k])); - [/* Spot-check a few of the WASM API methods. */ - 'alloc', 'dealloc', 'installFunction' - ].forEach((k)=>T.assert(wasm[k] instanceof Function)); - - T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0). - assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0). - assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error'); - - try { - throw new sqlite3.WasmAllocError; - }catch(e){ - T.assert(e instanceof Error) - .assert(e instanceof sqlite3.WasmAllocError) - .assert("Allocation failed." === e.message); - } - try { - throw new sqlite3.WasmAllocError("test",{ - cause: 3 - }); - }catch(e){ - T.assert(3 === e.cause) - .assert("test" === e.message); - } - try {throw new sqlite3.WasmAllocError("test","ing",".")} - catch(e){T.assert("test ing ." === e.message)} - - try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) } - catch(e){ T.assert('SQLITE_SCHEMA' === e.message) } - try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) } - catch(e){ - T.assert('SQLITE_CORRUPT'===e.message) - .assert(true===e.cause); - } - }) - //////////////////////////////////////////////////////////////////// - .t('strglob/strlike', function(sqlite3){ - T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). - assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). - assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). - assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); - }) - //////////////////////////////////////////////////////////////////// - ;/*end of basic sanity checks*/ - - //////////////////////////////////////////////////////////////////// - T.g('C/WASM Utilities') - .t('sqlite3.wasm namespace', function(sqlite3){ - const w = wasm; - const chr = (x)=>x.charCodeAt(0); - //log("heap getters..."); - { - const li = [8, 16, 32]; - if(w.bigIntEnabled) li.push(64); - for(const n of li){ - const bpe = n/8; - const s = w.heapForSize(n,false); - T.assert(bpe===s.BYTES_PER_ELEMENT). - assert(w.heapForSize(s.constructor) === s); - const u = w.heapForSize(n,true); - T.assert(bpe===u.BYTES_PER_ELEMENT). - assert(s!==u). - assert(w.heapForSize(u.constructor) === u); - } - } - - // isPtr32() - { - const ip = w.isPtr32; - T.assert(ip(0)) - .assert(!ip(-1)) - .assert(!ip(1.1)) - .assert(!ip(0xffffffff)) - .assert(ip(0x7fffffff)) - .assert(!ip()) - .assert(!ip(null)/*might change: under consideration*/) - ; - } - - //log("jstrlen()..."); - { - T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc")); - } - - //log("jstrcpy()..."); - { - const fillChar = 10; - let ua = new Uint8Array(8), rc, - refill = ()=>ua.fill(fillChar); - refill(); - rc = w.jstrcpy("hello", ua); - T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]); - refill(); - ua[5] = chr('!'); - rc = w.jstrcpy("HELLO", ua, 0, -1, false); - T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]); - refill(); - rc = w.jstrcpy("the end", ua, 4); - //log("rc,ua",rc,ua); - T.assert(4===rc).assert(0===ua[7]). - assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); - refill(); - rc = w.jstrcpy("the end", ua, 4, -1, false); - T.assert(4===rc).assert(chr(' ')===ua[7]). - assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); - refill(); - rc = w.jstrcpy("", ua, 0, 1, true); - //log("rc,ua",rc,ua); - T.assert(1===rc).assert(0===ua[0]); - refill(); - rc = w.jstrcpy("x", ua, 0, 1, true); - //log("rc,ua",rc,ua); - T.assert(1===rc).assert(0===ua[0]); - refill(); - rc = w.jstrcpy('äbä', ua, 0, 1, true); - T.assert(1===rc, 'Must not write partial multi-byte char.') - .assert(0===ua[0]); - refill(); - rc = w.jstrcpy('äbä', ua, 0, 2, true); - T.assert(1===rc, 'Must not write partial multi-byte char.') - .assert(0===ua[0]); - refill(); - rc = w.jstrcpy('äbä', ua, 0, 2, false); - T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]); - }/*jstrcpy()*/ - - //log("cstrncpy()..."); - { - const scope = w.scopedAllocPush(); - try { - let cStr = w.scopedAllocCString("hello"); - const n = w.cstrlen(cStr); - let cpy = w.scopedAlloc(n+10); - let rc = w.cstrncpy(cpy, cStr, n+10); - T.assert(n+1 === rc). - assert("hello" === w.cstringToJs(cpy)). - assert(chr('o') === w.getMemValue(cpy+n-1)). - assert(0 === w.getMemValue(cpy+n)); - let cStr2 = w.scopedAllocCString("HI!!!"); - rc = w.cstrncpy(cpy, cStr2, 3); - T.assert(3===rc). - assert("HI!lo" === w.cstringToJs(cpy)). - assert(chr('!') === w.getMemValue(cpy+2)). - assert(chr('l') === w.getMemValue(cpy+3)); - }finally{ - w.scopedAllocPop(scope); - } - } - - //log("jstrToUintArray()..."); - { - let a = w.jstrToUintArray("hello", false); - T.assert(5===a.byteLength).assert(chr('o')===a[4]); - a = w.jstrToUintArray("hello", true); - T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]); - a = w.jstrToUintArray("äbä", false); - T.assert(5===a.byteLength).assert(chr('b')===a[2]); - a = w.jstrToUintArray("äbä", true); - T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]); - } - - //log("allocCString()..."); - { - const cstr = w.allocCString("hällo, world"); - const n = w.cstrlen(cstr); - T.assert(13 === n) - .assert(0===w.getMemValue(cstr+n)) - .assert(chr('d')===w.getMemValue(cstr+n-1)); - } - - //log("scopedAlloc() and friends..."); - { - const alloc = w.alloc, dealloc = w.dealloc; - w.alloc = w.dealloc = null; - T.assert(!w.scopedAlloc.level) - .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) - .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); - w.alloc = alloc; - T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); - w.dealloc = dealloc; - T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/) - .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) - .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/); - const asc = w.scopedAllocPush(); - let asc2; - try { - const p1 = w.scopedAlloc(16), - p2 = w.scopedAlloc(16); - T.assert(1===w.scopedAlloc.level) - .assert(Number.isFinite(p1)) - .assert(Number.isFinite(p2)) - .assert(asc[0] === p1) - .assert(asc[1]===p2); - asc2 = w.scopedAllocPush(); - const p3 = w.scopedAlloc(16); - T.assert(2===w.scopedAlloc.level) - .assert(Number.isFinite(p3)) - .assert(2===asc.length) - .assert(p3===asc2[0]); - - const [z1, z2, z3] = w.scopedAllocPtr(3); - T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2) - .assert(0===w.getMemValue(z1,'i32'), 'allocPtr() must zero the targets') - .assert(0===w.getMemValue(z3,'i32')); - }finally{ - // Pop them in "incorrect" order to make sure they behave: - w.scopedAllocPop(asc); - T.assert(0===asc.length); - T.mustThrowMatching(()=>w.scopedAllocPop(asc), - /^Invalid state object/); - if(asc2){ - T.assert(2===asc2.length,'Should be p3 and z1'); - w.scopedAllocPop(asc2); - T.assert(0===asc2.length); - T.mustThrowMatching(()=>w.scopedAllocPop(asc2), - /^Invalid state object/); - } - } - T.assert(0===w.scopedAlloc.level); - w.scopedAllocCall(function(){ - T.assert(1===w.scopedAlloc.level); - const [cstr, n] = w.scopedAllocCString("hello, world", true); - T.assert(12 === n) - .assert(0===w.getMemValue(cstr+n)) - .assert(chr('d')===w.getMemValue(cstr+n-1)); - }); - }/*scopedAlloc()*/ - - //log("xCall()..."); - { - const pJson = w.xCall('sqlite3_wasm_enum_json'); - T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300); - } - - //log("xWrap()..."); - { - T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'), - /requires 0 arg/). - assert(w.xWrap.resultAdapter('i32') instanceof Function). - assert(w.xWrap.argAdapter('i32') instanceof Function); - let fw = w.xWrap('sqlite3_libversion','utf8'); - T.mustThrowMatching(()=>fw(1), /requires 0 arg/); - let rc = fw(); - T.assert('string'===typeof rc).assert(rc.length>5); - rc = w.xCallWrapped('sqlite3_wasm_enum_json','*'); - T.assert(rc>0 && Number.isFinite(rc)); - rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8'); - T.assert('string'===typeof rc).assert(rc.length>300); - if(haveWasmCTests()){ - fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:free',['i32']); - rc = fw(0); - T.assert('hello'===rc); - rc = fw(1); - T.assert(null===rc); - - if(w.bigIntEnabled){ - w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v)); - w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v)); - fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice'); - rc = fw(1); - T.assert(12n===rc); - - w.scopedAllocCall(function(){ - let pI1 = w.scopedAlloc(8), pI2 = pI1+4; - w.setMemValue(pI1, 0,'*')(pI2, 0, '*'); - let f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']); - let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64'); - T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2)); - }); - } - } - } - }/*WhWasmUtil*/) - - //////////////////////////////////////////////////////////////////// - .t('sqlite3.StructBinder (jaccwabyt)', function(sqlite3){ - const S = sqlite3, W = S.wasm; - const MyStructDef = { - sizeof: 16, - members: { - p4: {offset: 0, sizeof: 4, signature: "i"}, - pP: {offset: 4, sizeof: 4, signature: "P"}, - ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true}, - cstr: {offset: 12, sizeof: 4, signature: "s"} - } - }; - if(W.bigIntEnabled){ - const m = MyStructDef; - m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"}; - m.sizeof += m.members.p8.sizeof; - } - const StructType = S.StructBinder.StructType; - const K = S.StructBinder('my_struct',MyStructDef); - T.mustThrowMatching(()=>K(), /via 'new'/). - mustThrowMatching(()=>new K('hi'), /^Invalid pointer/); - const k1 = new K(), k2 = new K(); - try { - T.assert(k1.constructor === K). - assert(K.isA(k1)). - assert(k1 instanceof K). - assert(K.prototype.lookupMember('p4').key === '$p4'). - assert(K.prototype.lookupMember('$p4').name === 'p4'). - mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/). - assert(undefined === K.prototype.lookupMember('nope',false)). - assert(k1 instanceof StructType). - assert(StructType.isA(k1)). - assert(K.resolveToInstance(k1.pointer)===k1). - mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/). - assert(k1 === StructType.instanceForPointer(k1.pointer)). - mustThrowMatching(()=>k1.$ro = 1, /read-only/); - Object.keys(MyStructDef.members).forEach(function(key){ - key = K.memberKey(key); - T.assert(0 == k1[key], - "Expecting allocation to zero the memory "+ - "for "+key+" but got: "+k1[key]+ - " from "+k1.memoryDump()); - }); - T.assert('number' === typeof k1.pointer). - mustThrowMatching(()=>k1.pointer = 1, /pointer/). - assert(K.instanceForPointer(k1.pointer) === k1); - k1.$p4 = 1; k1.$pP = 2; - T.assert(1 === k1.$p4).assert(2 === k1.$pP); - if(MyStructDef.members.$p8){ - k1.$p8 = 1/*must not throw despite not being a BigInt*/; - k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2); - T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8); - } - T.assert(!k1.ondispose); - k1.setMemberCString('cstr', "A C-string."); - T.assert(Array.isArray(k1.ondispose)). - assert(k1.ondispose[0] === k1.$cstr). - assert('number' === typeof k1.$cstr). - assert('A C-string.' === k1.memberToJsString('cstr')); - k1.$pP = k2; - T.assert(k1.$pP === k2); - k1.$pP = null/*null is special-cased to 0.*/; - T.assert(0===k1.$pP); - let ptr = k1.pointer; - k1.dispose(); - T.assert(undefined === k1.pointer). - assert(undefined === K.instanceForPointer(ptr)). - mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); - const k3 = new K(); - ptr = k3.pointer; - T.assert(k3 === K.instanceForPointer(ptr)); - K.disposeAll(); - T.assert(ptr). - assert(undefined === k2.pointer). - assert(undefined === k3.pointer). - assert(undefined === K.instanceForPointer(ptr)); - }finally{ - k1.dispose(); - k2.dispose(); - } - - if(!W.bigIntEnabled){ - log("Skipping WasmTestStruct tests: BigInt not enabled."); - return; - } - - const WTStructDesc = - W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0]; - const autoResolvePtr = true /* EXPERIMENTAL */; - if(autoResolvePtr){ - WTStructDesc.members.ppV.signature = 'P'; - } - const WTStruct = S.StructBinder(WTStructDesc); - //log(WTStruct.structName, WTStruct.structInfo); - const wts = new WTStruct(); - //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype)); - try{ - T.assert(wts.constructor === WTStruct). - assert(WTStruct.memberKeys().indexOf('$ppV')>=0). - assert(wts.memberKeys().indexOf('$v8')>=0). - assert(!K.isA(wts)). - assert(WTStruct.isA(wts)). - assert(wts instanceof WTStruct). - assert(wts instanceof StructType). - assert(StructType.isA(wts)). - assert(wts === StructType.instanceForPointer(wts.pointer)); - T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). - assert(0===wts.$ppV).assert(0===wts.$xFunc). - assert(WTStruct.instanceForPointer(wts.pointer) === wts); - const testFunc = - W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/); - let counter = 0; - //log("wts.pointer =",wts.pointer); - const wtsFunc = function(arg){ - /*log("This from a JS function called from C, "+ - "which itself was called from JS. arg =",arg);*/ - ++counter; - T.assert(WTStruct.instanceForPointer(arg) === wts); - if(3===counter){ - tossQuietly("Testing exception propagation."); - } - } - wts.$v4 = 10; wts.$v8 = 20; - wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc')) - T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8) - .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc) - .assert(0 === wts.$cstr) - .assert(wts.memberIsString('$cstr')) - .assert(!wts.memberIsString('$v4')) - .assert(null === wts.memberToJsString('$cstr')) - .assert(W.functionEntry(wts.$xFunc) instanceof Function); - /* It might seem silly to assert that the values match - what we just set, but recall that all of those property - reads and writes are, via property interceptors, - actually marshaling their data to/from a raw memory - buffer, so merely reading them back is actually part of - testing the struct-wrapping API. */ - - testFunc(wts.pointer); - //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV); - T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8) - .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)) - .assert('string' === typeof wts.memberToJsString('cstr')) - .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr')) - .mustThrowMatching(()=>wts.memberToJsString('xFunc'), - /Invalid member type signature for C-string/) - ; - testFunc(wts.pointer); - T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8) - .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)); - /** The 3rd call to wtsFunc throw from JS, which is called - from C, which is called from JS. Let's ensure that - that exception propagates back here... */ - T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/); - W.uninstallFunction(wts.$xFunc); - wts.$xFunc = 0; - if(autoResolvePtr){ - wts.$ppV = 0; - T.assert(!wts.$ppV); - //WTStruct.debugFlags(0x03); - wts.$ppV = wts; - T.assert(wts === wts.$ppV) - //WTStruct.debugFlags(0); - } - wts.setMemberCString('cstr', "A C-string."); - T.assert(Array.isArray(wts.ondispose)). - assert(wts.ondispose[0] === wts.$cstr). - assert('A C-string.' === wts.memberToJsString('cstr')); - const ptr = wts.pointer; - wts.dispose(); - T.assert(ptr).assert(undefined === wts.pointer). - assert(undefined === WTStruct.instanceForPointer(ptr)) - }finally{ - wts.dispose(); - } - }/*StructBinder*/) - - //////////////////////////////////////////////////////////////////// - .t('sqlite3.StructBinder part 2', function(sqlite3){ - // https://www.sqlite.org/c3ref/vfs.html - // https://www.sqlite.org/c3ref/io_methods.html - const sqlite3_io_methods = capi.sqlite3_io_methods, - sqlite3_vfs = capi.sqlite3_vfs, - sqlite3_file = capi.sqlite3_file; - //log("struct sqlite3_file", sqlite3_file.memberKeys()); - //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys()); - //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys()); - const installMethod = function callee(tgt, name, func){ - if(1===arguments.length){ - return (n,f)=>callee(tgt,n,f); - } - if(!callee.argcProxy){ - callee.argcProxy = function(func,sig){ - return function(...args){ - if(func.length!==arguments.length){ - toss("Argument mismatch. Native signature is:",sig); - } - return func.apply(this, args); - } - }; - callee.ondisposeRemoveFunc = function(){ - if(this.__ondispose){ - const who = this; - this.__ondispose.forEach( - (v)=>{ - if('number'===typeof v){ - try{wasm.uninstallFunction(v)} - catch(e){/*ignore*/} - }else{/*wasm function wrapper property*/ - delete who[v]; - } - } - ); - delete this.__ondispose; - } - }; - }/*static init*/ - const sigN = tgt.memberSignature(name), - memKey = tgt.memberKey(name); - //log("installMethod",tgt, name, sigN); - if(!tgt.__ondispose){ - T.assert(undefined === tgt.ondispose); - tgt.ondispose = [callee.ondisposeRemoveFunc]; - tgt.__ondispose = []; - } - const fProxy = callee.argcProxy(func, sigN); - const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); - tgt[memKey] = pFunc; - /** - ACHTUNG: function pointer IDs are from a different pool than - allocation IDs, starting at 1 and incrementing in steps of 1, - so if we set tgt[memKey] to those values, we'd very likely - later misinterpret them as plain old pointer addresses unless - unless we use some silly heuristic like "all values <5k are - presumably function pointers," or actually perform a function - lookup on every pointer to first see if it's a function. That - would likely work just fine, but would be kludgy. - - It turns out that "all values less than X are functions" is - essentially how it works in wasm: a function pointer is - reported to the client as its index into the - __indirect_function_table. - - So... once jaccwabyt can be told how to access the - function table, it could consider all pointer values less - than that table's size to be functions. As "real" pointer - values start much, much higher than the function table size, - that would likely work reasonably well. e.g. the object - pointer address for sqlite3's default VFS is (in this local - setup) 65104, whereas the function table has fewer than 600 - entries. - */ - const wrapperKey = '$'+memKey; - tgt[wrapperKey] = fProxy; - tgt.__ondispose.push(pFunc, wrapperKey); - //log("tgt.__ondispose =",tgt.__ondispose); - return (n,f)=>callee(tgt, n, f); - }/*installMethod*/; - - const installIOMethods = function instm(iom){ - (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type."); - if(!instm._requireFileArg){ - instm._requireFileArg = function(arg,methodName){ - arg = capi.sqlite3_file.resolveToInstance(arg); - if(!arg){ - err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file."); - } - return arg; - }; - instm._methods = { - // https://sqlite.org/c3ref/io_methods.html - xClose: /*i(P)*/function(f){ - /* int (*xClose)(sqlite3_file*) */ - log("xClose(",f,")"); - if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE; - f.dispose(/*noting that f has externally-owned memory*/); - return 0; - }, - xRead: /*i(Ppij)*/function(f,dest,n,offset){ - /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */ - log("xRead(",arguments,")"); - if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE; - wasm.heap8().fill(0, dest + offset, n); - return 0; - }, - xWrite: /*i(Ppij)*/function(f,dest,n,offset){ - /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */ - log("xWrite(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE; - return 0; - }, - xTruncate: /*i(Pj)*/function(f){ - /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */ - log("xTruncate(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE; - return 0; - }, - xSync: /*i(Pi)*/function(f){ - /* int (*xSync)(sqlite3_file*, int flags) */ - log("xSync(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE; - return 0; - }, - xFileSize: /*i(Pp)*/function(f,pSz){ - /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */ - log("xFileSize(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE; - wasm.setMemValue(pSz, 0/*file size*/); - return 0; - }, - xLock: /*i(Pi)*/function(f){ - /* int (*xLock)(sqlite3_file*, int) */ - log("xLock(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE; - return 0; - }, - xUnlock: /*i(Pi)*/function(f){ - /* int (*xUnlock)(sqlite3_file*, int) */ - log("xUnlock(",arguments,")"); - if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE; - return 0; - }, - xCheckReservedLock: /*i(Pp)*/function(){ - /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */ - log("xCheckReservedLock(",arguments,")"); - return 0; - }, - xFileControl: /*i(Pip)*/function(){ - /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */ - log("xFileControl(",arguments,")"); - return capi.SQLITE_NOTFOUND; - }, - xSectorSize: /*i(P)*/function(){ - /* int (*xSectorSize)(sqlite3_file*) */ - log("xSectorSize(",arguments,")"); - return 0/*???*/; - }, - xDeviceCharacteristics:/*i(P)*/function(){ - /* int (*xDeviceCharacteristics)(sqlite3_file*) */ - log("xDeviceCharacteristics(",arguments,")"); - return 0; - } - }; - }/*static init*/ - iom.$iVersion = 1; - Object.keys(instm._methods).forEach( - (k)=>installMethod(iom, k, instm._methods[k]) - ); - }/*installIOMethods()*/; - - const iom = new sqlite3_io_methods, sfile = new sqlite3_file; - const err = console.error.bind(console); - try { - const IOM = sqlite3_io_methods, S3F = sqlite3_file; - //log("iom proto",iom,iom.constructor.prototype); - //log("sfile",sfile,sfile.constructor.prototype); - T.assert(0===sfile.$pMethods).assert(iom.pointer > 0); - //log("iom",iom); - sfile.$pMethods = iom.pointer; - T.assert(iom.pointer === sfile.$pMethods) - .assert(IOM.resolveToInstance(iom)) - .assert(undefined ===IOM.resolveToInstance(sfile)) - .mustThrow(()=>IOM.resolveToInstance(0,true)) - .assert(S3F.resolveToInstance(sfile.pointer)) - .assert(undefined===S3F.resolveToInstance(iom)) - .assert(iom===IOM.resolveToInstance(sfile.$pMethods)); - T.assert(0===iom.$iVersion); - installIOMethods(iom); - T.assert(1===iom.$iVersion); - //log("iom.__ondispose",iom.__ondispose); - T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10); - }finally{ - iom.dispose(); - T.assert(undefined === iom.__ondispose); - } - - const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null)); - try { - const SB = sqlite3.StructBinder; - T.assert(dVfs instanceof SB.StructType) - .assert(dVfs.pointer) - .assert('sqlite3_vfs' === dVfs.structName) - .assert(!!dVfs.structInfo) - .assert(SB.StructType.hasExternalPointer(dVfs)) - .assert(dVfs.$iVersion>0) - .assert('number'===typeof dVfs.$zName) - .assert('number'===typeof dVfs.$xSleep) - .assert(wasm.functionEntry(dVfs.$xOpen)) - .assert(dVfs.memberIsString('zName')) - .assert(dVfs.memberIsString('$zName')) - .assert(!dVfs.memberIsString('pAppData')) - .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'), - /Invalid member type signature for C-string/) - .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/) - .assert('string' === typeof dVfs.memberToJsString('zName')) - .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName')) - ; - //log("Default VFS: @",dVfs.pointer); - Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){ - const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname], - addr = dVfs[mk], prefix = 'defaultVfs.'+mname; - if(1===mbr.signature.length){ - let sep = '?', val = undefined; - switch(mbr.signature[0]){ - // TODO: move this into an accessor, e.g. getPreferredValue(member) - case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break - case 'p': case 'P': sep = '@'; val = dVfs[mk]; break; - case 's': sep = '='; - val = dVfs.memberToJsString(mname); - break; - } - //log(prefix, sep, val); - }else{ - //log(prefix," = funcptr @",addr, wasm.functionEntry(addr)); - } - }); - }finally{ - dVfs.dispose(); - T.assert(undefined===dVfs.pointer); - } - }/*StructBinder part 2*/) - - //////////////////////////////////////////////////////////////////// - .t('sqlite3.wasm.pstack', function(sqlite3){ - const P = wasm.pstack; - const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError; - const stack = P.pointer; - T.assert(0===stack % 8 /* must be 8-byte aligned */); - try{ - const remaining = P.remaining; - T.assert(P.quota >= 4096) - .assert(remaining === P.quota) - .mustThrowMatching(()=>P.alloc(0), isAllocErr) - .mustThrowMatching(()=>P.alloc(-1), isAllocErr); - let p1 = P.alloc(12); - T.assert(p1 === stack - 16/*8-byte aligned*/) - .assert(P.pointer === p1); - let p2 = P.alloc(7); - T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/) - .mustThrowMatching(()=>P.alloc(remaining), isAllocErr) - .assert(24 === stack - p2) - .assert(P.pointer === p2); - let n = remaining - (stack - p2); - let p3 = P.alloc(n); - T.assert(p3 === stack-remaining) - .mustThrowMatching(()=>P.alloc(1), isAllocErr); - }finally{ - P.restore(stack); - } - - T.assert(P.pointer === stack); - try { - const [p1, p2, p3] = P.allocChunks(3,4); - T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/) - .assert(p2 === p1 + 4) - .assert(p3 === p2 + 4); - T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16), - (e)=>e instanceof sqlite3.WasmAllocError) - }finally{ - P.restore(stack); - } - - T.assert(P.pointer === stack); - try { - let [p1, p2, p3] = P.allocPtr(3,false); - let sPos = stack-16/*always rounded to multiple of 8*/; - T.assert(P.pointer === sPos) - .assert(p2 === p1 + 4) - .assert(p3 === p2 + 4); - [p1, p2, p3] = P.allocPtr(3); - T.assert(P.pointer === sPos-24/*3 x 8 bytes*/) - .assert(p2 === p1 + 8) - .assert(p3 === p2 + 8); - p1 = P.allocPtr(); - T.assert('number'===typeof p1); - }finally{ - P.restore(stack); - } - }/*pstack tests*/) - - //////////////////////////////////////////////////////////////////// - ;/*end of C/WASM utils checks*/ - - T.g('sqlite3_randomness()') - .t('To memory buffer', function(sqlite3){ - const stack = wasm.pstack.pointer; - try{ - const n = 520; - const p = wasm.pstack.alloc(n); - T.assert(0===wasm.getMemValue(p)) - .assert(0===wasm.getMemValue(p+n-1)); - T.assert(undefined === capi.sqlite3_randomness(n - 10, p)); - let j, check = 0; - const heap = wasm.heap8u(); - for(j = 0; j < 10 && 0===check; ++j){ - check += heap[p + j]; - } - T.assert(check > 0); - check = 0; - // Ensure that the trailing bytes were not modified... - for(j = n - 10; j < n && 0===check; ++j){ - check += heap[p + j]; - } - T.assert(0===check); - }finally{ - wasm.pstack.restore(stack); - } - }) - .t('To byte array', function(sqlite3){ - const ta = new Uint8Array(117); - let i, n = 0; - for(i=0; i0); - const t0 = new Uint8Array(0); - T.assert(t0 === capi.sqlite3_randomness(t0), - "0-length array is a special case"); - }) - ;/*end sqlite3_randomness() checks*/ - - //////////////////////////////////////////////////////////////////////// - T.g('sqlite3.oo1') - .t('Create db', function(sqlite3){ - const dbFile = '/tester1.db'; - wasm.sqlite3_wasm_vfs_unlink(0, dbFile); - const db = this.db = new sqlite3.oo1.DB(dbFile); - T.assert(Number.isInteger(db.pointer)) - .mustThrowMatching(()=>db.pointer=1, /read-only/) - .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)) - .assert('main'===db.dbName(0)) - .assert('string' === typeof db.dbVfsName()); - // Custom db error message handling via sqlite3_prepare_v2/v3() - let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null); - T.assert(capi.SQLITE_MISUSE === rc) - .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL")) - .assert(dbFile === db.dbFilename()) - .assert(!db.dbFilename('nope')); - }) - - //////////////////////////////////////////////////////////////////// - .t('DB.Stmt', function(S){ - let st = this.db.prepare( - new TextEncoder('utf-8').encode("select 3 as a") - ); - //debug("statement =",st); - try { - T.assert(Number.isInteger(st.pointer)) - .mustThrowMatching(()=>st.pointer=1, /read-only/) - .assert(1===this.db.openStatementCount()) - .assert(!st._mayGet) - .assert('a' === st.getColumnName(0)) - .assert(1===st.columnCount) - .assert(0===st.parameterCount) - .mustThrow(()=>st.bind(1,null)) - .assert(true===st.step()) - .assert(3 === st.get(0)) - .mustThrow(()=>st.get(1)) - .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER)) - .assert(3 === st.get(0,capi.SQLITE_INTEGER)) - .assert(3 === st.getInt(0)) - .assert('3' === st.get(0,capi.SQLITE_TEXT)) - .assert('3' === st.getString(0)) - .assert(3.0 === st.get(0,capi.SQLITE_FLOAT)) - .assert(3.0 === st.getFloat(0)) - .assert(3 === st.get({}).a) - .assert(3 === st.get([])[0]) - .assert(3 === st.getJSON(0)) - .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array) - .assert(1===st.get(0,capi.SQLITE_BLOB).length) - .assert(st.getBlob(0) instanceof Uint8Array) - .assert('3'.charCodeAt(0) === st.getBlob(0)[0]) - .assert(st._mayGet) - .assert(false===st.step()) - .assert(!st._mayGet) - ; - T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). - assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). - assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). - assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); - }finally{ - st.finalize(); - } - T.assert(!st.pointer) - .assert(0===this.db.openStatementCount()); - }) - - //////////////////////////////////////////////////////////////////////// - .t('sqlite3_js_...()', function(){ - const db = this.db; - if(1){ - const vfsList = capi.sqlite3_js_vfs_list(); - T.assert(vfsList.length>1); - T.assert('string'===typeof vfsList[0]); - //log("vfsList =",vfsList); - for(const v of vfsList){ - T.assert('string' === typeof v) - .assert(capi.sqlite3_vfs_find(v) > 0); - } - } - /** - Trivia: the magic db name ":memory:" does not actually use the - "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS - name. Instead, it uses the default VFS with an in-memory btree. - Thus this.db's VFS may not be memdb even though it's an in-memory - db. - */ - const pVfsMem = capi.sqlite3_vfs_find('memdb'), - pVfsDflt = capi.sqlite3_vfs_find(0), - pVfsDb = capi.sqlite3_js_db_vfs(db.pointer); - T.assert(pVfsMem > 0) - .assert(pVfsDflt > 0) - .assert(pVfsDb > 0) - .assert(pVfsMem !== pVfsDflt - /* memdb lives on top of the default vfs */) - .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem) - ; - /*const vMem = new capi.sqlite3_vfs(pVfsMem), - vDflt = new capi.sqlite3_vfs(pVfsDflt), - vDb = new capi.sqlite3_vfs(pVfsDb);*/ - const duv = capi.sqlite3_js_db_uses_vfs; - T.assert(pVfsDflt === duv(db.pointer, 0) - || pVfsMem === duv(db.pointer,0)) - .assert(!duv(db.pointer, "foo")) - ; - }/*sqlite3_js_...()*/) - - //////////////////////////////////////////////////////////////////// - .t('Table t', function(sqlite3){ - const db = this.db; - let list = []; - let rc = db.exec({ - sql:['CREATE TABLE t(a,b);', - // ^^^ using TEMP TABLE breaks the db export test - "INSERT INTO t(a,b) VALUES(1,2),(3,4),", - "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for - off-by-one bug in string-to-WASM conversion*/], - saveSql: list, - bind: [5,6] - }); - //debug("Exec'd SQL:", list); - T.assert(rc === db) - .assert(2 === list.length) - .assert('string'===typeof list[1]) - .assert(4===db.changes()); - if(wasm.bigIntEnabled){ - T.assert(4n===db.changes(false,true)); - } - let blob = db.selectValue("select b from t where a='blob'"); - T.assert(blob instanceof Uint8Array). - assert(0x68===blob[0] && 0x69===blob[1]); - blob = null; - let counter = 0, colNames = []; - list.length = 0; - db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{ - rowMode: 'object', - resultRows: list, - columnNames: colNames, - callback: function(row,stmt){ - ++counter; - T.assert((row.a%2 && row.a<6) || 'blob'===row.a); - } - }); - T.assert(2 === colNames.length) - .assert('a' === colNames[0]) - .assert(4 === counter) - .assert(4 === list.length); - list.length = 0; - db.exec("SELECT a a, b b FROM t",{ - rowMode: 'array', - callback: function(row,stmt){ - ++counter; - T.assert(Array.isArray(row)) - .assert((0===row[1]%2 && row[1]<7) - || (row[1] instanceof Uint8Array)); - } - }); - T.assert(8 === counter); - T.assert(Number.MIN_SAFE_INTEGER === - db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)). - assert(Number.MAX_SAFE_INTEGER === - db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER)); - if(wasm.bigIntEnabled && haveWasmCTests()){ - const mI = wasm.xCall('sqlite3_wasm_test_int64_max'); - const b = BigInt(Number.MAX_SAFE_INTEGER * 2); - T.assert(b === db.selectValue("SELECT "+b)). - assert(b === db.selectValue("SELECT ?", b)). - assert(mI == db.selectValue("SELECT $x", {$x:mI})); - }else{ - /* Curiously, the JS spec seems to be off by one with the definitions - of MIN/MAX_SAFE_INTEGER: - - https://github.com/emscripten-core/emscripten/issues/17391 */ - T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))). - mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1))); - } - - let st = db.prepare("update t set b=:b where a='blob'"); - try { - const ndx = st.getParamIndex(':b'); - T.assert(1===ndx); - st.bindAsBlob(ndx, "ima blob").reset(true); - } finally { - st.finalize(); - } - - try { - db.prepare("/*empty SQL*/"); - toss("Must not be reached."); - }catch(e){ - T.assert(e instanceof sqlite3.SQLite3Error) - .assert(0==e.message.indexOf('Cannot prepare empty')); - } - }) - - //////////////////////////////////////////////////////////////////////// - .t('selectArray/Object()', function(sqlite3){ - const db = this.db; - let rc = db.selectArray('select a, b from t where a=?', 5); - T.assert(Array.isArray(rc)) - .assert(2===rc.length) - .assert(5===rc[0] && 6===rc[1]); - rc = db.selectArray('select a, b from t where b=-1'); - T.assert(undefined === rc); - rc = db.selectObject('select a A, b b from t where b=?', 6); - T.assert(rc && 'object'===typeof rc) - .assert(5===rc.A) - .assert(6===rc.b); - rc = db.selectArray('select a, b from t where b=-1'); - T.assert(undefined === rc); - }) - - //////////////////////////////////////////////////////////////////////// - .t('sqlite3_js_db_export()', function(){ - const db = this.db; - const xp = capi.sqlite3_js_db_export(db.pointer); - T.assert(xp instanceof Uint8Array) - .assert(xp.byteLength>0) - .assert(0 === xp.byteLength % 512); - }/*sqlite3_js_db_export()*/) - - //////////////////////////////////////////////////////////////////// - .t('Scalar UDFs', function(sqlite3){ - const db = this.db; - db.createFunction("foo",(pCx,a,b)=>a+b); - T.assert(7===db.selectValue("select foo(3,4)")). - assert(5===db.selectValue("select foo(3,?)",2)). - assert(5===db.selectValue("select foo(?,?2)",[1,4])). - assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5})); - db.createFunction("bar", { - arity: -1, - xFunc: (pCx,...args)=>{ - let rc = 0; - for(const v of args) rc += v; - return rc; - } - }).createFunction({ - name: "asis", - xFunc: (pCx,arg)=>arg - }); - T.assert(0===db.selectValue("select bar()")). - assert(1===db.selectValue("select bar(1)")). - assert(3===db.selectValue("select bar(1,2)")). - assert(-1===db.selectValue("select bar(1,2,-4)")). - assert('hi' === db.selectValue("select asis('hi')")). - assert('hi' === db.selectValue("select ?",'hi')). - assert(null === db.selectValue("select null")). - assert(null === db.selectValue("select asis(null)")). - assert(1 === db.selectValue("select ?",1)). - assert(2 === db.selectValue("select ?",[2])). - assert(3 === db.selectValue("select $a",{$a:3})). - assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))). - assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)"))); - - let blobArg = new Uint8Array(2); - blobArg.set([0x68, 0x69], 0); - let blobRc = db.selectValue("select asis(?1)", blobArg); - T.assert(blobRc instanceof Uint8Array). - assert(2 === blobRc.length). - assert(0x68==blobRc[0] && 0x69==blobRc[1]); - blobRc = db.selectValue("select asis(X'6869')"); - T.assert(blobRc instanceof Uint8Array). - assert(2 === blobRc.length). - assert(0x68==blobRc[0] && 0x69==blobRc[1]); - - blobArg = new Int8Array(2); - blobArg.set([0x68, 0x69]); - //debug("blobArg=",blobArg); - blobRc = db.selectValue("select asis(?1)", blobArg); - T.assert(blobRc instanceof Uint8Array). - assert(2 === blobRc.length); - //debug("blobRc=",blobRc); - T.assert(0x68==blobRc[0] && 0x69==blobRc[1]); - }) - - //////////////////////////////////////////////////////////////////// - .t({ - name: 'Aggregate UDFs', - test: function(sqlite3){ - const db = this.db; - const sjac = capi.sqlite3_js_aggregate_context; - db.createFunction({ - name: 'summer', - xStep: (pCtx, n)=>{ - const ac = sjac(pCtx, 4); - wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32'); - }, - xFinal: (pCtx)=>{ - const ac = sjac(pCtx, 0); - return ac ? wasm.getMemValue(ac,'i32') : 0; - } - }); - let v = db.selectValue([ - "with cte(v) as (", - "select 3 union all select 5 union all select 7", - ") select summer(v), summer(v+1) from cte" - /* ------------------^^^^^^^^^^^ ensures that we're handling - sqlite3_aggregate_context() properly. */ - ]); - T.assert(15===v); - T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"), - /wrong number of arguments/); - - db.createFunction({ - name: 'summerN', - arity: -1, - xStep: (pCtx, ...args)=>{ - const ac = sjac(pCtx, 4); - let sum = wasm.getMemValue(ac, 'i32'); - for(const v of args) sum += Number(v); - wasm.setMemValue(ac, sum, 'i32'); - }, - xFinal: (pCtx)=>{ - const ac = sjac(pCtx, 0); - capi.sqlite3_result_int( pCtx, ac ? wasm.getMemValue(ac,'i32') : 0 ); - // xFinal() may either return its value directly or call - // sqlite3_result_xyz() and return undefined. Both are - // functionally equivalent. - } - }); - T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)')); - T.mustThrowMatching(()=>{ - db.createFunction('nope',{ - xFunc: ()=>{}, xStep: ()=>{} - }); - }, /scalar or aggregate\?/); - T.mustThrowMatching(()=>{ - db.createFunction('nope',{xStep: ()=>{}}); - }, /Missing xFinal/); - T.mustThrowMatching(()=>{ - db.createFunction('nope',{xFinal: ()=>{}}); - }, /Missing xStep/); - T.mustThrowMatching(()=>{ - db.createFunction('nope',{}); - }, /Missing function-type properties/); - T.mustThrowMatching(()=>{ - db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'}); - }, /xDestroy property must be a function/); - T.mustThrowMatching(()=>{ - db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'}); - }, /Invalid value for pApp/); - } - }/*aggregate UDFs*/) - - //////////////////////////////////////////////////////////////////////// - .t({ - name: 'Aggregate UDFs (64-bit)', - predicate: ()=>wasm.bigIntEnabled, - test: function(sqlite3){ - const db = this.db; - const sjac = capi.sqlite3_js_aggregate_context; - db.createFunction({ - name: 'summer64', - xStep: (pCtx, n)=>{ - const ac = sjac(pCtx, 8); - wasm.setMemValue(ac, wasm.getMemValue(ac,'i64') + BigInt(n), 'i64'); - }, - xFinal: (pCtx)=>{ - const ac = sjac(pCtx, 0); - return ac ? wasm.getMemValue(ac,'i64') : 0n; - } - }); - let v = db.selectValue([ - "with cte(v) as (", - "select 9007199254740991 union all select 1 union all select 2", - ") select summer64(v), summer64(v+1) from cte" - ]); - T.assert(9007199254740994n===v); - } - }/*aggregate UDFs*/) - - //////////////////////////////////////////////////////////////////// - .t({ - name: 'Window UDFs', - test: function(){ - /* Example window function, table, and results taken from: - https://sqlite.org/windowfunctions.html#udfwinfunc */ - const db = this.db; - const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n); - const xValueFinal = (pCtx)=>{ - const ac = sjac(pCtx, 0); - return ac ? wasm.getMemValue(ac,'i32') : 0; - }; - const xStepInverse = (pCtx, n)=>{ - const ac = sjac(pCtx); - wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32'); - }; - db.createFunction({ - name: 'winsumint', - xStep: (pCtx, n)=>xStepInverse(pCtx, n), - xInverse: (pCtx, n)=>xStepInverse(pCtx, -n), - xFinal: xValueFinal, - xValue: xValueFinal - }); - db.exec([ - "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", - "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" - ]); - let rc = db.exec({ - returnValue: 'resultRows', - sql:[ - "SELECT x, winsumint(y) OVER (", - "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", - ") AS sum_y ", - "FROM twin ORDER BY x;" - ] - }); - T.assert(Array.isArray(rc)) - .assert(5 === rc.length); - let count = 0; - for(const row of rc){ - switch(++count){ - case 1: T.assert('a'===row[0] && 9===row[1]); break; - case 2: T.assert('b'===row[0] && 12===row[1]); break; - case 3: T.assert('c'===row[0] && 16===row[1]); break; - case 4: T.assert('d'===row[0] && 12===row[1]); break; - case 5: T.assert('e'===row[0] && 9===row[1]); break; - default: toss("Too many rows to window function."); - } - } - const resultRows = []; - rc = db.exec({ - resultRows, - returnValue: 'resultRows', - sql:[ - "SELECT x, winsumint(y) OVER (", - "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", - ") AS sum_y ", - "FROM twin ORDER BY x;" - ] - }); - T.assert(rc === resultRows) - .assert(5 === rc.length); - - rc = db.exec({ - returnValue: 'saveSql', - sql: "select 1; select 2; -- empty\n; select 3" - }); - T.assert(Array.isArray(rc)) - .assert(3===rc.length) - .assert('select 1;' === rc[0]) - .assert('select 2;' === rc[1]) - .assert('-- empty\n; select 3' === rc[2] - /* Strange but true. */); - - T.mustThrowMatching(()=>{ - db.exec({sql:'', returnValue: 'nope'}); - }, /^Invalid returnValue/); - - db.exec("DROP TABLE twin"); - } - }/*window UDFs*/) - - //////////////////////////////////////////////////////////////////// - .t("ATTACH", function(){ - const db = this.db; - const resultRows = []; - db.exec({ - sql:new TextEncoder('utf-8').encode([ - // ^^^ testing string-vs-typedarray handling in exec() - "attach 'session' as foo;", - "create table foo.bar(a);", - "insert into foo.bar(a) values(1),(2),(3);", - "select a from foo.bar order by a;" - ].join('')), - rowMode: 0, - resultRows - }); - T.assert(3===resultRows.length) - .assert(2===resultRows[1]); - T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a')); - let colCount = 0, rowCount = 0; - const execCallback = function(pVoid, nCols, aVals, aNames){ - colCount = nCols; - ++rowCount; - T.assert(2===aVals.length) - .assert(2===aNames.length) - .assert(+(aVals[1]) === 2 * +(aVals[0])); - }; - let rc = capi.sqlite3_exec( - db.pointer, "select a, a*2 from foo.bar", execCallback, - 0, 0 - ); - T.assert(0===rc).assert(3===rowCount).assert(2===colCount); - rc = capi.sqlite3_exec( - db.pointer, "select a from foo.bar", ()=>{ - tossQuietly("Testing throwing from exec() callback."); - }, 0, 0 - ); - T.assert(capi.SQLITE_ABORT === rc); - db.exec("detach foo"); - T.mustThrow(()=>db.exec("select * from foo.bar")); - }) - - //////////////////////////////////////////////////////////////////// - .t({ - name: 'C-side WASM tests (if compiled in)', - predicate: haveWasmCTests, - test: function(){ - const w = wasm, db = this.db; - const stack = w.scopedAllocPush(); - let ptrInt; - const origValue = 512; - const ptrValType = 'i32'; - try{ - ptrInt = w.scopedAlloc(4); - w.setMemValue(ptrInt,origValue, ptrValType); - const cf = w.xGet('sqlite3_wasm_test_intptr'); - const oldPtrInt = ptrInt; - //log('ptrInt',ptrInt); - //log('getMemValue(ptrInt)',w.getMemValue(ptrInt)); - T.assert(origValue === w.getMemValue(ptrInt, ptrValType)); - const rc = cf(ptrInt); - //log('cf(ptrInt)',rc); - //log('ptrInt',ptrInt); - //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType)); - T.assert(2*origValue === rc). - assert(rc === w.getMemValue(ptrInt,ptrValType)). - assert(oldPtrInt === ptrInt); - const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/; - const o64 = 0x010203040506/*>32-bit integer*/; - const ptrType64 = 'i64'; - if(w.bigIntEnabled){ - w.setMemValue(pi64, o64, ptrType64); - //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64); - const v64 = ()=>w.getMemValue(pi64,ptrType64) - //log("getMemValue(pi64)",v64()); - T.assert(v64() == o64); - //T.assert(o64 === w.getMemValue(pi64, ptrType64)); - const cf64w = w.xGet('sqlite3_wasm_test_int64ptr'); - cf64w(pi64); - //log("getMemValue(pi64)",v64()); - T.assert(v64() == BigInt(2 * o64)); - cf64w(pi64); - T.assert(v64() == BigInt(4 * o64)); - - const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2'); - T.assert(BigInt(2 * o64) === - biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError - in the call :/ */)); - - const pMin = w.scopedAlloc(16); - const pMax = pMin + 8; - const g64 = (p)=>w.getMemValue(p,ptrType64); - w.setMemValue(pMin, 0, ptrType64); - w.setMemValue(pMax, 0, ptrType64); - const minMaxI64 = [ - w.xCall('sqlite3_wasm_test_int64_min'), - w.xCall('sqlite3_wasm_test_int64_max') - ]; - T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)). - assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER)); - //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]); - w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax); - T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch"). - assert(g64(pMax) === minMaxI64[1], "int64 mismatch"); - //log("pMin",g64(pMin), "pMax",g64(pMax)); - w.setMemValue(pMin, minMaxI64[0], ptrType64); - T.assert(g64(pMin) === minMaxI64[0]). - assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))). - assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax))); - const rxRange = /too big/; - T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))}, - rxRange). - mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))}, - (e)=>rxRange.test(e.message)); - }else{ - log("No BigInt support. Skipping related tests."); - log("\"The problem\" here is that we can manipulate, at the byte level,", - "heap memory to set 64-bit values, but we can't get those values", - "back into JS because of the lack of 64-bit integer support."); - } - }finally{ - const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1); - //log("x=",x,"y=",y,"z=",z); // just looking at the alignment - w.scopedAllocPop(stack); - } - } - }/* jaccwabyt-specific tests */) - - .t('Close db', function(){ - T.assert(this.db).assert(Number.isInteger(this.db.pointer)); - wasm.exports.sqlite3_wasm_db_reset(this.db.pointer); - this.db.close(); - T.assert(!this.db.pointer); - }) - ;/* end of oo1 checks */ - - //////////////////////////////////////////////////////////////////////// - T.g('kvvfs') - .t('kvvfs sanity checks', function(sqlite3){ - if(isWorker()){ - T.assert( - !capi.sqlite3_vfs_find('kvvfs'), - "Expecting kvvfs to be unregistered." - ); - log("kvvfs is (correctly) unavailable in a Worker."); - return; - } - const filename = 'session'; - const pVfs = capi.sqlite3_vfs_find('kvvfs'); - T.assert(pVfs); - const JDb = sqlite3.oo1.JsStorageDb; - const unlink = ()=>JDb.clearStorage(filename); - unlink(); - let db = new JDb(filename); - try { - db.exec([ - 'create table kvvfs(a);', - 'insert into kvvfs(a) values(1),(2),(3)' - ]); - T.assert(3 === db.selectValue('select count(*) from kvvfs')); - db.close(); - db = new JDb(filename); - db.exec('insert into kvvfs(a) values(4),(5),(6)'); - T.assert(6 === db.selectValue('select count(*) from kvvfs')); - }finally{ - db.close(); - unlink(); - } - }/*kvvfs sanity checks*/) - ;/* end kvvfs tests */ - - //////////////////////////////////////////////////////////////////////// - T.g('OPFS (Worker thread only and only in supported browsers)', - (sqlite3)=>{return !!sqlite3.opfs}) - .t({ - name: 'OPFS sanity checks', - test: async function(sqlite3){ - const opfs = sqlite3.opfs; - const filename = 'sqlite3-tester1.db'; - const pVfs = capi.sqlite3_vfs_find('opfs'); - T.assert(pVfs); - const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn); - unlink(); - let db = new opfs.OpfsDb(filename); - try { - db.exec([ - 'create table p(a);', - 'insert into p(a) values(1),(2),(3)' - ]); - T.assert(3 === db.selectValue('select count(*) from p')); - db.close(); - db = new opfs.OpfsDb(filename); - db.exec('insert into p(a) values(4),(5),(6)'); - T.assert(6 === db.selectValue('select count(*) from p')); - }finally{ - db.close(); - unlink(); - } - - if(1){ - // Sanity-test sqlite3_wasm_vfs_create_file()... - const fSize = 1379; - let sh; - try{ - T.assert(!(await opfs.entryExists(filename))); - let rc = wasm.sqlite3_wasm_vfs_create_file( - pVfs, filename, null, fSize - ); - T.assert(0===rc) - .assert(await opfs.entryExists(filename)); - const fh = await opfs.rootDirectory.getFileHandle(filename); - sh = await fh.createSyncAccessHandle(); - T.assert(fSize === await sh.getSize()); - }finally{ - if(sh) await sh.close(); - unlink(); - } - } - - // Some sanity checks of the opfs utility functions... - const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); - const aDir = testDir+'/test/dir'; - T.assert(await opfs.mkdir(aDir), "mkdir failed") - .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists") - .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)") - .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed") - .assert(!(await opfs.unlink(testDir+'/test/dir')), - "delete 2b should have failed (dir already deleted)") - .assert((await opfs.unlink(testDir, true)), "delete 3 failed") - .assert(!(await opfs.entryExists(testDir)), - "entryExists(",testDir,") should have failed"); - } - }/*OPFS sanity checks*/) - ;/* end OPFS tests */ - - //////////////////////////////////////////////////////////////////////// - log("Loading and initializing sqlite3 WASM module..."); - if(!isUIThread()){ - /* - If sqlite3.js is in a directory other than this script, in order - to get sqlite3.js to resolve sqlite3.wasm properly, we have to - explicitly tell it where sqlite3.js is being loaded from. We do - that by passing the `sqlite3.dir=theDirName` URL argument to - _this_ script. That URL argument will be seen by the JS/WASM - loader and it will adjust the sqlite3.wasm path accordingly. If - sqlite3.js/.wasm are in the same directory as this script then - that's not needed. - - URL arguments passed as part of the filename via importScripts() - are simply lost, and such scripts see the self.location of - _this_ script. - */ - let sqlite3Js = 'sqlite3.js'; - const urlParams = new URL(self.location.href).searchParams; - if(urlParams.has('sqlite3.dir')){ - sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; - } - importScripts(sqlite3Js); - } - self.sqlite3InitModule({ - print: log, - printErr: error - }).then(function(sqlite3){ - //console.log('sqlite3 =',sqlite3); - log("Done initializing WASM/JS bits. Running tests..."); - capi = sqlite3.capi; - wasm = sqlite3.wasm; - log("sqlite3 version:",capi.sqlite3_libversion(), - capi.sqlite3_sourceid()); - if(wasm.bigIntEnabled){ - log("BigInt/int64 support is enabled."); - }else{ - logClass('warning',"BigInt/int64 support is disabled."); - } - if(haveWasmCTests()){ - log("sqlite3_wasm_test_...() APIs are available."); - }else{ - logClass('warning',"sqlite3_wasm_test_...() APIs unavailable."); - } - TestUtil.runTests(sqlite3); - }); -})(); diff --git a/ext/wasm/tests/opfs/concurrency/index.html b/ext/wasm/tests/opfs/concurrency/index.html new file mode 100644 index 000000000..595ab2452 --- /dev/null +++ b/ext/wasm/tests/opfs/concurrency/index.html @@ -0,0 +1,49 @@ + + + + + + + + sqlite3 OPFS Worker concurrency tester + + + +

    +

    + OPFS concurrency tester using multiple independent Workers. + Disclaimer: concurrency in OPFS is currently a pain point! +

    +

    + URL flags: pass a number of workers using + the workers=N URL flag. Set the time between each + workload with interval=N (milliseconds). Set the + number of worker iterations with iterations=N. + Enable OPFS VFS verbosity with verbose=1-3 (output + goes to the dev console). Disable/enable "unlock ASAP" mode + (higher concurrency, lower speed) with unlock-asap=0-1. +

    +

    Achtung: if it does not start to do anything within a couple of + seconds, check the dev console: Chrome sometimes fails to load + the wasm module due to "cannot allocate WasmMemory." Closing and + re-opening the tab usually resolves it, but sometimes restarting + the browser is required. +

    +
    + + +
    +
    + + + + diff --git a/ext/wasm/tests/opfs/concurrency/test.js b/ext/wasm/tests/opfs/concurrency/test.js new file mode 100644 index 000000000..14cd6f514 --- /dev/null +++ b/ext/wasm/tests/opfs/concurrency/test.js @@ -0,0 +1,136 @@ +(async function(self){ + + const logCss = (function(){ + const mapToString = (v)=>{ + switch(typeof v){ + case 'number': case 'string': case 'boolean': + case 'undefined': case 'bigint': + return ''+v; + default: break; + } + if(null===v) return 'null'; + if(v instanceof Error){ + v = { + message: v.message, + stack: v.stack, + errorClass: v.name + }; + } + return JSON.stringify(v,undefined,2); + }; + const normalizeArgs = (args)=>args.map(mapToString); + const logTarget = document.querySelector('#test-output'); + const logCss = function(cssClass,...args){ + const ln = document.createElement('div'); + if(cssClass){ + for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){ + ln.classList.add(c); + } + } + ln.append(document.createTextNode(normalizeArgs(args).join(' '))); + logTarget.append(ln); + }; + const cbReverse = document.querySelector('#cb-log-reverse'); + const cbReverseKey = 'tester1:cb-log-reverse'; + const cbReverseIt = ()=>{ + logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); + localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0); + }; + cbReverse.addEventListener('change', cbReverseIt, true); + if(localStorage.getItem(cbReverseKey)){ + cbReverse.checked = !!(+localStorage.getItem(cbReverseKey)); + } + cbReverseIt(); + return logCss; + })(); + const stdout = (...args)=>logCss('',...args); + const stderr = (...args)=>logCss('error',...args); + + const wait = async (ms)=>{ + return new Promise((resolve)=>setTimeout(resolve,ms)); + }; + + const urlArgsJs = new URL(document.currentScript.src).searchParams; + const urlArgsHtml = new URL(self.location.href).searchParams; + const options = Object.create(null); + options.sqlite3Dir = urlArgsJs.get('sqlite3.dir'); + options.workerCount = ( + urlArgsHtml.has('workers') ? +urlArgsHtml.get('workers') : 3 + ) || 4; + options.opfsVerbose = ( + urlArgsHtml.has('verbose') ? +urlArgsHtml.get('verbose') : 1 + ) || 1; + options.interval = ( + urlArgsHtml.has('interval') ? +urlArgsHtml.get('interval') : 1000 + ) || 1000; + options.iterations = ( + urlArgsHtml.has('iterations') ? +urlArgsHtml.get('iterations') : 10 + ) || 10; + options.unlockAsap = ( + urlArgsHtml.has('unlock-asap') ? +urlArgsHtml.get('unlock-asap') : 0 + ) || 0; + options.noUnlink = !!urlArgsHtml.has('no-unlink'); + const workers = []; + workers.post = (type,...args)=>{ + for(const w of workers) w.postMessage({type, payload:args}); + }; + workers.counts = {loaded: 0, passed: 0, failed: 0}; + const checkFinished = function(){ + if(workers.counts.passed + workers.counts.failed !== workers.length){ + return; + } + if(workers.counts.failed>0){ + logCss('tests-fail',"Finished with",workers.counts.failed,"failure(s)."); + }else{ + logCss('tests-pass',"All",workers.length,"workers finished."); + } + }; + workers.onmessage = function(msg){ + msg = msg.data; + const prefix = 'Worker #'+msg.worker+':'; + switch(msg.type){ + case 'loaded': + stdout(prefix,"loaded"); + if(++workers.counts.loaded === workers.length){ + stdout("All",workers.length,"workers loaded. Telling them to run..."); + workers.post('run'); + } + break; + case 'stdout': stdout(prefix,...msg.payload); break; + case 'stderr': stderr(prefix,...msg.payload); break; + case 'error': stderr(prefix,"ERROR:",...msg.payload); break; + case 'finished': + ++workers.counts.passed; + logCss('tests-pass',prefix,...msg.payload); + checkFinished(); + break; + case 'failed': + ++workers.counts.failed; + logCss('tests-fail',prefix,"FAILED:",...msg.payload); + checkFinished(); + break; + default: logCss('error',"Unhandled message type:",msg); break; + } + }; + + stdout("Launching",options.workerCount,"workers. Options:",options); + workers.uri = ( + 'worker.js?' + + 'sqlite3.dir='+options.sqlite3Dir + + '&interval='+options.interval + + '&iterations='+options.iterations + + '&opfs-verbose='+options.opfsVerbose + + '&opfs-unlock-asap='+options.unlockAsap + ); + for(let i = 0; i < options.workerCount; ++i){ + stdout("Launching worker..."); + workers.push(new Worker( + workers.uri+'&workerId='+(i+1)+( + (i || options.noUnlink) ? '' : '&unlink-db' + ) + )); + } + // Have to delay onmessage assignment until after the loop + // to avoid that early workers get an undue head start. + workers.forEach((w)=>w.onmessage = workers.onmessage); +})(self); diff --git a/ext/wasm/tests/opfs/concurrency/worker.js b/ext/wasm/tests/opfs/concurrency/worker.js new file mode 100644 index 000000000..5d28bedee --- /dev/null +++ b/ext/wasm/tests/opfs/concurrency/worker.js @@ -0,0 +1,113 @@ +importScripts( + (new URL(self.location.href).searchParams).get('sqlite3.dir') + '/sqlite3.js' +); +self.sqlite3InitModule().then(async function(sqlite3){ + const urlArgs = new URL(self.location.href).searchParams; + const options = { + workerName: urlArgs.get('workerId') || Math.round(Math.random()*10000), + unlockAsap: urlArgs.get('opfs-unlock-asap') || 0 /*EXPERIMENTAL*/ + }; + const wPost = (type,...payload)=>{ + postMessage({type, worker: options.workerName, payload}); + }; + const stdout = (...args)=>wPost('stdout',...args); + const stderr = (...args)=>wPost('stderr',...args); + if(!sqlite3.opfs){ + stderr("OPFS support not detected. Aborting."); + return; + } + + const wait = async (ms)=>{ + return new Promise((resolve)=>setTimeout(resolve,ms)); + }; + + const dbName = 'concurrency-tester.db'; + if(urlArgs.has('unlink-db')){ + await sqlite3.opfs.unlink(dbName); + stdout("Unlinked",dbName); + } + wPost('loaded'); + let db; + const interval = Object.assign(Object.create(null),{ + delay: urlArgs.has('interval') ? (+urlArgs.get('interval') || 750) : 750, + handle: undefined, + count: 0 + }); + const finish = ()=>{ + if(db){ + if(!db.pointer) return; + db.close(); + } + if(interval.error){ + wPost('failed',"Ending work after interval #"+interval.count, + "due to error:",interval.error); + }else{ + wPost('finished',"Ending work after",interval.count,"intervals."); + } + }; + const run = async function(){ + db = new sqlite3.oo1.OpfsDb({ + filename: 'file:'+dbName+'?opfs-unlock-asap='+options.unlockAsap, + flags: 'c' + }); + sqlite3.capi.sqlite3_busy_timeout(db.pointer, 5000); + db.transaction((db)=>{ + db.exec([ + "create table if not exists t1(w TEXT UNIQUE ON CONFLICT REPLACE,v);", + "create table if not exists t2(w TEXT UNIQUE ON CONFLICT REPLACE,v);" + ]); + }); + + const maxIterations = + urlArgs.has('iterations') ? (+urlArgs.get('iterations') || 10) : 10; + stdout("Starting interval-based db updates with delay of",interval.delay,"ms."); + const doWork = async ()=>{ + const tm = new Date().getTime(); + ++interval.count; + const prefix = "v(#"+interval.count+")"; + stdout("Setting",prefix,"=",tm); + try{ + db.exec({ + sql:"INSERT OR REPLACE INTO t1(w,v) VALUES(?,?)", + bind: [options.workerName, new Date().getTime()] + }); + //stdout("Set",prefix); + }catch(e){ + interval.error = e; + } + }; + if(1){/*use setInterval()*/ + setTimeout(async function timer(){ + await doWork(); + if(interval.error || maxIterations === interval.count){ + finish(); + }else{ + setTimeout(timer, interval.delay); + } + }, interval.delay); + }else{ + /*This approach provides no concurrency whatsoever: each worker + is run to completion before any others can work.*/ + let i; + for(i = 0; i < maxIterations; ++i){ + await doWork(); + if(interval.error) break; + await wait(interval.ms); + } + finish(); + } + }/*run()*/; + + self.onmessage = function({data}){ + switch(data.type){ + case 'run': run().catch((e)=>{ + if(!interval.error) interval.error = e; + finish(); + }); + break; + default: + stderr("Unhandled message type '"+data.type+"'."); + break; + } + }; +}); diff --git a/ext/wasm/wasmfs.make b/ext/wasm/wasmfs.make index 81b41870c..020014f3e 100644 --- a/ext/wasm/wasmfs.make +++ b/ext/wasm/wasmfs.make @@ -15,98 +15,117 @@ MAKEFILE.wasmfs := $(lastword $(MAKEFILE_LIST)) # Worker to an Emscripten quirk regarding loading nested Workers. dir.wasmfs := $(dir.wasm) sqlite3-wasmfs.js := $(dir.wasmfs)/sqlite3-wasmfs.js +sqlite3-wasmfs.mjs := $(dir.wasmfs)/sqlite3-wasmfs.mjs sqlite3-wasmfs.wasm := $(dir.wasmfs)/sqlite3-wasmfs.wasm CLEAN_FILES += $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.wasm) \ - $(subst .js,.worker.js,$(sqlite3-wasmfs.js)) + $(subst .js,.worker.js,$(sqlite3-wasmfs.js)) \ + $(sqlite3-wasmfs.mjs) \ + $(subst .mjs,.worker.mjs,$(sqlite3-wasmfs.mjs)) ######################################################################## # emcc flags for .c/.o. -sqlite3-wasmfs.cflags := -sqlite3-wasmfs.cflags += -std=c99 -fPIC -sqlite3-wasmfs.cflags += -pthread -sqlite3-wasmfs.cflags += $(cflags.common) -sqlite3-wasmfs.cflags += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS +cflags.sqlite3-wasmfs := +cflags.sqlite3-wasmfs += -std=c99 -fPIC +cflags.sqlite3-wasmfs += -pthread +cflags.sqlite3-wasmfs += $(cflags.speedtest1) +cflags.sqlite3-wasmfs += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS ######################################################################## # emcc flags specific to building the final .js/.wasm file... -sqlite3-wasmfs.jsflags := -fPIC -sqlite3-wasmfs.jsflags += --no-entry -sqlite3-wasmfs.jsflags += --minify 0 -sqlite3-wasmfs.jsflags += -sMODULARIZE -sqlite3-wasmfs.jsflags += -sSTRICT_JS -sqlite3-wasmfs.jsflags += -sDYNAMIC_EXECUTION=0 -sqlite3-wasmfs.jsflags += -sNO_POLYFILL -sqlite3-wasmfs.jsflags += -sEXPORTED_FUNCTIONS=@$(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) -sqlite3-wasmfs.jsflags += -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory,allocateUTF8OnStack - # wasmMemory ==> for -sIMPORTED_MEMORY - # allocateUTF8OnStack ==> wasmfs internals -sqlite3-wasmfs.jsflags += -sUSE_CLOSURE_COMPILER=0 -sqlite3-wasmfs.jsflags += -sIMPORTED_MEMORY -#sqlite3-wasmfs.jsflags += -sINITIAL_MEMORY=13107200 -#sqlite3-wasmfs.jsflags += -sTOTAL_STACK=4194304 -sqlite3-wasmfs.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) -sqlite3-wasmfs.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. -#sqlite3-wasmfs.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API -# Perhaps the wasmfs build doesn't? -#sqlite3-wasmfs.jsflags += -sABORTING_MALLOC -sqlite3-wasmfs.jsflags += -sALLOW_TABLE_GROWTH -sqlite3-wasmfs.jsflags += -Wno-limited-postlink-optimizations +emcc.flags.sqlite3-wasmfs := -fPIC +emcc.flags.sqlite3-wasmfs += --no-entry +emcc.flags.sqlite3-wasmfs += --minify 0 +emcc.flags.sqlite3-wasmfs += -sMODULARIZE +emcc.flags.sqlite3-wasmfs += -sEXPORT_NAME=$(sqlite3.js.init-func) +emcc.flags.sqlite3-wasmfs += -sSTRICT_JS +emcc.flags.sqlite3-wasmfs += -sDYNAMIC_EXECUTION=0 +emcc.flags.sqlite3-wasmfs += -sNO_POLYFILL +emcc.flags.sqlite3-wasmfs += -sWASM_BIGINT=$(emcc.WASM_BIGINT) +emcc.flags.sqlite3-wasmfs += -sEXPORTED_FUNCTIONS=@$(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) +emcc.flags.sqlite3-wasmfs += -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory,allocateUTF8OnStack + # wasmMemory ==> for -sIMPORTED_MEMORY + # allocateUTF8OnStack ==> wasmfs internals +emcc.flags.sqlite3-wasmfs += -sUSE_CLOSURE_COMPILER=0 +emcc.flags.sqlite3-wasmfs += -Wno-limited-postlink-optimizations # ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag. -sqlite3-wasmfs.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=0 -sqlite3-wasmfs.jsflags += -sLLD_REPORT_UNDEFINED -#sqlite3-wasmfs.jsflags += --import-undefined -sqlite3-wasmfs.jsflags += -sMEMORY64=0 -sqlite3-wasmfs.jsflags += -sINITIAL_MEMORY=128450560 +emcc.flags.sqlite3-wasmfs += -sALLOW_TABLE_GROWTH +emcc.flags.sqlite3-wasmfs += -sSTACK_SIZE=512KB +emcc.flags.sqlite3-wasmfs += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. +emcc.flags.sqlite3-wasmfs += -sMEMORY64=0 +emcc.flags.sqlite3-wasmfs += -sIMPORTED_MEMORY +emcc.flags.sqlite3-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) # ^^^^ 64MB is not enough for WASMFS/OPFS test runs using batch-runner.js -sqlite3-wasmfs.fsflags := -pthread -sWASMFS -sPTHREAD_POOL_SIZE=2 -sENVIRONMENT=web,worker -# -sPTHREAD_POOL_SIZE values of 2 or higher trigger that bug. -sqlite3-wasmfs.jsflags += $(sqlite3-wasmfs.fsflags) -#sqlite3-wasmfs.jsflags += -sALLOW_MEMORY_GROWTH +sqlite3-wasmfs.fsflags := -pthread -sWASMFS \ + -sPTHREAD_POOL_SIZE=2 -sENVIRONMENT=web,worker \ + -sERROR_ON_UNDEFINED_SYMBOLS=0 -sLLD_REPORT_UNDEFINED +# ^^^^^ why undefined symbols are necessary for the wasmfs build is anyone's guess. +emcc.flags.sqlite3-wasmfs += $(sqlite3-wasmfs.fsflags) +#emcc.flags.sqlite3-wasmfs += -sALLOW_MEMORY_GROWTH #^^^ using ALLOW_MEMORY_GROWTH produces a warning from emcc: # USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, # see https://github.com/WebAssembly/design/issues/1271 [-Wpthreads-mem-growth] -sqlite3-wasmfs.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) -$(eval $(call call-make-pre-js,sqlite3-wasmfs)) -sqlite3-wasmfs.jsflags += $(pre-post-common.flags) $(pre-post-sqlite3-wasmfs.flags) -$(sqlite3-wasmfs.js): $(sqlite3-wasm.c) \ - $(EXPORTED_FUNCTIONS.api) $(MAKEFILE) $(MAKEFILE.wasmfs) \ - $(pre-post-sqlite3-wasmfs.deps) +# And, indeed, it runs slowly if memory is permitted to grow. +emcc.flags.sqlite3-wasmfs.vanilla := +emcc.flags.sqlite3-wasmfs.esm := -sEXPORT_ES6 -sUSE_ES6_IMPORT_META +$(eval $(call call-make-pre-js,sqlite3-wasmfs,vanilla)) +$(eval $(call call-make-pre-js,sqlite3-wasmfs,esm)) +Xemcc.flags.sqlite3-wasmfs.vanilla += \ + $(pre-post-common.flags.vanilla) \ + $(pre-post-sqlite3-wasmfs.flags.vanilla) +Xemcc.flags.sqlite3-wasmfs.esm += \ + $(pre-post-common.flags.esm) \ + $(pre-post-sqlite3-wasmfs.flags.esm) +$(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(sqlite3-wasm.c) \ + $(EXPORTED_FUNCTIONS.api) $(MAKEFILE) $(MAKEFILE.wasmfs) +$(sqlite3-wasmfs.js): $(pre-post-sqlite3-wasmfs.deps.vanilla) +$(sqlite3-wasmfs.mjs): $(pre-post-sqlite3-wasmfs.deps.esm) +# SQLITE3-WASMFS.xJS.RECIPE is the wasmfs-specific counterpart +# of SQLITE3.xJS.RECIPE from the main makefile. +define SQLITE3-WASMFS.xJS.RECIPE @echo "Building $@ ..." $(emcc.bin) -o $@ $(emcc_opt_full) $(emcc.flags) \ - $(sqlite3-wasmfs.cflags) $(sqlite3-wasmfs.jsflags) \ + $(cflags.sqlite3-wasmfs) \ + $(emcc.flags.sqlite3-wasmfs) $(emcc.flags.sqlite3-wasmfs.$(1)) \ + $(pre-post-sqlite3-wasmfs.flags.$(1)) \ $(sqlite3-wasm.c) + @$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(1)) chmod -x $(sqlite3-wasmfs.wasm) $(maybe-wasm-strip) $(sqlite3-wasmfs.wasm) - @ls -la $@ $(sqlite3-wasmfs.wasm) + @ls -la $(sqlite3-wasmfs.wasm) sqlite3-wasmfs*js +endef +$(sqlite3-wasmfs.js): + $(call SQLITE3-WASMFS.xJS.RECIPE,vanilla) +$(sqlite3-wasmfs.mjs): $(sqlite3-wasmfs.js) + $(call SQLITE3-WASMFS.xJS.RECIPE,esm) $(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.js) -wasmfs: $(sqlite3-wasmfs.js) -all: wasmfs +wasmfs: $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs) +#all: wasmfs ######################################################################## # speedtest1 for wasmfs. speedtest1-wasmfs.js := $(dir.wasmfs)/speedtest1-wasmfs.js speedtest1-wasmfs.wasm := $(subst .js,.wasm,$(speedtest1-wasmfs.js)) -speedtest1-wasmfs.eflags := $(sqlite3-wasmfs.fsflags) -speedtest1-wasmfs.eflags += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS -speedtest1-wasmfs.eflags += -sALLOW_MEMORY_GROWTH=0 -speedtest1-wasmfs.eflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) -$(eval $(call call-make-pre-js,speedtest1-wasmfs)) +emcc.flags.speedtest1-wasmfs := $(sqlite3-wasmfs.fsflags) +emcc.flags.speedtest1-wasmfs += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS +emcc.flags.speedtest1-wasmfs += -sALLOW_MEMORY_GROWTH=0 +emcc.flags.speedtest1-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) +#$(eval $(call call-make-pre-js,speedtest1-wasmfs,vanilla)) $(speedtest1-wasmfs.js): $(speedtest1.cses) $(sqlite3-wasmfs.js) \ $(MAKEFILE) $(MAKEFILE.wasmfs) \ - $(pre-post-speedtest1-wasmfs.deps) \ + $(pre-post-sqlite3-wasmfs.deps) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." $(emcc.bin) \ - $(speedtest1-wasmfs.eflags) $(speedtest1-common.eflags) \ - $(pre-post-speedtest1-wasmfs.flags) \ - $(speedtest1.cflags) \ - $(sqlite3-wasmfs.cflags) \ + $(emcc.speedtest1.common) $(emcc.flags.speedtest1-wasmfs) \ + $(pre-post-sqlite3-wasmfs.flags.vanilla) \ + $(cflags.sqlite3-wasmfs) \ -o $@ $(speedtest1.cses) -lm $(maybe-wasm-strip) $(speedtest1-wasmfs.wasm) ls -la $@ $(speedtest1-wasmfs.wasm) -speedtest1: $(speedtest1-wasmfs.js) +#speedtest1: $(speedtest1-wasmfs.js) +wasmfs: $(speedtest1-wasmfs.js) CLEAN_FILES += $(speedtest1-wasmfs.js) $(speedtest1-wasmfs.wasm) \ $(subst .js,.worker.js,$(speedtest1-wasmfs.js)) # end speedtest1.js diff --git a/main.mk b/main.mk index 76c62fcec..628f3d162 100644 --- a/main.mk +++ b/main.mk @@ -192,24 +192,6 @@ SRC = \ # Source code for extensions # -SRC += \ - $(TOP)/ext/fts1/fts1.c \ - $(TOP)/ext/fts1/fts1.h \ - $(TOP)/ext/fts1/fts1_hash.c \ - $(TOP)/ext/fts1/fts1_hash.h \ - $(TOP)/ext/fts1/fts1_porter.c \ - $(TOP)/ext/fts1/fts1_tokenizer.h \ - $(TOP)/ext/fts1/fts1_tokenizer1.c -SRC += \ - $(TOP)/ext/fts2/fts2.c \ - $(TOP)/ext/fts2/fts2.h \ - $(TOP)/ext/fts2/fts2_hash.c \ - $(TOP)/ext/fts2/fts2_hash.h \ - $(TOP)/ext/fts2/fts2_icu.c \ - $(TOP)/ext/fts2/fts2_porter.c \ - $(TOP)/ext/fts2/fts2_tokenizer.h \ - $(TOP)/ext/fts2/fts2_tokenizer.c \ - $(TOP)/ext/fts2/fts2_tokenizer1.c SRC += \ $(TOP)/ext/fts3/fts3.c \ $(TOP)/ext/fts3/fts3.h \ @@ -316,7 +298,6 @@ TESTSRC = \ $(TOP)/src/test4.c \ $(TOP)/src/test5.c \ $(TOP)/src/test6.c \ - $(TOP)/src/test7.c \ $(TOP)/src/test8.c \ $(TOP)/src/test9.c \ $(TOP)/src/test_autoext.c \ @@ -345,7 +326,6 @@ TESTSRC = \ $(TOP)/src/test_quota.c \ $(TOP)/src/test_rtree.c \ $(TOP)/src/test_schema.c \ - $(TOP)/src/test_server.c \ $(TOP)/src/test_sqllog.c \ $(TOP)/src/test_superlock.c \ $(TOP)/src/test_syscall.c \ @@ -363,6 +343,7 @@ TESTSRC = \ TESTSRC += \ $(TOP)/ext/misc/amatch.c \ $(TOP)/ext/misc/appendvfs.c \ + $(TOP)/ext/misc/basexx.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/cksumvfs.c \ $(TOP)/ext/misc/closure.c \ @@ -396,7 +377,6 @@ TESTSRC += \ $(TOP)/ext/recover/test_recover.c -#TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c #TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c TESTSRC2 = \ @@ -481,14 +461,6 @@ HDR = \ # Header files used by extensions # -EXTHDR += \ - $(TOP)/ext/fts1/fts1.h \ - $(TOP)/ext/fts1/fts1_hash.h \ - $(TOP)/ext/fts1/fts1_tokenizer.h -EXTHDR += \ - $(TOP)/ext/fts2/fts2.h \ - $(TOP)/ext/fts2/fts2_hash.h \ - $(TOP)/ext/fts2/fts2_tokenizer.h EXTHDR += \ $(TOP)/ext/fts3/fts3.h \ $(TOP)/ext/fts3/fts3Int.h \ @@ -689,9 +661,6 @@ sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl sqlite3-all.c: sqlite3.c $(TOP)/tool/split-sqlite3c.tcl tclsh $(TOP)/tool/split-sqlite3c.tcl -fts2amal.c: target_source $(TOP)/ext/fts2/mkfts2amal.tcl - tclsh $(TOP)/ext/fts2/mkfts2amal.tcl - # Rules to build the LEMON compiler generator # lemon: $(TOP)/tool/lemon.c $(TOP)/tool/lempar.c @@ -757,6 +726,8 @@ SHELL_SRC = \ $(TOP)/src/shell.c.in \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ + $(TOP)/ext/misc/base64.c \ + $(TOP)/ext/misc/base85.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ @@ -784,24 +755,6 @@ shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl icu.o: $(TOP)/ext/icu/icu.c $(HDR) $(EXTHDR) $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/icu/icu.c -fts2.o: $(TOP)/ext/fts2/fts2.c $(HDR) $(EXTHDR) - $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2.c - -fts2_hash.o: $(TOP)/ext/fts2/fts2_hash.c $(HDR) $(EXTHDR) - $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_hash.c - -fts2_icu.o: $(TOP)/ext/fts2/fts2_icu.c $(HDR) $(EXTHDR) - $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_icu.c - -fts2_porter.o: $(TOP)/ext/fts2/fts2_porter.c $(HDR) $(EXTHDR) - $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_porter.c - -fts2_tokenizer.o: $(TOP)/ext/fts2/fts2_tokenizer.c $(HDR) $(EXTHDR) - $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_tokenizer.c - -fts2_tokenizer1.o: $(TOP)/ext/fts2/fts2_tokenizer1.c $(HDR) $(EXTHDR) - $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts2/fts2_tokenizer1.c - fts3.o: $(TOP)/ext/fts3/fts3.c $(HDR) $(EXTHDR) $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3.c @@ -976,6 +929,10 @@ tcltest: ./testfixture$(EXE) testrunner: testfixture$(EXE) ./testfixture$(EXE) $(TOP)/test/testrunner.tcl +# Runs both fuzztest and testrunner, consecutively. +# +devtest: testfixture$(EXE) fuzztest testrunner + # A very quick test using only testfixture and omitting all the slower # tests. Designed to run in under 3 minutes on a workstation. # diff --git a/manifest b/manifest index 1d8427952..f17d7340c 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.40.1 -D 2022-12-28T14:03:47.023 +C Version\s3.41.0 +D 2023-02-21T18:09:37.622 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 78e4c4916f2c3993a8a454018745ff02094a8029d449d0c22db98f1cf8260420 +F Makefile.in 594f07f8829020a45d825234edf14046544b5bf6bc0057a9d5b83b58686b1c46 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 -F Makefile.msc e7a564ceec71f0d9666031d5638cf0d4f88b050b44e8df5d32125137cd259ac0 +F Makefile.msc b0026b61d1242add00c28dbe169271a763fffcb3af6762bcdbeb20c3efe6a69f F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e -F VERSION abec048a7ac74ad5559a336fe048d624207ac1e21be7be4356f3fa6b1ad4bec8 +F VERSION 413ec94920a487ae32c9a2a8819544d690662d6f7c7ce025c0d0b8a1e74fa9db F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -15,14 +15,14 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc 8401a514e4e70add3c6448348ae31322d5cb7db427b05a20828f943c3ddb2733 +F autoconf/Makefile.msc 20366d19fbfc3fceecce95344a2114069eaab4fc22e4f02430da167a1e2ddf04 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac 2bbcc0f449361c5dfbcfc51ebadc5118e4c4e8c670674a10b6443356ecdb493e +F autoconf/tea/configure.ac d1eaa72f4d202ca106f4b2762733709d3f89a877da2e385a57881c4ad0427be7 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,8 +33,8 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 291769d08ff3a3091f420ccf7d2e1010153f778d6372217d4cc6fcbd2d14bb2d x -F configure.ac 48dc6bfee293eef05910faf085760f2fd79b680aa47b50e8e6a22ca40bb026bb +F configure 6ba84abf7878bb072d460766d29e492028468775d4c09464940343092eb6493c x +F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f @@ -54,46 +54,20 @@ F ext/expert/expert1.test 95b00567ce0775126a1b788af2d055255014714ecfddc97913864d F ext/expert/sqlite3expert.c a912efbad597eafdb0ce934ebc11039f3190b2d479685d89184e107f65d856e1 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 -F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e -F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b -F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea -F ext/fts1/fts1.c a39f7d21c2994d27c959ef9c3505c81542c81432 -F ext/fts1/fts1.h 6060b8f62c1d925ea8356cb1a6598073eb9159a6 -F ext/fts1/fts1_hash.c 3196cee866edbebb1c0521e21672e6d599965114 -F ext/fts1/fts1_hash.h e7f0d761353996a8175eda351104acfde23afcb0 -F ext/fts1/fts1_porter.c b1c7304b8988ba3f764a147cdd32043b4913ea7b -F ext/fts1/fts1_tokenizer.h fdea722c38a9f82ed921642981234f666e47919c -F ext/fts1/fts1_tokenizer1.c fd00d1fe4dc30dfc5c64cba695ce34f4af20d2fa -F ext/fts1/fulltext.c 37698e1909584f6d8ea67d1485e3ad39dbf42d19 -F ext/fts1/fulltext.h 08525a47852d1d62a0be81d3fc3fe2d23b094efd -F ext/fts1/simple_tokenizer.c bbfa4e3b2a26ef17d4edc6d98cd4a3f5396d998a -F ext/fts1/tokenizer.h 0c53421b832366d20d720d21ea3e1f6e66a36ef9 -F ext/fts2/README.tokenizers 21e3684ea5a095b55d70f6878b4ce6af5932dfb7 -F ext/fts2/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts2/fts2.c 72c816a9ae448049fbbe8f18a85698765fc7956c -F ext/fts2/fts2.h da5f76c65163301d1068a971fd32f4119e3c95fa -F ext/fts2/fts2_hash.c 011a1d32de45bb1b519a1fd0048e857d6a843558 -F ext/fts2/fts2_hash.h 1824b99dfd8d0225facbdb26a2c87289b2e7dcf8 -F ext/fts2/fts2_icu.c 51c5cd3c04954badd329fa738c95fcdb717b5188 -F ext/fts2/fts2_porter.c 2cd4a507bf3c3085fe66f59b0f2a325f65aaacf5 -F ext/fts2/fts2_tokenizer.c b529493d55e55497213c37e1f31680a77746be26 -F ext/fts2/fts2_tokenizer.h 27a1a99ca2d615cf7e142839b8d79e8751b4529e -F ext/fts2/fts2_tokenizer1.c 07e223eecb483d448313b5f1553a4f299a7fb7a1 -F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0 F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c7cc3bf59ee F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 46c116ee0868fc09520d61a1b747c62343c71c399ba67a3d0395b0be29b1a19f +F ext/fts3/fts3.c 66d2ced306ac88c39c00d81184f2c60f338696af6ae8cc26ed7c361b157b09f7 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h ae2a44b04cddb5fb35cac4ea5f7f819b2894fd258186465777a19f7acfdf84ed +F ext/fts3/fts3Int.h e573c6d881f7238d77cc3fd2396cbb9b2fe13efef7d2ad295a155151c4e7efbd F ext/fts3/fts3_aux.c f0dc9bd98582615b7750218899bd0c729879b6bbf94d1be57ca1833ff49afc6f F ext/fts3/fts3_expr.c 903bfb9433109fffb10e910d7066c49cbf8eeae316adc93f0499c4da7dfc932a F ext/fts3/fts3_hash.c 8b6e31bfb0844c27dc6092c2620bdb1fca17ed613072db057d96952c6bdb48b7 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf F ext/fts3/fts3_icu.c 305ce7fb6036484085b5556a9c8e62acdc7763f0f4cdf5fd538212a9f3720116 F ext/fts3/fts3_porter.c e19807ce0ae31c1c6e9898e89ecc93183d7ec224ea101af039722a4f49e5f2b8 -F ext/fts3/fts3_snippet.c f9a8149173553113f3c495a503843e30028b5dc3723d0ca798c5ad6142e130e6 +F ext/fts3/fts3_snippet.c 4d6523e3eddeb7b46e7a82b3476a0a86a0c04821e0e2b8dd40f45ee28057cb13 F ext/fts3/fts3_term.c f45a1e7c6ef464abb1231245d123dae12266b69e05cc56e14045b76591ae92d1 F ext/fts3/fts3_test.c d8d7b2734f894e8a489987447658e374cdd3a3bc8575c401decf1911cb7c6454 F ext/fts3/fts3_tokenize_vtab.c a95feda3590f3c3e17672fe35b67ea6112471aeea4c07ef7744a6606b66549aa @@ -116,10 +90,10 @@ F ext/fts5/fts5Int.h c0d46e399e345e35985b72a1c1af025973bfaa5b1e3563b0ce3bb0ce144 F ext/fts5/fts5_aux.c f558e1fb9f0f86a4f7489e258c162e1f947de5ff2709087fbb465fddb7092f98 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 501e7d3566bc92766b0e11c0109a7c5a6146bc41144195459af5422f6c2078aa -F ext/fts5/fts5_expr.c 40174a64829d30cc86e8266306ad24980f6911edd5ca0b8c1ce7821ea1341b88 +F ext/fts5/fts5_expr.c 48e8e45261c6030cf5c77f606217a22722b1a4d0b34e2ba6cbfc386581627989 F ext/fts5/fts5_hash.c d4fb70940359f2120ccd1de7ffe64cc3efe65de9e8995b822cd536ff64c96982 -F ext/fts5/fts5_index.c a8ee270724ae1f958d0ce9897bcd60a5b760ecbeaa058fc8632805a283f1c20a -F ext/fts5/fts5_main.c 2bf44b9efdecc7fd936f320064666235b91bba8a6c4f3dc6e2d7eae6662570ed +F ext/fts5/fts5_index.c df5b29576a409f673e54b470723d817df9d5167cff208c48ab9a3773cba6fa89 +F ext/fts5/fts5_main.c fe67b6fb2ef134d9dbfa3941c63f777d755b075449be1863cb913a7f8754cb69 F ext/fts5/fts5_storage.c 76c6085239eb44424004c022e9da17a5ecd5aaec859fba90ad47d3b08f4c8082 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee @@ -200,6 +174,7 @@ F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e4 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd F ext/fts5/test/fts5onepass.test f9b7d9b2c334900c6542a869760290e2ab5382af8fbd618834bf1fcc3e7b84da F ext/fts5/test/fts5optimize.test 36a752d24c818792032e4ff502936fc9cc5ef938721696396fdc79214b2717f1 +F ext/fts5/test/fts5optimize2.test c7c97693abe8a2cb572acfb1f252d78f03d3984094cfc5eb2285a76d8a702a92 F ext/fts5/test/fts5phrase.test 13e5d8e9083077b3d9c74315b3c92ec723cc6eb37c8155e0bfe1bba00559f07b F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd116514f79c49c5a F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15 @@ -220,7 +195,7 @@ F ext/fts5/test/fts5synonym2.test b54cce5c34ec08ed616f646635538ae82e34a0e28f947e F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 -F ext/fts5/test/fts5trigram.test 5b4feb53a4d5aca70c841f6919c8719b5a9c805474727dda99285fccdd2e9cce +F ext/fts5/test/fts5trigram.test c76acc1913a06182e791a0dfdae285b9cdd67327a1a35b34cabf0a6aa09cf05e F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602 F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9 @@ -267,19 +242,19 @@ F ext/lsm1/lsm-test/lsmtest_tdb4.c cbe230727b9413d244062943371af1421ace472ccb023 F ext/lsm1/lsm-test/lsmtest_util.c 241622db5a332a09c8e6e7606b617d288a37b557f7d3bce0bb97809f67cc2806 F ext/lsm1/lsm-test/lsmtest_win32.c 0e0a224674c4d3170631c41b026b56c7e1672b151f5261e1b4cc19068641da2d F ext/lsm1/lsm.h 0f6f64ff071471cb87bf98beb8386566f30ea001 -F ext/lsm1/lsmInt.h 5983690e05e83653cc01ba9d8fbf8455e534ddf8349ed9adedbf46a7549760b0 -F ext/lsm1/lsm_ckpt.c 0eabfaf812ddb4ea43add38f05e430694cd054eb622c3e35af4c43118a2d5321 -F ext/lsm1/lsm_file.c 3c51841d5b3e7da162693cbac9a9f47eeedf6bcbbe2969a4d25e30c428c9fe36 +F ext/lsm1/lsmInt.h 3bcc280347196e4ed14925b64a07685415238bf41317db0598c8d3f6aaceb9c1 +F ext/lsm1/lsm_ckpt.c ad9a8028d401be9e76f20c4d86d49f33f4fc27785577b452ca955094314a72b4 +F ext/lsm1/lsm_file.c 5486f4a63b19e4d7d972ee2482f29ebdf06c29544f31845f713cccb5199f9ad1 F ext/lsm1/lsm_log.c a8bf334532109bba05b09a504ee45fc393828b0d034ca61ab45e3940709d9a7c -F ext/lsm1/lsm_main.c b5703f8042e71d3a2d65e671f6832e077e79e89e9975818f67f969922618db63 +F ext/lsm1/lsm_main.c 87770a9c7e73859fce7620cb79623776ba4b30369086229ad82c3e6eeaf45457 F ext/lsm1/lsm_mem.c 4c51ea9fa285ee6e35301b33491642d071740a0a F ext/lsm1/lsm_mutex.c 378edf0a2b142b4f7640ee982df06d50b98788ea -F ext/lsm1/lsm_shared.c 76adfc1ed9ffebaf92746dde4b370ccc48143ca8b05b563816eadd2aadf1c525 -F ext/lsm1/lsm_sorted.c 6f7d8cf7a7d3d3f1ab5d9ba6347e8f39f3d73c00ec48afcd0c4bcbefd806f9b8 +F ext/lsm1/lsm_shared.c c67282a4f2c91e2a3362bdd40a81f9041cd587973ffc4bca8b8fbdab5470dee1 +F ext/lsm1/lsm_sorted.c bc276055afc21e7f23538d39d7cf2722379b56c79778ab7232f710e3374d501c F ext/lsm1/lsm_str.c 65e361b488c87b10bf3e5c0070b14ffc602cf84f094880bece77bbf6678bca82 F ext/lsm1/lsm_tree.c 682679d7ef2b8b6f2fe77aeb532c8d29695bca671c220b0abac77069de5fb9fb F ext/lsm1/lsm_unix.c 11e0a5c19d754a4e1d93dfad06de8cc201f10f886b8e61a4c599ed34e334fc24 -F ext/lsm1/lsm_varint.c 43f954af668a66c7928b81597c14d6ad4be9fedbc276bbd80f52fa28a02fdb62 +F ext/lsm1/lsm_varint.c fe134ad7b2db1ecd99b6a155d2f3625cfd497730e227ae18892452e457b73327 F ext/lsm1/lsm_vtab.c e57aa3eb456bf2b98064014027e097c9402d6dec7b59564ddbfa1c0ead8f96c5 F ext/lsm1/lsm_win32.c 0a4acbd7e8d136dd3a5753f0a9e7a9802263a9d96cef3278cf120bcaa724db7c F ext/lsm1/test/lsm1_common.tcl 5ed4bab07c93be2e4f300ebe46007ecf4b3e20bc5fbe1dedaf04a8774a6d8d82 @@ -289,17 +264,20 @@ F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f23 F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824 +F ext/misc/base64.c d43d2b209c8ab70ca3f860104bb353b0f52a1c5462a2466140025c954e4f3ea7 x +F ext/misc/base85.c 77dfd5813d23ea561d0348f922583888e78f8eaeb2b9a4a28226d092389890b8 +F ext/misc/basexx.c 5e859e1820620aa8080fb9145eb47089de426ae808f6abb01a8e12921c3a8e67 F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9 -F ext/misc/carray.c b752f46411e4e47e34dce6f0c88bc8e51bb821ba9e49bfcd882506451c928f69 -F ext/misc/carray.h d2b1b12486d531367c37832d3d0dad34eea4bdd83ed839d445521ef01f0bc4e3 +F ext/misc/carray.c 0ba03f1e6647785d4e05b51be567f5652f06941314ff9d3d3763900aa353b6b5 +F ext/misc/carray.h 503209952ccf2431c7fd899ebb92bf46bf7635b38aace42ec8aa1b8d7b6e98a5 F ext/misc/cksumvfs.c 9224e33cc0cb6aa61ff1d7d7b8fd6fe56beca9f9c47954fa4ae0a69bef608f69 F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c85c243 F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9 F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 -F ext/misc/decimal.c 09f967dcf4a1ee35a76309829308ec278d3648168733f4a1147820e11ebefd12 +F ext/misc/decimal.c 57d85fa20a5a74d3b0dfc78ab7934ae6c9f5aa8eed915faa2b5246bec87ddc6d F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720 @@ -316,17 +294,17 @@ F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196 F ext/misc/qpvtab.c 09738419e25f603a35c0ac8bd0a04daab794f48d08a9bc07a6085b9057b99009 -F ext/misc/regexp.c 064838f7b31e90d312cce089cfafbb992034e75d359009c48886ca06c0a794b2 +F ext/misc/regexp.c f50ab59bfa8934b7ed98de069d2c74c187f2ef523fb09e85f8840f6459a90942 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 F ext/misc/series.c 8d79354f2c3d46b95ee21272a07cf0bcabb58d1f2b06d9e7b8a31dca1dacb3e5 F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d -F ext/misc/shathree.c 7b17615869a495659f1569ada1d8d3d21b4a24614f2746d93cc87ef7c0b6b36d +F ext/misc/shathree.c 9b7af7b7d55b27c5bbd16548b67fe6aa47a819e71bc6a80a456144f86f4e834d F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f -F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6 -F ext/misc/stmt.c ed05ad78013edccd43f4fc33d8244001f6f886eb2dfd2106ba33616ac514c6fb +F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7 +F ext/misc/stmt.c bc30d60d55e70d0133f10ac6103fe9336543f673740b73946f98758a2bb16dd7 F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b @@ -338,56 +316,57 @@ F ext/misc/vfsstat.c 474d08efc697b8eba300082cb1eb74a5f0f3df31ed257db1cb07e72ab0e F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546 -F ext/misc/zipfile.c 22afe121d1a5e318453b7cdbc0f5492161d2fd4fce548ff3605da05e89be7140 +F ext/misc/zipfile.c f98239261488397618ce4754c500626d1de20cd2d44bf2f2d571d7ddaab668a7 F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 -F ext/rbu/rbu1.test c62904bd9526dcdc3496a21199aaf14ae191bbadbf67f076bf16be6b3f2115c2 -F ext/rbu/rbu10.test 06d2bc934a03a0978e750cc9c95b419d9b0bcbec1fc77128e33e377c3a73240b -F ext/rbu/rbu11.test 5c834cf491086b45e071eabf71f708febc143e86a384a92de69e0b1a4cace144 -F ext/rbu/rbu12.test 29f8b2118f6c96fac3755bd6d2b55c2db24f878b1f11fbfbe294f3a230a3dcdc -F ext/rbu/rbu13.test 1285298e3360ec74511764841b3c174dcfe21da2f618c22febf1a20abd0365c2 -F ext/rbu/rbu14.test 4a7bf0b3a4516d3ab0bc0ba4ceb53eb7e3324147ccda152e561060f659dbba31 -F ext/rbu/rbu3.test d6c6cc7a1326e8e23b9820f30bd3054f22092e503fadfd2a660ae006653f6d80 -F ext/rbu/rbu5.test 724b38ea5f722e3d22dc76343860bd998bb676c3f78c4bc8175df5c5d7720e23 -F ext/rbu/rbu6.test 401064236d3cf86b7edc01c586d7c5554f48553946fbfa1a3af35d7e47dce9e3 -F ext/rbu/rbu7.test ae25f47b56f178197fc1098537a35a39176cc73d1629b03dc9d795929fc36ec2 -F ext/rbu/rbu8.test b98a6fc58ead84a0e6ddee775b9702cd981f318d5d4fd1d4df0fa0c40db7251b -F ext/rbu/rbu9.test 0e4d985e25620d61920597e8ea69c871c9e8c1f5a0be2ae9fa70bb641d74378c -F ext/rbu/rbuA.test b34a90cb495682c25b5fc03a9d5e7a4fc99541c29256f25e2e2a4f6542b4f5b3 -F ext/rbu/rbuB.test 52b07158824c6927b7e25554ace92a695cdebfc296ae3d308ac386984aded9bc -F ext/rbu/rbuC.test 80f1cc2fb74f44b1128fd0ed8eedab3a76fefeb72a947860e2869ef76fc8dc6b -F ext/rbu/rbu_common.tcl 60d904133ff843fe72cc0514e9dd2486707181e6e0fbab20979da28c48d21de9 -F ext/rbu/rbubusy.test f38ef557358564491b8a2ee70e4cad31e40fcea57a16f27bc56ba40a59bbde50 -F ext/rbu/rbucollate.test cac528a9a46318cba42e61258bb42660bbbf4fdb9a8c863de5a54ad0c658d197 -F ext/rbu/rbucrash.test 000981a1fe8a6e4d9a684232f6a129e66a3ef595f5ed74655e2f9c68ffa613b4 -F ext/rbu/rbucrash2.test efa143cc94228eb0266d3f1abfbee60a5838a84cef7cc3fcb8c145b74d96fd41 -F ext/rbu/rbudiff.test abe895a8d479e4d33acb40e244e3d8e2cd25f55a18dfa8b9f83e13d00073f600 -F ext/rbu/rbudor.test e3e8623926012f43eebe51fedf06a102df2640750d971596b052495f2536db20 -F ext/rbu/rbuexlock.test 4634a5526d02bf80b0c563f95774bd5af5783e3219ddeb30e413753c9a65510c -F ext/rbu/rbuexpr.test 10d0420537c3bc7666e576d72adeffe7e86cfbb00dcc30aa9ce096c042415190 -F ext/rbu/rbufault.test 2d7f567b79d558f6e093c58808cab4354f8a174e3802f69e7790a9689b3c09f8 -F ext/rbu/rbufault2.test c81327a3ac2c385b9b954db3644d4e0df93eeebfc3de9f1f29975a1e73fd3d0c -F ext/rbu/rbufault3.test b2fcc9db5c982b869f67d1d4688d8cb515d5b92f58011fff95665f2e62cec179 -F ext/rbu/rbufault4.test 03d2849c3df7d7bd14a622e789ff049e5080edd34a79cd432e01204db2a5930a -F ext/rbu/rbufts.test 0ae8d1da191c75bd776b86e24456db0fb6e97b7c944259fae5407ea55d23c31d -F ext/rbu/rbumisc.test 329986cf5dd51890c4eb906c2f960ebb773a79a64bed90f506b7c417825b37eb -F ext/rbu/rbumulti.test 5fb139058f37ddc5a113c5b93238de915b769b7792de41b44c983bc7c18cf5b9 -F ext/rbu/rbupartial.test f25df014b8dbe3c5345851fba6e66f79ab237f57dc201b2d5f0dbae658ae5a4c -F ext/rbu/rbuprogress.test 857cf1f8166c83ef977edb9ef4fc42d80f71fbd798652b46ae2f3a7031870f8d -F ext/rbu/rburename.test a9b4aea612352b74c45de1757edd2ecb2079348b1d4cc734572dc29e55b1b376 -F ext/rbu/rburesume.test dbdc4ca504e9c76375a69e5f0d91205db967dcc509a5166ca80231f8fda49eb1 -F ext/rbu/rbusave.test f4190a1a86fccf84f723af5c93813365ae33feda35845ba107b59683d1cdd926 -F ext/rbu/rbusplit.test b37e7b40b38760881dc9c854bd40b4744c6b6cd74990754eca3bda0f407051e8 -F ext/rbu/rbutemplimit.test 05ceefa90a2e26a99f40dd48282ed63a00df5e59c1f2bfd479c143e201a1b0ba -F ext/rbu/rbuvacuum.test 55e101e90168c2b31df6c9638fe73dc7f7cc666b6142266d1563697d79f73534 -F ext/rbu/rbuvacuum2.test 2643b58f4d8d3573db0f93faae18805a35ab162b4c55ff6b656062ff432ed55b -F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc -F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10 -F ext/rbu/sqlite3rbu.c c4ba7901b2d3e0c7845f30840e3ffb35c6f999d6da0d80f121866491f982794c -F ext/rbu/sqlite3rbu.h 02d981e2d39c151391759e1a400e29c7388730812957ac3db8dad7f6c9f9cfc8 +F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 +F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363 +F ext/rbu/rbu11.test 8584f80ef4be00e6beec4154f638847ffc40b5f2832ffadfbaf558ae40e50cb5 +F ext/rbu/rbu12.test ec63aa7bfc3c65c1d774bf4357ed731723827d211d9d7cb0efa171bbaeeebaf4 +F ext/rbu/rbu13.test 658edbc3325d79252a98b761fde95460e439f80e820ff29e10261e25f870b3b6 +F ext/rbu/rbu14.test 05dac607a62f62102f4db92135979a8a4501143638060019aca08c753822cf39 +F ext/rbu/rbu3.test 4a81517af618c3bf8c72e2d0b81c7c06acb8d176036d63d8e6669b73342306ae +F ext/rbu/rbu5.test e21820b83822ae4c12afc2078a7b6c0523fb0cefe69c8b23c044cea91359e81c +F ext/rbu/rbu6.test db2ff1f832dfc9e34c7910b17e157c2fe0e36024a3fe1119dd6437640dc07c82 +F ext/rbu/rbu7.test 5fa41734613a3ae1bb93d280eb3c341cff5dcc72652ff9ec7fbaa12425eda9c2 +F ext/rbu/rbu8.test 93d45824dab8f68872b6d22acc787ab18ba92ef0fa0d430be37653d0246c7a0d +F ext/rbu/rbu9.test 4b66f0705442711a44b54ef2cc3c59952f1ea15f12e34442681bdb1a6eb33065 +F ext/rbu/rbuA.test 3f8fdd4ae7b9a0571af7361cd88359254f63e445ac4acfe395173e31d7e3fc31 +F ext/rbu/rbuB.test c639803bbc1dc9358afe6abe046dc4d3e9965238b75239b04e3a8e33e3e90f85 +F ext/rbu/rbuC.test 5326ea3954754c68fd518beb70d3e6b6690af53e1a5fa102d650e4110b26b4c5 +F ext/rbu/rbu_common.tcl 15d063397a89aeaf26b4cbdf6f29911b4154a902ba61a40c4f180ab452454a63 +F ext/rbu/rbubusy.test 88298187ad35aac9084436d85ca66b3722f96eaa704a09cfe5f931d452ab7237 +F ext/rbu/rbucollate.test 9852ec5e5ba7f3b04ce849a24ef7298e03ae0f16e58e6031d0f845234559feec +F ext/rbu/rbucrash.test d2b5d619d9281c89cad74401b73b46172daa89906940b1d739c813ddc0cd2cf5 +F ext/rbu/rbucrash2.test 0a1a72223d880215ce2893a3260320c31a9358d23cb124c610e4f0d984a93285 +F ext/rbu/rbudiff.test 8b8b8b569c68fc880134e0fac4bf6b4b7a907aea4cc6eacf7e1d45e1d47b6aac +F ext/rbu/rbudor.test 293a192e668bb8e9c7c9704b080c1086ee17496f768e0f1823049e7d02651d1b +F ext/rbu/rbuexlock.test e3ece733cc8e0a6cb08533a41ed6f562438539ed309028c63375a5adee4a263b +F ext/rbu/rbuexpr.test 2c91617509c88b6e9030f7bf6ff720df26032fcd801adc25533feae726a57382 +F ext/rbu/rbufault.test c51de14067cfe867849530d3d1718ffeb28522f28d52937f95dd7bc2116eb42e +F ext/rbu/rbufault2.test 8cc8f6298d2d7d20080b2c77e65b607af8b89839f9d87c0972b27e6442edc258 +F ext/rbu/rbufault3.test d14ff46e050816ce43c4ed320a0927712636ac11bf48bfc5f74601f183af5445 +F ext/rbu/rbufault4.test 39fbf093b7e16aae85dc309262ec570d217a1578538c1c74dd621e5451c083d6 +F ext/rbu/rbufts.test df754d2f96c22d1da8b5d685b4a4a49863971920856d17620cef724e3a9b6edd +F ext/rbu/rbumisc.test 6641749e42c83062824c86b3d03a47f8ec35760f341bc023f53e612655b0a8af +F ext/rbu/rbumulti.test 6f6cdd9b3775108aada5216762cbbd7b5d5caa7cb620b3e6e1b8ace81286a2e0 +F ext/rbu/rbupartial.test 4ed7789f47128c8aa7ff58445face8a070cef852993afe03c863913f3cea8729 +F ext/rbu/rbupass.test 2ee86581a441f3b4b449b99a2dc203d5d6a08750dd2ee9ab6a02743e238d3c8a +F ext/rbu/rbuprogress.test db8bb26a8123d35f52acfc3984b56caa31c8fcd1fa3589991b9c8e8a68e64b59 +F ext/rbu/rburename.test 8d8a6a6ba896338d0610658e1f60e8055a181d5913e1e21c41b866a8f15bb7cd +F ext/rbu/rburesume.test 1403752d152b55efb7fc25749c0fccc790061371ec9ffe428cc04f8a69bb834c +F ext/rbu/rbusave.test 588b618dad9d65c4b13d03a79931de82213503fedc26bdf5789c996ecf427fba +F ext/rbu/rbusplit.test a6dedd23cf37bcf2e8646d9d7139846e96d60d92f9bc6d6ba6ca8c24c0bd1f72 +F ext/rbu/rbutemplimit.test 4980df2d4b74f4dd982add8f78809106154ef5a3c4bdce747422ab0b0481e029 +F ext/rbu/rbuvacuum.test 542561741ff2b262e3694bc6012b44694ee62c545845319a06f323783b15311e +F ext/rbu/rbuvacuum2.test ae097d04feb041446a74fac94b24bffeb3fdd60e32b848c5611e507ab702b81b +F ext/rbu/rbuvacuum3.test 3ce42695fdf21aaa3499e857d7d4253bc499ad759bcd6c9362042c13cd37d8de +F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69eefaebb205 +F ext/rbu/sqlite3rbu.c 348bb6251e6ec459de102f8b2dd50789a98643ef7a28e56e4c787ac9659c15ea +F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 -F ext/recover/dbdata.c 8f1f75d636431de69d7977ec50fc41bfdd0c48c510d5ee7eae0cbd4164e1429a -F ext/recover/recover1.test 6a12f73330a2ca4d1428730c09272e294c5082c2806ae9e56db846374e03090d +F ext/recover/dbdata.c dc25628e405c86936c597e28f3e6f56a257029c3034c5ef7f6b10f7c02f41018 +F ext/recover/recover1.test 2072993624d5e32fef20ae03b17fc06c02bcb344421fe17bb329b24d2a51e647 F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e F ext/recover/recovercorrupt.test 64c081ad1200ae77b447da99eb724785d6bf71715f394543dc7689642e92bf49 @@ -399,7 +378,7 @@ F ext/recover/recoverpgsz.test 3658ab8e68475b1bb87d6af88baa04551c84b73280a566a1b F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a3a21030057bfd81411 F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486 -F ext/recover/sqlite3recover.c 6d37a422c8917b793548a7b2c6e7ff8e1af15ba5453eef37bb69331828b73186 +F ext/recover/sqlite3recover.c 3346e73e8570cb803618f0d4b65a0d07a3e246c6591347bdf04c7d0bb6d3bbde F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 @@ -412,8 +391,8 @@ F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc782 F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c335096108c12c01bddbadcec F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/geopoly.c bb0dcd013dbb3fe0335b6bbae0d9c29fcfffda93d5ef6c31daa47e802132e435 -F ext/rtree/rtree.c b963ebced19f249d8523396a779351481d13a67c36c3c1f034b982886cd39a42 +F ext/rtree/geopoly.c 971e0b5bd9adaf0811feb8c0842a310811159da10319eb0e74fdb42bf26b99ca +F ext/rtree/rtree.c e05929e78d127613a9eea5dc372c77a049484892d8e9fac1fe0cce85ce4fba81 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test d47f58832145fcfed9067bc457ca8664962196c4566c17a1ebd679367db55d11 F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d @@ -435,7 +414,7 @@ F ext/rtree/rtreeH.test 0885151ee8429242625600ae47142cca935332c70a06737f35af53a7 F ext/rtree/rtreeI.test 608e77f7fde9be5a12eae316baef640fffaafcfa90a3d67443e78123e19c4ca4 F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195 F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b587936f5f6bceed -F ext/rtree/rtreecheck.test 1f542257f21c8a22ce3462c852ec1a0847fa8b3133053abfab3972764210e8bc +F ext/rtree/rtreecheck.test 4e859a9cd49d2353ff10c122f72183ec37b400e35d2b0349b2e9696649b6a00e F ext/rtree/rtreecirc.test aec664eb21ae943aeb344191407afff5d392d3ae9d12b9a112ced0d9c5de298e F ext/rtree/rtreeconnect.test 225ad3fcb483d36cbee423a25052a6bbae762c9576ae9268332360c68c170d3d F ext/rtree/rtreedoc.test 27a5703cb1200f6f69051de68da546cef3dfdcf59be73afadfc50b9f9c9960d9 @@ -469,7 +448,7 @@ F ext/session/sessionG.test 3828b944cd1285f4379340fd36f8b64c464fc84df6ff3ccbc955 F ext/session/sessionH.test b17afdbd3b8f17e9bab91e235acf167cf35485db2ab2df0ea8893fbb914741a4 F ext/session/session_common.tcl f613174665456b2d916ae8df3e5735092a1c1712f36f46840172e9a01e8cc53e F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3 -F ext/session/sessionat.test 52993535f1230a42f70886643574ba7ae60ef854f8add9c8e3fcc3eb5c564bd2 +F ext/session/sessionat.test 46fd847f6ed194ebb7ebef9fe68b2e2ec88d9c2383a6846cddc5604b35f1d4ae F ext/session/sessionbig.test 890ade19e3f80f3d3a3e83821ff79c5e2af906a67ecb5450879f0015cadf101e F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 @@ -481,82 +460,87 @@ F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2 F ext/session/sessionsize.test 6f644aff31c7f1e4871e9ff3542766e18da68fc7e587b83a347ea9820a002dd8 F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5 F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c b4254dd6e785cdd206c9ca7118796cf82273627fe2d4fd647597f08c2f821f96 +F ext/session/sqlite3session.c 13bdc093416cd284d4075328dd8599eb59bcedc23a21d561a15d78805c5866bf F ext/session/sqlite3session.h 0907de79bc13a2e3af30a6dc29acc60792a3eaf7d33d44cf52500d0f3c2b2171 -F ext/session/test_session.c 6aa7e97407987e81431b26730889db4719efebebbb6fe7502802d19d58b35798 +F ext/session/test_session.c 2de472b4d7e62e85ca1992094612725e2450a77dbf7523db64de94197812462e F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 3aa8c160705ab9d9d4d552a1f4e630925a65a27df216befe2e9a956904434c1d -F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20 +F ext/wasm/GNUmakefile 5418b4702f4ad0f2162a7e0d128042e8d9219827e3d36978bd2dd6e26ce8f68e +F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9 -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 9120c2f8f51fa85f46dcf4dcb6b12f4a807d428f6089b99cdb08d8ddfcfd88b2 +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 -F ext/wasm/api/README.md 1350088aee90e959ad9a94fab1bb6bcb5e99d4d27f976db389050f54f2640c78 -F ext/wasm/api/extern-post-js.js f3dc4219a2a1f183d98452dcbd55a0c5351b759eccca75480a92473974d8b047 +F ext/wasm/api/README.md 77a2f1f2fc60a35def7455dffc8d3f2c56385d6ac5c6cecc60fa938252ea2c54 +F ext/wasm/api/extern-post-js.c-pp.js 44a3a169f55a8dba42cf688954b2625b9b9e6174f2ff02d4918a2ca8c3beab7f F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1 -F ext/wasm/api/post-js-header.js d6ab3dfef4a06960d28a7eaa338d4e2a1a5981e9b38718168bbde8fdb2a439b8 -F ext/wasm/api/pre-js.js 287e462f969342b032c03900e668099fa1471d852df7a472de5bc349161d9c04 -F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34 -F ext/wasm/api/sqlite3-api-glue.js 056f44b82c126358a0175e08a892d56fadfce177b0d7a0012502a6acf67ea6d5 -F ext/wasm/api/sqlite3-api-oo1.js e9a83489bbb4838ce0aee46eaaa9350e0e25a5b926b565e4f5ae8e840e4fbaed -F ext/wasm/api/sqlite3-api-opfs.js 5c6d0903e100b2918920d8c596ab037cd9a67df19e388ebde4fe085218780af0 -F ext/wasm/api/sqlite3-api-prologue.js fd526fa017fa2578673ca18158354515c719e719a5d93f2f6d0e43f39170430e -F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f -F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 -F ext/wasm/api/sqlite3-opfs-async-proxy.js 24d1c1982a012d998907105a4ff1ff6881bf462395e90c06326817701e69f093 +F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 +F ext/wasm/api/pre-js.c-pp.js 9ece5de1bb0509f0a8a360712fcc9c1291b9516c0be5bd66acedd6edbcec37a1 +F ext/wasm/api/sqlite3-api-cleanup.js 2d63eb84267a1d15ce002e083d6396a521471da8af3afa76846d50f39a54d65e +F ext/wasm/api/sqlite3-api-glue.js 0a93e58aabf52b32ddccbb107a1fd4552f2505e103ab63396c4d0a0743704785 +F ext/wasm/api/sqlite3-api-oo1.js 9b50c188513c70438a497914089cfeac79b6ac2d73501775538f9e467325ea15 +F ext/wasm/api/sqlite3-api-prologue.js 5cc817b67a774bfa3c47d4c2fa484b10b24b5529a66094b35546f3ebba1ef646 +F ext/wasm/api/sqlite3-api-worker1.js 9551f04cdfcde354e5a6ccb48951e007d618abb4e95758297b7fd44ccffdf89f +F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 +F ext/wasm/api/sqlite3-opfs-async-proxy.js 7795b84b66a7a8dedc791340709b310bb497c3c72a80bef364fa2a58e2ddae3f +F ext/wasm/api/sqlite3-v-helper.js 6f6c3e390a72e08b0a5b16a0d567d7af3c04d172831853a29d72a6f1dd40ff24 +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js ca291837840b3eae3a60810721a7970c98f7c7cd3ee1c879acb7e91f1e3fe65a F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 -F ext/wasm/api/sqlite3-wasm.c 8fc8f47680df0e9a6c0f2f03cb004148645ecc983aa216daba09cb21f7e092a2 -F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b -F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54 +F ext/wasm/api/sqlite3-wasm.c 76625a70937a8522d014ef686c32db5b53a3ee61850323f5c601d2ac39fe52fe +F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js c5ac33e39f21a3481812d7333ca6e18853640d423a01960ca8dbc6e7c5c3c21c +F ext/wasm/api/sqlite3-worker1.c-pp.js 77b3835192469e9da23926ec8f78fb0b114a51d048dc54388709ac22b5c5f0a0 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 -F ext/wasm/batch-runner.js 49609e89aaac9989d6c1ad3fae268e4878e1ad7bc5fd3e5c2f44959660780b2e +F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e +F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4 F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b -F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f -F ext/wasm/common/testing.css 35889709547d89a6109ff83b25c11bbc91d8dd43aab8722e428655ca98880a06 -F ext/wasm/common/whwasmutil.js 16a592d5c304a2d268ca1c28e08a5b029a2f3cbe10af78dbc3456cfc9e3559d1 +F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 +F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f +F ext/wasm/common/whwasmutil.js cad510071533dbe47787bce53f2e0dcab5b05d2dde1dbe477d8fb04e00d4e8c4 F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8 F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f -F ext/wasm/demo-worker1-promiser.js b85a2bb1b918db4f09dfa24419241cb3edad7791389425c2505092e9b715017d +F ext/wasm/demo-worker1-promiser.js b99c550763fa792c204e9a7cceadd976004036d9fc3e22fab7051712e30d207d F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d F ext/wasm/demo-worker1.js a619adffc98b75b66c633b00f747b856449a134a9a0357909287d80a182d70fa -F ext/wasm/dist.make 481289899a07958439d07ee4302ff86235fa0fbb72f17ea05db2be90a94abf90 -F ext/wasm/fiddle.make e570ec1bfc7d803507a2e514fe32f673fe001b2114b85c73c3964a462ba8bcfc +F ext/wasm/dist.make f55f9c9e1980ea11a59964e59535c66175a17f004d1c2e274522c3366b3a084a +F ext/wasm/fiddle.make 396a0d6e2b3aa63ad448ea71d91676cb9cbbba8f5e6529cf1248d17f29142f47 F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f -F ext/wasm/fiddle/fiddle-worker.js b4a0c8ab6c0983218543ca771c45f6075449f63a1dcf290ae5a681b2cba8800d +F ext/wasm/fiddle/fiddle-worker.js 163d6139a93fab4bcb72064923df050d4e7c0ff0d8aa061ce8776a6e75da8a10 F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 -F ext/wasm/index-dist.html cb0da16cba0f21cda2c25724c5869102d48eb0af04446acd3cd0ca031f80ed19 -F ext/wasm/index.html ce6a68a75532b47e3c0adb83381a06d15de8c0ac0331fb7bf31d33f8e7c77dc4 -F ext/wasm/jaccwabyt/jaccwabyt.js 95f573de1826474c9605dda620ee622fcb1673ae74f191eb324c0853aa4dcb66 -F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e -F ext/wasm/module-symbols.html b8eebafef8e536624bbe5f7a3da40c07a9062b843dfd3161a0bb72cbb6763dc5 +F ext/wasm/index-dist.html 22379774f0ad4edcaaa8cf9c674c82e794cc557719a8addabed74eb8069d412e +F ext/wasm/index.html dd900891844caebd9cadbddd704f66bd841d7c12fd69ce5af490e2c10fb49f45 +F ext/wasm/jaccwabyt/jaccwabyt.js 06f2ef1ad640c26c593def3d960336e9bb789819b920516480895c38ed5f58fa +F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb +F ext/wasm/module-symbols.html 841de62fc198988b8330e238c260e70ec93028b096e1a1234db31b187a899d10 F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06 F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18 -F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0 -F ext/wasm/speedtest1-worker.html d75d9de9347c6f2f3278b73ccb6bceb559080644aef385fe496fa389f58bcd66 +F ext/wasm/speedtest1-wasmfs.html 7a301f4f5b6ad4f5d37fd6e7ca03a2f5d5547fd289da60a39075a93d7646d354 +F ext/wasm/speedtest1-worker.html fe6b36a63de1012bb9fb4d2fb888b6de9c589c21b0aa3ae054459b0093e077bf F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1 -F ext/wasm/speedtest1.html e4c4e5c1c8ec1ad13c995e346e4216a1df152fd2c5cd17e0793b865b2f3c5000 +F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f -F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac -F ext/wasm/tester1-worker.html 51bf39e2b87f974ae3d5bc3086e2fb36d258f3698c54f6e21ba4b3b99636fa27 -F ext/wasm/tester1.html 624ec41cd9f78a1f2b6d7df70aaa7a6394396b1f2455ecbd6de5775c1275b121 -F ext/wasm/tester1.js bff806de454de115922d78c056f11d523ec7ed9ed3839d4e21433a9f72558b88 +F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b +F ext/wasm/tester1-worker.html 258d08f1ba9cc2d455958751e26be833893cf9ff7853e9436e593e1f778a386b +F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 +F ext/wasm/tester1.c-pp.js 9844c675bd1f2353deabd7847d10c4fa55ff78a5c773073a239197d186123de7 +F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 +F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d +F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 -F ext/wasm/wasmfs.make edfd60691d10fd19ada4c061280fd7fbe4cf5f6bf6b913268e8ebedfccea6ab5 +F ext/wasm/wasmfs.make cf9a68162d92ca2bcb0b9528b244cb36d5cc2d84ccc9c2d398461927d6e75aea F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk fd90f1bd90bd4070c9af7e9c8396bd0cf4208a0186c8d18fa6e2609dff447d5d +F main.mk ac6b13f8ecc43f377e9912380ea4cf366051d7f784cf61c8886e03e1cf0fbefa F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -569,39 +553,39 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 -F src/alter.c 0390ca1d69ec3626cfa9f153114b7ab233e6b2bada6a9eb91361ed385fe90deb -F src/analyze.c d2fce73f6a024897593012c6ca25368629fa4aeb49960d88a52fac664582e483 -F src/attach.c 4431f82f0247bf3aaf91589acafdff77d1882235c95407b36da1585c765fbbc8 +F src/alter.c 3ca2f449c890f8b86ec9e06f0c4fccf0648941c3308a16904cb2852227db83f7 +F src/analyze.c b597c382f23b19cce563211181e84b7e8edddd6871d5f630bbadedb57e562806 +F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39 F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7 F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca -F src/btree.c 0d2aa1331a63eefc054faa8701cfe3ba1cbe27540cb02f65c61b0605bf6b5004 -F src/btree.h edfb08230b6fd6b0a165ba82defd3471b0e71d577d0c66807d4994d4aade4332 -F src/btreeInt.h 8ce1332edd89dfd2461d561ac10a0ab5601c8e06200cb5230596c3caaf54482e -F src/build.c d3e43e950e4e377c1d451a4862556792acdef1faba14a03f899d30d09731c48b +F src/btree.c 2f012aea074de6319c191cbf5c45034de79bf1c762826e381b1cf75421d8d831 +F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc +F src/btreeInt.h 06bb2c1a07172d5a1cd27a2a5d617b93b1e976c5873709c31964786f86365a6e +F src/build.c c55ab6d1b089ceef57160e840f05f692955ac90944c3d04fcf01d97fd7bfd08d F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d -F src/date.c 94ce83b4cd848a387680a5f920c9018c16655db778c4d36525af0a0f34679ac5 -F src/dbpage.c f1a87f4ebcf22284e0aaf0697862f4ccfc120dcd6db3d8dfa3b049b2580c01d8 -F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d +F src/date.c f21815ca7172ce073db3163ac54c8d9f2841077165c1a6123b4d1c376a0c7ec7 +F src/dbpage.c d47549716549311f79dc39fe5c8fb19390a6eb2c960f8e37c89a9c4de0c1052e +F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef F src/delete.c 86573edae75e3d3e9a8b590d87db8e47222103029df4f3e11fa56044459b514e -F src/expr.c 847f87d9df3ede2b2b0a8db088af0b9c1923b21009f8ea1b9b7b28cb0a383170 +F src/expr.c 2e5e67e800c9416996df52409746a2f3c7dadf5d38b5a367ce379b239e3f40c8 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 722f20779f5342a787922deded3628d8c74b5249cab04098cf17ee2f2aaff002 -F src/func.c d25f3c667d59dbac195e65f3539fdbbd8a36c142ce7bcdb45d1baf07446ad13a +F src/func.c 64c4cb2eebf6ddb6b4b08dc17790e7152b887f223f8caf0c0fe96da1c5a876ca F src/global.c e06ff8e0acd85aec13563c9ecb44fbbf38232ccf73594998fd880b92d619594b -F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19 +F src/hash.c c6af5f96a7a76d000f07c5402c48c318c2566beecdee9e78b9d9f60ce7119565 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 -F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 +F src/hwtime.h b638809e083b601b618df877b2e89cb87c2a47a01f4def10be4c4ebb54664ac7 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 90a32bc7faa755cd5292ade21d2b3c6edba8fd1d70754a364caccabfde2c3bb2 -F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6 +F src/insert.c 1b11a2e33ee52db93c02fddac67e39d00161d61b69fac2675b82f2aa68c1b61c +F src/json.c c85ed6fce06f43d414b0d7fff64749d43a0dbd1067123ee407bd3a0752454161 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 25663175950c5c4404b9377840b7b4c6fe5c53b415caf43634c62f442c02a9a7 -F src/main.c dcb6d30c31dcfd6c901e753d6618ef27838b2895a1a35d4ac9ac1c10c1bee128 -F src/malloc.c dfddca1e163496c0a10250cedeafaf56dff47673e0f15888fb0925340a8e3f90 +F src/loadext.c b04eb648cedc45efe4298e1ef439ac4f0096ae27b5f01accb0a1f49d57789128 +F src/main.c f5ed7b748d7e54b96ac577a9d87af9461f13f092bbe684d473448010333402dc +F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 @@ -618,53 +602,52 @@ F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae4185 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 F src/os.c 81c9c1c52eab711e27e33fd51fe5788488d3a02bc1a71439857abbee5d0d2c97 F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 -F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 -F src/os_kv.c 0e59600d25b72034c7666b8b7dcc527f039b5d9c16f24a7eca4c08c66f63c364 +F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 +F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c c710dfac1b86a87c82053fa397ae31628217a8a86630904c5496ce649e5ada2b +F src/os_unix.c 90c4fa0a88c8b0817c7ce4dbea0ac3aebfd7deb0797945ac34dfd25006ba393a F src/os_win.c 295fe45f18bd86f2477f4cd79f3377c6f883ceb941b1f46808665c73747f2345 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 6176d9752eb580419e8fef4592dc417a6b00ddfd43ee22f818819bf8840ceee8 +F src/pager.c 39af8ff7c73a991f61f4d1e3a6f8f98b1c8e29144723507822774cac5e6ee0b5 F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 -F src/parse.y 8e67d820030d2655b9942ffe61c1e7e6b96cea2f2f72183533299393907d0564 +F src/parse.y 960d2da92a23f8ba2ca22748a51bd75ee2c575564f2cbc59f119640e7f5b4c5d F src/pcache.c f4268f7f73c6a3db12ce22fd25bc68dc42315d19599414ab1207d7cf32f79197 F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache1.c dee95e3cd2b61e6512dc814c5ab76d5eb36f0bfc9441dbb4260fccc0d12bbddc -F src/pragma.c 894c2621d35edd4beea9b331cfdb1b42032394420074d2294c8febe548eea8a1 +F src/pragma.c 82d5090a35eac75876d3b41f48d06b2370553c9576bf2942233c462e03eb94c9 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c 1b02be0441eda4579471fea097f678effcbb77ef0c39ab3f703c837822bcd674 -F src/printf.c e99ee9741e79ae3873458146f59644276657340385ade4e76a5f5d1c25793764 +F src/prepare.c ce87a08cfddd45a147150db34190b1986f2d4a0e0828858cb6bd908c78fb02e3 +F src/printf.c ff4b05e38bf928ff1b80d3dda4f977b10fe39ecbfe69c018224c7e5594fb2455 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633 +F src/resolve.c d62c5665279cc7485f9d45b5e20911cc7b19c203f268321a90d05d74f4725750 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 9886d6669f5787471aab6ae52af76fad90b53edb1c218fc9ed9d953363bc5184 -F src/shell.c.in f2736e84caf751e94477776df8ab0e4ebc8311de00594d997abd81533debc85c -F src/sqlite.h.in b729158b4c018117fe318f0be60a8c0da10e53cb2153f8349d05fde7ceb0d15b +F src/select.c d44e78e103cb52b3bdf8d0b7ac2518b7a4cbaf12237bd9078f6897d230d2516e +F src/shell.c.in 6f36f5ca05f1bebf74935a7fcf2dce983016e807a09cbd752a673583ad7da087 +F src/sqlite.h.in 662a2fa083d093896b92560c871dea6d86792b49dc4bf7b4e8dbeca8e7171488 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f -F src/sqliteInt.h 2c24ba38f78e32fe5d7ec136321a6ad827698b33ca98664970a8b7274d69ef7c +F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 +F src/sqliteInt.h 4dcc36b21dd0c77efeb0bb38b0d31916bc63fb429c8a89d186c945eba0816447 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 4e64ba300a5a26e0f1170e09032429faeb65e45e8f3d1a7833e8edb69fc2979e -F src/test1.c 40c9a40975512985762f87b83d0c63e4904833a9fe78cbcca664a37095301b1d +F src/tclsqlite.c 8522a04fb9c84faa1d80354430ae0ee9349727a3a4b32e3cfe39b9be8324cabd +F src/test1.c 29ab8aca939818a61bf791e4ff5d45df6da30940c0980dcf502562b925de4e05 F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d F src/test6.c ae73a3a42bbc982fb9e301b84d30bda65a307be48c6dff20aba1461e17a9b0ce -F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010 F src/test8.c 0c856d6ff6b0d2ff6696addc467a15ed17c6910f14475302cd5b3b4e54406161 F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 -F src/test_bestindex.c 8294d8223b7f18a3ddb7f9a0e30815dcca4e61681f78b538c870f7d934f88b81 +F src/test_bestindex.c 68c62586d2ae9f032903fe53be743657d0c2aac0a850b880938b668e1161d516 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c 8264637b06a3c1f0727c88d1ea32dcf7986b9e7e358a970cae87cdac8a5b2708 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f -F src/test_demovfs.c 7cc7623d1025d1e92c51da20fd25060759733b7a356a121545a3b7d2faa8a0f1 +F src/test_demovfs.c 38a459d1c78fd9afa770445b224c485e079018d6ac07332ff9bd07b54d2b8ce9 F src/test_devsym.c aff2255ea290d7718da08af30cdf18e470ff7325a5eff63e0057b1496ed66593 F src/test_fs.c ba1e1dc18fd3159fdba0b9c4256f14032159785320dfbd6776eb9973cb75d480 F src/test_func.c 24df3a346c012b1fc9e1001d346db6054deb426db0a7437e92490630e71c9b0a @@ -686,15 +669,14 @@ F src/test_quota.c 6cb9297115b551f433a9ad1741817a9831abed99 F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b -F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939 -F src/test_tclsh.c 7dd98be675a1dc0d1fd302b8247bab992c909db384df054381a2279ad76f9b0e +F src/test_tclsh.c 3ff5d188a72f00807425954ea3b493dfd3a4b890ecc6700ea83bad2fd1332ecf F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc -F src/test_thread.c 269ea9e1fa5828dba550eb26f619aa18aedbc29fd92f8a5f6b93521fbb74a61c +F src/test_thread.c 7ddcf0c8b79fa3c1d172f82f322302c963d923cdb503c6171f3c8081586d0b01 F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43 -F src/test_vfs.c 2cc38a79892017702d13da79ad5152c196eec19bbd67fbde4d88065aac894a84 +F src/test_vfs.c 193c18da3dbf62a0e33ae7a240bbef938a50846672ee947664512b77d853fe81 F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698 F src/test_windirent.c a895e2c068a06644eef91a7f0a32182445a893b9a0f33d0cdb4283dca2486ac1 F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a90484215 @@ -702,36 +684,36 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 1305797eab3542a0896b552c6e7669c972c1468e11e92b370533c1f37a37082b -F src/treeview.c 07787f67cd297a6d09d04b8d70c06769c60c9c1d9080378f93929c16f8fd3298 +F src/treeview.c fccf3b8c517c1f55cb380c1522febe6921fcb2bd800c16c78cab571d0eb0ccbd F src/trigger.c 5e68b790f022b8dafbfb0eb244786512a95c9575fc198719d2557d73e5795858 -F src/update.c 5b0302c47cf31b533d5dff04c497ca1d8b9d89c39727e633fbe7b882fd5ac5aa +F src/update.c f118e51768d2c1309e3c81e9f91141b22b8a1339cbc5969b1b2d810feaa25b22 F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c 0be191521ff6d2805995f4910f0b6231b42843678b2efdc1abecaf39929a673f +F src/util.c 3ff7bc2b48dd425b1448304bb86273b05da1621f136d51dbb9789f8803559a1f F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd -F src/vdbe.c 0c7cb1b934ad8611e14e7efaf2c3a95df7dd3f7964d63ea07fef42a23df86131 -F src/vdbe.h 58675f47dcf3105bab182c3ad3726efd60ffd003e954386904ac9107d0d2b743 -F src/vdbeInt.h 17b7461ffcf9ee760d1341731715a419f6b8c763089a7ece25c2e8098d702b3f -F src/vdbeapi.c 1e8713d0b653acb43cd1bdf579c40e005c4844ea90f414f065946a83db3c27fb -F src/vdbeaux.c 8ebe337e82d99cf3b01cd4fd67103a5b0054d53fee8456b90dbeba46ebf97ceb +F src/vdbe.c 0bf4804f6e9cae872502aa4533a62ba3f6e97ee28523302deb27f9c990b5f44f +F src/vdbe.h 73b904a6b3bb27f308c6cc287a5751ebc7f1f89456be0ed068a12b92844c6e8c +F src/vdbeInt.h a4147a4ddf613cb1bcb555ace9e9e74a9c099d65facd88155f191b1fb4d74cfb +F src/vdbeapi.c 40c47b1528d308a322203de21d2e0d711753257ed9771771b6129214b1d65932 +F src/vdbeaux.c 3f9e3b6585e7434aa11300169dd66ddf0fc963a0c6f7940bdc058335dadeb353 F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd -F src/vdbemem.c 6cfed43758d57b6e3b99d9cdedfeccd86e45a07e427b22d8487cbdbebb6c522a +F src/vdbemem.c 0388576b7cf0be13ce14b9e3b8aa90b8a1b923b60321d0242131ae0b5732b43b F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 -F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c -F src/vtab.c b2f993aa954078985bc40317bb2140fe0880a08a7440f3a428b60fce74636808 +F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac +F src/vtab.c a39f6ed161f16a84ff445af9dba4776b2cf0898be08a33bee8e1128a66c0074b F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c 42f0433beedec9ba0ac0945732e1a8c10b5081f1e82caf4507ac493d79f552af -F src/whereInt.h df0c79388c0b71b4a91f480d02791679fe0345d40410435c541c8893e95a4d3f -F src/wherecode.c 133a94f82858787217d073143617df19e4a6a7d0b771a1519f957608109ad5a5 -F src/whereexpr.c 05295b44b54eea76d1ba766f0908928d0e20e990c249344c9521454d3d09c7ae -F src/window.c 928e215840e2f2d9a2746e018c9643ef42c66c4ab6630ef0df7fa388fa145e86 +F src/where.c 38c36bb654b4172148aef3b295bbbc1342b52078cf20174f771c746caa98f69a +F src/whereInt.h e25203e5bfee149f5f1225ae0166cfb4f1e65490c998a024249e98bb0647377c +F src/wherecode.c b82d0d33315e1526904b95155e55e61149c4462147668e1cc4567c812735eff1 +F src/whereexpr.c a60a1f88650c561eded95c5affbe846cda71fac35033e1280f28e47934df33c8 +F src/window.c 76a27cff9ea2ded0c2c3527187029259440fabcc4cc4c07b11d942c78494a614 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 -F test/affinity3.test b5c19d504dec222c0dc66642673d23dce915d35737b68e74d9f237b80493eb53 +F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggnested.test 7269d07ac879fce161cb26c8fabe65cba5715742fac8a1fccac570dcdaf28f00 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 @@ -781,11 +763,11 @@ F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c F test/attach.test 54f8e49e88d0de48f6428267a678465863d2b8f72320612f35bd5c02e240bc2f -F test/attach2.test 256bd240da1835fb8408dd59fb7ef71f8358c7a756c46662434d11d07ba3a0ce +F test/attach2.test 6d1e3a457ce260d6fc8e5945c07fba6c76dc2aa90e1c701f067b50ee88f7315a F test/attach3.test c59d92791070c59272e00183b7353eeb94915976 F test/attach4.test 00e754484859998d124d144de6d114d920f2ed6ca2f961e6a7f4183c714f885e F test/attachmalloc.test 67309af95c6b765c13e7d2279d7fccbef78e6eb0565d75d51cefd5dc88784549 -F test/auth.test 0f246deec5cb2f6f893f8fbb76628f182c08fe40f178b254dd72467ca012f657 +F test/auth.test 5b8558a40571ebc55c1581cb7cec3b2348a699542a0a51b83ef21c6a953d95e3 F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec @@ -809,6 +791,7 @@ F test/backup_ioerr.test 4c3c7147cee85b024ecf6e150e090c32fdbb5135 F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399 +F test/basexx1.test d8a50f0744b93dca656625597bcd3499ff4b9a4ea2a82432b119b7d46e3e0c08 F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c F test/bestindex1.test 856a453dff8c68b4568601eed5a8b5e20b4763af9229f3947c215729ed878db0 F test/bestindex2.test 394ff8fbf34703391247116d6a44e1c50ee7282236ee77909044573cefc37bc0 @@ -819,6 +802,7 @@ F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e F test/bestindex8.test 333ad8c6a554b885a49b68c019166eda92b05f493a92b36b0acdf7f766d04dad F test/bestindex9.test bf2eb8556e8d5c00ef3ee18c521751cd03c1b55454b6e7683b4c6742e3131b23 +F test/bestindexA.test dd7b7439a46169b45d0305c4cbbb14fc20c7044acc2055c767d2f838b3479c3f F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -830,7 +814,7 @@ F test/bind2.test 918bc35135f4141809ead7585909cde57d44db90a7a62aef540127148f91aa F test/bindxfer.test efecd12c580c14df5f4ad3b3e83c667744a4f7e0 F test/bitvec.test 75894a880520164d73b1305c1c3f96882615e142 F test/blob.test e7ac6c7d3a985cc4678c64f325292529a69ae252 -F test/bloom1.test 5318eb0648dff073ca9b9a54387ec2c0a7a07ed3490461fe2db0d074b2eb0e7f +F test/bloom1.test 2785a190fcc2a5e170e5d38b08aca8ff0f3e3b4a74d47453d6ac1bd355180a6a F test/boundary1.tcl 6421b2d920d8b09539503a8673339d32f7609eb1 F test/boundary1.test 66d7f4706ccdb42d58eafdb081de07b0eb42d77b F test/boundary2.tcl e34ef4e930cf1083150d4d2c603e146bd3b76bcb @@ -850,11 +834,11 @@ F test/cachespill.test 895997f84a25b323b166aecb69baab2d6380ea98f9e0bcc688c4493c5 F test/capi2.test 4ee545824adc3eb33bf57ef89f77440b28188ec3da72e5425ff0fcdba32e8d5a F test/capi3.test 3910a73c38ac76d69778dd9eb481ab7cd6ed59117fc047b4f6056a5c72529de1 F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4 -F test/capi3c.test 54e2dc0c8fd7c34ad1590d1be6864397da2438c95a9f5aee2f8fbc60c112e44b +F test/capi3c.test 31d3a6778f2d06f2d9222bd7660c41a516d1518a059b069e96ebbeadb5a490f7 F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe -F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212a2634 -F test/cast.test 6064022ba9af31a8a2ff7bb345e5bd0e74172ffad85bdab5898a42d8227c7585 +F test/carray01.test 23ed7074307c4a829ba5ff2970993a9d87db7c5cdbbe1a2cbef672d0df6d6e31 +F test/cast.test af2286fdd28f3470b7dcad23977282b8cc117747ad55acff74a770dad3b19398 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9 @@ -878,7 +862,7 @@ F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151eca F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1 F test/colname.test 87ad5458bb8709312dac0d6755fd30e8e4ca83298d0a9ef6e5c24277a3c3390e F test/columncount.test 6fe99c2f35738b0129357a1cf3fa483f76140f4cd8a89014c88c33c876d2638f -F test/conflict.test ac0667090f66130ac77d5fb764655558ca6600dd6d88f670ca9123b61c448337 +F test/conflict.test b705cddf025a675d3c13d62fa78ab1e2696fb8e07a3d7cccce1596ff8b301492 F test/conflict2.test 5557909ce683b1073982f5d1b61dfb1d41e369533bfdaf003180c5bc87282dd1 F test/conflict3.test 81865d9599609aca394fb3b9cd5f561d4729ea5b176bece3644f6ecb540f88ac F test/contrib01.test 2a1cbc0f2f48955d7d073f725765da6fbceda6b4 @@ -935,7 +919,7 @@ F test/dbfuzz001.test 55e1a3504f8dea84155e09912fe3b1c3ad77e0b1a938ec42ca03b8e51b F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23 F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e887d -F test/dbpagefault.test b893e9e43d55edc0cdf3f71ae093802f80f0fb517d839eefeae5647c49a41f36 +F test/dbpagefault.test d9111a62f3601d3efc6841ace3940181937342d245f92a1cca6cba8206d4f58a F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test fcf403fd5585f47342234e153c4a4338cd737b8e0884ac66fc484df47dbcf1a7 @@ -951,17 +935,17 @@ F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a199 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e F test/distinct.test a7687c2fb50c93f6a486936c51439a93221c6e1188f9bc7b27b3ec26f9c58b1e F test/distinct2.test cd1d15a4a2abf579298f7161e821ed50c0119136fe0424db85c52cf0adc230d1 -F test/distinctagg.test d76ef2e91fe810630c176d6bd0a58c14d5851c3125f0a1d977db87ba76359639 +F test/distinctagg.test 14ec5026e684eddd414c61c08692b43773e224ac92efbed6ec08c6994bc39723 F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075 F test/e_blobopen.test 29f6055ee453b8e679fe9570c4d3acfedbef821622c5dad16875148c5952ef50 F test/e_blobwrite.test 3075ff539827576d9a34cbb5a2ac75eb65fb49cd5aadc27686b0719fbf99c156 F test/e_changes.test 0f8c3e6aab7335cb772d5a3ea34ca4c82f98d0eb896e2eb3add971c16984b405 -F test/e_createtable.test e3b9782e80c0cf2898ac0eb1d9cd058412955ff53a8e47307a60c8289d62ff9c +F test/e_createtable.test 31b9bcb6ac8876bc7ec342d86d9c231a84c62b442093a6651dfd0fa93650eea3 F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7 -F test/e_expr.test bc6aa5906ba967587525bc746ea403011557cf6d8e4fc9efb1fab5dae7fb4fd6 +F test/e_expr.test 27e905ed17266c745bffe65f56b809c13ae6e225e56aeda1aaec926b32439286 F test/e_fkey.test feeba6238aeff9d809fb6236b351da8df4ae9bda89e088e54526b31a0cbfeec5 F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e @@ -982,7 +966,7 @@ F test/enc.test 9a7be5479da985381d740b15f432800f65e2c87029ee57a318f42cb2eb43763a F test/enc2.test 848bf05f15b011719f478dddb7b5e9aea35e39e457493cba4c4eef75d849a5ec F test/enc3.test 55ef64416d72975c66167310a51dc9fc544ba3ae4858b8d5ab22f4cb6500b087 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test 473aea9599b4b7af46614b55198cd78167e4eccd48e60812a40db47c5c41dea9 +F test/eqp.test f3f7548d2f3df03e2f23ecaf35c7c2cc7b89848bd7c3606d94a010521b7ea4f6 F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/eval.test 73969a2d43a511bf44080c44485a8c4d796b6a4f038d19e491867081155692c0 F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650bf0747 @@ -991,14 +975,14 @@ F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 -F test/exprfault.test 497cc0b8fe6a677f49b55cb485e040f709ec2834b84f25912fe9c2dfeeda33db +F test/exprfault.test da33606d799718e2f8e34efd0e5858884a1ad87f608774c552a7f5517cc27181 F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9 F test/external_reader.test c7d34694f1b25c32d866f56ac80c1e29edddc42b4ef90cad589263ffac2cde0c F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 F test/fallocate.test 37a62e396a68eeede8f8d2ecf23573a80faceb630788d314d0a073d862616717 F test/filectrl.test 6e871c2d35dead1d9a88e176e8d2ca094fec6bb3 F test/filefmt.test f393e80c4b8d493b7a7f8f3809a8425bbf4292af1f5140f01cb1427798a2bbd4 -F test/filter1.test 6c483ecf7886c8843a8612c021aa23f33c581f584151f251842b3a3592c95ac8 +F test/filter1.test 590f8ba9a0cd0823b80d89ac75c5ce72276189cef9225d2436adaf1ee87f3727 F test/filter2.tcl 44e525497ce07382915f01bd29ffd0fa49dab3adb87253b5e5103ba8f93393e8 F test/filter2.test 485cf95d1f6d6ceee5632201ca52a71868599836f430cdee42e5f7f14666e30a F test/filterfault.test c08fb491d698e8df6c122c98f7db1c65ffcfcad2c1ab0e07fa8a5be1b34eaa8b @@ -1082,7 +1066,7 @@ F test/fts3corrupt6.test f417c910254f32c0bc9ead7affa991a1d5aec35b3b32a183ffb05ee F test/fts3cov.test 7eacdbefd756cfa4dc2241974e3db2834e9b372ca215880e00032222f32194cf F test/fts3d.test 2bd8c97bcb9975f2334147173b4872505b6a41359a4f9068960a36afe07a679f F test/fts3defer.test f4c20e4c7153d20a98ee49ee5f3faef624fefc9a067f8d8d629db380c4d9f1de -F test/fts3defer2.test 3da52ca2114e300e9971eee2f0cc1a2e5f27e6a9ee67957d49e63e41fdfcc0e7 +F test/fts3defer2.test 3bbe54a7fca7d548bb7ac4f59447ee591070bfbe0c9f3e279defa0b898e9afbb F test/fts3defer3.test dd53fc13223c6d8264a98244e9b19abd35ed71cd F test/fts3drop.test 1b906e293d6773812587b3dc458cb9e8f3f0c297 F test/fts3dropmod.test 7de242ea1c8a713a8b143ea54468f4b1c4953fa068349e23ac178e2c90c59889 @@ -1090,7 +1074,7 @@ F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851 F test/fts3expr.test ebae205a7a89446c32583bcd492dcb817b9f6b31819bb4dde2583bb99c77e526 F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a F test/fts3expr3.test c4d4a7d6327418428c96e0a3a1137c251b8dfbf8 -F test/fts3expr4.test f5b2832549f01b1f7f73389fa21d4b875499bc95bf7c8b36271844888c6a0938 +F test/fts3expr4.test 6c7675bbdbffe6ffc95e9e861500b8ac3f739c4d004ffda812f138eeb1b45529 F test/fts3expr5.test a5b9a053becbdb8e973fbf4d6d3abaabeb42d511d1848bd57931f3e0a1cf983e F test/fts3f.test 8c438d5e1cab526b0021988fb1dc70cf3597b006a33ffd6c955ee89929077fe3 F test/fts3fault.test f4e1342acfe6d216a001490e8cd52afac1f9ffe4a11bbcdcb296129a45c5df45 @@ -1154,7 +1138,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 30475c820dc5ab8a87fa3be1fe8ba8199ebfe2544508a759d653688d8d168766 +F test/fuzzcheck.c 0b8543d29c6f529cb73937607c8649f685510838e40c6287f06df083433c93b6 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1162,23 +1146,23 @@ F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e4 F test/fuzzdata5.db e35f64af17ec48926481cfaf3b3855e436bd40d1cfe2d59a9474cb4b748a52a5 F test/fuzzdata6.db 92a80e4afc172c24f662a10a612d188fb272de4a9bd19e017927c95f737de6d7 F test/fuzzdata7.db 0166b56fd7a6b9636a1d60ef0a060f86ddaecf99400a666bb6e5bbd7199ad1f2 -F test/fuzzdata8.db 653423800b7671e67caa740e977d80e1360f0d69e9992851f3ea5c4a69a2724a +F test/fuzzdata8.db f6c2f2af4deaaae0ddb3310d509c2659990794aa653dc501b80a0534c3493f80 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc -F test/fuzzinvariants.c 7877178eaa10eb3ea986f81a7010efc371ccd3e13ee5b14fa290b0459002a36a +F test/fuzzinvariants.c a153253600b2b33a7d5710d40e89b2ac1373a1912517867fb995a45b2d67dcb8 F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c F test/gencol1.test cc0dbb0ee116e5602e18ea7d47f2a0f76b26e09a823b7c36ef254370c2b0f3c1 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 F test/having.test a89236dd8d55aa50c4805f82ac9daf64d477a44d712d8209c118978d0ca21ec9 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711 -F test/hook.test bb9e03b93fa4bc3bf0f164e455ddaee3603dee0501cb61119baed557c8f936be +F test/hook.test 18cae9140fa7f9a6f346e892a3fe3e31b2ca0be1494cd01b918adb74281016a6 F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 F test/icu.test 716a6b89fbabe5cc63e0cd4c260befb08fd7b9d761f04d43669233292f0753b1 F test/ieee754.test b0945d12be7d255f3dfa18e2511b17ca37e0edd2b803231c52d05b86c04ab26e F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 -F test/in.test 55503d3633c434120d8b2d5966a34f0d9a55393a589050b1366afe4b188093c7 +F test/in.test 7399a6562fb09de7f786901f0b24b7bbe7c0b541f29ead9eb13f091edea37da4 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 F test/in4.test fdd1d8134da8376985c2edba6035a2de1f6c731524d2ffa651419e8fe2cd1c5a @@ -1205,7 +1189,7 @@ F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6 F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3 F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 -F test/indexexpr1.test 3360c2a29a8844e7c4b13293567025281257f9e13a31854cfff6959cede11502 +F test/indexexpr1.test b2a15637dcbae7fd8d7e2fc51f74ac4feaf5510130ee2089a5ec5bd1ef7270e1 F test/indexexpr2.test 2c7abe3c48f8aaa5a448615ab4d13df3662185d28419c00999670834a3f0b484 F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 @@ -1218,10 +1202,10 @@ F test/insertfault.test ac63d14ea3b49c573673a572f4014b9117383a03e497c58f308b5c77 F test/instr.test 107df2b9b74a4b59315916b575590a08f2a714de0754abe541f10a0971d0a2a4 F test/instrfault.test 95e28efade652e6d51ae11b377088fe523a581a07ec428009e152a4dd0e0f44c F test/intarray.test bb976b0b3df0ebb6a2eddfb61768280440e672beba5460ed49679ea984ccf440 -F test/interrupt.test 16ea879ec728cb76414c148c5f24afd5d1f91054 +F test/interrupt.test ac1ef50ec9ab8e4f0e17c47629f82539d4b22558904e321ed5abea2e6187da7a F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d F test/intpkey.test aee694afed1a65c86c4e69ad030224b3fc268113d00685234d40079fca16bad3 -F test/intreal.test 2a87e85a5949bd55e41ef04c58f5800587c5380bdbc559ff1c79b614b0e6a533 +F test/intreal.test 68829a8bb073ee1610ca3f8f9e0f99b0371fb36e0fa64862dd5ced4ef03c2343 F test/io.test f138f3fe696d1ed8c51dfea5b325910d319a1b29e1d25ea57231a02092f02cca F test/ioerr.test 530d05801ff1b6327018b2e140da34a74effa2773a844ddb8dc79c32e9567318 F test/ioerr2.test 2593563599e2cc6b6b4fcf5878b177bdd5d8df26 @@ -1234,10 +1218,10 @@ F test/join.test e32cb9b1491eed682489e2cde33a22a4eb7611fe5aa3b0aa4b275fe27ab3f3a F test/join2.test 466b07233820f5deee66a6c3bf6e4500c8bbf7b83649e67606f5f649c07928c0 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 -F test/join5.test d22b6cba8fb59ab3f1c82701434c360705eb12d4ce200c449f37b018fc47681a +F test/join5.test 91f1f4c7d81fd87b58e9ba7cf4a2b5d39e3583b4f8e498a162722a60259c5208 F test/join6.test f809c025fa253f9e150c0e9afd4cef8813257bceeb6f46e04041228c9403cc2c F test/join7.test 2268dcbb54b724391dda3748ea95c60d960607ffeed67885675998e7117697f6 -F test/join8.test ef5fb09a7ce6b59addb8bd16e11607db6c44a0afcac5774a5dc03193fd0a1df5 +F test/join8.test 40bdf5612444e986187edc5fd5ea9094cb7975b78cac563a97f1f7aefde34ba6 F test/join9.test 9056ddd3b0c0f4f9d658f4521038d9a37dc23ead8ca9a505d0b0db2b6a471e05 F test/joinA.test 7eab225dc1c1ab258a5e62513a4ed7cabbd3db971d59d5d92f4fb6fa14c12f6a F test/joinB.test 1b2ba3fc8568b49411787fccbf540570c148e9b6a53a30f80691cb6268098ded @@ -1308,17 +1292,18 @@ F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7 F test/memdb1.test 2c4e9cc10d21c6bf4e217d72b7f6b8ba9b2605971bb2c5e6df76018e189f98f5 +F test/memdb2.test 7789975b96b0726032a22c1afc026592c7ff175bf05be11f1d640791541a77ea F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c F test/memjournal2.test 6b9083cfaab9a3281ec545c3da2487999e8025fb7501bbae10f713f80c56454c F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 F test/memsubsys1.test 86b8158752af9188ed5b32a30674a1ef71183e6bc4e6808e815cd658ca9058a6 -F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08 +F test/memsubsys2.test 774b93cb09ca50d1b759bb7c645baa2a9ce172edc3a3da67d5150a26a9fc2a08 F test/merge1.test 2de6d6ef8d25402764b1aab49d8f9d7f89208c89a6674e437f76de4c812157b8 F test/minmax.test fe638b55d77d2375531a8f549b338eafcd9adfbd2f72df37ed77d9b26ca0a71a F test/minmax2.test cf9311babb6f0518d04e42fd6a42c619531c4309a9dd790a2c4e9b3bc595e0de F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 F test/minmax4.test 272ca395257f05937dc96441c9dde4bc9fbf116a8d4fa02baeb0d13d50e36c87 -F test/misc1.test 294c97185354030c4ce40e7141b72f7a589585f2a44b666825381eb3df98f07c +F test/misc1.test 9955e70cab5e284d77e358cfa1f1dd43f5e4bc00a421018581b0fbd62206a6a1 F test/misc2.test 71e746af479119386ac2ed7ab7d81d99970e75b49ffd3e8efffee100b4b5f350 F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d F test/misc4.test 10cd6addb2fa9093df4751a1b92b50440175dd5468a6ec84d0386e78f087db0e @@ -1336,9 +1321,9 @@ F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a -F test/multiplex3.test d228f59eac91839a977eac19f21d053f03e4d101 +F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7 F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4 -F test/mutex1.test 177db2e4edb530f2ff21edc52ac79a412dbe63e4c47c3ae9504d3fb4f1ce81fa +F test/mutex1.test 4d7ecb155ae5ba9ca6f1817c1c74d51b5bb284d7f599de1859a9f2c9a1ca0d38 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test 437d40e6d0778b050d7750726c0cbd2c9936b81962926e8f8c48ca698f00f4d1 F test/nockpt.test 8c43b25af63b0bd620cf1b003529e37b6f1dc53bd22690e96a1bd73f78dde53a @@ -1346,7 +1331,7 @@ F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e F test/normalize.test f23b6c5926c59548635fcf39678ac613e726121e073dd902a3062fbb83903b72 F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 -F test/notify3.test 10ff25cde502e72a92053a2f215d64bece4ef934 +F test/notify3.test 796c7b7157f55c93b4e672b724e9c923a6fc6aa72ac419379a623e2350472e22 F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18 F test/notnull2.test 8e1aa4f311df37f9d2cd4f5563eea955b8028c692151404e9cc896420a01a3dc F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5 @@ -1359,7 +1344,7 @@ F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394 F test/optfuzz-db01.c 9f2fa80b8f84ebbf1f2e8b13421a4e0477fe300f6686fbd76cac1d2db66e0fdc F test/optfuzz-db01.txt 21f6bdeadc701cf11528276e2a55c70bfcb846ba42df327f979bd9e7b6ce7041 F test/optfuzz.c 690430a0bf0ad047d5a168bf52b05b2ee97aedaad8c14337e9eb5050faa64994 -F test/orderby1.test a4bba04b9c60a21e53486fbc173a596b29641a3b3a57a0f26a1cbef1360358e9 +F test/orderby1.test 02cfd870127a7342170b829175c5c53e9e7405744451ac1aeb2f7e2b0c18ca76 F test/orderby2.test bc11009f7cd99d96b1b11e57b199b00633eb5b04 F test/orderby3.test 8619d06a3debdcd80a27c0fdea5c40b468854b99 F test/orderby4.test 4d39bfbaaa3ae64d026ca2ff166353d2edca4ba4 @@ -1374,7 +1359,7 @@ F test/ossfuzz.c 9636dad2092a05a32110df0ca06713038dd0c43dd89a77dabe4b8b0d7109671 F test/ossshell.c f125c5bd16e537a2549aa579b328dd1c59905e7ab1338dfc210e755bb7b69f17 F test/ovfl.test 199c482696defceacee8c8e0e0ef36da62726b2f F test/pager1.test ffd885cdc98b986c9f746496508c0c4810ed0eaade3575ddf53c222e85880552 -F test/pager2.test 67b8f40ae98112bcdba1f2b2d03ea83266418c71 +F test/pager2.test 57ce815e31a7509fcdf7c5474577fd2e9cfee1281d45601e0f7a3bd5534d70a4 F test/pager3.test 4e9a83d6ca0838d7c602c9eb93d1357562d9059c1e02ffb138a8271020838370 F test/pager4.test a122e9e6925d5b23b31e3dfef8c6a44bbf19590e F test/pagerfault.test 63c5da625562c66345ab4528790327ca63db2f6f9cbae2aba8cb7c51de3d1628 @@ -1385,10 +1370,11 @@ F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305 F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3 F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 +F test/pendingrace.test cbdf0f74bc939fb43cebad64dda7a0b5a3941a10b7e9cc2b596ff3e423a18156 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 3e0d6eea70e5087f3240b1a2fe621b0c73445f38a262029f0a1d2d89564026f7 +F test/permutations.test 8bd6b6db541e2a7f9bb894be99ef5c00526b23762c4a00c574e1cba697495125 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f -F test/pragma.test 620622fb0815f1cbea8e26e1d8abad38e0cbcbed8927fd84048fe9fd6239e323 +F test/pragma.test aeefa47ba5ebbf4ffc6addc223568d2a95dc7ec3ba3e3c4a26b199e805f94514 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31 F test/pragma4.test ca5e4dfc46adfe490f75d73734f70349d95a199e6510973899e502eef2c8b1f8 @@ -1399,9 +1385,10 @@ F test/printf.test 390d0d7fcffc3c4ea3c1bb4cbb267444e32b33b048ae21895f23a291844fe F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224cce60 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc -F test/pushdown.test 5e72c51c5e33253ed639ccee1e01ce62d62b6eee5ca893cd82334e4ee7b1d7fc +F test/pushdown.test f270b8071c02efc218430e0d388c155e1962eaa1d3a3ab186dd38ad6d7e178a4 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 +F test/quickcheck.test f86b25b33455af0189b4d3fe7bd6e553115e80b2d7ec9bbe9a6b37fce0881bfe F test/quota-glob.test 32901e9eed6705d68ca3faee2a06b73b57cb3c26 F test/quota.test bfb269ce81ea52f593f9648316cd5013d766dd2a F test/quota2.test 7dc12e08b11cbc4c16c9ba2aa2e040ea8d8ab4b8 @@ -1414,10 +1401,10 @@ F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0 F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest_data.tcl 0db8aee0c348090fd06da47020ab4ed8ec692e0723427b2f3947d4dfb806f3b0 -F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b +F test/releasetest_data.tcl b550dd1b122a9c969df794d05ea272df535f10ff1a245062e7ba080822378016 +F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test 1366e04566cfe1a082d17b1e0f195ec64473c79374b3a5d4ae00c43d885dea31 +F test/returning1.test 3ef7b264598b3292be0cdb028e4acb7524c5fd409b33b78449f894dfd68db334 F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f @@ -1434,7 +1421,7 @@ F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 F test/rowvalue7.test c1cbdbf407029db01f87764097c6ac02a1c5a37efd2776eff32a9cdfdf6f2dba F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 -F test/rowvalue9.test cb5380df82dca9db463081e26952c1e097b34fc2c2ac87453970c048d97df687 +F test/rowvalue9.test 138252b53b835208a5712e01595403a0ae32b4bc58284d9fe6bea10e58203fe4 F test/rowvalueA.test 51f79b6098c193f838168752c9640f4eae6c63346bf64b5bed4f4e22fe2c71d0 F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972d511c54fff F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f @@ -1447,7 +1434,8 @@ F test/savepoint5.test 0735db177e0ebbaedc39812c8d065075d563c4fd F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7 F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 -F test/scanstatus.test 9a0ed37ab6d57b50567282788fffdf832d9b16739ecc41bff9d77a8d767cf317 +F test/scanstatus.test 74391c2c0926994bf0962db6c04c9ff5b95d15a41d2e076fe011b73f92815e70 +F test/scanstatus2.test ca6c258425977e5935f0ff3a8670d9b5d813dd5b83cf0bbe39acfc0fd2b5a0b1 F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce @@ -1474,10 +1462,9 @@ F test/selectD.test 6d1909b49970bf92f45ce657505befcef5fc7cbc13544e18103a316d3218 F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3 F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840 -F test/server1.test c2b00864514a68a0e6fd518659dc95d0050307a357a08969872bef027d785dc4 F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb -F test/sessionfuzz.c f74c4e806bab5a093fb9c11b6123d17a6e0cf73fb7a0f49b12f5a75bf0b7b1a8 +F test/sessionfuzz.c 5eef09af01eeff6f20250ae4c0112c2e576e4d2f2026cc9a49dc5be6886fa6ee F test/shared.test f022874d9d299fe913529dc10f52ad5a386e4e7ff709270b9b1111b3a0f3420a F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879 F test/shared3.test f8cd07c1a2b7cdb315c01671a0b2f8e3830b11ef31da6baa9a9cd8da88965403 @@ -1490,10 +1477,10 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test e4b4de56f454708e0747b52915135baa2cbfec4965406d6eaf02a4a5c22a9880 -F test/shell2.test 1190b951373fdfe719bc6ac16962bc743dfa4355db8ae546c0bb9bf559a28d4a +F test/shell1.test ab88e763854ea4734796067e20e258589b4d5bb9e77d70d8c4c8e99cf77c8b64 +F test/shell2.test 35c0c19d3198ee7669a748c1aedcce27a776ee575cc76128f8fcf665b79672f7 F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a -F test/shell4.test 7dc8a515705bc093d8ffe381670e8fa7a969661e8ed177c35c847e3c6dfc35e2 +F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f @@ -1501,7 +1488,7 @@ F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54 F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 -F test/shrink.test 9521e5e0d74c0b6192794f3de3a3e5e3190d465527ae365d96763ef753c7229c +F test/shrink.test 2668e607dcdfa19c52828c09b69685b38da793856582ae31debf79d90c7bbbdc F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329 F test/skipscan1.test 1a9972e1dc15ca3887f306d3cd9a29679afb382eca0f3539f3b746f3c2ccaf68 F test/skipscan2.test b032ed3e0ba5caa4df6c43ef22c31566aac67783bc031869155989a7ccdb5bd5 @@ -1516,16 +1503,16 @@ F test/snapshot_fault.test f6c5ef7cb93bf92fbb4e864ecc5c87df7d3a250064838822db5b4 F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a393cdd41c F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 -F test/sort.test c2adc635c2564241fefec0b3a68391ef6868fd3b +F test/sort.test f86751134159abb5e5fd4381a0d7038c91013638cd1e3fa1d7850901f6df6196 F test/sort2.test cc23b7c19d684657559e8a55b02f7fcee03851d0 F test/sort3.test 1480ed7c4c157682542224e05e3b75faf4a149e5 -F test/sort4.test 5c34d9623a4ae5921d956dfa2b70e77ed0fc6e5c +F test/sort4.test cca6f4b0b5255882645bbbe346a6a9f4a5c7b6a18513a6a7bf4ac1c4761ddc19 F test/sort5.test 6b43ae0e2169b5ceed441844492e55ba7f1ae0790528395ddf7888ab3094525d F test/sorterref.test 9a606c86a4c682db5eeaaefa0565b52102778db53e48ca7101cd4f9ebcc0ad94 F test/sortfault.test d4ccf606a0c77498e2beb542764fd9394acb4d66 -F test/speed1.test f2974a91d79f58507ada01864c0e323093065452 +F test/speed1.test 0381cfd05e5e7ccfd5eb570976f9075c67ab3e34991a1addf80663b184395219 F test/speed1p.explain d841e650a04728b39e6740296b852dccdca9b2cb -F test/speed1p.test b180e98609c7677382cf618c0ec9b69f789033a8 +F test/speed1p.test 7191cec2aaf8876317bec58cf9c0f3750ab8b9bc23fc8a4000b77da578c7aadc F test/speed2.test 53177056baf6556dcbdcf032bbdfc41c1aa74ded F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 @@ -1537,7 +1524,7 @@ F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb F test/sqldiff1.test 182058e09c7082de5c6a470ff9c291337bbeb650052c2cc68fbb3d7e25861d91 -F test/sqllimits1.test 69d110987dbdb4bea9dbc3f151c93b4697ae6cd7e6c3a519119dc1140c8607fd +F test/sqllimits1.test b28e5cc8d337aaf290614d96a47e8fbfb720bb7ad35620c9d5432996fd413ac4 F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805 F test/stat.test 123212a20ceb496893d5254a5f6c76442ce549fdc08d1702d8288a2bbaac8408 @@ -1557,7 +1544,7 @@ F test/swarmvtab.test 250231404fcac88f61a6c147bb0e3a118ed879278cd3ccb0ae2d3a729e F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27cbd0aa5c F test/swarmvtab3.test 41a3ab47cb7a834d4e5336425103b617410a67bb95d335ef536f887587ece073 F test/swarmvtabfault.test 8a67a9f27c61073a47990829e92bc0c64420a807cb642b15a25f6c788210ed95 -F test/symlink.test 72b22238d4405ba34df8e60b335d290a3b1129fd5c260835c944c1e4e77288a9 +F test/symlink.test 4368af0e213dd6e726a6240a16f2bb96a5a58f83f2d5d60652f27547b28cbf06 F test/symlink2.test 9531f475a53d8781c4f81373f87faf2e2aff4f5fb2102ec6386e0c827916a670 F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d4333092 F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039 @@ -1575,8 +1562,9 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 65c29b6f1dbf71b0e59a7b221d7e849dfa5a55fa7d0a2902811e8abdecdb1d44 -F test/testrunner.tcl 86b57135754ab2160aeb04b4829d321fb285a5cfa7a505fe61d69aed605854cc +F test/tester.tcl 8d54d40a55e12554b4509bc12078b201b233c8e842a7543629094a21b1ba956d +F test/testrunner.tcl e0b36a732956c69ce19370611b8dc69b44e627d5ea8ec2a5455ca3e8a170ddaa +F test/testrunner_data.tcl 8169c68654ac8906833b8a6aadca973358a441ebf88270dd05c153e5f96f76b8 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1624,6 +1612,7 @@ F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed F test/tkt-91e2e8ba6f.test 08c4f94ae07696b05c9b822da0b4e5337a2f54c5 F test/tkt-94c04eaadb.test f738c57c7f68ab8be1c054415af7774617cb6223 +F test/tkt-99378177930f87bd.test 2f07020a82ed1c56bdad60a8a6ef508b2f8a1fb056300b7ec650cbd9975b46bf F test/tkt-9a8b09f8e6.test b2ef151d0984b2ebf237760dbeaa50724e5a0667 F test/tkt-9d68c883.test 16f7cb96781ba579bc2e19bb14b4ad609d9774b6 F test/tkt-9f2eb3abac.test cb6123ac695a08b4454c3792fbe85108f67fabf8 @@ -1760,7 +1749,7 @@ F test/triggerE.test 612969cb57a4ef792059ad6d01af0117e1ae862c283753ffcc9a6428642 F test/triggerF.test 5d76f0a8c428ff87a4d5ed52da06f6096a2c787a1e21b846111dfac4123de3ad F test/triggerG.test 2b816093c91ba73c733cfa8aedcc210ad819d72a98b1da30768a3c56505233e9 F test/triggerupfrom.test d1f9e56090408115c522bee626cc33a2f3370f627a5e341d832589d72e3aa271 -F test/trustschema1.test 4e970aef0bfe0cee139703cc7209d0e0f07725d999b180ba50770f49edef1494 +F test/trustschema1.test d2996bb284859c99956ac706160eab9f086919da738d19bfef3ac431cce8fd47 F test/tt3_checkpoint.c ac7ca661d739280c89d9c253897df64a59a49369bd1247207ac0f655b622579d F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9 @@ -1770,7 +1759,8 @@ F test/tt3_vacuum.c 71b254cde1fc49d6c8c44efd54f4668f3e57d7b3a8f4601ade069f75a999 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a -F test/unionall.test bfeeea6c18c09a46f7e8bed69173e31c71336152e6253fb37c7257c76509f0e4 +F test/unhex.test 47b547f4b35e4f6525ecac7c7839bd3ae4eb4613d4e8932592eff55da83308f1 +F test/unionall.test eb9afa030897af75fd2f0dd28354ef63c8a5897b6c76aa1f15acae61a12eabcf F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1 F test/unionallfault.test 652bfbb630e6c43135965dc1e8f0a9a791da83aec885d626a632fe1909c56f73 F test/unionvtab.test e1704ab1b4c1bb3ffc9da4681f8e85a0b909fd80b937984fc94b27415ac8e5a4 @@ -1807,10 +1797,10 @@ F test/vacuum6.test b137b04bf3392d3f5c3b8fda0ce85a6775a70ca112f6559f74ff52dc9ce0 F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 -F test/view.test d16e49e89ada6137d1447777ef2a74574526a3b024e6733bf53ae2960da8c17c +F test/view.test d4c4281e1679245829db35597817282f60dc513fc39cc5439078f009bd118487 F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 -F test/vt02.c 33ecddc0832d4cd24e9e9fa83d868981b1e049462f4ec9080710353f6479a534 +F test/vt02.c 86253b57d6bc2170dfca33f45fd099b66d0bf874e95ecd7786dcbb134f179469 F test/vtab1.test 09a72330d0f31eda2ffaa828b06a6b917fb86250ee72de0301570af725774c07 F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e @@ -1874,7 +1864,7 @@ F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2a F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1 F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec F test/wapptest.tcl 1bea58a6a8e68a73f542ee4fca28b771b84ed803bd0c9e385087070b3d747b3c x -F test/where.test c033c4c156e463fe1074a56dc914dfaa91de82b0dcf8a02f5fbc67cebecfdd17 +F test/where.test 59abb854eee24f166b5f7ba9d17eb250abc59ce0a66c48912ffb10763648196d F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6 F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 @@ -1894,7 +1884,7 @@ F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test c4bb7e2ca56d49bd8ab5c7bd085b8b83e353922b46904d68aefb3c7468643581 F test/whereJ.test fc05e374cc9f2dc204148d6c06822c380ad388895fe97a6d335b94a26a08aecf F test/whereK.test 0270ab7f04ba5436fb9156d31d642a1c82727f4c4bfe5ba90d435c78cf44684a -F test/whereL.test 50171e3ec00b4c8ad5ec773119a35d9e9642cec45154b44c366d628326479f4d +F test/whereL.test 9d7c8a9f4e5e82d6859e61cf8758c3856c7e0a7fd8be11c92cac8c3ec39228fd F test/whereM.test 0dbc9998783458ddcf3cc078ca7c2951d8b2677d472ecf0028f449ed327c0250 F test/wherefault.test 6cf2a9c5712952d463d3f45ebee7f6caf400984df51a195d884cfb7eb0e837a7 F test/wherelfault.test 9012e4ef5259058b771606616bd007af5d154e64cc25fa9fd4170f6411db44e3 @@ -1920,7 +1910,7 @@ F test/window8.tcl 5e02e41d9d9a80f597063aed1a381eb19d1d0ef677a4f0df352c5365cf23f F test/window8.test 4ab16817414af0c904abe2ebdf88eb6c2b00058b84f9748c6174ff11fc45f1ed F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae4836 F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be -F test/windowB.test f2fb42b864b0cf431c956407583e9478a74c3642bdf8737fdcb6ff4a40298b07 +F test/windowB.test aad7c31739999f68a98a813cfd78390918fc70f56d2d925317a1523cab548ecf F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2 F test/windowD.test 65cf5a765fb8072450e8a0de2979ce7f09a38d87724fe1280c6444073e3da49b F test/windowE.test 6ba0c8048e4cc02b942e56640f8fcd50fd7ca72c876656c40f6baf42e316684c @@ -1930,10 +1920,10 @@ F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df F test/with1.test 9ad67fdeb2bbd808a5763c9060e214ea232f9b18d846ea3a59756dc38bdc3880 F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df -F test/with3.test 1e2e8d5e7b1d955342d0d18c250aaaa6e6bcf36ef2a818477bd01cb74f9a5d66 +F test/with3.test e7bf809bf75c1f44f98bca78bc331dbf542002c5227bf53c1261144db4e824c8 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8 -F test/with6.test c18592592b5a1c5802fd4e933d506f7b34ebbe8fdd585229793e960ae58d433f +F test/with6.test 7afab289442bd0a023c18deef854642932294fa63cdb885a4b4db69e28c5fbf9 F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64 F test/without_rowid1.test a5210b8770dc4736bca4e74bc96588f43025ad03ad6a80f885afd36d9890e217 F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99 @@ -1947,7 +1937,7 @@ F test/writecrash.test f1da7f7adfe8d7f09ea79b42e5ca6dcc41102f27f8e334ad71539501d F test/zeroblob.test 7b74cefc7b281dfa2b07cd237987fbe94b4a2037a7771e9e83f2d5f608b1d99e F test/zeroblobfault.test 861d8191a0d944dfebb3cb4d2c5b4e46a5a119eaec5a63dd996c2389f8063441 F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc -F test/zipfile.test 0d8758d8c0d63f16644f959689f78969d223789d998964276554039f067b4548 +F test/zipfile.test 4178a2de98739e9adac41cb8f0be5553df060e470c846f51fdbed247117728a7 F test/zipfile2.test 9903388a602a3834189857a985106ff95c3bba6a3969e0134127df991889db5d F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb44 @@ -1980,19 +1970,19 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl c185cf1bdcd3d9bd3c06f77a2fd2df8a4a0d07266f992ecda75286965ba3574c x +F tool/mkctimec.tcl 38e3db33210a200aae791635125052a643a27aa0619a0debf19aa9c55e1b2dde x F tool/mkkeywordhash.c 35bfc41adacc4aa6ef6fca7fd0c63e0ec0534b78daf4d0cfdebe398216bbffc3 F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a895ab33 F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef -F tool/mkopcodeh.tcl bcb2bd5affb545fd219ef0304c7978e2a356407ab723f45ec8569235892c1c3f +F tool/mkopcodeh.tcl 769d9e6a8b462323150dc13a8539d6064664b72974f7894befe2491cc73e05cd F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d -F tool/mkshellc.tcl 02d0de8349ef830c0fb20d29680320bde2466e2ec422e5bd94c4317a7a7e8cc9 +F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971fed0 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f -F tool/mksqlite3c.tcl 4fc26a9bfa0c4505b203d7ca0cf086e75ebcd4d63eb719c82f37e3fecdf23d37 -F tool/mksqlite3h.tcl 1f5e4a1dbbbc43c83cc6e74fe32c6c620502240b66c7c0f33a51378e78fc4edf +F tool/mksqlite3c.tcl eb47021591b1ad4a6862e2cb5625f1ac67ec1e0c6db5ba3953c069c635284cf5 +F tool/mksqlite3h.tcl d391cff7cad0a372ee1406faee9ccc7dad9cb80a0c95cae0f73d10dd26e06762 F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845 @@ -2004,12 +1994,12 @@ F tool/replace.tcl 937c931ad560688e85bdd6258bdc754371bb1e2732e1fb28ef441e44c9228 F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076 -F tool/showdb.c 72239e95e1d05a2941c6a1d86b4d857812be4dadd71f24e381db572f350fc172 +F tool/showdb.c 495a43b759ae37a0c4561a557a70090cb79b8c1601204e5a77e8b5360e65a954 F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818 F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564 F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809 F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c2a1 -F tool/showwal.c 0253c187ae16fdae9cde89e63e1dfcd3bb35e5416d066415f99e2f8cac6ab03d +F tool/showwal.c 4699048f68b6dd7b451011abfff404b8890d5a0b7dab78d2ad50d018116239d5 F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe F tool/spaceanal.tcl 1b5be34c6223cb1af06da2a10fb77863eb869b1962d055820b0a11cf2336ab45 F tool/speed-check.sh 9b27e158330a6587e92214b2344cc6fadde514996c0648fc0de7955ef7a79d77 @@ -2030,9 +2020,9 @@ F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d F tool/symbols.sh 1612bd947750e21e7b47befad5f6b3825b06cce0705441f903bf35ced65ae9b9 F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003 F tool/vdbe-compress.tcl 1dcb7632e57cf57105248029e6e162fddaf6c0fccb3bb9e6215603752c5a2d4a -F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f +F tool/vdbe_profile.tcl 3ac5a4a9449f4baf77059358ea050db3e34395ccf59c5464d29b91746d5b961e F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 -F tool/warnings.sh d58dc38367cc776550f90327e205d7946802d4004fb9f291fd8b81256bc1eedd +F tool/warnings.sh ab651bb82586c43ff8b560beceac959735bf917b44c5e0f67ba3426e474f29f8 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F vsixtest/App.xaml b76d3b48860e7454775c47ea38ffea9c4abe3e85 F vsixtest/App.xaml.cpp 41158ee43269820136fa3bba00c0bd91b26cc38b650ee392aec2a8d823e54318 @@ -2055,10 +2045,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ff1d456161e57314b0bb4e2e720eb1cd00f71b4bf4c459fad9fef4f11d38c19f -R 3583cb8fddea8cad93eaac66eb583e8a +P 409a19145e9558686acab730b94c6d53691b6d0efcea1e9239fc7e5863586a85 +R 8cba8b939a0caa0ad935e665db07c75b T +sym-release * -T +sym-version-3.40.1 * +T +sym-version-3.41.0 * U drh -Z dd3aba06a636bceff5c4750b57949095 +Z dc017a46fcafa54656c31865fbc11b74 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8b119aa1d..fd45b5ef9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -df5c253c0b3dd24916e4ec7cf77d3db5294cc9fd45ae7b9c5e82ad8197f38a24 +05941c2a04037fc3ed2ffae11f5d2260706f89431f463518740f72ada350866d diff --git a/src/alter.c b/src/alter.c index d8a998287..cebeec385 100644 --- a/src/alter.c +++ b/src/alter.c @@ -741,13 +741,14 @@ static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( pParse->nErr==0 ){ const RenameToken *p; - u8 i = 0; + u32 i = 1; for(p=pParse->pRename; p; p=p->pNext){ if( p->p ){ assert( p->p!=pPtr ); - i += *(u8*)(p->p); + i += *(u8*)(p->p) | 1; } } + assert( i>0 ); } } #else diff --git a/src/analyze.c b/src/analyze.c index 8562b9d7f..f3356ea3c 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -1597,6 +1597,8 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ ** and its contents. */ void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){ + assert( db!=0 ); + assert( pIdx!=0 ); #ifdef SQLITE_ENABLE_STAT4 if( pIdx->aSample ){ int j; @@ -1606,7 +1608,7 @@ void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){ } sqlite3DbFree(db, pIdx->aSample); } - if( db && db->pnBytesFreed==0 ){ + if( db->pnBytesFreed==0 ){ pIdx->nSample = 0; pIdx->aSample = 0; } diff --git a/src/attach.c b/src/attach.c index 1732be27b..4a6a25bf0 100644 --- a/src/attach.c +++ b/src/attach.c @@ -85,7 +85,7 @@ static void attachFunc( char *zErr = 0; unsigned int flags; Db *aNew; /* New array of Db pointers */ - Db *pNew; /* Db object for the newly attached database */ + Db *pNew = 0; /* Db object for the newly attached database */ char *zErrDyn = 0; sqlite3_vfs *pVfs; @@ -105,13 +105,26 @@ static void attachFunc( /* This is not a real ATTACH. Instead, this routine is being called ** from sqlite3_deserialize() to close database db->init.iDb and ** reopen it as a MemDB */ + Btree *pNewBt = 0; pVfs = sqlite3_vfs_find("memdb"); if( pVfs==0 ) return; - pNew = &db->aDb[db->init.iDb]; - if( pNew->pBt ) sqlite3BtreeClose(pNew->pBt); - pNew->pBt = 0; - pNew->pSchema = 0; - rc = sqlite3BtreeOpen(pVfs, "x\0", db, &pNew->pBt, 0, SQLITE_OPEN_MAIN_DB); + rc = sqlite3BtreeOpen(pVfs, "x\0", db, &pNewBt, 0, SQLITE_OPEN_MAIN_DB); + if( rc==SQLITE_OK ){ + Schema *pNewSchema = sqlite3SchemaGet(db, pNewBt); + if( pNewSchema ){ + /* Both the Btree and the new Schema were allocated successfully. + ** Close the old db and update the aDb[] slot with the new memdb + ** values. */ + pNew = &db->aDb[db->init.iDb]; + if( ALWAYS(pNew->pBt) ) sqlite3BtreeClose(pNew->pBt); + pNew->pBt = pNewBt; + pNew->pSchema = pNewSchema; + }else{ + sqlite3BtreeClose(pNewBt); + rc = SQLITE_NOMEM; + } + } + if( rc ) goto attach_error; }else{ /* This is a real ATTACH ** @@ -224,7 +237,7 @@ static void attachFunc( } #endif if( rc ){ - if( !REOPEN_AS_MEMDB(db) ){ + if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){ int iDb = db->nDb - 1; assert( iDb>=2 ); if( db->aDb[iDb].pBt ){ @@ -341,6 +354,8 @@ static void codeAttach( sqlite3* db = pParse->db; int regArgs; + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto attach_end; + if( pParse->nErr ) goto attach_end; memset(&sName, 0, sizeof(NameContext)); sName.pParse = pParse; diff --git a/src/btree.c b/src/btree.c index 9f1bc56f6..4fbe0b3db 100644 --- a/src/btree.c +++ b/src/btree.c @@ -1900,62 +1900,67 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ ** Only the following combinations are supported. Anything different ** indicates a corrupt database files: ** -** PTF_ZERODATA -** PTF_ZERODATA | PTF_LEAF -** PTF_LEAFDATA | PTF_INTKEY -** PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF +** PTF_ZERODATA (0x02, 2) +** PTF_LEAFDATA | PTF_INTKEY (0x05, 5) +** PTF_ZERODATA | PTF_LEAF (0x0a, 10) +** PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF (0x0d, 13) */ static int decodeFlags(MemPage *pPage, int flagByte){ BtShared *pBt; /* A copy of pPage->pBt */ assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - pPage->leaf = (u8)(flagByte>>3); assert( PTF_LEAF == 1<<3 ); - flagByte &= ~PTF_LEAF; - pPage->childPtrSize = 4-4*pPage->leaf; pBt = pPage->pBt; - if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ - /* EVIDENCE-OF: R-07291-35328 A value of 5 (0x05) means the page is an - ** interior table b-tree page. */ - assert( (PTF_LEAFDATA|PTF_INTKEY)==5 ); - /* EVIDENCE-OF: R-26900-09176 A value of 13 (0x0d) means the page is a - ** leaf table b-tree page. */ - assert( (PTF_LEAFDATA|PTF_INTKEY|PTF_LEAF)==13 ); - pPage->intKey = 1; - if( pPage->leaf ){ + pPage->max1bytePayload = pBt->max1bytePayload; + if( flagByte>=(PTF_ZERODATA | PTF_LEAF) ){ + pPage->childPtrSize = 0; + pPage->leaf = 1; + if( flagByte==(PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF) ){ pPage->intKeyLeaf = 1; pPage->xCellSize = cellSizePtrTableLeaf; pPage->xParseCell = btreeParseCellPtr; + pPage->intKey = 1; + pPage->maxLocal = pBt->maxLeaf; + pPage->minLocal = pBt->minLeaf; + }else if( flagByte==(PTF_ZERODATA | PTF_LEAF) ){ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + pPage->maxLocal = pBt->maxLocal; + pPage->minLocal = pBt->minLocal; }else{ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + return SQLITE_CORRUPT_PAGE(pPage); + } + }else{ + pPage->childPtrSize = 4; + pPage->leaf = 0; + if( flagByte==(PTF_ZERODATA) ){ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + pPage->maxLocal = pBt->maxLocal; + pPage->minLocal = pBt->minLocal; + }else if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ pPage->intKeyLeaf = 0; pPage->xCellSize = cellSizePtrNoPayload; pPage->xParseCell = btreeParseCellPtrNoPayload; + pPage->intKey = 1; + pPage->maxLocal = pBt->maxLeaf; + pPage->minLocal = pBt->minLeaf; + }else{ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + return SQLITE_CORRUPT_PAGE(pPage); } - pPage->maxLocal = pBt->maxLeaf; - pPage->minLocal = pBt->minLeaf; - }else if( flagByte==PTF_ZERODATA ){ - /* EVIDENCE-OF: R-43316-37308 A value of 2 (0x02) means the page is an - ** interior index b-tree page. */ - assert( (PTF_ZERODATA)==2 ); - /* EVIDENCE-OF: R-59615-42828 A value of 10 (0x0a) means the page is a - ** leaf index b-tree page. */ - assert( (PTF_ZERODATA|PTF_LEAF)==10 ); - pPage->intKey = 0; - pPage->intKeyLeaf = 0; - pPage->xCellSize = cellSizePtr; - pPage->xParseCell = btreeParseCellPtrIndex; - pPage->maxLocal = pBt->maxLocal; - pPage->minLocal = pBt->minLocal; - }else{ - /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is - ** an error. */ - pPage->intKey = 0; - pPage->intKeyLeaf = 0; - pPage->xCellSize = cellSizePtr; - pPage->xParseCell = btreeParseCellPtrIndex; - return SQLITE_CORRUPT_PAGE(pPage); } - pPage->max1bytePayload = pBt->max1bytePayload; return SQLITE_OK; } @@ -5495,9 +5500,25 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ ** on success. Set *pRes to 0 if the cursor actually points to something ** or set *pRes to 1 if the table is empty. */ +static SQLITE_NOINLINE int btreeLast(BtCursor *pCur, int *pRes){ + int rc = moveToRoot(pCur); + if( rc==SQLITE_OK ){ + assert( pCur->eState==CURSOR_VALID ); + *pRes = 0; + rc = moveToRightmost(pCur); + if( rc==SQLITE_OK ){ + pCur->curFlags |= BTCF_AtLast; + }else{ + pCur->curFlags &= ~BTCF_AtLast; + } + }else if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + *pRes = 1; + rc = SQLITE_OK; + } + return rc; +} int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ - int rc; - assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); @@ -5518,23 +5539,7 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ *pRes = 0; return SQLITE_OK; } - - rc = moveToRoot(pCur); - if( rc==SQLITE_OK ){ - assert( pCur->eState==CURSOR_VALID ); - *pRes = 0; - rc = moveToRightmost(pCur); - if( rc==SQLITE_OK ){ - pCur->curFlags |= BTCF_AtLast; - }else{ - pCur->curFlags &= ~BTCF_AtLast; - } - }else if( rc==SQLITE_EMPTY ){ - assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); - *pRes = 1; - rc = SQLITE_OK; - } - return rc; + return btreeLast(pCur, pRes); } /* Move the cursor so that it points to an entry in a table (a.k.a INTKEY) @@ -6601,7 +6606,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ /* If the database supports auto-vacuum, write an entry in the pointer-map ** to indicate that the page is free. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc); if( rc ) goto freepage_out; } @@ -7041,24 +7046,20 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ ** in pTemp or the original pCell) and also record its index. ** Allocating a new entry in pPage->aCell[] implies that ** pPage->nOverflow is incremented. -** -** *pRC must be SQLITE_OK when this routine is called. */ -static void insertCell( +static int insertCell( MemPage *pPage, /* Page into which we are copying */ int i, /* New cell becomes the i-th cell of the page */ u8 *pCell, /* Content of the new cell */ int sz, /* Bytes of content in pCell */ u8 *pTemp, /* Temp storage space for pCell, if needed */ - Pgno iChild, /* If non-zero, replace first 4 bytes with this value */ - int *pRC /* Read and write return code from here */ + Pgno iChild /* If non-zero, replace first 4 bytes with this value */ ){ int idx = 0; /* Where to write new cell content in data[] */ int j; /* Loop counter */ u8 *data; /* The content of the whole page */ u8 *pIns; /* The point in pPage->aCellIdx[] where no cell inserted */ - assert( *pRC==SQLITE_OK ); assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); assert( MX_CELL(pPage->pBt)<=10921 ); assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); @@ -7093,14 +7094,13 @@ static void insertCell( }else{ int rc = sqlite3PagerWrite(pPage->pDbPage); if( rc!=SQLITE_OK ){ - *pRC = rc; - return; + return rc; } assert( sqlite3PagerIswriteable(pPage->pDbPage) ); data = pPage->aData; assert( &data[pPage->cellOffset]==pPage->aCellIdx ); rc = allocateSpace(pPage, sz, &idx); - if( rc ){ *pRC = rc; return; } + if( rc ){ return rc; } /* The allocateSpace() routine guarantees the following properties ** if it returns successfully */ assert( idx >= 0 ); @@ -7127,13 +7127,16 @@ static void insertCell( assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); #ifndef SQLITE_OMIT_AUTOVACUUM if( pPage->pBt->autoVacuum ){ + int rc2 = SQLITE_OK; /* The cell may contain a pointer to an overflow page. If so, write ** the entry for the overflow page into the pointer map. */ - ptrmapPutOvflPtr(pPage, pPage, pCell, pRC); + ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2); + if( rc2 ) return rc2; } #endif } + return SQLITE_OK; } /* @@ -7234,14 +7237,16 @@ struct CellArray { ** computed. */ static void populateCellCache(CellArray *p, int idx, int N){ + MemPage *pRef = p->pRef; + u16 *szCell = p->szCell; assert( idx>=0 && idx+N<=p->nCell ); while( N>0 ){ assert( p->apCell[idx]!=0 ); - if( p->szCell[idx]==0 ){ - p->szCell[idx] = p->pRef->xCellSize(p->pRef, p->apCell[idx]); + if( szCell[idx]==0 ){ + szCell[idx] = pRef->xCellSize(pRef, p->apCell[idx]); }else{ assert( CORRUPT_DB || - p->szCell[idx]==p->pRef->xCellSize(p->pRef, p->apCell[idx]) ); + szCell[idx]==pRef->xCellSize(pRef, p->apCell[idx]) ); } idx++; N--; @@ -7443,8 +7448,8 @@ static int pageFreeArray( int nRet = 0; int i; int iEnd = iFirst + nCell; - u8 *pFree = 0; - int szFree = 0; + u8 *pFree = 0; /* \__ Parameters for pending call to */ + int szFree = 0; /* / freeSpace() */ for(i=iFirst; iapCell[i]; @@ -7465,6 +7470,9 @@ static int pageFreeArray( return 0; } }else{ + /* The current cell is adjacent to and before the pFree cell. + ** Combine the two regions into one to reduce the number of calls + ** to freeSpace(). */ pFree = pCell; szFree += sz; } @@ -7672,7 +7680,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc); if( szCell>pNew->minLocal ){ ptrmapPutOvflPtr(pNew, pNew, pCell, &rc); @@ -7700,8 +7708,8 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ /* Insert the new divider cell into pParent. */ if( rc==SQLITE_OK ){ - insertCell(pParent, pParent->nCell, pSpace, (int)(pOut-pSpace), - 0, pPage->pgno, &rc); + rc = insertCell(pParent, pParent->nCell, pSpace, (int)(pOut-pSpace), + 0, pPage->pgno); } /* Set the right-child pointer of pParent to point to the new page. */ @@ -7810,7 +7818,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ /* If this is an auto-vacuum database, update the pointer-map entries ** for any b-tree or overflow pages that pTo now contains the pointers to. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ *pRC = setChildPtrmaps(pTo); } } @@ -8234,15 +8242,17 @@ static int balance_nonroot( d = r + 1 - leafData; (void)cachedCellSize(&b, d); do{ + int szR, szD; assert( d szLeft-(b.szCell[r]+(i==k-1?0:2)))){ + && (bBulk || szRight+szD+2 > szLeft-(szR+(i==k-1?0:2)))){ break; } - szRight += b.szCell[d] + 2; - szLeft -= b.szCell[r] + 2; + szRight += szD + 2; + szLeft -= szR + 2; cntNew[i-1] = r; r--; d--; @@ -8296,7 +8306,7 @@ static int balance_nonroot( cntOld[i] = b.nCell; /* Set the pointer-map entry for the new sibling page. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc); if( rc!=SQLITE_OK ){ goto balance_cleanup; @@ -8389,7 +8399,7 @@ static int balance_nonroot( ** updated. This happens below, after the sibling pages have been ** populated, not here. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ MemPage *pOld; MemPage *pNew = pOld = apNew[0]; int cntOldNext = pNew->nCell + pNew->nOverflow; @@ -8486,7 +8496,7 @@ static int balance_nonroot( rc = SQLITE_CORRUPT_BKPT; goto balance_cleanup; } - insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno, &rc); + rc = insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno); if( rc!=SQLITE_OK ) goto balance_cleanup; assert( sqlite3PagerIswriteable(pParent->pDbPage) ); } @@ -8582,7 +8592,7 @@ static int balance_nonroot( ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); - }else if( ISAUTOVACUUM && !leafCorrection ){ + }else if( ISAUTOVACUUM(pBt) && !leafCorrection ){ /* Fix the pointer map entries associated with the right-child of each ** sibling page. All other pointer map entries have already been taken ** care of. */ @@ -8603,7 +8613,7 @@ static int balance_nonroot( } #if 0 - if( ISAUTOVACUUM && rc==SQLITE_OK && apNew[0]->isInit ){ + if( ISAUTOVACUUM(pBt) && rc==SQLITE_OK && apNew[0]->isInit ){ /* The ptrmapCheckPages() contains assert() statements that verify that ** all pointer map pages are set correctly. This is helpful while ** debugging. This is usually disabled because a corrupt database may @@ -8665,7 +8675,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc); } } @@ -8904,9 +8914,13 @@ static int btreeOverwriteContent( /* ** Overwrite the cell that cursor pCur is pointing to with fresh content -** contained in pX. +** contained in pX. In this variant, pCur is pointing to an overflow +** cell. */ -static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ +static SQLITE_NOINLINE int btreeOverwriteOverflowCell( + BtCursor *pCur, /* Cursor pointing to cell to ovewrite */ + const BtreePayload *pX /* Content to write into the cell */ +){ int iOffset; /* Next byte of pX->pData to write */ int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */ int rc; /* Return code */ @@ -8915,16 +8929,12 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ Pgno ovflPgno; /* Next overflow page to write */ u32 ovflPageSize; /* Size to write on overflow page */ - if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd - || pCur->info.pPayload < pPage->aData + pPage->cellOffset - ){ - return SQLITE_CORRUPT_BKPT; - } + assert( pCur->info.nLocalinfo.pPayload, pX, 0, pCur->info.nLocal); if( rc ) return rc; - if( pCur->info.nLocal==nTotal ) return SQLITE_OK; /* Now overwrite the overflow pages */ iOffset = pCur->info.nLocal; @@ -8954,6 +8964,29 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ return SQLITE_OK; } +/* +** Overwrite the cell that cursor pCur is pointing to with fresh content +** contained in pX. +*/ +static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ + int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */ + MemPage *pPage = pCur->pPage; /* Page being written */ + + if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd + || pCur->info.pPayload < pPage->aData + pPage->cellOffset + ){ + return SQLITE_CORRUPT_BKPT; + } + if( pCur->info.nLocal==nTotal ){ + /* The entire cell is local */ + return btreeOverwriteContent(pPage, pCur->info.pPayload, pX, + 0, pCur->info.nLocal); + }else{ + /* The cell contains overflow content */ + return btreeOverwriteOverflowCell(pCur, pX); + } +} + /* ** Insert a new record into the BTree. The content of the new record @@ -8997,7 +9030,6 @@ int sqlite3BtreeInsert( int idx; MemPage *pPage; Btree *p = pCur->pBtree; - BtShared *pBt = p->pBt; unsigned char *oldCell; unsigned char *newCell = 0; @@ -9016,7 +9048,7 @@ int sqlite3BtreeInsert( ** not to clear the cursor here. */ if( pCur->curFlags & BTCF_Multiple ){ - rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur); + rc = saveAllCursors(p->pBt, pCur->pgnoRoot, pCur); if( rc ) return rc; if( loc && pCur->iPage<0 ){ /* This can only happen if the schema is corrupt such that there is more @@ -9040,8 +9072,8 @@ int sqlite3BtreeInsert( assert( cursorOwnsBtShared(pCur) ); assert( (pCur->curFlags & BTCF_WriteFlag)!=0 - && pBt->inTransaction==TRANS_WRITE - && (pBt->btsFlags & BTS_READ_ONLY)==0 ); + && p->pBt->inTransaction==TRANS_WRITE + && (p->pBt->btsFlags & BTS_READ_ONLY)==0 ); assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); /* Assert that the caller has been consistent. If this cursor was opened @@ -9158,26 +9190,28 @@ int sqlite3BtreeInsert( pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); assert( pPage->isInit || CORRUPT_DB ); - newCell = pBt->pTmpSpace; + newCell = p->pBt->pTmpSpace; assert( newCell!=0 ); + assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); if( flags & BTREE_PREFORMAT ){ rc = SQLITE_OK; - szNew = pBt->nPreformatSize; + szNew = p->pBt->nPreformatSize; if( szNew<4 ) szNew = 4; - if( ISAUTOVACUUM && szNew>pPage->maxLocal ){ + if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){ CellInfo info; pPage->xParseCell(pPage, newCell, &info); if( info.nPayload!=info.nLocal ){ Pgno ovfl = get4byte(&newCell[szNew-4]); - ptrmapPut(pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, &rc); + ptrmapPut(p->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, &rc); + if( NEVER(rc) ) goto end_insert; } } }else{ rc = fillInCell(pPage, newCell, pX, &szNew); + if( rc ) goto end_insert; } - if( rc ) goto end_insert; assert( szNew==pPage->xCellSize(pPage, newCell) ); - assert( szNew <= MX_CELL_SIZE(pBt) ); + assert( szNew <= MX_CELL_SIZE(p->pBt) ); idx = pCur->ix; if( loc==0 ){ CellInfo info; @@ -9197,7 +9231,7 @@ int sqlite3BtreeInsert( testcase( pCur->curFlags & BTCF_ValidOvfl ); invalidateOverflowCache(pCur); if( info.nSize==szNew && info.nLocal==info.nPayload - && (!ISAUTOVACUUM || szNewminLocal) + && (!ISAUTOVACUUM(p->pBt) || szNewminLocal) ){ /* Overwrite the old cell with the new if they are the same size. ** We could also try to do this if the old cell is smaller, then add @@ -9227,7 +9261,7 @@ int sqlite3BtreeInsert( }else{ assert( pPage->leaf ); } - insertCell(pPage, idx, newCell, szNew, 0, 0, &rc); + rc = insertCell(pPage, idx, newCell, szNew, 0, 0); assert( pPage->nOverflow==0 || rc==SQLITE_OK ); assert( rc!=SQLITE_OK || pPage->nCell>0 || pPage->nOverflow>0 ); @@ -9300,7 +9334,6 @@ int sqlite3BtreeInsert( ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. */ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ - int rc = SQLITE_OK; BtShared *pBt = pDest->pBt; u8 *aOut = pBt->pTmpSpace; /* Pointer to next output buffer */ const u8 *aIn; /* Pointer to next input buffer */ @@ -9323,7 +9356,9 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ if( nIn==nRem && nInpPage->maxLocal ){ memcpy(aOut, aIn, nIn); pBt->nPreformatSize = nIn + (aOut - pBt->pTmpSpace); + return SQLITE_OK; }else{ + int rc = SQLITE_OK; Pager *pSrcPager = pSrc->pBt->pPager; u8 *pPgnoOut = 0; Pgno ovflIn = 0; @@ -9375,7 +9410,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); put4byte(pPgnoOut, pgnoNew); - if( ISAUTOVACUUM && pPageOut ){ + if( ISAUTOVACUUM(pBt) && pPageOut ){ ptrmapPut(pBt, pgnoNew, PTRMAP_OVERFLOW2, pPageOut->pgno, &rc); } releasePage(pPageOut); @@ -9391,9 +9426,8 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ releasePage(pPageOut); sqlite3PagerUnref(pPageIn); + return rc; } - - return rc; } /* @@ -9548,7 +9582,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ assert( pTmp!=0 ); rc = sqlite3PagerWrite(pLeaf->pDbPage); if( rc==SQLITE_OK ){ - insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n, &rc); + rc = insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n); } dropCell(pLeaf, pLeaf->nCell-1, nCell, &rc); if( rc ) return rc; @@ -10147,6 +10181,41 @@ Pager *sqlite3BtreePager(Btree *p){ } #ifndef SQLITE_OMIT_INTEGRITY_CHECK +/* +** Record an OOM error during integrity_check +*/ +static void checkOom(IntegrityCk *pCheck){ + pCheck->rc = SQLITE_NOMEM; + pCheck->mxErr = 0; /* Causes integrity_check processing to stop */ + if( pCheck->nErr==0 ) pCheck->nErr++; +} + +/* +** Invoke the progress handler, if appropriate. Also check for an +** interrupt. +*/ +static void checkProgress(IntegrityCk *pCheck){ + sqlite3 *db = pCheck->db; + if( AtomicLoad(&db->u1.isInterrupted) ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress ){ + assert( db->nProgressOps>0 ); + pCheck->nStep++; + if( (pCheck->nStep % db->nProgressOps)==0 + && db->xProgress(db->pProgressArg) + ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } + } +#endif +} + /* ** Append a message to the error message string. */ @@ -10156,6 +10225,7 @@ static void checkAppendMsg( ... ){ va_list ap; + checkProgress(pCheck); if( !pCheck->mxErr ) return; pCheck->mxErr--; pCheck->nErr++; @@ -10169,7 +10239,7 @@ static void checkAppendMsg( sqlite3_str_vappendf(&pCheck->errMsg, zFormat, ap); va_end(ap); if( pCheck->errMsg.accError==SQLITE_NOMEM ){ - pCheck->bOomFault = 1; + checkOom(pCheck); } } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ @@ -10211,7 +10281,6 @@ static int checkRef(IntegrityCk *pCheck, Pgno iPage){ checkAppendMsg(pCheck, "2nd reference to page %d", iPage); return 1; } - if( AtomicLoad(&pCheck->db->u1.isInterrupted) ) return 1; setPageReferenced(pCheck, iPage); return 0; } @@ -10234,7 +10303,7 @@ static void checkPtrmap( rc = ptrmapGet(pCheck->pBt, iChild, &ePtrmapType, &iPtrmapParent); if( rc!=SQLITE_OK ){ - if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) pCheck->bOomFault = 1; + if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) checkOom(pCheck); checkAppendMsg(pCheck, "Failed to read ptrmap key=%d", iChild); return; } @@ -10341,7 +10410,9 @@ static void checkList( ** lower 16 bits are the index of the last byte of that range. */ static void btreeHeapInsert(u32 *aHeap, u32 x){ - u32 j, i = ++aHeap[0]; + u32 j, i; + assert( aHeap!=0 ); + i = ++aHeap[0]; aHeap[i] = x; while( (j = i/2)>0 && aHeap[j]>aHeap[i] ){ x = aHeap[j]; @@ -10418,6 +10489,8 @@ static int checkTreePage( /* Check that the page exists */ + checkProgress(pCheck); + if( pCheck->mxErr==0 ) goto end_of_check; pBt = pCheck->pBt; usableSize = pBt->usableSize; if( iPage==0 ) return 0; @@ -10663,13 +10736,14 @@ static int checkTreePage( ** the unverified btrees. Except, if aRoot[1] is 1, then the freelist ** checks are still performed. */ -char *sqlite3BtreeIntegrityCheck( +int sqlite3BtreeIntegrityCheck( sqlite3 *db, /* Database connection that is running the check */ Btree *p, /* The btree to be checked */ Pgno *aRoot, /* An array of root pages numbers for individual trees */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ - int *pnErr /* Write number of errors seen to this variable */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ ){ Pgno i; IntegrityCk sCheck; @@ -10692,18 +10766,12 @@ char *sqlite3BtreeIntegrityCheck( assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE ); VVA_ONLY( nRef = sqlite3PagerRefcount(pBt->pPager) ); assert( nRef>=0 ); + memset(&sCheck, 0, sizeof(sCheck)); sCheck.db = db; sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; sCheck.nPage = btreePagecount(sCheck.pBt); sCheck.mxErr = mxErr; - sCheck.nErr = 0; - sCheck.bOomFault = 0; - sCheck.zPfx = 0; - sCheck.v1 = 0; - sCheck.v2 = 0; - sCheck.aPgRef = 0; - sCheck.heap = 0; sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH); sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL; if( sCheck.nPage==0 ){ @@ -10712,12 +10780,12 @@ char *sqlite3BtreeIntegrityCheck( sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1); if( !sCheck.aPgRef ){ - sCheck.bOomFault = 1; + checkOom(&sCheck); goto integrity_ck_cleanup; } sCheck.heap = (u32*)sqlite3PageMalloc( pBt->pageSize ); if( sCheck.heap==0 ){ - sCheck.bOomFault = 1; + checkOom(&sCheck); goto integrity_ck_cleanup; } @@ -10798,16 +10866,17 @@ char *sqlite3BtreeIntegrityCheck( integrity_ck_cleanup: sqlite3PageFree(sCheck.heap); sqlite3_free(sCheck.aPgRef); - if( sCheck.bOomFault ){ + *pnErr = sCheck.nErr; + if( sCheck.nErr==0 ){ sqlite3_str_reset(&sCheck.errMsg); - sCheck.nErr++; + *pzOut = 0; + }else{ + *pzOut = sqlite3StrAccumFinish(&sCheck.errMsg); } - *pnErr = sCheck.nErr; - if( sCheck.nErr==0 ) sqlite3_str_reset(&sCheck.errMsg); /* Make sure this analysis did not leave any unref() pages. */ assert( nRef==sqlite3PagerRefcount(pBt->pPager) ); sqlite3BtreeLeave(p); - return sqlite3StrAccumFinish(&sCheck.errMsg); + return sCheck.rc; } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ diff --git a/src/btree.h b/src/btree.h index bb29870d2..b9078f901 100644 --- a/src/btree.h +++ b/src/btree.h @@ -183,7 +183,7 @@ int sqlite3BtreeNewDb(Btree *p); ** reduce network bandwidth. ** ** Note that BTREE_HINT_FLAGS with BTREE_BULKLOAD is the only hint used by -** standard SQLite. The other hints are provided for extentions that use +** standard SQLite. The other hints are provided for extensions that use ** the SQLite parser and code generator but substitute their own storage ** engine. */ @@ -329,7 +329,15 @@ const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); u32 sqlite3BtreePayloadSize(BtCursor*); sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor*); -char *sqlite3BtreeIntegrityCheck(sqlite3*,Btree*,Pgno*aRoot,int nRoot,int,int*); +int sqlite3BtreeIntegrityCheck( + sqlite3 *db, /* Database connection that is running the check */ + Btree *p, /* The btree to be checked */ + Pgno *aRoot, /* An array of root pages numbers for individual trees */ + int nRoot, /* Number of entries in aRoot[] */ + int mxErr, /* Stop reporting errors after this many */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ +); struct Pager *sqlite3BtreePager(Btree*); i64 sqlite3BtreeRowCountEst(BtCursor*); diff --git a/src/btreeInt.h b/src/btreeInt.h index 1f45553dc..79c3296d0 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -674,15 +674,15 @@ struct BtCursor { ** So, this macro is defined instead. */ #ifndef SQLITE_OMIT_AUTOVACUUM -#define ISAUTOVACUUM (pBt->autoVacuum) +#define ISAUTOVACUUM(pBt) (pBt->autoVacuum) #else -#define ISAUTOVACUUM 0 +#define ISAUTOVACUUM(pBt) 0 #endif /* -** This structure is passed around through all the sanity checking routines -** in order to keep track of some global state information. +** This structure is passed around through all the PRAGMA integrity_check +** checking routines in order to keep track of some global state information. ** ** The aRef[] array is allocated so that there is 1 bit for each page in ** the database. As the integrity-check proceeds, for each page used in @@ -698,7 +698,8 @@ struct IntegrityCk { Pgno nPage; /* Number of pages in the database */ int mxErr; /* Stop accumulating errors when this reaches zero */ int nErr; /* Number of messages written to zErrMsg so far */ - int bOomFault; /* A memory allocation error has occurred */ + int rc; /* SQLITE_OK, SQLITE_NOMEM, or SQLITE_INTERRUPT */ + u32 nStep; /* Number of steps into the integrity_check process */ const char *zPfx; /* Error message prefix */ Pgno v1; /* Value for first %u substitution in zPfx */ int v2; /* Value for second %d substitution in zPfx */ diff --git a/src/build.c b/src/build.c index effaeb733..0390e321f 100644 --- a/src/build.c +++ b/src/build.c @@ -307,6 +307,7 @@ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ char saveBuf[PARSE_TAIL_SZ]; if( pParse->nErr ) return; + if( pParse->eParseMode ) return; assert( pParse->nested<10 ); /* Nesting should only be of limited depth */ va_start(ap, zFormat); zSql = sqlite3VMPrintf(db, zFormat, ap); @@ -2004,6 +2005,13 @@ void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType){ if( pCol->colFlags & COLFLAG_PRIMKEY ){ makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */ } + if( ALWAYS(pExpr) && pExpr->op==TK_ID ){ + /* The value of a generated column needs to be a real expression, not + ** just a reference to another column, in order for covering index + ** optimizations to work correctly. So if the value is not an expression, + ** turn it into one by adding a unary "+" operator. */ + pExpr = sqlite3PExpr(pParse, TK_UPLUS, pExpr, 0); + } sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); pExpr = 0; goto generated_done; @@ -2140,7 +2148,8 @@ static char *createTableStmt(sqlite3 *db, Table *p){ /* SQLITE_AFF_TEXT */ " TEXT", /* SQLITE_AFF_NUMERIC */ " NUM", /* SQLITE_AFF_INTEGER */ " INT", - /* SQLITE_AFF_REAL */ " REAL" + /* SQLITE_AFF_REAL */ " REAL", + /* SQLITE_AFF_FLEXNUM */ " NUM", }; int len; const char *zType; @@ -2156,10 +2165,12 @@ static char *createTableStmt(sqlite3 *db, Table *p){ testcase( pCol->affinity==SQLITE_AFF_NUMERIC ); testcase( pCol->affinity==SQLITE_AFF_INTEGER ); testcase( pCol->affinity==SQLITE_AFF_REAL ); + testcase( pCol->affinity==SQLITE_AFF_FLEXNUM ); zType = azType[pCol->affinity - SQLITE_AFF_BLOB]; len = sqlite3Strlen30(zType); - assert( pCol->affinity==SQLITE_AFF_BLOB + assert( pCol->affinity==SQLITE_AFF_BLOB + || pCol->affinity==SQLITE_AFF_FLEXNUM || pCol->affinity==sqlite3AffinityType(zType, 0) ); memcpy(&zStmt[k], zType, len); k += len; @@ -2574,6 +2585,7 @@ int sqlite3ShadowTableName(sqlite3 *db, const char *zName){ ** not pass them into code generator routines by mistake. */ static int markImmutableExprStep(Walker *pWalker, Expr *pExpr){ + (void)pWalker; ExprSetVVAProperty(pExpr, EP_Immutable); return WRC_Continue; } @@ -3140,8 +3152,7 @@ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ && pTable->nCol==pSel->pEList->nExpr ){ assert( db->mallocFailed==0 ); - sqlite3SelectAddColumnTypeAndCollation(pParse, pTable, pSel, - SQLITE_AFF_NONE); + sqlite3SubqueryColumnTypes(pParse, pTable, pSel, SQLITE_AFF_NONE); } }else{ /* CREATE VIEW name AS... without an argument list. Construct diff --git a/src/date.c b/src/date.c index a3e58bc4e..7cc6fa684 100644 --- a/src/date.c +++ b/src/date.c @@ -749,7 +749,7 @@ static int parseModifier( i64 iOrigJD; /* Original localtime */ i64 iGuess; /* Guess at the corresponding utc time */ int cnt = 0; /* Safety to prevent infinite loop */ - int iErr; /* Guess is off by this much */ + i64 iErr; /* Guess is off by this much */ computeJD(p); iGuess = iOrigJD = p->iJD; diff --git a/src/dbpage.c b/src/dbpage.c index 458f2bd50..17e5f44f5 100644 --- a/src/dbpage.c +++ b/src/dbpage.c @@ -72,6 +72,10 @@ static int dbpageConnect( ){ DbpageTable *pTab = 0; int rc = SQLITE_OK; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); rc = sqlite3_declare_vtab(db, @@ -110,6 +114,7 @@ static int dbpageDisconnect(sqlite3_vtab *pVtab){ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; int iPlan = 0; + (void)tab; /* If there is a schema= constraint, it must be honored. Report a ** ridiculously large estimated cost if the schema= constraint is @@ -225,6 +230,8 @@ static int dbpageFilter( sqlite3 *db = pTab->db; Btree *pBt; + (void)idxStr; + /* Default setting is no rows of result */ pCsr->pgno = 1; pCsr->mxPgno = 0; @@ -239,7 +246,7 @@ static int dbpageFilter( pCsr->iDb = 0; } pBt = db->aDb[pCsr->iDb].pBt; - if( pBt==0 ) return SQLITE_OK; + if( NEVER(pBt==0) ) return SQLITE_OK; pCsr->pPager = sqlite3BtreePager(pBt); pCsr->szPage = sqlite3BtreeGetPageSize(pBt); pCsr->mxPgno = sqlite3BtreeLastPage(pBt); @@ -320,6 +327,7 @@ static int dbpageUpdate( Pager *pPager; int szPage; + (void)pRowid; if( pTab->db->flags & SQLITE_Defensive ){ zErr = "read-only"; goto update_fail; @@ -329,18 +337,20 @@ static int dbpageUpdate( goto update_fail; } pgno = sqlite3_value_int(argv[0]); - if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ + if( sqlite3_value_type(argv[0])==SQLITE_NULL + || (Pgno)sqlite3_value_int(argv[1])!=pgno + ){ zErr = "cannot insert"; goto update_fail; } zSchema = (const char*)sqlite3_value_text(argv[4]); - iDb = zSchema ? sqlite3FindDbName(pTab->db, zSchema) : -1; - if( iDb<0 ){ + iDb = ALWAYS(zSchema) ? sqlite3FindDbName(pTab->db, zSchema) : -1; + if( NEVER(iDb<0) ){ zErr = "no such schema"; goto update_fail; } pBt = pTab->db->aDb[iDb].pBt; - if( pgno<1 || pBt==0 || pgno>sqlite3BtreeLastPage(pBt) ){ + if( NEVER(pgno<1) || NEVER(pBt==0) || NEVER(pgno>sqlite3BtreeLastPage(pBt)) ){ zErr = "bad page number"; goto update_fail; } @@ -379,12 +389,11 @@ static int dbpageBegin(sqlite3_vtab *pVtab){ DbpageTable *pTab = (DbpageTable *)pVtab; sqlite3 *db = pTab->db; int i; - int rc = SQLITE_OK; - for(i=0; rc==SQLITE_OK && inDb; i++){ + for(i=0; inDb; i++){ Btree *pBt = db->aDb[i].pBt; - if( pBt ) rc = sqlite3BtreeBeginTrans(pBt, 1, 0); + if( pBt ) (void)sqlite3BtreeBeginTrans(pBt, 1, 0); } - return rc; + return SQLITE_OK; } diff --git a/src/dbstat.c b/src/dbstat.c index bb88a76f4..0a89d0524 100644 --- a/src/dbstat.c +++ b/src/dbstat.c @@ -163,6 +163,7 @@ static int statConnect( StatTable *pTab = 0; int rc = SQLITE_OK; int iDb; + (void)pAux; if( argc>=4 ){ Token nm; @@ -216,6 +217,7 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int iSchema = -1; int iName = -1; int iAgg = -1; + (void)tab; /* Look for a valid schema=? constraint. If found, change the idxNum to ** 1 and request the value of that constraint be sent to xFilter. And @@ -741,6 +743,8 @@ static int statFilter( int iArg = 0; /* Count of argv[] parameters used so far */ int rc = SQLITE_OK; /* Result of this operation */ const char *zName = 0; /* Only provide analysis of this table */ + (void)argc; + (void)idxStr; statResetCsr(pCsr); sqlite3_finalize(pCsr->pStmt); @@ -824,16 +828,16 @@ static int statColumn( } break; case 4: /* ncell */ - sqlite3_result_int(ctx, pCsr->nCell); + sqlite3_result_int64(ctx, pCsr->nCell); break; case 5: /* payload */ - sqlite3_result_int(ctx, pCsr->nPayload); + sqlite3_result_int64(ctx, pCsr->nPayload); break; case 6: /* unused */ - sqlite3_result_int(ctx, pCsr->nUnused); + sqlite3_result_int64(ctx, pCsr->nUnused); break; case 7: /* mx_payload */ - sqlite3_result_int(ctx, pCsr->nMxPayload); + sqlite3_result_int64(ctx, pCsr->nMxPayload); break; case 8: /* pgoffset */ if( !pCsr->isAgg ){ @@ -841,7 +845,7 @@ static int statColumn( } break; case 9: /* pgsize */ - sqlite3_result_int(ctx, pCsr->szPage); + sqlite3_result_int64(ctx, pCsr->szPage); break; case 10: { /* schema */ sqlite3 *db = sqlite3_context_db_handle(ctx); diff --git a/src/expr.c b/src/expr.c index 7a4e59f28..ea3c90167 100644 --- a/src/expr.c +++ b/src/expr.c @@ -44,48 +44,121 @@ char sqlite3TableColumnAffinity(const Table *pTab, int iCol){ */ char sqlite3ExprAffinity(const Expr *pExpr){ int op; - while( ExprHasProperty(pExpr, EP_Skip|EP_IfNullRow) ){ - assert( pExpr->op==TK_COLLATE - || pExpr->op==TK_IF_NULL_ROW - || (pExpr->op==TK_REGISTER && pExpr->op2==TK_IF_NULL_ROW) ); - pExpr = pExpr->pLeft; - assert( pExpr!=0 ); - } op = pExpr->op; - if( op==TK_REGISTER ) op = pExpr->op2; - if( op==TK_COLUMN || op==TK_AGG_COLUMN ){ - assert( ExprUseYTab(pExpr) ); - assert( pExpr->y.pTab!=0 ); - return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); - } - if( op==TK_SELECT ){ - assert( ExprUseXSelect(pExpr) ); - assert( pExpr->x.pSelect!=0 ); - assert( pExpr->x.pSelect->pEList!=0 ); - assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); - return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); - } + while( 1 /* exit-by-break */ ){ + if( op==TK_COLUMN || (op==TK_AGG_COLUMN && pExpr->y.pTab!=0) ){ + assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); + return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + } + if( op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); + assert( pExpr->x.pSelect!=0 ); + assert( pExpr->x.pSelect->pEList!=0 ); + assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); + return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); + } #ifndef SQLITE_OMIT_CAST - if( op==TK_CAST ){ - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - return sqlite3AffinityType(pExpr->u.zToken, 0); - } + if( op==TK_CAST ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + return sqlite3AffinityType(pExpr->u.zToken, 0); + } #endif - if( op==TK_SELECT_COLUMN ){ - assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); - assert( pExpr->iColumn < pExpr->iTable ); - assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); - return sqlite3ExprAffinity( - pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr - ); - } - if( op==TK_VECTOR ){ - assert( ExprUseXList(pExpr) ); - return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); + if( op==TK_SELECT_COLUMN ){ + assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); + assert( pExpr->iColumn < pExpr->iTable ); + assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); + return sqlite3ExprAffinity( + pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr + ); + } + if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); + return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); + } + if( ExprHasProperty(pExpr, EP_Skip|EP_IfNullRow) ){ + assert( pExpr->op==TK_COLLATE + || pExpr->op==TK_IF_NULL_ROW + || (pExpr->op==TK_REGISTER && pExpr->op2==TK_IF_NULL_ROW) ); + pExpr = pExpr->pLeft; + op = pExpr->op; + continue; + } + if( op!=TK_REGISTER || (op = pExpr->op2)==TK_REGISTER ) break; } return pExpr->affExpr; } +/* +** Make a guess at all the possible datatypes of the result that could +** be returned by an expression. Return a bitmask indicating the answer: +** +** 0x01 Numeric +** 0x02 Text +** 0x04 Blob +** +** If the expression must return NULL, then 0x00 is returned. +*/ +int sqlite3ExprDataType(const Expr *pExpr){ + while( pExpr ){ + switch( pExpr->op ){ + case TK_COLLATE: + case TK_IF_NULL_ROW: + case TK_UPLUS: { + pExpr = pExpr->pLeft; + break; + } + case TK_NULL: { + pExpr = 0; + break; + } + case TK_STRING: { + return 0x02; + } + case TK_BLOB: { + return 0x04; + } + case TK_CONCAT: { + return 0x06; + } + case TK_VARIABLE: + case TK_AGG_FUNCTION: + case TK_FUNCTION: { + return 0x07; + } + case TK_COLUMN: + case TK_AGG_COLUMN: + case TK_SELECT: + case TK_CAST: + case TK_SELECT_COLUMN: + case TK_VECTOR: { + int aff = sqlite3ExprAffinity(pExpr); + if( aff>=SQLITE_AFF_NUMERIC ) return 0x05; + if( aff==SQLITE_AFF_TEXT ) return 0x06; + return 0x07; + } + case TK_CASE: { + int res = 0; + int ii; + ExprList *pList = pExpr->x.pList; + assert( ExprUseXList(pExpr) && pList!=0 ); + assert( pList->nExpr > 0); + for(ii=1; iinExpr; ii+=2){ + res |= sqlite3ExprDataType(pList->a[ii].pExpr); + } + if( pList->nExpr % 2 ){ + res |= sqlite3ExprDataType(pList->a[pList->nExpr-1].pExpr); + } + return res; + } + default: { + return 0x01; + } + } /* End of switch(op) */ + } /* End of while(pExpr) */ + return 0x00; +} + /* ** Set the collating sequence for expression pExpr to be the collating ** sequence named by pToken. Return a pointer to a new Expr node that @@ -173,7 +246,9 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ while( p ){ int op = p->op; if( op==TK_REGISTER ) op = p->op2; - if( op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER ){ + if( (op==TK_AGG_COLUMN && p->y.pTab!=0) + || op==TK_COLUMN || op==TK_TRIGGER + ){ int j; assert( ExprUseYTab(p) ); assert( p->y.pTab!=0 ); @@ -3255,6 +3330,9 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ SelectDest dest; /* How to deal with SELECT result */ int nReg; /* Registers to allocate */ Expr *pLimit; /* New limit expression */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; /* Address of OP_Explain instruction */ +#endif Vdbe *v = pParse->pVdbe; assert( v!=0 ); @@ -3307,8 +3385,9 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ ** In both cases, the query is augmented with "LIMIT 1". Any ** preexisting limit is discarded in place of the new LIMIT 1. */ - ExplainQueryPlan((pParse, 1, "%sSCALAR SUBQUERY %d", + ExplainQueryPlan2(addrExplain, (pParse, 1, "%sSCALAR SUBQUERY %d", addrOnce?"":"CORRELATED ", pSel->selId)); + sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, -1); nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1; sqlite3SelectDestInit(&dest, 0, pParse->nMem+1); pParse->nMem += nReg; @@ -3351,6 +3430,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); } + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); /* Subroutine return */ assert( ExprUseYSub(pExpr) ); @@ -4023,11 +4103,14 @@ static int exprCodeInlineFunction( ** the type affinity of the argument. This is used for testing of ** the SQLite type logic. */ - const char *azAff[] = { "blob", "text", "numeric", "integer", "real" }; + const char *azAff[] = { "blob", "text", "numeric", "integer", + "real", "flexnum" }; char aff; assert( nFarg==1 ); aff = sqlite3ExprAffinity(pFarg->a[0].pExpr); - sqlite3VdbeLoadString(v, target, + assert( aff<=SQLITE_AFF_NONE + || (aff>=SQLITE_AFF_BLOB && aff<=SQLITE_AFF_FLEXNUM) ); + sqlite3VdbeLoadString(v, target, (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]); break; } @@ -4037,7 +4120,7 @@ static int exprCodeInlineFunction( } /* -** Check to see if pExpr is one of the indexed expressions on pParse->pIdxExpr. +** Check to see if pExpr is one of the indexed expressions on pParse->pIdxEpr. ** If it is, then resolve the expression by reading from the index and ** return the register into which the value has been read. If pExpr is ** not an indexed expression, then return negative. @@ -4049,7 +4132,7 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup( ){ IndexedExpr *p; Vdbe *v; - for(p=pParse->pIdxExpr; p; p=p->pIENext){ + for(p=pParse->pIdxEpr; p; p=p->pIENext){ int iDataCur = p->iDataCur; if( iDataCur<0 ) continue; if( pParse->iSelfTab ){ @@ -4069,10 +4152,10 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup( sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); sqlite3VdbeGoto(v, 0); - p = pParse->pIdxExpr; - pParse->pIdxExpr = 0; + p = pParse->pIdxEpr; + pParse->pIdxEpr = 0; sqlite3ExprCode(pParse, pExpr, target); - pParse->pIdxExpr = p; + pParse->pIdxEpr = p; sqlite3VdbeJumpHere(v, addr+2); }else{ sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); @@ -4111,7 +4194,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ expr_code_doover: if( pExpr==0 ){ op = TK_NULL; - }else if( pParse->pIdxExpr!=0 + }else if( pParse->pIdxEpr!=0 && !ExprHasProperty(pExpr, EP_Leaf) && (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0 ){ @@ -4128,15 +4211,16 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( pExpr->iAgg>=0 && pExpr->iAggnColumn ); pCol = &pAggInfo->aCol[pExpr->iAgg]; if( !pAggInfo->directMode ){ - assert( pCol->iMem>0 ); - return pCol->iMem; + return AggInfoColumnReg(pAggInfo, pExpr->iAgg); }else if( pAggInfo->useSortingIdx ){ Table *pTab = pCol->pTab; sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, pCol->iSorterColumn, target); - if( pCol->iColumn<0 ){ + if( pTab==0 ){ + /* No comment added */ + }else if( pCol->iColumn<0 ){ VdbeComment((v,"%s.rowid",pTab->zName)); - }else if( ALWAYS(pTab!=0) ){ + }else{ VdbeComment((v,"%s.%s", pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ @@ -4144,6 +4228,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ } } return target; + }else if( pExpr->y.pTab==0 ){ + /* This case happens when the argument to an aggregate function + ** is rewritten by aggregateConvertIndexedExprRefToColumn() */ + sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, pExpr->iColumn, target); + return target; } /* Otherwise, fall thru into the TK_COLUMN case */ /* no break */ deliberate_fall_through @@ -4164,7 +4253,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( pExpr->y.pTab!=0 ); aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); if( aff>SQLITE_AFF_BLOB ){ - static const char zAff[] = "B\000C\000D\000E"; + static const char zAff[] = "B\000C\000D\000E\000F"; assert( SQLITE_AFF_BLOB=='A' ); assert( SQLITE_AFF_TEXT=='B' ); sqlite3VdbeAddOp4(v, OP_Affinity, iReg, 1, 0, @@ -4441,7 +4530,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3ErrorMsg(pParse, "misuse of aggregate: %#T()", pExpr); }else{ - return pInfo->aFunc[pExpr->iAgg].iMem; + return AggInfoFuncReg(pInfo, pExpr->iAgg); } break; } @@ -4730,7 +4819,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( pAggInfo ){ assert( pExpr->iAgg>=0 && pExpr->iAggnColumn ); if( !pAggInfo->directMode ){ - inReg = pAggInfo->aCol[pExpr->iAgg].iMem; + inReg = AggInfoColumnReg(pAggInfo, pExpr->iAgg); break; } if( pExpr->pAggInfo->useSortingIdx ){ @@ -6161,10 +6250,8 @@ int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ ** it does, make a copy. This is done because the pExpr argument is ** subject to change. ** -** The copy is stored on pParse->pConstExpr with a register number of 0. -** This will cause the expression to be deleted automatically when the -** Parse object is destroyed, but the zero register number means that it -** will not generate any code in the preamble. +** The copy is scheduled for deletion using the sqlite3ExprDeferredDelete() +** which builds on the sqlite3ParserAddCleanup() mechanism. */ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ if( ALWAYS(!ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced)) @@ -6175,7 +6262,6 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ Parse *pParse = pWalker->pParse; sqlite3 *db = pParse->db; if( pExpr->op!=TK_AGG_FUNCTION ){ - assert( pExpr->op==TK_AGG_COLUMN || pExpr->op==TK_IF_NULL_ROW ); assert( iAgg>=0 && iAggnColumn ); if( pAggInfo->aCol[iAgg].pCExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); @@ -6242,6 +6328,73 @@ static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){ return i; } +/* +** Search the AggInfo object for an aCol[] entry that has iTable and iColumn. +** Return the index in aCol[] of the entry that describes that column. +** +** If no prior entry is found, create a new one and return -1. The +** new column will have an idex of pAggInfo->nColumn-1. +*/ +static void findOrCreateAggInfoColumn( + Parse *pParse, /* Parsing context */ + AggInfo *pAggInfo, /* The AggInfo object to search and/or modify */ + Expr *pExpr /* Expr describing the column to find or insert */ +){ + struct AggInfo_col *pCol; + int k; + + assert( pAggInfo->iFirstReg==0 ); + pCol = pAggInfo->aCol; + for(k=0; knColumn; k++, pCol++){ + if( pCol->iTable==pExpr->iTable + && pCol->iColumn==pExpr->iColumn + && pExpr->op!=TK_IF_NULL_ROW + ){ + goto fix_up_expr; + } + } + k = addAggInfoColumn(pParse->db, pAggInfo); + if( k<0 ){ + /* OOM on resize */ + assert( pParse->db->mallocFailed ); + return; + } + pCol = &pAggInfo->aCol[k]; + assert( ExprUseYTab(pExpr) ); + pCol->pTab = pExpr->y.pTab; + pCol->iTable = pExpr->iTable; + pCol->iColumn = pExpr->iColumn; + pCol->iSorterColumn = -1; + pCol->pCExpr = pExpr; + if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){ + int j, n; + ExprList *pGB = pAggInfo->pGroupBy; + struct ExprList_item *pTerm = pGB->a; + n = pGB->nExpr; + for(j=0; jpExpr; + if( pE->op==TK_COLUMN + && pE->iTable==pExpr->iTable + && pE->iColumn==pExpr->iColumn + ){ + pCol->iSorterColumn = j; + break; + } + } + } + if( pCol->iSorterColumn<0 ){ + pCol->iSorterColumn = pAggInfo->nSortingColumn++; + } +fix_up_expr: + ExprSetVVAProperty(pExpr, EP_NoReduce); + assert( pExpr->pAggInfo==0 || pExpr->pAggInfo==pAggInfo ); + pExpr->pAggInfo = pAggInfo; + if( pExpr->op==TK_COLUMN ){ + pExpr->op = TK_AGG_COLUMN; + } + pExpr->iAgg = (i16)k; +} + /* ** This is the xExprCallback for a tree walker. It is used to ** implement sqlite3ExprAnalyzeAggregates(). See sqlite3ExprAnalyzeAggregates @@ -6255,7 +6408,37 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ AggInfo *pAggInfo = pNC->uNC.pAggInfo; assert( pNC->ncFlags & NC_UAggInfo ); + assert( pAggInfo->iFirstReg==0 ); switch( pExpr->op ){ + default: { + IndexedExpr *pIEpr; + Expr tmp; + assert( pParse->iSelfTab==0 ); + if( (pNC->ncFlags & NC_InAggFunc)==0 ) break; + if( pParse->pIdxEpr==0 ) break; + for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ + int iDataCur = pIEpr->iDataCur; + if( iDataCur<0 ) continue; + if( sqlite3ExprCompare(0, pExpr, pIEpr->pExpr, iDataCur)==0 ) break; + } + if( pIEpr==0 ) break; + if( NEVER(!ExprUseYTab(pExpr)) ) break; + if( pExpr->pAggInfo!=0 ) break; /* Already resolved by outer context */ + + /* If we reach this point, it means that expression pExpr can be + ** translated into a reference to an index column as described by + ** pIEpr. + */ + memset(&tmp, 0, sizeof(tmp)); + tmp.op = TK_AGG_COLUMN; + tmp.iTable = pIEpr->iIdxCur; + tmp.iColumn = pIEpr->iIdxCol; + findOrCreateAggInfoColumn(pParse, pAggInfo, &tmp); + pAggInfo->aCol[tmp.iAgg].pCExpr = pExpr; + pExpr->pAggInfo = pAggInfo; + pExpr->iAgg = tmp.iAgg; + return WRC_Prune; + } case TK_IF_NULL_ROW: case TK_AGG_COLUMN: case TK_COLUMN: { @@ -6267,67 +6450,9 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ if( ALWAYS(pSrcList!=0) ){ SrcItem *pItem = pSrcList->a; for(i=0; inSrc; i++, pItem++){ - struct AggInfo_col *pCol; assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); if( pExpr->iTable==pItem->iCursor ){ - /* If we reach this point, it means that pExpr refers to a table - ** that is in the FROM clause of the aggregate query. - ** - ** Make an entry for the column in pAggInfo->aCol[] if there - ** is not an entry there already. - */ - int k; - pCol = pAggInfo->aCol; - for(k=0; knColumn; k++, pCol++){ - if( pCol->iTable==pExpr->iTable - && pCol->iColumn==pExpr->iColumn - && pExpr->op!=TK_IF_NULL_ROW - ){ - break; - } - } - if( (k>=pAggInfo->nColumn) - && (k = addAggInfoColumn(pParse->db, pAggInfo))>=0 - ){ - pCol = &pAggInfo->aCol[k]; - assert( ExprUseYTab(pExpr) ); - pCol->pTab = pExpr->y.pTab; - pCol->iTable = pExpr->iTable; - pCol->iColumn = pExpr->iColumn; - pCol->iMem = ++pParse->nMem; - pCol->iSorterColumn = -1; - pCol->pCExpr = pExpr; - if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){ - int j, n; - ExprList *pGB = pAggInfo->pGroupBy; - struct ExprList_item *pTerm = pGB->a; - n = pGB->nExpr; - for(j=0; jpExpr; - if( pE->op==TK_COLUMN - && pE->iTable==pExpr->iTable - && pE->iColumn==pExpr->iColumn - ){ - pCol->iSorterColumn = j; - break; - } - } - } - if( pCol->iSorterColumn<0 ){ - pCol->iSorterColumn = pAggInfo->nSortingColumn++; - } - } - /* There is now an entry for pExpr in pAggInfo->aCol[] (either - ** because it was there before or because we just created it). - ** Convert the pExpr to be a TK_AGG_COLUMN referring to that - ** pAggInfo->aCol[] entry. - */ - ExprSetVVAProperty(pExpr, EP_NoReduce); - pExpr->pAggInfo = pAggInfo; - if( pExpr->op==TK_COLUMN ){ - pExpr->op = TK_AGG_COLUMN; - } - pExpr->iAgg = (i16)k; + findOrCreateAggInfoColumn(pParse, pAggInfo, pExpr); break; } /* endif pExpr->iTable==pItem->iCursor */ } /* end loop over pSrcList */ @@ -6357,7 +6482,6 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); pItem = &pAggInfo->aFunc[i]; pItem->pFExpr = pExpr; - pItem->iMem = ++pParse->nMem; assert( ExprUseUToken(pExpr) ); pItem->pFunc = sqlite3FindFunction(pParse->db, pExpr->u.zToken, diff --git a/src/func.c b/src/func.c index b42b2588a..c1efd8acf 100644 --- a/src/func.c +++ b/src/func.c @@ -1082,7 +1082,7 @@ void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){ } case SQLITE_BLOB: { char const *zBlob = sqlite3_value_blob(pValue); - int nBlob = sqlite3_value_bytes(pValue); + i64 nBlob = sqlite3_value_bytes(pValue); assert( zBlob==sqlite3_value_blob(pValue) ); /* No encoding change */ sqlite3StrAccumEnlarge(pStr, nBlob*2 + 4); if( pStr->accError==0 ){ @@ -1223,6 +1223,96 @@ static void hexFunc( } } +/* +** Buffer zStr contains nStr bytes of utf-8 encoded text. Return 1 if zStr +** contains character ch, or 0 if it does not. +*/ +static int strContainsChar(const u8 *zStr, int nStr, u32 ch){ + const u8 *zEnd = &zStr[nStr]; + const u8 *z = zStr; + while( zcount; } if( pHash ) *pHash = h; - while( count-- ){ + while( count ){ assert( elem!=0 ); if( sqlite3StrICmp(elem->pKey,pKey)==0 ){ return elem; } elem = elem->next; + count--; } return &nullElement; } diff --git a/src/hwtime.h b/src/hwtime.h index 037c55a97..d27204a69 100644 --- a/src/hwtime.h +++ b/src/hwtime.h @@ -48,9 +48,9 @@ #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; } #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) diff --git a/src/insert.c b/src/insert.c index 1f6cdffcc..65d11bee5 100644 --- a/src/insert.c +++ b/src/insert.c @@ -1793,6 +1793,7 @@ void sqlite3GenerateConstraintChecks( case OE_Fail: { char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, pCol->zCnName); + testcase( zMsg==0 && db->mallocFailed==0 ); sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, iReg); sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); diff --git a/src/json.c b/src/json.c index 3636d3665..67448421b 100644 --- a/src/json.c +++ b/src/json.c @@ -2473,6 +2473,13 @@ static int jsonEachBestIndex( idxMask |= iMask; } } + if( pIdxInfo->nOrderBy>0 + && pIdxInfo->aOrderBy[0].iColumn<0 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; + } + if( (unusableMask & ~idxMask)!=0 ){ /* If there are any unusable constraints on JSON or ROOT, then reject ** this entire plan */ @@ -2668,10 +2675,10 @@ void sqlite3RegisterJsonFunctions(void){ #endif WAGGREGATE(json_group_array, 1, 0, 0, jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS), + SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), WAGGREGATE(json_group_object, 2, 0, 0, jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS) + SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC) }; sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); #endif diff --git a/src/loadext.c b/src/loadext.c index c14338f8b..40bde9ce8 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -510,7 +510,9 @@ static const sqlite3_api_routines sqlite3Apis = { #endif sqlite3_db_name, /* Version 3.40.0 and later */ - sqlite3_value_encoding + sqlite3_value_encoding, + /* Version 3.41.0 and later */ + sqlite3_is_interrupted }; /* True if x is the directory separator character diff --git a/src/main.c b/src/main.c index 4008d71d6..eaecb56df 100644 --- a/src/main.c +++ b/src/main.c @@ -1566,6 +1566,7 @@ const char *sqlite3ErrName(int rc){ case SQLITE_NOTICE_RECOVER_WAL: zName = "SQLITE_NOTICE_RECOVER_WAL";break; case SQLITE_NOTICE_RECOVER_ROLLBACK: zName = "SQLITE_NOTICE_RECOVER_ROLLBACK"; break; + case SQLITE_NOTICE_RBU: zName = "SQLITE_NOTICE_RBU"; break; case SQLITE_WARNING: zName = "SQLITE_WARNING"; break; case SQLITE_WARNING_AUTOINDEX: zName = "SQLITE_WARNING_AUTOINDEX"; break; case SQLITE_DONE: zName = "SQLITE_DONE"; break; @@ -1795,7 +1796,9 @@ int sqlite3_busy_timeout(sqlite3 *db, int ms){ */ void sqlite3_interrupt(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) ){ + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ (void)SQLITE_MISUSE_BKPT; return; } @@ -1803,6 +1806,21 @@ void sqlite3_interrupt(sqlite3 *db){ AtomicStore(&db->u1.isInterrupted, 1); } +/* +** Return true or false depending on whether or not an interrupt is +** pending on connection db. +*/ +int sqlite3_is_interrupted(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return AtomicLoad(&db->u1.isInterrupted)!=0; +} /* ** This function is exactly the same as sqlite3_create_function(), except @@ -1847,7 +1865,7 @@ int sqlite3CreateFunc( /* The SQLITE_INNOCUOUS flag is the same bit as SQLITE_FUNC_UNSAFE. But ** the meaning is inverted. So flip the bit. */ assert( SQLITE_FUNC_UNSAFE==SQLITE_INNOCUOUS ); - extraFlags ^= SQLITE_FUNC_UNSAFE; + extraFlags ^= SQLITE_FUNC_UNSAFE; /* tag-20230109-1 */ #ifndef SQLITE_OMIT_UTF16 @@ -1865,11 +1883,11 @@ int sqlite3CreateFunc( case SQLITE_ANY: { int rc; rc = sqlite3CreateFunc(db, zFunctionName, nArg, - (SQLITE_UTF8|extraFlags)^SQLITE_FUNC_UNSAFE, + (SQLITE_UTF8|extraFlags)^SQLITE_FUNC_UNSAFE, /* tag-20230109-1 */ pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor); if( rc==SQLITE_OK ){ rc = sqlite3CreateFunc(db, zFunctionName, nArg, - (SQLITE_UTF16LE|extraFlags)^SQLITE_FUNC_UNSAFE, + (SQLITE_UTF16LE|extraFlags)^SQLITE_FUNC_UNSAFE, /* tag-20230109-1*/ pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor); } if( rc!=SQLITE_OK ){ diff --git a/src/malloc.c b/src/malloc.c index af83743fc..48c460060 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -279,7 +279,7 @@ static void mallocWithAlarm(int n, void **pp){ ** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 ** This provides a 256-byte safety margin for defense against 32-bit ** signed integer overflow bugs when computing memory allocation sizes. -** Parnoid applications might want to reduce the maximum allocation size +** Paranoid applications might want to reduce the maximum allocation size ** further for an even larger safety margin. 0x3fffffff or 0x0fffffff ** or even smaller would be reasonable upper bounds on the size of a memory ** allocations for most applications. @@ -793,9 +793,14 @@ char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){ */ char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ int n; +#ifdef SQLITE_DEBUG + /* Because of the way the parser works, the span is guaranteed to contain + ** at least one non-space character */ + for(n=0; sqlite3Isspace(zStart[n]); n++){ assert( &zStart[n]0) && sqlite3Isspace(zStart[n-1]) ) n--; + while( sqlite3Isspace(zStart[n-1]) ) n--; return sqlite3DbStrNDup(db, zStart, n); } diff --git a/src/os_common.h b/src/os_common.h index 1ed4d7a8e..5b532af0a 100644 --- a/src/os_common.h +++ b/src/os_common.h @@ -35,12 +35,6 @@ */ #ifdef SQLITE_PERFORMANCE_TRACE -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -#include "hwtime.h" - static sqlite_uint64 g_start; static sqlite_uint64 g_elapsed; #define TIMER_START g_start=sqlite3Hwtime() diff --git a/src/os_kv.c b/src/os_kv.c index 322588be9..5e0ea49f1 100644 --- a/src/os_kv.c +++ b/src/os_kv.c @@ -52,7 +52,9 @@ struct KVVfsFile { char *aJrnl; /* Journal content */ int szPage; /* Last known page size */ sqlite3_int64 szDb; /* Database file size. -1 means unknown */ + char *aData; /* Buffer to hold page data */ }; +#define SQLITE_KVOS_SZ 133073 /* ** Methods for KVVfsFile @@ -415,8 +417,7 @@ static int kvvfsDecode(const char *a, char *aOut, int nOut){ if( j+n>nOut ) return -1; memset(&aOut[j], 0, n); j += n; - c = aIn[i]; - if( c==0 ) break; + if( c==0 || mult==1 ) break; /* progress stalled if mult==1 */ }else{ aOut[j] = c<<4; c = kvvfsHexValue[aIn[++i]]; @@ -493,6 +494,7 @@ static int kvvfsClose(sqlite3_file *pProtoFile){ SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); + sqlite3_free(pFile->aData); return SQLITE_OK; } @@ -541,7 +543,7 @@ static int kvvfsReadDb( unsigned int pgno; int got, n; char zKey[30]; - char aData[133073]; + char *aData = pFile->aData; assert( iOfst>=0 ); assert( iAmt>=0 ); SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); @@ -558,7 +560,8 @@ static int kvvfsReadDb( pgno = 1; } sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); - got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, aData, sizeof(aData)-1); + got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, + aData, SQLITE_KVOS_SZ-1); if( got<0 ){ n = 0; }else{ @@ -566,7 +569,7 @@ static int kvvfsReadDb( if( iOfst+iAmt<512 ){ int k = iOfst+iAmt; aData[k*2] = 0; - n = kvvfsDecode(aData, &aData[2000], sizeof(aData)-2000); + n = kvvfsDecode(aData, &aData[2000], SQLITE_KVOS_SZ-2000); if( n>=iOfst+iAmt ){ memcpy(zBuf, &aData[2000+iOfst], iAmt); n = iAmt; @@ -625,7 +628,7 @@ static int kvvfsWriteDb( KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; char zKey[30]; - char aData[131073]; + char *aData = pFile->aData; SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); assert( iAmt>=512 && iAmt<=65536 ); assert( (iAmt & (iAmt-1))==0 ); @@ -834,6 +837,10 @@ static int kvvfsOpen( }else{ pFile->zClass = "local"; } + pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ); + if( pFile->aData==0 ){ + return SQLITE_NOMEM; + } pFile->aJrnl = 0; pFile->nJrnl = 0; pFile->szPage = -1; diff --git a/src/os_unix.c b/src/os_unix.c index 722e4b2c3..6e9ee3263 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -95,7 +95,8 @@ #include #include /* amalgamator: keep */ #include -#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) # include #endif @@ -183,9 +184,46 @@ */ #define SQLITE_MAX_SYMLINKS 100 +/* +** Remove and stub certain info for WASI (WebAssembly System +** Interface) builds. +*/ +#ifdef SQLITE_WASI +# undef HAVE_FCHMOD +# undef HAVE_FCHOWN +# undef HAVE_MREMAP +# define HAVE_MREMAP 0 +# ifndef SQLITE_DEFAULT_UNIX_VFS +# define SQLITE_DEFAULT_UNIX_VFS "unix-dotfile" + /* ^^^ should SQLITE_DEFAULT_UNIX_VFS be "unix-none"? */ +# endif +# ifndef F_RDLCK +# define F_RDLCK 0 +# define F_WRLCK 1 +# define F_UNLCK 2 +# if __LONG_MAX == 0x7fffffffL +# define F_GETLK 12 +# define F_SETLK 13 +# define F_SETLKW 14 +# else +# define F_GETLK 5 +# define F_SETLK 6 +# define F_SETLKW 7 +# endif +# endif +#else /* !SQLITE_WASI */ +# ifndef HAVE_FCHMOD +# define HAVE_FCHMOD +# endif +#endif /* SQLITE_WASI */ + +#ifdef SQLITE_WASI +# define osGetpid(X) (pid_t)1 +#else /* Always cast the getpid() return type for compatibility with ** kernel modules in VxWorks. */ -#define osGetpid(X) (pid_t)getpid() +# define osGetpid(X) (pid_t)getpid() +#endif /* ** Only set the lastErrno if the error code is a real error and not @@ -457,7 +495,11 @@ static struct unix_syscall { #define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\ aSyscall[13].pCurrent) +#if defined(HAVE_FCHMOD) { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, +#else + { "fchmod", (sqlite3_syscall_ptr)0, 0 }, +#endif #define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE @@ -493,14 +535,16 @@ static struct unix_syscall { #endif #define osGeteuid ((uid_t(*)(void))aSyscall[21].pCurrent) -#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) { "mmap", (sqlite3_syscall_ptr)mmap, 0 }, #else { "mmap", (sqlite3_syscall_ptr)0, 0 }, #endif #define osMmap ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[22].pCurrent) -#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) { "munmap", (sqlite3_syscall_ptr)munmap, 0 }, #else { "munmap", (sqlite3_syscall_ptr)0, 0 }, @@ -1651,7 +1695,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){ ** ** UNLOCKED -> SHARED ** SHARED -> RESERVED -** SHARED -> (PENDING) -> EXCLUSIVE +** SHARED -> EXCLUSIVE ** RESERVED -> (PENDING) -> EXCLUSIVE ** PENDING -> EXCLUSIVE ** @@ -1684,19 +1728,20 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** A RESERVED lock is implemented by grabbing a write-lock on the ** 'reserved byte'. ** - ** A process may only obtain a PENDING lock after it has obtained a - ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock - ** on the 'pending byte'. This ensures that no new SHARED locks can be - ** obtained, but existing SHARED locks are allowed to persist. A process - ** does not have to obtain a RESERVED lock on the way to a PENDING lock. - ** This property is used by the algorithm for rolling back a journal file - ** after a crash. + ** An EXCLUSIVE lock may only be requested after either a SHARED or + ** RESERVED lock is held. An EXCLUSIVE lock is implemented by obtaining + ** a write-lock on the entire 'shared byte range'. Since all other locks + ** require a read-lock on one of the bytes within this range, this ensures + ** that no other locks are held on the database. ** - ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is - ** implemented by obtaining a write-lock on the entire 'shared byte - ** range'. Since all other locks require a read-lock on one of the bytes - ** within this range, this ensures that no other locks are held on the - ** database. + ** If a process that holds a RESERVED lock requests an EXCLUSIVE, then + ** a PENDING lock is obtained first. A PENDING lock is implemented by + ** obtaining a write-lock on the 'pending byte'. This ensures that no new + ** SHARED locks can be obtained, but existing SHARED locks are allowed to + ** persist. If the call to this function fails to obtain the EXCLUSIVE + ** lock in this case, it holds the PENDING lock intead. The client may + ** then re-attempt the EXCLUSIVE lock later on, after existing SHARED + ** locks have cleared. */ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; @@ -1767,7 +1812,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ lock.l_len = 1L; lock.l_whence = SEEK_SET; if( eFileLock==SHARED_LOCK - || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLockeFileLock==RESERVED_LOCK) ){ lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK); lock.l_start = PENDING_BYTE; @@ -1778,6 +1823,9 @@ static int unixLock(sqlite3_file *id, int eFileLock){ storeLastErrno(pFile, tErrno); } goto end_lock; + }else if( eFileLock==EXCLUSIVE_LOCK ){ + pFile->eFileLock = PENDING_LOCK; + pInode->eFileLock = PENDING_LOCK; } } @@ -1865,13 +1913,9 @@ static int unixLock(sqlite3_file *id, int eFileLock){ } #endif - if( rc==SQLITE_OK ){ pFile->eFileLock = eFileLock; pInode->eFileLock = eFileLock; - }else if( eFileLock==EXCLUSIVE_LOCK ){ - pFile->eFileLock = PENDING_LOCK; - pInode->eFileLock = PENDING_LOCK; } end_lock: @@ -6462,12 +6506,10 @@ static void appendOnePathElement( if( zName[0]=='.' ){ if( nName==1 ) return; if( zName[1]=='.' && nName==2 ){ - if( pPath->nUsed<=1 ){ - pPath->rc = SQLITE_ERROR; - return; + if( pPath->nUsed>1 ){ + assert( pPath->zOut[0]=='/' ); + while( pPath->zOut[--pPath->nUsed]!='/' ){} } - assert( pPath->zOut[0]=='/' ); - while( pPath->zOut[--pPath->nUsed]!='/' ){} return; } } @@ -6679,7 +6721,7 @@ static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ ** than the argument. */ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){ -#if OS_VXWORKS +#if OS_VXWORKS || _POSIX_C_SOURCE >= 199309L struct timespec sp; sp.tv_sec = microseconds / 1000000; diff --git a/src/pager.c b/src/pager.c index 883e6532c..89073b8c7 100644 --- a/src/pager.c +++ b/src/pager.c @@ -2935,7 +2935,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** see if it is possible to delete the super-journal. */ assert( zSuper==&pPager->pTmpSpace[4] ); - memset(&zSuper[-4], 0, 4); + memset(pPager->pTmpSpace, 0, 4); rc = pager_delsuper(pPager, zSuper); testcase( rc!=SQLITE_OK ); } @@ -3556,7 +3556,6 @@ void sqlite3PagerShrink(Pager *pPager){ ** Numeric values associated with these states are OFF==1, NORMAL=2, ** and FULL=3. */ -#ifndef SQLITE_OMIT_PAGER_PRAGMAS void sqlite3PagerSetFlags( Pager *pPager, /* The pager to set safety level for */ unsigned pgFlags /* Various flags */ @@ -3591,7 +3590,6 @@ void sqlite3PagerSetFlags( pPager->doNotSpill |= SPILLFLAG_OFF; } } -#endif /* ** The following global variable is incremented whenever the library @@ -4693,7 +4691,6 @@ int sqlite3PagerOpen( u32 szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; /* Default page size */ const char *zUri = 0; /* URI args to copy */ int nUriByte = 1; /* Number of bytes of URI args at *zUri */ - int nUri = 0; /* Number of URI parameters */ /* Figure out how much space is required for each journal file-handle ** (there are two of them, the main journal and the sub-journal). */ @@ -4741,7 +4738,6 @@ int sqlite3PagerOpen( while( *z ){ z += strlen(z)+1; z += strlen(z)+1; - nUri++; } nUriByte = (int)(&z[1] - zUri); assert( nUriByte>=1 ); @@ -4997,18 +4993,7 @@ int sqlite3PagerOpen( pPager->memDb = (u8)memDb; pPager->readOnly = (u8)readOnly; assert( useJournal || pPager->tempFile ); - pPager->noSync = pPager->tempFile; - if( pPager->noSync ){ - assert( pPager->fullSync==0 ); - assert( pPager->extraSync==0 ); - assert( pPager->syncFlags==0 ); - assert( pPager->walSyncFlags==0 ); - }else{ - pPager->fullSync = 1; - pPager->extraSync = 0; - pPager->syncFlags = SQLITE_SYNC_NORMAL; - pPager->walSyncFlags = SQLITE_SYNC_NORMAL | (SQLITE_SYNC_NORMAL<<2); - } + sqlite3PagerSetFlags(pPager, (SQLITE_DEFAULT_SYNCHRONOUS+1)|PAGER_CACHESPILL); /* pPager->pFirst = 0; */ /* pPager->pFirstSynced = 0; */ /* pPager->pLast = 0; */ @@ -6269,7 +6254,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ # define DIRECT_MODE isDirectMode #endif - if( !pPager->changeCountDone && ALWAYS(pPager->dbSize>0) ){ + if( !pPager->changeCountDone && pPager->dbSize>0 ){ PgHdr *pPgHdr; /* Reference to page 1 */ assert( !pPager->tempFile && isOpen(pPager->fd) ); @@ -7009,7 +6994,11 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ */ const char *sqlite3PagerFilename(const Pager *pPager, int nullIfMemDb){ static const char zFake[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - return (nullIfMemDb && pPager->memDb) ? &zFake[4] : pPager->zFilename; + if( nullIfMemDb && (pPager->memDb || sqlite3IsMemdb(pPager->pVfs)) ){ + return &zFake[4]; + }else{ + return pPager->zFilename; + } } /* diff --git a/src/parse.y b/src/parse.y index d627f22ba..760cb114a 100644 --- a/src/parse.y +++ b/src/parse.y @@ -1302,6 +1302,11 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { sqlite3ExprListDelete(pParse->db, Y); pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); A = sqlite3PExpr(pParse, TK_EQ, A, pRHS); + }else if( Y->nExpr==1 && pRHS->op==TK_SELECT ){ + A = sqlite3PExpr(pParse, TK_IN, A, 0); + sqlite3PExprAddSelect(pParse, A, pRHS->x.pSelect); + pRHS->x.pSelect = 0; + sqlite3ExprListDelete(pParse->db, Y); }else{ A = sqlite3PExpr(pParse, TK_IN, A, 0); if( A==0 ){ diff --git a/src/pragma.c b/src/pragma.c index a0aa123fc..522a12d33 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1785,12 +1785,21 @@ void sqlite3Pragma( ** will also prepopulate the cursor column cache that is used ** by the OP_IsType code, so it is a required step. */ - mxCol = pTab->nCol-1; - while( mxCol>=0 - && ((pTab->aCol[mxCol].colFlags & COLFLAG_VIRTUAL)!=0 - || pTab->iPKey==mxCol) ) mxCol--; + assert( !IsVirtual(pTab) ); + if( HasRowid(pTab) ){ + mxCol = -1; + for(j=0; jnCol; j++){ + if( (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)==0 ) mxCol++; + } + if( mxCol==pTab->iPKey ) mxCol--; + }else{ + /* COLFLAG_VIRTUAL columns are not included in the WITHOUT ROWID + ** PK index column-count, so there is no need to account for them + ** in this case. */ + mxCol = sqlite3PrimaryKeyIndex(pTab)->nColumn-1; + } if( mxCol>=0 ){ - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, mxCol, 3); + sqlite3VdbeAddOp3(v, OP_Column, iDataCur, mxCol, 3); sqlite3VdbeTypeofColumn(v, 3); } @@ -1958,7 +1967,8 @@ void sqlite3Pragma( if( !isQuick ){ /* Omit the remaining tests for quick_check */ /* Validate index entries for the current row */ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - int jmp2, jmp3, jmp4, jmp5; + int jmp2, jmp3, jmp4, jmp5, label6; + int kk; int ckUniq = sqlite3VdbeMakeLabel(pParse); if( pPk==pIdx ) continue; r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3, @@ -1976,13 +1986,32 @@ void sqlite3Pragma( sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); jmp4 = integrityCheckResultRow(v); sqlite3VdbeJumpHere(v, jmp2); + + /* Any indexed columns with non-BINARY collations must still hold + ** the exact same text value as the table. */ + label6 = 0; + for(kk=0; kknKeyCol; kk++){ + if( pIdx->azColl[kk]==sqlite3StrBINARY ) continue; + if( label6==0 ) label6 = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur+j, kk, 3); + sqlite3VdbeAddOp3(v, OP_Ne, 3, label6, r1+kk); VdbeCoverage(v); + } + if( label6 ){ + int jmp6 = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeResolveLabel(v, label6); + sqlite3VdbeLoadString(v, 3, "row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " values differ from index "); + sqlite3VdbeGoto(v, jmp5-1); + sqlite3VdbeJumpHere(v, jmp6); + } + /* For UNIQUE indexes, verify that only one entry exists with the ** current key. The entry is unique if (1) any column is NULL ** or (2) the next entry has a different key */ if( IsUniqueIndex(pIdx) ){ int uniqOk = sqlite3VdbeMakeLabel(pParse); int jmp6; - int kk; for(kk=0; kknKeyCol; kk++){ int iCol = pIdx->aiColumn[kk]; assert( iCol!=XN_ROWID && iColnCol ); diff --git a/src/prepare.c b/src/prepare.c index 1e7a1222b..b2613e2c1 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -306,7 +306,12 @@ int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){ #else encoding = SQLITE_UTF8; #endif - sqlite3SetTextEncoding(db, encoding); + if( db->nVdbeActive>0 && encoding!=ENC(db) ){ + rc = SQLITE_LOCKED; + goto initone_error_out; + }else{ + sqlite3SetTextEncoding(db, encoding); + } }else{ /* If opening an attached database, the encoding much match ENC(db) */ if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){ @@ -520,8 +525,8 @@ static void schemaIsValid(Parse *pParse){ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){ + if( DbHasProperty(db, iDb, DB_SchemaLoaded) ) pParse->rc = SQLITE_SCHEMA; sqlite3ResetOneSchema(db, iDb); - pParse->rc = SQLITE_SCHEMA; } /* Close the transaction, if one was opened. */ diff --git a/src/printf.c b/src/printf.c index 3602e1fcb..335ad0684 100644 --- a/src/printf.c +++ b/src/printf.c @@ -736,13 +736,26 @@ void sqlite3_str_vappendf( } } if( precision>1 ){ + i64 nPrior = 1; width -= precision-1; if( width>1 && !flag_leftjustify ){ sqlite3_str_appendchar(pAccum, width-1, ' '); width = 0; } - while( precision-- > 1 ){ - sqlite3_str_append(pAccum, buf, length); + sqlite3_str_append(pAccum, buf, length); + precision--; + while( precision > 1 ){ + i64 nCopyBytes; + if( nPrior > precision-1 ) nPrior = precision - 1; + nCopyBytes = length*nPrior; + if( nCopyBytes + pAccum->nChar >= pAccum->nAlloc ){ + sqlite3StrAccumEnlarge(pAccum, nCopyBytes); + } + if( pAccum->accError ) break; + sqlite3_str_append(pAccum, + &pAccum->zText[pAccum->nChar-nCopyBytes], nCopyBytes); + precision -= nPrior; + nPrior *= 2; } } bufpt = buf; @@ -970,9 +983,9 @@ void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){ ** Return the number of bytes of text that StrAccum is able to accept ** after the attempted enlargement. The value returned might be zero. */ -int sqlite3StrAccumEnlarge(StrAccum *p, int N){ +int sqlite3StrAccumEnlarge(StrAccum *p, i64 N){ char *zNew; - assert( p->nChar+(i64)N >= p->nAlloc ); /* Only called if really needed */ + assert( p->nChar+N >= p->nAlloc ); /* Only called if really needed */ if( p->accError ){ testcase(p->accError==SQLITE_TOOBIG); testcase(p->accError==SQLITE_NOMEM); @@ -983,8 +996,7 @@ int sqlite3StrAccumEnlarge(StrAccum *p, int N){ return p->nAlloc - p->nChar - 1; }else{ char *zOld = isMalloced(p) ? p->zText : 0; - i64 szNew = p->nChar; - szNew += (sqlite3_int64)N + 1; + i64 szNew = p->nChar + N + 1; if( szNew+p->nChar<=p->mxAlloc ){ /* Force exponential buffer size growth as long as it does not overflow, ** to avoid having to call this routine too often */ @@ -1014,7 +1026,8 @@ int sqlite3StrAccumEnlarge(StrAccum *p, int N){ return 0; } } - return N; + assert( N>=0 && N<=0x7fffffff ); + return (int)N; } /* diff --git a/src/resolve.c b/src/resolve.c index 1c3a9d909..9677f9de9 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -202,6 +202,32 @@ static void extendFJMatch( } } +/* +** Return TRUE (non-zero) if zTab is a valid name for the schema table pTab. +*/ +static SQLITE_NOINLINE int isValidSchemaTableName( + const char *zTab, /* Name as it appears in the SQL */ + Table *pTab, /* The schema table we are trying to match */ + Schema *pSchema /* non-NULL if a database qualifier is present */ +){ + const char *zLegacy; + assert( pTab!=0 ); + assert( pTab->tnum==1 ); + if( sqlite3StrNICmp(zTab, "sqlite_", 7)!=0 ) return 0; + zLegacy = pTab->zName; + if( strcmp(zLegacy+7, &LEGACY_TEMP_SCHEMA_TABLE[7])==0 ){ + if( sqlite3StrICmp(zTab+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ + return 1; + } + if( pSchema==0 ) return 0; + if( sqlite3StrICmp(zTab+7, &LEGACY_SCHEMA_TABLE[7])==0 ) return 1; + if( sqlite3StrICmp(zTab+7, &PREFERRED_SCHEMA_TABLE[7])==0 ) return 1; + }else{ + if( sqlite3StrICmp(zTab+7, &PREFERRED_SCHEMA_TABLE[7])==0 ) return 1; + } + return 0; +} + /* ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up ** that name in the set of source tables in pSrcList and make the pExpr @@ -355,15 +381,17 @@ static int lookupName( } assert( zDb==0 || zTab!=0 ); if( zTab ){ - const char *zTabName; if( zDb ){ if( pTab->pSchema!=pSchema ) continue; if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue; } - zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName; - assert( zTabName!=0 ); - if( sqlite3StrICmp(zTabName, zTab)!=0 ){ - continue; + if( pItem->zAlias!=0 ){ + if( sqlite3StrICmp(zTab, pItem->zAlias)!=0 ){ + continue; + } + }else if( sqlite3StrICmp(zTab, pTab->zName)!=0 ){ + if( pTab->tnum!=1 ) continue; + if( !isValidSchemaTableName(zTab, pTab, pSchema) ) continue; } assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT && pItem->zAlias ){ @@ -506,6 +534,7 @@ static int lookupName( if( pParse->bReturning ){ eNewExprOp = TK_REGISTER; pExpr->op2 = TK_COLUMN; + pExpr->iColumn = iCol; pExpr->iTable = pNC->uNC.iBaseReg + (pTab->nCol+1)*pExpr->iTable + sqlite3TableColumnToStorage(pTab, iCol) + 1; }else{ diff --git a/src/select.c b/src/select.c index b0f79f840..1b5a1b506 100644 --- a/src/select.c +++ b/src/select.c @@ -65,6 +65,10 @@ struct SortCtx { } aDefer[4]; #endif struct RowLoadInfo *pDeferredRowLoad; /* Deferred row loading info or NULL */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrPush; /* First instruction to push data into sorter */ + int addrPushEnd; /* Last instruction that pushes data into sorter */ +#endif }; #define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ @@ -721,6 +725,10 @@ static void pushOntoSorter( */ assert( nData==1 || regData==regOrigData || regOrigData==0 ); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pSort->addrPush = sqlite3VdbeCurrentAddr(v); +#endif + if( nPrefixReg ){ assert( nPrefixReg==nExpr+bSeq ); regBase = regData - nPrefixReg; @@ -821,6 +829,9 @@ static void pushOntoSorter( sqlite3VdbeChangeP2(v, iSkip, pSort->labelOBLopt ? pSort->labelOBLopt : sqlite3VdbeCurrentAddr(v)); } +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pSort->addrPushEnd = sqlite3VdbeCurrentAddr(v)-1; +#endif } /* @@ -1287,9 +1298,6 @@ static void selectInnerLoop( testcase( eDest==SRT_Fifo ); testcase( eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); - if( pDest->zAffSdst ){ - sqlite3VdbeChangeP4(v, -1, pDest->zAffSdst, nResultCol); - } #ifndef SQLITE_OMIT_CTE if( eDest==SRT_DistFifo ){ /* If the destination is DistFifo, then cursor (iParm+1) is open @@ -1647,6 +1655,16 @@ static void generateSortTail( int bSeq; /* True if sorter record includes seq. no. */ int nRefKey = 0; struct ExprList_item *aOutEx = p->pEList->a; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; /* Address of OP_Explain instruction */ +#endif + + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat>0?"RIGHT PART OF ":"") + ); + sqlite3VdbeScanStatusRange(v, addrExplain,pSort->addrPush,pSort->addrPushEnd); + sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, pSort->addrPush); + assert( addrBreak<0 ); if( pSort->labelBkOut ){ @@ -1759,6 +1777,7 @@ static void generateSortTail( VdbeComment((v, "%s", aOutEx[i].zEName)); } } + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); switch( eDest ){ case SRT_Table: case SRT_EphemTab: { @@ -1820,6 +1839,7 @@ static void generateSortTail( }else{ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v); } + sqlite3VdbeScanStatusRange(v, addrExplain, sqlite3VdbeCurrentAddr(v)-1, -1); if( pSort->regReturn ) sqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn); sqlite3VdbeResolveLabel(v, addrBreak); } @@ -2091,7 +2111,7 @@ void sqlite3GenerateColumnNames( if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; - SELECTTRACE(1,pParse,pSelect,("generating column names\n")); + TREETRACE(0x80,pParse,pSelect,("generating column names\n")); pTabList = pSelect->pSrc; pEList = pSelect->pEList; assert( v!=0 ); @@ -2191,7 +2211,7 @@ int sqlite3ColumnsFromExprList( *pnCol = nCol; *paCol = aCol; - for(i=0, pCol=aCol; imallocFailed; i++, pCol++){ + for(i=0, pCol=aCol; inErr; i++, pCol++){ struct ExprList_item *pX = &pEList->a[i]; struct ExprList_item *pCollide; /* Get an appropriate name for the column @@ -2241,7 +2261,10 @@ int sqlite3ColumnsFromExprList( if( zName[j]==':' ) nName = j; } zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt); - if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt); + sqlite3ProgressCheck(pParse); + if( cnt>3 ){ + sqlite3_randomness(sizeof(cnt), &cnt); + } } pCol->zCnName = zName; pCol->hName = sqlite3StrIHash(zName); @@ -2254,71 +2277,105 @@ int sqlite3ColumnsFromExprList( } } sqlite3HashClear(&ht); - if( db->mallocFailed ){ + if( pParse->nErr ){ for(j=0; jrc; } return SQLITE_OK; } /* -** Add type and collation information to a column list based on -** a SELECT statement. -** -** The column list presumably came from selectColumnNamesFromExprList(). -** The column list has only names, not types or collations. This -** routine goes through and adds the types and collations. -** -** This routine requires that all identifiers in the SELECT -** statement be resolved. +** pTab is a transient Table object that represents a subquery of some +** kind (maybe a parenthesized subquery in the FROM clause of a larger +** query, or a VIEW, or a CTE). This routine computes type information +** for that Table object based on the Select object that implements the +** subquery. For the purposes of this routine, "type infomation" means: +** +** * The datatype name, as it might appear in a CREATE TABLE statement +** * Which collating sequence to use for the column +** * The affinity of the column */ -void sqlite3SelectAddColumnTypeAndCollation( - Parse *pParse, /* Parsing contexts */ - Table *pTab, /* Add column type information to this table */ - Select *pSelect, /* SELECT used to determine types and collations */ - char aff /* Default affinity for columns */ +void sqlite3SubqueryColumnTypes( + Parse *pParse, /* Parsing contexts */ + Table *pTab, /* Add column type information to this table */ + Select *pSelect, /* SELECT used to determine types and collations */ + char aff /* Default affinity. */ ){ sqlite3 *db = pParse->db; - NameContext sNC; Column *pCol; CollSeq *pColl; - int i; + int i,j; Expr *p; struct ExprList_item *a; + NameContext sNC; assert( pSelect!=0 ); assert( (pSelect->selFlags & SF_Resolved)!=0 ); - assert( pTab->nCol==pSelect->pEList->nExpr || db->mallocFailed ); + assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 ); + assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); if( db->mallocFailed ) return; + while( pSelect->pPrior ) pSelect = pSelect->pPrior; + a = pSelect->pEList->a; memset(&sNC, 0, sizeof(sNC)); sNC.pSrcList = pSelect->pSrc; - a = pSelect->pEList->a; for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; - i64 n, m; + i64 n; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; - zType = columnType(&sNC, p, 0, 0, 0); /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); - if( zType ){ - m = sqlite3Strlen30(zType); - n = sqlite3Strlen30(pCol->zCnName); - pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); - if( pCol->zCnName ){ - memcpy(&pCol->zCnName[n+1], zType, m+1); - pCol->colFlags |= COLFLAG_HASTYPE; + if( pCol->affinity<=SQLITE_AFF_NONE ){ + pCol->affinity = aff; + }else if( pCol->affinity>=SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ + pCol->affinity = SQLITE_AFF_FLEXNUM; + } + if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ + int m = 0; + Select *pS2; + for(m=0, pS2=pSelect->pNext; pS2; pS2=pS2->pNext){ + m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); + } + if( pCol->affinity==SQLITE_AFF_TEXT && (m&0x01)!=0 ){ + pCol->affinity = SQLITE_AFF_BLOB; + }else + if( pCol->affinity>=SQLITE_AFF_NUMERIC && (m&0x02)!=0 ){ + pCol->affinity = SQLITE_AFF_BLOB; + } + } + zType = columnType(&sNC, p, 0, 0, 0); + if( zType==0 || pCol->affinity!=sqlite3AffinityType(zType, 0) ){ + if( pCol->affinity==SQLITE_AFF_NUMERIC + || pCol->affinity==SQLITE_AFF_FLEXNUM + ){ + zType = "NUM"; }else{ - testcase( pCol->colFlags & COLFLAG_HASTYPE ); + zType = 0; + for(j=1; jaffinity ){ + zType = sqlite3StdType[j]; + break; + } + } + } + } + if( zType ){ + i64 m = sqlite3Strlen30(zType); + n = sqlite3Strlen30(pCol->zCnName); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + if( pCol->zCnName ){ + memcpy(&pCol->zCnName[n+1], zType, m+1); + pCol->colFlags |= COLFLAG_HASTYPE; + }else{ + testcase( pCol->colFlags & COLFLAG_HASTYPE ); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); } } - if( pCol->affinity<=SQLITE_AFF_NONE ) pCol->affinity = aff; pColl = sqlite3ExprCollSeq(pParse, p); if( pColl ){ assert( pTab->pIndex==0 ); @@ -2352,7 +2409,7 @@ Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect, char aff){ pTab->zName = 0; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); - sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSelect, aff); + sqlite3SubqueryColumnTypes(pParse, pTab, pSelect, aff); pTab->iPKey = -1; if( db->mallocFailed ){ sqlite3DeleteTable(db, pTab); @@ -2877,7 +2934,7 @@ static int multiSelect( pPrior->iLimit = p->iLimit; pPrior->iOffset = p->iOffset; pPrior->pLimit = p->pLimit; - SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL left...\n")); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); rc = sqlite3Select(pParse, pPrior, &dest); pPrior->pLimit = 0; if( rc ){ @@ -2895,7 +2952,7 @@ static int multiSelect( } } ExplainQueryPlan((pParse, 1, "UNION ALL")); - SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL right...\n")); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); rc = sqlite3Select(pParse, p, &dest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; @@ -2948,7 +3005,7 @@ static int multiSelect( */ assert( !pPrior->pOrderBy ); sqlite3SelectDestInit(&uniondest, priorOp, unionTab); - SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); rc = sqlite3Select(pParse, pPrior, &uniondest); if( rc ){ goto multi_select_end; @@ -2968,7 +3025,7 @@ static int multiSelect( uniondest.eDest = op; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", sqlite3SelectOpName(p->op))); - SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); rc = sqlite3Select(pParse, p, &uniondest); testcase( rc!=SQLITE_OK ); assert( p->pOrderBy==0 ); @@ -3029,7 +3086,7 @@ static int multiSelect( /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); - SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT left...\n")); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT left...\n")); rc = sqlite3Select(pParse, pPrior, &intersectdest); if( rc ){ goto multi_select_end; @@ -3046,7 +3103,7 @@ static int multiSelect( intersectdest.iSDParm = tab2; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", sqlite3SelectOpName(p->op))); - SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT right...\n")); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT right...\n")); rc = sqlite3Select(pParse, p, &intersectdest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; @@ -4050,6 +4107,34 @@ static ExprList *findLeftmostExprlist(Select *pSel){ return pSel->pEList; } +/* +** Return true if any of the result-set columns in the compound query +** have incompatible affinities on one or more arms of the compound. +*/ +static int compoundHasDifferentAffinities(Select *p){ + int ii; + ExprList *pList; + assert( p!=0 ); + assert( p->pEList!=0 ); + assert( p->pPrior!=0 ); + pList = p->pEList; + for(ii=0; iinExpr; ii++){ + char aff; + Select *pSub1; + assert( pList->a[ii].pExpr!=0 ); + aff = sqlite3ExprAffinity(pList->a[ii].pExpr); + for(pSub1=p->pPrior; pSub1; pSub1=pSub1->pPrior){ + assert( pSub1->pEList!=0 ); + assert( pSub1->pEList->nExpr>ii ); + assert( pSub1->pEList->a[ii].pExpr!=0 ); + if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){ + return 1; + } + } + } + return 0; +} + #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** This routine attempts to flatten subqueries as a performance optimization. @@ -4153,7 +4238,8 @@ static ExprList *findLeftmostExprlist(Select *pSel){ ** query or there are no RIGHT or FULL JOINs in any arm ** of the subquery. (This is a duplicate of condition (27b).) ** (17h) The corresponding result set expressions in all arms of the -** compound must have the same affinity. +** compound must have the same affinity. (See restriction (9) +** on the push-down optimization.) ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, @@ -4372,19 +4458,7 @@ static int flattenSubquery( if( (p->selFlags & SF_Recursive) ) return 0; /* Restriction (17h) */ - for(ii=0; iipEList->nExpr; ii++){ - char aff; - assert( pSub->pEList->a[ii].pExpr!=0 ); - aff = sqlite3ExprAffinity(pSub->pEList->a[ii].pExpr); - for(pSub1=pSub->pPrior; pSub1; pSub1=pSub1->pPrior){ - assert( pSub1->pEList!=0 ); - assert( pSub1->pEList->nExpr>ii ); - assert( pSub1->pEList->a[ii].pExpr!=0 ); - if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){ - return 0; - } - } - } + if( compoundHasDifferentAffinities(pSub) ) return 0; if( pSrc->nSrc>1 ){ if( pParse->nSelect>500 ) return 0; @@ -4395,7 +4469,7 @@ static int flattenSubquery( } /***** If we reach this point, flattening is permitted. *****/ - SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n", + TREETRACE(0x4,pParse,p,("flatten %u.%p from term %d\n", pSub->selId, pSub, iFrom)); /* Authorize the subquery */ @@ -4474,7 +4548,7 @@ static int flattenSubquery( if( pPrior ) pPrior->pNext = pNew; pNew->pNext = p; p->pPrior = pNew; - SELECTTRACE(2,pParse,p,("compound-subquery flattener" + TREETRACE(0x4,pParse,p,("compound-subquery flattener" " creates %u as peer\n",pNew->selId)); } assert( pSubitem->pSelect==0 ); @@ -4654,8 +4728,8 @@ static int flattenSubquery( sqlite3SelectDelete(db, pSub1); #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x100 ){ - SELECTTRACE(0x100,pParse,p,("After flattening:\n")); + if( sqlite3TreeTrace & 0x4 ){ + TREETRACE(0x4,pParse,p,("After flattening:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -5029,12 +5103,14 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** be materialized. (This restriction is implemented in the calling ** routine.) ** -** (8) The subquery may not be a compound that uses UNION, INTERSECT, -** or EXCEPT. (We could, perhaps, relax this restriction to allow -** this case if none of the comparisons operators between left and -** right arms of the compound use a collation other than BINARY. -** But it is a lot of work to check that case for an obscure and -** minor optimization, so we omit it for now.) +** (8) If the subquery is a compound that uses UNION, INTERSECT, +** or EXCEPT, then all of the result set columns for all arms of +** the compound must use the BINARY collating sequence. +** +** (9) If the subquery is a compound, then all arms of the compound must +** have the same affinity. (This is the same as restriction (17h) +** for query flattening.) +** ** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. @@ -5051,20 +5127,44 @@ static int pushDownWhereTerms( if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0; if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ) return 0; -#ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pPrior ){ Select *pSel; + int notUnionAll = 0; for(pSel=pSubq; pSel; pSel=pSel->pPrior){ u8 op = pSel->op; assert( op==TK_ALL || op==TK_SELECT || op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT ); - if( op!=TK_ALL && op!=TK_SELECT ) return 0; /* restriction (8) */ + if( op!=TK_ALL && op!=TK_SELECT ){ + notUnionAll = 1; + } +#ifndef SQLITE_OMIT_WINDOWFUNC if( pSel->pWin ) return 0; /* restriction (6b) */ +#endif + } + if( compoundHasDifferentAffinities(pSubq) ){ + return 0; /* restriction (9) */ + } + if( notUnionAll ){ + /* If any of the compound arms are connected using UNION, INTERSECT, + ** or EXCEPT, then we must ensure that none of the columns use a + ** non-BINARY collating sequence. */ + for(pSel=pSubq; pSel; pSel=pSel->pPrior){ + int ii; + const ExprList *pList = pSel->pEList; + assert( pList!=0 ); + for(ii=0; iinExpr; ii++){ + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pList->a[ii].pExpr); + if( !sqlite3IsBinary(pColl) ){ + return 0; /* Restriction (8) */ + } + } + } } }else{ +#ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pWin && pSubq->pWin->pPartition==0 ) return 0; - } #endif + } #ifdef SQLITE_DEBUG /* Only the first term of a compound can have a WITH clause. But make @@ -5526,9 +5626,6 @@ static int resolveFromTermToCte( pFrom->fg.isCte = 1; pFrom->u2.pCteUse = pCteUse; pCteUse->nUse++; - if( pCteUse->nUse>=2 && pCteUse->eM10d==M10d_Any ){ - pCteUse->eM10d = M10d_Yes; - } /* Check if this is a recursive CTE. */ pRecTerm = pSel = pFrom->pSelect; @@ -6068,8 +6165,8 @@ static int selectExpander(Walker *pWalker, Select *p){ } } #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x100 ){ - SELECTTRACE(0x100,pParse,p,("After result-set wildcard expansion:\n")); + if( sqlite3TreeTrace & 0x8 ){ + TREETRACE(0x8,pParse,p,("After result-set wildcard expansion:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -6120,14 +6217,14 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ ** This is a Walker.xSelectCallback callback for the sqlite3SelectTypeInfo() ** interface. ** -** For each FROM-clause subquery, add Column.zType and Column.zColl -** information to the Table structure that represents the result set -** of that subquery. +** For each FROM-clause subquery, add Column.zType, Column.zColl, and +** Column.affinity information to the Table structure that represents +** the result set of that subquery. ** ** The Table structure that represents the result set was constructed -** by selectExpander() but the type and collation information was omitted -** at that point because identifiers had not yet been resolved. This -** routine is called after identifier resolution. +** by selectExpander() but the type and collation and affinity information +** was omitted at that point because identifiers had not yet been resolved. +** This routine is called after identifier resolution. */ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ Parse *pParse; @@ -6147,9 +6244,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ /* A sub-query in the FROM clause of a SELECT */ Select *pSel = pFrom->pSelect; if( pSel ){ - while( pSel->pPrior ) pSel = pSel->pPrior; - sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSel, - SQLITE_AFF_NONE); + sqlite3SubqueryColumnTypes(pParse, pTab, pSel, SQLITE_AFF_NONE); } } } @@ -6204,6 +6299,173 @@ void sqlite3SelectPrep( sqlite3SelectAddTypeInfo(pParse, p); } +#if TREETRACE_ENABLED +/* +** Display all information about an AggInfo object +*/ +static void printAggInfo(AggInfo *pAggInfo){ + int ii; + for(ii=0; iinColumn; ii++){ + struct AggInfo_col *pCol = &pAggInfo->aCol[ii]; + sqlite3DebugPrintf( + "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d" + " iSorterColumn=%d %s\n", + ii, pCol->pTab ? pCol->pTab->zName : "NULL", + pCol->iTable, pCol->iColumn, pAggInfo->iFirstReg+ii, + pCol->iSorterColumn, + ii>=pAggInfo->nAccumulator ? "" : " Accumulator"); + sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0); + } + for(ii=0; iinFunc; ii++){ + sqlite3DebugPrintf("agg-func[%d]: iMem=%d\n", + ii, pAggInfo->iFirstReg+pAggInfo->nColumn+ii); + sqlite3TreeViewExpr(0, pAggInfo->aFunc[ii].pFExpr, 0); + } +} +#endif /* TREETRACE_ENABLED */ + +/* +** Analyze the arguments to aggregate functions. Create new pAggInfo->aCol[] +** entries for columns that are arguments to aggregate functions but which +** are not otherwise used. +** +** The aCol[] entries in AggInfo prior to nAccumulator are columns that +** are referenced outside of aggregate functions. These might be columns +** that are part of the GROUP by clause, for example. Other database engines +** would throw an error if there is a column reference that is not in the +** GROUP BY clause and that is not part of an aggregate function argument. +** But SQLite allows this. +** +** The aCol[] entries beginning with the aCol[nAccumulator] and following +** are column references that are used exclusively as arguments to +** aggregate functions. This routine is responsible for computing +** (or recomputing) those aCol[] entries. +*/ +static void analyzeAggFuncArgs( + AggInfo *pAggInfo, + NameContext *pNC +){ + int i; + assert( pAggInfo!=0 ); + assert( pAggInfo->iFirstReg==0 ); + pNC->ncFlags |= NC_InAggFunc; + for(i=0; inFunc; i++){ + Expr *pExpr = pAggInfo->aFunc[i].pFExpr; + assert( ExprUseXList(pExpr) ); + sqlite3ExprAnalyzeAggList(pNC, pExpr->x.pList); +#ifndef SQLITE_OMIT_WINDOWFUNC + assert( !IsWindowFunc(pExpr) ); + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3ExprAnalyzeAggregates(pNC, pExpr->y.pWin->pFilter); + } +#endif + } + pNC->ncFlags &= ~NC_InAggFunc; +} + +/* +** An index on expressions is being used in the inner loop of an +** aggregate query with a GROUP BY clause. This routine attempts +** to adjust the AggInfo object to take advantage of index and to +** perhaps use the index as a covering index. +** +*/ +static void optimizeAggregateUseOfIndexedExpr( + Parse *pParse, /* Parsing context */ + Select *pSelect, /* The SELECT statement being processed */ + AggInfo *pAggInfo, /* The aggregate info */ + NameContext *pNC /* Name context used to resolve agg-func args */ +){ + assert( pAggInfo->iFirstReg==0 ); + pAggInfo->nColumn = pAggInfo->nAccumulator; + if( ALWAYS(pAggInfo->nSortingColumn>0) ){ + if( pAggInfo->nColumn==0 ){ + pAggInfo->nSortingColumn = 0; + }else{ + pAggInfo->nSortingColumn = + pAggInfo->aCol[pAggInfo->nColumn-1].iSorterColumn+1; + } + } + analyzeAggFuncArgs(pAggInfo, pNC); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + IndexedExpr *pIEpr; + TREETRACE(0x20, pParse, pSelect, + ("AggInfo (possibly) adjusted for Indexed Exprs\n")); + sqlite3TreeViewSelect(0, pSelect, 0); + for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ + printf("data-cursor=%d index={%d,%d}\n", + pIEpr->iDataCur, pIEpr->iIdxCur, pIEpr->iIdxCol); + sqlite3TreeViewExpr(0, pIEpr->pExpr, 0); + } + printAggInfo(pAggInfo); + } +#else + UNUSED_PARAMETER(pSelect); + UNUSED_PARAMETER(pParse); +#endif +} + +/* +** Walker callback for aggregateConvertIndexedExprRefToColumn(). +*/ +static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){ + AggInfo *pAggInfo; + struct AggInfo_col *pCol; + UNUSED_PARAMETER(pWalker); + if( pExpr->pAggInfo==0 ) return WRC_Continue; + if( pExpr->op==TK_AGG_COLUMN ) return WRC_Continue; + if( pExpr->op==TK_AGG_FUNCTION ) return WRC_Continue; + if( pExpr->op==TK_IF_NULL_ROW ) return WRC_Continue; + pAggInfo = pExpr->pAggInfo; + assert( pExpr->iAgg>=0 && pExpr->iAggnColumn ); + pCol = &pAggInfo->aCol[pExpr->iAgg]; + pExpr->op = TK_AGG_COLUMN; + pExpr->iTable = pCol->iTable; + pExpr->iColumn = pCol->iColumn; + return WRC_Prune; +} + +/* +** Convert every pAggInfo->aFunc[].pExpr such that any node within +** those expressions that has pAppInfo set is changed into a TK_AGG_COLUMN +** opcode. +*/ +static void aggregateConvertIndexedExprRefToColumn(AggInfo *pAggInfo){ + int i; + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = aggregateIdxEprRefToColCallback; + for(i=0; inFunc; i++){ + sqlite3WalkExpr(&w, pAggInfo->aFunc[i].pFExpr); + } +} + + +/* +** Allocate a block of registers so that there is one register for each +** pAggInfo->aCol[] and pAggInfo->aFunc[] entry in pAggInfo. The first +** register in this block is stored in pAggInfo->iFirstReg. +** +** This routine may only be called once for each AggInfo object. Prior +** to calling this routine: +** +** * The aCol[] and aFunc[] arrays may be modified +** * The AggInfoColumnReg() and AggInfoFuncReg() macros may not be used +** +** After clling this routine: +** +** * The aCol[] and aFunc[] arrays are fixed +** * The AggInfoColumnReg() and AggInfoFuncReg() macros may be used +** +*/ +static void assignAggregateRegisters(Parse *pParse, AggInfo *pAggInfo){ + assert( pAggInfo!=0 ); + assert( pAggInfo->iFirstReg==0 ); + pAggInfo->iFirstReg = pParse->nMem + 1; + pParse->nMem += pAggInfo->nColumn + pAggInfo->nFunc; +} + /* ** Reset the aggregate accumulator. ** @@ -6217,24 +6479,13 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ int i; struct AggInfo_func *pFunc; int nReg = pAggInfo->nFunc + pAggInfo->nColumn; + assert( pAggInfo->iFirstReg>0 ); assert( pParse->db->pParse==pParse ); assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( nReg==0 ) return; if( pParse->nErr ) return; -#ifdef SQLITE_DEBUG - /* Verify that all AggInfo registers are within the range specified by - ** AggInfo.mnReg..AggInfo.mxReg */ - assert( nReg==pAggInfo->mxReg-pAggInfo->mnReg+1 ); - for(i=0; inColumn; i++){ - assert( pAggInfo->aCol[i].iMem>=pAggInfo->mnReg - && pAggInfo->aCol[i].iMem<=pAggInfo->mxReg ); - } - for(i=0; inFunc; i++){ - assert( pAggInfo->aFunc[i].iMem>=pAggInfo->mnReg - && pAggInfo->aFunc[i].iMem<=pAggInfo->mxReg ); - } -#endif - sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->mnReg, pAggInfo->mxReg); + sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->iFirstReg, + pAggInfo->iFirstReg+nReg-1); for(pFunc=pAggInfo->aFunc, i=0; inFunc; i++, pFunc++){ if( pFunc->iDistinct>=0 ){ Expr *pE = pFunc->pFExpr; @@ -6266,15 +6517,16 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ ExprList *pList; assert( ExprUseXList(pF->pFExpr) ); pList = pF->pFExpr->x.pList; - sqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0); + sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), + pList ? pList->nExpr : 0); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } } /* -** Update the accumulator memory cells for an aggregate based on -** the current cursor position. +** Generate code that will update the accumulator memory cells for an +** aggregate based on the current cursor position. ** ** If regAcc is non-zero and there are no min() or max() aggregates ** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator @@ -6294,6 +6546,8 @@ static void updateAccumulator( struct AggInfo_func *pF; struct AggInfo_col *pC; + assert( pAggInfo->iFirstReg>0 ); + if( pParse->nErr ) return; pAggInfo->directMode = 1; for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){ int nArg; @@ -6354,7 +6608,7 @@ static void updateAccumulator( if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, pF->iMem); + sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nArg); sqlite3ReleaseTempRange(pParse, regAgg, nArg); @@ -6369,7 +6623,7 @@ static void updateAccumulator( addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v); } for(i=0, pC=pAggInfo->aCol; inAccumulator; i++, pC++){ - sqlite3ExprCode(pParse, pC->pCExpr, pC->iMem); + sqlite3ExprCode(pParse, pC->pCExpr, AggInfoColumnReg(pAggInfo,i)); } pAggInfo->directMode = 0; @@ -6465,26 +6719,31 @@ static void havingToWhere(Parse *pParse, Select *p){ sqlite3WalkExpr(&sWalker, p->pHaving); #if TREETRACE_ENABLED if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){ - SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); + TREETRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif } /* -** Check to see if the pThis entry of pTabList is a self-join of a prior view. -** If it is, then return the SrcItem for the prior view. If it is not, -** then return 0. +** Check to see if the pThis entry of pTabList is a self-join of another view. +** Search FROM-clause entries in the range of iFirst..iEnd, including iFirst +** but stopping before iEnd. +** +** If pThis is a self-join, then return the SrcItem for the first other +** instance of that view found. If pThis is not a self-join then return 0. */ static SrcItem *isSelfJoinView( SrcList *pTabList, /* Search for self-joins in this FROM clause */ - SrcItem *pThis /* Search for prior reference to this subquery */ + SrcItem *pThis, /* Search for prior reference to this subquery */ + int iFirst, int iEnd /* Range of FROM-clause entries to search. */ ){ SrcItem *pItem; assert( pThis->pSelect!=0 ); if( pThis->pSelect->selFlags & SF_PushDown ) return 0; - for(pItem = pTabList->a; pItema[iFirst++]; if( pItem->pSelect==0 ) continue; if( pItem->fg.viaCoroutine ) continue; if( pItem->zName==0 ) continue; @@ -6553,6 +6812,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ assert( ExprUseXList(pExpr) ); if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */ if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */ + if( ExprHasProperty(pExpr, EP_WinFunc) ) return 0;/* Not a window function */ pSub = p->pSrc->a[0].pSelect; if( pSub==0 ) return 0; /* The FROM is a subquery */ if( pSub->pPrior==0 ) return 0; /* Must be a compound ry */ @@ -6597,8 +6857,8 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ p->selFlags &= ~SF_Aggregate; #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x400 ){ - SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n")); + if( sqlite3TreeTrace & 0x200 ){ + TREETRACE(0x200,pParse,p,("After count-of-view optimization:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -6629,6 +6889,68 @@ static int sameSrcAlias(SrcItem *p0, SrcList *pSrc){ return 0; } +/* +** Return TRUE (non-zero) if the i-th entry in the pTabList SrcList can +** be implemented as a co-routine. The i-th entry is guaranteed to be +** a subquery. +** +** The subquery is implemented as a co-routine if all of the following are +** true: +** +** (1) The subquery will likely be implemented in the outer loop of +** the query. This will be the case if any one of the following +** conditions hold: +** (a) The subquery is the only term in the FROM clause +** (b) The subquery is the left-most term and a CROSS JOIN or similar +** requires it to be the outer loop +** (c) All of the following are true: +** (i) The subquery is the left-most subquery in the FROM clause +** (ii) There is nothing that would prevent the subquery from +** being used as the outer loop if the sqlite3WhereBegin() +** routine nominates it to that position. +** (iii) The query is not a UPDATE ... FROM +** (2) The subquery is not a CTE that should be materialized because +** (a) the AS MATERIALIZED keyword is used, or +** (b) the CTE is used multiple times and does not have the +** NOT MATERIALIZED keyword +** (3) The subquery is not part of a left operand for a RIGHT JOIN +** (4) The SQLITE_Coroutine optimization disable flag is not set +** (5) The subquery is not self-joined +*/ +static int fromClauseTermCanBeCoroutine( + Parse *pParse, /* Parsing context */ + SrcList *pTabList, /* FROM clause */ + int i, /* Which term of the FROM clause holds the subquery */ + int selFlags /* Flags on the SELECT statement */ +){ + SrcItem *pItem = &pTabList->a[i]; + if( pItem->fg.isCte ){ + const CteUse *pCteUse = pItem->u2.pCteUse; + if( pCteUse->eM10d==M10d_Yes ) return 0; /* (2a) */ + if( pCteUse->nUse>=2 && pCteUse->eM10d!=M10d_No ) return 0; /* (2b) */ + } + if( pTabList->a[0].fg.jointype & JT_LTORJ ) return 0; /* (3) */ + if( OptimizationDisabled(pParse->db, SQLITE_Coroutines) ) return 0; /* (4) */ + if( isSelfJoinView(pTabList, pItem, i+1, pTabList->nSrc)!=0 ){ + return 0; /* (5) */ + } + if( i==0 ){ + if( pTabList->nSrc==1 ) return 1; /* (1a) */ + if( pTabList->a[1].fg.jointype & JT_CROSS ) return 1; /* (1b) */ + if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ + return 1; + } + if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ + while( 1 /*exit-by-break*/ ){ + if( pItem->fg.jointype & (JT_OUTER|JT_CROSS) ) return 0; /* (1c-ii) */ + if( i==0 ) break; + i--; + pItem--; + if( pItem->pSelect!=0 ) return 0; /* (1c-i) */ + } + return 1; +} + /* ** Generate code for the SELECT statement given in the p argument. ** @@ -6674,8 +6996,8 @@ int sqlite3Select( assert( db->mallocFailed==0 ); if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; #if TREETRACE_ENABLED - SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain)); - if( sqlite3TreeTrace & 0x10100 ){ + TREETRACE(0x1,pParse,p, ("begin processing:\n", pParse->addrExplain)); + if( sqlite3TreeTrace & 0x10000 ){ if( (sqlite3TreeTrace & 0x10001)==0x10000 ){ sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d", __FILE__, __LINE__); @@ -6695,8 +7017,8 @@ int sqlite3Select( /* All of these destinations are also able to ignore the ORDER BY clause */ if( p->pOrderBy ){ #if TREETRACE_ENABLED - SELECTTRACE(1,pParse,p, ("dropping superfluous ORDER BY:\n")); - if( sqlite3TreeTrace & 0x100 ){ + TREETRACE(0x800,pParse,p, ("dropping superfluous ORDER BY:\n")); + if( sqlite3TreeTrace & 0x800 ){ sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); } #endif @@ -6716,8 +7038,8 @@ int sqlite3Select( assert( db->mallocFailed==0 ); assert( p->pEList!=0 ); #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x104 ){ - SELECTTRACE(0x104,pParse,p, ("after name resolution:\n")); + if( sqlite3TreeTrace & 0x10 ){ + TREETRACE(0x10,pParse,p, ("after name resolution:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -6758,8 +7080,8 @@ int sqlite3Select( goto select_end; } #if TREETRACE_ENABLED - if( p->pWin && (sqlite3TreeTrace & 0x108)!=0 ){ - SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n")); + if( p->pWin && (sqlite3TreeTrace & 0x40)!=0 ){ + TREETRACE(0x40,pParse,p, ("after window rewrite:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -6790,7 +7112,7 @@ int sqlite3Select( && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ - SELECTTRACE(0x100,pParse,p, + TREETRACE(0x1000,pParse,p, ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); assert( pItem->iCursor>=0 ); @@ -6846,7 +7168,7 @@ int sqlite3Select( && (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */ && OptimizationEnabled(db, SQLITE_OmitOrderBy) ){ - SELECTTRACE(0x100,pParse,p, + TREETRACE(0x800,pParse,p, ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3ExprListDelete, @@ -6901,8 +7223,8 @@ int sqlite3Select( if( p->pPrior ){ rc = multiSelect(pParse, p, pDest); #if TREETRACE_ENABLED - SELECTTRACE(0x1,pParse,p,("end compound-select processing\n")); - if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + TREETRACE(0x400,pParse,p,("end compound-select processing\n")); + if( (sqlite3TreeTrace & 0x400)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -6922,13 +7244,13 @@ int sqlite3Select( && propagateConstants(pParse, p) ){ #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x100 ){ - SELECTTRACE(0x100,pParse,p,("After constant propagation:\n")); + if( sqlite3TreeTrace & 0x2000 ){ + TREETRACE(0x2000,pParse,p,("After constant propagation:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif }else{ - SELECTTRACE(0x100,pParse,p,("Constant propagation not helpful\n")); + TREETRACE(0x2000,pParse,p,("Constant propagation not helpful\n")); } #ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION @@ -7001,36 +7323,23 @@ int sqlite3Select( && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem) ){ #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x100 ){ - SELECTTRACE(0x100,pParse,p, + if( sqlite3TreeTrace & 0x4000 ){ + TREETRACE(0x4000,pParse,p, ("After WHERE-clause push-down into subquery %d:\n", pSub->selId)); sqlite3TreeViewSelect(0, p, 0); } #endif assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 ); }else{ - SELECTTRACE(0x100,pParse,p,("Push-down not possible\n")); + TREETRACE(0x4000,pParse,p,("Push-down not possible\n")); } zSavedAuthContext = pParse->zAuthContext; pParse->zAuthContext = pItem->zName; /* Generate code to implement the subquery - ** - ** The subquery is implemented as a co-routine if all of the following are - ** true: - ** - ** (1) the subquery is guaranteed to be the outer loop (so that - ** it does not need to be computed more than once), and - ** (2) the subquery is not a CTE that should be materialized - ** (3) the subquery is not part of a left operand for a RIGHT JOIN */ - if( i==0 - && (pTabList->nSrc==1 - || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) /* (1) */ - && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */ - && (pTabList->a[0].fg.jointype & JT_LTORJ)==0 /* (3) */ - ){ + if( fromClauseTermCanBeCoroutine(pParse, pTabList, i, p->selFlags) ){ /* Implement a co-routine that will return a single row of the result ** set on each invocation. */ @@ -7061,7 +7370,7 @@ int sqlite3Select( VdbeComment((v, "%!S", pItem)); } pSub->nSelectRow = pCteUse->nRowEst; - }else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){ + }else if( (pPrior = isSelfJoinView(pTabList, pItem, 0, i))!=0 ){ /* This view has already been materialized by a prior entry in ** this same FROM clause. Reuse it. */ if( pPrior->addrFillSub ){ @@ -7075,6 +7384,9 @@ int sqlite3Select( ** the same view can reuse the materialization. */ int topAddr; int onceAddr = 0; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; +#endif pItem->regReturn = ++pParse->nMem; topAddr = sqlite3VdbeAddOp0(v, OP_Goto); @@ -7090,15 +7402,14 @@ int sqlite3Select( VdbeNoopComment((v, "materialize %!S", pItem)); } sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); - ExplainQueryPlan((pParse, 1, "MATERIALIZE %!S", pItem)); - dest.zAffSdst = sqlite3TableAffinityStr(db, pItem->pTab); + + ExplainQueryPlan2(addrExplain, (pParse, 1, "MATERIALIZE %!S", pItem)); sqlite3Select(pParse, pSub, &dest); - sqlite3DbFree(db, dest.zAffSdst); - dest.zAffSdst = 0; pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1); VdbeComment((v, "end %!S", pItem)); + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); sqlite3VdbeJumpHere(v, topAddr); sqlite3ClearTempRegCache(pParse); if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){ @@ -7124,8 +7435,8 @@ int sqlite3Select( sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x400 ){ - SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n")); + if( sqlite3TreeTrace & 0x8000 ){ + TREETRACE(0x8000,pParse,p,("After all FROM-clause analysis:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -7161,8 +7472,8 @@ int sqlite3Select( sDistinct.isTnct = 2; #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x400 ){ - SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n")); + if( sqlite3TreeTrace & 0x20000 ){ + TREETRACE(0x20000,pParse,p,("Transform DISTINCT into GROUP BY:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -7248,7 +7559,7 @@ int sqlite3Select( /* Begin the database scan. */ - SELECTTRACE(1,pParse,p,("WhereBegin\n")); + TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, p->pEList, p, wctrlFlags, p->nSelectRow); if( pWInfo==0 ) goto select_end; @@ -7265,7 +7576,7 @@ int sqlite3Select( sSort.pOrderBy = 0; } } - SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); /* If sorting index that was created by a prior OP_OpenEphemeral ** instruction ended up not being needed, then change the OP_OpenEphemeral @@ -7304,7 +7615,7 @@ int sqlite3Select( /* End the database scan loop. */ - SELECTTRACE(1,pParse,p,("WhereEnd\n")); + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); } }else{ @@ -7385,12 +7696,14 @@ int sqlite3Select( goto select_end; } pAggInfo->selId = p->selId; +#ifdef SQLITE_DEBUG + pAggInfo->pSelect = p; +#endif memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; sNC.uNC.pAggInfo = pAggInfo; VVA_ONLY( sNC.ncFlags = NC_UAggInfo; ) - pAggInfo->mnReg = pParse->nMem+1; pAggInfo->nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0; pAggInfo->pGroupBy = pGroupBy; sqlite3ExprAnalyzeAggList(&sNC, pEList); @@ -7411,45 +7724,17 @@ int sqlite3Select( }else{ minMaxFlag = WHERE_ORDERBY_NORMAL; } - for(i=0; inFunc; i++){ - Expr *pExpr = pAggInfo->aFunc[i].pFExpr; - assert( ExprUseXList(pExpr) ); - sNC.ncFlags |= NC_InAggFunc; - sqlite3ExprAnalyzeAggList(&sNC, pExpr->x.pList); -#ifndef SQLITE_OMIT_WINDOWFUNC - assert( !IsWindowFunc(pExpr) ); - if( ExprHasProperty(pExpr, EP_WinFunc) ){ - sqlite3ExprAnalyzeAggregates(&sNC, pExpr->y.pWin->pFilter); - } -#endif - sNC.ncFlags &= ~NC_InAggFunc; - } - pAggInfo->mxReg = pParse->nMem; + analyzeAggFuncArgs(pAggInfo, &sNC); if( db->mallocFailed ) goto select_end; #if TREETRACE_ENABLED - if( sqlite3TreeTrace & 0x400 ){ - int ii; - SELECTTRACE(0x400,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); sqlite3TreeViewSelect(0, p, 0); if( minMaxFlag ){ sqlite3DebugPrintf("MIN/MAX Optimization (0x%02x) adds:\n", minMaxFlag); sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY"); } - for(ii=0; iinColumn; ii++){ - struct AggInfo_col *pCol = &pAggInfo->aCol[ii]; - sqlite3DebugPrintf( - "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d" - " iSorterColumn=%d\n", - ii, pCol->pTab ? pCol->pTab->zName : "NULL", - pCol->iTable, pCol->iColumn, pCol->iMem, - pCol->iSorterColumn); - sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0); - } - for(ii=0; iinFunc; ii++){ - sqlite3DebugPrintf("agg-func[%d]: iMem=%d\n", - ii, pAggInfo->aFunc[ii].iMem); - sqlite3TreeViewExpr(0, pAggInfo->aFunc[ii].pFExpr, 0); - } + printAggInfo(pAggInfo); } #endif @@ -7518,7 +7803,7 @@ int sqlite3Select( ** in the right order to begin with. */ sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); - SELECTTRACE(1,pParse,p,("WhereBegin\n")); + TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 @@ -7527,8 +7812,12 @@ int sqlite3Select( sqlite3ExprListDelete(db, pDistinct); goto select_end; } + if( pParse->pIdxEpr ){ + optimizeAggregateUseOfIndexedExpr(pParse, p, pAggInfo, &sNC); + } + assignAggregateRegisters(pParse, pAggInfo); eDist = sqlite3WhereIsDistinct(pWInfo); - SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){ /* The optimizer is able to deliver rows in group by order so ** we do not have to sort. The OP_OpenEphemeral table will be @@ -7577,7 +7866,7 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3ReleaseTempRange(pParse, regBase, nCol); - SELECTTRACE(1,pParse,p,("WhereEnd\n")); + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++; sortOut = sqlite3GetTempReg(pParse); @@ -7587,6 +7876,23 @@ int sqlite3Select( pAggInfo->useSortingIdx = 1; } + /* If there are entries in pAgggInfo->aFunc[] that contain subexpressions + ** that are indexed (and that were previously identified and tagged + ** in optimizeAggregateUseOfIndexedExpr()) then those subexpressions + ** must now be converted into a TK_AGG_COLUMN node so that the value + ** is correctly pulled from the index rather than being recomputed. */ + if( pParse->pIdxEpr ){ + aggregateConvertIndexedExprRefToColumn(pAggInfo); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20, pParse, p, + ("AggInfo function expressions converted to reference index\n")); + sqlite3TreeViewSelect(0, p, 0); + printAggInfo(pAggInfo); + } +#endif + } + /* If the index or temporary table used by the GROUP BY sort ** will naturally deliver rows in the order required by the ORDER BY ** clause, cancel the ephemeral table open coded earlier. @@ -7655,7 +7961,7 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop); VdbeCoverage(v); }else{ - SELECTTRACE(1,pParse,p,("WhereEnd\n")); + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); sqlite3VdbeChangeToNoop(v, addrSortingIdx); } @@ -7765,7 +8071,8 @@ int sqlite3Select( if( pKeyInfo ){ sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO); } - sqlite3VdbeAddOp2(v, OP_Count, iCsr, pAggInfo->aFunc[0].iMem); + assignAggregateRegisters(pParse, pAggInfo); + sqlite3VdbeAddOp2(v, OP_Count, iCsr, AggInfoFuncReg(pAggInfo,0)); sqlite3VdbeAddOp1(v, OP_Close, iCsr); explainSimpleCount(pParse, pTab, pBest); }else{ @@ -7801,6 +8108,7 @@ int sqlite3Select( pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; } + assignAggregateRegisters(pParse, pAggInfo); /* This case runs if the aggregate has no GROUP BY clause. The ** processing is much simpler since there is only a single row @@ -7817,13 +8125,13 @@ int sqlite3Select( assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 ); assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 ); - SELECTTRACE(1,pParse,p,("WhereBegin\n")); + TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy, pDistinct, p, minMaxFlag|distFlag, 0); if( pWInfo==0 ){ goto select_end; } - SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); eDist = sqlite3WhereIsDistinct(pWInfo); updateAccumulator(pParse, regAcc, pAggInfo, eDist); if( eDist!=WHERE_DISTINCT_NOOP ){ @@ -7837,7 +8145,7 @@ int sqlite3Select( if( minMaxFlag ){ sqlite3WhereMinMaxOptEarlyOut(v, pWInfo); } - SELECTTRACE(1,pParse,p,("WhereEnd\n")); + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); finalizeAggFunctions(pParse, pAggInfo); } @@ -7859,8 +8167,6 @@ int sqlite3Select( ** and send them to the callback one by one. */ if( sSort.pOrderBy ){ - explainTempTable(pParse, - sSort.nOBSat>0 ? "RIGHT PART OF ORDER BY":"ORDER BY"); assert( p->pEList==pEList ); generateSortTail(pParse, p, &sSort, pEList->nExpr, pDest); } @@ -7884,7 +8190,7 @@ int sqlite3Select( if( pAggInfo && !db->mallocFailed ){ for(i=0; inColumn; i++){ Expr *pExpr = pAggInfo->aCol[i].pCExpr; - assert( pExpr!=0 ); + if( pExpr==0 ) continue; assert( pExpr->pAggInfo==pAggInfo ); assert( pExpr->iAgg==i ); } @@ -7898,8 +8204,8 @@ int sqlite3Select( #endif #if TREETRACE_ENABLED - SELECTTRACE(0x1,pParse,p,("end processing\n")); - if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + TREETRACE(0x1,pParse,p,("end processing\n")); + if( (sqlite3TreeTrace & 0x40000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif diff --git a/src/shell.c.in b/src/shell.c.in index 2b4cede76..de3b6a73b 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -21,7 +21,7 @@ typedef unsigned short int u16; /* ** Optionally #include a user-defined header, whereby compilation options -** may be set prior to where they take effect, but after platform setup. +** may be set prior to where they take effect, but after platform setup. ** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include ** file. Note that this macro has a like effect on sqlite3.c compilation. */ @@ -111,7 +111,7 @@ typedef unsigned char u8; #if !defined(_WIN32) && !defined(WIN32) # include -# if !defined(__RTP__) && !defined(_WRS_KERNEL) +# if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) # include # endif #endif @@ -166,6 +166,14 @@ typedef unsigned char u8; # define SHELL_USE_LOCAL_GETLINE 1 #endif +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if defined(GCC_VERSION) && GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif #if defined(_WIN32) || defined(WIN32) # if SQLITE_OS_WINRT @@ -192,7 +200,7 @@ typedef unsigned char u8; /* Make sure isatty() has a prototype. */ extern int isatty(int); -# if !defined(__RTP__) && !defined(_WRS_KERNEL) +# if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) /* popen and pclose are not C89 functions and so are ** sometimes omitted from the header */ extern FILE *popen(const char*,const char*); @@ -467,8 +475,108 @@ static char *Argv0; ** Prompt strings. Initialized in main. Settable with ** .prompt main continue */ -static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/ -static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ +#define PROMPT_LEN_MAX 20 +/* First line prompt. default: "sqlite> " */ +static char mainPrompt[PROMPT_LEN_MAX]; +/* Continuation prompt. default: " ...> " */ +static char continuePrompt[PROMPT_LEN_MAX]; + +/* This is variant of the standard-library strncpy() routine with the +** one change that the destination string is always zero-terminated, even +** if there is no zero-terminator in the first n-1 characters of the source +** string. +*/ +static char *shell_strncpy(char *dest, const char *src, size_t n){ + size_t i; + for(i=0; iinParenLevel += ni; + if( ni==0 ) p->inParenLevel = 0; + p->zScannerAwaits = 0; +} + +/* Record that a lexeme is opened, or closed with args==0. */ +static void setLexemeOpen(struct DynaPrompt *p, char *s, char c){ + if( s!=0 || c==0 ){ + p->zScannerAwaits = s; + p->acAwait[0] = 0; + }else{ + p->acAwait[0] = c; + p->zScannerAwaits = p->acAwait; + } +} + +/* Upon demand, derive the continuation prompt to display. */ +static char *dynamicContinuePrompt(void){ + if( continuePrompt[0]==0 + || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){ + return continuePrompt; + }else{ + if( dynPrompt.zScannerAwaits ){ + size_t ncp = strlen(continuePrompt); + size_t ndp = strlen(dynPrompt.zScannerAwaits); + if( ndp > ncp-3 ) return continuePrompt; + strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits); + while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' '; + shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, + PROMPT_LEN_MAX-4); + }else{ + if( dynPrompt.inParenLevel>9 ){ + shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4); + }else if( dynPrompt.inParenLevel<0 ){ + shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4); + }else{ + shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4); + dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel); + } + shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4); + } + } + return dynPrompt.dynamicPrompt; +} +#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ /* ** Render output like fprintf(). Except, if the output is going to the @@ -729,7 +837,7 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ if( in!=0 ){ zResult = local_getline(zPrior, in); }else{ - zPrompt = isContinuation ? continuePrompt : mainPrompt; + zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; #if SHELL_USE_LOCAL_GETLINE printf("%s", zPrompt); fflush(stdout); @@ -946,7 +1054,7 @@ static void shellModuleSchema( char *zFake; UNUSED_PARAMETER(nVal); zName = (const char*)sqlite3_value_text(apVal[0]); - zFake = zName ? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; + zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; if( zFake ){ sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), -1, sqlite3_free); @@ -1045,6 +1153,13 @@ INCLUDE ../ext/misc/memtrace.c INCLUDE ../ext/misc/shathree.c INCLUDE ../ext/misc/uint.c INCLUDE ../ext/misc/decimal.c +#undef sqlite3_base_init +#define sqlite3_base_init sqlite3_base64_init +INCLUDE ../ext/misc/base64.c +#undef sqlite3_base_init +#define sqlite3_base_init sqlite3_base85_init +#define OMIT_BASE85_CHECKER +INCLUDE ../ext/misc/base85.c INCLUDE ../ext/misc/ieee754.c INCLUDE ../ext/misc/series.c INCLUDE ../ext/misc/regexp.c @@ -1066,9 +1181,14 @@ INCLUDE ../ext/expert/sqlite3expert.c #define SQLITE_SHELL_HAVE_RECOVER 0 #endif #if SQLITE_SHELL_HAVE_RECOVER -INCLUDE ../ext/recover/dbdata.c INCLUDE ../ext/recover/sqlite3recover.h +# ifndef SQLITE_HAVE_SQLITE3R +INCLUDE ../ext/recover/dbdata.c INCLUDE ../ext/recover/sqlite3recover.c +# endif +#endif +#ifdef SQLITE_SHELL_EXTSRC +# include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC) #endif #if defined(SQLITE_ENABLE_SESSION) @@ -1496,7 +1616,7 @@ static void editFunc( } sz = j; p[sz] = 0; - } + } sqlite3_result_text64(context, (const char*)p, sz, sqlite3_free, SQLITE_UTF8); } @@ -2077,7 +2197,7 @@ static void eqp_render_level(ShellState *p, int iEqpId){ /* ** Display and reset the EXPLAIN QUERY PLAN data */ -static void eqp_render(ShellState *p){ +static void eqp_render(ShellState *p, i64 nCycle){ EQPGraphRow *pRow = p->sGraph.pRow; if( pRow ){ if( pRow->zText[0]=='-' ){ @@ -2088,6 +2208,8 @@ static void eqp_render(ShellState *p){ utf8_printf(p->out, "%s\n", pRow->zText+3); p->sGraph.pRow = pRow->pNext; sqlite3_free(pRow); + }else if( nCycle>0 ){ + utf8_printf(p->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle); }else{ utf8_printf(p->out, "QUERY PLAN\n"); } @@ -2669,9 +2791,9 @@ static char *shell_error_context(const char *zSql, sqlite3 *db){ shell_check_oom(zCode); for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } if( iOffset<25 ){ - zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode, iOffset, ""); + zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode,iOffset,""); }else{ - zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode, iOffset-14, ""); + zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode,iOffset-14,""); } return zMsg; } @@ -2859,7 +2981,7 @@ static int display_stats( if( pArg->statsOn==3 ){ if( pArg->pStmt ){ - iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset); raw_printf(pArg->out, "VM-steps: %d\n", iCur); } return 0; @@ -2940,8 +3062,10 @@ static int display_stats( raw_printf(pArg->out, "Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur); - iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset); - iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset); + iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, + bReset); + iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, + bReset); if( iHit || iMiss ){ raw_printf(pArg->out, "Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss); @@ -2965,6 +3089,35 @@ static int display_stats( return 0; } + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +static int scanStatsHeight(sqlite3_stmt *p, int iEntry){ + int iPid = 0; + int ret = 1; + sqlite3_stmt_scanstatus_v2(p, iEntry, + SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid + ); + while( iPid!=0 ){ + int ii; + for(ii=0; 1; ii++){ + int iId; + int res; + res = sqlite3_stmt_scanstatus_v2(p, ii, + SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId + ); + if( res ) break; + if( iId==iPid ){ + sqlite3_stmt_scanstatus_v2(p, ii, + SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid + ); + } + } + ret++; + } + return ret; +} +#endif + /* ** Display scan stats. */ @@ -2976,40 +3129,77 @@ static void display_scanstats( UNUSED_PARAMETER(db); UNUSED_PARAMETER(pArg); #else - int i, k, n, mx; - raw_printf(pArg->out, "-------- scanstats --------\n"); - mx = 0; - for(k=0; k<=mx; k++){ - double rEstLoop = 1.0; - for(i=n=0; 1; i++){ - sqlite3_stmt *p = pArg->pStmt; - sqlite3_int64 nLoop, nVisit; - double rEst; - int iSid; - const char *zExplain; - if( sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NLOOP, (void*)&nLoop) ){ - break; + static const int f = SQLITE_SCANSTAT_COMPLEX; + sqlite3_stmt *p = pArg->pStmt; + int ii = 0; + i64 nTotal = 0; + int nWidth = 0; + eqp_reset(pArg); + + for(ii=0; 1; ii++){ + const char *z = 0; + int n = 0; + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ + break; + } + n = strlen(z) + scanStatsHeight(p, ii)*3; + if( n>nWidth ) nWidth = n; + } + nWidth += 4; + + sqlite3_stmt_scanstatus_v2(p, -1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal); + for(ii=0; 1; ii++){ + i64 nLoop = 0; + i64 nRow = 0; + i64 nCycle = 0; + int iId = 0; + int iPid = 0; + const char *z = 0; + const char *zName = 0; + char *zText = 0; + double rEst = 0.0; + + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ + break; + } + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName); + + zText = sqlite3_mprintf("%s", z); + if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ + char *z = 0; + if( nCycle>=0 && nTotal>0 ){ + z = sqlite3_mprintf("%zcycles=%lld [%d%%]", z, + nCycle, ((nCycle*100)+nTotal/2) / nTotal + ); } - sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_SELECTID, (void*)&iSid); - if( iSid>mx ) mx = iSid; - if( iSid!=k ) continue; - if( n==0 ){ - rEstLoop = (double)nLoop; - if( k>0 ) raw_printf(pArg->out, "-------- subquery %d -------\n", k); + if( nLoop>=0 ){ + z = sqlite3_mprintf("%z%sloops=%lld", z, z ? " " : "", nLoop); } - n++; - sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NVISIT, (void*)&nVisit); - sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EST, (void*)&rEst); - sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EXPLAIN, (void*)&zExplain); - utf8_printf(pArg->out, "Loop %2d: %s\n", n, zExplain); - rEstLoop *= rEst; - raw_printf(pArg->out, - " nLoop=%-8lld nRow=%-8lld estRow=%-8lld estRow/Loop=%-8g\n", - nLoop, nVisit, (sqlite3_int64)(rEstLoop+0.5), rEst + if( nRow>=0 ){ + z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow); + } + + if( zName && pArg->scanstatsOn>1 ){ + double rpl = (double)nRow / (double)nLoop; + z = sqlite3_mprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst); + } + + zText = sqlite3_mprintf( + "% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z ); } + + eqp_append(pArg, iId, iPid, zText); + sqlite3_free(zText); } - raw_printf(pArg->out, "---------------------------\n"); + + eqp_render(pArg, nTotal); #endif } @@ -3249,7 +3439,7 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ ** characters */ static void print_box_line(FILE *out, int N){ - const char zDash[] = + const char zDash[] = BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; const int nDash = sizeof(zDash) - 1; @@ -3378,7 +3568,7 @@ static char *translateForDisplayAndDup( break; } zOut[j] = 0; - return (char*)zOut; + return (char*)zOut; } /* Extract the value of the i-th current column for pStmt as an SQL literal @@ -3739,8 +3929,8 @@ static void exec_prepared_stmt( ** caller to eventually free this buffer using sqlite3_free(). */ static int expertHandleSQL( - ShellState *pState, - const char *zSql, + ShellState *pState, + const char *zSql, char **pzErr ){ assert( pState->expert.pExpert ); @@ -3750,7 +3940,7 @@ static int expertHandleSQL( /* ** This function is called either to silently clean up the object -** created by the ".expert" command (if bCancel==1), or to generate a +** created by the ".expert" command (if bCancel==1), or to generate a ** report from it and then clean it up (if bCancel==0). ** ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error @@ -3845,7 +4035,8 @@ static int expertDotCommand( if( rc==SQLITE_OK ){ pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); if( pState->expert.pExpert==0 ){ - raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory"); + raw_printf(stderr, "sqlite3_expert_new: %s\n", + zErr ? zErr : "out of memory"); rc = SQLITE_ERROR; }else{ sqlite3_expert_config( @@ -3933,10 +4124,10 @@ static int shell_exec( int iEqpId = sqlite3_column_int(pExplain, 0); int iParentId = sqlite3_column_int(pExplain, 1); if( zEQPLine==0 ) zEQPLine = ""; - if( zEQPLine[0]=='-' ) eqp_render(pArg); + if( zEQPLine[0]=='-' ) eqp_render(pArg, 0); eqp_append(pArg, iEqpId, iParentId, zEQPLine); } - eqp_render(pArg); + eqp_render(pArg, 0); } sqlite3_finalize(pExplain); sqlite3_free(zEQP); @@ -3985,7 +4176,7 @@ static int shell_exec( bind_prepared_stmt(pArg, pStmt); exec_prepared_stmt(pArg, pStmt); explain_data_delete(pArg); - eqp_render(pArg); + eqp_render(pArg, 0); /* print usage stats if stats on */ if( pArg && pArg->statsOn ){ @@ -4510,7 +4701,7 @@ static const char *(azHelp[]) = { #endif ".prompt MAIN CONTINUE Replace the standard prompts", #ifndef SQLITE_SHELL_FIDDLE - ".quit Exit this program", + ".quit Stop interpreting input stream, exit if primary.", ".read FILE Read input from FILE or command output", " If FILE begins with \"|\", it is a command that generates the input.", #endif @@ -4525,7 +4716,7 @@ static const char *(azHelp[]) = { ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", #endif - ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", + ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off", ".schema ?PATTERN? Show the CREATE statements matching PATTERN", " Options:", " --indent Try to pretty-print the schema", @@ -4598,6 +4789,7 @@ static const char *(azHelp[]) = { ".unmodule NAME ... Unregister virtual table modules", " --allexcept Unregister everything except those named", #endif + ".version Show source, library and compiler versions", ".vfsinfo ?AUX? Information about the top-level VFS", ".vfslist List all available VFSes", ".vfsname ?AUX? Print the name of the VFS stack", @@ -4806,7 +4998,7 @@ int deduceDatabaseType(const char *zName, int dfltZip){ } } fclose(f); - return rc; + return rc; } #ifndef SQLITE_OMIT_DESERIALIZE @@ -4905,8 +5097,8 @@ readHexDb_error: ** offset (4*) of the blob. */ static void shellInt32( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ const unsigned char *pBlob; @@ -4933,8 +5125,8 @@ static void shellInt32( ** using "..." with internal double-quote characters doubled. */ static void shellIdQuote( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ const char *zName = (const char*)sqlite3_value_text(argv[0]); @@ -4949,8 +5141,8 @@ static void shellIdQuote( ** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. */ static void shellUSleepFunc( - sqlite3_context *context, - int argcUnused, + sqlite3_context *context, + int argcUnused, sqlite3_value **argv ){ int sleep = sqlite3_value_int(argv[0]); @@ -4962,7 +5154,7 @@ static void shellUSleepFunc( /* ** Scalar function "shell_escape_crnl" used by the .recover command. ** The argument passed to this function is the output of built-in -** function quote(). If the first character of the input is "'", +** function quote(). If the first character of the input is "'", ** indicating that the value passed to quote() was a text value, ** then this function searches the input for "\n" and "\r" characters ** and adds a wrapper similar to the following: @@ -4973,8 +5165,8 @@ static void shellUSleepFunc( ** of the input is returned. */ static void shellEscapeCrnl( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ const char *zText = (const char*)sqlite3_value_text(argv[0]); @@ -5074,13 +5266,13 @@ static void open_db(ShellState *p, int openFlags){ if( zDbFilename==0 || zDbFilename[0]==0 ){ p->openMode = SHELL_OPEN_NORMAL; }else{ - p->openMode = (u8)deduceDatabaseType(zDbFilename, + p->openMode = (u8)deduceDatabaseType(zDbFilename, (openFlags & OPEN_DB_ZIPFILE)!=0); } } switch( p->openMode ){ case SHELL_OPEN_APPENDVFS: { - sqlite3_open_v2(zDbFilename, &p->db, + sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); break; } @@ -5115,12 +5307,15 @@ static void open_db(ShellState *p, int openFlags){ } exit(1); } + #ifndef SQLITE_OMIT_LOAD_EXTENSION sqlite3_enable_load_extension(p->db, 1); #endif sqlite3_shathree_init(p->db, 0, 0); sqlite3_uint_init(p->db, 0, 0); sqlite3_decimal_init(p->db, 0, 0); + sqlite3_base64_init(p->db, 0, 0); + sqlite3_base85_init(p->db, 0, 0); sqlite3_regexp_init(p->db, 0, 0); sqlite3_ieee_init(p->db, 0, 0); sqlite3_series_init(p->db, 0, 0); @@ -5137,6 +5332,34 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_sqlar_init(p->db, 0, 0); } #endif +#ifdef SQLITE_SHELL_EXTFUNCS + /* Create a preprocessing mechanism for extensions to make + * their own provisions for being built into the shell. + * This is a short-span macro. See further below for usage. + */ +#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant +#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) + /* Let custom-included extensions get their ..._init() called. + * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause + * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) + * inititialization routine to be called. + */ + { + int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); + /* Let custom-included extensions expose their functionality. + * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause + * the SQL functions, virtual tables, collating sequences or + * VFS's implemented by the extension to be registered. + */ + if( irc==SQLITE_OK + || irc==SQLITE_OK_LOAD_PERMANENTLY ){ + SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); + } +#undef SHELL_SUB_MACRO +#undef SHELL_SUBMACRO + } +#endif + sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, shellAddSchemaName, 0, 0); sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, @@ -5157,6 +5380,7 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, editFunc, 0, 0); #endif + if( p->openMode==SHELL_OPEN_ZIPFILE ){ char *zSql = sqlite3_mprintf( "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); @@ -5203,7 +5427,7 @@ void close_db(sqlite3 *db){ if( rc ){ utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); - } + } } #if HAVE_READLINE || HAVE_EDITLINE @@ -5233,6 +5457,8 @@ static char *readline_completion_generator(const char *text, int state){ return zRet; } static char **readline_completion(const char *zText, int iStart, int iEnd){ + (void)iStart; + (void)iEnd; rl_attempted_completion_over = 1; return rl_completion_matches(zText, readline_completion_generator); } @@ -5248,7 +5474,7 @@ static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ char *zSql; char zBuf[1000]; - if( nLine>sizeof(zBuf)-30 ) return; + if( nLine>(i64)sizeof(zBuf)-30 ) return; if( zLine[0]=='.' || zLine[0]=='#') return; for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} if( i==nLine-1 ) return; @@ -5264,7 +5490,7 @@ static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ while( sqlite3_step(pStmt)==SQLITE_ROW ){ const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); int nCompletion = sqlite3_column_bytes(pStmt, 0); - if( iStart+nCompletion < sizeof(zBuf)-1 && zCompletion ){ + if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){ memcpy(zBuf+iStart, zCompletion, nCompletion+1); linenoiseAddCompletion(lc, zBuf); } @@ -5459,10 +5685,13 @@ static int sql_trace_callback( /* ** A no-op routine that runs with the ".breakpoint" doc-command. This is ** a useful spot to set a debugger breakpoint. +** +** This routine does not do anything practical. The code are there simply +** to prevent the compiler from optimizing this routine out. */ static void test_breakpoint(void){ - static int nCall = 0; - nCall++; + static unsigned int nCall = 0; + if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n"); } /* @@ -5761,7 +5990,7 @@ static void tryToCloneSchema( char *zErrMsg = 0; zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema" - " WHERE %s", zWhere); + " WHERE %s ORDER BY rowid ASC", zWhere); shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ @@ -5774,12 +6003,14 @@ static void tryToCloneSchema( zName = sqlite3_column_text(pQuery, 0); zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; - printf("%s... ", zName); fflush(stdout); - sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); - if( zErrMsg ){ - utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); - sqlite3_free(zErrMsg); - zErrMsg = 0; + if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){ + printf("%s... ", zName); fflush(stdout); + sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_free(zErrMsg); + zErrMsg = 0; + } } if( xForEach ){ xForEach(p, newDb, (const char*)zName); @@ -5803,6 +6034,7 @@ static void tryToCloneSchema( zName = sqlite3_column_text(pQuery, 0); zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; + if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ) continue; printf("%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ @@ -5906,7 +6138,7 @@ static int db_int(sqlite3 *db, const char *zSql){ return res; } -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) +#if defined(SQLITE_SHELL_HAVE_RECOVER) /* ** Convert a 2-byte or 4-byte big-endian integer into a native integer */ @@ -6444,16 +6676,16 @@ static int lintDotCommand( #if !defined SQLITE_OMIT_VIRTUALTABLE static void shellPrepare( - sqlite3 *db, - int *pRc, - const char *zSql, + sqlite3 *db, + int *pRc, + const char *zSql, sqlite3_stmt **ppStmt ){ *ppStmt = 0; if( *pRc==SQLITE_OK ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ - raw_printf(stderr, "sql error: %s (%d)\n", + raw_printf(stderr, "sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db) ); *pRc = rc; @@ -6469,10 +6701,10 @@ static void shellPrepare( ** nuisance compiler warnings about "defined but not used". */ void shellPreparePrintf( - sqlite3 *db, - int *pRc, + sqlite3 *db, + int *pRc, sqlite3_stmt **ppStmt, - const char *zFmt, + const char *zFmt, ... ){ *ppStmt = 0; @@ -6498,7 +6730,7 @@ void shellPreparePrintf( ** nuisance compiler warnings about "defined but not used". */ void shellFinalize( - int *pRc, + int *pRc, sqlite3_stmt *pStmt ){ if( pStmt ){ @@ -6520,7 +6752,7 @@ void shellFinalize( ** nuisance compiler warnings about "defined but not used". */ void shellReset( - int *pRc, + int *pRc, sqlite3_stmt *pStmt ){ int rc = sqlite3_reset(pStmt); @@ -6568,7 +6800,7 @@ static int arUsage(FILE *f){ } /* -** Print an error message for the .ar command to stderr and return +** Print an error message for the .ar command to stderr and return ** SQLITE_ERROR. */ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ @@ -6634,7 +6866,7 @@ static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ break; case AR_SWITCH_APPEND: pAr->bAppend = 1; - /* Fall thru into --file */ + deliberate_fall_through; case AR_SWITCH_FILE: pAr->zFile = zArg; break; @@ -6649,7 +6881,7 @@ static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ /* ** Parse the command line for an ".ar" command. The results are written into ** structure (*pAr). SQLITE_OK is returned if the command line is parsed -** successfully, otherwise an error message is written to stderr and +** successfully, otherwise an error message is written to stderr and ** SQLITE_ERROR returned. */ static int arParseCommand( @@ -6845,7 +7077,7 @@ static int arCheckEntries(ArCommand *pAr){ ** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. */ static void arWhereClause( - int *pRc, + int *pRc, ArCommand *pAr, char **pzWhere /* OUT: New WHERE clause */ ){ @@ -6860,7 +7092,7 @@ static void arWhereClause( for(i=0; inArg; i++){ const char *z = pAr->azArg[i]; zWhere = sqlite3_mprintf( - "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", + "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z ); if( zWhere==0 ){ @@ -6875,10 +7107,10 @@ static void arWhereClause( } /* -** Implementation of .ar "lisT" command. +** Implementation of .ar "lisT" command. */ static int arListCommand(ArCommand *pAr){ - const char *zSql = "SELECT %s FROM %s WHERE %s"; + const char *zSql = "SELECT %s FROM %s WHERE %s"; const char *azCols[] = { "name", "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name" @@ -6900,7 +7132,7 @@ static int arListCommand(ArCommand *pAr){ if( pAr->bVerbose ){ utf8_printf(pAr->p->out, "%s % 10d %s %s\n", sqlite3_column_text(pSql, 0), - sqlite3_column_int(pSql, 1), + sqlite3_column_int(pSql, 1), sqlite3_column_text(pSql, 2), sqlite3_column_text(pSql, 3) ); @@ -6957,17 +7189,17 @@ static int arRemoveCommand(ArCommand *pAr){ } /* -** Implementation of .ar "eXtract" command. +** Implementation of .ar "eXtract" command. */ static int arExtractCommand(ArCommand *pAr){ - const char *zSql1 = + const char *zSql1 = "SELECT " " ($dir || name)," " writefile(($dir || name), %s, mode, mtime) " "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" " AND name NOT GLOB '*..[/\\]*'"; - const char *azExtraArg[] = { + const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", "data" }; @@ -6993,7 +7225,7 @@ static int arExtractCommand(ArCommand *pAr){ if( zDir==0 ) rc = SQLITE_NOMEM; } - shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, + shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere ); @@ -7071,7 +7303,7 @@ static int arCreateOrUpdateCommand( int bUpdate, /* true for a --create. */ int bOnlyIfChanged /* Only update if file has changed */ ){ - const char *zCreate = + const char *zCreate = "CREATE TABLE IF NOT EXISTS sqlar(\n" " name TEXT PRIMARY KEY, -- name of the file\n" " mode INT, -- access permissions\n" @@ -7113,7 +7345,7 @@ static int arCreateOrUpdateCommand( arExecSql(pAr, "PRAGMA page_size=512"); rc = arExecSql(pAr, "SAVEPOINT ar;"); if( rc!=SQLITE_OK ) return rc; - zTemp[0] = 0; + zTemp[0] = 0; if( pAr->bZip ){ /* Initialize the zipfile virtual table, if necessary */ if( pAr->zFile ){ @@ -7207,7 +7439,7 @@ static int arDotCommand( }else if( cmd.zFile ){ int flags; if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; - if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT + if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){ flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; }else{ @@ -7218,10 +7450,10 @@ static int arDotCommand( utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } - rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, + rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "cannot open file: %s (%s)\n", + utf8_printf(stderr, "cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db) ); goto end_ar_command; @@ -7337,7 +7569,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ bRowids = 0; } else{ - utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); + utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); showHelp(pState->out, azArg[0]); return 1; } @@ -7700,7 +7932,7 @@ static int do_meta_command(char *zLine, ShellState *p){ return 1; } if( zDb==0 ) zDb = "main"; - rc = sqlite3_open_v2(zDestFile, &pDest, + rc = sqlite3_open_v2(zDestFile, &pDest, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); if( rc!=SQLITE_OK ){ utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); @@ -7950,7 +8182,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg>1 && ii==ArraySize(aDbConfig) ){ utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); - } + } }else #if SQLITE_SHELL_HAVE_RECOVER @@ -8017,7 +8249,7 @@ static int do_meta_command(char *zLine, ShellState *p){ " substr(o.name, 1, length(name)+1) == (name||'_')" ")", azArg[i], azArg[i] ); - + if( zLike ){ zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr); }else{ @@ -8150,7 +8382,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_OMIT_VIRTUALTABLE if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ - raw_printf(stderr, + raw_printf(stderr, "Cannot run experimental commands such as \"%s\" in safe mode\n", azArg[0]); rc = 1; @@ -8169,7 +8401,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } aCtrl[] = { { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, - { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, + { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ @@ -8190,7 +8422,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); zCmd = nArg>=2 ? azArg[1] : "help"; - if( zCmd[0]=='-' + if( zCmd[0]=='-' && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0) && nArg>=4 ){ @@ -9418,10 +9650,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='p' && cli_strncmp(azArg[0], "prompt", n)==0 ){ if( nArg >= 2) { - strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); + shell_strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); } if( nArg >= 3) { - strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); + shell_strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); } }else @@ -9524,12 +9756,16 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ - p->scanstatsOn = (u8)booleanValue(azArg[1]); + if( cli_strcmp(azArg[1], "est")==0 ){ + p->scanstatsOn = 2; + }else{ + p->scanstatsOn = (u8)booleanValue(azArg[1]); + } #ifndef SQLITE_ENABLE_STMT_SCANSTATUS raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); #endif }else{ - raw_printf(stderr, "Usage: .scanstats on|off\n"); + raw_printf(stderr, "Usage: .scanstats on|off|est\n"); rc = 1; } }else @@ -9564,7 +9800,8 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( zName==0 ){ zName = azArg[ii]; }else{ - raw_printf(stderr, "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); + raw_printf(stderr, + "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } @@ -9680,7 +9917,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( (c=='s' && n==11 && cli_strncmp(azArg[0], "selecttrace", n)==0) || (c=='t' && n==9 && cli_strncmp(azArg[0], "treetrace", n)==0) ){ - unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); }else @@ -9865,7 +10102,8 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ - raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); + raw_printf(stderr, + "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); goto meta_command_exit; } pSession = &pAuxDb->aSession[pAuxDb->nSession]; @@ -10079,12 +10317,12 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( bSchema ){ - zSql = "SELECT lower(name) FROM sqlite_schema" + zSql = "SELECT lower(name) as tname FROM sqlite_schema" " WHERE type='table' AND coalesce(rootpage,0)>1" " UNION ALL SELECT 'sqlite_schema'" " ORDER BY 1 collate nocase"; }else{ - zSql = "SELECT lower(name) FROM sqlite_schema" + zSql = "SELECT lower(name) as tname FROM sqlite_schema" " WHERE type='table' AND coalesce(rootpage,0)>1" " AND name NOT LIKE 'sqlite_%'" " ORDER BY 1 collate nocase"; @@ -10145,6 +10383,58 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ shell_exec(p, zSql, 0); } +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE) + { + int lrc; + char *zRevText = /* Query for reversible to-blob-to-text check */ + "SELECT lower(name) as tname FROM sqlite_schema\n" + "WHERE type='table' AND coalesce(rootpage,0)>1\n" + "AND name NOT LIKE 'sqlite_%%'%s\n" + "ORDER BY 1 collate nocase"; + zRevText = sqlite3_mprintf(zRevText, zLike? " AND name LIKE $tspec" : ""); + zRevText = sqlite3_mprintf( + /* lower-case query is first run, producing upper-case query. */ + "with tabcols as materialized(\n" + "select tname, cname\n" + "from (" + " select ss.tname as tname, ti.name as cname\n" + " from (%z) ss\n inner join pragma_table_info(tname) ti))\n" + "select 'SELECT total(bad_text_count) AS bad_text_count\n" + "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n" + " from (select 'SELECT COUNT(*) AS bad_text_count\n" + "FROM '||tname||' WHERE '\n" + "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n" + "|| ' AND typeof('||cname||')=''text'' ',\n" + "' OR ') as query, tname from tabcols group by tname)" + , zRevText); + shell_check_oom(zRevText); + if( bDebug ) utf8_printf(p->out, "%s\n", zRevText); + lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); + assert(lrc==SQLITE_OK); + if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC); + lrc = SQLITE_ROW==sqlite3_step(pStmt); + if( lrc ){ + const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); + sqlite3_stmt *pCheckStmt; + lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); + if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery); + if( SQLITE_OK==lrc ){ + if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){ + double countIrreversible = sqlite3_column_double(pCheckStmt, 0); + if( countIrreversible>0 ){ + int sz = (int)(countIrreversible + 0.5); + utf8_printf(stderr, + "Digest includes %d invalidly encoded text field%s.\n", + sz, (sz>1)? "s": ""); + } + } + sqlite3_finalize(pCheckStmt); + } + sqlite3_finalize(pStmt); + } + sqlite3_free(zRevText); + } +#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */ sqlite3_free(zSql); }else @@ -10374,28 +10664,28 @@ static int do_meta_command(char *zLine, ShellState *p){ int unSafe; /* Not valid for --safe mode */ const char *zUsage; /* Usage notes */ } aCtrl[] = { - { "always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, - { "assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" }, - /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/ - /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ - { "byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, - { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, - /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ - { "imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, - { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, - { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, - { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, - { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, + {"always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, + {"assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" }, + /*{"benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/ + /*{"bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ + {"byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, + {"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, + /*{"fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ + {"imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, + {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, + {"localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, + {"never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, + {"optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, #ifdef YYCOVERAGE - { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" }, -#endif - { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " }, - { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" }, - { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" }, - { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" }, - { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, - { "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, - { "tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, + {"parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" }, +#endif + {"pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " }, + {"prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" }, + {"prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" }, + {"prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" }, + {"seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, + {"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, + {"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, }; int testctrl = -1; int iCtrl = -1; @@ -10656,7 +10946,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else{ output_file_close(p->traceOut); - p->traceOut = output_file_open(azArg[1], 0); + p->traceOut = output_file_open(z, 0); } } if( p->traceOut==0 ){ @@ -10820,7 +11110,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( c=='w' && cli_strncmp(azArg[0], "wheretrace", n)==0 ){ - unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); }else @@ -10872,7 +11162,8 @@ typedef enum { ** The scan is resumable for subsequent lines when prior ** return values are passed as the 2nd argument. */ -static QuickScanState quickscan(char *zLine, QuickScanState qss){ +static QuickScanState quickscan(char *zLine, QuickScanState qss, + SCAN_TRACKER_REFTYPE pst){ char cin; char cWait = (char)qss; /* intentional narrowing loss */ if( cWait==0 ){ @@ -10896,17 +11187,25 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss){ if( *zLine=='*' ){ ++zLine; cWait = '*'; + CONTINUE_PROMPT_AWAITS(pst, "/*"); qss = QSS_SETV(qss, cWait); goto TermScan; } break; case '[': cin = ']'; - /* fall thru */ + deliberate_fall_through; case '`': case '\'': case '"': cWait = cin; qss = QSS_HasDark | cWait; + CONTINUE_PROMPT_AWAITC(pst, cin); goto TermScan; + case '(': + CONTINUE_PAREN_INCR(pst, 1); + break; + case ')': + CONTINUE_PAREN_INCR(pst, -1); + break; default: break; } @@ -10922,16 +11221,19 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss){ continue; ++zLine; cWait = 0; + CONTINUE_PROMPT_AWAITC(pst, 0); qss = QSS_SETV(qss, 0); goto PlainScan; case '`': case '\'': case '"': if(*zLine==cWait){ + /* Swallow doubled end-delimiter.*/ ++zLine; continue; } - /* fall thru */ + deliberate_fall_through; case ']': cWait = 0; + CONTINUE_PROMPT_AWAITC(pst, 0); qss = QSS_SETV(qss, 0); goto PlainScan; default: assert(0); @@ -10955,17 +11257,15 @@ static int line_is_command_terminator(char *zLine){ zLine += 2; /* SQL Server */ else return 0; - return quickscan(zLine, QSS_Start)==QSS_Start; + return quickscan(zLine, QSS_Start, 0)==QSS_Start; } /* -** We need a default sqlite3_complete() implementation to use in case -** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes -** any arbitrary text is a complete SQL statement. This is not very -** user-friendly, but it does seem to work. +** The CLI needs a working sqlite3_complete() to work properly. So error +** out of the build if compiling with SQLITE_OMIT_COMPLETE. */ #ifdef SQLITE_OMIT_COMPLETE -#define sqlite3_complete(x) 1 +# error the CLI application is imcompatable with SQLITE_OMIT_COMPLETE. #endif /* @@ -11038,9 +11338,9 @@ static void echo_group_input(ShellState *p, const char *zDo){ #ifdef SQLITE_SHELL_FIDDLE /* -** Alternate one_input_line() impl for wasm mode. This is not in the primary impl -** because we need the global shellState and cannot access it from that function -** without moving lots of code around (creating a larger/messier diff). +** Alternate one_input_line() impl for wasm mode. This is not in the primary +** impl because we need the global shellState and cannot access it from that +** function without moving lots of code around (creating a larger/messier diff). */ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ /* Parse the next line from shellState.wasm.zInput. */ @@ -11097,6 +11397,7 @@ static int process_input(ShellState *p){ } ++p->inputNesting; p->lineno = 0; + CONTINUE_PROMPT_RESET; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ fflush(p->out); zLine = one_input_line(p->in, zLine, nSql>0); @@ -11115,7 +11416,7 @@ static int process_input(ShellState *p){ && line_is_complete(zSql, nSql) ){ memcpy(zLine,";",2); } - qss = quickscan(zLine, qss); + qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE); if( QSS_PLAINWHITE(qss) && nSql==0 ){ /* Just swallow single-line whitespace */ echo_group_input(p, zLine); @@ -11123,6 +11424,7 @@ static int process_input(ShellState *p){ continue; } if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ + CONTINUE_PROMPT_RESET; echo_group_input(p, zLine); if( zLine[0]=='.' ){ rc = do_meta_command(zLine, p); @@ -11158,6 +11460,7 @@ static int process_input(ShellState *p){ if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); + CONTINUE_PROMPT_RESET; nSql = 0; if( p->outCount ){ output_reset(p); @@ -11177,6 +11480,7 @@ static int process_input(ShellState *p){ /* This may be incomplete. Let the SQL parser deal with that. */ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); + CONTINUE_PROMPT_RESET; } free(zSql); free(zLine); @@ -11198,7 +11502,7 @@ static char *find_home_dir(int clearFlag){ if( home_dir ) return home_dir; #if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \ - && !defined(__RTP__) && !defined(_WRS_KERNEL) + && !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) { struct passwd *pwent; uid_t uid = getuid(); @@ -11253,9 +11557,43 @@ static char *find_home_dir(int clearFlag){ return home_dir; } +/* +** On non-Windows platforms, look for $XDG_CONFIG_HOME. +** If ${XDG_CONFIG_HOME}/sqlite3/sqliterc is found, return +** the path to it, else return 0. The result is cached for +** subsequent calls. +*/ +static const char *find_xdg_config(void){ +#if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \ + || defined(__RTP__) || defined(_WRS_KERNEL) + return 0; +#else + static int alreadyTried = 0; + static char *zConfig = 0; + const char *zXdgHome; + + if( alreadyTried!=0 ){ + return zConfig; + } + alreadyTried = 1; + zXdgHome = getenv("XDG_CONFIG_HOME"); + if( zXdgHome==0 ){ + return 0; + } + zConfig = sqlite3_mprintf("%s/sqlite3/sqliterc", zXdgHome); + shell_check_oom(zConfig); + if( access(zConfig,0)!=0 ){ + sqlite3_free(zConfig); + zConfig = 0; + } + return zConfig; +#endif +} + /* ** Read input from the file given by sqliterc_override. Or if that -** parameter is NULL, take input from ~/.sqliterc +** parameter is NULL, take input from the first of find_xdg_config() +** or ~/.sqliterc which is found. ** ** Returns the number of errors. */ @@ -11269,7 +11607,10 @@ static void process_sqliterc( FILE *inSaved = p->in; int savedLineno = p->lineno; - if (sqliterc == NULL) { + if( sqliterc == NULL ){ + sqliterc = find_xdg_config(); + } + if( sqliterc == NULL ){ home_dir = find_home_dir(0); if( home_dir==0 ){ raw_printf(stderr, "-- warning: cannot find home directory;" @@ -11818,12 +12159,12 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ data.openFlags |= SQLITE_OPEN_NOFOLLOW; }else if( cli_strcmp(z,"-ascii")==0 ){ data.mode = MODE_Ascii; - sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Unit); - sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Record); + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); }else if( cli_strcmp(z,"-tabs")==0 ){ data.mode = MODE_List; - sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Tab); - sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row); + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row); }else if( cli_strcmp(z,"-separator")==0 ){ sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, "%s",cmdline_option_value(argc,argv,++i)); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 0d3040a4b..049bc1713 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -174,8 +174,8 @@ extern "C" { ** function is provided for use in DLLs since DLL users usually do not have ** direct access to string constants within the DLL. ^The ** sqlite3_libversion_number() function returns an integer equal to -** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns -** a pointer to a string constant whose value is the same as the +** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns +** a pointer to a string constant whose value is the same as the ** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built ** using an edited copy of [the amalgamation], then the last four characters ** of the hash might be different from [SQLITE_SOURCE_ID].)^ @@ -190,20 +190,20 @@ int sqlite3_libversion_number(void); /* ** CAPI3REF: Run-Time Library Compilation Options Diagnostics ** -** ^The sqlite3_compileoption_used() function returns 0 or 1 -** indicating whether the specified option was defined at -** compile time. ^The SQLITE_ prefix may be omitted from the -** option name passed to sqlite3_compileoption_used(). +** ^The sqlite3_compileoption_used() function returns 0 or 1 +** indicating whether the specified option was defined at +** compile time. ^The SQLITE_ prefix may be omitted from the +** option name passed to sqlite3_compileoption_used(). ** ** ^The sqlite3_compileoption_get() function allows iterating ** over the list of options that were defined at compile time by ** returning the N-th compile time option string. ^If N is out of range, -** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ -** prefix is omitted from any strings returned by +** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ +** prefix is omitted from any strings returned by ** sqlite3_compileoption_get(). ** ** ^Support for the diagnostic functions sqlite3_compileoption_used() -** and sqlite3_compileoption_get() may be omitted by specifying the +** and sqlite3_compileoption_get() may be omitted by specifying the ** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time. ** ** See also: SQL functions [sqlite_compileoption_used()] and @@ -227,7 +227,7 @@ const char *sqlite3_compileoption_get(int N); ** SQLite can be compiled with or without mutexes. When ** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes ** are enabled and SQLite is threadsafe. When the -** [SQLITE_THREADSAFE] macro is 0, +** [SQLITE_THREADSAFE] macro is 0, ** the mutexes are omitted. Without the mutexes, it is not safe ** to use SQLite concurrently from more than one thread. ** @@ -284,14 +284,14 @@ typedef struct sqlite3 sqlite3; ** ** ^The sqlite3_int64 and sqlite_int64 types can store integer values ** between -9223372036854775808 and +9223372036854775807 inclusive. ^The -** sqlite3_uint64 and sqlite_uint64 types can store integer values +** sqlite3_uint64 and sqlite_uint64 types can store integer values ** between 0 and +18446744073709551615 inclusive. */ #ifdef SQLITE_INT64_TYPE typedef SQLITE_INT64_TYPE sqlite_int64; # ifdef SQLITE_UINT64_TYPE typedef SQLITE_UINT64_TYPE sqlite_uint64; -# else +# else typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; # endif #elif defined(_MSC_VER) || defined(__BORLANDC__) @@ -323,7 +323,7 @@ typedef sqlite_uint64 sqlite3_uint64; ** resources are deallocated. ** ** Ideally, applications should [sqlite3_finalize | finalize] all -** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and +** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated ** with the [sqlite3] object prior to attempting to close the object. ** ^If the database connection is associated with unfinalized prepared @@ -367,7 +367,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** The sqlite3_exec() interface is a convenience wrapper around ** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()], ** that allows an application to run multiple statements of SQL -** without having to use a lot of C code. +** without having to use a lot of C code. ** ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, ** semicolon-separate SQL statements passed into its 2nd argument, @@ -407,7 +407,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** from [sqlite3_column_name()]. ** ** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer -** to an empty string, or a pointer that contains only whitespace and/or +** to an empty string, or a pointer that contains only whitespace and/or ** SQL comments, then no SQL statements are evaluated and the database ** is not changed. ** @@ -563,6 +563,7 @@ int sqlite3_exec( #define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) +#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) @@ -765,7 +766,7 @@ struct sqlite3_file { ** requested lock, then the call to xLock() is a no-op. ** xUnlock() downgrades the database file lock to either SHARED or NONE. * If the lock is already at or below the requested lock state, then the call -** to xUnlock() is a no-op. +** to xUnlock() is a no-op. ** The xCheckReservedLock() method checks whether any database connection, ** either in this process or in some other process, is holding a RESERVED, ** PENDING, or EXCLUSIVE lock on the file. It returns true @@ -893,7 +894,7 @@ struct sqlite3_io_methods { **
  • [[SQLITE_FCNTL_CHUNK_SIZE]] ** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS ** extends and truncates the database file in chunks of a size specified -** by the user. The fourth argument to [sqlite3_file_control()] should +** by the user. The fourth argument to [sqlite3_file_control()] should ** point to an integer (type int) containing the new chunk-size to use ** for the nominated database. Allocating database file space in large ** chunks (say 1MB at a time), may reduce file-system fragmentation and @@ -916,24 +917,24 @@ struct sqlite3_io_methods { **
  • [[SQLITE_FCNTL_SYNC]] ** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and ** sent to the VFS immediately before the xSync method is invoked on a -** database file descriptor. Or, if the xSync method is not invoked -** because the user has configured SQLite with -** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place ** of the xSync method. In most cases, the pointer argument passed with ** this file-control is NULL. However, if the database file is being synced ** as part of a multi-database commit, the argument points to a nul-terminated -** string containing the transactions super-journal file name. VFSes that -** do not need this signal should silently ignore this opcode. Applications -** should not call [sqlite3_file_control()] with this opcode as doing so may -** disrupt the operation of the specialized VFSes that do require it. +** string containing the transactions super-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. ** **
  • [[SQLITE_FCNTL_COMMIT_PHASETWO]] ** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite ** and sent to the VFS after a transaction has been committed immediately ** but before the database is unlocked. VFSes that do not need this signal ** should silently ignore this opcode. Applications should not call -** [sqlite3_file_control()] with this opcode as doing so may disrupt the -** operation of the specialized VFSes that do require it. +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. ** **
  • [[SQLITE_FCNTL_WIN32_AV_RETRY]] ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic @@ -1006,7 +1007,7 @@ struct sqlite3_io_methods { ** upper-most shim only. ** **
  • [[SQLITE_FCNTL_PRAGMA]] -** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] +** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] ** file control is sent to the open [sqlite3_file] object corresponding ** to the database file to which the pragma statement refers. ^The argument ** to the [SQLITE_FCNTL_PRAGMA] file control is an array of @@ -1017,7 +1018,7 @@ struct sqlite3_io_methods { ** of the char** argument point to a string obtained from [sqlite3_mprintf()] ** or the equivalent and that string will become the result of the pragma or ** the error message if the pragma fails. ^If the -** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal +** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal ** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] ** file control returns [SQLITE_OK], then the parser assumes that the ** VFS has handled the PRAGMA itself and the parser generates a no-op @@ -1057,7 +1058,7 @@ struct sqlite3_io_methods { ** The argument is a pointer to a value of type sqlite3_int64 that ** is an advisory maximum number of bytes in the file to memory map. The ** pointer is overwritten with the old value. The limit is not changed if -** the value originally pointed to is negative, and so the current limit +** the value originally pointed to is negative, and so the current limit ** can be queried by passing in a pointer to a negative number. This ** file-control is used internally to implement [PRAGMA mmap_size]. ** @@ -1101,7 +1102,7 @@ struct sqlite3_io_methods { **
  • [[SQLITE_FCNTL_RBU]] ** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by ** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for -** this opcode. +** this opcode. ** **
  • [[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]] ** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then @@ -1118,7 +1119,7 @@ struct sqlite3_io_methods { ** **
  • [[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically. ** This file control returns [SQLITE_OK] if and only if the writes were ** all performed successfully and have been committed to persistent storage. @@ -1130,7 +1131,7 @@ struct sqlite3_io_methods { ** **
  • [[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. ** ^This file control takes the file descriptor out of batch write mode ** so that all subsequent write operations are independent. @@ -1139,8 +1140,8 @@ struct sqlite3_io_methods { ** **
  • [[SQLITE_FCNTL_LOCK_TIMEOUT]] ** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS -** to block for up to M milliseconds before failing when attempting to -** obtain a file lock using the xLock or xShmLock methods of the VFS. +** to block for up to M milliseconds before failing when attempting to +** obtain a file lock using the xLock or xShmLock methods of the VFS. ** The parameter is a pointer to a 32-bit signed integer that contains ** the value that M is to be set to. Before returning, the 32-bit signed ** integer is overwritten with the previous value of M. @@ -1175,7 +1176,6 @@ struct sqlite3_io_methods { ** in wal mode after the client has finished copying pages from the wal ** file to the database file, but before the *-shm file is updated to ** record the fact that the pages have been checkpointed. -** ** **
  • [[SQLITE_FCNTL_EXTERNAL_READER]] ** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect @@ -1188,16 +1188,16 @@ struct sqlite3_io_methods { ** the database is not a wal-mode db, or if there is no such connection in any ** other process. This opcode cannot be used to detect transactions opened ** by clients within the current process, only within other processes. -** ** **
  • [[SQLITE_FCNTL_CKSM_FILE]] -** Used by the cksmvfs VFS module only. +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use interally by the +** [checksum VFS shim] only. ** **
  • [[SQLITE_FCNTL_RESET_CACHE]] ** If there is currently no transaction open on the database, and the -** database is not a temp db, then this file-control purges the contents -** of the in-memory page cache. If there is an open transaction, or if -** the db is a temp-db, it is a no-op, not an error. +** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control +** purges the contents of the in-memory page cache. If there is an open +** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. ** */ #define SQLITE_FCNTL_LOCKSTATE 1 @@ -1344,14 +1344,14 @@ typedef const char *sqlite3_filename; ** the [sqlite3_file] can safely store a pointer to the ** filename if it needs to remember the filename for some reason. ** If the zFilename parameter to xOpen is a NULL pointer then xOpen -** must invent its own temporary name for the file. ^Whenever the +** must invent its own temporary name for the file. ^Whenever the ** xFilename parameter is NULL it will also be the case that the ** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. ** ** The flags argument to xOpen() includes all bits set in ** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] ** or [sqlite3_open16()] is used, then flags includes at least -** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. ** If xOpen() opens a file read-only then it sets *pOutFlags to ** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. ** @@ -1393,10 +1393,10 @@ typedef const char *sqlite3_filename; ** ^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 +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the ** SQLITE_OPEN_CREATE, is used to indicate that file should always ** be created, and that it is an error if it already exists. -** It is not used to indicate the file should be opened +** It is not used to indicate the file should be opened ** for exclusive access. ** ** ^At least szOsFile bytes of memory are allocated by SQLite @@ -1420,7 +1420,7 @@ typedef const char *sqlite3_filename; ** non-zero error code if there is an I/O error or if the name of ** the file given in the second argument is illegal. If SQLITE_OK ** is returned, then non-zero or zero is written into *pResOut to indicate -** whether or not the file is accessible. +** whether or not the file is accessible. ** ** ^SQLite will always allocate at least mxPathname+1 bytes for the ** output buffer xFullPathname. The exact size of the output buffer @@ -1440,16 +1440,16 @@ typedef const char *sqlite3_filename; ** 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 -** Day Number multiplied by 86400000 (the number of milliseconds in -** a 24-hour day). +** Day Number multiplied by 86400000 (the number of milliseconds in +** a 24-hour day). ** ^SQLite will use the xCurrentTimeInt64() method to get the current -** date and time if that method is available (if iVersion is 2 or +** 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. ** ** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided -** by some VFSes to facilitate testing of the VFS code. By overriding +** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can ** simulate faults and error conditions that would otherwise be difficult ** or impossible to induce. The set of system calls that can be overridden @@ -1540,7 +1540,7 @@ struct sqlite3_vfs { ** ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given on the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED @@ -1703,7 +1703,7 @@ int sqlite3_db_config(sqlite3*, int op, ...); ** This object is used in only one place in the SQLite interface. ** A pointer to an instance of this object is the argument to ** [sqlite3_config()] when the configuration option is -** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. +** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. ** By creating an instance of this object ** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) ** during configuration, an application can specify an alternative @@ -1826,7 +1826,7 @@ struct sqlite3_mem_methods { ** SQLITE_CONFIG_SERIALIZED configuration option.
  • ** ** [[SQLITE_CONFIG_MALLOC]]
    SQLITE_CONFIG_MALLOC
    -**
    ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +**
    ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is ** a pointer to an instance of the [sqlite3_mem_methods] structure. ** The argument specifies ** alternative low-level memory allocation routines to be used in place of @@ -1877,7 +1877,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_CONFIG_PAGECACHE]]
    SQLITE_CONFIG_PAGECACHE
    **
    ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool ** that SQLite can use for the database page cache with the default page -** cache implementation. +** cache implementation. ** This configuration option is a no-op if an application-defined page ** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]. ** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to @@ -1905,7 +1905,7 @@ struct sqlite3_mem_methods { ** additional cache line.
    ** ** [[SQLITE_CONFIG_HEAP]]
    SQLITE_CONFIG_HEAP
    -**
    ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +**
    ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer ** that SQLite will use for all of its dynamic memory allocation needs ** beyond those provided for by [SQLITE_CONFIG_PAGECACHE]. ** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled @@ -1960,7 +1960,7 @@ struct sqlite3_mem_methods { ** configuration on individual connections.)^
    ** ** [[SQLITE_CONFIG_PCACHE2]]
    SQLITE_CONFIG_PCACHE2
    -**
    ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +**
    ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** a pointer to an [sqlite3_pcache_methods2] object. This object specifies ** the interface to a custom page cache implementation.)^ ** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.
    @@ -1974,7 +1974,7 @@ struct sqlite3_mem_methods { **
    The SQLITE_CONFIG_LOG option is used to configure the SQLite ** global [error log]. ** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a -** function with a call signature of void(*)(void*,int,const char*), +** function with a call signature of void(*)(void*,int,const char*), ** and a pointer to void. ^If the function pointer is not NULL, it is ** invoked by [sqlite3_log()] to process each logging event. ^If the ** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op. @@ -2083,7 +2083,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_CONFIG_STMTJRNL_SPILL]] **
    SQLITE_CONFIG_STMTJRNL_SPILL **
    ^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which -** becomes the [statement journal] spill-to-disk threshold. +** becomes the [statement journal] spill-to-disk threshold. ** [Statement journals] are held in memory until their size (in bytes) ** exceeds this threshold, at which point they are written to disk. ** Or if the threshold is -1, statement journals are always held @@ -2105,7 +2105,7 @@ struct sqlite3_mem_methods { ** than the configured sorter-reference size threshold - then a reference ** is stored in each sorted record and the required column values loaded ** from the database as records are returned in sorted order. The default -** value for this option is to never use this optimization. Specifying a +** value for this option is to never use this optimization. Specifying a ** negative value for this option restores the default behaviour. ** This option is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option. @@ -2133,7 +2133,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ #define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ #define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ -/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ #define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ #define SQLITE_CONFIG_PCACHE 14 /* no-op */ #define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ @@ -2168,7 +2168,7 @@ struct sqlite3_mem_methods { **
    ** [[SQLITE_DBCONFIG_LOOKASIDE]] **
    SQLITE_DBCONFIG_LOOKASIDE
    -**
    ^This option takes three additional arguments that determine the +**
    ^This option takes three additional arguments that determine the ** [lookaside memory allocator] configuration for the [database connection]. ** ^The first argument (the third parameter to [sqlite3_db_config()] is a ** pointer to a memory buffer to use for lookaside memory. @@ -2184,9 +2184,9 @@ struct sqlite3_mem_methods { ** 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. +** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero. ** Any attempt to change the lookaside memory configuration when lookaside -** memory is in use leaves the configuration unchanged and returns +** memory is in use leaves the configuration unchanged and returns ** [SQLITE_BUSY].)^
    ** ** [[SQLITE_DBCONFIG_ENABLE_FKEY]] @@ -2334,8 +2334,12 @@ struct sqlite3_mem_methods { **
  • sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); ** ** Because resetting a database is destructive and irreversible, the -** process requires the use of this obscure API and multiple steps to help -** ensure that it does not happen by accident. +** process requires the use of this obscure API and multiple steps to +** help ensure that it does not happen by accident. Because this +** feature must be capable of resetting corrupt databases, and +** shutting down virtual tables may require access to that corrupt +** storage, the library must abandon any installed virtual tables +** without calling their xDestroy() methods. ** ** [[SQLITE_DBCONFIG_DEFENSIVE]]
    SQLITE_DBCONFIG_DEFENSIVE
    **
    The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the @@ -2674,8 +2678,12 @@ sqlite3_int64 sqlite3_total_changes64(sqlite3*); ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. +** +** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether +** or not an interrupt is currently in effect for [database connection] D. */ void sqlite3_interrupt(sqlite3*); +int sqlite3_is_interrupted(sqlite3*); /* ** CAPI3REF: Determine If An SQL Statement Is Complete @@ -3283,7 +3291,7 @@ SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** execution of the prepared statement, such as at the start of each ** trigger subprogram. ^The P argument is a pointer to the ** [prepared statement]. ^The X argument is a pointer to a string which -** is the unexpanded SQL text of the prepared statement or an SQL comment +** is the unexpanded SQL text of the prepared statement or an SQL comment ** that indicates the invocation of a trigger. ^The callback can compute ** the same text that would have been returned by the legacy [sqlite3_trace()] ** interface by using the X argument when X begins with "--" and invoking @@ -3293,13 +3301,13 @@ SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, **
    ^An SQLITE_TRACE_PROFILE callback provides approximately the same ** information as is provided by the [sqlite3_profile()] callback. ** ^The P argument is a pointer to the [prepared statement] and the -** X argument points to a 64-bit integer which is the estimated of -** the number of nanosecond that the prepared statement took to run. +** X argument points to a 64-bit integer which is approximately +** the number of nanoseconds that the prepared statement took to run. ** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes. ** ** [[SQLITE_TRACE_ROW]]
    SQLITE_TRACE_ROW
    **
    ^An SQLITE_TRACE_ROW callback is invoked whenever a prepared -** statement generates a single row of result. +** statement generates a single row of result. ** ^The P argument is a pointer to the [prepared statement] and the ** X argument is unused. ** @@ -3357,7 +3365,7 @@ int sqlite3_trace_v2( ** ** ^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 +** [sqlite3_step()] and [sqlite3_prepare()] and similar for ** database connection D. An example use for this ** interface is to keep a GUI updated during a large query. ** @@ -3382,6 +3390,13 @@ int sqlite3_trace_v2( ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** +** The progress handler callback would originally only be invoked from the +** bytecode engine. It still might be invoked during [sqlite3_prepare()] +** and similar because those routines might force a reparse of the schema +** which involves running the bytecode engine. However, beginning with +** SQLite version 3.41.0, the progress handler callback might also be +** invoked directly from [sqlite3_prepare()] while analyzing and generating +** code for complex queries. */ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); @@ -3389,7 +3404,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** CAPI3REF: Opening A New Database Connection ** CONSTRUCTOR: sqlite3 ** -** ^These routines open an SQLite database file as specified by the +** ^These routines open an SQLite database file as specified by the ** filename argument. ^The filename argument is interpreted as UTF-8 for ** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte ** order for sqlite3_open16(). ^(A [database connection] handle is usually @@ -3418,13 +3433,18 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** **
    ** ^(
    [SQLITE_OPEN_READONLY]
    -**
    The database is opened in read-only mode. If the database does not -** already exist, an error is returned.
    )^ +**
    The database is opened in read-only mode. If the database does +** not already exist, an error is returned.
    )^ ** ** ^(
    [SQLITE_OPEN_READWRITE]
    -**
    The database is opened for reading and writing if possible, or reading -** only if the file is write protected by the operating system. In either -** case the database must already exist, otherwise an error is returned.
    )^ +**
    The database is opened for reading and writing if possible, or +** reading only if the file is write protected by the operating +** system. In either case the database must already exist, otherwise +** an error is returned. For historical reasons, if opening in +** read-write mode fails due to OS-level permissions, an attempt is +** made to open it in read-only mode. [sqlite3_db_readonly()] can be +** used to determine whether the database is actually +** read-write.
    )^ ** ** ^(
    [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
    **
    The database is opened for reading and writing, and is created if @@ -5405,10 +5425,21 @@ int sqlite3_create_window_function( ** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in ** schema structures such as [CHECK constraints], [DEFAULT clauses], ** [expression indexes], [partial indexes], or [generated columns]. -** The SQLITE_DIRECTONLY flags is a security feature which is recommended -** for all [application-defined SQL functions], and especially for functions -** that have side-effects or that could potentially leak sensitive -** information. +**

    +** The SQLITE_DIRECTONLY flag is recommended for any +** [application-defined SQL function] +** that has side-effects or that could potentially leak sensitive information. +** This will prevent attacks in which an application is tricked +** into using a database file that has had its schema surreptiously +** modified to invoke the application-defined function in ways that are +** harmful. +**

    +** Some people say it is good practice to set SQLITE_DIRECTONLY on all +** [application-defined SQL functions], regardless of whether or not they +** are security sensitive, as doing so prevents those functions from being used +** inside of the database schema, and thus ensures that the database +** can be inspected and modified using generic tools (such as the [CLI]) +** that do not have access to the application-defined functions. **

    ** ** [[SQLITE_INNOCUOUS]]
    SQLITE_INNOCUOUS
    @@ -5549,16 +5580,6 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), ** then the conversion is performed. Otherwise no conversion occurs. ** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ ** -** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], -** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current encoding -** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) -** returns something other than SQLITE_TEXT, then the return value from -** sqlite3_value_encoding(X) is meaningless. ^Calls to -** sqlite3_value_text(X), sqlite3_value_text16(X), sqlite3_value_text16be(X), -** sqlite3_value_text16le(X), sqlite3_value_bytes(X), or -** sqlite3_value_bytes16(X) might change the encoding of the value X and -** thus change the return from subsequent calls to sqlite3_value_encoding(X). -** ** ^Within the [xUpdate] method of a [virtual table], the ** sqlite3_value_nochange(X) interface returns true if and only if ** the column corresponding to X is unchanged by the UPDATE operation @@ -5623,6 +5644,27 @@ int sqlite3_value_type(sqlite3_value*); int sqlite3_value_numeric_type(sqlite3_value*); int sqlite3_value_nochange(sqlite3_value*); int sqlite3_value_frombind(sqlite3_value*); + +/* +** CAPI3REF: Report the internal text encoding state of an sqlite3_value object +** METHOD: sqlite3_value +** +** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], +** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current text encoding +** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) +** returns something other than SQLITE_TEXT, then the return value from +** sqlite3_value_encoding(X) is meaningless. ^Calls to +** [sqlite3_value_text(X)], [sqlite3_value_text16(X)], [sqlite3_value_text16be(X)], +** [sqlite3_value_text16le(X)], [sqlite3_value_bytes(X)], or +** [sqlite3_value_bytes16(X)] might change the encoding of the value X and +** thus change the return from subsequent calls to sqlite3_value_encoding(X). +** +** This routine is intended for used by applications that test and validate +** the SQLite implementation. This routine is inquiring about the opaque +** internal state of an [sqlite3_value] object. Ordinary applications should +** not need to know what the internal state of an sqlite3_value object is and +** hence should not need to use this interface. +*/ int sqlite3_value_encoding(sqlite3_value*); /* @@ -7003,15 +7045,6 @@ int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void)); */ void sqlite3_reset_auto_extension(void); -/* -** The interface to the virtual-table mechanism is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ - /* ** Structures used by the virtual table interface */ @@ -7130,10 +7163,10 @@ struct sqlite3_module { ** when the omit flag is true there is no guarantee that the constraint will ** not be checked again using byte code.)^ ** -** ^The idxNum and idxPtr values are recorded and passed into the +** ^The idxNum and idxStr values are recorded and passed into the ** [xFilter] method. -** ^[sqlite3_free()] is used to free idxPtr if and only if -** needToFreeIdxPtr is true. +** ^[sqlite3_free()] is used to free idxStr if and only if +** needToFreeIdxStr is true. ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in ** the correct order to satisfy the ORDER BY clause so that no separate @@ -7253,7 +7286,7 @@ struct sqlite3_index_info { ** the [sqlite3_vtab_collation()] interface. For most real-world virtual ** tables, the collating sequence of constraints does not matter (for example ** because the constraints are numeric) and so the sqlite3_vtab_collation() -** interface is no commonly needed. +** interface is not commonly needed. */ #define SQLITE_INDEX_CONSTRAINT_EQ 2 #define SQLITE_INDEX_CONSTRAINT_GT 4 @@ -7412,16 +7445,6 @@ int sqlite3_declare_vtab(sqlite3*, const char *zSQL); */ int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); -/* -** The interface to the virtual-table mechanism defined above (back up -** to a comment remarkably similar to this one) is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ - /* ** CAPI3REF: A Handle To An Open BLOB ** KEYWORDS: {BLOB handle} {BLOB handles} @@ -9625,7 +9648,7 @@ int sqlite3_vtab_nochange(sqlite3_context*); **
  • Otherwise, "BINARY" is returned. ** */ -SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); +const char *sqlite3_vtab_collation(sqlite3_index_info*,int); /* ** CAPI3REF: Determine if a virtual table query is DISTINCT @@ -9782,21 +9805,20 @@ int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); ** is undefined and probably harmful. ** ** The X parameter in a call to sqlite3_vtab_in_first(X,P) or -** sqlite3_vtab_in_next(X,P) must be one of the parameters to the +** sqlite3_vtab_in_next(X,P) should be one of the parameters to the ** xFilter method which invokes these routines, and specifically ** a parameter that was previously selected for all-at-once IN constraint ** processing use the [sqlite3_vtab_in()] interface in the ** [xBestIndex|xBestIndex method]. ^(If the X parameter is not ** an xFilter argument that was selected for all-at-once IN constraint -** processing, then these routines return [SQLITE_MISUSE])^ or perhaps -** exhibit some other undefined or harmful behavior. +** processing, then these routines return [SQLITE_ERROR].)^ ** ** ^(Use these routines to access all values on the right-hand side ** of the IN constraint using code like the following: ** **

     **    for(rc=sqlite3_vtab_in_first(pList, &pVal);
    -**        rc==SQLITE_OK && pVal
    +**        rc==SQLITE_OK && pVal;
     **        rc=sqlite3_vtab_in_next(pList, &pVal)
     **    ){
     **      // do something with pVal
    @@ -9894,6 +9916,10 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal);
     ** managed by the prepared statement S and will be automatically freed when
     ** S is finalized.
     **
    +** Not all values are available for all query elements. When a value is
    +** not available, the output variable is set to -1 if the value is numeric,
    +** or to NULL if it is a string (SQLITE_SCANSTAT_NAME).
    +**
     ** 
    ** [[SQLITE_SCANSTAT_NLOOP]]
    SQLITE_SCANSTAT_NLOOP
    **
    ^The [sqlite3_int64] variable pointed to by the V parameter will be @@ -9921,12 +9947,24 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] ** description for the X-th loop. ** -** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECT
    +** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECTID
    **
    ^The "int" variable pointed to by the V parameter will be set to the -** "select-id" for the X-th loop. The select-id identifies which query or -** subquery the loop is part of. The main query has a select-id of zero. -** The select-id is the same value as is output in the first column -** of an [EXPLAIN QUERY PLAN] query. +** id for the X-th query plan element. The id value is unique within the +** statement. The select-id is the same value as is output in the first +** column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_PARENTID]]
    SQLITE_SCANSTAT_PARENTID
    +**
    The "int" variable pointed to by the V parameter will be set to the +** the id of the parent of the current query element, if applicable, or +** to zero if the query element has no parent. This is the same value as +** returned in the second column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_NCYCLE]]
    SQLITE_SCANSTAT_NCYCLE
    +**
    The sqlite3_int64 output value is set to the number of cycles, +** according to the processor time-stamp counter, that elapsed while the +** query element was being processed. This value is not available for +** all query elements - if it is unavailable the output variable is +** set to -1. **
    */ #define SQLITE_SCANSTAT_NLOOP 0 @@ -9935,12 +9973,14 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); #define SQLITE_SCANSTAT_NAME 3 #define SQLITE_SCANSTAT_EXPLAIN 4 #define SQLITE_SCANSTAT_SELECTID 5 +#define SQLITE_SCANSTAT_PARENTID 6 +#define SQLITE_SCANSTAT_NCYCLE 7 /* ** CAPI3REF: Prepared Statement Scan Status ** METHOD: sqlite3_stmt ** -** This interface returns information about the predicted and measured +** These interfaces return information about the predicted and measured ** performance for pStmt. Advanced applications can use this ** interface to compare the predicted and the measured performance and ** issue warnings and/or rerun [ANALYZE] if discrepancies are found. @@ -9951,19 +9991,25 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** ** The "iScanStatusOp" parameter determines which status information to return. ** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior -** of this interface is undefined. -** ^The requested measurement is written into a variable pointed to by -** the "pOut" parameter. -** Parameter "idx" identifies the specific loop to retrieve statistics for. -** Loops are numbered starting from zero. ^If idx is out of range - less than -** zero or greater than or equal to the total number of loops used to implement -** the statement - a non-zero value is returned and the variable that pOut -** points to is unchanged. -** -** ^Statistics might not be available for all loops in all statements. ^In cases -** where there exist loops with no available statistics, this function behaves -** as if the loop did not exist - it returns non-zero and leave the variable -** that pOut points to unchanged. +** of this interface is undefined. ^The requested measurement is written into +** a variable pointed to by the "pOut" parameter. +** +** The "flags" parameter must be passed a mask of flags. At present only +** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX +** is specified, then status information is available for all elements +** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If +** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements +** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of +** the EXPLAIN QUERY PLAN output) are available. Invoking API +** sqlite3_stmt_scanstatus() is equivalent to calling +** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. +** +** Parameter "idx" identifies the specific query element to retrieve statistics +** for. Query elements are numbered starting from zero. A value of -1 may be +** to query for statistics regarding the entire query. ^If idx is out of range +** - less than -1 or greater than or equal to the total number of query +** elements used to implement the statement - a non-zero value is returned and +** the variable that pOut points to is unchanged. ** ** See also: [sqlite3_stmt_scanstatus_reset()] */ @@ -9973,6 +10019,19 @@ int sqlite3_stmt_scanstatus( int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ void *pOut /* Result written here */ ); +int sqlite3_stmt_scanstatus_v2( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + int flags, /* Mask of flags defined below */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Prepared Statement Scan Status +** KEYWORDS: {scan status flags} +*/ +#define SQLITE_SCANSTAT_COMPLEX 0x0001 /* ** CAPI3REF: Zero Scan-Status Counters @@ -10063,6 +10122,10 @@ int sqlite3_db_cacheflush(sqlite3*); ** function is not defined for operations on WITHOUT ROWID tables, or for ** DELETE operations on rowid tables. ** +** ^The sqlite3_preupdate_hook(D,C,P) function returns the P argument from +** the previous call on the same [database connection] D, or NULL for +** the first call on D. +** ** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], ** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces ** provide additional information about a preupdate event. These routines @@ -10468,6 +10531,19 @@ int sqlite3_deserialize( # undef double #endif +#if defined(__wasi__) +# undef SQLITE_WASI +# define SQLITE_WASI 1 +# undef SQLITE_OMIT_WAL +# define SQLITE_OMIT_WAL 1/* because it requires shared memory APIs */ +# ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION +# endif +# ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +# endif +#endif + #ifdef __cplusplus } /* End of the 'extern "C"' block */ #endif diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 79702d7b2..19e030028 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -359,6 +359,8 @@ struct sqlite3_api_routines { const char *(*db_name)(sqlite3*,int); /* Version 3.40.0 and later */ int (*value_encoding)(sqlite3_value*); + /* Version 3.41.0 and later */ + int (*is_interrupted)(sqlite3*); }; /* @@ -685,6 +687,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_db_name sqlite3_api->db_name /* Version 3.40.0 and later */ #define sqlite3_value_encoding sqlite3_api->value_encoding +/* Version 3.41.0 and later */ +#define sqlite3_is_interrupted sqlite3_api->is_interrupted #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index e4b74f6d0..9af38e8df 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -819,15 +819,9 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */ /* ** The datatype used to store estimates of the number of rows in a -** table or index. This is an unsigned integer type. For 99.9% of -** the world, a 32-bit integer is sufficient. But a 64-bit integer -** can be used at compile-time if desired. +** table or index. */ -#ifdef SQLITE_64BIT_STATS - typedef u64 tRowcnt; /* 64-bit only if requested at compile-time */ -#else - typedef u32 tRowcnt; /* 32-bit is the default */ -#endif +typedef u64 tRowcnt; /* ** Estimated quantities used for query planning are stored as 16-bit @@ -973,9 +967,9 @@ typedef INT16_TYPE LogEst; ** pointers. In that case, only verify 4-byte alignment. */ #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC -# define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&3)==0) +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) #else -# define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&7)==0) +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) #endif /* @@ -1029,15 +1023,38 @@ extern u32 sqlite3TreeTrace; && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \ || defined(SQLITE_ENABLE_TREETRACE)) # define TREETRACE_ENABLED 1 -# define SELECTTRACE(K,P,S,X) \ +# define TREETRACE(K,P,S,X) \ if(sqlite3TreeTrace&(K)) \ sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ sqlite3DebugPrintf X #else -# define SELECTTRACE(K,P,S,X) +# define TREETRACE(K,P,S,X) # define TREETRACE_ENABLED 0 #endif +/* TREETRACE flag meanings: +** +** 0x00000001 Beginning and end of SELECT processing +** 0x00000002 WHERE clause processing +** 0x00000004 Query flattener +** 0x00000008 Result-set wildcard expansion +** 0x00000010 Query name resolution +** 0x00000020 Aggregate analysis +** 0x00000040 Window functions +** 0x00000080 Generated column names +** 0x00000100 Move HAVING terms into WHERE +** 0x00000200 Count-of-view optimization +** 0x00000400 Compound SELECT processing +** 0x00000800 Drop superfluous ORDER BY +** 0x00001000 LEFT JOIN simplifies to JOIN +** 0x00002000 Constant propagation +** 0x00004000 Push-down optimization +** 0x00008000 After all FROM-clause analysis +** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing +** 0x00020000 Transform DISTINCT into GROUP BY +** 0x00040000 SELECT tree dump after all code has been generated +*/ + /* ** Macros for "wheretrace" */ @@ -1050,6 +1067,36 @@ extern u32 sqlite3WhereTrace; # define WHERETRACE(K,X) #endif +/* +** Bits for the sqlite3WhereTrace mask: +** +** (---any--) Top-level block structure +** 0x-------F High-level debug messages +** 0x----FFF- More detail +** 0xFFFF---- Low-level debug messages +** +** 0x00000001 Code generation +** 0x00000002 Solver +** 0x00000004 Solver costs +** 0x00000008 WhereLoop inserts +** +** 0x00000010 Display sqlite3_index_info xBestIndex calls +** 0x00000020 Range an equality scan metrics +** 0x00000040 IN operator decisions +** 0x00000080 WhereLoop cost adjustements +** 0x00000100 +** 0x00000200 Covering index decisions +** 0x00000400 OR optimization +** 0x00000800 Index scanner +** 0x00001000 More details associated with code generation +** 0x00002000 +** 0x00004000 Show all WHERE terms at key points +** 0x00008000 Show the full SELECT statement at key places +** +** 0x00010000 Show more detail when printing WHERE terms +** 0x00020000 Show WHERE terms returned from whereScanNext() +*/ + /* ** An instance of the following structure is used to store the busy-handler @@ -1791,6 +1838,7 @@ struct sqlite3 { #define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ +#define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -1875,8 +1923,14 @@ struct FuncDestructor { ** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG ** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API ** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API -** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS +** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS -- opposite meanings!!! ** SQLITE_FUNC_ENCMASK depends on SQLITE_UTF* macros in the API +** +** Note that even though SQLITE_FUNC_UNSAFE and SQLITE_INNOCUOUS have the +** same bit value, their meanings are inverted. SQLITE_FUNC_UNSAFE is +** used internally and if set means tha the function has side effects. +** SQLITE_INNOCUOUS is used by application code and means "not unsafe". +** See multiple instances of tag-20230109-1. */ #define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */ #define SQLITE_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */ @@ -1993,7 +2047,7 @@ struct FuncDestructor { {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } #define JFUNCTION(zName, nArg, iArg, xFunc) \ - {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|\ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|\ SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ @@ -2185,6 +2239,7 @@ struct CollSeq { #define SQLITE_AFF_NUMERIC 0x43 /* 'C' */ #define SQLITE_AFF_INTEGER 0x44 /* 'D' */ #define SQLITE_AFF_REAL 0x45 /* 'E' */ +#define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */ #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) @@ -2716,16 +2771,15 @@ struct AggInfo { ** from source tables rather than from accumulators */ u8 useSortingIdx; /* In direct mode, reference the sorting index rather ** than the source table */ + u16 nSortingColumn; /* Number of columns in the sorting index */ int sortingIdx; /* Cursor number of the sorting index */ int sortingIdxPTab; /* Cursor number of pseudo-table */ - int nSortingColumn; /* Number of columns in the sorting index */ - int mnReg, mxReg; /* Range of registers allocated for aCol and aFunc */ + int iFirstReg; /* First register in range for aCol[] and aFunc[] */ ExprList *pGroupBy; /* The group by clause */ struct AggInfo_col { /* For each column used in source tables */ Table *pTab; /* Source table */ Expr *pCExpr; /* The original expression */ int iTable; /* Cursor number of the source table */ - int iMem; /* Memory location that acts as accumulator */ i16 iColumn; /* Column number within the source table */ i16 iSorterColumn; /* Column number in the sorting index */ } *aCol; @@ -2736,14 +2790,27 @@ struct AggInfo { struct AggInfo_func { /* For each aggregate function */ Expr *pFExpr; /* Expression encoding the function */ FuncDef *pFunc; /* The aggregate function implementation */ - int iMem; /* Memory location that acts as accumulator */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ int iDistAddr; /* Address of OP_OpenEphemeral */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ u32 selId; /* Select to which this AggInfo belongs */ +#ifdef SQLITE_DEBUG + Select *pSelect; /* SELECT statement that this AggInfo supports */ +#endif }; +/* +** Macros to compute aCol[] and aFunc[] register numbers. +** +** These macros should not be used prior to the call to +** assignAggregateRegisters() that computes the value of pAggInfo->iFirstReg. +** The assert()s that are part of this macro verify that constraint. +*/ +#define AggInfoColumnReg(A,I) (assert((A)->iFirstReg),(A)->iFirstReg+(I)) +#define AggInfoFuncReg(A,I) \ + (assert((A)->iFirstReg),(A)->iFirstReg+(A)->nColumn+(I)) + /* ** The datatype ynVar is a signed integer, either 16-bit or 32-bit. ** Usually it is 16-bits. But if SQLITE_MAX_VARIABLE_NUMBER is greater @@ -3403,6 +3470,7 @@ struct Select { #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ +#define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ /* True if S exists and has SF_NestedFrom */ #define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) @@ -3511,7 +3579,7 @@ struct SelectDest { int iSDParm2; /* A second parameter for the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ - char *zAffSdst; /* Affinity used for SRT_Set, SRT_Table, and similar */ + char *zAffSdst; /* Affinity used for SRT_Set */ ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ }; @@ -3570,10 +3638,10 @@ struct TriggerPrg { #else typedef unsigned int yDbMask; # define DbMaskTest(M,I) (((M)&(((yDbMask)1)<<(I)))!=0) -# define DbMaskZero(M) (M)=0 -# define DbMaskSet(M,I) (M)|=(((yDbMask)1)<<(I)) -# define DbMaskAllZero(M) (M)==0 -# define DbMaskNonZero(M) (M)!=0 +# define DbMaskZero(M) ((M)=0) +# define DbMaskSet(M,I) ((M)|=(((yDbMask)1)<<(I))) +# define DbMaskAllZero(M) ((M)==0) +# define DbMaskNonZero(M) ((M)!=0) #endif /* @@ -3656,7 +3724,7 @@ struct Parse { int nLabelAlloc; /* Number of slots in aLabel */ int *aLabel; /* Space to hold the labels */ ExprList *pConstExpr;/* Constant expressions */ - IndexedExpr *pIdxExpr;/* List of expressions used by active indexes */ + IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */ Token constraintName;/* Name of the constraint currently being parsed */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ @@ -3680,6 +3748,9 @@ struct Parse { u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u32 oldmask; /* Mask of old.* columns referenced */ u32 newmask; /* Mask of new.* columns referenced */ +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */ +#endif u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ u8 bReturning; /* Coding a RETURNING trigger */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ @@ -4428,13 +4499,11 @@ int sqlite3HeapNearlyFull(void); #ifdef SQLITE_USE_ALLOCA # define sqlite3StackAllocRaw(D,N) alloca(N) # define sqlite3StackAllocRawNN(D,N) alloca(N) -# define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N) # define sqlite3StackFree(D,P) # define sqlite3StackFreeNN(D,P) #else # define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N) # define sqlite3StackAllocRawNN(D,N) sqlite3DbMallocRawNN(D,N) -# define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N) # define sqlite3StackFree(D,P) sqlite3DbFree(D,P) # define sqlite3StackFreeNN(D,P) sqlite3DbFreeNN(D,P) #endif @@ -4559,6 +4628,7 @@ char *sqlite3VMPrintf(sqlite3*,const char*, va_list); #endif void sqlite3SetString(char **, sqlite3*, const char*); +void sqlite3ProgressCheck(Parse*); void sqlite3ErrorMsg(Parse*, const char*, ...); int sqlite3ErrorToParser(sqlite3*,int); void sqlite3Dequote(char*); @@ -4616,7 +4686,7 @@ const char *sqlite3ColumnColl(Column*); void sqlite3DeleteColumnNames(sqlite3*,Table*); void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); -void sqlite3SelectAddColumnTypeAndCollation(Parse*,Table*,Select*,char); +void sqlite3SubqueryColumnTypes(Parse*,Table*,Select*,char); Table *sqlite3ResultSetOfSelect(Parse*,Select*,char); void sqlite3OpenSchemaTable(Parse *, int); Index *sqlite3PrimaryKeyIndex(Table*); @@ -4936,7 +5006,7 @@ int sqlite3FixExpr(DbFixer*, Expr*); int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); int sqlite3RealSameAsInt(double,sqlite3_int64); i64 sqlite3RealToI64(double); -void sqlite3Int64ToText(i64,char*); +int sqlite3Int64ToText(i64,char*); int sqlite3AtoF(const char *z, double*, int, u8); int sqlite3GetInt32(const char *, int*); int sqlite3GetUInt32(const char*, u32*); @@ -4987,6 +5057,7 @@ char sqlite3CompareAffinity(const Expr *pExpr, char aff2); int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); char sqlite3TableColumnAffinity(const Table*,int); char sqlite3ExprAffinity(const Expr *pExpr); +int sqlite3ExprDataType(const Expr *pExpr); int sqlite3Atoi64(const char*, i64*, int, u8); int sqlite3DecOrHexToI64(const char*, i64*); void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); @@ -5003,6 +5074,9 @@ const char *sqlite3ErrName(int); #ifndef SQLITE_OMIT_DESERIALIZE int sqlite3MemdbInit(void); +int sqlite3IsMemdb(const sqlite3_vfs*); +#else +# define sqlite3IsMemdb(X) 0 #endif const char *sqlite3ErrStr(int); @@ -5142,7 +5216,7 @@ int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); -int sqlite3StrAccumEnlarge(StrAccum*, int); +int sqlite3StrAccumEnlarge(StrAccum*, i64); char *sqlite3StrAccumFinish(StrAccum*); void sqlite3StrAccumSetError(StrAccum*, u8); void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); @@ -5500,4 +5574,10 @@ const char **sqlite3CompileOptions(int *pnOpt); int sqlite3KvvfsInit(void); #endif +#if defined(VDBE_PROFILE) \ + || defined(SQLITE_PERFORMANCE_TRACE) \ + || defined(SQLITE_ENABLE_STMT_SCANSTATUS) +sqlite3_uint64 sqlite3Hwtime(void); +#endif + #endif /* SQLITEINT_H */ diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 4e25a6501..c455c29f1 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -3054,6 +3054,9 @@ static int SQLITE_TCLAPI DbObjCmd( if( pDb->zProgress ){ Tcl_AppendResult(interp, pDb->zProgress, (char*)0); } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + sqlite3_progress_handler(pDb->db, 0, 0, 0); +#endif }else if( objc==4 ){ char *zProgress; int len; diff --git a/src/test1.c b/src/test1.c index ba1a2252f..b1f0baafc 100644 --- a/src/test1.c +++ b/src/test1.c @@ -2188,7 +2188,7 @@ static int SQLITE_TCLAPI test_stmt_status( #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* -** Usage: sqlite3_stmt_scanstatus STMT IDX +** Usage: sqlite3_stmt_scanstatus ?-flags FLAGS? STMT IDX */ static int SQLITE_TCLAPI test_stmt_scanstatus( void * clientData, @@ -2203,36 +2203,99 @@ static int SQLITE_TCLAPI test_stmt_scanstatus( const char *zExplain; sqlite3_int64 nLoop; sqlite3_int64 nVisit; + sqlite3_int64 nCycle; double rEst; int res; + int flags = 0; + int iSelectId = 0; + int iParentId = 0; - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "STMT IDX"); + if( objc==5 ){ + struct Flag { + const char *zFlag; + int flag; + } aTbl[] = { + {"complex", SQLITE_SCANSTAT_COMPLEX}, + {0, 0} + }; + + Tcl_Obj **aFlag = 0; + int nFlag = 0; + int ii; + + if( Tcl_ListObjGetElements(interp, objv[2], &nFlag, &aFlag) ){ + return TCL_ERROR; + } + for(ii=0; ii +#endif #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -4086,6 +4157,7 @@ static int SQLITE_TCLAPI test_bind_value_from_select( ** -int64 ** -double ** -text +** -blob ** ** Each call clears static data. Called with no options does nothing ** but clear static data. @@ -4125,6 +4197,11 @@ static int SQLITE_TCLAPI test_carray_bind( sqlite3_free(((char**)aStaticData)[i]); } } + if( eStaticType==4 ){ + for(i=0; i -#include -#include -#include -#include - -/* -** Interfaces defined in server.c -*/ -int sqlite3_client_open(const char*, sqlite3**); -int sqlite3_client_prepare(sqlite3*,const char*,int, - sqlite3_stmt**,const char**); -int sqlite3_client_step(sqlite3_stmt*); -int sqlite3_client_reset(sqlite3_stmt*); -int sqlite3_client_finalize(sqlite3_stmt*); -int sqlite3_client_close(sqlite3*); -int sqlite3_server_start(void); -int sqlite3_server_stop(void); -void sqlite3_server_start2(int *pnDecr); - -/* -** Each thread is controlled by an instance of the following -** structure. -*/ -typedef struct Thread Thread; -struct Thread { - /* The first group of fields are writable by the supervisor thread - ** and read-only to the client threads - */ - char *zFilename; /* Name of database file */ - void (*xOp)(Thread*); /* next operation to do */ - char *zArg; /* argument usable by xOp */ - volatile int opnum; /* Operation number */ - volatile int busy; /* True if this thread is in use */ - - /* The next group of fields are writable by the client threads - ** but read-only to the superviser thread. - */ - volatile int completed; /* Number of operations completed */ - sqlite3 *db; /* Open database */ - sqlite3_stmt *pStmt; /* Pending operation */ - char *zErr; /* operation error */ - char *zStaticErr; /* Static error message */ - int rc; /* operation return code */ - int argc; /* number of columns in result */ - const char *argv[100]; /* result columns */ - const char *colv[100]; /* result column names */ - - /* Initialized to 1 by the supervisor thread when the client is - ** created, and then deemed read-only to the supervisor thread. - ** Is set to 0 by the server thread belonging to this client - ** just before it exits. - */ - int nServer; /* Number of server threads running */ -}; - -/* -** There can be as many as 26 threads running at once. Each is named -** by a capital letter: A, B, C, ..., Y, Z. -*/ -#define N_THREAD 26 -static Thread threadset[N_THREAD]; - -/* -** The main loop for a thread. Threads use busy waiting. -*/ -static void *client_main(void *pArg){ - Thread *p = (Thread*)pArg; - if( p->db ){ - sqlite3_client_close(p->db); - } - sqlite3_client_open(p->zFilename, &p->db); - if( SQLITE_OK!=sqlite3_errcode(p->db) ){ - p->zErr = strdup(sqlite3_errmsg(p->db)); - sqlite3_client_close(p->db); - p->db = 0; - } - p->pStmt = 0; - p->completed = 1; - while( p->opnum<=p->completed ) sched_yield(); - while( p->xOp ){ - if( p->zErr && p->zErr!=p->zStaticErr ){ - sqlite3_free(p->zErr); - p->zErr = 0; - } - (*p->xOp)(p); - p->completed++; - while( p->opnum<=p->completed ) sched_yield(); - } - if( p->pStmt ){ - sqlite3_client_finalize(p->pStmt); - p->pStmt = 0; - } - if( p->db ){ - sqlite3_client_close(p->db); - p->db = 0; - } - if( p->zErr && p->zErr!=p->zStaticErr ){ - sqlite3_free(p->zErr); - p->zErr = 0; - } - p->completed++; -#ifndef SQLITE_OMIT_DEPRECATED - sqlite3_thread_cleanup(); -#endif - return 0; -} - -/* -** Get a thread ID which is an upper case letter. Return the index. -** If the argument is not a valid thread ID put an error message in -** the interpreter and return -1. -*/ -static int parse_client_id(Tcl_Interp *interp, const char *zArg){ - if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper((unsigned char)zArg[0]) ){ - Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0); - return -1; - } - return zArg[0] - 'A'; -} - -/* -** Usage: client_create NAME FILENAME -** -** NAME should be an upper case letter. Start the thread running with -** an open connection to the given database. -*/ -static int SQLITE_TCLAPI tcl_client_create( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - pthread_t x; - int rc; - - if( argc!=3 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID FILENAME", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( threadset[i].busy ){ - Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0); - return TCL_ERROR; - } - threadset[i].busy = 1; - sqlite3_free(threadset[i].zFilename); - threadset[i].zFilename = sqlite3_mprintf("%s", argv[2]); - threadset[i].opnum = 1; - threadset[i].completed = 0; - rc = pthread_create(&x, 0, client_main, &threadset[i]); - if( rc ){ - Tcl_AppendResult(interp, "failed to create the thread", 0); - sqlite3_free(threadset[i].zFilename); - threadset[i].busy = 0; - return TCL_ERROR; - } - pthread_detach(x); - if( threadset[i].nServer==0 ){ - threadset[i].nServer = 1; - sqlite3_server_start2(&threadset[i].nServer); - } - return TCL_OK; -} - -/* -** Wait for a thread to reach its idle state. -*/ -static void client_wait(Thread *p){ - while( p->opnum>p->completed ) sched_yield(); -} - -/* -** Usage: client_wait ID -** -** Wait on thread ID to reach its idle state. -*/ -static int SQLITE_TCLAPI tcl_client_wait( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - return TCL_OK; -} - -/* -** Stop a thread. -*/ -static void stop_thread(Thread *p){ - client_wait(p); - p->xOp = 0; - p->opnum++; - client_wait(p); - sqlite3_free(p->zArg); - p->zArg = 0; - sqlite3_free(p->zFilename); - p->zFilename = 0; - p->busy = 0; -} - -/* -** Usage: client_halt ID -** -** Cause a client thread to shut itself down. Wait for the shutdown to be -** completed. If ID is "*" then stop all client threads. -*/ -static int SQLITE_TCLAPI tcl_client_halt( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID", 0); - return TCL_ERROR; - } - if( argv[1][0]=='*' && argv[1][1]==0 ){ - for(i=0; i=N_THREAD ){ - sqlite3_server_stop(); - while( 1 ){ - for(i=0; i=threadset[i].argc ){ - Tcl_AppendResult(interp, "column number out of range", 0); - return TCL_ERROR; - } - Tcl_AppendResult(interp, threadset[i].argv[n], 0); - return TCL_OK; -} - -/* -** Usage: client_colname ID N -** -** Wait on the most recent client_step to complete, then return the -** name of the N-th columns in the result set. -*/ -static int SQLITE_TCLAPI tcl_client_colname( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - int n; - - if( argc!=3 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID N", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR; - client_wait(&threadset[i]); - if( n<0 || n>=threadset[i].argc ){ - Tcl_AppendResult(interp, "column number out of range", 0); - return TCL_ERROR; - } - Tcl_AppendResult(interp, threadset[i].colv[n], 0); - return TCL_OK; -} - -extern const char *sqlite3ErrName(int); - -/* -** Usage: client_result ID -** -** Wait on the most recent operation to complete, then return the -** result code from that operation. -*/ -static int SQLITE_TCLAPI tcl_client_result( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - const char *zName; - - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - zName = sqlite3ErrName(threadset[i].rc); - Tcl_AppendResult(interp, zName, 0); - return TCL_OK; -} - -/* -** Usage: client_error ID -** -** Wait on the most recent operation to complete, then return the -** error string. -*/ -static int SQLITE_TCLAPI tcl_client_error( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - Tcl_AppendResult(interp, threadset[i].zErr, 0); - return TCL_OK; -} - -/* -** This procedure runs in the thread to compile an SQL statement. -*/ -static void do_compile(Thread *p){ - if( p->db==0 ){ - p->zErr = p->zStaticErr = "no database is open"; - p->rc = SQLITE_ERROR; - return; - } - if( p->pStmt ){ - sqlite3_client_finalize(p->pStmt); - p->pStmt = 0; - } - p->rc = sqlite3_client_prepare(p->db, p->zArg, -1, &p->pStmt, 0); -} - -/* -** Usage: client_compile ID SQL -** -** Compile a new virtual machine. -*/ -static int SQLITE_TCLAPI tcl_client_compile( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - if( argc!=3 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID SQL", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - threadset[i].xOp = do_compile; - sqlite3_free(threadset[i].zArg); - threadset[i].zArg = sqlite3_mprintf("%s", argv[2]); - threadset[i].opnum++; - return TCL_OK; -} - -/* -** This procedure runs in the thread to step the virtual machine. -*/ -static void do_step(Thread *p){ - int i; - if( p->pStmt==0 ){ - p->zErr = p->zStaticErr = "no virtual machine available"; - p->rc = SQLITE_ERROR; - return; - } - p->rc = sqlite3_client_step(p->pStmt); - if( p->rc==SQLITE_ROW ){ - p->argc = sqlite3_column_count(p->pStmt); - for(i=0; ipStmt); i++){ - p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i); - } - for(i=0; iargc; i++){ - p->colv[i] = sqlite3_column_name(p->pStmt, i); - } - } -} - -/* -** Usage: client_step ID -** -** Advance the virtual machine by one step -*/ -static int SQLITE_TCLAPI tcl_client_step( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " IDL", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - threadset[i].xOp = do_step; - threadset[i].opnum++; - return TCL_OK; -} - -/* -** This procedure runs in the thread to finalize a virtual machine. -*/ -static void do_finalize(Thread *p){ - if( p->pStmt==0 ){ - p->zErr = p->zStaticErr = "no virtual machine available"; - p->rc = SQLITE_ERROR; - return; - } - p->rc = sqlite3_client_finalize(p->pStmt); - p->pStmt = 0; -} - -/* -** Usage: client_finalize ID -** -** Finalize the virtual machine. -*/ -static int SQLITE_TCLAPI tcl_client_finalize( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " IDL", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - threadset[i].xOp = do_finalize; - sqlite3_free(threadset[i].zArg); - threadset[i].zArg = 0; - threadset[i].opnum++; - return TCL_OK; -} - -/* -** This procedure runs in the thread to reset a virtual machine. -*/ -static void do_reset(Thread *p){ - if( p->pStmt==0 ){ - p->zErr = p->zStaticErr = "no virtual machine available"; - p->rc = SQLITE_ERROR; - return; - } - p->rc = sqlite3_client_reset(p->pStmt); - p->pStmt = 0; -} - -/* -** Usage: client_reset ID -** -** Finalize the virtual machine. -*/ -static int SQLITE_TCLAPI tcl_client_reset( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i; - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " IDL", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - threadset[i].xOp = do_reset; - sqlite3_free(threadset[i].zArg); - threadset[i].zArg = 0; - threadset[i].opnum++; - return TCL_OK; -} - -/* -** Usage: client_swap ID ID -** -** Interchange the sqlite* pointer between two threads. -*/ -static int SQLITE_TCLAPI tcl_client_swap( - void *NotUsed, - Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ - int argc, /* Number of arguments */ - const char **argv /* Text of each argument */ -){ - int i, j; - sqlite3 *temp; - if( argc!=3 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ID1 ID2", 0); - return TCL_ERROR; - } - i = parse_client_id(interp, argv[1]); - if( i<0 ) return TCL_ERROR; - if( !threadset[i].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[i]); - j = parse_client_id(interp, argv[2]); - if( j<0 ) return TCL_ERROR; - if( !threadset[j].busy ){ - Tcl_AppendResult(interp, "no such thread", 0); - return TCL_ERROR; - } - client_wait(&threadset[j]); - temp = threadset[i].db; - threadset[i].db = threadset[j].db; - threadset[j].db = temp; - return TCL_OK; -} - -/* -** Register commands with the TCL interpreter. -*/ -int Sqlitetest7_Init(Tcl_Interp *interp){ - static struct { - char *zName; - Tcl_CmdProc *xProc; - } aCmd[] = { - { "client_create", (Tcl_CmdProc*)tcl_client_create }, - { "client_wait", (Tcl_CmdProc*)tcl_client_wait }, - { "client_halt", (Tcl_CmdProc*)tcl_client_halt }, - { "client_argc", (Tcl_CmdProc*)tcl_client_argc }, - { "client_argv", (Tcl_CmdProc*)tcl_client_argv }, - { "client_colname", (Tcl_CmdProc*)tcl_client_colname }, - { "client_result", (Tcl_CmdProc*)tcl_client_result }, - { "client_error", (Tcl_CmdProc*)tcl_client_error }, - { "client_compile", (Tcl_CmdProc*)tcl_client_compile }, - { "client_step", (Tcl_CmdProc*)tcl_client_step }, - { "client_reset", (Tcl_CmdProc*)tcl_client_reset }, - { "client_finalize", (Tcl_CmdProc*)tcl_client_finalize }, - { "client_swap", (Tcl_CmdProc*)tcl_client_swap }, - }; - int i; - - for(i=0; ipFindFunctionList ){ + TestFindFunction *p = pTab->pFindFunctionList; + pTab->pFindFunctionList = p->pNext; + sqlite3_free(p); + } Tcl_DecrRefCount(pTab->pCmd); sqlite3_free(pTab); return SQLITE_OK; @@ -398,7 +413,7 @@ static void testBestIndexObjConstraints( for(ii=0; iinConstraint; ii++){ struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii]; Tcl_Obj *pElem = Tcl_NewObj(); - const char *zOp = "?"; + const char *zOp = 0; Tcl_IncrRefCount(pElem); @@ -438,7 +453,11 @@ static void testBestIndexObjConstraints( } Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("op", -1)); - Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj(zOp, -1)); + if( zOp ){ + Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj(zOp, -1)); + }else{ + Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->op)); + } Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("column", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->iColumn)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("usable", -1)); @@ -693,6 +712,83 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ return rc; } +static void tclFunction(sqlite3_context *pCtx, int nArg, sqlite3_value **apArg){ + TestFindFunction *p = (TestFindFunction*)sqlite3_user_data(pCtx); + Tcl_Interp *interp = p->pTab->interp; + Tcl_Obj *pScript = 0; + Tcl_Obj *pRet = 0; + int ii; + + pScript = Tcl_DuplicateObj(p->pTab->pCmd); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("function", -1)); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(p->zName, -1)); + + for(ii=0; iiinterp; + Tcl_Obj *pScript = 0; + int rc = SQLITE_OK; + + pScript = Tcl_DuplicateObj(pTab->pCmd); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement( + interp, pScript, Tcl_NewStringObj("xFindFunction", -1) + ); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewIntObj(nArg)); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(zName, -1)); + rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(pScript); + + if( rc==SQLITE_OK ){ + Tcl_Obj *pObj = Tcl_GetObjResult(interp); + + if( Tcl_GetIntFromObj(interp, pObj, &iRet) ){ + rc = SQLITE_ERROR; + }else if( iRet>0 ){ + sqlite3_int64 nName = strlen(zName); + sqlite3_int64 nByte = nName + 1 + sizeof(TestFindFunction); + TestFindFunction *pNew = 0; + + pNew = (TestFindFunction*)sqlite3_malloc64(nByte); + if( pNew==0 ){ + iRet = 0; + }else{ + memset(pNew, 0, nByte); + pNew->zName = (const char*)&pNew[1]; + memcpy((char*)pNew->zName, zName, nName); + pNew->pTab = pTab; + pNew->pNext = pTab->pFindFunctionList; + pTab->pFindFunctionList = pNew; + *ppArg = (void*)pNew; + *pxFunc = tclFunction; + } + } + } + + return iRet; +} + /* ** A virtual table module that provides read-only access to a ** Tcl global variable namespace. @@ -716,7 +812,7 @@ static sqlite3_module tclModule = { 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ - 0, /* xFindMethod */ + tclFindFunction, /* xFindFunction */ 0, /* xRename */ }; diff --git a/src/test_demovfs.c b/src/test_demovfs.c index 29307675b..e990e98f2 100644 --- a/src/test_demovfs.c +++ b/src/test_demovfs.c @@ -461,7 +461,6 @@ static int demoDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ if( rc==0 && dirSync ){ int dfd; /* File descriptor open on directory */ - int i; /* Iterator variable */ char *zSlash; char zDir[MAXPATHNAME+1]; /* Name of directory containing file zPath */ diff --git a/src/test_server.c b/src/test_server.c deleted file mode 100644 index 4eb1cf196..000000000 --- a/src/test_server.c +++ /dev/null @@ -1,516 +0,0 @@ -/* -** 2006 January 07 -** -** 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 demonstration code. Nothing in this file gets compiled -** or linked into the SQLite library unless you use a non-standard option: -** -** -DSQLITE_SERVER=1 -** -** The configure script will never generate a Makefile with the option -** above. You will need to manually modify the Makefile if you want to -** include any of the code from this file in your project. Or, at your -** option, you may copy and paste the code from this file and -** thereby avoiding a recompile of SQLite. -** -** -** This source file demonstrates how to use SQLite to create an SQL database -** server thread in a multiple-threaded program. One or more client threads -** send messages to the server thread and the server thread processes those -** messages in the order received and returns the results to the client. -** -** One might ask: "Why bother? Why not just let each thread connect -** to the database directly?" There are a several of reasons to -** prefer the client/server approach. -** -** (1) Some systems (ex: Redhat9) have broken threading implementations -** that prevent SQLite database connections from being used in -** a thread different from the one where they were created. With -** the client/server approach, all database connections are created -** and used within the server thread. Client calls to the database -** can be made from multiple threads (though not at the same time!) -** -** (2) Beginning with SQLite version 3.3.0, when two or more -** connections to the same database occur within the same thread, -** they can optionally share their database cache. This reduces -** I/O and memory requirements. Cache shared is controlled using -** the sqlite3_enable_shared_cache() API. -** -** (3) Database connections on a shared cache use table-level locking -** instead of file-level locking for improved concurrency. -** -** (4) Database connections on a shared cache can by optionally -** set to READ UNCOMMITTED isolation. (The default isolation for -** SQLite is SERIALIZABLE.) When this occurs, readers will -** never be blocked by a writer and writers will not be -** blocked by readers. There can still only be a single writer -** at a time, but multiple readers can simultaneously exist with -** that writer. This is a huge increase in concurrency. -** -** To summarize the rational for using a client/server approach: prior -** to SQLite version 3.3.0 it probably was not worth the trouble. But -** with SQLite version 3.3.0 and beyond you can get significant performance -** and concurrency improvements and memory usage reductions by going -** client/server. -** -** Note: The extra features of version 3.3.0 described by points (2) -** through (4) above are only available if you compile without the -** option -DSQLITE_OMIT_SHARED_CACHE. -** -** Here is how the client/server approach works: The database server -** thread is started on this procedure: -** -** void *sqlite3_server(void *NotUsed); -** -** The sqlite_server procedure runs as long as the g.serverHalt variable -** is false. A mutex is used to make sure no more than one server runs -** at a time. The server waits for messages to arrive on a message -** queue and processes the messages in order. -** -** Two convenience routines are provided for starting and stopping the -** server thread: -** -** void sqlite3_server_start(void); -** void sqlite3_server_stop(void); -** -** Both of the convenience routines return immediately. Neither will -** ever give an error. If a server is already started or already halted, -** then the routines are effectively no-ops. -** -** Clients use the following interfaces: -** -** sqlite3_client_open -** sqlite3_client_prepare -** sqlite3_client_step -** sqlite3_client_reset -** sqlite3_client_finalize -** sqlite3_client_close -** -** These interfaces work exactly like the standard core SQLite interfaces -** having the same names without the "_client_" infix. Many other SQLite -** interfaces can be used directly without having to send messages to the -** server as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined. -** The following interfaces fall into this second category: -** -** sqlite3_bind_* -** sqlite3_changes -** sqlite3_clear_bindings -** sqlite3_column_* -** sqlite3_complete -** sqlite3_create_collation -** sqlite3_create_function -** sqlite3_data_count -** sqlite3_db_handle -** sqlite3_errcode -** sqlite3_errmsg -** sqlite3_last_insert_rowid -** sqlite3_total_changes -** sqlite3_transfer_bindings -** -** A single SQLite connection (an sqlite3* object) or an SQLite statement -** (an sqlite3_stmt* object) should only be passed to a single interface -** function at a time. The connections and statements can be passed from -** any thread to any of the functions listed in the second group above as -** long as the same connection is not in use by two threads at once and -** as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined. Additional -** information about the SQLITE_ENABLE_MEMORY_MANAGEMENT constraint is -** below. -** -** The busy handler for all database connections should remain turned -** off. That means that any lock contention will cause the associated -** sqlite3_client_step() call to return immediately with an SQLITE_BUSY -** error code. If a busy handler is enabled and lock contention occurs, -** then the entire server thread will block. This will cause not only -** the requesting client to block but every other database client as -** well. It is possible to enhance the code below so that lock -** contention will cause the message to be placed back on the top of -** the queue to be tried again later. But such enhanced processing is -** not included here, in order to keep the example simple. -** -** This example code assumes the use of pthreads. Pthreads -** implementations are available for windows. (See, for example -** http://sourceware.org/pthreads-win32/announcement.html.) Or, you -** can translate the locking and thread synchronization code to use -** windows primitives easily enough. The details are left as an -** exercise to the reader. -** -**** Restrictions Associated With SQLITE_ENABLE_MEMORY_MANAGEMENT **** -** -** If you compile with SQLITE_ENABLE_MEMORY_MANAGEMENT defined, then -** SQLite includes code that tracks how much memory is being used by -** each thread. These memory counts can become confused if memory -** is allocated by one thread and then freed by another. For that -** reason, when SQLITE_ENABLE_MEMORY_MANAGEMENT is used, all operations -** that might allocate or free memory should be performanced in the same -** thread that originally created the database connection. In that case, -** many of the operations that are listed above as safe to be performed -** in separate threads would need to be sent over to the server to be -** done there. If SQLITE_ENABLE_MEMORY_MANAGEMENT is defined, then -** the following functions can be used safely from different threads -** without messing up the allocation counts: -** -** sqlite3_bind_parameter_name -** sqlite3_bind_parameter_index -** sqlite3_changes -** sqlite3_column_blob -** sqlite3_column_count -** sqlite3_complete -** sqlite3_data_count -** sqlite3_db_handle -** sqlite3_errcode -** sqlite3_errmsg -** sqlite3_last_insert_rowid -** sqlite3_total_changes -** -** The remaining functions are not thread-safe when memory management -** is enabled. So one would have to define some new interface routines -** along the following lines: -** -** sqlite3_client_bind_* -** sqlite3_client_clear_bindings -** sqlite3_client_column_* -** sqlite3_client_create_collation -** sqlite3_client_create_function -** sqlite3_client_transfer_bindings -** -** The example code in this file is intended for use with memory -** management turned off. So the implementation of these additional -** client interfaces is left as an exercise to the reader. -** -** It may seem surprising to the reader that the list of safe functions -** above does not include things like sqlite3_bind_int() or -** sqlite3_column_int(). But those routines might, in fact, allocate -** or deallocate memory. In the case of sqlite3_bind_int(), if the -** parameter was previously bound to a string that string might need -** to be deallocated before the new integer value is inserted. In -** the case of sqlite3_column_int(), the value of the column might be -** a UTF-16 string which will need to be converted to UTF-8 then into -** an integer. -*/ - -/* Include this to get the definition of SQLITE_THREADSAFE, in the -** case that default values are used. -*/ -#include "sqliteInt.h" - -/* -** Only compile the code in this file on UNIX with a SQLITE_THREADSAFE build -** and only if the SQLITE_SERVER macro is defined. -*/ -#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE) -#if SQLITE_OS_UNIX && SQLITE_THREADSAFE - -/* -** We require only pthreads and the public interface of SQLite. -*/ -#include -#include "sqlite3.h" - -/* -** Messages are passed from client to server and back again as -** instances of the following structure. -*/ -typedef struct SqlMessage SqlMessage; -struct SqlMessage { - int op; /* Opcode for the message */ - sqlite3 *pDb; /* The SQLite connection */ - sqlite3_stmt *pStmt; /* A specific statement */ - int errCode; /* Error code returned */ - const char *zIn; /* Input filename or SQL statement */ - int nByte; /* Size of the zIn parameter for prepare() */ - const char *zOut; /* Tail of the SQL statement */ - SqlMessage *pNext; /* Next message in the queue */ - SqlMessage *pPrev; /* Previous message in the queue */ - pthread_mutex_t clientMutex; /* Hold this mutex to access the message */ - pthread_cond_t clientWakeup; /* Signal to wake up the client */ -}; - -/* -** Legal values for SqlMessage.op -*/ -#define MSG_Open 1 /* sqlite3_open(zIn, &pDb) */ -#define MSG_Prepare 2 /* sqlite3_prepare(pDb, zIn, nByte, &pStmt, &zOut) */ -#define MSG_Step 3 /* sqlite3_step(pStmt) */ -#define MSG_Reset 4 /* sqlite3_reset(pStmt) */ -#define MSG_Finalize 5 /* sqlite3_finalize(pStmt) */ -#define MSG_Close 6 /* sqlite3_close(pDb) */ -#define MSG_Done 7 /* Server has finished with this message */ - - -/* -** State information about the server is stored in a static variable -** named "g" as follows: -*/ -static struct ServerState { - pthread_mutex_t queueMutex; /* Hold this mutex to access the msg queue */ - pthread_mutex_t serverMutex; /* Held by the server while it is running */ - pthread_cond_t serverWakeup; /* Signal this condvar to wake up the server */ - volatile int serverHalt; /* Server halts itself when true */ - SqlMessage *pQueueHead; /* Head of the message queue */ - SqlMessage *pQueueTail; /* Tail of the message queue */ -} g = { - PTHREAD_MUTEX_INITIALIZER, - PTHREAD_MUTEX_INITIALIZER, - PTHREAD_COND_INITIALIZER, -}; - -/* -** Send a message to the server. Block until we get a reply. -** -** The mutex and condition variable in the message are uninitialized -** when this routine is called. This routine takes care of -** initializing them and destroying them when it has finished. -*/ -static void sendToServer(SqlMessage *pMsg){ - /* Initialize the mutex and condition variable on the message - */ - pthread_mutex_init(&pMsg->clientMutex, 0); - pthread_cond_init(&pMsg->clientWakeup, 0); - - /* Add the message to the head of the server's message queue. - */ - pthread_mutex_lock(&g.queueMutex); - pMsg->pNext = g.pQueueHead; - if( g.pQueueHead==0 ){ - g.pQueueTail = pMsg; - }else{ - g.pQueueHead->pPrev = pMsg; - } - pMsg->pPrev = 0; - g.pQueueHead = pMsg; - pthread_mutex_unlock(&g.queueMutex); - - /* Signal the server that the new message has be queued, then - ** block waiting for the server to process the message. - */ - pthread_mutex_lock(&pMsg->clientMutex); - pthread_cond_signal(&g.serverWakeup); - while( pMsg->op!=MSG_Done ){ - pthread_cond_wait(&pMsg->clientWakeup, &pMsg->clientMutex); - } - pthread_mutex_unlock(&pMsg->clientMutex); - - /* Destroy the mutex and condition variable of the message. - */ - pthread_mutex_destroy(&pMsg->clientMutex); - pthread_cond_destroy(&pMsg->clientWakeup); -} - -/* -** The following 6 routines are client-side implementations of the -** core SQLite interfaces: -** -** sqlite3_open -** sqlite3_prepare -** sqlite3_step -** sqlite3_reset -** sqlite3_finalize -** sqlite3_close -** -** Clients should use the following client-side routines instead of -** the core routines above. -** -** sqlite3_client_open -** sqlite3_client_prepare -** sqlite3_client_step -** sqlite3_client_reset -** sqlite3_client_finalize -** sqlite3_client_close -** -** Each of these routines creates a message for the desired operation, -** sends that message to the server, waits for the server to process -** then message and return a response. -*/ -int sqlite3_client_open(const char *zDatabaseName, sqlite3 **ppDb){ - SqlMessage msg; - msg.op = MSG_Open; - msg.zIn = zDatabaseName; - sendToServer(&msg); - *ppDb = msg.pDb; - return msg.errCode; -} -int sqlite3_client_prepare( - sqlite3 *pDb, - const char *zSql, - int nByte, - sqlite3_stmt **ppStmt, - const char **pzTail -){ - SqlMessage msg; - msg.op = MSG_Prepare; - msg.pDb = pDb; - msg.zIn = zSql; - msg.nByte = nByte; - sendToServer(&msg); - *ppStmt = msg.pStmt; - if( pzTail ) *pzTail = msg.zOut; - return msg.errCode; -} -int sqlite3_client_step(sqlite3_stmt *pStmt){ - SqlMessage msg; - msg.op = MSG_Step; - msg.pStmt = pStmt; - sendToServer(&msg); - return msg.errCode; -} -int sqlite3_client_reset(sqlite3_stmt *pStmt){ - SqlMessage msg; - msg.op = MSG_Reset; - msg.pStmt = pStmt; - sendToServer(&msg); - return msg.errCode; -} -int sqlite3_client_finalize(sqlite3_stmt *pStmt){ - SqlMessage msg; - msg.op = MSG_Finalize; - msg.pStmt = pStmt; - sendToServer(&msg); - return msg.errCode; -} -int sqlite3_client_close(sqlite3 *pDb){ - SqlMessage msg; - msg.op = MSG_Close; - msg.pDb = pDb; - sendToServer(&msg); - return msg.errCode; -} - -/* -** This routine implements the server. To start the server, first -** make sure g.serverHalt is false, then create a new detached thread -** on this procedure. See the sqlite3_server_start() routine below -** for an example. This procedure loops until g.serverHalt becomes -** true. -*/ -void *sqlite3_server(void *NotUsed){ - if( pthread_mutex_trylock(&g.serverMutex) ){ - return 0; /* Another server is already running */ - } - sqlite3_enable_shared_cache(1); - while( !g.serverHalt ){ - SqlMessage *pMsg; - - /* Remove the last message from the message queue. - */ - pthread_mutex_lock(&g.queueMutex); - while( g.pQueueTail==0 && g.serverHalt==0 ){ - pthread_cond_wait(&g.serverWakeup, &g.queueMutex); - } - pMsg = g.pQueueTail; - if( pMsg ){ - if( pMsg->pPrev ){ - pMsg->pPrev->pNext = 0; - }else{ - g.pQueueHead = 0; - } - g.pQueueTail = pMsg->pPrev; - } - pthread_mutex_unlock(&g.queueMutex); - if( pMsg==0 ) break; - - /* Process the message just removed - */ - pthread_mutex_lock(&pMsg->clientMutex); - switch( pMsg->op ){ - case MSG_Open: { - pMsg->errCode = sqlite3_open(pMsg->zIn, &pMsg->pDb); - break; - } - case MSG_Prepare: { - pMsg->errCode = sqlite3_prepare(pMsg->pDb, pMsg->zIn, pMsg->nByte, - &pMsg->pStmt, &pMsg->zOut); - break; - } - case MSG_Step: { - pMsg->errCode = sqlite3_step(pMsg->pStmt); - break; - } - case MSG_Reset: { - pMsg->errCode = sqlite3_reset(pMsg->pStmt); - break; - } - case MSG_Finalize: { - pMsg->errCode = sqlite3_finalize(pMsg->pStmt); - break; - } - case MSG_Close: { - pMsg->errCode = sqlite3_close(pMsg->pDb); - break; - } - } - - /* Signal the client that the message has been processed. - */ - pMsg->op = MSG_Done; - pthread_mutex_unlock(&pMsg->clientMutex); - pthread_cond_signal(&pMsg->clientWakeup); - } - pthread_mutex_unlock(&g.serverMutex); - return 0; -} - -/* -** Start a server thread if one is not already running. If there -** is aleady a server thread running, the new thread will quickly -** die and this routine is effectively a no-op. -*/ -void sqlite3_server_start(void){ - pthread_t x; - int rc; - g.serverHalt = 0; - rc = pthread_create(&x, 0, sqlite3_server, 0); - if( rc==0 ){ - pthread_detach(x); - } -} - -/* -** A wrapper around sqlite3_server() that decrements the int variable -** pointed to by the first argument after the sqlite3_server() call -** returns. -*/ -static void *serverWrapper(void *pnDecr){ - void *p = sqlite3_server(0); - (*(int*)pnDecr)--; - return p; -} - -/* -** This function is the similar to sqlite3_server_start(), except that -** the integer pointed to by the first argument is decremented when -** the server thread exits. -*/ -void sqlite3_server_start2(int *pnDecr){ - pthread_t x; - int rc; - g.serverHalt = 0; - rc = pthread_create(&x, 0, serverWrapper, (void*)pnDecr); - if( rc==0 ){ - pthread_detach(x); - } -} - -/* -** If a server thread is running, then stop it. If no server is -** running, this routine is effectively a no-op. -** -** This routine waits until the server has actually stopped before -** returning. -*/ -void sqlite3_server_stop(void){ - g.serverHalt = 1; - pthread_cond_broadcast(&g.serverWakeup); - pthread_mutex_lock(&g.serverMutex); - pthread_mutex_unlock(&g.serverMutex); -} - -#endif /* SQLITE_OS_UNIX && SQLITE_THREADSAFE */ -#endif /* defined(SQLITE_SERVER) */ diff --git a/src/test_tclsh.c b/src/test_tclsh.c index c133deca2..32aee4267 100644 --- a/src/test_tclsh.c +++ b/src/test_tclsh.c @@ -64,7 +64,6 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ extern int Sqlitetest4_Init(Tcl_Interp*); extern int Sqlitetest5_Init(Tcl_Interp*); extern int Sqlitetest6_Init(Tcl_Interp*); - extern int Sqlitetest7_Init(Tcl_Interp*); extern int Sqlitetest8_Init(Tcl_Interp*); extern int Sqlitetest9_Init(Tcl_Interp*); extern int Sqlitetestasync_Init(Tcl_Interp*); @@ -136,7 +135,6 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ Sqlitetest4_Init(interp); Sqlitetest5_Init(interp); Sqlitetest6_Init(interp); - Sqlitetest7_Init(interp); Sqlitetest8_Init(interp); Sqlitetest9_Init(interp); Sqlitetestasync_Init(interp); diff --git a/src/test_thread.c b/src/test_thread.c index de0fdb434..126fd9836 100644 --- a/src/test_thread.c +++ b/src/test_thread.c @@ -384,6 +384,27 @@ static int SQLITE_TCLAPI clock_seconds_proc( return TCL_OK; } +/* +** The [clock_milliseconds] command. This is more or less the same as the +** regular tcl [clock milliseconds]. +*/ +static int SQLITE_TCLAPI clock_milliseconds_proc( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + Tcl_Time now; + Tcl_GetTime(&now); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj( + ((Tcl_WideInt)now.sec * 1000) + (now.usec / 1000) + )); + UNUSED_PARAMETER(clientData); + UNUSED_PARAMETER(objc); + UNUSED_PARAMETER(objv); + return TCL_OK; +} + /************************************************************************* ** This block contains the implementation of the [sqlite3_blocking_step] ** command available to threads created by [sqlthread spawn] commands. It @@ -617,15 +638,26 @@ static int SQLITE_TCLAPI blocking_prepare_v2_proc( ** Register commands with the TCL interpreter. */ int SqlitetestThread_Init(Tcl_Interp *interp){ - Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0); - Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); + struct TclCmd { + int (*xProc)(void*, Tcl_Interp*, int, Tcl_Obj*const*); + const char *zName; + int iCtx; + } aCmd[] = { + { sqlthread_proc, "sqlthread", 0 }, + { clock_seconds_proc, "clock_second", 0 }, + { clock_milliseconds_proc, "clock_milliseconds", 0 }, #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) - Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); - Tcl_CreateObjCommand(interp, - "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); - Tcl_CreateObjCommand(interp, - "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); + { blocking_step_proc, "sqlite3_blocking_step", 0 }, + { blocking_prepare_v2_proc, "sqlite3_blocking_prepare_v2", 1 }, + { blocking_prepare_v2_proc, "sqlite3_nonblocking_prepare_v2", 0 }, #endif + }; + int ii; + + for(ii=0; iizFilename, -1), Tcl_NewStringObj(zLock, -1), 0, 0); } + if( p->mask&TESTVFS_LOCK_MASK && tvfsInjectIoerr(p) ){ + return SQLITE_IOERR_LOCK; + } return sqlite3OsLock(pFd->pReal, eLock); } @@ -500,7 +503,7 @@ static int tvfsUnlock(sqlite3_file *pFile, int eLock){ tvfsExecTcl(p, "xUnlock", Tcl_NewStringObj(pFd->zFilename, -1), Tcl_NewStringObj(zLock, -1), 0, 0); } - if( p->mask&TESTVFS_WRITE_MASK && tvfsInjectIoerr(p) ){ + if( p->mask&TESTVFS_UNLOCK_MASK && tvfsInjectIoerr(p) ){ return SQLITE_IOERR_UNLOCK; } return sqlite3OsUnlock(pFd->pReal, eLock); diff --git a/src/treeview.c b/src/treeview.c index 89a128dff..9f630b156 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -218,6 +218,13 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ sqlite3_str_appendf(&x, " ON"); } + if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc"); + if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated"); + if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized"); + if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine"); + if( pItem->fg.notCte ) sqlite3_str_appendf(&x, " notCte"); + if( pItem->fg.isNestedFrom ) sqlite3_str_appendf(&x, " isNestedFrom"); + sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, inSrc-1); n = 0; @@ -487,7 +494,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewPop(&pView); return; } - if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){ + if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags || pExpr->pAggInfo ){ StrAccum x; sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0); sqlite3_str_appendf(&x, " fg.af=%x.%c", @@ -504,6 +511,9 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ if( ExprHasVVAProperty(pExpr, EP_Immutable) ){ sqlite3_str_appendf(&x, " IMMUTABLE"); } + if( pExpr->pAggInfo!=0 ){ + sqlite3_str_appendf(&x, " agg-column[%d]", pExpr->iAgg); + } sqlite3StrAccumFinish(&x); }else{ zFlgs[0] = 0; diff --git a/src/update.c b/src/update.c index 809b30653..a4e57d1d4 100644 --- a/src/update.c +++ b/src/update.c @@ -263,7 +263,8 @@ static void updateFromSelect( } } pSelect = sqlite3SelectNew(pParse, pList, - pSrc, pWhere2, pGrp, 0, pOrderBy2, SF_UFSrcCheck|SF_IncludeHidden, pLimit2 + pSrc, pWhere2, pGrp, 0, pOrderBy2, + SF_UFSrcCheck|SF_IncludeHidden|SF_UpdateFrom, pLimit2 ); if( pSelect ) pSelect->selFlags |= SF_OrderByReqd; sqlite3SelectDestInit(&dest, eDest, iEph); diff --git a/src/util.c b/src/util.c index 32e9c2778..632d317e3 100644 --- a/src/util.c +++ b/src/util.c @@ -175,6 +175,26 @@ void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *zFormat, ...){ } } +/* +** Check for interrupts and invoke progress callback. +*/ +void sqlite3ProgressCheck(Parse *p){ + sqlite3 *db = p->db; + if( AtomicLoad(&db->u1.isInterrupted) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress && (++p->nProgressSteps)>=db->nProgressOps ){ + if( db->xProgress(db->pProgressArg) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } + p->nProgressSteps = 0; + } +#endif +} + /* ** Add an error message to pParse->zErrMsg and increment pParse->nErr. ** @@ -632,11 +652,14 @@ int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ #endif /* -** Render an signed 64-bit integer as text. Store the result in zOut[]. +** Render an signed 64-bit integer as text. Store the result in zOut[] and +** return the length of the string that was stored, in bytes. The value +** returned does not include the zero terminator at the end of the output +** string. ** ** The caller must ensure that zOut[] is at least 21 bytes in size. */ -void sqlite3Int64ToText(i64 v, char *zOut){ +int sqlite3Int64ToText(i64 v, char *zOut){ int i; u64 x; char zTemp[22]; @@ -653,6 +676,7 @@ void sqlite3Int64ToText(i64 v, char *zOut){ }while( x ); if( v<0 ) zTemp[i--] = '-'; memcpy(zOut, &zTemp[i+1], sizeof(zTemp)-1-i); + return sizeof(zTemp)-2-i; } /* @@ -1713,3 +1737,12 @@ int sqlite3VListNameToNum(VList *pIn, const char *zName, int nName){ }while( i=SQLITE_AFF_NUMERIC ){ assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL - || affinity==SQLITE_AFF_NUMERIC ); + || affinity==SQLITE_AFF_NUMERIC || affinity==SQLITE_AFF_FLEXNUM ); if( (pRec->flags & MEM_Int)==0 ){ /*OPTIMIZATION-IF-FALSE*/ - if( (pRec->flags & MEM_Real)==0 ){ + if( (pRec->flags & (MEM_Real|MEM_IntReal))==0 ){ if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); - }else{ + }else if( affinity<=SQLITE_AFF_REAL ){ sqlite3VdbeIntegerAffinity(pRec); } } @@ -617,17 +624,6 @@ void sqlite3VdbeRegisterDump(Vdbe *v){ # define REGISTER_TRACE(R,M) #endif - -#ifdef VDBE_PROFILE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -#include "hwtime.h" - -#endif - #ifndef NDEBUG /* ** This function is only called from within an assert() expression. It @@ -717,11 +713,10 @@ int sqlite3VdbeExec( ){ Op *aOp = p->aOp; /* Copy of p->aOp */ Op *pOp = aOp; /* Current operation */ -#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) - Op *pOrigOp; /* Value of pOp at the top of the loop */ -#endif #ifdef SQLITE_DEBUG + Op *pOrigOp; /* Value of pOp at the top of the loop */ int nExtraDelete = 0; /* Verifies FORDELETE and AUXDELETE flags */ + u8 iCompareIsInit = 0; /* iCompare is initialized */ #endif int rc = SQLITE_OK; /* Value to return */ sqlite3 *db = p->db; /* The database */ @@ -737,13 +732,15 @@ int sqlite3VdbeExec( Mem *pIn2 = 0; /* 2nd input operand */ Mem *pIn3 = 0; /* 3rd input operand */ Mem *pOut = 0; /* Output operand */ -#ifdef VDBE_PROFILE - u64 start; /* CPU clock count at start of opcode */ +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + u64 *pnCycle = 0; #endif /*** INSERT STACK UNION HERE ***/ assert( p->eVdbeState==VDBE_RUN_STATE ); /* sqlite3_step() verifies this */ - sqlite3VdbeEnter(p); + if( DbMaskNonZero(p->lockMask) ){ + sqlite3VdbeEnter(p); + } #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( db->xProgress ){ u32 iPrior = p->aCounter[SQLITE_STMTSTATUS_VM_STEP]; @@ -764,7 +761,6 @@ int sqlite3VdbeExec( assert( p->bIsReader || p->readOnly!=0 ); p->iCurrentTime = 0; assert( p->explain==0 ); - p->pResultSet = 0; db->busyHandler.nBusy = 0; if( AtomicLoad(&db->u1.isInterrupted) ) goto abort_due_to_interrupt; sqlite3VdbeIOTraceSql(p); @@ -801,12 +797,14 @@ int sqlite3VdbeExec( assert( rc==SQLITE_OK ); assert( pOp>=aOp && pOp<&aOp[p->nOp]); -#ifdef VDBE_PROFILE - start = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); -#endif nVmStep++; -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - if( p->anExec ) p->anExec[(int)(pOp-aOp)]++; +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec++; + pnCycle = &pOp->nCycle; +# ifdef VDBE_PROFILE + if( sqlite3NProfileCnt==0 ) +# endif + *pnCycle -= sqlite3Hwtime(); #endif /* Only allow tracing if SQLITE_DEBUG is defined. @@ -868,7 +866,7 @@ int sqlite3VdbeExec( } } #endif -#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) +#ifdef SQLITE_DEBUG pOrigOp = pOp; #endif @@ -1152,6 +1150,12 @@ case OP_Halt: { #ifdef SQLITE_DEBUG if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); } #endif + + /* A deliberately coded "OP_Halt SQLITE_INTERNAL * * * *" opcode indicates + ** something is wrong with the code generator. Raise an assertion in order + ** to bring this to the attention of fuzzers and other testing tools. */ + assert( pOp->p1!=SQLITE_INTERNAL ); + if( p->pFrame && pOp->p1==SQLITE_OK ){ /* Halt the sub-program. Return control to the parent frame. */ pFrame = p->pFrame; @@ -1593,10 +1597,10 @@ case OP_ResultRow: { assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); p->cacheCtr = (p->cacheCtr + 2)|1; - p->pResultSet = &aMem[pOp->p1]; + p->pResultRow = &aMem[pOp->p1]; #ifdef SQLITE_DEBUG { - Mem *pMem = p->pResultSet; + Mem *pMem = p->pResultRow; int i; for(i=0; ip2; i++){ assert( memIsValid(&pMem[i]) ); @@ -2126,7 +2130,6 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ flags1 = pIn1->flags; flags3 = pIn3->flags; if( (flags1 & flags3 & MEM_Int)!=0 ){ - assert( (pOp->p5 & SQLITE_AFF_MASK)!=SQLITE_AFF_TEXT || CORRUPT_DB ); /* Common case of comparison of two integers */ if( pIn3->u.i > pIn1->u.i ){ if( sqlite3aGTb[pOp->opcode] ){ @@ -2134,18 +2137,21 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ goto jump_to_p2; } iCompare = +1; + VVA_ONLY( iCompareIsInit = 1; ) }else if( pIn3->u.i < pIn1->u.i ){ if( sqlite3aLTb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } iCompare = -1; + VVA_ONLY( iCompareIsInit = 1; ) }else{ if( sqlite3aEQb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } iCompare = 0; + VVA_ONLY( iCompareIsInit = 1; ) } VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3); break; @@ -2177,6 +2183,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ goto jump_to_p2; } iCompare = 1; /* Operands are not equal */ + VVA_ONLY( iCompareIsInit = 1; ) break; } }else{ @@ -2187,14 +2194,14 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ if( (flags1 | flags3)&MEM_Str ){ if( (flags1 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn1,0); - testcase( flags3==pIn3->flags ); + assert( flags3==pIn3->flags || CORRUPT_DB ); flags3 = pIn3->flags; } if( (flags3 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn3,0); } } - }else if( affinity==SQLITE_AFF_TEXT ){ + }else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){ if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn1->flags & MEM_Int ); testcase( pIn1->flags & MEM_Real ); @@ -2202,7 +2209,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); - if( pIn1==pIn3 ) flags3 = flags1 | MEM_Str; + if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; } if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn3->flags & MEM_Int ); @@ -2233,6 +2240,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ res2 = sqlite3aGTb[pOp->opcode]; } iCompare = res; + VVA_ONLY( iCompareIsInit = 1; ) /* Undo any changes made by applyAffinity() to the input registers. */ assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) ); @@ -2271,6 +2279,7 @@ case OP_ElseEq: { /* same as TK_ESCAPE, jump */ break; } #endif /* SQLITE_DEBUG */ + assert( iCompareIsInit ); VdbeBranchTaken(iCompare==0, 2); if( iCompare==0 ) goto jump_to_p2; break; @@ -2365,6 +2374,7 @@ case OP_Compare: { pColl = pKeyInfo->aColl[i]; bRev = (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC); iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl); + VVA_ONLY( iCompareIsInit = 1; ) if( iCompare ){ if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) && ((aMem[p1+idx].flags & MEM_Null) || (aMem[p2+idx].flags & MEM_Null)) @@ -2389,6 +2399,7 @@ case OP_Compare: { */ case OP_Jump: { /* jump */ assert( pOp>aOp && pOp[-1].opcode==OP_Compare ); + assert( iCompareIsInit ); if( iCompare<0 ){ VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1]; }else if( iCompare==0 ){ @@ -2788,7 +2799,7 @@ case OP_Offset: { /* out3 */ ** typeof() function or the IS NULL or IS NOT NULL operators or the ** equivalent. In this case, all content loading can be omitted. */ -case OP_Column: { +case OP_Column: { /* ncycle */ u32 p2; /* column number to retrieve */ VdbeCursor *pC; /* The VDBE cursor */ BtCursor *pCrsr; /* The B-Tree cursor corresponding to pC */ @@ -3137,7 +3148,7 @@ case OP_TypeCheck: { } case COLTYPE_REAL: { testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_Real ); - testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_IntReal ); + assert( (pIn1->flags & MEM_IntReal)==0 ); if( pIn1->flags & MEM_Int ){ /* When applying REAL affinity, if the result is still an MEM_Int ** that will fit in 6 bytes, then change the type to MEM_IntReal @@ -4140,7 +4151,7 @@ case OP_SetCookie: { ** ** See also: OP_OpenRead, OP_ReopenIdx */ -case OP_ReopenIdx: { +case OP_ReopenIdx: { /* ncycle */ int nField; KeyInfo *pKeyInfo; u32 p2; @@ -4161,7 +4172,7 @@ case OP_ReopenIdx: { } /* If the cursor is not currently open or is open on a different ** index, then fall through into OP_OpenRead to force a reopen */ -case OP_OpenRead: +case OP_OpenRead: /* ncycle */ case OP_OpenWrite: assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); @@ -4255,7 +4266,7 @@ case OP_OpenWrite: ** ** Duplicate ephemeral cursors are used for self-joins of materialized views. */ -case OP_OpenDup: { +case OP_OpenDup: { /* ncycle */ VdbeCursor *pOrig; /* The original cursor to be duplicated */ VdbeCursor *pCx; /* The new cursor */ @@ -4317,8 +4328,8 @@ case OP_OpenDup: { ** by this opcode will be used for automatically created transient ** indices in joins. */ -case OP_OpenAutoindex: -case OP_OpenEphemeral: { +case OP_OpenAutoindex: /* ncycle */ +case OP_OpenEphemeral: { /* ncycle */ VdbeCursor *pCx; KeyInfo *pKeyInfo; @@ -4476,7 +4487,7 @@ case OP_OpenPseudo: { ** Close a cursor previously opened as P1. If P1 is not ** currently open, this instruction is a no-op. */ -case OP_Close: { +case OP_Close: { /* ncycle */ assert( pOp->p1>=0 && pOp->p1nCursor ); sqlite3VdbeFreeCursor(p, p->apCsr[pOp->p1]); p->apCsr[pOp->p1] = 0; @@ -4593,10 +4604,10 @@ case OP_ColumnsUsed: { ** ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ -case OP_SeekLT: /* jump, in3, group */ -case OP_SeekLE: /* jump, in3, group */ -case OP_SeekGE: /* jump, in3, group */ -case OP_SeekGT: { /* jump, in3, group */ +case OP_SeekLT: /* jump, in3, group, ncycle */ +case OP_SeekLE: /* jump, in3, group, ncycle */ +case OP_SeekGE: /* jump, in3, group, ncycle */ +case OP_SeekGT: { /* jump, in3, group, ncycle */ int res; /* Comparison result */ int oc; /* Opcode */ VdbeCursor *pC; /* The cursor to seek */ @@ -4862,7 +4873,7 @@ case OP_SeekGT: { /* jump, in3, group */ ** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0. ** */ -case OP_SeekScan: { +case OP_SeekScan: { /* ncycle */ VdbeCursor *pC; int res; int nStep; @@ -4984,7 +4995,7 @@ case OP_SeekScan: { ** ** P1 must be a valid b-tree cursor. */ -case OP_SeekHit: { +case OP_SeekHit: { /* ncycle */ VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; @@ -5116,7 +5127,7 @@ case OP_IfNotOpen: { /* jump */ ** ** See also: NotFound, Found, NotExists */ -case OP_IfNoHope: { /* jump, in3 */ +case OP_IfNoHope: { /* jump, in3, ncycle */ VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; @@ -5130,9 +5141,9 @@ case OP_IfNoHope: { /* jump, in3 */ /* Fall through into OP_NotFound */ /* no break */ deliberate_fall_through } -case OP_NoConflict: /* jump, in3 */ -case OP_NotFound: /* jump, in3 */ -case OP_Found: { /* jump, in3 */ +case OP_NoConflict: /* jump, in3, ncycle */ +case OP_NotFound: /* jump, in3, ncycle */ +case OP_Found: { /* jump, in3, ncycle */ int alreadyExists; int ii; VdbeCursor *pC; @@ -5262,7 +5273,7 @@ case OP_Found: { /* jump, in3 */ ** ** See also: Found, NotFound, NoConflict, SeekRowid */ -case OP_SeekRowid: { /* jump, in3 */ +case OP_SeekRowid: { /* jump, in3, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; @@ -5287,7 +5298,7 @@ case OP_SeekRowid: { /* jump, in3 */ } /* Fall through into OP_NotExists */ /* no break */ deliberate_fall_through -case OP_NotExists: /* jump, in3 */ +case OP_NotExists: /* jump, in3, ncycle */ pIn3 = &aMem[pOp->p3]; assert( (pIn3->flags & MEM_Int)!=0 || pOp->opcode==OP_SeekRowid ); assert( pOp->p1>=0 && pOp->p1nCursor ); @@ -5567,8 +5578,11 @@ case OP_Insert: { if( pOp->p5 & OPFLAG_ISNOOP ) break; #endif - if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; - if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; + assert( (pOp->p5 & OPFLAG_LASTROWID)==0 || (pOp->p5 & OPFLAG_NCHANGE)!=0 ); + if( pOp->p5 & OPFLAG_NCHANGE ){ + p->nChange++; + if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; + } assert( (pData->flags & (MEM_Blob|MEM_Str))!=0 || pData->n==0 ); x.pData = pData->z; x.nData = pData->n; @@ -5579,6 +5593,7 @@ case OP_Insert: { x.nZero = 0; } x.pKey = 0; + assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), seekResult @@ -5910,7 +5925,7 @@ case OP_RowData: { ** be a separate OP_VRowid opcode for use with virtual tables, but this ** one opcode now works for both table types. */ -case OP_Rowid: { /* out2 */ +case OP_Rowid: { /* out2, ncycle */ VdbeCursor *pC; i64 v; sqlite3_vtab *pVtab; @@ -6009,8 +6024,8 @@ case OP_NullRow: { ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. */ -case OP_SeekEnd: -case OP_Last: { /* jump */ +case OP_SeekEnd: /* ncycle */ +case OP_Last: { /* jump, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; @@ -6111,17 +6126,22 @@ case OP_Sort: { /* jump */ ** If the table or index is not empty, fall through to the following ** instruction. ** +** If P2 is zero, that is an assertion that the P1 table is never +** empty and hence the jump will never be taken. +** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. */ -case OP_Rewind: { /* jump */ +case OP_Rewind: { /* jump, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p5==0 ); + assert( pOp->p2>=0 && pOp->p2nOp ); + pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( isSorter(pC)==(pOp->opcode==OP_SorterSort) ); @@ -6141,9 +6161,10 @@ case OP_Rewind: { /* jump */ } if( rc ) goto abort_due_to_error; pC->nullRow = (u8)res; - assert( pOp->p2>0 && pOp->p2nOp ); - VdbeBranchTaken(res!=0,2); - if( res ) goto jump_to_p2; + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + } break; } @@ -6209,7 +6230,7 @@ case OP_SorterNext: { /* jump */ rc = sqlite3VdbeSorterNext(db, pC); goto next_tail; -case OP_Prev: /* jump */ +case OP_Prev: /* jump, ncycle */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p5==0 || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP @@ -6224,7 +6245,7 @@ case OP_Prev: /* jump */ rc = sqlite3BtreePrevious(pC->uc.pCursor, pOp->p3); goto next_tail; -case OP_Next: /* jump */ +case OP_Next: /* jump, ncycle */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p5==0 || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP @@ -6416,8 +6437,8 @@ case OP_IdxDelete: { ** ** See also: Rowid, MakeRecord. */ -case OP_DeferredSeek: -case OP_IdxRowid: { /* out2 */ +case OP_DeferredSeek: /* ncycle */ +case OP_IdxRowid: { /* out2, ncycle */ VdbeCursor *pC; /* The P1 index cursor */ VdbeCursor *pTabCur; /* The P2 table cursor (OP_DeferredSeek only) */ i64 rowid; /* Rowid that P1 current points to */ @@ -6479,8 +6500,8 @@ case OP_IdxRowid: { /* out2 */ ** seek operation now, without further delay. If the cursor seek has ** already occurred, this instruction is a no-op. */ -case OP_FinishSeek: { - VdbeCursor *pC; /* The P1 index cursor */ +case OP_FinishSeek: { /* ncycle */ + VdbeCursor *pC; /* The P1 index cursor */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; @@ -6535,10 +6556,10 @@ case OP_FinishSeek: { ** If the P1 index entry is less than or equal to the key value then jump ** to P2. Otherwise fall through to the next instruction. */ -case OP_IdxLE: /* jump */ -case OP_IdxGT: /* jump */ -case OP_IdxLT: /* jump */ -case OP_IdxGE: { /* jump */ +case OP_IdxLE: /* jump, ncycle */ +case OP_IdxGT: /* jump, ncycle */ +case OP_IdxLT: /* jump, ncycle */ +case OP_IdxGE: { /* jump, ncycle */ VdbeCursor *pC; int res; UnpackedRecord r; @@ -6949,13 +6970,14 @@ case OP_IntegrityCk: { pIn1 = &aMem[pOp->p1]; assert( pOp->p5nDb ); assert( DbMaskTest(p->btreeMask, pOp->p5) ); - z = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], nRoot, - (int)pnErr->u.i+1, &nErr); + rc = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], nRoot, + (int)pnErr->u.i+1, &nErr, &z); sqlite3VdbeMemSetNull(pIn1); if( nErr==0 ){ assert( z==0 ); - }else if( z==0 ){ - goto no_mem; + }else if( rc ){ + sqlite3_free(z); + goto abort_due_to_error; }else{ pnErr->u.i -= nErr-1; sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free); @@ -7159,9 +7181,6 @@ case OP_Program: { /* jump */ pFrame->aOp = p->aOp; pFrame->nOp = p->nOp; pFrame->token = pProgram->token; -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - pFrame->anExec = p->anExec; -#endif #ifdef SQLITE_DEBUG pFrame->iFrameMagic = SQLITE_FRAME_MAGIC; #endif @@ -7198,9 +7217,6 @@ case OP_Program: { /* jump */ memset(pFrame->aOnce, 0, (pProgram->nOp + 7)/8); p->aOp = aOp = pProgram->aOp; p->nOp = pProgram->nOp; -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - p->anExec = 0; -#endif #ifdef SQLITE_DEBUG /* Verify that second and subsequent executions of the same trigger do not ** try to reuse register values from the first use. */ @@ -7957,7 +7973,7 @@ case OP_VDestroy: { ** P1 is a cursor number. This opcode opens a cursor to the virtual ** table and stores that cursor in P1. */ -case OP_VOpen: { +case OP_VOpen: { /* ncycle */ VdbeCursor *pCur; sqlite3_vtab_cursor *pVCur; sqlite3_vtab *pVtab; @@ -8004,7 +8020,7 @@ case OP_VOpen: { ** cursor. Register P3 is used to hold the values returned by ** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). */ -case OP_VInitIn: { /* out2 */ +case OP_VInitIn: { /* out2, ncycle */ VdbeCursor *pC; /* The cursor containing the RHS values */ ValueList *pRhs; /* New ValueList object to put in reg[P2] */ @@ -8015,7 +8031,7 @@ case OP_VInitIn: { /* out2 */ pRhs->pOut = &aMem[pOp->p3]; pOut = out2Prerelease(p, pOp); pOut->flags = MEM_Null; - sqlite3VdbeMemSetPointer(pOut, pRhs, "ValueList", sqlite3_free); + sqlite3VdbeMemSetPointer(pOut, pRhs, "ValueList", sqlite3VdbeValueListFree); break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -8041,7 +8057,7 @@ case OP_VInitIn: { /* out2 */ ** ** A jump is made to P2 if the result set after filtering would be empty. */ -case OP_VFilter: { /* jump */ +case OP_VFilter: { /* jump, ncycle */ int nArg; int iQuery; const sqlite3_module *pModule; @@ -8101,7 +8117,7 @@ case OP_VFilter: { /* jump */ ** bits (OPFLAG_LENGTHARG or OPFLAG_TYPEOFARG) but those bits are ** unused by OP_VColumn. */ -case OP_VColumn: { +case OP_VColumn: { /* ncycle */ sqlite3_vtab *pVtab; const sqlite3_module *pModule; Mem *pDest; @@ -8153,7 +8169,7 @@ case OP_VColumn: { ** jump to instruction P2. Or, if the virtual table has reached ** the end of its result set, then fall through to the next instruction. */ -case OP_VNext: { /* jump */ +case OP_VNext: { /* jump, ncycle */ sqlite3_vtab *pVtab; const sqlite3_module *pModule; int res; @@ -8736,12 +8752,12 @@ default: { /* This is really OP_Noop, OP_Explain */ *****************************************************************************/ } -#ifdef VDBE_PROFILE - { - u64 endTime = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); - if( endTime>start ) pOrigOp->cycles += endTime - start; - pOrigOp->cnt++; - } +#if defined(VDBE_PROFILE) + *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); + pnCycle = 0; +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + *pnCycle += sqlite3Hwtime(); + pnCycle = 0; #endif /* The following code adds nothing to the actual functionality @@ -8817,6 +8833,18 @@ default: { /* This is really OP_Noop, OP_Explain */ ** release the mutexes on btrees that were acquired at the ** top. */ vdbe_return: +#if defined(VDBE_PROFILE) + if( pnCycle ){ + *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); + pnCycle = 0; + } +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( pnCycle ){ + *pnCycle += sqlite3Hwtime(); + pnCycle = 0; + } +#endif + #ifndef SQLITE_OMIT_PROGRESS_CALLBACK while( nVmStep>=nProgressLimit && db->xProgress!=0 ){ nProgressLimit += db->nProgressOps; @@ -8828,7 +8856,9 @@ default: { /* This is really OP_Noop, OP_Explain */ } #endif p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; - sqlite3VdbeLeave(p); + if( DbMaskNonZero(p->lockMask) ){ + sqlite3VdbeLeave(p); + } assert( rc!=SQLITE_OK || nExtraDelete==0 || sqlite3_strlike("DELETE%",p->zSql,0)!=0 ); diff --git a/src/vdbe.h b/src/vdbe.h index a244468b5..3caf1f787 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -67,14 +67,14 @@ struct VdbeOp { #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ #endif -#ifdef VDBE_PROFILE - u32 cnt; /* Number of times this instruction was executed */ - u64 cycles; /* Total time spent executing this instruction */ -#endif #ifdef SQLITE_VDBE_COVERAGE u32 iSrcLine; /* Source-code line that generated this opcode ** with flags in the upper 8 bits */ #endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + u64 nExec; + u64 nCycle; +#endif }; typedef struct VdbeOp VdbeOp; @@ -205,14 +205,20 @@ void sqlite3VdbeEndCoroutine(Vdbe*,int); #endif VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); #ifndef SQLITE_OMIT_EXPLAIN - void sqlite3VdbeExplain(Parse*,u8,const char*,...); + int sqlite3VdbeExplain(Parse*,u8,const char*,...); void sqlite3VdbeExplainPop(Parse*); int sqlite3VdbeExplainParent(Parse*); # define ExplainQueryPlan(P) sqlite3VdbeExplain P +# ifdef SQLITE_ENABLE_STMT_SCANSTATUS +# define ExplainQueryPlan2(V,P) (V = sqlite3VdbeExplain P) +# else +# define ExplainQueryPlan2(V,P) ExplainQueryPlan(P) +# endif # define ExplainQueryPlanPop(P) sqlite3VdbeExplainPop(P) # define ExplainQueryPlanParent(P) sqlite3VdbeExplainParent(P) #else # define ExplainQueryPlan(P) +# define ExplainQueryPlan2(V,P) # define ExplainQueryPlanPop(P) # define ExplainQueryPlanParent(P) 0 # define sqlite3ExplainBreakpoint(A,B) /*no-op*/ @@ -385,8 +391,12 @@ int sqlite3VdbeBytecodeVtabInit(sqlite3*); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*); +void sqlite3VdbeScanStatusRange(Vdbe*, int, int, int); +void sqlite3VdbeScanStatusCounters(Vdbe*, int, int, int); #else -# define sqlite3VdbeScanStatus(a,b,c,d,e) +# define sqlite3VdbeScanStatus(a,b,c,d,e,f) +# define sqlite3VdbeScanStatusRange(a,b,c,d) +# define sqlite3VdbeScanStatusCounters(a,b,c,d) #endif #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 0c6301c8f..b901a0180 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -171,7 +171,6 @@ struct VdbeFrame { Vdbe *v; /* VM this frame belongs to */ VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ Op *aOp; /* Program instructions for parent frame */ - i64 *anExec; /* Event counters from parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ u8 *aOnce; /* Bitmask used by OP_Once */ @@ -387,10 +386,19 @@ typedef unsigned bft; /* Bit Field Type */ /* The ScanStatus object holds a single value for the ** sqlite3_stmt_scanstatus() interface. +** +** aAddrRange[]: +** This array is used by ScanStatus elements associated with EQP +** notes that make an SQLITE_SCANSTAT_NCYCLE value available. It is +** an array of up to 3 ranges of VM addresses for which the Vdbe.anCycle[] +** values should be summed to calculate the NCYCLE value. Each pair of +** integer addresses is a start and end address (both inclusive) for a range +** instructions. A start value of 0 indicates an empty range. */ typedef struct ScanStatus ScanStatus; struct ScanStatus { int addrExplain; /* OP_Explain for loop */ + int aAddrRange[6]; int addrLoop; /* Address of "loops" counter */ int addrVisit; /* Address of "rows visited" counter */ int iSelectID; /* The "Select-ID" for this loop */ @@ -446,7 +454,7 @@ struct Vdbe { int nOp; /* Number of instructions in the program */ int nOpAlloc; /* Slots allocated for aOp[] */ Mem *aColName; /* Column names to return */ - Mem *pResultSet; /* Pointer to an array of results */ + Mem *pResultRow; /* Current output row */ char *zErrMsg; /* Error message written here */ VList *pVList; /* Name of variables */ #ifndef SQLITE_OMIT_TRACE @@ -483,7 +491,6 @@ struct Vdbe { SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS - i64 *anExec; /* Number of times each op has been executed */ int nScan; /* Entries in aScan[] */ ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */ #endif @@ -650,6 +657,8 @@ int sqlite3VdbeSorterRewind(const VdbeCursor *, int *); int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *); int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); +void sqlite3VdbeValueListFree(void*); + #ifdef SQLITE_DEBUG void sqlite3VdbeIncrWriteCounter(Vdbe*, VdbeCursor*); void sqlite3VdbeAssertAbortable(Vdbe*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 04295342b..476b6a2ad 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -15,6 +15,7 @@ */ #include "sqliteInt.h" #include "vdbeInt.h" +#include "opcodes.h" #ifndef SQLITE_OMIT_DEPRECATED /* @@ -505,7 +506,10 @@ void sqlite3_result_text64( ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); - if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + if( enc!=SQLITE_UTF8 ){ + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + n &= ~(u64)1; + } if( n>0x7fffffff ){ (void)invokeValueDestructor(z, xDel, pCtx); }else{ @@ -520,7 +524,7 @@ void sqlite3_result_text16( void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16NATIVE, xDel); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16NATIVE, xDel); } void sqlite3_result_text16be( sqlite3_context *pCtx, @@ -529,7 +533,7 @@ void sqlite3_result_text16be( void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16BE, xDel); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16BE, xDel); } void sqlite3_result_text16le( sqlite3_context *pCtx, @@ -538,7 +542,7 @@ void sqlite3_result_text16le( void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16LE, xDel); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16LE, xDel); } #endif /* SQLITE_OMIT_UTF16 */ void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ @@ -749,7 +753,7 @@ static int sqlite3Step(Vdbe *p){ /* If the statement completed successfully, invoke the profile callback */ checkProfileCallback(db, p); #endif - + p->pResultRow = 0; if( rc==SQLITE_DONE && db->autoCommit ){ assert( p->rc==SQLITE_OK ); p->rc = doWalCallbacks(db); @@ -878,6 +882,17 @@ int sqlite3_vtab_nochange(sqlite3_context *p){ return sqlite3_value_nochange(p->pOut); } +/* +** The destructor function for a ValueList object. This needs to be +** a separate function, unknowable to the application, to ensure that +** calls to sqlite3_vtab_in_first()/sqlite3_vtab_in_next() that are not +** preceeded by activation of IN processing via sqlite3_vtab_int() do not +** try to access a fake ValueList object inserted by a hostile extension. +*/ +void sqlite3VdbeValueListFree(void *pToDelete){ + sqlite3_free(pToDelete); +} + /* ** Implementation of sqlite3_vtab_in_first() (if bNext==0) and ** sqlite3_vtab_in_next() (if bNext!=0). @@ -892,8 +907,15 @@ static int valueFromValueList( *ppOut = 0; if( pVal==0 ) return SQLITE_MISUSE; - pRhs = (ValueList*)sqlite3_value_pointer(pVal, "ValueList"); - if( pRhs==0 ) return SQLITE_MISUSE; + if( (pVal->flags & MEM_Dyn)==0 || pVal->xDel!=sqlite3VdbeValueListFree ){ + return SQLITE_ERROR; + }else{ + assert( (pVal->flags&(MEM_TypeMask|MEM_Term|MEM_Subtype)) == + (MEM_Null|MEM_Term|MEM_Subtype) ); + assert( pVal->eSubtype=='p' ); + assert( pVal->u.zPType!=0 && strcmp(pVal->u.zPType,"ValueList")==0 ); + pRhs = (ValueList*)pVal->z; + } if( bNext ){ rc = sqlite3BtreeNext(pRhs->pCsr, 0); }else{ @@ -1113,7 +1135,7 @@ int sqlite3_column_count(sqlite3_stmt *pStmt){ */ int sqlite3_data_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; - if( pVm==0 || pVm->pResultSet==0 ) return 0; + if( pVm==0 || pVm->pResultRow==0 ) return 0; return pVm->nResColumn; } @@ -1168,8 +1190,8 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ if( pVm==0 ) return (Mem*)columnNullValue(); assert( pVm->db ); sqlite3_mutex_enter(pVm->db->mutex); - if( pVm->pResultSet!=0 && inResColumn && i>=0 ){ - pOut = &pVm->pResultSet[i]; + if( pVm->pResultRow!=0 && inResColumn && i>=0 ){ + pOut = &pVm->pResultRow[i]; }else{ sqlite3Error(pVm->db, SQLITE_RANGE); pOut = (Mem*)columnNullValue(); @@ -1603,7 +1625,10 @@ int sqlite3_bind_text64( unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); - if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + if( enc!=SQLITE_UTF8 ){ + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + nData &= ~(u16)1; + } return bindText(pStmt, i, zData, nData, xDel, enc); } #ifndef SQLITE_OMIT_UTF16 @@ -1611,10 +1636,10 @@ int sqlite3_bind_text16( sqlite3_stmt *pStmt, int i, const void *zData, - int nData, + int n, void (*xDel)(void*) ){ - return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF16NATIVE); + return bindText(pStmt, i, zData, n & ~(u64)1, xDel, SQLITE_UTF16NATIVE); } #endif /* SQLITE_OMIT_UTF16 */ int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ @@ -2105,23 +2130,60 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ /* ** Return status data for a single loop within query pStmt. */ -int sqlite3_stmt_scanstatus( +int sqlite3_stmt_scanstatus_v2( sqlite3_stmt *pStmt, /* Prepared statement being queried */ - int idx, /* Index of loop to report on */ + int iScan, /* Index of loop to report on */ int iScanStatusOp, /* Which metric to return */ + int flags, void *pOut /* OUT: Write the answer here */ ){ Vdbe *p = (Vdbe*)pStmt; ScanStatus *pScan; - if( idx<0 || idx>=p->nScan ) return 1; - pScan = &p->aScan[idx]; + int idx; + + if( iScan<0 ){ + int ii; + if( iScanStatusOp==SQLITE_SCANSTAT_NCYCLE ){ + i64 res = 0; + for(ii=0; iinOp; ii++){ + res += p->aOp[ii].nCycle; + } + *(i64*)pOut = res; + return 0; + } + return 1; + } + if( flags & SQLITE_SCANSTAT_COMPLEX ){ + idx = iScan; + pScan = &p->aScan[idx]; + }else{ + /* If the COMPLEX flag is clear, then this function must ignore any + ** ScanStatus structures with ScanStatus.addrLoop set to 0. */ + for(idx=0; idxnScan; idx++){ + pScan = &p->aScan[idx]; + if( pScan->zName ){ + iScan--; + if( iScan<0 ) break; + } + } + } + if( idx>=p->nScan ) return 1; + switch( iScanStatusOp ){ case SQLITE_SCANSTAT_NLOOP: { - *(sqlite3_int64*)pOut = p->anExec[pScan->addrLoop]; + if( pScan->addrLoop>0 ){ + *(sqlite3_int64*)pOut = p->aOp[pScan->addrLoop].nExec; + }else{ + *(sqlite3_int64*)pOut = -1; + } break; } case SQLITE_SCANSTAT_NVISIT: { - *(sqlite3_int64*)pOut = p->anExec[pScan->addrVisit]; + if( pScan->addrVisit>0 ){ + *(sqlite3_int64*)pOut = p->aOp[pScan->addrVisit].nExec; + }else{ + *(sqlite3_int64*)pOut = -1; + } break; } case SQLITE_SCANSTAT_EST: { @@ -2154,6 +2216,45 @@ int sqlite3_stmt_scanstatus( } break; } + case SQLITE_SCANSTAT_PARENTID: { + if( pScan->addrExplain ){ + *(int*)pOut = p->aOp[ pScan->addrExplain ].p2; + }else{ + *(int*)pOut = -1; + } + break; + } + case SQLITE_SCANSTAT_NCYCLE: { + i64 res = 0; + if( pScan->aAddrRange[0]==0 ){ + res = -1; + }else{ + int ii; + for(ii=0; iiaAddrRange); ii+=2){ + int iIns = pScan->aAddrRange[ii]; + int iEnd = pScan->aAddrRange[ii+1]; + if( iIns==0 ) break; + if( iIns>0 ){ + while( iIns<=iEnd ){ + res += p->aOp[iIns].nCycle; + iIns++; + } + }else{ + int iOp; + for(iOp=0; iOpnOp; iOp++){ + Op *pOp = &p->aOp[iOp]; + if( pOp->p1!=iEnd ) continue; + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_NCYCLE)==0 ){ + continue; + } + res += p->aOp[iOp].nCycle; + } + } + } + } + *(i64*)pOut = res; + break; + } default: { return 1; } @@ -2161,11 +2262,28 @@ int sqlite3_stmt_scanstatus( return 0; } +/* +** Return status data for a single loop within query pStmt. +*/ +int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement being queried */ + int iScan, /* Index of loop to report on */ + int iScanStatusOp, /* Which metric to return */ + void *pOut /* OUT: Write the answer here */ +){ + return sqlite3_stmt_scanstatus_v2(pStmt, iScan, iScanStatusOp, 0, pOut); +} + /* ** Zero all counters associated with the sqlite3_stmt_scanstatus() data. */ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; - memset(p->anExec, 0, p->nOp * sizeof(i64)); + int ii; + for(ii=0; iinOp; ii++){ + Op *pOp = &p->aOp[ii]; + pOp->nExec = 0; + pOp->nCycle = 0; + } } #endif /* SQLITE_ENABLE_STMT_SCANSTATUS */ diff --git a/src/vdbeaux.c b/src/vdbeaux.c index fd196f37b..d04d8f1e1 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -212,6 +212,8 @@ static int growOpArray(Vdbe *v, int nOp){ */ static void test_addop_breakpoint(int pc, Op *pOp){ static int n = 0; + (void)pc; + (void)pOp; n++; } #endif @@ -262,16 +264,16 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOp->zComment = 0; #endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec = 0; + pOp->nCycle = 0; +#endif #ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ sqlite3VdbePrintOp(0, i, &p->aOp[i]); test_addop_breakpoint(i, &p->aOp[i]); } #endif -#ifdef VDBE_PROFILE - pOp->cycles = 0; - pOp->cnt = 0; -#endif #ifdef SQLITE_VDBE_COVERAGE pOp->iSrcLine = 0; #endif @@ -439,8 +441,9 @@ void sqlite3ExplainBreakpoint(const char *z1, const char *z2){ ** If the bPush flag is true, then make this opcode the parent for ** subsequent Explains until sqlite3VdbeExplainPop() is called. */ -void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ -#ifndef SQLITE_DEBUG +int sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ + int addr = 0; +#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) /* Always include the OP_Explain opcodes if SQLITE_DEBUG is defined. ** But omit them (for performance) during production builds */ if( pParse->explain==2 ) @@ -455,13 +458,15 @@ void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ va_end(ap); v = pParse->pVdbe; iThis = v->nOp; - sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0, + addr = sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0, zMsg, P4_DYNAMIC); sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetLastOp(v)->p4.z); if( bPush){ pParse->addrExplain = iThis; } + sqlite3VdbeScanStatus(v, iThis, 0, 0, 0, 0); } + return addr; } /* @@ -569,6 +574,9 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){ int i; for(i=p->nLabelAlloc; iaLabel[i] = -1; #endif + if( nNewSize>=100 && (nNewSize/100)>(p->nLabelAlloc/100) ){ + sqlite3ProgressCheck(p); + } p->nLabelAlloc = nNewSize; p->aLabel[j] = v->nOp; } @@ -1119,6 +1127,7 @@ void sqlite3VdbeScanStatus( aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); if( aNew ){ ScanStatus *pNew = &aNew[p->nScan++]; + memset(pNew, 0, sizeof(ScanStatus)); pNew->addrExplain = addrExplain; pNew->addrLoop = addrLoop; pNew->addrVisit = addrVisit; @@ -1127,6 +1136,62 @@ void sqlite3VdbeScanStatus( p->aScan = aNew; } } + +/* +** Add the range of instructions from addrStart to addrEnd (inclusive) to +** the set of those corresponding to the sqlite3_stmt_scanstatus() counters +** associated with the OP_Explain instruction at addrExplain. The +** sum of the sqlite3Hwtime() values for each of these instructions +** will be returned for SQLITE_SCANSTAT_NCYCLE requests. +*/ +void sqlite3VdbeScanStatusRange( + Vdbe *p, + int addrExplain, + int addrStart, + int addrEnd +){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + if( addrEnd<0 ) addrEnd = sqlite3VdbeCurrentAddr(p)-1; + for(ii=0; iiaAddrRange); ii+=2){ + if( pScan->aAddrRange[ii]==0 ){ + pScan->aAddrRange[ii] = addrStart; + pScan->aAddrRange[ii+1] = addrEnd; + break; + } + } + } +} + +/* +** Set the addresses for the SQLITE_SCANSTAT_NLOOP and SQLITE_SCANSTAT_NROW +** counters for the query element associated with the OP_Explain at +** addrExplain. +*/ +void sqlite3VdbeScanStatusCounters( + Vdbe *p, + int addrExplain, + int addrLoop, + int addrVisit +){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + pScan->addrLoop = addrLoop; + pScan->addrVisit = addrVisit; + } +} #endif @@ -2256,7 +2321,6 @@ int sqlite3VdbeList( ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ releaseMemArray(pMem, 8); - p->pResultSet = 0; if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or @@ -2313,7 +2377,7 @@ int sqlite3VdbeList( sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); p->nResColumn = 8; } - p->pResultSet = pMem; + p->pResultRow = pMem; if( db->mallocFailed ){ p->rc = SQLITE_NOMEM; rc = SQLITE_ERROR; @@ -2424,7 +2488,7 @@ static void *allocSpace( ** running it. */ void sqlite3VdbeRewind(Vdbe *p){ -#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) +#if defined(SQLITE_DEBUG) int i; #endif assert( p!=0 ); @@ -2453,8 +2517,8 @@ void sqlite3VdbeRewind(Vdbe *p){ p->nFkConstraint = 0; #ifdef VDBE_PROFILE for(i=0; inOp; i++){ - p->aOp[i].cnt = 0; - p->aOp[i].cycles = 0; + p->aOp[i].nExec = 0; + p->aOp[i].nCycle = 0; } #endif } @@ -2563,9 +2627,6 @@ void sqlite3VdbeMakeReady( p->aVar = allocSpace(&x, 0, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, 0, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, 0, nCursor*sizeof(VdbeCursor*)); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - p->anExec = allocSpace(&x, 0, p->nOp*sizeof(i64)); -#endif if( x.nNeeded ){ x.pSpace = p->pFree = sqlite3DbMallocRawNN(db, x.nNeeded); x.nFree = x.nNeeded; @@ -2574,9 +2635,6 @@ void sqlite3VdbeMakeReady( p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*)); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - p->anExec = allocSpace(&x, p->anExec, p->nOp*sizeof(i64)); -#endif } } @@ -2591,9 +2649,6 @@ void sqlite3VdbeMakeReady( p->nMem = nMem; initMemArray(p->aMem, nMem, db, MEM_Undefined); memset(p->apCsr, 0, nCursor*sizeof(VdbeCursor*)); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - memset(p->anExec, 0, p->nOp*sizeof(i64)); -#endif } sqlite3VdbeRewind(p); } @@ -2651,9 +2706,6 @@ static void closeCursorsInFrame(Vdbe *p){ int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ Vdbe *v = pFrame->v; closeCursorsInFrame(v); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - v->anExec = pFrame->anExec; -#endif v->aOp = pFrame->aOp; v->nOp = pFrame->nOp; v->aMem = pFrame->aMem; @@ -3457,7 +3509,7 @@ int sqlite3VdbeReset(Vdbe *p){ sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; } - p->pResultSet = 0; + p->pResultRow = 0; #ifdef SQLITE_DEBUG p->nWrite = 0; #endif @@ -3485,10 +3537,12 @@ int sqlite3VdbeReset(Vdbe *p){ } for(i=0; inOp; i++){ char zHdr[100]; + i64 cnt = p->aOp[i].nExec; + i64 cycles = p->aOp[i].nCycle; sqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ", - p->aOp[i].cnt, - p->aOp[i].cycles, - p->aOp[i].cnt>0 ? p->aOp[i].cycles/p->aOp[i].cnt : 0 + cnt, + cycles, + cnt>0 ? cycles/cnt : 0 ); fprintf(out, "%s", zHdr); sqlite3VdbePrintOp(out, i, &p->aOp[i]); diff --git a/src/vdbemem.c b/src/vdbemem.c index f14599def..be52062d5 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -114,9 +114,9 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ i64 x; assert( (p->flags&MEM_Int)*2==sizeof(x) ); memcpy(&x, (char*)&p->u, (p->flags&MEM_Int)*2); - sqlite3Int64ToText(x, zBuf); + p->n = sqlite3Int64ToText(x, zBuf); #else - sqlite3Int64ToText(p->u.i, zBuf); + p->n = sqlite3Int64ToText(p->u.i, zBuf); #endif }else{ sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); @@ -124,6 +124,7 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ (p->flags & MEM_IntReal)!=0 ? (double)p->u.i : p->u.r); assert( acc.zText==zBuf && acc.mxAlloc<=0 ); zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ + p->n = acc.nChar; } } @@ -151,6 +152,7 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ ** This routine is for use inside of assert() statements only. */ int sqlite3VdbeMemValidStrRep(Mem *p){ + Mem tmp; char zBuf[100]; char *z; int i, j, incr; @@ -167,7 +169,8 @@ int sqlite3VdbeMemValidStrRep(Mem *p){ assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 ); } if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1; - vdbeMemRenderNum(sizeof(zBuf), zBuf, p); + memcpy(&tmp, p, sizeof(tmp)); + vdbeMemRenderNum(sizeof(zBuf), zBuf, &tmp); z = p->z; i = j = 0; incr = 1; @@ -436,7 +439,7 @@ int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ vdbeMemRenderNum(nByte, pMem->z, pMem); assert( pMem->z!=0 ); - pMem->n = sqlite3Strlen30NN(pMem->z); + assert( pMem->n==sqlite3Strlen30NN(pMem->z) ); pMem->enc = SQLITE_UTF8; pMem->flags |= MEM_Str|MEM_Term; if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); @@ -676,32 +679,35 @@ int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){ } /* -** The MEM structure is already a MEM_Real. Try to also make it a -** MEM_Int if we can. +** The MEM structure is already a MEM_Real or MEM_IntReal. Try to +** make it a MEM_Int if we can. */ void sqlite3VdbeIntegerAffinity(Mem *pMem){ - i64 ix; assert( pMem!=0 ); - assert( pMem->flags & MEM_Real ); + assert( pMem->flags & (MEM_Real|MEM_IntReal) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); - ix = doubleToInt64(pMem->u.r); - - /* Only mark the value as an integer if - ** - ** (1) the round-trip conversion real->int->real is a no-op, and - ** (2) The integer is neither the largest nor the smallest - ** possible integer (ticket #3922) - ** - ** The second and third terms in the following conditional enforces - ** the second condition under the assumption that addition overflow causes - ** values to wrap around. - */ - if( pMem->u.r==ix && ix>SMALLEST_INT64 && ixu.i = ix; + if( pMem->flags & MEM_IntReal ){ MemSetTypeFlag(pMem, MEM_Int); + }else{ + i64 ix = doubleToInt64(pMem->u.r); + + /* Only mark the value as an integer if + ** + ** (1) the round-trip conversion real->int->real is a no-op, and + ** (2) The integer is neither the largest nor the smallest + ** possible integer (ticket #3922) + ** + ** The second and third terms in the following conditional enforces + ** the second condition under the assumption that addition overflow causes + ** values to wrap around. + */ + if( pMem->u.r==ix && ix>SMALLEST_INT64 && ixu.i = ix; + MemSetTypeFlag(pMem, MEM_Int); + } } } @@ -1502,8 +1508,6 @@ static int valueFromFunction( goto value_from_function_out; } - testcase( pCtx->pParse->rc==SQLITE_ERROR ); - testcase( pCtx->pParse->rc==SQLITE_OK ); memset(&ctx, 0, sizeof(ctx)); ctx.pOut = pVal; ctx.pFunc = pFunc; @@ -1515,17 +1519,22 @@ static int valueFromFunction( }else{ sqlite3ValueApplyAffinity(pVal, aff, SQLITE_UTF8); assert( rc==SQLITE_OK ); + assert( enc==pVal->enc + || (pVal->flags & MEM_Str)==0 + || db->mallocFailed ); +#if 0 /* Not reachable except after a prior failure */ rc = sqlite3VdbeChangeEncoding(pVal, enc); if( rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal) ){ rc = SQLITE_TOOBIG; pCtx->pParse->nErr++; } +#endif } - pCtx->pParse->rc = rc; value_from_function_out: if( rc!=SQLITE_OK ){ pVal = 0; + pCtx->pParse->rc = rc; } if( apVal ){ for(i=0; ipVtab; int rc = SQLITE_OK; + (void)idxStr; bytecodevtabCursorClear(pCur); pCur->iRowid = 0; diff --git a/src/vtab.c b/src/vtab.c index 3814b0355..2dd92762b 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -211,10 +211,10 @@ void sqlite3VtabUnlock(VTable *pVTab){ pVTab->nRef--; if( pVTab->nRef==0 ){ sqlite3_vtab *p = pVTab->pVtab; - sqlite3VtabModuleUnref(pVTab->db, pVTab->pMod); if( p ){ p->pModule->xDisconnect(p); } + sqlite3VtabModuleUnref(pVTab->db, pVTab->pMod); sqlite3DbFree(db, pVTab); } } diff --git a/src/where.c b/src/where.c index 0c083484f..08dd21824 100644 --- a/src/where.c +++ b/src/where.c @@ -705,7 +705,7 @@ static void translateColumnToCopy( #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED) static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ int i; - if( !sqlite3WhereTrace ) return; + if( (sqlite3WhereTrace & 0x10)==0 ) return; for(i=0; inConstraint; i++){ sqlite3DebugPrintf( " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", @@ -725,7 +725,7 @@ static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ } static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ int i; - if( !sqlite3WhereTrace ) return; + if( (sqlite3WhereTrace & 0x10)==0 ) return; for(i=0; inConstraint; i++){ sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n", i, @@ -812,6 +812,57 @@ static int termCanDriveIndex( #ifndef SQLITE_OMIT_AUTOMATIC_INDEX + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Argument pIdx represents an automatic index that the current statement +** will create and populate. Add an OP_Explain with text of the form: +** +** CREATE AUTOMATIC INDEX ON () [WHERE ] +** +** This is only required if sqlite3_stmt_scanstatus() is enabled, to +** associate an SQLITE_SCANSTAT_NCYCLE and SQLITE_SCANSTAT_NLOOP +** values with. In order to avoid breaking legacy code and test cases, +** the OP_Explain is not added if this is an EXPLAIN QUERY PLAN command. +*/ +static void explainAutomaticIndex( + Parse *pParse, + Index *pIdx, /* Automatic index to explain */ + int bPartial, /* True if pIdx is a partial index */ + int *pAddrExplain /* OUT: Address of OP_Explain */ +){ + if( pParse->explain!=2 ){ + Table *pTab = pIdx->pTable; + const char *zSep = ""; + char *zText = 0; + int ii = 0; + sqlite3_str *pStr = sqlite3_str_new(pParse->db); + sqlite3_str_appendf(pStr,"CREATE AUTOMATIC INDEX ON %s(", pTab->zName); + assert( pIdx->nColumn>1 ); + assert( pIdx->aiColumn[pIdx->nColumn-1]==XN_ROWID ); + for(ii=0; ii<(pIdx->nColumn-1); ii++){ + const char *zName = 0; + int iCol = pIdx->aiColumn[ii]; + + zName = pTab->aCol[iCol].zCnName; + sqlite3_str_appendf(pStr, "%s%s", zSep, zName); + zSep = ", "; + } + zText = sqlite3_str_finish(pStr); + if( zText==0 ){ + sqlite3OomFault(pParse->db); + }else{ + *pAddrExplain = sqlite3VdbeExplain( + pParse, 0, "%s)%s", zText, (bPartial ? " WHERE " : "") + ); + sqlite3_free(zText); + } + } +} +#else +# define explainAutomaticIndex(a,b,c,d) +#endif + /* ** Generate code to construct the Index object for an automatic index ** and to set up the WhereLevel object pLevel so that the code generator @@ -847,6 +898,9 @@ static SQLITE_NOINLINE void constructAutomaticIndex( SrcItem *pTabItem; /* FROM clause term being indexed */ int addrCounter = 0; /* Address where integer counter is initialized */ int regBase; /* Array of registers where record is assembled */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExp = 0; /* Address of OP_Explain */ +#endif /* Generate code to skip over the creation and initialization of the ** transient index on 2nd and subsequent iterations of the loop. */ @@ -970,6 +1024,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( pIdx->azColl[n] = sqlite3StrBINARY; /* Create the automatic index */ + explainAutomaticIndex(pParse, pIdx, pPartial!=0, &addrExp); assert( pLevel->iIdxCur>=0 ); pLevel->iIdxCur = pParse->nTab++; sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); @@ -1005,6 +1060,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, regBase, pLoop->u.btree.nEq); } + sqlite3VdbeScanStatusCounters(v, addrExp, addrExp, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); @@ -1025,6 +1081,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( /* Jump here when skipping the initialization */ sqlite3VdbeJumpHere(v, addrInit); + sqlite3VdbeScanStatusRange(v, addrExp, addrExp, -1); end_auto_index_create: sqlite3ExprDelete(pParse->db, pPartial); @@ -1564,12 +1621,12 @@ static int whereKeyStats( if( iCol>0 ){ pRec->nField = iCol; assert( sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)<=0 - || pParse->db->mallocFailed ); + || pParse->db->mallocFailed || CORRUPT_DB ); } if( i>0 ){ pRec->nField = nField; assert( sqlite3VdbeRecordCompare(aSample[i-1].n, aSample[i-1].p, pRec)<0 - || pParse->db->mallocFailed ); + || pParse->db->mallocFailed || CORRUPT_DB ); } } } @@ -1742,7 +1799,7 @@ static int whereRangeSkipScanEst( int nAdjust = (sqlite3LogEst(p->nSample) - sqlite3LogEst(nDiff)); pLoop->nOut -= nAdjust; *pbDone = 1; - WHERETRACE(0x10, ("range skip-scan regions: %u..%u adjust=%d est=%d\n", + WHERETRACE(0x20, ("range skip-scan regions: %u..%u adjust=%d est=%d\n", nLower, nUpper, nAdjust*-1, pLoop->nOut)); } @@ -1920,7 +1977,7 @@ static int whereRangeScanEst( if( nNewnOut>nOut ){ - WHERETRACE(0x10,("Range scan lowers nOut from %d to %d\n", + WHERETRACE(0x20,("Range scan lowers nOut from %d to %d\n", pLoop->nOut, nOut)); } #endif @@ -2018,7 +2075,7 @@ static int whereEqualScanEst( pBuilder->nRecValid = nEq; whereKeyStats(pParse, p, pRec, 0, a); - WHERETRACE(0x10,("equality scan regions %s(%d): %d\n", + WHERETRACE(0x20,("equality scan regions %s(%d): %d\n", p->zName, nEq-1, (int)a[1])); *pnRow = a[1]; @@ -2066,9 +2123,9 @@ static int whereInScanEst( } if( rc==SQLITE_OK ){ - if( nRowEst > nRow0 ) nRowEst = nRow0; + if( nRowEst > (tRowcnt)nRow0 ) nRowEst = nRow0; *pnRow = nRowEst; - WHERETRACE(0x10,("IN row estimate: est=%d\n", nRowEst)); + WHERETRACE(0x20,("IN row estimate: est=%d\n", nRowEst)); } assert( pBuilder->nRecValid==nRecValid ); return rc; @@ -2177,7 +2234,7 @@ void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){ sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm); } sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); - if( p->nLTerm && (sqlite3WhereTrace & 0x100)!=0 ){ + if( p->nLTerm && (sqlite3WhereTrace & 0x4000)!=0 ){ int i; for(i=0; inLTerm; i++){ sqlite3WhereTermPrint(p->aLTerm[i], i); @@ -2641,6 +2698,7 @@ static void whereLoopOutputAdjust( if( pX->iParent>=0 && (&pWC->a[pX->iParent])==pTerm ) break; } if( j<0 ){ + sqlite3ProgressCheck(pWC->pWInfo->pParse); if( pLoop->maskSelf==pTerm->prereqAll ){ /* If there are extra terms in the WHERE clause not used by an index ** that depend only on the table being scanned, and that will tend to @@ -2808,7 +2866,10 @@ static int whereLoopAddBtreeIndex( WhereTerm *pTop = 0, *pBtm = 0; /* Top and bottom range constraints */ pNew = pBuilder->pNew; - if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; + assert( db->mallocFailed==0 || pParse->nErr>0 ); + if( pParse->nErr ){ + return pParse->rc; + } WHERETRACE(0x800, ("BEGIN %s.addBtreeIdx(%s), nEq=%d, nSkip=%d, rRun=%d\n", pProbe->pTable->zName,pProbe->zName, pNew->u.btree.nEq, pNew->nSkip, pNew->rRun)); @@ -3055,7 +3116,7 @@ static int whereLoopAddBtreeIndex( && pNew->nOut+10 > pProbe->aiRowLogEst[0] ){ #if WHERETRACE_ENABLED /* 0x01 */ - if( sqlite3WhereTrace & 0x01 ){ + if( sqlite3WhereTrace & 0x20 ){ sqlite3DebugPrintf( "STAT4 determines term has low selectivity:\n"); sqlite3WhereTermPrint(pTerm, 999); @@ -3092,9 +3153,17 @@ static int whereLoopAddBtreeIndex( ** seek only. Then, if this is a non-covering index, add the cost of ** visiting the rows in the main table. */ assert( pSrc->pTab->szTabRow>0 ); - rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; + if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ + /* The pProbe->szIdxRow is low for an IPK table since the interior + ** pages are small. Thuse szIdxRow gives a good estimate of seek cost. + ** But the leaf pages are full-size, so pProbe->szIdxRow would badly + ** under-estimate the scanning cost. */ + rCostIdx = pNew->nOut + 16; + }else{ + rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; + } pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx); - if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK))==0 ){ + if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK|WHERE_EXPRIDX))==0 ){ pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16); } ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult); @@ -3116,6 +3185,9 @@ static int whereLoopAddBtreeIndex( && (pNew->u.btree.nEqnKeyCol || pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY) ){ + if( pNew->u.btree.nEq>3 ){ + sqlite3ProgressCheck(pParse); + } whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); } pNew->nOut = saved_nOut; @@ -3247,16 +3319,39 @@ static int whereUsablePartialIndex( return 0; } +/* +** pIdx is an index containing expressions. Check it see if any of the +** expressions in the index match the pExpr expression. +*/ +static int exprIsCoveredByIndex( + const Expr *pExpr, + const Index *pIdx, + int iTabCur +){ + int i; + for(i=0; inColumn; i++){ + if( pIdx->aiColumn[i]==XN_EXPR + && sqlite3ExprCompare(0, pExpr, pIdx->aColExpr->a[i].pExpr, iTabCur)==0 + ){ + return 1; + } + } + return 0; +} + /* ** Structure passed to the whereIsCoveringIndex Walker callback. */ +typedef struct CoveringIndexCheck CoveringIndexCheck; struct CoveringIndexCheck { Index *pIdx; /* The index */ int iTabCur; /* Cursor number for the corresponding table */ + u8 bExpr; /* Uses an indexed expression */ + u8 bUnidx; /* Uses an unindexed column not within an indexed expr */ }; /* -** Information passed in is pWalk->u.pCovIdxCk. Call is pCk. +** Information passed in is pWalk->u.pCovIdxCk. Call it pCk. ** ** If the Expr node references the table with cursor pCk->iTabCur, then ** make sure that column is covered by the index pCk->pIdx. We know that @@ -3268,71 +3363,103 @@ struct CoveringIndexCheck { ** ** If this node does not disprove that the index can be a covering index, ** then just return WRC_Continue, to continue the search. +** +** If pCk->pIdx contains indexed expressions and one of those expressions +** matches pExpr, then prune the search. */ static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){ - int i; /* Loop counter */ - const Index *pIdx; /* The index of interest */ - const i16 *aiColumn; /* Columns contained in the index */ - u16 nColumn; /* Number of columns in the index */ - if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_AGG_COLUMN ) return WRC_Continue; - if( pExpr->iColumn<(BMS-1) ) return WRC_Continue; - if( pExpr->iTable!=pWalk->u.pCovIdxCk->iTabCur ) return WRC_Continue; - pIdx = pWalk->u.pCovIdxCk->pIdx; - aiColumn = pIdx->aiColumn; - nColumn = pIdx->nColumn; - for(i=0; iiColumn ) return WRC_Continue; - } - pWalk->eCode = 1; - return WRC_Abort; + int i; /* Loop counter */ + const Index *pIdx; /* The index of interest */ + const i16 *aiColumn; /* Columns contained in the index */ + u16 nColumn; /* Number of columns in the index */ + CoveringIndexCheck *pCk; /* Info about this search */ + + pCk = pWalk->u.pCovIdxCk; + pIdx = pCk->pIdx; + if( (pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN) ){ + /* if( pExpr->iColumn<(BMS-1) && pIdx->bHasExpr==0 ) return WRC_Continue;*/ + if( pExpr->iTable!=pCk->iTabCur ) return WRC_Continue; + pIdx = pWalk->u.pCovIdxCk->pIdx; + aiColumn = pIdx->aiColumn; + nColumn = pIdx->nColumn; + for(i=0; iiColumn ) return WRC_Continue; + } + pCk->bUnidx = 1; + return WRC_Abort; + }else if( pIdx->bHasExpr + && exprIsCoveredByIndex(pExpr, pIdx, pWalk->u.pCovIdxCk->iTabCur) ){ + pCk->bExpr = 1; + return WRC_Prune; + } + return WRC_Continue; } /* ** pIdx is an index that covers all of the low-number columns used by -** pWInfo->pSelect (columns from 0 through 62). But there are columns -** in pWInfo->pSelect beyond 62. This routine tries to answer the question -** of whether pIdx covers *all* columns in the query. +** pWInfo->pSelect (columns from 0 through 62) or an index that has +** expressions terms. Hence, we cannot determine whether or not it is +** a covering index by using the colUsed bitmasks. We have to do a search +** to see if the index is covering. This routine does that search. ** -** Return 0 if pIdx is a covering index. Return non-zero if pIdx is -** not a covering index or if we are unable to determine if pIdx is a -** covering index. +** The return value is one of these: ** -** This routine is an optimization. It is always safe to return non-zero. -** But returning zero when non-zero should have been returned can lead to -** incorrect bytecode and assertion faults. +** 0 The index is definitely not a covering index +** +** WHERE_IDX_ONLY The index is definitely a covering index +** +** WHERE_EXPRIDX The index is likely a covering index, but it is +** difficult to determine precisely because of the +** expressions that are indexed. Score it as a +** covering index, but still keep the main table open +** just in case we need it. +** +** This routine is an optimization. It is always safe to return zero. +** But returning one of the other two values when zero should have been +** returned can lead to incorrect bytecode and assertion faults. */ static SQLITE_NOINLINE u32 whereIsCoveringIndex( WhereInfo *pWInfo, /* The WHERE clause context */ Index *pIdx, /* Index that is being tested */ int iTabCur /* Cursor for the table being indexed */ ){ - int i; + int i, rc; struct CoveringIndexCheck ck; Walker w; if( pWInfo->pSelect==0 ){ /* We don't have access to the full query, so we cannot check to see ** if pIdx is covering. Assume it is not. */ - return 1; - } - for(i=0; inColumn; i++){ - if( pIdx->aiColumn[i]>=BMS-1 ) break; + return 0; } - if( i>=pIdx->nColumn ){ - /* pIdx does not index any columns greater than 62, but we know from - ** colMask that columns greater than 62 are used, so this is not a - ** covering index */ - return 1; + if( pIdx->bHasExpr==0 ){ + for(i=0; inColumn; i++){ + if( pIdx->aiColumn[i]>=BMS-1 ) break; + } + if( i>=pIdx->nColumn ){ + /* pIdx does not index any columns greater than 62, but we know from + ** colMask that columns greater than 62 are used, so this is not a + ** covering index */ + return 0; + } } ck.pIdx = pIdx; ck.iTabCur = iTabCur; + ck.bExpr = 0; + ck.bUnidx = 0; memset(&w, 0, sizeof(w)); w.xExprCallback = whereIsCoveringIndexWalkCallback; w.xSelectCallback = sqlite3SelectWalkNoop; w.u.pCovIdxCk = &ck; - w.eCode = 0; sqlite3WalkSelect(&w, pWInfo->pSelect); - return w.eCode; + if( ck.bUnidx ){ + rc = 0; + }else if( ck.bExpr ){ + rc = WHERE_EXPRIDX; + }else{ + rc = WHERE_IDX_ONLY; + } + return rc; } /* @@ -3417,7 +3544,7 @@ static int whereLoopAddBtree( sPk.aiRowLogEst = aiRowEstPk; sPk.onError = OE_Replace; sPk.pTable = pTab; - sPk.szIdxRow = pTab->szTabRow; + sPk.szIdxRow = 3; /* TUNING: Interior rows of IPK table are very small */ sPk.idxType = SQLITE_IDXTYPE_IPK; aiRowEstPk[0] = pTab->nRowLogEst; aiRowEstPk[1] = 0; @@ -3468,7 +3595,8 @@ static int whereLoopAddBtree( if( !IsView(pTab) && (pTab->tabFlags & TF_Ephemeral)==0 ){ pNew->rSetup += 28; }else{ - pNew->rSetup -= 10; + pNew->rSetup -= 25; /* Greatly reduced setup cost for auto indexes + ** on ephemeral materializations of views */ } ApplyCostMultiplier(pNew->rSetup, pTab->costMult); if( pNew->rSetup<0 ) pNew->rSetup = 0; @@ -3548,14 +3676,38 @@ static int whereLoopAddBtree( }else{ Bitmask m; if( pProbe->isCovering ){ - pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; m = 0; + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; }else{ m = pSrc->colUsed & pProbe->colNotIdxed; - if( m==TOPBIT ){ - m = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor); + pNew->wsFlags = WHERE_INDEXED; + if( m==TOPBIT || (pProbe->bHasExpr && !pProbe->bHasVCol && m!=0) ){ + u32 isCov = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor); + if( isCov==0 ){ + WHERETRACE(0x200, + ("-> %s is not a covering index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + assert( m!=0 ); + }else{ + m = 0; + pNew->wsFlags |= isCov; + if( isCov & WHERE_IDX_ONLY ){ + WHERETRACE(0x200, + ("-> %s is a covering expression index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + }else{ + assert( isCov==WHERE_EXPRIDX ); + WHERETRACE(0x200, + ("-> %s might be a covering expression index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + } + } + }else if( m==0 ){ + WHERETRACE(0x200, + ("-> %s a covering index according to bitmasks\n", + pProbe->zName, m==0 ? "is" : "is not")); + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; } - pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; } /* Full scan via index */ @@ -3728,7 +3880,7 @@ static int whereLoopAddVirtualOne( ** that the particular combination of parameters provided is unusable. ** Make no entries in the loop table. */ - WHERETRACE(0xffff, (" ^^^^--- non-viable plan rejected!\n")); + WHERETRACE(0xffffffff, (" ^^^^--- non-viable plan rejected!\n")); return SQLITE_OK; } return rc; @@ -3839,7 +3991,7 @@ static int whereLoopAddVirtualOne( sqlite3_free(pNew->u.vtab.idxStr); pNew->u.vtab.needFree = 0; } - WHERETRACE(0xffff, (" bIn=%d prereqIn=%04llx prereqOut=%04llx\n", + WHERETRACE(0xffffffff, (" bIn=%d prereqIn=%04llx prereqOut=%04llx\n", *pbIn, (sqlite3_uint64)mPrereq, (sqlite3_uint64)(pNew->prereq & ~mPrereq))); @@ -3944,7 +4096,7 @@ int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ && !defined(SQLITE_OMIT_VIRTUALTABLE) /* ** Cause the prepared statement that is associated with a call to -** xBestIndex to potentiall use all schemas. If the statement being +** xBestIndex to potentially use all schemas. If the statement being ** prepared is read-only, then just start read transactions on all ** schemas. But if this is a write operation, start writes on all ** schemas. @@ -3959,7 +4111,7 @@ void sqlite3VtabUsesAllSchemas(sqlite3_index_info *pIdxInfo){ for(i=0; iwriteMask ){ + if( DbMaskNonZero(pParse->writeMask) ){ for(i=0; ipTab->zName)); - WHERETRACE(0x40, (" VirtualOne: all usable\n")); + WHERETRACE(0x800, (" VirtualOne: all usable\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, &bRetry ); @@ -4056,7 +4208,7 @@ static int whereLoopAddVirtual( /* If the plan produced by the earlier call uses an IN(...) term, call ** xBestIndex again, this time with IN(...) terms disabled. */ if( bIn ){ - WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n")); + WHERETRACE(0x800, (" VirtualOne: all usable w/o IN\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn, 0); assert( bIn==0 ); @@ -4082,7 +4234,7 @@ static int whereLoopAddVirtual( mPrev = mNext; if( mNext==ALLBITS ) break; if( mNext==mBest || mNext==mBestNoIn ) continue; - WHERETRACE(0x40, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", + WHERETRACE(0x800, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext)); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn, 0); @@ -4096,7 +4248,7 @@ static int whereLoopAddVirtual( ** that requires no source tables at all (i.e. one guaranteed to be ** usable), make a call here with all source tables disabled */ if( rc==SQLITE_OK && seenZero==0 ){ - WHERETRACE(0x40, (" VirtualOne: all disabled\n")); + WHERETRACE(0x800, (" VirtualOne: all disabled\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn, 0); if( bIn==0 ) seenZeroNoIN = 1; @@ -4106,7 +4258,7 @@ static int whereLoopAddVirtual( ** that requires no source tables at all and does not use an IN(...) ** operator, make a final call to obtain one here. */ if( rc==SQLITE_OK && seenZeroNoIN==0 ){ - WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n")); + WHERETRACE(0x800, (" VirtualOne: all disabled and w/o IN\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn, 0); } @@ -4162,7 +4314,7 @@ static int whereLoopAddOr( sSubBuild = *pBuilder; sSubBuild.pOrSet = &sCur; - WHERETRACE(0x200, ("Begin processing OR-clause %p\n", pTerm)); + WHERETRACE(0x400, ("Begin processing OR-clause %p\n", pTerm)); for(pOrTerm=pOrWC->a; pOrTermeOperator & WO_AND)!=0 ){ sSubBuild.pWC = &pOrTerm->u.pAndInfo->wc; @@ -4179,9 +4331,9 @@ static int whereLoopAddOr( } sCur.n = 0; #ifdef WHERETRACE_ENABLED - WHERETRACE(0x200, ("OR-term %d of %p has %d subterms:\n", + WHERETRACE(0x400, ("OR-term %d of %p has %d subterms:\n", (int)(pOrTerm-pOrWC->a), pTerm, sSubBuild.pWC->nTerm)); - if( sqlite3WhereTrace & 0x400 ){ + if( sqlite3WhereTrace & 0x20000 ){ sqlite3WhereClausePrint(sSubBuild.pWC); } #endif @@ -4196,8 +4348,6 @@ static int whereLoopAddOr( if( rc==SQLITE_OK ){ rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable); } - assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0 - || rc==SQLITE_NOMEM ); testcase( rc==SQLITE_NOMEM && sCur.n>0 ); testcase( rc==SQLITE_DONE ); if( sCur.n==0 ){ @@ -4243,7 +4393,7 @@ static int whereLoopAddOr( pNew->prereq = sSum.a[i].prereq; rc = whereLoopInsert(pBuilder, pNew); } - WHERETRACE(0x200, ("End processing OR-clause %p\n", pTerm)); + WHERETRACE(0x400, ("End processing OR-clause %p\n", pTerm)); } } return rc; @@ -4591,8 +4741,8 @@ static i8 wherePathSatisfiesOrderBy( if( pOBExpr->iTable!=iCur ) continue; if( pOBExpr->iColumn!=iColumn ) continue; }else{ - Expr *pIdxExpr = pIndex->aColExpr->a[j].pExpr; - if( sqlite3ExprCompareSkip(pOBExpr, pIdxExpr, iCur) ){ + Expr *pIxExpr = pIndex->aColExpr->a[j].pExpr; + if( sqlite3ExprCompareSkip(pOBExpr, pIxExpr, iCur) ){ continue; } } @@ -4724,37 +4874,56 @@ static const char *wherePathName(WherePath *pPath, int nLoop, WhereLoop *pLast){ ** order. */ static LogEst whereSortingCost( - WhereInfo *pWInfo, - LogEst nRow, - int nOrderBy, - int nSorted + WhereInfo *pWInfo, /* Query planning context */ + LogEst nRow, /* Estimated number of rows to sort */ + int nOrderBy, /* Number of ORDER BY clause terms */ + int nSorted /* Number of initial ORDER BY terms naturally in order */ ){ - /* TUNING: Estimated cost of a full external sort, where N is + /* Estimated cost of a full external sort, where N is ** the number of rows to sort is: ** - ** cost = (3.0 * N * log(N)). + ** cost = (K * N * log(N)). ** ** Or, if the order-by clause has X terms but only the last Y ** terms are out of order, then block-sorting will reduce the ** sorting cost to: ** - ** cost = (3.0 * N * log(N)) * (Y/X) + ** cost = (K * N * log(N)) * (Y/X) ** - ** The (Y/X) term is implemented using stack variable rScale - ** below. + ** The constant K is at least 2.0 but will be larger if there are a + ** large number of columns to be sorted, as the sorting time is + ** proportional to the amount of content to be sorted. The algorithm + ** does not currently distinguish between fat columns (BLOBs and TEXTs) + ** and skinny columns (INTs). It just uses the number of columns as + ** an approximation for the row width. + ** + ** And extra factor of 2.0 or 3.0 is added to the sorting cost if the sort + ** is built using OP_IdxInsert and OP_Sort rather than with OP_SorterInsert. */ - LogEst rScale, rSortCost; - assert( nOrderBy>0 && 66==sqlite3LogEst(100) ); - rScale = sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; - rSortCost = nRow + rScale + 16; + LogEst rSortCost, nCol; + assert( pWInfo->pSelect!=0 ); + assert( pWInfo->pSelect->pEList!=0 ); + /* TUNING: sorting cost proportional to the number of output columns: */ + nCol = sqlite3LogEst((pWInfo->pSelect->pEList->nExpr+59)/30); + rSortCost = nRow + nCol; + if( nSorted>0 ){ + /* Scale the result by (Y/X) */ + rSortCost += sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; + } /* Multiple by log(M) where M is the number of output rows. ** Use the LIMIT for M if it is smaller. Or if this sort is for ** a DISTINCT operator, M will be the number of distinct output ** rows, so fudge it downwards a bit. */ - if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 && pWInfo->iLimitiLimit; + if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 ){ + rSortCost += 10; /* TUNING: Extra 2.0x if using LIMIT */ + if( nSorted!=0 ){ + rSortCost += 6; /* TUNING: Extra 1.5x if also using partial sort */ + } + if( pWInfo->iLimitiLimit; + } }else if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT) ){ /* TUNING: In the sort for a DISTINCT operator, assume that the DISTINCT ** reduces the number of output rows by a factor of 2 */ @@ -4906,11 +5075,11 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ pWInfo, nRowEst, nOrderBy, isOrdered ); } - /* TUNING: Add a small extra penalty (5) to sorting as an + /* TUNING: Add a small extra penalty (3) to sorting as an ** extra encouragment to the query planner to select a plan ** where the rows emerge in the correct order without any sorting ** required. */ - rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 5; + rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 3; WHERETRACE(0x002, ("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n", @@ -5258,7 +5427,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ pLoop->cId = '0'; #endif #ifdef WHERETRACE_ENABLED - if( sqlite3WhereTrace ){ + if( sqlite3WhereTrace & 0x02 ){ sqlite3DebugPrintf("whereShortCut() used to compute solution\n"); } #endif @@ -5388,7 +5557,7 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( } } if( pTerm drop loop %c not used\n", pLoop->cId)); + WHERETRACE(0xffffffff, ("-> drop loop %c not used\n", pLoop->cId)); notReady &= ~pLoop->maskSelf; for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ @@ -5427,28 +5596,27 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( const WhereInfo *pWInfo ){ int i; - LogEst nSearch; + LogEst nSearch = 0; assert( pWInfo->nLevel>=2 ); assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_BloomFilter) ); - nSearch = pWInfo->a[0].pWLoop->nOut; - for(i=1; inLevel; i++){ + for(i=0; inLevel; i++){ WhereLoop *pLoop = pWInfo->a[i].pWLoop; const unsigned int reqFlags = (WHERE_SELFCULL|WHERE_COLUMN_EQ); - if( (pLoop->wsFlags & reqFlags)==reqFlags + SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab]; + Table *pTab = pItem->pTab; + if( (pTab->tabFlags & TF_HasStat1)==0 ) break; + pTab->tabFlags |= TF_StatsUsed; + if( i>=1 + && (pLoop->wsFlags & reqFlags)==reqFlags /* vvvvvv--- Always the case if WHERE_COLUMN_EQ is defined */ && ALWAYS((pLoop->wsFlags & (WHERE_IPK|WHERE_INDEXED))!=0) ){ - SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab]; - Table *pTab = pItem->pTab; - pTab->tabFlags |= TF_StatsUsed; - if( nSearch > pTab->nRowLogEst - && (pTab->tabFlags & TF_HasStat1)!=0 - ){ + if( nSearch > pTab->nRowLogEst ){ testcase( pItem->fg.jointype & JT_LEFT ); pLoop->wsFlags |= WHERE_BLOOMFILTER; pLoop->wsFlags &= ~WHERE_IDX_ONLY; - WHERETRACE(0xffff, ( + WHERETRACE(0xffffffff, ( "-> use Bloom-filter on loop %c because there are ~%.1e " "lookups into %s which has only ~%.1e rows\n", pLoop->cId, (double)sqlite3LogEstToInt(nSearch), pTab->zName, @@ -5461,13 +5629,13 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( /* ** This is an sqlite3ParserAddCleanup() callback that is invoked to -** free the Parse->pIdxExpr list when the Parse object is destroyed. +** free the Parse->pIdxEpr list when the Parse object is destroyed. */ static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ Parse *pParse = (Parse*)pObject; - while( pParse->pIdxExpr!=0 ){ - IndexedExpr *p = pParse->pIdxExpr; - pParse->pIdxExpr = p->pIENext; + while( pParse->pIdxEpr!=0 ){ + IndexedExpr *p = pParse->pIdxEpr; + pParse->pIdxEpr = p->pIENext; sqlite3ExprDelete(db, p->pExpr); sqlite3DbFreeNN(db, p); } @@ -5479,13 +5647,13 @@ static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ ** number for the index and iDataCur is the cursor number for the corresponding ** table. ** -** This routine adds IndexedExpr entries to the Parse->pIdxExpr field for +** This routine adds IndexedExpr entries to the Parse->pIdxEpr field for ** each of the expressions in the index so that the expression code generator ** will know to replace occurrences of the indexed expression with ** references to the corresponding column of the index. */ static SQLITE_NOINLINE void whereAddIndexedExpr( - Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxExpr */ + Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxEpr */ Index *pIdx, /* The index-on-expression that contains the expressions */ int iIdxCur, /* Cursor number for pIdx */ SrcItem *pTabItem /* The FROM clause entry for the table */ @@ -5514,7 +5682,13 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( if( sqlite3ExprIsConstant(pExpr) ) continue; p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; - p->pIENext = pParse->pIdxExpr; + p->pIENext = pParse->pIdxEpr; +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x200 ){ + sqlite3DebugPrintf("New pParse->pIdxEpr term {%d,%d}\n", iIdxCur, i); + if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(pExpr); + } +#endif p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); p->iDataCur = pTabItem->iCursor; p->iIdxCur = iIdxCur; @@ -5523,7 +5697,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS p->zIdxName = pIdx->zName; #endif - pParse->pIdxExpr = p; + pParse->pIdxEpr = p; if( p->pIENext==0 ){ sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse); } @@ -5815,13 +5989,13 @@ WhereInfo *sqlite3WhereBegin( /* Construct the WhereLoop objects */ #if defined(WHERETRACE_ENABLED) - if( sqlite3WhereTrace & 0xffff ){ + if( sqlite3WhereTrace & 0xffffffff ){ sqlite3DebugPrintf("*** Optimizer Start *** (wctrlFlags: 0x%x",wctrlFlags); if( wctrlFlags & WHERE_USE_LIMIT ){ sqlite3DebugPrintf(", limit: %d", iAuxArg); } sqlite3DebugPrintf(")\n"); - if( sqlite3WhereTrace & 0x100 ){ + if( sqlite3WhereTrace & 0x8000 ){ Select sSelect; memset(&sSelect, 0, sizeof(sSelect)); sSelect.selFlags = SF_WhereBegin; @@ -5831,10 +6005,10 @@ WhereInfo *sqlite3WhereBegin( sSelect.pEList = pResultSet; sqlite3TreeViewSelect(0, &sSelect, 0); } - } - if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */ - sqlite3DebugPrintf("---- WHERE clause at start of analysis:\n"); - sqlite3WhereClausePrint(sWLB.pWC); + if( sqlite3WhereTrace & 0x4000 ){ /* Display all WHERE clause terms */ + sqlite3DebugPrintf("---- WHERE clause at start of analysis:\n"); + sqlite3WhereClausePrint(sWLB.pWC); + } } #endif @@ -5850,7 +6024,7 @@ WhereInfo *sqlite3WhereBegin( ** loops will be built using the revised truthProb values. */ if( sWLB.bldFlags2 & SQLITE_BLDF2_2NDPASS ){ WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); - WHERETRACE(0xffff, + WHERETRACE(0xffffffff, ("**** Redo all loop computations due to" " TERM_HIGHTRUTH changes ****\n")); while( pWInfo->pLoops ){ @@ -5936,11 +6110,11 @@ WhereInfo *sqlite3WhereBegin( } #if defined(WHERETRACE_ENABLED) - if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */ + if( sqlite3WhereTrace & 0x4000 ){ /* Display all terms of the WHERE clause */ sqlite3DebugPrintf("---- WHERE clause at end of analysis:\n"); sqlite3WhereClausePrint(sWLB.pWC); } - WHERETRACE(0xffff,("*** Optimizer Finished ***\n")); + WHERETRACE(0xffffffff,("*** Optimizer Finished ***\n")); #endif pWInfo->pParse->nQueryLoop += pWInfo->nRowOut; @@ -6474,9 +6648,16 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ last = pWInfo->iEndWhere; } if( pIdx->bHasExpr ){ - IndexedExpr *p = pParse->pIdxExpr; + IndexedExpr *p = pParse->pIdxEpr; while( p ){ if( p->iIdxCur==pLevel->iIdxCur ){ +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x200 ){ + sqlite3DebugPrintf("Disable pParse->pIdxEpr term {%d,%d}\n", + p->iIdxCur, p->iIdxCol); + if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(p->pExpr); + } +#endif p->iDataCur = -1; p->iIdxCur = -1; } diff --git a/src/whereInt.h b/src/whereInt.h index 28ede5be6..b89a4513e 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -634,5 +634,6 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); #define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ #define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ #define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */ +#define WHERE_EXPRIDX 0x04000000 /* Uses an index-on-expressions */ #endif /* !defined(SQLITE_WHEREINT_H) */ diff --git a/src/wherecode.c b/src/wherecode.c index e36d1c996..860acb44c 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -270,6 +270,8 @@ int sqlite3WhereExplainBloomFilter( zMsg = sqlite3StrAccumFinish(&str); ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), pParse->addrExplain, 0, zMsg,P4_DYNAMIC); + + sqlite3VdbeScanStatus(v, sqlite3VdbeCurrentAddr(v)-1, 0, 0, 0, 0); return ret; } #endif /* SQLITE_OMIT_EXPLAIN */ @@ -292,14 +294,27 @@ void sqlite3WhereAddScanStatus( ){ const char *zObj = 0; WhereLoop *pLoop = pLvl->pWLoop; - if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ + int wsFlags = pLoop->wsFlags; + int viaCoroutine = 0; + + if( (wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ zObj = pLoop->u.btree.pIndex->zName; }else{ zObj = pSrclist->a[pLvl->iFrom].zName; + viaCoroutine = pSrclist->a[pLvl->iFrom].fg.viaCoroutine; } sqlite3VdbeScanStatus( v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj ); + + if( viaCoroutine==0 ){ + if( (wsFlags & (WHERE_MULTI_OR|WHERE_AUTO_INDEX))==0 ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iTabCur); + } + if( wsFlags & WHERE_INDEXED ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); + } + } } #endif @@ -359,7 +374,7 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ pTerm->wtFlags |= TERM_CODED; } #ifdef WHERETRACE_ENABLED - if( sqlite3WhereTrace & 0x20000 ){ + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ sqlite3DebugPrintf("DISABLE-"); sqlite3WhereTermPrint(pTerm, (int)(pTerm - (pTerm->pWC->a))); } @@ -474,68 +489,75 @@ static Expr *removeUnindexableInClauseTerms( Expr *pX /* The IN expression to be reduced */ ){ sqlite3 *db = pParse->db; + Select *pSelect; /* Pointer to the SELECT on the RHS */ Expr *pNew; pNew = sqlite3ExprDup(db, pX, 0); if( db->mallocFailed==0 ){ - ExprList *pOrigRhs; /* Original unmodified RHS */ - ExprList *pOrigLhs; /* Original unmodified LHS */ - ExprList *pRhs = 0; /* New RHS after modifications */ - ExprList *pLhs = 0; /* New LHS after mods */ - int i; /* Loop counter */ - Select *pSelect; /* Pointer to the SELECT on the RHS */ - - assert( ExprUseXSelect(pNew) ); - pOrigRhs = pNew->x.pSelect->pEList; - assert( pNew->pLeft!=0 ); - assert( ExprUseXList(pNew->pLeft) ); - pOrigLhs = pNew->pLeft->x.pList; - for(i=iEq; inLTerm; i++){ - if( pLoop->aLTerm[i]->pExpr==pX ){ - int iField; - assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 ); - iField = pLoop->aLTerm[i]->u.x.iField - 1; - if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */ - pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); - pOrigRhs->a[iField].pExpr = 0; - assert( pOrigLhs->a[iField].pExpr!=0 ); - pLhs = sqlite3ExprListAppend(pParse, pLhs, pOrigLhs->a[iField].pExpr); - pOrigLhs->a[iField].pExpr = 0; + for(pSelect=pNew->x.pSelect; pSelect; pSelect=pSelect->pPrior){ + ExprList *pOrigRhs; /* Original unmodified RHS */ + ExprList *pOrigLhs = 0; /* Original unmodified LHS */ + ExprList *pRhs = 0; /* New RHS after modifications */ + ExprList *pLhs = 0; /* New LHS after mods */ + int i; /* Loop counter */ + + assert( ExprUseXSelect(pNew) ); + pOrigRhs = pSelect->pEList; + assert( pNew->pLeft!=0 ); + assert( ExprUseXList(pNew->pLeft) ); + if( pSelect==pNew->x.pSelect ){ + pOrigLhs = pNew->pLeft->x.pList; } - } - sqlite3ExprListDelete(db, pOrigRhs); - sqlite3ExprListDelete(db, pOrigLhs); - pNew->pLeft->x.pList = pLhs; - pNew->x.pSelect->pEList = pRhs; - if( pLhs && pLhs->nExpr==1 ){ - /* Take care here not to generate a TK_VECTOR containing only a - ** single value. Since the parser never creates such a vector, some - ** of the subroutines do not handle this case. */ - Expr *p = pLhs->a[0].pExpr; - pLhs->a[0].pExpr = 0; - sqlite3ExprDelete(db, pNew->pLeft); - pNew->pLeft = p; - } - pSelect = pNew->x.pSelect; - if( pSelect->pOrderBy ){ - /* If the SELECT statement has an ORDER BY clause, zero the - ** iOrderByCol variables. These are set to non-zero when an - ** ORDER BY term exactly matches one of the terms of the - ** result-set. Since the result-set of the SELECT statement may - ** have been modified or reordered, these variables are no longer - ** set correctly. Since setting them is just an optimization, - ** it's easiest just to zero them here. */ - ExprList *pOrderBy = pSelect->pOrderBy; - for(i=0; inExpr; i++){ - pOrderBy->a[i].u.x.iOrderByCol = 0; + for(i=iEq; inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iField; + assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 ); + iField = pLoop->aLTerm[i]->u.x.iField - 1; + if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */ + pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); + pOrigRhs->a[iField].pExpr = 0; + if( pOrigLhs ){ + assert( pOrigLhs->a[iField].pExpr!=0 ); + pLhs = sqlite3ExprListAppend(pParse,pLhs,pOrigLhs->a[iField].pExpr); + pOrigLhs->a[iField].pExpr = 0; + } + } + } + sqlite3ExprListDelete(db, pOrigRhs); + if( pOrigLhs ){ + sqlite3ExprListDelete(db, pOrigLhs); + pNew->pLeft->x.pList = pLhs; + } + pSelect->pEList = pRhs; + if( pLhs && pLhs->nExpr==1 ){ + /* Take care here not to generate a TK_VECTOR containing only a + ** single value. Since the parser never creates such a vector, some + ** of the subroutines do not handle this case. */ + Expr *p = pLhs->a[0].pExpr; + pLhs->a[0].pExpr = 0; + sqlite3ExprDelete(db, pNew->pLeft); + pNew->pLeft = p; + } + if( pSelect->pOrderBy ){ + /* If the SELECT statement has an ORDER BY clause, zero the + ** iOrderByCol variables. These are set to non-zero when an + ** ORDER BY term exactly matches one of the terms of the + ** result-set. Since the result-set of the SELECT statement may + ** have been modified or reordered, these variables are no longer + ** set correctly. Since setting them is just an optimization, + ** it's easiest just to zero them here. */ + ExprList *pOrderBy = pSelect->pOrderBy; + for(i=0; inExpr; i++){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } } - } #if 0 - printf("For indexing, change the IN expr:\n"); - sqlite3TreeViewExpr(0, pX, 0); - printf("Into:\n"); - sqlite3TreeViewExpr(0, pNew, 0); + printf("For indexing, change the IN expr:\n"); + sqlite3TreeViewExpr(0, pX, 0); + printf("Into:\n"); + sqlite3TreeViewExpr(0, pNew, 0); #endif + } } return pNew; } @@ -1346,13 +1368,15 @@ Bitmask sqlite3WhereCodeOneLoopStart( pLevel->notReady = notReady & ~sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); bRev = (pWInfo->revMask>>iLevel)&1; VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName)); -#if WHERETRACE_ENABLED /* 0x20800 */ - if( sqlite3WhereTrace & 0x800 ){ +#if WHERETRACE_ENABLED /* 0x4001 */ + if( sqlite3WhereTrace & 0x1 ){ sqlite3DebugPrintf("Coding level %d of %d: notReady=%llx iFrom=%d\n", iLevel, pWInfo->nLevel, (u64)notReady, pLevel->iFrom); - sqlite3WhereLoopPrint(pLoop, pWC); + if( sqlite3WhereTrace & 0x1000 ){ + sqlite3WhereLoopPrint(pLoop, pWC); + } } - if( sqlite3WhereTrace & 0x20000 ){ + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ if( iLevel==0 ){ sqlite3DebugPrintf("WHERE clause being coded:\n"); sqlite3TreeViewExpr(0, pWInfo->pWhere, 0); @@ -2276,7 +2300,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( } /* Loop through table entries that match term pOrTerm. */ ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1)); - WHERETRACE(0xffff, ("Subplan for OR-clause:\n")); + WHERETRACE(0xffffffff, ("Subplan for OR-clause:\n")); pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, 0, WHERE_OR_SUBCLAUSE, iCovCur); assert( pSubWInfo || pParse->nErr ); @@ -2513,12 +2537,12 @@ Bitmask sqlite3WhereCodeOneLoopStart( } #endif } -#ifdef WHERETRACE_ENABLED /* 0xffff */ +#ifdef WHERETRACE_ENABLED /* 0xffffffff */ if( sqlite3WhereTrace ){ VdbeNoopComment((v, "WhereTerm[%d] (%p) priority=%d", pWC->nTerm-j, pTerm, iLoop)); } - if( sqlite3WhereTrace & 0x800 ){ + if( sqlite3WhereTrace & 0x4000 ){ sqlite3DebugPrintf("Coding auxiliary constraint:\n"); sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); } @@ -2547,8 +2571,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( pTerm->leftCursor!=iCur ) continue; if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue; pE = pTerm->pExpr; -#ifdef WHERETRACE_ENABLED /* 0x800 */ - if( sqlite3WhereTrace & 0x800 ){ +#ifdef WHERETRACE_ENABLED /* 0x4001 */ + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ sqlite3DebugPrintf("Coding transitive constraint:\n"); sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); } @@ -2663,13 +2687,13 @@ Bitmask sqlite3WhereCodeOneLoopStart( } } -#if WHERETRACE_ENABLED /* 0x20800 */ - if( sqlite3WhereTrace & 0x20000 ){ +#if WHERETRACE_ENABLED /* 0x4001 */ + if( sqlite3WhereTrace & 0x4000 ){ sqlite3DebugPrintf("All WHERE-clause terms after coding level %d:\n", iLevel); sqlite3WhereClausePrint(pWC); } - if( sqlite3WhereTrace & 0x800 ){ + if( sqlite3WhereTrace & 0x1 ){ sqlite3DebugPrintf("End Coding level %d: notReady=%llx\n", iLevel, (u64)pLevel->notReady); } diff --git a/src/whereexpr.c b/src/whereexpr.c index 24246c1aa..4f86753bf 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -974,36 +974,40 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ */ static SQLITE_NOINLINE int exprMightBeIndexed2( SrcList *pFrom, /* The FROM clause */ - Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */ int *aiCurCol, /* Write the referenced table cursor and column here */ - Expr *pExpr /* An operand of a comparison operator */ + Expr *pExpr, /* An operand of a comparison operator */ + int j /* Start looking with the j-th pFrom entry */ ){ Index *pIdx; int i; int iCur; - for(i=0; mPrereq>1; i++, mPrereq>>=1){} - iCur = pFrom->a[i].iCursor; - for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->aColExpr==0 ) continue; - for(i=0; inKeyCol; i++){ - if( pIdx->aiColumn[i]!=XN_EXPR ) continue; - assert( pIdx->bHasExpr ); - if( sqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ - aiCurCol[0] = iCur; - aiCurCol[1] = XN_EXPR; - return 1; + do{ + iCur = pFrom->a[j].iCursor; + for(pIdx=pFrom->a[j].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->aColExpr==0 ) continue; + for(i=0; inKeyCol; i++){ + if( pIdx->aiColumn[i]!=XN_EXPR ) continue; + assert( pIdx->bHasExpr ); + if( sqlite3ExprCompareSkip(pExpr,pIdx->aColExpr->a[i].pExpr,iCur)==0 + && pExpr->op!=TK_STRING + ){ + aiCurCol[0] = iCur; + aiCurCol[1] = XN_EXPR; + return 1; + } } } - } + }while( ++j < pFrom->nSrc ); return 0; } static int exprMightBeIndexed( SrcList *pFrom, /* The FROM clause */ - Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */ int *aiCurCol, /* Write the referenced table cursor & column here */ Expr *pExpr, /* An operand of a comparison operator */ int op /* The specific comparison operator */ ){ + int i; + /* If this expression is a vector to the left or right of a ** inequality constraint (>, <, >= or <=), perform the processing ** on the first element of the vector. */ @@ -1013,7 +1017,6 @@ static int exprMightBeIndexed( if( pExpr->op==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ assert( ExprUseXList(pExpr) ); pExpr = pExpr->x.pList->a[0].pExpr; - } if( pExpr->op==TK_COLUMN ){ @@ -1021,9 +1024,16 @@ static int exprMightBeIndexed( aiCurCol[1] = pExpr->iColumn; return 1; } - if( mPrereq==0 ) return 0; /* No table references */ - if( (mPrereq&(mPrereq-1))!=0 ) return 0; /* Refs more than one table */ - return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr); + + for(i=0; inSrc; i++){ + Index *pIdx; + for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->aColExpr ){ + return exprMightBeIndexed2(pFrom,aiCurCol,pExpr,i); + } + } + } + return 0; } @@ -1149,7 +1159,7 @@ static void exprAnalyze( pLeft = pLeft->x.pList->a[pTerm->u.x.iField-1].pExpr; } - if( exprMightBeIndexed(pSrc, prereqLeft, aiCurCol, pLeft, op) ){ + if( exprMightBeIndexed(pSrc, aiCurCol, pLeft, op) ){ pTerm->leftCursor = aiCurCol[0]; assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pTerm->u.x.leftColumn = aiCurCol[1]; @@ -1157,7 +1167,7 @@ static void exprAnalyze( } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; if( pRight - && exprMightBeIndexed(pSrc, pTerm->prereqRight, aiCurCol, pRight, op) + && exprMightBeIndexed(pSrc, aiCurCol, pRight, op) && !ExprHasProperty(pRight, EP_FixedCol) ){ WhereTerm *pNew; @@ -1368,7 +1378,6 @@ static void exprAnalyze( transferJoinMarkings(pNewExpr1, pExpr); idxNew1 = whereClauseInsert(pWC, pNewExpr1, wtFlags); testcase( idxNew1==0 ); - exprAnalyze(pSrc, pWC, idxNew1); pNewExpr2 = sqlite3ExprDup(db, pLeft, 0); pNewExpr2 = sqlite3PExpr(pParse, TK_LT, sqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName), @@ -1376,6 +1385,7 @@ static void exprAnalyze( transferJoinMarkings(pNewExpr2, pExpr); idxNew2 = whereClauseInsert(pWC, pNewExpr2, wtFlags); testcase( idxNew2==0 ); + exprAnalyze(pSrc, pWC, idxNew1); exprAnalyze(pSrc, pWC, idxNew2); pTerm = &pWC->a[idxTerm]; if( isComplete ){ @@ -1432,7 +1442,7 @@ static void exprAnalyze( && pTerm->u.x.iField==0 && pExpr->pLeft->op==TK_VECTOR && ALWAYS( ExprUseXSelect(pExpr) ) - && pExpr->x.pSelect->pPrior==0 + && (pExpr->x.pSelect->pPrior==0 || (pExpr->x.pSelect->selFlags & SF_Values)) #ifndef SQLITE_OMIT_WINDOWFUNC && pExpr->x.pSelect->pWin==0 #endif @@ -1620,6 +1630,13 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ assert( pWC->a[ii].eOperator==WO_ROWVAL ); continue; } + if( pWC->a[ii].nChild ){ + /* If this term has child terms, then they are also part of the + ** pWC->a[] array. So this term can be ignored, as a LIMIT clause + ** will only be added if each of the child terms passes the + ** (leftCursor==iCsr) test below. */ + continue; + } if( pWC->a[ii].leftCursor!=iCsr ) return; } diff --git a/src/window.c b/src/window.c index f13ea8b02..56de38ba3 100644 --- a/src/window.c +++ b/src/window.c @@ -1069,7 +1069,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ pSub = sqlite3SelectNew( pParse, pSublist, pSrc, pWhere, pGroupBy, pHaving, pSort, 0, 0 ); - SELECTTRACE(1,pParse,pSub, + TREETRACE(0x40,pParse,pSub, ("New window-function subquery in FROM clause of (%u/%p)\n", p->selId, p)); p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); @@ -1079,6 +1079,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ if( p->pSrc ){ Table *pTab2; p->pSrc->a[0].pSelect = pSub; + p->pSrc->a[0].fg.isCorrelated = 1; sqlite3SrcListAssignCursors(pParse, p->pSrc); pSub->selFlags |= SF_Expanded|SF_OrderByReqd; pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE); @@ -2944,8 +2945,7 @@ void sqlite3WindowCodeStep( VdbeCoverageNeverNullIf(v, op==OP_Ge); /* NeverNull because bound */ VdbeCoverageNeverNullIf(v, op==OP_Le); /* values previously checked */ windowAggFinal(&s, 0); - sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); windowReturnOneRow(&s); sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr); sqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd); @@ -2957,13 +2957,10 @@ void sqlite3WindowCodeStep( } if( pMWin->eStart!=TK_UNBOUNDED ){ - sqlite3VdbeAddOp2(v, OP_Rewind, s.start.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.start.csr); } - sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1); - VdbeCoverageNeverTaken(v); - sqlite3VdbeAddOp2(v, OP_Rewind, s.end.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); + sqlite3VdbeAddOp1(v, OP_Rewind, s.end.csr); if( regPeer && pOrderBy ){ sqlite3VdbeAddOp3(v, OP_Copy, regNewPeer, regPeer, pOrderBy->nExpr-1); sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.start.reg, pOrderBy->nExpr-1); diff --git a/test/affinity3.test b/test/affinity3.test index 48942de72..3695ea847 100644 --- a/test/affinity3.test +++ b/test/affinity3.test @@ -104,20 +104,20 @@ do_execsql_test affinity3-200 { CREATE TABLE mzed AS SELECT * FROM idmap; } -#do_execsql_test affinity3-210 { - #PRAGMA automatic_index=ON; - #SELECT * FROM data JOIN idmap USING(id); -#} {1 abc a 4 xyz e} +do_execsql_test affinity3-210 { + PRAGMA automatic_index=ON; + SELECT * FROM data JOIN idmap USING(id); +} {4 xyz e} do_execsql_test affinity3-220 { SELECT * FROM data JOIN mzed USING(id); -} {1 abc a 4 xyz e} +} {4 xyz e} do_execsql_test affinity3-250 { PRAGMA automatic_index=OFF; SELECT * FROM data JOIN idmap USING(id); -} {1 abc a 4 xyz e} +} {4 xyz e} do_execsql_test affinity3-260 { SELECT * FROM data JOIN mzed USING(id); -} {1 abc a 4 xyz e} +} {4 xyz e} finish_test diff --git a/test/attach2.test b/test/attach2.test index eb87b60f6..91e14ee7a 100644 --- a/test/attach2.test +++ b/test/attach2.test @@ -17,6 +17,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix attach2 ifcapable !attach { finish_test @@ -388,4 +389,63 @@ do_test attach2-6.3 { db close +ifcapable utf16 { + forcedelete test.db2 ;# utf-16 + forcedelete test.db3 ;# utf-16 + forcedelete test.db4 ;# utf-8 + + sqlite3 db2 test.db2 + do_execsql_test -db db2 1.1 { + PRAGMA encoding = 'utf16'; + CREATE TABLE t2(x); + INSERT INTO t2 VALUES('text2'); + } + db2 close + + sqlite3 db3 test.db3 + do_execsql_test -db db3 1.2 { + PRAGMA encoding = 'utf16'; + CREATE TABLE t3(x); + INSERT INTO t3 VALUES('text3'); + } + db3 close + + sqlite3 db4 test.db4 + do_execsql_test -db db4 1.3 { + PRAGMA encoding = 'utf8'; + CREATE TABLE t4(x); + INSERT INTO t4 VALUES('text4'); + } + db4 close + + reset_db + do_execsql_test 2.1 { + PRAGMA encoding = 'utf16'; + ATTACH 'test.db2' AS aux; + SELECT * FROM t2; + } {text2} + + reset_db + do_execsql_test 2.2 { + ATTACH 'test.db4' AS aux; + SELECT * FROM t4; + } {text4} + + db close + sqlite3 db test.db2 + do_execsql_test 2.3 { + ATTACH 'test.db3' AS aux; + SELECT * FROM t3; + SELECT * FROM t2; + } {text3 text2} + + db close + sqlite3 db test.db2 + do_catchsql_test 2.4 { + ATTACH 'test.db4' AS aux; + } {1 {attached databases must use the same text encoding as main database}} + + db close +} + finish_test diff --git a/test/auth.test b/test/auth.test index d8afa2dbf..1d56f7034 100644 --- a/test/auth.test +++ b/test/auth.test @@ -2246,6 +2246,21 @@ ifcapable altertable&&vtab { } {main t1 {} {}} } +# 2022-12-28 +# The sqlite3_declare_vtab() call that occurs during pragma_table_list +# should not cause an authentication failure. +# +ifcapable vtab { + do_test auth-1.359 { + proc auth {code arg1 arg2 arg3 arg4 args} { + if {$code=="SQLITE_UPDATE"} { + return SQLITE_DENY + } + return SQLITE_OK + } + catchsql {SELECT * FROM pragma_table_list WHERE name='xyzzy';} + } {0 {}} +} do_test auth-2.1 { proc auth {code arg1 arg2 arg3 arg4 args} { diff --git a/test/basexx1.test b/test/basexx1.test new file mode 100644 index 000000000..947a5678f --- /dev/null +++ b/test/basexx1.test @@ -0,0 +1,155 @@ +# 2022 November 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix basexx + +if {[catch {load_static_extension db basexx} error]} { + puts "Skipping basexx tests, hit load error: $error" + finish_test; return +} + +# Empty blobs encode to empty strings. +do_execsql_test 100 { + SELECT base64(x'')||base85(x''); +} {{}} + +# Empty strings decode to empty blobs. +do_execsql_test 101 { + SELECT hex(x'01'||base64('')||base85('')||x'02'); +} {0102} + +# Basic base64 encoding +do_execsql_test 102 { + SELECT base64(x'000102030405'); + SELECT base64(x'0001020304'); + SELECT base64(x'00010203'); +} {{AAECAwQF +} {AAECAwQ= +} {AAECAw== +}} + +# Basic base64 decoding with pad chars +do_execsql_test 103 { + SELECT hex(base64('AAECAwQF')); + SELECT hex(base64('AAECAwQ=')); + SELECT hex(base64('AAECAw==')); +} {000102030405 0001020304 00010203} + +# Basic base64 decoding without pad chars and with whitespace +do_execsql_test 104 { + SELECT hex(base64(' AAECAwQF ')); + SELECT hex(base64(' AAECAwQ')); + SELECT hex(base64('AAECAw ')); +} {000102030405 0001020304 00010203} + +# Basic base85 encoding +do_execsql_test 105 { + SELECT base85(x'000102030405'); + SELECT base85(x'0001020304'); + SELECT base85(x'00010203'); +} {{##/2,#2/ +} {##/2,#* +} {##/2, +}} + +# Basic base85 decoding with and without whitespace +do_execsql_test 106 { + SELECT hex(base85('##/2,#2/')); + SELECT hex(base85('##/2,#*')); + SELECT hex(base85('##/2,')); + SELECT hex(base85(' ##/2,#2/ ')); + SELECT hex(base85(' ##/2,#*')); + SELECT hex(base85('##/2, ')); +} {000102030405 0001020304 00010203 000102030405 0001020304 00010203} + +# Round-trip some random blobs. +do_execsql_test 107 { + CREATE TEMP TABLE rb( len int, b blob ) STRICT; + INSERT INTO rb(len) VALUES (1),(2),(3),(4),(5),(150),(151),(152),(153),(1054); + UPDATE rb SET b = randomblob(len); + SELECT len, base64(base64(b))=b, base85(base85(b))=b + FROM rb ORDER BY len; +} {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1} + +# Same round-trip but with space or junk prepended and/or appended or not. +do_execsql_test 108 { + CREATE TEMP TABLE junk(j text, rank int); + INSERT INTO junk VALUES ('',0),(' ',1),('~',2); + SELECT len, base64(j.j||base64(b)||j.j)=b, base85(j.j||base85(b)||j.j)=b + FROM rb r, junk j WHERE j.rank=(r.len+r.len/25)%3 ORDER BY len; +} {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1} + +# Exercise the fail-on-too-large result feature. + +set inLimit [sqlite3_limit db SQLITE_LIMIT_LENGTH -1] +sqlite3_limit db SQLITE_LIMIT_LENGTH 1300 + +do_catchsql_test 109 { + SELECT len, base64(b) FROM rb WHERE len>200; +} {1 {blob expanded to base64 too big}} + +do_catchsql_test 110 { + SELECT len, base85(b) FROM rb WHERE len>200; +} {1 {blob expanded to base85 too big}} + +do_catchsql_test 111 { + SELECT length(base85(b))=1335 FROM rb WHERE len=1054; +} {1 {blob expanded to base85 too big}} + +sqlite3_limit db SQLITE_LIMIT_LENGTH $inLimit + +# Exercise is_base85(t) + +do_execsql_test 112 { + SELECT is_base85(' '||base85(x'123456')||char(10)), + is_base85('#$%&*+,-./0123456789:;<=>?@' + ||'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + ||'[\]^_`' + ||'abcdefghijklmnopqrstuvwxyz'), + is_base85('!'), is_base85('"'), is_base85(''''), is_base85('('), + is_base85(')'), is_base85(char(123)), is_base85('|'), is_base85(char(125)), + is_base85('~'), is_base85(char(127)); +} {1 1 0 0 0 0 0 0 0 0 0 0} + +do_execsql_test 113 { + SELECT is_base85(NULL) IS NULL; +} {1} + +do_catchsql_test 114 { + SELECT is_base85(1); +} {1 {is_base85 accepts only text or NULL}} + +do_catchsql_test 115 { + SELECT is_base85(1.1); +} {1 {is_base85 accepts only text or NULL}} + +do_catchsql_test 116 { + SELECT is_base85(x'00'); +} {1 {is_base85 accepts only text or NULL}} + +# Round-trip many bigger random blobs. + +do_execsql_test 117 { + CREATE TABLE bs(b blob, num); + INSERT INTO bs SELECT randomblob(4000 + n%3), n + FROM ( + WITH RECURSIVE seq(n) AS ( + VALUES(1) UNION ALL SELECT n+1 + FROM seq WHERE n<100 + ) SELECT n FROM seq); + SELECT num FROM bs WHERE base64(base64(b))!=b; + SELECT num FROM bs WHERE base85(base85(b))!=b; +} {} + +finish_test diff --git a/test/bestindexA.test b/test/bestindexA.test new file mode 100644 index 000000000..650404eaa --- /dev/null +++ b/test/bestindexA.test @@ -0,0 +1,138 @@ +# 2020-01-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 +set testprefix bestindexA + +ifcapable !vtab { + finish_test + return +} + + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE x(a, b, c)" + } + + xBestIndex { + set hdl [lindex $args 0] + set clist [$hdl constraints] + foreach c $clist { + array set C $c + lappend ::vtab_constraints [list $C(op) $C(column)] + } + return [list] + } + + xFilter { + return "" + } + + xFindFunction { + foreach {nArg name} $args {} + if {$nArg==2 && $name=="even"} { + return 152 + } + return 0 + } + + } + + return {} +} + +register_tcl_module db +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING tcl(vtab_command); +} + +proc do_xbestindex_test {tn sql res} { + set script [subst { + execsql "$sql" + set ::vtab_constraints + }] + + uplevel [list do_test $tn $script [list {*}$res]] + set ::vtab_constraints [list] +} + +do_xbestindex_test 1.1 { + SELECT * FROM t1 WHERE a=? +} { + {eq 0} +} + +do_xbestindex_test 1.2 { + SELECT * FROM t1 WHERE a=? LIMIT 10 +} { + {eq 0} + {limit 0} +} + +do_xbestindex_test 1.3 { + SELECT * FROM t1 WHERE a=? AND (b+1)=? LIMIT 10 +} { + {eq 0} +} + +proc error_function {args} { error "not a function!" } +db function even error_function + +do_xbestindex_test 1.4 { + SELECT * FROM t1 WHERE even(a, ?) +} { + {152 0} +} + +do_xbestindex_test 1.5 { + SELECT * FROM t1 WHERE b=10 AND even(a, ?) +} { + {eq 1} + {152 0} +} + +do_xbestindex_test 1.6 { + SELECT * FROM t1 WHERE b=10 LIMIT 10 +} { + {eq 1} + {limit 0} +} + +do_xbestindex_test 1.7 { + SELECT * FROM t1 WHERE even(b,?) LIMIT 10 +} { + {152 1} + {limit 0} +} + +do_xbestindex_test 1.8 { + SELECT * FROM t1 WHERE b!=? LIMIT 10 +} { + {ne 1} + {limit 0} +} + +do_xbestindex_test 1.9 { + SELECT * FROM t1 WHERE ?=a LIMIT 10 +} { + {eq 0} + {limit 0} +} + + +finish_test + + + diff --git a/test/bloom1.test b/test/bloom1.test index 12b53ddf1..1846e4d63 100644 --- a/test/bloom1.test +++ b/test/bloom1.test @@ -66,5 +66,39 @@ do_execsql_test 1.5 { AND t3.e=t1.b AND t3.f!='silly' } {result} -finish_test +# 2023-02-05 +# https://sqlite.org/forum/forumpost/56de336385 +# +# Do not employ a Bloom filter if the table being filtered or any table +# wo the left of the table being filtered lacks STAT1 data, since we +# cannot make a good Bloom filter usefulness determination without STAT1 +# data. +# +reset_db +do_execsql_test 2.0 { + CREATE TABLE objs(c INTEGER, s INTEGER, p INTEGER, o INTEGER); + CREATE UNIQUE INDEX objs_cspo ON objs(o,p,c,s); + ANALYZE; + DELETE FROM sqlite_stat1; + INSERT INTO sqlite_stat1 VALUES('objs','objs_cspo','520138 21 20 19 1'); + ANALYZE sqlite_schema; +} +do_eqp_test 2.1 { + WITH RECURSIVE transit(x) AS ( + SELECT s FROM objs WHERE p=9 AND o=32805 + UNION + SELECT objs.s FROM objs, transit WHERE objs.p=9 AND objs.o=transit.x + ) + SELECT x FROM transit; +} { + QUERY PLAN + |--CO-ROUTINE transit + | |--SETUP + | | `--SEARCH objs USING COVERING INDEX objs_cspo (o=? AND p=?) + | `--RECURSIVE STEP + | |--SCAN transit + | `--SEARCH objs USING COVERING INDEX objs_cspo (o=? AND p=?) + `--SCAN transit +} +finish_test diff --git a/test/capi3c.test b/test/capi3c.test index 889ecbe85..247dbf35e 100644 --- a/test/capi3c.test +++ b/test/capi3c.test @@ -1269,6 +1269,7 @@ ifcapable progress { sqlite3_finalize $STMT } {SQLITE_INTERRUPT} do_test capi3c-21.4 { + db progress set STMT [sqlite3_prepare $DB {SELECT * FROM t3} -1 TAIL] db progress 5 "expr 1" sqlite3_step $STMT diff --git a/test/carray01.test b/test/carray01.test index d6243eb27..1af9cb66e 100644 --- a/test/carray01.test +++ b/test/carray01.test @@ -23,110 +23,137 @@ ifcapable !vtab { load_static_extension db carray # Parameter $stmt must be a prepared statement created using -# the sqlite3_prepare_v2 command and with parameters fullly bound. +# the sqlite3_prepare_v2 command and with parameters fully bound. # This routine simply runs the statement, gathers the result, and # returns a list containing the result. # # If the optional second argument is true, then the stmt is finalized # after it is run. # -proc run_stmt {stmt {finalizeFlag 0}} { +proc run_stmt {stmt} { set r {} while {[sqlite3_step $stmt]=="SQLITE_ROW"} { for {set i 0} {$i<[sqlite3_data_count $stmt]} {incr i} { lappend r [sqlite3_column_text $stmt $i] } } - if {$finalizeFlag} { - sqlite3_finalize $stmt - } else { - sqlite3_reset $stmt - } + sqlite3_reset $stmt return $r } do_test 100 { set STMT [sqlite3_prepare_v2 db {SELECT 5 IN carray(?3)} -1] sqlite3_carray_bind $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 101 { sqlite3_carray_bind -static $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} +do_test 102 { + set STMT2 [sqlite3_prepare_v2 db { + SELECT DISTINCT typeof(value) FROM carray(?3)} -1] + sqlite3_carray_bind $STMT2 3 1 2 3 4 5 6 7 + run_stmt $STMT2 +} {integer} do_test 110 { sqlite3_carray_bind $STMT 3 1 2 3 4 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {0} do_test 120 { sqlite3_carray_bind -int64 $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} +do_test 120b { + sqlite3_carray_bind -int64 $STMT2 3 1 2 3 4 5 6 7 + run_stmt $STMT2 +} {integer} do_test 121 { sqlite3_carray_bind -int64 -transient $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 122 { sqlite3_carray_bind -int64 -static $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 123 { sqlite3_carray_bind -int32 -transient $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 124 { sqlite3_carray_bind -int32 -static $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 125 { sqlite3_carray_bind -int32 $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 130 { sqlite3_carray_bind -int64 $STMT 3 1 2 3 4 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {0} do_test 131 { sqlite3_carray_bind -int64 -transient $STMT 3 1 2 3 4 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {0} do_test 131 { sqlite3_carray_bind -int64 -static $STMT 3 1 2 3 4 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {0} do_test 140 { sqlite3_carray_bind -double $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 141 { sqlite3_carray_bind -double -transient $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 142 { sqlite3_carray_bind -double -static $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} +do_test 143 { + sqlite3_carray_bind -double $STMT2 3 1 2 3 4 5 6 7 + run_stmt $STMT2 +} {real} do_test 150 { sqlite3_carray_bind -double $STMT 3 1 2 3 4 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {0} do_test 160 { sqlite3_carray_bind -double $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {1} do_test 170 { sqlite3_carray_bind -text -static $STMT 3 1 2 3 4 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {0} +do_test 171 { + sqlite3_carray_bind -text -static $STMT2 3 1 2 3 4 6 7 + run_stmt $STMT2 +} {text} do_test 180 { sqlite3_carray_bind -text -transient $STMT 3 1 2 3 4 5 6 7 - run_stmt $STMT 0 + run_stmt $STMT } {0} do_test 190 { + sqlite3_carray_bind -blob -static $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT +} {0} +do_test 191 { + sqlite3_carray_bind -blob -static $STMT2 3 1 2 3 4 5 6 7 + run_stmt $STMT2 +} {blob} +do_test 200 { + sqlite3_carray_bind -blob -transient $STMT 3 1 2 3 4 5 6 7 + run_stmt $STMT +} {0} +do_test 300 { sqlite3_carray_bind $STMT 3 - run_stmt $STMT 0 + run_stmt $STMT } {0} sqlite3_finalize $STMT +sqlite3_finalize $STMT2 finish_test diff --git a/test/cast.test b/test/cast.test index cbeec47c9..ebfea5aa0 100644 --- a/test/cast.test +++ b/test/cast.test @@ -481,7 +481,80 @@ do_execsql_test cast-9.0 { CREATE VIEW v1(c0, c1) AS SELECT CAST(0.0 AS NUMERIC), COUNT(*) OVER () FROM t0; SELECT v1.c0 FROM v1, t0 WHERE v1.c0=0; -} {0} +} {0.0} + +# Set the 2022-12-10 "reopen" of ticket [https://sqlite.org/src/tktview/57c47526c3] +# +do_execsql_test cast-9.1 { + CREATE TABLE dual(dummy TEXT); + INSERT INTO dual VALUES('X'); + SELECT CAST(4 AS NUMERIC); +} {4} +do_execsql_test cast-9.2 { + SELECT CAST(4.0 AS NUMERIC); +} {4.0} +do_execsql_test cast-9.3 { + SELECT CAST(4.5 AS NUMERIC); +} {4.5} +do_execsql_test cast-9.4 { + SELECT x, typeof(x) FROM (SELECT CAST(4 AS NUMERIC) AS x) JOIN dual; +} {4 integer} +do_execsql_test cast-9.5 { + SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4 AS NUMERIC) AS x); +} {4 integer} +do_execsql_test cast-9.10 { + SELECT x, typeof(x) FROM (SELECT CAST(4.0 AS NUMERIC) AS x) JOIN dual; +} {4.0 real} +do_execsql_test cast-9.11 { + SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4.0 AS NUMERIC) AS x); +} {4.0 real} +do_execsql_test cast-9.12 { + SELECT x, typeof(x) FROM (SELECT CAST(4.5 AS NUMERIC) AS x) JOIN dual; +} {4.5 real} +do_execsql_test cast-9.13 { + SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4.5 AS NUMERIC) AS x); +} {4.5 real} +# 2022-12-15 dbsqlfuzz c9ee6f9a0a8b8fefb02cf69de2a8b67ca39525c8 +# +# Added a new SQLITE_AFF_FLEXNUM that does not try to convert int to real or +# real to int. +# +do_execsql_test cast-10.1 { + VALUES(CAST(44 AS REAL)),(55); +} {44.0 55} +do_execsql_test cast-10.2 { + SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55; +} {44.0 55} +do_execsql_test cast-10.3 { + SELECT * FROM (VALUES(CAST(44 AS REAL)),(55)); +} {44.0 55} +do_execsql_test cast-10.4 { + SELECT * FROM (SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55); +} {44.0 55} +do_execsql_test cast-10.5 { + SELECT * FROM dual CROSS JOIN (VALUES(CAST(44 AS REAL)),(55)); +} {X 44.0 X 55} +do_execsql_test cast-10.6 { + SELECT * FROM dual CROSS JOIN (SELECT CAST(44 AS REAL) AS 'm' + UNION ALL SELECT 55); +} {X 44.0 X 55} +ifcapable vtab { + do_execsql_test cast-10.7 { + DROP VIEW v1; + CREATE VIEW v1 AS SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55; + SELECT name, type FROM pragma_table_info('v1'); + } {m NUM} + do_execsql_test cast-10.8 { + CREATE VIEW v2 AS VALUES(CAST(44 AS REAL)),(55); + SELECT type FROM pragma_table_info('v2'); + } {NUM} + do_execsql_test cast-10.9 { + SELECT * FROM v1; + } {44.0 55} + do_execsql_test cast-10.10 { + SELECT * FROM v2; + } {44.0 55} +} finish_test diff --git a/test/conflict.test b/test/conflict.test index e0d0c87ec..ba3ad9b0d 100644 --- a/test/conflict.test +++ b/test/conflict.test @@ -857,4 +857,35 @@ do_execsql_test conflict-15.30 { SELECT * FROM t1; } {1} +# 2023-01-16 https://sqlite.org/forum/forumpost/aa580a5af38a58e3 +# The parser accepts an ON CONFLICT clause on table CHECK constraints +# but not on column CHECK constraints. But the CHECK constraint is +# ignored. It has been like this since version 3.0.0. +# +# There might be applications and/or databases in the wild that have +# table CHECK constraints with ON CONFLICT clauses. In as much as this +# is a harmless quirk, we continue to support it in order to avoid +# breaking those legacy applications and databases. +# +reset_db +do_catchsql_test conflict-16.1 { + -- ON CONFLICT clauses are not allowed on column CHECK constraints + CREATE TABLE t1(a INT CHECK( a!=5 ) ON CONFLICT ignore); +} {1 {near "ON": syntax error}} +do_execsql_test conflict-16.2 { + -- ON CONFLICT is allowed on table CHECK constraints + CREATE TABLE t1(a INT, CHECK( a!=5 ) ON CONFLICT ignore); +} {} +do_catchsql_test conflict-16.3 { + -- The ON CONFLICT clause is in-op + INSERT INTO t1(a) VALUES(4),(5),(6); +} {1 {CHECK constraint failed: a!=5}} +do_execsql_test conflict-16.4 { + SELECT a FROM t1 ORDER BY a; +} {} +do_execsql_test conflict-16.5 { + INSERT OR IGNORE INTO t1(a) VALUES(4),(5),(6); + SELECT a FROM t1 ORDER BY a; +} {4 6} + finish_test diff --git a/test/dbpagefault.test b/test/dbpagefault.test index b41ac6623..544d279ce 100644 --- a/test/dbpagefault.test +++ b/test/dbpagefault.test @@ -46,11 +46,11 @@ do_faultsim_test 2 -prep { } -body { execsql { CREATE VIRTUAL TABLE t1 USING sqlite_dbpage(); - INSERT INTO t1 DEFAULT VALUES; + UPDATE t1 SET data=zeroblob(1024) WHERE pgno=1 AND schema='aux'; } } -test { execsql { PRAGMA journal_mode = off } - faultsim_test_result {1 {no such schema}} {1 {SQL logic error}} + faultsim_test_result {0 {}} {1 {no such schema}} {1 {SQL logic error}} {1 {unable to open a temporary database file for storing temporary tables}} } reset_db diff --git a/test/distinctagg.test b/test/distinctagg.test index a34312ef9..6e46c8861 100644 --- a/test/distinctagg.test +++ b/test/distinctagg.test @@ -184,6 +184,7 @@ foreach {tn use_t3 sql res} { 10 1 "SELECT count(DISTINCT x) FROM t1 LEFT JOIN t3 ON (t3.x=0)" 0 } { + unset -nocomplain a do_test 5.$tn.1 { set bUse 0 db eval "EXPLAIN $sql" a { diff --git a/test/e_createtable.test b/test/e_createtable.test index 3ecffae5c..92ccc8032 100644 --- a/test/e_createtable.test +++ b/test/e_createtable.test @@ -1514,9 +1514,10 @@ do_createtable_tests 4.14 -error {NOT NULL constraint failed: %s} { 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. +# EVIDENCE-OF: R-34093-09213 PRIMARY KEY, UNIQUE and NOT NULL +# constraints may be explicitly assigned another default conflict +# resolution algorithm by including a conflict-clause in their +# definitions. # # Conflict clauses: ABORT, ROLLBACK, IGNORE, FAIL, REPLACE # @@ -1631,9 +1632,9 @@ foreach {tn tbl res ac 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. +# EVIDENCE-OF: R-17539-59899 Or, if a constraint definition does not +# include a conflict-clause, 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 diff --git a/test/e_expr.test b/test/e_expr.test index 3f8d5ad85..5ad5993bb 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -215,7 +215,7 @@ foreach {tn literal type} { # # EVIDENCE-OF: R-03679-60639 Equals can be either = or ==. # -# EVIDENCE-OF: R-30082-38996 The non-equals operator can be either != or +# EVIDENCE-OF: R-49372-18364 The not-equal operator can be either != or # <>. # foreach {tn literal different} { @@ -1892,9 +1892,10 @@ 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-22239-33740 A subquery that returns two or more columns -# is a row value subquery and can only be used as the operand of a -# comparison operator. +# EVIDENCE-OF: R-43101-20178 A subquery that returns two or more columns +# is a row value subquery and can only be used as an operand of a +# comparison operator or as the value in an UPDATE SET clause whose +# column name list has the same size. # # The following block tests that errors are returned in a bunch of cases # where a subquery returns more than one column. diff --git a/test/eqp.test b/test/eqp.test index eda95776f..61fd617d8 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -94,7 +94,7 @@ do_eqp_test 1.7.1 { SELECT * FROM t3 JOIN (SELECT 1) } { QUERY PLAN - |--MATERIALIZE (subquery-xxxxxx) + |--CO-ROUTINE (subquery-xxxxxx) | `--SCAN CONSTANT ROW |--SCAN (subquery-xxxxxx) `--SCAN t3 @@ -103,7 +103,7 @@ do_eqp_test 1.7.2 { SELECT * FROM t3 JOIN (SELECT 1) AS v1 } { QUERY PLAN - |--MATERIALIZE v1 + |--CO-ROUTINE v1 | `--SCAN CONSTANT ROW |--SCAN v1 `--SCAN t3 @@ -112,7 +112,7 @@ do_eqp_test 1.7.3 { SELECT * FROM t3 AS xx JOIN (SELECT 1) AS yy } { QUERY PLAN - |--MATERIALIZE yy + |--CO-ROUTINE yy | `--SCAN CONSTANT ROW |--SCAN yy `--SCAN xx @@ -123,7 +123,7 @@ do_eqp_test 1.8 { SELECT * FROM t3 JOIN (SELECT 1 UNION SELECT 2) } { QUERY PLAN - |--MATERIALIZE (subquery-xxxxxx) + |--CO-ROUTINE (subquery-xxxxxx) | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW @@ -136,7 +136,7 @@ do_eqp_test 1.9 { SELECT * FROM t3 JOIN (SELECT 1 EXCEPT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN - |--MATERIALIZE abc + |--CO-ROUTINE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW @@ -149,7 +149,7 @@ do_eqp_test 1.10 { SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN - |--MATERIALIZE abc + |--CO-ROUTINE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW @@ -163,7 +163,7 @@ do_eqp_test 1.11 { SELECT * FROM t3 JOIN (SELECT 1 UNION ALL SELECT a FROM t3 LIMIT 17) abc } { QUERY PLAN - |--MATERIALIZE abc + |--CO-ROUTINE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW @@ -295,7 +295,7 @@ det 3.2.2 { ORDER BY x2.y LIMIT 5 } { QUERY PLAN - |--MATERIALIZE x1 + |--CO-ROUTINE x1 | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY |--MATERIALIZE x2 @@ -834,7 +834,7 @@ do_eqp_test 9.1 { ORDER BY 1; } { QUERY PLAN - |--MATERIALIZE thread + |--CO-ROUTINE thread | |--SCAN x USING INDEX forumthread | |--USING ROWID SEARCH ON TABLE private FOR IN-OPERATOR | |--CORRELATED SCALAR SUBQUERY xxxxxx diff --git a/test/exprfault.test b/test/exprfault.test index 9e5977e1d..f0abe9ac3 100644 --- a/test/exprfault.test +++ b/test/exprfault.test @@ -31,5 +31,15 @@ do_faultsim_test 1.1 -faults oom* -prep { faultsim_test_result {0 {}} } +do_faultsim_test 2 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + SELECT hex ( unhex('ABCDEF') ); + } +} -test { + faultsim_test_result {0 ABCDEF} +} + finish_test diff --git a/test/filter1.test b/test/filter1.test index 7b2cf9cc3..bb36e179b 100644 --- a/test/filter1.test +++ b/test/filter1.test @@ -220,6 +220,18 @@ do_execsql_test 7.1 { 444 {} 120000 } - +# 2023-02-17 dbsqlfuzz 4f8e0de6e272bbbb3e1b41cb5aea31e0b47297e3 +# count() with FILTER clause using the COUNTOFVIEW optimization. +# +reset_db +do_execsql_test 8.0 { + CREATE TABLE t0(c0 INT); + CREATE TABLE t1a(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1a VALUES(1,'one'),(2,NULL),(3,'three'); + CREATE TABLE t1b(c INTEGER PRIMARY KEY, d TEXT); + INSERT INTO t1b VALUES(4,'four'),(5,NULL),(6,'six'); + CREATE VIEW t1 AS SELECT a, b FROM t1a UNION ALL SELECT c, d FROM t1b; + SELECT count()FILTER(WHERE b IS NULL) FROM t1; +} 2 finish_test diff --git a/test/fts3defer2.test b/test/fts3defer2.test index 46b62b901..51d2afc49 100644 --- a/test/fts3defer2.test +++ b/test/fts3defer2.test @@ -161,5 +161,33 @@ foreach {tn sql} { } {1 {1 1 1 4 4 11 912 6} 3 {1 1 1 4 4 11 912 6}} } +do_execsql_test 2.5 { + INSERT INTO t3(t3) VALUES('rebuild'); +} +do_execsql_test 2.6 { + SELECT rowid, length(offsets(t3)) FROM t3 WHERE t3 MATCH '(a NEAR a)'; +} {11 228929} +do_execsql_test 2.7 { + SELECT rowid, length(offsets(t3)) FROM t3 WHERE t3 MATCH '(a NEAR b NEAR a)'; +} {1 23 3 23 11 205} +do_execsql_test 2.8 { + SELECT rowid, length(offsets(t3)) FROM t3 WHERE t3 MATCH '(a NEAR b)'; +} {1 15 3 15 11 106} + +do_execsql_test 2.9 { + SELECT rowid, length(matchinfo(t3)) FROM t3 WHERE t3 MATCH '(a NEAR a)'; +} {11 32} +do_execsql_test 2.10 { + SELECT rowid, length(matchinfo(t3)) FROM t3 WHERE t3 MATCH '(a NEAR b NEAR a)' +} {1 44 3 44 11 44} +do_execsql_test 2.11 { + SELECT rowid, length(matchinfo(t3)) FROM t3 WHERE t3 MATCH '(a NEAR b)'; +} {1 32 3 32 11 32} + +do_execsql_test 2.12 { + SELECT rowid, length(matchinfo(t3)) FROM t3 + WHERE t3 MATCH '(a NEAR b NEAR a NEAR b NEAR a)' +} {1 68 3 68 11 68} + finish_test diff --git a/test/fts3expr4.test b/test/fts3expr4.test index 4a6bd293c..b9227aef5 100644 --- a/test/fts3expr4.test +++ b/test/fts3expr4.test @@ -50,7 +50,16 @@ do_icu_expr_test 1.6 { "(x OR y)" } {PHRASE 3 0 ( x or y )} # is passed to the tokenizer. # do_icu_expr_test 1.7 {a:word} {PHRASE 0 0 word} -do_icu_expr_test 1.8 {d:word} {PHRASE 3 0 d:word} +# do_icu_expr_test 1.8 {d:word} {PHRASE 3 0 d:word} +do_test 1.8 { + set res [ + db one {SELECT fts3_exprtest('icu en_US', 'd:word', 'a', 'b', 'c')} + ] + expr { + $res=="PHRASE 3 0 d:word" || + $res=="AND {AND {PHRASE 3 0 d} {PHRASE 3 0 :}} {PHRASE 3 0 word}" + } +} 1 set sqlite_fts3_enable_parentheses 0 diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index a0bcc3bf0..ee7749d96 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -1963,13 +1963,13 @@ int main(int argc, char **argv){ quietFlag = 0; verboseFlag++; eVerbosity++; - if( verboseFlag>1 ) runFlags |= SQL_TRACE; + if( verboseFlag>2 ) runFlags |= SQL_TRACE; }else if( (nV = numberOfVChar(z))>=1 ){ quietFlag = 0; verboseFlag += nV; eVerbosity += nV; - if( verboseFlag>1 ) runFlags |= SQL_TRACE; + if( verboseFlag>2 ) runFlags |= SQL_TRACE; }else if( strcmp(z,"version")==0 ){ int ii; @@ -2114,11 +2114,11 @@ int main(int argc, char **argv){ if( zName==0 ) continue; if( strcmp(zName, "oss-fuzz")==0 ){ ossFuzzThisDb = sqlite3_column_int(pStmt,1); - if( verboseFlag ) printf("Config: oss-fuzz=%d\n", ossFuzzThisDb); + if( verboseFlag>1 ) printf("Config: oss-fuzz=%d\n", ossFuzzThisDb); } if( strcmp(zName, "limit-mem")==0 ){ nMemThisDb = sqlite3_column_int(pStmt,1); - if( verboseFlag ) printf("Config: limit-mem=%d\n", nMemThisDb); + if( verboseFlag>1 ) printf("Config: limit-mem=%d\n", nMemThisDb); } } sqlite3_finalize(pStmt); @@ -2144,14 +2144,14 @@ int main(int argc, char **argv){ size_t kk = strlen(zLine); while( kk>0 && zLine[kk-1]<=' ' ) kk--; sqlite3_bind_text(pStmt, 1, zLine, (int)kk, SQLITE_STATIC); - if( verboseFlag ) printf("loading %.*s\n", (int)kk, zLine); + if( verboseFlag>1 ) printf("loading %.*s\n", (int)kk, zLine); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); if( rc ) fatalError("insert failed for %s", zLine); } }else{ sqlite3_bind_text(pStmt, 1, argv[i], -1, SQLITE_STATIC); - if( verboseFlag ) printf("loading %s\n", argv[i]); + if( verboseFlag>1 ) printf("loading %s\n", argv[i]); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); if( rc ) fatalError("insert failed for %s", argv[i]); @@ -2235,11 +2235,13 @@ int main(int argc, char **argv){ i = (int)strlen(zDbName) - 1; while( i>0 && zDbName[i-1]!='/' && zDbName[i-1]!='\\' ){ i--; } zDbName += i; - sqlite3_prepare_v2(db, "SELECT msg FROM readme", -1, &pStmt, 0); - if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ - printf("%s: %s\n", zDbName, sqlite3_column_text(pStmt,0)); + if( verboseFlag ){ + sqlite3_prepare_v2(db, "SELECT msg FROM readme", -1, &pStmt, 0); + if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ + printf("%s: %s\n", zDbName, sqlite3_column_text(pStmt,0)); + } + sqlite3_finalize(pStmt); } - sqlite3_finalize(pStmt); } /* Rebuild the database, if requested */ @@ -2287,7 +2289,7 @@ int main(int argc, char **argv){ /* Run a test using each SQL script against each database. */ - if( !verboseFlag && !quietFlag && !bSpinner && !bScript ){ + if( verboseFlag<2 && !quietFlag && !bSpinner && !bScript ){ printf("%s:", zDbName); } for(pSql=g.pFirstSql; pSql; pSql=pSql->pNext){ @@ -2301,7 +2303,7 @@ int main(int argc, char **argv){ int idx = pSql->seq; printf("\r%s: %d/%d ", zDbName, idx, nTotal); fflush(stdout); - }else if( verboseFlag ){ + }else if( verboseFlag>1 ){ printf("%s\n", g.zTestName); fflush(stdout); }else if( !quietFlag ){ @@ -2340,7 +2342,7 @@ int main(int argc, char **argv){ int idx = pSql->seq*g.nDb + pDb->id - 1; printf("\r%s: %d/%d ", zDbName, idx, nTotal); fflush(stdout); - }else if( verboseFlag ){ + }else if( verboseFlag>1 ){ printf("%s\n", g.zTestName); fflush(stdout); }else if( !quietFlag ){ @@ -2442,7 +2444,7 @@ int main(int argc, char **argv){ }else if( bSpinner ){ int nTotal = g.nDb*g.nSql; printf("\r%s: %d/%d \n", zDbName, nTotal, nTotal); - }else if( !quietFlag && !verboseFlag ){ + }else if( !quietFlag && verboseFlag<2 ){ printf(" 100%% - %d tests\n", g.nDb*g.nSql); } diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db index aff7e27340cd560731ada86f167851c766694516..a4969047aa677cdad6e9786fd825d28a6630661b 100644 GIT binary patch delta 253603 zcmeEv31C#!)&Jc0-ee||ZL;sl%L0>Sl9@M4W?~2=K-dW(1VkaiB$+@2l8^)lxP-XX z1(g`(xS)VrtyTpWUKK0Q)~&THZMAL{sI9eH75mxt`>e(Pxo`H#OfnPDe*b<1a`TpZ zfA`*V&pr3tbI-kR>m#p`t%t5+wr9o{%4F&yndc{VNNbcA%F5e6+Sj&Q5zDfl;XBp$ z*ss_x+0TX(l)FIldd!*uR|0lmaDhz5+6U)j*gQB7!=}MG7|tD>h2fmR85qtOtiy29 z;4}=agOf3|3|3=k9-M@sX|M`I!{7xNY6q=4q4DuL0KbVW*-oYFU?;6a)@Rq@J3~wAv#qfKBDHvWen2h1zV3JHWz+5qy zh&TO%@ffZjjKi>VFc!nLgV7kS8H~cPc~FJnl0hYg4TA~{=L|+*IDJr#VdWr)VaXta zVZoIc#9x7datj8eUnV2}vk8OK8!-5+4}-t-V(?K927l%Co0Mc?Taxrj z3=*(CNgOsLiN+QrDgy>GY&h9JvE5{U(P8kratwZj4Jdn6i@__{h_atx>&bqk!9c`T zlRb!yCfki|Cc77#Om-*Vf#J>AVzO=77~F*ICF{$?pbJ||b}2TKtOJ`zwhCKEW>;g- zhAkw!CwRaZZ~O4ZOF@6*2k6TE3@i zLuXr2pWrO)qS_^O4GU)1H}SSARZ(j(tp6mx5Y&-sB}~iGrb96wWr^e$%$rx+RL2`& zEg!WQu6Z#w1qT0=o&t}&qnikauZmd%M?WJ=pkbmi7Wr2;3x=+a>3|n6$*O{d^3-HF z@{aDZsOgOh8hD9ZJS-FwE`&EH^XZ($YT_Z!%uI#EXUhuU&~s(+@Zd{PDX?u@iV}2h z#i?RjH}v$_JNs6&+4}57HaNN~CK=Yet6L0*-_>QPu};yS3^8j~(J>#asBkhp?!u)c=HZq-HeWDR_tpPw8zyM9q!V-psx3E5mW z8;(E4@X6XDbGfC`tS{E8iZq?`%QVGRDn0OT#icjfTDy9Sr2Oz=b&AqI*V-ds@2}Hwxe^EloV6)vlsC*YUeGgb3Is1FJS0#c{+4o z7n#A>ioy1;m?U_jBqb7dzpE=u6$D<2ytZqVfnKQAb+sW=!M>Q7it1L!yWaIZef%_M zoAvbdbgYw-NTt9+;9n^po-*W$HhXJV8Y}vc*?0x zZlzt((=n63j9Ozc@`KdjovC^|JG#5=eMNl+ZJD6eEYoN;&G=hhUarv^F%3$D zJ#*udi}`i7zSh-zk><$rNA?|g;m8w54jkD>nJZ9VOiXE6+ts>8%XhRDJ4z+!@rMW7 zPfH6y`%r0uqDnQjZszQI_`y563GnMT^D|-IKXZ~(oMNbDL+|DleI4uUT^stU%k(hs z9bG=7nFwp&(UmIA)UhI+eAS|bci+_&=p;t#?O0uel)-uw zX@426B{3 zibN6RNC&45C=w^pcpLgXvJxriXusYKl;imYOX?c=BGrP%>G*f*MZAq~tgW9}$4{?Y zGz~UQi7SMyr*)B$O|$D3sc5y}R5h0b>xc7GCY+>wS<|X1#wu!gO}VC+*S4sfEzoCl zG&~&rM@+V|MaK&mnTPH3<7UFjH8BFbd>|ot$3YTD1bA|nA_X2WN5?}=qb{A;;ORA* zJV<oPU&-rL#LYU{<`iDl4W38ep;BVBln z{9>a`p3fl-1W+#`nHdrvAjxq^md&HF-BIE2W7rk9-bnNc*O29#HdadOJD^SQ8_=p$ zba->pxXAgp74|jdrm6UObu*gy8H?x5gS!_a3x0GsHXRP<#HGto5KL@?_YTJvo}&SA z@V=-Fxc@C(rq^(IK|%$5{>y}1_~5eCB*(yL`k+(|dri@Kz%Po9h3%9oa-jdk@+E2iUMTMP@|P$$MTq8OiA zJ8drCv;aHB@oDljY2;18=AYJBM+fDm+NtyE_Q0(!L{jSakS+^9Zp{6f80UG+UQ?ea{bd}1x9+&oZb*|_`VLH6f zoC4-enaR*_b5yR^SpC>CbsTh=8P&vUP2VOBzo3zCsn&FCL^Fqa%{mY_V5Yn552+0&Z17wV){mVKbW#f<~Q1{>{UGYA*65B@ERi0%-#9Tue;) zoU>8`TtZyx`>_2rT?P!_5uFBydgD20o=1}4)-K%zBS!hpTD8$JldClvx44Dc9Fn*L z2Ru$^(za4HAQ9(3GFj_td+VAil|v zOfvie^=H_AASwZdH>PAr+JTB6sS>1*<9(K6ydP`Sk9Usxt`U*nMCT9L z($HTKaOiglY^-z7iq@`m-L@VaSV~cEE^R4BQBW+Y20G0|4IOOoHx9c&>uOt1QD0x5 zmPeI_j)SOT^N#lp-2v9mAsKM6H(pNqcks+m>BCEvIL4d88{_5W%Pt8#$Sa(KJRI5@ zTL-7V&WTo6sRXJx*Dt{FhAPisyOPT*IO{P#J@A;1gS|A)^R96om4Mmvny6-h`h8;^ zUuL#itcFShsPBnOhKq3g$5H<9frJci4Pde@ZUT!+1np4sy#{?b>(-NBzA!B|q6f9{ zo;FDd$P20jU?;*kxNIQ|{+MKiHn*(3FEh=d8+ZL8M?tmq7#Q3bmk9Umh`t)~??M&i z*!>wvuvd|!XQ@7MQjx?%!zHL&Q)Ssb((WitN&Ajxn~%Mt8=#ZQMksk#w_~g!0UmuJ zHZ?+;>s3MGYr47K^E@zaW=j(T%1Z+zO%hJj^3`!NXUf z&belIrc(IGZ4!Sq^9BIP0+Wb*-Zsh`03tjSsw=?2e&1{@Jn#3{JMB z8OA6R3LLI=FnM}egj4LWe7kdIPNy0ZgGw3S`NOGAs#KN45RS>_pdu;_{_#4O3kz=5 zDkS^Yl-3dR1*eL-WHvHn1O6^{*WvOim6y?l?-z-WdXUO?t!;zB!|@Z7O?tgqTUls=caPXV_$b6@^Yc^Vpf>2p*rq$czCOS18&{1BSjJ8LwVI4W;zDP z3K=^U&t!j{t)sIXYTwhpEOk6|?ut{wkKflVNY(rU&2$EfRWF$J7C~>ZN?JWnO$cQI zU%pvWHd#|hpA~5e%R9=qcrLGVUeZ6!RU<+hejI`88bu9luJWd-J9&pJ1OU z%%eKQ;7@Tf+Nh0ALEY9z3P@JAg>UH!;p(rW5@U*yLz?h+sY&bTRq5U;iErd6B(#&m zFP92*ZaQKNc0@OD3AnmWBV2-=WqiGaN`0w>{v9OBtAoJtLXrdPewMECbPlzz&B@jR zFTA5mkL&XmE$6$UBFH;06%?CXNEaL z*%{+yZiVI_8kEGXw2vi2@d;fov@Op_fSo6FZE)`iGoAr`~lG@=|*-{xZS@yDQDca)Zl;1fohLi2f@*1*SW_CInIj72Kdv!=@u>Dn; z2HBrx7t%T0^4mJ1IoU%?q|OoT)j906VUn%1qoL6bI~7t!DuC0v5rMI{Jik!-I{G53e8qGzf zmPLHKhOb*Xdr{LOl;)11F%MK3Xd?_428NzylWAoy9yUgCvWGAIZY6;<@y4z%(Df)> zl#?xkp$aCk(5sueO&yeZ`-<8+diy#$Tl@IQAS@thkuGDU0gl~@3pw>AOeDm8A5C8; zDoMOFXvQO$vqeg1gCoU0P$reoX>+nPg5L8~YAz`O^-ZZsO!XS?0Tl;P?@?76P~!p& zrHSR?ZfXL+K)^JE^hCSg5~0F%hckBi}`n{Pdnn-aMk4Pzc<0H$Z6*_6MPkK0j>=1QFI) zrvQ?K@_fEnLp2JVnD=yT>S(L3aIu!llWeK2DSUy zcU!a;4b(1;&hY3mN~+6X>or5(ne;3;(WQ=yL&cTHUL=V(q&~#Ik5Za0WV$tTwvXk{Huh?fu~=Qr*LibS>EqUbD^dq7H#1h z%3{*Wa4Eyxjl5(F<*)3N(xrUeyzWVo<|L7&TGAv=U5X|jY*ju&TJ6+8&I#S3kL1neG6_db=i^=AZs>D6{1r7~J z(z@NY3)&m9p<{bSG|mJ(D#Vk7!6UWlNhDNkRsfT5|c@3upRBdgk zM)kS0h4M760%mY(KO zQgVEUNL!2V!Wanep^@q1YswN}_lnYpbSLY#AXbrsgK_?-p~~kc%QGgzEJW>Sf zU(@C7cwe_O#GuPg{>c^q(XEl`7g3!zm{CV5Vd$^Ie9=XkLP5&^l<#OD2+JZM4n zBSZJ3Ph?l1M!BAamQ>iW{Ie1 zCSOOalxYP${x2~)dAAx8n$xs<8J< z3oo$yaC|asc~%hxpFYCJ!mAq-Qb2t_n*zB**@~oA8|o=5db>N;uAMyDQq7~f!b{#Q zc_y>oRB3V!GtQpk90%vko?FKofgINsLege<;>OSKf z1B>t}j%wN>lh!q_6`ZR=CZs<$h%_jpgURCg^?Yj;?AyrX7-IT*>59MLI~ePoWhg49 zBdwzisJ%p}ESm^l-IkvV*5Uj(I{9>_6X5t!E(dB`3sPsf5^t7f*;DJ9F08}$G4i$b z(|MB~hq|W4jrFtZXY!NWQ_SiG^&Ti!p<&NO=$t}{S|w>n&Q@sgSk%1e>L4l(1$55D z$@a>aCzu+NBioBSzFP$wGCA3kowT#WZ^}-GV@pw?zWaVMjl1UXWf|hB`$-Oox?EhJ z&dEM+9}%kI!>4h@*7hdHS^`o(^d^7$>D~PMj zo$6GdsTj6CnUMoueib`krJ?S3=yWLCC>~4W8VNl5GMNDCF6P?(?Sy5}Pwn_?q-1|K z_Zfli4QLJ9U0DzTw_Zys#QsakO@#Er-nFCx)QvH5aN3&nhS+dD`8`8Ng3S=WUojDM zzb&5%-B+MkysVpC&oW!4!>MgCsZet(5yfBk5j{&TgS@pQS=@CeSx>|pHtpeB{>WkfIZ!j0^Ir$c|kn5j?^-^ zltPZs$XmrPuE2(bV;W8mlLyFZc>P^s62G}QW;1~+en;-2kp^M#0W3<*P313$m{S>n zOTNw=2K_4X3_LkZn#fh+p`Vi)0`qgYJ-)>?E9YEWNC{s5;>m9G{X?jn05 zDBE4@^2yr*>wg%VB^EzO-eJjgFnFS50J_@>;({NM{R)n`P{u%fje5q@TkDhz`#?gL`19T5Aws^l z;}BU*vc--=WC=%d#X~oBhR;=?byzb@C>QgPohqfMo6xR~{!1FyuCF{R>^TVg7Sh)r`HL++4x5RSb{E}~7+BL4X#$zaJ2h`*Gq64(5J+{qGv zqq4g;;nL~n~~pzZNCfRQ<*H0S2BW9p3ljiKxayfD|V=v z9jyF5jB!6)Qm)Gp^OKlUY}5mnYn*-E2>bF2QsBn-bt*WI_pL$ZRQ z+SH6&mldJ{aRC)NPH|g~Q|CURyaf&~Dy_(qBr9F}rHY%=cJJ^7!E7J8Z+U@uJI}0P ztRjfluDWWzrt--1_RHE;X)+nHJ zpVo-}fF`Z6%&aw5XpI)F(W*7l>AKb=XiWyK$*47%v?jCGRG~Flv?eQ#w#Dd&xFe$g zN+&XO%CCx`US#M_d8SaE8lkzIzuc~ZDHGIul+-Glg-O+@dD1D2(n4LLW!7T2`DI0F z{D>ODvHRjQN&^aWY}tPJ=DxTTxZ(|T=zaA@f(||h<%(Q76~vL-#Raa`m}}{+vh@15 zMv`2bNkfA&S_3r)>tMxlnnqD2QrRRs>u-y@H}sTYc};h2e8E#6SbY+=a{U{J@bhL!KC z>tU{*X%(}JnGYHADBRmnFf4+WS<8~g;M4x7nIgXa5lemu^{bd0#G6gbMpp5-!*6iM z8q|1oR^~S>c>)d-Ec-2G%p9?E5_1Pjo`l06luUtje=3O+Km4#HgTVF~%m#7uM-QZh;Gp1}+gvQNDGvyyfK+Br-yG<;E#DLyfq zS4e#n+4h?_2AwlQQs)I^kOMg4LnSxF{{GuoKv*qHCr@rIE-DPv~H zo8KU&SE zsm)6otNHw8I-=a76(g{71q`7GEYIR2bG6OR2eO94|3$djcYg>SO8thMVM zYk`jIVq9&Be@>n&MZm?LTGTje=Q(0HZe4e-_(mk>^WqB|m>x2Gqb&yZCg`)p-5pFT zLk<9WIQd5LmbJ_Vmb?H9-z3w-wfo9$XP|o(T4cMfW`^L3UgkRH#})9!pVhw=-|J&E zEO`<5R&_S$1ttYftI#@ex<#Eptx}x0vY)|m@E{yo%j|&HWA$;UOl0MXuU^hr6;#VS zOl@!*`}Bq)eLo5^^i(kmredc`ZmY&=5^B;Y#~XQrbXnS3?a;3KB_D2-TiOc$Ie-U_ z+6Nq#He0o3;>GB5&xRafe9lI*c&1ILp`Bmiyh}jMmUyL zFdv@IF4)29^#TaH^V3j&TevpAP_9B_ne?P8$!SS&GXh8wV0EJX!%_C#2a0BP*KpDvE6-~d;1JUl7WMErKY-> z=zG@CID39=<3;@3x{I`%QC~AqzkFPthOIv?!o5DY&;c-*k%+!>RF!ixQOP-F+9K@D z#ero8&eBIPk8exsq^1tJrHf(c=Uic=)6%2q0bxmYBCIIP%b7?mDST0Xd8cii9Vw@E zwr+`I`vD_+9e2^mUdOTRkm7DbF(-RwHGDHQF9m*bJ(Dahxt2*Fc|RRqS;Wb{FYQcl zba-j8!O64O!jShEpOV;zQ)|gF)3p@?&6ad;5gf9`DnV*h_mTmF3z%p`&r53WMfP$# z75CVy=@LFxCLM+toC7=+sZNB3uM`}-*2AmO28}lQIR$CuW3YlxtK$NdYh^u|DHWvd zoaohwk;@O^@6X_iYX~X;*E7Fn$xASJJu?7b%|Rii{(YQAJh+V+iXx@r!aJEgL|MYg z?u?}djFX?BZoTbBwD^C1H&ZM&-_6`l$jdu+GPfes_MHsQFnm2F0n_la*j$dt1L$)8aomOsM0!IEE!w?4xZvrzUV^Jc^m1NJN<4E-p5FMU0e zjl_Z;rAtS+==K;h1GYbgF8i-M##GW&eJnW!gSY9>1ba0bB~JMf^D~wlht8!bsbcJN zj7Wf28D_%vr_y(Ueq(yBIO$oYnCw5q%#R>tV*Sg^J|ce&Gx!bYE6Y9werD+m(efJe zIz!$Ng_pFI1ZrL`EdqS>yqJ50`2!(uirg^<=Xt+{eJ^L(paIk-@vh%6m$Kw7IQ$fI zA=KQ+mWtQC#azLXw_)FntVOJPk9n0P?|`s0vOv_FWZq-RyCU}oit--p`IB-oO#hI1 zZg_r0I;8%Id2#s1RmtL?{>(fTLCVFuPcwE_{%b7a30Qb}{0ca_e!m(H{^Gs@dr* z`IE>c(T5)asY=Ke^U~Pgv*gdP@G2hd5LxUqEcqA?ol#sS4)E+XEcry_3fcTfq7yY` z>|2cdeXRaxBB!SipNpJ{{yhyt_v+`s$-kz}1a)m@BpiJ?UJj=pMGHZ9PI9zZ^JUr# z%<$=sXed`FXTapx^!Zdp{sNA-W|fM%Ni6E_e}&pR$}GdV6SYvKPfr+bo~VKakEZE> zKZ!xZ*J*p;^vSd%V#8GSX+r)BcuR6VG=Gz(67|zr>@r`9+$>s_zrm@Tc=PbFVI?$t zoVHZFYaWaI@OL=$YFeYXq=9{$CI5ikv1zN}==Y)`;Ff-QhPbVX-NcgrhEtETW;pUh zT%p*!lzo^b{{x5r#>|1e$?A3Df#vKhmV5<=)M=Sw?n+h>snW}3gC9>aX4saoM7+40{Q*n9*>Oo$HX78J zG*BPSyjpyrkG)xm%Mo*~V4r2=U*py+M*QNT)F=iThIGxa(xRRS+i%pb70+CS7A1zT z@I|CyBkfI-#UEY6&O!Sg2YFYdW4m3?UX6EhF?VhHg)DrtgMA38O(KB&It`6b+p&om zqJZ^3;FDl?f4W-SwKn};Mto@pTaCC%*z?;oY?M2Zs0@h&au-_x7$Zrn-^mt65`)Wg-~IrXsy62qhK+q za0jE}0$6#ER;^G~R92V_tuWZkW)293RzpjLK?;qAN_`s&E&5|tEi<;ZH|r~K=Sc9OCLLaRLhWr&8lUXWiy>}qRJ!f#w>wT*OX;~;V73Iy%8rf z&Phib9AA);4}TfXS5uc3C0u+n8al3eOqmcQY9#KMLz6#UrWbOxsY&RgXDCJO$T0)P z-ScN;12|;$DsJqnK->J<;eF^RJGX>Of$pVod2ss4)M(H@tK!o2m9!Q@3wnK;k%aAd zS7}!*tF!~(qn{4^$8js**p;jb8a|1ci7rG$ebXj0Cxadz#BBJ%gHf@W*lJ@u-9*-G788MWoUIQy}bV%@v@m=%ACD91Hn4Ux7|mayb4A4sbkN1SIHi&tKf3DGBKHV^baZrR7l+Lx=E><_azq= z845ls{0x=mz>M-3JT4|G!V{STstOYonHH;2FNG*Iaq^DW746vHsRRuwM;*Kq5~oz^ zTkHX(X@zkBMXf#*aT<(M!VsqB0K!yB#h6=~UNo9C#ZK@3k%aIV($p1SwG8bb5PyAq zD#ZUhAx-Y^#Xo*6iylNfqNCu}`%@!W8@lSnYjP}bIzAzf9`_}crlrzfI1bXF^hvXg^6G`rQdVSrkL~*^w%E`7jdc@b! z11$R7C*HKyf?{pC=Q#?>WESN*3{J&a&7nKuLX&Ux=)byt8G9?NTo_+N6^ZzVpTQ9v zFOuPcS6NhsUSK~!RUt*>4zXwOB30ymh8Iebh6Aes9r}OyYqn49PJuAPP9pAJO)e*QtmF!yYzO(AW?c?>^J3P+-dmGQpq~?y4etZV zr%RJM5tk-$E!DM1HgkU=w^%Np%*lS$=_2Y1c)OUJ9jX6mMK!9c*GbPs95|mLa$GLP zP4H(*xHDq!pV=xRR+n=x6Y94f#eFVS1CW$giZ=_~Mh3ayPzskU-g}xoPQ;JRTsnig z+wM3n8Sed(-3}-J&PGDT-`GBJVkP&x2wYRXu7;~%8KVJSoW@OsqIsMelBRP#v_=;3 zy6M~&7PfxDra{&$?ovF?g`i>u``+Oa#d~IPc9s!T@cuL|pC-J7rm2MM=5g;b`gUlY z&+TM{R*}?m-{2zm3=Y?t8n_KK(gosm4HS6I0?q{1CeEC#S|*@&)9lvJ^m+q6!aj?! zt6+N*mmlHi?YLE*Th3IP#JI)W7YrO-f!k+xUxKG#=$3Lzl$Zw3#KF^ThL&=PY?{-_ z#qHaIhT2l;RE>oj%2MHnKP^pz?XM(8D4g!jaBLURqq*vMEjqUC-jkpkxL2D@I>2 zoX2MXU&FPd4v17GX&e4;PQ0n}HD2CT#ulRnut2Bw?UzT$(5&3jXFbnb? zdV$2M?LVnIK5QbFoL zI24s6rfuauVaWoKyM{(P1u+&9XI>B#jr8C>Oz7cbbuy~N1pV<9et6TMtERK3e0W3J|2694`n z7sE13E8*@p*jm_qFP8}24|D1rPjPbC^*$>XdmrJpv$$}5{7J4*Eczj(+Qo4EaV`yZ zKh3QK^#N|7_{+V>^<)_?k#ahm0&@y@QN-f5Jj3l_$#S^<0M`#&ALBGo`T}=3=pW-^ z#j@wP?LSU2**%WVKm~b32!TnsOjlZlW@$a%&@*O9-T{*qiUt3B4(tj$~g;77?xg6h>7uI+HeDasD4DI8=@lO+4u>@3#{8Ns+ z4-G2>ARo#*^6{zv$gD?ua!!in%>SHVISLyd)adjN4{OvG2m&=O`Uk-pt+9f=8#sqp zAwi)Wuz3|`H2Q~x=Y=&2{R2Rmr05~gUuf~oBLJ3fgwXF)A@u)H<r7td&h8L1+2e!!~oa+`JWAzU;h43LV)Z;9e-7h-r_-U z-Mf`)9R01-#A&Igw6s?=V{VkpR9U5sWJZ&(WcsFM!=3)2@tF@} zP?+%lRhS)VMnH|9&x8?JVHL}4k*eX$|Lm}Q2oe?)J^Z$H^m2!V=Yi6r6rt?z{VxEN z5B)#~hCR7OYcNJ|+ELX&_|9YUR0~8vU$LaX!`cW;T8iTa7>#}XScGkkF4Ilj}Bc?PZHs9NlXfEInv_!baZPT%wM3c!0krD(wKa8a0n1SC`|;lB{mM0 z?aE7zz=L@4Twvuod!HL<~p|G3C=ZZ>JKaJHl&L_Qk{5<)#BK0CedZWlvM|K=@QHe)rMXL#b-J?Kp8f*#KKMh%BFXz@h?#^8P%vLY)v zengt#_;0d3(He%jItAX|NnM`v(L)kz;973?cxXCHZL7v}gs|jVbU&1~hB#xovXD-Q zcERVvJFiBmBJq?;L0^P!fWr%IcplKUgk;IpA8IybYdx(E$3H*n)J0LCe!EbYS}?zXEsVuJ1yp>YbZXq%HCB z(0V18JG>8)IIJoUbN-Q91Sb}yL^#eeCXdL-BWRw!k0@R+&p>mkV-e)i<6KG|uFsn2 zA^;uLsCpuVo z-k9NF1(3nPppd0jpDq2*Nzt72KPOm@%7zCuI{m}LlDfV(#l5eCK)LzPJMLK5L9oV3 z7U=zVZlLumL@@fF!zu=Va+#OLTp;>ZjkB}^2CKh=Orgr709Ev(a}e1;C(a!sqmCOd zY^bpK|63^5==2|1oj^~{rda;@pAD8@8~a0@m4P-;z3DTz5fY7HVHs$hfd2Ill#hQ! z{OTDfNw8-lpu*xm4=SD1Q2i}T<7^9>Wzkm@e_U2t%%2)T|7}*<0jEh86@p70CSCL{ zJbOzmlE&EHqQU^}%7F~G)C!Admgr&UvXTti2;Zr>){lb%GQkK|@w9%ZS3%9SegL6# z0#}l=E0)t->i}~@1+2d-!_fY^u|kXP8_O^MabksNs`bv=IlhKQe^;^2(o{?HcR@M% z(69b9hDSs3E^nY~KdeAe0zShYza~)rLGeOWMm)I%h4Ms^u0o-Is94S_QQTHs@e1^B z1XKX}2TB*{*$Avak;Z!F%>SHVIr0h*DiHm@RdcOhvy6-W;Z@NuN8c9tzc~qV(g3ZL zc`_sq-OUt&g*Fq7qJ9QIw_Va@1oSaQ!r`jSl8K=i?I3PRW^$g}Dj8lry^2%=>`|m< zfJL1U8{s%-92P#09*q~@gePw2+^mg&W4Fa6gY_9men zGYyx#!?j~BLu00&;qpmR@$~VINs>2w`;MP+f_x4q8!nNYPcfIZQ1&V}6F;*_x~|vG z2K7tmz}tB(GfTYkCGM3d`CLwRV?T{6V%!~!^bt-0;;V62{qCXI5_sT?^pb%})E^x` zbiKX>_3>q<3QMz~!0|INRZ-3!NI&WbgIC4j`I6TW6?ErM zjD(fO*qCDJyu}uJxZg%w5B*w@b6rB(sONofqf{goiv;AaIoa_e(!s*-MJC3zc-A4l zqb$`lOKA%r$(4~ml3cXzClbYT1-O6suX9}dnoBoAj;~`3uUs{aZUXGxrk;nJ>I>bF z&`_LUfJ0BjDRYfB+P27VbRz*S8Q`4BWTSVuBTB#zBX$2ZF9}X=#xsJO>*JMha)T_IcJC^~^1B*?{q{U30DEaD8XBy`Z?p;Nnak_UAf2h~?di?DZ zkGCY_*>Xnli}zBW@bN=Wp=&2}+pa+{;5UNF2Hfw6DTix6dia08Q@Tsb@KaUBrS1g5 zay<^`uRDLKT;Z$rf%5XJVxTDQXMxmB_e*5oaIk2v8RcI==zk82^?y^{_gEChp#OjRnn!>&mvhKmAwZ2me@{mV z0m~EF=L+S(rV(P%1vh&C_rV!~;d<-B3L3!`F~DE{{LcopxaOeBY0|SOM}MC)W7ZJwm6yt61lD-6KH#eJMNEovT=0JUvzt z@Ap3W2gM3s?goYO&K$2pp?|1YBS?U9V6$I-7{>?Br~V`3j`hR`YplqD-h1VL)>t0= z(4m6S|GQfE@T$M3_@P{dUPTvM*l=6eh<{T4;bV=!aUq8b6^#BAFI=pkg&GAak7g9w znn!}txgxYVo1QC!duKOS9} z;Bv)wuf*Vyv{Mp{vv9@YEw_ubpUXF|-CWH3obumqm{g5k=#F#!ThZ;_z0xzriphwQ zJE|LerDH^bT8wrU$27CJyzbm9RnG!mp;0E(hVWjP&!y z(ouwjONIsd>z31!Pzqlk2!t7qd>c(pQtWWB#vnmJD0lq>RV4VcfLOt!2Y-Y4pEIoS zKn17&xi1zv#bJlN!l#+WMgMbHwDZPNpXTsq)bqwtN|83dGfe!c6zo0(J`I`wL%<4F z4FBieP{I4Zf2zRu-dLk@l5cX916N8<{PQ0to-exN&L8SO#aJ4suAx5jr^$I^sq4%R z>Aa6|Ifol8zQ=f=9obmX9)ffDw`(j7(NIb`C!i1-!78Ehm?z!{Ln(IqKzaEK0S`jp ztFtTGd1I-MphlfPbo#p}f8JOcm@|E!x~SztZ{H}=T`2Sq6>F@#@cjs=Kz%X<99s4f zSOKax5(?$OR&W=|j9+~bB2yD{v}h;{ z!8(cubY#PVTeXpp{cNgY;z&CwJxRm)jW2I(=^~wbc=F>uLcQArA?wRQ|^sn0PL)HqxYA74|JkSO`YR^q4l7V&-d z{=VYU&3bsel;kAg20?43-sPqmD`OrevFN6XpICWn^8!xx$Bk~E((${gQvdB_f*+b8 zSO3IVk0%;mv-%vM0;0Z2&#>5nzt@O^J!zMbr)w_lCP7V>J4?{tbIhZLTP%oo?eNeQ zx_`q_!>*3(*IT?~v@1sFfBmpRPw0v1&R@bhZ>S6?;XLGWU7Z8UJD`yM=L~B+P{GHD z@1|I8{&P`w^uO4FuNzM_gEs4VLuF9X(3UuFs2rUiM}Hea7YdpGL%B~g!J8vQ2e(3JUUQe*wUyslIdv>6x!JnNsRQk~7Kgv*9=~Fc?&KdQ&j}Q#X zT;WfSfHRrI`SO>(s^$IHuZrHUMqH1^{4{bnSYvd7fKcxG zk0{xwXaTXpgK)&g{LdNIc%XvQ|GOzxF#2OB-WIe4&SBBcTfl-+nKI~k3)px%wJS{s zd>V}ZN3==^Sgvfx`s)o9y#J4d;&|_kHKNQRKzQdL2n*%F&EvtvH2!esEnp*Q64*;? z>~=eE0rObE{J*$$-U8<0pAoKlt`~lPy@i0|6?Xeu{33Zt0so%u^(uHG_nY$;FdusT zM_It!jqFp!v7Y)2!aLa6u%D4+U|%zCsZBd|t`9PFJl z{{UFj)DJPtAh zCpx;tc&rafIlnsv@05~+aQ)wMqa6n@ekx-I$fxMpc8Vx|)U}Y4ebM6fL4%V5n-1!u zOnlMk=P=@EyZxz_26T&qp@%c_08bD-i_;sa1KXK0#JmB2hsOAb6aD0<<0RA>1U@iE zq{GSR^aK&VmS|NmrzpQON#|3P5<^FJFbzx+dj8iW5`r4EYaK6~8%<4wz2tQE~xO9lPPJ3W8A z-Gt{~x0&^p3OtV4^Dl(_gXZl)3H53ult2ALLpnQqAErhi-|OH|4*G{<)(ValHq8)fl=}NtVXV7x z7As)>PONaK60CTB#lmjC!zXIE{O^b7gB2$IC8&V)49rFXa`)?VcJ zmOUKKVv^y)Ze0d`%q2wuf3D_YU{8-c1-AV>K?NBLwd$#S{emXmBz-P^)1+#vbg@~l zb3ECsmtN1CJ-3eU7tr=NyM9q!V-tQ`hJJgdzjf_~HhUW!tkb1W@7L&UZ5F*9KZPP# z+S&~!gUxQDpEa=<1!J2~*{;WLs8_aI4freI$49JIlfA`eX|vg|+(nwgWrk)A3{KbO z!LdR{4p*MZjcCFBwROB^LYsa0gyk#RS545+PtMo#7AxOG|5jEy?#&j9nMe@$I;3meqG`1ab#U{UTtQK#Drzehs^C;L{TxqOTml@wFfJQj zJW!MXZO=!ga;?=u1*&YoC3i)?`1u+F8oPLQ0zdmC#>#Y_uew7JhV@zwi(QlIC*HwH3g{RYmcIAf^S|ZH`K`b2TvF1jW^b)oD?WA zKJ(81Y_YuZ_kuz>a8HoTF+qs*_rmkVa_bD|ZI*g8og+mZ)<2i|45?LVM@`6R$5Y>( z-hThTP!lVD_Snfs&g=$Wy0QC=h6i`1BGd$D~5%E;h zBI;_mN15qxHFWza=8yY}(bVT~KmBC%eNWGA(SJNx z3`0P6|DZD(AyJd{KXoX|M;;+&_4vIkm(-+mWLjrUwK1~tpDg~{oi_HdDS1~ z!2SMF1KxY`M@t$03xXFgtyg}5-iHJgBK?D6oj3G3yyI-tdKaRh&*_(l&1caMSkW?Q z#nn=?p_Q(yA{Pio>R9IhqYvnhfGewXoz>|@XlTdh=+mge?-Wak%_b?S(JLwS3YC%? z4Vcs*CH1lKNl)9p3%^9TVOdET3Og25IP@P2Dx@#ia7$xr~bhz6ZrWEtiaJmf9lNtoM1U}3J=Od|3Kvo z4{KEV2Z73WWILJ-kNTOFc`Rx-QeU?G+9og0wtNk~_PU2%hqKBerq5o~G`k-13P?5_ z%1=#!-Ji#5;p(rW5}PD&b2`2lzMQl_i z3B-erXYPI)gpY!!4E`n@sL}h$2(cbK)3M(KES9_eW2g6y5fIBeLa5i6|2e}N4^(ja zpYvj2XZ`*NE*%&B&tcJA7P0VbUx8b9JjDt||M6grAg-gBqe6vA|5335Qzh6_2UM{7 z2TK+B`S^=9GXL>oji7%BP$APl1gyZc8Tr&3YGnOK#vkp8H`a*y2Tq7`;1Ty5|NO^+ z=f?8OKP0FS=^qp;s6|W_+J0UC`ukMI=C})*xld%j+6R6&0xHD%`=e3Aiwb|O_!Zf+ zGKmc!#J|%dW_KCcXnh1UD7lPhxW_@D)-t-6n0`t^5G;ZTZFUB<*;R&nIG>kE4D9TsYft{e`|a0TUtHWfvzYN_U|7AV#ZNfJx~IC7Dmwm703ITDro(^ zV}*7-u40YIzg4RAxUd2jb9AWi=s!AEkmee-SR?8mIQHmI&!Si(@;?hK|3ZcW6)OEh z!3vT75|m4|9Z`S(N{oIVcCq}=*7M6hY`oyb3k@nX`iF)U68-(4{OTXPI)UE!VFijZ z)-!kh=K#x<8Kl-Ij5Or^I$4SIf>zX7txcid)+~A7KsfRPw6!2&~{A+Br z;!XhS_e{5Q+HfN%>OQ!a7vG|;Viu_L+pnmt?ZJoN;yB6UjxZV-qrqbVvl;fL`Xyr7 zZ@4!Id2IiC+zvT+At$>F4u7pGf~CLVjN;!;aVkdsIHun#a)02yVaYxix;ZLM%=(C% ztCU~F$*!FTr>qJ=y!SLG5VBM}`UR(F$dfz1d($(LH*l^Q}|Y+lbtGZ7fuOBb#gs9 zuwO6lR1zC>S>;!7FWsVCDW0~<<5>Amu?#Omysf-YJa$H*C3hTED#hj1^6y8GSH*?~ zxseeqGvtvGq*bh)Cx3x(KOgRG5lOxL43WQrX@3DjLWEh&UMi1a;mIszHl)s!-z0vv zNS@4*Uy6K#d?G8BER(;(DH*a?#uSph$O8`&#q}L_PByfJ7Ssx#+vFzY(c$@Rob3BK z?nf#pe=KIjj#{M^w(itj1-s|V<#1E0ypO!TzfI0?_?=^ZI{Jk2^J*70!M<1InQ*e2 z*F{KM(_5k29Gz8DrRsO~G-;1~nP4f_@@s9qeJeKF)^4yDnen!_V^wF79xqz>B8^T{ zjC_re;`8*k1Wqfgm>HJR=9C%EV@mW zkVv?+T^`R~iJQZBF3(7V`{$Qsz@e)Z@wS7fzcgNbbFvN1{ust!OFuDcH?u=k_JpR{* ziAp#Da&=rWKlLKsCQ%Hjf|8E-@ z;Ggp{G;sCKn6z@fRoiFpS;xCSwT=D*S^@tVv_|?JZ(2PnWC&@MkydQwi-_Xpgm&y? z5<5z*EWm;XV+vCE4QWuaYO054&nG1w5mL=)4vI3=ra z-P3uRN>x^-k{Dvv3A)eDLjpQz091Ii8nG#2aDh0VHnYJ!J#N8_+>mwzvtrB*@buDIxLk97wXQE6_5 zRlpDCmnBMlOkz}hsEo?U7RkuxvPC!=&Q#nJxtf#hPjL&M2sn66oE26+6W0u%t&|^D z{AOFkDo%DM?8!(Fpj#gi38!sx{ddRDDZc!SeE2K;eFOICV-4{5vvDYl?m=O6?AbU$ zym2)hc;1Aeb@CiBf35tKg1iNXV+=Yt^el>lhFCU2+*_&dA@JnU9OdxINpkVf2Kn>| zB_lg1V?^0OB+1i?;f_mD9(zP|gxGzv{7FKc+TXeDf(}mhV80i+@Ld`t4ah%{{}v;> zE1tYgo<-n^X@+z$d#ijJ1NuyLDqL}ud>xK^G8ynU$*+WjS2#Y$d9;OX<0=%#{DLLe z+crsOTx{sqx3$2Yo8;MW)z$L+f_^<;TsCCry3EYaUc}F+rHg^?9@?)5;e*md z=+0x};09Yk0u28wJ&iRODxiBPE)7mRQYL`ucd?lv=b1hmQ#o# z#UHPm+r`OlhLcuBB^{R?$YR_c`IiyoPe4B43gFP+RXB2NiTalK)}$sqk;t_5#|I!O z?E4MpLv@;@jde%f%}Gz z-yEJwwx>g5bnh|q3j+Ub*`^(0lpg<)`E!B)PNrACFvsYLpPD}v#DBUvIZ&NI9Tg5if^ugKv=K~vc@GZGr&xp3X%bR~RnRr1L<19bp z*CL(V9J52eGs$u>zM1utTVldFJvzEH_wLFinRyuh{OpKo3w4_NG z2h*M7jb`g@F3Td$#@ilPump}T274Nuq&iAn4E}X%yq87RPPCx5CSImv^VJ#poMOu+ zE6*$X{B4$b9IblWTBScwVW~kuDshh`7VG7e77PU+LTUMlg*3IWpf6SWEpur)S1aW4h_+6$6}>&b=D3pIauHKDPwhg=eSn7M?vGN#m|edRu>gvZXIFkD_twk}lGxUTnD? z-=gWr-xKn5*L2ID@GSJoX8Y~~+TP$&M@@4wk8J1;eYdp*4Q~iq;OAPKMuv{kV z7q?pO6huC;q@2?Jk&vn9wORfwD0~uPcor9XN{UKKX#4o2EWKp8OlCl{5wXmIsk(DsA6x`3JdbQfBJYud?jmMcz@|NGA_!P9guO7ZD>2Dmh?~St2 z>W33a-?+ol%<+6C`9>$9;kO$N&mv!KQmWo(r{yRzcG8h+6G!Pa_gWsnw`@9cgQcH7 zu+#FKRjxB}FLR?!FPZAF>x_}sF4GWaeZx{#u9vnwXvwqiWu4PCj*dKNS!&_koj%P( zwGUaAnfcs(QLFUB4_OK@EE%X;o_?||*)GtrhZ0X>m*Mm2$is=lg3{IQLufJ27tpx3 z5{BvLJ!N?i-wLs$CjCkO^K+Iwf#daY#}e-2^v;(p<1sZoddIM&>6~7>H2GeR32cAo z`6ALn6Nd*g-1C-Y0)9l{bCRkF;@VJs-FueB#+8<&k^1OkmIElEl-$oIZqN&p5)(On z_g^iOLoBmQLWgM<8aq|oZP!G-`D;s>AoKmMjMM}y%Q?L*H`ANnS+XpAIhxX++?r6R z7Y?w#jE7A80G)d*IhE5>tE}&{xb%Dlab3w<=+!7Wky?ICPTK#LH3#Jnq$2}T?$GaZ zStkfI{kTOuAXzab_)6j)NNLgM{L`||$gfHdza=@#$nOm!zrnQkrL;SG^KCKv9Rn9BKpy4?;XUjZ^&bAqdXG4_-bOi@ zsQ5^l-!JWMR?3A|vdqYPjdhkK*jX078d;B~s`~Uqs?~%r)-SK&M`QGpm8s}x^vYkb zaEvAQd&!&6Z9|4YhvLm5>#~|(_04FsMts9$dIO{LqIGwuW<`r={+&t2axGEwqgIQa z>P44R?Y%aUHF6Z*m1toosy`wVJ4(?yx3sn_i9+_4GRH zG1P1l9eF5qi@tS})yGlceb#?cyT_48p+&aO_0#oMtEgLQWr^b?J#?n^ZL9Ny&@ofrKtw}D{ z$6jx}UtlH0u=-t0RaYmMP|b~2n?88C^(m|6M-%@o_u~Wet50y^-itJuxHZxmnf_?o4(1axClVdiHi}I!E#It?$$7RmpDF3omdM z4@4*4N1B#Af>rH9o(eWvU*~x+c%1b-R=<6yZ9S{v)%vwJT3fM}HIgqm_3llSx`1`< zU(jz&)Db3Q9lzcB5uUz;I-=xBeF{xci_?tI^wcTB1es57Oh-n4NwBIqHP1x zQt5*?5vQ%iV~y1hFSlNXedx!6Y4k#aRb|zlNk@upP)jvhpW^hC`>h{gL7SzE`>ebq z7Me_jm=Jxf2lF+%X!@D9-x?L15IQEr29(-xASsFGq$`$cifFo~jkFi61N9MI*2gVo z*~Bm5sbgco{0CN7U(1PCj?h9V^ablo3*UV8GELQ2ykON(*xUmzSuK+IiHZ9Jqwqc_ zty8s72plOoGF!$`(#zKCSl74Uy^!YbO6pJ3hZMx>=f7pW7d2?5aZ@dm^b6m!-i>dI zXq?{?t;Zj;eui(0Y2c~+K6=(i)_3vkQsQE5i|A;4fuw7nS}(JL>z{ApW|-!qjg3;r zC0e*>x@|n|t&)n!z26yGT*5;4HZ>rCo8RqfDk(?gE=>zksDL#HFM+;H=@M_-~R6Z_+Hd=t^0pb!CH=OO)+u}>zW{fZ%Ee{HB_$pfMXuCM+fRvcl)RxJndYVB+USZM<|Mlt zeh%gAuE#|OMR0Yir$(4p$u3WE5&K83yR&1 z?$Y|;gQafwFMh);>+I!bS`BDo=;Sb0B2|8xZl(9`$^(bh#z}F8jPeU5vd>ZBqx?q- zi;Me~FqzBm$j;7Y0Uw<`8t6-*+v!$^H9E$qWb z#-Cp`WYVNDy)BqK*Vi&_;moDTX7`4aI4XQ4IgN(i$wkqMx%QZpZdKaE@sq|5K?uj? zm-^;RgW9crc1!cjd9CDnR!q!UIDbw>R`-J~zDpXXtzg+fVO3si#HK`8M?;BUQB0%X zDNG2>D)46IIkK_~SUM$(I?~f4h5Qm1tzMx-C&M-{!2!XsTgC5iS3qS~GagBW!%y6h zyy!&sjmegiYKNjuj#iIjLiI$%1odZv0+tmHq}Z0iVtU|Nb4<3+ftZ{P{pIdQ*>m;H z^I99{wN@}Yg1@ucT{m$@ z@-jlt5G(rD<)RZK!xHG#-LO2&T%7eE6Qj05iAwT0a{m|9cCw*QoaART26AKuh`9fT z;zC@FhHZ`uGYM^67&d>AlP+)2(BD^$W?NomAMCoYu3-=MvAq|n@sd)^p|khiGTJ-G z=AnkYavy3BO%JE7Yhz`~eIi=5$|c1f1a74*>7f~`TwbPJFKehn7qS_MJh6M16?;qJ zLI}-zlM%p`buW7{@4@0Gyx0z3TwdzISrUdvVepqSF1w1$*iZ4HlwHJgEW6TRc5deO zc;;2^>dR()uuNAm(EFtxmIJ$nSh2=0i%W}S>evY>`t!^4Bgpfrm=J!}q*~nS@;Dl3 z?*d2E1jq34HIr(YX5Ql%QFZ>vvBP`KrftEU3Txchs)>`vSB-WA^B7${Y+`T+G%^aV zvJH;x_BJg7`l=v@VK6-d{~M;K2q!7<%kW-B$6}=K*Kr&DO#?5rqeMNXka<>Tx8s17lU1 z^1U^I_c_?20Nv@>gmjwwqUa#shw*)A^;Mi4-pv;{eR+<2KeK96+7DJwRJOyP?HJHI z7{+!^+V?Q;_)jZwRI7LtGXG%Bi|^aV)z-Jq9PETXn5esR`)^vA*W0$9R?b@3Ja1Z~ zub!=0Gk&YVp;cq6MqhYQwPUVt=DdP>H=Rz%9ZHpVMAy>lN310@a8ito-W*k!O`CqO z#wf<#Z0yDrMa+EGmt3QH8vb9+RWC*Y0XVd3maRVPG2^;uXV&H`;C2X_~@F! zj;v<9vK-^9u~>{{3rP;uh9xAE_IOAEIrzTi>V_^RG&{}Yf@l_NX6KDDIwk%&MSgvl4(eJ46LRHCZv7B#Xct=VI zGvOL$RMRYzp(UCQ^@j$jeK#{b{r&(P#8@WY@Hfdt@5D$|){@31h-1ZUOcOY2mZVrV z=vUx<6}eAaU)=wyKXl?2*qLG6pGYi!Eznr#)@h0 zNOLr`H%n&n?o;>buREkdf!jz&oYDdP;S6b{z+F#WgQP*UEg*&KCmhlOo@!zvuBY8u z()dkFB-nccQY7Jqcpy&_L~aueDz-#Z!Z7Jk{SB8?AaF#}%Pqj16ic57+>LZRAUXBC za_JqbG#+?WuAgZ<8aq^ayI4ztQvZJHSS}l*bFcZf=kl~f8dN3CGT(IjAT2?^uSy!v zn{PmqZ>EEl(vn_0gHzuq)Q_*0$~o%Hl}1zL`O*Sf@m~JzFjYw(v*tm=Z{qK!?dzo|{pbhz zX&jw6KYaytULmcb)dMA)e&Ql24105}w3e>9SjuIO?;>rGw1T!>3dl^$L@CwKfZl^$ zSi0jEJ;o~ArT1SWJ;!nP(#iAFm+Pyqlr9sv`}B^L5;S7H&0>8k^9R~lzwDvoX`#`~ z&U~kS@Or5l+8BO06vX^;MvX1Jw0Dt~0tMVWMog7-%e~U`z`gK$H?(NUqG>BwA$A$I z>OKh3&+o?j{-PVYB2wt+KA41OMuh6>*_(iy!iz#CO)!*=Nw=-`?t)D}9(%yMzVwiddDKD=Fm3B|cg4YDFxZ&TVoqWA6#aGMeg3h3u(}vLOVqFy(<)GCN8&0z*#6Fy}SmW zw&cVlQ0m=457qCKDgjg4{(Vv;Io`yQd+%M6B(*M>IoAmpn5yoUusfRg{d6)PCbX2h zrM_Gf9m@|LOl$6z(&^~R&}>?t8DXU*Z^qk$hStu{LoBp(S$>?k)L>Djo1@~fAB8vO zB}N+>21l9PYe5Dl<2Qgan^tl*0a5B``%d7G-n?6yO;2p#9W;2Cw2&f(Cq&SrFDBT8 zk~|ObYixa4p&?ZBvt*?U7TTl8u|01dt?H**>77=brF)lVizDHJL`R~r7oi0Q8mBe* zfl2c9@;y1U_vJYEradu9w11aWcEe?2IKA^|h@E!rlnPl57V^uN)978&C0P!qKMy4X zvedEwiijrX8Qe#=j)p;fvT58RTh0Ma{E7`IM7OVa@nUu8_?lYB#Hzugs~t=F)2j5$ ze39|Sbd^7MRq&I&KDULo?MXDNWIh(@O!`6M#M3gf%mmEE7F_Q@P$$OMzh0o6wCnJZ9#j2u3;W<2@!+N0^3?3i$xKbz~GEzjH~^`o%6 zr4WkPDHU4{hhY9ea`g=z%VzdoL3pTpKI_%DOI=oRqRBK7%lLWH);KLyG(C=;1@xqY z%<2V~&U$U4!`-`=pTjr*LX+kWD$>e(O#PVZ2EuB=1^KbI?on9`Mav5!*ot4vy<>k@ zJ=dFS7CNGvb%5*iL?=eS@sb5ITbBg&Qicj|PP0FoS}w?sKfBn=^S$;ZdG*;9VAk8R zvyFP7iXFIa>%G2dsVz?!Hfglpm6?#kb+aLlnM{wNiyn}!Ez=yL=@x@8s4OdDnqVf& z{qOX`bjRBn4xm)+4?pV7DUW6S9y2RbDfzNVSDX%Eb=p2SJ(qqQ^u_X>i$OY`fZnGOl& zLjB$S5D2)(4m^m*$x>OcQo6Q9O9%d)*Q<&Gof8v6c*B8~Fs*CnHBs1T4!LT($iJvU)@=hEm^vBNmgE47PL;jtmVsFJ$YF%SqLecmtRy?UR3VE0Ylgl za%lUg&}at)^w#Z|+Z5XNb1=AG6rdEHfem{M!eoT+wq$Z-{Pk*E4gcLQFFnB2xUaqFbV%pxI#_z zMm1r|6+=&E{MExyR#aL-m3e9m?MubkQV*S0VnZA99QBSWqoWOA>h{0Ov9$J-tp3h4 z$o|#j{N-Rdt0yD;#snwL9i_&SV~8TrwWnl@9a9Um^8(&4(~Ts?J{e4L{d+vQLS zyH_r3?AeWT@^bPVMw|{m+qK7n4>OM1@)USo9_SXKki#ek_sE{#xUru*va%d!^li=_ z^$yGqdBHdS<Uj#@@3~chSZE6%?uz57> zs>@O@P7q^@<+$DQczSV-YNtVVKFS{VkL;jp_sA`L zo}b1=0S0s^DzdTe|GvK>yZft0Q83hG8jAVdPog|66N-Y1+5GOO=WLdvAZ>3G###_eP^*c^UQ+e*s`mB#}g%viF=ia2l zOY)qw`hO(r{vz99EB7|-wH3^x!)Zxl^n;RZsKC9WSA`Ue;MI0_093NB&(P$n!cdDj}^FS z`cGDw-dY^iSC3D#eaxHRLM4ysX>E>A40uPhA%Y*Lu24CZim!$5Li^rSnKlfv&8Nz= zREgT{sR{v}FkL?{+x8I8eMlXVR)C`NY_AF2U+DPCu>0uPJyr*hO}1jH7-37(Gu^g7 zTe%$lr*a!Fn2(~skM-kMTT>yXe`AyN(|v6~8Lh5N^|4}F0m3|ft+kb9518EaTa)+d zTkf>hW1uevcm+THl#T>a2h-`-9ewrLLv1$-+-DSiD8;LzurIA9j=eM z*!G~n{f)w-t%LPp(`}Cl_7ltw4~0`naVf3tpBAh4ZLk3vm@k@MY9W-^UtcrFCJVN| z@0p_IK|$1yU94vn_#eFg!F-?+^{{#HHlfNko1cD4PjKlEudvl}q%N_YVvYNnPR_SA z>L=#fws3mOQX6FW-bV?K4rdR2qjM{5HcrQnzO~5Tc@apl81e}C1~o5HcdoX*XO#u+ zxQRc=*K)_f@w~=a+t1Tn24eMv@Di%)ve_uJAS9EC+CQ|Lv_jFeO@yaZF}o#Ud{rA2 z98%R}m}5%I>x!8h6i!C*lz8xiQ`fn*0-&Rtj5gEVmGGrVy~cLT{4H*NPbapiS92w_ zY`c2%Wy6^9lAF<(Z)>T zc*E_cnWR*133bR3Y1DaD%$+o^EF2qGX|j!G-zb$~lp)Vf+a<-(>}}C5`t(NW?mmve z)e|qM#s&o>12!Zt^v6LkIUoq#{08lP$eK^jY?d;qvK$%Sbe}nF(`G3`HkdxcMr3&6 z(V&~8Jj&k;HPSqMf<|I$X-EydWRHp=@n+~LTC>$K8j~%^w7xZ2qH7KUZ(@86{0QkE zUo+0!9Wry?!qx@O1-^L=&2#;b&YXAkv1zi(XU(09kwMq?D6)?<#};l} zU0uxcKkB0kLee%w zLp<3ueIig+DCZ?sj$oz^N9y@{?Tt2|Rq@?I<4#);Q9mIihn4WS{^LuwpV?w5SgC7G z>LNPSFKrfex*Y#xh4tkRJZj4@n>{E|HcA9tZRgE`fFID_ldc3>J;WpTmiZ^647=Vj zH5Dt}Ic-~Dv`wLLCsO#1+sj`9`(`Z6Kx+};zy zf4lc0jNZdSEpPanyBLaseyz6{F1fp5fx;YOA>fMHTc6*IqVULNwk<#A!kl8>wRg z9sU8BGF(j7tH!2{`9&eCIcnLTYTQqu@LX#KE9tO6E@zT)F_n&(T_4k-8IHHvpcV0S zBGD=zNRlP9xfF#D?kW5cqwqwOldd1RKcx@~MJY92ubd?BGn&|k(xy3%lH*1D;&TTt z!0|!`5XcU;L(0HQ3w`Oxn$*T~XUi1;;TFk<%v_ls?v=m9*rlN;r|wf+lZ=kM%P1vV z-##^U!mrwBoNq5cknpD(Q}YS^#85c`MdkMN=1959%9ZP(<76N$g)HPFGT)*x4kTtBLsAwMn)MfcTf zUUc7iJ>A#E^FscCIr3JK>#uKXk#~va0ix;ZdAMIr4<85*rRI-So4$3C+-T9ouOnaO zELsJifh>b^%1H|i*V9+Z?{SJy8WecGHkrN#L-@J0M$!g~rgc%h58{YjLuXSDEMJMM zsT@EyBy|WFT*Njo`0g@Gf7ux%A9hX;j7UY$Xhua*`+jFwqVXLYeN%BGq737{r@YjF z7~rYI4Fv^c|1u=OTEsvHrNuPpqUao2y}BTIRT&QY?Tvm@WNcm@2DoD|yQUHd6UHBl z&C8&6OuN)`92#oM^fe8Qb=dfVagLjSA1KFX7OMclnybk8#xi5y%J7Zh_?Xi&ep>8m z@YRL5%Ffy!>!~XRitrD*LSmwOq?UT7>g?V>&$@3rb8YX$)VPG8R1*Xg1YNQ{j+(KK zwu%ONuPdalVjR*t!SG6jM3WL4?{27QJL9BOfA;;eKKGaX{bj89m__|Ic8Mrv_bl{%jGJR>)o_YuA$V=3T3)*o&0hl;FI-?;bXiNW&pz66no|3 z#u6OrOI7RTsdVdlIfnkUjZ3r`LT$DI(dld8<<7;fW#Ls}2$ulM`75%rPSKI|awZ+R zi8I>_Lw@Um#zx|Q3dxK=LavWOd`WCu^NenGd`>FZ;;Ve-HVx5tt7s^e8C z71zsI6m}3Erpg9+B$r7w*UJ533JWh(>npa%GN*^!Eq}`#=lYr8%+e3N zUm)G=(W>E0ID@)6<)PF$T(!{YJK>x6!B2Ahruu~MXxrx48d(^=u|*pS!+L`;BFa~h z5);|>aTZ}Hsxdu6q+;%eyi*HXcwRgEClJ3ItF|Qxhl;#6#N7!dMzSFg}vD zY*JKezAe^ao4~X_IPDvjlpV1nxi&gBH7uIG+z~6G568tvW>U+1M<#tY-!W!(&$A$$ z{I;xeUsFSwuNb^6CPY&caKJ_Gk_KRdLC8hk@`lE;`qHwdVsE3@17(We>uDJD;!;s&|1vn!T*3Va4jwbYFd0J}MT)RmftedY=wlD2MA?6hG= z>}b;Z^06UVy(}=xooK#e67PT$_kV_6> z#Rn_bkTYcp-E@#kqP3R+y_yB=!jSRROz{#lBDIz{ar8TLp3m-vam>uFnm~tkNeL+i z?Kb;oHP*K}-Pt`zL!YCG;gIU-=RNY{+%WyIPB}v~kATFqn5y29N0Vp2{0~_eec2=p z;@sK-uye++fxdg#nb20_6r?NdP|ID9e3vwGS7-MxPS}v zc3*xgN4E+VKl9WZL3z)}s$Te^{2H1tnc5rk$LrIdlRG&5lfTMv;1DjP_L=!5G)=ao z_g3i#T)&783o($6=H_>>>bM13onw&?oRHzeBV0^-9!MC|yF%o0`s#1w3M)679{*9+ zWnt>|OSDm<>F(}sWowX*H3+p7rl9)M_KWtP1woh&P4!eeyctNbyt+#CYi@T^TeWw45Gg|BV}rN zF;}HqH>--iEy+Grun6@i@)C0YB=^;`9rm|Tzzki?FiM)K-x9}Far8nuSE*l-ZC`#&{n5&EB#)o;jIKcSOtc#V#hZbENYJ8ZlizAE=3v_X`@nNAZ z)*2sLb#bEcVG$i0YR6ofY_xl^E?#V0zEofFQ^*`%Z}Ho&!*I3fVgs%V!jg1KYqWdx zvKjUlQTwIj_y>GK{)GN@tDOm0EA*A`ge~IqB}?pAp;0Tz zHP@b@JD1ykwwh~1(~rylMTZeO%%dN?-2Mb_7glS}g=u5Wrpwxy8&c&j`j$PaGRD6N zZ;jlk_E`PBUHKWDKIkg@lNjd!LN*lA@tY7l#j(!*A!>9PO?fc?efr=NF`5ot&q?~v z?RF8@FQ=9V^Wlbhy?v8l7p^dDtkqaQY4ujjjkFu>$MMh_z540=M|l0(D@5@=6oA1l4_Wmt5W8GGFlZaGpRQ}{vnc4L^dow8n1XRl(rpz*p$tHIqyZ4A;n zZ8Xw}+IdJvX(N!Hrwv0oOshsZKpTRzTpNhAOshazq76V=to29g(fT3H)8KC?WNL7| z5gb}6(gdvpX_QusG(UejQ%Tg9)?BJrh73r9L%gP6_F(?XEW(U{(Dw#Ky2bs89I{(KEY!q;k6q~~cC zq(e1yEbrB@SMWI+SU0{8LqmqIKq4(g!nMH*`_!dKPAo$5RV$J&79#l+o=4nAEoeXY z;XHgiItR)7vyr?#3(0FUkQ@O$a<4QZc^=Ci_mmGw*EA#t>X1Be5t92aM6&w=B=??= zWY;7lTP7gc09QXQfDz>uU_iN+QAp-uIJr5)ku+j3xjKv`w`vmDL~bMolp9usWI!d7 zLX0ezf+6J+F_v7s7fA%hlMBT_atcO}lcA&JOc*%R4;VMo7llaPhb_qTCk&wJxjZD# zVh~MFVDwB6LK-&d7&X)F7&Oy1jG1W*hRk%MgU!G77%|hjWF*&MyiBc70hpRGTBcbT zCeus|kLePOj;S#WNdrd6bfJpme2j@{0u(T&8jOqSJPeDe8lz$=!=RX)7!y;1fFu&* zVv69*!Z46PD6W{d2ww|d37-ld3h&XreaTU)_{;be{H6SSem398PvbA-C-7tV;d~X} zpD*DH`E0%qpUlVd;k=zUbEmme+&{Q4xlg#`5SHHHUge(Wp5z|mbZ#$q4|fN*mD|j1 z0?inx3(i%a7YxoA|0Hov3}5Uvuwr|0%1 zr>x>^IjF1UGIWNxB>v#u1o|MI7{CaK69NvTH31BPI645L5zku{z)*-I z0<8F90StyXB#?l#G=MP?ivyq<(H+33h*<#)i`X{+auHJk7!xrufbkIH0~iZ2EP%lf z)c^)Uv;{B-qBX!F6*LUG$Y~%F;b(0*(jT>DNWarSF~T?6s!Q?jAKGH1CpA!wa6$vw z2!GQ+H^TpDAROUitp(`^+FYdXXrLP5hz7C|p4C7%!ZTVU(nDGU(tVl_=`L*=(z`U! zjc})SG15CUkdJVi2Ko_h)g~joNdpB58?;HNop6md9`mFyfb7r+b4K72q-g;^(xd=# zG{*&yqd6vk9L;I~Ihut4aumM~AV=}<0pul~2!I^LF9Q>iejWf-ik}9Mt@u#@#3>$U z)G59n7>o4Hz!;=Q0-#dyrB#8E`1e9!1kz^%!;wB60KJM&2C9*E1%@I$7#M=|p}=6I z`va9o_XY+by*B{T6?X;(V9Li`hEcPqm&G9!E{j1GPhEylGhY{2iS*h4dfprepy$o2 z0!xrK2e4R~=LS}x&&{&~=yP*j0DW$r5I}F6M+DH@=E?vTDq*dL76_~U_#MOS4$MUV zn@reEJtf#pi#$*+xjlFn6*0$3SS_HAk*9cZpgsG_4v1l|;^KNd18)#^5fM(|5Q(o1 z^~?rRQs%ES06UE^jW7=t_DpH_4@(;mcFxy`97`L@*o|TqFwnyy+Iszn9aS#lH)USq zH>?`o57lcsZRgE ztP64+7fhO2a^62C_sO0)b7^baMROoO8@@8|(F%1?`ULo2MSf5iNvj`Hz;8?uqyL*6 zT#u;Me4z0RA(NrjyK9{pykbbo9xr@vz25S&5;wpcz_&;BH$1vVj-60Fej?;p_)fLK z)3d*v2O#8_bInkQDTaj!c8g0)SdSx2mq594p|m|xL)^kAa!BLV$(#JqnKb91y^kzh z*Uo7ZVWzY6+_2m6+xB$}wFxY`prHvn=8JaI^Ei-I82uh;|wko_}DA= z+v|AfVm{h$?;C1N9aQ34ygP7l>kozTdg?y=aYN6vk-qxaK1H8)$bK6bj zPj|j(dZo0d>x@24>#g5ChI)S!^1FrHUxcOTBfhmi$n_Tb4+u$HXxt0-YI411Km5yz zqsY7U%RaZyvzRA~rmo7KLLwAoxOI!9T_Bnsj_SGki=sDi5}vvl?`?Y5F?$WCPx!{( z$#dI#OYd&7jwVmw_#JSP?We9}-Lpd2r<`wQ(IT%tzbE%XxNRt+{;^qs{DnY~H}+1W zLLGNDaNO;i!jzTV#rmo+_HBwD8lh++SEqNxC<{35ZW^^yh9x~t`HPjCrl&cS(VTS8 z4ZMb^i@OL8GKL%zufLL}xOwwVRQq0PX;lzVCqtv@Q|yVL1X1Xi8)GhF|#UKg6$@7FO@{dqwT>>hR4 zS&Ff90KL7Aa7iIpkh)j1UT?HNBI9lkyL$#X>~yFr+#2-IpZ&01rah+>FFo;;m>)T7 z(VUqu75kQCW#`GxEZ?HmX8PmF~-CO;fI5VQxa@=e6rq7-e zM=5OSGmX%maMHJzT`tj7LvE~cZgH)3sN zV+1{L6ShDD!SJtKPF?lP<&^ELXI^Y$EdhfpJb?EBI@zkEG2*fDdciOS5o2|I+!xMdzg{ahrHfAMoBO%t&dQpS6M|{cPSdp?x*b5v)u};mB7?W_2(jHXyQ)g(XNMHS+L{NKII8>{hc8mVS93{%S>2gJs z1=EivVJbp+{D=|x%6jz-twA*1K<-nq#=4PC?N=xvf80=%{`*70cq_Ex>Fwuh_0%z4 z`OMt4UDo`x%BO5GKZ-j~kZ+q(N!vP=SM>J3D*b`CTC5!4xF>br?aIdhis{NC)*0Jb zXFNr|oyzU{&gIHvw&1WO=U#+>`37NVfP zGi;8=YSj~lgCSsO=8ljM3M&gSi_FpC%t~c*`Vl09#d~JXw@I;Q)b+oyDpB}kC6eW2 z4LciF+q**jrx~Wu>s4HA5`ea@QqM2JI&iwcVoSAIxrH?FuoA|~Re1m&kKCk0(T1^H ztdMusw`el}Ey3@RG|&gH*cTQ@MA1W8xhdge919xjn-?GsMfaMzaW7@kgzsWg>F9(Q z%Yn<49M0T`xH$!EoA&7-g7rK%gHo! zkWB_NR7`kUVXV(v3uM`Q~OO&>!5Q^a&mCvo*E41CAAQIm;#V>HLQuueu z->A^3*!9%Ul#x6Qze{e`3OGN2fDC!%)6?&j#AR|#Pb^nPl-Jbt|1{EE_H#@1ps^Q#bbfVm7V;-wukwb`QS zX@l!ErdG-(zY2l7XOPW zJ)1l5o^qqe;O)Jl+r?-bKmBsO!pt7&`H!qOe-A zn@<6ZN#T@z|hP{RFRua^}CV-H}ed4+H$RwXwE;8L{J+RNDXp2?Wk2zkvw-SBw?R zB!3kBt~x`&|7Y~ebTYri`=_5_4S$P%Y22`gIO=ROi|NM3+rzoTCJm2@ULVPgb{4xKU@lE)yMfEsxN3gM+F)xJd|^z zl593`v^RX0P)!wF!UYQS9o?|m1&x$87Y~$TvV2D4LXBK=NLh4A<}9e+l7 zS3fGLec>d`rQ^H-F8zV|)iIp)DvP!NCjl_%jq8>cgG}Hy!nytXA!ULS6dJDI2`ux6@PGemF0Nt|gvks&V&QKGmia&7{~{qY zcf0n0<`@Imy_v)@#;gZA*7HBetOY_7XwVu?X7MAmd!)#7rdWJ8BghnJzY(}33Bq6d z$FLu^q-V;O;GrP~6KOx|w?QEDp4nOEvx2+4%om1|>ltgfIj9ia8k>6_-TyLxJ#RMl ziDHQCxG+?^0%p`AZ?OSJ`--+7kXG5VzUo6fb$ZMf)1X}u^=8+Kx~y!P|2YDC{WV)Hpy{*VuYKi4 z_)XMKip%FNMKonDh5l5<#hU@C2ZRtvop=${?573UY6%@48lqUx(Jr_jAO9&BPzGUb zv(-4}&g=|2G0#lT{E>G4rBLnDmY0=LFk?!ee0N<|&qps_s6>xOfK?`rw2i5eXIDY# z@vr&OWC(13m)5swi$&ATt*l+c=(P;^d36qP$u#$X96^p)C5Otx6(?QmR2918A!h{X zPW34owG`op-RZGWbkxg9sZMvE17H{!l6#{R^&p7a7$yg8JfDhfv9~9IO8?IGhXoDO zW;7yD-JF?oXSP<9mK1qQ`_oRR>fq|>d8hgT(6$KN{9v{^l(7ajTPa$W=Y)GW9bc1_ zvTFx8s4 z0z}KN1?MnW+R&^a0QOdYZ=++wv0Gt|qQa-v`n4s9XiOb9kW_WXNM83T+FgBPi~{fo$9;P9zcA9=Pv1^(%;=-IC_9^9>S2-YODn~U$4iQqOZ}{ zD?q^XbS~Mx8@!FE_N#=;uzy8d6}3N}p>+jDg%EQ$&tuUjW?kqa;LdaO!xfK=t zcBVOGxPgJ`%?4(to=zNF!#pY(0`eDEepOIjmM`l}F`lzK>tDAG=)!LPtF^@8%s~Lp z1?G5Ky(r&?Evoi5)kk}7!#e+dVp3XM51x==FuRv(1UKTZU<=VQUb?zjL-6(+44NFw z12NPw#FZzwy)If8p(IGb6{x(9y2Nn!1#D9iqXAH_FN0aI%+~`{XM6elP#jdl#yaEd z>i@>rWKq?viHXKISX$knsUHsHsZWb$gr2!&2F)L!R_S)P`ZaI<3C3O%0B!0FiCdx1 z9iU!^K>i0RRgoQ(lwlGka~a_6;yCI03=PND?;LFa#kOu%L-Zxp>ie85idQ9RE5Y>* z2;Ld4PP2&2RBxuPv1$z+Y>%2rr~k@@(U+stNd4#tu#HsqEw-$2Gu0TSft{!WV86Sp zzMNKms}^y^bgDs34FzVm6y9Y}9WsWhh>m>=l3d!T)*0T+BHZ0PwDNj2c2kooTM-fg z57lj29GXI5??d(ZS_T%`)7iWoE>lNNLNR;dwn)($^e$_l!gph%>76U!HFf1FS&22g z+nG%>yE{DJ8L#`Ov$h;K+J_Hl%c}Q-7iem5!G9lziWa$ia6b0AN*nw|jrGM%{xXld z!3|ik&+qrZ1`Ms1-___Y@&JBJM-Jx3#WDp|{S4m%#7=?xde6FUIKcObY=r`>d*U(Q zcON|55yUwH1eVB!Q{$oBaC&8NjFl=LE=-|gf96!0SOuln*c{%L;PWp8Huu*)^24)e z@9=yF9ep=8DT*y)SQCwy8a(&8iNBEh90Lqx{MuCksG!^Vo&k=BQBw*nYHGq*O4lR? zFi$r1>$w?6C-%ewj=5Ymo6G94KalMk^-aLQAH7M13C|>2?yObCO%DoYv*~e@>2a)O z`>bmQ2H+sGeq(1-C1D1!0uTsbewZa5l4KdI+eX^d0Ao15)&bM+ro*X1BK_@6i!IBPqCzNF z%g-DEdn=P!S<2*uCYauhgX3_iN33Lz7y(6^;Mxe&sc`^Q5g_aCvT|08%g3nku$N0) z6*P>y9g`*?RvVCd239XfoxO33F|T|*{9TaQ>J1mkfPKI>`zu_)Pmg!_i~uyjpu@Ow zwqwVcBJi7F0WL?41;1gO8bgr((RD$-zf{EQch8IRfd=(!*%E+P@%;%s6VO2~O)rqBa$KQ>a_&)?hpZUBwl!b|hcl<= zp`9UFkFy(aJPsd<;!<77YW4m&2zY@^I&c& zpO0u?lfio$0t|MOV1N`B0L4KgV<4;5_JRCVn}ZW=Lhn+8n^VkLo6~;~5#~(RJ+W0p zCP2`u&ssC=BhQjJSM*a83WC8ty8S3z2tPTtYD_hPfB+Q(^$-)SL@pFzxOtUZ$%UdD zPfHtC1aMx#)|EY^W=zZU_EiD+DemaqLo(^aCt&!e{eU3A%>O8qE%eCEaplB--Ei`f z5{=7~x3GBz3 zKZFHTVJAd($1lMaJ$}MOM*~|y$2!W2N`m1*;CUFlQtIsv4uTyg7#ze?S_U6R_)L0@ z7$?DSIKf|3z3Tpt$!q(**d!X&o*F`f%D|Q% z{y4OSV3X)Rb$~H>zeh*Wy$E{NnqNg%Izvn0AqWfLKZ|GU%2A7HT1{A(oL^i}24r7j zzL!O1iZ!Ad89{S?ui)Pa>Gc1%LRomG-X$bIHp`WD25HAwoIHrg1%B$9kUJ>lY*byV z$64QdQfDw8BU^HY|NfGL!-o6Izn_i4Ve}z`+RHjrHRyZzicdYjL%aS%hw4bcslZK$ zN7f`W00a&=2Mn}A+mHF2OQ5JqLLa+)_uoyhsA zZiSjPx(B?3-J9uTSojULo5H8W1*w(UO+RV8vb7nR!oW{O5 zeYyTTlFnC?>Vs|~#uN3;b6b218jYKHpjRo`E07rcEn5W!4D@<~oa^%CaCkZ6Qr!D! za7Q!(ZRF9hUAc+ylC4V0%cagcaO}qSFGWOCWodHIvxFYFMa@d+uB#I(B`SAz=}?#{ zG4#0wv4)|Dlt8Uw*5U^rRj_ECA#;?+-Q4Gux>>7qV7N!UR}IJ ztrpFJ5YcoiJ8;L?uDbWNu#lJt{qQ!m0K8kqf!o2m!IU==AWKd*jl?$D-?}#~5H6ZN zWHE=%{vosAQFBCG0b}NJuD>)82KTrH=WKg2ZF|BQMemddp;oqQ!i9*!XF$d(qpAk% zf?wXG7F+zqh{a!GNYXuvsl^Y$xxR_MZ&3Ttix9Et(9LQHE62HZNgx#L@#3CxGT5e> ziFY==ax)ff1P@EacDkWaMaYiskYWEht5emU8fOTk|7#{DNh#WnUq~$7SR1yW2CS>=nlx}+B-spJw7BH$=aH0Tz?miy<5{;Fk zKA|O>rF$ssF(-mOm4>XNHT~4^q~BW>jl=++FE6f)(fV`Kq|v|G`;~bX)NyoQm=_l0vtN6KIZ7ASE8S%lnGHe2i8Y#hXp<} zPNbhQ(h`D*)EoB1;II}fFd04euHG<2L3iz5P8#_Z(yLt|@zuYQH+s#n6W~>RZm@)2 z*JC5NyQ8hBM=+s|PvZyM3~y*`d^n(dsqHkUZ%a#nQ)%ZC8;;UoQ{eb!Zh(IM3fml> zB0~A0F+D6CjsRaY7g(&U0$-LBr;^0x&Y;H?m=O-Xkv^NYJ{D`G^5{6l%(Bj=pC3{4 ze~Zr@`bY4&|B}hgJe$ew5#oyA>{#SsEN^#H8mOy*k4tobk_-(;w@?QW4q+7yS&gAc zQv`p07PWjHh6AtvT41(j2gNrwdE+Q_Ee{Ri=+G$stY>4`{{Lg|z2l>*(*N15I%v?LH|0tqu&D@X#u zqoNeMA_5k!m7=bz=nA-N*%c+$wF|qhx~}5)eeRS5P**>@pU?jO_$9BL%)N8Vx#ynq zoaa2x`*}YL+-UUe)BGaOC%85F`(ulVe72Y<)PySy`^p+&(AJ0N3E04TFq;Y0{HtcT z6|7`R{ddrWD(nq-&;75rCZA8h`0J%97LEn<)L+2e{I9gwU!+VX$QfR8&>V2A6qFPf zmy}S_k*qLk9tY0};{o+9M{jK6M0#nSEuSj;AzNts@R%?W__y`i zix#GREsVv}$I-!*#F(F!hLrS97I>0bE06sebKJ#Tm;a-t&Nlr*#?>D6Jb}*u7vney z#PxyJ`@dVvJL!e9FakUFz^VC{?QjM)-z*x*F+04J?!Q?y{enql-_X2%2|qKHz?gFI z_25`=@lJPx^L%@;zoY;BP+HqtPx+TaDNHQ%rs2VeD$w5Qw||F@6EM^K+%WVy1pdcn znehxn8vcK6gmE%b_NeTi?%;mIOOUQu$G39Xx?;$)bp4Gr?nt1+t!F~?1D~?HBarc- zgVA3NWCr!0eEg)^tNbVQEnzJ$aM0PJ5-2 z*LudRjM@(}07K#QYodH%4ek5N+QDpRSy);H#32%JExz8G&OA5dv_(bzG85X~(4Z(< z7nJR${U0Y!rtUXwit7 zy2ThfeQ~ogR(+~2VGtN0w+Tp0wpU1?GaU&rOo!=5mfs{iN8-GMv+CnlBn(A9-va`& z95p7qMv2=6J3FR74csm~W|UoZjjlx`ElSuPvbZtZzOrd)Ygkl8}Z0L+cKF!>7{Vk-XdU;`QuGQXrhf=9l16p+=p9W5^?Mlj-1~{3_zeq z>Hr7H18vddXyQVg7g!{If!+6lPCI@RGGYYsFtMkrRI8e;137i@Uh82vA*#0Jo8cGO zZ|x4*)*)g!3A`%*0Rqd-54H^bmNQ@FaHAu2GO8wR_-c03p;yguM?(jxJa6xiYXo-` zK%;j5-rnLty4QL&uOD>q32Ria!5zzB>|GlW{Q)Twude>B^$K3|CipGb7g)0oY=FvE zxgtK3&J@GgUyvp#B>jaqiiiV+%`?3wwIG9e!^v+S>%*n8!AUIAdG<;6?%S;PocKyvL&tQbs!dPTvFQ#Yz~WnT~+S&9|sl| zb}#MvkC1a*;Gl)H0t#V5?C4uRWG8YM=wtg0kphg?BCNP62De&&|>+0fAavZ(VSnUBD0m zLOZYIhZ&1GLy~{m8G^o!Ky(bXmbY^AXl^vfU|+pgj0B^x7uf{vx`{);?T-GVv6if2 z&Qb<_A%V{Wp=Wef1bxUSO`*hp#2JlV;x?L)FNLKr(vn)%{ylzHA3`EOx2A%9F^@z( zFFbG!g${fZ7nuw`tF*G|g&C9bn-|(s>KoP$o-#FKroDf|+^Y0`Ig4gig4u0mawfZ> zmD-O1v-F?QdxP;_d)2@G7u4S`qZxpVVO|0$N@}ue1I}gdL%YgXWmq)F0swybKIZYX zhiUeMxn(3iZx?9va+?_JTc}~;*t9P`oc8RCchH<=(Xg238sn&w3l8z6-S}3g`xdIl z8S}=~Ze;q|?xj{@?)$BsqHT+`Ph=o5;IBRi5t$#q6ec#{!nIF~)UjlC0ql{qdwygz z9e-AirV}?C6*_)(n2BOni%DCgMtx9Oei<#Eofk!ax+WC9vdvN~wci;jw!z6?i?LA) zTE`y7#$sH(nBAkH1XkTTMvxE7t_y$cz8V(27BT##u*x#z2s4g?a~Yq#A$>jjSc>Ih z90!aVY^mPkm;tp2c2_Mdbf5+2u>GJE#Bp9|c+YW#IL;dd>OZQ$KOAt~hPt{fRp-Lz z6~F*phm^72AI5m@nD zChWUn<-<+F+du+L+mhf;*6Z#J%w#t8K3avN3O3i@Q} z4Z)=LGYCrnNzv)&(A%GcCDOnz!j96eYb_zveq~r2yXy6rl;^6ra0I8u#0%lUyt|L; z>?{193q=wm$TLp&v`+UlM#!V)E6UudfEzDoPc(?)k)V%qyR63lc#0Y+%;`-^{vXOq zGm}vYFq8!7DE^b#XwXA@{{lI4@o$!I*DuaSGxb+wqhVnDi+(T8mCl?cH0OvEHMpSE zZ*YeHpd1;OG+2%|9}6>NX&PTIIK3~Q60@^99dL-jesM5eIIid2X?LB>7Pw%2-G(GL zav|KHc>vUyT;V-*W{|+>>*}6l`O^<%_F=*VtxSMo=@t6^!02^G#I)o!TKf549Bi_}xcFoueWPSNc zwyO`_{8e-?w}y6FVztJ%;}1mxglSh^ZXMZdqo=5h%vtYN4iQI>p4F| zw;bpz?6^Q*VfDD6tFVp(LLGm9>FFPAO8%+-Qs_(#bQoM84_8#$%K(0U$BFyNj)N}J z(`V?;Daj&zcvOh8^taSPmf*EFQq^ps9eD!nX~IdW?h=mE_;tcNbf8)&pyodam(#9N zyNRB^FI=E4r9v^A%ofn@M@>%k+fu<8L<0q719c7%%GfsxX~MzqGwP$|!f>8;wedr! zDOSIpeX~eC(w#Yrr_{m18zg;@Gms{yAo=yQAp(N4nA+YxMhK%5hk)k!%Mjsul6mm< zKqY#ez2&%i9COjGTvijM@ zc}0%(WfdKv-qj*pitkq({9{-hXZ}r;8~Hb`L0aAEBBKa006pi0BYF|0kfFf!KxCl= z>J_eB$VO}+wFV?qfG-jz`YbuL7#6g%8*}3;93ZwXDuNvjTB*i5$sn*GKfeh5nqOwg zXCn<4DggB)F6k(S9q;upM=)|^6+(F|w9xol!v=;riW&-#7Ycij^!)8JFn6pz5)vH* zRg@+yh>oDf3047JkbP!=V4xeAR@&ewE68U#(rwe==9$D|B>YTKY)b<(fk>MzjC~xw zNQ%T}Q9orACRey9@vVk=_3=ru9p#Kuit$k$Y!w>ddv_r4-W$-1cZC(iT>>Lj(zjig z zr_QPGxFafEBP=xV`roJrMhFT=Bj*TnsQs0k$JM;e!XKauUqMxegiT~`gn4W1P?-B3 zA0yPM30sBtaNR4_BUdC;dhLMf_=!RRx2-YZR&b>Y@9_Go)T7rVI61X+s$k&MemjIm z(cPfspB$xiDb^67H2W?=w&<_9yVdQ)H;V$_fA4!#MX5r--4+MHnU*W`H*nZ#OpgrXcMNZv!4*|zyV!!CP4sg z&=KJ|Y$42Zra6i7t}urZ65<-wl4pdEkwFicCw41=S{P-m%cCOK-#Nowpx6DrwC6kZ z%NGR;8h#VL-m30x5ytfz;3@|AAM^l3KHMU#MgwJKMXU5DVIdmr25N~GQq;6l!ds?b z-6&mPSy^tF;k`+v5|u$aE<@(26Mq55CkIYzDsRtK=(Ak3-2OAh zNz@)31=^O4!FqbBJ=daOAZkE#fLt5u*`l(67Uc=CG=8=e)~6WRLotjnPMkHxNKSyL zKxZ+2Q2p&!FLy)uZb5<}40iJ0*a8+=ZCQ#z4!xudr04iB8;e3l59=d@Eh?c2d#w3e zUaBxfvn4N7*8IY15>r@vWRU=RoK5x6Uxqpuo#-WAs9hV2P~oV z(j8HD+VFzOLUWc|k|=2!zSz|zWP-jc#^qj%k(2 zB_@%Ipm4Fa@PU|cDeuKtGc#bm_lKSB)$LdvnSKWvz5NKy0nCUlOX~q{^9YU*DX4}>R8uJAvYL$x8k%~bfryBp2~z=fIx^k52AL#`pbJyM9Myk@wUpj*VNS#sGTfVIyW!+?348EZ=w0;Vry|4Um#2V!gy^r$B{lx|pIDK@cmeJf!mUHpQ6 z2tC~hjpaBO6JkhblMx+V!6(wLt)Ui!Hvf?Glo+e8!5g~sLxIJn=tK3~4qyd>lKBU@ z9S^n)y^b^QUhD3!*FC+C4Y!3z9xe-1ngkF6mO7UtiL03=zUwNd8=i?yJrelo$i{iu*mZ%)C$7}ai-tog;ofsU)CaES(L z$KF@V%%ZIZk(zsRZal5ooNJ}pe#rB2=CddR?bw`a30vutN6qE-W-s2XyoENl=_`2s zZpz(@4B(Dr@g8Q?e~NA!e#bf<`Mu@JI|<*0X*Am>Ff z0F(s<4P_+_H4P=jPRa`t%LSlfv38zaSm1Kj*A!7ln3w|`V3%IZq*GzyUY3?Ugsu-4 z5yz<0-%4VHI3D?{sw2c*T(G0QtRA^C>Wb*}t>!q&h!WSp^?zrS*bb;xTC}**P=e{D zfgXq!K?bAK-$wB<;x=fL55mGm)T}r$nu~Om_~g2i5dbtW zIj@;%TxulJtCw+Nn=hZJKPxumw%@GIbWdhfZ$+rNZy}#ule;S0<@-Yxv<8+$H$5$N zdQR%%oH~2zh<>SK`lU9cF3290ow_VLbwzgSr5QkuuGCJ#$rmL!rQ9vaQ5vb(s`Q?t zassS*5T?F+0+n~!lE#(w%}urQdmq|+Ck};t&T=^W73TLVI-ZLt!nEvs*wT^C201J^!@Ytf%HR6awz46^9s#;tk*_H4c^^Jz`SO}IScJfHK5g>EDkh@6u8&$4PDU*=tYM+vg@X2d z7F7vW8#Oj8z`*`1NO?hb+AdLP5KK>iz=OP+2tA=(*heXiymjUj( z_oi5ZhEEL*YpX41YY}*|hfba{aV#SCSU;(?=Vk^jg9V8<-+;evF62gmGT_97c7C#v zLc4DpjpOTLfvfnQ%T->R<@ZSC+iUvM?jbp0^wm0jeqg}sL$mzKhpS>Cbu}eM1S3I- zw(!4${@h1=NqzZ@;DEAyC*6CS`4V;9m%@YCx{KzNi|uO1H^Ti;xRImlrPf~a6_Un> zsn^Kj?Hrv;76o+$FJ6g-(Crv}_fk?Hae_KUFYd)<_R#K9F<+gYEb>u*?Jg!%4 zJ;Pp$O~T^6j!uq?NjABVxpGySH&Z4{s2vqiQ9f>LK)O2ZKE41-I+qwVT!WPa(jP;; zJr6h0lc~F~sI0iK973o!uSkE(prOM@;@a8hgZ+8^K2;wko(l@m?>~R0Q8CkOz;bw? zUiXCNacLq@SqUn%wP#_-xgXv2LF9G8sl5InT30GgVI935{;f*&h(oME4}Jj0sPr5! z#;Zw%BJzspA5r)JRp2w>&{O5R%_X#}ub4#rOT^#OtP-5Q zDNSrq@tE}-Ur;zhI?JJU*&ICt;x^!-gxirwfE?mX*^1xhp z3#6OsS>wcn1Y)hT5DGpTAl7S73E?4LPdP&KfZ*zyY|Et6sYxaU@hueHOI zM7I4%hy*P>XSKIoL_o|Y8nA2yTx5mJ;*H;nU1Zt{8>?)@KKg#WSrSn)+0-))dz*Hp zqap_mu_Aj0cuShbdZ$QwcwA_h9)?Ie6CuS0Edw77+{9<64f67#fFVRn-ZgH@WQI2z zIV8=eboe+-=)kA(Hk#Wgh&Ep~uQaA6Wv3P778a$^{qaH(Z8@foG)x={&4SL>r9@R| zQLF&DozLm6X&$Zso{d>0S0Zw~b?IgGn##Qj;q{CtfzEy;^`XIrneTJ zw^h_@tF_oq2mn)pj~W1@%&u~6f}!?dIk5tg|7Q5cGW1V&ahu7X3Aigr54iCs z2#S9tGa`ou4~&-irArUmEe6iCvB^CcSdneh#Z{UJEMoH_Rso=EY5<=rwqOS{hS%DL@4}mnFNb-nygIbncso04$a*NaY(us%D zjY=5+8YMsH61P}jS1W?iys(H`?g$ABMl6_?rO82u^>QM$&ru9ujGgeUKB^7NS5#PY zio>|VIFvMdmd}KRT4Q+v%m}MLb9*{rt78AzC$BY&eL`#uoB8pB(@UY`;v)ECtJPBH z?4KTG4(D*+DEkqQIj-sb{iQDR=;C5H5Xn&=6yHS6H$r5^YgqX*SC0~vZ7t`*!;t+bi z(t+5?k>UipeXc0ef@-my?jM1<75>rSoeS=)li&eCWM}NAtso9_)Ty+J-$H_>4$YmAql;f zodry+u*`&7tLb3BErjBbY2H-n9*SUjJyX(G^9ba-?jgC6-V%e7Y2Zgl{ya8IA4cLF z%z%y=n4@>jf=&=WQ+$mE%n_?;)(A0`j?O{$_8lX{KA|oo5yR_wN^9<~=ZF>m<>rp~ z-)vp^qSmGFhYQ2Zo1E@p06A(VxPP-nquI*l8Lc%dMu>w0Eqy6_QNOSoJGE|nL0|SS zoRM#>S6^mJUSLb;c&!v^@;Sz6$;W`aAqGmIOhaBG9T_d^(?K2JH7@&?h-vkclM7lV zE*zcJG-}MK1ry3vJ9@h3f@4Z@Pp9M%zuL;hCTm)H&*VIG+Tfw%Cu0Hb?Vpe7bdRBb z?l)i8&y5V1H?L&*7?g_i!Q6D?WSxb^bXD*9b_-IQ_JI#u)$ffca+p*2waLsG)nh zbM2a3$U)EbO`VA9W5i58RDEF5hb5wF?C_x)GX$*|DE6WDGkL+mlSdAnWC2rJI=x*E zvCy(5IW8I`CmC=q*wb=Zv}X;B-rlZJK-GT@ji(ki%?FRLRibV>z3L$WwVlVMIZ4}I@ktR_12ys!* zTl*D5LA+oT(D+t9p0a8I5pUn4fZk*+93PundBm3X?(boKVH;>bD;iMir(BtiD?{D$ z5QEg;JrcAxt5tN=sam}?LAjGv{ssKKglwDIV(~Z}581VhS(nWwgVph)#RssyJE0~` zOCH3j!zPGFjK)Tt?jO2FjDXqZE}i=l5SM5Yx`SQqR4JcqAy(eFoy*Rv@y31(4-iW2L8?rXVN&l0{3W8Q)#@~TbsoKYJW&h zqGzXx&M^HS*{k)7JN&P&X`rJYghc#zp?v2V&7ik3pQ@&p{!&)*_YFpWk=RfK~x%UKv=BJfpopStoum-Ncz9v%tJny#h2 ze*UPCh-sOj0doT+uCRjmB3^x2tOHr*?V1;PRXM(=^@oH)`a@G>&X)F}BYFLwRsDR; zto4SfU!;9Fg`~e?B@o$T0Y&l#^4LoqaXk=Z^}$u*C9sP93H|jnC9M_vVx7}Z6=%^A z6;`Ad*NPH-xK`X6+?2+qHU#3bB|6+wTfs{8jPs~vtE&`O8jHjq`QNuAOGfBB1lKUR~se7d)dyOkYMoyYMaxBu`7G(?@IePNYiN1h9wyLL=tE@5G#1sov z+2krZxJQWKb1Eo_3yz>X@L&gFZBN`^6O#2>;8IdZSQzaZ7n4T2+*rf>a3&!X4r%e$ zn0IK)@`yNkZKD`T5B^0T8R_dKjO&Dr^?F~DC*rPyBw$C-yB{LO@w=Xw_@LEZF5H|z z^G%^ollmqz^4nAviBp>u9(cwKx^E>XP$-zS#UcO^dW;1ws!Hh_Pj`BNGdWtwhtYfM zu$pRkFhr(}$$b+87ys#ZhRufQ_xK!Xi~ zC7giqF2l79sVu@uD9txyMHc4QJF!yqqEwvhKLewIKmHb&*xq~?zQ{3a5Rv@gRpOIu zGJOkw@6xQR#7otgYsAyw{zI;le?$%@^wK1CSBE$j*#Gyh*SW_-G12DWX)0=QKfD(2 z;M5OD*lM1p;{5MJ4ep6h0K8ELI6GNAvK1>Jqy8_f@ktJsc1jwy#sZ^4ofD)fB@MK@ z)ldNd5Fibg_HzuP_J4q4InBaZ#WIAZ^Z@MuOfrUsZD}7LMpxWqi=pZ##JB`zW$;F! z_!Be({l*OX?5~VYcPC|N%oJ+ISO?P9@t=UU8vWuU{C!G} zu`$Jza|hPV>0`twb=3{xX}ATDg7ID-(ya3%j8)Ay@Y4NaE=QIbL8(-E2lo=g*M5S( zFLH9!fn#FQI2D(9&1mHK!8+)SsBal>+LGg*f`}9^^$+S>tb#$j0zLC{t;bch=A5R@ zck?l{x;ASC4USEYqjgV%P;pN^bXJ|qRD`F5-4m{QB%X6aW(amBcYnXq~zUdGb_B;6u)$y5lIj??vNQ?rY^IP@M z8F5e$9e-6`PER~74&(LzRFk-%(>(p~wxN__e<%Kh*MFyS2b2HFsXsg~1_879J-JMY z;0`^T+|8@wvXK>v-Qyf}eG;)yZN5Lb6GxuZj{HGAq?d5{BTJGW!!ZV(ddSSSf3QgR z&=+rrHe`^doqrND)!b9!AA@)UPc8QZMX7P!VtBC0Jp-Hoj}6d7xr%PF1j6wfT*W|D zvw|Yjm`}wIaf*?ghp{Zb?y$I&I{O5P>hI5pMc8j5*IR~45?_;x*^>=X4}B>P!G1Fh z`bN8MMV?L^6PL4!1*=t$Bp(i<=KB&O$$nf!oQuw2QLCOvP7b0IaVf{>Opv4m z8$!wu-3rq#t-m=rLQT?3aJ3mi=|E`;W`G}(E!4adJnZM9ZNU`Zl>DxG&FkV5{J~Jk z0~Giyy{=nxZfku!1l`miyin(!iH9UF3YI5?u5T;3x^?wI{di#x1%YSs@qO(e981ICzd^se{ z)O)&$Ve=Axm^r$hO;`S(f(CkfpBPI`)j`2+|NeC4o1l7M$UkQ>gGdX4b2GA*x|S?V z&tR63^bEhXi%olF&J6skb)7#^=ChqGMZSe>OHnQk0C{p?Wyt2m|3R*tY&}qii|Fls zN*?9En}rk_lOqzNnMCAuGD}>k(emjwOmxph3g2|EmV{oHAN^~lto_v9Y7l?)B^O{F zX^6ho;GW}4hFQrpqf36(<%`$|Kzeni1)YT_`B$qt|F=D+_(v3+f2+q-{JI{a0hWBa zd!XBdXi8MCJU0JQ|M8N?=>u1|Aot;5zoE_PNR`xRG{kQi>aJGv21&;`qXF~}h6GhVL;`-o zkO;Gy_5tAKReX|~I$Crqd0BJ~F_3WwE;k;fh}T`_w|_Gs6!fe1*Tu8};Jb z^`8i0Oacc#BEx&Ug8BKSh!RKGIrf$zo4J#cHsz*M$Y(-~2@&Mj51!x|;hEIlo*RFj z{o_}Fh7fev%jyBc!OwzR{YasUEgfvdz>FyYq|m>JPy5&A4#qXA1v7fh9X-pAK#%Ip zfM*&uS@T!Zv7R+eX^cbsQ&uUYPZuIp^fA8#lt=^Z?%ppeaRTv^R zBk6p+QLnB32ysIf1A(hwIaJdpORt-@-EB37W_dx`+xcu=R##QD`L;$ka&P`li-7<~ zNu`uRGp_*VrF|Pn8FuZ13p%=5l7g{@KrSFOE%RQ)(52PVF&_B>Qe4tHCSWr9zsf$R2FRLdzHnFgt(oQbWE-!n8-u<(h@p)A}E5% z*woIU?pm6l4Vh$3-n!EIMhC#$&|jf;mliVagTmSQz&UGy9z`qws}M0G#r5d9Ap1bL zmDY#Jz8j{7&WF>b*q`as6uT)m_m}i$+|Fms10(JK4IB=<*Z;0P4|<43;lM!T-q;Rr z>@Li6&{LT8?k8d~+4mDKH1BEKS}1RC>s}8#p}!mZs55$OZUOA8W=ZbrrPB&hy6Ll&LA3gIK`Y9%*S zUk#?e?zMa{-T0S?XgW2O6LQkQN1PF`zaoIe=NSuF9n0Z)^BNOt%Bgz=ZwJM*6d$-r zik!}rzknCT4r|8xI_Z<>OGi!~VPDC}tD$rHdkoW=X??#qgjJ9<|Ls`TVfM2s zz3#j-0KM@a^CM;+Wajc+pfCgH`^2oDRiKFB&+S@U7ShKn_=xkX?qThdP0j}C@03?7 zZB(lpq;O81S|^2aRC_fN4QLJaWP=n;k6xdfio=2YZ)=Og5>lw?jG zLT@gUl13SF*rP&nu>9|}F!OsMIt2)8Lx`FmlLIkbuZ7=gA?sjlX~2ArsIn4ocq>~W zVMRpxc;tof^Rb+NHoPWIhou3*Xoa90n2q_|8^_i_kqVMIy|jx|1Kn)u04#U#+$E#Ww-J$H>^*lH#pV@3CJ%%;+nniXY4hy! z*$(g6VCyjZn5w}ea3KvDeU2zy9>R1=R{9^`&n*5eQ-9VjgAoRx~^QIyszES zY8sOgTMV#RkE#d&83T_om{z`{gsFz0Qz?Zrly#T@7A?QPNptR#is;0|93xK0VI^SJ zMm&Z;>KdP2NMV9RT>yFe;gH&$$_ju9eaQ^q;EWt&Ti{%#uR1NrSp?h}oBs1_Py?La z>ug*eiHiaWjL!Rx-JU&%FX)@0;#}Bq(M7aN_gp4eON7KKK}mbfaX}UUR;wO~>_;cY zrUb_o;AkK+^6~VnA$lUk%b3dZiZQoMixp>w;2p3Z2ITYp^cqM2_TdddAVS%90_Nur z%+y|-v!Il@-&jBo+lFx$XZCE@IcjSB-?LOSwc4;{0YB`7SvaG{fuY-b+}1HSq!x0f zY2YAI@{MurR*aC}JL1`m>H}NsHg(=DNKSC{8fe1P>Dk-v%nnuiw@SYY;tX33tjDUZ(B<7Y5|TZF7X+ALg5XJ5)aqwZ?7ujEw6jHI<3wHG8Z z%b}r!&c2+RyG@M?Q-^g(PXq}D*o~q1HLy2WQa~r0an-|T;>OUvzr`hxJuWwrqCYT( z(5bDl!RljINh7f6DxWoeaWK4$s)b)Do zSKG$qPayP+RJD1lv>BGG0o2tm%1H?;gR0f7H%Na*BIXN0w zKpFTP!{AM|ZbW(Q9OWOyn!&E>@A2*hFxY)OK-;YaN2?Vr(gfaU$j4cOyo-S553FJd z1~q%N|NmJ3&yRJpf2=dohe5w~TsM@Ku|RQe9t|x#{C_^m*(+{4f0+OLDA!`Xv7Ygg zrv1vX&cyn~dVqZXR1QUes$R(slYd+V82U3R0AyYR(>KnGpGQMsiuw_?AYTKtwJb2i z=S&k?Rzw?7(z79@H2&ilLm0FuwrF$q0D9A=(u6-nMsN+(^>it9q`8l#htiP+ zNONWT&@8hlWGD?xk_xtrDK^oKnS3a%eK#vAHPAM#^^2S5E^BRCT3-&N1G0|#x)pRCDaY)T0ACt7ZH z&8wfQVLNE?n4CzN)q`c#We^P{C`9k>{7Xb6-PdZ1(-Qs~@{vdsJ2Sxpz#1&7r&8xP zn0CmoufIX7Cpprj2s-nRB%_`^-oI6AWE&1oD0h|xD(Y%Ro}OA)E3k<=9XdYEYheU>$@t}lmLh;S+23+dU7OgD%_zi2nIsv-sGMZ2BF z1*L@q=KZ9o^zt!7$BdgeJ)`GZ#YII0MdW%qs}C)yu#4PS+OZ)bf)$baVL(Mu(grS8 z0|7KYY&|F-5Et`JS#fE776QPj??G!VR|}#QE{~o#XccMjp8$DAVGFvnYBW{-NeZVE z_u9?cE_$piG>rCFBg1ItI)I|3w{lG-Kb_t*u&N0>lk2k9rn$>qwU=GCm=4cNiJ-s! zDJM4E_rN`0Vw?@97G55%UUHxGs0oh$?K*xtTUvI3-)4$?vAXl1G#A!zSh)?Owj8)^ z$d=}DS?agXN=7JqVr&(4p9bLhvq;%SE1#3HxS}o23Ayz0bJDwpqLLEux{RxiN~89- zr2?*Ii`ci2Mjn$8LZ~xbLf0OXDqyWSc1$Y7zqcLHS(N#L^eT16%0m!9Kk`LsR0QUt zx}uWVrA7J0n8Zr8--0srz>Cs5%zJDYO|J6raJu^?=^KDhcORGLlJsyCJi#ZVGh7g+ zAF?kAi>9G{VLDh`#{ABl%MUS9Un_m=xHa!mOB}a$Uj!W0Hx)x z%;SQW(K*Jfys8#h9{VSV&D${#he(NBc2)XHubE?N)#xchCn1u)b;L?%zcCZiOIin{ z&KTIoxqQ-+8O5%_QylrOr4v)@m)QGVYMOJ zD+D!nE1(&PU2x3DypKg!q z?Gb#LAp@;7jy|_qBS{ueAetqH=FjJ2qwMu7%NMmSZCsq5juT-0^1k25t)U;EMU?}w`Pe{xV%Wr?<40Ew9ts@` zT=%U`7DR}wI!8kQM~?s#?&`|&!+OJ)c9$bb_&j?S!#j-(r2m!fEc6SJ`Po==`L&!Mq>c}=mINOSx0!zllE z`eSl( z8_<@a)dsX>`cmM-E>dhKJZx>Cmk(MO=~-k4bxuwm!1}Tk9#)+WfhxuJVdF-RMzrhL z;TWihj!mzns>#Vf@7D(j9+9r*tjzwy zsKsC{s4oTMrR1cMK>*(W?NKR-q_a|t2qREKeVNw-4ZAMoorOT@4w5YbR7V(hVfTew zxS@uc9)rxuKPRPG?1S+xswNM$*VBP0+ZeiJHJ_Eh)Xw}23yUgR)VQ#IQRDn3h|}6N zYd{c|i%`;0Tog4su{Q8wv`6uV>FUu`c`QfG-`IAktBmrM9F=CuS26)PgAQlPIebB> z`gx`d>G@)?{7b*CgJ^H38&V6MSZnSqT3*TUCTOY5&z?DYBX3?W<=z1;_k#CI0Ogjwuqt;;eS z@CWm8D`%%=;Yz%A6dgZoh@kx@K7>co`D@QbXGPgFG&(f@;-JSp29$SnX_|f3QhS;` zV?agv1psA?h{5dCpFTS-*2|mpV`ti_IxjROdNosP3z5?nud~TYh<}L%jsQA27MWfKUI7q&Gw|8; z{8C#ey|@YT?qpKmNV;`%RwRuo236|)XW*djISw#+8?4|A=N7!@#c4~c2e}qj55i(SNZNzgkbo`BcCN~2}kkOp}o@6k?z*TLSOS^M|!f4UM(il2&Rb2d* zg^df@C=bUdt|r%pxL!)K3MKg(>~}FOccI$!qO=8DH36md18mij+=s7bis3${@J%54 z2&A~31Ea&}{$hSCiJwXTR8PDrsiuh;;U42|yq1~ULG>;T;;QRv3#0`o=%a*RE;%J~LI#+%4 zgcQm6<-M_MW9T#0Vp5NPEES*@3+U~WXaVU_=}L9y8R>7q`c}Q}j(~AAp8yyZh&cHJ zR*%Mm-${DCk>i4OTrfNzKj=3ubYG@gIr%Kip^I2Qr-U=o`fyEpdClrSFXt%tG0Chz zocYKREk1U{9(hMAj)X`w`djb_T>cnv5>ZOy#K5m{FWg)BJ-K$z17`+L)RUFYSf%>9O`e;ys@=T|Ej|` z78m;Tt^A7t^cR}GM*no}G#72are*&HmarbhJVjH?&#$eoa;g5xQCCL}(&a2vEIAFc z&-e1BIp(aKZ77~OXXWbIzOeBi9l-L7xi;NqbdC=5?kVn-;GD^2qi-3oF4^WS_I+50 zrODMsL+kFT?iJKk9XgmzzJ=P?)VVoZpb8;uIy1;OUu$v$tGi94jx9* zgJHp2q_s=v$opm^J^4Fna9|W_E8}q9uw2zg$qYxfV#~=T(bV~(RN@_n%i`pX3<+*n z#U{#;>X>x-VH~!a&519l$Ft=Epkyz{K*-;^AZ&}e-67Az*K6oQSw5mZQz%zpYb{A- zat)pOTWlW;wD6(o@lu?|8`h~Os^s@Mni~lsxd|>c}lve4uDu}%j&irJo zdzD`IZ9mpPHR_>0@+1tym8jEt;&^Dx$8WZ6Q4bE0J57emq3d*d0~f|;C@HLcJ=-eW ztM$6Q09|G%vzhnVtR{|;PxFGI9bb1q(O^F?48E*{)81q9chvny(EjgfH6;0Adh@mn zd@V=);w#?F(Y8tQBHr*DwdEWBIH&$$ikyeWzd{|bO~#jF-T*$k;!{3ebxfCE#uryo z<4$~!0usx|VHco!S+OyC60 zR4!NB+GP*U?@|X`CO^Z|K(`{(=XPbaI_XNe(_mbOwxUI>RWue&Iq>W^vu4|J<3jgZ zz3#yofdj5$2rcjc8?J}8!yij>n^IrHv=#Xf^(N`F&Jjo7(k9R)x-4 zvRxIppeW5YLTcCR^1YmT|1I)loN@y-FO(D2lwI;^oN^9am%*gfdn6s*T8IudNN6|7yF@=?7 zzhOCfa2<9YkQgj8k8djho+idb;@yO!9G|@q4A837Ao>!d<8jEkTLmV+s?jW0;)Ntl zU9W_so4h8Ud=mRB(bO+K+tL;Y!~yeop}m*2JH7SSQur{faj~m$Nv=jJ7{ExJPvxWW z`2pGjhgf+kRA`X;&*&IUk6U{+1f1adKR`j!TEwO+Y07Xm=YJg5m-K zKps}BfpIW87_JIT)Ef4$z80kUC}tP19#QOsPJ+n8(xNi-e008&lHx*iQ}k*7e`JQ$ zM}b$aq{zZ{GgvSVs4K23L6#{$dIDp|Sy&6C1#H@kJ|C$~K%ZYw=P0D^O*s(;3&w9T zN$)_TZ?<6-zWW_Mg_`cllVUNpLCswc_WiPgdh|~WLGZC)jRv}+dq1$+Y3C{=eca#- zGSkEJtvN_z9BGQQXmtJT1Px;mIAOxBFdJ?8E-sdK9tCOl<#oB~+x8|J;s65ibNsWD zdO{SPmSTGQ^#laZf2-HPs5e7m?xeW64$!hcBtHi^wu{~#;xDe^YKz9*%(nU0(= zuzbdb+xLv}=+uN`ayN|OJFw#plE0U$)j6w_9Xz!kRXQbzw7L?Ar(%c8$=)5cyaKB1 zor9GaT2`a5%$9~b>G&~y4#&&dIQVOnsS;fO|)W(C7P;55QFcs0BhDSO$wp$GhxwyNj(BS zmy>T`9UE$urt=jQ)Ez2C#DcGO`MmU1nlBBJq-idsr?0^uXhvvv9$ryrJf}CMFLgC8 ztIwdS!vmQ=?z?J(5=zr=;Il1RKva4)aoQxL#L%u= z_yIIF&ISx!oUJ;!XCi1V@Eg_g{jgSqkgo*mdmQ5R&n-~0=W zS_2LrJ#LU4sJK1gh@Z0%TF{Jp^FruE4D5FE7b*@O{^q+EDpho}FBidH-M!?@aoA|> zb#gncdBtEoPZyl7I)5*pgub$evZu&*sg>XIM>u+Gt9+EQ{~*808}8fo5=`(jrpvL^ z{TZ6%_uc$t8hef(r?wpx%P|&b$r5tB$$NRjUUkhgVi~WN|5^SmH2V9gWv9G}X@~hd z*>9H()c;nwjc%!d^J>;<`A*)jk6Lb%H_=P=@=T@`y@N=`|?p# z?EqCSX4=<>n)dYoRkp}S)!%+9_cyVWrEsDx((#L!6Rn0fzg*|xp$XnWQiihh;PY}6 z6Y(NFNs%FT<(aPG)$@CXyZz`f|`GbHK}`&l)Xly z#|W8-42GS`3cpZHWJ2+&TWUQ9)taHOh5I4wczWAA`g(O&A6r}y{U%RY%Nw4dqnz!q z`m{qCh&uh2q!CJ~I-pQ_7F*AP>{Ccp7g|v<6$yHr9BnuxWYDYwA)p`IlUPD)Bn&si zs`jNLl0pXuD7>CGJV#aE%NbO$Rvtu-`L_FLe2yZj{nyDyIdXi7HR7DX%I&=2d3wns z=hK@{!L@eDW%5w!9vx&|-M+_!Xqx*` ze1y7kq_Qj6=)wBz>2&t+d+7N|N|DjF!jHv%`1cK7YW$uhH3T1vIW}6c$_h zIvr?J`l`}WkZJMsEr{frBgY~1shK7F?vGux>s35 zNvn7DePUWrD2@L?xeqH^JtIvvyo$d!NqSd5p3cm-Syk8d%5K#0Ep^u-+x@)yz)i|{ z7?HZ^b0NYJ+qt(hEH$r;}MZ9a`XkY zNMp4C_k-axXlwc@Z16?$YV)hgW;EUBRF$KwSAX-mvKG(&1;0@KR6EY3_D|AHBM(=bGW@BCIyp%c{{Lgxl3G3x$qWg@O|mQF5Jp1ZJ1 zspwJ#xYA$gh*ii{JGUxEP94YDWS%xXsN`>3ul(lVr`q8CQk5!g=lHw$zbQEW@6_^! zvPpHcEBEl~fUlJ+O-2uVWDm_?4_TlM1O(6Dg z&8BG<-_fRL3=XRPo3bl#vyZe<6r-)Hzo*@ADIbwELn$Q;k!ZCd-1ZE{$vJW^SNf?H zQMPB$P(P^pINLcB51hn=%WZqq_V492n4A{aUO}HQ@^m0i8ArQnln6TVk#dFlMY`<} zq<@6E&tc2qEJo9I$rG;E9qhxN;sy>6X4K!Xz!L`0=`{AJOYt;DtSF3z3m)Mz>V5*g zr}n|Nc9K3;V7S&9jq3W3l{O@V%19Z3nD9)l=->bwM5)oN9^I?R9NC|5S0_Z_r*@BG3iOc6H~?wgYB^2lE`L9*O?D_O@{#4b|G3&tcv&>`JQ*7J11yXeqE zFd}9?6yHWSzGxlLwt>GQ=-Z$#gFX-XFz6lrc76wc1HYNaUL1`dbBqUB$IKC)H7F$y zC_Bs{p4BMLo>e-X={pb1i>7Zp%Ta#mxeVo7o>r8{JS`}n^uQ!*ddRZ~|HEU$L58n9xTWC}kA(6A4{mID$AcRi-tgeY zhF3hevEgM;Fv^!axV_;84{mQb>M^2xQi~ir=s_h64|-4u!)+c^Lf6O5uDsd34&`-j zbY6qojm~RmbEDfDn%wBN!D~9obg{bN%R2BDgBNw+{RKC6;Qa;Hcd+-@mv-MC0x$kU zHhA%kX{!Da8w~%(bXEU^_926I9<~*#rAKVXASJTMbqFcsD%Z;{^@`_g>oBB~U={W< zn8Blg1=UI0%|>I+mR67b;LEmikVcufORhTMdwCD9Vm`qH^YCl7F}yKPZFxu8#L*F_ zvVcx}qBxkm{{wgMlqNTvG~qd05fk#;IQ973wgO13e5(FL8Ke$-&o%*K2;ikh^O;hjas- zuf6sv->23&cbyM?2Ma7%s_#`28V0jYKZSmXHt-RgN>hD;pJC6!Z-ECy075k16I~32 zMOv3m#s0y=pBZTafu!y_+K?Drz@}(TnW(0dR&@-{0wH)XyWnraYNF$lB7-6_I^0f; z)lvlu3O0R@+I!MOjqX9poD~;M@md`yPDbr(&q;$$z{R2ug*3p!?iK6S`yg1ujt zNfX=Y?xh+*nnt^Sl?Je3VWy{XJc~9hK=4N=hv^fvrZ=sP)^(+lC#8WDr7<67eLI_m zK zR2P}!DXYkwW#Z_2$iL%8;0c~opTZ=16lZAp6hPSxKW4VjBa_Y1j@aZF(BU-4Y0T!a zYB4yLkQ|em8k3&pF;ep6I>c)Kh?vF4Qvsv4bFw)jx;fd+-3@$|2<8B{3lJB0c_}fm zFh9h!mO_o21wgtC5KZQVE%~aRP^VPz*9_~^otkz8nQ40}0@XB=%^VBRGT9uJ`p0$r zuFZ+9)p_80>FyhBR{DMiqKlPsd831WS0KGH*&Mm6K?^9?)g3B85q+;`39LJ4@}(yp z#_IZDrrAx6kzI5Mihy~Rgm-*I)~Ucen5&dJF6D!M%yPyNGKj+ z`Wi!IIBj=J*=$s{X&b&}v+aeFkHCCKn=ate9IC4nb6LSy(S*-6(rtdI@ zKu>&W93KQZ^wDD=KkwxV)Y0&LVh8qIfvFFM)DToXnwCGOb5QsaZS#?XD91Sy}81v8$+%N<0fj@VS0dN%rw1>M%+Nb=S=5Xh}xI1 z{kO%g;=2t@li=Ho?WyrwQx(-LH%aWxGE)i~dLye}WgM=i+>~WG?^I6E7bW4<5ikj>hZ#Om`sNXhRTu(Fv&ht zz#n@o(xBSWu{%svMqSat$raJCd{lXa8D0)3zP2NvIrlDDULGhgZGyDcO=y@_(G6EE zAJeAW8t*j4>2#xTdLm0NG>K}qa;)^F(z;1h{E~P-7(l`*#1Rxd$n+3hep$?;h9C4X z6yMWF&+2ZH-d8TorQl2-6P+Jo3g+;$+g=l^1Q%;alVIVvFwC@%ow!bl5O@}x4&fMg zh(zJ}(&_3$;z0@@Y3f#em*~fq^n^h;gkORrLvEu>Na8T}iPTUe#j8`D)Knymq|~)y zCcQD*lumVP#ccNL5pg8wgnCPeZQ3{1l)+z1KGnV}PNtxvVi9e+5%0$LJ#jTC#x~z0 zeo40e(&x11CKDI~RJsCoWR?^!R8KSwWcvq7S!!C7>p4@%j^vsgf;uma8Xpjy^y|lB zI-;K4Wzu_UR}uN25UpgiMYp>iVGVs9H!FE7fOB?v;hbH~*2niph%FMzWhcZfvJF>ko#~UJ?&YGdX8QD2JKQX8mXASbjY`X-lww$Kv?V@WG&~NeTZvDa z(SJfUSkbD1%1bz}uF@@_N5V)jjW^j?#ezpELv zUzD-f*OtacKGVF(1$tY z`Vhm~@Cq05jx$@j0k*Z$ARYT?O)TOMbMG~EVo}RtmkMfU0X@Dvb|mkPV&%Oa>7loC z|BkQ5n4H9OYr}(*xSO@bC6rn~+`|JNJomPca6I&p8g=;k>>$5(%?_UC8v}7;8!r!h z8oLltG&@6Mr=T4gJ-u6_wvpo9(vYSy*XN5wCQ22fQX;}*_j?n`wc6}pWxS#dAB zKvk6j2Xi{|Fq*GHYKF(RoXRQA`F#koNKG=rpT;?EJ%4~<;&C~1bQu?SyS2ESo6AW_ zNi1*iIX4%Sl#-Cjea_7nC2&Wa;&bMH<^rtmsbGra1-5ZBD@Ty@o095rdBe@jWeq4n zoQYmHbAjS!_8vt-vd7Il28p#EKXanuS1$A<1M^Tw+^YDMc|;fYF?$^4{0uNc;Z){s zbN&sP$$1`_0DOZE!lNl6u{xN_x$Cvr%iP9gkkrbT*Lsw@kpJ|x&DnNdtcR$1WzK}( z-(Bt~`{%3rgJwCp;%`|>R|jxGklJ_6mdn>T%qdjTlM~scFXP&)4R_v90YYk0#DypR zg%JPZ8|O~J7%IvGszOS>vj}PJg$3!(LU#(Bx$cw+iJTjS&lB#<1uPC#2Kd=1YHh45 zzK!b>AW}+lzsFyinujq9bq229WbU5jA08_5j3yir?R;uccCo%4I9?PFZS&OPbFz(_ z7IPAATA+1yj*;jtmqs7l;?Tv!F2$#e>Kyh+_U+$ICJpu4X?jtuo6EFMoA^Fv9t)mp z`UIgSpPH=f*XK-nL9M%)ox0uFUd^B*e5|az^I7dJrirlSoH2#6udM+};Gn5&*GneQ zgjXc8nFmdk(EI10u0<>>NHnOK-yzdDC{Rf-L)f_>noM>68Xawog&P^FbQh+7&-63& zX&4c7ORL;p^!!I^)6I$|R8IT!BI1&FntHMEADJFQ>z8rmD2!)n3}!Pvhg_LaVMw zVU8b6-=KY~_6)B`X2Cz1&f?1wH1-x2G1UZI%Vsa2c$;o69 z7+wfgJ{NxSfEbQi!2hN~hNQRo&H;&1@E0j|%6T69f-Ga@4}!2c`85}xg^FNSL<|HC z0)w0hdHJq9AR49OHr!l}{{Z}?NT=re5haw*g|LT@o5NnDf%O|9Iv~8}iO4)U2r~!F z<3&g^L(*eXQF1cIJr79W=YV3-EZ>o}mz0DAGE7>|M#LN9G`bR9SOB=JPfiNQWa1*9 zH^#|NAY7<0C7IJL;f?}o+7e`HMmXjhG^8{uUVcvm5Dz%QUoKBkH>9=ljrSQ3-6_JE z=4ax)tN{EfVXF9MEJQ+gvbQvEuo6Nm@E4_dN_Q#WlAM0t;*!!igzL=bqA~)S(ZxNK zJ5>9CE|kK5ibpCU&wft&DZZ1im^S;%X8Q6Mdmy#1_JddZ3#5}>!Q-RznN>D*Mj^Gk z#n-N&n|JgAr+}O-Ce{_ho$gV>H{-gPpgO8j9fivAhG9+n3a~|Q=-o2YJN8P) z00b-sx&;ZWMOgC`amnf83)9mlAO^%EF1?W^R|rBb49Pv)66T9v5UguVO(x+1`MO}- z)kb_`{Z+IP8dDG#L5U-sCM7g23}G>CMJ`_wTNM}{Y!XUyk83*7(^LE-$$8Q4yMDgG zk78aB1AUMu?M$HOw}6;%#v?wRDf@s3Fyp2OAQQe7uBDV3wTK9LWv1o*=#QAsK^@xB z8y{OOT>rszU4u_0)%H&SuJa8r8D~$n)>UN>4q_Q$Vi9aX*J)Kxcv4IFlxIO*#P2ZC z*Ws>vs43GjXlL$6HJYkr55wP~(ygbaK9&!5HZFAv6!@vRiIyda(D&lS@720$w!E7N z55OlAEoymiVyRsCyuvbLjz$ zj*eV3hqCs4#7`k@H_)m6fl!zSh}=51k%Bu5W@Z~Aeu;0JSY?W%y+DV@h_|qHA{wod?r2?l4 z!qfvG1+*u9)rvJCV}SUSUtBz+IY%xp1BqWwR~tHG&UpheVnA~1z#wd00It@S>1Yk0?zx`*7k0k> zNebeYCvY;E>KhGVegMJp#5gwV>HnK8a62GhLGV6^&m&c}Voz2g=~fH7UNE{Ll_!hu zsCAEm7DePr`eW26na;59W{4(ydmN!yrev00A|AxIC#W$T_6cX1_#CX(uuXl5m=%9% zykfPcXFo7@VGEXutFX*GjGDI4kwsE8op0yVv-U5Dr-WVOq_F}OSBO<=-BvpLvbciX zvQk`(g16C-VWx1}oCQ;BT&{FpP2q2e$M{;Yora7sz0D3D5>o|Q+Dlr&)~*rfU;(OQ z5mTg*0u6gd+(9+hOW`c?Zt*xy)YGp=k;pLc9`RX}yo3FEO!QGR-}}W2IJuL-@CB>C zyixoL#+aw+$SIMr>^d=5pqm~Ohq0*-iud8VXK2zk5x(GuMeYmUMYYfAvnc2haT!fY z2^r5EPl%^+JEKb9kO(Tz2p&ld=?EEFHUrFDC6A$lPj3+&xN|oh_}SKra+%(r*RlhL zo~6>%khN5|S0tl*4M>6|B9Yi zpe}guS5Nv&UFhh=;9v%0s6oxmDo^3Fm(>-TmJ3vx0c+T-$)*)-bAp6-ym5s%pWCPQ zfw8k76~HXq1Uk$o!I@u_4`Y(J&H#y-SCsBfrs8C2-jx>7TxCb}JOv#TJHll6e5Q{~ z!w!l8oGE$YB3Ph@4lmM=nKBRRuIsHnLfQ7TzGmDs6duG0zMjq?s}}G{6CMGl)+4zA!vBu?b%sA z1Fy1=UQbJkeG;^hk)ZC}S?m*o;Np|TJ`P)75avdfMfT{az%XJpZ7r4@B|aT2Sirp8 z<89HwyMeBFo)m_F)+%?3mCntR^qR=wQ-L#Jwec9;ilj@;@|dof44 zDJ~th{{v&05(e|~>$56);fnQ~kmN=RI;;+)PhRv3Q2ZBNUp6g@AS?qWG3%-a!D{;QNta5;vtCk`2-m5W}d6Q4+D zS^6dERpepK6TxK4J%u-9)#59p##A}1+%_)C@V$86nZlPlajs4ODj<{hI$`NFK&wCnG_@Yj-X>7 z0wZ+mBy$|acMFfElVgC2a`+TD9K4t~@Den&*B$53{znd9Zc%zmna*-po| zOMx_esNBR}bDNU|boZq^a}sTz69#DAU^$hNI+|s2d=Y0VN5iAKwSmy_HhHnPv8^gx ztEKarMp4D>g2H&5IzqxzJWdvD;XOwZ;C~s#PbDOHc{NiLoj8(-L&!z)-UgS9a#326 z*Ef>{jzR#NC*vy&|IONDo2I1DhrihENX?D!X6SBoa@YJJ?r$ttv-_7}qsNEX;g)r~ zl3~#nvG6t0(o|_FTk)E>3@h3Zy8L8NcLw?XBEG$?EYnruqJw5h+8$30ga*j?3F()mISQ9J0;3^>^FBpG3G<%p#&6b`MtG^i6Y!vMG6S{%nzRR~M@B_!J<6oF zwRGfpgGtNVNbaWtEmWfo4)o{jJp2$GCq*gbdc(&QhGd?E0vrJq;C%3m2T97TEm_mtV(3C-Qm z+!4)Pk?1WG-C^{0XOg!_3>g%~dr#>{{LhQ#aJ%pbI9&m4-fR-|6^^%yH-|QOyAv0; zv_|>e`#Mk$PW*-U@ds}MJ?|;1wd>3Ow|(C{N`Bw1P8;2CyFEyFw6+sWql9#nkd6{~=cJ>AbTolm!`vtUXw**FlAUf8;6?%N6f_LpR`eAtOCEp4 z?>dY-B7XlD`L=)jmjg8aSO0Rmm&b>qcvg(gA#UJ2=fhvX$^T$)=_xtnRo?Q|#P0oA)8q0pEBlHJi-Q+c90F)uMc zv4Gn%U4V6OZL7Biej)|H?X4$T8UZ&1R2F`L8=1dH!tDiKJ{V(hG`}1~et9q^17ZnYFpk6Rc|2YY#-w*_BEcPJ-1sz?kB3RZ zgW-W%k8_KS~*JKneU#G`TR`B&HO_AQP_Tg z-ano@swa2}bGQQC7ukOb<|uqSoyI3j;6OSvV|(IvCpSb;wk14b?@@kD1vF2!fcOVUE0v`K>vQ(a2!;>fP8SQ%QbD zQ*~iS-ylRejvk6@^LR$-{Q2GK@XrAGI{&IZh@RXVVD?E#gMg)CeT+@WBk)Ao==r|! z{=!-6l@StVUpPK#g7N?kU??>|3Vw^54nGxgSrbVYAG(n4y(J`oP9_7l>wKVp5M5g1 z0479vGN~j(Bp%>&(>U34_VtNfIxNBv(h^ki9s% zeS2CrH`a&TH-RDcw)-OjsPQ-$M^9$puBdXYKX155Qy&TJpddaeZEUG4$_rRCtMKMH zcMSiDS}7tn6bT_J-Ek6r269Es^o|bp#u5o3N9R8mL!h94M7>@ybY@+@5}!os6vE5) zBCEh(s-R7W4Gy-rNn8PC{X>*+g0jAm_7GSYuVWW}5PhNWpJdJ{VhNIN@?)!M_)%#a z3@dRnsoY0*oY#4ZoHNAd3A^G6to?7|r>N~T?KdM@_57zs19K*sKM`288nJk4-KVT! zX6zX3kOOr9Xgev{*xf#oFPzhT*hYWJjW&FO62BnVH_{ut4R15V0mF;6EWkXIdWK3b zsdZnnh?7PEZu-AT6R4YP3SyIf5j}>SuUN!qMtHTh7sU=l=GZYqH6b9_pM5V&L8$y| ziulU-1s%4*c!c=i$!tZKv<$61Ls=&b^(_06)E)g5A@TnDhB=>*`Uz}MXX!bxpb8sQ z!UmY`?iRFrGb{SCnpkO~x?J};8v9+VlGdV$DhvHTr8NJM&fHc%R@5VSua(;V?F#bm z_2Y7AKP@V$SEKq1N*Z(?rR)Brf_$ZZ%-sQ$ws;G>dKhc=e%pMXC=P+C)bx-^pgaNu82>*T66wy*P5}UbIloR{{I=RnRnQ=HRr2) zujTu{RBPr++<^b5wB{=F3P4nuG=}nPYRmatspRa?U|sq*>CIlXxt#An{-NH?BfhTC z#-VUxulCRC%^tNG!`k~tiMsj&6`E_T$`mO_8O5O9l25D}6=BDa&Z>m62sy9^tA*uY5G@MII_BJjh!E+aIs60m!l94UIo12)7 zn2q!Vm{k-Lb=R){RjpsR&VQ+5*Z1o4rC-E`lQY)O*s7OX{zxyU1B=Y<16vek1wR=% zZyYr<>Ip;0AF1Q(muo}m!@U9RXxdFOv~fY*s)#2A&Yh2LS0WpPY?Y7=Rj{96*l4fl zhZNx+&?}`|2V&3pAA|LOv%wnc^jUnq2Y3~1P0eQczif>6nD(13`*h-YgYN$e(|q}# z*=BGf=f7!}q3mF;w8Q^?oAGblWjGXn&lPs~E4LZ{F~h^U{W~)}X58OATU@W*yxIS7 z4L8U&H+g@}6ko2?^?%M3PmeRT-@kl|@fWrie4|0}74feBY%%_`#lW~}u~z@r4D(!1 zMkyZSd{yX*(En^PV3{xfZ#2z&_P{;qw!8Vai$`9m;YGO`i(vH<#oC zoA%Ez5+!g>n4v6qM;B@*Mdslzpq1M2$mDW9gkqQfx~^C__YO zA^cLDirmL5-voSN12T5=^|-}z?No7D{!YQKje4)Z9Ayu`HG5-_y%s`VuXfHb)zH(M}Px?Q8zBak>* zdX2&co2IhuDF~;d<3mgbu6%}gwO-xw7(B3bRhzv3mZ$klt=IfP0Bfg~rx_-w^;-7H z5jaFz6Z;p@iFZX8B}|tZ)p{Quoyw5aUnVH|{QNP?aF?z%k)N%ZEzR>WsCiC$ijt2$ z-f*yE#UKpIyFCaNxnD^GS_Y;S(Y|`a%N;5P;(|LoXmBkT@BlB95;J@mSNSqwp)^ZC zoM*`*X|)orO>14!EZ$qIc>ox&b;}c__hN0j&-Hysrar*at*g6hcJCFdJP{RxL zt9~lzI@T*sIv`xpg9O-wbfl!N8EKjXuoDTi)+)U}mG?pwJf^IrTQNwKJ|%!9RPu`g zsQh|d#0N}E=N`JUXL*pHK9G*RFGeciXS}1w@F z!ru+-O}SqKqT=L55G=2~1X$MZ`U#uqzHbpmxBWMt0J{8|!H0LRi7LNAV%fPfVjmiE z$;VEa---d0`??{DZCxpiMm-`0Zx`3I`)-qBk;FDctNMW=UJuSBjNUS8FaglygTs3_G5tpAR~)jj1DC{j;MtV`3d3PXLcyecQnfND>}>grSewAwKO(_b zsSl$AI?2q!PD{%Lmf6GnFn&HIy`WX=+f(UVh8kXHj)r?6+wo_1Fvr^lgbAbS+o=wa zLhSG}5+IcH5fuEM^esapLeRnvEU-pu!fmqWwvJ%_lz`9LAR%i&--!jEmhSRaN2soW z;D9XcSaejov1Ro{&TchW@(v}9gfD$V!PS?R5Lae=h#knwFc;S=c* z6a;Kvt@I9MUPfzpgfc^fa-2?GF`VpQNzWPevAYZv!?dbzP5cchO`Eyvj07AiJ;22D zam>}*+*M$)XQiX4JsxCw=F7BXvRFZlA*NVr2sCF>`Af!^82ertjOv_J`G92SA=alg zf}Z7E5;wQLt~mDf1<8tX5~%VaDU_KnO5flq5-B*`e3rU*-~cNxNo#nd^dMZ7M6PnJ z#Y1)b4}+x{f~7}d8A+kq?>bm0Gs?V!9r80Lq3l#vI)#rI#D8~Sql3*`QEnQQ&JdS) z0=@|W-;c0el6eRkpH9NGSg=i4%wU_)Uq_{7;>YZi-Hh3ptHOX4|CZ!!#iz}yrI z(%t-qEap06d;_OW7~bX{?@7uHy-CnX6It$e##_(;>T<0jL_Xdo5>A?skf zmaq^#5U9abN=vpQg}7%2voB@;qSsPDc&OdvA=ZUy+DnX620(nOF-K=|Cg&ks<8oL!59qM8~@RUC$0;JYRFitJqxUkV_921-bz_mM#|w zOEorKN;+cM)2Rt)wdDL(@(aRuo;i;&LPSaeiHDYsmSd{wmT0MF119^nAB=jzNc%_2 zDfI46*@WK@jFv@eKSuUZgCabq(i}?hKN|J)-cC70<8&hm{SE+3I5wHXg=BKvYPJL* zP$s!31p%bW|FrzoC`58i2F(|IZI+P291j=y+w5PyYG^Jq**iZqZ){b^>l>IsYwTlPQ zCnKm5n{7N{Vl#UNC}W)bL(9Qo48RAVNhseD&d-CeTM07~0U!Zp8w3sWu>VBFW%A#6 zQh+m|P`MUChzJYzvSS!=X)20CN&yZb0vw441&Xu6NozVIhAl1FFO=+0b_)a_tPyc+ zT#$H+@zQI+x9a%=cWjX-z)(p{FG4IKk4i;kAF#M&8EXIjHFz&2^JOETmOi;r&P4yY z*2>A{yadkjh-^E2P);F2K>;~|fKIduAD#)02jFj9`JeyB!6$g>_$V=g-d6DcxZ>O# zr^kcgxMMO7EE8qUxO}L+BOUn#UWcdC0e(OFd}MTp0r_jr$QZByEc+zhq%H%-l*#N^3zM(e%a4ox{}~mSs|WMmIQ!* zsZ{?NSHB-u-eKHA+49eisnP%Rn7Y;wq5~tNdwH_~cdLXqRlAWMw8e0@wqlG{^{}5u zsykkBy1F!r^Z$yJ>vXl0Ymo_%(KoD)su<0|BwZJvv;uGgTZn(Q!hb@n>M)i6gj5Y` zma2Gy(fEv|`upULe@bvi{6|(J;U7#!e^FM)d6tGhoKs3f{555Cyu{@c2S-A#0t7P0 z9O2RLZDsb*HuLMR$?89vR}tyMJjCr>B#&xqXr1Tefz9){@b2l6(a04YF+AHbDy#R9 zzK#Xmsk}p%SS=5QrrYzP%RRrf%a1;(i|y%hcI{3RJ9ZhW>l*8T^r!6|yM#y0kK`+0 zK~iE~VRBk3lD`m`j)f%2mxu#I>c?@dCFyDn=6}XS2ojR3+{3&4U!t)5>lPLO zLgNo*O_l9lwdH^46kwPFc|zNfPv_jx5r1jOyFxG*9aoNU5{|@l>Qz=avm~y}oj0R! z()_8jii)E^%hVFi^e4xD=RX|#Jcvmx99OA#2*(k|bXYLlKDc76R#heQrFAe>WSUnP z^*7z!vjY3IM^yi!_4dy!e|jUwHPPR=s{qzpj*?pcvoTzo4qmz{*TL{Vqv(hBLgK44 zDGy>i8F`{eqeX5dfLstA#98VT#NPkOx_{(V>&aE?heu@tb`;n0yO?;;XJH$rO5{a* zl>Nvy3kT1sx~7^B{hf~F1a+WQ9lCVr)|^e=HrJ&&QZY&)kAnq!Bp1qbETW5}-Ss1e zj~wYq-KV7;x^$(G`wUWen=7N54L%7xImqF1#H7$S5nVc#yTK~#>V}-49vM-|J$j_X z02-hr;~$u|piB3z15u8aYrS6lFd5D}F0|)oL3UIqupgY46SsbYV$7Eps(UuuLnZ_rT z0}39@&og|AgbbQA)5oD+yqK2F^a0Hf{!rx>9}7p{9;}PJx%lSdNXp*kldfRMJeD%c z=b8}QDb&5hCrSeNi${e6p`n7rtzgK|kyRQiojrUdYN}bHnwt?DcR1HFi-4D>1=Bw7N4nhX}^&0Y{b*ndg1To{>`lL57u5 z(OW%LEbMOdLZ2vRO0PuFs`{&h?}ZB0E>(YQE>rbuXI4zmsy1^RnM~R++uX&dpIMnv z0SDuQOSy>XLqT)Q)|s_z(r7sv{)HcnYW1#r%non>wdraMR<5-qnvr{><&2)xp>}!RtW6AP3EaSYW;kcQ>)epJ47JloQ!f! zVrjCW4GM_^wx9zN-X%?96bMu$@c6-(E z==!Qp&OYA^s#zqkGiS^em~ro@8XjLy>kb83Si`&KB6Zbw7vJJ-xwERYaRNY#Eyk)z zx%*kSCi8KfUNuyuKCK#xft;mZ?^994iJ&5G^UJI{U(}nj4@tSHo~{x%>+3MbbfBRwIVY+6ev%6uuxkIcUTsAu!7W@8-Jz+eY8dN$XJLdH#|UaKai01T(F zho&p0RG|0FHMb7}ys#_PsF-I_uq%(se=~#5{%gsPzU>2mwZscv67d?q&3E$F@JB_W zid5hl46$0N-h0BN|h&k7YRBe#zXQ{B@Rz)cvygWh&_>`WTQ| zUsMPPf%U-I6IP#Y!*!P zZhwSB{K=-mSih{Z+7`qURghVxP`}`HujAvSlC8gJp04*~M5qU-)B`xlX-~DbDym{K zi@RXn0im%LS6A@?Wg>vS-bYJ*GhfHP(OS%yNcU8^vT~_0USpvXAwh0xT&gir_BZ}@ zY+0F4qQG7^TGB8P*3L4)>+Hn>0qY)eqqe{${rX z;?Vt6dD{Ox`@vxez_$me>xceM(uZ0uv=lZPMQ@q~Ayl%Y4AvIL{}a zIb(wcLUhDhy5q7b828(sL|PS=!FfpyQafbK~D5Nn!Xd0Aiw`&!cR^ewfH;% z=>-Mv@T+DQ%bagHCsYmZZ0Ew}NeX=3JbUNIojasi!WKG@#9ZX?Y*(xSX_bS zT@=5Cnyl6?Ro$Pjr%Co;6V-Kx9_B>^ep0<_hJ|5~!F?a)v8G_5othesSwt4}vAMm{ zg2z$dc7!~|hE<)P@5f@xEJsmj-OjA9CJ0r|rHAPJNl**bB#6UlSgvIx`)Z*@#@no~ zazrw=XR)O}uHHdMz65nrNned`)rry#?1N>NZaBP?j(mf|HG?$Ts=6(;Rk^=>&i1Xc z>_UXa3@}gS(el;SN$i8wmJVv2{+arMis{vBEthoqZD`UiYVxzLX6x2jW}_rVO+i+$ zir-^-O{3TEE`WzApZQ<1jDzF7k0yxCxzDmztfzJ2SY^K-M)~#&LM$1~P zXnWZ5OshsiB`;dmQT7Rt&wsdAY0PO81% zx+uVN%KH;}Ok15R^!ow7T0AR~gz&%)^wK?=*!Tq*oyX)A_Z)j;{~^=UW3$E$=`m!< zsOwz4d-RKrEgLaDHnVqs=$Lbh9K%Y}vT}f4{i99t1r&n2ta#?se5$-vlS>VQt!5g& zUu|i}w_DJwif00}KP}&-HcgL_&BIZ zRQJ&2#kk2Ssk?Gty;b?<8{prb&TR0HqTCCBu5%dFembv1C!(WZr=c6Lf@f!hrd3^^ z++Od_bI0%}tl!|@mEK+(69uAHn~om+81R1CsrJtN@<%ncqnrtd#3@H>^^r z&#P8pAzfg2dT_-oID=(nkkHz4TZm`bvo=|xSkLDz4bYUHzi)BHOmMUu5uanv;j^FWjdJIz?AidpaJGMmq=2C5xu4Wot& zmUArV2@4NxIzXiq8(P&c%9lB|SWaX3y?|va5hF#g&S8t|EVp2MzDPBLHP^HLPg|Z< z)X0~qxK{l+SH~FCF|$y;%la!X{}rm7XlT z4N#(GR$CI;@ROFEX!c>2v(2(ysQPVh2t6-YiwIrSsVehHKbF10(h>1612x_0glt8q zuS(xYO#?Mh5x=r@Q~K|1I`w9tlS;xxKbHA}g)4sVF!!St1SmOg(uFa+px%%f?>bY{ zq2j+`7J1e0vZjlcU|f2Xl_ptlgC@M);^6f&)ZYiKe2pQsgI32#<0?s{) zbs(SH?mparZw4(kWaFD_oCP8VJm;_>1l1Vi1T ziZZS0)I$E^*77ij&+BXzbF`|H*D0?u2pST%$(`8Ku>wQ%r$H@4-NFK!EdB5z-^b~b zRJlDyq@X5?i%$+H^LVU&sQ*;efkk07w1?&?Di5?Sr^dTu2lBTDg6oIltP>&0Pgm8B z00Fks`YImd)2ag{h*3Sc7cM1V!N{iwn#6)0R7hT|KO@4C=pNr#a-G`Y&lxt@_6+wjk>SYJ5ZuGH#XrD~ed5`G}nyVC{gC0R+-50yxM+tiVCm zpJ7>lceV#!NR9Ih^B-aTn-bgjEvtOQJRNKumGKiOH#MjWh{2r2XR; zGPV9Jm5#AuWI~?Py5-iV*iX4uKa}|$E#Izb&pH-ZPvF}*in!CBOxYAvM>Q*qeiR-q zuA+w9;{h*RWZg>J2PVXk^AvajwnhZ&*{o^Sd(boIsrIm?Gc`P*$z;P;YYx;p0#V+xcxna>dQyO`pYEnxVAx0*b#xIs2dpaXEOmc#-XS z+In0u*j#2!L#&|!y}rfTlP(XiKDFyngc;&CmQ!Qhu2CM1PA=DgltN_?SkA*%t-uUh zt-s(JH7mW*`jEix*vqGP{7%e6gN8*sVFjrR2HGpUP#)uC(6WfF{NPNJD}y=qSWj!! z1|3CwWF5ga9Q}Izq@ta?=a>~F3TGjIs<%n{z60bz@A6g$_cfDmT zRqG9$Db<)jwZE97nX}P4-`A%CT)C;8L_B;;DfZEIdv= ziO1k%xgFSr9N8w&LryuF29A_>`T}+f)>gN=e&MlmkU+6VNB4V)(In7NiT8MeY1xH(%a#t(jM{A1~0spizcMU4+_ekJF{>S2Qvep^`y9>NXoi7Hq{4aM-cLr z3u0f1=sX9Is>6vAH-DK*bM zJAaCMHs##v8|p-+K(jAK@zJ+|_vPkcM_J+gGVimwVa}XFXTxG51chs7KK_|fs^QR zc;GYSXlD{>_(U1H?LfIeuU@HQwb}9njm{8-dJ|aS{c<8!;!Wn^Ea*x3elTIymVg-F{*NlY;ap-ec zG!`Oejn3_jvR2>ftK%=r&}qo{!lK#<-8^QKJV*Z_!+Sd-3%ZS;IDY}&V`SHigv2B& zsSFh9M7m&rT`I_zGRF$Oyv_j1+%NP+O}Fe9x?Ndrw`-N#%cT^DZ4*YQ$7Vhq(DAU8 zhtlS1zjn0tL9>pAOp^U+@ItjOeZzm7cskk|no%};Zeh>7W+o+&QZIr~Dm7HkT?y&2 zbrvv*<$RCL`SPQ3DEp)?eu^M~iEM*;HQ#a@T=*nY#7;Siy|n}4pv9p35QJU|1+jP# z-j0E6a;)}LpnRP#thL01NASgZmz6wvl_0WQ!Ktcg>RIX zu$n3Iaf8oF4LGG0O@L*ulCS6Mv;jUjLk6ALDTlQ*;ZA`UG6D_hNu}Aao%0Y*hOzQC z23;=_=1IM%%MAHWc4)D@L9bg0RqcgNyn!3|ij?)p*p*=8d?ShB$RagezE_^gD*}D9 zp*Op8F zIo8Ri^=d;us=UiQlAU}$-mVnGP)wyFu=~^U8|2mtp?ep|!8G84QR1W9PXk0GrB*_%F?1p) z2Daqq;yO~pR7I18RNc5)w$Qjseu4a$k&dN15@sUSCOthZzo-Bho=HXN=|BcbNh(N9 zPj(k2q~#^17o-;Cr6%X4r-EZ4FE2Gc5y-=-pomCMOi#|kA6I@}eiFdk@{*Fk|C;Jf zEC9<&VrnX)6iA@M?R^QtuR^MH1wyQIy z(~&Ge9tY=GVp@x@hXdryD4sOCa9-i8xrLY%1(OyOx@X5YN+#vahONWpsd>iKS%t;3 z3kq=p#I%Jt?Vb(EJO>ue8B+^q8&ZH27l)T`p_S}?^+JYaC(5e#}yL6R=@KYp} z7hQ<65Cc~mYDjD~^tC#}?JRZnBrUMkP6 ziVydpnh|lvs&7Jrh|hHT%FK->^f$V{eav=&+ z8HQ5M4@NWFaG&g_X6shV$An#5+YsEB z&9a`gy(zH9XXFIT{~VUJ&j$XK?CExFn(FN7OnMGVklHYU+_5$ghwYU?9A+3vSufkV zve*V0c0VS}%2xWa^KZ%lSf7TY z+8fAyn|%Zu(h{3uR5QgM&VG4UHsFdIY58rozfrbX zA52^C0%=Ui$GUU0Zglt&%Kp?gH^>E3Je(RpxJc&mZ5%c#HYlqf$sgk8@pS4HTNKNF z*Z!57GK+1+RPm`?t~T7nPQ7l!7Sv&n6It5l^6NUaVIs>J5ft0EO)YBSTcVZZk97P94@EKv-W4uwZF&-=)pX4 zPYat)4N0~f)^JhojfUludsf&JEa(e)mq3r_g%4rd)wZ)f>Xm+Mlff2;!BV(4U?mdZ z{_dayOKrI{_Le|DT2>t%z_N|DZIG7;CNMz7yUo6m-R5Uoh%(_Df6d;X4Gyv~e4E1R z``UV{S#QbKfHq8J^#g64kjYkU&tg-pw)^nP;RvVuv49FNGl0E1AdJLN7!b1uct1U_j4{~ zer&suo$YNC(c&^%KG@-8HBIs>u;10&;{FF^|>1Y#S>m?_Z!k!)?#24L4K$L0caxsn+>{X5JQT(5(#6 zs?ILu?VO|79N7o+f}-&9zSqZKr@SDjp1v7x3)LCmlQk@4Ik(wsG*o^}&$D3-i&)NT zJKx5>FAwG47qgsocAY?%V&f9%aPP1W*|Ixq%djELv2$C#VF}B*$NsFE?VD!niv}*G zBT`%`Eqg3Hn$^s(<)LrOS?MKtEvzsb?JhNysN>>51Z_hTRfc7BATPWN?OSB`XQ^ej zB7=HmFx$M?X2y)ZqakW#5ba%Ro7&E>;(%*qAgl>15c6OOTgZisFP(YR9#5l|*<>zY z7T{CCGvJWs*?f}Wqk(CMv@!=6ciyT-qxhZUr9W z7IHmlt7Q%Q96b08m8LbQ*RX9fY+oq_-A2LnItz2|uwh@{ z80CXa`&nCi<<#vYoYmjN@W>BhVBX2+M@T<78R3{UQr{(#E%{Zc+)lqZy8*YM{P|;!@YZMTp6NO z{hY$z>l5th_iTeOsn+4u+{c2y*MEa~vLGl}>BsviczF12lzY3bUCUccz}tO*>Ta{W z#KmbSJATp@i9;JG_=fOvEpjYId4te(Y`;;j3$o1Ce_@-6mT#n>LArcuNRF!^N0MzC zn>5$HR;gU1y7@>T|XkYQtt~`pI6(=AO6Rh01C$#Y79U z|6uz9-)d=p1>{lRzuWk>=t0Wr6)>JMPj$Z9*1(mL7? zs0~}lH9^_OZlK_iH_%;sY;#&(KsQ6X?*rF_e=K8pf=00A^1pT*hlWy>;Pu)$~Y&o zCy(ney;O$hnfp!q^Vq0)yZ?;R?)}W&XvcKs0`AJ`F&*pbAkVE0lyy?h;-XF#_zdBD zyaTK>$2L^WGPA>GU^l+V-dAmSfgE4SSrl}urZe_j_85BNylpX^Wkx--F0p@&yI!PI z+r+-?&L3e2>zOCy2I&v{m-_280$cG$nG8NWEU&!I>@S*m0U^I;I{Wr zv&Lh#CzNKt!Xj4MPT=80eHeeZS6Reuwvoc4!|m8b#@L`$c%O2ha`|g4Vzo^N_VEky zPyF(ORPsyg)|Mrz6ZVxV!|N@Q(v0=f29Z?6hlsDT&HV7$z?Cx1depvEXLx6K_Da|f zAG6Q$HN3gI-%5*C)#TyeYqW%Q;&0|9eBPp*06#0Mn;%w#N5vFkm_i*D*>Z@EWZE8P zxw*EnO3jBU>sk3ImAJzYf%TZJhR!d5Vwl-r+tBhH!<6S}ggz_pyXtLmubkls9T{Rf zMR-LckgSclMUB(L!f4%Z@{|1Z+jJz`)|*eH6_Dav!eM4yZ%bym)iyM)S-7>^)nJEb z#PBYw53>(OZ+|P-E7cx_Ys=n9j#Xg@k@V`aiV~?PH|Gco)^{}J4g39S!+Wf0s2w5) zFJUoTd&mx6M#KB8DMvZxv{h5hG+P+Md$>+1`2*H8nxATX%?8KHSbMN?^cZWp(f%Q( zShZ~oOZw1$2(s~b)v2jw<|wpZr)Hy1+qa_JkGMAXCTsl6&KH4?DJL%OS+?&hdtZF} zgmMz%ki+_|{WQLvpxPEG$Rcxj(e){oenZ_U%? zP*6x5qL{{nNAOj02#b#jJ10+*Ue049S}h z*!9&zb%=jjp>wiF{K6)n`Cqau!PWsyx!0CNffKQpxoJdL0_zbHb`sik7=>HHrs@n| zKRHH z8|lk|VRzHZQ^PK@__(ll@UG7B$z^6+XM`d0X4Yo=J!-@MV(&ZPn=04;o3u@u6WVl- zw8=RcO#&@#k|rIpT86S|849wRE=3tFRs|U?nBYQfMR=T0P*GHrs_+U9TKA^a>vcfh-uwIg{vXktp7Wk}Jnw$a^E~hOIZVE*l*z2T%+R7_d-4sV z(aT@3ZYiTw$y#O`?v{tbKOihsrLo5G8N=je--^7xq;3av!;2~n&Ap_^t$c+%yS}3C z?2Iy2{H-3kyG_}KYpLbqbnK`2ETG15hSkcbuc>vO%0(;AHC#*!a}6rO9;jhYk2efL z4ZdOBo{T%dJE5H!+If{q={K_d(+wh;wHp=wjy1=m50VGQ(qo(NPMPHI$yAxXpLV22 zYFJB&p&VWYz%~(D?mlJI57cRlG%|CQp$ldGNF`g1L#Q}8=L@;X7@7>~r>LL!d0FVh z9vyEO9?r+*1H7e=P{}>Ui%I$nKo31bA(lne8B7pji4kExE#xdzEll>Je%|S)@pTDG zEsc_u^b_Lo-7!QxlVTswIn10RrA?f2O*kuY2&ve<9yNSI&S>#z_Tp-TSD^&;CD{UY z?ktyzwfGHS={4{?u||BA+D5QKJ{oGZzBaqMGk|>4dO$AC4_8R`F z)Wnl3fd|!^@HJEM7?yUWCV>>U7@pW_G=c&DS}c@XGLaOw8;;Onle2+0L-uMIBKG)JtTt8uW-!?*z zEt1Otf`vnX9o_4O1!#jb>h2JM*;6T%Q{(sM2=-I5Nr`2Yi929>VQQfL+W@8`?Kixq z)aaROjR;d)-hqS;Ji=B=M%e>~CY{<%&U-lBE;cj5s8X=Me`tW-Pm@8N(754P$Qjq(NOfEcs#tLn{<7*E){3ie2XnMhP zDIYl|YEBe}28UV4+XgT%wM;Xb+Kw28D>Y`;8tKGWXxrIR7pFCuPj-!kTsNqpUyCrl zgmGq#2>bj3xx+a<==<`iYBfn#VHIynfZu3rth3dSrKB!0V0XCAuv4bX&f;QChr-Eu zraD@MSPr+7wO%Koc|c%*HKiFBqx${Hw;F?MS%&cvH1+`M9Ieq)@jR)VljBEF7Wpoh zrm!=t#yTO=pUHuWeBSV4-adTX|CJMwiC+%$^>aRpP2gJTW7aWS8YdHx%UX-YD^O&j zaRRmcsFK(ZUZV=-I?0qJ?dKD3lY*DNQjYFo?N1n1DS6q;VXHDBOYD3?ljeF9GORQc|YVz5Evoi+EpP`ST^a46C zKjVP>`N{D43FDE(ul2$8qL2>MW{l*UtfXLP&oJJA=tUIkNr#!+EaNk15g35XSG^|h zvFvbR`V$HUxzL%IG=oSwClWnSWyJoh8BB_@m=qR!k@2vC9&L>|pEaIi#7DSh$gY*f zVnE!S9`hWFS!nzcK|^tF$@o|HX06eIlo00|c`xizsVRr4ma>iY#wvst)23=;5_S2F zUy$xB{qKZp7Rg(_q}K)C{M09*FU>tcCah=f%9kK3xsT zmr^r=%BqbCtaN?M380CtJY=u?M%19cu1krKrw=`U-w%2 z6%<>OR?0uX_OR=(H_k)RV`%ei19p*{jMzmqW1$IG!Q!RG2#XiZIO^V`!N=&WMtqEF z##7m2dMESTf%&1-Oq7}F4KEwUv$J*>Z^ZjaC0u<)J6BgnUuTwWtgA|EuuBvMm` z12;xU;vP0a5~rC=t|7*8Y*L#Mrcs(H<#8y6G^dsMCOnBWmKb+cAa}%g#O{9*7 z>@zOJ{Dh{V^INpfoVShRlc1^wW{9_@23FJD$sDYLpznU>ZR4a=m7dDspDPINi7#CBFS~D!l0<4^zWk6Y1832 zw;C>vPc0cUo;GJjB&AG}Y~yB(T{gC6anWqY`Q9Sym=WiU2mFpJNtqg(pPipwkX@Kv zl$})s7lVb+>>Cp!M?o>T6=gomd87Ns zy2E6vIpy@Q@Mh)H%IVx%daN?)>gE1yoI6=gn_c0fW$(tc=>ualbXl4i&8PTbu(M^! zvG302@6KqN;~x+awz-(U8&1t17)^YAnu_0aHurVo(+c+di$*UO$7{}{&950}Qj;}X zO9Pc+GHZO@xElRFi=Lw8&-WczI)HuGCqwhC+5ZBXW3A9dZi{+x)gR&<^#d>q~ReXfJ|`<(UT{R5#z z$&iBqykpv@G%}2s8hLw|i70a^?selrX8+Lms*=QSjqwzFLqb1758uc~LpjC0ZA^@D zRtbIZT)4a{iN62VIDn!L7&r1ctD^@GfM3m=Hc&_9Po%}E2D&h^E2;H#oM`B6f2D6e zxw;jm-lkejhs&}w%HHlTS`Is4x%<&l{S68FuLb|Gx4+K64dJH9ee0`z0 zKSiX{qRm3`TiejtXVLF|DTXF&Bg;lRZUn92%Lt-<2}} z9(ftr+^jh!2CnMHmrNZc1+7-dvaL>5N$#lb%M8Gnt z(^bcZCK1iS!Ggn9MFiFNkB;p(cxX?<4-$5D8H>SXKsL6qws(-_vbGP5L&H@XABIX5 z&HbD4=3~du{XLfI&OT!%=^%EF6>VC9JO_*;VLr6<0JxyIrw0zE)PoVHWcPrdU2vA8 zV&G7q4CnX&jMg&*g0>I7As&8E(FdV=YC64hz?i6vwbQDN+LXklz3U3s5(m@Ky_MOc z>CE#ICPwkKf_EWBy=Tm)h?)IjDSnAEMOl$einC&ex%)fnUHzqyRnI^VT$OTX25?kT z=+5td^t7Se+w&HlSG~BN8n3hr$|%Izj)YucyhL8}<++VL2f;AAi%&T^4US!g25_bQ zN!O9&<7P;o1v2Gmqa3q9$|Kk)kJaEf#c^zT0Q9sWW+u6`=5&hP5}r(AGn}J;_jfF1 zpIoR+CaE&kKu(tuzRs&+GwJLp_|mGR&Z^j4@-Bt>d-1y1_zfQzRiv73cr|K6lHVB- zc6ZLPLjqi14J!S)X)6`y$Hvm4+jFC7|3}6~0BL9{@jJ^U(B`+T8O;2#%+{Pu?F|EA zjCRl%&_w!O-1K9!KE9Z`TsR2YaWE~CX}>o91Dq19el+K@10NYV9_P2l4^=E8J6uvk zVWD&PEOgU(iXes?-yI#SQy~sisx{{z?ZN^&JWFS!CYRof^>DmXpCy4e$*;i=7H0W4 zx0H`(e39eS8GhBUI-_nEtcW%{^`1B^g4_^|M=Ox+qv&0yKDD9H?X3ojQ^38h0jk`g%bLj{V^f9un{yUIx4}^fnIM zik~n6;?w6aOw=PTZ>3=UgXGE&-WtmBqA6eV=uNFpk`gcrUk#i?=5n zeFs~Yw-a*jscug$^5FUn15Mx&L5`f+uZ&7`kNgirI-P0!QV18mIJnM7J7!79@!&L+i(CbQQT}@GJWsX$J&3b z06yXccVTeQ?epjdXnJ~6pjbCI3iRp|2FQa77r}$h^PoX+;uQUb+Et=2dF^oOlvgf~ zR-LI6X_!YJ2}4;76@VJ@m0^raZ--s0X_l^l-kPk>u=XL?C5O?`rfp-Qp7GW9~ zH_XO?n2+5s(rNoRJd$4Y!aT5TrcP%&>Io74s47tXpvACotjRFJWY{$yKHIS9)BxSk zSCfZPmtR>8_vZQN2ca5Hw|QVChca;ISGf@A$p@-|4?lnfx{x!7Edpgl9=P9@?+b7b zFKHM9MQ~XR02c1H8-s$sq_W}qlm_`W53ZE?Ea$eeV29 zq1wv}F7m;Xq7T6?H%BN$81AaDoWt2Qni~#~Q33AzoM$M9;U0e65r!EW3>Odp=f|l$ z1%I2z{~{%-B&Q-@fj%Ww?o4?U^7R1ygbdO1)znm=AC2v0=?Hx>961-k&v>P?qDUx^ zyRV|E&;xV1s!C@i9Fn_nt%T{Fx2Uopud<@hE61ty=2cXgH!Qv7&?NPXg z#rbdz3X&(MK-N?Bc@{*K|M6J>_lRy>x!-MMieQ{EM+eh0C3^8BlR)6J;NLR|;AI}I z$1D7MCxO6c!OtcEU!>8&zi%1{!Fj-^f&SY;^*^$SNm7ygA6smcc0*l5cre;B&a?;i8tA?gFiI(lcxr0%7e4@qqF3g z!SBq&pj-S~CxC#GJaqd=y?BDbk3kq2?o{|4+10Ul)-^5pc%%0w%kF_<( z?P2ZBuSOHlno;`E;nM5h90CvCm;PiUZgbD#! zE>y=268M}B<`1t-yeYs2_QXa{;2*$}kx7V9?lu=ggA)b*qo;*Jq!cs&xj4Tf-&?@* z;B?E&9irzKfJ(e8%I6a5^NyB*C5o4U4VYJeAW8lf<+p%Ujf@SgW4I4=1;rYb^smE><7gjI)PRp2iUG64`0?{V+x zjVEIcqg4ud!8P~=fr*0^_sS6q5Q_BznaYigf$6Mp1J$7d+e#sSz?K25NmpStmZ%C3 zewa{*&8x>#xbQYlj_N9`!P^4dSMun+v4Y!0Q3ZcnjUP5?LB8RQ8_X!!oz-$SJQDqF zh&~aMAo!NI55_Hw@tsu#@YEYh7Ni(GQ{=)c{`2BB?_ZuQ*hu^>Pw2+48o$ckw{oMy zyRMw5Fqjw|TTfy%(I{Rw9}NF_lp;j&@tpW`Rd2K);e}O&z5?)v6+Zm(s)`EvVB}4T z@j$IZ^+^r<5mj&&;w56EQ44)FxCfvo{_|obk_(eF;^`5kfWO9%cNupa%>NZtJAIx) zpL})mEAT%#L>BK1xioMfMfmdec>!A9^8zJ<3&8*@kYBiXI&f0_TFtK*m^=aR)?iM& zEVt8#D#8qd~KfuVs| z1-&`((deWd!}L;gbzY&%SAc7tuSlSGhT*%CS6yypm$w>y=_{!bTzx)Zg;(VH zytsN$xgx;_KDWBaSA~A20tpKHTAIPx|0ir9XURfT}eqvfaSP4M!w5sX)oFtebh#yeYHQBW?XA)EqJ z+ciQ4IVX!Tv}dR;kvz8x@$_X^R=iHK_s`~XxS%{RkJ_0kQMK^=`Sa^t4eq$SeEr^Fc|H}frB7{H@5sHlm zseg}_WWW`9$3ANO5Sdl)X0tE-$SH>|&3YQGOA8gjS|E8lv>ax5mM!bh_uc2=)5QBZSB- zxIu;zZd8nwBCnQWOI^X1yP)3bcDQmN+`$)MUbZvGRS1x?f&xfTdjz~Vy$D%q1sC(W z<=d>vA$fh|tL|(Mw0cxhs7Pv%vvV$SJJDB1bFkMtY^Rpb88*XHHUEs+XDrEGDxu!_t#s!b>xrbB)3&|T&`LjZ}`mnHn|Li690yhSJo)5tIRe9Cb zpuMuv<;oubaNJD_*rfDcJ&`Q&##tk-Qw*w(HjKzl z=4w#i5KI}@`&kJJxDu(dnEQof=!@$Y&s#v7U)36^{<(M?)ut;3Kt-_m?*^D;Uy@;5 ze?^2wn+0%ZC?EKQqHi|haNR2-A~Vx^nvqX}9)%N52X6{nSN3UIb7?-@f~Si7d9b@(ThGP9vN$$fKAAAG+J*fTETm6nAJ8&D7V|l)@tt0Y55Ov{ zy%oF}v3DYdFcX*A`4xx{DU2-Cfd2(q3S*-y%Jk9Ug2zQSmT{IAOX^2udTAIJhV;mn z@~{NS0#E)IUyxvif`1VQLOFa|b7lis!_NZH7Ykhl-lNj1f&8b z?u~Tuhd_^N``Gvn%_m(HMYrnWqntIB5UW&Fkl3nAp&LjCtDi8<5_*Jm!|1wJT`bu? zF`llk#{OMX1%T6FY~aLI;fLOWcZP<6i9BJ&Z;6hNwx2~GlL+w=mw=!IpsD%LICx~a z2cah#;m-HwLpFzFhB$7B&xW4e1*G`6R1(B4#ZCecHh`m7!E*(6`kG2Gg*D(K5e4=L zbm(*AW%N@$w3J<+8~ai2=SCq040T>X1vV@>LmFQ{&>V$CJV8EBP^hf%(!KQqB^qyQ?dCSsYAv^C+X4HmMR!iPK6)a`hi{+EYeNm8ynRW z6@^SS3XAf%Wd<)f9RgbvVkHn3kNd%c;?4BP{m=~Uz6OJT@oQ=E>R=&m{?0+4odbpH zF~JL#4a}5dYl01ob%I_fjFxEi7Y9jsct+78>&D&}GcSZx{b{Hdp-Z1EZ$$~=`S`}S2*P^284j4_% zJ||6x=6e)3)4_s;I=~?iob$Y66e;#*U2Hncb0mjsqXSLmQEQbG+iVOxiAplaBLrdeRRp*kW73bN> z_kcP>Q#}hXu4jyyI-1&->5cSBOtgolUI4rI3A*USh7d)tHW@Q<%Baax`6fDfRO$GV z5u=bDEIm$Lw8$~0DqAY`Ev{Zz&+kU`Du7Y*Y>6|M+0qib2MCyzFdnKXfKaRkg3E$D zw-+Cj?kYRlr4PvBS=?bj=pVhkD8<@bs@-r$w5ne=ta%jg!nN#yu9IarZo8eKfcjU_=6j*yjrhlviouBv}JN{?fB^&%|-aFqyuxEX|f*a|JF{dJ-QSB zscQa@tD22q6=X&YL#9Dy)_C)SKjOU7pX%n{p>76C*~4Oj5$&UD=4}aev#hp6jQ_f- zneRzS|9_~Oxnfz?F8{i!Ir!0mQQb(ZvSD}le@y9|PB9fQ82VG+{NK|zkKnddtN0AX zhu*I%p1E-HR}^j4i1J@iJVPh<+ZE4w|4GGjU)@`e>g`vR&Rpjl1F`9^E1d@?R8`AT z2&@Bqhl0`=IB1Sj^|{BYpYgh;5MtY^d;!9d8sN71ARNJooVWzqS)zCUU(rEhM6Nz+ zjPXbG(8i!W#_ux604u;3+@KzM`2VZ|x->}4FDjsa>m>O9OZiMMf2>TP+Qk`B4aaGn ztA0W2jFELQ`j+?aAJIJb=y7Fz2h_m-am_R6?W=jlsu5JRaK&@Oe_!(~t8{v3Xn_9u zzpQ!o^i@1_HT(%Q&;Kc{^Usvd7=Qmwtuu7P$10sG{|j1YdV2`8jQ_)`<}v8z-=%8i zvXnp7&9Wl-->PmN`R`CSQ*p6AX+e*6n6Jnu)-8wB$HDdPkExdF@DGW0S)Z(|Dx|V0 zQgZmQT4fI{{#=)oegeg^%Lj`K7#UE^Y5L?SoCSdkI_{)?jV47QZNOE-p;ftaYKiJD zjZTP!f_OP#T$@C3!%7a~`HEUYO`Ykvqp<}A{?upvXY^VB3#4f1-Zj1QEAZ-lRe79; z|09Ps=$lzkGW>a9BczYzt@(fdzy>Pue^-JwRkmX98P>3cmWO!g_(D(>jxU%K&Vipt z#%$sCf|t;yEQqeU-1-P8G5@VgFkrt zqa9p^)^k$E`8T#Yu%QlT_~jROy7LIXnY;crT!T^^v}D0dCLHj$R4e(IXzu zF`#^(^mGS3^{J{by}Eip@V`;=-^o3XaKA&XTgGaX3s?=YM+r2a{MG1+Y z%cV@DG1C>X@pb1fs;?eW0bm%o2d&S;*%JExDoH=C?@|$4_#0vq0cNLk|19h-vsdP4po7IWb7A;IB z$!&aw?bC>PfS$XW+TEtZG^9}kRQU^80*j3nzd(2s9cl+$U1_X%A7JO2sr5mQW}hfN z2KcdcR3}N9RQ$N9KbxE(?m&cVaj-|diUlNb1)kQED?we&=2^w-@w9;yotnR}*Bv4} z`)Y_G@zvo}+Mf&1^&z?JhY%0cY#-Qq*QdYvULIF|6gQOj&` zJRG%YZgdMSRyRkw8o;T~Yqzq&!^Nji<(sHvrm&X|cZlb4M9#r%@hGtg6}p+q=BlT$ z8KcE*07=^D^0@P_cCZIZQBcUc?1f>9QDGMX z-s{|`-2?oC;GASSHx8XmbfI{bpxOLLivN^|up|rf>U`R1WVI3E zAO$^mig-1vjg%5)49#1pd6_urxat9dSF8XHw^HYQqLo()ZVj5AH>J>9Bg9O0&?hG2 z?QJZgN`x;j%~m>~a=yVz2b-oT`qYF_69BM#q#3+PZ>QqFnnqFWD>*K9(R^`@+|Jv` zH^SYY)qX7Ate}xg&HSG zjnuMDB`ES)$yUQf3PJ~piAcE}r+&SjY7e^BQ|a;g+;Ej5wkz`3f$cdn6|s<+9rKSA zj{s8bjrn4{vZjE1X9I;YcY!#C4qs*11wvrc-CSaFf`+O?);&M^b@O+iSeO?L~#T$Rz>PDrZS{y*pyo=k#J^4G5mcs)83iA6z5VyN^1?t6xx0$M_c$lVIl@vZ9tvr)wNF97}50Pf;lm z=ke(LE{5Or<}dq9>rboiii(ehs)fV54RyEydq=KGic}@s;*~8?h$neUG?x`6guwUmFkO%xcztqVn|5E0TmTt>%+p< zV@h2SwQHPzFffDleD;*n_KzZ?=;jQqnk{Jr zcChvc@j1AcT`#68>7!+0hEmf?o2P3AGS_kuPAoO|QS&N85yR^CB%bak(?E4QThti%s1 zQ23&m#c7?-F0B_&R{%|UPmX^G$DM6`B*#VNM#;TlQn&!ickykN=3bEcAoH1|P4GzD zqTUAxR?}5{sPAUJObPJCSFIEm1J*oFJ&5+*h*ep$hxz&=Hiw>_Eo00+M4Op7m&&)P z`*HHufy#%ed5tuT6F!_8Ip%q+NnUt z?p|7@5ht?AJLFZnjSf5u^y1pphBTJ{fcOHkZKusif`v71F~D`_{N3UPtTvCaRXaGK z@!>6+b?o{_#K~|YGK_upq!@=q?h#bvNs3d67qQZVQnHf!;@Lv^&xO)-EfJx7XZ(phxxeyNPg)y8Ys#CJq3oYoa%Jtoto z{fa48A?0sa4+zIar5aRC0aRo@gr30fMHaVRyg}Y*>KG{h2Qqw#)jn*v0q#4Z#Rk6S zeW=vzW3_FDFR&1A&1qt5z7UrqS_j1*F}%YLd?}hxuTCntUOhRu1hd1>8ZJ}trDYoc zrPZhI`aygi9wth5z9_k{_q>2||H9&yh_3=;e1v4@6?>IBqlFSyyi;0dm6843Eu|qZ_y~AIgl7Ve zWJbIL@COXn>SG_n;_$>Zf`M%;l;EpU^FAGTL_L|BPElK#v{ZZwE=op7k0~`DFyH4n zz-+EPD9)C5+7BtthXIA{s*Pia*T{h%G2hpSMX^TX-3)8f{V4Zu)ZITr&q}u%&JAs+ zye~&7c1@Mwp+WQapl^w;dg+c(-D}SjljSOY!W1te3pg7R9|I4zLvsnQ_@}JyRL-C@ zrW#B6P0rw z*N$zmBjvHh8W&1$z-P>pbcf+KV-$CrF@l^=iT(M={}{jSVCSI% zfa~vQQBPu6mckV@+liA&Im@KKC^cVDvmlr@D%I;o5bn(2Q|lt?@-raFj(7f zl%`Pe)|^XO?rP~3G~kzn!$Xg-a-$I&^UcksVeFzc(xrIu6)9fP0GWNfIGdGUCh_&@ z8&E4Oo>pA?%9v(uG)ozNIp+@eEq{k{HFF>6G+J zc6FP>LQ)<4WvLc+=0txVZb0}H3n00NG%qaAGDjQ z%@3^Z310qHf{M!as_$Zrx0)iAy!Zt6^h?qpl%ZxN2SxZ? zK)b%nx)z!wjNA_Kbe>xjl^szfQ1LAFdsI9U5bMq=X*2^hFC$KlT1#VIlXfcw4K=S< zpLT-vx*Uk{doRKyZKsPH)YuFVB zqzL$+9mASGmKKDo1bEICVyJtDcm~DpRUbTB1GxdY*8@REbv9{YZ&N-DcHw?eMT=hI zLv*3(T^=Wv>IO&v&HhpXTD%ZPrdtfRZZHBd_au8w1)3%vGsdw0q@Dy;@qFQdV4mi* z-kk^TsPvMLp(dAO@r5Uk?-n03VEE3H$-#a(`y(VX(-IMWeT>nj574J6bRZhvGx#*? zLQ6df#}pkeiW^CqU>eOq=@WQ)sdOMtAin(fr^9Ee2e8i*O^K*c8m(HVcI^5(2icvP zW_lRe=}Dn>?&jYGFUz~-M5-NT;%A5j00REil*vB^ns7=nd@|FTOdCM&csSk>enziL zft^7x>^a>(4o(fYYL5gYd`hwDQ?*jaWZgTY90f1(CBCW)HfkQC_OM3+rV&b3nrsZnx!YOygVF== z2-4%Cf$nKHj3tzQ15e}tFqyHC4W4Rx3Y7Mz=BrhS46D})d|KXa=;|E>yE!S-lmdJZ zlo&U18V)Kc_D-dmc8xHNrScn0aQ*YKHey$~X)F9-z>D9PKVLJp^47BAH;~%Tu^3s$ zeUeilFM`f-rZRZDGYv-v=8#Jgr?TSZ(jDAak&sKx3F<7iDTatOsQfvmDZD91fgms2d{Deb!8TlGnvchPI#3}lVawN=)}YA>Sc%bi zih>PoHa&rtg>3UbMK_k`P{(a#_6?@D(4$2x?oKg5!BVa_rDCE_Vy+g`i3u4_ zLk;hscbnot&@kppQ#UB+`~Kq1OuEnX9ttmJo%7V`@~V75PFF&{>F`W}T3!by!Y*+% z9o}skeEX?;OmIY1isPJ~V_J(Z_+z=$<586`2o#N^?z=_!$b7;CADO}^T4fg+Sov~k zh1_AM(yAM(sY0ha{%Cj&*dH;5bPbO{P0l_)__c>1h;KhR%^i3Z>v* zO*@n-VFZd8N0Z)yBZD1XMk{-x%hVsq##7wQFjyM=zUe{Qaffa?%lgoig5I4R5%%aT zet2^fyYiq3zKDde$Y2ufvzb@0ra1Fb#m@0&l{`mES;Uv7E8!{we$jT4Db-NS{`!;2 zp->4Ekz@+B$D2nZ@-CD%l|@9DYr#!ViwN5h7M1j1XpG zW1`JFLHA_jaYpbob?rydQ6wEPS=ni^=7*3SYWRg|5F3_geq3EDoSE-+dulvYz!3mW zVZNJVOXA235Ho>HS?J~9UPVB{Du6pN3)Q3{Hv_Il*Z04vs$+3|S^9C-{`y2$mBC@S;wDQ;=5i5j=2#HaMh zgxmHw3xb(Y+1IKBjaQ}uS88rG7*xoF-?yF}ouk7M0}i#oE9))vddRdr%9sf_IpAaV zl2LI?mApYRyyW~^Fe$x#h#=i1Dbf88rf@v*qO;=!cO?pk zrBQF;?QaO89mYpJ$vISqv*2jyy#3I+!OS)%n585<@z(5VLG)VzlaT5_jpiYkP@UB2x|Gq))<2aMdnfGO?s0uFQ2aKP6MdH zB`Ja(bp?Sv4b_)eK=1K1GO<96eLTuYvG;{X01Bz~0_>8TT2i9u$eNT`YT$_3mhDj) zwCJ%UjfaC+0D20h3^j$IM40KqBZo}F;BrB~#+jF0)?6ELxyUMe1%mek`%^;Chmu?ux%QYQr2q z*!S(lJ*#pW&5g6jfIl$+zpDgn8mbHww;DkC)VKioQ(54w254M0CV3U0DXZ|V3diB9 z+#Wy&RyZpQa1h9iR>5U9U}LK=m%JR!)d~0?K<Ryehqvi3JysZdH%1AfRAQL`vM^sZ_(BhCy8<8v_953*6)Z(4C?Mx3vz}URO3|nhUEVK{yHOol zzJk;~-Vq~aV}A9`8x#R7lVj2Q`eq$$2^!jz0(aZNN@HQ^t#l%1tY+Xh8!k6Y&$1UY z%nxJFKO-V++vz<$7032xnnmoNvk1>kMd@oYWMT#nlyU=`yI!2qHphbCZHyJI1^ajNLpqy)_RD zrypO|Wsvkw^OYnum=igBb`G`w(_BueSDQbA zWUg6e&hVXuBygLj5L1Ii#*Q*`R=U#sFwblem0e=Kmo+XiPmp=3S{A2>-LIq_E6s1y z)m}>pB0)ZH`oR1aHI$jHYK&?=cgZl!JcRbWYg>1!>=7!^Qf=oP2Si)BA=C5k+ z7E?&tr|Y5}pPMyQ>_}O`UoQ=2NXXF1MyxdNjZ_NDSYy4pF`N(F^A)>xn7bfqD~||!Q0na#hIzA35sPdtjtn=4)hi$@3X65ntuI8o$o_zVHH3xf zP7i>Q*G&!#$8}Pmm=9e3@8{vV(*nZ~J~A)_*TDfGLFxJj@SUNv1d4D?4M3Quiw(GO z)dXB&VKLtYoVflykb~>{0erW`yc@{E_0>RsTwe*;!y3Y3UJlss&>4XLyOs5g?TrUfx;CgW&8P}BoeA>s<1)yS!IXe)CYh?iL z5o2ZrVsM=vfG{JbG=Qqd3=d!+#^eVyxVi$+(Kf_50`Th>V-BEeW6}fRxF!eSe=;UM zfHsKf=SK_0sQu`P=pX$naQ()=4A(FHz@3Rc5Qo+DH6#X#OYtwYWa$=L7v-KYWV_clyu9wPB+lPD+IJ{wiFr@Wa=Pu*P4G>uNt= zGwb|waGmcz6W1C)U!N=d({Y{SKLgj9{?l=t=AVXZnSTnd6a4(!W{kfK*WvzBTu<>& z!qx5P-%VNm3AkqX$K#sl=O17F8vJ7*8~o8f8rMVqQ*nL6KN8oM{Ux|Q?=QynaX)%p zv&%me*L(aN|7)-xy{=jAN3Uzn_jBw0`F`}eW}Y9tuBrB;*EJRXTwG`S(f69u{pfqm zsebf&tTBLlO!4 zd)@rNI9$&TpxU~sz-SB~-ORvgxXuWS!gYE8wHGd9?a!HqDHztDRtUYb&HW+}dO2%< z385R_5x(LRJxf6^4mGDx%&R&p&3)Niv8&B|7Mvbc0OoLH?+m1_LuR8OtlgOE_u*sI z)BA!?%{OFO+oj0+O8Ik?mG3g&q=4gfodQeLBjz-cy3O3dxNsG@!Y%ul@eA{NAS>G} zJBAdlCYRc>k9Kq<+m5G*fudWmdNudS7~X7k+gSOV<}Fw|ui-gDss?Ztd=k7;9D$AxQ<-cnI%%GEalAqrFkVvyPkXo%Qm+CpXO;;J8z`M zQRb(1K4&S!tDDF-#+=u?@GoG~A}!mo%iqj=6U+exyG&zQ0=jCT?i_oxbhPYeG$`H@ z&eT5WfC{k~T~vFu`6}x8z?{uD{Hav>p*f0YbPH>FRW}!YTmNES zNsEra_3tMpODgJcEBTTY!>Cb}uBN7k92zS9POW8gtd?8R3R_v(bLM;H9q<5s__lc! z-Fuh0jBkDu>499!M5S;$D|^WtsbCtHc)EsR&Owuq*6#Jpj zvI}i=HoX1yJEo2Lk+#HAAP9GDm;7f%guU5k(4qxjV!q*)n;~(z4VAc??LOTy0+r~1 zbEWa4EW1=n;T}3|l;vzTf4t=ewF>IzypZ~NOOgK^wD<*bSwCK6NuxtQnWAX#hoYKu za8Y!_R7)jQPOt#6w>>R}?9(h`ur>=j)(!E`iwN7g@)#low)S+(Vvw;NQCg{Xnq@DG zm~I)XfN{m78I~{P9=Q(|AC?>d^VQC@G*OH%SKW_)(!1Rd$N;R9+SewgG3P8xokCgQ zp<8BKN_24S!BHRi=amPaR`ieAmf2J@$1t`(-hyhFSS+ZeD<>+_XwD?~*4i`? zUM($+@LK45FAc)iBT<#)$WlhHTUZcBuWVB270ao4fdVE9+ZArK#i|wk5^45yMG{%g zNcGZ)#^@B4nYE$dI$;lYM+IVu_l_I~e{kQxr( z4u8t6Pe-QGm6aBO9)CI79BHnos4&xDpDvsJQDsS{&sy{X<;Y zP|FSJda9k$FP_iioqX6qCoJq@5wqkQ#?awEsMDg+a^Yd|VTyOduEihxyQ9Vr%dyVj zqJt@rLf2emF$=;YTipKnm>XyFAFi zPA(u%t;ByJ*(l=FAPn+Q#HvJUcrjU)Y>eYsz?}e>j^e*OX9b{;W4IHx1AkSg>Ac<= z2u|_gjZc4g68hOYF5dG3ksne(D!B;4=vgg^QScquJY5C3S-Vhoa(LUzkpzmZwtG>3KY7{;7`&z;6a@D{E zrwwxMkoeWWgA|vFL0;Lwg?*43LT2dd%^o6Tgh0p^Dgccs3trG?auFDfC4u7Ny4RSv~H2MeXaC{Z2aH0u)%q5=? z2Ueq}fN%vn@v8jU5SyXvxE3bV*$q86L8&Nuy=Pd3Y9)(&xqcvgp%Lxjl<~rQ23_yaA*JHor%5N)SYYK0Wl|wuBC(JS!P7vJ~wLuMf;&}l2@Wb52z#M{XJRskQ_pV4_A&43(rKAH8|{6jWw40eyWBfw;E1Kc&utHr`1mA zH}EhUvezOANDGkrWB5Hyiq8|$*x3v0*D7iGJR6)rzhyNsvDO|fvzX7YxMs^CB@^pI z0nbw0ddqf}@PT!bECGCu#a%B)SRRV-Jd3+o4rmMoyuji%TcF)t)ol%GcVXIhn;hfP zP>h#Y+%^P|xYoXvzW>e&t~^ZG2LnLMJxZ4NlXap@N(aTYTKsJP7@J8+U7y<2)U^)B z8j2@cW9jNRyNXJcnV&$Y=t{L3HhgQBxtC6s+iL5CFsjs+C1>(7m0T7xfX%tyz6D=C zugxYUZqt} zT9DUmJg?IG;9U4~ts|aID7VGP@BhkjpG4u*^t8povhTJ>$)T@NYn2VDw)0f`%4{lD zY|I=Wzj~d`oM*cYp|4n6^g@?aM}`ONBZsjW<*@UYH{Y`F^B_dX*3@TNVEiBH&**Y-fP*zwvCp1mPV!6e=nnWQ$-E8*Ra%^e*imW=o>lRklQ0SCF}Ywf)^vjGjCt zQ$>4++w3&x1^c(WBKxV`Z%byypIO?J%zLTLCKuVoW?m+f{tt_bdDn(Q53re6%Awz& zWN(wL4Fh1`>vnwmh6(S{%w}6Hy?I)ujoQAooWW*}&eVlR;?4UWa-Cw+vq|sRTa_&9 zI-652;sZAGdfRlRqJlPuTN9||eS0~}x+(PXLsB%`QrY+q?J!rFy~Y;H2DR8EIo(HW z=4P1)treu*8Y1m)Z02opsE&u;5eog7W^S`x&pNy9xk}2q+cu1rR%Cv~$^AR6!qBG1 z?Y3O*s)4m`w@t#x9dA2RF60wx&9FLPcV|Bz=9J&tBl(~XzWS7v{Ak~+WD};^hRAUb zvJ!=3FWS~=O{T#u_BQ(bdxd7hBeprbpwC!IB!64F*OJb}D2G{2^EoTgAe0Z^U?zuH zNi>gE`-38aIag)|4dxEBk~khMdv#_oS~ry>IsQswvI9p~@~tk|y&XaCP3o|v$oYQ3 zTt(J>m{Ap0xSP*#{KQH42XhVKfyMVLBH7T_Z4No=m*gt3KEV1nWsa8Dmamv=6k@Rq zhhv}|;cMm^BL`%M0=}WT8#AwFZ4;~*No`I?H0$ql1Uu!Q%vHvVXu~4G274UALHI3m zP2-^%g^pN7HGO`q)kG5u9UoBL3~LIf>^tU~!6WP&<%p!NJqimg*n^MDrXuSQx^b}M z9vMeR)hGa~3<&Wa55cE*}qV`oX4V#Xp4l`$y)gmP7x6wPw(0M{sHQiCpun z`xRamcaHTCTI?HJ5e=DdmFMvhs(XmnX#wiQGD?Fr)rL{sULIPeY4!+asCCGzu{MfI)>#XoUU4*{ zw607w9a?9Fk@*>%q-#SYX=v5?j#O5DzC($@c%h?d!=vztEiG}#mgjJguDIE1XDgOE zE>>=M%$m>Ue3F?Vm#-yLjlGyw-fZ2^Za$d#j$G}2)ZCDny5SaU1iQM(UZUxji~cu4emXi#wgklqeQo4%UmGWMnZccG{C+V|ldJ zld?S8%wJ}Cw6^0|93e}xVCS2-Z9>qH!o)_o!lNSk+Z z4oK@c83zRM$%!~1ZT>HCK-z+nazNU`6LCP=q7!jI+EY%(0ci*QA_t@$d{PcbJLEVH zNIUeGI3Vq?6LCP=Vgd)WKvB&)Z{y6_F&w?j>u5_(#1d(TAIB1DNBlBNq#bzzmPkA5 zL@bf^)MHs9?PnxFW!iiWS?ZgwYL|PaZ z|C}Y#mY$p?(w3c&C7OJ4mT1b!S)!>YXNjhrkR>|(S6HGmPRJ5XKOsvr<5yXtGk*(9 zH1n5PqFEx3-P*(YR)=ADQo(w_4#vP44C#7R^8_#%_WOqmE(*$G^dr4EwKp`yJtD@~gSyMeGU9D^%?<}P%0I3HfROg&E4rf*7*q3b_V#0UMb_2KV? zpsy;f^OK#a{mTrV`Gj0y?@{JnUt2|v3b-Zp^~Ztc9@nE4*;25*g5JtpFwG3z_1cMY zDrue>QlI%hMQG{rwK`E9%7_|&iq1$magwRja;`nm(WflApn*f})|t-PuuiM!DG8o2 zEBf=SLe9LNSsg7Y&ZV-I(O&v$M)XR0=`;8(+dC&?5^erOI|RmO*-GtvE{Qu3PRn=N zQ`Ha(fQMMX8B-p#+n&yumW51QB0oQ3*U;xTS`wAoMHF`{KJ9Biu{Pnz-kO+5s(s5M zY|XbR>CsKF&C=H5gO8^vpOG0sxr1%|%U4^+M8BNTvvOH5RM9SG;-x&Ob~snnj&Vy?A+ z5iScY2ttcUUfKoJz0+c410S%x5>C#k!N3ctY!_q);)6C6diW8G9CHb^raB}phkH4k z-c>sUo_r~FGFuiapJi)-k%cyM;D(1R+z3v45sSIQexH(UjB$*Wzi^gO*~2)@1Nqw& z1v~#m%ax#LIg8oBDY|!nP0novi+O-QL#QS{uVgU~+Hn+p{2P{Ch~B_r9+so$+xW>h zZKH=2I(&m>wb^k}IBR(@lT|F@1Fmq?uBP@KP(SxbzwQgCeV5|jk6ijy+#e862#u_557lWG1SdZC;y`JP4!PNQI?-fez2J#hIA;3v>bcEB!Io4F34N-f#!^I|K zIPMRp?5i?U)d;)}r@hHlVD+-W#a5g|(_T-C5mpNwei?F@p(CwNAm|3F8_nesqpk1Z z=|*Z@XU}0{vmLuopDvr6_f0Ir>G%NIZpoCNZ|)UNJrPc^w`IoBkgb_&PQ^y@-Ii(D zu->X;kWSr)COD#qp-pF7dh=;B&R~E4c2%IwWm5{+0>IDZBLN2Tc|z50ept}R^T&e zZ>18wqXe=Kh)QZp9Zgsfih_x6liLsz*wKjtllJ$3GVoL;+|`9?w^H2IjsYxZiS;kY z;dW|XVbwF^a_a|px`R5O&6K51@8fA3WmGv%V~rX1MpWWni1S-6v8EDCd82tocS3)U zK6PGdeHoGOq65{C24$?ZeuSsHSw=0dOONa*L8;wN?YHv0ueI{L?_tFsq2JltOC466 zGQ2rR^}Ve46FwT6Ry*R@XDb{DO46Df$tn+DV0VyWlXUhg^8VT3*1%_geKz?E}<&i31~OhdhFIQnAwZDLeFlH39Y5MZVou zGu3uN1U&IU>jMa4R4hPj*ti>Ov(aa_vc{VnXh7+@ASn-$$!)i@^67T0kUNYvi9UVY znxfS1#_Gfe#;tr{NRJ02?_tHske#!)xAkqNhv>jlP!Y`0+qR%eHIP^x+U|%`(#QKa zxers^W(Uq@c37*T)c!gsuI{jLWmX;cVjRcLU0`oMs#3D3wCAGv_R@g`c0KFb>BvyB zEQjqWd5Lag-TiGkY)@JKtz5kc=~1XMQb+*!*m{awyA+A+-OKD|bYf_i zOOzE+42mcV2%cn!hUj-sYf=GFv29Ht{qatb3lE<``8I zgR1FVyzOM&3*@)2VU>O11M5Ip)5M@@x?IloW!7CMzklohQTOHnQC98WI5Q0UZ0s;> zgAB9X_Y5=as354AJK$bvGP0z(f#Q~$T2fkWWOg6(ArW}ebYY_rl_TCFUxy-bCA zTA#A_bIuH?S>NaRzVAQ3pM`Rt^}5e}&b6QG0?G#=38T)v7Jav==~>a{ZPqnUw7J;S zksGrvYJ)35mgkv|h~qKI>`} z_2;;?XqEXnQF@$ptrn#|8r=Z0#3?`%180i#C9cn&_TgR6hLz%HQ z9an3^K4qyRd6UaG=4)a_K4Yn)P=CqBd|%Xj&QiyTo|kM)PO}epTa&oiD(nlEdKGV1 zzFHekM}w@c9=novAAHGDui^D#$Lg<7{~l0%#Zsq;v3)x>k=vILTOsUg$fxFyy_B1q znBGI#Hw;8ur}&F;>0trnx2(N@8$`HqiNUzER+OGb*a?$~{hnl1!5pzpjPf11ccrIu z+tLU4mSMy#k0X);?BDk?))0d)9s>Z}vBqB~HM-8m1qp z?P}QHe=W2IAv^#GIs-xHEAubvdIol=_G`@H%=nwR12-f7gfZCoyZK!)-H)_?hb5os z2U$Deom*#0;w+YdTb%9_^*`bMI-i}Jn%L4k>5UlkXDa!?>|&iWO?qKzx&!>tzI3}X z>=!Eex4D!mYfM3`yghv5a`RLsN; zOOp?$KY*Ejzq15#VG&*e(3kYxt{|UuO+vlF{;ps5;Nu zSMwpu*O}tj#1GRS#*lwfMU6F!h0U_UIq(wO(q7(2jmRF;tV!(SCQ}-0EZm|r`BM%H zZp^gbX}Uwf?)oDAI;B#rq!ouuWlXx>`mUnee)J%k`O}G|*1NgksK#HZR+0NgE4Yfz zbZ`~w0CL}qv43p@sNGf~hCYGRv=A%*cZ8pT@QasG2eA`tL1mfseENsz6HLt>YvR*J zGuTFT2-Q!3CF!?2t$|<~LSa~Ulv@rdpgUI6sm<2(%dJ7H6#?onS0?VsPO}uSVzv22 z45OwM+c_tp7lUeO#ZGH3%ecmZC@k%-nDSWiqt+M1B5CPVA5#Li7iE+5xb+uq>|t0G z9Xs))fHB$pI*9um5ce&r3&fp3+;4<4dDsg8EaFnXkv-gWtB&j;=HBNh3h%TeG1r%- zqxf1Gz>r55Swgs3NgYiu_z1hVX$%)q1xfcl$I=TAr1huvQ)!_Hn_5}o=7H^kKM=rsNoLoO4Y7kl@+4L7Yc$cU#(*9ni zydD>kr!jUaS2*KLyRc{U`_ke#-F{Lu?nNbm7KC-2Dn4f#dFEJ(8Jt0$6SXGt%mS1^E9JOysku{Hf|G#l$H z9Y~%7@NemqEeQ+~=S!%%(pOc=bdQS>Ciw~mc?lAz&`~t=_)4$f)JRYtjuYEF;+RHu?kxL8EViaw=H|PXJJj_fcKJbRUZ9wZ7_6T(h3p z!X$0yRC2&TceC^tc^t6E$O~?Ino#9aF$>!P(#?Q8<=LDlH{s+yPl=^0(-<(Bu*brK!B8=fF=B7iuqzJ`Tq&6xHQAA#>gx5-p;7e5CRE&Lo*(u#*L zK0E_n@)t)Y{BF}|HpyUq05JO`!hb1tPqlogQm&lKPRy|M0%YNszM5KRTZUgs8)k_` ztRdG-b1}U+*ODa^n=G-xQZH(1Y3%~bT&~*8Wp!V2?PWKqcwWGEExG2I-Som@OMhD8 z(|1O2khAWl(pn76Z+siXG-8YJMuM^6vuWqUUUKzijS9=SG{uFJZy26SZ~JE%k+ z`AV91iaF$d$(%|19>Es9#CmzH=rotsJ^=`pSDQ^#xf8?zez?ZQ3CGKsv#J8l^Q`-+F17+N7vC!cIn7cVA zJ(xD!Yze=VLe=_1hpArds-muT=pbEhz#($mLCYAL`FL6$7Y9d)iI-5>?;N5dSec`p zoK&nbi)cXVrL^{K{w>#FVm{Ojh_&KT!71R0_bp?Yw800@GFtl)Am^%B234$g11+bu zCwbqye2rFI>Nn`!95<4FktLpWj7>LUSwl?TWxa{kp0*5E*3{CmFo-U28eAp-T}h|D zgdB2WQaU)q_HbY8Mq2v=pJWOJ%$;e=ahp z>Gv1S@WP#JMzkb9H6q7NhQ6s=;_C#P^*TIjc-0!(ze;PsC&fPP_KST&Nl?{(%B$BB zMOvqE$?qM7SL`GB6t?mzTm`+>R7B6`nFjQD7}f}=*VD2-rWHLh;BZm6ftK|{VYh?f zd{Nj$%LbsZ<0B58!D`^T+Y(W8JJmmxCR4{r)N~H=(peHU8*!J)w1Oqd*2Uua4pQt) zOJW`OiSJoq^1j!d)U-1#i{8irqYd>gpa-?}R&AnX)eW$6-xc-UbZQR= zt4)AaWI^L>K3-cKN05UWL>`)Sz%{*DuT?}$L%F%}!OUu`XB@*83~mij!QT=UJEA3lvE#VU7ap-WH ze2_j#;dfXu@O7VnLaoD~`y8S)vkR<(P(rG=lm32F5<3xKhLW6n9q!(2B{As^ zI=06o(aF)kG_5h_{xl-gEMKZ~juoG%mBMo^B@b(1W4wf^Xcoh?(Xkidjo9sbcpO)1 z?>1#I-NV*ZqJAf}Jz>RCN1ORlKjwRZr%1ma&f+>Z2=pgUSbNd(oz`q-JY7X3P&j)8mj2|*)uyIuBfU%=S4;fb~a+WIQ zDNIU75v8uioihEq@RM3LAXQDR=Yry?^16s1+WuazboeJ4kL%*;vC?D%Z3&D_r4fCS zf)tq$D~%}>^Cq(P>2FlWll|4OOnNCpt z)A(|X<_)kh_;l>)O7-(S{1l(!1zPf^CHGQmaTdz=aSjTDcdIXZ zsnqRx`LrV35_N%=1q#x$pFa7wrI#=gM%(B1wKt6Rfr$ur3yB*@< z+uoOdt%m{P;>YW>!)f+j!Qh+^SG&Byqz3*%khR5X9qi$BI0t@+j^4}prE5vfBB-FF8s(``-{6G7Mg zxoG3l6Gto6P<4OJi}ZuMuzbx@zY|>^_ICM(rFQb?ZcZRBK>e+&Ad@QMt%Vn^9&iOQ z4C(6AEcF-On(KZ8LIJ^dEcJIW+-~o1XP!>ApuQ3dMxDc~5lZ!0ma5`K%b!&U7|*fP zVE(Lo(c7bwQq`6}m}0C|feE^(>$P+U+!L%^;QpSvj;3SFPPFnJ{{u^n6FuMZ_WT#6 zCR!%5s%q;N)c(j)jl8zw7E=U$e48nZuj)_K^MA<4f*Uv1~%A3M!8owf3(HKZNut+v($WE$esA zTm3tgJOm!P@O#_?U^$J}H}TE?V5!Bt;khD<82vm;9mt<2-eQdjbLZ8%t6Vi0?@yLG zm=_;>N=&NpW2wXWb8CdyY#IeiE$7ee&wA^XEOm^iH<6y9Id!Byz`rxACy{L}nVow&Gt2XOcbE>IQ$z1JeSBa_^w*Gl@Jqjk8y+g%Pi&z07iZrKnFO&x0V*a4^G4!*YAGDDB1et`@y~`etuw8r|@l0&Ra3Z8!MZn%LeZ z-j>rt2734ePb#5F$GzA*?!*&GmA|H`*p81a_(q_G8^i==ICj99=ZRC6u_(RQS88E< z@8d)1efHZYMO!P|+aiGRDWko>*r@)4G}>C2qQk{{>1!1s088Sr zHS<_oTUr^Mt9q;x*tf!rsF6`6?g5sYy^}jw+tZ?8jIY4S+MX2!6MO|3tnGPGFws|l zyM+5iL8Y(2&Dvf;L5~FP-Da7r?RDNvcVoJDsw~#_rg=1W>$8gZhhweqB`g4^4S%U> zu?*Q@l5hc6Hw6AtZx)!cE|_75i25AX_AbWTnlw47tFB}#8`b;+Zi#|!@jZl z(y3<@y*44rAp4}i3il80V#Ou!8%6s<*7l=lzuniqh_(I7=k7MUHi>5aSlb_>*=}F6 zV%DaxpjkHs)-$+NdAC>zYYX62#n+mI8r(Zkf7TX)QU-eLUG)L1O(P0k^%V?cZQ;D% zhFD7sK^2ZWw2$LrGN`im_?NP_Xi*;x<=4?=CRdMeJxRbgh_%Iw0pIcscm|5V3BUoDwZ^o%ROE{Z;pJ+CcwH5Hv?mK!y5Z08jwnAR& zddw8S^gpFPD%zHFFLDFhzS=tKB7ZBcIrpw}1#2s{e5$~8#4UfM$8$bJGm5nh5l|?7 z=VCN#8zG>G;0nrei-=LL8N=EtMCsqlzfofPvD7x!@{Xc_HL1-OE^BDUv9>EkH~3yL zDZ;!~v>VUbCh^j4JM4T>I)Sx$P|Cs#K1i-)Z8f}^;0X*2^}_a*ZK1GYipJdIZaXLq?AoGIGEeqob(X^O)8zOO1`+BUdgg(R#NcgwEX@5<)laF!VyaFT-Tzs49B$_v8%!AtSB)m!ep=(`_?y zzx2_@;U%NTAY0WGBg&{GQBI6-`{HQ&BgCP9B2u$0gClc&=*`>HS1t_jTXSMKxnH#Q zqiy@Givu;2HeRuE0du@)J*H4=u5Mf(xpF?(p8Kub(EJ|9+5BfBZEw)8q>L@Hg9qJ(09By(- zO*K^SUZsskc+=FvT}{k?EwvrCUQ3-{TCe0Y!;p_@ zQus8n;#*JUU_>+5tu|=%HmGc|VlPhPrCGeyFa?KUFoN{IVb${~hZCUqkk4@)9ec-` zK_|Y1E<{w|_2SsM58#x?-uTIy54fjO_%W-AkFy5pD!zjU;@MBE5y0U}TOaYhgsN}( zU^9@I!1gC>RmX7C8HI1MchBRpu~IYB3t)MNwU3gv#oCg1ZEyaYSS)e5S2N2CuQAd7 zy4dZrnOkMUa=6~M2J-e?w$#)KVEAJT4IOAlYJ|7pmVkkO7Vlio+`m|HeOVW7`vKo) z4qK6HUk26C+g3e8pZ!QjvdgxO4>Xq#l*=%iFxzPwm^+To2G~RR@Fw199tX+7d)|-4 zJ-uxZ@w=hnYH7`Ub}GlFRx-WW1}7xV0zM{825(s1q8vz4+ury2f(o~6g?-n~? zDV3=0{m7`XBjD8A*3iF$;cGtH4K%#QHi1nXXnPn?EMuo!_7uEyhix2H4z%CGn=EG) z1MNxJ?NRm$HfMwl85K1vI1unrddu3uu!14t1Kr3A-5mUb;`t_45p1i*XuWNB@qt$I z<>unmgeU@zu)?+oVNaHi%V*tK4#iHEGQkU9Jio=3S z=5JZ!MColDDyW0H7nwVkYu50>O#XgzF8a>A(;U%hE$x5HdYXS{_(EZUFZ#&T>9M`T z$5=-Sn=Or&_qV067fNjXm3*mZvLA1@X#nDS4q^%i5gFR>MytiR8#szs@7v>i!Rg!e zwm7Axi51H>D2j$x*!uFZZ~wcqu#p3ebXbY@<6`~q;K#4yuNyU&_rH_RjySpEH&O)h>Bmg!JFby8=N6Dcd?S+ZTkdl2uX)M%?G-h&uOH}V>ZaYZMIYR zOq;2~YTLk9C4sN~Xmq`Yw}M_Z(f*o%>|R!pXiLP-*k}7xskx5>lg@!D68qwAKJfiw zeJY`087z$zqFP#Ut$F6|DYsTNCC)xXD$vb9@~&VghLUk6B%O$WvnD4yM*0 zY$2@nE!(|V`>njz%xj@02GYsluhWbFX5*ju2C*0qu<-r1aZ1*4#`>@*c#uOBdtsyS zt0S6+DEwX5 zt>91ti=Mab#GFqutnI4+TD;>4tI)7$w)d>?$BKV?QwhCy!2Q80URIBMYc`vna28_z z>2)*Fqt*MyiE2AkyPCGYY@MB~WhM>~3y-#U;ln>gO}%X!`97-Lsn z;o+fqnm_gCYxV}tes9|^yvc4>GT1guY_~T4%-DXjt>0hIGDp~RFv~Nv-wvEze6#Io zcBRAq7>eNJe%5-N?>LiKzCC<7<1g&EQLH@I&X;d5d6c%Hf-v&UHDAGjmBdblfBP}3 zlfx<0&6?-`LhxVUW2VsZ6*emyHqee#Gn#!!t7u#Mw}^)U@rVczns#da+V+yzO-w(+ z{yo6m&pfMaTd+j$S|{=MeUT3>ad0(a-wb5S$J=jIDK#(Cp(Xa~=t#9)&u*A#U+EvF zd4=c6K$3^-P^y@2w^HkLdm{WH?Sb^?E<56VRN5Z|WUo@&GEf`MPP>LC&9aA6Yb!!@ z8kgGxSVOga9a3su)9B3f04&%TL_9QCaie4M0VaO9x?Phd% zjaqKC!x?3U{VP1ZP8~PdW2yb5GK8EB_Gng62hKpLd4r~|x8Fq@HUl8X340iOvcWzU zKtn-P>SC%Eb-#1$cUk!Z(8f!PwKu zFxReO&3D+Rp!{w6<4$`%wa&7KvO#y*pY!+Eypu~^v+&wtv7Wr%4qNejdomro(XNHJ z17aKM7T6;g-D{tY{_j%f2X;uZ&GygzLpARq!XvUGWWvqp0lSO!eGvSLQu97jJt{yt zPT^QPX5R)HpwxUo`bI2SWyn z@~|D+`Pb~w&TBeo-*LN}=I^t|uwjS9?mb0Ex7rOGXO9S=jJ?`};^`Oh1hu$Qx=q2> z|6!ktre{c_l-50cyHtmkXQ^epGMaV#YHv}nJpt07KyTJt=arpo=ObvN?6Obh|Q8^CPN% z1-B~kjsF{^?vkC<+|LzD9r<#f-9Jg!AiDO`@)sai_e%vF!~C08+#pr5>TKzL^!S~Y z{~={k-F0|_Ez#0Qwl`nWVWOofzn9@lJ5`2XQ-A5%K+XBb7Oh;u&h?jcia^aT80Sw) zO>un0jLFgo+(QU=v`}YkToAR6kp|GiS4b0sw0=ly;&R`)-w{Vg^5x+I7y>ZAvX_TS zMT%gp!j0&th`)MDmh-52lrxZBFaEJbiJEbQBV(Hqcmav(a^vB@-1V z#U)W!by_bnR!SCcaN+Fi0%VQymKNKwUG-%VDjrjC(%%eN9^gTm3Kz|~2HpR>U2@R6 z$>auPekQ3!!HfRu)Y=bXt@sf-n`u&+O9fj?+4kf)#qoLKK zrCwoU%LkT>DK!e4ci5F`gAxg6!Y9E0+T~D(Qm0O%r&(G}5E%kg#Ylo^v>_7m#hJ1j z+dKq*PE^v@#h< z?IbO2^+@w0H#qY$-Tb1obCK*p=Iziyqo^ob8KW4Bt))!zWu$%-(S@N&xIcJ%-<3D0 z{7x+4yK*|UE(zB%oknf|A9)X=E$d6Ad#JNk+Qz?|5-VdsD_Ohcf9}z8N+kTKF=R-{A>-=y_~!wKTv#P|F8Grpr?4Dq8fOlt6Wh zq!3y#Rf@xAJoSZiH5^91n<_;q$a#~TM%NsWSJ0!^N!Kc~YAJlW#HBc`j#fm;Q)u!G z3E3U1T}kYb8Pbi&@W^JAJwH`GfvP|I^tEXB`}YBFkhG*})jlketHr9(yzES+GSRkNTc54gQY zL)3FozR!QUD8ImsOyb>Iq)Tqfd#}A;xXm7AH|84c#`$jJ_`#*4N{yLLSK$<@h*CyT z?BM_nb>{mMY#Syd?ZOD$kF|Q)XmSMFLJwM@e|N(rqM9M|2@@RJa^506HmaHP7EZ z-!rFbs%MUk+yxPcu+F_gTKa0UReWrkd|nktx8A4=r+zmhqHi5i=k@u=xB=$;?p64Y z>yfAcn7WA1Y30C-qisIqe7n3Dd(h4%^GDpSs~A-IEa%&4(cUyUH8Cr`!) z(a~2`Dn%KEf9Qy(oVijA-TFyJsOefyLk$wPUwme3r_DiJ(QZ!?EEBC*qqFw@PoJ~l zpW+@|{54nJ5f)7yJM=25n~R+uzQYhn#yn*N0w&!vS4!^ZODkXHshV0ddG5690f@B+ z7T@RZ4!giJ*k{`MmyQ`ADx2kNt+FYL}b4;t5rGyY}p zlMRi2D!-3r@PoLJYUW9`h(#G*BMpR%vOqe;k0b)7YNIGM+<}Zy1t2XwXOrP<-iE^s zCHZdoYKB9%Dmakqai5D2!!ZnZw=z&0i2>rEWR)OR9MwBYm7Xf9$cP&Vub&MhMXPG@ zhf-fteyw!7sp)+wF38P`Q0Un#mD0o0B(t(Q51w^6sSobLPFy})3Z%*da-uSO3bjSZ z*?e=y)3IIhYDEF1-iwo3o$jzJv%$(fE%#OC=hHE_bf?;lQ|oqDd&fTZzAWibf+Sx& zrB@YC3`ck0hvAo1yGm3@?^Z=6S(2MlKSG)F2H*`hwcjlFp|cYt1Z{slG!#({Th7WR z8gQi)&I#r)Ifag{mE!$tT%k1cv^1196v~kjfHUwoyF7q3BK*4D*_pMI3UaHfkv)tD zh<15u@}>x&YQg`>CS{h3+<}fhYP;;QLd;sXFlctd5l4G#A$3}vkO01Va*C$H7l{qTr( zzX7gUij7iCNDta2{b)o798^@GR7{i0XRE#Y})a5}XV~3A1 zS~!lKF|EcTBjEAh)_ZuRacqj{XRYD}1N`R!D&?+uWn;$}N0p5qGLYQWia7slqYb&0 z9r1$oQAo-J8uP9`#%<^ETDtLyyBiyGEMK1m1UR~M6ekyB%8bA#vuDq|p@5qF;hE!iGHpFM!HiS9Xh-Q+;iqiys{rCDRo$y=^k=6Ur6gsjHgRi1BlG~T z2rRUeUVTNSC2$&XYJO^bf85kEK)M=XAf)*mDe=@yNi5YNr6W1~PU(JjV3E{EsnjM@ zeP2l;{mX%6Cxu#fagh>H+BI}-#oCNk{7oAf}caSkx(y|Q^avj&$JC;%1cxftc zol0$uGHg<7q-3BoL^^q4I7es8*pWM=46rFFXk#LGw(A{s{4U831c=DjZmEcs50NSr zZ0mheB>*wgtXTPWT3#fpY0DuT*(2Xc6IoP?bXKX>S}x?^pr#Z#nx#D`O$rHYgqh{& z6h2uMe@ELB3mOAh*OQV4NHh%vb~^H_oJ@6ZNnf(yHt9D^BT>s3X%M^jRjC@DrsMrp z1Sn>+1+PgbxkgMYF^opMF8qxO^2mW1=Bt+{VPzZ&0rp9oLj-LfT{38x*KeeC4P3>Z$tDj}<}p1~g(x5%6G@T1ZU;7@%3F2-G48lNKoO zb7|qF=shYxAal5hfzPBJYUx4FzFm&P-{mMfmyT#99%4kBO&hM3>!>_R9(Fm}4!a0# zlY&VLslD2jL0e|I29xUsDf+)g)ZswX-TMnMyK8e1_xl-n2uItoF@KM)=~NcT%k#r~ z(6rbyi0e`$X9)a!{cOp)DMKFdH<)=Uwaj+?&zLzUqz9n?h@M3=eYik7Vrh!@r zTgK{SiSnIbdZ6h1V`FDtb8eTZVn5g4=VtPShP3y zM(8N{Oj0<&<8p+Ae%&Bo?Wx8#+{9!QD=y&z8N&h2) zq_!m)3I1+5lS&eLC2v}ipbA#{ZShmg_S*uyw=uXSzcB>K3mf<;zm*<4CiM-~<~_Km z5!RO1=kvmw=pIGNUnQ-n2vM(vYos>c1;oSJ=1G&mpp{Ak*%R+d|J%X3*o;Ns;^@7E;9~XgC}zWeM3$HfX_3tX?Vapx6%~p_M1bT|?tvmvprD zLww~8k{n6qXjw<)Yapz4N-})3V;mt=JV~ym=KQ!}oOc>c>(@d&>pUwXv-EKPV45B4 z;K8!BMO0GkNTMAlAg?+n%cE5MXj0b+s7so6$iZ;e0+(Fyv7}ZaL=bqRIGXe^2yW*H zX(Kh~N+s0qggl3C_yqcp?HikyO3*aQwkCe;cFx! z!W9)6XL=Sc0$tDb79kOi`QK*r?lExbxK>*clS-~pG2r5! z)+CTVHewDPzS8OYK=UQvlt1R-i!#P0jEIvvTy}jPNXBJ zz!^VUqzczp$q?&g#P9*Xfmbc2=C$%e^kQ{#HA@+(g$d~VCT$%xlxzD3c;-%yD2(Uz>hLj8+a#iU-+P^HhAALJc zd&tI7$INN7phys_frMDz7tP7e^1gabW}YY^=lWzP4y87qoD;M`P+ka5j9dp)2?03X zg-BsFM=GQZZ>W;#sc|V$RJ>8IrF*|}X3^|7Vge}MlAJ+5lsc+ud5j{NOzY)rn)a!r zr`BwBD&6@hG*X?zbRqOv?+6F=GT|?w#YwyZ^2(m|Pz)Abjfih6Kf@*kqhsg+pzZ;O zftG(Arr(I{Q*`p9*dzr)pN1qyZpzm4gS*`ip8eZ#aJL5U$!&z6?Z4{02lrsKLC<*< z8GJTQ<1JxWz5#p3G$P>8&(so}Ac*5|K@ckXOZ^q%%qDX0I4u$zY6r5WJEgfo9LnHF z^6N_vVgxyE@+v4wsUp&m+;b#L{=-4c`_IR3?8c1bKsp>B$K!!%2T|(%*za}M$ZmcP zr_+`bQu0No4|2$W|KZGS|EJ@|ZKB#Ms3lr@m1a+OO{ZJ;hyRyHER8;|h)M1pYV`i$ zRH+Kg$$hZ&CH7=XPs!m!CTQMM4l>Yp|QT4n@SPxYC5~8428bba;f(e@f9`k0af& zR&M*t=@SQS2o;mo`FH1Vp+aUjc!3J09IJi&@{`9k$`HU#-KV{h3dd`2Ru&aed9!v# z&yN1vBQ={t#(zR*(R`Uk(+WvODzg8?SM(sZ;BFQg>K58ROwQm+ znUo9Y%^&%@$URvu>)BWT6N)o(7{*XbEOgE=9!>uzj3*e31ZB3HPUTC_&=23}d;M>A zoIrXPaRM98D!9{~b}V^rP|WDTTendCSCLVf-8x$z>pX2{9n=DIC(oKaYu3COGbST2 zs?{Sw^zTuo*r;TMA)hkdclP(^Hi*kPihtV30sTv_fZDV9aYqdGTPFw7x)mWo6e=mh z1!g1g6>~VC<5<&3?d*T%FnYE(0kakH9^ZF?ejTfQ>M}MXTCs%fAE#9+ z{u7o{Sm@Lpnk*_#i-c+It4OX81eQEo>X>$cVfcSWfK>c-F9;9?NnjaHI^*bcMNI5P zUktokC#=BE#88LJnOTsVSCF4gp5H(ovOkmb{+>Reo2r%lgZCL4ai8XV{$+f`1+G3( zkxi%4PwxsNNQG=>{EVJv(7K12nYdJ)BeR$zfHX% zI6oBSxQ2y)BUzwTfIz)luq_`^{14I>fm&pF)?P#PgQS<(fqzMTATUg(mfiBF?8{%I zL_AfJ@r*Qziq~P5=SNDBuo$@HLbme{sT{L-$Ww^Ox$8gB2T;nc*x;ut<*n#6g}QFV zeOF_s3}?VPe|ZL*A1MDG5ZH)2UuUWrB%3fLyGbiAhOz*OmbEn$o)LGBnhz;oxkxPl zfgp%0W#&K$QOml?4(VBy;R6d8W)KwP_mwv1~83EIaK#|!=}ol8~I9x7h|6|j4L z+G_m3^W#@JeYZxw$!qUokhc1AXBF*qI#%R*oxN$5bMV?T$URI-V-{Kd2vXK8YWq!o zpDN##hHcs@y@Z?RO5~s|a7>0yzvPh8&Dtl(*9pZ80te?pjf_`~quG&;Xeh3mT+%pd zUMyWhoeIaFTpzWFpiMGyg>)U)ZcL)(-@+&b8EPyCq>kD@kl%(N)ApP62bJxS$MMyG zMRZlM3>) zkaZ5K=qj$HMsiGXgaiYZu?FIsE7YN*t=}n3q=<+ZK^x{PgLF0sOcz;8s3j*ZlFt8@ znoBeGgoV)LEOmm}=oY@qJ%)>h?4Dm28m(TPE|d-{CQB&_gdd*_W6aSP6)ihWeGG+YlaLX1A%vlHxZSgpr z7C8$^O<+0mg`@Ub&#b;^)vy#BZH3kx%i%qT+YUc>c>CVNZ5A%@rP0!7p*BdEfPi5= zcHa1lbjt!JPxq)4{+Th7j`U8`gl5ae9GIRE8k@o`$$1b}pz8Eya(&HDxkFj(_ZoKP zp5U#OjZsj34&?i9hJtU&`OWpJOrDt}s1u^R`=%l$*Oi@bIUY^3soas`*)L#h2Vj1fl)v^;Y;B_3LoHp_g!d-+MjV{VMk0B>5V^v6Py2$g!91@KIE^ z4MzuT-x!wbksk^QXau)#i0U6s?8o5FaS}Sy>oMkXD*sx}U|BQdBUptM6dolb(p;Tf zgQpw07NKO*NcfBxf0u3t+VXj2+{B_5$bo@@jWH^}gC_oAP6))X35f%04+*Zlg;M{G z0lPICaCYm2sj0IW#~eH1bKM1AK|Mbk8j0c1XJjF1WmW+)Q0H^oPlT1qR~TaH$5O|X9`>YzsUGy7p$OjA(1`u; z!3@6XR*Frx5+C+9WCfOgV&DgYv2Wp&-B*Hp*-9DiW!kmWd5ye*VI#eQLlCd>!%blL zH1R_)QDBPo4@_^2V~X45KR|kK#b_HS{4s5BR&S+~l#kA0sEk7n)0jV&>7{A!lr2By7(>hA-x{^LGSVb{-*dbaFA`6iU# zNu5W-hOiNj%1;By5K@6V%GsD!c{}RvBKK-(7%ST)WUu4sA-w&8{u}{*z<%B@M*;METCQ$1Q20yonLzC(yvDr*i(Cpzds#k-!S3VC zPa!Rjlq*@wYqAGL_cPTY`Fv2Ywwa@&oILL21T=Srq-X3KSp_H8t6=JkQ#j^RM-Y-I z>YtYF>;;8mrJ&F)tR<7HSz8yy0LNVdBS%FAw6{~v*|!t;qYZ}ETxp^Bt+E@ zal|WkcgSN^fsKjqb8+(bSVhSdh*trnQD4^YH~D#RRSxNST6Qf+_KvUQvOw(?4E7MY z&GO5<05tX$DS#Qik-x^$Kg`@phY`?ZN)yG?cqmt?Amb0J%F#3jvbIP=zM`Z zhk+rT92DyBm*b~Y`#tEF1JY+v(Upy<)RN?w7OLI7wY(9c)+;9Q2Fh9(tACZ#HfiK{ zI7hbgp324)rcZW2D$;I80b+V@l<&Eeb}`T{m;+&BdWReLpsn-M?qVfZ$@^g3ijv== zFJJ#>)$}ANAElwZ%hOabTskcF;y;m`rPMr9)`9c#lwsF`Ii87*z)1UxMf!i!dLUOsC;Yk{w#d?adu)qb;;(J9DZAsDv(eRBB#uioSwA7KK#+B^?sDp*4 z*wwC#LDYOz#57vFIvmt`encQm9;pCT^V1@?ywPzUB>fq<)WHt{vU1Z*xtI+%Ido$y5W!Bch+01Q-qj%i~ z4}Y~6SZ=1_4FxS5ZO~A7wqYBlcp=-c(%%Em?G6`OPRcRd02SDY97DeOB5eVjGpeW5 z^7P9&@XK(!@-XR?Y(xRj?qeCb2E=r29$-kJw0y&S4B2;}A&FSN;oCTOPPN;WQ;l@v z$W0EXWEX-5;P*Uww!ko-L)1{IP=q5SK?i@9>M5Q{aBji7fM45RVt9aq@*=&OYsjFD{SBKjlXHOKEm93M zjN|dqCJr=|!~N&UfrbqVe-HW;4>Fjz`_D_&`FxdXYx8N*Aj8-Ip3~Yxrv@3^^l?IB z8ogZs!_VXEQzAmF7H)trPMJc-1_Zb|1qd}zFiv@udvgGD-5|*->#TK#m{Slyt;|{L`5uYb?XLbuMTh* z!oiaM{K8QlIE*98ovb>R^jl^cLj>M1D_ z9IBt6*B4}pky;M~#0QED(p98Cpz1SN7$kbULwvqhg)R$cp5SmNvd$DjiYXZmt4Oz} zNTv}nhHRrsN0y8zHHy)yXRU(29;oF_;AE>_3-T5ViOR(Ayh}r6kEx6&MTX83@#cx+OX>%&#E#r zpH;dH0XzlfrH7N@a9$zk>Z_EPW%&HRJDdUHaI%P8gTLr|fTHsZr!PB&45v`R(@EmI zO%x4Zqv$?{JnkAgvX5cs#RsbiyB^2qAb+FRDY~EG^o4U&ge%Jzju34wF+}|J95I}u z8HzA>ZViwUQhRIPpzHyLL;MUq`q#7L7N;dhoEBIzPYyC<^_-V*L_pLYES-RKYq}`v z`~yC~Zx0BF3F$sD2u4+%97jt|$461+#~OsL>p2*On*VaIr~Igor`ad4JM{D6-F>he zu^;aria>=C2UG>tOSgHqk71$vuN$4aTgQ11$X~X!L>ZfPMdL1YnaZ>-;_gAESKw@1 zAn$oG4g#f19%{%Q zX>6D`XL`*dAeypK12IegD~(fTX6A6Q-`J4FhxEu$O!#!8Rg5tO<&>~Yof2Iji1ZZW72JEv?aV32 zb;B>fJssA@5g)6=g1H|sv;po5hdA~h4lylWp^oF|^5&-D zjsbyrtN1Rt$HXQ7IcOtggawPmr?FMw%-*%lF=zC5={<-`z{lDv#kl*%jaq z-T^f4ljob5O=SEi^u+F%z+npZ&@^X1ajcJ%DjNiX-!UcPHhfuH)L05=Z5BF;a7JySO{7X9h^jm)`ZTXhL4g>G;d94WK2$Wci(K$ zH&k?{+t!Ar(7*>Ah^hXJE@ZGvY*wp>+W;;~ndbAQnO$2u7mBheP>A`=s9vpDCeF>p zE`AM}ITPVbor7hO5!n1xZ!(ls|JZ)7*`axB5qIeKh; z81k+><3QdOZ6|q{1Ip~@93SDSi#j%iO=UCM9h>maGyX2RaOPpvXE1b zmf(O!7k9LP7h*13YIZKerQCM|ZCvR1_oG>j8E~VdrX``dd~scYT3p%E{=PB%I)vr= z&VjqM+CSJ;cIN{Mc1YvQR?rm%s3Z+iPT0RVdexckDFqW=75fsJ2$wl7)Dyd2M;4kwN z=08cENN0;u7f3Ci21U}@pP_s_x;6psoxeEh5gy~qAbz14WU_5Xf<%?M&arG|obv{N z7|ay2l0x7z_N8GJhpAq{0#lrQg901f&=>XP8NJ-w zAL5vILn~|`IxVYrByLnt;lCXX9Qv^gFmJ`u=-82XCqk_?|7l2OqkB8Epbvtv|BL{X z0BA7XQ{{BC--?|o|3DqA@wy16Sd>%-AK2P>M2~1r*vvG8oIhf$NNPK&&mjHYgrJ_6 ziBzyjL!1xcm7-Xe)~Qom3i?H#oiF=rzNpdE72&L-zCSx&?sfiJW@rVp*4gPlng3q$Bqw9%B8javlf>o$u#APWcs-ee*$hYJ+#qX@p+s z3yvN#*?Nz2oFYV**i_b-qw;%z>$h`2jXa*{D>il_1N_$F*jS*pJ;kxv5NmXXTdEtg zLA8bp++wP7xX|Kol2Ebmq1Nj%7lQdTz76JZkh%} zlc_p(IPDt?pQ#1avGMfPRE>t+R~@TUSU~sqpBz^$L|!fu;B9tE6a`l_^`QIx%}pIt_w1}_=9d$)qH^JTy&n|9S9}0)wP>yV@oPrSuQ9!-1rmf za%bh?zx=EMh{7K4n9x*TJSrcF|KVWe;@{fZJpAC_8vI*b!%L~8Hn!I?`03>q!c&F5D` z+`I#tLifRW3Mwh-6(z{6>wimb>FO_I5JneXys$2}EDB4*t;cB9wCchv8gWjW74NZE z*^Hydl#MEd(_uI73{kp)iqEKG^u~rc^J;qB3A|eOLR#F*{4Fg`))mp*=m<9@nH512 zm%kgVJ;9N3Y=eeyLD}k+o-Y}OnpQgE3#tm;xtC4}ihoI`DO@_`*VKfN+-+;?Lsnzh z-Bi6NEH>0T1`Ys{$h;XmmpZ?VImQN0bM8^9b;tc&( z*~_7IY}iaEQwHl&^764MvdC5A_=*wdMhreJy9AS6x`uawAYHg{-DRk4+)z0h?UHY1FjrrhqIX|Ao zj<0r_m7zM*>gvWkmET5wTr+7)rE`~3*X!YxPM%*?XU6q<=lLL|&PrYLa2NjG`wU^U z_|9M*ee_3c25WRZ@u_8YtSBf0*z}i>w>Nb; zP0YU43Ep1kpzyrp>1_6cPQ+!>Ia&Q7=K%#f_?R;fxHN;p`z3>4*yh~iuh6lkkf0^d zaSu#h!@_nte@1UNEenxbXo-KuN~#PC2&30$LjMUD>}WRmX=e-fchNzc_$LN#?iDl< zeEan&d9-X>NGGKf23<}1mz>`#bkw#zWIJnn*;xS)a*$Xj2?mANoG>Wpa+&Amq%8{O z`o%d(;O0E$xix97f`vZmTrCRnkyIz?cDCsq=L~;;U4eyCUk!88K}Cj|^*ipw#Z_Hz zYDrX0ruI7&!EE{oC!wehZRrF4rgMLoijMAf_F)fpI2VT~8~d`5nBb^gm4fua&tztZ@IV$(EZ zGn@6B^A3f-u75US;2PKYQ`y5IT)C_pK=o^kuhG%3lL`pS89^Jqi5$bGD>C{+qAFxN zf-}neHM&By90aTeSKNFm{#2FDT(s4GR|A++w% zC`96p^zX&`#bxjX7|K+M8RzlKFldOyFT}njEhTVj1}v)o9#b<@X4e_FU}s;*7VjaHDS@ z-B`j}`e#hSLpgemq1*B^=CkVuX3SA58waqnTbJAR1BGv@mye_BGgYgTR}}5o%LYy*9C8`XuLw@_p}*JtjZ}h zRB>4849Emh!2LeAE}2p$X@m0JHPu{qTMKkiTb%{@uEvFu(5vy+q^xY9W5oY()p9fq z6fWpSZcbi)uA5fW>!WD)h_r;aY;P^%d5F_J592A4&Jjcgl0Da>!&dB@K?M~<<@XveX>yPeRb*p~2>fAb~ z&Q>nPfi+z&Qi^;lzRQuXP~{$rlX|A+8X&?-GoCPbe~AYb;%yf1s%MoNmoyy(Gtmqh z5nRiGZWJeWmFPnFZ=hjEjal}H3Th>RqZoc}EvHPW^!fDM;FaoSaj)2mD%fS>f(s}e zj%wh;?E!bAd2Cbb3W}X#yXqWJCYlhf`@7Fq%nlDO3~5Ad+TRQ+hoBu_50w0hDM4pO z&J6eK)?27&a!QCv*q_1a9aQ#w^!Sze4*yR-$AtmF|1Us%s1;YClJ+rrA#@;!5sY0Z z?$v2j@?EZg-;e@u_x18xxw<;Ayhbhpi$}TH=5L`|&aZO?GWptC+FECbS$W-L08zkE zUkm;YSqHr;YM=FeztkS<5ay|tsr}XVmx$3UzU@oaT0sgmZ`O8(S_yK z7@E9M3bq7_8W~xrd45`sEyT~`r7#S7O>L^{{X{>FUU)|7DodMyC-2aFxrMC085Ezc)VKh6akB5^{ zH58%EJI!WV*N9N5ZGS_+jJ#J9>IX?5o@G&@Ji+H9{p@jlu0K>EdATlGjf2G*0s>TH zdip@y08&H_unjnICc<^Q0K&Kfe5ULVENp75YK8Hlqud!IEP}C#fj9RT+f|>VWuV8TXT(+_IFDp9htxq&D98C$w*JV z-!Ydo8f@VAUJA8P{xY2v&f1=3x_hbV6aqK3VPQ8*o=rF${QinxrTX4YkaDi(q+_sB)kB00GgRaZ_P#U zZefu^CmvyWls$({)91^O&X0W(X6H*=*)SwyoXZ?qxt?E`%f@On^^`o19Tfxzi_<)o zM6=I@N%WT`ES~Zj*z2$m9bU>RwFO0VwL}N4`Fu9nSmO&@rM6!i+!Wr(;)c7@#Il>& zC)j3~MLaB58krVCj_`p$=hj$=O-(XUeiO??+_L|<7A>q>P_?*j!NR#sjkLIll?f!( z^!p}u3!PjJ60tTjk1yakE;9xmYq!8-ZD}(bO=Eg6#&8)+pabIPeH*nIp#|BB+$X-$ zlr35(O-_iyp_2J7rxQC5L?BUehj}FBZPF%4xitu)EwuxJzUU&q#9~DZA_-NMuwn3XJlfi`|b&4Q=sy^{;Ur48}W_D1M8`jF_+WNt!KjzQhR{GaB@cH{B? z|4x!2(~RJJTHlS(jNV_tyxqN!jSC;3$3;kF$H)XdGJ9lF`5rh4VA-}rmihy+`gdPr zsD%aNkWx*^FwF$&{VtjLlEP>{4bpsipk=~oI4GICx9Nk-yFvl(Y8T6Ecb{v0+AS&-IR!uYyH45{g)d7`;F z8s4ys&4ylUB7!VM6KQm7%vv&?Ol7Oq<4 zg&Si>W8bv|WWdB}wiBOY2ZE&*^wLV(PZ)<&<;!8L(`|C$6nXZr(GnC?R0xR^8VLn# zxzI&P1v)D&d)W{@zt9Y(-d9izk+T?_TWD3l)zx1R!|+!RZAfvyenKc@e4-K5GW9Nz z*P;%BQNF6-fSUroL3)4yAmjy~V50s{mkX0@sipAhtAG_>n9=4ow)zqW1f1zpla0fE zdM0bq4Ow=PgJFGBQq1rSC3DJDdvz&r$yrN%3mo>^9;DG}N=Zw$K!G1%gTP&1dJr3G zM-C$4E08;8OnPdb2Ya#V?7Ebxx8HhOh5ZgJC?bb~Pif#Ol?w}tgq9}X0iz#ntj<0X zI@w)u*Hrf;wJMd5XYuiU#%&QYHb{d&<>L}Y(3P6(P*WfKrH8&A84FdK2neKcB?)1a z@>oI?eR?@O+@A>v{-Y0ev`lC=t$oltSs%1zY2VC-(~1iSJ1$bB@Kg*xBx%T2knih@ zq0B1u$n^ydzldnQAKzzE?+^McI{KX%9jysL=I~X~M!NGQLpL?g1V=mBYDtCvcT0Uj z9xd8sh^P?4tOo*|$FBr(DoSBtVLdcQj>2M43q)1S9yCMHj1?5RaMg)FAAuBuKvcz% zf@Fn7MP{h81a-LR;x0pCq`w{T8ecoud)RgxQo_}`eN|>*}B8b#+&!q*5C;5byhu9 z-DP`KEH_K=d5Dr&Kv9=A18@F$foH$wdu6)6&z#R794G&-lgEkn-8+F zI(XaC`sNTE7krQv8x*;u)I(WsvczDYG7PC%!LO&BNsXcLvL=>Rd~34AWOz#FWoDMZ z=#`n7(MQ0@Q1T$?OSp6BP~O>X9Yx=sjI{b3$*)92CKtNLPnlYQHv|^GnjCwPOUdfX zoKn-D#Kh;~G8eB5-X%xIQe^mPHx7%X{U7KOb?J>u5V;Czxbsc+0HvlzMpD66W5y`F zIXUogS=3mo9-`$JgGA`Pnu7?sx_no`hIW0F7P)cSi^7NdRZvd`={!MC;&o{^ZTN+^ z9BKh!6;9=tp$2W|Q&yKyPp{lG%nJ!>zfFCUP&Ag()`x}DwVlX)bpBsvNzC1HeA;-| zIxAA?we4UvNLD(>HrhA-AHEFVr4OF5;xxXsw%f#v&8IJ3WFu(OQ?_*2rP!V1{?UxI zN7GTS>H%9gjXlUV515%-G-D|JO;#yJ6vBOBqLKED(&B9Jw3sBS{ZbR5j>v{b^bw|P zHPTOd81Vnu{TizW=}pxK?M~=>utuo^Y`Clks7nK zdQ*rOKFj-K#6`kIIAk1W4QA=Frx$pq>4RPns{T~I`#7u6giDX2!N==r=4GU}D(MvQ znl%bmH1=oE|0;TDxG>1|uuh{+dSY;v1zvELX+nd&cY`qnqs2I0@g>_0`^Xx!_!PO% zB5phPQu0x*`ewFP1=+tXlqONM z;3u{wSXzswo}u*z66aAbB_g%##>9WqP*%9?rFGl#Lix@Zc{&UnKLx8A%nj6jDDhrC zD^^ClUsr_L!jTE-8|d|}`N}(K`?UktFoz^DEf8wPU;(c!XaF zk&|(4BekR$pSXcaFpi`9vj(m7EVWSL6E}i11Dc}esJt=>&a$yGqLrl01;y|lwMBxj{yE_YTqtR;$<#*KU8QhP|tnRt$gA* z8Ch(loz(n2(inzb#bT9oFycI4G(mn6_q<4+GZu0Bu>2L&lrJIG(N}raj>-`++%ZY+ z(S=I8ir_3z;6$trk|f1BvS^oGH?PwiyP1zImw$&Ac5{7&+z?{)-mee(ah8)J${pjW zcU|N}kd@@wR&8V>zon(`{| zX_Fm*(%tdlloZN46`R8E+#?^*8NtF(lT$>Su9nYvT!tZDdYvvrTMD>qjeH!x-XJ@Z z5gwf#8^))s!#!FFUahMW>{K>8ZU=ww8Tk>A`%Hf47I_~gct4su+|eFg$*a5ME>u23 z?R(;GC;QWEq(Ik!OKP@3TM0LTjV1ipC3&?bPNlLNrM9|*Qr9Y{C~T_4CV;MRs7H; zc{~R4_W=3ff%fP}ug|nco$1rm661J>Z!ci{7(n$&2v4#@6n{p?9%U0@2$m5GoThvrNVi(0 zlJm$S>bOF=r(#nD_5M}8_&Fs%CMQ$YXvY~So@An~w`1%0_nFEtP)jdGM8Rs^vjXu| zWf!rJ0!_b9dq2`T_^ez-(9;E4KU3E#2z$DQpN75RkC ziW}tp+HRL3Oub*;jClSslz9GgFtL9%WMck$lZp9yz{LDl|HPR19Tf^P)JPWtC+1#7 z&`)(A{rnkY`L~bhYG8H%&ckJF+7P8^JPV>V$48^R`#A!aa}ut+y#6Bguoz0M}! zT^WcnG7zP@Y#=B-V0HUJxs0W_UaS=3d`t1L+M)trg{{UC!}sm5{yR?HUM6FV%XVth zu@YFUq=QLVNEM0gvHmP%Q++_N^*ikGN2jnh0=yWIu>RGKDLwhC1 zRJ|n*E~Vuk2aoh|IqKOJXQFYd;>1ac78|vnh)t)my>bO#8SQvPr?tdWd5-cKKR3>i zB(m>Y5_q|+%-8UF6C6*8RmPIo|0wBvqTS(!*>W~to9SrQ23v-s+}@!K|EQlM?}9ZL z?2fLjmSKSkvLuuHT~oAw#qL!cBehz~NWQO4?pC4ShMy^P^^z@d5ZqL8b1K{nUG z!`xZwz}b+NQ9N&rd>ZFnFj=@NS<-l3ulzDj0+`^KhGK}j%kY$7&nno%$bxc*LkEAk eIT-!tQVEQ33zxGTCjs-YF}P!Ibh~IJ>3;#up1}bC delta 66139 zcmeFZcXU5_%`0NfQ!!C;L!Dq$C6~0YMU^ zsgy(^3z3p2C@LjEL=cd~f{K)IFM2HqZ|=jDtM|P(-uK5h#`pd4GRB!XXYaMwnq{>) z=Wp%eS`RJW`w=M#v;=dUek1pvKYeNIcrlo3JBF(sTOsKw{}AsOL?)AQWE2@f`jH&cm1L7Nl1O4nGzojg6+cnV<2e3#IhR|) z@#Ew^_+z--8|e_a7t+3R52U$rSEM;|7okyEk5nbcAr)meQbBHul*(;5ZVCNEZq0G@S2+f0vmA}|wj7D{ zE4d}oGcvMGYvoX+Om-qYAft}yemMx~9yt(ch3tp)RoQ`bn`}q=iflu=QMMpmC!3MJ zB%6>fm5oRj$p)lzWIfU)MY0b6O_sGt$H*F_!(|ES09i!ZM;4HFmwBX_GDVsoqZ+Au zF_N~6khETiBx(T?%X}oNIY_u!97q0`f#m1uNd8%blaYKj3H47d zOvJArO+a$uc_atNA=z7iq&y$Vnh{7=3`4SbD3V!&k<1u`q-Y?L7X~1i*bm9`Xi+j2 zElEbBAxVA?k|AhJ(!V>BK4?nP3vEcU(S#&33rR)?Bui2=kS3w!NFo}Jv`a-2hh`(u ziAY)`An|L5M2CjsenY!)|3Z6lKea+~A8p3nL6dQ}(PCU<1d?lLHSTOUlH+JO?kzMN zx7USaj}yrbv>mqvO~<{2mgANOApe}lAIT!LAvfI@Ns$A|L^LBe5bek1p~<*hv=!GI zZN&9L6LCG&NII*KB%_tMC^QoO@iUj=7k-A^)uXS`6G07?=QH}TL37DIo^)fvLh^(s z-QnhsKEB|7iNvv9#f0&s2RzsnH5#TZAf@oaD~4gL?{acRB5D@6hIApKByg9_P-OF4 z2gPp$`GS5O`6ua8Q$iL|qG5~Ilh;I1D{$N0(2!${WI3D2TRh3B*-Cn-M7_W*OH!@} zK!@1@R#>ou{3z-KZheHeFks;X`0y2yD2_$``;_k_9}y#bypv4Q^sRxXDpc}G%8OG}O3VjK@(T=tR^LKQ4%|*gfG8`HbX-v(2;U0m) zCgOy8l}!cf9*`t9_cD2zkP%RIg(QLVQ*9%ZgqQ`E`;dG^72Ayj%MMKh#9mA92!%fo z6I*bdtmMfkaJSR>LDuhtu}>R`nUW_4n__GE;49)U&O>1L(Sc&Ix^7C`Q*^kWPgYC~XTacCL1KP89Y;1klH zJkM&AXcmE-148BBRWXoVYa-i;Qp*G||ATx3&EKX^V7Vf#Qk4&+MGm^JE%0sTI3M5XG)h2-UsX_GEpWv_=B4H64TInb?{R6o@aY zbyF)<&+x?f;5ZlzFSMqC?5B3rNXSgc+im$5U^83T#zZ=m^0T`4hK5G$H7DFoiM2_g zmw7V#KXwAN(=?W!L0_X}4rH%M?+sZm3w>DQnsg_HSAIuT+-yo$gYh}~6PXKnV`4(# z+7Y*Ri)|Sg?)XBMY6HgXG!`Gw*6rKuw39tiK!jfC018i?^l0Z8nrUm4m2vf6~ z0kn-I`U%|XbSRDD?Qpl3(ZUQP=+BfaVM0E|nWz}Z()bW?4rt}amJn+jqG)uJcRa{Y z^rgEa=*Lhugw(VOge3vg$hJ?Qr`4jrz_rpmnTyK#%ij8k|Xpayt^qmofRIXWrS>CTec*RC(!x?jfUK9$${*^aolu~ zjjZ(5SzR3#VTm8n^OS62 z$4}D&0{adoTiKj*bTuWLA?vN=*34Z`9cmK9x?G{}@Zu`0XDd5)mrf^Ts|pNIH8QgW zt7xKQDA@+&e84KU_8U5jC$B(j>ipH?_AJ8m{T{kJcwUMTsSy*oNnHLf(Q|p7Nd3?d&$XT2Yhp5oiG9?%bD!6aK0^1_LUU{#IUwe z{AWDb&(2KyJl%fs$6d1Ru8IQ)nN~CgSVZz#(wQrQHSRzmmJKn3Ly78dji|spP$5&BT(0l9|T!ip%q&(m|w(`cc5A;B(t>P{287cg}8mXFcz85 zU+2lYOc<-wattcdbeYg;JpWEvPLePnZSm<0jW6(#a4w(k&w4NB4-@hp z`!dfblrqh?wlBQv;rr~$0H0liy}q0uO35i`aQVE#wy)&J^5irVRx2`n0I91p-(@FD z_)a|ekO>=FVMNamEpwkz2FxWUoKYD144N)G63gn# z)R5agWH8%&R_P9xq1GPKmkq4v_w(d)sOTL$0WSP*P{G>aQUF_hiJ!xhE6~(NNQCnz zY~gIg6@E8Qu0q``z6Vq#+FxMrUgx{<jl87MwiG$}c6uW2v%%Gy?+jH< z{vwM94)^X;?Hj1 zf)FimrBHC*=7Mdlg>Cw=*7zk2f2UnX%M}QZ_CED5jNvvKi0SfL102R z;Spc%G>am?p5xau%Nd_x^lj&rHiTap83V6&7AC5=d&u?!Xg^p$jrS71Mt=T*173KH zU;fF2{`f`BJ#@#r+qqNW+!!I8wH+*Et4S+Xm@llwLd`W}gfVJq!@izz+B;btnCk0 zx<#0Vlh_^scMMyFBJWcA7#Bq4>itu~@9as1u--c|pFrsj;WoRsOmOgoW2L*5A|XwR zAbW-B%7cWk(l_ubt6!bmi$aNwW-C`IG}ve?jCw!49UE67yeesgRJS`}tlO;x*D_}d z7QIQpji>mGi?4a2f}+obmT8?5xjDG&n)Wj)>mYzv0mb z!$zkPR&j|eB#>Vsgh3lh?la2~maP=LbyGH+hQY?;*6;E$4ys?pp7!M!@czjfN3?+s%iOruU zBoLNyLim8717~?fxbKw-P_v8KnmS=NB_^o4EChmkyHL+izjmIOp<=t>fNjf!5@=i@ zXdr31Fq1`mB-~K(R(DdF%Nf9&&Dd3l4HkbYc;Jmkf{|@_C`{vt9V``s9}D_L_>RX_ zb@4U9!&W>JaB1*?#W%5Rn|}$~!zCgvnZB_2YhfZR`B~7iIfD3-RDMTLsovt~Tijc? zZtj-KirNX>p7Dy{@vOa%c$!H5M_a`S+(XFAw|aY;50tzkXqfx1AQ2J0auUD_?s5Nd~sZfG?{TnO%AqLHcm#W1ey`9Ob!~((GXA<=*K6 z2hOtU@sVu60o5BsN?tlYO+cRjpNwFy?Nl|8-FsC+4SJ%Ykb%<-)D;?{0L#X+pAM_E zR7^nz(wJ~Wg%&`+h8VE}9#i#HiyZ`Rc`s-R*Tk`Hr&Mu-WU>pVRc=b!m!DCM6SD+v zZHo6c+`p_cve(b3hVmo}3YPnJWh3)^&lBc5H@E62aBnGHGY;-uR;^HXELn)tP36MBJa~7JUrUS;5hcX+9F$J;n+z3a zRe@~P71cbRbb`1IK5bdM>#7DF>B^cOg$43peqY_BbmwkRml-+`VlRY`Whd^aIx8Wg z?BOnDsZ-%n*8{TiU1wSSLsdLada~woVI>4=kGNX1+Fw<6jnEmRJCJQR#DRvFmPLTL+iB5ToFGVpLP zUE2bD1ErVQy-3L+kipCuE=BMxCRX}F@Z~u>N5e@K{`3C5qUj}YH9m?KQ{aB0q=J*H zZ7Hn2n-omc!&Xl0iHqaFXLl3SL(6h{;Jgw1?2?wfkR+`qWKd0tLEWRo!1Q#T|FcSz~NaE6UnrS*QcyEfY9o?9tnEChfr?{LTjQZRrF|Vcy-*5Ki+KXK!P`s!E&WTYv7!#k zr4Q8;$|mKaq=TPHieatJDIsj~3TXo&<7!q)T?C<@z?DGdYS&+R!78GpNh^Bx6}UCZ zh);d0SEXiVS|dGFVbFzqmhpt~8>I#{831V0j?DK}X+DvrmW=3+y&7Cw!~R<3f!?^u zy1!jAi%JA}8dO~INoQFV(saErP~f(K%g^+*IBVXAJ&F|8R7sVBFi5#qaG3wIs(q5H z=CJgHlIb-^r1^q481FCsw|x7g(7%to{w(XbbdriQu|*8NsD0M5^|ewFPv$b=l=OtB z^V-3+eDt7jYp-Gb&qxP(45c*n^9cdm-utlBv(m>rU65SfSF|(#dg(S#7i!_c70HJM zUXs4&g+=Z}Ew1}g3l@F_Bd_96f!p8dFWTN}(mC16MoGh~m(=n@aEdR62m5gRKQ;S4 zNew6V^ZsV-uyILaM4AK}l{ z@L*yPZp-Xyms%JhaJ!+hv*2R+LFzR`P1e3WeYk-8TknMOtdBEncs7PXDnn9XT87GB zndx4{&pK$#Hcwzvoa%B+9F?-7)#|APmYmjPv$vzweXzClK(a#M>!T>$lR+T(T;2OcQ+3dBC8k9RayaR{HNP)Yp@|I_Kbre;R61=eq z^7LU>*^N%>me{~%$TNoxWh1+(cjB)t&@dY(S9dS%V0JCh3h%iV~Y9+R*G?qHR^wY`$G6cmOo3qg9>C=KBWoy;b2qkYqL47C+Ug8ye9~4{)4Pjv$)Hku> z{m@_yMK^bo`fdDm02*Xo(C zeJCX*Z$sX2R~Oc!N__->9fE4Vu#4>SA$4o~b-3nySnV`GeN_}g_oW|)<`b6t zIBXwLR@r}03~0z@uC7mOJ$Opp51)Jo;yw-U1h~j{Vyn-nXX7P|YkVHwjrBUKuEAgL zLR`<#bu6~R6-d~D2K8X0Y6?1A+!Wly=JN-1&$FqxrQ$W@R7Yt$N*Abf>BGD>nqI>1j5xa(Sm+}qG4Udl&h|XD|IvdPz|vxy*5wQIfN&1ExtR2^7UVUBJ{ZZLdbf zsv6Zd(DPF31x2`e{Cv(V$0GlQt*+6CUcFs`>dE>nRQ z?+cHj^chen>vCB0291EpnzSfCcC&{j3`flu(C|mZHn!cP!Psor`Yh9ESjP#P zQ10GfOA_AB3+S`a=11%|i|Ox_Z(Sf&1FKugHXHJ9V{+N-$hPzF}mb#z_9d zOiQ9v{4I-KqX{M8KSFZ@iuv$3MMobKRXa@1l3O5)I9!miuPC%#qPaxLcTjm(jbV~v z4Ted`_X>j;Z(gREi8I@6DCivVdik}8>5AeNbN>jXQ}j5qmuSAh(s!V=P(J|NOEeP8 z-=Nunm+nHtGTi|5AtEQi;^#FU2*9eKqBtr9z6^}S_*k&Um(@?uEF~LjGe*J3xf)Di za^xN~tkex;ZSpjy2n&8y^9|bRJ`*Z5R1KKN%{o2df#c%(95Ga0JkgjeuSu-FeZ zTeC1GuzdbB%!NMN>8V-A{hCuMm7b&XseTV?mGUdQ6@jrvgA?7uRSOE)q8g2i&;O(5 zh(@Jms+|E}5oxtyI!2mCdTwBp88OEtTcrH$o@4jQ^?YuM3F+U`8jL)|EC2dLPgwXo(a?Q{x# z?)z+j16{R!%QtH?;ME;kOmJZ2yQY^GS6Cx-aH<^;FkX9%eU_`maK8!i!`0}A_Sa${ z#0*V4v=}QGto;HLVBqVLt8}--~zfp(H)-PvKT zzM&L@MTjETy)?&5x7EgkoNrAS@O}15e znE+%0ltx3qvRfO&Dm)7Ff8(hL>St=(vODEkUqYO$sY08AnYf(=MygPak=61D9pm?NK!}rQuGoDO?6fWnMh(Dn8J5WL>UnYb8)^h|X2{q1Y!9ybrUj-zdE#x~5r+D{MaE zdvKl@NTfB6MHn(^`?P`kFZl!kMn3%5wV$-%l(eb&MJc@;aW=RfFOb$2E=Di!-`rr~ zNXUBRt7gAH(h9s7i(Iv1Uk(hi5#~#DcPNR6rf*^}#`djbU=f0@QBp1C_yOEftZR;X zf0E1z+!uxa7Oj^djfnmn*?6(BNGf&wDblk=Waac9f3F;h{Yu3HZ!_EGNo~*ZB zcbF$BP_-~{8uS^0&mZv94d6AY?!@?nq{O&nsQQ;*E{yz_B1#S)7X|A-FTcK zZljkuqrI+#kaV^sPX`31W(P>nz8<@E$v~r)eUPEcpdg}N|}fP?Qn#ruwfC*5_= zqbEW;>~zVvpq^18T=6inyKWpC-$S>VhzNnAS<3fvQnt0Xj_u0Pjie--O=^!ZDhcvZ)f1qxU%C^DySq+8m2Y})w)q8JkR)J-y`v-}ybNmK z>Ie34=08h!i;B-7mpz&5Smbx!O_ReryYzy($p{ijn2*t$lAkc)w60871XbiH_NM1jJ^ zOwt#c=4)mut)oAh9~xfGwyoEU!2#P1H;njjKWKO_q9Z)P={v74jEtD4Yz$4+5Cp(FQW}dpZ>+cF*cY!ynU~Zg#Xm zmrKBKPWP)KA)uAOG=`Z!sQp|SB^ z31I&w#0?GQZ9WikQ`e}Vf9N>YYn8=CSj!)DU#iISke#K8V9sLwW$!FGfz_plkHQ>J z8%;MTxf^wp6|d0GR(7CtB4l^dB!PRS{wRC+i_XYXI*C~>rsJ0R+#k9eNrA#$3&tRG z0?Rn9f1N4{8jqhBpkNe_5T>uyZ5ZzT-@@mnLTR^%fd~TAp@)t|ra@`1h%BY*6AZWb zm>R^3g;4iF#4M;?95o55W=8&^w9=B+`02w%1xR;I>uqX83wndG1K;X@3)Cx>uB0xY>zp|ExU~vNRj=7Druu)=(>2a(F9f3`9@V85C|MtWUN6 zHLq5RnXHc<^j}gf_DUr~ofO_yY4g0dP0yd!8KLhiiAHRy?Vr}E_F4y#Lv%S1g4s3H zS|bnj->I?laC8OBULTe7UyT&w(El~Ud;dkS{HP9BTB_&4y)V>zaV0e5W!{3?eUVoc zQ^iwg#^iF%T)kQpP1tzHKO2A7+xRkU=VjK=u4OEyBHa=GEUQ|7$SdM%aJ?3J6P$gF zv;S^95217oxb{Ti1hx{bOzB!^_#k53-}}gN3^=UQ*NVi(PCr7{sCg z1sG~z?kX=&o1nT|M3U0Et8hQ}qgwF`>1H55s-4d&H1p8RYjD7D6r)`KE49_?vjj~3 zWuMZY(9*5@$H-PxVUIsmSVt)>uQ{XNE)YAbIIDj{5FMBanhJII^r`UfY#-b$yUZH4 zy+J=t707de=2n$QK8&=e+Om1ex=u-eO`kHAN!-b@r}Mzs(Gl# zU3*!W-cTcq-Z76tCuh&XiG$Ti}w;-b2&P!2@LW-LnB9{#wa&xVr; zy6Z5?*RWkh-`q4q4uF$)^s<)jE!D^fgxlfcExGKjet?SZLbhIqCc*3vy>}a3hcgYQ z&|+`E6txt=j4p?!8qqlPJtoXF zJmz`&eiT?P8Zc8Z*MQzOJ%z|^yiVz9D41)w0Q2L}Yb=~+5Ml5qm&tCT~M$+17=4Z~{JFL9*ELZH7fC^f?SzcyA(TXs7oJqK5W*zaUt6pZ7}x;)%Rp zE+U}F`{h&2mU_P+qUV_R%V$ie^?tdGxfAb~&k^k7{c;5wj~J97gIDaUOsMx>{(>#q zY@I?`;bp^mwA3{wT*2!+y&ej#tA=(g`MTi~G$QG}1%pz>wUBgQ#R1>(H44B?-HawQZmIC)^A&#dGf7chFzS^;qzZl-5 zI{LMI#w>@6+|m*ytoL7(@X;b4#NrfEVce|ltW|}_>NjEl0<;S!t)h&)MrMI4?7?-FkZ8Bnl-irab+%&k{ z6A%G+Q;cC+`k;i!Q36+?`tycPksBdC^dE372BaN|-*j1jo=Nq>srmCyA|gg3p5G)Bni?+~}u5zM;HHy$C+82Kl5$43|$Gj;~| zAY=XiLAF2H7{r!NHBMEDh-9kE`mmFfJx;wq%3yE0&qcjk16zM@qLCrxU~>_0$^_C&l=+ zMB1X3KmV4azHdQdiw~RlqyR}A# zM0wlB2PT)oi-xkc7mc40+(qPdH_gC4`huwmt{yZ-LVb!U2#_fa?EKs~3V}x1({;Gk zUooQN=mQt78C$S7zcmh^FozlQ*oR*l(+TlaxciE-X;+N_%9a%K)mg`lOB9H_43ke7 z53uONMufSxx?$`|c7Jaaj0#kR=hkqv1zEtY!JWVosdAv4ByjI58CS0oL*eRU<1%${ zrB+T<+Kh{9J3aP=}BhN?HM0^D0?vORk%@LwOCly??7FSe=L%Sf z!?XrbF%ax$+N6ozn1UY=^(8fFcsAXqM>) zYYH-jA`%Lk;!Po78f1FF8k>z6d|MG_TBJm!lJRnG50d~{2aS2qHr$v4Z=NDLRv&JP zN1POH&9u14M44_&BnB#X8ZklcHjU;n0A+dEbRS~(8FegVk*Pa{uF0ljq%~O9nFfIQ zb7MEC4aW}EOKlp?G&v?5e*N2<_MvmmAtJJLmT3}UQ#+eBQXD5O7Mf~#IMvhC0Zea^ zR0w%!+`=yRGQ~*cGfjvwzsK>@$UP+=@)c39luPKF{urH=%#tu40~y;H0|Q zif*0KUZKSEx%Xl&?oo_*Mo)#xr=!zG}; z(6j>9>P#(amYFan*@3yXnGRzZwTtNvObjuCvgDmoD#~K6N>eg{CobavRKojSb$J7O6Xm-?WsJcMQ_?b) zu(zvB=r43+weOfZV3@4@m??`q$Fh%^-seGR^p({wj+Q$M+`4XxGP}T%i>4OJFzLBM zEqB7RHP2cIVLj_i?@%UOH$CPVZ8p6x2wf1ktKcRwAa^G(FzrvKpQzXipYFqiUrZAa zeRcQ+^LmVb)d*%m@k9=D{9ru%a2V&GJ;IAiB`k^FtLN%3^_;f5YS1Txr2(&!-o1poz>h2PHys_2v72;8Sa|Q zHg;2QZiy+PAoC+$SrG=cbB~A3W6WdNq;T^lOk@p)f(3S*vZBn7B`Wt~ZnrrY+Z(zq zNJa$K(R6q)!8`;)jysL)WSqGJrToB}1oK0!DEC%iao#v*$wYIkGBFxqVv-c0sBFZG zGvyrABoq`mFvc>`90ZTo+SN*T9EP8fP?xWr^(@lgpJ9sUZLE3}6uqpC2j?R!e=Y*^ zJX0rVqhOsWGZijO&;~>CWUYVzJ+nV^b~b;dqI>~pUNpO5OSpLsRJ~}Uk=6C+7g{*iI$(Xd9;imu(vU$tK6BAB;_j;!ju>1 z$$e4tv%M9l_(^!}1(uyKRH3p9sR#2$p7yN^8a6)AYXz*;s2cz6WmE~azobUKBOr}=bf*&+1=6$v6f zxEUnp?}f1* z2hx0x4QYQ5Zl8r79y8Kx4<5l6I(Tq46WV%kH4$2PaOn~(9-NE>qX!ixh%(xN|6N{$ z^p3m`U+lpDAkV`ew`7cE^IyqxklvJMA^luN94X%*V=$dRCl@09NS=oDv^*7Qos1gd zkIGY!9+D>_eM5d8>2`S>(rxk>q?_c?NXul*Wbmuye59-7kw|5E1k%N_vZ5_nBoD=Z z^W-5&XUl_;PL~IvIek4i1LJ?Q2P+q!cqSly;K3Y)c*ipq>31HiT>Qp^)rvPfqmVXu zh9mvhgEfiodax$(kOz}K;x5ksq}x6C`UP>dr!UfF9!yJ#i#!ObSRxjB5X~*-dvMPo z4)OFv+QZWWX;)8oq@6t7kY;+iB2DsiMjG$wgfz;7**!7LlZ7|Dmc;x&eSL>&$5QP0s%=XN(#=a1-E8!rHC3xUi;=GIpiNaC5870< z(6a#P9M62DGd*Zt)dUZkS2fl%8|feqnpc(KnSs=8^q@^u;Yxp3weU=?iL!j4QppH2 z-r!wX2SdqLs|{+e@WC)3*7CZFSGvYrh>f?n;6zDACNz%_FIJre^B)Rx7j4YoAlrpr?nEF=DQHy%|` z9@sb3(wC@srKc~2rk2)&5c0mIHM=?7g3%^^8MA!mk6D_jMV9sGLl^j?upGY}ERX$b zA!wFmHOn1mc?gZW*m1EbElzJ#48Q9<)of!-e?NI54%c zwu0D%)G@HFl{K6BePp?f?E;v848P7A&su(vM0qqOXT4H_nYU@6Q6H#gA)i?=LC0^v zCblRI_hJ36SPo+4uRK*BOewNNv5q$^wOHL&$otG6F{_Q1rD{D#EPJ zr`4eV2$$wJ*ECzkqkQAgOYg79HyG-}tU8pE~YS)9=OIBr$nJ#87wf-Tl>u-d(>R@E{KF}ZE563hJ< zyJYzf;XAPPSl}&ao^45j+KoN}%S^XohKGL}Di`2+ z$k^RljH!CGDwdpOy)CKaiI^0(K4Vm68J3hV!j0S<-aS;FAaLhW{&NpjOhn{bMXlvG zCE&?d;im@H-PJqUxgRXC|LMtQme$Amp+NZ~tZ1-hKC13ovOi4QW)a!e0oL)T(|4e# z$b!(o!PYM&4W^{{rM#W)`gcUx*)3hYGDk+tXKAp~_!z}MIJPoPo{SC$zI%e&3XEE1 z@p^aDM0t|HRS)#CSk~CVSLMCVc2BVirMZaabD>MCo@xCJ@q_gy`MWkuEanUAbx{?92dB7@ z(rlZws+Ei&(G3ddWysSdsw&+Qte_`$YWR!Wd}IXX9eo;}!%p52iVyOtzi2D}E97BM&4_wf%2I? z1$fBbHj@qNXxoEk`V?Efitsw0NQRf&6E-f#whcSP7tkWchRVvd?ePXTuj89YY?xW& zZ{SM^*aE ziNxvgye*$qthV*Wh4bqToID$2CW7}W;MK1oZJq6X72k+Ye*+~RTO4#atc_$?&mVeG zMg-=Trwcc>7s_h4*)p-IZ?W+AQ2UuF3ECXCMM6}f&5zC7YrBd4WFE%YaR{IPcRyix z)V(bM?H4?rWiin6T72|7{QTIa4ZCp3gs^<9;d2$ndA4!%Iilybp|`(IST|K(2qVwi zCTjS5XnTa~{oD4%=WKWi>Z(bE+zYk?TK;ZX8+ks;IZ|nI7Q?h_CK~$o?lU&8q3=VU z&JzCg#2i8bci_bJ08bxD$jl)7Ubfy~_owXkmo`6q=pXEa+1`A~F(p{?fw$zp*oknZ`r3=pN0l^9N*K z=W#6R?aEku3~@JY-C*ra+jQJc@FsT7Y){1NPoUsqDGN+*`Xnjhc48;&c7$vT9CZ24 zc9glrn6nh>Z`-iLZ~|f3K`|$JC~S`K1?PQRgmRIxY-da;#&?TjK8EHnyHz9bsd0(P zajBTOdKB%$T;X=i2?_#KZ;g4b{ACgIyDIBs$|3` zHsWM=-ZsX7b*nxZD=$W&0T;Eu3OyJH(j&YtJ+*A@`OF!tVs0j`2DLqcSHr+$`#y@V z7_(f*rE-6YeE>GE0oQZ(n^60PN`#PRdk@I`#YciSbA$0MK8<_=+m>M;NAV?3P&C^( z0h)6WsW>~!z8as@!Rp-LKHgG*rL?THlf4KJQ5vA_bi5q({YjpUgh!C3C+EN zA3|G|eRX-0Edugy@;JZGmXw3vJOUtROM1w>#P^0JX?B0ex@Vuw+dML~wX4x}bZo=g(JtTzYo)5{hqN>auE_y}t^BWrH}{iX6k}(+Auj zoSZrfB`S{y?zQ%bD#3r{0*?UO*V++{=2z0$gF!qzyy%C?6Yp-|e#dCFF2Ripu;5dX z+B&ccB=gXGGfA;HYOC>aZW-wk}%Wz#R232b+MLn?bno9OYrp?gheg0 zUy*Pv#Mgj$iv>V-ZG;2fTx~bQ)|ozbma)lx8dVsyN2tO;C*=-dq>npfrE3xg2CyR?hF1G)jGKDAF5$Tuh_dGGcDo4JmVm!zeiP&J1IDX6<@b| z@OlJnDe=Q|%#HTF_$v}hhll=LzBwxo+{GPQvchle>#=i2LFxS9I0*iQ&u1IIx4(cT zqoH`HgojM;*m1WmV2*U86ay8x!D;{Q)y?*Cc)b-gjPb#D4z=`&U_&0-y(^5+1~Zfn z1YX)5??`%qyNj=8q^u_Sy zWsqYSTaxEks-i+XV#o0cJP}Ghbs(~7p`#3CPGUvT4mE|llS2@G{C13i zIX#ZA2o;jSvWezH;XQf~JtCS1L*pF&Fludx77n~YdxL44<9AlS!qE;7Jf%X>_o^1K zWUy-Tf3bxd%;GXvw!%~>723lWE=T6yS?Y-|+xXOh?iVMd!51wZZNYiTF@iNObWEg> zIWfcyzePEGp<;;xUs)iA_(4#yqq`E#Nso7@;OmeOS*k57eJ=<)^b2Xr-YItYqUZXz zS30o^TO9GoWhOKg2zD^%I9_DiD;)pS2x!k;PraYqYDc@LKqRPk+)|jysM+TbgkY** z&NTmpIODj=fgS`RdXu}j$^%mVkDf(FLp7}FhL?9E>t{is&xSvkOiILG(ff~DFq17UM1 z>N4j+HMDrgk*^ZEpe((fN|AHjaTwdn0c0Ja7>$m2UM2Ka-v0va$2rQ_E8jSt$NPOC zXQHE)o%-I9iNA93__AX(T%Qw)uYkN9fNxdGNb!L}ldm3XO};K>{>ibHLdAW@Xe#8f z`kx%%;_0}s)E01|EK>)Qmpe2p;2(}#O6F7O2jmMppnYMcUm2@;h0EiHXb!8R=yFk39s?|;Xl zdcU`RbBkx5x%b@d+;h+JKR?(sT09?31^qj{#&5VZBe5FB(CG^b4b|PzjHHf9nsCl- zuBlW?VmPgh<^aYX3PjmhY9!SHbf|+SfZspK$t^TTq|{}EHkxk!s+q%sx@ZdI@}@#s z5EtFYDScLGjJPpHH}rp<(2tH^^OPUJjfj-RLdJ&DHWx1~7GFVUDVva(fUx7?%4UC^ zw++x*5rC-dr7@$66KUyY4T5a?YQ6y}leloJAf+HyW8+47p*kV`$tH8*cFkLeh8?0A zjGs)QY^Me>!oxL)5tcngr}t?rblt4!%wtArUc;lQRQisl1b}a$M`=#KPSE!(3Izjp zYBq_Hx=+b(!~Y@qNzE~-Y#NvD*EE(CTQyDSzFX6UjxS5`Q|0+E8Gk=sqXSEwL8fgf za9^3F`4ClSQ*A-Yi@fA{&2#t*m^*`F5J@;!^ELjO$-AD{w18E5MM@*?lBYSQP=+iD zks3l4;S`u7J2TF&OG=@?VH# zk8@(F<}`Z#Yy$vUrGx-KRz=O<(43RXo-6JT!}W-0jqz{%F#%FAfEU!pbcU~HtR{+v zY}4$J$`JbhbxcPociyQv3hpwSa)xP^i}ltT-jky=a!MMZSwQOzniv|jN3%sLn?pGx zHOuLwSp#pcQJO0dVHq#WUf@)_rZdFYeVQteHkVT4#60c~&f`2v^=SY&QK?A^$}$Tm ztGV_KTGmeYBo$O?yYavinlPo*uj3~^)0ALULIW%FbK-~E_oe*71x=DfCJS_E!+969 z6C^zKOHDDF$fumw)O|U%eLKwMLxQi>NkUf`Iv}n&-o$em!rvt;xjD z=Aqh`D6w2Sld5+qn^Qq2&70ikCk>;zm)UO7j+M~T1WgKM2OLVO9^Y0W?m|j_=dPwi zq42{=_e^Ulouqy4A=^hVkwcu*udIL4Z{SFYwjPXoF&-=}u276eoP*XBN(+_Rc~aRj zsyeSd#P6%Lu;$8^Qw{lkFqm;Iqu9T3f+fM0nZDUSqr>T-q!Y7)i=?SBD zvs6|@CzCX->C3g+#?+^UfTZgHPoDylb|#u$NvB!Y3P^_9<+QY!ZYa%2)M%)tzt%{* z+BLz^J=vw<=k3}OwcKw4e+QroA|IP+cPeCs=qFL$gRpqo^-_Ekr!?2jRVn;%8s0uj zjL{9=(N>#*0V&3dYdL42b_ve)HQF`o?WDbcwe~9I6lz|>4PIyC50bTw)Cxb`kl$&? z_;DkN0LY0Eej~Cq_-vr6JDOH}`3r4(2~X*zt-)--BY5ZFzh5pvQ|s|+Db>paFy3IT zxEgJu@?lyxNA=degt5)hBCM?>T$@SD6SaAC>Yz56RAaO_l|y6*JMACupJmDF$N^^>%RBx>1~Z3@2)kjYef zTQe(o{!#T8z!XgXT)UmJKT!nbxe_$<24y_mG?o(1X?k(iRBb6}+{z{0bUUTId!`nM zxU#qC^e$yHu3W4I!XS1>6)Waw?_t2UQDG}hBDbBZ9fN^($U+Wu5Z&JgeH<&&5iW|E z3_qvXN(qCselc=y;sfFjT>>WyzYx8(vnRXr;+(%i38f$Str-T!?PABEA z71~O?^)A_;(5BM-<#2CYdPr*$x>mSroWVg*eFRu-xv^RUm%XDMh&tY*)4jDH{`YcX zET>0M(|)*!N4MmHX<7wBF3xMCrL=sBwsrC2+JzNIf;;yhN9lC;rEf@&Yw`Xe%Gsk` z&I!Y{Z%R4&l=ek*9oPT2AD*J<`W?|b=pbc>x%68t0+bStYpW6Qr}J@HFD+CDng7vx zFhB6jy&Ny37NKC39a%Nk?~#RUoBLm5^^2&j3ZJ8Z|J3#j;!JV+LaiUZRhxAaxTZ{7 z6+C8-0Zv$_a@^O%RXY+9W6Y1WXCCEkZv32p4H670n}A@R;-Fos9dQXK&flBVHC9_e#PEf zx`Q|dn(;1`u7xB_cKX#QKOA){T%r%Fp}h)`om@S|58veS;i986MPH!wl%1jlsk+es zl-9PDQtBSvGgPxxDWhDYE|;PXYoWaikyUfvVeJAKDVE0f#65hOq-{m4PB79lT(wXe z{df1}Ake7uOJ$!@PJ7^=my{_L4><^dgFvjX=w8DpY(TO$>c)}Lqx()O zJ0}na5kQ))UCLPRQBtmm*DaIF{cysq5Rr|kj7fbi*qbk2o#byW3*jIid$^I?i#~mZ z&v}lPYiIpecY=*<6WzH$mrdHvRBh626wNp=#uo|0`vqT2)d2%T2JFgSTCeo)WokrJ zqNXzy%+~3sGDVw2HOCqSkK;>JyHGcb(l08M^l}qi7}s@8iQ`v$>Xu_Ewi24(hXVg3 z?OUwR(Cr7mtJxIohwJc>hZZE;EO(xV9~Q@_S?>YH8MH@Gs+ZdYn0l0+|wU0Jv>T z(smGnpl!NsU0<0 zylAs-zDn+ICxqpos}n9$P;2kdty9Q;E}G?UTd`Aj9|FsdXyq5qIIGzvWo#NmS?=AV z>nD}{$~o_9mm};{t(`*E?`spps{T|`@lJ360Nb!x*H-N0VhNu*pnC*U@H=I`uSNK0 zr7i;pEqrtLL6Vu-G=pz`ge5AK-L3dU_bHeN{BRNcgm3z=>x}LKX!rwW_z)`iz3EiO zMRY|dprNVsa#Kv_7di;(*ex@THihBwq7H^fc_^*>uIV^&eLGK?oqS5}S*y#$G{EEc ze8|Ik?_Sf%F@g_JuY&ALeJ@e<2vdLoQFR;6nM$7C)CiS!cBjZNPOjG-!!s36*BfD7 zzP!QY<%2)#x=EyRHBUDiCrbFKyE?)vVKm)tgv57GcL`L4({ioKN7>U&y{IP6xS4FJ z=4RYoqVKCz_&do${;&vAQu%Y0-V>^Tjxa>th)TcGL#B<;Z%|A9N&KLx{((dxKoI27 zR60wJ7!#fTFvbo}yIb2(POZKxP0dJBalAwS8D5OSi?N(|*Z7-+@3+v)KvPrNc+&u< z;{-hddgL0~B{8M*;5Pc5_)E*Xlwma(Y;#D1$?K5QZxMckXe5t3}1BNv2o~plz z9SFbL`wtHTpwl*?i_Ru0dhkq?f~RgUFP5;axBe2^k3st;%G-iSyOb;XT}5U6{t>4} zh*SHnDEG3Q%67+Ex#mNCSHZ-A4Vm~=#0IMTSpO<_&d?tPB^HWS8g4Zh$HN;QYs6#U z;$i+Y@TJxdAH$(TM8U{C_{7tp@is7ot@^7t&EixDjURsXA$e?OyFVr3_C?X?K2lrbJ(O5md+-&<~&DdB55(Lzm? zrJq%CQU4UqlWqW72{K2G25)Wsc zr~axh#-k)YJwy+40hWW6u~v2lXFQq9{YeABumbyV4VPt7|6^>6F#G`ey1X~Q--DwY8E)W@_8=B6-Tm~i8KrLzkLG2K^vHOEr)gx* z&>N}JYH)M+Si=Df_@hOoQ`-OkzRZy_-a^!qAAZbR2RDpOBXOrwaX+`A94&Q4ojs_r zPJgMWvVR2M^cr?T-0#I35)GLcY2moki%N@9$58d7Dg&kOv6dC3_le@poedMf*n1=1 zFBmE&0HwVg)x&TTmG;E1`ci$arT3o9izT5&xtp5^+WS#`zU7NO)$+A(lGg;C)>11XgfXuiCsEaV%R#)0Ph9)VEAGP^YbJFjym}eihch7j{LU;xZ@de!?Px5#79Q=?f;xYq zi{%N=8xE`JC6Fc z)>~*}Q)Sb=ElqV&vUE0{7vnpg`nH3U%||K5_LAZ~Q7aM3rEkJ}DhvZKaTD0CG9m7| z|D(p;RPvS4!k%{x|G^g%>4{g&-MQC(!#-3#iJmAh0}8Oxuo-_%rYBaKSJLwSMn4;l z8m@vzPeI(Yk%f~|ri4bFGW6%k9~;)7o~LNcIRhlXPYjR%{h+yr~p%ddFU{pcyG%Bps*l9t1(?L`jjjLkr*T}Gteaeu+S3Wb?Pz@rhUvPAx z%-7VBqDg35pDQ}Z~bmWY?R%EY#Hs0 zFJV|_ZP^wDiDH2ns2LrNFJc6r;cUOz09>xlM)(BDpZ#m9G`us_2)IA_a}D!z9$VuT zGp zNXF#l2=%l41(JNFT#CBmZG1e<1P7_Q@x~^gU@mo0gd-+*lJPC@j{)!o`BTDOYV1ZE z1Q6)l9HSpQ0#1nX1(deS^d7yawNXPYk}GR<%N9Pb7gA-(cy`b69hZobypLCIYd#y1F?Y>0zQIX%$sb6%70{>pX$$m>CyNE`mhjvSVoqO=5t~tIT0l#g{zQyw)rwYmnm{rT#T~(AGGIX5TXo;vVOWi0Y9SP*kk4DY6>qp?=$CXhYSB z5FpZL>rVqUz*vJf3TgRn!)mgGnyzqmz8SYlRJ?+gR~TIMpo2b)-&UCVB0#By=^9VG zVtf@pDWU_(`WK25O|5w6*TKcO60Tu}1U}fv)Kya4Ro{ybT{Aw4p;<+#p~gz;-(7z& zxY!tr4H$3zZW)n(pqOQKMqIt+tEu#TTMXr%Y81&VfIw4WOD{zu<}D|8KG=U@UC ze2#bilc0xh=07`zOKgaIEobMN3qooA9z##6zGHw+CPe!#(X0OG$~sQV z6SCoN$@&y@=z(}|eQ}B&yQDRC2`4WzTj6Zi(`cu;%gy<#78r3e36XF3tCMNC{-TH6 zKgLu6llNdrYl-Tp!#@ZU{PIK9Q08`1Pcg~6gA*#)#ZIvm2_M+uA2&fApK5|SUcQM2 zyd~*F+4As%bZa6QgxGI{{q}$?ci_^LIoUXoYOX3f(em%1aKHH$uvFJfHJWJGHDx1u zpb8I;&TCZolre^GO~J10`@PacsZ))OC^IbF%R73S;0+<)OwpekR`Hs?rWV-9Lj;_B zFlrNo&RSKe8cJUjne@2H%C~Hr=|@P-=MST`YIYb?;9D9RDZry_TsqV=8ix`zhVnP4 z#BJWjyM~(vq57>{^{f&0lBm%paa($ms^%Dfp}M)I2v%m9fKwoUi>u}vTY+<~Ht2!G zk3(imo~byv?zVB>xFjRclO~%43)oIoi;&mFyv39!;Z&P>Gs+pJ!!oISCuK*5mxwkq z0CRB06fS0E7u%YIV|$?Km#FNfGL28oHsNk5FQe$Srt2`jnwo&Za&A1%RF9tlze@aU zH?o|Wpr_9_iIZawM{hAL{j)hjbCuL8+Z0~$is>VWWJ57r!Hv7;uc2})3$&ax!L^38j~F4yhG6uDrnc&nV?;l@8jqwl^5K5 zPI7w!sIi{bTZ}DfW7o(%JbIJqbyV~&9m+R;N~!OIm@yj+LnzW|OccZNhg|9(PNwaq zch&O!6)OKQflVc%v^i>r34R9hy&&xXC%Vl7-lE(DcnkSKpg5Qw(7fe_0DKX2EvWKS zQz|X!5P5{T!qf-N9iqZ9CImjcW4Z$RDk=Jc>2+?ED;R0YI%5=HJ7ChIqADt!Xo}(E z7Gqloa+^#yxZ`2dNBI3=ioRmHK}q*@9bwcmE#qvLF&5^UR5NrJA@cVru@&x<)xX)` z_j=5QWM^@n##GQ%+l&V2q8m}o2C0QkbTtE%T>`Wc?MJ2`6jJ$7ivB(ZA@ns$Jw4=87)VIC4pnF=7r{G@xGD=vV4N#)2#Gd2l^%ZnyI ze)ER|+sOYoz>tv$J$x>l6D`d(9 z0BB2;Aw?NK=nK=sWH0sckD|+y&8NfU7Y>c`!-xI>;vRy_sTQaqW^p`yMRg}^YYCt!lc}W2W}$>C+gP4F zC6Xk?|LHuKhEI*`K$&$m8Si}0dz7)I&QM%x*3=B zXTLGOg56X{^~;p-`@L@73fj$%b1AmK zPgK4l32;iK@OC9G&n z<{>goYNF;x;w)zr2+T+RTS+`P1yU~*dHrx#-!d2@lSk=ACN*E^YH>-_ijaMC{1as% zKL$Z=is~kGXc*O13i+>yyMYkAz*5MoI#`|(>TLxaC_7kU2{+j_G_Rwj6aUc70^5^9 zR+N~QNeLZgA5(Q$Y#uV9#KQEqC{E3pi&9D;0541tu`KXv%o`fpCHM`MT?`Eqevg2} zt{iKyQ{#9zuiY46X)0AHcw}-cG#Ga>ERCd61wx`Frd-}J-f|HZnxU3W5~%_ieD=2o zI{R?T7PPOXw1SS`(17SpVf^DLOJh6=qry(nfUU{20JcUE4*Wo5Vp9&X+-<0A2&#^t zgbVRdEKRaN|Eh?j%Be~%of@Hhk`wyH_QKRpvn1iQMqK)Hdn{ySjs4al( zmgO2Q+(?y#3wOid5(d{ycSa#DDf>+8W|TWMN=5xzSW87pK`L~p&q`I#SIx^Fw&HQg$Dy*KvhAfsnZJF5REm}gh1@?4s_`H>l>4mJ#$SA5abPvYm5=jJEk0$r zuTWS(mYeE_TY<)1ZJCMMJXAl*3NMt;EFXp{72a{}5)+=}$llfwSo=ppqxk9bmgUk= zh3}D~^5zbzI~KZ;KJIVb!B2l_S&kgK&8U2YEv_iNv=JxGv0@Gr_J?BN3pDED-3S>Zh%UTVcS;`>xL|gxZCEA8cy`ddxK~w8lZqn3RhevHGuaOl-0G$<% zLyC4(KQwfx$n7=)XiC<;L2iN+MRR!MX>HCUM4n%PvXLAN!lo!G6{WtL9o=MX7H^ zaQa0H?y56B0*J=t3D(C%LqJ9NC=|y+2P+UpKC#H)fnW~_Z6+NSQh@*Y?|;gSeme1t zb&6ck=RK#Nlb^L720QIm-o_96=#_^dOjv>7r%OY_8qxti8m34oZ|l#-E7KdM>+i4A z)a^hB6tjvhRC_%Jc_aTE7@RbURL=U@vJq>tr^?Kg-K|}*uG13QBjC;cz}!@H`!lZZ zX^qCKT{$P;x)>U&2Ns*?JEQO2FhY-V&SGmXDfb&_-3zNxcS^i%nZf&KTLEM@?{Vuw zyaJqAWkkYXD|Z-g9%X$3H9kgQNLF56g~S)!D$DvQUhPSzE?D;RzH!!Gc%xTQVx^03 zeHTal^+$Q}MKvI2dW3|Im-N7q+DW21{RZ?on~6(cBTi?ubS?(^v>JIXrlukVxi=k3-A&^D6tTFKy{S7wEWo?;Z)8#~*I zM4x3;awP%~y(xx;#n&uQI_|dapk1p{TT((^oQ!syZwg~zg>@x{bTpM3t#MRol%;b2 z2pR4Ob*)2zuo5PNoEM@%c*;>L&dvQ+ac+*`#C_INu<^GI9Zx%v)r)M0ti9;gek(Fs z)`TJab+X8AK}z#q;Ot!TqXk}}zghj7Qk87h8qeK9wqF zu>GzT2AAr%&|{)faUWA;v;7b8Fw$-9%(igb6)fSIWN&U8$m{JkC==T>v7N*qJxw#> zY)|uZvAD=7{Ih}Vt)YzZwm*a1TE*L5=cQ&_j#R10L9NdsIIM-G8E!DP=VASaRsYS0 z%9Dcewl?AnXvFvlVkQ&_n^pjlcB;?Jox9rJ$GU$l$yCwOCOSHYs;1dol-$EsP8+|7 zS8`2)O>7wGUXO;4ENv|^boA8MO{PRv6S`P@6r)?Fe-4g0<>t4TOT&9Rz`TSBDxV}wnPW*3ru zs!e3$vpLoHao_Pa1O+M(v94H5bDP<6Y3oGWV9IS_i{xQxwhj_DPqBT2-yt7UOPia@ zoVG6XUQ633@qZC~da5lE1inmJEgFAMwtZ2p*_LCwf^l0y8E3)~-8ReiCNAw#BeDeu zXrpLp=(b*dw#>J|J5})_nnh^!jZPzAGKh=f>FaDOQ5`%&>pSh^%0)KZTU!1Wr=Sx* zc8ccMO}3k$q<|9d#p&thPo3scQW>szi+}F)J*{72%fe+Z&P(%*?l7`ylWM8aQriz= zeHHTQyPaUR+P>U2n8V+){eoIn@ZCb23F6w0Ah|_UZA;q9+skZ2LBL8XeKBSXJvi88 zFWJ7+HKhUP02P$9*ES9gMGN58Ya1P^P%K5&ME3S}FP~^AebsY^FaqYY% znqG~_ofO#Vt8Mqy7-cx^BvanCKbI^6zS9#FHQ?5$c$I4pn~nJW3EOX6cEk2vP}Eq5 zxb_ral)Z0@rc5{-P}I%NV>qGCwn)m>uWX|f(g4n`YqmVh;#$g4i{ugxw>bmiMg}n#Hc?;Ft z>WpO5e{>G#Rx10aN-2B_B|q4Z8x;Y#Am>e9&=N@URFZAh&?k%IB4~-i-hz(4Z)-t; zv*uWC>bCzN4--jBKD6W+b0lvv+heh?-T7R}jh?aMHX+wkLEROJY@aMTL> zPGn^5Xm5>SEaS8^2h36Fw;VWZ+BiG1CdGaoeJrP}ay#sqsdm^i6}u^|m&3;`y4i)i zzlUlp&b$0aPy1s^c>of??VxF@IMiXH*VFCY6^gxkX9W~U;Ad}yHMJ9M!>=pCiQL44 z?b}fSQ&yC-r^xd(g$lAgHY)4lP*BNrhlPI|Zdc(AWP*xt{7C7cKp)sKTg8<9k?Ix| zoOPY1^ofpVq7pkI89sXrDoW ziS`9j#d}=a-vJQ-;ZaSw!({u%kn|65#wV_xq5meMC6zpFA1hTH4=|Z*PqetfH)&&Zk5U$PBK` zwRZ(!hbil}^G%MLVy}?U)+?^QylavDhFls@^M+UKEhS-!Bl{Z#RLBErqbjK*lZG#g zi2#ajTq90jhFMc6_M^3{vY=0EA!w*q$hHKxnL)X~2B~-4Jo1Gi!T)Zk;40k4_W;q_`8^`QT zLHMVXx7?G!CC}K8;quki34bT+Kxa19HF7}d5u*5v@>Z)oRMx^VjZ!B%D!DAm@d#!% z+u57Xp0u|>6=%7;mt#2azOf7>)a_IIL8;;#ZeWgXlEIoTzc?1_#T;HzBg|-&=mpI372b()(cP2_H`+ibO$}~9|iOMfi)fEisfd`IAZvU%&f=U`_w0EOJD-Baz ztMmpUA-U#0M1W2Yb#w)DQPfr?27nBf=N~6JI7j&EtLgiAXDb>ka~MRYzQE^-@W+II z)n8vSzJv#s;w#EIsy+-1P3JsXAFt}j)tQcbFwqdj*PIyd$o{Wx`q80pD6yrZlq=Kh zDNsH?qI!%gZ`jWTnf|x%#8J(nl9|pD-dShA8m#C$ie8T_Jqhg`kfy5dDIhB4I3Vt$ zjjJR%uNJX94g^grzUT55&H)H?5$W*C-dAb7ciKC8LwKv>`m2t{nCb*)B-y$+u1FR2R6EgWW$c(JP@kn5Lb+odS9nTS z$7(cnn@=Be!a9KD88S{i>#~E(PIRo`?|V3){Zsr%r$2Vy7n`XSC-!n6Tt#t*qu&7+ zpt2#VIlR4}VW?>!!9EDQOiotW1*`Rx&o-q>JJ zgerdJ=u7G!uxCDsjSi0EU5Y;CT1-e%7~U|8tued5QCWoJh~Tg>{GV|S4<7vv%be?0 z18Zv@oI#{@v;RR|=GhIrYKmhTD8ENh?Nm=vWnyv(+1{~F;W2j|%Y$wG!Ks%VOR(h( zjxyX-A6FqA%0YW8Dt_8ARjRm8^>ds#{LC!JQZ)8JM5-Y{)ANpRL#4_PI-~@P>OR+T z0Dnm+jiX0W>MQM6Q1$Vo1gfc)#L=mnlC6M)ZVbhhq&fTij`MP02QSK7w0`6Mw0DO(I5f|3mXFzD_ zWe?~16^?wo)|d-_63F?eNmp-K$pIDCmD0Tuy3IQje z6GN0bE}!L$ll=GS^YP1$aWZKDZlzy4!X;r!!(K-q2IjM#e-j#`RSFo!dwnsT&8^G58S-kG0?4Of%G#pkgahF#58aSmEXQ>V>X@VGfr0~O+9rra3 zXlZ)9bB99N`knNEhV0Fp)78pm@AL>Xm4(zlbZDwx=X8m;o%l2tkh1#!qTU&#z6Icc z?3)?!^TUHJx1_Yr_Oq1zjT6SNW{wi78xL7AwVPvILqGZl`_T#_uKmD&P1)~)czEzt z+iMB)G>~m2*j!Y7%L!-AJo{VX>qI*JnZ2vnNq%tg=5Yy>u6MNMOoIbM`xkQyujmH) zq{>IQywuT0%GKHS@?f)VsQ!ebnrv#9vq8KK%9TMu=N93>x=E7;IM+&*ZMpum13U+l zFqfAPbUuxZ+m7qc2A{ocFQ$^4xc*=sIt6QP&-LfUSJizT&~;sOLL$4*7HleKKS=IA8lArqWCP;ZhD@Q|l`k`sr364P(~xi)_7}B1N@=t0ooPpZRR%>h zN(R8wIhS3Wl6|?=c9H$I$r5LsRN0M7qmxUq6@{Esz0`?|i?TbV<=F?&ja$yf)Wzyl z7I#!dkaoH%fnP6hjt2QXIBlUFD;okt8)|$7*PBu2oUJ*z*jWRC+d!6e&dCa8&x3;k zdS1HDnG>o|rh=qiRDZ+Tlt+H)ES0Vp0POF}_K^gpEqoW1y~Pefo!K78bgEf~#&Y`! z;WG6DRao&+XK(Qi5^zO;L;th8>EAiasP3#QM|90DUg}F3$5lwLy50FHNQI^H=cM)Y z-3jLkI(ox-k6Z0_0;NmYUu-Td@2GRanl|0(Tq#u!psYrr8qPfHERpiN?>c*8mNU41 zvm;%~CAG+;D{3D|X-}&3oLlYa5*+p|sA~{#=N(R(@tw1|vEf(v8zzlZOwAju=h{+<1DFm*DQ9Cc>GKMrCO zbfC`NQsqcGeOqB+$5m$y+8RY^?zqmB+9dgQaL5=#)>*1jj!sNy4denoSncc@D%R!O zVl1AZw3cxJvYl6VZdeLYtoq4mhmHVtv-?Olwa%FhGpXwTPx@nmFAc{hi_<1W07%X1 zQ;(#m50dMUOT@D=NcmV!%a73E{7P|sPYG?^AMgoj;H!*Df21e)7Zn@E!u^wfdTO78|}n3a5y#2ap^1c zuKO^WX5!^ZoTiEpH=+h=N(M&2r=!^FnkrRJ=DbReaBz3Heg+v+DDQ};CAW6DszJe1 zRDLoR;8$K3z^{~3x%^D*07x8Vp6B@E7OwLOc>p1%H-a7jxX$ZE%SNvfozo51Bm7h`!+BY<(0rwC@%%3pu7+O3TN2qz$BE%0~1hw7=U6oEHN++ zWy=5*$m)xMOq4GN#-MB;cp@Z3wcv7KH2!lgFcRhI0Dw7FM+3u9?hgz>xjQfj<<0;! z_o}x7SeB~wfdMF22l}F15$J<*S)ez{mjl4!ROJVHqRb5doiLTfo>>s0*|7c z9DonJDl3qNazr2%WuFCsF8I%*fzBwC0?8V8 zzXQtTd|16ATIaV#>CH!vBP{voafB`(J&p*^M~}lF&xd<2Yy*XGBeypWH6hkuce9*2LPj~<7g%}0;JPv@h<;V1La)$n8aMu6Lfzn`zi zAMfR(ui<6+T9mKlYf!Gqhxiv>nBN%X%lYVQ_zU^CB8N}OM~}nD<)g3RnfWS|!}8JN z@FDr=arj`dIl~9#hobDC4>>-(XTAhwmjJpN-YGB-Wny41%D4dhj-|?MDjk+GhHNTd zGoZ+*r*dj;1oWR`r*d{D7s73oIh6QR#3{}#^R|~#SQpo?QsqoaoFN{)?ZwT1Y*$wa zUV9oTG+jrwY;ZwlEO2)S@)~3fZsvMGb<GKqw9|3QW;VyvsC}&fBtY;>byy3~D+%fSo zzCXe%g;epoyNse@d<*FEDs@w`jd5L(D(7(GF}EBFp+B?mJ>dBzuAnQk@&%X}Qg#X= z6M+WjQXo2KybDfy%DHrUjYr5Iu3GRo*kkVjV;($Ckc*UQN-zuM0xHdoc=sRd4Yd9e zq%~KhqONS2p8_&1yR1mm?&i)Avbl(LzY`T$^~gYJ0=3@DHgc8N|k;t2N)Bm z-R?oUqY{@Uc<==%agkae*11lC9=j~$=Ccj-+`wHbRjnlCzstkQP|n9^5ua}BY6d6X zKhy8+4bt@@M-TSiLb|&i>PQ9NTHHE;%P)E`HXv{z7p!s3MBiTm`v;?aVV&zmjP%QN zdR@4IQg?VV1v&Y6gajWaJ%f1IW>-#_Jm3(nu_EKgU!3@yAYvgt%S67qk020z;knAy zrJg=PB9?RcIQJ5WSi`)TK}z`EUY7#x7La|0=dw72HcCY6!Hj^)LayrO%9Dz=UkLtw z1y!w8-xfm=7Fuz{bp&cSg;jajl&Xd)O)0 zr&47x*Isud2URkM(K=CWouf9Wieac?U~7(@V}E#duBO^Mj&4HWv`KikLQ1*5;=U{OZ?w{{XkKEIpwVR6`M z5#L6oQSJ>_@E*U3cHWM9y`lB%68=FNhv0VQYh=IMVHQol9JS&f4NQ^8twzg zUFg)njs_mBihCWjyiNlixXS-s8{X;2Z46S?v{Z#u8fWADN!ol$SaBqxVW(< z?v_NMT#1R>3Um_35{lYwO&98kcX^FIZZcYWlS*ry5V1^gLd1HD633}vlefjeCa>H^ z^_mE1YFu$bQv(x34|@W)@y4B0N|if<*G(#W#nqiBCd9pp7j^+Z#Pxtf+r~XW#buPZ z*9G674snm+yK+k0=Yr}wIS#68RjxTazgt{BD&9*4U&q-w zH#2UFgpWTS*H-b+Nk;ks5~WB#_|srg^K7PS`P_gPu9tJEv^=yYy{Q8x$j1D*NU(J! z7j_DG;DQa$uxerzBPXr{{ot7r@or}xmO)KLv(VYWulS+b3 ze|6kKD8=IuPyFz@3o2lhPPWN8d{f+yn3nfYLnYTPiW7e4ug85O!Zr4WNFHV!c~e!E z5I_KY4bZXGp1#!fMw~uOd3aUd0POSIOoBXM4=3BS4?$7UaWA#r>2AVb?T9miogPBv z@OQrBG5npNw}zLrbU%l+cZBTq;cfq&8h0SscDp|0Q;BXkt}BmH)wA9@>RjoDGHic` zFpg^LzK!R{=vEr67wH|`yV2DTs5~|T>e6KQVa(h|B$@99zNOOV7vmE04cNPnkeeM|`*!oGz z>Ejv86NbCj{^crFfNU=z1qhkSR~^s>S^{&cwHTn${MJ18VD!6sUv!`aC0vi&rBHr| zDo#_OGc<|o>fG<)5>n!s){qN0*x~P}WTCq+&VA*X)&R=Q+2mOciGDz%Eu1{TeGEhO zDKwH10M3o|JT1<~WP0R5CnIIgb+zDkC%b!MNIs*CK5BrdPIbSDJI~p~Hm%!H+GKSy zU;fG48b_24(VnYv+|%&tIm%k&LH@Z}Xj1g=3$j@49SbTYbPXFw73EB|eT=tTCAf6%r-z#oJ`zbF{?ZA}toSy3)0xa={#tDr> zulKwM?&TrMFDcdF>rUC9CnxfpmF`7K6NDio+lmbdZ7r$6e}ez)X&y*~HD{C<+NnJB ztb3?Lt^D@gmH_|^;AjF;10{rcT9D&&_r);f<$du1==^RE56%Q#oa>&bP=1caYPhPo z8z^0w=iNFfefyrGp8oJMVYHOF(&r3gs?jOsjMs#a~~MU9YY}noxI7WIR6R z{s|NCHKl!;yo%yG1MGD7hwgN|_6?T%U8h((3Cq3JRJkR2 zhUo28&YJA8NO{>oH(Yp2{!k^+smoa4%4?i84L=b_RoF8pD1mU=XtvPsM^G?*i-|h(qoxpwg?N2n%?w)qVdZ@K$Yz{{UyF+3V zEcQoy?s0Uzdu?#v_ON?5P%QIYC74t;PNKGDsS zh;8^gE?l9os<_XBrM&3DSrDQE_VDD0G)}$ify7eqR7AEA#Scpww+HHBYy68Ot9VOJ zwLR1^l)2lxi;tyylF_h&au%p@Mh^DijD(0#KQINzX2U(NLU3%4`2tXEH_x2nfv(7g zG3Y13wo*k`b`7+H{ya}1cE(toOe*#6-T|17zj^4r6V=_5)bKmP*iE3FPkPejI0-^j zRP}ZfEhP*HRddZdo~XF;dmMN#iZUW? zX6miLMc~M95oX>n*8^7(RTHXxQ$3Ih+-euw_PLK?s$TRQma1Yn>vl5a%j^WtXdzjv zVkyJnN*B}mIaf~iz^MjhdSh=8~qqB3#Lir~{+??MC>{p7tWo|-wQSkzT^H?~`Fv2pHh&v{(8 z;39Ut<^P}dxzg~~!Bu31*1LnydOs={r4JiIg)1o#C?sF00IlkGL)%vUih2Osc6vi4 zG|IgH=Q6r#-_e zu6r2DrzBYjIHU-3#&EeJ(Bl^SIs|}6avQ+R|J+<+lZecYDyRokEg#xE{=GJWT5(k? z%6LV6k}$mA)2UJF1S|G$`N=-znWK}`u&SFlGcQ8#nEZgl##}e6~Zq5(Fy`fmM zZ7E}$8jwy`y}*D$>oNR66K@ilXvb*@9;iz(rkA*8x7UVc8|xV=YHLp!Kd53TwWs?7*##b()#Y+MZ6@=D^p4X$z0gnJ1tb9g1$!8ndJ7!rYKwh$Qk zYNtgIoF_xWscuzxPrfC)YN0NcRycf=%gG#-{>5_168xOAE zx2nByL2i=Hd4oMaV_h9{A7dzSx?=n@I49F<38ftyy?{HYNM0g3Fp#Pix?)(H@BINM zz+kSw<7p`oO`Z&L`XN;4Q@7y*A9){@a(c0AVX$`C1nzm>h9dtjRTAw?bM+>)zE;#Z z3^=4NMBA+P9s{Mrsqh6j5rb>ZgW%oFRnxEn_6btJXdb82Rjv(!hU?JQOc2gSx`p&xrPM@ieO#cV3*dm5&5I&>eL=$h}>YqIck|6hkpa}o09p0{(!qJr2 z(><6|D?M9x!9_+CNYNPM1TNtSy8hG3_6kQmVJ zplTcvv3gcWxcG#3D*hZ#wJ*B?OIqzk7>Q~Er)_X`l5omd?-6`CkxP?3xNiJ4eH++w z!3zKl)g+F->5h@G_Dio0TfHkR=H3(7>SNL9Q=GHkvla%$QqL-S@VMKAv>*{BRQ;U_ zE)5~7sg(DVdj<9E<(|X|n{ilk_VeBW!8x8rIY&HSQTj@?8h0yi6A(0=v*hXp68b&E zy`F79d#|AJ8GyV|!*@S3(_I_uA9n*~?+kO ze-D>f@x8^B@}NQm^MLAUx1o6^&HuYRk(rZd>^3EZkVIs#U&l0t*;EBXfRc%=1}f; zak-T6jmk`wZ~MZiZa`cV*=E5nW6Vo&P58Xa=R;LUJ+&hS5V;;7vKI)6(#|Hb}Lqji8eLEj=@(lOA zkBtvx*tWip_{3D-evDWzIG+!N%V`%zoe3DQ#i-*IF8js@TV$mvVzFqjm68xe7PDegC9j*2qp#kCX<^14A%SEk3GlB!h)wzdikMjo?=0hYmCCAzCn zF$h1%iFbUb;QsM{eSHa7)K&LCGpw_KfVd$7gSgGWF#C5l25`?zp{&=;1>AE5QZsV_ z!CoyjaN$^Lf~jdPVX4Q;1xn4#6wAs=->}RjOQEc+)c&9Q9W={+f1ZcunY*8R?)jc` z@44rmbEUcPYZA7fa=ymtC1jjFmrDogXJg13$}83*)X8A|!(b8bP|@-(hxo`yy%S|{ z<^Mt#>zz+xB0v1N9!?U%4j|H9mw5_aFi}4pY!VM<&iA>4RPj;6dBZgQJXP4CwH-^a zEk5A)0eSnAzbw|c7XR;2Z8b@Zbn^uq)%5r%qAG`Liz%=f9P znJ8*(9t-kp;sW)Qf3?eEs_U&!p*dAu22l2W`Y_Sa5w85X`7o#`och@``Esa=zniCT zjX8Wkc{3ClRP?Ma6j}tkin7&e4G*2KUk5aN$T>#+YYGDKouzLx^~Lm>MxP_tLO-%44V3t@K8$ay((eL}ptW18{Tq(7 zq*@V)3S77iM=WWoh24_EtNqo$2zNPPmNhCt;+;@|H7fyhmP_Iq; zu{7*rtcrGT(vRXVs`V+M(NO_PYXGi=@wZN0f<5qIzl!3^)1YS|T4?|7PDwQAP5p2_ z@;|*zp$_|OX?h79J#3i$H}KTF@GX5I05}N%PQj6%UT7aaM?eMn#}_b5&?8{RNqu>! z!1HNtY|#IV)9`3*OK$eH9y+41Gt_uZpGIZN;vD?=IX$$+VgKXC>-t|5{QD(+$n6^e zTYz$x(k7T_XJzQd(IrDfygAuvD1dH7`KR?ss<8UCmJ*n3ACRAr@u}1LAb|EI28Nr3 z1{tESsb+c?ydk)z#~XsMZ)kg9n3Zxn;nddWx_)!d+Wh8BW{E=wPb*pIO8E zcc#K)QP_8!a89mm@z=i02^ZzG!=L^hJ`?1$+n@e{vW6?0Q)RF*jPj$?o}=pX=&Y{6 zVgf`yf1N9waLw|ff?s++btHzk%EepEb8uOiVbIY2F{Uiat~Nu@8xYpO#nmXO7-wor zrtXFaDw2%g4FNDsE#4uT7+*EWqT@#{-YqI-81bSp!#GmZfxiKl5zfy)u!ZvEDW>v3 z%C7Nudkq#TqIFSlGy+mQZ9?dS1O5_dE_)f`dCdXyLkgbyZR#R`_X`&v7PW^A@{jc^ z7atYr{x^&<^ksV5<6^MuTwG^{cDQe2>MpeTjf+ppb%N4@sd9HJG;pI0<3+{ax%iY^ z5|-AUipCj0r~zSr;1Q=8{z`A8eut&L!Nv7*mB_T1inV4f%_=m2h62KFa`8F2ASTU1 z&rdU8^AQksi;FLc%po)U;Qh(P-^=OQa#|I@#SLi-Y8Jg+Cp_#bF{{-_r!gAy1`E6+MJEe80oc-7EK)Y5Qq zoaiO<#}qr6Mks?QbC(Uf;s9vE^QOrC)%g1h=e+51`hY(jLECc-Fxxwyn#2tU%!AyCqG3y2y@s1e!*%R*>+9RBy32wN(mMlEX z7-lR{s$!@h*NDsETq7=rVXjc@HW;~2nROSCbloJ6*plOxSsnO*)gV`I%W)gUK&JElbR5;rv?j1;i}fG|`rSWJI~bQ+UA%9k1*U5?(~KP`<(3WX zDnJ!aS+lKKRR4ZjbI#vk#m=JovcIDQ8Ife#Z9T#99n4?j6>`1>I^_UWBAu9Q(erEh z7C5Jxvs7S2M~#Y2nD%x&L}_{%ikJuO(L6N8 zu9u7SRM0U+&kY?@A{D&<1q<#b5))HYaIJ2iF8VT1RC0=i`dzeMpzN=WZMfeh>+9I| zx>Mv8Na4%~Qw=&jWNb%8zgUBKqSM}1?m3kd))XvogIM5vpIxqRr1HyFP|snRdeU%H zX1dH*j~bg}ekN*6g2RaXOUBL=nUT^!k$Lu(RF+`M$7P@yo4?+6dC!WSR%VjPMUm}+ zhxn(=$vcZ|KxSEgKS&nR^)Ywi+;uO*W#;tR09qu@c8jXkkj9vU3* z?8YX4&zGVkLWZw{Z(ag;}B7)dCRn`NOq;-&kbGp%0W}Kl1(E` zwK6E(xN(%+Q*~2M-MR5`Ib%mtMh|YBC}-?$%IL|B1*V${rap$pA&(!j4Mk?7ETIOoZZwJ*60e>7B@AKRI5}fSQ^ru%O_DQqn-dgt zWsFi)@rmhYfvI7vE0jz2G*!)I-84C)rYR$jbu*A5HZ2dZsUi)R1S(kl>Mi^6X}x9G z057NCnn?6Dg0qasr8)hL#T99(vXqm(tEfhDmPM3g&H?+;9I;`io7=|_Ou1J~&8Y6> zG>cL-inAP|N`1Yd87=;gv5+#`DuWRx!o)IdkH(AnG&n%rnD#l=Vhm^bM5#buQs&f`kiS>N~LNXXZ4Yrk2e-0%8y_2 zt&+zX&sqIMQQb-_PVVPz4LHWs`+J(eSr5vsoK(Z zAr;SIHjM_jY93?}`%Bd%&KhKS0G3mi42keQG2LjcC@=)k>`hj&EmKYAtf6xEa|BQt zvn(Z7%RuFG)^NG!*R0`OcLMlQstP!3w8#_kk|8fo0;fHUAygL<-IB&%H^3?=KvhV! zKN{>*_CZ=J_~uP}9jJSPvnGjl)t(egFvN}tLNp{U&reAfEv9f*p#_h_WU)5%Y}z^u zHI=ib$&itNY`IYun^iJo(>QB}C=wEsAwe$y(b^Ol@lSHrY`N7Mw0bGS4i`07(q0Gk zOy{h*q9F1;YkStm+CN2`m=p))+Redq{Df5{;CPC&o)MMmHyc3@uNVu3X!S4i49LAl7;AA$L&s*Da?u(X69Eu@a8NwBasX2$iYaTY^qBB4>2ch4s?f7wzCa;4~mNL`A3=`Yq;+SX`=K~8|Gm`^uala&m)T80UBwFlGQR;C_G6_lA0wpFt; z?Q=n;+LRb7`_O0;O-d;52}?YW>u#@ru*^u&(Vs1C@W}YMxs50;rDClrGJwm}SbHi?bkf2p8zik0UKuot8E zuS|#$f70lnhNsLj!spZ3ZYc?HfNC?5A;NZ@?>Czm=v%;j2itdGuLp@r)h%o(yPT%R zkkFE0kf&>c=xnK3_FET4i7|$`kfJIWK&uw=_LheJ3SP6ooTsEkpIQ2f2`u96v2yMr zk!xCD!pZGRi;UdG3@3A-;U#Co`=;bBK`@+(k2amR`gWQQX z#EY|~mKWz5{M*MhRJjMey*&0^2T<7j1*l*gI@tg_%vC1XVXD@_djQ;L&zNm{2BTHU zqpj!c@5N|CW*cMz+rZfka(b>m{R(Galhe=m(;GSax=1hTWtHo1;_Mr8`C@f|G+JdxK( zcMNBy<7*9g$N2xF5GcR{RJhW}0#7UJW6Ot(?`Y{pb;Ffj`f7%$Eg#=#_)O-z+etT4 z*_Il58cxabeAg!?C6EMXx~4Se-CBsUPf}$V-eA*o8KwhfIlY6jVwLTM?qTy^CsrBy zomB7^mZ<0*K{rJY82?LcXIPI?cABMHh<3ToE?KVJ7V>Y%Ukl^0oWR0u37#${vzyM2 zQ^I<-!367B)th(~n1&U-Ca>sQq^mQ)PvLd>%X*u1C&jQ|2ugelP}a8w9h(lN)j?Q~SH@P1u_SU@v>9e3s#!7Npy;f3!M^g+7itlqa7?qA$J9S?wn9@-L5)BSqsIvq_f=l zd(`Mofs>IG^F=f{i~}tWZ|zKG!Drs*?3wbs*ZAjsg!1NEI(aH<{WjH~)w~fs?UTyjs zy?y~bMEzdlWtnC_;&LN;<^5IS3_Ist|I9w-a*G%%+mX_a%Z8W>fv1yP?htwP@0r7R z=+9O;z>EqJwcK+1t)hM8E}Umq9XEXN%9bGMh6s{j)!ZtR0qrdz-Haenw zg3}Pk)jwMvM0Xd+{0kH}zL5|#LLVp3;UZV9H4OXz^T9r1O)qiP22t@Z`B z>Z^w972OQb`S!P!!Mp@Mb;>?Z_0#TUuG%K|{}3dxILk85viHkt^gUO-E{Yl!r8MWv z9>zWh_8wt{-G}N2DqnBtEVv;QME%Yfx=@2XO@`tM$4k zvOR!;I)82h=Z_XB_)ApoYiI2afEzh~oLqg8T>X^4`j4DHNv=LAr5PR78=CW(a#MSB zciLb66XzGo3h5{-SGCH^~5PREkw-qkiW68FEDnIOFH5O`TBjqQBxVoIhJ0 zxk?`Sdw=e)u)PxvW{+03MhQHNzSd6 zbG825KRJJ`$X)iR71!A60E&9ej17~=5Cv&2xL|%yTM`IHtzg|z(^n9~6HM(yTFJUP zkloszql?(ZTPpG=BNTm(dO|9q%t;cbR>^>VK% z{$4{l{~drQOG<{Myr4k12-aPc>)8Bt;L3NiwI}x}vhGF?YSuN#rCxujhIQBEbf!P8 zW!-f--QAxKXWb1s-P@mz*r_vPT175{JgsJ|3lizpr_n3REaF3~m{#b==Y`YpO#GE>obtbvx zOn=L^q_dfa@rc9LH7Jc^ol`C?^_RAztZQj-ixg|m1?RhcWMmu}@mfP2&$>*x_CkN{ z1lDyI>B__u8Bd9<>n)cr^Ox&b*H@$?6H+26cZDTf;3SEPZ>F@Qd;=KxJsMZYxZz55 zd#XJIj*zZ4!P%Vsk`=C^)X9ilBKN({-#46143^Uy{ppUBx8GH*U063kq>rDm;>`1zQD|n>de-I3jU4_)2G&gx>Dv=bVcm3*&OYED zG!;JkjgGr@rB|bD2Vh2$2N|>iFXE#!g$z0kb}`>9Vx+3hU=$N=I}lTPCj`^Wq`%+Y zoX0C zwfDi7=%mc)cKA^$wXa2wF3Op1H&XU=dpb{^k0ZNM?Pl{u2k!XOX4}ObzZ&Y?GI>xh zZJ%KuLk&ypkBZ?!lvF*-p1`Kr_Eus~p-!jb+4dH2-sGr5=-_HQLboE^23oxhFe082ZO36po+MR$qb7z#J8fujl z_8`9UtQ{T|0HFLpK@C-KrDK!~&IJDCReLI`Lmw=F$zEvpC~3>P_I3ma+~QZ1fTg<( zaW_9~iH9BU%S70n&DC~Hy=*oDS)-QV%<3M?qZ|jRPlF>_o9oyjy6BDPV2+KVB$?|D*m3)%zK_iZ#88P795R;s z2v9nSb}zvO{k*+i?ns6LRxJVQ``LWd4&ykvkD3#9Y);g0vpd1j4GZE>bJ-^jaP3O7 zqmuGIvsWqA4{*>adD;3=&Nh3ZXqbf0&GD8zQ$$1)=s~((o^GfZBioM-Oz@Hn15EYQ zIRYgklpOhy{RJ6ka3-robT-q2v8Em()hr>KC;UwgPYxhpx1=U_OlfE2~eLO?pT5m9;2*6 z$0%xd)&30wMBEpvp)j6pho^Q>*Fkx0hlo*AIZ`13t7klpaNZW{7yx_>6$S0ZNbtA_ zAU~9mGK{mT?fd^C9C4~r=Tg-yd(2-r7WO0RJdtS;7%04JnZ>#n$~_II+S&HY0^JSr zVz>Saztj-!I4SxVL5ff8xV^rDuW#QEAO(TxiTtz05e^7P3J4PfgacqBw8FkX?spWI z?{Q4V#okFff}#W8^qt4iTB#n*nWya7$o0Kx_lmy9{M#om7T?}g@Z$4!StL9z2*3WH zWGZej)bK9R^x5GW29x+i9(+7!elHIa<&X!NK$%k=2+Tde@gYb9TH=ikKk}mG*@})P z2|OiI?kvYm)(vr-#Uf3noRju4vC?e?<{v^+yvLkn7gEIGmm&q6bJ&g;@`>XeUn|vx z0+;j~p4*?I@T9DsK~bmeg@VW`e@snGQs@}fMPep8F_SN4 zc#C&tiFF0pq>EzV64!nR>STFR^X;AGkxB$c!|{IY-)Ch2bfwg|z(27_9B4m*@Br1f z{J*A#Gp2f;e|B-Z9Z<5UpZN!s{FkfR;8TH5S4Sh(cs`kbwl5VVh3j#FR{`t-(FRAu z!wwt`)z6BQTTs#w+%=!KUl&!LEe}1)sGvfrWUPQH@+AAs3 z;@HWLe(uzGa%C(gE?Kp?nWhe1YK zxq!zgpgSgGddWXz_EJBQ7o|En^ZgAD*h8z~@3+>z=wBHNgR#i(iPXzsedu^srWcO< z&G93Etl&JGV-3y_#r7d$mL$412q42{^61V*fzsR{rTPUr+F2S$$0Mako*gEY25QtV zI?NLMvN~*P%4sEars`Hw9Az6NI19>^LV2iGS_jBh(sqN?k-~>c;WQ*xg4yz5sS_op zNWnZMQd*1}t7vL+d=>k$OQGJ@!ifV@8y|*z@YQe5J2`=E(YiO)TT0vz!0Z{6ENyA$cCHSINS3>m- zfEH&+g>*ArilyA%QV?t#q!6x2mb#)w6$f>eZUu&@*J6I7soEyB;|?j(85F%tRR#%) z3zG!Jg?b%jt&7A~-zJHz{(9JSqnT2@fmS6-cr56Zoww_K@+=5&8b;?DVnLb zG#>3=rG|MDl#6|(bAf90X4?px9S<6z5bH0Q*qbdKLY*xL5+{SSm7;(<)(@0UA$uD| znt`%hgJk6U21{=u>or6V6TRi4H>J9o295`8Hw%IJ-06~oOGZj_kiVUS#>)M@PWz`z zNmMx!tNMJQq+;&`45(7SVRwv@Y&IJelt^jp&X?fULA`@=XGriTJVk;(Vf9Xg7n961 zZM@Wy@1HIS>fJ?k{iN8XlOG7Cv@sxz&NC%g<*Avnp^a1jBlVXm9 zGgYZ1&Qx#Gr703_O6N_HzXu$W%&~QMHC@4=9mZC#Pyzw@rEucN3eWCs6er3g0Fz<|SLCd{ljxsyrx4) z?2|qPzc|F^neHWcAyO;hg@_t4$lh|LvGsuTGi-lH1qB=zN1Y5x_}Qi6sYfxn5cL5J za|FhiQge7G7!6+nLL(WU@p%cNn&q*&~j&)S@6b|6|yD*V82c>@e_C=`~ zMjb26F~!S-iXWv*A?lAdJykM>ul^`C!!Za}tLl$Q_pbX(PCO=^h9Y#Qy9%*5<3lMj z*x8LX{3;C&Ri8AC&ci)f?K*dBI)2#I_wPVWVDVFcd75-P-H4-X_Ozy~wEP~<(VXAL*#lzkKh7&5Y8a%d>sfb3>L4Q8IbTAgA}4|csn1f> z`|gQcknemx)bBQ2aV9`yG1-@c?GlRc&YP2)<5p-4OD)GKF?83 z-#{JZzwAU{zaZxhgnE$R;_VwLg3A`W^07JC>AE0R?pxa4#od`xW;){m>IA&D=qo^- z!{{*$6aEHuE|R&(2agT&oqEjh66wxLKF|)iTIBQs=I?0IQr8kHI_A>S zntXRlIzHAlm}APFSCm@y<-)>Ilco%t^7zD2RKC*Hg4-{14gpO}YO+L^=g|Qta`g|J zpDdXO;}vH(Nb&+Qu28`kJ3@-icd01<9p`xtt8yYVgZe5df}K5i)h_2qTxxt42=8&f zNt5}>-OjJ%O=$yV&2obeY;?W@up7C2pA!nRh^@|RAxia6oHt2C90AgGAhv`4jz;6# zx<*Bhx)GpZw{uZwm{QRyKyg9Q3gJRD3XEHVs4wqK5g2-w%%3>DD)ld`f=i%l-Ys6` z$dl*>?U-{h*5OCM{ws$MapBBZ{h6~fZ~ol*AHaQ`qOx32jh%K1)!1)z_R)ANzV%@C z);kY@NdJHf*l0xVe%I;cZB@<|cz%`V66YG|6(>70Dvsd8Tff^ql%qzuI>8}-0UYw@ z3y{7fP{20=hri?hPb&CDLU5^k7aaC!0;p<``)fWw#TAE?f^^ZYBe-UF`3SV0OQ!n( z(d$pSvXx4WlJaWd;`{jpErKcGVmn+u&vdOtqd;m@2jUIaGcKH6^XIrG@U*$E8?ZAg zga^5i3;;ArE_v4VH0FK_9_Ig{`_(>0htMZL&$Fj_^qBh*1u6z$5UZRIF6 z{VC1oy5%lgh&nJJCqR)FP!o^?HBGnB%8Zh!K>@hjY6B$=IAI4oveZPZd8A}YP{4li z$kLySH^&jWY8JI>UJZ{jraTKhZ6puYMCbJozjYdl+p7r5n z)H;_Pmwe|XPzQmKg)jo?qLy~Oj4Ph9Zlq=})Z!|vHU_mrzBP_1b(y6KOyC%bci0aq^X zJ?-k+>W10diKrSLK3(!}2F!`9}OObZG)*j>zRf&#AJG;jr{X)W8fn=EF^IWBGf zC%63g+@fKTBb3z3w^=T*OmUnG3clH(aBTN}#+lXBO4G>DmV0 z9a3wt+~faFtbI9G>3$1}z3GVYkSwNlT3$Bc7!G!>PWLZ7QR_YeI@P$!WSS$B)iVz< zwcJTcO?!&^Ug|=}wJtojUg%T{#sPquxg)v%P8ZHu)fZjY;5sG#e!7zAe3%;W&=i+4 zAkt&A+^Ym4lBqlrBJS!Y*HF6D+}(<9Ih_s#NDp=%fiH)WEVR&O}9m#Wg zyJ2XqNuh=T5%=)}ecd}jZboW+$BmF41Ke=@u1O>FQ%?A08t8r_P@$o!x7^2?KDOPi zC#iax>lQYOu19F|9Q2%d%>51J{wWRLmFsSeuYYDxzRmEQ!D&fn->f>d|fp6zTw$CkSb_}MM)=^%oh zlr_$o#{G{q?LM0En4RvGfVUT&U}qZLdf!#ZkTk=D99$is)GAhs4M@2nj2;!Jf6XCa z9^A}~MU&s3YVKQ^TN=QT@3^l3@*aS^kANHxFHX9@1LwNmWOBga3U+YizqvBG?m=UR!&kiLO1 z%YNB8nA=%A@bs_AAs7lgc;_o_r{EI1XAWlbAZ4Vu5yH;t`L8OZ6vyeq;$4-SJ9hP) zLc9JI-96nxN`ry|7SBMwlZ(51eh5;Q26O&>o|U1&rBI#Li^pYd{;sx<=_&Md{Q z{e_vREivbKdI7XY0NSIJw>7Q;ZluCb?^1?A8@_&`H$x`TL1bFe_7eXv(9;4-0Y~?Z zt?>e6ut$8DgDLap)(d!1u4gJzLn!mN);dNo<~-=aXL<)yZujuXWSHgMQgJq}4YwTQ z`94Ug8AkixYo1QMjx_&@6DNAkg;3DRsCUCEx|Bi*xkbFvl{jRo=QB5JX9Y|S?$|&gkhXFoT_Ien0f3% z&x-(V4CTy=$2PyrgKfTMELGiz$ANO0=M>iVaV#rVcQ`KI`X#)@hgNusLqkd-M~e|l zCMMiL$1Zr(w5ZavIz%&mtFu(irYg@dz&8p5PM}-Y!;kQ>bsif!p2!(D!)uka@>Ne1 zU5SX$EKiI`qUr`k3&I0itokLTk&4zw71OKlds>MvdlE&x8dS#1ws*x93Zw3aMTlF_tIn@l+u71l31GWRg4H7eo13 z2n|1~#EKyOUhE(fYs0z-elc^SwN#5u^Ej-rT_DvIqM3&LSxckF z!RqLu2MRIGY#!9n`?7+Ms=PKOeRNvWAb1$&a=e4xo;dJshz;QS*0sA?0A*GXy~ z@}p-xK8GSIs7Y=v{M~oq%xfNOoi)Wg$l`_7bIqH&5U&2s^B`t8mlXv`6R?K6~hN%#3b#wU#b73DSa zRw*_ArK)3i>vQENPc*iFN!Y_*^AzCL`y-W5Lu%&Jc59M>axLD6xirQ*8z3%V#gB*u zMu@aCU%*tL;E=XnFDNhql2=4^wF^8JgTPDB!@>>UVAr3&R5wt#u#Dsh9lR9)Y!MYy z=x~#A7d`Q-h{bKuSd778__hNv;xnJJ8mCwiK{yq47KHp#9WjNG&Npivkn3YUJJQuie ziZ=@6w1(>1wl(3Qd|YSR_e<+;yk@$01oGh{BSGY^jeCGz8RhZto|)e6nhguRK}{FM zV*?Z~;S4esjtU2bmP@5Ag)>TA6Pu}RmA4rcz3iQ>)@*pbptJ=%SdPZz%rt$z*QU~} zTlh$641|g}m{|GZd~Zjv#WLpaUasSc+vUGXY*ukaoc zcYB&md?Gg401ba!@)U53jHDJ^ZR(f++ni;|!X#DmDxc7+(m+51v>#Xe>`i9Wl>1ePhTi{H*^<`eDq8Tcz22&j3T=9DuwqkyVw2cAtWks=NGojx zsW4x-6Eri>5{H>?1c0wo?ON{?PArG_ajN+?Y(t0%ZWc6e;MrR8R^EQtn*$JbP+o{| zA$rsc%M{H{&Ql8?x279j*$0_s7w1JJKcQgtPVWMlf|>H7;k~Znly?HS_U=x2hZSLj zeOS7dGfsP<1=7Gx=a!&hh%*z$h^g(yzZR$X_d0HHS4Z; z&wzz&2UvUA9H#ZbN$4G)>~LEAg#0b+0}JhRW7p4KB|6)Gx5t$0-e_diaKYhDM-&xX zJ5};}aIp0N9v__EHB?#JhF{aB z_YVx$>_N?glEZ4YIC1U2E2IOPBGb1)oVTU=*!0IkH1CynD2=85ZPQ;1(HubK!?e9a zRC9W%PY{j}O9PXj3QHGAK#Z`&^jr7=6=Aac;{)u%(godpNSl9cmQAk)Mn>~!Q+jn+ zNNF6@hk{j^xUVZ6p%*nr(RCfw+SA)n?qYZV9yU)I%4y#8k*FLGi-(zV<@dX%e+2A( zwB(`Ec3jmneFzfA(eh(@&YM1sXY@`l)GA98_{xCvNPu#3*TD1#!5HB)x)6VD%- zJ`E)I>8{-L=GY(Vf&vhsF*n-G_vNMU2?-5I3s6oDNCTs3AGRu`v^}&kgVC-NUtOKv zOsUd*`CM>m5;_xNRnL{<(uV?o&jG+`d}H#Y&(NnYrz3RsvhtDWhWk^L>z zRwmT($=&Ju!b3_sfon`bZ^^XhPQ@Tz?9tB>npK!KfN3!ovO-N}fuoQwp+VB9A z?^Vdp-_X|$%F6AKL8<00HE>nz`%|gb+#E5&KE&y;;s$0xhd8?bY-=sW4ek)j{hYoX z80Z$AZA^zQw_~$Vj`#X9!jz?HJgmF#7o|!YP!&~*M=$S)=an&3*vq#tMDqtc-7BbE zN`|tc$y@j{3epeq&4Y5wtK|*DeS;t&FNEQ&5ujC4u{U`j=Z^HjFI443Z6rsG_Wg)W z|7Cx9AXPnlRMGkvA!>OWRCA8r4q5l&c=2DUyu9+-a5`c?9(AHmH<#=6eEfjD5q= zG=g#lg~Gl0Oy6dtnsHUN3lb`=*!MADj-=x4&EZnG)c1O*QX5T?#^#7>xX_2&AZ>HX ze=@KYX`^ChQo@Xu|Kq9UKDY|d#!%ifEpY9(%y%I$P}>4-6)$^REgc%)k}WHIb%9!K zO9{S*Z1xe5`;_Gk($l8O!0{D*;#YCTYTvu)p%sE5g62X?EkOal<=PRBF0(7uk8O27 zZGE=ojf%~_TOnF_!_dZZ-U6q%Lf-1b6|%N16?AdqQE;`dIlzmfEcPK5*VOpYl>esB z$$2|{*!FAN;c_P~mg+A_eMSBdes8yLOQ@z4uUXEFv;T=dDmbGu^J_J}{(&LdHW()n z??K{H*>o-*j|&l-JsREU5J)m`EKhFml?R3nceqduI;}aeV6KH3uW{W`9}JAN?U$bD z59g7w3GMlT>%KPvLmd|ACA7)Qqk=~A*pogS*t8w!tStuaj86HEAk~o)pY}oFJ3c;~ z`=3S=rM460M6^$&>^BnD@!@)32}s<=4_xwX$D$(Of>yt@IA$mh|K3-L$_98YK-B2O zed$R8IBXZRDO~$vLIzyFf9TsQc*G=_zi+OcI=;pJLahDWIyiGdI}LTjV#@nNRFkQXk4 zV80hk2Rz*;e9mX$GCBYv=^hxJ_rTa8L%@iq(Rbm`eK-D6G6eoScSCB*_!0PX@E%u2 zSK!YI2z_r9$CTpzRCfCtnBDIN)4$9;?gn#U#uR+VPWI(yJgLxVdmY6s>OZk7B7XEH zvnS(&H;Q85xJRBqMM&au0snnuz7T($_ODJDBk16ejP*Y7g^+$f75(HZqRhgKUFX8Q{+8vaIR3z-DI|gDi8lWvD#lG|` z!3L6)Jm!(imEdLb=xpZES%VUym)G6K6vq5G{9TjbMbJv4@rax)J)6R zCc0TnS)rPkCT3heD>Dh41n+N;`gX}6!_&XEj2a6Nk587ecfiEPK0pgIt%JTP1PJ%C zeL*H}EwxM7c*^$?EEJ#i31yXbDH~HVHp8^5Jo6zGFT=%tOf207a<77w6`6sdp`{tX d`pRU2fLqO`X_-fHhTo(LSy~-aEP7e^{{RW*66gQ` diff --git a/test/fuzzinvariants.c b/test/fuzzinvariants.c index c0ed2dde5..883f8cdfc 100644 --- a/test/fuzzinvariants.c +++ b/test/fuzzinvariants.c @@ -136,16 +136,11 @@ int fuzz_invariant( } sqlite3_finalize(pCk); - if( sqlite3_strlike("%group%by%order%by%desc%",sqlite3_sql(pStmt),0)==0 ){ - /* dbsqlfuzz crash-647c162051c9b23ce091b7bbbe5125ce5f00e922 - ** Original statement is: - ** - ** SELECT a,c,d,b,'' FROM t1 GROUP BY 1 HAVING d<>345 ORDER BY a DESC; - ** - ** The values of c, d, and b are indeterminate and change when the - ** enclosed in the test query because the DESC is dropped. - ** - ** SELECT * FROM (...) WHERE "a"==0 + if( sqlite3_strlike("%group%by%",sqlite3_sql(pStmt),0)==0 ){ + /* + ** If there is a GROUP BY clause, it might not cover every term in the + ** output. And then non-covered terms can take on a value from any + ** row in the result set. This can cause differing answers. */ goto not_a_fault; } @@ -236,7 +231,7 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ const char *zIn; size_t nIn; const char *zAnd = "WHERE"; - int i; + int i, j; sqlite3_str *pTest; sqlite3_stmt *pBase = 0; sqlite3 *db = sqlite3_db_handle(pStmt); @@ -281,6 +276,14 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ ** WHERE clause. */ continue; } + for(j=0; j1 && i+2!=iCnt ) continue; if( zColName==0 ) continue; diff --git a/test/hook.test b/test/hook.test index bb868df8b..3d735875d 100644 --- a/test/hook.test +++ b/test/hook.test @@ -16,6 +16,7 @@ # sqlite_commit_hook (tests hook-1..hook-3 inclusive) # sqlite_update_hook (tests hook-4-*) # sqlite_rollback_hook (tests hook-5.*) +# sqlite_preupdate_hook (tests hook-7..hook-12) # # $Id: hook.test,v 1.15 2009/04/07 14:14:23 danielk1977 Exp $ diff --git a/test/in.test b/test/in.test index 7c4cc5187..716c17f59 100644 --- a/test/in.test +++ b/test/in.test @@ -806,5 +806,30 @@ do_execsql_test in-21.1 { SELECT COUNT(*) FROM t0 ORDER BY (t0.c0 IN ()); } {0} +# Ignore extra parentheses around a subquery on the RHS of an IN operator, +# because that is what PostgreSQL does. +# +do_execsql_test in-22.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x INT PRIMARY KEY, y INT); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<8) + INSERT INTO t1(x,y) SELECT x, x*100 FROM c; + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(a INT); + INSERT INTO t2 VALUES(2),(4),(6); + SELECT * FROM t1 WHERE x IN (SELECT a FROM t2); +} {2 200 4 400 6 600} +do_execsql_test in-22.2 { + SELECT * FROM t1 WHERE x IN ((SELECT a FROM t2)); +} {2 200 4 400 6 600} +do_execsql_test in-22.3 { + SELECT * FROM t1 WHERE x IN (((SELECT a FROM t2))); +} {2 200 4 400 6 600} +do_execsql_test in-22.4 { + SELECT * FROM t1 WHERE x IN ((((((SELECT a FROM t2)))))); +} {2 200 4 400 6 600} + + + finish_test diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 042132b81..7d94806cd 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -420,7 +420,7 @@ do_execsql_test indexexpr1-1500 { # 2018-01-03 OSSFuzz discovers another test case for the same problem # above. # -do_execsql_test indexexpr-1510 { +do_execsql_test indexexpr1-1510 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(a PRIMARY KEY,b UNIQUE); REPLACE INTO t1 VALUES(2, 1); @@ -434,18 +434,18 @@ do_execsql_test indexexpr-1510 { # a numeric table column, trouble can arise since there are multiple # string that can map to the same numeric value. (Ex: 123, 0123, 000123). # -do_execsql_test indexexpr-1600 { +do_execsql_test indexexpr1-1600 { DROP TABLE IF EXISTS t1; CREATE TABLE t1 (a INTEGER, b); CREATE INDEX idx1 ON t1 (lower(a)); INSERT INTO t1 VALUES('0001234',3); PRAGMA integrity_check; } {ok} -do_execsql_test indexexpr-1610 { +do_execsql_test indexexpr1-1610 { INSERT INTO t1 VALUES('1234',0),('001234',2),('01234',1); SELECT b FROM t1 WHERE lower(a)='1234' ORDER BY +b; } {0 1 2 3} -do_execsql_test indexexpr-1620 { +do_execsql_test indexexpr1-1620 { SELECT b FROM t1 WHERE lower(a)='01234' ORDER BY +b; } {} @@ -453,7 +453,7 @@ do_execsql_test indexexpr-1620 { # ExprImpliesExpr theorem prover bug: # "(NULL IS FALSE) IS FALSE" does not imply "NULL IS NULL" # -do_execsql_test indexexpr-1700 { +do_execsql_test indexexpr1-1700 { DROP TABLE IF EXISTS t0; CREATE TABLE t0(c0); INSERT INTO t0(c0) VALUES (0); @@ -468,17 +468,17 @@ do_execsql_test indexexpr-1700 { # computing the expression. # ifcapable like_match_blobs { - do_execsql_test indexexpr-1800 { + do_execsql_test indexexpr1-1800 { DROP TABLE IF EXISTS t0; CREATE TABLE t0(c0 REAL, c1 TEXT); CREATE INDEX i0 ON t0(+c0, c0); INSERT INTO t0(c0) VALUES(0); SELECT CAST(+ t0.c0 AS BLOB) LIKE 0 FROM t0; } {0} - do_execsql_test indexexpr-1810 { + do_execsql_test indexexpr1-1810 { SELECT CAST(+ t0.c0 AS BLOB) LIKE '0.0' FROM t0; } {1} - do_execsql_test indexexpr-1820 { + do_execsql_test indexexpr1-1820 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(x REAL); CREATE INDEX t1x ON t1(x, +x); @@ -491,19 +491,106 @@ ifcapable like_match_blobs { # Assertion fault during a DELETE INDEXED BY. # reset_db -do_execsql_test indexexpr-1900 { +do_execsql_test indexexpr1-1900 { CREATE TABLE t1(x TEXT PRIMARY KEY, y TEXT, z INT); INSERT INTO t1(x,y,z) VALUES('alpha','ALPHA',1),('bravo','charlie',1); CREATE INDEX i1 ON t1(+y COLLATE NOCASE); SELECT * FROM t1; } {alpha ALPHA 1 bravo charlie 1} -do_execsql_test indexexpr-1910 { +do_execsql_test indexexpr1-1910 { DELETE FROM t1 INDEXED BY i1 WHERE x IS +y COLLATE NOCASE IN (SELECT z FROM t1) RETURNING *; } {alpha ALPHA 1} -do_execsql_test indexexpr-1920 { +do_execsql_test indexexpr1-1920 { SELECT * FROM t1; } {bravo charlie 1} +# 2022-11-28 Ticket 695a1a53de +# Improved ability to recognize that an index on an expression is a +# covering index. +# +reset_db +do_execsql_test indexexpr1-2000 { + CREATE TABLE t1(a INT, b TEXT); + INSERT INTO t1(a,b) VALUES + (10, '{"one":5,"two":6}'), + (10, '{"one":50,"two":60}'), + (10, '{"three":99}'), + (11, '{"one":100,"two":200}'); + CREATE INDEX t1_one ON t1(a, b->>'one'); + CREATE INDEX t1_two ON t1(a, b->>'two'); +} +do_execsql_test indexexpr1-2010 { + EXPLAIN QUERY PLAN + SELECT sum(b->>'one') FROM t1 WHERE a=10; /* Query AA */ +} {/.* t1_one .*/} +do_execsql_test indexexpr1-2011 { + SELECT sum(b->>'one') FROM t1 WHERE a=10; /* Query AA */ +} {55} +do_execsql_test indexexpr1-2020 { + EXPLAIN QUERY PLAN + SELECT sum(b->>'two') FROM t1 WHERE a=10; /* Query BB */ +} {/.* t1_two .*/} +do_execsql_test indexexpr1-2021 { + SELECT sum(b->>'two') FROM t1 WHERE a=10; /* Query BB */ +} {66} +do_execsql_test indexexpr1-2030 { + DROP TABLE t1; + CREATE TABLE t1(a INT, b TEXT, c INT, d INT); + INSERT INTO t1(a,b,c,d) VALUES + (1, '{"x":1}', 12, 3), + (1, '{"x":2}', 4, 5), + (1, '{"x":1}', 6, 11), + (2, '{"x":1}', 22, 3), + (2, '{"x":2}', 4, 5), + (3, '{"x":1}', 6, 7); + CREATE INDEX t1x ON t1(d, a, b->>'x', c); +} +do_execsql_test indexexpr1-2030 { + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1; +} {1 6 4 54 46} +do_execsql_test indexexpr1-2030 { + explain query plan + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1; +} {/.*SCAN t1 USING INDEX t1x.*/} + +reset_db +do_execsql_test indexexpr1-2100 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); + INSERT INTO t1(a,b) VALUES(1,0); + CREATE INDEX x1 ON t1( "y" ); + CREATE INDEX x2 ON t1( +"y" ); + CREATE INDEX x3 ON t1( +'y' ); + CREATE INDEX x4 ON t1( "y*" ); +} +do_execsql_test indexexpr1-2110 { + UPDATE t1 SET b=100 WHERE (SELECT 'y') GLOB "y"; + SELECT b FROM t1; +} 100 +do_execsql_test indexexpr1-2120 { + UPDATE t1 SET b=200 WHERE (SELECT 'y') GLOB +"y"; + SELECT b FROM t1; +} 200 +do_execsql_test indexexpr1-2130 { + UPDATE t1 SET b=300 WHERE (SELECT 'y') GLOB +'y'; + SELECT b FROM t1; +} 300 +do_execsql_test indexexpr1-2140 { + UPDATE t1 SET b=400 WHERE (SELECT 'y') GLOB "y*"; + SELECT b FROM t1; +} 400 + + + finish_test diff --git a/test/interrupt.test b/test/interrupt.test index 8774aee86..23d986987 100644 --- a/test/interrupt.test +++ b/test/interrupt.test @@ -94,14 +94,28 @@ ifcapable {vacuum && !default_autovacuum} { } 1 } ifcapable {explain} { - do_test interrupt-2.5 { + do_test interrupt-2.5.1 { + sqlite3_is_interrupted $DB + } {0} + do_test interrupt-2.5.2 { + unset -nocomplain ::interrupt_count + set ::interrupt_count 0 set sql {EXPLAIN SELECT max(a,b), a, b FROM t1} execsql $sql - set rc [catch {db eval $sql {sqlite3_interrupt $DB}} msg] + set rc [catch {db eval $sql { + sqlite3_interrupt $DB; + incr ::interrupt_count [sqlite3_is_interrupted $DB]; + }} msg] lappend rc $msg } {1 interrupted} + do_test interrupt-2.5.3 { + set ::interrupt_count + } {1} } integrity_check interrupt-2.6 +do_test interrupt-2.7 { + sqlite3_is_interrupted $DB +} {0} # Ticket #594. If an interrupt occurs in the middle of a transaction # and that transaction is later rolled back, the internal schema tables do diff --git a/test/intreal.test b/test/intreal.test index 1a3db0a23..d9ffe9909 100644 --- a/test/intreal.test +++ b/test/intreal.test @@ -95,4 +95,22 @@ do_execsql_test 3.0 { PRAGMA integrity_check; } {a {} ok} + +reset_db +do_execsql_test 4.0 { + CREATE TABLE t1(a REAL, b AS ('expr') ); +} +do_execsql_test 4.1 { + INSERT INTO t1 VALUES( REPLACE(0, '', 'expr') ); +} +do_execsql_test 4.2 { + INSERT INTO t1 SELECT REPLACE(4, '', 'expr'); +} +do_execsql_test 4.3 { + SELECT typeof(a), a FROM t1; +} { + real 0.0 + real 4.0 +} + finish_test diff --git a/test/join5.test b/test/join5.test index 2ffe3f8ac..85e09c57d 100644 --- a/test/join5.test +++ b/test/join5.test @@ -287,6 +287,8 @@ do_eqp_test 7.2 { do_execsql_test 7.3 { CREATE TABLE t3(x); + INSERT INTO t3(x) VALUES(1); + CREATE INDEX t3x ON t3(x); CREATE TABLE t4(x, y, z); CREATE INDEX t4xy ON t4(x, y); @@ -296,6 +298,8 @@ do_execsql_test 7.3 { INSERT INTO t4 SELECT i/10, i, i FROM s; ANALYZE; + UPDATE sqlite_stat1 SET stat='1000000 10 1' WHERE idx='t3x'; + ANALYZE sqlite_schema; } do_eqp_test 7.4 { diff --git a/test/join8.test b/test/join8.test index 481430556..1140c104f 100644 --- a/test/join8.test +++ b/test/join8.test @@ -258,7 +258,7 @@ do_execsql_test join8-7020 { ) SELECT * FROM t0 FULL JOIN t4 ON t0.a=t4.d AND t4.z>0 ORDER BY coalesce(t0.a, t0.y+200, t4.d); -} {/.*BLOOM FILTER ON t2.*BLOOM FILTER ON t3.*BLOOM FILTER ON t4.*/} +} {/.*BLOOM FILTER ON t2.*BLOOM FILTER ON t3.*/} # 2022-05-12 Difference with PG found (by Dan) while exploring # https://sqlite.org/forum/forumpost/677a0ab93fcd9ccd diff --git a/test/memdb2.test b/test/memdb2.test new file mode 100644 index 000000000..286bfc3f8 --- /dev/null +++ b/test/memdb2.test @@ -0,0 +1,77 @@ +# 2022-12-05 +# +# 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 the "memdb" VFS +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix memdb2 +do_not_use_codec + +ifcapable !deserialize { + finish_test + return +} + +db close + +#------------------------------------------------------------------------- +# Test that when using a memdb database, it is not possible to upgrade +# to an EXCLUSIVE lock if some other client is holding SHARED. +# +foreach {tn fname} { + 1 file:/test.db?vfs=memdb + 2 file:\\test.db?vfs=memdb +} { + if {$tn==2} breakpoint + sqlite3 db $fname -uri 1 + sqlite3 db2 $fname -uri 1 + + + do_execsql_test 1.$tn.1 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + } + + do_execsql_test -db db2 1.$tn.2 { + BEGIN; + SELECT * FROM t1; + } {1 2} + + do_execsql_test 1.$tn.3 { + BEGIN; + INSERT INTO t1 VALUES(3, 4); + } + + do_catchsql_test 1.$tn.4 { + COMMIT + } {1 {database is locked}} + + do_execsql_test -db db2 1.$tn.5 { + SELECT * FROM t1; + END; + } {1 2} + + do_execsql_test 1.$tn.6 { + COMMIT + } {} + + do_execsql_test -db db2 1.$tn.7 { + SELECT * FROM t1 + } {1 2 3 4} + + db close + db2 close +} + +finish_test + diff --git a/test/memsubsys2.test b/test/memsubsys2.test index 68e4e22d3..9085dd295 100644 --- a/test/memsubsys2.test +++ b/test/memsubsys2.test @@ -74,18 +74,23 @@ do_test memsubsys2-1.2 { # Test 2: Verify that the highwater mark increases after a large # allocation. # -sqlite3_memory_highwater 1 -set highwater [sqlite3_memory_highwater 0] -do_test memsubsys2-2.1 { - sqlite3_free [set x [sqlite3_malloc 100000]] - expr {$x!="0"} -} {1} -do_test memsubsys2-2.2.1 { - expr {[sqlite3_memory_highwater 0]>=[sqlite3_memory_used]+100000} -} {1} -do_test memsubsys2-2.2.2 { - expr {[sqlite3_memory_highwater 0]>=$highwater+50000} -} {1} +# Do not run this if [sqlite3_memory_used] returns 0. This indicates +# an SQLITE_DEFAULT_MEMSTATUS=0 build. +# +if {[sqlite3_memory_used]!=0} { + sqlite3_memory_highwater 1 + set highwater [sqlite3_memory_highwater 0] + do_test memsubsys2-2.1 { + sqlite3_free [set x [sqlite3_malloc 100000]] + expr {$x!="0"} + } {1} + do_test memsubsys2-2.2.1 { + expr {[sqlite3_memory_highwater 0]>=[sqlite3_memory_used]+100000} + } {1} + do_test memsubsys2-2.2.2 { + expr {[sqlite3_memory_highwater 0]>=$highwater+50000} + } {1} +} # Test 3: Verify that turning of memstatus disables the statistics # tracking. diff --git a/test/misc1.test b/test/misc1.test index b1b1b083c..44914156c 100644 --- a/test/misc1.test +++ b/test/misc1.test @@ -605,14 +605,17 @@ do_execsql_test misc1-19.2 { SELECT * FROM t19b; } {4 5 6} -# 2015-05-20: CREATE TABLE AS should not store INT value is a TEXT +# 2015-05-20: CREATE TABLE AS should not store INT value in a TEXT # column. # +# 2022-12-14: Change: The column is not TEXT if the AS SELECT is +# a compound with different types on each arm. +# do_execsql_test misc1-19.3 { CREATE TABLE t19c(x TEXT); CREATE TABLE t19d AS SELECT * FROM t19c UNION ALL SELECT 1234; SELECT x, typeof(x) FROM t19d; -} {1234 text} +} {1234 integer} # 2014-05-16: Tests for the SQLITE_TESTCTRL_FAULT_INSTALL feature. # @@ -622,11 +625,11 @@ proc fault_callback {n} { lappend ::fault_callbacks $n return 0 } -do_test misc1-19.1 { +do_test misc1-19.11 { sqlite3_test_control_fault_install fault_callback set fault_callbacks } {0} -do_test misc1-19.2 { +do_test misc1-19.12 { sqlite3_test_control_fault_install set fault_callbacks } {0} diff --git a/test/multiplex3.test b/test/multiplex3.test index c1e741acd..318835016 100644 --- a/test/multiplex3.test +++ b/test/multiplex3.test @@ -82,6 +82,8 @@ do_faultsim_test 1 -prep { multiplex_restore_db sqlite3 db file:test.db?8_3_names=1 sqlite3_multiplex_control db main chunk_size [expr 256*1024] + execsql { PRAGMA journal_mode = truncate } + execsql { PRAGMA synchronous = off } } -body { execsql { UPDATE t1 SET a=randomblob(12), b=randomblob(1500) WHERE (rowid%32)=0 diff --git a/test/mutex1.test b/test/mutex1.test index aac04bf49..0fc8368f7 100644 --- a/test/mutex1.test +++ b/test/mutex1.test @@ -118,6 +118,7 @@ ifcapable threadsafe1&&shared_cache { do_test mutex1.2.$mode.1 { catch {db close} sqlite3_shutdown + sqlite3_config_memstatus 1 sqlite3_config $mode } SQLITE_OK diff --git a/test/notify3.test b/test/notify3.test index 4b5e8016b..6d65b644d 100644 --- a/test/notify3.test +++ b/test/notify3.test @@ -108,8 +108,8 @@ if {[presql] == ""} { } " 0 0 0 0 $err SQLITE_LOCKED SQLITE_LOCKED_SHAREDCACHE 1 0 0 1 $err SQLITE_LOCKED_SHAREDCACHE SQLITE_LOCKED_SHAREDCACHE - 2 0 1 0 $err SQLITE_LOCKED SQLITE_LOCKED_SHAREDCACHE - 3 0 1 1 $err SQLITE_LOCKED_SHAREDCACHE SQLITE_LOCKED_SHAREDCACHE + 2 0 1 0 $noerr SQLITE_OK SQLITE_OK + 3 0 1 1 $noerr SQLITE_OK SQLITE_OK 4 1 0 0 $err SQLITE_LOCKED SQLITE_LOCKED_SHAREDCACHE 5 1 0 1 $err SQLITE_LOCKED_SHAREDCACHE SQLITE_LOCKED_SHAREDCACHE 6 1 1 0 $noerr SQLITE_OK SQLITE_OK diff --git a/test/orderby1.test b/test/orderby1.test index 9b5df4c75..7432b5473 100644 --- a/test/orderby1.test +++ b/test/orderby1.test @@ -43,6 +43,7 @@ do_test 1.0 { (NULL, 1, 3, 'one-c'), (NULL, 2, 1, 'two-a'), (NULL, 3, 1, 'three-a'); + ANALYZE; COMMIT; } } {} @@ -180,6 +181,7 @@ do_test 2.0 { (1, 3, 'one-c'), (20, 1, 'two-a'), (3, 1, 'three-a'); + ANALYZE; COMMIT; } } {} @@ -327,6 +329,7 @@ do_test 3.0 { (NULL, 1, 3, 'one-c'), (NULL, 2, 1, 'two-a'), (NULL, 3, 1, 'three-a'); + ANALYZE; COMMIT; } } {} diff --git a/test/pager2.test b/test/pager2.test index 0e2b33b83..1d78b30f5 100644 --- a/test/pager2.test +++ b/test/pager2.test @@ -165,4 +165,6 @@ do_test pager2-3.1 { list [catch { db3 eval { INSERT INTO t1 VALUES(3, 4) } } msg] $msg } {1 {no such table: t1}} +db1 close + finish_test diff --git a/test/pendingrace.test b/test/pendingrace.test new file mode 100644 index 000000000..f0e1a18ff --- /dev/null +++ b/test/pendingrace.test @@ -0,0 +1,126 @@ +# 2023 January 31 +# +# 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 pendingrace + +# This test file tests that a race condition surrounding hot-journal +# rollback that once existed has been resolved. The problem was that +# if, when attempting to upgrade from a SHARED to EXCLUSIVE lock in +# order to roll back a hot journal, a connection failed to take the +# lock, the file-descriptor was left holding a PENDING lock for +# a very short amount of time. In a multi-threaded deployment, this +# could allow a second connection to read the database without rolling +# back the hot journal. +# + +testvfs tvfs +db close +sqlite3 db test.db -vfs tvfs + +# Create a 20 page database using connection [db]. Connection [db] uses +# Tcl VFS wrapper "tvfs", but it is configured to do straight pass-through +# for now. +# +do_execsql_test 1.0 { + PRAGMA cache_size = 5; + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10 + ) + INSERT INTO t1 SELECT hex(randomblob(100)), hex(randomblob(100)) FROM s; +} {} +do_test 1.1a { + set nPg [db one { PRAGMA page_count }] + expr ($nPg==20 || $nPg==21) +} 1 + +# Simulate a crash in another process. This leaves the db with a hot-journal. +# Without the journal the db is corrupt. +# +sqlite3 db2 test.db +do_execsql_test -db db2 1.1 { + PRAGMA cache_size = 5; + BEGIN; + UPDATE t1 SET b=hex(randomblob(100)); +} +db_save +db2 close +proc my_db_restore {} { + forcecopy sv_test.db-journal test.db-journal + + set fd1 [open sv_test.db r] + fconfigure $fd1 -encoding binary -translation binary + set data [read $fd1] + close $fd1 + + set fd1 [open test.db w] + fconfigure $fd1 -encoding binary -translation binary + puts -nonewline $fd1 $data + close $fd1 +} +my_db_restore +do_test 1.2 { + file exists test.db-journal +} {1} + +# Set up connection [db2] to use Tcl VFS wrapper [tvfs2]. Which is configured +# so that the first call to xUnlock() fails. And then all VFS calls thereafter +# fail as well. +# +testvfs tvfs2 +tvfs2 filter xUnlock +tvfs2 script xUnlock +set ::seen_unlock 0 +proc xUnlock {args} { + if {$::seen_unlock==0} { + set ::seen_unlock 1 + tvfs2 ioerr 1 1 + tvfs2 filter {xLock xUnlock} + } + return "" +} +sqlite3 db2 test.db -vfs tvfs2 + +# Configure [tvfs] (used by [db]) so that within the first call to xAccess, +# [db2] attempts to read the db. This causes [db2] to fail to upgrade to +# EXCLUSIVE, leaving it with a PENDING lock. Which it holds on to, +# as the xUnlock() and all subsequent VFS calls fail. +# +tvfs filter xAccess +tvfs script xAccess +set ::seen_access 0 +proc xAccess {args} { + if {$::seen_access==0} { + set ::seen_access 1 + catch { db2 eval { SELECT count(*)+0 FROM t1 } } + breakpoint + } + return "" +} + +# Run an integrity check using [db]. +do_catchsql_test 1.3 { + PRAGMA integrity_check +} {1 {database is locked}} + +db close +db2 close +tvfs delete +tvfs2 delete + +finish_test + + + diff --git a/test/permutations.test b/test/permutations.test index add02d27c..eb173f79b 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -92,12 +92,14 @@ foreach f [glob -nocomplain \ $testdir/../ext/expert/*.test \ $testdir/../ext/lsm1/test/*.test \ $testdir/../ext/recover/*.test \ + $testdir/../ext/rbu/*.test \ ] { lappend alltests $f } foreach f [glob -nocomplain $testdir/../ext/session/*.test] { lappend alltests $f } +unset f if {$::tcl_platform(platform)!="unix"} { set alltests [test_set $alltests -exclude crash.test crash2.test] @@ -114,7 +116,7 @@ set allquicktests [test_set $alltests -exclude { corruptC.test crash.test crash2.test crash3.test crash4.test crash5.test crash6.test crash7.test delete3.test e_fts3.test fts3rnd.test fkey_malloc.test fuzz.test fuzz3.test fuzz_malloc.test in2.test loadext.test - misc7.test mutex2.test notify2.test onefile.test pagerfault2.test + misc7.test mutex2.test onefile.test pagerfault2.test savepoint4.test savepoint6.test select9.test speed1.test speed1p.test speed2.test speed3.test speed4.test speed4p.test sqllimits1.test tkt2686.test thread001.test thread002.test @@ -190,7 +192,7 @@ test_suite "veryquick" -prefix "" -description { that test malloc and IO errors are omitted. } -files [ test_set $allquicktests -exclude *malloc* *ioerr* *fault* *bigfile* *_err* \ - *fts5corrupt* *fts5big* *fts5aj* + *fts5corrupt* *fts5big* *fts5aj* *rbucrash* ] test_suite "shell" -prefix "" -description { @@ -783,6 +785,7 @@ test_suite "inmemory_journal" -description { pager.test exclusive.test jrnlmode.test sync.test misc1.test journal1.test conflict.test crash8.test tkt3457.test io.test journal3.test 8_3_names.test shmlock.test + pendingrace.test pager1.test async4.test corrupt.test filefmt.test pager2.test corrupt5.test corruptA.test pageropt.test @@ -914,6 +917,8 @@ ifcapable threadsafe { autoinstall_test_functions } -shutdown { catch {db close} + catch {db2 close} + catch {db3 close} sqlite3_shutdown install_mutex_counters 0 sqlite3_initialize @@ -1059,7 +1064,7 @@ test_suite "session_strm" -description { test_suite "rbu" -description { RBU tests. } -files [ - test_set [glob -nocomplain $::testdir/../ext/rbu/*.test] -exclude rbu.test + test_set [glob -nocomplain $::testdir/../ext/rbu/*.test] ] test_suite "no_optimization" -description { diff --git a/test/pragma.test b/test/pragma.test index 4fc97648b..9ab332c3f 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -582,6 +582,63 @@ do_test pragma-3.30 { } } {} +# The values stored in indexes must be byte-for-byte identical to the +# values stored in tables. +# +reset_db +do_execsql_test pragma-3.40 { + CREATE TABLE t1( + a INTEGER PRIMARY KEY, + b TEXT COLLATE nocase, + c INT COLLATE nocase, + d TEXT + ); + INSERT INTO t1(a,b,c,d) VALUES + (1, 'one','one','one'), + (2, 'two','two','two'), + (3, 'three','three','three'), + (4, 'four','four','four'), + (5, 'five','five','five'); + CREATE INDEX t1bcd ON t1(b,c,d); + CREATE TABLE t2( + a INTEGER PRIMARY KEY, + b TEXT COLLATE nocase, + c INT COLLATE nocase, + d TEXT + ); + INSERT INTO t2(a,b,c,d) VALUES + (1, 'one','one','one'), + (2, 'two','two','TWO'), + (3, 'three','THREE','three'), + (4, 'FOUR','four','four'), + (5, 'FIVE','FIVE','five'); + CREATE INDEX t2bcd ON t2(b,c,d); + CREATE TEMP TABLE saved_schema AS SELECT name, rootpage FROM sqlite_schema; + PRAGMA writable_schema=ON; + UPDATE sqlite_schema + SET rootpage=(SELECT rootpage FROM saved_schema WHERE name='t2bcd') + WHERE name='t1bcd'; + UPDATE sqlite_schema + SET rootpage=(SELECT rootpage FROM saved_schema WHERE name='t1bcd') + WHERE name='t2bcd'; + PRAGMA Writable_schema=RESET; +} +ifcapable vtab { + do_execsql_test pragma-3.41 { + SELECT integrity_check AS x FROM pragma_integrity_check ORDER BY 1; + } { + {row 2 missing from index t1bcd} + {row 2 missing from index t2bcd} + {row 3 values differ from index t1bcd} + {row 3 values differ from index t2bcd} + {row 4 values differ from index t1bcd} + {row 4 values differ from index t2bcd} + {row 5 values differ from index t1bcd} + {row 5 values differ from index t2bcd} + } +} +db eval {DROP TABLE t2} + # Test modifying the cache_size of an attached database. ifcapable pager_pragmas&&attach { do_test pragma-4.1 { diff --git a/test/pushdown.test b/test/pushdown.test index af5162495..ca816fb21 100644 --- a/test/pushdown.test +++ b/test/pushdown.test @@ -86,6 +86,41 @@ do_test 2.2 { set L } {three} - +# 2022-11-25 dbsqlfuzz crash-3a548de406a50e896c1bf7142692d35d339d697f +# Disable the push-down optimization for compound subqueries if any +# arm of the compound has an incompatible affinity. +# +reset_db +do_execsql_test 3.1 { + CREATE TABLE t0(c0 INT); + INSERT INTO t0 VALUES(0); + CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1_a VALUES(1,'one'); + CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT); + INSERT INTO t1_b VALUES(2,'two'); + CREATE VIEW v0 AS SELECT CAST(t0.c0 AS INTEGER) AS c0 FROM t0; + CREATE VIEW v1(a,b) AS SELECT a, b FROM t1_a UNION ALL SELECT c, 0 FROM t1_b; + SELECT v1.a, quote(v1.b), t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0,v1; +} { + 1 'one' 0 + 2 0 0 +} +do_execsql_test 3.2 { + SELECT a, quote(b), cd FROM ( + SELECT v1.a, v1.b, t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0, v1 + ) WHERE a=2 AND b='0' AND cd=0; +} {} +do_execsql_test 3.3 { + SELECT a, quote(b), cd FROM ( + SELECT v1.a, v1.b, t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0, v1 + ) WHERE a=1 AND b='one' AND cd=0; +} {1 'one' 0} +do_execsql_test 3.4 { + SELECT a, quote(b), cd FROM ( + SELECT v1.a, v1.b, t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0, v1 + ) WHERE a=2 AND b=0 AND cd=0; +} { + 2 0 0 +} finish_test diff --git a/test/quickcheck.test b/test/quickcheck.test new file mode 100644 index 000000000..94016e845 --- /dev/null +++ b/test/quickcheck.test @@ -0,0 +1,34 @@ +# 2023 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 +set testprefix quickcheck + +do_execsql_test 1.0 { + CREATE TABLE t1( + a INTEGER NOT NULL, b INTEGER NOT NULL, c AS (a+1), + PRIMARY KEY(b, a) + ) WITHOUT ROWID; + + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); +} + +do_execsql_test 1.1 { + PRAGMA quick_check +} { + ok +} + +finish_test + diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl index 6efc44b92..2f4e71e8c 100644 --- a/test/releasetest_data.tcl +++ b/test/releasetest_data.tcl @@ -570,6 +570,174 @@ proc main_script {args} { } } +proc main_trscript {args} { + set bMsvc 0 + set nArg [llength $args] + if {$nArg==3} { + if {![is_prefix [lindex $args 0] -msvc 2]} usage + set bMsvc 1 + } elseif {$nArg<2 || $nArg>3} { + usage + } + set config [lindex $args end-1] + set srcdir [lindex $args end] + + set opts [list] ;# OPTS value + set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value + set makeOpts [list] ;# Extra args for [make] + set configOpts [list] ;# Extra args for [configure] + + if {$::tcl_platform(platform)=="windows" || $bMsvc} { + lappend opts -DSQLITE_OS_WIN=1 + } else { + lappend opts -DSQLITE_OS_UNIX=1 + } + + # Figure out if this is a synthetic ndebug or debug configuration. + # + set bRemoveDebug 0 + if {[string match *-ndebug $config]} { + set bRemoveDebug 1 + set config [string range $config 0 end-7] + } + if {[string match *-debug $config]} { + lappend opts -DSQLITE_DEBUG + lappend opts -DSQLITE_EXTRA_IFNULLROW + set config [string range $config 0 end-6] + } + regexp {^(.*)-[0-9]+} $config -> config + + # Ensure that the named configuration exists. + # + if {![info exists ::Configs($config)]} { + puts stderr "No such config: $config" + exit 1 + } + + # Loop through the parameters of the nominated configuration, updating + # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as + # follows: + # + # 1. If the parameter begins with a "*", discard it. + # + # 2. If $bRemoveDebug is set and the parameter is -DSQLITE_DEBUG or + # -DSQLITE_DEBUG=1, discard it + # + # 3. If the parameter begins with "-D", add it to $opts. + # + # 4. If the parameter begins with "--" add it to $configOpts. Unless + # this command is preparing a script for MSVC - then add an + # equivalent to $makeOpts or $opts. + # + # 5. If the parameter begins with "-" add it to $cflags. If in MSVC + # mode and the parameter is an -O option, instead add + # an OPTIMIZATIONS= switch to $makeOpts. + # + # 6. If none of the above apply, add the parameter to $makeOpts + # + foreach param $::Configs($config) { + if {[string range $param 0 0]=="*"} continue + + if {$bRemoveDebug} { + if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1" + || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1" + || $param=="--enable-debug" + } { + continue + } + } + + if {[string range $param 0 1]=="-D"} { + lappend opts $param + continue + } + + if {[string range $param 0 1]=="--"} { + if {$bMsvc} { + switch -- $param { + --disable-amalgamation { + lappend makeOpts USE_AMALGAMATION=0 + } + --disable-shared { + lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 + } + --enable-fts5 { + lappend opts -DSQLITE_ENABLE_FTS5 + } + --enable-shared { + lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 + } + --enable-session { + lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK + lappend opts -DSQLITE_ENABLE_SESSION + } + default { + error "Cannot translate $param for MSVC" + } + } + } else { + lappend configOpts $param + } + + continue + } + + if {[string range $param 0 0]=="-"} { + if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { + lappend makeOpts OPTIMIZATIONS=$level + } else { + lappend cflags $param + } + continue + } + + lappend makeOpts $param + } + + # Some configurations specify -DHAVE_USLEEP=0. For all others, add + # -DHAVE_USLEEP=1. + # + if {[lsearch $opts "-DHAVE_USLEEP=0"]<0} { + lappend opts -DHAVE_USLEEP=1 + } + + if {$bMsvc==0} { + puts {set -e} + puts {} + puts {if [ "$#" -ne 1 ] ; then} + puts { echo "Usage: $0 " } + puts { exit -1 } + puts {fi } + puts "SRCDIR=\"$srcdir\"" + puts {} + puts "TCL=\"[::tcl::pkgconfig get libdir,install]\"" + + puts {if [ ! -f Makefile ] ; then} + puts " \$SRCDIR/configure --with-tcl=\$TCL $configOpts" + puts {fi} + puts {} + puts {OPTS=" -DSQLITE_NO_SYNC=1"} + foreach o $opts { + puts "OPTS=\"\$OPTS $o\"" + } + puts {} + puts "CFLAGS=\"$cflags\"" + puts {} + puts "make \$1 \"CFLAGS=\$CFLAGS\" \"OPTS=\$OPTS\" $makeOpts" + } else { + + set srcdir [file nativename [file normalize $srcdir]] + # set srcdir [string map [list "\\" "\\\\"] $srcdir] + + puts {set TARGET=%1} + set makecmd "nmake /f $srcdir\\Makefile.msc TOP=\"$srcdir\" %TARGET% " + append makecmd "\"CFLAGS=$cflags\" \"OPTS=$opts\" $makeOpts" + + puts "set TMP=%CD%" + puts $makecmd + } +} + proc main_tests {args} { set bNodebug 0 set nArg [llength $args] @@ -648,6 +816,8 @@ if {[string match ${cmd}* configurations] && $n==0} { main_configurations } elseif {[string match ${cmd}* script]} { main_script {*}[lrange $argv 1 end] +} elseif {[string match ${cmd}* trscript]} { + main_trscript {*}[lrange $argv 1 end] } elseif {[string match ${cmd}* platforms] && $n==0} { main_platforms } elseif {[string match ${cmd}* tests]} { diff --git a/test/resetdb.test b/test/resetdb.test index bdb835411..f2d325164 100644 --- a/test/resetdb.test +++ b/test/resetdb.test @@ -255,4 +255,46 @@ do_execsql_test 740 { PRAGMA integrity_check; } {1 ok} +#------------------------------------------------------------------------- +ifcapable utf16 { + reset_db + do_execsql_test 800 { + PRAGMA encoding = 'utf8'; + CREATE TABLE t1(a, b); + PRAGMA encoding; + } {UTF-8} + + db close + sqlite3 db test.db + + sqlite3 db2 test.db + do_execsql_test -db db2 810 { + CREATE TEMP TABLE t2(x); + INSERT INTO t2 VALUES('hello world'); + SELECT name FROM sqlite_schema; + } {t1} + do_test 820 { + db eval "PRAGMA encoding = 'utf16'" + sqlite3_db_config db RESET_DB 1 + } {1} + do_test 830 { + db eval VACUUM + sqlite3_db_config db RESET_DB 0 + } {0} + do_test 840 { + string range [db eval { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('one', 'two'); + PRAGMA encoding; + }] 0 5 + } {UTF-16} + + do_test 850 { + catchsql { SELECT * FROM t1; } db2 + } {1 {attached databases must use the same text encoding as main database}} + do_test 860 { + catchsql { SELECT * FROM t2; } db2 + } {1 {attached databases must use the same text encoding as main database}} +} + finish_test diff --git a/test/returning1.test b/test/returning1.test index 92e10ee9f..326af800e 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -399,5 +399,13 @@ foreach {tn temp} { } } +# 2022-01-13 https://sqlite.org/forum/forumpost/d010a26798 +# +reset_db +do_execsql_test 17.0 { + CREATE TABLE bug(id INTEGER PRIMARY KEY NOT NULL, x); + INSERT INTO bug(id,x) VALUES(20, NULL); + UPDATE bug SET x=NULL WHERE id = 20 RETURNING quote(x), x IS NULL; +} {NULL 1} finish_test diff --git a/test/rowvalue9.test b/test/rowvalue9.test index 611aa3fbb..aee5e7ea4 100644 --- a/test/rowvalue9.test +++ b/test/rowvalue9.test @@ -308,6 +308,46 @@ do_catchsql_test 8.2 { SELECT a FROM t1 NATURAL JOIN t1 WHERE (a,b)> (SELECT 2 IN (SELECT 2,2), 2); } {1 {sub-select returns 2 columns - expected 1}} +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 9.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 1), (1, 2), (2, 2), (2, 3), (3, 3), (3, 4), (4, 4); +} + +do_execsql_test 9.1 { + SELECT * FROM t1 WHERE (a, b) IN ( (3, 3), (2, 2) ); +} { + 2 2 3 3 +} +do_execsql_test 9.2 { + CREATE INDEX i1 ON t1(a); +} + +do_execsql_test 9.4 { + SELECT * FROM t1 WHERE (a, b) IN ( (3, 3), (2, 2) ); +} { + 2 2 3 3 +} +do_eqp_test 9.4e { + SELECT * FROM t1 WHERE (a, b) IN ( (3, 3), (2, 2) ); +} { + *SEARCH t1 USING INDEX i1* +} + +do_execsql_test 9.5 { + CREATE INDEX i2 ON t1(b, a); + SELECT * FROM t1 WHERE (a, b) IN ( (3, 3), (2, 2) ); +} { + 2 2 3 3 +} +do_eqp_test 9.5e { + SELECT * FROM t1 WHERE (a, b) IN ( (3, 3), (2, 2) ); +} { + *SEARCH t1 USING COVERING INDEX i2* +} + finish_test diff --git a/test/scanstatus.test b/test/scanstatus.test index 46249f665..fa00a356b 100644 --- a/test/scanstatus.test +++ b/test/scanstatus.test @@ -36,7 +36,9 @@ proc do_scanstatus_test {tn res} { while {1} { set r [sqlite3_stmt_scanstatus $stmt $idx] if {[llength $r]==0} break - lappend ret {*}$r + foreach v {nLoop nVisit nEst zName zExplain} { + lappend ret $v [dict get $r $v] + } incr idx } @@ -311,11 +313,13 @@ do_execsql_test 5.0 { do_execsql_test 5.1.1 { SELECT count(*) FROM t1 WHERE a IN (SELECT b FROM t1 AS ii) } {2} -do_scanstatus_test 5.1.2 { - nLoop 1 nVisit 10 nEst 10.0 zName t1bc - zExplain {SCAN ii USING COVERING INDEX t1bc} - nLoop 1 nVisit 2 nEst 8.0 zName sqlite_autoindex_t1_1 - zExplain {SEARCH t1 USING COVERING INDEX sqlite_autoindex_t1_1 (a=?)} +ifcapable stat4 { + do_scanstatus_test 5.1.2 { + nLoop 1 nVisit 10 nEst 10.0 zName t1 + zExplain {SCAN ii} + nLoop 1 nVisit 2 nEst 8.0 zName sqlite_autoindex_t1_1 + zExplain {SEARCH t1 USING COVERING INDEX sqlite_autoindex_t1_1 (a=?)} + } } do_execsql_test 5.2.1 { @@ -337,21 +341,23 @@ do_scanstatus_test 5.3.3 { {SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)} } -do_eqp_test 5.4.1 { - SELECT count(*) FROM t1, t2 WHERE y = c; -} { - QUERY PLAN - |--SCAN t1 USING COVERING INDEX t1bc - `--SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?) -} -do_execsql_test 5.4.2 { - SELECT count(*) FROM t1, t2 WHERE y = c; -} {200} -do_scanstatus_test 5.4.3 { - nLoop 1 nVisit 10 nEst 10.0 zName t1bc - zExplain {SCAN t1 USING COVERING INDEX t1bc} - nLoop 10 nVisit 200 nEst 56.0 zName t2xy - zExplain {SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)} +ifcapable stat4 { + do_eqp_test 5.4.1 { + SELECT count(*) FROM t1, t2 WHERE y = c; + } { + QUERY PLAN + |--SCAN t1 + `--SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?) + } + do_execsql_test 5.4.2 { + SELECT count(*) FROM t1, t2 WHERE y = c; + } {200} + do_scanstatus_test 5.4.3 { + nLoop 1 nVisit 10 nEst 10.0 zName t1 + zExplain {SCAN t1} + nLoop 10 nVisit 200 nEst 56.0 zName t2xy + zExplain {SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)} + } } do_eqp_test 5.5.1 { diff --git a/test/scanstatus2.test b/test/scanstatus2.test new file mode 100644 index 000000000..497cbe67d --- /dev/null +++ b/test/scanstatus2.test @@ -0,0 +1,237 @@ +# 2022 December 5 +# +# 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 scanstatus2 + +ifcapable !scanstatus { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(x, y); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t2 VALUES('a', 'b'); + INSERT INTO t2 VALUES('c', 'd'); + INSERT INTO t2 VALUES('e', 'f'); +} + +proc do_zexplain_test {v2 tn sql res} { + db eval $sql + set stmt [db version -last-stmt-ptr] + set idx 0 + set ret [list] + + set cmd sqlite3_stmt_scanstatus + set f [list] + if {$v2} { lappend f complex } + + while {1} { + set r [sqlite3_stmt_scanstatus -flags $f $stmt $idx] + if {[llength $r]==0} break + lappend ret [dict get $r zExplain] + incr idx + } + uplevel [list do_test $tn [list set {} $ret] [list {*}$res]] +} + +proc get_cycles {stmt} { + set r [sqlite3_stmt_scanstatus $stmt -1] + dict get $r nCycle +} + +proc foreach_scan {varname stmt body} { + upvar $varname var + + for {set ii 0} {1} {incr ii} { + set r [sqlite3_stmt_scanstatus -flags complex $stmt $ii] + if {[llength $r]==0} break + array set var $r + uplevel $body + } +} + +proc get_eqp_graph {stmt iPar nIndent} { + set res "" + foreach_scan A $stmt { + if {$A(iParentId)==$iPar} { + set txt $A(zExplain) + if {$A(nCycle)>=0} { + append txt " (nCycle=$A(nCycle))" + } + append res "[string repeat - $nIndent]$txt\n" + append res [get_eqp_graph $stmt $A(iSelectId) [expr $nIndent+2]] + } + } + set res +} + +proc get_graph {stmt} { + set nCycle [get_cycles $stmt] + set res "QUERY (nCycle=$nCycle)\n" + append res [get_eqp_graph $stmt 0 2] +} + +proc do_graph_test {tn sql res} { + db eval $sql + set stmt [db version -last-stmt-ptr] + set graph [string trim [get_graph $stmt]] + + set graph [regsub -all {nCycle=[0-9]+} $graph nCycle=nnn] + uplevel [list do_test $tn [list set {} $graph] [string trim $res]] +} + +proc puts_graph {sql} { + db eval $sql + set stmt [db version -last-stmt-ptr] + puts [string trim [get_graph $stmt]] +} + + +do_zexplain_test 0 1.1 { + SELECT (SELECT a FROM t1 WHERE b=x) FROM t2 WHERE y=2 +} { + {SCAN t2} + {SCAN t1} +} +do_zexplain_test 1 1.2 { + SELECT (SELECT a FROM t1 WHERE b=x) FROM t2 WHERE y=2 +} { + {SCAN t2} + {CORRELATED SCALAR SUBQUERY 1} + {SCAN t1} +} + +do_graph_test 1.3 { + SELECT (SELECT a FROM t1 WHERE b=x) FROM t2 WHERE y=2 +} { +QUERY (nCycle=nnn) +--SCAN t2 (nCycle=nnn) +--CORRELATED SCALAR SUBQUERY 1 (nCycle=nnn) +----SCAN t1 (nCycle=nnn) +} + +do_graph_test 1.4 { + WITH v2(x,y) AS MATERIALIZED ( + SELECT x,y FROM t2 + ) + SELECT * FROM t1, v2 ORDER BY y; +} { +QUERY (nCycle=nnn) +--MATERIALIZE v2 (nCycle=nnn) +----SCAN t2 (nCycle=nnn) +--SCAN v2 (nCycle=nnn) +--SCAN t1 (nCycle=nnn) +--USE TEMP B-TREE FOR ORDER BY (nCycle=nnn) +} + +#------------------------------------------------------------------------- +ifcapable fts5 { + reset_db + do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(a); + INSERT INTO ft VALUES('abc'); + INSERT INTO ft VALUES('def'); + INSERT INTO ft VALUES('ghi'); + } + + do_graph_test 2.1 { + SELECT * FROM ft('def') + } { +QUERY (nCycle=nnn) +--SCAN ft VIRTUAL TABLE INDEX 0:M1 (nCycle=nnn) + } +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE x1(a, b); + CREATE TABLE x2(c, d); + + WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000) + INSERT INTO x1 SELECT i, i FROM s; + INSERT INTO x2 SELECT a, b FROM x1; +} + +do_graph_test 2.1 { + SELECT * FROM x1, x2 WHERE c=+a; +} { +QUERY (nCycle=nnn) +--SCAN x1 (nCycle=nnn) +--CREATE AUTOMATIC INDEX ON x2(c, d) (nCycle=nnn) +--SEARCH x2 USING AUTOMATIC COVERING INDEX (c=?) (nCycle=nnn) +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE TABLE rt1 (id INTEGER PRIMARY KEY, x1, x2); + CREATE TABLE rt2 (id, x1, x2); +} + +do_graph_test 4.1 { + SELECT * FROM rt1, rt2 WHERE rt1.id%2 AND rt2.x1=rt1.x1; +} { +QUERY (nCycle=nnn) +--SCAN rt1 (nCycle=nnn) +--CREATE AUTOMATIC INDEX ON rt2(x1, id, x2) (nCycle=nnn) +--SEARCH rt2 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) +} + +do_graph_test 4.2 { + SELECT rt2.id FROM rt1, rt2 WHERE rt1.id%2 AND rt2.x1=rt1.x1; +} { +QUERY (nCycle=nnn) +--SCAN rt1 (nCycle=nnn) +--CREATE AUTOMATIC INDEX ON rt2(x1, id) (nCycle=nnn) +--SEARCH rt2 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) +} + +do_graph_test 4.3 { + SELECT rt2.id FROM rt1, rt2 WHERE rt1.id%2 AND (rt2.x1+1)=(rt1.x1+1); +} { +QUERY (nCycle=nnn) +--SCAN rt1 (nCycle=nnn) +--SCAN rt2 (nCycle=nnn) +} + +do_graph_test 4.4 { + SELECT rt2.id FROM rt1, rt2 WHERE rt1.id%2 AND rt2.x1=(rt1.x1+1) AND rt2.id>5; +} { +QUERY (nCycle=nnn) +--SCAN rt1 (nCycle=nnn) +--CREATE AUTOMATIC INDEX ON rt2(x1, id) WHERE (nCycle=nnn) +--SEARCH rt2 USING AUTOMATIC PARTIAL COVERING INDEX (x1=?) (nCycle=nnn) +} + +do_graph_test 4.5 { + SELECT v1.cnt FROM rt1, ( + SELECT count(*) AS cnt, rt2.x1 AS x1 FROM rt2 GROUP BY x1 + ) AS v1 WHERE rt1.x1=v1.x1 +} { +QUERY (nCycle=nnn) +--CO-ROUTINE v1 +----SCAN rt2 (nCycle=nnn) +----USE TEMP B-TREE FOR GROUP BY +--SCAN rt1 (nCycle=nnn) +--CREATE AUTOMATIC INDEX ON v1(x1, cnt) (nCycle=nnn) +--SEARCH v1 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) +} + +finish_test + + diff --git a/test/server1.test b/test/server1.test deleted file mode 100644 index c80921709..000000000 --- a/test/server1.test +++ /dev/null @@ -1,180 +0,0 @@ -# 2006 January 09 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# 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 is testing the server mode of SQLite. -# -# This file is derived from thread1.test -# -# $Id: server1.test,v 1.5 2007/08/29 18:20:17 drh Exp $ - - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# Skip this whole file if the server testing code is not enabled -# -if {[llength [info command client_step]]==0 || [sqlite3 -has-codec]} { - finish_test - return -} - -# This test does not work on older PPC Macs due to problems in the -# pthreads library. So skip it. -# -if {$tcl_platform(machine)=="Power Macintosh" && - $tcl_platform(byteOrder)=="bigEndian"} { - finish_test - return -} - -# The sample server implementation does not work right when memory -# management is enabled. -# -ifcapable (memorymanage||mutex_noop) { - finish_test - return -} - -# Create some data to work with -# -do_test server1-1.1 { - execsql { - CREATE TABLE t1(a,b); - INSERT INTO t1 VALUES(1,'abcdefgh'); - INSERT INTO t1 SELECT a+1, b||b FROM t1; - INSERT INTO t1 SELECT a+2, b||b FROM t1; - INSERT INTO t1 SELECT a+4, b||b FROM t1; - SELECT count(*), max(length(b)) FROM t1; - } -} {8 64} - -# Interleave two threads on read access. Then make sure a third -# thread can write the database. In other words: -# -# read-lock A -# read-lock B -# unlock A -# unlock B -# write-lock C -# -do_test server1-1.2 { - client_create A test.db - client_create B test.db - client_create C test.db - client_compile A {SELECT a FROM t1} - client_step A - client_result A -} SQLITE_ROW -do_test server1-1.3 { - client_argc A -} 1 -do_test server1-1.4 { - client_argv A 0 -} 1 -do_test server1-1.5 { - client_compile B {SELECT b FROM t1} - client_step B - client_result B -} SQLITE_ROW -do_test server1-1.6 { - client_argc B -} 1 -do_test server1-1.7 { - client_argv B 0 -} abcdefgh -do_test server1-1.8 { - client_finalize A - client_result A -} SQLITE_OK -do_test server1-1.9 { - client_finalize B - client_result B -} SQLITE_OK -do_test server1-1.10 { - client_compile C {CREATE TABLE t2(x,y)} - client_step C - client_result C -} SQLITE_DONE -do_test server1-1.11 { - client_finalize C - client_result C -} SQLITE_OK -do_test server1-1.12 { - catchsql {SELECT name FROM sqlite_master} - execsql {SELECT name FROM sqlite_master} -} {t1 t2} - - -# Read from table t1. Do not finalize the statement. This -# will leave the lock pending. -# -do_test server1-2.1 { - client_halt * - client_create A test.db - client_compile A {SELECT a FROM t1} - client_step A - client_result A -} SQLITE_ROW - -# Read from the same table from another thread. This is allows. -# -do_test server1-2.2 { - client_create B test.db - client_compile B {SELECT b FROM t1} - client_step B - client_result B -} SQLITE_ROW - -# Write to a different table from another thread. This is allowed -# because in server mode with a shared cache we have table-level locking. -# -do_test server1-2.3 { - client_create C test.db - client_compile C {INSERT INTO t2 VALUES(98,99)} - client_step C - client_result C - client_finalize C - client_result C -} SQLITE_OK - -# But we cannot insert into table t1 because threads A and B have it locked. -# -do_test server1-2.4 { - client_compile C {INSERT INTO t1 VALUES(98,99)} - client_step C - client_result C - client_finalize C - client_result C -} SQLITE_LOCKED -do_test server1-2.5 { - client_finalize B - client_wait B - client_compile C {INSERT INTO t1 VALUES(98,99)} - client_step C - client_result C - client_finalize C - client_result C -} SQLITE_LOCKED - -# Insert into t1 is successful after finishing the other two threads. -do_test server1-2.6 { - client_finalize A - client_wait A - client_compile C {INSERT INTO t1 VALUES(98,99)} - client_step C - client_result C - client_finalize C - client_result C -} SQLITE_OK - -client_halt * -sqlite3_enable_shared_cache 0 -finish_test diff --git a/test/sessionfuzz.c b/test/sessionfuzz.c index d8cc1bebf..f2e4cd5a6 100644 --- a/test/sessionfuzz.c +++ b/test/sessionfuzz.c @@ -699,7 +699,7 @@ static const char zHelp[] = #include #include #ifndef OMIT_ZLIB -#include "zlib.h" +#include #endif /* diff --git a/test/shell1.test b/test/shell1.test index 26426535b..fbbda0348 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -1257,4 +1257,16 @@ select 2,1; select 3,4; 2,1 3,4}} +#---------------------------------------------------------------------------- +# Test cases shell1-10.*: Test that certain static extensions are there. +# +do_test shell1-10.1 { + catchcmd :memory: { +.mode list +.header off +select base64(base64(cast('digity-doo' as blob))), + base85(base85(cast('digity-doo' as blob))); +} +} {0 digity-doo|digity-doo} + finish_test diff --git a/test/shell2.test b/test/shell2.test index 6b5c2bc10..aba04a149 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -202,5 +202,19 @@ do_test shell2-1.4.9 { SELECT writefile('DoNotCare', x'');} } {1 {line 2: cannot use the writefile() function in safe mode}} +# Verify that .clone handles sequence table. +# See https://sqlite.org/forum/forumpost/71ff9e6c4c +do_test shell2-1.4.9 { + forcedelete clone.db + set res [catchcmd :memory: [string trim { + CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT); + INSERT INTO t VALUES (1),(2); +.clone clone.db +.open clone.db + SELECT max(seq) FROM sqlite_sequence;}]] +} {0 {t... done +done +2}} + finish_test diff --git a/test/shell4.test b/test/shell4.test index 068072202..193dc8b69 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -125,6 +125,12 @@ SELECT * FROM t1;}} do_test shell4-2.5 { catchcmd ":memory:" "CREATE TABLE t1(x);\n.trace stdout\nSELECT * FROM t1;" } {0 {SELECT * FROM t1;}} +do_test shell4-2.6 { + catchcmd ":memory:" { +CREATE TABLE t1(x); +.trace --stmt stdout +SELECT * FROM t1;} +} {0 {SELECT * FROM t1;}} } do_test shell4-3.1 { diff --git a/test/shrink.test b/test/shrink.test index bc4a707bf..cee6919e8 100644 --- a/test/shrink.test +++ b/test/shrink.test @@ -17,6 +17,13 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl test_set_config_pagecache 0 0 +if {[sqlite3_memory_used]==0} { + # SQLITE_DEFAULT_MEMSTATUS=0 build. + finish_test + return +} + + unset -nocomplain baseline do_test shrink-1.1 { db eval { diff --git a/test/sort.test b/test/sort.test index d73ecea48..e6da6c6ba 100644 --- a/test/sort.test +++ b/test/sort.test @@ -595,4 +595,36 @@ do_execsql_test 17.1 { SELECT * FROM sqlite_master ORDER BY sql; } {} +# 2022-12-03 Ticket e8b674241947eb3b +# Improve estimates for the cost of sorting relative +# to the cost of doing an index lookup, so as to get +# a better query plan. See the ticket for a deetailed +# example. +# +reset_db +do_execsql_test 18.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<50) + -- increase to 5000 for actual test data ----^^ + INSERT INTO t1(a,b,c) SELECT x, random()%5000, random()%5000 FROM c; + CREATE TABLE t2(d,e,f); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<500) + -- increase to 50000 for actual test data -----^^^ + INSERT INTO t2(d,e,f) SELECT + NULLIF(0, random()%2), random()%5000, random()%5000 + FROM c; + ANALYZE; + UPDATE sqlite_stat1 SET stat='50000' WHERE tbl='t2'; + UPDATE sqlite_stat1 SET stat='5000' WHERE tbl='t1'; + ANALYZE sqlite_schema; +} {} +do_execsql_test 18.2 { + EXPLAIN QUERY PLAN + SELECT a FROM t1 JOIN t2 + WHERE a IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) + AND a=CASE WHEN d IS NOT NULL THEN e ELSE f END + ORDER BY a; +} {/.*SCAN t2.*SEARCH t1.*/} +# ^^^^^^^--^^^^^^^^^--- t2 should be the outer loop. + finish_test diff --git a/test/sort4.test b/test/sort4.test index 13d9a5999..84125885a 100644 --- a/test/sort4.test +++ b/test/sort4.test @@ -8,6 +8,8 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: superslow +# # This file implements regression tests for SQLite library. # # The tests in this file are brute force tests of the multi-threaded diff --git a/test/speed1.test b/test/speed1.test index 86d13ba4a..0dec3ba83 100644 --- a/test/speed1.test +++ b/test/speed1.test @@ -14,12 +14,14 @@ # $Id: speed1.test,v 1.11 2009/04/09 01:23:49 drh Exp $ # +catch {db close} sqlite3_shutdown #sqlite3_config_scratch 29000 1 set old_lookaside [sqlite3_config_lookaside 1000 300] #sqlite3_config_pagecache 1024 10000 set testdir [file dirname $argv0] source $testdir/tester.tcl +reset_db speed_trial_init speed1 # Set a uniform random seed diff --git a/test/speed1p.test b/test/speed1p.test index 6bf7b10e1..fc05067e3 100644 --- a/test/speed1p.test +++ b/test/speed1p.test @@ -16,12 +16,14 @@ # $Id: speed1p.test,v 1.7 2009/04/09 01:23:49 drh Exp $ # +catch { db close } sqlite3_shutdown #sqlite3_config_scratch 29000 1 set old_lookaside [sqlite3_config_lookaside 2048 300] #sqlite3_config_pagecache 1024 11000 set testdir [file dirname $argv0] source $testdir/tester.tcl +reset_db speed_trial_init speed1 sqlite3_memdebug_vfs_oom_test 0 diff --git a/test/sqllimits1.test b/test/sqllimits1.test index 1c3f66b7f..f16208f23 100644 --- a/test/sqllimits1.test +++ b/test/sqllimits1.test @@ -16,6 +16,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix sqllimits1 # Verify that the default per-connection limits are the same as # the compile-time hard limits. @@ -347,7 +348,7 @@ do_test sqllimits1-5.14.6 { } {SQLITE_TOOBIG} ifcapable utf16 { do_test sqllimits1-5.14.7 { - catch {sqlite3_bind_text16 $::STMT 1 $::str1 $np1} res + catch {sqlite3_bind_text16 $::STMT 1 $::str1 [expr $np1+1]} res set res } {SQLITE_TOOBIG} } @@ -923,4 +924,55 @@ do_catchsql_test sqllimits1-18.2 { UNION VALUES(11); } {1 {too many terms in compound SELECT}} +#------------------------------------------------------------------------- +# +reset_db +ifcapable utf16 { + do_execsql_test 19.0 { + PRAGMA encoding = 'utf16'; + } + set bigstr [string repeat abcdefghij 5000] + set bigstr16 [encoding convertto unicode $bigstr] + + do_test 19.1 { + string length $bigstr16 + } {100000} + + do_test 19.2 { + set ::stmt [sqlite3_prepare db "SELECT length( ? )" -1 TAIL] + sqlite3_bind_text16 $::stmt 1 $bigstr16 100000 + sqlite3_step $::stmt + set val [sqlite3_column_int $::stmt 0] + sqlite3_finalize $::stmt + set val + } {50000} + + sqlite3_limit db SQLITE_LIMIT_LENGTH 100000 + + do_test 19.3 { + set ::stmt [sqlite3_prepare db "SELECT length( ? )" -1 TAIL] + sqlite3_bind_text16 $::stmt 1 $bigstr16 100000 + sqlite3_step $::stmt + set val [sqlite3_column_int $::stmt 0] + sqlite3_finalize $::stmt + set val + } {50000} + + sqlite3_limit db SQLITE_LIMIT_LENGTH 99999 + + do_test 19.4 { + set ::stmt [sqlite3_prepare db "SELECT length( ? )" -1 TAIL] + list [catch { sqlite3_bind_text16 $::stmt 1 $bigstr16 100000 } msg] $msg + } {1 SQLITE_TOOBIG} + sqlite3_finalize $::stmt + + sqlite3_limit db SQLITE_LIMIT_LENGTH 100000 + + do_test 19.5 { + set ::stmt [sqlite3_prepare db "SELECT length( ? )" -1 TAIL] + list [catch { sqlite3_bind_text16 $::stmt 1 $bigstr16 100002 } msg] $msg + } {1 SQLITE_TOOBIG} + sqlite3_finalize $::stmt +} + finish_test diff --git a/test/symlink.test b/test/symlink.test index 98b2a32c0..685cae5a4 100644 --- a/test/symlink.test +++ b/test/symlink.test @@ -207,4 +207,32 @@ do_test 4.4.2 { list [file exists x/test.db-wal] [file exists w/test.db-wal] } {1 0} +#------------------------------------------------------------------------- +# Check that extra ".." in a path are ignored. +reset_db +do_execsql_test 5.0 { + CREATE TABLE xyz(x, y, z); + INSERT INTO xyz VALUES(1, 2, 3); +} + +set path [pwd] +set nLink [llength [split $path /]] +set path "[string repeat ../ [expr $nLink*2]]..${path}/test.db" + +sqlite3 db2 $path +do_execsql_test -db db2 5.1 { + SELECT * FROM xyz; +} {1 2 3} +db close + +forcedelete test.db2 +file link test.db2 $path +sqlite3 db2 test.db2 +do_execsql_test -db db2 5.2 { + SELECT * FROM xyz; +} {1 2 3} +forcedelete test.db2 + + + finish_test diff --git a/test/tester.tcl b/test/tester.tcl index 9353b9124..4658590cf 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -552,6 +552,7 @@ if {[info exists cmdlinearg]==0} { } } } + unset -nocomplain a set testdir [file normalize $testdir] set cmdlinearg(TESTFIXTURE_HOME) [pwd] set cmdlinearg(INFO_SCRIPT) [file normalize [info script]] @@ -1062,7 +1063,16 @@ proc append_graph {prefix dxname cxname level} { # proc do_eqp_test {name sql res} { if {[regexp {^\s+QUERY PLAN\n} $res]} { - uplevel do_test $name [list [list query_plan_graph $sql]] [list $res] + + set query_plan [query_plan_graph $sql] + + if {[list {*}$query_plan]==[list {*}$res]} { + uplevel [list do_test $name [list set {} ok] ok] + } else { + uplevel [list \ + do_test $name [list query_plan_graph $sql] $res + ] + } } else { if {[string index $res 0]!="/"} { set res "/*$res*/" @@ -1309,9 +1319,11 @@ proc finalize_testing {} { if {$::cmdlinearg(binarylog)} { vfslog finalize binarylog } - if {$sqlite_open_file_count} { - output2 "$sqlite_open_file_count files were left open" - incr nErr + if {[info exists ::run_thread_tests_called]==0} { + if {$sqlite_open_file_count} { + output2 "$sqlite_open_file_count files were left open" + incr nErr + } } if {[lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]>0 || [sqlite3_memory_used]>0} { @@ -2493,8 +2505,10 @@ proc test_restore_config_pagecache {} { catch {db3 close} sqlite3_shutdown - eval sqlite3_config_pagecache $::old_pagecache_config - unset ::old_pagecache_config + if {[info exists ::old_pagecache_config]} { + eval sqlite3_config_pagecache $::old_pagecache_config + unset ::old_pagecache_config + } sqlite3_initialize autoinstall_test_functions sqlite3 db test.db diff --git a/test/testrunner.tcl b/test/testrunner.tcl index da4efde26..a71b055e4 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -1,87 +1,68 @@ +set dir [pwd] +set testdir [file dirname $argv0] +set saved $argv +set argv [list] +source [file join $testdir testrunner_data.tcl] +source [file join $testdir permutations.test] +set argv $saved +cd $dir + #------------------------------------------------------------------------- # Usage: # proc usage {} { - set a0 testrunner.tcl - - set ::argv [list] - uplevel [list source $::testdir/permutations.test] - - puts stderr "Usage: $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?" - puts stderr "" - puts stderr "where SWITCHES are:" - puts stderr " --jobs NUMBER-OF-JOBS" - puts stderr "" - puts stderr "available PERMUTATION values are:" - set ii 0 - foreach name [lsort [array names ::testspec]] { - if {($ii % 3)==0} { puts -nonewline stderr " " } - puts -nonewline stderr [format "% -22s" $name] - if {($ii % 3)==2} { puts stderr "" } - incr ii - } - puts stderr "" - puts stderr "" - puts stderr "Examples:" - puts stderr " 1) Run the veryquick tests:" - puts stderr " $a0" - puts stderr " 2) Run all test scripts in the source tree:" - puts stderr " $a0 full" - puts stderr " 2) Run the 'memsubsys1' permutation:" - puts stderr " $a0 memsubsys1" - puts stderr " 3) Run all permutations usually run by \[make fulltest\]" - puts stderr " $a0 release" - puts stderr " 4) Run all scripts that match the pattern 'select%':" - puts stderr " $a0 select%" - puts stderr " $a0 all select%" - puts stderr " $a0 full select%" - puts stderr " 5) Run all scripts that are part of the veryquick permutation and match the pattern 'select%':" - puts stderr " $a0 veryquick select%" - puts stderr " 6) Run the 'memsubsys1' permutation, but just those scripts that match 'window%':" - puts stderr " $a0 memsubsys1 window%" - puts stderr " 7) Run all the permutations, but only the scripts that match either 'fts5%' or 'rtree%':" - puts stderr " $a0 release fts5% rtree%" + set a0 [file tail $::argv0] + + puts stderr [string trim [subst -nocommands { +Usage: + $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS? + $a0 PERMUTATION FILE + $a0 njob ?NJOB? + $a0 status + + where SWITCHES are: + --jobs NUMBER-OF-JOBS + --fuzztest + --zipvfs ZIPVFS-SOURCE-DIR + +Interesting values for PERMUTATION are: + + veryquick - a fast subset of the tcl test scripts. This is the default. + full - all tcl test scripts. + all - all tcl test scripts, plus a subset of test scripts rerun + with various permutations. + release - full release test with various builds. + +If no PATTERN arguments are present, all tests specified by the PERMUTATION +are run. Otherwise, each pattern is interpreted as a glob pattern. Only +those tcl tests for which the final component of the filename matches at +least one specified pattern are run. + +If no PATTERN arguments are present, then various fuzztest, threadtest +and other tests are run as part of the "release" permutation. These are +omitted if any PATTERN arguments are specified on the command line. + +If a PERMUTATION is specified and is followed by the path to a Tcl script +instead of a list of patterns, then that single Tcl test script is run +with the specified permutation. + +The --fuzztest option is ignored if the PERMUTATION is "release". Otherwise, +if it is present, then "make -C fuzztest" is run as part of the tests, +where is the directory containing the testfixture binary used to +run the script. + +The "status" and "njob" commands are designed to be run from the same +directory as a running testrunner.tcl script that is running tests. The +"status" command prints a report describing the current state and progress +of the tests. The "njob" command may be used to query or modify the number +of sub-processes the test script uses to run tests. + }]] exit 1 } #------------------------------------------------------------------------- -#------------------------------------------------------------------------- -# The database schema used by the testrunner.db database. -# -set R(schema) { - DROP TABLE IF EXISTS script; - DROP TABLE IF EXISTS msg; - DROP TABLE IF EXISTS malloc; - - CREATE TABLE script( - config TEXT, - filename TEXT, -- full path to test script - slow BOOLEAN, -- true if script is "slow" - state TEXT CHECK( state IN ('ready', 'running', 'done') ), - testfixtureid, -- Id of process that ran script - time INTEGER, -- Time in ms - nerr INTEGER, -- if 'done', the number of errors - ntest INTEGER, -- if 'done', the number of tests - output TEXT, -- full output of test script - PRIMARY KEY(config, filename) - ); - - CREATE TABLE malloc( - id INTEGER PRIMARY KEY, - nmalloc INTEGER, - nbyte INTEGER, - leaker TEXT - ); - - CREATE TABLE msg( - id INTEGER PRIMARY KEY, - msg TEXT - ); -} -#------------------------------------------------------------------------- - #------------------------------------------------------------------------- # Try to estimate a the number of processes to use. # @@ -113,593 +94,791 @@ proc default_njob {} { } #------------------------------------------------------------------------- +#------------------------------------------------------------------------- +# Setup various default values in the global TRG() array. +# +set TRG(dbname) [file normalize testrunner.db] +set TRG(logname) [file normalize testrunner.log] +set TRG(build.logname) [file normalize testrunner_build.log] +set TRG(info_script) [file normalize [info script]] +set TRG(timeout) 10000 ;# Default busy-timeout for testrunner.db +set TRG(nJob) [default_njob] ;# Default number of helper processes +set TRG(patternlist) [list] +set TRG(cmdline) $argv +set TRG(reporttime) 2000 +set TRG(fuzztest) 0 ;# is the fuzztest option present. +set TRG(zipvfs) "" ;# -zipvfs option, if any + +switch -nocase -glob -- $tcl_platform(os) { + *darwin* { + set TRG(platform) osx + set TRG(make) make.sh + set TRG(makecmd) "bash make.sh" + } + *linux* { + set TRG(platform) linux + set TRG(make) make.sh + set TRG(makecmd) "bash make.sh" + } + *win* { + set TRG(platform) win + set TRG(make) make.bat + set TRG(makecmd) make.bat + } + default { + error "cannot determine platform!" + } +} +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------- +# The database schema used by the testrunner.db database. +# +set TRG(schema) { + DROP TABLE IF EXISTS script; + DROP TABLE IF EXISTS config; -set R(dbname) [file normalize testrunner.db] -set R(logname) [file normalize testrunner.log] -set R(info_script) [file normalize [info script]] -set R(timeout) 10000 ;# Default busy-timeout for testrunner. -set R(nJob) [default_njob] ;# Default number of helper processes -set R(leaker) "" ;# Name of first script to leak memory + CREATE TABLE script( + build TEXT DEFAULT '', + config TEXT, + filename TEXT, -- full path to test script + slow BOOLEAN, -- true if script is "slow" + state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ), + time INTEGER, -- Time in ms + output TEXT, -- full output of test script + priority AS ((config='make') + ((config='build')*2) + (slow*4)), + jobtype AS ( + CASE WHEN config IN ('build', 'make') THEN config ELSE 'script' END + ), + PRIMARY KEY(build, config, filename) + ); -set R(patternlist) [list] + CREATE TABLE config( + name TEXT COLLATE nocase PRIMARY KEY, + value + ) WITHOUT ROWID; -set testdir [file dirname $argv0] + CREATE INDEX i1 ON script(state, jobtype); + CREATE INDEX i2 ON script(state, priority); +} +#------------------------------------------------------------------------- -# Parse the command line options. There are two ways to invoke this -# script - to create a helper or coordinator process. If there are -# no helper processes, the coordinator runs test scripts. -# -# To create a helper process: -# -# testrunner.tcl helper ID +#-------------------------------------------------------------------------- +# Check if this script is being invoked to run a single file. If so, +# run it. # -# where ID is an integer greater than 0. The process will create and -# run tests in the "testdir$ID" directory. Helper processes are only -# created by coordinators - there is no need for a user to create -# helper processes manually. +if {[llength $argv]==2 + && ([lindex $argv 0]=="" || [info exists ::testspec([lindex $argv 0])]) + && [file exists [lindex $argv 1]] +} { + set permutation [lindex $argv 0] + set script [file normalize [lindex $argv 1]] + set ::argv [list] + + if {$permutation=="full"} { + + set testdir [file dirname $argv0] + source $::testdir/tester.tcl + unset -nocomplain ::G(isquick) + reset_db + + } elseif {$permutation!="default" && $permutation!=""} { + + if {[info exists ::testspec($permutation)]==0} { + error "no such permutation: $permutation" + } + + array set O $::testspec($permutation) + set ::G(perm:name) $permutation + set ::G(perm:prefix) $O(-prefix) + set ::G(isquick) 1 + set ::G(perm:dbconfig) $O(-dbconfig) + set ::G(perm:presql) $O(-presql) + + rename finish_test helper_finish_test + proc finish_test {} " + uplevel { + $O(-shutdown) + } + helper_finish_test + " + + eval $O(-initialize) + } + + reset_db + source $script + exit +} +#-------------------------------------------------------------------------- + +#-------------------------------------------------------------------------- +# Check if this is the "njob" command: # -# If the first argument is anything other than "helper", then a coordinator -# process is started. See the implementation of the [usage] proc above for -# details. +if {([llength $argv]==2 || [llength $argv]==1) + && [string compare -nocase njob [lindex $argv 0]]==0 +} { + sqlite3 mydb $TRG(dbname) + if {[llength $argv]==2} { + set param [lindex $argv 1] + if {[string is integer $param]==0 || $param<1 || $param>128} { + puts stderr "parameter must be an integer between 1 and 128" + exit 1 + } + + mydb eval { REPLACE INTO config VALUES('njob', $param); } + } + set res [mydb one { SELECT value FROM config WHERE name='njob' }] + mydb close + puts "$res" + exit +} +#-------------------------------------------------------------------------- + +#-------------------------------------------------------------------------- +# Check if this is the "status" command: # -switch -- [lindex $argv 0] { - helper { - set R(helper) 1 - set R(helper_id) [lindex $argv 1] - set argv [list --testdir=testdir$R(helper_id)] +if {[llength $argv]==1 + && [string compare -nocase status [lindex $argv 0]]==0 +} { + + proc display_job {build config filename {tm ""}} { + if {$config=="build"} { + set fname "build: $filename" + set config "" + } elseif {$config=="make"} { + set fname "make: $filename" + set config "" + } else { + set fname [file normalize $filename] + if {[string first $::srcdir $fname]==0} { + set fname [string range $fname [string length $::srcdir]+1 end] + } + } + set dfname [format %-33s $fname] + + set dbuild "" + set dconfig "" + set dparams "" + set dtm "" + if {$build!=""} { set dbuild $build } + if {$config!="" && $config!="full"} { set dconfig $config } + if {$dbuild!="" || $dconfig!=""} { + append dparams "(" + if {$dbuild!=""} {append dparams "build=$dbuild"} + if {$dbuild!="" && $dconfig!=""} {append dparams " "} + if {$dconfig!=""} {append dparams "config=$dconfig"} + append dparams ")" + set dparams [format %-33s $dparams] + } + if {$tm!=""} { + set dtm "\[${tm}ms\]" + } + puts " $dfname $dparams $dtm" } - default { - set R(helper) 0 - set R(helper_id) 0 + sqlite3 mydb $TRG(dbname) + mydb timeout 1000 + mydb eval BEGIN + set cmdline [mydb one { SELECT value FROM config WHERE name='cmdline' }] + set nJob [mydb one { SELECT value FROM config WHERE name='njob' }] + set tm [expr [clock_milliseconds] - [mydb one { + SELECT value FROM config WHERE name='start' + }]] + + set total 0 + foreach s {"" ready running done failed} { set S($s) 0 } + mydb eval { + SELECT state, count(*) AS cnt FROM script GROUP BY 1 + } { + incr S($state) $cnt + incr total $cnt + } + set fin [expr $S(done)+$S(failed)] + if {$cmdline!=""} {set cmdline " $cmdline"} + + set f "" + if {$S(failed)>0} { + set f "$S(failed) FAILED, " + } + puts "Command line: \[testrunner.tcl$cmdline\]" + puts "Jobs: $nJob" + puts "Summary: ${tm}ms, ($fin/$total) finished, ${f}$S(running) running" + + set srcdir [file dirname [file dirname $TRG(info_script)]] + if {$S(running)>0} { + puts "Running: " + set now [clock_milliseconds] + mydb eval { + SELECT build, config, filename, time FROM script WHERE state='running' + ORDER BY time + } { + display_job $build $config $filename [expr $now-$time] + } + } + if {$S(failed)>0} { + puts "Failures: " + mydb eval { + SELECT build, config, filename FROM script WHERE state='failed' + ORDER BY 3 + } { + display_job $build $config $filename + } } + + mydb close + exit } -if {$R(helper)==0} { - for {set ii 0} {$ii < [llength $argv]} {incr ii} { - set a [lindex $argv $ii] - set n [string length $a] - - if {[string range $a 0 0]=="-"} { - if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} { - incr ii - set R(nJob) [lindex $argv $ii] - } else { - usage - } + +#------------------------------------------------------------------------- +# Parse the command line. +# +for {set ii 0} {$ii < [llength $argv]} {incr ii} { + set isLast [expr $ii==([llength $argv]-1)] + set a [lindex $argv $ii] + set n [string length $a] + + if {[string range $a 0 0]=="-"} { + if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} { + incr ii + set TRG(nJob) [lindex $argv $ii] + if {$isLast} { usage } + } elseif {($n>2 && [string match "$a*" --fuzztest]) || $a=="-f"} { + set TRG(fuzztest) 1 + } elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} { + incr ii + set TRG(zipvfs) [lindex $argv $ii] + if {$isLast} { usage } } else { - lappend R(patternlist) [string map {% *} $a] + usage } + } else { + lappend TRG(patternlist) [string map {% *} $a] + } +} +set argv [list] + + + +# This script runs individual tests - tcl scripts or [make xyz] commands - +# in directories named "testdir$N", where $N is an integer. This variable +# contains a list of integers indicating the directories in use. +# +# This variable is accessed only via the following commands: +# +# dirs_nHelper +# Return the number of entries currently in the list. +# +# dirs_freeDir IDIR +# Remove value IDIR from the list. It is an error if it is not present. +# +# dirs_allocDir +# Select a value that is not already in the list. Add it to the list +# and return it. +# +set TRG(dirs_in_use) [list] + +proc dirs_nHelper {} { + global TRG + llength $TRG(dirs_in_use) +} +proc dirs_freeDir {iDir} { + global TRG + set out [list] + foreach d $TRG(dirs_in_use) { + if {$iDir!=$d} { lappend out $d } } + if {[llength $out]!=[llength $TRG(dirs_in_use)]-1} { + error "dirs_freeDir could not find $iDir" + } + set TRG(dirs_in_use) $out +} +proc dirs_allocDir {} { + global TRG + array set inuse [list] + foreach d $TRG(dirs_in_use) { + set inuse($d) 1 + } + for {set iRet 0} {[info exists inuse($iRet)]} {incr iRet} { } + lappend TRG(dirs_in_use) $iRet + return $iRet +} + +set testdir [file dirname $argv0] + +# Check that directory $dir exists. If it does not, create it. If +# it does, delete its contents. +# +proc create_or_clear_dir {dir} { + set dir [file normalize $dir] + catch { file mkdir $dir } + foreach f [glob -nocomplain [file join $dir *]] { + catch { file delete -force $f } + } +} - set argv [list] +proc copy_dir {from to} { + foreach f [glob -nocomplain [file join $from *]] { + catch { file copy -force $f $to } + } +} + +proc build_to_dirname {bname} { + set fold [string tolower [string map {- _} $bname]] + return "testrunner_build_$fold" } -source $testdir/permutations.test #------------------------------------------------------------------------- # Return a list of tests to run. Each element of the list is itself a # list of two elements - the name of a permuations.test configuration # followed by the full path to a test script. i.e.: # -# {CONFIG FILENAME} {CONFIG FILENAME} ... +# {BUILD CONFIG FILENAME} {BUILD CONFIG FILENAME} ... # proc testset_patternlist {patternlist} { + global TRG + + set testset [list] ;# return value set first [lindex $patternlist 0] - if {$first=="all"} { set first "full" } if {$first=="release"} { + set platform $::TRG(platform) + + set patternlist [lrange $patternlist 1 end] + foreach b [trd_builds $platform] { + foreach c [trd_configs $platform $b] { + testset_append testset $b $c $patternlist + } - # The following mirrors the set of test suites invoked by "all.test". - # - set clist { - full - no_optimization memsubsys1 memsubsys2 singlethread - multithread onefile utf16 exclusive persistent_journal - persistent_journal_error no_journal no_journal_error - autovacuum_ioerr no_mutex_try fullmutex journaltest - inmemory_journal pcache0 pcache10 pcache50 pcache90 - pcache100 prepare mmap + if {[llength $patternlist]==0 || $b=="User-Auth"} { + set target testfixture + } else { + set target coretestprogs + } + lappend testset [list $b build $target] } - ifcapable rbu { lappend clist rbu } - if {$::tcl_platform(platform)=="unix"} { - ifcapable !default_autovacuum { - lappend clist autovacuum_crash + + if {[llength $patternlist]==0} { + foreach b [trd_builds $platform] { + foreach e [trd_extras $platform $b] { + lappend testset [list $b make $e] + } } } + + set TRG(fuzztest) 0 ;# ignore --fuzztest option in this case + + } elseif {$first=="all"} { + + set clist [trd_all_configs] set patternlist [lrange $patternlist 1 end] + foreach c $clist { + testset_append testset "" $c $patternlist + } } elseif {[info exists ::testspec($first)]} { set clist $first - set patternlist [lrange $patternlist 1 end] + testset_append testset "" $first [lrange $patternlist 1 end] } elseif { [llength $patternlist]==0 } { - set clist veryquick + testset_append testset "" veryquick $patternlist } else { - set clist full + testset_append testset "" full $patternlist + } + if {$TRG(fuzztest)} { + if {$TRG(platform)=="win"} { error "todo" } + lappend testset [list "" make fuzztest] } - set testset [list] + set testset +} - foreach config $clist { - catch { array unset O } - array set O $::testspec($config) - foreach f $O(-files) { - if {[file pathtype $f]!="absolute"} { - set f [file join $::testdir $f] - } - lappend testset [list $config [file normalize $f]] - } - } +proc testset_append {listvar build config patternlist} { + upvar $listvar lvar + + catch { array unset O } + array set O $::testspec($config) - if {[llength $patternlist]>0} { - foreach t $testset { - set tail [file tail [lindex $t 1]] + foreach f $O(-files) { + if {[llength $patternlist]>0} { + set bMatch 0 foreach p $patternlist { - if {[string match $p $tail]} { - lappend ret $t - break; + if {[string match $p [file tail $f]]} { + set bMatch 1 + break } } + if {$bMatch==0} continue } - } else { - set ret $testset - } - set ret + if {[file pathtype $f]!="absolute"} { + set f [file join $::testdir $f] + } + lappend lvar [list $build $config $f] + } } + #-------------------------------------------------------------------------- proc r_write_db {tcl} { - global R - - sqlite3_test_control_pending_byte 0x010000 - sqlite3 db $R(dbname) - db timeout $R(timeout) - db eval { BEGIN EXCLUSIVE } - + trdb eval { BEGIN EXCLUSIVE } uplevel $tcl - - db eval { COMMIT } - db close + trdb eval { COMMIT } } -proc make_new_testset {} { - global R - - set tests [testset_patternlist $R(patternlist)] - r_write_db { - db eval $R(schema) - foreach t $tests { - foreach {c s} $t {} - set slow 0 - - set fd [open $s] - for {set ii 0} {$ii<100 && ![eof $fd]} {incr ii} { - set line [gets $fd] - if {[string match -nocase *testrunner:* $line]} { - regexp -nocase {.*testrunner:(.*)} $line -> properties - foreach p $properties { - if {$p=="slow"} { set slow 1 } - } - } - } - close $fd - - db eval { - INSERT INTO script(config, filename, slow, state) - VALUES ($c, $s, $slow, 'ready') - } - } - } -} - -# Find the next job in the database and mark it as 'running'. Then return -# a list consisting of the -# -# CONFIG FILENAME +# Obtain a new job to be run by worker $iJob (an integer). A job is +# returned as a three element list: # -# pair for the test. +# {$build $config $file} # -proc get_next_test {} { - global R - set myid $R(helper_id) +proc r_get_next_job {iJob} { + global T + + if {($iJob%2)} { + set orderby "ORDER BY priority ASC" + } else { + set orderby "ORDER BY priority DESC" + } r_write_db { set f "" set c "" - db eval { - SELECT config, filename FROM script WHERE state='ready' - ORDER BY - (slow * (($myid+1) % 2)) DESC, - config!='full', - config, - filename - LIMIT 1 - } { + trdb eval " + SELECT build, config, filename + FROM script + WHERE state='ready' + $orderby LIMIT 1 + " { + set b $build set c $config set f $filename } if {$f!=""} { - db eval { - UPDATE script SET state='running', testfixtureid=$myid - WHERE (config, filename) = ($c, $f) + set tm [clock_milliseconds] + set T($iJob) $tm + trdb eval { + UPDATE script SET state='running', time=$tm + WHERE (build, config, filename) = ($b, $c, $f) } } } if {$f==""} { return "" } - list $c $f + list $b $c $f } -proc r_testname {config filename} { - set name [file tail $filename] - if {$config!="" && $config!="full" && $config!="veryquick"} { - set name "$config-$name" - } - return $name -} +#rename r_get_next_job r_get_next_job_r +#proc r_get_next_job {iJob} { +# puts [time { set res [r_get_next_job_r $iJob] }] +# set res +#} -proc r_set_test_result {config filename ms nerr ntest output} { - global R +proc make_new_testset {} { + global TRG - set f [r_testname $config $filename] - if {$nerr==0} { - set msg "$f... Ok" - } else { - set msg "$f... FAILED - $nerr errors of $ntest tests" - } - append msg " (${ms}ms)" - if {$R(helper)} { - append msg " (helper $R(helper_id))" - } + set tests [testset_patternlist $TRG(patternlist)] - sqlite3_shutdown - set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1] - set nByte [sqlite3_memory_used] - if {($nByte>0 || $nMalloc>0) && $R(leaker)==""} { - set R(leaker) $f + if {$TRG(zipvfs)!=""} { + source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl] + set tests [concat $tests [zipvfs_testrunner_testset]] } r_write_db { - db eval { - UPDATE script - SET state='done', output=$output, nerr=$nerr, ntest=$ntest, time=$ms - WHERE (config, filename)=($config, $filename); - INSERT INTO msg(msg) VALUES ($msg); - } - } -} + trdb eval $TRG(schema) + set nJob $TRG(nJob) + set cmdline $TRG(cmdline) + set tm [clock_milliseconds] + trdb eval { REPLACE INTO config VALUES('njob', $nJob ); } + trdb eval { REPLACE INTO config VALUES('cmdline', $cmdline ); } + trdb eval { REPLACE INTO config VALUES('start', $tm ); } -set R(iNextMsg) 1 -proc r_get_messages {{db ""}} { - global R + foreach t $tests { + foreach {b c s} $t {} + set slow 0 - sqlite3_test_control_pending_byte 0x010000 + if {$c!="make" && $c!="build"} { + set fd [open $s] + for {set ii 0} {$ii<100 && ![eof $fd]} {incr ii} { + set line [gets $fd] + if {[string match -nocase *testrunner:* $line]} { + regexp -nocase {.*testrunner:(.*)} $line -> properties + foreach p $properties { + if {$p=="slow"} { set slow 1 } + if {$p=="superslow"} { set slow 2 } + } + } + } + close $fd + } - if {$db==""} { - sqlite3 rgmhandle $R(dbname) - set dbhandle rgmhandle - $dbhandle timeout $R(timeout) - } else { - set dbhandle $db - } + if {$c=="make" && $b==""} { + # --fuzztest option + set slow 1 + } - $dbhandle transaction { - set next $R(iNextMsg) - set ret [$dbhandle eval {SELECT msg FROM msg WHERE id>=$next}] - set R(iNextMsg) [$dbhandle one {SELECT COALESCE(max(id), 0)+1 FROM msg}] - } + if {$c=="veryquick"} { + set c "" + } - if {$db==""} { - rgmhandle close - } + set state ready + if {$b!="" && $c!="build"} { + set state "" + } - set ret + trdb eval { + INSERT INTO script(build, config, filename, slow, state) + VALUES ($b, $c, $s, $slow, $state) + } + } + } } -# This is called after all tests have been run to write the leaked memory -# report into the malloc table of testrunner.db. -# -proc r_memory_report {} { - global R +proc script_input_ready {fd iJob b c f} { + global TRG + global O + global T + + if {[eof $fd]} { + set ::done 1 + fconfigure $fd -blocking 1 + set state "done" + set rc [catch { close $fd } msg] + if {$rc} { + puts "FAILED: $b $c $f" + set state "failed" + } - sqlite3_shutdown + set tm [expr [clock_milliseconds] - $T($iJob)] - set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1] - set nByte [sqlite3_memory_used] - set id $R(helper_id) - set leaker $R(leaker) + puts $TRG(log) "### $b ### $c ### $f ${tm}ms ($state)" + puts $TRG(log) [string trim $O($iJob)] - r_write_db { - db eval { - INSERT INTO malloc(id, nMalloc, nByte, leaker) - VALUES($id, $nMalloc, $nByte, $leaker) + r_write_db { + set output $O($iJob) + trdb eval { + UPDATE script SET output = $output, state=$state, time=$tm + WHERE (build, config, filename) = ($b, $c, $f) + } + if {$state=="done" && $c=="build"} { + trdb eval { + UPDATE script SET state = 'ready' WHERE (build, state)==($b, '') + } + } } - } -} - -#-------------------------------------------------------------------------- -# -set ::R_INSTALL_PUTS_WRAPPER { - proc puts_sts_wrapper {args} { - set n [llength $args] - if {$n==1 || ($n==2 && [string first [lindex $args 0] -nonewline]==0)} { - uplevel puts_into_caller $args - } else { - # A channel was explicitly specified. - uplevel puts_sts_original $args + dirs_freeDir $iJob + launch_some_jobs + incr ::wakeup + } else { + set rc [catch { gets $fd line } res] + if {$rc} { + puts "ERROR $res" + } + if {$res>=0} { + append O($iJob) "$line\n" } } - rename puts puts_sts_original - proc puts {args} { uplevel puts_sts_wrapper $args } -} -proc r_install_puts_wrapper {} $::R_INSTALL_PUTS_WRAPPER -proc r_uninstall_puts_wrapper {} { - rename puts "" - rename puts_sts_original puts } -proc slave_test_script {script} { - - # Create the interpreter used to run the test script. - interp create tinterp - - # Populate some global variables that tester.tcl expects to see. - foreach {var value} [list \ - ::argv0 $::argv0 \ - ::argv {} \ - ::SLAVE 1 \ - ] { - interp eval tinterp [list set $var $value] - } - - # The alias used to access the global test counters. - tinterp alias set_test_counter set_test_counter +proc dirname {ii} { + return "testdir$ii" +} - # Set up an empty ::cmdlinearg array in the slave. - interp eval tinterp [list array set ::cmdlinearg [array get ::cmdlinearg]] +proc launch_another_job {iJob} { + global TRG + global O + global T - # Set up the ::G array in the slave. - interp eval tinterp [list array set ::G [array get ::G]] - interp eval tinterp [list set ::G(runner.tcl) 1] + set testfixture [info nameofexec] + set script $TRG(info_script) - interp eval tinterp $::R_INSTALL_PUTS_WRAPPER - tinterp alias puts_into_caller puts_into_caller + set dir [dirname $iJob] + create_or_clear_dir $dir - # Load the various test interfaces implemented in C. - load_testfixture_extensions tinterp + set O($iJob) "" + + set job [r_get_next_job $iJob] + if {$job==""} { return 0 } - # Run the test script. - set rc [catch { interp eval tinterp $script } msg opt] - if {$rc} { - puts_into_caller $msg - puts_into_caller [dict get $opt -errorinfo] - incr ::TC(errors) - } + foreach {b c f} $job {} - # Check if the interpreter call [run_thread_tests] - if { [interp eval tinterp {info exists ::run_thread_tests_called}] } { - set ::run_thread_tests_called 1 - } + if {$c=="build"} { + set testdir [file dirname $TRG(info_script)] + set srcdir [file dirname $testdir] + set builddir [build_to_dirname $b] + create_or_clear_dir $builddir - # Delete the interpreter used to run the test script. - interp delete tinterp -} + if {$b=="Zipvfs"} { + set script [zipvfs_testrunner_script] + } else { + set cmd [info nameofexec] + lappend cmd [file join $testdir releasetest_data.tcl] + lappend cmd trscript + if {$TRG(platform)=="win"} { lappend cmd -msvc } + lappend cmd $b $srcdir + set script [exec {*}$cmd] + } -proc slave_test_file {zFile} { - set tail [file tail $zFile] + set fd [open [file join $builddir $TRG(make)] w] + puts $fd $script + close $fd - # Remember the value of the shared-cache setting. So that it is possible - # to check afterwards that it was not modified by the test script. - # - ifcapable shared_cache { set scs [sqlite3_enable_shared_cache] } + puts "Launching build \"$b\" in directory $builddir..." + set target coretestprogs + if {$b=="User-Auth"} { set target testfixture } - # Run the test script in a slave interpreter. - # - unset -nocomplain ::run_thread_tests_called - reset_prng_state - set ::sqlite_open_file_count 0 - set time [time { slave_test_script [list source $zFile] }] - set ms [expr [lindex $time 0] / 1000] + set cmd "$TRG(makecmd) $target" + set dir $builddir - r_install_puts_wrapper + } elseif {$c=="make"} { + if {$b==""} { + if {$f!="fuzztest"} { error "corruption in testrunner.db!" } + # Special case - run [make fuzztest] + set makedir [file dirname $testfixture] + if {$TRG(platform)=="win"} { + error "how?" + } else { + set cmd [list make -C $makedir fuzztest] + } + } else { + set builddir [build_to_dirname $b] + copy_dir $builddir $dir + set cmd "$TRG(makecmd) $f" + } + } else { + if {$b==""} { + set testfixture [info nameofexec] + } else { + set tail testfixture + if {$TRG(platform)=="win"} { set tail testfixture.exe } + set testfixture [file normalize [file join [build_to_dirname $b] $tail]] + } - # Test that all files opened by the test script were closed. Omit this - # if the test script has "thread" in its name. The open file counter - # is not thread-safe. - # - if {[info exists ::run_thread_tests_called]==0} { - do_test ${tail}-closeallfiles { expr {$::sqlite_open_file_count>0} } {0} + if {$c=="valgrind"} { + set testfixture "valgrind -v --error-exitcode=1 $testfixture" + set ::env(OMIT_MISUSE) 1 + } + set cmd [concat $testfixture [list $script $c $f]] } - set ::sqlite_open_file_count 0 - # Test that the global "shared-cache" setting was not altered by - # the test script. - # - ifcapable shared_cache { - set res [expr {[sqlite3_enable_shared_cache] == $scs}] - do_test ${tail}-sharedcachesetting [list set {} $res] 1 - } + set pwd [pwd] + cd $dir + set fd [open "|$cmd 2>@1" r] + cd $pwd + set pid [pid $fd] - # Add some info to the output. - # - output2 "Time: $tail $ms ms" - show_memstats + fconfigure $fd -blocking false + fileevent $fd readable [list script_input_ready $fd $iJob $b $c $f] + unset -nocomplain ::env(OMIT_MISUSE) - r_uninstall_puts_wrapper - return $ms + return 1 } -proc puts_into_caller {args} { - global R - if {[llength $args]==1} { - append R(output) [lindex $args 0] - append R(output) "\n" - } else { - append R(output) [lindex $args 1] - } -} +proc one_line_report {} { + global TRG -#------------------------------------------------------------------------- -# -proc r_final_report {} { - global R - - sqlite3_test_control_pending_byte 0x010000 - sqlite3 db $R(dbname) - - db timeout $R(timeout) - - set errcode 0 - - # Create the text log file. This is just the concatenation of the - # 'output' column of the database for every script that was run. - set fd [open $R(logname) w] - db eval {SELECT output FROM script ORDER BY config!='full',config,filename} { - puts $fd $output - } - close $fd - - # Check if any scripts reported errors. If so, print one line noting - # how many errors, and another identifying the scripts in which they - # occured. Or, if no errors occurred, print out "no errors at all!". - sqlite3 db $R(dbname) - db timeout $R(timeout) - db eval { SELECT sum(nerr) AS nerr, sum(ntest) AS ntest FROM script } { } - puts "$nerr errors from $ntest tests." - if {$nerr>0} { - db eval { SELECT config, filename FROM script WHERE nerr>0 } { - lappend errlist [r_testname $config $filename] - } - puts "Errors in: $errlist" - set errcode 1 - } + set tm [expr [clock_milliseconds] - $TRG(starttime)] + set tm [format "%d" [expr int($tm/1000.0 + 0.5)]] - # Check if any scripts were not run or did not finish. Print out a - # line identifying them if there are any. - set errlist [list] - db eval { SELECT config, filename FROM script WHERE state!='done' } { - lappend errlist [r_testname $config $filename] - } - if {$errlist!=[list]} { - puts "Tests DID NOT FINISH (crashed?): $errlist" - set errcode 1 + foreach s {ready running done failed} { + set v($s,build) 0 + set v($s,make) 0 + set v($s,script) 0 } - set bLeak 0 - db eval { - SELECT id, nmalloc, nbyte, leaker FROM malloc - WHERE nmalloc>0 OR nbyte>0 - } { - if {$id==0} { - set line "This process " - } else { - set line "Helper $id " + r_write_db { + trdb eval { + SELECT state, jobtype, count(*) AS cnt + FROM script + GROUP BY state, jobtype + } { + set v($state,$jobtype) $cnt + if {[info exists t($jobtype)]} { + incr t($jobtype) $cnt + } else { + set t($jobtype) $cnt + } } - append line "leaked $nbyte byte in $nmalloc allocations" - if {$leaker!=""} { append line " (perhaps in [file tail $leaker])" } - puts $line - set bLeak 1 } - if {$bLeak==0} { - puts "No leaks - all allocations freed." - } - - db close - puts "Test database is $R(dbname)" - puts "Test log file is $R(logname)" - if {$errcode} { - puts "This test has FAILED." + set text "" + foreach j [array names t] { + set fin [expr $v(done,$j) + $v(failed,$j)] + lappend text "$j ($fin/$t($j)) f=$v(failed,$j) r=$v(running,$j)" } - return $errcode -} - -if {$R(helper)==0} { - make_new_testset -} + if {[info exists TRG(reportlength)]} { + puts -nonewline "[string repeat " " $TRG(reportlength)]\r" + } + set report "${tm}s: [join $text { }]" + set TRG(reportlength) [string length $report] + if {[string length $report]<80} { + puts -nonewline "$report\r" + flush stdout + } else { + puts $report + } -set R(nHelperRunning) 0 -if {$R(helper)==0 && $R(nJob)>1} { - cd $cmdlinearg(TESTFIXTURE_HOME) - for {set ii 1} {$ii <= $R(nJob)} {incr ii} { - set cmd "[info nameofexec] $R(info_script) helper $ii 2>@1" - puts "Launching helper $ii ($cmd)" - set chan [open "|$cmd" r] - fconfigure $chan -blocking false - fileevent $chan readable [list r_helper_readable $ii $chan] - incr R(nHelperRunning) - } - cd $cmdlinearg(testdir) + after $TRG(reporttime) one_line_report } -proc r_helper_readable {id chan} { - set data [gets $chan] - if {$data!=""} { puts "helper $id:$data" } - if {[eof $chan]} { - puts "helper $id is finished" - incr ::R(nHelperRunning) -1 - close $chan +proc launch_some_jobs {} { + global TRG + r_write_db { + set nJob [trdb one { SELECT value FROM config WHERE name='njob' }] + } + while {[dirs_nHelper]<$nJob} { + set iDir [dirs_allocDir] + if {0==[launch_another_job $iDir]} { + dirs_freeDir $iDir + break; + } } } -if {$R(nHelperRunning)==0} { - while { ""!=[set t [get_next_test]] } { - set R(output) "" - set TC(count) 0 - set TC(errors) 0 - - foreach {config filename} $t {} - - array set O $::testspec($config) - set ::G(perm:name) $config - set ::G(perm:prefix) $O(-prefix) - set ::G(isquick) 1 - set ::G(perm:dbconfig) $O(-dbconfig) - set ::G(perm:presql) $O(-presql) +proc run_testset {} { + global TRG + set ii 0 - eval $O(-initialize) - set ms [slave_test_file $filename] - eval $O(-shutdown) + set TRG(starttime) [clock_milliseconds] + set TRG(log) [open $TRG(logname) w] - unset -nocomplain ::G(perm:sqlite3_args) - unset ::G(perm:name) - unset ::G(perm:prefix) - unset ::G(perm:dbconfig) - unset ::G(perm:presql) + launch_some_jobs + # launch_another_job $ii - r_set_test_result $config $filename $ms $TC(errors) $TC(count) $R(output) - - if {$R(helper)==0} { - foreach msg [r_get_messages] { puts $msg } - } + one_line_report + while {[dirs_nHelper]>0} { + after 500 {incr ::wakeup} + vwait ::wakeup } + close $TRG(log) + one_line_report - # Tests are finished - write a record into testrunner.db describing - # any memory leaks. - r_memory_report - -} else { - set TTT 0 - sqlite3 db $R(dbname) - db timeout $R(timeout) - while {$R(nHelperRunning)>0} { - after 250 { incr TTT } - vwait TTT - foreach msg [r_get_messages db] { puts $msg } + r_write_db { + set nErr [trdb one {SELECT count(*) FROM script WHERE state='failed'}] + if {$nErr>0} { + puts "$nErr failures:" + trdb eval { + SELECT build, config, filename FROM script WHERE state='failed' + } { + puts "FAILED: $build $config $filename" + } + } } - db close -} -set errcode 0 -if {$R(helper)==0} { - set errcode [r_final_report] + puts "\nTest database is $TRG(dbname)" + puts "Test log is $TRG(logname)" } -exit $errcode +sqlite3 trdb $TRG(dbname) +trdb timeout $TRG(timeout) +set tm [lindex [time { make_new_testset }] 0] +puts "built testset in [expr $tm/1000]ms.." +run_testset +trdb close +#puts [pwd] diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl new file mode 100644 index 000000000..f74ee146c --- /dev/null +++ b/test/testrunner_data.tcl @@ -0,0 +1,143 @@ + + + +namespace eval trd { + variable tcltest + variable extra + variable all_configs + + + # Tcl tests to run for various builds. + # + set tcltest(linux.Fast-One) veryquick + set tcltest(linux.Debug-One) veryquick + set tcltest(linux.Debug-Two) veryquick + set tcltest(linux.Have-Not) veryquick + set tcltest(linux.Secure-Delete) veryquick + set tcltest(linux.Unlock-Notify) veryquick + set tcltest(linux.User-Auth) veryquick + set tcltest(linux.Update-Delete-Limit) veryquick + set tcltest(linux.Extra-Robustness) veryquick + set tcltest(linux.Device-Two) veryquick + set tcltest(linux.No-lookaside) veryquick + set tcltest(linux.Devkit) veryquick + set tcltest(linux.Apple) veryquick + set tcltest(linux.Sanitize) veryquick + set tcltest(linux.Device-One) all + set tcltest(linux.Default) all_plus_autovacuum_crash + set tcltest(linux.Valgrind) valgrind + + set tcltest(osx.Locking-Style) veryquick + set tcltest(osx.Have-Not) veryquick + set tcltest(osx.Apple) all + + set tcltest(win.Stdcall) veryquick + set tcltest(win.Have-Not) veryquick + set tcltest(win.Windows-Memdebug) veryquick + set tcltest(win.Windows-Win32Heap) veryquick + set tcltest(win.Default) full + + # Extra [make xyz] tests that should be run for various builds. + # + set extra(linux.Check-Symbols) checksymbols + set extra(linux.Fast-One) {fuzztest sourcetest} + set extra(linux.Debug-One) {fuzztest sourcetest mptest} + set extra(linux.Debug-Two) {fuzztest sourcetest} + set extra(linux.Have-Not) {fuzztest sourcetest} + set extra(linux.Secure-Delete) {fuzztest sourcetest} + set extra(linux.Unlock-Notify) {fuzztest sourcetest} + set extra(linux.Update-Delete-Limit) {fuzztest sourcetest} + set extra(linux.Extra-Robustness) {fuzztest sourcetest} + set extra(linux.Device-Two) {fuzztest sourcetest threadtest} + set extra(linux.No-lookaside) {fuzztest sourcetest} + set extra(linux.Devkit) {fuzztest sourcetest} + set extra(linux.Apple) {fuzztest sourcetest} + set extra(linux.Sanitize) {fuzztest sourcetest} + set extra(linux.Default) {fuzztest sourcetest threadtest} + + set extra(osx.Apple) {fuzztest threadtest} + set extra(osx.Have-Not) {fuzztest sourcetest} + set extra(osx.Locking-Style) {mptest fuzztest sourcetest} + + set extra(win.Default) mptest + set extra(win.Stdcall) {fuzztest sourcetest} + set extra(win.Windows-Memdebug) {fuzztest sourcetest} + set extra(win.Windows-Win32Heap) {fuzztest sourcetest} + set extra(win.Have-Not) {fuzztest sourcetest} + + # The following mirrors the set of test suites invoked by "all.test". + # + set all_configs { + full no_optimization memsubsys1 memsubsys2 singlethread + multithread onefile utf16 exclusive persistent_journal + persistent_journal_error no_journal no_journal_error + autovacuum_ioerr no_mutex_try fullmutex journaltest + inmemory_journal pcache0 pcache10 pcache50 pcache90 + pcache100 prepare mmap + } +} + + +#------------------------------------------------------------------------- +proc trd_import {} { + uplevel { + variable ::trd::tcltest + variable ::trd::extra + variable ::trd::all_configs + } +} + +proc trd_builds {platform} { + trd_import + + set klist [lsort -uniq [concat \ + [array names tcltest ${platform}.*] \ + [array names extra ${platform}.*] \ + ]] + if {[llength $klist]==0} { + error "no such platform: $platform" + } + + set ret "" + foreach k $klist { + foreach {p c} [split $k "."] {} + lappend ret $c + } + set ret +} + +proc trd_configs {platform build} { + trd_import + + set clist [list] + + if {[info exists tcltest($platform.$build)]} { + set clist $tcltest($platform.$build) + if {$clist=="all"} { + set clist $all_configs + } elseif {$clist=="all_plus_autovacuum_crash"} { + set clist [concat $all_configs autovacuum_crash] + } + } + + set clist +} + +proc trd_extras {platform build} { + trd_import + + set elist [list] + if {[info exists extra($platform.$build)]} { + set elist $extra($platform.$build) + } + + set elist +} + +proc trd_all_configs {} { + trd_import + set all_configs +} + + + diff --git a/test/tkt-99378177930f87bd.test b/test/tkt-99378177930f87bd.test new file mode 100644 index 000000000..868f36c3a --- /dev/null +++ b/test/tkt-99378177930f87bd.test @@ -0,0 +1,179 @@ +# 2022-11-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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# +# This file implements tests to verify that the enhancement +# request documented by ticket 99378177930f87bd is working. +# +# The enhancement is that if an aggregate query with a GROUP BY clause +# uses subexpressions in the arguments to aggregate functions that are +# also columns of an index, then the values are pulled from the index +# rather than being recomputed. This has the potential to make some +# indexed queries works as if the index were covering. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test tkt-99378-100 { + CREATE TABLE t1(a INT, b TEXT, c INT, d INT); + INSERT INTO t1(a,b,c,d) VALUES + (1, '{"x":1}', 12, 3), + (1, '{"x":2}', 4, 5), + (1, '{"x":1}', 6, 11), + (2, '{"x":1}', 22, 3), + (2, '{"x":2}', 4, 5), + (3, '{"x":1}', 6, 7); + CREATE INDEX t1x ON t1(d, a, b->>'x', c); +} {} +do_execsql_test tkt-99378-110 { + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} { + 1 2 1 16 12 + 2 2 1 26 22 + 3 1 1 6 6 +} + +# The proof that the index on the expression is being used is in the +# fact that the byte code contains no "Function" opcodes. In other words, +# the ->> operator (which is implemented by a function) is never invoked. +# Instead, the b->>'x' value is pulled out of the index. +# +do_execsql_test tkt-99378-120 { + EXPLAIN + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} {~/Function/} + + +do_execsql_test tkt-99378-130 { + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY +a; +} { + 1 2 1 16 12 + 2 2 1 26 22 + 3 1 1 6 6 +} +do_execsql_test tkt-99378-140 { + EXPLAIN + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY +a; +} {~/Function/} + +do_execsql_test tkt-99378-200 { + DROP INDEX t1x; + CREATE INDEX t1x ON t1(a, d, b->>'x', c); +} +do_execsql_test tkt-99378-210 { + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} { + 1 2 1 16 12 + 2 2 1 26 22 + 3 1 1 6 6 +} +do_execsql_test tkt-99378-220 { + EXPLAIN + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} {~/Function/} +do_execsql_test tkt-99378-230 { + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} { + 1 2 1 16 12 + 2 2 1 26 22 + 3 1 1 6 6 +} +do_execsql_test tkt-99378-240 { + EXPLAIN + SELECT a, + SUM(1) AS t1, + SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, + SUM(c) AS t3, + SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 + FROM t1 + WHERE d BETWEEN 0 and 10 + GROUP BY a; +} {~/Function/} + +# 2022-12-20 dbsqlfuzz a644e70d7683a7ca59c71861a153c1dccf8850b9 +# +do_execsql_test tkt-99378-300 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INT); + CREATE INDEX i1 ON t1(a,a=a); + INSERT INTO t1 VALUES(1),(2),(3),(4); + SELECT * FROM t1 NATURAL JOIN t1 + WHERE a==1 + OR ( + (SELECT avg( + (SELECT sum((SELECT 1 FROM t1 NATURAL RIGHT JOIN t1 WHERE a=a)))) AS xyz + ) + AND a==2 + ); +} {1 2} +do_execsql_test tkt-99378-310 { + DROP INDEX i1; + SELECT * FROM t1 NATURAL JOIN t1 + WHERE a==1 + OR ( + (SELECT avg( + (SELECT sum((SELECT 1 FROM t1 NATURAL RIGHT JOIN t1 WHERE a=a)))) AS xyz + ) + AND a==2 + ); +} {1 2} + +finish_test diff --git a/test/trustschema1.test b/test/trustschema1.test index dba954f14..8edaf8051 100644 --- a/test/trustschema1.test +++ b/test/trustschema1.test @@ -247,5 +247,16 @@ do_execsql_test 3.131 { SELECT * FROM t2; } {} +# 2023-01-09 https://sqlite.org/forum/forumpost/c88a671ad083d153 +# +do_execsql_test 4.1 { + PRAGMA trusted_schema=OFF; + CREATE VIEW test41(x) AS SELECT json_extract('{"a":123}','$.a'); + SELECT * FROM test41; +} 123 +do_execsql_test 4.2 { + PRAGMA trusted_schema=ON; + SELECT * FROM test41; +} 123 finish_test diff --git a/test/unhex.test b/test/unhex.test new file mode 100644 index 000000000..f41e906a8 --- /dev/null +++ b/test/unhex.test @@ -0,0 +1,102 @@ +# 2023 January 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 [file join $testdir tester.tcl] + +set testprefix unhex + + +foreach {tn hex} { + 1 0000 + 2 FFFF + 3 0123456789ABCDEF +} { + do_execsql_test 1.$tn.1 { + SELECT hex( unhex( $hex ) ); + } $hex + + do_execsql_test 1.$tn.2 { + SELECT hex( unhex( lower( $hex ) ) ); + } $hex +} + +do_execsql_test 2.0 { + SELECT typeof( unhex('') ), length( unhex('') ); +} {blob 0} + +foreach {tn hex} { + 1 ABC + 2 hello + 3 123456x7 + 4 0xff +} { + do_execsql_test 2.$tn { + SELECT unhex( $hex ) IS NULL; + } 1 +} + +do_catchsql_test 3.0 { + SELECT unhex(); +} {1 {wrong number of arguments to function unhex()}} +do_catchsql_test 3.1 { + SELECT unhex('ABCD', '1234', ''); +} {1 {wrong number of arguments to function unhex()}} + +#-------------------------------------------------------------------------- +# Test the 2-argument version. +# +foreach {tn hex} { + 1 "FFFF ABCD" + 2 "FFFF ABCD" + 3 "FFFFABCD " + 4 " FFFFABCD" + 5 "--FFFF AB- -CD- " + 6 "--" + 7 " --" +} { + set out "" + foreach x [split $hex ""] { + if {[string is xdigit $x]} { append out $x } + } + + do_execsql_test 5.$tn.1 { + SELECT hex( unhex($hex, ' -') ); + } [list $out] +} + +do_execsql_test 6.0 { + SELECT typeof( unhex(' ', ' -') ), length( unhex('-', ' -') ); +} {blob 0} + + +do_execsql_test 6.1 " + SELECT hex( unhex('\u0E01ABCD\u0E02', '\uE01\uE02') ) +" {ABCD} +do_execsql_test 6.2 " + SELECT typeof( unhex('\u0E01ABCD\u0E02', '\uE03\uE02') ) +" {null} +do_execsql_test 6.3 " + SELECT hex( unhex('\u0E01AB CD\uE02\uE01', '\uE01 \uE02') ) +" {ABCD} + +#-------------------------------------------------------------------------- +# Test that if either argument is NULL, the returned value is also NULL. +# +do_execsql_test 6.4.1 { SELECT typeof(unhex(NULL)) } {null} +do_execsql_test 6.4.2 { SELECT typeof(unhex(NULL, ' ')) } {null} +do_execsql_test 6.4.3 { SELECT typeof(unhex('1234', NULL)) } {null} + + +finish_test + + diff --git a/test/unionall.test b/test/unionall.test index 7783c04a6..32fc76543 100644 --- a/test/unionall.test +++ b/test/unionall.test @@ -408,37 +408,39 @@ do_execsql_test 8.1 { SELECT a, b FROM t1_a UNION ALL SELECT c, c FROM t1_b UNION ALL SELECT e, f FROM t1_c; -} + SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1; +} {1 one 0 {} 4 four 0 {} 2 2 0 {} 5 5 0 {} 3 three 0 {} 6 six 0 {}} + optimization_control db all 1 do_execsql_test 8.2 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; } {2 2 0 {}} do_execsql_test 8.3 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; -} {} +} {2 2 0 {}} do_execsql_test 8.4 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; -} {2 2 0 {}} +} {} optimization_control db query-flattener,push-down 0 do_execsql_test 8.5 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; } {2 2 0 {}} do_execsql_test 8.6 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; -} {} +} {2 2 0 {}} do_execsql_test 8.7 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; -} {2 2 0 {}} +} {} optimization_control db all 0 do_execsql_test 8.8 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; } {2 2 0 {}} do_execsql_test 8.9 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; -} {} +} {2 2 0 {}} do_execsql_test 8.10 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; -} {2 2 0 {}} +} {} finish_test diff --git a/test/view.test b/test/view.test index b0b6da268..90c9c9909 100644 --- a/test/view.test +++ b/test/view.test @@ -775,5 +775,30 @@ do_catchsql_test view-29.1 { SELECT name FROM sqlite_schema ORDER BY name; } {0 {t1 t2}} +#------------------------------------------------------------------------- +# 2022-12-11. https://sqlite.org/src/info/679ed6a2 +# +# 2022-12-14 change: If the AS SELECT of a VIEW is a compound where +# the datatypes on each arm of the compound are different, then the +# datatype of the overall column is BLOB (ANY). +# +reset_db +do_execsql_test view-30.0 { + CREATE TABLE t0(a INT, b TEXT); + + INSERT INTO t0 VALUES(1,'one'); + + CREATE VIEW t1 AS SELECT a, b FROM t0 UNION ALL SELECT 2, 2; + CREATE VIEW t2(a,b) AS SELECT a, b FROM t0 UNION ALL SELECT 2, 2; +} + +ifcapable schema_pragmas { + do_execsql_test view-30.1 { + PRAGMA table_info = t1; + } { 0 a INT 0 {} 0 1 b BLOB 0 {} 0 } + do_execsql_test view-30.2 { + PRAGMA table_info = t2; + } { 0 a INT 0 {} 0 1 b BLOB 0 {} 0 } +} finish_test diff --git a/test/vt02.c b/test/vt02.c index 40bd6fcb3..f83fc9af9 100644 --- a/test/vt02.c +++ b/test/vt02.c @@ -177,6 +177,8 @@ #include #endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Forward declarations */ typedef struct vt02_vtab vt02_vtab; typedef struct vt02_cur vt02_cur; @@ -999,6 +1001,10 @@ static void vt02CoreInit(sqlite3 *db){ sqlite3_create_module(db, "vt02pkabcd", &vt02Module, (void*)zPkABCDSchema); } +#else +# define vt02CoreInit(db) +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + #ifdef TH3_VERSION static void vt02_init(th3state *p, int iDb, char *zArg){ vt02CoreInit(th3dbPointer(p, iDb)); diff --git a/test/where.test b/test/where.test index b559f5ee9..0a8cfd572 100644 --- a/test/where.test +++ b/test/where.test @@ -545,6 +545,7 @@ do_test where-6.1 { CREATE INDEX t3acb ON t3(a,c,b); INSERT INTO t3 SELECT w, 101-w, y FROM t1; SELECT count(*), sum(a), sum(b), sum(c) FROM t3; + ANALYZE; } } {100 5050 5050 348550} do_test where-6.2 { @@ -1622,16 +1623,85 @@ do_execsql_test where-28.1 { ifcapable vtab { do_execsql_test where-29.1 { SELECT DISTINCT 'xyz' FROM pragma_cache_size - WHERE rowid OR abs(0) - ORDER BY - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1; + WHERE rowid OR abs(0) + ORDER BY + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1; } {xyz} } +# 2023-01-30 +# Tests case for the query planner performance issue reported by +# https://sqlite.org/forum/forumpost/1d571c0296 +# +# The fix was to adjust the cost of computing an automatic index for +# ephemeral tables, to help ensure that they are generated if they are +# needed. The test case below only looks at the query plan. But 12x +# improved performance has been verified by populating the "raw" table +# with 100K rows of random data and running actual speed tests. +# +do_test where-30.1 { + unset -nocomplain res + set res {} + db eval {CREATE TABLE raw(country,date,total,delta, UNIQUE(country,date));} + db eval { + EXPLAIN QUERY PLAN + WITH + -- Find the country and min/max date + init(country, date, fin) AS (SELECT country, min(date), max(date) + FROM raw WHERE total > 0 GROUP BY country), + + -- Generate the date stream for each country + src(country, date) AS (SELECT raw.country, raw.date + FROM raw JOIN init i on raw.country = i.country AND raw.date > i.date + ORDER BY raw.country, raw.date), + + -- Generate the x & y for each entry in the country/date stream + vals(country, date, x, y) AS (SELECT src.country, src.date, + julianday(raw.date) - julianday(src.date), log(delta+1) + FROM src JOIN raw on raw.country = src.country + AND raw.date > date(src.date,'-7 days') + AND raw.date <= src.date AND delta >= 0), + + -- Accumulate the data we need + sums(country, date, x2, x, n, xy, y) AS (SELECT country, date, + sum(x*x*1.0), sum(x*1.0), sum(1.0), sum(x*y*1.0), sum(y*1.0) + FROM vals GROUP BY 1, 2), + + -- use these to calculate to divisor for the inverse matrix + mult(country, date, m) AS (SELECT country, date, 1.0/(x2 * n - x * x) + FROM sums), + + -- Build the inverse matrix + inv(country, date, a,b,c,d) AS (SELECT mult.country, mult.date, n * m, + -x * m, -x * m, x2 * m + FROM mult JOIN sums on sums.country=mult.country + AND mult.date=sums.date), + + -- Calculate the coefficients for the least squares fit + fit(country, date, a, b) AS (SELECT inv.country, inv.date, + a * xy + b * y, c * xy + d * y + FROM inv + JOIN mult on mult.country = inv.country AND mult.date = inv.date + JOIN sums on sums.country = mult.country AND sums.date = mult.date + ) + SELECT *, nFin/nPrev - 1 AS growth, log(2)/log(nFin/nPrev) AS doubling + FROM (SELECT f.*, exp(b) - 1 AS nFin, exp(a* (-1) + b) - 1 AS nPrev + FROM fit f JOIN init i on i.country = f.country + AND f.date <= date(i.fin,'-3 days')) + WHERE nPrev > 0 AND nFin > 0; + } { + if {$parent!=0} continue + if {![string match SCAN* $detail]} continue + lappend res SCAN + } + set res +} {SCAN} +# ^^^^^^-- there should only be one top-level table scan in the query plan. + finish_test diff --git a/test/whereL.test b/test/whereL.test index b6f86fc2d..c3bdcb8f3 100644 --- a/test/whereL.test +++ b/test/whereL.test @@ -189,5 +189,24 @@ do_execsql_test 610 { AND A.RunYearMonth = B.RunYearMonth; } {4 202004 4 202004 5 202004 5 202004} +# 2023-02-10 https://sqlite.org/forum/forumpost/0a539c76db3b9e29 +# The original constant propagation implementation caused a performance +# regression. Because "abs(v)" was rewritten into "abs(1)" it no longer +# matches the indexed column and the index is not used. +# +reset_db +do_execsql_test 700 { + CREATE TABLE t1(v INTEGER); + WITH RECURSIVE c(x) AS (VALUES(-10) UNION ALL SELECT x+1 FROM c WHERE x<10) + INSERT INTO t1(v) SELECT x FROM c; + CREATE INDEX idx ON t1( abs(v) ); + SELECT v FROM t1 WHERE abs(v)=1 and v=1; +} 1 +do_eqp_test 710 { + SELECT v FROM t1 WHERE abs(v)=1 and v=1; +} { + QUERY PLAN + `--SEARCH t1 USING INDEX idx (=?) +} finish_test diff --git a/test/windowB.test b/test/windowB.test index 0a9aef724..32193a378 100644 --- a/test/windowB.test +++ b/test/windowB.test @@ -410,4 +410,77 @@ do_execsql_test 10.3 { one,two } +ifcapable json1&&vtab { +if {[permutation]!="no_optimization"} { + + do_execsql_test 11.0 { + SELECT value FROM json_each('[1,2,3,4,5]'); + } {1 2 3 4 5} + + do_execsql_test 11.1 { + SELECT key, value FROM json_each('[1,2,3,4,5]'); + } {0 1 1 2 2 3 3 4 4 5} + do_execsql_test 11.2 { + SELECT rowid, value FROM json_each('[1,2,3,4,5]'); + } {0 1 1 2 2 3 3 4 4 5} + + do_execsql_test 11.3 { + SELECT sum(value) OVER (ORDER BY rowid) FROM json_each('[1,2,3,4,5]') + } {1 3 6 10 15} + + do_execsql_test 11.4 { + SELECT sum(value) OVER ( + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) FROM json_each('[1,2,3,4,5]') + } {1 3 6 10 15} + + do_eqp_test 11.5 { + SELECT sum(value) OVER ( + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) FROM json_each('[1,2,3,4,5]') + } { + QUERY PLAN + |--CO-ROUTINE (subquery-xxxxxx) + | `--SCAN json_each VIRTUAL TABLE INDEX 1: + `--SCAN (subquery-xxxxxx) + } + + do_eqp_test 11.6 { + SELECT sum(value) OVER (ORDER BY rowid) FROM json_each('[1,2,3,4,5]') + } { + QUERY PLAN + |--CO-ROUTINE (subquery-xxxxxx) + | `--SCAN json_each VIRTUAL TABLE INDEX 1: + `--SCAN (subquery-xxxxxx) + } + + do_eqp_test 11.8 { + SELECT sum(value) OVER (ORDER BY rowid DESC) FROM json_each('[1,2,3,4,5]') + } { + QUERY PLAN + |--CO-ROUTINE (subquery-xxxxxx) + | |--SCAN json_each VIRTUAL TABLE INDEX 1: + | `--USE TEMP B-TREE FOR ORDER BY + `--SCAN (subquery-xxxxxx) + } + + do_execsql_test 11.9 { + SELECT sum(value) OVER (ORDER BY rowid DESC) FROM json_each('[1,2,3,4,5]') + } {5 9 12 14 15} + + do_execsql_test 11.10 { + SELECT sum(value) OVER (ORDER BY value ASC) FROM json_each('[2,1,4,3,5]') + } {1 3 6 10 15} + do_eqp_test 11.11 { + SELECT sum(value) OVER (ORDER BY value ASC) FROM json_each('[2,1,4,3,5]') + } { + QUERY PLAN + |--CO-ROUTINE (subquery-xxxxxx) + | |--SCAN json_each VIRTUAL TABLE INDEX 1: + | `--USE TEMP B-TREE FOR ORDER BY + `--SCAN (subquery-xxxxxx) + } +}} + finish_test + diff --git a/test/with3.test b/test/with3.test index 1aed92a99..85889453a 100644 --- a/test/with3.test +++ b/test/with3.test @@ -89,7 +89,7 @@ ifcapable analyze { SELECT * FROM cnt, y1 WHERE i=a } [string map {"\n " \n} { QUERY PLAN - |--MATERIALIZE cnt + |--CO-ROUTINE cnt | |--SETUP | | `--SCAN CONSTANT ROW | `--RECURSIVE STEP @@ -103,7 +103,7 @@ ifcapable analyze { SELECT * FROM cnt, y1 WHERE i=a } [string map {"\n " \n} { QUERY PLAN - |--MATERIALIZE cnt + |--CO-ROUTINE cnt | |--SETUP | | `--SCAN CONSTANT ROW | `--RECURSIVE STEP @@ -125,7 +125,7 @@ do_eqp_test 3.2.2 { WHERE c.id=w2.pk AND c.id=w1.pk; } { QUERY PLAN - |--MATERIALIZE c + |--CO-ROUTINE c | |--SETUP | | |--SCAN CONSTANT ROW | | `--SCALAR SUBQUERY xxxxxx diff --git a/test/with6.test b/test/with6.test index 20e518b01..91d64fa29 100644 --- a/test/with6.test +++ b/test/with6.test @@ -87,7 +87,7 @@ do_eqp_test 131 { (SELECT x FROM c LIMIT 5) AS c3; } { QUERY PLAN - |--MATERIALIZE c1 + |--CO-ROUTINE c1 | |--CO-ROUTINE c | | `--SCAN 2 CONSTANT ROWS | `--SCAN c @@ -123,7 +123,7 @@ do_eqp_test 141 { (SELECT x FROM c LIMIT 7) AS c3; } { QUERY PLAN - |--MATERIALIZE c1 + |--CO-ROUTINE c1 | |--MATERIALIZE c | | `--SCAN 2 CONSTANT ROWS | `--SCAN c @@ -151,7 +151,7 @@ do_eqp_test 151 { (SELECT x FROM c LIMIT 7) AS c3; } { QUERY PLAN - |--MATERIALIZE c1 + |--CO-ROUTINE c1 | |--MATERIALIZE c | | `--SCAN 2 CONSTANT ROWS | `--SCAN c @@ -228,7 +228,7 @@ do_eqp_test 211 { SELECT y FROM t2 ORDER BY y; } { QUERY PLAN - |--MATERIALIZE c1 + |--CO-ROUTINE c1 | |--CO-ROUTINE c | | `--SCAN 3 CONSTANT ROWS | `--SCAN c @@ -318,5 +318,56 @@ do_eqp_test 331 { `--SCAN t3 } +# 2023-02-01 +# https://sqlite.org/forum/forumpost/1d571c02963355ed +# +# Just because a CTE is used more than once, does not mean it should be +# marked with M10d_Yes and hence prohibited from participating in the +# query flattening optimization. +# +reset_db +db eval { + CREATE TABLE raw(country,date,total,delta, UNIQUE(country,date)); +} +do_eqp_test 400 { + with recursive + init(country, date, fin) AS (SELECT country, min(date), max(date) FROM raw WHERE total > 0 GROUP BY country), + src(country, date) AS (SELECT raw.country, raw.date + FROM raw JOIN init i on raw.country = i.country AND raw.date > i.date + ORDER BY raw.country, raw.date), + vals(country, date, x, y) AS (SELECT src.country, src.date, julianday(raw.date) - julianday(src.date), log(delta+1) + FROM src JOIN raw on raw.country = src.country AND raw.date > date(src.date,'-7 days') AND raw.date <= src.date AND delta >= 0), + sums(country, date, x2, x, n, xy, y) AS (SELECT country, date, sum(x*x*1.0), sum(x*1.0), sum(1.0), sum(x*y*1.0), sum(y*1.0) FROM vals GROUP BY 1, 2), + mult(country, date, m) AS (SELECT country, date, 1.0/(x2 * n - x * x) FROM sums), + inv(country, date, a,b,c,d) AS (SELECT mult.country, mult.date, n * m, -x * m, -x * m, x2 * m + FROM mult JOIN sums on sums.country=mult.country AND mult.date=sums.date), + fit(country, date, a, b) AS (SELECT inv.country, inv.date, a * xy + b * y, c * xy + d * y + FROM inv + JOIN mult on mult.country = inv.country AND mult.date = inv.date + JOIN sums on sums.country = mult.country AND sums.date = mult.date + ) + SELECT *, nFin/nPrev - 1 AS growth, log(2)/log(nFin/nPrev) AS doubling + FROM (SELECT f.*, exp(b) - 1 AS nFin, exp(a* (-1) + b) - 1 AS nPrev + FROM fit f JOIN init i on i.country = f.country AND f.date <= date(i.fin,'-3 days')) + WHERE nPrev > 0 AND nFin > 0; +} { + QUERY PLAN + |--MATERIALIZE sums + | |--MATERIALIZE src + | | |--MATERIALIZE init + | | | `--SCAN raw USING INDEX sqlite_autoindex_raw_1 + | | |--SCAN i + | | |--SEARCH raw USING COVERING INDEX sqlite_autoindex_raw_1 (country=? AND date>?) + | | `--USE TEMP B-TREE FOR ORDER BY + | |--SCAN src + | |--SEARCH raw USING INDEX sqlite_autoindex_raw_1 (country=? AND date>? AND date set addstatic 1 set linemacros 0 set useapicall 0 +set enable_recover 0 set srcdir tsrc for {set i 0} {$i<[llength $argv]} {incr i} { set x [lindex $argv $i] - if {[regexp {^-?-nostatic$} $x]} { + if {[regexp {^-?-enable-recover$} $x]} { + set enable_recover 1 + } elseif {[regexp {^-?-nostatic$} $x]} { set addstatic 0 } elseif {[regexp {^-?-linemacros(?:=([01]))?$} $x ma ulm]} { if {$ulm == ""} {set ulm 1} @@ -78,7 +81,9 @@ close $in # Open the output file and write a header comment at the beginning # of the file. # -set out [open sqlite3.c w] +set fname sqlite3.c +if {$enable_recover} { set fname sqlite3r.c } +set out [open $fname w] # Force the output to use unix line endings, even on Windows. fconfigure $out -translation lf set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1] @@ -162,6 +167,7 @@ foreach hdr { vxworks.h wal.h whereInt.h + sqlite3recover.h } { set available_hdr($hdr) 1 } @@ -325,7 +331,7 @@ proc copy_file {filename} { # used subroutines first in order to help the compiler find # inlining opportunities. # -foreach file { +set flist { sqliteInt.h os_common.h ctime.c @@ -441,7 +447,11 @@ foreach file { sqlite3session.c fts5.c stmt.c -} { +} +if {$enable_recover} { + lappend flist sqlite3recover.c dbdata.c +} +foreach file $flist { copy_file $srcdir/$file } diff --git a/tool/mksqlite3h.tcl b/tool/mksqlite3h.tcl index 9078a1575..bd579c28b 100644 --- a/tool/mksqlite3h.tcl +++ b/tool/mksqlite3h.tcl @@ -40,9 +40,16 @@ set TOP [lindex $argv 0] # set useapicall 0 +# Include sqlite3recover.h? +# +set enable_recover 0 + if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} { set useapicall 1 } +if {[lsearch -regexp [lrange $argv 1 end] {^-+enable-recover}] != -1} { + set enable_recover 1 +} # Get the SQLite version number (ex: 3.6.18) from the $TOP/VERSION file. # @@ -84,6 +91,9 @@ set filelist [subst { $TOP/ext/session/sqlite3session.h $TOP/ext/fts5/fts5.h }] +if {$enable_recover} { + lappend filelist "$TOP/ext/recover/sqlite3recover.h" +} # These are the functions that accept a variable number of arguments. They # always need to use the "cdecl" calling convention even when another calling diff --git a/tool/showdb.c b/tool/showdb.c index 611e603fe..0e99331d7 100644 --- a/tool/showdb.c +++ b/tool/showdb.c @@ -1096,6 +1096,18 @@ static void ptrmap_coverage_report(const char *zDbName){ } } +/* +** Check the range validity for a page number. Print an error and +** exit if the page is out of range. +*/ +static void checkPageValidity(int iPage){ + if( iPage<1 || iPage>g.mxPage ){ + fprintf(stderr, "Invalid page number %d: valid range is 1..%d\n", + iPage, g.mxPage); + exit(1); + } +} + /* ** Print a usage comment */ @@ -1184,10 +1196,12 @@ int main(int argc, char **argv){ continue; } iStart = strtoul(azArg[i], &zLeft, 0); + checkPageValidity(iStart); if( zLeft && strcmp(zLeft,"..end")==0 ){ iEnd = g.mxPage; }else if( zLeft && zLeft[0]=='.' && zLeft[1]=='.' ){ iEnd = strtol(&zLeft[2], 0, 0); + checkPageValidity(iEnd); }else if( zLeft && zLeft[0]=='b' ){ int ofst, nByte, hdrSize; unsigned char *a; diff --git a/tool/showwal.c b/tool/showwal.c index bc7406dc1..84e4afa13 100644 --- a/tool/showwal.c +++ b/tool/showwal.c @@ -512,6 +512,18 @@ static void decode_btree_page( } } +/* +** Check the range validity for a page number. Print an error and +** exit if the page is out of range. +*/ +static void checkPageValidity(int iPage, int mxPage){ + if( iPage<1 || iPage>mxPage ){ + fprintf(stderr, "Invalid page number %d: valid range is 1..%d\n", + iPage, mxPage); + exit(1); + } +} + int main(int argc, char **argv){ struct stat sbuf; unsigned char zPgSz[4]; @@ -559,10 +571,12 @@ int main(int argc, char **argv){ continue; } iStart = strtol(argv[i], &zLeft, 0); + checkPageValidity(iStart, mxFrame); if( zLeft && strcmp(zLeft,"..end")==0 ){ iEnd = mxFrame; }else if( zLeft && zLeft[0]=='.' && zLeft[1]=='.' ){ iEnd = strtol(&zLeft[2], 0, 0); + checkPageValidity(iEnd, mxFrame); }else if( zLeft && zLeft[0]=='b' ){ i64 ofst; int nByte, hdrSize; diff --git a/tool/vdbe_profile.tcl b/tool/vdbe_profile.tcl index a0dc99ec3..b7240e356 100644 --- a/tool/vdbe_profile.tcl +++ b/tool/vdbe_profile.tcl @@ -66,6 +66,8 @@ foreach stmt $allstmt { puts "********************************************************************" puts [string trim $sql($stmt)] puts "Execution count: $cnt($stmt)" + set tcx 0 + set ttx 0 for {set i 0} {[info exists stat($stmt,$i)]} {incr i} { foreach {cx tx detail} $stat($stmt,$i) break if {$cx==0} { @@ -74,7 +76,11 @@ foreach stmt $allstmt { set ax [expr {$tx/$cx}] } puts [format {%8d %12d %12d %4d %s} $cx $tx $ax $i $detail] + incr tcx $cx + incr ttx $tx } + set tax [expr {$tcx>0?$ttx/$tcx:0}] + puts [format {%8d %12d %12d TOTAL} $tcx $ttx $tax] } puts "********************************************************************" puts "OPCODES:" diff --git a/tool/warnings.sh b/tool/warnings.sh index a839d235b..60d2b4210 100644 --- a/tool/warnings.sh +++ b/tool/warnings.sh @@ -11,7 +11,13 @@ if uname | grep -i openbsd ; then else # Use these for testing on Linux and Mac OSX: WARNING_OPTS="-Wshadow -Wall -Wextra -pedantic-errors -Wno-long-long" - WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra" + gccvers=`gcc -v 2>&1 | grep '^gcc version'` + if test "$gccvers" '<' 'gcc version 6' + then + WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra" + else + WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra -Wimplicit-fallthrough=0" + fi fi rm -f sqlite3.c From d95b3f427e57e5fe71a786f1021519655158506d Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 23 Jan 2023 14:50:14 -0500 Subject: [PATCH 091/340] adjust test for new attach behavior introduced in upstream check-in a02da71f --- test/sqlcipher-core.test | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index da28d56b1..45c3588d1 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -242,10 +242,18 @@ db2 close file delete -force test.db file delete -force test2.db -# attach an empty encrypted database as the first op -# on a keyed database and verify different -# salts but same keys (because derivation of the key spec -# has not occured yet) +# attach an empty encrypted database as the first operation on a keyed database. Verify +# that the new database has the same salt as the original. +# +# HISTORICAL NOTE: The original behavior of SQLCipher under these conditions +# was that the databases would have different salts but the same keys. This was because +# derivation of the key spec would not have occurred yet. However, upstream check-in +# https://sqlite.org/src/info/a02da71f3a80dd8e changed this behavior by +# forcing a read of the main database schema during the attach operation. +# This causes the main database to be opened and the key derivation logic to fire which +# reads the salt. Thus the current behavior of this test should now be identical +# to the previous attach-empty-database-with-default-key. + setup test.db "'testkey'" do_test attach-empty-database-with-default-key-first-op { sqlite_orig db test.db @@ -267,7 +275,7 @@ do_test attach-empty-database-with-default-key-first-op { } db2] lappend rc [string equal [hexio_read test.db 0 16] [hexio_read test2.db 0 16]] -} {{ok 1} 0} +} {{ok 1} 1} db close db2 close file delete -force test.db From c1b174250bf0a1f26b7c398c7f5d41d24514aed8 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Sun, 12 Mar 2023 16:00:35 -0400 Subject: [PATCH 092/340] Snapshot of upstream SQLite 3.41.1 --- VERSION | 2 +- configure | 18 ++++++------ manifest | 54 +++++++++++++++++----------------- manifest.uuid | 2 +- src/build.c | 1 + src/expr.c | 42 +++++++++++++++++++------- src/func.c | 12 ++++++++ src/pragma.c | 17 +++++++++++ src/resolve.c | 12 +++----- src/select.c | 16 ++++++---- src/sqliteInt.h | 1 + src/trigger.c | 2 +- src/vdbe.c | 3 +- src/where.c | 3 ++ src/whereexpr.c | 2 +- test/bloom1.test | 16 +++++++++- test/countofview.test | 23 +++++++++++++++ test/gencol1.test | 54 ++++++++++++++++++++++++++++++++++ test/join2.test | 49 ++++++++++++++++++++++++++++++ test/returning1.test | 19 +++++++++--- test/subtype1.test | 20 ++++++++++++- test/tkt-99378177930f87bd.test | 15 ++++++++++ test/window1.test | 16 ++++++++++ 23 files changed, 326 insertions(+), 73 deletions(-) diff --git a/VERSION b/VERSION index 371986f6d..d26daf738 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.41.0 +3.41.1 diff --git a/configure b/configure index 56e74f5ec..f92c35702 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.69 for sqlite 3.41.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.41.1. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.41.0' -PACKAGE_STRING='sqlite 3.41.0' +PACKAGE_VERSION='3.41.1' +PACKAGE_STRING='sqlite 3.41.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1470,7 +1470,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.41.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.41.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1535,7 +1535,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.41.0:";; + short | recursive ) echo "Configuration of sqlite 3.41.1:";; esac cat <<\_ACEOF @@ -1665,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.41.0 +sqlite configure 3.41.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2084,7 +2084,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.41.0, which was +It was created by sqlite $as_me 3.41.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12457,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.41.0, which was +This file was extended by sqlite $as_me 3.41.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12523,7 +12523,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.41.0 +sqlite config.status 3.41.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/manifest b/manifest index f17d7340c..c390011be 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.41.0 -D 2023-02-21T18:09:37.622 +C Version\s3.41.1 +D 2023-03-10T12:13:52.703 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in 594f07f8829020a45d825234edf14046544b5bf6bc0057a9d5b83b58686b1c46 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 F Makefile.msc b0026b61d1242add00c28dbe169271a763fffcb3af6762bcdbeb20c3efe6a69f F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e -F VERSION 413ec94920a487ae32c9a2a8819544d690662d6f7c7ce025c0d0b8a1e74fa9db +F VERSION c5a7e41af0b98e84b2f05e4738cee239c15cd3f226b2e4a2e4c5918d7516fdd8 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 6ba84abf7878bb072d460766d29e492028468775d4c09464940343092eb6493c x +F configure 41ce709752785dd617c4752fa2bb84e6c0e3f02f57b84ae48e0a170957052732 x F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -563,7 +563,7 @@ F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca F src/btree.c 2f012aea074de6319c191cbf5c45034de79bf1c762826e381b1cf75421d8d831 F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc F src/btreeInt.h 06bb2c1a07172d5a1cd27a2a5d617b93b1e976c5873709c31964786f86365a6e -F src/build.c c55ab6d1b089ceef57160e840f05f692955ac90944c3d04fcf01d97fd7bfd08d +F src/build.c 4fed662d383527c808d85f53b9c544ead425ac8b9c7cb38501a64e3797921d41 F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d @@ -571,10 +571,10 @@ F src/date.c f21815ca7172ce073db3163ac54c8d9f2841077165c1a6123b4d1c376a0c7ec7 F src/dbpage.c d47549716549311f79dc39fe5c8fb19390a6eb2c960f8e37c89a9c4de0c1052e F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef F src/delete.c 86573edae75e3d3e9a8b590d87db8e47222103029df4f3e11fa56044459b514e -F src/expr.c 2e5e67e800c9416996df52409746a2f3c7dadf5d38b5a367ce379b239e3f40c8 +F src/expr.c 399c10566b94ded9b565c01382026bfa0a7350782d4e9aa4194af1ac05f8af31 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 722f20779f5342a787922deded3628d8c74b5249cab04098cf17ee2f2aaff002 -F src/func.c 64c4cb2eebf6ddb6b4b08dc17790e7152b887f223f8caf0c0fe96da1c5a876ca +F src/func.c d187be57a886ddf4e6b7ef584a494361899be3df5eee6d4a747b68ff4aff4122 F src/global.c e06ff8e0acd85aec13563c9ecb44fbbf38232ccf73594998fd880b92d619594b F src/hash.c c6af5f96a7a76d000f07c5402c48c318c2566beecdee9e78b9d9f60ce7119565 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 @@ -614,19 +614,19 @@ F src/parse.y 960d2da92a23f8ba2ca22748a51bd75ee2c575564f2cbc59f119640e7f5b4c5d F src/pcache.c f4268f7f73c6a3db12ce22fd25bc68dc42315d19599414ab1207d7cf32f79197 F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache1.c dee95e3cd2b61e6512dc814c5ab76d5eb36f0bfc9441dbb4260fccc0d12bbddc -F src/pragma.c 82d5090a35eac75876d3b41f48d06b2370553c9576bf2942233c462e03eb94c9 +F src/pragma.c e698baae96396cac8ff55afef1c0b84632a4b825548bf98f0c4fd1e0a90ed4bc F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c ce87a08cfddd45a147150db34190b1986f2d4a0e0828858cb6bd908c78fb02e3 F src/printf.c ff4b05e38bf928ff1b80d3dda4f977b10fe39ecbfe69c018224c7e5594fb2455 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c d62c5665279cc7485f9d45b5e20911cc7b19c203f268321a90d05d74f4725750 +F src/resolve.c 4233c3030341bf1a21cea90890e6b3d3531721acc62ede147e899d36ffad8238 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c d44e78e103cb52b3bdf8d0b7ac2518b7a4cbaf12237bd9078f6897d230d2516e +F src/select.c 316c9bd757700692400369568a38a8d87f830f9fa896539abcbc331c228cbd20 F src/shell.c.in 6f36f5ca05f1bebf74935a7fcf2dce983016e807a09cbd752a673583ad7da087 F src/sqlite.h.in 662a2fa083d093896b92560c871dea6d86792b49dc4bf7b4e8dbeca8e7171488 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 -F src/sqliteInt.h 4dcc36b21dd0c77efeb0bb38b0d31916bc63fb429c8a89d186c945eba0816447 +F src/sqliteInt.h 107157709ddfb05583343b69f14b0fa159f3ff8899ae2115415977a64815b364 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -685,13 +685,13 @@ F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 1305797eab3542a0896b552c6e7669c972c1468e11e92b370533c1f37a37082b F src/treeview.c fccf3b8c517c1f55cb380c1522febe6921fcb2bd800c16c78cab571d0eb0ccbd -F src/trigger.c 5e68b790f022b8dafbfb0eb244786512a95c9575fc198719d2557d73e5795858 +F src/trigger.c 6072c531d9bcc3980528150a1b03fda2e85a08c10023fafb42f93ffd68607ffe F src/update.c f118e51768d2c1309e3c81e9f91141b22b8a1339cbc5969b1b2d810feaa25b22 F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 3ff7bc2b48dd425b1448304bb86273b05da1621f136d51dbb9789f8803559a1f F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd -F src/vdbe.c 0bf4804f6e9cae872502aa4533a62ba3f6e97ee28523302deb27f9c990b5f44f +F src/vdbe.c b3fd04b0643edd7e0a4356aff6d2cf50f04d0e182e292c3a330d1afffe3100e1 F src/vdbe.h 73b904a6b3bb27f308c6cc287a5751ebc7f1f89456be0ed068a12b92844c6e8c F src/vdbeInt.h a4147a4ddf613cb1bcb555ace9e9e74a9c099d65facd88155f191b1fb4d74cfb F src/vdbeapi.c 40c47b1528d308a322203de21d2e0d711753257ed9771771b6129214b1d65932 @@ -706,10 +706,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c 38c36bb654b4172148aef3b295bbbc1342b52078cf20174f771c746caa98f69a +F src/where.c 7e048157dd645223feba2198553325718b8f76a1bfc136219c9f93612f5c13c7 F src/whereInt.h e25203e5bfee149f5f1225ae0166cfb4f1e65490c998a024249e98bb0647377c F src/wherecode.c b82d0d33315e1526904b95155e55e61149c4462147668e1cc4567c812735eff1 -F src/whereexpr.c a60a1f88650c561eded95c5affbe846cda71fac35033e1280f28e47934df33c8 +F src/whereexpr.c 16d1eefd95f69843b45aba6d04fe2b63fc4f51584dff85ae380f5c20718f3c75 F src/window.c 76a27cff9ea2ded0c2c3527187029259440fabcc4cc4c07b11d942c78494a614 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 @@ -814,7 +814,7 @@ F test/bind2.test 918bc35135f4141809ead7585909cde57d44db90a7a62aef540127148f91aa F test/bindxfer.test efecd12c580c14df5f4ad3b3e83c667744a4f7e0 F test/bitvec.test 75894a880520164d73b1305c1c3f96882615e142 F test/blob.test e7ac6c7d3a985cc4678c64f325292529a69ae252 -F test/bloom1.test 2785a190fcc2a5e170e5d38b08aca8ff0f3e3b4a74d47453d6ac1bd355180a6a +F test/bloom1.test 589361c1f20158a8583863738c883f0e73e82d18422c9b4ed9c7068c13c2d310 F test/boundary1.tcl 6421b2d920d8b09539503a8673339d32f7609eb1 F test/boundary1.test 66d7f4706ccdb42d58eafdb081de07b0eb42d77b F test/boundary2.tcl e34ef4e930cf1083150d4d2c603e146bd3b76bcb @@ -891,7 +891,7 @@ F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c F test/corruptN.test 7c099d153a554001b4fb829c799b01f2ea6276cbc32479131e0db0da4efd9cc4 F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 F test/count.test cd4bd531066e8d77ef8fe1e3fc8253d042072e117ccab214b290cf83f1602249 -F test/countofview.test e17d6e6688cf74f22783c9ec6e788c0790ee4fbbaee713affd00b1ac0bb39b86 +F test/countofview.test f9025dba8a9bb673a1f3ce25405900f630f81592c226546242df1ef1e62d0192 F test/coveridxscan.test f35c7208dedc4f98e471c569df64c0f95a49f6e072d8dc7c8f99bdee2697de1b F test/crash.test fb9dc4a02dcba30d4aa5c2c226f98b220b2b959f F test/crash2.test 5b14d4eb58b880e231361d3b609b216acda86651 @@ -1152,7 +1152,7 @@ F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc F test/fuzzinvariants.c a153253600b2b33a7d5710d40e89b2ac1373a1912517867fb995a45b2d67dcb8 F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c -F test/gencol1.test cc0dbb0ee116e5602e18ea7d47f2a0f76b26e09a823b7c36ef254370c2b0f3c1 +F test/gencol1.test aef8b0670abd4b1ae4cae786b15a43758d86f6cd9f12b381d45d96bb51e597c9 F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 F test/having.test a89236dd8d55aa50c4805f82ac9daf64d477a44d712d8209c118978d0ca21ec9 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 @@ -1215,7 +1215,7 @@ F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9 F test/join.test e32cb9b1491eed682489e2cde33a22a4eb7611fe5aa3b0aa4b275fe27ab3f3ac -F test/join2.test 466b07233820f5deee66a6c3bf6e4500c8bbf7b83649e67606f5f649c07928c0 +F test/join2.test c378a2c59db8da13a265481c9aeab08d854c524f56a0eda12fa7bc535bfbebb1 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 F test/join5.test 91f1f4c7d81fd87b58e9ba7cf4a2b5d39e3583b4f8e498a162722a60259c5208 @@ -1404,7 +1404,7 @@ F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2 F test/releasetest_data.tcl b550dd1b122a9c969df794d05ea272df535f10ff1a245062e7ba080822378016 F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test 3ef7b264598b3292be0cdb028e4acb7524c5fd409b33b78449f894dfd68db334 +F test/returning1.test ee72bece38ea39ef82a219d8f6619e5c1c15835ea127cd7e6e30012cde462dfe F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f @@ -1538,7 +1538,7 @@ F test/subquery.test 3a1a5b600b8d4f504d2a2c61f33db820983dba94a0ef3e4aedca8f0165e F test/subquery2.test 90cf944b9de8204569cf656028391e4af1ccc8c0cc02d4ef38ee3be8de1ffb12 F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303 F test/substr.test a673e3763e247e9b5e497a6cacbaf3da2bd8ec8921c0677145c109f2e633f36b -F test/subtype1.test 45c85632abd38f7ea9b33f17448d966d67550f552e0822bab74576814d0d1718 +F test/subtype1.test 7a9c55ed84d4ce551203d18046f925e293d75f69da81bff71aaf2696e4a2a748 F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12 F test/swarmvtab.test 250231404fcac88f61a6c147bb0e3a118ed879278cd3ccb0ae2d3a729e1e8e26 F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27cbd0aa5c @@ -1612,7 +1612,7 @@ F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed F test/tkt-91e2e8ba6f.test 08c4f94ae07696b05c9b822da0b4e5337a2f54c5 F test/tkt-94c04eaadb.test f738c57c7f68ab8be1c054415af7774617cb6223 -F test/tkt-99378177930f87bd.test 2f07020a82ed1c56bdad60a8a6ef508b2f8a1fb056300b7ec650cbd9975b46bf +F test/tkt-99378177930f87bd.test 28530bf9903dcd7743185ce78b1c02b1f9ba09fe4fa77a70ecbd0af83fe3353c F test/tkt-9a8b09f8e6.test b2ef151d0984b2ebf237760dbeaa50724e5a0667 F test/tkt-9d68c883.test 16f7cb96781ba579bc2e19bb14b4ad609d9774b6 F test/tkt-9f2eb3abac.test cb6123ac695a08b4454c3792fbe85108f67fabf8 @@ -1895,7 +1895,7 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2 F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test 08bac934238ff66987635ea3367c5aed5b4c088c414441beaba426e68a8257c1 +F test/window1.test 5ad2f3e2aec3f2dc5cf4a66998d42a455b8ebd57ec173c0329f6b8788bcae0aa F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -2045,10 +2045,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 409a19145e9558686acab730b94c6d53691b6d0efcea1e9239fc7e5863586a85 -R 8cba8b939a0caa0ad935e665db07c75b +P cc8a0ee40cfc83ab42a0ff452de0a53fe17784aefb944ea7ac2cb078a8310730 +R 36a17de70fdfc70a02115f278d762e2d T +sym-release * -T +sym-version-3.41.0 * +T +sym-version-3.41.1 * U drh -Z dc017a46fcafa54656c31865fbc11b74 +Z 6594aef6b42302816d333b0c05d59faf # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index fd45b5ef9..f4b2140eb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -05941c2a04037fc3ed2ffae11f5d2260706f89431f463518740f72ada350866d +20399f3eda5ec249d147ba9e48da6e87f969d7966a9a896764ca437ff7e737ff diff --git a/src/build.c b/src/build.c index 0390e321f..6e3b7b81f 100644 --- a/src/build.c +++ b/src/build.c @@ -2012,6 +2012,7 @@ void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType){ ** turn it into one by adding a unary "+" operator. */ pExpr = sqlite3PExpr(pParse, TK_UPLUS, pExpr, 0); } + if( pExpr && pExpr->op!=TK_RAISE ) pExpr->affExpr = pCol->affinity; sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); pExpr = 0; goto generated_done; diff --git a/src/expr.c b/src/expr.c index ea3c90167..6135da786 100644 --- a/src/expr.c +++ b/src/expr.c @@ -4133,6 +4133,7 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup( IndexedExpr *p; Vdbe *v; for(p=pParse->pIdxEpr; p; p=p->pIENext){ + u8 exprAff; int iDataCur = p->iDataCur; if( iDataCur<0 ) continue; if( pParse->iSelfTab ){ @@ -4140,6 +4141,16 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup( iDataCur = -1; } if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue; + assert( p->aff>=SQLITE_AFF_BLOB && p->aff<=SQLITE_AFF_NUMERIC ); + exprAff = sqlite3ExprAffinity(pExpr); + if( (exprAff<=SQLITE_AFF_BLOB && p->aff!=SQLITE_AFF_BLOB) + || (exprAff==SQLITE_AFF_TEXT && p->aff!=SQLITE_AFF_TEXT) + || (exprAff>=SQLITE_AFF_NUMERIC && p->aff!=SQLITE_AFF_NUMERIC) + ){ + /* Affinity mismatch on a generated column */ + continue; + } + v = pParse->pVdbe; assert( v!=0 ); if( p->bMaybeNullRow ){ @@ -4719,10 +4730,13 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ return target; } case TK_COLLATE: { - if( !ExprHasProperty(pExpr, EP_Collate) - && ALWAYS(pExpr->pLeft) - && pExpr->pLeft->op==TK_FUNCTION - ){ + if( !ExprHasProperty(pExpr, EP_Collate) ){ + /* A TK_COLLATE Expr node without the EP_Collate tag is a so-called + ** "SOFT-COLLATE" that is added to constraints that are pushed down + ** from outer queries into sub-queries by the push-down optimization. + ** Clear subtypes as subtypes may not cross a subquery boundary. + */ + assert( pExpr->pLeft ); inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); if( inReg!=target ){ sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); @@ -4830,16 +4844,22 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ break; } } - addrINR = sqlite3VdbeAddOp1(v, OP_IfNullRow, pExpr->iTable); - /* Temporarily disable factoring of constant expressions, since - ** even though expressions may appear to be constant, they are not - ** really constant because they originate from the right-hand side - ** of a LEFT JOIN. */ - pParse->okConstFactor = 0; + addrINR = sqlite3VdbeAddOp3(v, OP_IfNullRow, pExpr->iTable, 0, target); + /* The OP_IfNullRow opcode above can overwrite the result register with + ** NULL. So we have to ensure that the result register is not a value + ** that is suppose to be a constant. Two defenses are needed: + ** (1) Temporarily disable factoring of constant expressions + ** (2) Make sure the computed value really is stored in register + ** "target" and not someplace else. + */ + pParse->okConstFactor = 0; /* note (1) above */ inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); pParse->okConstFactor = okConstFactor; + if( inReg!=target ){ /* note (2) above */ + sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); + inReg = target; + } sqlite3VdbeJumpHere(v, addrINR); - sqlite3VdbeChangeP3(v, addrINR, inReg); break; } diff --git a/src/func.c b/src/func.c index c1efd8acf..045c60613 100644 --- a/src/func.c +++ b/src/func.c @@ -2161,6 +2161,18 @@ static void ceilingFunc( static double xCeil(double x){ return ceil(x); } static double xFloor(double x){ return floor(x); } +/* +** Some systems do not have log2() and log10() in their standard math +** libraries. +*/ +#if defined(HAVE_LOG10) && HAVE_LOG10==0 +# define log10(X) (0.4342944819032517867*log(X)) +#endif +#if defined(HAVE_LOG2) && HAVE_LOG2==0 +# define log2(X) (1.442695040888963456*log(X)) +#endif + + /* ** Implementation of SQL functions: ** diff --git a/src/pragma.c b/src/pragma.c index 522a12d33..01c2d486e 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1987,6 +1987,23 @@ void sqlite3Pragma( jmp4 = integrityCheckResultRow(v); sqlite3VdbeJumpHere(v, jmp2); + /* The OP_IdxRowid opcode is an optimized version of OP_Column + ** that extracts the rowid off the end of the index record. + ** But it only works correctly if index record does not have + ** any extra bytes at the end. Verify that this is the case. */ + if( HasRowid(pTab) ){ + int jmp7; + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur+j, 3); + jmp7 = sqlite3VdbeAddOp3(v, OP_Eq, 3, 0, r1+pIdx->nColumn-1); + VdbeCoverage(v); + sqlite3VdbeLoadString(v, 3, + "rowid not at end-of-record for row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " of index "); + sqlite3VdbeGoto(v, jmp5-1); + sqlite3VdbeJumpHere(v, jmp7); + } + /* Any indexed columns with non-BINARY collations must still hold ** the exact same text value as the table. */ label6 = 0; diff --git a/src/resolve.c b/src/resolve.c index 9677f9de9..dfb343439 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -947,14 +947,10 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ testcase( ExprHasProperty(pExpr, EP_OuterON) ); assert( !ExprHasProperty(pExpr, EP_IntValue) ); - if( pExpr->op==TK_NOTNULL ){ - pExpr->u.zToken = "true"; - ExprSetProperty(pExpr, EP_IsTrue); - }else{ - pExpr->u.zToken = "false"; - ExprSetProperty(pExpr, EP_IsFalse); - } - pExpr->op = TK_TRUEFALSE; + pExpr->u.iValue = (pExpr->op==TK_NOTNULL); + pExpr->flags |= EP_IntValue; + pExpr->op = TK_INTEGER; + for(i=0, p=pNC; p && ipNext, i++){ p->nRef = anRef[i]; } diff --git a/src/select.c b/src/select.c index 1b5a1b506..0e692684b 100644 --- a/src/select.c +++ b/src/select.c @@ -2332,8 +2332,6 @@ void sqlite3SubqueryColumnTypes( pCol->affinity = sqlite3ExprAffinity(p); if( pCol->affinity<=SQLITE_AFF_NONE ){ pCol->affinity = aff; - }else if( pCol->affinity>=SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ - pCol->affinity = SQLITE_AFF_FLEXNUM; } if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ int m = 0; @@ -2347,6 +2345,9 @@ void sqlite3SubqueryColumnTypes( if( pCol->affinity>=SQLITE_AFF_NUMERIC && (m&0x02)!=0 ){ pCol->affinity = SQLITE_AFF_BLOB; } + if( pCol->affinity>=SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ + pCol->affinity = SQLITE_AFF_FLEXNUM; + } } zType = columnType(&sNC, p, 0, 0, 0); if( zType==0 || pCol->affinity!=sqlite3AffinityType(zType, 0) ){ @@ -3861,7 +3862,7 @@ static Expr *substExpr( sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ sqlite3 *db = pSubst->pParse->db; - if( pSubst->isOuterJoin && pCopy->op!=TK_COLUMN ){ + if( pSubst->isOuterJoin ){ memset(&ifNullRow, 0, sizeof(ifNullRow)); ifNullRow.op = TK_IF_NULL_ROW; ifNullRow.pLeft = pCopy; @@ -6377,10 +6378,12 @@ static void optimizeAggregateUseOfIndexedExpr( NameContext *pNC /* Name context used to resolve agg-func args */ ){ assert( pAggInfo->iFirstReg==0 ); + assert( pSelect!=0 ); + assert( pSelect->pGroupBy!=0 ); pAggInfo->nColumn = pAggInfo->nAccumulator; if( ALWAYS(pAggInfo->nSortingColumn>0) ){ if( pAggInfo->nColumn==0 ){ - pAggInfo->nSortingColumn = 0; + pAggInfo->nSortingColumn = pSelect->pGroupBy->nExpr; }else{ pAggInfo->nSortingColumn = pAggInfo->aCol[pAggInfo->nColumn-1].iSorterColumn+1; @@ -6805,6 +6808,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ if( p->pEList->nExpr!=1 ) return 0; /* Single result column */ if( p->pWhere ) return 0; if( p->pGroupBy ) return 0; + if( p->pOrderBy ) return 0; pExpr = p->pEList->a[0].pExpr; if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */ assert( ExprUseUToken(pExpr) ); @@ -6815,7 +6819,8 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ if( ExprHasProperty(pExpr, EP_WinFunc) ) return 0;/* Not a window function */ pSub = p->pSrc->a[0].pSelect; if( pSub==0 ) return 0; /* The FROM is a subquery */ - if( pSub->pPrior==0 ) return 0; /* Must be a compound ry */ + if( pSub->pPrior==0 ) return 0; /* Must be a compound */ + if( pSub->selFlags & SF_CopyCte ) return 0; /* Not a CTE */ do{ if( pSub->op!=TK_ALL && pSub->pPrior ) return 0; /* Must be UNION ALL */ if( pSub->pWhere ) return 0; /* No WHERE clause */ @@ -7258,7 +7263,6 @@ int sqlite3Select( && countOfViewOptimization(pParse, p) ){ if( db->mallocFailed ) goto select_end; - pEList = p->pEList; pTabList = p->pSrc; } #endif diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 9af38e8df..59637eb9e 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3660,6 +3660,7 @@ struct IndexedExpr { int iIdxCur; /* The index cursor */ int iIdxCol; /* The index column that contains value of pExpr */ u8 bMaybeNullRow; /* True if we need an OP_IfNullRow check */ + u8 aff; /* Affinity of the pExpr expression */ IndexedExpr *pIENext; /* Next in a list of all indexed expressions */ #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS const char *zIdxName; /* Name of index, used only for bytecode comments */ diff --git a/src/trigger.c b/src/trigger.c index 02d854023..f83c719a3 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -983,7 +983,7 @@ static void codeReturningTrigger( } sqlite3ExprListDelete(db, sSelect.pEList); pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab); - if( !db->mallocFailed ){ + if( pParse->nErr==0 ){ NameContext sNC; memset(&sNC, 0, sizeof(sNC)); if( pReturning->nRetCol==0 ){ diff --git a/src/vdbe.c b/src/vdbe.c index f3853e7fd..a9febbb6e 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -683,8 +683,7 @@ static u64 filterHash(const Mem *aMem, const Op *pOp){ }else if( p->flags & MEM_Real ){ h += sqlite3VdbeIntValue(p); }else if( p->flags & (MEM_Str|MEM_Blob) ){ - h += p->n; - if( p->flags & MEM_Zero ) h += p->u.nZero; + /* no-op */ } } return h; diff --git a/src/where.c b/src/where.c index 08dd21824..5d1f0dcb3 100644 --- a/src/where.c +++ b/src/where.c @@ -5694,6 +5694,9 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( p->iIdxCur = iIdxCur; p->iIdxCol = i; p->bMaybeNullRow = bMaybeNullRow; + if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){ + p->aff = pIdx->zColAff[i]; + } #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS p->zIdxName = pIdx->zName; #endif diff --git a/src/whereexpr.c b/src/whereexpr.c index 4f86753bf..7860480dd 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -1856,7 +1856,7 @@ void sqlite3WhereTabFuncArgs( pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); - if( pItem->fg.jointype & (JT_LEFT|JT_LTORJ) ){ + if( pItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){ joinType = EP_OuterON; }else{ joinType = EP_InnerON; diff --git a/test/bloom1.test b/test/bloom1.test index 1846e4d63..87a7c8632 100644 --- a/test/bloom1.test +++ b/test/bloom1.test @@ -99,6 +99,20 @@ do_eqp_test 2.1 { | |--SCAN transit | `--SEARCH objs USING COVERING INDEX objs_cspo (o=? AND p=?) `--SCAN transit -} +} + +# 2023-02-28 +# https://sqlite.org/forum/forumpost/0846211821 +# +# Bloom filter gives an incorrect result if the collating sequence is +# anything other than binary. +# +reset_db +do_execsql_test 3.1 { + CREATE TABLE t0(x TEXT COLLATE rtrim); + INSERT INTO t0(x) VALUES ('a'), ('b'), ('c'); + CREATE VIEW v0(y) AS SELECT DISTINCT x FROM t0; + SELECT count(*) FROM t0, v0 WHERE x='b '; +} 3 finish_test diff --git a/test/countofview.test b/test/countofview.test index 0ee511ff3..7e94fa5ee 100644 --- a/test/countofview.test +++ b/test/countofview.test @@ -51,6 +51,29 @@ do_execsql_test 2.1 { SELECT count(*) FROM v1 GROUP BY y; } {3 3} +# 2023-03-01 dbsqlfuzz ef8623915d843b150c159166ee4548c78cc6895a +# count-of-view should not apply to CTEs. +# +ifcapable progress { + proc progress_stop args {return 1} + db progress 1000 progress_stop + do_catchsql_test 3.1 { + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c) + SELECT count(*) FROM c; + } {1 interrupted} +} +# 2023-03-07 dbsqlfuzz 23d782160b71c3f8f535ccb2da313dfc8eb8c631 +# +do_execsql_test 4.1 { + DROP TABLE t1; + DROP TABLE t2; + DROP TABLE t3; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1 VALUES(4,'four'); + CREATE TABLE t2(c INTEGER PRIMARY KEY, d TEXT); + CREATE VIEW t3 AS SELECT a, b FROM t1 UNION ALL SELECT c, d FROM t2; + SELECT count(*) FROM t3 ORDER BY sum(a); +} 1 finish_test diff --git a/test/gencol1.test b/test/gencol1.test index ee0ebc53f..f3fbb0dfb 100644 --- a/test/gencol1.test +++ b/test/gencol1.test @@ -615,4 +615,58 @@ do_execsql_test gencol1-22.1 { AND (y.a=2 OR (x.b LIKE '2*' AND y.a=x.b)); } {2 2 2 2} + +# 2023-03-02 dbsqlfuzz 65f5eb57f8859344d5f1f33e08c77ee12960ed83 +# +set typelist {ANY INT REAL BLOB TEXT {}} +set cnt 0 +foreach t1 $typelist { + foreach t2 $typelist { + incr cnt + db eval " + DROP TABLE IF EXISTS t1; + CREATE TABLE t1( + x $t1, + a $t2 AS (x) VIRTUAL, + b BLOB AS (x) VIRTUAL + ); + CREATE INDEX x2 ON t1(a); + INSERT INTO t1(x) VALUES(NULL),('1'),(2),(3.5),('xyz'); + " + set x1 [lsort [db eval {SELECT typeof(b) FROM t1}]] + do_test gencol1-23.1.$cnt { + lsort [db eval {SELECT typeof(b) FROM t1 INDEXED BY x2}] + } $x1 + } +} +do_execsql_test gencol1-23.2 { + DROP TABLE t1; + CREATE TABLE t1( + x, + a INT AS (x) VIRTUAL, + b BLOB AS (x) VIRTUAL + ); + CREATE INDEX x2 ON t1(a); + INSERT INTO t1(x) VALUES(NULL),('1'),('xyz'),(2),(3.5); + SELECT quote(a) FROM t1 INDEXED BY x2; +} {NULL 1 2 3.5 'xyz'} +do_execsql_test gencol1-23.3 { + EXPLAIN SELECT a FROM t1 INDEXED BY x2; +} {~/Column 0/} +# ^^^^^^^^---- verfies that x2 acts like a covering index +do_execsql_test gencol1-23.4 { + EXPLAIN SELECT b FROM t1 INDEXED BY x2; +} {/Column 0/} +# ^^^^^^^^^^--- Must reference the original table in this case because +# of the different datatype on column b. + +# 2023-03-07 https://sqlite.org/forum/forumpost/b312e075b5 +# +do_execsql_test gencol1-23.5 { + CREATE TABLE v0(c1 INT, c2 AS (RAISE(IGNORE))); +} +do_catchsql_test gencol1-23.6 { + SELECT * FROM v0; +} {1 {RAISE() may only be used within a trigger-program}} + finish_test diff --git a/test/join2.test b/test/join2.test index e549c4934..39cabf880 100644 --- a/test/join2.test +++ b/test/join2.test @@ -354,5 +354,54 @@ do_execsql_test 9.11 { SELECT ccc, ccc IS NULL AS ddd FROM t1 LEFT JOIN v2; } {{} 1} +# 2023-03-01 https://sqlite.org/forum/forumpost/26387ea7ef +# When flattening a VIEW which is the RHS of a LEFT JOIN, always put +# an TK_IF_NULL_ROW operator on all accesses, even TK_COLUMN nodes, since +# the TK_COLUMN might reference an outer subquery. +# +reset_db +db null NULL +do_execsql_test 10.1 { + CREATE TABLE t1 (x INTEGER); + INSERT INTO t1 VALUES(1); -- Some true value + CREATE TABLE t2 (z TEXT); + INSERT INTO t2 VALUES('some value'); + CREATE TABLE t3(w TEXT); + INSERT INTO t3 VALUES('some other value'); +} +do_execsql_test 10.2 { + SELECT ( + SELECT 1 FROM t2 LEFT JOIN (SELECT x AS v FROM t3) ON 500=v WHERE (v OR FALSE) + ) FROM t1; +} NULL +do_execsql_test 10.3 { + SELECT ( + SELECT 1 FROM t2 LEFT JOIN (SELECT x AS v FROM t3) ON 500=v WHERE (v) + ) FROM t1; +} NULL +optimization_control db all 0 +do_execsql_test 10.4 { + SELECT ( + SELECT 1 FROM t2 LEFT JOIN (SELECT x AS v FROM t3) ON 500=v WHERE (v OR FALSE) + ) FROM t1; +} NULL + +# 2023-03-02 https://sqlite.org/forum/forumpost/402f05296d +# +# The TK_IF_NULL_ROW expression node must ensure that it does not overwrite +# the result register of an OP_Once subroutine. +# +optimization_control db all 1 +do_execsql_test 11.1 { + DROP TABLE t1; + DROP TABLE t2; + DROP TABLE t3; + CREATE TABLE t1(x TEXT, y INTEGER); + INSERT INTO t1(x,y) VALUES(NULL,-2),(NULL,1),('0',2); + CREATE TABLE t2(z INTEGER); + INSERT INTO t2(z) VALUES(2),(-2); + CREATE VIEW t3 AS SELECT z, (SELECT count(*) FROM t1) AS w FROM t2; + SELECT * FROM t1 LEFT JOIN t3 ON y=z; +} {NULL -2 -2 3 NULL 1 NULL NULL 0 2 2 3} finish_test diff --git a/test/returning1.test b/test/returning1.test index 326af800e..0d97b7020 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -212,13 +212,13 @@ do_execsql_test 10.2 { END; } -do_catchsql_test 10.3 { +do_catchsql_test 10.3a { INSERT INTO t1(a, b) VALUES(1234, 5678) RETURNING rowid; -} {1 {no such column: rowid}} +} {1 {no such column: new.rowid}} -do_catchsql_test 10.3 { +do_catchsql_test 10.3b { UPDATE t1 SET a='z' WHERE b='y' RETURNING rowid; -} {1 {no such column: rowid}} +} {1 {no such column: new.rowid}} do_execsql_test 10.4 { SELECT * FROM log; @@ -408,4 +408,15 @@ do_execsql_test 17.0 { UPDATE bug SET x=NULL WHERE id = 20 RETURNING quote(x), x IS NULL; } {NULL 1} +# 2023-03-08 https://sqlite.org/forum/forumpost/f5a2b1db87 +# NULL pointer dereference following an error. +# +do_execsql_test 18.0 { + CREATE TABLE v0(c1 INT); + CREATE VIEW view_2(c1) AS SELECT CASE WHEN c1 COLLATE TRUE THEN TRUE ELSE TRUE END FROM v0; +} +do_catchsql_test 18.1 { + INSERT INTO view_2 DEFAULT VALUES RETURNING *; +} {1 {no such collation sequence: TRUE}} + finish_test diff --git a/test/subtype1.test b/test/subtype1.test index d15f737bc..4d8e68e6a 100644 --- a/test/subtype1.test +++ b/test/subtype1.test @@ -53,7 +53,25 @@ do_execsql_test subtype1-231 { WITH t4(a) AS NOT MATERIALIZED (SELECT json(1)) SELECT subtype(a) FROM t4; } {0} - +# 2023-03-01 +# https://sqlite.org/forum/forumpost/37dd14a538 +# +# Additional tests to show that subtypes do not traverse subquery boundaries. +# +do_execsql_test subtype1-300 { + CREATE TABLE t0(c0); + INSERT INTO t0 VALUES ('1'); + CREATE VIEW v0(c0) AS SELECT CASE WHEN 1 THEN json_patch('1', '1') END + FROM t0 GROUP BY t0.c0; + SELECT * FROM v0 WHERE json_quote(v0.c0) != '1'; +} {1} +do_execsql_test subtype1-310 { + SELECT *, json_quote(y) FROM (SELECT +json('1') AS y); +} {1 {"1"}} +do_execsql_test subtype1-320 { + SELECT *, json_quote(y) FROM (SELECT +json('1') AS y) + WHERE json_quote(y)='"1"'; +} {1 {"1"}} finish_test diff --git a/test/tkt-99378177930f87bd.test b/test/tkt-99378177930f87bd.test index 868f36c3a..ff73529bb 100644 --- a/test/tkt-99378177930f87bd.test +++ b/test/tkt-99378177930f87bd.test @@ -176,4 +176,19 @@ do_execsql_test tkt-99378-310 { ); } {1 2} +# 2023-03-04 https://sqlite.org/forum/forumpost/a68313d054 +# +do_execsql_test tkt-99378-400 { + DROP TABLE t1; + CREATE TABLE t0(w); + INSERT INTO t0(w) VALUES(1); + CREATE TABLE t1(x); + INSERT INTO t1(x) VALUES(1); + CREATE INDEX t1x ON t1(x > 0); + CREATE VIEW t2(y) AS SELECT avg(w) FROM t0 GROUP BY w>1; + CREATE VIEW t3(z) AS SELECT count(*) FROM t2 WHERE y BETWEEN 0 and 0; + SELECT count(*) FROM t1 NOT INDEXED WHERE (SELECT z FROM t3); + SELECT count(*) FROM t1 INDEXED BY t1x WHERE (SELECT z FROM t3); +} {0 0} + finish_test diff --git a/test/window1.test b/test/window1.test index 0f2282984..471ac2dce 100644 --- a/test/window1.test +++ b/test/window1.test @@ -2210,4 +2210,20 @@ do_execsql_test 72.1 { SELECT * FROM v1 WHERE true; } {1 0} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 72.0 { + CREATE TABLE t0(c0); + INSERT INTO t0(c0) VALUES (0); + CREATE VIEW v0(c0) AS SELECT TOTAL(0) OVER (PARTITION BY t0.c0) FROM t0; +} +do_execsql_test 72.1 { + SELECT COUNT(*) FROM ( + SELECT TOTAL(0) OVER (PARTITION BY t0.c0) FROM t0 + ) + WHERE ('1' IS NOT ('abcde' NOTNULL)); +} {1} + + finish_test From 340f1460ef23a8ab11fd88f440c84f9f13613d71 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 27 Mar 2023 13:54:33 -0400 Subject: [PATCH 093/340] Snapshot of upstream SQLite 3.41.2 --- VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 +++++----- ext/fts3/fts3_write.c | 8 +++-- ext/recover/dbdata.c | 9 +++-- manifest | 70 +++++++++++++++++++-------------------- manifest.uuid | 2 +- src/btree.c | 4 +-- src/build.c | 5 +-- src/delete.c | 2 +- src/expr.c | 5 ++- src/pcache.c | 8 ++--- src/pcache.h | 6 ++-- src/resolve.c | 2 +- src/select.c | 4 ++- src/shell.c.in | 1 + src/sqliteInt.h | 5 ++- src/trigger.c | 1 + src/update.c | 20 ++++++++--- src/vtab.c | 2 ++ src/where.c | 20 +++++++++-- test/bloom1.test | 58 ++++++++++++++++++++++++++++++++ test/corruptL.test | 7 ++-- test/delete.test | 24 ++++++++++++++ test/distinct.test | 56 +++++++++++++++++++++++++++++++ test/distinct2.test | 12 +++++++ test/join2.test | 24 ++++++++++++++ test/notnull2.test | 18 ---------- test/returning1.test | 19 +++++++++++ test/tester.tcl | 23 +++++++++++++ test/update.test | 33 ++++++++++++++++++ 31 files changed, 373 insertions(+), 97 deletions(-) diff --git a/VERSION b/VERSION index d26daf738..0805a14b4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.41.1 +3.41.2 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index f6291d255..8c06474a1 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.41.0]) +AC_INIT([sqlite],[3.41.2]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index f92c35702..5d6de8181 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.69 for sqlite 3.41.1. +# Generated by GNU Autoconf 2.69 for sqlite 3.41.2. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.41.1' -PACKAGE_STRING='sqlite 3.41.1' +PACKAGE_VERSION='3.41.2' +PACKAGE_STRING='sqlite 3.41.2' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1470,7 +1470,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.41.1 to adapt to many kinds of systems. +\`configure' configures sqlite 3.41.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1535,7 +1535,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.41.1:";; + short | recursive ) echo "Configuration of sqlite 3.41.2:";; esac cat <<\_ACEOF @@ -1665,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.41.1 +sqlite configure 3.41.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2084,7 +2084,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.41.1, which was +It was created by sqlite $as_me 3.41.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12457,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.41.1, which was +This file was extended by sqlite $as_me 3.41.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12523,7 +12523,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.41.1 +sqlite config.status 3.41.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 6a727eaf5..393f8a871 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -2667,16 +2667,18 @@ static int fts3MsrBufferData( char *pList, i64 nList ){ - if( nList>pMsr->nBuffer ){ + if( (nList+FTS3_NODE_PADDING)>pMsr->nBuffer ){ char *pNew; - pMsr->nBuffer = nList*2; - pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, pMsr->nBuffer); + int nNew = nList*2 + FTS3_NODE_PADDING; + pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, nNew); if( !pNew ) return SQLITE_NOMEM; pMsr->aBuffer = pNew; + pMsr->nBuffer = nNew; } assert( nList>0 ); memcpy(pMsr->aBuffer, pList, nList); + memset(&pMsr->aBuffer[nList], 0, FTS3_NODE_PADDING); return SQLITE_OK; } diff --git a/ext/recover/dbdata.c b/ext/recover/dbdata.c index da02b754b..51c68db3f 100644 --- a/ext/recover/dbdata.c +++ b/ext/recover/dbdata.c @@ -512,10 +512,14 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ if( pCsr->bOnePage==0 && pCsr->iPgno>pCsr->szDb ) return SQLITE_OK; rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage); if( rc!=SQLITE_OK ) return rc; - if( pCsr->aPage ) break; + if( pCsr->aPage && pCsr->nPage>=256 ) break; + sqlite3_free(pCsr->aPage); + pCsr->aPage = 0; if( pCsr->bOnePage ) return SQLITE_OK; pCsr->iPgno++; } + + assert( iOff+3+2<=pCsr->nPage ); pCsr->iCell = pTab->bPtr ? -2 : 0; pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]); } @@ -750,8 +754,7 @@ static int dbdataGetEncoding(DbdataCursor *pCsr){ int nPg1 = 0; u8 *aPg1 = 0; rc = dbdataLoadPage(pCsr, 1, &aPg1, &nPg1); - assert( rc!=SQLITE_OK || nPg1==0 || nPg1>=512 ); - if( rc==SQLITE_OK && nPg1>0 ){ + if( rc==SQLITE_OK && nPg1>=(56+4) ){ pCsr->enc = get_uint32(&aPg1[56]); } sqlite3_free(aPg1); diff --git a/manifest b/manifest index c390011be..03a098cb5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.41.1 -D 2023-03-10T12:13:52.703 +C Version\s3.41.2 +D 2023-03-22T11:56:21.623 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in 594f07f8829020a45d825234edf14046544b5bf6bc0057a9d5b83b58686b1c46 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 F Makefile.msc b0026b61d1242add00c28dbe169271a763fffcb3af6762bcdbeb20c3efe6a69f F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e -F VERSION c5a7e41af0b98e84b2f05e4738cee239c15cd3f226b2e4a2e4c5918d7516fdd8 +F VERSION b0157838cb8e50bf2bc42bb69fc1426e6e6a4b705f6a9cdfe7d5b4d40b85f43b F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -22,7 +22,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac d1eaa72f4d202ca106f4b2762733709d3f89a877da2e385a57881c4ad0427be7 +F autoconf/tea/configure.ac 9891bfd342d17505b6f69858ab3be1ba4416bfe64b8abcb577f32ebadade7a3b F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 41ce709752785dd617c4752fa2bb84e6c0e3f02f57b84ae48e0a170957052732 x +F configure e3cc91e4da6fff443dcd010ba3acd12053ab111e5021c84ebdfb84ed1aa67898 x F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -76,7 +76,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c 4fb644df0ff840267e47a724286c7a1fa5540273a7ce15756dd5913a101ec302 +F ext/fts3/fts3_write.c 33d2d0db4dd4e7a7a7e9a7f790414293277f9e7682a2fd9d61c713bfc37cd8b6 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -365,7 +365,7 @@ F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69 F ext/rbu/sqlite3rbu.c 348bb6251e6ec459de102f8b2dd50789a98643ef7a28e56e4c787ac9659c15ea F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 -F ext/recover/dbdata.c dc25628e405c86936c597e28f3e6f56a257029c3034c5ef7f6b10f7c02f41018 +F ext/recover/dbdata.c 81115741d685f25cbeb52e233afc5b510bf2a144ea802eb7b64da2b95dc9b274 F ext/recover/recover1.test 2072993624d5e32fef20ae03b17fc06c02bcb344421fe17bb329b24d2a51e647 F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e @@ -560,18 +560,18 @@ F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7 F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca -F src/btree.c 2f012aea074de6319c191cbf5c45034de79bf1c762826e381b1cf75421d8d831 +F src/btree.c 0621c72ab215588f649d6f4466c6212d5b3ee3283eb4aa00d89e202cd5951bea F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc F src/btreeInt.h 06bb2c1a07172d5a1cd27a2a5d617b93b1e976c5873709c31964786f86365a6e -F src/build.c 4fed662d383527c808d85f53b9c544ead425ac8b9c7cb38501a64e3797921d41 +F src/build.c 8357d6ca9a8c9afc297c431df28bc2af407b47f3ef2311875276c944b30c4d54 F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d F src/date.c f21815ca7172ce073db3163ac54c8d9f2841077165c1a6123b4d1c376a0c7ec7 F src/dbpage.c d47549716549311f79dc39fe5c8fb19390a6eb2c960f8e37c89a9c4de0c1052e F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef -F src/delete.c 86573edae75e3d3e9a8b590d87db8e47222103029df4f3e11fa56044459b514e -F src/expr.c 399c10566b94ded9b565c01382026bfa0a7350782d4e9aa4194af1ac05f8af31 +F src/delete.c 201fe0763c52783d205c8c13cdd9d55c1bd5cb21c1f036753f99103b43284b90 +F src/expr.c e7c0cdde3bc5e5b82b02264547456e4454e1ea5a91b9649b3a02b6422a660122 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 722f20779f5342a787922deded3628d8c74b5249cab04098cf17ee2f2aaff002 F src/func.c d187be57a886ddf4e6b7ef584a494361899be3df5eee6d4a747b68ff4aff4122 @@ -611,22 +611,22 @@ F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 39af8ff7c73a991f61f4d1e3a6f8f98b1c8e29144723507822774cac5e6ee0b5 F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 F src/parse.y 960d2da92a23f8ba2ca22748a51bd75ee2c575564f2cbc59f119640e7f5b4c5d -F src/pcache.c f4268f7f73c6a3db12ce22fd25bc68dc42315d19599414ab1207d7cf32f79197 -F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 +F src/pcache.c 842410539b544e12d5fccfcf29890782f46a58f227a77bc0bd76243799662c0c +F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c dee95e3cd2b61e6512dc814c5ab76d5eb36f0bfc9441dbb4260fccc0d12bbddc F src/pragma.c e698baae96396cac8ff55afef1c0b84632a4b825548bf98f0c4fd1e0a90ed4bc F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c ce87a08cfddd45a147150db34190b1986f2d4a0e0828858cb6bd908c78fb02e3 F src/printf.c ff4b05e38bf928ff1b80d3dda4f977b10fe39ecbfe69c018224c7e5594fb2455 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 4233c3030341bf1a21cea90890e6b3d3531721acc62ede147e899d36ffad8238 +F src/resolve.c 6a0253379cc15b3f80321362a61f487a8ef7cd2487fe62e1eb2317b3f871c61f F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 316c9bd757700692400369568a38a8d87f830f9fa896539abcbc331c228cbd20 -F src/shell.c.in 6f36f5ca05f1bebf74935a7fcf2dce983016e807a09cbd752a673583ad7da087 +F src/select.c 9c6f537469c54af6e5b981d68ac18714562c497e6ff57782f338bb17b3ec9777 +F src/shell.c.in 7b184442d3bf93c79ba9c5abbb74a23958843706d97f9c1e3426a2d32ed3691b F src/sqlite.h.in 662a2fa083d093896b92560c871dea6d86792b49dc4bf7b4e8dbeca8e7171488 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 -F src/sqliteInt.h 107157709ddfb05583343b69f14b0fa159f3ff8899ae2115415977a64815b364 +F src/sqliteInt.h 7bedd33b1715035dd4b7d0cd6e28cd6b5f31923365e555a5ee66bf6791bc8b8a F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -685,8 +685,8 @@ F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 1305797eab3542a0896b552c6e7669c972c1468e11e92b370533c1f37a37082b F src/treeview.c fccf3b8c517c1f55cb380c1522febe6921fcb2bd800c16c78cab571d0eb0ccbd -F src/trigger.c 6072c531d9bcc3980528150a1b03fda2e85a08c10023fafb42f93ffd68607ffe -F src/update.c f118e51768d2c1309e3c81e9f91141b22b8a1339cbc5969b1b2d810feaa25b22 +F src/trigger.c f34367fad4df451b5dfe63fcc1d384fd16e40077e42092b1c3682dedeef5a7e3 +F src/update.c 76664e1beae86e8e961983ebe19a4ee9ebd7e26683ead2b288ba08f81fc7ba4e F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 3ff7bc2b48dd425b1448304bb86273b05da1621f136d51dbb9789f8803559a1f @@ -701,12 +701,12 @@ F src/vdbemem.c 0388576b7cf0be13ce14b9e3b8aa90b8a1b923b60321d0242131ae0b5732b43b F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac -F src/vtab.c a39f6ed161f16a84ff445af9dba4776b2cf0898be08a33bee8e1128a66c0074b +F src/vtab.c 4516e9fecff8d4f353132be0468bc6424b102937c50c52e01fd9a0a9e9e34ca5 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c 7e048157dd645223feba2198553325718b8f76a1bfc136219c9f93612f5c13c7 +F src/where.c a016443c4f4853175f939dd4658e236922b14f926f06c5ee60dedeb259684bda F src/whereInt.h e25203e5bfee149f5f1225ae0166cfb4f1e65490c998a024249e98bb0647377c F src/wherecode.c b82d0d33315e1526904b95155e55e61149c4462147668e1cc4567c812735eff1 F src/whereexpr.c 16d1eefd95f69843b45aba6d04fe2b63fc4f51584dff85ae380f5c20718f3c75 @@ -814,7 +814,7 @@ F test/bind2.test 918bc35135f4141809ead7585909cde57d44db90a7a62aef540127148f91aa F test/bindxfer.test efecd12c580c14df5f4ad3b3e83c667744a4f7e0 F test/bitvec.test 75894a880520164d73b1305c1c3f96882615e142 F test/blob.test e7ac6c7d3a985cc4678c64f325292529a69ae252 -F test/bloom1.test 589361c1f20158a8583863738c883f0e73e82d18422c9b4ed9c7068c13c2d310 +F test/bloom1.test 80d2a16891a23fbef2a334cc7dcb6b4a60371fbaedac00aa5e3ddacac4fe3b24 F test/boundary1.tcl 6421b2d920d8b09539503a8673339d32f7609eb1 F test/boundary1.test 66d7f4706ccdb42d58eafdb081de07b0eb42d77b F test/boundary2.tcl e34ef4e930cf1083150d4d2c603e146bd3b76bcb @@ -886,7 +886,7 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test a17bbf54fdde78d43cf3cc34b0057719fd4a173a3d824285b67dc5257c064c7b F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test 9d1a0055c8db19baccd12f22ac36a33ec7d63afb59e82eb30835aea8f89b94df +F test/corruptL.test b42978028afc5eefc8b51d8d7cd6a9344ba7362d7ed4511ee2070f56e06d5a1c F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 F test/corruptN.test 7c099d153a554001b4fb829c799b01f2ea6276cbc32479131e0db0da4efd9cc4 F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 @@ -924,7 +924,7 @@ F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test fcf403fd5585f47342234e153c4a4338cd737b8e0884ac66fc484df47dbcf1a7 F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50 -F test/delete.test 31832b0c45ecb51a54348c68db173be462985901e6ed7f403d6d7a8f70ab4ef0 +F test/delete.test 2686e1c98d552ef37d79ad55b17b93fe96fad9737786917ce3839767f734c48f F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab F test/delete4.test 51fafebe9503a40796d1aae1565c60524cada720e50eecac01b7fd0419d9ea0b @@ -933,8 +933,8 @@ F test/descidx1.test edc8adee58d491b06c7157c50364eaf1c3605c9c19f8093cb1ea2b6184f F test/descidx2.test a0ba347037ff3b811f4c6ceca5fd0f9d5d72e74e59f2d9de346a9d2f6ad78298 F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a1999b926 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e -F test/distinct.test a7687c2fb50c93f6a486936c51439a93221c6e1188f9bc7b27b3ec26f9c58b1e -F test/distinct2.test cd1d15a4a2abf579298f7161e821ed50c0119136fe0424db85c52cf0adc230d1 +F test/distinct.test 691c9e850b0d0b56b66e7e235453198cb4cf0760e324b7403d3c5abbeab0a014 +F test/distinct2.test bb71cc7b5e58e895787f9910a788c254f679928d324732d063fe9bc202ecbe71 F test/distinctagg.test 14ec5026e684eddd414c61c08692b43773e224ac92efbed6ec08c6994bc39723 F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075 @@ -1215,7 +1215,7 @@ F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9 F test/join.test e32cb9b1491eed682489e2cde33a22a4eb7611fe5aa3b0aa4b275fe27ab3f3ac -F test/join2.test c378a2c59db8da13a265481c9aeab08d854c524f56a0eda12fa7bc535bfbebb1 +F test/join2.test 8561fe82ce434ac96de91544072e578dc2cadddf2d9bc9cd802f866a9b92502e F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 F test/join5.test 91f1f4c7d81fd87b58e9ba7cf4a2b5d39e3583b4f8e498a162722a60259c5208 @@ -1333,7 +1333,7 @@ F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 F test/notify3.test 796c7b7157f55c93b4e672b724e9c923a6fc6aa72ac419379a623e2350472e22 F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18 -F test/notnull2.test 8e1aa4f311df37f9d2cd4f5563eea955b8028c692151404e9cc896420a01a3dc +F test/notnull2.test 1ee4acbd614d3cf5f1c4a52f5af7fc771b82352f1a51a86afeaa02c9df1d82ef F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5 F test/null.test b7ff206a1c60fe01aa2abd33ef9ea83c93727d993ca8a613de86e925c9f2bc6f F test/nulls1.test 7a5e4346ee4285034100b4cd20e6784f16a9d6c927e44ecdf10034086bbee9c9 @@ -1404,7 +1404,7 @@ F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2 F test/releasetest_data.tcl b550dd1b122a9c969df794d05ea272df535f10ff1a245062e7ba080822378016 F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test ee72bece38ea39ef82a219d8f6619e5c1c15835ea127cd7e6e30012cde462dfe +F test/returning1.test c78efa2829d5f44165fcc7959961b298ae005d7775f09e5542bb2815bbaee692 F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f @@ -1562,7 +1562,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 8d54d40a55e12554b4509bc12078b201b233c8e842a7543629094a21b1ba956d +F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede F test/testrunner.tcl e0b36a732956c69ce19370611b8dc69b44e627d5ea8ec2a5455ca3e8a170ddaa F test/testrunner_data.tcl 8169c68654ac8906833b8a6aadca973358a441ebf88270dd05c153e5f96f76b8 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 @@ -1769,7 +1769,7 @@ F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264 F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2 F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97 F test/unordered.test 0edaf3411d300693bca595897c5201421c6c5ec787990a1dfe2f7f60ae93f1e2 -F test/update.test eb7f4eb172ce270e51bb67d7867521f33a63635bb671e261bbafccaef3bd6db2 +F test/update.test 90772ede84cfc779fc3d31884a84ec4c74deb501eeb09a1c6c91c03d8e94c0d8 F test/update2.test 67455bc61fcbcf96923c45b3bc4f87bc72be7d67575ad35f134906148c7b06d3 F test/upfrom1.tcl 8859d9d437f03b44174c4524a7a734a391fd4526fcff65be08285dafc9dc9041 F test/upfrom1.test 8cb06689e99cd707d884faa16da0e8eb26ff658bb01c47ddf72fadade666e6e1 @@ -2045,10 +2045,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P cc8a0ee40cfc83ab42a0ff452de0a53fe17784aefb944ea7ac2cb078a8310730 -R 36a17de70fdfc70a02115f278d762e2d +P 2bb74aa501b7245f4e4d789630fe8c5265c7644ce223404fe7360d5a1fe8cd76 +R 0365b6bb63f816f9874eaae0438c7a22 T +sym-release * -T +sym-version-3.41.1 * +T +sym-version-3.41.2 * U drh -Z 6594aef6b42302816d333b0c05d59faf +Z e44ecdc2c529af7984c41c11e476f7a6 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f4b2140eb..5c230627f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -20399f3eda5ec249d147ba9e48da6e87f969d7966a9a896764ca437ff7e737ff +0d1fc92f94cb6b76bffe3ec34d69cffde2924203304e8ffc4155597af0c191da diff --git a/src/btree.c b/src/btree.c index 4fbe0b3db..3de1a0a50 100644 --- a/src/btree.c +++ b/src/btree.c @@ -6084,7 +6084,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ pPage = pCur->pPage; idx = ++pCur->ix; - if( NEVER(!pPage->isInit) || sqlite3FaultSim(412) ){ + if( !pPage->isInit || sqlite3FaultSim(412) ){ return SQLITE_CORRUPT_BKPT; } @@ -9213,6 +9213,7 @@ int sqlite3BtreeInsert( assert( szNew==pPage->xCellSize(pPage, newCell) ); assert( szNew <= MX_CELL_SIZE(p->pBt) ); idx = pCur->ix; + pCur->info.nSize = 0; if( loc==0 ){ CellInfo info; assert( idx>=0 ); @@ -9285,7 +9286,6 @@ int sqlite3BtreeInsert( ** larger than the largest existing key, it is possible to insert the ** row without seeking the cursor. This can be a big performance boost. */ - pCur->info.nSize = 0; if( pPage->nOverflow ){ assert( rc==SQLITE_OK ); pCur->curFlags &= ~(BTCF_ValidNKey); diff --git a/src/build.c b/src/build.c index 6e3b7b81f..eecfa4705 100644 --- a/src/build.c +++ b/src/build.c @@ -1451,7 +1451,7 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){ if( pParse->pNewTrigger ){ sqlite3ErrorMsg(pParse, "cannot use RETURNING in a trigger"); }else{ - assert( pParse->bReturning==0 ); + assert( pParse->bReturning==0 || pParse->ifNotExists ); } pParse->bReturning = 1; pRet = sqlite3DbMallocZero(db, sizeof(*pRet)); @@ -1477,7 +1477,8 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){ pRet->retTStep.pTrig = &pRet->retTrig; pRet->retTStep.pExprList = pList; pHash = &(db->aDb[1].pSchema->trigHash); - assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 || pParse->nErr ); + assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 + || pParse->nErr || pParse->ifNotExists ); if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig) ==&pRet->retTrig ){ sqlite3OomFault(db); diff --git a/src/delete.c b/src/delete.c index 704a3c711..22ef7ab65 100644 --- a/src/delete.c +++ b/src/delete.c @@ -483,7 +483,7 @@ void sqlite3DeleteFrom( #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ { u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK; - if( sNC.ncFlags & NC_VarSelect ) bComplex = 1; + if( sNC.ncFlags & NC_Subquery ) bComplex = 1; wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW); if( HasRowid(pTab) ){ /* For a rowid table, initialize the RowSet to an empty set */ diff --git a/src/expr.c b/src/expr.c index 6135da786..87adcd344 100644 --- a/src/expr.c +++ b/src/expr.c @@ -3839,9 +3839,10 @@ void sqlite3ExprCodeGeneratedColumn( ){ int iAddr; Vdbe *v = pParse->pVdbe; + int nErr = pParse->nErr; assert( v!=0 ); assert( pParse->iSelfTab!=0 ); - if( pParse->iSelfTab>0 ){ + if( pParse->iSelfTab>0 ){ iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut); }else{ iAddr = 0; @@ -3851,6 +3852,7 @@ void sqlite3ExprCodeGeneratedColumn( sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1); } if( iAddr ) sqlite3VdbeJumpHere(v, iAddr); + if( pParse->nErr>nErr ) pParse->db->errByteOffset = -1; } #endif /* SQLITE_OMIT_GENERATED_COLUMNS */ @@ -3867,6 +3869,7 @@ void sqlite3ExprCodeGetColumnOfTable( Column *pCol; assert( v!=0 ); assert( pTab!=0 ); + assert( iCol!=XN_EXPR ); if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); VdbeComment((v, "%s.rowid", pTab->zName)); diff --git a/src/pcache.c b/src/pcache.c index 0407e06b2..153628dc9 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -41,7 +41,7 @@ struct PCache { PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */ PgHdr *pSynced; /* Last synced page in dirty page list */ - int nRefSum; /* Sum of ref counts over all pages */ + i64 nRefSum; /* Sum of ref counts over all pages */ int szCache; /* Configured cache size */ int szSpill; /* Size before spilling occurs */ int szPage; /* Size of every page in this cache */ @@ -71,7 +71,7 @@ struct PCache { unsigned char *a; int j; pPg = (PgHdr*)pLower->pExtra; - printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags); + printf("%3lld: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags); a = (unsigned char *)pLower->pBuf; for(j=0; j<12; j++) printf("%02x", a[j]); printf(" ptr %p\n", pPg); @@ -815,14 +815,14 @@ PgHdr *sqlite3PcacheDirtyList(PCache *pCache){ ** This is not the total number of pages referenced, but the sum of the ** reference count for all pages. */ -int sqlite3PcacheRefCount(PCache *pCache){ +i64 sqlite3PcacheRefCount(PCache *pCache){ return pCache->nRefSum; } /* ** Return the number of references to the page supplied as an argument. */ -int sqlite3PcachePageRefcount(PgHdr *p){ +i64 sqlite3PcachePageRefcount(PgHdr *p){ return p->nRef; } diff --git a/src/pcache.h b/src/pcache.h index eb55396af..f945dab1a 100644 --- a/src/pcache.h +++ b/src/pcache.h @@ -40,7 +40,7 @@ struct PgHdr { ** private to pcache.c and should not be accessed by other modules. ** pCache is grouped with the public elements for efficiency. */ - i16 nRef; /* Number of users of this page */ + i64 nRef; /* Number of users of this page */ PgHdr *pDirtyNext; /* Next element in list of dirty pages */ PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */ /* NB: pDirtyNext and pDirtyPrev are undefined if the @@ -121,12 +121,12 @@ void sqlite3PcacheClearSyncFlags(PCache *); void sqlite3PcacheClear(PCache*); /* Return the total number of outstanding page references */ -int sqlite3PcacheRefCount(PCache*); +i64 sqlite3PcacheRefCount(PCache*); /* Increment the reference count of an existing page */ void sqlite3PcacheRef(PgHdr*); -int sqlite3PcachePageRefcount(PgHdr*); +i64 sqlite3PcachePageRefcount(PgHdr*); /* Return the total number of pages stored in the cache */ int sqlite3PcachePagecount(PCache*); diff --git a/src/resolve.c b/src/resolve.c index dfb343439..4b36ecca3 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -1252,8 +1252,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pNC->nRef>=nRef ); if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); - pNC->ncFlags |= NC_VarSelect; } + pNC->ncFlags |= NC_Subquery; } break; } diff --git a/src/select.c b/src/select.c index 0e692684b..2a66c79e3 100644 --- a/src/select.c +++ b/src/select.c @@ -3862,7 +3862,9 @@ static Expr *substExpr( sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ sqlite3 *db = pSubst->pParse->db; - if( pSubst->isOuterJoin ){ + if( pSubst->isOuterJoin + && (pCopy->op!=TK_COLUMN || pCopy->iTable!=pSubst->iNewTable) + ){ memset(&ifNullRow, 0, sizeof(ifNullRow)); ifNullRow.op = TK_IF_NULL_ROW; ifNullRow.pLeft = pCopy; diff --git a/src/shell.c.in b/src/shell.c.in index de3b6a73b..de8bc3884 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -2774,6 +2774,7 @@ static char *shell_error_context(const char *zSql, sqlite3 *db){ if( db==0 || zSql==0 || (iOffset = sqlite3_error_offset(db))<0 + || iOffset>=strlen(zSql) ){ return sqlite3_mprintf(""); } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 59637eb9e..2614f4be4 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3341,7 +3341,7 @@ struct NameContext { #define NC_HasAgg 0x000010 /* One or more aggregate functions seen */ #define NC_IdxExpr 0x000020 /* True if resolving columns of CREATE INDEX */ #define NC_SelfRef 0x00002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */ -#define NC_VarSelect 0x000040 /* A correlated subquery has been seen */ +#define NC_Subquery 0x000040 /* A subquery has been seen */ #define NC_UEList 0x000080 /* True if uNC.pEList is used */ #define NC_UAggInfo 0x000100 /* True if uNC.pAggInfo is used */ #define NC_UUpsert 0x000200 /* True if uNC.pUpsert is used */ @@ -3712,6 +3712,9 @@ struct Parse { u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ +#endif +#ifdef SQLITE_DEBUG + u8 ifNotExists; /* Might be true if IF NOT EXISTS. Assert()s only */ #endif int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ diff --git a/src/trigger.c b/src/trigger.c index f83c719a3..d179d747a 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -202,6 +202,7 @@ void sqlite3BeginTrigger( }else{ assert( !db->init.busy ); sqlite3CodeVerifySchema(pParse, iDb); + VVA_ONLY( pParse->ifNotExists = 1; ) } goto trigger_cleanup; } diff --git a/src/update.c b/src/update.c index a4e57d1d4..bd1ddb1a3 100644 --- a/src/update.c +++ b/src/update.c @@ -727,12 +727,22 @@ void sqlite3Update( /* Begin the database scan. ** ** Do not consider a single-pass strategy for a multi-row update if - ** there are any triggers or foreign keys to process, or rows may - ** be deleted as a result of REPLACE conflict handling. Any of these - ** things might disturb a cursor being used to scan through the table - ** or index, causing a single-pass approach to malfunction. */ + ** there is anything that might disrupt the cursor being used to do + ** the UPDATE: + ** (1) This is a nested UPDATE + ** (2) There are triggers + ** (3) There are FOREIGN KEY constraints + ** (4) There are REPLACE conflict handlers + ** (5) There are subqueries in the WHERE clause + */ flags = WHERE_ONEPASS_DESIRED; - if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){ + if( !pParse->nested + && !pTrigger + && !hasFK + && !chngKey + && !bReplace + && (sNC.ncFlags & NC_Subquery)==0 + ){ flags |= WHERE_ONEPASS_MULTIROW; } pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,0,0,flags,iIdxCur); diff --git a/src/vtab.c b/src/vtab.c index 2dd92762b..58452dfc5 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -610,7 +610,9 @@ static int vtabCallConstructor( sCtx.pPrior = db->pVtabCtx; sCtx.bDeclared = 0; db->pVtabCtx = &sCtx; + pTab->nTabRef++; rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr); + sqlite3DeleteTable(db, pTab); db->pVtabCtx = sCtx.pPrior; if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); assert( sCtx.pTab==pTab ); diff --git a/src/where.c b/src/where.c index 5d1f0dcb3..448c95576 100644 --- a/src/where.c +++ b/src/where.c @@ -1123,6 +1123,10 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( Vdbe *v = pParse->pVdbe; /* VDBE under construction */ WhereLoop *pLoop = pLevel->pWLoop; /* The loop being coded */ int iCur; /* Cursor for table getting the filter */ + IndexedExpr *saved_pIdxEpr; /* saved copy of Parse.pIdxEpr */ + + saved_pIdxEpr = pParse->pIdxEpr; + pParse->pIdxEpr = 0; assert( pLoop!=0 ); assert( v!=0 ); @@ -1179,9 +1183,8 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( int r1 = sqlite3GetTempRange(pParse, n); int jj; for(jj=0; jjaiColumn[jj]; assert( pIdx->pTable==pItem->pTab ); - sqlite3ExprCodeGetColumnOfTable(v, pIdx->pTable, iCur, iCol,r1+jj); + sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iCur, jj, r1+jj); } sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, n); sqlite3ReleaseTempRange(pParse, r1, n); @@ -1212,6 +1215,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( } }while( iLevel < pWInfo->nLevel ); sqlite3VdbeJumpHere(v, addrOnce); + pParse->pIdxEpr = saved_pIdxEpr; } @@ -1510,6 +1514,7 @@ static int whereKeyStats( assert( pRec!=0 ); assert( pIdx->nSample>0 ); assert( pRec->nField>0 ); + /* Do a binary search to find the first sample greater than or equal ** to pRec. If pRec contains a single field, the set of samples to search @@ -1555,7 +1560,12 @@ static int whereKeyStats( ** it is extended to two fields. The duplicates that this creates do not ** cause any problems. */ - nField = MIN(pRec->nField, pIdx->nSample); + if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ + nField = pIdx->nKeyCol; + }else{ + nField = pIdx->nColumn; + } + nField = MIN(pRec->nField, nField); iCol = 0; iSample = pIdx->nSample * nField; do{ @@ -5283,6 +5293,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){ pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; } + if( pWInfo->pSelect->pOrderBy + && pWInfo->nOBSat > pWInfo->pSelect->pOrderBy->nExpr ){ + pWInfo->nOBSat = pWInfo->pSelect->pOrderBy->nExpr; + } }else{ pWInfo->revMask = pFrom->revLoop; if( pWInfo->nOBSat<=0 ){ diff --git a/test/bloom1.test b/test/bloom1.test index 87a7c8632..af0ee62ac 100644 --- a/test/bloom1.test +++ b/test/bloom1.test @@ -114,5 +114,63 @@ do_execsql_test 3.1 { CREATE VIEW v0(y) AS SELECT DISTINCT x FROM t0; SELECT count(*) FROM t0, v0 WHERE x='b '; } 3 +do_eqp_test 3.2 { + SELECT count(*) FROM t0, v0 WHERE x='b '; +} { + QUERY PLAN + |--CO-ROUTINE v0 + | |--SCAN t0 + | `--USE TEMP B-TREE FOR DISTINCT + |--SCAN v0 + `--SEARCH t0 USING AUTOMATIC PARTIAL COVERING INDEX (x=?) +} +# ^^^^^--- The key feature in the previous result is that no Bloom filter +# is used. In the following, a Bloom filter is used because the data type +# is INT instead of TEXT. +do_execsql_test 3.3 { + CREATE TABLE t1(x INT COLLATE rtrim); + INSERT INTO t1(x) VALUES ('a'), ('b'), ('c'); + CREATE VIEW v1(y) AS SELECT DISTINCT x FROM t1; + SELECT count(*) FROM t1, v1 WHERE x='b '; +} 3 +# 2023-03-14 +# https://sqlite.org/forum/forumpost/d47a0e8e3a +# https://sqlite.org/forum/forumpost/2e427099d5 +# +# Both reports are for the same problem - using a Bloom filter on an +# expression index can cause issues. +# +reset_db +do_execsql_test 4.1 { + CREATE TABLE t1(x TEXT, y INT, z TEXT); + INSERT INTO t1(rowid,x,y,z) VALUES(12,'aa','bb','aa'); + CREATE INDEX i1x ON t1(1 IS true,z); + CREATE TABLE t0(x TEXT); + INSERT INTO t0(rowid,x) VALUES(4,'aa'); + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t0',NULL,'20'); + INSERT INTO sqlite_stat1 VALUES('t1','i1x','18 18 2'); + ANALYZE sqlite_schema; +} +do_execsql_test 4.2 { + SELECT * FROM t0 NATURAL JOIN t1 WHERE z=t1.x; +} {aa bb aa} +do_execsql_test 4.3 { + DROP TABLE t0; + CREATE TABLE t0(a TEXT); + INSERT INTO t0 VALUES ('xyz'); + CREATE INDEX t0x ON t0(a IS FALSE) WHERE false; + DROP TABLE t1; + CREATE TABLE t1(b INT); + INSERT INTO t1 VALUES('aaa'),('bbb'),('ccc'),('ddd'),(NULL); + CREATE TABLE t2(c REAL); + INSERT INTO t2 VALUES(7); + ANALYZE; + CREATE INDEX t2x ON t2(true IN ()); +} +do_execsql_test 4.4 { + SELECT * FROM t0 LEFT JOIN t1 LEFT JOIN t2 ON (b NOTNULL)==(c IN ()) WHERE c; +} {xyz {} 7.0} + finish_test diff --git a/test/corruptL.test b/test/corruptL.test index 3a841a819..65d9821b3 100644 --- a/test/corruptL.test +++ b/test/corruptL.test @@ -1269,11 +1269,14 @@ do_test 15.0 { }]} {} extra_schema_checks 0 -do_execsql_test 15.1 { +do_catchsql_test 15.1 { PRAGMA cell_size_check = 0; UPDATE c1 SET c= NOT EXISTS(SELECT 1 FROM c1 ORDER BY (SELECT 1 FROM c1 ORDER BY a)) +10 WHERE d BETWEEN 4 AND 7; -} {} +} {1 {database disk image is malformed}} extra_schema_checks 1 +do_execsql_test 15.2 { + PRAGMA integrity_check; +} {/in database main/} #------------------------------------------------------------------------- reset_db diff --git a/test/delete.test b/test/delete.test index a448e52dd..214bae6f7 100644 --- a/test/delete.test +++ b/test/delete.test @@ -416,4 +416,28 @@ do_execsql_test delete-11.1 { } {6 2 12 4 18 6 19 23 20 40} +# 2023-03-15 +# https://sqlite.org/forum/forumpost/e61252062c9d286d +# +# When the WHERE clause of a DELETE statement contains a subquery +# which uses the table that is being deleted from and there is a +# short-circuit operator of some kind in the WHERE clause such that +# the subquery might not run right away, then the subquery might +# run after one or more rows have been deleted, which can change +# the result of the subquery, and result in the wrong answer. +# +# Similar problem for UPDATE tested by update-21.4 +# https://sqlite.org/forum/forumpost/0007d1fdb1 +# +reset_db +do_execsql_test delete-12.0 { + CREATE TABLE t0(vkey INTEGER, pkey INTEGER,c1 INTEGER); + INSERT INTO t0 VALUES(2,1,-20),(2,2,NULL),(2,3,0),(8,4,95); + DELETE FROM t0 WHERE NOT ( + (t0.vkey <= t0.c1) AND + (t0.vkey <> (SELECT vkey FROM t0 ORDER BY vkey LIMIT 1 OFFSET 2)) + ); + SELECT * FROM t0; +} {8 4 95} + finish_test diff --git a/test/distinct.test b/test/distinct.test index 446f85bb8..760b2341f 100644 --- a/test/distinct.test +++ b/test/distinct.test @@ -348,5 +348,61 @@ foreach {tn idx} { } } +# 2023-03-16 +# https://sqlite.org/forum/forumpost/16ce2bb7a639e29b +# ticket c36cdb4afd504dc1 +# ticket 4051a7f931d9ba24 +# ticket d6fd512f50513ab7 +# +do_execsql_test 10.1 { + SELECT DISTINCT + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + ORDER BY + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x'; +} {1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1} +do_execsql_test 10.2 { + EXPLAIN + SELECT DISTINCT + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + ORDER BY + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x','x','x','x','x','x','x', + 'x','x','x','x'; +} {/0 Init 0 /} +do_execsql_test 10.3 { + EXPLAIN CREATE TABLE t2 AS SELECT DISTINCT ':memory:', 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ORDER BY '%J%j%w%s', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', '%J%j%w%s', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 42e-300, 'unixepoch', 'unixepoch', 'unixepoch' LIMIT 0xda; +} {/0 Init 0/} +do_execsql_test 10.4 { + DROP TABLE IF EXISTS t0; + CREATE TABLE t0 AS SELECT DISTINCT 0xda, 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 0xda-0xda-42e-300, 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0', 'lit0' ORDER BY '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%Y-%m-%d', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', 'lit0', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', 'auto', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', ':memory:', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', '%%', ''; + SELECT count(*) FROM t0; +} {1} +do_execsql_test 10.5 { + DROP TABLE IF EXISTS t2; + CREATE TABLE t2 AS SELECT DISTINCT ':memory:', 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0.0*7/0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ORDER BY '%J%j%w%s', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', '%J%j%w%s', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 'unixepoch', 42e-300, 'unixepoch', 'unixepoch', 'unixepoch' LIMIT 0xda; + SELECT count(*) FROM t2; +} {1} finish_test diff --git a/test/distinct2.test b/test/distinct2.test index 46eace6f0..958d9634f 100644 --- a/test/distinct2.test +++ b/test/distinct2.test @@ -301,4 +301,16 @@ do_execsql_test 3030 { {} 1 a } +#------------------------------------------------------------------------- +# +reset_db + +do_execsql_test 4010 { + CREATE TABLE t1(a, b COLLATE RTRIM); + INSERT INTO t1 VALUES(1, ''), (2, ' '), (3, ' '); +} +do_execsql_test 4020 { + SELECT b FROM t1 UNION SELECT 1; +} {1 { }} + finish_test diff --git a/test/join2.test b/test/join2.test index 39cabf880..15e76f965 100644 --- a/test/join2.test +++ b/test/join2.test @@ -404,4 +404,28 @@ do_execsql_test 11.1 { SELECT * FROM t1 LEFT JOIN t3 ON y=z; } {NULL -2 -2 3 NULL 1 NULL NULL 0 2 2 3} +# 2023-03-11 https://sqlite.org/forum/forumpost/b405033490fa56d9 +# The fix that test 11.1 above checks also caused a performance regression. +# This test case verifies that the performance regression has been resolved. +# +do_execsql_test 12.1 { + DROP TABLE t1; + DROP TABLE t2; + DROP VIEW t3; + CREATE TABLE t1(a INTEGER PRIMARY KEY); + WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<100) + INSERT INTO t1(a) SELECT n FROM c; + CREATE VIEW t2(b) AS SELECT a FROM t1; +} +do_vmstep_test 12.2 { + SELECT * FROM t1 LEFT JOIN t2 ON a=b LIMIT 10 OFFSET 98; +} 2000 {99 99 100 100} +do_eqp_test 12.3 { + SELECT * FROM t1 LEFT JOIN t2 ON a=b LIMIT 10 OFFSET 98; +} { + QUERY PLAN + |--SCAN t1 + `--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN +} + finish_test diff --git a/test/notnull2.test b/test/notnull2.test index 12fffe28c..7f6808681 100644 --- a/test/notnull2.test +++ b/test/notnull2.test @@ -28,24 +28,6 @@ do_execsql_test 1.0 { INSERT INTO t2 SELECT * FROM t1; } -proc do_vmstep_test {tn sql nstep {res {}}} { - uplevel [list do_execsql_test $tn.0 $sql $res] - - set vmstep [db status vmstep] - if {[string range $nstep 0 0]=="+"} { - set body "if {$vmstep<$nstep} { - error \"got $vmstep, expected more than [string range $nstep 1 end]\" - }" - } else { - set body "if {$vmstep>$nstep} { - error \"got $vmstep, expected less than $nstep\" - }" - } - - # set name "$tn.vmstep=$vmstep,expect=$nstep" - set name "$tn.1" - uplevel [list do_test $name $body {}] -} do_vmstep_test 1.1.1 { SELECT * FROM t1 LEFT JOIN t2 WHERE a=c AND d IS NULL; diff --git a/test/returning1.test b/test/returning1.test index 0d97b7020..5ab44f2cb 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -419,4 +419,23 @@ do_catchsql_test 18.1 { INSERT INTO view_2 DEFAULT VALUES RETURNING *; } {1 {no such collation sequence: TRUE}} +# 2023-03-16 +# https://sqlite.org/forum/forumpost/c99d6e0329 +# ticket d15b3a4ea901ef0d +# ticket 89d259d45b855a0d +# +# A RETURNING clause on an IF NOT EXISTS trigger does not generate +# an error if the trigger already exists. +# +do_execsql_test 19.0 { + DROP TABLE IF EXISTS t1;CREATE TABLE t1(a); + CREATE TRIGGER r1 AFTER UPDATE ON t1 BEGIN VALUES(0); END; +} {} +do_catchsql_test 19.1 { + CREATE TRIGGER IF NOT EXISTS r1 AFTER DELETE ON t1 BEGIN + INSERT INTO t1(a) VALUES (1) RETURNING FALSE; + INSERT INTO t1(a) VALUES (2) RETURNING TRUE; + END; +} {0 {}} + finish_test diff --git a/test/tester.tcl b/test/tester.tcl index 4658590cf..021830aa9 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -949,6 +949,29 @@ proc normalize_list {L} { set L2 } +# Run SQL and verify that the number of "vmsteps" required is greater +# than or less than some constant. +# +proc do_vmstep_test {tn sql nstep {res {}}} { + uplevel [list do_execsql_test $tn.0 $sql $res] + + set vmstep [db status vmstep] + if {[string range $nstep 0 0]=="+"} { + set body "if {$vmstep<$nstep} { + error \"got $vmstep, expected more than [string range $nstep 1 end]\" + }" + } else { + set body "if {$vmstep>$nstep} { + error \"got $vmstep, expected less than $nstep\" + }" + } + + # set name "$tn.vmstep=$vmstep,expect=$nstep" + set name "$tn.1" + uplevel [list do_test $name $body {}] +} + + # Either: # # do_execsql_test TESTNAME SQL ?RES? diff --git a/test/update.test b/test/update.test index 66efd10ec..47b6029be 100644 --- a/test/update.test +++ b/test/update.test @@ -732,4 +732,37 @@ do_execsql_test update-20.30 { PRAGMA integrity_check; } {ok} +# 2023-03-16 https://sqlite.org/forum/forumpost/0007d1fdb1 +# A subquery in the WHERE clause of an UPDATE and behind a +# short-circuit evaluation caused problems because multi-row +# single-pass was selected. +# +# Similar problem for DELETE tested by delete-12.0. +# https://sqlite.org/src/info/73f0036f045bf371 +# +reset_db +do_execsql_test update-21.1 { + CREATE TABLE t1 (vkey INTEGER, c5 INTEGER); + INSERT INTO t1 VALUES(3,NULL),(6,-54); +} +db null NULL +do_execsql_test update-21.2 { + BEGIN; + UPDATE t1 SET vkey = 100 WHERE c5 is null; + SELECT * FROM t1 ORDER BY vkey, c5; + ROLLBACK; +} {6 -54 100 NULL} +do_execsql_test update-21.3 { + BEGIN; + UPDATE t1 SET vkey = 100 WHERE NOT (-10*(select min(vkey) from t1) >= c5); + SELECT * FROM t1 ORDER BY vkey, c5; + ROLLBACK; +} {3 NULL 6 -54} +do_execsql_test update-21.4 { + BEGIN; + UPDATE t1 SET vkey = 100 WHERE c5 is null OR NOT (-10*(select min(vkey) from t1) >= c5); + SELECT * FROM t1 ORDER BY vkey, c5; + ROLLBACK; +} {6 -54 100 NULL} + finish_test From 175ef2f59bc828056fd3dc48d4c231a16632bb9e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 10 Apr 2023 11:30:51 -0400 Subject: [PATCH 094/340] changelog update --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f381d01cb..898ec62bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. ## [unreleased] - (? 2023 - [unreleased changes]) +## [4.5.4] - (April 2023 - [4.5.4 changes]) +- Updates baseline to upstream SQLite 3.41.2 +- Updates minimum Apple SDK versions in podspec for new Xcode compatibility +- Return runtime OpenSSL version from PRAGMA cipher_provider_version (instead of hardcoded value) +- Adds guard against zero block size and crash if cryptographic provider initialization fails +- When an ATTACH occurs creating a new encrypted database as the first operation after keying the main database, the new database will have the same salt value. + ## [4.5.3] - (December 2022 - [4.5.3 changes]) - Updates baseline to upstream SQLite 3.39.4 @@ -213,7 +220,9 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 -[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.3...prerelease +[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.4...prerelease +[4.5.4]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.4 +[4.5.4 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.3...v4.5.4 [4.5.3]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.3 [4.5.3 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.2...v4.5.3 [4.5.2]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.2 From 5e49545c6d9d59dcc3dd9e4fca5f8978944fcdbb Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 13 Apr 2023 13:34:20 -0400 Subject: [PATCH 095/340] update tags for podspec --- SQLCipher.podspec.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index c42806385..dcea74f92 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.3" + "tag": "v4.5.4" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.3", + "version": "4.5.4", "subspecs": [ { "compiler_flags": [ From c7f9a1c348fde215ac18ce2b78a62ce8111a3743 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 25 Apr 2023 15:07:24 -0400 Subject: [PATCH 096/340] adjust provider version check to work with older versions of openssl --- src/crypto_openssl.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index 4482375c8..8ab1ad3ab 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -147,7 +147,11 @@ static const char* sqlcipher_openssl_get_provider_name(void *ctx) { } static const char* sqlcipher_openssl_get_provider_version(void *ctx) { +#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) + return OPENSSL_VERSION_TEXT; +#else return OpenSSL_version(OPENSSL_VERSION); +#endif } /* generate a defined number of random bytes */ From 827424ec6a2f6fcb29d5afffd19c271c3d401492 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 1 May 2023 10:35:35 -0400 Subject: [PATCH 097/340] bump version to 4.5.5 --- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 4f4b42529..55fd8fbd3 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -84,7 +84,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.4 +#define CIPHER_VERSION_NUMBER 4.5.5 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index 0adaa53f2..257827729 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.4 community}} +} {{4.5.5 community}} db close file delete -force test.db From bfaa4f169ad4357dd17f04ad1f39145b0cbbe6f4 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 19 May 2023 10:01:41 -0400 Subject: [PATCH 098/340] Snapshot of upstream SQLite 3.42.0 --- Makefile.in | 43 +- Makefile.msc | 34 +- README.md | 2 +- VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 +- doc/lemon.html | 2 +- ext/fts5/fts5Int.h | 12 +- ext/fts5/fts5_aux.c | 8 +- ext/fts5/fts5_config.c | 26 +- ext/fts5/fts5_expr.c | 54 +- ext/fts5/fts5_index.c | 632 +++++++++-- ext/fts5/fts5_main.c | 24 + ext/fts5/test/fts5_common.tcl | 4 + ext/fts5/test/fts5ab.test | 6 +- ext/fts5/test/fts5af.test | 30 + ext/fts5/test/fts5ak.test | 26 + ext/fts5/test/fts5corrupt7.test | 99 ++ ext/fts5/test/fts5limits.test | 47 + ext/fts5/test/fts5misc.test | 62 +- ext/fts5/test/fts5secure.test | 278 +++++ ext/fts5/test/fts5secure2.test | 87 ++ ext/fts5/test/fts5secure3.test | 166 +++ ext/fts5/test/fts5secure4.test | 170 +++ ext/fts5/test/fts5secure5.test | 129 +++ ext/fts5/test/fts5secure6.test | 55 + ext/fts5/test/fts5securefault.test | 225 ++++ ext/fts5/test/fts5version.test | 76 +- ext/misc/base64.c | 25 +- ext/misc/base85.c | 24 +- ext/misc/basexx.c | 7 +- ext/misc/randomjson.c | 202 ++++ ext/misc/regexp.c | 4 +- ext/misc/series.c | 221 +++- ext/misc/shathree.c | 3 +- ext/misc/zipfile.c | 21 +- ext/rbu/rbuexlock.test | 81 ++ ext/rbu/sqlite3rbu.c | 20 +- ext/recover/dbdata.c | 7 +- ext/recover/recover1.test | 46 + ext/recover/recoverbuild.test | 42 + ext/recover/sqlite3recover.c | 2 +- ext/rtree/rtree.c | 21 +- ext/rtree/rtree6.test | 2 + ext/session/session2.test | 4 +- ext/session/sessionD.test | 24 - ext/session/sessionG.test | 18 +- ext/session/sessionH.test | 2 +- ext/session/session_common.tcl | 92 +- ext/session/sessionat.test | 12 +- ext/session/sessionbig.test | 4 +- ext/session/sessionfault.test | 2 +- ext/session/sessionfault2.test | 2 +- ext/session/sessionnoop2.test | 180 +++ ext/session/sessionrebase.test | 76 ++ ext/session/sessionrowid.test | 282 +++++ ext/session/sessionsize.test | 14 +- ext/session/sessionstat1.test | 12 +- ext/session/sqlite3session.c | 376 +++++-- ext/session/sqlite3session.h | 47 +- ext/session/test_session.c | 49 +- ext/wasm/GNUmakefile | 182 ++- ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see | 5 + ext/wasm/api/extern-post-js.c-pp.js | 30 +- ext/wasm/api/pre-js.c-pp.js | 6 +- ext/wasm/api/sqlite3-api-cleanup.js | 16 +- ext/wasm/api/sqlite3-api-glue.js | 19 +- ext/wasm/api/sqlite3-api-oo1.js | 4 +- ext/wasm/api/sqlite3-api-prologue.js | 89 +- ext/wasm/api/sqlite3-api-worker1.js | 12 +- ext/wasm/api/sqlite3-opfs-async-proxy.js | 24 +- ext/wasm/api/sqlite3-v-helper.js | 13 +- ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 118 +- ext/wasm/api/sqlite3-wasm.c | 126 ++- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js | 32 +- ext/wasm/api/sqlite3-worker1.c-pp.js | 16 +- ext/wasm/common/whwasmutil.js | 10 +- ext/wasm/demo-worker1-promiser.js | 4 +- ext/wasm/demo-worker1.js | 4 +- ext/wasm/dist.make | 3 +- ext/wasm/example_extra_init.c | 23 + ext/wasm/fiddle.make | 2 +- ext/wasm/jaccwabyt/jaccwabyt.js | 2 +- ext/wasm/speedtest1-worker.html | 9 +- ext/wasm/tester1.c-pp.js | 23 +- manifest | 393 ++++--- manifest.uuid | 2 +- src/alter.c | 23 + src/analyze.c | 60 +- src/backup.c | 13 +- src/btree.c | 368 ++++-- src/btreeInt.h | 9 +- src/build.c | 2 +- src/callback.c | 1 + src/date.c | 91 +- src/dbpage.c | 2 +- src/delete.c | 8 +- src/expr.c | 183 ++- src/fkey.c | 16 +- src/func.c | 2 +- src/global.c | 4 +- src/insert.c | 74 +- src/json.c | 1001 ++++++++++++++--- src/loadext.c | 10 +- src/main.c | 41 +- src/msvc.h | 4 + src/os_unix.c | 8 +- src/os_win.c | 13 +- src/pager.c | 22 +- src/parse.y | 23 +- src/pcache.c | 17 +- src/pragma.c | 25 +- src/prepare.c | 10 +- src/printf.c | 152 ++- src/resolve.c | 3 +- src/select.c | 205 +++- src/shell.c.in | 709 +++++++----- src/sqlite.h.in | 151 ++- src/sqliteInt.h | 43 +- src/test1.c | 71 ++ src/test_syscall.c | 26 +- src/trigger.c | 3 + src/update.c | 2 +- src/util.c | 20 +- src/vdbe.c | 38 +- src/vdbe.h | 4 + src/vdbeapi.c | 40 +- src/vdbeaux.c | 125 +- src/vdbeblob.c | 4 +- src/vdbemem.c | 22 +- src/vtab.c | 7 + src/wal.c | 33 +- src/where.c | 139 ++- src/wherecode.c | 90 +- src/whereexpr.c | 7 +- src/window.c | 1 + test/aggfault.test | 43 + test/altertab.test | 18 + test/analyze3.test | 27 + test/analyzeE.test | 52 + test/autoindex1.test | 1 + test/autoindex3.test | 1 + test/backup.test | 23 + test/bloom1.test | 12 + test/corrupt2.test | 12 +- test/corrupt3.test | 10 +- test/corrupt7.test | 4 +- test/corruptC.test | 4 +- test/corruptD.test | 2 +- test/corruptI.test | 7 +- test/countofview.test | 28 + test/cursorhint.test | 52 + test/date.test | 6 + test/fkey1.test | 11 + test/fts3join.test | 1 + test/func8.test | 64 ++ test/fuzzcheck.c | 25 +- test/fuzzinvariants.c | 64 +- test/in.test | 32 +- test/indexexpr1.test | 25 +- test/indexexpr2.test | 53 + test/join.test | 100 +- test/join8.test | 11 + test/joinH.test | 30 + test/json/README.md | 27 + test/json/json-generator.tcl | 401 +++++++ test/json/json-q1.txt | 4 + test/json/json-speed-check.sh | 80 ++ test/json101.test | 140 ++- test/json102.test | 33 +- test/json104.test | 42 + test/json501.test | 304 +++++ test/json502.test | 40 + test/misc1.test | 3 + test/pager2.test | 39 +- test/pragma.test | 83 +- test/printf.test | 49 +- test/pushdown.test | 106 +- test/returning1.test | 1 + test/scanstatus.test | 15 +- test/scanstatus2.test | 38 + test/seekscan1.test | 5 + test/selectC.test | 5 + test/selectH.test | 118 ++ test/shell1.test | 15 + test/shell2.test | 46 + test/skipscan1.test | 10 + test/speedtest1.c | 17 +- test/tabfunc01.test | 3 +- test/testrunner.tcl | 35 +- test/thread3.test | 78 ++ test/tkt-99378177930f87bd.test | 2 + test/vt02.c | 10 +- test/walpersist.test | 18 +- test/window1.test | 137 +++ test/with1.test | 2 +- test/with3.test | 1 + test/with6.test | 4 + test/zipfile.test | 21 + tool/mkkeywordhash.c | 13 +- tool/mkmsvcmin.tcl | 1 + tool/omittest.tcl | 64 +- tool/showwal.c | 11 +- tool/speed-check.sh | 3 + tool/split-sqlite3c.tcl | 9 +- 205 files changed, 10003 insertions(+), 2083 deletions(-) create mode 100644 ext/fts5/test/fts5corrupt7.test create mode 100644 ext/fts5/test/fts5limits.test create mode 100644 ext/fts5/test/fts5secure.test create mode 100644 ext/fts5/test/fts5secure2.test create mode 100644 ext/fts5/test/fts5secure3.test create mode 100644 ext/fts5/test/fts5secure4.test create mode 100644 ext/fts5/test/fts5secure5.test create mode 100644 ext/fts5/test/fts5secure6.test create mode 100644 ext/fts5/test/fts5securefault.test mode change 100755 => 100644 ext/misc/base64.c create mode 100644 ext/misc/randomjson.c create mode 100644 ext/recover/recoverbuild.test create mode 100644 ext/session/sessionnoop2.test create mode 100644 ext/session/sessionrowid.test create mode 100644 ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see create mode 100644 ext/wasm/example_extra_init.c create mode 100644 test/aggfault.test create mode 100644 test/func8.test create mode 100644 test/json/README.md create mode 100644 test/json/json-generator.tcl create mode 100644 test/json/json-q1.txt create mode 100755 test/json/json-speed-check.sh create mode 100644 test/json501.test create mode 100644 test/json502.test create mode 100644 test/selectH.test create mode 100644 test/thread3.test diff --git a/Makefile.in b/Makefile.in index 2a71bd2c5..61cb3ef0e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -602,17 +602,35 @@ SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC FUZZERSHELL_OPT = FUZZCHECK_OPT += -I$(TOP)/test FUZZCHECK_OPT += -I$(TOP)/ext/recover -FUZZCHECK_OPT += -DSQLITE_OMIT_LOAD_EXTENSION -FUZZCHECK_OPT += -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 -FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 -FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4 -FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS3_PARENTHESIS -FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS5 -FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE -FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY -FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB -FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB +FUZZCHECK_OPT += \ + -DSQLITE_OSS_FUZZ \ + -DSQLITE_ENABLE_BYTECODE_VTAB \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_BYTECODE_VTAB \ + -DSQLITE_ENABLE_DESERIALIZE \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_FTS3_PARENTHESIS \ + -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_FTS5 \ + -DSQLITE_ENABLE_GEOPOLY \ + -DSQLITE_ENABLE_MATH_FUNCTIONS \ + -DSQLITE_ENABLE_MEMSYS5 \ + -DSQLITE_ENABLE_NORMALIZE \ + -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_ENABLE_PREUPDATE_HOOK \ + -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_SESSION \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \ + -DSQLITE_ENABLE_STAT4 \ + -DSQLITE_ENABLE_STMT_SCANSTATUS \ + -DSQLITE_MAX_MEMORY=50000000 \ + -DSQLITE_MAX_MMAP_SIZE=0 \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_PRINTF_PRECISION_LIMIT=1000 \ + -DSQLITE_PRIVATE="" + FUZZCHECK_SRC += $(TOP)/test/fuzzcheck.c FUZZCHECK_SRC += $(TOP)/test/ossfuzz.c FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c @@ -679,6 +697,9 @@ fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h fuzzcheck$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) +fuzzcheck-asan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) + $(LTLINK) -o $@ -fsanitize=address $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) + ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \ $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) diff --git a/Makefile.msc b/Makefile.msc index 5528b8e72..87814894d 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1526,7 +1526,7 @@ TESTSRC = \ $(TOP)\ext\fts3\fts3_term.c \ $(TOP)\ext\fts3\fts3_test.c \ $(TOP)\ext\rbu\test_rbu.c \ - $(TOP)\ext\session\test_session.c + $(TOP)\ext\session\test_session.c # Statically linked extensions. # @@ -1671,6 +1671,33 @@ FUZZERSHELL_COMPILE_OPTS = FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -I$(TOP)\test -I$(TOP)\ext\recover FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MEMSYS5 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OSS_FUZZ +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBSTAT_VTAB +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DESERIALIZE +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS3_PARENTHESIS +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS4 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS5 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MATH_FUNCTIONS +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MEMSYS5 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_NORMALIZE +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_PREUPDATE_HOOK +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_RTREE +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_SESSION +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_STMTVTAB +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_STAT4 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MEMORY=50000000 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MMAP_SIZE=0 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRIVATE="" + FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION @@ -1787,7 +1814,10 @@ dbfuzz.exe: $(TOP)\test\dbfuzz.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(DBFUZZ_COMPILE_OPTS) $(TOP)\test\dbfuzz.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) fuzzcheck.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H) - $(LTLINK) $(NO_WARN) $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) + $(LTLINK) /F 8388608 $(NO_WARN) $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) + +fuzzcheck-asan.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H) + $(LTLINK) $(NO_WARN) /fsanitize=address $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) ossshell.exe: $(OSSSHELL_SRC) $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(FUZZCHECK_OPTS) $(OSSSHELL_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) diff --git a/README.md b/README.md index 7ad42061c..0df8b58c2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ and most of the documentation are managed separately. ## Version Control -SQLite sources are managed using the +SQLite sources are managed using [Fossil](https://www.fossil-scm.org/), a distributed version control system that was specifically designed and written to support SQLite development. The [Fossil repository](https://sqlite.org/src/timeline) contains the urtext. diff --git a/VERSION b/VERSION index 0805a14b4..476cebe06 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.41.2 +3.42.0 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index 8c06474a1..e26780e2e 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.41.2]) +AC_INIT([sqlite],[3.42.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 5d6de8181..29ca76b69 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.69 for sqlite 3.41.2. +# Generated by GNU Autoconf 2.69 for sqlite 3.42.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.41.2' -PACKAGE_STRING='sqlite 3.41.2' +PACKAGE_VERSION='3.42.0' +PACKAGE_STRING='sqlite 3.42.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1470,7 +1470,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.41.2 to adapt to many kinds of systems. +\`configure' configures sqlite 3.42.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1535,7 +1535,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.41.2:";; + short | recursive ) echo "Configuration of sqlite 3.42.0:";; esac cat <<\_ACEOF @@ -1665,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.41.2 +sqlite configure 3.42.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2084,7 +2084,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.41.2, which was +It was created by sqlite $as_me 3.42.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12457,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.41.2, which was +This file was extended by sqlite $as_me 3.42.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12523,7 +12523,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.41.2 +sqlite config.status 3.42.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/doc/lemon.html b/doc/lemon.html index 324b3f331..16aea8784 100644 --- a/doc/lemon.html +++ b/doc/lemon.html @@ -322,7 +322,7 @@

    3.2.1 Allocating The Parse Object On Stack

    • Declare a local variable of type "yyParser"
    • Initialize the variable using ParseInit() -
    • Pass a pointer to the variable in calls ot Parse() +
    • Pass a pointer to the variable in calls to Parse()
    • Deallocate substructure in the parse variable using ParseFinalize().
    diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index e7e7043c6..5d05da875 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -199,6 +199,7 @@ struct Fts5Config { int ePattern; /* FTS_PATTERN_XXX constant */ /* Values loaded from the %_config table */ + int iVersion; /* fts5 file format 'version' */ int iCookie; /* Incremented when %_config is modified */ int pgsz; /* Approximate page size used in %_data */ int nAutomerge; /* 'automerge' setting */ @@ -207,6 +208,7 @@ struct Fts5Config { int nHashSize; /* Bytes of memory for in-memory hash */ char *zRank; /* Name of rank function */ char *zRankArgs; /* Arguments to rank function */ + int bSecureDelete; /* 'secure-delete' */ /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ char **pzErrmsg; @@ -216,8 +218,11 @@ struct Fts5Config { #endif }; -/* Current expected value of %_config table 'version' field */ -#define FTS5_CURRENT_VERSION 4 +/* Current expected value of %_config table 'version' field. And +** the expected version if the 'secure-delete' option has ever been +** set on the table. */ +#define FTS5_CURRENT_VERSION 4 +#define FTS5_CURRENT_VERSION_SECUREDELETE 5 #define FTS5_CONTENT_NORMAL 0 #define FTS5_CONTENT_NONE 1 @@ -383,6 +388,7 @@ struct Fts5IndexIter { ** above. */ #define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 #define FTS5INDEX_QUERY_NOOUTPUT 0x0020 +#define FTS5INDEX_QUERY_SKIPHASH 0x0040 /* ** Create/destroy an Fts5Index object. @@ -537,7 +543,7 @@ int sqlite3Fts5GetVarintLen(u32 iVal); u8 sqlite3Fts5GetVarint(const unsigned char*, u64*); int sqlite3Fts5PutVarint(unsigned char *p, u64 v); -#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&b) +#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&(b)) #define fts5GetVarint sqlite3Fts5GetVarint #define fts5FastGetVarint32(a, iOff, nVal) { \ diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c index 77f6d5bab..b178f4733 100644 --- a/ext/fts5/fts5_aux.c +++ b/ext/fts5/fts5_aux.c @@ -163,7 +163,7 @@ static int fts5HighlightCb( if( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK; iPos = p->iPos++; - if( p->iRangeEnd>0 ){ + if( p->iRangeEnd>=0 ){ if( iPosiRangeStart || iPos>p->iRangeEnd ) return SQLITE_OK; if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff; } @@ -175,7 +175,7 @@ static int fts5HighlightCb( } if( iPos==p->iter.iEnd ){ - if( p->iRangeEnd && p->iter.iStartiRangeStart ){ + if( p->iRangeEnd>=0 && p->iter.iStartiRangeStart ){ fts5HighlightAppend(&rc, p, p->zOpen, -1); } fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); @@ -186,7 +186,7 @@ static int fts5HighlightCb( } } - if( p->iRangeEnd>0 && iPos==p->iRangeEnd ){ + if( p->iRangeEnd>=0 && iPos==p->iRangeEnd ){ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); p->iOff = iEndOff; if( iPos>=p->iter.iStart && iPositer.iEnd ){ @@ -221,6 +221,7 @@ static void fts5HighlightFunction( memset(&ctx, 0, sizeof(HighlightContext)); ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]); ctx.zClose = (const char*)sqlite3_value_text(apVal[2]); + ctx.iRangeEnd = -1; rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn); if( ctx.zIn ){ @@ -406,6 +407,7 @@ static void fts5SnippetFunction( iCol = sqlite3_value_int(apVal[0]); ctx.zOpen = fts5ValueToText(apVal[1]); ctx.zClose = fts5ValueToText(apVal[2]); + ctx.iRangeEnd = -1; zEllips = fts5ValueToText(apVal[3]); nToken = sqlite3_value_int(apVal[4]); diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c index ab1a846b1..7a4c7b817 100644 --- a/ext/fts5/fts5_config.c +++ b/ext/fts5/fts5_config.c @@ -550,6 +550,7 @@ int sqlite3Fts5ConfigParse( rc = SQLITE_ERROR; } + assert( (pRet->abUnindexed && pRet->azCol) || rc!=SQLITE_OK ); for(i=3; rc==SQLITE_OK && ibSecureDelete = (bVal ? 1 : 0); + } }else{ *pbBadkey = 1; } @@ -947,15 +960,20 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ rc = sqlite3_finalize(p); } - if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){ + if( rc==SQLITE_OK + && iVersion!=FTS5_CURRENT_VERSION + && iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE + ){ rc = SQLITE_ERROR; if( pConfig->pzErrmsg ){ assert( 0==*pConfig->pzErrmsg ); - *pConfig->pzErrmsg = sqlite3_mprintf( - "invalid fts5 file format (found %d, expected %d) - run 'rebuild'", - iVersion, FTS5_CURRENT_VERSION + *pConfig->pzErrmsg = sqlite3_mprintf("invalid fts5 file format " + "(found %d, expected %d or %d) - run 'rebuild'", + iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE ); } + }else{ + pConfig->iVersion = iVersion; } if( rc==SQLITE_OK ){ diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index e4072db7a..0e018420d 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -17,6 +17,10 @@ #include "fts5Int.h" #include "fts5parse.h" +#ifndef SQLITE_FTS5_MAX_EXPR_DEPTH +# define SQLITE_FTS5_MAX_EXPR_DEPTH 256 +#endif + /* ** All token types in the generated fts5parse.h file are greater than 0. */ @@ -57,11 +61,17 @@ struct Fts5Expr { ** FTS5_NOT (nChild, apChild valid) ** FTS5_STRING (pNear valid) ** FTS5_TERM (pNear valid) +** +** iHeight: +** Distance from this node to furthest leaf. This is always 0 for nodes +** of type FTS5_STRING and FTS5_TERM. For all other nodes it is one +** greater than the largest child value. */ struct Fts5ExprNode { int eType; /* Node type */ int bEof; /* True at EOF */ int bNomatch; /* True if entry is not a match */ + int iHeight; /* Distance to tree leaf nodes */ /* Next method for this node. */ int (*xNext)(Fts5Expr*, Fts5ExprNode*, int, i64); @@ -131,6 +141,31 @@ struct Fts5Parse { int bPhraseToAnd; /* Convert "a+b" to "a AND b" */ }; +/* +** Check that the Fts5ExprNode.iHeight variables are set correctly in +** the expression tree passed as the only argument. +*/ +#ifndef NDEBUG +static void assert_expr_depth_ok(int rc, Fts5ExprNode *p){ + if( rc==SQLITE_OK ){ + if( p->eType==FTS5_TERM || p->eType==FTS5_STRING || p->eType==0 ){ + assert( p->iHeight==0 ); + }else{ + int ii; + int iMaxChild = 0; + for(ii=0; iinChild; ii++){ + Fts5ExprNode *pChild = p->apChild[ii]; + iMaxChild = MAX(iMaxChild, pChild->iHeight); + assert_expr_depth_ok(SQLITE_OK, pChild); + } + assert( p->iHeight==iMaxChild+1 ); + } + } +} +#else +# define assert_expr_depth_ok(rc, p) +#endif + void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){ va_list ap; va_start(ap, zFmt); @@ -245,6 +280,8 @@ int sqlite3Fts5ExprNew( }while( sParse.rc==SQLITE_OK && t!=FTS5_EOF ); sqlite3Fts5ParserFree(pEngine, fts5ParseFree); + assert_expr_depth_ok(sParse.rc, sParse.pExpr); + /* If the LHS of the MATCH expression was a user column, apply the ** implicit column-filter. */ if( iColnCol && sParse.pExpr && sParse.rc==SQLITE_OK ){ @@ -407,7 +444,7 @@ int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){ Fts5Parse sParse; memset(&sParse, 0, sizeof(sParse)); - if( *pp1 ){ + if( *pp1 && p2 ){ Fts5Expr *p1 = *pp1; int nPhrase = p1->nPhrase + p2->nPhrase; @@ -432,7 +469,7 @@ int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){ } sqlite3_free(p2->apExprPhrase); sqlite3_free(p2); - }else{ + }else if( p2 ){ *pp1 = p2; } @@ -2206,6 +2243,7 @@ static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ } static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){ + int ii = p->nChild; if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){ int nByte = sizeof(Fts5ExprNode*) * pSub->nChild; memcpy(&p->apChild[p->nChild], pSub->apChild, nByte); @@ -2214,6 +2252,9 @@ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){ }else{ p->apChild[p->nChild++] = pSub; } + for( ; iinChild; ii++){ + p->iHeight = MAX(p->iHeight, p->apChild[ii]->iHeight + 1); + } } /* @@ -2244,6 +2285,7 @@ static Fts5ExprNode *fts5ParsePhraseToAnd( if( pRet ){ pRet->eType = FTS5_AND; pRet->nChild = nTerm; + pRet->iHeight = 1; fts5ExprAssignXNext(pRet); pParse->nPhrase--; for(ii=0; iiiHeight>SQLITE_FTS5_MAX_EXPR_DEPTH ){ + sqlite3Fts5ParseError(pParse, + "fts5 expression tree is too large (maximum depth %d)", + SQLITE_FTS5_MAX_EXPR_DEPTH + ); + sqlite3_free(pRet); + pRet = 0; + } } } } diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 694cc16e4..eaeeeff4f 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -302,6 +302,8 @@ struct Fts5Index { sqlite3_stmt *pIdxSelect; int nRead; /* Total number of blocks read */ + sqlite3_stmt *pDeleteFromIdx; + sqlite3_stmt *pDataVersion; i64 iStructVersion; /* data_version when pStruct read */ Fts5Structure *pStruct; /* Current db structure (or NULL) */ @@ -394,9 +396,6 @@ struct Fts5CResult { ** iLeafOffset: ** Byte offset within the current leaf that is the first byte of the ** position list data (one byte passed the position-list size field). -** rowid field of the current entry. Usually this is the size field of the -** position list data. The exception is if the rowid for the current entry -** is the last thing on the leaf page. ** ** pLeaf: ** Buffer containing current leaf page data. Set to NULL at EOF. @@ -955,6 +954,7 @@ static int fts5StructureDecode( rc = FTS5_CORRUPT; break; } + assert( pSeg!=0 ); i += fts5GetVarint32(&pData[i], pSeg->iSegid); i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst); i += fts5GetVarint32(&pData[i], pSeg->pgnoLast); @@ -985,6 +985,7 @@ static int fts5StructureDecode( */ static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){ fts5StructureMakeWritable(pRc, ppStruct); + assert( (ppStruct!=0 && (*ppStruct)!=0) || (*pRc)!=SQLITE_OK ); if( *pRc==SQLITE_OK ){ Fts5Structure *pStruct = *ppStruct; int nLevel = pStruct->nLevel; @@ -1443,42 +1444,25 @@ static int fts5DlidxLvlPrev(Fts5DlidxLvl *pLvl){ pLvl->bEof = 1; }else{ u8 *a = pLvl->pData->p; - i64 iVal; - int iLimit; - int ii; - int nZero = 0; - - /* Currently iOff points to the first byte of a varint. This block - ** decrements iOff until it points to the first byte of the previous - ** varint. Taking care not to read any memory locations that occur - ** before the buffer in memory. */ - iLimit = (iOff>9 ? iOff-9 : 0); - for(iOff--; iOff>iLimit; iOff--){ - if( (a[iOff-1] & 0x80)==0 ) break; - } - fts5GetVarint(&a[iOff], (u64*)&iVal); - pLvl->iRowid -= iVal; - pLvl->iLeafPgno--; + pLvl->iOff = 0; + fts5DlidxLvlNext(pLvl); + while( 1 ){ + int nZero = 0; + int ii = pLvl->iOff; + u64 delta = 0; - /* Skip backwards past any 0x00 varints. */ - for(ii=iOff-1; ii>=pLvl->iFirstOff && a[ii]==0x00; ii--){ - nZero++; - } - if( ii>=pLvl->iFirstOff && (a[ii] & 0x80) ){ - /* The byte immediately before the last 0x00 byte has the 0x80 bit - ** set. So the last 0x00 is only a varint 0 if there are 8 more 0x80 - ** bytes before a[ii]. */ - int bZero = 0; /* True if last 0x00 counts */ - if( (ii-8)>=pLvl->iFirstOff ){ - int j; - for(j=1; j<=8 && (a[ii-j] & 0x80); j++); - bZero = (j>8); + while( a[ii]==0 ){ + nZero++; + ii++; } - if( bZero==0 ) nZero--; + ii += sqlite3Fts5GetVarint(&a[ii], &delta); + + if( ii>=iOff ) break; + pLvl->iLeafPgno += nZero+1; + pLvl->iRowid += delta; + pLvl->iOff = ii; } - pLvl->iLeafPgno -= nZero; - pLvl->iOff = iOff - nZero; } return pLvl->bEof; @@ -1674,7 +1658,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ i64 iOff = pIter->iLeafOffset; ASSERT_SZLEAF_OK(pIter->pLeaf); - if( iOff>=pIter->pLeaf->szLeaf ){ + while( iOff>=pIter->pLeaf->szLeaf ){ fts5SegIterNextPage(p, pIter); if( pIter->pLeaf==0 ){ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; @@ -1773,10 +1757,12 @@ static void fts5SegIterInit( fts5SegIterSetNext(p, pIter); pIter->pSeg = pSeg; pIter->iLeafPgno = pSeg->pgnoFirst-1; - fts5SegIterNextPage(p, pIter); + do { + fts5SegIterNextPage(p, pIter); + }while( p->rc==SQLITE_OK && pIter->pLeaf && pIter->pLeaf->nn==4 ); } - if( p->rc==SQLITE_OK ){ + if( p->rc==SQLITE_OK && pIter->pLeaf ){ pIter->iLeafOffset = 4; assert( pIter->pLeaf!=0 ); assert_nc( pIter->pLeaf->nn>4 ); @@ -1970,7 +1956,7 @@ static void fts5SegIterNext_None( iOff = pIter->iLeafOffset; /* Next entry is on the next page */ - if( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){ + while( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){ fts5SegIterNextPage(p, pIter); if( p->rc || pIter->pLeaf==0 ) return; pIter->iRowid = 0; @@ -2163,7 +2149,7 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ Fts5Data *pLast = 0; int pgnoLast = 0; - if( pDlidx ){ + if( pDlidx && p->pConfig->iVersion==FTS5_CURRENT_VERSION ){ int iSegid = pIter->pSeg->iSegid; pgnoLast = fts5DlidxIterPgno(pDlidx); pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); @@ -2724,7 +2710,8 @@ static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ /* ** Move the seg-iter so that it points to the first rowid on page iLeafPgno. -** It is an error if leaf iLeafPgno does not exist or contains no rowids. +** It is an error if leaf iLeafPgno does not exist. Unless the db is +** a 'secure-delete' db, if it contains no rowids then this is also an error. */ static void fts5SegIterGotoPage( Fts5Index *p, /* FTS5 backend object */ @@ -2739,21 +2726,23 @@ static void fts5SegIterGotoPage( fts5DataRelease(pIter->pNextLeaf); pIter->pNextLeaf = 0; pIter->iLeafPgno = iLeafPgno-1; - fts5SegIterNextPage(p, pIter); - assert( p->rc!=SQLITE_OK || pIter->iLeafPgno==iLeafPgno ); - if( p->rc==SQLITE_OK && ALWAYS(pIter->pLeaf!=0) ){ + while( p->rc==SQLITE_OK ){ int iOff; - u8 *a = pIter->pLeaf->p; - int n = pIter->pLeaf->szLeaf; - + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf==0 ) break; iOff = fts5LeafFirstRowidOff(pIter->pLeaf); - if( iOff<4 || iOff>=n ){ - p->rc = FTS5_CORRUPT; - }else{ - iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); - pIter->iLeafOffset = iOff; - fts5SegIterLoadNPos(p, pIter); + if( iOff>0 ){ + u8 *a = pIter->pLeaf->p; + int n = pIter->pLeaf->szLeaf; + if( iOff<4 || iOff>=n ){ + p->rc = FTS5_CORRUPT; + }else{ + iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; + fts5SegIterLoadNPos(p, pIter); + } + break; } } } @@ -3468,7 +3457,7 @@ static void fts5MultiIterNew( if( iLevel<0 ){ assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); nSeg = pStruct->nSegment; - nSeg += (p->pHash ? 1 : 0); + nSeg += (p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH)); }else{ nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment); } @@ -3489,7 +3478,7 @@ static void fts5MultiIterNew( if( p->rc==SQLITE_OK ){ if( iLevel<0 ){ Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; - if( p->pHash ){ + if( p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH) ){ /* Add a segment iterator for the current contents of the hash table. */ Fts5SegIter *pIter = &pNew->aSeg[iIter++]; fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter); @@ -4244,7 +4233,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr); fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n); fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p); - fts5BufferAppendBlob(&p->rc, &buf, pData->szLeaf-iOff,&pData->p[iOff]); + fts5BufferAppendBlob(&p->rc, &buf,pData->szLeaf-iOff,&pData->p[iOff]); if( p->rc==SQLITE_OK ){ /* Set the szLeaf field */ fts5PutU16(&buf.p[2], (u16)buf.n); @@ -4522,16 +4511,16 @@ static void fts5IndexCrisismerge( ){ const int nCrisis = p->pConfig->nCrisisMerge; Fts5Structure *pStruct = *ppStruct; - int iLvl = 0; - - assert( p->rc!=SQLITE_OK || pStruct->nLevel>0 ); - while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){ - fts5IndexMergeLevel(p, &pStruct, iLvl, 0); - assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) ); - fts5StructurePromote(p, iLvl+1, pStruct); - iLvl++; + if( pStruct && pStruct->nLevel>0 ){ + int iLvl = 0; + while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){ + fts5IndexMergeLevel(p, &pStruct, iLvl, 0); + assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) ); + fts5StructurePromote(p, iLvl+1, pStruct); + iLvl++; + } + *ppStruct = pStruct; } - *ppStruct = pStruct; } static int fts5IndexReturn(Fts5Index *p){ @@ -4565,6 +4554,413 @@ static int fts5PoslistPrefix(const u8 *aBuf, int nMax){ return ret; } +/* +** Execute the SQL statement: +** +** DELETE FROM %_idx WHERE (segid, (pgno/2)) = ($iSegid, $iPgno); +** +** This is used when a secure-delete operation removes the last term +** from a segment leaf page. In that case the %_idx entry is removed +** too. This is done to ensure that if all instances of a token are +** removed from an fts5 database in secure-delete mode, no trace of +** the token itself remains in the database. +*/ +static void fts5SecureDeleteIdxEntry( + Fts5Index *p, /* FTS5 backend object */ + int iSegid, /* Id of segment to delete entry for */ + int iPgno /* Page number within segment */ +){ + if( iPgno!=1 ){ + assert( p->pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE ); + if( p->pDeleteFromIdx==0 ){ + fts5IndexPrepareStmt(p, &p->pDeleteFromIdx, sqlite3_mprintf( + "DELETE FROM '%q'.'%q_idx' WHERE (segid, (pgno/2)) = (?1, ?2)", + p->pConfig->zDb, p->pConfig->zName + )); + } + if( p->rc==SQLITE_OK ){ + sqlite3_bind_int(p->pDeleteFromIdx, 1, iSegid); + sqlite3_bind_int(p->pDeleteFromIdx, 2, iPgno); + sqlite3_step(p->pDeleteFromIdx); + p->rc = sqlite3_reset(p->pDeleteFromIdx); + } + } +} + +/* +** This is called when a secure-delete operation removes a position-list +** that overflows onto segment page iPgno of segment pSeg. This function +** rewrites node iPgno, and possibly one or more of its right-hand peers, +** to remove this portion of the position list. +** +** Output variable (*pbLastInDoclist) is set to true if the position-list +** removed is followed by a new term or the end-of-segment, or false if +** it is followed by another rowid/position list. +*/ +static void fts5SecureDeleteOverflow( + Fts5Index *p, + Fts5StructureSegment *pSeg, + int iPgno, + int *pbLastInDoclist +){ + const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE); + int pgno; + Fts5Data *pLeaf = 0; + assert( iPgno!=1 ); + + *pbLastInDoclist = 1; + for(pgno=iPgno; p->rc==SQLITE_OK && pgno<=pSeg->pgnoLast; pgno++){ + i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno); + int iNext = 0; + u8 *aPg = 0; + + pLeaf = fts5DataRead(p, iRowid); + if( pLeaf==0 ) break; + aPg = pLeaf->p; + + iNext = fts5GetU16(&aPg[0]); + if( iNext!=0 ){ + *pbLastInDoclist = 0; + } + if( iNext==0 && pLeaf->szLeaf!=pLeaf->nn ){ + fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext); + } + + if( iNext==0 ){ + /* The page contains no terms or rowids. Replace it with an empty + ** page and move on to the right-hand peer. */ + const u8 aEmpty[] = {0x00, 0x00, 0x00, 0x04}; + assert_nc( bDetailNone==0 || pLeaf->nn==4 ); + if( bDetailNone==0 ) fts5DataWrite(p, iRowid, aEmpty, sizeof(aEmpty)); + fts5DataRelease(pLeaf); + pLeaf = 0; + }else if( bDetailNone ){ + break; + }else if( iNext>=pLeaf->szLeaf || iNext<4 ){ + p->rc = FTS5_CORRUPT; + break; + }else{ + int nShift = iNext - 4; + int nPg; + + int nIdx = 0; + u8 *aIdx = 0; + + /* Unless the current page footer is 0 bytes in size (in which case + ** the new page footer will be as well), allocate and populate a + ** buffer containing the new page footer. Set stack variables aIdx + ** and nIdx accordingly. */ + if( pLeaf->nn>pLeaf->szLeaf ){ + int iFirst = 0; + int i1 = pLeaf->szLeaf; + int i2 = 0; + + aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2); + if( aIdx==0 ) break; + i1 += fts5GetVarint32(&aPg[i1], iFirst); + i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift); + if( i1nn ){ + memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1); + i2 += (pLeaf->nn-i1); + } + nIdx = i2; + } + + /* Modify the contents of buffer aPg[]. Set nPg to the new size + ** in bytes. The new page is always smaller than the old. */ + nPg = pLeaf->szLeaf - nShift; + memmove(&aPg[4], &aPg[4+nShift], nPg-4); + fts5PutU16(&aPg[2], nPg); + if( fts5GetU16(&aPg[0]) ) fts5PutU16(&aPg[0], 4); + if( nIdx>0 ){ + memcpy(&aPg[nPg], aIdx, nIdx); + nPg += nIdx; + } + sqlite3_free(aIdx); + + /* Write the new page to disk and exit the loop */ + assert( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, iRowid, aPg, nPg); + break; + } + } + fts5DataRelease(pLeaf); +} + +/* +** Completely remove the entry that pSeg currently points to from +** the database. +*/ +static void fts5DoSecureDelete( + Fts5Index *p, + Fts5SegIter *pSeg +){ + const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE); + int iSegid = pSeg->pSeg->iSegid; + u8 *aPg = pSeg->pLeaf->p; + int nPg = pSeg->pLeaf->nn; + int iPgIdx = pSeg->pLeaf->szLeaf; + + u64 iDelta = 0; + u64 iNextDelta = 0; + int iNextOff = 0; + int iOff = 0; + int nIdx = 0; + u8 *aIdx = 0; + int bLastInDoclist = 0; + int iIdx = 0; + int iStart = 0; + int iKeyOff = 0; + int iPrevKeyOff = 0; + int iDelKeyOff = 0; /* Offset of deleted key, if any */ + + nIdx = nPg-iPgIdx; + aIdx = sqlite3Fts5MallocZero(&p->rc, nIdx+16); + if( p->rc ) return; + memcpy(aIdx, &aPg[iPgIdx], nIdx); + + /* At this point segment iterator pSeg points to the entry + ** this function should remove from the b-tree segment. + ** + ** In detail=full or detail=column mode, pSeg->iLeafOffset is the + ** offset of the first byte in the position-list for the entry to + ** remove. Immediately before this comes two varints that will also + ** need to be removed: + ** + ** + the rowid or delta rowid value for the entry, and + ** + the size of the position list in bytes. + ** + ** Or, in detail=none mode, there is a single varint prior to + ** pSeg->iLeafOffset - the rowid or delta rowid value. + ** + ** This block sets the following variables: + ** + ** iStart: + ** iDelta: + */ + { + int iSOP; + if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){ + iStart = pSeg->iTermLeafOffset; + }else{ + iStart = fts5GetU16(&aPg[0]); + } + + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + assert_nc( iSOP<=pSeg->iLeafOffset ); + + if( bDetailNone ){ + while( iSOPiLeafOffset ){ + if( aPg[iSOP]==0x00 ) iSOP++; + if( aPg[iSOP]==0x00 ) iSOP++; + iStart = iSOP; + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + } + + iNextOff = iSOP; + if( iNextOffiEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++; + if( iNextOffiEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++; + + }else{ + int nPos = 0; + iSOP += fts5GetVarint32(&aPg[iSOP], nPos); + while( iSOPiLeafOffset ){ + iStart = iSOP + (nPos/2); + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + iSOP += fts5GetVarint32(&aPg[iSOP], nPos); + } + assert_nc( iSOP==pSeg->iLeafOffset ); + iNextOff = pSeg->iLeafOffset + pSeg->nPos; + } + } + + iOff = iStart; + if( iNextOff>=iPgIdx ){ + int pgno = pSeg->iLeafPgno+1; + fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); + iNextOff = iPgIdx; + }else{ + /* Set bLastInDoclist to true if the entry being removed is the last + ** in its doclist. */ + for(iIdx=0, iKeyOff=0; iIdxiTermLeafOffset && pSeg->iLeafPgno==pSeg->iTermLeafPgno + ){ + /* The entry being removed was the only position list in its + ** doclist. Therefore the term needs to be removed as well. */ + int iKey = 0; + for(iIdx=0, iKeyOff=0; iIdx(u32)iStart ) break; + iKeyOff += iVal; + } + + iDelKeyOff = iOff = iKeyOff; + if( iNextOff!=iPgIdx ){ + int nPrefix = 0; + int nSuffix = 0; + int nPrefix2 = 0; + int nSuffix2 = 0; + + iDelKeyOff = iNextOff; + iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2); + iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2); + + if( iKey!=1 ){ + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix); + } + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix); + + nPrefix = MIN(nPrefix, nPrefix2); + nSuffix = (nPrefix2 + nSuffix2) - nPrefix; + + if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){ + p->rc = FTS5_CORRUPT; + }else{ + if( iKey!=1 ){ + iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); + } + iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); + if( nPrefix2>nPrefix ){ + memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); + iOff += (nPrefix2-nPrefix); + } + memmove(&aPg[iOff], &aPg[iNextOff], nSuffix2); + iOff += nSuffix2; + iNextOff += nSuffix2; + } + } + }else if( iStart==4 ){ + int iPgno; + + assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno ); + /* The entry being removed may be the only position list in + ** its doclist. */ + for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ + Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); + int bEmpty = (pPg && pPg->nn==4); + fts5DataRelease(pPg); + if( bEmpty==0 ) break; + } + + if( iPgno==pSeg->iTermLeafPgno ){ + i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); + Fts5Data *pTerm = fts5DataRead(p, iId); + if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ + u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; + int nTermIdx = pTerm->nn - pTerm->szLeaf; + int iTermIdx = 0; + int iTermOff = 0; + + while( 1 ){ + u32 iVal = 0; + int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal); + iTermOff += iVal; + if( (iTermIdx+nByte)>=nTermIdx ) break; + iTermIdx += nByte; + } + nTermIdx = iTermIdx; + + memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); + fts5PutU16(&pTerm->p[2], iTermOff); + + fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); + if( nTermIdx==0 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); + } + } + fts5DataRelease(pTerm); + } + } + + if( p->rc==SQLITE_OK ){ + const int nMove = nPg - iNextOff; + int nShift = 0; + + memmove(&aPg[iOff], &aPg[iNextOff], nMove); + iPgIdx -= (iNextOff - iOff); + nPg = iPgIdx; + fts5PutU16(&aPg[2], iPgIdx); + + nShift = iNextOff - iOff; + for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdxiOff ){ + iKeyOff -= nShift; + nShift = 0; + } + nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff); + iPrevKeyOff = iKeyOff; + } + } + + if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); + } + + assert_nc( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg); + } + sqlite3_free(aIdx); +} + +/* +** This is called as part of flushing a delete to disk in 'secure-delete' +** mode. It edits the segments within the database described by argument +** pStruct to remove the entries for term zTerm, rowid iRowid. +*/ +static void fts5FlushSecureDelete( + Fts5Index *p, + Fts5Structure *pStruct, + const char *zTerm, + i64 iRowid +){ + const int f = FTS5INDEX_QUERY_SKIPHASH; + int nTerm = (int)strlen(zTerm); + Fts5Iter *pIter = 0; /* Used to find term instance */ + + fts5MultiIterNew(p, pStruct, f, 0, (const u8*)zTerm, nTerm, -1, 0, &pIter); + if( fts5MultiIterEof(p, pIter)==0 ){ + i64 iThis = fts5MultiIterRowid(pIter); + if( iThisrc==SQLITE_OK + && fts5MultiIterEof(p, pIter)==0 + && iRowid==fts5MultiIterRowid(pIter) + ){ + Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + fts5DoSecureDelete(p, pSeg); + } + } + + fts5MultiIterFree(pIter); +} + + /* ** Flush the contents of in-memory hash table iHash to a new level-0 ** segment on disk. Also update the corresponding structure record. @@ -4587,6 +4983,7 @@ static void fts5FlushOneHash(Fts5Index *p){ if( iSegid ){ const int pgsz = p->pConfig->pgsz; int eDetail = p->pConfig->eDetail; + int bSecureDelete = p->pConfig->bSecureDelete; Fts5StructureSegment *pSeg; /* New segment within pStruct */ Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ @@ -4609,40 +5006,77 @@ static void fts5FlushOneHash(Fts5Index *p){ } while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ const char *zTerm; /* Buffer containing term */ + int nTerm; /* Size of zTerm in bytes */ const u8 *pDoclist; /* Pointer to doclist for this term */ int nDoclist; /* Size of doclist in bytes */ - /* Write the term for this entry to disk. */ + /* Get the term and doclist for this entry. */ sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); - fts5WriteAppendTerm(p, &writer, (int)strlen(zTerm), (const u8*)zTerm); - if( p->rc!=SQLITE_OK ) break; + nTerm = (int)strlen(zTerm); + if( bSecureDelete==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + if( p->rc!=SQLITE_OK ) break; + assert( writer.bFirstRowidInPage==0 ); + } - assert( writer.bFirstRowidInPage==0 ); - if( pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ + if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ /* The entire doclist will fit on the current leaf. */ fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); }else{ + int bTermWritten = !bSecureDelete; i64 iRowid = 0; - u64 iDelta = 0; + i64 iPrev = 0; int iOff = 0; /* The entire doclist will not fit on this leaf. The following ** loop iterates through the poslists that make up the current ** doclist. */ while( p->rc==SQLITE_OK && iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ + iOff++; + continue; + } + } + } + + if( p->rc==SQLITE_OK && bTermWritten==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + bTermWritten = 1; + assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 ); + } if( writer.bFirstRowidInPage ){ fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); writer.bFirstRowidInPage = 0; fts5WriteDlidxAppend(p, &writer, iRowid); - if( p->rc!=SQLITE_OK ) break; }else{ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iDelta); + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid-iPrev); } + if( p->rc!=SQLITE_OK ) break; assert( pBuf->n<=pBuf->nSpace ); + iPrev = iRowid; if( eDetail==FTS5_DETAIL_NONE ){ if( iOffnLevel==0 ){ - fts5StructureAddLevel(&p->rc, &pStruct); - } - fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); - if( p->rc==SQLITE_OK ){ - pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; - pSeg->iSegid = iSegid; - pSeg->pgnoFirst = 1; - pSeg->pgnoLast = pgnoLast; - pStruct->nSegment++; + assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); + if( pgnoLast>0 ){ + /* Update the Fts5Structure. It is written back to the database by the + ** fts5StructureRelease() call below. */ + if( pStruct->nLevel==0 ){ + fts5StructureAddLevel(&p->rc, &pStruct); + } + fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); + if( p->rc==SQLITE_OK ){ + pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; + pSeg->iSegid = iSegid; + pSeg->pgnoFirst = 1; + pSeg->pgnoLast = pgnoLast; + pStruct->nSegment++; + } + fts5StructurePromote(p, 0, pStruct); } - fts5StructurePromote(p, 0, pStruct); } fts5IndexAutomerge(p, &pStruct, pgnoLast); @@ -5455,6 +5892,7 @@ int sqlite3Fts5IndexClose(Fts5Index *p){ sqlite3_finalize(p->pIdxDeleter); sqlite3_finalize(p->pIdxSelect); sqlite3_finalize(p->pDataVersion); + sqlite3_finalize(p->pDeleteFromIdx); sqlite3Fts5HashFree(p->pHash); sqlite3_free(p->zDataTbl); sqlite3_free(p); @@ -6085,6 +6523,7 @@ static void fts5IndexIntegrityCheckSegment( Fts5StructureSegment *pSeg /* Segment to check internal consistency */ ){ Fts5Config *pConfig = p->pConfig; + int bSecureDelete = (pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE); sqlite3_stmt *pStmt = 0; int rc2; int iIdxPrevLeaf = pSeg->pgnoFirst-1; @@ -6120,7 +6559,19 @@ static void fts5IndexIntegrityCheckSegment( ** is also a rowid pointer within the leaf page header, it points to a ** location before the term. */ if( pLeaf->nn<=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + + if( nIdxTerm==0 + && pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE + && pLeaf->nn==pLeaf->szLeaf + && pLeaf->nn==4 + ){ + /* special case - the very first page in a segment keeps its %_idx + ** entry even if all the terms are removed from it by secure-delete + ** operations. */ + }else{ + p->rc = FTS5_CORRUPT; + } + }else{ int iOff; /* Offset of first term on leaf */ int iRowidOff; /* Offset of first rowid on leaf */ @@ -6184,9 +6635,12 @@ static void fts5IndexIntegrityCheckSegment( ASSERT_SZLEAF_OK(pLeaf); if( iRowidOff>=pLeaf->szLeaf ){ p->rc = FTS5_CORRUPT; - }else{ + }else if( bSecureDelete==0 || iRowidOff>0 ){ + i64 iDlRowid = fts5DlidxIterRowid(pDlidx); fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid); - if( iRowid!=fts5DlidxIterRowid(pDlidx) ) p->rc = FTS5_CORRUPT; + if( iRowidrc = FTS5_CORRUPT; + } } fts5DataRelease(pLeaf); } diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 5392b3ba0..13921ce49 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -1623,6 +1623,8 @@ static int fts5UpdateMethod( Fts5Config *pConfig = pTab->p.pConfig; int eType0; /* value_type() of apVal[0] */ int rc = SQLITE_OK; /* Return code */ + int bUpdateOrDelete = 0; + /* A transaction must be open when this is called. */ assert( pTab->ts.eState==1 || pTab->ts.eState==2 ); @@ -1633,6 +1635,11 @@ static int fts5UpdateMethod( || sqlite3_value_type(apVal[0])==SQLITE_NULL ); assert( pTab->p.pConfig->pzErrmsg==0 ); + if( pConfig->pgsz==0 ){ + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + if( rc!=SQLITE_OK ) return rc; + } + pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; /* Put any active cursors into REQUIRE_SEEK state. */ @@ -1685,6 +1692,7 @@ static int fts5UpdateMethod( else if( nArg==1 ){ i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0); + bUpdateOrDelete = 1; } /* INSERT or UPDATE */ @@ -1700,6 +1708,7 @@ static int fts5UpdateMethod( if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){ i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); + bUpdateOrDelete = 1; } fts5StorageInsert(&rc, pTab, apVal, pRowid); } @@ -1728,10 +1737,24 @@ static int fts5UpdateMethod( rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); fts5StorageInsert(&rc, pTab, apVal, pRowid); } + bUpdateOrDelete = 1; } } } + if( rc==SQLITE_OK + && bUpdateOrDelete + && pConfig->bSecureDelete + && pConfig->iVersion==FTS5_CURRENT_VERSION + ){ + rc = sqlite3Fts5StorageConfigValue( + pTab->pStorage, "version", 0, FTS5_CURRENT_VERSION_SECUREDELETE + ); + if( rc==SQLITE_OK ){ + pConfig->iVersion = FTS5_CURRENT_VERSION_SECUREDELETE; + } + } + pTab->p.pConfig->pzErrmsg = 0; return rc; } @@ -2591,6 +2614,7 @@ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint); fts5TripCursors(pTab); + pTab->p.pConfig->pgsz = 0; return sqlite3Fts5StorageRollback(pTab->pStorage); } diff --git a/ext/fts5/test/fts5_common.tcl b/ext/fts5/test/fts5_common.tcl index 0f371dcfd..9c012932d 100644 --- a/ext/fts5/test/fts5_common.tcl +++ b/ext/fts5/test/fts5_common.tcl @@ -594,6 +594,10 @@ proc nearset_rc {aCol args} { list } +proc dump {tname} { + execsql_pp "SELECT * FROM ${tname}_idx" + execsql_pp "SELECT id, quote(block), fts5_decode(id,block) FROM ${tname}_data" +} #------------------------------------------------------------------------- # Code for a simple Tcl tokenizer that supports synonyms at query time. diff --git a/ext/fts5/test/fts5ab.test b/ext/fts5/test/fts5ab.test index 3979dd44b..5aa745658 100644 --- a/ext/fts5/test/fts5ab.test +++ b/ext/fts5/test/fts5ab.test @@ -180,7 +180,11 @@ if {[detail_is_full]} { } {1 2} } -do_execsql_test 4.5 { +do_execsql_test 4.5.1 { + SELECT rowid FROM s1 WHERE s1 MATCH 'a AND x' +} {1 2} + +do_execsql_test 4.5.2 { SELECT rowid FROM s1 WHERE s1 MATCH 'a x' } {1 2} diff --git a/ext/fts5/test/fts5af.test b/ext/fts5/test/fts5af.test index a3ff330ef..3d7929509 100644 --- a/ext/fts5/test/fts5af.test +++ b/ext/fts5/test/fts5af.test @@ -193,4 +193,34 @@ do_execsql_test 5.6 { } ;# foreach_detail_mode +reset_db +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE t1 USING fts5(colA, colB); + INSERT INTO t1 VALUES('A B C', 'D E F'); +} + +do_execsql_test 6.1 { + SELECT colA, colB, snippet(t1,0,'[', ']','...',1) FROM t1 WHERE t1 MATCH 'B'; +} {{A B C} {D E F} ...[B]...} +breakpoint +do_execsql_test 6.2 { + SELECT colA, colB, snippet(t1, 1,'[',']','...',2) FROM t1 WHERE t1 MATCH 'B'; +} {{A B C} {D E F} {D E...}} +do_execsql_test 6.3 { + SELECT colA, colB, snippet(t1, 1,'[',']','...',1) FROM t1 WHERE t1 MATCH 'B'; +} {{A B C} {D E F} {D...}} + +do_execsql_test 6.1 { + SELECT colA, colB, snippet(t1,0,'[', ']','...',1) FROM t1 WHERE t1 MATCH 'A'; +} {{A B C} {D E F} [A]...} +breakpoint +do_execsql_test 6.2 { + SELECT colA, colB, snippet(t1, 1,'[',']','...',2) FROM t1 WHERE t1 MATCH 'A'; +} {{A B C} {D E F} {D E...}} +do_execsql_test 6.3 { + SELECT colA, colB, snippet(t1, 1,'[',']','...',1) FROM t1 WHERE t1 MATCH 'A'; +} {{A B C} {D E F} {D...}} + + + finish_test diff --git a/ext/fts5/test/fts5ak.test b/ext/fts5/test/fts5ak.test index 0a3cd6a78..e248f2a32 100644 --- a/ext/fts5/test/fts5ak.test +++ b/ext/fts5/test/fts5ak.test @@ -154,4 +154,30 @@ do_execsql_test 3.2 { } +# 2023-04-06 https://sqlite.org/forum/forumpost/cae4367d9b +# +# This is not a test of FTS5, but rather a test of the of what happens to +# prepared statements that encounter SQLITE_SCHEMA while other prepared +# statements are running. The original problem POC used FTS5, and so +# is seems reasonable to put the test here. +# +# The vdbeaux24.test module in TH3 also tests this same behavior but +# without requiring FTS5 or an other extension. +# +reset_db +db null NULL +do_execsql_test 4.0 { + CREATE TABLE t5(a PRIMARY KEY); + INSERT INTO t5 VALUES(0); + CREATE VIRTUAL TABLE t6 USING fts5(0); + DELETE FROM t6; + CREATE TABLE t7(x); + WITH cte(a) AS ( + SELECT a FROM t5 + WHERE ((0,0) IN (SELECT 0, LAG(0) OVER (PARTITION BY 0) FROM t6), 0) + < (a,0) + ) + SELECT max(a) FROM cte; +} NULL + finish_test diff --git a/ext/fts5/test/fts5corrupt7.test b/ext/fts5/test/fts5corrupt7.test new file mode 100644 index 000000000..75995a7c0 --- /dev/null +++ b/ext/fts5/test/fts5corrupt7.test @@ -0,0 +1,99 @@ +# 2023 April 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5corrupt7 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} +sqlite3_fts5_may_be_corrupt 1 + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('pgsz', 32); +} + +set doc [string repeat "a b " 30] + +do_execsql_test 1.1 { + BEGIN; + INSERT INTO t1(rowid, x) VALUES(123, $doc); + INSERT INTO t1(rowid, x) VALUES(124, $doc); + COMMIT; +} + +execsql_pp { + SELECT id, fts5_decode(id, block), quote(block) FROM t1_data +} + +set rows [db eval { SELECT rowid FROM t1_data }] +db_save_and_close + +foreach r $rows { + db_restore_and_reopen + + proc edit_block {b} { + binary scan $b c* in + set out [lreplace $in 0 1 255 255] + binary format c* $out + } + db func edit_block edit_block + + do_execsql_test 1.2.$r.1 { + UPDATE t1_data SET block = edit_block(block) WHERE rowid=$r; + } + + do_execsql_test 1.2.$r.2 { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } + + do_test 1.2.$r.3 { + catchsql { DELETE FROM t1 WHERE rowid=123; } + catchsql { DELETE FROM t1 WHERE rowid=124; } + set {} {} + } {} + + db close +} + +foreach r $rows { +set r 137438953475 + db_restore_and_reopen + + proc edit_block {b} { + binary scan $b c* in + set out [lreplace $in end end 127] + binary format c* $out + } + db func edit_block edit_block + + do_execsql_test 1.2.$r.1 { + UPDATE t1_data SET block = edit_block(block) WHERE rowid=$r; + } + + do_execsql_test 1.2.$r.2 { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } + + do_test 1.2.$r.3 { + catchsql { DELETE FROM t1 WHERE rowid=124; } + catchsql { DELETE FROM t1 WHERE rowid=123; } + set {} {} + } {} + + db close +} + +finish_test diff --git a/ext/fts5/test/fts5limits.test b/ext/fts5/test/fts5limits.test new file mode 100644 index 000000000..90d175aa3 --- /dev/null +++ b/ext/fts5/test/fts5limits.test @@ -0,0 +1,47 @@ +# 2023 May 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5limits +return_if_no_fts5 + + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(x); +} + +# Default limit for expression depth is 256 +# +foreach {tn nRepeat op bErr} { + 1 200 AND 0 + 2 200 NOT 0 + 3 200 OR 0 + + 4 260 AND 0 + 5 260 NOT 1 + 6 260 OR 0 +} { + set L [string repeat "abc " $nRepeat] + set Q [join $L " $op "] + + set res {0 {}} + if {$bErr} { + set res "1 {fts5 expression tree is too large (maximum depth 256)}" + } + + do_catchsql_test 1.$tn { + SELECT * FROM ft($Q) + } $res +} + +finish_test + diff --git a/ext/fts5/test/fts5misc.test b/ext/fts5/test/fts5misc.test index 416b4c808..da3f65269 100644 --- a/ext/fts5/test/fts5misc.test +++ b/ext/fts5/test/fts5misc.test @@ -329,7 +329,7 @@ do_execsql_test 12.3 { reset_db sqlite3_db_config db DEFENSIVE 1 -do_execsql_test 13.0 { +do_execsql_test 13.1.0 { CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT); CREATE VIRTUAL TABLE b USING fts5(name); CREATE TRIGGER a_trigger AFTER INSERT ON a BEGIN @@ -337,18 +337,44 @@ do_execsql_test 13.0 { END; } -do_test 13.1 { +do_test 13.1.1 { set ::STMT [ sqlite3_prepare db "INSERT INTO a VALUES (1, 'foo') RETURNING id;" -1 dummy ] sqlite3_step $::STMT } {SQLITE_ROW} -do_test 13.2 { +do_test 13.1.2 { sqlite3_finalize $::STMT } {SQLITE_OK} -do_test 13.3 { +do_test 13.1.3 { + sqlite3_errmsg db +} {not an error} + +reset_db +sqlite3_db_config db DEFENSIVE 1 +do_execsql_test 13.2.0 { + BEGIN; + CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT); + CREATE VIRTUAL TABLE b USING fts5(name); + CREATE TRIGGER a_trigger AFTER INSERT ON a BEGIN + INSERT INTO b (name) VALUES ('foo'); + END; +} + +do_test 13.2.1 { + set ::STMT [ + sqlite3_prepare db "INSERT INTO a VALUES (1, 'foo') RETURNING id;" -1 dummy + ] + sqlite3_step $::STMT +} {SQLITE_ROW} + +do_test 13.2.2 { + sqlite3_finalize $::STMT +} {SQLITE_OK} + +do_test 13.2.3 { sqlite3_errmsg db } {not an error} @@ -443,5 +469,33 @@ do_execsql_test -db db2 16.6 { SELECT * FROM x1 } {abc def} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 17.1 { + CREATE VIRTUAL TABLE ft USING fts5(x, tokenize="unicode61 separators 'X'"); +} +do_execsql_test 17.2 { + SELECT 0 FROM ft WHERE ft MATCH 'X' AND ft MATCH 'X' +} +do_execsql_test 17.3 { + SELECT 0 FROM ft('X') +} + +do_execsql_test 17.4 { + CREATE VIRTUAL TABLE t0 USING fts5(c0, t="trigram"); + INSERT INTO t0 VALUES('assertionfaultproblem'); +} +do_execsql_test 17.5 { + SELECT 0 FROM t0(0) WHERE c0 GLOB 0; +} {} + +do_execsql_test 17.5 { + SELECT c0 FROM t0 WHERE c0 GLOB '*f*'; +} {assertionfaultproblem} +do_execsql_test 17.5 { + SELECT c0 FROM t0 WHERE c0 GLOB '*faul*'; +} {assertionfaultproblem} + + finish_test diff --git a/ext/fts5/test/fts5secure.test b/ext/fts5/test/fts5secure.test new file mode 100644 index 000000000..50d84cef7 --- /dev/null +++ b/ext/fts5/test/fts5secure.test @@ -0,0 +1,278 @@ +# 2023 Feb 17 +# +# 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +ifcapable !fts5 { finish_test ; return } +set ::testprefix fts5secure + +proc dump {tname} { + execsql_pp "SELECT * FROM ${tname}_idx" + execsql_pp "SELECT id, quote(block), fts5_decode(id,block) FROM ${tname}_data" +} + + +do_execsql_test 0.0 { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + CREATE VIRTUAL TABLE v1 USING fts5vocab('t1', 'instance'); + INSERT INTO t1(rowid, ab) VALUES + (0,'abc'), (1,'abc'), (2,'abc'), (3,'abc'), (4,'def'); +} + +do_execsql_test 0.1 { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); +} + +do_execsql_test 0.2 { + DELETE FROM t1 WHERE rowid=2; +} + +do_execsql_test 0.3 { + SELECT count(*) FROM t1_data +} 3 + +do_execsql_test 0.4 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +do_execsql_test 0.5 { + DELETE FROM t1 WHERE rowid=3; +} + +do_execsql_test 0.6 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +do_execsql_test 0.7 { + DELETE FROM t1 WHERE rowid=0; +} + +do_execsql_test 0.8 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +#---------------------------------- + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t2 USING fts5(ab); + INSERT INTO t2(rowid, ab) VALUES (5, 'key'), (6, 'value'); + INSERT INTO t2(t2, rank) VALUES('secure-delete', 1); +} + +#execsql_pp { SELECT id, quote(block) FROM t1_data } +#execsql_pp { SELECT segid, quote(term), pgno FROM t1_idx } + +do_execsql_test 1.1 { + DELETE FROM t2 WHERE rowid = 5; +} + +do_execsql_test 1.2 { + INSERT INTO t2(t2) VALUES('integrity-check'); +} + +do_execsql_test 1.3 { + DELETE FROM t2 WHERE rowid = 6; +} + +do_execsql_test 1.4 { + INSERT INTO t2(t2) VALUES('integrity-check'); +} + +do_execsql_test 1.5 { + SELECT * FROM t2('value'); + SELECT * FROM t2('v*'); +} + +do_execsql_test 1.6 { + SELECT * FROM t2('value') ORDER BY rowid DESC; + SELECT * FROM t2('v*') ORDER BY rowid DESC; +} +execsql_pp { + SELECT id, quote(block) FROM t2_data; +} + +#---------------------------------- + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(ab); + CREATE VIRTUAL TABLE vocab USING fts5vocab('ft', 'instance'); + INSERT INTO ft(rowid, ab) VALUES + (1, 'one'), + (2, 'two'), + (3, 'three'), + (4, 'four'), + (5, 'one one'), + (6, 'one two'), + (7, 'one three'), + (8, 'one four'), + (9, 'two one'), + (10, 'two two'), + (11, 'two three'), + (12, 'two four'), + (13, 'three one'), + (14, 'three two'), + (15, 'three three'), + (16, 'three four'); +} + +do_execsql_test 2.1 { + SELECT count(*) FROM ft_data; +} {3} + +do_execsql_test 2.2 { + INSERT INTO ft(ft, rank) VALUES('secure-delete', 1); +} + +do_execsql_test 2.3 { + DELETE FROM ft WHERE rowid=9; +} + +do_execsql_test 2.4 { + INSERT INTO ft(ft) VALUES('integrity-check'); +} + +do_execsql_test 2.5 { + DELETE FROM ft WHERE ab LIKE '%two%' +} + +do_execsql_test 2.6 { + INSERT INTO ft(ft) VALUES('integrity-check'); +} + +do_execsql_test 2.7 { + SELECT count(*) FROM ft_data; +} {3} + +#---------------------------------- +reset_db + +set ::vocab { + one two three four five six seven eight nine ten + eleven twelve thirteen fourteen fifteen sixteen + seventeen eighteen nineteen twenty +} +proc rnddoc {} { + set nVocab [llength $::vocab] + set ret [list] + for {set ii 0} {$ii < 8} {incr ii} { + lappend ret [lindex $::vocab [expr int(abs(rand()) * $nVocab)]] + } + set ret +} + +proc contains {list val} { + expr {[lsearch $list $val]>=0} +} + +foreach {tn pgsz} { + 2 64 + 1 1000 +} { + reset_db + db function rnddoc rnddoc + db function contains contains + + expr srand(1) + + do_execsql_test 3.$tn.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('pgsz', $pgsz); + WITH s(i) AS ( + VALUES(1) UNION SELECT i+1 FROM s WHERE i<20 + ) + INSERT INTO t1 SELECT rnddoc() FROM s; + } + + do_execsql_test 3.$tn.1 { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } + + foreach {rowid} { + 6 16 3 4 9 14 13 7 20 15 19 10 11 2 5 18 17 1 12 8 + } { + + do_execsql_test 3.$tn.2.$rowid { + DELETE FROM t1 WHERE rowid=$rowid; + } + do_execsql_test 3.$tn.2.$rowid.ic { + INSERT INTO t1(t1) VALUES('integrity-check'); + } + + foreach v $::vocab { + do_execsql_test 3.$tn.2.$rowid.q.$v { + SELECT rowid FROM t1($v) + } [db eval {SELECT rowid FROM t1 WHERE contains(x, $v)}] + + do_execsql_test 3.$tn.2.$rowid.q.$v.DESC { + SELECT rowid FROM t1($v) ORDER BY 1 DESC + } [db eval {SELECT rowid FROM t1 WHERE contains(x, $v) ORDER BY 1 DESC}] + } + } +} + +do_execsql_test 3.3 { + INSERT INTO t1(x) VALUES('optimize'); + INSERT INTO t1(t1) VALUES('optimize'); + SELECT count(*) FROM t1_data; +} {3} + +#---------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('pgsz', 32); + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); +} + +set L1 [string repeat abcdefghij 10] +set L2 [string repeat 1234567890 10] + +do_execsql_test 4.1 { + INSERT INTO t1 VALUES('aa' || $L1 || ' ' || $L2); +} +do_execsql_test 4.2 { + DELETE FROM t1 WHERE rowid=1 +} +do_execsql_test 4.3 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +#---------------------------------- +reset_db +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('pgsz', 32); + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); +} + +set doc "aa [string repeat {abc } 60]" + +do_execsql_test 5.1 { + BEGIN; + INSERT INTO t1 VALUES($doc); + INSERT INTO t1 VALUES('aa abc'); + COMMIT; +} + +do_execsql_test 5.2 { + DELETE FROM t1 WHERE rowid = 1; +} + +do_execsql_test 5.3 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2 +do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2 + + +finish_test + diff --git a/ext/fts5/test/fts5secure2.test b/ext/fts5/test/fts5secure2.test new file mode 100644 index 000000000..04ff66219 --- /dev/null +++ b/ext/fts5/test/fts5secure2.test @@ -0,0 +1,87 @@ +# 2023 Feb 17 +# +# 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +ifcapable !fts5 { finish_test ; return } +set ::testprefix fts5secure2 + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(col); + INSERT INTO ft VALUES('data for the table'); + INSERT INTO ft VALUES('more of the same'); + INSERT INTO ft VALUES('and extra data'); +} + +do_execsql_test 1.1 { + SELECT * FROM ft_config +} {version 4} + +do_execsql_test 1.2 { + INSERT INTO ft(ft, rank) VALUES('secure-delete', 1); + SELECT * FROM ft_config; +} {secure-delete 1 version 4} + +do_execsql_test 1.3 { + INSERT INTO ft(ft, rank) VALUES('secure-delete', 1); + SELECT * FROM ft_config; +} {secure-delete 1 version 4} + +do_execsql_test 1.4 { + DELETE FROM ft WHERE rowid=2; + SELECT * FROM ft_config; +} {secure-delete 1 version 5} + +do_execsql_test 1.5 { + SELECT rowid, col FROM ft('data'); +} {1 {data for the table} 3 {and extra data}} + +db close +sqlite3 db test.db + +do_execsql_test 1.6 { + SELECT rowid, col FROM ft('data'); +} {1 {data for the table} 3 {and extra data}} + +#------------------------------------------------------------------------ + +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(col); + INSERT INTO ft VALUES('one zero one one zero'); + INSERT INTO ft(ft, rank) VALUES('secure-delete', 1); +} + +do_execsql_test 2.1 { + SELECT count(*) FROM ft_data WHERE block=X'00000004'; +} {0} + +do_execsql_test 2.2 { + UPDATE ft SET col = 'zero one zero zero one' WHERE rowid=1; +} + +do_execsql_test 2.3 { + SELECT count(*) FROM ft_data WHERE block=X'00000004'; +} {1} + +do_execsql_test 2.4 { + INSERT INTO ft VALUES('one zero zero one'); + DELETE FROM ft WHERE rowid=1; +} + +do_execsql_test 2.5 { + SELECT count(*) FROM ft_data WHERE block=X'00000004'; +} {2} + + +finish_test + + diff --git a/ext/fts5/test/fts5secure3.test b/ext/fts5/test/fts5secure3.test new file mode 100644 index 000000000..bc56e0820 --- /dev/null +++ b/ext/fts5/test/fts5secure3.test @@ -0,0 +1,166 @@ +# 2023 Feb 17 +# +# 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. +# +#************************************************************************* +# +# TESTRUNNER: slow +# + +source [file join [file dirname [info script]] fts5_common.tcl] +ifcapable !fts5 { finish_test ; return } +set ::testprefix fts5secure3 + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(col); + INSERT INTO ft VALUES('data for the table'); + INSERT INTO ft VALUES('more of the same'); + INSERT INTO ft VALUES('and extra data'); + + INSERT INTO ft(ft, rank) VALUES('secure-delete', 1); +} + +do_execsql_test 1.1 { + BEGIN; + INSERT INTO ft(rowid, col) VALUES(0, 'the next data'); + DELETE FROM ft WHERE rowid=1; + DELETE FROM ft WHERE rowid=2; + INSERT INTO ft(rowid, col) VALUES(6, 'with some more of the same data'); + COMMIT; +} + +do_execsql_test 1.2 { + INSERT INTO ft(ft) VALUES('integrity-check'); +} + +#------------------------------------------------------------------------- + +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('pgsz', 64); + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + BEGIN; + INSERT INTO t1 VALUES('the start'); +} +do_test 2.1 { + for {set i 0} {$i < 1000} {incr i} { + execsql { INSERT INTO t1 VALUES('the ' || hex(randomblob(3))) } + } + execsql { + INSERT INTO t1 VALUES('the end'); + COMMIT; + } +} {} + +do_execsql_test 2.2 { + DELETE FROM t1 WHERE rowid BETWEEN 2 AND 1000; +} + +do_execsql_test 2.3 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +do_execsql_test 2.6 { + INSERT INTO t1(rowid, x) VALUES(500, 'middle'); + INSERT INTO t1(rowid, x) VALUES(501, 'value'); + SELECT * FROM t1('the middle'); +} + +do_execsql_test 2.7 { + INSERT INTO t1(t1) VALUES('optimize'); +} + +do_execsql_test 2.8 { + SELECT count(*) FROM t1_data +} 4 + +#execsql_pp { SELECT id, quote(block), fts5_decode(id, block) FROM t1_data; } + +#------------------------------------------------------------------------- +# Tests with large/small rowid values. +# + +reset_db + +expr srand(0) + +set vocab { + Popper Poppins Popsicle Porfirio Porrima Porsche + Porter Portia Portland Portsmouth Portugal Portuguese + Poseidon Post PostgreSQL Potemkin Potomac Potsdam + Pottawatomie Potter Potts Pound Poussin Powell + PowerPC PowerPoint Powers Powhatan Poznan Prada + Prado Praetorian Prague Praia Prakrit Pratchett + Pratt Pravda Praxiteles Preakness Precambrian Preminger + Premyslid Prensa Prentice Pres Presbyterian Presbyterianism +} +proc newdoc {} { + for {set i 0} {$i<8} {incr i} { + lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]] + } + set ret +} +db func newdoc newdoc + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE fff USING fts5(y); + INSERT INTO fff(fff, rank) VALUES('pgsz', 64); + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + INSERT INTO fff(fff, rank) VALUES('secure-delete', 1); +} + +proc lshuffle {in} { + set out [list] + while {[llength $in]>0} { + set idx [expr int(abs(rand()) * [llength $in])] + lappend out [lindex $in $idx] + set in [lreplace $in $idx $idx] + } + set out +} + +#dump fff + +set iTest 1 +foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] { + #if {$iTest==1} { dump fff } + #if {$iTest==1} { breakpoint } + do_execsql_test 3.1.$iTest.$ii { + DELETE FROM fff WHERE rowid=$ii; + } + #if {$iTest==1} { dump fff } + if {($iTest % 20)==0} { + do_execsql_test 3.1.$iTest.$ii.ic { + INSERT INTO fff(fff) VALUES('integrity-check'); + } + } + #if {$iTest==1} { break } + incr iTest +} + +#execsql_pp { SELECT rowid FROM fff('post') ORDER BY rowid ASC } +#breakpoint +#execsql_pp { +# SELECT rowid FROM fff('post') ORDER BY rowid DESC +#} +# +#dump fff + + +finish_test + diff --git a/ext/fts5/test/fts5secure4.test b/ext/fts5/test/fts5secure4.test new file mode 100644 index 000000000..7588a3468 --- /dev/null +++ b/ext/fts5/test/fts5secure4.test @@ -0,0 +1,170 @@ +# 2023 April 14 +# +# 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +return_if_no_fts5 +set ::testprefix fts5secure4 + +#------------------------------------------------------------------------- +# Test using the 'delete' command to attempt to delete a token that +# is not present in the index in secure-delete mode. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, b, content=x1); + + CREATE TABLE x1(rowid INTEGER PRIMARY KEY, a, b); + INSERT INTO x1 VALUES + (1, 'hello world', 'today xyz'), + (2, 'not the day', 'crunch crumble and chomp'), + (3, 'one', 'two'); + INSERT INTO t1(t1) VALUES('rebuild'); +} + +do_execsql_test 1.1 { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); +} + +do_execsql_test 1.2 { + INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 4, 'nosuchtoken', ''); +} + +do_execsql_test 1.3 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +do_execsql_test 1.4 { + INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 1, 'crunch', ''); +} + +do_execsql_test 1.5 { + INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 3, 'crunch', ''); +} + +do_execsql_test 1.6 { + INSERT INTO t1(t1) VALUES('integrity-check'); +} + +do_execsql_test 1.7 { +CREATE VIRTUAL TABLE y1 USING fts5(xx, prefix='1,2'); +INSERT INTO y1(y1, rank) VALUES('pgsz', 64); +INSERT INTO y1(y1, rank) VALUES('secure-delete', 1); +} +do_execsql_test 1.8 { + BEGIN; + INSERT INTO y1(rowid, xx) VALUES(1, 'abc def'); + INSERT INTO y1(rowid, xx) VALUES(2, 'reallyreallylongtoken'); + COMMIT; +} +do_execsql_test 1.9 { + DELETE FROM y1 WHERE rowid=1; + INSERT INTO y1(y1) VALUES('integrity-check'); +} + +do_execsql_test 1.10 { + CREATE VIRTUAL TABLE w1 USING fts5(ww, content=""); + INSERT INTO w1(rowid, ww) VALUES(123, ''); +} +do_catchsql_test 1.11 { + INSERT INTO w1(w1, rowid, ww) VALUES('delete', 123, 'xyz'); +} {1 {database disk image is malformed}} +do_catchsql_test 1.12 { + DROP TABLE w1; + CREATE VIRTUAL TABLE w1 USING fts5(ww, content=""); + INSERT INTO w1(rowid, ww) VALUES(123, ''); + DELETE FROM w1_data WHERE id>10; + INSERT INTO w1(w1, rowid, ww) VALUES('delete', 123, 'xyz'); +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +# Test using secure-delete with detail=none or detail=col. +# +foreach {tn d} {1 full 2 none 3 column} { + reset_db + do_execsql_test 2.$tn.1 " + CREATE VIRTUAL TABLE x1 USING fts5(xx, yy, zz, detail=$d, prefix='10,20'); + INSERT INTO x1(x1, rank) VALUES('pgsz', 64); + INSERT INTO x1(x1, rank) VALUES('secure-delete', 1); + " + + do_execsql_test 2.$tn.2 { + BEGIN; + INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c'); + INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c'); + INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c'); + INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c'); + INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c'); + COMMIT; + INSERT INTO x1(x1) VALUES('integrity-check'); + } + + do_execsql_test 2.$tn.3 { + DELETE FROM x1 WHERE rowid IN (2, 4, 6); + INSERT INTO x1(x1) VALUES('integrity-check'); + } + + do_execsql_test 2.$tn.4 { + DELETE FROM x1 WHERE rowid IN (1, 3, 5); + INSERT INTO x1(x1) VALUES('integrity-check'); + } + + do_execsql_test 2.$tn.5 { + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO x1 + SELECT 'seems to be', 'used brew to', 'everything is working' FROM s + UNION ALL + SELECT 'used brew to', 'everything is working', 'seems to be' FROM s + UNION ALL + SELECT 'everything is working', 'seems to be', 'used brew to' FROM s + UNION ALL + SELECT 'abc', 'zzz', 'a b c d' + UNION ALL + SELECT 'z', 'z', 'z' FROM s + } + + do_test 2.$tn.6 { + for {set i 300} {$i > 200} {incr i -1} { + execsql { + DELETE FROM x1 WHERE rowid=$i; + INSERT INTO x1(x1) VALUES('integrity-check'); + } + } + } {} + + do_test 2.$tn.7 { + for {set i 1} {$i < 100} {incr i} { + execsql { + DELETE FROM x1 WHERE rowid=$i; + INSERT INTO x1(x1) VALUES('integrity-check'); + } + } + } {} + + do_test 2.$tn.8 { + foreach i [db eval {SELECT rowid FROM x1}] { + execsql { + DELETE FROM x1 WHERE rowid=$i; + INSERT INTO x1(x1) VALUES('integrity-check'); + } + } + } {} + + do_execsql_test 2.$tn.9 { + SELECT * FROM x1 + } {} +} + + + +finish_test + diff --git a/ext/fts5/test/fts5secure5.test b/ext/fts5/test/fts5secure5.test new file mode 100644 index 000000000..ca8570211 --- /dev/null +++ b/ext/fts5/test/fts5secure5.test @@ -0,0 +1,129 @@ +# 2023 April 14 +# +# 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +return_if_no_fts5 +set ::testprefix fts5secure5 +return_if_no_fts5 + +proc dump {} { + execsql_pp { + SELECT id, quote(block), fts5_decode_none(id, block) FROM ft1_data + } +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none); + INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1); +} + +do_execsql_test 1.1 { + BEGIN; + INSERT INTO ft1(rowid, a) VALUES(1, 'abcd'); + INSERT INTO ft1(rowid, a) VALUES(2, 'abcd'); + INSERT INTO ft1(rowid, a) VALUES(3, 'abcd'); + COMMIT; +} +do_execsql_test 1.2 { + DELETE FROM ft1 WHERE rowid=1; +} +do_execsql_test 1.3 { + INSERT INTO ft1(ft1) VALUES('integrity-check'); +} +do_execsql_test 1.4 { + DELETE FROM ft1 WHERE rowid=3; +} +do_execsql_test 1.5 { + INSERT INTO ft1(ft1) VALUES('integrity-check'); +} +do_execsql_test 1.6 { + DELETE FROM ft1 WHERE rowid=3; +} +do_execsql_test 1.7 { + INSERT INTO ft1(ft1) VALUES('integrity-check'); +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none); + INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1); +} + +do_execsql_test 2.1 { + BEGIN; + INSERT INTO ft1(rowid, a) VALUES(1, 'abcd one'); + INSERT INTO ft1(rowid, a) VALUES(2, 'abcd two'); + INSERT INTO ft1(rowid, a) VALUES(3, 'abcd two'); + INSERT INTO ft1(rowid, a) VALUES(4, 'abcd two'); + INSERT INTO ft1(rowid, a) VALUES(5, 'abcd three'); + COMMIT; +} + +do_execsql_test 2.2a { + DELETE FROM ft1 WHERE rowid=3; +} +do_execsql_test 2.2b { + INSERT INTO ft1(ft1) VALUES('integrity-check'); +} +do_execsql_test 2.3a { + DELETE FROM ft1 WHERE rowid=2; +} +do_execsql_test 2.3b { + INSERT INTO ft1(ft1) VALUES('integrity-check'); +} +do_execsql_test 2.4a { + DELETE FROM ft1 WHERE rowid=4; +} +do_execsql_test 2.4b { + INSERT INTO ft1(ft1) VALUES('integrity-check'); +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none, prefix=1); + INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1); + INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64); +} +do_execsql_test 3.1 { + BEGIN; + INSERT INTO ft1(a) VALUES('c'); + COMMIT; +} +do_execsql_test 3.2 { + DELETE FROM ft1 WHERE rowid IN (1); + INSERT INTO ft1(ft1) VALUES('integrity-check'); +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none); + INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1); + INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64); + + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500 + ) + INSERT INTO ft1 SELECT 'abcdefg' FROM s; +} + +do_test 4.1 { + for {set i 500} {$i > 0} {incr i -1} { + execsql { DELETE FROM ft1 WHERE rowid=$i } + execsql { INSERT INTO ft1(ft1) VALUES('integrity-check') } + } +} {} + +finish_test + diff --git a/ext/fts5/test/fts5secure6.test b/ext/fts5/test/fts5secure6.test new file mode 100644 index 000000000..e561a43f7 --- /dev/null +++ b/ext/fts5/test/fts5secure6.test @@ -0,0 +1,55 @@ +# 2023 Feb 17 +# +# 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +ifcapable !fts5 { finish_test ; return } +set ::testprefix fts5secure6 + +db progress 1 progress_handler +set ::PHC 0 +proc progress_handler {args} { + incr ::PHC + if {($::PHC % 100000)==0} breakpoint + return 0 +} + +proc setup {} { + db eval { + DROP TABLE IF EXISTS t1; + CREATE VIRTUAL TABLE t1 USING fts5(x); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) + INSERT INTO t1 SELECT 'a b c d e f g h i j k' FROM s; + } +} + +foreach {tn sd} { + 1 0 + 2 1 +} { + setup + do_execsql_test 1.$tn.0 { + INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) + } + set PHC 0 + do_execsql_test 1.$tn.1 { DELETE FROM t1; } + set phc($tn) $PHC +} + +do_test 1.3 { + expr $phc(1)*5 < $phc(2) +} {1} + + +finish_test + diff --git a/ext/fts5/test/fts5securefault.test b/ext/fts5/test/fts5securefault.test new file mode 100644 index 000000000..63874ece5 --- /dev/null +++ b/ext/fts5/test/fts5securefault.test @@ -0,0 +1,225 @@ +# 2023 April 14 +# +# 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 is testing the FTS5 module. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +source $testdir/malloc_common.tcl +set testprefix fts5securefault + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +return_if_no_fts5 + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + INSERT INTO t1(rowid, ab) VALUES + (0, 'abc'), (1, 'abc'), (2, 'abc'), (3, 'abc'), (4, 'def'); +} +faultsim_save_and_close + +do_faultsim_test 1.1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} -body { + execsql { DELETE FROM t1 WHERE rowid=2 } +} -test { + faultsim_test_result {0 {}} +} +do_faultsim_test 1.2 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} -body { + execsql { DELETE FROM t1 WHERE rowid IN(0, 1, 2, 3, 4) } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +# +reset_db +set big [string repeat abcdefghij 5] +set big2 [string repeat klmnopqrst 5] +set doc "$big $big2" + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + INSERT INTO t1(t1, rank) VALUES('pgsz', 64); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<4 + ) + INSERT INTO t1(rowid, ab) SELECT i, $doc FROM s; +} +faultsim_save_and_close + +do_faultsim_test 2.1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} -body { + execsql { DELETE FROM t1 WHERE rowid = 3 } + execsql { DELETE FROM t1 WHERE rowid = 4 } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +# +reset_db +set big [string repeat abcdefghij 5] +set big2 [string repeat klmnopqrst 5] +set doc "$big $big2" + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + INSERT INTO t1(t1, rank) VALUES('pgsz', 64); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<25 + ) + INSERT INTO t1(rowid, ab) SELECT i, $doc FROM s; + + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + DELETE FROM t1 WHERE rowid BETWEEN 3 AND 23; +} +faultsim_save_and_close + +do_faultsim_test 3.1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} -body { + execsql { DELETE FROM t1 WHERE rowid = 24 } + execsql { DELETE FROM t1 WHERE rowid = 25 } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +# +reset_db +set doc [string repeat "tok " 400] + +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + INSERT INTO t1(t1, rank) VALUES('pgsz', 64); + INSERT INTO t1(rowid, ab) VALUES(1, $doc), (2, $doc), (3, $doc); +} +faultsim_save_and_close + +do_faultsim_test 4.1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} -body { + execsql { DELETE FROM t1 WHERE rowid = 2 } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +# +reset_db + +set doc1 [string repeat "abc " 10] +set doc2 [string repeat "def " 10] + +do_test 5.0 { + execsql { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + INSERT INTO t1(t1, rank) VALUES('pgsz', 64); + BEGIN; + } + for {set i 0} {$i < 50} {incr i} { + execsql { + INSERT INTO t1(rowid, ab) VALUES($i, 'abcdefg'); + } + } + execsql { + INSERT INTO t1(rowid, ab) VALUES(105, 'def'); + COMMIT; + } +} {} +faultsim_save_and_close + +do_faultsim_test 5.1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} -body { + execsql { DELETE FROM t1 WHERE rowid = 105 } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +# +reset_db +do_test 6.0 { + execsql { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + INSERT INTO t1(t1, rank) VALUES('pgsz', 64); + BEGIN; + INSERT INTO t1(rowid, ab) VALUES(1, 'abcdefg'); + INSERT INTO t1(rowid, ab) VALUES(2, 'abcdefg'); + INSERT INTO t1(rowid, ab) VALUES(3, 'abcdefg'); + COMMIT; + } +} {} +faultsim_save_and_close + +do_faultsim_test 6.1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} -body { + execsql { + UPDATE t1 SET ab='abcdefg' WHERE rowid=2; + } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +# +reset_db +do_test 7.0 { + execsql { + CREATE VIRTUAL TABLE t1 USING fts5(ab); + INSERT INTO t1(t1, rank) VALUES('pgsz', 32); + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } +} {} +faultsim_save_and_close + +do_faultsim_test 7.1 -faults oom* -prep { + faultsim_restore_and_reopen + set big1 "[string repeat x 50] [string repeat y 50] [string repeat z 50]" + execsql { + BEGIN; + INSERT INTO t1 VALUES($big1); + } +} -body { + execsql { COMMIT } +} -test { + faultsim_test_result {0 {}} +} + + +finish_test diff --git a/ext/fts5/test/fts5version.test b/ext/fts5/test/fts5version.test index 60ec81c03..79fd94e6b 100644 --- a/ext/fts5/test/fts5version.test +++ b/ext/fts5/test/fts5version.test @@ -38,20 +38,20 @@ do_execsql_test 1.3 { sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.4 { - UPDATE t1_config set v=5 WHERE k='version'; + UPDATE t1_config set v=6 WHERE k='version'; } do_test 1.5 { db close sqlite3 db test.db catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' } -} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}} +} {1 {invalid fts5 file format (found 6, expected 4 or 5) - run 'rebuild'}} do_test 1.6 { db close sqlite3 db test.db catchsql { INSERT INTO t1 VALUES('x y z') } -} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}} +} {1 {invalid fts5 file format (found 6, expected 4 or 5) - run 'rebuild'}} do_test 1.7 { sqlite3_db_config db DEFENSIVE 0 @@ -59,7 +59,75 @@ do_test 1.7 { db close sqlite3 db test.db catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' } -} {1 {invalid fts5 file format (found 0, expected 4) - run 'rebuild'}} +} {1 {invalid fts5 file format (found 0, expected 4 or 5) - run 'rebuild'}} + +do_test 1.8 { + sqlite3_db_config db DEFENSIVE 0 + execsql { INSERT INTO t1_config VALUES('version', 4) } + execsql { INSERT INTO t1(t1, rank) VALUES('secure-delete', 1) } +} {} + +do_execsql_test 1.10 { + SELECT * FROM t1_config +} {secure-delete 1 version 4} + +do_execsql_test 1.11 { + INSERT INTO t1(rowid, one) VALUES(123, 'one two three'); + DELETE FROM t1 WHERE rowid=123; + SELECT * FROM t1_config +} {secure-delete 1 version 5} + +do_execsql_test 1.11 { + INSERT INTO t1(t1) VALUES('rebuild'); + SELECT * FROM t1_config +} {secure-delete 1 version 4} + +do_execsql_test 1.12 { + SELECT * FROM t1_config +} {secure-delete 1 version 4} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE xyz USING fts5(x); + INSERT INTO xyz(rowid, x) VALUES + (1, 'one document'), + (2, 'two document'), + (3, 'three document'), + (4, 'four document'), + (5, 'five document'), + (6, 'six document'); + + INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 1); + SELECT v FROM xyz_config WHERE k='version'; +} {4} + +do_execsql_test 2.1 { + BEGIN; + INSERT INTO xyz(rowid, x) VALUES(7, 'seven document'); + SAVEPOINT one; + DELETE FROM xyz WHERE rowid = 4; +} + +do_execsql_test 2.2 { + SELECT v FROM xyz_config WHERE k='version'; +} {5} + +do_execsql_test 2.3 { + ROLLBACK TO one; + SELECT v FROM xyz_config WHERE k='version'; +} {4} + + +do_execsql_test 2.4 { + DELETE FROM xyz WHERE rowid = 3; + COMMIT; + SELECT v FROM xyz_config WHERE k='version'; +} {5} + + finish_test + diff --git a/ext/misc/base64.c b/ext/misc/base64.c old mode 100755 new mode 100644 index 69eff6181..bc7a976ab --- a/ext/misc/base64.c +++ b/ext/misc/base64.c @@ -76,6 +76,7 @@ typedef unsigned char u8; #define U8_TYPEDEF #endif +/* Decoding table, ASCII (7-bit) value to base 64 digit value or other */ static const u8 b64DigitValues[128] = { /* HT LF VT FF CR */ ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND, @@ -147,9 +148,9 @@ static char* toBase64( u8 *pIn, int nbIn, char *pOut ){ } /* Skip over text which is not base64 numeral(s). */ -static char * skipNonB64( char *s ){ +static char * skipNonB64( char *s, int nc ){ char c; - while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; + while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; return s; } @@ -158,7 +159,7 @@ static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){ if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; while( ncIn>0 && *pIn!=PAD_CHAR ){ static signed char nboi[] = { 0, 0, 1, 2, 3 }; - char *pUse = skipNonB64(pIn); + char *pUse = skipNonB64(pIn, ncIn); unsigned long qv = 0L; int nti, nbo, nac; ncIn -= (pUse - pIn); @@ -218,9 +219,16 @@ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ sqlite3_result_error(context, "blob expanded to base64 too big", -1); return; } + bBuf = (u8*)sqlite3_value_blob(av[0]); + if( !bBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_text(context,"",-1,SQLITE_STATIC); + break; + } cBuf = sqlite3_malloc(nc); if( !cBuf ) goto memFail; - bBuf = (u8*)sqlite3_value_blob(av[0]); nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf); sqlite3_result_text(context, cBuf, nc, sqlite3_free); break; @@ -233,9 +241,16 @@ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ }else if( nb<1 ){ nb = 1; } + cBuf = (char *)sqlite3_value_text(av[0]); + if( !cBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_zeroblob(context, 0); + break; + } bBuf = sqlite3_malloc(nb); if( !bBuf ) goto memFail; - cBuf = (char *)sqlite3_value_text(av[0]); nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf); sqlite3_result_blob(context, bBuf, nb, sqlite3_free); break; diff --git a/ext/misc/base85.c b/ext/misc/base85.c index 5ec136dbc..e7ef0a04c 100644 --- a/ext/misc/base85.c +++ b/ext/misc/base85.c @@ -140,9 +140,9 @@ static u8 base85DigitValue( char c ){ #define B85_DARK_MAX 80 -static char * skipNonB85( char *s ){ +static char * skipNonB85( char *s, int nc ){ char c; - while( (c = *s) && !IS_B85(c) ) ++s; + while( nc-- > 0 && (c = *s) && !IS_B85(c) ) ++s; return s; } @@ -212,7 +212,7 @@ static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){ if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; while( ncIn>0 ){ static signed char nboi[] = { 0, 0, 1, 2, 3, 4 }; - char *pUse = skipNonB85(pIn); + char *pUse = skipNonB85(pIn, ncIn); unsigned long qv = 0L; int nti, nbo; ncIn -= (pUse - pIn); @@ -297,9 +297,16 @@ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ sqlite3_result_error(context, "blob expanded to base85 too big", -1); return; } + bBuf = (u8*)sqlite3_value_blob(av[0]); + if( !bBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_text(context,"",-1,SQLITE_STATIC); + break; + } cBuf = sqlite3_malloc(nc); if( !cBuf ) goto memFail; - bBuf = (u8*)sqlite3_value_blob(av[0]); nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf); sqlite3_result_text(context, cBuf, nc, sqlite3_free); break; @@ -312,9 +319,16 @@ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ }else if( nb<1 ){ nb = 1; } + cBuf = (char *)sqlite3_value_text(av[0]); + if( !cBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_zeroblob(context, 0); + break; + } bBuf = sqlite3_malloc(nb); if( !bBuf ) goto memFail; - cBuf = (char *)sqlite3_value_text(av[0]); nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf); sqlite3_result_blob(context, bBuf, nb, sqlite3_free); break; diff --git a/ext/misc/basexx.c b/ext/misc/basexx.c index c1808aa70..0dcde5435 100644 --- a/ext/misc/basexx.c +++ b/ext/misc/basexx.c @@ -69,9 +69,12 @@ __declspec(dllexport) #endif int sqlite3_basexx_init(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ + int rc1; + int rc2; + init_api_ptr(pApi); - int rc1 = BASE64_INIT(db); - int rc2 = BASE85_INIT(db); + rc1 = BASE64_INIT(db); + rc2 = BASE85_INIT(db); if( rc1==SQLITE_OK && rc2==SQLITE_OK ){ BASE64_EXPOSE(db, pzErr); diff --git a/ext/misc/randomjson.c b/ext/misc/randomjson.c new file mode 100644 index 000000000..3a6f545fe --- /dev/null +++ b/ext/misc/randomjson.c @@ -0,0 +1,202 @@ +/* +** 2023-04-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 SQLite extension implements a the random_json(SEED) and +** random_json5(SEED) functions. Given a numeric SEED value, these +** routines generate pseudo-random JSON or JSON5, respectively. The +** same value is always generated for the same seed. +** +** These SQL functions are intended for testing. They do not have any +** practical real-world use, that we know of. +** +** COMPILE: +** +** gcc --shared -fPIC -o randomjson.so -I. ext/misc/randomjson.c +** +** USING FROM THE CLI: +** +** .load ./randomjson +** SELECT random_json(1); +*/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +/* Pseudo-random number generator */ +typedef struct Prng { + unsigned int x, y; +} Prng; + +/* Reseed the PRNG */ +static void prngSeed(Prng *p, unsigned int iSeed){ + p->x = iSeed | 1; + p->y = iSeed; +} + +/* Extract a random number */ +static unsigned int prngInt(Prng *p){ + p->x = (p->x>>1) ^ ((1+~(p->x&1)) & 0xd0000001); + p->y = p->y*1103515245 + 12345; + return p->x ^ p->y; +} + +static const char *azJsonAtoms[] = { + /* JSON /* JSON-5 */ + "0", "0", + "1", "1", + "-1", "-1", + "2", "+2", + "3", "3", + "2.5", "2.5", + "0.75", ".75", + "-4.0e2", "-4.e2", + "5.0e-3", "+5e-3", + "0", "0x0", + "512", "0x200", + "256", "+0x100", + "-2748", "-0xabc", + "true", "true", + "false", "false", + "null", "null", + "9.0e999", "Infinity", + "-9.0e999", "-Infinity", + "9.0e999", "+Infinity", + "null", "NaN", + "-0.0005123", "-0.0005123", + "4.35e-3", "+4.35e-3", + "\"gem\\\"hay\"", "\"gem\\\"hay\"", + "\"icy'joy\"", "'icy\\'joy\'", + "\"keylog\"", "\"key\\\nlog\"", + "\"mix\\\\\\tnet\"", "\"mix\\\\\\tnet\"", + "{}", "{}", + "[]", "[]", + "[]", "[/*empty*/]", + "{}", "{//empty\n}", + "\"ask\"", "\"ask\"", + "\"bag\"", "\"bag\"", + "\"can\"", "\"can\"", + "\"day\"", "\"day\"", + "\"end\"", "'end'", + "\"fly\"", "\"fly\"", + "\"\"", "\"\"", +}; +static const char *azJsonTemplate[] = { + /* JSON JSON-5 */ + "{\"a\":%,\"b\":%,\"c\":%}", "{a:%,b:%,c:%}", + "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"e\":%}", "{a:%,b:%,c:%,d:%,e:%}", + "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,\"\":%}", + "{\"d\":%}", "{d:%}", + "{\"eeee\":%, \"ffff\":%}", "{eeee:% /*and*/, ffff:%}", + "{\"$g\":%,\"_h_\":%}", "{$g:%,_h_:%,}", + "{\"x\":%,\n \"y\":%}", "{\"x\":%,\n \"y\":%}", + "{\"a b c d\":%,\"e\":%,\"f\":%,\"x\":%,\"y\":%}", + "{\"a b c d\":%,e:%,f:%,x:%,y:%}", + "{\"Z\":%}", "{Z:%,}", + "[%]", "[%,]", + "[%,%]", "[%,%]", + "[%,%,%]", "[%,%,%,]", + "[%,%,%,%]", "[%,%,%,%]", + "[%,%,%,%,%]", "[%,%,%,%,%]", +}; + +#define count(X) (sizeof(X)/sizeof(X[0])) + +#define STRSZ 10000 + +static void jsonExpand( + const char *zSrc, + char *zDest, + Prng *p, + int eType, /* 0 for JSON, 1 for JSON5 */ + unsigned int r /* Growth probability 0..1000. 0 means no growth */ +){ + unsigned int i, j, k; + const char *z; + size_t n; + + j = 0; + if( zSrc==0 ){ + k = prngInt(p)%(count(azJsonTemplate)/2); + k = k*2 + eType; + zSrc = azJsonTemplate[k]; + } + if( strlen(zSrc)>=STRSZ/10 ) r = 0; + for(i=0; zSrc[i]; i++){ + if( zSrc[i]!='%' ){ + if( jnState; + unsigned int iFirst = p->nState; if( rePeek(p)=='^' ){ re_append(p, RE_OP_CC_EXC, 0); p->sIn.i++; @@ -619,7 +619,7 @@ static const char *re_subcompile_string(ReCompiled *p){ if( rePeek(p)==']' ){ p->sIn.i++; break; } } if( c==0 ) return "unclosed '['"; - p->aArg[iFirst] = p->nState - iFirst; + if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst; break; } case '\\': { diff --git a/ext/misc/series.c b/ext/misc/series.c index 3941d96c4..0deabf95a 100644 --- a/ext/misc/series.c +++ b/ext/misc/series.c @@ -1,5 +1,5 @@ /* -** 2015-08-18 +** 2015-08-18, 2023-04-28 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -12,7 +12,19 @@ ** ** This file demonstrates how to create a table-valued-function using ** a virtual table. This demo implements the generate_series() function -** which gives similar results to the eponymous function in PostgreSQL. +** which gives the same results as the eponymous function in PostgreSQL, +** within the limitation that its arguments are signed 64-bit integers. +** +** Considering its equivalents to generate_series(start,stop,step): A +** value V[n] sequence is produced for integer n ascending from 0 where +** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 ) +** for each produced value (independent of production time ordering.) +** +** All parameters must be either integer or convertable to integer. +** The start parameter is required. +** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff) +** The step parameter defaults to 1 and 0 is treated as 1. +** ** Examples: ** ** SELECT * FROM generate_series(0,100,5); @@ -28,6 +40,14 @@ ** ** Integers 20 through 29. ** +** SELECT * FROM generate_series(0,-100,-5); +** +** Integers 0 -5 -10 ... -100. +** +** SELECT * FROM generate_series(0,-1); +** +** Empty sequence. +** ** HOW IT WORKS ** ** The generate_series "function" is really a virtual table with the @@ -40,6 +60,9 @@ ** step HIDDEN ** ); ** +** The virtual table also has a rowid, logically equivalent to n+1 where +** "n" is the ascending integer in the aforesaid production definition. +** ** Function arguments in queries against this virtual table are translated ** into equality constraints against successive hidden columns. In other ** words, the following pairs of queries are equivalent to each other: @@ -72,9 +95,126 @@ SQLITE_EXTENSION_INIT1 #include #include +#include #ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return that member of a generate_series(...) sequence whose 0-based +** index is ix. The 0th member is given by smBase. The sequence members +** progress per ix increment by smStep. +*/ +static sqlite3_int64 genSeqMember(sqlite3_int64 smBase, + sqlite3_int64 smStep, + sqlite3_uint64 ix){ + if( ix>=(sqlite3_uint64)LLONG_MAX ){ + /* Get ix into signed i64 range. */ + ix -= (sqlite3_uint64)LLONG_MAX; + /* With 2's complement ALU, this next can be 1 step, but is split into + * 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */ + smBase += (LLONG_MAX/2) * smStep; + smBase += (LLONG_MAX - LLONG_MAX/2) * smStep; + } + /* Under UBSAN (or on 1's complement machines), must do this last term + * in steps to avoid the dreaded (and harmless) signed multiply overlow. */ + if( ix>=2 ){ + sqlite3_int64 ix2 = (sqlite3_int64)ix/2; + smBase += ix2*smStep; + ix -= ix2; + } + return smBase + ((sqlite3_int64)ix)*smStep; +} +typedef unsigned char u8; + +typedef struct SequenceSpec { + sqlite3_int64 iBase; /* Starting value ("start") */ + sqlite3_int64 iTerm; /* Given terminal value ("stop") */ + sqlite3_int64 iStep; /* Increment ("step") */ + sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */ + sqlite3_uint64 uSeqIndexNow; /* Current index during generation */ + sqlite3_int64 iValueNow; /* Current value during generation */ + u8 isNotEOF; /* Sequence generation not exhausted */ + u8 isReversing; /* Sequence is being reverse generated */ +} SequenceSpec; + +/* +** Prepare a SequenceSpec for use in generating an integer series +** given initialized iBase, iTerm and iStep values. Sequence is +** initialized per given isReversing. Other members are computed. +*/ +static void setupSequence( SequenceSpec *pss ){ + int bSameSigns; + pss->uSeqIndexMax = 0; + pss->isNotEOF = 0; + bSameSigns = (pss->iBase < 0)==(pss->iTerm < 0); + if( pss->iTerm < pss->iBase ){ + sqlite3_uint64 nuspan = 0; + if( bSameSigns ){ + nuspan = (sqlite3_uint64)(pss->iBase - pss->iTerm); + }else{ + /* Under UBSAN (or on 1's complement machines), must do this in steps. + * In this clause, iBase>=0 and iTerm<0 . */ + nuspan = 1; + nuspan += pss->iBase; + nuspan += -(pss->iTerm+1); + } + if( pss->iStep<0 ){ + pss->isNotEOF = 1; + if( nuspan==ULONG_MAX ){ + pss->uSeqIndexMax = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1; + }else if( pss->iStep>LLONG_MIN ){ + pss->uSeqIndexMax = nuspan/-pss->iStep; + } + } + }else if( pss->iTerm > pss->iBase ){ + sqlite3_uint64 puspan = 0; + if( bSameSigns ){ + puspan = (sqlite3_uint64)(pss->iTerm - pss->iBase); + }else{ + /* Under UBSAN (or on 1's complement machines), must do this in steps. + * In this clause, iTerm>=0 and iBase<0 . */ + puspan = 1; + puspan += pss->iTerm; + puspan += -(pss->iBase+1); + } + if( pss->iStep>0 ){ + pss->isNotEOF = 1; + pss->uSeqIndexMax = puspan/pss->iStep; + } + }else if( pss->iTerm == pss->iBase ){ + pss->isNotEOF = 1; + pss->uSeqIndexMax = 0; + } + pss->uSeqIndexNow = (pss->isReversing)? pss->uSeqIndexMax : 0; + pss->iValueNow = (pss->isReversing) + ? genSeqMember(pss->iBase, pss->iStep, pss->uSeqIndexMax) + : pss->iBase; +} + +/* +** Progress sequence generator to yield next value, if any. +** Leave its state to either yield next value or be at EOF. +** Return whether there is a next value, or 0 at EOF. +*/ +static int progressSequence( SequenceSpec *pss ){ + if( !pss->isNotEOF ) return 0; + if( pss->isReversing ){ + if( pss->uSeqIndexNow > 0 ){ + pss->uSeqIndexNow--; + pss->iValueNow -= pss->iStep; + }else{ + pss->isNotEOF = 0; + } + }else{ + if( pss->uSeqIndexNow < pss->uSeqIndexMax ){ + pss->uSeqIndexNow++; + pss->iValueNow += pss->iStep; + }else{ + pss->isNotEOF = 0; + } + } + return pss->isNotEOF; +} /* series_cursor is a subclass of sqlite3_vtab_cursor which will ** serve as the underlying representation of a cursor that scans @@ -83,12 +223,7 @@ SQLITE_EXTENSION_INIT1 typedef struct series_cursor series_cursor; struct series_cursor { sqlite3_vtab_cursor base; /* Base class - must be first */ - int isDesc; /* True to count down rather than up */ - sqlite3_int64 iRowid; /* The rowid */ - sqlite3_int64 iValue; /* Current value ("value") */ - sqlite3_int64 mnValue; /* Mimimum value ("start") */ - sqlite3_int64 mxValue; /* Maximum value ("stop") */ - sqlite3_int64 iStep; /* Increment ("step") */ + SequenceSpec ss; /* (this) Derived class data */ }; /* @@ -170,12 +305,7 @@ static int seriesClose(sqlite3_vtab_cursor *cur){ */ static int seriesNext(sqlite3_vtab_cursor *cur){ series_cursor *pCur = (series_cursor*)cur; - if( pCur->isDesc ){ - pCur->iValue -= pCur->iStep; - }else{ - pCur->iValue += pCur->iStep; - } - pCur->iRowid++; + progressSequence( & pCur->ss ); return SQLITE_OK; } @@ -191,23 +321,23 @@ static int seriesColumn( series_cursor *pCur = (series_cursor*)cur; sqlite3_int64 x = 0; switch( i ){ - case SERIES_COLUMN_START: x = pCur->mnValue; break; - case SERIES_COLUMN_STOP: x = pCur->mxValue; break; - case SERIES_COLUMN_STEP: x = pCur->iStep; break; - default: x = pCur->iValue; break; + case SERIES_COLUMN_START: x = pCur->ss.iBase; break; + case SERIES_COLUMN_STOP: x = pCur->ss.iTerm; break; + case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break; + default: x = pCur->ss.iValueNow; break; } sqlite3_result_int64(ctx, x); return SQLITE_OK; } /* -** Return the rowid for the current row. In this implementation, the -** first row returned is assigned rowid value 1, and each subsequent -** row a value 1 more than that of the previous. +** Return the rowid for the current row, logically equivalent to n+1 where +** "n" is the ascending integer in the aforesaid production definition. */ static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ series_cursor *pCur = (series_cursor*)cur; - *pRowid = pCur->iRowid; + sqlite3_uint64 n = pCur->ss.uSeqIndexNow; + *pRowid = (sqlite3_int64)((n<0xffffffffffffffff)? n+1 : 0); return SQLITE_OK; } @@ -217,14 +347,10 @@ static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ */ static int seriesEof(sqlite3_vtab_cursor *cur){ series_cursor *pCur = (series_cursor*)cur; - if( pCur->isDesc ){ - return pCur->iValue < pCur->mnValue; - }else{ - return pCur->iValue > pCur->mxValue; - } + return !pCur->ss.isNotEOF; } -/* True to cause run-time checking of the start=, stop=, and/or step= +/* True to cause run-time checking of the start=, stop=, and/or step= ** parameters. The only reason to do this is for testing the ** constraint checking logic for virtual tables in the SQLite core. */ @@ -235,7 +361,7 @@ static int seriesEof(sqlite3_vtab_cursor *cur){ /* ** This method is called to "rewind" the series_cursor object back ** to the first row of output. This method is always called at least -** once prior to any call to seriesColumn() or seriesRowid() or +** once prior to any call to seriesColumn() or seriesRowid() or ** seriesEof(). ** ** The query plan selected by seriesBestIndex is passed in the idxNum @@ -255,7 +381,7 @@ static int seriesEof(sqlite3_vtab_cursor *cur){ ** (so that seriesEof() will return true) if the table is empty. */ static int seriesFilter( - sqlite3_vtab_cursor *pVtabCursor, + sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStrUnused, int argc, sqlite3_value **argv ){ @@ -263,46 +389,41 @@ static int seriesFilter( int i = 0; (void)idxStrUnused; if( idxNum & 1 ){ - pCur->mnValue = sqlite3_value_int64(argv[i++]); + pCur->ss.iBase = sqlite3_value_int64(argv[i++]); }else{ - pCur->mnValue = 0; + pCur->ss.iBase = 0; } if( idxNum & 2 ){ - pCur->mxValue = sqlite3_value_int64(argv[i++]); + pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); }else{ - pCur->mxValue = 0xffffffff; + pCur->ss.iTerm = 0xffffffff; } if( idxNum & 4 ){ - pCur->iStep = sqlite3_value_int64(argv[i++]); - if( pCur->iStep==0 ){ - pCur->iStep = 1; - }else if( pCur->iStep<0 ){ - pCur->iStep = -pCur->iStep; + pCur->ss.iStep = sqlite3_value_int64(argv[i++]); + if( pCur->ss.iStep==0 ){ + pCur->ss.iStep = 1; + }else if( pCur->ss.iStep<0 ){ if( (idxNum & 16)==0 ) idxNum |= 8; } }else{ - pCur->iStep = 1; + pCur->ss.iStep = 1; } for(i=0; imnValue = 1; - pCur->mxValue = 0; + pCur->ss.iBase = 1; + pCur->ss.iTerm = 0; + pCur->ss.iStep = 1; break; } } if( idxNum & 8 ){ - pCur->isDesc = 1; - pCur->iValue = pCur->mxValue; - if( pCur->iStep>0 ){ - pCur->iValue -= (pCur->mxValue - pCur->mnValue)%pCur->iStep; - } + pCur->ss.isReversing = pCur->ss.iStep > 0; }else{ - pCur->isDesc = 0; - pCur->iValue = pCur->mnValue; + pCur->ss.isReversing = pCur->ss.iStep < 0; } - pCur->iRowid = 1; + setupSequence( &pCur->ss ); return SQLITE_OK; } diff --git a/ext/misc/shathree.c b/ext/misc/shathree.c index 765c69181..ba3ea581f 100644 --- a/ext/misc/shathree.c +++ b/ext/misc/shathree.c @@ -10,7 +10,8 @@ ** ****************************************************************************** ** -** This SQLite extension implements functions that compute SHA3 hashes. +** This SQLite extension implements functions that compute SHA3 hashes +** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard. ** Two SQL functions are implemented: ** ** sha3(X,SIZE) diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index 480fbe399..4dbf80b19 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -1097,7 +1097,10 @@ static int zipfileColumn( ** it to be a directory either if the mode suggests so, or if ** the final character in the name is '/'. */ u32 mode = pCDS->iExternalAttr >> 16; - if( !(mode & S_IFDIR) && pCDS->zFile[pCDS->nFile-1]!='/' ){ + if( !(mode & S_IFDIR) + && pCDS->nFile>=1 + && pCDS->zFile[pCDS->nFile-1]!='/' + ){ sqlite3_result_blob(ctx, "", 0, SQLITE_STATIC); } } @@ -1534,9 +1537,19 @@ static u32 zipfileGetTime(sqlite3_value *pVal){ */ static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){ if( pOld ){ - ZipfileEntry **pp; - for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext)); - *pp = (*pp)->pNext; + if( pTab->pFirstEntry==pOld ){ + pTab->pFirstEntry = pOld->pNext; + if( pTab->pLastEntry==pOld ) pTab->pLastEntry = 0; + }else{ + ZipfileEntry *p; + for(p=pTab->pFirstEntry; p; p=p->pNext){ + if( p->pNext==pOld ){ + p->pNext = pOld->pNext; + if( pTab->pLastEntry==pOld ) pTab->pLastEntry = p; + break; + } + } + } zipfileEntryFree(pOld); } } diff --git a/ext/rbu/rbuexlock.test b/ext/rbu/rbuexlock.test index 27fd6c4ba..28ab308fc 100644 --- a/ext/rbu/rbuexlock.test +++ b/ext/rbu/rbuexlock.test @@ -207,5 +207,86 @@ do_test 3.4.0 { } SQLITE_OK rbu close +#------------------------------------------------------------------------- +reset_db +forcedelete rbu1.db +forcedelete rbu2.db + +sqlite3 rbu rbu1.db +do_execsql_test -db rbu 4.1 { + CREATE TABLE data_t1(a, b, rbu_control); + INSERT INTO data_t1 VALUES(1, 'one', 0); +} +rbu close +sqlite3 rbu rbu2.db +do_execsql_test -db rbu 4.2 { + CREATE TABLE data_t1(a, b, rbu_control); + INSERT INTO data_t1 VALUES(2, 'two', 0); +} +rbu close + +do_execsql_test 4.3 { + CREATE TABLE t1(a PRIMARY KEY, b); +} +db close + +do_test 4.4 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db + rbu step + rbu state +} {oal} + +sqlite3 cons test.db +do_execsql_test -db cons 4.5 { + SELECT * FROM t1 +} {} + +do_test 4.6 { rbu step ; rbu state } {oal} +do_test 4.7 { rbu step ; rbu state } {move} +do_execsql_test -db cons 4.8 { + SELECT * FROM t1 +} {} +do_test 4.9 { rbu step ; rbu state } {checkpoint} +do_test 4.10 { + catchsql { SELECT * FROM t1 } cons +} {1 {database is locked}} +do_test 4.11 { rbu step ; rbu state } {checkpoint} +do_test 4.11 { rbu step ; rbu state } {done} +rbu close + +do_test 4.12 { + catchsql { SELECT * FROM t1 } cons +} {0 {1 one}} + +do_test 4.13 { + sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu2.db + rbu step + rbu state +} {oal} + +do_test 4.14 { + catchsql { SELECT * FROM t1 } cons +} {0 {1 one}} + +do_test 4.15 { rbu step ; rbu state } {oal} +do_test 4.16 { rbu step ; rbu state } {move} + +do_test 4.17 { + catchsql { SELECT * FROM t1 } cons +} {0 {1 one}} + +do_test 4.18 { rbu step ; rbu state } {checkpoint} +do_test 4.19 { + catchsql { SELECT * FROM t1 } cons +} {1 {database is locked}} +do_test 4.20 { rbu step ; rbu state } {checkpoint} +do_test 4.21 { rbu step ; rbu state } {done} +rbu close + +do_test 4.22 { + catchsql { SELECT * FROM t1 } cons +} {0 {1 one 2 two}} + +cons close finish_test diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c index 2db66f67a..15b05cede 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -3141,6 +3141,11 @@ static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){ p->rc = pDb->pMethods->xWrite(pDb, p->aBuf, p->pgsz, iOff); } +/* +** This value is copied from the definition of ZIPVFS_CTRL_FILE_POINTER +** in zipvfs.h. +*/ +#define RBU_ZIPVFS_CTRL_FILE_POINTER 230439 /* ** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if @@ -3149,9 +3154,20 @@ static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){ static int rbuLockDatabase(sqlite3 *db){ int rc = SQLITE_OK; sqlite3_file *fd = 0; - sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); - if( fd->pMethods ){ + sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd); + if( fd ){ + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); + if( rc==SQLITE_OK ){ + rc = fd->pMethods->xUnlock(fd, SQLITE_LOCK_NONE); + } + sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd); + }else{ + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + } + + if( rc==SQLITE_OK && fd->pMethods ){ rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); if( rc==SQLITE_OK ){ rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE); diff --git a/ext/recover/dbdata.c b/ext/recover/dbdata.c index 51c68db3f..878a61f1d 100644 --- a/ext/recover/dbdata.c +++ b/ext/recover/dbdata.c @@ -167,6 +167,7 @@ static int dbdataConnect( (void)argc; (void)argv; (void)pzErr; + sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); if( rc==SQLITE_OK ){ pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable)); if( pTab==0 ){ @@ -812,8 +813,6 @@ static int dbdataFilter( } if( rc==SQLITE_OK ){ rc = sqlite3_bind_text(pCsr->pStmt, 1, zSchema, -1, SQLITE_TRANSIENT); - }else{ - pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); } /* Try to determine the encoding of the db by inspecting the header @@ -822,6 +821,10 @@ static int dbdataFilter( rc = dbdataGetEncoding(pCsr); } + if( rc!=SQLITE_OK ){ + pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); + } + if( rc==SQLITE_OK ){ rc = dbdataNext(pCursor); } diff --git a/ext/recover/recover1.test b/ext/recover/recover1.test index fef1bf90f..070dd03d6 100644 --- a/ext/recover/recover1.test +++ b/ext/recover/recover1.test @@ -316,5 +316,51 @@ do_execsql_test 16.9 { COMMIT; } {1 2 3 4} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 17.1 { + CREATE TABLE t(a, PRIMARY KEY(a, a COLLATE NOCASE)) WITHOUT ROWID; + INSERT INTO t VALUES('abc'); + INSERT INTO t VALUES('def'); +} +do_test 17.2 { + set R [sqlite3_recover_init db main test.db2] + $R run + list [catch { $R finish } msg] $msg +} {0 {}} + +#------------------------------------------------------------------------- +foreach enc {utf8 utf16 utf16le utf16be} { + reset_db + do_execsql_test 18.$enc.1 { + PRAGMA auto_vacuum = 0; + PRAGMA encoding='utf16'; + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b); + INSERT INTO t1 VALUES('abc', 'def'); + PRAGMA writable_schema = 1; + DELETE FROM sqlite_schema WHERE name='t1'; + } + + proc my_sql_hook {sql} { + if {[string match "INSERT INTO lostandfound*" $sql]} { + lappend ::script $sql + } + return 0 + } + do_test 18.$enc.2 { + set ::script [list] + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R config lostandfound lostandfound + $R run + $R finish + set ::script + } {{INSERT INTO lostandfound VALUES(2, 2, 2, 1, 'abc', 'def')}} +} + + + + + finish_test diff --git a/ext/recover/recoverbuild.test b/ext/recover/recoverbuild.test new file mode 100644 index 000000000..d119be7fd --- /dev/null +++ b/ext/recover/recoverbuild.test @@ -0,0 +1,42 @@ +# 2023 February 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recoverbuild + + +# The following tests verify that if the recovery extension is used with +# a build that does not support the sqlite_dbpage table, the error message +# is "no such table: sqlite_dbpage", and not something more generic. +# +reset_db +create_null_module db sqlite_dbpage +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1 VALUES(123, 'one hundred and twenty three'); +} + +forcedelete test.db2 +do_test 1.1 { + set R [sqlite3_recover_init db main test.db2] +} {/sqlite_recover.*/} + +do_test 1.2 { + $R run +} {1} + +do_test 1.3 { + list [catch { $R finish } msg] $msg +} {1 {no such table: sqlite_dbpage}} + +finish_test + diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index 8306e8ed8..29fff0e7e 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -1106,7 +1106,7 @@ static void recoverAddTable( int iField = sqlite3_column_int(pStmt, 0); int iCol = sqlite3_column_int(pStmt, 1); - assert( iFieldnCol && iColnCol ); + assert( iColnCol ); pNew->aCol[iCol].iField = iField; pNew->bIntkey = 0; diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 38d09d249..da5e2a97a 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -471,16 +471,17 @@ struct RtreeMatchArg { ** at run-time. */ #ifndef SQLITE_BYTEORDER -#if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ - defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ - defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ - defined(__arm__) -# define SQLITE_BYTEORDER 1234 -#elif defined(sparc) || defined(__ppc__) -# define SQLITE_BYTEORDER 4321 -#else -# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */ -#endif +# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64) +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ppc__) || \ + defined(__ARMEB__) || defined(__AARCH64EB__) +# define SQLITE_BYTEORDER 4321 +# else +# define SQLITE_BYTEORDER 0 +# endif #endif diff --git a/ext/rtree/rtree6.test b/ext/rtree/rtree6.test index b6dfe992a..1cbb2c6e8 100644 --- a/ext/rtree/rtree6.test +++ b/ext/rtree/rtree6.test @@ -104,6 +104,7 @@ do_eqp_test rtree6.2.4.1 { } { QUERY PLAN |--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1 + |--BLOOM FILTER ON t2 (v=?) `--SEARCH t2 USING AUTOMATIC COVERING INDEX (v=?) } do_eqp_test rtree6.2.4.2 { @@ -111,6 +112,7 @@ do_eqp_test rtree6.2.4.2 { } { QUERY PLAN |--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1 + |--BLOOM FILTER ON t2 (v=?) `--SEARCH t2 USING AUTOMATIC PARTIAL COVERING INDEX (v=?) } diff --git a/ext/session/session2.test b/ext/session/session2.test index 806687745..207b98740 100644 --- a/ext/session/session2.test +++ b/ext/session/session2.test @@ -191,7 +191,7 @@ do_common_sql { } foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3 %T4% t4} $set_of_tests] { - do_then_apply_sql $sql + do_then_apply_sql -ignorenoop $sql do_test 2.$tn { compare_db db db2 } {} } @@ -598,7 +598,7 @@ do_common_sql { INSERT INTO t1 SELECT NULL, 0, 0, 0, 0, 0 FROM s } -do_then_apply_sql { +do_then_apply_sql -ignorenoop { UPDATE t1 SET f=f+1 WHERE a=1; UPDATE t1 SET e=e+1 WHERE a=2; UPDATE t1 SET e=e+1, f=f+1 WHERE a=3; diff --git a/ext/session/sessionD.test b/ext/session/sessionD.test index ec652e2d6..9fccbfa96 100644 --- a/ext/session/sessionD.test +++ b/ext/session/sessionD.test @@ -41,30 +41,6 @@ proc scksum {db dbname} { return [md5 $txt] } -proc do_diff_test {tn setup} { - reset_db - forcedelete test.db2 - execsql { ATTACH 'test.db2' AS aux } - execsql $setup - - sqlite3session S db main - foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] { - S attach $tbl - S diff aux $tbl - } - - set C [S changeset] - S delete - - sqlite3 db2 test.db2 - sqlite3changeset_apply db2 $C "" - uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok - db2 close - - set cksum [scksum db main] - uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum] -} - # Ensure that the diff produced by comparing the current contents of [db] # with itself is empty. proc do_empty_diff_test {tn} { diff --git a/ext/session/sessionG.test b/ext/session/sessionG.test index 58ea17d2e..1ebcc926a 100644 --- a/ext/session/sessionG.test +++ b/ext/session/sessionG.test @@ -34,7 +34,7 @@ do_test 1.0 { INSERT INTO t1 VALUES(2, 'two'); INSERT INTO t1 VALUES(3, 'three'); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { DELETE FROM t1 WHERE a=1; INSERT INTO t1 VALUES(4, 'one'); } @@ -42,7 +42,7 @@ do_test 1.0 { } {} do_test 1.1 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { DELETE FROM t1 WHERE a=4; INSERT INTO t1 VALUES(1, 'one'); } @@ -51,7 +51,7 @@ do_test 1.1 { do_test 1.2 { execsql { INSERT INTO t1 VALUES(5, 'five') } db2 - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1 VALUES(11, 'eleven'); INSERT INTO t1 VALUES(12, 'five'); } @@ -82,7 +82,7 @@ do_test 2.2.1 { # It is not possible to apply the changeset generated by the following # SQL, as none of the three updated rows may be updated as part of the # first pass. - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=0 WHERE a=1; UPDATE t1 SET b=1 WHERE a=2; UPDATE t1 SET b=2 WHERE a=3; @@ -109,7 +109,7 @@ do_test 3.1 { } {} do_test 3.3 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=4 WHERE a=3; UPDATE t1 SET b=3 WHERE a=2; UPDATE t1 SET b=2 WHERE a=1; @@ -118,7 +118,7 @@ do_test 3.3 { } {} do_test 3.4 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=1 WHERE a=1; UPDATE t1 SET b=2 WHERE a=2; UPDATE t1 SET b=3 WHERE a=3; @@ -148,7 +148,7 @@ do_test 4.1 { } {} do_test 4.2 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=4 WHERE a=3; UPDATE t1 SET b=3 WHERE a=2; UPDATE t1 SET b=2 WHERE a=1; @@ -161,7 +161,7 @@ do_test 4.2 { } {} do_test 4.3 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=1 WHERE a=1; UPDATE t1 SET b=2 WHERE a=2; UPDATE t1 SET b=3 WHERE a=3; @@ -191,7 +191,7 @@ do_execsql_test -db db2 5.0.2 { } do_test 5.1 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t2 VALUES(4, 5, 6); INSERT INTO t3 VALUES(7, 8, 9); diff --git a/ext/session/sessionH.test b/ext/session/sessionH.test index 8ba23119c..e1b12571c 100644 --- a/ext/session/sessionH.test +++ b/ext/session/sessionH.test @@ -25,7 +25,7 @@ do_test 1.0 { do_common_sql { CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b)); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERe i<10000 ) diff --git a/ext/session/session_common.tcl b/ext/session/session_common.tcl index c52ac457c..3ff84f1c5 100644 --- a/ext/session/session_common.tcl +++ b/ext/session/session_common.tcl @@ -52,6 +52,7 @@ proc do_conflict_test {tn args} { proc bgerror {args} { set ::background_error $args } sqlite3session S db main + S object_config rowid 1 foreach t $O(-tables) { S attach $t } execsql $O(-sql) @@ -81,6 +82,7 @@ proc changeset_from_sql {sql {dbname main}} { } set rc [catch { sqlite3session S db $dbname + S object_config rowid 1 db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" { S attach $name } @@ -112,24 +114,59 @@ proc patchset_from_sql {sql {dbname main}} { return $patchset } -proc do_then_apply_sql {sql {dbname main}} { - proc xConflict args { return "OMIT" } +# Usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME? +# +proc do_then_apply_sql {args} { + + set bIgnoreNoop 0 + set a1 [lindex $args 0] + if {[string length $a1]>1 && [string first $a1 -ignorenoop]==0} { + set bIgnoreNoop 1 + set args [lrange $args 1 end] + } + + if {[llength $args]!=1 && [llength $args]!=2} { + error "usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME?" + } + + set sql [lindex $args 0] + if {[llength $args]==1} { + set dbname main + } else { + set dbname [lindex $args 1] + } + + set ::n_conflict 0 + proc xConflict args { incr ::n_conflict ; return "OMIT" } set rc [catch { sqlite3session S db $dbname + S object_config rowid 1 db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" { S attach $name } db eval $sql - sqlite3changeset_apply db2 [S changeset] xConflict + set ::changeset [S changeset] + sqlite3changeset_apply db2 $::changeset xConflict } msg] catch { S delete } - if {$rc} {error $msg} + + if {$bIgnoreNoop} { + set nSave $::n_conflict + set ::n_conflict 0 + proc xConflict args { incr ::n_conflict ; return "OMIT" } + sqlite3changeset_apply_v2 -ignorenoop db2 $::changeset xConflict + if {$::n_conflict!=$nSave} { + error "-ignorenoop problem ($::n_conflict $nSave)..." + } + } } proc do_iterator_test {tn tbl_list sql res} { sqlite3session S db main + S object_config rowid 1 + if {[llength $tbl_list]==0} { S attach * } foreach t $tbl_list {S attach $t} @@ -139,6 +176,7 @@ proc do_iterator_test {tn tbl_list sql res} { foreach v $res { lappend r $v } set x [list] +# set ::c [S changeset] ; execsql_pp { SELECT quote($::c) } sqlite3session_foreach c [S changeset] { lappend x $c } uplevel do_test $tn [list [list set {} $x]] [list $r] @@ -213,3 +251,49 @@ proc number_name {n} { if {$txt==""} {set txt zero} return $txt } + +proc scksum {db dbname} { + + if {$dbname=="temp"} { + set master sqlite_temp_master + } else { + set master $dbname.sqlite_master + } + + set alltab [$db eval "SELECT name FROM $master WHERE type='table'"] + set txt [$db eval "SELECT * FROM $master ORDER BY type,name,sql"] + foreach tab $alltab { + set cols [list] + db eval "PRAGMA $dbname.table_info = $tab" x { + lappend cols "quote($x(name))" + } + set cols [join $cols ,] + append txt [db eval "SELECT $cols FROM $dbname.$tab ORDER BY $cols"] + } + return [md5 $txt] +} + +proc do_diff_test {tn setup} { + reset_db + forcedelete test.db2 + execsql { ATTACH 'test.db2' AS aux } + execsql $setup + + sqlite3session S db main + S object_config rowid 1 + foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] { + S attach $tbl + S diff aux $tbl + } + + set C [S changeset] + S delete + + sqlite3 db2 test.db2 + sqlite3changeset_apply db2 $C "" + uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok + db2 close + + set cksum [scksum db main] + uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum] +} diff --git a/ext/session/sessionat.test b/ext/session/sessionat.test index e3f9e31ed..e14901e8b 100644 --- a/ext/session/sessionat.test +++ b/ext/session/sessionat.test @@ -110,7 +110,7 @@ eval [string map [list %WR% $trailing] { CREATE TABLE t3(a, b, c DEFAULT 'D', PRIMARY KEY(b)) %WR%; } do_test $tn.3.2 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t3 VALUES(1, 2); INSERT INTO t3 VALUES(3, 4); INSERT INTO t3 VALUES(5, 6); @@ -118,7 +118,7 @@ eval [string map [list %WR% $trailing] { db2 eval {SELECT * FROM t3} } {1 2 D 3 4 D 5 6 D} do_test $tn.3.3 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t3 SET a=45 WHERE b=4; DELETE FROM t3 WHERE a=5; }; @@ -253,7 +253,7 @@ eval [string map [list %WR% $trailing] { CREATE TABLE t8(a PRIMARY KEY, b, c, d DEFAULT 'D', e DEFAULT 'E'); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t8 VALUES(1, 2, 3); INSERT INTO t8 VALUES(4, 5, 6); } @@ -264,7 +264,7 @@ eval [string map [list %WR% $trailing] { SELECT * FROM t8 } {1 2 3 D E 4 5 6 D E} - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t8 SET c=45 WHERE a=4; } do_execsql_test $tn.7.3.1 { @@ -282,7 +282,7 @@ eval [string map [list %WR% $trailing] { do_execsql_test -db db2 $tn.8.1 { CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h, i, j, k, l); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t9 VALUES(1, 2, 3, 4, 5, 6, 7, 8); } do_then_apply_sql { @@ -291,7 +291,7 @@ eval [string map [list %WR% $trailing] { do_execsql_test -db db2 $tn.8.2 { SELECT * FROM t9 } {1 2 3 4 5 6 7 450 {} {} {} {}} - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t9 SET h=NULL } do_execsql_test -db db2 $tn.8.2 { diff --git a/ext/session/sessionbig.test b/ext/session/sessionbig.test index 80ce00a0f..462e21f61 100644 --- a/ext/session/sessionbig.test +++ b/ext/session/sessionbig.test @@ -43,7 +43,7 @@ do_execsql_test -db db2 1.1 { } do_test 1.2 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); @@ -71,7 +71,7 @@ do_test 1.3 { do_test 1.4 { set rc [catch { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); diff --git a/ext/session/sessionfault.test b/ext/session/sessionfault.test index be6c4568c..96e966b41 100644 --- a/ext/session/sessionfault.test +++ b/ext/session/sessionfault.test @@ -44,7 +44,7 @@ do_faultsim_test 1.1 -faults oom-* -prep { faultsim_restore_and_reopen sqlite3 db2 test.db2 } -body { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1 VALUES('a string value', 8, 9); UPDATE t1 SET c = 10 WHERE a = 1; DELETE FROM t1 WHERE a = 4; diff --git a/ext/session/sessionfault2.test b/ext/session/sessionfault2.test index dd00eaa1c..a2dc39e43 100644 --- a/ext/session/sessionfault2.test +++ b/ext/session/sessionfault2.test @@ -132,7 +132,7 @@ do_faultsim_test 1.1 -faults oom-* -prep { faultsim_restore_and_reopen sqlite3 db2 test.db2 } -body { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO sqlite_stat1 VALUES('x', 'y', 45); UPDATE sqlite_stat1 SET stat = 123 WHERE tbl='t1' AND idx='i1'; UPDATE sqlite_stat1 SET stat = 456 WHERE tbl='t2'; diff --git a/ext/session/sessionnoop2.test b/ext/session/sessionnoop2.test new file mode 100644 index 000000000..e406c10ca --- /dev/null +++ b/ext/session/sessionnoop2.test @@ -0,0 +1,180 @@ +# 2011 March 07 +# +# 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. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionnoop2 + +foreach {tn wo} { + 1 "" + 2 " WITHOUT ROWID " +} { + reset_db + eval [string map [list %WO% $wo] { +do_execsql_test $tn.1.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%; + INSERT INTO t1 VALUES('a', 'A', 'AAA'); + INSERT INTO t1 VALUES('b', 'B', 'BBB'); + INSERT INTO t1 VALUES('c', 'C', 'CCC'); + INSERT INTO t1 VALUES('d', 'D', 'DDD'); + INSERT INTO t1 VALUES('e', 'E', 'EEE'); +} + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_execsql_test -db db2 $tn.1.1 { + CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%; + INSERT INTO t1 VALUES('a', 'A', 'AAA'); + INSERT INTO t1 VALUES('b', 'B', '123'); + INSERT INTO t1 VALUES('c', 'C', 'CCC'); + INSERT INTO t1 VALUES('e', 'E', 'EEE'); + INSERT INTO t1 VALUES('f', 'F', 'FFF'); +} + +set C [changeset_from_sql { + UPDATE t1 SET c='123' WHERE a='b'; + DELETE FROM t1 WHERE a='d'; + INSERT INTO t1 VALUES('f', 'F', 'FFF'); +}] + + +set ::conflict_list [list] +proc xConflict {args} { + lappend ::conflict_list $args + return "OMIT" +} +do_test $tn.1.2 { + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} [list {*}{ + {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}} + {INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}} + {DELETE t1 NOTFOUND {t d t D t DDD}} +}] +do_test $tn.1.3 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} [list {*}{ + {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}} + {INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}} + {DELETE t1 NOTFOUND {t d t D t DDD}} +}] + +do_test $tn.1.4 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list +} {} + +do_execsql_test -db db2 1.5 { + UPDATE t1 SET b='G' WHERE a='f'; + UPDATE t1 SET c='456' WHERE a='b'; +} + +do_test $tn.1.6 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list +} [list {*}{ + {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 456}} + {INSERT t1 CONFLICT {t f t F t FFF} {t f t G t FFF}} +}] + +db2 close + +#-------------------------------------------------------------------------- + +reset_db +forcedelete test.db2 +sqlite3 db2 test.db2 +do_execsql_test $tn.2.0 { + CREATE TABLE t1(a PRIMARY KEY, b) %WO%; +} +do_execsql_test -db db2 $tn.2.1 { + CREATE TABLE t1(a PRIMARY KEY, b, c DEFAULT 'val') %WO%; +} + +do_test $tn.2.2 { + do_then_apply_sql -ignorenoop { + INSERT INTO t1 VALUES(1, 2); + } + do_then_apply_sql -ignorenoop { + UPDATE t1 SET b=2 WHERE a=1 + } +} {} + +db2 close + +}] +} + + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +do_execsql_test 3.0 { + CREATE TABLE xyz(a, b, c, PRIMARY KEY(a, b), UNIQUE(c)); + ANALYZE; + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO xyz SELECT i, i, i FROM s; + VACUUM INTO 'test.db2'; +} + +set C [changeset_from_sql { ANALYZE }] +sqlite3 db2 test.db2 + +set ::conflict_list [list] +proc xConflict {args} { lappend ::conflict_list $args ; return "OMIT" } +do_test 3.1 { + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} {} + +do_test 3.2 { + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list +} {} + +do_test 3.3 { + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} [list {*}{ + {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}}} + {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_2 t {100 1}} {t xyz t sqlite_autoindex_xyz_2 t {100 1}}} +}] + +do_execsql_test -db db2 3.4 { + UPDATE sqlite_stat1 SET stat='200 1 1' WHERE idx='sqlite_autoindex_xyz_1'; +} + +do_test 3.5 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list +} [list {*}{ + {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {200 1 1}}} +}] + + + +finish_test + diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index cdf332283..033348f9c 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -84,6 +84,7 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { db eval BEGIN sqlite3session S1 db main + S1 object_config rowid 1 S1 attach * execsql $sql1 db set c1 [S1 changeset] @@ -91,6 +92,7 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { if {$i==1} { sqlite3session S2 db2 main + S2 object_config rowid 1 S2 attach * execsql $sql2 db2 set c2 [S2 changeset] @@ -100,6 +102,7 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { foreach sql [split $sql2 ";"] { if {[string is space $sql]} continue sqlite3session S2 db2 main + S2 object_config rowid 1 S2 attach * execsql $sql db2 lappend c2 [S2 changeset] @@ -341,6 +344,79 @@ do_rebase_test 2.2.3 { OMIT } { SELECT * FROM t2 WHERE z='B' } { 1 one B } + +reset_db +do_execsql_test 2.3.0 { + CREATE TABLE t1 (b TEXT); + INSERT INTO t1(rowid, b) VALUES(1, 'one'); + INSERT INTO t1(rowid, b) VALUES(2, 'two'); + INSERT INTO t1(rowid, b) VALUES(3, 'three'); +} +do_rebase_test 2.3.1 { + UPDATE t1 SET b = 'two.1' WHERE rowid=2 +} { + UPDATE t1 SET b = 'two.2' WHERE rowid=2; +} { + OMIT +} { SELECT rowid, * FROM t1 } {1 one 2 two.1 3 three} + +do_rebase_test 2.3.2 { + UPDATE t1 SET b = 'two.1' WHERE rowid=2 +} { + UPDATE t1 SET b = 'two.2' WHERE rowid=2; +} { + REPLACE +} { SELECT rowid, * FROM t1 } {1 one 2 two.2 3 three} + +do_rebase_test 2.3.3 { + DELETE FROM t1 WHERE rowid=3 +} { + DELETE FROM t1 WHERE rowid=3; +} { + OMIT +} { SELECT rowid, * FROM t1 } {1 one 2 two} + +do_rebase_test 2.3.4 { + DELETE FROM t1 WHERE rowid=1 +} { + UPDATE t1 SET b='one.2' WHERE rowid=1 +} { + OMIT +} { SELECT rowid, * FROM t1 } {2 two 3 three} + +do_rebase_test 2.3.6 { + UPDATE t1 SET b='three.1' WHERE rowid=3 +} { + DELETE FROM t1 WHERE rowid=3; +} { + OMIT +} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three.1} + +do_rebase_test 2.3.7 { + UPDATE t1 SET b='three.1' WHERE rowid=3 +} { + DELETE FROM t1 WHERE rowid=3; +} { + REPLACE +} { SELECT rowid, * FROM t1 } {1 one 2 two} + +do_rebase_test 2.3.8 { + INSERT INTO t1(rowid, b) VALUES(4, 'four.1') +} { + INSERT INTO t1(rowid, b) VALUES(4, 'four.2'); +} { + REPLACE +} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.2} + +do_rebase_test 2.3.9 { + INSERT INTO t1(rowid, b) VALUES(4, 'four.1') +} { + INSERT INTO t1(rowid, b) VALUES(4, 'four.2'); +} { + OMIT +} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.1} + + #------------------------------------------------------------------------- reset_db do_execsql_test 3.0 { diff --git a/ext/session/sessionrowid.test b/ext/session/sessionrowid.test new file mode 100644 index 000000000..a39105ff9 --- /dev/null +++ b/ext/session/sessionrowid.test @@ -0,0 +1,282 @@ +# 2011 Mar 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. +# +#*********************************************************************** +# +# The focus of this file is testing the session module. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionrowid + +do_execsql_test 0.0 { + CREATE TABLE t1(a, b); +} + +foreach {tn rowid bEmpty} { + 1 0 1 + 2 1 0 + 3 -1 1 +} { + do_test 0.$tn { + sqlite3session S db main + if {$rowid>=0} { S object_config rowid $rowid } + S attach t1 + execsql { INSERT INTO t1 VALUES(1, 2); } + expr [string length [S changeset]]==0 + } $bEmpty + S delete +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); +} + +do_iterator_test 1.1 t1 { + INSERT INTO t1 VALUES('i', 'one'); +} { + {INSERT t1 0 X.. {} {i 1 t i t one}} +} + +do_execsql_test 1.2 { + SELECT rowid, * FROM t1 +} {1 i one} + +do_iterator_test 1.3 t1 { + UPDATE t1 SET b='two' +} { + {UPDATE t1 0 X.. {i 1 {} {} t one} {{} {} {} {} t two}} +} + +do_iterator_test 1.4 t1 { + DELETE FROM t1; +} { + {DELETE t1 0 X.. {i 1 t i t two} {}} +} + +do_iterator_test 1.5 t1 { + INSERT INTO t1(rowid, a, b) VALUES(14, 'hello', 'world'); + INSERT INTO t1(rowid, a, b) VALUES(NULL, 'yes', 'no'); + INSERT INTO t1(rowid, a, b) VALUES(-123, 'ii', 'iii'); +} { + {INSERT t1 0 X.. {} {i -123 t ii t iii}} + {INSERT t1 0 X.. {} {i 15 t yes t no}} + {INSERT t1 0 X.. {} {i 14 t hello t world}} +} + +do_iterator_test 1.6 t1 { + UPDATE t1 SET a='deluxe' WHERE rowid=14; + DELETE FROM t1 WHERE rowid=-123; + INSERT INTO t1 VALUES('x', 'xi'); +} { + {DELETE t1 0 X.. {i -123 t ii t iii} {}} + {UPDATE t1 0 X.. {i 14 t hello {} {}} {{} {} t deluxe {} {}}} + {INSERT t1 0 X.. {} {i 16 t x t xi}} +} + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_execsql_test 2.0 { + CREATE TABLE t1(a, b); +} +do_execsql_test -db db2 2.0.1 { + CREATE TABLE t1(a, b); +} + +proc xConflict {args} { + puts "CONFLICT!" + return "OMIT" +} + +do_test 2.1 { + set C [changeset_from_sql { + INSERT INTO t1 VALUES('abc', 'def'); + }] + sqlite3changeset_apply db2 $C xConflict + execsql { SELECT * FROM t1 } db2 +} {abc def} +do_test 2.2 { + set C [changeset_from_sql { + UPDATE t1 SET b='hello' + }] + sqlite3changeset_apply db2 $C xConflict + execsql { SELECT * FROM t1 } db2 +} {abc hello} +do_test 2.3 { + set C [changeset_from_sql { + DELETE FROM t1 WHERE b='hello' + }] + sqlite3changeset_apply db2 $C xConflict + execsql { SELECT * FROM t1 } db2 +} {} + +do_test 2.4 { + do_then_apply_sql { + INSERT INTO t1 VALUES('i', 'one'); + INSERT INTO t1 VALUES('ii', 'two'); + INSERT INTO t1 VALUES('iii', 'three'); + INSERT INTO t1 VALUES('iv', 'four'); + } + compare_db db db2 +} {} + +do_test 2.5 { + do_then_apply_sql { + DELETE FROM t1 WHERE a='ii'; + UPDATE t1 SET b='THREE' WHERE a='iii'; + UPDATE t1 SET a='III' WHERE a='iii'; + INSERT INTO t1 VALUES('v', 'five'); + } + compare_db db db2 +} {} + +do_execsql_test 2.6 {SELECT * FROM t1} {i one III THREE iv four v five} +do_execsql_test -db db2 2.7 {SELECT * FROM t1} {i one III THREE iv four v five} + +#------------------------------------------------------------------------- +db2 close +reset_db +forcedelete test.db2 +sqlite3 db2 test.db2 + +set init_sql { + CREATE TABlE t4(a, b); + CREATE INDEX t4a ON t4(a); + CREATE UNIQUE INDEX t4b ON t4(b); +} + +do_execsql_test 3.0 $init_sql +do_execsql_test -db db2 3.0a $init_sql + +do_execsql_test -db db2 3.1 { + INSERT INTO t4(rowid, a, b) VALUES(43, 'hello', 'world'); +} +do_conflict_test 3.2 -sql { + INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def'); +} -tables t4 -conflicts { + {INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}} +} +do_execsql_test -db db2 3.3 { + SELECT * FROM t4 +} {hello world} + +do_execsql_test 3.4 { DELETE FROM t4 } +do_conflict_test 3.5 -sql { + INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def'); +} -tables t4 -conflicts { + {INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}} +} -policy REPLACE +do_execsql_test -db db2 3.6 { + SELECT * FROM t4 +} {abc def} + +do_execsql_test 3.7 { DELETE FROM t4 } +do_conflict_test 3.8 -sql { + INSERT INTO t4(rowid, a, b) VALUES(45, 'xyz', 'def'); +} -tables t4 -conflicts { + {INSERT t4 CONSTRAINT {i 45 t xyz t def}} +} +do_execsql_test -db db2 3.9 { + SELECT * FROM t4 +} {abc def} + + +do_execsql_test -db db 3.10a { DELETE FROM t4 } +do_execsql_test -db db2 3.10b { DELETE FROM t4 } + +do_execsql_test -db db 3.11a { + INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one'); + INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two'); +} +do_execsql_test -db db2 3.11b { + INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip'); +} + +do_conflict_test 3.12 -sql { + DELETE FROM t4 WHERE a='one'; +} -tables t4 -conflicts { + {DELETE t4 DATA {i 111 t one t one} {i 111 t one t blip}} +} +do_execsql_test -db db2 3.13 { + SELECT * FROM t4 +} {one blip} + +do_conflict_test 3.14 -sql { + DELETE FROM t4 WHERE a='two'; +} -tables t4 -conflicts { + {DELETE t4 NOTFOUND {i 222 t two t two}} +} +do_execsql_test -db db2 3.15 { + SELECT * FROM t4 +} {one blip} + +do_execsql_test -db db 3.16a { DELETE FROM t4 } +do_execsql_test -db db2 3.16b { DELETE FROM t4 } + +do_execsql_test -db db 3.17a { + INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one'); + INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two'); +} +do_execsql_test -db db2 3.17b { + INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip'); +} + +do_conflict_test 3.18 -sql { + UPDATE t4 SET b='xyz' WHERE a='one' +} -tables t4 -conflicts { + {UPDATE t4 DATA {i 111 {} {} t one} {{} {} {} {} t xyz} {i 111 t one t blip}} +} +do_execsql_test -db db2 3.19 { + SELECT * FROM t4 +} {one blip} + +do_conflict_test 3.20 -sql { + UPDATE t4 SET b='123' WHERE a='two' +} -tables t4 -conflicts { + {UPDATE t4 NOTFOUND {i 222 {} {} t two} {{} {} {} {} t 123}} +} +do_execsql_test -db db2 3.21 { + SELECT * FROM t4 +} {one blip} +db2 close + +#-------------------------------------------------------------------------- +breakpoint +do_diff_test 4.0 { + CREATE TABLE t1(x, y); + CREATE TABLE aux.t1(x, y); + INSERT INTO t1 VALUES(1, 2); +} + +do_diff_test 4.1 { + CREATE TABLE t1(x, y); + CREATE TABLE aux.t1(x, y); + INSERT INTO aux.t1 VALUES(1, 2); +} + +do_diff_test 4.2 { + CREATE TABLE t1(x, y); + CREATE TABLE aux.t1(x, y); + INSERT INTO t1(rowid, x, y) VALUES(413, 'hello', 'there'); + INSERT INTO aux.t1(rowid, x, y) VALUES(413, 'hello', 'world'); +} + +finish_test + diff --git a/ext/session/sessionsize.test b/ext/session/sessionsize.test index 04d05514d..01638c667 100644 --- a/ext/session/sessionsize.test +++ b/ext/session/sessionsize.test @@ -113,17 +113,17 @@ do_execsql_test 3.0 { do_test 3.1 { sqlite3session S db main - S object_config_size -1 + S object_config size -1 } 1 -do_test 3.2.1 { S object_config_size 0 } 0 -do_test 3.2.2 { S object_config_size -1 } 0 -do_test 3.2.3 { S object_config_size 1 } 1 -do_test 3.2.4 { S object_config_size -1 } 1 +do_test 3.2.1 { S object_config size 0 } 0 +do_test 3.2.2 { S object_config size -1 } 0 +do_test 3.2.3 { S object_config size 1 } 1 +do_test 3.2.4 { S object_config size -1 } 1 do_test 3.3 { S attach t1 } {} -do_test 3.4 { S object_config_size 1 } {SQLITE_MISUSE} -do_test 3.4 { S object_config_size -1 } {1} +do_test 3.4 { S object_config size 1 } {SQLITE_MISUSE} +do_test 3.4 { S object_config size -1 } {1} S delete diff --git a/ext/session/sessionstat1.test b/ext/session/sessionstat1.test index 774899d96..2757d6044 100644 --- a/ext/session/sessionstat1.test +++ b/ext/session/sessionstat1.test @@ -82,7 +82,7 @@ do_test 2.0 { } {} do_test 2.1 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { WITH s(i) AS ( SELECT 0 UNION ALL SELECT i+1 FROM s WHERE (i+1)<32 ) @@ -100,7 +100,7 @@ do_execsql_test -db db2 2.2 { } do_test 2.3 { - do_then_apply_sql { DROP INDEX t1c } + do_then_apply_sql -ignorenoop { DROP INDEX t1c } } {} do_execsql_test -db db2 2.4 { @@ -111,7 +111,7 @@ do_execsql_test -db db2 2.4 { } do_test 2.3 { - do_then_apply_sql { DROP TABLE t1 } + do_then_apply_sql -ignorenoop { DROP TABLE t1 } } {} do_execsql_test -db db2 2.4 { @@ -153,16 +153,16 @@ do_execsql_test 3.2 { } {t1 null 4} do_test 3.3 { execsql { DELETE FROM sqlite_stat1 } - do_then_apply_sql { ANALYZE } + do_then_apply_sql -ignorenoop { ANALYZE } execsql { SELECT * FROM sqlite_stat1 } db2 } {t1 null 4} do_test 3.4 { execsql { INSERT INTO t1 VALUES(5,5,5) } - do_then_apply_sql { ANALYZE } + do_then_apply_sql -ignorenoop { ANALYZE } execsql { SELECT * FROM sqlite_stat1 } db2 } {t1 null 5} do_test 3.5 { - do_then_apply_sql { DROP TABLE t1 } + do_then_apply_sql -ignorenoop { DROP TABLE t1 } execsql { SELECT * FROM sqlite_stat1 } db2 } {} diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index a3f28abe9..679408849 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -25,6 +25,8 @@ typedef struct SessionInput SessionInput; # endif #endif +#define SESSIONS_ROWID "_rowid_" + static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE; typedef struct SessionHook SessionHook; @@ -46,6 +48,7 @@ struct sqlite3_session { int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ + int bImplicitPK; /* True to handle tables with implicit PK */ int rc; /* Non-zero if an error has occurred */ void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); @@ -122,6 +125,7 @@ struct SessionTable { char *zName; /* Local name of table */ int nCol; /* Number of columns in table zName */ int bStat1; /* True if this is sqlite_stat1 */ + int bRowid; /* True if this table uses rowid for PK */ const char **azCol; /* Column names */ u8 *abPK; /* Array of primary key flags */ int nEntry; /* Total number of entries in hash table */ @@ -514,6 +518,7 @@ static unsigned int sessionHashAppendType(unsigned int h, int eType){ */ static int sessionPreupdateHash( sqlite3_session *pSession, /* Session object that owns pTab */ + i64 iRowid, SessionTable *pTab, /* Session table handle */ int bNew, /* True to hash the new.* PK */ int *piHash, /* OUT: Hash value */ @@ -522,48 +527,53 @@ static int sessionPreupdateHash( unsigned int h = 0; /* Hash value to return */ int i; /* Used to iterate through columns */ - assert( *pbNullPK==0 ); - assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) ); - for(i=0; inCol; i++){ - if( pTab->abPK[i] ){ - int rc; - int eType; - sqlite3_value *pVal; - - if( bNew ){ - rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal); - }else{ - rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal); - } - if( rc!=SQLITE_OK ) return rc; + if( pTab->bRowid ){ + assert( pTab->nCol-1==pSession->hook.xCount(pSession->hook.pCtx) ); + h = sessionHashAppendI64(h, iRowid); + }else{ + assert( *pbNullPK==0 ); + assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) ); + for(i=0; inCol; i++){ + if( pTab->abPK[i] ){ + int rc; + int eType; + sqlite3_value *pVal; - eType = sqlite3_value_type(pVal); - h = sessionHashAppendType(h, eType); - if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - i64 iVal; - if( eType==SQLITE_INTEGER ){ - iVal = sqlite3_value_int64(pVal); + if( bNew ){ + rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal); }else{ - double rVal = sqlite3_value_double(pVal); - assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); - memcpy(&iVal, &rVal, 8); + rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal); } - h = sessionHashAppendI64(h, iVal); - }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ - const u8 *z; - int n; - if( eType==SQLITE_TEXT ){ - z = (const u8 *)sqlite3_value_text(pVal); + if( rc!=SQLITE_OK ) return rc; + + eType = sqlite3_value_type(pVal); + h = sessionHashAppendType(h, eType); + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + i64 iVal; + if( eType==SQLITE_INTEGER ){ + iVal = sqlite3_value_int64(pVal); + }else{ + double rVal = sqlite3_value_double(pVal); + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&iVal, &rVal, 8); + } + h = sessionHashAppendI64(h, iVal); + }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + const u8 *z; + int n; + if( eType==SQLITE_TEXT ){ + z = (const u8 *)sqlite3_value_text(pVal); + }else{ + z = (const u8 *)sqlite3_value_blob(pVal); + } + n = sqlite3_value_bytes(pVal); + if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; + h = sessionHashAppendBlob(h, n, z); }else{ - z = (const u8 *)sqlite3_value_blob(pVal); + assert( eType==SQLITE_NULL ); + assert( pTab->bStat1==0 || i!=1 ); + *pbNullPK = 1; } - n = sqlite3_value_bytes(pVal); - if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; - h = sessionHashAppendBlob(h, n, z); - }else{ - assert( eType==SQLITE_NULL ); - assert( pTab->bStat1==0 || i!=1 ); - *pbNullPK = 1; } } } @@ -846,6 +856,7 @@ static int sessionMergeUpdate( */ static int sessionPreupdateEqual( sqlite3_session *pSession, /* Session object that owns SessionTable */ + i64 iRowid, /* Rowid value if pTab->bRowid */ SessionTable *pTab, /* Table associated with change */ SessionChange *pChange, /* Change to compare to */ int op /* Current pre-update operation */ @@ -853,6 +864,11 @@ static int sessionPreupdateEqual( int iCol; /* Used to iterate through columns */ u8 *a = pChange->aRecord; /* Cursor used to scan change record */ + if( pTab->bRowid ){ + if( a[0]!=SQLITE_INTEGER ) return 0; + return sessionGetI64(&a[1])==iRowid; + } + assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE ); for(iCol=0; iColnCol; iCol++){ if( !pTab->abPK[iCol] ){ @@ -997,7 +1013,8 @@ static int sessionTableInfo( int *pnCol, /* OUT: number of columns */ const char **pzTab, /* OUT: Copy of zThis */ const char ***pazCol, /* OUT: Array of column names for table */ - u8 **pabPK /* OUT: Array of booleans - true for PK col */ + u8 **pabPK, /* OUT: Array of booleans - true for PK col */ + int *pbRowid /* OUT: True if only PK is a rowid */ ){ char *zPragma; sqlite3_stmt *pStmt; @@ -1009,6 +1026,7 @@ static int sessionTableInfo( u8 *pAlloc = 0; char **azCol = 0; u8 *abPK = 0; + int bRowid = 0; /* Set to true to use rowid as PK */ assert( pazCol && pabPK ); @@ -1053,10 +1071,15 @@ static int sessionTableInfo( } nByte = nThis + 1; + bRowid = (pbRowid!=0); while( SQLITE_ROW==sqlite3_step(pStmt) ){ nByte += sqlite3_column_bytes(pStmt, 1); nDbCol++; + if( sqlite3_column_int(pStmt, 5) ) bRowid = 0; } + if( nDbCol==0 ) bRowid = 0; + nDbCol += bRowid; + nByte += strlen(SESSIONS_ROWID); rc = sqlite3_reset(pStmt); if( rc==SQLITE_OK ){ @@ -1078,6 +1101,14 @@ static int sessionTableInfo( } i = 0; + if( bRowid ){ + size_t nName = strlen(SESSIONS_ROWID); + memcpy(pAlloc, SESSIONS_ROWID, nName+1); + azCol[i] = (char*)pAlloc; + pAlloc += nName+1; + abPK[i] = 1; + i++; + } while( SQLITE_ROW==sqlite3_step(pStmt) ){ int nName = sqlite3_column_bytes(pStmt, 1); const unsigned char *zName = sqlite3_column_text(pStmt, 1); @@ -1089,7 +1120,6 @@ static int sessionTableInfo( i++; } rc = sqlite3_reset(pStmt); - } /* If successful, populate the output variables. Otherwise, zero them and @@ -1106,6 +1136,7 @@ static int sessionTableInfo( if( pzTab ) *pzTab = 0; sessionFree(pSession, azCol); } + if( pbRowid ) *pbRowid = bRowid; sqlite3_finalize(pStmt); return rc; } @@ -1127,7 +1158,8 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ u8 *abPK; assert( pTab->azCol==0 || pTab->abPK==0 ); pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, - pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK + pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK, + (pSession->bImplicitPK ? &pTab->bRowid : 0) ); if( pSession->rc==SQLITE_OK ){ int i; @@ -1199,6 +1231,7 @@ static int sessionUpdateMaxSize( ){ i64 nNew = 2; if( pC->op==SQLITE_INSERT ){ + if( pTab->bRowid ) nNew += 9; if( op!=SQLITE_DELETE ){ int ii; for(ii=0; iinCol; ii++){ @@ -1215,12 +1248,16 @@ static int sessionUpdateMaxSize( }else{ int ii; u8 *pCsr = pC->aRecord; - for(ii=0; iinCol; ii++){ + if( pTab->bRowid ){ + nNew += 9 + 1; + pCsr += 9; + } + for(ii=pTab->bRowid; iinCol; ii++){ int bChanged = 1; int nOld = 0; int eType; sqlite3_value *p = 0; - pSession->hook.xNew(pSession->hook.pCtx, ii, &p); + pSession->hook.xNew(pSession->hook.pCtx, ii-pTab->bRowid, &p); if( p==0 ){ return SQLITE_NOMEM; } @@ -1299,6 +1336,7 @@ static int sessionUpdateMaxSize( */ static void sessionPreupdateOneChange( int op, /* One of SQLITE_UPDATE, INSERT, DELETE */ + i64 iRowid, sqlite3_session *pSession, /* Session object pTab is attached to */ SessionTable *pTab /* Table that change applies to */ ){ @@ -1314,7 +1352,7 @@ static void sessionPreupdateOneChange( /* Check the number of columns in this xPreUpdate call matches the ** number of columns in the table. */ - if( pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx) ){ + if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){ pSession->rc = SQLITE_SCHEMA; return; } @@ -1347,14 +1385,16 @@ static void sessionPreupdateOneChange( /* Calculate the hash-key for this change. If the primary key of the row ** includes a NULL value, exit early. Such changes are ignored by the ** session module. */ - rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull); + rc = sessionPreupdateHash( + pSession, iRowid, pTab, op==SQLITE_INSERT, &iHash, &bNull + ); if( rc!=SQLITE_OK ) goto error_out; if( bNull==0 ){ /* Search the hash table for an existing record for this row. */ SessionChange *pC; for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){ - if( sessionPreupdateEqual(pSession, pTab, pC, op) ) break; + if( sessionPreupdateEqual(pSession, iRowid, pTab, pC, op) ) break; } if( pC==0 ){ @@ -1369,7 +1409,7 @@ static void sessionPreupdateOneChange( /* Figure out how large an allocation is required */ nByte = sizeof(SessionChange); - for(i=0; inCol; i++){ + for(i=0; i<(pTab->nCol-pTab->bRowid); i++){ sqlite3_value *p = 0; if( op!=SQLITE_INSERT ){ TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p); @@ -1384,6 +1424,9 @@ static void sessionPreupdateOneChange( rc = sessionSerializeValue(0, p, &nByte); if( rc!=SQLITE_OK ) goto error_out; } + if( pTab->bRowid ){ + nByte += 9; /* Size of rowid field - an integer */ + } /* Allocate the change object */ pC = (SessionChange *)sessionMalloc64(pSession, nByte); @@ -1400,7 +1443,12 @@ static void sessionPreupdateOneChange( ** required values and encodings have already been cached in memory. ** It is not possible for an OOM to occur in this block. */ nByte = 0; - for(i=0; inCol; i++){ + if( pTab->bRowid ){ + pC->aRecord[0] = SQLITE_INTEGER; + sessionPutI64(&pC->aRecord[1], iRowid); + nByte = 9; + } + for(i=0; i<(pTab->nCol-pTab->bRowid); i++){ sqlite3_value *p = 0; if( op!=SQLITE_INSERT ){ pSession->hook.xOld(pSession->hook.pCtx, i, &p); @@ -1515,9 +1563,10 @@ static void xPreUpdate( pSession->rc = sessionFindTable(pSession, zName, &pTab); if( pTab ){ assert( pSession->rc==SQLITE_OK ); - sessionPreupdateOneChange(op, pSession, pTab); + assert( op==SQLITE_UPDATE || iKey1==iKey2 ); + sessionPreupdateOneChange(op, iKey1, pSession, pTab); if( op==SQLITE_UPDATE ){ - sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab); + sessionPreupdateOneChange(SQLITE_INSERT, iKey2, pSession, pTab); } } } @@ -1556,6 +1605,7 @@ static void sessionPreupdateHooks( typedef struct SessionDiffCtx SessionDiffCtx; struct SessionDiffCtx { sqlite3_stmt *pStmt; + int bRowid; int nOldOff; }; @@ -1564,17 +1614,17 @@ struct SessionDiffCtx { */ static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; - *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff); + *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff+p->bRowid); return SQLITE_OK; } static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; - *ppVal = sqlite3_column_value(p->pStmt, iVal); + *ppVal = sqlite3_column_value(p->pStmt, iVal+p->bRowid); return SQLITE_OK; } static int sessionDiffCount(void *pCtx){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; - return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt); + return (p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt)) - p->bRowid; } static int sessionDiffDepth(void *pCtx){ (void)pCtx; @@ -1653,14 +1703,16 @@ static char *sessionExprCompareOther( static char *sessionSelectFindNew( const char *zDb1, /* Pick rows in this db only */ const char *zDb2, /* But not in this one */ + int bRowid, const char *zTbl, /* Table name */ const char *zExpr ){ + const char *zSel = (bRowid ? SESSIONS_ROWID ", *" : "*"); char *zRet = sqlite3_mprintf( - "SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS (" + "SELECT %s FROM \"%w\".\"%w\" WHERE NOT EXISTS (" " SELECT 1 FROM \"%w\".\"%w\" WHERE %s" ")", - zDb1, zTbl, zDb2, zTbl, zExpr + zSel, zDb1, zTbl, zDb2, zTbl, zExpr ); return zRet; } @@ -1674,7 +1726,9 @@ static int sessionDiffFindNew( char *zExpr ){ int rc = SQLITE_OK; - char *zStmt = sessionSelectFindNew(zDb1, zDb2, pTab->zName,zExpr); + char *zStmt = sessionSelectFindNew( + zDb1, zDb2, pTab->bRowid, pTab->zName, zExpr + ); if( zStmt==0 ){ rc = SQLITE_NOMEM; @@ -1685,8 +1739,10 @@ static int sessionDiffFindNew( SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; pDiffCtx->pStmt = pStmt; pDiffCtx->nOldOff = 0; + pDiffCtx->bRowid = pTab->bRowid; while( SQLITE_ROW==sqlite3_step(pStmt) ){ - sessionPreupdateOneChange(op, pSession, pTab); + i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0); + sessionPreupdateOneChange(op, iRowid, pSession, pTab); } rc = sqlite3_finalize(pStmt); } @@ -1696,6 +1752,27 @@ static int sessionDiffFindNew( return rc; } +/* +** Return a comma-separated list of the fully-qualified (with both database +** and table name) column names from table pTab. e.g. +** +** "main"."t1"."a", "main"."t1"."b", "main"."t1"."c" +*/ +static char *sessionAllCols( + const char *zDb, + SessionTable *pTab +){ + int ii; + char *zRet = 0; + for(ii=0; iinCol; ii++){ + zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"", + zRet, (zRet ? ", " : ""), zDb, pTab->zName, pTab->azCol[ii] + ); + if( !zRet ) break; + } + return zRet; +} + static int sessionDiffFindModified( sqlite3_session *pSession, SessionTable *pTab, @@ -1710,11 +1787,13 @@ static int sessionDiffFindModified( if( zExpr2==0 ){ rc = SQLITE_NOMEM; }else{ + char *z1 = sessionAllCols(pSession->zDb, pTab); + char *z2 = sessionAllCols(zFrom, pTab); char *zStmt = sqlite3_mprintf( - "SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)", - pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2 + "SELECT %s,%s FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)", + z1, z2, pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2 ); - if( zStmt==0 ){ + if( zStmt==0 || z1==0 || z2==0 ){ rc = SQLITE_NOMEM; }else{ sqlite3_stmt *pStmt; @@ -1725,12 +1804,15 @@ static int sessionDiffFindModified( pDiffCtx->pStmt = pStmt; pDiffCtx->nOldOff = pTab->nCol; while( SQLITE_ROW==sqlite3_step(pStmt) ){ - sessionPreupdateOneChange(SQLITE_UPDATE, pSession, pTab); + i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0); + sessionPreupdateOneChange(SQLITE_UPDATE, iRowid, pSession, pTab); } rc = sqlite3_finalize(pStmt); } - sqlite3_free(zStmt); } + sqlite3_free(zStmt); + sqlite3_free(z1); + sqlite3_free(z2); } return rc; @@ -1769,9 +1851,12 @@ int sqlite3session_diff( int bHasPk = 0; int bMismatch = 0; int nCol; /* Columns in zFrom.zTbl */ + int bRowid = 0; u8 *abPK; const char **azCol = 0; - rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK); + rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK, + pSession->bImplicitPK ? &bRowid : 0 + ); if( rc==SQLITE_OK ){ if( pTo->nCol!=nCol ){ bMismatch = 1; @@ -2113,9 +2198,10 @@ static void sessionAppendStr( int *pRc ){ int nStr = sqlite3Strlen30(zStr); - if( 0==sessionBufferGrow(p, nStr, pRc) ){ + if( 0==sessionBufferGrow(p, nStr+1, pRc) ){ memcpy(&p->aBuf[p->nBuf], zStr, nStr); p->nBuf += nStr; + p->aBuf[p->nBuf] = 0x00; } } @@ -2137,6 +2223,27 @@ static void sessionAppendInteger( sessionAppendStr(p, aBuf, pRc); } +static void sessionAppendPrintf( + SessionBuffer *p, /* Buffer to append to */ + int *pRc, + const char *zFmt, + ... +){ + if( *pRc==SQLITE_OK ){ + char *zApp = 0; + va_list ap; + va_start(ap, zFmt); + zApp = sqlite3_vmprintf(zFmt, ap); + if( zApp==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + sessionAppendStr(p, zApp, pRc); + } + va_end(ap); + sqlite3_free(zApp); + } +} + /* ** This function is a no-op if *pRc is other than SQLITE_OK when it is ** called. Otherwise, append the string zStr enclosed in quotes (") and @@ -2151,7 +2258,7 @@ static void sessionAppendIdent( const char *zStr, /* String to quote, escape and append */ int *pRc /* IN/OUT: Error code */ ){ - int nStr = sqlite3Strlen30(zStr)*2 + 2 + 1; + int nStr = sqlite3Strlen30(zStr)*2 + 2 + 2; if( 0==sessionBufferGrow(p, nStr, pRc) ){ char *zOut = (char *)&p->aBuf[p->nBuf]; const char *zIn = zStr; @@ -2162,6 +2269,7 @@ static void sessionAppendIdent( } *zOut++ = '"'; p->nBuf = (int)((u8 *)zOut - p->aBuf); + p->aBuf[p->nBuf] = 0x00; } } @@ -2297,7 +2405,7 @@ static int sessionAppendUpdate( /* If at least one field has been modified, this is not a no-op. */ if( bChanged ) bNoop = 0; - /* Add a field to the old.* record. This is omitted if this modules is + /* Add a field to the old.* record. This is omitted if this module is ** currently generating a patchset. */ if( bPatchset==0 ){ if( bChanged || abPK[i] ){ @@ -2386,12 +2494,20 @@ static int sessionAppendDelete( ** Formulate and prepare a SELECT statement to retrieve a row from table ** zTab in database zDb based on its primary key. i.e. ** -** SELECT * FROM zDb.zTab WHERE pk1 = ? AND pk2 = ? AND ... +** SELECT *, FROM zDb.zTab WHERE (pk1, pk2,...) IS (?1, ?2,...) +** +** where is: +** +** 1 AND (?A OR ?1 IS ) AND ... +** +** for each non-pk . */ static int sessionSelectStmt( sqlite3 *db, /* Database handle */ + int bIgnoreNoop, const char *zDb, /* Database name */ const char *zTab, /* Table name */ + int bRowid, int nCol, /* Number of columns in table */ const char **azCol, /* Names of table columns */ u8 *abPK, /* PRIMARY KEY array */ @@ -2399,8 +2515,50 @@ static int sessionSelectStmt( ){ int rc = SQLITE_OK; char *zSql = 0; + const char *zSep = ""; + const char *zCols = bRowid ? SESSIONS_ROWID ", *" : "*"; int nSql = -1; + int i; + + SessionBuffer nooptest = {0, 0, 0}; + SessionBuffer pkfield = {0, 0, 0}; + SessionBuffer pkvar = {0, 0, 0}; + + sessionAppendStr(&nooptest, ", 1", &rc); + + if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){ + sessionAppendStr(&nooptest, " AND (?6 OR ?3 IS stat)", &rc); + sessionAppendStr(&pkfield, "tbl, idx", &rc); + sessionAppendStr(&pkvar, + "?1, (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", &rc + ); + zCols = "tbl, ?2, stat"; + }else{ + for(i=0; izDb, zName, &nCol, 0,&azCol,&abPK); - if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){ + rc = sessionTableInfo( + 0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK, + (pSession->bImplicitPK ? &bRowid : 0) + ); + if( rc==SQLITE_OK && ( + pTab->nCol!=nCol + || pTab->bRowid!=bRowid + || memcmp(abPK, pTab->abPK, nCol) + )){ rc = SQLITE_SCHEMA; } @@ -2593,7 +2762,8 @@ static int sessionGenerateChangeset( /* Build and compile a statement to execute: */ if( rc==SQLITE_OK ){ rc = sessionSelectStmt( - db, pSession->zDb, zName, nCol, azCol, abPK, &pSel); + db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel + ); } nNoop = buf.nBuf; @@ -2676,7 +2846,7 @@ int sqlite3session_changeset( int rc; if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE; - rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset); + rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset); assert( rc || pnChangeset==0 || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize ); @@ -2794,6 +2964,19 @@ int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){ break; } + case SQLITE_SESSION_OBJCONFIG_ROWID: { + int iArg = *(int*)pArg; + if( iArg>=0 ){ + if( pSession->pTable ){ + rc = SQLITE_MISUSE; + }else{ + pSession->bImplicitPK = (iArg!=0); + } + } + *(int*)pArg = pSession->bImplicitPK; + break; + } + default: rc = SQLITE_MISUSE; } @@ -3782,6 +3965,8 @@ struct SessionApplyCtx { SessionBuffer rebase; /* Rebase information (if any) here */ u8 bRebaseStarted; /* If table header is already in rebase */ u8 bRebase; /* True to collect rebase information */ + u8 bIgnoreNoop; /* True to ignore no-op conflicts */ + int bRowid; }; /* Number of prepared UPDATE statements to cache. */ @@ -4032,8 +4217,10 @@ static int sessionSelectRow( const char *zTab, /* Table name */ SessionApplyCtx *p /* Session changeset-apply context */ ){ - return sessionSelectStmt( - db, "main", zTab, p->nCol, p->azCol, p->abPK, &p->pSelect); + /* TODO */ + return sessionSelectStmt(db, p->bIgnoreNoop, + "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect + ); } /* @@ -4192,20 +4379,33 @@ static int sessionBindRow( */ static int sessionSeekToRow( sqlite3_changeset_iter *pIter, /* Changeset iterator */ - u8 *abPK, /* Primary key flags array */ - sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */ + SessionApplyCtx *p ){ + sqlite3_stmt *pSelect = p->pSelect; int rc; /* Return code */ int nCol; /* Number of columns in table */ int op; /* Changset operation (SQLITE_UPDATE etc.) */ const char *zDummy; /* Unused */ + sqlite3_clear_bindings(pSelect); sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); rc = sessionBindRow(pIter, op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old, - nCol, abPK, pSelect + nCol, p->abPK, pSelect ); + if( op!=SQLITE_DELETE && p->bIgnoreNoop ){ + int ii; + for(ii=0; rc==SQLITE_OK && iiabPK[ii]==0 ){ + sqlite3_value *pVal = 0; + sqlite3changeset_new(pIter, ii, &pVal); + sqlite3_bind_int(pSelect, ii+1+nCol, (pVal==0)); + if( pVal ) rc = sessionBindValue(pSelect, ii+1, pVal); + } + } + } + if( rc==SQLITE_OK ){ rc = sqlite3_step(pSelect); if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect); @@ -4320,16 +4520,22 @@ static int sessionConflictHandler( /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ if( pbReplace ){ - rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p); }else{ rc = SQLITE_OK; } if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ - pIter->pConflict = p->pSelect; - res = xConflict(pCtx, eType, pIter); - pIter->pConflict = 0; + if( p->bIgnoreNoop + && sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) + ){ + res = SQLITE_CHANGESET_OMIT; + }else{ + pIter->pConflict = p->pSelect; + res = xConflict(pCtx, eType, pIter); + pIter->pConflict = 0; + } rc = sqlite3_reset(p->pSelect); }else if( rc==SQLITE_OK ){ if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){ @@ -4437,7 +4643,7 @@ static int sessionApplyOneOp( sqlite3_step(p->pDelete); rc = sqlite3_reset(p->pDelete); - if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){ rc = sessionConflictHandler( SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry ); @@ -4494,7 +4700,7 @@ static int sessionApplyOneOp( /* Check if there is a conflicting row. For sqlite_stat1, this needs ** to be done using a SELECT, as there is no PRIMARY KEY in the ** database schema to throw an exception if a duplicate is inserted. */ - rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p); if( rc==SQLITE_ROW ){ rc = SQLITE_CONSTRAINT; sqlite3_reset(p->pSelect); @@ -4671,6 +4877,7 @@ static int sessionChangesetApply( memset(&sApply, 0, sizeof(sApply)); sApply.bRebase = (ppRebase && pnRebase); sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP); sqlite3_mutex_enter(sqlite3_db_mutex(db)); if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); @@ -4708,6 +4915,7 @@ static int sessionChangesetApply( sApply.bStat1 = 0; sApply.bDeferConstraints = 1; sApply.bRebaseStarted = 0; + sApply.bRowid = 0; memset(&sApply.constraints, 0, sizeof(SessionBuffer)); /* If an xFilter() callback was specified, invoke it now. If the @@ -4727,8 +4935,8 @@ static int sessionChangesetApply( int i; sqlite3changeset_pk(pIter, &abPK, 0); - rc = sessionTableInfo(0, - db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK + rc = sessionTableInfo(0, db, "main", zNew, + &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid ); if( rc!=SQLITE_OK ) break; for(i=0; iSQLITE_SESSION_OBJCONFIG_SIZE
    @@ -105,12 +109,21 @@ void sqlite3session_delete(sqlite3_session *pSession); ** ** It is an error (SQLITE_MISUSE) to attempt to modify this setting after ** the first table has been attached to the session object. +** +**
    SQLITE_SESSION_OBJCONFIG_ROWID
    +** This option is used to set, clear or query the flag that enables +** collection of data for tables with no explicit PRIMARY KEY. +** +** Normally, tables with no explicit PRIMARY KEY are simply ignored +** by the sessions module. However, if this flag is set, it behaves +** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted +** as their leftmost columns. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. */ -int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); - -/* -*/ -#define SQLITE_SESSION_OBJCONFIG_SIZE 1 +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 +#define SQLITE_SESSION_OBJCONFIG_ROWID 2 /* ** CAPI3REF: Enable Or Disable A Session Object @@ -1243,9 +1256,23 @@ int sqlite3changeset_apply_v2( ** Invert the changeset before applying it. This is equivalent to inverting ** a changeset using sqlite3changeset_invert() before applying it. It is ** an error to specify this flag with a patchset. +** +**
    SQLITE_CHANGESETAPPLY_IGNORENOOP
    +** Do not invoke the conflict handler callback for any changes that +** would not actually modify the database even if they were applied. +** Specifically, this means that the conflict handler is not invoked +** for: +**
      +**
    • a delete change if the row being deleted cannot be found, +**
    • an update change if the modified fields are already set to +** their new values in the conflicting row, or +**
    • an insert change if all fields of the conflicting row match +** the row being inserted. +**
    */ #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 /* ** CAPI3REF: Constants Passed To The Conflict Handler diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 242e0fb0f..0836238b5 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -76,9 +76,11 @@ int sql_exec_changeset( ){ sqlite3_session *pSession = 0; int rc; + int val = 1; /* Create a new session object */ rc = sqlite3session_create(db, "main", &pSession); + sqlite3session_object_config(pSession, SQLITE_SESSION_OBJCONFIG_ROWID, &val); /* Configure the session object to record changes to all tables */ if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL); @@ -260,7 +262,7 @@ static int SQLITE_TCLAPI test_session_cmd( { "diff", 2, "FROMDB TBL", }, /* 8 */ { "memory_used", 0, "", }, /* 9 */ { "changeset_size", 0, "", }, /* 10 */ - { "object_config_size", 1, "INTEGER", }, /* 11 */ + { "object_config", 2, "OPTION INTEGER", }, /* 11 */ { 0 } }; int iSub; @@ -379,15 +381,27 @@ static int SQLITE_TCLAPI test_session_cmd( Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize)); break; } - case 11: { + case 11: { /* object_config */ + struct ObjConfOpt { + const char *zName; + int opt; + } aOpt[] = { + { "size", SQLITE_SESSION_OBJCONFIG_SIZE }, + { "rowid", SQLITE_SESSION_OBJCONFIG_ROWID }, + { 0, 0 } + }; + size_t sz = sizeof(aOpt[0]); + int rc; int iArg; - if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){ + int iOpt; + if( Tcl_GetIndexFromObjStruct(interp,objv[2],aOpt,sz,"option",0,&iOpt) ){ return TCL_ERROR; } - rc = sqlite3session_object_config( - pSession, SQLITE_SESSION_OBJCONFIG_SIZE, &iArg - ); + if( Tcl_GetIntFromObj(interp, objv[3], &iArg) ){ + return TCL_ERROR; + } + rc = sqlite3session_object_config(pSession, aOpt[iOpt].opt, &iArg); if( rc!=SQLITE_OK ){ extern const char *sqlite3ErrName(int); Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); @@ -793,32 +807,31 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( memset(&sStr, 0, sizeof(sStr)); sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); - /* Check for the -nosavepoint flag */ + /* Check for the -nosavepoint, -invert or -ignorenoop switches */ if( bV2 ){ - if( objc>1 ){ + while( objc>1 ){ const char *z1 = Tcl_GetString(objv[1]); int n = strlen(z1); if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){ flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT; - objc--; - objv++; } - } - if( objc>1 ){ - const char *z1 = Tcl_GetString(objv[1]); - int n = strlen(z1); - if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ + else if( n>2 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ flags |= SQLITE_CHANGESETAPPLY_INVERT; - objc--; - objv++; } + else if( n>2 && n<=11 && 0==sqlite3_strnicmp("-ignorenoop", z1, n) ){ + flags |= SQLITE_CHANGESETAPPLY_IGNORENOOP; + }else{ + break; + } + objc--; + objv++; } } if( objc!=4 && objc!=5 ){ const char *zMsg; if( bV2 ){ - zMsg = "?-nosavepoint? ?-inverse? " + zMsg = "?-nosavepoint? ?-inverse? ?-ignorenoop? " "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; }else{ zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 7ffd866f2..a99513bfa 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -44,7 +44,7 @@ # 1) Consolidate the code generation for sqlite3*.*js into a script # which generates the makefile code, rather than using $(call) and # $(eval), or at least centralize the setup of the numerous vars -# related to each build variant (vanilla, esm, bundler-friendly). +# related to each build variant $(JS_BUILD_MODES). # SHELL := $(shell which bash 2>/dev/null) MAKEFILE := $(lastword $(MAKEFILE_LIST)) @@ -52,7 +52,9 @@ CLEAN_FILES := DISTCLEAN_FILES := ./--dummy-- default: all release: oz - +# JS_BUILD_MODES exists solely to reduce repetition in documentation +# below. +JS_BUILD_MODES := vanilla esm bunder-friendly node # Emscripten SDK home dir and related binaries... EMSDK_HOME ?= $(word 1,$(wildcard $(HOME)/emsdk $(HOME)/src/emsdk)) emcc.bin ?= $(word 1,$(wildcard $(EMSDK_HOME)/upstream/emscripten/emcc) $(shell which emcc)) @@ -145,8 +147,27 @@ ifeq (,$(wildcard $(dir.tmp))) dir._tmp := $(shell mkdir -p $(dir.tmp)) endif -sqlite3.c := $(dir.top)/sqlite3.c +######################################################################## +# Set up sqlite3.c and sqlite3.h... +# +# To build with SEE (https://sqlite.org/see), either put sqlite3-see.c +# in the top of this build tree or pass +# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only +# encryption modules with no 3rd-party dependencies will currently +# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not +# coincidentally, those 3 modules are included in the sqlite3-see.c +# bundle. +# +# A custom sqlite3.c must not have any spaces in its name. +sqlite3.canonical.c := $(dir.top)/sqlite3.c +sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c)) sqlite3.h := $(dir.top)/sqlite3.h +ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null)) + SQLITE_C_IS_SEE := 0 +else + SQLITE_C_IS_SEE := 1 + $(info This is an SEE build.) +endif # Most SQLITE_OPT flags are set in sqlite3-wasm.c but we need them # made explicit here for building speedtest1.c. SQLITE_OPT = \ @@ -169,10 +190,13 @@ SQLITE_OPT = \ -DSQLITE_OS_KV_OPTIONAL=1 \ '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \ -DSQLITE_USE_URI=1 \ - -DSQLITE_WASM_ENABLE_C_TESTS + -DSQLITE_WASM_ENABLE_C_TESTS \ + -DSQLITE_C=$(sqlite3.c) -$(sqlite3.c) $(sqlite3.h): +.NOTPARALLEL: $(sqlite3.h) +$(sqlite3.h): $(MAKE) -C $(dir.top) sqlite3.c +$(sqlite3.c): $(sqlite3.h) .PHONY: clean distclean clean: @@ -189,13 +213,42 @@ else $(info Development build. Use '$(MAKE) release' for a smaller release build.) endif +######################################################################## +# Adding custom C code via sqlite3_wasm_extra_init.c: +# +# If the canonical build process finds the file +# sqlite3_wasm_extra_init.c in the main wasm build directory, it +# arranges to include that file in the build of sqlite3.wasm and +# defines SQLITE_EXTRA_INIT=sqlite3_wasm_extra_init. +# +# sqlite3_wasm_extra_init() must be a function with this signature: +# +# int sqlite3_wasm_extra_init(const char *) +# +# and the sqlite3 library will call it with an argument of NULL one +# time during sqlite3_initialize(). If it returns non-0, +# initialization of the library will fail. +# +# The filename can be overridden with: +# +# make sqlite3_wasm_extra_init.c=my_custom_stuff.c +# +# See example_extra_init.c for an example implementation. +######################################################################## +sqlite3_wasm_extra_init.c ?= $(wildcard sqlite3_wasm_extra_init.c) +cflags.wasm_extra_init := +ifneq (,$(sqlite3_wasm_extra_init.c)) + $(info Enabling SQLITE_EXTRA_INIT via $(sqlite3_wasm_extra_init.c).) + cflags.wasm_extra_init := -DSQLITE_WASM_EXTRA_INIT +endif + # bin.version-info = binary to output various sqlite3 version info for # embedding in the JS files and in building the distribution zip file. # It must NOT be in $(dir.tmp) because we need it to survive the # cleanup process for the dist build to work properly. bin.version-info := $(dir.wasm)/version-info $(bin.version-info): $(dir.wasm)/version-info.c $(sqlite3.h) $(MAKEFILE) - $(CC) -O0 -I$(dir.top) -o $@ $< + $(CC) -O0 -I$(dir $(sqlite3.c)) -o $@ $< DISTCLEAN_FILES += $(bin.version-info) # bin.stripcomments is used for stripping C/C++-style comments from JS @@ -253,7 +306,7 @@ endef ######################################################################## # cflags.common = C compiler flags for all builds -cflags.common := -I. -I.. -I$(dir.top) +cflags.common := -I. -I$(dir $(sqlite3.c)) # emcc.WASM_BIGINT = 1 for BigInt (C int64) support, else 0. The API # disables certain features if BigInt is not enabled and such builds # _are not tested_ on any regular basis. @@ -296,10 +349,14 @@ emcc_opt_full := $(emcc_opt) -g3 # EXPORTED_FUNCTIONS.* = files for use with Emscripten's # -sEXPORTED_FUNCTION flag. -EXPORTED_FUNCTIONS.api.in := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) +EXPORTED_FUNCTIONS.api.main := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) +EXPORTED_FUNCTIONS.api.in := $(EXPORTED_FUNCTIONS.api.main) +ifeq (1,$(SQLITE_C_IS_SEE)) + EXPORTED_FUNCTIONS.api.in += $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-see) +endif EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api -$(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE) - cp $(EXPORTED_FUNCTIONS.api.in) $@ +$(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(sqlite3.c) $(MAKEFILE) + cat $(EXPORTED_FUNCTIONS.api.in) > $@ # sqlite3-license-version.js = generated JS file with the license # header and version info. @@ -402,14 +459,20 @@ emcc.exportedRuntimeMethods := \ emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY -ifeq (3.1.31,$(emcc.version)) - emcc.jsflags += -sSTRICT_JS=0 - $(warning Disabling -sSTRICT_JS for emcc $(emcc.version): \ - https://github.com/emscripten-core/emscripten/issues/18610) -else - emcc.jsflags += -sSTRICT_JS=1 -endif -emcc.environment := -sENVIRONMENT=web,worker +emcc.jsflags += -sSTRICT_JS=0 +# STRICT_JS disabled due to: +# https://github.com/emscripten-core/emscripten/issues/18610 +# TL;DR: does not work with MODULARIZE or EXPORT_ES6 as of version 3.1.31. + +# -sENVIRONMENT values for the various build modes: +emcc.environment.vanilla := web,worker +emcc.environment.bundler-friendly := $(emcc.environment.vanilla) +emcc.environment.esm := $(emcc.environment.vanilla) +emcc.environment.node := node +# Note that adding "node" to the list for the other builds causes +# Emscripten to generate code which confuses node: it cannot reliably +# determine whether the build is for a browser or for node. + ######################################################################## # -sINITIAL_MEMORY: How much memory we need to start with is governed # at least in part by whether -sALLOW_MEMORY_GROWTH is enabled. If so, @@ -422,9 +485,9 @@ emcc.environment := -sENVIRONMENT=web,worker # such test results are inconsistent due to browser internals which # are opaque to us. emcc.jsflags += -sALLOW_MEMORY_GROWTH -emcc.INITIAL_MEMORY.128 := 13107200 +emcc.INITIAL_MEMORY.128 := 134217728 emcc.INITIAL_MEMORY.96 := 100663296 -emcc.INITIAL_MEMORY.64 := 64225280 +emcc.INITIAL_MEMORY.64 := 67108864 emcc.INITIAL_MEMORY.32 := 33554432 emcc.INITIAL_MEMORY.16 := 16777216 emcc.INITIAL_MEMORY.8 := 8388608 @@ -441,6 +504,8 @@ emcc.jsflags += -sSTACK_SIZE=512KB # ^^^ ACHTUNG: emsdk 3.1.27 reduced the default stack size from 5MB to # a mere 64KB, which leads to silent memory corruption via the kvvfs # VFS, which requires twice that for its xRead() and xWrite() methods. +# 2023-03: those methods have since been adapted to use a malloc()'d +# buffer. ######################################################################## # $(sqlite3.js.init-func) is the name Emscripten assigns our exported # module init/load function. This symbol name is hard-coded in @@ -509,7 +574,7 @@ emcc.jsflags += -sLLD_REPORT_UNDEFINED $(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) @echo "Making $@..." @{ \ - echo 'self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ + echo 'globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ echo -n ' sqlite3.version = '; \ $(bin.version-info) --json; \ echo ';'; \ @@ -551,10 +616,11 @@ $(post-js.js.in): $(post-jses.js) $(MAKEFILE) ######################################################################## # call-make-pre-post is a $(call)able which creates rules for # pre-js-$(1).js. $1 = the base name of the JS file on whose behalf -# this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is the build -# mode: one of (vanilla, esm, bundler-friendly). This sets up -# --[extern-][pre/post]-js flags in $(pre-post-$(1).flags.$(2)) and -# dependencies in $(pre-post-$(1).deps.$(2)). +# this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is the +# build mode: one of $(JS_BUILD_MODES). This +# sets up --[extern-][pre/post]-js flags in +# $(pre-post-$(1).flags.$(2)) and dependencies in +# $(pre-post-$(1).deps.$(2)). define call-make-pre-post pre-post-$(1).flags.$(2) ?= $$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(2)) $$(MAKEFILE) @@ -579,6 +645,7 @@ endef # https://github.com/emscripten-core/emscripten/issues/14383 sqlite3.wasm := $(dir.dout)/sqlite3.wasm sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c +sqlite3-wasm.cses := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c) # sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter # (predictably) results in a slightly faster binary. We're close # enough to the target speed requirements that the 500ms makes a @@ -626,18 +693,20 @@ pre-post-common.flags := \ pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) ######################################################################## # SETUP_LIB_BUILD_MODE is a $(call)'able which sets up numerous pieces -# for one of the build modes (vanilla, esm, bundler-friendly). +# for one of the build modes. # -# $1 = build mode name +# $1 = build mode name: one of $(JS_BUILD_MODES) # $2 = 1 for ESM build mode, else 0 # $3 = resulting sqlite-api JS/MJS file # $4 = resulting JS/MJS file # $5 = -D... flags for $(bin.c-pp) -# $6 = emcc -sXYZ flags +# $6 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out) +# +# emcc.environment.$(1) must be set to a value for the -sENVIRONMENT flag. define SETUP_LIB_BUILD_MODE $(info Setting up build [$(1)]: $(4)) c-pp.D.$(1) := $(5) -pre-js.js.$(1) := $$(dir.api)/pre-js.$(1).js +pre-js.js.$(1) := $$(dir.tmp)/pre-js.$(1).js $$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)),$$(c-pp.D.$(1)))) post-js.js.$(1) := $$(dir.tmp)/post-js.$(1).js $$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)),$$(c-pp.D.$(1)))) @@ -652,19 +721,21 @@ pre-post-jses.deps.$(1) := $$(pre-post-jses.deps.common) \ $$(eval $$(call call-make-pre-post,sqlite3,$(1))) emcc.flags.sqlite3.$(1) := $(6) $$(eval $$(call C-PP.FILTER, $$(sqlite3-api.js.in), $(3), $(5))) -$(4): $(3) -$(4): $(3) $$(MAKEFILE) $$(sqlite3-wasm.c) $$(EXPORTED_FUNCTIONS.api) $$(pre-post-sqlite3.deps.$(1)) +$(4): $(3) $$(MAKEFILE) $$(sqlite3-wasm.cses) $$(EXPORTED_FUNCTIONS.api) $$(pre-post-sqlite3.deps.$(1)) @echo "Building $$@ ..." $$(emcc.bin) -o $$@ $$(emcc_opt_full) $$(emcc.flags) \ $$(emcc.jsflags) \ + -sENVIRONMENT=$$(emcc.environment.$(1)) \ $$(pre-post-sqlite3.flags.$(1)) $$(emcc.flags.sqlite3.$(1)) \ - $$(cflags.common) $$(SQLITE_OPT) $$(sqlite3-wasm.c) + $$(cflags.common) $$(SQLITE_OPT) $$(cflags.wasm_extra_init) $$(sqlite3-wasm.cses) @$$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(2)) - @if [ bundler-friendly = $(1) ]; then \ - echo "Patching $(3) for sqlite3.wasm..."; \ - rm -f $$(dir.dout)/sqlite3-bundler-friendly.wasm; \ - sed -i -e 's/sqlite3-bundler-friendly.wasm/sqlite3.wasm/g' $$@ || exit $$$$?; \ - fi + @case $(1) in \ + bundler-friendly|node) \ + echo "Patching $(3) for sqlite3.wasm..."; \ + rm -f $$(dir.dout)/sqlite3-$(1).wasm; \ + sed -i -e 's/sqlite3-$(1).wasm/sqlite3.wasm/g' $$@ || exit $$$$?; \ + ;; \ + esac chmod -x $$(sqlite3.wasm) $$(maybe-wasm-strip) $$(sqlite3.wasm) @ls -la $@ $$(sqlite3.wasm) @@ -680,6 +751,8 @@ sqlite3-api.mjs := $(dir.dout)/sqlite3-api.mjs sqlite3.mjs := $(dir.dout)/sqlite3.mjs sqlite3-api-bundler-friendly.mjs := $(dir.dout)/sqlite3-api-bundler-friendly.mjs sqlite3-bundler-friendly.mjs := $(dir.dout)/sqlite3-bundler-friendly.mjs +sqlite3-api-node.mjs := $(dir.dout)/sqlite3-api-node.mjs +sqlite3-node.mjs := $(dir.dout)/sqlite3-node.mjs # Maintenance reminder: careful not to introduce spaces around args $1, $2 #$(info $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) $(eval $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) @@ -687,7 +760,10 @@ $(eval $(call SETUP_LIB_BUILD_MODE,esm,1, $(sqlite3-api.mjs), $(sqlite3.mjs), \ -Dtarget=es6-module, -sEXPORT_ES6 -sUSE_ES6_IMPORT_META)) $(eval $(call SETUP_LIB_BUILD_MODE,bundler-friendly,1,\ $(sqlite3-api-bundler-friendly.mjs),$(sqlite3-bundler-friendly.mjs),\ - $(c-pp.D.esm) -Dtarget=es6-bundler-friendly, $(emcc.flags.sqlite3.esm))) + $(c-pp.D.esm) -Dtarget=es6-bundler-friendly)) +$(eval $(call SETUP_LIB_BUILD_MODE,node,1,\ + $(sqlite3-api-node.mjs),$(sqlite3-node.mjs),\ + $(c-pp.D.bundler-friendly) -Dtarget=node)) # The various -D... values used by *.c-pp.js include: # # -Dtarget=es6-module: for all ESM module builds @@ -699,18 +775,26 @@ $(eval $(call SETUP_LIB_BUILD_MODE,bundler-friendly,1,\ # as string literals so that bundlers' static-analysis tools can # find those files and include them in their bundles. # +# -Dtarget=es6-module -Dtarget=es6-bundler-friendly -Dtarget=node: is +# intended for use by node.js for node.js, as opposed to by +# node.js on behalf of a browser. Mixing -sENVIRONMENT=web and +# -sENVIRONMENT=node leads to ambiguity and confusion on node's +# part, as it's unable to reliably determine whether the target is +# a browser or node. +# ######################################################################## ######################################################################## -# We have to ensure that we do not build both $(sqlite3*.*js) in -# parallel because both result in the creation of $(sqlite3.wasm). We -# have no way to build just the .mjs file without also building the -# .wasm file because the generated .mjs file has to include info about -# the imports needed by the wasm file, so they have to be built +# We have to ensure that we do not build $(sqlite3*.*js) in parallel +# because they all result in the creation of $(sqlite3.wasm). We have +# no way to build just a .[m]js file without also building the .wasm +# file because the generated .[m]js file has to include info about the +# imports needed by the wasm file, so they have to be built # together. i.e. we're building $(sqlite3.wasm) multiple times, but # that's unavoidable (and harmless, just a waste of build time). $(sqlite3.wasm): $(sqlite3.js) $(sqlite3.mjs): $(sqlite3.js) $(sqlite3-bundler-friendly.mjs): $(sqlite3.mjs) +$(sqlite3-node.mjs): $(sqlite3.mjs) CLEAN_FILES += $(sqlite3.wasm) ######################################################################## @@ -723,7 +807,7 @@ sqlite3-worker1.js.in := $(dir.api)/sqlite3-worker1.c-pp.js sqlite3-worker1-promiser.js.in := $(dir.api)/sqlite3-worker1-promiser.c-pp.js sqlite3-worker1.js := $(dir.dout)/sqlite3-worker1.js sqlite3-worker1-promiser.js := $(dir.dout)/sqlite3-worker1-promiser.js -sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-friendly.js +sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\ @@ -772,7 +856,7 @@ emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY) emcc.speedtest1.common += -sINVOKE_RUN=0 emcc.speedtest1.common += --no-entry emcc.speedtest1.common += -sABORTING_MALLOC -emcc.speedtest1.common += -sSTRICT_JS +emcc.speedtest1.common += -sSTRICT_JS=0 emcc.speedtest1.common += -sMODULARIZE emcc.speedtest1.common += -Wno-limited-postlink-optimizations EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) @@ -803,9 +887,9 @@ speedtest1.exit-runtime1 := -sEXIT_RUNTIME=1 # -sEXIT_RUNTIME=1 but we need EXIT_RUNTIME=0 for the worker-based app # which runs speedtest1 multiple times. -$(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api) +$(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api.main) @echo "Making $@ ..." - @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ + @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api.main); } > $@ speedtest1.js := $(dir.dout)/speedtest1.js speedtest1.wasm := $(dir.dout)/speedtest1.wasm cflags.speedtest1 := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM @@ -816,9 +900,11 @@ $(speedtest1.js): $(MAKEFILE) $(speedtest1.cses) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." $(emcc.bin) \ - $(emcc.speedtest1) $(emcc.speedtest1.common) \ + $(emcc.speedtest1) -I$(dir $(sqlite3.canonical.c)) \ + $(emcc.speedtest1.common) \ $(cflags.speedtest1) $(pre-post-speedtest1.flags.vanilla) \ $(SQLITE_OPT) \ + -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c) \ $(speedtest1.exit-runtime0) \ -o $@ $(speedtest1.cses) -lm $(maybe-wasm-strip) $(speedtest1.wasm) diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see new file mode 100644 index 000000000..83f3a97db --- /dev/null +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see @@ -0,0 +1,5 @@ +_sqlite3_key +_sqlite3_key_v2 +_sqlite3_rekey +_sqlite3_rekey_v2 +_sqlite3_activate_see diff --git a/ext/wasm/api/extern-post-js.c-pp.js b/ext/wasm/api/extern-post-js.c-pp.js index a577a63e1..927bf64f9 100644 --- a/ext/wasm/api/extern-post-js.c-pp.js +++ b/ext/wasm/api/extern-post-js.c-pp.js @@ -28,7 +28,7 @@ const toExportForESM = for non-ES6 Module cases but wrong for ES6 modules because those resolve this symbol differently. */ sqlite3InitModule; if(!originalInit){ - throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build."); + throw new Error("Expecting globalThis.sqlite3InitModule to be defined by the Emscripten build."); } /** We need to add some state which our custom Module.locateFile() @@ -41,11 +41,13 @@ const toExportForESM = into the global scope and delete it when sqlite3InitModule() is called. */ - const initModuleState = self.sqlite3InitModuleState = Object.assign(Object.create(null),{ - moduleScript: self?.document?.currentScript, + const initModuleState = globalThis.sqlite3InitModuleState = Object.assign(Object.create(null),{ + moduleScript: globalThis?.document?.currentScript, isWorker: ('undefined' !== typeof WorkerGlobalScope), - location: self.location, - urlParams: new URL(self.location.href).searchParams + location: globalThis.location, + urlParams: globalThis?.location?.href + ? new URL(globalThis.location.href).searchParams + : new URLSearchParams() }); initModuleState.debugModule = initModuleState.urlParams.has('sqlite3.debugModule') @@ -60,14 +62,14 @@ const toExportForESM = initModuleState.sqlite3Dir = li.join('/') + '/'; } - self.sqlite3InitModule = function ff(...args){ - //console.warn("Using replaced sqlite3InitModule()",self.location); + globalThis.sqlite3InitModule = function ff(...args){ + //console.warn("Using replaced sqlite3InitModule()",globalThis.location); return originalInit(...args).then((EmscriptenModule)=>{ - if(self.window!==self && + if('undefined'!==typeof WorkerGlobalScope && (EmscriptenModule['ENVIRONMENT_IS_PTHREAD'] || EmscriptenModule['_pthread_self'] || 'function'===typeof threadAlert - || self.location.pathname.endsWith('.worker.js') + || globalThis?.location?.pathname?.endsWith?.('.worker.js') )){ /** Workaround for wasmfs-generated worker, which calls this routine from each individual thread and requires that its @@ -88,10 +90,10 @@ const toExportForESM = throw e; }); }; - self.sqlite3InitModule.ready = originalInit.ready; + globalThis.sqlite3InitModule.ready = originalInit.ready; - if(self.sqlite3InitModuleState.moduleScript){ - const sim = self.sqlite3InitModuleState; + if(globalThis.sqlite3InitModuleState.moduleScript){ + const sim = globalThis.sqlite3InitModuleState; let src = sim.moduleScript.src.split('/'); src.pop(); sim.scriptDir = src.join('/') + '/'; @@ -99,7 +101,7 @@ const toExportForESM = initModuleState.debugModule('sqlite3InitModuleState =',initModuleState); if(0){ console.warn("Replaced sqlite3InitModule()"); - console.warn("self.location.href =",self.location.href); + console.warn("globalThis.location.href =",globalThis.location.href); if('undefined' !== typeof document){ console.warn("document.currentScript.src =", document?.currentScript?.src); @@ -119,7 +121,7 @@ const toExportForESM = /* AMD modules get injected in a way we cannot override, so we can't handle those here. */ //#endif // !target=es6-module - return self.sqlite3InitModule /* required for ESM */; + return globalThis.sqlite3InitModule /* required for ESM */; })(); //#if target=es6-module export default toExportForESM; diff --git a/ext/wasm/api/pre-js.c-pp.js b/ext/wasm/api/pre-js.c-pp.js index a25c7ce77..878f3e054 100644 --- a/ext/wasm/api/pre-js.c-pp.js +++ b/ext/wasm/api/pre-js.c-pp.js @@ -6,12 +6,12 @@ */ // See notes in extern-post-js.js -const sqlite3InitModuleState = self.sqlite3InitModuleState +const sqlite3InitModuleState = globalThis.sqlite3InitModuleState || Object.assign(Object.create(null),{ debugModule: ()=>{} }); -delete self.sqlite3InitModuleState; -sqlite3InitModuleState.debugModule('self.location =',self.location); +delete globalThis.sqlite3InitModuleState; +sqlite3InitModuleState.debugModule('globalThis.location =',globalThis.location); //#ifnot target=es6-bundler-friendly /** diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index 7c23f8f89..d38b401bf 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -25,7 +25,7 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build exports: Module['asm'], memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */ }, - self.sqlite3ApiConfig || {} + globalThis.sqlite3ApiConfig || {} ); /** @@ -33,29 +33,29 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build sqlite3ApiBootstrap(). That decision will be revisited at some point, as we really want client code to be able to call this to configure certain parts. Clients may modify - self.sqlite3ApiBootstrap.defaultConfig to tweak the default + globalThis.sqlite3ApiBootstrap.defaultConfig to tweak the default configuration used by a no-args call to sqlite3ApiBootstrap(), but must have first loaded their WASM module in order to be able to provide the necessary configuration state. */ - //console.warn("self.sqlite3ApiConfig = ",self.sqlite3ApiConfig); - self.sqlite3ApiConfig = SABC; + //console.warn("globalThis.sqlite3ApiConfig = ",globalThis.sqlite3ApiConfig); + globalThis.sqlite3ApiConfig = SABC; let sqlite3; try{ - sqlite3 = self.sqlite3ApiBootstrap(); + sqlite3 = globalThis.sqlite3ApiBootstrap(); }catch(e){ console.error("sqlite3ApiBootstrap() error:",e); throw e; }finally{ - delete self.sqlite3ApiBootstrap; - delete self.sqlite3ApiConfig; + delete globalThis.sqlite3ApiBootstrap; + delete globalThis.sqlite3ApiConfig; } Module.sqlite3 = sqlite3 /* Needed for customized sqlite3InitModule() to be able to pass the sqlite3 object off to the client. */; }else{ console.warn("This is not running in an Emscripten module context, so", - "self.sqlite3ApiBootstrap() is _not_ being called due to lack", + "globalThis.sqlite3ApiBootstrap() is _not_ being called due to lack", "of config info for the WASM environment.", "It must be called manually."); } diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 7db23bacc..f444ec975 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -16,13 +16,13 @@ initializes the main API pieces so that the downstream components (e.g. sqlite3-api-oo1.js) have all that they need. */ -self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 'use strict'; const toss = (...args)=>{throw new Error(args.join(' '))}; const toss3 = sqlite3.SQLite3Error.toss; const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; - self.WhWasmUtilInstaller(wasm); - delete self.WhWasmUtilInstaller; + globalThis.WhWasmUtilInstaller(wasm); + delete globalThis.WhWasmUtilInstaller; if(0){ /** @@ -327,6 +327,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); } + if(wasm.exports.sqlite3_activate_see instanceof Function){ + wasm.bindingSignatures.push( + ["sqlite3_key", "int", "sqlite3*", "string", "int"], + ["sqlite3_key_v2","int","sqlite3*","string","*","int"], + ["sqlite3_rekey", "int", "sqlite3*", "string", "int"], + ["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"], + ["sqlite3_activate_see", undefined, "string"] + ); + } /** Functions which require BigInt (int64) support are separated from the others because we need to conditionally bind them or apply @@ -605,7 +614,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Install JS<->C struct bindings for the non-opaque struct types we need... */ - sqlite3.StructBinder = self.Jaccwabyt({ + sqlite3.StructBinder = globalThis.Jaccwabyt({ heap: 0 ? wasm.memory : wasm.heap8u, alloc: wasm.alloc, dealloc: wasm.dealloc, @@ -613,7 +622,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ memberPrefix: /* Never change this: this prefix is baked into any amount of code and client-facing docs. */ '$' }); - delete self.Jaccwabyt; + delete globalThis.Jaccwabyt; {// wasm.xWrap() bindings... diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index 914497602..ac6678c88 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -12,9 +12,9 @@ This file contains the so-called OO #1 API wrapper for the sqlite3 WASM build. It requires that sqlite3-api-glue.js has already run - and it installs its deliverable as self.sqlite3.oo1. + and it installs its deliverable as globalThis.sqlite3.oo1. */ -self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const toss = (...args)=>{throw new Error(args.join(' '))}; const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)}; diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index b08ad7a7c..c882d5b24 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -29,7 +29,7 @@ exposed by this API. It is intended to be called one time at the end of the API amalgamation process, passed configuration details for the current environment, and then optionally be removed from - the global object using `delete self.sqlite3ApiBootstrap`. + the global object using `delete globalThis.sqlite3ApiBootstrap`. This function is not intended for client-level use. It is intended for use in creating bundles configured for specific WASM @@ -58,7 +58,7 @@ WASM-exported memory. - `bigIntEnabled`: true if BigInt support is enabled. Defaults to - true if `self.BigInt64Array` is available, else false. Some APIs + true if `globalThis.BigInt64Array` is available, else false. Some APIs will throw exceptions if called without BigInt support, as BigInt is required for marshalling C-side int64 into and out of JS. (Sidebar: it is technically possible to add int64 support via @@ -100,8 +100,8 @@ */ 'use strict'; -self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( - apiConfig = (self.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig) +globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( + apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig) ){ if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */ console.warn("sqlite3ApiBootstrap() called multiple times.", @@ -117,7 +117,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( -sWASM_BIGINT=1, else it will not. */ return !!Module.HEAPU64; } - return !!self.BigInt64Array; + return !!globalThis.BigInt64Array; })(), debug: console.debug.bind(console), warn: console.warn.bind(console), @@ -772,7 +772,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( isBindableTypedArray, isInt32, isSQLableTypedArray, isTypedArray, typedArrayToString, - isUIThread: ()=>(self.window===self && !!self.document), + isUIThread: ()=>(globalThis.window===globalThis && !!globalThis.document), // is this true for ESM?: 'undefined'===typeof WorkerGlobalScope isSharedTypedArray, toss: function(...args){throw new Error(args.join(' '))}, @@ -1203,9 +1203,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( console.error("sqlite3_wasmfs_opfs_dir() can no longer work due "+ "to incompatible WASMFS changes. It will be removed."); if(!pdir - || !self.FileSystemHandle - || !self.FileSystemDirectoryHandle - || !self.FileSystemFileHandle){ + || !globalThis.FileSystemHandle + || !globalThis.FileSystemDirectoryHandle + || !globalThis.FileSystemFileHandle){ return __wasmfsOpfsDir = ""; } try{ @@ -1461,8 +1461,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const rc = Object.create(null); rc.prefix = 'kvvfs-'+which; rc.stores = []; - if('session'===which || ""===which) rc.stores.push(self.sessionStorage); - if('local'===which || ""===which) rc.stores.push(self.localStorage); + if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage); + if('local'===which || ""===which) rc.stores.push(globalThis.localStorage); return rc; }; @@ -1537,8 +1537,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Full docs: https://sqlite.org/c3ref/db_config.html Returns capi.SQLITE_MISUSE if op is not a valid operation ID. + + The variants which take `(int, int*)` arguments treat a + missing or falsy pointer argument as 0. */ - capi.sqlite3_db_config = function f(pDb, op, ...args){ + capi.sqlite3_db_config = function(pDb, op, ...args){ if(!this.s){ this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int', ['sqlite3*', 'int', 'string:static'] @@ -1548,31 +1551,32 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int', ['sqlite3*', 'int', 'int','*']); } - const c = capi; switch(op){ - case c.SQLITE_DBCONFIG_ENABLE_FKEY: - case c.SQLITE_DBCONFIG_ENABLE_TRIGGER: - case c.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: - case c.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: - case c.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: - case c.SQLITE_DBCONFIG_ENABLE_QPSG: - case c.SQLITE_DBCONFIG_TRIGGER_EQP: - case c.SQLITE_DBCONFIG_RESET_DATABASE: - case c.SQLITE_DBCONFIG_DEFENSIVE: - case c.SQLITE_DBCONFIG_WRITABLE_SCHEMA: - case c.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: - case c.SQLITE_DBCONFIG_DQS_DML: - case c.SQLITE_DBCONFIG_DQS_DDL: - case c.SQLITE_DBCONFIG_ENABLE_VIEW: - case c.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: - case c.SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case capi.SQLITE_DBCONFIG_ENABLE_FKEY: + case capi.SQLITE_DBCONFIG_ENABLE_TRIGGER: + case capi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case capi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case capi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case capi.SQLITE_DBCONFIG_ENABLE_QPSG: + case capi.SQLITE_DBCONFIG_TRIGGER_EQP: + case capi.SQLITE_DBCONFIG_RESET_DATABASE: + case capi.SQLITE_DBCONFIG_DEFENSIVE: + case capi.SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case capi.SQLITE_DBCONFIG_DQS_DML: + case capi.SQLITE_DBCONFIG_DQS_DDL: + case capi.SQLITE_DBCONFIG_ENABLE_VIEW: + case capi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case capi.SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case capi.SQLITE_DBCONFIG_STMT_SCANSTATUS: + case capi.SQLITE_DBCONFIG_REVERSE_SCANORDER: return this.ip(pDb, op, args[0], args[1] || 0); - case c.SQLITE_DBCONFIG_LOOKASIDE: + case capi.SQLITE_DBCONFIG_LOOKASIDE: return this.pii(pDb, op, args[0], args[1], args[2]); - case c.SQLITE_DBCONFIG_MAINDBNAME: + case capi.SQLITE_DBCONFIG_MAINDBNAME: return this.s(pDb, op, args[0]); default: - return c.SQLITE_MISUSE; + return capi.SQLITE_MISUSE; } }.bind(Object.create(null)); @@ -1962,7 +1966,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return sqlite3; }/*sqlite3ApiBootstrap()*/; /** - self.sqlite3ApiBootstrap.initializers is an internal detail used by + globalThis.sqlite3ApiBootstrap.initializers is an internal detail used by the various pieces of the sqlite3 API's amalgamation process. It must not be modified by client code except when plugging such code into the amalgamation process. @@ -1980,14 +1984,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( utilized until the whwasmutil.js part is plugged in via sqlite3-api-glue.js. */ -self.sqlite3ApiBootstrap.initializers = []; +globalThis.sqlite3ApiBootstrap.initializers = []; /** - self.sqlite3ApiBootstrap.initializersAsync is an internal detail + globalThis.sqlite3ApiBootstrap.initializersAsync is an internal detail used by the sqlite3 API's amalgamation process. It must not be modified by client code except when plugging such code into the amalgamation process. - The counterpart of self.sqlite3ApiBootstrap.initializers, + The counterpart of globalThis.sqlite3ApiBootstrap.initializers, specifically for initializers which are asynchronous. All entries in this list must be either async functions, non-async functions which return a Promise, or a Promise. Each function in the list is called @@ -1999,10 +2003,10 @@ self.sqlite3ApiBootstrap.initializers = []; This list is not processed until the client calls sqlite3.asyncPostInit(). This means, for example, that intializers - added to self.sqlite3ApiBootstrap.initializers may push entries to + added to globalThis.sqlite3ApiBootstrap.initializers may push entries to this list. */ -self.sqlite3ApiBootstrap.initializersAsync = []; +globalThis.sqlite3ApiBootstrap.initializersAsync = []; /** Client code may assign sqlite3ApiBootstrap.defaultConfig an object-type value before calling sqlite3ApiBootstrap() (without @@ -2012,13 +2016,12 @@ self.sqlite3ApiBootstrap.initializersAsync = []; an environment-suitable configuration without having to define a new global-scope symbol. */ -self.sqlite3ApiBootstrap.defaultConfig = Object.create(null); +globalThis.sqlite3ApiBootstrap.defaultConfig = Object.create(null); /** Placeholder: gets installed by the first call to - self.sqlite3ApiBootstrap(). However, it is recommended that the + globalThis.sqlite3ApiBootstrap(). However, it is recommended that the caller of sqlite3ApiBootstrap() capture its return value and delete - self.sqlite3ApiBootstrap after calling it. It returns the same + globalThis.sqlite3ApiBootstrap after calling it. It returns the same value which will be stored here. */ -self.sqlite3ApiBootstrap.sqlite3 = undefined; - +globalThis.sqlite3ApiBootstrap.sqlite3 = undefined; diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index f82be6cd0..d1c63c96e 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -313,11 +313,11 @@ options.columnNames may be populated by the call to db.exec(). */ -self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ sqlite3.initWorker1API = function(){ 'use strict'; const toss = (...args)=>{throw new Error(args.join(' '))}; - if('function' !== typeof importScripts){ + if(!(globalThis.WorkerGlobalScope instanceof Function)){ toss("initWorker1API() must be run from a Worker thread."); } const self = this.self; @@ -382,10 +382,10 @@ sqlite3.initWorker1API = function(){ */ post: function(msg,xferList){ if(xferList && xferList.length){ - self.postMessage( msg, Array.from(xferList) ); + globalThis.postMessage( msg, Array.from(xferList) ); xferList.length = 0; }else{ - self.postMessage(msg); + globalThis.postMessage(msg); } }, /** Map of DB IDs to DBs. */ @@ -589,7 +589,7 @@ sqlite3.initWorker1API = function(){ } }/*wMsgHandler*/; - self.onmessage = async function(ev){ + globalThis.onmessage = async function(ev){ ev = ev.data; let result, dbId = ev.dbId, evType = ev.type; const arrivalTime = performance.now(); @@ -637,6 +637,6 @@ sqlite3.initWorker1API = function(){ result: result }, wState.xfer); }; - self.postMessage({type:'sqlite3-api',result:'worker1-ready'}); + globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'}); }.bind({self, sqlite3}); }); diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js index 1456ae08d..ddcad8f61 100644 --- a/ext/wasm/api/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -50,10 +50,10 @@ const wPost = (type,...args)=>postMessage({type, payload:args}); const installAsyncProxy = function(self){ const toss = function(...args){throw new Error(args.join(' '))}; - if(self.window === self){ + if(globalThis.window === globalThis){ toss("This code cannot run from the main thread.", "Load it as a Worker from a separate Worker."); - }else if(!navigator.storage.getDirectory){ + }else if(!navigator?.storage?.getDirectory){ toss("This API requires navigator.storage.getDirectory."); } @@ -106,8 +106,8 @@ const installAsyncProxy = function(self){ w += m.wait; m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; } - console.log(self.location.href, - "metrics for",self.location.href,":\n", + console.log(globalThis?.location?.href, + "metrics for",globalThis?.location?.href,":\n", metrics, "\nTotal of",n,"op(s) for",t,"ms", "approx",w,"ms spent waiting on OPFS APIs."); @@ -843,7 +843,7 @@ const installAsyncProxy = function(self){ navigator.storage.getDirectory().then(function(d){ state.rootDir = d; - self.onmessage = function({data}){ + globalThis.onmessage = function({data}){ switch(data.type){ case 'opfs-async-init':{ /* Receive shared state from synchronous partner */ @@ -880,17 +880,17 @@ const installAsyncProxy = function(self){ wPost('opfs-async-loaded'); }).catch((e)=>error("error initializing OPFS asyncer:",e)); }/*installAsyncProxy()*/; -if(!self.SharedArrayBuffer){ +if(!globalThis.SharedArrayBuffer){ wPost('opfs-unavailable', "Missing SharedArrayBuffer API.", "The server must emit the COOP/COEP response headers to enable that."); -}else if(!self.Atomics){ +}else if(!globalThis.Atomics){ wPost('opfs-unavailable', "Missing Atomics API.", "The server must emit the COOP/COEP response headers to enable that."); -}else if(!self.FileSystemHandle || - !self.FileSystemDirectoryHandle || - !self.FileSystemFileHandle || - !self.FileSystemFileHandle.prototype.createSyncAccessHandle || - !navigator.storage.getDirectory){ +}else if(!globalThis.FileSystemHandle || + !globalThis.FileSystemDirectoryHandle || + !globalThis.FileSystemFileHandle || + !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle || + !navigator?.storage?.getDirectory){ wPost('opfs-unavailable',"Missing required OPFS APIs."); }else{ installAsyncProxy(self); diff --git a/ext/wasm/api/sqlite3-v-helper.js b/ext/wasm/api/sqlite3-v-helper.js index 10be8ebce..80ab7c5b0 100644 --- a/ext/wasm/api/sqlite3-v-helper.js +++ b/ext/wasm/api/sqlite3-v-helper.js @@ -15,10 +15,13 @@ with its virtual table counterpart, sqlite3.vtab. */ 'use strict'; -self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; const vfs = Object.create(null), vtab = Object.create(null); + const StructBinder = sqlite3.StructBinder + /* we require a local alias b/c StructBinder is removed from the sqlite3 + object during the final steps of the API cleanup. */; sqlite3.vfs = vfs; sqlite3.vtab = vtab; @@ -112,7 +115,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const installMethod = function callee( tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck ){ - if(!(tgt instanceof sqlite3.StructBinder.StructType)){ + if(!(tgt instanceof StructBinder.StructType)){ toss("Usage error: target object is-not-a StructType."); }else if(!(func instanceof Function) && !wasm.isPtr(func)){ toss("Usage errror: expecting a Function or WASM pointer to one."); @@ -132,7 +135,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }; /* An ondispose() callback for use with - sqlite3.StructBinder-created types. */ + StructBinder-created types. */ callee.removeFuncList = function(){ if(this.ondispose.__removeFuncList){ this.ondispose.__removeFuncList.forEach( @@ -221,7 +224,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ and the first is an object, it's instead equivalent to calling installMethods(this,...arguments). */ - sqlite3.StructBinder.StructType.prototype.installMethod = function callee( + StructBinder.StructType.prototype.installMethod = function callee( name, func, applyArgcCheck = installMethod.installMethodArgcCheck ){ return (arguments.length < 3 && name && 'object'===typeof name) @@ -233,7 +236,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Equivalent to calling installMethods() with a first argument of this object. */ - sqlite3.StructBinder.StructType.prototype.installMethods = function( + StructBinder.StructType.prototype.installMethods = function( methods, applyArgcCheck = installMethod.installMethodArgcCheck ){ return installMethods(this, methods, applyArgcCheck); diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index 3e3255b0c..5c584702d 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -18,7 +18,7 @@ after sqlite3-api-oo1.js and before sqlite3-api-cleanup.js. */ 'use strict'; -self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** installOpfsVfs() returns a Promise which, on success, installs an sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs @@ -76,23 +76,23 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ `opfs` property, containing several OPFS-specific utilities. */ const installOpfsVfs = function callee(options){ - if(!self.SharedArrayBuffer - || !self.Atomics){ + if(!globalThis.SharedArrayBuffer + || !globalThis.Atomics){ return Promise.reject( new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+ "The server must emit the COOP/COEP response headers to enable those. "+ "See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep") ); - }else if(self.window===self && self.document){ + }else if('undefined'===typeof WorkerGlobalScope){ return Promise.reject( new Error("The OPFS sqlite3_vfs cannot run in the main thread "+ "because it requires Atomics.wait().") ); - }else if(!self.FileSystemHandle || - !self.FileSystemDirectoryHandle || - !self.FileSystemFileHandle || - !self.FileSystemFileHandle.prototype.createSyncAccessHandle || - !navigator.storage.getDirectory){ + }else if(!globalThis.FileSystemHandle || + !globalThis.FileSystemDirectoryHandle || + !globalThis.FileSystemFileHandle || + !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle || + !navigator?.storage?.getDirectory){ return Promise.reject( new Error("Missing required OPFS APIs.") ); @@ -100,7 +100,7 @@ const installOpfsVfs = function callee(options){ if(!options || 'object'!==typeof options){ options = Object.create(null); } - const urlParams = new URL(self.location.href).searchParams; + const urlParams = new URL(globalThis.location.href).searchParams; if(undefined===options.verbose){ options.verbose = urlParams.has('opfs-verbose') ? (+urlParams.get('opfs-verbose') || 2) : 1; @@ -112,16 +112,16 @@ const installOpfsVfs = function callee(options){ options.proxyUri = callee.defaultProxyUri; } - //sqlite3.config.warn("OPFS options =",options,self.location); + //sqlite3.config.warn("OPFS options =",options,globalThis.location); if('function' === typeof options.proxyUri){ options.proxyUri = options.proxyUri(); } - const thePromise = new Promise(function(promiseResolve, promiseReject_){ + const thePromise = new Promise(function(promiseResolve_, promiseReject_){ const loggers = { - 0:sqlite3.config.error.bind(console), - 1:sqlite3.config.warn.bind(console), - 2:sqlite3.config.log.bind(console) + 0:sqlite3.config.error, + 1:sqlite3.config.warn, + 2:sqlite3.config.log }; const logImpl = (level,...args)=>{ if(options.verbose>level) loggers[level]("OPFS syncer:",...args); @@ -149,11 +149,11 @@ const installOpfsVfs = function callee(options){ Returns true if _this_ thread has access to the OPFS APIs. */ const thisThreadHasOPFS = ()=>{ - return self.FileSystemHandle && - self.FileSystemDirectoryHandle && - self.FileSystemFileHandle && - self.FileSystemFileHandle.prototype.createSyncAccessHandle && - navigator.storage.getDirectory; + return globalThis.FileSystemHandle && + globalThis.FileSystemDirectoryHandle && + globalThis.FileSystemFileHandle && + globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle && + navigator?.storage?.getDirectory; }; /** @@ -171,8 +171,8 @@ const installOpfsVfs = function callee(options){ m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0; } - sqlite3.config.log(self.location.href, - "metrics for",self.location.href,":",metrics, + sqlite3.config.log(globalThis.location.href, + "metrics for",globalThis.location.href,":",metrics, "\nTotal of",n,"op(s) for",t, "ms (incl. "+w+" ms of waiting on the async side)"); sqlite3.config.log("Serialization metrics:",metrics.s11n); @@ -193,10 +193,16 @@ const installOpfsVfs = function callee(options){ }/*metrics*/; const opfsVfs = new sqlite3_vfs(); const opfsIoMethods = new sqlite3_io_methods(); - const promiseReject = function(err){ + let promiseWasRejected = undefined; + const promiseReject = (err)=>{ + promiseWasRejected = true; opfsVfs.dispose(); return promiseReject_(err); }; + const promiseResolve = (value)=>{ + promiseWasRejected = false; + return promiseResolve_(value); + }; const W = //#if target=es6-bundler-friendly new Worker(new URL("sqlite3-opfs-async-proxy.js", import.meta.url)); @@ -205,6 +211,18 @@ const installOpfsVfs = function callee(options){ //#else new Worker(options.proxyUri); //#endif + setTimeout(()=>{ + /* At attempt to work around a browser-specific quirk in which + the Worker load is failing in such a way that we neither + resolve nor reject it. This workaround gives that resolve/reject + a time limit and rejects if that timer expires. Discussion: + https://sqlite.org/forum/forumpost/a708c98dcb3ef */ + if(undefined===promiseWasRejected){ + promiseReject( + new Error("Timeout while waiting for OPFS async proxy worker.") + ); + } + }, 4000); W._originalOnError = W.onerror /* will be restored later */; W.onerror = function(err){ // The error object doesn't contain any useful info when the @@ -335,7 +353,6 @@ const installOpfsVfs = function callee(options){ state.opIds.xClose = i++; state.opIds.xDelete = i++; state.opIds.xDeleteNoWait = i++; - state.opIds.xFileControl = i++; state.opIds.xFileSize = i++; state.opIds.xLock = i++; state.opIds.xOpen = i++; @@ -700,12 +717,9 @@ const installOpfsVfs = function callee(options){ return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN; }, xFileControl: function(pFile, opId, pArg){ - mTimeStart('xFileControl'); - const rc = (capi.SQLITE_FCNTL_SYNC===opId) - ? opRun('xSync', pFile, 0) - : capi.SQLITE_NOTFOUND; - mTimeEnd(); - return rc; + /*mTimeStart('xFileControl'); + mTimeEnd();*/ + return capi.SQLITE_NOTFOUND; }, xFileSize: function(pFile,pSz64){ mTimeStart('xFileSize'); @@ -761,8 +775,11 @@ const installOpfsVfs = function callee(options){ return rc; }, xSync: function(pFile,flags){ + mTimeStart('xSync'); ++metrics.xSync.count; - return 0; // impl'd in xFileControl() + const rc = opRun('xSync', pFile, flags); + mTimeEnd(); + return rc; }, xTruncate: function(pFile,sz64){ mTimeStart('xTruncate'); @@ -1171,7 +1188,15 @@ const installOpfsVfs = function callee(options){ /* Truncate journal mode is faster than delete for this vfs, per speedtest1. That gap seems to have closed with Chrome version 108 or 109, but "persist" is very roughly 5-6% - faster than truncate in initial tests. */ + faster than truncate in initial tests. + + For later analysis: Roy Hashimoto notes that TRUNCATE + and PERSIST modes may decrease OPFS concurrency because + multiple connections can open the journal file in those + modes: + + https://github.com/rhashimoto/wa-sqlite/issues/68 + */ "pragma journal_mode=persist;", /* This vfs benefits hugely from cache on moderate/large @@ -1261,14 +1286,18 @@ const installOpfsVfs = function callee(options){ promiseReject(new Error(data.payload.join(' '))); break; case 'opfs-async-loaded': - /*Arrives as soon as the asyc proxy finishes loading. - Pass our config and shared state on to the async worker.*/ + /* Arrives as soon as the asyc proxy finishes loading. + Pass our config and shared state on to the async + worker. */ W.postMessage({type: 'opfs-async-init',args: state}); break; - case 'opfs-async-inited':{ - /*Indicates that the async partner has received the 'init' - and has finished initializing, so the real work can - begin...*/ + case 'opfs-async-inited': { + /* Indicates that the async partner has received the 'init' + and has finished initializing, so the real work can + begin... */ + if(true===promiseWasRejected){ + break /* promise was already rejected via timer */; + } try { sqlite3.vfs.installVfs({ io: {struct: opfsIoMethods, methods: ioSyncWrappers}, @@ -1300,10 +1329,15 @@ const installOpfsVfs = function callee(options){ } break; } - default: - promiseReject(e); - error("Unexpected message from the async worker:",data); + default: { + const errMsg = ( + "Unexpected message from the OPFS async worker: " + + JSON.stringify(data) + ); + error(errMsg); + promiseReject(new Error(errMsg)); break; + } }/*switch(data.type)*/ }/*W.onmessage()*/; })/*thePromise*/; @@ -1311,7 +1345,7 @@ const installOpfsVfs = function callee(options){ }/*installOpfsVfs()*/; installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js"; -self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ +globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ try{ let proxyJs = installOpfsVfs.defaultProxyUri; if(sqlite3.scriptInfo.sqlite3Dir){ diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index e7513509c..dbe594dc3 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -163,15 +163,14 @@ # define SQLITE_USE_URI 1 #endif -#include -#include "sqlite3.c" /* yes, .c instead of .h. */ - -#if defined(__EMSCRIPTEN__) -# include +#ifdef SQLITE_WASM_EXTRA_INIT +# define SQLITE_EXTRA_INIT sqlite3_wasm_extra_init #endif +#include + /* -** SQLITE_WASM_KEEP is functionally identical to EMSCRIPTEN_KEEPALIVE +** SQLITE_WASM_EXPORT is functionally identical to EMSCRIPTEN_KEEPALIVE ** but is not Emscripten-specific. It explicitly marks functions for ** export into the target wasm file without requiring explicit listing ** of those functions in Emscripten's -sEXPORTED_FUNCTIONS=... list @@ -193,10 +192,34 @@ ** this writing we are tied to Emscripten for various reasons ** and cannot test the library with other build environments. */ -#define SQLITE_WASM_KEEP __attribute__((used,visibility("default"))) +#define SQLITE_WASM_EXPORT __attribute__((used,visibility("default"))) // See also: //__attribute__((export_name("theExportedName"), used, visibility("default"))) +/* +** Which sqlite3.c we're using needs to be configurable to enable +** building against a custom copy, e.g. the SEE variant. Note that we +** #include the .c file, rather than the header, so that the WASM +** extensions have access to private API internals. +** +** The caveat here is that custom variants need to account for +** exporting any necessary symbols (e.g. sqlite3_activate_see()). We +** cannot export them from here using SQLITE_WASM_EXPORT because that +** attribute (apparently) has to be part of the function definition. +*/ +#ifndef SQLITE_C +# define SQLITE_C sqlite3.c /* yes, .c instead of .h. */ +#endif +#define INC__STRINGIFY_(f) #f +#define INC__STRINGIFY(f) INC__STRINGIFY_(f) +#include INC__STRINGIFY(SQLITE_C) +#undef INC__STRINGIFY_ +#undef INC__STRINGIFY +#undef SQLITE_C + +#if defined(__EMSCRIPTEN__) +# include +#endif #if 0 /* @@ -210,24 +233,24 @@ ** Another option is to malloc() a chunk of our own and call that our ** "stack". */ -SQLITE_WASM_KEEP void * sqlite3_wasm_stack_end(void){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_end(void){ extern void __heap_base /* see https://stackoverflow.com/questions/10038964 */; return &__heap_base; } -SQLITE_WASM_KEEP void * sqlite3_wasm_stack_begin(void){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_begin(void){ extern void __data_end; return &__data_end; } static void * pWasmStackPtr = 0; -SQLITE_WASM_KEEP void * sqlite3_wasm_stack_ptr(void){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_ptr(void){ if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end(); return pWasmStackPtr; } -SQLITE_WASM_KEEP void sqlite3_wasm_stack_restore(void * p){ +SQLITE_WASM_EXPORT void sqlite3_wasm_stack_restore(void * p){ pWasmStackPtr = p; } -SQLITE_WASM_KEEP void * sqlite3_wasm_stack_alloc(int n){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){ if(n<=0) return 0; n = (n + 7) & ~7 /* align to 8-byte boundary */; unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr(); @@ -263,14 +286,14 @@ static struct { /* ** Returns the current pstack position. */ -SQLITE_WASM_KEEP void * sqlite3_wasm_pstack_ptr(void){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){ return PStack.pPos; } /* ** Sets the pstack position poitner to p. Results are undefined if the ** given value did not come from sqlite3_wasm_pstack_ptr(). */ -SQLITE_WASM_KEEP void sqlite3_wasm_pstack_restore(unsigned char * p){ +SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){ assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos); assert(0==(p & 0x7)); if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){ @@ -285,7 +308,7 @@ SQLITE_WASM_KEEP void sqlite3_wasm_pstack_restore(unsigned char * p){ ** JS code from having to do so, and most uses of the pstack will ** call for doing so). */ -SQLITE_WASM_KEEP void * sqlite3_wasm_pstack_alloc(int n){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){ if( n<=0 ) return 0; //if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */; n = (n + 7) & ~7 /* align to 8-byte boundary */; @@ -298,7 +321,7 @@ SQLITE_WASM_KEEP void * sqlite3_wasm_pstack_alloc(int n){ ** Return the number of bytes left which can be ** sqlite3_wasm_pstack_alloc()'d. */ -SQLITE_WASM_KEEP int sqlite3_wasm_pstack_remaining(void){ +SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){ assert(PStack.pPos >= PStack.pBegin); assert(PStack.pPos <= PStack.pEnd); return (int)(PStack.pPos - PStack.pBegin); @@ -309,7 +332,7 @@ SQLITE_WASM_KEEP int sqlite3_wasm_pstack_remaining(void){ ** any space which is currently allocated. This value is a ** compile-time constant. */ -SQLITE_WASM_KEEP int sqlite3_wasm_pstack_quota(void){ +SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){ return (int)(PStack.pEnd - PStack.pBegin); } @@ -327,7 +350,7 @@ SQLITE_WASM_KEEP int sqlite3_wasm_pstack_quota(void){ ** ** Returns err_code. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ if( db!=0 ){ if( 0!=zMsg ){ @@ -349,7 +372,7 @@ struct WasmTestStruct { void (*xFunc)(void*); }; typedef struct WasmTestStruct WasmTestStruct; -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT void sqlite3_wasm_test_struct(WasmTestStruct * s){ if(s){ s->v4 *= 2; @@ -377,7 +400,7 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){ ** buffer is not large enough for the generated JSON and needs to be ** increased. In debug builds that will trigger an assert(). */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT const char * sqlite3_wasm_enum_json(void){ static char aBuffer[1024 * 20] = {0} /* where the JSON goes */; int n = 0, nChildren = 0, nStruct = 0 @@ -472,6 +495,7 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_CHANGESETSTART_INVERT); DefInt(SQLITE_CHANGESETAPPLY_NOSAVEPOINT); DefInt(SQLITE_CHANGESETAPPLY_INVERT); + DefInt(SQLITE_CHANGESETAPPLY_IGNORENOOP); DefInt(SQLITE_CHANGESET_DATA); DefInt(SQLITE_CHANGESET_NOTFOUND); @@ -543,6 +567,8 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_DBCONFIG_ENABLE_VIEW); DefInt(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT); DefInt(SQLITE_DBCONFIG_TRUSTED_SCHEMA); + DefInt(SQLITE_DBCONFIG_STMT_SCANSTATUS); + DefInt(SQLITE_DBCONFIG_REVERSE_SCANORDER); DefInt(SQLITE_DBCONFIG_MAX); } _DefGroup; @@ -614,6 +640,7 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_FCNTL_CKPT_START); DefInt(SQLITE_FCNTL_EXTERNAL_READER); DefInt(SQLITE_FCNTL_CKSM_FILE); + DefInt(SQLITE_FCNTL_RESET_CACHE); } _DefGroup; DefGroup(flock) { @@ -903,6 +930,7 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_VTAB_CONSTRAINT_SUPPORT); DefInt(SQLITE_VTAB_INNOCUOUS); DefInt(SQLITE_VTAB_DIRECTONLY); + DefInt(SQLITE_VTAB_USES_ALL_SCHEMAS); DefInt(SQLITE_ROLLBACK); //DefInt(SQLITE_IGNORE); // Also used by sqlite3_authorizer() callback DefInt(SQLITE_FAIL); @@ -1182,7 +1210,7 @@ const char * sqlite3_wasm_enum_json(void){ ** method, SQLITE_MISUSE is returned, else the result of the xDelete() ** call is returned. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ int rc = SQLITE_MISUSE /* ??? */; if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0); @@ -1200,7 +1228,7 @@ int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ ** defaulting to "main" if zDbName is 0. Returns 0 if no db with the ** given name is open. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ sqlite3_vfs * pVfs = 0; sqlite3_file_control(pDb, zDbName ? zDbName : "main", @@ -1223,7 +1251,7 @@ sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ ** Returns 0 on success, an SQLITE_xxx code on error. Returns ** SQLITE_MISUSE if pDb is NULL. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_db_reset(sqlite3 *pDb){ int rc = SQLITE_MISUSE; if( pDb ){ @@ -1254,7 +1282,7 @@ int sqlite3_wasm_db_reset(sqlite3 *pDb){ ** sqlite3_wasm_db_serialize() is arguably the better way to achieve ** this. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_db_export_chunked( sqlite3* pDb, int (*xCallback)(unsigned const char *zOut, int n) ){ sqlite3_int64 nSize = 0; @@ -1305,7 +1333,7 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb, ** If `*pOut` is not NULL, the caller is responsible for passing it to ** sqlite3_free() to free it. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, unsigned char **pOut, sqlite3_int64 *nOut, unsigned int mFlags ){ @@ -1362,7 +1390,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, ** portability, so that the API can still work in builds where BigInt ** support is disabled or unavailable. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, const char *zFilename, const unsigned char * pData, @@ -1446,7 +1474,7 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, ** NUL-terminated pointer to that string. It is up to the caller to ** use sqlite3_wasm_pstack_restore() to free the returned pointer. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass, const char *zKeyIn){ assert(sqlite3KvvfsMethods.nKeySize>24); @@ -1465,7 +1493,7 @@ char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass, ** Returns the pointer to the singleton object which holds the kvvfs ** I/O methods and associated state. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } @@ -1480,7 +1508,7 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ ** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a ** valid value. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ switch(op){ case SQLITE_VTAB_DIRECTONLY: @@ -1500,7 +1528,7 @@ int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ ** Wrapper for the variants of sqlite3_db_config() which take ** (int,int*) variadic args. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ switch(op){ case SQLITE_DBCONFIG_ENABLE_FKEY: @@ -1519,6 +1547,8 @@ int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ case SQLITE_DBCONFIG_ENABLE_VIEW: case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: case SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case SQLITE_DBCONFIG_STMT_SCANSTATUS: + case SQLITE_DBCONFIG_REVERSE_SCANORDER: return sqlite3_db_config(pDb, op, arg1, pArg2); default: return SQLITE_MISUSE; } @@ -1531,7 +1561,7 @@ int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ ** Wrapper for the variants of sqlite3_db_config() which take ** (void*,int,int) variadic args. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){ switch(op){ case SQLITE_DBCONFIG_LOOKASIDE: @@ -1547,7 +1577,7 @@ int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int ** Wrapper for the variants of sqlite3_db_config() which take ** (const char *) variadic args. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ switch(op){ case SQLITE_DBCONFIG_MAINDBNAME: @@ -1564,7 +1594,7 @@ int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ ** Binding for combinations of sqlite3_config() arguments which take ** a single integer argument. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_config_i(int op, int arg){ return sqlite3_config(op, arg); } @@ -1576,7 +1606,7 @@ int sqlite3_wasm_config_i(int op, int arg){ ** Binding for combinations of sqlite3_config() arguments which take ** two int arguments. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_config_ii(int op, int arg1, int arg2){ return sqlite3_config(op, arg1, arg2); } @@ -1588,7 +1618,7 @@ int sqlite3_wasm_config_ii(int op, int arg1, int arg2){ ** Binding for combinations of sqlite3_config() arguments which take ** a single i64 argument. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){ return sqlite3_config(op, arg); } @@ -1617,7 +1647,7 @@ int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){ ** Safari-specific quirk covered at ** https://sqlite.org/forum/info/e5b20e1feb37a19a. **/ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT void * sqlite3_wasm_ptr_to_sqlite3_free(void){ return (void*)sqlite3_free; } @@ -1647,7 +1677,7 @@ void * sqlite3_wasm_ptr_to_sqlite3_free(void){ ** the virtual FS fails. In builds compiled without SQLITE_ENABLE_WASMFS ** defined, SQLITE_NOTFOUND is returned without side effects. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_init_wasmfs(const char *zMountPoint){ static backend_t pOpfs = 0; if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs"; @@ -1667,7 +1697,7 @@ int sqlite3_wasm_init_wasmfs(const char *zMountPoint){ return pOpfs ? 0 : SQLITE_NOMEM; } #else -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_init_wasmfs(const char *zUnused){ //emscripten_console_warn("WASMFS OPFS is not compiled in."); if(zUnused){/*unused*/} @@ -1677,51 +1707,51 @@ int sqlite3_wasm_init_wasmfs(const char *zUnused){ #if SQLITE_WASM_TESTS -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int sqlite3_wasm_test_intptr(int * p){ return *p = *p * 2; } -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT void * sqlite3_wasm_test_voidptr(void * p){ return p; } -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int64_t sqlite3_wasm_test_int64_max(void){ return (int64_t)0x7fffffffffffffff; } -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int64_t sqlite3_wasm_test_int64_min(void){ return ~sqlite3_wasm_test_int64_max(); } -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int64_t sqlite3_wasm_test_int64_times2(int64_t x){ return x * 2; } -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){ *max = sqlite3_wasm_test_int64_max(); *min = sqlite3_wasm_test_int64_min(); /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/ } -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT int64_t sqlite3_wasm_test_int64ptr(int64_t * p){ /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/ return *p = *p * 2; } -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT void sqlite3_wasm_test_stack_overflow(int recurse){ if(recurse) sqlite3_wasm_test_stack_overflow(recurse); } /* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */ -SQLITE_WASM_KEEP +SQLITE_WASM_EXPORT char * sqlite3_wasm_test_str_hello(int fail){ char * s = fail ? 0 : (char *)sqlite3_malloc(6); if(s){ @@ -1732,4 +1762,4 @@ char * sqlite3_wasm_test_str_hello(int fail){ } #endif /* SQLITE_WASM_TESTS */ -#undef SQLITE_WASM_KEEP +#undef SQLITE_WASM_EXPORT diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index 0f1ae39ea..48a74d472 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -114,7 +114,7 @@ by all client code except that which tests this API. The `row` property contains the row result in the form implied by the `rowMode` option (defaulting to `'array'`). The `rowNumber` is a - 1-based integer value incremented by 1 on each call into th + 1-based integer value incremented by 1 on each call into the callback. At the end of the result set, the same event is fired with @@ -122,8 +122,17 @@ the end of the result set has been reached. Note that the rows arrive via worker-posted messages, with all the implications of that. + + Notable shortcomings: + + - This API was not designed with ES6 modules in mind. Neither Firefox + nor Safari support, as of March 2023, the {type:"module"} flag to the + Worker constructor, so that particular usage is not something we're going + to target for the time being: + + https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker */ -self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ +globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ // Inspired by: https://stackoverflow.com/a/52439530 if(1===arguments.length && 'function'===typeof arguments[0]){ const f = config; @@ -160,7 +169,7 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ if(msgHandler && msgHandler.onrow){ msgHandler.onrow(ev); return; - } + } if(config.onunhandled) config.onunhandled(arguments[0]); else err("sqlite3Worker1Promiser() unhandled worker message:",ev); return; @@ -236,10 +245,13 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ return p; }; }/*sqlite3Worker1Promiser()*/; -self.sqlite3Worker1Promiser.defaultConfig = { +globalThis.sqlite3Worker1Promiser.defaultConfig = { worker: function(){ //#if target=es6-bundler-friendly - return new Worker("sqlite3-worker1.js"); + return new Worker("sqlite3-worker1-bundler-friendly.mjs",{ + type: 'module' /* Noting that neither Firefox nor Safari suppor this, + as of this writing. */ + }); //#else let theJs = "sqlite3-worker1.js"; if(this.currentScript){ @@ -247,17 +259,17 @@ self.sqlite3Worker1Promiser.defaultConfig = { src.pop(); theJs = src.join('/')+'/' + theJs; //sqlite3.config.warn("promiser currentScript, theJs =",this.currentScript,theJs); - }else{ - //sqlite3.config.warn("promiser self.location =",self.location); - const urlParams = new URL(self.location.href).searchParams; + }else if(globalThis.location){ + //sqlite3.config.warn("promiser globalThis.location =",globalThis.location); + const urlParams = new URL(globalThis.location.href).searchParams; if(urlParams.has('sqlite3.dir')){ theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } } - return new Worker(theJs + self.location.search); + return new Worker(theJs + globalThis.location.search); //#endif }.bind({ - currentScript: self?.document?.currentScript + currentScript: globalThis?.document?.currentScript }), onerror: (...args)=>console.error('worker1 promiser error',...args) }; diff --git a/ext/wasm/api/sqlite3-worker1.c-pp.js b/ext/wasm/api/sqlite3-worker1.c-pp.js index 9e9c3ac42..f26042230 100644 --- a/ext/wasm/api/sqlite3-worker1.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1.c-pp.js @@ -31,20 +31,20 @@ - `sqlite3.dir`, if set, treats the given directory name as the directory from which `sqlite3.js` will be loaded. */ -"use strict"; -(()=>{ //#if target=es6-bundler-friendly - importScripts('sqlite3.js'); +import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs'; //#else - const urlParams = new URL(self.location.href).searchParams; +"use strict"; +{ + const urlParams = globalThis.location + ? new URL(self.location.href).searchParams + : new URLSearchParams(); let theJs = 'sqlite3.js'; if(urlParams.has('sqlite3.dir')){ theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } //console.warn("worker1 theJs =",theJs); importScripts(theJs); +} //#endif - sqlite3InitModule().then((sqlite3)=>{ - sqlite3.initWorker1API(); - }); -})(); +sqlite3InitModule().then(sqlite3 => sqlite3.initWorker1API()); diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index e50210206..489979941 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -45,8 +45,8 @@ Intended usage: ``` - self.WhWasmUtilInstaller(appObject); - delete self.WhWasmUtilInstaller; + globalThis.WhWasmUtilInstaller(appObject); + delete globalThis.WhWasmUtilInstaller; ``` Its global-scope symbol is intended only to provide an easy way to @@ -171,7 +171,7 @@ https://fossil.wanderinghorse.net/r/jaccwabbyt/file/common/whwasmutil.js */ -self.WhWasmUtilInstaller = function(target){ +globalThis.WhWasmUtilInstaller = function(target){ 'use strict'; if(undefined===target.bigIntEnabled){ target.bigIntEnabled = !!self['BigInt64Array']; @@ -2194,7 +2194,7 @@ self.WhWasmUtilInstaller = function(target){ Error handling is up to the caller, who may attach a `catch()` call to the promise. */ -self.WhWasmUtilInstaller.yawl = function(config){ +globalThis.WhWasmUtilInstaller.yawl = function(config){ const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'}); const wui = this; const finalThen = function(arg){ @@ -2240,4 +2240,4 @@ self.WhWasmUtilInstaller.yawl = function(config){ .then(finalThen); }; return loadWasm; -}.bind(self.WhWasmUtilInstaller)/*yawl()*/; +}.bind(globalThis.WhWasmUtilInstaller)/*yawl()*/; diff --git a/ext/wasm/demo-worker1-promiser.js b/ext/wasm/demo-worker1-promiser.js index ef955403c..e8d3d4e5a 100644 --- a/ext/wasm/demo-worker1-promiser.js +++ b/ext/wasm/demo-worker1-promiser.js @@ -9,7 +9,7 @@ * May you share freely, never taking more than you give. *********************************************************************** - + Demonstration of the sqlite3 Worker API #1 Promiser: a Promise-based proxy for for the sqlite3 Worker #1 API. */ @@ -81,7 +81,7 @@ }); logHtml('', "Sending 'open' message and waiting for its response before continuing..."); - + await wtest('open', { filename: dbFilename, simulateError: 0 /* if true, fail the 'open' */, diff --git a/ext/wasm/demo-worker1.js b/ext/wasm/demo-worker1.js index cc63f3a7c..f70179c5e 100644 --- a/ext/wasm/demo-worker1.js +++ b/ext/wasm/demo-worker1.js @@ -62,7 +62,7 @@ return this.queue.shift(); } }; - + const testCount = ()=>{ logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms"); }; @@ -74,7 +74,7 @@ (ev.workerRespondTime - ev.workerReceivedTime),"ms.", "Round-trip event time =", (performance.now() - ev.departureTime),"ms.", - (evd.errorClass ? ev.message : "")//, JSON.stringify(evd) + (evd.errorClass ? evd.message : "")//, JSON.stringify(evd) ); }; diff --git a/ext/wasm/dist.make b/ext/wasm/dist.make index 7073c24b7..3f99ad5a5 100644 --- a/ext/wasm/dist.make +++ b/ext/wasm/dist.make @@ -70,7 +70,8 @@ STRIP_K1.js := $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) \ $(sqlite3-worker1-bundler-friendly.js) $(sqlite3-worker1-promiser-bundler-friendly.js) # STRIP_K2.js = list of JS files which need to be passed through # $(bin.stripcomments) with two -k flags. -STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) $(sqlite3-bundler-friendly.mjs) +STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \ + $(sqlite3-bundler-friendly.mjs) $(sqlite3-node.mjs) ######################################################################## # dist: create the end-user deliverable archive. # diff --git a/ext/wasm/example_extra_init.c b/ext/wasm/example_extra_init.c new file mode 100644 index 000000000..b91d757cd --- /dev/null +++ b/ext/wasm/example_extra_init.c @@ -0,0 +1,23 @@ +/* +** If the canonical build process finds the file +** sqlite3_wasm_extra_init.c in the main wasm build directory, it +** arranges to include that file in the build of sqlite3.wasm and +** defines SQLITE_EXTRA_INIT=sqlite3_wasm_extra_init. +** +** The C file must define the function sqlite3_wasm_extra_init() with +** this signature: +** +** int sqlite3_wasm_extra_init(const char *) +** +** and the sqlite3 library will call it with an argument of NULL one +** time during sqlite3_initialize(). If it returns non-0, +** initialization of the library will fail. +*/ + +#include "sqlite3.h" +#include + +int sqlite3_wasm_extra_init(const char *z){ + fprintf(stderr,"%s: %s()\n", __FILE__, __func__); + return 0; +} diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make index 7facd7e9e..cbe6ab351 100644 --- a/ext/wasm/fiddle.make +++ b/ext/wasm/fiddle.make @@ -29,7 +29,7 @@ fiddle.emcc-flags = \ --minify 0 \ -sALLOW_TABLE_GROWTH \ -sABORTING_MALLOC \ - -sSTRICT_JS \ + -sSTRICT_JS=0 \ -sENVIRONMENT=web,worker \ -sMODULARIZE \ -sDYNAMIC_EXECUTION=0 \ diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index d4ec719fb..bde7d051e 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -19,7 +19,7 @@ */ 'use strict'; -self.Jaccwabyt = function StructBinderFactory(config){ +globalThis.Jaccwabyt = function StructBinderFactory(config){ /* ^^^^ it is recommended that clients move that object into wherever they'd like to have it and delete the self-held copy ("self" being the global window or worker object). This API does not require the diff --git a/ext/wasm/speedtest1-worker.html b/ext/wasm/speedtest1-worker.html index cd0fdb027..0d88049b9 100644 --- a/ext/wasm/speedtest1-worker.html +++ b/ext/wasm/speedtest1-worker.html @@ -23,7 +23,7 @@
    Downloading...
    - +
    = " clauses of the UPDATE statement. So, before + ** running SelectPrep(), change all the eEName values in + ** pStep->pExprList to ENAME_SPAN (from their current value of + ** ENAME_NAME). This is to prevent any ids in ON() clauses that are + ** part of pSrc from being incorrectly resolved against the + ** a[].zEName values as if they were column aliases. */ + renameSetENames(pStep->pExprList, ENAME_SPAN); sqlite3SelectPrep(pParse, pSel, 0); + renameSetENames(pStep->pExprList, ENAME_NAME); rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK; assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList ); assert( pSrc==pSel->pSrc ); diff --git a/src/analyze.c b/src/analyze.c index f3356ea3c..0823bcaef 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -994,11 +994,15 @@ static void analyzeOneTable( int regIdxname = iMem++; /* Register containing index name */ int regStat1 = iMem++; /* Value for the stat column of sqlite_stat1 */ int regPrev = iMem; /* MUST BE LAST (see below) */ +#ifdef SQLITE_ENABLE_STAT4 + int doOnce = 1; /* Flag for a one-time computation */ +#endif #ifdef SQLITE_ENABLE_PREUPDATE_HOOK - Table *pStat1 = 0; + Table *pStat1 = 0; #endif - pParse->nMem = MAX(pParse->nMem, iMem); + sqlite3TouchRegister(pParse, iMem); + assert( sqlite3NoTempsInRange(pParse, regNewRowid, iMem) ); v = sqlite3GetVdbe(pParse); if( v==0 || NEVER(pTab==0) ){ return; @@ -1104,7 +1108,7 @@ static void analyzeOneTable( ** the regPrev array and a trailing rowid (the rowid slot is required ** when building a record to insert into the sample column of ** the sqlite_stat4 table. */ - pParse->nMem = MAX(pParse->nMem, regPrev+nColTest); + sqlite3TouchRegister(pParse, regPrev+nColTest); /* Open a read-only cursor on the index being analyzed. */ assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); @@ -1276,7 +1280,35 @@ static void analyzeOneTable( int addrIsNull; u8 seekOp = HasRowid(pTab) ? OP_NotExists : OP_NotFound; - pParse->nMem = MAX(pParse->nMem, regCol+nCol); + if( doOnce ){ + int mxCol = nCol; + Index *pX; + + /* Compute the maximum number of columns in any index */ + for(pX=pTab->pIndex; pX; pX=pX->pNext){ + int nColX; /* Number of columns in pX */ + if( !HasRowid(pTab) && IsPrimaryKeyIndex(pX) ){ + nColX = pX->nKeyCol; + }else{ + nColX = pX->nColumn; + } + if( nColX>mxCol ) mxCol = nColX; + } + + /* Allocate space to compute results for the largest index */ + sqlite3TouchRegister(pParse, regCol+mxCol); + doOnce = 0; +#ifdef SQLITE_DEBUG + /* Verify that the call to sqlite3ClearTempRegCache() below + ** really is needed. + ** https://sqlite.org/forum/forumpost/83cb4a95a0 (2023-03-25) + */ + testcase( !sqlite3NoTempsInRange(pParse, regEq, regCol+mxCol) ); +#endif + sqlite3ClearTempRegCache(pParse); /* tag-20230325-1 */ + assert( sqlite3NoTempsInRange(pParse, regEq, regCol+mxCol) ); + } + assert( sqlite3NoTempsInRange(pParse, regEq, regCol+nCol) ); addrNext = sqlite3VdbeCurrentAddr(v); callStatGet(pParse, regStat, STAT_GET_ROWID, regSampleRowid); @@ -1357,6 +1389,11 @@ static void analyzeDatabase(Parse *pParse, int iDb){ for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ Table *pTab = (Table*)sqliteHashData(k); analyzeOneTable(pParse, pTab, 0, iStatCur, iMem, iTab); +#ifdef SQLITE_ENABLE_STAT4 + iMem = sqlite3FirstAvailableRegister(pParse, iMem); +#else + assert( iMem==sqlite3FirstAvailableRegister(pParse,iMem) ); +#endif } loadAnalysis(pParse, iDb); } @@ -1744,6 +1781,10 @@ static int loadStatTbl( pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); assert( pIdx==0 || pIdx->nSample==0 ); if( pIdx==0 ) continue; + if( pIdx->aSample!=0 ){ + /* The same index appears in sqlite_stat4 under multiple names */ + continue; + } assert( !HasRowid(pIdx->pTable) || pIdx->nColumn==pIdx->nKeyCol+1 ); if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ nIdxCol = pIdx->nKeyCol; @@ -1751,6 +1792,7 @@ static int loadStatTbl( nIdxCol = pIdx->nColumn; } pIdx->nSampleCol = nIdxCol; + pIdx->mxSample = nSample; nByte = sizeof(IndexSample) * nSample; nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample; nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ @@ -1790,6 +1832,11 @@ static int loadStatTbl( if( zIndex==0 ) continue; pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); if( pIdx==0 ) continue; + if( pIdx->nSample>=pIdx->mxSample ){ + /* Too many slots used because the same index appears in + ** sqlite_stat4 using multiple names */ + continue; + } /* This next condition is true if data has already been loaded from ** the sqlite_stat4 table. */ nCol = pIdx->nSampleCol; @@ -1833,11 +1880,12 @@ static int loadStat4(sqlite3 *db, const char *zDb){ const Table *pStat4; assert( db->lookaside.bDisable ); - if( (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0 + if( OptimizationEnabled(db, SQLITE_Stat4) + && (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0 && IsOrdinaryTable(pStat4) ){ rc = loadStatTbl(db, - "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx", + "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx COLLATE nocase", "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4", zDb ); diff --git a/src/backup.c b/src/backup.c index d9671750f..22615d149 100644 --- a/src/backup.c +++ b/src/backup.c @@ -242,13 +242,7 @@ static int backupOnePage( assert( !isFatalError(p->rc) ); assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ); assert( zSrcData ); - - /* Catch the case where the destination is an in-memory database and the - ** page sizes of the source and destination differ. - */ - if( nSrcPgsz!=nDestPgsz && sqlite3PagerIsMemdb(pDestPager) ){ - rc = SQLITE_READONLY; - } + assert( nSrcPgsz==nDestPgsz || sqlite3PagerIsMemdb(pDestPager)==0 ); /* This loop runs once for each destination page spanned by the source ** page. For each iteration, variable iOff is set to the byte offset @@ -381,7 +375,10 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); pgszDest = sqlite3BtreeGetPageSize(p->pDest); destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); - if( SQLITE_OK==rc && destMode==PAGER_JOURNALMODE_WAL && pgszSrc!=pgszDest ){ + if( SQLITE_OK==rc + && (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager)) + && pgszSrc!=pgszDest + ){ rc = SQLITE_READONLY; } diff --git a/src/btree.c b/src/btree.c index 3de1a0a50..87bc0058b 100644 --- a/src/btree.c +++ b/src/btree.c @@ -136,8 +136,8 @@ sqlite3_uint64 sqlite3BtreeSeekCount(Btree *pBt){ int corruptPageError(int lineno, MemPage *p){ char *zMsg; sqlite3BeginBenignMalloc(); - zMsg = sqlite3_mprintf("database corruption page %d of %s", - (int)p->pgno, sqlite3PagerFilename(p->pBt->pPager, 0) + zMsg = sqlite3_mprintf("database corruption page %u of %s", + p->pgno, sqlite3PagerFilename(p->pBt->pPager, 0) ); sqlite3EndBenignMalloc(); if( zMsg ){ @@ -946,8 +946,25 @@ int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow){ */ void sqlite3BtreeCursorHint(BtCursor *pCur, int eHintType, ...){ /* Used only by system that substitute their own storage engine */ +#ifdef SQLITE_DEBUG + if( ALWAYS(eHintType==BTREE_HINT_RANGE) ){ + va_list ap; + Expr *pExpr; + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3CursorRangeHintExprCheck; + va_start(ap, eHintType); + pExpr = va_arg(ap, Expr*); + w.u.aMem = va_arg(ap, Mem*); + va_end(ap); + assert( pExpr!=0 ); + assert( w.u.aMem!=0 ); + sqlite3WalkExpr(&w, pExpr); + } +#endif /* SQLITE_DEBUG */ } -#endif +#endif /* SQLITE_ENABLE_CURSOR_HINTS */ + /* ** Provide flag hints to the cursor. @@ -1032,7 +1049,7 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){ pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage); if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){ - TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent)); + TRACE(("PTRMAP_UPDATE: %u->(%u,%u)\n", key, eType, parent)); *pRC= rc = sqlite3PagerWrite(pDbPage); if( rc==SQLITE_OK ){ pPtrmap[offset] = eType; @@ -1231,27 +1248,31 @@ static void btreeParseCellPtr( iKey = *pIter; if( iKey>=0x80 ){ u8 x; - iKey = ((iKey&0x7f)<<7) | ((x = *++pIter) & 0x7f); + iKey = (iKey<<7) ^ (x = *++pIter); if( x>=0x80 ){ - iKey = (iKey<<7) | ((x =*++pIter) & 0x7f); + iKey = (iKey<<7) ^ (x = *++pIter); if( x>=0x80 ){ - iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + iKey = (iKey<<7) ^ 0x10204000 ^ (x = *++pIter); if( x>=0x80 ){ - iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); if( x>=0x80 ){ - iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); if( x>=0x80 ){ - iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); if( x>=0x80 ){ - iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); if( x>=0x80 ){ - iKey = (iKey<<8) | (*++pIter); + iKey = (iKey<<8) ^ 0x8000 ^ (*++pIter); } } } } } + }else{ + iKey ^= 0x204000; } + }else{ + iKey ^= 0x4000; } } pIter++; @@ -1328,10 +1349,11 @@ static void btreeParseCell( ** ** cellSizePtrNoPayload() => table internal nodes ** cellSizePtrTableLeaf() => table leaf nodes -** cellSizePtr() => all index nodes & table leaf nodes +** cellSizePtr() => index internal nodes +** cellSizeIdxLeaf() => index leaf nodes */ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ - u8 *pIter = pCell + pPage->childPtrSize; /* For looping over bytes of pCell */ + u8 *pIter = pCell + 4; /* For looping over bytes of pCell */ u8 *pEnd; /* End mark for a varint */ u32 nSize; /* Size value to return */ @@ -1344,6 +1366,49 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ pPage->xParseCell(pPage, pCell, &debuginfo); #endif + assert( pPage->childPtrSize==4 ); + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pItermaxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + assert( nSize>4 ); + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} +static u16 cellSizePtrIdxLeaf(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + assert( pPage->childPtrSize==0 ); nSize = *pIter; if( nSize>=0x80 ){ pEnd = &pIter[8]; @@ -1580,10 +1645,10 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ /* These conditions have already been verified in btreeInitPage() ** if PRAGMA cell_size_check=ON. */ - if( pciCellLast ){ + if( pc>iCellLast ){ return SQLITE_CORRUPT_PAGE(pPage); } - assert( pc>=iCellStart && pc<=iCellLast ); + assert( pc>=0 && pc<=iCellLast ); size = pPage->xCellSize(pPage, &src[pc]); cbrk -= size; if( cbrkusableSize ){ @@ -1698,7 +1763,7 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ ** allocation is being made in order to insert a new cell, so we will ** also end up needing a new cell pointer. */ -static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ +static SQLITE_INLINE int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ const int hdr = pPage->hdrOffset; /* Local cache of pPage->hdrOffset */ u8 * const data = pPage->aData; /* Local cache of pPage->aData */ int top; /* First byte of cell content area */ @@ -1724,13 +1789,14 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ ** integer, so a value of 0 is used in its place. */ pTmp = &data[hdr+5]; top = get2byte(pTmp); - assert( top<=(int)pPage->pBt->usableSize ); /* by btreeComputeFreeSpace() */ if( gap>top ){ if( top==0 && pPage->pBt->usableSize==65536 ){ top = 65536; }else{ return SQLITE_CORRUPT_PAGE(pPage); } + }else if( top>(int)pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); } /* If there is enough space between gap and top for one more cell pointer, @@ -1813,7 +1879,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( iSize>=4 ); /* Minimum cell size is 4 */ - assert( iStart<=pPage->pBt->usableSize-4 ); + assert( CORRUPT_DB || iStart<=pPage->pBt->usableSize-4 ); /* The list of freeblocks must be in ascending order. Find the ** spot on the list where iStart should be inserted. @@ -1870,6 +1936,11 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ } pTmp = &data[hdr+5]; x = get2byte(pTmp); + if( pPage->pBt->btsFlags & BTS_FAST_SECURE ){ + /* Overwrite deleted information with zeros when the secure_delete + ** option is enabled */ + memset(&data[iStart], 0, iSize); + } if( iStart<=x ){ /* The new freeblock is at the beginning of the cell content area, ** so just extend the cell content area rather than create another @@ -1881,14 +1952,9 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ }else{ /* Insert the new freeblock into the freelist */ put2byte(&data[iPtr], iStart); + put2byte(&data[iStart], iFreeBlk); + put2byte(&data[iStart+2], iSize); } - if( pPage->pBt->btsFlags & BTS_FAST_SECURE ){ - /* Overwrite deleted information with zeros when the secure_delete - ** option is enabled */ - memset(&data[iStart], 0, iSize); - } - put2byte(&data[iStart], iFreeBlk); - put2byte(&data[iStart+2], iSize); pPage->nFree += iOrigSize; return SQLITE_OK; } @@ -1925,14 +1991,14 @@ static int decodeFlags(MemPage *pPage, int flagByte){ }else if( flagByte==(PTF_ZERODATA | PTF_LEAF) ){ pPage->intKey = 0; pPage->intKeyLeaf = 0; - pPage->xCellSize = cellSizePtr; + pPage->xCellSize = cellSizePtrIdxLeaf; pPage->xParseCell = btreeParseCellPtrIndex; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; }else{ pPage->intKey = 0; pPage->intKeyLeaf = 0; - pPage->xCellSize = cellSizePtr; + pPage->xCellSize = cellSizePtrIdxLeaf; pPage->xParseCell = btreeParseCellPtrIndex; return SQLITE_CORRUPT_PAGE(pPage); } @@ -3798,7 +3864,7 @@ static int relocatePage( if( iDbPage<3 ) return SQLITE_CORRUPT_BKPT; /* Move page iDbPage from its current location to page number iFreePage */ - TRACE(("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", + TRACE(("AUTOVACUUM: Moving %u to free page %u (ptr page %u type %u)\n", iDbPage, iFreePage, iPtrPage, eType)); rc = sqlite3PagerMovepage(pPager, pDbPage->pDbPage, iFreePage, isCommit); if( rc!=SQLITE_OK ){ @@ -6084,7 +6150,8 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ pPage = pCur->pPage; idx = ++pCur->ix; - if( !pPage->isInit || sqlite3FaultSim(412) ){ + if( sqlite3FaultSim(412) ) pPage->isInit = 0; + if( !pPage->isInit ){ return SQLITE_CORRUPT_BKPT; } @@ -6347,7 +6414,7 @@ static int allocateBtreePage( memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4); *ppPage = pTrunk; pTrunk = 0; - TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); + TRACE(("ALLOCATE: %u trunk - %u free pages left\n", *pPgno, n-1)); }else if( k>(u32)(pBt->usableSize/4 - 2) ){ /* Value of k is out of range. Database corruption */ rc = SQLITE_CORRUPT_PGNO(iTrunk); @@ -6413,7 +6480,7 @@ static int allocateBtreePage( } } pTrunk = 0; - TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); + TRACE(("ALLOCATE: %u trunk - %u free pages left\n", *pPgno, n-1)); #endif }else if( k>0 ){ /* Extract a leaf from the trunk */ @@ -6458,8 +6525,8 @@ static int allocateBtreePage( ){ int noContent; *pPgno = iPage; - TRACE(("ALLOCATE: %d was leaf %d of %d on trunk %d" - ": %d more free pages\n", + TRACE(("ALLOCATE: %u was leaf %u of %u on trunk %u" + ": %u more free pages\n", *pPgno, closest+1, k, pTrunk->pgno, n-1)); rc = sqlite3PagerWrite(pTrunk->pDbPage); if( rc ) goto end_allocate_page; @@ -6515,7 +6582,7 @@ static int allocateBtreePage( ** becomes a new pointer-map page, the second is used by the caller. */ MemPage *pPg = 0; - TRACE(("ALLOCATE: %d from end of file (pointer-map page)\n", pBt->nPage)); + TRACE(("ALLOCATE: %u from end of file (pointer-map page)\n", pBt->nPage)); assert( pBt->nPage!=PENDING_BYTE_PAGE(pBt) ); rc = btreeGetUnusedPage(pBt, pBt->nPage, &pPg, bNoContent); if( rc==SQLITE_OK ){ @@ -6538,7 +6605,7 @@ static int allocateBtreePage( releasePage(*ppPage); *ppPage = 0; } - TRACE(("ALLOCATE: %d from end of file\n", *pPgno)); + TRACE(("ALLOCATE: %u from end of file\n", *pPgno)); } assert( CORRUPT_DB || *pPgno!=PENDING_BYTE_PAGE(pBt) ); @@ -6666,7 +6733,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ } rc = btreeSetHasContent(pBt, iPage); } - TRACE(("FREE-PAGE: %d leaf on trunk page %d\n",pPage->pgno,pTrunk->pgno)); + TRACE(("FREE-PAGE: %u leaf on trunk page %u\n",pPage->pgno,pTrunk->pgno)); goto freepage_out; } } @@ -6687,7 +6754,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ put4byte(pPage->aData, iTrunk); put4byte(&pPage->aData[4], 0); put4byte(&pPage1->aData[32], iPage); - TRACE(("FREE-PAGE: %d new trunk page replacing %d\n", pPage->pgno, iTrunk)); + TRACE(("FREE-PAGE: %u new trunk page replacing %u\n", pPage->pgno, iTrunk)); freepage_out: if( pPage ){ @@ -7046,6 +7113,14 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ ** in pTemp or the original pCell) and also record its index. ** Allocating a new entry in pPage->aCell[] implies that ** pPage->nOverflow is incremented. +** +** The insertCellFast() routine below works exactly the same as +** insertCell() except that it lacks the pTemp and iChild parameters +** which are assumed zero. Other than that, the two routines are the +** same. +** +** Fixes or enhancements to this routine should be reflected in +** insertCellFast()! */ static int insertCell( MemPage *pPage, /* Page into which we are copying */ @@ -7068,14 +7143,103 @@ static int insertCell( assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB ); assert( pPage->nFree>=0 ); + assert( iChild>0 ); if( pPage->nOverflow || sz+2>pPage->nFree ){ if( pTemp ){ memcpy(pTemp, pCell, sz); pCell = pTemp; } - if( iChild ){ - put4byte(pCell, iChild); + put4byte(pCell, iChild); + j = pPage->nOverflow++; + /* Comparison against ArraySize-1 since we hold back one extra slot + ** as a contingency. In other words, never need more than 3 overflow + ** slots but 4 are allocated, just to be safe. */ + assert( j < ArraySize(pPage->apOvfl)-1 ); + pPage->apOvfl[j] = pCell; + pPage->aiOvfl[j] = (u16)i; + + /* When multiple overflows occur, they are always sequential and in + ** sorted order. This invariants arise because multiple overflows can + ** only occur when inserting divider cells into the parent page during + ** balancing, and the dividers are adjacent and sorted. + */ + assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */ + assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */ + }else{ + int rc = sqlite3PagerWrite(pPage->pDbPage); + if( NEVER(rc!=SQLITE_OK) ){ + return rc; + } + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + data = pPage->aData; + assert( &data[pPage->cellOffset]==pPage->aCellIdx ); + rc = allocateSpace(pPage, sz, &idx); + if( rc ){ return rc; } + /* The allocateSpace() routine guarantees the following properties + ** if it returns successfully */ + assert( idx >= 0 ); + assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); + assert( idx+sz <= (int)pPage->pBt->usableSize ); + pPage->nFree -= (u16)(2 + sz); + /* In a corrupt database where an entry in the cell index section of + ** a btree page has a value of 3 or less, the pCell value might point + ** as many as 4 bytes in front of the start of the aData buffer for + ** the source page. Make sure this does not cause problems by not + ** reading the first 4 bytes */ + memcpy(&data[idx+4], pCell+4, sz-4); + put4byte(&data[idx], iChild); + pIns = pPage->aCellIdx + i*2; + memmove(pIns+2, pIns, 2*(pPage->nCell - i)); + put2byte(pIns, idx); + pPage->nCell++; + /* increment the cell count */ + if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; + assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pPage->pBt->autoVacuum ){ + int rc2 = SQLITE_OK; + /* The cell may contain a pointer to an overflow page. If so, write + ** the entry for the overflow page into the pointer map. + */ + ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2); + if( rc2 ) return rc2; } +#endif + } + return SQLITE_OK; +} + +/* +** This variant of insertCell() assumes that the pTemp and iChild +** parameters are both zero. Use this variant in sqlite3BtreeInsert() +** for performance improvement, and also so that this variant is only +** called from that one place, and is thus inlined, and thus runs must +** faster. +** +** Fixes or enhancements to this routine should be reflected into +** the insertCell() routine. +*/ +static int insertCellFast( + MemPage *pPage, /* Page into which we are copying */ + int i, /* New cell becomes the i-th cell of the page */ + u8 *pCell, /* Content of the new cell */ + int sz /* Bytes of content in pCell */ +){ + int idx = 0; /* Where to write new cell content in data[] */ + int j; /* Loop counter */ + u8 *data; /* The content of the whole page */ + u8 *pIns; /* The point in pPage->aCellIdx[] where no cell inserted */ + + assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); + assert( MX_CELL(pPage->pBt)<=10921 ); + assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); + assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) ); + assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB ); + assert( pPage->nFree>=0 ); + assert( pPage->nOverflow==0 ); + if( sz+2>pPage->nFree ){ j = pPage->nOverflow++; /* Comparison against ArraySize-1 since we hold back one extra slot ** as a contingency. In other words, never need more than 3 overflow @@ -7107,17 +7271,7 @@ static int insertCell( assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); assert( idx+sz <= (int)pPage->pBt->usableSize ); pPage->nFree -= (u16)(2 + sz); - if( iChild ){ - /* In a corrupt database where an entry in the cell index section of - ** a btree page has a value of 3 or less, the pCell value might point - ** as many as 4 bytes in front of the start of the aData buffer for - ** the source page. Make sure this does not cause problems by not - ** reading the first 4 bytes */ - memcpy(&data[idx+4], pCell+4, sz-4); - put4byte(&data[idx], iChild); - }else{ - memcpy(&data[idx], pCell, sz); - } + memcpy(&data[idx], pCell, sz); pIns = pPage->aCellIdx + i*2; memmove(pIns+2, pIns, 2*(pPage->nCell - i)); put2byte(pIns, idx); @@ -7302,7 +7456,7 @@ static int rebuildPage( assert( i(u32)usableSize ){ j = 0; } + if( NEVER(j>(u32)usableSize) ){ j = 0; } memcpy(&pTmp[j], &aData[j], usableSize - j); for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kpBt->usableSize]; u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize]; int nRet = 0; - int i; + int i, j; int iEnd = iFirst + nCell; - u8 *pFree = 0; /* \__ Parameters for pending call to */ - int szFree = 0; /* / freeSpace() */ + int nFree = 0; + int aOfst[10]; + int aAfter[10]; for(i=iFirst; iapCell[i]; if( SQLITE_WITHIN(pCell, pStart, pEnd) ){ int sz; + int iAfter; + int iOfst; /* No need to use cachedCellSize() here. The sizes of all cells that ** are to be freed have already been computing while deciding which ** cells need freeing */ sz = pCArray->szCell[i]; assert( sz>0 ); - if( pFree!=(pCell + sz) ){ - if( pFree ){ - assert( pFree>aData && (pFree - aData)<65536 ); - freeSpace(pPg, (u16)(pFree - aData), szFree); + iOfst = (u16)(pCell - aData); + iAfter = iOfst+sz; + for(j=0; jpEnd ){ - return 0; + } + if( j>=nFree ){ + if( nFree>=(int)(sizeof(aOfst)/sizeof(aOfst[0])) ){ + for(j=0; jpEnd ) return 0; + nFree++; } nRet++; } } - if( pFree ){ - assert( pFree>aData && (pFree - aData)<65536 ); - freeSpace(pPg, (u16)(pFree - aData), szFree); + for(j=0; jpPg->aDataEnd ) goto editpage_fail; + if( NEVER(pData>pPg->aDataEnd) ) goto editpage_fail; /* Add cells to the start of the page */ if( iNew0 || (pParent->pgno==1 && pParent->nCell==0) || CORRUPT_DB); - TRACE(("BALANCE: old: %d(nc=%d) %d(nc=%d) %d(nc=%d)\n", + TRACE(("BALANCE: old: %u(nc=%u) %u(nc=%u) %u(nc=%u)\n", apOld[0]->pgno, apOld[0]->nCell, nOld>=2 ? apOld[1]->pgno : 0, nOld>=2 ? apOld[1]->nCell : 0, nOld>=3 ? apOld[2]->pgno : 0, nOld>=3 ? apOld[2]->nCell : 0 @@ -8357,8 +8519,8 @@ static int balance_nonroot( } } - TRACE(("BALANCE: new: %d(%d nc=%d) %d(%d nc=%d) %d(%d nc=%d) " - "%d(%d nc=%d) %d(%d nc=%d)\n", + TRACE(("BALANCE: new: %u(%u nc=%u) %u(%u nc=%u) %u(%u nc=%u) " + "%u(%u nc=%u) %u(%u nc=%u)\n", apNew[0]->pgno, szNew[0], cntNew[0], nNew>=2 ? apNew[1]->pgno : 0, nNew>=2 ? szNew[1] : 0, nNew>=2 ? cntNew[1] - cntNew[0] - !leafData : 0, @@ -8603,7 +8765,7 @@ static int balance_nonroot( } assert( pParent->isInit ); - TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n", + TRACE(("BALANCE: finished: old=%u new=%u cells=%u\n", nOld, nNew, b.nCell)); /* Free any old pages that were not reused as new pages. @@ -8688,7 +8850,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ assert( sqlite3PagerIswriteable(pRoot->pDbPage) ); assert( pChild->nCell==pRoot->nCell || CORRUPT_DB ); - TRACE(("BALANCE: copy root %d into %d\n", pRoot->pgno, pChild->pgno)); + TRACE(("BALANCE: copy root %u into %u\n", pRoot->pgno, pChild->pgno)); /* Copy the overflow cells from pRoot to pChild */ memcpy(pChild->aiOvfl, pRoot->aiOvfl, @@ -9171,7 +9333,7 @@ int sqlite3BtreeInsert( } } assert( pCur->eState==CURSOR_VALID - || (pCur->eState==CURSOR_INVALID && loc) ); + || (pCur->eState==CURSOR_INVALID && loc) || CORRUPT_DB ); pPage = pCur->pPage; assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) ); @@ -9186,7 +9348,7 @@ int sqlite3BtreeInsert( if( rc ) return rc; } - TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", + TRACE(("INSERT: table=%u nkey=%lld ndata=%u page=%u %s\n", pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); assert( pPage->isInit || CORRUPT_DB ); @@ -9262,7 +9424,7 @@ int sqlite3BtreeInsert( }else{ assert( pPage->leaf ); } - rc = insertCell(pPage, idx, newCell, szNew, 0, 0); + rc = insertCellFast(pPage, idx, newCell, szNew); assert( pPage->nOverflow==0 || rc==SQLITE_OK ); assert( rc!=SQLITE_OK || pPage->nCell>0 || pPage->nOverflow>0 ); @@ -9486,6 +9648,9 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ){ return SQLITE_CORRUPT_BKPT; } + if( pCell<&pPage->aCellIdx[pPage->nCell] ){ + return SQLITE_CORRUPT_BKPT; + } /* If the BTREE_SAVEPOSITION bit is on, then the cursor position must ** be preserved following this delete operation. If the current delete @@ -10234,7 +10399,8 @@ static void checkAppendMsg( sqlite3_str_append(&pCheck->errMsg, "\n", 1); } if( pCheck->zPfx ){ - sqlite3_str_appendf(&pCheck->errMsg, pCheck->zPfx, pCheck->v1, pCheck->v2); + sqlite3_str_appendf(&pCheck->errMsg, pCheck->zPfx, + pCheck->v0, pCheck->v1, pCheck->v2); } sqlite3_str_vappendf(&pCheck->errMsg, zFormat, ap); va_end(ap); @@ -10274,11 +10440,11 @@ static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){ */ static int checkRef(IntegrityCk *pCheck, Pgno iPage){ if( iPage>pCheck->nPage || iPage==0 ){ - checkAppendMsg(pCheck, "invalid page number %d", iPage); + checkAppendMsg(pCheck, "invalid page number %u", iPage); return 1; } if( getPageReferenced(pCheck, iPage) ){ - checkAppendMsg(pCheck, "2nd reference to page %d", iPage); + checkAppendMsg(pCheck, "2nd reference to page %u", iPage); return 1; } setPageReferenced(pCheck, iPage); @@ -10304,13 +10470,13 @@ static void checkPtrmap( rc = ptrmapGet(pCheck->pBt, iChild, &ePtrmapType, &iPtrmapParent); if( rc!=SQLITE_OK ){ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) checkOom(pCheck); - checkAppendMsg(pCheck, "Failed to read ptrmap key=%d", iChild); + checkAppendMsg(pCheck, "Failed to read ptrmap key=%u", iChild); return; } if( ePtrmapType!=eType || iPtrmapParent!=iParent ){ checkAppendMsg(pCheck, - "Bad ptr map entry key=%d expected=(%d,%d) got=(%d,%d)", + "Bad ptr map entry key=%u expected=(%u,%u) got=(%u,%u)", iChild, eType, iParent, ePtrmapType, iPtrmapParent); } } @@ -10335,7 +10501,7 @@ static void checkList( if( checkRef(pCheck, iPage) ) break; N--; if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){ - checkAppendMsg(pCheck, "failed to get page %d", iPage); + checkAppendMsg(pCheck, "failed to get page %u", iPage); break; } pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage); @@ -10348,7 +10514,7 @@ static void checkList( #endif if( n>pCheck->pBt->usableSize/4-2 ){ checkAppendMsg(pCheck, - "freelist leaf count too big on page %d", iPage); + "freelist leaf count too big on page %u", iPage); N--; }else{ for(i=0; i<(int)n; i++){ @@ -10380,7 +10546,7 @@ static void checkList( } if( N && nErrAtStart==pCheck->nErr ){ checkAppendMsg(pCheck, - "%s is %d but should be %d", + "%s is %u but should be %u", isFreeList ? "size" : "overflow list length", expected-N, expected); } @@ -10495,8 +10661,8 @@ static int checkTreePage( usableSize = pBt->usableSize; if( iPage==0 ) return 0; if( checkRef(pCheck, iPage) ) return 0; - pCheck->zPfx = "Page %u: "; - pCheck->v1 = iPage; + pCheck->zPfx = "Tree %u page %u: "; + pCheck->v0 = pCheck->v1 = iPage; if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){ checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); @@ -10522,7 +10688,7 @@ static int checkTreePage( hdr = pPage->hdrOffset; /* Set up for cell analysis */ - pCheck->zPfx = "On tree page %u cell %d: "; + pCheck->zPfx = "Tree %u page %u cell %u: "; contentOffset = get2byteNotZero(&data[hdr+5]); assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */ @@ -10542,7 +10708,7 @@ static int checkTreePage( pgno = get4byte(&data[hdr+8]); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - pCheck->zPfx = "On page %u at right child: "; + pCheck->zPfx = "Tree %u page %u right child: "; checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage); } #endif @@ -10566,7 +10732,7 @@ static int checkTreePage( pc = get2byteAligned(pCellIdx); pCellIdx -= 2; if( pcusableSize-4 ){ - checkAppendMsg(pCheck, "Offset %d out of range %d..%d", + checkAppendMsg(pCheck, "Offset %u out of range %u..%u", pc, contentOffset, usableSize-4); doCoverageCheck = 0; continue; @@ -10698,7 +10864,7 @@ static int checkTreePage( */ if( heap[0]==0 && nFrag!=data[hdr+7] ){ checkAppendMsg(pCheck, - "Fragmentation of %d bytes reported as %d on page %u", + "Fragmentation of %u bytes reported as %u on page %u", nFrag, data[hdr+7], iPage); } } @@ -10795,7 +10961,7 @@ int sqlite3BtreeIntegrityCheck( /* Check the integrity of the freelist */ if( bCkFreelist ){ - sCheck.zPfx = "Main freelist: "; + sCheck.zPfx = "Freelist: "; checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), get4byte(&pBt->pPage1->aData[36])); sCheck.zPfx = 0; @@ -10812,7 +10978,7 @@ int sqlite3BtreeIntegrityCheck( mxInHdr = get4byte(&pBt->pPage1->aData[52]); if( mx!=mxInHdr ){ checkAppendMsg(&sCheck, - "max rootpage (%d) disagrees with header (%d)", + "max rootpage (%u) disagrees with header (%u)", mx, mxInHdr ); } @@ -10843,7 +11009,7 @@ int sqlite3BtreeIntegrityCheck( for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ #ifdef SQLITE_OMIT_AUTOVACUUM if( getPageReferenced(&sCheck, i)==0 ){ - checkAppendMsg(&sCheck, "Page %d is never used", i); + checkAppendMsg(&sCheck, "Page %u: never used", i); } #else /* If the database supports auto-vacuum, make sure no tables contain @@ -10851,11 +11017,11 @@ int sqlite3BtreeIntegrityCheck( */ if( getPageReferenced(&sCheck, i)==0 && (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, "Page %d is never used", i); + checkAppendMsg(&sCheck, "Page %u: never used", i); } if( getPageReferenced(&sCheck, i)!=0 && (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, "Pointer map page %d is referenced", i); + checkAppendMsg(&sCheck, "Page %u: pointer map referenced", i); } #endif } diff --git a/src/btreeInt.h b/src/btreeInt.h index 79c3296d0..6d3578422 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -172,7 +172,7 @@ ** byte are used. The integer consists of all bytes that have bit 8 set and ** the first byte with bit 8 clear. The most significant byte of the integer ** appears first. A variable-length integer may not be more than 9 bytes long. -** As a special case, all 8 bytes of the 9th byte are used as data. This +** As a special case, all 8 bits of the 9th byte are used as data. This ** allows a 64-bit integer to be encoded in 9 bytes. ** ** 0x00 becomes 0x00000000 @@ -556,7 +556,7 @@ struct BtCursor { #define BTCF_WriteFlag 0x01 /* True if a write cursor */ #define BTCF_ValidNKey 0x02 /* True if info.nKey is valid */ #define BTCF_ValidOvfl 0x04 /* True if aOverflow is valid */ -#define BTCF_AtLast 0x08 /* Cursor is pointing ot the last entry */ +#define BTCF_AtLast 0x08 /* Cursor is pointing to the last entry */ #define BTCF_Incrblob 0x10 /* True if an incremental I/O handle */ #define BTCF_Multiple 0x20 /* Maybe another cursor on the same btree */ #define BTCF_Pinned 0x40 /* Cursor is busy and cannot be moved */ @@ -701,8 +701,9 @@ struct IntegrityCk { int rc; /* SQLITE_OK, SQLITE_NOMEM, or SQLITE_INTERRUPT */ u32 nStep; /* Number of steps into the integrity_check process */ const char *zPfx; /* Error message prefix */ - Pgno v1; /* Value for first %u substitution in zPfx */ - int v2; /* Value for second %d substitution in zPfx */ + Pgno v0; /* Value for first %u substitution in zPfx (root page) */ + Pgno v1; /* Value for second %u substitution in zPfx (current pg) */ + int v2; /* Value for third %d substitution in zPfx */ StrAccum errMsg; /* Accumulate the error message text here */ u32 *heap; /* Min-heap used for analyzing cell coverage */ sqlite3 *db; /* Database connection running the check */ diff --git a/src/build.c b/src/build.c index eecfa4705..9be444c3c 100644 --- a/src/build.c +++ b/src/build.c @@ -848,7 +848,7 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ if( IsOrdinaryTable(pTable) ){ sqlite3FkDelete(db, pTable); } -#ifndef SQLITE_OMIT_VIRTUAL_TABLE +#ifndef SQLITE_OMIT_VIRTUALTABLE else if( IsVirtual(pTable) ){ sqlite3VtabClear(db, pTable); } diff --git a/src/callback.c b/src/callback.c index 6cbe8e584..c36d51a4e 100644 --- a/src/callback.c +++ b/src/callback.c @@ -185,6 +185,7 @@ void sqlite3SetTextEncoding(sqlite3 *db, u8 enc){ ** strings is BINARY. */ db->pDfltColl = sqlite3FindCollSeq(db, enc, sqlite3StrBINARY, 0); + sqlite3ExpirePreparedStatements(db, 1); } /* diff --git a/src/date.c b/src/date.c index 7cc6fa684..9b7957bbf 100644 --- a/src/date.c +++ b/src/date.c @@ -77,6 +77,7 @@ struct DateTime { char validTZ; /* True (1) if tz is valid */ char tzSet; /* Timezone was set explicitly */ char isError; /* An overflow has occurred */ + char useSubsec; /* Display subsecond precision */ }; @@ -391,6 +392,11 @@ static int parseDateOrTime( }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8)>0 ){ setRawDateNumber(p, r); return 0; + }else if( (sqlite3StrICmp(zDate,"subsec")==0 + || sqlite3StrICmp(zDate,"subsecond")==0) + && sqlite3NotPureFunc(context) ){ + p->useSubsec = 1; + return setDateTimeToCurrent(context, p); } return 1; } @@ -805,8 +811,22 @@ static int parseModifier( ** ** Move the date backwards to the beginning of the current day, ** or month or year. + ** + ** subsecond + ** subsec + ** + ** Show subsecond precision in the output of datetime() and + ** unixepoch() and strftime('%s'). */ - if( sqlite3_strnicmp(z, "start of ", 9)!=0 ) break; + if( sqlite3_strnicmp(z, "start of ", 9)!=0 ){ + if( sqlite3_stricmp(z, "subsec")==0 + || sqlite3_stricmp(z, "subsecond")==0 + ){ + p->useSubsec = 1; + rc = 0; + } + break; + } if( !p->validJD && !p->validYMD && !p->validHMS ) break; z += 9; computeYMD(p); @@ -1004,7 +1024,11 @@ static void unixepochFunc( DateTime x; if( isDate(context, argc, argv, &x)==0 ){ computeJD(&x); - sqlite3_result_int64(context, x.iJD/1000 - 21086676*(i64)10000); + if( x.useSubsec ){ + sqlite3_result_double(context, (x.iJD - 21086676*(i64)10000000)/1000.0); + }else{ + sqlite3_result_int64(context, x.iJD/1000 - 21086676*(i64)10000); + } } } @@ -1020,8 +1044,8 @@ static void datetimeFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - int Y, s; - char zBuf[24]; + int Y, s, n; + char zBuf[32]; computeYMD_HMS(&x); Y = x.Y; if( Y<0 ) Y = -Y; @@ -1042,15 +1066,28 @@ static void datetimeFunc( zBuf[15] = '0' + (x.m/10)%10; zBuf[16] = '0' + (x.m)%10; zBuf[17] = ':'; - s = (int)x.s; - zBuf[18] = '0' + (s/10)%10; - zBuf[19] = '0' + (s)%10; - zBuf[20] = 0; + if( x.useSubsec ){ + s = (int)1000.0*x.s; + zBuf[18] = '0' + (s/10000)%10; + zBuf[19] = '0' + (s/1000)%10; + zBuf[20] = '.'; + zBuf[21] = '0' + (s/100)%10; + zBuf[22] = '0' + (s/10)%10; + zBuf[23] = '0' + (s)%10; + zBuf[24] = 0; + n = 24; + }else{ + s = (int)x.s; + zBuf[18] = '0' + (s/10)%10; + zBuf[19] = '0' + (s)%10; + zBuf[20] = 0; + n = 20; + } if( x.Y<0 ){ zBuf[0] = '-'; - sqlite3_result_text(context, zBuf, 20, SQLITE_TRANSIENT); + sqlite3_result_text(context, zBuf, n, SQLITE_TRANSIENT); }else{ - sqlite3_result_text(context, &zBuf[1], 19, SQLITE_TRANSIENT); + sqlite3_result_text(context, &zBuf[1], n-1, SQLITE_TRANSIENT); } } } @@ -1067,7 +1104,7 @@ static void timeFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - int s; + int s, n; char zBuf[16]; computeHMS(&x); zBuf[0] = '0' + (x.h/10)%10; @@ -1076,11 +1113,24 @@ static void timeFunc( zBuf[3] = '0' + (x.m/10)%10; zBuf[4] = '0' + (x.m)%10; zBuf[5] = ':'; - s = (int)x.s; - zBuf[6] = '0' + (s/10)%10; - zBuf[7] = '0' + (s)%10; - zBuf[8] = 0; - sqlite3_result_text(context, zBuf, 8, SQLITE_TRANSIENT); + if( x.useSubsec ){ + s = (int)1000.0*x.s; + zBuf[6] = '0' + (s/10000)%10; + zBuf[7] = '0' + (s/1000)%10; + zBuf[8] = '.'; + zBuf[9] = '0' + (s/100)%10; + zBuf[10] = '0' + (s/10)%10; + zBuf[11] = '0' + (s)%10; + zBuf[12] = 0; + n = 12; + }else{ + s = (int)x.s; + zBuf[6] = '0' + (s/10)%10; + zBuf[7] = '0' + (s)%10; + zBuf[8] = 0; + n = 8; + } + sqlite3_result_text(context, zBuf, n, SQLITE_TRANSIENT); } } @@ -1211,8 +1261,13 @@ static void strftimeFunc( break; } case 's': { - i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); - sqlite3_str_appendf(&sRes,"%lld",iS); + if( x.useSubsec ){ + sqlite3_str_appendf(&sRes,"%.3f", + (x.iJD - 21086676*(i64)10000000)/1000.0); + }else{ + i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); + sqlite3_str_appendf(&sRes,"%lld",iS); + } break; } case 'S': { diff --git a/src/dbpage.c b/src/dbpage.c index 17e5f44f5..32a9ce55b 100644 --- a/src/dbpage.c +++ b/src/dbpage.c @@ -78,6 +78,7 @@ static int dbpageConnect( (void)pzErr; sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); rc = sqlite3_declare_vtab(db, "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)"); if( rc==SQLITE_OK ){ @@ -161,7 +162,6 @@ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ ){ pIdxInfo->orderByConsumed = 1; } - sqlite3VtabUsesAllSchemas(pIdxInfo); return SQLITE_OK; } diff --git a/src/delete.c b/src/delete.c index 22ef7ab65..27a554916 100644 --- a/src/delete.c +++ b/src/delete.c @@ -114,13 +114,15 @@ static int tabIsReadOnly(Parse *pParse, Table *pTab){ ** If pTab is writable but other errors have occurred -> return 1. ** If pTab is writable and no prior errors -> return 0; */ -int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){ +int sqlite3IsReadOnly(Parse *pParse, Table *pTab, Trigger *pTrigger){ if( tabIsReadOnly(pParse, pTab) ){ sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName); return 1; } #ifndef SQLITE_OMIT_VIEW - if( !viewOk && IsView(pTab) ){ + if( IsView(pTab) + && (pTrigger==0 || (pTrigger->bReturning && pTrigger->pNext==0)) + ){ sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); return 1; } @@ -374,7 +376,7 @@ void sqlite3DeleteFrom( goto delete_from_cleanup; } - if( sqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){ + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ goto delete_from_cleanup; } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); diff --git a/src/expr.c b/src/expr.c index 87adcd344..a81b4595b 100644 --- a/src/expr.c +++ b/src/expr.c @@ -278,11 +278,10 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ }else{ Expr *pNext = p->pRight; /* The Expr.x union is never used at the same time as Expr.pRight */ - assert( ExprUseXList(p) ); - assert( p->x.pList==0 || p->pRight==0 ); - if( p->x.pList!=0 && !db->mallocFailed ){ + assert( !ExprUseXList(p) || p->x.pList==0 || p->pRight==0 ); + if( ExprUseXList(p) && p->x.pList!=0 && !db->mallocFailed ){ int i; - for(i=0; ALWAYS(ix.pList->nExpr); i++){ + for(i=0; ix.pList->nExpr; i++){ if( ExprHasProperty(p->x.pList->a[i].pExpr, EP_Collate) ){ pNext = p->x.pList->a[i].pExpr; break; @@ -1114,9 +1113,9 @@ Select *sqlite3ExprListToValues(Parse *pParse, int nElem, ExprList *pEList){ ** Join two expressions using an AND operator. If either expression is ** NULL, then just return the other expression. ** -** If one side or the other of the AND is known to be false, then instead -** of returning an AND expression, just return a constant expression with -** a value of false. +** If one side or the other of the AND is known to be false, and neither side +** is part of an ON clause, then instead of returning an AND expression, +** just return a constant expression with a value of false. */ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ sqlite3 *db = pParse->db; @@ -1124,14 +1123,17 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ return pRight; }else if( pRight==0 ){ return pLeft; - }else if( (ExprAlwaysFalse(pLeft) || ExprAlwaysFalse(pRight)) - && !IN_RENAME_OBJECT - ){ - sqlite3ExprDeferredDelete(pParse, pLeft); - sqlite3ExprDeferredDelete(pParse, pRight); - return sqlite3Expr(db, TK_INTEGER, "0"); }else{ - return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); + u32 f = pLeft->flags | pRight->flags; + if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse))==EP_IsFalse + && !IN_RENAME_OBJECT + ){ + sqlite3ExprDeferredDelete(pParse, pLeft); + sqlite3ExprDeferredDelete(pParse, pRight); + return sqlite3Expr(db, TK_INTEGER, "0"); + }else{ + return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); + } } } @@ -2376,12 +2378,17 @@ int sqlite3ExprIsTableConstant(Expr *p, int iCur){ } /* -** Check pExpr to see if it is an invariant constraint on data source pSrc. +** Check pExpr to see if it is an constraint on the single data source +** pSrc = &pSrcList->a[iSrc]. In other words, check to see if pExpr +** constrains pSrc but does not depend on any other tables or data +** sources anywhere else in the query. Return true (non-zero) if pExpr +** is a constraint on pSrc only. +** ** This is an optimization. False negatives will perhaps cause slower ** queries, but false positives will yield incorrect answers. So when in ** doubt, return 0. ** -** To be an invariant constraint, the following must be true: +** To be an single-source constraint, the following must be true: ** ** (1) pExpr cannot refer to any table other than pSrc->iCursor. ** @@ -2392,13 +2399,31 @@ int sqlite3ExprIsTableConstant(Expr *p, int iCur){ ** ** (4) If pSrc is the right operand of a LEFT JOIN, then... ** (4a) pExpr must come from an ON clause.. - (4b) and specifically the ON clause associated with the LEFT JOIN. +** (4b) and specifically the ON clause associated with the LEFT JOIN. ** ** (5) If pSrc is not the right operand of a LEFT JOIN or the left ** operand of a RIGHT JOIN, then pExpr must be from the WHERE ** clause, not an ON clause. +** +** (6) Either: +** +** (6a) pExpr does not originate in an ON or USING clause, or +** +** (6b) The ON or USING clause from which pExpr is derived is +** not to the left of a RIGHT JOIN (or FULL JOIN). +** +** Without this restriction, accepting pExpr as a single-table +** constraint might move the the ON/USING filter expression +** from the left side of a RIGHT JOIN over to the right side, +** which leads to incorrect answers. See also restriction (9) +** on push-down. */ -int sqlite3ExprIsTableConstraint(Expr *pExpr, const SrcItem *pSrc){ +int sqlite3ExprIsSingleTableConstraint( + Expr *pExpr, /* The constraint */ + const SrcList *pSrcList, /* Complete FROM clause */ + int iSrc /* Which element of pSrcList to use */ +){ + const SrcItem *pSrc = &pSrcList->a[iSrc]; if( pSrc->fg.jointype & JT_LTORJ ){ return 0; /* rule (3) */ } @@ -2408,6 +2433,19 @@ int sqlite3ExprIsTableConstraint(Expr *pExpr, const SrcItem *pSrc){ }else{ if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (5) */ } + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) /* (6a) */ + && (pSrcList->a[0].fg.jointype & JT_LTORJ)!=0 /* Fast pre-test of (6b) */ + ){ + int jj; + for(jj=0; jjw.iJoin==pSrcList->a[jj].iCursor ){ + if( (pSrcList->a[jj].fg.jointype & JT_LTORJ)!=0 ){ + return 0; /* restriction (6) */ + } + break; + } + } + } return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ } @@ -2650,7 +2688,7 @@ int sqlite3IsRowid(const char *z){ ** pX is the RHS of an IN operator. If pX is a SELECT statement ** that can be simplified to a direct table access, then return ** a pointer to the SELECT statement. If pX is not a SELECT statement, -** or if the SELECT statement needs to be manifested into a transient +** or if the SELECT statement needs to be materialized into a transient ** table, then return NULL. */ #ifndef SQLITE_OMIT_SUBQUERY @@ -2936,7 +2974,6 @@ int sqlite3FindInIndex( CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); int j; - assert( pReq!=0 || pRhs->iColumn==XN_ROWID || pParse->nErr ); for(j=0; jaiColumn[j]!=pRhs->iColumn ) continue; assert( pIdx->azColl[j] ); @@ -3842,7 +3879,7 @@ void sqlite3ExprCodeGeneratedColumn( int nErr = pParse->nErr; assert( v!=0 ); assert( pParse->iSelfTab!=0 ); - if( pParse->iSelfTab>0 ){ + if( pParse->iSelfTab>0 ){ iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut); }else{ iAddr = 0; @@ -4222,7 +4259,19 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ AggInfo *pAggInfo = pExpr->pAggInfo; struct AggInfo_col *pCol; assert( pAggInfo!=0 ); - assert( pExpr->iAgg>=0 && pExpr->iAggnColumn ); + assert( pExpr->iAgg>=0 ); + if( pExpr->iAgg>=pAggInfo->nColumn ){ + /* Happens when the left table of a RIGHT JOIN is null and + ** is using an expression index */ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); +#ifdef SQLITE_VDBE_COVERAGE + /* Verify that the OP_Null above is exercised by tests + ** tag-20230325-2 */ + sqlite3VdbeAddOp2(v, OP_NotNull, target, 1); + VdbeCoverageNeverTaken(v); +#endif + break; + } pCol = &pAggInfo->aCol[pExpr->iAgg]; if( !pAggInfo->directMode ){ return AggInfoColumnReg(pAggInfo, pExpr->iAgg); @@ -4397,11 +4446,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ #ifndef SQLITE_OMIT_CAST case TK_CAST: { /* Expressions of the form: CAST(pLeft AS token) */ - inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); - if( inReg!=target ){ - sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); - inReg = target; - } + sqlite3ExprCode(pParse, pExpr->pLeft, target); + assert( inReg==target ); assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3VdbeAddOp2(v, OP_Cast, target, sqlite3AffinityType(pExpr->u.zToken, 0)); @@ -4740,13 +4786,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** Clear subtypes as subtypes may not cross a subquery boundary. */ assert( pExpr->pLeft ); - inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); - if( inReg!=target ){ - sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); - inReg = target; - } - sqlite3VdbeAddOp1(v, OP_ClrSubtype, inReg); - return inReg; + sqlite3ExprCode(pParse, pExpr->pLeft, target); + sqlite3VdbeAddOp1(v, OP_ClrSubtype, target); + return target; }else{ pExpr = pExpr->pLeft; goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. */ @@ -4856,12 +4898,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** "target" and not someplace else. */ pParse->okConstFactor = 0; /* note (1) above */ - inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); + sqlite3ExprCode(pParse, pExpr->pLeft, target); + assert( target==inReg ); pParse->okConstFactor = okConstFactor; - if( inReg!=target ){ /* note (2) above */ - sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); - inReg = target; - } sqlite3VdbeJumpHere(v, addrINR); break; } @@ -5099,7 +5138,9 @@ void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); if( inReg!=target ){ u8 op; - if( ALWAYS(pExpr) && ExprHasProperty(pExpr,EP_Subquery) ){ + if( ALWAYS(pExpr) + && (ExprHasProperty(pExpr,EP_Subquery) || pExpr->op==TK_REGISTER) + ){ op = OP_Copy; }else{ op = OP_SCopy; @@ -6284,9 +6325,11 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ int iAgg = pExpr->iAgg; Parse *pParse = pWalker->pParse; sqlite3 *db = pParse->db; + assert( iAgg>=0 ); if( pExpr->op!=TK_AGG_FUNCTION ){ - assert( iAgg>=0 && iAggnColumn ); - if( pAggInfo->aCol[iAgg].pCExpr==pExpr ){ + if( iAggnColumn + && pAggInfo->aCol[iAgg].pCExpr==pExpr + ){ pExpr = sqlite3ExprDup(db, pExpr, 0); if( pExpr ){ pAggInfo->aCol[iAgg].pCExpr = pExpr; @@ -6295,8 +6338,9 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ } }else{ assert( pExpr->op==TK_AGG_FUNCTION ); - assert( iAgg>=0 && iAggnFunc ); - if( pAggInfo->aFunc[iAgg].pFExpr==pExpr ){ + if( ALWAYS(iAggnFunc) + && pAggInfo->aFunc[iAgg].pFExpr==pExpr + ){ pExpr = sqlite3ExprDup(db, pExpr, 0); if( pExpr ){ pAggInfo->aFunc[iAgg].pFExpr = pExpr; @@ -6446,7 +6490,12 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ } if( pIEpr==0 ) break; if( NEVER(!ExprUseYTab(pExpr)) ) break; - if( pExpr->pAggInfo!=0 ) break; /* Already resolved by outer context */ + for(i=0; inSrc; i++){ + if( pSrcList->a[0].iCursor==pIEpr->iDataCur ) break; + } + if( i>=pSrcList->nSrc ) break; + if( NEVER(pExpr->pAggInfo!=0) ) break; /* Resolved by outer context */ + if( pParse->nErr ){ return WRC_Abort; } /* If we reach this point, it means that expression pExpr can be ** translated into a reference to an index column as described by @@ -6457,6 +6506,9 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ tmp.iTable = pIEpr->iIdxCur; tmp.iColumn = pIEpr->iIdxCol; findOrCreateAggInfoColumn(pParse, pAggInfo, &tmp); + if( pParse->nErr ){ return WRC_Abort; } + assert( pAggInfo->aCol!=0 ); + assert( tmp.iAggnColumn ); pAggInfo->aCol[tmp.iAgg].pCExpr = pExpr; pExpr->pAggInfo = pAggInfo; pExpr->iAgg = tmp.iAgg; @@ -6480,7 +6532,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ } /* endif pExpr->iTable==pItem->iCursor */ } /* end loop over pSrcList */ } - return WRC_Prune; + return WRC_Continue; } case TK_AGG_FUNCTION: { if( (pNC->ncFlags & NC_InAggFunc)==0 @@ -6633,6 +6685,37 @@ void sqlite3ClearTempRegCache(Parse *pParse){ pParse->nRangeReg = 0; } +/* +** Make sure sufficient registers have been allocated so that +** iReg is a valid register number. +*/ +void sqlite3TouchRegister(Parse *pParse, int iReg){ + if( pParse->nMemnMem = iReg; +} + +#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_DEBUG) +/* +** Return the latest reusable register in the set of all registers. +** The value returned is no less than iMin. If any register iMin or +** greater is in permanent use, then return one more than that last +** permanent register. +*/ +int sqlite3FirstAvailableRegister(Parse *pParse, int iMin){ + const ExprList *pList = pParse->pConstExpr; + if( pList ){ + int i; + for(i=0; inExpr; i++){ + if( pList->a[i].u.iConstExprReg>=iMin ){ + iMin = pList->a[i].u.iConstExprReg + 1; + } + } + } + pParse->nTempReg = 0; + pParse->nRangeReg = 0; + return iMin; +} +#endif /* SQLITE_ENABLE_STAT4 || SQLITE_DEBUG */ + /* ** Validate that no temporary register falls within the range of ** iFirst..iLast, inclusive. This routine is only call from within assert() @@ -6652,6 +6735,14 @@ int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){ return 0; } } + if( pParse->pConstExpr ){ + ExprList *pList = pParse->pConstExpr; + for(i=0; inExpr; i++){ + int iReg = pList->a[i].u.iConstExprReg; + if( iReg==0 ) continue; + if( iReg>=iFirst && iReg<=iLast ) return 0; + } + } return 1; } #endif /* SQLITE_DEBUG */ diff --git a/src/fkey.c b/src/fkey.c index cae6bb19d..29609916b 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -1317,22 +1317,22 @@ static Trigger *fkActionTrigger( if( action==OE_Restrict ){ int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - Token tFrom; - Token tDb; + SrcList *pSrc; Expr *pRaise; - tFrom.z = zFrom; - tFrom.n = nFrom; - tDb.z = db->aDb[iDb].zDbSName; - tDb.n = sqlite3Strlen30(tDb.z); - pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed"); if( pRaise ){ pRaise->affExpr = OE_Abort; } + pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + if( pSrc ){ + assert( pSrc->nSrc==1 ); + pSrc->a[0].zName = sqlite3DbStrDup(db, zFrom); + pSrc->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); + } pSelect = sqlite3SelectNew(pParse, sqlite3ExprListAppend(pParse, 0, pRaise), - sqlite3SrcListAppend(pParse, 0, &tDb, &tFrom), + pSrc, pWhere, 0, 0, 0, 0, 0 ); diff --git a/src/func.c b/src/func.c index 045c60613..4d0da5ded 100644 --- a/src/func.c +++ b/src/func.c @@ -1518,7 +1518,7 @@ static void trimFunc( /* ** The "unknown" function is automatically substituted in place of ** any unrecognized function name when doing an EXPLAIN or EXPLAIN QUERY PLAN -** when the SQLITE_ENABLE_UNKNOWN_FUNCTION compile-time option is used. +** when the SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION compile-time option is used. ** When the "sqlite3" command-line shell is built using this functionality, ** that allows an EXPLAIN or EXPLAIN QUERY PLAN for complex queries ** involving application-defined functions to be examined in a generic diff --git a/src/global.c b/src/global.c index 799b1e47c..fcba7d7fa 100644 --- a/src/global.c +++ b/src/global.c @@ -97,7 +97,7 @@ const unsigned char *sqlite3aGTb = &sqlite3UpperToLower[256+12-OP_Ne]; ** isalnum() 0x06 ** isxdigit() 0x08 ** toupper() 0x20 -** SQLite identifier character 0x40 +** SQLite identifier character 0x40 $, _, or non-ascii ** Quote character 0x80 ** ** Bit 0x20 is set if the mapped character requires translation to upper @@ -291,7 +291,7 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */ 0, /* iPrngSeed */ #ifdef SQLITE_DEBUG - {0,0,0,0,0,0} /* aTune */ + {0,0,0,0,0,0}, /* aTune */ #endif }; diff --git a/src/insert.c b/src/insert.c index 65d11bee5..423af7723 100644 --- a/src/insert.c +++ b/src/insert.c @@ -70,45 +70,47 @@ void sqlite3OpenTable( ** is managed along with the rest of the Index structure. It will be ** released when sqlite3DeleteIndex() is called. */ -const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ +static SQLITE_NOINLINE const char *computeIndexAffStr(sqlite3 *db, Index *pIdx){ + /* The first time a column affinity string for a particular index is + ** required, it is allocated and populated here. It is then stored as + ** a member of the Index structure for subsequent use. + ** + ** The column affinity string will eventually be deleted by + ** sqliteDeleteIndex() when the Index structure itself is cleaned + ** up. + */ + int n; + Table *pTab = pIdx->pTable; + pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); if( !pIdx->zColAff ){ - /* The first time a column affinity string for a particular index is - ** required, it is allocated and populated here. It is then stored as - ** a member of the Index structure for subsequent use. - ** - ** The column affinity string will eventually be deleted by - ** sqliteDeleteIndex() when the Index structure itself is cleaned - ** up. - */ - int n; - Table *pTab = pIdx->pTable; - pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); - if( !pIdx->zColAff ){ - sqlite3OomFault(db); - return 0; - } - for(n=0; nnColumn; n++){ - i16 x = pIdx->aiColumn[n]; - char aff; - if( x>=0 ){ - aff = pTab->aCol[x].affinity; - }else if( x==XN_ROWID ){ - aff = SQLITE_AFF_INTEGER; - }else{ - assert( x==XN_EXPR ); - assert( pIdx->bHasExpr ); - assert( pIdx->aColExpr!=0 ); - aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr); - } - if( affSQLITE_AFF_NUMERIC) aff = SQLITE_AFF_NUMERIC; - pIdx->zColAff[n] = aff; + sqlite3OomFault(db); + return 0; + } + for(n=0; nnColumn; n++){ + i16 x = pIdx->aiColumn[n]; + char aff; + if( x>=0 ){ + aff = pTab->aCol[x].affinity; + }else if( x==XN_ROWID ){ + aff = SQLITE_AFF_INTEGER; + }else{ + assert( x==XN_EXPR ); + assert( pIdx->bHasExpr ); + assert( pIdx->aColExpr!=0 ); + aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr); } - pIdx->zColAff[n] = 0; + if( affSQLITE_AFF_NUMERIC) aff = SQLITE_AFF_NUMERIC; + pIdx->zColAff[n] = aff; } - + pIdx->zColAff[n] = 0; return pIdx->zColAff; } +const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ + if( !pIdx->zColAff ) return computeIndexAffStr(db, pIdx); + return pIdx->zColAff; +} + /* ** Compute an affinity string for a table. Space is obtained @@ -794,7 +796,7 @@ void sqlite3Insert( /* Cannot insert into a read-only table. */ - if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ goto insert_cleanup; } @@ -1241,7 +1243,7 @@ void sqlite3Insert( } /* Copy the new data already generated. */ - assert( pTab->nNVCol>0 ); + assert( pTab->nNVCol>0 || pParse->nErr>0 ); sqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1); #ifndef SQLITE_OMIT_GENERATED_COLUMNS diff --git a/src/json.c b/src/json.c index 67448421b..8735634c8 100644 --- a/src/json.c +++ b/src/json.c @@ -104,6 +104,7 @@ static const char * const jsonType[] = { #define JNODE_PATCH 0x10 /* Patch with JsonNode.u.pPatch */ #define JNODE_APPEND 0x20 /* More ARRAY/OBJECT entries at u.iAppend */ #define JNODE_LABEL 0x40 /* Is a label of an object */ +#define JNODE_JSON5 0x80 /* Node contains JSON5 enhancements */ /* A single node of parsed JSON @@ -130,10 +131,12 @@ struct JsonParse { JsonNode *aNode; /* Array of nodes containing the parse */ const char *zJson; /* Original JSON string */ u32 *aUp; /* Index of parent of each node */ - u8 oom; /* Set to true if out of memory */ - u8 nErr; /* Number of errors seen */ u16 iDepth; /* Nesting depth */ + u8 nErr; /* Number of errors seen */ + u8 oom; /* Set to true if out of memory */ + u8 hasNonstd; /* True if input uses non-standard features like JSON5 */ int nJson; /* Length of the zJson string in bytes */ + u32 iErr; /* Error location in zJson[] */ u32 iHold; /* Replace cache line with the lowest iHold value */ }; @@ -141,10 +144,10 @@ struct JsonParse { ** Maximum nesting depth of JSON for this implementation. ** ** This limit is needed to avoid a stack overflow in the recursive -** descent parser. A depth of 2000 is far deeper than any sane JSON -** should go. +** descent parser. A depth of 1000 is far deeper than any sane JSON +** should go. Historical note: This limit was 2000 prior to version 3.42.0 */ -#define JSON_MAX_DEPTH 2000 +#define JSON_MAX_DEPTH 1000 /************************************************************************** ** Utility routines for dealing with JsonString objects @@ -294,6 +297,129 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ assert( p->nUsednAlloc ); } +/* +** The zIn[0..N] string is a JSON5 string literal. Append to p a translation +** of the string literal that standard JSON and that omits all JSON5 +** features. +*/ +static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ + u32 i; + jsonAppendChar(p, '"'); + zIn++; + N -= 2; + while( N>0 ){ + for(i=0; i0 ){ + jsonAppendRaw(p, zIn, i); + zIn += i; + N -= i; + if( N==0 ) break; + } + assert( zIn[0]=='\\' ); + switch( (u8)zIn[1] ){ + case '\'': + jsonAppendChar(p, '\''); + break; + case 'v': + jsonAppendRaw(p, "\\u0009", 6); + break; + case 'x': + jsonAppendRaw(p, "\\u00", 4); + jsonAppendRaw(p, &zIn[2], 2); + zIn += 2; + N -= 2; + break; + case '0': + jsonAppendRaw(p, "\\u0000", 6); + break; + case '\r': + if( zIn[2]=='\n' ){ + zIn++; + N--; + } + break; + case '\n': + break; + case 0xe2: + assert( N>=4 ); + assert( 0x80==(u8)zIn[2] ); + assert( 0xa8==(u8)zIn[3] || 0xa9==(u8)zIn[3] ); + zIn += 2; + N -= 2; + break; + default: + jsonAppendRaw(p, zIn, 2); + break; + } + zIn += 2; + N -= 2; + } + jsonAppendChar(p, '"'); +} + +/* +** The zIn[0..N] string is a JSON5 integer literal. Append to p a translation +** of the string literal that standard JSON and that omits all JSON5 +** features. +*/ +static void jsonAppendNormalizedInt(JsonString *p, const char *zIn, u32 N){ + if( zIn[0]=='+' ){ + zIn++; + N--; + }else if( zIn[0]=='-' ){ + jsonAppendChar(p, '-'); + zIn++; + N--; + } + if( zIn[0]=='0' && (zIn[1]=='x' || zIn[1]=='X') ){ + sqlite3_int64 i = 0; + int rc = sqlite3DecOrHexToI64(zIn, &i); + if( rc<=1 ){ + jsonPrintf(100,p,"%lld",i); + }else{ + assert( rc==2 ); + jsonAppendRaw(p, "9.0e999", 7); + } + return; + } + jsonAppendRaw(p, zIn, N); +} + +/* +** The zIn[0..N] string is a JSON5 real literal. Append to p a translation +** of the string literal that standard JSON and that omits all JSON5 +** features. +*/ +static void jsonAppendNormalizedReal(JsonString *p, const char *zIn, u32 N){ + u32 i; + if( zIn[0]=='+' ){ + zIn++; + N--; + }else if( zIn[0]=='-' ){ + jsonAppendChar(p, '-'); + zIn++; + N--; + } + if( zIn[0]=='.' ){ + jsonAppendChar(p, '0'); + } + for(i=0; i0 ){ + jsonAppendRaw(p, zIn, N); + } +} + + + /* ** Append a function parameter value to the JSON string under ** construction. @@ -307,8 +433,11 @@ static void jsonAppendValue( jsonAppendRaw(p, "null", 4); break; } - case SQLITE_INTEGER: case SQLITE_FLOAT: { + jsonPrintf(100, p, "%!0.15g", sqlite3_value_double(pValue)); + break; + } + case SQLITE_INTEGER: { const char *z = (const char*)sqlite3_value_text(pValue); u32 n = (u32)sqlite3_value_bytes(pValue); jsonAppendRaw(p, z, n); @@ -421,17 +550,38 @@ static void jsonRenderNode( break; } case JSON_STRING: { + assert( pNode->eU==1 ); if( pNode->jnFlags & JNODE_RAW ){ - assert( pNode->eU==1 ); - jsonAppendString(pOut, pNode->u.zJContent, pNode->n); - break; + if( pNode->jnFlags & JNODE_LABEL ){ + jsonAppendChar(pOut, '"'); + jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + jsonAppendChar(pOut, '"'); + }else{ + jsonAppendString(pOut, pNode->u.zJContent, pNode->n); + } + }else if( pNode->jnFlags & JNODE_JSON5 ){ + jsonAppendNormalizedString(pOut, pNode->u.zJContent, pNode->n); + }else{ + jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); } - /* no break */ deliberate_fall_through + break; + } + case JSON_REAL: { + assert( pNode->eU==1 ); + if( pNode->jnFlags & JNODE_JSON5 ){ + jsonAppendNormalizedReal(pOut, pNode->u.zJContent, pNode->n); + }else{ + jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + } + break; } - case JSON_REAL: case JSON_INT: { assert( pNode->eU==1 ); - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + if( pNode->jnFlags & JNODE_JSON5 ){ + jsonAppendNormalizedInt(pOut, pNode->u.zJContent, pNode->n); + }else{ + jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + } break; } case JSON_ARRAY: { @@ -547,59 +697,41 @@ static void jsonReturn( } case JSON_INT: { sqlite3_int64 i = 0; + int rc; + int bNeg = 0; const char *z; + + assert( pNode->eU==1 ); z = pNode->u.zJContent; - if( z[0]=='-' ){ z++; } - while( z[0]>='0' && z[0]<='9' ){ - unsigned v = *(z++) - '0'; - if( i>=LARGEST_INT64/10 ){ - if( i>LARGEST_INT64/10 ) goto int_as_real; - if( z[0]>='0' && z[0]<='9' ) goto int_as_real; - if( v==9 ) goto int_as_real; - if( v==8 ){ - if( pNode->u.zJContent[0]=='-' ){ - sqlite3_result_int64(pCtx, SMALLEST_INT64); - goto int_done; - }else{ - goto int_as_real; - } - } - } - i = i*10 + v; + if( z[0]=='-' ){ z++; bNeg = 1; } + else if( z[0]=='+' ){ z++; } + rc = sqlite3DecOrHexToI64(z, &i); + if( rc<=1 ){ + sqlite3_result_int64(pCtx, bNeg ? -i : i); + }else if( rc==3 && bNeg ){ + sqlite3_result_int64(pCtx, SMALLEST_INT64); + }else{ + goto to_double; } - if( pNode->u.zJContent[0]=='-' ){ i = -i; } - sqlite3_result_int64(pCtx, i); - int_done: break; - int_as_real: ; /* no break */ deliberate_fall_through } case JSON_REAL: { double r; -#ifdef SQLITE_AMALGAMATION const char *z; assert( pNode->eU==1 ); + to_double: z = pNode->u.zJContent; sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); -#else - assert( pNode->eU==1 ); - r = strtod(pNode->u.zJContent, 0); -#endif sqlite3_result_double(pCtx, r); break; } case JSON_STRING: { -#if 0 /* Never happens because JNODE_RAW is only set by json_set(), - ** json_insert() and json_replace() and those routines do not - ** call jsonReturn() */ if( pNode->jnFlags & JNODE_RAW ){ assert( pNode->eU==1 ); sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n, SQLITE_TRANSIENT); - }else -#endif - assert( (pNode->jnFlags & JNODE_RAW)==0 ); - if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ + }else if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ /* JSON formatted without any backslash-escapes */ assert( pNode->eU==1 ); sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2, @@ -611,18 +743,17 @@ static void jsonReturn( const char *z; char *zOut; u32 j; + u32 nOut = n; assert( pNode->eU==1 ); z = pNode->u.zJContent; - zOut = sqlite3_malloc( n+1 ); + zOut = sqlite3_malloc( nOut+1 ); if( zOut==0 ){ sqlite3_result_error_nomem(pCtx); break; } for(i=1, j=0; iaNode[pParse->nNode]; - p->eType = (u8)eType; - p->jnFlags = 0; + p->eType = (u8)(eType & 0xff); + p->jnFlags = (u8)(eType >> 8); VVA( p->eU = zContent ? 1 : 0 ); p->n = n; p->u.zJContent = zContent; return pParse->nNode++; } +/* +** Return true if z[] begins with 2 (or more) hexadecimal digits +*/ +static int jsonIs2Hex(const char *z){ + return sqlite3Isxdigit(z[0]) && sqlite3Isxdigit(z[1]); +} + /* ** Return true if z[] begins with 4 (or more) hexadecimal digits */ static int jsonIs4Hex(const char *z){ - int i; - for(i=0; i<4; i++) if( !sqlite3Isxdigit(z[i]) ) return 0; - return 1; + return jsonIs2Hex(z) && jsonIs2Hex(&z[2]); } +/* +** Return the number of bytes of JSON5 whitespace at the beginning of +** the input string z[]. +** +** JSON5 whitespace consists of any of the following characters: +** +** Unicode UTF-8 Name +** U+0009 09 horizontal tab +** U+000a 0a line feed +** U+000b 0b vertical tab +** U+000c 0c form feed +** U+000d 0d carriage return +** U+0020 20 space +** U+00a0 c2 a0 non-breaking space +** U+1680 e1 9a 80 ogham space mark +** U+2000 e2 80 80 en quad +** U+2001 e2 80 81 em quad +** U+2002 e2 80 82 en space +** U+2003 e2 80 83 em space +** U+2004 e2 80 84 three-per-em space +** U+2005 e2 80 85 four-per-em space +** U+2006 e2 80 86 six-per-em space +** U+2007 e2 80 87 figure space +** U+2008 e2 80 88 punctuation space +** U+2009 e2 80 89 thin space +** U+200a e2 80 8a hair space +** U+2028 e2 80 a8 line separator +** U+2029 e2 80 a9 paragraph separator +** U+202f e2 80 af narrow no-break space (NNBSP) +** U+205f e2 81 9f medium mathematical space (MMSP) +** U+3000 e3 80 80 ideographical space +** U+FEFF ef bb bf byte order mark +** +** In addition, comments between '/', '*' and '*', '/' and +** from '/', '/' to end-of-line are also considered to be whitespace. +*/ +static int json5Whitespace(const char *zIn){ + int n = 0; + const u8 *z = (u8*)zIn; + while( 1 /*exit by "goto whitespace_done"*/ ){ + switch( z[n] ){ + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x20: { + n++; + break; + } + case '/': { + if( z[n+1]=='*' && z[n+2]!=0 ){ + int j; + for(j=n+3; z[j]!='/' || z[j-1]!='*'; j++){ + if( z[j]==0 ) goto whitespace_done; + } + n = j+1; + break; + }else if( z[n+1]=='/' ){ + int j; + char c; + for(j=n+2; (c = z[j])!=0; j++){ + if( c=='\n' || c=='\r' ) break; + if( 0xe2==(u8)c && 0x80==(u8)z[j+1] + && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2]) + ){ + j += 2; + break; + } + } + n = j; + if( z[n] ) n++; + break; + } + goto whitespace_done; + } + case 0xc2: { + if( z[n+1]==0xa0 ){ + n += 2; + break; + } + goto whitespace_done; + } + case 0xe1: { + if( z[n+1]==0x9a && z[n+2]==0x80 ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xe2: { + if( z[n+1]==0x80 ){ + u8 c = z[n+2]; + if( c<0x80 ) goto whitespace_done; + if( c<=0x8a || c==0xa8 || c==0xa9 || c==0xaf ){ + n += 3; + break; + } + }else if( z[n+1]==0x81 && z[n+2]==0x9f ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xe3: { + if( z[n+1]==0x80 && z[n+2]==0x80 ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xef: { + if( z[n+1]==0xbb && z[n+2]==0xbf ){ + n += 3; + break; + } + goto whitespace_done; + } + default: { + goto whitespace_done; + } + } + } + whitespace_done: + return n; +} + +/* +** Extra floating-point literals to allow in JSON. +*/ +static const struct NanInfName { + char c1; + char c2; + char n; + char eType; + char nRepl; + char *zMatch; + char *zRepl; +} aNanInfName[] = { + { 'i', 'I', 3, JSON_REAL, 7, "inf", "9.0e999" }, + { 'i', 'I', 8, JSON_REAL, 7, "infinity", "9.0e999" }, + { 'n', 'N', 3, JSON_NULL, 4, "NaN", "null" }, + { 'q', 'Q', 4, JSON_NULL, 4, "QNaN", "null" }, + { 's', 'S', 4, JSON_NULL, 4, "SNaN", "null" }, +}; + /* ** Parse a single JSON value which begins at pParse->zJson[i]. Return the ** index of the first character past the end of the value parsed. ** -** Return negative for a syntax error. Special cases: return -2 if the -** first non-whitespace character is '}' and return -3 if the first -** non-whitespace character is ']'. +** Special return values: +** +** 0 End if input +** -1 Syntax error +** -2 '}' seen +** -3 ']' seen +** -4 ',' seen +** -5 ':' seen */ static int jsonParseValue(JsonParse *pParse, u32 i){ char c; @@ -769,151 +1074,430 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ int x; JsonNode *pNode; const char *z = pParse->zJson; - while( fast_isspace(z[i]) ){ i++; } - if( (c = z[i])=='{' ){ +json_parse_restart: + switch( (u8)z[i] ){ + case '{': { /* Parse object */ iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); if( iThis<0 ) return -1; + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + pParse->iErr = i; + return -1; + } for(j=i+1;;j++){ - while( fast_isspace(z[j]) ){ j++; } - if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; + u32 nNode = pParse->nNode; x = jsonParseValue(pParse, j); - if( x<0 ){ - pParse->iDepth--; - if( x==(-2) && pParse->nNode==(u32)iThis+1 ) return j+1; - return -1; + if( x<=0 ){ + if( x==(-2) ){ + j = pParse->iErr; + if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1; + break; + } + j += json5Whitespace(&z[j]); + if( sqlite3JsonId1(z[j]) + || (z[j]=='\\' && z[j+1]=='u' && jsonIs4Hex(&z[j+2])) + ){ + int k = j+1; + while( (sqlite3JsonId2(z[k]) && json5Whitespace(&z[k])==0) + || (z[k]=='\\' && z[k+1]=='u' && jsonIs4Hex(&z[k+2])) + ){ + k++; + } + jsonParseAddNode(pParse, JSON_STRING | (JNODE_RAW<<8), k-j, &z[j]); + pParse->hasNonstd = 1; + x = k; + }else{ + if( x!=-1 ) pParse->iErr = j; + return -1; + } } if( pParse->oom ) return -1; - pNode = &pParse->aNode[pParse->nNode-1]; - if( pNode->eType!=JSON_STRING ) return -1; + pNode = &pParse->aNode[nNode]; + if( pNode->eType!=JSON_STRING ){ + pParse->iErr = j; + return -1; + } pNode->jnFlags |= JNODE_LABEL; j = x; - while( fast_isspace(z[j]) ){ j++; } - if( z[j]!=':' ) return -1; - j++; + if( z[j]==':' ){ + j++; + }else{ + if( fast_isspace(z[j]) ){ + do{ j++; }while( fast_isspace(z[j]) ); + if( z[j]==':' ){ + j++; + goto parse_object_value; + } + } + x = jsonParseValue(pParse, j); + if( x!=(-5) ){ + if( x!=(-1) ) pParse->iErr = j; + return -1; + } + j = pParse->iErr+1; + } + parse_object_value: x = jsonParseValue(pParse, j); - pParse->iDepth--; - if( x<0 ) return -1; + if( x<=0 ){ + if( x!=(-1) ) pParse->iErr = j; + return -1; + } j = x; - while( fast_isspace(z[j]) ){ j++; } - c = z[j]; - if( c==',' ) continue; - if( c!='}' ) return -1; - break; + if( z[j]==',' ){ + continue; + }else if( z[j]=='}' ){ + break; + }else{ + if( fast_isspace(z[j]) ){ + do{ j++; }while( fast_isspace(z[j]) ); + if( z[j]==',' ){ + continue; + }else if( z[j]=='}' ){ + break; + } + } + x = jsonParseValue(pParse, j); + if( x==(-4) ){ + j = pParse->iErr; + continue; + } + if( x==(-2) ){ + j = pParse->iErr; + break; + } + } + pParse->iErr = j; + return -1; } pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + pParse->iDepth--; return j+1; - }else if( c=='[' ){ + } + case '[': { /* Parse array */ iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); if( iThis<0 ) return -1; + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + pParse->iErr = i; + return -1; + } memset(&pParse->aNode[iThis].u, 0, sizeof(pParse->aNode[iThis].u)); for(j=i+1;;j++){ - while( fast_isspace(z[j]) ){ j++; } - if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; x = jsonParseValue(pParse, j); - pParse->iDepth--; - if( x<0 ){ - if( x==(-3) && pParse->nNode==(u32)iThis+1 ) return j+1; + if( x<=0 ){ + if( x==(-3) ){ + j = pParse->iErr; + if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1; + break; + } + if( x!=(-1) ) pParse->iErr = j; return -1; } j = x; - while( fast_isspace(z[j]) ){ j++; } - c = z[j]; - if( c==',' ) continue; - if( c!=']' ) return -1; - break; + if( z[j]==',' ){ + continue; + }else if( z[j]==']' ){ + break; + }else{ + if( fast_isspace(z[j]) ){ + do{ j++; }while( fast_isspace(z[j]) ); + if( z[j]==',' ){ + continue; + }else if( z[j]==']' ){ + break; + } + } + x = jsonParseValue(pParse, j); + if( x==(-4) ){ + j = pParse->iErr; + continue; + } + if( x==(-3) ){ + j = pParse->iErr; + break; + } + } + pParse->iErr = j; + return -1; } pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + pParse->iDepth--; return j+1; - }else if( c=='"' ){ + } + case '\'': { + u8 jnFlags; + char cDelim; + pParse->hasNonstd = 1; + jnFlags = JNODE_JSON5; + goto parse_string; + case '"': /* Parse string */ - u8 jnFlags = 0; + jnFlags = 0; + parse_string: + cDelim = z[i]; j = i+1; for(;;){ c = z[j]; if( (c & ~0x1f)==0 ){ /* Control characters are not allowed in strings */ + pParse->iErr = j; return -1; } if( c=='\\' ){ c = z[++j]; if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' || c=='n' || c=='r' || c=='t' - || (c=='u' && jsonIs4Hex(z+j+1)) ){ - jnFlags = JNODE_ESCAPE; + || (c=='u' && jsonIs4Hex(&z[j+1])) ){ + jnFlags |= JNODE_ESCAPE; + }else if( c=='\'' || c=='0' || c=='v' || c=='\n' + || (0xe2==(u8)c && 0x80==(u8)z[j+1] + && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2])) + || (c=='x' && jsonIs2Hex(&z[j+1])) ){ + jnFlags |= (JNODE_ESCAPE|JNODE_JSON5); + pParse->hasNonstd = 1; + }else if( c=='\r' ){ + if( z[j+1]=='\n' ) j++; + jnFlags |= (JNODE_ESCAPE|JNODE_JSON5); + pParse->hasNonstd = 1; }else{ + pParse->iErr = j; return -1; } - }else if( c=='"' ){ + }else if( c==cDelim ){ break; } j++; } - jsonParseAddNode(pParse, JSON_STRING, j+1-i, &z[i]); - if( !pParse->oom ) pParse->aNode[pParse->nNode-1].jnFlags = jnFlags; + jsonParseAddNode(pParse, JSON_STRING | (jnFlags<<8), j+1-i, &z[i]); return j+1; - }else if( c=='n' - && strncmp(z+i,"null",4)==0 - && !sqlite3Isalnum(z[i+4]) ){ - jsonParseAddNode(pParse, JSON_NULL, 0, 0); - return i+4; - }else if( c=='t' - && strncmp(z+i,"true",4)==0 - && !sqlite3Isalnum(z[i+4]) ){ - jsonParseAddNode(pParse, JSON_TRUE, 0, 0); - return i+4; - }else if( c=='f' - && strncmp(z+i,"false",5)==0 - && !sqlite3Isalnum(z[i+5]) ){ - jsonParseAddNode(pParse, JSON_FALSE, 0, 0); - return i+5; - }else if( c=='-' || (c>='0' && c<='9') ){ + } + case 't': { + if( strncmp(z+i,"true",4)==0 && !sqlite3Isalnum(z[i+4]) ){ + jsonParseAddNode(pParse, JSON_TRUE, 0, 0); + return i+4; + } + pParse->iErr = i; + return -1; + } + case 'f': { + if( strncmp(z+i,"false",5)==0 && !sqlite3Isalnum(z[i+5]) ){ + jsonParseAddNode(pParse, JSON_FALSE, 0, 0); + return i+5; + } + pParse->iErr = i; + return -1; + } + case '+': { + u8 seenDP, seenE, jnFlags; + pParse->hasNonstd = 1; + jnFlags = JNODE_JSON5; + goto parse_number; + case '.': + if( sqlite3Isdigit(z[i+1]) ){ + pParse->hasNonstd = 1; + jnFlags = JNODE_JSON5; + seenE = 0; + seenDP = JSON_REAL; + goto parse_number_2; + } + pParse->iErr = i; + return -1; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': /* Parse number */ - u8 seenDP = 0; - u8 seenE = 0; + jnFlags = 0; + parse_number: + seenDP = JSON_INT; + seenE = 0; assert( '-' < '0' ); + assert( '+' < '0' ); + assert( '.' < '0' ); + c = z[i]; + if( c<='0' ){ - j = c=='-' ? i+1 : i; - if( z[j]=='0' && z[j+1]>='0' && z[j+1]<='9' ) return -1; + if( c=='0' ){ + if( (z[i+1]=='x' || z[i+1]=='X') && sqlite3Isxdigit(z[i+2]) ){ + assert( seenDP==JSON_INT ); + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + for(j=i+3; sqlite3Isxdigit(z[j]); j++){} + goto parse_number_finish; + }else if( sqlite3Isdigit(z[i+1]) ){ + pParse->iErr = i+1; + return -1; + } + }else{ + if( !sqlite3Isdigit(z[i+1]) ){ + /* JSON5 allows for "+Infinity" and "-Infinity" using exactly + ** that case. SQLite also allows these in any case and it allows + ** "+inf" and "-inf". */ + if( (z[i+1]=='I' || z[i+1]=='i') + && sqlite3StrNICmp(&z[i+1], "inf",3)==0 + ){ + pParse->hasNonstd = 1; + if( z[i]=='-' ){ + jsonParseAddNode(pParse, JSON_REAL, 8, "-9.0e999"); + }else{ + jsonParseAddNode(pParse, JSON_REAL, 7, "9.0e999"); + } + return i + (sqlite3StrNICmp(&z[i+4],"inity",5)==0 ? 9 : 4); + } + if( z[i+1]=='.' ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + goto parse_number_2; + } + pParse->iErr = i; + return -1; + } + if( z[i+1]=='0' ){ + if( sqlite3Isdigit(z[i+2]) ){ + pParse->iErr = i+1; + return -1; + }else if( (z[i+2]=='x' || z[i+2]=='X') && sqlite3Isxdigit(z[i+3]) ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + for(j=i+4; sqlite3Isxdigit(z[j]); j++){} + goto parse_number_finish; + } + } + } } - j = i+1; - for(;; j++){ + parse_number_2: + for(j=i+1;; j++){ c = z[j]; - if( c>='0' && c<='9' ) continue; + if( sqlite3Isdigit(c) ) continue; if( c=='.' ){ - if( z[j-1]=='-' ) return -1; - if( seenDP ) return -1; - seenDP = 1; + if( seenDP==JSON_REAL ){ + pParse->iErr = j; + return -1; + } + seenDP = JSON_REAL; continue; } if( c=='e' || c=='E' ){ - if( z[j-1]<'0' ) return -1; - if( seenE ) return -1; - seenDP = seenE = 1; + if( z[j-1]<'0' ){ + if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + }else{ + pParse->iErr = j; + return -1; + } + } + if( seenE ){ + pParse->iErr = j; + return -1; + } + seenDP = JSON_REAL; + seenE = 1; c = z[j+1]; if( c=='+' || c=='-' ){ j++; c = z[j+1]; } - if( c<'0' || c>'9' ) return -1; + if( c<'0' || c>'9' ){ + pParse->iErr = j; + return -1; + } continue; } break; } - if( z[j-1]<'0' ) return -1; - jsonParseAddNode(pParse, seenDP ? JSON_REAL : JSON_INT, - j - i, &z[i]); + if( z[j-1]<'0' ){ + if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + }else{ + pParse->iErr = j; + return -1; + } + } + parse_number_finish: + jsonParseAddNode(pParse, seenDP | (jnFlags<<8), j - i, &z[i]); return j; - }else if( c=='}' ){ + } + case '}': { + pParse->iErr = i; return -2; /* End of {...} */ - }else if( c==']' ){ + } + case ']': { + pParse->iErr = i; return -3; /* End of [...] */ - }else if( c==0 ){ + } + case ',': { + pParse->iErr = i; + return -4; /* List separator */ + } + case ':': { + pParse->iErr = i; + return -5; /* Object label/value separator */ + } + case 0: { return 0; /* End of file */ - }else{ + } + case 0x09: + case 0x0a: + case 0x0d: + case 0x20: { + do{ + i++; + }while( fast_isspace(z[i]) ); + goto json_parse_restart; + } + case 0x0b: + case 0x0c: + case '/': + case 0xc2: + case 0xe1: + case 0xe2: + case 0xe3: + case 0xef: { + j = json5Whitespace(&z[i]); + if( j>0 ){ + i += j; + pParse->hasNonstd = 1; + goto json_parse_restart; + } + pParse->iErr = i; + return -1; + } + case 'n': { + if( strncmp(z+i,"null",4)==0 && !sqlite3Isalnum(z[i+4]) ){ + jsonParseAddNode(pParse, JSON_NULL, 0, 0); + return i+4; + } + /* fall-through into the default case that checks for NaN */ + } + default: { + u32 k; + int nn; + c = z[i]; + for(k=0; khasNonstd = 1; + return i + nn; + } + pParse->iErr = i; return -1; /* Syntax error */ } + } /* End switch(z[i]) */ } /* @@ -937,7 +1521,14 @@ static int jsonParse( if( i>0 ){ assert( pParse->iDepth==0 ); while( fast_isspace(zJson[i]) ) i++; - if( zJson[i] ) i = -1; + if( zJson[i] ){ + i += json5Whitespace(&zJson[i]); + if( zJson[i] ){ + jsonParseReset(pParse); + return 1; + } + pParse->hasNonstd = 1; + } } if( i<=0 ){ if( pCtx!=0 ){ @@ -1008,6 +1599,15 @@ static int jsonParseFindParents(JsonParse *pParse){ ** is no longer valid, parse the JSON again and return the new parse, ** and also register the new parse so that it will be available for ** future sqlite3_get_auxdata() calls. +** +** If an error occurs and pErrCtx!=0 then report the error on pErrCtx +** and return NULL. +** +** If an error occurs and pErrCtx==0 then return the Parse object with +** JsonParse.nErr non-zero. If the caller invokes this routine with +** pErrCtx==0 and it gets back a JsonParse with nErr!=0, then the caller +** is responsible for invoking jsonParseFree() on the returned value. +** But the caller may invoke jsonParseFree() *only* if pParse->nErr!=0. */ static JsonParse *jsonParseCached( sqlite3_context *pCtx, @@ -1057,6 +1657,10 @@ static JsonParse *jsonParseCached( p->zJson = (char*)&p[1]; memcpy((char*)p->zJson, zJson, nJson+1); if( jsonParse(p, pErrCtx, p->zJson) ){ + if( pErrCtx==0 ){ + p->nErr = 1; + return p; + } sqlite3_free(p); return 0; } @@ -1071,7 +1675,7 @@ static JsonParse *jsonParseCached( ** Compare the OBJECT label at pNode against zKey,nKey. Return true on ** a match. */ -static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){ +static int jsonLabelCompare(const JsonNode *pNode, const char *zKey, u32 nKey){ assert( pNode->eU==1 ); if( pNode->jnFlags & JNODE_RAW ){ if( pNode->n!=nKey ) return 0; @@ -1081,6 +1685,15 @@ static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){ return strncmp(pNode->u.zJContent+1, zKey, nKey)==0; } } +static int jsonSameLabel(const JsonNode *p1, const JsonNode *p2){ + if( p1->jnFlags & JNODE_RAW ){ + return jsonLabelCompare(p2, p1->u.zJContent, p1->n); + }else if( p2->jnFlags & JNODE_RAW ){ + return jsonLabelCompare(p1, p2->u.zJContent, p2->n); + }else{ + return p1->n==p2->n && strncmp(p1->u.zJContent,p2->u.zJContent,p1->n)==0; + } +} /* forward declaration */ static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**); @@ -1551,7 +2164,7 @@ static void jsonExtractFunc( zPath = (const char*)sqlite3_value_text(argv[1]); if( zPath==0 ) return; if( flags & JSON_ABPATH ){ - if( zPath[0]!='$' ){ + if( zPath[0]!='$' || (zPath[1]!='.' && zPath[1]!='[' && zPath[1]!=0) ){ /* The -> and ->> operators accept abbreviated PATH arguments. This ** is mostly for compatibility with PostgreSQL, but also for ** convenience. @@ -1642,12 +2255,10 @@ static JsonNode *jsonMergePatch( assert( pPatch[i].eU==1 ); nKey = pPatch[i].n; zKey = pPatch[i].u.zJContent; - assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); for(j=1; jn; j += jsonNodeSize(&pTarget[j+1])+1 ){ assert( pTarget[j].eType==JSON_STRING ); assert( pTarget[j].jnFlags & JNODE_LABEL ); - assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); - if( pTarget[j].n==nKey && strncmp(pTarget[j].u.zJContent,zKey,nKey)==0 ){ + if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){ if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH) ) break; if( pPatch[i+1].eType==JSON_NULL ){ pTarget[j+1].jnFlags |= JNODE_REMOVE; @@ -1934,8 +2545,8 @@ static void jsonTypeFunc( /* ** json_valid(JSON) ** -** Return 1 if JSON is a well-formed JSON string according to RFC-7159. -** Return 0 otherwise. +** Return 1 if JSON is a well-formed canonical JSON string according +** to RFC-7159. Return 0 otherwise. */ static void jsonValidFunc( sqlite3_context *ctx, @@ -1944,8 +2555,69 @@ static void jsonValidFunc( ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; p = jsonParseCached(ctx, argv, 0); - sqlite3_result_int(ctx, p!=0); + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(ctx); + sqlite3_free(p); + }else{ + sqlite3_result_int(ctx, p->nErr==0 && p->hasNonstd==0); + if( p->nErr ) jsonParseFree(p); + } +} + +/* +** json_error_position(JSON) +** +** If the argument is not an interpretable JSON string, then return the 1-based +** character position at which the parser first recognized that the input +** was in error. The left-most character is 1. If the string is valid +** JSON, then return 0. +** +** Note that json_valid() is only true for strictly conforming canonical JSON. +** But this routine returns zero if the input contains extension. Thus: +** +** (1) If the input X is strictly conforming canonical JSON: +** +** json_valid(X) returns true +** json_error_position(X) returns 0 +** +** (2) If the input X is JSON but it includes extension (such as JSON5) that +** are not part of RFC-8259: +** +** json_valid(X) returns false +** json_error_position(X) return 0 +** +** (3) If the input X cannot be interpreted as JSON even taking extensions +** into account: +** +** json_valid(X) return false +** json_error_position(X) returns 1 or more +*/ +static void jsonErrorFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *p; /* The parse */ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + p = jsonParseCached(ctx, argv, 0); + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(ctx); + sqlite3_free(p); + }else if( p->nErr==0 ){ + sqlite3_result_int(ctx, 0); + }else{ + int n = 1; + u32 i; + const char *z = p->zJson; + for(i=0; iiErr && ALWAYS(z[i]); i++){ + if( (z[i]&0xc0)!=0x80 ) n++; + } + sqlite3_result_int(ctx, n); + jsonParseFree(p); + } } @@ -2289,14 +2961,16 @@ static void jsonAppendObjectPathElement( assert( pNode->eU==1 ); z = pNode->u.zJContent; nn = pNode->n; - assert( nn>=2 ); - assert( z[0]=='"' ); - assert( z[nn-1]=='"' ); - if( nn>2 && sqlite3Isalpha(z[1]) ){ - for(jj=2; jjjnFlags & JNODE_RAW)==0 ){ + assert( nn>=2 ); + assert( z[0]=='"' || z[0]=='\'' ); + assert( z[nn-1]=='"' || z[0]=='\'' ); + if( nn>2 && sqlite3Isalpha(z[1]) ){ + for(jj=2; jj, 2, JSON_JSON, jsonExtractFunc), JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc), diff --git a/src/loadext.c b/src/loadext.c index 40bde9ce8..a00df6536 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -585,7 +585,11 @@ static int sqlite3LoadExtension( /* tag-20210611-1. Some dlopen() implementations will segfault if given ** an oversize filename. Most filesystems have a pathname limit of 4K, ** so limit the extension filename length to about twice that. - ** https://sqlite.org/forum/forumpost/08a0d6d9bf */ + ** https://sqlite.org/forum/forumpost/08a0d6d9bf + ** + ** Later (2023-03-25): Save an extra 6 bytes for the filename suffix. + ** See https://sqlite.org/forum/forumpost/24083b579d. + */ if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found; handle = sqlite3OsDlOpen(pVfs, zFile); @@ -593,7 +597,9 @@ static int sqlite3LoadExtension( for(ii=0; ii63 || (MASKBIT64(op) & mAnytimeConfigOption)==0 ){ + return SQLITE_MISUSE_BKPT; + } + testcase( op==SQLITE_CONFIG_LOG ); + testcase( op==SQLITE_CONFIG_PCACHE_HDRSZ ); + } va_start(ap, op); switch( op ){ @@ -501,6 +513,7 @@ int sqlite3_config(int op, ...){ break; } case SQLITE_CONFIG_MEMSTATUS: { + assert( !sqlite3GlobalConfig.isInit ); /* Cannot change at runtime */ /* EVIDENCE-OF: R-61275-35157 The SQLITE_CONFIG_MEMSTATUS option takes ** single argument of type int, interpreted as a boolean, which enables ** or disables the collection of memory allocation statistics. */ @@ -624,8 +637,10 @@ int sqlite3_config(int op, ...){ ** sqlite3GlobalConfig.xLog = va_arg(ap, void(*)(void*,int,const char*)); */ typedef void(*LOGFUNC_t)(void*,int,const char*); - sqlite3GlobalConfig.xLog = va_arg(ap, LOGFUNC_t); - sqlite3GlobalConfig.pLogArg = va_arg(ap, void*); + LOGFUNC_t xLog = va_arg(ap, LOGFUNC_t); + void *pLogArg = va_arg(ap, void*); + AtomicStore(&sqlite3GlobalConfig.xLog, xLog); + AtomicStore(&sqlite3GlobalConfig.pLogArg, pLogArg); break; } @@ -639,7 +654,8 @@ int sqlite3_config(int op, ...){ ** argument of type int. If non-zero, then URI handling is globally ** enabled. If the parameter is zero, then URI handling is globally ** disabled. */ - sqlite3GlobalConfig.bOpenUri = va_arg(ap, int); + int bOpenUri = va_arg(ap, int); + AtomicStore(&sqlite3GlobalConfig.bOpenUri, bOpenUri); break; } @@ -954,6 +970,8 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ { SQLITE_DBCONFIG_DQS_DML, SQLITE_DqsDML }, { SQLITE_DBCONFIG_LEGACY_FILE_FORMAT, SQLITE_LegacyFileFmt }, { SQLITE_DBCONFIG_TRUSTED_SCHEMA, SQLITE_TrustedSchema }, + { SQLITE_DBCONFIG_STMT_SCANSTATUS, SQLITE_StmtScanStatus }, + { SQLITE_DBCONFIG_REVERSE_SCANORDER, SQLITE_ReverseOrder }, }; unsigned int i; rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ @@ -2939,9 +2957,9 @@ int sqlite3ParseUri( assert( *pzErrMsg==0 ); - if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */ - || sqlite3GlobalConfig.bOpenUri) /* IMP: R-51689-46548 */ - && nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */ + if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */ + || AtomicLoad(&sqlite3GlobalConfig.bOpenUri)) /* IMP: R-51689-46548 */ + && nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */ ){ char *zOpt; int eState; /* Parser state when parsing URI */ @@ -3347,6 +3365,9 @@ static int openDatabase( #endif #if defined(SQLITE_DEFAULT_LEGACY_ALTER_TABLE) | SQLITE_LegacyAlter +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) + | SQLITE_StmtScanStatus #endif ; sqlite3HashInit(&db->aCollSeq); @@ -3912,7 +3933,7 @@ int sqlite3_sleep(int ms){ /* This function works in milliseconds, but the underlying OsSleep() ** API uses microseconds. Hence the 1000's. */ - rc = (sqlite3OsSleep(pVfs, 1000*ms)/1000); + rc = (sqlite3OsSleep(pVfs, ms<0 ? 0 : 1000*ms)/1000); return rc; } diff --git a/src/msvc.h b/src/msvc.h index f17422733..10675e9a7 100644 --- a/src/msvc.h +++ b/src/msvc.h @@ -38,4 +38,8 @@ #define SQLITE_4_BYTE_ALIGNED_MALLOC #endif /* defined(_MSC_VER) && !defined(_WIN64) */ +#if !defined(HAVE_LOG2) && defined(_MSC_VER) && _MSC_VER<1800 +#define HAVE_LOG2 0 +#endif /* !defined(HAVE_LOG2) && defined(_MSC_VER) && _MSC_VER<1800 */ + #endif /* SQLITE_MSVC_H */ diff --git a/src/os_unix.c b/src/os_unix.c index 6e9ee3263..d0ebb86b9 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -72,7 +72,7 @@ #endif /* Use pread() and pwrite() if they are available */ -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__linux__) # define HAVE_PREAD 1 # define HAVE_PWRITE 1 #endif @@ -3322,12 +3322,6 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){ ** Seek to the offset passed as the second argument, then read cnt ** bytes into pBuf. Return the number of bytes actually read. ** -** NB: If you define USE_PREAD or USE_PREAD64, then it might also -** be necessary to define _XOPEN_SOURCE to be 500. This varies from -** one system to another. Since SQLite does not define USE_PREAD -** in any form by default, we will not attempt to define _XOPEN_SOURCE. -** See tickets #2741 and #2681. -** ** To avoid stomping the errno value on a failed read the lastErrno value ** is set before returning. */ diff --git a/src/os_win.c b/src/os_win.c index abecf1b93..b7b689745 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -5197,7 +5197,7 @@ static int winOpen( if( isReadWrite ){ int rc2, isRO = 0; sqlite3BeginBenignMalloc(); - rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } @@ -5214,7 +5214,7 @@ static int winOpen( if( isReadWrite ){ int rc2, isRO = 0; sqlite3BeginBenignMalloc(); - rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } @@ -5234,7 +5234,7 @@ static int winOpen( if( isReadWrite ){ int rc2, isRO = 0; sqlite3BeginBenignMalloc(); - rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } @@ -5457,6 +5457,13 @@ static int winAccess( OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n", zFilename, flags, pResOut)); + if( zFilename==0 ){ + *pResOut = 0; + OSTRACE(("ACCESS name=%s, pResOut=%p, *pResOut=%d, rc=SQLITE_OK\n", + zFilename, pResOut, *pResOut)); + return SQLITE_OK; + } + zConverted = winConvertFromUtf8Filename(zFilename); if( zConverted==0 ){ OSTRACE(("ACCESS name=%s, rc=SQLITE_IOERR_NOMEM\n", zFilename)); diff --git a/src/pager.c b/src/pager.c index 89073b8c7..44384de5c 100644 --- a/src/pager.c +++ b/src/pager.c @@ -2605,6 +2605,8 @@ static int pager_truncate(Pager *pPager, Pgno nPage){ int rc = SQLITE_OK; assert( pPager->eState!=PAGER_ERROR ); assert( pPager->eState!=PAGER_READER ); + PAGERTRACE(("Truncate %d npage %u\n", PAGERID(pPager), nPage)); + if( isOpen(pPager->fd) && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) @@ -5522,6 +5524,10 @@ static int getPageNormal( if( !isOpen(pPager->fd) || pPager->dbSizepPager->mxPgno ){ rc = SQLITE_FULL; + if( pgno<=pPager->dbSize ){ + sqlite3PcacheRelease(pPg); + pPg = 0; + } goto pager_acquire_err; } if( noContent ){ @@ -5686,10 +5692,12 @@ DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ /* ** Release a page reference. ** -** The sqlite3PagerUnref() and sqlite3PagerUnrefNotNull() may only be -** used if we know that the page being released is not the last page. +** The sqlite3PagerUnref() and sqlite3PagerUnrefNotNull() may only be used +** if we know that the page being released is not the last reference to page1. ** The btree layer always holds page1 open until the end, so these first -** to routines can be used to release any page other than BtShared.pPage1. +** two routines can be used to release any page other than BtShared.pPage1. +** The assert() at tag-20230419-2 proves that this constraint is always +** honored. ** ** Use sqlite3PagerUnrefPageOne() to release page1. This latter routine ** checks the total number of outstanding pages and if the number of @@ -5705,7 +5713,7 @@ void sqlite3PagerUnrefNotNull(DbPage *pPg){ sqlite3PcacheRelease(pPg); } /* Do not use this routine to release the last reference to page1 */ - assert( sqlite3PcacheRefCount(pPager->pPCache)>0 ); + assert( sqlite3PcacheRefCount(pPager->pPCache)>0 ); /* tag-20230419-2 */ } void sqlite3PagerUnref(DbPage *pPg){ if( pPg ) sqlite3PagerUnrefNotNull(pPg); @@ -7465,13 +7473,15 @@ int sqlite3PagerWalSupported(Pager *pPager){ */ static int pagerExclusiveLock(Pager *pPager){ int rc; /* Return code */ + u8 eOrigLock; /* Original lock */ - assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK ); + assert( pPager->eLock>=SHARED_LOCK ); + eOrigLock = pPager->eLock; rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); if( rc!=SQLITE_OK ){ /* If the attempt to grab the exclusive lock failed, release the ** pending lock that may have been obtained instead. */ - pagerUnlockDb(pPager, SHARED_LOCK); + pagerUnlockDb(pPager, eOrigLock); } return rc; diff --git a/src/parse.y b/src/parse.y index 760cb114a..541b9ffe5 100644 --- a/src/parse.y +++ b/src/parse.y @@ -296,17 +296,19 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,A,Y);} // %token_class id ID|INDEXED. - // And "ids" is an identifer-or-string. // %token_class ids ID|STRING. +// An identifier or a join-keyword +// +%token_class idj ID|INDEXED|JOIN_KW. + // The name of a column or table can be any of the following: // %type nm {Token} -nm(A) ::= id(A). +nm(A) ::= idj(A). nm(A) ::= STRING(A). -nm(A) ::= JOIN_KW(A). // A typetoken is really zero or more tokens that form a type name such // as can be found after the column name in a CREATE TABLE statement. @@ -713,7 +715,7 @@ seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) on_using(N seltablist(A) ::= stl_prefix(A) LP seltablist(F) RP as(Z) on_using(N). { if( A==0 && Z.n==0 && N.pOn==0 && N.pUsing==0 ){ A = F; - }else if( F->nSrc==1 ){ + }else if( ALWAYS(F!=0) && F->nSrc==1 ){ A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,0,&N); if( A ){ SrcItem *pNew = &A->a[A->nSrc-1]; @@ -1084,8 +1086,7 @@ idlist(A) ::= nm(Y). expr(A) ::= term(A). expr(A) ::= LP expr(X) RP. {A = X;} -expr(A) ::= id(X). {A=tokenExpr(pParse,TK_ID,X); /*A-overwrites-X*/} -expr(A) ::= JOIN_KW(X). {A=tokenExpr(pParse,TK_ID,X); /*A-overwrites-X*/} +expr(A) ::= idj(X). {A=tokenExpr(pParse,TK_ID,X); /*A-overwrites-X*/} expr(A) ::= nm(X) DOT nm(Y). { Expr *temp1 = tokenExpr(pParse,TK_ID,X); Expr *temp2 = tokenExpr(pParse,TK_ID,Y); @@ -1138,19 +1139,19 @@ expr(A) ::= CAST LP expr(E) AS typetoken(T) RP. { %endif SQLITE_OMIT_CAST -expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP. { +expr(A) ::= idj(X) LP distinct(D) exprlist(Y) RP. { A = sqlite3ExprFunction(pParse, Y, &X, D); } -expr(A) ::= id(X) LP STAR RP. { +expr(A) ::= idj(X) LP STAR RP. { A = sqlite3ExprFunction(pParse, 0, &X, 0); } %ifndef SQLITE_OMIT_WINDOWFUNC -expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP filter_over(Z). { +expr(A) ::= idj(X) LP distinct(D) exprlist(Y) RP filter_over(Z). { A = sqlite3ExprFunction(pParse, Y, &X, D); sqlite3WindowAttach(pParse, A, Z); } -expr(A) ::= id(X) LP STAR RP filter_over(Z). { +expr(A) ::= idj(X) LP STAR RP filter_over(Z). { A = sqlite3ExprFunction(pParse, 0, &X, 0); sqlite3WindowAttach(pParse, A, Z); } @@ -1377,7 +1378,7 @@ case_else(A) ::= ELSE expr(X). {A = X;} case_else(A) ::= . {A = 0;} %type case_operand {Expr*} %destructor case_operand {sqlite3ExprDelete(pParse->db, $$);} -case_operand(A) ::= expr(X). {A = X; /*A-overwrites-X*/} +case_operand(A) ::= expr(A). case_operand(A) ::= . {A = 0;} %type exprlist {ExprList*} diff --git a/src/pcache.c b/src/pcache.c index 153628dc9..8a96cbeae 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -70,11 +70,15 @@ struct PCache { PgHdr *pPg; unsigned char *a; int j; - pPg = (PgHdr*)pLower->pExtra; - printf("%3lld: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags); - a = (unsigned char *)pLower->pBuf; - for(j=0; j<12; j++) printf("%02x", a[j]); - printf(" ptr %p\n", pPg); + if( pLower==0 ){ + printf("%3d: NULL\n", i); + }else{ + pPg = (PgHdr*)pLower->pExtra; + printf("%3d: nRef %2lld flgs %02x data ", i, pPg->nRef, pPg->flags); + a = (unsigned char *)pLower->pBuf; + for(j=0; j<12; j++) printf("%02x", a[j]); + printf(" ptr %p\n", pPg); + } } static void pcacheDump(PCache *pCache){ int N; @@ -87,9 +91,8 @@ struct PCache { if( N>sqlite3PcacheMxDump ) N = sqlite3PcacheMxDump; for(i=1; i<=N; i++){ pLower = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0); - if( pLower==0 ) continue; pcachePageTrace(i, pLower); - if( ((PgHdr*)pLower)->pPage==0 ){ + if( pLower && ((PgHdr*)pLower)->pPage==0 ){ sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0); } } diff --git a/src/pragma.c b/src/pragma.c index 01c2d486e..68a153185 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1524,7 +1524,7 @@ void sqlite3Pragma( zDb = db->aDb[iDb].zDbSName; sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); - if( pTab->nCol+regRow>pParse->nMem ) pParse->nMem = pTab->nCol + regRow; + sqlite3TouchRegister(pParse, pTab->nCol+regRow); sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead); sqlite3VdbeLoadString(v, regResult, pTab->zName); assert( IsOrdinaryTable(pTab) ); @@ -1565,7 +1565,7 @@ void sqlite3Pragma( ** regRow..regRow+n. If any of the child key values are NULL, this ** row cannot cause an FK violation. Jump directly to addrOk in ** this case. */ - if( regRow+pFK->nCol>pParse->nMem ) pParse->nMem = regRow+pFK->nCol; + sqlite3TouchRegister(pParse, regRow + pFK->nCol); for(j=0; jnCol; j++){ int iCol = aiCols ? aiCols[j] : pFK->aCol[j].iFrom; sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, iCol, regRow+j); @@ -1694,6 +1694,7 @@ void sqlite3Pragma( if( iDb>=0 && i!=iDb ) continue; sqlite3CodeVerifySchema(pParse, i); + pParse->okConstFactor = 0; /* tag-20230327-1 */ /* Do an integrity check of the B-Tree ** @@ -1729,7 +1730,7 @@ void sqlite3Pragma( aRoot[0] = cnt; /* Make sure sufficient number of registers have been allocated */ - pParse->nMem = MAX( pParse->nMem, 8+mxIdx ); + sqlite3TouchRegister(pParse, 8+mxIdx); sqlite3ClearTempRegCache(pParse); /* Do the b-tree integrity checks */ @@ -1879,15 +1880,29 @@ void sqlite3Pragma( labelOk = sqlite3VdbeMakeLabel(pParse); if( pCol->notNull ){ /* (1) NOT NULL columns may not contain a NULL */ + int jmp3; int jmp2 = sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); - sqlite3VdbeChangeP5(v, 0x0f); VdbeCoverage(v); + if( p1<0 ){ + sqlite3VdbeChangeP5(v, 0x0f); /* INT, REAL, TEXT, or BLOB */ + jmp3 = jmp2; + }else{ + sqlite3VdbeChangeP5(v, 0x0d); /* INT, TEXT, or BLOB */ + /* OP_IsType does not detect NaN values in the database file + ** which should be treated as a NULL. So if the header type + ** is REAL, we have to load the actual data using OP_Column + ** to reliably determine if the value is a NULL. */ + sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3); + jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk); + VdbeCoverage(v); + } zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, pCol->zCnName); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); if( doTypeCheck ){ sqlite3VdbeGoto(v, labelError); sqlite3VdbeJumpHere(v, jmp2); + sqlite3VdbeJumpHere(v, jmp3); }else{ /* VDBE byte code will fall thru */ } @@ -1995,7 +2010,7 @@ void sqlite3Pragma( int jmp7; sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur+j, 3); jmp7 = sqlite3VdbeAddOp3(v, OP_Eq, 3, 0, r1+pIdx->nColumn-1); - VdbeCoverage(v); + VdbeCoverageNeverNull(v); sqlite3VdbeLoadString(v, 3, "rowid not at end-of-record for row "); sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); diff --git a/src/prepare.c b/src/prepare.c index b2613e2c1..ff4826502 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -306,7 +306,9 @@ int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){ #else encoding = SQLITE_UTF8; #endif - if( db->nVdbeActive>0 && encoding!=ENC(db) ){ + if( db->nVdbeActive>0 && encoding!=ENC(db) + && (db->mDbFlags & DBFLAG_Vacuum)==0 + ){ rc = SQLITE_LOCKED; goto initone_error_out; }else{ @@ -700,7 +702,11 @@ static int sqlite3Prepare( sParse.db = db; sParse.pReprepare = pReprepare; assert( ppStmt && *ppStmt==0 ); - if( db->mallocFailed ) sqlite3ErrorMsg(&sParse, "out of memory"); + if( db->mallocFailed ){ + sqlite3ErrorMsg(&sParse, "out of memory"); + db->errCode = rc = SQLITE_NOMEM; + goto end_prepare; + } assert( sqlite3_mutex_held(db->mutex) ); /* For a long-term use prepared statement avoid the use of diff --git a/src/printf.c b/src/printf.c index 335ad0684..3e1782d46 100644 --- a/src/printf.c +++ b/src/printf.c @@ -142,6 +142,20 @@ static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ } #endif /* SQLITE_OMIT_FLOATING_POINT */ +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** "*val" is a u64. *msd is a divisor used to extract the +** most significant digit of *val. Extract that most significant +** digit and return it. +*/ +static char et_getdigit_int(u64 *val, u64 *msd){ + u64 x = (*val)/(*msd); + *val -= x*(*msd); + if( *msd>=10 ) *msd /= 10; + return '0' + (char)(x & 15); +} +#endif /* SQLITE_OMIT_FLOATING_POINT */ + /* ** Set the StrAccum object to an error mode. */ @@ -234,6 +248,8 @@ void sqlite3_str_vappendf( char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ LONGDOUBLE_TYPE realvalue; /* Value for real types */ + sqlite_uint64 msd; /* Divisor to get most-significant-digit + ** of longvalue */ const et_info *infop; /* Pointer to the appropriate info structure */ char *zOut; /* Rendering buffer */ int nOut; /* Size of the rendering buffer */ @@ -540,52 +556,78 @@ void sqlite3_str_vappendf( }else{ prefix = flag_prefix; } + exp = 0; if( xtype==etGENERIC && precision>0 ) precision--; testcase( precision>0xfff ); - idx = precision & 0xfff; - rounder = arRound[idx%10]; - while( idx>=10 ){ rounder *= 1.0e-10; idx -= 10; } - if( xtype==etFLOAT ){ - double rx = (double)realvalue; - sqlite3_uint64 u; - int ex; - memcpy(&u, &rx, sizeof(u)); - ex = -1023 + (int)((u>>52)&0x7ff); - if( precision+(ex/3) < 15 ) rounder += realvalue*3e-16; - realvalue += rounder; - } - /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ - exp = 0; - if( sqlite3IsNaN((double)realvalue) ){ - bufpt = "NaN"; - length = 3; - break; - } - if( realvalue>0.0 ){ - LONGDOUBLE_TYPE scale = 1.0; - while( realvalue>=1e100*scale && exp<=350 ){ scale *= 1e100;exp+=100;} - while( realvalue>=1e10*scale && exp<=350 ){ scale *= 1e10; exp+=10; } - while( realvalue>=10.0*scale && exp<=350 ){ scale *= 10.0; exp++; } - realvalue /= scale; - while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; } - while( realvalue<1.0 ){ realvalue *= 10.0; exp--; } - if( exp>350 ){ - bufpt = buf; - buf[0] = prefix; - memcpy(buf+(prefix!=0),"Inf",4); - length = 3+(prefix!=0); + if( realvalue<1.0e+16 + && realvalue==(LONGDOUBLE_TYPE)(longvalue = (u64)realvalue) + ){ + /* Number is a pure integer that can be represented as u64 */ + for(msd=1; msd*10<=longvalue; msd *= 10, exp++){} + if( exp>precision && xtype!=etFLOAT ){ + u64 rnd = msd/2; + int kk = precision; + while( kk-- > 0 ){ rnd /= 10; } + longvalue += rnd; + } + }else{ + msd = 0; + longvalue = 0; /* To prevent a compiler warning */ + idx = precision & 0xfff; + rounder = arRound[idx%10]; + while( idx>=10 ){ rounder *= 1.0e-10; idx -= 10; } + if( xtype==etFLOAT ){ + double rx = (double)realvalue; + sqlite3_uint64 u; + int ex; + memcpy(&u, &rx, sizeof(u)); + ex = -1023 + (int)((u>>52)&0x7ff); + if( precision+(ex/3) < 15 ) rounder += realvalue*3e-16; + realvalue += rounder; + } + if( sqlite3IsNaN((double)realvalue) ){ + if( flag_zeropad ){ + bufpt = "null"; + length = 4; + }else{ + bufpt = "NaN"; + length = 3; + } break; } + + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + if( ALWAYS(realvalue>0.0) ){ + LONGDOUBLE_TYPE scale = 1.0; + while( realvalue>=1e100*scale && exp<=350){ scale*=1e100;exp+=100;} + while( realvalue>=1e10*scale && exp<=350 ){ scale*=1e10; exp+=10; } + while( realvalue>=10.0*scale && exp<=350 ){ scale *= 10.0; exp++; } + realvalue /= scale; + while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 ){ realvalue *= 10.0; exp--; } + if( exp>350 ){ + if( flag_zeropad ){ + realvalue = 9.0; + exp = 999; + }else{ + bufpt = buf; + buf[0] = prefix; + memcpy(buf+(prefix!=0),"Inf",4); + length = 3+(prefix!=0); + break; + } + } + if( xtype!=etFLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + } } - bufpt = buf; + /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ - if( xtype!=etFLOAT ){ - realvalue += rounder; - if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } - } if( xtype==etGENERIC ){ flag_rtz = !flag_alternateform; if( exp<-4 || exp>precision ){ @@ -602,16 +644,18 @@ void sqlite3_str_vappendf( }else{ e2 = exp; } + nsd = 16 + flag_altform2*10; + bufpt = buf; { i64 szBufNeeded; /* Size of a temporary buffer needed */ szBufNeeded = MAX(e2,0)+(i64)precision+(i64)width+15; + if( cThousand && e2>0 ) szBufNeeded += (e2+2)/3; if( szBufNeeded > etBUFSIZE ){ bufpt = zExtra = printfTempBuf(pAccum, szBufNeeded); if( bufpt==0 ) return; } } zOut = bufpt; - nsd = 16 + flag_altform2*10; flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2; /* The sign in front of the number */ if( prefix ){ @@ -620,9 +664,15 @@ void sqlite3_str_vappendf( /* Digits prior to the decimal point */ if( e2<0 ){ *(bufpt++) = '0'; + }else if( msd>0 ){ + for(; e2>=0; e2--){ + *(bufpt++) = et_getdigit_int(&longvalue,&msd); + if( cThousand && (e2%3)==0 && e2>1 ) *(bufpt++) = ','; + } }else{ for(; e2>=0; e2--){ *(bufpt++) = et_getdigit(&realvalue,&nsd); + if( cThousand && (e2%3)==0 && e2>1 ) *(bufpt++) = ','; } } /* The decimal point */ @@ -636,8 +686,14 @@ void sqlite3_str_vappendf( *(bufpt++) = '0'; } /* Significant digits after the decimal point */ - while( (precision--)>0 ){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); + if( msd>0 ){ + while( (precision--)>0 ){ + *(bufpt++) = et_getdigit_int(&longvalue,&msd); + } + }else{ + while( (precision--)>0 ){ + *(bufpt++) = et_getdigit(&realvalue,&nsd); + } } /* Remove trailing zeros and the "." if no digits follow the "." */ if( flag_rtz && flag_dp ){ @@ -1318,12 +1374,22 @@ char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){ return zBuf; } char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ - char *z; + StrAccum acc; va_list ap; + if( n<=0 ) return zBuf; +#ifdef SQLITE_ENABLE_API_ARMOR + if( zBuf==0 || zFormat==0 ) { + (void)SQLITE_MISUSE_BKPT; + if( zBuf ) zBuf[0] = 0; + return zBuf; + } +#endif + sqlite3StrAccumInit(&acc, 0, zBuf, n, 0); va_start(ap,zFormat); - z = sqlite3_vsnprintf(n, zBuf, zFormat, ap); + sqlite3_str_vappendf(&acc, zFormat, ap); va_end(ap); - return z; + zBuf[acc.nChar] = 0; + return zBuf; } /* diff --git a/src/resolve.c b/src/resolve.c index 4b36ecca3..adfcc8dbe 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -467,7 +467,8 @@ static int lookupName( assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); if( pParse->bReturning ){ if( (pNC->ncFlags & NC_UBaseReg)!=0 - && (zTab==0 || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0) + && ALWAYS(zTab==0 + || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0) ){ pExpr->iTable = op!=TK_DELETE; pTab = pParse->pTriggerTab; diff --git a/src/select.c b/src/select.c index 2a66c79e3..f32db2c2a 100644 --- a/src/select.c +++ b/src/select.c @@ -717,7 +717,7 @@ static void pushOntoSorter( ** (2) All output columns are included in the sort record. In that ** case regData==regOrigData. ** (3) Some output columns are omitted from the sort record due to - ** the SQLITE_ENABLE_SORTER_REFERENCE optimization, or due to the + ** the SQLITE_ENABLE_SORTER_REFERENCES optimization, or due to the ** SQLITE_ECEL_OMITREF optimization, or due to the ** SortCtx.pDeferredRowLoad optimiation. In any of these cases ** regOrigData is 0 to prevent this routine from trying to copy @@ -2318,7 +2318,7 @@ void sqlite3SubqueryColumnTypes( assert( (pSelect->selFlags & SF_Resolved)!=0 ); assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 ); assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); - if( db->mallocFailed ) return; + if( db->mallocFailed || IN_RENAME_OBJECT ) return; while( pSelect->pPrior ) pSelect = pSelect->pPrior; a = pSelect->pEList->a; memset(&sNC, 0, sizeof(sNC)); @@ -2363,18 +2363,16 @@ void sqlite3SubqueryColumnTypes( break; } } - } - } - if( zType ){ - i64 m = sqlite3Strlen30(zType); - n = sqlite3Strlen30(pCol->zCnName); - pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); - if( pCol->zCnName ){ - memcpy(&pCol->zCnName[n+1], zType, m+1); - pCol->colFlags |= COLFLAG_HASTYPE; - }else{ - testcase( pCol->colFlags & COLFLAG_HASTYPE ); - pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); + } + } + if( zType ){ + i64 m = sqlite3Strlen30(zType); + n = sqlite3Strlen30(pCol->zCnName); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); + if( pCol->zCnName ){ + memcpy(&pCol->zCnName[n+1], zType, m+1); + pCol->colFlags |= COLFLAG_HASTYPE; } } pColl = sqlite3ExprCollSeq(pParse, p); @@ -4241,8 +4239,7 @@ static int compoundHasDifferentAffinities(Select *p){ ** query or there are no RIGHT or FULL JOINs in any arm ** of the subquery. (This is a duplicate of condition (27b).) ** (17h) The corresponding result set expressions in all arms of the -** compound must have the same affinity. (See restriction (9) -** on the push-down optimization.) +** compound must have the same affinity. ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, @@ -5110,10 +5107,24 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** or EXCEPT, then all of the result set columns for all arms of ** the compound must use the BINARY collating sequence. ** -** (9) If the subquery is a compound, then all arms of the compound must -** have the same affinity. (This is the same as restriction (17h) -** for query flattening.) -** +** (9) All three of the following are true: +** +** (9a) The WHERE clause expression originates in the ON or USING clause +** of a join (either an INNER or an OUTER join), and +** +** (9b) The subquery is to the right of the ON/USING clause +** +** (9c) There is a RIGHT JOIN (or FULL JOIN) in between the ON/USING +** clause and the subquery. +** +** Without this restriction, the push-down optimization might move +** the ON/USING filter expression from the left side of a RIGHT JOIN +** over to the right side, which leads to incorrect answers. See +** also restriction (6) in sqlite3ExprIsSingleTableConstraint(). +** +** (10) The inner query is not the right-hand table of a RIGHT JOIN. +** +** (11) The subquery is not a VALUES clause ** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. @@ -5122,13 +5133,20 @@ static int pushDownWhereTerms( Parse *pParse, /* Parse context (for malloc() and error reporting) */ Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ Expr *pWhere, /* The WHERE clause of the outer query */ - SrcItem *pSrc /* The subquery term of the outer FROM clause */ + SrcList *pSrcList, /* The complete from clause of the outer query */ + int iSrc /* Which FROM clause term to try to push into */ ){ Expr *pNew; + SrcItem *pSrc; /* The subquery FROM term into which WHERE is pushed */ int nChng = 0; + pSrc = &pSrcList->a[iSrc]; if( pWhere==0 ) return 0; - if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0; - if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ) return 0; + if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ){ + return 0; /* restrictions (2) and (11) */ + } + if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ){ + return 0; /* restrictions (10) */ + } if( pSubq->pPrior ){ Select *pSel; @@ -5144,9 +5162,6 @@ static int pushDownWhereTerms( if( pSel->pWin ) return 0; /* restriction (6b) */ #endif } - if( compoundHasDifferentAffinities(pSubq) ){ - return 0; /* restriction (9) */ - } if( notUnionAll ){ /* If any of the compound arms are connected using UNION, INTERSECT, ** or EXCEPT, then we must ensure that none of the columns use a @@ -5186,11 +5201,28 @@ static int pushDownWhereTerms( return 0; /* restriction (3) */ } while( pWhere->op==TK_AND ){ - nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, pSrc); + nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, pSrcList, iSrc); pWhere = pWhere->pLeft; } -#if 0 /* Legacy code. Checks now done by sqlite3ExprIsTableConstraint() */ +#if 0 /* These checks now done by sqlite3ExprIsSingleTableConstraint() */ + if( ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) /* (9a) */ + && (pSrcList->a[0].fg.jointype & JT_LTORJ)!=0 /* Fast pre-test of (9c) */ + ){ + int jj; + for(jj=0; jjw.iJoin==pSrcList->a[jj].iCursor ){ + /* If we reach this point, both (9a) and (9b) are satisfied. + ** The following loop checks (9c): + */ + for(jj++; jja[jj].fg.jointype & JT_RIGHT)!=0 ){ + return 0; /* restriction (9) */ + } + } + } + } + } if( isLeftJoin && (ExprHasProperty(pWhere,EP_OuterON)==0 || pWhere->w.iJoin!=iCursor) @@ -5204,7 +5236,7 @@ static int pushDownWhereTerms( } #endif - if( sqlite3ExprIsTableConstraint(pWhere, pSrc) ){ + if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){ nChng++; pSubq->selFlags |= SF_PushDown; while( pSubq ){ @@ -5238,6 +5270,78 @@ static int pushDownWhereTerms( } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ +/* +** Check to see if a subquery contains result-set columns that are +** never used. If it does, change the value of those result-set columns +** to NULL so that they do not cause unnecessary work to compute. +** +** Return the number of column that were changed to NULL. +*/ +static int disableUnusedSubqueryResultColumns(SrcItem *pItem){ + int nCol; + Select *pSub; /* The subquery to be simplified */ + Select *pX; /* For looping over compound elements of pSub */ + Table *pTab; /* The table that describes the subquery */ + int j; /* Column number */ + int nChng = 0; /* Number of columns converted to NULL */ + Bitmask colUsed; /* Columns that may not be NULLed out */ + + assert( pItem!=0 ); + if( pItem->fg.isCorrelated || pItem->fg.isCte ){ + return 0; + } + assert( pItem->pTab!=0 ); + pTab = pItem->pTab; + assert( pItem->pSelect!=0 ); + pSub = pItem->pSelect; + assert( pSub->pEList->nExpr==pTab->nCol ); + if( (pSub->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){ + testcase( pSub->selFlags & SF_Distinct ); + testcase( pSub->selFlags & SF_Aggregate ); + return 0; + } + for(pX=pSub; pX; pX=pX->pPrior){ + if( pX->pPrior && pX->op!=TK_ALL ){ + /* This optimization does not work for compound subqueries that + ** use UNION, INTERSECT, or EXCEPT. Only UNION ALL is allowed. */ + return 0; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pX->pWin ){ + /* This optimization does not work for subqueries that use window + ** functions. */ + return 0; + } +#endif + } + colUsed = pItem->colUsed; + if( pSub->pOrderBy ){ + ExprList *pList = pSub->pOrderBy; + for(j=0; jnExpr; j++){ + u16 iCol = pList->a[j].u.x.iOrderByCol; + if( iCol>0 ){ + iCol--; + colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); + } + } + } + nCol = pTab->nCol; + for(j=0; jpPrior) { + Expr *pY = pX->pEList->a[j].pExpr; + if( pY->op==TK_NULL ) continue; + pY->op = TK_NULL; + ExprClearProperty(pY, EP_Skip|EP_Unlikely); + pX->selFlags |= SF_PushDown; + nChng++; + } + } + return nChng; +} + + /* ** The pFunc is the only aggregate function in the query. Check to see ** if the query is a candidate for the min/max optimization. @@ -6384,12 +6488,13 @@ static void optimizeAggregateUseOfIndexedExpr( assert( pSelect->pGroupBy!=0 ); pAggInfo->nColumn = pAggInfo->nAccumulator; if( ALWAYS(pAggInfo->nSortingColumn>0) ){ - if( pAggInfo->nColumn==0 ){ - pAggInfo->nSortingColumn = pSelect->pGroupBy->nExpr; - }else{ - pAggInfo->nSortingColumn = - pAggInfo->aCol[pAggInfo->nColumn-1].iSorterColumn+1; + int mx = pSelect->pGroupBy->nExpr - 1; + int j, k; + for(j=0; jnColumn; j++){ + k = pAggInfo->aCol[j].iSorterColumn; + if( k>mx ) mx = k; } + pAggInfo->nSortingColumn = mx+1; } analyzeAggFuncArgs(pAggInfo, pNC); #if TREETRACE_ENABLED @@ -6423,11 +6528,13 @@ static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_AGG_FUNCTION ) return WRC_Continue; if( pExpr->op==TK_IF_NULL_ROW ) return WRC_Continue; pAggInfo = pExpr->pAggInfo; - assert( pExpr->iAgg>=0 && pExpr->iAggnColumn ); + if( NEVER(pExpr->iAgg>=pAggInfo->nColumn) ) return WRC_Continue; + assert( pExpr->iAgg>=0 ); pCol = &pAggInfo->aCol[pExpr->iAgg]; pExpr->op = TK_AGG_COLUMN; pExpr->iTable = pCol->iTable; pExpr->iColumn = pCol->iColumn; + ExprClearProperty(pExpr, EP_Skip|EP_Collate); return WRC_Prune; } @@ -6781,7 +6888,6 @@ static void agginfoFree(sqlite3 *db, AggInfo *p){ sqlite3DbFreeNN(db, p); } -#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION /* ** Attempt to transform a query of the form ** @@ -6809,6 +6915,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate */ if( p->pEList->nExpr!=1 ) return 0; /* Single result column */ if( p->pWhere ) return 0; + if( p->pHaving ) return 0; if( p->pGroupBy ) return 0; if( p->pOrderBy ) return 0; pExpr = p->pEList->a[0].pExpr; @@ -6828,7 +6935,8 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ if( pSub->pWhere ) return 0; /* No WHERE clause */ if( pSub->pLimit ) return 0; /* No LIMIT clause */ if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */ - pSub = pSub->pPrior; /* Repeat over compound */ + assert( pSub->pHaving==0 ); /* Due to the previous */ + pSub = pSub->pPrior; /* Repeat over compound */ }while( pSub ); /* If we reach this point then it is OK to perform the transformation */ @@ -6871,7 +6979,6 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ #endif return 1; } -#endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */ /* ** If any term of pSrc, or any SF_NestedFrom sub-query, is not the same @@ -7127,7 +7234,7 @@ int sqlite3Select( pTabList->a[0].fg.jointype & JT_LTORJ); } - /* No futher action if this term of the FROM clause is no a subquery */ + /* No futher action if this term of the FROM clause is not a subquery */ if( pSub==0 ) continue; /* Catch mismatch in the declared columns of a view and the number of @@ -7260,14 +7367,12 @@ int sqlite3Select( TREETRACE(0x2000,pParse,p,("Constant propagation not helpful\n")); } -#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION if( OptimizationEnabled(db, SQLITE_QueryFlattener|SQLITE_CountOfView) && countOfViewOptimization(pParse, p) ){ if( db->mallocFailed ) goto select_end; pTabList = p->pSrc; } -#endif /* For each term in the FROM clause, do two things: ** (1) Authorized unreferenced tables @@ -7326,7 +7431,7 @@ int sqlite3Select( if( OptimizationEnabled(db, SQLITE_PushDown) && (pItem->fg.isCte==0 || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) - && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem) + && pushDownWhereTerms(pParse, pSub, p->pWhere, pTabList, i) ){ #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x4000 ){ @@ -7340,6 +7445,22 @@ int sqlite3Select( TREETRACE(0x4000,pParse,p,("Push-down not possible\n")); } + /* Convert unused result columns of the subquery into simple NULL + ** expressions, to avoid unneeded searching and computation. + */ + if( OptimizationEnabled(db, SQLITE_NullUnusedCols) + && disableUnusedSubqueryResultColumns(pItem) + ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x4000 ){ + TREETRACE(0x4000,pParse,p, + ("Change unused result columns to NULL for subquery %d:\n", + pSub->selId)); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + } + zSavedAuthContext = pParse->zAuthContext; pParse->zAuthContext = pItem->zName; diff --git a/src/shell.c.in b/src/shell.c.in index de8bc3884..72e449840 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -99,6 +99,7 @@ typedef unsigned short int u16; #include #include #include +#include #include "sqlite3.h" typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; @@ -227,6 +228,7 @@ typedef unsigned char u8; #if SQLITE_OS_WINRT #include #endif +#define WIN32_LEAN_AND_MEAN #include /* string conversion routines only needed on Win32 */ @@ -441,11 +443,25 @@ static void endTimer(void){ static int bail_on_error = 0; /* -** Threat stdin as an interactive input if the following variable +** Treat stdin as an interactive input if the following variable ** is true. Otherwise, assume stdin is connected to a file or pipe. */ static int stdin_is_interactive = 1; +#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \ + && !defined(SHELL_OMIT_WIN_UTF8) +# define SHELL_WIN_UTF8_OPT 1 +#else +# define SHELL_WIN_UTF8_OPT 0 +#endif + +#if SHELL_WIN_UTF8_OPT +/* +** Setup console for UTF-8 input/output when following variable true. +*/ +static int console_utf8 = 0; +#endif + /* ** On Windows systems we have to know if standard output is a console ** in order to translate UTF-8 into MBCS. The following variable is @@ -578,16 +594,150 @@ static char *dynamicContinuePrompt(void){ } #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ +#if SHELL_WIN_UTF8_OPT +/* Following struct is used for -utf8 operation. */ +static struct ConsoleState { + int stdinEof; /* EOF has been seen on console input */ + int infsMode; /* Input file stream mode upon shell start */ + UINT inCodePage; /* Input code page upon shell start */ + UINT outCodePage; /* Output code page upon shell start */ + HANDLE hConsoleIn; /* Console input handle */ + DWORD consoleMode; /* Console mode upon shell start */ +} conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; + +#ifndef _O_U16TEXT /* For build environments lacking this constant: */ +# define _O_U16TEXT 0x20000 +#endif + +/* +** Prepare console, (if known to be a WIN32 console), for UTF-8 +** input (from either typing or suitable paste operations) and for +** UTF-8 rendering. This may "fail" with a message to stderr, where +** the preparation is not done and common "code page" issues occur. +*/ +static void console_prepare(void){ + HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE); + DWORD consoleMode = 0; + if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR + && GetConsoleMode( hCI, &consoleMode) ){ + if( !IsValidCodePage(CP_UTF8) ){ + fprintf(stderr, "Cannot use UTF-8 code page.\n"); + console_utf8 = 0; + return; + } + conState.hConsoleIn = hCI; + conState.consoleMode = consoleMode; + conState.inCodePage = GetConsoleCP(); + conState.outCodePage = GetConsoleOutputCP(); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; + SetConsoleMode(conState.hConsoleIn, consoleMode); + conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); + console_utf8 = 1; + }else{ + console_utf8 = 0; + } +} + +/* +** Undo the effects of console_prepare(), if any. +*/ +static void SQLITE_CDECL console_restore(void){ + if( console_utf8 && conState.inCodePage!=0 + && conState.hConsoleIn!=INVALID_HANDLE_VALUE ){ + _setmode(_fileno(stdin), conState.infsMode); + SetConsoleCP(conState.inCodePage); + SetConsoleOutputCP(conState.outCodePage); + SetConsoleMode(conState.hConsoleIn, conState.consoleMode); + /* Avoid multiple calls. */ + conState.hConsoleIn = INVALID_HANDLE_VALUE; + conState.consoleMode = 0; + console_utf8 = 0; + } +} + +/* +** Collect input like fgets(...) with special provisions for input +** from the Windows console to get around its strange coding issues. +** Defers to plain fgets() when input is not interactive or when the +** startup option, -utf8, has not been provided or taken effect. +*/ +static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ + if( fin==0 ) fin = stdin; + if( fin==stdin && stdin_is_interactive && console_utf8 ){ +# define SQLITE_IALIM 150 + wchar_t wbuf[SQLITE_IALIM]; + int lend = 0; + int noc = 0; + if( ncmax==0 || conState.stdinEof ) return 0; + buf[0] = 0; + while( noc SQLITE_IALIM*4+1 + noc) + ? SQLITE_IALIM : (ncmax-1 - noc)/4; +# undef SQLITE_IALIM + DWORD nbr = 0; + BOOL bRC = ReadConsoleW(conState.hConsoleIn, wbuf, na, &nbr, 0); + if( !bRC || (noc==0 && nbr==0) ) return 0; + if( nbr > 0 ){ + int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, + wbuf,nbr,0,0,0,0); + if( nmb !=0 && noc+nmb <= ncmax ){ + int iseg = noc; + nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, + wbuf,nbr,buf+noc,nmb,0,0); + noc += nmb; + /* Fixup line-ends as coded by Windows for CR (or "Enter".)*/ + if( noc > 0 ){ + if( buf[noc-1]=='\n' ){ + lend = 1; + if( noc > 1 && buf[noc-2]=='\r' ){ + buf[noc-2] = '\n'; + --noc; + } + } + } + /* Check for ^Z (anywhere in line) too. */ + while( iseg < noc ){ + if( buf[iseg]==0x1a ){ + conState.stdinEof = 1; + noc = iseg; /* Chop ^Z and anything following. */ + break; + } + ++iseg; + } + }else break; /* Drop apparent garbage in. (Could assert.) */ + }else break; + } + /* If got nothing, (after ^Z chop), must be at end-of-file. */ + if( noc == 0 ) return 0; + buf[noc] = 0; + return buf; + }else{ + return fgets(buf, ncmax, fin); + } +} + +# define fgets(b,n,f) utf8_fgets(b,n,f) +#endif /* SHELL_WIN_UTF8_OPT */ + /* ** Render output like fprintf(). Except, if the output is going to the -** console and if this is running on a Windows machine, translate the -** output from UTF-8 into MBCS. +** console and if this is running on a Windows machine, and if the -utf8 +** option is unavailable or (available and inactive), translate the +** output from UTF-8 into MBCS for output through 8-bit stdout stream. +** (With -utf8 active, no translation is needed and must not be done.) */ #if defined(_WIN32) || defined(WIN32) void utf8_printf(FILE *out, const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); - if( stdout_is_console && (out==stdout || out==stderr) ){ + if( stdout_is_console && (out==stdout || out==stderr) +# if SHELL_WIN_UTF8_OPT + && !console_utf8 +# endif + ){ char *z1 = sqlite3_vmprintf(zFormat, ap); char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); sqlite3_free(z1); @@ -619,7 +769,7 @@ static void shell_out_of_memory(void){ /* Check a pointer to see if it is NULL. If it is NULL, exit with an ** out-of-memory error. */ -static void shell_check_oom(void *p){ +static void shell_check_oom(const void *p){ if( p==0 ) shell_out_of_memory(); } @@ -798,9 +948,14 @@ static char *local_getline(char *zLine, FILE *in){ } } #if defined(_WIN32) || defined(WIN32) - /* For interactive input on Windows systems, translate the - ** multi-byte characterset characters into UTF-8. */ - if( stdin_is_interactive && in==stdin ){ + /* For interactive input on Windows systems, without -utf8, + ** translate the multi-byte characterset characters into UTF-8. + ** This is the translation that predates the -utf8 option. */ + if( stdin_is_interactive && in==stdin +# if SHELL_WIN_UTF8_OPT + && !console_utf8 +# endif /* SHELL_WIN_UTF8_OPT */ + ){ char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); if( zTrans ){ i64 nTrans = strlen(zTrans)+1; @@ -841,10 +996,21 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ #if SHELL_USE_LOCAL_GETLINE printf("%s", zPrompt); fflush(stdout); - zResult = local_getline(zPrior, stdin); + do{ + zResult = local_getline(zPrior, stdin); + zPrior = 0; + /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ + if( zResult==0 ) sqlite3_sleep(50); + }while( zResult==0 && seenInterrupt>0 ); #else free(zPrior); zResult = shell_readline(zPrompt); + while( zResult==0 ){ + /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ + sqlite3_sleep(50); + if( seenInterrupt==0 ) break; + zResult = shell_readline(""); + } if( zResult && *zResult ) shell_add_history(zResult); #endif } @@ -984,6 +1150,7 @@ static void appendText(ShellText *p, const char *zAppend, char quote){ */ static char quoteChar(const char *zName){ int i; + if( zName==0 ) return '"'; if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"'; for(i=0; zName[i]; i++){ if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"'; @@ -1185,7 +1352,7 @@ INCLUDE ../ext/recover/sqlite3recover.h # ifndef SQLITE_HAVE_SQLITE3R INCLUDE ../ext/recover/dbdata.c INCLUDE ../ext/recover/sqlite3recover.c -# endif +# endif /* SQLITE_HAVE_SQLITE3R */ #endif #ifdef SQLITE_SHELL_EXTSRC # include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC) @@ -1367,6 +1534,7 @@ static ShellState shellState; #define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */ #define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */ #define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */ +#define SHFLG_TestingMode 0x00000400 /* allow unsafe testing features */ /* ** Macros for testing and setting shellFlgs @@ -1610,6 +1778,7 @@ static void editFunc( }else{ /* If the file did not originally contain \r\n then convert any new ** \r\n back into \n */ + p[sz] = 0; for(i=j=0; i2 ) exit(1); + if( ++seenInterrupt>1 ) exit(1); if( globalDb ) sqlite3_interrupt(globalDb); } @@ -2088,6 +2258,7 @@ static void printSchemaLine(FILE *out, const char *z, const char *zTail){ int i; for(i=0; iout, "1e999"); + raw_printf(p->out, "9.0e+999"); }else if( ur==0xfff0000000000000LL ){ - raw_printf(p->out, "-1e999"); + raw_printf(p->out, "-9.0e+999"); }else{ sqlite3_int64 ir = (sqlite3_int64)r; if( r==(double)ir ){ @@ -2568,9 +2739,9 @@ static int shell_callback( sqlite3_uint64 ur; memcpy(&ur,&r,sizeof(r)); if( ur==0x7ff0000000000000LL ){ - raw_printf(p->out, "1e999"); + raw_printf(p->out, "9.0e+999"); }else if( ur==0xfff0000000000000LL ){ - raw_printf(p->out, "-1e999"); + raw_printf(p->out, "-9.0e+999"); }else{ sqlite3_snprintf(50,z,"%!.20g", r); raw_printf(p->out, "%s", z); @@ -2774,7 +2945,7 @@ static char *shell_error_context(const char *zSql, sqlite3 *db){ if( db==0 || zSql==0 || (iOffset = sqlite3_error_offset(db))<0 - || iOffset>=strlen(zSql) + || iOffset>=(int)strlen(zSql) ){ return sqlite3_mprintf(""); } @@ -2786,7 +2957,7 @@ static char *shell_error_context(const char *zSql, sqlite3 *db){ len = strlen(zSql); if( len>78 ){ len = 78; - while( (zSql[len]&0xc0)==0x80 ) len--; + while( len>0 && (zSql[len]&0xc0)==0x80 ) len--; } zCode = sqlite3_mprintf("%.*s", len, zSql); shell_check_oom(zCode); @@ -3387,12 +3558,13 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ if( nVar==0 ) return; /* Nothing to do */ if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters", "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ - return; /* Parameter table does not exist */ + rc = SQLITE_NOTFOUND; + pQ = 0; + }else{ + rc = sqlite3_prepare_v2(pArg->db, + "SELECT value FROM temp.sqlite_parameters" + " WHERE key=?1", -1, &pQ, 0); } - rc = sqlite3_prepare_v2(pArg->db, - "SELECT value FROM temp.sqlite_parameters" - " WHERE key=?1", -1, &pQ, 0); - if( rc || pQ==0 ) return; for(i=1; i<=nVar; i++){ char zNum[30]; const char *zVar = sqlite3_bind_parameter_name(pStmt, i); @@ -3401,8 +3573,16 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ zVar = zNum; } sqlite3_bind_text(pQ, 1, zVar, -1, SQLITE_STATIC); - if( sqlite3_step(pQ)==SQLITE_ROW ){ + if( rc==SQLITE_OK && pQ && sqlite3_step(pQ)==SQLITE_ROW ){ sqlite3_bind_value(pStmt, i, sqlite3_column_value(pQ, 0)); +#ifdef NAN + }else if( sqlite3_strlike("_NAN", zVar, 0)==0 ){ + sqlite3_bind_double(pStmt, i, NAN); +#endif +#ifdef INFINITY + }else if( sqlite3_strlike("_INF", zVar, 0)==0 ){ + sqlite3_bind_double(pStmt, i, INFINITY); +#endif }else{ sqlite3_bind_null(pStmt, i); } @@ -3646,7 +3826,7 @@ static void exec_prepared_stmt_columnar( azData = sqlite3_malloc64( nAlloc*sizeof(char*) ); shell_check_oom(azData); azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) ); - shell_check_oom((void*)azNextLine); + shell_check_oom(azNextLine); memset((void*)azNextLine, 0, nColumn*sizeof(char*) ); if( p->cmOpts.bQuote ){ azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) ); @@ -3676,6 +3856,7 @@ static void exec_prepared_stmt_columnar( } if( wx<0 ) wx = -wx; uz = (const unsigned char*)sqlite3_column_name(pStmt,i); + if( uz==0 ) uz = (u8*)""; azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw); } do{ @@ -4609,13 +4790,13 @@ static const char *(azHelp[]) = { " input text.", #endif #ifndef SQLITE_OMIT_TEST_CONTROL - ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", + ",imposter INDEX TABLE Create imposter table TABLE on index INDEX", #endif ".indexes ?TABLE? Show names of indexes", " If TABLE is specified, only show indexes for", " tables matching TABLE using the LIKE operator.", #ifdef SQLITE_ENABLE_IOTRACE - ".iotrace FILE Enable I/O diagnostic logging to FILE", + ",iotrace FILE Enable I/O diagnostic logging to FILE", #endif ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT", ".lint OPTIONS Report potential schema issues.", @@ -4624,8 +4805,10 @@ static const char *(azHelp[]) = { #if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE) ".load FILE ?ENTRY? Load an extension library", #endif -#ifndef SQLITE_SHELL_FIDDLE - ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", +#if !defined(SQLITE_SHELL_FIDDLE) + ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout", +#else + ".log on|off Turn logging on or off.", #endif ".mode MODE ?OPTIONS? Set output mode", " MODE is one of:", @@ -4722,7 +4905,7 @@ static const char *(azHelp[]) = { " Options:", " --indent Try to pretty-print the schema", " --nosys Omit objects whose names start with \"sqlite_\"", - ".selftest ?OPTIONS? Run tests defined in the SELFTEST table", + ",selftest ?OPTIONS? Run tests defined in the SELFTEST table", " Options:", " --init Create a new SELFTEST table", " -v Verbose output", @@ -4764,9 +4947,9 @@ static const char *(azHelp[]) = { #endif ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", #ifndef SQLITE_SHELL_FIDDLE - ".testcase NAME Begin redirecting output to 'testcase-out.txt'", + ",testcase NAME Begin redirecting output to 'testcase-out.txt'", #endif - ".testctrl CMD ... Run various sqlite3_test_control() operations", + ",testctrl CMD ... Run various sqlite3_test_control() operations", " Run \".testctrl\" with no arguments for details", ".timeout MS Try opening locked tables for MS milliseconds", ".timer on|off Turn SQL timer on or off", @@ -4818,16 +5001,41 @@ static int showHelp(FILE *out, const char *zPattern){ || cli_strcmp(zPattern,"-all")==0 || cli_strcmp(zPattern,"--all")==0 ){ - /* Show all commands, but only one line per command */ - if( zPattern==0 ) zPattern = ""; + enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 }; + enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 }; + /* Show all or most commands + ** *zPattern==0 => summary of documented commands only + ** *zPattern=='0' => whole help for undocumented commands + ** Otherwise => whole help for documented commands + */ + enum HelpWanted hw = HW_SummaryOnly; + enum HelpHave hh = HH_More; + if( zPattern!=0 ){ + hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull; + } for(i=0; i) of the blob. -*/ -static void shellInt32( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - const unsigned char *pBlob; - int nBlob; - int iInt; - - UNUSED_PARAMETER(argc); - nBlob = sqlite3_value_bytes(argv[0]); - pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]); - iInt = sqlite3_value_int(argv[1]); - - if( iInt>=0 && (iInt+1)*4<=nBlob ){ - const unsigned char *a = &pBlob[iInt*4]; - sqlite3_int64 iVal = ((sqlite3_int64)a[0]<<24) - + ((sqlite3_int64)a[1]<<16) - + ((sqlite3_int64)a[2]<< 8) - + ((sqlite3_int64)a[3]<< 0); - sqlite3_result_int64(context, iVal); - } -} - -/* -** Scalar function "shell_idquote(X)" returns string X quoted as an identifier, -** using "..." with internal double-quote characters doubled. -*/ -static void shellIdQuote( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - const char *zName = (const char*)sqlite3_value_text(argv[0]); - UNUSED_PARAMETER(argc); - if( zName ){ - char *z = sqlite3_mprintf("\"%w\"", zName); - sqlite3_result_text(context, z, -1, sqlite3_free); - } -} - /* ** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. */ @@ -5152,97 +5328,6 @@ static void shellUSleepFunc( sqlite3_result_int(context, sleep); } -/* -** Scalar function "shell_escape_crnl" used by the .recover command. -** The argument passed to this function is the output of built-in -** function quote(). If the first character of the input is "'", -** indicating that the value passed to quote() was a text value, -** then this function searches the input for "\n" and "\r" characters -** and adds a wrapper similar to the following: -** -** replace(replace(, '\n', char(10), '\r', char(13)); -** -** Or, if the first character of the input is not "'", then a copy -** of the input is returned. -*/ -static void shellEscapeCrnl( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - const char *zText = (const char*)sqlite3_value_text(argv[0]); - UNUSED_PARAMETER(argc); - if( zText && zText[0]=='\'' ){ - i64 nText = sqlite3_value_bytes(argv[0]); - i64 i; - char zBuf1[20]; - char zBuf2[20]; - const char *zNL = 0; - const char *zCR = 0; - i64 nCR = 0; - i64 nNL = 0; - - for(i=0; zText[i]; i++){ - if( zNL==0 && zText[i]=='\n' ){ - zNL = unused_string(zText, "\\n", "\\012", zBuf1); - nNL = strlen(zNL); - } - if( zCR==0 && zText[i]=='\r' ){ - zCR = unused_string(zText, "\\r", "\\015", zBuf2); - nCR = strlen(zCR); - } - } - - if( zNL || zCR ){ - i64 iOut = 0; - i64 nMax = (nNL > nCR) ? nNL : nCR; - i64 nAlloc = nMax * nText + (nMax+64)*2; - char *zOut = (char*)sqlite3_malloc64(nAlloc); - if( zOut==0 ){ - sqlite3_result_error_nomem(context); - return; - } - - if( zNL && zCR ){ - memcpy(&zOut[iOut], "replace(replace(", 16); - iOut += 16; - }else{ - memcpy(&zOut[iOut], "replace(", 8); - iOut += 8; - } - for(i=0; zText[i]; i++){ - if( zText[i]=='\n' ){ - memcpy(&zOut[iOut], zNL, nNL); - iOut += nNL; - }else if( zText[i]=='\r' ){ - memcpy(&zOut[iOut], zCR, nCR); - iOut += nCR; - }else{ - zOut[iOut] = zText[i]; - iOut++; - } - } - - if( zNL ){ - memcpy(&zOut[iOut], ",'", 2); iOut += 2; - memcpy(&zOut[iOut], zNL, nNL); iOut += nNL; - memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12; - } - if( zCR ){ - memcpy(&zOut[iOut], ",'", 2); iOut += 2; - memcpy(&zOut[iOut], zCR, nCR); iOut += nCR; - memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12; - } - - sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT); - sqlite3_free(zOut); - return; - } - } - - sqlite3_result_value(context, argv[0]); -} - /* Flags for open_db(). ** ** The default behavior of open_db() is to exit(1) if the database fails to @@ -5302,11 +5387,29 @@ static void open_db(ShellState *p, int openFlags){ if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", zDbFilename, sqlite3_errmsg(p->db)); - if( openFlags & OPEN_DB_KEEPALIVE ){ - sqlite3_open(":memory:", &p->db); - return; + if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ + exit(1); + } + sqlite3_close(p->db); + sqlite3_open(":memory:", &p->db); + if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ + utf8_printf(stderr, + "Also: unable to open substitute in-memory database.\n" + ); + exit(1); + }else{ + utf8_printf(stderr, + "Notice: using substitute in-memory database instead of \"%s\"\n", + zDbFilename); } - exit(1); + } + sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0); + + /* Reflect the use or absence of --unsafe-testing invocation. */ + { + int testmode_on = ShellHasFlag(p,SHFLG_TestingMode); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0); } #ifndef SQLITE_OMIT_LOAD_EXTENSION @@ -5324,9 +5427,6 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_fileio_init(p->db, 0, 0); sqlite3_completion_init(p->db, 0, 0); #endif -#if SQLITE_SHELL_HAVE_RECOVER - sqlite3_dbdata_init(p->db, 0, 0); -#endif #ifdef SQLITE_HAVE_ZLIB if( !p->bSafeModePersist ){ sqlite3_zipfile_init(p->db, 0, 0); @@ -5367,12 +5467,6 @@ static void open_db(ShellState *p, int openFlags){ shellModuleSchema, 0, 0); sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, shellPutsFunc, 0, 0); - sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0, - shellEscapeCrnl, 0, 0); - sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0, - shellInt32, 0, 0); - sqlite3_create_function(p->db, "shell_idquote", 1, SQLITE_UTF8, 0, - shellIdQuote, 0, 0); sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, shellUSleepFunc, 0, 0); #ifndef SQLITE_NOHAVE_SYSTEM @@ -5399,9 +5493,9 @@ static void open_db(ShellState *p, int openFlags){ aData = (unsigned char*)readFile(zDbFilename, &nData); }else{ aData = readHexDb(p, &nData); - if( aData==0 ){ - return; - } + } + if( aData==0 ){ + return; } rc = sqlite3_deserialize(p->db, "main", aData, nData, nData, SQLITE_DESERIALIZE_RESIZEABLE | @@ -5415,8 +5509,13 @@ static void open_db(ShellState *p, int openFlags){ } #endif } - if( p->bSafeModePersist && p->db!=0 ){ - sqlite3_set_authorizer(p->db, safeModeAuth, p); + if( p->db!=0 ){ + if( p->bSafeModePersist ){ + sqlite3_set_authorizer(p->db, safeModeAuth, p); + } + sqlite3_db_config( + p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 + ); } } @@ -5515,6 +5614,7 @@ static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ ** \' -> ' ** \\ -> backslash ** \NNN -> ascii character NNN in octal +** \xHH -> ascii character HH in hexadecimal */ static void resolve_backslashes(char *z){ int i, j; @@ -5543,6 +5643,15 @@ static void resolve_backslashes(char *z){ c = '\''; }else if( c=='\\' ){ c = '\\'; + }else if( c=='x' ){ + int nhd = 0, hdv; + u8 hv = 0; + while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){ + hv = (u8)((hv<<4)|hdv); + ++nhd; + } + i += nhd; + c = (u8)hv; }else if( c>='0' && c<='7' ){ c -= '0'; if( z[i+1]>='0' && z[i+1]<='7' ){ @@ -5642,7 +5751,7 @@ static int sql_trace_callback( utf8_printf(p->traceOut, "-- closing database connection\n"); return 0; } - if( mType!=SQLITE_TRACE_ROW && ((const char*)pX)[0]=='-' ){ + if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ zSql = (const char*)pX; }else{ pStmt = (sqlite3_stmt*)pP; @@ -5674,7 +5783,7 @@ static int sql_trace_callback( break; } case SQLITE_TRACE_PROFILE: { - sqlite3_int64 nNanosec = *(sqlite3_int64*)pX; + sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); break; } @@ -5750,8 +5859,8 @@ static void import_append_char(ImportCtx *p, int c){ */ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ int c; - int cSep = p->cColSep; - int rSep = p->cRowSep; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; p->n = 0; c = fgetc(p->in); if( c==EOF || seenInterrupt ){ @@ -5840,8 +5949,8 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ */ static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ int c; - int cSep = p->cColSep; - int rSep = p->cRowSep; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; p->n = 0; c = fgetc(p->in); if( c==EOF || seenInterrupt ){ @@ -6035,7 +6144,7 @@ static void tryToCloneSchema( zName = sqlite3_column_text(pQuery, 0); zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; - if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ) continue; + if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue; printf("%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ @@ -6139,7 +6248,7 @@ static int db_int(sqlite3 *db, const char *zSql){ return res; } -#if defined(SQLITE_SHELL_HAVE_RECOVER) +#if SQLITE_SHELL_HAVE_RECOVER /* ** Convert a 2-byte or 4-byte big-endian integer into a native integer */ @@ -6202,7 +6311,9 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ if( sqlite3_step(pStmt)==SQLITE_ROW && sqlite3_column_bytes(pStmt,0)>100 ){ - memcpy(aHdr, sqlite3_column_blob(pStmt,0), 100); + const u8 *pb = sqlite3_column_blob(pStmt,0); + shell_check_oom(pb); + memcpy(aHdr, pb, 100); sqlite3_finalize(pStmt); }else{ raw_printf(stderr, "unable to read database header\n"); @@ -7019,7 +7130,10 @@ static int arParseCommand( } } } - + if( pAr->eCmd==0 ){ + utf8_printf(stderr, "Required argument missing. Usage:\n"); + return arUsage(stderr); + } return SQLITE_OK; } @@ -7785,7 +7899,7 @@ FROM (\ sqlite3_bind_int(pStmt, 1, nDigits); rc = sqlite3_step(pStmt); sqlite3_finalize(pStmt); - assert(rc==SQLITE_DONE); + if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); } assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */ rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); @@ -8035,7 +8149,6 @@ static int do_meta_command(char *zLine, ShellState *p){ raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); rc = 2; }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ - raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n"); rc = 2; }else if( testcase_glob(azArg[1],zRes)==0 ){ utf8_printf(stderr, @@ -8165,6 +8278,8 @@ static int do_meta_command(char *zLine, ShellState *p){ { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, + { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER }, + { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS }, { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP }, { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA }, { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA }, @@ -8716,8 +8831,8 @@ static int do_meta_command(char *zLine, ShellState *p){ " for import\n"); goto meta_command_exit; } - sCtx.cColSep = p->colSeparator[0]; - sCtx.cRowSep = p->rowSeparator[0]; + sCtx.cColSep = (u8)p->colSeparator[0]; + sCtx.cRowSep = (u8)p->rowSeparator[0]; } sCtx.zFile = zFile; sCtx.nLine = 1; @@ -8913,6 +9028,12 @@ static int do_meta_command(char *zLine, ShellState *p){ int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ int i; + if( !ShellHasFlag(p,SHFLG_TestingMode) ){ + utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", + "imposter"); + rc = 1; + goto meta_command_exit; + } if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n" " .imposter off\n"); @@ -9097,7 +9218,8 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *zFile, *zProc; char *zErrMsg = 0; failIfSafeMode(p, "cannot run .load in safe mode"); - if( nArg<2 ){ + if( nArg<2 || azArg[1][0]==0 ){ + /* Must have a non-empty FILE. (Will not load self.) */ raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); rc = 1; goto meta_command_exit; @@ -9114,19 +9236,25 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif -#ifndef SQLITE_SHELL_FIDDLE if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){ - failIfSafeMode(p, "cannot run .log in safe mode"); if( nArg!=2 ){ raw_printf(stderr, "Usage: .log FILENAME\n"); rc = 1; }else{ const char *zFile = azArg[1]; + if( p->bSafeMode + && cli_strcmp(zFile,"on")!=0 + && cli_strcmp(zFile,"off")!=0 + ){ + raw_printf(stdout, "cannot set .log to anything other " + "than \"on\" or \"off\"\n"); + zFile = "off"; + } output_file_close(p->pLog); + if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout"; p->pLog = output_file_open(zFile, 0); } }else -#endif if( c=='m' && cli_strncmp(azArg[0], "mode", n)==0 ){ const char *zMode = 0; @@ -9762,6 +9890,10 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ p->scanstatsOn = (u8)booleanValue(azArg[1]); } + open_db(p, 0); + sqlite3_db_config( + p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 + ); #ifndef SQLITE_ENABLE_STMT_SCANSTATUS raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); #endif @@ -10411,28 +10543,36 @@ static int do_meta_command(char *zLine, ShellState *p){ shell_check_oom(zRevText); if( bDebug ) utf8_printf(p->out, "%s\n", zRevText); lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); - assert(lrc==SQLITE_OK); - if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC); - lrc = SQLITE_ROW==sqlite3_step(pStmt); - if( lrc ){ - const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); - sqlite3_stmt *pCheckStmt; - lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); - if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery); - if( SQLITE_OK==lrc ){ - if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){ - double countIrreversible = sqlite3_column_double(pCheckStmt, 0); - if( countIrreversible>0 ){ - int sz = (int)(countIrreversible + 0.5); - utf8_printf(stderr, - "Digest includes %d invalidly encoded text field%s.\n", - sz, (sz>1)? "s": ""); + if( lrc!=SQLITE_OK ){ + /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the + ** user does cruel and unnatural things like ".limit expr_depth 0". */ + rc = 1; + }else{ + if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC); + lrc = SQLITE_ROW==sqlite3_step(pStmt); + if( lrc ){ + const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); + sqlite3_stmt *pCheckStmt; + lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); + if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery); + if( lrc!=SQLITE_OK ){ + rc = 1; + }else{ + if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){ + double countIrreversible = sqlite3_column_double(pCheckStmt, 0); + if( countIrreversible>0 ){ + int sz = (int)(countIrreversible + 0.5); + utf8_printf(stderr, + "Digest includes %d invalidly encoded text field%s.\n", + sz, (sz>1)? "s": ""); + } } + sqlite3_finalize(pCheckStmt); } - sqlite3_finalize(pCheckStmt); + sqlite3_finalize(pStmt); } - sqlite3_finalize(pStmt); } + if( rc ) utf8_printf(stderr, ".sha3sum failed.\n"); sqlite3_free(zRevText); } #endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */ @@ -10695,6 +10835,12 @@ static int do_meta_command(char *zLine, ShellState *p){ int i, n2; const char *zCmd = 0; + if( !ShellHasFlag(p,SHFLG_TestingMode) ){ + utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", + "testctrl"); + rc = 1; + goto meta_command_exit; + } open_db(p, 0); zCmd = nArg>=2 ? azArg[1] : "help"; @@ -11642,6 +11788,7 @@ static void process_sqliterc( ** Show available command line options */ static const char zOptions[] = + " -- treat no subsequent arguments as options\n" #if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) " -A ARGS... run \".archive ARGS\" and exit\n" #endif @@ -11693,6 +11840,10 @@ static const char zOptions[] = " -stats print memory stats before each finalize\n" " -table set output mode to 'table'\n" " -tabs set output mode to 'tabs'\n" + " -unsafe-testing allow unsafe commands and modes for testing\n" +#if SHELL_WIN_UTF8_OPT + " -utf8 setup interactive console code page for UTF-8\n" +#endif " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE @@ -11704,9 +11855,9 @@ static const char zOptions[] = ; static void usage(int showDetail){ utf8_printf(stderr, - "Usage: %s [OPTIONS] FILENAME [SQL]\n" + "Usage: %s [OPTIONS] [FILENAME [SQL]]\n" "FILENAME is the name of an SQLite database. A new database is created\n" - "if the file does not previously exist.\n", Argv0); + "if the file does not previously exist. Defaults to :memory:.\n", Argv0); if( showDetail ){ utf8_printf(stderr, "OPTIONS include:\n%s", zOptions); }else{ @@ -11738,9 +11889,11 @@ static void main_init(ShellState *data) { memcpy(data->rowSeparator,SEP_Row, 2); data->showHeader = 0; data->shellFlgs = SHFLG_Lookaside; + sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); +#if !defined(SQLITE_SHELL_FIDDLE) verify_uninitialized(); +#endif sqlite3_config(SQLITE_CONFIG_URI, 1); - sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> "); sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> "); @@ -11783,6 +11936,10 @@ static char *cmdline_option_value(int argc, char **argv, int i){ return argv[i]; } +static void sayAbnormalExit(void){ + if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); +} + #ifndef SQLITE_SHELL_IS_UTF8 # if (defined(_WIN32) || defined(WIN32)) \ && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) @@ -11803,7 +11960,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ char **argv; #endif #ifdef SQLITE_DEBUG - sqlite3_int64 mem_main_enter = sqlite3_memory_used(); + sqlite3_int64 mem_main_enter = 0; #endif char *zErrMsg = 0; #ifdef SQLITE_SHELL_FIDDLE @@ -11817,15 +11974,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ int warnInmemoryDb = 0; int readStdin = 1; int nCmd = 0; + int nOptsEnd = argc; char **azCmd = 0; const char *zVfs = 0; /* Value of -vfs command-line option */ #if !SQLITE_SHELL_IS_UTF8 char **argvToFree = 0; int argcToFree = 0; #endif - - setBinaryMode(stdin, 0); setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ + #ifdef SQLITE_SHELL_FIDDLE stdin_is_interactive = 0; stdout_is_console = 1; @@ -11834,7 +11991,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdin_is_interactive = isatty(0); stdout_is_console = isatty(1); #endif - +#if SHELL_WIN_UTF8_OPT + atexit(console_restore); /* Needs revision for CLI as library call */ +#endif + atexit(sayAbnormalExit); +#ifdef SQLITE_DEBUG + mem_main_enter = sqlite3_memory_used(); +#endif #if !defined(_WIN32_WCE) if( getenv("SQLITE_DEBUG_BREAK") ){ if( isatty(0) && isatty(2) ){ @@ -11855,6 +12018,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } } #endif + /* Register a valid signal handler early, before much else is done. */ +#ifdef SIGINT + signal(SIGINT, interrupt_handler); +#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) + if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){ + fprintf(stderr, "No ^C handler.\n"); + } +#endif #if USE_SYSTEM_SQLITE+0!=1 if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ @@ -11894,15 +12065,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ assert( argc>=1 && argv && argv[0] ); Argv0 = argv[0]; - /* Make sure we have a valid signal handler early, before anything - ** else is done. - */ -#ifdef SIGINT - signal(SIGINT, interrupt_handler); -#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) - SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); -#endif - #ifdef SQLITE_SHELL_DBNAME_PROC { /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name @@ -11920,11 +12082,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ ** the size of the alternative malloc heap, ** and the first command to execute. */ +#ifndef SQLITE_SHELL_FIDDLE verify_uninitialized(); +#endif for(i=1; inOptsEnd ){ if( data.aAuxDb->zDbFilename==0 ){ data.aAuxDb->zDbFilename = z; }else{ @@ -11936,9 +12100,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ shell_check_oom(azCmd); azCmd[nCmd-1] = z; } + continue; } if( z[1]=='-' ) z++; - if( cli_strcmp(z,"-separator")==0 + if( cli_strcmp(z, "-")==0 ){ + nOptsEnd = i; + continue; + }else if( cli_strcmp(z,"-separator")==0 || cli_strcmp(z,"-nullvalue")==0 || cli_strcmp(z,"-newline")==0 || cli_strcmp(z,"-cmd")==0 @@ -11960,6 +12128,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ zSize = cmdline_option_value(argc, argv, ++i); szHeap = integerValue(zSize); if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; + verify_uninitialized(); sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #else (void)cmdline_option_value(argc, argv, ++i); @@ -11973,6 +12142,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( sz>0 && n>0 && 0xffffffffffffLL/sz0 && sz>0) ? malloc(n*sz) : 0, sz, n); data.shellFlgs |= SHFLG_Pagecache; @@ -11982,11 +12152,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( sz<0 ) sz = 0; n = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( n<0 ) n = 0; + verify_uninitialized(); sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; }else if( cli_strcmp(z,"-threadsafe")==0 ){ int n; n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); switch( n ){ case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; @@ -12010,10 +12182,12 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif }else if( cli_strcmp(z,"-mmap")==0 ){ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz); -#ifdef SQLITE_ENABLE_SORTER_REFERENCES +#if defined(SQLITE_ENABLE_SORTER_REFERENCES) }else if( cli_strcmp(z,"-sorterref")==0 ){ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz); #endif }else if( cli_strcmp(z,"-vfs")==0 ){ @@ -12047,11 +12221,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-nonce")==0 ){ free(data.zNonce); data.zNonce = strdup(argv[++i]); + }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ + ShellSetFlag(&data,SHFLG_TestingMode); }else if( cli_strcmp(z,"-safe")==0 ){ /* no-op - catch this on the second pass */ } } +#ifndef SQLITE_SHELL_FIDDLE verify_uninitialized(); +#endif #ifdef SQLITE_SHELL_INIT_PROC @@ -12115,7 +12293,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ */ for(i=1; i=nOptsEnd ) continue; if( z[1]=='-' ){ z++; } if( cli_strcmp(z,"-init")==0 ){ i++; @@ -12207,6 +12385,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdin_is_interactive = 1; }else if( cli_strcmp(z,"-batch")==0 ){ stdin_is_interactive = 0; + }else if( cli_strcmp(z,"-utf8")==0 ){ +#if SHELL_WIN_UTF8_OPT + console_utf8 = 1; +#endif /* SHELL_WIN_UTF8_OPT */ }else if( cli_strcmp(z,"-heap")==0 ){ i++; }else if( cli_strcmp(z,"-pagecache")==0 ){ @@ -12277,6 +12459,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif }else if( cli_strcmp(z,"-safe")==0 ){ data.bSafeMode = data.bSafeModePersist = 1; + }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ + /* Acted upon in first pass. */ }else{ utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); raw_printf(stderr,"Use -help for a list of options.\n"); @@ -12284,6 +12468,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } data.cMode = data.mode; } +#if SHELL_WIN_UTF8_OPT + if( console_utf8 && stdin_is_interactive ){ + console_prepare(); + }else{ + setBinaryMode(stdin, 0); + console_utf8 = 0; + } +#endif if( !readStdin ){ /* Run all arguments that do not begin with '-' as if they were separate @@ -12299,6 +12491,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } }else{ open_db(&data, 0); + echo_group_input(&data, azCmd[i]); rc = shell_exec(&data, azCmd[i], &zErrMsg); if( zErrMsg || rc ){ if( zErrMsg!=0 ){ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 049bc1713..b78adc2a3 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -1655,20 +1655,23 @@ int sqlite3_os_end(void); ** must ensure that no other SQLite interfaces are invoked by other ** threads while sqlite3_config() is running. ** -** The sqlite3_config() interface -** may only be invoked prior to library initialization using -** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. -** ^If sqlite3_config() is called after [sqlite3_initialize()] and before -** [sqlite3_shutdown()] then it will return SQLITE_MISUSE. -** Note, however, that ^sqlite3_config() can be called as part of the -** implementation of an application-defined [sqlite3_os_init()]. -** ** The first argument to sqlite3_config() is an integer ** [configuration option] that determines ** what property of SQLite is to be configured. Subsequent arguments ** vary depending on the [configuration option] ** in the first argument. ** +** For most configuration options, the sqlite3_config() interface +** may only be invoked prior to library initialization using +** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. +** The exceptional configuration options that may be invoked at any time +** are called "anytime configuration options". +** ^If sqlite3_config() is called after [sqlite3_initialize()] and before +** [sqlite3_shutdown()] with a first argument that is not an anytime +** configuration option, then the sqlite3_config() call will return SQLITE_MISUSE. +** Note, however, that ^sqlite3_config() can be called as part of the +** implementation of an application-defined [sqlite3_os_init()]. +** ** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. ** ^If the option is unknown or SQLite is unable to set the option ** then this routine returns a non-zero [error code]. @@ -1776,6 +1779,23 @@ struct sqlite3_mem_methods { ** These constants are the available integer configuration options that ** can be passed as the first argument to the [sqlite3_config()] interface. ** +** Most of the configuration options for sqlite3_config() +** will only work if invoked prior to [sqlite3_initialize()] or after +** [sqlite3_shutdown()]. The few exceptions to this rule are called +** "anytime configuration options". +** ^Calling [sqlite3_config()] with a first argument that is not an +** anytime configuration option in between calls to [sqlite3_initialize()] and +** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE. +** +** The set of anytime configuration options can change (by insertions +** and/or deletions) from one release of SQLite to the next. +** As of SQLite version 3.42.0, the complete set of anytime configuration +** options is: +**
      +**
    • SQLITE_CONFIG_LOG +**
    • SQLITE_CONFIG_PCACHE_HDRSZ +**
    +** ** New configuration options may be added in future releases of SQLite. ** Existing configuration options might be discontinued. Applications ** should check the return code from [sqlite3_config()] to make sure that @@ -2122,28 +2142,28 @@ struct sqlite3_mem_methods { ** compile-time option is not set, then the default maximum is 1073741824. ** */ -#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ -#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ -#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ -#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ -#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ -#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ -#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ -#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ -#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ -#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ -#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ -/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ -#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ -#define SQLITE_CONFIG_PCACHE 14 /* no-op */ -#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ -#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ -#define SQLITE_CONFIG_URI 17 /* int */ -#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ -#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ +#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ +#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ +#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ +#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ +#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ +#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ +#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ +#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ +#define SQLITE_CONFIG_PCACHE 14 /* no-op */ +#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ +#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ +#define SQLITE_CONFIG_URI 17 /* int */ +#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ -#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ -#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ +#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ +#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ #define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ #define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ #define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ @@ -2378,7 +2398,7 @@ struct sqlite3_mem_methods { ** ** ** [[SQLITE_DBCONFIG_DQS_DML]] -**
    SQLITE_DBCONFIG_DQS_DML +**
    SQLITE_DBCONFIG_DQS_DML
    **
    The SQLITE_DBCONFIG_DQS_DML option activates or deactivates ** the legacy [double-quoted string literal] misfeature for DML statements ** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The @@ -2387,7 +2407,7 @@ struct sqlite3_mem_methods { **
    ** ** [[SQLITE_DBCONFIG_DQS_DDL]] -**
    SQLITE_DBCONFIG_DQS_DDL +**
    SQLITE_DBCONFIG_DQS_DDL
    **
    The SQLITE_DBCONFIG_DQS option activates or deactivates ** the legacy [double-quoted string literal] misfeature for DDL statements, ** such as CREATE TABLE and CREATE INDEX. The @@ -2396,7 +2416,7 @@ struct sqlite3_mem_methods { **
    ** ** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]] -**
    SQLITE_DBCONFIG_TRUSTED_SCHEMA +**
    SQLITE_DBCONFIG_TRUSTED_SCHEMA
    **
    The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to ** assume that database schemas are untainted by malicious content. ** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite @@ -2416,7 +2436,7 @@ struct sqlite3_mem_methods { **
    ** ** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]] -**
    SQLITE_DBCONFIG_LEGACY_FILE_FORMAT +**
    SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
    **
    The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates ** the legacy file format flag. When activated, this flag causes all newly ** created database file to have a schema format version number (the 4-byte @@ -2425,7 +2445,7 @@ struct sqlite3_mem_methods { ** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, ** newly created databases are generally not understandable by SQLite versions ** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there -** is now scarcely any need to generated database files that are compatible +** is now scarcely any need to generate database files that are compatible ** all the way back to version 3.0.0, and so this setting is of little ** practical use, but is provided so that SQLite can continue to claim the ** ability to generate new database files that are compatible with version @@ -2436,6 +2456,38 @@ struct sqlite3_mem_methods { ** not considered a bug since SQLite versions 3.3.0 and earlier do not support ** either generated columns or decending indexes. **
    +** +** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] +**
    SQLITE_DBCONFIG_STMT_SCANSTATUS
    +**
    The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in +** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears +** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() +** statistics. For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is prepared and when it +** is stepped. The flag is set (collection of statistics is enabled) +** by default. This option takes two arguments: an integer and a pointer to +** an integer.. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the statement scanstatus option. If the second argument +** is not NULL, then the value of the statement scanstatus setting after +** processing the first argument is written into the integer that the second +** argument points to. +**
    +** +** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]] +**
    SQLITE_DBCONFIG_REVERSE_SCANORDER
    +**
    The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order +** in which tables and indexes are scanned so that the scans start at the end +** and work toward the beginning rather than starting at the beginning and +** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the +** same as setting [PRAGMA reverse_unordered_selects]. This option takes +** two arguments which are an integer and a pointer to an integer. The first +** argument is 1, 0, or -1 to enable, disable, or leave unchanged the +** reverse scan order flag, respectively. If the second argument is not NULL, +** then 0 or 1 is written into the integer that the second argument points to +** depending on if the reverse scan order flag is set after processing the +** first argument. +**
    +** ** */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ @@ -2456,7 +2508,9 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */ #define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */ #define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1017 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */ +#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -6201,6 +6255,13 @@ void sqlite3_activate_cerod( ** 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. +** +** If a negative argument is passed to sqlite3_sleep() the results vary by +** VFS and operating system. Some system treat a negative argument as an +** instruction to sleep forever. Others understand it to mean do not sleep +** at all. ^In SQLite version 3.42.0 and later, a negative +** argument passed into sqlite3_sleep() is changed to zero before it is relayed +** down into the xSleep method of the VFS. */ int sqlite3_sleep(int); @@ -7820,7 +7881,7 @@ int sqlite3_vfs_unregister(sqlite3_vfs*); ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() ** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable +** sqlite3_mutex_try() as an optimization so this is acceptable ** behavior.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was @@ -7828,9 +7889,9 @@ int sqlite3_vfs_unregister(sqlite3_vfs*); ** is undefined if the mutex is not currently entered by the ** calling thread or is not currently allocated. ** -** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or -** sqlite3_mutex_leave() is a NULL pointer, then all three routines -** behave as no-ops. +** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), +** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer, +** then any of the four routines behaves as a no-op. ** ** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. */ @@ -9564,18 +9625,28 @@ int sqlite3_vtab_config(sqlite3*, int op, ...); ** [[SQLITE_VTAB_INNOCUOUS]]
    SQLITE_VTAB_INNOCUOUS
    **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the -** the [xConnect] or [xCreate] methods of a [virtual table] implmentation +** the [xConnect] or [xCreate] methods of a [virtual table] implementation ** identify that virtual table as being safe to use from within triggers ** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the ** virtual table can do no serious harm even if it is controlled by a ** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS ** flag unless absolutely necessary. **
    +** +** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]
    SQLITE_VTAB_USES_ALL_SCHEMAS
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** instruct the query planner to begin at least a read transaction on +** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the +** virtual table is used. +**
    ** */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 #define SQLITE_VTAB_INNOCUOUS 2 #define SQLITE_VTAB_DIRECTONLY 3 +#define SQLITE_VTAB_USES_ALL_SCHEMAS 4 /* ** CAPI3REF: Determine The Virtual Table Conflict Policy diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 2614f4be4..2c893770b 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -224,8 +224,8 @@ #endif /* -** WAL mode depends on atomic aligned 32-bit loads and stores in a few -** places. The following macros try to make this explicit. +** A few places in the code require atomic load/store of aligned +** integer values. */ #ifndef __has_extension # define __has_extension(x) 0 /* compatibility with non-clang compilers */ @@ -281,15 +281,22 @@ #endif /* -** A macro to hint to the compiler that a function should not be +** Macros to hint to the compiler that a function should or should not be ** inlined. */ #if defined(__GNUC__) # define SQLITE_NOINLINE __attribute__((noinline)) +# define SQLITE_INLINE __attribute__((always_inline)) inline #elif defined(_MSC_VER) && _MSC_VER>=1310 # define SQLITE_NOINLINE __declspec(noinline) +# define SQLITE_INLINE __forceinline #else # define SQLITE_NOINLINE +# define SQLITE_INLINE +#endif +#if defined(SQLITE_COVERAGE_TEST) || defined(__STRICT_ANSI__) +# undef SQLITE_INLINE +# define SQLITE_INLINE #endif /* @@ -1757,7 +1764,7 @@ struct sqlite3 { #define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ /* result set is empty */ #define SQLITE_IgnoreChecks 0x00000200 /* Do not enforce check constraints */ -#define SQLITE_ReadUncommit 0x00000400 /* READ UNCOMMITTED in shared-cache */ +#define SQLITE_StmtScanStatus 0x00000400 /* Enable stmt_scanstats() counters */ #define SQLITE_NoCkptOnClose 0x00000800 /* No checkpoint on close()/DETACH */ #define SQLITE_ReverseOrder 0x00001000 /* Reverse unordered SELECTs */ #define SQLITE_RecTriggers 0x00002000 /* Enable recursive triggers */ @@ -1783,6 +1790,7 @@ struct sqlite3 { /* DELETE, or UPDATE and return */ /* the count using a callback. */ #define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */ +#define SQLITE_ReadUncommit HI(0x00004) /* READ UNCOMMITTED in shared-cache */ /* Flags used only if debugging */ #ifdef SQLITE_DEBUG @@ -1839,6 +1847,7 @@ struct sqlite3 { /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ #define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ +#define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -2310,6 +2319,7 @@ struct VTable { sqlite3_vtab *pVtab; /* Pointer to vtab instance */ int nRef; /* Number of pointers to this structure */ u8 bConstraint; /* True if constraints are supported */ + u8 bAllSchemas; /* True if might use any attached schema */ u8 eVtabRisk; /* Riskiness of allowing hacker access */ int iSavepoint; /* Depth of the SAVEPOINT stack */ VTable *pNext; /* Next in linked list (see above) */ @@ -2690,6 +2700,7 @@ struct Index { ** expression, or a reference to a VIRTUAL column */ #ifdef SQLITE_ENABLE_STAT4 int nSample; /* Number of elements in aSample[] */ + int mxSample; /* Number of slots allocated to aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */ IndexSample *aSample; /* Samples of the left-most key */ @@ -4176,6 +4187,7 @@ struct Walker { struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */ SrcItem *pSrcItem; /* A single FROM clause item */ DbFixer *pFix; /* See sqlite3FixSelect() */ + Mem *aMem; /* See sqlite3BtreeCursorHint() */ } u; }; @@ -4445,6 +4457,8 @@ int sqlite3CantopenError(int); # define sqlite3Isxdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x08) # define sqlite3Tolower(x) (sqlite3UpperToLower[(unsigned char)(x)]) # define sqlite3Isquote(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x80) +# define sqlite3JsonId1(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x42) +# define sqlite3JsonId2(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x46) #else # define sqlite3Toupper(x) toupper((unsigned char)(x)) # define sqlite3Isspace(x) isspace((unsigned char)(x)) @@ -4454,6 +4468,8 @@ int sqlite3CantopenError(int); # define sqlite3Isxdigit(x) isxdigit((unsigned char)(x)) # define sqlite3Tolower(x) tolower((unsigned char)(x)) # define sqlite3Isquote(x) ((x)=='"'||(x)=='\''||(x)=='['||(x)=='`') +# define sqlite3JsonId1(x) (sqlite3IsIdChar(x)&&(x)<'0') +# define sqlite3JsonId2(x) sqlite3IsIdChar(x) #endif int sqlite3IsIdChar(u8); @@ -4647,6 +4663,10 @@ void sqlite3ReleaseTempReg(Parse*,int); int sqlite3GetTempRange(Parse*,int); void sqlite3ReleaseTempRange(Parse*,int,int); void sqlite3ClearTempRegCache(Parse*); +void sqlite3TouchRegister(Parse*,int); +#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_DEBUG) +int sqlite3FirstAvailableRegister(Parse*,int); +#endif #ifdef SQLITE_DEBUG int sqlite3NoTempsInRange(Parse*,int,int); #endif @@ -4797,7 +4817,7 @@ Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, Expr*,ExprList*,u32,Expr*); void sqlite3SelectDelete(sqlite3*, Select*); Table *sqlite3SrcListLookup(Parse*, SrcList*); -int sqlite3IsReadOnly(Parse*, Table*, int); +int sqlite3IsReadOnly(Parse*, Table*, Trigger*); void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*); @@ -4886,7 +4906,7 @@ int sqlite3ExprIsConstantNotJoin(Expr*); int sqlite3ExprIsConstantOrFunction(Expr*, u8); int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); int sqlite3ExprIsTableConstant(Expr*,int); -int sqlite3ExprIsTableConstraint(Expr*,const SrcItem*); +int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int); #ifdef SQLITE_ENABLE_CURSOR_HINTS int sqlite3ExprContainsSubquery(Expr*); #endif @@ -5334,10 +5354,7 @@ int sqlite3VtabCallDestroy(sqlite3*, int, const char *); int sqlite3VtabBegin(sqlite3 *, VTable *); FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); -#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ - && !defined(SQLITE_OMIT_VIRTUALTABLE) - void sqlite3VtabUsesAllSchemas(sqlite3_index_info*); -#endif +void sqlite3VtabUsesAllSchemas(Parse*); sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); @@ -5584,4 +5601,10 @@ int sqlite3KvvfsInit(void); sqlite3_uint64 sqlite3Hwtime(void); #endif +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +# define IS_STMT_SCANSTATUS(db) (db->flags & SQLITE_StmtScanStatus) +#else +# define IS_STMT_SCANSTATUS(db) 0 +#endif + #endif /* SQLITEINT_H */ diff --git a/src/test1.c b/src/test1.c index b1f0baafc..772b896ac 100644 --- a/src/test1.c +++ b/src/test1.c @@ -16,6 +16,13 @@ #include "sqliteInt.h" #if SQLITE_OS_WIN # include "os_win.h" +# include +#else +# include +# if defined(__APPLE__) +# include +# include +# endif #endif #include "vdbeInt.h" @@ -2384,6 +2391,31 @@ static int SQLITE_TCLAPI vfsCurrentTimeInt64( return TCL_OK; } +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Usage: create_null_module DB NAME +*/ +static int SQLITE_TCLAPI test_create_null_module( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3 *db; + char *zName; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + zName = Tcl_GetString(objv[2]); + + sqlite3_create_module(db, zName, 0, 0); + return TCL_OK; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + #ifdef SQLITE_ENABLE_SNAPSHOT /* ** Usage: sqlite3_snapshot_get DB DBNAME @@ -8283,6 +8315,7 @@ static int SQLITE_TCLAPI test_sqlite3_db_config( { "DQS_DML", SQLITE_DBCONFIG_DQS_DML }, { "DQS_DDL", SQLITE_DBCONFIG_DQS_DDL }, { "LEGACY_FILE_FORMAT", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, + { "STMT_SCANSTATUS", SQLITE_DBCONFIG_STMT_SCANSTATUS }, }; int i; int v = 0; @@ -8639,6 +8672,40 @@ static int SQLITE_TCLAPI test_autovacuum_pages( return TCL_OK; } +/* +** Usage: number_of_cores +** +** Return a guess at the number of available cores available on the +** processor on which this process is running. +*/ +static int SQLITE_TCLAPI guess_number_of_cores( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + unsigned int nCore = 1; +#if SQLITE_OS_WIN + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + nCore = (unsigned int)sysinfo.dwNumberOfProcessors; +#elif defined(__APPLE__) + int nm[2]; + size_t len = 4; + nm[0] = CTL_HW; nm[1] = HW_AVAILCPU; + sysctl(nm, 2, &nCore, &len, NULL, 0); + if( nCore<1 ){ + nm[1] = HW_NCPU; + sysctl(nm, 2, &nCore, &len, NULL, 0); + } +#else + nCore = sysconf(_SC_NPROCESSORS_ONLN); +#endif + if( nCore<=0 ) nCore = 1; + Tcl_SetObjResult(interp, Tcl_NewIntObj((int)nCore)); + return SQLITE_OK; +} + /* ** Register commands with the TCL interpreter. @@ -8939,6 +9006,10 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "test_write_db", test_write_db, 0 }, { "sqlite3_register_cksumvfs", test_register_cksumvfs, 0 }, { "sqlite3_unregister_cksumvfs", test_unregister_cksumvfs, 0 }, + { "number_of_cores", guess_number_of_cores, 0 }, +#ifndef SQLITE_OMIT_VIRTUALTABLE + { "create_null_module", test_create_null_module, 0 }, +#endif }; static int bitmask_size = sizeof(Bitmask)*8; static int longdouble_size = sizeof(LONGDOUBLE_TYPE); diff --git a/src/test_syscall.c b/src/test_syscall.c index 947f9a9d9..3cd1034d3 100644 --- a/src/test_syscall.c +++ b/src/test_syscall.c @@ -110,15 +110,15 @@ static int ts_stat(const char *zPath, struct stat *p); static int ts_fstat(int fd, struct stat *p); static int ts_ftruncate(int fd, off_t n); static int ts_fcntl(int fd, int cmd, ... ); -static int ts_read(int fd, void *aBuf, size_t nBuf); -static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off); +static ssize_t ts_read(int fd, void *aBuf, size_t nBuf); +static ssize_t ts_pread(int fd, void *aBuf, size_t nBuf, off_t off); /* Note: pread64() and pwrite64() actually use off64_t as the type on their ** last parameter. But that datatype is not defined on many systems ** (ex: Mac, OpenBSD). So substitute a likely equivalent: sqlite3_uint64 */ -static int ts_pread64(int fd, void *aBuf, size_t nBuf, sqlite3_uint64 off); -static int ts_write(int fd, const void *aBuf, size_t nBuf); -static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off); -static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, sqlite3_uint64 off); +static ssize_t ts_pread64(int fd, void *aBuf, size_t nBuf, sqlite3_uint64 off); +static ssize_t ts_write(int fd, const void *aBuf, size_t nBuf); +static ssize_t ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off); +static ssize_t ts_pwrite64(int fd, const void *aBuf, size_t nBuf, sqlite3_uint64 off); static int ts_fchmod(int fd, mode_t mode); static int ts_fallocate(int fd, off_t off, off_t len); static void *ts_mmap(void *, size_t, int, int, int, off_t); @@ -211,7 +211,7 @@ static int tsErrno(const char *zFunc){ /* ** A wrapper around tsIsFail(). If tsIsFail() returns non-zero, set the ** value of errno before returning. -*/ +*/ static int tsIsFailErrno(const char *zFunc){ if( tsIsFail() ){ errno = tsErrno(zFunc); @@ -313,7 +313,7 @@ static int ts_fcntl(int fd, int cmd, ... ){ /* ** A wrapper around read(). */ -static int ts_read(int fd, void *aBuf, size_t nBuf){ +static ssize_t ts_read(int fd, void *aBuf, size_t nBuf){ if( tsIsFailErrno("read") ){ return -1; } @@ -323,7 +323,7 @@ static int ts_read(int fd, void *aBuf, size_t nBuf){ /* ** A wrapper around pread(). */ -static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off){ +static ssize_t ts_pread(int fd, void *aBuf, size_t nBuf, off_t off){ if( tsIsFailErrno("pread") ){ return -1; } @@ -333,7 +333,7 @@ static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off){ /* ** A wrapper around pread64(). */ -static int ts_pread64(int fd, void *aBuf, size_t nBuf, sqlite3_uint64 off){ +static ssize_t ts_pread64(int fd, void *aBuf, size_t nBuf, sqlite3_uint64 off){ if( tsIsFailErrno("pread64") ){ return -1; } @@ -343,7 +343,7 @@ static int ts_pread64(int fd, void *aBuf, size_t nBuf, sqlite3_uint64 off){ /* ** A wrapper around write(). */ -static int ts_write(int fd, const void *aBuf, size_t nBuf){ +static ssize_t ts_write(int fd, const void *aBuf, size_t nBuf){ if( tsIsFailErrno("write") ){ if( tsErrno("write")==EINTR ) orig_write(fd, aBuf, nBuf/2); return -1; @@ -354,7 +354,7 @@ static int ts_write(int fd, const void *aBuf, size_t nBuf){ /* ** A wrapper around pwrite(). */ -static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off){ +static ssize_t ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off){ if( tsIsFailErrno("pwrite") ){ return -1; } @@ -364,7 +364,7 @@ static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off){ /* ** A wrapper around pwrite64(). */ -static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, sqlite3_uint64 off){ +static ssize_t ts_pwrite64(int fd, const void *aBuf, size_t nBuf, sqlite3_uint64 off){ if( tsIsFailErrno("pwrite64") ){ return -1; } diff --git a/src/trigger.c b/src/trigger.c index d179d747a..bcb2132f0 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -1453,6 +1453,9 @@ u32 sqlite3TriggerColmask( Trigger *p; assert( isNew==1 || isNew==0 ); + if( IsView(pTab) ){ + return 0xffffffff; + } for(p=pTrigger; p; p=p->pNext){ if( p->op==op && (tr_tm&p->tr_tm) diff --git a/src/update.c b/src/update.c index bd1ddb1a3..c1f136398 100644 --- a/src/update.c +++ b/src/update.c @@ -408,7 +408,7 @@ void sqlite3Update( if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto update_cleanup; } - if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ goto update_cleanup; } diff --git a/src/util.c b/src/util.c index 632d317e3..3d4e01438 100644 --- a/src/util.c +++ b/src/util.c @@ -670,13 +670,15 @@ int sqlite3Int64ToText(i64 v, char *zOut){ } i = sizeof(zTemp)-2; zTemp[sizeof(zTemp)-1] = 0; - do{ - zTemp[i--] = (x%10) + '0'; + while( 1 /*exit-by-break*/ ){ + zTemp[i] = (x%10) + '0'; x = x/10; - }while( x ); - if( v<0 ) zTemp[i--] = '-'; - memcpy(zOut, &zTemp[i+1], sizeof(zTemp)-1-i); - return sizeof(zTemp)-2-i; + if( x==0 ) break; + i--; + }; + if( v<0 ) zTemp[--i] = '-'; + memcpy(zOut, &zTemp[i], sizeof(zTemp)-i); + return sizeof(zTemp)-1-i; } /* @@ -841,7 +843,9 @@ int sqlite3DecOrHexToI64(const char *z, i64 *pOut){ u = u*16 + sqlite3HexToInt(z[k]); } memcpy(pOut, &u, 8); - return (z[k]==0 && k-i<=16) ? 0 : 2; + if( k-i>16 ) return 2; + if( z[k]!=0 ) return 1; + return 0; }else #endif /* SQLITE_OMIT_HEX_INTEGER */ { @@ -877,7 +881,7 @@ int sqlite3GetInt32(const char *zNum, int *pValue){ u32 u = 0; zNum += 2; while( zNum[0]=='0' ) zNum++; - for(i=0; sqlite3Isxdigit(zNum[i]) && i<8; i++){ + for(i=0; i<8 && sqlite3Isxdigit(zNum[i]); i++){ u = u*16 + sqlite3HexToInt(zNum[i]); } if( (u&0x80000000)==0 && sqlite3Isxdigit(zNum[i])==0 ){ diff --git a/src/vdbe.c b/src/vdbe.c index a9febbb6e..2aa4e6df2 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -683,7 +683,10 @@ static u64 filterHash(const Mem *aMem, const Op *pOp){ }else if( p->flags & MEM_Real ){ h += sqlite3VdbeIntValue(p); }else if( p->flags & (MEM_Str|MEM_Blob) ){ - /* no-op */ + /* All strings have the same hash and all blobs have the same hash, + ** though, at least, those hashes are different from each other and + ** from NULL. */ + h += 4093 + (p->flags & (MEM_Str|MEM_Blob)); } } return h; @@ -733,6 +736,7 @@ int sqlite3VdbeExec( Mem *pOut = 0; /* Output operand */ #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) u64 *pnCycle = 0; + int bStmtScanStatus = IS_STMT_SCANSTATUS(db)!=0; #endif /*** INSERT STACK UNION HERE ***/ @@ -797,13 +801,17 @@ int sqlite3VdbeExec( assert( pOp>=aOp && pOp<&aOp[p->nOp]); nVmStep++; -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + +#if defined(VDBE_PROFILE) pOp->nExec++; pnCycle = &pOp->nCycle; -# ifdef VDBE_PROFILE - if( sqlite3NProfileCnt==0 ) -# endif + if( sqlite3NProfileCnt==0 ) *pnCycle -= sqlite3Hwtime(); +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( bStmtScanStatus ){ + pOp->nExec++; + pnCycle = &pOp->nCycle; *pnCycle -= sqlite3Hwtime(); + } #endif /* Only allow tracing if SQLITE_DEBUG is defined. @@ -2391,7 +2399,7 @@ case OP_Compare: { /* Opcode: Jump P1 P2 P3 * * ** ** Jump to the instruction at address P1, P2, or P3 depending on whether -** in the most recent OP_Compare instruction the P1 vector was less than +** in the most recent OP_Compare instruction the P1 vector was less than, ** equal to, or greater than the P2 vector, respectively. ** ** This opcode must immediately follow an OP_Compare opcode. @@ -2618,6 +2626,12 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ ** (0x01) bit. SQLITE_FLOAT is the 0x02 bit. SQLITE_TEXT is 0x04. ** SQLITE_BLOB is 0x08. SQLITE_NULL is 0x10. ** +** WARNING: This opcode does not reliably distinguish between NULL and REAL +** when P1>=0. If the database contains a NaN value, this opcode will think +** that the datatype is REAL when it should be NULL. When P1<0 and the value +** is already stored in register P3, then this opcode does reliably +** distinguish between NULL and REAL. The problem only arises then P1>=0. +** ** Take the jump to address P2 if and only if the datatype of the ** value determined by P1 and P3 corresponds to one of the bits in the ** P5 bitmask. @@ -2731,7 +2745,7 @@ case OP_IfNullRow: { /* jump */ VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; - if( ALWAYS(pC) && pC->nullRow ){ + if( pC && pC->nullRow ){ sqlite3VdbeMemSetNull(aMem + pOp->p3); goto jump_to_p2; } @@ -3226,7 +3240,7 @@ case OP_Affinity: { }else{ pIn1->u.r = (double)pIn1->u.i; pIn1->flags |= MEM_Real; - pIn1->flags &= ~MEM_Int; + pIn1->flags &= ~(MEM_Int|MEM_Str); } } REGISTER_TRACE((int)(pIn1-aMem), pIn1); @@ -4965,6 +4979,7 @@ case OP_SeekScan: { /* ncycle */ break; } nStep--; + pC->cacheStatus = CACHE_STALE; rc = sqlite3BtreeNext(pC->uc.pCursor, 0); if( rc ){ if( rc==SQLITE_DONE ){ @@ -7617,6 +7632,7 @@ case OP_AggFinal: { } sqlite3VdbeChangeEncoding(pMem, encoding); UPDATE_MAX_BLOBSIZE(pMem); + REGISTER_TRACE((int)(pMem-aMem), pMem); break; } @@ -8755,8 +8771,10 @@ default: { /* This is really OP_Noop, OP_Explain */ *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); pnCycle = 0; #elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) - *pnCycle += sqlite3Hwtime(); - pnCycle = 0; + if( pnCycle ){ + *pnCycle += sqlite3Hwtime(); + pnCycle = 0; + } #endif /* The following code adds nothing to the actual functionality diff --git a/src/vdbe.h b/src/vdbe.h index 3caf1f787..d28837f94 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -403,4 +403,8 @@ void sqlite3VdbeScanStatusCounters(Vdbe*, int, int, int); void sqlite3VdbePrintOp(FILE*, int, VdbeOp*); #endif +#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) +int sqlite3CursorRangeHintExprCheck(Walker *pWalker, Expr *pExpr); +#endif + #endif /* SQLITE_VDBE_H */ diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 476b6a2ad..d8fcda96d 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -271,7 +271,7 @@ int sqlite3_value_type(sqlite3_value* pVal){ SQLITE_NULL, /* 0x1f (not possible) */ SQLITE_FLOAT, /* 0x20 INTREAL */ SQLITE_NULL, /* 0x21 (not possible) */ - SQLITE_TEXT, /* 0x22 INTREAL + TEXT */ + SQLITE_FLOAT, /* 0x22 INTREAL + TEXT */ SQLITE_NULL, /* 0x23 (not possible) */ SQLITE_FLOAT, /* 0x24 (not possible) */ SQLITE_NULL, /* 0x25 (not possible) */ @@ -1337,9 +1337,9 @@ static const void *columnName( assert( db!=0 ); n = sqlite3_column_count(pStmt); if( N=0 ){ + u8 prior_mallocFailed = db->mallocFailed; N += useType*n; sqlite3_mutex_enter(db->mutex); - assert( db->mallocFailed==0 ); #ifndef SQLITE_OMIT_UTF16 if( useUtf16 ){ ret = sqlite3_value_text16((sqlite3_value*)&p->aColName[N]); @@ -1351,7 +1351,8 @@ static const void *columnName( /* A malloc may have failed inside of the _text() call. If this ** is the case, clear the mallocFailed flag and return NULL. */ - if( db->mallocFailed ){ + assert( db->mallocFailed==0 || db->mallocFailed==1 ); + if( db->mallocFailed > prior_mallocFailed ){ sqlite3OomClear(db); ret = 0; } @@ -2138,15 +2139,24 @@ int sqlite3_stmt_scanstatus_v2( void *pOut /* OUT: Write the answer here */ ){ Vdbe *p = (Vdbe*)pStmt; - ScanStatus *pScan; + VdbeOp *aOp = p->aOp; + int nOp = p->nOp; + ScanStatus *pScan = 0; int idx; + if( p->pFrame ){ + VdbeFrame *pFrame; + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + aOp = pFrame->aOp; + nOp = pFrame->nOp; + } + if( iScan<0 ){ int ii; if( iScanStatusOp==SQLITE_SCANSTAT_NCYCLE ){ i64 res = 0; - for(ii=0; iinOp; ii++){ - res += p->aOp[ii].nCycle; + for(ii=0; iiaddrLoop>0 ){ - *(sqlite3_int64*)pOut = p->aOp[pScan->addrLoop].nExec; + *(sqlite3_int64*)pOut = aOp[pScan->addrLoop].nExec; }else{ *(sqlite3_int64*)pOut = -1; } @@ -2180,7 +2190,7 @@ int sqlite3_stmt_scanstatus_v2( } case SQLITE_SCANSTAT_NVISIT: { if( pScan->addrVisit>0 ){ - *(sqlite3_int64*)pOut = p->aOp[pScan->addrVisit].nExec; + *(sqlite3_int64*)pOut = aOp[pScan->addrVisit].nExec; }else{ *(sqlite3_int64*)pOut = -1; } @@ -2202,7 +2212,7 @@ int sqlite3_stmt_scanstatus_v2( } case SQLITE_SCANSTAT_EXPLAIN: { if( pScan->addrExplain ){ - *(const char**)pOut = p->aOp[ pScan->addrExplain ].p4.z; + *(const char**)pOut = aOp[ pScan->addrExplain ].p4.z; }else{ *(const char**)pOut = 0; } @@ -2210,7 +2220,7 @@ int sqlite3_stmt_scanstatus_v2( } case SQLITE_SCANSTAT_SELECTID: { if( pScan->addrExplain ){ - *(int*)pOut = p->aOp[ pScan->addrExplain ].p1; + *(int*)pOut = aOp[ pScan->addrExplain ].p1; }else{ *(int*)pOut = -1; } @@ -2218,7 +2228,7 @@ int sqlite3_stmt_scanstatus_v2( } case SQLITE_SCANSTAT_PARENTID: { if( pScan->addrExplain ){ - *(int*)pOut = p->aOp[ pScan->addrExplain ].p2; + *(int*)pOut = aOp[ pScan->addrExplain ].p2; }else{ *(int*)pOut = -1; } @@ -2236,18 +2246,18 @@ int sqlite3_stmt_scanstatus_v2( if( iIns==0 ) break; if( iIns>0 ){ while( iIns<=iEnd ){ - res += p->aOp[iIns].nCycle; + res += aOp[iIns].nCycle; iIns++; } }else{ int iOp; - for(iOp=0; iOpnOp; iOp++){ - Op *pOp = &p->aOp[iOp]; + for(iOp=0; iOpp1!=iEnd ) continue; if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_NCYCLE)==0 ){ continue; } - res += p->aOp[iOp].nCycle; + res += aOp[iOp].nCycle; } } } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index d04d8f1e1..ecbf2d892 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -443,10 +443,10 @@ void sqlite3ExplainBreakpoint(const char *z1, const char *z2){ */ int sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ int addr = 0; -#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) +#if !defined(SQLITE_DEBUG) /* Always include the OP_Explain opcodes if SQLITE_DEBUG is defined. ** But omit them (for performance) during production builds */ - if( pParse->explain==2 ) + if( pParse->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) #endif { char *zMsg; @@ -820,6 +820,8 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ Op *pOp; Parse *pParse = p->pParse; int *aLabel = pParse->aLabel; + + assert( pParse->db->mallocFailed==0 ); /* tag-20230419-1 */ p->readOnly = 1; p->bIsReader = 0; pOp = &p->aOp[p->nOp-1]; @@ -879,6 +881,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ ** have non-negative values for P2. */ assert( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ); assert( ADDR(pOp->p2)<-pParse->nLabel ); + assert( aLabel!=0 ); /* True because of tag-20230419-1 */ pOp->p2 = aLabel[ADDR(pOp->p2)]; } break; @@ -1122,18 +1125,20 @@ void sqlite3VdbeScanStatus( LogEst nEst, /* Estimated number of output rows */ const char *zName /* Name of table or index being scanned */ ){ - sqlite3_int64 nByte = (p->nScan+1) * sizeof(ScanStatus); - ScanStatus *aNew; - aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); - if( aNew ){ - ScanStatus *pNew = &aNew[p->nScan++]; - memset(pNew, 0, sizeof(ScanStatus)); - pNew->addrExplain = addrExplain; - pNew->addrLoop = addrLoop; - pNew->addrVisit = addrVisit; - pNew->nEst = nEst; - pNew->zName = sqlite3DbStrDup(p->db, zName); - p->aScan = aNew; + if( IS_STMT_SCANSTATUS(p->db) ){ + sqlite3_int64 nByte = (p->nScan+1) * sizeof(ScanStatus); + ScanStatus *aNew; + aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); + if( aNew ){ + ScanStatus *pNew = &aNew[p->nScan++]; + memset(pNew, 0, sizeof(ScanStatus)); + pNew->addrExplain = addrExplain; + pNew->addrLoop = addrLoop; + pNew->addrVisit = addrVisit; + pNew->nEst = nEst; + pNew->zName = sqlite3DbStrDup(p->db, zName); + p->aScan = aNew; + } } } @@ -1150,20 +1155,22 @@ void sqlite3VdbeScanStatusRange( int addrStart, int addrEnd ){ - ScanStatus *pScan = 0; - int ii; - for(ii=p->nScan-1; ii>=0; ii--){ - pScan = &p->aScan[ii]; - if( pScan->addrExplain==addrExplain ) break; - pScan = 0; - } - if( pScan ){ - if( addrEnd<0 ) addrEnd = sqlite3VdbeCurrentAddr(p)-1; - for(ii=0; iiaAddrRange); ii+=2){ - if( pScan->aAddrRange[ii]==0 ){ - pScan->aAddrRange[ii] = addrStart; - pScan->aAddrRange[ii+1] = addrEnd; - break; + if( IS_STMT_SCANSTATUS(p->db) ){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + if( addrEnd<0 ) addrEnd = sqlite3VdbeCurrentAddr(p)-1; + for(ii=0; iiaAddrRange); ii+=2){ + if( pScan->aAddrRange[ii]==0 ){ + pScan->aAddrRange[ii] = addrStart; + pScan->aAddrRange[ii+1] = addrEnd; + break; + } } } } @@ -1180,19 +1187,21 @@ void sqlite3VdbeScanStatusCounters( int addrLoop, int addrVisit ){ - ScanStatus *pScan = 0; - int ii; - for(ii=p->nScan-1; ii>=0; ii--){ - pScan = &p->aScan[ii]; - if( pScan->addrExplain==addrExplain ) break; - pScan = 0; - } - if( pScan ){ - pScan->addrLoop = addrLoop; - pScan->addrVisit = addrVisit; + if( IS_STMT_SCANSTATUS(p->db) ){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + pScan->addrLoop = addrLoop; + pScan->addrVisit = addrVisit; + } } } -#endif +#endif /* defined(SQLITE_ENABLE_STMT_SCANSTATUS) */ /* @@ -1616,7 +1625,7 @@ VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ /* Return the most recently added opcode */ -VdbeOp * sqlite3VdbeGetLastOp(Vdbe *p){ +VdbeOp *sqlite3VdbeGetLastOp(Vdbe *p){ return sqlite3VdbeGetOp(p, p->nOp - 1); } @@ -3320,6 +3329,8 @@ int sqlite3VdbeHalt(Vdbe *p){ db->flags &= ~(u64)SQLITE_DeferFKs; sqlite3CommitInternalChanges(db); } + }else if( p->rc==SQLITE_SCHEMA && db->nVdbeActive>1 ){ + p->nChange = 0; }else{ sqlite3RollbackAll(db, SQLITE_OK); p->nChange = 0; @@ -3638,9 +3649,9 @@ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ #ifdef SQLITE_ENABLE_NORMALIZE sqlite3DbFree(db, p->zNormSql); { - DblquoteStr *pThis, *pNext; - for(pThis=p->pDblStr; pThis; pThis=pNext){ - pNext = pThis->pNextStr; + DblquoteStr *pThis, *pNxt; + for(pThis=p->pDblStr; pThis; pThis=pNxt){ + pNxt = pThis->pNextStr; sqlite3DbFree(db, pThis); } } @@ -5267,6 +5278,20 @@ int sqlite3NotPureFunc(sqlite3_context *pCtx){ return 1; } +#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) +/* +** This Walker callback is used to help verify that calls to +** sqlite3BtreeCursorHint() with opcode BTREE_HINT_RANGE have +** byte-code register values correctly initialized. +*/ +int sqlite3CursorRangeHintExprCheck(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_REGISTER ){ + assert( (pWalker->u.aMem[pExpr->iTable].flags & MEM_Undefined)==0 ); + } + return WRC_Continue; +} +#endif /* SQLITE_ENABLE_CURSOR_HINTS && SQLITE_DEBUG */ + #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Transfer error message text from an sqlite3_vtab.zErrMsg (text stored @@ -5329,6 +5354,16 @@ void sqlite3VdbePreUpdateHook( PreUpdate preupdate; const char *zTbl = pTab->zName; static const u8 fakeSortOrder = 0; +#ifdef SQLITE_DEBUG + int nRealCol; + if( pTab->tabFlags & TF_WithoutRowid ){ + nRealCol = sqlite3PrimaryKeyIndex(pTab)->nColumn; + }else if( pTab->tabFlags & TF_HasVirtual ){ + nRealCol = pTab->nNVCol; + }else{ + nRealCol = pTab->nCol; + } +#endif assert( db->pPreUpdate==0 ); memset(&preupdate, 0, sizeof(PreUpdate)); @@ -5345,8 +5380,8 @@ void sqlite3VdbePreUpdateHook( assert( pCsr!=0 ); assert( pCsr->eCurType==CURTYPE_BTREE ); - assert( pCsr->nField==pTab->nCol - || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1) + assert( pCsr->nField==nRealCol + || (pCsr->nField==nRealCol+1 && op==SQLITE_DELETE && iReg==-1) ); preupdate.v = v; diff --git a/src/vdbeblob.c b/src/vdbeblob.c index a18ee05b5..32987da13 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -342,7 +342,7 @@ int sqlite3_blob_open( if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt); sqlite3DbFree(db, pBlob); } - sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : (char*)0), zErr); sqlite3DbFree(db, zErr); sqlite3ParseObjectReset(&sParse); rc = sqlite3ApiExit(db, rc); @@ -501,7 +501,7 @@ int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ((Vdbe*)p->pStmt)->rc = SQLITE_OK; rc = blobSeekToRow(p, iRow, &zErr); if( rc!=SQLITE_OK ){ - sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : (char*)0), zErr); sqlite3DbFree(db, zErr); } assert( rc!=SQLITE_SCHEMA ); diff --git a/src/vdbemem.c b/src/vdbemem.c index be52062d5..d3cd55ba9 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -157,6 +157,7 @@ int sqlite3VdbeMemValidStrRep(Mem *p){ char *z; int i, j, incr; if( (p->flags & MEM_Str)==0 ) return 1; + if( p->db && p->db->mallocFailed ) return 1; if( p->flags & MEM_Term ){ /* Insure that the string is properly zero-terminated. Pay particular ** attention to the case where p->n is odd */ @@ -439,7 +440,7 @@ int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ vdbeMemRenderNum(nByte, pMem->z, pMem); assert( pMem->z!=0 ); - assert( pMem->n==sqlite3Strlen30NN(pMem->z) ); + assert( pMem->n==(int)sqlite3Strlen30NN(pMem->z) ); pMem->enc = SQLITE_UTF8; pMem->flags |= MEM_Str|MEM_Term; if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); @@ -1483,8 +1484,11 @@ static int valueFromFunction( if( pList ) nVal = pList->nExpr; assert( !ExprHasProperty(p, EP_IntValue) ); pFunc = sqlite3FindFunction(db, p->u.zToken, nVal, enc, 0); +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + if( pFunc==0 ) return SQLITE_OK; +#endif assert( pFunc ); - if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 + if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) ){ return SQLITE_OK; @@ -1519,16 +1523,11 @@ static int valueFromFunction( }else{ sqlite3ValueApplyAffinity(pVal, aff, SQLITE_UTF8); assert( rc==SQLITE_OK ); - assert( enc==pVal->enc - || (pVal->flags & MEM_Str)==0 - || db->mallocFailed ); -#if 0 /* Not reachable except after a prior failure */ rc = sqlite3VdbeChangeEncoding(pVal, enc); - if( rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal) ){ + if( NEVER(rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal)) ){ rc = SQLITE_TOOBIG; pCtx->pParse->nErr++; } -#endif } value_from_function_out: @@ -1592,6 +1591,13 @@ static int valueFromExpr( rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx); testcase( rc!=SQLITE_OK ); if( *ppVal ){ +#ifdef SQLITE_ENABLE_STAT4 + rc = ExpandBlob(*ppVal); +#else + /* zero-blobs only come from functions, not literal values. And + ** functions are only processed under STAT4 */ + assert( (ppVal[0][0].flags & MEM_Zero)==0 ); +#endif sqlite3VdbeMemCast(*ppVal, aff, enc); sqlite3ValueApplyAffinity(*ppVal, affinity, enc); } diff --git a/src/vtab.c b/src/vtab.c index 58452dfc5..ad629bb03 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -1102,7 +1102,10 @@ int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){ break; } if( xMethod && pVTab->iSavepoint>iSavepoint ){ + u64 savedFlags = (db->flags & SQLITE_Defensive); + db->flags &= ~(u64)SQLITE_Defensive; rc = xMethod(pVTab->pVtab, iSavepoint); + db->flags |= savedFlags; } sqlite3VtabUnlock(pVTab); } @@ -1331,6 +1334,10 @@ int sqlite3_vtab_config(sqlite3 *db, int op, ...){ p->pVTable->eVtabRisk = SQLITE_VTABRISK_High; break; } + case SQLITE_VTAB_USES_ALL_SCHEMAS: { + p->pVTable->bAllSchemas = 1; + break; + } default: { rc = SQLITE_MISUSE_BKPT; break; diff --git a/src/wal.c b/src/wal.c index fdc4ac39b..46d284c1b 100644 --- a/src/wal.c +++ b/src/wal.c @@ -746,19 +746,40 @@ static void walChecksumBytes( assert( nByte>=8 ); assert( (nByte&0x00000007)==0 ); assert( nByte<=65536 ); + assert( nByte%4==0 ); - if( nativeCksum ){ + if( !nativeCksum ){ do { + s1 += BYTESWAP32(aData[0]) + s2; + s2 += BYTESWAP32(aData[1]) + s1; + aData += 2; + }while( aDataszPage==szPage ); + if( (int)pWal->szPage!=szPage ){ + return SQLITE_CORRUPT_BKPT; /* TH3 test case: cov1/corrupt155.test */ + } /* Setup information needed to write frames into the WAL */ w.pWal = pWal; diff --git a/src/where.c b/src/where.c index 448c95576..25405cedf 100644 --- a/src/where.c +++ b/src/where.c @@ -831,7 +831,7 @@ static void explainAutomaticIndex( int bPartial, /* True if pIdx is a partial index */ int *pAddrExplain /* OUT: Address of OP_Explain */ ){ - if( pParse->explain!=2 ){ + if( IS_STMT_SCANSTATUS(pParse->db) && pParse->explain!=2 ){ Table *pTab = pIdx->pTable; const char *zSep = ""; char *zText = 0; @@ -870,8 +870,7 @@ static void explainAutomaticIndex( */ static SQLITE_NOINLINE void constructAutomaticIndex( Parse *pParse, /* The parsing context */ - const WhereClause *pWC, /* The WHERE clause */ - const SrcItem *pSrc, /* The FROM clause term to get the next index */ + WhereClause *pWC, /* The WHERE clause */ const Bitmask notReady, /* Mask of cursors that are not available */ WhereLevel *pLevel /* Write new index here */ ){ @@ -892,10 +891,12 @@ static SQLITE_NOINLINE void constructAutomaticIndex( char *zNotUsed; /* Extra space on the end of pIdx */ Bitmask idxCols; /* Bitmap of columns used for indexing */ Bitmask extraCols; /* Bitmap of additional columns */ - u8 sentWarning = 0; /* True if a warnning has been issued */ + u8 sentWarning = 0; /* True if a warning has been issued */ + u8 useBloomFilter = 0; /* True to also add a Bloom filter */ Expr *pPartial = 0; /* Partial Index Expression */ int iContinue = 0; /* Jump here to skip excluded rows */ - SrcItem *pTabItem; /* FROM clause term being indexed */ + SrcList *pTabList; /* The complete FROM clause */ + SrcItem *pSrc; /* The FROM clause term to get the next index */ int addrCounter = 0; /* Address where integer counter is initialized */ int regBase; /* Array of registers where record is assembled */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS @@ -911,6 +912,8 @@ static SQLITE_NOINLINE void constructAutomaticIndex( /* Count the number of columns that will be added to the index ** and used to match WHERE clause constraints */ nKeyCol = 0; + pTabList = pWC->pWInfo->pTabList; + pSrc = &pTabList->a[pLevel->iFrom]; pTable = pSrc->pTab; pWCEnd = &pWC->a[pWC->nTerm]; pLoop = pLevel->pWLoop; @@ -921,7 +924,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( ** WHERE clause (or the ON clause of a LEFT join) that constrain which ** rows of the target table (pSrc) that can be used. */ if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && sqlite3ExprIsTableConstraint(pExpr, pSrc) + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, pLevel->iFrom) ){ pPartial = sqlite3ExprAnd(pParse, pPartial, sqlite3ExprDup(pParse->db, pExpr, 0)); @@ -962,7 +965,11 @@ static SQLITE_NOINLINE void constructAutomaticIndex( ** original table changes and the index and table cannot both be used ** if they go out of sync. */ - extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); + if( IsView(pTable) ){ + extraCols = ALLBITS; + }else{ + extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); + } mxBitCol = MIN(BMS-1,pTable->nCol); testcase( pTable->nCol==BMS-1 ); testcase( pTable->nCol==BMS-2 ); @@ -998,6 +1005,16 @@ static SQLITE_NOINLINE void constructAutomaticIndex( assert( pColl!=0 || pParse->nErr>0 ); /* TH3 collate01.800 */ pIdx->azColl[n] = pColl ? pColl->zName : sqlite3StrBINARY; n++; + if( ALWAYS(pX->pLeft!=0) + && sqlite3ExprAffinity(pX->pLeft)!=SQLITE_AFF_TEXT + ){ + /* TUNING: only use a Bloom filter on an automatic index + ** if one or more key columns has the ability to hold numeric + ** values, since strings all have the same hash in the Bloom + ** filter implementation and hence a Bloom filter on a text column + ** is not usually helpful. */ + useBloomFilter = 1; + } } } } @@ -1030,20 +1047,21 @@ static SQLITE_NOINLINE void constructAutomaticIndex( sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); - if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ + if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) && useBloomFilter ){ + sqlite3WhereExplainBloomFilter(pParse, pWC->pWInfo, pLevel); pLevel->regFilter = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Blob, 10000, pLevel->regFilter); } /* Fill the automatic index with content */ - pTabItem = &pWC->pWInfo->pTabList->a[pLevel->iFrom]; - if( pTabItem->fg.viaCoroutine ){ - int regYield = pTabItem->regReturn; + assert( pSrc == &pWC->pWInfo->pTabList->a[pLevel->iFrom] ); + if( pSrc->fg.viaCoroutine ){ + int regYield = pSrc->regReturn; addrCounter = sqlite3VdbeAddOp2(v, OP_Integer, 0, 0); - sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub); + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pSrc->addrFillSub); addrTop = sqlite3VdbeAddOp1(v, OP_Yield, regYield); VdbeCoverage(v); - VdbeComment((v, "next row of %s", pTabItem->pTab->zName)); + VdbeComment((v, "next row of %s", pSrc->pTab->zName)); }else{ addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v); } @@ -1064,14 +1082,14 @@ static SQLITE_NOINLINE void constructAutomaticIndex( sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); - if( pTabItem->fg.viaCoroutine ){ + if( pSrc->fg.viaCoroutine ){ sqlite3VdbeChangeP2(v, addrCounter, regBase+n); testcase( pParse->db->mallocFailed ); assert( pLevel->iIdxCur>0 ); translateColumnToCopy(pParse, addrTop, pLevel->iTabCur, - pTabItem->regResult, pLevel->iIdxCur); + pSrc->regResult, pLevel->iIdxCur); sqlite3VdbeGoto(v, addrTop); - pTabItem->fg.viaCoroutine = 0; + pSrc->fg.viaCoroutine = 0; }else{ sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); @@ -1134,9 +1152,11 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); do{ + const SrcList *pTabList; const SrcItem *pItem; const Table *pTab; u64 sz; + int iSrc; sqlite3WhereExplainBloomFilter(pParse, pWInfo, pLevel); addrCont = sqlite3VdbeMakeLabel(pParse); iCur = pLevel->iTabCur; @@ -1150,7 +1170,9 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( ** testing complicated. By basing the blob size on the value in the ** sqlite_stat1 table, testing is much easier. */ - pItem = &pWInfo->pTabList->a[pLevel->iFrom]; + pTabList = pWInfo->pTabList; + iSrc = pLevel->iFrom; + pItem = &pTabList->a[iSrc]; assert( pItem!=0 ); pTab = pItem->pTab; assert( pTab!=0 ); @@ -1167,7 +1189,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( for(pTerm=pWInfo->sWC.a; pTermpExpr; if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && sqlite3ExprIsTableConstraint(pExpr, pItem) + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, iSrc) ){ sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); } @@ -1471,6 +1493,9 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ sqlite3ErrorMsg(pParse, "%s", pVtab->zErrMsg); } } + if( pTab->u.vtab.p->bAllSchemas ){ + sqlite3VtabUsesAllSchemas(pParse); + } sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = 0; return rc; @@ -2001,7 +2026,7 @@ static int whereRangeScanEst( UNUSED_PARAMETER(pBuilder); assert( pLower || pUpper ); #endif - assert( pUpper==0 || (pUpper->wtFlags & TERM_VNULL)==0 ); + assert( pUpper==0 || (pUpper->wtFlags & TERM_VNULL)==0 || pParse->nErr>0 ); nNew = whereRangeAdjust(pLower, nOut); nNew = whereRangeAdjust(pUpper, nNew); @@ -4102,8 +4127,6 @@ int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ return pHidden->eDistinct; } -#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ - && !defined(SQLITE_OMIT_VIRTUALTABLE) /* ** Cause the prepared statement that is associated with a call to ** xBestIndex to potentially use all schemas. If the statement being @@ -4113,9 +4136,7 @@ int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ ** ** This is used by the (built-in) sqlite_dbpage virtual table. */ -void sqlite3VtabUsesAllSchemas(sqlite3_index_info *pIdxInfo){ - HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; - Parse *pParse = pHidden->pParse; +void sqlite3VtabUsesAllSchemas(Parse *pParse){ int nDb = pParse->db->nDb; int i; for(i=0; inLevel>=2 ); @@ -5547,6 +5575,7 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( if( pWInfo->pOrderBy ){ tabUsed |= sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pOrderBy); } + hasRightJoin = (pWInfo->pTabList->a[0].fg.jointype & JT_LTORJ)!=0; for(i=pWInfo->nLevel-1; i>=1; i--){ WhereTerm *pTerm, *pEnd; SrcItem *pItem; @@ -5569,6 +5598,12 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( break; } } + if( hasRightJoin + && ExprHasProperty(pTerm->pExpr, EP_InnerON) + && pTerm->pExpr->w.iJoin==pItem->iCursor + ){ + break; /* restriction (5) */ + } } if( pTerm drop loop %c not used\n", pLoop->cId)); @@ -5968,22 +6003,45 @@ WhereInfo *sqlite3WhereBegin( } if( pParse->nErr ) goto whereBeginError; - /* Special case: WHERE terms that do not refer to any tables in the join - ** (constant expressions). Evaluate each such term, and jump over all the - ** generated code if the result is not true. + /* The False-WHERE-Term-Bypass optimization: + ** + ** If there are WHERE terms that are false, then no rows will be output, + ** so skip over all of the code generated here. ** - ** Do not do this if the expression contains non-deterministic functions - ** that are not within a sub-select. This is not strictly required, but - ** preserves SQLite's legacy behaviour in the following two cases: + ** Conditions: ** - ** FROM ... WHERE random()>0; -- eval random() once per row - ** FROM ... WHERE (SELECT random())>0; -- eval random() once overall + ** (1) The WHERE term must not refer to any tables in the join. + ** (2) The term must not come from an ON clause on the + ** right-hand side of a LEFT or FULL JOIN. + ** (3) The term must not come from an ON clause, or there must be + ** no RIGHT or FULL OUTER joins in pTabList. + ** (4) If the expression contains non-deterministic functions + ** that are not within a sub-select. This is not required + ** for correctness but rather to preserves SQLite's legacy + ** behaviour in the following two cases: + ** + ** WHERE random()>0; -- eval random() once per row + ** WHERE (SELECT random())>0; -- eval random() just once overall + ** + ** Note that the Where term need not be a constant in order for this + ** optimization to apply, though it does need to be constant relative to + ** the current subquery (condition 1). The term might include variables + ** from outer queries so that the value of the term changes from one + ** invocation of the current subquery to the next. */ for(ii=0; iinBase; ii++){ - WhereTerm *pT = &sWLB.pWC->a[ii]; + WhereTerm *pT = &sWLB.pWC->a[ii]; /* A term of the WHERE clause */ + Expr *pX; /* The expression of pT */ if( pT->wtFlags & TERM_VIRTUAL ) continue; - if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){ - sqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL); + pX = pT->pExpr; + assert( pX!=0 ); + assert( pT->prereqAll!=0 || !ExprHasProperty(pX, EP_OuterON) ); + if( pT->prereqAll==0 /* Conditions (1) and (2) */ + && (nTabList==0 || exprIsDeterministic(pX)) /* Condition (4) */ + && !(ExprHasProperty(pX, EP_InnerON) /* Condition (3) */ + && (pTabList->a[0].fg.jointype & JT_LTORJ)!=0 ) + ){ + sqlite3ExprIfFalse(pParse, pX, pWInfo->iBreak, SQLITE_JUMPIFNULL); pT->wtFlags |= TERM_CODED; } } @@ -6226,7 +6284,7 @@ WhereInfo *sqlite3WhereBegin( assert( n<=pTab->nCol ); } #ifdef SQLITE_ENABLE_CURSOR_HINTS - if( pLoop->u.btree.pIndex!=0 ){ + if( pLoop->u.btree.pIndex!=0 && (pTab->tabFlags & TF_WithoutRowid)==0 ){ sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ|bFordelete); }else #endif @@ -6363,11 +6421,11 @@ WhereInfo *sqlite3WhereBegin( sqlite3VdbeJumpHere(v, iOnce); } } + assert( pTabList == pWInfo->pTabList ); if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){ if( (wsFlags & WHERE_AUTO_INDEX)!=0 ){ #ifndef SQLITE_OMIT_AUTOMATIC_INDEX - constructAutomaticIndex(pParse, &pWInfo->sWC, - &pTabList->a[pLevel->iFrom], notReady, pLevel); + constructAutomaticIndex(pParse, &pWInfo->sWC, notReady, pLevel); #endif }else{ sqlite3ConstructBloomFilter(pWInfo, ii, pLevel, notReady); @@ -6684,7 +6742,8 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ k = pLevel->addrBody + 1; #ifdef SQLITE_DEBUG if( db->flags & SQLITE_VdbeAddopTrace ){ - printf("TRANSLATE opcodes in range %d..%d\n", k, last-1); + printf("TRANSLATE cursor %d->%d in opcode range %d..%d\n", + pLevel->iTabCur, pLevel->iIdxCur, k, last-1); } /* Proof that the "+1" on the k value above is safe */ pOp = sqlite3VdbeGetOp(v, k - 1); diff --git a/src/wherecode.c b/src/wherecode.c index 860acb44c..a998c0a4f 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -111,9 +111,9 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){ /* ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN -** command, or if either SQLITE_DEBUG or SQLITE_ENABLE_STMT_SCANSTATUS was -** defined at compile-time. If it is not a no-op, a single OP_Explain opcode -** is added to the output to describe the table scan strategy in pLevel. +** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG +** was defined at compile-time. If it is not a no-op, a single OP_Explain +** opcode is added to the output to describe the table scan strategy in pLevel. ** ** If an OP_Explain opcode is added to the VM, its address is returned. ** Otherwise, if no OP_Explain is coded, zero is returned. @@ -125,8 +125,8 @@ int sqlite3WhereExplainOneScan( u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ ){ int ret = 0; -#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) - if( sqlite3ParseToplevel(pParse)->explain==2 ) +#if !defined(SQLITE_DEBUG) + if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) #endif { SrcItem *pItem = &pTabList->a[pLevel->iFrom]; @@ -292,27 +292,29 @@ void sqlite3WhereAddScanStatus( WhereLevel *pLvl, /* Level to add scanstatus() entry for */ int addrExplain /* Address of OP_Explain (or 0) */ ){ - const char *zObj = 0; - WhereLoop *pLoop = pLvl->pWLoop; - int wsFlags = pLoop->wsFlags; - int viaCoroutine = 0; - - if( (wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ - zObj = pLoop->u.btree.pIndex->zName; - }else{ - zObj = pSrclist->a[pLvl->iFrom].zName; - viaCoroutine = pSrclist->a[pLvl->iFrom].fg.viaCoroutine; - } - sqlite3VdbeScanStatus( - v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj - ); - - if( viaCoroutine==0 ){ - if( (wsFlags & (WHERE_MULTI_OR|WHERE_AUTO_INDEX))==0 ){ - sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iTabCur); + if( IS_STMT_SCANSTATUS( sqlite3VdbeDb(v) ) ){ + const char *zObj = 0; + WhereLoop *pLoop = pLvl->pWLoop; + int wsFlags = pLoop->wsFlags; + int viaCoroutine = 0; + + if( (wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ + zObj = pLoop->u.btree.pIndex->zName; + }else{ + zObj = pSrclist->a[pLvl->iFrom].zName; + viaCoroutine = pSrclist->a[pLvl->iFrom].fg.viaCoroutine; } - if( wsFlags & WHERE_INDEXED ){ - sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); + sqlite3VdbeScanStatus( + v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj + ); + + if( viaCoroutine==0 ){ + if( (wsFlags & (WHERE_MULTI_OR|WHERE_AUTO_INDEX))==0 ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iTabCur); + } + if( wsFlags & WHERE_INDEXED ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); + } } } } @@ -1009,11 +1011,12 @@ static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){ */ static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){ int rc = WRC_Continue; + int reg; struct CCurHint *pHint = pWalker->u.pCCurHint; if( pExpr->op==TK_COLUMN ){ if( pExpr->iTable!=pHint->iTabCur ){ - int reg = ++pWalker->pParse->nMem; /* Register for column value */ - sqlite3ExprCode(pWalker->pParse, pExpr, reg); + reg = ++pWalker->pParse->nMem; /* Register for column value */ + reg = sqlite3ExprCodeTarget(pWalker->pParse, pExpr, reg); pExpr->op = TK_REGISTER; pExpr->iTable = reg; }else if( pHint->pIdx!=0 ){ @@ -1021,15 +1024,15 @@ static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){ pExpr->iColumn = sqlite3TableColumnToIndex(pHint->pIdx, pExpr->iColumn); assert( pExpr->iColumn>=0 ); } - }else if( pExpr->op==TK_AGG_FUNCTION ){ - /* An aggregate function in the WHERE clause of a query means this must - ** be a correlated sub-query, and expression pExpr is an aggregate from - ** the parent context. Do not walk the function arguments in this case. - ** - ** todo: It should be possible to replace this node with a TK_REGISTER - ** expression, as the result of the expression must be stored in a - ** register at this point. The same holds for TK_AGG_COLUMN nodes. */ + }else if( pExpr->pAggInfo ){ rc = WRC_Prune; + reg = ++pWalker->pParse->nMem; /* Register for column value */ + reg = sqlite3ExprCodeTarget(pWalker->pParse, pExpr, reg); + pExpr->op = TK_REGISTER; + pExpr->iTable = reg; + }else if( pExpr->op==TK_TRUEFALSE ){ + /* Do not walk disabled expressions. tag-20230504-1 */ + return WRC_Prune; } return rc; } @@ -1131,7 +1134,7 @@ static void codeCursorHint( } if( pExpr!=0 ){ sWalker.xExprCallback = codeCursorHintFixExpr; - sqlite3WalkExpr(&sWalker, pExpr); + if( pParse->nErr==0 ) sqlite3WalkExpr(&sWalker, pExpr); sqlite3VdbeAddOp4(v, OP_CursorHint, (sHint.pIdx ? sHint.iIdxCur : sHint.iTabCur), 0, 0, (const char*)pExpr, P4_EXPR); @@ -1925,7 +1928,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** guess. */ addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan, (pIdx->aiRowLogEst[0]+9)/10); - if( pRangeStart ){ + if( pRangeStart || pRangeEnd ){ sqlite3VdbeChangeP5(v, 1); sqlite3VdbeChangeP2(v, addrSeekScan, sqlite3VdbeCurrentAddr(v)+1); addrSeekScan = 0; @@ -1966,16 +1969,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( assert( pLevel->p2==0 ); if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; - if( addrSeekScan ){ - /* For a seek-scan that has a range on the lowest term of the index, - ** we have to make the top of the loop be code that sets the end - ** condition of the range. Otherwise, the OP_SeekScan might jump - ** over that initialization, leaving the range-end value set to the - ** range-start value, resulting in a wrong answer. - ** See ticket 5981a8c041a3c2f3 (2021-11-02). - */ - pLevel->p2 = sqlite3VdbeCurrentAddr(v); - } + assert( addrSeekScan==0 ); codeExprOrVector(pParse, pRight, regBase+nEq, nTop); whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); if( (pRangeEnd->wtFlags & TERM_VNULL)==0 @@ -2009,7 +2003,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( zEndAff ) sqlite3DbNNFreeNN(db, zEndAff); /* Top of the loop body */ - if( pLevel->p2==0 ) pLevel->p2 = sqlite3VdbeCurrentAddr(v); + pLevel->p2 = sqlite3VdbeCurrentAddr(v); /* Check if the index cursor is past the end of the range. */ if( nConstraint ){ diff --git a/src/whereexpr.c b/src/whereexpr.c index 7860480dd..a979b6f2b 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -1211,7 +1211,7 @@ static void exprAnalyze( && 0==sqlite3ExprCanBeNull(pLeft) ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); - pExpr->op = TK_TRUEFALSE; + pExpr->op = TK_TRUEFALSE; /* See tag-20230504-1 */ pExpr->u.zToken = "false"; ExprSetProperty(pExpr, EP_IsFalse); pTerm->prereqAll = 0; @@ -1856,9 +1856,12 @@ void sqlite3WhereTabFuncArgs( pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); - if( pItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){ + if( pItem->fg.jointype & (JT_LEFT|JT_RIGHT) ){ + testcase( pItem->fg.jointype & JT_LEFT ); /* testtag-20230227a */ + testcase( pItem->fg.jointype & JT_RIGHT ); /* testtag-20230227b */ joinType = EP_OuterON; }else{ + testcase( pItem->fg.jointype & JT_LTORJ ); /* testtag-20230227c */ joinType = EP_InnerON; } sqlite3SetJoinExpr(pTerm, pItem->iCursor, joinType); diff --git a/src/window.c b/src/window.c index 56de38ba3..a8081aa24 100644 --- a/src/window.c +++ b/src/window.c @@ -785,6 +785,7 @@ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ } /* no break */ deliberate_fall_through + case TK_IF_NULL_ROW: case TK_AGG_FUNCTION: case TK_COLUMN: { int iCol = -1; diff --git a/test/aggfault.test b/test/aggfault.test new file mode 100644 index 000000000..7c16b039c --- /dev/null +++ b/test/aggfault.test @@ -0,0 +1,43 @@ +# 2023 March 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix aggfault + + +do_execsql_test 1 { + CREATE TABLE t1(x); + CREATE INDEX t1x ON t1(x, x=0); +} +faultsim_save_and_close + +do_faultsim_test 2 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { SELECT * FROM sqlite_schema } +} -body { + execsql { + SELECT * FROM t1 AS a1 WHERE ( + SELECT count(x AND 0=a1.x) FROM t1 GROUP BY abs(1) + ) AND x=( + SELECT * FROM t1 AS a1 + WHERE (SELECT count(x IS 1 AND a1.x=0) + FROM t1 + GROUP BY abs(1)) AND x=0 + ); + } +} -test { + faultsim_test_result {0 {}} +} + + +finish_test diff --git a/test/altertab.test b/test/altertab.test index 6d8347ec1..9cc43e14d 100644 --- a/test/altertab.test +++ b/test/altertab.test @@ -981,4 +981,22 @@ do_catchsql_test 32.0 { ALTER TABLE t1 RENAME TO x; } {1 {error in trigger r1: no tables specified}} +# 2023-04-13 https://sqlite.org/forum/forumpost/ff3840145a +# +reset_db +do_execsql_test 33.0 { + CREATE TABLE t1(a TEXT); + INSERT INTO t1(a) VALUES('abc'),('def'),(NULL); + CREATE TABLE t2(b TEXT); + CREATE TRIGGER r3 AFTER INSERT ON t1 BEGIN + UPDATE t2 SET (b,a)=(SELECT 1) FROM t1 JOIN t2 ON (SELECT * FROM (SELECT a)); + END; +} +do_catchsql_test 33.1 { + ALTER TABLE t1 RENAME COLUMN a TO b; +} {1 {error in trigger r3 after rename: no such column: a}} +do_execsql_test 33.2 { + SELECT quote(a) FROM t1 ORDER BY +a; +} {NULL 'abc' 'def'} + finish_test diff --git a/test/analyze3.test b/test/analyze3.test index 7469c537c..c5d7a7cb1 100644 --- a/test/analyze3.test +++ b/test/analyze3.test @@ -736,4 +736,31 @@ do_execsql_test 7.2 { ANALYZE sqlite_master; } +# 2023-04-22 https://sqlite.org/forum/info/6c118daad0f1f5ef +# Case differences in the sqlite_stat4.idx field should not matter. +# +reset_db +do_execsql_test 8.0 { + CREATE TABLE t1(a PRIMARY KEY, v) WITHOUT ROWID; + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat1 VALUES('t1','t1','1 1'); + INSERT INTO sqlite_stat4 VALUES('t1','t1','1','0','0',X'021b76657273696f6e'); + INSERT INTO sqlite_stat4 VALUES('T1','T1','1','0','0',X'021b76657273696f6e'); + ANALYZE sqlite_schema; +} {} + +# 2023-05-03 https://sqlite.org/forum/forumpost/537d8ab118 +# Same index appears by two different names in the sqlite_stat4 table. +# +reset_db +do_execsql_test 8.1 { + CREATE TABLE t1(a INT PRIMARY KEY, b INT) WITHOUT ROWID; + ANALYZE sqlite_schema; + INSERT INTO sqlite_stat4 VALUES + ('t1','t1','1','2','2',X'03000103'), + ('t1','sqlite_autoindex_t1_1','1','2','2',X'03000103'); + ANALYZE sqlite_schema; + PRAGMA integrity_check; +} {ok} + finish_test diff --git a/test/analyzeE.test b/test/analyzeE.test index 733b79367..2547daa1c 100644 --- a/test/analyzeE.test +++ b/test/analyzeE.test @@ -239,4 +239,56 @@ do_execsql_test analyzeE-4.11 { SELECT * FROM t1 WHERE a<1900 AND c=123 } {/SCAN t1/} +# 2023-03-23 https://sqlite.org/forum/forumpost/dc4854437b +# +reset_db +do_execsql_test analyzeE-5.0 { + PRAGMA encoding = 'UTF-16'; + CREATE TABLE t0 (c1 TEXT); + INSERT INTO t0 VALUES (''); + CREATE INDEX i0 ON t0(c1); + ANALYZE; + SELECT * FROM t0 WHERE t0.c1 BETWEEN '' AND (ABS('')); +} {{}} + +# 2023-03-24 https://sqlite.org/forum/forumpost/bc39e531e5 +# +reset_db +do_execsql_test analyzeE-6.0 { + CREATE TABLE t1(x); + CREATE INDEX i1 ON t1(x,x,x,x,x||2); + CREATE INDEX i2 ON t1(1<2); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<1000) + INSERT INTO t1(x) SELECT x FROM c; + ANALYZE; +} {} +do_execsql_test analyzeE-6.1 { + SELECT count(*)>1 FROM sqlite_stat4 WHERE idx='i2' AND neq='1000 1'; +} 1 +do_execsql_test analyzeE-6.2 { + SELECT count(*) FROM sqlite_stat4 WHERE idx='i2' AND neq<>'1000 1'; +} 0 +do_execsql_test analyzeE-6.3 { + SELECT count(*)>1 FROM sqlite_stat4 WHERE idx='i1' AND neq='1 1 1 1 1 1'; +} 1 +do_execsql_test analyzeE-6.4 { + SELECT count(*) FROM sqlite_stat4 WHERE idx='i1' AND neq<>'1 1 1 1 1 1'; +} 0 + +# 2023-03-25 https://sqlite.org/forum/forumpost/5275207102 +# Correctly expand zeroblobs while processing STAT4 information +# during query planning. +# +reset_db +do_execsql_test analyzeE-7.0 { + CREATE TABLE t1(a TEXT COLLATE binary); + CREATE INDEX t1x ON t1(a); + INSERT INTO t1(a) VALUES(0),('apple'),(NULL),(''),('banana'); + ANALYZE; + SELECT format('(%s)',a) FROM t1 WHERE t1.a > CAST(zeroblob(5) AS TEXT); +} {(0) (apple) (banana)} +do_execsql_test analyzeE-7.1 { + SELECT format('(%s)',a) FROM t1 WHERE t1.a <= CAST(zeroblob(5) AS TEXT); +} {()} + finish_test diff --git a/test/autoindex1.test b/test/autoindex1.test index 4b290c796..08bd9f74e 100644 --- a/test/autoindex1.test +++ b/test/autoindex1.test @@ -194,6 +194,7 @@ do_eqp_test autoindex1-501 { QUERY PLAN |--SCAN t501 `--CORRELATED LIST SUBQUERY xxxxxx + |--BLOOM FILTER ON t502 (y=?) `--SEARCH t502 USING AUTOMATIC COVERING INDEX (y=?) } do_eqp_test autoindex1-502 { diff --git a/test/autoindex3.test b/test/autoindex3.test index 824a82973..3da7a7058 100644 --- a/test/autoindex3.test +++ b/test/autoindex3.test @@ -86,6 +86,7 @@ do_eqp_test 220 { } { QUERY PLAN |--SEARCH v USING INDEX ve (e>?) + |--BLOOM FILTER ON u (b=?) `--SEARCH u USING AUTOMATIC COVERING INDEX (b=?) } diff --git a/test/backup.test b/test/backup.test index 1572ded55..ad1a383a0 100644 --- a/test/backup.test +++ b/test/backup.test @@ -977,5 +977,28 @@ do_test backup-11.1 { sqlite3_backup B db1 main db2 temp B finish } {SQLITE_OK} +db1 close +db2 close + +#------------------------------------------------------------------------- +do_test backup-12.1 { + sqlite3 db1 :memory: + sqlite3 db2 :memory: + db1 eval { + PRAGMA page_size = 8192; + CREATE TABLE t1(x); + } + db2 eval { + PRAGMA page_size = 1024; + CREATE TABLE t2(x); + } + + sqlite3_backup B db1 main db2 temp + B step 100 + B finish +} {SQLITE_READONLY} + + + finish_test diff --git a/test/bloom1.test b/test/bloom1.test index af0ee62ac..151f364ae 100644 --- a/test/bloom1.test +++ b/test/bloom1.test @@ -133,6 +133,17 @@ do_execsql_test 3.3 { CREATE VIEW v1(y) AS SELECT DISTINCT x FROM t1; SELECT count(*) FROM t1, v1 WHERE x='b '; } 3 +do_eqp_test 3.4 { + SELECT count(*) FROM t1, v1 WHERE x='b '; +} { + QUERY PLAN + |--CO-ROUTINE v1 + | |--SCAN t1 + | `--USE TEMP B-TREE FOR DISTINCT + |--SCAN v1 + |--BLOOM FILTER ON t1 (x=?) + `--SEARCH t1 USING AUTOMATIC PARTIAL COVERING INDEX (x=?) +} # 2023-03-14 # https://sqlite.org/forum/forumpost/d47a0e8e3a @@ -173,4 +184,5 @@ do_execsql_test 4.4 { SELECT * FROM t0 LEFT JOIN t1 LEFT JOIN t2 ON (b NOTNULL)==(c IN ()) WHERE c; } {xyz {} 7.0} + finish_test diff --git a/test/corrupt2.test b/test/corrupt2.test index f97a526ef..96d28490a 100644 --- a/test/corrupt2.test +++ b/test/corrupt2.test @@ -99,7 +99,7 @@ do_test corrupt2-1.4 { # of MemPage.nFree catchsql {PRAGMA quick_check} db2 } {0 {{*** in database main *** -Page 1: free space corruption}}} +Tree 1 page 1: free space corruption}}} do_test corrupt2-1.5 { db2 close @@ -120,7 +120,7 @@ do_test corrupt2-1.5 { sqlite3 db2 corrupt.db catchsql {PRAGMA quick_check} db2 } {0 {{*** in database main *** -Page 1: free space corruption}}} +Tree 1 page 1: free space corruption}}} db2 close # Corrupt a database by having 2 indices of the same name: @@ -248,8 +248,8 @@ do_test corrupt2-5.1 { } set result } {{*** in database main *** -On tree page 2 cell 0: 2nd reference to page 10 -Page 4 is never used}} +Tree 11 page 2 cell 0: 2nd reference to page 10 +Page 4: never used}} db2 close @@ -591,7 +591,7 @@ do_test 14.2 { do_execsql_test 14.3 { PRAGMA integrity_check; } {{*** in database main *** -Main freelist: size is 3 but should be 2}} +Freelist: size is 3 but should be 2}} # Use 2 of the free pages on the free-list. # @@ -603,7 +603,7 @@ do_execsql_test 14.4 { do_execsql_test 14.5 { PRAGMA integrity_check; } {{*** in database main *** -Main freelist: size is 1 but should be 0}} +Freelist: size is 1 but should be 0}} finish_test diff --git a/test/corrupt3.test b/test/corrupt3.test index 7a2e174ea..691302f7a 100644 --- a/test/corrupt3.test +++ b/test/corrupt3.test @@ -82,7 +82,7 @@ do_test corrupt3-1.8 { PRAGMA integrity_check } } {0 {{*** in database main *** -On tree page 2 cell 0: 2nd reference to page 3}}} +Tree 2 page 2 cell 0: 2nd reference to page 3}}} # Change the pointer for the first page of the overflow # change to be a non-existant page. @@ -100,8 +100,8 @@ do_test corrupt3-1.10 { PRAGMA integrity_check } } {0 {{*** in database main *** -On tree page 2 cell 0: invalid page number 4 -Page 3 is never used}}} +Tree 2 page 2 cell 0: invalid page number 4 +Page 3: never used}}} do_test corrupt3-1.11 { db close hexio_write test.db 2044 [hexio_render_int32 0] @@ -115,7 +115,7 @@ do_test corrupt3-1.12 { PRAGMA integrity_check } } {0 {{*** in database main *** -On tree page 2 cell 0: overflow list length is 0 but should be 1 -Page 3 is never used}}} +Tree 2 page 2 cell 0: overflow list length is 0 but should be 1 +Page 3: never used}}} finish_test diff --git a/test/corrupt7.test b/test/corrupt7.test index aa66cc7ec..b62515b78 100644 --- a/test/corrupt7.test +++ b/test/corrupt7.test @@ -70,14 +70,14 @@ do_test corrupt7-2.1 { sqlite3 db test.db db eval {PRAGMA integrity_check(1)} } {{*** in database main *** -On tree page 2 cell 15: Offset 65457 out of range 945..1020}} +Tree 2 page 2 cell 15: Offset 65457 out of range 945..1020}} do_test corrupt7-2.2 { db close hexio_write test.db 1062 04 sqlite3 db test.db db eval {PRAGMA integrity_check(1)} } {{*** in database main *** -On tree page 2 cell 15: Offset 1201 out of range 945..1020}} +Tree 2 page 2 cell 15: Offset 1201 out of range 945..1020}} # The code path that was causing the buffer overrun that this test # case was checking for was removed. diff --git a/test/corruptC.test b/test/corruptC.test index a56abeec7..f5733a818 100644 --- a/test/corruptC.test +++ b/test/corruptC.test @@ -98,7 +98,7 @@ do_test corruptC-2.1 { sqlite3 db test.db catchsql {PRAGMA integrity_check} } {0 {{*** in database main *** -Page 3: free space corruption}}} +Tree 3 page 3: free space corruption}}} # test that a corrupt content offset size is handled (seed 5649) # @@ -165,7 +165,7 @@ do_test corruptC-2.5 { catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;} catchsql {PRAGMA integrity_check} } {0 {{*** in database main *** -On tree page 4 cell 19: Extends off end of page} {database disk image is malformed}}} +Tree 4 page 4 cell 19: Extends off end of page} {database disk image is malformed}}} # {0 {{*** in database main *** # Corruption detected in cell 710 on page 4 diff --git a/test/corruptD.test b/test/corruptD.test index eb6ccb3fc..c35388adf 100644 --- a/test/corruptD.test +++ b/test/corruptD.test @@ -113,7 +113,7 @@ do_test corruptD-1.1.1 { hexio_write test.db [expr 1024+1] FFFF catchsql { PRAGMA quick_check } } {0 {{*** in database main *** -Page 2: free space corruption}}} +Tree 2 page 2: free space corruption}}} do_test corruptD-1.1.2 { incr_change_counter hexio_write test.db [expr 1024+1] [hexio_render_int32 1021] diff --git a/test/corruptI.test b/test/corruptI.test index 12ce44654..65ef37625 100644 --- a/test/corruptI.test +++ b/test/corruptI.test @@ -123,18 +123,13 @@ do_execsql_test 4.0 { set root [db one {SELECT rootpage FROM sqlite_master}] set offset [expr ($root-1) * 65536] -ifcapable oversize_cell_check { - set res {1 {database disk image is malformed}} -} else { - set res {0 {}} -} do_test 4.1 { db close hexio_write test.db [expr $offset + 8 + 2] 0000 hexio_write test.db [expr $offset + 5] 0000 sqlite3 db test.db catchsql { DELETE FROM t1 WHERE a=0 } -} $res +} {1 {database disk image is malformed}} #------------------------------------------------------------------------- diff --git a/test/countofview.test b/test/countofview.test index 7e94fa5ee..cea6fa427 100644 --- a/test/countofview.test +++ b/test/countofview.test @@ -76,4 +76,32 @@ do_execsql_test 4.1 { SELECT count(*) FROM t3 ORDER BY sum(a); } 1 +# 2023-03-31 dbsqlfuzz 6a107e3055bd22afab31cfddabc2d9d54fcbaf69 +# Having clauses should disqualify count-of-view +# +reset_db +do_execsql_test 5.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1 VALUES(1,'one'),(4,'four'); + CREATE TABLE t2(c INTEGER PRIMARY KEY, d TEXT); + INSERT INTO t2 VALUES(2,'two'),(5,'five'); + CREATE VIEW t3 AS SELECT a, b FROM t1 UNION ALL SELECT c, d FROM t2; + SELECT count(*) FROM t3 HAVING count(*)>0; +} 4 +do_execsql_test 5.2 { + SELECT count(*) FROM t3 HAVING count(*)>5; +} {} +do_execsql_test 5.3 { + SELECT count(*) FROM t3 HAVING max(b)>'mmm'; +} 4 +do_execsql_test 5.4 { + SELECT count(*) FROM t3 HAVING min(b)>'mmm'; +} {} +do_execsql_test 5.5 { + SELECT count(*) FROM ( + SELECT a, max(b) FROM t1 HAVING a<100 UNION ALL SELECT c, d FROM t2 + ) +} 3 + + finish_test diff --git a/test/cursorhint.test b/test/cursorhint.test index a3397b867..d1bd4e8e7 100644 --- a/test/cursorhint.test +++ b/test/cursorhint.test @@ -159,4 +159,56 @@ do_test 4.6desc { } } {AND(AND(EQ(c0,22),GE(c1,10)),LE(c1,20))} +# 2023-03-24 https://sqlite.org/forum/forumpost/591006b1cc +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(x TEXT PRIMARY KEY) WITHOUT ROWID; + CREATE VIEW t2 AS SELECT 0 FROM t1 WHERE x>='a' OR x='1'; + SELECT * FROM t2 RIGHT JOIN t1 ON true; +} +# Additional test case from https://sqlite.org/forum/forumpost/d34ad68c36?t=c +# which is a different way to acces the same problem. +# +do_execsql_test 5.1 { + CREATE TABLE v1 (c1, PRIMARY KEY( c1 )) WITHOUT ROWID; + CREATE VIEW v2 AS SELECT 0 FROM v1 WHERE c1 IS '' OR c1 > ''; + CREATE VIEW v3 AS SELECT 0 FROM v2 JOIN (v2 RIGHT JOIN v1); + CREATE VIEW v4 AS SELECT 0 FROM v3, v3; + SELECT * FROM v3 JOIN v3 AS a0, v4 AS a1, v4 AS a2, v3 AS a3, + v3 AS a4, v4 AS a5 + ORDER BY 1; +} + +# 2023-04-10 https://sqlite.org/forum/forumpost/0b53708c95 +# +do_execsql_test 6.0 { + CREATE TABLE t6(a TEXT UNIQUE, b TEXT); + INSERT INTO t6(a,b) VALUES('uvw','xyz'),('abc','def'); + WITH v1(a) AS (SELECT a COLLATE NOCASE FROM t6) + SELECT v1.a, count(*) FROM t6 LEFT JOIN v1 ON true + GROUP BY 1 + HAVING (SELECT true FROM t6 AS aa LEFT JOIN t6 AS bb ON length(v1.a)>5); +} {abc 2 uvw 2} + + +# 2023-05-04 https://sqlite.org/forum/forumpost/29a47cf6d1 +# +# codeCursorHint() should not walk expressions that have been optimized +# out and converted to TRUE or FALSE. This only comes up when compiling +# with SQLITE_ENABLE_CURSOR_HINTS +# +reset_db +do_execsql_test 7.1 { + CREATE TABLE t1(a INT PRIMARY KEY) WITHOUT ROWID; + CREATE TABLE t2(b INT PRIMARY KEY) WITHOUT ROWID; + CREATE TABLE t3(c INT PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t1(a) VALUES(1),(2); + INSERT INTO t2(b) VALUES(4),(8); + INSERT INTO t3(c) VALUES(16),(32); + CREATE VIEW v4(d) AS SELECT c FROM t3; + SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN v4 ON (d IS NULL); +} {} + + finish_test diff --git a/test/date.test b/test/date.test index 62233ad8f..3e9318189 100644 --- a/test/date.test +++ b/test/date.test @@ -544,4 +544,10 @@ datetest 17.7 {datetime(38,'start of year')} {-4712-01-01 00:00:00} # datetest 18.1 {strftime('%f',1.234,'unixepoch','localtime')} {01.234} +# 2023-04 The 'subsecond' (or 'subsec') modifier alters resolutions +# to at least milliseconds. Added for release 3.42.0 . +datetest 18.2 {unixepoch('1970-01-01T00:00:00.1', 'subsec')} {0.1} +datetest 18.3 {unixepoch('1970-01-01T00:00:00.2', 'subsecond')} {0.2} +datetest 18.4 {julianday('-4713-11-24 13:40:48.864', 'subsec')} {0.07001} +datetest 18.5 {typeof(unixepoch('now', 'subsecond'))} {real} finish_test diff --git a/test/fkey1.test b/test/fkey1.test index db93be501..46e7f64a1 100644 --- a/test/fkey1.test +++ b/test/fkey1.test @@ -272,4 +272,15 @@ do_catchsql_test 8.3 { REINDEX; } {1 {database disk image is malformed}} +# 2023-04-13 https://bugs.chromium.org/p/chromium/issues/detail?id=1405220 +# Avoid double-de-quoting of table names when processing foreign keys. +# +reset_db +do_execsql_test 9.1 { + PRAGMA foreign_keys = ON; + CREATE TABLE """1"("""2", """3" PRIMARY KEY); + CREATE TABLE """4"("""5" REFERENCES """1" ON DELETE RESTRICT); + DELETE FROM """1"; +} + finish_test diff --git a/test/fts3join.test b/test/fts3join.test index 3333b1ab4..cbd08b63f 100644 --- a/test/fts3join.test +++ b/test/fts3join.test @@ -100,6 +100,7 @@ do_eqp_test 4.2 { |--MATERIALIZE rr | `--SCAN ft4 VIRTUAL TABLE INDEX 3: |--SCAN t4 + |--BLOOM FILTER ON rr (docid=?) `--SEARCH rr USING AUTOMATIC COVERING INDEX (docid=?) LEFT-JOIN } diff --git a/test/func8.test b/test/func8.test new file mode 100644 index 000000000..348dfb7f6 --- /dev/null +++ b/test/func8.test @@ -0,0 +1,64 @@ +# 2023-03-17 +# +# 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. +# +#************************************************************************* +# +# Test cases for SQL functions with names that are the same as join +# keywords: CROSS FULL INNER LEFT NATURAL OUTER RIGHT +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +proc joinx {args} {return [join $args -]} +db func cross {joinx cross} +db func full {joinx full} +db func inner {joinx inner} +db func left {joinx left} +db func natural {joinx natural} +db func outer {joinx outer} +db func right {joinx right} +do_execsql_test func8-100 { + CREATE TABLE cross(cross,full,inner,left,natural,outer,right); + CREATE TABLE full(cross,full,inner,left,natural,outer,right); + CREATE TABLE inner(cross,full,inner,left,natural,outer,right); + CREATE TABLE left(cross,full,inner,left,natural,outer,right); + CREATE TABLE natural(cross,full,inner,left,natural,outer,right); + CREATE TABLE outer(cross,full,inner,left,natural,outer,right); + CREATE TABLE right(cross,full,inner,left,natural,outer,right); + INSERT INTO cross VALUES(1,2,3,4,5,6,7); + INSERT INTO full VALUES(1,2,3,4,5,6,7); + INSERT INTO inner VALUES(1,2,3,4,5,6,7); + INSERT INTO left VALUES(1,2,3,4,5,6,7); + INSERT INTO natural VALUES(1,2,3,4,5,6,7); + INSERT INTO outer VALUES(1,2,3,4,5,6,7); + INSERT INTO right VALUES(1,2,3,4,5,6,7); +} +do_execsql_test func8-110 { + SELECT cross(cross,full,inner,left,natural,outer,right) FROM cross; +} cross-1-2-3-4-5-6-7 +do_execsql_test func8-120 { + SELECT full(cross,full,inner,left,natural,outer,right) FROM full; +} full-1-2-3-4-5-6-7 +do_execsql_test func8-130 { + SELECT inner(cross,full,inner,left,natural,outer,right) FROM inner; +} inner-1-2-3-4-5-6-7 +do_execsql_test func8-140 { + SELECT left(cross,full,inner,left,natural,outer,right) FROM left; +} left-1-2-3-4-5-6-7 +do_execsql_test func8-150 { + SELECT natural(cross,full,inner,left,natural,outer,right) FROM natural; +} natural-1-2-3-4-5-6-7 +do_execsql_test func8-160 { + SELECT outer(cross,full,inner,left,natural,outer,right) FROM outer; +} outer-1-2-3-4-5-6-7 +do_execsql_test func8-170 { + SELECT right(cross,full,inner,left,natural,outer,right) FROM right; +} right-1-2-3-4-5-6-7 + +finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index ee7749d96..59bddc96a 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -1001,12 +1001,14 @@ static int recoverSqlCb(void *pCtx, const char *zSql){ */ static int recoverDatabase(sqlite3 *db){ int rc; /* Return code from this routine */ + const char *zRecoveryDb = ""; /* Name of "recovery" database */ const char *zLAF = "lost_and_found"; /* Name of "lost_and_found" table */ int bFreelist = 1; /* True to scan the freelist */ int bRowids = 1; /* True to restore ROWID values */ - sqlite3_recover *p; /* The recovery object */ + sqlite3_recover *p = 0; /* The recovery object */ p = sqlite3_recover_init_sql(db, "main", recoverSqlCb, 0); + sqlite3_recover_config(p, 789, (void*)zRecoveryDb); sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); @@ -1038,7 +1040,7 @@ static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){ printf("RUNNING-SQL: [%s]\n", zSql); fflush(stdout); } - (*pBtsFlags) &= ~BTS_BADPRAGMA; + (*pBtsFlags) &= BTS_BADPRAGMA; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc==SQLITE_OK ){ int nRow = 0; @@ -1181,6 +1183,7 @@ int runCombinedDbSqlInput( sqlite3_free(aDb); return 1; } + sqlite3_db_config(cx.db, SQLITE_DBCONFIG_STMT_SCANSTATUS, 1, 0); if( bVdbeDebug ){ sqlite3_exec(cx.db, "PRAGMA vdbe_debug=ON", 0, 0, 0); } @@ -1768,6 +1771,8 @@ static void showHelp(void){ " --timeout N Maximum time for any one test in N millseconds\n" " -v|--verbose Increased output. Repeat for more output.\n" " --vdbe-debug Activate VDBE debugging.\n" +" --wait N Wait N seconds before continuing - useful for\n" +" attaching an MSVC debugging.\n" ); } @@ -1980,6 +1985,22 @@ int main(int argc, char **argv){ } return 0; }else + if( strcmp(z,"wait")==0 ){ + int iDelay; + if( i>=argc-1 ) fatalError("missing arguments on %s", argv[i]); + iDelay = integerValue(argv[++i]); + printf("Waiting %d seconds:", iDelay); + fflush(stdout); + while( 1 /*exit-by-break*/ ){ + sqlite3_sleep(1000); + iDelay--; + if( iDelay<=0 ) break; + printf(" %d", iDelay); + fflush(stdout); + } + printf("\n"); + fflush(stdout); + }else if( strcmp(z,"is-dbsql")==0 ){ i++; for(i++; i=2 ){ char *zSql = sqlite3_expanded_sql(pTestStmt); - printf("invariant-sql #%d:\n%s\n", iCnt, zSql); + printf("invariant-sql row=%d #%d:\n%s\n", iRow, iCnt, zSql); sqlite3_free(zSql); } while( (rc = sqlite3_step(pTestStmt))==SQLITE_ROW ){ @@ -115,6 +115,8 @@ int fuzz_invariant( if( rc==SQLITE_DONE ){ /* No matching output row found */ sqlite3_stmt *pCk = 0; + int iOrigRSO; + /* This is not a fault if the database file is corrupt, because anything ** can happen with a corrupt database file */ @@ -124,6 +126,12 @@ int fuzz_invariant( sqlite3_finalize(pTestStmt); return rc; } + if( eVerbosity>=2 ){ + char *zSql = sqlite3_expanded_sql(pCk); + printf("invariant-validity-check #1:\n%s\n", zSql); + sqlite3_free(zSql); + } + rc = sqlite3_step(pCk); if( rc!=SQLITE_ROW || sqlite3_column_text(pCk, 0)==0 @@ -136,28 +144,29 @@ int fuzz_invariant( } sqlite3_finalize(pCk); - if( sqlite3_strlike("%group%by%",sqlite3_sql(pStmt),0)==0 ){ - /* - ** If there is a GROUP BY clause, it might not cover every term in the - ** output. And then non-covered terms can take on a value from any - ** row in the result set. This can cause differing answers. - */ - goto not_a_fault; + /* + ** If inverting the scan order also results in a miss, assume that the + ** query is ambiguous and do not report a fault. + */ + sqlite3_db_config(db, SQLITE_DBCONFIG_REVERSE_SCANORDER, -1, &iOrigRSO); + sqlite3_db_config(db, SQLITE_DBCONFIG_REVERSE_SCANORDER, !iOrigRSO, 0); + sqlite3_prepare_v2(db, sqlite3_sql(pStmt), -1, &pCk, 0); + sqlite3_db_config(db, SQLITE_DBCONFIG_REVERSE_SCANORDER, iOrigRSO, 0); + if( eVerbosity>=2 ){ + char *zSql = sqlite3_expanded_sql(pCk); + printf("invariant-validity-check #2:\n%s\n", zSql); + sqlite3_free(zSql); } - - if( sqlite3_strlike("%limit%)%order%by%", sqlite3_sql(pTestStmt),0)==0 ){ - /* crash-89bd6a6f8c6166e9a4c5f47b3e70b225f69b76c6 - ** Original statement is: - ** - ** SELECT a,b,c* FROM t1 LIMIT 1%5<4 - ** - ** When running: - ** - ** SELECT * FROM (...) ORDER BY 1 - ** - ** A different subset of the rows come out - */ - goto not_a_fault; + while( (rc = sqlite3_step(pCk))==SQLITE_ROW ){ + for(i=0; i=nCol ) break; + } + sqlite3_finalize(pCk); + if( rc==SQLITE_DONE ){ + sqlite3_finalize(pTestStmt); + return SQLITE_DONE; } /* The original sameValue() comparison assumed a collating sequence @@ -169,6 +178,12 @@ int fuzz_invariant( "SELECT ?1=?2 OR ?1=?2 COLLATE nocase OR ?1=?2 COLLATE rtrim", -1, &pCk, 0); if( rc==SQLITE_OK ){ + if( eVerbosity>=2 ){ + char *zSql = sqlite3_expanded_sql(pCk); + printf("invariant-validity-check #3:\n%s\n", zSql); + sqlite3_free(zSql); + } + sqlite3_reset(pTestStmt); while( (rc = sqlite3_step(pTestStmt))==SQLITE_ROW ){ for(i=0; i=2 ){ + char *zSql = sqlite3_expanded_sql(pCk); + printf("invariant-validity-check #4:\n%s\n", zSql); + sqlite3_free(zSql); + } sqlite3_bind_pointer(pCk, 1, pStmt, "stmt-pointer", 0); rc = sqlite3_step(pCk); } diff --git a/test/in.test b/test/in.test index 716c17f59..601c7e3b4 100644 --- a/test/in.test +++ b/test/in.test @@ -829,7 +829,35 @@ do_execsql_test in-22.4 { SELECT * FROM t1 WHERE x IN ((((((SELECT a FROM t2)))))); } {2 200 4 400 6 600} - - +# 2023-04-04 https://sqlite.org/forum/forumpost/dc16ec63d3 +# Faulty assert() statement in the IN optimization. +# +do_execsql_test in-23.0 { + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(a TEXT, b INT); + INSERT INTO t4(a,b) VALUES('abc',0),('ABC',1),('def',2); + CREATE INDEX t4x ON t4(a, +a COLLATE NOCASE); + SELECT a0.a, group_concat(a1.a) AS b + FROM t4 AS a0 JOIN t4 AS a1 + GROUP BY a0.a + HAVING (SELECT sum( (a1.a == +a0.a COLLATE NOCASE) IN (SELECT b FROM t4))); +} {ABC abc,ABC,def abc abc,ABC,def def abc,ABC,def} +do_execsql_test in-23.0-b { + SELECT a0.a, group_concat(a1.a) AS b + FROM t4 AS a0 JOIN t4 AS a1 + GROUP BY a0.a + HAVING (SELECT sum( (a1.a GLOB +a0.a COLLATE NOCASE) IN (SELECT b FROM t4))); +} {ABC abc,ABC,def abc abc,ABC,def def abc,ABC,def} +# +# Follow-up forum/forumpost/0713a16a44 +# +do_execsql_test in-23.1 { + CREATE VIEW t5 AS + SELECT 1 AS b + WHERE (SELECT count(0=NOT+a COLLATE NOCASE IN (SELECT 0)) + FROM t4 + GROUP BY a); + SELECT * FROM t5; +} 1 finish_test diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 7d94806cd..51ef73bbf 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -591,6 +591,29 @@ do_execsql_test indexexpr1-2140 { SELECT b FROM t1; } 400 - +# 2023-04-18 Forum post https://sqlite.org/forum/forumpost/f34e32d120 from +# Alexis King. +# +# This problem originates at check-in b9190d3da70c4171 (2022-11-25). +# A similar problem arose on 2023-03-04 at +# https://sqlite.org/forum/forumpost/a68313d054 and was fixed at +# check-in e06973876993926f. See the test case tkt-99378-400. +# +reset_db +do_execsql_test indexexpr1-2200 { + CREATE TABLE t1(id INTEGER PRIMARY KEY, tag INT); + INSERT INTO t1 VALUES (0, 7), (1, 8); + CREATE TABLE t2(type INT, t1_id INT, value INT); + INSERT INTO t2 VALUES (0, 0, 100), (0, 1, 101); + CREATE INDEX t1x ON t1(-tag); + SELECT u.tag, v.max_value + FROM (SELECT tag FROM t1 GROUP BY -tag) u + JOIN (SELECT t1.tag AS "tag", t2.type AS "type", + MAX(t2.value) AS "max_value" + FROM t1 + JOIN t2 ON t2.t1_id = t1.id + GROUP BY t2.type, t1.tag + ) v ON v.type = 0 AND v.tag = u.tag; +} {7 100 8 101} finish_test diff --git a/test/indexexpr2.test b/test/indexexpr2.test index 8c1171e03..4c21421e8 100644 --- a/test/indexexpr2.test +++ b/test/indexexpr2.test @@ -372,4 +372,57 @@ foreach {tn expr} { " {1 1} } +# 2023-03-24 https://sqlite.org/forum/forumpost/79cf371080 +# +reset_db +do_execsql_test 9.0 { + CREATE TABLE t1(a INT, b INT); + CREATE INDEX t1x ON t1(a, abs(b)); + CREATE TABLE t2(c INT, d INT); + INSERT INTO t1(a,b) VALUES(4,4),(5,-5),(5,20),(6,6); + INSERT INTO t2(c,d) VALUES(100,1),(200,1),(300,2); + SELECT *, + (SELECT max(c+abs(b)) FROM t2 GROUP BY d ORDER BY d LIMIT 1) AS subq + FROM t1 WHERE a=5; +} {5 -5 205 5 20 220} + +# 2023-04-03 https://sqlite.org/forum/forumpost/44270909bb +# and https://sqlite.org/forum/forumpost/e45108732c which are the +# same problem, namely the failure to omit the EP_Collate property +# from an expression node when changing it from TK_COLLATE into +# TK_AGG_COLUMN because it resolves to an indexed expression. +# +reset_db +do_execsql_test 10.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + CREATE INDEX t1x ON t1 (b, +b COLLATE NOCASE); + INSERT INTO t1(a,b) VALUES(1,'abcde'); + SELECT * FROM t1 AS a0 + WHERE (SELECT count(a0.b=+a0.b COLLATE NOCASE IN (b)) FROM t1 GROUP BY 2.5) + ORDER BY a0.b; +} {1 abcde} +do_execsql_test 10.1 { + CREATE TABLE t2(a TEXT); + INSERT INTO t2 VALUES('alice'),('bob'),('cindy'),('david'); + CREATE INDEX t2x ON t2 (+a COLLATE NOCASE); + SELECT count(+a COLLATE NOCASE IN (SELECT 1)) AS x + FROM t2 + GROUP BY SUBSTR(0,0); +} 4 + +# 2023-04-03 https://sqlite.org/forum/forumpost/409ebc7368 +# When a generated column appears in both an outer and an inner loop +# (that is to say, the same table is used in both loops) and the +# generated column is indexed and it is used inside an aggregate function, +# make sure that the terms resolve to the correct aggregate. +# +do_execsql_test 11.0 { + CREATE TABLE t3 (a INT, b AS (-a)); + CREATE INDEX t3x ON t3(b, a); + INSERT INTO t3(a) VALUES(44); + SELECT * FROM t3 AS a0 + WHERE (SELECT sum(-a0.a=b) FROM t3 GROUP BY b) + GROUP BY b; +} {44 -44} + finish_test diff --git a/test/join.test b/test/join.test index f0ac0be8b..aa526aeb2 100644 --- a/test/join.test +++ b/test/join.test @@ -1038,18 +1038,56 @@ do_execsql_test join-22.10 { } {11} # 2019-12-22 ticket 7929c1efb2d67e98 +# Verification of testtag-20230227a +# +# 2023-02-27 https://sqlite.org/forum/forumpost/422e635f3beafbf6 +# Verification of testtag-20230227a, testtag-20230227b, and testtag-20230227c # reset_db ifcapable vtab { -do_execsql_test join-23.10 { - CREATE TABLE t0(c0); - INSERT INTO t0(c0) VALUES(123); - CREATE VIEW v0(c0) AS SELECT 0 GROUP BY 1; - SELECT t0.c0, v0.c0, vt0.name - FROM v0, t0 LEFT JOIN pragma_table_info('t0') AS vt0 - ON vt0.name LIKE 'c0' - WHERE v0.c0 == 0; -} {123 0 c0} + do_execsql_test join-23.10 { + CREATE TABLE t0(c0); + INSERT INTO t0(c0) VALUES(123); + CREATE VIEW v0(c0) AS SELECT 0 GROUP BY 1; + SELECT t0.c0, v0.c0, vt0.name + FROM v0, t0 LEFT JOIN pragma_table_info('t0') AS vt0 + ON vt0.name LIKE 'c0' + WHERE v0.c0 == 0; + } {123 0 c0} + do_execsql_test join-23.20 { + CREATE TABLE a(value TEXT); + INSERT INTO a(value) SELECT value FROM json_each('["a", "b", null]'); + CREATE TABLE b(value TEXT); + INSERT INTO b(value) SELECT value FROM json_each('["a", "c", null]'); + SELECT a.value, b.value FROM a RIGHT JOIN b ON a.value = b.value; + } {a a {} c {} {}} + do_execsql_test join-23.21 { + SELECT a.value, b.value FROM b LEFT JOIN a ON a.value = b.value; + } {a a {} c {} {}} + do_execsql_test join-23.22 { + SELECT a.value, b.value + FROM json_each('["a", "c", null]') AS b + LEFT JOIN + json_each('["a", "b", null]') AS a ON a.value = b.value; + } {a a {} c {} {}} + do_execsql_test join-23.23 { + SELECT a.value, b.value + FROM json_each('["a", "b", null]') AS a + RIGHT JOIN + json_each('["a", "c", null]') AS b ON a.value = b.value; + } {a a {} c {} {}} + do_execsql_test join-23.24 { + SELECT a.value, b.value + FROM json_each('["a", "b", null]') AS a + RIGHT JOIN + b ON a.value = b.value; + } {a a {} c {} {}} + do_execsql_test join-23.25 { + SELECT a.value, b.value + FROM a + RIGHT JOIN + json_each('["a", "c", null]') AS b ON a.value = b.value; + } {a a {} c {} {}} } #------------------------------------------------------------------------- @@ -1207,4 +1245,48 @@ do_eqp_test join-28.2 { # |--SCAN t4 # `--SEARCH t3 USING AUTOMATIC COVERING INDEX (a=?) + +# 2023-05-01 https://sqlite.org/forum/forumpost/96cd4a7e9e +# +reset_db +db null NULL +do_execsql_test join-29.1 { + CREATE TABLE t0(a INT); INSERT INTO t0(a) VALUES (1); + CREATE TABLE t1(b INT); INSERT INTO t1(b) VALUES (2); + CREATE VIEW v2(c) AS SELECT 3 FROM t1; + SELECT * FROM t1 JOIN v2 ON 0 FULL OUTER JOIN t0 ON true; +} {NULL NULL 1} +do_execsql_test join-29.2 { + SELECT * FROM t1 JOIN v2 ON 1=0 FULL OUTER JOIN t0 ON true; +} {NULL NULL 1} +do_execsql_test join-29.3 { + SELECT * FROM t1 JOIN v2 ON false FULL OUTER JOIN t0 ON true; +} {NULL NULL 1} + +# 2023-05-11 https://sqlite.org/forum/forumpost/49f2c7f690 +# Verify that omit-noop-join optimization does not apply if the table +# to be omitted has an inner-join constraint and there is a RIGHT JOIN +# anywhere in the query. +# +reset_db +db null NULL +do_execsql_test join-30.1 { + CREATE TABLE t0(z INT); INSERT INTO t0 VALUES(1),(2); + CREATE TABLE t1(a INT); INSERT INTO t1 VALUES(1); + CREATE TABLE t2(b INT); INSERT INTO t2 VALUES(2); + CREATE TABLE t3(c INT, d INT); INSERT INTO t3 VALUES(3,4); + CREATE TABLE t4(e INT); INSERT INTO t4 VALUES(5); + CREATE VIEW v5(x,y) AS SELECT c, d FROM t3 LEFT JOIN t4 ON false; +} +do_execsql_test join-30.2 { + SELECT DISTINCT a, b + FROM t1 RIGHT JOIN t2 ON a=b LEFT JOIN v5 ON false + WHERE x <= y; +} {} +do_execsql_test join-30.3 { + SELECT DISTINCT a, b + FROM t0 JOIN t1 ON z=a RIGHT JOIN t2 ON a=b LEFT JOIN v5 ON false + WHERE x <= y; +} {} + finish_test diff --git a/test/join8.test b/test/join8.test index 1140c104f..fc50df32f 100644 --- a/test/join8.test +++ b/test/join8.test @@ -159,6 +159,7 @@ do_catchsql_test join8-5000 { } {0 {- -}} # 2022-04-29 dbsqlfuzz 19f1102a70cf966ab249de56d944fc20dbebcfcf +# Verification of testtag-20230227b and testtag-20230227c # reset_db do_execsql_test join8-6000 { @@ -181,6 +182,16 @@ do_execsql_test join8-6020 { SELECT value, t1.* FROM json_each('17') NATURAL RIGHT JOIN t1 WHERE (a,b) IN (SELECT rowid, b FROM t1); } {17 1 2} +do_execsql_test join8-6021 { + SELECT value, t1.* FROM json_each('null') NATURAL RIGHT JOIN t1 + WHERE (a,b) IN (SELECT rowid, b FROM t1); +} {{} 1 2} +do_execsql_test join8-6022 { + CREATE TABLE a(key TEXT); + INSERT INTO a(key) VALUES('a'),('b'); + SELECT quote(a.key), b.value + FROM a RIGHT JOIN json_each('["a","c"]') AS b ON a.key=b.value; +} {'a' a NULL c} # Bloom filter usage by RIGHT and FULL JOIN # diff --git a/test/joinH.test b/test/joinH.test index 1d5f66afa..78d155629 100644 --- a/test/joinH.test +++ b/test/joinH.test @@ -89,5 +89,35 @@ do_execsql_test 4.4 { SELECT (d IS NULL) FROM t1 RIGHT JOIN t2 ON (j=33); } {1} +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t0(w); + CREATE TABLE t1(x); + CREATE TABLE t2(y); + CREATE TABLE t3(z); + INSERT INTO t3 VALUES('t3val'); +} + +do_execsql_test 5.1 { + SELECT * FROM t1 INNER JOIN t2 ON (0) RIGHT OUTER JOIN t3; +} {{} {} t3val} + +do_execsql_test 5.2 { + SELECT * FROM t1 INNER JOIN t2 ON (0) FULL OUTER JOIN t3; +} {{} {} t3val} + +do_execsql_test 5.3 { + SELECT * FROM t3 LEFT JOIN t2 ON (0); +} {t3val {}} + +do_execsql_test 5.4 { + SELECT * FROM t0 RIGHT JOIN t1 INNER JOIN t2 ON (0) RIGHT JOIN t3 +} {{} {} {} t3val} + +do_execsql_test 5.5 { + SELECT * FROM t0 RIGHT JOIN t1 INNER JOIN t2 ON (0) +} {} finish_test diff --git a/test/json/README.md b/test/json/README.md new file mode 100644 index 000000000..6a1611492 --- /dev/null +++ b/test/json/README.md @@ -0,0 +1,27 @@ +The files in this subdirectory are used to help measure the performance +of the SQLite JSON parser. + +# 1.0 Prerequisites + + 1. Valgrind + + 2. Fossil + +# 2.0 Setup + + 1. Run: "`tclsh json-generator.tcl | sqlite3 json100mb.db`" to create + the 100 megabyte test database. Do this so that the "json100mb.db" + file lands in the directory from which you will run tests, not in + the test/json subdirectory of the source tree. + + 2. Build the baseline sqlite3.c file. ("`make sqlite3.c`") + + 3. Run "`sh json-speed-check-1.sh trunk`". This creates the baseline + profile in "jout-trunk.txt". + +# 3.0 Testing + + 1. Build the sqlite3.c to be tested. + + 2. Run "`sh json-speed-check-1.sh x1`". The profile output will appear + in jout-x1.txt. Substitute any label you want in place of "x1". diff --git a/test/json/json-generator.tcl b/test/json/json-generator.tcl new file mode 100644 index 000000000..d499bc730 --- /dev/null +++ b/test/json/json-generator.tcl @@ -0,0 +1,401 @@ +#!/usr/bin/tclsh +# +# Generate SQL that will populate an SQLite database with about 100 megabytes +# of pseudo-random JSON text. +# +# tclsh json-generator.tcl | sqlite3 json110mb.db +# +# srand() is used to initialize the random seed so that the same JSON +# is generated for every run. +# +expr srand(12345678) +set wordlist { + ability able abroad access account act + action active actor add address adept + adroit advance advice affect age ageless + agency agent agile agree air airfare + airline airport alert almond alpha always + amend amount amplify analyst anchor angel + angelic angle ankle annual answer antique + anybody anyhow appeal apple apricot apt + area argon arm army arrival arsenic + art artful article arugula aside ask + aspect assist assume atom atone attempt + author autumn average avocado award awl + azure back bacon bag bagel bake + baker balance ball balloon bamboo banana + band banjo bank barium base basil + basin basis basket bass bat bath + battery beach beak bean bear bearcub + beauty beef beet beige being bell + belly belt bench bend benefit best + beta better beyond bicycle bid big + bike bill bird biscuit bismuth bisque + bit black blank blest blind bliss + block bloom blue board boat body + bokchoy bone bonus book bookish boot + border boron boss bossy bottle bottom + bow bowl bowtie box brain brainy + branch brave bravely bread break breath + breezy brick bridge brie brief briefly + bright broad broil bromine bronze brother + brow brown brush buddy budget buffalo + bug bugle bull bunch burger burly + burrito bus busy butter button buy + buyer byte cab cabbage cabinet cable + cadet cadmium caesium cake calcium caliper + call caller calm calmly camera camp + can canary cancel candle candy cap + capable caper capital captain car carbon + card care career careful carp carpet + carrot carry case cash cassava casual + cat catch catfish catsear catsup cause + cave celery cell century chain chair + chalk chance change channel chapter chard + charge charity chart check cheddar cheery + cheese chicken chicory chiffon child chin + chip chives choice chowder chum church + circle city claim clam class classic + classy clay clean cleaner clear clearly + clerk click client climate clock clorine + closet clothes cloud clown club clue + cluster coach coast coat cobbler cobolt + cod code coffee colby cold collar + college comb combine comet comfort command + comment common company complex concept concern + concert conduit consist contact contest context + control convert cook cookie copilot copper + copy coral cordial corn corner corny + correct cost count counter country county + couple courage course court cover cow + cowbird crab crack craft crash crazy + cream credit creek cress crevice crew + crimson croaker crop cross crowd cube + cuckoo cuisine culture cup current curve + cut cyan cycle dagger daily dance + dare darter data date day daylily + deal dear dearly debate debit decade + decimal deep deft deftly degree delay + deluxe deposit depth design desk detail + device dew diamond diet dig dill + dinner dip direct dirt dish disk + display diver divide divine doctor dodger + donut door dot double dough draft + drag dragon drama draw drawer drawing + dream drill drink drive driver drop + drum dry dryer drywall duck due + dump dusk dust duty dye eagle + ear earring earth ease east easy + eat economy edge editor eel effect + effort egg eight elbow elegant element + elf elk email emerald employ end + endive endless energy engine enjoy enter + entry equal equip error escape essay + eternal evening event exam example excuse + exit expert extent extreme eye face + fact factor factual fail failure fair + fajita fall family fan fang farm + farmer fat fault feature feed feel + feeling fench fennel festive few fiber + field fig figure file fill film + filter final finance finding finger finish + fire fish fishing fit fitting five + fix flier flight floor floral florine + flour flow flower fly flying focus + fold folding food foot force forest + forever forgive form formal format fortune + forum frame free freedom freely fresh + friend frog front fruit fuchsia fuel + fun funny future gain galaxy gallium + game gamma gap garage garden garlic + gas gate gather gauge gear gem + gene general gentle gently gherkin ghost + gift give glad glass gleeful glossy + glove glue goal goat goby gold + goldeye golf good gouda goulash gourd + grab grace grade gram grand grape + grapes grass gravy gray great green + grits grocery ground group grouper grout + growth guard guave guess guest guide + guitar gumbo guppy habit hacksaw haddock + hafnium hagfish hair half halibut hall + hammer hand handle handy hanger happy + hat havarti hay haybale head health + healthy hearing heart hearty heat heavy + heel height helium hello help helpful + herald herring hide high highly highway + hill hip hipster hire history hit + hoki hold hole holiday holly home + honest honey hook hope hopeful horizon + horn horse host hotel hour house + housing human humane humor hunt hurry + ice icecube icefish icy idea ideal + image impact impress inch income indigo + initial inkpen insect inside intense invite + iodine iridium iron island issue item + ivory jacket jargon javelin jello jelly + jewel job jocund join joint joke + jovial joy joyful joyous judge juice + jump junior jury just justice kale + keel keep kelp ketchup key keyhole + keyway khaki kick kid kidney kiloohm + kind kindly king kitchen kite kiwi + knee knife krill krypton kumquat lab + lace lack ladder lake lamp lamprey + land laser laugh law lawn lawyer + layer lead leader leading leaf leafy + league leather leave lecture leek leg + lemon length lentil lesson let letter + lettuce level library life lift light + lily lime limit line linen link + lip list listen lithium lively living + lizard load loan lobster local lock + log long longfin look lotus love + lovely loving low lucid luck luffa + lunch lung machine magenta magnet mail + main major make mall manager mango + manner many map march market maroon + martian master match math matter maximum + maybe meal meaning meat media medium + meet meeting melody melon member memory + mention menu mercury merry mess message + messy metal meter method micron middle + might mile milk mind mine minimum + minnow minor mint minute mirror miss + mission misty mix mixer mixture mobile + mode model moment monitor monk month + moon moray morning most motor mouse + mouth move mover movie much mud + mudfish muffin mullet munster muon muscle + music mustard nail name nation native + natural nature navy neat neatly nebula + neck needle neon nerve net network + neutron news nibble nice nickel night + niobium nobody noise noodle normal north + nose note nothing notice nova novel + number nurse nursery oar object offer + office officer oil okay okra old + olive one onion open opening opinion + option orange orbit orchid order oregano + other ounce outcome outside oven owner + oxygen oyster pace pack package page + pager paint pair pale pan pancake + papaya paper pardon parent park parking + parsley parsnip part partner party pass + passage past pasta path patient pattern + pause pay pea peace peach peacock + peahen peak peanut pear pearl pen + penalty pencil pension people pepper perch + perfect period permit person phase phone + photo phrase physics piano pick picture + pie piece pigeon pike pilot pin + pink pinkie pious pipe pitch pizza + place plan plane planet plant planter + plastic plate play player playful plenty + pliers plum pod poem poet poetry + point police policy pollock pony pool + pop popover poptart pork port portal + post pot potato pound powder power + present press price pride primary print + prior private prize problem process produce + product profile profit program project promise + prompt proof proper protein proton public + puff puffer pull pumpkin pup pupfish + pure purple purpose push put quality + quark quarter quiet quill quit quote + rabbit raccoon race radiant radio radish + radium radon rain rainbow raise ramp + ranch range rasp rate ratio ray + razor reach read reading real reality + reason recipe record recover red redeem + reed reef refuse region regret regular + relaxed release relief relish remote remove + rent repair repeat reply report request + reserve resist resolve resort rest result + return reveal review reward ribbon rice + rich ride ridge right ring rise + risk river rivet road roast rock + rocket role roll roof room rope + rose rough roughy round row royal + rub ruby rudder ruin rule run + runner rush rust sacred saddle safe + safety sail salad salami sale salmon + salt sample sand sander sandy sauce + save saving saw scale scampi scene + scheme school score screen script sea + search season seat second secret sector + seemly self sell senate senior sense + series serve set shake shape share + shark shell shift shine shiny ship + shock shoe shoot shop shovel show + side sign signal silk silly silver + simple sing singer single sink site + size skill skin sky slate sleep + sleepy slice slide slip smart smell + smelt smile smoke smooth snap snipe + snow snowy sock socket sodium soft + softly soil sole solid song sorrel + sort soul sound soup source south + space spare speech speed spell spend + sphere spice spider spirit spite split + spoon sport spot spray spread spring + squab square squash stable staff stage + stand staple star start state status + stay steak steel step stern stew + stick still stock stone stop store + storm story strain street stress strike + string stroke strong studio study stuff + style sugar suit sulfur summer sun + sunny sunset super superb surf survey + sweet swim swing switch symbol system + table tackle tail tale talk tan + tank tap tape target task taste + tau tea teach teal team tear + tell ten tender tennis tent term + test tetra text thanks theme theory + thing think thread throat thumb ticket + tidy tie tiger till time timely + tin tip title toast today toe + tomato tone tongue tool tooth top + topic total touch tough tour towel + tower town track trade train trash + travel tray treat tree trick trip + trout trowel truck trupet trust truth + try tube tuna tune turf turkey + turn turnip tutor tux tweet twist + two type union unique unit upbeat + upper use useful user usual valley + value van vase vast veil vein + velvet verse very vessel vest video + view violet visit visual vivid voice + volume vowel voyage waffle wait wake + walk wall warm warmth wasabi wash + watch water wave wax way wealth + wear web wedge week weekly weight + west whale what wheat wheel when + where while who whole why will + win wind window wing winner winter + wire wish witty wolf wonder wood + wool woolly word work worker world + worry worth worthy wrap wrench wrist + writer xenon yak yam yard yarrow + year yearly yellow yew yogurt young + youth zebra zephyr zinc zone zoo +} +set nwordlist [llength $wordlist] + +proc random_char {} { + return [string index \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + [expr {int(rand()*52)}]] +} +proc random_label {} { + set label [random_char] + while {rand()>0.8} { + append label [random_char] + } + if {rand()>0.9} {append label -} + append label [format %d [expr {int(rand()*100)}]] + return $label +} +proc random_numeric {} { + set n [expr {(rand()*2-1.0)*1e6}] + switch [expr {int(rand()*6)}] { + 0 {set format %.3f} + 1 {set format %.6E} + 2 {set format %.4e} + default {set format %g} + } + return [format $format $n] +} + + +proc random_json {limit indent} { + global nwordlist wordlist + set res {} + if {$indent==0 || ($limit>0 && rand()>0.5)} { + incr limit -1 + incr indent 2 + set n [expr {int(rand()*5)+1}] + if {$n==5} {incr n [expr {int(rand()*10)}]} + if {rand()>0.5} { + set res \173\n + for {set i 0} {$i<$n} {incr i} { + append res [string repeat { } $indent] + if {rand()>0.8} { + if {rand()>0.5} { + set sep ":\n [string repeat { } $indent]" + } else { + set sep " : " + } + } else { + set sep : + } + append res \"[random_label]\"$sep[random_json $limit $indent] + if {$i<$n-1} {append res ,} + append res \n + } + incr indent -2 + append res [string repeat { } $indent] + append res \175 + return $res + } else { + set res \[\n + for {set i 0} {$i<$n} {incr i} { + append res [string repeat { } $indent] + append res [random_json $limit $indent] + if {$i<$n-1} {append res ,} + append res \n + } + incr indent -2 + append res [string repeat { } $indent] + append res \] + return $res + } + } elseif {rand()>0.9} { + if {rand()>0.7} {return "true"} + if {rand()>0.5} {return "false"} + return "null" + } elseif {rand()>0.5} { + return [random_numeric] + } else { + set res \" + set n [expr {int(rand()*4)+1}] + if {$n>=4} {set n [expr {$n+int(rand()*6)}]} + for {set i 0} {$i<$n} {incr i} { + if {rand()<0.05} { + set w [random_numeric] + } else { + set k [expr {int(rand()*$nwordlist)}] + set w [lindex $wordlist $k] + } + if {rand()<0.07} { + set w \\\"$w\\\" + } + if {$i<$n-1} { + switch [expr {int(rand()*9)}] { + 0 {set sp {, }} + 1 {set sp "\\n "} + 2 {set sp "-"} + default {set sp { }} + } + append res $w$sp + } else { + append res $w + if {rand()<0.2} {append res .} + } + } + return $res\" + } +} + +puts "CREATE TABLE IF NOT EXISTS data1(x JSON);" +puts "BEGIN;" +set sz 0 +for {set i 0} {$sz<100000000} {incr i} { + set j [random_json 7 0] + incr sz [string length $j] + puts "INSERT INTO data1(x) VALUES('$j');" +} +puts "COMMIT;" +puts "SELECT sum(length(x)) FROM data1;" diff --git a/test/json/json-q1.txt b/test/json/json-q1.txt new file mode 100644 index 000000000..0395f0c06 --- /dev/null +++ b/test/json/json-q1.txt @@ -0,0 +1,4 @@ +.mode qbox +.timer on +.param set $label 'q87' +SELECT rowid, x->>$label FROM data1 WHERE x->>$label IS NOT NULL; diff --git a/test/json/json-speed-check.sh b/test/json/json-speed-check.sh new file mode 100755 index 000000000..f7e7f1be5 --- /dev/null +++ b/test/json/json-speed-check.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# +# This is a template for a script used for day-to-day size and +# performance monitoring of SQLite. Typical usage: +# +# sh speed-check.sh trunk # Baseline measurement of trunk +# sh speed-check.sh x1 # Measure some experimental change +# fossil xdiff --tk jout-trunk.txt jout-x1.txt # View chanages +# +# There are multiple output files, all with a base name given by +# the first argument: +# +# summary-$BASE.txt # Copy of standard output +# jout-$BASE.txt # cachegrind output +# explain-$BASE.txt # EXPLAIN listings (only with --explain) +# +if test "$1" = "" +then + echo "Usage: $0 OUTPUTFILE [OPTIONS]" + exit +fi +NAME=$1 +shift +#CC_OPTS="-DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_MEMSYS5" +CC_OPTS="-DSQLITE_ENABLE_MEMSYS5" +CC=gcc +LEAN_OPTS="-DSQLITE_THREADSAFE=0" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_MEMSTATUS=0" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_LIKE_DOESNT_MATCH_BLOBS" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_MAX_EXPR_DEPTH=0" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DECLTYPE" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DEPRECATED" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_PROGRESS_CALLBACK" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_SHARED_CACHE" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_USE_ALLOCA" +BASELINE="trunk" +doExplain=0 +doCachegrind=1 +doVdbeProfile=0 +doWal=1 +doDiff=1 +while test "$1" != ""; do + case $1 in + --nodiff) + doDiff=0 + ;; + --lean) + CC_OPTS="$CC_OPTS $LEAN_OPTS" + ;; + --clang) + CC=clang + ;; + --gcc7) + CC=gcc-7 + ;; + -*) + CC_OPTS="$CC_OPTS $1" + ;; + *) + BASELINE=$1 + ;; + esac + shift +done +echo "NAME = $NAME" | tee summary-$NAME.txt +echo "CC_OPTS = $CC_OPTS" | tee -a summary-$NAME.txt +rm -f cachegrind.out.* jsonshell +$CC -g -Os -Wall -I. $CC_OPTS ./shell.c ./sqlite3.c -o jsonshell -ldl -lpthread +ls -l jsonshell | tee -a summary-$NAME.txt +home=`echo $0 | sed -e 's,/[^/]*$,,'` +echo ./jsonshell json100mb.db "<$home/json-q1.txt" +valgrind --tool=cachegrind ./jsonshell json100mb.db <$home/json-q1.txt \ + 2>&1 | tee -a summary-$NAME.txt +cg_anno.tcl cachegrind.out.* >jout-$NAME.txt +echo '*****************************************************' >>jout-$NAME.txt +sed 's/^[0-9=-]\{9\}/==00000==/' summary-$NAME.txt >>jout-$NAME.txt +if test "$NAME" != "$BASELINE"; then + fossil xdiff --tk -c 20 jout-$BASELINE.txt jout-$NAME.txt +fi diff --git a/test/json101.test b/test/json101.test index 135c988e9..543e4c71e 100644 --- a/test/json101.test +++ b/test/json101.test @@ -310,12 +310,33 @@ do_execsql_test json-6.1 { SELECT json_valid('{"a":55,"b":72,}'); } {0} do_execsql_test json-6.2 { + SELECT json_error_position('{"a":55,"b":72,}'); +} {0} +do_execsql_test json-6.3 { + SELECT json_valid(json('{"a":55,"b":72,}')); +} {1} +do_execsql_test json-6.4 { + SELECT json_valid('{"a":55,"b":72 , }'); +} {0} +do_execsql_test json-6.5 { + SELECT json_error_position('{"a":55,"b":72 , }'); +} {0} +do_execsql_test json-6.6 { + SELECT json_error_position('{"a":55,"b":72,,}'); +} {16} +do_execsql_test json-6.7 { SELECT json_valid('{"a":55,"b":72}'); } {1} -do_execsql_test json-6.3 { - SELECT json_valid('["a",55,"b",72,]'); +do_execsql_test json-6.8 { + SELECT json_error_position('["a",55,"b",72,]'); } {0} -do_execsql_test json-6.4 { +do_execsql_test json-6.9 { + SELECT json_error_position('["a",55,"b",72 , ]'); +} {0} +do_execsql_test json-6.10 { + SELECT json_error_position('["a",55,"b",72,,]'); +} {16} +do_execsql_test json-6.11 { SELECT json_valid('["a",55,"b",72]'); } {1} @@ -700,20 +721,20 @@ do_execsql_test json-10.95 { # do_execsql_test json-11.0 { /* Shallow enough to be parsed */ - SELECT json_valid(printf('%.2000c0%.2000c','[',']')); + SELECT json_valid(printf('%.1000c0%.1000c','[',']')); } {1} do_execsql_test json-11.1 { /* Too deep by one */ - SELECT json_valid(printf('%.2001c0%.2001c','[',']')); + SELECT json_valid(printf('%.1001c0%.1001c','[',']')); } {0} do_execsql_test json-11.2 { /* Shallow enough to be parsed { */ - SELECT json_valid(replace(printf('%.2000c0%.2000c','[','}'),'[','{"a":')); + SELECT json_valid(replace(printf('%.1000c0%.1000c','[','}'),'[','{"a":')); /* } */ } {1} do_execsql_test json-11.3 { /* Too deep by one { */ - SELECT json_valid(replace(printf('%.2001c0%.2001c','[','}'),'[','{"a":')); + SELECT json_valid(replace(printf('%.1001c0%.1001c','[','}'),'[','{"a":')); /* } */ } {0} @@ -885,4 +906,109 @@ do_execsql_test json-19.3 { SELECT * FROM t1; } {} +# 2023-03-17 positive and negative infinities +# +do_execsql_test json-20.1 { + SELECT json_object('a',2e370,'b',-3e380); +} {{{"a":9.0e+999,"b":-9.0e+999}}} +do_execsql_test json-20.2 { + SELECT json_object('a',2e370,'b',-3e380)->>'a'; +} Inf +do_execsql_test json-20.3 { + SELECT json_object('a',2e370,'b',-3e380)->>'b'; +} {-Inf} + +# 2023-05-02 https://sqlite.org/forum/forumpost/06c6334412 +# JSON functions should normally return NULL when given +# a NULL value as the JSON input. +# +db null NULL +do_execsql_test json-21.1 { + SELECT json_valid(NULL); +} NULL +do_execsql_test json-21.2 { + SELECT json_error_position(NULL); +} NULL +do_execsql_test json-21.3 { + SELECT json(NULL); +} NULL +do_execsql_test json-21.4 { + SELECT json_array(NULL); +} {[null]} +do_execsql_test json-21.5 { + SELECT json_extract(NULL); +} NULL +do_execsql_test json-21.6 { + SELECT json_insert(NULL,'$',123); +} NULL +do_execsql_test json-21.7 { + SELECT NULL->0; +} NULL +do_execsql_test json-21.8 { + SELECT NULL->>0; +} NULL +do_execsql_test json-21.9 { + SELECT '{a:5}'->NULL; +} NULL +do_execsql_test json-21.10 { + SELECT '{a:5}'->>NULL; +} NULL +do_catchsql_test json-21.11 { + SELECT json_object(NULL,5); +} {1 {json_object() labels must be TEXT}} +do_execsql_test json-21.12 { + SELECT json_patch(NULL,'{a:5}'); +} NULL +do_execsql_test json-21.13 { + SELECT json_patch('{a:5}',NULL); +} NULL +do_execsql_test json-21.14 { + SELECT json_patch(NULL,NULL); +} NULL +do_execsql_test json-21.15 { + SELECT json_remove(NULL,'$'); +} NULL +do_execsql_test json-21.16 { + SELECT json_remove('{a:5,b:7}',NULL); +} NULL +do_execsql_test json-21.17 { + SELECT json_replace(NULL,'$.a',123); +} NULL +do_execsql_test json-21.18 { + SELECT json_replace('{a:5,b:7}',NULL,NULL); +} {{{"a":5,"b":7}}} +do_execsql_test json-21.19 { + SELECT json_set(NULL,'$.a',123); +} NULL +do_execsql_test json-21.20 { + SELECT json_set('{a:5,b:7}',NULL,NULL); +} {{{"a":5,"b":7}}} +do_execsql_test json-21.21 { + SELECT json_type(NULL); +} NULL +do_execsql_test json-21.22 { + SELECT json_type('{a:5,b:7}',NULL); +} NULL +do_execsql_test json-21.23 { + SELECT json_quote(NULL); +} null +do_execsql_test json-21.24 { + SELECT count(*) FROM json_each(NULL); +} 0 +do_execsql_test json-21.25 { + SELECT count(*) FROM json_tree(NULL); +} 0 +do_execsql_test json-21.26 { + WITH c(x) AS (VALUES(1),(2.0),(NULL),('three')) + SELECT json_group_array(x) FROM c; +} {[1,2.0,null,"three"]} +do_execsql_test json-21.27 { + WITH c(x,y) AS (VALUES('a',1),('b',2.0),('c',NULL),(NULL,'three'),('e','four')) + SELECT json_group_object(x,y) FROM c; +} {{{"a":1,"b":2.0,"c":null,:"three","e":"four"}}} + + + + + finish_test diff --git a/test/json102.test b/test/json102.test index f551c4b82..bfd5e7ed0 100644 --- a/test/json102.test +++ b/test/json102.test @@ -301,18 +301,27 @@ for {set i 0} {$i<100} {incr i} { # allowing them. The following tests verify that the problem is now # fixed. # -do_execsql_test json102-1401 { SELECT json_valid('{"x":01}') } 0 -do_execsql_test json102-1402 { SELECT json_valid('{"x":-01}') } 0 -do_execsql_test json102-1403 { SELECT json_valid('{"x":0}') } 1 -do_execsql_test json102-1404 { SELECT json_valid('{"x":-0}') } 1 -do_execsql_test json102-1405 { SELECT json_valid('{"x":0.1}') } 1 -do_execsql_test json102-1406 { SELECT json_valid('{"x":-0.1}') } 1 -do_execsql_test json102-1407 { SELECT json_valid('{"x":0.0000}') } 1 -do_execsql_test json102-1408 { SELECT json_valid('{"x":-0.0000}') } 1 -do_execsql_test json102-1409 { SELECT json_valid('{"x":01.5}') } 0 -do_execsql_test json102-1410 { SELECT json_valid('{"x":-01.5}') } 0 -do_execsql_test json102-1411 { SELECT json_valid('{"x":00}') } 0 -do_execsql_test json102-1412 { SELECT json_valid('{"x":-00}') } 0 +foreach {id j x0 x5} { + 1401 {'{"x":01}'} 0 0 + 1402 {'{"x":-01}'} 0 0 + 1403 {'{"x":0}'} 1 1 + 1404 {'{"x":-0}'} 1 1 + 1405 {'{"x":0.1}'} 1 1 + 1406 {'{"x":-0.1}'} 1 1 + 1407 {'{"x":0.0000}'} 1 1 + 1408 {'{"x":-0.0000}'} 1 1 + 1409 {'{"x":01.5}'} 0 0 + 1410 {'{"x":-01.5}'} 0 0 + 1411 {'{"x":00}'} 0 0 + 1412 {'{"x":-00}'} 0 0 + 1413 {'{"x":+0}'} 0 1 + 1414 {'{"x":+5}'} 0 1 + 1415 {'{"x":+5.5}'} 0 1 +} { + do_execsql_test json102-$id " + SELECT json_valid($j), NOT json_error_position($j); + " [list $x0 $x5] +} #------------------------------------------------------------------------ # 2017-04-10 ticket 6c9b5514077fed34551f98e64c09a10dc2fc8e16 diff --git a/test/json104.test b/test/json104.test index 56dd2738c..c3c43d1e9 100644 --- a/test/json104.test +++ b/test/json104.test @@ -30,6 +30,48 @@ do_execsql_test json104-100 { } }'); } {{{"a":"z","c":{"d":"e"}}}} +do_execsql_test json104-101 { + SELECT json_patch('{ + "a": "b", + "c": { + "d": "e", + "f": "g" + } + }','{ + a:"z", + c: { + f: null + } + }'); +} {{{"a":"z","c":{"d":"e"}}}} +do_execsql_test json104-102 { + SELECT json_patch('{ + a: "b", + c: { + d: "e", + f: "g" + } + }','{ + "a":"z", + "c": { + "f": null + } + }'); +} {{{"a":"z","c":{"d":"e"}}}} +do_execsql_test json104-103 { + SELECT json_patch('{ + a: "b", + c: { + d: "e", + f: "g" + } + }','{ + a:"z", + c: { + f: null + } + }'); +} {{{"a":"z","c":{"d":"e"}}}} # This is the example from pages 4 and 5 of RFC-7396 diff --git a/test/json501.test b/test/json501.test new file mode 100644 index 000000000..a37326973 --- /dev/null +++ b/test/json501.test @@ -0,0 +1,304 @@ +# 2023-04-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 tests for the JSON5 enhancements to the +# JSON SQL functions extension to the SQLite library. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix json501 + +# From https://spec.json5.org/#introduction +# +#----------------------------------------------------------------------------- +# Summary of Features +# +# The following ECMAScript 5.1 features, which are not supported in JSON, have +# been extended to JSON5. +# +# Objects +# +# 1) Object keys may be an ECMAScript 5.1 IdentifierName. +# 2) Objects may have a single trailing comma. +# +# Arrays +# +# 3) Arrays may have a single trailing comma. +# +# Strings +# +# 4) Strings may be single quoted. +# 5) Strings may span multiple lines by escaping new line characters. +# 6) Strings may include character escapes. +# +# Numbers +# +# 7) Numbers may be hexadecimal. +# 8) Numbers may have a leading or trailing decimal point. +# 9) Numbers may be IEEE 754 positive infinity, negative infinity, and NaN. +# 10) Numbers may begin with an explicit plus sign. +# +# Comments +# +# 11) Single and multi-line comments are allowed. +# +# White Space +# +# 12) Additional white space characters are allowed. +#----------------------------------------------------------------------------- +# +# Test number in this file are of the form X.Y where X is one of the item +# numbers in the feature list above and Y is the test sequence number. +# + +############################################################################### +# 1) Object keys may be an ECMAScript 5.1 IdentifierName. +do_execsql_test 1.1 { + WITH c(x) AS (VALUES('{a:5,b:6}')) + SELECT x->>'a', json(x), json_valid(x), NOT json_error_position(x) FROM c; +} {5 {{"a":5,"b":6}} 0 1} +do_execsql_test 1.2 { + SELECT '[7,null,{a:5,b:6},[8,9]]'->>'$[2].b'; +} {6} +do_execsql_test 1.3 { + SELECT '{ $123 : 789 }'->>'$."$123"'; +} 789 +do_execsql_test 1.4 { + SELECT '{ _123$xyz : 789 }'->>'$."_123$xyz"'; +} 789 +do_execsql_test 1.5 { + SELECT '{ MNO_123$xyz : 789 }'->>'$."MNO_123$xyz"'; +} 789 + +do_execsql_test 1.6 { + SELECT json('{ MNO_123$xyz : 789 }'); +} [list {{"MNO_123$xyz":789}}] + +do_catchsql_test 1.10 { + SELECT json('{ MNO_123/xyz : 789 }'); +} {1 {malformed JSON}} + +do_execsql_test 1.11 { + SELECT '{ MNO_123æxyz : 789 }'->>'MNO_123æxyz'; +} {789} + +############################################################################### +# 2) Objects may have a single trailing comma. + +do_execsql_test 2.1 { + WITH c(x) AS (VALUES('{"a":5, "b":6, }')) + SELECT x->>'b', json(x), json_valid(x), NOT json_error_position(x) FROM c; +} {6 {{"a":5,"b":6}} 0 1} +do_execsql_test 2.2 { + SELECT '{a:5, b:6 , }'->>'b'; +} 6 +do_catchsql_test 2.3 { + SELECT '{a:5, b:6 ,, }'->>'b'; +} {1 {malformed JSON}} +do_catchsql_test 2.4 { + SELECT '{a:5, b:6, ,}'->>'b'; +} {1 {malformed JSON}} + +############################################################################### +# 3) Arrays may have a single trailing comma. + +do_execsql_test 3.1 { + WITH c(x) AS (VALUES('[5, 6,]')) + SELECT x->>1, json(x), json_valid(x), NOT json_error_position(x) FROM c; +} {6 {[5,6]} 0 1} +do_execsql_test 3.2 { + SELECT '[5, 6 , ]'->>1; +} 6 +do_catchsql_test 3.3 { + SELECT '[5, 6,,]'->>1; +} {1 {malformed JSON}} +do_catchsql_test 3.4 { + SELECT '[5, 6 , , ]'->>1; +} {1 {malformed JSON}} + +############################################################################### +# 4) Strings may be single quoted. + +do_execsql_test 4.1 { + WITH c(x) AS (VALUES('{"a": ''abcd''}')) + SELECT x->>'a', json(x), json_valid(x), NOT json_error_position(x) FROM c; +} {abcd {{"a":"abcd"}} 0 1} +do_execsql_test 4.2 { + SELECT '{b: 123, ''a'': ''ab\''cd''}'->>'a'; +} {ab'cd} + +############################################################################### +# 5) Strings may span multiple lines by escaping new line characters. + +do_execsql_test 5.1 { + WITH c(x) AS (VALUES('{a: "abc'||char(0x5c,0x0a)||'xyz"}')) + SELECT x->>'a', json(x), json_valid(x), NOT json_error_position(x) FROM c; +} {abcxyz {{"a":"abcxyz"}} 0 1} +do_execsql_test 5.2 { + SELECT ('{a: "abc'||char(0x5c,0x0d)||'xyz"}')->>'a'; +} {abcxyz} +do_execsql_test 5.3 { + SELECT ('{a: "abc'||char(0x5c,0x0d,0x0a)||'xyz"}')->>'a'; +} {abcxyz} +do_execsql_test 5.4 { + SELECT ('{a: "abc'||char(0x5c,0x2028)||'xyz"}')->>'a'; +} {abcxyz} +do_execsql_test 5.5 { + SELECT ('{a: "abc'||char(0x5c,0x2029)||'xyz"}')->>'a'; +} {abcxyz} + + +############################################################################### +# 6) Strings may include character escapes. + +do_execsql_test 6.1 { + SELECT ('{a: "abc'||char(0x5c,0x27)||'xyz"}')->>'a'; +} {abc'xyz} +do_execsql_test 6.2 { + SELECT ('{a: "abc'||char(0x5c,0x22)||'xyz"}')->>'a'; +} {abc"xyz} +do_execsql_test 6.3 { + SELECT ('{a: "abc'||char(0x5c,0x5c)||'xyz"}')->>'a'; +} {{abc\xyz}} +do_execsql_test 6.4 { + SELECT hex(('{a: "abc\bxyz"}')->>'a'); +} {6162630878797A} +do_execsql_test 6.5 { + SELECT hex(('{a: "abc\f\n\r\t\vxyz"}')->>'a'); +} {6162630C0A0D090B78797A} +do_execsql_test 6.6 { + SELECT hex(('{a: "abc\0xyz"}')->>'a'); +} {6162630078797A} +do_execsql_test 6.7 { + SELECT '{a: "abc\x35\x4f\x6Exyz"}'->>'a'; +} {abc5Onxyz} +do_execsql_test 6.8 { + SELECT '{a: "\x6a\x6A\x6b\x6B\x6c\x6C\x6d\x6D\x6e\x6E\x6f\x6F"}'->>'a'; +} {jjkkllmmnnoo} + +############################################################################### +# 7) Numbers may be hexadecimal. + +do_execsql_test 7.1 { + SELECT '{a: 0x0}'->>'a'; +} 0 +do_execsql_test 7.2 { + SELECT '{a: -0x0}'->>'a'; +} 0 +do_execsql_test 7.3 { + SELECT '{a: +0x0}'->>'a'; +} 0 +do_execsql_test 7.4 { + SELECT '{a: 0xabcdef}'->>'a'; +} 11259375 +do_execsql_test 7.5 { + SELECT '{a: -0xaBcDeF}'->>'a'; +} -11259375 +do_execsql_test 7.6 { + SELECT '{a: +0xABCDEF}'->>'a'; +} 11259375 + +############################################################################### +# 8) Numbers may have a leading or trailing decimal point. + +do_execsql_test 8.1 { + WITH c(x) AS (VALUES('{x: 4.}')) SELECT x->>'x', json(x) FROM c; +} {4.0 {{"x":4.0}}} +do_execsql_test 8.2 { + WITH c(x) AS (VALUES('{x: +4.}')) SELECT x->>'x', json(x) FROM c; +} {4.0 {{"x":4.0}}} +do_execsql_test 8.3 { + WITH c(x) AS (VALUES('{x: -4.}')) SELECT x->>'x', json(x) FROM c; +} {-4.0 {{"x":-4.0}}} +do_execsql_test 8.3 { + WITH c(x) AS (VALUES('{x: .5}')) SELECT x->>'x', json(x) FROM c; +} {0.5 {{"x":0.5}}} +do_execsql_test 8.4 { + WITH c(x) AS (VALUES('{x: -.5}')) SELECT x->>'x', json(x) FROM c; +} {-0.5 {{"x":-0.5}}} +do_execsql_test 8.5 { + WITH c(x) AS (VALUES('{x: +.5}')) SELECT x->>'x', json(x) FROM c; +} {0.5 {{"x":0.5}}} +do_execsql_test 8.6 { + WITH c(x) AS (VALUES('{x: 4.e0}')) SELECT x->>'x', json(x) FROM c; +} {4.0 {{"x":4.0e0}}} +do_execsql_test 8.7 { + WITH c(x) AS (VALUES('{x: +4.e1}')) SELECT x->>'x', json(x) FROM c; +} {40.0 {{"x":4.0e1}}} +do_execsql_test 8.8 { + WITH c(x) AS (VALUES('{x: -4.e2}')) SELECT x->>'x', json(x) FROM c; +} {-400.0 {{"x":-4.0e2}}} +do_execsql_test 8.9 { + WITH c(x) AS (VALUES('{x: .5e3}')) SELECT x->>'x', json(x) FROM c; +} {500.0 {{"x":0.5e3}}} +do_execsql_test 8.10 { + WITH c(x) AS (VALUES('{x: -.5e-1}')) SELECT x->>'x', json(x) FROM c; +} {-0.05 {{"x":-0.5e-1}}} +do_execsql_test 8.11 { + WITH c(x) AS (VALUES('{x: +.5e-2}')) SELECT x->>'x', json(x) FROM c; +} {0.005 {{"x":0.5e-2}}} + + +############################################################################### +# 9) Numbers may be IEEE 754 positive infinity, negative infinity, and NaN. + +do_execsql_test 9.1 { + WITH c(x) AS (VALUES('{x: +Infinity}')) SELECT x->>'x', json(x) FROM c; +} {Inf {{"x":9.0e999}}} +do_execsql_test 9.2 { + WITH c(x) AS (VALUES('{x: -Infinity}')) SELECT x->>'x', json(x) FROM c; +} {-Inf {{"x":-9.0e999}}} +do_execsql_test 9.3 { + WITH c(x) AS (VALUES('{x: Infinity}')) SELECT x->>'x', json(x) FROM c; +} {Inf {{"x":9.0e999}}} +do_execsql_test 9.4 { + WITH c(x) AS (VALUES('{x: NaN}')) SELECT x->>'x', json(x) FROM c; +} {{} {{"x":null}}} + +############################################################################### +# 10) Numbers may begin with an explicit plus sign. + +do_execsql_test 10.1 { + SELECT '{a: +123}'->'a'; +} 123 + +############################################################################### +# 11) Single and multi-line comments are allowed. + +do_execsql_test 11.1 { + SELECT ' /* abc */ { /*def*/ aaa /* xyz */ : // to the end of line + 123 /* xyz */ , /* 123 */ }'->>'aaa'; +} 123 + +############################################################################### +# 12) Additional white space characters are allowed. + +do_execsql_test 12.1 { + SELECT (char(0x09,0x0a,0x0b,0x0c,0x0d,0x20,0xa0,0x2028,0x2029) + || '{a: "xyz"}')->>'a'; +} xyz +do_execsql_test 12.2 { + SELECT ('{a:' || char(0x09,0x0a,0x0b,0x0c,0x0d,0x20,0xa0,0x2028,0x2029) + || '"xyz"}')->>'a'; +} xyz +do_execsql_test 12.3 { + SELECT (char(0x1680,0x2000,0x2001,0x2002,0x2003,0x2004,0x2005, + 0x2006,0x2007,0x2008,0x2009,0x200a,0x3000,0xfeff) + || '{a: "xyz"}')->>'a'; +} xyz +do_execsql_test 12.4 { + SELECT ('{a: ' ||char(0x1680,0x2000,0x2001,0x2002,0x2003,0x2004,0x2005, + 0x2006,0x2007,0x2008,0x2009,0x200a,0x3000,0xfeff) + || ' "xyz"}')->>'a'; +} xyz + + +finish_test diff --git a/test/json502.test b/test/json502.test new file mode 100644 index 000000000..595bf6331 --- /dev/null +++ b/test/json502.test @@ -0,0 +1,40 @@ +# 2023-04-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 tests for the JSON5 enhancements to the +# JSON SQL functions extension to the SQLite library. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix json502 + +ifcapable vtab { + +do_execsql_test 1.1 { + CREATE TABLE t1(x JSON); + INSERT INTO t1(x) VALUES('{a:{b:{c:"hello",},},}'); + SELECT fullkey FROM t1, json_tree(x); +} {{$} {$.a} {$.a.b} {$.a.b.c}} + +} + +do_execsql_test 2.1 { + SELECT json_error_position('{a:null,{"h":[1,[1,2,3]],"j":"abc"}:true}'); +} 9 +do_catchsql_test 2.2 { + SELECT json('{a:null,{"h":[1,[1,2,3]],"j":"abc"}:true}'); +} {1 {malformed JSON}} +do_catchsql_test 2.3 { + SELECT '{a:null,{"h":[1,[1,2,3]],"j":"abc"}:true}'->'$h[#-1]'; +} {1 {malformed JSON}} + + +finish_test diff --git a/test/misc1.test b/test/misc1.test index 44914156c..83acc752a 100644 --- a/test/misc1.test +++ b/test/misc1.test @@ -592,6 +592,9 @@ do_test misc1-18.1 { set n [sqlite3_sleep 100] expr {$n>=100} } {1} +do_test misc1-18.2 { + sqlite3_sleep -100 +} {0} # 2014-01-10: In a CREATE TABLE AS, if one or more of the column names # are an empty string, that is still OK. diff --git a/test/pager2.test b/test/pager2.test index 1d78b30f5..ef05cc76a 100644 --- a/test/pager2.test +++ b/test/pager2.test @@ -147,24 +147,25 @@ do_test pager2-2.2 { file size test.db } {3072} -#------------------------------------------------------------------------- -# Test that shared in-memory databases seem to work. -# -db close -do_test pager2-3.1 { - forcedelete test.db - sqlite3_shutdown - sqlite3_config_uri 1 - - sqlite3 db1 {file:test.db?mode=memory&cache=shared} - sqlite3 db2 {file:test.db?mode=memory&cache=shared} - sqlite3 db3 test.db - - db1 eval { CREATE TABLE t1(a, b) } - db2 eval { INSERT INTO t1 VALUES(1, 2) } - list [catch { db3 eval { INSERT INTO t1 VALUES(3, 4) } } msg] $msg -} {1 {no such table: t1}} - -db1 close +ifcapable shared_cache { + #------------------------------------------------------------------------- + # Test that shared in-memory databases seem to work. + # + db close + do_test pager2-3.1 { + forcedelete test.db + sqlite3_shutdown + sqlite3_config_uri 1 + + sqlite3 db1 {file:test.db?mode=memory&cache=shared} + sqlite3 db2 {file:test.db?mode=memory&cache=shared} + sqlite3 db3 test.db + + db1 eval { CREATE TABLE t1(a, b) } + db2 eval { INSERT INTO t1 VALUES(1, 2) } + list [catch { db3 eval { INSERT INTO t1 VALUES(3, 4) } } msg] $msg + } {1 {no such table: t1}} + db1 close +} finish_test diff --git a/test/pragma.test b/test/pragma.test index 9ab332c3f..5b45a7440 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -433,9 +433,9 @@ ifcapable attach { PRAGMA integrity_check } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} do_execsql_test pragma-3.9b { PRAGMA t2.integrity_check=t2; } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} @@ -447,79 +447,79 @@ Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2 PRAGMA integrity_check=1 } } {{*** in database t2 *** -Page 4 is never used}} +Page 4: never used}} do_test pragma-3.11 { execsql { PRAGMA integrity_check=5 } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2}} +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2}} do_test pragma-3.12 { execsql { PRAGMA integrity_check=4 } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2}} +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2}} do_test pragma-3.13 { execsql { PRAGMA integrity_check=3 } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used}} +Page 4: never used +Page 5: never used +Page 6: never used}} do_test pragma-3.14 { execsql { PRAGMA integrity_check(2) } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used}} +Page 4: never used +Page 5: never used}} do_test pragma-3.15 { execsql { ATTACH 'testerr.db' AS t3; PRAGMA integrity_check } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} do_test pragma-3.16 { execsql { PRAGMA integrity_check(10) } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2}} +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2}} do_test pragma-3.17 { execsql { PRAGMA integrity_check=8 } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** -Page 4 is never used -Page 5 is never used}} +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 4: never used +Page 5: never used}} do_test pragma-3.18 { execsql { PRAGMA integrity_check=4 } } {{*** in database t2 *** -Page 4 is never used -Page 5 is never used -Page 6 is never used} {row 1 missing from index i2}} +Page 4: never used +Page 5: never used +Page 6: never used} {row 1 missing from index i2}} } do_test pragma-3.19 { catch {db close} @@ -2060,4 +2060,17 @@ ifcapable !has_codec { } {0 {{database disk image is malformed}}} } database_never_corrupt + +# 2023-03-27. Register allocation issue in integrity_check discovered +# by new assert() statements added in [6f8b97f31a4c8552]. +# dbsqlfuzz dc9ab26037cf5ef797d28cd1ae0855ade584216d +# tag-20230327-1 +# +reset_db +do_execsql_test 25.0 { + CREATE TABLE t1(a INT, b AS (a*2) NOT NULL); + CREATE TEMP TABLE t2(a PRIMARY KEY, b, c UNIQUE) WITHOUT ROWID; + CREATE UNIQUE INDEX t2x ON t2(c,b); + PRAGMA integrity_check; +} ok finish_test diff --git a/test/printf.test b/test/printf.test index 445470fc0..6d4ad71d2 100644 --- a/test/printf.test +++ b/test/printf.test @@ -16,7 +16,6 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl - do_test printf-1.1.1 { sqlite3_mprintf_int {abc: %d %x %o :xyz}\ 1 1 1 @@ -3786,4 +3785,52 @@ do_execsql_test printf-16.1 { SELECT printf('%.*g',2147483647,0.01); } {0.01} +# 2023-02-23 https://sqlite.org/forum/forumpost/d1387c3979c7f557 +# Loss of precision when doing floating-point to decimal +# conversions on values that have no factional part. +# +do_execsql_test printf-17.1 { + SELECT format('%!.20g', 13.0); +} 13.0 +do_execsql_test printf-17.2 { + SELECT format('%.3e', 199990000.0); +} 2.000e+08 +do_execsql_test printf-17.3 { + SELECT format('%.3f', 199990000.0); +} 199990000.000 +do_execsql_test printf-17.4 { + SELECT format('%.3g', 199990000.0); +} 2e+08 +do_execsql_test printf-17.5 { + SELECT format('%.4e', 199990000.0); +} 1.9999e+08 +do_execsql_test printf-17.6 { + SELECT format('%.4f', 199990000.0); +} 199990000.0000 +do_execsql_test printf-17.7 { + SELECT format('%.4g', 199990000.0); +} 2e+08 +do_execsql_test printf-17.8 { + SELECT format('%.5e', 199990000.0); +} 1.99990e+08 +do_execsql_test printf-17.9 { + SELECT format('%.5f', 199990000.0); +} 199990000.00000 +do_execsql_test printf-17.10 { + SELECT format('%.5g', 199990000.0); +} 1.9999e+08 +do_execsql_test printf-17.11 { + SELECT format('%.30f',1.0000000000000000076e-50); +} 0.000000000000000000000000000000 + +#------------------------------------------------------------------------- +# dbsqlfuzz ad651aad4bb2100f3a724129a555d8d773366d46 +# +db close +sqlite3 db test.db +sqlite3_db_config_lookaside db 0 0 0 +do_execsql_test printf-18.1 { + SELECT length( format('%,.249f', -5.0e-300) ); +} {252} + finish_test diff --git a/test/pushdown.test b/test/pushdown.test index ca816fb21..1fbe6f34c 100644 --- a/test/pushdown.test +++ b/test/pushdown.test @@ -122,5 +122,109 @@ do_execsql_test 3.4 { } { 2 0 0 } - + +# 2023-02-22 https://sqlite.org/forum/forumpost/bcc4375032 +# Performance regression caused by check-in [1ad41840c5e0fa70] from 2022-11-25. +# That check-in added a new restriction on push-down. The new restriction is +# no longer necessary after check-in [27655c9353620aa5] from 2022-12-14. +# +do_execsql_test 3.5 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INT, b INT, c TEXT, PRIMARY KEY(a,b)) WITHOUT ROWID; + INSERT INTO t1(a,b,c) VALUES + (1,100,'abc'), + (2,200,'def'), + (3,300,'abc'); + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(a INT, b INT, c TEXT, PRIMARY KEY(a,b)) WITHOUT ROWID; + INSERT INTO t2(a,b,c) VALUES + (1,110,'efg'), + (2,200,'hij'), + (3,330,'klm'); + CREATE VIEW v3 AS + SELECT a, b, c FROM t1 + UNION ALL + SELECT a, b, 'xyz' FROM t2; + SELECT * FROM v3 WHERE a=2 AND b=200; +} {2 200 def 2 200 xyz} +do_eqp_test 3.6 { + SELECT * FROM v3 WHERE a=2 AND b=200; +} { + QUERY PLAN + |--CO-ROUTINE v3 + | `--COMPOUND QUERY + | |--LEFT-MOST SUBQUERY + | | `--SEARCH t1 USING PRIMARY KEY (a=? AND b=?) + | `--UNION ALL + | `--SEARCH t2 USING PRIMARY KEY (a=? AND b=?) + `--SCAN v3 +} +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# We want both arms of the compound subquery to use the +# primary key. + +# The following is a test of the count-of-view optimization. This does +# not have anything to do with push-down. It is here because this is a +# convenient place to put the test. +# +do_execsql_test 3.7 { + SELECT count(*) FROM v3; +} 6 +do_eqp_test 3.8 { + SELECT count(*) FROM v3; +} { + QUERY PLAN + |--SCAN CONSTANT ROW + |--SCALAR SUBQUERY xxxxxx + | `--SCAN t1 + `--SCALAR SUBQUERY xxxxxx + `--SCAN t2 +} +# ^^^^^^^^^^^^^^^^^^^^ +# The query should be converted into: +# SELECT (SELECT count(*) FROM t1)+(SELECT count(*) FROM t2) + +# 2023-05-09 https://sqlite.org/forum/forumpost/a7d4be7fb6 +# Restriction (9) on the push-down optimization. +# +reset_db +db null - +do_execsql_test 4.1 { + CREATE TABLE t1(a INT); + CREATE TABLE t2(b INT); + CREATE TABLE t3(c INT); + INSERT INTO t3(c) VALUES(3); + CREATE TABLE t4(d INT); + CREATE TABLE t5(e INT); + INSERT INTO t5(e) VALUES(5); + CREATE VIEW v6(f,g) AS SELECT d, e FROM t4 RIGHT JOIN t5 ON true; + SELECT * FROM t1 JOIN t2 ON false RIGHT JOIN t3 ON true CROSS JOIN v6; +} {- - 3 - 5} +do_execsql_test 4.2 { + SELECT * FROM v6 JOIN t5 ON false RIGHT JOIN t3 ON true; +} {- - - 3} +do_execsql_test 4.3 { + SELECT * FROM t1 JOIN t2 ON false JOIN v6 ON true RIGHT JOIN t3 ON true; +} {- - - - 3} + +# 2023-05-15 https://sqlite.org/forum/forumpost/f3f546025a +# This is restriction (6) on sqlite3ExprIsSingleTableConstraint(). +# That restriction (now) used to implement restriction (9) on push-down. +# It is used for other things too, so it is not purely a push-down +# restriction. But it seems convenient to put it here. +# +reset_db +db null - +do_execsql_test 5.0 { + CREATE TABLE t1(a INT); INSERT INTO t1 VALUES(1); + CREATE TABLE t2(b INT); INSERT INTO t2 VALUES(2); + CREATE TABLE t3(c INT); INSERT INTO t3 VALUES(3); + CREATE TABLE t4(d INT); INSERT INTO t4 VALUES(4); + CREATE TABLE t5(e INT); INSERT INTO t5 VALUES(5); + SELECT * + FROM t1 JOIN t2 ON null RIGHT JOIN t3 ON true + LEFT JOIN (t4 JOIN t5 ON d+1=e) ON d=4 + WHERE e>0; +} {- - 3 4 5} + finish_test diff --git a/test/returning1.test b/test/returning1.test index 5ab44f2cb..6c098dc25 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -414,6 +414,7 @@ do_execsql_test 17.0 { do_execsql_test 18.0 { CREATE TABLE v0(c1 INT); CREATE VIEW view_2(c1) AS SELECT CASE WHEN c1 COLLATE TRUE THEN TRUE ELSE TRUE END FROM v0; + CREATE TRIGGER x1 INSTEAD OF INSERT ON view_2 BEGIN SELECT true; END; } do_catchsql_test 18.1 { INSERT INTO view_2 DEFAULT VALUES RETURNING *; diff --git a/test/scanstatus.test b/test/scanstatus.test index fa00a356b..549e7fd3c 100644 --- a/test/scanstatus.test +++ b/test/scanstatus.test @@ -45,12 +45,20 @@ proc do_scanstatus_test {tn res} { uplevel [list do_test $tn [list set {} $ret] [list {*}$res]] } -do_execsql_test 1.1 { SELECT count(*) FROM t1, t2; } 6 -do_scanstatus_test 1.2 { +do_execsql_test 1.1a { SELECT count(*) FROM t1, t2; } 6 +do_scanstatus_test 1.1b { nLoop 1 nVisit 2 nEst 1048576.0 zName t1 zExplain {SCAN t1} nLoop 2 nVisit 6 nEst 1048576.0 zName t2 zExplain {SCAN t2} } +sqlite3_db_config db STMT_SCANSTATUS 0 + +do_execsql_test 1.2a { SELECT count(*) FROM t1, t2; } 6 +do_scanstatus_test 1.2b { +} + +sqlite3_db_config db STMT_SCANSTATUS 1 + do_execsql_test 1.3 { ANALYZE; SELECT count(*) FROM t1, t2; @@ -94,6 +102,7 @@ do_scanstatus_test 1.10 { # Try a few different types of scans. # reset_db +sqlite3_db_config db STMT_SCANSTATUS 1 do_execsql_test 2.1 { CREATE TABLE x1(i INTEGER PRIMARY KEY, j); INSERT INTO x1 VALUES(1, 'one'); @@ -277,6 +286,7 @@ do_scanstatus_test 4.2.2 { # Further tests of different scan types. # reset_db +sqlite3_db_config db STMT_SCANSTATUS 1 proc tochar {i} { set alphabet {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} return [lindex $alphabet [expr $i % [llength $alphabet]]] @@ -365,6 +375,7 @@ do_eqp_test 5.5.1 { } { QUERY PLAN |--SCAN t3 + |--BLOOM FILTER ON t1 (c=?) `--SEARCH t1 USING AUTOMATIC COVERING INDEX (c=?) } do_execsql_test 5.5.2 { diff --git a/test/scanstatus2.test b/test/scanstatus2.test index 497cbe67d..fb62af210 100644 --- a/test/scanstatus2.test +++ b/test/scanstatus2.test @@ -19,6 +19,8 @@ ifcapable !scanstatus { return } +sqlite3_db_config db STMT_SCANSTATUS 1 + do_execsql_test 1.0 { CREATE TABLE t1(a, b); CREATE TABLE t2(x, y); @@ -141,6 +143,7 @@ QUERY (nCycle=nnn) #------------------------------------------------------------------------- ifcapable fts5 { reset_db + sqlite3_db_config db STMT_SCANSTATUS 1 do_execsql_test 2.0 { CREATE VIRTUAL TABLE ft USING fts5(a); INSERT INTO ft VALUES('abc'); @@ -158,6 +161,7 @@ QUERY (nCycle=nnn) #------------------------------------------------------------------------- reset_db +sqlite3_db_config db STMT_SCANSTATUS 1 do_execsql_test 3.0 { CREATE TABLE x1(a, b); CREATE TABLE x2(c, d); @@ -173,11 +177,13 @@ do_graph_test 2.1 { QUERY (nCycle=nnn) --SCAN x1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON x2(c, d) (nCycle=nnn) +--BLOOM FILTER ON x2 (c=?) --SEARCH x2 USING AUTOMATIC COVERING INDEX (c=?) (nCycle=nnn) } #------------------------------------------------------------------------- reset_db +sqlite3_db_config db STMT_SCANSTATUS 1 do_execsql_test 4.0 { CREATE TABLE rt1 (id INTEGER PRIMARY KEY, x1, x2); CREATE TABLE rt2 (id, x1, x2); @@ -189,6 +195,7 @@ do_graph_test 4.1 { QUERY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON rt2(x1, id, x2) (nCycle=nnn) +--BLOOM FILTER ON rt2 (x1=?) --SEARCH rt2 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) } @@ -198,6 +205,7 @@ do_graph_test 4.2 { QUERY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON rt2(x1, id) (nCycle=nnn) +--BLOOM FILTER ON rt2 (x1=?) --SEARCH rt2 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) } @@ -215,6 +223,7 @@ do_graph_test 4.4 { QUERY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON rt2(x1, id) WHERE (nCycle=nnn) +--BLOOM FILTER ON rt2 (x1=?) --SEARCH rt2 USING AUTOMATIC PARTIAL COVERING INDEX (x1=?) (nCycle=nnn) } @@ -229,9 +238,38 @@ QUERY (nCycle=nnn) ----USE TEMP B-TREE FOR GROUP BY --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON v1(x1, cnt) (nCycle=nnn) +--BLOOM FILTER ON v1 (x1=?) --SEARCH v1 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) } +#------------------------------------------------------------------------- +reset_db + +ifcapable trace { + do_execsql_test 5.0 { + CREATE TABLE t1(x, y); + CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN + SELECT 1; + END; + INSERT INTO t1 VALUES(1, 2); + } + + proc trace {stmt sql} { + array set A [sqlite3_stmt_scanstatus -flags complex [format %x $stmt] 0] + lappend ::trace_explain $A(zExplain) + } + db trace_v2 trace + + set ::trace_explain [list] + do_execsql_test 5.1 { + DELETE FROM t1 WHERE x=1; + } + + do_test 5.2 { + set ::trace_explain + } {{SCAN t1} {SCAN t1} {SCAN t1}} +} + finish_test diff --git a/test/seekscan1.test b/test/seekscan1.test index e68d71449..0e53c1916 100644 --- a/test/seekscan1.test +++ b/test/seekscan1.test @@ -24,6 +24,7 @@ do_execsql_test 1.0 { ANALYZE; } + do_execsql_test 1.1 { SELECT a,b,c FROM t1 WHERE b IN (234, 345) AND c BETWEEN 6 AND 6.5 AND a='abc' @@ -59,5 +60,9 @@ do_execsql_test 1.4 { abc 345 7 } +do_execsql_test 1.5 { + SELECT a,b,c FROM t1 WHERE b IN (235, 345) AND c<=3 AND a='abc' ORDER BY a, b; +} + finish_test diff --git a/test/selectC.test b/test/selectC.test index 63851ca5d..42fa1d11b 100644 --- a/test/selectC.test +++ b/test/selectC.test @@ -230,6 +230,11 @@ do_execsql_test selectC-4.2 { select a from (select distinct a, b from t_distinct_bug) } {1 1 1} +do_execsql_test selectC-4.2b { + CREATE VIEW v42b AS SELECT DISTINCT a, b FROM t_distinct_bug; + SELECT a FROM v42b; +} {1 1 1} + do_execsql_test selectC-4.3 { select a, udf() from (select distinct a, b from t_distinct_bug) } {1 1 1 2 1 3} diff --git a/test/selectH.test b/test/selectH.test new file mode 100644 index 000000000..a97679bda --- /dev/null +++ b/test/selectH.test @@ -0,0 +1,118 @@ +# 2023-02-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. +# +#*********************************************************************** +# +# Test cases for the omit-unused-subquery-column optimization. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix selectH + +do_execsql_test 1.1 { + CREATE TABLE t1( + c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, + c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, + c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, + c30, c31, c32, c33, c34, c35, c36, c37, c38, c39, + c40, c41, c42, c43, c44, c45, c46, c47, c48, c49, + c50, c51, c52, c53, c54, c55, c56, c57, c58, c59, + c60, c61, c62, c63, c64, c65 + ); + INSERT INTO t1 VALUES( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65 + ); + CREATE INDEX t1c60 ON t1(c60); +} + +# The SQL counter(N) function adjusts the value of the global +# TCL variable ::selectH_cnt by the value N and returns the new +# value. By putting calls to counter(N) as unused columns in a +# view or subquery, we can check to see if the counter gets incremented, +# and if not that means that the unused column was omitted. +# +unset -nocomplain selectH_cnt +set selectH_cnt 0 +proc selectH_counter {amt} { + global selectH_cnt + incr selectH_cnt $amt + return $selectH_cnt +} +db func counter selectH_counter + +do_execsql_test 1.2 { + SELECT DISTINCT c44 FROM ( + SELECT c0 AS a, *, counter(1) FROM t1 + UNION ALL + SELECT c1 AS a, *, counter(1) FROM t1 + ) WHERE c60=60; +} {44} +do_test 1.3 { + set ::selectH_cnt +} {0} + +do_execsql_test 2.1 { + SELECT a FROM ( + SELECT counter(1) AS cnt, c15 AS a, *, c62 AS b FROM t1 + UNION ALL + SELECT counter(1) AS cnt, c16 AS a, *, c61 AS b FROM t1 + ORDER BY b + ); +} {16 15} +do_test 2.2 { + set ::selectH_cnt +} {0} + +do_execsql_test 3.1 { + CREATE VIEW v1 AS + SELECT c16 AS a, *, counter(1) AS x FROM t1 + UNION ALL + SELECT c17 AS a, *, counter(1) AS x FROM t1 + UNION ALL + SELECT c18 AS a, *, counter(1) AS x FROM t1 + UNION ALL + SELECT c19 AS a, *, counter(1) AS x FROM t1; + SELECT count(*) FROM v1 WHERE c60=60; +} {4} +do_test 3.2 { + set ::selectH_cnt +} {0} +do_execsql_test 3.3 { + SELECT count(a) FROM v1 WHERE c60=60; +} {4} +do_execsql_test 3.4 { + SELECT a FROM v1 WHERE c60=60; +} {16 17 18 19} +do_test 3.5 { + set ::selectH_cnt +} {0} +do_execsql_test 3.6 { + SELECT x FROM v1 WHERE c60=60; +} {1 2 3 4} +do_test 3.7 { + set ::selectH_cnt +} {4} + +# 2023-02-25 dbsqlfuzz bf1d3ed6e0e0dd8766027797d43db40c776d2b15 +# +do_execsql_test 4.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + SELECT 1 FROM (SELECT DISTINCT name COLLATE rtrim FROM sqlite_schema + UNION ALL SELECT a FROM t1); +} 1 + +finish_test diff --git a/test/shell1.test b/test/shell1.test index fbbda0348..d01737910 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -175,6 +175,16 @@ do_test shell1-1.16.1 { set x [catchcmd "-version test.db" ""] } {/3.[0-9.]+ 20\d\d-[01]\d-\d\d \d\d:\d\d:\d\d [0-9a-f]+/} +# Handle no-more-options option +forcedelete ./--db +do_test shell1-1.17.1 { + catchcmd {-- --db "CREATE TABLE T(c1);"} +} {0 {}} +do_test shell1-1.17.2 { + catchcmd {-- --db "SELECT name from sqlite_schema;"} +} {0 T} +forcedelete ./--db + #---------------------------------------------------------------------------- # Test cases shell1-2.*: Basic "dot" command token parsing. # @@ -244,6 +254,11 @@ do_test shell1-2.4.2 { catchcmd "test.db" ".mode \"csv\"" } {0 {}} +# check that certain quoted arg escapes work +do_test shell1-2.5.1 { + catchcmd ":memory:" ".print \"\\060\\077 \\x3f\\x30 \\a\\t\"" +} [list 0 "0? ?0 \a\t"] + #---------------------------------------------------------------------------- # Test cases shell1-3.*: Basic test that "dot" command can be called. diff --git a/test/shell2.test b/test/shell2.test index aba04a149..3fad4bd66 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -216,5 +216,51 @@ do_test shell2-1.4.9 { done 2}} +# Verify that generate_series stays sane near 64-bit range boundaries. +# See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280 +do_test shell2-1.4.10 { + set res [catchcmd :memory: [string trim { + SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1); + SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1); + SELECT avg(rowid),min(value),max(value) FROM generate_series( + -9223372036854775808,9223372036854775807,1085102592571150095); + SELECT * FROM generate_series(-9223372036854775808,9223372036854775807, + 9223372036854775807); + SELECT value,rowid FROM generate_series(-4611686018427387904, + 4611686018427387904, 4611686018427387904) ORDER BY value DESC; + SELECT * FROM generate_series(0,-2,-1); + SELECT * FROM generate_series(0,-2); + SELECT * FROM generate_series(0,2) LIMIT 3;}]] +} {0 {9223372036854775807 +9223372036854775807 +9.5|-9223372036854775808|9223372036854775807 +-9223372036854775808 +-1 +9223372036854775806 +4611686018427387904|3 +0|2 +-4611686018427387904|1 +0 +-1 +-2 +0 +1 +2}} + +# Bug discovered while messing around, .import hangs with +# bit 7 set in column separator. +do_test shell2-1.4.11 { + forcedelete dummy.csv + set df [open dummy.csv w] + puts $df dog,cat + close $df + set res [catchcmd :memory: [string trim { + CREATE TABLE t(line text); +.mode ascii +.separator "\377" "\n" +.import dummy.csv t + SELECT count(*) FROM t;}]] +} {0 1} + finish_test diff --git a/test/skipscan1.test b/test/skipscan1.test index 94062fb17..bd5b83d34 100644 --- a/test/skipscan1.test +++ b/test/skipscan1.test @@ -419,4 +419,14 @@ do_execsql_test skipscan1-4.10 { AND a <= 10; } {3} +# 2023-03-24 https://sqlite.org/forum/forumpost/8cc1dc0fe9 +# +reset_db +do_execsql_test skipscan1-5.0 { + CREATE TABLE t1(a TEXT, UNIQUE(a,a,a)); + INSERT INTO t1 VALUES (hex(zeroblob(241))),(1),(2),(3); + ANALYZE; + SELECT max(a) FROM t1 WHERE a IN t1; +} {3} + finish_test diff --git a/test/speedtest1.c b/test/speedtest1.c index 12c8d69b5..6aff89b37 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -12,6 +12,7 @@ static const char zHelp[] = " --checkpoint Run PRAGMA wal_checkpoint after each test case\n" " --exclusive Enable locking_mode=EXCLUSIVE\n" " --explain Like --sqlonly but with added EXPLAIN keywords\n" + " --fullfsync Enable fullfsync=TRUE\n" " --heap SZ MIN Memory allocator uses SZ bytes & min allocation MIN\n" " --incrvacuum Enable incremenatal vacuum mode\n" " --journal M Set the journal_mode to M\n" @@ -39,6 +40,7 @@ static const char zHelp[] = " --size N Relative test size. Default=100\n" " --strict Use STRICT table where appropriate\n" " --stats Show statistics at the end\n" + " --stmtscanstatus Activate SQLITE_DBCONFIG_STMT_SCANSTATUS\n" " --temp N N from 0 to 9. 0: no temp table. 9: all temp tables\n" " --testset T Run test-set T (main, cte, rtree, orm, fp, debug)\n" " --trace Turn on SQL tracing\n" @@ -100,6 +102,7 @@ static struct Global { int nRepeat; /* Repeat selects this many times */ int doCheckpoint; /* Run PRAGMA wal_checkpoint after each trans */ int nReserve; /* Reserve bytes */ + int stmtScanStatus; /* True to activate Stmt ScanStatus reporting */ int doBigTransactions; /* Enable transactions on tests 410 and 510 */ const char *zWR; /* Might be WITHOUT ROWID */ const char *zNN; /* Might be NOT NULL */ @@ -2201,6 +2204,7 @@ int main(int argc, char **argv){ int doAutovac = 0; /* True for --autovacuum */ int cacheSize = 0; /* Desired cache size. 0 means default */ int doExclusive = 0; /* True for --exclusive */ + int doFullFSync = 0; /* True for --fullfsync */ int nHeap = 0, mnHeap = 0; /* Heap size from --heap */ int doIncrvac = 0; /* True for --incrvacuum */ const char *zJMode = 0; /* Journal mode */ @@ -2265,6 +2269,8 @@ int main(int argc, char **argv){ cacheSize = integerValue(argv[++i]); }else if( strcmp(z,"exclusive")==0 ){ doExclusive = 1; + }else if( strcmp(z,"fullfsync")==0 ){ + doFullFSync = 1; }else if( strcmp(z,"checkpoint")==0 ){ g.doCheckpoint = 1; }else if( strcmp(z,"explain")==0 ){ @@ -2391,6 +2397,8 @@ int main(int argc, char **argv){ }else if( strcmp(z,"reserve")==0 ){ ARGC_VALUE_CHECK(1); g.nReserve = atoi(argv[++i]); + }else if( strcmp(z,"stmtscanstatus")==0 ){ + g.stmtScanStatus = 1; }else if( strcmp(z,"without-rowid")==0 ){ if( strstr(g.zWR,"WITHOUT")!=0 ){ /* no-op */ @@ -2474,6 +2482,9 @@ int main(int argc, char **argv){ if( g.nReserve>0 ){ sqlite3_file_control(g.db, 0, SQLITE_FCNTL_RESERVE_BYTES, &g.nReserve); } + if( g.stmtScanStatus ){ + sqlite3_db_config(g.db, SQLITE_DBCONFIG_STMT_SCANSTATUS, 1, 0); + } /* Set database connection options */ sqlite3_create_function(g.db, "random", 0, SQLITE_UTF8, 0, randomFunc, 0, 0); @@ -2504,7 +2515,11 @@ int main(int argc, char **argv){ if( cacheSize ){ speedtest1_exec("PRAGMA cache_size=%d", cacheSize); } - if( noSync ) speedtest1_exec("PRAGMA synchronous=OFF"); + if( noSync ){ + speedtest1_exec("PRAGMA synchronous=OFF"); + }else if( doFullFSync ){ + speedtest1_exec("PRAGMA fullfsync=ON"); + } if( doExclusive ){ speedtest1_exec("PRAGMA locking_mode=EXCLUSIVE"); } diff --git a/test/tabfunc01.test b/test/tabfunc01.test index 0a7e3bc24..8e90c549f 100644 --- a/test/tabfunc01.test +++ b/test/tabfunc01.test @@ -61,7 +61,7 @@ do_execsql_test tabfunc01-1.8 { } {30 25 20 15 10 5 0} do_execsql_test tabfunc01-1.9 { SELECT rowid, * FROM generate_series(0,32,5) ORDER BY value DESC; -} {1 30 2 25 3 20 4 15 5 10 6 5 7 0} +} {7 30 6 25 5 20 4 15 3 10 2 5 1 0} do_execsql_test tabfunc01-1.10 { SELECT rowid, * FROM generate_series(0,32,5) ORDER BY +value DESC; } {7 30 6 25 5 20 4 15 3 10 2 5 1 0} @@ -270,6 +270,7 @@ do_test tabfunc01-750 { } {5.0 x5 | 7.0 x7 | 13.0 x13 | 17.0 x17 | 23.0 x23 |} # ticket https://www.sqlite.org/src/info/2ae0c599b735d59e +# Verification of testtag-20230227a do_test tabfunc01-751 { db eval { SELECT aa.value, bb.value, '|' diff --git a/test/testrunner.tcl b/test/testrunner.tcl index a71b055e4..4e21ce08a 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -71,26 +71,32 @@ of sub-processes the test script uses to run tests. # switch. # proc guess_number_of_cores {} { - set ret 4 + if {[catch {number_of_cores} ret]} { + set ret 4 - if {$::tcl_platform(os)=="Darwin"} { - set cmd "sysctl -n hw.logicalcpu" - } else { - set cmd "nproc" - } - catch { - set fd [open "|$cmd" r] - set ret [gets $fd] - close $fd - set ret [expr $ret] + if {$::tcl_platform(os)=="Darwin"} { + set cmd "sysctl -n hw.logicalcpu" + } else { + set cmd "nproc" + } + catch { + set fd [open "|$cmd" r] + set ret [gets $fd] + close $fd + set ret [expr $ret] + } } return $ret } proc default_njob {} { set nCore [guess_number_of_cores] - set nHelper [expr int($nCore*0.75)] - expr $nHelper>0 ? $nHelper : 1 + if {$nCore<=2} { + set nHelper 1 + } else { + set nHelper [expr int($nCore*0.5)] + } + return $nHelper } #------------------------------------------------------------------------- @@ -878,6 +884,9 @@ proc run_testset {} { sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) set tm [lindex [time { make_new_testset }] 0] +if {$TRG(nJob)>1} { + puts "splitting work across $TRG(nJob) cores" +} puts "built testset in [expr $tm/1000]ms.." run_testset trdb close diff --git a/test/thread3.test b/test/thread3.test new file mode 100644 index 000000000..25699b765 --- /dev/null +++ b/test/thread3.test @@ -0,0 +1,78 @@ +# 2023 May 13 +# +# 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 +if {[run_thread_tests]==0} { finish_test ; return } + +set testprefix thread3 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + PRAGMA journal_mode = DELETE; +} {delete} + +proc wait_for_var {varname} { + if {0==[uplevel [list info exists $varname]]} { + uplevel [list vwait $varname] + } + uplevel [list set $varname] +} + +set nAttempt 250 + +do_test 1.1 { + for {set i 0} {$i < $nAttempt} {incr i} { + unset -nocomplain X + unset -nocomplain Y + + sqlthread spawn X { + sqlite3 dbI test.db + dbI timeout 100 + set rc 1 + set nBusy 0 + while {$rc} { + set rc [catch { + dbI eval { INSERT INTO t1 VALUES(203, 'message') RETURNING a; } + } msg] + if {$rc} { incr nBusy } + } + dbI close + set nBusy + } + + sqlthread spawn Y { + sqlite3 dbR test.db + catch { + dbR eval { SELECT count(*) FROM t1 } + } msg + dbR close + set msg + } + + wait_for_var X + wait_for_var Y + incr nTotalBusy $X + } + + execsql { SELECT count(*) FROM t1 } + set {} {} +} {} + +do_execsql_test "1.Total BUSY errors: $nTotalBusy .2" { + SELECT count(*) FROM t1; +} $nAttempt + +finish_test + diff --git a/test/tkt-99378177930f87bd.test b/test/tkt-99378177930f87bd.test index ff73529bb..ba9fdc702 100644 --- a/test/tkt-99378177930f87bd.test +++ b/test/tkt-99378177930f87bd.test @@ -178,6 +178,8 @@ do_execsql_test tkt-99378-310 { # 2023-03-04 https://sqlite.org/forum/forumpost/a68313d054 # +# See also indexexpr1-2200 added on 2023-03-18. +# do_execsql_test tkt-99378-400 { DROP TABLE t1; CREATE TABLE t0(w); diff --git a/test/vt02.c b/test/vt02.c index f83fc9af9..ddad136fb 100644 --- a/test/vt02.c +++ b/test/vt02.c @@ -177,8 +177,6 @@ #include #endif -#ifndef SQLITE_OMIT_VIRTUALTABLE - /* Forward declarations */ typedef struct vt02_vtab vt02_vtab; typedef struct vt02_cur vt02_cur; @@ -381,7 +379,7 @@ static int vt02Filter( sqlite3_int64 v; pVal = 0; if( sqlite3_vtab_in_first(0, &pVal)!=SQLITE_MISUSE - || sqlite3_vtab_in_first(argv[iArg], &pVal)!=SQLITE_MISUSE + || sqlite3_vtab_in_first(argv[iArg], &pVal)!=SQLITE_ERROR ){ vt02ErrMsg(pCursor->pVtab, "unexpected success from sqlite3_vtab_in_first()"); @@ -420,7 +418,7 @@ static int vt02Filter( } if( bUseOffset ){ int nSkip = sqlite3_value_int(argv[iArg]); - while( nSkip-- > 0 ) vt02Next(pCursor); + while( nSkip-- > 0 && pCur->iiEof ) vt02Next(pCursor); } return SQLITE_OK; @@ -1001,10 +999,6 @@ static void vt02CoreInit(sqlite3 *db){ sqlite3_create_module(db, "vt02pkabcd", &vt02Module, (void*)zPkABCDSchema); } -#else -# define vt02CoreInit(db) -#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ - #ifdef TH3_VERSION static void vt02_init(th3state *p, int iDb, char *zArg){ vt02CoreInit(th3dbPointer(p, iDb)); diff --git a/test/walpersist.test b/test/walpersist.test index 692728dda..73e6de13d 100644 --- a/test/walpersist.test +++ b/test/walpersist.test @@ -121,6 +121,22 @@ do_test walpersist-3.4 { sqlite3 db test.db execsql { PRAGMA integrity_check } } {ok} - + +# 2023-05-07 https://sqlite.org/forum/forumpost/8130545bc6 +# +reset_db +do_test 4.1 { + db eval { + PRAGMA journal_mode=WAL; + CREATE TABLE t1(x); + } + file_control_persist_wal db 1 + db eval { + PRAGMA journal_mode=TRUNCATE; + PRAGMA journal_mode=MEMORY; + PRAGMA journal_mode=WAL; + PRAGMA journal_mode=PERSIST; + } +} {truncate memory wal persist} finish_test diff --git a/test/window1.test b/test/window1.test index 471ac2dce..783a739e3 100644 --- a/test/window1.test +++ b/test/window1.test @@ -2225,5 +2225,142 @@ do_execsql_test 72.1 { WHERE ('1' IS NOT ('abcde' NOTNULL)); } {1} +# 2023-03-28 https://sqlite.org/forum/forumpost/dc3b92cfa0 (Song Liu) +# +reset_db +do_execsql_test 73.0 { + CREATE TABLE t1(a INT); + INSERT INTO t1(a) VALUES(1),(2),(4); + CREATE VIEW t2(b,c) AS SELECT * FROM t1 JOIN t1 A ORDER BY sum(0) OVER(PARTITION BY 0); + CREATE TRIGGER x1 INSTEAD OF UPDATE ON t2 BEGIN SELECT true; END; +} +do_execsql_test 73.1 { + SELECT * FROM t2; +} {1 1 1 2 1 4 2 1 2 2 2 4 4 1 4 2 4 4} +do_execsql_test 73.2 { + UPDATE t2 SET c=99 WHERE b=4 RETURNING *; +} {4 99 4 99 4 99} +do_execsql_test 73.3 { + SELECT *, nth_value(15,2) OVER() FROM t2, t1 WHERE b=4; +} { + 4 1 1 15 + 4 2 1 15 + 4 4 1 15 + 4 1 2 15 + 4 2 2 15 + 4 4 2 15 + 4 1 4 15 + 4 2 4 15 + 4 4 4 15 +} +do_execsql_test 73.4 { + UPDATE t2 SET c=nth_value(15,2) OVER() FROM (SELECT * FROM t1) WHERE b=4 RETURNING *; +} { + 4 15 + 4 15 + 4 15 + 4 15 + 4 15 + 4 15 + 4 15 + 4 15 + 4 15 +} +do_execsql_test 73.5 { + DROP TRIGGER x1; +} +do_catchsql_test 73.6 { + UPDATE t2 SET c=99 WHERE b=4 RETURNING *; +} {1 {cannot modify t2 because it is a view}} +do_catchsql_test 73.7 { + UPDATE t2 SET c=nth_value(15,2) OVER() FROM (SELECT * FROM t1) WHERE b=4 RETURNING *; +} {1 {cannot modify t2 because it is a view}} + +# 2023-03-28 https://sqlite.org/forum/forumpost/bad532820c +# +reset_db +do_execsql_test 74.0 { + CREATE TABLE t1 (a INT, b INT); + CREATE TABLE t2 (c INT, d INT); + CREATE INDEX idx ON t1(abs(a)); + INSERT INTO t1 VALUES(1,2),(3,4); + INSERT INTO t2 VALUES(5,6),(7,8); +} +do_execsql_test 74.1 { + SELECT ( + SELECT count( a ) FROM t2 LIMIT 1 + ) + FROM t1; +} {2} ;# Verified using PG 14.2 +do_execsql_test 74.2 { + SELECT ( + SELECT count( a+c ) FROM t2 LIMIT 1 + ) + FROM t1; +} {2 2} ;# verified on PG 14.2. Crashes PG 9.6! +do_execsql_test 74.3 { + SELECT ( + SELECT count( ( SELECT(sum(0) OVER(ORDER BY c, abs(a))) ) ) + FROM t2 GROUP BY c LIMIT 1 + ) + FROM t1; +} {1 1} ;# verified on PG 14.2 +do_execsql_test 74.4 { + /* Original test case reported in https://sqlite.org/forum/forumpost/bad532820c + CREATE TABLE v0 (c1); + CREATE INDEX i ON v0 (c1, c1=1); + SELECT 0 FROM v0 AS a1 + WHERE (SELECT count((SELECT(sum(0) OVER(PARTITION BY(c1), (a1.c1=1) )))) + FROM v0 + GROUP BY hex(0)) + AND a1.c1=0; +} {} + +# 2023-04-11 https://sqlite.org/forum/forumpost/6c5678e3da +# An ALWAYS() turns out to be sometimes false. +# +do_execsql_test 75.0 { + DROP TABLE t1; + CREATE TABLE t1(a INT, b INT); + CREATE INDEX t1x ON t1(a+b); +} +do_catchsql_test 75.1 { + SELECT count((SELECT count(a0.a+a0.b) ORDER BY sum(0) OVER (PARTITION BY 0))) + FROM t1 AS a0 JOIN t1 AS a1 + GROUP BY a1.a; +} {1 {misuse of aggregate: count()}} + +# 2023-04-13 https://sqlite.org/forum/forumpost/0d48347967 +reset_db +do_execsql_test 76.0 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1(a,b) VALUES (111,222),(111,223),(118,229); + CREATE INDEX t1a ON t1(a); + CREATE TABLE t2(x INT); + INSERT INTO t2 VALUES (333),(444),(555); +} +do_execsql_test 76.1 { + SELECT c, (SELECT c + sum(1) OVER ()) AS "res" + FROM t2 LEFT JOIN (SELECT +a AS c FROM t1) AS v1 ON true + GROUP BY c + ORDER by c; +} {111 112 118 119} +# ^^^^^^^^^^^^^^^^^-- results verified against PG 14.2 + +do_execsql_test 76.2 { + CREATE TABLE t3(x); + CREATE TABLE t4(y); + INSERT INTO t3 VALUES(100), (200), (400); + INSERT INTO t4 VALUES(100), (300), (400); +} +do_execsql_test 76.3 { + SELECT (SELECT y+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y; +} {100 {} 400} +do_execsql_test 76.4 { + SELECT (SELECT y+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y GROUP BY x; +} {100 {} 400} +do_execsql_test 76.5 { + SELECT (SELECT max(y)+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y GROUP BY x; +} {100 {} 400} finish_test diff --git a/test/with1.test b/test/with1.test index 91cbc7952..7400a7adf 100644 --- a/test/with1.test +++ b/test/with1.test @@ -1124,7 +1124,7 @@ do_execsql_test 24.1 { CREATE VIEW v1 AS SELECT max(a), min(b) FROM t1 GROUP BY c; } do_test 24.1 { - set program [db eval {EXPLAIN SELECT 1 FROM v1,v1,v1}] + set program [db eval {EXPLAIN SELECT * FROM v1 AS aa, v1 AS bb, v1 AS cc}] expr [lsearch $program OpenDup]>0 } {1} do_execsql_test 24.2 { diff --git a/test/with3.test b/test/with3.test index 85889453a..650740dcc 100644 --- a/test/with3.test +++ b/test/with3.test @@ -109,6 +109,7 @@ ifcapable analyze { | `--RECURSIVE STEP | `--SCAN cnt |--SCAN y1 + |--BLOOM FILTER ON cnt (i=?) `--SEARCH cnt USING AUTOMATIC COVERING INDEX (i=?) }] } diff --git a/test/with6.test b/test/with6.test index 91d64fa29..2a4bfc646 100644 --- a/test/with6.test +++ b/test/with6.test @@ -363,9 +363,13 @@ do_eqp_test 400 { | |--SEARCH raw USING INDEX sqlite_autoindex_raw_1 (country=? AND date>? AND date=0; i=((int)aKWNext[i])-1){\n"); + printf(" for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){\n"); printf(" if( aKWLen[i]!=n ) continue;\n"); printf(" zKW = &zKWText[aKWOffset[i]];\n"); printf("#ifdef SQLITE_ASCII\n"); @@ -687,7 +687,7 @@ int main(int argc, char **argv){ printf(" if( j=SQLITE_N_KEYWORD ) return SQLITE_ERROR;\n"); + printf(" i++;\n"); printf(" *pzName = zKWText + aKWOffset[i];\n"); printf(" *pnName = aKWLen[i];\n"); printf(" return SQLITE_OK;\n"); diff --git a/tool/mkmsvcmin.tcl b/tool/mkmsvcmin.tcl index ef948a05c..6cb145db8 100644 --- a/tool/mkmsvcmin.tcl +++ b/tool/mkmsvcmin.tcl @@ -110,3 +110,4 @@ set data [string map [list " \$(ALL_TCL_TARGETS)" ""] $data] set data [string map [list "\$(TOP)\\src\\" "\$(TOP)\\"] $data] writeFile $toFileName $data +puts "generated $toFileName from $fromFileName" diff --git a/tool/omittest.tcl b/tool/omittest.tcl index b54f2984d..8862c685f 100644 --- a/tool/omittest.tcl +++ b/tool/omittest.tcl @@ -192,9 +192,9 @@ proc main {argv} { SQLITE_OMIT_AUTOMATIC_INDEX \ SQLITE_OMIT_AUTORESET \ SQLITE_OMIT_AUTOVACUUM \ + SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS \ SQLITE_OMIT_BETWEEN_OPTIMIZATION \ SQLITE_OMIT_BLOB_LITERAL \ - SQLITE_OMIT_BTREECOUNT \ SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA \ SQLITE_OMIT_CAST \ SQLITE_OMIT_CHECK \ @@ -225,7 +225,7 @@ proc main {argv} { SQLITE_OMIT_LOOKASIDE \ SQLITE_OMIT_MEMORYDB \ SQLITE_OMIT_OR_OPTIMIZATION \ - SQLITE_OMIT_PAGER_PRAGMAS \ + SQLITE_OMIT_PAGER_PRAGMAS \ SQLITE_OMIT_PARSER_TRACE \ SQLITE_OMIT_POPEN \ SQLITE_OMIT_PRAGMA \ @@ -244,8 +244,9 @@ proc main {argv} { SQLITE_OMIT_TRACE \ SQLITE_OMIT_TRIGGER \ SQLITE_OMIT_TRUNCATE_OPTIMIZATION \ + SQLITE_OMIT_TWOSIZE_LOOKASIDE \ SQLITE_OMIT_UPSERT \ - SQLITE_OMIT_UTF16 \ + SQLITE_OMIT_UTF \ SQLITE_OMIT_VACUUM \ SQLITE_OMIT_VIEW \ SQLITE_OMIT_VIRTUALTABLE \ @@ -258,24 +259,69 @@ proc main {argv} { set ::ENABLE_SYMBOLS [list \ SQLITE_ALLOW_ROWID_IN_VIEW \ SQLITE_DISABLE_DIRSYNC \ + SQLITE_DISABLE_FTS \ + SQLITE_DISABLE_INTRINSIC \ SQLITE_DISABLE_LFS \ + SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS \ + SQLITE_DISABLE_SKIPAHEAD_DISTINCT \ + SQLITE_ENABLE_API_ARMOR \ SQLITE_ENABLE_ATOMIC_WRITE \ + SQLITE_ENABLE_BATCH_ATOMIC_WRITE \ + SQLITE_ENABLE_BYTECODE_VTAB \ + SQLITE_ENABLE_CEROD \ SQLITE_ENABLE_COLUMN_METADATA \ + SQLITE_ENABLE_COLUMN_USED_MASK \ + SQLITE_ENABLE_COMMENTS \ + SQLITE_ENABLE_CORRUPT_PGNO \ + SQLITE_ENABLE_COSTMULT \ + SQLITE_ENABLE_CURSOR_HINTS \ + SQLITE_ENABLE_DBPAGE_VTAB \ + SQLITE_ENABLE_DBSTAT_VTAB \ SQLITE_ENABLE_EXPENSIVE_ASSERT \ - SQLITE_ENABLE_FTS3 \ - SQLITE_ENABLE_FTS3_PARENTHESIS \ - SQLITE_ENABLE_FTS4 \ + SQLITE_ENABLE_EXPLAIN_COMMENTS \ + SQLITE_ENABLE_FTS \ + SQLITE_ENABLE_GEOPOLY \ + SQLITE_ENABLE_HIDDEN_COLUMNS \ + SQLITE_ENABLE_ICU \ + SQLITE_ENABLE_ICU_COLLATIONS \ + SQLITE_ENABLE_INTERNAL_FUNCTIONS \ SQLITE_ENABLE_IOTRACE \ SQLITE_ENABLE_LOAD_EXTENSION \ SQLITE_ENABLE_LOCKING_STYLE \ + SQLITE_ENABLE_MATH_FUNCTIONS \ SQLITE_ENABLE_MEMORY_MANAGEMENT \ - SQLITE_ENABLE_MEMSYS3 \ - SQLITE_ENABLE_MEMSYS5 \ + SQLITE_ENABLE_MEMSYS \ + SQLITE_ENABLE_MODULE_COMMENTS \ + SQLITE_ENABLE_MULTIPLEX \ + SQLITE_ENABLE_MULTITHREADED_CHECKS \ + SQLITE_ENABLE_NORMALIZE \ + SQLITE_ENABLE_NULL_TRIM \ + SQLITE_ENABLE_OFFSET_SQL_FUNC \ SQLITE_ENABLE_OVERSIZE_CELL_CHECK \ + SQLITE_ENABLE_PREUPDATE_HOOK \ + SQLITE_ENABLE_QPSG \ + SQLITE_ENABLE_RBU \ SQLITE_ENABLE_RTREE \ - SQLITE_ENABLE_STAT3 \ + SQLITE_ENABLE_SELECTTRACE \ + SQLITE_ENABLE_SESSION \ + SQLITE_ENABLE_SETLK_TIMEOUT \ + SQLITE_ENABLE_SNAPSHOT \ + SQLITE_ENABLE_SORTER_MMAP\ + SQLITE_ENABLE_SORTER_REFERENCE \ + SQLITE_ENABLE_SORTER_REFERENCES \ + SQLITE_ENABLE_SQLLOG\ + SQLITE_ENABLE_STAT \ + SQLITE_ENABLE_STMT_SCANSTATUS \ + SQLITE_ENABLE_STMTVTAB \ + SQLITE_ENABLE_TREETRACE \ + SQLITE_ENABLE_UNKNOWN_FUNCTION \ + SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \ SQLITE_ENABLE_UNLOCK_NOTIFY \ SQLITE_ENABLE_UPDATE_DELETE_LIMIT \ + SQLITE_ENABLE_URI_00_ERROR \ + SQLITE_ENABLE_VFSTRACE \ + SQLITE_ENABLE_WHERETRACE \ + SQLITE_ENABLE_ZIPVFS \ ] # Process any command line options. diff --git a/tool/showwal.c b/tool/showwal.c index 84e4afa13..a8e9b53b3 100644 --- a/tool/showwal.c +++ b/tool/showwal.c @@ -233,16 +233,21 @@ static void print_oneline_frame(int iFrame, Cksum *pCksum){ extendCksum(pCksum, getContent(iStart+24, pagesize), pagesize, 0); s0 = getInt32(aData+16); s1 = getInt32(aData+20); - fprintf(stdout, "Frame %4d: %6d %6d 0x%08x,%08x 0x%08x,%08x %s\n", + fprintf(stdout, "Frame %4d: %6d %6d 0x%08x,%08x 0x%08x,%08x", iFrame, getInt32(aData), getInt32(aData+4), getInt32(aData+8), getInt32(aData+12), s0, - s1, - (s0==pCksum->s0 && s1==pCksum->s1) ? "" : "cksum-fail" + s1 ); + if( s0==pCksum->s0 && s1==pCksum->s1 ){ + fprintf(stdout, "\n"); + }else{ + fprintf(stdout, " should be 0x%08x,%08x\n", + pCksum->s0, pCksum->s1); + } /* Reset the checksum so that a single frame checksum failure will not ** cause all subsequent frames to also show a failure. */ diff --git a/tool/speed-check.sh b/tool/speed-check.sh index 4e070565e..06cbd3574 100644 --- a/tool/speed-check.sh +++ b/tool/speed-check.sh @@ -158,6 +158,9 @@ while test "$1" != ""; do --fp) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset fp" ;; + --stmtscanstatus) + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --stmtscanstatus" + ;; -*) CC_OPTS="$CC_OPTS $1" ;; diff --git a/tool/split-sqlite3c.tcl b/tool/split-sqlite3c.tcl index 9751e7de9..0308431da 100644 --- a/tool/split-sqlite3c.tcl +++ b/tool/split-sqlite3c.tcl @@ -48,7 +48,14 @@ set filecnt 0 proc write_one_file {content} { global filecnt incr filecnt - set out [open sqlite3-$filecnt.c w] + set label $filecnt + if {$filecnt>9} { + set label [string index ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop \ + [expr {$filecnt-10}]] + } else { + set label $filecnt + } + set out [open sqlite3-$label.c w] fconfigure $out -translation lf puts -nonewline $out $content close $out From 80803645126cc39d4a36b1d982af97d6725192ae Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 18 Jul 2023 15:55:42 -0400 Subject: [PATCH 099/340] adjustments for constant time function volatile variables --- src/crypto_impl.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 1451aa351..e45098a9b 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -285,7 +285,7 @@ void sqlcipher_deactivate() { Note: As suggested by Joachim Schipper (joachim.schipper@fox-it.com) */ void* sqlcipher_memset(void *v, unsigned char value, sqlite_uint64 len) { - sqlite_uint64 i = 0; + volatile sqlite_uint64 i = 0; volatile unsigned char *a = v; if (v == NULL) return v; @@ -302,8 +302,8 @@ void* sqlcipher_memset(void *v, unsigned char value, sqlite_uint64 len) { matches a single value (i.e. the memory is all zeros) returns 0 if match, 1 of no match */ int sqlcipher_ismemset(const void *v, unsigned char value, sqlite_uint64 len) { - const unsigned char *a = v; - sqlite_uint64 i = 0, result = 0; + const volatile unsigned char *a = v; + volatile sqlite_uint64 i = 0, result = 0; for(i = 0; i < len; i++) { result |= a[i] ^ value; @@ -315,8 +315,8 @@ int sqlcipher_ismemset(const void *v, unsigned char value, sqlite_uint64 len) { /* constant time memory comparison routine. returns 0 if match, 1 if no match */ int sqlcipher_memcmp(const void *v0, const void *v1, int len) { - const unsigned char *a0 = v0, *a1 = v1; - int i = 0, result = 0; + const volatile unsigned char *a0 = v0, *a1 = v1; + volatile int i = 0, result = 0; for(i = 0; i < len; i++) { result |= a0[i] ^ a1[i]; From 1e25210634d49f664bd589b55ee7ad1dd23953cd Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 16 Aug 2023 12:11:18 -0400 Subject: [PATCH 100/340] do not allow key to be changed on a connection after it has been used for a successful operation --- src/crypto.c | 11 +++++++++ src/crypto.h | 8 ++++--- src/crypto_impl.c | 5 +++-- test/sqlcipher-core.test | 42 +++++++++++++++++++++++++---------- test/sqlcipher-integrity.test | 38 +++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 17 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 5dcda8492..784f387a0 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -770,6 +770,8 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error decrypting page %d data: %d", pgno, rc); sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); + } else { + ctx->flags |= CIPHER_FLAG_KEY_USED; /* inline to avoid function call */ } memcpy(pData, buffer, page_sz); /* copy buffer data back to pData and return */ return pData; @@ -804,6 +806,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } + ctx->flags |= CIPHER_FLAG_KEY_USED; /* inline to avoid function call */ return buffer; /* return persistent buffer data, pData remains intact */ break; @@ -833,6 +836,14 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { sqlite3_file *fd; codec_ctx *ctx; + ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); + + if(ctx != NULL && sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_KEY_USED)) { + /* there is already a codec attached to this database, so we should not proceed */ + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipherCodecAttach: no codec attached to db, exiting"); + return SQLITE_OK; + } + /* check if the sqlite3_file is open, and if not force handle to NULL */ if((fd = sqlite3PagerFile(pPager))->pMethods == 0) fd = NULL; diff --git a/src/crypto.h b/src/crypto.h index 55fd8fbd3..eb38092ed 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -103,9 +103,10 @@ void sqlite3pager_reset(Pager *pPager); #endif /* possible flags for cipher_ctx->flags */ -#define CIPHER_FLAG_HMAC 0x01 -#define CIPHER_FLAG_LE_PGNO 0x02 -#define CIPHER_FLAG_BE_PGNO 0x04 +#define CIPHER_FLAG_HMAC (1 << 0) +#define CIPHER_FLAG_LE_PGNO (1 << 1) +#define CIPHER_FLAG_BE_PGNO (1 << 2) +#define CIPHER_FLAG_KEY_USED (1 << 3) #ifndef DEFAULT_CIPHER_FLAGS #define DEFAULT_CIPHER_FLAGS CIPHER_FLAG_HMAC | CIPHER_FLAG_LE_PGNO @@ -356,3 +357,4 @@ void sqlcipher_vdbe_return_string(Parse*, const char*, const char*, int); #endif #endif /* END SQLCIPHER */ + diff --git a/src/crypto_impl.c b/src/crypto_impl.c index e45098a9b..82d6f446d 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -583,8 +583,8 @@ void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey) { } static void sqlcipher_set_derive_key(codec_ctx *ctx, int derive) { - if(ctx->read_ctx != NULL) ctx->read_ctx->derive_key = 1; - if(ctx->write_ctx != NULL) ctx->write_ctx->derive_key = 1; + if(ctx->read_ctx != NULL) ctx->read_ctx->derive_key = derive; + if(ctx->write_ctx != NULL) ctx->write_ctx->derive_key = derive; } /** @@ -1541,6 +1541,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { if( rc!=SQLITE_OK ) goto handle_error; sqlcipherCodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz); + sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_KEY_USED); sqlcipherCodecAttach(db, 0, keyspec, keyspec_sz); srcfile = sqlite3PagerFile(pSrc->pBt->pPager); diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index 45c3588d1..573c2a553 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -194,6 +194,8 @@ file delete -force test.db setup test.db "'testkey'" do_test attach-database-with-default-key { sqlite_orig db2 test2.db + set rc {} + execsql { PRAGMA key = 'testkey'; PRAGMA cipher_add_random = "x'deadbaad'"; @@ -577,29 +579,45 @@ do_test journal-mode-wal { db close file delete -force test.db +# open a database and try to use an invalid +# passphrase. verify that an error is returned +# and that data couldn't be read. without closing the databsae +# set the correct key and verify it is working. setup test.db "'testkey'" -do_test multiple-key-calls-safe-1 { +do_test multiple-key-calls-safe-wrong-key-first { + sqlite_orig db test.db + set rc {} + + lappend rc [catchsql { + PRAGMA key = 'testkey2'; + SELECT count(*) FROM sqlite_schema; + }] + + lappend rc [execsql { + PRAGMA key = 'testkey'; + SELECT count(*) FROM sqlite_schema; + }] +} {{1 {file is not a database}} {ok 1}} +db close +file delete -force test.db + +# open a databse and use the valid key. Then +# use pragma key to try to set an invalid key +# without closing the database. It should not do anything + +setup test.db "'testkey'" +do_test multiple-key-calls-safe { sqlite_orig db test.db execsql { PRAGMA key = 'testkey'; PRAGMA cache_size = 0; SELECT name FROM sqlite_schema WHERE type='table'; - } -} {ok t1} - -do_test multiple-key-calls-safe-2 { - catchsql { PRAGMA key = 'wrong key'; SELECT name FROM sqlite_schema WHERE type='table'; - } -} {1 {file is not a database}} - -do_test multiple-key-calls-safe-3 { - execsql { PRAGMA key = 'testkey'; SELECT name FROM sqlite_schema WHERE type='table'; } -} {ok t1} +} {ok t1 ok t1 ok t1} db close file delete -force test.db diff --git a/test/sqlcipher-integrity.test b/test/sqlcipher-integrity.test index 639f9495d..5e13eff98 100644 --- a/test/sqlcipher-integrity.test +++ b/test/sqlcipher-integrity.test @@ -341,4 +341,42 @@ do_test integrity-check-plaintext-header { } {{} 1 {{HMAC verification failed for page 1} {HMAC verification failed for page 2}}} file delete -force test.db +# test that changing the key in the middle of database operations does +# not cause a corruption +do_test change-key-middle { + sqlite_orig db test.db + + set rc {} + + execsql { + PRAGMA key = 'testkey'; + CREATE table t1(a,b); + } + + for {set i 1} {$i<=1000} {incr i} { + execsql "INSERT INTO t1 VALUES($i,'value $i');" + } + + execsql { + PRAGMA key = 'diffkey'; + } + + for {set i 1} {$i<=1000} {incr i} { + execsql "INSERT INTO t1 VALUES($i,'value $i');" + } + + db close + + sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + SELECT name FROM sqlite_schema; + PRAGMA cipher_integrity_check; + PRAGMA integrity_check; + SELECT count(*) FROM t1; + } +} {ok t1 ok 2000} +db close +file delete -force test.db + finish_test From bdc020d166263d3f2bb7f79162c09d99a5675dbb Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 16 Aug 2023 12:18:02 -0400 Subject: [PATCH 101/340] eliminate unused skip_read_hmac (cleanup from last migration refactor) --- src/crypto.h | 1 - src/crypto_impl.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index eb38092ed..9a4fcb920 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -215,7 +215,6 @@ typedef struct { int plaintext_header_sz; int hmac_algorithm; int kdf_algorithm; - unsigned int skip_read_hmac; unsigned int need_kdf_salt; unsigned int flags; unsigned char *kdf_salt; diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 82d6f446d..221d257c3 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1112,7 +1112,7 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int memcpy(iv_out, iv_in, ctx->iv_sz); /* copy the iv from the input to output buffer */ } - if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT) && !ctx->skip_read_hmac) { + if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, in, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac operation on decrypt failed for pgno=%d", pgno); goto error; From 15fe89b30f05d6a577dfdbc56bdb4be7615957f9 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 16 Aug 2023 12:53:52 -0400 Subject: [PATCH 102/340] rework kdf salt flags --- src/crypto.h | 2 +- src/crypto_impl.c | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 9a4fcb920..4b639fba9 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -107,6 +107,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_FLAG_LE_PGNO (1 << 1) #define CIPHER_FLAG_BE_PGNO (1 << 2) #define CIPHER_FLAG_KEY_USED (1 << 3) +#define CIPHER_FLAG_HAS_KDF_SALT (1 << 4) #ifndef DEFAULT_CIPHER_FLAGS #define DEFAULT_CIPHER_FLAGS CIPHER_FLAG_HMAC | CIPHER_FLAG_LE_PGNO @@ -215,7 +216,6 @@ typedef struct { int plaintext_header_sz; int hmac_algorithm; int kdf_algorithm; - unsigned int need_kdf_salt; unsigned int flags; unsigned char *kdf_salt; unsigned char *hmac_kdf_salt; diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 221d257c3..6fa3a7f5f 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -792,7 +792,7 @@ void* sqlcipher_codec_ctx_get_data(codec_ctx *ctx) { static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { sqlite3_file *fd = sqlite3PagerFile(ctx->pBt->pBt->pPager); - if(!ctx->need_kdf_salt) { + if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT)) { return SQLITE_OK; /* don't reload salt when not needed */ } @@ -805,14 +805,14 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { return SQLITE_ERROR; } } - ctx->need_kdf_salt = 0; + sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT); return SQLITE_OK; } int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int size) { if(size >= ctx->kdf_salt_sz) { memcpy(ctx->kdf_salt, salt, ctx->kdf_salt_sz); - ctx->need_kdf_salt = 0; + sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT); return SQLITE_OK; } sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_kdf_salt: attempt to set salt of incorrect size %d", size); @@ -821,7 +821,7 @@ int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int si int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) { int rc = SQLITE_OK; - if(ctx->need_kdf_salt) { + if(!sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT)) { if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_get_kdf_salt: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); } @@ -914,9 +914,6 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi /* setup default flags */ ctx->flags = default_flags; - /* defer attempt to read KDF salt until first use */ - ctx->need_kdf_salt = 1; - /* setup the crypto provider */ sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating provider"); ctx->provider = (sqlcipher_provider *) sqlcipher_malloc(sizeof(sqlcipher_provider)); @@ -1181,7 +1178,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if(c_ctx->pass && c_ctx->pass_sz) { /* if key material is present on the context for derivation */ /* if necessary, initialize the salt from the header or random source */ - if(ctx->need_kdf_salt) { + if(!sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT)) { if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); return rc; From e44eb58938f89098b2dfca9b46f54b773f4159ab Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 16 Aug 2023 13:38:32 -0400 Subject: [PATCH 103/340] standardize flag handling --- src/crypto.c | 34 ++++++++++++++++---------------- src/crypto.h | 11 ++++++----- src/crypto_impl.c | 50 +++++++++++++++++------------------------------ 3 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 784f387a0..bdbb2202f 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -114,13 +114,13 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef if( zRight ) { unsigned int flags = sqlcipher_get_test_flags(); if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { - flags |= TEST_FAIL_ENCRYPT; + SQLCIPHER_FLAG_SET(flags,TEST_FAIL_ENCRYPT); } else if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { - flags |= TEST_FAIL_DECRYPT; + SQLCIPHER_FLAG_SET(flags,TEST_FAIL_DECRYPT); } else if(sqlite3_stricmp(zRight, "fail_migrate")==0) { - flags |= TEST_FAIL_MIGRATE; + SQLCIPHER_FLAG_SET(flags,TEST_FAIL_MIGRATE); } sqlcipher_set_test_flags(flags); } @@ -129,13 +129,13 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef if( zRight ) { unsigned int flags = sqlcipher_get_test_flags(); if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { - flags &= ~TEST_FAIL_ENCRYPT; + SQLCIPHER_FLAG_UNSET(flags,TEST_FAIL_ENCRYPT); } else if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { - flags &= ~TEST_FAIL_DECRYPT; + SQLCIPHER_FLAG_UNSET(flags,TEST_FAIL_DECRYPT); } else if(sqlite3_stricmp(zRight, "fail_migrate")==0) { - flags &= ~TEST_FAIL_MIGRATE; + SQLCIPHER_FLAG_UNSET(flags,TEST_FAIL_MIGRATE); } sqlcipher_set_test_flags(flags); } @@ -310,22 +310,22 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef char *deprecation = "PRAGMA cipher_hmac_pgno is deprecated, please remove from use"; /* clear both pgno endian flags */ if(sqlite3_stricmp(zRight, "le") == 0) { - sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO); - sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_LE_PGNO); + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_BE_PGNO); + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_LE_PGNO); } else if(sqlite3_stricmp(zRight, "be") == 0) { - sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO); - sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_BE_PGNO); + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_LE_PGNO); + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_BE_PGNO); } else if(sqlite3_stricmp(zRight, "native") == 0) { - sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO); - sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO); + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_LE_PGNO); + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_BE_PGNO); } sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", deprecation, P4_TRANSIENT); sqlite3_log(SQLITE_WARNING, deprecation); } else { - if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_LE_PGNO)) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_LE_PGNO)) { sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "le", P4_TRANSIENT); - } else if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_BE_PGNO)) { + } else if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_BE_PGNO)) { sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "be", P4_TRANSIENT); } else { sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "native", P4_TRANSIENT); @@ -771,7 +771,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); } else { - ctx->flags |= CIPHER_FLAG_KEY_USED; /* inline to avoid function call */ + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_KEY_USED); } memcpy(pData, buffer, page_sz); /* copy buffer data back to pData and return */ return pData; @@ -806,7 +806,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } - ctx->flags |= CIPHER_FLAG_KEY_USED; /* inline to avoid function call */ + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_KEY_USED); return buffer; /* return persistent buffer data, pData remains intact */ break; @@ -838,7 +838,7 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); - if(ctx != NULL && sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_KEY_USED)) { + if(ctx != NULL && SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) { /* there is already a codec attached to this database, so we should not proceed */ sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipherCodecAttach: no codec attached to db, exiting"); return SQLITE_OK; diff --git a/src/crypto.h b/src/crypto.h index 4b639fba9..804306d83 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -102,13 +102,18 @@ void sqlite3pager_reset(Pager *pPager); #define PBKDF2_ITER 256000 #endif -/* possible flags for cipher_ctx->flags */ +#define SQLCIPHER_FLAG_GET(FLAG,BIT) ((FLAG & BIT) != 0) +#define SQLCIPHER_FLAG_SET(FLAG,BIT) FLAG |= BIT +#define SQLCIPHER_FLAG_UNSET(FLAG,BIT) FLAG &= ~BIT + +/* possible flags for codec_ctx->flags */ #define CIPHER_FLAG_HMAC (1 << 0) #define CIPHER_FLAG_LE_PGNO (1 << 1) #define CIPHER_FLAG_BE_PGNO (1 << 2) #define CIPHER_FLAG_KEY_USED (1 << 3) #define CIPHER_FLAG_HAS_KDF_SALT (1 << 4) + #ifndef DEFAULT_CIPHER_FLAGS #define DEFAULT_CIPHER_FLAGS CIPHER_FLAG_HMAC | CIPHER_FLAG_LE_PGNO #endif @@ -287,10 +292,6 @@ unsigned char sqlcipher_get_hmac_salt_mask(void); int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use); int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx); -int sqlcipher_codec_ctx_set_flag(codec_ctx *ctx, unsigned int flag); -int sqlcipher_codec_ctx_unset_flag(codec_ctx *ctx, unsigned int flag); -int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag); - const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx); int sqlcipher_codec_ctx_migrate(codec_ctx *ctx); int sqlcipher_codec_add_random(codec_ctx *ctx, const char *data, int random_sz); diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 6fa3a7f5f..318c48b03 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -664,12 +664,12 @@ int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *ctx) { /* set the global default flag for HMAC */ void sqlcipher_set_default_use_hmac(int use) { - if(use) default_flags |= CIPHER_FLAG_HMAC; - else default_flags &= ~CIPHER_FLAG_HMAC; + if(use) SQLCIPHER_FLAG_SET(default_flags, CIPHER_FLAG_HMAC); + else SQLCIPHER_FLAG_UNSET(default_flags,CIPHER_FLAG_HMAC); } int sqlcipher_get_default_use_hmac() { - return (default_flags & CIPHER_FLAG_HMAC) != 0; + return SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC); } void sqlcipher_set_hmac_salt_mask(unsigned char mask) { @@ -683,16 +683,16 @@ unsigned char sqlcipher_get_hmac_salt_mask() { /* set the codec flag for whether this individual database should be using hmac */ int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use) { if(use) { - sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_HMAC); + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HMAC); } else { - sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_HMAC); + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_HMAC); } return sqlcipher_codec_ctx_reserve_setup(ctx); } int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx) { - return (ctx->flags & CIPHER_FLAG_HMAC) != 0; + return SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC); } /* the length of plaintext header size must be: @@ -761,20 +761,6 @@ int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx) { return ctx->kdf_algorithm; } -int sqlcipher_codec_ctx_set_flag(codec_ctx *ctx, unsigned int flag) { - ctx->flags |= flag; - return SQLITE_OK; -} - -int sqlcipher_codec_ctx_unset_flag(codec_ctx *ctx, unsigned int flag) { - ctx->flags &= ~flag; - return SQLITE_OK; -} - -int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag) { - return (ctx->flags & flag) != 0; -} - void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_error: ctx=%p, error=%d", ctx, error); sqlite3pager_error(ctx->pBt->pBt->pPager, error); @@ -792,7 +778,7 @@ void* sqlcipher_codec_ctx_get_data(codec_ctx *ctx) { static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { sqlite3_file *fd = sqlite3PagerFile(ctx->pBt->pBt->pPager); - if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT)) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT)) { return SQLITE_OK; /* don't reload salt when not needed */ } @@ -805,14 +791,14 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { return SQLITE_ERROR; } } - sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT); + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT); return SQLITE_OK; } int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int size) { if(size >= ctx->kdf_salt_sz) { memcpy(ctx->kdf_salt, salt, ctx->kdf_salt_sz); - sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT); + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT); return SQLITE_OK; } sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_kdf_salt: attempt to set salt of incorrect size %d", size); @@ -821,7 +807,7 @@ int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int si int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) { int rc = SQLITE_OK; - if(!sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT)) { + if(!SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT)) { if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_get_kdf_salt: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); } @@ -973,8 +959,8 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi /* Note that use_hmac is a special case that requires recalculation of page size so we call set_use_hmac to perform setup */ - if((rc = sqlcipher_codec_ctx_set_use_hmac(ctx, default_flags & CIPHER_FLAG_HMAC)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting use_hmac %d", rc, default_flags & CIPHER_FLAG_HMAC); + if((rc = sqlcipher_codec_ctx_set_use_hmac(ctx, SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC))) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting use_hmac %d", rc, SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC)); return rc; } @@ -1051,9 +1037,9 @@ static int sqlcipher_page_hmac(codec_ctx *ctx, cipher_ctx *c_ctx, Pgno pgno, uns backwards compatibility on the most popular platforms, but can optionally be configured to use either big endian or native byte ordering via pragma. */ - if(ctx->flags & CIPHER_FLAG_LE_PGNO) { /* compute hmac using little endian pgno*/ + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_LE_PGNO)) { /* compute hmac using little endian pgno*/ sqlcipher_put4byte_le(pgno_raw, pgno); - } else if(ctx->flags & CIPHER_FLAG_BE_PGNO) { /* compute hmac using big endian pgno */ + } else if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_BE_PGNO)) { /* compute hmac using big endian pgno */ sqlite3Put4byte(pgno_raw, pgno); /* sqlite3Put4byte converts 32bit uint to big endian */ } else { /* use native byte ordering */ memcpy(pgno_raw, &pgno, sizeof(pgno)); @@ -1109,7 +1095,7 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int memcpy(iv_out, iv_in, ctx->iv_sz); /* copy the iv from the input to output buffer */ } - if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT)) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, in, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac operation on decrypt failed for pgno=%d", pgno); goto error; @@ -1140,7 +1126,7 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int goto error; }; - if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, out_start, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac operation on encrypt failed for pgno=%d", pgno); goto error; @@ -1178,7 +1164,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if(c_ctx->pass && c_ctx->pass_sz) { /* if key material is present on the context for derivation */ /* if necessary, initialize the salt from the header or random source */ - if(!sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_HAS_KDF_SALT)) { + if(!SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT)) { if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); return rc; @@ -1538,7 +1524,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { if( rc!=SQLITE_OK ) goto handle_error; sqlcipherCodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz); - sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_KEY_USED); + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_KEY_USED); sqlcipherCodecAttach(db, 0, keyspec, keyspec_sz); srcfile = sqlite3PagerFile(pSrc->pBt->pPager); From 48ae31693ab371b441f802017e442a906e02586c Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 16 Aug 2023 14:52:46 -0400 Subject: [PATCH 104/340] raise error when trying to rekey unencrypted database, or use an empty key --- src/crypto.c | 2 +- src/pragma.c | 6 ++++ test/sqlcipher-core.test | 13 ++++++++ test/sqlcipher-rekey.test | 64 ++++----------------------------------- 4 files changed, 26 insertions(+), 59 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index bdbb2202f..9c722fd42 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -971,7 +971,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(ctx == NULL) { /* there was no codec attached to this database, so this should do nothing! */ sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no codec attached to db, exiting"); - return SQLITE_OK; + return SQLITE_MISUSE; } sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: entering database mutex %p", db->mutex); diff --git a/src/pragma.c b/src/pragma.c index 27797f622..769bdd0fc 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -2621,6 +2621,12 @@ void sqlite3Pragma( sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "ok", SQLITE_STATIC); returnSingleText(v, "ok"); + } else { + sqlite3ErrorMsg(pParse, "An error occurred with PRAGMA key or rekey. " + "PRAGMA key requires a key of one or more characters. " + "PRAGMA rekey can only be run on an existing encrypted database. " + "Use sqlcipher_export() and ATTACH to convert encrypted/plaintext databases."); + goto pragma_out; } } break; diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index 573c2a553..5aed9fb66 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -877,6 +877,19 @@ do_test test_flags_combo { } {0 5 0} db close +# test empty key +# it should raise an error +do_test empty-key { + sqlite_orig db test.db + + catchsql { + PRAGMA key = ''; + } + +} {1 {An error occurred with PRAGMA key or rekey. PRAGMA key requires a key of one or more characters. PRAGMA rekey can only be run on an existing encrypted database. Use sqlcipher_export() and ATTACH to convert encrypted/plaintext databases.}} +db close +file delete -force test.db + # configure URI filename support # create a new encrypted database with the key via parameter # close database diff --git a/test/sqlcipher-rekey.test b/test/sqlcipher-rekey.test index 2e6534e3d..267b89077 100644 --- a/test/sqlcipher-rekey.test +++ b/test/sqlcipher-rekey.test @@ -38,72 +38,20 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/sqlcipher.tcl -# Test rekey as first operation on an empty database. should be a no-op -do_test rekey-as-first-op { +# Test rekey as first operation on an empty database +# it should raise an error +do_test rekey-as-first-op-on-empty { sqlite_orig db test.db - execsql { + catchsql { PRAGMA rekey = 'testkey'; - CREATE table t1(a,b); - BEGIN; - } - - for {set i 1} {$i<=100} {incr i} { - set r [expr {int(rand()*500000)}] - execsql "INSERT INTO t1 VALUES($i,'value $r');" } - execsql { - COMMIT; - } - - db close - sqlite_orig db test.db - - execsql { - PRAGMA rekey = 'testkey'; - SELECT count(*) FROM t1; - } - -} {ok 100} +} {1 {An error occurred with PRAGMA key or rekey. PRAGMA key requires a key of one or more characters. PRAGMA rekey can only be run on an existing encrypted database. Use sqlcipher_export() and ATTACH to convert encrypted/plaintext databases.}} db close file delete -force test.db -# Test rekey as first operation follwed by key -do_test rekey-then-key-as-first-ops { - sqlite_orig db test.db - - execsql { - PRAGMA rekey = '1234'; - PRAGMA key = 'testkey'; - CREATE table t1(a,b); - BEGIN; - } - - for {set i 1} {$i<=100} {incr i} { - set r [expr {int(rand()*500000)}] - execsql "INSERT INTO t1 VALUES($i,'value $r');" - } - - execsql { - COMMIT; - } - - db close - sqlite_orig db test.db - - execsql { - PRAGMA rekey = '4321'; - PRAGMA key = 'testkey'; - SELECT count(*) FROM t1; - } - -} {ok ok 100} -db close -file delete -force test.db - - -# test a rekey operation as the first op on a database +# test a rekey operation as the first op on an existing database # then test that now the new key opens the database # now close database re-open with new key setup test.db "'testkey'" From 8580a54505064325c22c48aef7fc8d7cdf6bd51a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 17 Aug 2023 11:12:50 -0400 Subject: [PATCH 105/340] update podspec to use next version --- SQLCipher.podspec.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index dcea74f92..d6a04af28 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.4" + "tag": "v4.5.5" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.4", + "version": "4.5.5", "subspecs": [ { "compiler_flags": [ From 7c460791eba939e6c6872825219a6644ca47283b Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 17 Aug 2023 13:05:03 -0400 Subject: [PATCH 106/340] changelog update --- CHANGELOG.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 898ec62bd..aac5d416f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. -## [unreleased] - (? 2023 - [unreleased changes]) +## [4.5.5] - (August 2023 - [4.5.5 changes]) +- Updates baseline to upstream SQLite 3.42.0 +- Do not allow key to be changed on a connection after it has been successfully used for an encryption or decryption operation to prevent accidental database corruption +- Raise an error if a rekey operation is attempted on an unencrypted database +- Raise an error when a key or rekey operation is passed an empty key +- Minor improvements to constant time functions +- Miscellaneous code and comment cleanup ## [4.5.4] - (April 2023 - [4.5.4 changes]) - Updates baseline to upstream SQLite 3.41.2 @@ -220,7 +226,8 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 -[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.4...prerelease +[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.5...prerelease +[4.5.5]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.4...v4.5.5 [4.5.4]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.4 [4.5.4 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.3...v4.5.4 [4.5.3]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.3 From 8ac38918445d5b0ecdaa1ad5b362c7dac9a91dba Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 1 Sep 2023 11:29:14 -0400 Subject: [PATCH 107/340] bump version number to 4.5.6 --- CHANGELOG.md | 8 ++++++-- SQLCipher.podspec.json | 4 ++-- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aac5d416f..e49104676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. +## [4.5.6] - (TBD - [4.5.6 changes]) + ## [4.5.5] - (August 2023 - [4.5.5 changes]) - Updates baseline to upstream SQLite 3.42.0 - Do not allow key to be changed on a connection after it has been successfully used for an encryption or decryption operation to prevent accidental database corruption @@ -226,8 +228,10 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 -[unreleased]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.5...prerelease -[4.5.5]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.4...v4.5.5 +[4.5.6]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.6 +[4.5.6 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.5...v4.5.6 +[4.5.5]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.5 +[4.5.5 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.4...v4.5.5 [4.5.4]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.4 [4.5.4 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.3...v4.5.4 [4.5.3]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.3 diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index d6a04af28..3157bd1b3 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.5" + "tag": "v4.5.6" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.5", + "version": "4.5.6", "subspecs": [ { "compiler_flags": [ diff --git a/src/crypto.h b/src/crypto.h index 804306d83..c95833086 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -84,7 +84,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.5 +#define CIPHER_VERSION_NUMBER 4.5.6 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index 257827729..daf7f8a9b 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.5 community}} +} {{4.5.6 community}} db close file delete -force test.db From c9d47f93318d85691276f572d992bff65388575c Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 1 Sep 2023 11:39:17 -0400 Subject: [PATCH 108/340] Snapshot of upstream SQLite 3.43.0 --- Makefile.in | 45 +- Makefile.linux-gcc | 6 - Makefile.msc | 66 +- README.md | 104 +- VERSION | 2 +- autoconf/Makefile.am | 2 +- autoconf/Makefile.msc | 27 + autoconf/tea/configure.ac | 2 +- configure | 18 +- doc/compile-for-windows.md | 85 + doc/lemon.html | 4 +- ext/fts3/fts3_write.c | 1 + ext/fts5/fts5.h | 12 +- ext/fts5/fts5Int.h | 15 + ext/fts5/fts5_config.c | 47 + ext/fts5/fts5_expr.c | 8 +- ext/fts5/fts5_hash.c | 23 +- ext/fts5/fts5_index.c | 1464 +- ext/fts5/fts5_main.c | 29 +- ext/fts5/fts5_storage.c | 84 +- ext/fts5/test/fts5bigid.test | 62 + ext/fts5/test/fts5contentless.test | 271 + ext/fts5/test/fts5contentless2.test | 208 + ext/fts5/test/fts5contentless3.test | 196 + ext/fts5/test/fts5contentless4.test | 248 + ext/fts5/test/fts5contentless5.test | 59 + ext/fts5/test/fts5corrupt5.test | 90 +- ext/fts5/test/fts5corrupt7.test | 29 + ext/fts5/test/fts5eb.test | 3 + ext/fts5/test/fts5faultF.test | 111 + ext/fts5/test/fts5secure6.test | 19 + ext/fts5/test/fts5synonym2.test | 3 + ext/jni/GNUmakefile | 346 + ext/jni/README.md | 234 + ext/jni/jar-dist.make | 55 + ext/jni/src/c/sqlite3-jni.c | 4420 +++ ext/jni/src/c/sqlite3-jni.h | 1989 ++ ext/jni/src/org/sqlite/jni/Authorizer.java | 32 + ext/jni/src/org/sqlite/jni/AutoExtension.java | 31 + ext/jni/src/org/sqlite/jni/BusyHandler.java | 45 + ext/jni/src/org/sqlite/jni/Collation.java | 28 + .../src/org/sqlite/jni/CollationNeeded.java | 28 + ext/jni/src/org/sqlite/jni/CommitHook.java | 25 + ext/jni/src/org/sqlite/jni/Fts5.java | 38 + ext/jni/src/org/sqlite/jni/Fts5Context.java | 23 + .../src/org/sqlite/jni/Fts5ExtensionApi.java | 86 + ext/jni/src/org/sqlite/jni/Fts5Function.java | 27 + .../src/org/sqlite/jni/Fts5PhraseIter.java | 24 + ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java | 30 + .../org/sqlite/jni/NativePointerHolder.java | 33 + ext/jni/src/org/sqlite/jni/OutputPointer.java | 165 + .../src/org/sqlite/jni/ProgressHandler.java | 27 + ext/jni/src/org/sqlite/jni/ResultCode.java | 155 + ext/jni/src/org/sqlite/jni/RollbackHook.java | 25 + ext/jni/src/org/sqlite/jni/SQLFunction.java | 172 + ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 1645 ++ ext/jni/src/org/sqlite/jni/Tester1.java | 1163 + ext/jni/src/org/sqlite/jni/TesterFts5.java | 87 + ext/jni/src/org/sqlite/jni/Tracer.java | 62 + ext/jni/src/org/sqlite/jni/UpdateHook.java | 25 + ext/jni/src/org/sqlite/jni/ValueHolder.java | 25 + ext/jni/src/org/sqlite/jni/fts5_api.java | 69 + .../sqlite/jni/fts5_extension_function.java | 37 + .../src/org/sqlite/jni/fts5_tokenizer.java | 49 + ext/jni/src/org/sqlite/jni/sqlite3.java | 37 + .../src/org/sqlite/jni/sqlite3_context.java | 66 + ext/jni/src/org/sqlite/jni/sqlite3_stmt.java | 25 + ext/jni/src/org/sqlite/jni/sqlite3_value.java | 19 + .../src/org/sqlite/jni/tester/SQLTester.java | 1421 + .../jni/tester/test-script-interpreter.md | 269 + ext/jni/src/tests/000-000-sanity.test | 52 + ext/jni/src/tests/000-001-ignored.test | 9 + ext/misc/decimal.c | 441 +- ext/misc/ieee754.c | 33 +- ext/misc/pcachetrace.c | 179 + ext/misc/series.c | 6 +- ext/recover/dbdata.c | 15 +- ext/recover/recovercorrupt2.test | 239 + ext/recover/sqlite3recover.c | 2 +- ext/rtree/rtree.c | 37 +- ext/rtree/rtree1.test | 28 + ext/session/sqlite3session.c | 1 + ext/wasm/GNUmakefile | 256 +- ext/wasm/README.md | 15 +- ext/wasm/api/README.md | 27 +- ext/wasm/api/extern-post-js.c-pp.js | 24 +- ext/wasm/api/sqlite3-api-cleanup.js | 6 +- ext/wasm/api/sqlite3-api-glue.js | 10 + ext/wasm/api/sqlite3-api-oo1.js | 217 +- ext/wasm/api/sqlite3-api-prologue.js | 194 +- ext/wasm/api/sqlite3-api-worker1.js | 19 + ext/wasm/api/sqlite3-opfs-async-proxy.js | 20 +- ext/wasm/api/sqlite3-v-helper.js | 7 +- ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js | 1230 + ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 121 +- ext/wasm/api/sqlite3-wasi.h | 69 - ext/wasm/api/sqlite3-wasm.c | 51 +- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js | 7 +- ext/wasm/common/SqliteTestUtil.js | 1 - ext/wasm/common/testing.css | 37 +- ext/wasm/common/whwasmutil.js | 78 +- ext/wasm/demo-123.js | 4 +- ext/wasm/demo-worker1-promiser.js | 29 +- ext/wasm/demo-worker1.js | 4 +- ext/wasm/dist.make | 2 +- ext/wasm/fiddle.make | 6 +- ext/wasm/fiddle/fiddle-worker.js | 10 +- ext/wasm/index.html | 30 +- ext/wasm/jaccwabyt/jaccwabyt.js | 6 +- ext/wasm/module-symbols.html | 1 + ext/wasm/scratchpad-wasmfs-main.js | 70 - ...asmfs-main.html => scratchpad-wasmfs.html} | 24 +- ext/wasm/scratchpad-wasmfs.mjs | 70 + ext/wasm/speedtest1-wasmfs.html | 167 +- ext/wasm/speedtest1-wasmfs.mjs | 90 + ext/wasm/speedtest1-worker.html | 7 +- ext/wasm/speedtest1-worker.js | 51 +- ext/wasm/tester1-worker.html | 2 +- ext/wasm/tester1.c-pp.js | 514 +- ext/wasm/wasmfs.make | 133 +- main.mk | 15 +- manifest | 505 +- manifest.uuid | 2 +- mkso.sh | 32 - sqlite3.1 | 161 +- src/alter.c | 148 +- src/analyze.c | 7 +- src/auth.c | 2 +- src/bitvec.c | 22 +- src/btmutex.c | 2 +- src/btree.c | 866 +- src/btree.h | 2 - src/btreeInt.h | 6 +- src/build.c | 218 +- src/ctime.c | 6 +- src/date.c | 239 +- src/delete.c | 114 +- src/expr.c | 421 +- src/fkey.c | 5 +- src/func.c | 302 +- src/global.c | 1 + src/hash.c | 2 +- src/hwtime.h | 2 +- src/insert.c | 134 +- src/json.c | 1107 +- src/loadext.c | 8 +- src/main.c | 241 +- src/mem1.c | 6 +- src/mutex_unix.c | 10 +- src/mutex_w32.c | 2 +- src/os.c | 4 +- src/os_unix.c | 743 +- src/os_win.c | 16 +- src/pager.c | 1107 +- src/pager.h | 4 + src/parse.y | 32 +- src/pcache.c | 18 +- src/pcache1.c | 98 +- src/pragma.c | 104 +- src/prepare.c | 9 +- src/printf.c | 265 +- src/resolve.c | 81 +- src/rowset.c | 12 +- src/select.c | 530 +- src/shell.c.in | 339 +- src/sqlite.h.in | 864 +- src/sqlite3ext.h | 4 + src/sqliteInt.h | 135 +- src/sqliteLimit.h | 20 +- src/tclsqlite.c | 33 +- src/test1.c | 205 +- src/test2.c | 2 +- src/test3.c | 2 +- src/test6.c | 98 +- src/test8.c | 2 +- src/test_config.c | 12 - src/test_devsym.c | 2 +- src/test_init.c | 12 +- src/test_intarray.h | 2 +- src/test_md5.c | 2 +- src/test_multiplex.c | 86 +- src/test_multiplex.h | 24 +- src/test_mutex.c | 2 +- src/test_pcache.c | 16 +- src/test_quota.c | 4 +- src/test_vfstrace.c | 2 +- src/test_windirent.h | 2 +- src/tokenize.c | 5 +- src/treeview.c | 3 +- src/update.c | 124 +- src/upsert.c | 2 +- src/util.c | 412 +- src/vacuum.c | 2 +- src/vdbe.c | 670 +- src/vdbe.h | 6 +- src/vdbeInt.h | 43 +- src/vdbeapi.c | 254 +- src/vdbeaux.c | 520 +- src/vdbemem.c | 96 +- src/vdbesort.c | 212 +- src/vdbevtab.c | 29 +- src/vtab.c | 82 +- src/wal.c | 903 +- src/wal.h | 5 + src/walker.c | 10 +- src/where.c | 432 +- src/whereInt.h | 14 +- src/wherecode.c | 153 +- src/whereexpr.c | 70 +- src/window.c | 228 +- test/analyze.test | 19 + test/atof1.test | 24 +- test/autoindex4.test | 24 +- test/corrupt2.test | 2 +- test/corruptL.test | 1 + test/decimal.test | 25 +- test/fp-speed-1.c | 164 + test/fpconv1.test | 44 + test/fts1a.test | 186 - test/fts1b.test | 147 - test/fts1c.test | 1213 - test/fts1d.test | 65 - test/fts1e.test | 85 - test/fts1f.test | 90 - test/fts1i.test | 88 - test/fts1j.test | 89 - test/fts1k.test | 69 - test/fts1l.test | 65 - test/fts1m.test | 50 - test/fts1n.test | 45 - test/fts1o.test | 138 - test/fts1porter.test | 23590 ---------------- test/fts2.test | 67 - test/fts2a.test | 202 - test/fts2b.test | 147 - test/fts2c.test | 1213 - test/fts2d.test | 65 - test/fts2e.test | 85 - test/fts2f.test | 90 - test/fts2g.test | 93 - test/fts2h.test | 76 - test/fts2i.test | 87 - test/fts2j.test | 89 - test/fts2k.test | 105 - test/fts2l.test | 69 - test/fts2m.test | 65 - test/fts2n.test | 196 - test/fts2o.test | 169 - test/fts2p.test | 357 - test/fts2q.test | 346 - test/fts2r.test | 121 - test/fts2token.test | 174 - test/func.test | 71 +- test/fuzzcheck.c | 65 +- test/fuzzdata6.db | Bin 1785856 -> 1785856 bytes test/fuzzdata8.db | Bin 4235264 -> 4237312 bytes test/join5.test | 22 +- test/joinA.test | 63 + test/joinH.test | 12 + test/json/README.md | 26 +- test/json/json-q1.txt | 20 + test/json101.test | 402 +- test/json102.test | 18 + test/kvtest.c | 2 +- test/like.test | 20 + test/permutations.test | 212 +- test/releasetest_data.tcl | 20 +- test/round1.test | 4 +- test/scanstatus2.test | 69 +- test/shell2.test | 9 + test/snapshot_fault.test | 1 + test/sort2.test | 2 +- test/speedtest1.c | 5 + test/sqldiff1.test | 22 + test/testrunner.tcl | 137 +- test/testrunner_data.tcl | 432 +- test/timediff1.test | 222 + test/update.test | 22 + test/upfrom4.test | 26 + test/upsert1.test | 13 + test/walseh1.test | 150 + test/where7.test | 39 +- test/wherelimit2.test | 30 + test/window1.test | 13 + tool/build-shell.sh | 1 - tool/custom.txt | 1246 + tool/lemon.c | 37 +- tool/mkctimec.tcl | 1 + tool/mkkeywordhash.c | 37 +- tool/mksqlite3c.tcl | 25 +- tool/speed-check.sh | 3 + tool/spellsift.tcl | 74 + tool/sqldiff.c | 28 +- tool/src-verify.c | 956 + {ext/wasm => tool}/version-info.c | 0 tool/warnings.sh | 1 - 296 files changed, 31995 insertions(+), 37389 deletions(-) create mode 100644 doc/compile-for-windows.md create mode 100644 ext/fts5/test/fts5bigid.test create mode 100644 ext/fts5/test/fts5contentless.test create mode 100644 ext/fts5/test/fts5contentless2.test create mode 100644 ext/fts5/test/fts5contentless3.test create mode 100644 ext/fts5/test/fts5contentless4.test create mode 100644 ext/fts5/test/fts5contentless5.test create mode 100644 ext/fts5/test/fts5faultF.test create mode 100644 ext/jni/GNUmakefile create mode 100644 ext/jni/README.md create mode 100644 ext/jni/jar-dist.make create mode 100644 ext/jni/src/c/sqlite3-jni.c create mode 100644 ext/jni/src/c/sqlite3-jni.h create mode 100644 ext/jni/src/org/sqlite/jni/Authorizer.java create mode 100644 ext/jni/src/org/sqlite/jni/AutoExtension.java create mode 100644 ext/jni/src/org/sqlite/jni/BusyHandler.java create mode 100644 ext/jni/src/org/sqlite/jni/Collation.java create mode 100644 ext/jni/src/org/sqlite/jni/CollationNeeded.java create mode 100644 ext/jni/src/org/sqlite/jni/CommitHook.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5Context.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5Function.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java create mode 100644 ext/jni/src/org/sqlite/jni/NativePointerHolder.java create mode 100644 ext/jni/src/org/sqlite/jni/OutputPointer.java create mode 100644 ext/jni/src/org/sqlite/jni/ProgressHandler.java create mode 100644 ext/jni/src/org/sqlite/jni/ResultCode.java create mode 100644 ext/jni/src/org/sqlite/jni/RollbackHook.java create mode 100644 ext/jni/src/org/sqlite/jni/SQLFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/SQLite3Jni.java create mode 100644 ext/jni/src/org/sqlite/jni/Tester1.java create mode 100644 ext/jni/src/org/sqlite/jni/TesterFts5.java create mode 100644 ext/jni/src/org/sqlite/jni/Tracer.java create mode 100644 ext/jni/src/org/sqlite/jni/UpdateHook.java create mode 100644 ext/jni/src/org/sqlite/jni/ValueHolder.java create mode 100644 ext/jni/src/org/sqlite/jni/fts5_api.java create mode 100644 ext/jni/src/org/sqlite/jni/fts5_extension_function.java create mode 100644 ext/jni/src/org/sqlite/jni/fts5_tokenizer.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3_context.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3_stmt.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3_value.java create mode 100644 ext/jni/src/org/sqlite/jni/tester/SQLTester.java create mode 100644 ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md create mode 100644 ext/jni/src/tests/000-000-sanity.test create mode 100644 ext/jni/src/tests/000-001-ignored.test create mode 100644 ext/misc/pcachetrace.c create mode 100644 ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js delete mode 100644 ext/wasm/api/sqlite3-wasi.h delete mode 100644 ext/wasm/scratchpad-wasmfs-main.js rename ext/wasm/{scratchpad-wasmfs-main.html => scratchpad-wasmfs.html} (54%) create mode 100644 ext/wasm/scratchpad-wasmfs.mjs create mode 100644 ext/wasm/speedtest1-wasmfs.mjs delete mode 100644 mkso.sh create mode 100644 test/fp-speed-1.c create mode 100644 test/fpconv1.test delete mode 100644 test/fts1a.test delete mode 100644 test/fts1b.test delete mode 100644 test/fts1c.test delete mode 100644 test/fts1d.test delete mode 100644 test/fts1e.test delete mode 100644 test/fts1f.test delete mode 100644 test/fts1i.test delete mode 100644 test/fts1j.test delete mode 100644 test/fts1k.test delete mode 100644 test/fts1l.test delete mode 100644 test/fts1m.test delete mode 100644 test/fts1n.test delete mode 100644 test/fts1o.test delete mode 100644 test/fts1porter.test delete mode 100644 test/fts2.test delete mode 100644 test/fts2a.test delete mode 100644 test/fts2b.test delete mode 100644 test/fts2c.test delete mode 100644 test/fts2d.test delete mode 100644 test/fts2e.test delete mode 100644 test/fts2f.test delete mode 100644 test/fts2g.test delete mode 100644 test/fts2h.test delete mode 100644 test/fts2i.test delete mode 100644 test/fts2j.test delete mode 100644 test/fts2k.test delete mode 100644 test/fts2l.test delete mode 100644 test/fts2m.test delete mode 100644 test/fts2n.test delete mode 100644 test/fts2o.test delete mode 100644 test/fts2p.test delete mode 100644 test/fts2q.test delete mode 100644 test/fts2r.test delete mode 100644 test/fts2token.test create mode 100644 test/timediff1.test create mode 100644 test/walseh1.test create mode 100644 tool/custom.txt create mode 100755 tool/spellsift.tcl create mode 100644 tool/src-verify.c rename {ext/wasm => tool}/version-info.c (100%) diff --git a/Makefile.in b/Makefile.in index 61cb3ef0e..e938cec52 100644 --- a/Makefile.in +++ b/Makefile.in @@ -599,7 +599,7 @@ SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC -FUZZERSHELL_OPT = +FUZZERSHELL_OPT = FUZZCHECK_OPT += -I$(TOP)/test FUZZCHECK_OPT += -I$(TOP)/ext/recover FUZZCHECK_OPT += \ @@ -690,6 +690,12 @@ srcck1$(BEXE): $(TOP)/tool/srcck1.c sourcetest: srcck1$(BEXE) sqlite3.c ./srcck1 sqlite3.c +src-verify: $(TOP)/tool/src-verify.c + $(BCC) -o src-verify$(BEXE) $(TOP)/tool/src-verify.c + +verify-source: ./src-verify + ./src-verify $(TOP) + fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZERSHELL_OPT) \ $(TOP)/tool/fuzzershell.c sqlite3.c $(TLIBS) @@ -775,7 +781,7 @@ mptest: mptester$(TEXE) cp fts5.c fts5.h tsrc touch .target_source -sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl +sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . @@ -1133,6 +1139,7 @@ SHELL_SRC = \ $(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/misc/memtrace.c \ + $(TOP)/ext/misc/pcachetrace.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/sqlite3recover.h \ @@ -1307,6 +1314,12 @@ testrunner: testfixture$(TEXE) # devtest: testfixture$(TEXE) fuzztest testrunner +mdevtest: + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest + +sdevtest: + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest + # Testing for a release # releasetest: testfixture$(TEXE) @@ -1506,6 +1519,7 @@ clean: rm -f LogEst$(TEXE) fts3view$(TEXE) rollback-test$(TEXE) showdb$(TEXE) rm -f showjournal$(TEXE) showstat4$(TEXE) showwal$(TEXE) speedtest1$(TEXE) rm -f wordcount$(TEXE) changeset$(TEXE) + rm -f version-info$(TEXT) rm -f sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def rm -f sqlite3.c rm -f sqlite3rc.h @@ -1521,6 +1535,8 @@ clean: rm -f dbhash dbhash.exe rm -f fts5.* fts5parse.* rm -f threadtest5 + rm -f src-verify + rm -f custom.rws distclean: clean rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \ @@ -1549,3 +1565,28 @@ sqlite3.dll: $(REAL_LIBOBJ) sqlite3.def # fiddle: sqlite3.c shell.c make -C ext/wasm fiddle emcc_opt=-Os + +# +# Spell-checking for source comments +# The sources checked are either C sources or C source templates. +# Their comments are extracted and processed through aspell using +# a custom dictionary that contains scads of odd identifiers that +# find their way into the comments. +# +# Currently, this target is setup to be "made" in-tree only. +# The output is ephemeral. Redirect it to guide spelling fixups, +# either to correct spelling or add words to tool/custom.txt. +# +./custom.rws: ./tool/custom.txt + @echo 'Updating custom dictionary from tool/custom.txt' + aspell --lang=en create master ./custom.rws < $< + +misspell: ./custom.rws + $(TCLSH_CMD) ./tool/spellsift.tcl ./src/*.c ./src/*.h ./src/*.in + +# +# tool/version-info: a utility for emitting sqlite3 version info +# in various forms. +# +version-info$(TEXE): $(TOP)/tool/version-info.c Makefile sqlite3.h + $(LTLINK) $(ST_OPT) -o $@ $(TOP)/tool/version-info.c diff --git a/Makefile.linux-gcc b/Makefile.linux-gcc index ad5d4dd09..fe7349ad4 100644 --- a/Makefile.linux-gcc +++ b/Makefile.linux-gcc @@ -22,12 +22,6 @@ TOP = ../sqlite BCC = gcc -g -O0 #BCC = /opt/ancic/bin/c89 -0 -#### If the target operating system supports the "usleep()" system -# call, then define the HAVE_USLEEP macro for all C modules. -# -#USLEEP = -USLEEP = -DHAVE_USLEEP=1 - #### If you want the SQLite library to be safe for use within a # multi-threaded program, then define the following macro # appropriately: diff --git a/Makefile.msc b/Makefile.msc index 87814894d..3179e301c 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -52,6 +52,13 @@ MINIMAL_AMALGAMATION = 0 USE_STDCALL = 0 !ENDIF +# Set this non-0 to use structured exception handling (SEH) for WAL mode +# in the core library. +# +!IFNDEF USE_SEH +USE_SEH = 1 +!ENDIF + # Set this non-0 to have the shell executable link against the core dynamic # link library. # @@ -218,6 +225,12 @@ WIN32HEAP = 0 OSTRACE = 0 !ENDIF +# enable address sanitizer using ASAN=1 on the command-line. +# +!IFNDEF ASAN +ASAN = 0 +!ENDIF + # Set this to one of the following values to enable various debugging # features. Each level includes the debugging options from the previous # levels. Currently, the recognized values for DEBUG are: @@ -389,6 +402,13 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1 !ENDIF +# Should structured exception handling (SEH) be enabled for WAL mode in +# the core library? +# +!IF $(USE_SEH)!=0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1 +!ENDIF + # These are the "extended" SQLite compilation options used when compiling for # the Windows 10 platform. # @@ -877,6 +897,13 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 !ENDIF !ENDIF + +# Address sanitizer if ASAN=1 +# +!IF $(ASAN)>0 +TCC = $(TCC) /fsanitize=address +!ENDIF + # <> # The locations of the Tcl header and library files. Also, the library that # non-stubs enabled programs using Tcl must link against. These variables @@ -1807,6 +1834,12 @@ srcck1.exe: $(TOP)\tool\srcck1.c sourcetest: srcck1.exe $(SQLITE3C) srcck1.exe $(SQLITE3C) +src-verify.exe: $(TOP)\tool\src-verify.c + $(LTLINK) $(NO_WARN) $(TOP)\tool\src-verify.c + +verify-source: src-verify.exe + src-verify.exe $(TOP) + fuzzershell.exe: $(TOP)\tool\fuzzershell.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(FUZZERSHELL_COMPILE_OPTS) $(TOP)\tool\fuzzershell.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) @@ -1870,7 +1903,7 @@ mptest: mptester.exe move vdbe.new tsrc\vdbe.c echo > .target_source -sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) +sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-verify.exe $(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl @@ -2226,8 +2259,8 @@ SHELL_SRC = \ $(TOP)\src\shell.c.in \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\completion.c \ - $(TOP)\ext\misc\base64.c \ - $(TOP)\ext\misc\base85.c \ + $(TOP)\ext\misc\base64.c \ + $(TOP)\ext\misc\base85.c \ $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\fileio.c \ $(TOP)\ext\misc\ieee754.c \ @@ -2238,9 +2271,10 @@ SHELL_SRC = \ $(TOP)\ext\expert\sqlite3expert.c \ $(TOP)\ext\expert\sqlite3expert.h \ $(TOP)\ext\misc\memtrace.c \ - $(TOP)/ext/recover/dbdata.c \ - $(TOP)/ext/recover/sqlite3recover.c \ - $(TOP)/ext/recover/sqlite3recover.h \ + $(TOP)\ext\misc\pcachetrace.c \ + $(TOP)\ext\recover\dbdata.c \ + $(TOP)\ext\recover\sqlite3recover.c \ + $(TOP)\ext\recover\sqlite3recover.h \ $(TOP)\src\test_windirent.c # If use of zlib is enabled, add the "zipfile.c" source file. @@ -2484,10 +2518,13 @@ testrunner: testfixture.exe # devtest: testfixture.exe fuzztest testrunner +mdevtest: + $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest + # Testing for a release # releasetest: testfixture.exe fuzztest - testfixture.exe $(TOP)/test/testrunner.tcl release + testfixture.exe $(TOP)\test\testrunner.tcl release smoketest: $(TESTPROGS) @@ -2515,14 +2552,14 @@ sqlite3_expert.exe: $(SQLITE3C) $(TOP)\ext\expert\sqlite3expert.h $(TOP)\ext\exp $(LTLINK) $(NO_WARN) $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c $(SQLITE3C) $(TLIBS) CHECKER_DEPS =\ - $(TOP)/tool/mkccode.tcl \ + $(TOP)\tool\mkccode.tcl \ sqlite3.c \ - $(TOP)/src/tclsqlite.c \ - $(TOP)/ext/repair/sqlite3_checker.tcl \ - $(TOP)/ext/repair/checkindex.c \ - $(TOP)/ext/repair/checkfreelist.c \ - $(TOP)/ext/misc/btreeinfo.c \ - $(TOP)/ext/repair/sqlite3_checker.c.in + $(TOP)\src\tclsqlite.c \ + $(TOP)\ext\repair\sqlite3_checker.tcl \ + $(TOP)\ext\repair\checkindex.c \ + $(TOP)\ext\repair\checkfreelist.c \ + $(TOP)\ext\misc\btreeinfo.c \ + $(TOP)\ext\repair\sqlite3_checker.c.in sqlite3_checker.c: $(CHECKER_DEPS) $(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\ext\repair\sqlite3_checker.c.in > $@ @@ -2661,4 +2698,5 @@ clean: del /Q showshm.exe sqlite3_checker.* sqlite3_expert.exe 2>NUL del /Q fts5.* fts5parse.* 2>NUL del /Q lsm.h lsm1.c 2>NUL + del /q src-verify.exe 2>NUL # <> diff --git a/README.md b/README.md index 0df8b58c2..ebb917e21 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,28 @@ If you pulled your SQLite source code from a secondary source and want to verify its integrity, there are hints on how to do that in the [Verifying Code Authenticity](#vauth) section below. -## Obtaining The Code +## Contacting The SQLite Developers + +The preferred way to ask questions or make comments about SQLite or to +report bugs against SQLite is to visit the +[SQLite Forum](https://sqlite.org/forum) at . +Anonymous postings are permitted. + +If you think you have found a bug that has security implications and +you do not want to report it on the public forum, you can send a private +email to drh at sqlite dot org. + +## Public Domain + +The SQLite source code is in the public domain. See + for details. + +Because SQLite is in the public domain, we do not normally accept pull +requests, because if we did take a pull request, the changes in that +pull request might carry a copyright and the SQLite source code would +then no longer be fully in the public domain. + +## Obtaining The SQLite Source Code If you do not want to use Fossil, you can download tarballs or ZIP archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows: @@ -82,9 +103,9 @@ For example: mkdir bld ;# Build will occur in a sibling directory cd bld ;# Change to the build directory ../sqlite/configure ;# Run the configure script - make ;# Run the makefile. + make ;# Builds the "sqlite3" command-line tool make sqlite3.c ;# Build the "amalgamation" source file - make test ;# Run some tests (requires Tcl) + make devtest ;# Run some tests (requires Tcl) See the makefile for additional targets. @@ -94,32 +115,33 @@ script does not work out for you, there is a generic makefile named can copy and edit to suit your needs. Comments on the generic makefile show what changes are needed. -## Using MSVC for Windows systems +## Compiling for Windows Using MSVC On Windows, all applicable build products can be compiled with MSVC. -First open the command prompt window associated with the desired compiler -version (e.g. "Developer Command Prompt for VS2013"). Next, use NMAKE -with the provided "Makefile.msc" to build one of the supported targets. - -For example, from the parent directory of the source subtree named "sqlite": - - mkdir bld - cd bld - nmake /f ..\sqlite\Makefile.msc TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc sqlite3.c TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc sqlite3.dll TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc sqlite3.exe TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc test TOP=..\sqlite - -There are several build options that can be set via the NMAKE command -line. For example, to build for WinRT, simply add "FOR_WINRT=1" argument -to the "sqlite3.dll" command line above. When debugging into the SQLite -code, adding the "DEBUG=1" argument to one of the above command lines is -recommended. - -SQLite does not require [Tcl](http://www.tcl.tk/) to run, but a Tcl installation -is required by the makefiles (including those for MSVC). SQLite contains -a lot of generated code and Tcl is used to do much of that code generation. +You will also need a working installation of TCL. +See the [compile-for-windows.md](doc/compile-for-windows.md) document for +additional information about how to install MSVC and TCL and configure your +build environment. + +If you want to run tests, you need to let SQLite know the location of your +TCL library, using a command like this: + + set TCLDIR=c:\Tcl + +SQLite uses "tclsh.exe" as part of the build process, and so that utility +program will need to be somewhere on your %PATH%. The finished SQLite library +does not contain any TCL code, but it does use TCL to help with the build process +and to run tests. + +Build using Makefile.msc. Example: + + nmake /f Makefile.msc + nmake /f Makefile.msc sqlite3.c + nmake /f Makefile.msc devtest + nmake /f Makefile.msc releasetest + +There are many other makefile targets. See comments in Makefile.msc for +details. ## Source Code Tour @@ -290,16 +312,13 @@ Key files: is not part of the core SQLite library. But as most of the tests in this repository are written in Tcl, the Tcl language bindings are important. - * **test*.c** - Files in the src/ folder that begin with "test" go into + * **test\*.c** - Files in the src/ folder that begin with "test" go into building the "testfixture.exe" program. The testfixture.exe program is an enhanced Tcl shell. The testfixture.exe program runs scripts in the test/ folder to validate the core SQLite code. The testfixture program (and some other test programs too) is built and run when you type "make test". - * **ext/misc/json1.c** - This file implements the various JSON functions - that are built into SQLite. - There are many other source files. Each has a succinct header comment that describes its purpose and role within the larger system. @@ -307,8 +326,8 @@ describes its purpose and role within the larger system. ## Verifying Code Authenticity The `manifest` file at the root directory of the source tree -contains either a SHA3-256 hash (for newer files) or a SHA1 hash (for -older files) for every source file in the repository. +contains either a SHA3-256 hash or a SHA1 hash +for every source file in the repository. The name of the version of the entire source tree is just the SHA3-256 hash of the `manifest` file itself, possibly with the last line of that file omitted if the last line begins with @@ -316,14 +335,25 @@ last line of that file omitted if the last line begins with The `manifest.uuid` file should contain the SHA3-256 hash of the `manifest` file. If all of the above hash comparisons are correct, then you can be confident that your source tree is authentic and unadulterated. +Details on the format for the `manifest` files are available +[on the Fossil website](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest). + +The process of checking source code authenticity is automated by the +makefile: + +> make verify-source + +Or on windows: + +> nmake /f Makefile.msc verify-source -The format of the `manifest` file should be mostly self-explanatory, but -if you want details, they are available -[here](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest). +Using the makefile to verify source integrity is good for detecting +accidental changes to the source tree, but malicious changes could be +hidden by also modifying the makefiles. ## Contacts -The main SQLite website is [http://www.sqlite.org/](http://www.sqlite.org/) +The main SQLite website is [http:/sqlite.org/](http://sqlite.org/) with geographically distributed backups at [http://www2.sqlite.org/](http://www2.sqlite.org) and [http://www3.sqlite.org/](http://www3.sqlite.org). diff --git a/VERSION b/VERSION index 476cebe06..a9184766b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.42.0 +3.43.0 diff --git a/autoconf/Makefile.am b/autoconf/Makefile.am index 694419b27..1eaa560ff 100644 --- a/autoconf/Makefile.am +++ b/autoconf/Makefile.am @@ -9,7 +9,7 @@ sqlite3_SOURCES = shell.c sqlite3.h EXTRA_sqlite3_SOURCES = sqlite3.c sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@ sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@ -sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS) +sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS) include_HEADERS = sqlite3.h sqlite3ext.h diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 09daa867e..13663d877 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -52,6 +52,13 @@ MINIMAL_AMALGAMATION = 0 USE_STDCALL = 0 !ENDIF +# Set this non-0 to use structured exception handling (SEH) for WAL mode +# in the core library. +# +!IFNDEF USE_SEH +USE_SEH = 1 +!ENDIF + # Set this non-0 to have the shell executable link against the core dynamic # link library. # @@ -180,6 +187,12 @@ WIN32HEAP = 0 OSTRACE = 0 !ENDIF +# enable address sanitizer using ASAN=1 on the command-line. +# +!IFNDEF ASAN +ASAN = 0 +!ENDIF + # Set this to one of the following values to enable various debugging # features. Each level includes the debugging options from the previous # levels. Currently, the recognized values for DEBUG are: @@ -311,6 +324,13 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1 !ENDIF +# Should structured exception handling (SEH) be enabled for WAL mode in +# the core library? +# +!IF $(USE_SEH)!=0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1 +!ENDIF + # These are the "extended" SQLite compilation options used when compiling for # the Windows 10 platform. # @@ -718,6 +738,13 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 !ENDIF +# Address sanitizer if ASAN=1 +# +!IF $(ASAN)>0 +TCC = $(TCC) /fsanitize=address +!ENDIF + + # Compiler options needed for programs that use the readline() library. # !IFNDEF READLINE_FLAGS diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index e26780e2e..093c99d67 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.42.0]) +AC_INIT([sqlite],[3.43.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 29ca76b69..6b0c584a4 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.69 for sqlite 3.42.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.43.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.42.0' -PACKAGE_STRING='sqlite 3.42.0' +PACKAGE_VERSION='3.43.0' +PACKAGE_STRING='sqlite 3.43.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1470,7 +1470,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.42.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.43.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1535,7 +1535,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.42.0:";; + short | recursive ) echo "Configuration of sqlite 3.43.0:";; esac cat <<\_ACEOF @@ -1665,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.42.0 +sqlite configure 3.43.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2084,7 +2084,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.42.0, which was +It was created by sqlite $as_me 3.43.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12457,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.42.0, which was +This file was extended by sqlite $as_me 3.43.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12523,7 +12523,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.42.0 +sqlite config.status 3.43.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md new file mode 100644 index 000000000..0bd39d21f --- /dev/null +++ b/doc/compile-for-windows.md @@ -0,0 +1,85 @@ +# Notes On Compiling SQLite On Windows 11 + +Here are step-by-step instructions on how to build SQLite from +canonical source on a new Windows 11 PC, as of 2023-08-16: + + 1. Install Microsoft Visual Studio. The free "community edition" + will work fine. Do a standard install for C++ development. + SQLite only needs the + "cl" compiler and the "nmake" build tool. + + 2. Under the "Start" menu, find "All Apps" then go to "Visual Studio 20XX" + and find "x64 Native Tools Command Prompt for VS 20XX". Pin that + application to your task bar, as you will use it a lot. Bring up + an instance of this command prompt and do all of the subsequent steps + in that "x64 Native Tools" command prompt. (Or use "x86" if you want + a 32-bit build.) The subsequent steps will not work in a vanilla + DOS prompt. Nor will they work in PowerShell. + + 3. Install TCL development libraries. This note assumes that you wil + install the TCL development libraries in the "`c:\Tcl`" directory. + Make adjustments + if you want TCL installed somewhere else. SQLite needs both the + "tclsh.exe" command-line tool as part of the build process, and + the "tcl86.lib" library in order to run tests. You will need + TCL version 8.6 or later. +
      +
    1. Get the TCL source archive, perhaps from + [https://www.tcl.tk/software/tcltk/download.html](https://www.tcl.tk/software/tcltk/download.html). +
    2. Untar or unzip the source archive. CD into the "win/" subfolder + of the source tree. +
    3. Run: `nmake /f makefile.vc release` +
    4. Run: `nmake /f makefile.vc INSTALLDIR=c:\Tcl install` +
    5. CD to `c:\Tcl\lib`. In that subfolder make a copy of the + "`tcl86t.lib`" file to the alternative name "`tcl86.lib`" + (omitting the second 't'). Leave the copy in the same directory + as the original. +
    6. CD to `c:\Tcl\bin`. Make a copy of the "`tclsh86t.exe`" + file into "`tclsh.exe`" (without the "86t") in the same directory. +
    7. Add `c:\Tcl\bin` to your %PATH%. To do this, go to Settings + and search for "path". Select "edit environment variables for + your account" and modify your default PATH accordingly. + You will need to close and reopen your command prompts after + making this change. +
    + + 4. Download the SQLite source tree and unpack it. CD into the + toplevel directory of the source tree. + + 5. Set the TCLDIR environment variable to point to your TCL installation. + Like this: +
      +
    • `set TCLDIR=c:\Tcl` +
    + + 6. Run the "`Makefile.msc`" makefile with an appropriate target. + Examples: +
      +
    • `nmake /f makefile.msc` +
    • `nmake /f makefile.msc sqlite3.c` +
    • `nmake /f makefile.msc devtest` +
    • `nmake /f makefile.msc releasetest` +
    + +## 32-bit Builds + +Doing a 32-bit build is just like doing a 64-bit build with the +following minor changes: + + 1. Use the "x86 Native Tools Command Prompt" instead of + "x64 Native Tools Command Prompt". "**x86**" instead of "**x64**". + + 2. Use a different installation directory for TCL. + The recommended directory is `c:\tcl32`. Thus you end up + with two TCL builds: +
      +
    • `c:\tcl` ← 64-bit (the default) +
    • `c:\tcl32` ← 32-bit +
    + + 3. Ensure that `c:\tcl32\bin` comes before `c:\tcl\bin` on + your PATH environment variable. You can achieve this using + a command like: +
      +
    • `set PATH=c:\tcl32\bin;%PATH%` +
    diff --git a/doc/lemon.html b/doc/lemon.html index 16aea8784..66665f46f 100644 --- a/doc/lemon.html +++ b/doc/lemon.html @@ -1229,8 +1229,8 @@

    6.0 History of Lemon

    Lemon was originally written by Richard Hipp sometime in the late 1980s on a Sun4 Workstation using K&R C. -There was a companion LL(1) parser generator program named "Lime", the -source code to which as been lost.

    +There was a companion LL(1) parser generator program named "Lime". +The Lime source code has been lost.

    The lemon.c source file was originally many separate files that were compiled together to generate the "lemon" executable. Sometime in the diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 393f8a871..32b483b34 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -4347,6 +4347,7 @@ static int fts3IncrmergeLoad( for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){ NodeReader reader; + memset(&reader, 0, sizeof(reader)); pNode = &pWriter->aNodeWriter[i]; if( pNode->block.a){ diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h index 081e534f3..323d73a28 100644 --- a/ext/fts5/fts5.h +++ b/ext/fts5/fts5.h @@ -263,7 +263,7 @@ struct Fts5PhraseIter { ** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 2 */ void *(*xUserData)(Fts5Context*); @@ -492,8 +492,8 @@ struct Fts5ExtensionApi { ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; @@ -541,7 +541,7 @@ struct fts5_api { int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); @@ -550,7 +550,7 @@ struct fts5_api { int (*xFindTokenizer)( fts5_api *pApi, const char *zName, - void **ppContext, + void **ppUserData, fts5_tokenizer *pTokenizer ); @@ -558,7 +558,7 @@ struct fts5_api { int (*xCreateFunction)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 5d05da875..8bbafbaaf 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -154,6 +154,10 @@ typedef struct Fts5Config Fts5Config; ** attempt to merge together. A value of 1 sets the object to use the ** compile time default. Zero disables auto-merge altogether. ** +** bContentlessDelete: +** True if the contentless_delete option was present in the CREATE +** VIRTUAL TABLE statement. +** ** zContent: ** ** zContentRowid: @@ -188,6 +192,7 @@ struct Fts5Config { int nPrefix; /* Number of prefix indexes */ int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */ int eContent; /* An FTS5_CONTENT value */ + int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */ char *zContent; /* content table */ char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ @@ -209,6 +214,7 @@ struct Fts5Config { char *zRank; /* Name of rank function */ char *zRankArgs; /* Arguments to rank function */ int bSecureDelete; /* 'secure-delete' */ + int nDeleteMerge; /* 'deletemerge' */ /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ char **pzErrmsg; @@ -531,6 +537,9 @@ int sqlite3Fts5IndexReset(Fts5Index *p); int sqlite3Fts5IndexLoadConfig(Fts5Index *p); +int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin); +int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid); + /* ** End of interface to code in fts5_index.c. **************************************************************************/ @@ -615,6 +624,11 @@ int sqlite3Fts5HashWrite( */ void sqlite3Fts5HashClear(Fts5Hash*); +/* +** Return true if the hash is empty, false otherwise. +*/ +int sqlite3Fts5HashIsEmpty(Fts5Hash*); + int sqlite3Fts5HashQuery( Fts5Hash*, /* Hash table to query */ int nPre, @@ -636,6 +650,7 @@ void sqlite3Fts5HashScanEntry(Fts5Hash *, ); + /* ** End of interface to code in fts5_hash.c. **************************************************************************/ diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c index 7a4c7b817..5d0770502 100644 --- a/ext/fts5/fts5_config.c +++ b/ext/fts5/fts5_config.c @@ -22,6 +22,8 @@ #define FTS5_DEFAULT_CRISISMERGE 16 #define FTS5_DEFAULT_HASHSIZE (1024*1024) +#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */ + /* Maximum allowed page size */ #define FTS5_MAX_PAGE_SIZE (64*1024) @@ -352,6 +354,16 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bContentlessDelete = (zArg[0]=='1'); + } + return rc; + } + if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ if( pConfig->zContentRowid ){ *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); @@ -596,6 +608,28 @@ int sqlite3Fts5ConfigParse( sqlite3_free(zTwo); } + /* We only allow contentless_delete=1 if the table is indeed contentless. */ + if( rc==SQLITE_OK + && pRet->bContentlessDelete + && pRet->eContent!=FTS5_CONTENT_NONE + ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 requires a contentless table" + ); + rc = SQLITE_ERROR; + } + + /* We only allow contentless_delete=1 if columnsize=0 is not present. + ** + ** This restriction may be removed at some point. + */ + if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 is incompatible with columnsize=0" + ); + rc = SQLITE_ERROR; + } + /* If a tokenizer= option was successfully parsed, the tokenizer has ** already been allocated. Otherwise, allocate an instance of the default ** tokenizer (unicode61) now. */ @@ -890,6 +924,18 @@ int sqlite3Fts5ConfigSetValue( } } + else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){ + int nVal = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nVal = sqlite3_value_int(pVal); + }else{ + *pbBadkey = 1; + } + if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE; + if( nVal>100 ) nVal = 0; + pConfig->nDeleteMerge = nVal; + } + else if( 0==sqlite3_stricmp(zKey, "rank") ){ const char *zIn = (const char*)sqlite3_value_text(pVal); char *zRank; @@ -938,6 +984,7 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE; pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; + pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE; zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); if( zSql ){ diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 0e018420d..f5101ba06 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -2477,7 +2477,7 @@ Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( return pRet; } -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ sqlite3_int64 nByte = 0; Fts5ExprTerm *p; @@ -2583,6 +2583,8 @@ static char *fts5ExprPrintTcl( if( zRet==0 ) return 0; } + }else if( pExpr->eType==0 ){ + zRet = sqlite3_mprintf("{}"); }else{ char const *zOp = 0; int i; @@ -2844,14 +2846,14 @@ static void fts5ExprFold( sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); } } -#endif /* ifdef SQLITE_TEST */ +#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called during initialization to register the fts5_expr() scalar ** UDF with the SQLite handle passed as the only argument. */ int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) struct Fts5ExprFunc { const char *z; void (*x)(sqlite3_context*,int,sqlite3_value**); diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c index bc9244fc0..7e50c3660 100644 --- a/ext/fts5/fts5_hash.c +++ b/ext/fts5/fts5_hash.c @@ -475,7 +475,6 @@ static int fts5HashEntrySort( pList = fts5HashEntryMerge(pList, ap[i]); } - pHash->nEntry = 0; sqlite3_free(ap); *ppSorted = pList; return SQLITE_OK; @@ -529,6 +528,28 @@ int sqlite3Fts5HashScanInit( return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan); } +#ifdef SQLITE_DEBUG +static int fts5HashCount(Fts5Hash *pHash){ + int nEntry = 0; + int ii; + for(ii=0; iinSlot; ii++){ + Fts5HashEntry *p = 0; + for(p=pHash->aSlot[ii]; p; p=p->pHashNext){ + nEntry++; + } + } + return nEntry; +} +#endif + +/* +** Return true if the hash table is empty, false otherwise. +*/ +int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){ + assert( pHash->nEntry==fts5HashCount(pHash) ); + return pHash->nEntry==0; +} + void sqlite3Fts5HashScanNext(Fts5Hash *p){ assert( !sqlite3Fts5HashScanEof(p) ); p->pScan = p->pScan->pScanNext; diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index eaeeeff4f..267489a7e 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -56,6 +56,24 @@ #define FTS5_MAX_LEVEL 64 +/* +** There are two versions of the format used for the structure record: +** +** 1. the legacy format, that may be read by all fts5 versions, and +** +** 2. the V2 format, which is used by contentless_delete=1 databases. +** +** Both begin with a 4-byte "configuration cookie" value. Then, a legacy +** format structure record contains a varint - the number of levels in +** the structure. Whereas a V2 structure record contains the constant +** 4 bytes [0xff 0x00 0x00 0x01]. This is unambiguous as the value of a +** varint has to be at least 16256 to begin with "0xFF". And the default +** maximum number of levels is 64. +** +** See below for more on structure record formats. +*/ +#define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01" + /* ** Details: ** @@ -63,7 +81,7 @@ ** ** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB); ** -** , contains the following 5 types of records. See the comments surrounding +** , contains the following 6 types of records. See the comments surrounding ** the FTS5_*_ROWID macros below for a description of how %_data rowids are ** assigned to each fo them. ** @@ -71,13 +89,13 @@ ** ** The set of segments that make up an index - the index structure - are ** recorded in a single record within the %_data table. The record consists -** of a single 32-bit configuration cookie value followed by a list of -** SQLite varints. If the FTS table features more than one index (because -** there are one or more prefix indexes), it is guaranteed that all share -** the same cookie value. +** of a single 32-bit configuration cookie value followed by a list of +** SQLite varints. ** -** Immediately following the configuration cookie, the record begins with -** three varints: +** If the structure record is a V2 record, the configuration cookie is +** followed by the following 4 bytes: [0xFF 0x00 0x00 0x01]. +** +** Next, the record continues with three varints: ** ** + number of levels, ** + total number of segments on all levels, @@ -92,6 +110,12 @@ ** + first leaf page number (often 1, always greater than 0) ** + final leaf page number ** +** Then, for V2 structures only: +** +** + lower origin counter value, +** + upper origin counter value, +** + the number of tombstone hash pages. +** ** 2. The Averages Record: ** ** A single record within the %_data table. The data is a list of varints. @@ -207,6 +231,38 @@ ** * A list of delta-encoded varints - the first rowid on each subsequent ** child page. ** +** 6. Tombstone Hash Page +** +** These records are only ever present in contentless_delete=1 tables. +** There are zero or more of these associated with each segment. They +** are used to store the tombstone rowids for rows contained in the +** associated segments. +** +** The set of nHashPg tombstone hash pages associated with a single +** segment together form a single hash table containing tombstone rowids. +** To find the page of the hash on which a key might be stored: +** +** iPg = (rowid % nHashPg) +** +** Then, within page iPg, which has nSlot slots: +** +** iSlot = (rowid / nHashPg) % nSlot +** +** Each tombstone hash page begins with an 8 byte header: +** +** 1-byte: Key-size (the size in bytes of each slot). Either 4 or 8. +** 1-byte: rowid-0-tombstone flag. This flag is only valid on the +** first tombstone hash page for each segment (iPg=0). If set, +** the hash table contains rowid 0. If clear, it does not. +** Rowid 0 is handled specially. +** 2-bytes: unused. +** 4-bytes: Big-endian integer containing number of entries on page. +** +** Following this are nSlot 4 or 8 byte slots (depending on the key-size +** in the first byte of the page header). The number of slots may be +** determined based on the size of the page record and the key-size: +** +** nSlot = (nByte - 8) / key-size */ /* @@ -240,6 +296,7 @@ #define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno) #define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno) +#define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1<<16), 0, 0, ipg) #ifdef SQLITE_DEBUG int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; } @@ -275,6 +332,12 @@ struct Fts5Data { /* ** One object per %_data table. +** +** nContentlessDelete: +** The number of contentless delete operations since the most recent +** call to fts5IndexFlush() or fts5IndexDiscardData(). This is tracked +** so that extra auto-merge work can be done by fts5IndexFlush() to +** account for the delete operations. */ struct Fts5Index { Fts5Config *pConfig; /* Virtual table configuration */ @@ -289,6 +352,8 @@ struct Fts5Index { int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ int bDelete; /* Current write is a delete */ + int nContentlessDelete; /* Number of contentless delete ops */ + int nPendingRow; /* Number of INSERT in hash table */ /* Error state. */ int rc; /* Current error code */ @@ -323,11 +388,23 @@ struct Fts5DoclistIter { ** The contents of the "structure" record for each index are represented ** using an Fts5Structure record in memory. Which uses instances of the ** other Fts5StructureXXX types as components. +** +** nOriginCntr: +** This value is set to non-zero for structure records created for +** contentlessdelete=1 tables only. In that case it represents the +** origin value to apply to the next top-level segment created. */ struct Fts5StructureSegment { int iSegid; /* Segment id */ int pgnoFirst; /* First leaf page number in segment */ int pgnoLast; /* Last leaf page number in segment */ + + /* contentlessdelete=1 tables only: */ + u64 iOrigin1; + u64 iOrigin2; + int nPgTombstone; /* Number of tombstone hash table pages */ + u64 nEntryTombstone; /* Number of tombstone entries that "count" */ + u64 nEntry; /* Number of rows in this segment */ }; struct Fts5StructureLevel { int nMerge; /* Number of segments in incr-merge */ @@ -337,6 +414,7 @@ struct Fts5StructureLevel { struct Fts5Structure { int nRef; /* Object reference count */ u64 nWriteCounter; /* Total leaves written to level 0 */ + u64 nOriginCntr; /* Origin value for next top-level segment */ int nSegment; /* Total segments in this structure */ int nLevel; /* Number of levels in this index */ Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */ @@ -425,6 +503,13 @@ struct Fts5CResult { ** ** iTermIdx: ** Index of current term on iTermLeafPgno. +** +** apTombstone/nTombstone: +** These are used for contentless_delete=1 tables only. When the cursor +** is first allocated, the apTombstone[] array is allocated so that it +** is large enough for all tombstones hash pages associated with the +** segment. The pages themselves are loaded lazily from the database as +** they are required. */ struct Fts5SegIter { Fts5StructureSegment *pSeg; /* Segment to iterate through */ @@ -433,6 +518,8 @@ struct Fts5SegIter { Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ i64 iLeafOffset; /* Byte offset within current leaf */ + Fts5Data **apTombstone; /* Array of tombstone pages */ + int nTombstone; /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); @@ -562,6 +649,60 @@ static u16 fts5GetU16(const u8 *aIn){ return ((u16)aIn[0] << 8) + aIn[1]; } +/* +** The only argument points to a buffer at least 8 bytes in size. This +** function interprets the first 8 bytes of the buffer as a 64-bit big-endian +** unsigned integer and returns the result. +*/ +static u64 fts5GetU64(u8 *a){ + return ((u64)a[0] << 56) + + ((u64)a[1] << 48) + + ((u64)a[2] << 40) + + ((u64)a[3] << 32) + + ((u64)a[4] << 24) + + ((u64)a[5] << 16) + + ((u64)a[6] << 8) + + ((u64)a[7] << 0); +} + +/* +** The only argument points to a buffer at least 4 bytes in size. This +** function interprets the first 4 bytes of the buffer as a 32-bit big-endian +** unsigned integer and returns the result. +*/ +static u32 fts5GetU32(const u8 *a){ + return ((u32)a[0] << 24) + + ((u32)a[1] << 16) + + ((u32)a[2] << 8) + + ((u32)a[3] << 0); +} + +/* +** Write iVal, formated as a 64-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU64(u8 *a, u64 iVal){ + a[0] = ((iVal >> 56) & 0xFF); + a[1] = ((iVal >> 48) & 0xFF); + a[2] = ((iVal >> 40) & 0xFF); + a[3] = ((iVal >> 32) & 0xFF); + a[4] = ((iVal >> 24) & 0xFF); + a[5] = ((iVal >> 16) & 0xFF); + a[6] = ((iVal >> 8) & 0xFF); + a[7] = ((iVal >> 0) & 0xFF); +} + +/* +** Write iVal, formated as a 32-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU32(u8 *a, u32 iVal){ + a[0] = ((iVal >> 24) & 0xFF); + a[1] = ((iVal >> 16) & 0xFF); + a[2] = ((iVal >> 8) & 0xFF); + a[3] = ((iVal >> 0) & 0xFF); +} + /* ** Allocate and return a buffer at least nByte bytes in size. ** @@ -789,10 +930,17 @@ static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){ /* ** Remove all records associated with segment iSegid. */ -static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){ +static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){ + int iSegid = pSeg->iSegid; i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0); i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0)-1; fts5DataDelete(p, iFirst, iLast); + + if( pSeg->nPgTombstone ){ + i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0); + i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1); + fts5DataDelete(p, iTomb1, iTomb2); + } if( p->pIdxDeleter==0 ){ Fts5Config *pConfig = p->pConfig; fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf( @@ -903,11 +1051,19 @@ static int fts5StructureDecode( int nSegment = 0; sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */ Fts5Structure *pRet = 0; /* Structure object to return */ + int bStructureV2 = 0; /* True for FTS5_STRUCTURE_V2 */ + u64 nOriginCntr = 0; /* Largest origin value seen so far */ /* Grab the cookie value */ if( piCookie ) *piCookie = sqlite3Fts5Get32(pData); i = 4; + /* Check if this is a V2 structure record. Set bStructureV2 if it is. */ + if( 0==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4) ){ + i += 4; + bStructureV2 = 1; + } + /* Read the total number of levels and segments from the start of the ** structure record. */ i += fts5GetVarint32(&pData[i], nLevel); @@ -958,6 +1114,14 @@ static int fts5StructureDecode( i += fts5GetVarint32(&pData[i], pSeg->iSegid); i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst); i += fts5GetVarint32(&pData[i], pSeg->pgnoLast); + if( bStructureV2 ){ + i += fts5GetVarint(&pData[i], &pSeg->iOrigin1); + i += fts5GetVarint(&pData[i], &pSeg->iOrigin2); + i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntry); + nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2); + } if( pSeg->pgnoLastpgnoFirst ){ rc = FTS5_CORRUPT; break; @@ -968,6 +1132,9 @@ static int fts5StructureDecode( } } if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT; + if( bStructureV2 ){ + pRet->nOriginCntr = nOriginCntr+1; + } if( rc!=SQLITE_OK ){ fts5StructureRelease(pRet); @@ -1180,6 +1347,7 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ Fts5Buffer buf; /* Buffer to serialize record into */ int iLvl; /* Used to iterate through levels */ int iCookie; /* Cookie value to store */ + int nHdr = (pStruct->nOriginCntr>0 ? (4+4+9+9+9) : (4+9+9)); assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); memset(&buf, 0, sizeof(Fts5Buffer)); @@ -1188,9 +1356,12 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ iCookie = p->pConfig->iCookie; if( iCookie<0 ) iCookie = 0; - if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){ + if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){ sqlite3Fts5Put32(buf.p, iCookie); buf.n = 4; + if( pStruct->nOriginCntr>0 ){ + fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4); + } fts5BufferSafeAppendVarint(&buf, pStruct->nLevel); fts5BufferSafeAppendVarint(&buf, pStruct->nSegment); fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter); @@ -1204,9 +1375,17 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ assert( pLvl->nMerge<=pLvl->nSeg ); for(iSeg=0; iSegnSeg; iSeg++){ - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid); - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst); - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast); + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast); + if( pStruct->nOriginCntr>0 ){ + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry); + } } } @@ -1729,6 +1908,23 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ } } +/* +** Allocate a tombstone hash page array (pIter->apTombstone) for the +** iterator passed as the second argument. If an OOM error occurs, leave +** an error in the Fts5Index object. +*/ +static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){ + const int nTomb = pIter->pSeg->nPgTombstone; + if( nTomb>0 ){ + Fts5Data **apTomb = 0; + apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb); + if( apTomb ){ + pIter->apTombstone = apTomb; + pIter->nTombstone = nTomb; + } + } +} + /* ** Initialize the iterator object pIter to iterate through the entries in ** segment pSeg. The iterator is left pointing to the first entry when @@ -1770,6 +1966,7 @@ static void fts5SegIterInit( pIter->iPgidxOff = pIter->pLeaf->szLeaf+1; fts5SegIterLoadTerm(p, pIter, 0); fts5SegIterLoadNPos(p, pIter); + fts5SegIterAllocTombstone(p, pIter); } } @@ -2471,6 +2668,7 @@ static void fts5SegIterSeekInit( } fts5SegIterSetNext(p, pIter); + fts5SegIterAllocTombstone(p, pIter); /* Either: ** @@ -2551,6 +2749,20 @@ static void fts5SegIterHashInit( fts5SegIterSetNext(p, pIter); } +/* +** Array ap[] contains n elements. Release each of these elements using +** fts5DataRelease(). Then free the array itself using sqlite3_free(). +*/ +static void fts5IndexFreeArray(Fts5Data **ap, int n){ + if( ap ){ + int ii; + for(ii=0; iiterm); fts5DataRelease(pIter->pLeaf); fts5DataRelease(pIter->pNextLeaf); + fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone); fts5DlidxIterFree(pIter->pDlidx); sqlite3_free(pIter->aRowidOffset); memset(pIter, 0, sizeof(Fts5SegIter)); @@ -2895,6 +3108,84 @@ static void fts5MultiIterSetEof(Fts5Iter *pIter){ pIter->iSwitchRowid = pSeg->iRowid; } +/* +** The argument to this macro must be an Fts5Data structure containing a +** tombstone hash page. This macro returns the key-size of the hash-page. +*/ +#define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0]==4 ? 4 : 8) + +#define TOMBSTONE_NSLOT(pPg) \ + ((pPg->nn > 16) ? ((pPg->nn-8) / TOMBSTONE_KEYSIZE(pPg)) : 1) + +/* +** Query a single tombstone hash table for rowid iRowid. Return true if +** it is found or false otherwise. The tombstone hash table is one of +** nHashTable tables. +*/ +static int fts5IndexTombstoneQuery( + Fts5Data *pHash, /* Hash table page to query */ + int nHashTable, /* Number of pages attached to segment */ + u64 iRowid /* Rowid to query hash for */ +){ + const int szKey = TOMBSTONE_KEYSIZE(pHash); + const int nSlot = TOMBSTONE_NSLOT(pHash); + int iSlot = (iRowid / nHashTable) % nSlot; + int nCollide = nSlot; + + if( iRowid==0 ){ + return pHash->p[1]; + }else if( szKey==4 ){ + u32 *aSlot = (u32*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + }else{ + u64 *aSlot = (u64*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + } + + return 0; +} + +/* +** Return true if the iterator passed as the only argument points +** to an segment entry for which there is a tombstone. Return false +** if there is no tombstone or if the iterator is already at EOF. +*/ +static int fts5MultiIterIsDeleted(Fts5Iter *pIter){ + int iFirst = pIter->aFirst[1].iFirst; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + + if( pSeg->pLeaf && pSeg->nTombstone ){ + /* Figure out which page the rowid might be present on. */ + int iPg = ((u64)pSeg->iRowid) % pSeg->nTombstone; + assert( iPg>=0 ); + + /* If tombstone hash page iPg has not yet been loaded from the + ** database, load it now. */ + if( pSeg->apTombstone[iPg]==0 ){ + pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex, + FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg) + ); + if( pSeg->apTombstone[iPg]==0 ) return 0; + } + + return fts5IndexTombstoneQuery( + pSeg->apTombstone[iPg], + pSeg->nTombstone, + pSeg->iRowid + ); + } + + return 0; +} + /* ** Move the iterator to the next entry. ** @@ -2932,7 +3223,9 @@ static void fts5MultiIterNext( fts5AssertMultiIterSetup(p, pIter); assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); - if( pIter->bSkipEmpty==0 || pSeg->nPos ){ + if( (pIter->bSkipEmpty==0 || pSeg->nPos) + && 0==fts5MultiIterIsDeleted(pIter) + ){ pIter->xSetOutputs(pIter, pSeg); return; } @@ -2964,7 +3257,9 @@ static void fts5MultiIterNext2( } fts5AssertMultiIterSetup(p, pIter); - }while( fts5MultiIterIsEmpty(p, pIter) ); + }while( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter)) + && (p->rc==SQLITE_OK) + ); } } @@ -3519,7 +3814,9 @@ static void fts5MultiIterNew( fts5MultiIterSetEof(pNew); fts5AssertMultiIterSetup(p, pNew); - if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){ + if( (pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew)) + || fts5MultiIterIsDeleted(pNew) + ){ fts5MultiIterNext(p, pNew, 0, 0); }else if( pNew->base.bEof==0 ){ Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; @@ -3697,7 +3994,9 @@ static void fts5IndexDiscardData(Fts5Index *p){ if( p->pHash ){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; + p->nPendingRow = 0; } + p->nContentlessDelete = 0; } /* @@ -4334,6 +4633,12 @@ static void fts5IndexMergeLevel( /* Read input from all segments in the input level */ nInput = pLvl->nSeg; + + /* Set the range of origins that will go into the output segment. */ + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pLvl->aSeg[0].iOrigin1; + pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1].iOrigin2; + } } bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); @@ -4393,8 +4698,11 @@ static void fts5IndexMergeLevel( int i; /* Remove the redundant segments from the %_data table */ + assert( pSeg->nEntry==0 ); for(i=0; iaSeg[i].iSegid); + Fts5StructureSegment *pOld = &pLvl->aSeg[i]; + pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone); + fts5DataRemoveSegment(p, pOld); } /* Remove the redundant segments from the input level */ @@ -4420,6 +4728,43 @@ static void fts5IndexMergeLevel( if( pnRem ) *pnRem -= writer.nLeafWritten; } +/* +** If this is not a contentless_delete=1 table, or if the 'deletemerge' +** configuration option is set to 0, then this function always returns -1. +** Otherwise, it searches the structure object passed as the second argument +** for a level suitable for merging due to having a large number of +** tombstones in the tombstone hash. If one is found, its index is returned. +** Otherwise, if there is no suitable level, -1. +*/ +static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){ + Fts5Config *pConfig = p->pConfig; + int iRet = -1; + if( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){ + int ii; + int nBest = 0; + + for(ii=0; iinLevel; ii++){ + Fts5StructureLevel *pLvl = &pStruct->aLevel[ii]; + i64 nEntry = 0; + i64 nTomb = 0; + int iSeg; + for(iSeg=0; iSegnSeg; iSeg++){ + nEntry += pLvl->aSeg[iSeg].nEntry; + nTomb += pLvl->aSeg[iSeg].nEntryTombstone; + } + assert_nc( nEntry>0 || pLvl->nSeg==0 ); + if( nEntry>0 ){ + int nPercent = (nTomb * 100) / nEntry; + if( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){ + iRet = ii; + nBest = nPercent; + } + } + } + } + return iRet; +} + /* ** Do up to nPg pages of automerge work on the index. ** @@ -4439,14 +4784,15 @@ static int fts5IndexMerge( int iBestLvl = 0; /* Level offering the most input segments */ int nBest = 0; /* Number of input segments on best level */ - /* Set iBestLvl to the level to read input segments from. */ + /* Set iBestLvl to the level to read input segments from. Or to -1 if + ** there is no level suitable to merge segments from. */ assert( pStruct->nLevel>0 ); for(iLvl=0; iLvlnLevel; iLvl++){ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; if( pLvl->nMerge ){ if( pLvl->nMerge>nBest ){ iBestLvl = iLvl; - nBest = pLvl->nMerge; + nBest = nMin; } break; } @@ -4455,22 +4801,18 @@ static int fts5IndexMerge( iBestLvl = iLvl; } } - - /* If nBest is still 0, then the index must be empty. */ -#ifdef SQLITE_DEBUG - for(iLvl=0; nBest==0 && iLvlnLevel; iLvl++){ - assert( pStruct->aLevel[iLvl].nSeg==0 ); + if( nBestaLevel[iBestLvl].nMerge==0 ){ - break; - } + if( iBestLvl<0 ) break; bRet = 1; fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem); if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){ fts5StructurePromote(p, iBestLvl+1, pStruct); } + + if( nMin==1 ) nMin = 2; } *ppStruct = pStruct; return bRet; @@ -4636,7 +4978,7 @@ static void fts5SecureDeleteOverflow( pLeaf = 0; }else if( bDetailNone ){ break; - }else if( iNext>=pLeaf->szLeaf || iNext<4 ){ + }else if( iNext>=pLeaf->szLeaf || pLeaf->nnszLeaf || iNext<4 ){ p->rc = FTS5_CORRUPT; break; }else{ @@ -4655,9 +4997,13 @@ static void fts5SecureDeleteOverflow( int i1 = pLeaf->szLeaf; int i2 = 0; + i1 += fts5GetVarint32(&aPg[i1], iFirst); + if( iFirstrc = FTS5_CORRUPT; + break; + } aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2); if( aIdx==0 ) break; - i1 += fts5GetVarint32(&aPg[i1], iFirst); i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift); if( i1nn ){ memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1); @@ -4840,7 +5186,9 @@ static void fts5DoSecureDelete( iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); } iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); - if( nPrefix2>nPrefix ){ + if( nPrefix2>pSeg->term.n ){ + p->rc = FTS5_CORRUPT; + }else if( nPrefix2>nPrefix ){ memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); iOff += (nPrefix2-nPrefix); } @@ -4977,187 +5325,197 @@ static void fts5FlushOneHash(Fts5Index *p){ /* Obtain a reference to the index structure and allocate a new segment-id ** for the new level-0 segment. */ pStruct = fts5StructureRead(p); - iSegid = fts5AllocateSegid(p, pStruct); fts5StructureInvalidate(p); - if( iSegid ){ - const int pgsz = p->pConfig->pgsz; - int eDetail = p->pConfig->eDetail; - int bSecureDelete = p->pConfig->bSecureDelete; - Fts5StructureSegment *pSeg; /* New segment within pStruct */ - Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ - Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ - - Fts5SegWriter writer; - fts5WriteInit(p, &writer, iSegid); - - pBuf = &writer.writer.buf; - pPgidx = &writer.writer.pgidx; - - /* fts5WriteInit() should have initialized the buffers to (most likely) - ** the maximum space required. */ - assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); - assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); - - /* Begin scanning through hash table entries. This loop runs once for each - ** term/doclist currently stored within the hash table. */ - if( p->rc==SQLITE_OK ){ - p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0); - } - while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ - const char *zTerm; /* Buffer containing term */ - int nTerm; /* Size of zTerm in bytes */ - const u8 *pDoclist; /* Pointer to doclist for this term */ - int nDoclist; /* Size of doclist in bytes */ - - /* Get the term and doclist for this entry. */ - sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); - nTerm = (int)strlen(zTerm); - if( bSecureDelete==0 ){ - fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); - if( p->rc!=SQLITE_OK ) break; - assert( writer.bFirstRowidInPage==0 ); - } - - if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ - /* The entire doclist will fit on the current leaf. */ - fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); - }else{ - int bTermWritten = !bSecureDelete; - i64 iRowid = 0; - i64 iPrev = 0; - int iOff = 0; - - /* The entire doclist will not fit on this leaf. The following - ** loop iterates through the poslists that make up the current - ** doclist. */ - while( p->rc==SQLITE_OK && iOffpConfig->pgsz; + int eDetail = p->pConfig->eDetail; + int bSecureDelete = p->pConfig->bSecureDelete; + Fts5StructureSegment *pSeg; /* New segment within pStruct */ + Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ + Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ + + Fts5SegWriter writer; + fts5WriteInit(p, &writer, iSegid); + + pBuf = &writer.writer.buf; + pPgidx = &writer.writer.pgidx; + + /* fts5WriteInit() should have initialized the buffers to (most likely) + ** the maximum space required. */ + assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + + /* Begin scanning through hash table entries. This loop runs once for each + ** term/doclist currently stored within the hash table. */ + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0); + } + while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ + const char *zTerm; /* Buffer containing term */ + int nTerm; /* Size of zTerm in bytes */ + const u8 *pDoclist; /* Pointer to doclist for this term */ + int nDoclist; /* Size of doclist in bytes */ + + /* Get the term and doclist for this entry. */ + sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); + nTerm = (int)strlen(zTerm); + if( bSecureDelete==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + if( p->rc!=SQLITE_OK ) break; + assert( writer.bFirstRowidInPage==0 ); + } + + if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ + /* The entire doclist will fit on the current leaf. */ + fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); + }else{ + int bTermWritten = !bSecureDelete; + i64 iRowid = 0; + i64 iPrev = 0; + int iOff = 0; + + /* The entire doclist will not fit on this leaf. The following + ** loop iterates through the poslists that make up the current + ** doclist. */ + while( p->rc==SQLITE_OK && iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ iOff++; - nDoclist = 0; - }else{ continue; } } - }else if( (pDoclist[iOff] & 0x01) ){ - fts5FlushSecureDelete(p, pStruct, zTerm, iRowid); - if( p->rc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ - iOff++; - continue; - } } - } - - if( p->rc==SQLITE_OK && bTermWritten==0 ){ - fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); - bTermWritten = 1; - assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 ); - } - - if( writer.bFirstRowidInPage ){ - fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); - writer.bFirstRowidInPage = 0; - fts5WriteDlidxAppend(p, &writer, iRowid); - }else{ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid-iPrev); - } - if( p->rc!=SQLITE_OK ) break; - assert( pBuf->n<=pBuf->nSpace ); - iPrev = iRowid; - - if( eDetail==FTS5_DETAIL_NONE ){ - if( iOffp[pBuf->n++] = 0; - iOff++; + + if( p->rc==SQLITE_OK && bTermWritten==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + bTermWritten = 1; + assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 ); + } + + if( writer.bFirstRowidInPage ){ + fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); + writer.bFirstRowidInPage = 0; + fts5WriteDlidxAppend(p, &writer, iRowid); + }else{ + u64 iRowidDelta = (u64)iRowid - (u64)iPrev; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta); + } + if( p->rc!=SQLITE_OK ) break; + assert( pBuf->n<=pBuf->nSpace ); + iPrev = iRowid; + + if( eDetail==FTS5_DETAIL_NONE ){ if( iOffp[pBuf->n++] = 0; iOff++; + if( iOffp[pBuf->n++] = 0; + iOff++; + } + } + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); } - } - if( (pBuf->n + pPgidx->n)>=pgsz ){ - fts5WriteFlushLeaf(p, &writer); - } - }else{ - int bDummy; - int nPos; - int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); - nCopy += nPos; - if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ - /* The entire poslist will fit on the current leaf. So copy - ** it in one go. */ - fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); }else{ - /* The entire poslist will not fit on this leaf. So it needs - ** to be broken into sections. The only qualification being - ** that each varint must be stored contiguously. */ - const u8 *pPoslist = &pDoclist[iOff]; - int iPos = 0; - while( p->rc==SQLITE_OK ){ - int nSpace = pgsz - pBuf->n - pPgidx->n; - int n = 0; - if( (nCopy - iPos)<=nSpace ){ - n = nCopy - iPos; - }else{ - n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); - } - assert( n>0 ); - fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); - iPos += n; - if( (pBuf->n + pPgidx->n)>=pgsz ){ - fts5WriteFlushLeaf(p, &writer); + int bDummy; + int nPos; + int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); + nCopy += nPos; + if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ + /* The entire poslist will fit on the current leaf. So copy + ** it in one go. */ + fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); + }else{ + /* The entire poslist will not fit on this leaf. So it needs + ** to be broken into sections. The only qualification being + ** that each varint must be stored contiguously. */ + const u8 *pPoslist = &pDoclist[iOff]; + int iPos = 0; + while( p->rc==SQLITE_OK ){ + int nSpace = pgsz - pBuf->n - pPgidx->n; + int n = 0; + if( (nCopy - iPos)<=nSpace ){ + n = nCopy - iPos; + }else{ + n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); + } + assert( n>0 ); + fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); + iPos += n; + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); + } + if( iPos>=nCopy ) break; } - if( iPos>=nCopy ) break; } + iOff += nCopy; } - iOff += nCopy; } } + + /* TODO2: Doclist terminator written here. */ + /* pBuf->p[pBuf->n++] = '\0'; */ + assert( pBuf->n<=pBuf->nSpace ); + if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); } - - /* TODO2: Doclist terminator written here. */ - /* pBuf->p[pBuf->n++] = '\0'; */ - assert( pBuf->n<=pBuf->nSpace ); - if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); - } - sqlite3Fts5HashClear(pHash); - fts5WriteFinish(p, &writer, &pgnoLast); - - assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); - if( pgnoLast>0 ){ - /* Update the Fts5Structure. It is written back to the database by the - ** fts5StructureRelease() call below. */ - if( pStruct->nLevel==0 ){ - fts5StructureAddLevel(&p->rc, &pStruct); - } - fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); - if( p->rc==SQLITE_OK ){ - pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; - pSeg->iSegid = iSegid; - pSeg->pgnoFirst = 1; - pSeg->pgnoLast = pgnoLast; - pStruct->nSegment++; + sqlite3Fts5HashClear(pHash); + fts5WriteFinish(p, &writer, &pgnoLast); + + assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); + if( pgnoLast>0 ){ + /* Update the Fts5Structure. It is written back to the database by the + ** fts5StructureRelease() call below. */ + if( pStruct->nLevel==0 ){ + fts5StructureAddLevel(&p->rc, &pStruct); + } + fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); + if( p->rc==SQLITE_OK ){ + pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; + pSeg->iSegid = iSegid; + pSeg->pgnoFirst = 1; + pSeg->pgnoLast = pgnoLast; + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pStruct->nOriginCntr; + pSeg->iOrigin2 = pStruct->nOriginCntr; + pSeg->nEntry = p->nPendingRow; + pStruct->nOriginCntr++; + } + pStruct->nSegment++; + } + fts5StructurePromote(p, 0, pStruct); } - fts5StructurePromote(p, 0, pStruct); } } - fts5IndexAutomerge(p, &pStruct, pgnoLast); + fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete); fts5IndexCrisismerge(p, &pStruct); fts5StructureWrite(p, pStruct); fts5StructureRelease(pStruct); + p->nContentlessDelete = 0; } /* @@ -5165,10 +5523,11 @@ static void fts5FlushOneHash(Fts5Index *p){ */ static void fts5IndexFlush(Fts5Index *p){ /* Unless it is empty, flush the hash table to disk */ - if( p->nPendingData ){ + if( p->nPendingData || p->nContentlessDelete ){ assert( p->pHash ); - p->nPendingData = 0; fts5FlushOneHash(p); + p->nPendingData = 0; + p->nPendingRow = 0; } } @@ -5184,17 +5543,22 @@ static Fts5Structure *fts5IndexOptimizeStruct( /* Figure out if this structure requires optimization. A structure does ** not require optimization if either: ** - ** + it consists of fewer than two segments, or - ** + all segments are on the same level, or - ** + all segments except one are currently inputs to a merge operation. + ** 1. it consists of fewer than two segments, or + ** 2. all segments are on the same level, or + ** 3. all segments except one are currently inputs to a merge operation. ** - ** In the first case, return NULL. In the second, increment the ref-count - ** on *pStruct and return a copy of the pointer to it. + ** In the first case, if there are no tombstone hash pages, return NULL. In + ** the second, increment the ref-count on *pStruct and return a copy of the + ** pointer to it. */ - if( nSeg<2 ) return 0; + if( nSeg==0 ) return 0; for(i=0; inLevel; i++){ int nThis = pStruct->aLevel[i].nSeg; - if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){ + int nMerge = pStruct->aLevel[i].nMerge; + if( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){ + if( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0].nPgTombstone==0 ){ + return 0; + } fts5StructureRef(pStruct); return pStruct; } @@ -5210,6 +5574,7 @@ static Fts5Structure *fts5IndexOptimizeStruct( pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL); pNew->nRef = 1; pNew->nWriteCounter = pStruct->nWriteCounter; + pNew->nOriginCntr = pStruct->nOriginCntr; pLvl = &pNew->aLevel[pNew->nLevel-1]; pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pLvl->aSeg ){ @@ -5240,6 +5605,7 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){ assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); + assert( p->nContentlessDelete==0 ); pStruct = fts5StructureRead(p); fts5StructureInvalidate(p); @@ -5269,7 +5635,10 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){ ** INSERT command. */ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ - Fts5Structure *pStruct = fts5StructureRead(p); + Fts5Structure *pStruct = 0; + + fts5IndexFlush(p); + pStruct = fts5StructureRead(p); if( pStruct ){ int nMin = p->pConfig->nUsermerge; fts5StructureInvalidate(p); @@ -5277,7 +5646,7 @@ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct); fts5StructureRelease(pStruct); pStruct = pNew; - nMin = 2; + nMin = 1; nMerge = nMerge*-1; } if( pStruct && pStruct->nLevel ){ @@ -5791,6 +6160,9 @@ int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ p->iWriteRowid = iRowid; p->bDelete = bDelete; + if( bDelete==0 ){ + p->nPendingRow++; + } return fts5IndexReturn(p); } @@ -5828,6 +6200,9 @@ int sqlite3Fts5IndexReinit(Fts5Index *p){ fts5StructureInvalidate(p); fts5IndexDiscardData(p); memset(&s, 0, sizeof(Fts5Structure)); + if( p->pConfig->bContentlessDelete ){ + s.nOriginCntr = 1; + } fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); fts5StructureWrite(p, &s); return fts5IndexReturn(p); @@ -6219,6 +6594,347 @@ int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ return fts5IndexReturn(p); } +/* +** Retrieve the origin value that will be used for the segment currently +** being accumulated in the in-memory hash table when it is flushed to +** disk. If successful, SQLITE_OK is returned and (*piOrigin) set to +** the queried value. Or, if an error occurs, an error code is returned +** and the final value of (*piOrigin) is undefined. +*/ +int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + *piOrigin = pStruct->nOriginCntr; + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} + +/* +** Buffer pPg contains a page of a tombstone hash table - one of nPg pages +** associated with the same segment. This function adds rowid iRowid to +** the hash table. The caller is required to guarantee that there is at +** least one free slot on the page. +** +** If parameter bForce is false and the hash table is deemed to be full +** (more than half of the slots are occupied), then non-zero is returned +** and iRowid not inserted. Or, if bForce is true or if the hash table page +** is not full, iRowid is inserted and zero returned. +*/ +static int fts5IndexTombstoneAddToPage( + Fts5Data *pPg, + int bForce, + int nPg, + u64 iRowid +){ + const int szKey = TOMBSTONE_KEYSIZE(pPg); + const int nSlot = TOMBSTONE_NSLOT(pPg); + const int nElem = fts5GetU32(&pPg->p[4]); + int iSlot = (iRowid / nPg) % nSlot; + int nCollide = nSlot; + + if( szKey==4 && iRowid>0xFFFFFFFF ) return 2; + if( iRowid==0 ){ + pPg->p[1] = 0x01; + return 0; + } + + if( bForce==0 && nElem>=(nSlot/2) ){ + return 1; + } + + fts5PutU32(&pPg->p[4], nElem+1); + if( szKey==4 ){ + u32 *aSlot = (u32*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU32((u8*)&aSlot[iSlot], (u32)iRowid); + }else{ + u64 *aSlot = (u64*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU64((u8*)&aSlot[iSlot], iRowid); + } + + return 0; +} + +/* +** This function attempts to build a new hash containing all the keys +** currently in the tombstone hash table for segment pSeg. The new +** hash will be stored in the nOut buffers passed in array apOut[]. +** All pages of the new hash use key-size szKey (4 or 8). +** +** Return 0 if the hash is successfully rebuilt into the nOut pages. +** Or non-zero if it is not (because one page became overfull). In this +** case the caller should retry with a larger nOut parameter. +** +** Parameter pData1 is page iPg1 of the hash table being rebuilt. +*/ +static int fts5IndexTombstoneRehash( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int nOut, /* Number of output pages */ + Fts5Data **apOut /* Array of output hash pages */ +){ + int ii; + int res = 0; + + /* Initialize the headers of all the output pages */ + for(ii=0; iip[0] = szKey; + fts5PutU32(&apOut[ii]->p[4], 0); + } + + /* Loop through the current pages of the hash table. */ + for(ii=0; res==0 && iinPgTombstone; ii++){ + Fts5Data *pData = 0; /* Page ii of the current hash table */ + Fts5Data *pFree = 0; /* Free this at the end of the loop */ + + if( iPg1==ii ){ + pData = pData1; + }else{ + pFree = pData = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii)); + } + + if( pData ){ + int szKeyIn = TOMBSTONE_KEYSIZE(pData); + int nSlotIn = (pData->nn - 8) / szKeyIn; + int iIn; + for(iIn=0; iInp[8]; + if( aSlot[iIn] ) iVal = fts5GetU32((u8*)&aSlot[iIn]); + }else{ + u64 *aSlot = (u64*)&pData->p[8]; + if( aSlot[iIn] ) iVal = fts5GetU64((u8*)&aSlot[iIn]); + } + + /* If iVal is not 0 at this point, insert it into the new hash table */ + if( iVal ){ + Fts5Data *pPg = apOut[(iVal % nOut)]; + res = fts5IndexTombstoneAddToPage(pPg, 0, nOut, iVal); + if( res ) break; + } + } + + /* If this is page 0 of the old hash, copy the rowid-0-flag from the + ** old hash to the new. */ + if( ii==0 ){ + apOut[0]->p[1] = pData->p[1]; + } + } + fts5DataRelease(pFree); + } + + return res; +} + +/* +** This is called to rebuild the hash table belonging to segment pSeg. +** If parameter pData1 is not NULL, then one page of the existing hash table +** has already been loaded - pData1, which is page iPg1. The key-size for +** the new hash table is szKey (4 or 8). +** +** If successful, the new hash table is not written to disk. Instead, +** output parameter (*pnOut) is set to the number of pages in the new +** hash table, and (*papOut) to point to an array of buffers containing +** the new page data. +** +** If an error occurs, an error code is left in the Fts5Index object and +** both output parameters set to 0 before returning. +*/ +static void fts5IndexTombstoneRebuild( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int *pnOut, /* OUT: Number of output pages */ + Fts5Data ***papOut /* OUT: Output hash pages */ +){ + const int MINSLOT = 32; + int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey); + int nSlot = 0; /* Number of slots in each output page */ + int nOut = 0; + + /* Figure out how many output pages (nOut) and how many slots per + ** page (nSlot). There are three possibilities: + ** + ** 1. The hash table does not yet exist. In this case the new hash + ** table will consist of a single page with MINSLOT slots. + ** + ** 2. The hash table exists but is currently a single page. In this + ** case an attempt is made to grow the page to accommodate the new + ** entry. The page is allowed to grow up to nSlotPerPage (see above) + ** slots. + ** + ** 3. The hash table already consists of more than one page, or of + ** a single page already so large that it cannot be grown. In this + ** case the new hash consists of (nPg*2+1) pages of nSlotPerPage + ** slots each, where nPg is the current number of pages in the + ** hash table. + */ + if( pSeg->nPgTombstone==0 ){ + /* Case 1. */ + nOut = 1; + nSlot = MINSLOT; + }else if( pSeg->nPgTombstone==1 ){ + /* Case 2. */ + int nElem = (int)fts5GetU32(&pData1->p[4]); + assert( pData1 && iPg1==0 ); + nOut = 1; + nSlot = MAX(nElem*4, MINSLOT); + if( nSlot>nSlotPerPage ) nOut = 0; + } + if( nOut==0 ){ + /* Case 3. */ + nOut = (pSeg->nPgTombstone * 2 + 1); + nSlot = nSlotPerPage; + } + + /* Allocate the required array and output pages */ + while( 1 ){ + int res = 0; + int ii = 0; + int szPage = 0; + Fts5Data **apOut = 0; + + /* Allocate space for the new hash table */ + assert( nSlot>=MINSLOT ); + apOut = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data*) * nOut); + szPage = 8 + nSlot*szKey; + for(ii=0; iirc, + sizeof(Fts5Data)+szPage + ); + if( pNew ){ + pNew->nn = szPage; + pNew->p = (u8*)&pNew[1]; + apOut[ii] = pNew; + } + } + + /* Rebuild the hash table. */ + if( p->rc==SQLITE_OK ){ + res = fts5IndexTombstoneRehash(p, pSeg, pData1, iPg1, szKey, nOut, apOut); + } + if( res==0 ){ + if( p->rc ){ + fts5IndexFreeArray(apOut, nOut); + apOut = 0; + nOut = 0; + } + *pnOut = nOut; + *papOut = apOut; + break; + } + + /* If control flows to here, it was not possible to rebuild the hash + ** table. Free all buffers and then try again with more pages. */ + assert( p->rc==SQLITE_OK ); + fts5IndexFreeArray(apOut, nOut); + nSlot = nSlotPerPage; + nOut = nOut*2 + 1; + } +} + + +/* +** Add a tombstone for rowid iRowid to segment pSeg. +*/ +static void fts5IndexTombstoneAdd( + Fts5Index *p, + Fts5StructureSegment *pSeg, + u64 iRowid +){ + Fts5Data *pPg = 0; + int iPg = -1; + int szKey = 0; + int nHash = 0; + Fts5Data **apHash = 0; + + p->nContentlessDelete++; + + if( pSeg->nPgTombstone>0 ){ + iPg = iRowid % pSeg->nPgTombstone; + pPg = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg)); + if( pPg==0 ){ + assert( p->rc!=SQLITE_OK ); + return; + } + + if( 0==fts5IndexTombstoneAddToPage(pPg, 0, pSeg->nPgTombstone, iRowid) ){ + fts5DataWrite(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg), pPg->p, pPg->nn); + fts5DataRelease(pPg); + return; + } + } + + /* Have to rebuild the hash table. First figure out the key-size (4 or 8). */ + szKey = pPg ? TOMBSTONE_KEYSIZE(pPg) : 4; + if( iRowid>0xFFFFFFFF ) szKey = 8; + + /* Rebuild the hash table */ + fts5IndexTombstoneRebuild(p, pSeg, pPg, iPg, szKey, &nHash, &apHash); + assert( p->rc==SQLITE_OK || (nHash==0 && apHash==0) ); + + /* If all has succeeded, write the new rowid into one of the new hash + ** table pages, then write them all out to disk. */ + if( nHash ){ + int ii = 0; + fts5IndexTombstoneAddToPage(apHash[iRowid % nHash], 1, nHash, iRowid); + for(ii=0; iiiSegid, ii); + fts5DataWrite(p, iTombstoneRowid, apHash[ii]->p, apHash[ii]->nn); + } + pSeg->nPgTombstone = nHash; + fts5StructureWrite(p, p->pStruct); + } + + fts5DataRelease(pPg); + fts5IndexFreeArray(apHash, nHash); +} + +/* +** Add iRowid to the tombstone list of the segment or segments that contain +** rows from origin iOrigin. Return SQLITE_OK if successful, or an SQLite +** error code otherwise. +*/ +int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + int bFound = 0; /* True after pSeg->nEntryTombstone incr. */ + int iLvl; + for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ + int iSeg; + for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + if( pSeg->iOrigin1<=(u64)iOrigin && pSeg->iOrigin2>=(u64)iOrigin ){ + if( bFound==0 ){ + pSeg->nEntryTombstone++; + bFound = 1; + } + fts5IndexTombstoneAdd(p, pSeg, iRowid); + } + } + } + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} /************************************************************************* ************************************************************************** @@ -6770,13 +7486,14 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ ** function only. */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Decode a segment-data rowid from the %_data table. This function is ** the opposite of macro FTS5_SEGMENT_ROWID(). */ static void fts5DecodeRowid( i64 iRowid, /* Rowid from %_data table */ + int *pbTombstone, /* OUT: Tombstone hash flag */ int *piSegid, /* OUT: Segment id */ int *pbDlidx, /* OUT: Dlidx flag */ int *piHeight, /* OUT: Height */ @@ -6792,13 +7509,16 @@ static void fts5DecodeRowid( iRowid >>= FTS5_DATA_DLI_B; *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); + iRowid >>= FTS5_DATA_ID_B; + + *pbTombstone = (int)(iRowid & 0x0001); } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ - int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */ - fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno); + int iSegid, iHeight, iPgno, bDlidx, bTomb; /* Rowid compenents */ + fts5DecodeRowid(iKey, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); if( iSegid==0 ){ if( iKey==FTS5_AVERAGES_ROWID ){ @@ -6808,14 +7528,16 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ } } else{ - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%ssegid=%d h=%d pgno=%d}", - bDlidx ? "dlidx " : "", iSegid, iHeight, iPgno + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%s%ssegid=%d h=%d pgno=%d}", + bDlidx ? "dlidx " : "", + bTomb ? "tombstone " : "", + iSegid, iHeight, iPgno ); } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugStructure( int *pRc, /* IN/OUT: error code */ Fts5Buffer *pBuf, @@ -6830,16 +7552,22 @@ static void fts5DebugStructure( ); for(iSeg=0; iSegnSeg; iSeg++){ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d}", + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d", pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast ); + if( pSeg->iOrigin1>0 ){ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " origin=%lld..%lld", + pSeg->iOrigin1, pSeg->iOrigin2 + ); + } + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** @@ -6864,9 +7592,9 @@ static void fts5DecodeStructure( fts5DebugStructure(pRc, pBuf, p); fts5StructureRelease(p); } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** @@ -6889,9 +7617,9 @@ static void fts5DecodeAverages( zSpace = " "; } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Buffer (a/n) is assumed to contain a list of serialized varints. Read ** each varint and append its string representation to buffer pBuf. Return @@ -6908,9 +7636,9 @@ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ } return iOff; } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The start of buffer (a/n) contains the start of a doclist. The doclist ** may or may not finish within the buffer. This function appends a text @@ -6943,9 +7671,9 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ return iOff; } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This function is part of the fts5_decode() debugging function. It is ** only ever used with detail=none tables. @@ -6986,9 +7714,9 @@ static void fts5DecodeRowidList( sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The implementation of user-defined scalar function fts5_decode(). */ @@ -6999,6 +7727,7 @@ static void fts5DecodeFunction( ){ i64 iRowid; /* Rowid for record being decoded */ int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */ + int bTomb; const u8 *aBlob; int n; /* Record to decode */ u8 *a = 0; Fts5Buffer s; /* Build up text to return here */ @@ -7021,7 +7750,7 @@ static void fts5DecodeFunction( if( a==0 ) goto decode_out; if( n>0 ) memcpy(a, aBlob, n); - fts5DecodeRowid(iRowid, &iSegid, &bDlidx, &iHeight, &iPgno); + fts5DecodeRowid(iRowid, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); fts5DebugRowid(&rc, &s, iRowid); if( bDlidx ){ @@ -7040,6 +7769,28 @@ static void fts5DecodeFunction( " %d(%lld)", lvl.iLeafPgno, lvl.iRowid ); } + }else if( bTomb ){ + u32 nElem = fts5GetU32(&a[4]); + int szKey = (aBlob[0]==4 || aBlob[0]==8) ? aBlob[0] : 8; + int nSlot = (n - 8) / szKey; + int ii; + sqlite3Fts5BufferAppendPrintf(&rc, &s, " nElem=%d", (int)nElem); + if( aBlob[1] ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, " 0"); + } + for(ii=0; iiszLeaf ){ + rc = FTS5_CORRUPT; + }else{ + fts5DecodeRowidList(&rc, &s, &a[iOff], iTermOff-iOff); + } iOff = iTermOff; if( iOffestimatedCost = (double)100; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 0; + for(i=0, p=pIdxInfo->aConstraint; inConstraint; i++, p++){ + if( p->usable==0 ) continue; + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==11 ){ + rc = SQLITE_OK; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + break; + } + } + return rc; +} + +/* +** This method is the destructor for bytecodevtab objects. +*/ +static int fts5structDisconnectMethod(sqlite3_vtab *pVtab){ + Fts5StructVtab *p = (Fts5StructVtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new bytecodevtab_cursor object. +*/ +static int fts5structOpenMethod(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){ + int rc = SQLITE_OK; + Fts5StructVcsr *pNew = 0; + + pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); + *ppCsr = (sqlite3_vtab_cursor*)pNew; + + return SQLITE_OK; +} + +/* +** Destructor for a bytecodevtab_cursor. +*/ +static int fts5structCloseMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + fts5StructureRelease(pCsr->pStruct); + sqlite3_free(pCsr); + return SQLITE_OK; +} + + +/* +** Advance a bytecodevtab_cursor to its next row of output. +*/ +static int fts5structNextMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + + assert( pCsr->pStruct ); + pCsr->iSeg++; + pCsr->iRowid++; + while( pCsr->iLevelnLevel && pCsr->iSeg>=p->aLevel[pCsr->iLevel].nSeg ){ + pCsr->iLevel++; + pCsr->iSeg = 0; + } + if( pCsr->iLevel>=p->nLevel ){ + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + } + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fts5structEofMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + return pCsr->pStruct==0; +} + +static int fts5structRowidMethod( + sqlite3_vtab_cursor *cur, + sqlite_int64 *piRowid +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + *piRowid = pCsr->iRowid; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the bytecodevtab_cursor +** is currently pointing. +*/ +static int fts5structColumnMethod( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + Fts5StructureSegment *pSeg = &p->aLevel[pCsr->iLevel].aSeg[pCsr->iSeg]; + + switch( i ){ + case 0: /* level */ + sqlite3_result_int(ctx, pCsr->iLevel); + break; + case 1: /* segment */ + sqlite3_result_int(ctx, pCsr->iSeg); + break; + case 2: /* merge */ + sqlite3_result_int(ctx, pCsr->iSeg < p->aLevel[pCsr->iLevel].nMerge); + break; + case 3: /* segid */ + sqlite3_result_int(ctx, pSeg->iSegid); + break; + case 4: /* leaf1 */ + sqlite3_result_int(ctx, pSeg->pgnoFirst); + break; + case 5: /* leaf2 */ + sqlite3_result_int(ctx, pSeg->pgnoLast); + break; + case 6: /* origin1 */ + sqlite3_result_int64(ctx, pSeg->iOrigin1); + break; + case 7: /* origin2 */ + sqlite3_result_int64(ctx, pSeg->iOrigin2); + break; + case 8: /* npgtombstone */ + sqlite3_result_int(ctx, pSeg->nPgTombstone); + break; + case 9: /* nentrytombstone */ + sqlite3_result_int64(ctx, pSeg->nEntryTombstone); + break; + case 10: /* nentry */ + sqlite3_result_int64(ctx, pSeg->nEntry); + break; + } + return SQLITE_OK; +} + +/* +** Initialize a cursor. +** +** idxNum==0 means show all subprograms +** idxNum==1 means show only the main bytecode and omit subprograms. +*/ +static int fts5structFilterMethod( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr *)pVtabCursor; + int rc = SQLITE_OK; + + const u8 *aBlob = 0; + int nBlob = 0; + + assert( argc==1 ); + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + + nBlob = sqlite3_value_bytes(argv[0]); + aBlob = (const u8*)sqlite3_value_blob(argv[0]); + rc = fts5StructureDecode(aBlob, nBlob, 0, &pCsr->pStruct); + if( rc==SQLITE_OK ){ + pCsr->iLevel = 0; + pCsr->iRowid = 0; + pCsr->iSeg = -1; + rc = fts5structNextMethod(pVtabCursor); + } + + return rc; +} + +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called as part of registering the FTS5 module with database @@ -7244,7 +8226,7 @@ static void fts5RowidFunction( ** SQLite error code is returned instead. */ int sqlite3Fts5IndexInit(sqlite3 *db){ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) int rc = sqlite3_create_function( db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 ); @@ -7261,6 +8243,36 @@ int sqlite3Fts5IndexInit(sqlite3 *db){ db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0 ); } + + if( rc==SQLITE_OK ){ + static const sqlite3_module fts5structure_module = { + 0, /* iVersion */ + 0, /* xCreate */ + fts5structConnectMethod, /* xConnect */ + fts5structBestIndexMethod, /* xBestIndex */ + fts5structDisconnectMethod, /* xDisconnect */ + 0, /* xDestroy */ + fts5structOpenMethod, /* xOpen */ + fts5structCloseMethod, /* xClose */ + fts5structFilterMethod, /* xFilter */ + fts5structNextMethod, /* xNext */ + fts5structEofMethod, /* xEof */ + fts5structColumnMethod, /* xColumn */ + fts5structRowidMethod, /* xRowid */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0); + } return rc; #else return SQLITE_OK; diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 13921ce49..c34a5a332 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -1624,7 +1624,6 @@ static int fts5UpdateMethod( int eType0; /* value_type() of apVal[0] */ int rc = SQLITE_OK; /* Return code */ int bUpdateOrDelete = 0; - /* A transaction must be open when this is called. */ assert( pTab->ts.eState==1 || pTab->ts.eState==2 ); @@ -1654,7 +1653,14 @@ static int fts5UpdateMethod( if( pConfig->eContent!=FTS5_CONTENT_NORMAL && 0==sqlite3_stricmp("delete", z) ){ - rc = fts5SpecialDelete(pTab, apVal); + if( pConfig->bContentlessDelete ){ + fts5SetVtabError(pTab, + "'delete' may not be used with a contentless_delete=1 table" + ); + rc = SQLITE_ERROR; + }else{ + rc = fts5SpecialDelete(pTab, apVal); + } }else{ rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); } @@ -1671,7 +1677,7 @@ static int fts5UpdateMethod( ** Cases 3 and 4 may violate the rowid constraint. */ int eConflict = SQLITE_ABORT; - if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){ eConflict = sqlite3_vtab_on_conflict(pConfig->db); } @@ -1679,8 +1685,12 @@ static int fts5UpdateMethod( assert( nArg!=1 || eType0==SQLITE_INTEGER ); /* Filter out attempts to run UPDATE or DELETE on contentless tables. - ** This is not suported. */ - if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){ + ** This is not suported. Except - DELETE is supported if the CREATE + ** VIRTUAL TABLE statement contained "contentless_delete=1". */ + if( eType0==SQLITE_INTEGER + && pConfig->eContent==FTS5_CONTENT_NONE + && pConfig->bContentlessDelete==0 + ){ pTab->p.base.zErrMsg = sqlite3_mprintf( "cannot %s contentless fts5 table: %s", (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName @@ -1767,8 +1777,7 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; fts5CheckTransactionState(pTab, FTS5_SYNC, 0); pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; - fts5TripCursors(pTab); - rc = sqlite3Fts5StorageSync(pTab->pStorage); + rc = sqlite3Fts5FlushToDisk(&pTab->p); pTab->p.pConfig->pzErrmsg = 0; return rc; } @@ -2535,6 +2544,12 @@ static int fts5ColumnMethod( sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); } pConfig->pzErrmsg = 0; + }else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){ + char *zErr = sqlite3_mprintf("cannot UPDATE a subset of " + "columns on fts5 contentless-delete table: %s", pConfig->zName + ); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); } return rc; } diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index 02b98d9e4..0a0af9d4b 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -77,10 +77,10 @@ static int fts5StorageGetStmt( "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ - "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */ + "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */ "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */ - "SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ + "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */ "SELECT %s FROM %s AS T", /* SCAN */ @@ -128,6 +128,19 @@ static int fts5StorageGetStmt( break; } + case FTS5_STMT_REPLACE_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, + (pC->bContentlessDelete ? ",?" : "") + ); + break; + + case FTS5_STMT_LOOKUP_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], + (pC->bContentlessDelete ? ",origin" : ""), + pC->zDb, pC->zName + ); + break; + default: zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); break; @@ -317,9 +330,11 @@ int sqlite3Fts5StorageOpen( } if( rc==SQLITE_OK && pConfig->bColumnsize ){ - rc = sqlite3Fts5CreateTable( - pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr - ); + const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB"; + if( pConfig->bContentlessDelete ){ + zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER"; + } + rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr); } if( rc==SQLITE_OK ){ rc = sqlite3Fts5CreateTable( @@ -396,7 +411,7 @@ static int fts5StorageDeleteFromIndex( ){ Fts5Config *pConfig = p->pConfig; sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */ - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ int rc2; /* sqlite3_reset() return code */ int iCol; Fts5InsertCtx ctx; @@ -412,7 +427,6 @@ static int fts5StorageDeleteFromIndex( ctx.pStorage = p; ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1]==0 ){ const char *zText; @@ -449,6 +463,37 @@ static int fts5StorageDeleteFromIndex( return rc; } +/* +** This function is called to process a DELETE on a contentless_delete=1 +** table. It adds the tombstone required to delete the entry with rowid +** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs, +** an SQLite error code. +*/ +static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){ + i64 iOrigin = 0; + sqlite3_stmt *pLookup = 0; + int rc = SQLITE_OK; + + assert( p->pConfig->bContentlessDelete ); + assert( p->pConfig->eContent==FTS5_CONTENT_NONE ); + + /* Look up the origin of the document in the %_docsize table. Store + ** this in stack variable iOrigin. */ + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pLookup, 1, iDel); + if( SQLITE_ROW==sqlite3_step(pLookup) ){ + iOrigin = sqlite3_column_int64(pLookup, 1); + } + rc = sqlite3_reset(pLookup); + } + + if( rc==SQLITE_OK && iOrigin!=0 ){ + rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel); + } + + return rc; +} /* ** Insert a record into the %_docsize table. Specifically, do: @@ -469,10 +514,17 @@ static int fts5StorageInsertDocsize( rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pReplace, 1, iRowid); - sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); - sqlite3_step(pReplace); - rc = sqlite3_reset(pReplace); - sqlite3_bind_null(pReplace, 2); + if( p->pConfig->bContentlessDelete ){ + i64 iOrigin = 0; + rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin); + sqlite3_bind_int64(pReplace, 3, iOrigin); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); + sqlite3_step(pReplace); + rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 2); + } } } return rc; @@ -536,7 +588,15 @@ int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){ /* Delete the index records */ if( rc==SQLITE_OK ){ - rc = fts5StorageDeleteFromIndex(p, iDel, apVal); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); + } + + if( rc==SQLITE_OK ){ + if( p->pConfig->bContentlessDelete ){ + rc = fts5StorageContentlessDelete(p, iDel); + }else{ + rc = fts5StorageDeleteFromIndex(p, iDel, apVal); + } } /* Delete the %_docsize record */ diff --git a/ext/fts5/test/fts5bigid.test b/ext/fts5/test/fts5bigid.test new file mode 100644 index 000000000..ae20ec641 --- /dev/null +++ b/ext/fts5/test/fts5bigid.test @@ -0,0 +1,62 @@ +# 2023 May 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5bigid + +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +set nRow 20000 + +proc do_ascdesc_test {tn query} { + set ::lAsc [db eval { SELECT rowid FROM x1($query) }] + set ::lDesc [db eval { SELECT rowid FROM x1($query) ORDER BY rowid DESC }] + do_test $tn.1 { lsort -integer $::lAsc } $::lAsc + do_test $tn.2 { lsort -integer -decr $::lDesc } $::lDesc + do_test $tn.3 { lsort -integer $::lDesc } $::lAsc +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING fts5(a); +} + +do_test 1.1 { + for {set ii 0} {$ii < $nRow} {incr ii} { + db eval { + REPLACE INTO x1(rowid, a) VALUES(random(), 'movement at the station'); + } + } +} {} + +do_ascdesc_test 1.2 "the" + +do_execsql_test 1.3 { + DELETE FROM x1 +} + +do_test 1.4 { + for {set ii 0} {$ii < $nRow} {incr ii} { + db eval { + INSERT INTO x1(rowid, a) VALUES( + $ii + 0x6FFFFFFFFFFFFFFF, 'movement at the station' + ); + } + } +} {} + +do_ascdesc_test 1.5 "movement" + +finish_test diff --git a/ext/fts5/test/fts5contentless.test b/ext/fts5/test/fts5contentless.test new file mode 100644 index 000000000..f75ccb44c --- /dev/null +++ b/ext/fts5/test/fts5contentless.test @@ -0,0 +1,271 @@ +# 2014 Dec 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 contains tests for the content= and content_rowid= options. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5contentless + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +# Check that it is not possible to specify "contentless_delete=1" for +# anything other than a contentless table. +# +set res(0) {0 {}} +set res(1) {1 {contentless_delete=1 requires a contentless table}} +foreach {tn sql bError} { + 1 "(a, b, contentless_delete=1)" 1 + 2 "(a, b, contentless_delete=1, content=abc)" 1 + 3 "(a, b, contentless_delete=1, content=)" 0 + 4 "(content=, contentless_delete=1, a)" 0 + 5 "(content='', contentless_delete=1, hello)" 0 +} { + execsql { BEGIN } + do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError) + execsql { ROLLBACK } +} + +# Check that it is not possible to specify "contentless_delete=1" +# along with columnsize=1. +# +set res(0) {0 {}} +set res(1) {1 {contentless_delete=1 is incompatible with columnsize=0}} +foreach {tn sql bError} { + 2 "(a, b, content='', contentless_delete=1, columnsize=0)" 1 +} { + execsql { BEGIN } + do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError) + execsql { ROLLBACK } +} + +# Check that if contentless_delete=1 is specified, then the "origin" +# column is added to the %_docsize table. +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE x1 USING fts5(c, content=''); + CREATE VIRTUAL TABLE x2 USING fts5(c, content='', contentless_delete=1); +} +do_execsql_test 3.1 { + SELECT sql FROM sqlite_schema WHERE name IN ('x1_docsize', 'x2_docsize'); +} { + {CREATE TABLE 'x1_docsize'(id INTEGER PRIMARY KEY, sz BLOB)} + {CREATE TABLE 'x2_docsize'(id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER)} +} + +do_execsql_test 3.2.1 { + SELECT hex(block) FROM x1_data WHERE id=10 +} {00000000000000} +do_execsql_test 3.2.2 { + SELECT hex(block) FROM x2_data WHERE id=10 +} {00000000FF000001000000} + +do_execsql_test 3.3 { + INSERT INTO x2 VALUES('first text'); + INSERT INTO x2 VALUES('second text'); +} +do_execsql_test 3.4 { + SELECT id, origin FROM x2_docsize +} {1 1 2 2} +do_execsql_test 3.5 { + SELECT level, segment, loc1, loc2 FROM fts5_structure( + (SELECT block FROM x2_data WHERE id=10) + ) +} { + 0 0 1 1 + 0 1 2 2 +} +do_execsql_test 3.6 { + INSERT INTO x2(x2) VALUES('optimize'); +} +do_execsql_test 3.7 { + SELECT level, segment, loc1, loc2 FROM fts5_structure( + (SELECT block FROM x2_data WHERE id=10) + ) +} { + 1 0 1 2 +} + +do_execsql_test 3.8 { + DELETE FROM x2 WHERE rowid=2; +} + +do_execsql_test 3.9 { + SELECT rowid FROM x2('text') +} {1} + +#-------------------------------------------------------------------------- +reset_db +proc document {n} { + set vocab [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] + set ret [list] + for {set ii 0} {$ii < $n} {incr ii} { + lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]] + } + set ret +} + +set nRow 1000 + +do_execsql_test 4.0 { + CREATE TABLE t1(x); + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); + INSERT INTO ft(ft, rank) VALUES('pgsz', 100); +} +do_test 4.1 { + for {set ii 0} {$ii < $nRow} {incr ii} { + set doc [document 6] + execsql { + INSERT INTO t1 VALUES($doc); + INSERT INTO ft VALUES($doc); + } + } +} {} + +foreach v {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} { + set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}] + set L2 [execsql {SELECT rowid FROM ft($v)}] + do_test 4.2.$v { set L1 } $L2 +} + +do_test 4.3 { + for {set ii 1} {$ii < $nRow} {incr ii 2} { + execsql { + DELETE FROM ft WHERE rowid=$ii; + DELETE FROM t1 WHERE rowid=$ii; + } + } +} {} + +foreach v {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} { + set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}] + set L2 [execsql {SELECT rowid FROM ft($v)}] + do_test 4.4.$v { set L1 } $L2 +} + +do_execsql_test 4.5 { + INSERT INTO ft(ft) VALUES('optimize'); +} {} + +foreach v {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} { + set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}] + set L2 [execsql {SELECT rowid FROM ft($v)}] + do_test 4.6.$v { set L1 } $L2 +} + +#execsql_pp { SELECT fts5_decode(id, block) FROM ft_data } + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); + INSERT INTO ft(rowid, x) VALUES(1, 'one two three'); + INSERT INTO ft(rowid, x) VALUES(2, 'one two four'); + INSERT INTO ft(rowid, x) VALUES(3, 'one two five'); + INSERT INTO ft(rowid, x) VALUES(4, 'one two seven'); + INSERT INTO ft(rowid, x) VALUES(5, 'one two eight'); +} + +do_execsql_test 5.1 { + DELETE FROM ft WHERE rowid=2 +} + +do_execsql_test 5.2 { + SELECT rowid FROM ft +} {1 3 4 5} + +do_catchsql_test 5.3 { + UPDATE ft SET x='four six' WHERE rowid=3 +} {0 {}} + +do_execsql_test 5.4 { + SELECT rowid FROM ft('one'); +} {1 4 5} + +do_execsql_test 5.5 { + REPLACE INTO ft(rowid, x) VALUES(3, 'four six'); + SELECT rowid FROM ft('one'); +} {1 4 5} + +do_execsql_test 5.6 { + REPLACE INTO ft(rowid, x) VALUES(6, 'one two eleven'); + SELECT rowid FROM ft('one'); +} {1 4 5 6} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); + INSERT INTO ft(rowid, x) VALUES(1, 'one two three'); + INSERT INTO ft(rowid, x) VALUES(2, 'one two four'); +} + +do_test 6.1 { + db eval { SELECT rowid FROM ft('one two') } { + if {$rowid==1} { + db eval { INSERT INTO ft(rowid, x) VALUES(3, 'one two four') } + } + } +} {} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 7.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); +} + +set lRowid [list -450 0 1 2 42] + +do_test 7.1 { + execsql BEGIN + foreach r $lRowid { + execsql { INSERT INTO ft(rowid, x) VALUES($r, 'one one one'); } + } + execsql COMMIT +} {} + +do_test 7.2 { + execsql BEGIN + foreach r $lRowid { + execsql { REPLACE INTO ft(rowid, x) VALUES($r, 'two two two'); } + } + execsql COMMIT +} {} + +do_execsql_test 7.3 { SELECT rowid FROM ft('one'); } {} +do_execsql_test 7.4 { SELECT rowid FROM ft('two'); } $lRowid + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 8.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); + INSERT INTO ft VALUES('hello world'); + INSERT INTO ft VALUES('one two three'); +} + +do_catchsql_test 8.1 { + INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, 'hello world'); +} {1 {'delete' may not be used with a contentless_delete=1 table}} + +do_execsql_test 8.2 { + BEGIN; + INSERT INTO ft(rowid, x) VALUES(3, 'four four four'); + DELETE FROM ft WHERE rowid=3; + COMMIT; + SELECT rowid FROM ft('four'); +} {} + +finish_test + diff --git a/ext/fts5/test/fts5contentless2.test b/ext/fts5/test/fts5contentless2.test new file mode 100644 index 000000000..fbb857ab3 --- /dev/null +++ b/ext/fts5/test/fts5contentless2.test @@ -0,0 +1,208 @@ +# 2023 July 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. +# +#*********************************************************************** +# +# This file contains tests for the content= and content_rowid= options. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5contentless2 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +proc vocab {} { + list aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp +} + +proc document {nToken} { + set doc [list] + set vocab [vocab] + for {set ii 0} {$ii < $nToken} {incr ii} { + lappend doc [lindex $vocab [expr int(rand()*[llength $vocab])]] + } + set doc +} +db func document document + +proc contains {doc token} { + expr {[lsearch $doc $token]>=0} +} +db func contains contains + +proc do_compare_tables_test {tn} { + uplevel [list do_test $tn { + foreach v [vocab] { + set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $v) }] + set l2 [execsql { SELECT rowid FROM t2($v) }] + if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" } + + set w "[string range $v 0 1]*" + set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }] + set l2 [execsql { SELECT rowid FROM t2($w) }] + if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" } + + set w "[string range $v 0 0]*" + set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }] + set l2 [execsql { SELECT rowid FROM t2($w) }] + if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" } + + set l1 [execsql { + SELECT rowid FROM t1 WHERE contains(doc, $v) ORDER BY rowid DESC + }] + set l2 [execsql { SELECT rowid FROM t2($v) ORDER BY rowid DESC }] + if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" } + } + set {} {} + } {}] +} + +proc lshuffle {in} { + set L [list] + set ret [list] + foreach elem $in { lappend L [list [expr rand()] $elem] } + foreach pair [lsort -index 0 $L] { lappend ret [lindex $pair 1] } + set ret +} + +expr srand(0) + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t2 USING fts5( + doc, prefix=2, content=, contentless_delete=1 + ); + + CREATE TABLE t1(doc); + CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN + DELETE FROM t2 WHERE rowid = old.rowid; + END; +} + +set SMALLEST64 -9223372036854775808 +set LARGEST64 9223372036854775807 + +foreach {tn r1 r2} { + 1 0 50 + 2 $SMALLEST64 $SMALLEST64+50 + 3 $LARGEST64-50 $LARGEST64 + 4 -50 -1 +} { + set r1 [expr $r1] + set r2 [expr $r2] + + do_test 1.1.$tn { + execsql BEGIN + for {set ii $r1} {$ii <= $r2} {incr ii} { + execsql { INSERT INTO t1(rowid, doc) VALUES ($ii, document(8)); } + } + execsql COMMIT + } {} +} +do_test 1.2 { + db eval { SELECT rowid, doc FROM t1 } { + execsql { INSERT INTO t2(rowid, doc) VALUES($rowid, $doc) } + } +} {} + +foreach {tn rowid} { + 1 $SMALLEST64 + 2 0 + 3 -5 + 4 -30 + 5 $LARGEST64 + 6 $LARGEST64-1 +} { + set rowid [expr $rowid] + do_execsql_test 1.3.$tn.1 { + DELETE FROM t1 WHERE rowid=$rowid + } + do_compare_tables_test 1.3.$tn.2 +} + +set iTest 1 +foreach r [lshuffle [execsql {SELECT rowid FROM t1}]] { + if {($iTest % 50)==0} { + execsql { INSERT INTO t2(t2) VALUES('optimize') } + } + if {($iTest % 5)==0} { + execsql { INSERT INTO t2(t2, rank) VALUES('merge', 5) } + } + do_execsql_test 1.4.$iTest.1($r) { + DELETE FROM t1 WHERE rowid=$r + } + do_compare_tables_test 1.4.$iTest.2 + incr iTest +} + +do_execsql_test 1.5 { + SELECT * FROM t1 +} {} + +#------------------------------------------------------------------------- +reset_db +db func document document + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) + INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s; +} + +do_execsql_test 2.1 { + BEGIN; + DELETE FROM t2 WHERE rowid=32; + DELETE FROM t2 WHERE rowid=64; + DELETE FROM t2 WHERE rowid=96; + DELETE FROM t2 WHERE rowid=128; + DELETE FROM t2 WHERE rowid=160; + DELETE FROM t2 WHERE rowid=192; + COMMIT; +} + +do_execsql_test 2.2 { + SELECT * FROM t2('128'); +} {} + +#------------------------------------------------------------------------- + +foreach {tn step} { + 1 3 + 2 7 + 3 15 +} { + set step [expr $step] + + reset_db + db func document document + do_execsql_test 3.$tn.0 { + CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1); + INSERT INTO t2(t2, rank) VALUES('pgsz', 100); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) + INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s; + } + do_execsql_test 3.$tn.1 { + DELETE FROM t2 WHERE (rowid % $step)==0 + } + do_execsql_test 3.$tn.2 { + SELECT * FROM t2( $step * 5 ) + } {} +} + + + +finish_test + diff --git a/ext/fts5/test/fts5contentless3.test b/ext/fts5/test/fts5contentless3.test new file mode 100644 index 000000000..a44311e45 --- /dev/null +++ b/ext/fts5/test/fts5contentless3.test @@ -0,0 +1,196 @@ +# 2023 July 21 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file contains tests for the content= and content_rowid= options. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5contentless3 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1); + BEGIN; + INSERT INTO ft VALUES('one one one'); + INSERT INTO ft VALUES('two two two'); + INSERT INTO ft VALUES('three three three'); + INSERT INTO ft VALUES('four four four'); + INSERT INTO ft VALUES('five five five'); + INSERT INTO ft VALUES('six six six'); + INSERT INTO ft VALUES('seven seven seven'); + INSERT INTO ft VALUES('eight eight eight'); + INSERT INTO ft VALUES('nine nine nine'); + COMMIT; + + DELETE FROM ft WHERE rowid=3; +} + +proc myhex {hex} { binary decode hex $hex } +db func myhex myhex + +do_execsql_test 1.1 { + UPDATE ft_data SET block = + myhex('04000000 00000001' || + '01020304 01020304 01020304 01020304' || + '01020304 01020304 01020304 01020304' + ) + WHERE id = (SELECT max(id) FROM ft_data); +} + +do_execsql_test 1.2 { + DELETE FROM ft WHERE rowid=1 +} + +do_execsql_test 1.3 { + SELECT rowid FROM ft('two'); +} {2} + +do_execsql_test 1.3 { + UPDATE ft_data SET block = + myhex('08000000 00000001' || + '0000000001020304 0000000001020304 0000000001020304 0000000001020304' || + '0000000001020304 0000000001020304 0000000001020304 0000000001020304' + ) + WHERE id = (SELECT max(id) FROM ft_data); +} + +do_execsql_test 1.4 { + SELECT rowid FROM ft('two'); +} {2} + +do_execsql_test 1.5 { + DELETE FROM ft WHERE rowid=4 +} + +do_execsql_test 1.6 { + UPDATE ft_data SET block = myhex('04000000 00000000') + WHERE id = (SELECT max(id) FROM ft_data); +} +do_execsql_test 1.7 { + SELECT rowid FROM ft('two'); +} {2} + +do_execsql_test 1.8 { + UPDATE ft_data SET block = myhex('04000000 00000000') + WHERE id = (SELECT max(id) FROM ft_data); +} +do_execsql_test 1.9 { + DELETE FROM ft WHERE rowid=8 +} {} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1); + INSERT INTO ft VALUES('one one one'); + INSERT INTO ft VALUES('two two two'); + INSERT INTO ft VALUES('three three three'); + INSERT INTO ft VALUES('four four four'); + INSERT INTO ft VALUES('five five five'); + INSERT INTO ft VALUES('six six six'); + INSERT INTO ft VALUES('seven seven seven'); + INSERT INTO ft VALUES('eight eight eight'); + INSERT INTO ft VALUES('nine nine nine'); +} + +do_execsql_test 2.1 { + INSERT INTO ft(ft) VALUES('optimize'); +} +do_execsql_test 2.2 { + SELECT count(*) FROM ft_data +} {3} +do_execsql_test 2.3 { + DELETE FROM ft WHERE rowid=5 +} +do_execsql_test 2.4 { + SELECT count(*) FROM ft_data +} {4} + +# Check that an 'optimize' works (rewrites the index) if there is a single +# segment with one or more tombstone hash pages. +do_execsql_test 2.5 { + INSERT INTO ft(ft) VALUES('optimize'); +} +do_execsql_test 2.6 { + SELECT count(*) FROM ft_data +} {3} + +# Check that an 'optimize' is a no-op if there is a single segment +# and no tombstone hash pages. +do_execsql_test 2.7 { + INSERT INTO ft(ft) VALUES('optimize'); + SELECT rowid FROM ft_data; +} [db eval {SELECT rowid FROM ft_data}] + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1); + INSERT INTO ft(ft, rank) VALUES('pgsz', 64); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) + INSERT INTO ft(rowid, x) SELECT i, i||' '||i||' '||i||' '||i FROM s; + INSERT INTO ft(ft) VALUES('optimize'); +} + +do_execsql_test 3.1 { + SELECT count(*) FROM ft_data +} {200} + +do_execsql_test 3.2 { + DELETE FROM ft WHERE (rowid % 50)==0; + SELECT count(*) FROM ft_data; +} {203} + +do_execsql_test 3.3 { + INSERT INTO ft(ft, rank) VALUES('merge', 500); + SELECT rowid FROM ft_data; +} [db eval {SELECT rowid FROM ft_data}] + +do_execsql_test 3.4 { + INSERT INTO ft(ft, rank) VALUES('merge', -1000); + SELECT count(*) FROM ft_data; +} {197} + +do_execsql_test 3.5 { + DELETE FROM ft WHERE (rowid % 50)==1; + SELECT count(*) FROM ft_data; +} {200} + +do_execsql_test 3.6 { + SELECT level, segment, npgtombstone FROM fts5_structure( + (SELECT block FROM ft_data WHERE id=10) + ) +} {1 0 3} + +do_test 3.6 { + while 1 { + set nChange [db total_changes] + execsql { INSERT INTO ft(ft, rank) VALUES('merge', -5) } + if {([db total_changes] - $nChange)<2} break + } +} {} + +do_execsql_test 3.7 { + SELECT level, segment, npgtombstone FROM fts5_structure( + (SELECT block FROM ft_data WHERE id=10) + ) +} {2 0 0} + + +finish_test + diff --git a/ext/fts5/test/fts5contentless4.test b/ext/fts5/test/fts5contentless4.test new file mode 100644 index 000000000..1c2666dcf --- /dev/null +++ b/ext/fts5/test/fts5contentless4.test @@ -0,0 +1,248 @@ +# 2023 July 21 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file contains tests for the content= and content_rowid= options. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5contentless4 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +proc document {n} { + set vocab [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] + set ret [list] + for {set ii 0} {$ii < $n} {incr ii} { + lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]] + } + set ret +} +db func document document + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); + INSERT INTO ft(ft, rank) VALUES('pgsz', 240); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) + INSERT INTO ft SELECT document(12) FROM s; +} + +do_execsql_test 1.1 { + INSERT INTO ft(ft) VALUES('optimize'); +} + +do_execsql_test 1.2 { + SELECT level, segment, nentry, nentrytombstone FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} {0 0 1000 0} + +do_execsql_test 1.3 { + DELETE FROM ft WHERE rowid < 50 +} + +do_execsql_test 1.4 { + SELECT level, segment, nentry, nentrytombstone FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} {0 0 1000 49} + +do_execsql_test 1.5 { + DELETE FROM ft WHERE rowid < 1000 +} + +do_execsql_test 1.6 { + SELECT level, segment, nentry, nentrytombstone FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} {1 0 1 0} + +#-------------------------------------------------------------------------- +reset_db +db func document document + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); +} + +do_test 2.1 { + for {set ii 0} {$ii < 5000} {incr ii} { + execsql { INSERT INTO ft VALUES( document(12) ) } + } +} {} + +do_execsql_test 2.2 { + SELECT sum(nentry) - sum(nentrytombstone) FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} {5000} + +for {set ii 5000} {$ii >= 0} {incr ii -100} { + do_execsql_test 2.3.$ii { + DELETE FROM ft WHERE rowid > $ii + } + do_execsql_test 2.3.$ii.2 { + SELECT + CAST((total(nentry) - total(nentrytombstone)) AS integer) + FROM + fts5_structure( (SELECT block FROM ft_data WHERE id=10) ) + } $ii +} + +execsql_pp { + SELECT * FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} + +do_test 2.4 { + for {set ii 0} {$ii < 5000} {incr ii} { + execsql { INSERT INTO ft VALUES( document(12) ) } + } +} {} + +for {set ii 1} {$ii <= 5000} {incr ii 10} { + do_execsql_test 2.3.$ii { + DELETE FROM ft WHERE rowid = $ii; + INSERT INTO ft VALUES( document(12) ); + INSERT INTO ft(ft, rank) VALUES('merge', -10); + } + + do_execsql_test 2.3.$ii.2 { + SELECT + CAST((total(nentry) - total(nentrytombstone)) AS integer) + FROM + fts5_structure( (SELECT block FROM ft_data WHERE id=10) ) + } 5000 +} + +#------------------------------------------------------------------------- +reset_db +db func document document +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO ft SELECT document(12) FROM s; +} + +do_catchsql_test 3.1 { + INSERT INTO ft(ft, rank) VALUES('deletemerge', 'text'); +} {1 {SQL logic error}} +do_catchsql_test 3.2 { + INSERT INTO ft(ft, rank) VALUES('deletemerge', 50); +} {0 {}} +do_execsql_test 3.3 { + SELECT * FROM ft_config WHERE k='deletemerge' +} {deletemerge 50} +do_catchsql_test 3.4 { + INSERT INTO ft(ft, rank) VALUES('deletemerge', 101); +} {0 {}} +do_execsql_test 3.5 { + SELECT * FROM ft_config WHERE k='deletemerge' +} {deletemerge 101} + +do_execsql_test 3.6 { + DELETE FROM ft WHERE rowid<95 +} + +do_execsql_test 3.7 { + SELECT nentrytombstone, nentry FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} {94 100} + +do_execsql_test 3.8 { + DELETE FROM ft WHERE rowid=95 +} + +do_execsql_test 3.9 { + SELECT nentrytombstone, nentry FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} {95 100} + +do_execsql_test 3.10 { + DELETE FROM ft; + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO ft SELECT document(12) FROM s; + INSERT INTO ft(ft, rank) VALUES('deletemerge', 50); +} + +do_execsql_test 3.11 { + DELETE FROM ft WHERE rowid<95 +} + +do_execsql_test 3.12 { + SELECT nentrytombstone, nentry FROM fts5_structure(( + SELECT block FROM ft_data WHERE id=10 + )) +} {0 6} + +#------------------------------------------------------------------------- +reset_db +db func document document +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x, content='', contentless_delete=1); + INSERT INTO x1(x1, rank) VALUES('usermerge', 16); + INSERT INTO x1(x1, rank) VALUES('deletemerge', 40); + INSERT INTO x1 VALUES('one'); + INSERT INTO x1 VALUES('two'); + INSERT INTO x1 VALUES('three'); + INSERT INTO x1 VALUES('four'); + INSERT INTO x1 VALUES('five'); + INSERT INTO x1 VALUES('six'); + INSERT INTO x1 VALUES('seven'); + INSERT INTO x1 VALUES('eight'); + INSERT INTO x1 VALUES('nine'); + INSERT INTO x1 VALUES('ten'); +} + +do_execsql_test 4.1 { + SELECT level, segment FROM fts5_structure(( + SELECT block FROM x1_data WHERE id=10 + )) +} { + 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 +} + +for {set ii 1} {$ii < 4} {incr ii} { + do_execsql_test 4.2.$ii { + DELETE FROM x1 WHERE rowid = $ii; + INSERT INTO x1(x1, rank) VALUES('merge', 5); + SELECT level, segment FROM fts5_structure(( + SELECT block FROM x1_data WHERE id=10 + )) + } { + 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 + } +} + +do_execsql_test 4.3 { + DELETE FROM x1 WHERE rowid = $ii; + INSERT INTO x1(x1, rank) VALUES('merge', 5); + SELECT level, segment, nentry FROM fts5_structure(( + SELECT block FROM x1_data WHERE id=10 + )) +} { + 1 0 6 +} + +finish_test + diff --git a/ext/fts5/test/fts5contentless5.test b/ext/fts5/test/fts5contentless5.test new file mode 100644 index 000000000..1541b0c68 --- /dev/null +++ b/ext/fts5/test/fts5contentless5.test @@ -0,0 +1,59 @@ +# 2023 August 7 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file contains tests for the content= and content_rowid= options. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5contentless5 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, b, c, content='', contentless_delete=1); + INSERT INTO t1 VALUES('A', 'B', 'C'); + INSERT INTO t1 VALUES('D', 'E', 'F'); + INSERT INTO t1 VALUES('G', 'H', 'I'); +} + +do_execsql_test 1.01 { + CREATE TABLE t2(x, y); + INSERT INTO t2 VALUES('x', 'y'); +} + +# explain_i "UPDATE t1 SET a='a' WHERE t1.rowid=1" +breakpoint +explain_i "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1 AND b IS NULL" + +#breakpoint +#explain_i "UPDATE t1 SET a='a' WHERE b IS NULL AND rowid=?" + +foreach {tn up err} { + 1 "UPDATE t1 SET a='a', b='b', c='c' WHERE rowid=1" 0 + 2 "UPDATE t1 SET a='a', b='b' WHERE rowid=1" 1 + 3 "UPDATE t1 SET b='b', c='c' WHERE rowid=1" 1 + 4 "UPDATE t1 SET a='a', c='c' WHERE rowid=1" 1 + 5 "UPDATE t1 SET a='a', c='c' WHERE t1.rowid=1 AND b IS NULL" 1 + 6 "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1" 1 + 7 "UPDATE t1 SET a='a', b='b', c='c' FROM t2 WHERE t1.rowid=1" 0 +} { + + set res(0) {0 {}} + set res(1) {1 {cannot UPDATE a subset of columns on fts5 contentless-delete table: t1}} + do_catchsql_test 1.$tn $up $res($err) +} + +finish_test + diff --git a/ext/fts5/test/fts5corrupt5.test b/ext/fts5/test/fts5corrupt5.test index 16682b132..efbe2e13e 100644 --- a/ext/fts5/test/fts5corrupt5.test +++ b/ext/fts5/test/fts5corrupt5.test @@ -15,7 +15,7 @@ # source [file join [file dirname [info script]] fts5_common.tcl] -set testprefix fts5corrupt3 +set testprefix fts5corrupt5 # If SQLITE_ENABLE_FTS5 is defined, omit this file. ifcapable !fts5 { @@ -793,6 +793,94 @@ do_catchsql_test 4.5 { REPLACE INTO t1(rowid,a,b,rowid) VALUES(200,1,2,3); } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db +do_test 5.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 28672 pagesize 4096 filename crash-0c6d3451d11597.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 07 00 00 00 04 ................ +| 96: 00 00 00 00 0d 00 00 00 07 0d d2 00 0f c4 0f 6d ...............m +| 112: 0f 02 0e ab 0e 4e 0d f6 0d d2 00 00 00 00 00 00 .....N.......... +| 3536: 00 00 22 07 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet +| 3552: 32 74 32 07 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE +| 3568: 20 74 32 28 78 29 56 06 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta +| 3584: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c +| 3600: 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB +| 3616: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k +| 3632: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) +| 3648: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 05 WITHOUT ROWID[. +| 3664: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d +| 3680: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize +| 3696: 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't +| 3712: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN +| 3728: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE +| 3744: 59 2c 20 73 7a 20 42 4c 4f 42 29 55 04 06 17 21 Y, sz BLOB)U...! +| 3760: 21 01 77 74 61 62 6c 65 74 31 5f 63 6f 6e 74 65 !.wtablet1_conte +| 3776: 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 04 43 52 45 ntt1_content.CRE +| 3792: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 6f ATE TABLE 't1_co +| 3808: 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 45 ntent'(id INTEGE +| 3824: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 63 R PRIMARY KEY, c +| 3840: 30 29 69 03 07 17 19 19 01 81 2d 74 61 62 6c 65 0)i.......-table +| 3856: 74 31 5f 69 64 78 74 31 5f 69 64 78 03 43 52 45 t1_idxt1_idx.CRE +| 3872: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 69 64 ATE TABLE 't1_id +| 3888: 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 x'(segid, term, +| 3904: 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 pgno, PRIMARY KE +| 3920: 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 29 20 Y(segid, term)) +| 3936: 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 02 07 WITHOUT ROWIDU.. +| 3952: 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 64 61 ......tablet1_da +| 3968: 74 61 74 31 5f 64 61 74 61 02 43 52 45 41 54 45 tat1_data.CREATE +| 3984: 20 54 41 42 4c 45 20 27 74 31 5f 64 61 74 61 27 TABLE 't1_data' +| 4000: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM +| 4016: 41 52 b9 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 AR. KEY, block B +| 4032: 4c 4f 42 29 3a 01 06 17 11 11 08 63 74 61 62 6c LOB):......ctabl +| 4048: 65 74 31 74 31 43 52 45 41 54 45 20 56 49 52 54 et1t1CREATE VIRT +| 4064: 55 41 4c 20 54 41 42 4c 45 20 74 31 20 55 53 49 UAL TABLE t1 USI +| 4080: 4e 47 20 66 74 73 35 28 63 6f 6e 74 65 6e 74 29 NG fts5(content) +| page 2 offset 4096 +| 0: 0d 00 00 00 03 0f bd 00 0f e8 0f ef 0f bd 00 00 ................ +| 16: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 24 84 80 .............$.. +| 4032: 80 80 80 01 03 00 4e 00 00 00 1e 06 30 61 62 61 ......N.....0aba +| 4048: 63 6b 01 02 02 04 02 66 74 02 02 02 04 04 6e 64 ck.....ft.....nd +| 4064: 6f 6e 03 02 02 04 0a 07 05 01 03 00 10 03 03 0f on.............. +| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 11 ...$............ +| page 3 offset 8192 +| 0: 0a 00 00 00 01 0f 00 00 00 00 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 03 0f e0 00 0f f6 0f ec 0f e0 00 00 ................ +| 4064: 0a 03 03 00 1b 61 62 61 6e 64 6f 6e 08 02 03 00 .....abandon.... +| 4080: 17 61 62 61 66 74 08 01 03 00 17 61 62 61 63 6b .abaft.....aback +| page 5 offset 16384 +| 0: 0d 00 00 00 03 0f ee 00 0f fa 0f f4 0f ee 00 00 ................ +| 16: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 03 ................ +| 4080: 03 00 0e 01 04 02 03 00 0e 01 04 01 03 00 0e 01 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| page 7 offset 24576 +| 0: 0d 00 00 10 03 0f d6 00 0f f4 10 e1 0f d6 00 00 ................ +| 16: 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4048: 00 00 00 00 00 00 09 03 02 1b 72 65 62 75 69 6c ..........rebuil +| 4064: 64 11 02 02 2b 69 6e 74 65 67 72 69 74 79 2d 63 d...+integrity-c +| 4080: 68 65 63 6b 0a 01 02 1d 6f 70 74 69 6d 00 00 00 heck....optim... +| end crash-0c6d3451d11597.db +}]} {} + +do_execsql_test 5.1 { + INSERT INTO t1(t1,rank) VALUES('secure-delete',1); +} +do_catchsql_test 5.4 { + UPDATE t1 SET content=randomblob(500); +} {1 {database disk image is malformed}} + + sqlite3_fts5_may_be_corrupt 0 finish_test diff --git a/ext/fts5/test/fts5corrupt7.test b/ext/fts5/test/fts5corrupt7.test index 75995a7c0..ae7f9da7d 100644 --- a/ext/fts5/test/fts5corrupt7.test +++ b/ext/fts5/test/fts5corrupt7.test @@ -96,4 +96,33 @@ set r 137438953475 db close } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + BEGIN; + INSERT INTO t1 VALUES('abc'); + INSERT INTO t1 VALUES('b d d d'); + COMMIT; + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); +} + +execsql_pp { + SELECT id, quote(block) FROM t1_data +} + +do_execsql_test 2.1 { + SELECT quote(block) FROM t1_data WHERE id > 10; +} {X'0000001A04306162630102020101620202020101640206030303040806'} + +do_execsql_test 2.2 { + UPDATE t1_data SET + block=X'0000001A04306162630102025501620202020101640206030303040806' + WHERE id>10 +} + +do_catchsql_test 2.3 { + DELETE FROM t1 WHERE rowid = 1 +} {1 {database disk image is malformed}} + finish_test diff --git a/ext/fts5/test/fts5eb.test b/ext/fts5/test/fts5eb.test index ce40e471a..0c775791f 100644 --- a/ext/fts5/test/fts5eb.test +++ b/ext/fts5/test/fts5eb.test @@ -95,6 +95,9 @@ do_execsql_test 3.3 { SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"/" OR "just"' ORDER BY rank; } {1 -1e-06} +do_execsql_test 3.4 " + SELECT fts5_expr_tcl('e AND \" \"'); +" {{AND [nearset -- {e}] [{}]}} finish_test diff --git a/ext/fts5/test/fts5faultF.test b/ext/fts5/test/fts5faultF.test new file mode 100644 index 000000000..96cc2b083 --- /dev/null +++ b/ext/fts5/test/fts5faultF.test @@ -0,0 +1,111 @@ +# 2023 July 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 is focused on OOM errors. Particularly those that may occur +# when using contentless_delete=1 databases. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +source $testdir/malloc_common.tcl +set testprefix fts5faultF + +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +faultsim_save_and_close +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen +} -body { + execsql { + CREATE VIRTUAL TABLE t1 USING fts5(x, y, content=, contentless_delete=1) + } +} -test { + faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}} +} + +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1); + BEGIN; + INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d'); + INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d'); + INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d'); + INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d'); + COMMIT; + DELETE FROM t1 WHERE rowid IN (2, 4); +} + +do_faultsim_test 2 -prep { + sqlite3 db test.db + execsql { SELECT rowid FROM t1 } +} -body { + execsql { + SELECT rowid FROM t1('b c'); + } +} -test { + faultsim_test_result {0 {1 3}} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1); + BEGIN; + INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d'); + INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d'); + INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d'); + INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d'); + COMMIT; +} + +faultsim_save_and_close +do_faultsim_test 3 -prep { + faultsim_restore_and_reopen + execsql { SELECT rowid FROM t1 } +} -body { + execsql { + INSERT INTO t1(rowid, doc) VALUES(5, 'a b c d'); + } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1); + INSERT INTO t1(t1, rank) VALUES('pgsz', 64); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) + INSERT INTO t1(rowid, doc) SELECT i, 'a b c d' FROM s; +} + +do_execsql_test 4.1 { DELETE FROM t1 WHERE rowid <= 25 } + +faultsim_save_and_close +do_faultsim_test 4 -faults oom-t* -prep { + faultsim_restore_and_reopen + execsql { SELECT rowid FROM t1 } +} -body { + execsql { + DELETE FROM t1 WHERE rowid < 100 + } +} -test { + faultsim_test_result {0 {}} +} + + +finish_test + diff --git a/ext/fts5/test/fts5secure6.test b/ext/fts5/test/fts5secure6.test index e561a43f7..5ab17c4f3 100644 --- a/ext/fts5/test/fts5secure6.test +++ b/ext/fts5/test/fts5secure6.test @@ -50,6 +50,25 @@ do_test 1.3 { expr $phc(1)*5 < $phc(2) } {1} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) +} + +do_execsql_test 2.1 { + BEGIN; + INSERT INTO t1(rowid, x) VALUES(-100000, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(-99999, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(9223372036854775800, 'abc def ghi'); + COMMIT; +} + +do_execsql_test 2.2 { + SELECT rowid FROM t1('def') +} {-100000 -99999 9223372036854775800} finish_test diff --git a/ext/fts5/test/fts5synonym2.test b/ext/fts5/test/fts5synonym2.test index 8bbfb0756..2c2705e34 100644 --- a/ext/fts5/test/fts5synonym2.test +++ b/ext/fts5/test/fts5synonym2.test @@ -122,6 +122,9 @@ foreach {tn expr} { 4.1 "NEAR(one two, 2)" 4.2 "NEAR(one two three, 2)" 4.3 "NEAR(eight nine, 1) OR NEAR(six seven, 1)" + + 5.1 "one + two" + 5.2 "1 + two" } { if {[fts5_expr_ok $expr ss]==0} { do_test 1.$tok.$tn.OMITTED { list } [list] diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile new file mode 100644 index 000000000..1be4c0443 --- /dev/null +++ b/ext/jni/GNUmakefile @@ -0,0 +1,346 @@ +# Quick-and-dirty makefile to bootstrap the sqlite3-jni project. This +# build assumes a Linux-like system. +default: all + +JAVA_HOME ?= $(HOME)/jdk/current +# e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64 +JDK_HOME ?= $(JAVA_HOME) +# ^^^ JDK_HOME is not as widely used as JAVA_HOME +bin.javac := $(JDK_HOME)/bin/javac +bin.java := $(JDK_HOME)/bin/java +bin.jar := $(JDK_HOME)/bin/jar +ifeq (,$(wildcard $(JDK_HOME))) +$(error set JDK_HOME to the top-most dir of your JDK installation.) +endif +MAKEFILE := $(lastword $(MAKEFILE_LIST)) +$(MAKEFILE): + +package.jar := sqlite3-jni.jar + +dir.top := ../.. +dir.tool := ../../tool +dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE))) + +dir.src := $(dir.jni)/src +dir.src.c := $(dir.src)/c +dir.bld := $(dir.jni)/bld +dir.bld.c := $(dir.bld) +dir.src.jni := $(dir.src)/org/sqlite/jni +dir.src.jni.tester := $(dir.src.jni)/tester +$(dir.bld.c): + mkdir -p $@ + +classpath := $(dir.src) +CLEAN_FILES := $(package.jar) +DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~ + +sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h +.NOTPARALLEL: $(sqlite3-jni.h) +SQLite3Jni.java := src/org/sqlite/jni/SQLite3Jni.java +SQLTester.java := src/org/sqlite/jni/tester/SQLTester.java +SQLite3Jni.class := $(SQLite3Jni.java:.java=.class) +SQLTester.class := $(SQLTester.java:.java=.class) + +######################################################################## +# The future of FTS5 customization in this API is as yet unclear. +# It would be a real doozy to bind to JNI. +enable.fts5 ?= 1 +# If enable.tester is 0, the org/sqlite/jni/tester/* bits are elided. +enable.tester ?= 1 + +# bin.version-info = binary to output various sqlite3 version info +# building the distribution zip file. +bin.version-info := $(dir.top)/version-info +.NOTPARALLEL: $(bin.version-info) +$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile + $(MAKE) -C $(dir.top) version-info + +# Be explicit about which Java files to compile so that we can work on +# in-progress files without requiring them to be in a compilable statae. +JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\ + BusyHandler.java \ + Collation.java \ + CollationNeeded.java \ + CommitHook.java \ + NativePointerHolder.java \ + OutputPointer.java \ + ProgressHandler.java \ + ResultCode.java \ + RollbackHook.java \ + SQLFunction.java \ + sqlite3_context.java \ + sqlite3.java \ + SQLite3Jni.java \ + sqlite3_stmt.java \ + sqlite3_value.java \ + Tester1.java \ + Tracer.java \ + UpdateHook.java \ + ValueHolder.java \ +) +ifeq (1,$(enable.fts5)) + JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\ + fts5_api.java \ + fts5_extension_function.java \ + fts5_tokenizer.java \ + Fts5.java \ + Fts5Context.java \ + Fts5ExtensionApi.java \ + Fts5Function.java \ + Fts5PhraseIter.java \ + Fts5Tokenizer.java \ + TesterFts5.java \ + ) +endif +JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java + +CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) +CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class) + +JAVA_FILES += $(JAVA_FILES.main) +ifeq (1,$(enable.tester)) + JAVA_FILES += $(JAVA_FILES.tester) +endif + +CLASS_FILES := +define DOTCLASS_DEPS +$(1).class: $(1).java $(MAKEFILE) +all: $(1).class +CLASS_FILES += $(1).class +endef +$(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B)))) +$(CLASS_FILES.tester): $(CLASS_FILES.main) +javac.flags ?= -Xlint:unchecked -Xlint:deprecation +java.flags ?= +jnicheck ?= 1 +ifeq (1,$(jnicheck)) + java.flags += -Xcheck:jni +endif +$(SQLite3Jni.class): $(JAVA_FILES) + $(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES) +all: $(SQLite3Jni.class) +#.PHONY: classfiles + +######################################################################## +# Set up sqlite3.c and sqlite3.h... +# +# To build with SEE (https://sqlite.org/see), either put sqlite3-see.c +# in the top of this build tree or pass +# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only +# encryption modules with no 3rd-party dependencies will currently +# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not +# coincidentally, those 3 modules are included in the sqlite3-see.c +# bundle. +# +# A custom sqlite3.c must not have any spaces in its name. +# $(sqlite3.canonical.c) must point to the sqlite3.c in +# the sqlite3 canonical source tree, as that source file +# is required for certain utility and test code. +sqlite3.canonical.c := $(firstword $(wildcard $(dir.src.c)/sqlite3.c) $(dir.top)/sqlite3.c) +sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h) $(dir.top)/sqlite3.h) +sqlite3.c := $(sqlite3.canonical.c) +sqlite3.h := $(sqlite3.canonical.h) +#ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null)) +# SQLITE_C_IS_SEE := 0 +#else +# SQLITE_C_IS_SEE := 1 +# $(info This is an SEE build.) +#endif + +.NOTPARALLEL: $(sqlite3.h) +$(sqlite3.h): + $(MAKE) -C $(dir.top) sqlite3.c +$(sqlite3.c): $(sqlite3.h) + +SQLITE_OPT = \ + -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_BYTECODE_VTAB \ + -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_OMIT_SHARED_CACHE \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_TEMP_STORE=2 \ + -DSQLITE_USE_URI=1 \ + -DSQLITE_C=$(sqlite3.c) +# -DSQLITE_DEBUG +# -DSQLITE_DEBUG is just to work around a -Wall warning +# for a var which gets set in all builds but only read +# via assert(). + +SQLITE_OPT += -g -DDEBUG -UNDEBUG + +ifeq (1,$(enable.fts5)) + SQLITE_OPT += -DSQLITE_ENABLE_FTS5 +endif + +sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c +sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o +sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h +sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so +# All javac-generated .h files must be listed in $(sqlite3-jni.h.in): +sqlite3-jni.h.in := +define ADD_JNI_H +sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h +$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).java +endef +$(eval $(call ADD_JNI_H,SQLite3Jni)) +ifeq (1,$(enable.fts5)) + $(eval $(call ADD_JNI_H,Fts5ExtensionApi)) + $(eval $(call ADD_JNI_H,fts5_api)) + $(eval $(call ADD_JNI_H,fts5_tokenizer)) +endif +ifeq (1,$(enable.tester)) + sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h + $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java +endif +#sqlite3-jni.dll.cfiles := $(dir.src.c) +sqlite3-jni.dll.cflags = \ + -fPIC \ + -I. \ + -I$(dir $(sqlite3.h)) \ + -I$(dir.src.c) \ + -I$(JDK_HOME)/include \ + $(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \ + -Wall +# Using (-Wall -Wextra) triggers an untennable number of +# gcc warnings from sqlite3.c for mundane things like +# unused parameters. +# +# The gross $(patsubst...) above is to include the platform-specific +# subdir which lives under $(JDK_HOME)/include and is a required +# include path for client-level code. +######################################################################## +ifeq (1,$(enable.tester)) + sqlite3-jni.dll.cflags += -DS3JNI_ENABLE_SQLTester +endif +$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) + cat $(sqlite3-jni.h.in) > $@ +$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) +$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) + $(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \ + $(sqlite3-jni.c) -shared -o $@ +all: $(sqlite3-jni.dll) + +.PHONY: test +test.flags ?= -v +test: $(SQLite3Jni.class) $(sqlite3-jni.dll) + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) + +tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) +tester.flags ?= # --verbose +.PHONY: tester tester-local tester-ext +ifeq (1,$(enable.tester)) +tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll) + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts) +tester: tester-local +else +tester: + @echo "SQLTester support is disabled. Build with enable.tester=1 to enable it." +endif + +tester.extdir.default := src/tests/ext +tester.extdir ?= $(tester.extdir.default) +tester.extern-scripts := $(wildcard $(tester.extdir)/*.test) +ifneq (,$(tester.extern-scripts)) +tester-ext: + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts) +else +tester-ext: + @echo "******************************************************"; \ + echo "*** Include the out-of-tree test suite in the 'tester'"; \ + echo "*** target by either symlinking its directory to"; \ + echo "*** $(tester.extdir.default) or passing it to make"; \ + echo "*** as tester.extdir=/path/to/that/dir."; \ + echo "******************************************************"; +endif + +tester-ext: tester-local +tester: tester-ext +tests: test tester +package.jar.in := $(abspath $(dir.src)/jar.in) +CLEAN_FILES += $(package.jar.in) +$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) + cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@ + @ls -la $@ + @echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag." + @echo "e.g. java -jar $@ -Djava.library.path=bld" + +$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in) + rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ + cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in) + +jar: $(package.jar) + +CLEAN_FILES += $(dir.bld.c)/* \ + $(dir.src.jni)/*.class \ + $(dir.src.jni.tester)/*.class \ + $(sqlite3-jni.dll) \ + hs_err_pid*.log + +.PHONY: clean distclean +clean: + -rm -f $(CLEAN_FILES) +distclean: clean + -rm -f $(DISTCLEAN_FILES) + -rm -fr $(dir.bld.c) + +######################################################################## +# disttribution bundle rules... + +ifeq (,$(filter snapshot,$(MAKECMDGOALS))) +dist-name-prefix := sqlite-jni +else +dist-name-prefix := sqlite-jni-snapshot-$(shell /usr/bin/date +%Y%m%d) +endif +dist-name := $(dist-name-prefix)-TEMP + + +dist-dir.top := $(dist-name) +dist-dir.src := $(dist-dir.top)/src +dist.top.extras := \ + README.md + +.PHONY: dist snapshot + +dist: \ + $(bin.version-info) $(sqlite3.canonical.c) \ + $(package.jar) $(MAKEFILE) + @echo "Making end-user deliverables..." + @rm -fr $(dist-dir.top) + @mkdir -p $(dist-dir.src) + @cp -p $(dist.top.extras) $(dist-dir.top)/. + @cp -p jar-dist.make $(dist-dir.top)/Makefile + @cp -p $(dir.src.c)/*.[ch] $(dist-dir.src)/. + @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.src)/. + @set -e; \ + vnum=$$($(bin.version-info) --download-version); \ + vjar=$$($(bin.version-info) --version); \ + vdir=$(dist-name-prefix)-$$vnum; \ + arczip=$$vdir.zip; \ + cp -p $(package.jar) $(dist-dir.top)/sqlite3-jni-$${vjar}.jar; \ + echo "Making $$arczip ..."; \ + rm -fr $$arczip $$vdir; \ + mv $(dist-dir.top) $$vdir; \ + zip -qr $$arczip $$vdir; \ + rm -fr $$vdir; \ + ls -la $$arczip; \ + set +e; \ + unzip -lv $$arczip || echo "Missing unzip app? Not fatal." + +snapshot: dist + +.PHONY: dist-clean +clean: dist-clean +dist-clean: + rm -fr $(dist-name) $(wildcard sqlite-jni-*.zip) diff --git a/ext/jni/README.md b/ext/jni/README.md new file mode 100644 index 000000000..cb51a21cd --- /dev/null +++ b/ext/jni/README.md @@ -0,0 +1,234 @@ +SQLite3 via JNI +======================================================================== + +This directory houses a Java Native Interface (JNI) binding for the +sqlite3 API. If you are reading this from the distribution ZIP file, +links to resources in the canonical source tree will note work. The +canonical copy of this file can be browsed at: + + + +Technical support is available in the forum: + + + + +> **FOREWARNING:** this subproject is very much in development and + subject to any number of changes. Please do not rely on any + information about its API until this disclaimer is removed. + +Project goals/requirements: + +- A [1-to-1(-ish) mapping of the C API](#1to1ish) to Java via JNI, + insofar as cross-language semantics allow for. A closely-related + goal is that [the C documentation](https://sqlite.org/c3ref/intro.html) + should be usable as-is, insofar as possible, for the JNI binding. + +- Support Java as far back as version 8 (2014). + +- Environment-independent. Should work everywhere both Java + and SQLite3 do. + +- No 3rd-party dependencies beyond the JDK. That includes no + build-level dependencies for specific IDEs and toolchains. We + welcome the addition of build files for arbitrary environments + insofar as they neither interfere with each other nor become + a maintenance burden for the sqlite developers. + +Non-goals: + +- Creation of high-level OO wrapper APIs. Clients are free to create + them off of the C-style API. + + +Significant TODOs +======================================================================== + +- The initial beta release with version 3.43 has severe threading + limitations. Namely, two threads cannot call into the JNI-bound API + at once. This limitation will be remove in a subsequent release. + + +Building +======================================================================== + +The canonical builds assumes a Linux-like environment and requires: + +- GNU Make +- A JDK supporting Java 8 or higher +- A modern C compiler. gcc and clang should both work. + +Put simply: + +``` +$ export JAVA_HOME=/path/to/jdk/root +$ make +$ make test +$ make clean +``` + + +One-to-One(-ish) Mapping to C +======================================================================== + +This JNI binding aims to provide as close to a 1-to-1 experience with +the C API as cross-language semantics allow. Exceptions are +necessarily made where cross-language semantics do not allow a 1-to-1, +and judiciously made where a 1-to-1 mapping would be unduly cumbersome +to use in Java. + +Golden Rule: _Never_ Throw from Callbacks +------------------------------------------------------------------------ + +JNI bindings which accept client-defined functions _must never throw +exceptions_ unless _very explicitly documented_ as being +throw-safe. Exceptions are generally reserved for higher-level +bindings which are constructed to specifically deal with them and +ensure that they do not leak C-level resources. Some of the JNI +bindings are provided as Java functions which expect this rule to +always hold. + +UTF-8(-ish) +------------------------------------------------------------------------ + +SQLite internally uses UTF-8 encoding, whereas Java natively uses +UTF-16. Java JNI has routines for converting to and from UTF-8, _but_ +Java uses what its docs call "[modified UTF-8][modutf8]." Care must be +taken when converting Java strings to UTF-8 to ensure that the proper +conversion is performed. In short, +`String.getBytes(StandardCharsets.UTF_8)` performs the proper +conversion in Java, and there is no JNI C API for that conversion +(JNI's `NewStringUTF()` returns MUTF-8). + +Known consequences and limitations of this discrepancy include: + +- Names of databases, tables, and collations must not contain + characters which differ in MUTF-8 and UTF-8, or certain APIs will + mis-translate them on their way between languages. APIs which + transfer other client-side data to Java take extra care to + convert the data at the cost of performance. + +[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 + + +Unwieldy Constructs are Re-mapped +------------------------------------------------------------------------ + +Some constructs, when modelled 1-to-1 from C to Java, are unduly +clumsy to work with in Java because they try to shoehorn C's way of +doing certain things into Java's wildly different ways. The following +subsections cover those, starting with a verbose explanation and +demonstration of where such changes are "really necessary"... + +### Custom Collations + +A prime example of where interface changes for Java are necessary for +usability is [registration of a custom +collation](https://sqlite.org/c3ref/create_collation.html): + +``` +// C: +int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep, + void *pUserData, + int (*xCompare)(void*,int,void const *,int,void const *)); + +int sqlite3_create_collation_v2(sqlite3 * db, const char * name, int eTextRep, + void *pUserData, + int (*xCompare)(void*,int,void const *,int,void const *), + void (*xDestroy)(void*)); +``` + +The `pUserData` object is optional client-defined state for the +`xCompare()` and/or `xDestroy()` callback functions, both of which are +passed that object as their first argument. That data is passed around +"externally" in C because that's how C models the world. If we were to +bind that part as-is to Java, the result would be awkward to use (^Yes, +we tried this.): + +``` +// Java: +int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, + Object pUserData, xCompareType xCompare); + +int sqlite3_create_collation_v2(sqlite3 db, String name, int eTextRep, + Object pUserData, + xCompareType xCompare, xDestroyType xDestroy); +``` + +The awkwardness comes from (A) having two distinctly different objects +for callbacks and (B) having their internal state provided separately, +which is ill-fitting in Java. For the sake of usability, C APIs which +follow that pattern use a slightly different Java interface: + +``` +int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, + Collation collation); +``` + +Where the `Collation` class has an abstract `xCompare()` method and +no-op `xDestroy()` method which can be overridden if needed, leading to +a much more Java-esque usage: + +``` +int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){ + + // Required comparison function: + @Override public int xCompare(byte[] lhs, byte[] rhs){ ... } + + // Optional finalizer function: + @Override public void xDestroy(){ ... } + + // Optional local state: + private String localState1 = + "This is local state. There are many like it, but this one is mine."; + private MyStateType localState2 = new MyStateType(); + ... +}); +``` + +Noting that: + +- It is still possible to bind in call-scope-local state via closures, + if desired. + +- No capabilities of the C API are lost or unduly obscured via the + above API reshaping, so power users need not make any compromises. + +- In the specific example above, `sqlite3_create_collation_v2()` + becomes superfluous because the provided interface effectively + provides both the v1 and v2 interfaces, the difference being that + overriding the `xDestroy()` method effectively gives it v2 + semantics. + +### User-defined SQL Functions (a.k.a. UDFs) + +The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html) +family of APIs make heavy use of function pointers to provide +client-defined callbacks, necessitating interface changes in the JNI +binding. The Java API has only one core function-registration function: + +``` +int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, + int encoding, SQLFunction func); +``` + +> Design question: does the encoding argument serve any purpose in JS? + +`SQLFunction` is not used directly, but is instead instantiated via +one of its three subclasses: + +- `SQLFunction.Scalar` implements simple scalar functions using but a + single callback. +- `SQLFunction.Aggregate` implements aggregate functions using two + callbacks. +- `SQLFunction.Window` implements window functions using four + callbacks. + +Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for +`SQLFunction` for how it's used. + +Reminder: see the disclaimer at the top of this document regarding the +in-flux nature of this API. + +[jsrc]: /file/ +[www]: https://sqlite.org diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make new file mode 100644 index 000000000..23a26e4a8 --- /dev/null +++ b/ext/jni/jar-dist.make @@ -0,0 +1,55 @@ +#!/this/is/make +#^^^^ help emacs out +# +# This is a POSIX-make-compatible makefile for building the sqlite3 +# JNI library from "dist" zip file. It must be edited to set the +# proper top-level JDK directory and, depending on the platform, add a +# platform-specific -I directory. It should build as-is with any +# 2020s-era version of gcc or clang. It requires JDK version 8 or +# higher. + +default: all + +JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64 +CFLAGS = \ + -fPIC \ + -Isrc \ + -I$(JAVA_HOME)/include \ + -I$(JAVA_HOME)/include/linux \ + -I$(JAVA_HOME)/include/apple \ + -I$(JAVA_HOME)/include/bsd \ + -Wall + +SQLITE_OPT = \ + -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_BYTECODE_VTAB \ + -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_OMIT_SHARED_CACHE \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_TEMP_STORE=2 \ + -DSQLITE_USE_URI=1 \ + -DSQLITE_ENABLE_FTS5 + +sqlite3-jni.dll = libsqlite3-jni.so +$(sqlite3-jni.dll): + @echo "************************************************************************"; \ + echo "*** If this fails to build, be sure to edit this makefile ***"; \ + echo "*** to configure it for your system. ***"; \ + echo "************************************************************************" + $(CC) $(CFLAGS) $(SQLITE_OPT) \ + src/sqlite3-jni.c -shared -o $@ + @echo "Now try running it with: make test" + +test: $(sqlite3-jni.dll) + java -jar -Djava.library.path=. sqlite3-jni-*.jar + +clean: + -rm -f $(sqlite3-jni.dll) + +all: $(sqlite3-jni.dll) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c new file mode 100644 index 000000000..b28ea7114 --- /dev/null +++ b/ext/jni/src/c/sqlite3-jni.c @@ -0,0 +1,4420 @@ +/* +** 2023-07-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 the JNI bindings declared in +** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated). +*/ + +/** + If you found this comment by searching the code for + CallStaticObjectMethod then you're the victim of an OpenJDK bug: + + https://bugs.openjdk.org/browse/JDK-8130659 + + It's known to happen with OpenJDK v8 but not with v19. + + This code does not use JNI's CallStaticObjectMethod(). +*/ + +/* +** Define any SQLITE_... config defaults we want if they aren't +** overridden by the builder. Please keep these alphabetized. +*/ + +/**********************************************************************/ +/* SQLITE_D... */ +#ifndef SQLITE_DEFAULT_CACHE_SIZE +# define SQLITE_DEFAULT_CACHE_SIZE -16384 +#endif +#if !defined(SQLITE_DEFAULT_PAGE_SIZE) +# define SQLITE_DEFAULT_PAGE_SIZE 8192 +#endif +#ifndef SQLITE_DQS +# define SQLITE_DQS 0 +#endif + +/**********************************************************************/ +/* SQLITE_ENABLE_... */ +#ifndef SQLITE_ENABLE_BYTECODE_VTAB +# define SQLITE_ENABLE_BYTECODE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBPAGE_VTAB +# define SQLITE_ENABLE_DBPAGE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBSTAT_VTAB +# define SQLITE_ENABLE_DBSTAT_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS +# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 +#endif +#ifndef SQLITE_ENABLE_MATH_FUNCTIONS +# define SQLITE_ENABLE_MATH_FUNCTIONS 1 +#endif +#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC +# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 +#endif +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK +# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ +#endif +#ifndef SQLITE_ENABLE_RTREE +# define SQLITE_ENABLE_RTREE 1 +#endif +//#ifndef SQLITE_ENABLE_SESSION +//# define SQLITE_ENABLE_SESSION 1 +//#endif +#ifndef SQLITE_ENABLE_STMTVTAB +# define SQLITE_ENABLE_STMTVTAB 1 +#endif +//#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +//# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +//#endif + +/**********************************************************************/ +/* SQLITE_M... */ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff +#endif + +/**********************************************************************/ +/* SQLITE_O... */ +#ifndef SQLITE_OMIT_DEPRECATED +# define SQLITE_OMIT_DEPRECATED 1 +#endif +#ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE +# define SQLITE_OMIT_SHARED_CACHE 1 +#endif +#ifdef SQLITE_OMIT_UTF16 +/* UTF16 is required for java */ +# undef SQLITE_OMIT_UTF16 1 +#endif + +/**********************************************************************/ +/* SQLITE_T... */ +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 2 +#endif +#ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +#endif + +/**********************************************************************/ +/* SQLITE_USE_... */ +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 1 +#endif + + +/* +** Which sqlite3.c we're using needs to be configurable to enable +** building against a custom copy, e.g. the SEE variant. We have to +** include sqlite3.c, as opposed to sqlite3.h, in order to get access +** to SQLITE_MAX_... and friends. This increases the rebuild time +** considerably but we need this in order to keep the exported values +** of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C build. +*/ +#ifndef SQLITE_C +# define SQLITE_C sqlite3.c +#endif +#define INC__STRINGIFY_(f) #f +#define INC__STRINGIFY(f) INC__STRINGIFY_(f) +#include INC__STRINGIFY(SQLITE_C) +#undef INC__STRINGIFY_ +#undef INC__STRINGIFY +#undef SQLITE_C + +#include "sqlite3-jni.h" +#include /* only for testing/debugging */ +#include + +/* Only for debugging */ +#define MARKER(pfexp) \ + do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ + printf pfexp; \ + } while(0) + +/* Creates a verbose JNI function name. */ +#define JFuncName(Suffix) \ + Java_org_sqlite_jni_SQLite3Jni_sqlite3_ ## Suffix + +/* Prologue for JNI functions. */ +#define JDECL(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncName(Suffix) +/** + Shortcuts for the first 2 parameters to all JNI bindings. + + The type of the jSelf arg differs, but no docs seem to mention + this: for static methods it's of type jclass and for non-static + it's jobject. jobject actually works for all funcs, in the sense + that it compiles and runs so long as we don't use jSelf (which is + only rarely needed in this code), but to be pedantically correct we + need the proper type in the signature. + + Not even the official docs mention this discrepancy: + + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers +*/ +#define JENV_OSELF JNIEnv * const env, jobject jSelf +#define JENV_CSELF JNIEnv * const env, jclass jKlazz +/* Helpers to account for -Xcheck:jni warnings about not having + checked for exceptions. */ +#define IFTHREW if((*env)->ExceptionCheck(env)) +#define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) +#define EXCEPTION_CLEAR (*env)->ExceptionClear(env) +#define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) +#define EXCEPTION_WARN_CALLBACK_THREW(STR) \ + MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \ + (*env)->ExceptionDescribe(env) +#define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT +#define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR + +/** To be used for cases where we're _really_ not expecting an + exception, e.g. looking up well-defined Java class members. */ +#define EXCEPTION_IS_FATAL(MSG) IFTHREW {\ + EXCEPTION_REPORT; EXCEPTION_CLEAR; \ + (*env)->FatalError(env, MSG); \ + } + +/** Helpers for extracting pointers from jobjects, noting that the + corresponding Java interfaces have already done the type-checking. + */ +#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3) +#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_stmt) +#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_value) +#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_context) +/* Helpers for Java value reference management. */ +static inline jobject new_global_ref(JNIEnv * const env, jobject const v){ + return v ? (*env)->NewGlobalRef(env, v) : NULL; +} +static inline void delete_global_ref(JNIEnv * const env, jobject const v){ + if(v) (*env)->DeleteGlobalRef(env, v); +} +static inline void delete_local_ref(JNIEnv * const env, jobject const v){ + if(v) (*env)->DeleteLocalRef(env, v); +} +#define REF_G(VAR) new_global_ref(env, (VAR)) +#define REF_L(VAR) (*env)->NewLocalRef(env, VAR) +#define UNREF_G(VAR) delete_global_ref(env,(VAR)) +#define UNREF_L(VAR) delete_local_ref(env,(VAR)) + +/** + Constant string class names used as keys for S3JniGlobal_nph_cache(), +S3Jni + and + friends. +*/ +static const struct { + const char * const sqlite3; + const char * const sqlite3_stmt; + const char * const sqlite3_context; + const char * const sqlite3_value; + const char * const OutputPointer_Int32; + const char * const OutputPointer_Int64; + const char * const OutputPointer_String; + const char * const OutputPointer_ByteArray; + const char * const OutputPointer_sqlite3; + const char * const OutputPointer_sqlite3_stmt; +#ifdef SQLITE_ENABLE_FTS5 + const char * const Fts5Context; + const char * const Fts5ExtensionApi; + const char * const fts5_api; + const char * const fts5_tokenizer; + const char * const Fts5Tokenizer; +#endif +} S3JniClassNames = { + "org/sqlite/jni/sqlite3", + "org/sqlite/jni/sqlite3_stmt", + "org/sqlite/jni/sqlite3_context", + "org/sqlite/jni/sqlite3_value", + "org/sqlite/jni/OutputPointer$Int32", + "org/sqlite/jni/OutputPointer$Int64", + "org/sqlite/jni/OutputPointer$String", + "org/sqlite/jni/OutputPointer$ByteArray", + "org/sqlite/jni/OutputPointer$sqlite3", + "org/sqlite/jni/OutputPointer$sqlite3_stmt", +#ifdef SQLITE_ENABLE_FTS5 + "org/sqlite/jni/Fts5Context", + "org/sqlite/jni/Fts5ExtensionApi", + "org/sqlite/jni/fts5_api", + "org/sqlite/jni/fts5_tokenizer", + "org/sqlite/jni/Fts5Tokenizer" +#endif +}; + +/** Create a trivial JNI wrapper for (int CName(void)). */ +#define WRAP_INT_VOID(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF){ \ + return (jint)CName(); \ + } + +/** Create a trivial JNI wrapper for (int CName(int)). */ +#define WRAP_INT_INT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jint arg){ \ + return (jint)CName((int)arg); \ + } + +/** Create a trivial JNI wrapper for (const mutf8_string * + CName(void)). This is only valid for functions which are known to + return ASCII or text which is equivalent in UTF-8 and MUTF-8. */ +#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ + JDECL(jstring,JniNameSuffix)(JENV_CSELF){ \ + return (*env)->NewStringUTF( env, CName() ); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ +#define WRAP_INT_STMT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpStmt){ \ + jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \ + EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \ + return rc; \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ +#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint n){ \ + return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ + } +/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ +#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ + JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ + return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ +#define WRAP_INT_DB(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ + return (jint)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ +#define WRAP_INT64_DB(JniNameSuffix,CName) \ + JDECL(jlong,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ + return (jlong)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ +#define WRAP_INT_SVALUE(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpSValue){ \ + return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \ + } + +/* Helpers for jstring and jbyteArray. */ +#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) +#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) +#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) +#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) + +/* Marker for code which needs(?) to be made thread-safe. REASON is a + terse reminder about why that function requires a mutex. +*/ +#define FIXME_THREADING(REASON) + +enum { + /** + Size of the NativePointerHolder cache. Need enough space for + (only) the library's NativePointerHolder types, a fixed count + known at build-time. If we add more than this a fatal error will + be triggered with a reminder to increase this. This value needs + to be exactly the number of entries in the S3JniClassNames + object. The S3JniClassNames entries are the keys for this particular + cache. + */ + NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *) +}; + +/** + Cache entry for NativePointerHolder lookups. +*/ +typedef struct S3JniNphCache S3JniNphCache; +struct S3JniNphCache { + const char * zClassName /* "full/class/Name". Must be a static + string pointer from the S3JniClassNames + struct. */; + jclass klazz /* global ref to the concrete + NativePointerHolder subclass represented by + zClassName */; + jmethodID midCtor /* klazz's no-arg constructor. Used by + new_NativePointerHolder_object(). */; + jfieldID fidValue /* NativePointerHolder.nativePointer and + OutputPointer.X.value */; + jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only + by the sqlite3_context binding. */; +}; + +/** + Cache for per-JNIEnv data. + + Potential TODO: move the jclass entries to global space because, + per https://developer.android.com/training/articles/perf-jni: + + > once you have a valid jclass global reference you can use it from + any attached thread. + + Whereas we cache new refs for each thread. +*/ +typedef struct S3JniEnvCache S3JniEnvCache; +struct S3JniEnvCache { + JNIEnv *env /* env in which this cache entry was created */; + //! The various refs to global classes might be cacheable a single + // time globally. Information online seems inconsistent on that + // point. + struct { + jclass cObj /* global ref to java.lang.Object */; + jclass cLong /* global ref to java.lang.Long */; + jclass cString /* global ref to java.lang.String */; + jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; + jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + jmethodID stringGetBytes /* the String.getBytes(Charset) method */; + } g /* refs to global Java state */; +#ifdef SQLITE_ENABLE_FTS5 + jobject jFtsExt /* Global ref to Java singleton for the + Fts5ExtensionApi instance. */; + struct { + jclass klazz; + jfieldID fidA; + jfieldID fidB; + } jPhraseIter; +#endif + S3JniEnvCache * pPrev /* Previous entry in the linked list */; + S3JniEnvCache * pNext /* Next entry in the linked list */; + /** TODO?: S3JniNphCache *pNphHit; + + and always set it to the most recent cache search result. + + The intent would be to help fast-track cache lookups and would + speed up, e.g., the sqlite3_value-to-Java-array loop in a + multi-threaded app. + */ + S3JniNphCache nph[NphCache_SIZE]; +}; + +static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){ + UNREF_G(p->klazz); + memset(p, 0, sizeof(S3JniNphCache)); +} + +#define S3JNI_ENABLE_AUTOEXT 1 +#if S3JNI_ENABLE_AUTOEXT +/* + Whether auto extensions are feasible here is currently unknown due + to... + + 1) JNIEnv/threading issues. A db instance is mapped to a specific + JNIEnv object but auto extensions may be added from any thread. In + such contexts, which JNIEnv do we use for the JNI APIs? + + 2) a chicken/egg problem involving the Java/C mapping of the db: + when auto extensions are run, the db has not yet been connected to + Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave + properly because they have a different jobject and the API + guarantees the user that _that_ object is the one the API will bind + the native to. + + If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 + instead of the client passing in an instance, we could work around + (2). +*/ +typedef struct S3JniAutoExtension S3JniAutoExtension; +typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*); +struct S3JniAutoExtension { + jobject jObj; + jmethodID midFunc; + S3JniAutoExtension_xEntryPoint xEntryPoint; + S3JniAutoExtension *pNext /* next linked-list entry */; + S3JniAutoExtension *pPrev /* previous linked-list entry */; +}; +#endif + +/** State for various hook callbacks. */ +typedef struct S3JniHook S3JniHook; +struct S3JniHook{ + jobject jObj /* global ref to Java instance */; + jmethodID midCallback /* callback method. Signature depends on + jObj's type */; + jclass klazz /* global ref to jObj's class. Only needed + by hooks which have an xDestroy() method, + as lookup of that method is deferred + until the object requires cleanup. */; +}; + +/** + Per-(sqlite3*) state for various JNI bindings. This state is + allocated as needed, cleaned up in sqlite3_close(_v2)(), and + recycled when possible. It is freed during sqlite3_shutdown(). +*/ +typedef struct S3JniDb S3JniDb; +struct S3JniDb { + JNIEnv *env /* The associated JNIEnv handle */; + sqlite3 *pDb /* The associated db handle */; + jobject jDb /* A global ref of the object which was passed to + sqlite3_open(_v2)(). We need this in order to have + an object to pass to sqlite3_collation_needed()'s + callback, or else we have to dynamically create one + for that purpose, which would be fine except that + it would be a different instance (and maybe even a + different class) than the one the user may expect + to receive. */; + char * zMainDbName /* Holds any string allocated on behave of + SQLITE_DBCONFIG_MAINDBNAME. */; + S3JniHook busyHandler; + S3JniHook collation; + S3JniHook collationNeeded; + S3JniHook commitHook; + S3JniHook progress; + S3JniHook rollbackHook; + S3JniHook trace; + S3JniHook updateHook; + S3JniHook authHook; +#ifdef SQLITE_ENABLE_FTS5 + jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; +#endif + S3JniDb * pNext /* Next entry in the available/free list */; + S3JniDb * pPrev /* Previous entry in the available/free list */; +}; + +/** + Global state, e.g. caches and metrics. +*/ +static struct { + /** + According to: https://developer.ibm.com/articles/j-jni/ + + > A thread can get a JNIEnv by calling GetEnv() using the JNI + invocation interface through a JavaVM object. The JavaVM object + itself can be obtained by calling the JNI GetJavaVM() method + using a JNIEnv object and can be cached and shared across + threads. Caching a copy of the JavaVM object enables any thread + with access to the cached object to get access to its own + JNIEnv when necessary. + */ + JavaVM * jvm; + struct { + S3JniEnvCache * aHead /* Linked list of in-use instances */; + S3JniEnvCache * aFree /* Linked list of free instances */; + } envCache; + struct { + S3JniDb * aUsed /* Linked list of in-use instances */; + S3JniDb * aFree /* Linked list of free instances */; + } perDb; + struct { + unsigned nphCacheHits; + unsigned nphCacheMisses; + unsigned envCacheHits; + unsigned envCacheMisses; + unsigned nDestroy /* xDestroy() calls across all types */; + struct { + /* Number of calls for each type of UDF callback. */ + unsigned nFunc; + unsigned nStep; + unsigned nFinal; + unsigned nValue; + unsigned nInverse; + } udf; + } metrics; +#if S3JNI_ENABLE_AUTOEXT + struct { + S3JniAutoExtension *pHead /* Head of the auto-extension list */; + S3JniDb * psOpening /* handle to the being-opened db. We + need this so that auto extensions + can have a consistent view of the + cross-language db connection and + behave property if they call further + db APIs. */; + int isRunning /* True while auto extensions are + running. This is used to prohibit + manipulation of the auto-extension + list while extensions are + running. */; + } autoExt; +#endif +} S3JniGlobal; + +#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) +static void s3jni_oom(JNIEnv * const env){ + (*env)->FatalError(env, "Out of memory.") /* does not return */; +} + +/** + sqlite3_malloc() proxy which fails fatally on OOM. This should + only be used for routines which manage global state and have no + recovery strategy for OOM. For sqlite3 API which can reasonably + return SQLITE_NOMEM, sqlite3_malloc() should be used instead. +*/ +static void * s3jni_malloc(JNIEnv * const env, size_t n){ + void * const rv = sqlite3_malloc(n); + if(n && !rv) s3jni_oom(env); + return rv; +} + +/** + Fetches the S3JniGlobal.envCache row for the given env, allocing + a row if needed. When a row is allocated, its state is initialized + insofar as possible. Calls (*env)->FatalError() if allocation of + an entry fails. That's hypothetically possible but "shouldn't happen." +*/ +FIXME_THREADING(S3JniEnvCache) +static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ + struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; + for( ; row; row = row->pNext ){ + if( row->env == env ){ + ++S3JniGlobal.metrics.envCacheHits; + return row; + } + } + ++S3JniGlobal.metrics.envCacheMisses; + row = S3JniGlobal.envCache.aFree; + if( row ){ + assert(!row->pPrev); + S3JniGlobal.envCache.aFree = row->pNext; + if( row->pNext ) row->pNext->pPrev = 0; + }else{ + row = sqlite3_malloc(sizeof(S3JniEnvCache)); + if( !row ){ + (*env)->FatalError(env, "Maintenance required: S3JniEnvCache is full.") + /* Does not return, but cc doesn't know that */; + return NULL; + } + } + memset(row, 0, sizeof(*row)); + row->pNext = S3JniGlobal.envCache.aHead; + if(row->pNext) row->pNext->pPrev = row; + S3JniGlobal.envCache.aHead = row; + row->env = env; + + /* Grab references to various global classes and objects... */ + row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); + EXCEPTION_IS_FATAL("Error getting reference to Object class."); + + row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); + EXCEPTION_IS_FATAL("Error getting reference to Long class."); + row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, + "", "(J)V"); + EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); + + row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); + EXCEPTION_IS_FATAL("Error getting reference to String class."); + row->g.ctorStringBA = + (*env)->GetMethodID(env, row->g.cString, + "", "([BLjava/nio/charset/Charset;)V"); + EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); + row->g.stringGetBytes = + (*env)->GetMethodID(env, row->g.cString, + "getBytes", "(Ljava/nio/charset/Charset;)[B"); + EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset)."); + + { /* StandardCharsets.UTF_8 */ + jfieldID fUtf8; + jclass const klazzSC = + (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); + EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); + fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", + "Ljava/nio/charset/Charset;"); + EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); + row->g.oCharsetUtf8 = + REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); + EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); + } + return row; +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own Java/JNI bindings. +** +** For purposes of certain hand-crafted JNI function bindings, we +** need a way of reporting errors which is consistent with the rest of +** the C API, as opposed to throwing JS exceptions. To that end, this +** internal-use-only function is a thin proxy around +** sqlite3ErrorWithMessage(). The intent is that it only be used from +** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not +** from client code. +** +** Returns err_code. +*/ +static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zMsg){ + if( db!=0 ){ + if( 0==zMsg ){ + sqlite3Error(db, err_code); + }else{ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + } + } + return err_code; +} + +/** + Creates a new jByteArray of length nP, copies p's contents into it, and + returns that byte array. + */ +static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ + jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); + } + return jba; +} + +/** + Uses the java.lang.String(byte[],Charset) constructor to create a + new String from UTF-8 string z. n is the number of bytes to + copy. If n<0 then sqlite3Strlen30() is used to calculate it. + + Returns NULL if z is NULL or on OOM, else returns a new jstring + owned by the caller. + + Sidebar: this is a painfully inefficient way to convert from + standard UTF-8 to a Java string, but JNI offers only algorithms for + working with MUTF-8, not UTF-8. +*/ +static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, + const char * const z, int n){ + jstring rv = NULL; + JNIEnv * const env = jc->env; + if( 0==n || (n<0 && z && !z[0]) ){ + /* Fast-track the empty-string case via the MUTF-8 API. We could + hypothetically do this for any strings where n<4 and z is + NUL-terminated and none of z[0..3] are NUL bytes. */ + rv = (*env)->NewStringUTF(env, ""); + }else if( z ){ + jbyteArray jba; + if( n<0 ) n = sqlite3Strlen30(z); + jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n); + if( jba ){ + rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA, + jba, jc->g.oCharsetUtf8); + UNREF_L(jba); + } + } + return rv; +} + +/** + Converts the given java.lang.String object into a NUL-terminated + UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). + Returns NULL if jstr is NULL or on allocation error. If jstr is not + NULL and nLen is not NULL then nLen is set to the length of the + returned string, not including the terminating NUL. If jstr is not + NULL and it returns NULL, this indicates an allocation error. In + that case, if nLen is not NULL then it is either set to 0 (if + fetching of jstr's bytes fails to allocate) or set to what would + have been the length of the string had C-string allocation + succeeded. + + The returned memory is allocated from sqlite3_malloc() and + ownership is transferred to the caller. +*/ +static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc, + jstring jstr, int *nLen){ + JNIEnv * const env = jc->env; + jbyteArray jba; + jsize nBa; + char *rv; + + if(!jstr) return 0; + jba = (*env)->CallObjectMethod(env, jstr, jc->g.stringGetBytes, + jc->g.oCharsetUtf8); + if( (*env)->ExceptionCheck(env) || !jba + /* order of these checks is significant for -Xlint:jni */ ) { + EXCEPTION_REPORT; + if( nLen ) *nLen = 0; + return 0; + } + nBa = (*env)->GetArrayLength(env, jba); + if( nLen ) *nLen = (int)nBa; + rv = sqlite3_malloc( nBa + 1 ); + if( rv ){ + (*env)->GetByteArrayRegion(env, jba, 0, nBa, (jbyte*)rv); + rv[nBa] = 0; + } + UNREF_L(jba); + return rv; +} + +/** + Expects to be passed a pointer from sqlite3_column_text16() or + sqlite3_value_text16() and a byte-length value from + sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a + Java String of exactly half that character length, returning NULL + if !p or (*env)->NewString() fails. +*/ +static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ + return p + ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) + : NULL; +} + +/** + Requires jx to be a Throwable. Calls its toString() method and + returns its value converted to a UTF-8 string. The caller owns the + returned string and must eventually sqlite3_free() it. Returns 0 + if there is a problem fetching the info or on OOM. + + Design note: we use toString() instead of getMessage() because the + former includes the exception type's name: + + Exception e = new RuntimeException("Hi"); + System.out.println(e.toString()); // java.lang.RuntimeException: Hi + System.out.println(e.getMessage()); // Hi + } +*/ +FIXME_THREADING(S3JniEnvCache) +static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + jmethodID mid; + jstring msg; + char * zMsg; + jclass const klazz = (*env)->GetObjectClass(env, jx); + mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;"); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + return 0; + } + msg = (*env)->CallObjectMethod(env, jx, mid); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + return 0; + } + zMsg = s3jni_jstring_to_utf8(jc, msg, 0); + UNREF_L(msg); + return zMsg; +} + +/** + Extracts the current JNI exception, sets ps->pDb's error message to + its message string, and clears the exception. If errCode is non-0, + it is used as-is, else SQLITE_ERROR is assumed. If there's a + problem extracting the exception's message, it's treated as + non-fatal and zDfltMsg is used in its place. + + This must only be called if a JNI exception is pending. + + Returns errCode unless it is 0, in which case SQLITE_ERROR is + returned. +*/ +static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, + int errCode, const char *zDfltMsg){ + jthrowable const ex = (*env)->ExceptionOccurred(env); + + if( 0==errCode ) errCode = SQLITE_ERROR; + if( ex ){ + char * zMsg; + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg); + sqlite3_free(zMsg); + UNREF_L(ex); + } + return errCode; +} + +/** + Extracts the (void xDestroy()) method from the given jclass and + applies it to jobj. If jObj is NULL, this is a no-op. If klazz is + NULL then it's derived from jobj. The lack of an xDestroy() method + is silently ignored and any exceptions thrown by the method trigger + a warning to stdout or stderr and then the exception is suppressed. +*/ +static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ + if(jObj){ + jmethodID method; + if(!klazz){ + klazz = (*env)->GetObjectClass(env, jObj); + assert(klazz); + } + method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); + //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); + if(method){ + ++S3JniGlobal.metrics.nDestroy; + (*env)->CallVoidMethod(env, jObj, method); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); + EXCEPTION_CLEAR; + } + }else{ + EXCEPTION_CLEAR; + } + } +} + +/** + Removes any Java references from s and clears its state. If + doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's + s is passed to s3jni_call_xDestroy() before any references are + cleared. It is legal to call this when the object has no Java + references. +*/ +static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){ + if(doXDestroy && s->klazz && s->jObj){ + s3jni_call_xDestroy(env, s->jObj, s->klazz); + } + UNREF_G(s->jObj); + UNREF_G(s->klazz); + memset(s, 0, sizeof(*s)); +} + +/** + Clears s's state and moves it to the free-list. +*/ +FIXME_THREADING(perDb) +static void S3JniDb_set_aside(S3JniDb * const s){ + if(s){ + JNIEnv * const env = s->env; + assert(s->pDb && "Else this object is already in the free-list."); + //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); + assert(s->pPrev != s); + assert(s->pNext != s); + assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); + if(s->pNext) s->pNext->pPrev = s->pPrev; + if(s->pPrev) s->pPrev->pNext = s->pNext; + else if(S3JniGlobal.perDb.aUsed == s){ + assert(!s->pPrev); + S3JniGlobal.perDb.aUsed = s->pNext; + } + sqlite3_free( s->zMainDbName ); +#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY) + UNHOOK(trace, 0); + UNHOOK(progress, 0); + UNHOOK(commitHook, 0); + UNHOOK(rollbackHook, 0); + UNHOOK(updateHook, 0); + UNHOOK(authHook, 0); + UNHOOK(collation, 1); + UNHOOK(collationNeeded, 1); + UNHOOK(busyHandler, 1); +#undef UNHOOK + UNREF_G(s->jDb); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(s->jFtsApi); +#endif + memset(s, 0, sizeof(S3JniDb)); + s->pNext = S3JniGlobal.perDb.aFree; + if(s->pNext) s->pNext->pPrev = s; + S3JniGlobal.perDb.aFree = s; + //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); + //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); + } +} + +/** + Requires that p has been snipped from any linked list it is + in. Clears all Java refs p holds and zeroes out p. +*/ +static void S3JniEnvCache_clear(S3JniEnvCache * const p){ + JNIEnv * const env = p->env; + if(env){ + int i; + UNREF_G(p->g.cObj); + UNREF_G(p->g.cLong); + UNREF_G(p->g.cString); + UNREF_G(p->g.oCharsetUtf8); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(p->jFtsExt); + UNREF_G(p->jPhraseIter.klazz); +#endif + for( i = 0; i < NphCache_SIZE; ++i ){ + S3JniNphCache_clear(env, &p->nph[i]); + } + memset(p, 0, sizeof(S3JniEnvCache)); + } +} + +/** + Cleans up all state in S3JniGlobal.perDb for th given JNIEnv. + Results are undefined if a Java-side db uses the API + from the given JNIEnv after this call. +*/ +FIXME_THREADING(perDb) +static void S3JniDb_free_for_env(JNIEnv *env){ + S3JniDb * ps = S3JniGlobal.perDb.aUsed; + S3JniDb * pNext = 0; + for( ; ps; ps = pNext ){ + pNext = ps->pNext; + if(ps->env == env){ + S3JniDb * const pPrev = ps->pPrev; + S3JniDb_set_aside(ps); + assert(pPrev ? pPrev->pNext!=ps : 1); + pNext = pPrev; + } + } +} + +/** + Uncache any state for the given JNIEnv, clearing all Java + references the cache owns. Returns true if env was cached and false + if it was not found in the cache. + + Also passes env to S3JniDb_free_for_env() to free up + what would otherwise be stale references. +*/ +static int S3JniGlobal_env_uncache(JNIEnv * const env){ + struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; + for( ; row; row = row->pNext ){ + if( row->env == env ){ + break; + } + } + if( !row ) return 0; + if( row->pNext ) row->pNext->pPrev = row->pPrev; + if( row->pPrev ) row->pPrev->pNext = row->pNext; + if( S3JniGlobal.envCache.aHead == row ){ + assert( !row->pPrev ); + S3JniGlobal.envCache.aHead = row->pNext; + } + S3JniEnvCache_clear(row); + assert( !row->pNext ); + assert( !row->pPrev ); + row->pNext = S3JniGlobal.envCache.aFree; + if( row->pNext ) row->pNext->pPrev = row; + S3JniGlobal.envCache.aFree = row; + S3JniDb_free_for_env(env); + return 1; +} + +static void S3JniGlobal_S3JniEnvCache_clear(void){ + while( S3JniGlobal.envCache.aHead ){ + S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); + } +} + +/** + Searches the NativePointerHolder cache for the given combination. + If it finds one, it returns it as-is. If it doesn't AND the cache + has a free slot, it populates that slot with (env, zClassName, + klazz) and returns it. If the cache is full with no match it + returns NULL. + + It is up to the caller to populate the other members of the returned + object if needed. + + zClassName must be a static string so we can use its address as a + cache key. + + This simple cache catches >99% of searches in the current + (2023-07-31) tests. +*/ +FIXME_THREADING(S3JniEnvCache) +static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ + /** + According to: + + https://developer.ibm.com/articles/j-jni/ + + > ... the IDs returned for a given class don't change for the + lifetime of the JVM process. But the call to get the field or + method can require significant work in the JVM, because + fields and methods might have been inherited from + superclasses, making the JVM walk up the class hierarchy to + find them. Because the IDs are the same for a given class, + you should look them up once and then reuse them. Similarly, + looking up class objects can be expensive, so they should be + cached as well. + */ + struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); + S3JniNphCache * freeSlot = 0; + S3JniNphCache * pCache = 0; + int i; + assert(envRow); + for( i = 0; i < NphCache_SIZE; ++i ){ + pCache = &envRow->nph[i]; + if(zClassName == pCache->zClassName){ + ++S3JniGlobal.metrics.nphCacheHits; +#define DUMP_NPH_CACHES 0 +#if DUMP_NPH_CACHES + MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", + S3JniGlobal.metrics.nphCacheHits, zClassName, pCache->klazz, pCache->fidValue, + pCache->midCtor)); +#endif + assert(pCache->klazz); + return pCache; + }else if(!freeSlot && !pCache->zClassName){ + freeSlot = pCache; + } + } + if(freeSlot){ + freeSlot->zClassName = zClassName; + freeSlot->klazz = (*env)->FindClass(env, zClassName); + EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); + freeSlot->klazz = REF_G(freeSlot->klazz); + ++S3JniGlobal.metrics.nphCacheMisses; +#if DUMP_NPH_CACHES + static unsigned int cacheMisses = 0; + MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", + S3JniGlobal.metrics.nphCacheMisses, zClassName, freeSlot->klazz, + freeSlot->fidValue, freeSlot->midCtor)); +#endif +#undef DUMP_NPH_CACHES + }else{ + (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low."); + } + return freeSlot; +} + +/** + Returns the ID of the "nativePointer" field from the given + NativePointerHolder class. + */ +static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ + jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); + EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); + return rv; +} + +/** + Sets a native ptr value in NativePointerHolder object ppOut. + zClassName must be a static string so we can use its address + as a cache key. +*/ +static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, + const char *zClassName){ + jfieldID setter = 0; + S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->klazz && pCache->fidValue){ + assert(zClassName == pCache->zClassName); + setter = pCache->fidValue; + assert(setter); + }else{ + jclass const klazz = + pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut); + setter = NativePointerHolder_getField(env, klazz); + if(pCache){ + assert(pCache->klazz); + assert(!pCache->fidValue); + assert(zClassName == pCache->zClassName); + pCache->fidValue = setter; + } + } + (*env)->SetLongField(env, ppOut, setter, (jlong)p); + EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); +} + +/** + Fetches a native ptr value from NativePointerHolder object ppOut. + zClassName must be a static string so we can use its address as a + cache key. +*/ +static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){ + if( pObj ){ + jfieldID getter = 0; + void * rv = 0; + S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->fidValue){ + getter = pCache->fidValue; + }else{ + jclass const klazz = + pCache ? pCache->klazz : (*env)->GetObjectClass(env, pObj); + getter = NativePointerHolder_getField(env, klazz); + if(pCache){ + assert(pCache->klazz); + assert(zClassName == pCache->zClassName); + pCache->fidValue = getter; + } + } + rv = (void*)(*env)->GetLongField(env, pObj, getter); + IFTHREW_REPORT; + return rv; + }else{ + return 0; + } +} + +/** + Extracts the new S3JniDb instance from the free-list, or + allocates one if needed, associats it with pDb, and returns. + Returns NULL on OOM. pDb MUST be associated with jDb via + NativePointerHolder_set(). +*/ +static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, + jobject jDb){ + S3JniDb * rv; + if(S3JniGlobal.perDb.aFree){ + rv = S3JniGlobal.perDb.aFree; + //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); + //MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext)); + S3JniGlobal.perDb.aFree = rv->pNext; + assert(rv->pNext != rv); + assert(rv->pPrev != rv); + assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1); + if(rv->pNext){ + assert(rv->pNext->pPrev == rv); + assert(rv->pPrev ? (rv->pNext == rv->pPrev->pNext) : 1); + rv->pNext->pPrev = 0; + rv->pNext = 0; + } + }else{ + rv = s3jni_malloc(env, sizeof(S3JniDb)); + //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb)); + if(rv){ + memset(rv, 0, sizeof(S3JniDb)); + } + } + if(rv){ + rv->pNext = S3JniGlobal.perDb.aUsed; + S3JniGlobal.perDb.aUsed = rv; + if(rv->pNext){ + assert(!rv->pNext->pPrev); + rv->pNext->pPrev = rv; + } + rv->jDb = REF_G(jDb); + rv->pDb = pDb; + rv->env = env; + } + return rv; +} + +#if 0 +static void S3JniDb_dump(S3JniDb *s){ + MARKER(("S3JniDb->env @ %p\n", s->env)); + MARKER(("S3JniDb->pDb @ %p\n", s->pDb)); + MARKER(("S3JniDb->trace.jObj @ %p\n", s->trace.jObj)); + MARKER(("S3JniDb->progress.jObj @ %p\n", s->progress.jObj)); + MARKER(("S3JniDb->commitHook.jObj @ %p\n", s->commitHook.jObj)); + MARKER(("S3JniDb->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); + MARKER(("S3JniDb->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); + MARKER(("S3JniDb->env @ %p\n", s->env)); +} +#endif + +/** + Returns the S3JniDb object for the given db. If allocIfNeeded is + true then a new instance will be allocated if no mapping currently + exists, else NULL is returned if no mapping is found. + + The 3rd and 4th args should normally only be non-0 for + sqlite3_open(_v2)(). For most other cases, they must be 0, in which + case the db handle will be fished out of the jDb object and NULL is + returned if jDb does not have any associated S3JniDb. + + If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST + be false and it will look for a matching db object. That usage is + required for functions, like sqlite3_context_db_handle(), which + return a (sqlite3*) but do not take one as an argument. +*/ +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, + sqlite3 *pDb, int allocIfNeeded){ + S3JniDb * s = S3JniGlobal.perDb.aUsed; + if(!jDb){ + if(pDb){ + assert(!allocIfNeeded); + }else{ + return 0; + } + } + assert(allocIfNeeded ? !!pDb : 1); + if(!allocIfNeeded && !pDb){ + pDb = PtrGet_sqlite3(jDb); + } + for( ; pDb && s; s = s->pNext){ + if(s->pDb == pDb) return s; + } + if(allocIfNeeded){ + s = S3JniDb_alloc(env, pDb, jDb); + } + return s; +} + +#if 0 +/** + An alternative form which searches for the S3JniDb instance for + pDb with no JNIEnv-specific info. This can be (but _should_ it be?) + called from the context of a separate JNIEnv than the one mapped + to in the returned object. Returns 0 if no match is found. +*/ +FIXME_THREADING(perDb) +static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){ + S3JniDb * s = S3JniGlobal.perDb.aUsed; + for( ; pDb && s; s = s->pNext){ + if(s->pDb == pDb) return s; + } + return 0; +} +#endif + +#if S3JNI_ENABLE_AUTOEXT +/** + Unlink ax from S3JniGlobal.autoExt and free it. +*/ +static void S3JniAutoExtension_free(JNIEnv * const env, + S3JniAutoExtension * const ax){ + if( ax ){ + if( ax->pNext ) ax->pNext->pPrev = ax->pPrev; + if( ax == S3JniGlobal.autoExt.pHead ){ + assert( !ax->pNext ); + S3JniGlobal.autoExt.pHead = ax->pNext; + }else if( ax->pPrev ){ + ax->pPrev->pNext = ax->pNext; + } + ax->pNext = ax->pPrev = 0; + UNREF_G(ax->jObj); + sqlite3_free(ax); + } +} + +/** + Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt. + Returns 0 on OOM or if there is an error collecting the required + state from jAutoExt (which must be an AutoExtension object). +*/ +static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, + jobject const jAutoExt){ + S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax)); + if( ax ){ + jclass klazz; + memset(ax, 0, sizeof(*ax)); + klazz = (*env)->GetObjectClass(env, jAutoExt); + if(!klazz){ + S3JniAutoExtension_free(env, ax); + return 0; + } + ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", + "(Lorg/sqlite/jni/sqlite3;)I"); + if(!ax->midFunc){ + MARKER(("Error getting xEntryPoint(sqlite3) from object.")); + S3JniAutoExtension_free(env, ax); + return 0; + } + ax->jObj = REF_G(jAutoExt); + ax->pNext = S3JniGlobal.autoExt.pHead; + if( ax->pNext ) ax->pNext->pPrev = ax; + S3JniGlobal.autoExt.pHead = ax; + } + return ax; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + +/** + Requires that jCx be a Java-side sqlite3_context wrapper for pCx. + This function calls sqlite3_aggregate_context() to allocate a tiny + sliver of memory, the address of which is set in + jCx->aggregateContext. The memory is only used as a key for + mapping client-side results of aggregate result sets across + calls to the UDF's callbacks. + + isFinal must be 1 for xFinal() calls and 0 for all others, the + difference being that the xFinal() invocation will not allocate + new memory if it was not already, resulting in a value of 0 + for jCx->aggregateContext. + + Returns 0 on success. Returns SQLITE_NOMEM on allocation error, + noting that it will not allocate when isFinal is true. It returns + SQLITE_ERROR if there's a serious internal error in dealing with + the JNI state. +*/ +static int udf_setAggregateContext(JNIEnv * env, jobject jCx, + sqlite3_context * pCx, + int isFinal){ + jfieldID member; + void * pAgg; + int rc = 0; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context); + if(pCache && pCache->klazz && pCache->fidSetAgg){ + member = pCache->fidSetAgg; + assert(member); + }else{ + jclass const klazz = + pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx); + member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J"); + if( !member ){ + IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; } + return s3jni_db_error(sqlite3_context_db_handle(pCx), + SQLITE_ERROR, + "Internal error: cannot find " + "sqlite3_context::aggregateContext field."); + } + if(pCache){ + assert(pCache->klazz); + assert(!pCache->fidSetAgg); + pCache->fidSetAgg = member; + } + } + pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); + if( pAgg || isFinal ){ + (*env)->SetLongField(env, jCx, member, (jlong)pAgg); + }else{ + assert(!pAgg); + rc = SQLITE_NOMEM; + } + return rc; +} + +/** + Common init for OutputPointer_set_Int32() and friends. zClassName must be a + pointer from S3JniClassNames. jOut must be an instance of that + class. Fetches the jfieldID for jOut's [value] property, which must + be of the type represented by the JNI type signature zTypeSig, and + stores it in pFieldId. Fails fatally if the property is not found, + as that presents a serious internal misuse. + + Property lookups are cached on a per-zClassName basis. Do not use + this routine with the same zClassName but different zTypeSig: it + will misbehave. +*/ +static void setupOutputPointer(JNIEnv * const env, const char *zClassName, + const char * const zTypeSig, + jobject const jOut, jfieldID * const pFieldId){ + jfieldID setter = 0; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->klazz && pCache->fidValue){ + setter = pCache->fidValue; + }else{ + const jclass klazz = (*env)->GetObjectClass(env, jOut); + /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/ + setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); + EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); + if(pCache){ + assert(!pCache->fidValue); + pCache->fidValue = setter; + } + } + *pFieldId = setter; +} + +/* Sets the value property of the OutputPointer.Int32 jOut object + to v. */ +static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_Int32, "I", jOut, &setter); + (*env)->SetIntField(env, jOut, setter, (jint)v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); +} + +/* Sets the value property of the OutputPointer.Int64 jOut object + to v. */ +static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); + (*env)->SetLongField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); +} + +static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, + jobject jDb){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3, + "Lorg/sqlite/jni/sqlite3;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, jDb); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); +} + +static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, + jobject jStmt){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3_stmt, + "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, jStmt); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); +} + +#ifdef SQLITE_ENABLE_FTS5 +#if 0 +/* Sets the value property of the OutputPointer.ByteArray jOut object + to v. */ +static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, + jbyteArray const v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_ByteArray, "[B", + jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); +} +#endif +/* Sets the value property of the OutputPointer.String jOut object + to v. */ +static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, + jstring const v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_String, + "Ljava/lang/String;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); +} +#endif /* SQLITE_ENABLE_FTS5 */ + +static int encodingTypeIsValid(int eTextRep){ + switch(eTextRep){ + case SQLITE_UTF8: case SQLITE_UTF16: + case SQLITE_UTF16LE: case SQLITE_UTF16BE: + return 1; + default: + return 0; + } +} + +static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, + int nRhs, const void *rhs){ + S3JniDb * const ps = pArg; + JNIEnv * env = ps->env; + jint rc = 0; + jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); + jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; + //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); + if(!jbaRhs){ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return 0; + //(*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); + } + (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); + (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); + rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback, + jbaLhs, jbaRhs); + EXCEPTION_IGNORE; + UNREF_L(jbaLhs); + UNREF_L(jbaRhs); + return (int)rc; +} + +/* Collation finalizer for use by the sqlite3 internals. */ +static void CollationState_xDestroy(void *pArg){ + S3JniDb * const ps = pArg; + S3JniHook_unref( ps->env, &ps->collation, 1 ); +} + +/* State for sqlite3_result_java_object() and + sqlite3_value_java_object(). */ +typedef struct { + /* The JNI docs say: + + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html + + > The VM is guaranteed to pass the same interface pointer to a + native method when it makes multiple calls to the native method + from the same Java thread. + + Per the accompanying diagram, the "interface pointer" is the + pointer-to-pointer which is passed to all JNI calls + (`JNIEnv *env`), implying that we need to be caching that. The + verbiage "interface pointer" implies, however, that we should be + storing the dereferenced `(*env)` pointer. + + This posts claims it's unsafe to cache JNIEnv at all, even when + it's always used in the same thread: + + https://stackoverflow.com/questions/12420463 + + And this one seems to contradict that: + + https://stackoverflow.com/questions/13964608 + + For later reference: + + https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242 + + https://developer.android.com/training/articles/perf-jni + + The later has the following say about caching: + + > If performance is important, it's useful to look the + [class/method ID] values up once and cache the results in your + native code. Because there is a limit of one JavaVM per + process, it's reasonable to store this data in a static local + structure. ... The class references, field IDs, and method IDs + are guaranteed valid until the class is unloaded. Classes are + only unloaded if all classes associated with a ClassLoader can + be garbage collected, which is rare but will not be impossible + in Android. Note however that the jclass is a class reference + and must be protected with a call to NewGlobalRef (see the next + section). + */ + JNIEnv * env; + jobject jObj; +} ResultJavaVal; + +/* For use with sqlite3_result/value_pointer() */ +#define RESULT_JAVA_VAL_STRING "ResultJavaVal" + +static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ + ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); + if(rv){ + rv->env = env; + rv->jObj = jObj ? REF_G(jObj) : 0; + } + return rv; +} + +static void ResultJavaVal_finalizer(void *v){ + if(v){ + ResultJavaVal * const rv = (ResultJavaVal*)v; + if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj); + sqlite3_free(rv); + } +} + + + +/** + Returns a new Java instance of the class named by zClassName, which + MUST be interface-compatible with NativePointerHolder and MUST have + a no-arg constructor. The NativePointerHolder_set() method is + passed the new Java object and pNative. Hypothetically returns NULL + if Java fails to allocate, but the JNI docs are not entirely clear + on that detail. + + Always use a static string pointer from S3JniClassNames for the 2nd + argument so that we can use its address as a cache key. +*/ +static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, + const void * pNative){ + jobject rv = 0; + jclass klazz = 0; + jmethodID ctor = 0; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->midCtor){ + assert( pCache->klazz ); + klazz = pCache->klazz; + ctor = pCache->midCtor; + }else{ + klazz = pCache + ? pCache->klazz + : (*env)->FindClass(env, zClassName); + ctor = klazz ? (*env)->GetMethodID(env, klazz, "", "()V") : 0; + EXCEPTION_IS_FATAL("Cannot find constructor for class."); + if(pCache){ + assert(zClassName == pCache->zClassName); + assert(pCache->klazz); + assert(!pCache->midCtor); + pCache->midCtor = ctor; + } + } + assert(klazz); + assert(ctor); + rv = (*env)->NewObject(env, klazz, ctor); + EXCEPTION_IS_FATAL("No-arg constructor threw."); + if(rv) NativePointerHolder_set(env, rv, pNative, zClassName); + return rv; +} + +static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3, sv); +} +static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_context, sv); +} +static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_stmt, sv); +} +static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_value, sv); +} + +enum UDFType { + UDF_SCALAR = 1, + UDF_AGGREGATE, + UDF_WINDOW, + UDF_UNKNOWN_TYPE/*for error propagation*/ +}; + +typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**); +typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**); +typedef void (*udf_xFinal_f)(sqlite3_context*); +/*typedef void (*udf_xValue_f)(sqlite3_context*);*/ +/*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/ + +/** + State for binding Java-side UDFs. +*/ +typedef struct S3JniUdf S3JniUdf; +struct S3JniUdf { + JNIEnv * env; /* env registered from */; + jobject jObj /* SQLFunction instance */; + jclass klazz /* jObj's class */; + char * zFuncName /* Only for error reporting and debug logging */; + enum UDFType type; + /** Method IDs for the various UDF methods. */ + jmethodID jmidxFunc; + jmethodID jmidxStep; + jmethodID jmidxFinal; + jmethodID jmidxValue; + jmethodID jmidxInverse; +}; + +static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ + S3JniUdf * const s = sqlite3_malloc(sizeof(S3JniUdf)); + if(s){ + const char * zFSI = /* signature for xFunc, xStep, xInverse */ + "(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V"; + const char * zFV = /* signature for xFinal, xValue */ + "(Lorg/sqlite/jni/sqlite3_context;)V"; + memset(s, 0, sizeof(S3JniUdf)); + s->env = env; + s->jObj = REF_G(jObj); + s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); +#define FGET(FuncName,FuncType,Field) \ + s->Field = (*env)->GetMethodID(env, s->klazz, FuncName, FuncType); \ + if(!s->Field) (*env)->ExceptionClear(env) + FGET("xFunc", zFSI, jmidxFunc); + FGET("xStep", zFSI, jmidxStep); + FGET("xFinal", zFV, jmidxFinal); + FGET("xValue", zFV, jmidxValue); + FGET("xInverse", zFSI, jmidxInverse); +#undef FGET + if(s->jmidxFunc) s->type = UDF_SCALAR; + else if(s->jmidxStep && s->jmidxFinal){ + s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE; + }else{ + s->type = UDF_UNKNOWN_TYPE; + } + } + return s; +} + +static void S3JniUdf_free(S3JniUdf * s){ + JNIEnv * const env = s->env; + if(env){ + //MARKER(("UDF cleanup: %s\n", s->zFuncName)); + s3jni_call_xDestroy(env, s->jObj, s->klazz); + UNREF_G(s->jObj); + UNREF_G(s->klazz); + } + sqlite3_free(s->zFuncName); + sqlite3_free(s); +} + +static void S3JniUdf_finalizer(void * s){ + //MARKER(("UDF finalizer @ %p\n", s)); + if(s) S3JniUdf_free((S3JniUdf*)s); +} + +/** + Helper for processing args to UDF handlers + with signature (sqlite3_context*,int,sqlite3_value**). +*/ +typedef struct { + jobject jcx; + jobjectArray jargv; +} udf_jargs; + +/** + Converts the given (cx, argc, argv) into arguments for the given + UDF, placing the result in the final argument. Returns 0 on + success, SQLITE_NOMEM on allocation error. + + TODO: see what we can do to optimize the + new_sqlite3_value_wrapper() call. e.g. find the ctor a single time + and call it here, rather than looking it up repeatedly. +*/ +static int udf_args(JNIEnv *env, + sqlite3_context * const cx, + int argc, sqlite3_value**argv, + jobject * jCx, jobjectArray *jArgv){ + jobjectArray ja = 0; + jobject jcx = new_sqlite3_context_wrapper(env, cx); + jint i; + *jCx = 0; + *jArgv = 0; + if(!jcx) goto error_oom; + ja = (*env)->NewObjectArray(env, argc, + S3JniGlobal_env_cache(env)->g.cObj, + NULL); + if(!ja) goto error_oom; + for(i = 0; i < argc; ++i){ + jobject jsv = new_sqlite3_value_wrapper(env, argv[i]); + if(!jsv) goto error_oom; + (*env)->SetObjectArrayElement(env, ja, i, jsv); + UNREF_L(jsv)/*array has a ref*/; + } + *jCx = jcx; + *jArgv = ja; + return 0; +error_oom: + sqlite3_result_error_nomem(cx); + UNREF_L(jcx); + UNREF_L(ja); + return SQLITE_NOMEM; +} + +static int udf_report_exception(sqlite3_context * cx, + const char *zFuncName, + const char *zFuncType){ + int rc; + char * z = + sqlite3_mprintf("Client-defined function %s.%s() threw. It should " + "not do that.", + zFuncName ? zFuncName : "", zFuncType); + if(z){ + sqlite3_result_error(cx, z, -1); + sqlite3_free(z); + rc = SQLITE_ERROR; + }else{ + sqlite3_result_error_nomem(cx); + rc = SQLITE_NOMEM; + } + return rc; +} + +/** + Sets up the state for calling a Java-side xFunc/xStep/xInverse() + UDF, calls it, and returns 0 on success. +*/ +static int udf_xFSI(sqlite3_context* pCx, int argc, + sqlite3_value** argv, + S3JniUdf * s, + jmethodID xMethodID, + const char * zFuncType){ + JNIEnv * const env = s->env; + udf_jargs args = {0,0}; + int rc = udf_args(s->env, pCx, argc, argv, &args.jcx, &args.jargv); + //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx)); + if(rc) return rc; + //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + if( UDF_SCALAR != s->type ){ + rc = udf_setAggregateContext(env, args.jcx, pCx, 0); + } + if( 0 == rc ){ + (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); + IFTHREW{ + rc = udf_report_exception(pCx, s->zFuncName, zFuncType); + } + } + UNREF_L(args.jcx); + UNREF_L(args.jargv); + return rc; +} + +/** + Sets up the state for calling a Java-side xFinal/xValue() UDF, + calls it, and returns 0 on success. +*/ +static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, + jmethodID xMethodID, + const char *zFuncType){ + JNIEnv * const env = s->env; + jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + int rc = 0; + //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx)); + if(!jcx){ + sqlite3_result_error_nomem(cx); + return SQLITE_NOMEM; + } + //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + if( UDF_SCALAR != s->type ){ + rc = udf_setAggregateContext(env, jcx, cx, 1); + } + if( 0 == rc ){ + (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); + IFTHREW{ + rc = udf_report_exception(cx,s->zFuncName, zFuncType); + } + } + UNREF_L(jcx); + return rc; +} + +static void udf_xFunc(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nFunc; + udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc"); +} +static void udf_xStep(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nStep; + udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep"); +} +static void udf_xFinal(sqlite3_context* cx){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nFinal; + udf_xFV(cx, s, s->jmidxFinal, "xFinal"); +} +static void udf_xValue(sqlite3_context* cx){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nValue; + udf_xFV(cx, s, s->jmidxValue, "xValue"); +} +static void udf_xInverse(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nInverse; + udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse"); +} + + +//////////////////////////////////////////////////////////////////////// +// What follows is the JNI/C bindings. They are in alphabetical order +// except for this macro-generated subset which are kept together here +// at the front... +//////////////////////////////////////////////////////////////////////// +WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) +WRAP_INT_DB(1changes, sqlite3_changes) +WRAP_INT64_DB(1changes64, sqlite3_changes64) +WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings) +WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes) +WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16) +WRAP_INT_STMT(1column_1count, sqlite3_column_count) +WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype) +WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name) +WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name) +WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name) +WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name) +WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) +WRAP_INT_STMT(1data_1count, sqlite3_data_count) +WRAP_INT_DB(1error_1offset, sqlite3_error_offset) +WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) +WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) +WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) +WRAP_INT_INT(1sleep, sqlite3_sleep) +WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid) +WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe) +WRAP_INT_DB(1total_1changes, sqlite3_total_changes) +WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64) +WRAP_INT_SVALUE(1value_1bytes, sqlite3_value_bytes) +WRAP_INT_SVALUE(1value_1bytes16, sqlite3_value_bytes16) +WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding) +WRAP_INT_SVALUE(1value_1frombind, sqlite3_value_frombind) +WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange) +WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) +WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) +WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) + +#if S3JNI_ENABLE_AUTOEXT +/* Central auto-extension handler. */ +FIXME_THREADING(autoExt) +static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ + S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; + int rc; + JNIEnv * env = 0; + S3JniDb * const ps = S3JniGlobal.autoExt.psOpening; + //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); + S3JniGlobal.autoExt.psOpening = 0; + if( !pAX ){ + assert( 0==S3JniGlobal.autoExt.isRunning ); + return 0; + } + else if( S3JniGlobal.autoExt.isRunning ){ + /* Necessary to avoid certain endless loop/stack overflow cases. */ + *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " + "auto-extensions are running."); + return SQLITE_MISUSE; + } + else if(!ps){ + MARKER(("Internal error: cannot find S3JniDb for auto-extension\n")); + return SQLITE_ERROR; + }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){ + assert(!"Cannot get JNIEnv"); + *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); + return SQLITE_ERROR; + } + assert( !ps->pDb /* it's still being opened */ ); + ps->pDb = pDb; + assert( ps->jDb ); + NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); + ++S3JniGlobal.autoExt.isRunning; + for( ; pAX; pAX = pAX->pNext ){ + rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); + IFTHREW { + jthrowable const ex = (*env)->ExceptionOccurred(env); + char * zMsg; + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + UNREF_L(ex); + *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); + sqlite3_free(zMsg); + rc = rc ? rc : SQLITE_ERROR; + break; + }else if( rc ){ + break; + } + } + --S3JniGlobal.autoExt.isRunning; + return rc; +} + +FIXME_THREADING(autoExt) +JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ + static int once = 0; + S3JniAutoExtension * ax; + + if( !jAutoExt ) return SQLITE_MISUSE; + else if( 0==once && ++once ){ + sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension ); + } + ax = S3JniGlobal.autoExt.pHead; + for( ; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + return 0 /* C API treats this as a no-op. */; + } + } + return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, + jint ndx, jbyteArray baData, jint nMax){ + int rc; + if(!baData){ + rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx); + }else{ + jbyte * const pBuf = JBA_TOC(baData); + rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, + SQLITE_TRANSIENT); + JBA_RELEASE(baData,pBuf); + } + return (jint)rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, + jint ndx, jdouble val){ + return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, + jint ndx, jint val){ + return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, + jint ndx, jlong val){ + return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ + int rc = 0; + jbyte * const pBuf = JBA_TOC(jName); + if(pBuf){ + rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt), + (const char *)pBuf); + JBA_RELEASE(jName, pBuf); + } + return rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, + jint ndx, jbyteArray baData, jint nMax){ + if(baData){ + jbyte * const pBuf = JBA_TOC(baData); + int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, + (int)nMax, SQLITE_TRANSIENT); + JBA_RELEASE(baData, pBuf); + return (jint)rc; + }else{ + return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); + } +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, + jint ndx, jint n){ + return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, + jint ndx, jlong n){ + return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); +} + +static int s3jni_busy_handler(void* pState, int n){ + S3JniDb * const ps = (S3JniDb *)pState; + int rc = 0; + if( ps->busyHandler.jObj ){ + JNIEnv * const env = ps->env; + rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, + ps->busyHandler.midCallback, (jint)n); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("busy-handler callback"); + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw."); + } + } + return rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc = 0; + if(!ps) return (jint)SQLITE_NOMEM; + if(jBusy){ + S3JniHook * const pHook = &ps->busyHandler; + if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ + /* Same object - this is a no-op. */ + return 0; + } + S3JniHook_unref(env, pHook, 1); + pHook->jObj = REF_G(jBusy); + pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); + pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); + IFTHREW { + S3JniHook_unref(env, pHook, 0); + rc = SQLITE_ERROR; + } + if(rc){ + return rc; + } + }else{ + S3JniHook_unref(env, &ps->busyHandler, 1); + } + return jBusy + ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) + : sqlite3_busy_handler(ps->pDb, 0, 0); +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + if( ps ){ + S3JniHook_unref(env, &ps->busyHandler, 1); + return sqlite3_busy_timeout(ps->pDb, (int)ms); + } + return SQLITE_MISUSE; +} + +#if S3JNI_ENABLE_AUTOEXT +FIXME_THREADING(autoExt) +JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ + S3JniAutoExtension * ax;; + if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE; + for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_free(env, ax); + return JNI_TRUE; + } + } + return JNI_FALSE; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + + +/** + Wrapper for sqlite3_close(_v2)(). +*/ +static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ + int rc = 0; + S3JniDb * ps = 0; + assert(version == 1 || version == 2); + ps = S3JniDb_for_db(env, jDb, 0, 0); + if(ps){ + //MARKER(("close()ing db@%p\n", ps->pDb)); + rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); + S3JniDb_set_aside(ps) + /* MUST come after close() because of ps->trace. */; + NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3); + } + return (jint)rc; +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ + return s3jni_close_db(env, pDb, 2); +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1close)(JENV_CSELF, jobject pDb){ + return s3jni_close_db(env, pDb, 1); +} + +/** + Assumes z is an array of unsigned short and returns the index in + that array of the first element with the value 0. +*/ +static unsigned int s3jni_utf16_strlen(void const * z){ + unsigned int i = 0; + const unsigned short * p = z; + while( p[i] ) ++i; + return i; +} + +/** + sqlite3_collation_needed16() hook impl. + */ +static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, + int eTextRep, const void * z16Name){ + S3JniDb * const ps = pState; + JNIEnv * const env = ps->env; + unsigned int const nName = s3jni_utf16_strlen(z16Name); + jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); + IFTHREW{ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + EXCEPTION_CLEAR; + }else{ + (*env)->CallVoidMethod(env, ps->collationNeeded.jObj, + ps->collationNeeded.midCallback, + ps->jDb, (jint)eTextRep, jName); + IFTHREW{ + s3jni_db_exception(env, ps, 0, "collation-needed callback threw"); + } + } + UNREF_L(jName); +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * const pHook = &ps->collationNeeded; + int rc; + + if( !ps ) return SQLITE_MISUSE; + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return 0; + } + if( !jHook ){ + UNREF_G(pOld); + memset(pHook, 0, sizeof(S3JniHook)); + sqlite3_collation_needed(ps->pDb, 0, 0); + return 0; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded", + "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I"); + IFTHREW { + rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, + "Cannot not find matching callback on " + "collation-needed hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + UNREF_G(pOld); + rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16); + } + return rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + void const * const p = sqlite3_column_blob(pStmt, (int)ndx); + int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0; + if( 0==p ) return NULL; + else{ + jbyteArray const jba = (*env)->NewByteArray(env, n); + (*env)->SetByteArrayRegion(env, jba, 0, n, (const jbyte *)p); + return jba; + } +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes(stmt, (int)ndx); + const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx); + return s3jni_new_jbyteArray(env, p, n); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes16(stmt, (int)ndx); + const void * const p = sqlite3_column_text16(stmt, (int)ndx); + return s3jni_text16_to_jstring(env, p, n); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); + return new_sqlite3_value_wrapper(env, sv); +} + + +static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ + JNIEnv * const env = ps->env; + int rc = isCommit + ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, + ps->commitHook.midCallback) + : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj, + ps->rollbackHook.midCallback), 0); + IFTHREW{ + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw."); + } + return rc; +} + +static int s3jni_commit_hook_impl(void *pP){ + return s3jni_commit_rollback_hook_impl(1, pP); +} + +static void s3jni_rollback_hook_impl(void *pP){ + (void)s3jni_commit_rollback_hook_impl(0, pP); +} + +FIXME_THREADING(perDb) +static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, + jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; + if(!ps){ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return 0; + } + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return pOld; + } + if( !jHook ){ + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(pHook, 0, sizeof(S3JniHook)); + if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0); + else sqlite3_rollback_hook(ps->pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, + isCommit ? "xCommitHook" : "xRollbackHook", + isCommit ? "()I" : "()V"); + IFTHREW { + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching callback on " + "hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps); + else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + +JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ + return s3jni_commit_rollback_hook(1, env, jDb, jHook); +} + + +JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){ + return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); +} + +JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ + const char *zUtf8 = JSTR_TOC(name); + const jboolean rc = + 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; + JSTR_RELEASE(name, zUtf8); + return rc; +} + +FIXME_THREADING(perDb) +JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ + sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); + S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0; + return ps ? ps->jDb : 0; +} + +JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, + jstring name, jint eTextRep, + jobject oCollation){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + int rc; + const char *zName; + S3JniHook * pHook; + if(!ps) return (jint)SQLITE_NOMEM; + pHook = &ps->collation; + klazz = (*env)->GetObjectClass(env, oCollation); + pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare", + "([B[B)I"); + IFTHREW{ + EXCEPTION_REPORT; + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Could not get xCompare() method for object."); + } + zName = JSTR_TOC(name); + rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, + ps, CollationState_xCompare, + CollationState_xDestroy); + JSTR_RELEASE(name, zName); + if( 0==rc ){ + pHook->jObj = REF_G(oCollation); + pHook->klazz = REF_G(klazz); + }else{ + S3JniHook_unref(env, pHook, 1); + } + return (jint)rc; +} + +static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, + jint nArg, jint eTextRep, jobject jFunctor){ + S3JniUdf * s = 0; + int rc; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + const char * zFuncName = 0; + + if( !encodingTypeIsValid(eTextRep) ){ + return s3jni_db_error(pDb, SQLITE_FORMAT, + "Invalid function encoding option."); + } + s = S3JniUdf_alloc(env, jFunctor); + if( !s ) return SQLITE_NOMEM; + else if( UDF_UNKNOWN_TYPE==s->type ){ + rc = s3jni_db_error(pDb, SQLITE_MISUSE, + "Cannot unambiguously determine function type."); + S3JniUdf_free(s); + goto error_cleanup; + } + zFuncName = JSTR_TOC(jFuncName); + if(!zFuncName){ + rc = SQLITE_NOMEM; + S3JniUdf_free(s); + goto error_cleanup; + } + if( UDF_WINDOW == s->type ){ + rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s, + udf_xStep, udf_xFinal, udf_xValue, + udf_xInverse, S3JniUdf_finalizer); + }else{ + udf_xFunc_f xFunc = 0; + udf_xStep_f xStep = 0; + udf_xFinal_f xFinal = 0; + if( UDF_SCALAR == s->type ){ + xFunc = udf_xFunc; + }else{ + assert( UDF_AGGREGATE == s->type ); + xStep = udf_xStep; + xFinal = udf_xFinal; + } + rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, + xFunc, xStep, xFinal, S3JniUdf_finalizer); + } + if( 0==rc ){ + s->zFuncName = sqlite3_mprintf("%s", zFuncName) + /* OOM here is non-fatal. Ignore it. Handling it would require + re-calling the appropriate create_function() func with 0 + for all xAbc args so that s would be finalized. */; + } +error_cleanup: + JSTR_RELEASE(jFuncName, zFuncName); + /* on create_function() error, s will be destroyed via create_function() */ + return (jint)rc; +} + +JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, + jint nArg, jint eTextRep, jobject jFunctor){ + return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); +} + +/* sqlite3_db_config() for (int,const char *) */ +JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( + JENV_CSELF, jobject jDb, jint op, jstring jStr +){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc; + char *zStr; + + switch( (ps && jStr) ? op : 0 ){ + case SQLITE_DBCONFIG_MAINDBNAME: + zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); + if( zStr ){ + rc = sqlite3_db_config(ps->pDb, (int)op, zStr); + if( rc ){ + sqlite3_free( zStr ); + }else{ + sqlite3_free( ps->zMainDbName ); + ps->zMainDbName = zStr; + } + }else{ + rc = SQLITE_NOMEM; + } + break; + default: + rc = SQLITE_MISUSE; + } + return rc; +} + +FIXME_THREADING(perDb) +/* sqlite3_db_config() for (int,int*) */ +/* ACHTUNG: openjdk v19 creates a different mangled name for this + function than openjdk v8 does. */ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc; + switch( ps ? op : 0 ){ + case SQLITE_DBCONFIG_ENABLE_FKEY: + case SQLITE_DBCONFIG_ENABLE_TRIGGER: + case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case SQLITE_DBCONFIG_ENABLE_QPSG: + case SQLITE_DBCONFIG_TRIGGER_EQP: + case SQLITE_DBCONFIG_RESET_DATABASE: + case SQLITE_DBCONFIG_DEFENSIVE: + case SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case SQLITE_DBCONFIG_DQS_DML: + case SQLITE_DBCONFIG_DQS_DDL: + case SQLITE_DBCONFIG_ENABLE_VIEW: + case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case SQLITE_DBCONFIG_STMT_SCANSTATUS: + case SQLITE_DBCONFIG_REVERSE_SCANORDER: { + int pOut = 0; + rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut ); + if( 0==rc && jOut ){ + OutputPointer_set_Int32(env, jOut, pOut); + } + break; + } + default: + rc = SQLITE_MISUSE; + } + return (jint)rc; +} + +/** + This is a workaround for openjdk v19 (and possibly others) encoding + this function's name differently than JDK v8 does. If we do not + install both names for this function then Java will not be able to + find the function in both environments. +*/ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +){ + return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + env, jKlazz, jDb, op, onOff, jOut + ); +} + +JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + char *zDbName; + jstring jRv = 0; + int nStr = 0; + + if( !ps || !jDbName ){ + return 0; + } + zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr); + if( zDbName ){ + char const * zRv = sqlite3_db_filename(ps->pDb, zDbName); + sqlite3_free(zDbName); + if( zRv ){ + jRv = s3jni_utf8_to_jstring(jc, zRv, -1); + } + } + return jRv; +} + + +JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent, + jobject jOutHigh, jboolean reset ){ + int iCur = 0, iHigh = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCurrent, iCur); + OutputPointer_set_Int32(env, jOutHigh, iHigh); + } + return (jint)rc; +} + + +JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE; +} + +JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; + return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; +} + +JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ + return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) + /* We know these values to be plain ASCII, so pose no + MUTF-8 incompatibility */; +} + +JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + jstring rv = 0; + if( pStmt ){ + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + char * zSql = sqlite3_expanded_sql(pStmt); + OOM_CHECK(zSql); + if( zSql ){ + rv = s3jni_utf8_to_jstring(jc, zSql, -1); + sqlite3_free(zSql); + } + } + return rv; +} + +JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb, + jboolean onoff){ + int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0); + return rc ? JNI_TRUE : JNI_FALSE; +} + +JDECL(jint,1initialize)(JENV_CSELF){ + return sqlite3_initialize(); +} + +JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ + int rc = 0; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + if( pStmt ){ + rc = sqlite3_finalize(pStmt); + NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt); + } + return rc; +} + + +JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ + return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); +} + +//! Pre-open() code common to sqlite3_open(_v2)(). +static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, + jstring jDbName, char **zDbName, + S3JniDb ** ps, jobject *jDb){ + *jc = S3JniGlobal_env_cache(env); + if(!*jc) return SQLITE_NOMEM; + *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; + if(jDbName && !*zDbName) return SQLITE_NOMEM; + *jDb = new_sqlite3_wrapper(env, 0); + if( !*jDb ){ + sqlite3_free(*zDbName); + *zDbName = 0; + return SQLITE_NOMEM; + } + *ps = S3JniDb_alloc(env, 0, *jDb); +#if S3JNI_ENABLE_AUTOEXT + if(*ps){ + assert(!S3JniGlobal.autoExt.psOpening); + S3JniGlobal.autoExt.psOpening = *ps; + } +#endif + //MARKER(("pre-open ps@%p\n", *ps)); + return *ps ? 0 : SQLITE_NOMEM; +} + +/** + Post-open() code common to both the sqlite3_open() and + sqlite3_open_v2() bindings. ps->jDb must be the + org.sqlite.jni.sqlite3 object which will hold the db's native + pointer. theRc must be the result code of the open() op. If + *ppDb is NULL then ps is set aside and its state cleared, + else ps is associated with *ppDb. If *ppDb is not NULL then + ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). + + Returns theRc. +*/ +static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, + sqlite3 **ppDb, jobject jOut, int theRc){ + //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); +#if S3JNI_ENABLE_AUTOEXT + assert( S3JniGlobal.autoExt.pHead ? ps!=S3JniGlobal.autoExt.psOpening : 1 ); + S3JniGlobal.autoExt.psOpening = 0; +#endif + if(*ppDb){ + assert(ps->jDb); +#if S3JNI_ENABLE_AUTOEXT + //MARKER(("*autoExt.pHead=%p, ppDb=%p, ps->pDb=%p\n", S3JniGlobal.autoExt.pHead, *ppDb, ps->pDb)); + // invalid when an autoext triggers another open(): + // assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); +#endif + ps->pDb = *ppDb; + NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); + }else{ + S3JniDb_set_aside(ps); + ps = 0; + } + OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); + return theRc; +} + +JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ + sqlite3 * pOut = 0; + char *zName = 0; + jobject jDb = 0; + S3JniDb * ps = 0; + S3JniEnvCache * jc = 0; + S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + if( 0==rc ){ + rc = sqlite3_open(zName, &pOut); + //MARKER(("env=%p, *env=%p\n", env, *env)); + //MARKER(("open() ps@%p db@%p\n", ps, pOut)); + rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + assert(rc==0 ? pOut!=0 : 1); + sqlite3_free(zName); + } + S3JniGlobal.autoExt.psOpening = prevOpening; + return (jint)rc; +} + +JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, + jobject jOut, jint flags, jstring strVfs){ + sqlite3 * pOut = 0; + char *zName = 0; + jobject jDb = 0; + S3JniDb * ps = 0; + S3JniEnvCache * jc = 0; + char *zVfs = 0; + S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + if( 0==rc && strVfs ){ + zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); + if( !zVfs ){ + rc = SQLITE_NOMEM; + } + } + if( 0==rc ){ + rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + } + //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut)); + /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", + zName, zVfs, pOut, (int)flags, nrc));*/ + rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + assert(rc==0 ? pOut!=0 : 1); + sqlite3_free(zName); + sqlite3_free(zVfs); + S3JniGlobal.autoExt.psOpening = prevOpening; + return (jint)rc; +} + +/* Proxy for the sqlite3_prepare[_v2/3]() family. */ +static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self, + jobject jDb, jbyteArray baSql, + jint nMax, jint prepFlags, + jobject jOutStmt, jobject outTail){ + sqlite3_stmt * pStmt = 0; + jobject jStmt = 0; + const char * zTail = 0; + jbyte * const pBuf = JBA_TOC(baSql); + int rc = SQLITE_ERROR; + assert(prepVersion==1 || prepVersion==2 || prepVersion==3); + if( !pBuf ){ + rc = baSql ? SQLITE_MISUSE : SQLITE_NOMEM; + goto end; + } + jStmt = new_sqlite3_stmt_wrapper(env, 0); + if( !jStmt ){ + rc = SQLITE_NOMEM; + goto end; + } + switch( prepVersion ){ + case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jDb), (const char *)pBuf, + (int)nMax, &pStmt, &zTail); + break; + case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jDb), (const char *)pBuf, + (int)nMax, &pStmt, &zTail); + break; + case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jDb), (const char *)pBuf, + (int)nMax, (unsigned int)prepFlags, + &pStmt, &zTail); + break; + default: + assert(0 && "Invalid prepare() version"); + } +end: + JBA_RELEASE(baSql,pBuf); + if( 0==rc ){ + if( 0!=outTail ){ + /* Noting that pBuf is deallocated now but its address is all we need. */ + assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1); + assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); + OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); + } + if( pStmt ){ + NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); + }else{ + /* Happens for comments and whitespace */ + UNREF_L(jStmt); + jStmt = 0; + } + }else{ + UNREF_L(jStmt); + jStmt = 0; + } +#if 0 + if( 0!=rc ){ + MARKER(("prepare rc = %d\n", rc)); + } +#endif + OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); + return (jint)rc; +} +JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(1, env, self, jDb, baSql, nMax, 0, + jOutStmt, outTail); +} +JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(2, env, self, jDb, baSql, nMax, 0, + jOutStmt, outTail); +} +JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(3, env, self, jDb, baSql, nMax, + prepFlags, jOutStmt, outTail); +} + + +static int s3jni_progress_handler_impl(void *pP){ + S3JniDb * const ps = (S3JniDb *)pP; + JNIEnv * const env = ps->env; + int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, + ps->progress.midCallback); + IFTHREW{ + rc = s3jni_db_exception(env, ps, rc, + "sqlite3_progress_handler() callback threw"); + } + return rc; +} + +JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){ + S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jmethodID xCallback; + if( n<1 || !jProgress ){ + if(ps){ + UNREF_G(ps->progress.jObj); + memset(&ps->progress, 0, sizeof(ps->progress)); + } + sqlite3_progress_handler(ps->pDb, 0, 0, 0); + return; + } + if(!ps){ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return; + } + klazz = (*env)->GetObjectClass(env, jProgress); + xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on " + "ProgressHandler object."); + }else{ + UNREF_G(ps->progress.jObj); + ps->progress.midCallback = xCallback; + ps->progress.jObj = REF_G(jProgress); + sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); + } +} + + +JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ + int rc = 0; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + if( pStmt ){ + rc = sqlite3_reset(pStmt); + } + return rc; +} + +#if S3JNI_ENABLE_AUTOEXT +JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ + if(!S3JniGlobal.autoExt.isRunning){ + while( S3JniGlobal.autoExt.pHead ){ + S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); + } + } +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + +/* sqlite3_result_text/blob() and friends. */ +static void result_blob_text(int asBlob, int as64, + int eTextRep/*only for (asBlob=0)*/, + JNIEnv * const env, sqlite3_context *pCx, + jbyteArray jBa, jlong nMax){ + if(jBa){ + jbyte * const pBuf = JBA_TOC(jBa); + jsize nBa = (*env)->GetArrayLength(env, jBa); + if( nMax>=0 && nBa>(jsize)nMax ){ + nBa = (jsize)nMax; + /** + From the sqlite docs: + + > If the 3rd parameter to any of the sqlite3_result_text* + interfaces other than sqlite3_result_text64() is negative, + then SQLite computes the string length itself by searching + the 2nd parameter for the first zero character. + + Note that the text64() interfaces take an unsigned value for + the length, which Java does not support. This binding takes + the approach of passing on negative values to the C API, + which will, in turn fail with SQLITE_TOOBIG at some later + point (recall that the sqlite3_result_xyz() family do not + have result values). + */ + } + if(as64){ /* 64-bit... */ + static const jsize nLimit64 = + SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary!*/ + /* jsize is int32, not int64! */; + if(nBa > nLimit64){ + sqlite3_result_error_toobig(pCx); + }else if(asBlob){ + sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBa, + SQLITE_TRANSIENT); + }else{ /* text64... */ + if(encodingTypeIsValid(eTextRep)){ + sqlite3_result_text64(pCx, (const char *)pBuf, + (sqlite3_uint64)nBa, + SQLITE_TRANSIENT, eTextRep); + }else{ + sqlite3_result_error_code(pCx, SQLITE_FORMAT); + } + } + }else{ /* 32-bit... */ + static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE; + if(nBa > nLimit){ + sqlite3_result_error_toobig(pCx); + }else if(asBlob){ + sqlite3_result_blob(pCx, pBuf, (int)nBa, + SQLITE_TRANSIENT); + }else{ + switch(eTextRep){ + case SQLITE_UTF8: + sqlite3_result_text(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16: + sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16LE: + sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16BE: + sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + } + } + JBA_RELEASE(jBa, pBuf); + } + }else{ + sqlite3_result_null(pCx); + } +} + +JDECL(void,1result_1blob)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ + return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1blob64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax){ + return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1double)(JENV_CSELF, jobject jpCx, jdouble v){ + sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v); +} + +JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, + int eTextRep){ + const char * zUnspecified = "Unspecified error."; + jsize const baLen = (*env)->GetArrayLength(env, baMsg); + jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL; + switch(pjBuf ? eTextRep : SQLITE_UTF8){ + case SQLITE_UTF8: { + const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified; + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); + break; + } + case SQLITE_UTF16: { + const void *zMsg = pjBuf + ? (const void *)pjBuf : (const void *)zUnspecified; + sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); + break; + } + default: + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), + "Invalid encoding argument passed " + "to sqlite3_result_error().", -1); + break; + } + JBA_RELEASE(baMsg,pjBuf); +} + +JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){ + sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR); +} + +JDECL(void,1result_1error_1nomem)(JENV_CSELF, jobject jpCx){ + sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1error_1toobig)(JENV_CSELF, jobject jpCx){ + sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1int)(JENV_CSELF, jobject jpCx, jint v){ + sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v); +} + +JDECL(void,1result_1int64)(JENV_CSELF, jobject jpCx, jlong v){ + sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +} + +JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ + if(v){ + ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v); + if(rjv){ + sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, RESULT_JAVA_VAL_STRING, + ResultJavaVal_finalizer); + }else{ + sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); + } + }else{ + sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); + } +} + +JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){ + sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ + return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax, + jint eTextRep){ + return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){ + sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){ + sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v); +} + +JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){ + return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +} + +JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ + return s3jni_commit_rollback_hook(0, env, jDb, jHook); +} + +/* sqlite3_set_authorizer() callback proxy. */ +static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, + const char*z2,const char*z3){ + S3JniDb * const ps = pState; + JNIEnv * const env = ps->env; + jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0; + jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; + jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; + jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; + S3JniHook const * const pHook = &ps->authHook; + int rc; + + assert( pHook->jObj ); + rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op, + s0, s1, s3, s3); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback"); + EXCEPTION_CLEAR; + } + UNREF_L(s0); + UNREF_L(s1); + UNREF_L(s2); + UNREF_L(s3); + return rc; +} + +JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniHook * const pHook = ps ? &ps->authHook : 0; + + if( !ps ) return SQLITE_MISUSE; + else if( !jHook ){ + S3JniHook_unref(env, pHook, 0); + return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); + }else{ + int rc = 0; + if( pHook->jObj ){ + if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){ + /* Same object - this is a no-op. */ + return 0; + } + S3JniHook_unref(env, pHook, 0); + } + pHook->jObj = REF_G(jHook); + pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); + pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, + "xAuth", + "(I" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;" + ")I"); + IFTHREW { + S3JniHook_unref(env, pHook, 0); + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Error setting up Java parts of authorizer hook."); + } + rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); + if( rc ) S3JniHook_unref(env, pHook, 0); + return rc; + } +} + + +JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ + sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), + (sqlite3_int64)rowId); +} + +FIXME_THREADING(nphCache) +JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset ){ + int iCur = 0, iHigh = 0; + int rc = sqlite3_status( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCurrent, iCur); + OutputPointer_set_Int32(env, jOutHigh, iHigh); + } + return (jint)rc; +} + +FIXME_THREADING(nphCache) +JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset ){ + sqlite3_int64 iCur = 0, iHigh = 0; + int rc = sqlite3_status64( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int64(env, jOutCurrent, iCur); + OutputPointer_set_Int64(env, jOutHigh, iHigh); + } + return (jint)rc; +} + +static int s3jni_strlike_glob(int isLike, JNIEnv *const env, + jbyteArray baG, jbyteArray baT, jint escLike){ + int rc = 0; + jbyte * const pG = JBA_TOC(baG); + jbyte * const pT = pG ? JBA_TOC(baT) : 0; + OOM_CHECK(pT); + + /* Note that we're relying on the byte arrays having been + NUL-terminated on the Java side. */ + rc = isLike + ? sqlite3_strlike((const char *)pG, (const char *)pT, + (unsigned int)escLike) + : sqlite3_strglob((const char *)pG, (const char *)pT); + JBA_RELEASE(baG, pG); + JBA_RELEASE(baT, pT); + return rc; +} + +JDECL(jint,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ + return s3jni_strlike_glob(0, env, baG, baT, 0); +} + +JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ + return s3jni_strlike_glob(1, env, baG, baT, escChar); +} + +JDECL(jint,1shutdown)(JENV_CSELF){ + S3JniGlobal_S3JniEnvCache_clear(); + /* Do not clear S3JniGlobal.jvm: it's legal to call + sqlite3_initialize() again to restart the lib. */ + return sqlite3_shutdown(); +} + +JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + jstring rv = 0; + if( pStmt ){ + const char * zSql = 0; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + zSql = sqlite3_sql(pStmt); + rv = s3jni_utf8_to_jstring(jc, zSql, -1); + OOM_CHECK(rv); + } + return rv; +} + +JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ + int rc = SQLITE_MISUSE; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); + if(pStmt){ + rc = sqlite3_step(pStmt); + } + return rc; +} + +static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ + S3JniDb * const ps = (S3JniDb *)pC; + JNIEnv * const env = ps->env; + jobject jX = NULL /* the tracer's X arg */; + jobject jP = NULL /* the tracer's P arg */; + jobject jPUnref = NULL /* potentially a local ref to jP */; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + int rc; + int createStmt = 0; + switch(traceflag){ + case SQLITE_TRACE_STMT: + jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); + if(!jX) return SQLITE_NOMEM; + /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/ + createStmt = 1; + break; + case SQLITE_TRACE_PROFILE: + jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1, + (jlong)*((sqlite3_int64*)pX)); + // hmm. ^^^ (*pX) really is zero. + // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); + if(!jX) return SQLITE_NOMEM; + createStmt = 1; + break; + case SQLITE_TRACE_ROW: + createStmt = 1; + break; + case SQLITE_TRACE_CLOSE: + jP = ps->jDb; + break; + default: + assert(!"cannot happen - unkown trace flag"); + return SQLITE_ERROR; + } + if( createStmt ){ + jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); + if(!jP){ + UNREF_L(jX); + return SQLITE_NOMEM; + } + } + assert(jP); + rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, + ps->trace.midCallback, + (jint)traceflag, jP, jX); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); + EXCEPTION_CLEAR; + rc = SQLITE_ERROR; + } + UNREF_L(jPUnref); + UNREF_L(jX); + return rc; +} + +JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + if( !traceMask || !jTracer ){ + if(ps){ + UNREF_G(ps->trace.jObj); + memset(&ps->trace, 0, sizeof(ps->trace)); + } + return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); + } + if(!ps) return SQLITE_NOMEM; + klazz = (*env)->GetObjectClass(env, jTracer); + ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", + "(ILjava/lang/Object;Ljava/lang/Object;)I"); + IFTHREW { + EXCEPTION_CLEAR; + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on Tracer object."); + } + ps->trace.jObj = REF_G(jTracer); + return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); +} + +static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, + const char *zTable, sqlite3_int64 nRowid){ + S3JniDb * const ps = pState; + JNIEnv * const env = ps->env; + /* ACHTUNG: this will break if zDb or zTable contain chars which are + different in MUTF-8 than UTF-8. That seems like a low risk, + but it's possible. */ + jstring jDbName; + jstring jTable; + jDbName = (*env)->NewStringUTF(env, zDb); + jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0; + IFTHREW { + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + }else{ + (*env)->CallVoidMethod(env, ps->updateHook.jObj, + ps->updateHook.midCallback, + (jint)opId, jDbName, jTable, (jlong)nRowid); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("update hook"); + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); + } + } + UNREF_L(jDbName); + UNREF_L(jTable); +} + + +JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * const pHook = &ps->updateHook; + if(!ps){ + s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); + return 0; + } + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return pOld; + } + if( !jHook ){ + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(pHook, 0, sizeof(S3JniHook)); + sqlite3_update_hook(ps->pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook", + "(ILjava/lang/String;Ljava/lang/String;J)V"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching callback on " + "update hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + + +JDECL(jbyteArray,1value_1blob)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const nLen = sqlite3_value_bytes(sv); + const jbyte * pBytes = sqlite3_value_blob(sv); + jbyteArray const jba = pBytes + ? (*env)->NewByteArray(env, (jsize)nLen) + : NULL; + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); + } + return jba; +} + + +JDECL(jdouble,1value_1double)(JENV_CSELF, jobject jpSVal){ + return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal)); +} + + +JDECL(jobject,1value_1dup)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal)); + return sv ? new_sqlite3_value_wrapper(env, sv) : 0; +} + +JDECL(void,1value_1free)(JENV_CSELF, jobject jpSVal){ + sqlite3_value_free(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jint,1value_1int)(JENV_CSELF, jobject jpSVal){ + return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){ + return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){ + ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING); + return rv ? rv->jObj : NULL; +} + +JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const n = sqlite3_value_bytes16(sv); + const void * const p = sqlite3_value_text16(sv); + return s3jni_text16_to_jstring(env, p, n); +} + +JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const n = sqlite3_value_bytes(sv); + const unsigned char * const p = sqlite3_value_text(sv); + return s3jni_new_jbyteArray(env, p, n); +} + +static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ + int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal)); + jbyteArray jba; + const jbyte * pBytes; + switch(mode){ + case SQLITE_UTF16: + pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal)); + break; + case SQLITE_UTF16LE: + pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal)); + break; + case SQLITE_UTF16BE: + pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal)); + break; + default: + assert(!"not possible"); + return NULL; + } + jba = pBytes + ? (*env)->NewByteArray(env, (jsize)nLen) + : NULL; + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); + } + return jba; +} + +JDECL(jbyteArray,1value_1text16)(JENV_CSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16, env, jpSVal); +} + +JDECL(jbyteArray,1value_1text16le)(JENV_CSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16LE, env, jpSVal); +} + +JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16BE, env, jpSVal); +} + +JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ + MARKER(("\nVarious bits of internal info:\n")); + puts("FTS5 is " +#ifdef SQLITE_ENABLE_FTS5 + "available" +#else + "unavailable" +#endif + "." + ); + puts("sizeofs:"); +#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) + SO(void*); + SO(S3JniEnvCache); + SO(S3JniHook); + SO(S3JniDb); + SO(S3JniClassNames); + printf("\t(^^^ %u NativePointerHolder subclasses)\n", + (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *))); + SO(S3JniGlobal); + SO(S3JniAutoExtension); + SO(S3JniUdf); + printf("Cache info:\n"); + printf("\tNativePointerHolder cache: %u misses, %u hits\n", + S3JniGlobal.metrics.nphCacheMisses, + S3JniGlobal.metrics.nphCacheHits); + printf("\tJNIEnv cache %u misses, %u hits\n", + S3JniGlobal.metrics.envCacheMisses, + S3JniGlobal.metrics.envCacheHits); + puts("Java-side UDF calls:"); +#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T) + UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); +#undef UDF + printf("xDestroy calls across all callback types: %u\n", + S3JniGlobal.metrics.nDestroy); +#undef SO +} + +//////////////////////////////////////////////////////////////////////// +// End of the sqlite3_... API bindings. Next up, FTS5... +//////////////////////////////////////////////////////////////////////// +#ifdef SQLITE_ENABLE_FTS5 + +/* Creates a verbose JNI Fts5 function name. */ +#define JFuncNameFtsXA(Suffix) \ + Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix +#define JFuncNameFtsApi(Suffix) \ + Java_org_sqlite_jni_fts5_1api_ ## Suffix +#define JFuncNameFtsTok(Suffix) \ + Java_org_sqlite_jni_fts5_tokenizer_ ## Suffix + +#define JDECLFtsXA(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsXA(Suffix) +#define JDECLFtsApi(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsApi(Suffix) +#define JDECLFtsTok(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsTok(Suffix) + +#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api) +#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer) +#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context) +#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer) +#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() + +/** + State for binding Java-side FTS5 auxiliary functions. +*/ +typedef struct { + JNIEnv * env; /* env registered from */; + jobject jObj /* functor instance */; + jclass klazz /* jObj's class */; + jobject jUserData /* 2nd arg to JNI binding of + xCreateFunction(), ostensibly the 3rd arg + to the lib-level xCreateFunction(), except + that we necessarily use that slot for a + Fts5JniAux instance. */; + char * zFuncName /* Only for error reporting and debug logging */; + jmethodID jmid /* callback member's method ID */; +} Fts5JniAux; + +static void Fts5JniAux_free(Fts5JniAux * const s){ + JNIEnv * const env = s->env; + if(env){ + /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ + s3jni_call_xDestroy(env, s->jObj, s->klazz); + UNREF_G(s->jObj); + UNREF_G(s->klazz); + UNREF_G(s->jUserData); + } + sqlite3_free(s->zFuncName); + sqlite3_free(s); +} + +static void Fts5JniAux_xDestroy(void *p){ + if(p) Fts5JniAux_free(p); +} + +static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ + Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux)); + if(s){ + const char * zSig = + "(Lorg/sqlite/jni/Fts5ExtensionApi;" + "Lorg/sqlite/jni/Fts5Context;" + "Lorg/sqlite/jni/sqlite3_context;" + "[Lorg/sqlite/jni/sqlite3_value;)V"; + memset(s, 0, sizeof(Fts5JniAux)); + s->env = env; + s->jObj = REF_G(jObj); + s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); + s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + Fts5JniAux_free(s); + s = 0; + } + } + return s; +} + +static inline Fts5ExtensionApi const * s3jni_ftsext(void){ + return &sFts5Api/*singleton from sqlite3.c*/; +} + +static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.Fts5Context, sv); +} +static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.fts5_api, sv); +} + +/** + Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton + instance, or NULL on OOM. +*/ +static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ + S3JniEnvCache * const row = S3JniGlobal_env_cache(env); + if( !row->jFtsExt ){ + row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi, + s3jni_ftsext()); + if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); + } + return row->jFtsExt; +} + +/* +** Return a pointer to the fts5_api instance for database connection +** db. If an error occurs, return NULL and leave an error in the +** database handle (accessible using sqlite3_errcode()/errmsg()). +*/ +static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ + fts5_api *pRet = 0; + sqlite3_stmt *pStmt = 0; + if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){ + sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL); + sqlite3_step(pStmt); + } + sqlite3_finalize(pStmt); + return pRet; +} + +JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jobject rv = 0; + if(!ps) return 0; + else if(ps->jFtsApi){ + rv = ps->jFtsApi; + }else{ + fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb); + if( pApi ){ + rv = new_fts5_api_wrapper(env, pApi); + ps->jFtsApi = rv ? REF_G(rv) : 0; + } + } + return rv; +} + + +JDECLFtsXA(jobject,getInstance)(JENV_CSELF){ + return s3jni_getFts5ExensionApi(env); +} + +JDECLFtsXA(jint,xColumnCount)(JENV_OSELF,jobject jCtx){ + Fts5ExtDecl; + return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); +} + +JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32){ + Fts5ExtDecl; + int n1 = 0; + int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); + if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1); + return rc; +} + +JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, + jobject jOut){ + Fts5ExtDecl; + const char *pz = 0; + int pn = 0; + int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, + &pz, &pn); + if( 0==rc ){ + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; + if( pz ){ + if( jstr ){ + OutputPointer_set_String(env, jOut, jstr); + UNREF_L(jstr)/*jOut has a reference*/; + }else{ + rc = SQLITE_NOMEM; + } + } + } + return (jint)rc; +} + +JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut64){ + Fts5ExtDecl; + sqlite3_int64 nOut = 0; + int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); + if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); + return (jint)rc; +} + +/** + Proxy for fts5_extension_function instances plugged in via + fts5_api::xCreateFunction(). +*/ +static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, + Fts5Context *pFts, + sqlite3_context *pCx, + int argc, + sqlite3_value **argv){ + Fts5JniAux * const pAux = pApi->xUserData(pFts); + JNIEnv *env; + jobject jpCx = 0; + jobjectArray jArgv = 0; + jobject jpFts = 0; + jobject jFXA; + int rc; + assert(pAux); + env = pAux->env; + jFXA = s3jni_getFts5ExensionApi(env); + if( !jFXA ) goto error_oom; + jpFts = new_Fts5Context_wrapper(env, pFts); + if(!jpFts) goto error_oom; + rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv); + if(rc) goto error_oom; + (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid, + jFXA, jpFts, jpCx, jArgv); + IFTHREW{ + EXCEPTION_CLEAR; + udf_report_exception(pCx, pAux->zFuncName, "xFunction"); + } + UNREF_L(jpFts); + UNREF_L(jpCx); + UNREF_L(jArgv); + return; +error_oom: + assert( !jArgv ); + assert( !jpCx ); + UNREF_L(jpFts); + sqlite3_result_error_nomem(pCx); + return; +} + +JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, + jobject jUserData, jobject jFunc){ + fts5_api * const pApi = PtrGet_fts5_api(jSelf); + int rc; + char const * zName; + Fts5JniAux * pAux; + assert(pApi); + zName = JSTR_TOC(jName); + if(!zName) return SQLITE_NOMEM; + pAux = Fts5JniAux_alloc(env, jFunc); + if( pAux ){ + rc = pApi->xCreateFunction(pApi, zName, pAux, + s3jni_fts5_extension_function, + Fts5JniAux_xDestroy); + }else{ + rc = SQLITE_NOMEM; + } + if( 0==rc ){ + pAux->jUserData = jUserData ? REF_G(jUserData) : 0; + pAux->zFuncName = sqlite3_mprintf("%s", zName) + /* OOM here is non-fatal. Ignore it. */; + } + JSTR_RELEASE(jName, zName); + return (jint)rc; +} + + +typedef struct s3jni_fts5AuxData s3jni_fts5AuxData; +struct s3jni_fts5AuxData { + JNIEnv *env; + jobject jObj; +}; + +static void s3jni_fts5AuxData_xDestroy(void *x){ + if(x){ + s3jni_fts5AuxData * const p = x; + if(p->jObj){ + JNIEnv *env = p->env; + s3jni_call_xDestroy(env, p->jObj, 0); + UNREF_G(p->jObj); + } + sqlite3_free(x); + } +} + +JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ + Fts5ExtDecl; + jobject rv = 0; + s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); + if(pAux){ + if(bClear){ + if( pAux->jObj ){ + rv = REF_L(pAux->jObj); + UNREF_G(pAux->jObj); + } + /* Note that we do not call xDestroy() in this case. */ + sqlite3_free(pAux); + }else{ + rv = pAux->jObj; + } + } + return rv; +} + +JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, + jobject jOutCol, jobject jOutOff){ + Fts5ExtDecl; + int n1 = 0, n2 = 2, n3 = 0; + int const rc = fext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutPhrase, n1); + OutputPointer_set_Int32(env, jOutCol, n2); + OutputPointer_set_Int32(env, jOutOff, n3); + } + return rc; +} + +JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){ + Fts5ExtDecl; + int nOut = 0; + int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); + if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut); + return (jint)rc; +} + +JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){ + Fts5ExtDecl; + return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); +} + +/** + Initializes jc->jPhraseIter if it needed it. +*/ +static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc, + jobject jIter){ + if(!jc->jPhraseIter.klazz){ + jclass klazz = (*env)->GetObjectClass(env, jIter); + jc->jPhraseIter.klazz = REF_G(klazz); + jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J"); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); + jc->jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J"); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); + } +} + +/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ +static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const jc, + Fts5PhraseIter const * const pSrc, + jobject jIter){ + assert(jc->jPhraseIter.klazz); + (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidA, (jlong)pSrc->a); + EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field."); + (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b); + EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field."); +} + +/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ +static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnvCache const * const jc, + jobject jIter, Fts5PhraseIter * const pDest){ + assert(jc->jPhraseIter.klazz); + pDest->a = + (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidA); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); + pDest->b = + (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); +} + +JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, + jobject jIter, jobject jOutCol, + jobject jOutOff){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int rc, iCol = 0, iOff = 0; + s3jni_phraseIter_init(env, jc, jIter); + rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase, + &iter, &iCol, &iOff); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutOff, iOff); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + } + return rc; +} + +JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, + jobject jIter, jobject jOutCol){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int rc, iCol = 0; + s3jni_phraseIter_init(env, jc, jIter); + rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase, + &iter, &iCol); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCol, iCol); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + } + return rc; +} + +JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, + jobject jOutCol, jobject jOutOff){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int iCol = 0, iOff = 0; + if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; + s3jni_phraseIter_JToN(env, jc, jIter, &iter); + fext->xPhraseNext(PtrGet_Fts5Context(jCtx), + &iter, &iCol, &iOff); + OutputPointer_set_Int32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutOff, iOff); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); +} + +JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, + jobject jOutCol){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int iCol = 0; + if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; + s3jni_phraseIter_JToN(env, jc, jIter, &iter); + fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol); + OutputPointer_set_Int32(env, jOutCol, iCol); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); +} + + +JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ + Fts5ExtDecl; + return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); +} + +/** + State for use with xQueryPhrase() and xTokenize(). +*/ +struct s3jni_xQueryPhraseState { + JNIEnv *env; + Fts5ExtensionApi const * fext; + S3JniEnvCache const * jc; + jmethodID midCallback; + jobject jCallback; + jobject jFcx; + /* State for xTokenize() */ + struct { + const char * zPrev; + int nPrev; + jbyteArray jba; + } tok; +}; + +static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, + Fts5Context * pFcx, void *pData){ + /* TODO: confirm that the Fts5Context passed to this function is + guaranteed to be the same one passed to xQueryPhrase(). If it's + not, we'll have to create a new wrapper object on every call. */ + struct s3jni_xQueryPhraseState const * s = pData; + JNIEnv * const env = s->env; + int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, + s->jc->jFtsExt, s->jFcx); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback"); + EXCEPTION_CLEAR; + rc = SQLITE_ERROR; + } + return rc; +} + +JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, + jobject jCallback){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + struct s3jni_xQueryPhraseState s; + jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; + s.env = env; + s.jc = jc; + s.jCallback = jCallback; + s.jFcx = jFcx; + s.fext = fext; + s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", + "(Lorg.sqlite.jni.Fts5ExtensionApi;" + "Lorg.sqlite.jni.Fts5Context;)I"); + EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method."); + return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s, + s3jni_xQueryPhrase); +} + + +JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){ + Fts5ExtDecl; + sqlite3_int64 nOut = 0; + int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); + if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); + return (jint)rc; +} + +JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){ + Fts5ExtDecl; + return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); +} + +JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ + Fts5ExtDecl; + int rc; + s3jni_fts5AuxData * pAux; + pAux = sqlite3_malloc(sizeof(*pAux)); + if(!pAux){ + if(jAux){ + // Emulate how xSetAuxdata() behaves when it cannot alloc + // its auxdata wrapper. + s3jni_call_xDestroy(env, jAux, 0); + } + return SQLITE_NOMEM; + } + pAux->env = env; + pAux->jObj = REF_G(jAux); + rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, + s3jni_fts5AuxData_xDestroy); + return rc; +} + +/** + xToken() impl for xTokenize(). +*/ +static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, + int nZ, int iStart, int iEnd){ + int rc; + struct s3jni_xQueryPhraseState * const s = p; + JNIEnv * const env = s->env; + jbyteArray jba; + if( s->tok.zPrev == z && s->tok.nPrev == nZ ){ + jba = s->tok.jba; + }else{ + if(s->tok.jba){ + UNREF_L(s->tok.jba); + } + s->tok.zPrev = z; + s->tok.nPrev = nZ; + s->tok.jba = (*env)->NewByteArray(env, (jint)nZ); + if( !s->tok.jba ) return SQLITE_NOMEM; + jba = s->tok.jba; + (*env)->SetByteArrayRegion(env, jba, 0, (jint)nZ, (const jbyte*)z); + } + rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, + (jint)tFlags, jba, (jint)iStart, + (jint)iEnd); + return rc; +} + +/** + Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() +*/ +static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, + jint tokFlags, jobject jFcx, + jbyteArray jbaText, jobject jCallback){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + struct s3jni_xQueryPhraseState s; + int rc = 0; + jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0; + jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0; + jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; + memset(&s, 0, sizeof(s)); + s.env = env; + s.jc = jc; + s.jCallback = jCallback; + s.jFcx = jFcx; + s.fext = fext; + s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I"); + IFTHREW { + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + JBA_RELEASE(jbaText, pText); + return SQLITE_ERROR; + } + s.tok.jba = REF_L(jbaText); + s.tok.zPrev = (const char *)pText; + s.tok.nPrev = (int)nText; + if( zClassName == S3JniClassNames.Fts5ExtensionApi ){ + rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), + (const char *)pText, (int)nText, + &s, s3jni_xTokenize_xToken); + }else if( zClassName == S3JniClassNames.fts5_tokenizer ){ + fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf); + rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags, + (const char *)pText, (int)nText, + s3jni_xTokenize_xToken); + }else{ + (*env)->FatalError(env, "This cannot happen. Maintenance required."); + } + if(s.tok.jba){ + assert( s.tok.zPrev ); + UNREF_L(s.tok.jba); + } + JBA_RELEASE(jbaText, pText); + return (jint)rc; +} + +JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText, + jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5ExtensionApi, + 0, jFcx, jbaText, jCallback); +} + +JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags, + jbyteArray jbaText, jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5Tokenizer, + tokFlags, jFcx, jbaText, jCallback); +} + + +JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ + Fts5ExtDecl; + Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx)); + return pAux ? pAux->jUserData : 0; +} + +#endif /* SQLITE_ENABLE_FTS5 */ + +//////////////////////////////////////////////////////////////////////// +// End of the main API bindings. Start of SQLTester bits... +//////////////////////////////////////////////////////////////////////// + +#ifdef S3JNI_ENABLE_SQLTester +typedef struct SQLTesterJni SQLTesterJni; +struct SQLTesterJni { + sqlite3_int64 nDup; +}; +static SQLTesterJni SQLTester = { + 0 +}; + +static void SQLTester_dup_destructor(void*pToFree){ + u64 *p = (u64*)pToFree; + assert( p!=0 ); + p--; + assert( p[0]==0x2bbf4b7c ); + p[0] = 0; + p[1] = 0; + sqlite3_free(p); +} + +/* +** Implementation of +** +** dup(TEXT) +** +** This SQL function simply makes a copy of its text argument. But it +** returns the result using a custom destructor, in order to provide +** tests for the use of Mem.xDel() in the SQLite VDBE. +*/ +static void SQLTester_dup_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + u64 *pOut; + char *z; + int n = sqlite3_value_bytes(argv[0]); + SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context); + + ++p->nDup; + if( n>0 && (pOut = sqlite3_malloc( (n+16)&~7 ))!=0 ){ + pOut[0] = 0x2bbf4b7c; + z = (char*)&pOut[1]; + memcpy(z, sqlite3_value_text(argv[0]), n); + z[n] = 0; + sqlite3_result_text(context, z, n, SQLTester_dup_destructor); + } + return; +} + +/* +** Return the number of calls to the dup() SQL function since the +** SQLTester context was opened or since the last dup_count() call. +*/ +static void SQLTester_dup_count_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context); + sqlite3_result_int64(context, p->nDup); + p->nDup = 0; +} + +/* +** Return non-zero if string z matches glob pattern zGlob and zero if the +** pattern does not match. +** +** To repeat: +** +** zero == no match +** non-zero == match +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front, or a hexadecimal +** literal of the form 0x... +*/ +static int SQLTester_strnotglob(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 && SQLTester_strnotglob(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( SQLTester_strnotglob(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=='#' ){ + if( z[0]=='0' + && (z[1]=='x' || z[1]=='X') + && sqlite3Isxdigit(z[2]) + ){ + z += 3; + while( sqlite3Isxdigit(z[0]) ){ z++; } + }else{ + if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++; + if( !sqlite3Isdigit(z[0]) ) return 0; + z++; + while( sqlite3Isdigit(z[0]) ){ z++; } + } + }else{ + if( c!=(*(z++)) ) return 0; + } + } + return *z==0; +} + +JNIEXPORT jint JNICALL +Java_org_sqlite_jni_tester_SQLTester_strglob( + JENV_CSELF, jbyteArray baG, jbyteArray baT +){ + int rc = 0; + jbyte * const pG = JBA_TOC(baG); + jbyte * const pT = pG ? JBA_TOC(baT) : 0; + OOM_CHECK(pT); + + /* Note that we're relying on the byte arrays having been + NUL-terminated on the Java side. */ + rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT); + JBA_RELEASE(baG, pG); + JBA_RELEASE(baT, pT); + return rc; +} + + +static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ + sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester, + SQLTester_dup_func, 0, 0); + sqlite3_create_function(pDb, "dup_count", 0, SQLITE_UTF8, &SQLTester, + SQLTester_dup_count_func, 0, 0); + return 0; +} + +JNIEXPORT void JNICALL +Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ + sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); +} + +#endif /* S3JNI_ENABLE_SQLTester */ +//////////////////////////////////////////////////////////////////////// +// End of SQLTester bindings. Start of lower-level bits. +//////////////////////////////////////////////////////////////////////// + + +/** + Uncaches the current JNIEnv from the S3JniGlobal state, clearing any + resources owned by that cache entry and making that slot available + for re-use. It is important that the Java-side decl of this + function be declared as synchronous. +*/ +JNIEXPORT jboolean JNICALL +Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ + return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; +} + +/** + Called during static init of the SQLite3Jni class to sync certain + compile-time constants to Java-space. + + This routine is part of the reason why we have to #include + sqlite3.c instead of sqlite3.h. +*/ +JNIEXPORT void JNICALL +Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ + enum JType { + JTYPE_INT, + JTYPE_BOOL + }; + typedef struct { + const char *zName; + enum JType jtype; + int value; + } ConfigFlagEntry; + const ConfigFlagEntry aLimits[] = { + {"SQLITE_ENABLE_FTS5", JTYPE_BOOL, +#ifdef SQLITE_ENABLE_FTS5 + 1 +#else + 0 +#endif + }, + {"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE}, + {"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH}, + {"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH}, + {"SQLITE_LIMIT_SQL_LENGTH", JTYPE_INT, SQLITE_LIMIT_SQL_LENGTH}, + {"SQLITE_MAX_SQL_LENGTH", JTYPE_INT, SQLITE_MAX_SQL_LENGTH}, + {"SQLITE_LIMIT_COLUMN", JTYPE_INT, SQLITE_LIMIT_COLUMN}, + {"SQLITE_MAX_COLUMN", JTYPE_INT, SQLITE_MAX_COLUMN}, + {"SQLITE_LIMIT_EXPR_DEPTH", JTYPE_INT, SQLITE_LIMIT_EXPR_DEPTH}, + {"SQLITE_MAX_EXPR_DEPTH", JTYPE_INT, SQLITE_MAX_EXPR_DEPTH}, + {"SQLITE_LIMIT_COMPOUND_SELECT", JTYPE_INT, SQLITE_LIMIT_COMPOUND_SELECT}, + {"SQLITE_MAX_COMPOUND_SELECT", JTYPE_INT, SQLITE_MAX_COMPOUND_SELECT}, + {"SQLITE_LIMIT_VDBE_OP", JTYPE_INT, SQLITE_LIMIT_VDBE_OP}, + {"SQLITE_MAX_VDBE_OP", JTYPE_INT, SQLITE_MAX_VDBE_OP}, + {"SQLITE_LIMIT_FUNCTION_ARG", JTYPE_INT, SQLITE_LIMIT_FUNCTION_ARG}, + {"SQLITE_MAX_FUNCTION_ARG", JTYPE_INT, SQLITE_MAX_FUNCTION_ARG}, + {"SQLITE_LIMIT_ATTACHED", JTYPE_INT, SQLITE_LIMIT_ATTACHED}, + {"SQLITE_MAX_ATTACHED", JTYPE_INT, SQLITE_MAX_ATTACHED}, + {"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_LIMIT_LIKE_PATTERN_LENGTH}, + {"SQLITE_MAX_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_MAX_LIKE_PATTERN_LENGTH}, + {"SQLITE_LIMIT_VARIABLE_NUMBER", JTYPE_INT, SQLITE_LIMIT_VARIABLE_NUMBER}, + {"SQLITE_MAX_VARIABLE_NUMBER", JTYPE_INT, SQLITE_MAX_VARIABLE_NUMBER}, + {"SQLITE_LIMIT_TRIGGER_DEPTH", JTYPE_INT, SQLITE_LIMIT_TRIGGER_DEPTH}, + {"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH}, + {"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS}, + {"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS}, + {0,0} + }; + jfieldID fieldId; + const ConfigFlagEntry * pConfFlag; + + memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); + if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){ + (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); + return; + } +#if 0 + /* Just for sanity checking... */ + (void)S3JniGlobal_env_cache(env); + if( !S3JniGlobal.envCache.aHead ){ + (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); + return; + } + assert( 1 == S3JniGlobal.metrics.envCacheMisses ); + assert( env == S3JniGlobal.envCache.aHead->env ); + assert( 0 != S3JniGlobal.envCache.aHead->g.cObj ); +#endif + + for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ + char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; + fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig); + EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); + //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); + assert(fieldId); + switch(pConfFlag->jtype){ + case JTYPE_INT: + (*env)->SetStaticIntField(env, jKlazz, fieldId, (jint)pConfFlag->value); + break; + case JTYPE_BOOL: + (*env)->SetStaticBooleanField(env, jKlazz, fieldId, + pConfFlag->value ? JNI_TRUE : JNI_FALSE); + break; + } + EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed."); + } +} diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h new file mode 100644 index 000000000..bcb55c4f1 --- /dev/null +++ b/ext/jni/src/c/sqlite3-jni.h @@ -0,0 +1,1989 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_SQLite3Jni */ + +#ifndef _Included_org_sqlite_jni_SQLite3Jni +#define _Included_org_sqlite_jni_SQLite3Jni +#ifdef __cplusplus +extern "C" { +#endif +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DENY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DENY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DELETE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DELETE 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INSERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INSERT 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_READ 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SELECT +#define org_sqlite_jni_SQLite3Jni_SQLITE_SELECT 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH +#define org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETACH +#define org_sqlite_jni_SQLite3Jni_SQLITE_DETACH 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE 30L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION 31L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE 33L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATIC 0LL +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -1LL +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TEXT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TEXT 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BLOB +#define org_sqlite_jni_SQLite3Jni_SQLITE_BLOB 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_NULL 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME 1000L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE 1001L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY 1002L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG 1007L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP 1008L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE 1009L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE 1010L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML 1013L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL 1014L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW 1015L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX 1019L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB 30L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT 34L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION 35L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT 36L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE 37L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES 38L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START 39L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER 40L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE 41L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE 42L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K 128L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND 512L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL 1024L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE 8192L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC 16384L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY 128L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX 32768L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX 65536L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE 131072L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE 262144L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE 33554432L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW 16777216L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB 512L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL 4096L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB 1024L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL 8192L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL 16384L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL 524288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK +#define org_sqlite_jni_SQLite3Jni_SQLITE_OK 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PERM +#define org_sqlite_jni_SQLite3Jni_SQLITE_PERM 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FULL 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL +#define org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY +#define org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG +#define org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH +#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_RANGE +#define org_sqlite_jni_SQLite3Jni_SQLITE_RANGE 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING +#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROW +#define org_sqlite_jni_SQLite3Jni_SQLITE_ROW 100L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DONE 101L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ 257L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY 513L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT 769L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ 266L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ 522L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE 778L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC 1034L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC 1290L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE 1546L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT 1802L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK 2058L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK 2314L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE 2570L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED 2826L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM 3082L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS 3338L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK 3850L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE 4106L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE 4362L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN 4618L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE 4874L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK 5130L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP 5386L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK 5642L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT 5898L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP 6154L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH 6410L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH 6666L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE 6922L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH 7178L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC 7434L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC 7690L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA 8202L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS 8458L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE 262L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB 518L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY 261L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT 517L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT 773L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR 270L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR 526L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH 782L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH 1038L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK 1550L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB 267L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE 523L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX 779L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY 264L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK 520L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK 776L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED 1032L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT 1288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY 1544L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK 516L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK 275L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK 531L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY 787L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION 1043L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL 1299L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY 1555L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER 1811L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE 2067L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB 2323L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID 2579L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED 2835L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE 3091L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL 283L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK 539L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX 284L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER +#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER 279L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY +#define org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED 99L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY 524288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS 2097152L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE 65L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB 66L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP 67L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE 68L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT 69L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL 71L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS 72L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT 73L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET 74L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FAIL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FAIL 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE 5L +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: init + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: uncacheJniEnv + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_auto_extension + * Signature: (Lorg/sqlite/jni/AutoExtension;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1auto_1extension + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_blob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1blob + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_double + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;ID)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1double + (JNIEnv *, jclass, jobject, jint, jdouble); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_int + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int + (JNIEnv *, jclass, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int64 + (JNIEnv *, jclass, jobject, jint, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_null + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1null + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_parameter_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_parameter_index + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1index + (JNIEnv *, jclass, jobject, jbyteArray); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_text + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_zeroblob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob + (JNIEnv *, jclass, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_zeroblob64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64 + (JNIEnv *, jclass, jobject, jint, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_busy_handler + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/BusyHandler;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1handler + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_busy_timeout + * Signature: (Lorg/sqlite/jni/sqlite3;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1timeout + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_cancel_auto_extension + * Signature: (Lorg/sqlite/jni/AutoExtension;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1cancel_1auto_1extension + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_changes + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_changes64 + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_clear_bindings + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1clear_1bindings + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_close + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_close_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close_1v2 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_blob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1blob + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_bytes + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_bytes16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes16 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_double + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)D + */ +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1double + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_int + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int64 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_database_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1database_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_origin_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1origin_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_table_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_text + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_type + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1type + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_value + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Lorg/sqlite/jni/sqlite3_value; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_collation_needed + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CollationNeeded;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1collation_1needed + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_context_db_handle + * Signature: (Lorg/sqlite/jni/sqlite3_context;)Lorg/sqlite/jni/sqlite3; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1context_1db_1handle + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_commit_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CommitHook;)Lorg/sqlite/jni/CommitHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1commit_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_compileoption_get + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1get + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_compileoption_used + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1used + (JNIEnv *, jclass, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_create_collation + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/Collation;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1collation + (JNIEnv *, jclass, jobject, jstring, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_create_function + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/SQLFunction;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1function + (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_data_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_filename + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename + (JNIEnv *, jclass, jobject, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2 + (JNIEnv *, jclass, jobject, jint, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2 + (JNIEnv *, jclass, jobject, jint, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_status + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1status + (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errcode + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errcode + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_expanded_sql + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1expanded_1sql + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_extended_errcode + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1errcode + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_extended_result_codes + * Signature: (Lorg/sqlite/jni/sqlite3;Z)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1result_1codes + (JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errmsg + * Signature: (Lorg/sqlite/jni/sqlite3;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errmsg + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errstr + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errstr + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_error_offset + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1error_1offset + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_finalize + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1finalize + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_initialize + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_last_insert_rowid + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1last_1insert_1rowid + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_libversion + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_libversion_number + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion_1number + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_open + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open + (JNIEnv *, jclass, jstring, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_open_v2 + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open_1v2 + (JNIEnv *, jclass, jstring, jobject, jint, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare + (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 + (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare_v3 + * Signature: (Lorg/sqlite/jni/sqlite3;[BIILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 + (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_progress_handler + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/ProgressHandler;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1progress_1handler + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_reset + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_reset_auto_extension + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset_1auto_1extension + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_double + * Signature: (Lorg/sqlite/jni/sqlite3_context;D)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1double + (JNIEnv *, jclass, jobject, jdouble); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_toobig + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1toobig + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_nomem + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1nomem + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_code + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1code + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_null + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1null + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_int + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;J)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int64 + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_java_object + * Signature: (Lorg/sqlite/jni/sqlite3_context;Ljava/lang/Object;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1java_1object + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_value + * Signature: (Lorg/sqlite/jni/sqlite3_context;Lorg/sqlite/jni/sqlite3_value;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1value + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_zeroblob + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_zeroblob64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob64 + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_blob + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_blob64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJ)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob64 + (JNIEnv *, jclass, jobject, jbyteArray, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_text + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_text64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 + (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status + * Signature: (ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status64 + * Signature: (ILorg/sqlite/jni/OutputPointer/Int64;Lorg/sqlite/jni/OutputPointer/Int64;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64 + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_rollback_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/RollbackHook;)Lorg/sqlite/jni/RollbackHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_set_authorizer + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/Authorizer;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1authorizer + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_set_last_insert_rowid + * Signature: (Lorg/sqlite/jni/sqlite3;J)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1last_1insert_1rowid + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sleep + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sleep + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sourceid + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sourceid + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sql + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sql + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_step + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1step + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_strglob + * Signature: ([B[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strglob + (JNIEnv *, jclass, jbyteArray, jbyteArray); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_strlike + * Signature: ([B[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strlike + (JNIEnv *, jclass, jbyteArray, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_threadsafe + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1threadsafe + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_total_changes + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_total_changes64 + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_trace_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/Tracer;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_update_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHook;)Lorg/sqlite/jni/UpdateHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1update_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_blob + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1blob + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_bytes + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_bytes16 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes16 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_double + * Signature: (Lorg/sqlite/jni/sqlite3_value;)D + */ +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1double + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_dupe + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Lorg/sqlite/jni/sqlite3_value; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1dupe + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_encoding + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1encoding + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_free + * Signature: (Lorg/sqlite/jni/sqlite3_value;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1free + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_int + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_java_object + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1object + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text_utf8 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text_1utf8 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16le + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16le + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16be + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16be + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_type + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1type + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_numeric_type + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1numeric_1type + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_nochange + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1nochange + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_frombind + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1frombind + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_subtype + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_shutdown + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_do_something_for_developer + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1for_1developer + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_Fts5ExtensionApi */ + +#ifndef _Included_org_sqlite_jni_Fts5ExtensionApi +#define _Included_org_sqlite_jni_Fts5ExtensionApi +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: getInstance + * Signature: ()Lorg/sqlite/jni/Fts5ExtensionApi; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_getInstance + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnCount + * Signature: (Lorg/sqlite/jni/Fts5Context;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnCount + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnSize + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnSize + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnText + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnText + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnTotalSize + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int64;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnTotalSize + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xGetAuxdata + * Signature: (Lorg/sqlite/jni/Fts5Context;Z)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xGetAuxdata + (JNIEnv *, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xInst + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInst + (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xInstCount + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInstCount + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseCount + * Signature: (Lorg/sqlite/jni/Fts5Context;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseFirst + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirst + (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseFirstColumn + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirstColumn + (JNIEnv *, jobject, jobject, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseNext + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNext + (JNIEnv *, jobject, jobject, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseNextColumn + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNextColumn + (JNIEnv *, jobject, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseSize + * Signature: (Lorg/sqlite/jni/Fts5Context;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseSize + (JNIEnv *, jobject, jobject, jint); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xQueryPhrase + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5ExtensionApi/xQueryPhraseCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xQueryPhrase + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xRowCount + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int64;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xRowid + * Signature: (Lorg/sqlite/jni/Fts5Context;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowid + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xSetAuxdata + * Signature: (Lorg/sqlite/jni/Fts5Context;Ljava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xTokenize + * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize + (JNIEnv *, jobject, jobject, jbyteArray, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xUserData + * Signature: (Lorg/sqlite/jni/Fts5Context;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xUserData + (JNIEnv *, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_fts5_api */ + +#ifndef _Included_org_sqlite_jni_fts5_api +#define _Included_org_sqlite_jni_fts5_api +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_fts5_api + * Method: getInstanceForDb + * Signature: (Lorg/sqlite/jni/sqlite3;)Lorg/sqlite/jni/fts5_api; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_fts5_api + * Method: xCreateFunction + * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5_extension_function;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction + (JNIEnv *, jobject, jstring, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_fts5_tokenizer */ + +#ifndef _Included_org_sqlite_jni_fts5_tokenizer +#define _Included_org_sqlite_jni_fts5_tokenizer +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_fts5_tokenizer + * Method: xTokenize + * Signature: (Lorg/sqlite/jni/Fts5Tokenizer;I[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize + (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_tester_SQLTester */ + +#ifndef _Included_org_sqlite_jni_tester_SQLTester +#define _Included_org_sqlite_jni_tester_SQLTester +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_tester_SQLTester + * Method: strglob + * Signature: ([B[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_tester_SQLTester_strglob + (JNIEnv *, jclass, jbyteArray, jbyteArray); + +/* + * Class: org_sqlite_jni_tester_SQLTester + * Method: installCustomExtensions + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/jni/src/org/sqlite/jni/Authorizer.java b/ext/jni/src/org/sqlite/jni/Authorizer.java new file mode 100644 index 000000000..114c27fc6 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Authorizer.java @@ -0,0 +1,32 @@ +/* +** 2023-08-05 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A callback for use with sqlite3_set_authorizer(). +*/ +public interface Authorizer { + /** + Must functions as described for the sqlite3_set_authorizer() + callback, with one caveat: the string values passed here were + initially (at the C level) encoded in standard UTF-8. If they + contained any constructs which are not compatible with MUTF-8, + these strings will not have the expected values. For further + details, see the documentation for the SQLite3Jni class. + + Must not throw. + */ + int xAuth(int opId, @Nullable String s1, @Nullable String s2, + @Nullable String s3, @Nullable String s4); +} diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java new file mode 100644 index 000000000..a2ab6a0f7 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java @@ -0,0 +1,31 @@ +/* +** 2023-08-05 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A callback for use with sqlite3_auto_extension(). +*/ +public interface AutoExtension { + /** + Must function as described for the sqlite3_auto_extension(), + with the caveat that the signature is more limited. + + As an exception (as it were) to the callbacks-must-not-throw + rule, AutoExtensions may do so and the exception's error message + will be set as the db's error string. + + Results are undefined if db is closed by an auto-extension. + */ + int xEntryPoint(sqlite3 db); +} diff --git a/ext/jni/src/org/sqlite/jni/BusyHandler.java b/ext/jni/src/org/sqlite/jni/BusyHandler.java new file mode 100644 index 000000000..8ce729c90 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/BusyHandler.java @@ -0,0 +1,45 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_busy_handler(). +*/ +public abstract class BusyHandler { + /** + Must function as documented for the sqlite3_busy_handler() + callback argument, minus the (void*) argument the C-level + function requires. + + Any exceptions thrown by this callback are suppressed in order to + retain the C-style API semantics of the JNI bindings. + */ + public abstract int xCallback(int n); + + /** + Optionally override to perform any cleanup when this busy + handler is destroyed. It is destroyed when: + + - The associated db is passed to sqlite3_close() or + sqlite3_close_v2(). + + - sqlite3_busy_handler() is called to replace the handler, + whether it's passed a null handler or any other instance of + this class. + + - sqlite3_busy_timeout() is called, which implicitly installs + a busy handler. + */ + public void xDestroy(){} +} diff --git a/ext/jni/src/org/sqlite/jni/Collation.java b/ext/jni/src/org/sqlite/jni/Collation.java new file mode 100644 index 000000000..a05b8ef9e --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Collation.java @@ -0,0 +1,28 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** +*/ +public abstract class Collation { + /** + Must compare the given byte arrays using memcmp() semantics. + */ + public abstract int xCompare(byte[] lhs, byte[] rhs); + /** + Called by SQLite when the collation is destroyed. If a Collation + requires custom cleanup, override this method. + */ + public void xDestroy() {} +} diff --git a/ext/jni/src/org/sqlite/jni/CollationNeeded.java b/ext/jni/src/org/sqlite/jni/CollationNeeded.java new file mode 100644 index 000000000..85214a1d2 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/CollationNeeded.java @@ -0,0 +1,28 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_collation_needed(). +*/ +public interface CollationNeeded { + /** + Has the same semantics as the C-level sqlite3_create_collation() + callback. + + If it throws, the exception message is passed on to the db and + the exception is suppressed. + */ + int xCollationNeeded(sqlite3 db, int eTextRep, String collationName); +} diff --git a/ext/jni/src/org/sqlite/jni/CommitHook.java b/ext/jni/src/org/sqlite/jni/CommitHook.java new file mode 100644 index 000000000..eaa75a004 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/CommitHook.java @@ -0,0 +1,25 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_commit_hook(). +*/ +public interface CommitHook { + /** + Works as documented for the sqlite3_commit_hook() callback. + Must not throw. + */ + int xCommitHook(); +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5.java b/ext/jni/src/org/sqlite/jni/Fts5.java new file mode 100644 index 000000000..102cf575a --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5.java @@ -0,0 +1,38 @@ +/* +** 2023-08-05 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_api*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class Fts5 { + /* Not used */ + private Fts5(){} + + //! Callback type for use with xTokenize() variants + public static interface xTokenizeCallback { + int xToken(int tFlags, byte txt[], int iStart, int iEnd); + } + + public static final int FTS5_TOKENIZE_QUERY = 0x0001; + public static final int FTS5_TOKENIZE_PREFIX = 0x0002; + public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004; + public static final int FTS5_TOKENIZE_AUX = 0x0008; + public static final int FTS5_TOKEN_COLOCATED = 0x0001; +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Context.java b/ext/jni/src/org/sqlite/jni/Fts5Context.java new file mode 100644 index 000000000..e78f67d55 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Context.java @@ -0,0 +1,23 @@ +/* +** 2023-08-04 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for communicating C-level (Fts5Context*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class Fts5Context extends NativePointerHolder { +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java new file mode 100644 index 000000000..ac041e300 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -0,0 +1,86 @@ +/* +** 2023-08-04 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; +import java.nio.charset.StandardCharsets; + +/** + ALMOST COMPLETELY UNTESTED. + + FAR FROM COMPLETE and the feasibility of binding this to Java + is still undetermined. This might be removed. +*/ +public final class Fts5ExtensionApi extends NativePointerHolder { + //! Only called from JNI + private Fts5ExtensionApi(){} + private int iVersion = 2; + + /* Callback type for used by xQueryPhrase(). */ + public static interface xQueryPhraseCallback { + int xCallback(Fts5ExtensionApi fapi, Fts5Context cx); + } + + /** + Returns the singleton instance of this class. + */ + public static synchronized native Fts5ExtensionApi getInstance(); + + public synchronized native int xColumnCount(@NotNull Fts5Context fcx); + public synchronized native int xColumnSize(@NotNull Fts5Context cx, int iCol, + @NotNull OutputPointer.Int32 pnToken); + public synchronized native int xColumnText(@NotNull Fts5Context cx, int iCol, + @NotNull OutputPointer.String txt); + public synchronized native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol, + @NotNull OutputPointer.Int64 pnToken); + public synchronized native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt); + public synchronized native int xInst(@NotNull Fts5Context cx, int iIdx, + @NotNull OutputPointer.Int32 piPhrase, + @NotNull OutputPointer.Int32 piCol, + @NotNull OutputPointer.Int32 piOff); + public synchronized native int xInstCount(@NotNull Fts5Context fcx, + @NotNull OutputPointer.Int32 pnInst); + public synchronized native int xPhraseCount(@NotNull Fts5Context fcx); + public synchronized native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol, + @NotNull OutputPointer.Int32 iOff); + public synchronized native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol); + public synchronized native void xPhraseNext(@NotNull Fts5Context cx, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol, + @NotNull OutputPointer.Int32 iOff); + public synchronized native void xPhraseNextColumn(@NotNull Fts5Context cx, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol); + public synchronized native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase); + public synchronized native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase, + @NotNull xQueryPhraseCallback callback); + public synchronized native int xRowCount(@NotNull Fts5Context fcx, + @NotNull OutputPointer.Int64 nRow); + public synchronized native long xRowid(@NotNull Fts5Context cx); + /* Note that the JNI binding lacks the C version's xDelete() + callback argument. Instead, if pAux has an xDestroy() method, it + is called if the FTS5 API finalizes the aux state (including if + allocation of storage for the auxdata fails). Any reference to + pAux held by the JNI layer will be relinquished regardless of + whether pAux has an xDestroy() method. */ + public synchronized native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux); + public synchronized native int xTokenize(@NotNull Fts5Context cx, @NotNull byte pText[], + @NotNull Fts5.xTokenizeCallback callback); + + public synchronized native Object xUserData(Fts5Context cx); + //^^^ returns the pointer passed as the 3rd arg to the C-level + // fts5_api::xCreateFunction. +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Function.java b/ext/jni/src/org/sqlite/jni/Fts5Function.java new file mode 100644 index 000000000..463ec034f --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Function.java @@ -0,0 +1,27 @@ +/* +** 2023-08-04 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Fts5Function is used in conjunction with the + sqlite3_create_fts_function() JNI-bound API to give that native code + access to the callback functions needed in order to implement + FTS5 auxiliary functions in Java. +*/ +public abstract class Fts5Function { + + public abstract void xFunction(Fts5ExtensionApi pApi, Fts5Context pFts, + sqlite3_context pCtx, sqlite3_value argv[]); + public void xDestroy() {} +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java b/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java new file mode 100644 index 000000000..eb4e05fdf --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java @@ -0,0 +1,24 @@ +/* +** 2023-08-04 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for C-level Fts5PhraseIter. They are only modified and + inspected by native-level code. +*/ +public final class Fts5PhraseIter extends NativePointerHolder { + //! Updated and used only by native code. + private long a; + private long b; +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java b/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java new file mode 100644 index 000000000..0d266a13d --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java @@ -0,0 +1,30 @@ +/* +** 2023-08-05x +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (Fts5Tokenizer*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. + + At the C level, the Fts5Tokenizer type is essentially a void + pointer used specifically for tokenizers. +*/ +public final class Fts5Tokenizer extends NativePointerHolder { + //! Only called from JNI. + private Fts5Tokenizer(){} +} diff --git a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java new file mode 100644 index 000000000..afe2618a0 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java @@ -0,0 +1,33 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A helper for passing pointers between JNI C code and Java, in + particular for output pointers of high-level object types in the + sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**). This is + intended to be subclassed and the ContextType is intended to be the + class which is doing the subclassing. The intent of the ContextType + is strictly to provide some level of type safety by avoiding that + NativePointerHolder is not inadvertently passed to an incompatible + function signature. + + These objects do not _own_ the pointer they refer to. They are + intended simply to communicate that pointer between C and Java. +*/ +public class NativePointerHolder { + //! Only set from JNI, where access permissions don't matter. + private long nativePointer = 0; + public final long getNativePointer(){ return nativePointer; } +} diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java new file mode 100644 index 000000000..82a90c918 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -0,0 +1,165 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Helper classes for handling JNI output pointers. + + We do not use a generic OutputPointer because working with those + from the native JNI code is unduly quirky due to a lack of + autoboxing at that level. + + The usage is similar for all of thes types: + + ``` + OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + assert( null==out.get() ); + int rc = sqlite3_open(":memory:", out); + if( 0!=rc ) ... error; + assert( null!=out.get() ); + sqlite3 db = out.take(); + assert( null==out.get() ); + ``` + + With the minor exception that the primitive types permit direct + access to the object's value via the `value` property, whereas the + JNI-level opaque types do not permit client-level code to set that + property. +*/ +public final class OutputPointer { + + /** + Output pointer for use with routines, such as sqlite3_open(), + which return a database handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3 { + private org.sqlite.jni.sqlite3 value; + //! Initializes with a null value. + public sqlite3(){value = null;} + //! Sets the current value to null. + public void clear(){value = null;} + //! Returns the current value. + public final org.sqlite.jni.sqlite3 get(){return value;} + //! Equivalent to calling get() then clear(). + public final org.sqlite.jni.sqlite3 take(){ + final org.sqlite.jni.sqlite3 v = value; + value = null; + return v; + } + } + + /** + Output pointer for use with routines, such as sqlite3_prepare(), + which return a statement handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3_stmt { + private org.sqlite.jni.sqlite3_stmt value; + //! Initializes with a null value. + public sqlite3_stmt(){value = null;} + //! Sets the current value to null. + public void clear(){value = null;} + //! Returns the current value. + public final org.sqlite.jni.sqlite3_stmt get(){return value;} + //! Equivalent to calling get() then clear(). + public final org.sqlite.jni.sqlite3_stmt take(){ + final org.sqlite.jni.sqlite3_stmt v = value; + value = null; + return v; + } + } + + /** + Output pointer for use with native routines which return integers via + output pointers. + */ + public static final class Int32 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public int value; + //! Initializes with the value 0. + public Int32(){this(0);} + //! Initializes with the value v. + public Int32(int v){value = v;} + //! Returns the current value. + public final int get(){return value;} + //! Sets the current value to v. + public final void set(int v){value = v;} + } + + /** + Output pointer for use with native routines which return 64-bit integers + via output pointers. + */ + public static final class Int64 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public long value; + //! Initializes with the value 0. + public Int64(){this(0);} + //! Initializes with the value v. + public Int64(long v){value = v;} + //! Returns the current value. + public final long get(){return value;} + //! Sets the current value. + public final void set(long v){value = v;} + } + + /** + Output pointer for use with native routines which return strings via + output pointers. + */ + public static final class String { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public java.lang.String value; + //! Initializes with a null value. + public String(){this(null);} + //! Initializes with the value v. + public String(java.lang.String v){value = v;} + //! Returns the current value. + public final java.lang.String get(){return value;} + //! Sets the current value. + public final void set(java.lang.String v){value = v;} + } + + /** + Output pointer for use with native routines which return byte + arrays via output pointers. + */ + public static final class ByteArray { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public byte[] value; + //! Initializes with the value null. + public ByteArray(){this(null);} + //! Initializes with the value v. + public ByteArray(byte[] v){value = v;} + //! Returns the current value. + public final byte[] get(){return value;} + //! Sets the current value. + public final void set(byte[] v){value = v;} + } +} diff --git a/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/ext/jni/src/org/sqlite/jni/ProgressHandler.java new file mode 100644 index 000000000..c806eebca --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ProgressHandler.java @@ -0,0 +1,27 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_progress_handler(). +*/ +public interface ProgressHandler { + /** + Works as documented for the sqlite3_progress_handler() callback. + + If it throws, the exception message is passed on to the db and + the exception is suppressed. + */ + int xCallback(); +} diff --git a/ext/jni/src/org/sqlite/jni/ResultCode.java b/ext/jni/src/org/sqlite/jni/ResultCode.java new file mode 100644 index 000000000..0989bc744 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ResultCode.java @@ -0,0 +1,155 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + This enum contains all of the core and "extended" result codes used + by the sqlite3 library. It is provided not for use with the C-style + API (with which it won't work) but for higher-level code which may + find it useful to map SQLite result codes to human-readable names. +*/ +public enum ResultCode { + SQLITE_OK(SQLite3Jni.SQLITE_OK), + SQLITE_ERROR(SQLite3Jni.SQLITE_ERROR), + SQLITE_INTERNAL(SQLite3Jni.SQLITE_INTERNAL), + SQLITE_PERM(SQLite3Jni.SQLITE_PERM), + SQLITE_ABORT(SQLite3Jni.SQLITE_ABORT), + SQLITE_BUSY(SQLite3Jni.SQLITE_BUSY), + SQLITE_LOCKED(SQLite3Jni.SQLITE_LOCKED), + SQLITE_NOMEM(SQLite3Jni.SQLITE_NOMEM), + SQLITE_READONLY(SQLite3Jni.SQLITE_READONLY), + SQLITE_INTERRUPT(SQLite3Jni.SQLITE_INTERRUPT), + SQLITE_IOERR(SQLite3Jni.SQLITE_IOERR), + SQLITE_CORRUPT(SQLite3Jni.SQLITE_CORRUPT), + SQLITE_NOTFOUND(SQLite3Jni.SQLITE_NOTFOUND), + SQLITE_FULL(SQLite3Jni.SQLITE_FULL), + SQLITE_CANTOPEN(SQLite3Jni.SQLITE_CANTOPEN), + SQLITE_PROTOCOL(SQLite3Jni.SQLITE_PROTOCOL), + SQLITE_EMPTY(SQLite3Jni.SQLITE_EMPTY), + SQLITE_SCHEMA(SQLite3Jni.SQLITE_SCHEMA), + SQLITE_TOOBIG(SQLite3Jni.SQLITE_TOOBIG), + SQLITE_CONSTRAINT(SQLite3Jni.SQLITE_CONSTRAINT), + SQLITE_MISMATCH(SQLite3Jni.SQLITE_MISMATCH), + SQLITE_MISUSE(SQLite3Jni.SQLITE_MISUSE), + SQLITE_NOLFS(SQLite3Jni.SQLITE_NOLFS), + SQLITE_AUTH(SQLite3Jni.SQLITE_AUTH), + SQLITE_FORMAT(SQLite3Jni.SQLITE_FORMAT), + SQLITE_RANGE(SQLite3Jni.SQLITE_RANGE), + SQLITE_NOTADB(SQLite3Jni.SQLITE_NOTADB), + SQLITE_NOTICE(SQLite3Jni.SQLITE_NOTICE), + SQLITE_WARNING(SQLite3Jni.SQLITE_WARNING), + SQLITE_ROW(SQLite3Jni.SQLITE_ROW), + SQLITE_DONE(SQLite3Jni.SQLITE_DONE), + SQLITE_ERROR_MISSING_COLLSEQ(SQLite3Jni.SQLITE_ERROR_MISSING_COLLSEQ), + SQLITE_ERROR_RETRY(SQLite3Jni.SQLITE_ERROR_RETRY), + SQLITE_ERROR_SNAPSHOT(SQLite3Jni.SQLITE_ERROR_SNAPSHOT), + SQLITE_IOERR_READ(SQLite3Jni.SQLITE_IOERR_READ), + SQLITE_IOERR_SHORT_READ(SQLite3Jni.SQLITE_IOERR_SHORT_READ), + SQLITE_IOERR_WRITE(SQLite3Jni.SQLITE_IOERR_WRITE), + SQLITE_IOERR_FSYNC(SQLite3Jni.SQLITE_IOERR_FSYNC), + SQLITE_IOERR_DIR_FSYNC(SQLite3Jni.SQLITE_IOERR_DIR_FSYNC), + SQLITE_IOERR_TRUNCATE(SQLite3Jni.SQLITE_IOERR_TRUNCATE), + SQLITE_IOERR_FSTAT(SQLite3Jni.SQLITE_IOERR_FSTAT), + SQLITE_IOERR_UNLOCK(SQLite3Jni.SQLITE_IOERR_UNLOCK), + SQLITE_IOERR_RDLOCK(SQLite3Jni.SQLITE_IOERR_RDLOCK), + SQLITE_IOERR_DELETE(SQLite3Jni.SQLITE_IOERR_DELETE), + SQLITE_IOERR_BLOCKED(SQLite3Jni.SQLITE_IOERR_BLOCKED), + SQLITE_IOERR_NOMEM(SQLite3Jni.SQLITE_IOERR_NOMEM), + SQLITE_IOERR_ACCESS(SQLite3Jni.SQLITE_IOERR_ACCESS), + SQLITE_IOERR_CHECKRESERVEDLOCK(SQLite3Jni.SQLITE_IOERR_CHECKRESERVEDLOCK), + SQLITE_IOERR_LOCK(SQLite3Jni.SQLITE_IOERR_LOCK), + SQLITE_IOERR_CLOSE(SQLite3Jni.SQLITE_IOERR_CLOSE), + SQLITE_IOERR_DIR_CLOSE(SQLite3Jni.SQLITE_IOERR_DIR_CLOSE), + SQLITE_IOERR_SHMOPEN(SQLite3Jni.SQLITE_IOERR_SHMOPEN), + SQLITE_IOERR_SHMSIZE(SQLite3Jni.SQLITE_IOERR_SHMSIZE), + SQLITE_IOERR_SHMLOCK(SQLite3Jni.SQLITE_IOERR_SHMLOCK), + SQLITE_IOERR_SHMMAP(SQLite3Jni.SQLITE_IOERR_SHMMAP), + SQLITE_IOERR_SEEK(SQLite3Jni.SQLITE_IOERR_SEEK), + SQLITE_IOERR_DELETE_NOENT(SQLite3Jni.SQLITE_IOERR_DELETE_NOENT), + SQLITE_IOERR_MMAP(SQLite3Jni.SQLITE_IOERR_MMAP), + SQLITE_IOERR_GETTEMPPATH(SQLite3Jni.SQLITE_IOERR_GETTEMPPATH), + SQLITE_IOERR_CONVPATH(SQLite3Jni.SQLITE_IOERR_CONVPATH), + SQLITE_IOERR_VNODE(SQLite3Jni.SQLITE_IOERR_VNODE), + SQLITE_IOERR_AUTH(SQLite3Jni.SQLITE_IOERR_AUTH), + SQLITE_IOERR_BEGIN_ATOMIC(SQLite3Jni.SQLITE_IOERR_BEGIN_ATOMIC), + SQLITE_IOERR_COMMIT_ATOMIC(SQLite3Jni.SQLITE_IOERR_COMMIT_ATOMIC), + SQLITE_IOERR_ROLLBACK_ATOMIC(SQLite3Jni.SQLITE_IOERR_ROLLBACK_ATOMIC), + SQLITE_IOERR_DATA(SQLite3Jni.SQLITE_IOERR_DATA), + SQLITE_IOERR_CORRUPTFS(SQLite3Jni.SQLITE_IOERR_CORRUPTFS), + SQLITE_LOCKED_SHAREDCACHE(SQLite3Jni.SQLITE_LOCKED_SHAREDCACHE), + SQLITE_LOCKED_VTAB(SQLite3Jni.SQLITE_LOCKED_VTAB), + SQLITE_BUSY_RECOVERY(SQLite3Jni.SQLITE_BUSY_RECOVERY), + SQLITE_BUSY_SNAPSHOT(SQLite3Jni.SQLITE_BUSY_SNAPSHOT), + SQLITE_BUSY_TIMEOUT(SQLite3Jni.SQLITE_BUSY_TIMEOUT), + SQLITE_CANTOPEN_NOTEMPDIR(SQLite3Jni.SQLITE_CANTOPEN_NOTEMPDIR), + SQLITE_CANTOPEN_ISDIR(SQLite3Jni.SQLITE_CANTOPEN_ISDIR), + SQLITE_CANTOPEN_FULLPATH(SQLite3Jni.SQLITE_CANTOPEN_FULLPATH), + SQLITE_CANTOPEN_CONVPATH(SQLite3Jni.SQLITE_CANTOPEN_CONVPATH), + SQLITE_CANTOPEN_SYMLINK(SQLite3Jni.SQLITE_CANTOPEN_SYMLINK), + SQLITE_CORRUPT_VTAB(SQLite3Jni.SQLITE_CORRUPT_VTAB), + SQLITE_CORRUPT_SEQUENCE(SQLite3Jni.SQLITE_CORRUPT_SEQUENCE), + SQLITE_CORRUPT_INDEX(SQLite3Jni.SQLITE_CORRUPT_INDEX), + SQLITE_READONLY_RECOVERY(SQLite3Jni.SQLITE_READONLY_RECOVERY), + SQLITE_READONLY_CANTLOCK(SQLite3Jni.SQLITE_READONLY_CANTLOCK), + SQLITE_READONLY_ROLLBACK(SQLite3Jni.SQLITE_READONLY_ROLLBACK), + SQLITE_READONLY_DBMOVED(SQLite3Jni.SQLITE_READONLY_DBMOVED), + SQLITE_READONLY_CANTINIT(SQLite3Jni.SQLITE_READONLY_CANTINIT), + SQLITE_READONLY_DIRECTORY(SQLite3Jni.SQLITE_READONLY_DIRECTORY), + SQLITE_ABORT_ROLLBACK(SQLite3Jni.SQLITE_ABORT_ROLLBACK), + SQLITE_CONSTRAINT_CHECK(SQLite3Jni.SQLITE_CONSTRAINT_CHECK), + SQLITE_CONSTRAINT_COMMITHOOK(SQLite3Jni.SQLITE_CONSTRAINT_COMMITHOOK), + SQLITE_CONSTRAINT_FOREIGNKEY(SQLite3Jni.SQLITE_CONSTRAINT_FOREIGNKEY), + SQLITE_CONSTRAINT_FUNCTION(SQLite3Jni.SQLITE_CONSTRAINT_FUNCTION), + SQLITE_CONSTRAINT_NOTNULL(SQLite3Jni.SQLITE_CONSTRAINT_NOTNULL), + SQLITE_CONSTRAINT_PRIMARYKEY(SQLite3Jni.SQLITE_CONSTRAINT_PRIMARYKEY), + SQLITE_CONSTRAINT_TRIGGER(SQLite3Jni.SQLITE_CONSTRAINT_TRIGGER), + SQLITE_CONSTRAINT_UNIQUE(SQLite3Jni.SQLITE_CONSTRAINT_UNIQUE), + SQLITE_CONSTRAINT_VTAB(SQLite3Jni.SQLITE_CONSTRAINT_VTAB), + SQLITE_CONSTRAINT_ROWID(SQLite3Jni.SQLITE_CONSTRAINT_ROWID), + SQLITE_CONSTRAINT_PINNED(SQLite3Jni.SQLITE_CONSTRAINT_PINNED), + SQLITE_CONSTRAINT_DATATYPE(SQLite3Jni.SQLITE_CONSTRAINT_DATATYPE), + SQLITE_NOTICE_RECOVER_WAL(SQLite3Jni.SQLITE_NOTICE_RECOVER_WAL), + SQLITE_NOTICE_RECOVER_ROLLBACK(SQLite3Jni.SQLITE_NOTICE_RECOVER_ROLLBACK), + SQLITE_WARNING_AUTOINDEX(SQLite3Jni.SQLITE_WARNING_AUTOINDEX), + SQLITE_AUTH_USER(SQLite3Jni.SQLITE_AUTH_USER), + SQLITE_OK_LOAD_PERMANENTLY(SQLite3Jni.SQLITE_OK_LOAD_PERMANENTLY); + + public final int value; + + ResultCode(int rc){ + value = rc; + ResultCodeMap.set(rc, this); + } + + /** + Returns the entry from this enum for the given result code, or + null if no match is found. + */ + public static ResultCode getEntryForInt(int rc){ + return ResultCodeMap.get(rc); + } + + /** + Internal level of indirection required because we cannot initialize + static enum members in an enum before the enum constructor is + invoked. + */ + private static final class ResultCodeMap { + private static final java.util.Map i2e + = new java.util.HashMap<>(); + private static void set(int rc, ResultCode e){ i2e.put(rc, e); } + private static ResultCode get(int rc){ return i2e.get(rc); } + } + +} diff --git a/ext/jni/src/org/sqlite/jni/RollbackHook.java b/ext/jni/src/org/sqlite/jni/RollbackHook.java new file mode 100644 index 000000000..4ce3cb93e --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/RollbackHook.java @@ -0,0 +1,25 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_rollback_hook(). +*/ +public interface RollbackHook { + /** + Works as documented for the sqlite3_rollback_hook() callback. + Must not throw. + */ + void xRollbackHook(); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java new file mode 100644 index 000000000..21e5fe622 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -0,0 +1,172 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + SQLFunction is used in conjunction with the + sqlite3_create_function() JNI-bound API to give that native code + access to the callback functions needed in order to implement SQL + functions in Java. + + This class is not used by itself, but is a marker base class. The + three UDF types are modelled by the inner classes Scalar, + Aggregate, and Window. Most simply, clients may create + anonymous classes from those to implement UDFs. Clients are free to + create their own classes for use with UDFs, so long as they conform + to the public interfaces defined by those three classes. The JNI + layer only actively relies on the SQLFunction base class. +*/ +public abstract class SQLFunction { + + /** + PerContextState assists aggregate and window functions in + managinga their accumulator state across calls to the UDF's + callbacks. + + If a given aggregate or window function is called multiple times + in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., + then the clients need some way of knowing which call is which so + that they can map their state between their various UDF callbacks + and reset it via xFinal(). This class takes care of such + mappings. + + This class works by mapping + sqlite3_context.getAggregateContext() to a single piece of + state, of a client-defined type (the T part of this class), which + persists across a "matching set" of the UDF's callbacks. + + This class is a helper providing commonly-needed functionality - + it is not required for use with aggregate or window functions. + Client UDFs are free to perform such mappings using custom + approaches. The provided Aggregate and Window classes + use this. + */ + public static final class PerContextState { + private final java.util.Map> map + = new java.util.HashMap<>(); + + /** + Should be called from a UDF's xStep(), xValue(), and xInverse() + methods, passing it that method's first argument and an initial + value for the persistent state. If there is currently no + mapping for cx.getAggregateContext() within the map, one is + created using the given initial value, else the existing one is + used and the 2nd argument is ignored. It returns a + ValueHolder which can be used to modify that state directly + without requiring that the client update the underlying map's + entry. + + T must be of a type which can be legally stored as a value in + java.util.HashMap. + */ + public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + ValueHolder rc = map.get(cx.getAggregateContext()); + if(null == rc){ + map.put(cx.getAggregateContext(), rc = new ValueHolder<>(initialValue)); + } + return rc; + } + + /** + Should be called from a UDF's xFinal() method and passed that + method's first argument. This function removes the value + associated with cx.getAggregateContext() from the map and + returns it, returning null if no other UDF method has been + called to set up such a mapping. The latter condition will be + the case if a UDF is used in a statement which has no result + rows. + */ + public T takeAggregateState(sqlite3_context cx){ + final ValueHolder h = map.remove(cx.getAggregateContext()); + return null==h ? null : h.value; + } + } + + //! Subclass for creating scalar functions. + public static abstract class Scalar extends SQLFunction { + + //! As for the xFunc() argument of the C API's sqlite3_create_function() + public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. + */ + public void xDestroy() {} + } + + /** + SQLFunction Subclass for creating aggregate functions. Its T is + the data type of its "accumulator" state, an instance of which is + intended to be be managed using the getAggregateState() and + takeAggregateState() methods. + */ + public static abstract class Aggregate extends SQLFunction { + + //! As for the xStep() argument of the C API's sqlite3_create_function() + public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); + + //! As for the xFinal() argument of the C API's sqlite3_create_function() + public abstract void xFinal(sqlite3_context cx); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. + */ + public void xDestroy() {} + + //! Per-invocation state for the UDF. + private final PerContextState map = new PerContextState<>(); + + /** + To be called from the implementation's xStep() method, as well + as the xValue() and xInverse() methods of the Window + subclass, to fetch the current per-call UDF state. On the + first call to this method for any given sqlite3_context + argument, the context is set to the given initial value. On all other + calls, the 2nd argument is ignored. + + @see PerContextState#takeAggregateState() + */ + protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + return map.getAggregateState(cx, initialValue); + } + + /** + To be called from the implementation's xFinal() method to fetch + the final state of the UDF and remove its mapping. + + @see PerContextState#takeAggregateState() + */ + protected final T takeAggregateState(sqlite3_context cx){ + return map.takeAggregateState(cx); + } + } + + /** + An SQLFunction subclass for creating window functions. Note that + Window inherits from Aggregate and each instance is + required to implement the inherited abstract methods from that + class. See Aggregate for information on managing the UDF's + invocation-specific state. + */ + public static abstract class Window extends Aggregate { + + //! As for the xInverse() argument of the C API's sqlite3_create_window_function() + public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); + + //! As for the xValue() argument of the C API's sqlite3_create_window_function() + public abstract void xValue(sqlite3_context cx); + } +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java new file mode 100644 index 000000000..9b2a17650 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -0,0 +1,1645 @@ +/* +** 2023-07-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 declares JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; +import java.nio.charset.StandardCharsets; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; + +/** + This annotation is for flagging parameters which may legally be + null, noting that they may behave differently if passed null but + are prepared to expect null as a value. + + This annotation is solely for the reader's information. +*/ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PARAMETER) +@interface Nullable{} + +/** + This annotation is for flagging parameters which may not legally be + null. Note that the C-style API does _not_ throw any + NullPointerExceptions on its own because it has a no-throw policy + in order to retain its C-style semantics. + + This annotation is solely for the reader's information. No policy + is in place to programmatically ensure that NotNull is conformed to + in client code. +*/ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PARAMETER) +@interface NotNull{} + +/** + This class contains the entire sqlite3 JNI API binding. For + client-side use, a static import is recommended: + + ``` + import static org.sqlite.jni.SQLite3Jni.*; + ``` + + The C-side part can be found in sqlite3-jni.c. + + + Only functions which materially differ from their C counterparts + are documented here. The C documetation is otherwise applicable + here: + + https://sqlite.org/c3ref/intro.html + + A handful of Java-specific APIs have been added. + + + ****************************************************************** + *** Warning regarding Java's Modified UTF-8 vs standard UTF-8: *** + ****************************************************************** + + SQLite internally uses UTF-8 encoding, whereas Java natively uses + UTF-16. Java JNI has routines for converting to and from UTF-8, + _but_ JNI uses what its docs call modified UTF-8 (see links below) + Care must be taken when converting Java strings to or from standard + UTF-8 to ensure that the proper conversion is performed. In short, + Java's `String.getBytes(StandardCharsets.UTF_8)` performs the proper + conversion in Java, and there are no JNI C APIs for that conversion + (JNI's `NewStringUTF()` requires its input to be in MUTF-8). + + The known consequences and limitations this discrepancy places on + the SQLite3 JNI binding include: + + - Any functions which return client-side data from a database + take extra care to perform proper conversion, at the cost of + efficiency. + + - Functions which return database identifiers require those + identifiers to have identical representations in UTF-8 and + MUTF-8. They do not perform such conversions (A) because of the + much lower risk of an encoding discrepancy and (B) to avoid + significant extra code involved (see both the Java- and C-side + implementations of sqlite3_db_filename() for an example). Names + of databases, tables, columns, collations, and functions MUST NOT + contain characters which differ in MUTF-8 and UTF-8, or certain + APIs will mis-translate them on their way between languages + (possibly leading to a crash). + + - C functions which take C-style strings without a length argument + require special care when taking input from Java. In particular, + Java strings converted to byte arrays for encoding purposes are + not NUL-terminated, and conversion to a Java byte array must be + careful to add one. Functions which take a length do not require + this. Search the SQLite3Jni class for "\0" for many examples. + + - Similarly, C-side code which deals with strings which might not be + NUL-terminated (e.g. while tokenizing in FTS5-related code) cannot + use JNI's new-string functions to return them to Java because none + of those APIs take a string-length argument. Such cases must + return byte arrays instead of strings. + + Further reading: + + - https://stackoverflow.com/questions/57419723 + - https://stackoverflow.com/questions/7921016 + - https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/ + - https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode + - https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 + +*/ +public final class SQLite3Jni { + static { + System.loadLibrary("sqlite3-jni"); + } + //! Not used + private SQLite3Jni(){} + //! Called from static init code. + private static native void init(); + + /** + Each thread which uses the SQLite3 JNI APIs should call + uncacheJniEnv() when it is done with the library - either right + before it terminates or when it is finished using the SQLite API. + This will clean up any cached per-JNIEnv info. Calling into the + library again after that "should" re-initialize the cache on + demand, but that's untested. + + This call forcibly wipes out all cached information for the + current JNIEnv, a side-effect of which is that behavior is + undefined if any database objects are (A) still active at the + time it is called _and_ (B) calls are subsequently made into the + library with such a database. Doing so will, at best, lead to a + crash. Azt worst, it will lead to the db possibly misbehaving + because some of its Java-bound state has been cleared. There is + no immediate harm in (A) so long as condition (B) is not met. + This process does _not_ actually close any databases or finalize + any prepared statements. For proper library behavior, and to + avoid C-side leaks, be sure to close them before calling this + function. + + Calling this from the main application thread is not strictly + required but is "polite." Additional threads must call this + before ending or they will leak cache entries in the C heap, + which in turn may keep numerous Java-side global references + active. + + This routine returns false without side effects if the current + JNIEnv is not cached, else returns true, but this information is + primarily for testing of the JNI bindings and is not information + which client-level code should use to make any informed + decisions. + */ + public static synchronized native boolean uncacheJniEnv(); + + ////////////////////////////////////////////////////////////////////// + // Maintenance reminder: please keep the sqlite3_.... functions + // alphabetized. The SQLITE_... values. on the other hand, are + // grouped by category. + + + /** + Functions almost as documented for the C API, with these + exceptions: + + - The callback interface is more limited because of + cross-language differences. Specifically, auto-extensions do + not have access to the sqlite3_api object which native + auto-extensions do. + + - If an auto-extension opens a db, thereby triggering recursion + in the auto-extension handler, it will fail with a message + explaining that recursion is not permitted. + + - All of the other auto extension routines will fail without side + effects if invoked from within the execution of an + auto-extension. i.e. auto extensions can neither be added, + removed, nor cleared while one registered with this function is + running. Auto-extensions registered directly with the library + via C code, as opposed to indirectly via Java, do not have that + limitation. + + See the AutoExtension class docs for more information. + + Achtung: it is as yet unknown whether auto extensions registered + from one JNIEnv (thread) can be safely called from another. + */ + public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); + + public static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ + return (null == data) + ? sqlite3_bind_null(stmt, ndx) + : sqlite3_bind_blob(stmt, ndx, data, data.length); + } + + private static synchronized native int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n + ); + + public static synchronized native int sqlite3_bind_double( + @NotNull sqlite3_stmt stmt, int ndx, double v + ); + + public static synchronized native int sqlite3_bind_int( + @NotNull sqlite3_stmt stmt, int ndx, int v + ); + + public static synchronized native int sqlite3_bind_int64( + @NotNull sqlite3_stmt stmt, int ndx, long v + ); + + public static synchronized native int sqlite3_bind_null( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_bind_parameter_count( + @NotNull sqlite3_stmt stmt + ); + + + /** A level of indirection required to ensure that the input to the + C-level function of the same name is a NUL-terminated UTF-8 + string. */ + private static synchronized native int sqlite3_bind_parameter_index( + @NotNull sqlite3_stmt stmt, byte[] paramName + ); + + public static int sqlite3_bind_parameter_index( + @NotNull sqlite3_stmt stmt, @NotNull String paramName + ){ + final byte[] utf8 = (paramName+"\0").getBytes(StandardCharsets.UTF_8); + return sqlite3_bind_parameter_index(stmt, utf8); + } + + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data + ){ + if(null == data) return sqlite3_bind_null(stmt, ndx); + final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8); + return sqlite3_bind_text(stmt, ndx, utf8, utf8.length); + } + + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ + return (null == data) + ? sqlite3_bind_null(stmt, ndx) + : sqlite3_bind_text(stmt, ndx, data, data.length); + } + + /** + Works like the C-level sqlite3_bind_text() but (A) assumes + SQLITE_TRANSIENT for the final parameter and (B) behaves like + sqlite3_bind_null() if the data argument is null. + */ + private static synchronized native int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes + ); + + public static synchronized native int sqlite3_bind_zeroblob( + @NotNull sqlite3_stmt stmt, int ndx, int n + ); + + public static synchronized native int sqlite3_bind_zeroblob64( + @NotNull sqlite3_stmt stmt, int ndx, long n + ); + + /** + As for the C-level function of the same name, with a BusyHandler + instance in place of a callback function. Pass it a null handler + to clear the busy handler. Calling this multiple times with the + same object is a no-op on the second and subsequent calls. + */ + public static synchronized native int sqlite3_busy_handler( + @NotNull sqlite3 db, @Nullable BusyHandler handler + ); + + public static synchronized native int sqlite3_busy_timeout( + @NotNull sqlite3 db, int ms + ); + + /** + Works like the C API except that it returns false, without side + effects, if auto extensions are currently running. (The JNI-level + list of extensions cannot be manipulated while it is being traversed.) + */ + public static synchronized native boolean sqlite3_cancel_auto_extension( + @NotNull AutoExtension ax + ); + + public static synchronized native int sqlite3_changes( + @NotNull sqlite3 db + ); + + public static synchronized native long sqlite3_changes64( + @NotNull sqlite3 db + ); + + public static synchronized native int sqlite3_clear_bindings( + @NotNull sqlite3_stmt stmt + ); + + public static synchronized native int sqlite3_close( + @NotNull sqlite3 db + ); + + public static synchronized native int sqlite3_close_v2( + @NotNull sqlite3 db + ); + + public static synchronized native byte[] sqlite3_column_blob( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_bytes( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_bytes16( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_count( + @NotNull sqlite3_stmt stmt + ); + + public static synchronized native double sqlite3_column_double( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_int( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native long sqlite3_column_int64( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native String sqlite3_column_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native String sqlite3_column_database_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + Column counterpart of sqlite3_value_java_object(). + */ + public static Object sqlite3_column_java_object( + @NotNull sqlite3_stmt stmt, int ndx + ){ + Object rv = null; + sqlite3_value v = sqlite3_column_value(stmt, ndx); + if(null!=v){ + v = sqlite3_value_dupe(v) /* we need a "protected" value */; + if(null!=v){ + rv = sqlite3_value_java_object(v); + sqlite3_value_free(v); + } + } + return rv; + } + + /** + Column counterpart of sqlite3_value_java_casted(). + */ + @SuppressWarnings("unchecked") + public static T sqlite3_column_java_casted( + @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class type + ){ + final Object o = sqlite3_column_java_object(stmt, ndx); + return type.isInstance(o) ? (T)o : null; + } + + public static synchronized native String sqlite3_column_origin_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native String sqlite3_column_table_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + To extract _standard_ UTF-8, use sqlite3_column_text(). + This API includes no functions for working with Java's Modified + UTF-8. + */ + public static synchronized native String sqlite3_column_text16( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + Returns the given column's contents as UTF-8-encoded (not MUTF-8) text. + Use sqlite3_column_text16() to fetch the text + */ + public static synchronized native byte[] sqlite3_column_text( + @NotNull sqlite3_stmt stmt, int ndx + ); + + // The real utility of this function is questionable. + // /** + // Returns a Java value representation based on the value of + // sqlite_value_type(). For integer types it returns either Integer + // or Long, depending on whether the value will fit in an + // Integer. For floating-point values it always returns type Double. + + // If the column was bound using sqlite3_result_java_object() then + // that value, as an Object, is returned. + // */ + // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt, + // int ndx){ + // sqlite3_value v = sqlite3_column_value(stmt, ndx); + // Object rv = null; + // if(null == v) return v; + // v = sqlite3_value_dup(v)/*need a protected value*/; + // if(null == v) return v /* OOM error in C */; + // if(112/* 'p' */ == sqlite3_value_subtype(v)){ + // rv = sqlite3_value_java_object(v); + // }else{ + // switch(sqlite3_value_type(v)){ + // case SQLITE_INTEGER: { + // final long i = sqlite3_value_int64(v); + // rv = (i<=0x7fffffff && i>=-0x7fffffff-1) + // ? new Integer((int)i) : new Long(i); + // break; + // } + // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break; + // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break; + // case SQLITE_TEXT: rv = sqlite3_value_text(v); break; + // default: break; + // } + // } + // sqlite3_value_free(v); + // return rv; + // } + + public static synchronized native int sqlite3_column_type( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native sqlite3_value sqlite3_column_value( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + This functions like C's sqlite3_collation_needed16() because + Java's string type is compatible with that interface. + */ + public static synchronized native int sqlite3_collation_needed( + @NotNull sqlite3 db, @Nullable CollationNeeded callback + ); + + /** + Returns the db handle passed to sqlite3_open() or + sqlite3_open_v2(), as opposed to a new wrapper object. + */ + public static synchronized native sqlite3 sqlite3_context_db_handle( + @NotNull sqlite3_context cx + ); + + public static synchronized native CommitHook sqlite3_commit_hook( + @NotNull sqlite3 db, @Nullable CommitHook hook + ); + + public static native String sqlite3_compileoption_get( + int n + ); + + public static native boolean sqlite3_compileoption_used( + @NotNull String optName + ); + + public static synchronized native int sqlite3_create_collation( + @NotNull sqlite3 db, @NotNull String name, int eTextRep, + @NotNull Collation col + ); + + /** + The Java counterpart to the C-native sqlite3_create_function(), + sqlite3_create_function_v2(), and + sqlite3_create_window_function(). Which one it behaves like + depends on which methods the final argument implements. See + SQLFunction's inner classes (Scalar, Aggregate, and Window) + for details. + */ + public static synchronized native int sqlite3_create_function( + @NotNull sqlite3 db, @NotNull String functionName, + int nArg, int eTextRep, @NotNull SQLFunction func + ); + + public static synchronized native int sqlite3_data_count( + @NotNull sqlite3_stmt stmt + ); + + public static synchronized native String sqlite3_db_filename( + @NotNull sqlite3 db, @NotNull String dbName + ); + + /** + Overload for sqlite3_db_config() calls which take (int,int*) + variadic arguments. Returns SQLITE_MISUSE if op is not one of the + SQLITE_DBCONFIG_... options which uses this call form. + */ + public static synchronized native int sqlite3_db_config( + @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out + ); + + /** + Overload for sqlite3_db_config() calls which take a (const char*) + variadic argument. As of SQLite3 v3.43 the only such option is + SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not + SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be + extended in future versions. + */ + public static synchronized native int sqlite3_db_config( + @NotNull sqlite3 db, int op, @NotNull String val + ); + + public static synchronized native int sqlite3_db_status( + @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); + + public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); + + public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db); + + public static synchronized native boolean sqlite3_extended_result_codes( + @NotNull sqlite3 db, boolean onoff + ); + + public static synchronized native String sqlite3_errmsg(@NotNull sqlite3 db); + + public static synchronized native String sqlite3_errstr(int resultCode); + + /** + Note that the offset values assume UTF-8-encoded SQL. + */ + public static synchronized native int sqlite3_error_offset(@NotNull sqlite3 db); + + public static synchronized native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); + + public static synchronized native int sqlite3_initialize(); + + public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); + + public static synchronized native String sqlite3_libversion(); + + public static synchronized native int sqlite3_libversion_number(); + + /** + Works like its C counterpart and makes the native pointer of the + underling (sqlite3*) object available via + ppDb.getNativePointer(). That pointer is necessary for looking up + the JNI-side native, but clients need not pay it any + heed. Passing the object to sqlite3_close() or sqlite3_close_v2() + will clear that pointer mapping. + + Recall that even if opening fails, the output pointer might be + non-null. Any error message about the failure will be in that + object and it is up to the caller to sqlite3_close() that + db handle. + + Pedantic note: though any number of Java-level sqlite3 objects + may refer to/wrap a single C-level (sqlite3*), the JNI internals + take a reference to the object which is passed to sqlite3_open() + or sqlite3_open_v2() so that they have a predictible object to + pass to, e.g., the sqlite3_collation_needed() callback. + */ + public static synchronized native int sqlite3_open( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb + ); + + public static synchronized native int sqlite3_open_v2( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, + int flags, @Nullable String zVfs + ); + + /** + The sqlite3_prepare() family of functions require slightly + different signatures than their native counterparts, but + overloading allows us to install several convenience forms. + + All of them which take their SQL in the form of a byte[] require + that it be in UTF-8 encoding unless explicitly noted otherwise. + + The forms which take a "tail" output pointer return (via that + output object) the index into their SQL byte array at which the + end of the first SQL statement processed by the call was + found. That's fundamentally how the C APIs work but making use of + that value requires more copying of the input SQL into + consecutively smaller arrays in order to consume all of + it. (There is an example of doing that in this project's Tester1 + class.) For that vast majority of uses, that capability is not + necessary, however, and overloads are provided which gloss over + that. + */ + private static synchronized native int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); + } + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, null); + } + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); + } + + private static synchronized native int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); + } + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, null); + } + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); + } + + private static synchronized native int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, pTailOffset); + } + + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, null); + } + + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull String sql, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); + } + + public static synchronized native void sqlite3_progress_handler( + @NotNull sqlite3 db, int n, @Nullable ProgressHandler h + ); + + //TODO??? void *sqlite3_preupdate_hook(...) and friends + + public static synchronized native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + + /** + Works like the C API except that it has no side effects if auto + extensions are currently running. (The JNI-level list of + extensions cannot be manipulated while it is being traversed.) + */ + public static synchronized native void sqlite3_reset_auto_extension(); + + public static synchronized native void sqlite3_result_double( + @NotNull sqlite3_context cx, double v + ); + + /** + The main sqlite3_result_error() impl of which all others are + proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and + msg must be encoded correspondingly. Any other eTextRep value + results in the C-level sqlite3_result_error() being called with + a complaint about the invalid argument. + */ + private static synchronized native void sqlite3_result_error( + @NotNull sqlite3_context cx, @Nullable byte[] msg, + int eTextRep + ); + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull byte[] utf8 + ){ + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull String msg + ){ + final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_8); + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @Nullable byte[] utf16 + ){ + sqlite3_result_error(cx, utf16, SQLITE_UTF16); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull String msg + ){ + final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_16); + sqlite3_result_error(cx, utf8, SQLITE_UTF16); + } + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull Exception e + ){ + sqlite3_result_error(cx, e.getMessage()); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull Exception e + ){ + sqlite3_result_error16(cx, e.getMessage()); + } + + public static synchronized native void sqlite3_result_error_toobig( + @NotNull sqlite3_context cx + ); + + public static synchronized native void sqlite3_result_error_nomem( + @NotNull sqlite3_context cx + ); + + public static synchronized native void sqlite3_result_error_code( + @NotNull sqlite3_context cx, int c + ); + + public static synchronized native void sqlite3_result_null( + @NotNull sqlite3_context cx + ); + + public static synchronized native void sqlite3_result_int( + @NotNull sqlite3_context cx, int v + ); + + public static synchronized native void sqlite3_result_int64( + @NotNull sqlite3_context cx, long v + ); + + /** + Binds the SQL result to the given object, or + sqlite3_result_null() if o is null. Use + sqlite3_value_java_object() or sqlite3_column_java_object() to + fetch it. + + This is implemented in terms of sqlite3_result_pointer(), but + that function is not exposed to JNI because its 3rd argument must + be a constant string (the library does not copy it), which we + cannot implement cross-language here unless, in the JNI layer, we + allocate such strings and store them somewhere for long-term use + (leaking them more likely than not). Even then, passing around a + pointer via Java like that has little practical use. + + Note that there is no sqlite3_bind_java_object() counterpart. + */ + public static synchronized native void sqlite3_result_java_object( + @NotNull sqlite3_context cx, @NotNull Object o + ); + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Integer v + ){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, int v + ){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Boolean v + ){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, boolean v + ){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Long v + ){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, long v + ){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Double v + ){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, double v + ){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @Nullable String v + ){ + sqlite3_result_text(cx, v); + } + + public static synchronized native void sqlite3_result_value( + @NotNull sqlite3_context cx, @NotNull sqlite3_value v + ); + + public static synchronized native void sqlite3_result_zeroblob( + @NotNull sqlite3_context cx, int n + ); + + public static synchronized native int sqlite3_result_zeroblob64( + @NotNull sqlite3_context cx, long n + ); + + private static synchronized native void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen + ); + + public static void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ + sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); + } + + /** + Binds the given text using C's sqlite3_result_blob64() unless: + + - blob is null ==> sqlite3_result_null() + + - blob is too large ==> sqlite3_result_error_toobig() + + If maxLen is larger than blob.length, it is truncated to that + value. If it is negative, results are undefined. + */ + private static synchronized native void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen + ); + + public static void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ + sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); + } + + private static synchronized native void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen + ); + + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ + sqlite3_result_text(cx, text, null==text ? 0 : text.length); + } + + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8); + sqlite3_result_text(cx, utf8, utf8.length); + } + } + + /** + Binds the given text using C's sqlite3_result_text64() unless: + + - text is null ==> sqlite3_result_null() + + - text is too large ==> sqlite3_result_error_toobig() + + - The `encoding` argument has an invalid value ==> + sqlite3_result_error_code() with SQLITE_FORMAT + + If maxLength (in bytes, not characters) is larger than + text.length, it is silently truncated to text.length. If it is + negative, results are undefined. + */ + private static synchronized native void sqlite3_result_text64( + @NotNull sqlite3_context cx, @Nullable byte[] text, + long maxLength, int encoding + ); + + public static synchronized native int sqlite3_status( + int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + public static synchronized native int sqlite3_status64( + int op, @NotNull OutputPointer.Int64 pCurrent, + @NotNull OutputPointer.Int64 pHighwater, boolean reset + ); + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16 using the platform's byte order. + */ + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ + sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16); + } + + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] b = text.getBytes(StandardCharsets.UTF_16); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16); + } + } + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16LE. + */ + public static void sqlite3_result_text16le( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] b = text.getBytes(StandardCharsets.UTF_16LE); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16LE); + } + } + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16BE. + */ + public static void sqlite3_result_text16be( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ + sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16BE); + } + + public static void sqlite3_result_text16be( + @NotNull sqlite3_context cx, @NotNull String text + ){ + final byte[] b = text.getBytes(StandardCharsets.UTF_16BE); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); + } + + public static synchronized native RollbackHook sqlite3_rollback_hook( + @NotNull sqlite3 db, @Nullable RollbackHook hook + ); + + //! Sets or unsets (if auth is null) the current authorizer. + public static synchronized native int sqlite3_set_authorizer( + @NotNull sqlite3 db, @Nullable Authorizer auth + ); + + public static synchronized native void sqlite3_set_last_insert_rowid( + @NotNull sqlite3 db, long rowid + ); + + public static synchronized native int sqlite3_sleep(int ms); + + public static synchronized native String sqlite3_sourceid(); + + public static synchronized native String sqlite3_sql(@NotNull sqlite3_stmt stmt); + + public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt); + + /** + Internal impl of the public sqlite3_strglob() method. Neither argument + may be NULL and both _MUST_ be NUL-terminated. + */ + private static synchronized native int sqlite3_strglob( + @NotNull byte[] glob, @NotNull byte[] txt + ); + + public static int sqlite3_strglob( + @NotNull String glob, @NotNull String txt + ){ + return sqlite3_strglob( + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8) + ); + } + + /** + Internal impl of the public sqlite3_strlike() method. Neither + argument may be NULL and both _MUST_ be NUL-terminated. + */ + private static synchronized native int sqlite3_strlike( + @NotNull byte[] glob, @NotNull byte[] txt, int escChar + ); + + public static int sqlite3_strlike( + @NotNull String glob, @NotNull String txt, char escChar + ){ + return sqlite3_strlike( + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8), + (int)escChar + ); + } + + public static synchronized native int sqlite3_threadsafe(); + + public static synchronized native int sqlite3_total_changes(@NotNull sqlite3 db); + + public static synchronized native long sqlite3_total_changes64(@NotNull sqlite3 db); + + /** + Works like C's sqlite3_trace_v2() except that the 3rd argument to that + function is elided here because the roles of that functions' 3rd and 4th + arguments are encapsulated in the final argument to this function. + + Unlike the C API, which is documented as always returning 0, this + implementation returns SQLITE_NOMEM if allocation of per-db + mapping state fails and SQLITE_ERROR if the given callback object + cannot be processed propertly (i.e. an internal error). + */ + public static synchronized native int sqlite3_trace_v2( + @NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer + ); + + public static synchronized native UpdateHook sqlite3_update_hook( + sqlite3 db, UpdateHook hook + ); + + public static synchronized native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_bytes(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_bytes16(@NotNull sqlite3_value v); + + public static synchronized native double sqlite3_value_double(@NotNull sqlite3_value v); + + public static synchronized native sqlite3_value sqlite3_value_dupe( + @NotNull sqlite3_value v + ); + + public static synchronized native int sqlite3_value_encoding(@NotNull sqlite3_value v); + + public static synchronized native void sqlite3_value_free(@Nullable sqlite3_value v); + + public static synchronized native int sqlite3_value_int(@NotNull sqlite3_value v); + + public static synchronized native long sqlite3_value_int64(@NotNull sqlite3_value v); + + /** + If the given value was set using sqlite3_result_java_value() then + this function returns that object, else it returns null. + + It is up to the caller to inspect the object to determine its + type, and cast it if necessary. + */ + public static synchronized native Object sqlite3_value_java_object( + @NotNull sqlite3_value v + ); + + /** + A variant of sqlite3_value_java_object() which returns the + fetched object cast to T if the object is an instance of the + given Class. It returns null in all other cases. + */ + @SuppressWarnings("unchecked") + public static T sqlite3_value_java_casted(@NotNull sqlite3_value v, + @NotNull Class type){ + final Object o = sqlite3_value_java_object(v); + return type.isInstance(o) ? (T)o : null; + } + + /** + See sqlite3_column_text() for notes about encoding conversions. + See sqlite3_value_text_utf8() for how to extract text in standard + UTF-8. + */ + public static synchronized native String sqlite3_value_text(@NotNull sqlite3_value v); + + /** + The sqlite3_value counterpart of sqlite3_column_text_utf8(). + */ + public static synchronized native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); + + public static synchronized native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); + + public static synchronized native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); + + public static synchronized native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_type(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_nochange(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_frombind(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_subtype(@NotNull sqlite3_value v); + + /** + Cleans up all per-JNIEnv and per-db state managed by the library + then calls the C-native sqlite3_shutdown(). + */ + public static synchronized native int sqlite3_shutdown(); + + /** + This is NOT part of the public API. It exists solely as a place + to hook in arbitrary C-side code during development and testing + of this library. + */ + public static synchronized native void sqlite3_do_something_for_developer(); + + ////////////////////////////////////////////////////////////////////// + // SQLITE_... constants follow... + + // version info + public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number(); + public static final String SQLITE_VERSION = sqlite3_libversion(); + public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); + + //! Feature flags which are initialized at lib startup. Necessarily + // non-final so that lib init can fill out the proper values, + // but modifying them from client code has no effect. + public static boolean SQLITE_ENABLE_FTS5 = false; + + // access + public static final int SQLITE_ACCESS_EXISTS = 0; + public static final int SQLITE_ACCESS_READWRITE = 1; + public static final int SQLITE_ACCESS_READ = 2; + + // authorizer + public static final int SQLITE_DENY = 1; + public static final int SQLITE_IGNORE = 2; + public static final int SQLITE_CREATE_INDEX = 1; + public static final int SQLITE_CREATE_TABLE = 2; + public static final int SQLITE_CREATE_TEMP_INDEX = 3; + public static final int SQLITE_CREATE_TEMP_TABLE = 4; + public static final int SQLITE_CREATE_TEMP_TRIGGER = 5; + public static final int SQLITE_CREATE_TEMP_VIEW = 6; + public static final int SQLITE_CREATE_TRIGGER = 7; + public static final int SQLITE_CREATE_VIEW = 8; + public static final int SQLITE_DELETE = 9; + public static final int SQLITE_DROP_INDEX = 10; + public static final int SQLITE_DROP_TABLE = 11; + public static final int SQLITE_DROP_TEMP_INDEX = 12; + public static final int SQLITE_DROP_TEMP_TABLE = 13; + public static final int SQLITE_DROP_TEMP_TRIGGER = 14; + public static final int SQLITE_DROP_TEMP_VIEW = 15; + public static final int SQLITE_DROP_TRIGGER = 16; + public static final int SQLITE_DROP_VIEW = 17; + public static final int SQLITE_INSERT = 18; + public static final int SQLITE_PRAGMA = 19; + public static final int SQLITE_READ = 20; + public static final int SQLITE_SELECT = 21; + public static final int SQLITE_TRANSACTION = 22; + public static final int SQLITE_UPDATE = 23; + public static final int SQLITE_ATTACH = 24; + public static final int SQLITE_DETACH = 25; + public static final int SQLITE_ALTER_TABLE = 26; + public static final int SQLITE_REINDEX = 27; + public static final int SQLITE_ANALYZE = 28; + public static final int SQLITE_CREATE_VTABLE = 29; + public static final int SQLITE_DROP_VTABLE = 30; + public static final int SQLITE_FUNCTION = 31; + public static final int SQLITE_SAVEPOINT = 32; + public static final int SQLITE_RECURSIVE = 33; + + // blob finalizers: these should, because they are treated as + // special pointer values in C, ideally have the same sizeof() as + // the platform's (void*), but we can't know that size from here. + public static final long SQLITE_STATIC = 0; + public static final long SQLITE_TRANSIENT = -1; + + // changeset + public static final int SQLITE_CHANGESETSTART_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1; + public static final int SQLITE_CHANGESETAPPLY_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4; + public static final int SQLITE_CHANGESET_DATA = 1; + public static final int SQLITE_CHANGESET_NOTFOUND = 2; + public static final int SQLITE_CHANGESET_CONFLICT = 3; + public static final int SQLITE_CHANGESET_CONSTRAINT = 4; + public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5; + public static final int SQLITE_CHANGESET_OMIT = 0; + public static final int SQLITE_CHANGESET_REPLACE = 1; + public static final int SQLITE_CHANGESET_ABORT = 2; + + // config + public static final int SQLITE_CONFIG_SINGLETHREAD = 1; + public static final int SQLITE_CONFIG_MULTITHREAD = 2; + public static final int SQLITE_CONFIG_SERIALIZED = 3; + public static final int SQLITE_CONFIG_MALLOC = 4; + public static final int SQLITE_CONFIG_GETMALLOC = 5; + public static final int SQLITE_CONFIG_SCRATCH = 6; + public static final int SQLITE_CONFIG_PAGECACHE = 7; + public static final int SQLITE_CONFIG_HEAP = 8; + public static final int SQLITE_CONFIG_MEMSTATUS = 9; + public static final int SQLITE_CONFIG_MUTEX = 10; + public static final int SQLITE_CONFIG_GETMUTEX = 11; + public static final int SQLITE_CONFIG_LOOKASIDE = 13; + public static final int SQLITE_CONFIG_PCACHE = 14; + public static final int SQLITE_CONFIG_GETPCACHE = 15; + public static final int SQLITE_CONFIG_LOG = 16; + public static final int SQLITE_CONFIG_URI = 17; + public static final int SQLITE_CONFIG_PCACHE2 = 18; + public static final int SQLITE_CONFIG_GETPCACHE2 = 19; + public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20; + public static final int SQLITE_CONFIG_SQLLOG = 21; + public static final int SQLITE_CONFIG_MMAP_SIZE = 22; + public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23; + public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24; + public static final int SQLITE_CONFIG_PMASZ = 25; + public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26; + public static final int SQLITE_CONFIG_SMALL_MALLOC = 27; + public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28; + public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29; + + // data types + public static final int SQLITE_INTEGER = 1; + public static final int SQLITE_FLOAT = 2; + public static final int SQLITE_TEXT = 3; + public static final int SQLITE_BLOB = 4; + public static final int SQLITE_NULL = 5; + + // db config + public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000; + public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001; + public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002; + public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003; + public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004; + public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005; + public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006; + public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007; + public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008; + public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009; + public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010; + public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011; + public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012; + public static final int SQLITE_DBCONFIG_DQS_DML = 1013; + public static final int SQLITE_DBCONFIG_DQS_DDL = 1014; + public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015; + public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016; + public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017; + public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018; + public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019; + public static final int SQLITE_DBCONFIG_MAX = 1019; + + // db status + public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0; + public static final int SQLITE_DBSTATUS_CACHE_USED = 1; + public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2; + public static final int SQLITE_DBSTATUS_STMT_USED = 3; + public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6; + public static final int SQLITE_DBSTATUS_CACHE_HIT = 7; + public static final int SQLITE_DBSTATUS_CACHE_MISS = 8; + public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9; + public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10; + public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11; + public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12; + public static final int SQLITE_DBSTATUS_MAX = 12; + + // encodings + public static final int SQLITE_UTF8 = 1; + public static final int SQLITE_UTF16LE = 2; + public static final int SQLITE_UTF16BE = 3; + public static final int SQLITE_UTF16 = 4; + public static final int SQLITE_UTF16_ALIGNED = 8; + + // fcntl + public static final int SQLITE_FCNTL_LOCKSTATE = 1; + public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2; + public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3; + public static final int SQLITE_FCNTL_LAST_ERRNO = 4; + public static final int SQLITE_FCNTL_SIZE_HINT = 5; + public static final int SQLITE_FCNTL_CHUNK_SIZE = 6; + public static final int SQLITE_FCNTL_FILE_POINTER = 7; + public static final int SQLITE_FCNTL_SYNC_OMITTED = 8; + public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9; + public static final int SQLITE_FCNTL_PERSIST_WAL = 10; + public static final int SQLITE_FCNTL_OVERWRITE = 11; + public static final int SQLITE_FCNTL_VFSNAME = 12; + public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13; + public static final int SQLITE_FCNTL_PRAGMA = 14; + public static final int SQLITE_FCNTL_BUSYHANDLER = 15; + public static final int SQLITE_FCNTL_TEMPFILENAME = 16; + public static final int SQLITE_FCNTL_MMAP_SIZE = 18; + public static final int SQLITE_FCNTL_TRACE = 19; + public static final int SQLITE_FCNTL_HAS_MOVED = 20; + public static final int SQLITE_FCNTL_SYNC = 21; + public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22; + public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23; + public static final int SQLITE_FCNTL_WAL_BLOCK = 24; + public static final int SQLITE_FCNTL_ZIPVFS = 25; + public static final int SQLITE_FCNTL_RBU = 26; + public static final int SQLITE_FCNTL_VFS_POINTER = 27; + public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28; + public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29; + public static final int SQLITE_FCNTL_PDB = 30; + public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31; + public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32; + public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33; + public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34; + public static final int SQLITE_FCNTL_DATA_VERSION = 35; + public static final int SQLITE_FCNTL_SIZE_LIMIT = 36; + public static final int SQLITE_FCNTL_CKPT_DONE = 37; + public static final int SQLITE_FCNTL_RESERVE_BYTES = 38; + public static final int SQLITE_FCNTL_CKPT_START = 39; + public static final int SQLITE_FCNTL_EXTERNAL_READER = 40; + public static final int SQLITE_FCNTL_CKSM_FILE = 41; + public static final int SQLITE_FCNTL_RESET_CACHE = 42; + + // flock + public static final int SQLITE_LOCK_NONE = 0; + public static final int SQLITE_LOCK_SHARED = 1; + public static final int SQLITE_LOCK_RESERVED = 2; + public static final int SQLITE_LOCK_PENDING = 3; + public static final int SQLITE_LOCK_EXCLUSIVE = 4; + + // iocap + public static final int SQLITE_IOCAP_ATOMIC = 1; + public static final int SQLITE_IOCAP_ATOMIC512 = 2; + public static final int SQLITE_IOCAP_ATOMIC1K = 4; + public static final int SQLITE_IOCAP_ATOMIC2K = 8; + public static final int SQLITE_IOCAP_ATOMIC4K = 16; + public static final int SQLITE_IOCAP_ATOMIC8K = 32; + public static final int SQLITE_IOCAP_ATOMIC16K = 64; + public static final int SQLITE_IOCAP_ATOMIC32K = 128; + public static final int SQLITE_IOCAP_ATOMIC64K = 256; + public static final int SQLITE_IOCAP_SAFE_APPEND = 512; + public static final int SQLITE_IOCAP_SEQUENTIAL = 1024; + public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048; + public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096; + public static final int SQLITE_IOCAP_IMMUTABLE = 8192; + public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384; + + // limits. These get injected at init-time so that they stay in sync + // with the compile-time options. This unfortunately means they are + // not final, but keeping them in sync with their C values seems + // more important than protecting users from assigning to these + // (with unpredictable results). + public static int SQLITE_MAX_ALLOCATION_SIZE = -1; + public static int SQLITE_LIMIT_LENGTH = -1; + public static int SQLITE_MAX_LENGTH = -1; + public static int SQLITE_LIMIT_SQL_LENGTH = -1; + public static int SQLITE_MAX_SQL_LENGTH = -1; + public static int SQLITE_LIMIT_COLUMN = -1; + public static int SQLITE_MAX_COLUMN = -1; + public static int SQLITE_LIMIT_EXPR_DEPTH = -1; + public static int SQLITE_MAX_EXPR_DEPTH = -1; + public static int SQLITE_LIMIT_COMPOUND_SELECT = -1; + public static int SQLITE_MAX_COMPOUND_SELECT = -1; + public static int SQLITE_LIMIT_VDBE_OP = -1; + public static int SQLITE_MAX_VDBE_OP = -1; + public static int SQLITE_LIMIT_FUNCTION_ARG = -1; + public static int SQLITE_MAX_FUNCTION_ARG = -1; + public static int SQLITE_LIMIT_ATTACHED = -1; + public static int SQLITE_MAX_ATTACHED = -1; + public static int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = -1; + public static int SQLITE_MAX_LIKE_PATTERN_LENGTH = -1; + public static int SQLITE_LIMIT_VARIABLE_NUMBER = -1; + public static int SQLITE_MAX_VARIABLE_NUMBER = -1; + public static int SQLITE_LIMIT_TRIGGER_DEPTH = -1; + public static int SQLITE_MAX_TRIGGER_DEPTH = -1; + public static int SQLITE_LIMIT_WORKER_THREADS = -1; + public static int SQLITE_MAX_WORKER_THREADS = -1; + + // open flags + public static final int SQLITE_OPEN_READONLY = 1; + public static final int SQLITE_OPEN_READWRITE = 2; + public static final int SQLITE_OPEN_CREATE = 4; + public static final int SQLITE_OPEN_URI = 64; + public static final int SQLITE_OPEN_MEMORY = 128; + public static final int SQLITE_OPEN_NOMUTEX = 32768; + public static final int SQLITE_OPEN_FULLMUTEX = 65536; + public static final int SQLITE_OPEN_SHAREDCACHE = 131072; + public static final int SQLITE_OPEN_PRIVATECACHE = 262144; + public static final int SQLITE_OPEN_EXRESCODE = 33554432; + public static final int SQLITE_OPEN_NOFOLLOW = 16777216; + public static final int SQLITE_OPEN_MAIN_DB = 256; + public static final int SQLITE_OPEN_MAIN_JOURNAL = 2048; + public static final int SQLITE_OPEN_TEMP_DB = 512; + public static final int SQLITE_OPEN_TEMP_JOURNAL = 4096; + public static final int SQLITE_OPEN_TRANSIENT_DB = 1024; + public static final int SQLITE_OPEN_SUBJOURNAL = 8192; + public static final int SQLITE_OPEN_SUPER_JOURNAL = 16384; + public static final int SQLITE_OPEN_WAL = 524288; + public static final int SQLITE_OPEN_DELETEONCLOSE = 8; + public static final int SQLITE_OPEN_EXCLUSIVE = 16; + + // prepare flags + public static final int SQLITE_PREPARE_PERSISTENT = 1; + public static final int SQLITE_PREPARE_NORMALIZE = 2; + public static final int SQLITE_PREPARE_NO_VTAB = 4; + + // result codes + public static final int SQLITE_OK = 0; + public static final int SQLITE_ERROR = 1; + public static final int SQLITE_INTERNAL = 2; + public static final int SQLITE_PERM = 3; + public static final int SQLITE_ABORT = 4; + public static final int SQLITE_BUSY = 5; + public static final int SQLITE_LOCKED = 6; + public static final int SQLITE_NOMEM = 7; + public static final int SQLITE_READONLY = 8; + public static final int SQLITE_INTERRUPT = 9; + public static final int SQLITE_IOERR = 10; + public static final int SQLITE_CORRUPT = 11; + public static final int SQLITE_NOTFOUND = 12; + public static final int SQLITE_FULL = 13; + public static final int SQLITE_CANTOPEN = 14; + public static final int SQLITE_PROTOCOL = 15; + public static final int SQLITE_EMPTY = 16; + public static final int SQLITE_SCHEMA = 17; + public static final int SQLITE_TOOBIG = 18; + public static final int SQLITE_CONSTRAINT = 19; + public static final int SQLITE_MISMATCH = 20; + public static final int SQLITE_MISUSE = 21; + public static final int SQLITE_NOLFS = 22; + public static final int SQLITE_AUTH = 23; + public static final int SQLITE_FORMAT = 24; + public static final int SQLITE_RANGE = 25; + public static final int SQLITE_NOTADB = 26; + public static final int SQLITE_NOTICE = 27; + public static final int SQLITE_WARNING = 28; + public static final int SQLITE_ROW = 100; + public static final int SQLITE_DONE = 101; + public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257; + public static final int SQLITE_ERROR_RETRY = 513; + public static final int SQLITE_ERROR_SNAPSHOT = 769; + public static final int SQLITE_IOERR_READ = 266; + public static final int SQLITE_IOERR_SHORT_READ = 522; + public static final int SQLITE_IOERR_WRITE = 778; + public static final int SQLITE_IOERR_FSYNC = 1034; + public static final int SQLITE_IOERR_DIR_FSYNC = 1290; + public static final int SQLITE_IOERR_TRUNCATE = 1546; + public static final int SQLITE_IOERR_FSTAT = 1802; + public static final int SQLITE_IOERR_UNLOCK = 2058; + public static final int SQLITE_IOERR_RDLOCK = 2314; + public static final int SQLITE_IOERR_DELETE = 2570; + public static final int SQLITE_IOERR_BLOCKED = 2826; + public static final int SQLITE_IOERR_NOMEM = 3082; + public static final int SQLITE_IOERR_ACCESS = 3338; + public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594; + public static final int SQLITE_IOERR_LOCK = 3850; + public static final int SQLITE_IOERR_CLOSE = 4106; + public static final int SQLITE_IOERR_DIR_CLOSE = 4362; + public static final int SQLITE_IOERR_SHMOPEN = 4618; + public static final int SQLITE_IOERR_SHMSIZE = 4874; + public static final int SQLITE_IOERR_SHMLOCK = 5130; + public static final int SQLITE_IOERR_SHMMAP = 5386; + public static final int SQLITE_IOERR_SEEK = 5642; + public static final int SQLITE_IOERR_DELETE_NOENT = 5898; + public static final int SQLITE_IOERR_MMAP = 6154; + public static final int SQLITE_IOERR_GETTEMPPATH = 6410; + public static final int SQLITE_IOERR_CONVPATH = 6666; + public static final int SQLITE_IOERR_VNODE = 6922; + public static final int SQLITE_IOERR_AUTH = 7178; + public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434; + public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690; + public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946; + public static final int SQLITE_IOERR_DATA = 8202; + public static final int SQLITE_IOERR_CORRUPTFS = 8458; + public static final int SQLITE_LOCKED_SHAREDCACHE = 262; + public static final int SQLITE_LOCKED_VTAB = 518; + public static final int SQLITE_BUSY_RECOVERY = 261; + public static final int SQLITE_BUSY_SNAPSHOT = 517; + public static final int SQLITE_BUSY_TIMEOUT = 773; + public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270; + public static final int SQLITE_CANTOPEN_ISDIR = 526; + public static final int SQLITE_CANTOPEN_FULLPATH = 782; + public static final int SQLITE_CANTOPEN_CONVPATH = 1038; + public static final int SQLITE_CANTOPEN_SYMLINK = 1550; + public static final int SQLITE_CORRUPT_VTAB = 267; + public static final int SQLITE_CORRUPT_SEQUENCE = 523; + public static final int SQLITE_CORRUPT_INDEX = 779; + public static final int SQLITE_READONLY_RECOVERY = 264; + public static final int SQLITE_READONLY_CANTLOCK = 520; + public static final int SQLITE_READONLY_ROLLBACK = 776; + public static final int SQLITE_READONLY_DBMOVED = 1032; + public static final int SQLITE_READONLY_CANTINIT = 1288; + public static final int SQLITE_READONLY_DIRECTORY = 1544; + public static final int SQLITE_ABORT_ROLLBACK = 516; + public static final int SQLITE_CONSTRAINT_CHECK = 275; + public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531; + public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787; + public static final int SQLITE_CONSTRAINT_FUNCTION = 1043; + public static final int SQLITE_CONSTRAINT_NOTNULL = 1299; + public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555; + public static final int SQLITE_CONSTRAINT_TRIGGER = 1811; + public static final int SQLITE_CONSTRAINT_UNIQUE = 2067; + public static final int SQLITE_CONSTRAINT_VTAB = 2323; + public static final int SQLITE_CONSTRAINT_ROWID = 2579; + public static final int SQLITE_CONSTRAINT_PINNED = 2835; + public static final int SQLITE_CONSTRAINT_DATATYPE = 3091; + public static final int SQLITE_NOTICE_RECOVER_WAL = 283; + public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539; + public static final int SQLITE_WARNING_AUTOINDEX = 284; + public static final int SQLITE_AUTH_USER = 279; + public static final int SQLITE_OK_LOAD_PERMANENTLY = 256; + + // serialize + public static final int SQLITE_SERIALIZE_NOCOPY = 1; + public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1; + public static final int SQLITE_DESERIALIZE_READONLY = 4; + public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2; + + // session + public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1; + public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1; + + // sqlite3 status + public static final int SQLITE_STATUS_MEMORY_USED = 0; + public static final int SQLITE_STATUS_PAGECACHE_USED = 1; + public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2; + public static final int SQLITE_STATUS_MALLOC_SIZE = 5; + public static final int SQLITE_STATUS_PARSER_STACK = 6; + public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7; + public static final int SQLITE_STATUS_MALLOC_COUNT = 9; + + // stmt status + public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1; + public static final int SQLITE_STMTSTATUS_SORT = 2; + public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3; + public static final int SQLITE_STMTSTATUS_VM_STEP = 4; + public static final int SQLITE_STMTSTATUS_REPREPARE = 5; + public static final int SQLITE_STMTSTATUS_RUN = 6; + public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7; + public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8; + public static final int SQLITE_STMTSTATUS_MEMUSED = 99; + + // sync flags + public static final int SQLITE_SYNC_NORMAL = 2; + public static final int SQLITE_SYNC_FULL = 3; + public static final int SQLITE_SYNC_DATAONLY = 16; + + // tracing flags + public static final int SQLITE_TRACE_STMT = 1; + public static final int SQLITE_TRACE_PROFILE = 2; + public static final int SQLITE_TRACE_ROW = 4; + public static final int SQLITE_TRACE_CLOSE = 8; + + // transaction state + public static final int SQLITE_TXN_NONE = 0; + public static final int SQLITE_TXN_READ = 1; + public static final int SQLITE_TXN_WRITE = 2; + + // udf flags + public static final int SQLITE_DETERMINISTIC = 2048; + public static final int SQLITE_DIRECTONLY = 524288; + public static final int SQLITE_INNOCUOUS = 2097152; + + // virtual tables + public static final int SQLITE_INDEX_SCAN_UNIQUE = 1; + public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2; + public static final int SQLITE_INDEX_CONSTRAINT_GT = 4; + public static final int SQLITE_INDEX_CONSTRAINT_LE = 8; + public static final int SQLITE_INDEX_CONSTRAINT_LT = 16; + public static final int SQLITE_INDEX_CONSTRAINT_GE = 32; + public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64; + public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65; + public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66; + public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67; + public static final int SQLITE_INDEX_CONSTRAINT_NE = 68; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70; + public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71; + public static final int SQLITE_INDEX_CONSTRAINT_IS = 72; + public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73; + public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74; + public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150; + public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1; + public static final int SQLITE_VTAB_INNOCUOUS = 2; + public static final int SQLITE_VTAB_DIRECTONLY = 3; + public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4; + public static final int SQLITE_ROLLBACK = 1; + public static final int SQLITE_FAIL = 3; + public static final int SQLITE_REPLACE = 5; + static { + // This MUST come after the SQLITE_MAX_... values or else + // attempting to modify them silently fails. + init(); + } +} diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java new file mode 100644 index 000000000..dca49faf6 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -0,0 +1,1163 @@ +/* +** 2023-07-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 contains a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni; +import static org.sqlite.jni.SQLite3Jni.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public class Tester1 { + private static final class Metrics { + int dbOpen; + } + + static final Metrics metrics = new Metrics(); + private static final OutputPointer.sqlite3_stmt outStmt + = new OutputPointer.sqlite3_stmt(); + + public static void out(Object val){ + System.out.print(val); + } + + public static void outln(Object val){ + System.out.println(val); + } + + @SuppressWarnings("unchecked") + public static void out(Object... vals){ + int n = 0; + for(Object v : vals) out((n++>0 ? " " : "")+v); + } + + @SuppressWarnings("unchecked") + public static void outln(Object... vals){ + out(vals); out("\n"); + } + + static int affirmCount = 0; + public static void affirm(Boolean v){ + ++affirmCount; + assert( v /* prefer assert over exception if it's enabled because + the JNI layer sometimes has to suppress exceptions. */); + if( !v ) throw new RuntimeException("Assertion failed."); + } + + private static void test1(){ + outln("libversion_number:", + sqlite3_libversion_number() + + "\n" + + sqlite3_libversion() + + "\n" + + SQLITE_SOURCE_ID); + affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); + //outln("threadsafe = "+sqlite3_threadsafe()); + affirm(SQLITE_MAX_LENGTH > 0); + affirm(SQLITE_MAX_TRIGGER_DEPTH>0); + } + + public static sqlite3 createNewDb(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); + ++metrics.dbOpen; + sqlite3 db = out.take(); + if( 0!=rc ){ + final String msg = db.getNativePointer()==0 + ? sqlite3_errstr(rc) + : sqlite3_errmsg(db); + throw new RuntimeException("Opening db failed: "+msg); + } + affirm( null == out.get() ); + affirm( 0 != db.getNativePointer() ); + rc = sqlite3_busy_timeout(db, 2000); + affirm( 0 == rc ); + return db; + } + + public static void execSql(sqlite3 db, String[] sql){ + execSql(db, String.join("", sql)); + } + + public static int execSql(sqlite3 db, boolean throwOnError, String sql){ + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + if(throwOnError) affirm(0 == rc); + else if( 0!=rc ) break; + pos = oTail.value; + stmt = outStmt.take(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + affirm(0 != stmt.getNativePointer()); + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + } + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ + break; + } + } + sqlite3_finalize(stmt); + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + if( 0!=rc && throwOnError){ + throw new RuntimeException("db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + } + return rc; + } + + public static void execSql(sqlite3 db, String sql){ + execSql(db, true, sql); + } + + public static sqlite3_stmt prepare(sqlite3 db, String sql){ + outStmt.clear(); + int rc = sqlite3_prepare(db, sql, outStmt); + affirm( 0 == rc ); + final sqlite3_stmt rv = outStmt.take(); + affirm( null == outStmt.get() ); + affirm( 0 != rv.getNativePointer() ); + return rv; + } + + private static void testCompileOption(){ + int i = 0; + String optName; + outln("compile options:"); + for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){ + outln("\t"+optName+"\t (used="+ + sqlite3_compileoption_used(optName)+")"); + } + + } + + private static void testOpenDb1(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); + ++metrics.dbOpen; + sqlite3 db = out.get(); + affirm(0 == rc); + affirm(0 < db.getNativePointer()); + sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null) + /* This function has different mangled names in jdk8 vs jdk19, + and this call is here to ensure that the build fails + if it cannot find both names. */; + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private static void testOpenDb2(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open_v2(":memory:", out, + SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE, null); + ++metrics.dbOpen; + affirm(0 == rc); + sqlite3 db = out.get(); + affirm(0 < db.getNativePointer()); + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private static void testPrepare123(){ + sqlite3 db = createNewDb(); + int rc; + rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt); + affirm(0 == rc); + sqlite3_stmt stmt = outStmt.get(); + affirm(0 != stmt.getNativePointer()); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + + { /* Demonstrate how to use the "zTail" option of + sqlite3_prepare() family of functions. */ + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = + "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)" + .getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); + } + //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos); + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + affirm(0 == rc); + stmt = outStmt.get(); + pos = oTail.value; + /*outln("SQL tail pos = "+pos+". Chunk = "+ + (new String(Arrays.copyOfRange(sqlChunk,0,pos), + StandardCharsets.UTF_8)));*/ + switch(n){ + case 1: affirm(19 == pos); break; + case 2: affirm(36 == pos); break; + default: affirm( false /* can't happen */ ); + + } + ++n; + affirm(0 != stmt.getNativePointer()); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + } + } + + + rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", + SQLITE_PREPARE_NORMALIZE, outStmt); + affirm(0 == rc); + stmt = outStmt.get(); + affirm(0 != stmt.getNativePointer()); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer() ); + sqlite3_close_v2(db); + } + + private static void testBindFetchInt(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);"); + affirm(1 == sqlite3_bind_parameter_count(stmt)); + final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a"); + affirm(1 == paramNdx); + int total1 = 0; + long rowid = -1; + int changes = sqlite3_changes(db); + int changesT = sqlite3_total_changes(db); + long changes64 = sqlite3_changes64(db); + long changesT64 = sqlite3_total_changes64(db); + int rc; + for(int i = 99; i < 102; ++i ){ + total1 += i; + rc = sqlite3_bind_int(stmt, paramNdx, i); + affirm(0 == rc); + rc = sqlite3_step(stmt); + sqlite3_reset(stmt); + affirm(SQLITE_DONE == rc); + long x = sqlite3_last_insert_rowid(db); + affirm(x > rowid); + rowid = x; + } + sqlite3_finalize(stmt); + affirm(300 == total1); + affirm(sqlite3_changes(db) > changes); + affirm(sqlite3_total_changes(db) > changesT); + affirm(sqlite3_changes64(db) > changes64); + affirm(sqlite3_total_changes64(db) > changesT64); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + int total2 = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + total2 += sqlite3_column_int(stmt, 0); + sqlite3_value sv = sqlite3_column_value(stmt, 0); + affirm( null != sv ); + affirm( 0 != sv.getNativePointer() ); + affirm( SQLITE_INTEGER == sqlite3_value_type(sv) ); + } + sqlite3_finalize(stmt); + affirm(total1 == total2); + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private static void testBindFetchInt64(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + long total1 = 0; + for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){ + total1 += i; + sqlite3_bind_int64(stmt, 1, i); + sqlite3_step(stmt); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + long total2 = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + total2 += sqlite3_column_int64(stmt, 0); + } + sqlite3_finalize(stmt); + affirm(total1 == total2); + sqlite3_close_v2(db); + } + + private static void testBindFetchDouble(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + double total1 = 0; + for(double i = 1.5; i < 5.0; i = i + 1.0 ){ + total1 += i; + sqlite3_bind_double(stmt, 1, i); + sqlite3_step(stmt); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + double total2 = 0; + int counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + ++counter; + total2 += sqlite3_column_double(stmt, 0); + } + affirm(4 == counter); + sqlite3_finalize(stmt); + affirm(total2<=total1+0.01 && total2>=total1-0.01); + sqlite3_close_v2(db); + } + + private static void testBindFetchText(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + String[] list1 = { "hell🤩", "w😃rld", "!" }; + int rc; + for( String e : list1 ){ + rc = sqlite3_bind_text(stmt, 1, e); + affirm(0 == rc); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE==rc); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + StringBuilder sbuf = new StringBuilder(); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + String txt = sqlite3_column_text16(stmt, 0); + //outln("txt = "+txt); + sbuf.append( txt ); + ++n; + } + sqlite3_finalize(stmt); + affirm(3 == n); + affirm("w😃rldhell🤩!".equals(sbuf.toString())); + sqlite3_close_v2(db); + } + + private static void testBindFetchBlob(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + byte[] list1 = { 0x32, 0x33, 0x34 }; + int rc = sqlite3_bind_blob(stmt, 1, list1); + affirm( 0==rc ); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + int n = 0; + int total = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + byte[] blob = sqlite3_column_blob(stmt, 0); + affirm(3 == blob.length); + int i = 0; + for(byte b : blob){ + affirm(b == list1[i++]); + total += b; + } + ++n; + } + sqlite3_finalize(stmt); + affirm(1 == n); + affirm(total == 0x32 + 0x33 + 0x34); + sqlite3_close_v2(db); + } + + private static void testSql(){ + sqlite3 db = createNewDb(); + sqlite3_stmt stmt = prepare(db, "SELECT 1"); + affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT ?"); + sqlite3_bind_text(stmt, 1, "hell😃"); + affirm( "SELECT 'hell😃'".equals(sqlite3_expanded_sql(stmt)) ); + sqlite3_finalize(stmt); + } + + private static void testCollation(){ + final sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + final ValueHolder xDestroyCalled = new ValueHolder<>(false); + final Collation myCollation = new Collation() { + private String myState = + "this is local state. There is much like it, but this is mine."; + @Override + // Reverse-sorts its inputs... + public int xCompare(byte[] lhs, byte[] rhs){ + int len = lhs.length > rhs.length ? rhs.length : lhs.length; + int c = 0, i = 0; + for(i = 0; i < len; ++i){ + c = lhs[i] - rhs[i]; + if(0 != c) break; + } + if(0==c){ + if(i < lhs.length) c = 1; + else if(i < rhs.length) c = -1; + } + return -c; + } + @Override + public void xDestroy() { + // Just demonstrates that xDestroy is called. + xDestroyCalled.value = true; + } + }; + final CollationNeeded collLoader = new CollationNeeded(){ + public int xCollationNeeded(sqlite3 dbArg, int eTextRep, String collationName){ + affirm(dbArg == db/* as opposed to a temporary object*/); + return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); + } + }; + int rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc ); + rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc /* Installing the same object again is a no-op */); + sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi"); + int counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String val = sqlite3_column_text16(stmt, 0); + ++counter; + //outln("REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 1: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 3: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a"); + counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String val = sqlite3_column_text16(stmt, 0); + ++counter; + //outln("Non-REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 3: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 1: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + sqlite3_finalize(stmt); + affirm(!xDestroyCalled.value); + rc = sqlite3_collation_needed(db, null); + affirm( 0 == rc ); + sqlite3_close_v2(db); + affirm(xDestroyCalled.value); + } + + private static void testToUtf8(){ + /** + Java docs seem contradictory, claiming to use "modified UTF-8" + encoding while also claiming to export using RFC 2279: + + https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html + + Let's ensure that we can convert to standard UTF-8 in Java code + (noting that the JNI native API has no way to do this). + */ + final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8); + affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); + } + + private static void testStatus(){ + final OutputPointer.Int64 cur64 = new OutputPointer.Int64(); + final OutputPointer.Int64 high64 = new OutputPointer.Int64(); + final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); + final OutputPointer.Int32 high32 = new OutputPointer.Int32(); + final sqlite3 db = createNewDb(); + execSql(db, "create table t(a); insert into t values(1),(2),(3)"); + + int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.value > 0 ); + affirm( high32.value >= cur32.value ); + + rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false); + affirm( 0 == rc ); + affirm( cur64.value > 0 ); + affirm( high64.value >= cur64.value ); + + cur32.value = 0; + high32.value = 1; + rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.value > 0 ); + affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ ); + + sqlite3_close_v2(db); + } + + private static void testUdf1(){ + final sqlite3 db = createNewDb(); + // These ValueHolders are just to confirm that the func did what we want... + final ValueHolder xDestroyCalled = new ValueHolder<>(false); + final ValueHolder xFuncAccum = new ValueHolder<>(0); + + // Create an SQLFunction instance using one of its 3 subclasses: + // Scalar, Aggregate, or Window: + SQLFunction func = + // Each of the 3 subclasses requires a different set of + // functions, all of which must be implemented. Anonymous + // classes are a convenient way to implement these. + new SQLFunction.Scalar(){ + public void xFunc(sqlite3_context cx, sqlite3_value[] args){ + affirm(db == sqlite3_context_db_handle(cx)); + int result = 0; + for( sqlite3_value v : args ) result += sqlite3_value_int(v); + xFuncAccum.value += result;// just for post-run testing + sqlite3_result_int(cx, result); + } + /* OPTIONALLY override xDestroy... */ + public void xDestroy(){ + xDestroyCalled.value = true; + } + }; + + // Register and use the function... + int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); + final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)"); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + affirm( 6 == sqlite3_column_int(stmt, 0) ); + ++n; + } + sqlite3_finalize(stmt); + affirm(1 == n); + affirm(6 == xFuncAccum.value); + affirm( !xDestroyCalled.value ); + sqlite3_close_v2(db); + affirm( xDestroyCalled.value ); + } + + private static void testUdfJavaObject(){ + final sqlite3 db = createNewDb(); + final ValueHolder testResult = new ValueHolder<>(db); + final SQLFunction func = new SQLFunction.Scalar(){ + public void xFunc(sqlite3_context cx, sqlite3_value args[]){ + sqlite3_result_java_object(cx, testResult.value); + } + }; + int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); + affirm(0 == rc); + final sqlite3_stmt stmt = prepare(db, "select myfunc()"); + affirm( 0 != stmt.getNativePointer() ); + affirm( testResult.value == db ); + int n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final sqlite3_value v = sqlite3_column_value(stmt, 0); + affirm( testResult.value == sqlite3_value_java_object(v) ); + affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) ); + affirm( testResult.value == + sqlite3_value_java_casted(v, testResult.value.getClass()) ); + affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) ); + affirm( null == sqlite3_value_java_casted(v, String.class) ); + ++n; + } + sqlite3_finalize(stmt); + affirm( 1 == n ); + sqlite3_close_v2(db); + } + + private static void testUdfAggregate(){ + final sqlite3 db = createNewDb(); + final ValueHolder xFinalNull = + // To confirm that xFinal() is called with no aggregate state + // when the corresponding result set is empty. + new ValueHolder<>(false); + SQLFunction func = new SQLFunction.Aggregate(){ + @Override + public void xStep(sqlite3_context cx, sqlite3_value[] args){ + this.getAggregateState(cx, 0).value += sqlite3_value_int(args[0]); + } + @Override + public void xFinal(sqlite3_context cx){ + final Integer v = this.takeAggregateState(cx); + if(null == v){ + xFinalNull.value = true; + sqlite3_result_null(cx); + }else{ + sqlite3_result_int(cx, v); + } + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)"); + int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func); + affirm(0 == rc); + sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t"); + int n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final int v = sqlite3_column_int(stmt, 0); + affirm( 6 == v ); + ++n; + } + affirm(!xFinalNull.value); + sqlite3_reset(stmt); + // Ensure that the accumulator is reset... + n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final int v = sqlite3_column_int(stmt, 0); + affirm( 6 == v ); + ++n; + } + sqlite3_finalize(stmt); + affirm( 1==n ); + + stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a"); + n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final int c0 = sqlite3_column_int(stmt, 0); + final int c1 = sqlite3_column_int(stmt, 1); + ++n; + affirm( 6 == c0 ); + affirm( 12 == c1 ); + } + affirm( 1 == n ); + affirm(!xFinalNull.value); + sqlite3_finalize(stmt); + + execSql(db, "SELECT myfunc(1) WHERE 0"); + affirm(xFinalNull.value); + sqlite3_close_v2(db); + } + + private static void testUdfWindow(){ + final sqlite3 db = createNewDb(); + /* Example window function, table, and results taken from: + https://sqlite.org/windowfunctions.html#udfwinfunc */ + final SQLFunction func = new SQLFunction.Window(){ + + private void xStepInverse(sqlite3_context cx, int v){ + this.getAggregateState(cx,0).value += v; + } + @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ + this.xStepInverse(cx, sqlite3_value_int(args[0])); + } + @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){ + this.xStepInverse(cx, -sqlite3_value_int(args[0])); + } + + private void xFinalValue(sqlite3_context cx, Integer v){ + if(null == v) sqlite3_result_null(cx); + else sqlite3_result_int(cx, v); + } + @Override public void xFinal(sqlite3_context cx){ + xFinalValue(cx, this.takeAggregateState(cx)); + } + @Override public void xValue(sqlite3_context cx){ + xFinalValue(cx, this.getAggregateState(cx,null).value); + } + }; + int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func); + affirm( 0 == rc ); + execSql(db, new String[] { + "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", + "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" + }); + final sqlite3_stmt stmt = prepare(db, + "SELECT x, winsumint(y) OVER ("+ + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+ + ") AS sum_y "+ + "FROM twin ORDER BY x;"); + affirm( 0 == rc ); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String s = sqlite3_column_text16(stmt, 0); + final int i = sqlite3_column_int(stmt, 1); + switch(++n){ + case 1: affirm( "a".equals(s) && 9==i ); break; + case 2: affirm( "b".equals(s) && 12==i ); break; + case 3: affirm( "c".equals(s) && 16==i ); break; + case 4: affirm( "d".equals(s) && 12==i ); break; + case 5: affirm( "e".equals(s) && 9==i ); break; + default: affirm( false /* cannot happen */ ); + } + } + sqlite3_finalize(stmt); + affirm( 5 == n ); + sqlite3_close_v2(db); + } + + private static void listBoundMethods(){ + if(false){ + final java.lang.reflect.Field[] declaredFields = + SQLite3Jni.class.getDeclaredFields(); + outln("Bound constants:\n"); + for(java.lang.reflect.Field field : declaredFields) { + if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + outln("\t"+field.getName()); + } + } + } + final java.lang.reflect.Method[] declaredMethods = + SQLite3Jni.class.getDeclaredMethods(); + final java.util.List funcList = new java.util.ArrayList<>(); + for(java.lang.reflect.Method m : declaredMethods){ + if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + funcList.add(name); + } + } + } + int count = 0; + java.util.Collections.sort(funcList); + for(String n : funcList){ + ++count; + outln("\t"+n+"()"); + } + outln(count+" functions named sqlite3_*."); + } + + private static void testTrace(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + /* Ensure that characters outside of the UTF BMP survive the trip + from Java to sqlite3 and back to Java. (At no small efficiency + penalty.) */ + final String nonBmpChar = "😃"; + sqlite3_trace_v2( + db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE + | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, + new Tracer(){ + public int xCallback(int traceFlag, Object pNative, Object x){ + ++counter.value; + //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); + switch(traceFlag){ + case SQLITE_TRACE_STMT: + affirm(pNative instanceof sqlite3_stmt); + //outln("TRACE_STMT sql = "+x); + affirm(x instanceof String); + affirm( ((String)x).indexOf(nonBmpChar) > 0 ); + break; + case SQLITE_TRACE_PROFILE: + affirm(pNative instanceof sqlite3_stmt); + affirm(x instanceof Long); + //outln("TRACE_PROFILE time = "+x); + break; + case SQLITE_TRACE_ROW: + affirm(pNative instanceof sqlite3_stmt); + affirm(null == x); + //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); + break; + case SQLITE_TRACE_CLOSE: + affirm(pNative instanceof sqlite3); + affirm(null == x); + break; + default: + affirm(false /*cannot happen*/); + break; + } + return 0; + } + }); + execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ + "SELECT 'w"+nonBmpChar+"orld'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + affirm( 7 == counter.value ); + } + + private static void testBusy(){ + final String dbName = "_busy-handler.db"; + final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); + + int rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + final sqlite3 db1 = outDb.get(); + execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); + rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + affirm( outDb.get() != db1 ); + final sqlite3 db2 = outDb.get(); + rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); + affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); + + final ValueHolder xDestroyed = new ValueHolder<>(false); + final ValueHolder xBusyCalled = new ValueHolder<>(0); + BusyHandler handler = new BusyHandler(){ + @Override public int xCallback(int n){ + //outln("busy handler #"+n); + return n > 2 ? 0 : ++xBusyCalled.value; + } + @Override public void xDestroy(){ + xDestroyed.value = true; + } + }; + rc = sqlite3_busy_handler(db2, handler); + affirm(0 == rc); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + affirm(!xDestroyed.value); + rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); + affirm( SQLITE_BUSY == rc); + assert( null == outStmt.get() ); + affirm( 3 == xBusyCalled.value ); + sqlite3_close_v2(db1); + affirm(!xDestroyed.value); + sqlite3_close_v2(db2); + affirm(xDestroyed.value); + try{ + final java.io.File f = new java.io.File(dbName); + f.delete(); + }catch(Exception e){ + /* ignore */ + } + } + + private static void testProgress(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + sqlite3_progress_handler(db, 1, new ProgressHandler(){ + public int xCallback(){ + ++counter.value; + return 0; + } + }); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( counter.value > 0 ); + int nOld = counter.value; + sqlite3_progress_handler(db, 0, null); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( nOld == counter.value ); + sqlite3_close_v2(db); + } + + private static void testCommitHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder hookResult = new ValueHolder<>(0); + final CommitHook theHook = new CommitHook(){ + public int xCommitHook(){ + ++counter.value; + return hookResult.value; + } + }; + CommitHook oldHook = sqlite3_commit_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 2 == counter.value ); + execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;"); + affirm( 2 == counter.value /* NOT invoked if no changes are made */ ); + execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;"); + affirm( 3 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); + affirm( 4 == counter.value ); + + final CommitHook newHook = new CommitHook(){ + public int xCommitHook(){return 0;} + }; + oldHook = sqlite3_commit_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); + affirm( 5 == counter.value ); + hookResult.value = SQLITE_ERROR; + int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); + affirm( SQLITE_CONSTRAINT == rc ); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + private static void testUpdateHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final UpdateHook theHook = new UpdateHook(){ + @SuppressWarnings("unchecked") + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + ++counter.value; + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + UpdateHook oldHook = sqlite3_update_hook(db, theHook); + affirm( null == oldHook ); + expectedOp.value = SQLITE_INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( theHook == oldHook ); + expectedOp.value = SQLITE_DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( null == oldHook ); + + final UpdateHook newHook = new UpdateHook(){ + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + } + }; + oldHook = sqlite3_update_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( newHook == oldHook ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + private static void testRollbackHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final RollbackHook theHook = new RollbackHook(){ + public void xRollbackHook(){ + ++counter.value; + } + }; + RollbackHook oldHook = sqlite3_rollback_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 0 == counter.value ); + execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;"); + affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ ); + + final RollbackHook newHook = new RollbackHook(){ + public void xRollbackHook(){return;} + }; + oldHook = sqlite3_rollback_hook(db, newHook); + affirm( theHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 1 == counter.value ); + oldHook = sqlite3_rollback_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 2 == counter.value ); + int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 0 == rc ); + affirm( 3 == counter.value ); + sqlite3_close_v2(db); + } + + /** + If FTS5 is available, runs FTS5 tests, else returns with no side + effects. If it is available but loading of the FTS5 bits fails, + it throws. + */ + @SuppressWarnings("unchecked") + private static void testFts5() throws Exception { + if( !SQLITE_ENABLE_FTS5 ){ + outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); + return; + } + Exception err = null; + try { + Class t = Class.forName("org.sqlite.jni.TesterFts5"); + java.lang.reflect.Constructor ctor = t.getConstructor(); + ctor.setAccessible(true); + ctor.newInstance() /* will run all tests */; + }catch(ClassNotFoundException e){ + outln("FTS5 classes not loaded."); + err = e; + }catch(NoSuchMethodException e){ + outln("FTS5 tester ctor not found."); + err = e; + }catch(Exception e){ + outln("Instantiation of FTS5 tester threw."); + err = e; + } + if( null != err ){ + outln("Exception: "+err); + err.printStackTrace(); + throw err; + } + } + + private static void testAuthorizer(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder authRc = new ValueHolder<>(0); + final Authorizer auth = new Authorizer(){ + public int xAuth(int op, String s0, String s1, String s2, String s3){ + ++counter.value; + //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3); + return authRc.value; + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + sqlite3_set_authorizer(db, auth); + execSql(db, "UPDATE t SET a=1"); + affirm( 1 == counter.value ); + authRc.value = SQLITE_DENY; + int rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( SQLITE_AUTH==rc ); + // TODO: expand these tests considerably + sqlite3_close(db); + } + + private static void testAutoExtension(){ + final ValueHolder val = new ValueHolder<>(0); + final ValueHolder toss = new ValueHolder<>(null); + final AutoExtension ax = new AutoExtension(){ + public synchronized int xEntryPoint(sqlite3 db){ + ++val.value; + if( null!=toss.value ){ + throw new RuntimeException(toss.value); + } + return 0; + } + }; + int rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close(createNewDb()); + affirm( 1==val.value ); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + sqlite3_reset_auto_extension(); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + // Must not add a new entry + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close( createNewDb() ); + affirm( 3==val.value ); + affirm( sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + sqlite3_close(createNewDb()); + affirm( 3==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + Exception err = null; + toss.value = "Throwing from AutoExtension."; + try{ + createNewDb(); + }catch(Exception e){ + err = e; + } + affirm( err!=null ); + affirm( err.getMessage().indexOf(toss.value)>0 ); + affirm( sqlite3_cancel_auto_extension(ax) ); + } + + private static void testSleep(){ + out("Sleeping briefly... "); + sqlite3_sleep(600); + outln("Woke up."); + } + + public static void main(String[] args) throws Exception { + final long timeStart = System.nanoTime(); + test1(); + if(false) testCompileOption(); + final java.util.List liArgs = + java.util.Arrays.asList(args); + testOpenDb1(); + testOpenDb2(); + testPrepare123(); + testBindFetchInt(); + testBindFetchInt64(); + testBindFetchDouble(); + testBindFetchText(); + testBindFetchBlob(); + testSql(); + testCollation(); + testToUtf8(); + testStatus(); + testUdf1(); + testUdfJavaObject(); + testUdfAggregate(); + testUdfWindow(); + testTrace(); + testBusy(); + testProgress(); + testCommitHook(); + testRollbackHook(); + testUpdateHook(); + testAuthorizer(); + testFts5(); + testAutoExtension(); + //testSleep(); + if(liArgs.indexOf("-v")>0){ + sqlite3_do_something_for_developer(); + //listBoundMethods(); + } + final long timeEnd = System.nanoTime(); + affirm( SQLite3Jni.uncacheJniEnv() ); + affirm( !SQLite3Jni.uncacheJniEnv() ); + outln("Tests done. Metrics:"); + outln("\tAssertions checked: "+affirmCount); + outln("\tDatabases opened: "+metrics.dbOpen); + + int nMethods = 0; + int nNatives = 0; + final java.lang.reflect.Method[] declaredMethods = + SQLite3Jni.class.getDeclaredMethods(); + for(java.lang.reflect.Method m : declaredMethods){ + int mod = m.getModifiers(); + if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + ++nMethods; + if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){ + ++nNatives; + } + } + } + } + outln("\tSQLite3Jni sqlite3_*() methods: "+ + nNatives+" native methods and "+ + (nMethods - nNatives)+" Java impls"); + outln("\tTotal time = " + +((timeEnd - timeStart)/1000000.0)+"ms"); + } +} diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java new file mode 100644 index 000000000..6439768e2 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -0,0 +1,87 @@ +/* +** 2023-08-04 +** +** 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 a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni; +import static org.sqlite.jni.SQLite3Jni.*; +import static org.sqlite.jni.Tester1.*; + +public class TesterFts5 { + + private static void test1(){ + Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); + affirm( null != fea ); + affirm( fea.getNativePointer() != 0 ); + affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/; + + sqlite3 db = createNewDb(); + fts5_api fApi = fts5_api.getInstanceForDb(db); + affirm( fApi != null ); + affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ ); + + execSql(db, new String[] { + "CREATE VIRTUAL TABLE ft USING fts5(a, b);", + "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');", + "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');" + }); + + final String pUserData = "This is pUserData"; + ValueHolder xDestroyCalled = new ValueHolder<>(false); + ValueHolder xFuncCount = new ValueHolder<>(0); + final fts5_extension_function func = new fts5_extension_function(){ + public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]){ + int nCols = ext.xColumnCount(fCx); + affirm( 2 == nCols ); + affirm( nCols == argv.length ); + affirm( ext.xUserData(fCx) == pUserData ); + if(true){ + OutputPointer.String op = new OutputPointer.String(); + for(int i = 0; i < nCols; ++i ){ + int rc = ext.xColumnText(fCx, i, op); + affirm( 0 == rc ); + final String val = op.value; + affirm( val.equals(sqlite3_value_text(argv[i])) ); + //outln("xFunction col "+i+": "+val); + } + } + ++xFuncCount.value; + } + public void xDestroy(){ + xDestroyCalled.value = true; + } + }; + + int rc = fApi.xCreateFunction("myaux", pUserData, func); + affirm( 0==rc ); + + affirm( 0==xFuncCount.value ); + execSql(db, "select myaux(ft,a,b) from ft;"); + affirm( 2==xFuncCount.value ); + affirm( !xDestroyCalled.value ); + sqlite3_close_v2(db); + affirm( xDestroyCalled.value ); + } + + public TesterFts5(){ + int oldAffirmCount = Tester1.affirmCount; + Tester1.affirmCount = 0; + final long timeStart = System.nanoTime(); + test1(); + final long timeEnd = System.nanoTime(); + outln("FTS5 Tests done. Metrics:"); + outln("\tAssertions checked: "+Tester1.affirmCount); + outln("\tTotal time = " + +((timeEnd - timeStart)/1000000.0)+"ms"); + Tester1.affirmCount = oldAffirmCount; + } +} diff --git a/ext/jni/src/org/sqlite/jni/Tracer.java b/ext/jni/src/org/sqlite/jni/Tracer.java new file mode 100644 index 000000000..fa62edbfa --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Tracer.java @@ -0,0 +1,62 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_trace_v2(). +*/ +public interface Tracer { + /** + Achtung: this interface is subject to change because the current + approach to mapping the passed-in natives back to Java is + uncomfortably quirky. + + Called by sqlite3 for various tracing operations, as per + sqlite3_trace_v2(). Note that this interface elides the 2nd + argument to the native trace callback, as that role is better + filled by instance-local state. + + The 2nd argument to this function, if non-0, will be a native + pointer to either an sqlite3 or sqlite3_stmt object, depending on + the first argument (see below). Client code can pass it to the + sqlite3 resp. sqlite3_stmt constructor to create a wrapping + object, if necessary. This API does not do so by default because + tracing can be called frequently, creating such a wrapper for + each call is comparatively expensive, and the objects are + probably only seldom useful. + + The final argument to this function is the "X" argument + documented for sqlite3_trace() and sqlite3_trace_v2(). Its type + depends on value of the first argument: + + - SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a string + containing the prepared SQL, with one caveat: JNI only provides + us with the ability to convert that string to MUTF-8, as + opposed to standard UTF-8, and is cannot be ruled out that that + difference may be significant for certain inputs. The + alternative would be that we first convert it to UTF-16 before + passing it on, but there's no readily-available way to do that + without calling back into the db to peform the conversion + (which would lead to further tracing). + + - SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long + holding an approximate number of nanoseconds the statement took + to run. + + - SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null. + + - SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. + */ + int xCallback(int traceFlag, Object pNative, Object pX); +} diff --git a/ext/jni/src/org/sqlite/jni/UpdateHook.java b/ext/jni/src/org/sqlite/jni/UpdateHook.java new file mode 100644 index 000000000..171e2bdb4 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/UpdateHook.java @@ -0,0 +1,25 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_update_hook(). +*/ +public interface UpdateHook { + /** + Works as documented for the sqlite3_update_hook() callback. + Must not throw. + */ + void xUpdateHook(int opId, String dbName, String tableName, long rowId); +} diff --git a/ext/jni/src/org/sqlite/jni/ValueHolder.java b/ext/jni/src/org/sqlite/jni/ValueHolder.java new file mode 100644 index 000000000..7f6a463ba --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ValueHolder.java @@ -0,0 +1,25 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A helper class which simply holds a single value. Its current use + is for communicating values out of anonymous classes, as doing so + requires a "final" reference. +*/ +public class ValueHolder { + public T value; + public ValueHolder(){} + public ValueHolder(T v){value = v;} +} diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java new file mode 100644 index 000000000..43b3d62de --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -0,0 +1,69 @@ +/* +** 2023-08-05 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_api*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class fts5_api extends NativePointerHolder { + /* Only invoked from JNI */ + private fts5_api(){} + public final int iVersion = 2; + + /** + Returns the fts5_api instance associated with the given db, or + null if something goes horribly wrong. + */ + public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db); + + // int (*xCreateTokenizer)( + // fts5_api *pApi, + // const char *zName, + // void *pContext, + // fts5_tokenizer *pTokenizer, + // void (*xDestroy)(void*) + // ); + + // /* Find an existing tokenizer */ + // int (*xFindTokenizer)( + // fts5_api *pApi, + // const char *zName, + // void **ppContext, + // fts5_tokenizer *pTokenizer + // ); + + // /* Create a new auxiliary function */ + // int (*xCreateFunction)( + // fts5_api *pApi, + // const char *zName, + // void *pContext, + // fts5_extension_function xFunction, + // void (*xDestroy)(void*) + // ); + + public synchronized native int xCreateFunction(@NotNull String name, + @Nullable Object userData, + @NotNull fts5_extension_function xFunction); + + public int xCreateFunction(@NotNull String name, + @NotNull fts5_extension_function xFunction){ + return xCreateFunction(name, null, xFunction); + } + +} diff --git a/ext/jni/src/org/sqlite/jni/fts5_extension_function.java b/ext/jni/src/org/sqlite/jni/fts5_extension_function.java new file mode 100644 index 000000000..0e273119f --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_extension_function.java @@ -0,0 +1,37 @@ +/* +** 2023-08-05 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + JNI-level wrapper for C's fts5_extension_function type. + +*/ +public abstract class fts5_extension_function { + // typedef void (*fts5_extension_function)( + // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + // Fts5Context *pFts, /* First arg to pass to pApi functions */ + // sqlite3_context *pCtx, /* Context for returning result/error */ + // int nVal, /* Number of values in apVal[] array */ + // sqlite3_value **apVal /* Array of trailing arguments */ + // ); + + /** + The callback implementation, corresponding to the xFunction + argument of C's fts5_api::xCreateFunction(). + */ + public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]); + //! Optionally override + public void xDestroy(){} +} diff --git a/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java new file mode 100644 index 000000000..097a0cc05 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java @@ -0,0 +1,49 @@ +/* +** 2023-08-05 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_tokenizer*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class fts5_tokenizer extends NativePointerHolder { + /* Only invoked by JNI */ + private fts5_tokenizer(){} + + // int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + // void (*xDelete)(Fts5Tokenizer*); + + public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags, + @NotNull byte pText[], + @NotNull Fts5.xTokenizeCallback callback); + + + // int (*xTokenize)(Fts5Tokenizer*, + // void *pCtx, + // int flags, /* Mask of FTS5_TOKENIZE_* flags */ + // const char *pText, int nText, + // int (*xToken)( + // void *pCtx, /* Copy of 2nd argument to xTokenize() */ + // int tflags, /* Mask of FTS5_TOKEN_* flags */ + // const char *pToken, /* Pointer to buffer containing token */ + // int nToken, /* Size of token in bytes */ + // int iStart, /* Byte offset of token within input text */ + // int iEnd /* Byte offset of end of token within input text */ + // ) + // ); +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/sqlite3.java new file mode 100644 index 000000000..cfc6c08d4 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3.java @@ -0,0 +1,37 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for communicating C-level (sqlite3*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java + and C via JNI. +*/ +public final class sqlite3 extends NativePointerHolder { + // Only invoked from JNI + private sqlite3(){} + + public String toString(){ + long ptr = getNativePointer(); + if( 0==ptr ){ + return sqlite3.class.getSimpleName()+"@null"; + } + String fn = SQLite3Jni.sqlite3_db_filename(this, "main"); + return sqlite3.class.getSimpleName() + +"@"+String.format("0x%08x",ptr) + +"["+((null == fn) ? "" : fn)+"]" + ; + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java new file mode 100644 index 000000000..a61ff21c7 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -0,0 +1,66 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + sqlite3_context instances are used in conjunction with user-defined + SQL functions (a.k.a. UDFs). +*/ +public final class sqlite3_context extends NativePointerHolder { + /** + For use only by the JNI layer. It's permitted to set this even + though it's private. + */ + private long aggregateContext = 0; + + /** + getAggregateContext() corresponds to C's + sqlite3_aggregate_context(), with a slightly different interface + to account for cross-language differences. It serves the same + purposes in a slightly different way: it provides a key which is + stable across invocations of "matching sets" of a UDF's callbacks, + such that all calls into those callbacks can determine which "set" + of those calls they belong to. + + If this object is being used in the context of an aggregate or + window UDF, this function returns a non-0 value which is distinct + for each set of UDF callbacks from a single invocation of the + UDF, otherwise it returns 0. The returned value is only only + valid within the context of execution of a single SQL statement, + and may be re-used by future invocations of the UDF in different + SQL statements. + + Consider this SQL, where MYFUNC is a user-defined aggregate function: + + SELECT MYFUNC(A), MYFUNC(B) FROM T; + + The xStep() and xFinal() methods of the callback need to be able + to differentiate between those two invocations in order to + perform their work properly. The value returned by + getAggregateContext() will be distinct for each of those + invocations of MYFUNC() and is intended to be used as a lookup + key for mapping callback invocations to whatever client-defined + state is needed by the UDF. + + There is one case where this will return 0 in the context of an + aggregate or window function: if the result set has no rows, + the UDF's xFinal() will be called without any other x...() members + having been called. In that one case, no aggregate context key will + have been generated. xFinal() implementations need to be prepared to + accept that condition as legal. + */ + public long getAggregateContext(){ + return aggregateContext; + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java new file mode 100644 index 000000000..d67230137 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java @@ -0,0 +1,25 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for communicating C-level (sqlite3_stmt*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class sqlite3_stmt extends NativePointerHolder { + // Only invoked from JNI. + private sqlite3_stmt(){} +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_value.java b/ext/jni/src/org/sqlite/jni/sqlite3_value.java new file mode 100644 index 000000000..2cfb32ff1 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_value.java @@ -0,0 +1,19 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +public final class sqlite3_value extends NativePointerHolder { + //! Invoked only from JNI. + private sqlite3_value(){} +} diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java new file mode 100644 index 000000000..ffdb867d9 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -0,0 +1,1421 @@ +/* +** 2023-08-08 +** +** 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 the main application entry pointer for the +** SQLTester framework. +*/ +package org.sqlite.jni.tester; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.nio.charset.StandardCharsets; +import java.util.regex.*; +import org.sqlite.jni.*; +import static org.sqlite.jni.SQLite3Jni.*; +import org.sqlite.jni.sqlite3; + + +/** + Modes for how to escape (or not) column values and names from + SQLTester.execSql() to the result buffer output. +*/ +enum ResultBufferMode { + //! Do not append to result buffer + NONE, + //! Append output escaped. + ESCAPED, + //! Append output as-is + ASIS +}; + +/** + Modes to specify how to emit multi-row output from + SQLTester.execSql() to the result buffer. +*/ +enum ResultRowMode { + //! Keep all result rows on one line, space-separated. + ONELINE, + //! Add a newline between each result row. + NEWLINE +}; + +/** + Base exception type for test-related failures. +*/ +class SQLTesterException extends RuntimeException { + private boolean bFatal = false; + + SQLTesterException(String msg){ + super(msg); + } + + protected SQLTesterException(String msg, boolean fatal){ + super(msg); + bFatal = fatal; + } + + /** + Indicates whether the framework should consider this exception + type as immediately fatal to the test run or not. + */ + final boolean isFatal(){ return bFatal; } +} + +class DbException extends SQLTesterException { + DbException(sqlite3 db, int rc, boolean closeDb){ + super("DB error #"+rc+": "+sqlite3_errmsg(db),true); + if( closeDb ) sqlite3_close_v2(db); + } + DbException(sqlite3 db, int rc){ + this(db, rc, false); + } +} + +/** + Generic test-failed exception. + */ +class TestScriptFailed extends SQLTesterException { + public TestScriptFailed(TestScript ts, String msg){ + super(ts.getOutputPrefix()+": "+msg, true); + } +} + +/** + Thrown when an unknown test command is encountered in a script. +*/ +class UnknownCommand extends SQLTesterException { + public UnknownCommand(TestScript ts, String cmd){ + super(ts.getOutputPrefix()+": unknown command: "+cmd, false); + } +} + +/** + Thrown when an "incompatible directive" is found in a script. This + can be the presence of a C-preprocessor construct, specific + metadata tags within a test script's header, or specific test + constructs which are incompatible with this particular + implementation. +*/ +class IncompatibleDirective extends SQLTesterException { + public IncompatibleDirective(TestScript ts, String line){ + super(ts.getOutputPrefix()+": incompatible directive: "+line, false); + } +} + +/** + Console output utility class. +*/ +class Outer { + private int verbosity = 0; + + static void out(Object val){ + System.out.print(val); + } + + Outer out(Object... vals){ + for(Object v : vals) out(v); + return this; + } + + Outer outln(Object... vals){ + out(vals).out("\n"); + return this; + } + + Outer verbose(Object... vals){ + if(verbosity>0){ + out("VERBOSE",(verbosity>1 ? "+: " : ": ")).outln(vals); + } + return this; + } + + void setVerbosity(int level){ + verbosity = level; + } + + int getVerbosity(){ + return verbosity; + } + + public boolean isVerbose(){return verbosity > 0;} + +} + +/** + This class provides an application which aims to implement the + rudimentary SQL-driven test tool described in the accompanying + test-script-interpreter.md. + + This is a work in progress. + + + An instance of this application provides a core set of services + which TestScript instances use for processing testing logic. + TestScripts, in turn, delegate the concrete test work to Command + objects, which the TestScript parses on their behalf. +*/ +public class SQLTester { + //! List of input script files. + private final java.util.List listInFiles = new ArrayList<>(); + //! Console output utility. + private final Outer outer = new Outer(); + //! Test input buffer. + private final StringBuilder inputBuffer = new StringBuilder(); + //! Test result buffer. + private final StringBuilder resultBuffer = new StringBuilder(); + //! Buffer for REQUIRED_PROPERTIES pragmas. + private final StringBuilder dbInitSql = new StringBuilder(); + //! Output representation of SQL NULL. + private String nullView = "nil"; + //! Total tests run. + private int nTotalTest = 0; + //! Total test script files run. + private int nTestFile = 0; + //! Number of scripts which were aborted. + private int nAbortedScript = 0; + //! Per-script test counter. + private int nTest = 0; + //! True to enable column name output from execSql() + private boolean emitColNames; + //! True to keep going regardless of how a test fails. + private boolean keepGoing = false; + //! The list of available db handles. + private final sqlite3[] aDb = new sqlite3[7]; + //! Index into aDb of the current db. + private int iCurrentDb = 0; + //! Name of the default db, re-created for each script. + private final String initialDbName = "test.db"; + + + public SQLTester(){ + reset(); + } + + void setVerbosity(int level){ + this.outer.setVerbosity( level ); + } + int getVerbosity(){ + return this.outer.getVerbosity(); + } + boolean isVerbose(){ + return this.outer.isVerbose(); + } + + void outputColumnNames(boolean b){ emitColNames = b; } + + void verbose(Object... vals){ + outer.verbose(vals); + } + + void outln(Object... vals){ + outer.outln(vals); + } + + void out(Object... vals){ + outer.out(vals); + } + + //! Adds the given test script to the to-test list. + public void addTestScript(String filename){ + listInFiles.add(filename); + //verbose("Added file ",filename); + } + + private void setupInitialDb() throws DbException { + if( null==aDb[0] ){ + Util.unlink(initialDbName); + openDb(0, initialDbName, true); + }else{ + outln("WARNING: setupInitialDb() unexpectedly ", + "triggered while it is opened."); + } + } + + static final String[] startEmoji = { + "🚴", "🏄", "🏇", "🤸", "⛹", "🏊", "⛷", "🧗", "🏋" + }; + static final int nStartEmoji = startEmoji.length; + static int iStartEmoji = 0; + + private static String nextStartEmoji(){ + return startEmoji[iStartEmoji++ % nStartEmoji]; + } + + public void runTests() throws Exception { + final long tStart = System.nanoTime(); + for(String f : listInFiles){ + reset(); + ++nTestFile; + final TestScript ts = new TestScript(f); + outln(nextStartEmoji(), " starting [",f,"]"); + boolean threw = false; + final long timeStart = System.nanoTime(); + try{ + ts.run(this); + }catch(SQLTesterException e){ + threw = true; + outln("🔥EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); + ++nAbortedScript; + if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); + else if( e.isFatal() ) throw e; + }finally{ + final long timeEnd = System.nanoTime(); + outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ", + ((timeEnd-timeStart)/1000000.0),"ms."); + //ts.getFilename()); + } + } + final long tEnd = System.nanoTime(); + outln("Total run-time: ",((tEnd-tStart)/1000000.0),"ms"); + Util.unlink(initialDbName); + } + + private StringBuilder clearBuffer(StringBuilder b){ + b.setLength(0);; + return b; + } + + StringBuilder clearInputBuffer(){ + return clearBuffer(inputBuffer); + } + + StringBuilder clearResultBuffer(){ + return clearBuffer(resultBuffer); + } + + StringBuilder getInputBuffer(){ return inputBuffer; } + + void appendInput(String n, boolean addNL){ + inputBuffer.append(n); + if(addNL) inputBuffer.append('\n'); + } + + void appendResult(String n, boolean addNL){ + resultBuffer.append(n); + if(addNL) resultBuffer.append('\n'); + } + + void appendDbInitSql(String n) throws DbException { + dbInitSql.append(n).append('\n'); + if( null!=getCurrentDb() ){ + //outln("RUNNING DB INIT CODE: ",n); + execSql(null, true, ResultBufferMode.NONE, null, n); + } + } + String getDbInitSql(){ return dbInitSql.toString(); } + + String getInputText(){ return inputBuffer.toString(); } + + String getResultText(){ return resultBuffer.toString(); } + + private String takeBuffer(StringBuilder b){ + final String rc = b.toString(); + clearBuffer(b); + return rc; + } + + String takeInputBuffer(){ return takeBuffer(inputBuffer); } + + String takeResultBuffer(){ return takeBuffer(resultBuffer); } + + int getCurrentDbId(){ return iCurrentDb; } + + SQLTester affirmDbId(int n) throws IndexOutOfBoundsException { + if(n<0 || n>=aDb.length){ + throw new IndexOutOfBoundsException("illegal db number: "+n); + } + return this; + } + + sqlite3 setCurrentDb(int n) throws Exception{ + return affirmDbId(n).aDb[n]; + } + + sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; } + + sqlite3 getDbById(int id) throws Exception{ + return affirmDbId(id).aDb[id]; + } + + void closeDb(int id) { + final sqlite3 db = affirmDbId(id).aDb[id]; + if( null != db ){ + sqlite3_close_v2(db); + aDb[id] = null; + } + } + + void closeDb() { closeDb(iCurrentDb); } + + void closeAllDbs(){ + for(int i = 0; i 0){ + //outln("RUNNING DB INIT CODE: ",dbInitSql.toString()); + rc = execSql(db, false, ResultBufferMode.NONE, + null, dbInitSql.toString()); + } + if( 0!=rc ){ + throw new DbException(db, rc, true); + } + return aDb[iCurrentDb] = db; + } + + sqlite3 openDb(int slot, String name, boolean createIfNeeded) throws DbException { + affirmDbId(slot); + iCurrentDb = slot; + return openDb(name, createIfNeeded); + } + + /** + Resets all tester context state except for that related to + tracking running totals. + */ + void reset(){ + clearInputBuffer(); + clearResultBuffer(); + clearBuffer(dbInitSql); + closeAllDbs(); + nTest = 0; + nullView = "nil"; + emitColNames = false; + iCurrentDb = 0; + dbInitSql.append("SELECT 1;"); + } + + void setNullValue(String v){nullView = v;} + + /** + If true, encountering an unknown command in a script causes the + remainder of the script to be skipped, rather than aborting the + whole script run. + */ + boolean skipUnknownCommands(){ + // Currently hard-coded. Potentially a flag someday. + return true; + } + + void incrementTestCounter(){ ++nTest; ++nTotalTest; } + + //! "Special" characters - we have to escape output if it contains any. + static final Pattern patternSpecial = Pattern.compile( + "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]" + ); + //! Either of '{' or '}'. + static final Pattern patternSquiggly = Pattern.compile("[{}]"); + + /** + Returns v or some escaped form of v, as defined in the tester's + spec doc. + */ + String escapeSqlValue(String v){ + if( "".equals(v) ) return "{}"; + Matcher m = patternSpecial.matcher(v); + if( !m.find() ){ + return v /* no escaping needed */; + } + m = patternSquiggly.matcher(v); + if( !m.find() ){ + return "{"+v+"}"; + } + final StringBuilder sb = new StringBuilder("\""); + final int n = v.length(); + for(int i = 0; i < n; ++i){ + final char ch = v.charAt(i); + switch(ch){ + case '\\': sb.append("\\\\"); break; + case '"': sb.append("\\\""); break; + default: + //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch)); + if( (int)ch < 32 ) sb.append(String.format("\\%03o", (int)ch)); + else sb.append(ch); + break; + } + } + sb.append("\""); + return sb.toString(); + } + + private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){ + sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)).append(' '); + final String msg = escapeSqlValue(sqlite3_errmsg(db)); + if( '{' == msg.charAt(0) ){ + sb.append(msg); + }else{ + sb.append('{').append(msg).append('}'); + } + } + + /** + Runs SQL on behalf of test commands and outputs the results following + the very specific rules of the test framework. + + If db is null, getCurrentDb() is assumed. If throwOnError is true then + any db-side error will result in an exception, else they result in + the db's result code. + + appendMode specifies how/whether to append results to the result + buffer. lineMode specifies whether to output all results in a + single line or one line per row. If appendMode is + ResultBufferMode.NONE then lineMode is ignored and may be null. + */ + public int execSql(sqlite3 db, boolean throwOnError, + ResultBufferMode appendMode, ResultRowMode lineMode, + String sql) throws SQLTesterException { + if( null==db && null==aDb[0] ){ + // Delay opening of the initial db to enable tests to change its + // name and inject on-connect code via, e.g., the MEMDB + // directive. this setup as the potential to misinteract with + // auto-extension timing and must be done carefully. + setupInitialDb(); + } + final OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + if( null==db ) db = getCurrentDb(); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + int spacing = 0 /* emit a space for --result if>0 */ ; + final StringBuilder sb = (ResultBufferMode.NONE==appendMode) + ? null : resultBuffer; + //outln("sqlChunk len= = ",sqlChunk.length); + try{ + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + /*outln("PREPARE rc ",rc," oTail=",oTail.get(),": ", + new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ + if( 0!=rc ){ + if(throwOnError){ + throw new DbException(db, rc); + }else if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; + } + pos = oTail.value; + stmt = outStmt.take(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + if( null!=sb ){ + // Add the output to the result buffer... + final int nCol = sqlite3_column_count(stmt); + String colName = null, val = null; + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + for(int i = 0; i < nCol; ++i){ + if( spacing++ > 0 ) sb.append(' '); + if( emitColNames ){ + colName = sqlite3_column_name(stmt, i); + switch(appendMode){ + case ASIS: + sb.append( colName ); + break; + case ESCAPED: + sb.append( escapeSqlValue(colName) ); + break; + default: + throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode); + } + sb.append(' '); + } + val = sqlite3_column_text16(stmt, i); + if( null==val ){ + sb.append( nullView ); + continue; + } + switch(appendMode){ + case ASIS: + sb.append( val ); + break; + case ESCAPED: + sb.append( escapeSqlValue(val) ); + break; + default: + throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode); + } + } + if( ResultRowMode.NEWLINE == lineMode ){ + spacing = 0; + sb.append('\n'); + } + } + }else{ + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} + } + sqlite3_finalize(stmt); + stmt = null; + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + else if( rc!=0 ){ + if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; + } + } + }finally{ + sqlite3_finalize(stmt); + } + if( 0!=rc && throwOnError ){ + throw new DbException(db, rc); + } + return rc; + } + + public static void main(String[] argv) throws Exception{ + installCustomExtensions(); + boolean dumpInternals = false; + final SQLTester t = new SQLTester(); + for(String a : argv){ + if(a.startsWith("-")){ + final String flag = a.replaceFirst("-+",""); + if( flag.equals("verbose") ){ + // Use --verbose up to 3 times + t.setVerbosity(t.getVerbosity() + 1); + }else if( flag.equals("keep-going") ){ + t.keepGoing = true; + }else if( flag.equals("internals") ){ + dumpInternals = true; + }else{ + throw new IllegalArgumentException("Unhandled flag: "+flag); + } + continue; + } + t.addTestScript(a); + } + final AutoExtension ax = new AutoExtension() { + private final SQLTester tester = t; + public int xEntryPoint(sqlite3 db){ + final String init = tester.getDbInitSql(); + if( !init.isEmpty() ){ + tester.execSql(db, true, ResultBufferMode.NONE, null, init); + } + return 0; + } + }; + sqlite3_auto_extension(ax); + try { + t.runTests(); + }finally{ + sqlite3_cancel_auto_extension(ax); + t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); + if( t.nAbortedScript > 0 ){ + t.outln("Aborted ",t.nAbortedScript," script(s)."); + } + if( dumpInternals ){ + sqlite3_do_something_for_developer(); + } + } + } + + /** + Internal impl of the public strglob() method. Neither argument + may be NULL and both _MUST_ be NUL-terminated. + */ + private static native int strglob(byte[] glob, byte[] txt); + + /** + Works essentially the same as sqlite3_strglob() except that the + glob character '#' matches a sequence of one or more digits. It + does not match when it appears at the start or middle of a series + of digits, e.g. "#23" or "1#3", but will match at the end, + e.g. "12#". + */ + static int strglob(String glob, String txt){ + return strglob( + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8) + ); + } + + /** + Sets up C-side components needed by the test framework. This must + not be called until main() is triggered so that it does not + interfere with library clients who don't use this class. + */ + static native void installCustomExtensions(); + static { + System.loadLibrary("sqlite3-jni") + /* Interestingly, when SQLTester is the main app, we have to + load that lib from here. The same load from SQLite3Jni does + not happen early enough. Without this, + installCustomExtensions() is an unresolved symbol. */; + } + +} + +/** + General utilities for the SQLTester bits. +*/ +final class Util { + + //! Throws a new T, appending all msg args into a string for the message. + static void toss(Class errorType, Object... msg) throws Exception { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + final java.lang.reflect.Constructor ctor = + errorType.getConstructor(String.class); + throw ctor.newInstance(sb.toString()); + } + + static void toss(Object... msg) throws Exception{ + toss(RuntimeException.class, msg); + } + + //! Tries to delete the given file, silently ignoring failure. + static void unlink(String filename){ + try{ + final java.io.File f = new java.io.File(filename); + f.delete(); + }catch(Exception e){ + /* ignore */ + } + } + + /** + Appends all entries in argv[1..end] into a space-separated + string, argv[0] is not included because it's expected to be a + command name. + */ + static String argvToString(String[] argv){ + StringBuilder sb = new StringBuilder(); + for(int i = 1; i < argv.length; ++i ){ + if( i>1 ) sb.append(" "); + sb.append( argv[i] ); + } + return sb.toString(); + } + +} + +/** + Base class for test script commands. It provides a set of utility + APIs for concrete command implementations. + + Each subclass must have a public no-arg ctor and must implement + the process() method which is abstract in this class. + + Commands are intended to be stateless, except perhaps for counters + and similar internals. Specifically, no state which changes the + behavior between any two invocations of process() should be + retained. +*/ +abstract class Command { + protected Command(){} + + /** + Must process one command-unit of work and either return + (on success) or throw (on error). + + The first two arguments specify the context of the test. The TestScript + provides the content of the test and the SQLTester providers the sandbox + in which that script is being evaluated. + + argv is a list with the command name followed by any arguments to + that command. The argcCheck() method from this class provides + very basic argc validation. + */ + public abstract void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception; + + /** + If argv.length-1 (-1 because the command's name is in argv[0]) does not + fall in the inclusive range (min,max) then this function throws. Use + a max value of -1 to mean unlimited. + */ + protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{ + int argc = argv.length-1; + if(argc=0 && argc>max)){ + if( min==max ){ + ts.toss(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ + ts.toss(argv[0]," requires ",min,"-",max," arguments."); + }else{ + ts.toss(argv[0]," requires at least ",min," arguments."); + } + } + } + + /** + Equivalent to argcCheck(argv,argc,argc). + */ + protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{ + argcCheck(ts, argv, argc, argc); + } +} + +//! --close command +class CloseDbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + Integer id; + if(argv.length>1){ + String arg = argv[1]; + if("all".equals(arg)){ + t.closeAllDbs(); + return; + } + else{ + id = Integer.parseInt(arg); + } + }else{ + id = t.getCurrentDbId(); + } + t.closeDb(id); + } +} + +//! --column-names command +class ColumnNamesCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.outputColumnNames( Integer.parseInt(argv[1])!=0 ); + } +} + +//! --db command +class DbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.setCurrentDb( Integer.parseInt(argv[1]) ); + } +} + +//! --glob command +class GlobCommand extends Command { + private boolean negate = false; + public GlobCommand(){} + protected GlobCommand(boolean negate){ this.negate = negate; } + + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText(); + final String sArgs = Util.argvToString(argv); + //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + final String glob = Util.argvToString(argv); + rc = SQLTester.strglob(glob, result); + if( (negate && 0==rc) || (!negate && 0!=rc) ){ + ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); + } + } +} + +//! --json command +class JsonCommand extends ResultCommand { + public JsonCommand(){ super(ResultBufferMode.ASIS); } +} + +//! --json-block command +class JsonBlockCommand extends TableResultCommand { + public JsonBlockCommand(){ super(true); } +} + +//! --new command +class NewDbCommand extends OpenDbCommand { + public NewDbCommand(){ super(true); } +} + +//! Placeholder dummy/no-op commands +class NoopCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + } +} + +//! --notglob command +class NotGlobCommand extends GlobCommand { + public NotGlobCommand(){ + super(true); + } +} + +//! --null command +class NullCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.setNullValue( argv[1] ); + } +} + +//! --open command +class OpenDbCommand extends Command { + private boolean createIfNeeded = false; + public OpenDbCommand(){} + protected OpenDbCommand(boolean c){createIfNeeded = c;} + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.openDb(argv[1], createIfNeeded); + } +} + +//! --print command +class PrintCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + st.out(ts.getOutputPrefix(),": "); + if( 1==argv.length ){ + st.out( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + } +} + +//! --result command +class ResultCommand extends Command { + private final ResultBufferMode bufferMode; + protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand(){ this(ResultBufferMode.ESCAPED); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //ts.verbose2(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nExpected result:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --run command +class RunCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!=rc && t.isVerbose() ){ + String msg = sqlite3_errmsg(db); + ts.verbose1(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand extends Command { + private final boolean jsonMode; + protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand(){ this(false); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0); + t.incrementTestCounter(); + String body = ts.fetchCommandBody(t); + if( null==body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end."); + }else{ + int n = body.length(); + body = body.substring(0, n-6); + } + final String[] globs = body.split("\\s*\\n\\s*"); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = globs[i].replaceAll("\\s+"," ").trim(); + //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --testcase command +class TestCaseCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + ts.setTestCaseName(argv[1]); + t.clearResultBuffer(); + t.clearInputBuffer(); + } +} + +//! --verbosity command +class VerbosityCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + ts.setVerbosity( Integer.parseInt(argv[1]) ); + } +} + +class CommandDispatcher { + + private static java.util.Map commandMap = + new java.util.HashMap<>(); + + /** + Returns a (cached) instance mapped to name, or null if no match + is found. + */ + static Command getCommandByName(String name){ + Command rv = commandMap.get(name); + if( null!=rv ) return rv; + switch(name){ + case "close": rv = new CloseDbCommand(); break; + case "column-names": rv = new ColumnNamesCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; + case "tableresult": rv = new TableResultCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; + case "verbosity": rv = new VerbosityCommand(); break; + default: rv = null; break; + } + if( null!=rv ) commandMap.put(name, rv); + return rv; + } + + /** + Treats argv[0] as a command name, looks it up with + getCommandByName(), and calls process() on that instance, passing + it arguments given to this function. + */ + static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{ + final Command cmd = getCommandByName(argv[0]); + if(null == cmd){ + throw new UnknownCommand(ts, argv[0]); + } + cmd.process(tester, ts, argv); + } +} + + +/** + This class represents a single test script. It handles (or + delegates) its the reading-in and parsing, but the details of + evaluation are delegated elsewhere. +*/ +class TestScript { + //! input file + private String filename = null; + //! Name pulled from the SCRIPT_MODULE_NAME directive of the file + private String moduleName = null; + //! Current test case name. + private String testCaseName = null; + //! Content buffer state. + private final Cursor cur = new Cursor(); + //! Utility for console output. + private final Outer outer = new Outer(); + + //! File content and parse state. + private static final class Cursor { + private final StringBuilder sb = new StringBuilder(); + byte[] src = null; + //! Current position in this.src. + int pos = 0; + //! Current line number. Starts at 0 for internal reasons and will + // line up with 1-based reality once parsing starts. + int lineNo = 0 /* yes, zero */; + //! Putback value for this.pos. + int putbackPos = 0; + //! Putback line number + int putbackLineNo = 0; + //! Peeked-to pos, used by peekLine() and consumePeeked(). + int peekedPos = 0; + //! Peeked-to line number. + int peekedLineNo = 0; + + //! Restore parsing state to the start of the stream. + void rewind(){ + sb.setLength(0); + pos = lineNo = putbackPos = putbackLineNo = peekedPos = peekedLineNo = 0 + /* kinda missing memset() about now. */; + } + } + + private byte[] readFile(String filename) throws Exception { + return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); + } + + /** + Initializes the script with the content of the given file. + Throws if it cannot read the file. + */ + public TestScript(String filename) throws Exception{ + this.filename = filename; + setVerbosity(2); + cur.src = readFile(filename); + } + + public String getFilename(){ + return filename; + } + + public String getModuleName(){ + return moduleName; + } + + /** + Verbosity level 0 produces no debug/verbose output. Level 1 produces + some and level 2 produces more. + */ + public void setVerbosity(int level){ + outer.setVerbosity(level); + } + + public String getOutputPrefix(){ + String rc = "["+(moduleName==null ? filename : moduleName)+"]"; + if( null!=testCaseName ) rc += "["+testCaseName+"]"; + return rc + " line "+ cur.lineNo; + } + + static final String[] verboseLabel = {"🔈",/*"🔉",*/"🔊","📢"}; + //! Output vals only if level<=current verbosity level. + private TestScript verboseN(int level, Object... vals){ + final int verbosity = outer.getVerbosity(); + if(verbosity>=level){ + outer.out( verboseLabel[level-1], getOutputPrefix(), " ",level,": " + ).outln(vals); + } + return this; + } + + TestScript verbose1(Object... vals){return verboseN(1,vals);} + TestScript verbose2(Object... vals){return verboseN(2,vals);} + TestScript verbose3(Object... vals){return verboseN(3,vals);} + + private void reset(){ + testCaseName = null; + cur.rewind(); + } + + void setTestCaseName(String n){ testCaseName = n; } + + /** + Returns the next line from the buffer, minus the trailing EOL. + + Returns null when all input is consumed. Throws if it reads + illegally-encoded input, e.g. (non-)characters in the range + 128-256. + */ + String getLine(){ + if( cur.pos==cur.src.length ){ + return null /* EOF */; + } + cur.putbackPos = cur.pos; + cur.putbackLineNo = cur.lineNo; + cur.sb.setLength(0); + final boolean skipLeadingWs = false; + byte b = 0, prevB = 0; + int i = cur.pos; + if(skipLeadingWs) { + /* Skip any leading spaces, including newlines. This will eliminate + blank lines. */ + for(; i < cur.src.length; ++i, prevB=b){ + b = cur.src[i]; + switch((int)b){ + case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; + case 10/*NL*/: ++cur.lineNo; continue; + default: break; + } + break; + } + if( i==cur.src.length ){ + return null /* EOF */; + } + } + boolean doBreak = false; + final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; + int nChar = 0 /* number of bytes in the char */; + for(; i < cur.src.length && !doBreak; ++i){ + b = cur.src[i]; + switch( (int)b ){ + case 13/*CR*/: continue; + case 10/*NL*/: + ++cur.lineNo; + if(cur.sb.length()>0) doBreak = true; + // Else it's an empty string + break; + default: + /* Multi-byte chars need to be gathered up and appended at + one time. Appending individual bytes to the StringBuffer + appends their integer value. */ + nChar = 1; + switch( b & 0xF0 ){ + case 0xC0: nChar = 2; break; + case 0xE0: nChar = 3; break; + case 0xF0: nChar = 4; break; + default: + if( b > 127 ) this.toss("Invalid character (#"+(int)b+")."); + break; + } + if( 1==nChar ){ + cur.sb.append((char)b); + }else{ + for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x]; + cur.sb.append(new String(Arrays.copyOf(aChar, nChar), + StandardCharsets.UTF_8)); + i += nChar-1; + } + break; + } + } + cur.pos = i; + final String rv = cur.sb.toString(); + if( i==cur.src.length && 0==rv.length() ){ + return null /* EOF */; + } + return rv; + }/*getLine()*/ + + /** + Fetches the next line then resets the cursor to its pre-call + state. consumePeeked() can be used to consume this peeked line + without having to re-parse it. + */ + String peekLine(){ + final int oldPos = cur.pos; + final int oldPB = cur.putbackPos; + final int oldPBL = cur.putbackLineNo; + final int oldLine = cur.lineNo; + final String rc = getLine(); + cur.peekedPos = cur.pos; + cur.peekedLineNo = cur.lineNo; + cur.pos = oldPos; + cur.lineNo = oldLine; + cur.putbackPos = oldPB; + cur.putbackLineNo = oldPBL; + return rc; + } + + /** + Only valid after calling peekLine() and before calling getLine(). + This places the cursor to the position it would have been at had + the peekLine() had been fetched with getLine(). + */ + void consumePeeked(){ + cur.pos = cur.peekedPos; + cur.lineNo = cur.peekedLineNo; + } + + /** + Restores the cursor to the position it had before the previous + call to getLine(). + */ + void putbackLine(){ + cur.pos = cur.putbackPos; + cur.lineNo = cur.putbackLineNo; + } + + private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{ + int nOk = 0; + for(String rp : props){ + verbose1("REQUIRED_PROPERTIES: ",rp); + switch(rp){ + case "RECURSIVE_TRIGGERS": + t.appendDbInitSql("pragma recursive_triggers=on;"); + ++nOk; + break; + case "TEMPSTORE_FILE": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + t.appendDbInitSql("pragma temp_store=1;"); + ++nOk; + break; + case "TEMPSTORE_MEM": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + t.appendDbInitSql("pragma temp_store=0;"); + ++nOk; + break; + default: + break; + } + } + return props.length == nOk; + } + + private static final Pattern patternRequiredProperties = + Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); + private static final Pattern patternScriptModuleName = + Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternMixedModuleName = + Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternCommand = + Pattern.compile("^--(([a-z-]+)( .*)?)$"); + + /** + Looks for "directives." If a compatible one is found, it is + processed and this function returns. If an incompatible one is found, + a description of it is returned and processing of the test must + end immediately. + */ + private void checkForDirective( + SQLTester tester, String line + ) throws IncompatibleDirective { + if(line.startsWith("#")){ + throw new IncompatibleDirective(this, "C-preprocessor input: "+line); + }else if(line.startsWith("---")){ + new IncompatibleDirective(this, "triple-dash: "+line); + } + Matcher m = patternScriptModuleName.matcher(line); + if( m.find() ){ + moduleName = m.group(1); + return; + } + m = patternRequiredProperties.matcher(line); + if( m.find() ){ + final String rp = m.group(1); + //if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); + //} + } + m = patternMixedModuleName.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); + } + if( line.indexOf("\n|")>=0 ){ + throw new IncompatibleDirective(this, "newline-pipe combination."); + } + return; + } + + boolean isCommandLine(String line, boolean checkForImpl){ + final Matcher m = patternCommand.matcher(line); + boolean rc = m.find(); + if( rc && checkForImpl ){ + rc = null!=CommandDispatcher.getCommandByName(m.group(2)); + } + return rc; + } + + /** + If line looks like a command, returns an argv for that command + invocation, else returns null. + */ + String[] getCommandArgv(String line){ + final Matcher m = patternCommand.matcher(line); + return m.find() ? m.group(1).trim().split("\\s+") : null; + } + + /** + Fetches lines until the next recognized command. Throws if + checkForDirective() does. Returns null if there is no input or + it's only whitespace. The returned string retains all whitespace. + + Note that "subcommands", --command-like constructs in the body + which do not match a known command name are considered to be + content, not commands. + */ + String fetchCommandBody(SQLTester tester){ + final StringBuilder sb = new StringBuilder(); + String line; + while( (null != (line = peekLine())) ){ + checkForDirective(tester, line); + if( !isCommandLine(line, true) ){ + sb.append(line).append("\n"); + consumePeeked(); + }else{ + break; + } + } + line = sb.toString(); + return line.trim().isEmpty() ? null : line; + } + + private void processCommand(SQLTester t, String[] argv) throws Exception{ + verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); + if(outer.getVerbosity()>1){ + final String input = t.getInputText(); + if( !input.isEmpty() ) verbose3("Input buffer = ",input); + } + CommandDispatcher.dispatch(t, this, argv); + } + + void toss(Object... msg) throws TestScriptFailed { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + throw new TestScriptFailed(this, sb.toString()); + } + + /** + Runs this test script in the context of the given tester object. + */ + public boolean run(SQLTester tester) throws Exception { + reset(); + setVerbosity(tester.getVerbosity()); + String line, directive; + String[] argv; + while( null != (line = getLine()) ){ + verbose3("input line: ",line); + checkForDirective(tester, line); + argv = getCommandArgv(line); + if( null!=argv ){ + processCommand(tester, argv); + continue; + } + tester.appendInput(line,true); + } + return true; + } +} diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md new file mode 100644 index 000000000..a51d64d10 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -0,0 +1,269 @@ +# Specifications For A Rudimentary SQLite Test Script Interpreter + +## Overview + +The purpose of the Test Script Interpreter is to read and interpret +script files that contain SQL commands and desired results. The +interpreter will check results and report an discrepencies found. + +The test script files are ASCII text files. The filename always ends with +".test". Each script is evaluated independently; context does not carry +forward from one script to the next. So, for example, the --null command +run in one test script does not cause any changes in the behavior of +subsequent test scripts. All open database connections are closed +at the end of each test script. All database files created by a test +script are deleted when the script finishes. + +## Parsing Rules: + + 1. The test script is read line by line, where a line is a sequence of + characters that runs up to the next '\\n' (0x0a) character or until + the end of the file. There is never a need to read ahead past the + end of the current line. + + 2. If any line contains the string " MODULE_NAME:" (with a space before + the initial "M") or "MIXED_MODULE_NAME:" then that test script is + incompatible with this spec. Processing of the test script should + end immediately. There is no need to read any more of the file. + In verbose mode, the interpreter might choose to emit an informational + messages saying that the test script was abandoned due to an + incompatible module type. + + 3. If any line contains the string "SCRIPT_MODULE_NAME:" then the input + script is known to be of the correct type for this specification and + processing may continue. The "MODULE_NAME" checking in steps 2 and 3 + may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME". + + 4. If any line contains "REQUIRED_PROPERTIES:" and that substring is followed + by any non-whitespace text, then the script is not compatible with this + spec. Processing should stop immediately. In verbose mode, the + interpreter might choose to emit an information message saying that the + test script was abandoned due to unsupported requirement properties. + + 5. If any line begins with the "\|" (0x7c) character, that indicates that + the input script is not compatible with this specification. Processing + of the script should stop immediately. In verbose mode, the interpreter + might choose to emit an informational message indicating that the + test script was abandoned because it contained "a dbtotxt format database + specification". + + 6. Any line that begins with "#" is a C-preprocessor line. The interpreter + described by this spec does not know how to deal with C-preprocessor lines. + Hence, processing should be abandoned. In verbose mode, the interpreter + might emit an informational message similar to + "script NAME abandoned due to C-preprocessor line: ..." + + 7. If a line begins with exactly two minus signs followed by a + lowercase letter, that is a command. Process commands as described + below. + + 8. All other lines should be accumulated into the "input buffer". + The various commands will have access to this input buffer. + Some commands will reset the buffer. + +## Initialization + +The initial state of the interpreter at the start of processing each script +is as if the following command sequence had been run: + +> ~~~ +--close all +--db 0 +--new test.db +--null nil +~~~ + +In words, all database connections are closed except for connection 0 (the +default) which is open on an empty database named "test.db". The string +"nil" is displayed for NULL column values. + +The only context carried forward after the evaluation of one test script +into the evaluation of the next test script is the count of the number of +tests run and the number of failures seen. + +## Commands: + +Each command looks like an SQL comment. The command begins at the left +margin (no leading space) and starts with exactly 2 minus signs ("-"). +The command name consists of lowercase letters and maybe a "-" or two. +Some commands have arguments. +The arguments are separated from the command name by one or more spaces. + +Commands have access to the input buffer and might reset the input buffer. +The command can also optionally read (and consume) additional text from +script that comes after the command. + +Unknown or unrecognized commands indicate that the script contains features +that are not (yet) supported by this specification. Processing of the +script should terminate immediately. When this happens and when the +interpreter is in a "verbose" mode, the interpreter might choose to emit +an informational message along the lines of "test script NAME abandoned +due to unsupported command: --whatever". + +The initial implemention will only recognize a few commands. Other +commands may be added later. The following is the initial set of +commands: + +### The --testcase command + +Every test case starts with a --testcase command. The --testcase +command resets both the "input buffer" and the "result buffer". The +argument to the --testcase command is the name of the test case. That +test case name is used for logging and debugging and when printing +errors. The input buffer is set to the body of the test case. + +### The --result command + +The --result command tries to execute the text in the input buffer as SQL. +For each row of result coming out of this SQL, the text of that result is +appended to the "result buffer". If a result row contains multiple columns, +the columns are processed from left to right. For each column, text is +appended to the result buffer according to the following rules: + + * If the result buffer already contains some text, append a space. + (In this way, all column values and all row values are separated from + each other by a single space.) + + * If sqlite3_column_text() returns NULL, then append "nil" - or + some other text that is specified by the --null command - and skip + all subsequent rules. + + * If sqlite3_column_text() is an empty string, append `{}` to the + result buffer and skip all subsequent rules. + + * If sqlite3_column_text() does not contain any special + characters, append it to the result buffer without any + formatting and skip all subsequent rules. Special characters are: + 0x00 to 0x20 (inclusive), double-quote (0x22), backslash (0x5c), + curly braces (0x7b and 0x7d). + + * If sqlite3_column_text() does not contains curly braces, then put + the text inside of `{...}` and append it and skip all subsequent rules. + + * Append the text within double-quotes (`"..."`) and within the text + escape '"' and '\\' by prepending a single '\\' and escape any + control characters (characters less than 0x20) using octal notation: + '\\NNN'. + +If an error is encountered while running the SQL, then append the +symbolic C-preprocessor name for the error +code (ex: "SQLITE_CONSTRAINT") as if it were a column value. Then append +the error message text as if it where a column value. Then stop processing. + +After the SQL text has been run, compare the content of the result buffer +against the argument to the --result command and report a testing error if +there are any differences. + +The --result command resets the input buffer, but it does not reset +the result buffer. This distinction does not matter for the --result +command itself, but it is important for related commands like --glob +and --notglob. Sometimes test cases will contains a bunch of SQL +followed by multiple --glob and/or --notglob statements. All of the +globs should be evaluted agains the result buffer correct, but the SQL +should only be run once. This is accomplished by resetting the input +buffer but not the result buffer. + +### The --glob command + +The --glob command works just like --result except that the argument to +--glob is interpreted as a TEST-GLOB pattern and the results are compared +using that glob pattern rather than using strcmp(). Other than that, +the two operate the same. + +The TEST-GLOB pattern is slightly different for a standard GLOB: + + * The '*' character matches zero or more characters. + + * The '?' character matches any single character + + * The '[...]' character sequence machines a single character + in between the brackets. + + * The '#' character matches one or more digits (This is the main + difference between standard unix-glob and TEST-GLOB. unix-glob + does not have this feature. It was added to because it comes + up a lot during SQLite testing.) + +### The --notglob command + +The --notglob command works just like --glob except that it reports an +error if the GLOB does match, rather than if the GLOB does not matches. + +### The --oom command + +This command is to be used for out-of-memory testing. It means that +OOM errors should be simulated to ensure that SQLite is able to deal with +them. This command can be silently ignored for now. We might add support +for this later. + +### The --tableresult command + +The --tableresult command works like --glob except that the GLOB pattern +to be matched is taken from subsequent lines of the input script up to +the next --end. Every span of one or more whitespace characters in this +pattern text is collapsed into a single space (0x20). +Leading and trailing whitespace are removed from the pattern. +The --end that ends the GLOB pattern is not part of the GLOB pattern, but +the --end is consumed from the script input. + +### The --new and --open commands + +The --new and --open commands cause a database file to be opened. +The name of the file is the argument to the command. The --new command +opens an initially empty database (it deletes the file before opening it) +whereas the --open command opens an existing database if it already +exists. + +### The --db command + +The script interpreter can have up to 7 different SQLite database +connections open at a time. The --db command is used to switch between +them. The argument to --db is an integer between 0 and 6 that selects +which database connection to use moving forward. + +### The --close command + +The --close command causes an existing database connection to close. +This command is a no-op if the database connection is not currently +open. There can be up to 7 different database connections, numbered 0 +through 6. The number of the database connection to close is an +argument to the --close command, which will fail if an out-of-range +value is provided. Or if the argument to --close is "all" then all +open database connections are closed. If passed no argument, the +currently-active database is assumed. + +### The --null command + +The NULL command changes the text that is used to represent SQL NULL +values in the result buffer. + +### The --run command + +The --run command executes text in the input buffer as if it where SQL. +However, nothing is added to the result buffer. Any output from the SQL +is silently ignored. Errors in the SQL are silently ignored. + +The --run command normally executes the SQL in the current database +connection. However, if --run has an argument that is an integer between +0 and 6 then the SQL is run in the alternative database connection specified +by that argument. + +### The --json and --json-block commands + +The --json and --json-block commands work like --result and --tableresult, +respectively. The difference is that column values are appended to the +result buffer literally, without ever enclosing the values in `{...}` or +`"..."` and without escaping any characters in the column value and comparison +is always an exact strcmp() not a GLOB. + +### The --print command + +The --print command emits both its arguments and its body (if any) to +stdout, indenting each line of output. + +### The --column-names command + +The --column-names command requires 0 or 1 as an argument, to disable +resp. enable it, and modifies SQL execution to include column names +in output. When this option is on, each column value emitted gets +prefixed by its column name, with a single space between them. diff --git a/ext/jni/src/tests/000-000-sanity.test b/ext/jni/src/tests/000-000-sanity.test new file mode 100644 index 000000000..2bfacb1ce --- /dev/null +++ b/ext/jni/src/tests/000-000-sanity.test @@ -0,0 +1,52 @@ +/* +** This is a comment. There are many like it but this one is mine. +** +** SCRIPT_MODULE_NAME: sanity-check +** xMIXED_MODULE_NAME: mixed-module +** xMODULE_NAME: module-name +** xREQUIRED_PROPERTIES: small fast reliable +** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS +** xREQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE +** +*/ +--print starting up 😃 +--close all +--oom +--db 0 +--new my.db +--null zilch +--testcase 1.0 +SELECT 1, null; +--result 1 zilch +--glob *zil* +--notglob *ZIL* +SELECT 1, 2; +intentional error; +--run +--testcase json-1 +SELECT json_array(1,2,3) +--json [1,2,3] +--testcase tableresult-1 + select 1, 'a'; + select 2, 'b'; +--tableresult + # [a-z] + 2 b +--end +--testcase json-block-1 + select json_array(1,2,3); + select json_object('a',1,'b',2); +--json-block + [1,2,3] + {"a":1,"b":2} +--end +--testcase col-names-on +--column-names 1 + select 1 as 'a', 2 as 'b'; +--result a 1 b 2 +--testcase col-names-off +--column-names 0 + select 1 as 'a', 2 as 'b'; +--result 1 2 +--close +--print reached the end 😃 diff --git a/ext/jni/src/tests/000-001-ignored.test b/ext/jni/src/tests/000-001-ignored.test new file mode 100644 index 000000000..5af852e19 --- /dev/null +++ b/ext/jni/src/tests/000-001-ignored.test @@ -0,0 +1,9 @@ +/* +** This script must be marked as ignored because it contains +** content which triggers that condition. +** +** SCRIPT_MODULE_NAME: ignored +** +*/ + +| diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index 4495cae46..9365ae68b 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -58,41 +58,24 @@ static void decimal_free(Decimal *p){ } /* -** Allocate a new Decimal object. Initialize it to the number given -** by the input string. +** Allocate a new Decimal object initialized to the text in zIn[]. +** Return NULL if any kind of error occurs. */ -static Decimal *decimal_new( - sqlite3_context *pCtx, - sqlite3_value *pIn, - int nAlt, - const unsigned char *zAlt -){ - Decimal *p; - int n, i; - const unsigned char *zIn; +static Decimal *decimalNewFromText(const char *zIn, int n){ + Decimal *p = 0; + int i; int iExp = 0; + p = sqlite3_malloc( sizeof(*p) ); - if( p==0 ) goto new_no_mem; + if( p==0 ) goto new_from_text_failed; p->sign = 0; p->oom = 0; p->isInit = 1; p->isNull = 0; p->nDigit = 0; p->nFrac = 0; - if( zAlt ){ - n = nAlt, - zIn = zAlt; - }else{ - if( sqlite3_value_type(pIn)==SQLITE_NULL ){ - p->a = 0; - p->isNull = 1; - return p; - } - n = sqlite3_value_bytes(pIn); - zIn = sqlite3_value_text(pIn); - } p->a = sqlite3_malloc64( n+1 ); - if( p->a==0 ) goto new_no_mem; + if( p->a==0 ) goto new_from_text_failed; for(i=0; isspace(zIn[i]); i++){} if( zIn[i]=='-' ){ p->sign = 1; @@ -143,7 +126,7 @@ static Decimal *decimal_new( } if( iExp>0 ){ p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); - if( p->a==0 ) goto new_no_mem; + if( p->a==0 ) goto new_from_text_failed; memset(p->a+p->nDigit, 0, iExp); p->nDigit += iExp; } @@ -162,7 +145,7 @@ static Decimal *decimal_new( } if( iExp>0 ){ p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); - if( p->a==0 ) goto new_no_mem; + if( p->a==0 ) goto new_from_text_failed; memmove(p->a+iExp, p->a, p->nDigit); memset(p->a, 0, iExp); p->nDigit += iExp; @@ -171,7 +154,76 @@ static Decimal *decimal_new( } return p; -new_no_mem: +new_from_text_failed: + if( p ){ + if( p->a ) sqlite3_free(p->a); + sqlite3_free(p); + } + return 0; +} + +/* Forward reference */ +static Decimal *decimalFromDouble(double); + +/* +** Allocate a new Decimal object from an sqlite3_value. Return a pointer +** to the new object, or NULL if there is an error. If the pCtx argument +** is not NULL, then errors are reported on it as well. +** +** If the pIn argument is SQLITE_TEXT or SQLITE_INTEGER, it is converted +** directly into a Decimal. For SQLITE_FLOAT or for SQLITE_BLOB of length +** 8 bytes, the resulting double value is expanded into its decimal equivalent. +** If pIn is NULL or if it is a BLOB that is not exactly 8 bytes in length, +** then NULL is returned. +*/ +static Decimal *decimal_new( + sqlite3_context *pCtx, /* Report error here, if not null */ + sqlite3_value *pIn, /* Construct the decimal object from this */ + int bTextOnly /* Always interpret pIn as text if true */ +){ + Decimal *p = 0; + int eType = sqlite3_value_type(pIn); + if( bTextOnly && (eType==SQLITE_FLOAT || eType==SQLITE_BLOB) ){ + eType = SQLITE_TEXT; + } + switch( eType ){ + case SQLITE_TEXT: + case SQLITE_INTEGER: { + const char *zIn = (const char*)sqlite3_value_text(pIn); + int n = sqlite3_value_bytes(pIn); + p = decimalNewFromText(zIn, n); + if( p==0 ) goto new_failed; + break; + } + + case SQLITE_FLOAT: { + p = decimalFromDouble(sqlite3_value_double(pIn)); + break; + } + + case SQLITE_BLOB: { + const unsigned char *x; + unsigned int i; + sqlite3_uint64 v = 0; + double r; + + if( sqlite3_value_bytes(pIn)!=sizeof(r) ) break; + x = sqlite3_value_blob(pIn); + for(i=0; ioom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){} + for(nZero=0; nZeroa[nZero]==0; nZero++){} + nFrac = p->nFrac + (nDigit - p->nDigit); + nDigit -= nZero; + z = sqlite3_malloc( nDigit+20 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( nDigit==0 ){ + zero = 0; + a = &zero; + nDigit = 1; + nFrac = 0; + }else{ + a = &p->a[nZero]; + } + if( p->sign && nDigit>0 ){ + z[0] = '-'; + }else{ + z[0] = '+'; + } + z[1] = a[0]+'0'; + z[2] = '.'; + if( nDigit==1 ){ + z[3] = '0'; + i = 4; + }else{ + for(i=1; iisNull ) goto cmp_done; - pB = decimal_new(context, argv[1], 0, 0); + pB = decimal_new(context, argv[1], 1); if( pB==0 || pB->isNull ) goto cmp_done; rc = decimal_cmp(pA, pB); if( rc<0 ) rc = -1; @@ -338,7 +435,7 @@ static void decimal_expand(Decimal *p, int nDigit, int nFrac){ } /* -** Add the value pB into pA. +** Add the value pB into pA. A := A + B. ** ** Both pA and pB might become denormalized by this routine. */ @@ -407,6 +504,172 @@ static void decimal_add(Decimal *pA, Decimal *pB){ } } +/* +** Multiply A by B. A := A * B +** +** All significant digits after the decimal point are retained. +** Trailing zeros after the decimal point are omitted as long as +** the number of digits after the decimal point is no less than +** either the number of digits in either input. +*/ +static void decimalMul(Decimal *pA, Decimal *pB){ + signed char *acc = 0; + int i, j, k; + int minFrac; + + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); + if( acc==0 ){ + pA->oom = 1; + goto mul_end; + } + memset(acc, 0, pA->nDigit + pB->nDigit + 2); + minFrac = pA->nFrac; + if( pB->nFracnFrac; + for(i=pA->nDigit-1; i>=0; i--){ + signed char f = pA->a[i]; + int carry = 0, x; + for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ + x = acc[k] + f*pB->a[j] + carry; + acc[k] = x%10; + carry = x/10; + } + x = acc[k] + carry; + acc[k] = x%10; + acc[k-1] += x/10; + } + sqlite3_free(pA->a); + pA->a = acc; + acc = 0; + pA->nDigit += pB->nDigit + 2; + pA->nFrac += pB->nFrac; + pA->sign ^= pB->sign; + while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ + pA->nFrac--; + pA->nDigit--; + } + +mul_end: + sqlite3_free(acc); +} + +/* +** Create a new Decimal object that contains an integer power of 2. +*/ +static Decimal *decimalPow2(int N){ + Decimal *pA = 0; /* The result to be returned */ + Decimal *pX = 0; /* Multiplier */ + if( N<-20000 || N>20000 ) goto pow2_fault; + pA = decimalNewFromText("1.0", 3); + if( pA==0 || pA->oom ) goto pow2_fault; + if( N==0 ) return pA; + if( N>0 ){ + pX = decimalNewFromText("2.0", 3); + }else{ + N = -N; + pX = decimalNewFromText("0.5", 3); + } + if( pX==0 || pX->oom ) goto pow2_fault; + while( 1 /* Exit by break */ ){ + if( N & 1 ){ + decimalMul(pA, pX); + if( pA->oom ) goto pow2_fault; + } + N >>= 1; + if( N==0 ) break; + decimalMul(pX, pX); + } + decimal_free(pX); + return pA; + +pow2_fault: + decimal_free(pA); + decimal_free(pX); + return 0; +} + +/* +** Use an IEEE754 binary64 ("double") to generate a new Decimal object. +*/ +static Decimal *decimalFromDouble(double r){ + sqlite3_int64 m, a; + int e; + int isNeg; + Decimal *pA; + Decimal *pX; + char zNum[100]; + if( r<0.0 ){ + isNeg = 1; + r = -r; + }else{ + isNeg = 0; + } + memcpy(&a,&r,sizeof(a)); + if( a==0 ){ + e = 0; + m = 0; + }else{ + e = a>>52; + m = a & ((((sqlite3_int64)1)<<52)-1); + if( e==0 ){ + m <<= 1; + }else{ + m |= ((sqlite3_int64)1)<<52; + } + while( e<1075 && m>0 && (m&1)==0 ){ + m >>= 1; + e++; + } + if( isNeg ) m = -m; + e = e - 1075; + if( e>971 ){ + return 0; /* A NaN or an Infinity */ + } + } + + /* At this point m is the integer significand and e is the exponent */ + sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m); + pA = decimalNewFromText(zNum, (int)strlen(zNum)); + pX = decimalPow2(e); + decimalMul(pA, pX); + decimal_free(pX); + return pA; +} + +/* +** SQL Function: decimal(X) +** OR: decimal_exp(X) +** +** Convert input X into decimal and then back into text. +** +** If X is originally a float, then a full decimal expansion of that floating +** point value is done. Or if X is an 8-byte blob, it is interpreted +** as a float and similarly expanded. +** +** The decimal_exp(X) function returns the result in exponential notation. +** decimal(X) returns a complete decimal, without the e+NNN at the end. +*/ +static void decimalFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p = decimal_new(context, argv[0], 0); + UNUSED_PARAMETER(argc); + if( p ){ + if( sqlite3_user_data(context)!=0 ){ + decimal_result_sci(context, p); + }else{ + decimal_result(context, p); + } + decimal_free(p); + } +} + /* ** Compare text in decimal order. */ @@ -417,8 +680,8 @@ static int decimalCollFunc( ){ const unsigned char *zA = (const unsigned char*)pKey1; const unsigned char *zB = (const unsigned char*)pKey2; - Decimal *pA = decimal_new(0, 0, nKey1, zA); - Decimal *pB = decimal_new(0, 0, nKey2, zB); + Decimal *pA = decimalNewFromText((const char*)zA, nKey1); + Decimal *pB = decimalNewFromText((const char*)zB, nKey2); int rc; UNUSED_PARAMETER(notUsed); if( pA==0 || pB==0 ){ @@ -443,8 +706,8 @@ static void decimalAddFunc( int argc, sqlite3_value **argv ){ - Decimal *pA = decimal_new(context, argv[0], 0, 0); - Decimal *pB = decimal_new(context, argv[1], 0, 0); + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); UNUSED_PARAMETER(argc); decimal_add(pA, pB); decimal_result(context, pA); @@ -456,8 +719,8 @@ static void decimalSubFunc( int argc, sqlite3_value **argv ){ - Decimal *pA = decimal_new(context, argv[0], 0, 0); - Decimal *pB = decimal_new(context, argv[1], 0, 0); + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); UNUSED_PARAMETER(argc); if( pB ){ pB->sign = !pB->sign; @@ -495,7 +758,7 @@ static void decimalSumStep( p->nFrac = 0; } if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pArg = decimal_new(context, argv[0], 0, 0); + pArg = decimal_new(context, argv[0], 1); decimal_add(p, pArg); decimal_free(pArg); } @@ -510,7 +773,7 @@ static void decimalSumInverse( p = sqlite3_aggregate_context(context, sizeof(*p)); if( p==0 ) return; if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pArg = decimal_new(context, argv[0], 0, 0); + pArg = decimal_new(context, argv[0], 1); if( pArg ) pArg->sign = !pArg->sign; decimal_add(p, pArg); decimal_free(pArg); @@ -531,66 +794,49 @@ static void decimalSumFinalize(sqlite3_context *context){ ** SQL Function: decimal_mul(X, Y) ** ** Return the product of X and Y. -** -** All significant digits after the decimal point are retained. -** Trailing zeros after the decimal point are omitted as long as -** the number of digits after the decimal point is no less than -** either the number of digits in either input. */ static void decimalMulFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ - Decimal *pA = decimal_new(context, argv[0], 0, 0); - Decimal *pB = decimal_new(context, argv[1], 0, 0); - signed char *acc = 0; - int i, j, k; - int minFrac; + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); UNUSED_PARAMETER(argc); if( pA==0 || pA->oom || pA->isNull || pB==0 || pB->oom || pB->isNull ){ goto mul_end; } - acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); - if( acc==0 ){ - sqlite3_result_error_nomem(context); + decimalMul(pA, pB); + if( pA->oom ){ goto mul_end; } - memset(acc, 0, pA->nDigit + pB->nDigit + 2); - minFrac = pA->nFrac; - if( pB->nFracnFrac; - for(i=pA->nDigit-1; i>=0; i--){ - signed char f = pA->a[i]; - int carry = 0, x; - for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ - x = acc[k] + f*pB->a[j] + carry; - acc[k] = x%10; - carry = x/10; - } - x = acc[k] + carry; - acc[k] = x%10; - acc[k-1] += x/10; - } - sqlite3_free(pA->a); - pA->a = acc; - acc = 0; - pA->nDigit += pB->nDigit + 2; - pA->nFrac += pB->nFrac; - pA->sign ^= pB->sign; - while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ - pA->nFrac--; - pA->nDigit--; - } decimal_result(context, pA); mul_end: - sqlite3_free(acc); decimal_free(pA); decimal_free(pB); } +/* +** SQL Function: decimal_pow2(N) +** +** Return the N-th power of 2. N must be an integer. +*/ +static void decimalPow2Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){ + Decimal *pA = decimalPow2(sqlite3_value_int(argv[0])); + decimal_result_sci(context, pA); + decimal_free(pA); + } +} + #ifdef _WIN32 __declspec(dllexport) #endif @@ -603,13 +849,16 @@ int sqlite3_decimal_init( static const struct { const char *zFuncName; int nArg; + int iArg; void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } aFunc[] = { - { "decimal", 1, decimalFunc }, - { "decimal_cmp", 2, decimalCmpFunc }, - { "decimal_add", 2, decimalAddFunc }, - { "decimal_sub", 2, decimalSubFunc }, - { "decimal_mul", 2, decimalMulFunc }, + { "decimal", 1, 0, decimalFunc }, + { "decimal_exp", 1, 1, decimalFunc }, + { "decimal_cmp", 2, 0, decimalCmpFunc }, + { "decimal_add", 2, 0, decimalAddFunc }, + { "decimal_sub", 2, 0, decimalSubFunc }, + { "decimal_mul", 2, 0, decimalMulFunc }, + { "decimal_pow2", 1, 0, decimalPow2Func }, }; unsigned int i; (void)pzErrMsg; /* Unused parameter */ @@ -619,7 +868,7 @@ int sqlite3_decimal_init( for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){ rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg, SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, - 0, aFunc[i].xFunc, 0, 0); + aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0); } if( rc==SQLITE_OK ){ rc = sqlite3_create_window_function(db, "decimal_sum", 1, diff --git a/ext/misc/ieee754.c b/ext/misc/ieee754.c index ff5d2d333..99489fe9c 100644 --- a/ext/misc/ieee754.c +++ b/ext/misc/ieee754.c @@ -256,6 +256,37 @@ static void ieee754func_to_blob( } } +/* +** SQL Function: ieee754_inc(r,N) +** +** Move the floating point value r by N quantums and return the new +** values. +** +** Behind the scenes: this routine merely casts r into a 64-bit unsigned +** integer, adds N, then casts the value back into float. +** +** Example: To find the smallest positive number: +** +** SELECT ieee754_inc(0.0,+1); +*/ +static void ieee754inc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + double r; + sqlite3_int64 N; + sqlite3_uint64 m1, m2; + double r2; + UNUSED_PARAMETER(argc); + r = sqlite3_value_double(argv[0]); + N = sqlite3_value_int64(argv[1]); + memcpy(&m1, &r, 8); + m2 = m1 + N; + memcpy(&r2, &m2, 8); + sqlite3_result_double(context, r2); +} + #ifdef _WIN32 __declspec(dllexport) @@ -277,7 +308,7 @@ int sqlite3_ieee_init( { "ieee754_exponent", 1, 2, ieee754func }, { "ieee754_to_blob", 1, 0, ieee754func_to_blob }, { "ieee754_from_blob", 1, 0, ieee754func_from_blob }, - + { "ieee754_inc", 2, 0, ieee754inc }, }; unsigned int i; int rc = SQLITE_OK; diff --git a/ext/misc/pcachetrace.c b/ext/misc/pcachetrace.c new file mode 100644 index 000000000..3757d8d4d --- /dev/null +++ b/ext/misc/pcachetrace.c @@ -0,0 +1,179 @@ +/* +** 2023-06-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 an extension that uses the SQLITE_CONFIG_PCACHE2 +** mechanism to add a tracing layer on top of pluggable page cache of +** SQLite. If this extension is registered prior to sqlite3_initialize(), +** it will cause all page cache activities to be logged on standard output, +** or to some other FILE specified by the initializer. +** +** This file needs to be compiled into the application that uses it. +** +** This extension is used to implement the --pcachetrace option of the +** command-line shell. +*/ +#include +#include +#include + +/* The original page cache routines */ +static sqlite3_pcache_methods2 pcacheBase; +static FILE *pcachetraceOut; + +/* Methods that trace pcache activity */ +static int pcachetraceInit(void *pArg){ + int nRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p)\n", pArg); + } + nRes = pcacheBase.xInit(pArg); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p) -> %d\n", pArg, nRes); + } + return nRes; +} +static void pcachetraceShutdown(void *pArg){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xShutdown(%p)\n", pArg); + } + pcacheBase.xShutdown(pArg); +} +static sqlite3_pcache *pcachetraceCreate(int szPage, int szExtra, int bPurge){ + sqlite3_pcache *pRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d)\n", + szPage, szExtra, bPurge); + } + pRes = pcacheBase.xCreate(szPage, szExtra, bPurge); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d) -> %p\n", + szPage, szExtra, bPurge, pRes); + } + return pRes; +} +static void pcachetraceCachesize(sqlite3_pcache *p, int nCachesize){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCachesize(%p, %d)\n", p, nCachesize); + } + pcacheBase.xCachesize(p, nCachesize); +} +static int pcachetracePagecount(sqlite3_pcache *p){ + int nRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p)\n", p); + } + nRes = pcacheBase.xPagecount(p); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p) -> %d\n", p, nRes); + } + return nRes; +} +static sqlite3_pcache_page *pcachetraceFetch( + sqlite3_pcache *p, + unsigned key, + int crFg +){ + sqlite3_pcache_page *pRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d)\n", p, key, crFg); + } + pRes = pcacheBase.xFetch(p, key, crFg); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d) -> %p\n", + p, key, crFg, pRes); + } + return pRes; +} +static void pcachetraceUnpin( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + int bDiscard +){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xUnpin(%p, %p, %d)\n", + p, pPg, bDiscard); + } + pcacheBase.xUnpin(p, pPg, bDiscard); +} +static void pcachetraceRekey( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + unsigned oldKey, + unsigned newKey +){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xRekey(%p, %p, %u, %u)\n", + p, pPg, oldKey, newKey); + } + pcacheBase.xRekey(p, pPg, oldKey, newKey); +} +static void pcachetraceTruncate(sqlite3_pcache *p, unsigned n){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xTruncate(%p, %u)\n", p, n); + } + pcacheBase.xTruncate(p, n); +} +static void pcachetraceDestroy(sqlite3_pcache *p){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xDestroy(%p)\n", p); + } + pcacheBase.xDestroy(p); +} +static void pcachetraceShrink(sqlite3_pcache *p){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xShrink(%p)\n", p); + } + pcacheBase.xShrink(p); +} + +/* The substitute pcache methods */ +static sqlite3_pcache_methods2 ersaztPcacheMethods = { + 0, + 0, + pcachetraceInit, + pcachetraceShutdown, + pcachetraceCreate, + pcachetraceCachesize, + pcachetracePagecount, + pcachetraceFetch, + pcachetraceUnpin, + pcachetraceRekey, + pcachetraceTruncate, + pcachetraceDestroy, + pcachetraceShrink +}; + +/* Begin tracing memory allocations to out. */ +int sqlite3PcacheTraceActivate(FILE *out){ + int rc = SQLITE_OK; + if( pcacheBase.xFetch==0 ){ + rc = sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &pcacheBase); + if( rc==SQLITE_OK ){ + rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &ersaztPcacheMethods); + } + } + pcachetraceOut = out; + return rc; +} + +/* Deactivate memory tracing */ +int sqlite3PcacheTraceDeactivate(void){ + int rc = SQLITE_OK; + if( pcacheBase.xFetch!=0 ){ + rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcacheBase); + if( rc==SQLITE_OK ){ + memset(&pcacheBase, 0, sizeof(pcacheBase)); + } + } + pcachetraceOut = 0; + return rc; +} diff --git a/ext/misc/series.c b/ext/misc/series.c index 0deabf95a..3bd567b2e 100644 --- a/ext/misc/series.c +++ b/ext/misc/series.c @@ -330,6 +330,10 @@ static int seriesColumn( return SQLITE_OK; } +#ifndef LARGEST_UINT64 +#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) +#endif + /* ** Return the rowid for the current row, logically equivalent to n+1 where ** "n" is the ascending integer in the aforesaid production definition. @@ -337,7 +341,7 @@ static int seriesColumn( static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ series_cursor *pCur = (series_cursor*)cur; sqlite3_uint64 n = pCur->ss.uSeqIndexNow; - *pRowid = (sqlite3_int64)((n<0xffffffffffffffff)? n+1 : 0); + *pRowid = (sqlite3_int64)((n #include @@ -664,8 +663,14 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){ bNextPage = 1; }else{ + int szField = 0; pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType); - pCsr->pPtr += dbdataValueBytes(iType); + szField = dbdataValueBytes(iType); + if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))pPtr = &pCsr->pRec[pCsr->nRec]; + }else{ + pCsr->pPtr += szField; + } } } } @@ -938,15 +943,11 @@ static int sqlite3DbdataRegister(sqlite3 *db){ return rc; } -#ifdef _WIN32 -__declspec(dllexport) -#endif int sqlite3_dbdata_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ - SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; return sqlite3DbdataRegister(db); } diff --git a/ext/recover/recovercorrupt2.test b/ext/recover/recovercorrupt2.test index 7147c67e9..29acc27a3 100644 --- a/ext/recover/recovercorrupt2.test +++ b/ext/recover/recovercorrupt2.test @@ -285,5 +285,244 @@ do_test 5.1 { list [catch { $R finish } msg] $msg } {0 {}} +#------------------------------------------------------------------------- +# +reset_db +do_test 6.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 8192 pagesize 4096 filename abc.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................ +| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n............. +| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................ +| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR +| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x) +| page 2 offset 4096 +| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................ +| 4064: 00 00 1c 01 02 41 61 62 63 64 65 66 67 68 69 6a .....Aabcdefghij +| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz +| end abc.db +}]} {} +do_test 6.1 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {0 {}} + +reset_db +breakpoint +do_test 6.2 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 8192 pagesize 4096 filename abc.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................ +| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n............. +| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................ +| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR +| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x) +| page 2 offset 4096 +| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................ +| 4064: 00 00 1c 01 02 8F FF FF FF 7E 65 66 67 68 69 6a .....Aabcdefghij +| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz +| end abc.db +}]} {} +do_test 6.3 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {0 {}} + +reset_db +breakpoint +do_test 7.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 4108 pagesize 4096 filename x1.db +| page 1 offset 0 +| 0: 02 01 00 00 00 00 14 15 40 00 00 00 00 00 00 00 ........@....... +| 16: 33 3a 6d 65 6d 6f 72 79 3a 02 02 02 02 02 02 02 3:memory:....... +| 32: 02 02 02 02 02 02 12 02 02 02 63 6f 6c 6f 72 20 ..........color +| 48: 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 02 02 02 spac............ +| 64: 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f 0a 70 02 i.so6.......?.p. +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 ................ +| 96: 06 02 02 02 02 5f 02 02 02 2c 02 02 02 02 02 02 ....._...,...... +| 112: 02 02 02 02 02 02 02 02 02 12 02 02 02 63 6f 6c .............col +| 128: 6f 72 20 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 or spac......... +| 144: 02 02 02 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f ...i.so6.......? +| 160: 0a 70 02 02 02 02 02 02 02 02 02 02 02 02 02 02 .p.............. +| 176: 01 00 00 06 02 02 02 02 5f 02 02 02 2c 02 02 00 ........_...,... +| 192: 00 01 00 01 00 00 00 01 00 02 fe 00 00 03 00 01 ................ +| 208: 00 00 00 01 c5 04 00 00 00 01 00 01 00 00 00 01 ................ +| 224: 00 fa 02 00 00 00 03 00 01 00 00 00 81 00 04 00 ................ +| 240: 00 00 01 00 01 00 00 00 01 00 02 00 fe 00 03 00 ................ +| 256: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 272: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 288: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 304: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 320: 01 00 02 00 00 00 03 00 01 00 00 00 40 00 84 00 ............@... +| 336: 84 00 84 00 01 00 00 00 09 00 06 00 f5 00 01 00 ................ +| 352: 08 01 03 00 03 00 62 00 62 00 23 00 01 00 62 00 ......b.b.#...b. +| 368: 04 00 1e 00 62 00 62 00 62 00 01 00 00 00 0a 00 ....b.b.b....... +| 384: 01 00 03 00 01 00 03 00 04 00 02 00 01 00 01 00 ................ +| 400: 08 00 01 00 31 c6 00 03 00 0c 00 12 00 18 00 02 ....1........... +| 416: 00 05 00 08 00 02 00 06 00 08 00 02 00 07 00 08 ................ +| 432: 00 02 00 01 00 01 00 08 00 01 00 0c 00 03 00 16 ................ +| 448: 00 1c 00 22 00 01 00 03 00 05 00 06 00 07 00 02 ................ +| 464: 00 05 00 09 00 02 00 06 00 09 00 02 00 07 00 09 ................ +| 480: 00 00 00 00 01 00 05 00 00 00 01 00 01 00 00 00 ................ +| 496: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 512: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 528: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 544: 01 00 02 00 00 f6 03 00 00 02 00 00 01 00 04 00 ................ +| 560: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 576: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 592: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 608: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 624: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 640: 01 3d 02 00 00 00 03 00 06 00 00 00 01 00 01 00 .=.............. +| 656: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 672: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 688: 01 00 02 00 00 00 55 52 4c 52 65 71 75 65 73 74 ......URLRequest +| 704: 43 6f 6e 00 00 00 01 01 0e d4 00 04 00 00 00 01 Con............. +| 720: 0e f8 00 04 00 00 00 01 0f 1c 00 04 00 00 00 01 ................ +| 736: 0f 00 00 01 00 00 00 01 0f 86 00 01 00 00 00 01 ................ +| 752: 0f 84 00 01 00 00 00 01 00 00 01 0f c0 00 01 00 ................ +| 768: 00 00 01 0f e8 00 d6 0f 00 01 6f 00 02 0f d6 00 ..........o..... +| 784: 02 34 03 03 03 00 01 00 00 00 01 00 05 00 00 00 .4.............. +| 800: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 816: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................ +| 832: 02 00 00 00 03 00 01 00 10 00 01 00 04 00 00 00 ................ +| 848: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 ................ +| 864: 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 00 ...@............ +| 880: 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 00 ................ +| 896: 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f 00 .wOF2s@#p...p... +| 912: 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 00 ............C... +| 928: 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 00 ................ +| 1024: 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f 00 ................ +| 1040: 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 0f ..........p..... +| 1056: d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 00 ................ +| 1072: 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 04 .....?#p........ +| 1088: 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 01 .....@.......... +| 1104: 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 0f ................ +| 1120: c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f 00 ..............o. +| 1136: 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 00 ......1.+.*..... +| 1152: 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 0f ........+...2... +| 1168: c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 c0 ..... .......$.. +| 1184: 00 00 00 03 00 00 01 24 00 2a 06 e4 00 00 00 03 .......$.*...... +| 1200: 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 26 ...%.8.........& +| 1216: 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 7a .4.F.......'...z +| 1232: 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 03 .......(.*...... +| 1248: 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 2a ...).4.........* +| 1264: 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 28 g4.........+...( +| 1280: 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 00 ................ +| 1296: 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 ................ +| 1312: 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 00 ................ +| 1328: 01 00 04 00 00 00 01 00 01 00 00 00 01 00 02 00 ................ +| 1344: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................ +| 1360: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 21 ...............! +| 1376: 04 00 01 00 00 00 00 00 01 00 00 00 01 00 02 00 ................ +| 1392: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................ +| 1408: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 00 ................ +| 1424: 01 00 05 00 00 00 01 00 01 00 00 01 00 02 02 02 ................ +| 1440: 12 02 02 02 63 6f 6c 6f 72 20 73 70 61 63 00 f3 ....color spac.. +| 1456: a0 81 a1 00 00 a0 02 02 02 02 69 95 73 6f 36 00 ..........i.so6. +| 1472: ff 0d 00 97 8c 90 3f 0a 70 02 02 02 02 02 02 02 ......?.p....... +| 1488: 02 02 02 02 02 02 02 01 00 00 06 02 02 02 02 5f ..............._ +| 1504: 02 02 02 2c 02 02 00 00 01 00 01 00 00 00 01 00 ...,............ +| 1520: 02 fe 00 00 03 00 01 00 00 00 01 c5 04 00 00 00 ................ +| 1536: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 1552: 00 00 81 00 04 00 00 00 01 00 01 00 00 00 01 00 ................ +| 1568: 02 00 fe 00 03 00 01 00 00 00 01 00 04 00 00 00 ................ +| 1584: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 1600: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................ +| 1616: 02 00 00 00 03 00 01 00 00 00 01 00 04 00 00 00 ................ +| 1632: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................ +| 1648: 00 00 40 00 84 00 84 00 84 00 01 00 00 00 09 00 ..@............. +| 1664: 06 00 f5 00 01 00 08 01 03 15 15 15 15 15 15 15 ................ +| 1680: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1696: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1712: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1728: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1744: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................ +| 1760: 15 15 15 15 15 15 15 15 15 15 15 00 03 00 62 00 ..............b. +| 1776: 62 00 23 00 01 00 62 00 04 00 1e 00 62 00 62 00 b.#...b.....b.b. +| 1792: 62 00 01 00 00 00 0a 00 01 00 03 00 01 00 03 00 b............... +| 1808: 04 00 02 00 01 00 01 00 08 00 01 00 31 c6 00 03 ............1... +| 1824: 00 0c 00 12 00 18 00 02 00 05 00 08 00 02 00 06 ................ +| 1840: 00 08 00 02 00 07 00 08 00 02 00 01 00 01 00 08 ................ +| 1856: 00 01 00 0c 00 03 00 16 00 1c 00 22 00 01 00 03 ................ +| 1872: 00 05 00 06 00 07 00 02 00 05 00 09 00 02 00 06 ................ +| 1888: 00 09 00 02 00 07 00 09 00 00 00 00 01 00 05 00 ................ +| 1904: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................ +| 1920: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................ +| 1936: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................ +| 1952: 00 00 01 0f d6 00 02 34 03 03 03 00 01 00 00 00 .......4........ +| 1968: 01 00 05 00 00 00 01 00 01 00 00 00 01 00 02 00 ................ +| 1984: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................ +| 2000: 01 00 00 00 01 00 02 fc 42 dc 19 5c 74 23 18 cd ........B...t#.. +| 2016: b3 a5 a8 7a 90 40 1d 66 12 5d e5 4f 85 00 68 f4 ...z.@.f.].O..h. +| 2032: 05 98 86 25 24 dd bc c2 f6 f6 4e a3 e2 61 d2 c6 ...%$.....N..a.. +| 2048: aa c1 56 50 d4 80 82 35 f1 e2 59 41 50 a6 da 51 ..VP...5..YAP..Q +| 2064: d4 62 9c 19 94 58 aa 31 30 8a 22 c2 5f 33 2b c9 .b...X.10..._3+. +| 2080: b6 e6 b4 11 4e 51 82 c4 d8 b6 d8 b4 06 04 fb 68 ....NQ.........h +| 2096: f4 d2 6f e7 cb 8a a8 82 d5 74 00 00 00 00 00 00 ..o......t...... +| 2368: 00 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 ................ +| 2432: 00 00 00 00 00 03 00 01 00 10 00 01 00 04 00 00 ................ +| 2448: 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 ................ +| 2464: 02 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 ....@........... +| 2480: 00 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 ................ +| 2496: 00 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f ..wOF2s@#p...p.. +| 2512: 00 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 .............C.. +| 2528: 00 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 ................ +| 2624: 00 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f ................ +| 2640: 00 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 ...........p.... +| 2656: 0f d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 ................ +| 2672: 00 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 ......?#p....... +| 2688: 04 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 ......@......... +| 2704: 01 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 ................ +| 2720: 0f c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f ...............o +| 2736: 00 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 .......1.+.*.... +| 2752: 00 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 .........+...2.. +| 2768: 0f c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 ...... .......$. +| 2784: c0 00 00 5a 03 00 00 01 24 00 2a 06 e4 00 00 00 ...Z....$.*..... +| 2800: 03 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 ....%.8......... +| 2816: 26 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 &.4.F.......'... +| 2832: 7a 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 z.......(.*..... +| 2848: 03 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 ....).4......... +| 2864: 2a 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 *g4.........+... +| 2880: 28 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 (............... +| 2896: 00 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 ................ +| 2912: 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 ................ +| 2928: 00 01 00 04 00 00 00 01 00 01 00 00 00 00 00 00 ................ +| 2992: 00 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .H.............. +| 3504: 00 00 00 00 00 00 00 00 00 00 00 97 00 00 00 00 ................ +| 3904: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09 ................ +| 3920: 03 fe 00 00 01 36 00 3c 0a 38 00 00 00 03 00 00 .....6.<.8...... +| 3936: 01 37 00 20 0a 74 00 00 00 fb ff ff 00 38 00 2a .7. .t.......8.* +| 3952: 0a 94 00 00 00 03 00 00 01 39 4f 54 54 4f 00 0e .........9OTTO.. +| 3968: 00 80 00 03 00 60 43 46 46 20 e3 ae 89 2a 00 00 .....`CFF ...*.. +| 3984: 02 b0 00 00 02 76 42 50 4f 53 00 15 00 0a 00 00 .....vBPOS...... +| 4000: 05 28 00 00 00 0c 54 53 55 42 c9 70 c3 06 00 00 .(....TSUB.p.... +| 4016: 05 34 1f 00 40 00 48 00 00 00 00 00 00 00 00 00 .4..@.H......... +| 4064: 00 00 00 00 00 08 00 01 00 01 00 01 00 01 00 06 ................ +| 4080: 00 02 00 08 00 01 00 01 00 01 00 01 00 00 00 00 ................ +| end x1.db +}]} {} +do_test 7.1 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {1 {file is not a database}} + finish_test diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index 29fff0e7e..c445c5179 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -2103,7 +2103,7 @@ static int recoverIsValidPage(u8 *aTmp, const u8 *a, int n){ if( iFree>(n-4) ) return 0; iNext = recoverGetU16(&a[iFree]); nByte = recoverGetU16(&a[iFree+2]); - if( iFree+nByte>n ) return 0; + if( iFree+nByte>n || nByte<4 ) return 0; if( iNext && iNext #include #include @@ -502,7 +507,7 @@ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ - assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(p) ); #if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 pCoord->u = _byteswap_ulong(*(u32*)p); #elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -556,7 +561,7 @@ static void writeInt16(u8 *p, int i){ } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; - assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(p) ); assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); #if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -1284,7 +1289,7 @@ static void rtreeNonleafConstraint( assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); - assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(pCellData) ); switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ case RTREE_FALSE: break; /* Never satisfied */ @@ -1337,7 +1342,7 @@ static void rtreeLeafConstraint( || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); pCellData += 8 + p->iCoord*4; - assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(pCellData) ); RTREE_DECODE_COORD(eInt, pCellData, xN); switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ @@ -1907,7 +1912,20 @@ static int rtreeFilter( p->pInfo->nCoord = pRtree->nDim2; p->pInfo->anQueue = pCsr->anQueue; p->pInfo->mxLevel = pRtree->iDepth + 1; - }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + }else if( eType==SQLITE_INTEGER ){ + sqlite3_int64 iVal = sqlite3_value_int64(argv[ii]); +#ifdef SQLITE_RTREE_INT_ONLY + p->u.rValue = iVal; +#else + p->u.rValue = (double)iVal; + if( iVal>=((sqlite3_int64)1)<<48 + || iVal<=-(((sqlite3_int64)1)<<48) + ){ + if( p->op==RTREE_LT ) p->op = RTREE_LE; + if( p->op==RTREE_GT ) p->op = RTREE_GE; + } +#endif + }else if( eType==SQLITE_FLOAT ){ #ifdef SQLITE_RTREE_INT_ONLY p->u.rValue = sqlite3_value_int64(argv[ii]); #else @@ -2038,11 +2056,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){ u8 op; + u8 doOmit = 1; switch( p->op ){ - case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break; - case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break; + case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; doOmit = 0; break; + case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; doOmit = 0; break; case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break; - case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break; + case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; doOmit = 0; break; case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break; case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break; default: op = 0; break; @@ -2051,7 +2070,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ zIdxStr[iIdx++] = op; zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0'); pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2); - pIdxInfo->aConstraintUsage[ii].omit = 1; + pIdxInfo->aConstraintUsage[ii].omit = doOmit; } } } diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index 034155341..633d0a5d5 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -756,4 +756,32 @@ do_execsql_test 20.4 { SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE x0 = 0; } {- - 0 0.0 0.0} +# 2023-05-19 https://sqlite.org/forum/forumpost/da61c4a1b5b4af19 +# Do not omit constraints that involve equality comparisons of +# floating-point values. +# +reset_db +do_execsql_test 21.0 { + CREATE VIRTUAL TABLE t1 USING rtree(id, x0, x1); + INSERT INTO t1 VALUES(0, 1, 9223372036854775807); + SELECT count(*) FROM t1 WHERE x1=9223372036854775807; +} {0} +do_execsql_test 21.1 { + SELECT x1=9223372036854775807 FROM t1; +} {0} + +# 2023-05-22 https://sqlite.org/forum/forumpost/da70ee0d0d +# Round-off error associated with using large integer constraints on +# a rtree search. +# +reset_db +do_execsql_test 22.0 { + CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 ); + INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800); + SELECT id FROM t1 WHERE x0 > 9223372036854775807; +} {123} +do_execsql_test 22.1 { + SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1; +} {123 1} + finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 679408849..9f862f246 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -891,6 +891,7 @@ static int sessionPreupdateEqual( rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal); } assert( rc==SQLITE_OK ); + (void)rc; /* Suppress warning about unused variable */ if( sqlite3_value_type(pVal)!=eType ) return 0; /* A SessionChange object never has a NULL value in a PK column */ diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index a99513bfa..080c42704 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -18,8 +18,6 @@ # quick, q = do just a minimal build (sqlite3.js/wasm, tester1) for # faster development-mode turnaround. # -# qo2, qoz = a combination of quick+o2/oz. -# # dist = create end user deliverables. Add dist.build=oX to build # with a specific optimization level, where oX is one of the # above-listed o? or qo? target names. @@ -46,11 +44,12 @@ # $(eval), or at least centralize the setup of the numerous vars # related to each build variant $(JS_BUILD_MODES). # +default: all +#default: quick SHELL := $(shell which bash 2>/dev/null) MAKEFILE := $(lastword $(MAKEFILE_LIST)) CLEAN_FILES := DISTCLEAN_FILES := ./--dummy-- -default: all release: oz # JS_BUILD_MODES exists solely to reduce repetition in documentation # below. @@ -68,13 +67,6 @@ ifeq (,$(emcc.version)) else $(info using emcc version [$(emcc.version)]) endif -emcc.version := $(shell "$(emcc.bin)" --version | sed -n 1p \ - | sed -e 's/^.* \([3-9][^ ]*\) .*$$/\1/;') -ifeq (,$(emcc.version)) - $(warning Cannot determine emcc version. This might unduly impact build flags.) -else - $(info using emcc version [$(emcc.version)]) -endif wasm-strip ?= $(shell which wasm-strip 2>/dev/null) ifeq (,$(filter clean,$(MAKECMDGOALS))) @@ -159,6 +151,9 @@ endif # bundle. # # A custom sqlite3.c must not have any spaces in its name. +# $(sqlite3.canonical.c) must point to the sqlite3.c in +# the sqlite3 canonical source tree, as that source file +# is required for certain utility and test code. sqlite3.canonical.c := $(dir.top)/sqlite3.c sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c)) sqlite3.h := $(dir.top)/sqlite3.h @@ -184,14 +179,19 @@ SQLITE_OPT = \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_UTF16 \ -DSQLITE_OMIT_SHARED_CACHE \ - -DSQLITE_OMIT_WAL \ -DSQLITE_THREADSAFE=0 \ - -DSQLITE_TEMP_STORE=3 \ + -DSQLITE_TEMP_STORE=2 \ -DSQLITE_OS_KV_OPTIONAL=1 \ '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \ -DSQLITE_USE_URI=1 \ -DSQLITE_WASM_ENABLE_C_TESTS \ -DSQLITE_C=$(sqlite3.c) +#SQLITE_OPT += -DSQLITE_DEBUG +# Enabling SQLITE_DEBUG will break sqlite3_wasm_vfs_create_file() +# (and thus sqlite3_js_vfs_create_file()). Those functions are +# deprecated and alternatives are in place, but this crash behavior +# can be used to find errant uses of sqlite3_js_vfs_create_file() +# in client code. .NOTPARALLEL: $(sqlite3.h) $(sqlite3.h): @@ -246,10 +246,10 @@ endif # embedding in the JS files and in building the distribution zip file. # It must NOT be in $(dir.tmp) because we need it to survive the # cleanup process for the dist build to work properly. -bin.version-info := $(dir.wasm)/version-info -$(bin.version-info): $(dir.wasm)/version-info.c $(sqlite3.h) $(MAKEFILE) - $(CC) -O0 -I$(dir $(sqlite3.c)) -o $@ $< -DISTCLEAN_FILES += $(bin.version-info) +bin.version-info := $(dir.top)/version-info +.NOTPARALLEL: $(bin.version-info) +$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile + $(MAKE) -C $(dir.top) version-info # bin.stripcomments is used for stripping C/C++-style comments from JS # files. The JS files contain large chunks of documentation which we @@ -379,20 +379,17 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js +sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js -# "External" API files which are part of our distribution +# SOAP.js is an external API file which is part of our distribution # but not part of the sqlite3-api.js amalgamation. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js -# COPY_XAPI = a $(call)able function to copy $1 to $(dir.dout), where -# $1 must be one of the "external" JS API files. -define COPY_XAPI -sqlite3-api.ext.jses += $$(dir.dout)/$$(notdir $(1)) -$$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE) - cp $$< $$@ -endef -$(foreach X,$(SOAP.js),\ - $(eval $(call COPY_XAPI,$(X)))) +SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js)) +sqlite3-api.ext.jses += $(SOAP.js.bld) +$(SOAP.js.bld): $(SOAP.js) + cp $< $@ + all quick: $(sqlite3-api.ext.jses) q: quick @@ -459,6 +456,13 @@ emcc.exportedRuntimeMethods := \ emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY +#emcc.jsflags += -sASYNCIFY=2 +# ^^^ ASYNCIFY=2 is for experimental JSPI support +# (https://v8.dev/blog/jspi), but enabling it causes the lib-level +# init code to throw inexplicable complaints about C-level function +# signatures not matching what we expect them to be. JSPI requires, as of +# this writing, requires an experimental Chrome flag: +# chrome://flags/#enable-experimental-webassembly-stack-switching emcc.jsflags += -sSTRICT_JS=0 # STRICT_JS disabled due to: # https://github.com/emscripten-core/emscripten/issues/18610 @@ -615,26 +619,40 @@ $(post-js.js.in): $(post-jses.js) $(MAKEFILE) ######################################################################## # call-make-pre-post is a $(call)able which creates rules for -# pre-js-$(1).js. $1 = the base name of the JS file on whose behalf -# this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is the -# build mode: one of $(JS_BUILD_MODES). This -# sets up --[extern-][pre/post]-js flags in -# $(pre-post-$(1).flags.$(2)) and dependencies in -# $(pre-post-$(1).deps.$(2)). +# pre-js-$(1)-$(2).js. $1 = the base name of the JS file on whose +# behalf this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is +# the build mode: one of $(JS_BUILD_MODES). This sets up +# --[extern-][pre/post]-js flags in $(pre-post-$(1)-$(2).flags) and +# dependencies in $(pre-post-$(1)-$(2).deps). The resulting files get +# filtered using $(C-PP.FILTER). Any flags necessary for such +# filtering need to be set in $(c-pp.D.$(1)-$(2)) before $(call)ing +# this. define call-make-pre-post -pre-post-$(1).flags.$(2) ?= -$$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(2)) $$(MAKEFILE) - cp $$(pre-js.js.$(2)) $$@ +pre-post-$(1)-$(2).flags ?= +pre-js.js.$(1)-$(2) := $$(dir.tmp)/pre-js.$(1)-$(2).intermediary.js +$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) +post-js.js.$(1)-$(2) := $$(dir.tmp)/post-js.$(1)-$(2).js +$$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) +extern-post-js.js.$(1)-$(2) := $$(dir.tmp)/extern-post-js.$(1)-$(2).js +$$(eval $$(call C-PP.FILTER,$$(extern-post-js.js.in),$$(extern-post-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) +pre-post-common.flags.$(1)-$(2) := \ + $$(pre-post-common.flags) \ + --post-js=$$(post-js.js.$(1)-$(2)) \ + --extern-post-js=$$(extern-post-js.js.$(1)-$(2)) +pre-post-jses.$(1)-$(2).deps := $$(pre-post-jses.deps.common) \ + $$(post-js.js.$(1)-$(2)) $$(extern-post-js.js.$(1)-$(2)) +$$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(1)-$(2)) $$(MAKEFILE) + cp $$(pre-js.js.$(1)-$(2)) $$@ @if [ sqlite3-wasmfs = $(1) ]; then \ echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \ elif [ sqlite3 != $(1) ]; then \ echo "Module[xNameOfInstantiateWasm].uri = '$(1).wasm';"; \ fi >> $$@ -pre-post-$(1).deps.$(2) := \ - $$(pre-post-jses.deps.$(2)) \ +pre-post-$(1)-$(2).deps := \ + $$(pre-post-jses.$(1)-$(2).deps) \ $$(dir.tmp)/pre-js-$(1)-$(2).js -pre-post-$(1).flags.$(2) += \ - $$(pre-post-common.flags.$(2)) \ +pre-post-$(1)-$(2).flags += \ + $$(pre-post-common.flags.$(1)-$(2)) \ --pre-js=$$(dir.tmp)/pre-js-$(1)-$(2).js endef # /post-js and pre-js @@ -645,7 +663,8 @@ endef # https://github.com/emscripten-core/emscripten/issues/14383 sqlite3.wasm := $(dir.dout)/sqlite3.wasm sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c -sqlite3-wasm.cses := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c) +sqlite3-wasm.cfiles := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c) +sqlite3-wasmfs.cfiles := $(sqlite3-wasm.cfiles) # sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter # (predictably) results in a slightly faster binary. We're close # enough to the target speed requirements that the 500ms makes a @@ -655,7 +674,8 @@ sqlite3-wasm.cses := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c) # SQLITE3.xJS.EXPORT-DEFAULT is part of SQLITE3-WASMFS.xJS.RECIPE and # SETUP_LIB_BUILD_MODE, factored into a separate piece to avoid code # duplication. $1 is 1 if the build mode needs this workaround (esm, -# bundler-friendly) and 0 if not (vanilla). +# bundler-friendly, node) and 0 if not (vanilla). $2 must be empty for +# all builds except sqlite3-wasmfs.mjs, in which case it must be 1. # # Reminder for ESM builds: even if we use -sEXPORT_ES6=0, emcc _still_ # adds: @@ -669,13 +689,20 @@ sqlite3-wasm.cses := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c) # # Upstream RFE: # https://github.com/emscripten-core/emscripten/issues/18237 +# +# Maintenance reminder: Mac sed works differently than GNU sed, so +# don't use sed for this. define SQLITE3.xJS.ESM-EXPORT-DEFAULT if [ x1 = x$(1) ]; then \ - echo "Fragile workaround for an Emscripten annoyance. See SQLITE3.xJS.RECIPE."; \ - sed -i -e '0,/^export default/{/^export default/d;}' $@ || exit $$?; \ - if ! grep -q '^export default' $@; then \ - echo "Cannot find export default." 1>&2; \ - exit 1; \ + echo "Fragile workaround for emscripten/issues/18237. See SQLITE3.xJS.RECIPE."; \ + {\ + awk '/^export default/ && !f{f=1; next} 1' $@ > $@.tmp && mv $@.tmp $@; \ + } || exit $$?; \ + if [ x != x$(2) ]; then \ + if ! grep -q '^export default' $@; then \ + echo "Cannot find export default." 1>&2; \ + exit 1; \ + fi; \ fi; \ fi endef @@ -695,53 +722,57 @@ pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) # SETUP_LIB_BUILD_MODE is a $(call)'able which sets up numerous pieces # for one of the build modes. # -# $1 = build mode name: one of $(JS_BUILD_MODES) -# $2 = 1 for ESM build mode, else 0 -# $3 = resulting sqlite-api JS/MJS file -# $4 = resulting JS/MJS file -# $5 = -D... flags for $(bin.c-pp) -# $6 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out) +# $1 = one of: sqlite3, sqlite3-wasmfs +# $2 = build mode name: one of $(JS_BUILD_MODES) +# $3 = 1 for ESM build mode, else 0 +# $4 = resulting sqlite-api JS/MJS file +# $5 = resulting JS/MJS file +# $6 = -D... flags for $(bin.c-pp) +# $7 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out) +# +# Maintenance reminder: be careful not to introduce spaces around args +# ($1, $2), otherwise string concatenation will malfunction. # -# emcc.environment.$(1) must be set to a value for the -sENVIRONMENT flag. +# emcc.environment.$(2) must be set to a value for the -sENVIRONMENT flag. +# +# $(cflags.$(1)) and $(cflags.$(1).$(2)) may be defined to append +# CFLAGS to a given build mode. +# +# $(emcc.flags.$(1)) and $(emcc.flags.$(1).$(2)) may be defined to +# append emcc-specific flags to a given build mode. define SETUP_LIB_BUILD_MODE -$(info Setting up build [$(1)]: $(4)) -c-pp.D.$(1) := $(5) -pre-js.js.$(1) := $$(dir.tmp)/pre-js.$(1).js -$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)),$$(c-pp.D.$(1)))) -post-js.js.$(1) := $$(dir.tmp)/post-js.$(1).js -$$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)),$$(c-pp.D.$(1)))) -extern-post-js.js.$(1) := $$(dir.tmp)/extern-post-js.$(1).js -$$(eval $$(call C-PP.FILTER,$$(extern-post-js.js.in),$$(extern-post-js.js.$(1)),$$(c-pp.D.$(1)))) -pre-post-common.flags.$(1) := \ - $$(pre-post-common.flags) \ - --post-js=$$(post-js.js.$(1)) \ - --extern-post-js=$$(extern-post-js.js.$(1)) -pre-post-jses.deps.$(1) := $$(pre-post-jses.deps.common) \ - $$(post-js.js.$(1)) $$(extern-post-js.js.$(1)) -$$(eval $$(call call-make-pre-post,sqlite3,$(1))) -emcc.flags.sqlite3.$(1) := $(6) -$$(eval $$(call C-PP.FILTER, $$(sqlite3-api.js.in), $(3), $(5))) -$(4): $(3) $$(MAKEFILE) $$(sqlite3-wasm.cses) $$(EXPORTED_FUNCTIONS.api) $$(pre-post-sqlite3.deps.$(1)) +$(info Setting up build [$(1)-$(2)]: $(5)) +c-pp.D.$(1)-$(2) := $(6) +$$(eval $$(call call-make-pre-post,$(1),$(2))) +emcc.flags.$(1).$(2) ?= +emcc.flags.$(1).$(2) += $(7) +$$(eval $$(call C-PP.FILTER, $$(sqlite3-api.js.in), $(4), $(6))) +$(5): $(4) $$(MAKEFILE) $$(sqlite3-wasm.cfiles) $$(EXPORTED_FUNCTIONS.api) $$(pre-post-$(1)-$(2).deps) @echo "Building $$@ ..." $$(emcc.bin) -o $$@ $$(emcc_opt_full) $$(emcc.flags) \ $$(emcc.jsflags) \ - -sENVIRONMENT=$$(emcc.environment.$(1)) \ - $$(pre-post-sqlite3.flags.$(1)) $$(emcc.flags.sqlite3.$(1)) \ - $$(cflags.common) $$(SQLITE_OPT) $$(cflags.wasm_extra_init) $$(sqlite3-wasm.cses) - @$$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(2)) - @case $(1) in \ + -sENVIRONMENT=$$(emcc.environment.$(2)) \ + $$(pre-post-$(1)-$(2).flags) \ + $$(emcc.flags.$(1)) $$(emcc.flags.$(1).$(2)) \ + $$(cflags.common) $$(SQLITE_OPT) \ + $$(cflags.$(1)) $$(cflags.$(1).$(2)) \ + $$(cflags.wasm_extra_init) $$(sqlite3-wasm.cfiles) + @$$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(3)) + @dotwasm=$$(basename $$@).wasm; \ + chmod -x $$$$dotwasm; \ + $(maybe-wasm-strip) $$$$dotwasm; \ + case $(2) in \ bundler-friendly|node) \ - echo "Patching $(3) for sqlite3.wasm..."; \ - rm -f $$(dir.dout)/sqlite3-$(1).wasm; \ - sed -i -e 's/sqlite3-$(1).wasm/sqlite3.wasm/g' $$@ || exit $$$$?; \ + echo "Patching $$@ for $(1).wasm..."; \ + rm -f $$$$dotwasm; \ + dotwasm=; \ + sed -i -e 's/$(1)-$(2).wasm/$(1).wasm/g' $$@ || exit $$$$?; \ ;; \ - esac - chmod -x $$(sqlite3.wasm) - $$(maybe-wasm-strip) $$(sqlite3.wasm) - @ls -la $@ $$(sqlite3.wasm) -all: $(4) -quick: $(4) -CLEAN_FILES += $(3) $(4) + esac; \ + ls -la $$$$dotwasm $$@ +all: $(5) +#quick: $(5) +CLEAN_FILES += $(4) $(5) endef # ^^^ /SETUP_LIB_BUILD_MODE ######################################################################## @@ -753,21 +784,24 @@ sqlite3-api-bundler-friendly.mjs := $(dir.dout)/sqlite3-api-bundler-friendly.mjs sqlite3-bundler-friendly.mjs := $(dir.dout)/sqlite3-bundler-friendly.mjs sqlite3-api-node.mjs := $(dir.dout)/sqlite3-api-node.mjs sqlite3-node.mjs := $(dir.dout)/sqlite3-node.mjs -# Maintenance reminder: careful not to introduce spaces around args $1, $2 -#$(info $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) -$(eval $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) -$(eval $(call SETUP_LIB_BUILD_MODE,esm,1, $(sqlite3-api.mjs), $(sqlite3.mjs), \ +#$(info $(call SETUP_LIB_BUILD_MODE,sqlite3,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) +$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,vanilla,0,\ + $(sqlite3-api.js), $(sqlite3.js))) +$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,esm,1,\ + $(sqlite3-api.mjs), $(sqlite3.mjs), \ -Dtarget=es6-module, -sEXPORT_ES6 -sUSE_ES6_IMPORT_META)) -$(eval $(call SETUP_LIB_BUILD_MODE,bundler-friendly,1,\ +$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,bundler-friendly,1,\ $(sqlite3-api-bundler-friendly.mjs),$(sqlite3-bundler-friendly.mjs),\ - $(c-pp.D.esm) -Dtarget=es6-bundler-friendly)) -$(eval $(call SETUP_LIB_BUILD_MODE,node,1,\ + $(c-pp.D.sqlite3-esm) -Dtarget=es6-bundler-friendly)) +$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,node,1,\ $(sqlite3-api-node.mjs),$(sqlite3-node.mjs),\ - $(c-pp.D.bundler-friendly) -Dtarget=node)) + $(c-pp.D.sqlite3-bundler-friendly) -Dtarget=node)) # The various -D... values used by *.c-pp.js include: # # -Dtarget=es6-module: for all ESM module builds # +# -Dtarget=node: for node.js builds +# # -Dtarget=es6-module -Dtarget=es6-bundler-friendly: intended for # "bundler-friendly" ESM module build. These have some restrictions # on how URL() objects are constructed in some contexts: URLs which @@ -841,7 +875,7 @@ clean-batch: # a regular basis with different -Ox flags and rebuilding the batch # pieces each time is an unnecessary time sink. batch: batch-runner.list -all: batch +#all: batch # end batch-runner.js ######################################################################## # Wasmified speedtest1 is our primary benchmarking tool. @@ -849,7 +883,7 @@ all: batch # emcc.speedtest1.common = emcc flags used by multiple builds of speedtest1 # emcc.speedtest1 = emcc flags used by main build of speedtest1 emcc.speedtest1.common := $(emcc_opt_full) -emcc.speedtest1 := +emcc.speedtest1 := -I. -I$(dir $(sqlite3.canonical.c)) emcc.speedtest1 += -sENVIRONMENT=web emcc.speedtest1 += -sALLOW_MEMORY_GROWTH emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) @@ -892,22 +926,24 @@ $(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api.main) @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api.main); } > $@ speedtest1.js := $(dir.dout)/speedtest1.js speedtest1.wasm := $(dir.dout)/speedtest1.wasm -cflags.speedtest1 := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM -speedtest1.cses := $(speedtest1.c) $(sqlite3-wasm.c) +emcc.flags.speedtest1-vanilla := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM + +speedtest1.cfiles := $(speedtest1.c) $(sqlite3-wasm.c) $(eval $(call call-make-pre-post,speedtest1,vanilla)) -$(speedtest1.js): $(MAKEFILE) $(speedtest1.cses) \ - $(pre-post-speedtest1.deps.vanilla) \ +$(speedtest1.js): $(MAKEFILE) $(speedtest1.cfiles) \ + $(pre-post-speedtest1-vanilla.deps) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." $(emcc.bin) \ - $(emcc.speedtest1) -I$(dir $(sqlite3.canonical.c)) \ + $(emcc.speedtest1) \ $(emcc.speedtest1.common) \ - $(cflags.speedtest1) $(pre-post-speedtest1.flags.vanilla) \ + $(emcc.flags.speedtest1-vanilla) $(pre-post-speedtest1-vanilla.flags) \ $(SQLITE_OPT) \ -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c) \ $(speedtest1.exit-runtime0) \ - -o $@ $(speedtest1.cses) -lm + -o $@ $(speedtest1.cfiles) -lm $(maybe-wasm-strip) $(speedtest1.wasm) + chmod -x $(speedtest1.wasm) ls -la $@ $(speedtest1.wasm) speedtest1: $(speedtest1.js) @@ -933,13 +969,14 @@ CLEAN_FILES += $(speedtest1.js) $(speedtest1.wasm) # # To create those, we filter tester1.c-pp.js with $(bin.c-pp)... $(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.js)) -$(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.esm))) +$(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.sqlite3-esm))) $(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1.html)) -$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.esm))) +$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.sqlite3-esm))) tester1: tester1.js tester1.mjs tester1.html tester1-esm.html # Note that we do not include $(sqlite3-bundler-friendly.mjs) in this # because bundlers are client-specific. all quick: tester1 +quick: $(sqlite3.js) ######################################################################## # Convenience rules to rebuild with various -Ox levels. Much @@ -959,8 +996,6 @@ o1: clean $(MAKE) -e "emcc_opt=-O1 $(o-xtra)" o2: clean $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" -qo2: clean - $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" quick o3: clean $(MAKE) -e "emcc_opt=-O3 $(o-xtra)" os: clean @@ -968,8 +1003,6 @@ os: clean $(MAKE) -e "emcc_opt=-Os $(o-xtra)" oz: clean $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" -qoz: clean - $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" quick ######################################################################## # Sub-makes... @@ -981,6 +1014,8 @@ include fiddle.make ifneq (,$(filter wasmfs,$(MAKECMDGOALS))) wasmfs.enable ?= 1 else +# Unconditionally enable wasmfs for [dist]clean so that the wasmfs +# sub-make can clean up. wasmfs.enable ?= $(if $(filter %clean,$(MAKECMDGOALS)),1,0) endif ifeq (1,$(wasmfs.enable)) @@ -1011,8 +1046,7 @@ endif # Push files to public wasm-testing.sqlite.org server wasm-testing.include = *.js *.mjs *.html \ ./tests \ - batch-runner.list \ - $(dir.dout) $(dir.sql) $(dir.common) $(dir.fiddle) $(dir.jacc) + $(dir.dout) $(dir.common) $(dir.fiddle) $(dir.jacc) wasm-testing.exclude = sql/speedtest1.sql wasm-testing.dir = /jail/sites/wasm-testing wasm-testing.dest ?= wasm-testing:$(wasm-testing.dir) diff --git a/ext/wasm/README.md b/ext/wasm/README.md index e8d66865d..0c328310d 100644 --- a/ext/wasm/README.md +++ b/ext/wasm/README.md @@ -82,7 +82,7 @@ features in the apps which use them. # Testing on a remote machine that is accessed via SSH -*NB: The following are developer notes, last validated on 2022-08-18* +*NB: The following are developer notes, last validated on 2023-07-19* * Remote: Install git, emsdk, and althttpd * Use a [version of althttpd][althttpd] from @@ -93,13 +93,14 @@ features in the apps which use them. * Local: `ssh -L 8180:localhost:8080 remote` * Local: Point your web-browser at http://localhost:8180/index.html -In order to enable [SharedArrayBuffers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer), -the web-browser requires that the two extra Cross-Origin lines be present -in HTTP reply headers and that the request must come from "localhost". -Since the web-server is on a different machine from -the web-broser, the localhost requirement means that the connection must be tunneled -using SSH. +In order to enable [SharedArrayBuffer][], the web-browser requires +that the two extra Cross-Origin lines be present in HTTP reply headers +and that the request must come from "localhost" (_or_ over an SSL +connection). Since the web-server is on a different machine from the +web-broser, the localhost requirement means that the connection must +be tunneled using SSH. [emscripten]: https://emscripten.org [althttpd]: https://sqlite.org/althttpd +[SharedArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md index 6618666ae..eb0f073cf 100644 --- a/ext/wasm/api/README.md +++ b/ext/wasm/api/README.md @@ -83,15 +83,18 @@ browser client: helpers for use by downstream code which creates `sqlite3_vfs` and `sqlite3_module` implementations. - **`sqlite3-vfs-opfs.c-pp.js`**\ - is an sqlite3 VFS implementation which supports Google Chrome's - Origin-Private FileSystem (OPFS) as a storage layer to provide - persistent storage for database files in a browser. It requires... + is an sqlite3 VFS implementation which supports the Origin-Private + FileSystem (OPFS) as a storage layer to provide persistent storage + for database files in a browser. It requires... - **`sqlite3-opfs-async-proxy.js`**\ is the asynchronous backend part of the OPFS proxy. It speaks directly to the (async) OPFS API and channels those results back to its synchronous counterpart. This file, because it must be started in its own Worker, is not part of the amalgamation. -- **`api/sqlite3-api-cleanup.js`**\ +- **`sqlite3-vfs-opfs-sahpool.c-pp.js`**\ + is another sqlite3 VFS supporting the OPFS, but uses a completely + different approach that the above-listed one. +- **`sqlite3-api-cleanup.js`**\ The previous files do not immediately extend the library. Instead they add callback functions to be called during its bootstrapping. Some also temporarily create global objects in order @@ -108,13 +111,15 @@ browser client: with `c-pp`](#c-pp), noting that such preprocessing may be applied after all of the relevant files are concatenated. That extension is used primarily to keep the code maintainers cognisant of the fact that -those files contain constructs which will not run as-is in JavaScript. +those files contain constructs which may not run as-is in any given +JavaScript environment. The build process glues those files together, resulting in -`sqlite3-api.js`, which is everything except for the `post-js-*.js` -files, and `sqlite3.js`, which is the Emscripten-generated amalgamated -output and includes the `post-js-*.js` parts, as well as the -Emscripten-provided module loading pieces. +`sqlite3-api.js`, which is everything except for the +`pre/post-js-*.js` files, and `sqlite3.js`, which is the +Emscripten-generated amalgamated output and includes the +`pre/post-js-*.js` parts, as well as the Emscripten-provided module +loading pieces. The non-JS outlier file is `sqlite3-wasm.c`: it is a proxy for `sqlite3.c` which `#include`'s that file and adds a couple more @@ -152,8 +157,8 @@ Preprocessing of Source Files ------------------------------------------------------------------------ Certain files in the build require preprocessing to filter in/out -parts which differ between vanilla JS builds and ES6 Module -(a.k.a. esm) builds. The preprocessor application itself is in +parts which differ between vanilla JS, ES6 Modules, and node.js +builds. The preprocessor application itself is in [`c-pp.c`](/file/ext/wasm/c-pp.c) and the complete technical details of such preprocessing are maintained in [`GNUMakefile`](/file/ext/wasm/GNUmakefile). diff --git a/ext/wasm/api/extern-post-js.c-pp.js b/ext/wasm/api/extern-post-js.c-pp.js index 927bf64f9..63e55051c 100644 --- a/ext/wasm/api/extern-post-js.c-pp.js +++ b/ext/wasm/api/extern-post-js.c-pp.js @@ -23,10 +23,7 @@ const toExportForESM = impls which Emscripten installs at some point in the file above this. */ - const originalInit = - /* Maintenance reminder: DO NOT use `self.` here. It's correct - for non-ES6 Module cases but wrong for ES6 modules because those - resolve this symbol differently. */ sqlite3InitModule; + const originalInit = sqlite3InitModule; if(!originalInit){ throw new Error("Expecting globalThis.sqlite3InitModule to be defined by the Emscripten build."); } @@ -65,19 +62,19 @@ const toExportForESM = globalThis.sqlite3InitModule = function ff(...args){ //console.warn("Using replaced sqlite3InitModule()",globalThis.location); return originalInit(...args).then((EmscriptenModule)=>{ +//#if wasmfs if('undefined'!==typeof WorkerGlobalScope && - (EmscriptenModule['ENVIRONMENT_IS_PTHREAD'] - || EmscriptenModule['_pthread_self'] - || 'function'===typeof threadAlert - || globalThis?.location?.pathname?.endsWith?.('.worker.js') - )){ + EmscriptenModule['ENVIRONMENT_IS_PTHREAD']){ /** Workaround for wasmfs-generated worker, which calls this routine from each individual thread and requires that its - argument be returned. All of the criteria above are fragile, - based solely on inspection of the offending code, not public - Emscripten details. */ + argument be returned. The conditional criteria above are + fragile, based solely on inspection of the offending code, + not public Emscripten details. */ + //console.warn("sqlite3InitModule() returning E-module.",EmscriptenModule); return EmscriptenModule; } +//#endif + //console.warn("sqlite3InitModule() returning sqlite3 object."); const s = EmscriptenModule.sqlite3; s.scriptInfo = initModuleState; //console.warn("sqlite3.scriptInfo =",s.scriptInfo); @@ -124,5 +121,6 @@ const toExportForESM = return globalThis.sqlite3InitModule /* required for ESM */; })(); //#if target=es6-module -export default toExportForESM; +sqlite3InitModule = toExportForESM; +export default sqlite3InitModule; //#endif diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index d38b401bf..65dbb4eb6 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -22,8 +22,10 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build */ const SABC = Object.assign( Object.create(null), { - exports: Module['asm'], - memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */ + exports: ('undefined'===typeof wasmExports) + ? Module['asm']/* emscripten <=3.1.43 */ + : wasmExports /* emscripten >=3.1.44 */, + memory: Module.wasmMemory /* gets set if built with -sIMPORTED_MEMORY */ }, globalThis.sqlite3ApiConfig || {} ); diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index f444ec975..60050461c 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -608,6 +608,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], ["sqlite3_wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int"], + ["sqlite3_wasm_posix_create_file", "int", "string","*", "int"], ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] ]; @@ -728,6 +729,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Populate api object with sqlite3_...() by binding the "raw" wasm exports into type-converting proxies using wasm.xWrap(). */ + if(0 === wasm.exports.sqlite3_step.length){ + /* This environment wraps exports in nullary functions, which means + we must disable the arg-count validation we otherwise perform + on the wrappers. */ + wasm.xWrap.doArgcCheck = false; + sqlite3.config.warn( + "Disabling sqlite3.wasm.xWrap.doArgcCheck due to environmental quirks." + ); + } for(const e of wasm.bindingSignatures){ capi[e[0]] = wasm.xWrap.apply(null, e); } diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index ac6678c88..4677b8976 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -55,6 +55,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(sqliteResultCode){ if(dbPtr instanceof DB) dbPtr = dbPtr.pointer; toss3( + sqliteResultCode, "sqlite3 result code",sqliteResultCode+":", (dbPtr ? capi.sqlite3_errmsg(dbPtr) @@ -330,10 +331,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - `db`: the DB object which created the statement. - - `columnCount`: the number of result columns in the query, or 0 for - queries which cannot return results. + - `columnCount`: the number of result columns in the query, or 0 + for queries which cannot return results. This property is a proxy + for sqlite3_column_count() and its use in loops should be avoided + because of the call overhead associated with that. The + `columnCount` is not cached when the Stmt is created because a + schema change made via a separate db connection between this + statement's preparation and when it is stepped may invalidate it. - - `parameterCount`: the number of bindable paramters in the query. + - `parameterCount`: the number of bindable parameters in the query. */ const Stmt = function(){ if(BindTypes!==arguments[2]){ @@ -341,7 +347,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } this.db = arguments[0]; __ptrMap.set(this, arguments[1]); - this.columnCount = capi.sqlite3_column_count(this.pointer); this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer); }; @@ -473,7 +478,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const __selectFirstRow = (db, sql, bind, ...getArgs)=>{ const stmt = db.prepare(sql); try { - return stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined; + const rc = stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined; + stmt.reset(/*for INSERT...RETURNING locking case*/); + return rc; }finally{ stmt.finalize(); } @@ -499,6 +506,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "Not an error." The various non-0 non-error codes need to be checked for in client code where they are expected. + The thrown exception's `resultCode` property will be the value of + the second argument to this function. + If it does not throw, it returns its first argument. */ DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode); @@ -546,7 +556,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } const pDb = this.pointer; Object.keys(__stmtMap.get(this)).forEach((k,s)=>{ - if(s && s.pointer) s.finalize(); + if(s && s.pointer){ + try{s.finalize()} + catch(e){/*ignore*/} + } }); __ptrMap.delete(this); __stmtMap.delete(this); @@ -701,18 +714,18 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ with identical names. - `callback` = a function which gets called for each row of the - result set, but only if that statement has any result - _rows_. The callback's "this" is the options object, noting - that this function synthesizes one if the caller does not pass - one to exec(). The second argument passed to the callback is - always the current Stmt object, as it's needed if the caller - wants to fetch the column names or some such (noting that they - could also be fetched via `this.columnNames`, if the client - provides the `columnNames` option). If the callback returns a - literal `false` (as opposed to any other falsy value, e.g. an - implicit `undefined` return), any ongoing statement-`step()` - iteration stops without an error. The return value of the - callback is otherwise ignored. + result set, but only if that statement has any result rows. The + callback's "this" is the options object, noting that this + function synthesizes one if the caller does not pass one to + exec(). The second argument passed to the callback is always + the current Stmt object, as it's needed if the caller wants to + fetch the column names or some such (noting that they could + also be fetched via `this.columnNames`, if the client provides + the `columnNames` option). If the callback returns a literal + `false` (as opposed to any other falsy value, e.g. an implicit + `undefined` return), any ongoing statement-`step()` iteration + stops without an error. The return value of the callback is + otherwise ignored. ACHTUNG: The callback MUST NOT modify the Stmt object. Calling any of the Stmt.get() variants, Stmt.getColumnName(), or @@ -733,7 +746,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ A.1) `'array'` (the default) causes the results of `stmt.get([])` to be passed to the `callback` and/or appended - to `resultRows` + to `resultRows`. A.2) `'object'` causes the results of `stmt.get(Object.create(null))` to be passed to the @@ -744,8 +757,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ A.3) `'stmt'` causes the current Stmt to be passed to the callback, but this mode will trigger an exception if - `resultRows` is an array because appending the statement to - the array would be downright unhelpful. + `resultRows` is an array because appending the transient + statement to the array would be downright unhelpful. B) An integer, indicating a zero-based column in the result row. Only that one single value will be passed on. @@ -775,7 +788,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ should return: A) The default value is (usually) `"this"`, meaning that the - DB object itself should be returned. The exceptions is if + DB object itself should be returned. The exception is if the caller passes neither of `callback` nor `returnValue` but does pass an explicit `rowMode` then the default `returnValue` is `"resultRows"`, described below. @@ -857,38 +870,53 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ bind = null; } if(evalFirstResult && stmt.columnCount){ - /* Only forward SELECT results for the FIRST query + /* Only forward SELECT-style results for the FIRST query in the SQL which potentially has them. */ + let gotColNames = Array.isArray( + opt.columnNames + /* As reported in + https://sqlite.org/forum/forumpost/7774b773937cbe0a + we need to delay fetching of the column names until + after the first step() (if we step() at all) because + a schema change between the prepare() and step(), via + another connection, may invalidate the column count + and names. */) ? 0 : 1; evalFirstResult = false; - if(Array.isArray(opt.columnNames)){ - stmt.getColumnNames(opt.columnNames); - } if(arg.cbArg || resultRows){ - for(; stmt.step(); stmt._isLocked = false){ - stmt._isLocked = true; + for(; stmt.step(); stmt._lockedByExec = false){ + if(0===gotColNames++) stmt.getColumnNames(opt.columnNames); + stmt._lockedByExec = true; const row = arg.cbArg(stmt); if(resultRows) resultRows.push(row); if(callback && false === callback.call(opt, row, stmt)){ break; } } - stmt._isLocked = false; + stmt._lockedByExec = false; + } + if(0===gotColNames){ + /* opt.columnNames was provided but we visited no result rows */ + stmt.getColumnNames(opt.columnNames); } }else{ stmt.step(); } - stmt.finalize(); + stmt.reset( + /* In order to trigger an exception in the + INSERT...RETURNING locking scenario: + https://sqlite.org/forum/forumpost/36f7a2e7494897df + */).finalize(); stmt = null; - } + }/*prepare() loop*/ }/*catch(e){ sqlite3.config.warn("DB.exec() is propagating exception",opt,e); throw e; }*/finally{ + wasm.scopedAllocPop(stack); if(stmt){ - delete stmt._isLocked; + delete stmt._lockedByExec; stmt.finalize(); } - wasm.scopedAllocPop(stack); } return arg.returnVal(); }/*exec()*/, @@ -1107,6 +1135,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ try { stmt.bind(bind); while(stmt.step()) rc.push(stmt.get(0,asType)); + stmt.reset(/*for INSERT...RETURNING locking case*/); }finally{ stmt.finalize(); } @@ -1241,7 +1270,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ not throw, it returns this object. */ checkRc: function(resultCode){ - return DB.checkRc(this, resultCode); + return checkSqlite3Rc(this, resultCode); } }/*DB.prototype*/; @@ -1302,15 +1331,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** - If stmt._isLocked is truthy, this throws an exception + If stmt._lockedByExec is truthy, this throws an exception complaining that the 2nd argument (an operation name, e.g. "bind()") is not legal while the statement is "locked". Locking happens before an exec()-like callback is passed a statement, to ensure that the callback does not mutate or finalize the statement. If it does not throw, it returns stmt. */ - const affirmUnlocked = function(stmt,currentOpName){ - if(stmt._isLocked){ + const affirmNotLockedByExec = function(stmt,currentOpName){ + if(stmt._lockedByExec){ toss3("Operation is illegal when statement is locked:",currentOpName); } return stmt; @@ -1323,14 +1352,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ success. */ const bindOne = function f(stmt,ndx,bindType,val){ - affirmUnlocked(affirmStmtOpen(stmt), 'bind()'); + affirmNotLockedByExec(affirmStmtOpen(stmt), 'bind()'); if(!f._){ f._tooBigInt = (v)=>toss3( "BigInt value is too big to store without precision loss:", v ); - /* Reminder: when not in BigInt mode, it's impossible for - JS to represent a number out of the range we can bind, - so we have no range checking. */ f._ = { string: function(stmt, ndx, val, asBlob){ const [pStr, n] = wasm.allocCString(val, true); @@ -1404,46 +1430,67 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Stmt.prototype = { /** - "Finalizes" this statement. This is a no-op if the - statement has already been finalizes. Returns - undefined. Most methods in this class will throw if called - after this is. + "Finalizes" this statement. This is a no-op if the statement + has already been finalized. Returns the result of + sqlite3_finalize() (0 on success, non-0 on error), or the + undefined value if the statement has already been + finalized. Regardless of success or failure, most methods in + this class will throw if called after this is. + + This method always throws if called when it is illegal to do + so. Namely, when triggered via a per-row callback handler of a + DB.exec() call. */ finalize: function(){ if(this.pointer){ - affirmUnlocked(this,'finalize()'); + affirmNotLockedByExec(this,'finalize()'); + const rc = capi.sqlite3_finalize(this.pointer); delete __stmtMap.get(this.db)[this.pointer]; - capi.sqlite3_finalize(this.pointer); __ptrMap.delete(this); delete this._mayGet; - delete this.columnCount; delete this.parameterCount; + delete this._lockedByExec; delete this.db; - delete this._isLocked; + return rc; } }, - /** Clears all bound values. Returns this object. - Throws if this statement has been finalized. */ + /** + Clears all bound values. Returns this object. Throws if this + statement has been finalized or if modification of the + statement is currently illegal (e.g. in the per-row callback of + a DB.exec() call). + */ clearBindings: function(){ - affirmUnlocked(affirmStmtOpen(this), 'clearBindings()') + affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()') capi.sqlite3_clear_bindings(this.pointer); this._mayGet = false; return this; }, /** - Resets this statement so that it may be step()ed again - from the beginning. Returns this object. Throws if this - statement has been finalized. + Resets this statement so that it may be step()ed again from the + beginning. Returns this object. Throws if this statement has + been finalized, if it may not legally be reset because it is + currently being used from a DB.exec() callback, or if the + underlying call to sqlite3_reset() returns non-0. If passed a truthy argument then this.clearBindings() is also called, otherwise any existing bindings, along with any memory allocated for them, are retained. + + In versions 3.42.0 and earlier, this function did not throw if + sqlite3_reset() returns non-0, but it was discovered that + throwing (or significant extra client-side code) is necessary + in order to avoid certain silent failure scenarios, as + discussed at: + + https://sqlite.org/forum/forumpost/36f7a2e7494897df */ reset: function(alsoClearBinds){ - affirmUnlocked(this,'reset()'); + affirmNotLockedByExec(this,'reset()'); if(alsoClearBinds) this.clearBindings(); - capi.sqlite3_reset(affirmStmtOpen(this).pointer); + const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer); this._mayGet = false; + checkSqlite3Rc(this.db, rc); return this; }, /** @@ -1592,7 +1639,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ value is returned. Throws on error. */ step: function(){ - affirmUnlocked(this, 'step()'); + affirmNotLockedByExec(this, 'step()'); const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer); switch(rc){ case capi.SQLITE_DONE: return this._mayGet = false; @@ -1627,11 +1674,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return this.reset(); }, /** - Functions like step() except that it finalizes this statement - immediately after stepping unless the step cannot be performed - because the statement is locked. Throws on error, but any error - other than the statement-is-locked case will also trigger - finalization of this statement. + Functions like step() except that it calls finalize() on this + statement immediately after stepping, even if the step() call + throws. On success, it returns true if the step indicated that a row of data was available, else it returns false. @@ -1643,9 +1688,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ``` */ stepFinalize: function(){ - const rc = this.step(); - this.finalize(); - return rc; + try{ + const rc = this.step(); + this.reset(/*for INSERT...RETURNING locking case*/); + return rc; + }finally{ + try{this.finalize()} + catch(e){/*ignored*/} + } }, /** Fetches the value from the given 0-based column index of @@ -1686,13 +1736,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } if(Array.isArray(ndx)){ let i = 0; - while(itoss3("The columnCount property is read-only.") + }); /** The OO API's public namespace. */ sqlite3.oo1 = { diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index c882d5b24..ca5d1c44f 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -53,7 +53,7 @@ - `memory`[^1]: optional WebAssembly.Memory object, defaulting to `exports.memory`. In Emscripten environments this should be set - to `Module.wasmMemory` if the build uses `-sIMPORT_MEMORY`, or be + to `Module.wasmMemory` if the build uses `-sIMPORTED_MEMORY`, or be left undefined/falsy to default to `exports.memory` when using WASM-exported memory. @@ -88,12 +88,12 @@ can be replaced with (e.g.) empty functions to squelch all such output. - - `wasmfsOpfsDir`[^1]: As of 2022-12-17, this feature does not - currently work due to incompatible Emscripten-side changes made - in the WASMFS+OPFS combination. This option is currently ignored. + - `wasmfsOpfsDir`[^1]: Specifies the "mount point" of the OPFS-backed + filesystem in WASMFS-capable builds. - [^1] = This property may optionally be a function, in which case this - function re-assigns calls that function to fetch the value, + + [^1] = This property may optionally be a function, in which case + this function calls that function to fetch the value, enabling delayed evaluation. The returned object is the top-level sqlite3 namespace object. @@ -125,11 +125,11 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( log: console.log.bind(console), wasmfsOpfsDir: '/opfs', /** - useStdAlloc is just for testing an allocator discrepancy. The + useStdAlloc is just for testing allocator discrepancies. The docs guarantee that this is false in the canonical builds. For 99% of purposes it doesn't matter which allocators we use, but - it becomes significant with, e.g., sqlite3_deserialize() - and certain wasm.xWrap.resultAdapter()s. + it becomes significant with, e.g., sqlite3_deserialize() and + certain wasm.xWrap.resultAdapter()s. */ useStdAlloc: false }, apiConfig || {}); @@ -149,11 +149,6 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( config[k] = config[k](); } }); - config.wasmOpfsDir = - /* 2022-12-17: WASMFS+OPFS can no longer be activated from the - main thread (aborts via a failed assert() if it's attempted), - which eliminates any(?) benefit to supporting it. */ false; - /** The main sqlite3 binding API gets installed into this object, mimicking the C API as closely as we can. The numerous members @@ -809,7 +804,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( || toss3("Missing API config.exports (WASM module exports)."), /** - When Emscripten compiles with `-sIMPORT_MEMORY`, it + When Emscripten compiles with `-sIMPORTED_MEMORY`, it initalizes the heap and imports it into wasm, as opposed to the other way around. In this case, the memory is not available via this.exports.memory. @@ -1177,31 +1172,31 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** State for sqlite3_wasmfs_opfs_dir(). */ let __wasmfsOpfsDir = undefined; /** - 2022-12-17: incompatible WASMFS changes have made WASMFS+OPFS - unavailable from the main thread, which eliminates the most - significant benefit of supporting WASMFS. This function is now a - no-op which always returns a falsy value. Before that change, - this function behaved as documented below (and how it will again - if we can find a compelling reason to support it). - If the wasm environment has a WASMFS/OPFS-backed persistent storage directory, its path is returned by this function. If it does not then it returns "" (noting that "" is a falsy value). The first time this is called, this function inspects the current environment to determine whether persistence support is available - and, if it is, enables it (if needed). + and, if it is, enables it (if needed). After the first call it + always returns the cached result. + + If the returned string is not empty, any files stored under the + given path (recursively) are housed in OPFS storage. If the + returned string is empty, this particular persistent storage + option is not available on the client. + + Though the mount point name returned by this function is intended + to remain stable, clients should not hard-coded it anywhere. Always call this function to get the path. - This function currently only recognizes the WASMFS/OPFS storage - combination and its path refers to storage rooted in the - Emscripten-managed virtual filesystem. + Note that this function is a no-op in must builds of this + library, as the WASMFS capability requires a custom + build. */ capi.sqlite3_wasmfs_opfs_dir = function(){ if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; // If we have no OPFS, there is no persistent dir const pdir = config.wasmfsOpfsDir; - console.error("sqlite3_wasmfs_opfs_dir() can no longer work due "+ - "to incompatible WASMFS changes. It will be removed."); if(!pdir || !globalThis.FileSystemHandle || !globalThis.FileSystemDirectoryHandle @@ -1223,8 +1218,6 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** - Experimental and subject to change or removal. - Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a non-empty string and the given name starts with (that string + '/'), else returns false. @@ -1234,13 +1227,6 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return (p && name) ? name.startsWith(p+'/') : false; }; - // This bit is highly arguable and is incompatible with the fiddle shell. - if(false && 0===wasm.exports.sqlite3_vfs_find(0)){ - /* Assume that sqlite3_initialize() has not yet been called. - This will be the case in an SQLITE_OS_KV build. */ - wasm.exports.sqlite3_initialize(); - } - /** Given an `sqlite3*`, an sqlite3_vfs name, and an optional db name (defaulting to "main"), returns a truthy value (see below) if @@ -1371,6 +1357,74 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** + If the current environment supports the POSIX file APIs, this routine + creates (or overwrites) the given file using those APIs. This is + primarily intended for use in Emscripten-based builds where the POSIX + APIs are transparently proxied by an in-memory virtual filesystem. + It may behave diffrently in other environments. + + The first argument must be either a JS string or WASM C-string + holding the filename. Note that this routine does _not_ create + intermediary directories if the filename has a directory part. + + The 2nd argument may either a valid WASM memory pointer, an + ArrayBuffer, or a Uint8Array. The 3rd must be the length, in + bytes, of the data array to copy. If the 2nd argument is an + ArrayBuffer or Uint8Array and the 3rd is not a positive integer + then the 3rd defaults to the array's byteLength value. + + Results are undefined if data is a WASM pointer and dataLen is + exceeds data's bounds. + + Throws if any arguments are invalid or if creating or writing to + the file fails. + + Added in 3.43 as an alternative for the deprecated + sqlite3_js_vfs_create_file(). + */ + capi.sqlite3_js_posix_create_file = function(filename, data, dataLen){ + let pData; + if(data && wasm.isPtr(data)){ + pData = data; + }else if(data instanceof ArrayBuffer || data instanceof Uint8Array){ + pData = wasm.allocFromTypedArray(data); + if(arguments.length<3 || !util.isInt32(dataLen) || dataLen<0){ + dataLen = data.byteLength; + } + }else{ + SQLite3Error.toss("Invalid 2nd argument for sqlite3_js_posix_create_file()."); + } + try{ + if(!util.isInt32(dataLen) || dataLen<0){ + SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file()."); + } + const rc = wasm.sqlite3_wasm_posix_create_file(filename, pData, dataLen); + if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", + capi.sqlite3_js_rc_str(rc)); + }finally{ + wasm.dealloc(pData); + } + }; + + /** + Deprecation warning: this function does not work properly in + debug builds of sqlite3 because its out-of-scope use of the + sqlite3_vfs API triggers assertions in the core library. That + was unfortunately not discovered until 2023-08-11. This function + is now deprecated and should not be used in new code. + + Alternative options: + + - "unix" VFS and its variants can get equivalent functionality + with sqlite3_js_posix_create_file(). + + - OPFS: use either sqlite3.oo1.OpfsDb.importDb(), for the "opfs" + VFS, or the importDb() method of the PoolUtil object provided + by the "opfs-sahpool" OPFS (noting that its VFS name may differ + depending on client-side configuration). We cannot proxy those + from here because the former is necessarily asynchronous and + the latter requires information not available to this function. + Creates a file using the storage appropriate for the given sqlite3_vfs. The first argument may be a VFS name (JS string only, NOT a WASM C-string), WASM-managed `sqlite3_vfs*`, or @@ -1416,9 +1470,13 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( VFS nor the WASM environment imposes requirements which break it. - "opfs": uses OPFS storage and creates directory parts of the - filename. + filename. It can only be used to import an SQLite3 database + file and will fail if given anything else. */ capi.sqlite3_js_vfs_create_file = function(vfs, filename, data, dataLen){ + config.warn("sqlite3_js_vfs_create_file() is deprecated and", + "should be avoided because it can lead to C-level crashes.", + "See its documentation for alternative options."); let pData; if(data){ if(wasm.isPtr(data)){ @@ -1446,7 +1504,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", capi.sqlite3_js_rc_str(rc)); }finally{ - wasm.dealloc(pData); + wasm.dealloc(pData); } }; @@ -1659,7 +1717,8 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( do not. */ tgt.push(capi.sqlite3_value_to_js( - wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)) + wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)), + throwIfCannotConvert )); } return tgt; @@ -1875,6 +1934,9 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( client: undefined, /** + This function is not part of the public interface, but a + piece of internal bootstrapping infrastructure. + Performs any optional asynchronous library-level initialization which might be required. This function returns a Promise which resolves to the sqlite3 namespace object. Any error in the @@ -1890,27 +1952,19 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( then it must be called by client-level code, which must not use the library until the returned promise resolves. - Bug: if called while a prior call is still resolving, the 2nd - call will resolve prematurely, before the 1st call has finished - resolving. The current build setup precludes that possibility, - so it's only a hypothetical problem if/when this function - ever needs to be invoked by clients. + If called multiple times it will return the same promise on + subsequent calls. The current build setup precludes that + possibility, so it's only a hypothetical problem if/when this + function ever needs to be invoked by clients. In Emscripten-based builds, this function is called automatically and deleted from this object. */ - asyncPostInit: async function(){ - let lip = sqlite3ApiBootstrap.initializersAsync; + asyncPostInit: async function ff(){ + if(ff.isReady instanceof Promise) return ff.isReady; + let lia = sqlite3ApiBootstrap.initializersAsync; delete sqlite3ApiBootstrap.initializersAsync; - if(!lip || !lip.length) return Promise.resolve(sqlite3); - lip = lip.map((f)=>{ - const p = (f instanceof Promise) ? f : f(sqlite3); - return p.catch((e)=>{ - console.error("an async sqlite3 initializer failed:",e); - throw e; - }); - }); - const postInit = ()=>{ + const postInit = async ()=>{ if(!sqlite3.__isUnderTest){ /* Delete references to internal-only APIs which are used by some initializers. Retain them when running in test mode @@ -1919,23 +1973,25 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /* It's conceivable that we might want to expose StructBinder to client-side code, but it's only useful if clients build their own sqlite3.wasm which contains their - one C struct types. */ + own C struct types. */ delete sqlite3.StructBinder; } return sqlite3; }; - if(1){ - /* Run all initializers in sequence. The advantage is that it - allows us to have post-init cleanup defined outside of this - routine at the end of the list and have it run at a - well-defined time. */ - let p = lip.shift(); - while(lip.length) p = p.then(lip.shift()); - return p.then(postInit); - }else{ - /* Run them in an arbitrary order. */ - return Promise.all(lip).then(postInit); + const catcher = (e)=>{ + config.error("an async sqlite3 initializer failed:",e); + throw e; + }; + if(!lia || !lia.length){ + return ff.isReady = postInit().catch(catcher); } + lia = lia.map((f)=>{ + return (f instanceof Function) ? async x=>f(sqlite3) : f; + }); + lia.push(postInit); + let p = Promise.resolve(sqlite3); + while(lia.length) p = p.then(lia.shift()); + return ff.isReady = p.catch(catcher); }, /** scriptInfo ideally gets injected into this object by the @@ -1995,7 +2051,7 @@ globalThis.sqlite3ApiBootstrap.initializers = []; specifically for initializers which are asynchronous. All entries in this list must be either async functions, non-async functions which return a Promise, or a Promise. Each function in the list is called - with the sqlite3 ojbect as its only argument. + with the sqlite3 object as its only argument. The resolved value of any Promise is ignored and rejection will kill the asyncPostInit() process (at an indeterminate point because all diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index d1c63c96e..9a386c13e 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -278,6 +278,19 @@ The arguments are in the same form accepted by oo1.DB.exec(), with the exceptions noted below. + If the `countChanges` arguments property (added in version 3.43) is + truthy then the `result` property contained by the returned object + will have a `changeCount` property which holds the number of changes + made by the provided SQL. Because the SQL may contain an arbitrary + number of statements, the `changeCount` is calculated by calling + `sqlite3_total_changes()` before and after the SQL is evaluated. If + the value of `countChanges` is 64 then the `changeCount` property + will be returned as a 64-bit integer in the form of a BigInt (noting + that that will trigger an exception if used in a BigInt-incapable + build). In the latter case, the number of changes is calculated by + calling `sqlite3_total_changes64()` before and after the SQL is + evaluated. + A function-type args.callback property cannot cross the window/Worker boundary, so is not useful here. If args.callback is a string then it is assumed to be a @@ -523,7 +536,13 @@ sqlite3.initWorker1API = function(){ } } try { + const changeCount = !!rc.countChanges + ? db.changes(true,(64===rc.countChanges)) + : undefined; db.exec(rc); + if(undefined !== changeCount){ + rc.changeCount = db.changes(true,64===rc.countChanges) - changeCount; + } if(rc.callback instanceof Function){ rc.callback = theCallback; /* Post a sentinel message to tell the client that the end diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js index ddcad8f61..cafd296c6 100644 --- a/ext/wasm/api/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -35,6 +35,9 @@ https://developer.chrome.com/blog/sync-methods-for-accesshandles/ + Firefox v111 and Safari 16.4, both released in March 2023, also + include this. + We cannot change to the sync forms at this point without breaking clients who use Chrome v104-ish or higher. truncate(), getSize(), flush(), and close() are now (as of v108) synchronous. Calling them @@ -818,9 +821,24 @@ const installAsyncProxy = function(self){ } while(!flagAsyncShutdown){ try { - if('timed-out'===Atomics.wait( + if('not-equal'!==Atomics.wait( state.sabOPView, state.opIds.whichOp, 0, state.asyncIdleWaitTime )){ + /* Maintenance note: we compare against 'not-equal' because + + https://github.com/tomayac/sqlite-wasm/issues/12 + + is reporting that this occassionally, under high loads, + returns 'ok', which leads to the whichOp being 0 (which + isn't a valid operation ID and leads to an exception, + along with a corresponding ugly console log + message). Unfortunately, the conditions for that cannot + be reliably reproduced. The only place in our code which + writes a 0 to the state.opIds.whichOp SharedArrayBuffer + index is a few lines down from here, and that instance + is required in order for clear communication between + the sync half of this proxy and this half. + */ await releaseImplicitLocks(); continue; } diff --git a/ext/wasm/api/sqlite3-v-helper.js b/ext/wasm/api/sqlite3-v-helper.js index 80ab7c5b0..e63da8afc 100644 --- a/ext/wasm/api/sqlite3-v-helper.js +++ b/ext/wasm/api/sqlite3-v-helper.js @@ -100,7 +100,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ACHTUNG: because we cannot generically know how to transform JS exceptions into result codes, the installed functions do no - automatic catching of exceptions. It is critical, to avoid + automatic catching of exceptions. It is critical, to avoid undefined behavior in the C layer, that methods mapped via this function do not throw. The exception, as it were, to that rule is... @@ -295,7 +295,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - If `struct.$zName` is falsy and the entry has a string-type `name` property, `struct.$zName` is set to the C-string form of - that `name` value before registerVfs() is called. + that `name` value before registerVfs() is called. That string + gets added to the on-dispose state of the struct. On success returns this object. Throws on error. */ @@ -608,7 +609,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ This is to facilitate creation of those methods inline in the passed-in object without requiring the client to explicitly get a reference to one of them in order to assign it to the other - one. + one. The `catchExceptions`-installed handlers will account for identical references to the above functions and will install the diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js new file mode 100644 index 000000000..709d3414c --- /dev/null +++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -0,0 +1,1230 @@ +//#ifnot target=node +/* + 2023-07-14 + + 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 holds a sqlite3_vfs backed by OPFS storage which uses a + different implementation strategy than the "opfs" VFS. This one is a + port of Roy Hashimoto's OPFS SyncAccessHandle pool: + + https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/AccessHandlePoolVFS.js + + As described at: + + https://github.com/rhashimoto/wa-sqlite/discussions/67 + + with Roy's explicit permission to permit us to port his to our + infrastructure rather than having to clean-room reverse-engineer it: + + https://sqlite.org/forum/forumpost/e140d84e71 + + Primary differences from the "opfs" VFS include: + + - This one avoids the need for a sub-worker to synchronize + communication between the synchronous C API and the + only-partly-synchronous OPFS API. + + - It does so by opening a fixed number of OPFS files at + library-level initialization time, obtaining SyncAccessHandles to + each, and manipulating those handles via the synchronous sqlite3_vfs + interface. If it cannot open them (e.g. they are already opened by + another tab) then the VFS will not be installed. + + - Because of that, this one lacks all library-level concurrency + support. + + - Also because of that, it does not require the SharedArrayBuffer, + so can function without the COOP/COEP HTTP response headers. + + - It can hypothetically support Safari 16.4+, whereas the "opfs" VFS + requires v17 due to a subworker/storage bug in 16.x which makes it + incompatible with that VFS. + + - This VFS requires the "semi-fully-sync" FileSystemSyncAccessHandle + (hereafter "SAH") APIs released with Chrome v108 (and all other + major browsers released since March 2023). If that API is not + detected, the VFS is not registered. +*/ +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + 'use strict'; + const toss = sqlite3.util.toss; + const toss3 = sqlite3.util.toss3; + const initPromises = Object.create(null); + const capi = sqlite3.capi; + const wasm = sqlite3.wasm; + // Config opts for the VFS... + const SECTOR_SIZE = 4096; + const HEADER_MAX_PATH_SIZE = 512; + const HEADER_FLAGS_SIZE = 4; + const HEADER_DIGEST_SIZE = 8; + const HEADER_CORPUS_SIZE = HEADER_MAX_PATH_SIZE + HEADER_FLAGS_SIZE; + const HEADER_OFFSET_FLAGS = HEADER_MAX_PATH_SIZE; + const HEADER_OFFSET_DIGEST = HEADER_CORPUS_SIZE; + const HEADER_OFFSET_DATA = SECTOR_SIZE; + /* Bitmask of file types which may persist across sessions. + SQLITE_OPEN_xyz types not listed here may be inadvertently + left in OPFS but are treated as transient by this VFS and + they will be cleaned up during VFS init. */ + const PERSISTENT_FILE_TYPES = + capi.SQLITE_OPEN_MAIN_DB | + capi.SQLITE_OPEN_MAIN_JOURNAL | + capi.SQLITE_OPEN_SUPER_JOURNAL | + capi.SQLITE_OPEN_WAL /* noting that WAL support is + unavailable in the WASM build.*/; + + /** Subdirectory of the VFS's space where "opaque" (randomly-named) + files are stored. Changing this effectively invalidates the data + stored under older names (orphaning it), so don't do that. */ + const OPAQUE_DIR_NAME = ".opaque"; + + /** + Returns short a string of random alphanumeric characters + suitable for use as a random filename. + */ + const getRandomName = ()=>Math.random().toString(36).slice(2); + + const textDecoder = new TextDecoder(); + const textEncoder = new TextEncoder(); + + const optionDefaults = Object.assign(Object.create(null),{ + name: 'opfs-sahpool', + directory: undefined /* derived from .name */, + initialCapacity: 6, + clearOnInit: false, + /* Logging verbosity 3+ == everything, 2 == warnings+errors, 1 == + errors only. */ + verbosity: 2 + }); + + /** Logging routines, from most to least serious. */ + const loggers = [ + sqlite3.config.error, + sqlite3.config.warn, + sqlite3.config.log + ]; + const log = sqlite3.config.log; + const warn = sqlite3.config.warn; + const error = sqlite3.config.error; + + /* Maps (sqlite3_vfs*) to OpfsSAHPool instances */ + const __mapVfsToPool = new Map(); + const getPoolForVfs = (pVfs)=>__mapVfsToPool.get(pVfs); + const setPoolForVfs = (pVfs,pool)=>{ + if(pool) __mapVfsToPool.set(pVfs, pool); + else __mapVfsToPool.delete(pVfs); + }; + /* Maps (sqlite3_file*) to OpfsSAHPool instances */ + const __mapSqlite3File = new Map(); + const getPoolForPFile = (pFile)=>__mapSqlite3File.get(pFile); + const setPoolForPFile = (pFile,pool)=>{ + if(pool) __mapSqlite3File.set(pFile, pool); + else __mapSqlite3File.delete(pFile); + }; + + /** + Impls for the sqlite3_io_methods methods. Maintenance reminder: + members are in alphabetical order to simplify finding them. + */ + const ioMethods = { + xCheckReservedLock: function(pFile,pOut){ + const pool = getPoolForPFile(pFile); + pool.log('xCheckReservedLock'); + pool.storeErr(); + wasm.poke32(pOut, 1); + return 0; + }, + xClose: function(pFile){ + const pool = getPoolForPFile(pFile); + pool.storeErr(); + const file = pool.getOFileForS3File(pFile); + if(file) { + try{ + pool.log(`xClose ${file.path}`); + pool.mapS3FileToOFile(pFile, false); + file.sah.flush(); + if(file.flags & capi.SQLITE_OPEN_DELETEONCLOSE){ + pool.deletePath(file.path); + } + }catch(e){ + pool.storeErr(e); + return capi.SQLITE_IOERR; + } + } + return 0; + }, + xDeviceCharacteristics: function(pFile){ + return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN; + }, + xFileControl: function(pFile, opId, pArg){ + return capi.SQLITE_NOTFOUND; + }, + xFileSize: function(pFile,pSz64){ + const pool = getPoolForPFile(pFile); + pool.log(`xFileSize`); + const file = pool.getOFileForS3File(pFile); + const size = file.sah.getSize() - HEADER_OFFSET_DATA; + //log(`xFileSize ${file.path} ${size}`); + wasm.poke64(pSz64, BigInt(size)); + return 0; + }, + xLock: function(pFile,lockType){ + const pool = getPoolForPFile(pFile); + pool.log(`xLock ${lockType}`); + pool.storeErr(); + const file = pool.getOFileForS3File(pFile); + file.lockType = lockType; + return 0; + }, + xRead: function(pFile,pDest,n,offset64){ + const pool = getPoolForPFile(pFile); + pool.storeErr(); + const file = pool.getOFileForS3File(pFile); + pool.log(`xRead ${file.path} ${n} @ ${offset64}`); + try { + const nRead = file.sah.read( + wasm.heap8u().subarray(pDest, pDest+n), + {at: HEADER_OFFSET_DATA + Number(offset64)} + ); + if(nRead < n){ + wasm.heap8u().fill(0, pDest + nRead, pDest + n); + return capi.SQLITE_IOERR_SHORT_READ; + } + return 0; + }catch(e){ + pool.storeErr(e); + return capi.SQLITE_IOERR; + } + }, + xSectorSize: function(pFile){ + return SECTOR_SIZE; + }, + xSync: function(pFile,flags){ + const pool = getPoolForPFile(pFile); + pool.log(`xSync ${flags}`); + pool.storeErr(); + const file = pool.getOFileForS3File(pFile); + //log(`xSync ${file.path} ${flags}`); + try{ + file.sah.flush(); + return 0; + }catch(e){ + pool.storeErr(e); + return capi.SQLITE_IOERR; + } + }, + xTruncate: function(pFile,sz64){ + const pool = getPoolForPFile(pFile); + pool.log(`xTruncate ${sz64}`); + pool.storeErr(); + const file = pool.getOFileForS3File(pFile); + //log(`xTruncate ${file.path} ${iSize}`); + try{ + file.sah.truncate(HEADER_OFFSET_DATA + Number(sz64)); + return 0; + }catch(e){ + pool.storeErr(e); + return capi.SQLITE_IOERR; + } + }, + xUnlock: function(pFile,lockType){ + const pool = getPoolForPFile(pFile); + pool.log('xUnlock'); + const file = pool.getOFileForS3File(pFile); + file.lockType = lockType; + return 0; + }, + xWrite: function(pFile,pSrc,n,offset64){ + const pool = getPoolForPFile(pFile); + pool.storeErr(); + const file = pool.getOFileForS3File(pFile); + pool.log(`xWrite ${file.path} ${n} ${offset64}`); + try{ + const nBytes = file.sah.write( + wasm.heap8u().subarray(pSrc, pSrc+n), + { at: HEADER_OFFSET_DATA + Number(offset64) } + ); + return nBytes === n ? 0 : capi.SQLITE_IOERR; + }catch(e){ + pool.storeErr(e); + return capi.SQLITE_IOERR; + } + } + }/*ioMethods*/; + + const opfsIoMethods = new capi.sqlite3_io_methods(); + opfsIoMethods.$iVersion = 1; + sqlite3.vfs.installVfs({ + io: {struct: opfsIoMethods, methods: ioMethods} + }); + + /** + Impls for the sqlite3_vfs methods. Maintenance reminder: members + are in alphabetical order to simplify finding them. + */ + const vfsMethods = { + xAccess: function(pVfs,zName,flags,pOut){ + //log(`xAccess ${wasm.cstrToJs(zName)}`); + const pool = getPoolForVfs(pVfs); + pool.storeErr(); + try{ + const name = pool.getPath(zName); + wasm.poke32(pOut, pool.hasFilename(name) ? 1 : 0); + }catch(e){ + /*ignored*/ + wasm.poke32(pOut, 0); + } + return 0; + }, + xCurrentTime: function(pVfs,pOut){ + wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000), + 'double'); + return 0; + }, + xCurrentTimeInt64: function(pVfs,pOut){ + wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(), + 'i64'); + return 0; + }, + xDelete: function(pVfs, zName, doSyncDir){ + const pool = getPoolForVfs(pVfs); + pool.log(`xDelete ${wasm.cstrToJs(zName)}`); + pool.storeErr(); + try{ + pool.deletePath(pool.getPath(zName)); + return 0; + }catch(e){ + pool.storeErr(e); + return capi.SQLITE_IOERR_DELETE; + } + }, + xFullPathname: function(pVfs,zName,nOut,pOut){ + //const pool = getPoolForVfs(pVfs); + //pool.log(`xFullPathname ${wasm.cstrToJs(zName)}`); + const i = wasm.cstrncpy(pOut, zName, nOut); + return i nOut) wasm.poke8(pOut + nOut - 1, 0); + }catch(e){ + return capi.SQLITE_NOMEM; + }finally{ + wasm.scopedAllocPop(scope); + } + } + return 0; + }, + //xSleep is optionally defined below + xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){ + const pool = getPoolForVfs(pVfs); + try{ + pool.log(`xOpen ${wasm.cstrToJs(zName)} ${flags}`); + // First try to open a path that already exists in the file system. + const path = (zName && wasm.peek8(zName)) + ? pool.getPath(zName) + : getRandomName(); + let sah = pool.getSAHForPath(path); + if(!sah && (flags & capi.SQLITE_OPEN_CREATE)) { + // File not found so try to create it. + if(pool.getFileCount() < pool.getCapacity()) { + // Choose an unassociated OPFS file from the pool. + sah = pool.nextAvailableSAH(); + pool.setAssociatedPath(sah, path, flags); + }else{ + // File pool is full. + toss('SAH pool is full. Cannot create file',path); + } + } + if(!sah){ + toss('file not found:',path); + } + // Subsequent I/O methods are only passed the sqlite3_file + // pointer, so map the relevant info we need to that pointer. + const file = {path, flags, sah}; + pool.mapS3FileToOFile(pFile, file); + file.lockType = capi.SQLITE_LOCK_NONE; + const sq3File = new capi.sqlite3_file(pFile); + sq3File.$pMethods = opfsIoMethods.pointer; + sq3File.dispose(); + wasm.poke32(pOutFlags, flags); + return 0; + }catch(e){ + pool.storeErr(e); + return capi.SQLITE_CANTOPEN; + } + }/*xOpen()*/ + }/*vfsMethods*/; + + /** + Creates and initializes an sqlite3_vfs instance for an + OpfsSAHPool. The argument is the VFS's name (JS string). + + Throws if the VFS name is already registered or if something + goes terribly wrong via sqlite3.vfs.installVfs(). + + Maintenance reminder: the only detail about the returned object + which is specific to any given OpfsSAHPool instance is the $zName + member. All other state is identical. + */ + const createOpfsVfs = function(vfsName){ + if( sqlite3.capi.sqlite3_vfs_find(vfsName)){ + toss3("VFS name is already registered:", vfsName); + } + const opfsVfs = new capi.sqlite3_vfs(); + /* We fetch the default VFS so that we can inherit some + methods from it. */ + const pDVfs = capi.sqlite3_vfs_find(null); + const dVfs = pDVfs + ? new capi.sqlite3_vfs(pDVfs) + : null /* dVfs will be null when sqlite3 is built with + SQLITE_OS_OTHER. */; + opfsVfs.$iVersion = 2/*yes, two*/; + opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof; + opfsVfs.$mxPathname = HEADER_MAX_PATH_SIZE; + opfsVfs.addOnDispose( + opfsVfs.$zName = wasm.allocCString(vfsName), + ()=>setPoolForVfs(opfsVfs.pointer, 0) + ); + + if(dVfs){ + /* Inherit certain VFS members from the default VFS, + if available. */ + opfsVfs.$xRandomness = dVfs.$xRandomness; + opfsVfs.$xSleep = dVfs.$xSleep; + dVfs.dispose(); + } + if(!opfsVfs.$xRandomness && !vfsMethods.xRandomness){ + /* If the default VFS has no xRandomness(), add a basic JS impl... */ + vfsMethods.xRandomness = function(pVfs, nOut, pOut){ + const heap = wasm.heap8u(); + let i = 0; + for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF; + return i; + }; + } + if(!opfsVfs.$xSleep && !vfsMethods.xSleep){ + vfsMethods.xSleep = (pVfs,ms)=>0; + } + sqlite3.vfs.installVfs({ + vfs: {struct: opfsVfs, methods: vfsMethods} + }); + return opfsVfs; + }; + + /** + Class for managing OPFS-related state for the + OPFS SharedAccessHandle Pool sqlite3_vfs. + */ + class OpfsSAHPool { + /* OPFS dir in which VFS metadata is stored. */ + vfsDir; + /* Directory handle to this.vfsDir. */ + #dhVfsRoot; + /* Directory handle to the subdir of this.#dhVfsRoot which holds + the randomly-named "opaque" files. This subdir exists in the + hope that we can eventually support client-created files in + this.#dhVfsRoot. */ + #dhOpaque; + /* Directory handle to this.dhVfsRoot's parent dir. Needed + for a VFS-wipe op. */ + #dhVfsParent; + /* Maps SAHs to their opaque file names. */ + #mapSAHToName = new Map(); + /* Maps client-side file names to SAHs. */ + #mapFilenameToSAH = new Map(); + /* Set of currently-unused SAHs. */ + #availableSAH = new Set(); + /* Maps (sqlite3_file*) to xOpen's file objects. */ + #mapS3FileToOFile_ = new Map(); + + /* Maps SAH to an abstract File Object which contains + various metadata about that handle. */ + //#mapSAHToMeta = new Map(); + + /** Buffer used by [sg]etAssociatedPath(). */ + #apBody = new Uint8Array(HEADER_CORPUS_SIZE); + // DataView for this.#apBody + #dvBody; + + // associated sqlite3_vfs instance + #cVfs; + + // Logging verbosity. See optionDefaults.verbosity. + #verbosity; + + constructor(options = Object.create(null)){ + this.#verbosity = options.verbosity ?? optionDefaults.verbosity; + this.vfsName = options.name || optionDefaults.name; + this.#cVfs = createOpfsVfs(this.vfsName); + setPoolForVfs(this.#cVfs.pointer, this); + this.vfsDir = options.directory || ("."+this.vfsName); + this.#dvBody = + new DataView(this.#apBody.buffer, this.#apBody.byteOffset); + this.isReady = this + .reset(!!(options.clearOnInit ?? optionDefaults.clearOnInit)) + .then(()=>{ + if(this.$error) throw this.$error; + return this.getCapacity() + ? Promise.resolve(undefined) + : this.addCapacity(options.initialCapacity + || optionDefaults.initialCapacity); + }); + } + + #logImpl(level,...args){ + if(this.#verbosity>level) loggers[level](this.vfsName+":",...args); + }; + log(...args){this.#logImpl(2, ...args)}; + warn(...args){this.#logImpl(1, ...args)}; + error(...args){this.#logImpl(0, ...args)}; + + getVfs(){return this.#cVfs} + + /* Current pool capacity. */ + getCapacity(){return this.#mapSAHToName.size} + + /* Current number of in-use files from pool. */ + getFileCount(){return this.#mapFilenameToSAH.size} + + /* Returns an array of the names of all + currently-opened client-specified filenames. */ + getFileNames(){ + const rc = []; + const iter = this.#mapFilenameToSAH.keys(); + for(const n of iter) rc.push(n); + return rc; + } + +// #createFileObject(sah,clientName,opaqueName){ +// const f = Object.assign(Object.create(null),{ +// clientName, opaqueName +// }); +// this.#mapSAHToMeta.set(sah, f); +// return f; +// } +// #unmapFileObject(sah){ +// this.#mapSAHToMeta.delete(sah); +// } + + /** + Adds n files to the pool's capacity. This change is + persistent across settings. Returns a Promise which resolves + to the new capacity. + */ + async addCapacity(n){ + for(let i = 0; i < n; ++i){ + const name = getRandomName(); + const h = await this.#dhOpaque.getFileHandle(name, {create:true}); + const ah = await h.createSyncAccessHandle(); + this.#mapSAHToName.set(ah,name); + this.setAssociatedPath(ah, '', 0); + //this.#createFileObject(ah,undefined,name); + } + return this.getCapacity(); + } + + /** + Reduce capacity by n, but can only reduce up to the limit + of currently-available SAHs. Returns a Promise which resolves + to the number of slots really removed. + */ + async reduceCapacity(n){ + let nRm = 0; + for(const ah of Array.from(this.#availableSAH)){ + if(nRm === n || this.getFileCount() === this.getCapacity()){ + break; + } + const name = this.#mapSAHToName.get(ah); + //this.#unmapFileObject(ah); + ah.close(); + await this.#dhOpaque.removeEntry(name); + this.#mapSAHToName.delete(ah); + this.#availableSAH.delete(ah); + ++nRm; + } + return nRm; + } + + /** + Releases all currently-opened SAHs. The only legal + operation after this is acquireAccessHandles(). + */ + releaseAccessHandles(){ + for(const ah of this.#mapSAHToName.keys()) ah.close(); + this.#mapSAHToName.clear(); + this.#mapFilenameToSAH.clear(); + this.#availableSAH.clear(); + } + + /** + Opens all files under this.vfsDir/this.#dhOpaque and acquires + a SAH for each. returns a Promise which resolves to no value + but completes once all SAHs are acquired. If acquiring an SAH + throws, SAHPool.$error will contain the corresponding + exception. + + If clearFiles is true, the client-stored state of each file is + cleared when its handle is acquired, including its name, flags, + and any data stored after the metadata block. + */ + async acquireAccessHandles(clearFiles){ + const files = []; + for await (const [name,h] of this.#dhOpaque){ + if('file'===h.kind){ + files.push([name,h]); + } + } + return Promise.all(files.map(async([name,h])=>{ + try{ + const ah = await h.createSyncAccessHandle() + this.#mapSAHToName.set(ah, name); + if(clearFiles){ + ah.truncate(HEADER_OFFSET_DATA); + this.setAssociatedPath(ah, '', 0); + }else{ + const path = this.getAssociatedPath(ah); + if(path){ + this.#mapFilenameToSAH.set(path, ah); + }else{ + this.#availableSAH.add(ah); + } + } + }catch(e){ + this.storeErr(e); + this.releaseAccessHandles(); + throw e; + } + })); + } + + /** + Given an SAH, returns the client-specified name of + that file by extracting it from the SAH's header. + + On error, it disassociates SAH from the pool and + returns an empty string. + */ + getAssociatedPath(sah){ + sah.read(this.#apBody, {at: 0}); + // Delete any unexpected files left over by previous + // untimely errors... + const flags = this.#dvBody.getUint32(HEADER_OFFSET_FLAGS); + if(this.#apBody[0] && + ((flags & capi.SQLITE_OPEN_DELETEONCLOSE) || + (flags & PERSISTENT_FILE_TYPES)===0)){ + warn(`Removing file with unexpected flags ${flags.toString(16)}`, + this.#apBody); + this.setAssociatedPath(sah, '', 0); + return ''; + } + + const fileDigest = new Uint32Array(HEADER_DIGEST_SIZE / 4); + sah.read(fileDigest, {at: HEADER_OFFSET_DIGEST}); + const compDigest = this.computeDigest(this.#apBody); + if(fileDigest.every((v,i) => v===compDigest[i])){ + // Valid digest + const pathBytes = this.#apBody.findIndex((v)=>0===v); + if(0===pathBytes){ + // This file is unassociated, so truncate it to avoid + // leaving stale db data laying around. + sah.truncate(HEADER_OFFSET_DATA); + } + return pathBytes + ? textDecoder.decode(this.#apBody.subarray(0,pathBytes)) + : ''; + }else{ + // Invalid digest + warn('Disassociating file with bad digest.'); + this.setAssociatedPath(sah, '', 0); + return ''; + } + } + + /** + Stores the given client-defined path and SQLITE_OPEN_xyz flags + into the given SAH. If path is an empty string then the file is + disassociated from the pool but its previous name is preserved + in the metadata. + */ + setAssociatedPath(sah, path, flags){ + const enc = textEncoder.encodeInto(path, this.#apBody); + if(HEADER_MAX_PATH_SIZE <= enc.written + 1/*NUL byte*/){ + toss("Path too long:",path); + } + this.#apBody.fill(0, enc.written, HEADER_MAX_PATH_SIZE); + this.#dvBody.setUint32(HEADER_OFFSET_FLAGS, flags); + + const digest = this.computeDigest(this.#apBody); + sah.write(this.#apBody, {at: 0}); + sah.write(digest, {at: HEADER_OFFSET_DIGEST}); + sah.flush(); + + if(path){ + this.#mapFilenameToSAH.set(path, sah); + this.#availableSAH.delete(sah); + }else{ + // This is not a persistent file, so eliminate the contents. + sah.truncate(HEADER_OFFSET_DATA); + this.#availableSAH.add(sah); + } + } + + /** + Computes a digest for the given byte array and returns it as a + two-element Uint32Array. This digest gets stored in the + metadata for each file as a validation check. Changing this + algorithm invalidates all existing databases for this VFS, so + don't do that. + */ + computeDigest(byteArray){ + let h1 = 0xdeadbeef; + let h2 = 0x41c6ce57; + for(const v of byteArray){ + h1 = 31 * h1 + (v * 307); + h2 = 31 * h2 + (v * 307); + } + return new Uint32Array([h1>>>0, h2>>>0]); + } + + /** + Re-initializes the state of the SAH pool, releasing and + re-acquiring all handles. + + See acquireAccessHandles() for the specifics of the clearFiles + argument. + */ + async reset(clearFiles){ + await this.isReady; + let h = await navigator.storage.getDirectory(); + let prev, prevName; + for(const d of this.vfsDir.split('/')){ + if(d){ + prev = h; + h = await h.getDirectoryHandle(d,{create:true}); + } + } + this.#dhVfsRoot = h; + this.#dhVfsParent = prev; + this.#dhOpaque = await this.#dhVfsRoot.getDirectoryHandle( + OPAQUE_DIR_NAME,{create:true} + ); + this.releaseAccessHandles(); + return this.acquireAccessHandles(clearFiles); + } + + /** + Returns the pathname part of the given argument, + which may be any of: + + - a URL object + - A JS string representing a file name + - Wasm C-string representing a file name + + All "../" parts and duplicate slashes are resolve/removed from + the returned result. + */ + getPath(arg) { + if(wasm.isPtr(arg)) arg = wasm.cstrToJs(arg); + return ((arg instanceof URL) + ? arg + : new URL(arg, 'file://localhost/')).pathname; + } + + /** + Removes the association of the given client-specified file + name (JS string) from the pool. Returns true if a mapping + is found, else false. + */ + deletePath(path) { + const sah = this.#mapFilenameToSAH.get(path); + if(sah) { + // Un-associate the name from the SAH. + this.#mapFilenameToSAH.delete(path); + this.setAssociatedPath(sah, '', 0); + } + return !!sah; + } + + /** + Sets e as this object's current error. Pass a falsy + (or no) value to clear it. + */ + storeErr(e){ + if(e) this.error(e); + return this.$error = e; + } + /** + Pops this object's Error object and returns + it (a falsy value if no error is set). + */ + popErr(){ + const rc = this.$error; + this.$error = undefined; + return rc; + } + + /** + Returns the next available SAH without removing + it from the set. + */ + nextAvailableSAH(){ + const [rc] = this.#availableSAH.keys(); + return rc; + } + + /** + Given an (sqlite3_file*), returns the mapped + xOpen file object. + */ + getOFileForS3File(pFile){ + return this.#mapS3FileToOFile_.get(pFile); + } + /** + Maps or unmaps (if file is falsy) the given (sqlite3_file*) + to an xOpen file object and to this pool object. + */ + mapS3FileToOFile(pFile,file){ + if(file){ + this.#mapS3FileToOFile_.set(pFile, file); + setPoolForPFile(pFile, this); + }else{ + this.#mapS3FileToOFile_.delete(pFile); + setPoolForPFile(pFile, false); + } + } + + /** + Returns true if the given client-defined file name is in this + object's name-to-SAH map. + */ + hasFilename(name){ + return this.#mapFilenameToSAH.has(name) + } + + /** + Returns the SAH associated with the given + client-defined file name. + */ + getSAHForPath(path){ + return this.#mapFilenameToSAH.get(path); + } + + /** + Removes this object's sqlite3_vfs registration and shuts down + this object, releasing all handles, mappings, and whatnot, + including deleting its data directory. There is currently no + way to "revive" the object and reaquire its resources. + + This function is intended primarily for testing. + + Resolves to true if it did its job, false if the + VFS has already been shut down. + */ + async removeVfs(){ + if(!this.#cVfs.pointer || !this.#dhOpaque) return false; + capi.sqlite3_vfs_unregister(this.#cVfs.pointer); + this.#cVfs.dispose(); + try{ + this.releaseAccessHandles(); + await this.#dhVfsRoot.removeEntry(OPAQUE_DIR_NAME, {recursive: true}); + this.#dhOpaque = undefined; + await this.#dhVfsParent.removeEntry( + this.#dhVfsRoot.name, {recursive: true} + ); + this.#dhVfsRoot = this.#dhVfsParent = undefined; + }catch(e){ + sqlite3.config.error(this.vfsName,"removeVfs() failed:",e); + /*otherwise ignored - there is no recovery strategy*/ + } + return true; + } + + + //! Documented elsewhere in this file. + exportFile(name){ + const sah = this.#mapFilenameToSAH.get(name) || toss("File not found:",name); + const n = sah.getSize() - HEADER_OFFSET_DATA; + const b = new Uint8Array(n>0 ? n : 0); + if(n>0){ + const nRead = sah.read(b, {at: HEADER_OFFSET_DATA}); + if(nRead != n){ + toss("Expected to read "+n+" bytes but read "+nRead+"."); + } + } + return b; + } + + //! Documented elsewhere in this file. + importDb(name, bytes){ + if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + const n = bytes.byteLength; + if(n<512 || n%512!=0){ + toss("Byte array size is invalid for an SQLite db."); + } + const header = "SQLite format 3"; + for(let i = 0; i < header.length; ++i){ + if( header.charCodeAt(i) !== bytes[i] ){ + toss("Input does not contain an SQLite database header."); + } + } + const sah = this.#mapFilenameToSAH.get(name) + || this.nextAvailableSAH() + || toss("No available handles to import to."); + const nWrote = sah.write(bytes, {at: HEADER_OFFSET_DATA}); + if(nWrote != n){ + this.setAssociatedPath(sah, '', 0); + toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); + }else{ + this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB); + } + } + + }/*class OpfsSAHPool*/; + + + /** + A OpfsSAHPoolUtil instance is exposed to clients in order to + manipulate an OpfsSAHPool object without directly exposing that + object and allowing for some semantic changes compared to that + class. + + Class docs are in the client-level docs for + installOpfsSAHPoolVfs(). + */ + class OpfsSAHPoolUtil { + /* This object's associated OpfsSAHPool. */ + #p; + + constructor(sahPool){ + this.#p = sahPool; + this.vfsName = sahPool.vfsName; + } + + async addCapacity(n){ return this.#p.addCapacity(n) } + + async reduceCapacity(n){ return this.#p.reduceCapacity(n) } + + getCapacity(){ return this.#p.getCapacity(this.#p) } + + getFileCount(){ return this.#p.getFileCount() } + getFileNames(){ return this.#p.getFileNames() } + + async reserveMinimumCapacity(min){ + const c = this.#p.getCapacity(); + return (c < min) ? this.#p.addCapacity(min - c) : c; + } + + exportFile(name){ return this.#p.exportFile(name) } + + importDb(name, bytes){ return this.#p.importDb(name,bytes) } + + async wipeFiles(){ return this.#p.reset(true) } + + unlink(filename){ return this.#p.deletePath(filename) } + + async removeVfs(){ return this.#p.removeVfs() } + + }/* class OpfsSAHPoolUtil */; + + /** + Returns a resolved Promise if the current environment + has a "fully-sync" SAH impl, else a rejected Promise. + */ + const apiVersionCheck = async ()=>{ + const dh = await navigator.storage.getDirectory(); + const fn = '.opfs-sahpool-sync-check-'+getRandomName(); + const fh = await dh.getFileHandle(fn, { create: true }); + const ah = await fh.createSyncAccessHandle(); + const close = ah.close(); + await close; + await dh.removeEntry(fn); + if(close?.then){ + toss("The local OPFS API is too old for opfs-sahpool:", + "it has an async FileSystemSyncAccessHandle.close() method."); + } + return true; + }; + + /** Only for testing a rejection case. */ + let instanceCounter = 0; + + /** + installOpfsSAHPoolVfs() asynchronously initializes the OPFS + SyncAccessHandle (a.k.a. SAH) Pool VFS. It returns a Promise which + either resolves to a utility object described below or rejects with + an Error value. + + Initialization of this VFS is not automatic because its + registration requires that it lock all resources it + will potentially use, even if client code does not want + to use them. That, in turn, can lead to locking errors + when, for example, one page in a given origin has loaded + this VFS but does not use it, then another page in that + origin tries to use the VFS. If the VFS were automatically + registered, the second page would fail to load the VFS + due to OPFS locking errors. + + If this function is called more than once with a given "name" + option (see below), it will return the same Promise. Calls for + different names will return different Promises which resolve to + independent objects and refer to different VFS registrations. + + On success, the resulting Promise resolves to a utility object + which can be used to query and manipulate the pool. Its API is + described at the end of these docs. + + This function accepts an options object to configure certain + parts but it is only acknowledged for the very first call and + ignored for all subsequent calls. + + The options, in alphabetical order: + + - `clearOnInit`: (default=false) if truthy, contents and filename + mapping are removed from each SAH it is acquired during + initalization of the VFS, leaving the VFS's storage in a pristine + state. Use this only for databases which need not survive a page + reload. + + - `initialCapacity`: (default=6) Specifies the default capacity of + the VFS. This should not be set unduly high because the VFS has + to open (and keep open) a file for each entry in the pool. This + setting only has an effect when the pool is initially empty. It + does not have any effect if a pool already exists. + + - `directory`: (default="."+`name`) Specifies the OPFS directory + name in which to store metadata for the `"opfs-sahpool"` + sqlite3_vfs. Only one instance of this VFS can be installed per + JavaScript engine, and any two engines with the same storage + directory name will collide with each other, leading to locking + errors and the inability to register the VFS in the second and + subsequent engine. Using a different directory name for each + application enables different engines in the same HTTP origin to + co-exist, but their data are invisible to each other. Changing + this name will effectively orphan any databases stored under + previous names. The default is unspecified but descriptive. This + option may contain multiple path elements, e.g. "foo/bar/baz", + and they are created automatically. In practice there should be + no driving need to change this. ACHTUNG: all files in this + directory are assumed to be managed by the VFS. Do not place + other files in that directory, as they may be deleted or + otherwise modified by the VFS. + + - `name`: (default="opfs-sahpool") sets the name to register this + VFS under. Normally this should not be changed, but it is + possible to register this VFS under multiple names so long as + each has its own separate directory to work from. The storage for + each is invisible to all others. The name must be a string + compatible with `sqlite3_vfs_register()` and friends and suitable + for use in URI-style database file names. + + Achtung: if a custom `name` is provided, a custom `directory` + must also be provided if any other instance is registered with + the default directory. If no directory is explicitly provided + then a directory name is synthesized from the `name` option. + + Peculiarities of this VFS: + + - Paths given to it _must_ be absolute. Relative paths will not + be properly recognized. This is arguably a bug but correcting it + requires some hoop-jumping in routines which have no business + doing tricks. + + - It is possible to install multiple instances under different + names, each sandboxed from one another inside their own private + directory. This feature exists primarily as a way for disparate + applications within a given HTTP origin to use this VFS without + introducing locking issues between them. + + + The API for the utility object passed on by this function's + Promise, in alphabetical order... + + - [async] number addCapacity(n) + + Adds `n` entries to the current pool. This change is persistent + across sessions so should not be called automatically at each app + startup (but see `reserveMinimumCapacity()`). Its returned Promise + resolves to the new capacity. Because this operation is necessarily + asynchronous, the C-level VFS API cannot call this on its own as + needed. + + - byteArray exportFile(name) + + Synchronously reads the contents of the given file into a Uint8Array + and returns it. This will throw if the given name is not currently + in active use or on I/O error. Note that the given name is _not_ + visible directly in OPFS (or, if it is, it's not from this VFS). + + - number getCapacity() + + Returns the number of files currently contained + in the SAH pool. The default capacity is only large enough for one + or two databases and their associated temp files. + + - number getFileCount() + + Returns the number of files from the pool currently allocated to + slots. This is not the same as the files being "opened". + + - array getFileNames() + + Returns an array of the names of the files currently allocated to + slots. This list is the same length as getFileCount(). + + - void importDb(name, bytes) + + Imports the contents of an SQLite database, provided as a byte + array or ArrayBuffer, under the given name, overwriting any + existing content. Throws if the pool has no available file slots, + on I/O error, or if the input does not appear to be a + database. In the latter case, only a cursory examination is made. + Note that this routine is _only_ for importing database files, + not arbitrary files, the reason being that this VFS will + automatically clean up any non-database files so importing them + is pointless. + + On a write error, the handle is removed from the pool and made + available for re-use. + + - [async] number reduceCapacity(n) + + Removes up to `n` entries from the pool, with the caveat that it can + only remove currently-unused entries. It returns a Promise which + resolves to the number of entries actually removed. + + - [async] boolean removeVfs() + + Unregisters the opfs-sahpool VFS and removes its directory from OPFS + (which means that _all client content_ is removed). After calling + this, the VFS may no longer be used and there is no way to re-add it + aside from reloading the current JavaScript context. + + Results are undefined if a database is currently in use with this + VFS. + + The returned Promise resolves to true if it performed the removal + and false if the VFS was not installed. + + If the VFS has a multi-level directory, e.g. "/foo/bar/baz", _only_ + the bottom-most directory is removed because this VFS cannot know for + certain whether the higher-level directories contain data which + should be removed. + + - [async] number reserveMinimumCapacity(min) + + If the current capacity is less than `min`, the capacity is + increased to `min`, else this returns with no side effects. The + resulting Promise resolves to the new capacity. + + - boolean unlink(filename) + + If a virtual file exists with the given name, disassociates it from + the pool and returns true, else returns false without side + effects. Results are undefined if the file is currently in active + use. + + - string vfsName + + The SQLite VFS name under which this pool's VFS is registered. + + - [async] void wipeFiles() + + Clears all client-defined state of all SAHs and makes all of them + available for re-use by the pool. Results are undefined if any such + handles are currently in use, e.g. by an sqlite3 db. + */ + sqlite3.installOpfsSAHPoolVfs = async function(options=Object.create(null)){ + const vfsName = options.name || optionDefaults.name; + if(0 && 2===++instanceCounter){ + throw new Error("Just testing rejection."); + } + if(initPromises[vfsName]){ + //console.warn("Returning same OpfsSAHPool result",options,vfsName,initPromises[vfsName]); + return initPromises[vfsName]; + } + if(!globalThis.FileSystemHandle || + !globalThis.FileSystemDirectoryHandle || + !globalThis.FileSystemFileHandle || + !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle || + !navigator?.storage?.getDirectory){ + return (initPromises[vfsName] = Promise.reject(new Error("Missing required OPFS APIs."))); + } + + /** + Maintenance reminder: the order of ASYNC ops in this function + is significant. We need to have them all chained at the very + end in order to be able to catch a race condition where + installOpfsSAHPoolVfs() is called twice in rapid succession, + e.g.: + + installOpfsSAHPoolVfs().then(console.warn.bind(console)); + installOpfsSAHPoolVfs().then(console.warn.bind(console)); + + If the timing of the async calls is not "just right" then that + second call can end up triggering the init a second time and chaos + ensues. + */ + return initPromises[vfsName] = apiVersionCheck().then(async function(){ + if(options.$testThrowInInit){ + throw options.$testThrowInInit; + } + const thePool = new OpfsSAHPool(options); + return thePool.isReady.then(async()=>{ + /** The poolUtil object will be the result of the + resolved Promise. */ + const poolUtil = new OpfsSAHPoolUtil(thePool); + if(sqlite3.oo1){ + const oo1 = sqlite3.oo1; + const theVfs = thePool.getVfs(); + const OpfsSAHPoolDb = function(...args){ + const opt = oo1.DB.dbCtorHelper.normalizeArgs(...args); + opt.vfs = theVfs.$zName; + oo1.DB.dbCtorHelper.call(this, opt); + }; + OpfsSAHPoolDb.prototype = Object.create(oo1.DB.prototype); + // yes or no? OpfsSAHPoolDb.PoolUtil = poolUtil; + poolUtil.OpfsSAHPoolDb = OpfsSAHPoolDb; + oo1.DB.dbCtorHelper.setVfsPostOpenSql( + theVfs.pointer, + function(oo1Db, sqlite3){ + sqlite3.capi.sqlite3_exec(oo1Db, [ + /* See notes in sqlite3-vfs-opfs.js */ + "pragma journal_mode=DELETE;", + "pragma cache_size=-16384;" + ], 0, 0, 0); + } + ); + }/*extend sqlite3.oo1*/ + thePool.log("VFS initialized."); + return poolUtil; + }).catch(async (e)=>{ + await thePool.removeVfs().catch(()=>{}); + return e; + }); + }).catch((err)=>{ + //error("rejecting promise:",err); + return initPromises[vfsName] = Promise.reject(err); + }); + }/*installOpfsSAHPoolVfs()*/; +}/*sqlite3ApiBootstrap.initializers*/); +//#else +/* + The OPFS SAH Pool VFS parts are elided from builds targeting + node.js. +*/ +//#endif target=node diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index 5c584702d..93482505a 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -1,3 +1,4 @@ +//#ifnot target=node /* 2022-09-18 @@ -23,7 +24,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ installOpfsVfs() returns a Promise which, on success, installs an sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs which accept a VFS. It is intended to be called via - sqlite3ApiBootstrap.initializersAsync or an equivalent mechanism. + sqlite3ApiBootstrap.initializers or an equivalent mechanism. The installed VFS uses the Origin-Private FileSystem API for all file storage. On error it is rejected with an exception @@ -101,6 +102,10 @@ const installOpfsVfs = function callee(options){ options = Object.create(null); } const urlParams = new URL(globalThis.location.href).searchParams; + if(urlParams.has('opfs-disable')){ + //sqlite3.config.warn('Explicitly not installing "opfs" VFS due to opfs-disable flag.'); + return Promise.resolve(sqlite3); + } if(undefined===options.verbose){ options.verbose = urlParams.has('opfs-verbose') ? (+urlParams.get('opfs-verbose') || 2) : 1; @@ -118,11 +123,11 @@ const installOpfsVfs = function callee(options){ options.proxyUri = options.proxyUri(); } const thePromise = new Promise(function(promiseResolve_, promiseReject_){ - const loggers = { - 0:sqlite3.config.error, - 1:sqlite3.config.warn, - 2:sqlite3.config.log - }; + const loggers = [ + sqlite3.config.error, + sqlite3.config.warn, + sqlite3.config.log + ]; const logImpl = (level,...args)=>{ if(options.verbose>level) loggers[level]("OPFS syncer:",...args); }; @@ -191,17 +196,18 @@ const installOpfsVfs = function callee(options){ s.count = s.time = 0; } }/*metrics*/; - const opfsVfs = new sqlite3_vfs(); const opfsIoMethods = new sqlite3_io_methods(); + const opfsVfs = new sqlite3_vfs() + .addOnDispose( ()=>opfsIoMethods.dispose()); let promiseWasRejected = undefined; const promiseReject = (err)=>{ promiseWasRejected = true; opfsVfs.dispose(); return promiseReject_(err); }; - const promiseResolve = (value)=>{ + const promiseResolve = ()=>{ promiseWasRejected = false; - return promiseResolve_(value); + return promiseResolve_(sqlite3); }; const W = //#if target=es6-bundler-friendly @@ -235,17 +241,17 @@ const installOpfsVfs = function callee(options){ ? new sqlite3_vfs(pDVfs) : null /* dVfs will be null when sqlite3 is built with SQLITE_OS_OTHER. */; + opfsIoMethods.$iVersion = 1; opfsVfs.$iVersion = 2/*yes, two*/; opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof; opfsVfs.$mxPathname = 1024/*sure, why not?*/; opfsVfs.$zName = wasm.allocCString("opfs"); // All C-side memory of opfsVfs is zeroed out, but just to be explicit: opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null; - opfsVfs.ondispose = [ + opfsVfs.addOnDispose( '$zName', opfsVfs.$zName, - 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null), - 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose() - ]; + 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null) + ); /** Pedantic sidebar about opfsVfs.ondispose: the entries in that array are items to clean up when opfsVfs.dispose() is called, but in this @@ -298,6 +304,7 @@ const installOpfsVfs = function callee(options){ lock contention to free up. */ state.asyncIdleWaitTime = 150; + /** Whether the async counterpart should log exceptions to the serialization channel. That produces a great deal of @@ -484,7 +491,8 @@ const installOpfsVfs = function callee(options){ This proxy de/serializes cross-thread function arguments and output-pointer values via the state.sabIO SharedArrayBuffer, using the region defined by (state.sabS11nOffset, - state.sabS11nOffset]. Only one dataset is recorded at a time. + state.sabS11nOffset + state.sabS11nSize]. Only one dataset is + recorded at a time. This is not a general-purpose format. It only supports the range of operations, and data sizes, needed by the @@ -831,22 +839,19 @@ const installOpfsVfs = function callee(options){ /* If it turns out that we need to adjust for timezone, see: https://stackoverflow.com/a/11760121/1458521 */ wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000), - 'double'); + 'double'); return 0; }, xCurrentTimeInt64: function(pVfs,pOut){ - // TODO: confirm that this calculation is correct wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(), - 'i64'); + 'i64'); return 0; }, xDelete: function(pVfs, zName, doSyncDir){ mTimeStart('xDelete'); - opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false); - /* We're ignoring errors because we cannot yet differentiate - between harmless and non-harmless failures. */ + const rc = opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false); mTimeEnd(); - return 0; + return rc; }, xFullPathname: function(pVfs,zName,nOut,pOut){ /* Until/unless we have some notion of "current dir" @@ -1088,7 +1093,7 @@ const installOpfsVfs = function callee(options){ propagate any exception on error, rather than returning false. */ opfsUtil.unlink = async function(fsEntryName, recursive = false, - throwOnError = false){ + throwOnError = false){ try { const [hDir, filenamePart] = await opfsUtil.getDirForFilename(fsEntryName, false); @@ -1163,11 +1168,41 @@ const installOpfsVfs = function callee(options){ doDir(opt.directory, 0); }; - //TODO to support fiddle and worker1 db upload: - //opfsUtil.createFile = function(absName, content=undefined){...} - //We have sqlite3.wasm.sqlite3_wasm_vfs_create_file() for this - //purpose but its interface and name are still under - //consideration. + /** + Asynchronously imports the given bytes (a byte array or + ArrayBuffer) into the given database file. + + It very specifically requires the input to be an SQLite3 + database and throws if that's not the case. It does so in + order to prevent this function from taking on a larger scope + than it is specifically intended to. i.e. we do not want it to + become a convenience for importing arbitrary files into OPFS. + + Throws on error. Resolves to the number of bytes written. + */ + opfsUtil.importDb = async function(filename, bytes){ + if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + const n = bytes.byteLength; + if(n<512 || n%512!=0){ + toss("Byte array size is invalid for an SQLite db."); + } + const header = "SQLite format 3"; + for(let i = 0; i < header.length; ++i){ + if( header.charCodeAt(i) !== bytes[i] ){ + toss("Input does not contain an SQLite database header."); + } + } + const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true); + const hFile = await hDir.getFileHandle(fnamePart, {create:true}); + const sah = await hFile.createSyncAccessHandle(); + sah.truncate(0); + const nWrote = sah.write(bytes, {at: 0}); + sah.close(); + if(nWrote != n){ + toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); + } + return nWrote; + }; if(sqlite3.oo1){ const OpfsDb = function(...args){ @@ -1177,6 +1212,7 @@ const installOpfsVfs = function callee(options){ }; OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); sqlite3.oo1.OpfsDb = OpfsDb; + OpfsDb.importDb = opfsUtil.importDb; sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( opfsVfs.pointer, function(oo1Db, sqlite3){ @@ -1185,19 +1221,23 @@ const installOpfsVfs = function callee(options){ contention. */ sqlite3.capi.sqlite3_busy_timeout(oo1Db, 10000); sqlite3.capi.sqlite3_exec(oo1Db, [ - /* Truncate journal mode is faster than delete for - this vfs, per speedtest1. That gap seems to have closed with - Chrome version 108 or 109, but "persist" is very roughly 5-6% - faster than truncate in initial tests. + /* As of July 2023, the PERSIST journal mode on OPFS is + somewhat slower than DELETE or TRUNCATE (it was faster + before Chrome version 108 or 109). TRUNCATE and DELETE + have very similar performance on OPFS. - For later analysis: Roy Hashimoto notes that TRUNCATE - and PERSIST modes may decrease OPFS concurrency because - multiple connections can open the journal file in those - modes: + Roy Hashimoto notes that TRUNCATE and PERSIST modes may + decrease OPFS concurrency because multiple connections + can open the journal file in those modes: https://github.com/rhashimoto/wa-sqlite/issues/68 + + Given that, and the fact that testing has not revealed + any appreciable difference between performance of + TRUNCATE and DELETE modes on OPFS, we currently (as of + 2023-07-13) default to DELETE mode. */ - "pragma journal_mode=persist;", + "pragma journal_mode=DELETE;", /* This vfs benefits hugely from cache on moderate/large speedtest1 --size 50 and --size 100 workloads. We @@ -1318,10 +1358,10 @@ const installOpfsVfs = function callee(options){ sqlite3.opfs = opfsUtil; opfsUtil.rootDirectory = d; log("End of OPFS sqlite3_vfs setup.", opfsVfs); - promiseResolve(sqlite3); + promiseResolve(); }).catch(promiseReject); }else{ - promiseResolve(sqlite3); + promiseResolve(); } }catch(e){ error(e); @@ -1358,7 +1398,10 @@ globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ }); }catch(e){ sqlite3.config.error("installOpfsVfs() exception:",e); - throw e; + return Promise.reject(e); } }); }/*sqlite3ApiBootstrap.initializers.push()*/); +//#else +/* The OPFS VFS parts are elided from builds targeting node.js. */ +//#endif target=node diff --git a/ext/wasm/api/sqlite3-wasi.h b/ext/wasm/api/sqlite3-wasi.h deleted file mode 100644 index 096f45dfe..000000000 --- a/ext/wasm/api/sqlite3-wasi.h +++ /dev/null @@ -1,69 +0,0 @@ -/** - Dummy function stubs to get sqlite3.c compiling with - wasi-sdk. This requires, in addition: - - -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID - - -lwasi-emulated-getpid -*/ -typedef unsigned mode_t; -int fchmod(int fd, mode_t mode); -int fchmod(int fd, mode_t mode){ - return (fd && mode) ? 0 : 0; -} -typedef unsigned uid_t; -typedef uid_t gid_t; -int fchown(int fd, uid_t owner, gid_t group); -int fchown(int fd, uid_t owner, gid_t group){ - return (fd && owner && group) ? 0 : 0; -} -uid_t geteuid(void); -uid_t geteuid(void){return 0;} -#if !defined(F_WRLCK) -enum { -F_WRLCK, -F_RDLCK, -F_GETLK, -F_SETLK, -F_UNLCK -}; -#endif - -#undef HAVE_PREAD - -#include -#define WASM__KEEP __attribute__((used)) - -#if 0 -/** - wasi-sdk cannot build sqlite3's default VFS without at least the following - functions. They are apparently syscalls which clients have to implement or - otherwise obtain. - - https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md -*/ -environ_get -environ_sizes_get -clock_time_get -fd_close -fd_fdstat_get -fd_fdstat_set_flags -fd_filestat_get -fd_filestat_set_size -fd_pread -fd_prestat_get -fd_prestat_dir_name -fd_read -fd_seek -fd_sync -fd_write -path_create_directory -path_filestat_get -path_filestat_set_times -path_open -path_readlink -path_remove_directory -path_unlink_file -poll_oneoff -proc_exit -#endif diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index dbe594dc3..fcfbc0692 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -141,9 +141,6 @@ #ifndef SQLITE_OMIT_UTF16 # define SQLITE_OMIT_UTF16 1 #endif -#ifndef SQLITE_OMIT_WAL -# define SQLITE_OMIT_WAL 1 -#endif #ifndef SQLITE_OS_KV_OPTIONAL # define SQLITE_OS_KV_OPTIONAL 1 #endif @@ -151,7 +148,7 @@ /**********************************************************************/ /* SQLITE_T... */ #ifndef SQLITE_TEMP_STORE -# define SQLITE_TEMP_STORE 3 +# define SQLITE_TEMP_STORE 2 #endif #ifndef SQLITE_THREADSAFE # define SQLITE_THREADSAFE 0 @@ -295,7 +292,7 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){ */ SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){ assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos); - assert(0==(p & 0x7)); + assert(0==((unsigned long long)p & 0x7)); if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){ PStack.pPos = p; } @@ -1353,6 +1350,13 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** +** ACHTUNG: it was discovered on 2023-08-11 that, with SQLITE_DEBUG, +** this function's out-of-scope use of the sqlite3_vfs/file/io_methods +** APIs leads to triggering of assertions in the core library. Its use +** is now deprecated and VFS-specific APIs for importing files need to +** be found to replace it. sqlite3_wasm_posix_create_file() is +** suitable for the "unix" family of VFSes. +** ** Creates a new file using the I/O API of the given VFS, containing ** the given number of bytes of the given data. If the file exists, it ** is truncated to the given length and populated with the given @@ -1398,7 +1402,14 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, int rc; sqlite3_file *pFile = 0; sqlite3_io_methods const *pIo; - const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE +#if 0 && defined(SQLITE_DEBUG) + | SQLITE_OPEN_MAIN_JOURNAL + /* ^^^^ This is for testing a horrible workaround to avoid + triggering a specific assert() in os_unix.c:unixOpen(). Please + do not enable this in real builds. */ +#endif + ; int flagsOut = 0; int fileExisted = 0; int doUnlock = 0; @@ -1464,6 +1475,34 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, return rc; } +/** +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Creates or overwrites a file using the POSIX file API, +** i.e. Emscripten's virtual filesystem. Creates or truncates +** zFilename, appends pData bytes to it, and returns 0 on success or +** SQLITE_IOERR on error. +*/ +SQLITE_WASM_EXPORT +int sqlite3_wasm_posix_create_file( const char *zFilename, + const unsigned char * pData, + int nData ){ + int rc; + FILE * pFile = 0; + int fileExisted = 0; + size_t nWrote = 1; + + if( !zFilename || nData<0 || (pData==0 && nData>0) ) return SQLITE_MISUSE; + pFile = fopen(zFilename, "w"); + if( 0==pFile ) return SQLITE_IOERR; + if( nData>0 ){ + nWrote = fwrite(pData, (size_t)nData, 1, pFile); + } + fclose(pFile); + return 1==nWrote ? 0 : SQLITE_IOERR; +} + /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index 48a74d472..bad599673 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -196,10 +196,9 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi if(1===arguments.length){ msg = arguments[0]; }else if(2===arguments.length){ - msg = { - type: arguments[0], - args: arguments[1] - }; + msg = Object.create(null); + msg.type = arguments[0]; + msg.args = arguments[1]; }else{ toss("Invalid arugments for sqlite3Worker1Promiser()-created factory."); } diff --git a/ext/wasm/common/SqliteTestUtil.js b/ext/wasm/common/SqliteTestUtil.js index 5ed423785..2c17824c5 100644 --- a/ext/wasm/common/SqliteTestUtil.js +++ b/ext/wasm/common/SqliteTestUtil.js @@ -156,7 +156,6 @@ } }; - /** This is a module object for use with the emscripten-installed sqlite3InitModule() factory function. diff --git a/ext/wasm/common/testing.css b/ext/wasm/common/testing.css index fb44f1d61..a9be76764 100644 --- a/ext/wasm/common/testing.css +++ b/ext/wasm/common/testing.css @@ -40,8 +40,41 @@ span.labeled-input { .tests-pass { background-color: green; color: white } .tests-fail { background-color: red; color: yellow } .faded { opacity: 0.5; } -.group-start { color: blue; } -.group-end { color: blue; } +.group-start { + color: blue; + background-color: skyblue; + font-weight: bold; + border-top: 1px dotted blue; + padding: 0.5em; + margin-top: 0.5em; +} +.group-end { + padding: 0.5em; + margin-bottom: 0.25em; + /*border-bottom: 1px dotted blue;*/ +} +.group-end.green { + background: lightgreen; + border-bottom: 1px dotted green; +} +.one-test-line, .skipping-group { + margin-left: 3em; +} +.skipping-test, .skipping-group { + padding: 0.25em 0.5em; + background-color: #ffff73; +} +.skipping-test { + margin-left: 6em; +} +.one-test-summary { + margin-left: 6em; +} +.full-test-summary { + padding-bottom: 0.5em; + padding-top: 0.5em; + border-top: 1px solid black; +} .input-wrapper { white-space: nowrap; display: flex; diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index 489979941..0437ef35d 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -111,7 +111,8 @@ of `target.instance` (a WebAssembly.Module instance) and it must contain the symbols exported by the WASM module associated with this code. In an Enscripten environment it must be set to - `Module['asm']`. The exports object must contain a minimum of the + `Module['asm']` (versions <=3.1.43) or `wasmExports` (versions + >=3.1.44). The exports object must contain a minimum of the following symbols: - `memory`: a WebAssembly.Memory object representing the WASM @@ -174,7 +175,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 'use strict'; if(undefined===target.bigIntEnabled){ - target.bigIntEnabled = !!self['BigInt64Array']; + target.bigIntEnabled = !!globalThis['BigInt64Array']; } /** Throws a new Error, the message of which is the concatenation of @@ -355,8 +356,8 @@ globalThis.WhWasmUtilInstaller = function(target){ break; default: if(target.bigIntEnabled){ - if(n===self['BigUint64Array']) return c.HEAP64U; - else if(n===self['BigInt64Array']) return c.HEAP64; + if(n===globalThis['BigUint64Array']) return c.HEAP64U; + else if(n===globalThis['BigInt64Array']) return c.HEAP64; break; } } @@ -612,8 +613,6 @@ globalThis.WhWasmUtilInstaller = function(target){ target.installFunction = (func, sig)=>__installFunction(func, sig, false); /** - EXPERIMENTAL! DO NOT USE IN CLIENT CODE! - Works exactly like installFunction() but requires that a scopedAllocPush() is active and uninstalls the given function when that alloc scope is popped via scopedAllocPop(). @@ -1179,7 +1178,7 @@ globalThis.WhWasmUtilInstaller = function(target){ cache.scopedAlloc.splice(n,1); for(let p; (p = state.pop()); ){ if(target.functionEntry(p)){ - //console.warn("scopedAllocPop() uninstalling transient function",p); + //console.warn("scopedAllocPop() uninstalling function",p); target.uninstallFunction(p); } else target.dealloc(p); @@ -1636,6 +1635,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 'and is not intended to be invoked from', 'client-level code. Invoked with:',opt); } + this.name = opt.name || "unnamed"; this.signature = opt.signature; if(opt.contextKey instanceof Function){ this.contextKey = opt.contextKey; @@ -1656,25 +1656,11 @@ globalThis.WhWasmUtilInstaller = function(target){ ? opt.callProxy : undefined; } - /** If true, the constructor emits a warning. The intent is that - this be set to true after bootstrapping of the higher-level - client library is complete, to warn downstream clients that - they shouldn't be relying on this implemenation detail which - does not have a stable interface. */ - static warnOnUse = false; - - /** If true, convertArg() will FuncPtrAdapter.debugOut() when it - (un)installs a function binding to/from WASM. Note that - deinstallation of bindScope=transient bindings happens - via scopedAllocPop() so will not be output. */ - static debugFuncInstall = false; - - /** Function used for debug output. */ - static debugOut = console.debug.bind(console); - - static bindScopes = [ - 'transient', 'context', 'singleton', 'permanent' - ]; + /** + Note that static class members are defined outside of the class + to work around an emcc toolchain build problem: one of the + tools in emsdk v3.1.42 does not support the static keyword. + */ /* Dummy impl. Overwritten per-instance as needed. */ contextKey(argv,argIndex){ @@ -1711,14 +1697,16 @@ globalThis.WhWasmUtilInstaller = function(target){ exactly the 2nd and 3rd arguments are. */ convertArg(v,argv,argIndex){ - //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.signature,this.transient,v); + //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v); let pair = this.singleton; if(!pair && this.isContext){ pair = this.contextMap(this.contextKey(argv,argIndex)); + //FuncPtrAdapter.debugOut(this.name, this.signature, "contextKey() =",this.contextKey(argv,argIndex), pair); } if(pair && pair[0]===v) return pair[1]; if(v instanceof Function){ /* Install a WASM binding and return its pointer. */ + //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair); if(this.callProxy) v = this.callProxy(v); const fp = __installFunction(v, this.signature, this.isTransient); if(FuncPtrAdapter.debugFuncInstall){ @@ -1732,7 +1720,18 @@ globalThis.WhWasmUtilInstaller = function(target){ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, this.contextKey(argv,argIndex), '@'+pair[1], v); } - try{target.uninstallFunction(pair[1])} + try{ + /* Because the pending native call might rely on the + pointer we're replacing, e.g. as is normally the case + with sqlite3's xDestroy() methods, we don't + immediately uninstall but instead add its pointer to + the scopedAlloc stack, which will be cleared when the + xWrap() mechanism is done calling the native + function. We're relying very much here on xWrap() + having pushed an alloc scope. + */ + cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]); + } catch(e){/*ignored*/} } pair[0] = v; @@ -1740,13 +1739,14 @@ globalThis.WhWasmUtilInstaller = function(target){ } return fp; }else if(target.isPtr(v) || null===v || undefined===v){ + //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair); if(pair && pair[1] && pair[1]!==v){ /* uninstall stashed mapping and replace stashed mapping with v. */ if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, this.contextKey(argv,argIndex), '@'+pair[1], v); } - try{target.uninstallFunction(pair[1])} + try{ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]) } catch(e){/*ignored*/} pair[0] = pair[1] = (v | 0); } @@ -1761,6 +1761,26 @@ globalThis.WhWasmUtilInstaller = function(target){ }/*convertArg()*/ }/*FuncPtrAdapter*/; + /** If true, the constructor emits a warning. The intent is that + this be set to true after bootstrapping of the higher-level + client library is complete, to warn downstream clients that + they shouldn't be relying on this implemenation detail which + does not have a stable interface. */ + xArg.FuncPtrAdapter.warnOnUse = false; + + /** If true, convertArg() will FuncPtrAdapter.debugOut() when it + (un)installs a function binding to/from WASM. Note that + deinstallation of bindScope=transient bindings happens + via scopedAllocPop() so will not be output. */ + xArg.FuncPtrAdapter.debugFuncInstall = false; + + /** Function used for debug output. */ + xArg.FuncPtrAdapter.debugOut = console.debug.bind(console); + + xArg.FuncPtrAdapter.bindScopes = [ + 'transient', 'context', 'singleton', 'permanent' + ]; + const __xArgAdapterCheck = (t)=>xArg.get(t) || toss("Argument adapter not found:",t); diff --git a/ext/wasm/demo-123.js b/ext/wasm/demo-123.js index 311afcc82..8e03ee80f 100644 --- a/ext/wasm/demo-123.js +++ b/ext/wasm/demo-123.js @@ -235,10 +235,10 @@ - get change count (total or statement-local, 32- or 64-bit) - get a DB's file name - + Misc. Stmt features: - - Various forms of bind() + - Various forms of bind() - clearBindings() - reset() - Various forms of step() diff --git a/ext/wasm/demo-worker1-promiser.js b/ext/wasm/demo-worker1-promiser.js index e8d3d4e5a..c2d24623a 100644 --- a/ext/wasm/demo-worker1-promiser.js +++ b/ext/wasm/demo-worker1-promiser.js @@ -64,15 +64,17 @@ callback = msgArgs; msgArgs = undefined; } - const p = workerPromise({type: msgType, args:msgArgs}); + const p = 1 + ? workerPromise({type: msgType, args:msgArgs}) + : workerPromise(msgType, msgArgs); return callback ? p.then(callback).finally(testCount) : p; }; + let sqConfig; const runTests = async function(){ const dbFilename = '/testing2.sqlite3'; startTime = performance.now(); - let sqConfig; await wtest('config-get', (ev)=>{ const r = ev.result; log('sqlite3.config subset:', r); @@ -102,12 +104,15 @@ sql: ["create table t(a,b)", "insert into t(a,b) values(1,2),(3,4),(5,6)" ].join(';'), - multi: true, - resultRows: [], columnNames: [] + resultRows: [], columnNames: [], + countChanges: sqConfig.bigIntEnabled ? 64 : true }, function(ev){ ev = ev.result; T.assert(0===ev.resultRows.length) - .assert(0===ev.columnNames.length); + .assert(0===ev.columnNames.length) + .assert(sqConfig.bigIntEnabled + ? (3n===ev.changeCount) + : (3===ev.changeCount)); }); await wtest('exec',{ @@ -125,12 +130,14 @@ await wtest('exec',{ sql: 'select a a, b b from t order by a', resultRows: [], columnNames: [], - rowMode: 'object' + rowMode: 'object', + countChanges: true }, function(ev){ ev = ev.result; T.assert(3===ev.resultRows.length) .assert(1===ev.resultRows[0].a) .assert(6===ev.resultRows[2].b) + .assert(0===ev.changeCount); }); await wtest( @@ -143,12 +150,13 @@ await wtest('exec',{ sql:'select 1 union all select 3', - resultRows: [], + resultRows: [] }, function(ev){ ev = ev.result; T.assert(2 === ev.resultRows.length) .assert(1 === ev.resultRows[0][0]) - .assert(3 === ev.resultRows[1][0]); + .assert(3 === ev.resultRows[1][0]) + .assert(undefined === ev.changeCount); }); const resultRowTest1 = function f(ev){ @@ -218,13 +226,12 @@ }); await wtest('exec',{ - multi: true, sql:[ 'pragma foreign_keys=0;', // ^^^ arbitrary query with no result columns 'select a, b from t order by a desc; select a from t;' - // multi-exec only honors results from the first - // statement with result columns (regardless of whether) + // exec() only honors SELECT results from the first + // statement with result columns (regardless of whether // it has any rows). ], rowMode: 1, diff --git a/ext/wasm/demo-worker1.js b/ext/wasm/demo-worker1.js index f70179c5e..60f5e8dec 100644 --- a/ext/wasm/demo-worker1.js +++ b/ext/wasm/demo-worker1.js @@ -209,8 +209,8 @@ // ^^^ arbitrary query with no result columns "select a, b from t order by a desc;", "select a from t;" - // multi-statement exec only honors results from the first - // statement with result columns (regardless of whether) + // exec() only honors SELECT results from the first + // statement with result columns (regardless of whether // it has any rows). ], rowMode: 1, diff --git a/ext/wasm/dist.make b/ext/wasm/dist.make index 3f99ad5a5..5d610e37b 100644 --- a/ext/wasm/dist.make +++ b/ext/wasm/dist.make @@ -38,7 +38,7 @@ dist-name := $(dist-name-prefix)-TEMP # date. Our general policy is that we want the smallest binaries for # dist zip files, so use the oz build unless there is a compelling # reason not to. -dist.build ?= qoz +dist.build ?= oz dist-dir.top := $(dist-name) dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout)) diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make index cbe6ab351..57141d7e2 100644 --- a/ext/wasm/fiddle.make +++ b/ext/wasm/fiddle.make @@ -18,7 +18,7 @@ endif ifeq (,$(SHELL_SRC)) $(error Could not parse SHELL_SRC from $(dir.top)/Makefile.) endif -$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl +$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c) $(MAKE) -C $(dir.top) shell.c # /shell.c ######################################################################## @@ -61,9 +61,9 @@ $(fiddle.SOAP.js): $(SOAP.js) $(eval $(call call-make-pre-post,fiddle-module,vanilla)) $(fiddle-module.js): $(MAKEFILE) $(MAKEFILE.fiddle) \ $(EXPORTED_FUNCTIONS.fiddle) \ - $(fiddle.cses) $(pre-post-fiddle-module.deps.vanilla) $(fiddle.SOAP.js) + $(fiddle.cses) $(pre-post-fiddle-module-vanilla.deps) $(fiddle.SOAP.js) $(emcc.bin) -o $@ $(fiddle.emcc-flags) \ - $(pre-post-fiddle-module.flags.vanilla) \ + $(pre-post-fiddle-module-vanilla.flags) \ $(fiddle.cses) $(maybe-wasm-strip) $(fiddle-module.wasm) gzip < $@ > $@.gz diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js index e239cbf51..67f61008f 100644 --- a/ext/wasm/fiddle/fiddle-worker.js +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -221,7 +221,7 @@ f._(); } }; - + self.onmessage = function f(ev){ ev = ev.data; if(!f.cache){ @@ -371,12 +371,14 @@ sqlite3InitModule(fiddleModule).then((_sqlite3)=>{ sqlite3 = _sqlite3; console.warn("Installing sqlite3 module globally (in Worker)", - "for use in the dev console."); - self.sqlite3 = sqlite3; + "for use in the dev console.", sqlite3); + globalThis.sqlite3 = sqlite3; const dbVfs = sqlite3.wasm.xWrap('fiddle_db_vfs', "*", ['string']); fiddleModule.fsUnlink = (fn)=>{ return sqlite3.wasm.sqlite3_wasm_vfs_unlink(dbVfs(0), fn); }; wMsg('fiddle-ready'); - })/*then()*/; + }).catch(e=>{ + console.error("Fiddle worker init failed:",e); + }); })(); diff --git a/ext/wasm/index.html b/ext/wasm/index.html index a9872a9ea..70ce0441e 100644 --- a/ext/wasm/index.html +++ b/ext/wasm/index.html @@ -93,9 +93,13 @@

  • The obligatory "misc." category... @@ -103,7 +107,8 @@
  • module-symbols gives a high-level overview of the symbols exposed by the JS module.
  • -
  • batch-runner: runs batches of SQL exported from speedtest1.
  • +
  • test-opfs-vfs (same with verbose output and sanity-checking tests) is an @@ -117,18 +122,19 @@
  • - + diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index bde7d051e..1846441e5 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -60,9 +60,9 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ memberPrefix = (config.memberPrefix || ""), memberSuffix = (config.memberSuffix || ""), bigIntEnabled = (undefined===config.bigIntEnabled - ? !!self['BigInt64Array'] : !!config.bigIntEnabled), - BigInt = self['BigInt'], - BigInt64Array = self['BigInt64Array'], + ? !!globalThis['BigInt64Array'] : !!config.bigIntEnabled), + BigInt = globalThis['BigInt'], + BigInt64Array = globalThis['BigInt64Array'], /* Undocumented (on purpose) config options: */ ptrSizeof = config.ptrSizeof || 4, ptrIR = config.ptrIR || 'i32' diff --git a/ext/wasm/module-symbols.html b/ext/wasm/module-symbols.html index 865f1b333..c5c6bc162 100644 --- a/ext/wasm/module-symbols.html +++ b/ext/wasm/module-symbols.html @@ -355,6 +355,7 @@

    Compilation Options

    sqlite3_js_db_vfs: 'wasm:/api-c-style.md#sqlite3_js_db_vfs', sqlite3_js_kvvfs_clear: 'wasm:/api-c-style.md#sqlite3_js_kvvfs', sqlite3_js_kvvfs_size: 'wasm:/api-c-style.md#sqlite3_js_kvvfs', + sqlite3_js_posix_create_file: 'wasm:/api-c-style.md#sqlite3_js_posix_create_file', sqlite3_js_rc_str: 'wasm:/api-c-style.md#sqlite3_js_rc_str', sqlite3_js_vfs_create_file: 'wasm:/api-c-style.md#sqlite3_js_vfs_create_file', sqlite3_js_vfs_list: 'wasm:/api-c-style.md#sqlite3_js_vfs_list', diff --git a/ext/wasm/scratchpad-wasmfs-main.js b/ext/wasm/scratchpad-wasmfs-main.js deleted file mode 100644 index 56f9325de..000000000 --- a/ext/wasm/scratchpad-wasmfs-main.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - 2022-05-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. - - *********************************************************************** - - A basic test script for sqlite3-api.js. This file must be run in - main JS thread and sqlite3.js must have been loaded before it. -*/ -'use strict'; -(function(){ - const toss = function(...args){throw new Error(args.join(' '))}; - const log = console.log.bind(console), - warn = console.warn.bind(console), - error = console.error.bind(console); - - const stdout = log; - const stderr = error; - - const test1 = function(db){ - db.exec("create table if not exists t(a);") - .transaction(function(db){ - db.prepare("insert into t(a) values(?)") - .bind(new Date().getTime()) - .stepFinalize(); - stdout("Number of values in table t:", - db.selectValue("select count(*) from t")); - }); - }; - - const runTests = function(sqlite3){ - const capi = sqlite3.capi, - oo = sqlite3.oo1, - wasm = sqlite3.wasm; - stdout("Loaded sqlite3:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); - const persistentDir = capi.sqlite3_wasmfs_opfs_dir(); - if(persistentDir){ - stdout("Persistent storage dir:",persistentDir); - }else{ - stderr("No persistent storage available."); - } - const startTime = performance.now(); - let db; - try { - db = new oo.DB(persistentDir+'/foo.db'); - stdout("DB filename:",db.filename); - const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', - banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; - [ - test1 - ].forEach((f)=>{ - const n = performance.now(); - stdout(banner1,"Running",f.name+"()..."); - f(db, sqlite3); - stdout(banner2,f.name+"() took ",(performance.now() - n),"ms"); - }); - }finally{ - if(db) db.close(); - } - stdout("Total test time:",(performance.now() - startTime),"ms"); - }; - - sqlite3InitModule(self.sqlite3TestModule).then(runTests); -})(); diff --git a/ext/wasm/scratchpad-wasmfs-main.html b/ext/wasm/scratchpad-wasmfs.html similarity index 54% rename from ext/wasm/scratchpad-wasmfs-main.html rename to ext/wasm/scratchpad-wasmfs.html index 91f61526c..c37febff1 100644 --- a/ext/wasm/scratchpad-wasmfs-main.html +++ b/ext/wasm/scratchpad-wasmfs.html @@ -10,20 +10,6 @@
    sqlite3 WASMFS/OPFS Main-thread Scratchpad
    - -
    -
    -
    Initializing app...
    -
    - On a slow internet connection this may take a moment. If this - message displays for "a long time", intialization may have - failed and the JavaScript console may contain clues as to why. -
    -
    -
    Downloading...
    -
    - -

    Scratchpad/test app for the WASMF/OPFS integration in the main window thread. This page requires that the sqlite3 API have been built with WASMFS support. If OPFS support is available then @@ -33,8 +19,12 @@

    All stuff on this page happens in the dev console.


    - - - + diff --git a/ext/wasm/scratchpad-wasmfs.mjs b/ext/wasm/scratchpad-wasmfs.mjs new file mode 100644 index 000000000..d6b69a1d6 --- /dev/null +++ b/ext/wasm/scratchpad-wasmfs.mjs @@ -0,0 +1,70 @@ +/* + 2022-05-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. + + *********************************************************************** + + A basic test script for sqlite3-api.js. This file must be run in + main JS thread and sqlite3.js must have been loaded before it. +*/ +import sqlite3InitModule from './jswasm/sqlite3-wasmfs.mjs'; +//console.log('sqlite3InitModule =',sqlite3InitModule); +const toss = function(...args){throw new Error(args.join(' '))}; +const log = console.log.bind(console), + warn = console.warn.bind(console), + error = console.error.bind(console); + +const stdout = log; +const stderr = error; + +const test1 = function(db){ + db.exec("create table if not exists t(a);") + .transaction(function(db){ + db.prepare("insert into t(a) values(?)") + .bind(new Date().getTime()) + .stepFinalize(); + stdout("Number of values in table t:", + db.selectValue("select count(*) from t")); + }); +}; + +const runTests = function(sqlite3){ + const capi = sqlite3.capi, + oo = sqlite3.oo1, + wasm = sqlite3.wasm; + stdout("Loaded module:",sqlite3); + stdout("Loaded sqlite3:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); + const persistentDir = capi.sqlite3_wasmfs_opfs_dir(); + if(persistentDir){ + stdout("Persistent storage dir:",persistentDir); + }else{ + stderr("No persistent storage available."); + } + const startTime = performance.now(); + let db; + try { + db = new oo.DB(persistentDir+'/foo.db'); + stdout("DB filename:",db.filename); + const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', + banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + [ + test1 + ].forEach((f)=>{ + const n = performance.now(); + stdout(banner1,"Running",f.name+"()..."); + f(db, sqlite3); + stdout(banner2,f.name+"() took ",(performance.now() - n),"ms"); + }); + }finally{ + if(db) db.close(); + } + stdout("Total test time:",(performance.now() - startTime),"ms"); +}; + +sqlite3InitModule().then(runTests); diff --git a/ext/wasm/speedtest1-wasmfs.html b/ext/wasm/speedtest1-wasmfs.html index 4c4db32bc..c018583d0 100644 --- a/ext/wasm/speedtest1-wasmfs.html +++ b/ext/wasm/speedtest1-wasmfs.html @@ -10,141 +10,46 @@
    speedtest1-wasmfs.wasm
    - - -
    -
    -
    Initializing app...
    -
    - On a slow internet connection this may take a moment. If this - message displays for "a long time", intialization may have - failed and the JavaScript console may contain clues as to why. -
    -
    -
    Downloading...
    -
    - -
    -
    This page starts running the main exe when it loads, which will - block the UI until it finishes! Adding UI controls to manually configure and start it - are TODO.
    - +
    Achtung: running it with the dev tools open may drastically slow it down. For faster results, keep the dev tools closed when running it!
    -
    Output is delayed/buffered because we cannot update the UI while the - speedtest is running. Output will appear below when ready...
    - - - + diff --git a/ext/wasm/speedtest1-wasmfs.mjs b/ext/wasm/speedtest1-wasmfs.mjs new file mode 100644 index 000000000..2d5ae322a --- /dev/null +++ b/ext/wasm/speedtest1-wasmfs.mjs @@ -0,0 +1,90 @@ +import sqlite3InitModule from './jswasm/speedtest1-wasmfs.mjs'; +const wMsg = (type,...args)=>{ + postMessage({type, args}); +}; +wMsg('log',"speedtest1-wasmfs starting..."); +/** + If this environment contains OPFS, this function initializes it and + returns the name of the dir on which OPFS is mounted, else it returns + an empty string. +*/ +const wasmfsDir = function f(wasmUtil,dirName="/opfs"){ + if(undefined !== f._) return f._; + if( !self.FileSystemHandle + || !self.FileSystemDirectoryHandle + || !self.FileSystemFileHandle){ + return f._ = ""; + } + try{ + if(0===wasmUtil.xCallWrapped( + 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], dirName + )){ + return f._ = dirName; + }else{ + return f._ = ""; + } + }catch(e){ + // sqlite3_wasm_init_wasmfs() is not available + return f._ = ""; + } +}; +wasmfsDir._ = undefined; + +const log = (...args)=>wMsg('log',...args); +const logErr = (...args)=>wMsg('logErr',...args); + +const runTests = function(sqlite3){ + console.log("Module inited.",sqlite3); + const wasm = sqlite3.wasm; + const __unlink = wasm.xWrap("sqlite3_wasm_vfs_unlink", "int", ["*","string"]); + const unlink = (fn)=>__unlink(0,fn); + const pDir = wasmfsDir(wasm); + if(pDir) log("Persistent storage:",pDir); + else{ + logErr("Expecting persistent storage in this build."); + return; + } + const scope = wasm.scopedAllocPush(); + const dbFile = pDir+"/speedtest1.db"; + const urlParams = new URL(self.location.href).searchParams; + const argv = ["speedtest1"]; + if(urlParams.has('flags')){ + argv.push(...(urlParams.get('flags').split(','))); + let i = argv.indexOf('--vfs'); + if(i>=0) argv.splice(i,2); + }else{ + argv.push( + "--singlethread", + "--nomutex", + //"--nosync", + "--nomemstat", + "--size", "10" + ); + } + + if(argv.indexOf('--memdb')>=0){ + logErr("WARNING: --memdb flag trumps db filename."); + } + argv.push("--big-transactions"/*important for tests 410 and 510!*/, + dbFile); + //log("argv =",argv); + // These log messages are not emitted to the UI until after main() returns. Fixing that + // requires moving the main() call and related cleanup into a timeout handler. + if(pDir) unlink(dbFile); + log("Starting native app:\n ",argv.join(' ')); + log("This will take a while and the browser might warn about the runaway JS.", + "Give it time..."); + setTimeout(function(){ + if(pDir) unlink(dbFile); + wasm.xCall('wasm_main', argv.length, + wasm.scopedAllocMainArgv(argv)); + wasm.scopedAllocPop(scope); + if(pDir) unlink(dbFile); + log("Done running native main()"); + }, 25); +}/*runTests()*/; + +sqlite3InitModule({ + print: log, + printErr: logErr +}).then(runTests); diff --git a/ext/wasm/speedtest1-worker.html b/ext/wasm/speedtest1-worker.html index 0d88049b9..3adf8f2ee 100644 --- a/ext/wasm/speedtest1-worker.html +++ b/ext/wasm/speedtest1-worker.html @@ -171,7 +171,8 @@ const urlParams = new URL(self.location.href).searchParams; const W = new Worker( "speedtest1-worker.js?sqlite3.dir=jswasm"+ - (urlParams.has('opfs-verbose') ? '&opfs-verbose' : '') + (urlParams.has('opfs-verbose') ? '&opfs-verbose' : '')+ + (urlParams.has('opfs-disable') ? '&opfs-disable' : '') ); const mPost = function(msgType,payload){ W.postMessage({type: msgType, data: payload}); @@ -259,7 +260,7 @@ flags["--utf16be"] = "Set text encoding to UTF-16BE"; flags["--utf16le"] = "Set text encoding to UTF-16LE"; flags["--verify"] = "Run additional verification steps."; - flags["--without"] = "rowid Use WITHOUT ROWID where appropriate"; + flags["--without-rowid"] = "Use WITHOUT ROWID where appropriate"; const preselectedFlags = [ '--big-transactions', '--singlethread' @@ -267,7 +268,7 @@ if(urlParams.has('flags')){ preselectedFlags.push(...urlParams.get('flags').split(',')); } - if('opfs'!==urlParams.get('vfs')){ + if(!urlParams.get('vfs')){ preselectedFlags.push('--memdb'); } Object.keys(flags).sort().forEach(function(f){ diff --git a/ext/wasm/speedtest1-worker.js b/ext/wasm/speedtest1-worker.js index c61cab919..dedc32029 100644 --- a/ext/wasm/speedtest1-worker.js +++ b/ext/wasm/speedtest1-worker.js @@ -5,7 +5,7 @@ if(urlParams.has('sqlite3.dir')){ speedtestJs = urlParams.get('sqlite3.dir') + '/' + speedtestJs; } - importScripts('common/whwasmutil.js', speedtestJs); + importScripts(speedtestJs); /** If this environment contains OPFS, this function initializes it and returns the name of the dir on which OPFS is mounted, else it returns @@ -48,7 +48,7 @@ const log = (...args)=>logMsg('stdout',args); const logErr = (...args)=>logMsg('stderr',args); - const runSpeedtest = function(cliFlagsArray){ + const runSpeedtest = async function(cliFlagsArray){ const scope = App.wasm.scopedAllocPush(); const dbFile = App.pDir+"/speedtest1.sqlite3"; try{ @@ -56,7 +56,28 @@ "speedtest1.wasm", ...cliFlagsArray, dbFile ]; App.logBuffer.length = 0; + const ndxSahPool = argv.indexOf('opfs-sahpool'); + const realSahName = 'opfs-sahpool-speedtest1'; + if(ndxSahPool>0){ + argv[ndxSahPool] = realSahName; + log("Updated argv for opfs-sahpool: --vfs",realSahName); + } mPost('run-start', [...argv]); + if(App.sqlite3.installOpfsSAHPoolVfs + && !App.sqlite3.$SAHPoolUtil + && ndxSahPool>0){ + log("Installing opfs-sahpool as",realSahName,"..."); + await App.sqlite3.installOpfsSAHPoolVfs({ + name: realSahName, + initialCapacity: 3, + clearOnInit: true, + verbosity: 2 + }).then(PoolUtil=>{ + log("opfs-sahpool successfully installed as",realSahName); + App.sqlite3.$SAHPoolUtil = PoolUtil; + //console.log("sqlite3.oo1.OpfsSAHPoolDb =", App.sqlite3.oo1.OpfsSAHPoolDb); + }); + } App.wasm.xCall('wasm_main', argv.length, App.wasm.scopedAllocMainArgv(argv)); }catch(e){ @@ -71,20 +92,38 @@ self.onmessage = function(msg){ msg = msg.data; switch(msg.type){ - case 'run': runSpeedtest(msg.data || []); break; + case 'run': + runSpeedtest(msg.data || []) + .catch(e=>mPost('error',e)); + break; default: logErr("Unhandled worker message type:",msg.type); break; } }; + const sahpSanityChecks = function(sqlite3){ + log("Attempting OpfsSAHPoolDb sanity checks..."); + const db = new sqlite3.oo1.OpfsSAHPoolDb('opfs-sahpoool.db'); + const fn = db.filename; + db.exec([ + 'create table t(a);', + 'insert into t(a) values(1),(2),(3);' + ]); + db.close(); + sqlite3.wasm.sqlite3_wasm_vfs_unlink(sqlite3_vfs_find("opfs-sahpool"), fn); + log("SAH sanity checks done."); + }; + const EmscriptenModule = { print: log, printErr: logErr, setStatus: (text)=>mPost('load-status',text) }; - self.sqlite3InitModule(EmscriptenModule).then((sqlite3)=>{ - const S = sqlite3; + log("Initializing speedtest1 module..."); + self.sqlite3InitModule(EmscriptenModule).then(async (sqlite3)=>{ + const S = globalThis.S = App.sqlite3 = sqlite3; + log("Loaded speedtest1 module. Setting up..."); App.vfsUnlink = function(pDb, fname){ const pVfs = S.wasm.sqlite3_wasm_db_vfs(pDb, 0); if(pVfs) S.wasm.sqlite3_wasm_vfs_unlink(pVfs, fname||0); @@ -95,5 +134,7 @@ //else log("Using transient storage."); mPost('ready',true); log("Registered VFSes:", ...S.capi.sqlite3_js_vfs_list()); + }).catch(e=>{ + logErr(e); }); })(); diff --git a/ext/wasm/tester1-worker.html b/ext/wasm/tester1-worker.html index 03e1f02b0..e768c3d6c 100644 --- a/ext/wasm/tester1-worker.html +++ b/ext/wasm/tester1-worker.html @@ -45,7 +45,7 @@

    sqlite3 tester #1: Worker thread

    if(urlParams.has('esm')){ logHtml('warning',"Attempting to run an ES6 Worker Module, "+ "which is not supported by all browsers! "+ - "e.g. Firefox (as of 2022-12) cannot do this."); + "e.g. Firefox (as of 2023-05) cannot do this."); workerArgs.push("tester1.mjs",{type:"module"}); document.querySelectorAll('title,#color-target').forEach((e)=>{ e.innerText = "sqlite3 tester #1: ES6 Worker Module"; diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index 02eeba7a4..bd945dcab 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -45,7 +45,7 @@ */ //#if target=es6-module import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs'; -self.sqlite3InitModule = sqlite3InitModule; +globalThis.sqlite3InitModule = sqlite3InitModule; //#else 'use strict'; //#endif @@ -57,7 +57,7 @@ self.sqlite3InitModule = sqlite3InitModule; */ let logClass; /* Predicate for tests/groups. */ - const isUIThread = ()=>(self.window===self && self.document); + const isUIThread = ()=>(globalThis.window===self && globalThis.document); /* Predicate for tests/groups. */ const isWorker = ()=>!isUIThread(); /* Predicate for tests/groups. */ @@ -65,6 +65,14 @@ self.sqlite3InitModule = sqlite3InitModule; const haveWasmCTests = ()=>{ return !!wasm.exports.sqlite3_wasm_test_intptr; }; + const hasOpfs = ()=>{ + return globalThis.FileSystemHandle + && globalThis.FileSystemDirectoryHandle + && globalThis.FileSystemFileHandle + && globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle + && navigator?.storage?.getDirectory; + }; + { const mapToString = (v)=>{ switch(typeof v){ @@ -98,6 +106,7 @@ self.sqlite3InitModule = sqlite3InitModule; logTarget.append(ln); }; const cbReverse = document.querySelector('#cb-log-reverse'); + //cbReverse.setAttribute('checked','checked'); const cbReverseKey = 'tester1:cb-log-reverse'; const cbReverseIt = ()=>{ logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); @@ -158,8 +167,6 @@ self.sqlite3InitModule = sqlite3InitModule; /** Running total of the number of tests run via this API. */ counter: 0, - /* Separator line for log messages. */ - separator: '------------------------------------------------------------', /** If expr is a function, it is called and its result is returned, coerced to a bool, else expr, coerced to @@ -247,13 +254,11 @@ self.sqlite3InitModule = sqlite3InitModule; return this; }, run: async function(sqlite3){ - log(TestUtil.separator); logClass('group-start',"Group #"+this.number+':',this.name); - const indent = ' '; if(this.predicate){ const p = this.predicate(sqlite3); if(!p || 'string'===typeof p){ - logClass('warning',indent, + logClass(['warning','skipping-group'], "SKIPPING group:", p ? p : "predicate says to" ); return; } @@ -265,26 +270,34 @@ self.sqlite3InitModule = sqlite3InitModule; for(const t of this.tests){ ++i; const n = this.number+"."+i; - log(indent, n+":", t.name); + logClass('one-test-line', n+":", t.name); if(t.predicate){ const p = t.predicate(sqlite3); if(!p || 'string'===typeof p){ - logClass('warning',indent, + logClass(['warning','skipping-test'], "SKIPPING:", p ? p : "predicate says to" ); skipped.push( n+': '+t.name ); continue; } } const tc = TestUtil.counter, now = performance.now(); - await t.test.call(groupState, sqlite3); + let rc = t.test.call(groupState, sqlite3); + /*if(rc instanceof Promise){ + rc = rc.catch((e)=>{ + error("Test failure:",e); + throw e; + }); + }*/ + await rc; const then = performance.now(); runtime += then - now; - logClass('faded',indent, indent, + logClass(['faded','one-test-summary'], TestUtil.counter - tc, 'assertion(s) in', roundMs(then-now),'ms'); } - logClass('green', - "Group #"+this.number+":",(TestUtil.counter - assertCount), + logClass(['green','group-end'], + "#"+this.number+":", + (TestUtil.counter - assertCount), "assertion(s) in",roundMs(runtime),"ms"); if(0 && skipped.length){ logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped); @@ -320,8 +333,7 @@ self.sqlite3InitModule = sqlite3InitModule; await g.run(sqlite3); runtime += performance.now() - now; } - log(TestUtil.separator); - logClass(['strong','green'], + logClass(['strong','green','full-test-summary'], "Done running tests.",TestUtil.counter,"assertions in", roundMs(runtime),'ms'); pok(); @@ -338,6 +350,11 @@ self.sqlite3InitModule = sqlite3InitModule; T.g = T.addGroup; T.t = T.addTest; let capi, wasm/*assigned after module init*/; + const sahPoolConfig = { + name: 'opfs-sahpool-tester1', + clearOnInit: true, + initialCapacity: 6 + }; //////////////////////////////////////////////////////////////////////// // End of infrastructure setup. Now define the tests... //////////////////////////////////////////////////////////////////////// @@ -1166,7 +1183,8 @@ self.sqlite3InitModule = sqlite3InitModule; try{db.checkRc(rc)} catch(e){ex = e} T.assert(ex instanceof sqlite3.SQLite3Error) - .assert(0===ex.message.indexOf("sqlite3 result code")) + .assert(capi.SQLITE_MISUSE===ex.resultCode) + .assert(0===ex.message.indexOf("SQLITE_MISUSE: sqlite3 result code")) .assert(ex.message.indexOf("Invalid SQL")>0); T.assert(db === db.checkRc(0)) .assert(db === sqlite3.oo1.DB.checkRc(db,0)) @@ -1219,6 +1237,7 @@ self.sqlite3InitModule = sqlite3InitModule; ); //debug("statement =",st); this.progressHandlerCount = 0; + let rc; try { T.assert(wasm.isPtr(st.pointer)) .mustThrowMatching(()=>st.pointer=1, /read-only/) @@ -1229,6 +1248,8 @@ self.sqlite3InitModule = sqlite3InitModule; ) === 0) .assert(!st._mayGet) .assert('a' === st.getColumnName(0)) + .mustThrowMatching(()=>st.columnCount=2, + /columnCount property is read-only/) .assert(1===st.columnCount) .assert(0===st.parameterCount) .mustThrow(()=>st.bind(1,null)) @@ -1264,10 +1285,11 @@ self.sqlite3InitModule = sqlite3InitModule; assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); }finally{ - st.finalize(); + rc = st.finalize(); } T.assert(!st.pointer) - .assert(0===this.db.openStatementCount()); + .assert(0===this.db.openStatementCount()) + .assert(0===rc); T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){ return (err instanceof sqlite3.SQLite3Error) @@ -1282,7 +1304,6 @@ self.sqlite3InitModule = sqlite3InitModule; if(1){ const vfsList = capi.sqlite3_js_vfs_list(); T.assert(vfsList.length>1); - //log("vfsList =",vfsList); wasm.scopedAllocCall(()=>{ const vfsArg = (v)=>wasm.xWrap.testConvertArg('sqlite3_vfs*',v); for(const v of vfsList){ @@ -1332,8 +1353,8 @@ self.sqlite3InitModule = sqlite3InitModule; sql:['CREATE TABLE t(a,b);', // ^^^ using TEMP TABLE breaks the db export test "INSERT INTO t(a,b) VALUES(1,2),(3,4),", - "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for - off-by-one bug in string-to-WASM conversion*/], + "(?,?)"/*intentionally missing semicolon to test for + off-by-one bug in string-to-WASM conversion*/], saveSql: list, bind: [5,6] }); @@ -1341,12 +1362,20 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert(rc === db) .assert(2 === list.length) .assert('string'===typeof list[1]) - .assert(4===db.changes()) + .assert(3===db.changes()) .assert(this.progressHandlerCount > 0, "Expecting progress callback.") if(wasm.bigIntEnabled){ - T.assert(4n===db.changes(false,true)); + T.assert(3n===db.changes(false,true)); } + rc = db.exec({ + sql: "INSERT INTO t(a,b) values('blob',X'6869') RETURNING 13", + rowMode: 0 + }); + T.assert(Array.isArray(rc)) + .assert(1===rc.length) + .assert(13 === rc[0]) + .assert(1===db.changes()); let vals = db.selectValues('select a from t order by a limit 2'); T.assert( 2 === vals.length ) @@ -1373,10 +1402,10 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert( 3 === this._myState /* Recall that "this" is the options object. */ + ).assert( + this.columnNames===colNames ).assert( this.columnNames[0]==='a' && this.columnNames[1]==='b' - /* options.columnNames is filled out before the first - Stmt.step(). */ ).assert( (row.a%2 && row.a<6) || 'blob'===row.a ); @@ -1386,6 +1415,14 @@ self.sqlite3InitModule = sqlite3InitModule; .assert('a' === colNames[0]) .assert(4 === counter) .assert(4 === list.length); + colNames = []; + db.exec({ + /* Ensure that columnNames is populated for empty result sets. */ + sql: "SELECT a a, b B FROM t WHERE 0", + columnNames: colNames + }); + T.assert(2===colNames.length) + .assert('a'===colNames[0] && 'B'===colNames[1]); list.length = 0; db.exec("SELECT a a, b b FROM t",{ rowMode: 'array', @@ -1438,11 +1475,14 @@ self.sqlite3InitModule = sqlite3InitModule; let st = db.prepare("update t set b=:b where a='blob'"); try { + T.assert(0===st.columnCount); const ndx = st.getParamIndex(':b'); T.assert(1===ndx); - st.bindAsBlob(ndx, "ima blob").reset(true); + st.bindAsBlob(ndx, "ima blob") + /*step() skipped intentionally*/.reset(true); } finally { - st.finalize(); + T.assert(0===st.finalize()) + .assert(undefined===st.finalize()); } try { @@ -1604,8 +1644,20 @@ self.sqlite3InitModule = sqlite3InitModule; .assert(3===rc[1].a) .assert(4===rc[1].b); }) - //////////////////////////////////////////////////////////////////////// + .t('selectArray/Object/Values() via INSERT/UPDATE...RETURNING', function(sqlite3){ + let rc = this.db.selectObject("INSERT INTO t(a,b) VALUES(83,84) RETURNING a as AA"); + T.assert(83===rc.AA); + rc = this.db.selectArray("UPDATE T set a=85 WHERE a=83 RETURNING b as BB"); + T.assert(Array.isArray(rc)).assert(84===rc[0]); + //log("select * from t:",this.db.selectObjects("select * from t order by a")); + rc = this.db.selectValues("UPDATE T set a=a*1 RETURNING a"); + T.assert(Array.isArray(rc)) + .assert(5 === rc.length) + .assert('number'===typeof rc[0]) + .assert(rc[0]|0 === rc[0] /* is small integer */); + }) + //////////////////////////////////////////////////////////////////////// .t({ name: 'sqlite3_js_db_export()', predicate: ()=>true, @@ -1619,13 +1671,12 @@ self.sqlite3InitModule = sqlite3InitModule; } }/*sqlite3_js_db_export()*/) .t({ - name: 'sqlite3_js_vfs_create_file() with db in default VFS', + name: 'sqlite3_js_posix_create_file()', predicate: ()=>true, test: function(sqlite3){ const db = this.db; - const pVfs = capi.sqlite3_js_db_vfs(db); - const filename = "sqlite3_js_vfs_create_file().db"; - capi.sqlite3_js_vfs_create_file(pVfs, filename, this.dbExport); + const filename = "sqlite3_js_posix_create_file.db"; + capi.sqlite3_js_posix_create_file(filename, this.dbExport); delete this.dbExport; const db2 = new sqlite3.oo1.DB(filename,'r'); try { @@ -1634,7 +1685,7 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert(n>0 && db2.selectValue(sql) === n); }finally{ db2.close(); - wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); + wasm.sqlite3_wasm_vfs_unlink(0, filename); } } }/*sqlite3_js_vfs_create_file()*/) @@ -2578,128 +2629,6 @@ self.sqlite3InitModule = sqlite3InitModule; }/*kvvfs sqlite3_js_vfs_create_file()*/) ;/* end kvvfs tests */ - //////////////////////////////////////////////////////////////////////// - T.g('OPFS: Origin-Private File System', - (sqlite3)=>(sqlite3.opfs - ? true : "requires Worker thread in a compatible browser")) - .t({ - name: 'OPFS db sanity checks', - test: async function(sqlite3){ - const filename = this.opfsDbFile = 'sqlite3-tester1.db'; - const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs'); - T.assert(pVfs); - const unlink = this.opfsUnlink = - (fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)}; - unlink(); - let db = new sqlite3.oo1.OpfsDb(filename); - try { - db.exec([ - 'create table p(a);', - 'insert into p(a) values(1),(2),(3)' - ]); - T.assert(3 === db.selectValue('select count(*) from p')); - db.close(); - db = new sqlite3.oo1.OpfsDb(filename); - db.exec('insert into p(a) values(4),(5),(6)'); - T.assert(6 === db.selectValue('select count(*) from p')); - this.opfsDbExport = capi.sqlite3_js_db_export(db); - T.assert(this.opfsDbExport instanceof Uint8Array) - .assert(this.opfsDbExport.byteLength>0 - && 0===this.opfsDbExport.byteLength % 512); - }finally{ - db.close(); - unlink(); - } - } - }/*OPFS db sanity checks*/) - .t({ - name: 'OPFS export/import', - test: async function(sqlite3){ - let db; - try { - const exp = this.opfsDbExport; - delete this.opfsDbExport; - capi.sqlite3_js_vfs_create_file("opfs", this.opfsDbFile, exp); - const db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); - T.assert(6 === db.selectValue('select count(*) from p')); - }finally{ - if(db) db.close(); - } - } - }/*OPFS export/import*/) - .t({ - name: 'OPFS utility APIs and sqlite3_js_vfs_create_file()', - test: async function(sqlite3){ - const filename = this.opfsDbFile; - const pVfs = this.opfsVfs; - const unlink = this.opfsUnlink; - T.assert(filename && pVfs && !!unlink); - delete this.opfsDbFile; - delete this.opfsVfs; - delete this.opfsUnlink; - unlink(); - // Sanity-test sqlite3_js_vfs_create_file()... - /************************************************************** - ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended - for client-side use. It is only for this project's own - internal use. Its APIs are subject to change or removal at - any time. - ***************************************************************/ - const opfs = sqlite3.opfs; - const fSize = 1379; - let sh; - try{ - T.assert(!(await opfs.entryExists(filename))); - capi.sqlite3_js_vfs_create_file( - pVfs, filename, null, fSize - ); - T.assert(await opfs.entryExists(filename)); - let fh = await opfs.rootDirectory.getFileHandle(filename); - sh = await fh.createSyncAccessHandle(); - T.assert(fSize === await sh.getSize()); - await sh.close(); - sh = undefined; - unlink(); - T.assert(!(await opfs.entryExists(filename))); - - const ba = new Uint8Array([1,2,3,4,5]); - capi.sqlite3_js_vfs_create_file( - "opfs", filename, ba - ); - T.assert(await opfs.entryExists(filename)); - fh = await opfs.rootDirectory.getFileHandle(filename); - sh = await fh.createSyncAccessHandle(); - T.assert(ba.byteLength === await sh.getSize()); - await sh.close(); - sh = undefined; - unlink(); - - T.mustThrowMatching(()=>{ - capi.sqlite3_js_vfs_create_file( - "no-such-vfs", filename, ba - ); - }, "SQLITE_NOTFOUND: Unknown sqlite3_vfs name: no-such-vfs"); - }finally{ - if(sh) await sh.close(); - unlink(); - } - - // Some sanity checks of the opfs utility functions... - const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); - const aDir = testDir+'/test/dir'; - T.assert(await opfs.mkdir(aDir), "mkdir failed") - .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists") - .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)") - .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed") - .assert(!(await opfs.unlink(testDir+'/test/dir')), - "delete 2b should have failed (dir already deleted)") - .assert((await opfs.unlink(testDir, true)), "delete 3 failed") - .assert(!(await opfs.entryExists(testDir)), - "entryExists(",testDir,") should have failed"); - } - }/*OPFS util sanity checks*/) - ;/* end OPFS tests */ - //////////////////////////////////////////////////////////////////////// T.g('Hook APIs') .t({ @@ -2905,8 +2834,7 @@ self.sqlite3InitModule = sqlite3InitModule; .assert( capi.sqlite3session_enable(pSession, -1) > 0 ) .assert(undefined === db1.selectValue('select a from t where rowid=2')); }else{ - warn("sqlite3session_enable() tests disabled due to unexpected results.", - "(Possibly a tester misunderstanding, as opposed to a bug.)"); + warn("sqlite3session_enable() tests are currently disabled."); } let db1Count = db1.selectValue("select count(*) from t"); T.assert( db1Count === (testSessionEnable ? 2 : 3) ); @@ -2971,17 +2899,265 @@ self.sqlite3InitModule = sqlite3InitModule; })/*session API sanity tests*/ ;/*end of session API group*/; + //////////////////////////////////////////////////////////////////////// + T.g('OPFS: Origin-Private File System', + (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs") + || 'requires "opfs" VFS')) + .t({ + name: 'OPFS db sanity checks', + test: async function(sqlite3){ + const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db'; + const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs'); + T.assert(pVfs); + const unlink = this.opfsUnlink = + (fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)}; + unlink(); + let db = new sqlite3.oo1.OpfsDb(filename); + try { + db.exec([ + 'create table p(a);', + 'insert into p(a) values(1),(2),(3)' + ]); + T.assert(3 === db.selectValue('select count(*) from p')); + db.close(); + db = new sqlite3.oo1.OpfsDb(filename); + db.exec('insert into p(a) values(4),(5),(6)'); + T.assert(6 === db.selectValue('select count(*) from p')); + this.opfsDbExport = capi.sqlite3_js_db_export(db); + T.assert(this.opfsDbExport instanceof Uint8Array) + .assert(this.opfsDbExport.byteLength>0 + && 0===this.opfsDbExport.byteLength % 512); + }finally{ + db.close(); + unlink(); + } + } + }/*OPFS db sanity checks*/) + .t({ + name: 'OPFS import', + test: async function(sqlite3){ + let db; + try { + const exp = this.opfsDbExport; + delete this.opfsDbExport; + this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(this.opfsDbFile, exp); + db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); + T.assert(6 === db.selectValue('select count(*) from p')). + assert( this.opfsImportSize == exp.byteLength ); + }finally{ + if(db) db.close(); + } + } + }/*OPFS export/import*/) + .t({ + name: '(Internal-use) OPFS utility APIs', + test: async function(sqlite3){ + const filename = this.opfsDbFile; + const pVfs = this.opfsVfs; + const unlink = this.opfsUnlink; + T.assert(filename && pVfs && !!unlink); + delete this.opfsDbFile; + delete this.opfsVfs; + delete this.opfsUnlink; + /************************************************************** + ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended + for client-side use. It is only for this project's own + internal use. Its APIs are subject to change or removal at + any time. + ***************************************************************/ + const opfs = sqlite3.opfs; + const fSize = this.opfsImportSize; + delete this.opfsImportSize; + let sh; + try{ + T.assert(await opfs.entryExists(filename)); + const [dirHandle, filenamePart] = await opfs.getDirForFilename(filename, false); + const fh = await dirHandle.getFileHandle(filenamePart); + sh = await fh.createSyncAccessHandle(); + T.assert(fSize === await sh.getSize()); + await sh.close(); + sh = undefined; + unlink(); + T.assert(!(await opfs.entryExists(filename))); + }finally{ + if(sh) await sh.close(); + unlink(); + } + + // Some sanity checks of the opfs utility functions... + const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); + const aDir = testDir+'/test/dir'; + T.assert(await opfs.mkdir(aDir), "mkdir failed") + .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists") + .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)") + .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed") + .assert(!(await opfs.unlink(testDir+'/test/dir')), + "delete 2b should have failed (dir already deleted)") + .assert((await opfs.unlink(testDir, true)), "delete 3 failed") + .assert(!(await opfs.entryExists(testDir)), + "entryExists(",testDir,") should have failed"); + } + }/*OPFS util sanity checks*/) + ;/* end OPFS tests */ + + //////////////////////////////////////////////////////////////////////// + T.g('OPFS SyncAccessHandle Pool VFS', + (sqlite3)=>(hasOpfs() || "requires OPFS APIs")) + .t({ + name: 'SAH sanity checks', + test: async function(sqlite3){ + T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name)) + .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0) + const inst = sqlite3.installOpfsSAHPoolVfs, + catcher = (e)=>{ + error("Cannot load SAH pool VFS.", + "This might not be a problem,", + "depending on the environment."); + return false; + }; + let u1, u2; + // Ensure that two immediately-consecutive installations + // resolve to the same Promise instead of triggering + // a locking error. + const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher), + P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher); + await Promise.all([P1, P2]); + if(!(await P1)) return; + T.assert(u1 === u2) + .assert(sahPoolConfig.name === u1.vfsName) + .assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name)) + .assert(u1.getCapacity() >= sahPoolConfig.initialCapacity + /* If a test fails before we get to nuke the VFS, we + can have more than the initial capacity on the next + run. */) + .assert(u1.getCapacity() + 2 === (await u2.addCapacity(2))) + .assert(2 === (await u2.reduceCapacity(2))) + .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0); + + T.assert(0 === u1.getFileCount()); + const dbName = '/foo.db'; + let db = new u1.OpfsSAHPoolDb(dbName); + T.assert(db instanceof sqlite3.oo1.DB) + .assert(1 === u1.getFileCount()); + db.exec([ + 'create table t(a);', + 'insert into t(a) values(1),(2),(3)' + ]); + T.assert(1 === u1.getFileCount()); + T.assert(3 === db.selectValue('select count(*) from t')); + db.close(); + T.assert(1 === u1.getFileCount()); + db = new u2.OpfsSAHPoolDb(dbName); + T.assert(1 === u1.getFileCount()); + db.close(); + const fileNames = u1.getFileNames(); + T.assert(1 === fileNames.length) + .assert(dbName === fileNames[0]) + .assert(1 === u1.getFileCount()) + + if(1){ // test exportFile() and importDb() + const dbytes = u1.exportFile(dbName); + T.assert(dbytes.length >= 4096); + const dbName2 = '/exported.db'; + u1.importDb(dbName2, dbytes); + T.assert( 2 == u1.getFileCount() ); + let db2 = new u1.OpfsSAHPoolDb(dbName2); + T.assert(db2 instanceof sqlite3.oo1.DB) + .assert(3 === db2.selectValue('select count(*) from t')); + db2.close(); + T.assert(true === u1.unlink(dbName2)) + .assert(false === u1.unlink(dbName2)) + .assert(1 === u1.getFileCount()) + .assert(1 === u1.getFileNames().length); + } + + T.assert(true === u1.unlink(dbName)) + .assert(false === u1.unlink(dbName)) + .assert(0 === u1.getFileCount()) + .assert(0 === u1.getFileNames().length); + + // Demonstrate that two SAH pools can coexist so long as + // they have different names. + const conf2 = JSON.parse(JSON.stringify(sahPoolConfig)); + conf2.name += '-test2'; + const POther = await inst(conf2); + //log("Installed second SAH instance as",conf2.name); + T.assert(0 === POther.getFileCount()) + .assert(true === await POther.removeVfs()); + + if(0){ + /* Enable this block to inspect vfs's contents via the dev + console or OPFS Explorer browser extension. The + following bits will remove them. */ + return; + } + T.assert(true === await u2.removeVfs()) + .assert(false === await u1.removeVfs()) + .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name)); + + let cErr, u3; + conf2.$testThrowInInit = new Error("Testing throwing during init."); + conf2.name = sahPoolConfig.name+'-err'; + const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e); + T.assert(P3 === conf2.$testThrowInInit) + .assert(cErr === P3) + .assert(undefined === u3) + .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name)); + } + }/*OPFS SAH Pool sanity checks*/) + + //////////////////////////////////////////////////////////////////////// + T.g('Bug Reports') + .t({ + name: 'Delete via bound parameter in subquery', + test: function(sqlite3){ + // Testing https://sqlite.org/forum/forumpost/40ce55bdf5 + // with the exception that that post uses "external content" + // for the FTS index. + const db = new sqlite3.oo1.DB();//(':memory:','wt'); + db.exec([ + "create virtual table f using fts5 (path);", + "insert into f(path) values('abc'),('def'),('ghi');" + ]); + const fetchEm = ()=> db.exec({ + sql: "SELECT * FROM f order by path", + rowMode: 'array' + }); + const dump = function(lbl){ + let rc = fetchEm(); + log((lbl ? (lbl+' results') : ''),rc); + }; + //dump('Full fts table'); + let rc = fetchEm(); + T.assert(3===rc.length); + db.exec(` + delete from f where rowid in ( + select rowid from f where path = :path + )`, + {bind: {":path": "def"}} + ); + //dump('After deleting one entry via subquery'); + rc = fetchEm(); + T.assert(2===rc.length) + .assert('abcghi'===rc.join('')); + //log('rc =',rc); + db.close(); + } + }) + ;/*end of Bug Reports group*/; + //////////////////////////////////////////////////////////////////////// log("Loading and initializing sqlite3 WASM module..."); if(0){ - self.sqlite3ApiConfig = { + globalThis.sqlite3ApiConfig = { debug: ()=>{}, log: ()=>{}, warn: ()=>{}, error: ()=>{} } } - if(!self.sqlite3InitModule && !isUIThread()){ +//#ifnot target=es6-module + if(!globalThis.sqlite3InitModule && !isUIThread()){ /* Vanilla worker, as opposed to an ES6 module worker */ /* If sqlite3.js is in a directory other than this script, in order @@ -2994,27 +3170,32 @@ self.sqlite3InitModule = sqlite3InitModule; that's not needed. URL arguments passed as part of the filename via importScripts() - are simply lost, and such scripts see the self.location of + are simply lost, and such scripts see the globalThis.location of _this_ script. */ let sqlite3Js = 'sqlite3.js'; - const urlParams = new URL(self.location.href).searchParams; + const urlParams = new URL(globalThis.location.href).searchParams; if(urlParams.has('sqlite3.dir')){ sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; } importScripts(sqlite3Js); } - self.sqlite3InitModule.__isUnderTest = +//#endif + globalThis.sqlite3InitModule.__isUnderTest = true /* disables certain API-internal cleanup so that we can test internal APIs from here */; - self.sqlite3InitModule({ + globalThis.sqlite3InitModule({ print: log, printErr: error - }).then(function(sqlite3){ - //console.log('sqlite3 =',sqlite3); + }).then(async function(sqlite3){ log("Done initializing WASM/JS bits. Running tests..."); sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes."); - self.S = sqlite3; + globalThis.S = sqlite3; + /*await sqlite3.installOpfsSAHPoolVfs(sahPoolConfig) + .then((u)=>log("Loaded",u.vfsName,"VFS")) + .catch(e=>{ + log("Cannot install OpfsSAHPool.",e); + });*/ capi = sqlite3.capi; wasm = sqlite3.wasm; log("sqlite3 version:",capi.sqlite3_libversion(), @@ -3029,6 +3210,7 @@ self.sqlite3InitModule = sqlite3InitModule; }else{ logClass('warning',"sqlite3_wasm_test_...() APIs unavailable."); } + log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', ')); TestUtil.runTests(sqlite3); }); })(self); diff --git a/ext/wasm/wasmfs.make b/ext/wasm/wasmfs.make index 020014f3e..71c0fe75a 100644 --- a/ext/wasm/wasmfs.make +++ b/ext/wasm/wasmfs.make @@ -6,14 +6,10 @@ # GNUMakefile. ######################################################################## MAKEFILE.wasmfs := $(lastword $(MAKEFILE_LIST)) +$(warning The WASMFS build is currently incomplete.) -# Maintenance reminder: these particular files cannot be built into a -# subdirectory because loading of the auxiliary -# sqlite3-wasmfs.worker.js file it creates fails if sqlite3-wasmfs.js -# is loaded from any directory other than the one in which the -# containing HTML lives. Similarly, they cannot be loaded from a -# Worker to an Emscripten quirk regarding loading nested Workers. -dir.wasmfs := $(dir.wasm) +#dir.wasmfs := $(dir.wasm) +dir.wasmfs := $(dir.dout) sqlite3-wasmfs.js := $(dir.wasmfs)/sqlite3-wasmfs.js sqlite3-wasmfs.mjs := $(dir.wasmfs)/sqlite3-wasmfs.mjs sqlite3-wasmfs.wasm := $(dir.wasmfs)/sqlite3-wasmfs.wasm @@ -28,105 +24,92 @@ CLEAN_FILES += $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.wasm) \ cflags.sqlite3-wasmfs := cflags.sqlite3-wasmfs += -std=c99 -fPIC cflags.sqlite3-wasmfs += -pthread -cflags.sqlite3-wasmfs += $(cflags.speedtest1) -cflags.sqlite3-wasmfs += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS +cflags.sqlite3-wasmfs += -DSQLITE_ENABLE_WASMFS ######################################################################## # emcc flags specific to building the final .js/.wasm file... -emcc.flags.sqlite3-wasmfs := -fPIC -emcc.flags.sqlite3-wasmfs += --no-entry -emcc.flags.sqlite3-wasmfs += --minify 0 -emcc.flags.sqlite3-wasmfs += -sMODULARIZE -emcc.flags.sqlite3-wasmfs += -sEXPORT_NAME=$(sqlite3.js.init-func) -emcc.flags.sqlite3-wasmfs += -sSTRICT_JS -emcc.flags.sqlite3-wasmfs += -sDYNAMIC_EXECUTION=0 -emcc.flags.sqlite3-wasmfs += -sNO_POLYFILL -emcc.flags.sqlite3-wasmfs += -sWASM_BIGINT=$(emcc.WASM_BIGINT) -emcc.flags.sqlite3-wasmfs += -sEXPORTED_FUNCTIONS=@$(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) -emcc.flags.sqlite3-wasmfs += -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory,allocateUTF8OnStack +emcc.flags.sqlite3-wasmfs := +emcc.flags.sqlite3-wasmfs += \ + -sEXPORTED_RUNTIME_METHODS=wasmMemory,allocateUTF8OnStack,stringToUTF8OnStack # wasmMemory ==> for -sIMPORTED_MEMORY - # allocateUTF8OnStack ==> wasmfs internals + # *OnStack ==> wasmfs internals (leaky abstraction) emcc.flags.sqlite3-wasmfs += -sUSE_CLOSURE_COMPILER=0 emcc.flags.sqlite3-wasmfs += -Wno-limited-postlink-optimizations # ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag. -emcc.flags.sqlite3-wasmfs += -sALLOW_TABLE_GROWTH -emcc.flags.sqlite3-wasmfs += -sSTACK_SIZE=512KB -emcc.flags.sqlite3-wasmfs += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. emcc.flags.sqlite3-wasmfs += -sMEMORY64=0 -emcc.flags.sqlite3-wasmfs += -sIMPORTED_MEMORY emcc.flags.sqlite3-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) # ^^^^ 64MB is not enough for WASMFS/OPFS test runs using batch-runner.js sqlite3-wasmfs.fsflags := -pthread -sWASMFS \ - -sPTHREAD_POOL_SIZE=2 -sENVIRONMENT=web,worker \ - -sERROR_ON_UNDEFINED_SYMBOLS=0 -sLLD_REPORT_UNDEFINED + -sPTHREAD_POOL_SIZE=1 \ + -sERROR_ON_UNDEFINED_SYMBOLS=0 -sLLD_REPORT_UNDEFINED # ^^^^^ why undefined symbols are necessary for the wasmfs build is anyone's guess. emcc.flags.sqlite3-wasmfs += $(sqlite3-wasmfs.fsflags) -#emcc.flags.sqlite3-wasmfs += -sALLOW_MEMORY_GROWTH +emcc.flags.sqlite3-wasmfs += -sALLOW_MEMORY_GROWTH=0 #^^^ using ALLOW_MEMORY_GROWTH produces a warning from emcc: # USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, # see https://github.com/WebAssembly/design/issues/1271 [-Wpthreads-mem-growth] # And, indeed, it runs slowly if memory is permitted to grow. -emcc.flags.sqlite3-wasmfs.vanilla := -emcc.flags.sqlite3-wasmfs.esm := -sEXPORT_ES6 -sUSE_ES6_IMPORT_META -$(eval $(call call-make-pre-js,sqlite3-wasmfs,vanilla)) -$(eval $(call call-make-pre-js,sqlite3-wasmfs,esm)) -Xemcc.flags.sqlite3-wasmfs.vanilla += \ - $(pre-post-common.flags.vanilla) \ - $(pre-post-sqlite3-wasmfs.flags.vanilla) -Xemcc.flags.sqlite3-wasmfs.esm += \ - $(pre-post-common.flags.esm) \ - $(pre-post-sqlite3-wasmfs.flags.esm) -$(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(sqlite3-wasm.c) \ - $(EXPORTED_FUNCTIONS.api) $(MAKEFILE) $(MAKEFILE.wasmfs) -$(sqlite3-wasmfs.js): $(pre-post-sqlite3-wasmfs.deps.vanilla) -$(sqlite3-wasmfs.mjs): $(pre-post-sqlite3-wasmfs.deps.esm) -# SQLITE3-WASMFS.xJS.RECIPE is the wasmfs-specific counterpart -# of SQLITE3.xJS.RECIPE from the main makefile. -define SQLITE3-WASMFS.xJS.RECIPE - @echo "Building $@ ..." - $(emcc.bin) -o $@ $(emcc_opt_full) $(emcc.flags) \ - $(cflags.sqlite3-wasmfs) \ - $(emcc.flags.sqlite3-wasmfs) $(emcc.flags.sqlite3-wasmfs.$(1)) \ - $(pre-post-sqlite3-wasmfs.flags.$(1)) \ - $(sqlite3-wasm.c) - @$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(1)) - chmod -x $(sqlite3-wasmfs.wasm) - $(maybe-wasm-strip) $(sqlite3-wasmfs.wasm) - @ls -la $(sqlite3-wasmfs.wasm) sqlite3-wasmfs*js -endef -$(sqlite3-wasmfs.js): - $(call SQLITE3-WASMFS.xJS.RECIPE,vanilla) -$(sqlite3-wasmfs.mjs): $(sqlite3-wasmfs.js) - $(call SQLITE3-WASMFS.xJS.RECIPE,esm) -$(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.js) -wasmfs: $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs) +#emcc.flags.sqlite3-wasmfs.vanilla := +#emcc.flags.sqlite3-wasmfs.esm := -sEXPORT_ES6 -sUSE_ES6_IMPORT_META +sqlite3-api.mjs.wasmfs := $(dir.tmp)/sqlite3-api-wasmfs.mjs +$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3-wasmfs,esm,1,\ + $(sqlite3-api.mjs.wasmfs), $(sqlite3-wasmfs.mjs),\ + $(c-pp.D.sqlite3-bundler-friendly) -Dwasmfs,\ + -sEXPORT_ES6 -sUSE_ES6_IMPORT_META\ +)) +$(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(MAKEFILE.wasmfs) +######################################################################## +# Build quirk: we cannot build BOTH .js and .mjs with our current +# build infrastructure because the supplemental *.worker.js files get +# generated with the name of the main module file +# ($(sqlite3-wasmfs.{js,mjs})) hard-coded in them. Thus the last one +# to get built gets the *.worker.js files mapped to it. In order to +# build both modes they would need to have distinct base names or +# output directories. "The problem" with giving them distinct base +# names is that it means that the corresponding .wasm file is also +# built/saved multiple times. +# +wasmfs.build.ext := mjs +$(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(SOAP.js.bld) +ifeq (js,$(wasmfs.build.ext)) + $(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.js) + wasmfs: $(sqlite3-wasmfs.js) +else + $(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.mjs) + wasmfs: $(sqlite3-wasmfs.mjs) +endif #all: wasmfs ######################################################################## # speedtest1 for wasmfs. -speedtest1-wasmfs.js := $(dir.wasmfs)/speedtest1-wasmfs.js -speedtest1-wasmfs.wasm := $(subst .js,.wasm,$(speedtest1-wasmfs.js)) +speedtest1-wasmfs.mjs := $(dir.wasmfs)/speedtest1-wasmfs.mjs +speedtest1-wasmfs.wasm := $(subst .mjs,.wasm,$(speedtest1-wasmfs.mjs)) emcc.flags.speedtest1-wasmfs := $(sqlite3-wasmfs.fsflags) -emcc.flags.speedtest1-wasmfs += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS +emcc.flags.speedtest1-wasmfs += $(SQLITE_OPT) emcc.flags.speedtest1-wasmfs += -sALLOW_MEMORY_GROWTH=0 emcc.flags.speedtest1-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) -#$(eval $(call call-make-pre-js,speedtest1-wasmfs,vanilla)) -$(speedtest1-wasmfs.js): $(speedtest1.cses) $(sqlite3-wasmfs.js) \ +#$(eval $(call call-make-pre-js,speedtest1-wasmfs,ems)) +$(speedtest1-wasmfs.mjs): $(speedtest1.cfiles) $(sqlite3-wasmfs.js) \ $(MAKEFILE) $(MAKEFILE.wasmfs) \ - $(pre-post-sqlite3-wasmfs.deps) \ + $(pre-post-sqlite3-wasmfs-esm.deps) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." $(emcc.bin) \ - $(emcc.speedtest1.common) $(emcc.flags.speedtest1-wasmfs) \ - $(pre-post-sqlite3-wasmfs.flags.vanilla) \ + $(pre-post-sqlite3-wasmfs-esm.flags) \ + $(cflags.common) \ $(cflags.sqlite3-wasmfs) \ - -o $@ $(speedtest1.cses) -lm + $(emcc.speedtest1.common) \ + $(emcc.flags.speedtest1-vanilla) \ + $(emcc.flags.sqlite3-wasmfs) \ + $(emcc.flags.speedtest1-wasmfs) \ + -o $@ $(speedtest1.cfiles) -lm + @$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,1) $(maybe-wasm-strip) $(speedtest1-wasmfs.wasm) + chmod -x $(speedtest1-wasmfs.wasm) ls -la $@ $(speedtest1-wasmfs.wasm) -#speedtest1: $(speedtest1-wasmfs.js) -wasmfs: $(speedtest1-wasmfs.js) -CLEAN_FILES += $(speedtest1-wasmfs.js) $(speedtest1-wasmfs.wasm) \ - $(subst .js,.worker.js,$(speedtest1-wasmfs.js)) +wasmfs: $(speedtest1-wasmfs.mjs) +CLEAN_FILES += $(speedtest1-wasmfs.mjs) $(speedtest1-wasmfs.wasm) \ + $(subst .js,.worker.js,$(speedtest1-wasmfs.mjs)) # end speedtest1.js ######################################################################## diff --git a/main.mk b/main.mk index 628f3d162..75730b1a6 100644 --- a/main.mk +++ b/main.mk @@ -566,6 +566,12 @@ srcck1$(EXE): $(TOP)/tool/srcck1.c sourcetest: srcck1$(EXE) sqlite3.c ./srcck1 sqlite3.c +src-verify: $(TOP)/tool/src-verify.c + $(BCC) -o src-verify$(EXE) $(TOP)/tool/src-verify.c + +verify-source: ./src-verify + ./src-verify $(TOP) + fuzzershell$(EXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h $(TCCX) -o fuzzershell$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ $(FUZZERSHELL_OPT) $(TOP)/tool/fuzzershell.c sqlite3.c \ @@ -638,7 +644,7 @@ target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl fts5.c cp fts5.c fts5.h tsrc touch target_source -sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl +sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl src-verify tclsh $(TOP)/tool/mksqlite3c.tcl cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . @@ -650,7 +656,7 @@ sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl sqlite3ext.h: target_source cp tsrc/sqlite3ext.h . -sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl +sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl src-verify tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros=1 echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c @@ -740,6 +746,7 @@ SHELL_SRC = \ $(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/misc/memtrace.c \ + $(TOP)/ext/misc/pcachetrace.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/sqlite3recover.h \ @@ -933,6 +940,9 @@ testrunner: testfixture$(EXE) # devtest: testfixture$(EXE) fuzztest testrunner +mdevtest: + tclsh $(TOP)/test/testrunner.tcl mdevtest + # A very quick test using only testfixture and omitting all the slower # tests. Designed to run in under 3 minutes on a workstation. # @@ -1117,3 +1127,4 @@ clean: rm -f fts5.* fts5parse.* rm -f lsm.h lsm1.c rm -f threadtest5 + rm -f src-verify diff --git a/manifest b/manifest index 515248a69..c84c400b7 100644 --- a/manifest +++ b/manifest @@ -1,28 +1,28 @@ -C Version\s3.42.0 -D 2023-05-16T12:36:15.536 +C Version\s3.43.0 +D 2023-08-24T12:36:59.632 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 764f2e3e8fb4ae1c8dfe03e65b2b3b01bd1fc57edf78ec2cab3a1301e90e1905 -F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 -F Makefile.msc ce7bea7931e1ef96b0f44c0362074a8bb62e61a0e7cfdb05afebd928adaacc2b -F README.md e05bd8fcb45da04ab045c37f79a98654e8aa3b3b8f302cfbba80a0d510df75f7 -F VERSION 17f95ae2fdf21f0e9575eb0b0511ea63f15d71dfff431b21c2b4adbfa70cfbbf +F Makefile.in 1e9105ffed727b1557ce59f0941c5d271e276ec00bc9823f03d77a89e131b918 +F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 +F Makefile.msc 26c2d196391a285c279adb10fd6001774d9b243af94b700b681e4a49cd476684 +F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8 +F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 -F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 +F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc 20366d19fbfc3fceecce95344a2114069eaab4fc22e4f02430da167a1e2ddf04 +F autoconf/Makefile.msc 3248809e70cf439a13e9faf82a4e12cbdb7b042006300ac67175fc5125b5c031 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac e6a7f2abf7a94b5dae00713ad6b3bebc59b878e2705607589830792c61d2ca38 +F autoconf/tea/configure.ac fce5eca183cf47853f7d5b81c1e7bfa41c021fb460939ec67ef83653ffc6a531 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,12 +33,13 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 2f2c090e0a1d051bb53741f0c961f5ebb24507a4f599d686ad6d7ff236144609 x +F configure 9dc3300339f4d6b3c3b108de60cc6ae6b3c547e25c7e6df280b4775db4de3a1b x F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd +F doc/compile-for-windows.md c52f2903f1cb11b2308798feecca2e44701b037b78f467a538ac5c46c28ee250 F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f -F doc/lemon.html d2862dbef72496e87f7996f37e814b146848190a742c12161d13fd15346051b0 +F doc/lemon.html 44a53a1d2b42d7751f7b2f478efb23c978e258d794bfd172442307a755b9fa44 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 @@ -76,7 +77,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c 33d2d0db4dd4e7a7a7e9a7f790414293277f9e7682a2fd9d61c713bfc37cd8b6 +F ext/fts3/fts3_write.c d28d9ef383ef848a7b77df4b9964abcc90d67a2b584120c0ad465972dce416e6 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -85,16 +86,16 @@ F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7 F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6dbd6348ef0cfc324a7 F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 -F ext/fts5/fts5.h c132a9323f22a972c4c93a8d5a3d901113a6e612faf30ca8e695788438c5ca2a -F ext/fts5/fts5Int.h ed48a096418ff4a7c02ac9bd1e8d40c46de21b79a132b8b08d3f32233703de7d +F ext/fts5/fts5.h 05501612cc655504c5dce8ba765ab621d50fc478490089beaa0d75e00b23e520 +F ext/fts5/fts5Int.h 78a63cc0795186cde5384816a9403a68c65774b35d952e05b81a1b4b158e07c8 F ext/fts5/fts5_aux.c 572d5ec92ba7301df2fea3258576332f2f4d2dfd66d8263afd157d9deceac480 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 -F ext/fts5/fts5_config.c 051056a9052f5d3a4d1c695f996fd364f920e341f136c60ab2c04aa7e267113f -F ext/fts5/fts5_expr.c 58fb8ceddfb1cefcd54510f9f2f33c220ef9d1b3fa77462111f5ae2a825ab7b1 -F ext/fts5/fts5_hash.c d4fb70940359f2120ccd1de7ffe64cc3efe65de9e8995b822cd536ff64c96982 -F ext/fts5/fts5_index.c de3cdae2e0056594aad97a728be5c43b6d7a6cdc7e9cc16f197892b2d8689c21 -F ext/fts5/fts5_main.c b4dba04a36aaf9b8e8cef0100b6dbb422cc74753eacc11d6401cac7a87c0f38d -F ext/fts5/fts5_storage.c 76c6085239eb44424004c022e9da17a5ecd5aaec859fba90ad47d3b08f4c8082 +F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 +F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 +F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d +F ext/fts5/fts5_index.c b484322421cbb421d22bb2cd304001b80596d671cd626367c8c806b889de4b42 +F ext/fts5/fts5_main.c 7070031993ba5b5d89b13206ec4ef624895f2f7c0ec72725913d301e4d382445 +F ext/fts5/fts5_storage.c 3c9b41fce41b6410f2e8f82eb035c6a29b2560483f773e6dc98cf3cb2e4ddbb5 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee F ext/fts5/fts5_test_tok.c a2bed8edb25f6432e8cdb62aad5916935c19dba8dac2b8324950cfff397e25ff @@ -121,6 +122,7 @@ F ext/fts5/test/fts5alter.test 5565f7e4605512b69171ac18ca84398603f9f6456dbe377be F ext/fts5/test/fts5auto.test 78989e6527ce69c9eddbef7392fea5c10b0010cd2b2ae68eec7bc869c471e691 F ext/fts5/test/fts5aux.test ebf6f2ff7cb556e83f66991b7f12bff016d3c83d4eab36704b649dd6b1437318 F ext/fts5/test/fts5auxdata.test eacc97ff04892f1a5f3d4df5a73f8bcbc3955ea1d12c9f24137eb1fc079e7611 +F ext/fts5/test/fts5bigid.test 2860854c2561a57594192b00c33a29f91cb85e25f3d6c03b5c2b8f62708f39dd F ext/fts5/test/fts5bigpl.test 6466c89b38439f0aba26ac09e232a6b963f29b1cbe1304f6a664fe1e7a8f5fd3 F ext/fts5/test/fts5bigtok.test 541119e616c637caea925a8c028c37c2c29e94383e00aa2f9198d530724b6e36 F ext/fts5/test/fts5cat.test daba0b80659460b0cb60bd1f40b402478a761fe7ea414c3c94c2be25568cc33a @@ -131,20 +133,25 @@ F ext/fts5/test/fts5config.test 60094712debc59286c59aef0e6cf511c37d866802776a825 F ext/fts5/test/fts5conflict.test 655925678e630d3cdf145d18725a558971806416f453ac8410ca8c04d934238d F ext/fts5/test/fts5connect.test 08030168fc96fc278fa81f28654fb7e90566f33aff269c073e19b3ae9126b2f4 F ext/fts5/test/fts5content.test 213506436fb2c87567b8e31f6d43ab30aab99354cec74ed679f22aad0cdbf283 +F ext/fts5/test/fts5contentless.test eb31159d62811b3a32fb1cfb36be20f9d9db75637c7fe6aa7f5c533db2d00576 +F ext/fts5/test/fts5contentless2.test 12c778d134a121b8bad000fbf3ae900d53226fee840ce36fe941b92737f1fda7 +F ext/fts5/test/fts5contentless3.test 487dce16b6677f68b44d7cbd158b9b7275d25e2c14d713f9188d9645bb699286 +F ext/fts5/test/fts5contentless4.test 0f43ededc2874f65d7da99b641a82239854d98d3fa43db729f284b723f23b69f +F ext/fts5/test/fts5contentless5.test 216962d51376b62e4ff45e8db00aa3e1ab548cb36b7fd450633e73b2d678f88e F ext/fts5/test/fts5corrupt.test 77ae6f41a7eba10620efb921cf7dbe218b0ef232b04519deb43581cb17a57ebe F ext/fts5/test/fts5corrupt2.test 7453752ba12ce91690c469a6449d412561cc604b1dec994e16ab132952e7805f F ext/fts5/test/fts5corrupt3.test 7da9895dafa404efd20728f66ff4b94399788bdc042c36fe2689801bba2ccd78 F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3 -F ext/fts5/test/fts5corrupt5.test 550d0884c14424f9acad051a741f1dd99ec9342277d938e91ff3daf9123d1209 +F ext/fts5/test/fts5corrupt5.test eb6ba5ca28ef7c4c6b01e850d388cdb3dacc8c4c2f383f79d0a98128257742b4 F ext/fts5/test/fts5corrupt6.test bf8eeae07825b088b9665d9d8e4accbd8dc9bf3cb85b6c64cf6c9e18ccc420a4 -F ext/fts5/test/fts5corrupt7.test f3e68673af2514e31dd67a2ed163f7f597252ab683dec155b8db0cdc0b668342 +F ext/fts5/test/fts5corrupt7.test 80ad7f683a8bda2404731bb77e8c3dbbb620c1f6cc583cca8239f6accd6338c0 F ext/fts5/test/fts5delete.test 619295b20dbc1d840b403ee07c878f52378849c3c02e44f2ee143b3e978a0aa7 F ext/fts5/test/fts5detail.test 54015e9c43ec4ba542cfb93268abdf280e0300f350efd08ee411284b03595cc4 F ext/fts5/test/fts5determin.test 1b77879b2ae818b5b71c859e534ee334dac088b7cf3ff3bf76a2c82b1c788d11 F ext/fts5/test/fts5dlidx.test b90852c55881b29dbac6380b274de27beae623ac4b6d567c6c8fb9cdc315a86e F ext/fts5/test/fts5doclist.test faa9e9cc3c0645fa6203667cb5f007c359447c6ee66753f71a58175c2497cacd F ext/fts5/test/fts5ea.test b01e3a18cdfabbff8104a96a5242a06a68a998a0 -F ext/fts5/test/fts5eb.test a973baadac524dbbb4ad9b0e99030e12cabde2c6b28e0ac437298007b642cd12 +F ext/fts5/test/fts5eb.test 5f0a86e9fe4715912e6bfa556368aae96d13c61a481373f24daa40429d5d5ca1 F ext/fts5/test/fts5fault1.test d28a65caee75db6897c3cf1358c5230d3bb2a3bf7fb31062c19c7e5382b3d2bd F ext/fts5/test/fts5fault2.test 69c8fdbef830cd0d450908d4504d5bb86609e255af99c421c20a0756251fe344 F ext/fts5/test/fts5fault3.test da2f9e3e56ff5740d68ebdd6877c97089e7ed28ddff28a0da87a6afea27e5522 @@ -158,6 +165,7 @@ F ext/fts5/test/fts5faultA.test be4487576bff8c22cee6597d1893b312f306504a8c6ccd3c F ext/fts5/test/fts5faultB.test d606bdb8e81aaeb6f41de3fc9fc7ae315733f0903fbff05cf54f5b045b729ab5 F ext/fts5/test/fts5faultD.test e7ed7895abfe6bc98a5e853826f6b74956e7ba7f594f1860bbf9e504b9647996 F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e4710c77eb8ce7075 +F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1 F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b996159f6909dc8079 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e @@ -194,13 +202,13 @@ F ext/fts5/test/fts5secure2.test 2e961d7eef939f294c56b5d895cac7f1c3a60b934ee2cfd F ext/fts5/test/fts5secure3.test c7e1080a6912f2a3ac68f2e05b88b72a99de38543509b2bbf427cac5c9c1c610 F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b9846cf7fc8337b67c97 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a -F ext/fts5/test/fts5secure6.test 7a959d834be6725c641b3c3b38ef86570ea671216ad803e054e4fdff33a72ce2 +F ext/fts5/test/fts5secure6.test a0a28cfb9bf9721408b65b5d7c7ce369af3d688e273da24d101c25d60cdce05c F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b F ext/fts5/test/fts5simple2.test 258a1b0c590409bfa5271e872c79572b319d2a56554d0585f68f146a0da603f0 F ext/fts5/test/fts5simple3.test d5c74a9d3ca71bd5dd5cacb7c55b86ea12cdddfc8b1910e3de2995206898380f F ext/fts5/test/fts5synonym.test 1651815b8008de170e8e600dcacc17521d765482ea8f074ae82cfa870d8bb7fb -F ext/fts5/test/fts5synonym2.test b54cce5c34ec08ed616f646635538ae82e34a0e28f947ec60b6fadbc4b3fb17a +F ext/fts5/test/fts5synonym2.test 8f891fc49cc1e8daed727051e77e1f42849c784a6a54bef82564761b2cb3e016 F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 @@ -224,6 +232,46 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 +F ext/jni/GNUmakefile 3deba6bc0bf37c1ee5f15d1ff3c3512ae2f3cf44a2b8ae7b4af92690514b0cb4 +F ext/jni/README.md 5c60e4580aa5c94ff74d7bef1fb6231e578f7764e831a07b5981b6ab62b35560 +F ext/jni/jar-dist.make 93da95f8fe01ef22fccacc27f2e805938058e91e8c72c0532558d3a812a42e74 +F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 +F ext/jni/src/c/sqlite3-jni.h 28565de9efc971195c684095ba0d184b90401290698c987f7ea3f54e47ff4f2f +F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 +F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 +F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c +F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 +F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8 +F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a +F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 +F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 01f890105c6b7edbbad1c0f5635f783cea62c4b2ae694a71e76514a936ee03ec +F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc +F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 +F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 +F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee +F ext/jni/src/org/sqlite/jni/OutputPointer.java d81f8bd43d2296ae373692370cfad16ddde76f5c14cd2760f7b4e1113ef56d4c +F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc +F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86 +F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 +F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65 +F ext/jni/src/org/sqlite/jni/Tester1.java 4253dc7bcff64500a9388f1a17d3d39dbe4eb9d7db9fc035ce6e2380d45ad5fc +F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 +F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d +F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d +F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee +F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8af53f70c4af6db24aab8d53d9ba +F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c +F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b +F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc +F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 +F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc +F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1f1286428fab38dfefe328e72b5735f533b19af8dd17712dd3df7e044d21c8b8 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e +F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d +F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -286,13 +334,13 @@ F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8b F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 -F ext/misc/decimal.c 57d85fa20a5a74d3b0dfc78ab7934ae6c9f5aa8eed915faa2b5246bec87ddc6d +F ext/misc/decimal.c 172cf81a8634e6a0f0bedaf71a8372fee63348cf5a3c4e1b78bb233c35889fdc F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720 F ext/misc/fossildelta.c 1240b2d3e52eab1d50c160c7fe1902a9bd210e052dc209200a750bbf885402d5 F ext/misc/fuzzer.c eae560134f66333e9e1ca4c8ffea75df42056e2ce8456734565dbe1c2a92bf3d -F ext/misc/ieee754.c 984d51fe23e956484ec1049df6f5257002e3ab338cabceb39761c2e80ad10bf4 +F ext/misc/ieee754.c 62a90978204d2c956d5036eb89e548e736ca5fac0e965912867ddd7bb833256d F ext/misc/memstat.c 3017a0832c645c0f8c773435620d663855f04690172316bd127270d1a7523d4d F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2d5537b F ext/misc/memvfs.c 7dffa8cc89c7f2d73da4bd4ccea1bcbd2bd283e3bb4cea398df7c372a197291b @@ -300,6 +348,7 @@ F ext/misc/mmapwarm.c 347caa99915fb254e8949ec131667b7fae99e2a9ce91bd468efb6dc372 F ext/misc/nextchar.c 7877914c2a80c2f181dd04c3dbef550dfb54c93495dc03da2403b5dd58f34edd F ext/misc/noop.c 81efe4cad9ec740e64388b14281cb983e6e2c223fed43eb77ab3e34946e0c1ab F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d09ea61f +F ext/misc/pcachetrace.c f4227ce03fb16aa8d6f321b72dd051097419d7a028a9853af048bee7645cb405 F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196 F ext/misc/qpvtab.c 09738419e25f603a35c0ac8bd0a04daab794f48d08a9bc07a6085b9057b99009 @@ -308,7 +357,7 @@ F ext/misc/regexp.c 4bdd0045912f81c84908bd535ec5ad3b1c8540b4287c70ab840709636240 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 -F ext/misc/series.c d4001360b2b05a831bbad378e131bd585b29499482e3f2557e86dbd3e2367f25 +F ext/misc/series.c dde5ba69cb9053ff32b5afd64e8d202472325bc052301e31e4d9c0d87e4fff50 F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d F ext/misc/shathree.c 543af7ce71d391cd3a9ab6924a6a1124efc63211fd0f2e240dc4b56077ba88ac F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 @@ -375,13 +424,13 @@ F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69 F ext/rbu/sqlite3rbu.c d4ddf8f0e93772556e452a6c2814063cf47efb760a0834391a9d0cd9859fa4b9 F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 -F ext/recover/dbdata.c 31d580785cf14eb3c20ed6fbb421a10a66569858f837928e6b326088c38d4c72 +F ext/recover/dbdata.c 81661e3a98cabb70be8f2760a67a8d6d5bf7aaa7a4055a53ff915ac884221a64 F ext/recover/recover1.test c484d01502239f11b61f23c1cee9f5dd19fa17617f8974e42e74d64639c524cf F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a F ext/recover/recoverbuild.test c74170e0f7b02456af41838afeb5353fdb985a48cc2331d661bbabbca7c6b8e3 F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e F ext/recover/recovercorrupt.test 64c081ad1200ae77b447da99eb724785d6bf71715f394543dc7689642e92bf49 -F ext/recover/recovercorrupt2.test 74bef7dd2d7dd4856f3da21be6e213d27da44827e0f5f0946ca0325b46d163ed +F ext/recover/recovercorrupt2.test b9b974f006340a1300b5a7e687bd6097c5238498181c6f92d87406a77ece430b F ext/recover/recoverfault.test 9d9f88eeb222615a25e7514f234c950d46bee20d24cd8db49d8fff8d650dcfe1 F ext/recover/recoverfault2.test 730e7371bcda769554d15460cb23126abba1be8eca9539ccabf63623e7bb7e09 F ext/recover/recoverold.test 68db3d6f85dd2b98e785b6c4da4f5eea4bbe52ccf6674d9a94c7506dc92596aa @@ -389,7 +438,7 @@ F ext/recover/recoverpgsz.test 3658ab8e68475b1bb87d6af88baa04551c84b73280a566a1b F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a3a21030057bfd81411 F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486 -F ext/recover/sqlite3recover.c b3813090c97df34858bab8ece4e8c49d9e2f56ba2b9bb019f57f44bc234b3c6d +F ext/recover/sqlite3recover.c e6eb20c469bcdb96f297f2241860bccabf9f036bfa7f3d47bcc6ca1191b108dc F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 @@ -403,9 +452,9 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c 971e0b5bd9adaf0811feb8c0842a310811159da10319eb0e74fdb42bf26b99ca -F ext/rtree/rtree.c 925888f7672b326fc2a29b3a212ec3ae4aa2331507ecccfaf7f0ac0335020330 +F ext/rtree/rtree.c 6954f4a3ca51c2e3db35c52e0513f3520999eb7a967f3d53b71db7ebddd8b3a5 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 -F ext/rtree/rtree1.test d47f58832145fcfed9067bc457ca8664962196c4566c17a1ebd679367db55d11 +F ext/rtree/rtree1.test 877d40b8b61b1f88cec9d4dc0ff8334f5b05299fac12a35141532e2881860e9d F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d F ext/rtree/rtree3.test 272594f88c344e973864008bbe4c71fd3a41a264c097d568593ee7886d83d409 F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b @@ -473,90 +522,89 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c e50a9218ee360db0a25298adc6614162d80ebe65d3f6a5b0a021e0902f6536a1 +F ext/session/sqlite3session.c 1971b61ca45babf0d9e4bb669a65b0903135e9828af2fcd4f0c8f1b7acf36b6f F ext/session/sqlite3session.h 653e9d49c4edae231df8a4c8d69c2145195aedb32462d4b44229dbee7d2680fb F ext/session/test_session.c 5285482f83cd92b4c1fe12fcf88210566a18312f4f2aa110f6399dae46aeccbb F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 38700d5074af690f004e4e5f3533164ab49693b9d0832929c4ecf97a0bc09494 +F ext/wasm/GNUmakefile d02f3c8798b754f68b1f6b422ccff894a10bf352fc9c4eb8945baeace1acac28 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 -F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9 +F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 -F ext/wasm/api/README.md 77a2f1f2fc60a35def7455dffc8d3f2c56385d6ac5c6cecc60fa938252ea2c54 -F ext/wasm/api/extern-post-js.c-pp.js 393ab78b807da94096eae1a68bfddb999a2299936a185d910162fe87a57a9a3a +F ext/wasm/api/README.md 5eb44fa02e9c693a1884a3692428647894b0380b24bca120866b7a24c8786134 +F ext/wasm/api/extern-post-js.c-pp.js c4154a7f90c2d7e51fd6738273908152036c3457fdc0b6523f1be3ef51105aac F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219 -F ext/wasm/api/sqlite3-api-cleanup.js cc21e3486da748463e02bbe51e2464c6ac136587cdfd5aa00cd0b5385f6ca808 -F ext/wasm/api/sqlite3-api-glue.js f1b2dcb944de5138bb5bd9a1559d2e76a4f3ec25260963d709e8237476688803 -F ext/wasm/api/sqlite3-api-oo1.js 2691a34a741015127b210954a1b9586764d3ff0c8a20f00fd15c00f339ecc79f -F ext/wasm/api/sqlite3-api-prologue.js 17f4ec398ba34c5c666fea8e8c4eb82064a35b302f2f2eb355283cd8d3f68ed5 -F ext/wasm/api/sqlite3-api-worker1.js 40a5b1813fcbe789f23ae196c833432c8c83e7054d660194ddfc51eab1c5b9bf +F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e +F ext/wasm/api/sqlite3-api-glue.js b65e546568f1dfb35205b9792feb5146a6323d71b55cda58e2ed30def6dd52f3 +F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8 +F ext/wasm/api/sqlite3-api-prologue.js 5f283b096b98bfb1ee2f2201e7ff0489dff00e29e1030c30953bdb4f5b87f4bd +F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b1738645c0134562bb84e88e2fec F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 -F ext/wasm/api/sqlite3-opfs-async-proxy.js 70914ae97784d3028150bbf252e07a423056c42cc345903c81b5fae661ce512f -F ext/wasm/api/sqlite3-v-helper.js e5c202a9ecde9ef818536d3f5faf26c03a1a9f5192b1ddea8bdabf30d75ef487 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 89640e4874a60cb2d973306b272384ffb45c7915375c7bb0355c7586f88dc39c -F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 -F ext/wasm/api/sqlite3-wasm.c 12a096d8e58a0af0589142bae5a3c27a0c7e19846755a1a37d2c206352fbedda -F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 2710a06a59620c6bf7ce298ab1fb6c9ce825b9f9379728b74c486db6613beecc +F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 +F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js abb69b5e008961026bf5ff433d7116cb046359af92a5daf73208af2e7ac80ae7 +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js e04fc2fda6a0200ef80efdbb4ddfa0254453558adb17ec3a230f93d2bf1d711c +F ext/wasm/api/sqlite3-wasm.c d4d4c2b349b43b7b861e6d2994299630fb79e07573ea6b61e28e8071b7d16b61 +F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4 -F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b +F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 -F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f -F ext/wasm/common/whwasmutil.js 749a1f81f85835e9a384e9706f2a955a7158f2b8cc9da33f41105cac7775a305 +F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f +F ext/wasm/common/whwasmutil.js 4c64594eecc7af4ae64259e95a71ba2a7edf118881aaff0bba86d0c7164e78e4 F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 -F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 +F ext/wasm/demo-123.js 38aa8faec4d0ace1c973bc8a7a1533584463ebeecd4c420daa7d9687beeb9cb5 F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8 F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f -F ext/wasm/demo-worker1-promiser.js 51b02509a109e82f623fb4c900c8b48b9a77cc13fbd038396f9a083b86593ae3 +F ext/wasm/demo-worker1-promiser.js 5e5c7d7c91cd7aae9cc733afd02569ba9c6928292db413b550e8b842f4b75e87 F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d -F ext/wasm/demo-worker1.js 2c7794d8bc4ab9ecf9cdc2c15de940b11a006942226e441ea41edd458dfc0a26 -F ext/wasm/dist.make 451fb1b732257849f6e898d2a862512a0401500ed369ef53bdfeddf9c77bc3b9 +F ext/wasm/demo-worker1.js 836bece8615b17b1b572584f7b15912236a5947fe8c68b98d2737d7e287447ef +F ext/wasm/dist.make 3a851858aad72e246a5d9c5aaf6b6a144305f1bf898ac1846760ea7bab95c9a3 F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f -F ext/wasm/fiddle.make dbe36b90b8907ae28ecb9c0e9fd8389dbdaecf117ea4fb2ea33864bdfa498a94 +F ext/wasm/fiddle.make fa2ba6e90457ba2a71cb745f200d409caf773076df75ae5b177cc225d7627a11 F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f -F ext/wasm/fiddle/fiddle-worker.js 163d6139a93fab4bcb72064923df050d4e7c0ff0d8aa061ce8776a6e75da8a10 +F ext/wasm/fiddle/fiddle-worker.js e0153f9af6500805c6f09c0b3cfdb7d857e9d6863dbee9d50d1628fccf5f4b4d F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 F ext/wasm/index-dist.html 22379774f0ad4edcaaa8cf9c674c82e794cc557719a8addabed74eb8069d412e -F ext/wasm/index.html dd900891844caebd9cadbddd704f66bd841d7c12fd69ce5af490e2c10fb49f45 -F ext/wasm/jaccwabyt/jaccwabyt.js 8287c0537fa0750414edbe75ce64668a81c8716df5ec4c3e6bb4f11bd1c36031 +F ext/wasm/index.html 4e7847b909f4ae0da8c829b150b79454050e53b3658431f138636257729cd42b +F ext/wasm/jaccwabyt/jaccwabyt.js 1264710db3cfbcb6887d95665b7aeba60c1126eaef789ca4cf1a4a17d5bc7f54 F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb -F ext/wasm/module-symbols.html 841de62fc198988b8330e238c260e70ec93028b096e1a1234db31b187a899d10 -F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06 -F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18 -F ext/wasm/speedtest1-wasmfs.html 7a301f4f5b6ad4f5d37fd6e7ca03a2f5d5547fd289da60a39075a93d7646d354 -F ext/wasm/speedtest1-worker.html 82869822e641c1bef3ec0cd2d7d2b6a42d0b4f68a7b160fb2e1dd0b523940a9b -F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1 +F ext/wasm/module-symbols.html dc476b403369b26a1a23773e13b80f41b9a49f0825e81435fe3600a7cfbbe337 +F ext/wasm/scratchpad-wasmfs.html a3d7388f3c4b263676b58b526846e9d02dfcb4014ff29d3a5040935286af5b96 +F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd842231505895eff00dbd57c63 +F ext/wasm/speedtest1-wasmfs.html 0e9d335a9b5b5fafe6e1bc8dc0f0ca7e22e6eb916682a2d7c36218bb7d67379d +F ext/wasm/speedtest1-wasmfs.mjs ac5cadbf4ffe69e9eaac8b45e8523f030521e02bb67d654c6eb5236d9c456cbe +F ext/wasm/speedtest1-worker.html e33e2064bda572c0c3ebaec7306c35aa758d9d27e245d67e807f8cc4a9351cc5 +F ext/wasm/speedtest1-worker.js 315d26198c46be7c85e26fda15d80ef882424276abde25ffd8b026fb02a35d8c F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b -F ext/wasm/tester1-worker.html 258d08f1ba9cc2d455958751e26be833893cf9ff7853e9436e593e1f778a386b +F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 -F ext/wasm/tester1.c-pp.js 587a18db0f794c594eb0fade37c92af3e2370501576bb08dafd8ed7d009b6516 +F ext/wasm/tester1.c-pp.js 64eb0ee6e695d5638d0f758f31a0ca2231e627ca5d768de3d8b44f9f494de8d4 F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 -F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 -F ext/wasm/wasmfs.make cf9a68162d92ca2bcb0b9528b244cb36d5cc2d84ccc9c2d398461927d6e75aea +F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966f1bc16a F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk ac6b13f8ecc43f377e9912380ea4cf366051d7f784cf61c8886e03e1cf0fbefa -F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 +F main.mk e18c03071dbb9da424212ba2a1ea331ae0e8f6d7d51283b7ad610c52ea11229c F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -565,44 +613,44 @@ F mptest/mptest.c aa41ace6dbc5050d76b02548d3521e6bbccae4f0 F mptest/multiwrite01.test dab5c5f8f9534971efce679152c5146da265222d F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b -F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 +F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 -F src/alter.c 482c534877fbb543f8295992cde925df55443febac5db5438d5aaba6f78c4940 -F src/analyze.c a1f3061af16c99f73aed0362160176c31a6452de1b02ada1d68f6839f2a37df0 +F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5 +F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39 -F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf +F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 -F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d -F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca -F src/btree.c ecaaf8d57cd8b5f4e3167bd59cf61cef031b4b2ee606e6afa11b96a60a14f9ef -F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc -F src/btreeInt.h b900603c8956bdeb313841f9b67bdeceef32c64d962d35477c07ec25e8cf0f9b -F src/build.c 7a7217f75f202eff03617ca447bb9c3bc07d5af49da1d3cff2b1a88e8e394686 +F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 +F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 +F src/btree.c 7a37bdf09f338561880860681cb03499a60c3bb0869e539c58bc1d2cdd705ff2 +F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 +F src/btreeInt.h 91a9e0c41a0e71fa91a742ec285c63dd8dcb38b73d14fae0ed7209174ff0fdc1 +F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d -F src/date.c aca9e0c08b400b21238b609aea7c09585396cd770985cf8f475560f69222dad3 +F src/ctime.c cff2b493de894383832347c3a3f936a05f4e089a92b42366838da1735f3848b5 +F src/date.c f73f203b3877cef866c60ab402aec2bf89597219b60635cf50cbe3c5e4533e94 F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387 F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef -F src/delete.c a9c6d3f51c0a31e9b831e0a0580a98d702904b42d216fee530940e40dec34873 -F src/expr.c 941fe758212c6cf0007c6d7daf5368e11c199376ace9b3018494296e18a27eac +F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 +F src/expr.c 1affe0cc049683ef0ef3545d9b6901508556b0ef7e2892a344c3df6d7288d79d F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c 03c134cc8bffe54835f742ddea0b72ebfc8f6b32773d175c71b8afeea6cb5c83 -F src/func.c 03e6b501f3056d0ba398bda17df938b2b566aa0b3ca7e1942a3cd1925d04ec36 -F src/global.c bd0892ade7289f6e20bff44c07d06371f2ff9b53cea359e7854b9b72f65adc30 -F src/hash.c c6af5f96a7a76d000f07c5402c48c318c2566beecdee9e78b9d9f60ce7119565 +F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 +F src/func.c dd1ecd1be6aaa67c9fa723f841e05e1536314c6aaa0509e25289b310f64dbb9c +F src/global.c 29f56a330ed9d1b5cd9b79ac0ca36f97ac3afc730ff8bfa987b0db9e559d684d +F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 -F src/hwtime.h b638809e083b601b618df877b2e89cb87c2a47a01f4def10be4c4ebb54664ac7 +F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c a8de1db43335fc4946370a7a7e47d89975ad678ddb15078a150e993ba2fb37d4 -F src/json.c 39b1c7527f3111923e65f168a87b03b591f12a41400a63d05c119794bee36620 +F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 +F src/json.c ae840f87b418f039f5d336b488933d09396bd31e6b31e855b93055ccaee4e255 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c be5af440f3192c58681b5d43167dbca3ccbfce394d89faa22378a14264781136 -F src/main.c 035be2e9ba2a0fc1701a8ab1880af3001a968a24556433538a6c073558ee4341 +F src/loadext.c 98cfba10989b3da6f1807ad42444017742db7f100a54f1032af7a8b1295912c0 +F src/main.c fde8f13c876a658b4e8b74b77d875ca887915c174ea6a2f3122d80966f93d865 F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 -F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de +F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff @@ -612,47 +660,47 @@ F src/msvc.h 80b35f95d93bf996ccb3e498535255f2ef1118c78764719a7cd15ab4106ccac9 F src/mutex.c 5e3409715552348732e97b9194abe92fdfcd934cfb681df4ba0ab87ac6c18d25 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 -F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d -F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541 +F src/mutex_unix.c bd52ec50e44a41fe1e3deb5a6e3fe98edb6f2059da3e46d196363d0fa3192cda +F src/mutex_w32.c 38b56d0bc8d54c17c20cbaaad3719b0c36b92fd07a7e34360d0c6a18d5589912 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 -F src/os.c 81c9c1c52eab711e27e33fd51fe5788488d3a02bc1a71439857abbee5d0d2c97 +F src/os.c 509452169d5ea739723e213b8e2481cf0e587f0e88579a912d200db5269f5f6d F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c 1b3ddb7814c4bf37f494c04d2ab30c1ced5b2c927267e1930ce7cd388787a96d -F src/os_win.c 2b2411279f7b24f927591561303fc5871845732df42641cbf695c23640b16975 +F src/os_unix.c 2e8b12107f75d1bd16412f312b4c5d5103191807a37836d3b81beb26436ad81b +F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 45e2ef5e9eb5cc0138bcc32b5d2299479be80b206f621873f59149dfb517f496 -F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 -F src/parse.y 146f9a1db7db5ef4299c6897d335e5abed348c2626190d2877d45ffa210fd4ca -F src/pcache.c 8ee13acccfd9accbf0af94910b7323dd7f7d55300d92ddafcf40e34fcc8e21be +F src/pager.c 993445a19b611d473ca007542ab3149840661a4c7e9f2d9e1ec008b7cc2abe78 +F src/pager.h f4d33fec8052603758792045493423b8871a996da2d0973927b7d36cd6070473 +F src/parse.y aeb7760d41cfa86465e3adba506500c021597049fd55f82a30e5b7045862c28c +F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 -F src/pcache1.c dee95e3cd2b61e6512dc814c5ab76d5eb36f0bfc9441dbb4260fccc0d12bbddc -F src/pragma.c 26ed2cfdc5c12aa1c707178635709684960288cacc9cff9d491a38ff10e395f1 +F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 +F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e -F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d +F src/prepare.c 80548297dc0e1fb3139cdebffb5a1bcac3dfac66d791012dd74838e70445072d +F src/printf.c e3ba080e2f409f9bfcc8d34724e6fc160e9c718dc92d0548f6b71b8b6f860ce2 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032 -F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 738c3a3d6929f8be66c319bad17f6b297bd60a4eb14006075c48a28487dc7786 -F src/shell.c.in 52836b4002a2cad8095b451f0c39a6542c23a231eb0ed5e39387bc8b1f7aaa9e -F src/sqlite.h.in c14a4471fcd897a03631ac7ad3d05505e895e7b6419ec5b96cae9bc4df7a9fc6 +F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 +F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 +F src/select.c 5f545a2c8702d4d3430bbb188cfec47d6c122d899061ef00cbe56af14591c574 +F src/shell.c.in 2f9be25294b68b07e7e81f0adcec4475aba6011b64f160e414efe226910c4d7b +F src/sqlite.h.in 73a366c1c45d5ac9888cfe81c458826a44498531d106cfb4f328193ab5f6f17d F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 -F src/sqliteInt.h a3ced8b9ebc573189c87b69f24bf10d2b9cd3cefefaae52623a2fa79e6fdd408 -F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 +F src/sqlite3ext.h 2f30b2671f4c03cd27a43f039e11251391066c97d11385f5f963bb40b03038ac +F src/sqliteInt.h 025ed58a41968ef80d64cdc194caa8dd207b0256b147253d762fdac7a62408f9 +F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 8522a04fb9c84faa1d80354430ae0ee9349727a3a4b32e3cfe39b9be8324cabd -F src/test1.c 8eab61fb2813aa212d97ab188e85fc9ca7b89d9ff5ff05d59d9aa0c491a6c721 -F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef -F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 +F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd +F src/test1.c ebba2473874a23add4a10881b90bd445cfa7f59f90749434938aec14239c6486 +F src/test2.c 54520d0565ef2b9bf0f8f1dcac43dc4d06baf4ffe13d10905f8d8c3ad3e4b9ab +F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d -F src/test6.c ae73a3a42bbc982fb9e301b84d30bda65a307be48c6dff20aba1461e17a9b0ce -F src/test8.c 0c856d6ff6b0d2ff6696addc467a15ed17c6910f14475302cd5b3b4e54406161 +F src/test6.c e53bc69dc3cb3815fb74df74f38159ec05ba6dd5273216062e26bc797f925530 +F src/test8.c ccc5d3e2a2bf7248f7da185e2afc4c08b4c6840447f5eb4dd106db165fddbdbc F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 @@ -660,27 +708,27 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 F src/test_bestindex.c 68c62586d2ae9f032903fe53be743657d0c2aac0a850b880938b668e1161d516 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 -F src/test_config.c 8264637b06a3c1f0727c88d1ea32dcf7986b9e7e358a970cae87cdac8a5b2708 +F src/test_config.c f0cc1f517deaa96dd384822ae2bb91534fa56aa458528b439830d709941d3932 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f F src/test_demovfs.c 38a459d1c78fd9afa770445b224c485e079018d6ac07332ff9bd07b54d2b8ce9 -F src/test_devsym.c aff2255ea290d7718da08af30cdf18e470ff7325a5eff63e0057b1496ed66593 +F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 F src/test_fs.c ba1e1dc18fd3159fdba0b9c4256f14032159785320dfbd6776eb9973cb75d480 F src/test_func.c 24df3a346c012b1fc9e1001d346db6054deb426db0a7437e92490630e71c9b0a F src/test_hexio.c 9478e56a0f08e07841a014a93b20e4ba2709ab56d039d1ca8020e26846aa19bd -F src/test_init.c 4413c211a94b62157ca4c145b3f27c497f03c664 +F src/test_init.c f2cc4774b7c9140f76e45ecbb2ae219f68e3acbbe248c0179db666a70eae9f08 F src/test_intarray.c 39b4181662a0f33a427748d87218e7578d913e683dc27eab7098bb41617cac71 -F src/test_intarray.h d57ae92f420cda25e22790dac474d60961bd0c500cbaa3338a05152d4a669ef7 +F src/test_intarray.h 6c3534641108cd1bea517a8e117dcba237081310a29a4c35bd2190caa8972293 F src/test_journal.c a0b9709b2f12b1ec819eea8a1176f283bca6d688a6d4a502bd6fd79786f4e287 F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd F src/test_malloc.c 21121ea85b49ec0bdb69995847cef9036ef9beca3ce63bbb776e4ea2ecc44b97 -F src/test_md5.c 7268e1e8c399d4a5e181b64ac20e1e6f3bc4dd9fc87abac02db145a3d951fa8c -F src/test_multiplex.c d8bc260a57c2028946a9bce9918d24e69e44cebd71ac6be240aa5b6b75e2b369 -F src/test_multiplex.h 5436d03f2d0501d04f3ed50a75819e190495b635 -F src/test_mutex.c abf486e91bd65e2448027d4bb505e7cce6ba110e1afb9bd348d1996961cadf0d +F src/test_md5.c 0472c86d561f7f9e4ff94080100c2783196f50e583bb83375b759450c5b81802 +F src/test_multiplex.c 70479161239d65af2a231550b270e9d11ece717ad7bf0e13ef42206586e9dd7f +F src/test_multiplex.h f0ff5b6f4462bfd46dac165d6375b9530d08089b7bcbe75e88e0926110db5363 +F src/test_mutex.c cd5bac43f2fd168f43c4326b1febe0966439217fac52afb270a6b8215f94cb40 F src/test_onefile.c f31e52e891c5fef6709b9fcef54ce660648a34172423a9cbdf4cbce3ba0049f4 F src/test_osinst.c d341f9d7613e007c8c3f7eba6cd307230047506aa8f97858c1fd21f5069616bd -F src/test_pcache.c a5cd24730cb43c5b18629043314548c9169abb00 -F src/test_quota.c 6cb9297115b551f433a9ad1741817a9831abed99 +F src/test_pcache.c 3960cd2c1350adc992c4bf7adcfb0d1ac0574733012bd1a5f94e195928577599 +F src/test_quota.c ea44c05f29b995bdb71c55eb0c602604884e55681d59b7736e604bbcc68b0464 F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b @@ -692,40 +740,40 @@ F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb26 F src/test_thread.c 7ddcf0c8b79fa3c1d172f82f322302c963d923cdb503c6171f3c8081586d0b01 F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43 F src/test_vfs.c 193c18da3dbf62a0e33ae7a240bbef938a50846672ee947664512b77d853fe81 -F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698 +F src/test_vfstrace.c a2ea82df2ed8927e9eba49bdba1aa1aeb1dcb13dbf6558cff036da813031de9a F src/test_windirent.c a895e2c068a06644eef91a7f0a32182445a893b9a0f33d0cdb4283dca2486ac1 -F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a90484215 +F src/test_windirent.h da2e5b73c32d09905fbdd00f27cd802212a32a58ead882736fe4f5eb775ebc50 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 1305797eab3542a0896b552c6e7669c972c1468e11e92b370533c1f37a37082b -F src/treeview.c fccf3b8c517c1f55cb380c1522febe6921fcb2bd800c16c78cab571d0eb0ccbd +F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 +F src/treeview.c 1d52fbc4e97161e65858d36e3424ea6e3fc045dd8a679c82b4b9593dc30de3bd F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0 -F src/update.c 3f4fb5ad7c9b48d7911974d6579192bb3a6c27f46140b6cbb9139cc8a77b8691 -F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 +F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 +F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c d4bcb560471cd94e6e17d448311f8d5bf81a7e5276295a53501058ef1b95dd1a -F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd -F src/vdbe.c fedd2dfa5165256c8e372f2ae9454c4a82cf60ce79a04dff80a86ab2116ea15a -F src/vdbe.h 637ae853b7d42ae3951034cc63ab7c8af837861f79504cdb5399552fcd89a884 -F src/vdbeInt.h a4147a4ddf613cb1bcb555ace9e9e74a9c099d65facd88155f191b1fb4d74cfb -F src/vdbeapi.c b4982cde547054c4f7341198db3c3008a48e1eb028f757601bf5bf2fc026cbcf -F src/vdbeaux.c 6ee48db408d4c297a363f1e31145c09793a580e7c508bb36063dd017d67117a2 +F src/util.c 278b81c3b33db1b5a5f3859adf8905c165b910080043061d44d3c5a25b4b406d +F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 +F src/vdbe.c 346d848a0bf8128e3e3722c5406f4bde6c32d7093b93402c6f8e0718d19305c3 +F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 +F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c +F src/vdbeapi.c 37341acd781fda162e8cf4d9fc2eaea2febad3b365877a9d7233b8c6d0960d85 +F src/vdbeaux.c e3aa5c46827cd95e0fc4d0f302fa3e901ab5f07258fdbb42709eeef40f63018d F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce -F src/vdbemem.c 1cac4028c0dabbf1f3259f107440e2780e05ac9fe419e9709e6eb4e166ba714b -F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 +F src/vdbemem.c 317b9f48708139db6239ade40c7980b4bc8233168383690d588dad6d8437f722 +F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 -F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac -F src/vtab.c 4758a96d36c9a120848386ae603b1ab32a4876e0a1faf81bfcfb524455e583dc +F src/vdbevtab.c 57fa8f56478e5b5cb558cb425e7878515e0a105c54f96f1d1bbf4b9433529254 +F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 7a65f64bfe4a783c5e2df73ffb0efc383dec934dee9e3ac706b2eeb3631d17ac -F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a -F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c b74a83b4c8f65b218c5c1c8d9122433f85ee1300fd9263ba1697d0e1040eeb36 -F src/whereInt.h e25203e5bfee149f5f1225ae0166cfb4f1e65490c998a024249e98bb0647377c -F src/wherecode.c b300db0bcd84ad6c2642bf3f509f92fad7b7d697b9856b64dd66d692d184d054 -F src/whereexpr.c 22cf19b0ececeaf838daed1039c5231a8778784eba5ad67b991442a23473fd3f -F src/window.c e075ea85bea322e30e361fa6e69eddba74f461e99e2a564dc09973f8a1fb27d9 +F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9 +F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 +F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 +F src/where.c b8917792f1e0dbfa28fb29e6cd3d560060d69667be0ba4c491cbc772363264f5 +F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a +F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 +F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 +F src/window.c b7ad9cff3ce8ae6f8cc25e18e1a258426cb6bd2999aace6f5248d781b2a74098 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 @@ -755,7 +803,7 @@ F test/altertab2.test 62597b6fd08feaba1b6bfe7d31dac6117c67e06dc9ce9c478a3abe75b5 F test/altertab3.test 6c432fbb9963e0bd6549bf1422f6861d744ee5a80cb3298564e81e556481df16 F test/altertrig.test fb5951d21a2c954be3b8a8cf8e10b5c0fa20687c53fd67d63cea88d08dd058d5 F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f -F test/analyze.test 547bb700f903107b38611b014ca645d6b5bb819f5210d7bf39c40802aafeb7d7 +F test/analyze.test 2fb21d7d64748636384e6cb8998dbf83968caf644c07fcb4f76c18f2e7ede94b F test/analyze3.test 03f4b3d794760cf15da2d85a52df9bae300e51c8fefe9c36cfae1f86dc10d23f F test/analyze4.test 68bd069f3ac7ac1e652ddd9f04f57d5606ddb4208450f5297005db7aa0dd707d F test/analyze5.test fa5131952303ac4146aba101b116b9c8cb89e2637531c334a6df7f7d19dddc0d @@ -774,7 +822,7 @@ F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7 F test/async4.test 1787e3952128aa10238bf39945126de7ca23685a F test/async5.test 383ab533fdb9f7ad228cc99ee66e1acb34cc0dc0 -F test/atof1.test 1f24a6f16e44892848298bd8e488159d4a60cf47be003d32747a136103c9bbac +F test/atof1.test f2765d7fdc1348ae58b279d096d301a208e46da623213877b2ba580dc2768975 F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d061 F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c @@ -791,7 +839,7 @@ F test/autoinc.test 997d6f185f138229dc4251583a1d04816423dddc2fc034871a01aeb1d728 F test/autoindex1.test d34caffb0384003ee28eae87679214c029e9be4b332d9649a79e0b94ab70502c F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df F test/autoindex3.test dcd6b2f8bed2be67b131e2e671f892e971d934e24fd00988952d0e0a67e24aa7 -F test/autoindex4.test 5df39313526b6f22a26bd119bbd97ca69f28386ab3c671fc10568d921c41eb08 +F test/autoindex4.test 3c2105e9172920e26f950ba3c5823e4972190e022c1e6f260ba476b0af24c593 F test/autoindex5.test 2ee94f033b87ca0160e08d81034c507aff8e230df2627f0304fa309b2fee19a3 F test/autovacuum.test 00671369bbf96c6a49989a9425f5b78b94075d6a4b031e5e00000c2c32f365df F test/autovacuum2.test 76f7eb4fe6a6bf6d33a196a7141dba98886d2fb53a268d7feca285d5da4759d7 @@ -883,7 +931,7 @@ F test/conflict2.test 5557909ce683b1073982f5d1b61dfb1d41e369533bfdaf003180c5bc87 F test/conflict3.test 81865d9599609aca394fb3b9cd5f561d4729ea5b176bece3644f6ecb540f88ac F test/contrib01.test 2a1cbc0f2f48955d7d073f725765da6fbceda6b4 F test/corrupt.test d7cb0300e4a297147b6a05e92a1684bc8973635c3bcaa3d66e983c9cbdbf47a3 -F test/corrupt2.test 6e0c1e1c2ff4bedde4bc73f16250d74ae5b3d9ece086640ce88b9a94620ba993 +F test/corrupt2.test 9745c55b3ff2d84d9b6dc4f7365f91a99e70d90f3127ebc97ff0549c418e4d3d F test/corrupt3.test 6a982535d52c8165654cbc79a043cfd0bf02495a5efbf4754295e056fc548539 F test/corrupt4.test b5ae41607e8d17d9c1f3e94fdb572ce061ed3beeebdb46fb3a348181b8c8a097 F test/corrupt5.test 387be3250795e2a86e6234745558b80efb248a357d0cd8e53bce75c7463f545d @@ -902,7 +950,7 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test 9d8cbf6214e492abe9e822e759b9751ae336cec0a6fe3ff3b37bfbd8ff9c22ca F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test b42978028afc5eefc8b51d8d7cd6a9344ba7362d7ed4511ee2070f56e06d5a1c +F test/corruptL.test 504d90502d9993440226edc355d2275524b89064ea3df5ee5c27f7028ec59d07 F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 F test/corruptN.test 7c099d153a554001b4fb829c799b01f2ea6276cbc32479131e0db0da4efd9cc4 F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 @@ -938,7 +986,7 @@ F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e8 F test/dbpagefault.test d9111a62f3601d3efc6841ace3940181937342d245f92a1cca6cba8206d4f58a F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef -F test/decimal.test fcf403fd5585f47342234e153c4a4338cd737b8e0884ac66fc484df47dbcf1a7 +F test/decimal.test ef731887b43ee32ef86e1c8fddb61a40789f988332c029c601dcf2c319277e9e F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50 F test/delete.test 2686e1c98d552ef37d79ad55b17b93fe96fad9737786917ce3839767f734c48f F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa @@ -1013,41 +1061,9 @@ F test/fkey8.test 51deda7f1a1448bca95875e4a6e1a3a75b4bd7215e924e845bd60de60e4d84 F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749 F test/fordelete.test ba98f14446b310f9c9d935b97ec748753d0144a28b356ba30d1f4f6958fdde5c F test/format4.test eeae341953db8b6bda7f549044797c3278a6cc345d11ada81471671b654f8ef4 +F test/fp-speed-1.c ca95152d579530c967240ead1387dc1208052d310dfa22bc9ebded56d4e6c026 +F test/fpconv1.test d5d8aa0c427533006c112fb1957cdd1ea68c1d0709470dabb9ca02c2e4c06ad8 F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c -F test/fts1a.test 46090311f85da51bb33bd5ce84f7948359c6d8d7 -F test/fts1b.test 5d8a01aefbecc8b7442b36c94c05eb7a845462d5 -F test/fts1c.test 85a525ce7428907469b4cce13d5563ce542ce64c -F test/fts1d.test a73deace5c18df4a549b12908bade4f05dcf1a2f -F test/fts1e.test 77244843e925560b5a0b70069c3e7ab62f181ed2 -F test/fts1f.test 2d6cb10d8b7a4e6edc321bbdb3982f1f48774714 -F test/fts1i.test 6bfe08cdfdced063a39a50c8601da65e6274d879 -F test/fts1j.test e3797475796043a161e348c46a309664cac83f7f -F test/fts1k.test 65d3b41487b9f738d11b0f00eca375c0ca6bd970 -F test/fts1l.test 15c119ed2362b2b28d5300c0540a6a43eab66c36 -F test/fts1m.test 2d9ca67b095d49f037a914087cc0a61e89da4f0c -F test/fts1n.test a2317dcd27b1d087ee3878b30e0a59c593c98b7a -F test/fts1o.test d1554caede42bba2c82fe613bcc921856c196b752449ead0470fac52a20fd3b8 -F test/fts1porter.test d86e9c3e0c7f8ff95add6582b4b585fb4e02b96d -F test/fts2.test e3fb95f96a650411574efc136f3fb10eef479ed7 -F test/fts2a.test 473a5c8b473a4e21a8e3fddaed1e59666e0c6ab7 -F test/fts2b.test 964abc0236c849c07ca1ae496bb25c268ae94816 -F test/fts2c.test ffb5a35230ac72c4354535c547965ce6824537c0 -F test/fts2d.test b7eaa671ca9a16997f3e5b158ee777ae21052b0b -F test/fts2e.test 2da13dbc2d009105f42196845c1e1ce136c03d38 -F test/fts2f.test cf84096235991709c1e61caa389632aa0a4f976d -F test/fts2g.test 3d26fe171bda6133ebf5a380731d70eaa2ef2f6f73d79769cf8946e622b6d597 -F test/fts2h.test 223af921323b409d4b5b18ff4e51619541b174bb -F test/fts2i.test 1b22451d1f13f7c509baec620dc3a4a754885dd6 -F test/fts2j.test 298fa1670aa21cd445b282d139b70c72e7ade12b -F test/fts2k.test c7ebf4a4937594aa07459e3e1bca1251c1be8659 -F test/fts2l.test 3333336621524cf7d60bb62d6ef6ab69647866ed -F test/fts2m.test 4b30142ead6f3ed076e880a2a464064c5ad58c51 -F test/fts2n.test 12b9c5352128cebd1c6b8395e43788d4b09087c2 -F test/fts2o.test 4054ac7433eb5440f1b1d200cfa449342dc4aabd991759139813e17c73e5bf9a -F test/fts2p.test 4b48c35c91e6a7dbf5ac8d1e5691823cc999aafb -F test/fts2q.test b2fbbe038b7a31a52a6079b215e71226d8c6a682 -F test/fts2r.test b154c30b63061d8725e320fba1a39e2201cadd5e -F test/fts2token.test d8070b241a15ff13592a9ae4a8b7c171af6f445a F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654 F test/fts3_common.tcl dffad248f9ce090800e272017d2898005c28ee6314fc1dd5550643a02666907a F test/fts3aa.test 814d60a1ba30b4a71d8f9306a6564bc7b636dd6efacd2ad80306f9b23ef3ebee @@ -1140,7 +1156,7 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test 4be8bed4be235e333f1e0ea31e32f5be3c9f456c30780363e7fcb15e3ff3e6bc +F test/func.test 2acd982669b2c15ceccc046385b458e985f90c9ca58b14d0ef75eec79c32f45b F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 @@ -1155,15 +1171,15 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 0d90cee9fd7ebecdfbdbe5bdc6fad92fa32410c48ccad747cd9e0ac603b9130c +F test/fuzzcheck.c 69b8549e112fb815931a8c14c7955a0c407ae91a79356eecb82458384f2cb989 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e42ed2 F test/fuzzdata5.db e35f64af17ec48926481cfaf3b3855e436bd40d1cfe2d59a9474cb4b748a52a5 -F test/fuzzdata6.db 92a80e4afc172c24f662a10a612d188fb272de4a9bd19e017927c95f737de6d7 +F test/fuzzdata6.db b8725a5f5cf7a3b7241a9038e57ca7e7cc8c3f4d86b44bd770617bda245ab2b0 F test/fuzzdata7.db 0166b56fd7a6b9636a1d60ef0a060f86ddaecf99400a666bb6e5bbd7199ad1f2 -F test/fuzzdata8.db f6c2f2af4deaaae0ddb3310d509c2659990794aa653dc501b80a0534c3493f80 +F test/fuzzdata8.db 40c85daae47da64387c3dab7bbd99c21e425c0bfdb4b149cb685b1ab474a2cb4 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc @@ -1235,41 +1251,41 @@ F test/join.test f7abfef3faeaf2800308872e33a57e5b6e4a2b44fb8c6b90c6068412e71a6cf F test/join2.test 8561fe82ce434ac96de91544072e578dc2cadddf2d9bc9cd802f866a9b92502e F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 -F test/join5.test 91f1f4c7d81fd87b58e9ba7cf4a2b5d39e3583b4f8e498a162722a60259c5208 +F test/join5.test 7cc3f3595bb41e60f3f96d5cb6dd8cfcbc31212f0136bba6fc081c082994a94a F test/join6.test f809c025fa253f9e150c0e9afd4cef8813257bceeb6f46e04041228c9403cc2c F test/join7.test 2268dcbb54b724391dda3748ea95c60d960607ffeed67885675998e7117697f6 F test/join8.test d384d63985e3991c404afccadaf3efd1cdf9cd72680167f80e3cb80b95c18c68 F test/join9.test 9056ddd3b0c0f4f9d658f4521038d9a37dc23ead8ca9a505d0b0db2b6a471e05 -F test/joinA.test 7eab225dc1c1ab258a5e62513a4ed7cabbd3db971d59d5d92f4fb6fa14c12f6a +F test/joinA.test 6ac4efdbb1eb9ca398162c5bc5623a757803b04bb4d76453c8563a0bdc2f73bd F test/joinB.test 1b2ba3fc8568b49411787fccbf540570c148e9b6a53a30f80691cb6268098ded F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f207 F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28 F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 -F test/joinH.test 705157cf9b9b7c207caf960812a7d0e4dc1dd45aa5fb2b563f12df59088645f3 +F test/joinH.test c9550bb6a0257cf99668a28485bb309bac542081702e89261b95542ab5f676b1 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e F test/jrnlmode.test 9b5bc01dac22223cb60ec2d5f97acf568d73820794386de5634dcadbea9e1946 F test/jrnlmode2.test 8759a1d4657c064637f8b079592651530db738419e1d649c6df7048cd724363d F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa -F test/json/README.md 506af1f54574b524106acb50d1a341ab5ddfa6d83fe25095007892b07e663e85 +F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35e30ee2 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f -F test/json/json-q1.txt 335a7c8ab291d354f33b7decc9559e99a2823d4142291c4be7aa339a631f3c2d +F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x -F test/json101.test 94126d4291d4a00e45f6988ce885c410de69243490e46e70e9946cb6e6f9ea02 -F test/json102.test 13dc9e7b7f359ecb861e02f9bd7019f7342a63d1c354273b0a8f3904050560a8 +F test/json101.test dc9d5a2a5b1fd1b54dbd71c538b17933cc98d84b4c1f821ead754933663dca55 +F test/json102.test 24f6f204f9cde45b971016691d0b92a9b4c58040d699e36d6b12cb165f9083ff F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d F test/json501.test f71710f60fa45b19dc336fbaac9e8362f70f80cf81badefdb845ed3f7c7c2ccc F test/json502.test 98c38e3c4573841028a1381dfb81d4c3f9b105d39668167da10d055e503f6d0b F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff -F test/kvtest.c feb4358fb022da8ebd098c45811f2f6507688bb6c43aa72b3e840df19026317b +F test/kvtest.c 6e0228409ea7ca0497dad503fbd109badb5e59545d131014b6aaac68b56f484a F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 F test/lemon-test01.y 58b764610fd934e189ffbb0bbfa33d171b9cb06019b55bdc04d090d6767e11d7 -F test/like.test 5fe0bc37f307aef0a453ce2de4632bdfc0759448f0421c39f6d53caefe905fac +F test/like.test 242ee7f5d08a031144c0daf63bbd7e7710c847ccf387a83347e0b61b3aa69526 F test/like2.test d3be15fefee3e02fc88942a9b98f26c5339bbdef7783c90023c092c4955fe3d3 F test/like3.test a76e5938fadbe6d32807284c796bafd869974a961057bc5fc5a28e06de98745c F test/limit.test 350f5d03c29e7dff9a2cde016f84f8d368d40bcd02fa2b2a52fa10c4bf3cbfaf @@ -1395,7 +1411,7 @@ F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/pendingrace.test cbdf0f74bc939fb43cebad64dda7a0b5a3941a10b7e9cc2b596ff3e423a18156 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 8bd6b6db541e2a7f9bb894be99ef5c00526b23762c4a00c574e1cba697495125 +F test/permutations.test f7caf8dd5c7b1da74842a48df116f7f193399c656d4ffc805cd0d9658568c675 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f F test/pragma.test 57a36226218c03cfb381019fe43234b2cefbd8a1f12825514f906a17ccf7991e F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f @@ -1424,7 +1440,7 @@ F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0 F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest_data.tcl b550dd1b122a9c969df794d05ea272df535f10ff1a245062e7ba080822378016 +F test/releasetest_data.tcl 80ef3941bf7ad136f4dc3d8960786f10a4f488797238171bdd757cc6eb4c3efa F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb F test/returning1.test db532cde29d6aebbc48c6ddc3149b30476f8e69ca7a2c4b53986c7635e6fd8ec @@ -1432,7 +1448,7 @@ F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb076793 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a -F test/round1.test 1bb32cf3fc505eed9e86b5e523d07e15d4428189665524587512fbcc85d114bb +F test/round1.test 29c3c9039936ed024d672f003c4d35ee11c14c0acb75c5f7d6188ff16190cfd4 F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test e29025be95baf6b32f0d5edef59a7633028325896a98f1caa8019559ca910350 @@ -1458,7 +1474,7 @@ F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7 F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test b249328caf4d317e71058006872b8012598a5fa045b30bf24a81eeff650ab49e -F test/scanstatus2.test 9a00becb1d60d168944f8e2f3585d44d46123aa95c5c7c25563309e1f15f924d +F test/scanstatus2.test d0434bc3b356fb9d948f3417846b2ed5bbc4bd4cc49bddb38ac86469f754bfb0 F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce @@ -1502,7 +1518,7 @@ F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 F test/shell1.test 300b77328aaafb9f3e7a53a26e4162fbf92181d92251d259ff105a2275ff998d -F test/shell2.test 09a202f57e7cd99788537f763e0845796a173fcea06a0d199a08d69446fe1daf +F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b @@ -1523,12 +1539,12 @@ F test/snapshot.test a504f2e7009f512ef66c719f0ea1c55a556bdaf1e1312c80a04d46fc1a3 F test/snapshot2.test 8d6ff5dd9cc503f6e12d408a30409c3f9c653507b24408d9cd7195931c89bc54 F test/snapshot3.test 8744313270c55f6e18574283553d3c5c5fe4c5970585663613a0e75c151e599b F test/snapshot4.test d4e9347ef2fcabc491fc893506c7bbaf334da3be111d6eb4f3a97cc623b78322 -F test/snapshot_fault.test f6c5ef7cb93bf92fbb4e864ecc5c87df7d3a250064838822db5b4d3a5563ede4 +F test/snapshot_fault.test 129234ceb9b26a0e1000e8563a16e790f5c1412354e70749cbd78c3d5d07d60a F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a393cdd41c F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test f86751134159abb5e5fd4381a0d7038c91013638cd1e3fa1d7850901f6df6196 -F test/sort2.test cc23b7c19d684657559e8a55b02f7fcee03851d0 +F test/sort2.test 2f8c66402a03adebe77ce7aafca129fbf32df27d6c9b8f7a9f1b958e39f56da8 F test/sort3.test 1480ed7c4c157682542224e05e3b75faf4a149e5 F test/sort4.test cca6f4b0b5255882645bbbe346a6a9f4a5c7b6a18513a6a7bf4ac1c4761ddc19 F test/sort5.test 6b43ae0e2169b5ceed441844492e55ba7f1ae0790528395ddf7888ab3094525d @@ -1542,12 +1558,12 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest1.c 9703465daf778dd90bad7adadc1d2ee205ec90a79437cababab0a4111190f0c8 +F test/speedtest1.c f9505ad6f9c2c3c488a370a2d193e9603a030e51126ef3ecfeb056d21f0e7ad5 F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb -F test/sqldiff1.test 182058e09c7082de5c6a470ff9c291337bbeb650052c2cc68fbb3d7e25861d91 +F test/sqldiff1.test 1b7ab4f312442c5cc6b3a5f299fa8ca051416d1dd173cb1126fd51bf64f2c3fb F test/sqllimits1.test b28e5cc8d337aaf290614d96a47e8fbfb720bb7ad35620c9d5432996fd413ac4 F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805 @@ -1587,8 +1603,8 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede -F test/testrunner.tcl 59490f189cac99b16b0376d0cc0a7ecfb753a84b89c9f4c361af337d88db53ac -F test/testrunner_data.tcl 8169c68654ac8906833b8a6aadca973358a441ebf88270dd05c153e5f96f76b8 +F test/testrunner.tcl a9fee4df57276bc9e446961b160068c269da5902cc8ffc3e8852d77626b7594c +F test/testrunner_data.tcl c448693eb6fdbadb78cb26f6253d4f335666f9836f988afa575de960b666b19f F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1604,6 +1620,7 @@ F test/threadtest3.c 655bff6c0895ec03f014126aa65e808fac9aae8c5a7a7da58a510cbe8b4 F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925 F test/threadtest5.c 9b4d782c58d8915d7e955ff8051f3d03628bda0d33b82971ea8c0f2f2808c421 F test/time-wordcount.sh 8e0b0f8109367827ad5d58f5cc849705731e4b90 +F test/timediff1.test d982b2b5f1b22f58380c5db94ea5b17518d50ad0c55583cf0ecfa0b176e20888 F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c F test/tkt-18458b1a.test 6a62cb1ee50fa3c620da59e3a6f531eb38fceaf7e2166203816b724524e6f1d6 F test/tkt-26ff0c2d1e.test c15bec890c4d226c0da2f35ff30f9e84c169cfef90e73a8cb5cec11d723dfa96 @@ -1794,15 +1811,15 @@ F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264 F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2 F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97 F test/unordered.test 0edaf3411d300693bca595897c5201421c6c5ec787990a1dfe2f7f60ae93f1e2 -F test/update.test 90772ede84cfc779fc3d31884a84ec4c74deb501eeb09a1c6c91c03d8e94c0d8 +F test/update.test 85d3f46d0863033370bd881b1097f5694369a8730e53c5f85d96f32b7b310b47 F test/update2.test 67455bc61fcbcf96923c45b3bc4f87bc72be7d67575ad35f134906148c7b06d3 F test/upfrom1.tcl 8859d9d437f03b44174c4524a7a734a391fd4526fcff65be08285dafc9dc9041 F test/upfrom1.test 8cb06689e99cd707d884faa16da0e8eb26ff658bb01c47ddf72fadade666e6e1 F test/upfrom2.test 66f3ebf721b3cebd922faee5c386bf244f816d416b57c000753ff51af62328a1 F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0 -F test/upfrom4.test 1cd82e9423e02b1f63d069e8665c6c3932ec424fd0043d033cc0ba99abf33236 +F test/upfrom4.test 78f742a6577c91a7a55c64edb8811004e7c6aa99b8d57b2320f70a918c357807 F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6 -F test/upsert1.test b0ae2f58680c5205b4bc1cdeed3c3d444057c506f6c44494fa3eac60731d68a2 +F test/upsert1.test a512e2f884d3a36159fce2e45108c236f78ae38e35bda55f4050db580ceb25d3 F test/upsert2.test 720e94d09f7362a282bc69b3c6b83d51daeaaf0440eb4920a08b86518b8c7496 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 @@ -1882,6 +1899,7 @@ F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db8 F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20 F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 +F test/walseh1.test 82da37763b0d87942dccd191e58321532ce3d44b87ef36e04ff9ce13f382bbae F test/walsetlk.test 34c901443b31ab720afc463f5b236c86ca5c4134402573dce91aa0761de8db5a F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370dbc7a3 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f @@ -1895,7 +1913,7 @@ F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b -F test/where7.test 1c1bf436bf31b913d4764a2b62ac6e98b9681e5c7ae2b562605592a56b7e946b +F test/where7.test 15041c7a5838f3bac98f3fb933709674a0b59367664e88fafaf105ff7416eb07 F test/where8.test 461ca40265ed996a6305da99bb024b0e41602bb586acf544c08f95922358e49f F test/where9.test 2db942671a002621eff4f713e347bb25243295f79d8990297cd160bebcfde3f7 F test/whereA.test 9d1077b117f1b68d5f739d94f36956c36cf995eb87bb19b77b2e81af020edd20 @@ -1914,13 +1932,13 @@ F test/whereM.test 0dbc9998783458ddcf3cc078ca7c2951d8b2677d472ecf0028f449ed327c0 F test/wherefault.test 6cf2a9c5712952d463d3f45ebee7f6caf400984df51a195d884cfb7eb0e837a7 F test/wherelfault.test 9012e4ef5259058b771606616bd007af5d154e64cc25fa9fd4170f6411db44e3 F test/wherelimit.test afb46397c6d7e964e6e294ba3569864a0c570fe3807afc634236c2b752372f31 -F test/wherelimit2.test 657a3f24aadee62d058c5091ea682dc4af4b95ffe32f137155be49799a58e721 +F test/wherelimit2.test b9e4bfe7b4d7c2f85f99cf2bd2c51369378d04b1f3d1b60557423752003bfd90 F test/widetab1.test c296a98e123762de79917350e45fa33fdf88577a2571eb3a64c8bf7e44ef74d1 F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2aeee74 F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test 5ba48e9d33231e6ef16f21426bade9ccc52abf65a10587bff90a6c14fe174594 +F test/window1.test 1e7e13d36235b9a08fcb9790f2b05383f2f8c9538532b027f455766686926114 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -1969,9 +1987,10 @@ F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb F tool/GetTclKit.bat d84033c6a93dfe735d247f48ba00292a1cc284dcf69963e5e672444e04534bbf F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91 F tool/build-all-msvc.bat c817b716e0edeecaf265a6775b63e5f45c34a6544f1d4114a222701ed5ac79ab x -F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367 +F tool/build-shell.sh f193b5e3eb4afcb4abbf96bf1475be6cfb74763ee2e50c82bc7ca105e8a136c5 F tool/cg_anno.tcl c1f875f5a4c9caca3d59937b16aff716f8b1883935f1b4c9ae23124705bc8099 x F tool/checkSpacing.c 810e51703529a204fc4e1eb060e9ab663e3c06d2 +F tool/custom.txt 6cdf298f43e1db4bb91406d14777669b8fb1df790837823fa6754c4308decc27 F tool/dbhash.c 5da0c61032d23d74f2ab84ffc5740f0e8abec94f2c45c0b4306be7eb3ae96df0 F tool/dbtotxt.c ca48d34eaca6d6b6e4bd6a7be2b72caf34475869054240244c60fa7e69a518d6 F tool/dbtotxt.md c9a57af8739957ef36d2cfad5c4b1443ff3688ed33e4901ee200c8b651f43f3c @@ -1986,7 +2005,7 @@ F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5 F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f -F tool/lemon.c ea5c8589c7749e9bd32ba10432aeeed3c16e215de72a12ada2bc707884837149 +F tool/lemon.c 19e368bc8e97ff4071115119a7911ca3b0c56eba7926d8ada8b4a86fcc69a176 F tool/lempar.c 57478ea48420da05faa873c6d1616321caa5464644588c97fbe8e0ea04450748 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 @@ -1995,8 +2014,8 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl 38e3db33210a200aae791635125052a643a27aa0619a0debf19aa9c55e1b2dde x -F tool/mkkeywordhash.c 9822bd1f58a70e5f84179df3045bba4791df69180e991bec88662703f2e93761 +F tool/mkctimec.tcl aca4b83e49aecf10368cd5d11bc4847061041ade026db5bd8da17ef201f1403b x +F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef F tool/mkopcodeh.tcl 769d9e6a8b462323150dc13a8539d6064664b72974f7894befe2491cc73e05cd @@ -2006,7 +2025,7 @@ F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f -F tool/mksqlite3c.tcl eb47021591b1ad4a6862e2cb5625f1ac67ec1e0c6db5ba3953c069c635284cf5 +F tool/mksqlite3c.tcl 49e39b1e616abc92fd8c24445f2b0a38881825f764541e0026f72371f0d84b65 F tool/mksqlite3h.tcl d391cff7cad0a372ee1406faee9ccc7dad9cb80a0c95cae0f73d10dd26e06762 F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 @@ -2027,17 +2046,19 @@ F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c F tool/showwal.c 65ecabae3a2dcff4116301d5a8dbb8c4964814da1b2aff6d85c806a88b71fa4e F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe F tool/spaceanal.tcl 1b5be34c6223cb1af06da2a10fb77863eb869b1962d055820b0a11cf2336ab45 -F tool/speed-check.sh c24c30cddd0ecb6d1d0775411d22f7619f55fa696a305487645e27b1b8fc05a2 +F tool/speed-check.sh 72dc85b2c0484af971ee3e7d10775f72b4e771e27e162c2099b3bf25517c25fb F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd +F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x F tool/split-sqlite3c.tcl 5aa60643afca558bc732b1444ae81a522326f91e1dc5665b369c54f09e20de60 -F tool/sqldiff.c 4f967c199c5f93eec64978e3a625d6c07fb1162212b1d48f65740d9eb4607eee +F tool/sqldiff.c efe73c6706b0ead9b4e415c684e4abf439e5f405793ae6ba50796e0e4a030349 F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 +F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37 @@ -2046,8 +2067,9 @@ F tool/symbols.sh 1612bd947750e21e7b47befad5f6b3825b06cce0705441f903bf35ced65ae9 F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003 F tool/vdbe-compress.tcl 1dcb7632e57cf57105248029e6e162fddaf6c0fccb3bb9e6215603752c5a2d4a F tool/vdbe_profile.tcl 3ac5a4a9449f4baf77059358ea050db3e34395ccf59c5464d29b91746d5b961e +F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 -F tool/warnings.sh ab651bb82586c43ff8b560beceac959735bf917b44c5e0f67ba3426e474f29f8 +F tool/warnings.sh 49a486c5069de041aedcbde4de178293e0463ae9918ecad7539eedf0ec77a139 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F vsixtest/App.xaml b76d3b48860e7454775c47ea38ffea9c4abe3e85 F vsixtest/App.xaml.cpp 41158ee43269820136fa3bba00c0bd91b26cc38b650ee392aec2a8d823e54318 @@ -2070,10 +2092,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4254f086419892634c80b0b915e14edea365adc6a45bcc75eecba889da274ac3 -R 5363a07f3e1462ec2f4570a33edb7167 +P 3f5bec9a684f6b3d88383617831862fc5595a51ad99ec12430ac6650b704ccbb +R 0c01ee5f49ac8bbf7d5b3e02bf525f93 +T +sym-major-release * T +sym-release * -T +sym-version-3.42.0 * +T +sym-version-3.43.0 * U drh -Z 0ee8e0816fa6ac728bed7565aed4ec35 +Z 2bd48930e1a5ff814b90ece6e1f4c0d0 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f9f720edb..3b7fefa60 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0 +0f80b798b3f4b81a7bb4233c58294edd0f1156f36b6ecf5ab8e83631d468778c diff --git a/mkso.sh b/mkso.sh deleted file mode 100644 index 6f2e8e25e..000000000 --- a/mkso.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -# -# This script is used to compile SQLite into a shared library on Linux. -# -# Two separate shared libraries are generated. "sqlite3.so" is the core -# library. "tclsqlite3.so" contains the TCL bindings and is the -# library that is loaded into TCL in order to run SQLite. -# -make target_source -cd tsrc -rm shell.c -TCLDIR=/home/drh/tcltk/846/linux/846linux -TCLSTUBLIB=$TCLDIR/libtclstub8.4g.a -OPTS='-DUSE_TCL_STUBS=1 -DNDEBUG=1 -DHAVE_DLOPEN=1' -OPTS="$OPTS -DSQLITE_THREADSAFE=1" -OPTS="$OPTS -DSQLITE_ENABLE_FTS3=1" -OPTS="$OPTS -DSQLITE_ENABLE_COLUMN_METADATA=1" -for i in *.c; do - if test $i != 'keywordhash.c'; then - CMD="cc -fPIC $OPTS -O2 -I. -I$TCLDIR -c $i" - echo $CMD - $CMD - fi -done -echo gcc -shared *.o $TCLSTUBLIB -o tclsqlite3.so -gcc -shared *.o $TCLSTUBLIB -o tclsqlite3.so -strip tclsqlite3.so -rm tclsqlite.c tclsqlite.o -echo gcc -shared *.o -o sqlite3.so -gcc -shared *.o -o sqlite3.so -strip sqlite3.so -cd .. diff --git a/sqlite3.1 b/sqlite3.1 index 80353b0ee..08b1ff262 100644 --- a/sqlite3.1 +++ b/sqlite3.1 @@ -2,7 +2,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH SQLITE3 1 "Fri Oct 31 10:41:31 EDT 2014" +.TH SQLITE3 1 "Fri Aug 11 23:50:12 CET 2023" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -49,9 +49,9 @@ a table named "memos" and insert a couple of records into that table: $ .B sqlite3 mydata.db .br -SQLite version 3.8.8 +SQLite version 3.43.0 2023-08-11 17:45:23 .br -Enter ".help" for instructions +Enter ".help" for usage hints. .br sqlite> .B create table memos(text, priority INTEGER); @@ -108,141 +108,13 @@ sqlite> .B .help .nf .tr %. -%backup ?DB? FILE Backup DB (default "main") to FILE -%bail on|off Stop after hitting an error. Default OFF -%clone NEWDB Clone data into NEWDB from the existing database -%databases List names and files of attached databases -%dump ?TABLE? ... Dump the database in an SQL text format - If TABLE specified, only dump tables matching - LIKE pattern TABLE. -%echo on|off Turn command echo on or off -%eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN -%exit Exit this program -%explain ?on|off? Turn output mode suitable for EXPLAIN on or off. - With no args, it turns EXPLAIN on. -%fullschema Show schema and the content of sqlite_stat tables -%headers on|off Turn display of headers on or off -%help Show this message -%import FILE TABLE Import data from FILE into TABLE -%indices ?TABLE? Show names of all indices - If TABLE specified, only show indices for tables - matching LIKE pattern TABLE. -%load FILE ?ENTRY? Load an extension library -%log FILE|off Turn logging on or off. FILE can be stderr/stdout -%mode MODE ?TABLE? Set output mode where MODE is one of: - csv Comma-separated values - column Left-aligned columns. (See .width) - html HTML
    code - insert SQL insert statements for TABLE - line One value per line - list Values delimited by .separator string - tabs Tab-separated values - tcl TCL list elements -%nullvalue STRING Use STRING in place of NULL values -%once FILENAME Output for the next SQL command only to FILENAME -%open ?FILENAME? Close existing database and reopen FILENAME -%output ?FILENAME? Send output to FILENAME or stdout -%print STRING... Print literal STRING -%prompt MAIN CONTINUE Replace the standard prompts -%quit Exit this program -%read FILENAME Execute SQL in FILENAME -%restore ?DB? FILE Restore content of DB (default "main") from FILE -%save FILE Write in-memory database into FILE -%schema ?TABLE? Show the CREATE statements - If TABLE specified, only show tables matching - LIKE pattern TABLE. -%separator STRING ?NL? Change separator used by output mode and .import - NL is the end-of-line mark for CSV -%shell CMD ARGS... Run CMD ARGS... in a system shell -%show Show the current values for various settings -%stats on|off Turn stats on or off -%system CMD ARGS... Run CMD ARGS... in a system shell -%tables ?TABLE? List names of tables - If TABLE specified, only list tables matching - LIKE pattern TABLE. -%timeout MS Try opening locked tables for MS milliseconds -%timer on|off Turn SQL timer on or off -%trace FILE|off Output each SQL statement as it is run -%vfsname ?AUX? Print the name of the VFS stack -%width NUM1 NUM2 ... Set column widths for "column" mode - Negative values right-justify -sqlite> +... .sp .fi -.SH OPTIONS -.B sqlite3 -has the following options: -.TP -.B \-bail -Stop after hitting an error. -.TP -.B \-batch -Force batch I/O. -.TP -.B \-column -Query results will be displayed in a table like form, using -whitespace characters to separate the columns and align the -output. -.TP -.BI \-cmd\ command -run -.I command -before reading stdin -.TP -.B \-csv -Set output mode to CSV (comma separated values). -.TP -.B \-echo -Print commands before execution. -.TP -.BI \-init\ file -Read and execute commands from -.I file -, which can contain a mix of SQL statements and meta-commands. -.TP -.B \-[no]header -Turn headers on or off. -.TP -.B \-help -Show help on options and exit. -.TP -.B \-html -Query results will be output as simple HTML tables. -.TP -.B \-interactive -Force interactive I/O. -.TP -.B \-line -Query results will be displayed with one value per line, rows -separated by a blank line. Designed to be easily parsed by -scripts or other programs -.TP -.B \-list -Query results will be displayed with the separator (|, by default) -character between each field value. The default. -.TP -.BI \-mmap\ N -Set default mmap size to -.I N -\. -.TP -.BI \-nullvalue\ string -Set string used to represent NULL values. Default is '' -(empty string). -.TP -.BI \-separator\ separator -Set output field separator. Default is '|'. -.TP -.B \-stats -Print memory stats before each finalize. -.TP -.B \-version -Show SQLite version. -.TP -.BI \-vfs\ name -Use -.I name -as the default VFS. + +The available commands differ by version and build options, so they +are not listed here. Please refer to your local copy for all available +options. .SH INIT FILE @@ -265,22 +137,25 @@ continue prompt = " ...> " .sp .fi -o If the file +o If the file +.B ${XDG_CONFIG_HOME}/sqlite3/sqliterc +or .B ~/.sqliterc -exists, it is processed first. -can be found in the user's home directory, it is -read and processed. It should generally only contain meta-commands. +exists, the first of those to be found is processed during startup. +It should generally only contain meta-commands. o If the -init option is present, the specified file is processed. o All other command line options are processed. .SH SEE ALSO -http://www.sqlite.org/cli.html +https://sqlite.org/cli.html +.br +https://sqlite.org/fiddle (a WebAssembly build of the CLI app) .br The sqlite3-doc package. .SH AUTHOR This manual page was originally written by Andreas Rottmann , for the Debian GNU/Linux system (but may be used -by others). It was subsequently revised by Bill Bumgarner and -further updated by Laszlo Boszormenyi . +by others). It was subsequently revised by Bill Bumgarner , +Laszlo Boszormenyi , and the sqlite3 developers. diff --git a/src/alter.c b/src/alter.c index 121b61711..d0b1f7f69 100644 --- a/src/alter.c +++ b/src/alter.c @@ -58,7 +58,7 @@ static void renameTestSchema( int bNoDQS /* Do not allow DQS in the schema */ ){ pParse->colNamesSet = 1; - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "SELECT 1 " "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" @@ -69,7 +69,7 @@ static void renameTestSchema( ); if( bTemp==0 ){ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "SELECT 1 " "FROM temp." LEGACY_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" @@ -82,20 +82,20 @@ static void renameTestSchema( /* ** Generate VM code to replace any double-quoted strings (but not double-quoted -** identifiers) within the "sql" column of the sqlite_schema table in +** identifiers) within the "sql" column of the sqlite_schema table in ** database zDb with their single-quoted equivalents. If argument bTemp is ** not true, similarly update all SQL statements in the sqlite_schema table ** of the temp db. */ static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){ - sqlite3NestedParse(pParse, - "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET sql = sqlite_rename_quotefix(%Q, sql)" "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb ); if( bTemp==0 ){ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE temp." LEGACY_SCHEMA_TABLE " SET sql = sqlite_rename_quotefix('temp', sql)" "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" @@ -118,8 +118,8 @@ static void renameReloadSchema(Parse *pParse, int iDb, u16 p5){ } /* -** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" -** command. +** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" +** command. */ void sqlite3AlterRenameTable( Parse *pParse, /* Parser context. */ @@ -129,7 +129,7 @@ void sqlite3AlterRenameTable( int iDb; /* Database that contains the table */ char *zDb; /* Name of database iDb */ Table *pTab; /* Table being renamed */ - char *zName = 0; /* NULL-terminated version of pName */ + char *zName = 0; /* NULL-terminated version of pName */ sqlite3 *db = pParse->db; /* Database connection */ int nTabName; /* Number of UTF-8 characters in zTabName */ const char *zTabName; /* Original name of the table */ @@ -156,7 +156,7 @@ void sqlite3AlterRenameTable( || sqlite3FindIndex(db, zName, zDb) || sqlite3IsShadowTableOf(db, pTab, zName) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "there is already another table or index with this name: %s", zName); goto exit_rename_table; } @@ -199,7 +199,7 @@ void sqlite3AlterRenameTable( /* Begin a transaction for database iDb. Then modify the schema cookie ** (since the ALTER TABLE modifies the schema). Call sqlite3MayAbort(), - ** as the scalar functions (e.g. sqlite_rename_table()) invoked by the + ** as the scalar functions (e.g. sqlite_rename_table()) invoked by the ** nested SQL may raise an exception. */ v = sqlite3GetVdbe(pParse); if( v==0 ){ @@ -213,7 +213,7 @@ void sqlite3AlterRenameTable( /* Rewrite all CREATE TABLE, INDEX, TRIGGER or VIEW statements in ** the schema to use the new table name. */ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, %d) " "WHERE (type!='index' OR tbl_name=%Q COLLATE nocase)" @@ -233,14 +233,14 @@ void sqlite3AlterRenameTable( "'sqlite_autoindex_' || %Q || substr(name,%d+18) " "ELSE name END " "WHERE tbl_name=%Q COLLATE nocase AND " - "(type='table' OR type='index' OR type='trigger');", + "(type='table' OR type='index' OR type='trigger');", zDb, - zName, zName, zName, + zName, zName, zName, nTabName, zTabName ); #ifndef SQLITE_OMIT_AUTOINCREMENT - /* If the sqlite_sequence table exists in this database, then update + /* If the sqlite_sequence table exists in this database, then update ** it with the new table name. */ if( sqlite3FindTable(db, "sqlite_sequence", zDb) ){ @@ -251,10 +251,10 @@ void sqlite3AlterRenameTable( #endif /* If the table being renamed is not itself part of the temp database, - ** edit view and trigger definitions within the temp database + ** edit view and trigger definitions within the temp database ** as required. */ if( iDb!=1 ){ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE sqlite_temp_schema SET " "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), " "tbl_name = " @@ -361,7 +361,7 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ return; } if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){ - /* If the default value for the new column was specified with a + /* If the default value for the new column was specified with a ** literal NULL, then set pDflt to 0. This simplifies checking ** for an SQL NULL default below. */ @@ -414,11 +414,11 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ ** have to use printf() to translate between these units: */ assert( IsOrdinaryTable(pTab) ); assert( IsOrdinaryTable(pNew) ); - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = printf('%%.%ds, ',sql) || %Q" " || substr(sql,1+length(printf('%%.%ds',sql))) " - "WHERE type = 'table' AND name = %Q", + "WHERE type = 'table' AND name = %Q", zDb, pNew->u.tab.addColOffset, zCol, pNew->u.tab.addColOffset, zTab ); @@ -462,14 +462,14 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ /* ** This function is called by the parser after the table-name in -** an "ALTER TABLE ADD" statement is parsed. Argument +** an "ALTER TABLE ADD" statement is parsed. Argument ** pSrc is the full-name of the table being altered. ** ** This routine makes a (partial) copy of the Table structure ** for the table being altered and sets Parse.pNewTable to point ** to it. Routines called by the parser as the column definition -** is parsed (i.e. sqlite3AddColumn()) add the new Column data to -** the copy. The copy of the Table structure is deleted by tokenize.c +** is parsed (i.e. sqlite3AddColumn()) add the new Column data to +** the copy. The copy of the Table structure is deleted by tokenize.c ** after parsing is finished. ** ** Routine sqlite3AlterFinishAddColumn() will be called to complete @@ -542,7 +542,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pTab->u.tab.pDfltList, 0); pNew->pSchema = db->aDb[iDb].pSchema; pNew->u.tab.addColOffset = pTab->u.tab.addColOffset; - pNew->nTabRef = 1; + assert( pNew->nTabRef==1 ); exit_begin_add_column: sqlite3SrcListDelete(db, pSrc); @@ -571,7 +571,7 @@ static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ } #endif if( zType ){ - sqlite3ErrorMsg(pParse, "cannot %s %s \"%s\"", + sqlite3ErrorMsg(pParse, "cannot %s %s \"%s\"", (bDrop ? "drop column from" : "rename columns of"), zType, pTab->zName ); @@ -611,7 +611,7 @@ void sqlite3AlterRenameColumn( if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column; if( SQLITE_OK!=isRealTable(pParse, pTab, 0) ) goto exit_rename_column; - /* Which schema holds the table to be altered */ + /* Which schema holds the table to be altered */ iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iSchema>=0 ); zDb = db->aDb[iSchema].zDbSName; @@ -648,7 +648,7 @@ void sqlite3AlterRenameColumn( if( !zNew ) goto exit_rename_column; assert( pNew->n>0 ); bQuote = sqlite3Isquote(pNew->z[0]); - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' " @@ -658,7 +658,7 @@ void sqlite3AlterRenameColumn( pTab->zName ); - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE temp." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, 1) " "WHERE type IN ('trigger', 'view')", @@ -708,7 +708,7 @@ struct RenameCtx { RenameToken *pList; /* List of tokens to overwrite */ int nList; /* Number of tokens in pList */ int iCol; /* Index of column being renamed */ - Table *pTab; /* Table being ALTERed */ + Table *pTab; /* Table being ALTERed */ const char *zOld; /* Old column name */ }; @@ -716,14 +716,14 @@ struct RenameCtx { /* ** This function is only for debugging. It performs two tasks: ** -** 1. Checks that pointer pPtr does not already appear in the +** 1. Checks that pointer pPtr does not already appear in the ** rename-token list. ** ** 2. Dereferences each pointer in the rename-token list. ** ** The second is most effective when debugging under valgrind or -** address-sanitizer or similar. If any of these pointers no longer -** point to valid objects, an exception is raised by the memory-checking +** address-sanitizer or similar. If any of these pointers no longer +** point to valid objects, an exception is raised by the memory-checking ** tool. ** ** The point of this is to prevent comparisons of invalid pointer values. @@ -918,7 +918,7 @@ void sqlite3RenameExprUnmap(Parse *pParse, Expr *pExpr){ } /* -** Remove all nodes that are part of expression-list pEList from the +** Remove all nodes that are part of expression-list pEList from the ** rename list. */ void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){ @@ -959,8 +959,8 @@ static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ ** the list maintained by the RenameCtx object. */ static RenameToken *renameTokenFind( - Parse *pParse, - struct RenameCtx *pCtx, + Parse *pParse, + struct RenameCtx *pCtx, const void *pPtr ){ RenameToken **pp; @@ -1008,12 +1008,12 @@ static int renameColumnSelectCb(Walker *pWalker, Select *p){ */ static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ RenameCtx *p = pWalker->u.pRename; - if( pExpr->op==TK_TRIGGER - && pExpr->iColumn==p->iCol + if( pExpr->op==TK_TRIGGER + && pExpr->iColumn==p->iCol && pWalker->pParse->pTriggerTab==p->pTab ){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); - }else if( pExpr->op==TK_COLUMN + }else if( pExpr->op==TK_COLUMN && pExpr->iColumn==p->iCol && ALWAYS(ExprUseYTab(pExpr)) && p->pTab==pExpr->y.pTab @@ -1047,14 +1047,14 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ } /* -** An error occured while parsing or otherwise processing a database +** An error occurred while parsing or otherwise processing a database ** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an ** ALTER TABLE RENAME COLUMN program. The error message emitted by the ** sub-routine is currently stored in pParse->zErrMsg. This function ** adds context to the error message and then stores it in pCtx. */ static void renameColumnParseError( - sqlite3_context *pCtx, + sqlite3_context *pCtx, const char *zWhen, sqlite3_value *pType, sqlite3_value *pObject, @@ -1064,7 +1064,7 @@ static void renameColumnParseError( const char *zN = (const char*)sqlite3_value_text(pObject); char *zErr; - zErr = sqlite3MPrintf(pParse->db, "error in %s %s%s%s: %s", + zErr = sqlite3MPrintf(pParse->db, "error in %s %s%s%s: %s", zT, zN, (zWhen[0] ? " " : ""), zWhen, pParse->zErrMsg ); @@ -1074,14 +1074,14 @@ static void renameColumnParseError( /* ** For each name in the the expression-list pEList (i.e. each -** pEList->a[i].zName) that matches the string in zOld, extract the +** pEList->a[i].zName) that matches the string in zOld, extract the ** corresponding rename-token from Parse object pParse and add it ** to the RenameCtx pCtx. */ static void renameColumnElistNames( - Parse *pParse, - RenameCtx *pCtx, - const ExprList *pEList, + Parse *pParse, + RenameCtx *pCtx, + const ExprList *pEList, const char *zOld ){ if( pEList ){ @@ -1099,14 +1099,14 @@ static void renameColumnElistNames( } /* -** For each name in the the id-list pIdList (i.e. each pIdList->a[i].zName) -** that matches the string in zOld, extract the corresponding rename-token +** For each name in the the id-list pIdList (i.e. each pIdList->a[i].zName) +** that matches the string in zOld, extract the corresponding rename-token ** from Parse object pParse and add it to the RenameCtx pCtx. */ static void renameColumnIdlistNames( - Parse *pParse, - RenameCtx *pCtx, - const IdList *pIdList, + Parse *pParse, + RenameCtx *pCtx, + const IdList *pIdList, const char *zOld ){ if( pIdList ){ @@ -1147,7 +1147,7 @@ static int renameParseSql( p->nQueryLoop = 1; rc = sqlite3RunParser(p, zSql); if( db->mallocFailed ) rc = SQLITE_NOMEM; - if( rc==SQLITE_OK + if( rc==SQLITE_OK && NEVER(p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0) ){ rc = SQLITE_CORRUPT_BKPT; @@ -1196,8 +1196,8 @@ static int renameEditSql( char *zBuf2 = 0; if( zNew ){ - /* Set zQuot to point to a buffer containing a quoted copy of the - ** identifier zNew. If the corresponding identifier in the original + /* Set zQuot to point to a buffer containing a quoted copy of the + ** identifier zNew. If the corresponding identifier in the original ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to ** point to zQuot so that all substitutions are made using the ** quoted version of the new column name. */ @@ -1220,7 +1220,7 @@ static int renameEditSql( /* At this point pRename->pList contains a list of RenameToken objects ** corresponding to all tokens in the input SQL that must be replaced - ** with the new column name, or with single-quoted versions of themselves. + ** with the new column name, or with single-quoted versions of themselves. ** All that remains is to construct and return the edited SQL string. */ if( zOut ){ int nOut = nSql; @@ -1259,7 +1259,7 @@ static int renameEditSql( iOff = pBest->t.z - zSql; if( pBest->t.n!=nReplace ){ - memmove(&zOut[iOff + nReplace], &zOut[iOff + pBest->t.n], + memmove(&zOut[iOff + nReplace], &zOut[iOff + pBest->t.n], nOut - (iOff + pBest->t.n) ); nOut += nReplace - pBest->t.n; @@ -1294,7 +1294,7 @@ static void renameSetENames(ExprList *pEList, int val){ /* ** Resolve all symbols in the trigger at pParse->pNewTrigger, assuming -** it was read from the schema of database zDb. Return SQLITE_OK if +** it was read from the schema of database zDb. Return SQLITE_OK if ** successful. Otherwise, return an SQLite error code and leave an error ** message in the Parse object. */ @@ -1308,7 +1308,7 @@ static int renameResolveTrigger(Parse *pParse){ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; assert( pNew->pTabSchema ); - pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, + pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName ); pParse->eTriggerOp = pNew->op; @@ -1601,7 +1601,7 @@ static void renameColumnFunc( if( rc!=SQLITE_OK ) goto renameColumnFunc_done; for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->zTarget ){ + if( pStep->zTarget ){ Table *pTarget = sqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb); if( pTarget==pTab ){ if( pStep->pUpsert ){ @@ -1647,7 +1647,7 @@ static void renameColumnFunc( } /* -** Walker expression callback used by "RENAME TABLE". +** Walker expression callback used by "RENAME TABLE". */ static int renameTableExprCb(Walker *pWalker, Expr *pExpr){ RenameCtx *p = pWalker->u.pRename; @@ -1661,7 +1661,7 @@ static int renameTableExprCb(Walker *pWalker, Expr *pExpr){ } /* -** Walker select callback used by "RENAME TABLE". +** Walker select callback used by "RENAME TABLE". */ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ int i; @@ -1691,7 +1691,7 @@ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ /* ** This C function implements an SQL user function that is used by SQL code ** generated by the ALTER TABLE ... RENAME command to modify the definition -** of any foreign key constraints that use the table being renamed as the +** of any foreign key constraints that use the table being renamed as the ** parent table. It is passed three arguments: ** ** 0: The database containing the table being renamed. @@ -1805,7 +1805,7 @@ static void renameTableFunc( else{ Trigger *pTrigger = sParse.pNewTrigger; TriggerStep *pStep; - if( 0==sqlite3_stricmp(sParse.pNewTrigger->table, zOld) + if( 0==sqlite3_stricmp(sParse.pNewTrigger->table, zOld) && sCtx.pTab->pSchema==pTrigger->pTabSchema ){ renameTokenFind(&sParse, &sCtx, sParse.pNewTrigger->table); @@ -1881,12 +1881,12 @@ static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){ ** ** CREATE TABLE t1(a, b, c); ** -** SELECT sqlite_rename_quotefix('main', +** SELECT sqlite_rename_quotefix('main', ** 'CREATE VIEW v1 AS SELECT "a", "string" FROM t1' ** ); ** ** returns the string: -** +** ** CREATE VIEW v1 AS SELECT "a", 'string' FROM t1 ** ** If there is a error in the input SQL, then raise an error, except @@ -1942,8 +1942,8 @@ static void renameQuotefixFunc( sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); #ifndef SQLITE_OMIT_GENERATED_COLUMNS for(i=0; inCol; i++){ - sqlite3WalkExpr(&sWalker, - sqlite3ColumnExpr(sParse.pNewTable, + sqlite3WalkExpr(&sWalker, + sqlite3ColumnExpr(sParse.pNewTable, &sParse.pNewTable->aCol[i])); } #endif /* SQLITE_OMIT_GENERATED_COLUMNS */ @@ -1960,7 +1960,7 @@ static void renameQuotefixFunc( #endif /* SQLITE_OMIT_TRIGGER */ } - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK ){ rc = renameEditSql(context, &sCtx, zInput, 0, 0); } renameTokenFree(db, sCtx.pList); @@ -2071,7 +2071,7 @@ static void renameTableTest( /* ** The implementation of internal UDF sqlite_drop_column(). -** +** ** Arguments: ** ** argv[0]: An integer - the index of the schema containing the table @@ -2107,7 +2107,7 @@ static void dropColumnFunc( rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1); if( rc!=SQLITE_OK ) goto drop_column_done; pTab = sParse.pNewTable; - if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){ + if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){ /* This can happen if the sqlite_schema table is corrupt */ rc = SQLITE_CORRUPT_BKPT; goto drop_column_done; @@ -2139,7 +2139,7 @@ static void dropColumnFunc( } /* -** This function is called by the parser upon parsing an +** This function is called by the parser upon parsing an ** ** ALTER TABLE pSrc DROP COLUMN pName ** @@ -2161,7 +2161,7 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_drop_column; - /* Make sure this is not an attempt to ALTER a view, virtual table or + /* Make sure this is not an attempt to ALTER a view, virtual table or ** system table. */ if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_drop_column; if( SQLITE_OK!=isRealTable(pParse, pTab, 1) ) goto exit_drop_column; @@ -2178,10 +2178,10 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ goto exit_drop_column; } - /* Do not allow the user to drop a PRIMARY KEY column or a column + /* Do not allow the user to drop a PRIMARY KEY column or a column ** constrained by a UNIQUE constraint. */ if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){ - sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"", + sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"", (pTab->aCol[iCol].colFlags&COLFLAG_PRIMKEY) ? "PRIMARY KEY" : "UNIQUE", zCol ); @@ -2206,7 +2206,7 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ #endif renameTestSchema(pParse, zDb, iDb==1, "", 0); renameFixQuotes(pParse, zDb, iDb==1); - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_drop_column(%d, sql, %d) " "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)" diff --git a/src/analyze.c b/src/analyze.c index 0823bcaef..a7a8b6d66 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -1849,14 +1849,15 @@ static int loadStatTbl( decodeIntArray((char*)sqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0); decodeIntArray((char*)sqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0); - /* Take a copy of the sample. Add two 0x00 bytes the end of the buffer. + /* Take a copy of the sample. Add 8 extra 0x00 bytes the end of the buffer. ** This is in case the sample record is corrupted. In that case, the ** sqlite3VdbeRecordCompare() may read up to two varints past the ** end of the allocated buffer before it realizes it is dealing with - ** a corrupt record. Adding the two 0x00 bytes prevents this from causing + ** a corrupt record. Or it might try to read a large integer from the + ** buffer. In any case, eight 0x00 bytes prevents this from causing ** a buffer overread. */ pSample->n = sqlite3_column_bytes(pStmt, 4); - pSample->p = sqlite3DbMallocZero(db, pSample->n + 2); + pSample->p = sqlite3DbMallocZero(db, pSample->n + 8); if( pSample->p==0 ){ sqlite3_finalize(pStmt); return SQLITE_NOMEM_BKPT; diff --git a/src/auth.c b/src/auth.c index 3a4f73a23..fa6c82d85 100644 --- a/src/auth.c +++ b/src/auth.c @@ -204,7 +204,7 @@ int sqlite3AuthCheck( sqlite3 *db = pParse->db; int rc; - /* Don't do any authorization checks if the database is initialising + /* Don't do any authorization checks if the database is initializing ** or if the parser is being invoked from within sqlite3_declare_vtab. */ assert( !IN_RENAME_OBJECT || db->xAuth==0 ); diff --git a/src/bitvec.c b/src/bitvec.c index 9393428d9..13f87d567 100644 --- a/src/bitvec.c +++ b/src/bitvec.c @@ -17,8 +17,8 @@ ** property. Usually only a few pages are meet either condition. ** So the bitmap is usually sparse and has low cardinality. ** But sometimes (for example when during a DROP of a large table) most -** or all of the pages in a database can get journalled. In those cases, -** the bitmap becomes dense with high cardinality. The algorithm needs +** or all of the pages in a database can get journalled. In those cases, +** the bitmap becomes dense with high cardinality. The algorithm needs ** to handle both cases well. ** ** The size of the bitmap is fixed when the object is created. @@ -39,13 +39,13 @@ /* Size of the Bitvec structure in bytes. */ #define BITVEC_SZ 512 -/* Round the union size down to the nearest pointer boundary, since that's how +/* Round the union size down to the nearest pointer boundary, since that's how ** it will be aligned within the Bitvec struct. */ #define BITVEC_USIZE \ (((BITVEC_SZ-(3*sizeof(u32)))/sizeof(Bitvec*))*sizeof(Bitvec*)) -/* Type of the array "element" for the bitmap representation. -** Should be a power of 2, and ideally, evenly divide into BITVEC_USIZE. +/* Type of the array "element" for the bitmap representation. +** Should be a power of 2, and ideally, evenly divide into BITVEC_USIZE. ** Setting this to the "natural word" size of your CPU may improve ** performance. */ #define BITVEC_TELEM u8 @@ -58,12 +58,12 @@ /* Number of u32 values in hash table. */ #define BITVEC_NINT (BITVEC_USIZE/sizeof(u32)) -/* Maximum number of entries in hash table before +/* Maximum number of entries in hash table before ** sub-dividing and re-hashing. */ #define BITVEC_MXHASH (BITVEC_NINT/2) /* Hashing function for the aHash representation. -** Empirical testing showed that the *37 multiplier -** (an arbitrary prime)in the hash function provided +** Empirical testing showed that the *37 multiplier +** (an arbitrary prime)in the hash function provided ** no fewer collisions than the no-op *1. */ #define BITVEC_HASH(X) (((X)*1)%BITVEC_NINT) @@ -109,7 +109,7 @@ struct Bitvec { /* ** Create a new bitmap object able to handle bits between 0 and iSize, -** inclusive. Return a pointer to the new object. Return NULL if +** inclusive. Return a pointer to the new object. Return NULL if ** malloc fails. */ Bitvec *sqlite3BitvecCreate(u32 iSize){ @@ -188,7 +188,7 @@ int sqlite3BitvecSet(Bitvec *p, u32 i){ h = BITVEC_HASH(i++); /* if there wasn't a hash collision, and this doesn't */ /* completely fill the hash, then just add it without */ - /* worring about sub-dividing and re-hashing. */ + /* worrying about sub-dividing and re-hashing. */ if( !p->u.aHash[h] ){ if (p->nSet<(BITVEC_NINT-1)) { goto bitvec_set_end; @@ -365,7 +365,7 @@ int sqlite3BitvecBuiltinTest(int sz, int *aOp){ break; } case 3: - case 4: + case 4: default: { nx = 2; sqlite3_randomness(sizeof(i), &i); diff --git a/src/btmutex.c b/src/btmutex.c index 45745b001..232831e03 100644 --- a/src/btmutex.c +++ b/src/btmutex.c @@ -178,7 +178,7 @@ int sqlite3BtreeHoldsMutex(Btree *p){ ** ** There is a corresponding leave-all procedures. ** -** Enter the mutexes in accending order by BtShared pointer address +** Enter the mutexes in ascending order by BtShared pointer address ** to avoid the possibility of deadlock when two threads with ** two or more btrees in common both try to lock all their btrees ** at the same instant. diff --git a/src/btree.c b/src/btree.c index 87bc0058b..c23f86e1d 100644 --- a/src/btree.c +++ b/src/btree.c @@ -1,3 +1,4 @@ + /* ** 2004 April 6 ** @@ -51,7 +52,7 @@ int sqlite3BtreeTrace=1; /* True to enable tracing */ #define BTALLOC_LE 2 /* Allocate any page <= the parameter */ /* -** Macro IfNotOmitAV(x) returns (x) if SQLITE_OMIT_AUTOVACUUM is not +** Macro IfNotOmitAV(x) returns (x) if SQLITE_OMIT_AUTOVACUUM is not ** defined, or 0 if it is. For example: ** ** bIncrVacuum = IfNotOmitAV(pBtShared->incrVacuum); @@ -66,7 +67,7 @@ int sqlite3BtreeTrace=1; /* True to enable tracing */ /* ** A list of BtShared objects that are eligible for participation ** in shared cache. This variable has file scope during normal builds, -** but the test harness needs to access it so we make it global for +** but the test harness needs to access it so we make it global for ** test builds. ** ** Access to this variable is protected by SQLITE_MUTEX_STATIC_MAIN. @@ -101,7 +102,7 @@ int sqlite3_enable_shared_cache(int enable){ ** manipulate entries in the BtShared.pLock linked list used to store ** shared-cache table level locks. If the library is compiled with the ** shared-cache feature disabled, then there is only ever one user - ** of each BtShared structure and so this locking is not necessary. + ** of each BtShared structure and so this locking is not necessary. ** So define the lock related functions as no-ops. */ #define querySharedCacheTableLock(a,b,c) SQLITE_OK @@ -157,15 +158,15 @@ int corruptPageError(int lineno, MemPage *p){ /* **** This function is only used as part of an assert() statement. *** ** -** Check to see if pBtree holds the required locks to read or write to the +** Check to see if pBtree holds the required locks to read or write to the ** table with root page iRoot. Return 1 if it does and 0 if not. ** -** For example, when writing to a table with root-page iRoot via +** For example, when writing to a table with root-page iRoot via ** Btree connection pBtree: ** ** assert( hasSharedCacheTableLock(pBtree, iRoot, 0, WRITE_LOCK) ); ** -** When writing to an index that resides in a sharable database, the +** When writing to an index that resides in a sharable database, the ** caller should have first obtained a lock specifying the root page of ** the corresponding table. This makes things a bit more complicated, ** as this module treats each table as a separate structure. To determine @@ -187,7 +188,7 @@ static int hasSharedCacheTableLock( BtLock *pLock; /* If this database is not shareable, or if the client is reading - ** and has the read-uncommitted flag set, then no lock is required. + ** and has the read-uncommitted flag set, then no lock is required. ** Return true immediately. */ if( (pBtree->sharable==0) @@ -229,13 +230,13 @@ static int hasSharedCacheTableLock( iTab = iRoot; } - /* Search for the required lock. Either a write-lock on root-page iTab, a + /* Search for the required lock. Either a write-lock on root-page iTab, a ** write-lock on the schema table, or (if the client is reading) a ** read-lock on iTab will suffice. Return 1 if any of these are found. */ for(pLock=pBtree->pBt->pLock; pLock; pLock=pLock->pNext){ - if( pLock->pBtree==pBtree + if( pLock->pBtree==pBtree && (pLock->iTable==iTab || (pLock->eLock==WRITE_LOCK && pLock->iTable==1)) - && pLock->eLock>=eLockType + && pLock->eLock>=eLockType ){ return 1; } @@ -268,7 +269,7 @@ static int hasSharedCacheTableLock( static int hasReadConflicts(Btree *pBtree, Pgno iRoot){ BtCursor *p; for(p=pBtree->pBt->pCursor; p; p=p->pNext){ - if( p->pgnoRoot==iRoot + if( p->pgnoRoot==iRoot && p->pBtree!=pBtree && 0==(p->pBtree->db->flags & SQLITE_ReadUncommit) ){ @@ -280,7 +281,7 @@ static int hasReadConflicts(Btree *pBtree, Pgno iRoot){ #endif /* #ifdef SQLITE_DEBUG */ /* -** Query to see if Btree handle p may obtain a lock of type eLock +** Query to see if Btree handle p may obtain a lock of type eLock ** (READ_LOCK or WRITE_LOCK) on the table with root-page iTab. Return ** SQLITE_OK if the lock may be obtained (by calling ** setSharedCacheTableLock()), or SQLITE_LOCKED if not. @@ -293,14 +294,14 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ assert( eLock==READ_LOCK || eLock==WRITE_LOCK ); assert( p->db!=0 ); assert( !(p->db->flags&SQLITE_ReadUncommit)||eLock==WRITE_LOCK||iTab==1 ); - + /* If requesting a write-lock, then the Btree must have an open write - ** transaction on this file. And, obviously, for this to be so there + ** transaction on this file. And, obviously, for this to be so there ** must be an open write transaction on the file itself. */ assert( eLock==READ_LOCK || (p==pBt->pWriter && p->inTrans==TRANS_WRITE) ); assert( eLock==READ_LOCK || pBt->inTransaction==TRANS_WRITE ); - + /* This routine is a no-op if the shared-cache is not enabled */ if( !p->sharable ){ return SQLITE_OK; @@ -315,7 +316,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ } for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ - /* The condition (pIter->eLock!=eLock) in the following if(...) + /* The condition (pIter->eLock!=eLock) in the following if(...) ** statement is a simplification of: ** ** (eLock==WRITE_LOCK || pIter->eLock==WRITE_LOCK) @@ -342,7 +343,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ #ifndef SQLITE_OMIT_SHARED_CACHE /* ** Add a lock on the table with root-page iTable to the shared-btree used -** by Btree handle p. Parameter eLock must be either READ_LOCK or +** by Btree handle p. Parameter eLock must be either READ_LOCK or ** WRITE_LOCK. ** ** This function assumes the following: @@ -354,7 +355,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ ** with the requested lock (i.e. querySharedCacheTableLock() has ** already been called and returned SQLITE_OK). ** -** SQLITE_OK is returned if the lock is added successfully. SQLITE_NOMEM +** SQLITE_OK is returned if the lock is added successfully. SQLITE_NOMEM ** is returned if a malloc attempt fails. */ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ @@ -368,11 +369,11 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ /* A connection with the read-uncommitted flag set will never try to ** obtain a read-lock using this function. The only read-lock obtained - ** by a connection in read-uncommitted mode is on the sqlite_schema + ** by a connection in read-uncommitted mode is on the sqlite_schema ** table, and that lock is obtained in BtreeBeginTrans(). */ assert( 0==(p->db->flags&SQLITE_ReadUncommit) || eLock==WRITE_LOCK ); - /* This function should only be called on a sharable b-tree after it + /* This function should only be called on a sharable b-tree after it ** has been determined that no other b-tree holds a conflicting lock. */ assert( p->sharable ); assert( SQLITE_OK==querySharedCacheTableLock(p, iTable, eLock) ); @@ -417,7 +418,7 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ ** Release all the table locks (locks obtained via calls to ** the setSharedCacheTableLock() procedure) held by Btree object p. ** -** This function assumes that Btree p has an open read or write +** This function assumes that Btree p has an open read or write ** transaction. If it does not, then the BTS_PENDING flag ** may be incorrectly cleared. */ @@ -449,7 +450,7 @@ static void clearAllSharedCacheTableLocks(Btree *p){ pBt->pWriter = 0; pBt->btsFlags &= ~(BTS_EXCLUSIVE|BTS_PENDING); }else if( pBt->nTransaction==2 ){ - /* This function is called when Btree p is concluding its + /* This function is called when Btree p is concluding its ** transaction. If there currently exists a writer, and p is not ** that writer, then the number of locks held by connections other ** than the writer must be about to drop to zero. In this case @@ -495,7 +496,7 @@ static int cursorHoldsMutex(BtCursor *p){ } /* Verify that the cursor and the BtShared agree about what is the current -** database connetion. This is important in shared-cache mode. If the database +** database connetion. This is important in shared-cache mode. If the database ** connection pointers get out-of-sync, it is possible for routines like ** btreeInitPage() to reference an stale connection pointer that references a ** a connection that has already closed. This routine is used inside assert() @@ -566,8 +567,8 @@ static void invalidateIncrblobCursors( #endif /* SQLITE_OMIT_INCRBLOB */ /* -** Set bit pgno of the BtShared.pHasContent bitvec. This is called -** when a page that previously contained data becomes a free-list leaf +** Set bit pgno of the BtShared.pHasContent bitvec. This is called +** when a page that previously contained data becomes a free-list leaf ** page. ** ** The BtShared.pHasContent bitvec exists to work around an obscure @@ -593,7 +594,7 @@ static void invalidateIncrblobCursors( ** may be lost. In the event of a rollback, it may not be possible ** to restore the database to its original configuration. ** -** The solution is the BtShared.pHasContent bitvec. Whenever a page is +** The solution is the BtShared.pHasContent bitvec. Whenever a page is ** moved to become a free-list leaf page, the corresponding bit is ** set in the bitvec. Whenever a leaf page is extracted from the free-list, ** optimization 2 above is omitted if the corresponding bit is already @@ -654,13 +655,13 @@ static void btreeReleaseAllCursorPages(BtCursor *pCur){ ** The cursor passed as the only argument must point to a valid entry ** when this function is called (i.e. have eState==CURSOR_VALID). This ** function saves the current cursor key in variables pCur->nKey and -** pCur->pKey. SQLITE_OK is returned if successful or an SQLite error +** pCur->pKey. SQLITE_OK is returned if successful or an SQLite error ** code otherwise. ** ** If the cursor is open on an intkey table, then the integer key ** (the rowid) is stored in pCur->nKey and pCur->pKey is left set to -** NULL. If the cursor is open on a non-intkey table, then pCur->pKey is -** set to point to a malloced buffer pCur->nKey bytes in size containing +** NULL. If the cursor is open on a non-intkey table, then pCur->pKey is +** set to point to a malloced buffer pCur->nKey bytes in size containing ** the key. */ static int saveCursorKey(BtCursor *pCur){ @@ -676,8 +677,8 @@ static int saveCursorKey(BtCursor *pCur){ /* For an index btree, save the complete key content. It is possible ** that the current key is corrupt. In that case, it is possible that ** the sqlite3VdbeRecordUnpack() function may overread the buffer by - ** up to the size of 1 varint plus 1 8-byte value when the cursor - ** position is restored. Hence the 17 bytes of padding allocated + ** up to the size of 1 varint plus 1 8-byte value when the cursor + ** position is restored. Hence the 17 bytes of padding allocated ** below. */ void *pKey; pCur->nKey = sqlite3BtreePayloadSize(pCur); @@ -699,11 +700,11 @@ static int saveCursorKey(BtCursor *pCur){ } /* -** Save the current cursor position in the variables BtCursor.nKey +** Save the current cursor position in the variables BtCursor.nKey ** and BtCursor.pKey. The cursor's state is set to CURSOR_REQUIRESEEK. ** ** The caller must ensure that the cursor is valid (has eState==CURSOR_VALID) -** prior to calling this routine. +** prior to calling this routine. */ static int saveCursorPosition(BtCursor *pCur){ int rc; @@ -742,7 +743,7 @@ static int SQLITE_NOINLINE saveCursorsOnList(BtCursor*,Pgno,BtCursor*); ** routine is called just before cursor pExcept is used to modify the ** table, for example in BtreeDelete() or BtreeInsert(). ** -** If there are two or more cursors on the same btree, then all such +** If there are two or more cursors on the same btree, then all such ** cursors should have their BTCF_Multiple flag set. The btreeCursor() ** routine enforces that rule. This routine only needs to be called in ** the uncommon case when pExpect has the BTCF_Multiple flag set. @@ -840,9 +841,9 @@ static int btreeMoveto( /* ** Restore the cursor to the position it was in (or as close to as possible) -** when saveCursorPosition() was called. Note that this call deletes the +** when saveCursorPosition() was called. Note that this call deletes the ** saved position info stored by saveCursorPosition(), so there can be -** at most one effective restoreCursorPosition() call after each +** at most one effective restoreCursorPosition() call after each ** saveCursorPosition(). */ static int btreeRestoreCursorPosition(BtCursor *pCur){ @@ -910,7 +911,7 @@ BtCursor *sqlite3BtreeFakeValidCursor(void){ /* ** This routine restores a cursor back to its original position after it ** has been moved by some outside activity (such as a btree rebalance or -** a row having been deleted out from under the cursor). +** a row having been deleted out from under the cursor). ** ** On success, the *pDifferentRow parameter is false if the cursor is left ** pointing at exactly the same row. *pDifferntRow is the row the cursor @@ -992,7 +993,7 @@ static Pgno ptrmapPageno(BtShared *pBt, Pgno pgno){ if( pgno<2 ) return 0; nPagesPerMapPage = (pBt->usableSize/5)+1; iPtrMap = (pgno-2)/nPagesPerMapPage; - ret = (iPtrMap*nPagesPerMapPage) + 2; + ret = (iPtrMap*nPagesPerMapPage) + 2; if( ret==PENDING_BYTE_PAGE(pBt) ){ ret++; } @@ -1480,7 +1481,7 @@ static u16 cellSizePtrTableLeaf(MemPage *pPage, u8 *pCell){ }while( *(pIter)>=0x80 && pIterxParseCell(pPage, pCell, &info); if( info.nLocalaDataEnd, pCell, pCell+info.nLocal) ){ + if( SQLITE_OVERFLOW(pSrc->aDataEnd, pCell, pCell+info.nLocal) ){ testcase( pSrc!=pPage ); *pRC = SQLITE_CORRUPT_BKPT; return; @@ -1589,7 +1590,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ /* This block handles pages with two or fewer free blocks and nMaxFrag ** or fewer fragmented bytes. In this case it is faster to move the ** two (or one) blocks of cells using memmove() and add the required - ** offsets to each pointer in the cell-pointer array than it is to + ** offsets to each pointer in the cell-pointer array than it is to ** reconstruct the entire page. */ if( (int)data[hdr+7]<=nMaxFrag ){ int iFree = get2byte(&data[hdr+1]); @@ -1634,7 +1635,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ iCellStart = get2byte(&data[hdr+5]); if( nCell>0 ){ temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart); + memcpy(temp, data, usableSize); src = temp; for(i=0; ipDbPage) ); assert( pPage->pBt ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); @@ -1858,7 +1859,7 @@ static SQLITE_INLINE int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ ** ** Even though the freeblock list was checked by btreeComputeFreeSpace(), ** that routine will not detect overlap between cells or freeblocks. Nor -** does it detect cells or freeblocks that encrouch into the reserved bytes +** does it detect cells or freeblocks that encroach into the reserved bytes ** at the end of the page. So do additional corruption checks inside this ** routine and return SQLITE_CORRUPT if any problems are found. */ @@ -1881,7 +1882,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ assert( iSize>=4 ); /* Minimum cell size is 4 */ assert( CORRUPT_DB || iStart<=pPage->pBt->usableSize-4 ); - /* The list of freeblocks must be in ascending order. Find the + /* The list of freeblocks must be in ascending order. Find the ** spot on the list where iStart should be inserted. */ hdr = pPage->hdrOffset; @@ -1900,7 +1901,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ return SQLITE_CORRUPT_PAGE(pPage); } assert( iFreeBlk>iPtr || iFreeBlk==0 || CORRUPT_DB ); - + /* At this point: ** iFreeBlk: First freeblock after iStart, or zero if none ** iPtr: The address of a pointer to iFreeBlk @@ -1917,7 +1918,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ iSize = iEnd - iStart; iFreeBlk = get2byte(&data[iFreeBlk]); } - + /* If iPtr is another freeblock (that is, if iPtr is not the freelist ** pointer in the page header) then check to see if iStart should be ** coalesced onto the end of iPtr. @@ -2075,7 +2076,7 @@ static int btreeComputeFreeSpace(MemPage *pPage){ /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will ** always be at least one cell before the first freeblock. */ - return SQLITE_CORRUPT_PAGE(pPage); + return SQLITE_CORRUPT_PAGE(pPage); } while( 1 ){ if( pc>iCellLast ){ @@ -2114,7 +2115,7 @@ static int btreeComputeFreeSpace(MemPage *pPage){ /* ** Do additional sanity check after btreeInitPage() if -** PRAGMA cell_size_check=ON +** PRAGMA cell_size_check=ON */ static SQLITE_NOINLINE int btreeCellSizeCheck(MemPage *pPage){ int iCellFirst; /* First allowable cell or freeblock offset */ @@ -2152,7 +2153,7 @@ static SQLITE_NOINLINE int btreeCellSizeCheck(MemPage *pPage){ ** Initialize the auxiliary information for a disk block. ** ** Return SQLITE_OK on success. If we see that the page does -** not contain a well-formed database page, then return +** not contain a well-formed database page, then return ** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not ** guarantee that the page is well-formed. It only shows that ** we failed to detect any corruption. @@ -2257,7 +2258,7 @@ static MemPage *btreePageFromDbPage(DbPage *pDbPage, Pgno pgno, BtShared *pBt){ pPage->hdrOffset = pgno==1 ? 100 : 0; } assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); - return pPage; + return pPage; } /* @@ -2317,68 +2318,41 @@ Pgno sqlite3BtreeLastPage(Btree *p){ /* ** Get a page from the pager and initialize it. -** -** If pCur!=0 then the page is being fetched as part of a moveToChild() -** call. Do additional sanity checking on the page in this case. -** And if the fetch fails, this routine must decrement pCur->iPage. -** -** The page is fetched as read-write unless pCur is not NULL and is -** a read-only cursor. -** -** If an error occurs, then *ppPage is undefined. It -** may remain unchanged, or it may be set to an invalid value. */ static int getAndInitPage( BtShared *pBt, /* The database file */ Pgno pgno, /* Number of the page to get */ MemPage **ppPage, /* Write the page pointer here */ - BtCursor *pCur, /* Cursor to receive the page, or NULL */ int bReadOnly /* True for a read-only page */ ){ int rc; DbPage *pDbPage; + MemPage *pPage; assert( sqlite3_mutex_held(pBt->mutex) ); - assert( pCur==0 || ppPage==&pCur->pPage ); - assert( pCur==0 || bReadOnly==pCur->curPagerFlags ); - assert( pCur==0 || pCur->iPage>0 ); if( pgno>btreePagecount(pBt) ){ - rc = SQLITE_CORRUPT_BKPT; - goto getAndInitPage_error1; + *ppPage = 0; + return SQLITE_CORRUPT_BKPT; } rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); if( rc ){ - goto getAndInitPage_error1; + *ppPage = 0; + return rc; } - *ppPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); - if( (*ppPage)->isInit==0 ){ + pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); + if( pPage->isInit==0 ){ btreePageFromDbPage(pDbPage, pgno, pBt); - rc = btreeInitPage(*ppPage); + rc = btreeInitPage(pPage); if( rc!=SQLITE_OK ){ - goto getAndInitPage_error2; + releasePage(pPage); + *ppPage = 0; + return rc; } } - assert( (*ppPage)->pgno==pgno || CORRUPT_DB ); - assert( (*ppPage)->aData==sqlite3PagerGetData(pDbPage) ); - - /* If obtaining a child page for a cursor, we must verify that the page is - ** compatible with the root page. */ - if( pCur && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){ - rc = SQLITE_CORRUPT_PGNO(pgno); - goto getAndInitPage_error2; - } + assert( pPage->pgno==pgno || CORRUPT_DB ); + assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); + *ppPage = pPage; return SQLITE_OK; - -getAndInitPage_error2: - releasePage(*ppPage); -getAndInitPage_error1: - if( pCur ){ - pCur->iPage--; - pCur->pPage = pCur->apPage[pCur->iPage]; - } - testcase( pgno==0 ); - assert( pgno!=0 || rc!=SQLITE_OK ); - return rc; } /* @@ -2461,7 +2435,7 @@ static void pageReinit(DbPage *pData){ ** call to btreeInitPage() will likely return SQLITE_CORRUPT. ** But no harm is done by this. And it is very important that ** btreeInitPage() be called on every btree page so we make - ** the call for every page that comes in for re-initing. */ + ** the call for every page that comes in for re-initializing. */ btreeInitPage(pPage); } } @@ -2479,11 +2453,11 @@ static int btreeInvokeBusyHandler(void *pArg){ /* ** Open a database file. -** +** ** zFilename is the name of the database file. If zFilename is NULL ** 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 +** Either way, the ephemeral database will be automatically deleted ** when sqlite3BtreeClose() is called. ** ** If zFilename is ":memory:" then an in-memory database is created @@ -2516,7 +2490,7 @@ int sqlite3BtreeOpen( /* 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 + /* Set the variable isMemdb to true for an in-memory database, or ** false for a file-based database. */ #ifdef SQLITE_OMIT_MEMORYDB @@ -2639,7 +2613,10 @@ int sqlite3BtreeOpen( assert( sizeof(u32)==4 ); assert( sizeof(u16)==2 ); assert( sizeof(Pgno)==4 ); - + + /* Suppress false-positive compiler warning from PVS-Studio */ + memset(&zDbHeader[16], 0, 8); + pBt = sqlite3MallocZero( sizeof(*pBt) ); if( pBt==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -2658,7 +2635,7 @@ int sqlite3BtreeOpen( pBt->db = db; sqlite3PagerSetBusyHandler(pBt->pPager, btreeInvokeBusyHandler, pBt); p->pBt = pBt; - + pBt->pCursor = 0; pBt->pPage1 = 0; if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY; @@ -2702,7 +2679,7 @@ int sqlite3BtreeOpen( if( rc ) goto btree_open_out; pBt->usableSize = pBt->pageSize - nReserve; assert( (pBt->pageSize & 7)==0 ); /* 8-byte alignment of pageSize */ - + #if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) /* Add the new BtShared object to the linked list sharable BtShareds. */ @@ -2831,7 +2808,7 @@ static int removeFromSharingList(BtShared *pBt){ } /* -** Make sure pBt->pTmpSpace points to an allocation of +** Make sure pBt->pTmpSpace points to an allocation of ** MX_CELL_SIZE(pBt) bytes with a 4-byte prefix for a left-child ** pointer. */ @@ -2856,7 +2833,7 @@ static SQLITE_NOINLINE int allocateTempSpace(BtShared *pBt){ ** can mean that fillInCell() only initializes the first 2 or 3 ** bytes of pTmpSpace, but that the first 4 bytes are copied from ** it into a database page. This is not actually a problem, but it - ** does cause a valgrind error when the 1 or 2 bytes of unitialized + ** does cause a valgrind error when the 1 or 2 bytes of uninitialized ** data is passed to system call write(). So to avoid this error, ** zero the first 4 bytes of temp space here. ** @@ -2911,7 +2888,7 @@ int sqlite3BtreeClose(Btree *p){ sqlite3BtreeLeave(p); /* If there are still other outstanding references to the shared-btree - ** structure, return now. The remainder of this procedure cleans + ** structure, return now. The remainder of this procedure cleans ** up the shared-btree. */ assert( p->wantToLock==0 && p->locked==0 ); @@ -3017,7 +2994,7 @@ int sqlite3BtreeSetPagerFlags( /* ** Change the default pages size and the number of reserved bytes per page. -** Or, if the page size has already been fixed, return SQLITE_READONLY +** Or, if the page size has already been fixed, return SQLITE_READONLY ** without changing anything. ** ** The page size must be a power of 2 between 512 and 65536. If the page @@ -3077,7 +3054,7 @@ int sqlite3BtreeGetPageSize(Btree *p){ ** held. ** ** This is useful in one special case in the backup API code where it is -** known that the shared b-tree mutex is held, but the mutex on the +** known that the shared b-tree mutex is held, but the mutex on the ** database handle that owns *p is not. In this case if sqlite3BtreeEnter() ** were to be called, it might collide with some other operation on the ** database handle that owns *p, causing undefined behavior. @@ -3091,7 +3068,7 @@ int sqlite3BtreeGetReserveNoMutex(Btree *p){ /* ** Return the number of bytes of space at the end of every page that -** are intentually left unused. This is the "reserved" space that is +** are intentionally left unused. This is the "reserved" space that is ** sometimes used by extensions. ** ** The value returned is the larger of the current reserve size and @@ -3157,7 +3134,7 @@ int sqlite3BtreeSecureDelete(Btree *p, int newFlag){ /* ** Change the 'auto-vacuum' property of the database. If the 'autoVacuum' ** parameter is non-zero, then auto-vacuum mode is enabled. If zero, it -** is disabled. The default value for the auto-vacuum property is +** is disabled. The default value for the auto-vacuum property is ** determined by the SQLITE_DEFAULT_AUTOVACUUM macro. */ int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){ @@ -3181,7 +3158,7 @@ int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){ } /* -** Return the value of the 'auto-vacuum' property. If auto-vacuum is +** Return the value of the 'auto-vacuum' property. If auto-vacuum is ** enabled 1 is returned. Otherwise 0. */ int sqlite3BtreeGetAutoVacuum(Btree *p){ @@ -3213,9 +3190,9 @@ static void setDefaultSyncFlag(BtShared *pBt, u8 safety_level){ Db *pDb; if( (db=pBt->db)!=0 && (pDb=db->aDb)!=0 ){ while( pDb->pBt==0 || pDb->pBt->pBt!=pBt ){ pDb++; } - if( pDb->bSyncSet==0 - && pDb->safety_level!=safety_level - && pDb!=&db->aDb[1] + if( pDb->bSyncSet==0 + && pDb->safety_level!=safety_level + && pDb!=&db->aDb[1] ){ pDb->safety_level = safety_level; sqlite3PagerSetFlags(pBt->pPager, @@ -3238,7 +3215,7 @@ static int newDatabase(BtShared*); ** SQLITE_OK is returned on success. If the file is not a ** well-formed database file, then SQLITE_CORRUPT is returned. ** SQLITE_BUSY is returned if the database is locked. SQLITE_NOMEM -** is returned if we run out of memory. +** is returned if we run out of memory. */ static int lockBtree(BtShared *pBt){ int rc; /* Result code from subfunctions */ @@ -3254,7 +3231,7 @@ static int lockBtree(BtShared *pBt){ if( rc!=SQLITE_OK ) return rc; /* Do some checking to help insure the file we opened really is - ** a valid database file. + ** a valid database file. */ nPage = get4byte(28+(u8*)pPage1->aData); sqlite3PagerPagecount(pBt->pPager, (int*)&nPageFile); @@ -3292,7 +3269,7 @@ static int lockBtree(BtShared *pBt){ } /* If the read version is set to 2, this database should be accessed - ** in WAL mode. If the log is not already open, open it now. Then + ** in WAL mode. If the log is not already open, open it now. Then ** return SQLITE_OK and return without populating BtShared.pPage1. ** The caller detects this and calls this function again. This is ** required as the version of page 1 currently in the page1 buffer @@ -3333,16 +3310,15 @@ static int lockBtree(BtShared *pBt){ /* EVIDENCE-OF: R-25008-21688 The size of a page is a power of two ** between 512 and 65536 inclusive. */ if( ((pageSize-1)&pageSize)!=0 - || pageSize>SQLITE_MAX_PAGE_SIZE - || pageSize<=256 + || pageSize>SQLITE_MAX_PAGE_SIZE + || pageSize<=256 ){ goto page1_init_failed; } - pBt->btsFlags |= BTS_PAGESIZE_FIXED; assert( (pageSize & 7)==0 ); /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte ** integer at offset 20 is the number of bytes of space at the end of - ** each page to reserve for extensions. + ** each page to reserve for extensions. ** ** EVIDENCE-OF: R-37497-42412 The size of the reserved region is ** determined by the one-byte unsigned integer found at an offset of 20 @@ -3358,6 +3334,7 @@ static int lockBtree(BtShared *pBt){ releasePageOne(pPage1); pBt->usableSize = usableSize; pBt->pageSize = pageSize; + pBt->btsFlags |= BTS_PAGESIZE_FIXED; freeTempSpace(pBt); rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, pageSize-usableSize); @@ -3377,6 +3354,7 @@ static int lockBtree(BtShared *pBt){ if( usableSize<480 ){ goto page1_init_failed; } + pBt->btsFlags |= BTS_PAGESIZE_FIXED; pBt->pageSize = pageSize; pBt->usableSize = usableSize; #ifndef SQLITE_OMIT_AUTOVACUUM @@ -3436,7 +3414,7 @@ static int countValidCursors(BtShared *pBt, int wrOnly){ int r = 0; for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ if( (wrOnly==0 || (pCur->curFlags & BTCF_WriteFlag)!=0) - && pCur->eState!=CURSOR_FAULT ) r++; + && pCur->eState!=CURSOR_FAULT ) r++; } return r; } @@ -3445,7 +3423,7 @@ static int countValidCursors(BtShared *pBt, int wrOnly){ /* ** If there are no outstanding cursors and we are not in the middle ** of a transaction but there is a read lock on the database, then -** this routine unrefs the first page of the database file which +** this routine unrefs the first page of the database file which ** has the effect of releasing the read lock. ** ** If there is a transaction in progress, this routine is a no-op. @@ -3529,8 +3507,8 @@ int sqlite3BtreeNewDb(Btree *p){ ** upgraded to exclusive by calling this routine a second time - the ** exclusivity flag only works for a new transaction. ** -** A write-transaction must be started before attempting any -** changes to the database. None of the following routines +** A write-transaction must be started before attempting any +** changes to the database. None of the following routines ** will work unless a transaction is started first: ** ** sqlite3BtreeCreateTable() @@ -3544,7 +3522,7 @@ int sqlite3BtreeNewDb(Btree *p){ ** If an initial attempt to acquire the lock fails because of lock contention ** and the database was previously unlocked, then invoke the busy handler ** if there is one. But if there was previously a read-lock, do not -** invoke the busy handler - just return SQLITE_BUSY. SQLITE_BUSY is +** invoke the busy handler - just return SQLITE_BUSY. SQLITE_BUSY is ** returned when there is already a read-lock in order to avoid a deadlock. ** ** Suppose there are two processes A and B. A has a read lock and B has @@ -3555,7 +3533,11 @@ int sqlite3BtreeNewDb(Btree *p){ ** when A already has a read lock, we encourage A to give up and let B ** proceed. */ -int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ +static SQLITE_NOINLINE int btreeBeginTrans( + Btree *p, /* The btree in which to start the transaction */ + int wrflag, /* True to start a write transaction */ + int *pSchemaVersion /* Put schema version number here, if not NULL */ +){ BtShared *pBt = p->pBt; Pager *pPager = pBt->pPager; int rc = SQLITE_OK; @@ -3572,8 +3554,8 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ } assert( pBt->inTransaction==TRANS_WRITE || IfNotOmitAV(pBt->bDoTruncate)==0 ); - if( (p->db->flags & SQLITE_ResetDatabase) - && sqlite3PagerIsreadonly(pPager)==0 + if( (p->db->flags & SQLITE_ResetDatabase) + && sqlite3PagerIsreadonly(pPager)==0 ){ pBt->btsFlags &= ~BTS_READ_ONLY; } @@ -3587,7 +3569,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ #ifndef SQLITE_OMIT_SHARED_CACHE { sqlite3 *pBlock = 0; - /* If another database handle has already opened a write transaction + /* If another database handle has already opened a write transaction ** on this shared-btree structure and a second write transaction is ** requested, return SQLITE_LOCKED. */ @@ -3612,8 +3594,8 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ } #endif - /* Any read-only or read-write transaction implies a read-lock on - ** page 1. So if some other shared-cache client already has a write-lock + /* Any read-only or read-write transaction implies a read-lock on + ** page 1. So if some other shared-cache client already has a write-lock ** on page 1, the transaction cannot be opened. */ rc = querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK); if( SQLITE_OK!=rc ) goto trans_begun; @@ -3636,7 +3618,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ /* Call lockBtree() until either pBt->pPage1 is populated or ** lockBtree() returns something other than SQLITE_OK. lockBtree() ** may return SQLITE_OK but leave pBt->pPage1 set to 0 if after - ** reading page 1 it discovers that the page-size of the database + ** reading page 1 it discovers that the page-size of the database ** file is not pBt->pageSize. In this case lockBtree() will update ** pBt->pageSize to the page-size of the file on disk. */ @@ -3657,7 +3639,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ } } } - + if( rc!=SQLITE_OK ){ (void)sqlite3PagerWalWriteLock(pPager, 0); unlockBtreeIfUnused(pBt); @@ -3696,7 +3678,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ /* If the db-size header field is incorrect (as it may be if an old ** client has been writing the database file), update it now. Doing - ** this sooner rather than later means the database size can safely + ** this sooner rather than later means the database size can safely ** re-read the database size from page 1 if a savepoint or transaction ** rollback occurs within the transaction. */ @@ -3727,6 +3709,28 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ sqlite3BtreeLeave(p); return rc; } +int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ + BtShared *pBt; + if( p->sharable + || p->inTrans==TRANS_NONE + || (p->inTrans==TRANS_READ && wrflag!=0) + ){ + return btreeBeginTrans(p,wrflag,pSchemaVersion); + } + pBt = p->pBt; + if( pSchemaVersion ){ + *pSchemaVersion = get4byte(&pBt->pPage1->aData[40]); + } + if( wrflag ){ + /* This call makes sure that the pager has the correct number of + ** open savepoints. If the second parameter is greater than 0 and + ** the sub-journal is not already open, then it will be opened here. + */ + return sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint); + }else{ + return SQLITE_OK; + } +} #ifndef SQLITE_OMIT_AUTOVACUUM @@ -3771,7 +3775,7 @@ static int setChildPtrmaps(MemPage *pPage){ ** that it points to iTo. Parameter eType describes the type of pointer to ** be modified, as follows: ** -** PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child +** PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child ** page of pPage. ** ** PTRMAP_OVERFLOW1: pPage is a btree-page. The pointer points at an overflow @@ -3822,9 +3826,9 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ } } } - + if( i==nCell ){ - if( eType!=PTRMAP_BTREE || + if( eType!=PTRMAP_BTREE || get4byte(&pPage->aData[pPage->hdrOffset+8])!=iFrom ){ return SQLITE_CORRUPT_PAGE(pPage); } @@ -3836,11 +3840,11 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ /* -** Move the open database page pDbPage to location iFreePage in the +** Move the open database page pDbPage to location iFreePage in the ** database. The pDbPage reference remains valid. ** ** The isCommit flag indicates that there is no need to remember that -** the journal needs to be sync()ed before database page pDbPage->pgno +** the journal needs to be sync()ed before database page pDbPage->pgno ** can be written to. The caller has already promised not to write to that ** page. */ @@ -3857,14 +3861,14 @@ static int relocatePage( Pager *pPager = pBt->pPager; int rc; - assert( eType==PTRMAP_OVERFLOW2 || eType==PTRMAP_OVERFLOW1 || + assert( eType==PTRMAP_OVERFLOW2 || eType==PTRMAP_OVERFLOW1 || eType==PTRMAP_BTREE || eType==PTRMAP_ROOTPAGE ); assert( sqlite3_mutex_held(pBt->mutex) ); assert( pDbPage->pBt==pBt ); if( iDbPage<3 ) return SQLITE_CORRUPT_BKPT; /* Move page iDbPage from its current location to page number iFreePage */ - TRACE(("AUTOVACUUM: Moving %u to free page %u (ptr page %u type %u)\n", + TRACE(("AUTOVACUUM: Moving %u to free page %u (ptr page %u type %u)\n", iDbPage, iFreePage, iPtrPage, eType)); rc = sqlite3PagerMovepage(pPager, pDbPage->pDbPage, iFreePage, isCommit); if( rc!=SQLITE_OK ){ @@ -3923,19 +3927,19 @@ static int allocateBtreePage(BtShared *, MemPage **, Pgno *, Pgno, u8); /* ** Perform a single step of an incremental-vacuum. If successful, return -** SQLITE_OK. If there is no work to do (and therefore no point in -** calling this function again), return SQLITE_DONE. Or, if an error +** SQLITE_OK. If there is no work to do (and therefore no point in +** calling this function again), return SQLITE_DONE. Or, if an error ** occurs, return some other error code. ** -** More specifically, this function attempts to re-organize the database so +** More specifically, this function attempts to re-organize the database so ** that the last page of the file currently in use is no longer in use. ** ** Parameter nFin is the number of pages that this database would contain ** were this function called until it returns SQLITE_DONE. ** -** If the bCommit parameter is non-zero, this function assumes that the -** caller will keep calling incrVacuumStep() until it returns SQLITE_DONE -** or an error. bCommit is passed true for an auto-vacuum-on-commit +** If the bCommit parameter is non-zero, this function assumes that the +** caller will keep calling incrVacuumStep() until it returns SQLITE_DONE +** or an error. bCommit is passed true for an auto-vacuum-on-commit ** operation, or false for an incremental vacuum. */ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ @@ -3966,7 +3970,7 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ if( bCommit==0 ){ /* Remove the page from the files free-list. This is not required ** if bCommit is non-zero. In that case, the free-list will be - ** truncated to zero after this function returns, so it doesn't + ** truncated to zero after this function returns, so it doesn't ** matter if it still contains some garbage entries. */ Pgno iFreePg; @@ -4015,7 +4019,7 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ } }while( bCommit && iFreePg>nFin ); assert( iFreePgpBt; + pBt = p->pBt; pPager = pBt->pPager; VVA_ONLY( nRef = sqlite3PagerRefcount(pPager); ) @@ -4204,7 +4208,7 @@ static int autoVacuumCommit(Btree *p){ ** ** Otherwise, sync the database file for the btree pBt. zSuperJrnl points to ** the name of a super-journal file that should be written into the -** individual journal file, or is NULL, indicating no super-journal file +** individual journal file, or is NULL, indicating no super-journal file ** (single database transaction). ** ** When this is called, the super-journal should already have been @@ -4255,8 +4259,8 @@ static void btreeEndTransaction(Btree *p){ downgradeAllSharedCacheTableLocks(p); p->inTrans = TRANS_READ; }else{ - /* If the handle had any kind of transaction open, decrement the - ** transaction count of the shared btree. If the transaction count + /* If the handle had any kind of transaction open, decrement the + ** transaction count of the shared btree. If the transaction count ** reaches 0, set the shared state to TRANS_NONE. The unlockBtreeIfUnused() ** call below will unlock the pager. */ if( p->inTrans!=TRANS_NONE ){ @@ -4267,7 +4271,7 @@ static void btreeEndTransaction(Btree *p){ } } - /* Set the current transaction state to TRANS_NONE and unlock the + /* Set the current transaction state to TRANS_NONE and unlock the ** pager if this call closed the only read or write transaction. */ p->inTrans = TRANS_NONE; unlockBtreeIfUnused(pBt); @@ -4288,12 +4292,12 @@ static void btreeEndTransaction(Btree *p){ ** the rollback journal (which causes the transaction to commit) and ** drop locks. ** -** Normally, if an error occurs while the pager layer is attempting to +** Normally, if an error occurs while the pager layer is attempting to ** finalize the underlying journal file, this function returns an error and ** the upper layer will attempt a rollback. However, if the second argument -** is non-zero then this b-tree transaction is part of a multi-file -** transaction. In this case, the transaction has already been committed -** (by deleting a super-journal file) and the caller will ignore this +** is non-zero then this b-tree transaction is part of a multi-file +** transaction. In this case, the transaction has already been committed +** (by deleting a super-journal file) and the caller will ignore this ** functions return code. So, even if an error occurs in the pager layer, ** reset the b-tree objects internal state to indicate that the write ** transaction has been closed. This is quite safe, as the pager will have @@ -4308,7 +4312,7 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ sqlite3BtreeEnter(p); btreeIntegrity(p); - /* If the handle has a write-transaction open, commit the shared-btrees + /* If the handle has a write-transaction open, commit the shared-btrees ** transaction and set the shared state to TRANS_READ. */ if( p->inTrans==TRANS_WRITE ){ @@ -4357,15 +4361,15 @@ int sqlite3BtreeCommit(Btree *p){ ** ** This routine gets called when a rollback occurs. If the writeOnly ** flag is true, then only write-cursors need be tripped - read-only -** cursors save their current positions so that they may continue -** following the rollback. Or, if writeOnly is false, all cursors are +** cursors save their current positions so that they may continue +** following the rollback. Or, if writeOnly is false, all cursors are ** tripped. In general, writeOnly is false if the transaction being ** rolled back modified the database schema. In this case b-tree root ** pages may be moved or deleted from the database altogether, making ** it unsafe for read cursors to continue. ** -** If the writeOnly flag is true and an error is encountered while -** saving the current position of a read-only cursor, all cursors, +** If the writeOnly flag is true and an error is encountered while +** saving the current position of a read-only cursor, all cursors, ** including all read-cursors are tripped. ** ** SQLITE_OK is returned if successful, or if an error occurs while @@ -4471,8 +4475,8 @@ int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){ /* ** Start a statement subtransaction. The subtransaction can be rolled -** back independently of the main transaction. You must start a transaction -** before starting a subtransaction. The subtransaction is ended automatically +** back independently of the main transaction. You must start a transaction +** before starting a subtransaction. The subtransaction is ended automatically ** if the main transaction commits or rolls back. ** ** Statement subtransactions are used around individual SQL statements @@ -4509,11 +4513,11 @@ int sqlite3BtreeBeginStmt(Btree *p, int iStatement){ /* ** The second argument to this function, op, is always SAVEPOINT_ROLLBACK ** or SAVEPOINT_RELEASE. This function either releases or rolls back the -** savepoint identified by parameter iSavepoint, depending on the value +** savepoint identified by parameter iSavepoint, depending on the value ** of op. ** ** Normally, iSavepoint is greater than or equal to zero. However, if op is -** SAVEPOINT_ROLLBACK, then iSavepoint may also be -1. In this case the +** SAVEPOINT_ROLLBACK, then iSavepoint may also be -1. In this case the ** contents of the entire transaction are rolled back. This is different ** from a normal transaction rollback, as no locks are released and the ** transaction remains open. @@ -4538,7 +4542,7 @@ int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ rc = newDatabase(pBt); btreeSetNPage(pBt, pBt->pPage1); - /* pBt->nPage might be zero if the database was corrupt when + /* pBt->nPage might be zero if the database was corrupt when ** the transaction was started. Otherwise, it must be at least 1. */ assert( CORRUPT_DB || pBt->nPage>0 ); } @@ -4576,10 +4580,10 @@ int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ ** is set. If FORDELETE is set, that is a hint to the implementation that ** this cursor will only be used to seek to and delete entries of an index ** as part of a larger DELETE statement. The FORDELETE hint is not used by -** this implementation. But in a hypothetical alternative storage engine +** this implementation. But in a hypothetical alternative storage engine ** in which index entries are automatically deleted when corresponding table ** rows are deleted, the FORDELETE flag is a hint that all SEEK and DELETE -** operations on this cursor can be no-ops and all READ operations can +** operations on this cursor can be no-ops and all READ operations can ** return a null row (2-bytes: 0x01 0x00). ** ** No checking is done to make sure that page iTable really is the @@ -4600,14 +4604,14 @@ static int btreeCursor( BtCursor *pX; /* Looping over other all cursors */ assert( sqlite3BtreeHoldsMutex(p) ); - assert( wrFlag==0 - || wrFlag==BTREE_WRCSR - || wrFlag==(BTREE_WRCSR|BTREE_FORDELETE) + assert( wrFlag==0 + || wrFlag==BTREE_WRCSR + || wrFlag==(BTREE_WRCSR|BTREE_FORDELETE) ); - /* The following assert statements verify that if this is a sharable - ** b-tree database, the connection is holding the required table locks, - ** and that no other connection has any open cursor that conflicts with + /* The following assert statements verify that if this is a sharable + ** b-tree database, the connection is holding the required table locks, + ** and that no other connection has any open cursor that conflicts with ** this lock. The iTable<1 term disables the check for corrupt schemas. */ assert( hasSharedCacheTableLock(p, iTable, pKeyInfo!=0, (wrFlag?2:1)) || iTable<1 ); @@ -4822,7 +4826,6 @@ void sqlite3BtreeCursorUnpin(BtCursor *pCur){ pCur->curFlags &= ~BTCF_Pinned; } -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC /* ** Return the offset into the database file for the start of the ** payload to which the cursor is pointing. @@ -4834,7 +4837,6 @@ i64 sqlite3BtreeOffset(BtCursor *pCur){ return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) + (i64)(pCur->info.pPayload - pCur->pPage->aData); } -#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ /* ** Return the number of bytes of payload for the entry that pCur is @@ -4860,7 +4862,7 @@ u32 sqlite3BtreePayloadSize(BtCursor *pCur){ ** routine always returns 2147483647 (which is the largest record ** that SQLite can handle) or more. But returning a smaller value might ** prevent large memory allocations when trying to interpret a -** corrupt datrabase. +** corrupt database. ** ** The current implementation merely returns the size of the underlying ** database file. @@ -4873,15 +4875,15 @@ sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor *pCur){ /* ** Given the page number of an overflow page in the database (parameter -** ovfl), this function finds the page number of the next page in the +** ovfl), this function finds the page number of the next page in the ** linked list of overflow pages. If possible, it uses the auto-vacuum -** pointer-map data instead of reading the content of page ovfl to do so. +** pointer-map data instead of reading the content of page ovfl to do so. ** ** If an error occurs an SQLite error code is returned. Otherwise: ** -** The page number of the next overflow page in the linked list is -** written to *pPgnoNext. If page ovfl is the last page in its linked -** list, *pPgnoNext is set to zero. +** The page number of the next overflow page in the linked list is +** written to *pPgnoNext. If page ovfl is the last page in its linked +** list, *pPgnoNext is set to zero. ** ** If ppPage is not NULL, and a reference to the MemPage object corresponding ** to page number pOvfl was obtained, then *ppPage is set to point to that @@ -4905,9 +4907,9 @@ static int getOverflowPage( #ifndef SQLITE_OMIT_AUTOVACUUM /* Try to find the next page in the overflow list using the - ** autovacuum pointer-map pages. Guess that the next page in - ** the overflow list is page number (ovfl+1). If that guess turns - ** out to be wrong, fall back to loading the data of page + ** autovacuum pointer-map pages. Guess that the next page in + ** the overflow list is page number (ovfl+1). If that guess turns + ** out to be wrong, fall back to loading the data of page ** number ovfl to determine the next page number. */ if( pBt->autoVacuum ){ @@ -4995,8 +4997,8 @@ static int copyPayload( ** ** If the current cursor entry uses one or more overflow pages ** this function may allocate space for and lazily populate -** the overflow page-list cache array (BtCursor.aOverflow). -** Subsequent calls use this cache to make seeking to the supplied offset +** the overflow page-list cache array (BtCursor.aOverflow). +** Subsequent calls use this cache to make seeking to the supplied offset ** more efficient. ** ** Once an overflow page-list cache has been allocated, it must be @@ -5012,7 +5014,7 @@ static int accessPayload( BtCursor *pCur, /* Cursor pointing to entry to read from */ u32 offset, /* Begin reading this far into payload */ u32 amt, /* Read this many bytes */ - unsigned char *pBuf, /* Write the bytes into this buffer */ + unsigned char *pBuf, /* Write the bytes into this buffer */ int eOp /* zero to read. non-zero to write. */ ){ unsigned char *aPayload; @@ -5066,7 +5068,7 @@ static int accessPayload( Pgno nextPage; nextPage = get4byte(&aPayload[pCur->info.nLocal]); - + /* If the BtCursor.aOverflow[] has not been allocated, allocate it now. ** ** The aOverflow[] array is sized at one entry for each overflow page @@ -5138,12 +5140,12 @@ static int accessPayload( #ifdef SQLITE_DIRECT_OVERFLOW_READ /* If all the following are true: ** - ** 1) this is a read operation, and + ** 1) this is a read operation, and ** 2) data is required from the start of this overflow page, and ** 3) there are no dirty pages in the page-cache ** 4) the database is file-backed, and ** 5) the page is not in the WAL file - ** 6) at least 4 bytes have already been read into the output buffer + ** 6) at least 4 bytes have already been read into the output buffer ** ** then data can be read directly from the database file into the ** output buffer, bypassing the page-cache altogether. This speeds @@ -5250,7 +5252,7 @@ int sqlite3BtreePayloadChecked(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ #endif /* SQLITE_OMIT_INCRBLOB */ /* -** Return a pointer to payload information from the entry that the +** Return a pointer to payload information from the entry that the ** pCur cursor is pointing to. The pointer is to the beginning of ** the key if index btrees (pPage->intKey==0) and is the data for ** table btrees (pPage->intKey==1). The number of bytes of available @@ -5322,6 +5324,7 @@ const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){ ** vice-versa). */ static int moveToChild(BtCursor *pCur, u32 newPgno){ + int rc; assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPageapPage[pCur->iPage] = pCur->pPage; pCur->ix = 0; pCur->iPage++; - return getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur, - pCur->curPagerFlags); + rc = getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur->curPagerFlags); + assert( pCur->pPage!=0 || rc!=SQLITE_OK ); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(newPgno); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + } + return rc; } #ifdef SQLITE_DEBUG /* -** Page pParent is an internal (non-leaf) tree page. This function +** Page pParent is an internal (non-leaf) tree page. This function ** asserts that page number iChild is the left-child if the iIdx'th ** cell in page pParent. Or, if iIdx is equal to the total number of ** cells in pParent, that page number iChild is the right-child of @@ -5358,7 +5371,7 @@ static void assertParentIndex(MemPage *pParent, int iIdx, Pgno iChild){ } } #else -# define assertParentIndex(x,y,z) +# define assertParentIndex(x,y,z) #endif /* @@ -5376,8 +5389,8 @@ static void moveToParent(BtCursor *pCur){ assert( pCur->iPage>0 ); assert( pCur->pPage ); assertParentIndex( - pCur->apPage[pCur->iPage-1], - pCur->aiIdx[pCur->iPage-1], + pCur->apPage[pCur->iPage-1], + pCur->aiIdx[pCur->iPage-1], pCur->pPage->pgno ); testcase( pCur->aiIdx[pCur->iPage-1] > pCur->apPage[pCur->iPage-1]->nCell ); @@ -5394,19 +5407,19 @@ static void moveToParent(BtCursor *pCur){ ** ** If the table has a virtual root page, then the cursor is moved to point ** to the virtual root page instead of the actual root page. A table has a -** virtual root page when the actual root page contains no cells and a +** virtual root page when the actual root page contains no cells and a ** single child page. This can only happen with the table rooted at page 1. ** -** If the b-tree structure is empty, the cursor state is set to +** If the b-tree structure is empty, the cursor state is set to ** CURSOR_INVALID and this routine returns SQLITE_EMPTY. Otherwise, ** the cursor is set to point to the first cell located on the root ** (or virtual root) page and the cursor state is set to CURSOR_VALID. ** ** If this function returns successfully, it may be assumed that the -** page-header flags indicate that the [virtual] root-page is the expected +** page-header flags indicate that the [virtual] root-page is the expected ** kind of b-tree page (i.e. if when opening the cursor the caller did not ** specify a KeyInfo structure the flags byte is set to 0x05 or 0x0D, -** indicating a table b-tree, or if the caller did specify a KeyInfo +** indicating a table b-tree, or if the caller did specify a KeyInfo ** structure the flags byte is set to 0x02 or 0x0A, indicating an index ** b-tree). */ @@ -5443,7 +5456,7 @@ static int moveToRoot(BtCursor *pCur){ sqlite3BtreeClearCursor(pCur); } rc = getAndInitPage(pCur->pBt, pCur->pgnoRoot, &pCur->pPage, - 0, pCur->curPagerFlags); + pCur->curPagerFlags); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; return rc; @@ -5457,19 +5470,19 @@ static int moveToRoot(BtCursor *pCur){ /* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor ** expected to open it on an index b-tree. Otherwise, if pKeyInfo is ** NULL, the caller expects a table b-tree. If this is not the case, - ** return an SQLITE_CORRUPT error. + ** return an SQLITE_CORRUPT error. ** ** Earlier versions of SQLite assumed that this test could not fail ** if the root page was already loaded when this function was called (i.e. - ** if pCur->iPage>=0). But this is not so if the database is corrupted - ** in such a way that page pRoot is linked into a second b-tree table + ** if pCur->iPage>=0). But this is not so if the database is corrupted + ** in such a way that page pRoot is linked into a second b-tree table ** (or the freelist). */ assert( pRoot->intKey==1 || pRoot->intKey==0 ); if( pRoot->isInit==0 || (pCur->pKeyInfo==0)!=pRoot->intKey ){ return SQLITE_CORRUPT_PAGE(pCur->pPage); } -skip_init: +skip_init: pCur->ix = 0; pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); @@ -5555,7 +5568,7 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ *pRes = 0; rc = moveToLeftmost(pCur); }else if( rc==SQLITE_EMPTY ){ - assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + assert( pCur->pgnoRoot==0 || (pCur->pPage!=0 && pCur->pPage->nCell==0) ); *pRes = 1; rc = SQLITE_OK; } @@ -5591,7 +5604,7 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ /* If the cursor already points to the last entry, this is a no-op. */ if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ #ifdef SQLITE_DEBUG - /* This block serves to assert() that the cursor really does point + /* This block serves to assert() that the cursor really does point ** to the last entry in the b-tree. */ int ii; for(ii=0; iiiPage; ii++){ @@ -5617,7 +5630,7 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ ** before or after the key. ** ** An integer is written into *pRes which is the result of -** comparing the key with the entry to which the cursor is +** comparing the key with the entry to which the cursor is ** pointing. The meaning of the integer written into ** *pRes is as follows: ** @@ -5660,7 +5673,7 @@ int sqlite3BtreeTableMoveto( /* If the requested key is one more than the previous key, then ** try to get there using sqlite3BtreeNext() rather than a full ** binary search. This is an optimization only. The correct answer - ** is still obtained without this case, only a little more slowely */ + ** is still obtained without this case, only a little more slowly. */ if( pCur->info.nKey+1==intKey ){ *pRes = 0; rc = sqlite3BtreeNext(pCur, 0); @@ -5807,10 +5820,10 @@ static int indexCellCompare( ** b-tree page. */ testcase( pCell+nCell+1==pPage->aDataEnd ); c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); - }else if( !(pCell[1] & 0x80) + }else if( !(pCell[1] & 0x80) && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal ){ - /* The record-size field is a 2 byte varint and the record + /* The record-size field is a 2 byte varint and the record ** fits entirely on the main b-tree page. */ testcase( pCell+nCell+2==pPage->aDataEnd ); c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); @@ -5845,7 +5858,7 @@ static int cursorOnLastPage(BtCursor *pCur){ ** before or after the key. ** ** An integer is written into *pRes which is the result of -** comparing the key with the entry to which the cursor is +** comparing the key with the entry to which the cursor is ** pointing. The meaning of the integer written into ** *pRes is as follows: ** @@ -5860,7 +5873,7 @@ static int cursorOnLastPage(BtCursor *pCur){ ** is larger than pIdxKey. ** ** The pIdxKey->eqSeen field is set to 1 if there -** exists an entry in the table that exactly matches pIdxKey. +** exists an entry in the table that exactly matches pIdxKey. */ int sqlite3BtreeIndexMoveto( BtCursor *pCur, /* The cursor to be moved */ @@ -5881,8 +5894,8 @@ int sqlite3BtreeIndexMoveto( xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); pIdxKey->errCode = 0; - assert( pIdxKey->default_rc==1 - || pIdxKey->default_rc==0 + assert( pIdxKey->default_rc==1 + || pIdxKey->default_rc==0 || pIdxKey->default_rc==-1 ); @@ -5910,7 +5923,7 @@ int sqlite3BtreeIndexMoveto( *pRes = c; return SQLITE_OK; /* Cursor already pointing at the correct spot */ } - if( pCur->iPage>0 + if( pCur->iPage>0 && indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0 && pIdxKey->errCode==SQLITE_OK ){ @@ -5964,9 +5977,9 @@ int sqlite3BtreeIndexMoveto( /* The maximum supported page-size is 65536 bytes. This means that ** the maximum number of record bytes stored on an index B-Tree ** page is less than 16384 bytes and may be stored as a 2-byte - ** varint. This information is used to attempt to avoid parsing - ** the entire cell by checking for the cases where the record is - ** stored entirely within the b-tree page by inspecting the first + ** varint. This information is used to attempt to avoid parsing + ** the entire cell by checking for the cases where the record is + ** stored entirely within the b-tree page by inspecting the first ** 2 bytes of the cell. */ nCell = pCell[0]; @@ -5976,10 +5989,10 @@ int sqlite3BtreeIndexMoveto( ** b-tree page. */ testcase( pCell+nCell+1==pPage->aDataEnd ); c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); - }else if( !(pCell[1] & 0x80) + }else if( !(pCell[1] & 0x80) && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal ){ - /* The record-size field is a 2 byte varint and the record + /* The record-size field is a 2 byte varint and the record ** fits entirely on the main b-tree page. */ testcase( pCell+nCell+2==pPage->aDataEnd ); c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); @@ -5987,10 +6000,10 @@ int sqlite3BtreeIndexMoveto( /* The record flows over onto one or more overflow pages. In ** this case the whole cell needs to be parsed, a buffer allocated ** and accessPayload() used to retrieve the record into the - ** buffer before VdbeRecordCompare() can be called. + ** buffer before VdbeRecordCompare() can be called. ** ** If the record is corrupt, the xRecordCompare routine may read - ** up to two varints past the end of the buffer. An extra 18 + ** up to two varints past the end of the buffer. An extra 18 ** bytes of padding is allocated at the end of the buffer in ** case this happens. */ void *pCellKey; @@ -6022,7 +6035,7 @@ int sqlite3BtreeIndexMoveto( c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); sqlite3_free(pCellKey); } - assert( + assert( (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) ); @@ -6056,10 +6069,36 @@ int sqlite3BtreeIndexMoveto( }else{ chldPg = get4byte(findCell(pPage, lwr)); } - pCur->ix = (u16)lwr; - rc = moveToChild(pCur, chldPg); - if( rc ) break; - } + + /* This block is similar to an in-lined version of: + ** + ** pCur->ix = (u16)lwr; + ** rc = moveToChild(pCur, chldPg); + ** if( rc ) break; + */ + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + if( pCur->iPage>=(BTCURSOR_MAX_DEPTH-1) ){ + return SQLITE_CORRUPT_BKPT; + } + pCur->aiIdx[pCur->iPage] = (u16)lwr; + pCur->apPage[pCur->iPage] = pCur->pPage; + pCur->ix = 0; + pCur->iPage++; + rc = getAndInitPage(pCur->pBt, chldPg, &pCur->pPage, pCur->curPagerFlags); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(chldPg); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + break; + } + /* + ***** End of in-lined moveToChild() call */ + } moveto_index_finish: pCur->info.nSize = 0; assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); @@ -6084,7 +6123,7 @@ int sqlite3BtreeEof(BtCursor *pCur){ /* ** Return an estimate for the number of rows in the table that pCur is -** pointing to. Return a negative number if no estimate is currently +** pointing to. Return a negative number if no estimate is currently ** available. */ i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ @@ -6108,7 +6147,7 @@ i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ } /* -** Advance the cursor to the next entry in the database. +** Advance the cursor to the next entry in the database. ** Return value: ** ** SQLITE_OK success @@ -6297,7 +6336,7 @@ int sqlite3BtreePrevious(BtCursor *pCur, int flags){ ** SQLITE_OK is returned on success. Any other return value indicates ** an error. *ppPage is set to NULL in the event of an error. ** -** If the "nearby" parameter is not 0, then an effort is made to +** If the "nearby" parameter is not 0, then an effort is made to ** locate a page close to the page number "nearby". This can be used in an ** attempt to keep related pages close to each other in the database file, ** which in turn can make database access faster. @@ -6339,7 +6378,7 @@ static int allocateBtreePage( Pgno iTrunk; u8 searchList = 0; /* If the free-list must be searched for 'nearby' */ u32 nSearch = 0; /* Count of the number of search attempts */ - + /* If eMode==BTALLOC_EXACT and a query of the pointer-map ** shows that the page 'nearby' is somewhere on the free-list, then ** the entire-list will be searched for that page. @@ -6402,8 +6441,8 @@ static int allocateBtreePage( ** is the number of leaf page pointers to follow. */ k = get4byte(&pTrunk->aData[4]); if( k==0 && !searchList ){ - /* The trunk has no leaves and the list is not being searched. - ** So extract the trunk page itself and use it as the newly + /* The trunk has no leaves and the list is not being searched. + ** So extract the trunk page itself and use it as the newly ** allocated page */ assert( pPrevTrunk==0 ); rc = sqlite3PagerWrite(pTrunk->pDbPage); @@ -6420,8 +6459,8 @@ static int allocateBtreePage( rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; #ifndef SQLITE_OMIT_AUTOVACUUM - }else if( searchList - && (nearby==iTrunk || (iTrunkaData[0], &pTrunk->aData[0], 4); } }else{ - /* The trunk page is required by the caller but it contains + /* The trunk page is required by the caller but it contains ** pointers to free-list leaves. The first leaf becomes a trunk ** page in this case. */ MemPage *pNewTrunk; Pgno iNewTrunk = get4byte(&pTrunk->aData[8]); - if( iNewTrunk>mxPage ){ + if( iNewTrunk>mxPage ){ rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; } @@ -6520,8 +6559,8 @@ static int allocateBtreePage( goto end_allocate_page; } testcase( iPage==mxPage ); - if( !searchList - || (iPage==nearby || (iPagepPage1; /* Local reference to page 1 */ MemPage *pPage; /* Page being freed. May be NULL. */ int rc; /* Return Code */ @@ -6740,7 +6779,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ /* If control flows to this point, then it was not possible to add the ** the page being freed as a leaf page of the first trunk in the free-list. - ** Possibly because the free-list is empty, or possibly because the + ** Possibly because the free-list is empty, or possibly because the ** first trunk in the free-list is full. Either way, the page being freed ** will become the new first trunk page in the free-list. */ @@ -6797,15 +6836,15 @@ static SQLITE_NOINLINE int clearCellOverflow( assert( pBt->usableSize > 4 ); ovflPageSize = pBt->usableSize - 4; nOvfl = (pInfo->nPayload - pInfo->nLocal + ovflPageSize - 1)/ovflPageSize; - assert( nOvfl>0 || + assert( nOvfl>0 || (CORRUPT_DB && (pInfo->nPayload + ovflPageSize)btreePagecount(pBt) ){ - /* 0 is not a legal page number and page 1 cannot be an - ** overflow page. Therefore if ovflPgno<2 or past the end of the + /* 0 is not a legal page number and page 1 cannot be an + ** overflow page. Therefore if ovflPgno<2 or past the end of the ** file the database must be corrupt. */ return SQLITE_CORRUPT_BKPT; } @@ -6817,11 +6856,11 @@ static SQLITE_NOINLINE int clearCellOverflow( if( ( pOvfl || ((pOvfl = btreePageLookup(pBt, ovflPgno))!=0) ) && sqlite3PagerPageRefcount(pOvfl->pDbPage)!=1 ){ - /* There is no reason any cursor should have an outstanding reference + /* There is no reason any cursor should have an outstanding reference ** to an overflow page belonging to a cell that is being deleted/updated. - ** So if there exists more than one reference to this page, then it - ** must not really be an overflow page and the database must be corrupt. - ** It is helpful to detect this before calling freePage2(), as + ** So if there exists more than one reference to this page, then it + ** must not really be an overflow page and the database must be corrupt. + ** It is helpful to detect this before calling freePage2(), as ** freePage2() may zero the page contents if secure-delete mode is ** enabled. If this 'overflow' page happens to be a page that the ** caller is iterating through or using in some other way, this @@ -6843,7 +6882,7 @@ static SQLITE_NOINLINE int clearCellOverflow( /* Call xParseCell to compute the size of a cell. If the cell contains ** overflow, then invoke cellClearOverflow to clear out that overflow. -** STore the result code (SQLITE_OK or some error code) in rc. +** Store the result code (SQLITE_OK or some error code) in rc. ** ** Implemented as macro to force inlining for performance. */ @@ -6907,7 +6946,7 @@ static int fillInCell( pSrc = pX->pKey; nHeader += putVarint32(&pCell[nHeader], nPayload); } - + /* Fill in the payload */ pPayload = &pCell[nHeader]; if( nPayload<=pPage->maxLocal ){ @@ -6998,8 +7037,8 @@ static int fillInCell( if( pBt->autoVacuum ){ do{ pgnoOvfl++; - } while( - PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt) + } while( + PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt) ); } #endif @@ -7007,9 +7046,9 @@ static int fillInCell( #ifndef SQLITE_OMIT_AUTOVACUUM /* If the database supports auto-vacuum, and the second or subsequent ** overflow page is being allocated, add an entry to the pointer-map - ** for that page now. + ** for that page now. ** - ** If this is the first overflow page, then write a partial entry + ** If this is the first overflow page, then write a partial entry ** to the pointer-map. If we write nothing to this pointer-map slot, ** then the optimistic overflow chain processing in clearCell() ** may misinterpret the uninitialized values and delete the @@ -7110,8 +7149,8 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ ** will not fit, then make a copy of the cell content into pTemp if ** pTemp is not null. Regardless of pTemp, allocate a new entry ** in pPage->apOvfl[] and make it point to the cell content (either -** in pTemp or the original pCell) and also record its index. -** Allocating a new entry in pPage->aCell[] implies that +** in pTemp or the original pCell) and also record its index. +** Allocating a new entry in pPage->aCell[] implies that ** pPage->nOverflow is incremented. ** ** The insertCellFast() routine below works exactly the same as @@ -7423,16 +7462,16 @@ static u16 cachedCellSize(CellArray *p, int N){ } /* -** Array apCell[] contains pointers to nCell b-tree page cells. The +** Array apCell[] contains pointers to nCell b-tree page cells. The ** szCell[] array contains the size in bytes of each cell. This function ** replaces the current contents of page pPg with the contents of the cell ** array. ** ** Some of the cells in apCell[] may currently be stored in pPg. This -** function works around problems caused by this by making a copy of any +** function works around problems caused by this by making a copy of any ** such cells before overwriting the page data. ** -** The MemPage.nFree field is invalidated by this function. It is the +** The MemPage.nFree field is invalidated by this function. It is the ** responsibility of the caller to set it correctly. */ static int rebuildPage( @@ -7459,7 +7498,7 @@ static int rebuildPage( if( NEVER(j>(u32)usableSize) ){ j = 0; } memcpy(&pTmp[j], &aData[j], usableSize - j); - for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kixNx[k]<=i; k++){} pSrcEnd = pCArray->apEnd[k]; pData = pEnd; @@ -7514,7 +7553,7 @@ static int rebuildPage( ** cell in the array. It is the responsibility of the caller to ensure ** that it is safe to overwrite this part of the cell-pointer array. ** -** When this function is called, *ppData points to the start of the +** When this function is called, *ppData points to the start of the ** content area on page pPg. If the size of the content area is extended, ** *ppData is updated to point to the new start of the content area ** before returning. @@ -7522,7 +7561,7 @@ static int rebuildPage( ** Finally, argument pBegin points to the byte immediately following the ** end of the space required by this page for the cell-pointer area (for ** all cells - not just those inserted by the current call). If the content -** area must be extended to before this point in order to accomodate all +** area must be extended to before this point in order to accommodate all ** cells in apCell[], then the cells do not fit and non-zero is returned. */ static int pageInsertArray( @@ -7542,7 +7581,7 @@ static int pageInsertArray( u8 *pEnd; /* Maximum extent of cell data */ assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */ if( iEnd<=iFirst ) return 0; - for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kixNx[k]<=i ; k++){} pEnd = pCArray->apEnd[k]; while( 1 /*Exit by break*/ ){ int sz, rc; @@ -7798,12 +7837,12 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); assert( pPage->nOverflow==1 ); - + if( pPage->nCell==0 ) return SQLITE_CORRUPT_BKPT; /* dbfuzz001.test */ assert( pPage->nFree>=0 ); assert( pParent->nFree>=0 ); - /* Allocate a new page. This page will become the right-sibling of + /* Allocate a new page. This page will become the right-sibling of ** pPage. Make the parent page writable, so that the new divider cell ** may be inserted. If both these operations are successful, proceed. */ @@ -7834,10 +7873,10 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ pNew->nFree = pBt->usableSize - pNew->cellOffset - 2 - szCell; /* If this is an auto-vacuum database, update the pointer map - ** with entries for the new page, and any pointer from the + ** with entries for the new page, and any pointer from the ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents - ** of the parent page are still manipulated by thh code below. + ** of the parent page are still manipulated by the code below. ** That is Ok, at this point the parent page is guaranteed to ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. @@ -7848,14 +7887,14 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ ptrmapPutOvflPtr(pNew, pNew, pCell, &rc); } } - + /* Create a divider cell to insert into pParent. The divider cell ** consists of a 4-byte page number (the page number of pPage) and ** a variable length key value (which must be the same value as the ** largest key on pPage). ** - ** To find the largest key value on pPage, first find the right-most - ** cell on pPage. The first two fields of this cell are the + ** To find the largest key value on pPage, first find the right-most + ** cell on pPage. The first two fields of this cell are the ** record-length (a variable length integer at most 32-bits in size) ** and the key value (a variable length integer, may have any value). ** The first of the while(...) loops below skips over the record-length @@ -7876,7 +7915,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ /* Set the right-child pointer of pParent to point to the new page. */ put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew); - + /* Release the reference to the new page. */ releasePage(pNew); } @@ -7888,7 +7927,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ #if 0 /* ** This function does not contribute anything to the operation of SQLite. -** it is sometimes activated temporarily while debugging code responsible +** it is sometimes activated temporarily while debugging code responsible ** for setting pointer-map entries. */ static int ptrmapCheckPages(MemPage **apPage, int nPage){ @@ -7903,7 +7942,7 @@ static int ptrmapCheckPages(MemPage **apPage, int nPage){ for(j=0; jnCell; j++){ CellInfo info; u8 *z; - + z = findCell(pPage, j); pPage->xParseCell(pPage, z, &info); if( info.nLocalpgno==1) ? 100 : 0); int rc; int iData; - - + + assert( pFrom->isInit ); assert( pFrom->nFree>=iToHdr ); assert( get2byte(&aFrom[iFromHdr+5]) <= (int)pBt->usableSize ); - + /* Copy the b-tree node content from page pFrom to page pTo. */ iData = get2byte(&aFrom[iFromHdr+5]); memcpy(&aTo[iData], &aFrom[iData], pBt->usableSize-iData); memcpy(&aTo[iToHdr], &aFrom[iFromHdr], pFrom->cellOffset + 2*pFrom->nCell); - + /* Reinitialize page pTo so that the contents of the MemPage structure ** match the new data. The initialization of pTo can actually fail under - ** fairly obscure circumstances, even though it is a copy of initialized + ** fairly obscure circumstances, even though it is a copy of initialized ** page pFrom. */ pTo->isInit = 0; @@ -7976,7 +8015,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ *pRC = rc; return; } - + /* If this is an auto-vacuum database, update the pointer-map entries ** for any b-tree or overflow pages that pTo now contains the pointers to. */ @@ -7991,13 +8030,13 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ ** (hereafter "the page") and up to 2 siblings so that all pages have about the ** same amount of free space. Usually a single sibling on either side of the ** page are used in the balancing, though both siblings might come from one -** side if the page is the first or last child of its parent. If the page +** side if the page is the first or last child of its parent. If the page ** has fewer than 2 siblings (something which can only happen if the page ** is a root page or a child of a root page) then all available siblings ** participate in the balancing. ** -** The number of siblings of the page might be increased or decreased by -** one or two in an effort to keep pages nearly full but not over full. +** The number of siblings of the page might be increased or decreased by +** one or two in an effort to keep pages nearly full but not over full. ** ** Note that when this routine is called, some of the cells on the page ** might not actually be stored in MemPage.aData[]. This can happen @@ -8008,7 +8047,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ ** inserted into or removed from the parent page (pParent). Doing so ** may cause the parent page to become overfull or underfull. If this ** happens, it is the responsibility of the caller to invoke the correct -** balancing routine to fix this problem (see the balance() routine). +** balancing routine to fix this problem (see the balance() routine). ** ** If this routine fails for any reason, it might leave the database ** in a corrupted state. So if this routine fails, the database should @@ -8023,7 +8062,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ ** of the page-size, the aOvflSpace[] buffer is guaranteed to be large ** enough for all overflow cells. ** -** If aOvflSpace is set to a null pointer, this function returns +** If aOvflSpace is set to a null pointer, this function returns ** SQLITE_NOMEM. */ static int balance_nonroot( @@ -8067,7 +8106,7 @@ static int balance_nonroot( assert( sqlite3PagerIswriteable(pParent->pDbPage) ); /* At this point pParent may have at most one overflow cell. And if - ** this overflow cell is present, it must be the cell with + ** this overflow cell is present, it must be the cell with ** index iParentIdx. This scenario comes about when this function ** is called (indirectly) from sqlite3BtreeDelete(). */ @@ -8079,11 +8118,11 @@ static int balance_nonroot( } assert( pParent->nFree>=0 ); - /* Find the sibling pages to balance. Also locate the cells in pParent - ** that divide the siblings. An attempt is made to find NN siblings on - ** either side of pPage. More siblings are taken from one side, however, + /* Find the sibling pages to balance. Also locate the cells in pParent + ** that divide the siblings. An attempt is made to find NN siblings on + ** either side of pPage. More siblings are taken from one side, however, ** if there are fewer than NN siblings on the other side. If pParent - ** has NB or fewer children then all children of pParent are taken. + ** has NB or fewer children then all children of pParent are taken. ** ** This loop also drops the divider cells from the parent page. This ** way, the remainder of the function does not have to deal with any @@ -8095,7 +8134,7 @@ static int balance_nonroot( nxDiv = 0; }else{ assert( bBulk==0 || bBulk==1 ); - if( iParentIdx==0 ){ + if( iParentIdx==0 ){ nxDiv = 0; }else if( iParentIdx==i ){ nxDiv = i-2+bBulk; @@ -8113,7 +8152,7 @@ static int balance_nonroot( pgno = get4byte(pRight); while( 1 ){ if( rc==SQLITE_OK ){ - rc = getAndInitPage(pBt, pgno, &apOld[i], 0, 0); + rc = getAndInitPage(pBt, pgno, &apOld[i], 0); } if( rc ){ memset(apOld, 0, (i+1)*sizeof(MemPage*)); @@ -8144,7 +8183,7 @@ static int balance_nonroot( ** This is safe because dropping a cell only overwrites the first ** four bytes of it, and this function does not need the first ** four bytes of the divider cell. So the pointer is safe to use - ** later on. + ** later on. ** ** But not if we are in secure-delete mode. In secure-delete mode, ** the dropCell() routine will overwrite the entire cell with zeroes. @@ -8307,7 +8346,7 @@ static int balance_nonroot( ** Figure out the number of pages needed to hold all b.nCell cells. ** Store this number in "k". Also compute szNew[] which is the total ** size of all cells on the i-th page and cntNew[] which is the index - ** in b.apCell[] of the cell that divides page i from page i+1. + ** in b.apCell[] of the cell that divides page i from page i+1. ** cntNew[k] should equal b.nCell. ** ** Values computed by this block: @@ -8317,7 +8356,7 @@ static int balance_nonroot( ** cntNew[i]: Index in b.apCell[] and b.szCell[] for the first cell to ** the right of the i-th sibling page. ** usableSpace: Number of bytes of space available on each sibling. - ** + ** */ usableSpace = pBt->usableSize - 12 + leafCorrection; for(i=k=0; i0), or ** (2) pPage is a virtual root page. A virtual root page is when @@ -8478,15 +8517,15 @@ static int balance_nonroot( } /* - ** Reassign page numbers so that the new pages are in ascending order. + ** Reassign page numbers so that the new pages are in ascending order. ** This helps to keep entries in the disk file in order so that a scan - ** of the table is closer to a linear scan through the file. That in turn + ** of the table is closer to a linear scan through the file. That in turn ** helps the operating system to deliver pages from the disk more rapidly. ** ** An O(N*N) sort algorithm is used, but since N is never more than NB+2 ** (5), that is not a performance concern. ** - ** When NB==3, this one optimization makes the database about 25% faster + ** When NB==3, this one optimization makes the database about 25% faster ** for large insertions and deletions. */ for(i=0; ipgno); /* If the sibling pages are not leaves, ensure that the right-child pointer - ** of the right-most new sibling page is set to the value that was + ** of the right-most new sibling page is set to the value that was ** originally in the same field of the right-most old sibling page. */ if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){ MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1]; memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4); } - /* Make any required updates to pointer map entries associated with + /* Make any required updates to pointer map entries associated with ** cells stored on sibling pages following the balance operation. Pointer ** map entries associated with divider cells are set by the insertCell() ** routine. The associated pointer map entries are: @@ -8556,9 +8595,9 @@ static int balance_nonroot( ** b) if the sibling pages are not leaves, the child page associated ** with the cell. ** - ** If the sibling pages are not leaves, then the pointer map entry - ** associated with the right-child of each sibling may also need to be - ** updated. This happens below, after the sibling pages have been + ** If the sibling pages are not leaves, then the pointer map entry + ** associated with the right-child of each sibling may also need to be + ** updated. This happens below, after the sibling pages have been ** populated, not here. */ if( ISAUTOVACUUM(pBt) ){ @@ -8583,7 +8622,7 @@ static int balance_nonroot( } /* Cell pCell is destined for new sibling page pNew. Originally, it - ** was either part of sibling page iOld (possibly an overflow cell), + ** was either part of sibling page iOld (possibly an overflow cell), ** or else the divider cell to the left of sibling page iOld. So, ** if sibling page iOld had the same page number as pNew, and if ** pCell really was a part of sibling page iOld (not a divider or @@ -8620,9 +8659,9 @@ static int balance_nonroot( if( !pNew->leaf ){ memcpy(&pNew->aData[8], pCell, 4); }else if( leafData ){ - /* If the tree is a leaf-data tree, and the siblings are leaves, - ** then there is no divider cell in b.apCell[]. Instead, the divider - ** cell consists of the integer key for the right-most cell of + /* If the tree is a leaf-data tree, and the siblings are leaves, + ** then there is no divider cell in b.apCell[]. Instead, the divider + ** cell consists of the integer key for the right-most cell of ** the sibling-page assembled above only. */ CellInfo info; @@ -8635,9 +8674,9 @@ static int balance_nonroot( pCell -= 4; /* Obscure case for non-leaf-data trees: If the cell at pCell was ** previously stored on a leaf node, and its reported size was 4 - ** bytes, then it may actually be smaller than this + ** bytes, then it may actually be smaller than this ** (see btreeParseCellPtr(), 4 bytes is the minimum size of - ** any cell). But it is important to pass the correct size to + ** any cell). But it is important to pass the correct size to ** insertCell(), so reparse the cell now. ** ** This can only happen for b-trees used to evaluate "IN (SELECT ...)" @@ -8652,9 +8691,9 @@ static int balance_nonroot( iOvflSpace += sz; assert( sz<=pBt->maxLocal+23 ); assert( iOvflSpace <= (int)pBt->pageSize ); - for(k=0; b.ixNx[k]<=j && ALWAYS(k=0 && iPg=1 || i>=0 ); + assert( iPg=0 /* On the upwards pass, or... */ || cntOld[iPg-1]>=cntNew[iPg-1] /* Condition (1) is true */ @@ -8735,8 +8776,8 @@ static int balance_nonroot( ** b-tree structure by one. This is described as the "balance-shallower" ** sub-algorithm in some documentation. ** - ** If this is an auto-vacuum database, the call to copyNodeContent() - ** sets all pointer-map entries corresponding to database image pages + ** If this is an auto-vacuum database, the call to copyNodeContent() + ** sets all pointer-map entries corresponding to database image pages ** for which the pointer is stored within the content being copied. ** ** It is critical that the child page be defragmented before being @@ -8747,7 +8788,7 @@ static int balance_nonroot( assert( nNew==1 || CORRUPT_DB ); rc = defragmentPage(apNew[0], -1); testcase( rc!=SQLITE_OK ); - assert( apNew[0]->nFree == + assert( apNew[0]->nFree == (get2byteNotZero(&apNew[0]->aData[5]) - apNew[0]->cellOffset - apNew[0]->nCell*2) || rc!=SQLITE_OK @@ -8777,7 +8818,7 @@ static int balance_nonroot( #if 0 if( ISAUTOVACUUM(pBt) && rc==SQLITE_OK && apNew[0]->isInit ){ /* The ptrmapCheckPages() contains assert() statements that verify that - ** all pointer map pages are set correctly. This is helpful while + ** all pointer map pages are set correctly. This is helpful while ** debugging. This is usually disabled because a corrupt database may ** cause an assert() statement to fail. */ ptrmapCheckPages(apNew, nNew); @@ -8807,15 +8848,15 @@ static int balance_nonroot( ** ** A new child page is allocated and the contents of the current root ** page, including overflow cells, are copied into the child. The root -** page is then overwritten to make it an empty page with the right-child +** page is then overwritten to make it an empty page with the right-child ** pointer pointing to the new page. ** -** Before returning, all pointer-map entries corresponding to pages +** Before returning, all pointer-map entries corresponding to pages ** that the new child-page now contains pointers to are updated. The ** entry corresponding to the new right-child pointer of the root ** page is also updated. ** -** If successful, *ppChild is set to contain a reference to the child +** If successful, *ppChild is set to contain a reference to the child ** page and SQLITE_OK is returned. In this case the caller is required ** to call releasePage() on *ppChild exactly once. If an error occurs, ** an error code is returned and *ppChild is set to 0. @@ -8829,7 +8870,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ assert( pRoot->nOverflow>0 ); assert( sqlite3_mutex_held(pBt->mutex) ); - /* Make pRoot, the root page of the b-tree, writable. Allocate a new + /* Make pRoot, the root page of the b-tree, writable. Allocate a new ** page that will become the new right-child of pPage. Copy the contents ** of the node stored on pRoot into the new child page. */ @@ -8894,7 +8935,7 @@ static int anotherValidCursor(BtCursor *pCur){ /* ** The page that pCur currently points to has just been modified in ** some way. This function figures out if this modification means the -** tree needs to be balanced, and if so calls the appropriate balancing +** tree needs to be balanced, and if so calls the appropriate balancing ** routine. Balancing routines are: ** ** balance_quick() @@ -8926,7 +8967,7 @@ static int balance(BtCursor *pCur){ ** balance_deeper() function to create a new child for the root-page ** and copy the current contents of the root-page to it. The ** next iteration of the do-loop will balance the child page. - */ + */ assert( balance_deeper_called==0 ); VVA_ONLY( balance_deeper_called++ ); rc = balance_deeper(pPage, &pCur->apPage[1]); @@ -8943,7 +8984,7 @@ static int balance(BtCursor *pCur){ } }else if( sqlite3PagerPageRefcount(pPage->pDbPage)>1 ){ /* The page being written is not a root page, and there is currently - ** more than one reference to it. This only happens if the page is one + ** more than one reference to it. This only happens if the page is one ** of its own ancestor pages. Corruption. */ rc = SQLITE_CORRUPT_BKPT; }else{ @@ -8965,17 +9006,17 @@ static int balance(BtCursor *pCur){ /* Call balance_quick() to create a new sibling of pPage on which ** to store the overflow cell. balance_quick() inserts a new cell ** into pParent, which may cause pParent overflow. If this - ** happens, the next iteration of the do-loop will balance pParent + ** happens, the next iteration of the do-loop will balance pParent ** use either balance_nonroot() or balance_deeper(). Until this ** happens, the overflow cell is stored in the aBalanceQuickSpace[] - ** buffer. + ** buffer. ** ** The purpose of the following assert() is to check that only a ** single call to balance_quick() is made for each call to this ** function. If this were not verified, a subtle bug involving reuse ** of the aBalanceQuickSpace[] might sneak in. */ - assert( balance_quick_called==0 ); + assert( balance_quick_called==0 ); VVA_ONLY( balance_quick_called++ ); rc = balance_quick(pParent, pPage, aBalanceQuickSpace); }else @@ -8986,15 +9027,15 @@ static int balance(BtCursor *pCur){ ** modifying the contents of pParent, which may cause pParent to ** become overfull or underfull. The next iteration of the do-loop ** will balance the parent page to correct this. - ** + ** ** If the parent page becomes overfull, the overflow cell or cells - ** are stored in the pSpace buffer allocated immediately below. + ** are stored in the pSpace buffer allocated immediately below. ** A subsequent iteration of the do-loop will deal with this by ** calling balance_nonroot() (balance_deeper() may be called first, ** but it doesn't deal with overflow cells - just moves them to a - ** different page). Once this subsequent call to balance_nonroot() + ** different page). Once this subsequent call to balance_nonroot() ** has completed, it is safe to release the pSpace buffer used by - ** the previous call, as the overflow cell data will have been + ** the previous call, as the overflow cell data will have been ** copied either into the body of a database page or into the new ** pSpace buffer passed to the latter call to balance_nonroot(). */ @@ -9002,9 +9043,9 @@ static int balance(BtCursor *pCur){ rc = balance_nonroot(pParent, iIdx, pSpace, iPage==1, pCur->hints&BTREE_BULKLOAD); if( pFree ){ - /* If pFree is not NULL, it points to the pSpace buffer used + /* If pFree is not NULL, it points to the pSpace buffer used ** by a previous call to balance_nonroot(). Its contents are - ** now stored either on real database pages or within the + ** now stored either on real database pages or within the ** new pSpace buffer, so it may be safely freed here. */ sqlite3PageFree(pFree); } @@ -9044,7 +9085,7 @@ static int btreeOverwriteContent( ){ int nData = pX->nData - iOffset; if( nData<=0 ){ - /* Overwritting with zeros */ + /* Overwriting with zeros */ int i; for(i=0; ipData to write */ @@ -9123,7 +9164,7 @@ static SQLITE_NOINLINE int btreeOverwriteOverflowCell( if( rc ) return rc; iOffset += ovflPageSize; }while( iOffsetpKeyInfo==0 ){ assert( pX->pKey==0 ); - /* If this is an insert into a table b-tree, invalidate any incrblob + /* If this is an insert into a table b-tree, invalidate any incrblob ** cursors open on the row being replaced */ if( p->hasIncrblobCur ){ invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); } - /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing + /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing ** to a row with the same key as the new entry being inserted. */ #ifdef SQLITE_DEBUG @@ -9284,14 +9325,14 @@ int sqlite3BtreeInsert( ** to an adjacent cell. Move the cursor so that it is pointing either ** to the cell to be overwritten or an adjacent cell. */ - rc = sqlite3BtreeTableMoveto(pCur, pX->nKey, + rc = sqlite3BtreeTableMoveto(pCur, pX->nKey, (flags & BTREE_APPEND)!=0, &loc); if( rc ) return rc; } }else{ /* This is an index or a WITHOUT ROWID table */ - /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing + /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing ** to a row with the same key as the new entry being inserted. */ assert( (flags & BTREE_SAVEPOSITION)==0 || loc==0 ); @@ -9311,7 +9352,7 @@ int sqlite3BtreeInsert( r.eqSeen = 0; rc = sqlite3BtreeIndexMoveto(pCur, &r, &loc); }else{ - rc = btreeMoveto(pCur, pX->pKey, pX->nKey, + rc = btreeMoveto(pCur, pX->pKey, pX->nKey, (flags & BTREE_APPEND)!=0, &loc); } if( rc ) return rc; @@ -9332,7 +9373,7 @@ int sqlite3BtreeInsert( } } } - assert( pCur->eState==CURSOR_VALID + assert( pCur->eState==CURSOR_VALID || (pCur->eState==CURSOR_INVALID && loc) || CORRUPT_DB ); pPage = pCur->pPage; @@ -9393,14 +9434,14 @@ int sqlite3BtreeInsert( BTREE_CLEAR_CELL(rc, pPage, oldCell, info); testcase( pCur->curFlags & BTCF_ValidOvfl ); invalidateOverflowCache(pCur); - if( info.nSize==szNew && info.nLocal==info.nPayload + if( info.nSize==szNew && info.nLocal==info.nPayload && (!ISAUTOVACUUM(p->pBt) || szNewminLocal) ){ /* Overwrite the old cell with the new if they are the same size. ** We could also try to do this if the old cell is smaller, then add ** the leftover space to the free list. But experiments show that ** doing that is no faster then skipping this optimization and just - ** calling dropCell() and insertCell(). + ** calling dropCell() and insertCell(). ** ** This optimization cannot be used on an autovacuum database if the ** new entry uses overflow pages, as the insertCell() call below is @@ -9428,7 +9469,7 @@ int sqlite3BtreeInsert( assert( pPage->nOverflow==0 || rc==SQLITE_OK ); assert( rc!=SQLITE_OK || pPage->nCell>0 || pPage->nOverflow>0 ); - /* If no error has occurred and pPage has an overflow cell, call balance() + /* If no error has occurred and pPage has an overflow cell, call balance() ** to redistribute the cells within the tree. Since balance() may move ** the cursor, zero the BtCursor.info.nSize and BTCF_ValidNKey ** variables. @@ -9454,7 +9495,7 @@ int sqlite3BtreeInsert( rc = balance(pCur); /* Must make sure nOverflow is reset to zero even if the balance() - ** fails. Internal data structure corruption will result otherwise. + ** fails. Internal data structure corruption will result otherwise. ** Also, set the cursor state to invalid. This stops saveCursorPosition() ** from trying to save the current position of the cursor. */ pCur->pPage->nOverflow = 0; @@ -9534,14 +9575,14 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ pPgnoOut = &aOut[nOut]; pBt->nPreformatSize += 4; } - + if( nRem>nIn ){ if( aIn+nIn+4>pSrc->pPage->aDataEnd ){ return SQLITE_CORRUPT_BKPT; } ovflIn = get4byte(&pSrc->info.pPayload[nIn]); } - + do { nRem -= nOut; do{ @@ -9566,7 +9607,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ } } }while( rc==SQLITE_OK && nOut>0 ); - + if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ Pgno pgnoNew; MemPage *pNew = 0; @@ -9585,7 +9626,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ } } }while( nRem>0 && rc==SQLITE_OK ); - + releasePage(pPageOut); sqlite3PagerUnref(pPageIn); return rc; @@ -9593,7 +9634,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ } /* -** Delete the entry that the cursor is pointing to. +** Delete the entry that the cursor is pointing to. ** ** If the BTREE_SAVEPOSITION bit of the flags parameter is zero, then ** the cursor is left pointing at an arbitrary location after the delete. @@ -9611,12 +9652,12 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ */ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ Btree *p = pCur->pBtree; - BtShared *pBt = p->pBt; + BtShared *pBt = p->pBt; int rc; /* Return code */ MemPage *pPage; /* Page to delete cell from */ unsigned char *pCell; /* Pointer to cell to delete */ int iCellIdx; /* Index of cell to delete */ - int iCellDepth; /* Depth of node containing pCell */ + int iCellDepth; /* Depth of node containing pCell */ CellInfo info; /* Size of the cell being deleted */ u8 bPreserve; /* Keep cursor valid. 2 for CURSOR_SKIPNEXT */ @@ -9655,8 +9696,8 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ /* If the BTREE_SAVEPOSITION bit is on, then the cursor position must ** be preserved following this delete operation. If the current delete ** will cause a b-tree rebalance, then this is done by saving the cursor - ** key and leaving the cursor in CURSOR_REQUIRESEEK state before - ** returning. + ** key and leaving the cursor in CURSOR_REQUIRESEEK state before + ** returning. ** ** If the current delete will not cause a rebalance, then the cursor ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately @@ -9670,7 +9711,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ */ bPreserve = (flags & BTREE_SAVEPOSITION)!=0; if( bPreserve ){ - if( !pPage->leaf + if( !pPage->leaf || (pPage->nFree+pPage->xCellSize(pPage,pCell)+2) > (int)(pBt->usableSize*2/3) || pPage->nCell==1 /* See dbfuzz001.test for a test case */ @@ -9766,7 +9807,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** on the leaf node first. If the balance proceeds far enough up the ** tree that we can be sure that any problem in the internal node has ** been corrected, so be it. Otherwise, after balancing the leaf node, - ** walk the cursor up the tree to the internal node and balance it as + ** walk the cursor up the tree to the internal node and balance it as ** well. */ assert( pCur->pPage->nOverflow==0 ); assert( pCur->pPage->nFree>=0 ); @@ -9827,7 +9868,7 @@ static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){ MemPage *pRoot; Pgno pgnoRoot; int rc; - int ptfFlags; /* Page-type flage for the root page of new table */ + int ptfFlags; /* Page-type flags for the root page of new table */ assert( sqlite3BtreeHoldsMutex(p) ); assert( pBt->inTransaction==TRANS_WRITE ); @@ -9930,7 +9971,7 @@ static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){ } }else{ pRoot = pPageMove; - } + } /* Update the pointer-map and meta-data with the new root-page number. */ ptrmapPut(pBt, pgnoRoot, PTRMAP_ROOTPAGE, 0, &rc); @@ -9996,9 +10037,9 @@ static int clearDatabasePage( if( pgno>btreePagecount(pBt) ){ return SQLITE_CORRUPT_BKPT; } - rc = getAndInitPage(pBt, pgno, &pPage, 0, 0); + rc = getAndInitPage(pBt, pgno, &pPage, 0); if( rc ) return rc; - if( (pBt->openFlags & BTREE_SINGLE)==0 + if( (pBt->openFlags & BTREE_SINGLE)==0 && sqlite3PagerPageRefcount(pPage->pDbPage) != (1 + (pgno==1)) ){ rc = SQLITE_CORRUPT_BKPT; @@ -10085,12 +10126,12 @@ int sqlite3BtreeClearTableOfCursor(BtCursor *pCur){ ** cursors on the table. ** ** If AUTOVACUUM is enabled and the page at iTable is not the last -** root page in the database file, then the last root page +** root page in the database file, then the last root page ** in the database file is moved into the slot formerly occupied by ** iTable and that last slot formerly occupied by the last root page ** is added to the freelist instead of iTable. In this say, all ** root pages are kept at the beginning of the database file, which -** is necessary for AUTOVACUUM to work right. *piMoved is set to the +** is necessary for AUTOVACUUM to work right. *piMoved is set to the ** page number that used to be the last root page in the file before ** the move. If no page gets moved, *piMoved is set to 0. ** The last root page is recorded in meta[3] and the value of @@ -10128,7 +10169,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ if( iTable==maxRootPgno ){ /* If the table being dropped is the table with the largest root-page - ** number in the database, put the root page on the free list. + ** number in the database, put the root page on the free list. */ freePage(pPage, &rc); releasePage(pPage); @@ -10137,7 +10178,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ } }else{ /* The table being dropped does not have the largest root-page - ** number in the database. So move the page that does into the + ** number in the database. So move the page that does into the ** gap left by the deleted root-page. */ MemPage *pMove; @@ -10179,7 +10220,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ releasePage(pPage); } #endif - return rc; + return rc; } int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){ int rc; @@ -10198,7 +10239,7 @@ int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){ ** is the number of free pages currently in the database. Meta[1] ** through meta[15] are available for use by higher layers. Meta[0] ** is read-only, the others are read/write. -** +** ** The schema layer numbers meta values differently. At the schema ** layer (and the SetCookie and ReadCookie opcodes) the number of ** free pages is not visible. So Cookie[0] is the same as Meta[1]. @@ -10268,7 +10309,7 @@ int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){ ** The first argument, pCur, is a cursor opened on some b-tree. Count the ** number of entries in the b-tree and write the result to *pnEntry. ** -** SQLITE_OK is returned if the operation is successfully executed. +** SQLITE_OK is returned if the operation is successfully executed. ** Otherwise, if an error is encountered (i.e. an IO error or database ** corruption) an SQLite error code is returned. */ @@ -10283,13 +10324,13 @@ int sqlite3BtreeCount(sqlite3 *db, BtCursor *pCur, i64 *pnEntry){ } /* Unless an error occurs, the following loop runs one iteration for each - ** page in the B-Tree structure (not including overflow pages). + ** page in the B-Tree structure (not including overflow pages). */ while( rc==SQLITE_OK && !AtomicLoad(&db->u1.isInterrupted) ){ int iIdx; /* Index of child node in parent */ MemPage *pPage; /* Current page of the b-tree */ - /* If this is a leaf page or the tree is not an int-key tree, then + /* If this is a leaf page or the tree is not an int-key tree, then ** this page contains countable entries. Increment the entry counter ** accordingly. */ @@ -10298,7 +10339,7 @@ int sqlite3BtreeCount(sqlite3 *db, BtCursor *pCur, i64 *pnEntry){ nEntry += pPage->nCell; } - /* pPage is a leaf node. This loop navigates the cursor so that it + /* pPage is a leaf node. This loop navigates the cursor so that it ** points to the first interior cell that it points to the parent of ** the next page in the tree that has not yet been visited. The ** pCur->aiIdx[pCur->iPage] value is set to the index of the parent cell @@ -10322,7 +10363,7 @@ int sqlite3BtreeCount(sqlite3 *db, BtCursor *pCur, i64 *pnEntry){ pPage = pCur->pPage; } - /* Descend to the child node of the cell that the cursor currently + /* Descend to the child node of the cell that the cursor currently ** points at. This is the right-child if (iIdx==pPage->nCell). */ iIdx = pCur->ix; @@ -10453,7 +10494,7 @@ static int checkRef(IntegrityCk *pCheck, Pgno iPage){ #ifndef SQLITE_OMIT_AUTOVACUUM /* -** Check that the entry in the pointer-map for page iChild maps to +** Check that the entry in the pointer-map for page iChild maps to ** page iParent, pointer type ptrType. If not, append an error message ** to pCheck. */ @@ -10476,7 +10517,7 @@ static void checkPtrmap( if( ePtrmapType!=eType || iPtrmapParent!=iParent ){ checkAppendMsg(pCheck, - "Bad ptr map entry key=%u expected=(%u,%u) got=(%u,%u)", + "Bad ptr map entry key=%u expected=(%u,%u) got=(%u,%u)", iChild, eType, iParent, ePtrmapType, iPtrmapParent); } } @@ -10571,7 +10612,7 @@ static void checkList( ** property. ** ** This heap is used for cell overlap and coverage testing. Each u32 -** entry represents the span of a cell or freeblock on a btree page. +** entry represents the span of a cell or freeblock on a btree page. ** The upper 16 bits are the index of the first byte of a range and the ** lower 16 bits are the index of the last byte of that range. */ @@ -10603,7 +10644,7 @@ static int btreeHeapPull(u32 *aHeap, u32 *pOut){ aHeap[j] = x; i = j; } - return 1; + return 1; } #ifndef SQLITE_OMIT_INTEGRITY_CHECK @@ -10611,7 +10652,7 @@ static int btreeHeapPull(u32 *aHeap, u32 *pOut){ ** Do various sanity checks on a single page of a tree. Return ** the tree depth. Root pages return 0. Parents of root pages ** return 1, and so forth. -** +** ** These checks are done: ** ** 1. Make sure that cells and freeblocks do not overlap @@ -10662,7 +10703,7 @@ static int checkTreePage( if( iPage==0 ) return 0; if( checkRef(pCheck, iPage) ) return 0; pCheck->zPfx = "Tree %u page %u: "; - pCheck->v0 = pCheck->v1 = iPage; + pCheck->v1 = iPage; if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){ checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); @@ -10811,7 +10852,7 @@ static int checkTreePage( ** ** EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header ** is the offset of the first freeblock, or zero if there are no - ** freeblocks on the page. + ** freeblocks on the page. */ i = get2byte(&data[hdr+1]); while( i>0 ){ @@ -10831,13 +10872,13 @@ static int checkTreePage( assert( (u32)j<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */ i = j; } - /* Analyze the min-heap looking for overlap between cells and/or + /* Analyze the min-heap looking for overlap between cells and/or ** freeblocks, and counting the number of untracked bytes in nFrag. - ** + ** ** Each min-heap entry is of the form: (start_address<<16)|end_address. ** There is an implied first entry the covers the page header, the cell ** pointer index, and the gap between the cell pointer index and the start - ** of cell content. + ** of cell content. ** ** The loop below pulls entries from the min-heap in order and compares ** the start_address against the previous end_address. If there is an @@ -10999,6 +11040,7 @@ int sqlite3BtreeIntegrityCheck( checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); } #endif + sCheck.v0 = aRoot[i]; checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64); } pBt->db->flags = savedDbFlags; @@ -11015,11 +11057,11 @@ int sqlite3BtreeIntegrityCheck( /* If the database supports auto-vacuum, make sure no tables contain ** references to pointer-map pages. */ - if( getPageReferenced(&sCheck, i)==0 && + if( getPageReferenced(&sCheck, i)==0 && (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ checkAppendMsg(&sCheck, "Page %u: never used", i); } - if( getPageReferenced(&sCheck, i)!=0 && + if( getPageReferenced(&sCheck, i)!=0 && (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ checkAppendMsg(&sCheck, "Page %u: pointer map referenced", i); } @@ -11084,7 +11126,7 @@ int sqlite3BtreeTxnState(Btree *p){ /* ** Run a checkpoint on the Btree passed as the first argument. ** -** Return SQLITE_LOCKED if this or any other connection has an open +** Return SQLITE_LOCKED if this or any other connection has an open ** transaction on the shared-cache the argument Btree is connected to. ** ** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART. @@ -11117,20 +11159,20 @@ int sqlite3BtreeIsInBackup(Btree *p){ /* ** This function returns a pointer to a blob of memory associated with ** a single shared-btree. The memory is used by client code for its own -** purposes (for example, to store a high-level schema associated with +** purposes (for example, to store a high-level schema associated with ** the shared-btree). The btree layer manages reference counting issues. ** ** The first time this is called on a shared-btree, nBytes bytes of memory -** are allocated, zeroed, and returned to the caller. For each subsequent +** are allocated, zeroed, and returned to the caller. For each subsequent ** call the nBytes parameter is ignored and a pointer to the same blob -** of memory returned. +** of memory returned. ** ** If the nBytes parameter is 0 and the blob of memory has not yet been ** allocated, a null pointer is returned. If the blob has already been ** allocated, it is returned as normal. ** -** Just before the shared-btree is closed, the function passed as the -** xFree argument when the memory allocation was made is invoked on the +** Just before the shared-btree is closed, the function passed as the +** xFree argument when the memory allocation was made is invoked on the ** blob of allocated memory. The xFree function should not call sqlite3_free() ** on the memory, the btree layer does that. */ @@ -11146,8 +11188,8 @@ void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){ } /* -** Return SQLITE_LOCKED_SHAREDCACHE if another user of the same shared -** btree as the argument handle holds an exclusive lock on the +** Return SQLITE_LOCKED_SHAREDCACHE if another user of the same shared +** btree as the argument handle holds an exclusive lock on the ** sqlite_schema table. Otherwise SQLITE_OK. */ int sqlite3BtreeSchemaLocked(Btree *p){ @@ -11188,11 +11230,11 @@ int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){ #ifndef SQLITE_OMIT_INCRBLOB /* -** Argument pCsr must be a cursor opened for writing on an -** INTKEY table currently pointing at a valid table entry. +** Argument pCsr must be a cursor opened for writing on an +** INTKEY table currently pointing at a valid table entry. ** This function modifies the data stored as part of that entry. ** -** Only the data content may only be modified, it is not possible to +** Only the data content may only be modified, it is not possible to ** change the length of the data stored. If this function is called with ** parameters that attempt to write past the end of the existing data, ** no modifications are made and SQLITE_CORRUPT is returned. @@ -11223,7 +11265,7 @@ int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){ VVA_ONLY(rc =) saveAllCursors(pCsr->pBt, pCsr->pgnoRoot, pCsr); assert( rc==SQLITE_OK ); - /* Check some assumptions: + /* Check some assumptions: ** (a) the cursor is open for writing, ** (b) there is a read/write transaction open, ** (c) the connection holds a write-lock on the table (if required), @@ -11242,7 +11284,7 @@ int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){ return accessPayload(pCsr, offset, amt, (unsigned char *)z, 1); } -/* +/* ** Mark this cursor as an incremental blob cursor. */ void sqlite3BtreeIncrblobCursor(BtCursor *pCur){ @@ -11252,14 +11294,14 @@ void sqlite3BtreeIncrblobCursor(BtCursor *pCur){ #endif /* -** Set both the "read version" (single byte at byte offset 18) and +** Set both the "read version" (single byte at byte offset 18) and ** "write version" (single byte at byte offset 19) fields in the database ** header to iVersion. */ int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){ BtShared *pBt = pBtree->pBt; int rc; /* Return code */ - + assert( iVersion==1 || iVersion==2 ); /* If setting the version fields to 1, do not automatically open the @@ -11328,7 +11370,7 @@ int sqlite3BtreeSharable(Btree *p){ /* ** Return the number of connections to the BtShared object accessed by -** the Btree handle passed as the only argument. For private caches +** the Btree handle passed as the only argument. For private caches ** this is always 1. For shared caches it may be 1 or greater. */ int sqlite3BtreeConnectionCount(Btree *p){ diff --git a/src/btree.h b/src/btree.h index b9078f901..b45ace7e1 100644 --- a/src/btree.h +++ b/src/btree.h @@ -321,9 +321,7 @@ int sqlite3BtreePrevious(BtCursor*, int flags); i64 sqlite3BtreeIntegerKey(BtCursor*); void sqlite3BtreeCursorPin(BtCursor*); void sqlite3BtreeCursorUnpin(BtCursor*); -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC i64 sqlite3BtreeOffset(BtCursor*); -#endif int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); u32 sqlite3BtreePayloadSize(BtCursor*); diff --git a/src/btreeInt.h b/src/btreeInt.h index 6d3578422..26a0bc686 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -180,7 +180,7 @@ ** 0x81 0x00 becomes 0x00000080 ** 0x82 0x00 becomes 0x00000100 ** 0x80 0x7f becomes 0x0000007f -** 0x8a 0x91 0xd1 0xac 0x78 becomes 0x12345678 +** 0x81 0x91 0xd1 0xac 0x78 becomes 0x12345678 ** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081 ** ** Variable length integers are used for rowids and to hold the number of @@ -263,7 +263,7 @@ typedef struct CellInfo CellInfo; ** page that has been loaded into memory. The information in this object ** is derived from the raw on-disk page content. ** -** As each database page is loaded into memory, the pager allocats an +** As each database page is loaded into memory, the pager allocates an ** instance of this object and zeros the first 8 bytes. (This is the ** "extra" information associated with each page of the pager.) ** @@ -719,7 +719,7 @@ struct IntegrityCk { /* ** get2byteAligned(), unlike get2byte(), requires that its argument point to a -** two-byte aligned address. get2bytea() is only used for accessing the +** two-byte aligned address. get2byteAligned() is only used for accessing the ** cell addresses in a btree header. */ #if SQLITE_BYTEORDER==4321 diff --git a/src/build.c b/src/build.c index 9be444c3c..fe3a781d6 100644 --- a/src/build.c +++ b/src/build.c @@ -37,7 +37,7 @@ struct TableLock { }; /* -** Record the fact that we want to lock a table at run-time. +** Record the fact that we want to lock a table at run-time. ** ** The table to be locked has root page iTab and is found in database iDb. ** A read or a write lock can be taken depending on isWritelock. @@ -100,7 +100,7 @@ void sqlite3TableLock( */ static void codeTableLocks(Parse *pParse){ int i; - Vdbe *pVdbe = pParse->pVdbe; + Vdbe *pVdbe = pParse->pVdbe; assert( pVdbe!=0 ); for(i=0; inTableLock; i++){ @@ -164,7 +164,7 @@ void sqlite3FinishCoding(Parse *pParse){ v = sqlite3GetVdbe(pParse); if( v==0 ) pParse->rc = SQLITE_ERROR; } - assert( !pParse->isMultiWrite + assert( !pParse->isMultiWrite || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort)); if( v ){ if( pParse->bReturning ){ @@ -234,15 +234,17 @@ void sqlite3FinishCoding(Parse *pParse){ pParse->nVtabLock = 0; #endif - /* Once all the cookies have been verified and transactions opened, - ** obtain the required table-locks. This is a no-op unless the +#ifndef SQLITE_OMIT_SHARED_CACHE + /* Once all the cookies have been verified and transactions opened, + ** obtain the required table-locks. This is a no-op unless the ** shared-cache feature is enabled. */ - codeTableLocks(pParse); + if( pParse->nTableLock ) codeTableLocks(pParse); +#endif /* Initialize any AUTOINCREMENT data structures required. */ - sqlite3AutoincrementBegin(pParse); + if( pParse->pAinc ) sqlite3AutoincrementBegin(pParse); /* Code constant expressions that where factored out of inner loops. ** @@ -386,7 +388,7 @@ Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ || sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 || sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 ){ - p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, LEGACY_TEMP_SCHEMA_TABLE); } }else{ @@ -413,7 +415,7 @@ Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, LEGACY_SCHEMA_TABLE); }else if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ - p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, LEGACY_TEMP_SCHEMA_TABLE); } } @@ -442,7 +444,7 @@ Table *sqlite3LocateTable( /* Read the database schema. If an error occurs, leave an error message ** and code in pParse and return NULL. */ - if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 + if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 && SQLITE_OK!=sqlite3ReadSchema(pParse) ){ return 0; @@ -495,7 +497,7 @@ Table *sqlite3LocateTable( ** sqlite3FixSrcList() for details. */ Table *sqlite3LocateTableItem( - Parse *pParse, + Parse *pParse, u32 flags, SrcItem *p ){ @@ -527,7 +529,7 @@ const char *sqlite3PreferredTableName(const char *zName){ } /* -** Locate the in-memory structure that describes +** Locate the in-memory structure that describes ** a particular index given the name of that index ** and the name of the database that contains the index. ** Return NULL if not found. @@ -755,7 +757,7 @@ void sqlite3ColumnSetColl( } /* -** Return the collating squence name for a column +** Return the collating sequence name for a column */ const char *sqlite3ColumnColl(Column *pCol){ const char *z; @@ -802,10 +804,10 @@ void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ ** ** This routine just deletes the data structure. It does not unlink ** the table data structure from the hash table. But it does destroy -** memory structures of the indices and foreign keys associated with +** memory structures of the indices and foreign keys associated with ** the table. ** -** The db parameter is optional. It is needed if the Table object +** The db parameter is optional. It is needed if the Table object ** contains lookaside memory. (Table objects in the schema do not use ** lookaside memory, but some ephemeral Table objects do.) Or the ** db parameter can be used with db->pnBytesFreed to measure the memory @@ -817,7 +819,7 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ #ifdef SQLITE_DEBUG /* Record the number of outstanding lookaside allocations in schema Tables ** prior to doing any free() operations. Since schema Tables do not use - ** lookaside, this number should not change. + ** lookaside, this number should not change. ** ** If malloc has already failed, it may be that it failed while allocating ** a Table object that was going to be marked ephemeral. So do not check @@ -835,7 +837,7 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ assert( pIndex->pSchema==pTable->pSchema || (IsVirtual(pTable) && pIndex->idxType!=SQLITE_IDXTYPE_APPDEF) ); if( db->pnBytesFreed==0 && !IsVirtual(pTable) ){ - char *zName = pIndex->zName; + char *zName = pIndex->zName; TESTONLY ( Index *pOld = ) sqlite3HashInsert( &pIndex->pSchema->idxHash, zName, 0 ); @@ -957,7 +959,7 @@ int sqlite3FindDbName(sqlite3 *db, const char *zName){ /* ** The token *pName contains the name of a database (either "main" or ** "temp" or the name of an attached db). This routine returns the -** index of the named database in db->aDb[], or -1 if the named db +** index of the named database in db->aDb[], or -1 if the named db ** does not exist. */ int sqlite3FindDb(sqlite3 *db, Token *pName){ @@ -973,7 +975,7 @@ int sqlite3FindDb(sqlite3 *db, Token *pName){ ** pName1 and pName2. If the table name was fully qualified, for example: ** ** CREATE TABLE xxx.yyy (...); -** +** ** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if ** the table name is not fully qualified, i.e.: ** @@ -1127,7 +1129,7 @@ i16 sqlite3StorageColumnToTable(Table *pTab, i16 iCol){ ** The storage column number (0,1,2,....) is the index of the value ** as it appears in the record on disk. Or, if the input column is ** the N-th virtual column (zero-based) then the storage number is -** the number of non-virtual columns in the table plus N. +** the number of non-virtual columns in the table plus N. ** ** The true column number is the index (0,1,2,...) of the column in ** the CREATE TABLE statement. @@ -1235,7 +1237,7 @@ void sqlite3StartTable( iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); if( iDb<0 ) return; if( !OMIT_TEMPDB && isTemp && pName2->n>0 && iDb!=1 ){ - /* If creating a temp table, the name may not be qualified. Unless + /* 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; @@ -1326,7 +1328,7 @@ void sqlite3StartTable( ** the schema table. Note in particular that we must go ahead ** and allocate the record number for the table entry now. Before any ** PRIMARY KEY or UNIQUE keywords are parsed. Those keywords will cause - ** indices to be created and the table record must come before the + ** indices to be created and the table record must come before the ** indices. Hence, the record number for the table must be allocated ** now. */ @@ -1344,7 +1346,7 @@ void sqlite3StartTable( } #endif - /* If the file format and encoding in the database have not been set, + /* If the file format and encoding in the database have not been set, ** set them now. */ reg1 = pParse->regRowid = ++pParse->nMem; @@ -1513,7 +1515,7 @@ void sqlite3AddColumn(Parse *pParse, Token sName, Token sType){ } if( !IN_RENAME_OBJECT ) sqlite3DequoteToken(&sName); - /* Because keywords GENERATE ALWAYS can be converted into indentifiers + /* Because keywords GENERATE ALWAYS can be converted into identifiers ** by the parser, we can sometimes end up with a typename that ends ** with "generated always". Check for this case and omit the surplus ** text. */ @@ -1573,7 +1575,7 @@ void sqlite3AddColumn(Parse *pParse, Token sName, Token sType){ pCol->zCnName = z; pCol->hName = hName; sqlite3ColumnPropertiesFromName(p, pCol); - + if( sType.n==0 ){ /* If there is no type specified, columns have the default affinity ** 'BLOB' with a default size of 4 bytes. */ @@ -1632,11 +1634,11 @@ void sqlite3AddNotNull(Parse *pParse, int onError){ ** Scan the column type name zType (length nType) and return the ** associated affinity type. ** -** This routine does a case-independent search of zType for the +** This routine does a case-independent search of zType for the ** substrings in the following table. If one of the substrings is ** found, the corresponding affinity is returned. If zType contains -** more than one of the substrings, entries toward the top of -** the table take priority. For example, if zType is 'BLOBINT', +** more than one of the substrings, entries toward the top of +** the table take priority. For example, if zType is 'BLOBINT', ** SQLITE_AFF_INTEGER is returned. ** ** Substring | Affinity @@ -1734,7 +1736,7 @@ void sqlite3AddDefaultValue( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The parsed expression of the default value */ const char *zStart, /* Start of the default value text */ - const char *zEnd /* First character past end of defaut value text */ + const char *zEnd /* First character past end of default value text */ ){ Table *p; Column *pCol; @@ -1775,7 +1777,7 @@ void sqlite3AddDefaultValue( /* ** Backwards Compatibility Hack: -** +** ** Historical versions of SQLite accepted strings as column names in ** indexes and PRIMARY KEY constraints and in UNIQUE constraints. Example: ** @@ -1809,11 +1811,11 @@ static void makeColumnPartOfPrimaryKey(Parse *pParse, Column *pCol){ sqlite3ErrorMsg(pParse, "generated columns cannot be part of the PRIMARY KEY"); } -#endif +#endif } /* -** Designate the PRIMARY KEY for the table. pList is a list of names +** Designate the PRIMARY KEY for the table. pList is a list of names ** of columns that form the primary key. If pList is NULL, then the ** most recently added column of the table is the primary key. ** @@ -1843,7 +1845,7 @@ void sqlite3AddPrimaryKey( int nTerm; if( pTab==0 ) goto primary_key_exit; if( pTab->tabFlags & TF_HasPrimaryKey ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "table \"%s\" has more than one primary key", pTab->zName); goto primary_key_exit; } @@ -1928,7 +1930,7 @@ void sqlite3AddCheckConstraint( while( sqlite3Isspace(zEnd[-1]) ){ zEnd--; } t.z = zStart; t.n = (int)(zEnd - t.z); - sqlite3ExprListSetName(pParse, pTab->pCheck, &t, 1); + sqlite3ExprListSetName(pParse, pTab->pCheck, &t, 1); } }else #endif @@ -1956,7 +1958,7 @@ void sqlite3AddCollateType(Parse *pParse, Token *pToken){ if( sqlite3LocateCollSeq(pParse, zColl) ){ Index *pIdx; sqlite3ColumnSetColl(db, &p->aCol[i], zColl); - + /* If the column is declared as " PRIMARY KEY COLLATE ", ** then an index may have been created on this column before the ** collation type was added. Correct this if it is the case. @@ -2054,7 +2056,7 @@ void sqlite3ChangeCookie(Parse *pParse, int iDb){ sqlite3 *db = pParse->db; Vdbe *v = pParse->pVdbe; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, (int)(1+(unsigned)db->aDb[iDb].pSchema->schema_cookie)); } @@ -2075,14 +2077,14 @@ static int identLength(const char *z){ } /* -** The first parameter is a pointer to an output buffer. The second +** The first parameter is a pointer to an output buffer. The second ** parameter is a pointer to an integer that contains the offset at ** which to write into the output buffer. This function copies the ** nul-terminated string pointed to by the third parameter, zSignedIdent, ** to the specified offset in the buffer and updates *pIdx to refer ** to the first byte after the last byte written before returning. -** -** If the string zSignedIdent consists entirely of alpha-numeric +** +** If the string zSignedIdent consists entirely of alphanumeric ** characters, does not begin with a digit and is not an SQL keyword, ** then it is copied to the output buffer exactly as it is. Otherwise, ** it is quoted using double-quotes. @@ -2125,7 +2127,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){ n += identLength(pCol->zCnName) + 5; } n += identLength(p->zName); - if( n<50 ){ + if( n<50 ){ zSep = ""; zSep2 = ","; zEnd = ")"; @@ -2168,7 +2170,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){ testcase( pCol->affinity==SQLITE_AFF_INTEGER ); testcase( pCol->affinity==SQLITE_AFF_REAL ); testcase( pCol->affinity==SQLITE_AFF_FLEXNUM ); - + zType = azType[pCol->affinity - SQLITE_AFF_BLOB]; len = sqlite3Strlen30(zType); assert( pCol->affinity==SQLITE_AFF_BLOB @@ -2234,7 +2236,7 @@ static void estimateIndexWidth(Index *pIdx){ for(i=0; inColumn; i++){ i16 x = pIdx->aiColumn[i]; assert( xpTable->nCol ); - wIndex += x<0 ? 1 : aCol[pIdx->aiColumn[i]].szEst; + wIndex += x<0 ? 1 : aCol[x].szEst; } pIdx->szIdxRow = sqlite3LogEst(wIndex*4); } @@ -2277,7 +2279,7 @@ static int isDupColumn(Index *pIdx, int nKey, Index *pPk, int iCol){ assert( j!=XN_ROWID && j!=XN_EXPR ); for(i=0; iaiColumn[i]>=0 || j>=0 ); - if( pIdx->aiColumn[i]==j + if( pIdx->aiColumn[i]==j && sqlite3StrICmp(pIdx->azColl[i], pPk->azColl[iCol])==0 ){ return 1; @@ -2329,7 +2331,7 @@ static void recomputeColumnsNotIndexed(Index *pIdx){ ** Changes include: ** ** (1) Set all columns of the PRIMARY KEY schema object to be NOT NULL. -** (2) Convert P3 parameter of the OP_CreateBtree from BTREE_INTKEY +** (2) Convert P3 parameter of the OP_CreateBtree from BTREE_INTKEY ** into BTREE_BLOBKEY. ** (3) Bypass the creation of the sqlite_schema table entry ** for the PRIMARY KEY as the primary key index is now @@ -2377,13 +2379,13 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ } /* Locate the PRIMARY KEY index. Or, if this table was originally - ** an INTEGER PRIMARY KEY table, create a new PRIMARY KEY index. + ** an INTEGER PRIMARY KEY table, create a new PRIMARY KEY index. */ if( pTab->iPKey>=0 ){ ExprList *pList; Token ipkToken; sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zCnName); - pList = sqlite3ExprListAppend(pParse, 0, + pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0)); if( pList==0 ){ pTab->tabFlags &= ~TF_WithoutRowid; @@ -2622,7 +2624,7 @@ static void markExprListImmutable(ExprList *pList){ ** the sqlite_schema table. We do not want to create it again. ** ** If the pSelect argument is not NULL, it means that this routine -** was called to create a table generated from a +** was called to create a table generated from a ** "CREATE TABLE ... AS SELECT ..." statement. The column names of ** the new table will match the result set of the SELECT. */ @@ -2701,7 +2703,7 @@ void sqlite3EndTable( pCol->notNull = OE_Abort; p->tabFlags |= TF_HasNotNull; } - } + } } assert( (p->tabFlags & TF_HasPrimaryKey)==0 @@ -2758,7 +2760,7 @@ void sqlite3EndTable( ** tree that have been allocated from lookaside memory, which is ** illegal in a schema and will lead to errors or heap corruption ** when the database connection closes. */ - sqlite3ColumnSetExpr(pParse, p, &p->aCol[ii], + sqlite3ColumnSetExpr(pParse, p, &p->aCol[ii], sqlite3ExprAlloc(db, TK_NULL, 0, 0)); } }else{ @@ -2796,7 +2798,7 @@ void sqlite3EndTable( sqlite3VdbeAddOp1(v, OP_Close, 0); - /* + /* ** Initialize zType for the new view or table. */ if( IsOrdinaryTable(p) ){ @@ -2880,12 +2882,12 @@ void sqlite3EndTable( Token *pEnd2 = tabOpts ? &pParse->sLastToken : pEnd; n = (int)(pEnd2->z - pParse->sNameToken.z); if( pEnd2->z[0]!=';' ) n += pEnd2->n; - zStmt = sqlite3MPrintf(db, + zStmt = sqlite3MPrintf(db, "CREATE %s %.*s", zType2, n, pParse->sNameToken.z ); } - /* A slot for the record has already been allocated in the + /* A slot for the record has already been allocated in the ** schema table. We just need to update that slot with all ** the information we've collected. */ @@ -3100,7 +3102,7 @@ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ ** Actually, the error above is now caught prior to reaching this point. ** But the following test is still important as it does come up ** in the following: - ** + ** ** CREATE TABLE main.ex1(a); ** CREATE TEMP VIEW ex1 AS SELECT a FROM ex1; ** SELECT * FROM temp.ex1; @@ -3148,7 +3150,7 @@ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ ** normally holds CHECK constraints on an ordinary table, but for ** a VIEW it holds the list of column names. */ - sqlite3ColumnsFromExprList(pParse, pTable->pCheck, + sqlite3ColumnsFromExprList(pParse, pTable->pCheck, &pTable->nCol, &pTable->aCol); if( pParse->nErr==0 && pTable->nCol==pSel->pEList->nExpr @@ -3181,7 +3183,7 @@ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ sqlite3DeleteColumnNames(db, pTable); } #endif /* SQLITE_OMIT_VIEW */ - return nErr; + return nErr; } int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ assert( pTable!=0 ); @@ -3220,7 +3222,7 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){ ** on tables and/or indices that are the process of being deleted. ** If you are unlucky, one of those deleted indices or tables might ** have the same rootpage number as the real table or index that is -** being moved. So we cannot stop searching after the first match +** being moved. So we cannot stop searching after the first match ** because the first match might be for one of the deleted indices ** or tables and not the table/index that is actually being moved. ** We must continue looping until all tables and indices with @@ -3257,7 +3259,7 @@ void sqlite3RootPageMoved(sqlite3 *db, int iDb, Pgno iFrom, Pgno iTo){ ** Also write code to modify the sqlite_schema table and internal schema ** if a root-page of another table is moved by the btree-layer whilst ** erasing iTable (this can happen with an auto-vacuum database). -*/ +*/ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ Vdbe *v = sqlite3GetVdbe(pParse); int r1 = sqlite3GetTempReg(pParse); @@ -3274,7 +3276,7 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ ** is in register NNN. See grammar rules associated with the TK_REGISTER ** token for additional information. */ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET rootpage=%d WHERE #%d AND rootpage=#%d", pParse->db->aDb[iDb].zDbSName, iTable, r1, r1); @@ -3291,7 +3293,7 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ static void destroyTable(Parse *pParse, Table *pTab){ /* If the database may be auto-vacuum capable (if SQLITE_OMIT_AUTOVACUUM ** is not defined), then it is important to call OP_Destroy on the - ** table and index root-pages in order, starting with the numerically + ** table and index root-pages in order, starting with the numerically ** largest root-page number. This guarantees that none of the root-pages ** to be destroyed is relocated by an earlier OP_Destroy. i.e. if the ** following were coded: @@ -3301,7 +3303,7 @@ static void destroyTable(Parse *pParse, Table *pTab){ ** OP_Destroy 5 0 ** ** and root page 5 happened to be the largest root-page number in the - ** database, then root page 5 would be moved to page 4 by the + ** database, then root page 5 would be moved to page 4 by the ** "OP_Destroy 4 0" opcode. The subsequent "OP_Destroy 5 0" would hit ** a free-list page. */ @@ -3382,7 +3384,7 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ */ pTrigger = sqlite3TriggerList(pParse, pTab); while( pTrigger ){ - assert( pTrigger->pSchema==pTab->pSchema || + assert( pTrigger->pSchema==pTab->pSchema || pTrigger->pSchema==db->aDb[1].pSchema ); sqlite3DropTriggerPtr(pParse, pTrigger); pTrigger = pTrigger->pNext; @@ -3409,7 +3411,7 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ ** created in the temp database that refers to a table in another ** database. */ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE tbl_name=%Q and type!='trigger'", pDb->zDbSName, pTab->zName); @@ -3660,8 +3662,8 @@ void sqlite3CreateForeignKey( } } if( j>=p->nCol ){ - sqlite3ErrorMsg(pParse, - "unknown column \"%s\" in foreign key definition", + sqlite3ErrorMsg(pParse, + "unknown column \"%s\" in foreign key definition", pFromCol->a[i].zEName); goto fk_end; } @@ -3687,7 +3689,7 @@ void sqlite3CreateForeignKey( pFKey->aAction[1] = (u8)((flags >> 8 ) & 0xff); /* ON UPDATE action */ assert( sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); - pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash, + pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash, pFKey->zTo, (void *)pFKey ); if( pNextTo==pFKey ){ @@ -3796,7 +3798,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr1); if( memRootPage<0 ) sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, (int)tnum, iDb, + sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, (int)tnum, iDb, (char *)pKey, P4_KEYINFO); sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); @@ -3815,7 +3817,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ ** user function that throws an exception when it is evaluated. But the ** overhead of adding a statement journal to a CREATE INDEX statement is ** very small (since most of the pages written do not contain content that - ** needs to be restored if the statement aborts), so we call + ** needs to be restored if the statement aborts), so we call ** sqlite3MayAbort() for all CREATE INDEX statements. */ sqlite3MayAbort(pParse); addr2 = sqlite3VdbeCurrentAddr(v); @@ -3888,7 +3890,7 @@ int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){ for(i=0; inExpr; i++){ if( pList->a[i].fg.bNulls ){ u8 sf = pList->a[i].fg.sortFlags; - sqlite3ErrorMsg(pParse, "unsupported use of NULLS %s", + sqlite3ErrorMsg(pParse, "unsupported use of NULLS %s", (sf==0 || sf==3) ? "FIRST" : "LAST" ); return 1; @@ -3899,8 +3901,8 @@ int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){ } /* -** Create a new index for an SQL table. pName1.pName2 is the name of the index -** and pTblList is the name of the table that is to be indexed. Both will +** Create a new index for an SQL table. pName1.pName2 is the name of the index +** and pTblList is the name of the table that is to be indexed. Both will ** be NULL for a primary key or an index that is created to satisfy a ** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable ** as the table to be indexed. pParse->pNewTable is a table that is @@ -3908,7 +3910,7 @@ int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){ ** ** pList is a list of columns to be indexed. pList will be NULL if this ** is a primary key or unique-constraint on the most recent column added -** to the table currently under construction. +** to the table currently under construction. */ void sqlite3CreateIndex( Parse *pParse, /* All information about this parse */ @@ -3960,7 +3962,7 @@ void sqlite3CreateIndex( */ if( pTblName!=0 ){ - /* Use the two-part index name to determine the database + /* Use the two-part index name to determine the database ** to search for the table. 'Fix' the table name to this db ** before looking up the table. */ @@ -3972,7 +3974,7 @@ void sqlite3CreateIndex( #ifndef SQLITE_OMIT_TEMPDB /* If the index name was unqualified, check if the table ** is a temp table. If so, set the database to 1. Do not do this - ** if initialising a database schema. + ** if initializing a database schema. */ if( !db->init.busy ){ pTab = sqlite3SrcListLookup(pParse, pTblName); @@ -3992,7 +3994,7 @@ void sqlite3CreateIndex( assert( db->mallocFailed==0 || pTab==0 ); if( pTab==0 ) goto exit_create_index; if( iDb==1 && db->aDb[iDb].pSchema!=pTab->pSchema ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "cannot create a TEMP index on non-TEMP table \"%s\"", pTab->zName); goto exit_create_index; @@ -4008,7 +4010,7 @@ void sqlite3CreateIndex( pDb = &db->aDb[iDb]; assert( pTab!=0 ); - if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 && db->init.busy==0 && pTblName!=0 #if SQLITE_USER_AUTHENTICATION @@ -4033,7 +4035,7 @@ void sqlite3CreateIndex( /* ** Find the name of the index. Make sure there is not already another - ** index or table with the same name. + ** index or table with the same name. ** ** Exception: If we are reading the names of permanent indices from the ** sqlite_schema table (because some other process changed the schema) and @@ -4133,8 +4135,8 @@ void sqlite3CreateIndex( } } - /* - ** Allocate the index structure. + /* + ** Allocate the index structure. */ nName = sqlite3Strlen30(zName); nExtraCol = pPk ? pPk->nKeyCol : 1; @@ -4255,7 +4257,7 @@ void sqlite3CreateIndex( int x = pPk->aiColumn[j]; assert( x>=0 ); if( isDupColumn(pIndex, pIndex->nKeyCol, pPk, j) ){ - pIndex->nColumn--; + pIndex->nColumn--; }else{ testcase( hasColumn(pIndex->aiColumn,pIndex->nKeyCol,x) ); pIndex->aiColumn[i] = x; @@ -4274,7 +4276,7 @@ void sqlite3CreateIndex( /* If this index contains every column of its table, then mark ** it as a covering index */ - assert( HasRowid(pTab) + assert( HasRowid(pTab) || pTab->iPKey<0 || sqlite3TableColumnToIndex(pIndex, pTab->iPKey)>=0 ); recomputeColumnsNotIndexed(pIndex); if( pTblName!=0 && pIndex->nColumn>=pTab->nCol ){ @@ -4330,13 +4332,13 @@ void sqlite3CreateIndex( if( pIdx->onError!=pIndex->onError ){ /* This constraint creates the same index as a previous ** constraint specified somewhere in the CREATE TABLE statement. - ** However the ON CONFLICT clauses are different. If both this + ** However the ON CONFLICT clauses are different. If both this ** constraint and the previous equivalent constraint have explicit ** ON CONFLICT clauses this is an error. Otherwise, use the ** explicitly specified behavior for the index. */ if( !(pIdx->onError==OE_Default || pIndex->onError==OE_Default) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "conflicting ON CONFLICT clauses specified", 0); } if( pIdx->onError==OE_Default ){ @@ -4357,7 +4359,7 @@ void sqlite3CreateIndex( if( !IN_RENAME_OBJECT ){ /* Link the new Index structure to its table and to the other - ** in-memory database structures. + ** in-memory database structures. */ assert( pParse->nErr==0 ); if( db->init.busy ){ @@ -4372,7 +4374,7 @@ void sqlite3CreateIndex( goto exit_create_index; } } - p = sqlite3HashInsert(&pIndex->pSchema->idxHash, + p = sqlite3HashInsert(&pIndex->pSchema->idxHash, pIndex->zName, pIndex); if( p ){ assert( p==pIndex ); /* Malloc must have failed */ @@ -4406,9 +4408,9 @@ void sqlite3CreateIndex( sqlite3BeginWriteOperation(pParse, 1, iDb); /* Create the rootpage for the index using CreateIndex. But before - ** doing so, code a Noop instruction and store its address in - ** Index.tnum. This is required in case this index is actually a - ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In + ** doing so, code a Noop instruction and store its address in + ** Index.tnum. This is required in case this index is actually a + ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In ** that case the convertToWithoutRowidTable() routine will replace ** the Noop with a Goto to jump over the VDBE code generated below. */ pIndex->tnum = (Pgno)sqlite3VdbeAddOp0(v, OP_Noop); @@ -4432,7 +4434,7 @@ void sqlite3CreateIndex( /* Add an entry in sqlite_schema for this index */ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "INSERT INTO %Q." LEGACY_SCHEMA_TABLE " VALUES('index',%Q,%Q,#%d,%Q);", db->aDb[iDb].zDbSName, pIndex->zName, @@ -4534,7 +4536,7 @@ void sqlite3DefaultRowEst(Index *pIdx){ /* Indexes with default row estimates should not have stat1 data */ assert( !pIdx->hasStat1 ); - /* Set the first entry (number of rows in the index) to the estimated + /* Set the first entry (number of rows in the index) to the estimated ** number of rows in the table, or half the number of rows in the table ** for a partial index. ** @@ -4830,7 +4832,7 @@ SrcList *sqlite3SrcListEnlarge( ** database name prefix. Like this: "database.table". The pDatabase ** points to the table name and the pTable points to the database name. ** The SrcList.a[].zName field is filled with the table name which might -** come from pTable (if pDatabase is NULL) or from pDatabase. +** come from pTable (if pDatabase is NULL) or from pDatabase. ** SrcList.a[].zDatabase is filled with the database name from pTable, ** or with NULL if no database is specified. ** @@ -4964,7 +4966,7 @@ SrcList *sqlite3SrcListAppendFromTerm( SrcItem *pItem; sqlite3 *db = pParse->db; if( !p && pOnUsing!=0 && (pOnUsing->pOn || pOnUsing->pUsing) ){ - sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", + sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", (pOnUsing->pOn ? "ON" : "USING") ); goto append_from_error; @@ -5011,7 +5013,7 @@ SrcList *sqlite3SrcListAppendFromTerm( } /* -** Add an INDEXED BY or NOT INDEXED clause to the most recently added +** Add an INDEXED BY or NOT INDEXED clause to the most recently added ** element of the source-list passed as the second argument. */ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ @@ -5024,7 +5026,7 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ assert( pItem->fg.isIndexedBy==0 ); assert( pItem->fg.isTabFunc==0 ); if( pIndexedBy->n==1 && !pIndexedBy->z ){ - /* A "NOT INDEXED" clause was supplied. See parse.y + /* A "NOT INDEXED" clause was supplied. See parse.y ** construct "indexed_opt" for details. */ pItem->fg.notIndexed = 1; }else{ @@ -5039,7 +5041,7 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ ** Append the contents of SrcList p2 to SrcList p1 and return the resulting ** SrcList. Or, if an error occurs, return NULL. In all cases, p1 and p2 ** are deleted by this function. -*/ +*/ SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ assert( p1 && p1->nSrc==1 ); if( p2 ){ @@ -5165,7 +5167,7 @@ void sqlite3EndTransaction(Parse *pParse, int eType){ assert( pParse->db!=0 ); assert( eType==TK_COMMIT || eType==TK_END || eType==TK_ROLLBACK ); isRollback = eType==TK_ROLLBACK; - if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, isRollback ? "ROLLBACK" : "COMMIT", 0, 0) ){ return; } @@ -5177,7 +5179,7 @@ void sqlite3EndTransaction(Parse *pParse, int eType){ /* ** This function is called by the parser when it parses a command to create, -** release or rollback an SQL savepoint. +** release or rollback an SQL savepoint. */ void sqlite3Savepoint(Parse *pParse, int op, Token *pName){ char *zName = sqlite3NameFromToken(pParse->db, pName); @@ -5204,7 +5206,7 @@ int sqlite3OpenTempDatabase(Parse *pParse){ if( db->aDb[1].pBt==0 && !pParse->explain ){ int rc; Btree *pBt; - static const int flags = + static const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | @@ -5252,7 +5254,7 @@ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ /* -** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each +** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each ** attached database. Otherwise, invoke it for the database named zDb only. */ void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb){ @@ -5298,9 +5300,9 @@ void sqlite3MultiWrite(Parse *pParse){ pToplevel->isMultiWrite = 1; } -/* +/* ** The code generator calls this routine if is discovers that it is -** possible to abort a statement prior to completion. In order to +** possible to abort a statement prior to completion. In order to ** perform this abort without corrupting the database, we need to make ** sure that the statement is protected by a statement transaction. ** @@ -5309,7 +5311,7 @@ void sqlite3MultiWrite(Parse *pParse){ ** such that the abort must occur after the multiwrite. This makes ** some statements involving the REPLACE conflict resolution algorithm ** go a little faster. But taking advantage of this time dependency -** makes it more difficult to prove that the code is correct (in +** makes it more difficult to prove that the code is correct (in ** particular, it prevents us from writing an effective ** implementation of sqlite3AssertMayAbort()) and so we have chosen ** to take the safe route and skip the optimization. @@ -5356,7 +5358,7 @@ void sqlite3UniqueConstraint( StrAccum errMsg; Table *pTab = pIdx->pTable; - sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, + sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, pParse->db->aLimit[SQLITE_LIMIT_LENGTH]); if( pIdx->aColExpr ){ sqlite3_str_appendf(&errMsg, "index '%q'", pIdx->zName); @@ -5372,8 +5374,8 @@ void sqlite3UniqueConstraint( } } zErr = sqlite3StrAccumFinish(&errMsg); - sqlite3HaltConstraint(pParse, - IsPrimaryKeyIndex(pIdx) ? SQLITE_CONSTRAINT_PRIMARYKEY + sqlite3HaltConstraint(pParse, + IsPrimaryKeyIndex(pIdx) ? SQLITE_CONSTRAINT_PRIMARYKEY : SQLITE_CONSTRAINT_UNIQUE, onError, zErr, P4_DYNAMIC, P5_ConstraintUnique); } @@ -5385,7 +5387,7 @@ void sqlite3UniqueConstraint( void sqlite3RowidConstraint( Parse *pParse, /* Parsing context */ int onError, /* Conflict resolution algorithm */ - Table *pTab /* The table with the non-unique rowid */ + Table *pTab /* The table with the non-unique rowid */ ){ char *zMsg; int rc; @@ -5627,9 +5629,9 @@ void sqlite3CteDelete(sqlite3 *db, Cte *pCte){ sqlite3DbFree(db, pCte); } -/* -** This routine is invoked once per CTE by the parser while parsing a -** WITH clause. The CTE described by teh third argument is added to +/* +** This routine is invoked once per CTE by the parser while parsing a +** WITH clause. The CTE described by the third argument is added to ** the WITH clause of the second argument. If the second argument is ** NULL, then a new WITH argument is created. */ diff --git a/src/ctime.c b/src/ctime.c index c853e5ba4..368724168 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -60,9 +60,6 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC "4_BYTE_ALIGNED_MALLOC", #endif -#ifdef SQLITE_64BIT_STATS - "64BIT_STATS", -#endif #ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN # if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), @@ -399,6 +396,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), #endif +#ifdef SQLITE_LEGACY_JSON_VALID + "LEGACY_JSON_VALID", +#endif #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS "LIKE_DOESNT_MATCH_BLOBS", #endif diff --git a/src/date.c b/src/date.c index 9b7957bbf..648cdeb88 100644 --- a/src/date.c +++ b/src/date.c @@ -110,8 +110,8 @@ struct DateTime { */ static int getDigits(const char *zDate, const char *zFormat, ...){ /* The aMx[] array translates the 3rd character of each format - ** spec into a max size: a b c d e f */ - static const u16 aMx[] = { 12, 14, 24, 31, 59, 9999 }; + ** spec into a max size: a b c d e f */ + static const u16 aMx[] = { 12, 14, 24, 31, 59, 14712 }; va_list ap; int cnt = 0; char nextC; @@ -452,17 +452,14 @@ static void computeYMD(DateTime *p){ ** Compute the Hour, Minute, and Seconds from the julian day number. */ static void computeHMS(DateTime *p){ - int s; + int day_ms, day_min; /* milliseconds, minutes into the day */ if( p->validHMS ) return; computeJD(p); - s = (int)((p->iJD + 43200000) % 86400000); - p->s = s/1000.0; - s = (int)p->s; - p->s -= s; - p->h = s/3600; - s -= p->h*3600; - p->m = s/60; - p->s += s - p->m*60; + day_ms = (int)((p->iJD + 43200000) % 86400000); + p->s = (day_ms % 60000)/1000.0; + day_min = day_ms/60000; + p->m = day_min % 60; + p->h = day_min / 60; p->rawS = 0; p->validHMS = 1; } @@ -641,6 +638,25 @@ static const struct { { 4, "year", 14713.0, 31536000.0 }, }; +/* +** If the DateTime p is raw number, try to figure out if it is +** a julian day number of a unix timestamp. Set the p value +** appropriately. +*/ +static void autoAdjustDate(DateTime *p){ + if( !p->rawS || p->validJD ){ + p->rawS = 0; + }else if( p->s>=-21086676*(i64)10000 /* -4713-11-24 12:00:00 */ + && p->s<=(25340230*(i64)10000)+799 /* 9999-12-31 23:59:59 */ + ){ + double r = p->s*1000.0 + 210866760000000.0; + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)(r + 0.5); + p->validJD = 1; + p->rawS = 0; + } +} + /* ** Process a modifier to a date-time stamp. The modifiers are ** as follows: @@ -684,19 +700,8 @@ static int parseModifier( */ if( sqlite3_stricmp(z, "auto")==0 ){ if( idx>1 ) return 1; /* IMP: R-33611-57934 */ - if( !p->rawS || p->validJD ){ - rc = 0; - p->rawS = 0; - }else if( p->s>=-21086676*(i64)10000 /* -4713-11-24 12:00:00 */ - && p->s<=(25340230*(i64)10000)+799 /* 9999-12-31 23:59:59 */ - ){ - r = p->s*1000.0 + 210866760000000.0; - clearYMD_HMS_TZ(p); - p->iJD = (sqlite3_int64)(r + 0.5); - p->validJD = 1; - p->rawS = 0; - rc = 0; - } + autoAdjustDate(p); + rc = 0; } break; } @@ -862,18 +867,73 @@ static int parseModifier( case '9': { double rRounder; int i; - for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){} + int Y,M,D,h,m,x; + const char *z2 = z; + char z0 = z[0]; + for(n=1; z[n]; n++){ + if( z[n]==':' ) break; + if( sqlite3Isspace(z[n]) ) break; + if( z[n]=='-' ){ + if( n==5 && getDigits(&z[1], "40f", &Y)==1 ) break; + if( n==6 && getDigits(&z[1], "50f", &Y)==1 ) break; + } + } if( sqlite3AtoF(z, &r, n, SQLITE_UTF8)<=0 ){ - rc = 1; + assert( rc==1 ); break; } - if( z[n]==':' ){ + if( z[n]=='-' ){ + /* A modifier of the form (+|-)YYYY-MM-DD adds or subtracts the + ** specified number of years, months, and days. MM is limited to + ** the range 0-11 and DD is limited to 0-30. + */ + if( z0!='+' && z0!='-' ) break; /* Must start with +/- */ + if( n==5 ){ + if( getDigits(&z[1], "40f-20a-20d", &Y, &M, &D)!=3 ) break; + }else{ + assert( n==6 ); + if( getDigits(&z[1], "50f-20a-20d", &Y, &M, &D)!=3 ) break; + z++; + } + if( M>=12 ) break; /* M range 0..11 */ + if( D>=31 ) break; /* D range 0..30 */ + computeYMD_HMS(p); + p->validJD = 0; + if( z0=='-' ){ + p->Y -= Y; + p->M -= M; + D = -D; + }else{ + p->Y += Y; + p->M += M; + } + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + computeJD(p); + p->validHMS = 0; + p->validYMD = 0; + p->iJD += (i64)D*86400000; + if( z[11]==0 ){ + rc = 0; + break; + } + if( sqlite3Isspace(z[11]) + && getDigits(&z[12], "20c:20e", &h, &m)==2 + ){ + z2 = &z[12]; + n = 2; + }else{ + break; + } + } + if( z2[n]==':' ){ /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the ** specified number of hours, minutes, seconds, and fractional seconds ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be ** omitted. */ - const char *z2 = z; + DateTime tx; sqlite3_int64 day; if( !sqlite3Isdigit(*z2) ) z2++; @@ -883,7 +943,7 @@ static int parseModifier( tx.iJD -= 43200000; day = tx.iJD/86400000; tx.iJD -= day*86400000; - if( z[0]=='-' ) tx.iJD = -tx.iJD; + if( z0=='-' ) tx.iJD = -tx.iJD; computeJD(p); clearYMD_HMS_TZ(p); p->iJD += tx.iJD; @@ -899,7 +959,7 @@ static int parseModifier( if( n>10 || n<3 ) break; if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--; computeJD(p); - rc = 1; + assert( rc==1 ); rRounder = r<0 ? -0.5 : +0.5; for(i=0; iM += (int)r; @@ -1067,7 +1126,7 @@ static void datetimeFunc( zBuf[16] = '0' + (x.m)%10; zBuf[17] = ':'; if( x.useSubsec ){ - s = (int)1000.0*x.s; + s = (int)(1000.0*x.s + 0.5); zBuf[18] = '0' + (s/10000)%10; zBuf[19] = '0' + (s/1000)%10; zBuf[20] = '.'; @@ -1114,7 +1173,7 @@ static void timeFunc( zBuf[4] = '0' + (x.m)%10; zBuf[5] = ':'; if( x.useSubsec ){ - s = (int)1000.0*x.s; + s = (int)(1000.0*x.s + 0.5); zBuf[6] = '0' + (s/10000)%10; zBuf[7] = '0' + (s/1000)%10; zBuf[8] = '.'; @@ -1185,7 +1244,7 @@ static void dateFunc( ** %M minute 00-59 ** %s seconds since 1970-01-01 ** %S seconds 00-59 -** %w day of week 0-6 sunday==0 +** %w day of week 0-6 Sunday==0 ** %W week of year 00-53 ** %Y year 0000-9999 ** %% % @@ -1325,6 +1384,117 @@ static void cdateFunc( dateFunc(context, 0, 0); } +/* +** timediff(DATE1, DATE2) +** +** Return the amount of time that must be added to DATE2 in order to +** convert it into DATE2. The time difference format is: +** +** +YYYY-MM-DD HH:MM:SS.SSS +** +** The initial "+" becomes "-" if DATE1 occurs before DATE2. For +** date/time values A and B, the following invariant should hold: +** +** datetime(A) == (datetime(B, timediff(A,B)) +** +** Both DATE arguments must be either a julian day number, or an +** ISO-8601 string. The unix timestamps are not supported by this +** routine. +*/ +static void timediffFunc( + sqlite3_context *context, + int NotUsed1, + sqlite3_value **argv +){ + char sign; + int Y, M; + DateTime d1, d2; + sqlite3_str sRes; + UNUSED_PARAMETER(NotUsed1); + if( isDate(context, 1, &argv[0], &d1) ) return; + if( isDate(context, 1, &argv[1], &d2) ) return; + computeYMD_HMS(&d1); + computeYMD_HMS(&d2); + if( d1.iJD>=d2.iJD ){ + sign = '+'; + Y = d1.Y - d2.Y; + if( Y ){ + d2.Y = d1.Y; + d2.validJD = 0; + computeJD(&d2); + } + M = d1.M - d2.M; + if( M<0 ){ + Y--; + M += 12; + } + if( M!=0 ){ + d2.M = d1.M; + d2.validJD = 0; + computeJD(&d2); + } + while( d1.iJDd2.iJD ){ + M--; + if( M<0 ){ + M = 11; + Y--; + } + d2.M++; + if( d2.M>12 ){ + d2.M = 1; + d2.Y++; + } + d2.validJD = 0; + computeJD(&d2); + } + d1.iJD = d2.iJD - d1.iJD; + d1.iJD += (u64)1486995408 * (u64)100000; + } + d1.validYMD = 0; + d1.validHMS = 0; + d1.validTZ = 0; + computeYMD_HMS(&d1); + sqlite3StrAccumInit(&sRes, 0, 0, 0, 100); + sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f", + sign, Y, M, d1.D-1, d1.h, d1.m, d1.s); + sqlite3ResultStrAccum(context, &sRes); +} + + /* ** current_timestamp() ** @@ -1399,6 +1569,7 @@ void sqlite3RegisterDateTimeFunctions(void){ PURE_DATE(time, -1, 0, 0, timeFunc ), PURE_DATE(datetime, -1, 0, 0, datetimeFunc ), PURE_DATE(strftime, -1, 0, 0, strftimeFunc ), + PURE_DATE(timediff, 2, 0, 0, timediffFunc ), DFUNCTION(current_time, 0, 0, 0, ctimeFunc ), DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc), DFUNCTION(current_date, 0, 0, 0, cdateFunc ), diff --git a/src/delete.c b/src/delete.c index 27a554916..2baff5b3d 100644 --- a/src/delete.c +++ b/src/delete.c @@ -19,7 +19,7 @@ ** (as in the FROM clause of a SELECT statement) in this case it contains ** the name of a single table, as one might find in an INSERT, DELETE, ** or UPDATE statement. Look up that table in the symbol table and -** return a pointer. Set an error message and return NULL if the table +** return a pointer. Set an error message and return NULL if the table ** name is not found or if any other error occurs. ** ** The following fields are initialized appropriate in pSrc: @@ -33,8 +33,9 @@ Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ Table *pTab; assert( pItem && pSrc->nSrc>=1 ); pTab = sqlite3LocateTableItem(pParse, 0, pItem); - sqlite3DeleteTable(pParse->db, pItem->pTab); + if( pItem->pTab ) sqlite3DeleteTable(pParse->db, pItem->pTab); pItem->pTab = pTab; + pItem->fg.notCte = 1; if( pTab ){ pTab->nTabRef++; if( pItem->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pItem) ){ @@ -44,7 +45,7 @@ Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ return pTab; } -/* Generate byte-code that will report the number of rows modified +/* Generate byte-code that will report the number of rows modified ** by a DELETE, INSERT, or UPDATE statement. */ void sqlite3CodeChangeCount(Vdbe *v, int regCounter, const char *zColName){ @@ -66,7 +67,7 @@ void sqlite3CodeChangeCount(Vdbe *v, int regCounter, const char *zColName){ ** the table is not SQLITE_VTAB_INNOCUOUS. ** ** 3) It is a system table (i.e. sqlite_schema), this call is not -** part of a nested parse and writable_schema pragma has not +** part of a nested parse and writable_schema pragma has not ** been specified ** ** 4) The table is a shadow table, the database connection is in @@ -85,7 +86,7 @@ static int vtabIsReadOnly(Parse *pParse, Table *pTab){ ** virtual tables if PRAGMA trusted_schema=ON. */ if( pParse->pToplevel!=0 - && pTab->u.vtab.p->eVtabRisk > + && pTab->u.vtab.p->eVtabRisk > ((pParse->db->flags & SQLITE_TrustedSchema)!=0) ){ sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", @@ -108,7 +109,7 @@ static int tabIsReadOnly(Parse *pParse, Table *pTab){ } /* -** Check to make sure the given table is writable. +** Check to make sure the given table is writable. ** ** If pTab is not writable -> generate an error message and return 1. ** If pTab is writable but other errors have occurred -> return 1. @@ -120,7 +121,7 @@ int sqlite3IsReadOnly(Parse *pParse, Table *pTab, Trigger *pTrigger){ return 1; } #ifndef SQLITE_OMIT_VIEW - if( IsView(pTab) + if( IsView(pTab) && (pTrigger==0 || (pTrigger->bReturning && pTrigger->pNext==0)) ){ sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); @@ -159,7 +160,7 @@ void sqlite3MaterializeView( assert( pFrom->a[0].fg.isUsing==0 ); assert( pFrom->a[0].u3.pOn==0 ); } - pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, + pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, SF_IncludeHidden, pLimit); sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur); sqlite3Select(pParse, pSel, &dest); @@ -187,7 +188,7 @@ Expr *sqlite3LimitWhere( sqlite3 *db = pParse->db; Expr *pLhs = NULL; /* LHS of IN(SELECT...) operator */ Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */ - ExprList *pEList = NULL; /* Expression list contaning only pSelectRowid */ + ExprList *pEList = NULL; /* Expression list containing only pSelectRowid*/ SrcList *pSelectSrc = NULL; /* SELECT rowid FROM x ... (dup of pSrc) */ Select *pSelect = NULL; /* Complete SELECT tree */ Table *pTab; @@ -208,11 +209,11 @@ Expr *sqlite3LimitWhere( return pWhere; } - /* Generate a select expression tree to enforce the limit/offset + /* Generate a select expression tree to enforce the limit/offset ** term for the DELETE or UPDATE statement. For example: ** DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 ** becomes: - ** DELETE FROM table_a WHERE rowid IN ( + ** DELETE FROM table_a WHERE rowid IN ( ** SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 ** ); */ @@ -225,14 +226,20 @@ Expr *sqlite3LimitWhere( ); }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol>=1 ); if( pPk->nKeyCol==1 ){ - const char *zName = pTab->aCol[pPk->aiColumn[0]].zCnName; + const char *zName; + assert( pPk->aiColumn[0]>=0 && pPk->aiColumn[0]nCol ); + zName = pTab->aCol[pPk->aiColumn[0]].zCnName; pLhs = sqlite3Expr(db, TK_ID, zName); pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, zName)); }else{ int i; for(i=0; inKeyCol; i++){ - Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); + Expr *p; + assert( pPk->aiColumn[i]>=0 && pPk->aiColumn[i]nCol ); + p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); pEList = sqlite3ExprListAppend(pParse, pEList, p); } pLhs = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); @@ -257,11 +264,11 @@ Expr *sqlite3LimitWhere( } /* generate the SELECT expression tree. */ - pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, + pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, pOrderBy,0,pLimit ); - /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ + /* now generate the new WHERE rowid IN clause for the DELETE/UPDATE */ pInClause = sqlite3PExpr(pParse, TK_IN, pLhs, 0); sqlite3PExprAddSelect(pParse, pInClause, pSelect); return pInClause; @@ -313,7 +320,7 @@ void sqlite3DeleteFrom( int addrEphOpen = 0; /* Instruction to open the Ephemeral table */ int bComplex; /* True if there are triggers or FKs or ** subqueries in the WHERE clause */ - + #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to delete from a view */ Trigger *pTrigger; /* List of table triggers, if required */ @@ -381,7 +388,7 @@ void sqlite3DeleteFrom( } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDbnDb ); - rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, + rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, db->aDb[iDb].zDbSName); assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE ); if( rcauth==SQLITE_DENY ){ @@ -417,7 +424,7 @@ void sqlite3DeleteFrom( */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, + sqlite3MaterializeView(pParse, pTab, pWhere, pOrderBy, pLimit, iTabCur ); iDataCur = iIdxCur = iTabCur; @@ -450,7 +457,7 @@ void sqlite3DeleteFrom( #ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION /* Special case: A DELETE without a WHERE clause deletes everything. ** It is easier just to erase the whole table. Prior to version 3.6.5, - ** this optimization caused the row change count (the value returned by + ** this optimization caused the row change count (the value returned by ** API function sqlite3_count_changes) to be set incorrectly. ** ** The "rcauth==SQLITE_OK" terms is the @@ -490,7 +497,7 @@ void sqlite3DeleteFrom( if( HasRowid(pTab) ){ /* For a rowid table, initialize the RowSet to an empty set */ pPk = 0; - nPk = 1; + assert( nPk==1 ); iRowSet = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); }else{ @@ -505,7 +512,7 @@ void sqlite3DeleteFrom( addrEphOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk); sqlite3VdbeSetP4KeyInfo(pParse, pPk); } - + /* Construct a query to find the rowid or primary key for every row ** to be deleted, based on the WHERE clause. Set variable eOnePass ** to indicate the strategy used to implement this delete: @@ -518,17 +525,18 @@ void sqlite3DeleteFrom( if( pWInfo==0 ) goto delete_from_cleanup; eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI ); - assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF ); + assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF + || OptimizationDisabled(db, SQLITE_OnePass) ); if( eOnePass!=ONEPASS_SINGLE ) sqlite3MultiWrite(pParse); if( sqlite3WhereUsesDeferredSeek(pWInfo) ){ sqlite3VdbeAddOp1(v, OP_FinishSeek, iTabCur); } - + /* Keep track of the number of rows to be deleted */ if( memCnt ){ sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); } - + /* Extract the rowid or primary key for the current row */ if( pPk ){ for(i=0; inMem; sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, -1, iKey); } - + if( eOnePass!=ONEPASS_OFF ){ /* For ONEPASS, no need to store the rowid/primary-key. There is only ** one, so just keep it in its register(s) and fall through to the @@ -573,10 +581,10 @@ void sqlite3DeleteFrom( } sqlite3WhereEnd(pWInfo); } - - /* Unless this is a view, open cursors for the table we are + + /* Unless this is a view, open cursors for the table we are ** deleting from and all its indices. If this is a view, then the - ** only effect this statement has is to fire the INSTEAD OF + ** only effect this statement has is to fire the INSTEAD OF ** triggers. */ if( !isView ){ @@ -593,7 +601,7 @@ void sqlite3DeleteFrom( sqlite3VdbeJumpHereOrPopInst(v, iAddrOnce); } } - + /* Set up a loop over the rowids/primary-keys that were found in the ** where-clause loop above. */ @@ -616,8 +624,8 @@ void sqlite3DeleteFrom( addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey); VdbeCoverage(v); assert( nKey==1 ); - } - + } + /* Delete the row */ #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pTab) ){ @@ -640,7 +648,7 @@ void sqlite3DeleteFrom( sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, iKey, nKey, count, OE_Default, eOnePass, aiCurOnePass[1]); } - + /* End of the loop over all rowids/primary-keys. */ if( eOnePass!=ONEPASS_OFF ){ sqlite3VdbeResolveLabel(v, addrBypass); @@ -651,7 +659,7 @@ void sqlite3DeleteFrom( }else{ sqlite3VdbeGoto(v, addrLoop); sqlite3VdbeJumpHere(v, addrLoop); - } + } } /* End non-truncate path */ /* Update the sqlite_sequence table by storing the content of the @@ -662,7 +670,7 @@ void sqlite3DeleteFrom( sqlite3AutoincrementEnd(pParse); } - /* Return the number of rows that were deleted. If this routine is + /* Return the number of rows that were deleted. If this routine is ** generating code because of a call to sqlite3NestedParse(), do not ** invoke the callback function. */ @@ -674,7 +682,7 @@ void sqlite3DeleteFrom( sqlite3AuthContextPop(&sContext); sqlite3SrcListDelete(db, pTabList); sqlite3ExprDelete(db, pWhere); -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) sqlite3ExprListDelete(db, pOrderBy); sqlite3ExprDelete(db, pLimit); #endif @@ -719,7 +727,7 @@ void sqlite3DeleteFrom( ** and nPk before reading from it. ** ** If eMode is ONEPASS_MULTI, then this call is being made as part -** of a ONEPASS delete that affects multiple rows. In this case, if +** of a ONEPASS delete that affects multiple rows. In this case, if ** iIdxNoSeek is a valid cursor number (>=0) and is not the same as ** iDataCur, then its position should be preserved following the delete ** operation. Or, if iIdxNoSeek is not a valid cursor number, the @@ -755,7 +763,7 @@ void sqlite3GenerateRowDelete( VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)", iDataCur, iIdxCur, iPk, (int)nPk)); - /* Seek cursor iCur to the row to delete. If this row no longer exists + /* Seek cursor iCur to the row to delete. If this row no longer exists ** (this can happen if a trigger program has already deleted it), do ** not attempt to delete it or fire any DELETE triggers. */ iLabel = sqlite3VdbeMakeLabel(pParse); @@ -765,7 +773,7 @@ void sqlite3GenerateRowDelete( VdbeCoverageIf(v, opSeek==OP_NotExists); VdbeCoverageIf(v, opSeek==OP_NotFound); } - + /* If there are any triggers to fire, allocate a range of registers to ** use for the old.* references in the triggers. */ if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){ @@ -782,7 +790,7 @@ void sqlite3GenerateRowDelete( iOld = pParse->nMem+1; pParse->nMem += (1 + pTab->nCol); - /* Populate the OLD.* pseudo-table register array. These values will be + /* Populate the OLD.* pseudo-table register array. These values will be ** used by any BEFORE and AFTER triggers that exist. */ sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld); for(iCol=0; iColnCol; iCol++){ @@ -796,11 +804,11 @@ void sqlite3GenerateRowDelete( /* Invoke BEFORE DELETE trigger programs. */ addrStart = sqlite3VdbeCurrentAddr(v); - sqlite3CodeRowTrigger(pParse, pTrigger, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel ); - /* If any BEFORE triggers were coded, then seek the cursor to the + /* If any BEFORE triggers were coded, then seek the cursor to the ** row to be deleted again. It may be that the BEFORE triggers moved ** the cursor or already deleted the row that the cursor was ** pointing to. @@ -817,21 +825,21 @@ void sqlite3GenerateRowDelete( } /* Do FK processing. This call checks that any FK constraints that - ** refer to this table (i.e. constraints attached to other tables) + ** refer to this table (i.e. constraints attached to other tables) ** are not violated by deleting this row. */ sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0); } /* Delete the index and table entries. Skip this step if pTab is really ** a view (in which case the only effect of the DELETE statement is to - ** fire the INSTEAD OF triggers). + ** fire the INSTEAD OF triggers). ** ** If variable 'count' is non-zero, then this OP_Delete instruction should ** invoke the update-hook. The pre-update-hook, on the other hand should ** be invoked unless table pTab is a system table. The difference is that - ** the update-hook is not invoked for rows removed by REPLACE, but the + ** the update-hook is not invoked for rows removed by REPLACE, but the ** pre-update-hook is. - */ + */ if( !IsView(pTab) ){ u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); @@ -851,16 +859,18 @@ void sqlite3GenerateRowDelete( /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key - ** to the row just deleted. */ + ** to the row just deleted. */ sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0); /* Invoke AFTER DELETE trigger programs. */ - sqlite3CodeRowTrigger(pParse, pTrigger, - TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel - ); + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, + TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel + ); + } /* Jump here if the row had already been deleted before any BEFORE - ** trigger programs were invoked. Or if a trigger program throws a + ** trigger programs were invoked. Or if a trigger program throws a ** RAISE(IGNORE) exception. */ sqlite3VdbeResolveLabel(v, iLabel); VdbeModuleComment((v, "END: GenRowDel()")); @@ -945,7 +955,7 @@ void sqlite3GenerateRowIndexDelete( ** its key into the same sequence of registers and if pPrior and pIdx share ** a column in common, then the register corresponding to that column already ** holds the correct value and the loading of that register is skipped. -** This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK +** This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK ** on a table with multiple indices, and especially with the ROWID or ** PRIMARY KEY columns of the index. */ @@ -968,7 +978,7 @@ int sqlite3GenerateIndexKey( if( pIdx->pPartIdxWhere ){ *piPartIdxLabel = sqlite3VdbeMakeLabel(pParse); pParse->iSelfTab = iDataCur + 1; - sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, + sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, SQLITE_JUMPIFNULL); pParse->iSelfTab = 0; pPrior = 0; /* Ticket a9efb42811fa41ee 2019-11-02; diff --git a/src/expr.c b/src/expr.c index a81b4595b..d96f36285 100644 --- a/src/expr.c +++ b/src/expr.c @@ -30,7 +30,7 @@ char sqlite3TableColumnAffinity(const Table *pTab, int iCol){ ** Return the 'affinity' of the expression pExpr if any. ** ** If pExpr is a column, a reference to a column via an 'AS' alias, -** or a sub-select with a column as the return value, then the +** or a sub-select with a column as the return value, then the ** affinity of that column is returned. Otherwise, 0x00 is returned, ** indicating no affinity for the expression. ** @@ -67,6 +67,7 @@ char sqlite3ExprAffinity(const Expr *pExpr){ if( op==TK_SELECT_COLUMN ){ assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); assert( pExpr->iColumn < pExpr->iTable ); + assert( pExpr->iColumn >= 0 ); assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); return sqlite3ExprAffinity( pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr @@ -201,7 +202,7 @@ Expr *sqlite3ExprSkipCollate(Expr *pExpr){ while( pExpr && ExprHasProperty(pExpr, EP_Skip) ){ assert( pExpr->op==TK_COLLATE ); pExpr = pExpr->pLeft; - } + } return pExpr; } @@ -221,7 +222,7 @@ Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){ assert( pExpr->op==TK_COLLATE ); pExpr = pExpr->pLeft; } - } + } return pExpr; } @@ -246,7 +247,7 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ while( p ){ int op = p->op; if( op==TK_REGISTER ) op = p->op2; - if( (op==TK_AGG_COLUMN && p->y.pTab!=0) + if( (op==TK_AGG_COLUMN && p->y.pTab!=0) || op==TK_COLUMN || op==TK_TRIGGER ){ int j; @@ -294,7 +295,7 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ break; } } - if( sqlite3CheckCollSeq(pParse, pColl) ){ + if( sqlite3CheckCollSeq(pParse, pColl) ){ pColl = 0; } return pColl; @@ -303,7 +304,7 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ /* ** Return the collation sequence for the expression pExpr. If ** there is no defined collating sequence, return a pointer to the -** defautl collation sequence. +** default collation sequence. ** ** See also: sqlite3ExprCollSeq() ** @@ -414,8 +415,8 @@ static u8 binaryCompareP5( ** it is not considered. */ CollSeq *sqlite3BinaryCompareCollSeq( - Parse *pParse, - const Expr *pLeft, + Parse *pParse, + const Expr *pLeft, const Expr *pRight ){ CollSeq *pColl; @@ -433,7 +434,7 @@ CollSeq *sqlite3BinaryCompareCollSeq( return pColl; } -/* Expresssion p is a comparison operator. Return a collation sequence +/* Expression p is a comparison operator. Return a collation sequence ** appropriate for the comparison operator. ** ** This is normally just a wrapper around sqlite3BinaryCompareCollSeq(). @@ -493,7 +494,7 @@ int sqlite3ExprIsVector(const Expr *pExpr){ } /* -** If the expression passed as the only argument is of type TK_VECTOR +** If the expression passed as the only argument is of type TK_VECTOR ** return the number of expressions in the vector. Or, if the expression ** is a sub-select, return the number of columns in the sub-select. For ** any other type of expression, return 1. @@ -547,7 +548,7 @@ Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ ** sqlite3ExprCode() will generate all necessary code to compute ** the iField-th column of the vector expression pVector. ** -** It is ok for pVector to be a scalar (as long as iField==0). +** It is ok for pVector to be a scalar (as long as iField==0). ** In that case, this routine works like sqlite3ExprDup(). ** ** The caller owns the returned Expr object and is responsible for @@ -613,7 +614,7 @@ Expr *sqlite3ExprForVectorField( /* ** If expression pExpr is of type TK_SELECT, generate code to evaluate -** it. Return the register in which the result is stored (or, if the +** it. Return the register in which the result is stored (or, if the ** sub-select returns more than one column, the first in an array ** of registers in which the result is stored). ** @@ -635,10 +636,10 @@ static int exprCodeSubselect(Parse *pParse, Expr *pExpr){ ** the register number of a register that contains the value of ** element iField of the vector. ** -** If pVector is a TK_SELECT expression, then code for it must have +** If pVector is a TK_SELECT expression, then code for it must have ** already been generated using the exprCodeSubselect() routine. In this ** case parameter regSelect should be the first in an array of registers -** containing the results of the sub-select. +** containing the results of the sub-select. ** ** If pVector is of type TK_VECTOR, then code for the requested field ** is generated. In this case (*pRegFree) may be set to the number of @@ -710,10 +711,10 @@ static void codeVectorCompare( sqlite3ErrorMsg(pParse, "row value misused"); return; } - assert( pExpr->op==TK_EQ || pExpr->op==TK_NE - || pExpr->op==TK_IS || pExpr->op==TK_ISNOT - || pExpr->op==TK_LT || pExpr->op==TK_GT - || pExpr->op==TK_LE || pExpr->op==TK_GE + assert( pExpr->op==TK_EQ || pExpr->op==TK_NE + || pExpr->op==TK_IS || pExpr->op==TK_ISNOT + || pExpr->op==TK_LT || pExpr->op==TK_GT + || pExpr->op==TK_LE || pExpr->op==TK_GE ); assert( pExpr->op==op || (pExpr->op==TK_IS && op==TK_EQ) || (pExpr->op==TK_ISNOT && op==TK_NE) ); @@ -730,7 +731,7 @@ static void codeVectorCompare( sqlite3VdbeAddOp2(v, OP_Integer, 1, dest); for(i=0; 1 /*Loop exits by "break"*/; i++){ int regFree1 = 0, regFree2 = 0; - Expr *pL = 0, *pR = 0; + Expr *pL = 0, *pR = 0; int r1, r2; assert( i>=0 && idb->aLimit[SQLITE_LIMIT_EXPR_DEPTH]; if( nHeight>mxHeight ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "Expression tree is too large (maximum depth %d)", mxHeight ); rc = SQLITE_ERROR; @@ -829,10 +830,10 @@ static void heightOfSelect(const Select *pSelect, int *pnHeight){ } /* -** Set the Expr.nHeight variable in the structure passed as an -** argument. An expression with no children, Expr.pList or +** Set the Expr.nHeight variable in the structure passed as an +** argument. An expression with no children, Expr.pList or ** Expr.pSelect member has a height of 1. Any other expression -** has a height equal to the maximum height of any other +** has a height equal to the maximum height of any other ** referenced Expr plus one. ** ** Also propagate EP_Propagate flags up from Expr.x.pList to Expr.flags, @@ -858,7 +859,7 @@ static void exprSetHeight(Expr *p){ ** leave an error in pParse. ** ** Also propagate all EP_Propagate flags from the Expr.x.pList into -** Expr.flags. +** Expr.flags. */ void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ if( pParse->nErr ) return; @@ -878,7 +879,7 @@ int sqlite3SelectExprHeight(const Select *p){ #else /* ABOVE: Height enforcement enabled. BELOW: Height enforcement off */ /* ** Propagate all EP_Propagate flags from the Expr.x.pList into -** Expr.flags. +** Expr.flags. */ void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ if( pParse->nErr ) return; @@ -889,6 +890,15 @@ void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ #define exprSetHeight(y) #endif /* SQLITE_MAX_EXPR_DEPTH>0 */ +/* +** Set the error offset for an Expr node, if possible. +*/ +void sqlite3ExprSetErrorOffset(Expr *pExpr, int iOfst){ + if( pExpr==0 ) return; + if( NEVER(ExprUseWJoin(pExpr)) ) return; + pExpr->w.iOfst = iOfst; +} + /* ** This routine is the core allocator for Expr nodes. ** @@ -948,7 +958,7 @@ Expr *sqlite3ExprAlloc( } #if SQLITE_MAX_EXPR_DEPTH>0 pNew->nHeight = 1; -#endif +#endif } return pNew; } @@ -1054,7 +1064,7 @@ void sqlite3PExprAddSelect(Parse *pParse, Expr *pExpr, Select *pSelect){ /* ** Expression list pEList is a list of vector values. This function ** converts the contents of pEList to a VALUES(...) Select statement -** returning 1 row for each element of the list. For example, the +** returning 1 row for each element of the list. For example, the ** expression list: ** ** ( (1,2), (3,4) (5,6) ) @@ -1085,7 +1095,7 @@ Select *sqlite3ExprListToValues(Parse *pParse, int nElem, ExprList *pEList){ nExprElem = 1; } if( nExprElem!=nElem ){ - sqlite3ErrorMsg(pParse, "IN(...) element has %d term%s - expected %d", + sqlite3ErrorMsg(pParse, "IN(...) element has %d term%s - expected %d", nExprElem, nExprElem>1?"s":"", nElem ); break; @@ -1125,7 +1135,7 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ return pLeft; }else{ u32 f = pLeft->flags | pRight->flags; - if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse))==EP_IsFalse + if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse))==EP_IsFalse && !IN_RENAME_OBJECT ){ sqlite3ExprDeferredDelete(pParse, pLeft); @@ -1157,7 +1167,7 @@ Expr *sqlite3ExprFunction( } assert( !ExprHasProperty(pNew, EP_InnerON|EP_OuterON) ); pNew->w.iOfst = (int)(pToken->z - pParse->zTail); - if( pList + if( pList && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] && !pParse->nested ){ @@ -1196,7 +1206,7 @@ void sqlite3ExprFunctionUsable( /* Functions prohibited in triggers and views if: ** (1) tagged with SQLITE_DIRECTONLY ** (2) not tagged with SQLITE_INNOCUOUS (which means it - ** is tagged with SQLITE_FUNC_UNSAFE) and + ** is tagged with SQLITE_FUNC_UNSAFE) and ** SQLITE_DBCONFIG_TRUSTED_SCHEMA is off (meaning ** that the schema is possibly tainted). */ @@ -1207,7 +1217,7 @@ void sqlite3ExprFunctionUsable( /* ** Assign a variable number to an expression that encodes a wildcard -** in the original SQL statement. +** in the original SQL statement. ** ** Wildcards consisting of a single "?" are assigned the next sequential ** variable number. @@ -1349,7 +1359,7 @@ void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ /* ** Arrange to cause pExpr to be deleted when the pParse is deleted. ** This is similar to sqlite3ExprDelete() except that the delete is -** deferred untilthe pParse is deleted. +** deferred until the pParse is deleted. ** ** The pExpr might be deleted immediately on an OOM error. ** @@ -1375,7 +1385,7 @@ void sqlite3ExprUnmapAndDelete(Parse *pParse, Expr *p){ } /* -** Return the number of bytes allocated for the expression structure +** Return the number of bytes allocated for the expression structure ** passed as the first argument. This is always one of EXPR_FULLSIZE, ** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE. */ @@ -1390,14 +1400,14 @@ static int exprStructSize(const Expr *p){ ** to store a copy of an expression or expression tree. They differ in ** how much of the tree is measured. ** -** dupedExprStructSize() Size of only the Expr structure +** dupedExprStructSize() Size of only the Expr structure ** dupedExprNodeSize() Size of Expr + space for token ** dupedExprSize() Expr + token + subtree components ** *************************************************************************** ** -** The dupedExprStructSize() function returns two values OR-ed together: -** (1) the space required for a copy of the Expr structure only and +** The dupedExprStructSize() function returns two values OR-ed together: +** (1) the space required for a copy of the Expr structure only and ** (2) the EP_xxx flags that indicate what the structure size should be. ** The return values is always one of: ** @@ -1424,7 +1434,7 @@ static int dupedExprStructSize(const Expr *p, int flags){ assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 ); - if( 0==flags || p->op==TK_SELECT_COLUMN + if( 0==flags || p->op==TK_SELECT_COLUMN #ifndef SQLITE_OMIT_WINDOWFUNC || ExprHasProperty(p, EP_WinFunc) #endif @@ -1432,7 +1442,7 @@ static int dupedExprStructSize(const Expr *p, int flags){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); - assert( !ExprHasProperty(p, EP_OuterON) ); + assert( !ExprHasProperty(p, EP_OuterON) ); assert( !ExprHasVVAProperty(p, EP_NoReduce) ); if( p->pLeft || p->x.pList ){ nSize = EXPR_REDUCEDSIZE | EP_Reduced; @@ -1445,7 +1455,7 @@ static int dupedExprStructSize(const Expr *p, int flags){ } /* -** This function returns the space in bytes required to store the copy +** This function returns the space in bytes required to store the copy ** of the Expr structure and a copy of the Expr.u.zToken string (if that ** string is defined.) */ @@ -1458,16 +1468,16 @@ static int dupedExprNodeSize(const Expr *p, int flags){ } /* -** Return the number of bytes required to create a duplicate of the +** Return the number of bytes required to create a duplicate of the ** expression passed as the first argument. The second argument is a ** mask containing EXPRDUP_XXX flags. ** ** The value returned includes space to create a copy of the Expr struct ** itself and the buffer referred to by Expr.u.zToken, if any. ** -** If the EXPRDUP_REDUCE flag is set, then the return value includes -** space to duplicate all Expr nodes in the tree formed by Expr.pLeft -** and Expr.pRight variables (but not for any structures pointed to or +** If the EXPRDUP_REDUCE flag is set, then the return value includes +** space to duplicate all Expr nodes in the tree formed by Expr.pLeft +** and Expr.pRight variables (but not for any structures pointed to or ** descended from the Expr.x.pList or Expr.x.pSelect variables). */ static int dupedExprSize(const Expr *p, int flags){ @@ -1482,8 +1492,8 @@ static int dupedExprSize(const Expr *p, int flags){ } /* -** This function is similar to sqlite3ExprDup(), except that if pzBuffer -** is not NULL then *pzBuffer is assumed to point to a buffer large enough +** This function is similar to sqlite3ExprDup(), except that if pzBuffer +** is not NULL then *pzBuffer is assumed to point to a buffer large enough ** to store the copy of expression p, the copies of p->u.zToken ** (if applicable), and the copies of the p->pLeft and p->pRight expressions, ** if any. Before returning, *pzBuffer is set to the first byte past the @@ -1530,7 +1540,7 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ }else{ u32 nSize = (u32)exprStructSize(p); memcpy(zAlloc, p, nSize); - if( nSizemallocFailed flag set. */ @@ -1661,7 +1671,7 @@ static void gatherSelectWindows(Select *p){ ** without effecting the originals. ** ** The expression list, ID, and source lists return by sqlite3ExprListDup(), -** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded +** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded ** by subsequent calls to sqlite*ListAppend() routines. ** ** Any tables that the SrcList might point to are not duplicated. @@ -1694,9 +1704,9 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){ Expr *pOldExpr = pOldItem->pExpr; Expr *pNewExpr; pItem->pExpr = sqlite3ExprDup(db, pOldExpr, flags); - if( pOldExpr + if( pOldExpr && pOldExpr->op==TK_SELECT_COLUMN - && (pNewExpr = pItem->pExpr)!=0 + && (pNewExpr = pItem->pExpr)!=0 ){ if( pNewExpr->pRight ){ pPriorSelectColOld = pOldExpr->pRight; @@ -1721,7 +1731,7 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){ /* ** If cursors, triggers, views and subqueries are all omitted from -** the build, then none of the following routines, except for +** the build, then none of the following routines, except for ** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes ** called with a NULL argument. */ @@ -1757,7 +1767,7 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){ pNewItem->u2.pCteUse->nUse++; } if( pNewItem->fg.isTabFunc ){ - pNewItem->u1.pFuncArg = + pNewItem->u1.pFuncArg = sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); } pTab = pNewItem->pTab = pOldItem->pTab; @@ -1892,7 +1902,7 @@ SQLITE_NOINLINE ExprList *sqlite3ExprListAppendGrow( struct ExprList_item *pItem; ExprList *pNew; pList->nAlloc *= 2; - pNew = sqlite3DbRealloc(db, pList, + pNew = sqlite3DbRealloc(db, pList, sizeof(*pList)+(pList->nAlloc-1)*sizeof(pList->a[0])); if( pNew==0 ){ sqlite3ExprListDelete(db, pList); @@ -1950,8 +1960,8 @@ ExprList *sqlite3ExprListAppendVector( if( NEVER(pColumns==0) ) goto vector_append_error; if( pExpr==0 ) goto vector_append_error; - /* If the RHS is a vector, then we can immediately check to see that - ** the size of the RHS and LHS match. But if the RHS is a SELECT, + /* If the RHS is a vector, then we can immediately check to see that + ** the size of the RHS and LHS match. But if the RHS is a SELECT, ** wildcards ("*") in the result set of the SELECT must be expanded before ** we can do the size check, so defer the size check until code generation. */ @@ -1977,7 +1987,7 @@ ExprList *sqlite3ExprListAppendVector( Expr *pFirst = pList->a[iFirst].pExpr; assert( pFirst!=0 ); assert( pFirst->op==TK_SELECT_COLUMN ); - + /* Store the SELECT statement in pRight so it will be deleted when ** sqlite3ExprListDelete() is called */ pFirst->pRight = pExpr; @@ -2003,13 +2013,13 @@ void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int eNulls){ assert( p->nExpr>0 ); assert( SQLITE_SO_UNDEFINED<0 && SQLITE_SO_ASC==0 && SQLITE_SO_DESC>0 ); - assert( iSortOrder==SQLITE_SO_UNDEFINED - || iSortOrder==SQLITE_SO_ASC - || iSortOrder==SQLITE_SO_DESC + assert( iSortOrder==SQLITE_SO_UNDEFINED + || iSortOrder==SQLITE_SO_ASC + || iSortOrder==SQLITE_SO_DESC ); - assert( eNulls==SQLITE_SO_UNDEFINED - || eNulls==SQLITE_SO_ASC - || eNulls==SQLITE_SO_DESC + assert( eNulls==SQLITE_SO_UNDEFINED + || eNulls==SQLITE_SO_ASC + || eNulls==SQLITE_SO_DESC ); pItem = &p->a[p->nExpr-1]; @@ -2191,7 +2201,7 @@ int sqlite3ExprIdToTrueFalse(Expr *pExpr){ ** and 0 if it is FALSE. */ int sqlite3ExprTruthValue(const Expr *pExpr){ - pExpr = sqlite3ExprSkipCollate((Expr*)pExpr); + pExpr = sqlite3ExprSkipCollateAndLikely((Expr*)pExpr); assert( pExpr->op==TK_TRUEFALSE ); assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 @@ -2248,7 +2258,7 @@ Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ ** when parsing an existing schema out of the sqlite_schema table and 4 ** when processing a new CREATE TABLE statement. A bound parameter raises ** an error for new statements, but is silently converted -** to NULL for existing schemas. This allows sqlite_schema tables that +** to NULL for existing schemas. This allows sqlite_schema tables that ** contain a bound parameter because they were generated by older versions ** of SQLite to be parsed by newer versions of SQLite without raising a ** malformed schema error. @@ -2480,7 +2490,7 @@ static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){ /* ** Walk the expression tree passed as the first argument. Return non-zero -** if the expression consists entirely of constants or copies of terms +** if the expression consists entirely of constants or copies of terms ** in pGroupBy that sort with the BINARY collation sequence. ** ** This routine is used to determine if a term of the HAVING clause can @@ -2510,7 +2520,7 @@ int sqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprList *pGroupBy){ /* ** Walk an expression tree for the DEFAULT field of a column definition -** in a CREATE TABLE statement. Return non-zero if the expression is +** in a CREATE TABLE statement. Return non-zero if the expression is ** acceptable for use as a DEFAULT. That is to say, return non-zero if ** the expression is constant or a function call with constant arguments. ** Return and 0 if there are any variables. @@ -2594,7 +2604,7 @@ int sqlite3ExprIsInteger(const Expr *p, int *pValue){ ** Return FALSE if there is no chance that the expression can be NULL. ** ** If the expression might be NULL or if the expression is too complex -** to tell return TRUE. +** to tell return TRUE. ** ** This routine is used as an optimization, to skip OP_IsNull opcodes ** when we know that a value cannot be NULL. Hence, a false positive @@ -2685,7 +2695,7 @@ int sqlite3IsRowid(const char *z){ } /* -** pX is the RHS of an IN operator. If pX is a SELECT statement +** pX is the RHS of an IN operator. If pX is a SELECT statement ** that can be simplified to a direct table access, then return ** a pointer to the SELECT statement. If pX is not a SELECT statement, ** or if the SELECT statement needs to be materialized into a transient @@ -2751,7 +2761,7 @@ static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){ #ifndef SQLITE_OMIT_SUBQUERY /* -** The argument is an IN operator with a list (not a subquery) on the +** The argument is an IN operator with a list (not a subquery) on the ** right-hand side. Return TRUE if that list is constant. */ static int sqlite3InRhsIsConstant(Expr *pIn){ @@ -2784,7 +2794,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** IN_INDEX_INDEX_ASC - The cursor was opened on an ascending index. ** IN_INDEX_INDEX_DESC - The cursor was opened on a descending index. ** IN_INDEX_EPH - The cursor was opened on a specially created and -** populated epheremal table. +** populated ephemeral table. ** IN_INDEX_NOOP - No cursor was allocated. The IN operator must be ** implemented as a sequence of comparisons. ** @@ -2797,7 +2807,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** an ephemeral table might need to be generated from the RHS and then ** pX->iTable made to point to the ephemeral table instead of an ** existing table. In this case, the creation and initialization of the -** ephmeral table might be put inside of a subroutine, the EP_Subrtn flag +** ephemeral table might be put inside of a subroutine, the EP_Subrtn flag ** will be set on pX and the pX->y.sub fields will be set to show where ** the subroutine is coded. ** @@ -2809,13 +2819,13 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** ** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate ** through the set members) then the b-tree must not contain duplicates. -** An epheremal table will be created unless the selected columns are guaranteed +** An ephemeral table will be created unless the selected columns are guaranteed ** to be unique - either because it is an INTEGER PRIMARY KEY or due to ** a UNIQUE constraint or index. ** -** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used -** for fast set membership tests) then an epheremal table must -** be used unless is a single INTEGER PRIMARY KEY column or an +** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used +** for fast set membership tests) then an ephemeral table must +** be used unless is a single INTEGER PRIMARY KEY column or an ** index can be found with the specified as its left-most. ** ** If the IN_INDEX_NOOP_OK and IN_INDEX_MEMBERSHIP are both set and @@ -2827,7 +2837,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** ** When the b-tree is being used for membership tests, the calling function ** might need to know whether or not the RHS side of the IN operator -** contains a NULL. If prRhsHasNull is not a NULL pointer and +** contains a NULL. If prRhsHasNull is not a NULL pointer and ** if there is any chance that the (...) might contain a NULL value at ** runtime, then a register is allocated and the register number written ** to *prRhsHasNull. If there is no chance that the (...) contains a @@ -2868,9 +2878,9 @@ int sqlite3FindInIndex( mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; iTab = pParse->nTab++; - /* If the RHS of this IN(...) operator is a SELECT, and if it matters + /* If the RHS of this IN(...) operator is a SELECT, and if it matters ** whether or not the SELECT result contains NULL values, check whether - ** or not NULL is actually possible (it may not be, for example, due + ** or not NULL is actually possible (it may not be, for example, due ** to NOT NULL constraints in the schema). If no NULL values are possible, ** set prRhsHasNull to 0 before continuing. */ if( prRhsHasNull && ExprUseXSelect(pX) ){ @@ -2885,7 +2895,7 @@ int sqlite3FindInIndex( } /* Check to see if an existing table or index can be used to - ** satisfy the query. This is preferable to generating a new + ** satisfy the query. This is preferable to generating a new ** ephemeral table. */ if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){ sqlite3 *db = pParse->db; /* Database connection */ @@ -2921,7 +2931,7 @@ int sqlite3FindInIndex( int affinity_ok = 1; int i; - /* Check that the affinity that will be used to perform each + /* Check that the affinity that will be used to perform each ** comparison is the same as the affinity of each column in table ** on the RHS of the IN operator. If it not, it is not possible to ** use any index of the RHS table. */ @@ -2966,14 +2976,14 @@ int sqlite3FindInIndex( continue; /* This index is not unique over the IN RHS columns */ } } - + colUsed = 0; /* Columns of index used so far */ for(i=0; ipLeft, i); Expr *pRhs = pEList->a[i].pExpr; CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); int j; - + for(j=0; jaiColumn[j]!=pRhs->iColumn ) continue; assert( pIdx->azColl[j] ); @@ -2988,7 +2998,7 @@ int sqlite3FindInIndex( colUsed |= mCol; if( aiMap ) aiMap[i] = j; } - + assert( i==nExpr || colUsed!=(MASKBIT(nExpr)-1) ); if( colUsed==(MASKBIT(nExpr)-1) ){ /* If we reach this point, that means the index pIdx is usable */ @@ -3000,11 +3010,11 @@ int sqlite3FindInIndex( VdbeComment((v, "%s", pIdx->zName)); assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; - + if( prRhsHasNull ){ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK i64 mask = (1<nMem; @@ -3068,8 +3078,8 @@ int sqlite3FindInIndex( #ifndef SQLITE_OMIT_SUBQUERY /* -** Argument pExpr is an (?, ?...) IN(...) expression. This -** function allocates and returns a nul-terminated string containing +** Argument pExpr is an (?, ?...) IN(...) expression. This +** function allocates and returns a nul-terminated string containing ** the affinities to be used for each column of the comparison. ** ** It is the responsibility of the caller to ensure that the returned @@ -3102,11 +3112,11 @@ static char *exprINAffinity(Parse *pParse, const Expr *pExpr){ #ifndef SQLITE_OMIT_SUBQUERY /* -** Load the Parse object passed as the first argument with an error +** Load the Parse object passed as the first argument with an error ** message of the form: ** ** "sub-select returns N columns - expected M" -*/ +*/ void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){ if( pParse->nErr==0 ){ const char *zFmt = "sub-select returns %d columns - expected %d"; @@ -3117,7 +3127,7 @@ void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){ /* ** Expression pExpr is a vector that has been used in a context where -** it is not permitted. If pExpr is a sub-select vector, this routine +** it is not permitted. If pExpr is a sub-select vector, this routine ** loads the Parse object with a message of the form: ** ** "sub-select returns N columns - expected 1" @@ -3125,7 +3135,7 @@ void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){ ** Or, if it is a regular scalar vector: ** ** "row value misused" -*/ +*/ void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ #ifndef SQLITE_OMIT_SUBQUERY if( ExprUseXSelect(pExpr) ){ @@ -3147,7 +3157,7 @@ void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ ** x IN (SELECT a FROM b) -- IN operator with subquery on the right ** ** The pExpr parameter is the IN operator. The cursor number for the -** constructed ephermeral table is returned. The first time the ephemeral +** constructed ephemeral table is returned. The first time the ephemeral ** table is computed, the cursor number is also stored in pExpr->iTable, ** however the cursor number returned might not be the same, as it might ** have been duplicated using OP_OpenDup. @@ -3188,7 +3198,7 @@ void sqlite3CodeRhsOfIN( if( !ExprHasProperty(pExpr, EP_VarSelect) && pParse->iSelfTab==0 ){ /* Reuse of the RHS is allowed */ /* If this routine has already been coded, but the previous code - ** might not have been invoked yet, so invoke it now as a subroutine. + ** might not have been invoked yet, so invoke it now as a subroutine. */ if( ExprHasProperty(pExpr, EP_Subrtn) ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); @@ -3265,7 +3275,7 @@ void sqlite3CodeRhsOfIN( if( rc ){ sqlite3KeyInfoUnref(pKeyInfo); return; - } + } assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); @@ -3354,7 +3364,7 @@ void sqlite3CodeRhsOfIN( ** ** The pExpr parameter is the SELECT or EXISTS operator to be coded. ** -** Return the register that holds the result. For a multi-column SELECT, +** Return the register that holds the result. For a multi-column SELECT, ** the result is stored in a contiguous array of registers and the ** return value is the register of the left-most result column. ** Return 0 if an error occurs. @@ -3411,7 +3421,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ if( !ExprHasProperty(pExpr, EP_VarSelect) ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } - + /* For a SELECT, generate code to put the values for all columns of ** the first row into an array of registers and return the index of ** the first register. @@ -3419,7 +3429,7 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists) ** into a register and return that register number. ** - ** In both cases, the query is augmented with "LIMIT 1". Any + ** In both cases, the query is augmented with "LIMIT 1". Any ** preexisting limit is discarded in place of the new LIMIT 1. */ ExplainQueryPlan2(addrExplain, (pParse, 1, "%sSCALAR SUBQUERY %d", @@ -3483,9 +3493,9 @@ int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ #ifndef SQLITE_OMIT_SUBQUERY /* -** Expr pIn is an IN(...) expression. This function checks that the -** sub-select on the RHS of the IN() operator has the same number of -** columns as the vector on the LHS. Or, if the RHS of the IN() is not +** Expr pIn is an IN(...) expression. This function checks that the +** sub-select on the RHS of the IN() operator has the same number of +** columns as the vector on the LHS. Or, if the RHS of the IN() is not ** a sub-query, that the LHS is a vector of size 1. */ int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ @@ -3510,18 +3520,18 @@ int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ ** x IN (SELECT ...) ** x IN (value, value, ...) ** -** The left-hand side (LHS) is a scalar or vector expression. The +** The left-hand side (LHS) is a scalar or vector expression. The ** right-hand side (RHS) is an array of zero or more scalar values, or a ** subquery. If the RHS is a subquery, the number of result columns must ** match the number of columns in the vector on the LHS. If the RHS is -** a list of values, the LHS must be a scalar. +** a list of values, the LHS must be a scalar. ** ** The IN operator is true if the LHS value is contained within the RHS. -** The result is false if the LHS is definitely not in the RHS. The -** result is NULL if the presence of the LHS in the RHS cannot be +** The result is false if the LHS is definitely not in the RHS. The +** result is NULL if the presence of the LHS in the RHS cannot be ** determined due to NULLs. ** -** This routine generates code that jumps to destIfFalse if the LHS is not +** This routine generates code that jumps to destIfFalse if the LHS is not ** contained within the RHS. If due to NULLs we cannot determine if the LHS ** is contained in the RHS then jump to destIfNull. If the LHS is contained ** within the RHS then fall through. @@ -3550,7 +3560,7 @@ static void sqlite3ExprCodeIN( int destStep6 = 0; /* Start of code for Step 6 */ int addrTruthOp; /* Address of opcode that determines the IN is true */ int destNotNull; /* Jump here if a comparison is not true in step 6 */ - int addrTop; /* Top of the step-6 loop */ + int addrTop; /* Top of the step-6 loop */ int iTab = 0; /* Index to use */ u8 okConstFactor = pParse->okConstFactor; @@ -3577,7 +3587,7 @@ static void sqlite3ExprCodeIN( aiMap, &iTab); assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH - || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC + || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC ); #ifdef SQLITE_DEBUG /* Confirm that aiMap[] contains nVector integer values between 0 and @@ -3589,8 +3599,8 @@ static void sqlite3ExprCodeIN( } #endif - /* Code the LHS, the from " IN (...)". If the LHS is a - ** vector, then it is stored in an array of nVector registers starting + /* Code the LHS, the from " IN (...)". If the LHS is a + ** vector, then it is stored in an array of nVector registers starting ** at r1. ** ** sqlite3FindInIndex() might have reordered the fields of the LHS vector @@ -3722,7 +3732,7 @@ static void sqlite3ExprCodeIN( } /* Step 5. If we do not care about the difference between NULL and - ** FALSE, then just return false. + ** FALSE, then just return false. */ if( destIfFalse==destIfNull ) sqlite3VdbeGoto(v, destIfFalse); @@ -3783,7 +3793,7 @@ static void sqlite3ExprCodeIN( ** Generate an instruction that will put the floating point ** value described by z[0..n-1] into register iMem. ** -** The z[] string will probably not be zero-terminated. But the +** 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. */ @@ -3948,7 +3958,7 @@ void sqlite3ExprCodeGetColumnOfTable( /* ** Generate code that will extract the iColumn-th column from -** table pTab and store the column value in register iReg. +** table pTab and store the column value in register iReg. ** ** There must be an open cursor to pTab in iTable when this routine ** is called. If iColumn<0 then code is generated that extracts the rowid. @@ -3962,10 +3972,13 @@ int sqlite3ExprCodeGetColumn( u8 p5 /* P5 value for OP_Column + FLAGS */ ){ assert( pParse->pVdbe!=0 ); + assert( (p5 & (OPFLAG_NOCHNG|OPFLAG_TYPEOFARG|OPFLAG_LENGTHARG))==p5 ); + assert( IsVirtual(pTab) || (p5 & OPFLAG_NOCHNG)==0 ); sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pTab, iTable, iColumn, iReg); if( p5 ){ VdbeOp *pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); if( pOp->opcode==OP_Column ) pOp->p5 = p5; + if( pOp->opcode==OP_VColumn ) pOp->p5 = (p5 & OPFLAG_NOCHNG); } return iReg; } @@ -3994,7 +4007,7 @@ static void exprToRegister(Expr *pExpr, int iReg){ /* ** Evaluate an expression (either a vector or a scalar expression) and store -** the result in continguous temporary registers. Return the index of +** the result in contiguous temporary registers. Return the index of ** the first register used to store the result. ** ** If the returned result register is a temporary scalar, then also write @@ -4034,7 +4047,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ */ static void setDoNotMergeFlagOnCopy(Vdbe *v){ if( sqlite3VdbeGetLastOp(v)->opcode==OP_Copy ){ - sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergable */ + sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergeable */ } } @@ -4091,7 +4104,7 @@ static int exprCodeInlineFunction( break; } #endif - default: { + default: { /* The UNLIKELY() function is a no-op. The result is the value ** of the first argument. */ @@ -4108,7 +4121,7 @@ static int exprCodeInlineFunction( case INLINEFUNC_expr_compare: { /* Compare two expressions using sqlite3ExprCompare() */ assert( nFarg==2 ); - sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3VdbeAddOp2(v, OP_Integer, sqlite3ExprCompare(0,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1), target); break; @@ -4117,20 +4130,20 @@ static int exprCodeInlineFunction( case INLINEFUNC_expr_implies_expr: { /* Compare two expressions using sqlite3ExprImpliesExpr() */ assert( nFarg==2 ); - sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3VdbeAddOp2(v, OP_Integer, sqlite3ExprImpliesExpr(pParse,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1), target); break; } case INLINEFUNC_implies_nonnull_row: { - /* REsult of sqlite3ExprImpliesNonNullRow() */ + /* Result of sqlite3ExprImpliesNonNullRow() */ Expr *pA1; assert( nFarg==2 ); pA1 = pFarg->a[1].pExpr; if( pA1->op==TK_COLUMN ){ - sqlite3VdbeAddOp2(v, OP_Integer, - sqlite3ExprImpliesNonNullRow(pFarg->a[0].pExpr,pA1->iTable), + sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3ExprImpliesNonNullRow(pFarg->a[0].pExpr,pA1->iTable,1), target); }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, target); @@ -4245,7 +4258,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ expr_code_doover: if( pExpr==0 ){ op = TK_NULL; - }else if( pParse->pIdxEpr!=0 + }else if( pParse->pIdxEpr!=0 && !ExprHasProperty(pExpr, EP_Leaf) && (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0 ){ @@ -4284,7 +4297,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ }else if( pCol->iColumn<0 ){ VdbeComment((v,"%s.rowid",pTab->zName)); }else{ - VdbeComment((v,"%s.%s", + VdbeComment((v,"%s.%s", pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, target); @@ -4306,7 +4319,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( ExprHasProperty(pExpr, EP_FixedCol) ){ /* This COLUMN expression is really a constant due to WHERE clause ** constraints, and that constant is coded by the pExpr->pLeft - ** expresssion. However, make sure the constant has the correct + ** expression. However, make sure the constant has the correct ** datatype by applying the Affinity of the table column to the ** constant. */ @@ -4501,7 +4514,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ case TK_BITOR: case TK_SLASH: case TK_LSHIFT: - case TK_RSHIFT: + case TK_RSHIFT: case TK_CONCAT: { assert( TK_AND==OP_And ); testcase( op==TK_AND ); assert( TK_OR==OP_Or ); testcase( op==TK_OR ); @@ -4632,7 +4645,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3ErrorMsg(pParse, "unknown function: %#T()", pExpr); break; } - if( pDef->funcFlags & SQLITE_FUNC_INLINE ){ + if( (pDef->funcFlags & SQLITE_FUNC_INLINE)!=0 && ALWAYS(pFarg!=0) ){ assert( (pDef->funcFlags & SQLITE_FUNC_UNSAFE)==0 ); assert( (pDef->funcFlags & SQLITE_FUNC_DIRECT)==0 ); return exprCodeInlineFunction(pParse, pFarg, @@ -4658,10 +4671,10 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ r1 = sqlite3GetTempRange(pParse, nFarg); } - /* For length() and typeof() functions with a column argument, + /* For length() and typeof() and octet_length() functions, ** set the P5 parameter to the OP_Column opcode to OPFLAG_LENGTHARG - ** or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data - ** loading. + ** or OPFLAG_TYPEOFARG or OPFLAG_BYTELENARG respectively, to avoid + ** unnecessary data loading. */ if( (pDef->funcFlags & (SQLITE_FUNC_LENGTH|SQLITE_FUNC_TYPEOF))!=0 ){ u8 exprOp; @@ -4671,14 +4684,16 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( exprOp==TK_COLUMN || exprOp==TK_AGG_COLUMN ){ assert( SQLITE_FUNC_LENGTH==OPFLAG_LENGTHARG ); assert( SQLITE_FUNC_TYPEOF==OPFLAG_TYPEOFARG ); - testcase( pDef->funcFlags & OPFLAG_LENGTHARG ); - pFarg->a[0].pExpr->op2 = - pDef->funcFlags & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG); + assert( SQLITE_FUNC_BYTELEN==OPFLAG_BYTELENARG ); + assert( (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG)==OPFLAG_BYTELENARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_LENGTHARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_TYPEOFARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_BYTELENARG); + pFarg->a[0].pExpr->op2 = pDef->funcFlags & OPFLAG_BYTELENARG; } } - sqlite3ExprCodeExprList(pParse, pFarg, r1, 0, - SQLITE_ECEL_DUP|SQLITE_ECEL_FACTOR); + sqlite3ExprCodeExprList(pParse, pFarg, r1, 0, SQLITE_ECEL_FACTOR); }else{ r1 = 0; } @@ -4691,7 +4706,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** see if it is a column in a virtual table. This is done because ** the left operand of infix functions (the operand we want to ** control overloading) ends up as the second argument to the - ** function. The expression "A glob B" is equivalent to + ** function. The expression "A glob B" is equivalent to ** "glob(B,A). We want to use the A in "A glob B" to test ** for function overloading. But we use the B term in "glob(B,A)". */ @@ -4702,7 +4717,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ } #endif if( pDef->funcFlags & SQLITE_FUNC_NEEDCOLL ){ - if( !pColl ) pColl = db->pDfltColl; + if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg, @@ -4810,7 +4825,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** ** The expression is implemented using an OP_Param opcode. The p1 ** parameter is set to 0 for an old.rowid reference, or to (i+1) - ** to reference another column of the old.* pseudo-table, where + ** to reference another column of the old.* pseudo-table, where ** i is the index of the column. For a new.rowid reference, p1 is ** set to (n+1), where n is the number of columns in each pseudo-table. ** For a reference to any other column in the new.* pseudo-table, p1 @@ -4824,7 +4839,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** ** p1==0 -> old.rowid p1==3 -> new.rowid ** p1==1 -> old.a p1==4 -> new.a - ** p1==2 -> old.b p1==5 -> new.b + ** p1==2 -> old.b p1==5 -> new.b */ Table *pTab; int iCol; @@ -4833,7 +4848,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( ExprUseYTab(pExpr) ); pTab = pExpr->y.pTab; iCol = pExpr->iColumn; - p1 = pExpr->iTable * (pTab->nCol+1) + 1 + p1 = pExpr->iTable * (pTab->nCol+1) + 1 + sqlite3TableColumnToStorage(pTab, iCol); assert( pExpr->iTable==0 || pExpr->iTable==1 ); @@ -4991,7 +5006,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ } #ifndef SQLITE_OMIT_TRIGGER case TK_RAISE: { - assert( pExpr->affExpr==OE_Rollback + assert( pExpr->affExpr==OE_Rollback || pExpr->affExpr==OE_Abort || pExpr->affExpr==OE_Fail || pExpr->affExpr==OE_Ignore @@ -5034,8 +5049,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** the end of the prepared statement in the initialization section. ** ** If regDest>=0 then the result is always stored in that register and the -** result is not reusable. If regDest<0 then this routine is free to -** store the value whereever it wants. The register where the expression +** result is not reusable. If regDest<0 then this routine is free to +** store the value wherever it wants. The register where the expression ** is stored is returned. When regDest<0, two identical expressions might ** code to the same register, if they do not contain function calls and hence ** are factored out into the initialization section at the end of the @@ -5255,7 +5270,7 @@ int sqlite3ExprCodeExprList( ** ** x BETWEEN y AND z ** -** The above is equivalent to +** The above is equivalent to ** ** x>=y AND x<=z ** @@ -5473,7 +5488,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } } sqlite3ReleaseTempReg(pParse, regFree1); - sqlite3ReleaseTempReg(pParse, regFree2); + sqlite3ReleaseTempReg(pParse, regFree2); } /* @@ -5634,7 +5649,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } #endif default: { - default_expr: + default_expr: if( ExprAlwaysFalse(pExpr) ){ sqlite3VdbeGoto(v, dest); }else if( ExprAlwaysTrue(pExpr) ){ @@ -5688,7 +5703,7 @@ static int exprCompareVariable( int res = 0; int iVar; sqlite3_value *pL, *pR = 0; - + sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR); if( pR ){ iVar = pVar->iColumn; @@ -5730,9 +5745,9 @@ static int exprCompareVariable( ** an incorrect 0 or 1 could lead to a malfunction. ** ** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in -** pParse->pReprepare can be matched against literals in pB. The +** pParse->pReprepare can be matched against literals in pB. The ** pParse->pVdbe->expmask bitmask is updated for each variable referenced. -** If pParse is NULL (the normal case) then any TK_VARIABLE term in +** If pParse is NULL (the normal case) then any TK_VARIABLE term in ** Argument pParse should normally be NULL. If it is not NULL and pA or ** pB causes a return value of 2. */ @@ -5763,7 +5778,7 @@ int sqlite3ExprCompare( if( pB->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA,pB->pLeft,iTab)<2 ){ return 1; } - if( pA->op==TK_AGG_COLUMN && pB->op==TK_COLUMN + if( pA->op==TK_AGG_COLUMN && pB->op==TK_COLUMN && pB->iTable<0 && pA->iTable==iTab ){ /* fall through */ @@ -5791,7 +5806,7 @@ int sqlite3ExprCompare( return 0; }else if( pA->op==TK_COLLATE ){ if( sqlite3_stricmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; - }else + }else if( pB->u.zToken!=0 && pA->op!=TK_COLUMN && pA->op!=TK_AGG_COLUMN @@ -5824,7 +5839,7 @@ int sqlite3ExprCompare( /* ** Compare two ExprList objects. Return 0 if they are identical, 1 -** if they are certainly different, or 2 if it is not possible to +** if they are certainly different, or 2 if it is not possible to ** determine if they are identical or not. ** ** If any subelement of pB has Expr.iTable==(-1) then it is allowed @@ -5912,8 +5927,8 @@ static int exprImpliesNotNull( case TK_MINUS: case TK_BITOR: case TK_LSHIFT: - case TK_RSHIFT: - case TK_CONCAT: + case TK_RSHIFT: + case TK_CONCAT: seenNot = 1; /* no break */ deliberate_fall_through case TK_STAR: @@ -5953,14 +5968,14 @@ static int exprImpliesNotNull( ** pE1: x!=123 pE2: x IS NOT NULL Result: true ** pE1: x!=?1 pE2: x IS NOT NULL Result: true ** pE1: x IS NULL pE2: x IS NOT NULL Result: false -** pE1: x IS ?2 pE2: x IS NOT NULL Reuslt: false +** pE1: x IS ?2 pE2: x IS NOT NULL Result: false ** ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has ** Expr.iTable<0 then assume a table number given by iTab. ** -** If pParse is not NULL, then the values of bound variables in pE1 are +** If pParse is not NULL, then the values of bound variables in pE1 are ** compared against literal values in pE2 and pParse->pVdbe->expmask is -** modified to record which bound variables are referenced. If pParse +** modified to record which bound variables are referenced. If pParse ** is NULL, then false will be returned if pE1 contains any bound variables. ** ** When in doubt, return false. Returning true might give a performance @@ -5990,11 +6005,29 @@ int sqlite3ExprImpliesExpr( return 0; } +/* This is a helper function to impliesNotNullRow(). In this routine, +** set pWalker->eCode to one only if *both* of the input expressions +** separately have the implies-not-null-row property. +*/ +static void bothImplyNotNullRow(Walker *pWalker, Expr *pE1, Expr *pE2){ + if( pWalker->eCode==0 ){ + sqlite3WalkExpr(pWalker, pE1); + if( pWalker->eCode ){ + pWalker->eCode = 0; + sqlite3WalkExpr(pWalker, pE2); + } + } +} + /* ** This is the Expr node callback for sqlite3ExprImpliesNonNullRow(). ** If the expression node requires that the table at pWalker->iCur ** have one or more non-NULL column, then set pWalker->eCode to 1 and abort. ** +** pWalker->mWFlags is non-zero if this inquiry is being undertaking on +** behalf of a RIGHT JOIN (or FULL JOIN). That makes a difference when +** evaluating terms in the ON clause of an inner join. +** ** This routine controls an optimization. False positives (setting ** pWalker->eCode to 1 when it should not be) are deadly, but false-negatives ** (never setting pWalker->eCode) is a harmless missed optimization. @@ -6003,28 +6036,33 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_AGG_FUNCTION ); if( ExprHasProperty(pExpr, EP_OuterON) ) return WRC_Prune; + if( ExprHasProperty(pExpr, EP_InnerON) && pWalker->mWFlags ){ + /* If iCur is used in an inner-join ON clause to the left of a + ** RIGHT JOIN, that does *not* mean that the table must be non-null. + ** But it is difficult to check for that condition precisely. + ** To keep things simple, any use of iCur from any inner-join is + ** ignored while attempting to simplify a RIGHT JOIN. */ + return WRC_Prune; + } switch( pExpr->op ){ case TK_ISNOT: case TK_ISNULL: case TK_NOTNULL: case TK_IS: - case TK_OR: case TK_VECTOR: - case TK_CASE: - case TK_IN: case TK_FUNCTION: case TK_TRUTH: + case TK_CASE: testcase( pExpr->op==TK_ISNOT ); testcase( pExpr->op==TK_ISNULL ); testcase( pExpr->op==TK_NOTNULL ); testcase( pExpr->op==TK_IS ); - testcase( pExpr->op==TK_OR ); testcase( pExpr->op==TK_VECTOR ); - testcase( pExpr->op==TK_CASE ); - testcase( pExpr->op==TK_IN ); testcase( pExpr->op==TK_FUNCTION ); testcase( pExpr->op==TK_TRUTH ); + testcase( pExpr->op==TK_CASE ); return WRC_Prune; + case TK_COLUMN: if( pWalker->u.iCur==pExpr->iTable ){ pWalker->eCode = 1; @@ -6032,21 +6070,38 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ } return WRC_Prune; + case TK_OR: case TK_AND: - if( pWalker->eCode==0 ){ + /* Both sides of an AND or OR must separately imply non-null-row. + ** Consider these cases: + ** 1. NOT (x AND y) + ** 2. x OR y + ** If only one of x or y is non-null-row, then the overall expression + ** can be true if the other arm is false (case 1) or true (case 2). + */ + testcase( pExpr->op==TK_OR ); + testcase( pExpr->op==TK_AND ); + bothImplyNotNullRow(pWalker, pExpr->pLeft, pExpr->pRight); + return WRC_Prune; + + case TK_IN: + /* Beware of "x NOT IN ()" and "x NOT IN (SELECT 1 WHERE false)", + ** both of which can be true. But apart from these cases, if + ** the left-hand side of the IN is NULL then the IN itself will be + ** NULL. */ + if( ExprUseXList(pExpr) && ALWAYS(pExpr->x.pList->nExpr>0) ){ sqlite3WalkExpr(pWalker, pExpr->pLeft); - if( pWalker->eCode ){ - pWalker->eCode = 0; - sqlite3WalkExpr(pWalker, pExpr->pRight); - } } return WRC_Prune; case TK_BETWEEN: - if( sqlite3WalkExpr(pWalker, pExpr->pLeft)==WRC_Abort ){ - assert( pWalker->eCode ); - return WRC_Abort; - } + /* In "x NOT BETWEEN y AND z" either x must be non-null-row or else + ** both y and z must be non-null row */ + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + sqlite3WalkExpr(pWalker, pExpr->pLeft); + bothImplyNotNullRow(pWalker, pExpr->x.pList->a[0].pExpr, + pExpr->x.pList->a[1].pExpr); return WRC_Prune; /* Virtual tables are allowed to use constraints like x=NULL. So @@ -6108,7 +6163,7 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ ** be non-NULL, then the LEFT JOIN can be safely converted into an ** ordinary join. */ -int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){ +int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab, int isRJ){ Walker w; p = sqlite3ExprSkipCollateAndLikely(p); if( p==0 ) return 0; @@ -6116,7 +6171,7 @@ int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){ p = p->pLeft; }else{ while( p->op==TK_AND ){ - if( sqlite3ExprImpliesNonNullRow(p->pLeft, iTab) ) return 1; + if( sqlite3ExprImpliesNonNullRow(p->pLeft, iTab, isRJ) ) return 1; p = p->pRight; } } @@ -6124,6 +6179,7 @@ int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){ w.xSelectCallback = 0; w.xSelectCallback2 = 0; w.eCode = 0; + w.mWFlags = isRJ!=0; w.u.iCur = iTab; sqlite3WalkExpr(&w, p); return w.eCode; @@ -6142,7 +6198,7 @@ struct IdxCover { }; /* -** Check to see if there are references to columns in table +** Check to see if there are references to columns in table ** pWalker->u.pIdxCover->iCur can be satisfied using the index ** pWalker->u.pIdxCover->pIdx. */ @@ -6184,7 +6240,7 @@ int sqlite3ExprCoveredByIndex( } -/* Structure used to pass information throught the Walker in order to +/* Structure used to pass information throughout the Walker in order to ** implement sqlite3ReferencesSrcList(). */ struct RefSrcList { @@ -6232,7 +6288,7 @@ static void selectRefLeave(Walker *pWalker, Select *pSelect){ } /* This is the Walker EXPR callback for sqlite3ReferencesSrcList(). -** +** ** Set the 0x01 bit of pWalker->eCode if there is a reference to any ** of the tables shown in RefSrcList.pRef. ** @@ -6377,7 +6433,7 @@ static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){ &i ); return i; -} +} /* ** Add a new element to the pAggInfo->aFunc[] array. Return the index of @@ -6386,7 +6442,7 @@ static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){ static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){ int i; pInfo->aFunc = sqlite3ArrayAllocate( - db, + db, pInfo->aFunc, sizeof(pInfo->aFunc[0]), &pInfo->nFunc, @@ -6397,10 +6453,10 @@ static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){ /* ** Search the AggInfo object for an aCol[] entry that has iTable and iColumn. -** Return the index in aCol[] of the entry that describes that column. +** Return the index in aCol[] of the entry that describes that column. ** ** If no prior entry is found, create a new one and return -1. The -** new column will have an idex of pAggInfo->nColumn-1. +** new column will have an index of pAggInfo->nColumn-1. */ static void findOrCreateAggInfoColumn( Parse *pParse, /* Parsing context */ @@ -6413,6 +6469,7 @@ static void findOrCreateAggInfoColumn( assert( pAggInfo->iFirstReg==0 ); pCol = pAggInfo->aCol; for(k=0; knColumn; k++, pCol++){ + if( pCol->pCExpr==pExpr ) return; if( pCol->iTable==pExpr->iTable && pCol->iColumn==pExpr->iColumn && pExpr->op!=TK_IF_NULL_ROW @@ -6538,7 +6595,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ if( (pNC->ncFlags & NC_InAggFunc)==0 && pWalker->walkerDepth==pExpr->op2 ){ - /* Check to see if pExpr is a duplicate of another aggregate + /* Check to see if pExpr is a duplicate of another aggregate ** function that is already in the pAggInfo structure */ struct AggInfo_func *pItem = pAggInfo->aFunc; @@ -6559,7 +6616,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ pItem->pFExpr = pExpr; assert( ExprUseUToken(pExpr) ); pItem->pFunc = sqlite3FindFunction(pParse->db, - pExpr->u.zToken, + pExpr->u.zToken, pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); if( pExpr->flags & EP_Distinct ){ pItem->iDistinct = pParse->nTab++; diff --git a/src/fkey.c b/src/fkey.c index 29609916b..3142e0ca6 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -1448,9 +1448,8 @@ void sqlite3FkDelete(sqlite3 *db, Table *pTab){ if( pFKey->pPrevTo ){ pFKey->pPrevTo->pNextTo = pFKey->pNextTo; }else{ - void *p = (void *)pFKey->pNextTo; - const char *z = (p ? pFKey->pNextTo->zTo : pFKey->zTo); - sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, p); + const char *z = (pFKey->pNextTo ? pFKey->pNextTo->zTo : pFKey->zTo); + sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, pFKey->pNextTo); } if( pFKey->pNextTo ){ pFKey->pNextTo->pPrevTo = pFKey->pPrevTo; diff --git a/src/func.c b/src/func.c index 4d0da5ded..ca0075edc 100644 --- a/src/func.c +++ b/src/func.c @@ -149,11 +149,47 @@ static void lengthFunc( } } +/* +** Implementation of the octet_length() function +*/ +static void bytelengthFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + UNUSED_PARAMETER(argc); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_BLOB: { + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + break; + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + i64 m = sqlite3_context_db_handle(context)->enc<=SQLITE_UTF8 ? 1 : 2; + sqlite3_result_int64(context, sqlite3_value_bytes(argv[0])*m); + break; + } + case SQLITE_TEXT: { + if( sqlite3_value_encoding(argv[0])<=SQLITE_UTF8 ){ + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + }else{ + sqlite3_result_int(context, sqlite3_value_bytes16(argv[0])); + } + break; + } + default: { + sqlite3_result_null(context); + break; + } + } +} + /* ** Implementation of the abs() function. ** ** IMP: R-23979-26855 The abs(X) function returns the absolute value of -** the numeric argument X. +** the numeric argument X. */ static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ assert( argc==1 ); @@ -170,7 +206,7 @@ static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ return; } iVal = -iVal; - } + } sqlite3_result_int64(context, iVal); break; } @@ -422,10 +458,10 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ */ if( r<-4503599627370496.0 || r>+4503599627370496.0 ){ /* The value has no fractional part so there is nothing to round */ - }else if( n==0 ){ + }else if( n==0 ){ r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); }else{ - zBuf = sqlite3_mprintf("%.*f",n,r); + zBuf = sqlite3_mprintf("%!.*f",n,r); if( zBuf==0 ){ sqlite3_result_error_nomem(context); return; @@ -515,7 +551,7 @@ static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ #define noopFunc versionFunc /* Substitute function - never called */ /* -** Implementation of random(). Return a random integer. +** Implementation of random(). Return a random integer. */ static void randomFunc( sqlite3_context *context, @@ -526,11 +562,11 @@ static void randomFunc( UNUSED_PARAMETER2(NotUsed, NotUsed2); sqlite3_randomness(sizeof(r), &r); if( r<0 ){ - /* We need to prevent a random number of 0x8000000000000000 + /* We need to prevent a random number of 0x8000000000000000 ** (or -9223372036854775808) since when you do abs() of that ** number of you get the same value back again. To do this ** in a way that is testable, mask the sign bit off of negative - ** values, resulting in a positive value. Then take the + ** values, resulting in a positive value. Then take the ** 2s complement of that positive value. The end result can ** therefore be no less than -9223372036854775807. */ @@ -568,8 +604,8 @@ static void randomBlob( ** value is the same as the sqlite3_last_insert_rowid() API function. */ static void last_insert_rowid( - sqlite3_context *context, - int NotUsed, + sqlite3_context *context, + int NotUsed, sqlite3_value **NotUsed2 ){ sqlite3 *db = sqlite3_context_db_handle(context); @@ -625,7 +661,7 @@ struct compareInfo { /* ** For LIKE and GLOB matching on EBCDIC machines, assume that every -** character is exactly one byte in size. Also, provde the Utf8Read() +** character is exactly one byte in size. Also, provide the Utf8Read() ** macro for fast reading of the next character in the common case where ** the next character is ASCII. */ @@ -677,7 +713,7 @@ static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 }; ** it the last character in the list. ** ** Like matching rules: -** +** ** '%' Matches any sequence of zero or more characters ** *** '_' Matches any one character @@ -700,13 +736,13 @@ static int patternCompare( u32 matchAll = pInfo->matchAll; /* "*" or "%" */ u8 noCase = pInfo->noCase; /* True if uppercase==lowercase */ const u8 *zEscaped = 0; /* One past the last escaped input char */ - + while( (c = Utf8Read(zPattern))!=0 ){ if( c==matchAll ){ /* Match "*" */ /* Skip over multiple "*" characters in the pattern. If there ** are also "?" characters, skip those as well, but consume a ** single character of the input string for each "?" skipped */ - while( (c=Utf8Read(zPattern)) == matchAll + while( (c=Utf8Read(zPattern)) == matchAll || (c == matchOne && matchOne!=0) ){ if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){ return SQLITE_NOWILDCARDMATCH; @@ -858,7 +894,7 @@ int sqlite3_like_count = 0; /* ** Implementation of the like() SQL function. This function implements -** the build-in LIKE operator. The first argument to the function is the +** the built-in LIKE operator. The first argument to the function is the ** pattern and the second argument is the string. So, the SQL statements: ** ** A LIKE B @@ -869,8 +905,8 @@ int sqlite3_like_count = 0; ** the GLOB operator. */ static void likeFunc( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ const unsigned char *zA, *zB; @@ -909,7 +945,7 @@ static void likeFunc( const unsigned char *zEsc = sqlite3_value_text(argv[2]); if( zEsc==0 ) return; if( sqlite3Utf8CharLen((char*)zEsc, -1)!=1 ){ - sqlite3_result_error(context, + sqlite3_result_error(context, "ESCAPE expression must be a single character", -1); return; } @@ -1022,8 +1058,8 @@ static void compileoptionusedFunc( #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ /* -** Implementation of the sqlite_compileoption_get() function. -** The result is a string that identifies the compiler options +** Implementation of the sqlite_compileoption_get() function. +** The result is a string that identifies the compiler options ** used to build SQLite. */ #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS @@ -1047,7 +1083,7 @@ static void compileoptiongetFunc( ** digits. */ static const char hexdigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /* @@ -1114,7 +1150,7 @@ void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){ } /* -** Implementation of the QUOTE() function. +** Implementation of the QUOTE() function. ** ** The quote(X) function returns the text of an SQL literal which is the ** value of its argument suitable for inclusion into an SQL statement. @@ -1140,7 +1176,7 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ /* ** The unicode() function. Return the integer unicode code-point value -** for the first character of the input string. +** for the first character of the input string. */ static void unicodeFunc( sqlite3_context *context, @@ -1191,6 +1227,7 @@ static void charFunc( *zOut++ = 0x80 + (u8)(c & 0x3F); } \ } + *zOut = 0; sqlite3_result_text64(context, (char*)z, zOut-z, sqlite3_free, SQLITE_UTF8); } @@ -1244,12 +1281,12 @@ static int strContainsChar(const u8 *zStr, int nStr, u32 ch){ ** decoded and returned as a blob. ** ** If there is only a single argument, then it must consist only of an -** even number of hexadeximal digits. Otherwise, return NULL. +** even number of hexadecimal digits. Otherwise, return NULL. ** ** Or, if there is a second argument, then any character that appears in ** the second argument is also allowed to appear between pairs of hexadecimal ** digits in the first argument. If any other character appears in the -** first argument, or if one of the allowed characters appears between +** first argument, or if one of the allowed characters appears between ** two hexadecimal digits that make up a single byte, NULL is returned. ** ** The following expressions are all true: @@ -1386,7 +1423,7 @@ static void replaceFunc( if( zOut==0 ){ return; } - loopLimit = nStr - nPattern; + loopLimit = nStr - nPattern; cntExpand = 0; for(i=j=0; i<=loopLimit; i++){ if( zStr[i]!=zPattern[0] || memcmp(&zStr[i], zPattern, nPattern) ){ @@ -1468,7 +1505,7 @@ static void trimFunc( SQLITE_SKIP_UTF8(z); } if( nChar>0 ){ - azChar = contextMalloc(context, + azChar = contextMalloc(context, ((i64)nChar)*(sizeof(char*)+sizeof(unsigned))); if( azChar==0 ){ return; @@ -1546,7 +1583,7 @@ static void unknownFunc( ** Compute the soundex encoding of a word. ** ** IMP: R-59782-00072 The soundex(X) function returns a string that is the -** soundex encoding of the string X. +** soundex encoding of the string X. */ static void soundexFunc( sqlite3_context *context, @@ -1634,13 +1671,68 @@ static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){ */ typedef struct SumCtx SumCtx; struct SumCtx { - double rSum; /* Floating point sum */ - i64 iSum; /* Integer sum */ + double rSum; /* Running sum as as a double */ + double rErr; /* Error term for Kahan-Babushka-Neumaier summation */ + i64 iSum; /* Running sum as a signed integer */ i64 cnt; /* Number of elements summed */ - u8 overflow; /* True if integer overflow seen */ - u8 approx; /* True if non-integer value was input to the sum */ + u8 approx; /* True if any non-integer value was input to the sum */ + u8 ovrfl; /* Integer overflow seen */ }; +/* +** Do one step of the Kahan-Babushka-Neumaier summation. +** +** https://en.wikipedia.org/wiki/Kahan_summation_algorithm +** +** Variables are marked "volatile" to defeat c89 x86 floating point +** optimizations can mess up this algorithm. +*/ +static void kahanBabuskaNeumaierStep( + volatile SumCtx *pSum, + volatile double r +){ + volatile double s = pSum->rSum; + volatile double t = s + r; + if( fabs(s) > fabs(r) ){ + pSum->rErr += (s - t) + r; + }else{ + pSum->rErr += (r - t) + s; + } + pSum->rSum = t; +} + +/* +** Add a (possibly large) integer to the running sum. +*/ +static void kahanBabuskaNeumaierStepInt64(volatile SumCtx *pSum, i64 iVal){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iBig, iSm; + iSm = iVal % 16384; + iBig = iVal - iSm; + kahanBabuskaNeumaierStep(pSum, iBig); + kahanBabuskaNeumaierStep(pSum, iSm); + }else{ + kahanBabuskaNeumaierStep(pSum, (double)iVal); + } +} + +/* +** Initialize the Kahan-Babaska-Neumaier sum from a 64-bit integer +*/ +static void kahanBabuskaNeumaierInit( + volatile SumCtx *p, + i64 iVal +){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iSm = iVal % 16384; + p->rSum = (double)(iVal - iSm); + p->rErr = (double)iSm; + }else{ + p->rSum = (double)iVal; + p->rErr = 0.0; + } +} + /* ** Routines used to compute the sum, average, and total. ** @@ -1660,15 +1752,29 @@ static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){ type = sqlite3_value_numeric_type(argv[0]); if( p && type!=SQLITE_NULL ){ p->cnt++; - if( type==SQLITE_INTEGER ){ - i64 v = sqlite3_value_int64(argv[0]); - p->rSum += v; - if( (p->approx|p->overflow)==0 && sqlite3AddInt64(&p->iSum, v) ){ - p->approx = p->overflow = 1; + if( p->approx==0 ){ + if( type!=SQLITE_INTEGER ){ + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + }else{ + i64 x = p->iSum; + if( sqlite3AddInt64(&x, sqlite3_value_int64(argv[0]))==0 ){ + p->iSum = x; + }else{ + p->ovrfl = 1; + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + } } }else{ - p->rSum += sqlite3_value_double(argv[0]); - p->approx = 1; + if( type==SQLITE_INTEGER ){ + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + }else{ + p->ovrfl = 0; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + } } } } @@ -1685,13 +1791,18 @@ static void sumInverse(sqlite3_context *context, int argc, sqlite3_value**argv){ if( ALWAYS(p) && type!=SQLITE_NULL ){ assert( p->cnt>0 ); p->cnt--; - assert( type==SQLITE_INTEGER || p->approx ); - if( type==SQLITE_INTEGER && p->approx==0 ){ - i64 v = sqlite3_value_int64(argv[0]); - p->rSum -= v; - p->iSum -= v; + if( !p->approx ){ + p->iSum -= sqlite3_value_int64(argv[0]); + }else if( type==SQLITE_INTEGER ){ + i64 iVal = sqlite3_value_int64(argv[0]); + if( iVal!=SMALLEST_INT64 ){ + kahanBabuskaNeumaierStepInt64(p, -iVal); + }else{ + kahanBabuskaNeumaierStepInt64(p, LARGEST_INT64); + kahanBabuskaNeumaierStepInt64(p, 1); + } }else{ - p->rSum -= sqlite3_value_double(argv[0]); + kahanBabuskaNeumaierStep(p, -sqlite3_value_double(argv[0])); } } } @@ -1702,10 +1813,12 @@ static void sumFinalize(sqlite3_context *context){ SumCtx *p; p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ - if( p->overflow ){ - sqlite3_result_error(context,"integer overflow",-1); - }else if( p->approx ){ - sqlite3_result_double(context, p->rSum); + if( p->approx ){ + if( p->ovrfl ){ + sqlite3_result_error(context,"integer overflow",-1); + }else{ + sqlite3_result_double(context, p->rSum+p->rErr); + } }else{ sqlite3_result_int64(context, p->iSum); } @@ -1715,14 +1828,27 @@ static void avgFinalize(sqlite3_context *context){ SumCtx *p; p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ - sqlite3_result_double(context, p->rSum/(double)p->cnt); + double r; + if( p->approx ){ + r = p->rSum+p->rErr; + }else{ + r = (double)(p->iSum); + } + sqlite3_result_double(context, r/(double)p->cnt); } } static void totalFinalize(sqlite3_context *context){ SumCtx *p; + double r = 0.0; p = sqlite3_aggregate_context(context, 0); - /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - sqlite3_result_double(context, p ? p->rSum : (double)0); + if( p ){ + if( p->approx ){ + r = p->rSum+p->rErr; + }else{ + r = (double)(p->iSum); + } + } + sqlite3_result_double(context, r); } /* @@ -1749,13 +1875,13 @@ static void countStep(sqlite3_context *context, int argc, sqlite3_value **argv){ #ifndef SQLITE_OMIT_DEPRECATED /* The sqlite3_aggregate_count() function is deprecated. But just to make - ** sure it still operates correctly, verify that its count agrees with our + ** sure it still operates correctly, verify that its count agrees with our ** internal count when using count(*) and when the total count can be ** expressed as a 32-bit integer. */ assert( argc==1 || p==0 || p->n>0x7fffffff || p->bInverse || p->n==sqlite3_aggregate_count(context) ); #endif -} +} static void countFinalize(sqlite3_context *context){ CountCtx *p; p = sqlite3_aggregate_context(context, 0); @@ -1772,7 +1898,7 @@ static void countInverse(sqlite3_context *ctx, int argc, sqlite3_value **argv){ p->bInverse = 1; #endif } -} +} #else # define countInverse 0 #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -1781,8 +1907,8 @@ static void countInverse(sqlite3_context *ctx, int argc, sqlite3_value **argv){ ** Routines to implement min() and max() aggregate functions. */ static void minmaxStep( - sqlite3_context *context, - int NotUsed, + sqlite3_context *context, + int NotUsed, sqlite3_value **argv ){ Mem *pArg = (Mem *)argv[0]; @@ -1944,7 +2070,7 @@ static void groupConcatInverse( if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); /* pGCC is always non-NULL since groupConcatStep() will have always - ** run frist to initialize it */ + ** run first to initialize it */ if( ALWAYS(pGCC) ){ int nVS; /* Must call sqlite3_value_text() to convert the argument into text prior @@ -1999,7 +2125,7 @@ static void groupConcatValue(sqlite3_context *context){ sqlite3_result_error_toobig(context); }else if( pAccum->accError==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); - }else{ + }else{ const char *zText = sqlite3_str_value(pAccum); sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT); } @@ -2028,8 +2154,10 @@ void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ ** sensitive. */ void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ + FuncDef *pDef; struct compareInfo *pInfo; int flags; + int nArg; if( caseSensitive ){ pInfo = (struct compareInfo*)&likeInfoAlt; flags = SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE; @@ -2037,17 +2165,20 @@ void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ pInfo = (struct compareInfo*)&likeInfoNorm; flags = SQLITE_FUNC_LIKE; } - sqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3FindFunction(db, "like", 2, SQLITE_UTF8, 0)->funcFlags |= flags; - sqlite3FindFunction(db, "like", 3, SQLITE_UTF8, 0)->funcFlags |= flags; + for(nArg=2; nArg<=3; nArg++){ + sqlite3CreateFunc(db, "like", nArg, SQLITE_UTF8, pInfo, likeFunc, + 0, 0, 0, 0, 0); + pDef = sqlite3FindFunction(db, "like", nArg, SQLITE_UTF8, 0); + pDef->funcFlags |= flags; + pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; + } } /* ** pExpr points to an expression which implements a function. If ** it is appropriate to apply the LIKE optimization to that function ** then set aWc[0] through aWc[2] to the wildcard characters and the -** escape character and then return TRUE. If the function is not a +** escape character and then return TRUE. If the function is not a ** LIKE-style function then return FALSE. ** ** The expression "a LIKE b ESCAPE c" is only considered a valid LIKE @@ -2312,6 +2443,37 @@ static void signFunc( sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); } +#ifdef SQLITE_DEBUG +/* +** Implementation of fpdecode(x,y,z) function. +** +** x is a real number that is to be decoded. y is the precision. +** z is the maximum real precision. +*/ +static void fpdecodeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + FpDecode s; + double x; + int y, z; + char zBuf[100]; + UNUSED_PARAMETER(argc); + assert( argc==3 ); + x = sqlite3_value_double(argv[0]); + y = sqlite3_value_int(argv[1]); + z = sqlite3_value_int(argv[2]); + sqlite3FpDecode(&s, x, y, z); + if( s.isSpecial==2 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "NaN"); + }else{ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%c%.*s/%d", s.sign, s.n, s.z, s.iDP); + } + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); +} +#endif /* SQLITE_DEBUG */ + /* ** All of the FuncDef structures in the aBuiltinFunc[] array above ** to the global function hash table. This occurs at start-time (as @@ -2376,12 +2538,16 @@ void sqlite3RegisterBuiltinFunctions(void){ FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(subtype, 1, 0, 0, subtypeFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH), + FUNCTION2(octet_length, 1, 0, 0, bytelengthFunc,SQLITE_FUNC_BYTELEN), FUNCTION(instr, 2, 0, 0, instrFunc ), FUNCTION(printf, -1, 0, 0, printfFunc ), FUNCTION(format, -1, 0, 0, printfFunc ), FUNCTION(unicode, 1, 0, 0, unicodeFunc ), FUNCTION(char, -1, 0, 0, charFunc ), FUNCTION(abs, 1, 0, 0, absFunc ), +#ifdef SQLITE_DEBUG + FUNCTION(fpdecode, 3, 0, 0, fpdecodeFunc ), +#endif #ifndef SQLITE_OMIT_FLOATING_POINT FUNCTION(round, 1, 0, 0, roundFunc ), FUNCTION(round, 2, 0, 0, roundFunc ), @@ -2411,16 +2577,16 @@ void sqlite3RegisterBuiltinFunctions(void){ WAGGREGATE(sum, 1,0,0, sumStep, sumFinalize, sumFinalize, sumInverse, 0), WAGGREGATE(total, 1,0,0, sumStep,totalFinalize,totalFinalize,sumInverse, 0), WAGGREGATE(avg, 1,0,0, sumStep, avgFinalize, avgFinalize, sumInverse, 0), - WAGGREGATE(count, 0,0,0, countStep, + WAGGREGATE(count, 0,0,0, countStep, countFinalize, countFinalize, countInverse, SQLITE_FUNC_COUNT|SQLITE_FUNC_ANYORDER ), - WAGGREGATE(count, 1,0,0, countStep, + WAGGREGATE(count, 1,0,0, countStep, countFinalize, countFinalize, countInverse, SQLITE_FUNC_ANYORDER ), - WAGGREGATE(group_concat, 1, 0, 0, groupConcatStep, + WAGGREGATE(group_concat, 1, 0, 0, groupConcatStep, groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), - WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, + WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), - + LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), #ifdef SQLITE_CASE_SENSITIVE_LIKE LIKEFUNC(like, 2, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), diff --git a/src/global.c b/src/global.c index fcba7d7fa..60cd13e2a 100644 --- a/src/global.c +++ b/src/global.c @@ -243,6 +243,7 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ 0, /* bSmallMalloc */ 1, /* bExtraSchemaChecks */ + sizeof(LONGDOUBLE_TYPE)>8, /* bUseLongDouble */ 0x7ffffffe, /* mxStrlen */ 0, /* neverCorrupt */ SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ diff --git a/src/hash.c b/src/hash.c index 1f0062a8f..8ec043f11 100644 --- a/src/hash.c +++ b/src/hash.c @@ -97,7 +97,7 @@ static void insertElement( } -/* Resize the hash table so that it cantains "new_size" buckets. +/* Resize the hash table so that it contains "new_size" buckets. ** ** The hash table might fail to resize if sqlite3_malloc() fails or ** if the new size is the same as the prior size. diff --git a/src/hwtime.h b/src/hwtime.h index d27204a69..f808fa40e 100644 --- a/src/hwtime.h +++ b/src/hwtime.h @@ -17,7 +17,7 @@ #define SQLITE_HWTIME_H /* -** The following routine only works on pentium-class (or newer) processors. +** The following routine only works on Pentium-class (or newer) processors. ** It uses the RDTSC opcode to read the cycle count value out of the ** processor and returns that value. This can be used for high-res ** profiling. diff --git a/src/insert.c b/src/insert.c index 423af7723..1c31ca233 100644 --- a/src/insert.c +++ b/src/insert.c @@ -15,7 +15,7 @@ #include "sqliteInt.h" /* -** Generate code that will +** Generate code that will ** ** (1) acquire a lock for table pTab then ** (2) open pTab as cursor iCur. @@ -35,8 +35,10 @@ void sqlite3OpenTable( assert( pParse->pVdbe!=0 ); v = pParse->pVdbe; assert( opcode==OP_OpenWrite || opcode==OP_OpenRead ); - sqlite3TableLock(pParse, iDb, pTab->tnum, - (opcode==OP_OpenWrite)?1:0, pTab->zName); + if( !pParse->db->noSharedCache ){ + sqlite3TableLock(pParse, iDb, pTab->tnum, + (opcode==OP_OpenWrite)?1:0, pTab->zName); + } if( HasRowid(pTab) ){ sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nNVCol); VdbeComment((v, "%s", pTab->zName)); @@ -52,7 +54,7 @@ void sqlite3OpenTable( /* ** Return a pointer to the column affinity string associated with index -** pIdx. A column affinity string has one character for each column in +** pIdx. A column affinity string has one character for each column in ** the table, according to the affinity of the column: ** ** Character Column affinity @@ -131,7 +133,7 @@ char *sqlite3TableAffinityStr(sqlite3 *db, const Table *pTab){ zColAff[j--] = 0; }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB ); } - return zColAff; + return zColAff; } /* @@ -165,7 +167,7 @@ char *sqlite3TableAffinityStr(sqlite3 *db, const Table *pTab){ ** For STRICT tables: ** ------------------ ** -** Generate an appropropriate OP_TypeCheck opcode that will verify the +** Generate an appropriate OP_TypeCheck opcode that will verify the ** datatypes against the column definitions in pTab. If iReg==0, that ** means an OP_MakeRecord opcode has already been generated and should be ** the last opcode generated. The new OP_TypeCheck needs to be inserted @@ -220,9 +222,9 @@ void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ /* ** Return non-zero if the table pTab in database iDb or any of its indices -** have been opened at any point in the VDBE program. This is used to see if -** a statement of the form "INSERT INTO SELECT ..." can -** run without using a temporary table for the results of the SELECT. +** have been opened at any point in the VDBE program. This is used to see if +** a statement of the form "INSERT INTO SELECT ..." can +** run without using a temporary table for the results of the SELECT. */ static int readsTable(Parse *p, int iDb, Table *pTab){ Vdbe *v = sqlite3GetVdbe(p); @@ -450,7 +452,7 @@ static int autoIncBegin( /* ** This routine generates code that will initialize all of the -** register used by the autoincrement tracker. +** register used by the autoincrement tracker. */ void sqlite3AutoincrementBegin(Parse *pParse){ AutoincInfo *p; /* Information about an AUTOINCREMENT */ @@ -479,7 +481,7 @@ void sqlite3AutoincrementBegin(Parse *pParse){ /* 8 */ {OP_Goto, 0, 11, 0}, /* 9 */ {OP_Next, 0, 2, 0}, /* 10 */ {OP_Integer, 0, 0, 0}, - /* 11 */ {OP_Close, 0, 0, 0} + /* 11 */ {OP_Close, 0, 0, 0} }; VdbeOp *aOp; pDb = &db->aDb[p->iDb]; @@ -817,7 +819,7 @@ void sqlite3Insert( ** ** This is the 2nd template. */ - if( pColumn==0 + if( pColumn==0 && pSelect!=0 && pTrigger==0 && xferOptimization(pParse, pTab, pSelect, onError, iDb) @@ -845,7 +847,7 @@ void sqlite3Insert( regData = regRowid+1; /* If the INSERT statement included an IDLIST term, then make sure - ** all elements of the IDLIST really are columns of the table and + ** all elements of the IDLIST really are columns of the table and ** remember the column indices. ** ** If the table has an INTEGER PRIMARY KEY column and that column @@ -880,7 +882,7 @@ void sqlite3Insert( } #ifndef SQLITE_OMIT_GENERATED_COLUMNS if( pTab->aCol[j].colFlags & (COLFLAG_STORED|COLFLAG_VIRTUAL) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "cannot INSERT into generated column \"%s\"", pTab->aCol[j].zCnName); goto insert_cleanup; @@ -937,7 +939,7 @@ void sqlite3Insert( ** the destination table (template 3). ** ** A temp table must be used if the table being updated is also one - ** of the tables being read by the SELECT statement. Also use a + ** of the tables being read by the SELECT statement. Also use a ** temp table in the case of row triggers. */ if( pTrigger || readsTable(pParse, iDb, pTab) ){ @@ -973,7 +975,7 @@ void sqlite3Insert( sqlite3ReleaseTempReg(pParse, regTempRowid); } }else{ - /* This is the case if the data for the INSERT is coming from a + /* This is the case if the data for the INSERT is coming from a ** single-row VALUES clause */ NameContext sNC; @@ -992,7 +994,7 @@ void sqlite3Insert( } /* If there is no IDLIST term but the table has an integer primary - ** key, the set the ipkColumn variable to the integer primary key + ** key, the set the ipkColumn variable to the integer primary key ** column index in the original table definition. */ if( pColumn==0 && nColumn>0 ){ @@ -1023,7 +1025,7 @@ void sqlite3Insert( } } if( nColumn!=(pTab->nCol-nHidden) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "table %S has %d columns but %d values were supplied", pTabList->a, pTab->nCol-nHidden, nColumn); goto insert_cleanup; @@ -1033,7 +1035,7 @@ void sqlite3Insert( sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId); goto insert_cleanup; } - + /* Initialize the count of rows to be inserted */ if( (db->flags & SQLITE_CountRows)!=0 @@ -1168,7 +1170,7 @@ void sqlite3Insert( }else if( pColumn==0 ){ /* Hidden columns that are not explicitly named in the INSERT ** get there default value */ - sqlite3ExprCodeFactorable(pParse, + sqlite3ExprCodeFactorable(pParse, sqlite3ColumnExpr(pTab, &pTab->aCol[i]), iRegStore); continue; @@ -1180,7 +1182,7 @@ void sqlite3Insert( if( j>=pColumn->nId ){ /* A column not named in the insert column list gets its ** default value */ - sqlite3ExprCodeFactorable(pParse, + sqlite3ExprCodeFactorable(pParse, sqlite3ColumnExpr(pTab, &pTab->aCol[i]), iRegStore); continue; @@ -1188,7 +1190,7 @@ void sqlite3Insert( k = j; }else if( nColumn==0 ){ /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */ - sqlite3ExprCodeFactorable(pParse, + sqlite3ExprCodeFactorable(pParse, sqlite3ColumnExpr(pTab, &pTab->aCol[i]), iRegStore); continue; @@ -1197,7 +1199,7 @@ void sqlite3Insert( } if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, k, iRegStore); + sqlite3VdbeAddOp3(v, OP_Column, srcTab, k, iRegStore); }else if( pSelect ){ if( regFromSelect!=regData ){ sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+k, iRegStore); @@ -1268,7 +1270,7 @@ void sqlite3Insert( } /* Fire BEFORE or INSTEAD OF triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, pTab, regCols-pTab->nCol-1, onError, endOfLoop); sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1); @@ -1353,7 +1355,7 @@ void sqlite3Insert( ** constraints or (b) there are no triggers and this table is not a ** parent table in a foreign key constraint. It is safe to set the ** flag in the second case as if any REPLACE constraint is hit, an - ** OP_Delete or OP_IdxDelete instruction will be executed on each + ** OP_Delete or OP_IdxDelete instruction will be executed on each ** cursor that is disturbed. And these instructions both clear the ** VdbeCursor.seekResult variable, disabling the OPFLAG_USESEEKRESULT ** functionality. */ @@ -1379,7 +1381,7 @@ void sqlite3Insert( if( pTrigger ){ /* Code AFTER triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, pTab, regData-2-pTab->nCol, onError, endOfLoop); } @@ -1417,7 +1419,7 @@ void sqlite3Insert( } /* - ** Return the number of rows inserted. If this routine is + ** Return the number of rows inserted. If this routine is ** generating code because of a call to sqlite3NestedParse(), do not ** invoke the callback function. */ @@ -1448,7 +1450,7 @@ void sqlite3Insert( #endif /* -** Meanings of bits in of pWalker->eCode for +** Meanings of bits in of pWalker->eCode for ** sqlite3ExprReferencesUpdatedColumn() */ #define CKCNSTRNT_COLUMN 0x01 /* CHECK constraint uses a changing column */ @@ -1457,7 +1459,7 @@ void sqlite3Insert( /* This is the Walker callback from sqlite3ExprReferencesUpdatedColumn(). * Set bit 0x01 of pWalker->eCode if pWalker->eCode to 0 and if this ** expression node references any of the -** columns that are being modifed by an UPDATE statement. +** columns that are being modified by an UPDATE statement. */ static int checkConstraintExprNode(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_COLUMN ){ @@ -1529,7 +1531,7 @@ struct IndexIterator { union { struct { /* Use this object for eType==0: A Index.pNext list */ Index *pIdx; /* The current Index */ - } lx; + } lx; struct { /* Use this object for eType==1; Array of IndexListTerm */ int nIdx; /* Size of the array */ IndexListTerm *aIdx; /* Array of IndexListTerms */ @@ -1573,7 +1575,7 @@ static Index *indexIteratorNext(IndexIterator *pIter, int *pIx){ return pIter->u.lx.pIdx; } } - + /* ** Generate code to do constraint checks prior to an INSERT or an UPDATE ** on table pTab. @@ -1680,7 +1682,7 @@ void sqlite3GenerateConstraintChecks( int *aiChng, /* column i is unchanged if aiChng[i]<0 */ Upsert *pUpsert /* ON CONFLICT clauses, if any. NULL otherwise */ ){ - Vdbe *v; /* VDBE under constrution */ + Vdbe *v; /* VDBE under construction */ Index *pIdx; /* Pointer to one of the indices */ Index *pPk = 0; /* The PRIMARY KEY index for WITHOUT ROWID tables */ sqlite3 *db; /* Database connection */ @@ -1712,9 +1714,9 @@ void sqlite3GenerateConstraintChecks( assert( v!=0 ); assert( !IsView(pTab) ); /* This table is not a VIEW */ nCol = pTab->nCol; - + /* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for - ** normal rowid tables. nPkField is the number of key fields in the + ** normal rowid tables. nPkField is the number of key fields in the ** pPk index or 1 for a rowid table. In other words, nPkField is the ** number of fields in the true primary key of the table. */ if( HasRowid(pTab) ){ @@ -2091,10 +2093,10 @@ void sqlite3GenerateConstraintChecks( ** the triggers and remove both the table and index b-tree entries. ** ** Otherwise, if there are no triggers or the recursive-triggers - ** flag is not set, but the table has one or more indexes, call - ** GenerateRowIndexDelete(). This removes the index b-tree entries - ** only. The table b-tree entry will be replaced by the new entry - ** when it is inserted. + ** flag is not set, but the table has one or more indexes, call + ** GenerateRowIndexDelete(). This removes the index b-tree entries + ** only. The table b-tree entry will be replaced by the new entry + ** when it is inserted. ** ** If either GenerateRowDelete() or GenerateRowIndexDelete() is called, ** also invoke MultiWrite() to indicate that this VDBE may require @@ -2163,7 +2165,7 @@ void sqlite3GenerateConstraintChecks( pIdx; pIdx = indexIteratorNext(&sIdxIter, &ix) ){ - int regIdx; /* Range of registers hold conent for pIdx */ + int regIdx; /* Range of registers holding content for pIdx */ int regR; /* Range of registers holding conflicting PK */ int iThisCur; /* Cursor for this UNIQUE index */ int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ @@ -2226,7 +2228,7 @@ void sqlite3GenerateConstraintChecks( #endif sqlite3VdbeReleaseRegisters(pParse, regIdx, pIdx->nColumn, 0, 0); - /* In an UPDATE operation, if this index is the PRIMARY KEY index + /* In an UPDATE operation, if this index is the PRIMARY KEY index ** of a WITHOUT ROWID table and there has been no change the ** primary key, then no collision is possible. The collision detection ** logic below can all be skipped. */ @@ -2237,7 +2239,7 @@ void sqlite3GenerateConstraintChecks( /* Find out what action to take in case there is a uniqueness conflict */ onError = pIdx->onError; - if( onError==OE_None ){ + if( onError==OE_None ){ sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; /* pIdx is not a UNIQUE index */ } @@ -2283,7 +2285,7 @@ void sqlite3GenerateConstraintChecks( /* Check to see if the new index entry will be unique */ sqlite3VdbeVerifyAbortable(v, onError); - addrConflictCk = + addrConflictCk = sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, regIdx, pIdx->nKeyCol); VdbeCoverage(v); @@ -2313,7 +2315,7 @@ void sqlite3GenerateConstraintChecks( } } if( isUpdate ){ - /* If currently processing the PRIMARY KEY of a WITHOUT ROWID + /* If currently processing the PRIMARY KEY of a WITHOUT ROWID ** table, only conflict if the new PRIMARY KEY values are actually ** different from the old. See TH3 withoutrowid04.test. ** @@ -2323,7 +2325,7 @@ void sqlite3GenerateConstraintChecks( int addrJump = sqlite3VdbeCurrentAddr(v)+pPk->nKeyCol; int op = OP_Ne; int regCmp = (IsPrimaryKeyIndex(pIdx) ? regIdx : regR); - + for(i=0; inKeyCol; i++){ char *p4 = (char*)sqlite3LocateCollSeq(pParse, pPk->azColl[i]); x = pPk->aiColumn[i]; @@ -2333,7 +2335,7 @@ void sqlite3GenerateConstraintChecks( op = OP_Eq; } x = sqlite3TableColumnToStorage(pTab, x); - sqlite3VdbeAddOp4(v, op, + sqlite3VdbeAddOp4(v, op, regOldData+1+x, addrJump, regCmp+i, p4, P4_COLLSEQ ); sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); @@ -2443,7 +2445,7 @@ void sqlite3GenerateConstraintChecks( } sqlite3VdbeResolveLabel(v, addrUniqueOk); if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); - if( pUpsertClause + if( pUpsertClause && upsertIpkReturn && sqlite3UpsertNextIsIPK(pUpsertClause) ){ @@ -2658,6 +2660,8 @@ int sqlite3OpenTableAndIndices( assert( op==OP_OpenRead || op==OP_OpenWrite ); assert( op==OP_OpenWrite || p5==0 ); + assert( piDataCur!=0 ); + assert( piIdxCur!=0 ); if( IsVirtual(pTab) ){ /* This routine is a no-op for virtual tables. Leave the output ** variables *piDataCur and *piIdxCur set to illegal cursor numbers @@ -2670,18 +2674,18 @@ int sqlite3OpenTableAndIndices( assert( v!=0 ); if( iBase<0 ) iBase = pParse->nTab; iDataCur = iBase++; - if( piDataCur ) *piDataCur = iDataCur; + *piDataCur = iDataCur; if( HasRowid(pTab) && (aToOpen==0 || aToOpen[0]) ){ sqlite3OpenTable(pParse, iDataCur, iDb, pTab, op); - }else{ + }else if( pParse->db->noSharedCache==0 ){ sqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName); } - if( piIdxCur ) *piIdxCur = iBase; + *piIdxCur = iBase; for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - if( piDataCur ) *piDataCur = iIdxCur; + *piDataCur = iIdxCur; p5 = 0; } if( aToOpen==0 || aToOpen[i+1] ){ @@ -2760,7 +2764,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ ** ** INSERT INTO tab1 SELECT * FROM tab2; ** -** The xfer optimization transfers raw records from tab2 over to tab1. +** The xfer optimization transfers raw records from tab2 over to tab1. ** Columns are not decoded and reassembled, which greatly improves ** performance. Raw index records are transferred in the same way. ** @@ -2888,8 +2892,8 @@ static int xferOptimization( Column *pDestCol = &pDest->aCol[i]; Column *pSrcCol = &pSrc->aCol[i]; #ifdef SQLITE_ENABLE_HIDDEN_COLUMNS - if( (db->mDbFlags & DBFLAG_Vacuum)==0 - && (pDestCol->colFlags | pSrcCol->colFlags) & COLFLAG_HIDDEN + if( (db->mDbFlags & DBFLAG_Vacuum)==0 + && (pDestCol->colFlags | pSrcCol->colFlags) & COLFLAG_HIDDEN ){ return 0; /* Neither table may have __hidden__ columns */ } @@ -2906,7 +2910,7 @@ static int xferOptimization( ** ** Nevertheless, this is a useful notational shorthand to tell SQLite ** to do a bulk transfer all of the content from t1 over to t2. - ** + ** ** We could, in theory, disable this (except for internal use by the ** VACUUM command where it is actually needed). But why do that? It ** seems harmless enough, and provides a useful service. @@ -2932,7 +2936,7 @@ static int xferOptimization( if( pDestCol->affinity!=pSrcCol->affinity ){ return 0; /* Affinity must be the same on all columns */ } - if( sqlite3_stricmp(sqlite3ColumnColl(pDestCol), + if( sqlite3_stricmp(sqlite3ColumnColl(pDestCol), sqlite3ColumnColl(pSrcCol))!=0 ){ return 0; /* Collating sequence must be the same on all columns */ } @@ -2947,7 +2951,7 @@ static int xferOptimization( assert( pDestExpr==0 || !ExprHasProperty(pDestExpr, EP_IntValue) ); assert( pSrcExpr==0 || pSrcExpr->op==TK_SPAN ); assert( pSrcExpr==0 || !ExprHasProperty(pSrcExpr, EP_IntValue) ); - if( (pDestExpr==0)!=(pSrcExpr==0) + if( (pDestExpr==0)!=(pSrcExpr==0) || (pDestExpr!=0 && strcmp(pDestExpr->u.zToken, pSrcExpr->u.zToken)!=0) ){ @@ -2979,9 +2983,9 @@ static int xferOptimization( } #endif #ifndef SQLITE_OMIT_FOREIGN_KEY - /* Disallow the transfer optimization if the destination table constains + /* Disallow the transfer optimization if the destination table contains ** any foreign key constraints. This is more restrictive than necessary. - ** But the main beneficiary of the transfer optimization is the VACUUM + ** But the main beneficiary of the transfer optimization is the VACUUM ** command, and the VACUUM command disables foreign key constraints. So ** the extra complication to make this rule less restrictive is probably ** not worth the effort. Ticket [6284df89debdfa61db8073e062908af0c9b6118e] @@ -3030,7 +3034,7 @@ static int xferOptimization( ** (If the destination is not initially empty, the rowid fields ** of index entries might need to change.) ** - ** (2) The destination has a unique index. (The xfer optimization + ** (2) The destination has a unique index. (The xfer optimization ** is unable to test uniqueness.) ** ** (3) onError is something other than OE_Abort and OE_Rollback. @@ -3106,14 +3110,14 @@ static int xferOptimization( /* This INSERT command is part of a VACUUM operation, which guarantees ** that the destination table is empty. If all indexed columns use ** collation sequence BINARY, then it can also be assumed that the - ** index will be populated by inserting keys in strictly sorted + ** index will be populated by inserting keys in strictly sorted ** order. In this case, instead of seeking within the b-tree as part ** of every OP_IdxInsert opcode, an OP_SeekEnd is added before the - ** OP_IdxInsert to seek to the point within the b-tree where each key + ** OP_IdxInsert to seek to the point within the b-tree where each key ** should be inserted. This is faster. ** ** If any of the indexed columns use a collation sequence other than - ** BINARY, this optimization is disabled. This is because the user + ** BINARY, this optimization is disabled. This is because the user ** might change the definition of a collation sequence and then run ** a VACUUM command. In that case keys may not be written in strictly ** sorted order. */ @@ -3131,9 +3135,9 @@ static int xferOptimization( } if( idxInsFlags!=(OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT) ){ sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); - if( (db->mDbFlags & DBFLAG_Vacuum)==0 - && !HasRowid(pDest) - && IsPrimaryKeyIndex(pDestIdx) + if( (db->mDbFlags & DBFLAG_Vacuum)==0 + && !HasRowid(pDest) + && IsPrimaryKeyIndex(pDestIdx) ){ codeWithoutRowidPreupdate(pParse, pDest, iDest, regData); } diff --git a/src/json.c b/src/json.c index 8735634c8..f8d4aa2a7 100644 --- a/src/json.c +++ b/src/json.c @@ -30,25 +30,51 @@ ** increase for the parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). */ static const char jsonIsSpace[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; #define fast_isspace(x) (jsonIsSpace[(unsigned char)x]) +/* +** Characters that are special to JSON. Control charaters, +** '"' and '\\'. +*/ +static const char jsonIsOk[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; + + #if !defined(SQLITE_DEBUG) && !defined(SQLITE_COVERAGE_TEST) # define VVA(X) #else @@ -59,6 +85,7 @@ static const char jsonIsSpace[] = { typedef struct JsonString JsonString; typedef struct JsonNode JsonNode; typedef struct JsonParse JsonParse; +typedef struct JsonCleanup JsonCleanup; /* An instance of this object represents a JSON string ** under construction. Really, this is a generic string accumulator @@ -74,16 +101,26 @@ struct JsonString { char zSpace[100]; /* Initial static space */ }; +/* A deferred cleanup task. A list of JsonCleanup objects might be +** run when the JsonParse object is destroyed. +*/ +struct JsonCleanup { + JsonCleanup *pJCNext; /* Next in a list */ + void (*xOp)(void*); /* Routine to run */ + void *pArg; /* Argument to xOp() */ +}; + /* JSON type values */ -#define JSON_NULL 0 -#define JSON_TRUE 1 -#define JSON_FALSE 2 -#define JSON_INT 3 -#define JSON_REAL 4 -#define JSON_STRING 5 -#define JSON_ARRAY 6 -#define JSON_OBJECT 7 +#define JSON_SUBST 0 /* Special edit node. Uses u.iPrev */ +#define JSON_NULL 1 +#define JSON_TRUE 2 +#define JSON_FALSE 3 +#define JSON_INT 4 +#define JSON_REAL 5 +#define JSON_STRING 6 +#define JSON_ARRAY 7 +#define JSON_OBJECT 8 /* The "subtype" set for JSON values */ #define JSON_SUBTYPE 74 /* Ascii for "J" */ @@ -92,52 +129,87 @@ struct JsonString { ** Names of the various JSON types: */ static const char * const jsonType[] = { + "subst", "null", "true", "false", "integer", "real", "text", "array", "object" }; /* Bit values for the JsonNode.jnFlag field */ -#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ -#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ -#define JNODE_REMOVE 0x04 /* Do not output */ -#define JNODE_REPLACE 0x08 /* Replace with JsonNode.u.iReplace */ -#define JNODE_PATCH 0x10 /* Patch with JsonNode.u.pPatch */ -#define JNODE_APPEND 0x20 /* More ARRAY/OBJECT entries at u.iAppend */ -#define JNODE_LABEL 0x40 /* Is a label of an object */ -#define JNODE_JSON5 0x80 /* Node contains JSON5 enhancements */ +#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ +#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ +#define JNODE_REMOVE 0x04 /* Do not output */ +#define JNODE_REPLACE 0x08 /* Target of a JSON_SUBST node */ +#define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */ +#define JNODE_LABEL 0x20 /* Is a label of an object */ +#define JNODE_JSON5 0x40 /* Node contains JSON5 enhancements */ -/* A single node of parsed JSON +/* A single node of parsed JSON. An array of these nodes describes +** a parse of JSON + edits. +** +** Use the json_parse() SQL function (available when compiled with +** -DSQLITE_DEBUG) to see a dump of complete JsonParse objects, including +** a complete listing and decoding of the array of JsonNodes. */ struct JsonNode { u8 eType; /* One of the JSON_ type values */ u8 jnFlags; /* JNODE flags */ u8 eU; /* Which union element to use */ - u32 n; /* Bytes of content, or number of sub-nodes */ + u32 n; /* Bytes of content for INT, REAL or STRING + ** Number of sub-nodes for ARRAY and OBJECT + ** Node that SUBST applies to */ union { const char *zJContent; /* 1: Content for INT, REAL, and STRING */ u32 iAppend; /* 2: More terms for ARRAY and OBJECT */ u32 iKey; /* 3: Key for ARRAY objects in json_tree() */ - u32 iReplace; /* 4: Replacement content for JNODE_REPLACE */ - JsonNode *pPatch; /* 5: Node chain of patch for JNODE_PATCH */ + u32 iPrev; /* 4: Previous SUBST node, or 0 */ } u; }; -/* A completely parsed JSON string + +/* A parsed and possibly edited JSON string. Lifecycle: +** +** 1. JSON comes in and is parsed into an array aNode[]. The original +** JSON text is stored in zJson. +** +** 2. Zero or more changes are made (via json_remove() or json_replace() +** or similar) to the aNode[] array. +** +** 3. A new, edited and mimified JSON string is generated from aNode +** and stored in zAlt. The JsonParse object always owns zAlt. +** +** Step 1 always happens. Step 2 and 3 may or may not happen, depending +** on the operation. +** +** aNode[].u.zJContent entries typically point into zJson. Hence zJson +** must remain valid for the lifespan of the parse. For edits, +** aNode[].u.zJContent might point to malloced space other than zJson. +** Entries in pClup are responsible for freeing that extra malloced space. +** +** When walking the parse tree in aNode[], edits are ignored if useMod is +** false. */ struct JsonParse { u32 nNode; /* Number of slots of aNode[] used */ u32 nAlloc; /* Number of slots of aNode[] allocated */ JsonNode *aNode; /* Array of nodes containing the parse */ - const char *zJson; /* Original JSON string */ + char *zJson; /* Original JSON string (before edits) */ + char *zAlt; /* Revised and/or mimified JSON */ u32 *aUp; /* Index of parent of each node */ + JsonCleanup *pClup;/* Cleanup operations prior to freeing this object */ u16 iDepth; /* Nesting depth */ u8 nErr; /* Number of errors seen */ u8 oom; /* Set to true if out of memory */ + u8 bJsonIsRCStr; /* True if zJson is an RCStr */ u8 hasNonstd; /* True if input uses non-standard features like JSON5 */ + u8 useMod; /* Actually use the edits contain inside aNode */ + u8 hasMod; /* aNode contains edits from the original zJson */ + u32 nJPRef; /* Number of references to this object */ int nJson; /* Length of the zJson string in bytes */ + int nAlt; /* Length of alternative JSON string zAlt, in bytes */ u32 iErr; /* Error location in zJson[] */ - u32 iHold; /* Replace cache line with the lowest iHold value */ + u32 iSubst; /* Last JSON_SUBST entry in aNode[] */ + u32 iHold; /* Age of this entry in the cache for LRU replacement */ }; /* @@ -170,17 +242,15 @@ static void jsonInit(JsonString *p, sqlite3_context *pCtx){ jsonZero(p); } - /* Free all allocated memory and reset the JsonString object back to its ** initial state. */ static void jsonReset(JsonString *p){ - if( !p->bStatic ) sqlite3_free(p->zBuf); + if( !p->bStatic ) sqlite3RCStrUnref(p->zBuf); jsonZero(p); } - -/* Report an out-of-memory (OOM) condition +/* Report an out-of-memory (OOM) condition */ static void jsonOom(JsonString *p){ p->bErr = 1; @@ -196,7 +266,7 @@ static int jsonGrow(JsonString *p, u32 N){ char *zNew; if( p->bStatic ){ if( p->bErr ) return 1; - zNew = sqlite3_malloc64(nTotal); + zNew = sqlite3RCStrNew(nTotal); if( zNew==0 ){ jsonOom(p); return SQLITE_NOMEM; @@ -205,12 +275,12 @@ static int jsonGrow(JsonString *p, u32 N){ p->zBuf = zNew; p->bStatic = 0; }else{ - zNew = sqlite3_realloc64(p->zBuf, nTotal); - if( zNew==0 ){ - jsonOom(p); + p->zBuf = sqlite3RCStrResize(p->zBuf, nTotal); + if( p->zBuf==0 ){ + p->bErr = 1; + jsonZero(p); return SQLITE_NOMEM; } - p->zBuf = zNew; } p->nAlloc = nTotal; return SQLITE_OK; @@ -218,12 +288,35 @@ static int jsonGrow(JsonString *p, u32 N){ /* Append N bytes from zIn onto the end of the JsonString string. */ -static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ - if( N==0 ) return; - if( (N+p->nUsed >= p->nAlloc) && jsonGrow(p,N)!=0 ) return; +static SQLITE_NOINLINE void jsonAppendExpand( + JsonString *p, + const char *zIn, + u32 N +){ + assert( N>0 ); + if( jsonGrow(p,N) ) return; memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; } +static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ + if( N==0 ) return; + if( N+p->nUsed >= p->nAlloc ){ + jsonAppendExpand(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} +static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ + assert( N>0 ); + if( N+p->nUsed >= p->nAlloc ){ + jsonAppendExpand(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} + /* Append formatted text (not to exceed N bytes) to the JsonString. */ @@ -238,10 +331,35 @@ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ /* Append a single character */ -static void jsonAppendChar(JsonString *p, char c){ - if( p->nUsed>=p->nAlloc && jsonGrow(p,1)!=0 ) return; +static SQLITE_NOINLINE void jsonAppendCharExpand(JsonString *p, char c){ + if( jsonGrow(p,1) ) return; p->zBuf[p->nUsed++] = c; } +static void jsonAppendChar(JsonString *p, char c){ + if( p->nUsed>=p->nAlloc ){ + jsonAppendCharExpand(p,c); + }else{ + p->zBuf[p->nUsed++] = c; + } +} + +/* Try to force the string to be a zero-terminated RCStr string. +** +** Return true on success. Return false if an OOM prevents this +** from happening. +*/ +static int jsonForceRCStr(JsonString *p){ + jsonAppendChar(p, 0); + if( p->bErr ) return 0; + p->nUsed--; + if( p->bStatic==0 ) return 1; + p->nAlloc = 0; + p->nUsed++; + jsonGrow(p, p->nUsed); + p->nUsed--; + return p->bStatic==0; +} + /* Append a comma separator to the output buffer, if the previous ** character is not '[' or '{'. @@ -250,7 +368,8 @@ static void jsonAppendSeparator(JsonString *p){ char c; if( p->nUsed==0 ) return; c = p->zBuf[p->nUsed-1]; - if( c!='[' && c!='{' ) jsonAppendChar(p, ','); + if( c=='[' || c=='{' ) return; + jsonAppendChar(p, ','); } /* Append the N-byte string in zIn to the end of the JsonString string @@ -264,11 +383,16 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ p->zBuf[p->nUsed++] = '"'; for(i=0; izBuf[p->nUsed++] = c; + }else if( c=='"' || c=='\\' ){ json_simple_escape: if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return; p->zBuf[p->nUsed++] = '\\'; - }else if( c<=0x1f ){ + p->zBuf[p->nUsed++] = c; + }else if( c=='\'' ){ + p->zBuf[p->nUsed++] = c; + }else{ static const char aSpecial[] = { 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -279,6 +403,7 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ assert( aSpecial['\n']=='n' ); assert( aSpecial['\r']=='r' ); assert( aSpecial['\t']=='t' ); + assert( c>=0 && czBuf[p->nUsed++] = 'u'; p->zBuf[p->nUsed++] = '0'; p->zBuf[p->nUsed++] = '0'; - p->zBuf[p->nUsed++] = '0' + (c>>4); - c = "0123456789abcdef"[c&0xf]; + p->zBuf[p->nUsed++] = "0123456789abcdef"[c>>4]; + p->zBuf[p->nUsed++] = "0123456789abcdef"[c&0xf]; } - p->zBuf[p->nUsed++] = c; } p->zBuf[p->nUsed++] = '"'; assert( p->nUsednAlloc ); @@ -310,10 +434,10 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ while( N>0 ){ for(i=0; i0 ){ - jsonAppendRaw(p, zIn, i); + jsonAppendRawNZ(p, zIn, i); zIn += i; N -= i; - if( N==0 ) break; + if( N==0 ) break; } assert( zIn[0]=='\\' ); switch( (u8)zIn[1] ){ @@ -321,16 +445,16 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ jsonAppendChar(p, '\''); break; case 'v': - jsonAppendRaw(p, "\\u0009", 6); + jsonAppendRawNZ(p, "\\u0009", 6); break; case 'x': - jsonAppendRaw(p, "\\u00", 4); - jsonAppendRaw(p, &zIn[2], 2); + jsonAppendRawNZ(p, "\\u00", 4); + jsonAppendRawNZ(p, &zIn[2], 2); zIn += 2; N -= 2; break; case '0': - jsonAppendRaw(p, "\\u0000", 6); + jsonAppendRawNZ(p, "\\u0000", 6); break; case '\r': if( zIn[2]=='\n' ){ @@ -348,7 +472,7 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ N -= 2; break; default: - jsonAppendRaw(p, zIn, 2); + jsonAppendRawNZ(p, zIn, 2); break; } zIn += 2; @@ -378,11 +502,12 @@ static void jsonAppendNormalizedInt(JsonString *p, const char *zIn, u32 N){ jsonPrintf(100,p,"%lld",i); }else{ assert( rc==2 ); - jsonAppendRaw(p, "9.0e999", 7); + jsonAppendRawNZ(p, "9.0e999", 7); } return; } - jsonAppendRaw(p, zIn, N); + assert( N>0 ); + jsonAppendRawNZ(p, zIn, N); } /* @@ -414,14 +539,14 @@ static void jsonAppendNormalizedReal(JsonString *p, const char *zIn, u32 N){ } } if( N>0 ){ - jsonAppendRaw(p, zIn, N); + jsonAppendRawNZ(p, zIn, N); } } /* -** Append a function parameter value to the JSON string under +** Append a function parameter value to the JSON string under ** construction. */ static void jsonAppendValue( @@ -430,7 +555,7 @@ static void jsonAppendValue( ){ switch( sqlite3_value_type(pValue) ){ case SQLITE_NULL: { - jsonAppendRaw(p, "null", 4); + jsonAppendRawNZ(p, "null", 4); break; } case SQLITE_FLOAT: { @@ -466,15 +591,25 @@ static void jsonAppendValue( /* Make the JSON in p the result of the SQL function. +** +** The JSON string is reset. */ static void jsonResult(JsonString *p){ if( p->bErr==0 ){ - sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, - p->bStatic ? SQLITE_TRANSIENT : sqlite3_free, - SQLITE_UTF8); - jsonZero(p); + if( p->bStatic ){ + sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + SQLITE_TRANSIENT, SQLITE_UTF8); + }else if( jsonForceRCStr(p) ){ + sqlite3RCStrRef(p->zBuf); + sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + (void(*)(void*))sqlite3RCStrUnref, + SQLITE_UTF8); + } + } + if( p->bErr==1 ){ + sqlite3_result_error_nomem(p->pCtx); } - assert( p->bStatic ); + jsonReset(p); } /************************************************************************** @@ -499,20 +634,73 @@ static u32 jsonNodeSize(JsonNode *pNode){ ** delete the JsonParse object itself. */ static void jsonParseReset(JsonParse *pParse){ - sqlite3_free(pParse->aNode); - pParse->aNode = 0; + while( pParse->pClup ){ + JsonCleanup *pTask = pParse->pClup; + pParse->pClup = pTask->pJCNext; + pTask->xOp(pTask->pArg); + sqlite3_free(pTask); + } + assert( pParse->nJPRef<=1 ); + if( pParse->aNode ){ + sqlite3_free(pParse->aNode); + pParse->aNode = 0; + } pParse->nNode = 0; pParse->nAlloc = 0; - sqlite3_free(pParse->aUp); - pParse->aUp = 0; + if( pParse->aUp ){ + sqlite3_free(pParse->aUp); + pParse->aUp = 0; + } + if( pParse->bJsonIsRCStr ){ + sqlite3RCStrUnref(pParse->zJson); + pParse->zJson = 0; + pParse->bJsonIsRCStr = 0; + } + if( pParse->zAlt ){ + sqlite3RCStrUnref(pParse->zAlt); + pParse->zAlt = 0; + } } /* ** Free a JsonParse object that was obtained from sqlite3_malloc(). +** +** Note that destroying JsonParse might call sqlite3RCStrUnref() to +** destroy the zJson value. The RCStr object might recursively invoke +** JsonParse to destroy this pParse object again. Take care to ensure +** that this recursive destructor sequence terminates harmlessly. */ static void jsonParseFree(JsonParse *pParse){ - jsonParseReset(pParse); - sqlite3_free(pParse); + if( pParse->nJPRef>1 ){ + pParse->nJPRef--; + }else{ + jsonParseReset(pParse); + sqlite3_free(pParse); + } +} + +/* +** Add a cleanup task to the JsonParse object. +** +** If an OOM occurs, the cleanup operation happens immediately +** and this function returns SQLITE_NOMEM. +*/ +static int jsonParseAddCleanup( + JsonParse *pParse, /* Add the cleanup task to this parser */ + void(*xOp)(void*), /* The cleanup task */ + void *pArg /* Argument to the cleanup */ +){ + JsonCleanup *pTask = sqlite3_malloc64( sizeof(*pTask) ); + if( pTask==0 ){ + pParse->oom = 1; + xOp(pArg); + return SQLITE_ERROR; + } + pTask->pJCNext = pParse->pClup; + pParse->pClup = pTask; + pTask->xOp = xOp; + pTask->pArg = pArg; + return SQLITE_OK; } /* @@ -521,32 +709,38 @@ static void jsonParseFree(JsonParse *pParse){ ** the number of JsonNode objects that are encoded. */ static void jsonRenderNode( + JsonParse *pParse, /* the complete parse of the JSON */ JsonNode *pNode, /* The node to render */ - JsonString *pOut, /* Write JSON here */ - sqlite3_value **aReplace /* Replacement values */ + JsonString *pOut /* Write JSON here */ ){ assert( pNode!=0 ); - if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){ - if( (pNode->jnFlags & JNODE_REPLACE)!=0 && ALWAYS(aReplace!=0) ){ - assert( pNode->eU==4 ); - jsonAppendValue(pOut, aReplace[pNode->u.iReplace]); - return; + while( (pNode->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ + u32 idx = (u32)(pNode - pParse->aNode); + u32 i = pParse->iSubst; + while( 1 /*exit-by-break*/ ){ + assert( inNode ); + assert( pParse->aNode[i].eType==JSON_SUBST ); + assert( pParse->aNode[i].eU==4 ); + assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ + pNode = &pParse->aNode[i+1]; + break; + } + i = pParse->aNode[i].u.iPrev; } - assert( pNode->eU==5 ); - pNode = pNode->u.pPatch; } switch( pNode->eType ){ default: { assert( pNode->eType==JSON_NULL ); - jsonAppendRaw(pOut, "null", 4); + jsonAppendRawNZ(pOut, "null", 4); break; } case JSON_TRUE: { - jsonAppendRaw(pOut, "true", 4); + jsonAppendRawNZ(pOut, "true", 4); break; } case JSON_FALSE: { - jsonAppendRaw(pOut, "false", 5); + jsonAppendRawNZ(pOut, "false", 5); break; } case JSON_STRING: { @@ -562,7 +756,8 @@ static void jsonRenderNode( }else if( pNode->jnFlags & JNODE_JSON5 ){ jsonAppendNormalizedString(pOut, pNode->u.zJContent, pNode->n); }else{ - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); } break; } @@ -571,7 +766,8 @@ static void jsonRenderNode( if( pNode->jnFlags & JNODE_JSON5 ){ jsonAppendNormalizedReal(pOut, pNode->u.zJContent, pNode->n); }else{ - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); } break; } @@ -580,7 +776,8 @@ static void jsonRenderNode( if( pNode->jnFlags & JNODE_JSON5 ){ jsonAppendNormalizedInt(pOut, pNode->u.zJContent, pNode->n); }else{ - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); } break; } @@ -589,15 +786,16 @@ static void jsonRenderNode( jsonAppendChar(pOut, '['); for(;;){ while( j<=pNode->n ){ - if( (pNode[j].jnFlags & JNODE_REMOVE)==0 ){ + if( (pNode[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ jsonAppendSeparator(pOut); - jsonRenderNode(&pNode[j], pOut, aReplace); + jsonRenderNode(pParse, &pNode[j], pOut); } j += jsonNodeSize(&pNode[j]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pNode->eU==2 ); - pNode = &pNode[pNode->u.iAppend]; + pNode = &pParse->aNode[pNode->u.iAppend]; j = 1; } jsonAppendChar(pOut, ']'); @@ -608,17 +806,18 @@ static void jsonRenderNode( jsonAppendChar(pOut, '{'); for(;;){ while( j<=pNode->n ){ - if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 ){ + if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ jsonAppendSeparator(pOut); - jsonRenderNode(&pNode[j], pOut, aReplace); + jsonRenderNode(pParse, &pNode[j], pOut); jsonAppendChar(pOut, ':'); - jsonRenderNode(&pNode[j+1], pOut, aReplace); + jsonRenderNode(pParse, &pNode[j+1], pOut); } j += 1 + jsonNodeSize(&pNode[j+1]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pNode->eU==2 ); - pNode = &pNode[pNode->u.iAppend]; + pNode = &pParse->aNode[pNode->u.iAppend]; j = 1; } jsonAppendChar(pOut, '}'); @@ -628,18 +827,29 @@ static void jsonRenderNode( } /* -** Return a JsonNode and all its descendents as a JSON string. +** Return a JsonNode and all its descendants as a JSON string. */ static void jsonReturnJson( + JsonParse *pParse, /* The complete JSON */ JsonNode *pNode, /* Node to return */ sqlite3_context *pCtx, /* Return value for this function */ - sqlite3_value **aReplace /* Array of replacement values */ + int bGenerateAlt /* Also store the rendered text in zAlt */ ){ JsonString s; - jsonInit(&s, pCtx); - jsonRenderNode(pNode, &s, aReplace); - jsonResult(&s); - sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + if( pParse->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( pParse->nErr==0 ){ + jsonInit(&s, pCtx); + jsonRenderNode(pParse, pNode, &s); + if( bGenerateAlt && pParse->zAlt==0 && jsonForceRCStr(&s) ){ + pParse->zAlt = sqlite3RCStrRef(s.zBuf); + pParse->nAlt = s.nUsed; + } + jsonResult(&s); + sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + } } /* @@ -677,9 +887,9 @@ static u32 jsonHexToInt4(const char *z){ ** Make the JsonNode the return value of the function. */ static void jsonReturn( + JsonParse *pParse, /* Complete JSON parse tree */ JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx, /* Return value for this function */ - sqlite3_value **aReplace /* Array of replacement values */ + sqlite3_context *pCtx /* Return value for this function */ ){ switch( pNode->eType ){ default: { @@ -700,8 +910,7 @@ static void jsonReturn( int rc; int bNeg = 0; const char *z; - - + assert( pNode->eU==1 ); z = pNode->u.zJContent; if( z[0]=='-' ){ z++; bNeg = 1; } @@ -826,7 +1035,7 @@ static void jsonReturn( } case JSON_ARRAY: case JSON_OBJECT: { - jsonReturnJson(pNode, pCtx, aReplace); + jsonReturnJson(pParse, pNode, pCtx, 0); break; } } @@ -848,6 +1057,12 @@ static int jsonParseAddNode(JsonParse*,u32,u32,const char*); #endif +/* +** Add a single node to pParse->aNode after first expanding the +** size of the aNode array. Return the index of the new node. +** +** If an OOM error occurs, set pParse->oom and return -1. +*/ static JSON_NOINLINE int jsonParseAddNodeExpand( JsonParse *pParse, /* Append the node to this object */ u32 eType, /* Node type */ @@ -864,7 +1079,7 @@ static JSON_NOINLINE int jsonParseAddNodeExpand( pParse->oom = 1; return -1; } - pParse->nAlloc = nNew; + pParse->nAlloc = sqlite3_msize(pNew)/sizeof(JsonNode); pParse->aNode = pNew; assert( pParse->nNodenAlloc ); return jsonParseAddNode(pParse, eType, n, zContent); @@ -882,10 +1097,13 @@ static int jsonParseAddNode( const char *zContent /* Content */ ){ JsonNode *p; - if( pParse->aNode==0 || pParse->nNode>=pParse->nAlloc ){ + assert( pParse->aNode!=0 || pParse->nNode>=pParse->nAlloc ); + if( pParse->nNode>=pParse->nAlloc ){ return jsonParseAddNodeExpand(pParse, eType, n, zContent); } + assert( pParse->aNode!=0 ); p = &pParse->aNode[pParse->nNode]; + assert( p!=0 ); p->eType = (u8)(eType & 0xff); p->jnFlags = (u8)(eType >> 8); VVA( p->eU = zContent ? 1 : 0 ); @@ -894,6 +1112,52 @@ static int jsonParseAddNode( return pParse->nNode++; } +/* +** Add an array of new nodes to the current pParse->aNode array. +** Return the index of the first node added. +** +** If an OOM error occurs, set pParse->oom. +*/ +static void jsonParseAddNodeArray( + JsonParse *pParse, /* Append the node to this object */ + JsonNode *aNode, /* Array of nodes to add */ + u32 nNode /* Number of elements in aNew */ +){ + assert( aNode!=0 ); + assert( nNode>=1 ); + if( pParse->nNode + nNode > pParse->nAlloc ){ + u32 nNew = pParse->nNode + nNode; + JsonNode *aNew = sqlite3_realloc64(pParse->aNode, nNew*sizeof(JsonNode)); + if( aNew==0 ){ + pParse->oom = 1; + return; + } + pParse->nAlloc = sqlite3_msize(aNew)/sizeof(JsonNode); + pParse->aNode = aNew; + } + memcpy(&pParse->aNode[pParse->nNode], aNode, nNode*sizeof(JsonNode)); + pParse->nNode += nNode; +} + +/* +** Add a new JSON_SUBST node. The node immediately following +** this new node will be the substitute content for iNode. +*/ +static int jsonParseAddSubstNode( + JsonParse *pParse, /* Add the JSON_SUBST here */ + u32 iNode /* References this node */ +){ + int idx = jsonParseAddNode(pParse, JSON_SUBST, iNode, 0); + if( pParse->oom ) return -1; + pParse->aNode[iNode].jnFlags |= JNODE_REPLACE; + pParse->aNode[idx].eU = 4; + pParse->aNode[idx].u.iPrev = pParse->iSubst; + pParse->iSubst = idx; + pParse->hasMod = 1; + pParse->useMod = 1; + return idx; +} + /* ** Return true if z[] begins with 2 (or more) hexadecimal digits */ @@ -1060,7 +1324,7 @@ static const struct NanInfName { ** ** Special return values: ** -** 0 End if input +** 0 End of input ** -1 Syntax error ** -2 '}' seen ** -3 ']' seen @@ -1235,15 +1499,12 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ jnFlags = 0; parse_string: cDelim = z[i]; - j = i+1; - for(;;){ + for(j=i+1; 1; j++){ + if( jsonIsOk[(unsigned char)z[j]] ) continue; c = z[j]; - if( (c & ~0x1f)==0 ){ - /* Control characters are not allowed in strings */ - pParse->iErr = j; - return -1; - } - if( c=='\\' ){ + if( c==cDelim ){ + break; + }else if( c=='\\' ){ c = z[++j]; if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' || c=='n' || c=='r' || c=='t' @@ -1263,10 +1524,11 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ pParse->iErr = j; return -1; } - }else if( c==cDelim ){ - break; + }else if( c<=0x1f ){ + /* Control characters are not allowed in strings */ + pParse->iErr = j; + return -1; } - j++; } jsonParseAddNode(pParse, JSON_STRING | (jnFlags<<8), j+1-i, &z[i]); return j+1; @@ -1502,20 +1764,18 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ /* ** Parse a complete JSON string. Return 0 on success or non-zero if there -** are any errors. If an error occurs, free all memory associated with -** pParse. +** are any errors. If an error occurs, free all memory held by pParse, +** but not pParse itself. ** -** pParse is uninitialized when this routine is called. +** pParse must be initialized to an empty parse object prior to calling +** this routine. */ static int jsonParse( JsonParse *pParse, /* Initialize and fill this JsonParse object */ - sqlite3_context *pCtx, /* Report errors here */ - const char *zJson /* Input JSON text to be parsed */ + sqlite3_context *pCtx /* Report errors here */ ){ int i; - memset(pParse, 0, sizeof(*pParse)); - if( zJson==0 ) return 1; - pParse->zJson = zJson; + const char *zJson = pParse->zJson; i = jsonParseValue(pParse, 0); if( pParse->oom ) i = -1; if( i>0 ){ @@ -1544,6 +1804,7 @@ static int jsonParse( return 0; } + /* Mark node i of pParse as being a child of iParent. Call recursively ** to fill in all the descendants of node i. */ @@ -1593,35 +1854,49 @@ static int jsonParseFindParents(JsonParse *pParse){ #define JSON_CACHE_SZ 4 /* Max number of cache entries */ /* -** Obtain a complete parse of the JSON found in the first argument -** of the argv array. Use the sqlite3_get_auxdata() cache for this -** parse if it is available. If the cache is not available or if it -** is no longer valid, parse the JSON again and return the new parse, -** and also register the new parse so that it will be available for +** Obtain a complete parse of the JSON found in the pJson argument +** +** Use the sqlite3_get_auxdata() cache to find a preexisting parse +** if it is available. If the cache is not available or if it +** is no longer valid, parse the JSON again and return the new parse. +** Also register the new parse so that it will be available for ** future sqlite3_get_auxdata() calls. ** ** If an error occurs and pErrCtx!=0 then report the error on pErrCtx ** and return NULL. ** -** If an error occurs and pErrCtx==0 then return the Parse object with -** JsonParse.nErr non-zero. If the caller invokes this routine with -** pErrCtx==0 and it gets back a JsonParse with nErr!=0, then the caller -** is responsible for invoking jsonParseFree() on the returned value. -** But the caller may invoke jsonParseFree() *only* if pParse->nErr!=0. +** The returned pointer (if it is not NULL) is owned by the cache in +** most cases, not the caller. The caller does NOT need to invoke +** jsonParseFree(), in most cases. +** +** Except, if an error occurs and pErrCtx==0 then return the JsonParse +** object with JsonParse.nErr non-zero and the caller will own the JsonParse +** object. In that case, it will be the responsibility of the caller to +** invoke jsonParseFree(). To summarize: +** +** pErrCtx!=0 || p->nErr==0 ==> Return value p is owned by the +** cache. Call does not need to +** free it. +** +** pErrCtx==0 && p->nErr!=0 ==> Return value is owned by the caller +** and so the caller must free it. */ static JsonParse *jsonParseCached( - sqlite3_context *pCtx, - sqlite3_value **argv, - sqlite3_context *pErrCtx + sqlite3_context *pCtx, /* Context to use for cache search */ + sqlite3_value *pJson, /* Function param containing JSON text */ + sqlite3_context *pErrCtx, /* Write parse errors here if not NULL */ + int bUnedited /* No prior edits allowed */ ){ - const char *zJson = (const char*)sqlite3_value_text(argv[0]); - int nJson = sqlite3_value_bytes(argv[0]); + char *zJson = (char*)sqlite3_value_text(pJson); + int nJson = sqlite3_value_bytes(pJson); JsonParse *p; JsonParse *pMatch = 0; int iKey; int iMinKey = 0; u32 iMinHold = 0xffffffff; u32 iMaxHold = 0; + int bJsonRCStr; + if( zJson==0 ) return 0; for(iKey=0; iKeynJson==nJson - && memcmp(p->zJson,zJson,nJson)==0 + && (p->hasMod==0 || bUnedited==0) + && (p->zJson==zJson || memcmp(p->zJson,zJson,nJson)==0) + ){ + p->nErr = 0; + p->useMod = 0; + pMatch = p; + }else + if( pMatch==0 + && p->zAlt!=0 + && bUnedited==0 + && p->nAlt==nJson + && memcmp(p->zAlt, zJson, nJson)==0 ){ p->nErr = 0; + p->useMod = 1; pMatch = p; }else if( p->iHoldiHold; @@ -1644,28 +1931,44 @@ static JsonParse *jsonParseCached( } } if( pMatch ){ + /* The input JSON text was found in the cache. Use the preexisting + ** parse of this JSON */ pMatch->nErr = 0; pMatch->iHold = iMaxHold+1; + assert( pMatch->nJPRef>0 ); /* pMatch is owned by the cache */ return pMatch; } - p = sqlite3_malloc64( sizeof(*p) + nJson + 1 ); + + /* The input JSON was not found anywhere in the cache. We will need + ** to parse it ourselves and generate a new JsonParse object. + */ + bJsonRCStr = sqlite3ValueIsOfClass(pJson,(void(*)(void*))sqlite3RCStrUnref); + p = sqlite3_malloc64( sizeof(*p) + (bJsonRCStr ? 0 : nJson+1) ); if( p==0 ){ sqlite3_result_error_nomem(pCtx); return 0; } memset(p, 0, sizeof(*p)); - p->zJson = (char*)&p[1]; - memcpy((char*)p->zJson, zJson, nJson+1); - if( jsonParse(p, pErrCtx, p->zJson) ){ + if( bJsonRCStr ){ + p->zJson = sqlite3RCStrRef(zJson); + p->bJsonIsRCStr = 1; + }else{ + p->zJson = (char*)&p[1]; + memcpy(p->zJson, zJson, nJson+1); + } + p->nJPRef = 1; + if( jsonParse(p, pErrCtx) ){ if( pErrCtx==0 ){ p->nErr = 1; + assert( p->nJPRef==1 ); /* Caller will own the new JsonParse object p */ return p; } - sqlite3_free(p); + jsonParseFree(p); return 0; } p->nJson = nJson; p->iHold = iMaxHold+1; + /* Transfer ownership of the new JsonParse to the cache */ sqlite3_set_auxdata(pCtx, JSON_CACHE_ID+iMinKey, p, (void(*)(void*))jsonParseFree); return (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iMinKey); @@ -1716,9 +2019,31 @@ static JsonNode *jsonLookupStep( ){ u32 i, j, nKey; const char *zKey; - JsonNode *pRoot = &pParse->aNode[iRoot]; + JsonNode *pRoot; + if( pParse->oom ) return 0; + pRoot = &pParse->aNode[iRoot]; + if( pRoot->jnFlags & (JNODE_REPLACE|JNODE_REMOVE) && pParse->useMod ){ + while( (pRoot->jnFlags & JNODE_REPLACE)!=0 ){ + u32 idx = (u32)(pRoot - pParse->aNode); + i = pParse->iSubst; + while( 1 /*exit-by-break*/ ){ + assert( inNode ); + assert( pParse->aNode[i].eType==JSON_SUBST ); + assert( pParse->aNode[i].eU==4 ); + assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ + pRoot = &pParse->aNode[i+1]; + iRoot = i+1; + break; + } + i = pParse->aNode[i].u.iPrev; + } + } + if( pRoot->jnFlags & JNODE_REMOVE ){ + return 0; + } + } if( zPath[0]==0 ) return pRoot; - if( pRoot->jnFlags & JNODE_REPLACE ) return 0; if( zPath[0]=='.' ){ if( pRoot->eType!=JSON_OBJECT ) return 0; zPath++; @@ -1752,14 +2077,16 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pRoot->eU==2 ); - iRoot += pRoot->u.iAppend; + iRoot = pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; } if( pApnd ){ u32 iStart, iLabel; JsonNode *pNode; + assert( pParse->useMod ); iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); iLabel = jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); zPath += i; @@ -1768,7 +2095,7 @@ static JsonNode *jsonLookupStep( if( pNode ){ pRoot = &pParse->aNode[iRoot]; assert( pRoot->eU==0 ); - pRoot->u.iAppend = iStart - iRoot; + pRoot->u.iAppend = iStart; pRoot->jnFlags |= JNODE_APPEND; VVA( pRoot->eU = 2 ); pParse->aNode[iLabel].jnFlags |= JNODE_RAW; @@ -1789,12 +2116,13 @@ static JsonNode *jsonLookupStep( if( pRoot->eType!=JSON_ARRAY ) return 0; for(;;){ while( j<=pBase->n ){ - if( (pBase[j].jnFlags & JNODE_REMOVE)==0 ) i++; + if( (pBase[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i++; j += jsonNodeSize(&pBase[j]); } if( (pBase->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pBase->eU==2 ); - iBase += pBase->u.iAppend; + iBase = pBase->u.iAppend; pBase = &pParse->aNode[iBase]; j = 1; } @@ -1822,13 +2150,16 @@ static JsonNode *jsonLookupStep( zPath += j + 1; j = 1; for(;;){ - while( j<=pRoot->n && (i>0 || (pRoot[j].jnFlags & JNODE_REMOVE)!=0) ){ - if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 ) i--; + while( j<=pRoot->n + && (i>0 || ((pRoot[j].jnFlags & JNODE_REMOVE)!=0 && pParse->useMod)) + ){ + if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i--; j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pRoot->eU==2 ); - iRoot += pRoot->u.iAppend; + iRoot = pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; } @@ -1838,13 +2169,14 @@ static JsonNode *jsonLookupStep( if( i==0 && pApnd ){ u32 iStart; JsonNode *pNode; + assert( pParse->useMod ); iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0); pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; assert( pRoot->eU==0 ); - pRoot->u.iAppend = iStart - iRoot; + pRoot->u.iAppend = iStart; pRoot->jnFlags |= JNODE_APPEND; VVA( pRoot->eU = 2 ); } @@ -1944,7 +2276,7 @@ static void jsonWrongNumArgs( char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", zFuncName); sqlite3_result_error(pCtx, zMsg, -1); - sqlite3_free(zMsg); + sqlite3_free(zMsg); } /* @@ -1971,47 +2303,90 @@ static void jsonRemoveAllNulls(JsonNode *pNode){ ** SQL functions used for testing and debugging ****************************************************************************/ +#if SQLITE_DEBUG +/* +** Print N node entries. +*/ +static void jsonDebugPrintNodeEntries( + JsonNode *aNode, /* First node entry to print */ + int N /* Number of node entries to print */ +){ + int i; + for(i=0; iaNode, p->nNode); +} +static void jsonDebugPrintNode(JsonNode *pNode){ + jsonDebugPrintNodeEntries(pNode, jsonNodeSize(pNode)); +} +#else + /* The usual case */ +# define jsonDebugPrintNode(X) +# define jsonDebugPrintParse(X) +#endif + #ifdef SQLITE_DEBUG /* -** The json_parse(JSON) function returns a string which describes -** a parse of the JSON provided. Or it returns NULL if JSON is not -** well-formed. +** SQL function: json_parse(JSON) +** +** Parse JSON using jsonParseCached(). Then print a dump of that +** parse on standard output. Return the mimified JSON result, just +** like the json() function. */ static void jsonParseFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ - JsonString s; /* Output string - not real JSON */ - JsonParse x; /* The parse */ - u32 i; + JsonParse *p; /* The parse */ assert( argc==1 ); - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - jsonParseFindParents(&x); - jsonInit(&s, ctx); - for(i=0; inNode); + printf("nAlloc = %u\n", p->nAlloc); + printf("nJson = %d\n", p->nJson); + printf("nAlt = %d\n", p->nAlt); + printf("nErr = %u\n", p->nErr); + printf("oom = %u\n", p->oom); + printf("hasNonstd = %u\n", p->hasNonstd); + printf("useMod = %u\n", p->useMod); + printf("hasMod = %u\n", p->hasMod); + printf("nJPRef = %u\n", p->nJPRef); + printf("iSubst = %u\n", p->iSubst); + printf("iHold = %u\n", p->iHold); + jsonDebugPrintNodeEntries(p->aNode, p->nNode); + jsonReturnJson(p, p->aNode, ctx, 1); } /* @@ -2035,7 +2410,7 @@ static void jsonTest1Func( /* ** Implementation of the json_QUOTE(VALUE) function. Return a JSON value -** corresponding to the SQL value input. Mostly this means putting +** corresponding to the SQL value input. Mostly this means putting ** double-quotes around strings and returning the unquoted string "null" ** when given a NULL input. */ @@ -2082,7 +2457,7 @@ static void jsonArrayFunc( ** json_array_length(JSON) ** json_array_length(JSON, PATH) ** -** Return the number of elements in the top-level JSON array. +** Return the number of elements in the top-level JSON array. ** Return 0 if the input is not a well-formed JSON array. */ static void jsonArrayLengthFunc( @@ -2095,7 +2470,7 @@ static void jsonArrayLengthFunc( u32 i; JsonNode *pNode; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; assert( p->nNode ); if( argc==2 ){ @@ -2108,9 +2483,14 @@ static void jsonArrayLengthFunc( return; } if( pNode->eType==JSON_ARRAY ){ - assert( (pNode->jnFlags & JNODE_APPEND)==0 ); - for(i=1; i<=pNode->n; n++){ - i += jsonNodeSize(&pNode[i]); + while( 1 /*exit-by-break*/ ){ + for(i=1; i<=pNode->n; n++){ + i += jsonNodeSize(&pNode[i]); + } + if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( p->useMod==0 ) break; + assert( pNode->eU==2 ); + pNode = &p->aNode[pNode->u.iAppend]; } } sqlite3_result_int64(ctx, n); @@ -2157,7 +2537,7 @@ static void jsonExtractFunc( JsonString jx; if( argc<2 ) return; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; if( argc==2 ){ /* With a single PATH argument */ @@ -2175,11 +2555,11 @@ static void jsonExtractFunc( */ jsonInit(&jx, ctx); if( sqlite3Isdigit(zPath[0]) ){ - jsonAppendRaw(&jx, "$[", 2); + jsonAppendRawNZ(&jx, "$[", 2); jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); - jsonAppendRaw(&jx, "]", 2); + jsonAppendRawNZ(&jx, "]", 2); }else{ - jsonAppendRaw(&jx, "$.", 1 + (zPath[0]!='[')); + jsonAppendRawNZ(&jx, "$.", 1 + (zPath[0]!='[')); jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); jsonAppendChar(&jx, 0); } @@ -2190,15 +2570,15 @@ static void jsonExtractFunc( } if( pNode ){ if( flags & JSON_JSON ){ - jsonReturnJson(pNode, ctx, 0); + jsonReturnJson(p, pNode, ctx, 0); }else{ - jsonReturn(pNode, ctx, 0); + jsonReturn(p, pNode, ctx); sqlite3_result_subtype(ctx, 0); } } }else{ pNode = jsonLookup(p, zPath, 0, ctx); - if( p->nErr==0 && pNode ) jsonReturn(pNode, ctx, 0); + if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx); } }else{ /* Two or more PATH arguments results in a JSON array with each @@ -2212,9 +2592,9 @@ static void jsonExtractFunc( if( p->nErr ) break; jsonAppendSeparator(&jx); if( pNode ){ - jsonRenderNode(pNode, &jx, 0); + jsonRenderNode(p, pNode, &jx); }else{ - jsonAppendRaw(&jx, "null", 4); + jsonAppendRawNZ(&jx, "null", 4); } } if( i==argc ){ @@ -2259,45 +2639,38 @@ static JsonNode *jsonMergePatch( assert( pTarget[j].eType==JSON_STRING ); assert( pTarget[j].jnFlags & JNODE_LABEL ); if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){ - if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH) ) break; + if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ) break; if( pPatch[i+1].eType==JSON_NULL ){ pTarget[j+1].jnFlags |= JNODE_REMOVE; }else{ JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]); if( pNew==0 ) return 0; - pTarget = &pParse->aNode[iTarget]; - if( pNew!=&pTarget[j+1] ){ - assert( pTarget[j+1].eU==0 - || pTarget[j+1].eU==1 - || pTarget[j+1].eU==2 ); - testcase( pTarget[j+1].eU==1 ); - testcase( pTarget[j+1].eU==2 ); - VVA( pTarget[j+1].eU = 5 ); - pTarget[j+1].u.pPatch = pNew; - pTarget[j+1].jnFlags |= JNODE_PATCH; + if( pNew!=&pParse->aNode[iTarget+j+1] ){ + jsonParseAddSubstNode(pParse, iTarget+j+1); + jsonParseAddNodeArray(pParse, pNew, jsonNodeSize(pNew)); } + pTarget = &pParse->aNode[iTarget]; } break; } } if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){ - int iStart, iPatch; - iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); + int iStart; + JsonNode *pApnd; + u32 nApnd; + iStart = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); - iPatch = jsonParseAddNode(pParse, JSON_TRUE, 0, 0); + pApnd = &pPatch[i+1]; + if( pApnd->eType==JSON_OBJECT ) jsonRemoveAllNulls(pApnd); + nApnd = jsonNodeSize(pApnd); + jsonParseAddNodeArray(pParse, pApnd, jsonNodeSize(pApnd)); if( pParse->oom ) return 0; - jsonRemoveAllNulls(pPatch); - pTarget = &pParse->aNode[iTarget]; - assert( pParse->aNode[iRoot].eU==0 || pParse->aNode[iRoot].eU==2 ); - testcase( pParse->aNode[iRoot].eU==2 ); + pParse->aNode[iStart].n = 1+nApnd; pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; + pParse->aNode[iRoot].u.iAppend = iStart; VVA( pParse->aNode[iRoot].eU = 2 ); - pParse->aNode[iRoot].u.iAppend = iStart - iRoot; iRoot = iStart; - assert( pParse->aNode[iPatch].eU==0 ); - VVA( pParse->aNode[iPatch].eU = 5 ); - pParse->aNode[iPatch].jnFlags |= JNODE_PATCH; - pParse->aNode[iPatch].u.pPatch = &pPatch[i+1]; + pTarget = &pParse->aNode[iTarget]; } } return pTarget; @@ -2313,25 +2686,28 @@ static void jsonPatchFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The JSON that is being patched */ - JsonParse y; /* The patch */ + JsonParse *pX; /* The JSON that is being patched */ + JsonParse *pY; /* The patch */ JsonNode *pResult; /* The result of the merge */ UNUSED_PARAMETER(argc); - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - if( jsonParse(&y, ctx, (const char*)sqlite3_value_text(argv[1])) ){ - jsonParseReset(&x); - return; - } - pResult = jsonMergePatch(&x, 0, y.aNode); - assert( pResult!=0 || x.oom ); - if( pResult ){ - jsonReturnJson(pResult, ctx, 0); + pX = jsonParseCached(ctx, argv[0], ctx, 1); + if( pX==0 ) return; + assert( pX->hasMod==0 ); + pX->hasMod = 1; + pY = jsonParseCached(ctx, argv[1], ctx, 1); + if( pY==0 ) return; + pX->useMod = 1; + pY->useMod = 1; + pResult = jsonMergePatch(pX, 0, pY->aNode); + assert( pResult!=0 || pX->oom ); + if( pResult && pX->oom==0 ){ + jsonDebugPrintParse(pX); + jsonDebugPrintNode(pResult); + jsonReturnJson(pX, pResult, ctx, 0); }else{ sqlite3_result_error_nomem(ctx); } - jsonParseReset(&x); - jsonParseReset(&y); } @@ -2387,26 +2763,118 @@ static void jsonRemoveFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; if( argc<1 ) return; - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; for(i=1; i<(u32)argc; i++){ zPath = (const char*)sqlite3_value_text(argv[i]); if( zPath==0 ) goto remove_done; - pNode = jsonLookup(&x, zPath, 0, ctx); - if( x.nErr ) goto remove_done; - if( pNode ) pNode->jnFlags |= JNODE_REMOVE; + pNode = jsonLookup(pParse, zPath, 0, ctx); + if( pParse->nErr ) goto remove_done; + if( pNode ){ + pNode->jnFlags |= JNODE_REMOVE; + pParse->hasMod = 1; + pParse->useMod = 1; + } } - if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnJson(x.aNode, ctx, 0); + if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ + jsonReturnJson(pParse, pParse->aNode, ctx, 1); } remove_done: - jsonParseReset(&x); + jsonDebugPrintParse(p); +} + +/* +** Substitute the value at iNode with the pValue parameter. +*/ +static void jsonReplaceNode( + sqlite3_context *pCtx, + JsonParse *p, + int iNode, + sqlite3_value *pValue +){ + int idx = jsonParseAddSubstNode(p, iNode); + if( idx<=0 ){ + assert( p->oom ); + return; + } + switch( sqlite3_value_type(pValue) ){ + case SQLITE_NULL: { + jsonParseAddNode(p, JSON_NULL, 0, 0); + break; + } + case SQLITE_FLOAT: { + char *z = sqlite3_mprintf("%!0.15g", sqlite3_value_double(pValue)); + int n; + if( z==0 ){ + p->oom = 1; + break; + } + n = sqlite3Strlen30(z); + jsonParseAddNode(p, JSON_REAL, n, z); + jsonParseAddCleanup(p, sqlite3_free, z); + break; + } + case SQLITE_INTEGER: { + char *z = sqlite3_mprintf("%lld", sqlite3_value_int64(pValue)); + int n; + if( z==0 ){ + p->oom = 1; + break; + } + n = sqlite3Strlen30(z); + jsonParseAddNode(p, JSON_INT, n, z); + jsonParseAddCleanup(p, sqlite3_free, z); + + break; + } + case SQLITE_TEXT: { + const char *z = (const char*)sqlite3_value_text(pValue); + u32 n = (u32)sqlite3_value_bytes(pValue); + if( z==0 ){ + p->oom = 1; + break; + } + if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){ + char *zCopy = sqlite3DbStrDup(0, z); + int k; + if( zCopy ){ + jsonParseAddCleanup(p, sqlite3_free, zCopy); + }else{ + p->oom = 1; + sqlite3_result_error_nomem(pCtx); + } + k = jsonParseAddNode(p, JSON_STRING, n, zCopy); + assert( k>0 || p->oom ); + if( p->oom==0 ) p->aNode[k].jnFlags |= JNODE_RAW; + }else{ + JsonParse *pPatch = jsonParseCached(pCtx, pValue, pCtx, 1); + if( pPatch==0 ){ + p->oom = 1; + break; + } + jsonParseAddNodeArray(p, pPatch->aNode, pPatch->nNode); + /* The nodes copied out of pPatch and into p likely contain + ** u.zJContent pointers into pPatch->zJson. So preserve the + ** content of pPatch until p is destroyed. */ + assert( pPatch->nJPRef>=1 ); + pPatch->nJPRef++; + jsonParseAddCleanup(p, (void(*)(void*))jsonParseFree, pPatch); + } + break; + } + default: { + jsonParseAddNode(p, JSON_NULL, 0, 0); + sqlite3_result_error(pCtx, "JSON cannot hold BLOB values", -1); + p->nErr++; + break; + } + } } /* @@ -2420,7 +2888,7 @@ static void jsonReplaceFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; @@ -2430,28 +2898,20 @@ static void jsonReplaceFunc( jsonWrongNumArgs(ctx, "replace"); return; } - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); - pNode = jsonLookup(&x, zPath, 0, ctx); - if( x.nErr ) goto replace_err; + pParse->useMod = 1; + pNode = jsonLookup(pParse, zPath, 0, ctx); + if( pParse->nErr ) goto replace_err; if( pNode ){ - assert( pNode->eU==0 || pNode->eU==1 || pNode->eU==4 ); - testcase( pNode->eU!=0 && pNode->eU!=1 ); - pNode->jnFlags |= (u8)JNODE_REPLACE; - VVA( pNode->eU = 4 ); - pNode->u.iReplace = i + 1; + jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - assert( x.aNode[0].eU==4 ); - sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); - }else{ - jsonReturnJson(x.aNode, ctx, argv); - } + jsonReturnJson(pParse, pParse->aNode, ctx, 1); replace_err: - jsonParseReset(&x); + jsonDebugPrintParse(pParse); } @@ -2472,7 +2932,7 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; @@ -2484,33 +2944,27 @@ static void jsonSetFunc( jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); return; } - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); bApnd = 0; - pNode = jsonLookup(&x, zPath, &bApnd, ctx); - if( x.oom ){ + pParse->useMod = 1; + pNode = jsonLookup(pParse, zPath, &bApnd, ctx); + if( pParse->oom ){ sqlite3_result_error_nomem(ctx); goto jsonSetDone; - }else if( x.nErr ){ + }else if( pParse->nErr ){ goto jsonSetDone; }else if( pNode && (bApnd || bIsSet) ){ - testcase( pNode->eU!=0 && pNode->eU!=1 ); - assert( pNode->eU!=3 && pNode->eU!=5 ); - VVA( pNode->eU = 4 ); - pNode->jnFlags |= (u8)JNODE_REPLACE; - pNode->u.iReplace = i + 1; + jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - assert( x.aNode[0].eU==4 ); - sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); - }else{ - jsonReturnJson(x.aNode, ctx, argv); - } + jsonDebugPrintParse(pParse); + jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonSetDone: - jsonParseReset(&x); + /* no cleanup required */; } /* @@ -2529,7 +2983,7 @@ static void jsonTypeFunc( const char *zPath; JsonNode *pNode; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; if( argc==2 ){ zPath = (const char*)sqlite3_value_text(argv[1]); @@ -2555,13 +3009,19 @@ static void jsonValidFunc( ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - p = jsonParseCached(ctx, argv, 0); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ +#ifdef SQLITE_LEGACY_JSON_VALID + /* Incorrect legacy behavior was to return FALSE for a NULL input */ + sqlite3_result_int(ctx, 0); +#endif + return; + } + p = jsonParseCached(ctx, argv[0], 0, 0); if( p==0 || p->oom ){ sqlite3_result_error_nomem(ctx); sqlite3_free(p); }else{ - sqlite3_result_int(ctx, p->nErr==0 && p->hasNonstd==0); + sqlite3_result_int(ctx, p->nErr==0 && (p->hasNonstd==0 || p->useMod)); if( p->nErr ) jsonParseFree(p); } } @@ -2602,7 +3062,7 @@ static void jsonErrorFunc( JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - p = jsonParseCached(ctx, argv, 0); + p = jsonParseCached(ctx, argv[0], 0, 0); if( p==0 || p->oom ){ sqlite3_result_error_nomem(ctx); sqlite3_free(p); @@ -2611,7 +3071,7 @@ static void jsonErrorFunc( }else{ int n = 1; u32 i; - const char *z = p->zJson; + const char *z = (const char*)sqlite3_value_text(argv[0]); for(i=0; iiErr && ALWAYS(z[i]); i++){ if( (z[i]&0xc0)!=0x80 ) n++; } @@ -2659,7 +3119,8 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, - pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free); + pStr->bStatic ? SQLITE_TRANSIENT : + (void(*)(void*))sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); @@ -2700,7 +3161,7 @@ static void jsonGroupInverse( pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); #ifdef NEVER /* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will - ** always have been called to initalize it */ + ** always have been called to initialize it */ if( NEVER(!pStr) ) return; #endif z = pStr->zBuf; @@ -2767,7 +3228,8 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, - pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free); + pStr->bStatic ? SQLITE_TRANSIENT : + (void(*)(void*))sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); @@ -2835,7 +3297,7 @@ static int jsonEachConnect( UNUSED_PARAMETER(argv); UNUSED_PARAMETER(argc); UNUSED_PARAMETER(pAux); - rc = sqlite3_declare_vtab(db, + rc = sqlite3_declare_vtab(db, "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path," "json HIDDEN,root HIDDEN)"); if( rc==SQLITE_OK ){ @@ -2878,7 +3340,6 @@ static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ /* Reset a JsonEachCursor back to its original state. Free any memory ** held. */ static void jsonEachCursorReset(JsonEachCursor *p){ - sqlite3_free(p->zJson); sqlite3_free(p->zRoot); jsonParseReset(&p->sParse); p->iRowid = 0; @@ -3016,7 +3477,7 @@ static int jsonEachColumn( case JEACH_KEY: { if( p->i==0 ) break; if( p->eType==JSON_OBJECT ){ - jsonReturn(pThis, ctx, 0); + jsonReturn(&p->sParse, pThis, ctx); }else if( p->eType==JSON_ARRAY ){ u32 iKey; if( p->bRecursive ){ @@ -3032,7 +3493,7 @@ static int jsonEachColumn( } case JEACH_VALUE: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; - jsonReturn(pThis, ctx, 0); + jsonReturn(&p->sParse, pThis, ctx); break; } case JEACH_TYPE: { @@ -3043,11 +3504,11 @@ static int jsonEachColumn( case JEACH_ATOM: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; if( pThis->eType>=JSON_ARRAY ) break; - jsonReturn(pThis, ctx, 0); + jsonReturn(&p->sParse, pThis, ctx); break; } case JEACH_ID: { - sqlite3_result_int64(ctx, + sqlite3_result_int64(ctx, (sqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0)); break; } @@ -3147,8 +3608,8 @@ static int jsonEachBestIndex( idxMask |= iMask; } } - if( pIdxInfo->nOrderBy>0 - && pIdxInfo->aOrderBy[0].iColumn<0 + if( pIdxInfo->nOrderBy>0 + && pIdxInfo->aOrderBy[0].iColumn<0 && pIdxInfo->aOrderBy[0].desc==0 ){ pIdxInfo->orderByConsumed = 1; @@ -3198,11 +3659,19 @@ static int jsonEachFilter( if( idxNum==0 ) return SQLITE_OK; z = (const char*)sqlite3_value_text(argv[0]); if( z==0 ) return SQLITE_OK; - n = sqlite3_value_bytes(argv[0]); - p->zJson = sqlite3_malloc64( n+1 ); - if( p->zJson==0 ) return SQLITE_NOMEM; - memcpy(p->zJson, z, (size_t)n+1); - if( jsonParse(&p->sParse, 0, p->zJson) ){ + memset(&p->sParse, 0, sizeof(p->sParse)); + p->sParse.nJPRef = 1; + if( sqlite3ValueIsOfClass(argv[0], (void(*)(void*))sqlite3RCStrUnref) ){ + p->sParse.zJson = sqlite3RCStrRef((char*)z); + }else{ + n = sqlite3_value_bytes(argv[0]); + p->sParse.zJson = sqlite3RCStrNew( n+1 ); + if( p->sParse.zJson==0 ) return SQLITE_NOMEM; + memcpy(p->sParse.zJson, z, (size_t)n+1); + } + p->sParse.bJsonIsRCStr = 1; + p->zJson = p->sParse.zJson; + if( jsonParse(&p->sParse, 0) ){ int rc = SQLITE_NOMEM; if( p->sParse.oom==0 ){ sqlite3_free(cur->pVtab->zErrMsg); @@ -3348,10 +3817,10 @@ void sqlite3RegisterJsonFunctions(void){ JFUNCTION(json_parse, 1, 0, jsonParseFunc), JFUNCTION(json_test1, 1, 0, jsonTest1Func), #endif - WAGGREGATE(json_group_array, 1, 0, 0, + WAGGREGATE(json_group_array, 1, 0, 0, jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), - WAGGREGATE(json_group_object, 2, 0, 0, + WAGGREGATE(json_group_object, 2, 0, 0, jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC) }; diff --git a/src/loadext.c b/src/loadext.c index a00df6536..e792fa5a9 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -512,7 +512,9 @@ static const sqlite3_api_routines sqlite3Apis = { /* Version 3.40.0 and later */ sqlite3_value_encoding, /* Version 3.41.0 and later */ - sqlite3_is_interrupted + sqlite3_is_interrupted, + /* Version 3.43.0 and later */ + sqlite3_stmt_explain }; /* True if x is the directory separator character @@ -591,6 +593,10 @@ static int sqlite3LoadExtension( ** See https://sqlite.org/forum/forumpost/24083b579d. */ if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found; + + /* Do not allow sqlite3_load_extension() to link to a copy of the + ** running application, by passing in an empty filename. */ + if( nMsg==0 ) goto extension_not_found; handle = sqlite3OsDlOpen(pVfs, zFile); #if SQLITE_OS_UNIX || SQLITE_OS_WIN diff --git a/src/main.c b/src/main.c index d7f366b5e..cd0200890 100644 --- a/src/main.c +++ b/src/main.c @@ -41,12 +41,6 @@ static int sqlite3TestExtInit(sqlite3 *db){ ** Forward declarations of external module initializer functions ** for modules that need them. */ -#ifdef SQLITE_ENABLE_FTS1 -int sqlite3Fts1Init(sqlite3*); -#endif -#ifdef SQLITE_ENABLE_FTS2 -int sqlite3Fts2Init(sqlite3*); -#endif #ifdef SQLITE_ENABLE_FTS5 int sqlite3Fts5Init(sqlite3*); #endif @@ -59,12 +53,6 @@ int sqlite3StmtVtabInit(sqlite3*); ** built-in extensions. */ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { -#ifdef SQLITE_ENABLE_FTS1 - sqlite3Fts1Init, -#endif -#ifdef SQLITE_ENABLE_FTS2 - sqlite3Fts2Init, -#endif #ifdef SQLITE_ENABLE_FTS3 sqlite3Fts3Init, #endif @@ -103,7 +91,7 @@ const char sqlite3_version[] = SQLITE_VERSION; #endif /* IMPLEMENTATION-OF: R-53536-42575 The sqlite3_libversion() function returns -** a pointer to the to the sqlite3_version[] string constant. +** a pointer to the to the sqlite3_version[] string constant. */ const char *sqlite3_libversion(void){ return sqlite3_version; } @@ -167,13 +155,13 @@ char *sqlite3_temp_directory = 0; char *sqlite3_data_directory = 0; /* -** Initialize SQLite. +** Initialize SQLite. ** ** This routine must be called to initialize the memory allocation, ** VFS, and mutex subsystems prior to doing any serious work with ** SQLite. But as long as you do not compile with SQLITE_OMIT_AUTOINIT ** this routine will be called automatically by key routines such as -** sqlite3_open(). +** sqlite3_open(). ** ** This routine is a no-op except on its very first call for the process, ** or for the first call after a call to sqlite3_shutdown. @@ -226,7 +214,7 @@ int sqlite3_initialize(void){ return SQLITE_OK; } - /* Make sure the mutex subsystem is initialized. If unable to + /* Make sure the mutex subsystem is initialized. If unable to ** initialize the mutex subsystem, return early with the error. ** If the system is so sick that we are unable to allocate a mutex, ** there is not much SQLite is going to be able to do. @@ -308,7 +296,7 @@ int sqlite3_initialize(void){ } #endif if( rc==SQLITE_OK ){ - sqlite3PCacheBufferSetup( sqlite3GlobalConfig.pPage, + sqlite3PCacheBufferSetup( sqlite3GlobalConfig.pPage, sqlite3GlobalConfig.szPage, sqlite3GlobalConfig.nPage); sqlite3MemoryBarrier(); sqlite3GlobalConfig.isInit = 1; @@ -539,7 +527,7 @@ int sqlite3_config(int op, ...){ ** a single parameter which is a pointer to an integer and writes into ** that integer the number of extra bytes per page required for each page ** in SQLITE_CONFIG_PAGECACHE. */ - *va_arg(ap, int*) = + *va_arg(ap, int*) = sqlite3HeaderSizeBtree() + sqlite3HeaderSizePcache() + sqlite3HeaderSizePcache1(); @@ -626,7 +614,7 @@ int sqlite3_config(int op, ...){ sqlite3GlobalConfig.nLookaside = va_arg(ap, int); break; } - + /* Record a pointer to the logger function and its first argument. ** The default is NULL. Logging is disabled if the function pointer is ** NULL. @@ -751,7 +739,7 @@ int sqlite3_config(int op, ...){ /* ** Set up the lookaside buffers for a database connection. -** Return SQLITE_OK on success. +** Return SQLITE_OK on success. ** If lookaside is already active, return SQLITE_BUSY. ** ** The sz parameter is the number of bytes in each lookaside slot. @@ -766,12 +754,12 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){ sqlite3_int64 szAlloc = sz*(sqlite3_int64)cnt; int nBig; /* Number of full-size slots */ int nSm; /* Number smaller LOOKASIDE_SMALL-byte slots */ - + if( sqlite3LookasideUsed(db,0)>0 ){ return SQLITE_BUSY; } /* Free any existing lookaside buffer for this handle before - ** allocating a new one so we don't have to have space for + ** allocating a new one so we don't have to have space for ** both at the same time. */ if( db->lookaside.bMalloced ){ @@ -1051,7 +1039,7 @@ int sqlite3IsBinary(const CollSeq *p){ } /* -** Another built-in collating sequence: NOCASE. +** Another built-in collating sequence: NOCASE. ** ** This collating sequence is intended to be used for "case independent ** comparison". SQLite's knowledge of upper and lower case equivalents @@ -1201,7 +1189,7 @@ static void disconnectAllVtab(sqlite3 *db){ /* ** Return TRUE if database connection db has unfinalized prepared -** statements or unfinished sqlite3_backup objects. +** statements or unfinished sqlite3_backup objects. */ static int connectionIsBusy(sqlite3 *db){ int j; @@ -1417,7 +1405,7 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ /* The temp-database schema is allocated differently from the other schema ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). ** So it needs to be freed here. Todo: Why not roll the temp schema into - ** the same sqliteMalloc() as the one that allocates the database + ** the same sqliteMalloc() as the one that allocates the database ** structure? */ sqlite3DbFree(db, db->aDb[1].pSchema); @@ -1448,7 +1436,7 @@ void sqlite3RollbackAll(sqlite3 *db, int tripCode){ assert( sqlite3_mutex_held(db->mutex) ); sqlite3BeginBenignMalloc(); - /* Obtain all b-tree mutexes before making any calls to BtreeRollback(). + /* Obtain all b-tree mutexes before making any calls to BtreeRollback(). ** This is important in case the transaction being rolled back has ** modified the database schema. If the b-tree mutexes are not taken ** here, then another shared-cache connection might sneak in between @@ -1677,9 +1665,9 @@ static int sqliteDefaultBusyCallback( void *ptr, /* Database connection */ int count /* Number of times table has been busy */ ){ -#if SQLITE_OS_WIN || HAVE_USLEEP +#if SQLITE_OS_WIN || !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP /* This case is for systems that have support for sleeping for fractions of - ** a second. Examples: All windows systems, unix systems with usleep() */ + ** a second. Examples: All windows systems, unix systems with nanosleep() */ static const u8 delays[] = { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 }; static const u8 totals[] = @@ -1734,7 +1722,7 @@ int sqlite3InvokeBusyHandler(BusyHandler *p){ }else{ p->nBusy++; } - return rc; + return rc; } /* @@ -1765,9 +1753,9 @@ int sqlite3_busy_handler( ** be invoked every nOps opcodes. */ void sqlite3_progress_handler( - sqlite3 *db, + sqlite3 *db, int nOps, - int (*xProgress)(void*), + int (*xProgress)(void*), void *pArg ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -1844,7 +1832,7 @@ int sqlite3_is_interrupted(sqlite3 *db){ ** This function is exactly the same as sqlite3_create_function(), except ** that it is designed to be called by internal code. The difference is ** that if a malloc() fails in sqlite3_create_function(), an error code -** is returned and the mallocFailed flag cleared. +** is returned and the mallocFailed flag cleared. */ int sqlite3CreateFunc( sqlite3 *db, @@ -1885,7 +1873,7 @@ int sqlite3CreateFunc( assert( SQLITE_FUNC_UNSAFE==SQLITE_INNOCUOUS ); extraFlags ^= SQLITE_FUNC_UNSAFE; /* tag-20230109-1 */ - + #ifndef SQLITE_OMIT_UTF16 /* If SQLITE_UTF16 is specified as the encoding type, transform this ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the @@ -1925,7 +1913,7 @@ int sqlite3CreateFunc( #else enc = SQLITE_UTF8; #endif - + /* Check if an existing function is being overridden or deleted. If so, ** and there are active VMs, then return SQLITE_BUSY. If a function ** is being overridden/deleted but there are no active VMs, allow the @@ -1934,7 +1922,7 @@ int sqlite3CreateFunc( p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 0); if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==(u32)enc && p->nArg==nArg ){ if( db->nVdbeActive ){ - sqlite3ErrorWithMsg(db, SQLITE_BUSY, + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to delete/modify user-function due to active statements"); assert( !db->mallocFailed ); return SQLITE_BUSY; @@ -2013,7 +2001,7 @@ static int createFunctionApi( pArg->xDestroy = xDestroy; pArg->pUserData = p; } - rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, + rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, xSFunc, xStep, xFinal, xValue, xInverse, pArg ); if( pArg && pArg->nRef==0 ){ @@ -2130,7 +2118,7 @@ static void sqlite3InvalidFunction( ** ** If the function already exists as a regular global function, then ** this routine is a no-op. If the function does not exist, then create -** a new one that always throws a run-time error. +** a new one that always throws a run-time error. ** ** When virtual tables intend to provide an overloaded function, they ** should call this routine to make sure the global function exists. @@ -2163,7 +2151,7 @@ int sqlite3_overload_function( #ifndef SQLITE_OMIT_TRACE /* ** Register a trace function. The pArg from the previously registered trace -** is returned. +** is returned. ** ** A NULL trace function means that no tracing is executes. A non-NULL ** trace is a pointer to a function that is invoked at the start of each @@ -2214,8 +2202,8 @@ int sqlite3_trace_v2( #ifndef SQLITE_OMIT_DEPRECATED /* -** Register a profile function. The pArg from the previously registered -** profile function is returned. +** Register a profile function. The pArg from the previously registered +** profile function is returned. ** ** A NULL profile function means that no profiling is executes. A non-NULL ** profile is a pointer to a function that is invoked at the conclusion of @@ -2349,7 +2337,7 @@ void *sqlite3_preupdate_hook( */ int sqlite3_autovacuum_pages( sqlite3 *db, /* Attach the hook to this database */ - unsigned int (*xCallback)(void*,const char*,u32,u32,u32), + unsigned int (*xCallback)(void*,const char*,u32,u32,u32), void *pArg, /* Argument to the function */ void (*xDestructor)(void*) /* Destructor for pArg */ ){ @@ -2377,7 +2365,7 @@ int sqlite3_autovacuum_pages( ** Invoke sqlite3_wal_checkpoint if the number of frames in the log file ** is greater than sqlite3.pWalArg cast to an integer (the value configured by ** wal_autocheckpoint()). -*/ +*/ int sqlite3WalDefaultHook( void *pClientData, /* Argument */ sqlite3 *db, /* Connection */ @@ -2513,7 +2501,7 @@ int sqlite3_wal_checkpoint_v2( /* ** Checkpoint database zDb. If zDb is NULL, or if the buffer zDb points -** to contains a zero-length string, all attached databases are +** to contains a zero-length string, all attached databases are ** checkpointed. */ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ @@ -2527,9 +2515,9 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ ** Run a checkpoint on database iDb. This is a no-op if database iDb is ** not currently open in WAL mode. ** -** If a transaction is open on the database being checkpointed, this -** function returns SQLITE_LOCKED and a checkpoint is not attempted. If -** an error occurs while running the checkpoint, an SQLite error code is +** If a transaction is open on the database being checkpointed, this +** function returns SQLITE_LOCKED and a checkpoint is not attempted. If +** an error occurs while running the checkpoint, an SQLite error code is ** returned (i.e. SQLITE_IOERR). Otherwise, SQLITE_OK. ** ** The mutex on database handle db should be held by the caller. The mutex @@ -2713,7 +2701,7 @@ int sqlite3_extended_errcode(sqlite3 *db){ } int sqlite3_system_errno(sqlite3 *db){ return db ? db->iSysErrno : 0; -} +} /* ** Return a string that describes the kind of error specified in the @@ -2730,7 +2718,7 @@ const char *sqlite3_errstr(int rc){ */ static int createCollation( sqlite3* db, - const char *zName, + const char *zName, u8 enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*), @@ -2738,7 +2726,7 @@ static int createCollation( ){ CollSeq *pColl; int enc2; - + assert( sqlite3_mutex_held(db->mutex) ); /* If SQLITE_UTF16 is specified as the encoding type, transform this @@ -2755,14 +2743,14 @@ static int createCollation( return SQLITE_MISUSE_BKPT; } - /* Check if this call is removing or replacing an existing collation + /* Check if this call is removing or replacing an existing collation ** sequence. If so, and there are active VMs, return busy. If there ** are no active VMs, invalidate any pre-compiled statements. */ pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 0); if( pColl && pColl->xCmp ){ if( db->nVdbeActive ){ - sqlite3ErrorWithMsg(db, SQLITE_BUSY, + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to delete/modify collation sequence due to active statements"); return SQLITE_BUSY; } @@ -2773,7 +2761,7 @@ static int createCollation( ** then any copies made by synthCollSeq() need to be invalidated. ** Also, collation destructor - CollSeq.xDel() - function may need ** to be called. - */ + */ if( (pColl->enc & ~SQLITE_UTF16_ALIGNED)==enc2 ){ CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName); int j; @@ -2924,7 +2912,7 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ ** query parameter. The second argument contains the URI (or non-URI filename) ** itself. When this function is called the *pFlags variable should contain ** the default flags to open the database handle with. The value stored in -** *pFlags may be updated before returning if the URI filename contains +** *pFlags may be updated before returning if the URI filename contains ** "cache=xxx" or "mode=xxx" query parameters. ** ** If successful, SQLITE_OK is returned. In this case *ppVfs is set to point to @@ -2936,7 +2924,7 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ ** the value returned in *pzFile to avoid a memory leak. ** ** If an error occurs, then an SQLite error code is returned and *pzErrMsg -** may be set to point to a buffer containing an English language error +** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to eventually release ** this buffer by calling sqlite3_free(). */ @@ -2944,7 +2932,7 @@ int sqlite3ParseUri( const char *zDefaultVfs, /* VFS to use if no "vfs=xxx" query option */ const char *zUri, /* Nul-terminated URI to parse */ unsigned int *pFlags, /* IN/OUT: SQLITE_OPEN_XXX flags */ - sqlite3_vfs **ppVfs, /* OUT: VFS to use */ + sqlite3_vfs **ppVfs, /* OUT: VFS to use */ char **pzFile, /* OUT: Filename component of URI */ char **pzErrMsg /* OUT: Error message (if rc!=SQLITE_OK) */ ){ @@ -2967,7 +2955,7 @@ int sqlite3ParseUri( int iOut = 0; /* Output character index */ u64 nByte = nUri+8; /* Bytes of space to allocate */ - /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen + /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen ** method that there may be extra parameters following the file-name. */ flags |= SQLITE_OPEN_URI; @@ -2985,7 +2973,7 @@ int sqlite3ParseUri( /* The following condition causes URIs with five leading / characters ** like file://///host/path to be converted into UNCs like //host/path. ** The correct URI for that UNC has only two or four leading / characters - ** file://host/path or file:////host/path. But 5 leading slashes is a + ** file://host/path or file:////host/path. But 5 leading slashes is a ** common error, we are told, so we handle it as a special case. */ if( strncmp(zUri+7, "///", 3)==0 ){ iIn++; } }else if( strncmp(zUri+5, "//localhost/", 12)==0 ){ @@ -2997,7 +2985,7 @@ int sqlite3ParseUri( iIn = 7; while( zUri[iIn] && zUri[iIn]!='/' ) iIn++; if( iIn!=7 && (iIn!=16 || memcmp("localhost", &zUri[7], 9)) ){ - *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s", + *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s", iIn-7, &zUri[7]); rc = SQLITE_ERROR; goto parse_uri_out; @@ -3005,8 +2993,8 @@ int sqlite3ParseUri( } #endif - /* Copy the filename and any query parameters into the zFile buffer. - ** Decode %HH escape codes along the way. + /* Copy the filename and any query parameters into the zFile buffer. + ** Decode %HH escape codes along the way. ** ** Within this loop, variable eState may be set to 0, 1 or 2, depending ** on the parsing context. As follows: @@ -3018,9 +3006,9 @@ int sqlite3ParseUri( eState = 0; while( (c = zUri[iIn])!=0 && c!='#' ){ iIn++; - if( c=='%' - && sqlite3Isxdigit(zUri[iIn]) - && sqlite3Isxdigit(zUri[iIn+1]) + if( c=='%' + && sqlite3Isxdigit(zUri[iIn]) + && sqlite3Isxdigit(zUri[iIn+1]) ){ int octet = (sqlite3HexToInt(zUri[iIn++]) << 4); octet += sqlite3HexToInt(zUri[iIn++]); @@ -3032,7 +3020,7 @@ int sqlite3ParseUri( ** case we ignore all text in the remainder of the path, name or ** value currently being parsed. So ignore the current character ** and skip to the next "?", "=" or "&", as appropriate. */ - while( (c = zUri[iIn])!=0 && c!='#' + while( (c = zUri[iIn])!=0 && c!='#' && (eState!=0 || c!='?') && (eState!=1 || (c!='=' && c!='&')) && (eState!=2 || c!='&') @@ -3069,7 +3057,7 @@ int sqlite3ParseUri( if( eState==1 ) zFile[iOut++] = '\0'; memset(zFile+iOut, 0, 4); /* end-of-options + empty journal filenames */ - /* Check if there were any options specified that should be interpreted + /* Check if there were any options specified that should be interpreted ** here. Options that are interpreted here include "vfs" and those that ** correspond to flags that may be passed to the sqlite3_open_v2() ** method. */ @@ -3105,7 +3093,7 @@ int sqlite3ParseUri( if( nOpt==4 && memcmp("mode", zOpt, 4)==0 ){ static struct OpenMode aOpenMode[] = { { "ro", SQLITE_OPEN_READONLY }, - { "rw", SQLITE_OPEN_READWRITE }, + { "rw", SQLITE_OPEN_READWRITE }, { "rwc", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE }, { "memory", SQLITE_OPEN_MEMORY }, { 0, 0 } @@ -3192,7 +3180,7 @@ static const char *uriParameter(const char *zFilename, const char *zParam){ /* ** This routine does the work of opening a database on behalf of -** sqlite3_open() and sqlite3_open16(). The database filename "zFilename" +** sqlite3_open() and sqlite3_open16(). The database filename "zFilename" ** is UTF-8 encoded. */ static int openDatabase( @@ -3245,11 +3233,11 @@ static int openDatabase( flags &= ~( SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_MAIN_DB | - SQLITE_OPEN_TEMP_DB | - SQLITE_OPEN_TRANSIENT_DB | - SQLITE_OPEN_MAIN_JOURNAL | - SQLITE_OPEN_TEMP_JOURNAL | - SQLITE_OPEN_SUBJOURNAL | + SQLITE_OPEN_TEMP_DB | + SQLITE_OPEN_TRANSIENT_DB | + SQLITE_OPEN_MAIN_JOURNAL | + SQLITE_OPEN_TEMP_JOURNAL | + SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_SUPER_JOURNAL | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_FULLMUTEX | @@ -3259,7 +3247,7 @@ static int openDatabase( /* Allocate the sqlite data structure */ db = sqlite3MallocZero( sizeof(sqlite3) ); if( db==0 ) goto opendb_out; - if( isThreadsafe + if( isThreadsafe #ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS || sqlite3GlobalConfig.bCoreMutex #endif @@ -3291,7 +3279,7 @@ static int openDatabase( db->nextPagesize = 0; db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ #ifdef SQLITE_ENABLE_SORTER_MMAP - /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map + /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map ** the temporary files used to do external sorts (see code in vdbesort.c) ** is disabled. It can still be used either by defining ** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the @@ -3310,14 +3298,14 @@ static int openDatabase( ** ** SQLITE_DQS SQLITE_DBCONFIG_DQS_DDL SQLITE_DBCONFIG_DQS_DML ** ---------- ----------------------- ----------------------- -** undefined on on +** undefined on on ** 3 on on ** 2 on off ** 1 off on ** 0 off off ** ** Legacy behavior is 3 (double-quoted string literals are allowed anywhere) -** and so that is the default. But developers are encouranged to use +** and so that is the default. But developers are encouraged to use ** -DSQLITE_DQS=0 (best) or -DSQLITE_DQS=1 (second choice) if possible. */ #if !defined(SQLITE_DQS) @@ -3406,7 +3394,7 @@ static int openDatabase( /* Parse the filename/URI argument ** - ** Only allow sensible combinations of bits in the flags argument. + ** 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 @@ -3460,7 +3448,7 @@ static int openDatabase( db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); /* The default safety_level for the main database is FULL; for the temp - ** database it is OFF. This matches the pager layer defaults. + ** database it is OFF. This matches the pager layer defaults. */ db->aDb[0].zDbSName = "main"; db->aDb[0].safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; @@ -3499,7 +3487,7 @@ static int openDatabase( #ifdef SQLITE_ENABLE_INTERNAL_FUNCTIONS /* Testing use only!!! The -DSQLITE_ENABLE_INTERNAL_FUNCTIONS=1 compile-time - ** option gives access to internal functions by default. + ** option gives access to internal functions by default. ** Testing use only!!! */ db->mDbFlags |= DBFLAG_InternalFunc; #endif @@ -3553,8 +3541,8 @@ static int openDatabase( ** Open a new database handle. */ int sqlite3_open( - const char *zFilename, - sqlite3 **ppDb + const char *zFilename, + sqlite3 **ppDb ){ return openDatabase(zFilename, ppDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); @@ -3573,7 +3561,7 @@ int sqlite3_open_v2( ** Open a new database handle. */ int sqlite3_open16( - const void *zFilename, + const void *zFilename, sqlite3 **ppDb ){ char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */ @@ -3612,9 +3600,9 @@ int sqlite3_open16( ** Register a new collation sequence with the database handle db. */ int sqlite3_create_collation( - sqlite3* db, - const char *zName, - int enc, + sqlite3* db, + const char *zName, + int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*) ){ @@ -3625,9 +3613,9 @@ int sqlite3_create_collation( ** Register a new collation sequence with the database handle db. */ int sqlite3_create_collation_v2( - sqlite3* db, - const char *zName, - int enc, + sqlite3* db, + const char *zName, + int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDel)(void*) @@ -3650,9 +3638,9 @@ int sqlite3_create_collation_v2( ** Register a new collation sequence with the database handle db. */ int sqlite3_create_collation16( - sqlite3* db, + sqlite3* db, const void *zName, - int enc, + int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*) ){ @@ -3680,8 +3668,8 @@ int sqlite3_create_collation16( ** db. Replace any previously installed collation sequence factory. */ int sqlite3_collation_needed( - sqlite3 *db, - void *pCollNeededArg, + sqlite3 *db, + void *pCollNeededArg, void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*) ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -3701,8 +3689,8 @@ int sqlite3_collation_needed( ** db. Replace any previously installed collation sequence factory. */ int sqlite3_collation_needed16( - sqlite3 *db, - void *pCollNeededArg, + sqlite3 *db, + void *pCollNeededArg, void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*) ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -3852,7 +3840,7 @@ int sqlite3_table_column_metadata( /* Find the column for which info is requested */ if( zColumnName==0 ){ - /* Query for existance of table only */ + /* Query for existence of table only */ }else{ for(iCol=0; iColnCol; iCol++){ pCol = &pTab->aCol[iCol]; @@ -3874,13 +3862,13 @@ int sqlite3_table_column_metadata( /* The following block stores the meta information that will be returned ** to the caller in local variables zDataType, zCollSeq, notnull, primarykey ** and autoinc. At this point there are two possibilities: - ** - ** 1. The specified column name was rowid", "oid" or "_rowid_" - ** and there is no explicitly declared IPK column. ** - ** 2. The table is not a view and the column name identified an + ** 1. The specified column name was rowid", "oid" or "_rowid_" + ** and there is no explicitly declared IPK column. + ** + ** 2. The table is not a view and the column name identified an ** explicitly declared column. Copy meta information from *pCol. - */ + */ if( pCol ){ zDataType = sqlite3ColumnType(pCol,0); zCollSeq = sqlite3ColumnColl(pCol); @@ -3930,7 +3918,7 @@ int sqlite3_sleep(int ms){ pVfs = sqlite3_vfs_find(0); if( pVfs==0 ) return 0; - /* This function works in milliseconds, but the underlying OsSleep() + /* This function works in milliseconds, but the underlying OsSleep() ** API uses microseconds. Hence the 1000's. */ rc = (sqlite3OsSleep(pVfs, ms<0 ? 0 : 1000*ms)/1000); @@ -4110,7 +4098,7 @@ int sqlite3_test_control(int op, ...){ /* ** sqlite3_test_control(BENIGN_MALLOC_HOOKS, xBegin, xEnd) ** - ** Register hooks to call to indicate which malloc() failures + ** Register hooks to call to indicate which malloc() failures ** are benign. */ case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: { @@ -4172,10 +4160,12 @@ int sqlite3_test_control(int op, ...){ sqlite3ShowSrcList(0); sqlite3ShowWith(0); sqlite3ShowUpsert(0); +#ifndef SQLITE_OMIT_TRIGGER sqlite3ShowTriggerStep(0); sqlite3ShowTriggerStepList(0); sqlite3ShowTrigger(0); sqlite3ShowTriggerList(0); +#endif #ifndef SQLITE_OMIT_WINDOWFUNC sqlite3ShowWindow(0); sqlite3ShowWinFunc(0); @@ -4230,7 +4220,7 @@ int sqlite3_test_control(int op, ...){ ** 10 little-endian, determined at run-time ** 432101 big-endian, determined at compile-time ** 123410 little-endian, determined at compile-time - */ + */ case SQLITE_TESTCTRL_BYTEORDER: { rc = SQLITE_BYTEORDER*100 + SQLITE_LITTLEENDIAN*10 + SQLITE_BIGENDIAN; break; @@ -4238,7 +4228,7 @@ int sqlite3_test_control(int op, ...){ /* sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite3 *db, int N) ** - ** Enable or disable various optimizations for testing purposes. The + ** Enable or disable various optimizations for testing purposes. The ** argument N is a bitmask of optimizations to be disabled. For normal ** operation N should be 0. The idea is that a test program (like the ** SQL Logic Test or SLT test module) can run the same SQL multiple times @@ -4292,7 +4282,7 @@ int sqlite3_test_control(int op, ...){ ** formed and never corrupt. This flag is clear by default, indicating that ** database files might have arbitrary corruption. Setting the flag during ** testing causes certain assert() statements in the code to be activated - ** that demonstrat invariants on well-formed database files. + ** that demonstrate invariants on well-formed database files. */ case SQLITE_TESTCTRL_NEVER_CORRUPT: { sqlite3GlobalConfig.neverCorrupt = va_arg(ap, int); @@ -4328,7 +4318,7 @@ int sqlite3_test_control(int op, ...){ /* sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr); ** - ** Set the VDBE coverage callback function to xCallback with context + ** Set the VDBE coverage callback function to xCallback with context ** pointer ptr. */ case SQLITE_TESTCTRL_VDBE_COVERAGE: { @@ -4442,11 +4432,11 @@ int sqlite3_test_control(int op, ...){ /* sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, op, ptr) ** - ** "ptr" is a pointer to a u32. + ** "ptr" is a pointer to a u32. ** ** op==0 Store the current sqlite3TreeTrace in *ptr ** op==1 Set sqlite3TreeTrace to the value *ptr - ** op==3 Store the current sqlite3WhereTrace in *ptr + ** op==2 Store the current sqlite3WhereTrace in *ptr ** op==3 Set sqlite3WhereTrace to the value *ptr */ case SQLITE_TESTCTRL_TRACEFLAGS: { @@ -4481,7 +4471,24 @@ int sqlite3_test_control(int op, ...){ *pI2 = sqlite3LogEst(*pU64); break; } - + +#if !defined(SQLITE_OMIT_WSD) + /* sqlite3_test_control(SQLITE_TESTCTRL_USELONGDOUBLE, int X); + ** + ** X<0 Make no changes to the bUseLongDouble. Just report value. + ** X==0 Disable bUseLongDouble + ** X==1 Enable bUseLongDouble + ** X==2 Set bUseLongDouble to its default value for this platform + */ + case SQLITE_TESTCTRL_USELONGDOUBLE: { + int b = va_arg(ap, int); + if( b==2 ) b = sizeof(LONGDOUBLE_TYPE)>8; + if( b>=0 ) sqlite3Config.bUseLongDouble = b>0; + rc = sqlite3Config.bUseLongDouble!=0; + break; + } +#endif + #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) /* sqlite3_test_control(SQLITE_TESTCTRL_TUNE, id, *piValue) @@ -4597,7 +4604,7 @@ void sqlite3_free_filename(const char *p){ /* ** This is a utility routine, useful to VFS implementations, that checks -** to see if a database file was a URI that contained a specific query +** to see if a database file was a URI that contained a specific query ** parameter, and if so obtains the value of the query parameter. ** ** The zFilename argument is the filename pointer passed into the xOpen() @@ -4745,11 +4752,11 @@ int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){ #ifdef SQLITE_ENABLE_SNAPSHOT /* -** Obtain a snapshot handle for the snapshot of database zDb currently +** Obtain a snapshot handle for the snapshot of database zDb currently ** being read by handle db. */ int sqlite3_snapshot_get( - sqlite3 *db, + sqlite3 *db, const char *zDb, sqlite3_snapshot **ppSnapshot ){ @@ -4782,11 +4789,11 @@ int sqlite3_snapshot_get( } /* -** Open a read-transaction on the snapshot idendified by pSnapshot. +** Open a read-transaction on the snapshot identified by pSnapshot. */ int sqlite3_snapshot_open( - sqlite3 *db, - const char *zDb, + sqlite3 *db, + const char *zDb, sqlite3_snapshot *pSnapshot ){ int rc = SQLITE_ERROR; @@ -4888,7 +4895,7 @@ int sqlite3_compileoption_used(const char *zOptName){ int i, n; int nOpt; const char **azCompileOpt; - + #if SQLITE_ENABLE_API_ARMOR if( zOptName==0 ){ (void)SQLITE_MISUSE_BKPT; @@ -4901,7 +4908,7 @@ int sqlite3_compileoption_used(const char *zOptName){ if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7; n = sqlite3Strlen30(zOptName); - /* Since nOpt is normally in single digits, a linear search is + /* Since nOpt is normally in single digits, a linear search is ** adequate. No need for a binary search. */ for(i=0; i1 ){ /* defer MT decisions to system malloc */ _sqliteZone_ = malloc_default_zone(); }else{ - /* only 1 core, use our own zone to contention over global locks, + /* only 1 core, use our own zone to contention over global locks, ** e.g. we have our own dedicated locks */ _sqliteZone_ = malloc_create_zone(4096, 0); malloc_set_zone_name(_sqliteZone_, "Sqlite_Heap"); diff --git a/src/mutex_unix.c b/src/mutex_unix.c index 2afaddec6..ac4331a67 100644 --- a/src/mutex_unix.c +++ b/src/mutex_unix.c @@ -26,7 +26,7 @@ /* ** The sqlite3_mutex.id, sqlite3_mutex.nRef, and sqlite3_mutex.owner fields -** are necessary under two condidtions: (1) Debug builds and (2) using +** are necessary under two conditions: (1) Debug builds and (2) using ** home-grown mutexes. Encapsulate these conditions into a single #define. */ #if defined(SQLITE_DEBUG) || defined(SQLITE_HOMEGROWN_RECURSIVE_MUTEX) @@ -64,7 +64,7 @@ struct sqlite3_mutex { ** there might be race conditions that can cause these routines to ** deliver incorrect results. In particular, if pthread_equal() is ** not an atomic operation, then these routines might delivery -** incorrect results. On most platforms, pthread_equal() is a +** incorrect results. On most platforms, pthread_equal() is a ** comparison of two integers and is therefore atomic. But we are ** told that HPUX is not such a platform. If so, then these routines ** will not always work correctly on HPUX. @@ -146,7 +146,7 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; } ** ** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() -** returns a different mutex on every call. But for the static +** returns a different mutex on every call. But for the static ** mutex types, the same mutex is returned on every call that has ** the same type number. */ @@ -257,7 +257,7 @@ static void pthreadMutexEnter(sqlite3_mutex *p){ ** is atomic - that it cannot be deceived into thinking self ** and p->owner are equal if p->owner changes between two values ** that are not equal to self while the comparison is taking place. - ** This implementation also assumes a coherent cache - that + ** This implementation also assumes a coherent cache - that ** separate processes cannot read different values from the same ** address at the same time. If either of these two conditions ** are not met, then the mutexes will fail and problems will result. @@ -300,7 +300,7 @@ static int pthreadMutexTry(sqlite3_mutex *p){ ** is atomic - that it cannot be deceived into thinking self ** and p->owner are equal if p->owner changes between two values ** that are not equal to self while the comparison is taking place. - ** This implementation also assumes a coherent cache - that + ** This implementation also assumes a coherent cache - that ** separate processes cannot read different values from the same ** address at the same time. If either of these two conditions ** are not met, then the mutexes will fail and problems will result. diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 09deda409..e0e0dfb06 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -38,7 +38,7 @@ struct sqlite3_mutex { CRITICAL_SECTION mutex; /* Mutex controlling the lock */ int id; /* Mutex type */ #ifdef SQLITE_DEBUG - volatile int nRef; /* Number of enterances */ + volatile int nRef; /* Number of entrances */ volatile DWORD owner; /* Thread holding this mutex */ volatile LONG trace; /* True to trace changes */ #endif diff --git a/src/os.c b/src/os.c index e2914e03c..a9fc732e7 100644 --- a/src/os.c +++ b/src/os.c @@ -137,7 +137,7 @@ int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ /* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite ** is using a regular VFS, it is called after the corresponding ** transaction has been committed. Injecting a fault at this point - ** confuses the test scripts - the COMMIT comand returns SQLITE_NOMEM + ** confuses the test scripts - the COMMIT command returns SQLITE_NOMEM ** but the transaction is committed anyway. ** ** The core must call OsFileControl() though, not OsFileControlHint(), @@ -279,7 +279,7 @@ int sqlite3OsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ }else{ return pVfs->xRandomness(pVfs, nByte, zBufOut); } - + } int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){ return pVfs->xSleep(pVfs, nMicro); diff --git a/src/os_unix.c b/src/os_unix.c index d0ebb86b9..59f67d142 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -22,7 +22,7 @@ ** This source file is organized into divisions where the logic for various ** subfunctions is contained within the appropriate division. PLEASE ** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed -** in the correct division and should be clearly labeled. +** in the correct division and should be clearly labelled. ** ** The layout of divisions is as follows: ** @@ -61,7 +61,7 @@ ** Styles 4, 5, and 7 are only available of SQLITE_ENABLE_LOCKING_STYLE ** is defined to 1. The SQLITE_ENABLE_LOCKING_STYLE also enables automatic ** selection of the appropriate locking style based on the filesystem -** where the database is located. +** where the database is located. */ #if !defined(SQLITE_ENABLE_LOCKING_STYLE) # if defined(__APPLE__) @@ -226,7 +226,7 @@ #endif /* -** Only set the lastErrno if the error code is a real error and not +** Only set the lastErrno if the error code is a real error and not ** a normal expected return code of SQLITE_BUSY or SQLITE_OK */ #define IS_LOCK_ERROR(x) ((x != SQLITE_OK) && (x != SQLITE_BUSY)) @@ -294,7 +294,7 @@ struct unixFile { ** whenever any part of the database changes. An assertion fault will ** occur if a file is updated without also updating the transaction ** counter. This test is made to avoid new problems similar to the - ** one described by ticket #3584. + ** one described by ticket #3584. */ unsigned char transCntrChng; /* True if the transaction counter changed */ unsigned char dbUpdate; /* True if any part of database file changed */ @@ -303,7 +303,7 @@ struct unixFile { #endif #ifdef SQLITE_TEST - /* In test mode, increase the size of this structure a bit so that + /* In test mode, increase the size of this structure a bit so that ** it is larger than the struct CrashFile defined in test6.c. */ char aPadding[32]; @@ -448,7 +448,7 @@ static struct unix_syscall { #ifdef __DJGPP__ { "fstat", 0, 0 }, #define osFstat(a,b,c) 0 -#else +#else { "fstat", (sqlite3_syscall_ptr)fstat, 0 }, #define osFstat ((int(*)(int,struct stat*))aSyscall[5].pCurrent) #endif @@ -609,7 +609,7 @@ static int robustFchown(int fd, uid_t uid, gid_t gid){ /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the -** "unix" VFSes. Return SQLITE_OK opon successfully updating the +** "unix" VFSes. Return SQLITE_OK upon successfully updating the ** system call pointer, or SQLITE_NOTFOUND if there is no configurable ** system call named zName. */ @@ -692,7 +692,7 @@ static const char *unixNextSystemCall(sqlite3_vfs *p, const char *zName){ /* ** Do not accept any file descriptor less than this value, in order to avoid -** opening database file using file descriptors that are commonly used for +** opening database file using file descriptors that are commonly used for ** standard input, output, and error. */ #ifndef SQLITE_MINIMUM_FILE_DESCRIPTOR @@ -734,7 +734,7 @@ static int robust_open(const char *z, int f, mode_t m){ (void)osUnlink(z); } osClose(fd); - sqlite3_log(SQLITE_WARNING, + sqlite3_log(SQLITE_WARNING, "attempt to open \"%s\" as file descriptor %d", z, fd); fd = -1; if( osOpen("/dev/null", O_RDONLY, m)<0 ) break; @@ -742,9 +742,9 @@ static int robust_open(const char *z, int f, mode_t m){ if( fd>=0 ){ if( m!=0 ){ struct stat statbuf; - if( osFstat(fd, &statbuf)==0 + if( osFstat(fd, &statbuf)==0 && statbuf.st_size==0 - && (statbuf.st_mode&0777)!=m + && (statbuf.st_mode&0777)!=m ){ osFchmod(fd, m); } @@ -759,11 +759,11 @@ static int robust_open(const char *z, int f, mode_t m){ /* ** Helper functions to obtain and relinquish the global mutex. The ** global mutex is used to protect the unixInodeInfo and -** vxworksFileId objects used by this file, all of which may be +** vxworksFileId objects used by this file, all of which may be ** shared by multiple threads. ** -** Function unixMutexHeld() is used to assert() that the global mutex -** is held when required. This function is only used as part of assert() +** Function unixMutexHeld() is used to assert() that the global mutex +** is held when required. This function is only used as part of assert() ** statements. e.g. ** ** unixEnterMutex() @@ -885,7 +885,7 @@ static int lockTrace(int fd, int op, struct flock *p){ static int robust_ftruncate(int h, sqlite3_int64 sz){ int rc; #ifdef __ANDROID__ - /* On Android, ftruncate() always uses 32-bit offsets, even if + /* On Android, ftruncate() always uses 32-bit offsets, even if ** _FILE_OFFSET_BITS=64 is defined. This means it is unsafe to attempt to ** truncate a file to any size larger than 2GiB. Silently ignore any ** such attempts. */ @@ -901,32 +901,32 @@ static int robust_ftruncate(int h, sqlite3_int64 sz){ ** This routine translates a standard POSIX errno code into something ** useful to the clients of the sqlite3 functions. Specifically, it is ** intended to translate a variety of "try again" errors into SQLITE_BUSY -** and a variety of "please close the file descriptor NOW" errors into +** and a variety of "please close the file descriptor NOW" errors into ** SQLITE_IOERR -** +** ** Errors during initialization of locks, or file system support for locks, ** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately. */ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { - assert( (sqliteIOErr == SQLITE_IOERR_LOCK) || - (sqliteIOErr == SQLITE_IOERR_UNLOCK) || + assert( (sqliteIOErr == SQLITE_IOERR_LOCK) || + (sqliteIOErr == SQLITE_IOERR_UNLOCK) || (sqliteIOErr == SQLITE_IOERR_RDLOCK) || (sqliteIOErr == SQLITE_IOERR_CHECKRESERVEDLOCK) ); switch (posixError) { - case EACCES: + case EACCES: case EAGAIN: case ETIMEDOUT: case EBUSY: case EINTR: - case ENOLCK: - /* random NFS retry error, unless during file system support + case ENOLCK: + /* random NFS retry error, unless during file system support * introspection, in which it actually means what it says */ return SQLITE_BUSY; - - case EPERM: + + case EPERM: return SQLITE_PERM; - - default: + + default: return sqliteIOErr; } } @@ -941,7 +941,7 @@ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { ** ** A pointer to an instance of the following structure can be used as a ** unique file ID in VxWorks. Each instance of this structure contains -** a copy of the canonical filename. There is also a reference count. +** a copy of the canonical filename. There is also a reference count. ** The structure is reclaimed when the number of pointers to it drops to ** zero. ** @@ -957,7 +957,7 @@ struct vxworksFileId { }; #if OS_VXWORKS -/* +/* ** All unique filenames are held on a linked list headed by this ** variable: */ @@ -1029,7 +1029,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ */ unixEnterMutex(); for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){ - if( pCandidate->nName==n + if( pCandidate->nName==n && memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0 ){ sqlite3_free(pNew); @@ -1122,7 +1122,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** cnt>0 means there are cnt shared locks on the file. ** ** Any attempt to lock or unlock a file first checks the locking -** structure. The fcntl() system call is only invoked to set a +** structure. The fcntl() system call is only invoked to set a ** POSIX lock if the internal lock structure transitions between ** a locked and an unlocked state. ** @@ -1131,7 +1131,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** If you close a file descriptor that points to a file that has locks, ** all locks on that file that are owned by the current process are ** released. To work around this problem, each unixInodeInfo object -** maintains a count of the number of pending locks on tha inode. +** maintains a count of the number of pending locks on the inode. ** When an attempt is made to close an unixFile, if there are ** other unixFile open on the same inode that are holding locks, the call ** to close() the file descriptor is deferred until all of the locks clear. @@ -1145,7 +1145,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** not posix compliant. Under LinuxThreads, a lock created by thread ** A cannot be modified or overridden by a different thread B. ** Only thread A can modify the lock. Locking behavior is correct -** if the appliation uses the newer Native Posix Thread Library (NPTL) +** if the application uses the newer Native Posix Thread Library (NPTL) ** on linux - with NPTL a lock created by thread A can override locks ** in thread B. But there is no way to know at compile-time which ** threading library is being used. So there is no way to know at @@ -1155,7 +1155,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** ** SQLite used to support LinuxThreads. But support for LinuxThreads ** was dropped beginning with version 3.7.0. SQLite will still work with -** LinuxThreads provided that (1) there is no more than one connection +** LinuxThreads provided that (1) there is no more than one connection ** per database file in the same process and (2) database connections ** do not move across threads. */ @@ -1172,7 +1172,7 @@ struct unixFileId { /* We are told that some versions of Android contain a bug that ** sizes ino_t at only 32-bits instead of 64-bits. (See ** https://android-review.googlesource.com/#/c/115351/3/dist/sqlite3.c) - ** To work around this, always allocate 64-bits for the inode number. + ** To work around this, always allocate 64-bits for the inode number. ** On small machines that only have 32-bit inodes, this wastes 4 bytes, ** but that should not be a big deal. */ /* WAS: ino_t ino; */ @@ -1260,7 +1260,7 @@ int unixFileMutexNotheld(unixFile *pFile){ ** strerror_r(). ** ** The first argument passed to the macro should be the error code that -** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). +** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). ** The two subsequent arguments should be the name of the OS function that ** failed (e.g. "unlink", "open") and the associated file-system path, ** if any. @@ -1278,7 +1278,7 @@ static int unixLogErrorAtLine( /* If this is not a threadsafe build (SQLITE_THREADSAFE==0), then use ** the strerror() function to obtain the human-readable error message ** equivalent to errno. Otherwise, use strerror_r(). - */ + */ #if SQLITE_THREADSAFE && defined(HAVE_STRERROR_R) char aErr[80]; memset(aErr, 0, sizeof(aErr)); @@ -1286,18 +1286,18 @@ static int unixLogErrorAtLine( /* If STRERROR_R_CHAR_P (set by autoconf scripts) or __USE_GNU is defined, ** assume that the system provides the GNU version of strerror_r() that - ** returns a pointer to a buffer containing the error message. That pointer - ** may point to aErr[], or it may point to some static storage somewhere. - ** Otherwise, assume that the system provides the POSIX version of + ** returns a pointer to a buffer containing the error message. That pointer + ** may point to aErr[], or it may point to some static storage somewhere. + ** Otherwise, assume that the system provides the POSIX version of ** strerror_r(), which always writes an error message into aErr[]. ** ** If the code incorrectly assumes that it is the POSIX version that is ** available, the error message will often be an empty string. Not a - ** huge problem. Incorrectly concluding that the GNU version is available + ** huge problem. Incorrectly concluding that the GNU version is available ** could lead to a segfault though. */ #if defined(STRERROR_R_CHAR_P) || defined(__USE_GNU) - zErr = + zErr = # endif strerror_r(iErrno, aErr, sizeof(aErr)-1); @@ -1347,8 +1347,8 @@ static void storeLastErrno(unixFile *pFile, int error){ } /* -** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. -*/ +** Close all file descriptors accumulated in the unixInodeInfo->pUnused list. +*/ static void closePendingFds(unixFile *pFile){ unixInodeInfo *pInode = pFile->pInode; UnixUnusedFd *p; @@ -1503,7 +1503,7 @@ static int fileHasMoved(unixFile *pFile){ #else struct stat buf; return pFile->pInode!=0 && - (osStat(pFile->zPath, &buf)!=0 + (osStat(pFile->zPath, &buf)!=0 || (u64)buf.st_ino!=pFile->pInode->fileId.ino); #endif } @@ -1584,7 +1584,7 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ } } #endif - + sqlite3_mutex_leave(pFile->pInode->pLockMutex); OSTRACE(("TEST WR-LOCK %d %d %d (unix)\n", pFile->h, rc, reserved)); @@ -1634,7 +1634,7 @@ static int osSetPosixAdvisoryLock( /* -** Attempt to set a system-lock on the file pFile. The lock is +** Attempt to set a system-lock on the file pFile. The lock is ** described by pLock. ** ** If the pFile was opened read/write from unix-excl, then the only lock @@ -1710,7 +1710,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** slightly in order to be compatible with Windows95 systems simultaneously ** accessing the same database file, in case that is ever required. ** - ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved + ** Symbols defined in os.h identify the 'pending byte' and the 'reserved ** byte', each single bytes at well known offsets, and the 'shared byte ** range', a range of 510 bytes at a well known offset. ** @@ -1718,7 +1718,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** byte'. If this is successful, 'shared byte range' is read-locked ** and the lock on the 'pending byte' released. (Legacy note: When ** SQLite was first developed, Windows95 systems were still very common, - ** and Widnows95 lacks a shared-lock capability. So on Windows95, a + ** and Windows95 lacks a shared-lock capability. So on Windows95, a ** single randomly selected by from the 'shared byte range' is locked. ** Windows95 is now pretty much extinct, but this work-around for the ** lack of shared-locks on Windows95 lives on, for backwards @@ -1726,20 +1726,20 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** ** A process may only obtain a RESERVED lock after it has a SHARED lock. ** A RESERVED lock is implemented by grabbing a write-lock on the - ** 'reserved byte'. + ** 'reserved byte'. ** ** An EXCLUSIVE lock may only be requested after either a SHARED or - ** RESERVED lock is held. An EXCLUSIVE lock is implemented by obtaining - ** a write-lock on the entire 'shared byte range'. Since all other locks - ** require a read-lock on one of the bytes within this range, this ensures - ** that no other locks are held on the database. + ** RESERVED lock is held. An EXCLUSIVE lock is implemented by obtaining + ** a write-lock on the entire 'shared byte range'. Since all other locks + ** require a read-lock on one of the bytes within this range, this ensures + ** that no other locks are held on the database. ** ** If a process that holds a RESERVED lock requests an EXCLUSIVE, then - ** a PENDING lock is obtained first. A PENDING lock is implemented by - ** obtaining a write-lock on the 'pending byte'. This ensures that no new - ** SHARED locks can be obtained, but existing SHARED locks are allowed to + ** a PENDING lock is obtained first. A PENDING lock is implemented by + ** obtaining a write-lock on the 'pending byte'. This ensures that no new + ** SHARED locks can be obtained, but existing SHARED locks are allowed to ** persist. If the call to this function fails to obtain the EXCLUSIVE - ** lock in this case, it holds the PENDING lock intead. The client may + ** lock in this case, it holds the PENDING lock instead. The client may ** then re-attempt the EXCLUSIVE lock later on, after existing SHARED ** locks have cleared. */ @@ -1767,7 +1767,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ /* Make sure the locking sequence is correct. ** (1) We never move from unlocked to anything higher than shared lock. - ** (2) SQLite never explicitly requests a pendig lock. + ** (2) SQLite never explicitly requests a pending lock. ** (3) A shared lock is always held when a reserve lock is requested. */ assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); @@ -1782,7 +1782,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ /* If some thread using this PID has a lock via a different unixFile* ** handle that precludes the requested lock, return BUSY. */ - if( (pFile->eFileLock!=pInode->eFileLock && + if( (pFile->eFileLock!=pInode->eFileLock && (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) ){ rc = SQLITE_BUSY; @@ -1793,7 +1793,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** has a SHARED or RESERVED lock, then increment reference counts and ** return SQLITE_OK. */ - if( eFileLock==SHARED_LOCK && + if( eFileLock==SHARED_LOCK && (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ assert( eFileLock==SHARED_LOCK ); assert( pFile->eFileLock==0 ); @@ -1811,7 +1811,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ */ lock.l_len = 1L; lock.l_whence = SEEK_SET; - if( eFileLock==SHARED_LOCK + if( eFileLock==SHARED_LOCK || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLock==RESERVED_LOCK) ){ lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK); @@ -1853,7 +1853,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ if( unixFileLock(pFile, &lock) && rc==SQLITE_OK ){ /* This could happen with a network mount */ tErrno = errno; - rc = SQLITE_IOERR_UNLOCK; + rc = SQLITE_IOERR_UNLOCK; } if( rc ){ @@ -1895,7 +1895,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ } } } - + #ifdef SQLITE_DEBUG /* Set up the transaction-counter change checking flags when @@ -1920,7 +1920,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ end_lock: sqlite3_mutex_leave(pInode->pLockMutex); - OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock), + OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock), rc==SQLITE_OK ? "ok" : "failed")); return rc; } @@ -1945,11 +1945,11 @@ static void setPendingFd(unixFile *pFile){ ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. -** +** ** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED ** the byte range is divided into 2 parts and the first part is unlocked then -** set to a read lock, then the other part is simply unlocked. This works -** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to +** set to a read lock, then the other part is simply unlocked. This works +** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to ** remove the write lock on a region when a read lock is set. */ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ @@ -1987,7 +1987,7 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ /* downgrading to a shared lock on NFS involves clearing the write lock ** before establishing the readlock - to avoid a race condition we downgrade - ** the lock in 2 blocks, so that part of the range will be covered by a + ** the lock in 2 blocks, so that part of the range will be covered by a ** write lock until the rest is covered by a read lock: ** 1: [WWWWW] ** 2: [....W] @@ -2003,7 +2003,7 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ if( handleNFSUnlock ){ int tErrno; /* Error code from system call errors */ off_t divSize = SHARED_SIZE - 1; - + lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; @@ -2045,11 +2045,11 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ lock.l_len = SHARED_SIZE; if( unixFileLock(pFile, &lock) ){ /* In theory, the call to unixFileLock() cannot fail because another - ** process is holding an incompatible lock. If it does, this + ** process is holding an incompatible lock. If it does, this ** indicates that the other process is not following the locking ** protocol. If this happens, return SQLITE_IOERR_RDLOCK. Returning - ** SQLITE_BUSY would confuse the upper layer (in practice it causes - ** an assert to fail). */ + ** SQLITE_BUSY would confuse the upper layer (in practice it causes + ** an assert to fail). */ rc = SQLITE_IOERR_RDLOCK; storeLastErrno(pFile, errno); goto end_unlock; @@ -2125,7 +2125,7 @@ static void unixUnmapfile(unixFile *pFd); #endif /* -** This function performs the parts of the "close file" operation +** This function performs the parts of the "close file" operation ** common to all locking schemes. It closes the directory and file ** handles, if they are valid, and sets all fields of the unixFile ** structure to 0. @@ -2188,7 +2188,7 @@ static int unixClose(sqlite3_file *id){ if( pInode->nLock ){ /* If there are outstanding locks, do not actually close the file just ** yet because that would clear those locks. Instead, add the file - ** descriptor to pInode->pUnused list. It will be automatically closed + ** descriptor to pInode->pUnused list. It will be automatically closed ** when the last lock is cleared. */ setPendingFd(pFile); @@ -2289,7 +2289,7 @@ static int dotlockCheckReservedLock(sqlite3_file *id, int *pResOut) { unixFile *pFile = (unixFile*)id; SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - + assert( pFile ); reserved = osAccess((const char*)pFile->lockingContext, 0)==0; OSTRACE(("TEST WR-LOCK %d %d %d (dotlock)\n", pFile->h, rc, reserved)); @@ -2343,7 +2343,7 @@ static int dotlockLock(sqlite3_file *id, int eFileLock) { #endif return SQLITE_OK; } - + /* grab an exclusive lock */ rc = osMkdir(zLockFile, 0777); if( rc<0 ){ @@ -2358,8 +2358,8 @@ static int dotlockLock(sqlite3_file *id, int eFileLock) { } } return rc; - } - + } + /* got it, set the type and return ok */ pFile->eFileLock = eFileLock; return rc; @@ -2383,7 +2383,7 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { OSTRACE(("UNLOCK %d %d was %d pid=%d (dotlock)\n", pFile->h, eFileLock, pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); - + /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; @@ -2396,7 +2396,7 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { pFile->eFileLock = SHARED_LOCK; return SQLITE_OK; } - + /* To fully unlock the database, delete the lock file */ assert( eFileLock==NO_LOCK ); rc = osRmdir(zLockFile); @@ -2408,7 +2408,7 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { rc = SQLITE_IOERR_UNLOCK; storeLastErrno(pFile, tErrno); } - return rc; + return rc; } pFile->eFileLock = NO_LOCK; return SQLITE_OK; @@ -2455,7 +2455,7 @@ static int robust_flock(int fd, int op){ #else # define robust_flock(a,b) flock(a,b) #endif - + /* ** This routine checks if there is a RESERVED lock held on the specified @@ -2467,16 +2467,16 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ int rc = SQLITE_OK; int reserved = 0; unixFile *pFile = (unixFile*)id; - + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - + assert( pFile ); - + /* Check if a thread in this process holds such a lock */ if( pFile->eFileLock>SHARED_LOCK ){ reserved = 1; } - + /* Otherwise see if some other process holds it. */ if( !reserved ){ /* attempt to get the lock */ @@ -2487,7 +2487,7 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ if ( lrc ) { int tErrno = errno; /* unlock failed with an error */ - lrc = SQLITE_IOERR_UNLOCK; + lrc = SQLITE_IOERR_UNLOCK; storeLastErrno(pFile, tErrno); rc = lrc; } @@ -2495,7 +2495,7 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ int tErrno = errno; reserved = 1; /* someone else might have it reserved */ - lrc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + lrc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); if( IS_LOCK_ERROR(lrc) ){ storeLastErrno(pFile, tErrno); rc = lrc; @@ -2549,15 +2549,15 @@ static int flockLock(sqlite3_file *id, int eFileLock) { assert( pFile ); - /* if we already have a lock, it is exclusive. + /* if we already have a lock, it is exclusive. ** Just adjust level and punt on outta here. */ if (pFile->eFileLock > NO_LOCK) { pFile->eFileLock = eFileLock; return SQLITE_OK; } - + /* grab an exclusive lock */ - + if (robust_flock(pFile->h, LOCK_EX | LOCK_NB)) { int tErrno = errno; /* didn't get, must be busy */ @@ -2569,7 +2569,7 @@ static int flockLock(sqlite3_file *id, int eFileLock) { /* got it, set the type and return ok */ pFile->eFileLock = eFileLock; } - OSTRACE(("LOCK %d %s %s (flock)\n", pFile->h, azFileLock(eFileLock), + OSTRACE(("LOCK %d %s %s (flock)\n", pFile->h, azFileLock(eFileLock), rc==SQLITE_OK ? "ok" : "failed")); #ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS if( (rc & 0xff) == SQLITE_IOERR ){ @@ -2589,23 +2589,23 @@ static int flockLock(sqlite3_file *id, int eFileLock) { */ static int flockUnlock(sqlite3_file *id, int eFileLock) { unixFile *pFile = (unixFile*)id; - + assert( pFile ); OSTRACE(("UNLOCK %d %d was %d pid=%d (flock)\n", pFile->h, eFileLock, pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); - + /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; } - + /* shared can just be set because we always have an exclusive */ if (eFileLock==SHARED_LOCK) { pFile->eFileLock = eFileLock; return SQLITE_OK; } - + /* no, really, unlock. */ if( robust_flock(pFile->h, LOCK_UN) ){ #ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS @@ -2656,14 +2656,14 @@ static int semXCheckReservedLock(sqlite3_file *id, int *pResOut) { unixFile *pFile = (unixFile*)id; SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - + assert( pFile ); /* Check if a thread in this process holds such a lock */ if( pFile->eFileLock>SHARED_LOCK ){ reserved = 1; } - + /* Otherwise see if some other process holds it. */ if( !reserved ){ sem_t *pSem = pFile->pInode->pSem; @@ -2722,14 +2722,14 @@ static int semXLock(sqlite3_file *id, int eFileLock) { sem_t *pSem = pFile->pInode->pSem; int rc = SQLITE_OK; - /* if we already have a lock, it is exclusive. + /* if we already have a lock, it is exclusive. ** Just adjust level and punt on outta here. */ if (pFile->eFileLock > NO_LOCK) { pFile->eFileLock = eFileLock; rc = SQLITE_OK; goto sem_end_lock; } - + /* lock semaphore now but bail out when already locked. */ if( sem_trywait(pSem)==-1 ){ rc = SQLITE_BUSY; @@ -2759,18 +2759,18 @@ static int semXUnlock(sqlite3_file *id, int eFileLock) { OSTRACE(("UNLOCK %d %d was %d pid=%d (sem)\n", pFile->h, eFileLock, pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); - + /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; } - + /* shared can just be set because we always have an exclusive */ if (eFileLock==SHARED_LOCK) { pFile->eFileLock = eFileLock; return SQLITE_OK; } - + /* no, really unlock. */ if ( sem_post(pSem)==-1 ) { int rc, tErrno = errno; @@ -2778,7 +2778,7 @@ static int semXUnlock(sqlite3_file *id, int eFileLock) { if( IS_LOCK_ERROR(rc) ){ storeLastErrno(pFile, tErrno); } - return rc; + return rc; } pFile->eFileLock = NO_LOCK; return SQLITE_OK; @@ -2844,7 +2844,7 @@ struct ByteRangeLockPB2 /* ** This is a utility for setting or clearing a bit-range lock on an ** AFP filesystem. -** +** ** Return SQLITE_OK on success, SQLITE_BUSY on failure. */ static int afpSetLock( @@ -2856,14 +2856,14 @@ static int afpSetLock( ){ struct ByteRangeLockPB2 pb; int err; - + pb.unLockFlag = setLockFlag ? 0 : 1; pb.startEndFlag = 0; pb.offset = offset; - pb.length = length; + pb.length = length; pb.fd = pFile->h; - - OSTRACE(("AFPSETLOCK [%s] for %d%s in range %llx:%llx\n", + + OSTRACE(("AFPSETLOCK [%s] for %d%s in range %llx:%llx\n", (setLockFlag?"ON":"OFF"), pFile->h, (pb.fd==-1?"[testval-1]":""), offset, length)); err = fsctl(path, afpfsByteRangeLock2FSCTL, &pb, 0); @@ -2898,9 +2898,9 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ int reserved = 0; unixFile *pFile = (unixFile*)id; afpLockingContext *context; - + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - + assert( pFile ); context = (afpLockingContext *) pFile->lockingContext; if( context->reserved ){ @@ -2912,12 +2912,12 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ if( pFile->pInode->eFileLock>SHARED_LOCK ){ reserved = 1; } - + /* Otherwise see if some other process holds it. */ if( !reserved ){ /* lock the RESERVED byte */ - int lrc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); + int lrc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); if( SQLITE_OK==lrc ){ /* if we succeeded in taking the reserved lock, unlock it to restore ** the original state */ @@ -2930,10 +2930,10 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ rc=lrc; } } - + sqlite3_mutex_leave(pFile->pInode->pLockMutex); OSTRACE(("TEST WR-LOCK %d %d %d (afp)\n", pFile->h, rc, reserved)); - + *pResOut = reserved; return rc; } @@ -2967,7 +2967,7 @@ static int afpLock(sqlite3_file *id, int eFileLock){ unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode = pFile->pInode; afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; - + assert( pFile ); OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (afp)\n", pFile->h, azFileLock(eFileLock), azFileLock(pFile->eFileLock), @@ -2985,13 +2985,13 @@ static int afpLock(sqlite3_file *id, int eFileLock){ /* Make sure the locking sequence is correct ** (1) We never move from unlocked to anything higher than shared lock. - ** (2) SQLite never explicitly requests a pendig lock. + ** (2) SQLite never explicitly requests a pending lock. ** (3) A shared lock is always held when a reserve lock is requested. */ assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); assert( eFileLock!=PENDING_LOCK ); assert( eFileLock!=RESERVED_LOCK || pFile->eFileLock==SHARED_LOCK ); - + /* This mutex is needed because pFile->pInode is shared across threads */ pInode = pFile->pInode; @@ -3000,18 +3000,18 @@ static int afpLock(sqlite3_file *id, int eFileLock){ /* If some thread using this PID has a lock via a different unixFile* ** handle that precludes the requested lock, return BUSY. */ - if( (pFile->eFileLock!=pInode->eFileLock && + if( (pFile->eFileLock!=pInode->eFileLock && (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) ){ rc = SQLITE_BUSY; goto afp_end_lock; } - + /* If a SHARED lock is requested, and some thread using this PID already ** has a SHARED or RESERVED lock, then increment reference counts and ** return SQLITE_OK. */ - if( eFileLock==SHARED_LOCK && + if( eFileLock==SHARED_LOCK && (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ assert( eFileLock==SHARED_LOCK ); assert( pFile->eFileLock==0 ); @@ -3021,12 +3021,12 @@ static int afpLock(sqlite3_file *id, int eFileLock){ pInode->nLock++; goto afp_end_lock; } - + /* A PENDING lock is needed before acquiring a SHARED lock and before ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will ** be released. */ - if( eFileLock==SHARED_LOCK + if( eFileLock==SHARED_LOCK || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLocknShared==0 ); assert( pInode->eFileLock==0 ); - + mask = (sizeof(long)==8) ? LARGEST_INT64 : 0x7fffffff; /* Now get the read-lock SHARED_LOCK */ /* note that the quality of the randomness doesn't matter that much */ - lk = random(); + lk = random(); pInode->sharedByte = (lk & mask)%(SHARED_SIZE - 1); - lrc1 = afpSetLock(context->dbPath, pFile, + lrc1 = afpSetLock(context->dbPath, pFile, SHARED_FIRST+pInode->sharedByte, 1, 1); if( IS_LOCK_ERROR(lrc1) ){ lrc1Errno = pFile->lastErrno; } /* Drop the temporary PENDING lock */ lrc2 = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); - + if( IS_LOCK_ERROR(lrc1) ) { storeLastErrno(pFile, lrc1Errno); rc = lrc1; @@ -3094,34 +3094,34 @@ static int afpLock(sqlite3_file *id, int eFileLock){ } if (!failed && eFileLock == EXCLUSIVE_LOCK) { /* Acquire an EXCLUSIVE lock */ - - /* Remove the shared lock before trying the range. we'll need to + + /* Remove the shared lock before trying the range. we'll need to ** reestablish the shared lock if we can't get the afpUnlock */ if( !(failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST + pInode->sharedByte, 1, 0)) ){ int failed2 = SQLITE_OK; - /* now attemmpt to get the exclusive lock range */ - failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST, + /* now attempt to get the exclusive lock range */ + failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 1); - if( failed && (failed2 = afpSetLock(context->dbPath, pFile, + if( failed && (failed2 = afpSetLock(context->dbPath, pFile, SHARED_FIRST + pInode->sharedByte, 1, 1)) ){ /* Can't reestablish the shared lock. Sqlite can't deal, this is ** a critical I/O error */ - rc = ((failed & 0xff) == SQLITE_IOERR) ? failed2 : + rc = ((failed & 0xff) == SQLITE_IOERR) ? failed2 : SQLITE_IOERR_LOCK; goto afp_end_lock; - } + } }else{ - rc = failed; + rc = failed; } } if( failed ){ rc = failed; } } - + if( rc==SQLITE_OK ){ pFile->eFileLock = eFileLock; pInode->eFileLock = eFileLock; @@ -3129,10 +3129,10 @@ static int afpLock(sqlite3_file *id, int eFileLock){ pFile->eFileLock = PENDING_LOCK; pInode->eFileLock = PENDING_LOCK; } - + afp_end_lock: sqlite3_mutex_leave(pInode->pLockMutex); - OSTRACE(("LOCK %d %s %s (afp)\n", pFile->h, azFileLock(eFileLock), + OSTRACE(("LOCK %d %s %s (afp)\n", pFile->h, azFileLock(eFileLock), rc==SQLITE_OK ? "ok" : "failed")); return rc; } @@ -3171,7 +3171,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { SimulateIOErrorBenign(1); SimulateIOError( h=(-1) ) SimulateIOErrorBenign(0); - + #ifdef SQLITE_DEBUG /* When reducing a lock such that other processes can start ** reading the database file again, make sure that the @@ -3186,7 +3186,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { || pFile->transCntrChng==1 ); pFile->inNormalWrite = 0; #endif - + if( pFile->eFileLock==EXCLUSIVE_LOCK ){ rc = afpSetLock(context->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 0); if( rc==SQLITE_OK && (eFileLock==SHARED_LOCK || pInode->nShared>1) ){ @@ -3199,11 +3199,11 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { } if( rc==SQLITE_OK && pFile->eFileLock>=PENDING_LOCK ){ rc = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); - } + } if( rc==SQLITE_OK && pFile->eFileLock>=RESERVED_LOCK && context->reserved ){ rc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1, 0); - if( !rc ){ - context->reserved = 0; + if( !rc ){ + context->reserved = 0; } } if( rc==SQLITE_OK && (eFileLock==SHARED_LOCK || pInode->nShared>1)){ @@ -3236,7 +3236,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { if( pInode->nLock==0 ) closePendingFds(pFile); } } - + sqlite3_mutex_leave(pInode->pLockMutex); if( rc==SQLITE_OK ){ pFile->eFileLock = eFileLock; @@ -3245,7 +3245,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { } /* -** Close a file & cleanup AFP specific locking context +** Close a file & cleanup AFP specific locking context */ static int afpClose(sqlite3_file *id) { int rc = SQLITE_OK; @@ -3303,7 +3303,7 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){ /* ** The code above is the NFS lock implementation. The code is specific ** to MacOSX and does not work on other unix platforms. No alternative -** is available. +** is available. ** ********************* End of the NFS lock implementation ********************** ******************************************************************************/ @@ -3311,7 +3311,7 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){ /****************************************************************************** **************** Non-locking sqlite3_file methods ***************************** ** -** The next division contains implementations for all methods of the +** The next division contains implementations for all methods of the ** sqlite3_file object other than the locking methods. The locking ** methods were defined in divisions above (one locking method per ** division). Those methods that are common to all locking modes @@ -3319,7 +3319,7 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){ */ /* -** Seek to the offset passed as the second argument, then read cnt +** Seek to the offset passed as the second argument, then read cnt ** bytes into pBuf. Return the number of bytes actually read. ** ** To avoid stomping the errno value on a failed read the lastErrno value @@ -3375,8 +3375,8 @@ static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){ ** wrong. */ static int unixRead( - sqlite3_file *id, - void *pBuf, + sqlite3_file *id, + void *pBuf, int amt, sqlite3_int64 offset ){ @@ -3391,12 +3391,12 @@ static int unixRead( #if 0 assert( pFile->pPreallocatedUnused==0 || offset>=PENDING_BYTE+512 - || offset+amt<=PENDING_BYTE + || offset+amt<=PENDING_BYTE ); #endif #if SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this read request as possible by transfering + /* Deal with as much of this read request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -3446,7 +3446,7 @@ static int unixRead( /* ** Attempt to seek the file-descriptor passed as the first argument to ** absolute offset iOff, then attempt to write nBuf bytes of data from -** pBuf to it. If an error occurs, return -1 and set *piErrno. Otherwise, +** pBuf to it. If an error occurs, return -1 and set *piErrno. Otherwise, ** return the actual number of bytes written (which may be less than ** nBuf). */ @@ -3506,10 +3506,10 @@ static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ ** or some other error code on failure. */ static int unixWrite( - sqlite3_file *id, - const void *pBuf, + sqlite3_file *id, + const void *pBuf, int amt, - sqlite3_int64 offset + sqlite3_int64 offset ){ unixFile *pFile = (unixFile*)id; int wrote = 0; @@ -3521,7 +3521,7 @@ static int unixWrite( #if 0 assert( pFile->pPreallocatedUnused==0 || offset>=PENDING_BYTE+512 - || offset+amt<=PENDING_BYTE + || offset+amt<=PENDING_BYTE ); #endif @@ -3548,7 +3548,7 @@ static int unixWrite( #endif #if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this write request as possible by transfering + /* Deal with as much of this write request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -3563,7 +3563,7 @@ static int unixWrite( } } #endif - + while( (wrote = seekAndWrite(pFile, offset, pBuf, amt))0 ){ amt -= wrote; offset += wrote; @@ -3629,8 +3629,8 @@ int sqlite3_fullsync_count = 0; ** ** SQLite sets the dataOnly flag if the size of the file is unchanged. ** The idea behind dataOnly is that it should only write the file content -** to disk, not the inode. We only set dataOnly if the file size is -** unchanged since the file size is part of the inode. However, +** to disk, not the inode. We only set dataOnly if the file size is +** unchanged since the file size is part of the inode. However, ** Ted Ts'o tells us that fdatasync() will also write the inode if the ** file size has changed. The only real difference between fdatasync() ** and fsync(), Ted tells us, is that fdatasync() will not flush the @@ -3644,7 +3644,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ int rc; /* The following "ifdef/elif/else/" block has the same structure as - ** the one below. It is replicated here solely to avoid cluttering + ** the one below. It is replicated here solely to avoid cluttering ** up the real code with the UNUSED_PARAMETER() macros. */ #ifdef SQLITE_NO_SYNC @@ -3658,7 +3658,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ UNUSED_PARAMETER(dataOnly); #endif - /* Record the number of times that we do a normal fsync() and + /* Record the number of times that we do a normal fsync() and ** FULLSYNC. This is used during testing to verify that this procedure ** gets called with the correct arguments. */ @@ -3670,7 +3670,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a ** no-op. But go ahead and call fstat() to validate the file ** descriptor as we need a method to provoke a failure during - ** coverate testing. + ** coverage testing. */ #ifdef SQLITE_NO_SYNC { @@ -3684,11 +3684,11 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ rc = 1; } /* If the FULLFSYNC failed, fall back to attempting an fsync(). - ** It shouldn't be possible for fullfsync to fail on the local + ** It shouldn't be possible for fullfsync to fail on the local ** file system (on OSX), so failure indicates that FULLFSYNC - ** isn't supported for this file system. So, attempt an fsync - ** and (for now) ignore the overhead of a superfluous fcntl call. - ** It'd be better to detect fullfsync support once and avoid + ** isn't supported for this file system. So, attempt an fsync + ** and (for now) ignore the overhead of a superfluous fcntl call. + ** It'd be better to detect fullfsync support once and avoid ** the fcntl call every time sync is called. */ if( rc ) rc = fsync(fd); @@ -3698,7 +3698,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ ** so currently we default to the macro that redefines fdatasync to fsync */ rc = fsync(fd); -#else +#else rc = fdatasync(fd); #if OS_VXWORKS if( rc==-1 && errno==ENOTSUP ){ @@ -3859,7 +3859,7 @@ static int unixTruncate(sqlite3_file *id, i64 nByte){ #if SQLITE_MAX_MMAP_SIZE>0 /* If the file was just truncated to a size smaller than the currently ** mapped region, reduce the effective mapping size as well. SQLite will - ** use read() and write() to access data beyond this point from now on. + ** use read() and write() to access data beyond this point from now on. */ if( nBytemmapSize ){ pFile->mmapSize = nByte; @@ -3905,8 +3905,8 @@ static int unixFileSize(sqlite3_file *id, i64 *pSize){ static int proxyFileControl(sqlite3_file*,int,void*); #endif -/* -** This function is called to handle the SQLITE_FCNTL_SIZE_HINT +/* +** This function is called to handle the SQLITE_FCNTL_SIZE_HINT ** file-control operation. Enlarge the database to nBytes in size ** (rounded up to the next chunk-size). If the database is already ** nBytes or larger, this routine is a no-op. @@ -3915,7 +3915,7 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ if( pFile->szChunk>0 ){ i64 nSize; /* Required file size */ struct stat buf; /* Used to hold return values of fstat() */ - + if( osFstat(pFile->h, &buf) ){ return SQLITE_IOERR_FSTAT; } @@ -3924,8 +3924,8 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ if( nSize>(i64)buf.st_size ){ #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE - /* The code below is handling the return value of osFallocate() - ** correctly. posix_fallocate() is defined to "returns zero on success, + /* The code below is handling the return value of osFallocate() + ** correctly. posix_fallocate() is defined to "returns zero on success, ** or an error number on failure". See the manpage for details. */ int err; do{ @@ -3933,7 +3933,7 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ }while( err==EINTR ); if( err && err!=EINVAL ) return SQLITE_IOERR_WRITE; #else - /* If the OS does not have posix_fallocate(), fake it. Write a + /* If the OS does not have posix_fallocate(), fake it. Write a ** single byte to the last byte in each block that falls entirely ** within the extended region. Then, if required, a single byte ** at offset (nSize-1), to set the size of the file correctly. @@ -4126,9 +4126,9 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ /* ** If pFd->sectorSize is non-zero when this function is called, it is a -** no-op. Otherwise, the values of pFd->sectorSize and -** pFd->deviceCharacteristics are set according to the file-system -** characteristics. +** no-op. Otherwise, the values of pFd->sectorSize and +** pFd->deviceCharacteristics are set according to the file-system +** characteristics. ** ** There are two versions of this function. One for QNX and one for all ** other systems. @@ -4162,7 +4162,7 @@ static void setDeviceCharacteristics(unixFile *pFd){ static void setDeviceCharacteristics(unixFile *pFile){ if( pFile->sectorSize == 0 ){ struct statvfs fsInfo; - + /* Set defaults for non-supported filesystems */ pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; pFile->deviceCharacteristics = 0; @@ -4271,7 +4271,7 @@ static int unixDeviceCharacteristics(sqlite3_file *id){ /* ** Return the system page size. ** -** This function should not be called directly by other code in this file. +** This function should not be called directly by other code in this file. ** Instead, it should be called via macro osGetpagesize(). */ static int unixGetpagesize(void){ @@ -4289,7 +4289,7 @@ static int unixGetpagesize(void){ #ifndef SQLITE_OMIT_WAL /* -** Object used to represent an shared memory buffer. +** Object used to represent an shared memory buffer. ** ** When multiple threads all reference the same wal-index, each thread ** has its own unixShm object, but they all point to a single instance @@ -4309,7 +4309,7 @@ static int unixGetpagesize(void){ ** nRef ** ** The following fields are read-only after the object is created: -** +** ** hShm ** zFilename ** @@ -4368,7 +4368,7 @@ struct unixShm { /* ** Use F_GETLK to check whether or not there are any readers with open ** wal-mode transactions in other processes on database file pFile. If -** no error occurs, return SQLITE_OK and set (*piOut) to 1 if there are +** no error occurs, return SQLITE_OK and set (*piOut) to 1 if there are ** such transactions, or 0 otherwise. If an error occurs, return an ** SQLite error code. The final value of *piOut is undefined in this ** case. @@ -4478,7 +4478,7 @@ static int unixShmSystemLock( } #endif - return rc; + return rc; } /* @@ -4534,7 +4534,7 @@ static void unixShmPurge(unixFile *pFd){ ** take it now. Return SQLITE_OK if successful, or an SQLite error ** code otherwise. ** -** If the DMS cannot be locked because this is a readonly_shm=1 +** If the DMS cannot be locked because this is a readonly_shm=1 ** connection and no other process already holds a lock, return ** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1. */ @@ -4545,7 +4545,7 @@ static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ /* Use F_GETLK to determine the locks other processes are holding ** on the DMS byte. If it indicates that another process is holding ** a SHARED lock, then this process may also take a SHARED lock - ** and proceed with opening the *-shm file. + ** and proceed with opening the *-shm file. ** ** Or, if no other process is holding any lock, then this process ** is the first to open it. In this case take an EXCLUSIVE lock on the @@ -4593,20 +4593,20 @@ static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ } /* -** Open a shared-memory area associated with open database file pDbFd. +** Open a shared-memory area associated with open database file pDbFd. ** This particular implementation uses mmapped files. ** ** The file used to implement shared-memory is in the same directory ** as the open database file and has the same name as the open database ** file with the "-shm" suffix added. For example, if the database file ** is "/home/user1/config.db" then the file that is created and mmapped -** for shared memory will be called "/home/user1/config.db-shm". +** for shared memory will be called "/home/user1/config.db-shm". ** ** Another approach to is to use files in /dev/shm or /dev/tmp or an ** some other tmpfs mount. But if a file in a different directory ** from the database file is used, then differing access permissions ** or a chroot() might cause two different processes on the same -** database to end up using different files for shared memory - +** database to end up using different files for shared memory - ** meaning that their memory would not really be shared - resulting ** in database corruption. Nevertheless, this tmpfs file usage ** can be enabled at compile-time using -DSQLITE_SHM_DIRECTORY="/dev/shm" @@ -4676,7 +4676,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ memset(pShmNode, 0, sizeof(*pShmNode)+nShmFilename); zShm = pShmNode->zFilename = (char*)&pShmNode[1]; #ifdef SQLITE_SHM_DIRECTORY - sqlite3_snprintf(nShmFilename, zShm, + sqlite3_snprintf(nShmFilename, zShm, SQLITE_SHM_DIRECTORY "/sqlite-shm-%x-%x", (u32)sStat.st_ino, (u32)sStat.st_dev); #else @@ -4751,22 +4751,22 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ } /* -** This function is called to obtain a pointer to region iRegion of the -** shared-memory associated with the database file fd. Shared-memory regions -** are numbered starting from zero. Each shared-memory region is szRegion +** This function is called to obtain a pointer to region iRegion of the +** shared-memory associated with the database file fd. Shared-memory regions +** are numbered starting from zero. Each shared-memory region is szRegion ** bytes in size. ** ** If an error occurs, an error code is returned and *pp is set to NULL. ** ** Otherwise, if the bExtend parameter is 0 and the requested shared-memory ** region has not been allocated (by any client, including one running in a -** separate process), then *pp is set to NULL and SQLITE_OK returned. If -** bExtend is non-zero and the requested shared-memory region has not yet +** separate process), then *pp is set to NULL and SQLITE_OK returned. If +** bExtend is non-zero and the requested shared-memory region has not yet ** been allocated, it is allocated by this function. ** ** If the shared-memory region has already been allocated or is allocated by -** this call as described above, then it is mapped into this processes -** address space (if it is not already), *pp is set to point to the mapped +** this call as described above, then it is mapped into this processes +** address space (if it is not already), *pp is set to point to the mapped ** memory and SQLITE_OK returned. */ static int unixShmMap( @@ -4821,7 +4821,7 @@ static int unixShmMap( rc = SQLITE_IOERR_SHMSIZE; goto shmpage_out; } - + if( sStat.st_sizehShm>=0 ){ pMem = osMmap(0, nMap, - pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, + pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, MAP_SHARED, pShmNode->hShm, szRegion*(i64)pShmNode->nRegion ); if( pMem==MAP_FAILED ){ @@ -4984,7 +4984,7 @@ static int unixShmLock( ** ** In other words, if this is a blocking lock, none of the locks that ** occur later in the above list than the lock being obtained may be - ** held. + ** held. ** ** It is not permitted to block on the RECOVER lock. */ @@ -5026,7 +5026,7 @@ static int unixShmLock( if( rc==SQLITE_OK ){ p->exclMask &= ~mask; p->sharedMask &= ~mask; - } + } } }else if( flags & SQLITE_SHM_SHARED ){ assert( n==1 ); @@ -5077,7 +5077,7 @@ static int unixShmLock( } /* -** Implement a memory barrier or memory fence on shared memory. +** Implement a memory barrier or memory fence on shared memory. ** ** All loads and stores begun before the barrier must complete before ** any load or store begun after the barrier. @@ -5087,15 +5087,15 @@ static void unixShmBarrier( ){ UNUSED_PARAMETER(fd); sqlite3MemoryBarrier(); /* compiler-defined memory barrier */ - assert( fd->pMethods->xLock==nolockLock - || unixFileMutexNotheld((unixFile*)fd) + assert( fd->pMethods->xLock==nolockLock + || unixFileMutexNotheld((unixFile*)fd) ); unixEnterMutex(); /* Also mutex, for redundancy */ unixLeaveMutex(); } /* -** Close a connection to shared-memory. Delete the underlying +** Close a connection to shared-memory. Delete the underlying ** storage if deleteFlag is true. ** ** If there is no shared memory associated with the connection then this @@ -5169,7 +5169,7 @@ static void unixUnmapfile(unixFile *pFd){ } /* -** Attempt to set the size of the memory mapping maintained by file +** Attempt to set the size of the memory mapping maintained by file ** descriptor pFd to nNew bytes. Any existing mapping is discarded. ** ** If successful, this function sets the following variables: @@ -5261,14 +5261,14 @@ static void unixRemapfile( /* ** Memory map or remap the file opened by file-descriptor pFd (if the file -** is already mapped, the existing mapping is replaced by the new). Or, if -** there already exists a mapping for this file, and there are still +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still ** outstanding xFetch() references to it, this function is a no-op. ** -** If parameter nByte is non-negative, then it is the requested size of -** the mapping to create. Otherwise, if nByte is less than zero, then the +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the ** requested size is the size of the file on disk. The actual size of the -** created mapping is either the requested size or the value configured +** created mapping is either the requested size or the value configured ** using SQLITE_FCNTL_MMAP_LIMIT, whichever is smaller. ** ** SQLITE_OK is returned if no error occurs (even if the mapping is not @@ -5309,7 +5309,7 @@ static int unixMapfile(unixFile *pFd, i64 nMap){ ** Finally, if an error does occur, return an SQLite error code. The final ** value of *pp is undefined in this case. ** -** If this function does return a pointer, the caller must eventually +** If this function does return a pointer, the caller must eventually ** release the reference by calling unixUnfetch(). */ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ @@ -5334,13 +5334,13 @@ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ } /* -** If the third argument is non-NULL, then this function releases a +** If the third argument is non-NULL, then this function releases a ** reference obtained by an earlier call to unixFetch(). The second ** argument passed to this function must be the same as the corresponding -** argument that was passed to the unixFetch() invocation. +** argument that was passed to the unixFetch() invocation. ** -** Or, if the third argument is NULL, then this function is being called -** to inform the VFS layer that, according to POSIX, any existing mapping +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping ** may now be invalid and should be unmapped. */ static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){ @@ -5348,7 +5348,7 @@ static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){ unixFile *pFd = (unixFile *)fd; /* The underlying database file */ UNUSED_PARAMETER(iOff); - /* If p==0 (unmap the entire file) then there must be no outstanding + /* If p==0 (unmap the entire file) then there must be no outstanding ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), ** then there must be at least one outstanding. */ assert( (p==0)==(pFd->nFetchOut==0) ); @@ -5556,8 +5556,8 @@ IOMETHODS( #endif #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE -/* -** This "finder" function attempts to determine the best locking strategy +/* +** This "finder" function attempts to determine the best locking strategy ** for the database file "filePath". It then returns the sqlite3_io_methods ** object that implements that strategy. ** @@ -5599,8 +5599,8 @@ static const sqlite3_io_methods *autolockIoFinderImpl( } /* Default case. Handles, amongst others, "nfs". - ** Test byte-range lock using fcntl(). If the call succeeds, - ** assume that the file-system supports POSIX style locks. + ** Test byte-range lock using fcntl(). If the call succeeds, + ** assume that the file-system supports POSIX style locks. */ lockInfo.l_len = 1; lockInfo.l_start = 0; @@ -5616,7 +5616,7 @@ static const sqlite3_io_methods *autolockIoFinderImpl( return &dotlockIoMethods; } } -static const sqlite3_io_methods +static const sqlite3_io_methods *(*const autolockIoFinder)(const char*,unixFile*) = autolockIoFinderImpl; #endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ @@ -5652,7 +5652,7 @@ static const sqlite3_io_methods *vxworksIoFinderImpl( return &semIoMethods; } } -static const sqlite3_io_methods +static const sqlite3_io_methods *(*const vxworksIoFinder)(const char*,unixFile*) = vxworksIoFinderImpl; #endif /* OS_VXWORKS */ @@ -5780,14 +5780,14 @@ static int fillInUnixFile( robust_close(pNew, h, __LINE__); h = -1; } - unixLeaveMutex(); + unixLeaveMutex(); } } #endif else if( pLockingStyle == &dotlockIoMethods ){ /* Dotfile locking uses the file path so it needs to be included in - ** the dotlockLockingContext + ** the dotlockLockingContext */ char *zLockFile; int nFilename; @@ -5825,7 +5825,7 @@ static int fillInUnixFile( unixLeaveMutex(); } #endif - + storeLastErrno(pNew, 0); #if OS_VXWORKS if( rc!=SQLITE_OK ){ @@ -5900,7 +5900,7 @@ static int unixGetTempname(int nBuf, char *zBuf){ /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this - ** function failing. + ** function failing. */ zBuf[0] = 0; SimulateIOError( return SQLITE_IOERR ); @@ -5937,7 +5937,7 @@ static int proxyTransformUnixFile(unixFile*, const char*); #endif /* -** Search for an unused file descriptor that was opened on the database +** Search for an unused file descriptor that was opened on the database ** file (not a journal or super-journal file) identified by pathname ** zPath with SQLITE_OPEN_XXX flags matching those passed as the second ** argument to this function. @@ -5946,7 +5946,7 @@ static int proxyTransformUnixFile(unixFile*, const char*); ** but the associated file descriptor could not be closed because some ** other file descriptor open on the same file is holding a file-lock. ** Refer to comments in the unixClose() function and the lengthy comment -** describing "Posix Advisory Locking" at the start of this file for +** describing "Posix Advisory Locking" at the start of this file for ** further details. Also, ticket #4018. ** ** If a suitable file descriptor is found, then it is returned. If no @@ -5957,8 +5957,8 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ /* Do not search for an unused file descriptor on vxworks. Not because ** vxworks would not benefit from the change (it might, we're not sure), - ** but because no way to test it is currently available. It is better - ** not to risk breaking vxworks support for the sake of such an obscure + ** but because no way to test it is currently available. It is better + ** not to risk breaking vxworks support for the sake of such an obscure ** feature. */ #if !OS_VXWORKS struct stat sStat; /* Results of stat() call */ @@ -6000,7 +6000,7 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ } /* -** Find the mode, uid and gid of file zFile. +** Find the mode, uid and gid of file zFile. */ static int getFileMode( const char *zFile, /* File name */ @@ -6024,16 +6024,16 @@ static int getFileMode( ** This function is called by unixOpen() to determine the unix permissions ** to create new files with. If no error occurs, then SQLITE_OK is returned ** and a value suitable for passing as the third argument to open(2) is -** written to *pMode. If an IO error occurs, an SQLite error code is +** written to *pMode. If an IO error occurs, an SQLite error code is ** returned and the value of *pMode is not modified. ** ** In most cases, this routine sets *pMode to 0, which will become ** an indication to robust_open() to create the file using ** SQLITE_DEFAULT_FILE_PERMISSIONS adjusted by the umask. -** But if the file being opened is a WAL or regular journal file, then -** this function queries the file-system for the permissions on the -** corresponding database file and sets *pMode to this value. Whenever -** possible, WAL and journal files are created using the same permissions +** But if the file being opened is a WAL or regular journal file, then +** this function queries the file-system for the permissions on the +** corresponding database file and sets *pMode to this value. Whenever +** possible, WAL and journal files are created using the same permissions ** as the associated database file. ** ** If the SQLITE_ENABLE_8_3_NAMES option is enabled, then the @@ -6065,7 +6065,7 @@ static int findCreateFileMode( ** "-journalNN" ** "-walNN" ** - ** where NN is a decimal number. The NN naming schemes are + ** where NN is a decimal number. The NN naming schemes are ** used by the test_multiplex.c module. ** ** In normal operation, the journal file name will always contain @@ -6101,7 +6101,7 @@ static int findCreateFileMode( /* ** Open the file zPath. -** +** ** Previously, the SQLite OS layer used three functions in place of this ** one: ** @@ -6112,13 +6112,13 @@ static int findCreateFileMode( ** These calls correspond to the following combinations of flags: ** ** ReadWrite() -> (READWRITE | CREATE) -** ReadOnly() -> (READONLY) +** ReadOnly() -> (READONLY) ** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE) ** ** The old OpenExclusive() accepted a boolean argument - "delFlag". If ** true, the file was configured to be automatically deleted when the -** file handle closed. To achieve the same effect using this new -** interface, add the DELETEONCLOSE flag to those specified above for +** file handle closed. To achieve the same effect using this new +** interface, add the DELETEONCLOSE flag to those specified above for ** OpenExclusive(). */ static int unixOpen( @@ -6153,8 +6153,8 @@ static int unixOpen( ** is called the directory file descriptor will be fsync()ed and close()d. */ int isNewJrnl = (isCreate && ( - eType==SQLITE_OPEN_SUPER_JOURNAL - || eType==SQLITE_OPEN_MAIN_JOURNAL + eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_WAL )); @@ -6164,9 +6164,9 @@ static int unixOpen( char zTmpname[MAX_PATHNAME+2]; const char *zName = zPath; - /* Check the following statements are true: + /* Check the following statements are true: ** - ** (a) Exactly one of the READWRITE and READONLY flags must be set, and + ** (a) Exactly one of the READWRITE and READONLY flags must be set, and ** (b) if CREATE is set, then READWRITE must also be set, and ** (c) if EXCLUSIVE is set, then CREATE must also be set. ** (d) if DELETEONCLOSE is set, then CREATE must also be set. @@ -6176,7 +6176,7 @@ static int unixOpen( assert(isExclusive==0 || isCreate); assert(isDelete==0 || isCreate); - /* The main DB, main journal, WAL file and super-journal are never + /* The main DB, main journal, WAL file and super-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 ); @@ -6184,9 +6184,9 @@ static int unixOpen( 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_SUPER_JOURNAL + assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL ); @@ -6240,7 +6240,7 @@ static int unixOpen( /* Determine the value of the flags parameter passed to POSIX function ** open(). These must be calculated even if open() is not called, as - ** they may be stored as part of the file handle and used by the + ** they may be stored as part of the file handle and used by the ** 'conch file' locking functions later on. */ if( isReadonly ) openFlags |= O_RDONLY; if( isReadWrite ) openFlags |= O_RDWR; @@ -6305,7 +6305,7 @@ static int unixOpen( if( p->pPreallocatedUnused ){ p->pPreallocatedUnused->fd = fd; - p->pPreallocatedUnused->flags = + p->pPreallocatedUnused->flags = flags & (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE); } @@ -6327,7 +6327,7 @@ static int unixOpen( p->openFlags = openFlags; } #endif - + #if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE if( fstatfs(fd, &fsInfo) == -1 ){ storeLastErrno(p, errno); @@ -6358,7 +6358,7 @@ static int unixOpen( char *envforce = getenv("SQLITE_FORCE_PROXY_LOCKING"); int useProxy = 0; - /* SQLITE_FORCE_PROXY_LOCKING==1 means force always use proxy, 0 means + /* SQLITE_FORCE_PROXY_LOCKING==1 means force always use proxy, 0 means ** never use proxy, NULL means use proxy for non-local files only. */ if( envforce!=NULL ){ useProxy = atoi(envforce)>0; @@ -6370,9 +6370,9 @@ static int unixOpen( if( rc==SQLITE_OK ){ rc = proxyTransformUnixFile((unixFile*)pFile, ":auto:"); if( rc!=SQLITE_OK ){ - /* Use unixClose to clean up the resources added in fillInUnixFile - ** and clear all the structure's references. Specifically, - ** pFile->pMethods will be NULL so sqlite3OsClose will be a no-op + /* Use unixClose to clean up the resources added in fillInUnixFile + ** and clear all the structure's references. Specifically, + ** pFile->pMethods will be NULL so sqlite3OsClose will be a no-op */ unixClose(pFile); return rc; @@ -6382,9 +6382,9 @@ static int unixOpen( } } #endif - - assert( zPath==0 || zPath[0]=='/' - || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL + + assert( zPath==0 || zPath[0]=='/' + || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL ); rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); @@ -6569,9 +6569,9 @@ static void appendAllPathElements( /* ** Turn a relative pathname into a full pathname. The relative path ** is stored as a nul-terminated string in the buffer pointed to by -** zPath. +** zPath. ** -** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes +** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes ** (in this case, MAX_PATHNAME bytes). The full-path is written to ** this buffer before returning. */ @@ -6631,7 +6631,7 @@ static void unixDlError(sqlite3_vfs *NotUsed, int nBuf, char *zBufOut){ unixLeaveMutex(); } static void (*unixDlSym(sqlite3_vfs *NotUsed, void *p, const char*zSym))(void){ - /* + /* ** GCC with -pedantic-errors says that C90 does not allow a void* to be ** cast into a pointer to a function. And yet the library dlsym() routine ** returns a void* which is really a pointer to a function. So how do we @@ -6641,7 +6641,7 @@ static void (*unixDlSym(sqlite3_vfs *NotUsed, void *p, const char*zSym))(void){ ** parameters void* and const char* and returning a pointer to a function. ** We initialize x by assigning it a pointer to the dlsym() function. ** (That assignment requires a cast.) Then we call the function that - ** x points to. + ** x points to. ** ** This work-around is unlikely to work correctly on any system where ** you really cannot cast a function pointer into void*. But then, on the @@ -6684,7 +6684,7 @@ static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ ** tests repeatable. */ memset(zBuf, 0, nBuf); - randomnessPid = osGetpid(0); + randomnessPid = osGetpid(0); #if !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS) { int fd, got; @@ -6715,12 +6715,17 @@ static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ ** than the argument. */ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){ -#if OS_VXWORKS || _POSIX_C_SOURCE >= 199309L +#if !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP+0 struct timespec sp; - sp.tv_sec = microseconds / 1000000; sp.tv_nsec = (microseconds % 1000000) * 1000; + + /* Almost all modern unix systems support nanosleep(). But if you are + ** compiling for one of the rare exceptions, you can use + ** -DHAVE_NANOSLEEP=0 (perhaps in conjuction with -DHAVE_USLEEP if + ** usleep() is available) in order to bypass the use of nanosleep() */ nanosleep(&sp, NULL); + UNUSED_PARAMETER(NotUsed); return microseconds; #elif defined(HAVE_USLEEP) && HAVE_USLEEP @@ -6752,7 +6757,7 @@ int sqlite3_current_time = 0; /* Fake system time in seconds since 1970. */ ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date ** cannot be found. */ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ @@ -6859,7 +6864,7 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** To address the performance and cache coherency issues, proxy file locking ** changes the way database access is controlled by limiting access to a ** single host at a time and moving file locks off of the database file -** and onto a proxy file on the local file system. +** and onto a proxy file on the local file system. ** ** ** Using proxy locks @@ -6885,19 +6890,19 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** actual proxy file name is generated from the name and path of the ** database file. For example: ** -** For database path "/Users/me/foo.db" +** For database path "/Users/me/foo.db" ** The lock path will be "/sqliteplocks/_Users_me_foo.db:auto:") ** ** Once a lock proxy is configured for a database connection, it can not ** be removed, however it may be switched to a different proxy path via ** the above APIs (assuming the conch file is not being held by another -** connection or process). +** connection or process). ** ** ** How proxy locking works ** ----------------------- ** -** Proxy file locking relies primarily on two new supporting files: +** Proxy file locking relies primarily on two new supporting files: ** ** * conch file to limit access to the database file to a single host ** at a time @@ -6924,11 +6929,11 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** host (the conch ensures that they all use the same local lock file). ** ** Requesting the lock proxy does not immediately take the conch, it is -** only taken when the first request to lock database file is made. +** only taken when the first request to lock database file is made. ** This matches the semantics of the traditional locking behavior, where ** opening a connection to a database file does not take a lock on it. -** The shared lock and an open file descriptor are maintained until -** the connection to the database is closed. +** The shared lock and an open file descriptor are maintained until +** the connection to the database is closed. ** ** The proxy file and the lock file are never deleted so they only need ** to be created the first time they are used. @@ -6942,7 +6947,7 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** automatically configured for proxy locking, lock files are ** named automatically using the same logic as ** PRAGMA lock_proxy_file=":auto:" -** +** ** SQLITE_PROXY_DEBUG ** ** Enables the logging of error messages during host id file @@ -6957,8 +6962,8 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** ** Permissions to use when creating a directory for storing the ** lock proxy files, only used when LOCKPROXYDIR is not set. -** -** +** +** ** As mentioned above, when compiled with SQLITE_PREFER_PROXY_LOCKING, ** setting the environment variable SQLITE_FORCE_PROXY_LOCKING to 1 will ** force proxy locking to be used for every database file opened, and 0 @@ -6968,12 +6973,12 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ */ /* -** Proxy locking is only available on MacOSX +** Proxy locking is only available on MacOSX */ #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE /* -** The proxyLockingContext has the path and file structures for the remote +** The proxyLockingContext has the path and file structures for the remote ** and local proxy files in it */ typedef struct proxyLockingContext proxyLockingContext; @@ -6989,10 +6994,10 @@ struct proxyLockingContext { sqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */ }; -/* -** The proxy lock file path for the database at dbPath is written into lPath, +/* +** The proxy lock file path for the database at dbPath is written into lPath, ** which must point to valid, writable memory large enough for a maxLen length -** file path. +** file path. */ static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ int len; @@ -7009,7 +7014,7 @@ static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ lPath, errno, osGetpid(0))); return SQLITE_IOERR_LOCK; } - len = strlcat(lPath, "sqliteplocks", maxLen); + len = strlcat(lPath, "sqliteplocks", maxLen); } # else len = strlcpy(lPath, "/tmp/", maxLen); @@ -7019,7 +7024,7 @@ static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ if( lPath[len-1]!='/' ){ len = strlcat(lPath, "/", maxLen); } - + /* transform the db path to a unique cache name */ dbLen = (int)strlen(dbPath); for( i=0; i 0) ){ /* only mkdir if leaf dir != "." or "/" or ".." */ - if( i-start>2 || (i-start==1 && buf[start] != '.' && buf[start] != '/') + if( i-start>2 || (i-start==1 && buf[start] != '.' && buf[start] != '/') || (i-start==2 && buf[start] != '.' && buf[start+1] != '.') ){ buf[i]='\0'; if( osMkdir(buf, SQLITE_DEFAULT_PROXYDIR_PERMISSIONS) ){ @@ -7124,13 +7129,13 @@ static int proxyCreateUnixFile( switch (terrno) { case EACCES: return SQLITE_PERM; - case EIO: + case EIO: return SQLITE_IOERR_LOCK; /* even though it is the conch */ default: return SQLITE_CANTOPEN_BKPT; } } - + pNew = (unixFile *)sqlite3_malloc64(sizeof(*pNew)); if( pNew==NULL ){ rc = SQLITE_NOMEM_BKPT; @@ -7144,13 +7149,13 @@ static int proxyCreateUnixFile( pUnused->fd = fd; pUnused->flags = openFlags; pNew->pPreallocatedUnused = pUnused; - + rc = fillInUnixFile(&dummyVfs, fd, (sqlite3_file*)pNew, path, 0); if( rc==SQLITE_OK ){ *ppFile = pNew; return SQLITE_OK; } -end_create_proxy: +end_create_proxy: robust_close(pNew, fd, __LINE__); sqlite3_free(pNew); sqlite3_free(pUnused); @@ -7169,7 +7174,7 @@ int sqlite3_hostid_num = 0; extern int gethostuuid(uuid_t id, const struct timespec *wait); #endif -/* get the host ID via gethostuuid(), pHostID must point to PROXY_HOSTIDLEN +/* get the host ID via gethostuuid(), pHostID must point to PROXY_HOSTIDLEN ** bytes of writable memory. */ static int proxyGetHostID(unsigned char *pHostID, int *pError){ @@ -7195,7 +7200,7 @@ static int proxyGetHostID(unsigned char *pHostID, int *pError){ pHostID[0] = (char)(pHostID[0] + (char)(sqlite3_hostid_num & 0xFF)); } #endif - + return SQLITE_OK; } @@ -7206,14 +7211,14 @@ static int proxyGetHostID(unsigned char *pHostID, int *pError){ #define PROXY_PATHINDEX (PROXY_HEADERLEN+PROXY_HOSTIDLEN) #define PROXY_MAXCONCHLEN (PROXY_HEADERLEN+PROXY_HOSTIDLEN+MAXPATHLEN) -/* -** Takes an open conch file, copies the contents to a new path and then moves +/* +** Takes an open conch file, copies the contents to a new path and then moves ** it back. The newly created file's file descriptor is assigned to the -** conch file structure and finally the original conch file descriptor is +** conch file structure and finally the original conch file descriptor is ** closed. Returns zero if successful. */ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ - proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; unixFile *conchFile = pCtx->conchFile; char tPath[MAXPATHLEN]; char buf[PROXY_MAXCONCHLEN]; @@ -7227,7 +7232,7 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ /* create a new path by replace the trailing '-conch' with '-break' */ pathLen = strlcpy(tPath, cPath, MAXPATHLEN); - if( pathLen>MAXPATHLEN || pathLen<6 || + if( pathLen>MAXPATHLEN || pathLen<6 || (strlcpy(&tPath[pathLen-5], "break", 6) != 5) ){ sqlite3_snprintf(sizeof(errmsg),errmsg,"path error (len %d)",(int)pathLen); goto end_breaklock; @@ -7269,24 +7274,24 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ return rc; } -/* Take the requested lock on the conch file and break a stale lock if the +/* Take the requested lock on the conch file and break a stale lock if the ** host id matches. */ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ - proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; unixFile *conchFile = pCtx->conchFile; int rc = SQLITE_OK; int nTries = 0; struct timespec conchModTime; - + memset(&conchModTime, 0, sizeof(conchModTime)); do { rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType); nTries ++; if( rc==SQLITE_BUSY ){ /* If the lock failed (busy): - * 1st try: get the mod time of the conch, wait 0.5s and try again. - * 2nd try: fail if the mod time changed or host id is different, wait + * 1st try: get the mod time of the conch, wait 0.5s and try again. + * 2nd try: fail if the mod time changed or host id is different, wait * 10 sec and try again * 3rd try: break the lock unless the mod time has changed. */ @@ -7295,20 +7300,20 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ storeLastErrno(pFile, errno); return SQLITE_IOERR_LOCK; } - + if( nTries==1 ){ conchModTime = buf.st_mtimespec; unixSleep(0,500000); /* wait 0.5 sec and try the lock again*/ - continue; + continue; } assert( nTries>1 ); - if( conchModTime.tv_sec != buf.st_mtimespec.tv_sec || + if( conchModTime.tv_sec != buf.st_mtimespec.tv_sec || conchModTime.tv_nsec != buf.st_mtimespec.tv_nsec ){ return SQLITE_BUSY; } - - if( nTries==2 ){ + + if( nTries==2 ){ char tBuf[PROXY_MAXCONCHLEN]; int len = osPread(conchFile->h, tBuf, PROXY_MAXCONCHLEN, 0); if( len<0 ){ @@ -7325,9 +7330,9 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ return SQLITE_BUSY; } unixSleep(0,10000000); /* wait 10 sec and try the lock again */ - continue; + continue; } - + assert( nTries==3 ); if( 0==proxyBreakConchLock(pFile, myHostID) ){ rc = SQLITE_OK; @@ -7340,19 +7345,19 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ } } } while( rc==SQLITE_BUSY && nTries<3 ); - + return rc; } -/* Takes the conch by taking a shared lock and read the contents conch, if -** lockPath is non-NULL, the host ID and lock file path must match. A NULL -** lockPath means that the lockPath in the conch file will be used if the -** host IDs match, or a new lock path will be generated automatically +/* Takes the conch by taking a shared lock and read the contents conch, if +** lockPath is non-NULL, the host ID and lock file path must match. A NULL +** lockPath means that the lockPath in the conch file will be used if the +** host IDs match, or a new lock path will be generated automatically ** and written to the conch file. */ static int proxyTakeConch(unixFile *pFile){ - proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; - + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + if( pCtx->conchHeld!=0 ){ return SQLITE_OK; }else{ @@ -7368,7 +7373,7 @@ static int proxyTakeConch(unixFile *pFile){ int readLen = 0; int tryOldLockPath = 0; int forceNewLockPath = 0; - + OSTRACE(("TAKECONCH %d for %s pid=%d\n", conchFile->h, (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), osGetpid(0))); @@ -7389,21 +7394,21 @@ static int proxyTakeConch(unixFile *pFile){ storeLastErrno(pFile, conchFile->lastErrno); rc = SQLITE_IOERR_READ; goto end_takeconch; - }else if( readLen<=(PROXY_HEADERLEN+PROXY_HOSTIDLEN) || + }else if( readLen<=(PROXY_HEADERLEN+PROXY_HOSTIDLEN) || readBuf[0]!=(char)PROXY_CONCHVERSION ){ - /* a short read or version format mismatch means we need to create a new - ** conch file. + /* a short read or version format mismatch means we need to create a new + ** conch file. */ createConch = 1; } /* if the host id matches and the lock path already exists in the conch - ** we'll try to use the path there, if we can't open that path, we'll - ** retry with a new auto-generated path + ** we'll try to use the path there, if we can't open that path, we'll + ** retry with a new auto-generated path */ do { /* in case we need to try again for an :auto: named lock file */ if( !createConch && !forceNewLockPath ){ - hostIdMatch = !memcmp(&readBuf[PROXY_HEADERLEN], myHostID, + hostIdMatch = !memcmp(&readBuf[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN); /* if the conch has data compare the contents */ if( !pCtx->lockProxyPath ){ @@ -7412,7 +7417,7 @@ static int proxyTakeConch(unixFile *pFile){ */ if( hostIdMatch ){ size_t pathLen = (readLen - PROXY_PATHINDEX); - + if( pathLen>=MAXPATHLEN ){ pathLen=MAXPATHLEN-1; } @@ -7428,23 +7433,23 @@ static int proxyTakeConch(unixFile *pFile){ readLen-PROXY_PATHINDEX) ){ /* conch host and lock path match */ - goto end_takeconch; + goto end_takeconch; } } - + /* if the conch isn't writable and doesn't match, we can't take it */ if( (conchFile->openFlags&O_RDWR) == 0 ){ rc = SQLITE_BUSY; goto end_takeconch; } - + /* either the conch didn't match or we need to create a new one */ if( !pCtx->lockProxyPath ){ proxyGetLockPath(pCtx->dbPath, lockPath, MAXPATHLEN); tempLockPath = lockPath; /* create a copy of the lock path _only_ if the conch is taken */ } - + /* update conch with host and path (this will fail if other process ** has a shared lock already), if the host id matches, use the big ** stick. @@ -7455,7 +7460,7 @@ static int proxyTakeConch(unixFile *pFile){ /* We are trying for an exclusive lock but another thread in this ** same process is still holding a shared lock. */ rc = SQLITE_BUSY; - } else { + } else { rc = proxyConchLock(pFile, myHostID, EXCLUSIVE_LOCK); } }else{ @@ -7464,7 +7469,7 @@ static int proxyTakeConch(unixFile *pFile){ if( rc==SQLITE_OK ){ char writeBuffer[PROXY_MAXCONCHLEN]; int writeSize = 0; - + writeBuffer[0] = (char)PROXY_CONCHVERSION; memcpy(&writeBuffer[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN); if( pCtx->lockProxyPath!=NULL ){ @@ -7477,8 +7482,8 @@ static int proxyTakeConch(unixFile *pFile){ robust_ftruncate(conchFile->h, writeSize); rc = unixWrite((sqlite3_file *)conchFile, writeBuffer, writeSize, 0); full_fsync(conchFile->h,0,0); - /* If we created a new conch file (not just updated the contents of a - ** valid conch file), try to match the permissions of the database + /* If we created a new conch file (not just updated the contents of a + ** valid conch file), try to match the permissions of the database */ if( rc==SQLITE_OK && createConch ){ struct stat buf; @@ -7502,14 +7507,14 @@ static int proxyTakeConch(unixFile *pFile){ } }else{ int code = errno; - fprintf(stderr, "STAT FAILED[%d] with %d %s\n", + fprintf(stderr, "STAT FAILED[%d] with %d %s\n", err, code, strerror(code)); #endif } } } conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, SHARED_LOCK); - + end_takeconch: OSTRACE(("TRANSPROXY: CLOSE %d\n", pFile->h)); if( rc==SQLITE_OK && pFile->openFlags ){ @@ -7532,7 +7537,7 @@ static int proxyTakeConch(unixFile *pFile){ rc = proxyCreateUnixFile(path, &pCtx->lockProxy, 1); if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM && tryOldLockPath ){ /* we couldn't create the proxy lock file with the old lock file path - ** so try again via auto-naming + ** so try again via auto-naming */ forceNewLockPath = 1; tryOldLockPath = 0; @@ -7552,7 +7557,7 @@ static int proxyTakeConch(unixFile *pFile){ } if( rc==SQLITE_OK ){ pCtx->conchHeld = 1; - + if( pCtx->lockProxy->pMethod == &afpIoMethods ){ afpLockingContext *afpCtx; afpCtx = (afpLockingContext *)pCtx->lockProxy->lockingContext; @@ -7564,7 +7569,7 @@ static int proxyTakeConch(unixFile *pFile){ OSTRACE(("TAKECONCH %d %s\n", conchFile->h, rc==SQLITE_OK?"ok":"failed")); return rc; - } while (1); /* in case we need to retry the :auto: lock file - + } while (1); /* in case we need to retry the :auto: lock file - ** we should never get here except via the 'continue' call. */ } } @@ -7580,7 +7585,7 @@ static int proxyReleaseConch(unixFile *pFile){ pCtx = (proxyLockingContext *)pFile->lockingContext; conchFile = pCtx->conchFile; OSTRACE(("RELEASECONCH %d for %s pid=%d\n", conchFile->h, - (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), + (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), osGetpid(0))); if( pCtx->conchHeld>0 ){ rc = conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); @@ -7608,13 +7613,13 @@ static int proxyCreateConchPathname(char *dbPath, char **pConchPath){ char *conchPath; /* buffer in which to construct conch name */ /* Allocate space for the conch filename and initialize the name to - ** the name of the original database file. */ + ** the name of the original database file. */ *pConchPath = conchPath = (char *)sqlite3_malloc64(len + 8); if( conchPath==0 ){ return SQLITE_NOMEM_BKPT; } memcpy(conchPath, dbPath, len+1); - + /* now insert a "." before the last / character */ for( i=(len-1); i>=0; i-- ){ if( conchPath[i]=='/' ){ @@ -7637,7 +7642,7 @@ static int proxyCreateConchPathname(char *dbPath, char **pConchPath){ /* Takes a fully configured proxy locking-style unix file and switches -** the local lock file path +** the local lock file path */ static int switchLockProxyPath(unixFile *pFile, const char *path) { proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; @@ -7646,7 +7651,7 @@ static int switchLockProxyPath(unixFile *pFile, const char *path) { if( pFile->eFileLock!=NO_LOCK ){ return SQLITE_BUSY; - } + } /* nothing to do if the path is NULL, :auto: or matches the existing path */ if( !path || path[0]=='\0' || !strcmp(path, ":auto:") || @@ -7664,7 +7669,7 @@ static int switchLockProxyPath(unixFile *pFile, const char *path) { sqlite3_free(oldPath); pCtx->lockProxyPath = sqlite3DbStrDup(0, path); } - + return rc; } @@ -7678,7 +7683,7 @@ static int switchLockProxyPath(unixFile *pFile, const char *path) { static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ #if defined(__APPLE__) if( pFile->pMethod == &afpIoMethods ){ - /* afp style keeps a reference to the db path in the filePath field + /* afp style keeps a reference to the db path in the filePath field ** of the struct */ assert( (int)strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); strlcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath, @@ -7699,9 +7704,9 @@ static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ } /* -** Takes an already filled in unix file and alters it so all file locking +** Takes an already filled in unix file and alters it so all file locking ** will be performed on the local proxy lock file. The following fields -** are preserved in the locking context so that they can be restored and +** are preserved in the locking context so that they can be restored and ** the unix structure properly cleaned up at close time: ** ->lockingContext ** ->pMethod @@ -7711,7 +7716,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { char dbPath[MAXPATHLEN+1]; /* Name of the database file */ char *lockPath=NULL; int rc = SQLITE_OK; - + if( pFile->eFileLock!=NO_LOCK ){ return SQLITE_BUSY; } @@ -7721,7 +7726,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { }else{ lockPath=(char *)path; } - + OSTRACE(("TRANSPROXY %d for %s pid=%d\n", pFile->h, (lockPath ? lockPath : ":auto:"), osGetpid(0))); @@ -7755,7 +7760,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { rc = SQLITE_OK; } } - } + } if( rc==SQLITE_OK && lockPath ){ pCtx->lockProxyPath = sqlite3DbStrDup(0, lockPath); } @@ -7767,7 +7772,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { } } if( rc==SQLITE_OK ){ - /* all memory is allocated, proxys are created and assigned, + /* all memory is allocated, proxys are created and assigned, ** switch the locking context and pMethod then return. */ pCtx->oldLockingContext = pFile->lockingContext; @@ -7775,12 +7780,12 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { pCtx->pOldMethod = pFile->pMethod; pFile->pMethod = &proxyIoMethods; }else{ - if( pCtx->conchFile ){ + if( pCtx->conchFile ){ pCtx->conchFile->pMethod->xClose((sqlite3_file *)pCtx->conchFile); sqlite3_free(pCtx->conchFile); } sqlite3DbFree(0, pCtx->lockProxyPath); - sqlite3_free(pCtx->conchFilePath); + sqlite3_free(pCtx->conchFilePath); sqlite3_free(pCtx); } OSTRACE(("TRANSPROXY %d %s\n", pFile->h, @@ -7818,7 +7823,7 @@ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){ if( isProxyStyle ){ /* turn off proxy locking - not supported. If support is added for ** switching proxy locking mode off then it will need to fail if - ** the journal mode is WAL mode. + ** the journal mode is WAL mode. */ rc = SQLITE_ERROR /*SQLITE_PROTOCOL? SQLITE_MISUSE?*/; }else{ @@ -7828,9 +7833,9 @@ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){ }else{ const char *proxyPath = (const char *)pArg; if( isProxyStyle ){ - proxyLockingContext *pCtx = + proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; - if( !strcmp(pArg, ":auto:") + if( !strcmp(pArg, ":auto:") || (pCtx->lockProxyPath && !strncmp(pCtx->lockProxyPath, proxyPath, MAXPATHLEN)) ){ @@ -7955,7 +7960,7 @@ static int proxyClose(sqlite3_file *id) { unixFile *lockProxy = pCtx->lockProxy; unixFile *conchFile = pCtx->conchFile; int rc = SQLITE_OK; - + if( lockProxy ){ rc = lockProxy->pMethod->xUnlock((sqlite3_file*)lockProxy, NO_LOCK); if( rc ) return rc; @@ -7992,7 +7997,7 @@ static int proxyClose(sqlite3_file *id) { ** The proxy locking style is intended for use with AFP filesystems. ** And since AFP is only supported on MacOSX, the proxy locking is also ** restricted to MacOSX. -** +** ** ******************* End of the proxy lock implementation ********************** ******************************************************************************/ @@ -8010,8 +8015,8 @@ static int proxyClose(sqlite3_file *id) { ** necessarily been initialized when this routine is called, and so they ** should not be used. */ -int sqlite3_os_init(void){ - /* +int sqlite3_os_init(void){ + /* ** The following macro defines an initializer for an sqlite3_vfs object. ** The name of the VFS is NAME. The pAppData is a pointer to a pointer ** to the "finder" function. (pAppData is a pointer to a pointer because @@ -8027,7 +8032,7 @@ int sqlite3_os_init(void){ ** ** Most finders simply return a pointer to a fixed sqlite3_io_methods ** object. But the "autolockIoFinder" available on MacOSX does a little - ** more than that; it looks at the filesystem type that hosts the + ** more than that; it looks at the filesystem type that hosts the ** database file and tries to choose an locking method appropriate for ** that filesystem time. */ @@ -8130,7 +8135,7 @@ int sqlite3_os_init(void){ /* Initialize temp file dir array. */ unixTempFileInit(); - return SQLITE_OK; + return SQLITE_OK; } /* @@ -8140,9 +8145,9 @@ int sqlite3_os_init(void){ ** to release dynamically allocated objects. But not on unix. ** This routine is a no-op for unix. */ -int sqlite3_os_end(void){ +int sqlite3_os_end(void){ unixBigLock = 0; - return SQLITE_OK; + return SQLITE_OK; } - + #endif /* SQLITE_OS_UNIX */ diff --git a/src/os_win.c b/src/os_win.c index b7b689745..dc16c08b5 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -1159,7 +1159,7 @@ static struct win_syscall { /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the -** "win32" VFSes. Return SQLITE_OK opon successfully updating the +** "win32" VFSes. Return SQLITE_OK upon successfully updating the ** system call pointer, or SQLITE_NOTFOUND if there is no configurable ** system call named zName. */ @@ -2739,7 +2739,7 @@ static int winRead( pFile->h, pBuf, amt, offset, pFile->locktype)); #if SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this read request as possible by transfering + /* Deal with as much of this read request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -2817,7 +2817,7 @@ static int winWrite( pFile->h, pBuf, amt, offset, pFile->locktype)); #if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this write request as possible by transfering + /* Deal with as much of this write request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -2927,7 +2927,7 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ ** all references to memory-mapped content are closed. That is doable, ** but involves adding a few branches in the common write code path which ** could slow down normal operations slightly. Hence, we have decided for - ** now to simply make trancations a no-op if there are pending reads. We + ** now to simply make transactions a no-op if there are pending reads. We ** can maybe revisit this decision in the future. */ return SQLITE_OK; @@ -2986,7 +2986,7 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ #ifdef SQLITE_TEST /* ** Count the number of fullsyncs and normal syncs. This is used to test -** that syncs and fullsyncs are occuring at the right times. +** that syncs and fullsyncs are occurring at the right times. */ int sqlite3_sync_count = 0; int sqlite3_fullsync_count = 0; @@ -3343,7 +3343,7 @@ static int winLock(sqlite3_file *id, int locktype){ */ if( locktype==EXCLUSIVE_LOCK && res ){ assert( pFile->locktype>=SHARED_LOCK ); - res = winUnlockReadLock(pFile); + (void)winUnlockReadLock(pFile); res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, SHARED_FIRST, 0, SHARED_SIZE, 0); if( res ){ @@ -4747,6 +4747,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; size_t i, j; + DWORD pid; int nPre = sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX); int nMax, nBuf, nDir, nLen; char *zBuf; @@ -4959,7 +4960,10 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ j = sqlite3Strlen30(zBuf); sqlite3_randomness(15, &zBuf[j]); + pid = osGetCurrentProcessId(); for(i=0; i<15; i++, j++){ + zBuf[j] += pid & 0xff; + pid >>= 8; zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; } zBuf[j] = 0; diff --git a/src/pager.c b/src/pager.c index 44384de5c..a53dc1889 100644 --- a/src/pager.c +++ b/src/pager.c @@ -10,7 +10,7 @@ ** ************************************************************************* ** This is the implementation of the page cache subsystem or "pager". -** +** ** The pager is used to access a database disk file. It implements ** atomic commit and rollback through the use of a journal file that ** is separate from the database file. The pager also implements file @@ -36,60 +36,60 @@ ** ** Definition: A page of the database file is said to be "overwriteable" if ** one or more of the following are true about the page: -** +** ** (a) The original content of the page as it was at the beginning of ** the transaction has been written into the rollback journal and ** synced. -** +** ** (b) The page was a freelist leaf page at the start of the transaction. -** +** ** (c) The page number is greater than the largest page that existed in ** the database file at the start of the transaction. -** +** ** (1) A page of the database file is never overwritten unless one of the ** following are true: -** +** ** (a) The page and all other pages on the same sector are overwriteable. -** +** ** (b) The atomic page write optimization is enabled, and the entire ** transaction other than the update of the transaction sequence ** number consists of a single page change. -** +** ** (2) The content of a page written into the rollback journal exactly matches ** both the content in the database when the rollback journal was written ** and the content in the database at the beginning of the current ** transaction. -** +** ** (3) Writes to the database file are an integer multiple of the page size ** in length and are aligned on a page boundary. -** +** ** (4) Reads from the database file are either aligned on a page boundary and ** an integer multiple of the page size in length or are taken from the ** first 100 bytes of the database file. -** +** ** (5) All writes to the database file are synced prior to the rollback journal ** being deleted, truncated, or zeroed. -** +** ** (6) If a super-journal file is used, then all writes to the database file ** are synced prior to the super-journal being deleted. -** +** ** Definition: Two databases (or the same database at two points it time) ** are said to be "logically equivalent" if they give the same answer to ** all queries. Note in particular the content of freelist leaf ** pages can be changed arbitrarily without affecting the logical equivalence ** of the database. -** +** ** (7) At any time, if any subset, including the empty set and the total set, -** of the unsynced changes to a rollback journal are removed and the +** of the unsynced changes to a rollback journal are removed and the ** journal is rolled back, the resulting database file will be logically ** equivalent to the database file at the beginning of the transaction. -** +** ** (8) When a transaction is rolled back, the xTruncate method of the VFS ** is called to restore the database file to the same size it was at ** the beginning of the transaction. (In some VFSes, the xTruncate ** method is a no-op, but that does not change the fact the SQLite will ** invoke it.) -** +** ** (9) Whenever the database file is modified, at least one bit in the range ** of bytes from 24 through 39 inclusive will be changed prior to releasing ** the EXCLUSIVE lock, thus signaling other connections on the same @@ -122,7 +122,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ /* ** The following two macros are used within the PAGERTRACE() macros above -** to print out file-descriptors. +** to print out file-descriptors. ** ** PAGERID() takes a pointer to a Pager struct as its argument. The ** associated file-descriptor is returned. FILEHANDLEID() takes an sqlite3_file @@ -143,7 +143,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** | | | ** | V | ** |<-------WRITER_LOCKED------> ERROR -** | | ^ +** | | ^ ** | V | ** |<------WRITER_CACHEMOD-------->| ** | | | @@ -155,7 +155,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** ** List of state transitions and the C [function] that performs each: -** +** ** OPEN -> READER [sqlite3PagerSharedLock] ** READER -> OPEN [pager_unlock] ** @@ -167,7 +167,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** WRITER_*** -> ERROR [pager_error] ** ERROR -> OPEN [pager_unlock] -** +** ** ** OPEN: ** @@ -181,9 +181,9 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** READER: ** -** In this state all the requirements for reading the database in +** In this state all the requirements for reading the database in ** rollback (non-WAL) mode are met. Unless the pager is (or recently -** was) in exclusive-locking mode, a user-level read transaction is +** was) in exclusive-locking mode, a user-level read transaction is ** open. The database size is known in this state. ** ** A connection running with locking_mode=normal enters this state when @@ -193,28 +193,28 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** this state even after the read-transaction is closed. The only way ** a locking_mode=exclusive connection can transition from READER to OPEN ** is via the ERROR state (see below). -** +** ** * A read transaction may be active (but a write-transaction cannot). ** * A SHARED or greater lock is held on the database file. -** * The dbSize variable may be trusted (even if a user-level read +** * The dbSize variable may be trusted (even if a user-level read ** transaction is not active). The dbOrigSize and dbFileSize variables ** may not be trusted at this point. ** * If the database is a WAL database, then the WAL connection is open. -** * Even if a read-transaction is not open, it is guaranteed that +** * Even if a read-transaction is not open, it is guaranteed that ** there is no hot-journal in the file-system. ** ** WRITER_LOCKED: ** ** The pager moves to this state from READER when a write-transaction -** is first opened on the database. In WRITER_LOCKED state, all locks -** required to start a write-transaction are held, but no actual +** is first opened on the database. In WRITER_LOCKED state, all locks +** required to start a write-transaction are held, but no actual ** modifications to the cache or database have taken place. ** -** In rollback mode, a RESERVED or (if the transaction was opened with +** In rollback mode, a RESERVED or (if the transaction was opened with ** BEGIN EXCLUSIVE) EXCLUSIVE lock is obtained on the database file when -** moving to this state, but the journal file is not written to or opened -** to in this state. If the transaction is committed or rolled back while -** in WRITER_LOCKED state, all that is required is to unlock the database +** moving to this state, but the journal file is not written to or opened +** to in this state. If the transaction is committed or rolled back while +** in WRITER_LOCKED state, all that is required is to unlock the database ** file. ** ** IN WAL mode, WalBeginWriteTransaction() is called to lock the log file. @@ -222,7 +222,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** is made to obtain an EXCLUSIVE lock on the database file. ** ** * A write transaction is active. -** * If the connection is open in rollback-mode, a RESERVED or greater +** * If the connection is open in rollback-mode, a RESERVED or greater ** lock is held on the database file. ** * If the connection is open in WAL-mode, a WAL write transaction ** is open (i.e. sqlite3WalBeginWriteTransaction() has been successfully @@ -241,7 +241,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** * A write transaction is active. ** * A RESERVED or greater lock is held on the database file. -** * The journal file is open and the first header has been written +** * The journal file is open and the first header has been written ** to it, but the header has not been synced to disk. ** * The contents of the page cache have been modified. ** @@ -254,7 +254,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** * A write transaction is active. ** * An EXCLUSIVE or greater lock is held on the database file. -** * The journal file is open and the first header has been written +** * The journal file is open and the first header has been written ** and synced to disk. ** * The contents of the page cache have been modified (and possibly ** written to disk). @@ -266,8 +266,8 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** A rollback-mode pager changes to WRITER_FINISHED state from WRITER_DBMOD ** state after the entire transaction has been successfully written into the ** database file. In this state the transaction may be committed simply -** by finalizing the journal file. Once in WRITER_FINISHED state, it is -** not possible to modify the database further. At this point, the upper +** by finalizing the journal file. Once in WRITER_FINISHED state, it is +** not possible to modify the database further. At this point, the upper ** layer must either commit or rollback the transaction. ** ** * A write transaction is active. @@ -275,19 +275,19 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** * All writing and syncing of journal and database data has finished. ** If no error occurred, all that remains is to finalize the journal to ** commit the transaction. If an error did occur, the caller will need -** to rollback the transaction. +** to rollback the transaction. ** ** ERROR: ** ** The ERROR state is entered when an IO or disk-full error (including -** SQLITE_IOERR_NOMEM) occurs at a point in the code that makes it -** difficult to be sure that the in-memory pager state (cache contents, +** SQLITE_IOERR_NOMEM) occurs at a point in the code that makes it +** difficult to be sure that the in-memory pager state (cache contents, ** db size etc.) are consistent with the contents of the file-system. ** ** Temporary pager files may enter the ERROR state, but in-memory pagers ** cannot. ** -** For example, if an IO error occurs while performing a rollback, +** For example, if an IO error occurs while performing a rollback, ** the contents of the page-cache may be left in an inconsistent state. ** At this point it would be dangerous to change back to READER state ** (as usually happens after a rollback). Any subsequent readers might @@ -297,13 +297,13 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** instead of READER following such an error. ** ** Once it has entered the ERROR state, any attempt to use the pager -** to read or write data returns an error. Eventually, once all +** to read or write data returns an error. Eventually, once all ** outstanding transactions have been abandoned, the pager is able to -** transition back to OPEN state, discarding the contents of the +** transition back to OPEN state, discarding the contents of the ** page-cache and any other in-memory state at the same time. Everything -** is reloaded from disk (and, if necessary, hot-journal rollback peformed) +** is reloaded from disk (and, if necessary, hot-journal rollback performed) ** when a read-transaction is next opened on the pager (transitioning -** the pager into READER state). At that point the system has recovered +** the pager into READER state). At that point the system has recovered ** from the error. ** ** Specifically, the pager jumps into the ERROR state if: @@ -319,21 +319,21 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** memory. ** ** In other cases, the error is returned to the b-tree layer. The b-tree -** layer then attempts a rollback operation. If the error condition +** layer then attempts a rollback operation. If the error condition ** persists, the pager enters the ERROR state via condition (1) above. ** ** Condition (3) is necessary because it can be triggered by a read-only ** statement executed within a transaction. In this case, if the error ** code were simply returned to the user, the b-tree layer would not ** automatically attempt a rollback, as it assumes that an error in a -** read-only statement cannot leave the pager in an internally inconsistent +** read-only statement cannot leave the pager in an internally inconsistent ** state. ** ** * The Pager.errCode variable is set to something other than SQLITE_OK. ** * There are one or more outstanding references to pages (after the ** last reference is dropped the pager should move back to OPEN state). ** * The pager is not an in-memory pager. -** +** ** ** Notes: ** @@ -343,7 +343,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** * Normally, a connection open in exclusive mode is never in PAGER_OPEN ** state. There are two exceptions: immediately after exclusive-mode has -** been turned on (and before any read or write transactions are +** been turned on (and before any read or write transactions are ** executed), and when the pager is leaving the "error state". ** ** * See also: assert_pager_state(). @@ -357,7 +357,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ #define PAGER_ERROR 6 /* -** The Pager.eLock variable is almost always set to one of the +** The Pager.eLock variable is almost always set to one of the ** following locking-states, according to the lock currently held on ** the database file: NO_LOCK, SHARED_LOCK, RESERVED_LOCK or EXCLUSIVE_LOCK. ** This variable is kept up to date as locks are taken and released by @@ -372,20 +372,20 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** to a less exclusive (lower) value than the lock that is actually held ** at the system level, but it is never set to a more exclusive value. ** -** This is usually safe. If an xUnlock fails or appears to fail, there may +** This is usually safe. If an xUnlock fails or appears to fail, there may ** be a few redundant xLock() calls or a lock may be held for longer than ** required, but nothing really goes wrong. ** ** The exception is when the database file is unlocked as the pager moves -** from ERROR to OPEN state. At this point there may be a hot-journal file +** from ERROR to OPEN state. At this point there may be a hot-journal file ** in the file-system that needs to be rolled back (as part of an OPEN->SHARED ** transition, by the same pager or any other). If the call to xUnlock() ** fails at this point and the pager is left holding an EXCLUSIVE lock, this ** can confuse the call to xCheckReservedLock() call made later as part ** of hot-journal detection. ** -** xCheckReservedLock() is defined as returning true "if there is a RESERVED -** lock held by this process or any others". So xCheckReservedLock may +** xCheckReservedLock() is defined as returning true "if there is a RESERVED +** lock held by this process or any others". So xCheckReservedLock may ** return true because the caller itself is holding an EXCLUSIVE lock (but ** doesn't know it because of a previous error in xUnlock). If this happens ** a hot-journal may be mistaken for a journal being created by an active @@ -396,18 +396,18 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** database in the ERROR state, Pager.eLock is set to UNKNOWN_LOCK. It ** is only changed back to a real locking state after a successful call ** to xLock(EXCLUSIVE). Also, the code to do the OPEN->SHARED state transition -** omits the check for a hot-journal if Pager.eLock is set to UNKNOWN_LOCK +** omits the check for a hot-journal if Pager.eLock is set to UNKNOWN_LOCK ** lock. Instead, it assumes a hot-journal exists and obtains an EXCLUSIVE ** lock on the database file before attempting to roll it back. See function ** PagerSharedLock() for more detail. ** -** Pager.eLock may only be set to UNKNOWN_LOCK when the pager is in +** Pager.eLock may only be set to UNKNOWN_LOCK when the pager is in ** PAGER_OPEN state. */ #define UNKNOWN_LOCK (EXCLUSIVE_LOCK+1) /* -** The maximum allowed sector size. 64KiB. If the xSectorsize() method +** The maximum allowed sector size. 64KiB. If the xSectorsize() method ** returns a value larger than this, then MAX_SECTOR_SIZE is used instead. ** This could conceivably cause corruption following a power failure on ** such a system. This is currently an undocumented limit. @@ -423,7 +423,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** When a savepoint is created, the PagerSavepoint.iHdrOffset field is ** set to 0. If a journal-header is written into the main journal while -** the savepoint is active, then iHdrOffset is set to the byte offset +** the savepoint is active, then iHdrOffset is set to the byte offset ** immediately following the last journal record written into the main ** journal before the journal-header. This is required during savepoint ** rollback (see pagerPlaybackSavepoint()). @@ -474,34 +474,34 @@ struct PagerSavepoint { ** ** changeCountDone ** -** This boolean variable is used to make sure that the change-counter -** (the 4-byte header field at byte offset 24 of the database file) is -** not updated more often than necessary. +** This boolean variable is used to make sure that the change-counter +** (the 4-byte header field at byte offset 24 of the database file) is +** not updated more often than necessary. ** -** It is set to true when the change-counter field is updated, which +** It is set to true when the change-counter field is updated, which ** can only happen if an exclusive lock is held on the database file. -** It is cleared (set to false) whenever an exclusive lock is +** It is cleared (set to false) whenever an exclusive lock is ** relinquished on the database file. Each time a transaction is committed, ** The changeCountDone flag is inspected. If it is true, the work of ** updating the change-counter is omitted for the current transaction. ** -** This mechanism means that when running in exclusive mode, a connection +** This mechanism means that when running in exclusive mode, a connection ** need only update the change-counter once, for the first transaction ** committed. ** ** setSuper ** ** When PagerCommitPhaseOne() is called to commit a transaction, it may -** (or may not) specify a super-journal name to be written into the +** (or may not) specify a super-journal name to be written into the ** journal file before it is synced to disk. ** -** Whether or not a journal file contains a super-journal pointer affects -** the way in which the journal file is finalized after the transaction is +** Whether or not a journal file contains a super-journal pointer affects +** the way in which the journal file is finalized after the transaction is ** committed or rolled back when running in "journal_mode=PERSIST" mode. ** If a journal file does not contain a super-journal pointer, it is ** finalized by overwriting the first journal header with zeroes. If -** it does contain a super-journal pointer the journal file is finalized -** by truncating it to zero bytes, just as if the connection were +** it does contain a super-journal pointer the journal file is finalized +** by truncating it to zero bytes, just as if the connection were ** running in "journal_mode=truncate" mode. ** ** Journal files that contain super-journal pointers cannot be finalized @@ -527,12 +527,12 @@ struct PagerSavepoint { ** to allocate a new page to prevent the journal file from being written ** while it is being traversed by code in pager_playback(). The SPILLFLAG_OFF ** case is a user preference. -** +** ** If the SPILLFLAG_NOSYNC bit is set, writing to the database from ** pagerStress() is permitted, but syncing the journal file is not. ** This flag is set by sqlite3PagerWrite() when the file-system sector-size ** is larger than the database page-size in order to prevent a journal sync -** from happening in between the journalling of two pages on the same sector. +** from happening in between the journalling of two pages on the same sector. ** ** subjInMemory ** @@ -540,16 +540,16 @@ struct PagerSavepoint { ** is opened as an in-memory journal file. If false, then in-memory ** sub-journals are only used for in-memory pager files. ** -** This variable is updated by the upper layer each time a new +** This variable is updated by the upper layer each time a new ** write-transaction is opened. ** ** dbSize, dbOrigSize, dbFileSize ** ** Variable dbSize is set to the number of pages in the database file. ** It is valid in PAGER_READER and higher states (all states except for -** OPEN and ERROR). +** OPEN and ERROR). ** -** dbSize is set based on the size of the database file, which may be +** dbSize is set based on the size of the database file, which may be ** larger than the size of the database (the value stored at offset ** 28 of the database header by the btree). If the size of the file ** is not an integer multiple of the page-size, the value stored in @@ -560,10 +560,10 @@ struct PagerSavepoint { ** ** During a write-transaction, if pages with page-numbers greater than ** dbSize are modified in the cache, dbSize is updated accordingly. -** Similarly, if the database is truncated using PagerTruncateImage(), +** Similarly, if the database is truncated using PagerTruncateImage(), ** dbSize is updated. ** -** Variables dbOrigSize and dbFileSize are valid in states +** Variables dbOrigSize and dbFileSize are valid in states ** PAGER_WRITER_LOCKED and higher. dbOrigSize is a copy of the dbSize ** variable at the start of the transaction. It is used during rollback, ** and to determine whether or not pages need to be journalled before @@ -572,12 +572,12 @@ struct PagerSavepoint { ** Throughout a write-transaction, dbFileSize contains the size of ** the file on disk in pages. It is set to a copy of dbSize when the ** write-transaction is first opened, and updated when VFS calls are made -** to write or truncate the database file on disk. +** to write or truncate the database file on disk. ** -** The only reason the dbFileSize variable is required is to suppress -** unnecessary calls to xTruncate() after committing a transaction. If, -** when a transaction is committed, the dbFileSize variable indicates -** that the database file is larger than the database image (Pager.dbSize), +** The only reason the dbFileSize variable is required is to suppress +** unnecessary calls to xTruncate() after committing a transaction. If, +** when a transaction is committed, the dbFileSize variable indicates +** that the database file is larger than the database image (Pager.dbSize), ** pager_truncate() is called. The pager_truncate() call uses xFilesize() ** to measure the database file on disk, and then truncates it if required. ** dbFileSize is not used when rolling back a transaction. In this case @@ -588,20 +588,20 @@ struct PagerSavepoint { ** dbHintSize ** ** The dbHintSize variable is used to limit the number of calls made to -** the VFS xFileControl(FCNTL_SIZE_HINT) method. +** the VFS xFileControl(FCNTL_SIZE_HINT) method. ** ** dbHintSize is set to a copy of the dbSize variable when a ** write-transaction is opened (at the same time as dbFileSize and ** dbOrigSize). If the xFileControl(FCNTL_SIZE_HINT) method is called, ** dbHintSize is increased to the number of pages that correspond to the -** size-hint passed to the method call. See pager_write_pagelist() for +** size-hint passed to the method call. See pager_write_pagelist() for ** details. ** ** errCode ** ** The Pager.errCode variable is only ever used in PAGER_ERROR state. It -** is set to zero in all other states. In PAGER_ERROR state, Pager.errCode -** is always set to SQLITE_FULL, SQLITE_IOERR or one of the SQLITE_IOERR_XXX +** is set to zero in all other states. In PAGER_ERROR state, Pager.errCode +** is always set to SQLITE_FULL, SQLITE_IOERR or one of the SQLITE_IOERR_XXX ** sub-codes. ** ** syncFlags, walSyncFlags @@ -704,7 +704,7 @@ struct Pager { /* ** Indexes for use with Pager.aStat[]. The Pager.aStat[] array contains -** the values accessed by passing SQLITE_DBSTATUS_CACHE_HIT, CACHE_MISS +** the values accessed by passing SQLITE_DBSTATUS_CACHE_HIT, CACHE_MISS ** or CACHE_WRITE to sqlite3_db_status(). */ #define PAGER_STAT_HIT 0 @@ -762,7 +762,7 @@ static const unsigned char aJournalMagic[] = { #define JOURNAL_PG_SZ(pPager) ((pPager->pageSize) + 8) /* -** The journal header size for this pager. This is usually the same +** The journal header size for this pager. This is usually the same ** size as a single disk sector. See also setSectorSize(). */ #define JOURNAL_HDR_SZ(pPager) (pPager->sectorSize) @@ -837,7 +837,7 @@ int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ # define pagerBeginReadTransaction(z) SQLITE_OK #endif -#ifndef NDEBUG +#ifndef NDEBUG /* ** Usage: ** @@ -866,25 +866,25 @@ static int assert_pager_state(Pager *p){ assert( p->tempFile==0 || p->eLock==EXCLUSIVE_LOCK ); assert( p->tempFile==0 || pPager->changeCountDone ); - /* If the useJournal flag is clear, the journal-mode must be "OFF". + /* If the useJournal flag is clear, the journal-mode must be "OFF". ** And if the journal-mode is "OFF", the journal file must not be open. */ assert( p->journalMode==PAGER_JOURNALMODE_OFF || p->useJournal ); assert( p->journalMode!=PAGER_JOURNALMODE_OFF || !isOpen(p->jfd) ); - /* Check that MEMDB implies noSync. And an in-memory journal. Since - ** this means an in-memory pager performs no IO at all, it cannot encounter - ** either SQLITE_IOERR or SQLITE_FULL during rollback or while finalizing - ** a journal file. (although the in-memory journal implementation may - ** return SQLITE_IOERR_NOMEM while the journal file is being written). It - ** is therefore not possible for an in-memory pager to enter the ERROR + /* Check that MEMDB implies noSync. And an in-memory journal. Since + ** this means an in-memory pager performs no IO at all, it cannot encounter + ** either SQLITE_IOERR or SQLITE_FULL during rollback or while finalizing + ** a journal file. (although the in-memory journal implementation may + ** return SQLITE_IOERR_NOMEM while the journal file is being written). It + ** is therefore not possible for an in-memory pager to enter the ERROR ** state. */ if( MEMDB ){ assert( !isOpen(p->fd) ); assert( p->noSync ); - assert( p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_MEMORY + assert( p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_MEMORY ); assert( p->eState!=PAGER_ERROR && p->eState!=PAGER_OPEN ); assert( pagerUseWal(p)==0 ); @@ -931,9 +931,9 @@ static int assert_pager_state(Pager *p){ ** to journal_mode=wal. */ assert( p->eLock>=RESERVED_LOCK ); - assert( isOpen(p->jfd) - || p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL ); } assert( pPager->dbOrigSize==pPager->dbFileSize ); @@ -945,9 +945,9 @@ static int assert_pager_state(Pager *p){ assert( pPager->errCode==SQLITE_OK ); assert( !pagerUseWal(pPager) ); assert( p->eLock>=EXCLUSIVE_LOCK ); - assert( isOpen(p->jfd) - || p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); assert( pPager->dbOrigSize<=pPager->dbHintSize ); @@ -957,9 +957,9 @@ static int assert_pager_state(Pager *p){ assert( p->eLock==EXCLUSIVE_LOCK ); assert( pPager->errCode==SQLITE_OK ); assert( !pagerUseWal(pPager) ); - assert( isOpen(p->jfd) - || p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); break; @@ -978,7 +978,7 @@ static int assert_pager_state(Pager *p){ } #endif /* ifndef NDEBUG */ -#ifdef SQLITE_DEBUG +#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 @@ -1129,7 +1129,7 @@ static int write32bits(sqlite3_file *fd, i64 offset, u32 val){ ** succeeds, set the Pager.eLock variable to match the (attempted) new lock. ** ** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is -** called, do not modify it. See the comment above the #define of +** called, do not modify it. See the comment above the #define of ** UNKNOWN_LOCK for an explanation of this. */ static int pagerUnlockDb(Pager *pPager, int eLock){ @@ -1153,11 +1153,11 @@ static int pagerUnlockDb(Pager *pPager, int eLock){ /* ** Lock the database file to level eLock, which must be either SHARED_LOCK, ** RESERVED_LOCK or EXCLUSIVE_LOCK. If the caller is successful, set the -** Pager.eLock variable to the new locking state. +** Pager.eLock variable to the new locking state. ** -** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is -** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. -** See the comment above the #define of UNKNOWN_LOCK for an explanation +** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is +** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. +** See the comment above the #define of UNKNOWN_LOCK for an explanation ** of this. */ static int pagerLockDb(Pager *pPager, int eLock){ @@ -1184,7 +1184,7 @@ static int pagerLockDb(Pager *pPager, int eLock){ ** (b) the value returned by OsSectorSize() is less than or equal ** to the page size. ** -** If it can be used, then the value returned is the size of the journal +** If it can be used, then the value returned is the size of the journal ** file when it contains rollback data for exactly one page. ** ** The atomic-batch-write optimization can be used if OsDeviceCharacteristics() @@ -1275,8 +1275,8 @@ static void checkPage(PgHdr *pPg){ /* ** When this is called the journal file for pager pPager must be open. -** This function attempts to read a super-journal file name from the -** end of the file and, if successful, copies it into memory supplied +** This function attempts to read a super-journal file name from the +** end of the file and, if successful, copies it into memory supplied ** by the caller. See comments above writeSuperJournal() for the format ** used to store a super-journal file name at the end of a journal file. ** @@ -1292,7 +1292,7 @@ static void checkPage(PgHdr *pPg){ ** nul-terminator byte is appended to the buffer following the ** super-journal file name. ** -** If it is determined that no super-journal file name is present +** If it is determined that no super-journal file name is present ** zSuper[0] is set to 0 and SQLITE_OK returned. ** ** If an error occurs while reading from the journal file, an SQLite @@ -1310,9 +1310,9 @@ static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u32 nSuper){ if( SQLITE_OK!=(rc = sqlite3OsFileSize(pJrnl, &szJ)) || szJ<16 || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-16, &len)) - || len>=nSuper + || len>=nSuper || len>szJ-16 - || len==0 + || len==0 || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-12, &cksum)) || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8)) || memcmp(aMagic, aJournalMagic, 8) @@ -1335,13 +1335,13 @@ static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u32 nSuper){ } zSuper[len] = '\0'; zSuper[len+1] = '\0'; - + return SQLITE_OK; } /* -** Return the offset of the sector boundary at or immediately -** following the value in pPager->journalOff, assuming a sector +** Return the offset of the sector boundary at or immediately +** following the value in pPager->journalOff, assuming a sector ** size of pPager->sectorSize bytes. ** ** i.e for a sector size of 512: @@ -1352,7 +1352,7 @@ static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u32 nSuper){ ** 512 512 ** 100 512 ** 2000 2048 -** +** */ static i64 journalHdrOffset(Pager *pPager){ i64 offset = 0; @@ -1374,12 +1374,12 @@ static i64 journalHdrOffset(Pager *pPager){ ** ** If doTruncate is non-zero or the Pager.journalSizeLimit variable is ** set to 0, then truncate the journal file to zero bytes in size. Otherwise, -** zero the 28-byte header at the start of the journal file. In either case, -** if the pager is not in no-sync mode, sync the journal file immediately +** zero the 28-byte header at the start of the journal file. In either case, +** if the pager is not in no-sync mode, sync the journal file immediately ** after writing or truncating it. ** ** If Pager.journalSizeLimit is set to a positive, non-zero value, and -** following the truncation or zeroing described above the size of the +** following the truncation or zeroing described above the size of the ** journal file in bytes is larger than this value, then truncate the ** journal file to Pager.journalSizeLimit bytes. The journal file does ** not need to be synced following this operation. @@ -1405,8 +1405,8 @@ static int zeroJournalHdr(Pager *pPager, int doTruncate){ rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags); } - /* At this point the transaction is committed but the write lock - ** is still held on the file. If there is a size limit configured for + /* At this point the transaction is committed but the write lock + ** is still held on the file. If there is a size limit configured for ** the persistent journal and the journal file currently consumes more ** space than that limit allows for, truncate it now. There is no need ** to sync the file following this operation. @@ -1434,7 +1434,7 @@ static int zeroJournalHdr(Pager *pPager, int doTruncate){ ** - 4 bytes: Initial database page count. ** - 4 bytes: Sector size used by the process that wrote this journal. ** - 4 bytes: Database page size. -** +** ** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space. */ static int writeJournalHdr(Pager *pPager){ @@ -1450,8 +1450,8 @@ static int writeJournalHdr(Pager *pPager){ nHeader = JOURNAL_HDR_SZ(pPager); } - /* If there are active savepoints and any of them were created - ** since the most recent journal header was written, update the + /* If there are active savepoints and any of them were created + ** since the most recent journal header was written, update the ** PagerSavepoint.iHdrOffset fields now. */ for(ii=0; iinSavepoint; ii++){ @@ -1462,10 +1462,10 @@ static int writeJournalHdr(Pager *pPager){ pPager->journalHdr = pPager->journalOff = journalHdrOffset(pPager); - /* + /* ** Write the nRec Field - the number of page records that follow this ** journal header. Normally, zero is written to this value at this time. - ** After the records are added to the journal (and the journal synced, + ** After the records are added to the journal (and the journal synced, ** if in full-sync mode), the zero is overwritten with the true number ** of records (see syncJournal()). ** @@ -1484,7 +1484,7 @@ static int writeJournalHdr(Pager *pPager){ */ assert( isOpen(pPager->fd) || pPager->noSync ); if( pPager->noSync || (pPager->journalMode==PAGER_JOURNALMODE_MEMORY) - || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND) + || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND) ){ memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic)); put32bits(&zHeader[sizeof(aJournalMagic)], 0xffffffff); @@ -1492,7 +1492,7 @@ static int writeJournalHdr(Pager *pPager){ memset(zHeader, 0, sizeof(aJournalMagic)+4); } - /* The random check-hash initializer */ + /* The random check-hash initializer */ sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit); /* The initial database size */ @@ -1511,23 +1511,23 @@ static int writeJournalHdr(Pager *pPager){ memset(&zHeader[sizeof(aJournalMagic)+20], 0, nHeader-(sizeof(aJournalMagic)+20)); - /* In theory, it is only necessary to write the 28 bytes that the - ** journal header consumes to the journal file here. Then increment the - ** Pager.journalOff variable by JOURNAL_HDR_SZ so that the next + /* In theory, it is only necessary to write the 28 bytes that the + ** journal header consumes to the journal file here. Then increment the + ** Pager.journalOff variable by JOURNAL_HDR_SZ so that the next ** record is written to the following sector (leaving a gap in the file ** that will be implicitly filled in by the OS). ** - ** However it has been discovered that on some systems this pattern can + ** However it has been discovered that on some systems this pattern can ** be significantly slower than contiguously writing data to the file, - ** even if that means explicitly writing data to the block of + ** even if that means explicitly writing data to the block of ** (JOURNAL_HDR_SZ - 28) bytes that will not be used. So that is what - ** is done. + ** is done. ** - ** The loop is required here in case the sector-size is larger than the + ** The loop is required here in case the sector-size is larger than the ** database page size. Since the zHeader buffer is only Pager.pageSize ** bytes in size, more than one call to sqlite3OsWrite() may be required ** to populate the entire journal header sector. - */ + */ for(nWrite=0; rc==SQLITE_OK&&nWritejournalHdr, nHeader)) rc = sqlite3OsWrite(pPager->jfd, zHeader, nHeader, pPager->journalOff); @@ -1625,29 +1625,29 @@ static int readJournalHdr( /* Check that the values read from the page-size and sector-size fields ** are within range. To be 'in range', both values need to be a power - ** of two greater than or equal to 512 or 32, and not greater than their + ** of two greater than or equal to 512 or 32, and not greater than their ** respective compile time maximum limits. */ if( iPageSize<512 || iSectorSize<32 || iPageSize>SQLITE_MAX_PAGE_SIZE || iSectorSize>MAX_SECTOR_SIZE - || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 + || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 ){ - /* If the either the page-size or sector-size in the journal-header is - ** invalid, then the process that wrote the journal-header must have - ** crashed before the header was synced. In this case stop reading + /* If the either the page-size or sector-size in the journal-header is + ** invalid, then the process that wrote the journal-header must have + ** crashed before the header was synced. In this case stop reading ** the journal file here. */ return SQLITE_DONE; } - /* Update the page-size to match the value read from the journal. - ** Use a testcase() macro to make sure that malloc failure within + /* Update the page-size to match the value read from the journal. + ** Use a testcase() macro to make sure that malloc failure within ** PagerSetPagesize() is tested. */ rc = sqlite3PagerSetPagesize(pPager, &iPageSize, -1); testcase( rc!=SQLITE_OK ); - /* Update the assumed sector-size to match the value used by + /* Update the assumed sector-size to match the value used by ** the process that created this journal. If this journal was ** created by a process other than this one, then this routine ** is being called from within pager_playback(). The local value @@ -1674,10 +1674,10 @@ static int readJournalHdr( ** + 4 bytes: super-journal name checksum. ** + 8 bytes: aJournalMagic[]. ** -** The super-journal page checksum is the sum of the bytes in thesuper-journal +** The super-journal page checksum is the sum of the bytes in the super-journal ** name, where each byte is interpreted as a signed 8-bit integer. ** -** If zSuper is a NULL pointer (occurs for a single database transaction), +** If zSuper is a NULL pointer (occurs for a single database transaction), ** this call is a no-op. */ static int writeSuperJournal(Pager *pPager, const char *zSuper){ @@ -1690,8 +1690,8 @@ static int writeSuperJournal(Pager *pPager, const char *zSuper){ assert( pPager->setSuper==0 ); assert( !pagerUseWal(pPager) ); - if( !zSuper - || pPager->journalMode==PAGER_JOURNALMODE_MEMORY + if( !zSuper + || pPager->journalMode==PAGER_JOURNALMODE_MEMORY || !isOpen(pPager->jfd) ){ return SQLITE_OK; @@ -1727,16 +1727,16 @@ static int writeSuperJournal(Pager *pPager, const char *zSuper){ } pPager->journalOff += (nSuper+20); - /* If the pager is in peristent-journal mode, then the physical + /* If the pager is in persistent-journal mode, then the physical ** journal-file may extend past the end of the super-journal name - ** and 8 bytes of magic data just written to the file. This is + ** and 8 bytes of magic data just written to the file. This is ** dangerous because the code to rollback a hot-journal file - ** will not be able to find the super-journal name to determine - ** whether or not the journal is hot. + ** will not be able to find the super-journal name to determine + ** whether or not the journal is hot. ** - ** Easiest thing to do in this scenario is to truncate the journal + ** Easiest thing to do in this scenario is to truncate the journal ** file to the required size. - */ + */ if( SQLITE_OK==(rc = sqlite3OsFileSize(pPager->jfd, &jrnlSize)) && jrnlSize>pPager->journalOff ){ @@ -1781,7 +1781,7 @@ static void releaseAllSavepoints(Pager *pPager){ } /* -** Set the bit number pgno in the PagerSavepoint.pInSavepoint +** Set the bit number pgno in the PagerSavepoint.pInSavepoint ** bitvecs of all open savepoints. Return SQLITE_OK if successful ** or SQLITE_NOMEM if a malloc failure occurs. */ @@ -1810,8 +1810,8 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){ ** not exhibit the UNDELETABLE_WHEN_OPEN property, the journal file is ** closed (if it is open). ** -** If the pager is in ERROR state when this function is called, the -** contents of the pager cache are discarded before switching back to +** If the pager is in ERROR state when this function is called, the +** contents of the pager cache are discarded before switching back to ** the OPEN state. Regardless of whether the pager is in exclusive-mode ** or not, any journal file left in the file-system will be treated ** as a hot-journal and rolled back the next time a read-transaction @@ -1819,9 +1819,9 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){ */ static void pager_unlock(Pager *pPager){ - assert( pPager->eState==PAGER_READER - || pPager->eState==PAGER_OPEN - || pPager->eState==PAGER_ERROR + assert( pPager->eState==PAGER_READER + || pPager->eState==PAGER_OPEN + || pPager->eState==PAGER_ERROR ); sqlite3BitvecDestroy(pPager->pInJournal); @@ -1897,18 +1897,18 @@ static void pager_unlock(Pager *pPager){ /* ** This function is called whenever an IOERR or FULL error that requires -** the pager to transition into the ERROR state may ahve occurred. -** The first argument is a pointer to the pager structure, the second -** the error-code about to be returned by a pager API function. The -** value returned is a copy of the second argument to this function. +** the pager to transition into the ERROR state may have occurred. +** The first argument is a pointer to the pager structure, the second +** the error-code about to be returned by a pager API function. The +** value returned is a copy of the second argument to this function. ** ** If the second argument is SQLITE_FULL, SQLITE_IOERR or one of the ** IOERR sub-codes, the pager enters the ERROR state and the error code ** is stored in Pager.errCode. While the pager remains in the ERROR state, ** all major API calls on the Pager will immediately return Pager.errCode. ** -** The ERROR state indicates that the contents of the pager-cache -** cannot be trusted. This state can be cleared by completely discarding +** The ERROR state indicates that the contents of the pager-cache +** cannot be trusted. This state can be cleared by completely discarding ** the contents of the pager-cache. If a transaction was active when ** the persistent error occurred, then the rollback journal may need ** to be replayed to restore the contents of the database file (as if @@ -1956,27 +1956,27 @@ static int pagerFlushOnCommit(Pager *pPager, int bCommit){ } /* -** This routine ends a transaction. A transaction is usually ended by -** either a COMMIT or a ROLLBACK operation. This routine may be called +** This routine ends a transaction. A transaction is usually ended by +** either a COMMIT or a ROLLBACK operation. This routine may be called ** after rollback of a hot-journal, or if an error occurs while opening ** the journal file or writing the very first journal-header of a ** database transaction. -** +** ** This routine is never called in PAGER_ERROR state. If it is called ** in PAGER_NONE or PAGER_SHARED state and the lock held is less ** exclusive than a RESERVED lock, it is a no-op. ** ** Otherwise, any active savepoints are released. ** -** If the journal file is open, then it is "finalized". Once a journal -** file has been finalized it is not possible to use it to roll back a +** If the journal file is open, then it is "finalized". Once a journal +** file has been finalized it is not possible to use it to roll back a ** transaction. Nor will it be considered to be a hot-journal by this ** or any other database connection. Exactly how a journal is finalized ** depends on whether or not the pager is running in exclusive mode and ** the current journal-mode (Pager.journalMode value), as follows: ** ** journalMode==MEMORY -** Journal file descriptor is simply closed. This destroys an +** Journal file descriptor is simply closed. This destroys an ** in-memory journal. ** ** journalMode==TRUNCATE @@ -1996,12 +1996,12 @@ static int pagerFlushOnCommit(Pager *pPager, int bCommit){ ** journalMode==PERSIST is used instead. ** ** After the journal is finalized, the pager moves to PAGER_READER state. -** If running in non-exclusive rollback mode, the lock on the file is +** If running in non-exclusive rollback mode, the lock on the file is ** downgraded to a SHARED_LOCK. ** ** SQLITE_OK is returned if no error occurs. If an error occurs during ** any of the IO operations to finalize the journal file or unlock the -** database then the IO error code is returned to the user. If the +** database then the IO error code is returned to the user. If the ** operation to finalize the journal file fails, then the code still ** tries to unlock the database file if not in exclusive mode. If the ** unlock operation fails as well, then the first error code related @@ -2020,9 +2020,9 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ ** 1. After a successful hot-journal rollback, it is called with ** eState==PAGER_NONE and eLock==EXCLUSIVE_LOCK. ** - ** 2. If a connection with locking_mode=exclusive holding an EXCLUSIVE + ** 2. If a connection with locking_mode=exclusive holding an EXCLUSIVE ** lock switches back to locking_mode=normal and then executes a - ** read-transaction, this function is called with eState==PAGER_READER + ** read-transaction, this function is called with eState==PAGER_READER ** and eLock==EXCLUSIVE_LOCK when the read-transaction is closed. */ assert( assert_pager_state(pPager) ); @@ -2032,7 +2032,7 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ } releaseAllSavepoints(pPager); - assert( isOpen(pPager->jfd) || pPager->pInJournal==0 + assert( isOpen(pPager->jfd) || pPager->pInJournal==0 || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); if( isOpen(pPager->jfd) ){ @@ -2070,9 +2070,9 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ */ int bDelete = !pPager->tempFile; assert( sqlite3JournalIsInMemory(pPager->jfd)==0 ); - assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE - || pPager->journalMode==PAGER_JOURNALMODE_MEMORY - || pPager->journalMode==PAGER_JOURNALMODE_WAL + assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE + || pPager->journalMode==PAGER_JOURNALMODE_MEMORY + || pPager->journalMode==PAGER_JOURNALMODE_WAL ); sqlite3OsClose(pPager->jfd); if( bDelete ){ @@ -2105,8 +2105,8 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ } if( pagerUseWal(pPager) ){ - /* Drop the WAL write-lock, if any. Also, if the connection was in - ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE + /* Drop the WAL write-lock, if any. Also, if the connection was in + ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE ** lock held on the database file. */ rc2 = sqlite3WalEndWriteTransaction(pPager->pWal); @@ -2114,7 +2114,7 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ }else if( rc==SQLITE_OK && bCommit && pPager->dbFileSize>pPager->dbSize ){ /* This branch is taken when committing a transaction in rollback-journal ** mode if the database file on disk is larger than the database image. - ** At this point the journal has been finalized and the transaction + ** At this point the journal has been finalized and the transaction ** successfully committed, but the EXCLUSIVE lock is still held on the ** file. So it is safe to truncate the database file to its minimum ** required size. */ @@ -2127,7 +2127,7 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; } - if( !pPager->exclusiveMode + if( !pPager->exclusiveMode && (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0)) ){ rc2 = pagerUnlockDb(pPager, SHARED_LOCK); @@ -2139,19 +2139,19 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ } /* -** Execute a rollback if a transaction is active and unlock the -** database file. +** Execute a rollback if a transaction is active and unlock the +** database file. ** -** If the pager has already entered the ERROR state, do not attempt +** If the pager has already entered the ERROR state, do not attempt ** the rollback at this time. Instead, pager_unlock() is called. The ** call to pager_unlock() will discard all in-memory pages, unlock -** the database file and move the pager back to OPEN state. If this -** means that there is a hot-journal left in the file-system, the next -** connection to obtain a shared lock on the pager (which may be this one) +** the database file and move the pager back to OPEN state. If this +** means that there is a hot-journal left in the file-system, the next +** connection to obtain a shared lock on the pager (which may be this one) ** will roll it back. ** ** If the pager has not already entered the ERROR state, but an IO or -** malloc error occurs during a rollback, then this will itself cause +** malloc error occurs during a rollback, then this will itself cause ** the pager to enter the ERROR state. Which will be cleared by the ** call to pager_unlock(), as described above. */ @@ -2172,10 +2172,10 @@ static void pagerUnlockAndRollback(Pager *pPager){ /* ** Parameter aData must point to a buffer of pPager->pageSize bytes -** of data. Compute and return a checksum based ont the contents of the +** of data. Compute and return a checksum based on the contents of the ** page of data and the current value of pPager->cksumInit. ** -** This is not a real checksum. It is really just the sum of the +** This is not a real checksum. It is really just the sum of the ** random initial value (pPager->cksumInit) and every 200th byte ** of the page data, starting with byte offset (pPager->pageSize%200). ** Each byte is interpreted as an 8-bit unsigned integer. @@ -2183,8 +2183,8 @@ static void pagerUnlockAndRollback(Pager *pPager){ ** Changing the formula used to compute this checksum results in an ** incompatible journal file format. ** -** If journal corruption occurs due to a power failure, the most likely -** scenario is that one end or the other of the record will be changed. +** If journal corruption occurs due to a power failure, the most likely +** scenario is that one end or the other of the record will be changed. ** It is much less likely that the two ends of the journal record will be ** correct and the middle be corrupt. Thus, this "checksum" scheme, ** though fast and simple, catches the mostly likely kind of corruption. @@ -2205,7 +2205,7 @@ static u32 pager_cksum(Pager *pPager, const u8 *aData){ ** The page begins at offset *pOffset into the file. The *pOffset ** value is increased to the start of the next page in the journal. ** -** The main rollback journal uses checksums - the statement journal does +** The main rollback journal uses checksums - the statement journal does ** not. ** ** If the page number of the page record read from the (sub-)journal file @@ -2225,7 +2225,7 @@ static u32 pager_cksum(Pager *pPager, const u8 *aData){ ** is successfully read from the (sub-)journal file but appears to be ** corrupted, SQLITE_DONE is returned. Data is considered corrupted in ** two circumstances: -** +** ** * If the record page-number is illegal (0 or PAGER_SJ_PGNO), or ** * If the record is being rolled back from the main journal file ** and the checksum field does not match the record content. @@ -2260,7 +2260,7 @@ static int pager_playback_one_page( assert( aData ); /* Temp storage must have already been allocated */ assert( pagerUseWal(pPager)==0 || (!isMainJrnl && isSavepnt) ); - /* Either the state is greater than PAGER_WRITER_CACHEMOD (a transaction + /* Either the state is greater than PAGER_WRITER_CACHEMOD (a transaction ** or savepoint rollback done at the request of the caller) or this is ** a hot-journal rollback. If it is a hot-journal rollback, the pager ** is in state OPEN and holds an EXCLUSIVE lock. Hot-journal rollback @@ -2326,7 +2326,7 @@ static int pager_playback_one_page( ** assert()able. ** ** If in WRITER_DBMOD, WRITER_FINISHED or OPEN state, then we update the - ** pager cache if it exists and the main file. The page is then marked + ** pager cache if it exists and the main file. The page is then marked ** not dirty. Since this code is only executed in PAGER_OPEN state for ** a hot-journal rollback, it is guaranteed that the page-cache is empty ** if the pager is in OPEN state. @@ -2390,18 +2390,18 @@ static int pager_playback_one_page( }else if( !isMainJrnl && pPg==0 ){ /* If this is a rollback of a savepoint and data was not written to ** the database and the page is not in-memory, there is a potential - ** problem. When the page is next fetched by the b-tree layer, it - ** will be read from the database file, which may or may not be - ** current. + ** problem. When the page is next fetched by the b-tree layer, it + ** will be read from the database file, which may or may not be + ** current. ** ** There are a couple of different ways this can happen. All are quite - ** obscure. When running in synchronous mode, this can only happen + ** obscure. When running in synchronous mode, this can only happen ** if the page is on the free-list at the start of the transaction, then ** populated, then moved using sqlite3PagerMovepage(). ** ** The solution is to add an in-memory page to the cache containing - ** the data just read from the sub-journal. Mark the page as dirty - ** and if the pager requires a journal-sync, then mark the page as + ** the data just read from the sub-journal. Mark the page as dirty + ** and if the pager requires a journal-sync, then mark the page as ** requiring a journal-sync before it is written. */ assert( isSavepnt ); @@ -2446,26 +2446,26 @@ static int pager_playback_one_page( ** This routine checks if it is possible to delete the super-journal file, ** and does so if it is. ** -** Argument zSuper may point to Pager.pTmpSpace. So that buffer is not +** Argument zSuper may point to Pager.pTmpSpace. So that buffer is not ** available for use within this function. ** -** When a super-journal file is created, it is populated with the names -** of all of its child journals, one after another, formatted as utf-8 -** encoded text. The end of each child journal file is marked with a +** When a super-journal file is created, it is populated with the names +** of all of its child journals, one after another, formatted as utf-8 +** encoded text. The end of each child journal file is marked with a ** nul-terminator byte (0x00). i.e. the entire contents of a super-journal ** file for a transaction involving two databases might be: ** ** "/home/bill/a.db-journal\x00/home/bill/b.db-journal\x00" ** -** A super-journal file may only be deleted once all of its child +** A super-journal file may only be deleted once all of its child ** journals have been rolled back. ** -** This function reads the contents of the super-journal file into +** This function reads the contents of the super-journal file into ** memory and loops through each of the child journal names. For ** each child journal, it checks if: ** ** * if the child journal exists, and if so -** * if the child journal contains a reference to super-journal +** * if the child journal contains a reference to super-journal ** file zSuper ** ** If a child journal can be found that matches both of the criteria @@ -2475,12 +2475,12 @@ static int pager_playback_one_page( ** ** If an IO error within this function, an error code is returned. This ** function allocates memory by calling sqlite3Malloc(). If an allocation -** fails, SQLITE_NOMEM is returned. Otherwise, if no IO or malloc errors +** fails, SQLITE_NOMEM is returned. Otherwise, if no IO or malloc errors ** occur, SQLITE_OK is returned. ** ** TODO: This function allocates a single block of memory to load ** the entire contents of the super-journal file. This could be -** a couple of kilobytes or so - potentially larger than the page +** a couple of kilobytes or so - potentially larger than the page ** size. */ static int pager_delsuper(Pager *pPager, const char *zSuper){ @@ -2541,7 +2541,7 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ /* One of the journals pointed to by the super-journal exists. ** Open it and check if it points at the super-journal. If ** so, return without deleting the super-journal file. - ** NB: zJournal is really a MAIN_JOURNAL. But call it a + ** NB: zJournal is really a MAIN_JOURNAL. But call it a ** SUPER_JOURNAL here so that the VFS will not send the zJournal ** name into sqlite3_database_file_object(). */ @@ -2566,7 +2566,7 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ } zJournal += (sqlite3Strlen30(zJournal)+1); } - + sqlite3OsClose(pSuper); rc = sqlite3OsDelete(pVfs, zSuper, 0); @@ -2582,20 +2582,20 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ /* -** This function is used to change the actual size of the database +** This function is used to change the actual size of the database ** file in the file-system. This only happens when committing a transaction, ** or rolling back a transaction (including rolling back a hot-journal). ** ** If the main database file is not open, or the pager is not in either -** DBMOD or OPEN state, this function is a no-op. Otherwise, the size -** of the file is changed to nPage pages (nPage*pPager->pageSize bytes). +** DBMOD or OPEN state, this function is a no-op. Otherwise, the size +** of the file is changed to nPage pages (nPage*pPager->pageSize bytes). ** If the file on disk is currently larger than nPage pages, then use the VFS ** xTruncate() method to truncate it. ** -** Or, it might be the case that the file on disk is smaller than -** nPage pages. Some operating system implementations can get confused if -** you try to truncate a file to some size that is larger than it -** currently is, so detect this case and write a single zero byte to +** Or, it might be the case that the file on disk is smaller than +** nPage pages. Some operating system implementations can get confused if +** you try to truncate a file to some size that is larger than it +** currently is, so detect this case and write a single zero byte to ** the end of the new file instead. ** ** If successful, return SQLITE_OK. If an IO error occurs while modifying @@ -2607,9 +2607,9 @@ static int pager_truncate(Pager *pPager, Pgno nPage){ assert( pPager->eState!=PAGER_READER ); PAGERTRACE(("Truncate %d npage %u\n", PAGERID(pPager), nPage)); - - if( isOpen(pPager->fd) - && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) + + if( isOpen(pPager->fd) + && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ i64 currentSize, newSize; int szPage = pPager->pageSize; @@ -2654,8 +2654,8 @@ int sqlite3SectorSize(sqlite3_file *pFile){ /* ** Set the value of the Pager.sectorSize variable for the given ** pager based on the value returned by the xSectorSize method -** of the open database file. The sector size will be used -** to determine the size and alignment of journal header and +** of the open database file. The sector size will be used +** to determine the size and alignment of journal header and ** super-journal pointers within created journal files. ** ** For temporary files the effective sector size is always 512 bytes. @@ -2678,7 +2678,7 @@ static void setSectorSize(Pager *pPager){ assert( isOpen(pPager->fd) || pPager->tempFile ); if( pPager->tempFile - || (sqlite3OsDeviceCharacteristics(pPager->fd) & + || (sqlite3OsDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_POWERSAFE_OVERWRITE)!=0 ){ /* Sector size doesn't matter for temporary files. Also, the file @@ -2692,15 +2692,15 @@ static void setSectorSize(Pager *pPager){ /* ** Playback the journal and thus restore the database file to -** the state it was in before we started making changes. +** the state it was in before we started making changes. ** -** The journal file format is as follows: +** The journal file format is as follows: ** ** (1) 8 byte prefix. A copy of aJournalMagic[]. ** (2) 4 byte big-endian integer which is the number of valid page records ** in the journal. If this value is 0xffffffff, then compute the ** number of page records from the journal size. -** (3) 4 byte big-endian integer which is the initial value for the +** (3) 4 byte big-endian integer which is the initial value for the ** sanity checksum. ** (4) 4 byte integer which is the number of pages to truncate the ** database to during a rollback. @@ -2729,7 +2729,7 @@ static void setSectorSize(Pager *pPager){ ** from the file size. This value is used when the user selects the ** no-sync option for the journal. A power failure could lead to corruption ** in this case. But for things like temporary table (which will be -** deleted when the power is restored) we don't care. +** deleted when the power is restored) we don't care. ** ** If the file opened as the journal file is not a well-formed ** journal file then all pages up to the first corrupted page are rolled @@ -2741,7 +2741,7 @@ static void setSectorSize(Pager *pPager){ ** and an error code is returned. ** ** The isHot parameter indicates that we are trying to rollback a journal -** that might be a hot journal. Or, it could be that the journal is +** that might be a hot journal. Or, it could be that the journal is ** preserved because of JOURNALMODE_PERSIST or JOURNALMODE_TRUNCATE. ** If the journal really is hot, reset the pager cache prior rolling ** back any content. If the journal is merely persistent, no reset is @@ -2792,9 +2792,9 @@ static int pager_playback(Pager *pPager, int isHot){ pPager->journalOff = 0; needPagerReset = isHot; - /* This loop terminates either when a readJournalHdr() or - ** pager_playback_one_page() call returns SQLITE_DONE or an IO error - ** occurs. + /* This loop terminates either when a readJournalHdr() or + ** pager_playback_one_page() call returns SQLITE_DONE or an IO error + ** occurs. */ while( 1 ){ /* Read the next journal header from the journal file. If there are @@ -2803,7 +2803,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** This indicates nothing more needs to be rolled back. */ rc = readJournalHdr(pPager, isHot, szJ, &nRec, &mxPg); - if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_OK ){ if( rc==SQLITE_DONE ){ rc = SQLITE_OK; } @@ -2831,7 +2831,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** chunk of the journal contains zero pages to be rolled back. But ** when doing a ROLLBACK and the nRec==0 chunk is the last chunk in ** the journal, it means that the journal might contain additional - ** pages that need to be rolled back and that the number of pages + ** pages that need to be rolled back and that the number of pages ** should be computed based on the journal file size. */ if( nRec==0 && !isHot && @@ -2853,7 +2853,7 @@ static int pager_playback(Pager *pPager, int isHot){ } } - /* Copy original pages out of the journal and back into the + /* Copy original pages out of the journal and back into the ** database file and/or page cache. */ for(u=0; ufd,SQLITE_FCNTL_DB_UNCHANGED,0); #endif - /* If this playback is happening automatically as a result of an IO or - ** malloc error that occurred after the change-counter was updated but - ** before the transaction was committed, then the change-counter - ** modification may just have been reverted. If this happens in exclusive + /* If this playback is happening automatically as a result of an IO or + ** malloc error that occurred after the change-counter was updated but + ** before the transaction was committed, then the change-counter + ** modification may just have been reverted. If this happens in exclusive ** mode, then subsequent transactions performed by the connection will not ** update the change-counter at all. This may lead to cache inconsistency ** problems for other processes at some point in the future. So, just @@ -2957,7 +2957,7 @@ static int pager_playback(Pager *pPager, int isHot){ /* ** Read the content for page pPg out of the database file (or out of -** the WAL if that is where the most recent copy if found) into +** the WAL if that is where the most recent copy if found) into ** pPg->pData. A shared lock or greater must be held on the database ** file before this function is called. ** @@ -3047,15 +3047,15 @@ static void pager_write_changecounter(PgHdr *pPg){ #ifndef SQLITE_OMIT_WAL /* -** This function is invoked once for each page that has already been +** This function is invoked once for each page that has already been ** written into the log file when a WAL transaction is rolled back. -** Parameter iPg is the page number of said page. The pCtx argument +** Parameter iPg is the page number of said page. The pCtx argument ** is actually a pointer to the Pager structure. ** ** If page iPg is present in the cache, and has no outstanding references, ** it is discarded. Otherwise, if there are one or more outstanding ** references, the page content is reloaded from the database. If the -** attempt to reload content from the database is required and fails, +** attempt to reload content from the database is required and fails, ** return an SQLite error code. Otherwise, SQLITE_OK. */ static int pagerUndoCallback(void *pCtx, Pgno iPg){ @@ -3081,7 +3081,7 @@ static int pagerUndoCallback(void *pCtx, Pgno iPg){ ** updated as data is copied out of the rollback journal and into the ** database. This is not generally possible with a WAL database, as ** rollback involves simply truncating the log file. Therefore, if one - ** or more frames have already been written to the log (and therefore + ** or more frames have already been written to the log (and therefore ** also copied into the backup databases) as part of this transaction, ** the backups must be restarted. */ @@ -3098,7 +3098,7 @@ static int pagerRollbackWal(Pager *pPager){ PgHdr *pList; /* List of dirty pages to revert */ /* For all pages in the cache that are currently dirty or have already - ** been written (but not committed) to the log file, do one of the + ** been written (but not committed) to the log file, do one of the ** following: ** ** + Discard the cached page (if refcount==0), or @@ -3120,11 +3120,11 @@ static int pagerRollbackWal(Pager *pPager){ ** 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 */ @@ -3138,7 +3138,7 @@ static int pagerWalFrames( assert( pPager->pWal ); assert( pList ); #ifdef SQLITE_DEBUG - /* Verify that the page list is in accending order */ + /* Verify that the page list is in ascending order */ for(p=pList; p && p->pDirty; p=p->pDirty){ assert( p->pgno < p->pDirty->pgno ); } @@ -3165,7 +3165,7 @@ static int pagerWalFrames( pPager->aStat[PAGER_STAT_WRITE] += nList; if( pList->pgno==1 ) pager_write_changecounter(pList); - rc = sqlite3WalFrames(pPager->pWal, + rc = sqlite3WalFrames(pPager->pWal, pPager->pageSize, pList, nTruncate, isCommit, pPager->walSyncFlags ); if( rc==SQLITE_OK && pPager->pBackup ){ @@ -3269,7 +3269,7 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ #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 +** exists if the database is not empty, or verify that the *-wal file does ** not exist (by deleting it) if the database file is empty. ** ** If the database is not empty and the *-wal file exists, open the pager @@ -3280,9 +3280,9 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ ** Return SQLITE_OK or an error code. ** ** The caller must hold a SHARED lock on the database file to call this -** function. Because an EXCLUSIVE lock on the db file is required to delete -** a WAL on a none-empty database, this ensures there is no race condition -** between the xAccess() below and an xDelete() being executed by some +** function. Because an EXCLUSIVE lock on the db file is required to delete +** a WAL on a none-empty database, this ensures there is no race condition +** between the xAccess() below and an xDelete() being executed by some ** other connection. */ static int pagerOpenWalIfPresent(Pager *pPager){ @@ -3318,21 +3318,21 @@ static int pagerOpenWalIfPresent(Pager *pPager){ /* ** Playback savepoint pSavepoint. Or, if pSavepoint==NULL, then playback -** the entire super-journal file. The case pSavepoint==NULL occurs when -** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction +** the entire super-journal file. The case pSavepoint==NULL occurs when +** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction ** savepoint. ** -** When pSavepoint is not NULL (meaning a non-transaction savepoint is +** When pSavepoint is not NULL (meaning a non-transaction savepoint is ** being rolled back), then the rollback consists of up to three stages, ** performed in the order specified: ** ** * Pages are played back from the main journal starting at byte -** offset PagerSavepoint.iOffset and continuing to +** offset PagerSavepoint.iOffset and continuing to ** PagerSavepoint.iHdrOffset, or to the end of the main journal ** file if PagerSavepoint.iHdrOffset is zero. ** ** * If PagerSavepoint.iHdrOffset is not zero, then pages are played -** back starting from the journal header immediately following +** back starting from the journal header immediately following ** PagerSavepoint.iHdrOffset to the end of the main journal file. ** ** * Pages are then played back from the sub-journal file, starting @@ -3348,7 +3348,7 @@ static int pagerOpenWalIfPresent(Pager *pPager){ ** journal file. There is no need for a bitvec in this case. ** ** In either case, before playback commences the Pager.dbSize variable -** is reset to the value that it held at the start of the savepoint +** is reset to the value that it held at the start of the savepoint ** (or transaction). No page with a page-number greater than this value ** is played back. If one is encountered it is simply skipped. */ @@ -3369,7 +3369,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){ } } - /* Set the database size back to the value it was before the savepoint + /* Set the database size back to the value it was before the savepoint ** being reverted was opened. */ pPager->dbSize = pSavepoint ? pSavepoint->nOrig : pPager->dbOrigSize; @@ -3422,7 +3422,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){ ** test is related to ticket #2565. See the discussion in the ** pager_playback() function for additional information. */ - if( nJRec==0 + if( nJRec==0 && pPager->journalHdr+JOURNAL_HDR_SZ(pPager)==pPager->journalOff ){ nJRec = (u32)((szJ - pPager->journalOff)/JOURNAL_PG_SZ(pPager)); @@ -3596,7 +3596,7 @@ void sqlite3PagerSetFlags( /* ** The following global variable is incremented whenever the library ** attempts to open a temporary file. This information is used for -** testing and analysis only. +** testing and analysis only. */ #ifdef SQLITE_TEST int sqlite3_opentemp_count = 0; @@ -3605,8 +3605,8 @@ int sqlite3_opentemp_count = 0; /* ** Open a temporary file. ** -** Write the file descriptor into *pFile. Return SQLITE_OK on success -** or some other error code if we fail. The OS will automatically +** Write the file descriptor into *pFile. Return SQLITE_OK on success +** or some other error code if we fail. The OS will automatically ** delete the temporary file when it is closed. ** ** The flags passed to the VFS layer xOpen() call are those specified @@ -3638,9 +3638,9 @@ static int pagerOpentemp( /* ** Set the busy handler function. ** -** The pager invokes the busy-handler if sqlite3OsLock() returns +** The pager invokes the busy-handler if sqlite3OsLock() returns ** SQLITE_BUSY when trying to upgrade from no-lock to a SHARED lock, -** or when trying to upgrade from a RESERVED lock to an EXCLUSIVE +** or when trying to upgrade from a RESERVED lock to an EXCLUSIVE ** lock. It does *not* invoke the busy handler when upgrading from ** SHARED to RESERVED, or when upgrading from SHARED to EXCLUSIVE ** (which occurs during hot-journal rollback). Summary: @@ -3652,7 +3652,7 @@ static int pagerOpentemp( ** SHARED_LOCK -> EXCLUSIVE_LOCK | No ** RESERVED_LOCK -> EXCLUSIVE_LOCK | Yes ** -** If the busy-handler callback returns non-zero, the lock is +** If the busy-handler callback returns non-zero, the lock is ** retried. If it returns zero, then the SQLITE_BUSY error is ** returned to the caller of the pager API function. */ @@ -3671,16 +3671,16 @@ void sqlite3PagerSetBusyHandler( } /* -** Change the page size used by the Pager object. The new page size +** Change the page size used by the Pager object. The new page size ** is passed in *pPageSize. ** ** If the pager is in the error state when this function is called, it -** is a no-op. The value returned is the error state error code (i.e. +** is a no-op. The value returned is the error state error code (i.e. ** one of SQLITE_IOERR, an SQLITE_IOERR_xxx sub-code or SQLITE_FULL). ** ** Otherwise, if all of the following are true: ** -** * the new page size (value of *pPageSize) is valid (a power +** * the new page size (value of *pPageSize) is valid (a power ** of two between 512 and SQLITE_MAX_PAGE_SIZE, inclusive), and ** ** * there are no outstanding page references, and @@ -3690,14 +3690,14 @@ void sqlite3PagerSetBusyHandler( ** ** then the pager object page size is set to *pPageSize. ** -** If the page size is changed, then this function uses sqlite3PagerMalloc() -** to obtain a new Pager.pTmpSpace buffer. If this allocation attempt -** fails, SQLITE_NOMEM is returned and the page size remains unchanged. +** If the page size is changed, then this function uses sqlite3PagerMalloc() +** to obtain a new Pager.pTmpSpace buffer. If this allocation attempt +** fails, SQLITE_NOMEM is returned and the page size remains unchanged. ** In all other cases, SQLITE_OK is returned. ** ** If the page size is not changed, either because one of the enumerated ** conditions above is not true, the pager was in error state when this -** function was called, or because the memory allocation attempt failed, +** function was called, or because the memory allocation attempt failed, ** then *pPageSize is set to the old, retained page size before returning. */ int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){ @@ -3707,7 +3707,7 @@ int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){ ** function may be called from within PagerOpen(), before the state ** of the Pager object is internally consistent. ** - ** At one point this function returned an error if the pager was in + ** At one point this function returned an error if the pager was in ** PAGER_ERROR state. But since PAGER_ERROR state guarantees that ** there is at least one outstanding page reference, this function ** is a no-op for that case anyhow. @@ -3716,8 +3716,8 @@ int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){ u32 pageSize = *pPageSize; assert( pageSize==0 || (pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE) ); if( (pPager->memDb==0 || pPager->dbSize==0) - && sqlite3PcacheRefCount(pPager->pPCache)==0 - && pageSize && pageSize!=(u32)pPager->pageSize + && sqlite3PcacheRefCount(pPager->pPCache)==0 + && pageSize && pageSize!=(u32)pPager->pageSize ){ char *pNew = NULL; /* New temp space */ i64 nByte = 0; @@ -3774,7 +3774,7 @@ void *sqlite3PagerTempSpace(Pager *pPager){ } /* -** Attempt to set the maximum database page count if mxPage is positive. +** Attempt to set the maximum database page count if mxPage is positive. ** Make no changes if mxPage is zero or negative. And never reduce the ** maximum page count below the current size of the database. ** @@ -3818,11 +3818,11 @@ void enable_simulated_io_errors(void){ /* ** Read the first N bytes from the beginning of the file into memory -** that pDest points to. +** that pDest points to. ** ** If the pager was opened on a transient file (zFilename==""), or ** opened on a file less than N bytes in size, the output buffer is -** zeroed and SQLITE_OK returned. The rationale for this is that this +** zeroed and SQLITE_OK returned. The rationale for this is that this ** function is used to read database headers, and a new transient or ** zero sized database has a header than consists entirely of zeroes. ** @@ -3855,7 +3855,7 @@ int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned char *pDest){ ** This function may only be called when a read-transaction is open on ** the pager. It returns the total number of pages in the database. ** -** However, if the file is between 1 and bytes in size, then +** However, if the file is between 1 and bytes in size, then ** this is considered a 1 page file. */ void sqlite3PagerPagecount(Pager *pPager, int *pnPage){ @@ -3870,19 +3870,19 @@ void sqlite3PagerPagecount(Pager *pPager, int *pnPage){ ** a similar or greater lock is already held, this function is a no-op ** (returning SQLITE_OK immediately). ** -** Otherwise, attempt to obtain the lock using sqlite3OsLock(). Invoke -** the busy callback if the lock is currently not available. Repeat -** until the busy callback returns false or until the attempt to +** Otherwise, attempt to obtain the lock using sqlite3OsLock(). Invoke +** the busy callback if the lock is currently not available. Repeat +** until the busy callback returns false or until the attempt to ** obtain the lock succeeds. ** ** Return SQLITE_OK on success and an error code if we cannot obtain -** the lock. If the lock is obtained successfully, set the Pager.state +** the lock. If the lock is obtained successfully, set the Pager.state ** variable to locktype before returning. */ static int pager_wait_on_lock(Pager *pPager, int locktype){ int rc; /* Return code */ - /* Check that this is either a no-op (because the requested lock is + /* Check that this is either a no-op (because the requested lock is ** already held), or one of the transitions that the busy-handler ** may be invoked during, according to the comment above ** sqlite3PagerSetBusyhandler(). @@ -3899,10 +3899,10 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){ } /* -** Function assertTruncateConstraint(pPager) checks that one of the +** Function assertTruncateConstraint(pPager) checks that one of the ** following is true for all dirty pages currently in the page-cache: ** -** a) The page number is less than or equal to the size of the +** a) The page number is less than or equal to the size of the ** current database image, in pages, OR ** ** b) if the page content were written at this time, it would not @@ -3914,9 +3914,9 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){ ** the database file. If a savepoint transaction were rolled back after ** this happened, the correct behavior would be to restore the current ** content of the page. However, since this content is not present in either -** the database file or the portion of the rollback journal and +** the database file or the portion of the rollback journal and ** sub-journal rolled back the content could not be restored and the -** database image would become corrupt. It is therefore fortunate that +** database image would become corrupt. It is therefore fortunate that ** this circumstance cannot arise. */ #if defined(SQLITE_DEBUG) @@ -3940,9 +3940,9 @@ static void assertTruncateConstraint(Pager *pPager){ #endif /* -** Truncate the in-memory database file image to nPage pages. This -** function does not actually modify the database file on disk. It -** just sets the internal state of the pager object so that the +** Truncate the in-memory database file image to nPage pages. This +** function does not actually modify the database file on disk. It +** just sets the internal state of the pager object so that the ** truncation will be done when the current transaction is committed. ** ** This function is only called right before committing a transaction. @@ -3957,11 +3957,11 @@ void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){ /* At one point the code here called assertTruncateConstraint() to ** ensure that all pages being truncated away by this operation are, - ** if one or more savepoints are open, present in the savepoint + ** if one or more savepoints are open, present in the savepoint ** journal so that they can be restored if the savepoint is rolled ** back. This is no longer necessary as this function is now only - ** called right before committing a transaction. So although the - ** Pager object may still have open savepoints (Pager.nSavepoint!=0), + ** called right before committing a transaction. So although the + ** Pager object may still have open savepoints (Pager.nSavepoint!=0), ** they cannot be rolled back. So the assertTruncateConstraint() call ** is no longer correct. */ } @@ -3973,12 +3973,12 @@ void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){ ** size of the journal file so that the pager_playback() routine knows ** that the entire journal file has been synced. ** -** Syncing a hot-journal to disk before attempting to roll it back ensures +** Syncing a hot-journal to disk before attempting to roll it back ensures ** that if a power-failure occurs during the rollback, the process that ** attempts rollback following system recovery sees the same journal ** content as this process. ** -** If everything goes as planned, SQLITE_OK is returned. Otherwise, +** If everything goes as planned, SQLITE_OK is returned. Otherwise, ** an SQLite error code. */ static int pagerSyncHotJournal(Pager *pPager){ @@ -3994,7 +3994,7 @@ static int pagerSyncHotJournal(Pager *pPager){ #if SQLITE_MAX_MMAP_SIZE>0 /* -** Obtain a reference to a memory mapped page object for page number pgno. +** Obtain a reference to a memory mapped page object for page number pgno. ** The new object will use the pointer pData, obtained from xFetch(). ** If successful, set *ppPage to point to the new page reference ** and return SQLITE_OK. Otherwise, return an SQLite error code and set @@ -4010,7 +4010,7 @@ static int pagerAcquireMapPage( PgHdr **ppPage /* OUT: Acquired page object */ ){ PgHdr *p; /* Memory mapped page to return */ - + if( pPager->pMmapFreelist ){ *ppPage = p = pPager->pMmapFreelist; pPager->pMmapFreelist = p->pDirty; @@ -4044,7 +4044,7 @@ static int pagerAcquireMapPage( #endif /* -** Release a reference to page pPg. pPg must have been returned by an +** Release a reference to page pPg. pPg must have been returned by an ** earlier call to pagerAcquireMapPage(). */ static void pagerReleaseMapPage(PgHdr *pPg){ @@ -4104,7 +4104,7 @@ static int databaseIsUnmoved(Pager *pPager){ ** result in a coredump. ** ** This function always succeeds. If a transaction is active an attempt -** is made to roll it back. If an error occurs during the rollback +** is made to roll it back. If an error occurs during the rollback ** a hot journal may be left in the filesystem but no error is returned ** to the caller. */ @@ -4121,7 +4121,7 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ { u8 *a = 0; assert( db || pPager->pWal==0 ); - if( db && 0==(db->flags & SQLITE_NoCkptOnClose) + if( db && 0==(db->flags & SQLITE_NoCkptOnClose) && SQLITE_OK==databaseIsUnmoved(pPager) ){ a = pTmp; @@ -4135,8 +4135,8 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ pager_unlock(pPager); }else{ /* If it is open, sync the journal file before calling UnlockAndRollback. - ** If this is not done, then an unsynced portion of the open journal - ** file may be played back into the database. If a power failure occurs + ** If this is not done, then an unsynced portion of the open journal + ** file may be played back into the database. If a power failure occurs ** while this is happening, the database could become corrupt. ** ** If an error occurs while trying to sync the journal, shift the pager @@ -4187,7 +4187,7 @@ void sqlite3PagerRef(DbPage *pPg){ ** disk and can be restored in the event of a hot-journal rollback. ** ** If the Pager.noSync flag is set, then this function is a no-op. -** Otherwise, the actions required depend on the journal-mode and the +** Otherwise, the actions required depend on the journal-mode and the ** device characteristics of the file-system, as follows: ** ** * If the journal file is an in-memory journal file, no action need @@ -4199,7 +4199,7 @@ void sqlite3PagerRef(DbPage *pPg){ ** been written following it. If the pager is operating in full-sync ** mode, then the journal file is synced before this field is updated. ** -** * If the device does not support the SEQUENTIAL property, then +** * If the device does not support the SEQUENTIAL property, then ** journal file is synced. ** ** Or, in pseudo-code: @@ -4208,11 +4208,11 @@ void sqlite3PagerRef(DbPage *pPg){ ** if( NOT SAFE_APPEND ){ ** if( ) xSync(); ** -** } +** } ** if( NOT SEQUENTIAL ) xSync(); ** } ** -** If successful, this routine clears the PGHDR_NEED_SYNC flag of every +** If successful, this routine clears the PGHDR_NEED_SYNC flag of every ** page currently held in memory before returning SQLITE_OK. If an IO ** error is encountered, then the IO error code is returned to the caller. */ @@ -4240,10 +4240,10 @@ static int syncJournal(Pager *pPager, int newHdr){ ** mode, then the journal file may at this point actually be larger ** than Pager.journalOff bytes. If the next thing in the journal ** file happens to be a journal-header (written as part of the - ** previous connection's transaction), and a crash or power-failure - ** occurs after nRec is updated but before this connection writes - ** anything else to the journal file (or commits/rolls back its - ** transaction), then SQLite may become confused when doing the + ** previous connection's transaction), and a crash or power-failure + ** occurs after nRec is updated but before this connection writes + ** anything else to the journal file (or commits/rolls back its + ** transaction), then SQLite may become confused when doing the ** hot-journal rollback following recovery. It may roll back all ** of this connections data, then proceed to rolling back the old, ** out-of-date data that follows it. Database corruption. @@ -4253,7 +4253,7 @@ static int syncJournal(Pager *pPager, int newHdr){ ** byte to the start of it to prevent it from being recognized. ** ** Variable iNextHdrOffset is set to the offset at which this - ** problematic header will occur, if it exists. aMagic is used + ** problematic header will occur, if it exists. aMagic is used ** as a temporary buffer to inspect the first couple of bytes of ** the potential journal header. */ @@ -4280,7 +4280,7 @@ static int syncJournal(Pager *pPager, int newHdr){ ** it as a candidate for rollback. ** ** This is not required if the persistent media supports the - ** SAFE_APPEND property. Because in this case it is not possible + ** SAFE_APPEND property. Because in this case it is not possible ** for garbage data to be appended to the file, the nRec field ** is populated with 0xFFFFFFFF when the journal header is written ** and never needs to be updated. @@ -4300,7 +4300,7 @@ 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->syncFlags| + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags| (pPager->syncFlags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0) ); if( rc!=SQLITE_OK ) return rc; @@ -4317,8 +4317,8 @@ static int syncJournal(Pager *pPager, int newHdr){ } } - /* Unless the pager is in noSync mode, the journal file was just - ** successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on + /* Unless the pager is in noSync mode, the journal file was just + ** successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on ** all pages. */ sqlite3PcacheClearSyncFlags(pPager->pPCache); @@ -4338,9 +4338,9 @@ static int syncJournal(Pager *pPager, int newHdr){ ** is called. Before writing anything to the database file, this lock ** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, ** SQLITE_BUSY is returned and no data is written to the database file. -** +** ** If the pager is a temp-file pager and the actual file-system file -** is not yet open, it is created and opened before any data is +** is not yet open, it is created and opened before any data is ** written out. ** ** Once the lock has been upgraded and, if necessary, the file opened, @@ -4355,7 +4355,7 @@ static int syncJournal(Pager *pPager, int newHdr){ ** in Pager.dbFileVers[] is updated to match the new value stored in ** the database file. ** -** If everything is successful, SQLITE_OK is returned. If an IO error +** If everything is successful, SQLITE_OK is returned. If an IO error ** occurs, an IO error code is returned. Or, if the EXCLUSIVE lock cannot ** be obtained, SQLITE_BUSY is returned. */ @@ -4381,7 +4381,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ ** file size will be. */ assert( rc!=SQLITE_OK || isOpen(pPager->fd) ); - if( rc==SQLITE_OK + if( rc==SQLITE_OK && pPager->dbHintSizedbSize && (pList->pDirty || pList->pgno>pPager->dbHintSize) ){ @@ -4403,7 +4403,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ */ if( pgno<=pPager->dbSize && 0==(pList->flags&PGHDR_DONT_WRITE) ){ i64 offset = (pgno-1)*(i64)pPager->pageSize; /* Offset to write */ - char *pData; /* Data to write */ + char *pData; /* Data to write */ assert( (pList->flags&PGHDR_NEED_SYNC)==0 ); if( pList->pgno==1 ) pager_write_changecounter(pList); @@ -4414,8 +4414,8 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset); /* If page 1 was just written, update Pager.dbFileVers to match - ** the value now stored in the database file. If writing this - ** page caused the database file to grow, update dbFileSize. + ** the value now stored in the database file. If writing this + ** page caused the database file to grow, update dbFileSize. */ if( pgno==1 ){ memcpy(&pPager->dbFileVers, &pData[24], sizeof(pPager->dbFileVers)); @@ -4443,18 +4443,18 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ } /* -** Ensure that the sub-journal file is open. If it is already open, this +** Ensure that the sub-journal file is open. If it is already open, this ** function is a no-op. ** -** SQLITE_OK is returned if everything goes according to plan. An -** SQLITE_IOERR_XXX error code is returned if a call to sqlite3OsOpen() +** SQLITE_OK is returned if everything goes according to plan. An +** SQLITE_IOERR_XXX error code is returned if a call to sqlite3OsOpen() ** fails. */ static int openSubJournal(Pager *pPager){ int rc = SQLITE_OK; if( !isOpen(pPager->sjfd) ){ - const int flags = SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_READWRITE - | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE + const int flags = SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE; int nStmtSpill = sqlite3Config.nStmtSpill; if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY || pPager->subjInMemory ){ @@ -4466,13 +4466,13 @@ static int openSubJournal(Pager *pPager){ } /* -** Append a record of the current state of page pPg to the sub-journal. +** Append a record of the current state of page pPg to the sub-journal. ** ** If successful, set the bit corresponding to pPg->pgno in the bitvecs ** for all open savepoints before returning. ** ** This function returns SQLITE_OK if everything is successful, an IO -** error code if the attempt to write to the sub-journal fails, or +** error code if the attempt to write to the sub-journal fails, or ** SQLITE_NOMEM if a malloc fails while setting a bit in a savepoint ** bitvec. */ @@ -4485,9 +4485,9 @@ static int subjournalPage(PgHdr *pPg){ assert( pPager->useJournal ); assert( isOpen(pPager->jfd) || pagerUseWal(pPager) ); assert( isOpen(pPager->sjfd) || pPager->nSubRec==0 ); - assert( pagerUseWal(pPager) - || pageInJournal(pPager, pPg) - || pPg->pgno>pPager->dbOrigSize + assert( pagerUseWal(pPager) + || pageInJournal(pPager, pPg) + || pPg->pgno>pPager->dbOrigSize ); rc = openSubJournal(pPager); @@ -4524,14 +4524,14 @@ static int subjournalPageIfRequired(PgHdr *pPg){ ** This function is called by the pcache layer when it has reached some ** soft memory limit. The first argument is a pointer to a Pager object ** (cast as a void*). The pager is always 'purgeable' (not an in-memory -** database). The second argument is a reference to a page that is +** database). The second argument is a reference to a page that is ** currently dirty but has no outstanding references. The page -** is always associated with the Pager object passed as the first +** is always associated with the Pager object passed as the first ** argument. ** ** The job of this function is to make pPg clean by writing its contents ** out to the database file, if possible. This may involve syncing the -** journal file. +** journal file. ** ** If successful, sqlite3PcacheMakeClean() is called on the page and ** SQLITE_OK returned. If an IO error occurs while trying to make the @@ -4556,7 +4556,7 @@ static int pagerStress(void *p, PgHdr *pPg){ ** a rollback or by user request, respectively. ** ** Spilling is also prohibited when in an error state since that could - ** lead to database corruption. In the current implementation it + ** lead to database corruption. In the current implementation it ** is impossible for sqlite3PcacheFetch() to be called with createFlag==3 ** while in the error state, hence it is impossible for this routine to ** be called in the error state. Nevertheless, we include a NEVER() @@ -4577,26 +4577,26 @@ static int pagerStress(void *p, PgHdr *pPg){ pPg->pDirty = 0; if( pagerUseWal(pPager) ){ /* Write a single frame for this page to the log. */ - rc = subjournalPageIfRequired(pPg); + rc = subjournalPageIfRequired(pPg); if( rc==SQLITE_OK ){ rc = pagerWalFrames(pPager, pPg, 0, 0); } }else{ - + #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE if( pPager->tempFile==0 ){ rc = sqlite3JournalCreate(pPager->jfd); if( rc!=SQLITE_OK ) return pager_error(pPager, rc); } #endif - + /* Sync the journal file if required. */ - if( pPg->flags&PGHDR_NEED_SYNC + if( pPg->flags&PGHDR_NEED_SYNC || pPager->eState==PAGER_WRITER_CACHEMOD ){ rc = syncJournal(pPager, 1); } - + /* Write the contents of the page out to the database file. */ if( rc==SQLITE_OK ){ assert( (pPg->flags&PGHDR_NEED_SYNC)==0 ); @@ -4610,7 +4610,7 @@ static int pagerStress(void *p, PgHdr *pPg){ sqlite3PcacheMakeClean(pPg); } - return pager_error(pPager, rc); + return pager_error(pPager, rc); } /* @@ -4641,8 +4641,8 @@ int sqlite3PagerFlush(Pager *pPager){ ** The zFilename argument is the path to the database file to open. ** If zFilename is NULL then a randomly-named temporary file is created ** and used as the file to be cached. Temporary files are be deleted -** automatically when they are closed. If zFilename is ":memory:" then -** all information is held in cache. It is never written to disk. +** automatically when they are closed. If zFilename is ":memory:" then +** all information is held in cache. It is never written to disk. ** This can be used to implement an in-memory database. ** ** The nExtra parameter specifies the number of bytes of space allocated @@ -4656,13 +4656,13 @@ int sqlite3PagerFlush(Pager *pPager){ ** of the PAGER_* flags. ** ** The vfsFlags parameter is a bitmask to pass to the flags parameter -** of the xOpen() method of the supplied VFS when opening files. +** of the xOpen() method of the supplied VFS when opening files. ** -** If the pager object is allocated and the specified file opened +** If the pager object is allocated and the specified file opened ** successfully, SQLITE_OK is returned and *ppPager set to point to ** the new pager object. If an error occurs, *ppPager is set to NULL ** and error code returned. This function may return SQLITE_NOMEM -** (sqlite3Malloc() is used to allocate memory), SQLITE_CANTOPEN or +** (sqlite3Malloc() is used to allocate memory), SQLITE_CANTOPEN or ** various SQLITE_IO_XXX errors. */ int sqlite3PagerOpen( @@ -4679,11 +4679,7 @@ int sqlite3PagerOpen( int rc = SQLITE_OK; /* Return code */ int tempFile = 0; /* True for temp files (incl. in-memory files) */ int memDb = 0; /* True if this is an in-memory file */ -#ifndef SQLITE_OMIT_DESERIALIZE int memJM = 0; /* Memory journal mode */ -#else -# define memJM 0 -#endif int readOnly = 0; /* True if this is a read-only file */ int journalFileSize; /* Bytes to allocate for each journal fd */ char *zPathname = 0; /* Full path to database file */ @@ -4759,7 +4755,7 @@ int sqlite3PagerOpen( } /* Allocate memory for the Pager structure, PCache object, the - ** three file descriptors, the database file name and the journal + ** three file descriptors, the database file name and the journal ** file name. The layout in memory is as follows: ** ** Pager object (sizeof(Pager) bytes) @@ -4802,12 +4798,13 @@ int sqlite3PagerOpen( ** specific formatting and order of the various filenames, so if the format ** changes here, be sure to change it there as well. */ + assert( SQLITE_PTRSIZE==sizeof(Pager*) ); pPtr = (u8 *)sqlite3MallocZero( ROUND8(sizeof(*pPager)) + /* Pager structure */ ROUND8(pcacheSize) + /* PCache object */ ROUND8(pVfs->szOsFile) + /* The main db file */ journalFileSize * 2 + /* The two journal files */ - sizeof(pPager) + /* Space to hold a pointer */ + SQLITE_PTRSIZE + /* Space to hold a pointer */ 4 + /* Database prefix */ nPathname + 1 + /* database filename */ nUriByte + /* query parameters */ @@ -4828,7 +4825,7 @@ int sqlite3PagerOpen( pPager->sjfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; pPager->jfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; assert( EIGHT_BYTE_ALIGNMENT(pPager->jfd) ); - memcpy(pPtr, &pPager, sizeof(pPager)); pPtr += sizeof(pPager); + memcpy(pPtr, &pPager, SQLITE_PTRSIZE); pPtr += SQLITE_PTRSIZE; /* Fill in the Pager.zFilename and pPager.zQueryParam fields */ pPtr += 4; /* Skip zero prefix */ @@ -4882,9 +4879,7 @@ int sqlite3PagerOpen( int fout = 0; /* VFS flags returned by xOpen() */ rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); assert( !memDb ); -#ifndef SQLITE_OMIT_DESERIALIZE pPager->memVfs = memJM = (fout&SQLITE_OPEN_MEMORY)!=0; -#endif readOnly = (fout&SQLITE_OPEN_READONLY)!=0; /* If the file was successfully opened for read/write access, @@ -4938,7 +4933,7 @@ int sqlite3PagerOpen( ** disk and uses an in-memory rollback journal. ** ** This branch also runs for files marked as immutable. - */ + */ act_like_temp_file: tempFile = 1; pPager->eState = PAGER_READER; /* Pretend we already have a lock */ @@ -4947,7 +4942,7 @@ int sqlite3PagerOpen( readOnly = (vfsFlags&SQLITE_OPEN_READONLY); } - /* The following call to PagerSetPagesize() serves to set the value of + /* The following call to PagerSetPagesize() serves to set the value of ** Pager.pageSize and to allocate the Pager.pTmpSpace buffer. */ if( rc==SQLITE_OK ){ @@ -4987,10 +4982,10 @@ int sqlite3PagerOpen( /* pPager->state = PAGER_UNLOCK; */ /* pPager->errMask = 0; */ pPager->tempFile = (u8)tempFile; - assert( tempFile==PAGER_LOCKINGMODE_NORMAL + assert( tempFile==PAGER_LOCKINGMODE_NORMAL || tempFile==PAGER_LOCKINGMODE_EXCLUSIVE ); assert( PAGER_LOCKINGMODE_EXCLUSIVE==1 ); - pPager->exclusiveMode = (u8)tempFile; + pPager->exclusiveMode = (u8)tempFile; pPager->changeCountDone = pPager->tempFile; pPager->memDb = (u8)memDb; pPager->readOnly = (u8)readOnly; @@ -5021,7 +5016,7 @@ int sqlite3PagerOpen( /* ** Return the sqlite3_file for the main database given the name -** of the corresonding WAL or Journal name as passed into +** of the corresponding WAL or Journal name as passed into ** xOpen. */ sqlite3_file *sqlite3_database_file_object(const char *zName){ @@ -5037,7 +5032,7 @@ sqlite3_file *sqlite3_database_file_object(const char *zName){ /* ** This function is called after transitioning from PAGER_UNLOCK to ** PAGER_SHARED state. It tests if there is a hot journal present in -** the file-system for the given pager. A hot journal is one that +** the file-system for the given pager. A hot journal is one that ** needs to be played back. According to this function, a hot-journal ** file exists if the following criteria are met: ** @@ -5056,10 +5051,10 @@ sqlite3_file *sqlite3_database_file_object(const char *zName){ ** at the end of the file. If there is, and that super-journal file ** does not exist, then the journal file is not really hot. In this ** case this routine will return a false-positive. The pager_playback() -** routine will discover that the journal file is not really hot and -** will not roll it back. +** routine will discover that the journal file is not really hot and +** will not roll it back. ** -** If a hot-journal file is found to exist, *pExists is set to 1 and +** If a hot-journal file is found to exist, *pExists is set to 1 and ** SQLITE_OK returned. If no hot-journal file is present, *pExists is ** set to 0 and SQLITE_OK returned. If an IO error occurs while trying ** to determine whether or not a hot-journal file exists, the IO error @@ -5087,7 +5082,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ int locked = 0; /* True if some process holds a RESERVED lock */ /* Race condition here: Another process might have been holding the - ** the RESERVED lock and have a journal open at the sqlite3OsAccess() + ** the RESERVED lock and have a journal open at the sqlite3OsAccess() ** call above, but then delete the journal and drop the lock before ** we get to the following sqlite3OsCheckReservedLock() call. If that ** is the case, this routine might think there is a hot journal when @@ -5120,7 +5115,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ /* The journal file exists and no other connection has a reserved ** or greater lock on the database file. Now check that there is ** at least one non-zero bytes at the start of the journal file. - ** If there is, then we consider this journal to be hot. If not, + ** If there is, then we consider this journal to be hot. If not, ** it can be ignored. */ if( !jrnlOpen ){ @@ -5170,7 +5165,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ ** on the database file), then an attempt is made to obtain a ** SHARED lock on the database file. Immediately after obtaining ** the SHARED lock, the file-system is checked for a hot-journal, -** which is played back if present. Following any hot-journal +** which is played back if present. Following any hot-journal ** rollback, the contents of the cache are validated by checking ** the 'change-counter' field of the database file header and ** discarded if they are found to be invalid. @@ -5181,8 +5176,8 @@ static int hasHotJournal(Pager *pPager, int *pExists){ ** the contents of the page cache and rolling back any open journal ** file. ** -** If everything is successful, SQLITE_OK is returned. If an IO error -** occurs while locking the database, checking for a hot-journal file or +** If everything is successful, SQLITE_OK is returned. If an IO error +** occurs while locking the database, checking for a hot-journal file or ** rolling back a journal file, the IO error code is returned. */ int sqlite3PagerSharedLock(Pager *pPager){ @@ -5190,7 +5185,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ /* This routine is only called from b-tree and only when there are no ** outstanding pages. This implies that the pager state should either - ** be OPEN or READER. READER is only possible if the pager is or was in + ** be OPEN or READER. READER is only possible if the pager is or was in ** exclusive access mode. */ assert( sqlite3PcacheRefCount(pPager->pPCache)==0 ); assert( assert_pager_state(pPager) ); @@ -5228,12 +5223,12 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** important that a RESERVED lock is not obtained on the way to the ** EXCLUSIVE lock. If it were, another process might open the ** database file, detect the RESERVED lock, and conclude that the - ** database is safe to read while this process is still rolling the + ** database is safe to read while this process is still rolling the ** hot-journal back. - ** + ** ** Because the intermediate RESERVED lock is not requested, any - ** other process attempting to access the database file will get to - ** this point in the code and fail to obtain its own EXCLUSIVE lock + ** other process attempting to access the database file will get to + ** this point in the code and fail to obtain its own EXCLUSIVE lock ** on the database file. ** ** Unless the pager is in locking_mode=exclusive mode, the lock is @@ -5243,17 +5238,17 @@ int sqlite3PagerSharedLock(Pager *pPager){ if( rc!=SQLITE_OK ){ goto failed; } - - /* If it is not already open and the file exists on disk, open the - ** journal for read/write access. Write access is required because - ** in exclusive-access mode the file descriptor will be kept open - ** and possibly used for a transaction later on. Also, write-access - ** is usually required to finalize the journal in journal_mode=persist + + /* If it is not already open and the file exists on disk, open the + ** journal for read/write access. Write access is required because + ** in exclusive-access mode the file descriptor will be kept open + ** and possibly used for a transaction later on. Also, write-access + ** is usually required to finalize the journal in journal_mode=persist ** mode (and also for journal_mode=truncate on some systems). ** - ** If the journal does not exist, it usually means that some - ** other connection managed to get in and roll it back before - ** this connection obtained the exclusive lock above. Or, it + ** If the journal does not exist, it usually means that some + ** other connection managed to get in and roll it back before + ** this connection obtained the exclusive lock above. Or, it ** may mean that the pager was in the error-state when this ** function was called and the journal file does not exist. */ @@ -5274,7 +5269,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ } } } - + /* Playback and delete the journal. Drop the database write ** lock and reacquire the read lock. Purge the cache before ** playing back the hot-journal so that we don't end up with @@ -5299,8 +5294,8 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** or roll back a hot-journal while holding an EXCLUSIVE lock. The ** pager_unlock() routine will be called before returning to unlock ** the file. If the unlock attempt fails, then Pager.eLock must be - ** set to UNKNOWN_LOCK (see the comment above the #define for - ** UNKNOWN_LOCK above for an explanation). + ** set to UNKNOWN_LOCK (see the comment above the #define for + ** UNKNOWN_LOCK above for an explanation). ** ** In order to get pager_unlock() to do this, set Pager.eState to ** PAGER_ERROR now. This is not actually counted as a transition @@ -5308,7 +5303,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** since we know that the same call to pager_unlock() will very ** shortly transition the pager object to the OPEN state. Calling ** assert_pager_state() would fail now, as it should not be possible - ** to be in ERROR state when there are zero outstanding page + ** to be in ERROR state when there are zero outstanding page ** references. */ pager_error(pPager, rc); @@ -5333,8 +5328,8 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** a 32-bit counter that is incremented with each change. The ** other bytes change randomly with each file change when ** a codec is in use. - ** - ** There is a vanishingly small chance that a change will not be + ** + ** There is a vanishingly small chance that a change will not be ** detected. The chance of an undetected change is so small that ** it can be neglected. */ @@ -5401,7 +5396,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** Except, in locking_mode=EXCLUSIVE when there is nothing to in ** the rollback journal, the unlock is not performed and there is ** nothing to rollback, so this routine is a no-op. -*/ +*/ static void pagerUnlockIfUnused(Pager *pPager){ if( sqlite3PcacheRefCount(pPager->pPCache)==0 ){ assert( pPager->nMmapOut==0 ); /* because page1 is never memory mapped */ @@ -5411,7 +5406,7 @@ static void pagerUnlockIfUnused(Pager *pPager){ /* ** The page getter methods each try to acquire a reference to a -** page with page number pgno. If the requested reference is +** page with page number pgno. If the requested reference is ** successfully obtained, it is copied to *ppPage and SQLITE_OK returned. ** ** There are different implementations of the getter method depending @@ -5421,22 +5416,22 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** getPageError() -- Used if the pager is in an error state ** getPageMmap() -- Used if memory-mapped I/O is enabled ** -** If the requested page is already in the cache, it is returned. +** If the requested page is already in the cache, it is returned. ** Otherwise, a new page object is allocated and populated with data ** read from the database file. In some cases, the pcache module may ** choose not to allocate a new page object and may reuse an existing ** object with no outstanding references. ** -** The extra data appended to a page is always initialized to zeros the -** first time a page is loaded into memory. If the page requested is +** The extra data appended to a page is always initialized to zeros the +** first time a page is loaded into memory. If the page requested is ** already in the cache when this function is called, then the extra ** data is left as it was when the page object was last used. ** -** If the database image is smaller than the requested page or if -** the flags parameter contains the PAGER_GET_NOCONTENT bit and the -** requested page is not already stored in the cache, then no -** actual disk read occurs. In this case the memory image of the -** page is initialized to all zeros. +** If the database image is smaller than the requested page or if +** the flags parameter contains the PAGER_GET_NOCONTENT bit and the +** requested page is not already stored in the cache, then no +** actual disk read occurs. In this case the memory image of the +** page is initialized to all zeros. ** ** If PAGER_GET_NOCONTENT is true, it means that we do not care about ** the contents of the page. This occurs in two scenarios: @@ -5507,7 +5502,7 @@ static int getPageNormal( return SQLITE_OK; }else{ - /* The pager cache has created a new page. Its content needs to + /* The pager cache has created a new page. Its content needs to ** be initialized. But first some error checks: ** ** (*) obsolete. Was: maximum page number is 2^31 @@ -5532,9 +5527,9 @@ static int getPageNormal( } if( noContent ){ /* Failure to set the bits in the InJournal bit-vectors is benign. - ** It merely means that we might do some extra work to journal a - ** page that does not need to be journaled. Nevertheless, be sure - ** to test the case where a malloc error occurs while trying to set + ** It merely means that we might do some extra work to journal a + ** page that does not need to be journaled. Nevertheless, be sure + ** to test the case where a malloc error occurs while trying to set ** a bit in a bit vector. */ sqlite3BeginBenignMalloc(); @@ -5584,7 +5579,7 @@ static int getPageMMap( /* It is acceptable to use a read-only (mmap) page for any page except ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY - ** flag was specified by the caller. And so long as the db is not a + ** flag was specified by the caller. And so long as the db is not a ** temporary or in-memory database. */ const int bMmapOk = (pgno>1 && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY)) @@ -5613,7 +5608,7 @@ static int getPageMMap( } if( bMmapOk && iFrame==0 ){ void *pData = 0; - rc = sqlite3OsFetch(pPager->fd, + rc = sqlite3OsFetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData ); if( rc==SQLITE_OK && pData ){ @@ -5670,12 +5665,12 @@ int sqlite3PagerGet( /* ** Acquire a page if it is already in the in-memory cache. Do ** not read the page from disk. Return a pointer to the page, -** or 0 if the page is not in cache. +** or 0 if the page is not in cache. ** ** See also sqlite3PagerGet(). The difference between this routine ** and sqlite3PagerGet() is that _get() will go to the disk and read ** in the page if the page is not already in cache. This routine -** returns NULL if the page is not in cache or if a disk I/O error +** returns NULL if the page is not in cache or if a disk I/O error ** has ever happened. */ DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ @@ -5730,24 +5725,24 @@ void sqlite3PagerUnrefPageOne(DbPage *pPg){ /* ** This function is called at the start of every write transaction. -** There must already be a RESERVED or EXCLUSIVE lock on the database +** There must already be a RESERVED or EXCLUSIVE lock on the database ** file when this routine is called. ** ** Open the journal file for pager pPager and write a journal header ** to the start of it. If there are active savepoints, open the sub-journal -** as well. This function is only used when the journal file is being -** opened to write a rollback log for a transaction. It is not used +** as well. This function is only used when the journal file is being +** opened to write a rollback log for a transaction. It is not used ** when opening a hot journal file to roll it back. ** ** If the journal file is already open (as it may be in exclusive mode), ** then this function just writes a journal header to the start of the -** already open file. +** already open file. ** ** Whether or not the journal file is opened by this function, the ** Pager.pInJournal bitvec structure is allocated. ** -** Return SQLITE_OK if everything is successful. Otherwise, return -** SQLITE_NOMEM if the attempt to allocate Pager.pInJournal fails, or +** Return SQLITE_OK if everything is successful. Otherwise, return +** SQLITE_NOMEM if the attempt to allocate Pager.pInJournal fails, or ** an IO error code if opening or writing the journal file fails. */ static int pager_open_journal(Pager *pPager){ @@ -5757,7 +5752,7 @@ static int pager_open_journal(Pager *pPager){ assert( pPager->eState==PAGER_WRITER_LOCKED ); assert( assert_pager_state(pPager) ); assert( pPager->pInJournal==0 ); - + /* If already in the error state, this function is a no-op. But on ** the other hand, this routine is never called if we are already in ** an error state. */ @@ -5768,7 +5763,7 @@ static int pager_open_journal(Pager *pPager){ if( pPager->pInJournal==0 ){ return SQLITE_NOMEM_BKPT; } - + /* Open the journal file if it is not already open. */ if( !isOpen(pPager->jfd) ){ if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){ @@ -5785,7 +5780,7 @@ static int pager_open_journal(Pager *pPager){ flags |= SQLITE_OPEN_MAIN_JOURNAL; nSpill = jrnlBufferSize(pPager); } - + /* Verify that the database still has the same name as it did when ** it was originally opened. */ rc = databaseIsUnmoved(pPager); @@ -5797,9 +5792,9 @@ static int pager_open_journal(Pager *pPager){ } assert( rc!=SQLITE_OK || isOpen(pPager->jfd) ); } - - - /* Write the first journal header to the journal file and open + + + /* Write the first journal header to the journal file and open ** the sub-journal if necessary. */ if( rc==SQLITE_OK ){ @@ -5825,12 +5820,12 @@ static int pager_open_journal(Pager *pPager){ } /* -** Begin a write-transaction on the specified pager object. If a +** Begin a write-transaction on the specified pager object. If a ** write-transaction has already been opened, this function is a no-op. ** ** If the exFlag argument is false, then acquire at least a RESERVED ** lock on the database file. If exFlag is true, then acquire at least -** an EXCLUSIVE lock. If such a lock is already held, no locking +** an EXCLUSIVE lock. If such a lock is already held, no locking ** functions need be called. ** ** If the subjInMemory argument is non-zero, then any sub-journal opened @@ -5838,7 +5833,7 @@ static int pager_open_journal(Pager *pPager){ ** has no effect if the sub-journal is already opened (as it may be when ** running in exclusive mode) or if the transaction does not require a ** sub-journal. If the subjInMemory argument is zero, then any required -** sub-journal is implemented in-memory if pPager is an in-memory database, +** sub-journal is implemented in-memory if pPager is an in-memory database, ** or using a temporary file otherwise. */ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){ @@ -5886,9 +5881,9 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){ ** ** WAL mode sets Pager.eState to PAGER_WRITER_LOCKED or CACHEMOD ** when it has an open transaction, but never to DBMOD or FINISHED. - ** This is because in those states the code to roll back savepoint - ** transactions may copy data from the sub-journal into the database - ** file as well as into the page cache. Which would be incorrect in + ** This is because in those states the code to roll back savepoint + ** transactions may copy data from the sub-journal into the database + ** file as well as into the page cache. Which would be incorrect in ** WAL mode. */ pPager->eState = PAGER_WRITER_LOCKED; @@ -5942,11 +5937,11 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ rc = write32bits(pPager->jfd, iOff+pPager->pageSize+4, cksum); if( rc!=SQLITE_OK ) return rc; - IOTRACE(("JOUT %p %d %lld %d\n", pPager, pPg->pgno, + IOTRACE(("JOUT %p %d %lld %d\n", pPager, pPg->pgno, pPager->journalOff, pPager->pageSize)); PAGER_INCR(sqlite3_pager_writej_count); PAGERTRACE(("JOURNAL %d page %d needSync=%d hash(%08x)\n", - PAGERID(pPager), pPg->pgno, + PAGERID(pPager), pPg->pgno, ((pPg->flags&PGHDR_NEED_SYNC)?1:0), pager_pagehash(pPg))); pPager->journalOff += 8 + pPager->pageSize; @@ -5961,9 +5956,9 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ } /* -** Mark a single data page as writeable. The page is written into the +** Mark a single data page as writeable. The page is written into the ** main journal or sub-journal as required. If the page is written into -** one of the journals, the corresponding bit is set in the +** one of the journals, the corresponding bit is set in the ** Pager.pInJournal bitvec and the PagerSavepoint.pInSavepoint bitvecs ** of any open savepoints as appropriate. */ @@ -5971,7 +5966,7 @@ static int pager_write(PgHdr *pPg){ Pager *pPager = pPg->pPager; int rc = SQLITE_OK; - /* This routine is not called unless a write-transaction has already + /* This routine is not called unless a write-transaction has already ** been started. The journal file may or may not be open at this point. ** It is never called in the ERROR state. */ @@ -5988,7 +5983,7 @@ static int pager_write(PgHdr *pPg){ ** 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. + ** 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. @@ -6033,7 +6028,7 @@ static int pager_write(PgHdr *pPg){ ** PGHDR_WRITEABLE bit that indicates that the page can be safely modified. */ pPg->flags |= PGHDR_WRITEABLE; - + /* If the statement journal is open and the page is not in it, ** then write the page into the statement journal. */ @@ -6117,7 +6112,7 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ } } - /* If the PGHDR_NEED_SYNC flag is set for any of the nPage pages + /* If the PGHDR_NEED_SYNC flag is set for any of the nPage pages ** starting at pg1, then it needs to be set for all of them. Because ** writing to any of these nPage pages may damage the others, the ** journal file must contain sync()ed copies of all of them @@ -6140,9 +6135,9 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ } /* -** Mark a data page as writeable. This routine must be called before -** making changes to a page. The caller must check the return value -** of this function and be careful not to change any page data unless +** Mark a data page as writeable. This routine must be called before +** making changes to a page. The caller must check the return value +** of this function and be careful not to change any page data unless ** this routine returns SQLITE_OK. ** ** The difference between this function and pager_write() is that this @@ -6193,13 +6188,13 @@ int sqlite3PagerIswriteable(DbPage *pPg){ ** on the given page is unused. The pager marks the page as clean so ** that it does not get written to disk. ** -** Tests show that this optimization can quadruple the speed of large +** Tests show that this optimization can quadruple the speed of large ** DELETE operations. ** ** This optimization cannot be used with a temp-file, as the page may ** have been dirty at the start of the transaction. In that case, if -** memory pressure forces page pPg out of the cache, the data does need -** to be written out to disk so that it may be read back in if the +** memory pressure forces page pPg out of the cache, the data does need +** to be written out to disk so that it may be read back in if the ** current transaction is rolled back. */ void sqlite3PagerDontWrite(PgHdr *pPg){ @@ -6215,17 +6210,17 @@ 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 +** 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. 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 +** 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 +** If the isDirectMode flag is zero, then this is done by calling ** sqlite3PagerWrite() on page 1, then modifying the contents of the ** page data. In this case the file will be updated when the current ** transaction is committed. @@ -6233,7 +6228,7 @@ void sqlite3PagerDontWrite(PgHdr *pPg){ ** The isDirectMode flag may only be non-zero if the library was compiled ** with the SQLITE_ENABLE_ATOMIC_WRITE macro defined. In this case, ** if isDirect is non-zero, then the database file is updated directly -** by writing an updated version of page 1 using a call to the +** by writing an updated version of page 1 using a call to the ** sqlite3OsWrite() function. */ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ @@ -6272,7 +6267,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ assert( pPgHdr==0 || rc==SQLITE_OK ); /* If page one was fetched successfully, and this function is not - ** operating in direct-mode, make page 1 writable. When not in + ** operating in direct-mode, make page 1 writable. When not in ** direct mode, page 1 is always held in cache and hence the PagerGet() ** above is always successful - hence the ALWAYS on rc==SQLITE_OK. */ @@ -6333,22 +6328,22 @@ int sqlite3PagerSync(Pager *pPager, const char *zSuper){ /* ** This function may only be called while a write-transaction is active in -** rollback. If the connection is in WAL mode, this call is a no-op. -** Otherwise, if the connection does not already have an EXCLUSIVE lock on +** rollback. If the connection is in WAL mode, this call is a no-op. +** Otherwise, if the connection does not already have an EXCLUSIVE lock on ** the database file, an attempt is made to obtain one. ** ** If the EXCLUSIVE lock is already held or the attempt to obtain it is ** successful, or the connection is in WAL mode, SQLITE_OK is returned. -** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is +** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is ** returned. */ int sqlite3PagerExclusiveLock(Pager *pPager){ int rc = pPager->errCode; assert( assert_pager_state(pPager) ); if( rc==SQLITE_OK ){ - assert( pPager->eState==PAGER_WRITER_CACHEMOD - || pPager->eState==PAGER_WRITER_DBMOD - || pPager->eState==PAGER_WRITER_LOCKED + assert( pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + || pPager->eState==PAGER_WRITER_LOCKED ); assert( assert_pager_state(pPager) ); if( 0==pagerUseWal(pPager) ){ @@ -6361,19 +6356,19 @@ int sqlite3PagerExclusiveLock(Pager *pPager){ /* ** Sync the database file for the pager pPager. zSuper points to the name ** of a super-journal file that should be written into the individual -** journal file. zSuper may be NULL, which is interpreted as no +** journal file. zSuper may be NULL, which is interpreted as no ** super-journal (a single database transaction). ** ** This routine ensures that: ** ** * The database file change-counter is updated, ** * the journal is synced (unless the atomic-write optimization is used), -** * all dirty pages are written to the database file, +** * all dirty pages are written to the database file, ** * the database file is truncated (if required), and -** * the database file synced. +** * the database file synced. ** -** The only thing that remains to commit the transaction is to finalize -** (delete, truncate or zero the first part of) the journal file (or +** The only thing that remains to commit the transaction is to finalize +** (delete, truncate or zero the first part of) the journal file (or ** delete the super-journal file if specified). ** ** Note that if zSuper==NULL, this does not overwrite a previous value @@ -6404,7 +6399,7 @@ int sqlite3PagerCommitPhaseOne( /* Provide the ability to easily simulate an I/O error during testing */ if( sqlite3FaultSim(400) ) return SQLITE_IOERR; - PAGERTRACE(("DATABASE SYNC: File=%s zSuper=%s nSize=%d\n", + PAGERTRACE(("DATABASE SYNC: File=%s zSuper=%s nSize=%d\n", pPager->zFilename, zSuper, pPager->dbSize)); /* If no database changes have been made, return early. */ @@ -6455,11 +6450,11 @@ int sqlite3PagerCommitPhaseOne( #ifdef SQLITE_ENABLE_ATOMIC_WRITE /* The following block updates the change-counter. Exactly how it ** does this depends on whether or not the atomic-update optimization - ** was enabled at compile time, and if this transaction meets the - ** runtime criteria to use the operation: + ** was enabled at compile time, and if this transaction meets the + ** runtime criteria to use the operation: ** ** * The file-system supports the atomic-write property for - ** blocks of size page-size, and + ** blocks of size page-size, and ** * This commit is not part of a multi-file transaction, and ** * Exactly one page has been modified and store in the journal file. ** @@ -6469,7 +6464,7 @@ int sqlite3PagerCommitPhaseOne( ** is not applicable to this transaction, call sqlite3JournalCreate() ** to make sure the journal file has actually been created, then call ** pager_incr_changecounter() to update the change-counter in indirect - ** mode. + ** mode. ** ** Otherwise, if the optimization is both enabled and applicable, ** then call pager_incr_changecounter() to update the change-counter @@ -6478,19 +6473,19 @@ int sqlite3PagerCommitPhaseOne( */ if( bBatch==0 ){ PgHdr *pPg; - assert( isOpen(pPager->jfd) - || pPager->journalMode==PAGER_JOURNALMODE_OFF - || pPager->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(pPager->jfd) + || pPager->journalMode==PAGER_JOURNALMODE_OFF + || pPager->journalMode==PAGER_JOURNALMODE_WAL ); - if( !zSuper && isOpen(pPager->jfd) - && pPager->journalOff==jrnlBufferSize(pPager) + if( !zSuper && isOpen(pPager->jfd) + && pPager->journalOff==jrnlBufferSize(pPager) && pPager->dbSize>=pPager->dbOrigSize && (!(pPg = sqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty) ){ - /* Update the db file change counter via the direct-write method. The - ** following call will modify the in-memory representation of page 1 - ** to include the updated change counter and then write page 1 - ** directly to the database file. Because of the atomic-write + /* Update the db file change counter via the direct-write method. The + ** following call will modify the in-memory representation of page 1 + ** to include the updated change counter and then write page 1 + ** directly to the database file. Because of the atomic-write ** property of the host file-system, this is safe. */ rc = pager_incr_changecounter(pPager, 1); @@ -6512,24 +6507,24 @@ int sqlite3PagerCommitPhaseOne( rc = pager_incr_changecounter(pPager, 0); #endif /* !SQLITE_ENABLE_ATOMIC_WRITE */ if( rc!=SQLITE_OK ) goto commit_phase_one_exit; - + /* Write the super-journal name into the journal file. If a - ** super-journal file name has already been written to the journal file, + ** super-journal file name has already been written to the journal file, ** or if zSuper is NULL (no super-journal), then this call is a no-op. */ rc = writeSuperJournal(pPager, zSuper); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; - + /* Sync the journal file and write all dirty pages to the database. - ** If the atomic-update optimization is being used, this sync will not + ** If the atomic-update optimization is being used, this sync will not ** create the journal file or perform any real IO. ** ** Because the change-counter page was just modified, unless the ** atomic-update optimization is used it is almost certain that the ** journal requires a sync here. However, in locking_mode=exclusive - ** on a system under memory pressure it is just possible that this is + ** on a system under memory pressure it is just possible that this is ** not the case. In this case it is likely enough that the redundant - ** xSync() call will be changed to a no-op by the OS anyhow. + ** xSync() call will be changed to a no-op by the OS anyhow. */ rc = syncJournal(pPager, 0); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; @@ -6570,7 +6565,7 @@ int sqlite3PagerCommitPhaseOne( } sqlite3PcacheCleanAll(pPager->pPCache); - /* If the file on disk is smaller than the database image, use + /* If the file on disk is smaller than the database image, use ** pager_truncate to grow the file here. This can happen if the database ** image was extended as part of the current transaction and then the ** last page in the db image moved to the free-list. In this case the @@ -6582,7 +6577,7 @@ int sqlite3PagerCommitPhaseOne( rc = pager_truncate(pPager, nNew); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; } - + /* Finally, sync the database file. */ if( !noSync ){ rc = sqlite3PagerSync(pPager, zSuper); @@ -6602,12 +6597,12 @@ int sqlite3PagerCommitPhaseOne( /* ** When this function is called, the database file has been completely ** updated to reflect the changes made by the current transaction and -** synced to disk. The journal file still exists in the file-system +** synced to disk. The journal file still exists in the file-system ** though, and if a failure occurs at this point it will eventually ** be used as a hot-journal and the current transaction rolled back. ** -** This function finalizes the journal file, either by deleting, -** truncating or partially zeroing it, so that it cannot be used +** This function finalizes the journal file, either by deleting, +** truncating or partially zeroing it, so that it cannot be used ** for hot-journal rollback. Once this is done the transaction is ** irrevocably committed. ** @@ -6633,15 +6628,15 @@ int sqlite3PagerCommitPhaseTwo(Pager *pPager){ ** this transaction, the pager is running in exclusive-mode and is ** using persistent journals, then this function is a no-op. ** - ** The start of the journal file currently contains a single journal + ** The start of the journal file currently contains a single journal ** header with the nRec field set to 0. If such a journal is used as ** a hot-journal during hot-journal rollback, 0 changes will be made - ** to the database file. So there is no need to zero the journal + ** to the database file. So there is no need to zero the journal ** header. Since the pager is in exclusive mode, there is no need ** to drop any locks either. */ - if( pPager->eState==PAGER_WRITER_LOCKED - && pPager->exclusiveMode + if( pPager->eState==PAGER_WRITER_LOCKED + && pPager->exclusiveMode && pPager->journalMode==PAGER_JOURNALMODE_PERSIST ){ assert( pPager->journalOff==JOURNAL_HDR_SZ(pPager) || !pPager->journalOff ); @@ -6655,7 +6650,7 @@ int sqlite3PagerCommitPhaseTwo(Pager *pPager){ } /* -** If a write transaction is open, then all changes made within the +** If a write transaction is open, then all changes made within the ** transaction are reverted and the current write-transaction is closed. ** The pager falls back to PAGER_READER state if successful, or PAGER_ERROR ** state if an error occurs. @@ -6665,14 +6660,14 @@ int sqlite3PagerCommitPhaseTwo(Pager *pPager){ ** ** Otherwise, in rollback mode, this function performs two functions: ** -** 1) It rolls back the journal file, restoring all database file and +** 1) It rolls back the journal file, restoring all database file and ** in-memory cache pages to the state they were in when the transaction ** was opened, and ** ** 2) It finalizes the journal file, so that it is not used for hot ** rollback at any point in the future. ** -** Finalization of the journal file (task 2) is only performed if the +** Finalization of the journal file (task 2) is only performed if the ** rollback is successful. ** ** In WAL mode, all cache-entries containing data modified within the @@ -6685,7 +6680,7 @@ int sqlite3PagerRollback(Pager *pPager){ PAGERTRACE(("ROLLBACK %d\n", PAGERID(pPager))); /* PagerRollback() is a no-op if called in READER or OPEN state. If - ** the pager is already in the ERROR state, the rollback is not + ** the pager is already in the ERROR state, the rollback is not ** attempted here. Instead, the error code is returned to the caller. */ assert( assert_pager_state(pPager) ); @@ -6701,7 +6696,7 @@ int sqlite3PagerRollback(Pager *pPager){ int eState = pPager->eState; rc = pager_end_transaction(pPager, 0, 0); if( !MEMDB && eState>PAGER_WRITER_LOCKED ){ - /* This can happen using journal_mode=off. Move the pager to the error + /* 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. */ @@ -6716,7 +6711,7 @@ int sqlite3PagerRollback(Pager *pPager){ assert( pPager->eState==PAGER_READER || rc!=SQLITE_OK ); assert( rc==SQLITE_OK || rc==SQLITE_FULL || rc==SQLITE_CORRUPT - || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR + || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR || rc==SQLITE_CANTOPEN ); @@ -6790,8 +6785,8 @@ int *sqlite3PagerStats(Pager *pPager){ ** it was added later. ** ** Before returning, *pnVal is incremented by the -** current cache hit or miss count, according to the value of eStat. If the -** reset parameter is non-zero, the cache hit or miss count is zeroed before +** current cache hit or miss count, according to the value of eStat. If the +** reset parameter is non-zero, the cache hit or miss count is zeroed before ** returning. */ void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){ @@ -6827,7 +6822,7 @@ int sqlite3PagerIsMemdb(Pager *pPager){ ** to make up the difference. If the number of savepoints is already ** equal to nSavepoint, then this function is a no-op. ** -** If a memory allocation fails, SQLITE_NOMEM is returned. If an error +** If a memory allocation fails, SQLITE_NOMEM is returned. If an error ** occurs while opening the sub-journal file, then an IO error code is ** returned. Otherwise, SQLITE_OK. */ @@ -6842,7 +6837,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){ assert( nSavepoint>nCurrent && pPager->useJournal ); /* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM - ** if the allocation fails. Otherwise, zero the new portion in case a + ** if the allocation fails. Otherwise, zero the new portion in case a ** malloc failure occurs while populating it in the for(...) loop below. */ aNew = (PagerSavepoint *)sqlite3Realloc( @@ -6891,7 +6886,7 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){ /* ** This function is called to rollback or release (commit) a savepoint. -** The savepoint to release or rollback need not be the most recently +** The savepoint to release or rollback need not be the most recently ** created savepoint. ** ** Parameter op is always either SAVEPOINT_ROLLBACK or SAVEPOINT_RELEASE. @@ -6899,29 +6894,29 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){ ** index iSavepoint. If it is SAVEPOINT_ROLLBACK, then rollback all changes ** that have occurred since the specified savepoint was created. ** -** The savepoint to rollback or release is identified by parameter +** The savepoint to rollback or release is identified by parameter ** iSavepoint. A value of 0 means to operate on the outermost savepoint ** (the first created). A value of (Pager.nSavepoint-1) means operate ** on the most recently created savepoint. If iSavepoint is greater than ** (Pager.nSavepoint-1), then this function is a no-op. ** ** If a negative value is passed to this function, then the current -** transaction is rolled back. This is different to calling +** transaction is rolled back. This is different to calling ** sqlite3PagerRollback() because this function does not terminate -** the transaction or unlock the database, it just restores the -** contents of the database to its original state. +** the transaction or unlock the database, it just restores the +** contents of the database to its original state. ** -** In any case, all savepoints with an index greater than iSavepoint +** In any case, all savepoints with an index greater than iSavepoint ** are destroyed. If this is a release operation (op==SAVEPOINT_RELEASE), ** then savepoint iSavepoint is also destroyed. ** ** This function may return SQLITE_NOMEM if a memory allocation fails, -** or an IO error code if an IO error occurs while rolling back a +** or an IO error code if an IO error occurs while rolling back a ** savepoint. If no errors occur, SQLITE_OK is returned. -*/ +*/ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ int rc = pPager->errCode; - + #ifdef SQLITE_ENABLE_ZIPVFS if( op==SAVEPOINT_RELEASE ) rc = SQLITE_OK; #endif @@ -6934,7 +6929,7 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ int nNew; /* Number of remaining savepoints after this op. */ /* Figure out how many savepoints will still be active after this - ** operation. Store this value in nNew. Then free resources associated + ** operation. Store this value in nNew. Then free resources associated ** with any savepoints that are destroyed by this operation. */ nNew = iSavepoint + (( op==SAVEPOINT_RELEASE ) ? 0 : 1); @@ -6967,14 +6962,14 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ rc = pagerPlaybackSavepoint(pPager, pSavepoint); assert(rc!=SQLITE_DONE); } - + #ifdef SQLITE_ENABLE_ZIPVFS - /* If the cache has been modified but the savepoint cannot be rolled + /* If the cache has been modified but the savepoint cannot be rolled ** back journal_mode=off, put the pager in the error state. This way, ** if the VFS used by this pager includes ZipVFS, the entire transaction ** can be rolled back at the ZipVFS level. */ - else if( - pPager->journalMode==PAGER_JOURNALMODE_OFF + else if( + pPager->journalMode==PAGER_JOURNALMODE_OFF && pPager->eState>=PAGER_WRITER_CACHEMOD ){ pPager->errCode = SQLITE_ABORT; @@ -7063,8 +7058,8 @@ const char *sqlite3PagerJournalname(Pager *pPager){ ** transaction is active). ** ** If the fourth argument, isCommit, is non-zero, then this page is being -** moved as part of a database reorganization just before the transaction -** is being committed. In this case, it is guaranteed that the database page +** moved as part of a database reorganization just before the transaction +** is being committed. In this case, it is guaranteed that the database page ** pPg refers to will not be written to again within this transaction. ** ** This function may return SQLITE_NOMEM or an IO error code if an error @@ -7092,7 +7087,7 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ } /* If the page being moved is dirty and has not been saved by the latest - ** savepoint, then save the current contents of the page into the + ** savepoint, then save the current contents of the page into the ** sub-journal now. This is required to handle the following scenario: ** ** BEGIN; @@ -7115,7 +7110,7 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ return rc; } - PAGERTRACE(("MOVE %d page %d (needSync=%d) moves to %d\n", + PAGERTRACE(("MOVE %d page %d (needSync=%d) moves to %d\n", PAGERID(pPager), pPg->pgno, (pPg->flags&PGHDR_NEED_SYNC)?1:0, pgno)); IOTRACE(("MOVE %p %d %d\n", pPager, pPg->pgno, pgno)) @@ -7123,7 +7118,7 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ ** be written to, store pPg->pgno in local variable needSyncPgno. ** ** If the isCommit flag is set, there is no need to remember that - ** the journal needs to be sync()ed before database page pPg->pgno + ** the journal needs to be sync()ed before database page pPg->pgno ** can be written to. The caller has already promised not to write to it. */ if( (pPg->flags&PGHDR_NEED_SYNC) && !isCommit ){ @@ -7134,8 +7129,8 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ } /* If the cache contains a page with page-number pgno, remove it - ** from its hash chain. Also, if the PGHDR_NEED_SYNC flag was set for - ** page pgno before the 'move' operation, it needs to be retained + ** from its hash chain. Also, if the PGHDR_NEED_SYNC flag was set for + ** page pgno before the 'move' operation, it needs to be retained ** for the page moved there. */ pPg->flags &= ~PGHDR_NEED_SYNC; @@ -7170,9 +7165,9 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ } if( needSyncPgno ){ - /* If needSyncPgno is non-zero, then the journal file needs to be + /* If needSyncPgno is non-zero, then the journal file needs to be ** sync()ed before any data is written to database file page needSyncPgno. - ** Currently, no such page exists in the page-cache and the + ** Currently, no such page exists in the page-cache and the ** "is journaled" bitvec flag has been set. This needs to be remedied by ** loading the page into the pager-cache and setting the PGHDR_NEED_SYNC ** flag. @@ -7203,9 +7198,9 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ #endif /* -** The page handle passed as the first argument refers to a dirty page -** with a page number other than iNew. This function changes the page's -** page number to iNew and sets the value of the PgHdr.flags field to +** The page handle passed as the first argument refers to a dirty page +** with a page number other than iNew. This function changes the page's +** page number to iNew and sets the value of the PgHdr.flags field to ** the value passed as the third parameter. */ void sqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){ @@ -7223,7 +7218,7 @@ void *sqlite3PagerGetData(DbPage *pPg){ } /* -** Return a pointer to the Pager.nExtra bytes of "extra" space +** Return a pointer to the Pager.nExtra bytes of "extra" space ** allocated along with the specified page. */ void *sqlite3PagerGetExtra(DbPage *pPg){ @@ -7232,7 +7227,7 @@ void *sqlite3PagerGetExtra(DbPage *pPg){ /* ** Get/set the locking-mode for this pager. Parameter eMode must be one -** of PAGER_LOCKINGMODE_QUERY, PAGER_LOCKINGMODE_NORMAL or +** of PAGER_LOCKINGMODE_QUERY, PAGER_LOCKINGMODE_NORMAL or ** PAGER_LOCKINGMODE_EXCLUSIVE. If the parameter is not _QUERY, then ** the locking-mode is set to the value specified. ** @@ -7306,7 +7301,7 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ assert( pPager->eState!=PAGER_ERROR ); pPager->journalMode = (u8)eMode; - /* When transistioning from TRUNCATE or PERSIST to any other journal + /* When transitioning from TRUNCATE or PERSIST to any other journal ** mode except WAL, unless the pager is in locking_mode=exclusive mode, ** delete the journal file. */ @@ -7479,7 +7474,7 @@ static int pagerExclusiveLock(Pager *pPager){ eOrigLock = pPager->eLock; rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); if( rc!=SQLITE_OK ){ - /* If the attempt to grab the exclusive lock failed, release the + /* If the attempt to grab the exclusive lock failed, release the ** pending lock that may have been obtained instead. */ pagerUnlockDb(pPager, eOrigLock); } @@ -7488,7 +7483,7 @@ static int pagerExclusiveLock(Pager *pPager){ } /* -** Call sqlite3WalOpen() to open the WAL handle. If the pager is in +** 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. @@ -7499,8 +7494,8 @@ static int pagerOpenWal(Pager *pPager){ assert( pPager->pWal==0 && pPager->tempFile==0 ); assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK ); - /* 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 + /* 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. */ @@ -7508,7 +7503,7 @@ static int pagerOpenWal(Pager *pPager){ rc = pagerExclusiveLock(pPager); } - /* Open the connection to the log file. If this operation fails, + /* 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 ){ @@ -7530,7 +7525,7 @@ static int pagerOpenWal(Pager *pPager){ ** If the pager passed as the first argument is open on a real database ** file (not a temp file or an in-memory database), and the WAL file ** is not already open, make an attempt to open it now. If successful, -** return SQLITE_OK. If an error occurs or the VFS used by the pager does +** return SQLITE_OK. If an error occurs or the VFS used by the pager does ** not support the xShmXXX() methods, return an error code. *pbOpen is ** not modified in either case. ** @@ -7572,7 +7567,7 @@ int sqlite3PagerOpenWal( ** This function is called to close the connection to the log file prior ** to switching from WAL to rollback mode. ** -** Before closing the log file, this function attempts to take an +** Before closing the log file, this function attempts to take an ** EXCLUSIVE lock on the database file. If this cannot be obtained, an ** error (SQLITE_BUSY) is returned and the log connection is not closed. ** If successful, the EXCLUSIVE lock is not released before returning. @@ -7598,7 +7593,7 @@ int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ rc = pagerOpenWal(pPager); } } - + /* Checkpoint and close the log. Because an EXCLUSIVE lock is held on ** the database file, the log and log-summary files will be deleted. */ @@ -7618,7 +7613,7 @@ int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ #ifdef SQLITE_ENABLE_SETLK_TIMEOUT /* ** If pager pPager is a wal-mode database not in exclusive locking mode, -** invoke the sqlite3WalWriteLock() function on the associated Wal object +** invoke the sqlite3WalWriteLock() function on the associated Wal object ** with the same db and bLock parameters as were passed to this function. ** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. */ @@ -7631,7 +7626,7 @@ int sqlite3PagerWalWriteLock(Pager *pPager, int bLock){ } /* -** Set the database handle used by the wal layer to determine if +** Set the database handle used by the wal layer to determine if ** blocking locks are required. */ void sqlite3PagerWalDb(Pager *pPager, sqlite3 *db){ @@ -7656,11 +7651,11 @@ int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot){ /* ** If this is a WAL database, store a pointer to pSnapshot. Next time a -** read transaction is opened, attempt to read from the snapshot it +** read transaction is opened, attempt to read from the snapshot it ** identifies. If this is not a WAL database, return an error. */ int sqlite3PagerSnapshotOpen( - Pager *pPager, + Pager *pPager, sqlite3_snapshot *pSnapshot ){ int rc = SQLITE_OK; @@ -7673,7 +7668,7 @@ int sqlite3PagerSnapshotOpen( } /* -** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this +** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this ** is not a WAL database, return an error. */ int sqlite3PagerSnapshotRecover(Pager *pPager){ @@ -7690,7 +7685,7 @@ int sqlite3PagerSnapshotRecover(Pager *pPager){ ** The caller currently has a read transaction open on the database. ** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise, ** this function takes a SHARED lock on the CHECKPOINTER slot and then -** checks if the snapshot passed as the second argument is still +** checks if the snapshot passed as the second argument is still ** available. If so, SQLITE_OK is returned. ** ** If the snapshot is not available, SQLITE_ERROR is returned. Or, if @@ -7734,4 +7729,10 @@ int sqlite3PagerWalFramesize(Pager *pPager){ } #endif +#ifdef SQLITE_USE_SEH +int sqlite3PagerWalSystemErrno(Pager *pPager){ + return sqlite3WalSystemErrno(pPager->pWal); +} +#endif + #endif /* SQLITE_OMIT_DISKIO */ diff --git a/src/pager.h b/src/pager.h index eb1306096..044c2573e 100644 --- a/src/pager.h +++ b/src/pager.h @@ -240,4 +240,8 @@ void sqlite3PagerRekey(DbPage*, Pgno, u16); # define enable_simulated_io_errors() #endif +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) +int sqlite3PagerWalSystemErrno(Pager*); +#endif + #endif /* SQLITE_PAGER_H */ diff --git a/src/parse.y b/src/parse.y index 541b9ffe5..867b62aa7 100644 --- a/src/parse.y +++ b/src/parse.y @@ -15,7 +15,7 @@ ** The canonical source code to this file ("parse.y") is a Lemon grammar ** file that specifies the input grammar and actions to take while parsing. ** That input file is processed by Lemon to generate a C-language -** implementation of a parser for the given grammer. You might be reading +** implementation of a parser for the given grammar. You might be reading ** this comment as part of the translated C-code. Edits should be made ** to the original parse.y sources. */ @@ -148,8 +148,8 @@ ecmd ::= SEMI. ecmd ::= cmdx SEMI. %ifndef SQLITE_OMIT_EXPLAIN ecmd ::= explain cmdx SEMI. {NEVER-REDUCE} -explain ::= EXPLAIN. { pParse->explain = 1; } -explain ::= EXPLAIN QUERY PLAN. { pParse->explain = 2; } +explain ::= EXPLAIN. { if( pParse->pReprepare==0 ) pParse->explain = 1; } +explain ::= EXPLAIN QUERY PLAN. { if( pParse->pReprepare==0 ) pParse->explain = 2; } %endif SQLITE_OMIT_EXPLAIN cmdx ::= cmd. { sqlite3FinishCoding(pParse); } @@ -267,7 +267,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,A,Y);} %wildcard ANY. // Define operator precedence early so that this is the first occurrence -// of the operator tokens in the grammer. Keeping the operators together +// of the operator tokens in the grammar. Keeping the operators together // causes them to be assigned integer values that are close together, // which keeps parser tables smaller. // @@ -554,12 +554,11 @@ select(A) ::= WITH wqlist(W) selectnowith(X). {A = attachWithToSelect(pParse,X,W select(A) ::= WITH RECURSIVE wqlist(W) selectnowith(X). {A = attachWithToSelect(pParse,X,W);} %endif /* SQLITE_OMIT_CTE */ -select(A) ::= selectnowith(X). { - Select *p = X; +select(A) ::= selectnowith(A). { + Select *p = A; if( p ){ parserDoubleLinkSelect(pParse, p); } - A = p; /*A-overwrites-X*/ } selectnowith(A) ::= oneselect(A). @@ -655,14 +654,17 @@ selcollist(A) ::= sclp(A) scanpt(B) expr(X) scanpt(Z) as(Y). { if( Y.n>0 ) sqlite3ExprListSetName(pParse, A, &Y, 1); sqlite3ExprListSetSpan(pParse,A,B,Z); } -selcollist(A) ::= sclp(A) scanpt STAR. { +selcollist(A) ::= sclp(A) scanpt STAR(X). { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); + sqlite3ExprSetErrorOffset(p, (int)(X.z - pParse->zTail)); A = sqlite3ExprListAppend(pParse, A, p); } -selcollist(A) ::= sclp(A) scanpt nm(X) DOT STAR. { - Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); - Expr *pLeft = tokenExpr(pParse, TK_ID, X); - Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); +selcollist(A) ::= sclp(A) scanpt nm(X) DOT STAR(Y). { + Expr *pRight, *pLeft, *pDot; + pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); + sqlite3ExprSetErrorOffset(pRight, (int)(Y.z - pParse->zTail)); + pLeft = tokenExpr(pParse, TK_ID, X); + pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); A = sqlite3ExprListAppend(pParse,A, pDot); } @@ -1758,7 +1760,7 @@ wqlist(A) ::= wqlist(A) COMMA wqitem(X). { %ifndef SQLITE_OMIT_WINDOWFUNC %type windowdefn_list {Window*} %destructor windowdefn_list {sqlite3WindowListDelete(pParse->db, $$);} -windowdefn_list(A) ::= windowdefn(Z). { A = Z; } +windowdefn_list(A) ::= windowdefn(A). windowdefn_list(A) ::= windowdefn_list(Y) COMMA windowdefn(Z). { assert( Z!=0 ); sqlite3WindowChain(pParse, Z, Y); @@ -1814,9 +1816,7 @@ window(A) ::= ORDER BY sortlist(Y) frame_opt(Z). { window(A) ::= nm(W) ORDER BY sortlist(Y) frame_opt(Z). { A = sqlite3WindowAssemble(pParse, Z, 0, Y, &W); } -window(A) ::= frame_opt(Z). { - A = Z; -} +window(A) ::= frame_opt(A). window(A) ::= nm(W) frame_opt(Z). { A = sqlite3WindowAssemble(pParse, Z, 0, 0, &W); } diff --git a/src/pcache.c b/src/pcache.c index 8a96cbeae..2974b0810 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -107,7 +107,7 @@ struct PCache { ** Return 1 if pPg is on the dirty list for pCache. Return 0 if not. ** This routine runs inside of assert() statements only. */ -#ifdef SQLITE_DEBUG +#if defined(SQLITE_ENABLE_EXPENSIVE_ASSERT) static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ PgHdr *p; for(p=pCache->pDirty; p; p=p->pDirtyNext){ @@ -115,6 +115,16 @@ static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ } return 0; } +static int pageNotOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 0; + } + return 1; +} +#else +# define pageOnDirtyList(A,B) 1 +# define pageNotOnDirtyList(A,B) 1 #endif /* @@ -135,7 +145,7 @@ int sqlite3PcachePageSanity(PgHdr *pPg){ assert( pCache!=0 ); /* Every page has an associated PCache */ if( pPg->flags & PGHDR_CLEAN ){ assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */ - assert( !pageOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirty list */ + assert( pageNotOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirtylist */ }else{ assert( (pPg->flags & PGHDR_DIRTY)!=0 );/* If not CLEAN must be DIRTY */ assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg ); @@ -271,7 +281,7 @@ static int numberOfCachePages(PCache *p){ return p->szCache; }else{ i64 n; - /* IMPLEMANTATION-OF: R-59858-46238 If the argument N is negative, then the + /* IMPLEMENTATION-OF: R-59858-46238 If the argument N is negative, then the ** number of cache pages is adjusted to be a number of pages that would ** use approximately abs(N*1024) bytes of memory based on the current ** page size. */ @@ -759,7 +769,7 @@ static PgHdr *pcacheMergeDirtyList(PgHdr *pA, PgHdr *pB){ } /* -** Sort the list of pages in accending order by pgno. Pages are +** Sort the list of pages in ascending order by pgno. Pages are ** connected by pDirty pointers. The pDirtyPrev pointers are ** corrupted by this sort. ** diff --git a/src/pcache1.c b/src/pcache1.c index adbe95395..1591f014c 100644 --- a/src/pcache1.c +++ b/src/pcache1.c @@ -64,14 +64,14 @@ ** ** The third case is a chunk of heap memory (defaulting to 100 pages worth) ** that is allocated when the page cache is created. The size of the local -** bulk allocation can be adjusted using +** bulk allocation can be adjusted using ** ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, (void*)0, 0, N). ** ** If N is positive, then N pages worth of memory are allocated using a single ** sqlite3Malloc() call and that memory is used for the first N pages allocated. ** Or if N is negative, then -1024*N bytes of memory are allocated and used -** for as many pages as can be accomodated. +** for as many pages as can be accommodated. ** ** Only one of (2) or (3) can be used. Once the memory available to (2) or ** (3) is exhausted, subsequent allocations fail over to the general-purpose @@ -89,7 +89,7 @@ typedef struct PgFreeslot PgFreeslot; typedef struct PGroup PGroup; /* -** Each cache entry is represented by an instance of the following +** Each cache entry is represented by an instance of the following ** structure. A buffer of PgHdr1.pCache->szPage bytes is allocated ** directly before this structure and is used to cache the page content. ** @@ -100,12 +100,12 @@ typedef struct PGroup PGroup; ** overrun area, so that overreads are harmless. ** ** Variables isBulkLocal and isAnchor were once type "u8". That works, -** but causes a 2-byte gap in the structure for most architectures (since +** but causes a 2-byte gap in the structure for most architectures (since ** pointers must be either 4 or 8-byte aligned). As this structure is located ** in memory directly after the associated page data, if the database is -** corrupt, code at the b-tree layer may overread the page buffer and +** corrupt, code at the b-tree layer may overread the page buffer and ** read part of this structure before the corruption is detected. This -** can cause a valgrind error if the unitialized gap is accessed. Using u16 +** can cause a valgrind error if the uninitialized gap is accessed. Using u16 ** ensures there is no such gap, and therefore no bytes of uninitialized ** memory in the structure. ** @@ -133,7 +133,7 @@ struct PgHdr1 { #define PAGE_IS_PINNED(p) ((p)->pLruNext==0) #define PAGE_IS_UNPINNED(p) ((p)->pLruNext!=0) -/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set +/* 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 other's unpinned ** pages when they are under memory pressure. A PGroup is an instance of ** the following object. @@ -169,13 +169,13 @@ struct PGroup { ** 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 +** 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) and the pnPurgeable pointer are all set when the - ** cache is created and are never changed thereafter. nMax may be + ** cache is created and are never changed thereafter. nMax may be ** modified at any time by a call to the pcache1Cachesize() method. ** The PGroup mutex must be held when accessing nMax. */ @@ -223,7 +223,7 @@ static SQLITE_WSD struct PCacheGlobal { */ int isInit; /* True if initialized */ int separateCache; /* Use a new PGroup for each PCache */ - int nInitPage; /* Initial bulk allocation size */ + int nInitPage; /* Initial bulk allocation size */ int szSlot; /* Size of each free slot */ int nSlot; /* The number of pcache slots */ int nReserve; /* Try to keep nFreeSlot above this */ @@ -264,7 +264,7 @@ static SQLITE_WSD struct PCacheGlobal { /* -** This function is called during initialization if a static buffer is +** This function is called during initialization if a static buffer is ** 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. @@ -334,8 +334,8 @@ static int pcache1InitBulk(PCache1 *pCache){ /* ** Malloc function used within this file to allocate space from the buffer -** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no -** such buffer exists or there is no space left in it, this function falls +** 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 @@ -442,7 +442,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){ }else{ #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT /* The group mutex must be released before pcache1Alloc() is called. This - ** is because it might call sqlite3_release_memory(), which assumes that + ** is because it might call sqlite3_release_memory(), which assumes that ** this mutex is not held. */ assert( pcache1.separateCache==0 ); assert( pCache->pGroup==&pcache1.grp ); @@ -569,7 +569,7 @@ static void pcache1ResizeHash(PCache1 *p){ } /* -** This function is used internally to remove the page pPage from the +** This function is used internally to remove the page pPage from the ** PGroup LRU list, if is part of it. If pPage is not part of the PGroup ** LRU list, then this function is a no-op. ** @@ -594,7 +594,7 @@ static PgHdr1 *pcache1PinPage(PgHdr1 *pPage){ /* -** Remove the page supplied as an argument from the hash table +** Remove the page supplied as an argument from the hash table ** (PCache1.apHash structure) that it is currently stored in. ** Also free the page if freePage is true. ** @@ -637,8 +637,8 @@ static void pcache1EnforceMaxPage(PCache1 *pCache){ } /* -** Discard all pages from cache pCache with a page number (key value) -** greater than or equal to iLimit. Any pinned pages that meet this +** Discard all pages from cache pCache with a page number (key value) +** greater than or equal to iLimit. Any pinned pages that meet this ** criteria are unpinned before they are discarded. ** ** The PCache mutex must be held when this function is called. @@ -670,7 +670,7 @@ static void pcache1TruncateUnsafe( PgHdr1 **pp; PgHdr1 *pPage; assert( hnHash ); - pp = &pCache->apHash[h]; + pp = &pCache->apHash[h]; while( (pPage = *pp)!=0 ){ if( pPage->iKey>=iLimit ){ pCache->nPage--; @@ -709,7 +709,7 @@ static int pcache1Init(void *NotUsed){ ** ** * Use a unified cache in single-threaded applications that have ** configured a start-time buffer for use as page-cache memory using - ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL + ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL ** pBuf argument. ** ** * Otherwise use separate caches (mode-1) @@ -744,7 +744,7 @@ static int pcache1Init(void *NotUsed){ /* ** Implementation of the sqlite3_pcache.xShutdown method. -** Note that the static mutex allocated in xInit does +** Note that the static mutex allocated in xInit does ** not need to be freed. */ static void pcache1Shutdown(void *NotUsed){ @@ -807,7 +807,7 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ } /* -** Implementation of the sqlite3_pcache.xCachesize method. +** Implementation of the sqlite3_pcache.xCachesize method. ** ** Configure the cache_size limit for a cache. */ @@ -832,7 +832,7 @@ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ } /* -** Implementation of the sqlite3_pcache.xShrink method. +** Implementation of the sqlite3_pcache.xShrink method. ** ** Free up as much memory as possible. */ @@ -851,7 +851,7 @@ static void pcache1Shrink(sqlite3_pcache *p){ } /* -** Implementation of the sqlite3_pcache.xPagecount method. +** Implementation of the sqlite3_pcache.xPagecount method. */ static int pcache1Pagecount(sqlite3_pcache *p){ int n; @@ -872,8 +872,8 @@ static int pcache1Pagecount(sqlite3_pcache *p){ ** for these steps, the main pcache1Fetch() procedure can run faster. */ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( - PCache1 *pCache, - unsigned int iKey, + PCache1 *pCache, + unsigned int iKey, int createFlag ){ unsigned int nPinned; @@ -915,8 +915,8 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( } } - /* Step 5. If a usable page buffer has still not been found, - ** attempt to allocate a new one. + /* Step 5. If a usable page buffer has still not been found, + ** attempt to allocate a new one. */ if( !pPage ){ pPage = pcache1AllocPage(pCache, createFlag==1); @@ -941,13 +941,13 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( } /* -** Implementation of the sqlite3_pcache.xFetch method. +** Implementation of the sqlite3_pcache.xFetch method. ** ** Fetch a page by key value. ** ** Whether or not a new page may be allocated by this function depends on ** the value of the createFlag argument. 0 means do not allocate a new -** page. 1 means allocate a new page if space is easily available. 2 +** page. 1 means allocate a new page if space is easily available. 2 ** means to try really hard to allocate a new page. ** ** For a non-purgeable cache (a cache used as the storage for an in-memory @@ -958,7 +958,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** There are three different approaches to obtaining space for a page, ** depending on the value of parameter createFlag (which may be 0, 1 or 2). ** -** 1. Regardless of the value of createFlag, the cache is searched for a +** 1. Regardless of the value of createFlag, the cache is searched for a ** copy of the requested page. If one is found, it is returned. ** ** 2. If createFlag==0 and the page is not already in the cache, NULL is @@ -972,13 +972,13 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** 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 +** the sum of nMax for all purgeable caches, less the sum of ** 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: ** -** (a) The number of pages allocated for the cache is already +** (a) The number of pages allocated for the cache is already ** PCache1.nMax, or ** ** (b) The number of pages allocated for all purgeable caches is @@ -990,7 +990,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** ** 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. +** proceed to step 5. ** ** 5. Otherwise, allocate and return a new page buffer. ** @@ -1000,8 +1000,8 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** invokes the appropriate routine. */ static PgHdr1 *pcache1FetchNoMutex( - sqlite3_pcache *p, - unsigned int iKey, + sqlite3_pcache *p, + unsigned int iKey, int createFlag ){ PCache1 *pCache = (PCache1 *)p; @@ -1030,8 +1030,8 @@ static PgHdr1 *pcache1FetchNoMutex( } #if PCACHE1_MIGHT_USE_GROUP_MUTEX static PgHdr1 *pcache1FetchWithMutex( - sqlite3_pcache *p, - unsigned int iKey, + sqlite3_pcache *p, + unsigned int iKey, int createFlag ){ PCache1 *pCache = (PCache1 *)p; @@ -1045,8 +1045,8 @@ static PgHdr1 *pcache1FetchWithMutex( } #endif static sqlite3_pcache_page *pcache1Fetch( - sqlite3_pcache *p, - unsigned int iKey, + sqlite3_pcache *p, + unsigned int iKey, int createFlag ){ #if PCACHE1_MIGHT_USE_GROUP_MUTEX || defined(SQLITE_DEBUG) @@ -1076,18 +1076,18 @@ static sqlite3_pcache_page *pcache1Fetch( ** Mark a page as unpinned (eligible for asynchronous recycling). */ static void pcache1Unpin( - sqlite3_pcache *p, - sqlite3_pcache_page *pPg, + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, int reuseUnlikely ){ PCache1 *pCache = (PCache1 *)p; PgHdr1 *pPage = (PgHdr1 *)pPg; PGroup *pGroup = pCache->pGroup; - + assert( pPage->pCache==pCache ); pcache1EnterMutex(pGroup); - /* It is an error to call this function if the page is already + /* It is an error to call this function if the page is already ** part of the PGroup LRU list. */ assert( pPage->pLruNext==0 ); @@ -1108,7 +1108,7 @@ static void pcache1Unpin( } /* -** Implementation of the sqlite3_pcache.xRekey method. +** Implementation of the sqlite3_pcache.xRekey method. */ static void pcache1Rekey( sqlite3_pcache *p, @@ -1119,7 +1119,7 @@ static void pcache1Rekey( PCache1 *pCache = (PCache1 *)p; PgHdr1 *pPage = (PgHdr1 *)pPg; PgHdr1 **pp; - unsigned int hOld, hNew; + unsigned int hOld, hNew; assert( pPage->iKey==iOld ); assert( pPage->pCache==pCache ); assert( iOld!=iNew ); /* The page number really is changing */ @@ -1147,7 +1147,7 @@ static void pcache1Rekey( } /* -** Implementation of the sqlite3_pcache.xTruncate method. +** Implementation of the sqlite3_pcache.xTruncate method. ** ** Discard all unpinned pages in the cache with a page number equal to ** or greater than parameter iLimit. Any pinned pages with a page number @@ -1164,7 +1164,7 @@ static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){ } /* -** Implementation of the sqlite3_pcache.xDestroy method. +** Implementation of the sqlite3_pcache.xDestroy method. ** ** Destroy a cache allocated using pcache1Create(). */ @@ -1230,7 +1230,7 @@ sqlite3_mutex *sqlite3Pcache1Mutex(void){ ** by the current thread may be sqlite3_free()ed. ** ** nReq is the number of bytes of memory required. Once this much has -** been released, the function returns. The return value is the total number +** been released, the function returns. The return value is the total number ** of bytes of memory released. */ int sqlite3PcacheReleaseMemory(int nReq){ diff --git a/src/pragma.c b/src/pragma.c index 68a153185..f15c1be27 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -26,13 +26,13 @@ ** that includes the PragType_XXXX macro definitions and the aPragmaName[] ** object. This ensures that the aPragmaName[] table is arranged in ** lexicographical order to facility a binary search of the pragma name. -** Do not edit pragma.h directly. Edit and rerun the script in at +** Do not edit pragma.h directly. Edit and rerun the script in at ** ../tool/mkpragmatab.tcl. */ #include "pragma.h" /* ** Interpret the given string as a safety level. Return 0 for OFF, -** 1 for ON or NORMAL, 2 for FULL, and 3 for EXTRA. Return 1 for an empty or +** 1 for ON or NORMAL, 2 for FULL, and 3 for EXTRA. Return 1 for an empty or ** unrecognized string argument. The FULL and EXTRA option is disallowed ** if the omitFull parameter it 1. ** @@ -91,7 +91,7 @@ static int getLockingMode(const char *z){ /* ** Interpret the given string as an auto-vacuum mode value. ** -** The following strings, "none", "full" and "incremental" are +** The following strings, "none", "full" and "incremental" are ** acceptable, as are their numeric equivalents: 0, 1 and 2 respectively. */ static int getAutoVacuum(const char *z){ @@ -245,7 +245,7 @@ static const char *actionName(u8 action){ case OE_SetDflt: zName = "SET DEFAULT"; break; case OE_Cascade: zName = "CASCADE"; break; case OE_Restrict: zName = "RESTRICT"; break; - default: zName = "NO ACTION"; + default: zName = "NO ACTION"; assert( action==OE_None ); break; } return zName; @@ -307,7 +307,7 @@ static void pragmaFunclistLine( int isBuiltin, /* True if this is a built-in function */ int showInternFuncs /* True if showing internal functions */ ){ - u32 mask = + u32 mask = SQLITE_DETERMINISTIC | SQLITE_DIRECTONLY | SQLITE_SUBTYPE | @@ -329,7 +329,7 @@ static void pragmaFunclistLine( && showInternFuncs==0 ){ continue; - } + } if( p->xValue!=0 ){ zType = "w"; }else if( p->xFinalize!=0 ){ @@ -364,7 +364,7 @@ static int integrityCheckResultRow(Vdbe *v){ } /* -** Process a pragma statement. +** Process a pragma statement. ** ** Pragmas are of this form: ** @@ -379,7 +379,7 @@ static int integrityCheckResultRow(Vdbe *v){ ** id and pId2 is any empty string. */ void sqlite3Pragma( - Parse *pParse, + Parse *pParse, Token *pId1, /* First part of [schema.]id field */ Token *pId2, /* Second part of [schema.]id field, or NULL */ Token *pValue, /* Token for , or NULL */ @@ -407,8 +407,8 @@ void sqlite3Pragma( if( iDb<0 ) return; pDb = &db->aDb[iDb]; - /* If the temp database has been explicitly named as part of the - ** pragma, make sure it is open. + /* If the temp database has been explicitly named as part of the + ** pragma, make sure it is open. */ if( iDb==1 && sqlite3OpenTempDatabase(pParse) ){ return; @@ -480,7 +480,7 @@ void sqlite3Pragma( } /* Register the result column names for pragmas that return results */ - if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 + if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 && ((pPragma->mPragFlg & PragFlg_NoColumns1)==0 || zRight==0) ){ setPragmaResultColumnNames(v, pPragma); @@ -488,7 +488,7 @@ void sqlite3Pragma( /* Jump to the appropriate pragma handler */ switch( pPragma->ePragTyp ){ - + #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) /* ** PRAGMA [schema.]default_cache_size @@ -604,7 +604,7 @@ void sqlite3Pragma( ** PRAGMA [schema.]max_page_count=N ** ** The first form reports the current setting for the - ** maximum number of pages in the database file. The + ** maximum number of pages in the database file. The ** second form attempts to change this setting. Both ** forms return the current setting. ** @@ -771,7 +771,7 @@ void sqlite3Pragma( */ rc = sqlite3BtreeSetAutoVacuum(pBt, eAuto); if( rc==SQLITE_OK && (eAuto==1 || eAuto==2) ){ - /* When setting the auto_vacuum mode to either "full" or + /* When setting the auto_vacuum mode to either "full" or ** "incremental", write the value of meta[6] in the database ** file. Before writing to meta[6], check that meta[3] indicates ** that this really is an auto-vacuum capable database. @@ -854,7 +854,7 @@ void sqlite3Pragma( ** ** The first form reports the current local setting for the ** page cache spill size. The second form turns cache spill on - ** or off. When turnning cache spill on, the size is set to the + ** or off. When turning cache spill on, the size is set to the ** current cache_size. The third form sets a spill size that ** may be different form the cache size. ** If N is positive then that is the @@ -873,7 +873,7 @@ void sqlite3Pragma( assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ returnSingleInt(v, - (db->flags & SQLITE_CacheSpill)==0 ? 0 : + (db->flags & SQLITE_CacheSpill)==0 ? 0 : sqlite3BtreeSetSpillSize(pDb->pBt,0)); }else{ int size = 1; @@ -1053,7 +1053,7 @@ void sqlite3Pragma( Pager *pPager = sqlite3BtreePager(pDb->pBt); char *proxy_file_path = NULL; sqlite3_file *pFile = sqlite3PagerFile(pPager); - sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE, + sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE, &proxy_file_path); returnSingleText(v, proxy_file_path); }else{ @@ -1061,10 +1061,10 @@ void sqlite3Pragma( sqlite3_file *pFile = sqlite3PagerFile(pPager); int res; if( zRight[0] ){ - res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, + res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, zRight); } else { - res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, + res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, NULL); } if( res!=SQLITE_OK ){ @@ -1074,8 +1074,8 @@ void sqlite3Pragma( } break; } -#endif /* SQLITE_ENABLE_LOCKING_STYLE */ - +#endif /* SQLITE_ENABLE_LOCKING_STYLE */ + /* ** PRAGMA [schema.]synchronous ** PRAGMA [schema.]synchronous=OFF|ON|NORMAL|FULL|EXTRA @@ -1090,7 +1090,7 @@ void sqlite3Pragma( returnSingleInt(v, pDb->safety_level-1); }else{ if( !db->autoCommit ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "Safety level may not be changed inside a transaction"); }else if( iDb!=1 ){ int iLevel = (getSafetyLevel(zRight,0,1)+1) & PAGER_SYNCHRONOUS_MASK; @@ -1138,7 +1138,7 @@ void sqlite3Pragma( } } - /* Many of the flag-pragmas modify the code generated by the SQL + /* Many of the flag-pragmas modify the code generated by the SQL ** compiler (eg. count_changes). So add an opcode to expire all ** compiled SQL statements after modifying a pragma value. */ @@ -1465,7 +1465,7 @@ void sqlite3Pragma( pFK = pTab->u.tab.pFKey; if( pFK ){ int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); - int i = 0; + int i = 0; pParse->nMem = 8; sqlite3CodeVerifySchema(pParse, iTabDb); while(pFK){ @@ -1562,8 +1562,8 @@ void sqlite3Pragma( addrOk = sqlite3VdbeMakeLabel(pParse); /* Generate code to read the child key values into registers - ** regRow..regRow+n. If any of the child key values are NULL, this - ** row cannot cause an FK violation. Jump directly to addrOk in + ** regRow..regRow+n. If any of the child key values are NULL, this + ** row cannot cause an FK violation. Jump directly to addrOk in ** this case. */ sqlite3TouchRegister(pParse, regRow + pFK->nCol); for(j=0; jnCol; j++){ @@ -1629,12 +1629,12 @@ void sqlite3Pragma( ** ** Verify the integrity of the database. ** - ** The "quick_check" is reduced version of + ** The "quick_check" is reduced version of ** integrity_check designed to detect most database corruption ** without the overhead of cross-checking indexes. Quick_check - ** is linear time wherease integrity_check is O(NlogN). + ** is linear time whereas integrity_check is O(NlogN). ** - ** The maximum nubmer of errors is 100 by default. A different default + ** The maximum number of errors is 100 by default. A different default ** can be specified using a numeric parameter N. ** ** Or, the parameter N can be the name of a table. In that case, only @@ -1770,7 +1770,7 @@ void sqlite3Pragma( sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, 1, 0, &iDataCur, &iIdxCur); /* reg[7] counts the number of entries in the table. - ** reg[8+i] counts the number of entries in the i-th index + ** reg[8+i] counts the number of entries in the i-th index */ sqlite3VdbeAddOp2(v, OP_Integer, 0, 7); for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ @@ -1795,7 +1795,7 @@ void sqlite3Pragma( if( mxCol==pTab->iPKey ) mxCol--; }else{ /* COLFLAG_VIRTUAL columns are not included in the WITHOUT ROWID - ** PK index column-count, so there is no need to account for them + ** PK index column-count, so there is no need to account for them ** in this case. */ mxCol = sqlite3PrimaryKeyIndex(pTab)->nColumn-1; } @@ -1895,7 +1895,7 @@ void sqlite3Pragma( sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3); jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk); VdbeCoverage(v); - } + } zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, pCol->zCnName); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); @@ -1967,7 +1967,7 @@ void sqlite3Pragma( for(k=pCheck->nExpr-1; k>0; k--){ sqlite3ExprIfFalse(pParse, pCheck->a[k].pExpr, addrCkFault, 0); } - sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, + sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, SQLITE_JUMPIFNULL); sqlite3VdbeResolveLabel(v, addrCkFault); pParse->iSelfTab = 0; @@ -2037,7 +2037,7 @@ void sqlite3Pragma( sqlite3VdbeGoto(v, jmp5-1); sqlite3VdbeJumpHere(v, jmp6); } - + /* For UNIQUE indexes, verify that only one entry exists with the ** current key. The entry is unique if (1) any column is NULL ** or (2) the next entry has a different key */ @@ -2082,7 +2082,7 @@ void sqlite3Pragma( sqlite3ReleaseTempRange(pParse, r2, pPk->nKeyCol); } } - } + } } { static const int iLn = VDBE_OFFSET_LINENO(2); @@ -2124,7 +2124,7 @@ void sqlite3Pragma( ** encoding that will be used for the main database file if a new file ** is created. If an existing main database file is opened, then the ** default text encoding for the existing database is used. - ** + ** ** In all cases new databases created using the ATTACH command are ** created to use the same default text encoding as the main database. If ** the main database has not been initialized and/or created when ATTACH @@ -2312,8 +2312,8 @@ void sqlite3Pragma( if( zRight ){ sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight)); } - returnSingleInt(v, - db->xWalCallback==sqlite3WalDefaultHook ? + returnSingleInt(v, + db->xWalCallback==sqlite3WalDefaultHook ? SQLITE_PTR_TO_INT(db->pWalArg) : 0); } break; @@ -2353,7 +2353,7 @@ void sqlite3Pragma( ** 0x0002 Run ANALYZE on tables that might benefit. On by default. ** See below for additional information. ** - ** 0x0004 (Not yet implemented) Record usage and performance + ** 0x0004 (Not yet implemented) Record usage and performance ** information from the current session in the ** database file so that it will be available to "optimize" ** pragmas run by future database connections. @@ -2364,7 +2364,7 @@ void sqlite3Pragma( ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all ** of the optimizations listed above except Debug Mode, including new ** optimizations that have not yet been invented. If new optimizations are - ** ever added that should be off by default, those off-by-default + ** ever added that should be off by default, those off-by-default ** optimizations will have bitmasks of 0x10000 or larger. ** ** DETERMINATION OF WHEN TO RUN ANALYZE @@ -2392,7 +2392,7 @@ void sqlite3Pragma( Schema *pSchema; /* The current schema */ Table *pTab; /* A table in the schema */ Index *pIdx; /* An index of the table */ - LogEst szThreshold; /* Size threshold above which reanalysis is needd */ + LogEst szThreshold; /* Size threshold above which reanalysis needed */ char *zSubSql; /* SQL statement for the OP_SqlExec opcode */ u32 opMask; /* Mask of operations to perform */ @@ -2425,7 +2425,7 @@ void sqlite3Pragma( } if( szThreshold ){ sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); - sqlite3VdbeAddOp3(v, OP_IfSmaller, iTabCur, + sqlite3VdbeAddOp3(v, OP_IfSmaller, iTabCur, sqlite3VdbeCurrentAddr(v)+2+(opMask&1), szThreshold); VdbeCoverage(v); } @@ -2559,7 +2559,7 @@ void sqlite3Pragma( pBt = db->aDb[i].pBt; if( pBt==0 || sqlite3BtreePager(pBt)==0 ){ zState = "closed"; - }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0, + }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0, SQLITE_FCNTL_LOCKSTATE, &j)==SQLITE_OK ){ zState = azLockName[j]; } @@ -2583,7 +2583,7 @@ void sqlite3Pragma( /* The following block is a no-op unless SQLITE_DEBUG is defined. Its only ** purpose is to execute assert() statements to verify that if the ** PragFlg_NoColumns1 flag is set and the caller specified an argument - ** to the PRAGMA, the implementation has not added any OP_ResultRow + ** to the PRAGMA, the implementation has not added any OP_ResultRow ** instructions to the VM. */ if( (pPragma->mPragFlg & PragFlg_NoColumns1) && zRight ){ sqlite3VdbeVerifyNoResultRow(v); @@ -2614,7 +2614,7 @@ struct PragmaVtabCursor { char *azArg[2]; /* Value of the argument and schema */ }; -/* +/* ** Pragma virtual table module xConnect method. */ static int pragmaVtabConnect( @@ -2676,7 +2676,7 @@ static int pragmaVtabConnect( return rc; } -/* +/* ** Pragma virtual table module xDisconnect method. */ static int pragmaVtabDisconnect(sqlite3_vtab *pVtab){ @@ -2774,11 +2774,11 @@ static int pragmaVtabNext(sqlite3_vtab_cursor *pVtabCursor){ return rc; } -/* +/* ** Pragma virtual table module xFilter method. */ static int pragmaVtabFilter( - sqlite3_vtab_cursor *pVtabCursor, + sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ @@ -2833,11 +2833,11 @@ static int pragmaVtabEof(sqlite3_vtab_cursor *pVtabCursor){ } /* The xColumn method simply returns the corresponding column from -** the PRAGMA. +** the PRAGMA. */ static int pragmaVtabColumn( - sqlite3_vtab_cursor *pVtabCursor, - sqlite3_context *ctx, + sqlite3_vtab_cursor *pVtabCursor, + sqlite3_context *ctx, int i ){ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; @@ -2850,7 +2850,7 @@ static int pragmaVtabColumn( return SQLITE_OK; } -/* +/* ** Pragma virtual table module xRowid method. */ static int pragmaVtabRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *p){ diff --git a/src/prepare.c b/src/prepare.c index ff4826502..9f843faa8 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -608,7 +608,7 @@ void sqlite3ParseObjectReset(Parse *pParse){ ** immediately. ** ** Use this mechanism for uncommon cleanups. There is a higher setup -** cost for this mechansim (an extra malloc), so it should not be used +** cost for this mechanism (an extra malloc), so it should not be used ** for common cleanups that happen on most calls. But for less ** common cleanups, we save a single NULL-pointer comparison in ** sqlite3ParseObjectReset(), which reduces the total CPU cycle count. @@ -700,7 +700,12 @@ static int sqlite3Prepare( sParse.pOuterParse = db->pParse; db->pParse = &sParse; sParse.db = db; - sParse.pReprepare = pReprepare; + if( pReprepare ){ + sParse.pReprepare = pReprepare; + sParse.explain = sqlite3_stmt_isexplain((sqlite3_stmt*)pReprepare); + }else{ + assert( sParse.pReprepare==0 ); + } assert( ppStmt && *ppStmt==0 ); if( db->mallocFailed ){ sqlite3ErrorMsg(&sParse, "out of memory"); diff --git a/src/printf.c b/src/printf.c index 3e1782d46..87ad91f79 100644 --- a/src/printf.c +++ b/src/printf.c @@ -105,57 +105,6 @@ static const et_info fmtinfo[] = { ** %!S Like %S but prefer the zName over the zAlias */ -/* Floating point constants used for rounding */ -static const double arRound[] = { - 5.0e-01, 5.0e-02, 5.0e-03, 5.0e-04, 5.0e-05, - 5.0e-06, 5.0e-07, 5.0e-08, 5.0e-09, 5.0e-10, -}; - -/* -** If SQLITE_OMIT_FLOATING_POINT is defined, then none of the floating point -** conversions will work. -*/ -#ifndef SQLITE_OMIT_FLOATING_POINT -/* -** "*val" is a double such that 0.1 <= *val < 10.0 -** Return the ascii code for the leading digit of *val, then -** multiply "*val" by 10.0 to renormalize. -** -** Example: -** input: *val = 3.14159 -** output: *val = 1.4159 function return = '3' -** -** The counter *cnt is incremented each time. After counter exceeds -** 16 (the number of significant digits in a 64-bit float) '0' is -** always returned. -*/ -static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ - int digit; - LONGDOUBLE_TYPE d; - if( (*cnt)<=0 ) return '0'; - (*cnt)--; - digit = (int)*val; - d = digit; - digit += '0'; - *val = (*val - d)*10.0; - return (char)digit; -} -#endif /* SQLITE_OMIT_FLOATING_POINT */ - -#ifndef SQLITE_OMIT_FLOATING_POINT -/* -** "*val" is a u64. *msd is a divisor used to extract the -** most significant digit of *val. Extract that most significant -** digit and return it. -*/ -static char et_getdigit_int(u64 *val, u64 *msd){ - u64 x = (*val)/(*msd); - *val -= x*(*msd); - if( *msd>=10 ) *msd /= 10; - return '0' + (char)(x & 15); -} -#endif /* SQLITE_OMIT_FLOATING_POINT */ - /* ** Set the StrAccum object to an error mode. */ @@ -247,20 +196,15 @@ void sqlite3_str_vappendf( u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ - LONGDOUBLE_TYPE realvalue; /* Value for real types */ - sqlite_uint64 msd; /* Divisor to get most-significant-digit - ** of longvalue */ + double realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ char *zOut; /* Rendering buffer */ int nOut; /* Size of the rendering buffer */ char *zExtra = 0; /* Malloced memory used by some conversion */ -#ifndef SQLITE_OMIT_FLOATING_POINT - int exp, e2; /* exponent of real numbers */ - int nsd; /* Number of significant digits returned */ - double rounder; /* Used for rounding floating point values */ + int exp, e2; /* exponent of real numbers */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ -#endif + PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */ char buf[etBUFSIZE]; /* Conversion buffer */ @@ -535,95 +479,62 @@ void sqlite3_str_vappendf( break; case etFLOAT: case etEXP: - case etGENERIC: + case etGENERIC: { + FpDecode s; + int iRound; + int j; + if( bArgList ){ realvalue = getDoubleArg(pArgList); }else{ realvalue = va_arg(ap,double); } -#ifdef SQLITE_OMIT_FLOATING_POINT - length = 0; -#else if( precision<0 ) precision = 6; /* Set default precision */ #ifdef SQLITE_FP_PRECISION_LIMIT if( precision>SQLITE_FP_PRECISION_LIMIT ){ precision = SQLITE_FP_PRECISION_LIMIT; } #endif - if( realvalue<0.0 ){ - realvalue = -realvalue; - prefix = '-'; + if( xtype==etFLOAT ){ + iRound = -precision; + }else if( xtype==etGENERIC ){ + iRound = precision; }else{ - prefix = flag_prefix; + iRound = precision+1; } - exp = 0; - if( xtype==etGENERIC && precision>0 ) precision--; - testcase( precision>0xfff ); - if( realvalue<1.0e+16 - && realvalue==(LONGDOUBLE_TYPE)(longvalue = (u64)realvalue) - ){ - /* Number is a pure integer that can be represented as u64 */ - for(msd=1; msd*10<=longvalue; msd *= 10, exp++){} - if( exp>precision && xtype!=etFLOAT ){ - u64 rnd = msd/2; - int kk = precision; - while( kk-- > 0 ){ rnd /= 10; } - longvalue += rnd; - } - }else{ - msd = 0; - longvalue = 0; /* To prevent a compiler warning */ - idx = precision & 0xfff; - rounder = arRound[idx%10]; - while( idx>=10 ){ rounder *= 1.0e-10; idx -= 10; } - if( xtype==etFLOAT ){ - double rx = (double)realvalue; - sqlite3_uint64 u; - int ex; - memcpy(&u, &rx, sizeof(u)); - ex = -1023 + (int)((u>>52)&0x7ff); - if( precision+(ex/3) < 15 ) rounder += realvalue*3e-16; - realvalue += rounder; - } - if( sqlite3IsNaN((double)realvalue) ){ - if( flag_zeropad ){ - bufpt = "null"; - length = 4; + sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 26 : 16); + if( s.isSpecial ){ + if( s.isSpecial==2 ){ + bufpt = flag_zeropad ? "null" : "NaN"; + length = sqlite3Strlen30(bufpt); + break; + }else if( flag_zeropad ){ + s.z[0] = '9'; + s.iDP = 1000; + s.n = 1; + }else{ + memcpy(buf, "-Inf", 5); + bufpt = buf; + if( s.sign=='-' ){ + /* no-op */ + }else if( flag_prefix ){ + buf[0] = flag_prefix; }else{ - bufpt = "NaN"; - length = 3; + bufpt++; } + length = sqlite3Strlen30(bufpt); break; } - - /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ - if( ALWAYS(realvalue>0.0) ){ - LONGDOUBLE_TYPE scale = 1.0; - while( realvalue>=1e100*scale && exp<=350){ scale*=1e100;exp+=100;} - while( realvalue>=1e10*scale && exp<=350 ){ scale*=1e10; exp+=10; } - while( realvalue>=10.0*scale && exp<=350 ){ scale *= 10.0; exp++; } - realvalue /= scale; - while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; } - while( realvalue<1.0 ){ realvalue *= 10.0; exp--; } - if( exp>350 ){ - if( flag_zeropad ){ - realvalue = 9.0; - exp = 999; - }else{ - bufpt = buf; - buf[0] = prefix; - memcpy(buf+(prefix!=0),"Inf",4); - length = 3+(prefix!=0); - break; - } - } - if( xtype!=etFLOAT ){ - realvalue += rounder; - if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } - } - } + } + if( s.sign=='-' ){ + prefix = '-'; + }else{ + prefix = flag_prefix; } + exp = s.iDP-1; + if( xtype==etGENERIC && precision>0 ) precision--; + /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. @@ -642,9 +553,8 @@ void sqlite3_str_vappendf( if( xtype==etEXP ){ e2 = 0; }else{ - e2 = exp; + e2 = s.iDP - 1; } - nsd = 16 + flag_altform2*10; bufpt = buf; { i64 szBufNeeded; /* Size of a temporary buffer needed */ @@ -662,16 +572,12 @@ void sqlite3_str_vappendf( *(bufpt++) = prefix; } /* Digits prior to the decimal point */ + j = 0; if( e2<0 ){ *(bufpt++) = '0'; - }else if( msd>0 ){ - for(; e2>=0; e2--){ - *(bufpt++) = et_getdigit_int(&longvalue,&msd); - if( cThousand && (e2%3)==0 && e2>1 ) *(bufpt++) = ','; - } }else{ for(; e2>=0; e2--){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); + *(bufpt++) = j1 ) *(bufpt++) = ','; } } @@ -681,19 +587,12 @@ void sqlite3_str_vappendf( } /* "0" digits after the decimal point but before the first ** significant digit of the number */ - for(e2++; e2<0; precision--, e2++){ - assert( precision>0 ); + for(e2++; e2<0 && precision>0; precision--, e2++){ *(bufpt++) = '0'; } /* Significant digits after the decimal point */ - if( msd>0 ){ - while( (precision--)>0 ){ - *(bufpt++) = et_getdigit_int(&longvalue,&msd); - } - }else{ - while( (precision--)>0 ){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); - } + while( (precision--)>0 ){ + *(bufpt++) = jcharset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; @@ -742,8 +642,8 @@ void sqlite3_str_vappendf( while( nPad-- ) bufpt[i++] = '0'; length = width; } -#endif /* !defined(SQLITE_OMIT_FLOATING_POINT) */ break; + } case etSIZE: if( !bArgList ){ *(va_arg(ap,int*)) = pAccum->nChar; @@ -1466,3 +1366,72 @@ void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ sqlite3_str_vappendf(p, zFormat, ap); va_end(ap); } + + +/***************************************************************************** +** Reference counted string storage +*****************************************************************************/ + +/* +** Increase the reference count of the string by one. +** +** The input parameter is returned. +*/ +char *sqlite3RCStrRef(char *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + p->nRCRef++; + return z; +} + +/* +** Decrease the reference count by one. Free the string when the +** reference count reaches zero. +*/ +void sqlite3RCStrUnref(char *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + assert( p->nRCRef>0 ); + if( p->nRCRef>=2 ){ + p->nRCRef--; + }else{ + sqlite3_free(p); + } +} + +/* +** Create a new string that is capable of holding N bytes of text, not counting +** the zero byte at the end. The string is uninitialized. +** +** The reference count is initially 1. Call sqlite3RCStrUnref() to free the +** newly allocated string. +** +** This routine returns 0 on an OOM. +*/ +char *sqlite3RCStrNew(u64 N){ + RCStr *p = sqlite3_malloc64( N + sizeof(*p) + 1 ); + if( p==0 ) return 0; + p->nRCRef = 1; + return (char*)&p[1]; +} + +/* +** Change the size of the string so that it is able to hold N bytes. +** The string might be reallocated, so return the new allocation. +*/ +char *sqlite3RCStrResize(char *z, u64 N){ + RCStr *p = (RCStr*)z; + RCStr *pNew; + assert( p!=0 ); + p--; + assert( p->nRCRef==1 ); + pNew = sqlite3_realloc64(p, N+sizeof(RCStr)+1); + if( pNew==0 ){ + sqlite3_free(p); + return 0; + }else{ + return (char*)&pNew[1]; + } +} diff --git a/src/resolve.c b/src/resolve.c index adfcc8dbe..7fc0151ad 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -166,7 +166,7 @@ Bitmask sqlite3ExprColUsed(Expr *pExpr){ pExTab = pExpr->y.pTab; assert( pExTab!=0 ); if( (pExTab->tabFlags & TF_HasGenerated)!=0 - && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0 + && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0 ){ testcase( pExTab->nCol==BMS-1 ); testcase( pExTab->nCol==BMS ); @@ -230,7 +230,7 @@ static SQLITE_NOINLINE int isValidSchemaTableName( /* ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up -** that name in the set of source tables in pSrcList and make the pExpr +** that name in the set of source tables in pSrcList and make the pExpr ** expression node refer back to that source column. The following changes ** are made to pExpr: ** @@ -454,7 +454,7 @@ static int lookupName( } /* if( pSrcList ) */ #if !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT) - /* If we have not already resolved the name, then maybe + /* If we have not already resolved the name, then maybe ** it is a new.* or old.* trigger argument reference. Or ** maybe it is an excluded.* from an upsert. Or maybe it is ** a reference in the RETURNING clause to a table being modified. @@ -492,7 +492,7 @@ static int lookupName( } #endif /* SQLITE_OMIT_UPSERT */ - if( pTab ){ + if( pTab ){ int iCol; u8 hCol = sqlite3StrIHash(zCol); pSchema = pTab->pSchema; @@ -633,7 +633,7 @@ static int lookupName( } goto lookupname_end; } - } + } } /* Advance to the next name context. The loop will exit when either @@ -824,7 +824,7 @@ Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){ ** ** static void notValid( ** Parse *pParse, // Leave error message here -** NameContext *pNC, // The name context +** NameContext *pNC, // The name context ** const char *zMsg, // Type of error ** int validMask, // Set of contexts for which prohibited ** Expr *pExpr // Invalidate this expression on error @@ -905,7 +905,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ /* The special operator TK_ROW means use the rowid for the first ** column in the FROM clause. This is used by the LIMIT and ORDER BY - ** clause processing on UPDATE and DELETE statements, and by + ** clause processing on UPDATE and DELETE statements, and by ** UPDATE ... FROM statement processing. */ case TK_ROW: { @@ -941,7 +941,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ int anRef[8]; NameContext *p; int i; - for(i=0, p=pNC; p && ipNext, i++){ + for(i=0, p=pNC; p && ipNext, i++){ anRef[i] = p->nRef; } sqlite3WalkExpr(pWalker, pExpr->pLeft); @@ -966,7 +966,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ** Or a database, table and column: ID.ID.ID ** ** The TK_ID and TK_OUT cases are combined so that there will only - ** be one call to lookupName(). Then the compiler will in-line + ** be one call to lookupName(). Then the compiler will in-line ** lookupName() for a size reduction and performance increase. */ case TK_ID: @@ -1057,7 +1057,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ** to likelihood(X,0.9375). */ /* TUNING: unlikely() probability is 0.0625. likely() is 0.9375 */ pExpr->iTable = pDef->zName[0]=='u' ? 8388608 : 125829120; - } + } } #ifndef SQLITE_OMIT_AUTHORIZATION { @@ -1119,11 +1119,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ || (pDef->xValue && pDef->xInverse && pDef->xSFunc && pDef->xFinalize) ); if( pDef && pDef->xValue==0 && pWin ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "%#T() may not be used as a window function", pExpr ); pNC->nNcErr++; - }else if( + }else if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) || (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pWin) || (is_agg && pWin && (pNC->ncFlags & NC_AllowWin)==0) @@ -1159,7 +1159,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } #ifndef SQLITE_OMIT_WINDOWFUNC else if( is_agg==0 && ExprHasProperty(pExpr, EP_WinFunc) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "FILTER may not be used with non-aggregate %#T()", pExpr ); @@ -1209,7 +1209,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } #endif pNC2 = pNC; - while( pNC2 + while( pNC2 && sqlite3ReferencesSrcList(pParse, pExpr, pNC2->pSrcList)==0 ){ pExpr->op2++; @@ -1221,7 +1221,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( SQLITE_FUNC_ANYORDER==NC_OrderAgg ); testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 ); testcase( (pDef->funcFlags & SQLITE_FUNC_ANYORDER)!=0 ); - pNC2->ncFlags |= NC_HasAgg + pNC2->ncFlags |= NC_HasAgg | ((pDef->funcFlags^SQLITE_FUNC_ANYORDER) & (SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER)); } @@ -1229,7 +1229,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ pNC->ncFlags |= savedAllowFlags; } /* FIX ME: Compute pExpr->affinity based on the expected return - ** type of the function + ** type of the function */ return WRC_Prune; } @@ -1318,7 +1318,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ sqlite3ErrorMsg(pParse, "row value misused"); sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); } - break; + break; } } assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); @@ -1433,7 +1433,7 @@ static void resolveOutOfRangeError( int mx, /* Largest permissible value of i */ Expr *pError /* Associate the error with the expression */ ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "%r %s BY term out of range - should be " "between 1 and %d", i, zType, mx); sqlite3RecordErrorOffsetOfExpr(pParse->db, pError); @@ -1499,7 +1499,7 @@ static int resolveCompoundOrderBy( iCol = resolveAsName(pParse, pEList, pE); if( iCol==0 ){ /* Now test if expression pE matches one of the values returned - ** by pSelect. In the usual case this is done by duplicating the + ** by pSelect. In the usual case this is done by duplicating the ** expression, resolving any symbols in it, and then comparing ** it against each expression returned by the SELECT statement. ** Once the comparisons are finished, the duplicate expression @@ -1694,7 +1694,7 @@ static int resolveOrderGroupBy( } for(j=0; jpEList->nExpr; j++){ if( sqlite3ExprCompare(0, pE, pSelect->pEList->a[j].pExpr, -1)==0 ){ - /* Since this expresion is being changed into a reference + /* Since this expression is being changed into a reference ** to an identical expression in the result set, remove all Window ** objects belonging to the expression from the Select.pWin list. */ windowRemoveExprFromSelect(pSelect, pE); @@ -1718,7 +1718,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ExprList *pGroupBy; /* The GROUP BY clause */ Select *pLeftmost; /* Left-most of SELECT of a compound */ sqlite3 *db; /* Database connection */ - + assert( p!=0 ); if( p->selFlags & SF_Resolved ){ @@ -1774,7 +1774,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ pSub->pOrderBy = p->pOrderBy; p->pOrderBy = 0; } - + /* Recursively resolve names in all subqueries in the FROM clause */ for(i=0; ipSrc->nSrc; i++){ @@ -1801,19 +1801,19 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } } - + /* Set up the local name-context to pass to sqlite3ResolveExprNames() to ** resolve the result-set expression list. */ sNC.ncFlags = NC_AllowAgg|NC_AllowWin; sNC.pSrcList = p->pSrc; sNC.pNext = pOuterNC; - + /* Resolve names in the result set. */ if( sqlite3ResolveExprListNames(&sNC, p->pEList) ) return WRC_Abort; sNC.ncFlags &= ~NC_AllowWin; - - /* If there are no aggregate functions in the result-set, and no GROUP BY + + /* If there are no aggregate functions in the result-set, and no GROUP BY ** expression, do not allow aggregates in any of the other expressions. */ assert( (p->selFlags & SF_Aggregate)==0 ); @@ -1825,7 +1825,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ }else{ sNC.ncFlags &= ~NC_AllowAgg; } - + /* Add the output column list to the name-context before parsing the ** other expressions in the SELECT statement. This is so that ** expressions in the WHERE clause (etc.) can refer to expressions by @@ -1850,7 +1850,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; if( pItem->fg.isTabFunc - && sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg) + && sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg) ){ return WRC_Abort; } @@ -1870,12 +1870,12 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ #endif /* The ORDER BY and GROUP BY clauses may not refer to terms in - ** outer queries + ** outer queries */ sNC.pNext = 0; sNC.ncFlags |= NC_AllowAgg|NC_AllowWin; - /* If this is a converted compound query, move the ORDER BY clause from + /* If this is a converted compound query, move the ORDER BY clause from ** the sub-query back to the parent query. At this point each term ** within the ORDER BY clause has been transformed to an integer value. ** These integers will be replaced by copies of the corresponding result @@ -1906,13 +1906,13 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ return WRC_Abort; } sNC.ncFlags &= ~NC_AllowWin; - - /* Resolve the GROUP BY clause. At the same time, make sure + + /* Resolve the GROUP BY clause. At the same time, make sure ** the GROUP BY clause does not contain aggregate functions. */ if( pGroupBy ){ struct ExprList_item *pItem; - + if( resolveOrderGroupBy(&sNC, p, pGroupBy, "GROUP") || db->mallocFailed ){ return WRC_Abort; } @@ -1954,7 +1954,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** checking on function usage and set a flag if any aggregate functions ** are seen. ** -** To resolve table columns references we look for nodes (or subtrees) of the +** To resolve table columns references we look for nodes (or subtrees) of the ** form X.Y.Z or Y.Z or just Z where ** ** X: The name of a database. Ex: "main" or "temp" or @@ -1986,7 +1986,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** ** SELECT a+b AS x, c+d AS y FROM t1 ORDER BY a+b; ** -** Function calls are checked to make sure that the function is +** Function calls are checked to make sure that the function is ** defined and that the correct number of arguments are specified. ** If the function is an aggregate function, then the NC_HasAgg flag is ** set and the opcode is changed from TK_FUNCTION to TK_AGG_FUNCTION. @@ -1996,7 +1996,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** An error message is left in pParse if anything is amiss. The number ** if errors is returned. */ -int sqlite3ResolveExprNames( +int sqlite3ResolveExprNames( NameContext *pNC, /* Namespace to resolve expressions in. */ Expr *pExpr /* The expression to be analyzed. */ ){ @@ -2017,7 +2017,8 @@ int sqlite3ResolveExprNames( return SQLITE_ERROR; } #endif - sqlite3WalkExpr(&w, pExpr); + assert( pExpr!=0 ); + sqlite3WalkExprNN(&w, pExpr); #if SQLITE_MAX_EXPR_DEPTH>0 w.pParse->nHeight -= pExpr->nHeight; #endif @@ -2035,7 +2036,7 @@ int sqlite3ResolveExprNames( ** just like sqlite3ResolveExprNames() except that it works for an expression ** list rather than a single expression. */ -int sqlite3ResolveExprListNames( +int sqlite3ResolveExprListNames( NameContext *pNC, /* Namespace to resolve expressions in. */ ExprList *pList /* The expression list to be analyzed. */ ){ @@ -2059,7 +2060,7 @@ int sqlite3ResolveExprListNames( return WRC_Abort; } #endif - sqlite3WalkExpr(&w, pExpr); + sqlite3WalkExprNN(&w, pExpr); #if SQLITE_MAX_EXPR_DEPTH>0 w.pParse->nHeight -= pExpr->nHeight; #endif @@ -2081,7 +2082,7 @@ int sqlite3ResolveExprListNames( /* ** Resolve all names in all expressions of a SELECT and in all -** decendents of the SELECT, including compounds off of p->pPrior, +** descendants of the SELECT, including compounds off of p->pPrior, ** subqueries in expressions, and subqueries used as FROM clause ** terms. ** diff --git a/src/rowset.c b/src/rowset.c index 0562320ab..5956cb2ad 100644 --- a/src/rowset.c +++ b/src/rowset.c @@ -35,14 +35,14 @@ ** extracts the least value from the RowSet. ** ** The INSERT primitive might allocate additional memory. Memory is -** allocated in chunks so most INSERTs do no allocation. There is an +** allocated in chunks so most INSERTs do no allocation. There is an ** upper bound on the size of allocated memory. No memory is freed ** until DESTROY. ** ** The TEST primitive includes a "batch" number. The TEST primitive ** will only see elements that were inserted before the last change ** in the batch number. In other words, if an INSERT occurs between -** two TESTs where the TESTs have the same batch nubmer, then the +** two TESTs where the TESTs have the same batch number, then the ** value added by the INSERT will not be visible to the second TEST. ** The initial batch number is zero, so if the very first TEST contains ** a non-zero batch number, it will see all prior INSERTs. @@ -83,7 +83,7 @@ ** in the list, pLeft points to the tree, and v is unused. The ** RowSet.pForest value points to the head of this forest list. */ -struct RowSetEntry { +struct RowSetEntry { i64 v; /* ROWID value for this entry */ struct RowSetEntry *pRight; /* Right subtree (larger entries) or list */ struct RowSetEntry *pLeft; /* Left subtree (smaller entries) */ @@ -235,7 +235,7 @@ void sqlite3RowSetInsert(RowSet *p, i64 rowid){ /* ** Merge two lists of RowSetEntry objects. Remove duplicates. ** -** The input lists are connected via pRight pointers and are +** The input lists are connected via pRight pointers and are ** assumed to each already be in sorted order. */ static struct RowSetEntry *rowSetEntryMerge( @@ -272,7 +272,7 @@ static struct RowSetEntry *rowSetEntryMerge( /* ** Sort all elements on the list of RowSetEntry objects into order of ** increasing v. -*/ +*/ static struct RowSetEntry *rowSetEntrySort(struct RowSetEntry *pIn){ unsigned int i; struct RowSetEntry *pNext, *aBucket[40]; @@ -345,7 +345,7 @@ static struct RowSetEntry *rowSetNDeepTree( struct RowSetEntry *pLeft; /* Left subtree */ if( *ppList==0 ){ /*OPTIMIZATION-IF-TRUE*/ /* Prevent unnecessary deep recursion when we run out of entries */ - return 0; + return 0; } if( iDepth>1 ){ /*OPTIMIZATION-IF-TRUE*/ /* This branch causes a *balanced* tree to be generated. A valid tree diff --git a/src/select.c b/src/select.c index f32db2c2a..321badd07 100644 --- a/src/select.c +++ b/src/select.c @@ -231,8 +231,8 @@ static Select *findRightmost(Select *p){ ** NATURAL FULL - JT_NATURAL|JT_LEFT|JT_RIGHT ** NATURAL FULL OUTER JT_NATRUAL|JT_LEFT|JT_RIGHT ** -** To preserve historical compatibly, SQLite also accepts a variety -** of other non-standard and in many cases non-sensical join types. +** To preserve historical compatibly, SQLite also accepts a variety +** of other non-standard and in many cases nonsensical join types. ** This routine makes as much sense at it can from the nonsense join ** type and returns a result. Examples of accepted nonsense join types ** include but are not limited to: @@ -282,7 +282,7 @@ int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ for(i=0; i<3 && apAll[i]; i++){ p = apAll[i]; for(j=0; jn==aKeyword[j].nChar + if( p->n==aKeyword[j].nChar && sqlite3StrNICmp((char*)p->z, &zKeyText[aKeyword[j].i], p->n)==0 ){ jointype |= aKeyword[j].code; break; @@ -368,7 +368,7 @@ static int tableAndColumnIndex( for(i=iStart; i<=iEnd; i++){ iCol = sqlite3ColumnIndex(pSrc->a[i].pTab, zCol); - if( iCol>=0 + if( iCol>=0 && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0) ){ if( piTab ){ @@ -426,7 +426,7 @@ void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){ } sqlite3SetJoinExpr(p->pLeft, iTable, joinFlag); p = p->pRight; - } + } } /* Undo the work of sqlite3SetJoinExpr(). This is used when a LEFT JOIN @@ -463,7 +463,7 @@ static void unsetJoinExpr(Expr *p, int iTable, int nullable){ } unsetJoinExpr(p->pLeft, iTable, nullable); p = p->pRight; - } + } } /* @@ -503,7 +503,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue; joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON; - /* If this is a NATURAL join, synthesize an approprate USING clause + /* If this is a NATURAL join, synthesize an appropriate USING clause ** to specify which columns should be joined. */ if( pRight->fg.jointype & JT_NATURAL ){ @@ -536,7 +536,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ } /* Create extra terms on the WHERE clause for each column named - ** in the USING clause. Example: If the two tables to be joined are + ** in the USING clause. Example: If the two tables to be joined are ** A and B and the USING clause names X, Y, and Z, then add this ** to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z ** Report an error if any column mentioned in the USING clause is @@ -718,8 +718,8 @@ static void pushOntoSorter( ** case regData==regOrigData. ** (3) Some output columns are omitted from the sort record due to ** the SQLITE_ENABLE_SORTER_REFERENCES optimization, or due to the - ** SQLITE_ECEL_OMITREF optimization, or due to the - ** SortCtx.pDeferredRowLoad optimiation. In any of these cases + ** SQLITE_ECEL_OMITREF optimization, or due to the + ** SortCtx.pDeferredRowLoad optimization. In any of these cases ** regOrigData is 0 to prevent this routine from trying to copy ** values that might not yet exist. */ @@ -760,7 +760,7 @@ static void pushOntoSorter( pParse->nMem += pSort->nOBSat; nKey = nExpr - pSort->nOBSat + bSeq; if( bSeq ){ - addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr); + addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr); }else{ addrFirst = sqlite3VdbeAddOp1(v, OP_SequenceTest, pSort->iECursor); } @@ -775,7 +775,7 @@ static void pushOntoSorter( testcase( pKI->nAllField > pKI->nKeyField+2 ); pOp->p4.pKeyInfo = sqlite3KeyInfoFromExprList(pParse,pSort->pOrderBy,nOBSat, pKI->nAllField-pKI->nKeyField-1); - pOp = 0; /* Ensure pOp not used after sqltie3VdbeAddOp3() */ + pOp = 0; /* Ensure pOp not used after sqlite3VdbeAddOp3() */ addrJmp = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v); pSort->labelBkOut = sqlite3VdbeMakeLabel(pParse); @@ -794,10 +794,10 @@ static void pushOntoSorter( /* At this point the values for the new sorter entry are stored ** in an array of registers. They need to be composed into a record ** and inserted into the sorter if either (a) there are currently - ** less than LIMIT+OFFSET items or (b) the new record is smaller than + ** less than LIMIT+OFFSET items or (b) the new record is smaller than ** the largest record currently in the sorter. If (b) is true and there ** are already LIMIT+OFFSET items in the sorter, delete the largest - ** entry before inserting the new one. This way there are never more + ** entry before inserting the new one. This way there are never more ** than LIMIT+OFFSET items in the sorter. ** ** If the new record does not need to be inserted into the sorter, @@ -869,7 +869,7 @@ static void codeOffset( ** The returned value in this case is a copy of parameter iTab. ** ** WHERE_DISTINCT_ORDERED: -** In this case rows are being delivered sorted order. The ephermal +** In this case rows are being delivered sorted order. The ephemeral ** table is not required. Instead, the current set of values ** is compared against previous row. If they match, the new row ** is not distinct and control jumps to VM address addrRepeat. Otherwise, @@ -885,10 +885,10 @@ static void codeOffset( ** In this case it has already been determined that the rows are distinct. ** No special action is required. The return value is zero. ** -** Parameter pEList is the list of expressions used to generated the -** contents of each row. It is used by this routine to determine (a) -** how many elements there are in the array of registers and (b) the -** collation sequences that should be used for the comparisons if +** Parameter pEList is the list of expressions used to generated the +** contents of each row. It is used by this routine to determine (a) +** how many elements there are in the array of registers and (b) the +** collation sequences that should be used for the comparisons if ** eTnctType is WHERE_DISTINCT_ORDERED. */ static int codeDistinct( @@ -990,7 +990,7 @@ static void fixDistinctOpenEph( sqlite3VdbeChangeToNoop(v, iOpenEphAddr+1); } if( eTnctType==WHERE_DISTINCT_ORDERED ){ - /* Change the OP_OpenEphemeral to an OP_Null that sets the MEM_Cleared + /* Change the OP_OpenEphemeral to an OP_Null that sets the MEM_Cleared ** bit on the first register of the previous value. This will cause the ** OP_Ne added in codeDistinct() to always fail on the first iteration of ** the loop even if the first row is all NULLs. */ @@ -1005,8 +1005,8 @@ static void fixDistinctOpenEph( #ifdef SQLITE_ENABLE_SORTER_REFERENCES /* ** This function is called as part of inner-loop generation for a SELECT -** statement with an ORDER BY that is not optimized by an index. It -** determines the expressions, if any, that the sorter-reference +** statement with an ORDER BY that is not optimized by an index. It +** determines the expressions, if any, that the sorter-reference ** optimization should be used for. The sorter-reference optimization ** is used for SELECT queries like: ** @@ -1016,11 +1016,11 @@ static void fixDistinctOpenEph( ** storing values read from that column in the sorter records, the PK of ** the row from table t1 is stored instead. Then, as records are extracted from ** the sorter to return to the user, the required value of bigblob is -** retrieved directly from table t1. If the values are very large, this +** retrieved directly from table t1. If the values are very large, this ** can be more efficient than storing them directly in the sorter records. ** -** The ExprList_item.fg.bSorterRef flag is set for each expression in pEList -** for which the sorter-reference optimization should be enabled. +** The ExprList_item.fg.bSorterRef flag is set for each expression in pEList +** for which the sorter-reference optimization should be enabled. ** Additionally, the pSort->aDefer[] array is populated with entries ** for all cursors required to evaluate all selected expressions. Finally. ** output variable (*ppExtra) is set to an expression list containing @@ -1094,7 +1094,7 @@ static void selectExprDefer( ** ** If srcTab is negative, then the p->pEList expressions ** are evaluated in order to get the data for this row. If srcTab is -** zero or more, then data is pulled from srcTab and p->pEList is used only +** zero or more, then data is pulled from srcTab and p->pEList is used only ** to get the number of columns and the collation sequence for each column. */ static void selectInnerLoop( @@ -1176,8 +1176,8 @@ static void selectInnerLoop( } if( pSort && hasDistinct==0 && eDest!=SRT_EphemTab && eDest!=SRT_Table ){ /* For each expression in p->pEList that is a copy of an expression in - ** the ORDER BY clause (pSort->pOrderBy), set the associated - ** iOrderByCol value to one more than the index of the ORDER BY + ** the ORDER BY clause (pSort->pOrderBy), set the associated + ** iOrderByCol value to one more than the index of the ORDER BY ** expression within the sort-key that pushOntoSorter() will generate. ** This allows the p->pEList field to be omitted from the sorted record, ** saving space and CPU cycles. */ @@ -1193,7 +1193,7 @@ static void selectInnerLoop( selectExprDefer(pParse, pSort, p->pEList, &pExtra); if( pExtra && pParse->db->mallocFailed==0 ){ /* If there are any extra PK columns to add to the sorter records, - ** allocate extra memory cells and adjust the OpenEphemeral + ** allocate extra memory cells and adjust the OpenEphemeral ** instruction to account for the larger records. This is only ** required if there are one or more WITHOUT ROWID tables with ** composite primary keys in the SortCtx.aDefer[] array. */ @@ -1223,7 +1223,7 @@ static void selectInnerLoop( testcase( eDest==SRT_Mem ); testcase( eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); - assert( eDest==SRT_Set || eDest==SRT_Mem + assert( eDest==SRT_Set || eDest==SRT_Mem || eDest==SRT_Coroutine || eDest==SRT_Output || eDest==SRT_Upfrom ); } @@ -1235,7 +1235,7 @@ static void selectInnerLoop( if( pExtra ) nResultCol += pExtra->nExpr; #endif if( p->iLimit - && (ecelFlags & SQLITE_ECEL_OMITREF)!=0 + && (ecelFlags & SQLITE_ECEL_OMITREF)!=0 && nPrefixReg>0 ){ assert( pSort!=0 ); @@ -1298,6 +1298,16 @@ static void selectInnerLoop( testcase( eDest==SRT_Fifo ); testcase( eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); +#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) + /* A destination of SRT_Table and a non-zero iSDParm2 parameter means + ** that this is an "UPDATE ... FROM" on a virtual table or view. In this + ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. + ** This does not affect operation in any way - it just allows MakeRecord + ** to process OPFLAG_NOCHANGE values without an assert() failing. */ + if( eDest==SRT_Table && pDest->iSDParm2 ){ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); + } +#endif #ifndef SQLITE_OMIT_CTE if( eDest==SRT_DistFifo ){ /* If the destination is DistFifo, then cursor (iParm+1) is open @@ -1366,7 +1376,7 @@ static void selectInnerLoop( }else{ int r1 = sqlite3GetTempReg(pParse); assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol ); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, + sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, r1, pDest->zAffSdst, nResultCol); sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); sqlite3ReleaseTempReg(pParse, r1); @@ -1384,7 +1394,7 @@ static void selectInnerLoop( } /* If this is a scalar select that is part of an expression, then - ** store the results in the appropriate memory cell or array of + ** store the results in the appropriate memory cell or array of ** memory cells and break out of the scan loop. */ case SRT_Mem: { @@ -1439,7 +1449,7 @@ static void selectInnerLoop( /* If the destination is DistQueue, then cursor (iParm+1) is open ** on a second ephemeral index that holds all values every previously ** added to the queue. */ - addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0, + addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0, regResult, nResultCol); VdbeCoverage(v); } @@ -1659,7 +1669,7 @@ static void generateSortTail( int addrExplain; /* Address of OP_Explain instruction */ #endif - ExplainQueryPlan2(addrExplain, (pParse, 0, + ExplainQueryPlan2(addrExplain, (pParse, 0, "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat>0?"RIGHT PART OF ":"") ); sqlite3VdbeScanStatusRange(v, addrExplain,pSort->addrPush,pSort->addrPushEnd); @@ -1706,7 +1716,7 @@ static void generateSortTail( if( pSort->labelBkOut ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } - sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, + sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, nKey+1+nColumn+nRefKey); if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak); @@ -1742,7 +1752,7 @@ static void generateSortTail( sqlite3VdbeAddOp1(v, OP_NullRow, iCsr); if( HasRowid(pTab) ){ sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iKey++, regKey); - sqlite3VdbeAddOp3(v, OP_SeekRowid, iCsr, + sqlite3VdbeAddOp3(v, OP_SeekRowid, iCsr, sqlite3VdbeCurrentAddr(v)+1, regKey); }else{ int k; @@ -1812,7 +1822,7 @@ static void generateSortTail( break; } default: { - assert( eDest==SRT_Output || eDest==SRT_Coroutine ); + assert( eDest==SRT_Output || eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); testcase( eDest==SRT_Coroutine ); if( eDest==SRT_Output ){ @@ -1852,14 +1862,14 @@ static void generateSortTail( ** original CREATE TABLE statement if the expression is a column. The ** declaration type for a ROWID field is INTEGER. Exactly when an expression ** is considered a column can be complex in the presence of subqueries. The -** result-set expression in all of the following SELECT statements is +** result-set expression in all of the following SELECT statements is ** considered a column by this function. ** ** SELECT col FROM tbl; ** SELECT (SELECT col FROM tbl; ** SELECT (SELECT col FROM tbl); ** SELECT abc FROM (SELECT col AS abc FROM tbl); -** +** ** The declaration type for any expression other than a column is NULL. ** ** This routine has either 3 or 6 parameters depending on whether or not @@ -1871,7 +1881,7 @@ static void generateSortTail( # define columnType(A,B,C,D,E) columnTypeImpl(A,B) #endif static const char *columnTypeImpl( - NameContext *pNC, + NameContext *pNC, #ifndef SQLITE_ENABLE_COLUMN_METADATA Expr *pExpr #else @@ -1914,19 +1924,19 @@ static const char *columnTypeImpl( if( pTab==0 ){ /* At one time, code such as "SELECT new.x" within a trigger would ** cause this condition to run. Since then, we have restructured how - ** trigger code is generated and so this condition is no longer + ** trigger code is generated and so this condition is no longer ** possible. However, it can still be true for statements like ** the following: ** ** CREATE TABLE t1(col INTEGER); ** SELECT (SELECT t1.col) FROM FROM t1; ** - ** when columnType() is called on the expression "t1.col" in the + ** when columnType() is called on the expression "t1.col" in the ** sub-select. In this case, set the column type to NULL, even ** though it should really be "INTEGER". ** ** This is not a problem, as the column type of "t1.col" is never - ** used. When columnType() is called on the expression + ** used. When columnType() is called on the expression ** "(SELECT t1.col)", the correct type is returned (see the TK_SELECT ** branch below. */ break; @@ -1944,9 +1954,9 @@ static const char *columnTypeImpl( #else && ALWAYS(iCol>=0) #endif - ){ + ){ /* If iCol is less than zero, then the expression requests the - ** rowid of the sub-select or view. This expression is legal (see + ** rowid of the sub-select or view. This expression is legal (see ** test case misc2.2.2) - it always evaluates to NULL. */ NameContext sNC; @@ -1954,7 +1964,7 @@ static const char *columnTypeImpl( sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol); + zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol); } }else{ /* A real table or a CTE table */ @@ -2000,13 +2010,13 @@ static const char *columnTypeImpl( sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); break; } #endif } -#ifdef SQLITE_ENABLE_COLUMN_METADATA +#ifdef SQLITE_ENABLE_COLUMN_METADATA if( pzOrigDb ){ assert( pzOrigTab && pzOrigCol ); *pzOrigDb = zOrigDb; @@ -2042,7 +2052,7 @@ static void generateColumnTypes( const char *zOrigCol = 0; zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); - /* The vdbe must make its own copy of the column-type and other + /* The vdbe must make its own copy of the column-type and other ** column specific strings, in case the schema is reset before this ** virtual machine is deleted. */ @@ -2101,13 +2111,6 @@ void sqlite3GenerateColumnNames( int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ -#ifndef SQLITE_OMIT_EXPLAIN - /* If this is an EXPLAIN, skip this step */ - if( pParse->explain ){ - return; - } -#endif - if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; @@ -2294,7 +2297,7 @@ int sqlite3ColumnsFromExprList( ** kind (maybe a parenthesized subquery in the FROM clause of a larger ** query, or a VIEW, or a CTE). This routine computes type information ** for that Table object based on the Select object that implements the -** subquery. For the purposes of this routine, "type infomation" means: +** subquery. For the purposes of this routine, "type information" means: ** ** * The datatype name, as it might appear in a CREATE TABLE statement ** * Which collating sequence to use for the column @@ -2438,9 +2441,9 @@ Vdbe *sqlite3GetVdbe(Parse *pParse){ ** Compute the iLimit and iOffset fields of the SELECT based on the ** pLimit expressions. pLimit->pLeft and pLimit->pRight hold the expressions ** that appear in the original SQL statement after the LIMIT and OFFSET -** keywords. Or NULL if those keywords are omitted. iLimit and iOffset -** are the integer memory register numbers for counters used to compute -** the limit and offset. If there is no limit and/or offset, then +** keywords. Or NULL if those keywords are omitted. iLimit and iOffset +** are the integer memory register numbers for counters used to compute +** the limit and offset. If there is no limit and/or offset, then ** iLimit and iOffset are negative. ** ** This routine changes the values of iLimit and iOffset only if @@ -2466,7 +2469,7 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ if( p->iLimit ) return; - /* + /* ** "LIMIT -1" always shows all rows. There is some ** controversy about what the correct behavior should be. ** The current implementation interprets "LIMIT 0" to mean @@ -2594,7 +2597,7 @@ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ ** inserted into the Queue table. The iDistinct table keeps a copy of all rows ** that have ever been inserted into Queue and causes duplicates to be ** discarded. If the operator is UNION ALL, then duplicates are allowed. -** +** ** If the query has an ORDER BY, then entries in the Queue table are kept in ** ORDER BY order and the first entry is extracted for each cycle. Without ** an ORDER BY, the Queue table is just a FIFO. @@ -2623,7 +2626,7 @@ static void generateWithRecursiveQuery( int iQueue; /* The Queue table */ int iDistinct = 0; /* To ensure unique results if UNION */ int eDest = SRT_Fifo; /* How to write to Queue */ - SelectDest destQueue; /* SelectDest targetting the Queue table */ + SelectDest destQueue; /* SelectDest targeting the Queue table */ int i; /* Loop counter */ int rc; /* Result code */ ExprList *pOrderBy; /* The ORDER BY clause */ @@ -2831,7 +2834,7 @@ static int hasAnchor(Select *p){ ** ** "p" points to the right-most of the two queries. the query on the ** left is p->pPrior. The left query could also be a compound query -** in which case this routine will be called recursively. +** in which case this routine will be called recursively. ** ** The results of the total query are to be written into a destination ** of type eDest with parameter iParm. @@ -2959,7 +2962,7 @@ static int multiSelect( p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); if( p->pLimit && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit) - && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) + && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) ){ p->nSelectRow = sqlite3LogEst((u64)nLimit); } @@ -2976,7 +2979,7 @@ static int multiSelect( Expr *pLimit; /* Saved values of p->nLimit */ int addr; SelectDest uniondest; - + testcase( p->op==TK_EXCEPT ); testcase( p->op==TK_UNION ); priorOp = SRT_Union; @@ -2998,8 +3001,8 @@ static int multiSelect( findRightmost(p)->selFlags |= SF_UsesEphemeral; assert( p->pEList ); } - - + + /* Code the SELECT statements to our left */ assert( !pPrior->pOrderBy ); @@ -3009,7 +3012,7 @@ static int multiSelect( if( rc ){ goto multi_select_end; } - + /* Code the current SELECT statement */ if( p->op==TK_EXCEPT ){ @@ -3038,7 +3041,7 @@ static int multiSelect( p->pLimit = pLimit; p->iLimit = 0; p->iOffset = 0; - + /* Convert the data in the temporary table into whatever form ** it is that we currently need. */ @@ -3067,7 +3070,7 @@ static int multiSelect( int addr; SelectDest intersectdest; int r1; - + /* INTERSECT is different from the others since it requires ** two temporary tables. Hence it has its own case. Begin ** by allocating the tables we will need. @@ -3075,13 +3078,13 @@ static int multiSelect( tab1 = pParse->nTab++; tab2 = pParse->nTab++; assert( p->pOrderBy==0 ); - + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0); assert( p->addrOpenEphm[0] == -1 ); p->addrOpenEphm[0] = addr; findRightmost(p)->selFlags |= SF_UsesEphemeral; assert( p->pEList ); - + /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); @@ -3090,7 +3093,7 @@ static int multiSelect( if( rc ){ goto multi_select_end; } - + /* Code the current SELECT into temporary table "tab2" */ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); @@ -3112,7 +3115,7 @@ static int multiSelect( } sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; - + /* Generate code to take the intersection of the two temporary ** tables. */ @@ -3137,7 +3140,7 @@ static int multiSelect( break; } } - + #ifndef SQLITE_OMIT_EXPLAIN if( p->pNext==0 ){ ExplainQueryPlanPop(pParse); @@ -3145,8 +3148,8 @@ static int multiSelect( #endif } if( pParse->nErr ) goto multi_select_end; - - /* Compute collating sequences used by + + /* Compute collating sequences used by ** temporary tables needed to implement the compound select. ** Attach the KeyInfo structure to all temporary tables. ** @@ -3223,7 +3226,7 @@ void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ /* ** Code an output subroutine for a coroutine implementation of a -** SELECT statment. +** SELECT statement. ** ** The data to be output is contained in pIn->iSdst. There are ** pIn->nSdst columns to be output. pDest is where the output should @@ -3258,7 +3261,7 @@ static int generateOutputSubroutine( addr = sqlite3VdbeCurrentAddr(v); iContinue = sqlite3VdbeMakeLabel(pParse); - /* Suppress duplicates for UNION, EXCEPT, and INTERSECT + /* Suppress duplicates for UNION, EXCEPT, and INTERSECT */ if( regPrev ){ int addr1, addr2; @@ -3300,7 +3303,7 @@ static int generateOutputSubroutine( int r1; testcase( pIn->nSdst>1 ); r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, + sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1, pDest->zAffSdst, pIn->nSdst); sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pDest->iSDParm, r1, pIn->iSdst, pIn->nSdst); @@ -3338,7 +3341,7 @@ static int generateOutputSubroutine( ** SRT_Output. This routine is never called with any other ** destination other than the ones handled above or SRT_Output. ** - ** For SRT_Output, results are stored in a sequence of registers. + ** For SRT_Output, results are stored in a sequence of registers. ** Then the OP_ResultRow opcode is used to cause sqlite3_step() to ** return the next row of result. */ @@ -3395,7 +3398,7 @@ static int generateOutputSubroutine( ** ** EofB: Called when data is exhausted from selectB. ** -** The implementation of the latter five subroutines depend on which +** The implementation of the latter five subroutines depend on which ** is used: ** ** @@ -3445,7 +3448,7 @@ static int generateOutputSubroutine( ** ** We call AltB, AeqB, AgtB, EofA, and EofB "subroutines" but they are not ** actually called using Gosub and they do not Return. EofA and EofB loop -** until all data is exhausted then jump to the "end" labe. AltB, AeqB, +** until all data is exhausted then jump to the "end" label. AltB, AeqB, ** and AgtB jump to either L2 or to one of EofA or EofB. */ #ifndef SQLITE_OMIT_COMPOUND_SELECT @@ -3482,7 +3485,7 @@ static int multiSelectOrderBy( int savedOffset; /* Saved value of p->iOffset */ int labelCmpr; /* Label for the start of the merge algorithm */ int labelEnd; /* Label for the end of the overall SELECT stmt */ - int addr1; /* Jump instructions that get retargetted */ + int addr1; /* Jump instructions that get retargeted */ int op; /* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */ KeyInfo *pKeyDup = 0; /* Comparison information for duplicate removal */ KeyInfo *pKeyMerge; /* Comparison information for merging rows */ @@ -3502,7 +3505,7 @@ static int multiSelectOrderBy( /* Patch up the ORDER BY clause */ - op = p->op; + op = p->op; assert( p->pPrior->pOrderBy==0 ); pOrderBy = p->pOrderBy; assert( pOrderBy ); @@ -3574,7 +3577,7 @@ static int multiSelectOrderBy( } } } - + /* Separate the left and the right query from one another */ nSelect = 1; @@ -3637,7 +3640,7 @@ static int multiSelectOrderBy( sqlite3VdbeEndCoroutine(v, regAddrA); sqlite3VdbeJumpHere(v, addr1); - /* Generate a coroutine to evaluate the SELECT statement on + /* Generate a coroutine to evaluate the SELECT statement on ** the right - the "B" select */ addrSelectB = sqlite3VdbeCurrentAddr(v) + 1; @@ -3646,7 +3649,7 @@ static int multiSelectOrderBy( savedLimit = p->iLimit; savedOffset = p->iOffset; p->iLimit = regLimitB; - p->iOffset = 0; + p->iOffset = 0; ExplainQueryPlan((pParse, 1, "RIGHT")); sqlite3Select(pParse, p, &destB); p->iLimit = savedLimit; @@ -3660,7 +3663,7 @@ static int multiSelectOrderBy( addrOutA = generateOutputSubroutine(pParse, p, &destA, pDest, regOutA, regPrev, pKeyDup, labelEnd); - + /* Generate a subroutine that outputs the current row of the B ** select as the next output row of the compound select. */ @@ -3677,7 +3680,7 @@ static int multiSelectOrderBy( */ if( op==TK_EXCEPT || op==TK_INTERSECT ){ addrEofA_noB = addrEofA = labelEnd; - }else{ + }else{ VdbeNoopComment((v, "eof-A subroutine")); addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); @@ -3692,7 +3695,7 @@ static int multiSelectOrderBy( if( op==TK_INTERSECT ){ addrEofB = addrEofA; if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; - }else{ + }else{ VdbeNoopComment((v, "eof-B subroutine")); addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v); @@ -3752,7 +3755,7 @@ static int multiSelectOrderBy( /* Make arrangements to free the 2nd and subsequent arms of the compound ** after the parse has finished */ if( pSplit->pPrior ){ - sqlite3ParserAddCleanup(pParse, + sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior); } pSplit->pPrior = pPrior; @@ -3819,13 +3822,13 @@ static void substSelect(SubstContext*, Select*, int); /* ** Scan through the expression pExpr. Replace every reference to ** a column in table number iTable with a copy of the iColumn-th -** entry in pEList. (But leave references to the ROWID column +** entry in pEList. (But leave references to the ROWID column ** unchanged.) ** ** This routine is part of the flattening procedure. A subquery ** whose result set is defined by pEList appears as entry in the ** FROM clause of a SELECT such that the VDBE cursor assigned to that -** FORM clause entry is iTable. This routine makes the necessary +** FORM clause entry is iTable. This routine makes the necessary ** changes to pExpr so that it refers directly to the source table ** of the subquery rather the result set of the subquery. */ @@ -3851,11 +3854,14 @@ static Expr *substExpr( #endif { Expr *pNew; - int iColumn = pExpr->iColumn; - Expr *pCopy = pSubst->pEList->a[iColumn].pExpr; + int iColumn; + Expr *pCopy; Expr ifNullRow; + iColumn = pExpr->iColumn; + assert( iColumn>=0 ); assert( pSubst->pEList!=0 && iColumnpEList->nExpr ); assert( pExpr->pRight==0 ); + pCopy = pSubst->pEList->a[iColumn].pExpr; if( sqlite3ExprIsVector(pCopy) ){ sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ @@ -4002,7 +4008,7 @@ static void recomputeColumnsUsed( #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** Assign new cursor numbers to each of the items in pSrc. For each -** new cursor number assigned, set an entry in the aCsrMap[] array +** new cursor number assigned, set an entry in the aCsrMap[] array ** to map the old cursor number to the new: ** ** aCsrMap[iOld+1] = iNew; @@ -4064,10 +4070,10 @@ static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ /* ** Assign a new cursor number to each cursor in the FROM clause (Select.pSrc) -** of the SELECT statement passed as the second argument, and to each +** of the SELECT statement passed as the second argument, and to each ** cursor in the FROM clause of any FROM clause sub-selects, recursively. ** Except, do not assign a new cursor number to the iExcept'th element in -** the FROM clause of (*p). Update all expressions and other references +** the FROM clause of (*p). Update all expressions and other references ** to refer to the new cursor numbers. ** ** Argument aCsrMap is an array that may be used for temporary working @@ -4076,8 +4082,8 @@ static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ ** * the array is larger than the largest cursor number used within the ** select statement passed as an argument, and ** -** * the array entries for all cursor numbers that do *not* appear in -** FROM clauses of the select statement as described above are +** * the array entries for all cursor numbers that do *not* appear in +** FROM clauses of the select statement as described above are ** initialized to zero. */ static void renumberCursors( @@ -4159,7 +4165,7 @@ static int compoundHasDifferentAffinities(Select *p){ ** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5 ** ** The code generated for this simplification gives the same result -** but only has to scan the data once. And because indices might +** but only has to scan the data once. And because indices might ** exist on the table t1, a complete scan of the data might be ** avoided. ** @@ -4188,7 +4194,7 @@ static int compoundHasDifferentAffinities(Select *p){ ** (4) The subquery can not be DISTINCT. ** ** (**) At one point restrictions (4) and (5) defined a subset of DISTINCT -** sub-queries that were excluded from this optimization. Restriction +** sub-queries that were excluded from this optimization. Restriction ** (4) has since been expanded to exclude all DISTINCT subqueries. ** ** (**) We no longer attempt to flatten aggregate subqueries. Was: @@ -4204,8 +4210,8 @@ static int compoundHasDifferentAffinities(Select *p){ ** (9) If the subquery uses LIMIT then the outer query may not be aggregate. ** ** (**) Restriction (10) was removed from the code on 2005-02-05 but we -** accidently carried the comment forward until 2014-09-15. Original -** constraint: "If the subquery is aggregate then the outer query +** accidentally carried the comment forward until 2014-09-15. Original +** constraint: "If the subquery is aggregate then the outer query ** may not use LIMIT." ** ** (11) The subquery and the outer query may not both have ORDER BY clauses. @@ -4223,7 +4229,7 @@ static int compoundHasDifferentAffinities(Select *p){ ** ** (16) If the outer query is aggregate, then the subquery may not ** use ORDER BY. (Ticket #2942) This used to not matter -** until we introduced the group_concat() function. +** until we introduced the group_concat() function. ** ** (17) If the subquery is a compound select, then ** (17a) all compound operators must be a UNION ALL, and @@ -4278,7 +4284,7 @@ static int compoundHasDifferentAffinities(Select *p){ ** recursive queries in multiSelect(). ** ** (**) We no longer attempt to flatten aggregate subqueries. Was: -** The subquery may not be an aggregate that uses the built-in min() or +** The subquery may not be an aggregate that uses the built-in min() or ** or max() functions. (Without this restriction, a query like: ** "SELECT x FROM (SELECT max(y), x FROM t1)" would not necessarily ** return the value X for which Y was maximal.) @@ -4296,7 +4302,8 @@ static int compoundHasDifferentAffinities(Select *p){ ** (27b) the subquery is a compound query and the RIGHT JOIN occurs ** in any arm of the compound query. (See also (17g).) ** -** (28) The subquery is not a MATERIALIZED CTE. +** (28) The subquery is not a MATERIALIZED CTE. (This is handled +** in the caller before ever reaching this routine.) ** ** ** In this routine, the "p" parameter is a pointer to the outer query. @@ -4323,7 +4330,7 @@ static int flattenSubquery( SrcList *pSubSrc; /* The FROM clause of the subquery */ int iParent; /* VDBE cursor number of the pSub result set temp table */ int iNewParent = -1;/* Replacement table for iParent */ - int isOuterJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ + int isOuterJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ SrcItem *pSubitem; /* The subquery */ @@ -4406,9 +4413,9 @@ static int flattenSubquery( if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ return 0; /* Restriction (27a) */ } - if( pSubitem->fg.isCte && pSubitem->u2.pCteUse->eM10d==M10d_Yes ){ - return 0; /* (28) */ - } + + /* Condition (28) is blocked by the caller */ + assert( !pSubitem->fg.isCte || pSubitem->u2.pCteUse->eM10d!=M10d_Yes ); /* Restriction (17): If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries @@ -4478,7 +4485,7 @@ static int flattenSubquery( testcase( i==SQLITE_DENY ); pParse->zAuthContext = zSavedAuthContext; - /* Delete the transient structures associated with thesubquery */ + /* Delete the transient structures associated with the subquery */ pSub1 = pSubitem->pSelect; sqlite3DbFree(db, pSubitem->zDatabase); sqlite3DbFree(db, pSubitem->zName); @@ -4490,13 +4497,13 @@ static int flattenSubquery( assert( pSubitem->fg.isUsing!=0 || pSubitem->u3.pOn==0 ); /* If the sub-query is a compound SELECT statement, then (by restrictions - ** 17 and 18 above) it must be a UNION ALL and the parent query must + ** 17 and 18 above) it must be a UNION ALL and the parent query must ** be of the form: ** - ** SELECT FROM () + ** SELECT FROM () ** ** followed by any ORDER BY, LIMIT and/or OFFSET clauses. This block - ** creates N-1 copies of the parent query without any ORDER BY, LIMIT or + ** creates N-1 copies of the parent query without any ORDER BY, LIMIT or ** OFFSET clauses and joins them to the left-hand-side of the original ** using UNION ALL operators. In this case N is the number of simple ** select statements in the compound sub-query. @@ -4570,7 +4577,7 @@ static int flattenSubquery( Table *pTabToDel = pSubitem->pTab; if( pTabToDel->nTabRef==1 ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - sqlite3ParserAddCleanup(pToplevel, + sqlite3ParserAddCleanup(pToplevel, (void(*)(sqlite3*,void*))sqlite3DeleteTable, pTabToDel); testcase( pToplevel->earlyCleanup ); @@ -4606,7 +4613,7 @@ static int flattenSubquery( if( pParent==p ){ jointype = pSubitem->fg.jointype; /* First time through the loop */ } - + /* The subquery uses a single slot of the FROM clause of the outer ** query. If the subquery has more than one element in its FROM clause, ** then expand the outer query to make space for it to hold all elements @@ -4642,10 +4649,10 @@ static int flattenSubquery( } pSrc->a[iFrom].fg.jointype &= JT_LTORJ; pSrc->a[iFrom].fg.jointype |= jointype | ltorj; - - /* Now begin substituting subquery result set expressions for + + /* Now begin substituting subquery result set expressions for ** references to the iParent in the outer query. - ** + ** ** Example: ** ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b; @@ -4660,7 +4667,7 @@ static int flattenSubquery( ** ORDER BY column expression is identical to the iOrderByCol'th ** expression returned by SELECT statement pSub. Since these values ** do not necessarily correspond to columns in SELECT statement pParent, - ** zero them before transfering the ORDER BY clause. + ** zero them before transferring the ORDER BY clause. ** ** Not doing this may cause an error if a subsequent call to this ** function attempts to flatten a compound sub-query into pParent @@ -4696,12 +4703,12 @@ static int flattenSubquery( x.pCList = findLeftmostExprlist(pSub); substSelect(&x, pParent, 0); } - + /* The flattened query is a compound if either the inner or the ** outer query is a compound. */ pParent->selFlags |= pSub->selFlags & SF_Compound; assert( (pSub->selFlags & SF_Distinct)==0 ); /* restriction (17b) */ - + /* ** SELECT ... FROM (SELECT ... LIMIT a OFFSET b) LIMIT x OFFSET y; ** @@ -4720,8 +4727,7 @@ static int flattenSubquery( } } - /* Finially, delete what is left of the subquery and return - ** success. + /* Finally, delete what is left of the subquery and return success. */ sqlite3AggInfoPersistWalkerInit(&w, pParse); sqlite3WalkSelect(&w,pSub1); @@ -4756,7 +4762,7 @@ struct WhereConst { /* ** Add a new entry to the pConst object. Except, do not add duplicate -** pColumn entires. Also, do not add if doing so would not be appropriate. +** pColumn entries. Also, do not add if doing so would not be appropriate. ** ** The caller guarantees the pColumn is a column and pValue is a constant. ** This routine has to do some additional checks before completing the @@ -4839,15 +4845,15 @@ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ /* ** This is a helper function for Walker callback propagateConstantExprRewrite(). ** -** Argument pExpr is a candidate expression to be replaced by a value. If -** pExpr is equivalent to one of the columns named in pWalker->u.pConst, +** Argument pExpr is a candidate expression to be replaced by a value. If +** pExpr is equivalent to one of the columns named in pWalker->u.pConst, ** then overwrite it with the corresponding value. Except, do not do so ** if argument bIgnoreAffBlob is non-zero and the affinity of pExpr ** is SQLITE_AFF_BLOB. */ static int propagateConstantExprRewriteOne( WhereConst *pConst, - Expr *pExpr, + Expr *pExpr, int bIgnoreAffBlob ){ int i; @@ -4892,7 +4898,7 @@ static int propagateConstantExprRewriteOne( ** ** + pExpr is a binary comparison operator (=, <=, >=, <, >) that ** uses an affinity other than TEXT and one of its immediate -** children is a column that matches one of the columns in +** children is a column that matches one of the columns in ** pWalker->u.pConst. */ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ @@ -4942,7 +4948,7 @@ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ ** SELECT * FROM t1 WHERE a=123 AND b=123; ** ** The two SELECT statements above should return different answers. b=a -** is alway true because the comparison uses numeric affinity, but b=123 +** is always true because the comparison uses numeric affinity, but b=123 ** is false because it uses text affinity and '0123' is not the same as '123'. ** To work around this, the expression tree is not actually changed from ** "b=a" to "b=123" but rather the "a" in "b=a" is tagged with EP_FixedCol @@ -5004,7 +5010,7 @@ static int propagateConstants( sqlite3DbFree(x.pParse->db, x.apExpr); nChng += x.nChng; } - }while( x.nChng ); + }while( x.nChng ); return nChng; } @@ -5014,19 +5020,19 @@ static int propagateConstants( ** This function is called to determine whether or not it is safe to ** push WHERE clause expression pExpr down to FROM clause sub-query ** pSubq, which contains at least one window function. Return 1 -** if it is safe and the expression should be pushed down, or 0 +** if it is safe and the expression should be pushed down, or 0 ** otherwise. ** -** It is only safe to push the expression down if it consists only +** It is only safe to push the expression down if it consists only ** of constants and copies of expressions that appear in the PARTITION ** BY clause of all window function used by the sub-query. It is safe ** to filter out entire partitions, but not rows within partitions, as ** this may change the results of the window functions. ** -** At the time this function is called it is guaranteed that +** At the time this function is called it is guaranteed that ** -** * the sub-query uses only one distinct window frame, and -** * that the window frame has a PARTITION BY clase. +** * the sub-query uses only one distinct window frame, and +** * that the window frame has a PARTITION BY clause. */ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ assert( pSubq->pWin->pPartition ); @@ -5084,19 +5090,19 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** But if the (b2=2) term were to be pushed down into the bb subquery, ** then the (1,1,NULL) row would be suppressed. ** -** (6) Window functions make things tricky as changes to the WHERE clause -** of the inner query could change the window over which window +** (6) Window functions make things tricky as changes to the WHERE clause +** of the inner query could change the window over which window ** functions are calculated. Therefore, do not attempt the optimization ** if: ** ** (6a) The inner query uses multiple incompatible window partitions. ** -** (6b) The inner query is a compound and uses window-functions. +** (6b) The inner query is a compound and uses window-functions. ** ** (6c) The WHERE clause does not consist entirely of constants and ** copies of expressions found in the PARTITION BY clause of ** all window-functions used by the sub-query. It is safe to -** filter out entire partitions, as this does not change the +** filter out entire partitions, as this does not change the ** window over which any window-function is calculated. ** ** (7) The inner query is a Common Table Expression (CTE) that should @@ -5153,7 +5159,7 @@ static int pushDownWhereTerms( int notUnionAll = 0; for(pSel=pSubq; pSel; pSel=pSel->pPrior){ u8 op = pSel->op; - assert( op==TK_ALL || op==TK_SELECT + assert( op==TK_ALL || op==TK_SELECT || op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT ); if( op!=TK_ALL && op!=TK_SELECT ){ notUnionAll = 1; @@ -5190,7 +5196,7 @@ static int pushDownWhereTerms( ** in the future. */ { - Select *pX; + Select *pX; for(pX=pSubq; pX; pX=pX->pPrior){ assert( (pX->selFlags & (SF_Recursive))==0 ); } @@ -5230,7 +5236,7 @@ static int pushDownWhereTerms( return 0; /* restriction (4) */ } if( ExprHasProperty(pWhere,EP_OuterON) - && pWhere->w.iJoin!=iCursor + && pWhere->w.iJoin!=iCursor ){ return 0; /* restriction (5) */ } @@ -5344,7 +5350,7 @@ static int disableUnusedSubqueryResultColumns(SrcItem *pItem){ /* ** The pFunc is the only aggregate function in the query. Check to see -** if the query is a candidate for the min/max optimization. +** if the query is a candidate for the min/max optimization. ** ** If the query is a candidate for the min/max optimization, then set ** *ppMinMax to be an ORDER BY clause to be used for the optimization @@ -5370,7 +5376,7 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ assert( !IsWindowFunc(pFunc) ); assert( ExprUseXList(pFunc) ); pEList = pFunc->x.pList; - if( pEList==0 + if( pEList==0 || pEList->nExpr!=1 || ExprHasProperty(pFunc, EP_WinFunc) || OptimizationDisabled(db, SQLITE_MinMaxOpt) @@ -5398,7 +5404,7 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ /* ** The select statement passed as the first argument is an aggregate query. -** The second argument is the associated aggregate-info object. This +** The second argument is the associated aggregate-info object. This ** function tests if the SELECT is of the form: ** ** SELECT count(*) FROM @@ -5419,8 +5425,8 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ assert( !p->pGroupBy ); - if( p->pWhere - || p->pEList->nExpr!=1 + if( p->pWhere + || p->pEList->nExpr!=1 || p->pSrc->nSrc!=1 || p->pSrc->a[0].pSelect || pAggInfo->nFunc!=1 @@ -5448,8 +5454,8 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ /* ** If the source-list item passed as an argument was augmented with an ** INDEXED BY clause, then try to locate the specified index. If there -** was such a clause and the named index cannot be found, return -** SQLITE_ERROR and leave an error in pParse. Otherwise, populate +** was such a clause and the named index cannot be found, return +** SQLITE_ERROR and leave an error in pParse. Otherwise, populate ** pFrom->pIndex and return SQLITE_OK. */ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ @@ -5459,8 +5465,8 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ assert( pTab!=0 ); assert( pFrom->fg.isIndexedBy!=0 ); - for(pIdx=pTab->pIndex; - pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy); + for(pIdx=pTab->pIndex; + pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy); pIdx=pIdx->pNext ); if( !pIdx ){ @@ -5474,7 +5480,7 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ } /* -** Detect compound SELECT statements that use an ORDER BY clause with +** Detect compound SELECT statements that use an ORDER BY clause with ** an alternative collating sequence. ** ** SELECT ... FROM t1 EXCEPT SELECT ... FROM t2 ORDER BY .. COLLATE ... @@ -5569,9 +5575,9 @@ static int cannotBeFunction(Parse *pParse, SrcItem *pFrom){ #ifndef SQLITE_OMIT_CTE /* -** Argument pWith (which may be NULL) points to a linked list of nested -** WITH contexts, from inner to outermost. If the table identified by -** FROM clause element pItem is really a common-table-expression (CTE) +** Argument pWith (which may be NULL) points to a linked list of nested +** WITH contexts, from inner to outermost. If the table identified by +** FROM clause element pItem is really a common-table-expression (CTE) ** then return a pointer to the CTE definition for that table. Otherwise ** return NULL. ** @@ -5607,7 +5613,7 @@ static struct Cte *searchWith( ** onto the top of the stack. If argument bFree is true, then this ** WITH clause will never be popped from the stack but should instead ** be freed along with the Parse object. In other cases, when -** bFree==0, the With object will be freed along with the SELECT +** bFree==0, the With object will be freed along with the SELECT ** statement with which it is associated. ** ** This routine returns a copy of pWith. Or, if bFree is true and @@ -5620,7 +5626,7 @@ static struct Cte *searchWith( With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ if( pWith ){ if( bFree ){ - pWith = (With*)sqlite3ParserAddCleanup(pParse, + pWith = (With*)sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3WithDelete, pWith); if( pWith==0 ) return 0; @@ -5635,7 +5641,7 @@ With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ } /* -** This function checks if argument pFrom refers to a CTE declared by +** This function checks if argument pFrom refers to a CTE declared by ** a WITH clause on the stack currently maintained by the parser (on the ** pParse->pWith linked list). And if currently processing a CTE ** CTE expression, through routine checks to see if the reference is @@ -5644,7 +5650,7 @@ With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ ** If pFrom matches a CTE according to either of these two above, pFrom->pTab ** and other fields are populated accordingly. ** -** Return 0 if no match is found. +** Return 0 if no match is found. ** Return 1 if a match is found. ** Return 2 if an error condition is detected. */ @@ -5743,8 +5749,8 @@ static int resolveFromTermToCte( assert( pRecTerm->pPrior!=0 ); for(i=0; inSrc; i++){ SrcItem *pItem = &pSrc->a[i]; - if( pItem->zDatabase==0 - && pItem->zName!=0 + if( pItem->zDatabase==0 + && pItem->zName!=0 && 0==sqlite3StrICmp(pItem->zName, pCte->zName) ){ pItem->pTab = pTab; @@ -5822,12 +5828,12 @@ static int resolveFromTermToCte( #ifndef SQLITE_OMIT_CTE /* -** If the SELECT passed as the second argument has an associated WITH +** If the SELECT passed as the second argument has an associated WITH ** clause, pop it from the stack stored as part of the Parse object. ** ** This function is used as the xSelectCallback2() callback by ** sqlite3SelectExpand() when walking a SELECT tree to resolve table -** names and other FROM clause elements. +** names and other FROM clause elements. */ void sqlite3SelectPopWith(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; @@ -5907,7 +5913,7 @@ static int inAnyUsingClause( ** (1) Make sure VDBE cursor numbers have been assigned to every ** element of the FROM clause. ** -** (2) Fill in the pTabList->a[].pTab fields in the SrcList that +** (2) Fill in the pTabList->a[].pTab fields in the SrcList that ** defines FROM clause. When views appear in the FROM clause, ** fill pTabList->a[].pSelect with a copy of the SELECT statement ** that implements the view. A copy is made of the view's SELECT @@ -6106,10 +6112,16 @@ static int selectExpander(Walker *pWalker, Select *p){ ** expanded. */ int tableSeen = 0; /* Set to 1 when TABLE matches */ char *zTName = 0; /* text of name of TABLE */ + int iErrOfst; if( pE->op==TK_DOT ){ assert( pE->pLeft!=0 ); assert( !ExprHasProperty(pE->pLeft, EP_IntValue) ); zTName = pE->pLeft->u.zToken; + assert( ExprUseWOfst(pE->pLeft) ); + iErrOfst = pE->pRight->w.iOfst; + }else{ + assert( ExprUseWOfst(pE) ); + iErrOfst = pE->w.iOfst; } for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; /* Table for this data source */ @@ -6146,6 +6158,7 @@ static int selectExpander(Walker *pWalker, Select *p){ for(ii=0; iinId; ii++){ const char *zUName = pUsing->a[ii].zName; pRight = sqlite3Expr(db, TK_ID, zUName); + sqlite3ExprSetErrorOffset(pRight, iErrOfst); pNew = sqlite3ExprListAppend(pParse, pNew, pRight); if( pNew ){ struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; @@ -6175,7 +6188,7 @@ static int selectExpander(Walker *pWalker, Select *p){ ** bit set. */ if( (p->selFlags & SF_IncludeHidden)==0 - && IsHiddenColumn(&pTab->aCol[j]) + && IsHiddenColumn(&pTab->aCol[j]) ){ continue; } @@ -6218,6 +6231,7 @@ static int selectExpander(Walker *pWalker, Select *p){ }else{ pExpr = pRight; } + sqlite3ExprSetErrorOffset(pExpr, iErrOfst); pNew = sqlite3ExprListAppend(pParse, pNew, pExpr); if( pNew==0 ){ break; /* OOM */ @@ -6417,9 +6431,9 @@ static void printAggInfo(AggInfo *pAggInfo){ sqlite3DebugPrintf( "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d" " iSorterColumn=%d %s\n", - ii, pCol->pTab ? pCol->pTab->zName : "NULL", + ii, pCol->pTab ? pCol->pTab->zName : "NULL", pCol->iTable, pCol->iColumn, pAggInfo->iFirstReg+ii, - pCol->iSorterColumn, + pCol->iSorterColumn, ii>=pAggInfo->nAccumulator ? "" : " Accumulator"); sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0); } @@ -6534,7 +6548,7 @@ static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){ pExpr->op = TK_AGG_COLUMN; pExpr->iTable = pCol->iTable; pExpr->iColumn = pCol->iColumn; - ExprClearProperty(pExpr, EP_Skip|EP_Collate); + ExprClearProperty(pExpr, EP_Skip|EP_Collate|EP_Unlikely); return WRC_Prune; } @@ -6565,7 +6579,7 @@ static void aggregateConvertIndexedExprRefToColumn(AggInfo *pAggInfo){ ** * The aCol[] and aFunc[] arrays may be modified ** * The AggInfoColumnReg() and AggInfoFuncReg() macros may not be used ** -** After clling this routine: +** After calling this routine: ** ** * The aCol[] and aFunc[] arrays are fixed ** * The AggInfoColumnReg() and AggInfoFuncReg() macros may be used @@ -6608,7 +6622,7 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ pFunc->iDistinct = -1; }else{ KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pE->x.pList,0,0); - pFunc->iDistAddr = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + pFunc->iDistAddr = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO); ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(DISTINCT)", pFunc->pFunc->zName)); @@ -6646,8 +6660,8 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ ** of setting and clearing regAcc. */ static void updateAccumulator( - Parse *pParse, - int regAcc, + Parse *pParse, + int regAcc, AggInfo *pAggInfo, int eDistinctType ){ @@ -6671,8 +6685,8 @@ static void updateAccumulator( pList = pF->pFExpr->x.pList; if( ExprHasProperty(pF->pFExpr, EP_WinFunc) ){ Expr *pFilter = pF->pFExpr->y.pWin->pFilter; - if( pAggInfo->nAccumulator - && (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + if( pAggInfo->nAccumulator + && (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) && regAcc ){ /* If regAcc==0, there there exists some min() or max() function @@ -6681,7 +6695,7 @@ static void updateAccumulator( if( regHit==0 ) regHit = ++pParse->nMem; /* If this is the first row of the group (regAcc contains 0), clear the ** "magnet" register regHit so that the accumulator registers - ** are populated if the FILTER clause jumps over the the + ** are populated if the FILTER clause jumps over the the ** invocation of min() or max() altogether. Or, if this is not ** the first row (regAcc contains 1), set the magnet register so that ** the accumulators are not populated unless the min()/max() is invoked @@ -6700,10 +6714,10 @@ static void updateAccumulator( regAgg = 0; } if( pF->iDistinct>=0 && pList ){ - if( addrNext==0 ){ + if( addrNext==0 ){ addrNext = sqlite3VdbeMakeLabel(pParse); } - pF->iDistinct = codeDistinct(pParse, eDistinctType, + pF->iDistinct = codeDistinct(pParse, eDistinctType, pF->iDistinct, addrNext, pList, regAgg); } if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ @@ -6770,10 +6784,10 @@ static void explainSimpleCount( /* ** sqlite3WalkExpr() callback used by havingToWhere(). ** -** If the node passed to the callback is a TK_AND node, return +** If the node passed to the callback is a TK_AND node, return ** WRC_Continue to tell sqlite3WalkExpr() to iterate through child nodes. ** -** Otherwise, return WRC_Prune. In this case, also check if the +** Otherwise, return WRC_Prune. In this case, also check if the ** sub-expression matches the criteria for being moved to the WHERE ** clause. If so, add it to the WHERE clause and replace the sub-expression ** within the HAVING expression with a constant "1". @@ -6788,7 +6802,7 @@ static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ ** belongs to an outer query. Do not move the expression to the WHERE ** clause in this obscure case, as doing so may corrupt the outer Select ** statements AggInfo structure. */ - if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) + if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) && ExprAlwaysFalse(pExpr)==0 && pExpr->pAggInfo==0 ){ @@ -7066,7 +7080,7 @@ static int fromClauseTermCanBeCoroutine( } /* -** Generate code for the SELECT statement given in the p argument. +** Generate code for the SELECT statement given in the p argument. ** ** The results are returned according to the SelectDest structure. ** See comments in sqliteInt.h for further information. @@ -7125,7 +7139,7 @@ int sqlite3Select( assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); if( IgnorableDistinct(pDest) ){ - assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || + assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ @@ -7135,7 +7149,7 @@ int sqlite3Select( if( sqlite3TreeTrace & 0x800 ){ sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); } -#endif +#endif sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3ExprListDelete, p->pOrderBy); @@ -7160,18 +7174,18 @@ int sqlite3Select( /* If the SF_UFSrcCheck flag is set, then this function is being called ** as part of populating the temp table for an UPDATE...FROM statement. - ** In this case, it is an error if the target object (pSrc->a[0]) name - ** or alias is duplicated within FROM clause (pSrc->a[1..n]). + ** In this case, it is an error if the target object (pSrc->a[0]) name + ** or alias is duplicated within FROM clause (pSrc->a[1..n]). ** - ** Postgres disallows this case too. The reason is that some other - ** systems handle this case differently, and not all the same way, + ** Postgres disallows this case too. The reason is that some other + ** systems handle this case differently, and not all the same way, ** which is just confusing. To avoid this, we follow PG's lead and ** disallow it altogether. */ if( p->selFlags & SF_UFSrcCheck ){ SrcItem *p0 = &p->pSrc->a[0]; if( sameSrcAlias(p0, p->pSrc) ){ - sqlite3ErrorMsg(pParse, - "target object/alias may not appear in FROM clause: %s", + sqlite3ErrorMsg(pParse, + "target object/alias may not appear in FROM clause: %s", p0->zAlias ? p0->zAlias : p0->pTab->zName ); goto select_end; @@ -7219,22 +7233,59 @@ int sqlite3Select( ** to a real table */ assert( pTab!=0 ); - /* Convert LEFT JOIN into JOIN if there are terms of the right table - ** of the LEFT JOIN used in the WHERE clause. + /* Try to simplify joins: + ** + ** LEFT JOIN -> JOIN + ** RIGHT JOIN -> JOIN + ** FULL JOIN -> RIGHT JOIN + ** + ** If terms of the i-th table are used in the WHERE clause in such a + ** way that the i-th table cannot be the NULL row of a join, then + ** perform the appropriate simplification. This is called + ** "OUTER JOIN strength reduction" in the SQLite documentation. */ - if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==JT_LEFT - && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) + if( (pItem->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 + && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor, + pItem->fg.jointype & JT_LTORJ) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ - TREETRACE(0x1000,pParse,p, - ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); - pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + if( pItem->fg.jointype & JT_LEFT ){ + if( pItem->fg.jointype & JT_RIGHT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to RIGHT-JOIN on term %d\n",i)); + pItem->fg.jointype &= ~JT_LEFT; + }else{ + TREETRACE(0x1000,pParse,p, + ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); + pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + } + } + if( pItem->fg.jointype & JT_LTORJ ){ + for(j=i+1; jnSrc; j++){ + SrcItem *pI2 = &pTabList->a[j]; + if( pI2->fg.jointype & JT_RIGHT ){ + if( pI2->fg.jointype & JT_LEFT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to LEFT-JOIN on term %d\n",j)); + pI2->fg.jointype &= ~JT_RIGHT; + }else{ + TREETRACE(0x1000,pParse,p, + ("RIGHT-JOIN simplifies to JOIN on term %d\n",j)); + pI2->fg.jointype &= ~(JT_RIGHT|JT_OUTER); + } + } + } + for(j=pTabList->nSrc-1; j>=i; j--){ + pTabList->a[j].fg.jointype &= ~JT_LTORJ; + if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; + } + } assert( pItem->iCursor>=0 ); unsetJoinExpr(p->pWhere, pItem->iCursor, pTabList->a[0].fg.jointype & JT_LTORJ); } - /* No futher action if this term of the FROM clause is not a subquery */ + /* No further action if this term of the FROM clause is not a subquery */ if( pSub==0 ) continue; /* Catch mismatch in the declared columns of a view and the number of @@ -7245,6 +7296,14 @@ int sqlite3Select( goto select_end; } + /* Do not attempt the usual optimizations (flattening and ORDER BY + ** elimination) on a MATERIALIZED common table expression because + ** a MATERIALIZED common table expression is an optimization fence. + */ + if( pItem->fg.isCte && pItem->u2.pCteUse->eM10d==M10d_Yes ){ + continue; + } + /* Do not try to flatten an aggregate subquery. ** ** Flattening an aggregate subquery is only possible if the outer query @@ -7274,6 +7333,8 @@ int sqlite3Select( ** (a) The outer query has a different ORDER BY clause ** (b) The subquery is part of a join ** See forum post 062d576715d277c8 + ** + ** Also retain the ORDER BY if the OmitOrderBy optimization is disabled. */ if( pSub->pOrderBy!=0 && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ @@ -7284,7 +7345,7 @@ int sqlite3Select( ){ TREETRACE(0x800,pParse,p, ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); - sqlite3ParserAddCleanup(pParse, + sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3ExprListDelete, pSub->pOrderBy); pSub->pOrderBy = 0; @@ -7429,7 +7490,7 @@ int sqlite3Select( ** inside the subquery. This can help the subquery to run more efficiently. */ if( OptimizationEnabled(db, SQLITE_PushDown) - && (pItem->fg.isCte==0 + && (pItem->fg.isCte==0 || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) && pushDownWhereTerms(pParse, pSub, p->pWhere, pTabList, i) ){ @@ -7471,7 +7532,7 @@ int sqlite3Select( ** set on each invocation. */ int addrTop = sqlite3VdbeCurrentAddr(v)+1; - + pItem->regReturn = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop); VdbeComment((v, "%!S", pItem)); @@ -7488,7 +7549,7 @@ int sqlite3Select( }else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){ /* This is a CTE for which materialization code has already been ** generated. Invoke the subroutine to compute the materialization, - ** the make the pItem->iCursor be a copy of the ephemerial table that + ** the make the pItem->iCursor be a copy of the ephemeral table that ** holds the result of the materialization. */ CteUse *pCteUse = pItem->u2.pCteUse; sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); @@ -7568,7 +7629,7 @@ int sqlite3Select( } #endif - /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and + /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and ** if the select-list is the same as the ORDER BY list, then this query ** can be rewritten as a GROUP BY. In other words, this: ** @@ -7578,12 +7639,12 @@ int sqlite3Select( ** ** SELECT xyz FROM ... GROUP BY xyz ORDER BY xyz ** - ** The second form is preferred as a single index (or temp-table) may be - ** used for both the ORDER BY and DISTINCT processing. As originally - ** written the query must use a temp-table for at least one of the ORDER + ** The second form is preferred as a single index (or temp-table) may be + ** used for both the ORDER BY and DISTINCT processing. As originally + ** written the query must use a temp-table for at least one of the ORDER ** BY and DISTINCT, and an index or separate temp-table for the other. */ - if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct + if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct && sqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0 #ifndef SQLITE_OMIT_WINDOWFUNC && p->pWin==0 @@ -7705,7 +7766,7 @@ int sqlite3Select( } TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); - /* If sorting index that was created by a prior OP_OpenEphemeral + /* If sorting index that was created by a prior OP_OpenEphemeral ** instruction ended up not being needed, then change the OP_OpenEphemeral ** into an OP_Noop. */ @@ -7778,8 +7839,8 @@ int sqlite3Select( if( p->nSelectRow>66 ) p->nSelectRow = 66; /* If there is both a GROUP BY and an ORDER BY clause and they are - ** identical, then it may be possible to disable the ORDER BY clause - ** on the grounds that the GROUP BY will cause elements to come out + ** identical, then it may be possible to disable the ORDER BY clause + ** on the grounds that the GROUP BY will cause elements to come out ** in the correct order. It also may not - the GROUP BY might use a ** database index that causes rows to be grouped together as required ** but not actually sorted. Either way, record the fact that the @@ -7789,8 +7850,8 @@ int sqlite3Select( int ii; /* The GROUP BY processing doesn't care whether rows are delivered in ** ASC or DESC order - only that each group is returned contiguously. - ** So set the ASC/DESC flags in the GROUP BY to match those in the - ** ORDER BY to maximize the chances of rows being delivered in an + ** So set the ASC/DESC flags in the GROUP BY to match those in the + ** ORDER BY to maximize the chances of rows being delivered in an ** order that makes the ORDER BY redundant. */ for(ii=0; iinExpr; ii++){ u8 sortFlags; @@ -7871,7 +7932,7 @@ int sqlite3Select( */ if( pGroupBy ){ KeyInfo *pKeyInfo; /* Keying information for the group by clause */ - int addr1; /* A-vs-B comparision jump */ + int addr1; /* A-vs-B comparison jump */ int addrOutputRow; /* Start of subroutine that outputs a result row */ int regOutputRow; /* Return address register for output subroutine */ int addrSetAbort; /* Set the abort flag and return */ @@ -7883,7 +7944,7 @@ int sqlite3Select( u16 distFlag = 0; int eDist = WHERE_DISTINCT_NOOP; - if( pAggInfo->nFunc==1 + if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 && ALWAYS(pAggInfo->aFunc[0].pFExpr!=0) && ALWAYS(ExprUseXList(pAggInfo->aFunc[0].pFExpr)) @@ -7899,13 +7960,13 @@ int sqlite3Select( /* If there is a GROUP BY clause we might need a sorting index to ** implement it. Allocate that sorting index now. If it turns out ** that we do not need it after all, the OP_SorterOpen instruction - ** will be converted into a Noop. + ** will be converted into a Noop. */ pAggInfo->sortingIdx = pParse->nTab++; pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pGroupBy, 0, pAggInfo->nColumn); - addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, - pAggInfo->sortingIdx, pAggInfo->nSortingColumn, + addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, + pAggInfo->sortingIdx, pAggInfo->nSortingColumn, 0, (char*)pKeyInfo, P4_KEYINFO); /* Initialize memory locations used by GROUP BY aggregate processing @@ -7932,7 +7993,7 @@ int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, - p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) + p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 ); if( pWInfo==0 ){ @@ -7962,9 +8023,13 @@ int sqlite3Select( int nCol; int nGroupBy; - explainTempTable(pParse, +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExp; /* Address of OP_Explain instruction */ +#endif + ExplainQueryPlan2(addrExp, (pParse, 0, "USE TEMP B-TREE FOR %s", (sDistinct.isTnct && (p->selFlags&SF_Distinct)==0) ? - "DISTINCT" : "GROUP BY"); + "DISTINCT" : "GROUP BY" + )); groupBySort = 1; nGroupBy = pGroupBy->nExpr; @@ -7989,18 +8054,23 @@ int sqlite3Select( } pAggInfo->directMode = 0; regRecord = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, 0, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord); sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); + sqlite3VdbeScanStatusRange(v, addrExp, sqlite3VdbeCurrentAddr(v)-2, -1); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3ReleaseTempRange(pParse, regBase, nCol); TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++; sortOut = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, sqlite3VdbeCurrentAddr(v), 0); sqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol); sqlite3VdbeAddOp2(v, OP_SorterSort, pAggInfo->sortingIdx, addrEnd); VdbeComment((v, "GROUP BY sort")); VdbeCoverage(v); pAggInfo->useSortingIdx = 1; + sqlite3VdbeScanStatusRange(v, addrExp, -1, sortPTab); + sqlite3VdbeScanStatusRange(v, addrExp, -1, pAggInfo->sortingIdx); } /* If there are entries in pAgggInfo->aFunc[] that contain subexpressions @@ -8025,9 +8095,9 @@ int sqlite3Select( ** clause, cancel the ephemeral table open coded earlier. ** ** This is an optimization - the correct answer should result regardless. - ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to + ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to ** disable this optimization for testing purposes. */ - if( orderByGrp && OptimizationEnabled(db, SQLITE_GroupByOrder) + if( orderByGrp && OptimizationEnabled(db, SQLITE_GroupByOrder) && (groupBySort || sqlite3WhereIsSorted(pWInfo)) ){ sSort.pOrderBy = 0; @@ -8173,7 +8243,7 @@ int sqlite3Select( ** ** (2013-10-03) Do not count the entries in a partial index. ** - ** In practice the KeyInfo structure will not be used. It is only + ** In practice the KeyInfo structure will not be used. It is only ** passed to keep OP_OpenRead happy. */ if( !HasRowid(pTab) ) pBest = sqlite3PrimaryKeyIndex(pTab); @@ -8215,7 +8285,7 @@ int sqlite3Select( ** that the accumulator registers are (a) updated only once if ** there are no min() or max functions or (b) always updated for the ** first row visited by the aggregate, so that they are updated at - ** least once even if the FILTER clause means the min() or max() + ** least once even if the FILTER clause means the min() or max() ** function visits zero rows. */ if( pAggInfo->nAccumulator ){ for(i=0; inFunc; i++){ @@ -8279,11 +8349,11 @@ int sqlite3Select( sSort.pOrderBy = 0; sqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL); - selectInnerLoop(pParse, p, -1, 0, 0, + selectInnerLoop(pParse, p, -1, 0, 0, pDest, addrEnd, addrEnd); } sqlite3VdbeResolveLabel(v, addrEnd); - + } /* endif aggregate query */ if( sDistinct.eTnctType==WHERE_DISTINCT_UNORDERED ){ diff --git a/src/shell.c.in b/src/shell.c.in index 72e449840..07d92d014 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -228,6 +228,7 @@ typedef unsigned char u8; #if SQLITE_OS_WINRT #include #endif +#undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include @@ -1206,6 +1207,46 @@ static char *shellFakeSchema( return s.z; } +/* +** SQL function: strtod(X) +** +** Use the C-library strtod() function to convert string X into a double. +** Used for comparing the accuracy of SQLite's internal text-to-float conversion +** routines against the C-library. +*/ +static void shellStrtod( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + char *z = (char*)sqlite3_value_text(apVal[0]); + UNUSED_PARAMETER(nVal); + if( z==0 ) return; + sqlite3_result_double(pCtx, strtod(z,0)); +} + +/* +** SQL function: dtostr(X) +** +** Use the C-library printf() function to convert real value X into a string. +** Used for comparing the accuracy of SQLite's internal float-to-text conversion +** routines against the C-library. +*/ +static void shellDtostr( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + double r = sqlite3_value_double(apVal[0]); + int n = nVal>=2 ? sqlite3_value_int(apVal[1]) : 26; + char z[400]; + if( n<1 ) n = 1; + if( n>350 ) n = 350; + sprintf(z, "%#+.*e", n, r); + sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); +} + + /* ** SQL function: shell_module_schema(X) ** @@ -1317,6 +1358,7 @@ INCLUDE test_windirent.c #define dirent DIRENT #endif INCLUDE ../ext/misc/memtrace.c +INCLUDE ../ext/misc/pcachetrace.c INCLUDE ../ext/misc/shathree.c INCLUDE ../ext/misc/uint.c INCLUDE ../ext/misc/decimal.c @@ -1411,7 +1453,7 @@ typedef struct ShellState ShellState; struct ShellState { sqlite3 *db; /* The database */ u8 autoExplain; /* Automatically turn on .explain mode */ - u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ + u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ u8 autoEQPtest; /* autoEQP is in test mode */ u8 autoEQPtrace; /* autoEQP is in trace mode */ u8 scanstatsOn; /* True to display scan stats before each finalize */ @@ -1423,7 +1465,7 @@ struct ShellState { u8 bSafeModePersist; /* The long-term value of bSafeMode */ ColModeOpts cmOpts; /* Option values affecting columnar mode output */ unsigned statsOn; /* True to display memory stats before each finalize */ - unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */ + unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */ int inputNesting; /* Track nesting level of .read and other redirects */ int outCount; /* Revert to stdout when reaching zero */ int cnt; /* Number of records displayed so far */ @@ -1474,7 +1516,7 @@ struct ShellState { int *aiIndent; /* Array of indents used in MODE_Explain */ int nIndent; /* Size of array aiIndent[] */ int iIndent; /* Index of current op in aiIndent[] */ - char *zNonce; /* Nonce for temporary safe-mode excapes */ + char *zNonce; /* Nonce for temporary safe-mode escapes */ EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ #ifdef SQLITE_SHELL_FIDDLE @@ -1516,7 +1558,7 @@ static ShellState shellState; /* Bits in the ShellState.flgProgress variable */ #define SHELL_PROGRESS_QUIET 0x01 /* Omit announcing every progress callback */ -#define SHELL_PROGRESS_RESET 0x02 /* Reset the count when the progres +#define SHELL_PROGRESS_RESET 0x02 /* Reset the count when the progress ** callback limit is reached, and for each ** top-level SQL statement */ #define SHELL_PROGRESS_ONCE 0x04 /* Cancel the --limit after firing once */ @@ -1565,6 +1607,7 @@ static ShellState shellState; #define MODE_Box 16 /* Unicode box-drawing characters */ #define MODE_Count 17 /* Output only a count of the rows of output */ #define MODE_Off 18 /* No query output shown */ +#define MODE_ScanExp 19 /* Like MODE_Explain, but for ".scanstats vm" */ static const char *modeDescr[] = { "line", @@ -2478,38 +2521,58 @@ static int shell_callback( } break; } + case MODE_ScanExp: case MODE_Explain: { - static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13}; - if( nArg>ArraySize(aExplainWidth) ){ - nArg = ArraySize(aExplainWidth); + static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13}; + static const int aExplainMap[] = {0, 1, 2, 3, 4, 5, 6, 7 }; + static const int aScanExpWidth[] = {4, 6, 6, 13, 4, 4, 4, 13, 2, 13}; + static const int aScanExpMap[] = {0, 9, 8, 1, 2, 3, 4, 5, 6, 7 }; + + const int *aWidth = aExplainWidth; + const int *aMap = aExplainMap; + int nWidth = ArraySize(aExplainWidth); + int iIndent = 1; + + if( p->cMode==MODE_ScanExp ){ + aWidth = aScanExpWidth; + aMap = aScanExpMap; + nWidth = ArraySize(aScanExpWidth); + iIndent = 3; } + if( nArg>nWidth ) nArg = nWidth; + + /* If this is the first row seen, print out the headers */ if( p->cnt++==0 ){ for(i=0; iout, w, azCol[i]); + utf8_width_print(p->out, aWidth[i], azCol[ aMap[i] ]); fputs(i==nArg-1 ? "\n" : " ", p->out); } for(i=0; iout, w); + print_dashes(p->out, aWidth[i]); fputs(i==nArg-1 ? "\n" : " ", p->out); } } + + /* If there is no data, exit early. */ if( azArg==0 ) break; + for(i=0; iw ){ - w = strlenChar(azArg[i]); + if( zVal && strlenChar(zVal)>w ){ + w = strlenChar(zVal); + zSep = " "; } - if( i==1 && p->aiIndent && p->pStmt ){ + if( i==iIndent && p->aiIndent && p->pStmt ){ if( p->iIndentnIndent ){ utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); } p->iIndent++; } - utf8_width_print(p->out, w, azArg[i] ? azArg[i] : p->nullValue); - fputs(i==nArg-1 ? "\n" : " ", p->out); + utf8_width_print(p->out, w, zVal ? zVal : p->nullValue); + fputs(i==nArg-1 ? "\n" : zSep, p->out); } break; } @@ -3030,7 +3093,7 @@ static int run_table_dump_query( */ static char *save_err_msg( sqlite3 *db, /* Database to query */ - const char *zPhase, /* When the error occcurs */ + const char *zPhase, /* When the error occurs */ int rc, /* Error code returned from API */ const char *zSql /* SQL string, or NULL */ ){ @@ -3290,17 +3353,11 @@ static int scanStatsHeight(sqlite3_stmt *p, int iEntry){ } #endif -/* -** Display scan stats. -*/ -static void display_scanstats( +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +static void display_explain_scanstats( sqlite3 *db, /* Database to query */ ShellState *pArg /* Pointer to ShellState */ ){ -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS - UNUSED_PARAMETER(db); - UNUSED_PARAMETER(pArg); -#else static const int f = SQLITE_SCANSTAT_COMPLEX; sqlite3_stmt *p = pArg->pStmt; int ii = 0; @@ -3372,8 +3429,9 @@ static void display_scanstats( } eqp_render(pArg, nTotal); -#endif } +#endif + /* ** Parameter azArray points to a zero-terminated array of strings. zStr @@ -3411,8 +3469,6 @@ static int str_in_array(const char *zStr, const char **azArray){ ** and "Goto" by 2 spaces. */ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ - const char *zSql; /* The text of the SQL statement */ - const char *z; /* Used to check if this is an EXPLAIN */ int *abYield = 0; /* True if op is an OP_Yield */ int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ int iOp; /* Index of operation in p->aiIndent[] */ @@ -3423,65 +3479,45 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ "Rewind", 0 }; const char *azGoto[] = { "Goto", 0 }; - /* Try to figure out if this is really an EXPLAIN statement. If this - ** cannot be verified, return early. */ - if( sqlite3_column_count(pSql)!=8 ){ - p->cMode = p->mode; - return; - } - zSql = sqlite3_sql(pSql); - if( zSql==0 ) return; - for(z=zSql; *z==' ' || *z=='\t' || *z=='\n' || *z=='\f' || *z=='\r'; z++); - if( sqlite3_strnicmp(z, "explain", 7) ){ - p->cMode = p->mode; - return; - } + /* The caller guarantees that the leftmost 4 columns of the statement + ** passed to this function are equivalent to the leftmost 4 columns + ** of EXPLAIN statement output. In practice the statement may be + ** an EXPLAIN, or it may be a query on the bytecode() virtual table. */ + assert( sqlite3_column_count(pSql)>=4 ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 0), "addr" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 1), "opcode" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 2), "p1" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 3), "p2" ) ); for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ int i; int iAddr = sqlite3_column_int(pSql, 0); const char *zOp = (const char*)sqlite3_column_text(pSql, 1); - - /* Set p2 to the P2 field of the current opcode. Then, assuming that - ** p2 is an instruction address, set variable p2op to the index of that - ** instruction in the aiIndent[] array. p2 and p2op may be different if - ** the current instruction is part of a sub-program generated by an - ** SQL trigger or foreign key. */ + int p1 = sqlite3_column_int(pSql, 2); int p2 = sqlite3_column_int(pSql, 3); + + /* Assuming that p2 is an instruction address, set variable p2op to the + ** index of that instruction in the aiIndent[] array. p2 and p2op may be + ** different if the current instruction is part of a sub-program generated + ** by an SQL trigger or foreign key. */ int p2op = (p2 + (iOp-iAddr)); /* Grow the p->aiIndent array as required */ if( iOp>=nAlloc ){ - if( iOp==0 ){ - /* Do further verfication that this is explain output. Abort if - ** it is not */ - static const char *explainCols[] = { - "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment" }; - int jj; - for(jj=0; jjcMode = p->mode; - sqlite3_reset(pSql); - return; - } - } - } nAlloc += 100; p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int)); shell_check_oom(p->aiIndent); abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); shell_check_oom(abYield); } + abYield[iOp] = str_in_array(zOp, azYield); p->aiIndent[iOp] = 0; p->nIndent = iOp+1; - if( str_in_array(zOp, azNext) && p2op>0 ){ for(i=p2op; iaiIndent[i] += 2; } - if( str_in_array(zOp, azGoto) && p2opnIndent - && (abYield[p2op] || sqlite3_column_int(pSql, 2)) - ){ + if( str_in_array(zOp, azGoto) && p2opaiIndent[i] += 2; } } @@ -3501,6 +3537,48 @@ static void explain_data_delete(ShellState *p){ p->iIndent = 0; } +static void exec_prepared_stmt(ShellState*, sqlite3_stmt*); + +/* +** Display scan stats. +*/ +static void display_scanstats( + sqlite3 *db, /* Database to query */ + ShellState *pArg /* Pointer to ShellState */ +){ +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(pArg); +#else + if( pArg->scanstatsOn==3 ){ + const char *zSql = + " SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec," + " round(ncycle*100.0 / (sum(ncycle) OVER ()), 2)||'%' AS cycles" + " FROM bytecode(?)"; + + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_stmt *pSave = pArg->pStmt; + pArg->pStmt = pStmt; + sqlite3_bind_pointer(pStmt, 1, pSave, "stmt-pointer", 0); + + pArg->cnt = 0; + pArg->cMode = MODE_ScanExp; + explain_data_prepare(pArg, pStmt); + exec_prepared_stmt(pArg, pStmt); + explain_data_delete(pArg); + + sqlite3_finalize(pStmt); + pArg->pStmt = pSave; + } + }else{ + display_explain_scanstats(db, pArg); + } +#endif +} + /* ** Disable and restore .wheretrace and .treetrace/.selecttrace settings. */ @@ -3796,7 +3874,7 @@ static char *quoted_column(sqlite3_stmt *pStmt, int i){ */ static void exec_prepared_stmt_columnar( ShellState *p, /* Pointer to ShellState */ - sqlite3_stmt *pStmt /* Statment to run */ + sqlite3_stmt *pStmt /* Statement to run */ ){ sqlite3_int64 nRow = 0; int nColumn = 0; @@ -4022,7 +4100,7 @@ columnar_end: */ static void exec_prepared_stmt( ShellState *pArg, /* Pointer to ShellState */ - sqlite3_stmt *pStmt /* Statment to run */ + sqlite3_stmt *pStmt /* Statement to run */ ){ int rc; sqlite3_uint64 nRow = 0; @@ -4281,7 +4359,7 @@ static int shell_exec( if( zStmtSql==0 ) zStmtSql = ""; while( IsSpace(zStmtSql[0]) ) zStmtSql++; - /* save off the prepared statment handle and reset row count */ + /* save off the prepared statement handle and reset row count */ if( pArg ){ pArg->pStmt = pStmt; pArg->cnt = 0; @@ -4290,16 +4368,15 @@ static int shell_exec( /* Show the EXPLAIN QUERY PLAN if .eqp is on */ if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ sqlite3_stmt *pExplain; - char *zEQP; int triggerEQP = 0; disable_debug_trace_modes(); sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); if( pArg->autoEQP>=AUTOEQP_trigger ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0); } - zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql); - shell_check_oom(zEQP); - rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); + pExplain = pStmt; + sqlite3_reset(pExplain); + rc = sqlite3_stmt_explain(pExplain, 2); if( rc==SQLITE_OK ){ while( sqlite3_step(pExplain)==SQLITE_ROW ){ const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3); @@ -4311,36 +4388,31 @@ static int shell_exec( } eqp_render(pArg, 0); } - sqlite3_finalize(pExplain); - sqlite3_free(zEQP); if( pArg->autoEQP>=AUTOEQP_full ){ /* Also do an EXPLAIN for ".eqp full" mode */ - zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql); - shell_check_oom(zEQP); - rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); + sqlite3_reset(pExplain); + rc = sqlite3_stmt_explain(pExplain, 1); if( rc==SQLITE_OK ){ pArg->cMode = MODE_Explain; + assert( sqlite3_stmt_isexplain(pExplain)==1 ); explain_data_prepare(pArg, pExplain); exec_prepared_stmt(pArg, pExplain); explain_data_delete(pArg); } - sqlite3_finalize(pExplain); - sqlite3_free(zEQP); } if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0); - /* Reprepare pStmt before reactiving trace modes */ - sqlite3_finalize(pStmt); - sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - if( pArg ) pArg->pStmt = pStmt; } + sqlite3_reset(pStmt); + sqlite3_stmt_explain(pStmt, 0); restore_debug_trace_modes(); } if( pArg ){ + int bIsExplain = (sqlite3_stmt_isexplain(pStmt)==1); pArg->cMode = pArg->mode; if( pArg->autoExplain ){ - if( sqlite3_stmt_isexplain(pStmt)==1 ){ + if( bIsExplain ){ pArg->cMode = MODE_Explain; } if( sqlite3_stmt_isexplain(pStmt)==2 ){ @@ -4350,7 +4422,7 @@ static int shell_exec( /* If the shell is currently in ".explain" mode, gather the extra ** data required to add indents to the output.*/ - if( pArg->cMode==MODE_Explain ){ + if( pArg->cMode==MODE_Explain && bIsExplain ){ explain_data_prepare(pArg, pStmt); } } @@ -4466,7 +4538,7 @@ static char **tableColumnList(ShellState *p, const char *zTab){ */ if( preserveRowid && isIPK ){ /* If a single PRIMARY KEY column with type INTEGER was seen, then it - ** might be an alise for the ROWID. But it might also be a WITHOUT ROWID + ** might be an alias for the ROWID. But it might also be a WITHOUT ROWID ** table or a INTEGER PRIMARY KEY DESC column, neither of which are ** ROWID aliases. To distinguish these cases, check to see if ** there is a "pk" entry in "PRAGMA index_list". There will be @@ -4727,7 +4799,6 @@ static const char *(azHelp[]) = { " --async Write to FILE without journal and fsync()", #endif ".bail on|off Stop after hitting an error. Default OFF", - ".binary on|off Turn binary output on or off. Default OFF", #ifndef SQLITE_SHELL_FIDDLE ".cd DIRECTORY Change the working directory to DIRECTORY", #endif @@ -4737,6 +4808,9 @@ static const char *(azHelp[]) = { ".clone NEWDB Clone data into NEWDB from the existing database", #endif ".connection [close] [#] Open or close an auxiliary database connection", +#if defined(_WIN32) || defined(WIN32) + ".crnl on|off Translate \\n to \\r\\n. Default ON", +#endif ".databases List names and files of attached databases", ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", #if SQLITE_SHELL_HAVE_RECOVER @@ -5443,7 +5517,7 @@ static void open_db(ShellState *p, int openFlags){ /* Let custom-included extensions get their ..._init() called. * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) - * inititialization routine to be called. + * initialization routine to be called. */ { int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); @@ -5461,6 +5535,12 @@ static void open_db(ShellState *p, int openFlags){ } #endif + sqlite3_create_function(p->db, "strtod", 1, SQLITE_UTF8, 0, + shellStrtod, 0, 0); + sqlite3_create_function(p->db, "dtostr", 1, SQLITE_UTF8, 0, + shellDtostr, 0, 0); + sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0, + shellDtostr, 0, 0); sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, shellAddSchemaName, 0, 0); sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, @@ -5520,7 +5600,7 @@ static void open_db(ShellState *p, int openFlags){ } /* -** Attempt to close the databaes connection. Report errors. +** Attempt to close the database connection. Report errors. */ void close_db(sqlite3 *db){ int rc = sqlite3_close(db); @@ -6014,7 +6094,7 @@ static void tryToCloneData( if( rc ){ utf8_printf(stderr, "Error %d: %s on [%s]\n", sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), - zQuery); + zInsert); goto end_data_xfer; } for(k=0; k<2; k++){ @@ -8086,6 +8166,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else + /* Undocumented. Legacy only. See "crnl" below */ if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){ if( nArg==2 ){ if( booleanValue(azArg[1]) ){ @@ -8094,6 +8175,8 @@ static int do_meta_command(char *zLine, ShellState *p){ setTextMode(p->out, 1); } }else{ + raw_printf(stderr, "The \".binary\" command is deprecated." + " Use \".crnl\" instead.\n"); raw_printf(stderr, "Usage: .binary on|off\n"); rc = 1; } @@ -8221,6 +8304,22 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else + if( c=='c' && n==4 && cli_strncmp(azArg[0], "crnl", n)==0 ){ + if( nArg==2 ){ + if( booleanValue(azArg[1]) ){ + setTextMode(p->out, 1); + }else{ + setBinaryMode(p->out, 1); + } + }else{ +#if !defined(_WIN32) && !defined(WIN32) + raw_printf(stderr, "The \".crnl\" is a no-op on non-Windows machines.\n"); +#endif + raw_printf(stderr, "Usage: .crnl on|off\n"); + rc = 1; + } + }else + if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ char **azName = 0; int nName = 0; @@ -9885,6 +9984,9 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ + if( cli_strcmp(azArg[1], "vm")==0 ){ + p->scanstatsOn = 3; + }else if( cli_strcmp(azArg[1], "est")==0 ){ p->scanstatsOn = 2; }else{ @@ -10530,7 +10632,8 @@ static int do_meta_command(char *zLine, ShellState *p){ "with tabcols as materialized(\n" "select tname, cname\n" "from (" - " select ss.tname as tname, ti.name as cname\n" + " select printf('\"%%w\"',ss.tname) as tname," + " printf('\"%%w\"',ti.name) as cname\n" " from (%z) ss\n inner join pragma_table_info(tname) ti))\n" "select 'SELECT total(bad_text_count) AS bad_text_count\n" "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n" @@ -10802,7 +10905,7 @@ static int do_meta_command(char *zLine, ShellState *p){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ - int unSafe; /* Not valid for --safe mode */ + int unSafe; /* Not valid unless --unsafe-testing */ const char *zUsage; /* Usage notes */ } aCtrl[] = { {"always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, @@ -10827,6 +10930,7 @@ static int do_meta_command(char *zLine, ShellState *p){ {"seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, {"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, {"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, + {"uselongdouble", SQLITE_TESTCTRL_USELONGDOUBLE,0,"?BOOLEAN|\"default\"?"}, }; int testctrl = -1; int iCtrl = -1; @@ -10835,12 +10939,6 @@ static int do_meta_command(char *zLine, ShellState *p){ int i, n2; const char *zCmd = 0; - if( !ShellHasFlag(p,SHFLG_TestingMode) ){ - utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", - "testctrl"); - rc = 1; - goto meta_command_exit; - } open_db(p, 0); zCmd = nArg>=2 ? azArg[1] : "help"; @@ -10854,6 +10952,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( cli_strcmp(zCmd,"help")==0 ){ utf8_printf(p->out, "Available test-controls:\n"); for(i=0; iout, " .testctrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage); } @@ -10865,6 +10964,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** of the option name, or a numerical value. */ n2 = strlen30(zCmd); for(i=0; ibSafeMode ){ - utf8_printf(stderr, - "line %d: \".testctrl %s\" may not be used in safe mode\n", - p->lineno, aCtrl[iCtrl].zCtrlName); - exit(1); }else{ switch(testctrl){ @@ -10957,6 +11052,21 @@ static int do_meta_command(char *zLine, ShellState *p){ } break; + /* sqlite3_test_control(int, int) */ + case SQLITE_TESTCTRL_USELONGDOUBLE: { + int opt = -1; + if( nArg==3 ){ + if( cli_strcmp(azArg[2],"default")==0 ){ + opt = 2; + }else{ + opt = booleanValue(azArg[2]); + } + } + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 1; + break; + } + /* sqlite3_test_control(sqlite3*) */ case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: rc2 = sqlite3_test_control(testctrl, p->db); @@ -11194,6 +11304,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif /* SQLITE_USER_AUTHENTICATION */ if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ + char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB @@ -11204,11 +11315,11 @@ static int do_meta_command(char *zLine, ShellState *p){ #if defined(__clang__) && defined(__clang_major__) utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__) "\n"); + CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); #elif defined(_MSC_VER) - utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n"); + utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); #elif defined(__GNUC__) && defined(__VERSION__) - utf8_printf(p->out, "gcc-" __VERSION__ "\n"); + utf8_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); #endif }else @@ -11830,6 +11941,7 @@ static const char zOptions[] = " -nonce STRING set the safe-mode escape nonce\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" + " -pcachetrace trace all page cache operations\n" " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" " -safe enable safe-mode\n" @@ -12092,7 +12204,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( data.aAuxDb->zDbFilename==0 ){ data.aAuxDb->zDbFilename = z; }else{ - /* Excesss arguments are interpreted as SQL (or dot-commands) and + /* Excess arguments are interpreted as SQL (or dot-commands) and ** mean that nothing is read from stdin */ readStdin = 0; nCmd++; @@ -12177,7 +12289,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif #ifdef SQLITE_ENABLE_MULTIPLEX }else if( cli_strcmp(z,"-multiplex")==0 ){ - extern int sqlite3_multiple_initialize(const char*,int); + extern int sqlite3_multiplex_initialize(const char*,int); sqlite3_multiplex_initialize(0, 1); #endif }else if( cli_strcmp(z,"-mmap")==0 ){ @@ -12216,11 +12328,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif }else if( cli_strcmp(z, "-memtrace")==0 ){ sqlite3MemTraceActivate(stderr); + }else if( cli_strcmp(z, "-pcachetrace")==0 ){ + sqlite3PcacheTraceActivate(stderr); }else if( cli_strcmp(z,"-bail")==0 ){ bail_on_error = 1; }else if( cli_strcmp(z,"-nonce")==0 ){ free(data.zNonce); - data.zNonce = strdup(argv[++i]); + data.zNonce = strdup(cmdline_option_value(argc, argv, ++i)); }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ ShellSetFlag(&data,SHFLG_TestingMode); }else if( cli_strcmp(z,"-safe")==0 ){ @@ -12379,7 +12493,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid()); + printf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ stdin_is_interactive = 1; @@ -12403,6 +12518,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ i++; }else if( cli_strcmp(z,"-memtrace")==0 ){ i++; + }else if( cli_strcmp(z,"-pcachetrace")==0 ){ + i++; #ifdef SQLITE_ENABLE_SORTER_REFERENCES }else if( cli_strcmp(z,"-sorterref")==0 ){ i++; diff --git a/src/sqlite.h.in b/src/sqlite.h.in index b78adc2a3..48009b145 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -131,7 +131,7 @@ extern "C" { ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** -** Since [version 3.6.18] ([dateof:3.6.18]), +** Since [version 3.6.18] ([dateof:3.6.18]), ** SQLite source code has been stored in the ** Fossil configuration management ** system. ^The SQLITE_SOURCE_ID macro evaluates to @@ -528,6 +528,7 @@ int sqlite3_exec( #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) @@ -716,7 +717,7 @@ int sqlite3_exec( /* ** CAPI3REF: OS Interface Open File Handle ** -** An [sqlite3_file] object represents an open file in the +** An [sqlite3_file] object represents an open file in the ** [sqlite3_vfs | OS interface layer]. Individual OS interface ** implementations will ** want to subclass this object by appending additional fields @@ -738,7 +739,7 @@ struct sqlite3_file { ** This object defines the methods used to perform various operations ** against the open file represented by the [sqlite3_file] object. ** -** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element +** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element ** to a non-NULL pointer, then the sqlite3_io_methods.xClose method ** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The ** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen] @@ -982,13 +983,13 @@ struct sqlite3_io_methods { **
  • [[SQLITE_FCNTL_OVERWRITE]] ** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening ** a write transaction to indicate that, unless it is rolled back for some -** reason, the entire database file will be overwritten by the current +** reason, the entire database file will be overwritten by the current ** transaction. This is used by VACUUM operations. ** **
  • [[SQLITE_FCNTL_VFSNAME]] ** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of ** all [VFSes] in the VFS stack. The names are of all VFS shims and the -** final bottom-level VFS are written into memory obtained from +** final bottom-level VFS are written into memory obtained from ** [sqlite3_malloc()] and the result is stored in the char* variable ** that the fourth parameter of [sqlite3_file_control()] points to. ** The caller is responsible for freeing the memory when done. As with @@ -1179,22 +1180,22 @@ struct sqlite3_io_methods { ** **
  • [[SQLITE_FCNTL_EXTERNAL_READER]] ** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect -** whether or not there is a database client in another process with a wal-mode +** whether or not there is a database client in another process with a wal-mode ** transaction open on the database or not. It is only available on unix.The ** (void*) argument passed with this file-control should be a pointer to a ** value of type (int). The integer value is set to 1 if the database is a wal ** mode database and there exists at least one client in another process that -** currently has an SQL transaction open on the database. It is set to 0 if +** currently has an SQL transaction open on the database. It is set to 0 if ** the database is not a wal-mode db, or if there is no such connection in any ** other process. This opcode cannot be used to detect transactions opened ** by clients within the current process, only within other processes. ** **
  • [[SQLITE_FCNTL_CKSM_FILE]] -** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use interally by the +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the ** [checksum VFS shim] only. ** **
  • [[SQLITE_FCNTL_RESET_CACHE]] -** If there is currently no transaction open on the database, and the +** If there is currently no transaction open on the database, and the ** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control ** purges the contents of the in-memory page cache. If there is an open ** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. @@ -1496,7 +1497,7 @@ struct sqlite3_vfs { /* ** The methods above are in versions 1 through 3 of the sqlite_vfs object. ** New fields may be appended in future versions. The iVersion - ** value will increment whenever this happens. + ** value will increment whenever this happens. */ }; @@ -1688,7 +1689,7 @@ int sqlite3_config(int, ...); ** [database connection] (specified in the first argument). ** ** The second argument to sqlite3_db_config(D,V,...) is the -** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code +** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code ** that indicates what aspect of the [database connection] is being configured. ** Subsequent arguments vary depending on the configuration verb. ** @@ -1736,7 +1737,7 @@ int sqlite3_db_config(sqlite3*, int op, ...); ** allocators round up memory allocations at least to the next multiple ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] -** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, +** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** ** The xInit method initializes the memory allocator. For example, @@ -1811,7 +1812,7 @@ struct sqlite3_mem_methods { ** by a single thread. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** it is not possible to change the [threading mode] from its default -** value of Single-thread and so [sqlite3_config()] will return +** value of Single-thread and so [sqlite3_config()] will return ** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD ** configuration option. ** @@ -2295,11 +2296,11 @@ struct sqlite3_mem_methods { ** until after the database connection closes. ** ** -** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] +** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] **
    SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
    -**
    Usually, when a database in wal mode is closed or detached from a -** database handle, SQLite checks if this will mean that there are now no -** connections at all to the database. If so, it performs a checkpoint +**
    Usually, when a database in wal mode is closed or detached from a +** database handle, SQLite checks if this will mean that there are now no +** connections at all to the database. If so, it performs a checkpoint ** operation before closing the connection. This option may be used to ** override this behaviour. The first parameter passed to this operation ** is an integer - positive to disable checkpoints-on-close, or zero (the @@ -2318,7 +2319,7 @@ struct sqlite3_mem_methods { ** slower. But the QPSG has the advantage of more predictable behavior. With ** the QPSG active, SQLite will always use the same query plan in the field as ** was used during testing in the lab. -** The first argument to this setting is an integer which is 0 to disable +** The first argument to this setting is an integer which is 0 to disable ** the QPSG, positive to enable QPSG, or negative to leave the setting ** unchanged. The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether the QPSG is disabled or enabled @@ -2326,15 +2327,15 @@ struct sqlite3_mem_methods { **
    ** ** [[SQLITE_DBCONFIG_TRIGGER_EQP]]
    SQLITE_DBCONFIG_TRIGGER_EQP
    -**
    By default, the output of EXPLAIN QUERY PLAN commands does not +**
    By default, the output of EXPLAIN QUERY PLAN commands does not ** include output for any operations performed by trigger programs. This ** option is used to set or clear (the default) a flag that governs this ** behavior. The first parameter passed to this operation is an integer - ** positive to enable output for trigger programs, or zero to disable it, ** or negative to leave the setting unchanged. -** The second parameter is a pointer to an integer into which is written -** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if -** it is not disabled, 1 if it is. +** The second parameter is a pointer to an integer into which is written +** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if +** it is not disabled, 1 if it is. **
    ** ** [[SQLITE_DBCONFIG_RESET_DATABASE]]
    SQLITE_DBCONFIG_RESET_DATABASE
    @@ -2348,7 +2349,7 @@ struct sqlite3_mem_methods { ** database, or calling sqlite3_table_column_metadata(), ignoring any ** errors. This step is only necessary if the application desires to keep ** the database in WAL mode after the reset if it was in WAL mode before -** the reset. +** the reset. **
  • sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); **
  • [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); **
  • sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); @@ -2364,7 +2365,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_DBCONFIG_DEFENSIVE]]
    SQLITE_DBCONFIG_DEFENSIVE
    **
    The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the ** "defensive" flag for a database connection. When the defensive -** flag is enabled, language features that allow ordinary SQL to +** flag is enabled, language features that allow ordinary SQL to ** deliberately corrupt the database file are disabled. The disabled ** features include but are not limited to the following: **
      @@ -2380,7 +2381,7 @@ struct sqlite3_mem_methods { **
      The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the ** "writable_schema" flag. This has the same effect and is logically equivalent ** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF]. -** The first argument to this setting is an integer which is 0 to disable +** The first argument to this setting is an integer which is 0 to disable ** the writable_schema, positive to enable writable_schema, or negative to ** leave the setting unchanged. The second parameter is a pointer to an ** integer into which is written 0 or 1 to indicate whether the writable_schema @@ -2424,7 +2425,7 @@ struct sqlite3_mem_methods { ** including: **
        **
      • Prohibit the use of SQL functions inside triggers, views, -** CHECK constraints, DEFAULT clauses, expression indexes, +** CHECK constraints, DEFAULT clauses, expression indexes, ** partial indexes, or generated columns ** unless those functions are tagged with [SQLITE_INNOCUOUS]. **
      • Prohibit the use of virtual tables inside of triggers or views @@ -2445,7 +2446,7 @@ struct sqlite3_mem_methods { ** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, ** newly created databases are generally not understandable by SQLite versions ** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there -** is now scarcely any need to generate database files that are compatible +** is now scarcely any need to generate database files that are compatible ** all the way back to version 3.0.0, and so this setting is of little ** practical use, but is provided so that SQLite can continue to claim the ** ability to generate new database files that are compatible with version @@ -2454,7 +2455,7 @@ struct sqlite3_mem_methods { ** the [VACUUM] command will fail with an obscure error when attempting to ** process a table with generated columns and a descending index. This is ** not considered a bug since SQLite versions 3.3.0 and earlier do not support -** either generated columns or decending indexes. +** either generated columns or descending indexes. **
      ** ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] @@ -2463,7 +2464,7 @@ struct sqlite3_mem_methods { ** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears ** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() ** statistics. For statistics to be collected, the flag must be set on -** the database handle both when the SQL statement is prepared and when it +** the database handle both when the SQL statement is prepared and when it ** is stepped. The flag is set (collection of statistics is enabled) ** by default. This option takes two arguments: an integer and a pointer to ** an integer.. The first argument is 1, 0, or -1 to enable, disable, or @@ -2537,8 +2538,8 @@ int sqlite3_extended_result_codes(sqlite3*, int onoff); ** ^The sqlite3_last_insert_rowid(D) interface usually returns the [rowid] of ** the most recent successful [INSERT] into a rowid table or [virtual table] ** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not -** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred -** on the database connection D, then sqlite3_last_insert_rowid(D) returns +** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred +** on the database connection D, then sqlite3_last_insert_rowid(D) returns ** zero. ** ** As well as being set automatically as rows are inserted into database @@ -2548,15 +2549,15 @@ int sqlite3_extended_result_codes(sqlite3*, int onoff); ** Some virtual table implementations may INSERT rows into rowid tables as ** part of committing a transaction (e.g. to flush data accumulated in memory ** to disk). In this case subsequent calls to this function return the rowid -** associated with these internal INSERT operations, which leads to +** associated with these internal INSERT operations, which leads to ** unintuitive results. Virtual table implementations that do write to rowid -** tables in this way can avoid this problem by restoring the original -** rowid value using [sqlite3_set_last_insert_rowid()] before returning +** tables in this way can avoid this problem by restoring the original +** rowid value using [sqlite3_set_last_insert_rowid()] before returning ** control to the user. ** -** ^(If an [INSERT] occurs within a trigger then this routine will -** return the [rowid] of the inserted row as long as the trigger is -** running. Once the trigger program ends, the value returned +** ^(If an [INSERT] occurs within a trigger then this routine will +** return the [rowid] of the inserted row as long as the trigger is +** running. Once the trigger program ends, the value returned ** by this routine reverts to what it was before the trigger was fired.)^ ** ** ^An [INSERT] that fails due to a constraint violation is not a @@ -2589,7 +2590,7 @@ sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); ** METHOD: sqlite3 ** ** The sqlite3_set_last_insert_rowid(D, R) method allows the application to -** set the value returned by calling sqlite3_last_insert_rowid(D) to R +** set the value returned by calling sqlite3_last_insert_rowid(D) to R ** without inserting a row into the database. */ void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); @@ -2602,43 +2603,43 @@ void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); ** deleted by the most recently completed INSERT, UPDATE or DELETE ** statement on the database connection specified by the only parameter. ** The two functions are identical except for the type of the return value -** and that if the number of rows modified by the most recent INSERT, UPDATE +** and that if the number of rows modified by the most recent INSERT, UPDATE ** or DELETE is greater than the maximum value supported by type "int", then ** the return value of sqlite3_changes() is undefined. ^Executing any other ** type of SQL statement does not modify the value returned by these functions. ** ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are -** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], ** [foreign key actions] or [REPLACE] constraint resolution are not counted. -** -** Changes to a view that are intercepted by -** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value -** returned by sqlite3_changes() immediately after an INSERT, UPDATE or -** DELETE statement run on a view is always zero. Only changes made to real +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real ** tables are counted. ** ** Things are more complicated if the sqlite3_changes() function is ** executed while a trigger program is running. This may happen if the ** program uses the [changes() SQL function], or if some other callback ** function invokes sqlite3_changes() directly. Essentially: -** +** **
        **
      • ^(Before entering a trigger program the value returned by -** sqlite3_changes() function is saved. After the trigger program +** sqlite3_changes() function is saved. After the trigger program ** has finished, the original value is restored.)^ -** -**
      • ^(Within a trigger program each INSERT, UPDATE and DELETE -** statement sets the value returned by sqlite3_changes() -** upon completion as normal. Of course, this value will not include -** any changes performed by sub-triggers, as the sqlite3_changes() +** +**
      • ^(Within a trigger program each INSERT, UPDATE and DELETE +** statement sets the value returned by sqlite3_changes() +** upon completion as normal. Of course, this value will not include +** any changes performed by sub-triggers, as the sqlite3_changes() ** value will be saved and restored after each sub-trigger has run.)^ **
      -** +** ** ^This means that if the changes() SQL function (or similar) is used -** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** by the first INSERT, UPDATE or DELETE statement within a trigger, it ** returns the value as set when the calling statement began executing. -** ^If it is used by the second or subsequent such statement within a trigger -** program, the value returned reflects the number of rows modified by the +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the ** previous INSERT, UPDATE or DELETE statement within the same trigger. ** ** If a separate thread makes changes on the same database connection @@ -2663,16 +2664,16 @@ sqlite3_int64 sqlite3_changes64(sqlite3*); ** ^These functions return the total number of rows inserted, modified or ** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed ** since the database connection was opened, including those executed as -** part of trigger programs. The two functions are identical except for the -** type of the return value and that if the number of rows modified by the +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the ** connection exceeds the maximum value supported by type "int", then -** the return value of sqlite3_total_changes() is undefined. ^Executing -** any other type of SQL statement does not affect the value returned by +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by ** sqlite3_total_changes(). -** +** ** ^Changes made as part of [foreign key actions] are included in the ** count, but those made as part of REPLACE constraint resolution are -** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers ** are not counted. ** ** The [sqlite3_total_changes(D)] interface only reports the number @@ -2681,7 +2682,7 @@ sqlite3_int64 sqlite3_changes64(sqlite3*); ** To detect changes against a database file from other database ** connections use the [PRAGMA data_version] command or the ** [SQLITE_FCNTL_DATA_VERSION] [file control]. -** +** ** If a separate thread makes changes on the same database connection ** while [sqlite3_total_changes()] is running then the value ** returned is unpredictable and not meaningful. @@ -2724,7 +2725,7 @@ sqlite3_int64 sqlite3_total_changes64(sqlite3*); ** ** ^The sqlite3_interrupt(D) call is in effect until all currently running ** SQL statements on [database connection] D complete. ^Any new SQL statements -** that are started after the sqlite3_interrupt() call and before the +** that are started after the sqlite3_interrupt() call and before the ** running statement count reaches zero are interrupted as if they had been ** running prior to the sqlite3_interrupt() call. ^New SQL statements ** that are started after the running statement count reaches zero are @@ -2735,6 +2736,7 @@ sqlite3_int64 sqlite3_total_changes64(sqlite3*); ** ** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether ** or not an interrupt is currently in effect for [database connection] D. +** It returns 1 if an interrupt is currently in effect, or 0 otherwise. */ void sqlite3_interrupt(sqlite3*); int sqlite3_is_interrupted(sqlite3*); @@ -2760,7 +2762,7 @@ int sqlite3_is_interrupted(sqlite3*); ** ^These routines do not parse the SQL statements thus ** will not detect syntactically incorrect SQL. ** -** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior +** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior ** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked ** automatically by sqlite3_complete16(). If that initialization fails, ** then the return value from sqlite3_complete16() will be non-zero @@ -2805,7 +2807,7 @@ int sqlite3_complete16(const void *sql); ** The presence of a busy handler does not guarantee that it will be invoked ** when there is lock contention. ^If SQLite determines that invoking the busy ** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] -** to the application instead of invoking the +** to the application instead of invoking the ** busy handler. ** Consider a scenario where one process is holding a read lock that ** it is trying to promote to a reserved lock and @@ -2830,7 +2832,7 @@ int sqlite3_complete16(const void *sql); ** database connection that invoked the busy handler. In other words, ** the busy handler is not reentrant. Any such actions ** result in undefined behavior. -** +** ** A busy handler must not close the database connection ** or [prepared statement] that invoked the busy handler. */ @@ -2948,7 +2950,7 @@ void sqlite3_free_table(char **result); ** These routines are work-alikes of the "printf()" family of functions ** from the standard C library. ** These routines understand most of the common formatting options from -** the standard library printf() +** the standard library printf() ** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]). ** See the [built-in printf()] documentation for details. ** @@ -3144,7 +3146,7 @@ void sqlite3_randomness(int N, void *P); ** requested is ok. ^When the callback returns [SQLITE_DENY], the ** [sqlite3_prepare_v2()] or equivalent call that triggered the ** authorizer will fail with an error message explaining that -** access is denied. +** access is denied. ** ** ^The first parameter to the authorizer callback is a copy of the third ** parameter to the sqlite3_set_authorizer() interface. ^The second parameter @@ -3197,7 +3199,7 @@ void sqlite3_randomness(int N, void *P); ** database connections for the meaning of "modify" in this paragraph. ** ** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the -** statement might be re-prepared during [sqlite3_step()] due to a +** statement might be re-prepared during [sqlite3_step()] due to a ** schema change. Hence, the application should ensure that the ** correct authorizer callback remains in place during the [sqlite3_step()]. ** @@ -3388,10 +3390,12 @@ SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** M argument should be the bitwise OR-ed combination of ** zero or more [SQLITE_TRACE] constants. ** -** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides -** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2(). +** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P) +** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or +** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each +** database connection may have at most one trace callback. ** -** ^The X callback is invoked whenever any of the events identified by +** ^The X callback is invoked whenever any of the events identified by ** mask M occur. ^The integer return value from the callback is currently ** ignored, though this may change in future releases. Callback ** implementations should return zero to ensure future compatibility. @@ -3423,8 +3427,8 @@ int sqlite3_trace_v2( ** 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 approximate number of +** ^The parameter P is passed through as the only parameter to the +** callback function X. ^The parameter N is the approximate number of ** [virtual machine instructions] that are evaluated between successive ** invocations of the callback X. ^If N is less than one then the progress ** handler is disabled. @@ -3601,17 +3605,17 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** information. ** ** URI filenames are parsed according to RFC 3986. ^If the URI contains an -** authority, then it must be either an empty string or the string -** "localhost". ^If the authority is not an empty string or "localhost", an -** error is returned to the caller. ^The fragment component of a URI, if +** authority, then it must be either an empty string or the string +** "localhost". ^If the authority is not an empty string or "localhost", an +** error is returned to the caller. ^The fragment component of a URI, if ** present, is ignored. ** ** ^SQLite uses the path component of the URI as the name of the disk file -** which contains the database. ^If the path begins with a '/' character, -** then it is interpreted as an absolute path. ^If the path does not begin +** which contains the database. ^If the path begins with a '/' character, +** then it is interpreted as an absolute path. ^If the path does not begin ** with a '/' (meaning that the authority section is omitted from the URI) -** then the path is interpreted as a relative path. -** ^(On windows, the first component of an absolute path +** then the path is interpreted as a relative path. +** ^(On windows, the first component of an absolute path ** is a drive specification (e.g. "C:").)^ ** ** [[core URI query parameters]] @@ -3631,13 +3635,13 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** **
    • mode: ^(The mode parameter may be set to either "ro", "rw", ** "rwc", or "memory". Attempting to set it to any other value is -** an error)^. -** ^If "ro" is specified, then the database is opened for read-only -** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the -** third argument to sqlite3_open_v2(). ^If the mode option is set to -** "rw", then the database is opened for read-write (but not create) -** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had -** been set. ^Value "rwc" is equivalent to setting both +** an error)^. +** ^If "ro" is specified, then the database is opened for read-only +** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the +** third argument to sqlite3_open_v2(). ^If the mode option is set to +** "rw", then the database is opened for read-write (but not create) +** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had +** been set. ^Value "rwc" is equivalent to setting both ** SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE. ^If the mode option is ** set to "memory" then a pure [in-memory database] that never reads ** or writes from disk is used. ^It is an error to specify a value for @@ -3647,7 +3651,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); **
    • cache: ^The cache parameter may be set to either "shared" or ** "private". ^Setting it to "shared" is equivalent to setting the ** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to -** sqlite3_open_v2(). ^Setting the cache parameter to "private" is +** sqlite3_open_v2(). ^Setting the cache parameter to "private" is ** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. ** ^If sqlite3_open_v2() is used and the "cache" parameter is present in ** a URI filename, its value overrides any behavior requested by setting @@ -3673,7 +3677,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** property on a database file that does in fact change can result ** in incorrect query results and/or [SQLITE_CORRUPT] errors. ** See also: [SQLITE_IOCAP_IMMUTABLE]. -** +** **
    ** ** ^Specifying an unknown parameter in the query component of a URI is not an @@ -3685,37 +3689,37 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** **
  • **
    URI filenames Results -**
    file:data.db +**
    file:data.db ** Open the file "data.db" in the current directory. **
    file:/home/fred/data.db
    -** file:///home/fred/data.db
    -** file://localhost/home/fred/data.db
    +** file:///home/fred/data.db
    +** file://localhost/home/fred/data.db
    ** Open the database file "/home/fred/data.db". -**
    file://darkstar/home/fred/data.db +**
    file://darkstar/home/fred/data.db ** An error. "darkstar" is not a recognized authority. -**
    +**
    ** file:///C:/Documents%20and%20Settings/fred/Desktop/data.db ** Windows only: Open the file "data.db" on fred's desktop on drive -** C:. Note that the %20 escaping in this example is not strictly +** C:. Note that the %20 escaping in this example is not strictly ** necessary - space characters can be used literally ** in URI filenames. -**
    file:data.db?mode=ro&cache=private +**
    file:data.db?mode=ro&cache=private ** Open file "data.db" in the current directory for read-only access. ** Regardless of whether or not shared-cache mode is enabled by ** default, use a private cache. **
    file:/home/fred/data.db?vfs=unix-dotfile ** Open file "/home/fred/data.db". Use the special VFS "unix-dotfile" ** that uses dot-files in place of posix advisory locking. -**
    file:data.db?mode=readonly +**
    file:data.db?mode=readonly ** An error. "readonly" is not a valid option for the "mode" parameter. ** Use "ro" instead: "file:data.db?mode=ro". **
    ** ** ^URI hexadecimal escape sequences (%HH) are supported within the path and ** query components of a URI. A hexadecimal escape sequence consists of a -** percent sign - "%" - followed by exactly two hexadecimal digits +** percent sign - "%" - followed by exactly two hexadecimal digits ** specifying an octet value. ^Before the path or query components of a -** URI filename are interpreted, they are encoded using UTF-8 and all +** URI filename are interpreted, they are encoded using UTF-8 and all ** hexadecimal escape sequences replaced by a single byte containing the ** corresponding octet. If this process generates an invalid UTF-8 encoding, ** the results are undefined. @@ -3751,14 +3755,14 @@ int sqlite3_open_v2( ** CAPI3REF: Obtain Values For URI Parameters ** ** These are utility routines, useful to [VFS|custom VFS implementations], -** that check if a database file was a URI that contained a specific query +** that check if a database file was a URI that contained a specific query ** parameter, and if so obtains the value of that query parameter. ** ** The first parameter to these interfaces (hereafter referred to ** as F) must be one of: **
      **
    • A database filename pointer created by the SQLite core and -** passed into the xOpen() method of a VFS implemention, or +** passed into the xOpen() method of a VFS implementation, or **
    • A filename obtained from [sqlite3_db_filename()], or **
    • A new filename constructed using [sqlite3_create_filename()]. **
    @@ -3769,7 +3773,7 @@ int sqlite3_open_v2( ** If F is a suitable filename (as described in the previous paragraph) ** and if P is the name of the query parameter, then ** sqlite3_uri_parameter(F,P) returns the value of the P -** parameter if it exists or a NULL pointer if P does not appear as a +** parameter if it exists or a NULL pointer if P does not appear as a ** query parameter on F. If P is a query parameter of F and it ** has no explicit value, then sqlite3_uri_parameter(F,P) returns ** a pointer to an empty string. @@ -3778,7 +3782,7 @@ int sqlite3_open_v2( ** parameter and returns true (1) or false (0) according to the value ** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the ** value of query parameter P is one of "yes", "true", or "on" in any -** case or if the value begins with a non-zero number. The +** case or if the value begins with a non-zero number. The ** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of ** query parameter P is one of "no", "false", or "off" in any case or ** if the value begins with a numeric zero. If P is not a query @@ -3796,7 +3800,7 @@ int sqlite3_open_v2( ** parameters minus 1. The N value is zero-based so N should be 0 to obtain ** the name of the first query parameter, 1 for the second parameter, and ** so forth. -** +** ** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and ** is not a database file pathname pointer that the SQLite core passed @@ -3853,14 +3857,14 @@ const char *sqlite3_filename_wal(sqlite3_filename); ** CAPI3REF: Database File Corresponding To A Journal ** ** ^If X is the name of a rollback or WAL-mode journal file that is -** passed into the xOpen method of [sqlite3_vfs], then +** passed into the xOpen method of [sqlite3_vfs], then ** sqlite3_database_file_object(X) returns a pointer to the [sqlite3_file] ** object that represents the main database file. ** ** This routine is intended for use in custom [VFS] implementations ** only. It is not a general-purpose interface. ** The argument sqlite3_file_object(X) must be a filename pointer that -** has been passed into [sqlite3_vfs].xOpen method where the +** has been passed into [sqlite3_vfs].xOpen method where the ** flags parameter to xOpen contains one of the bits ** [SQLITE_OPEN_MAIN_JOURNAL] or [SQLITE_OPEN_WAL]. Any other use ** of this routine results in undefined and probably undesirable @@ -3871,7 +3875,7 @@ sqlite3_file *sqlite3_database_file_object(const char*); /* ** CAPI3REF: Create and Destroy VFS Filenames ** -** These interfces are provided for use by [VFS shim] implementations and +** These interfaces are provided for use by [VFS shim] implementations and ** are not useful outside of that context. ** ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of @@ -3883,7 +3887,7 @@ sqlite3_file *sqlite3_database_file_object(const char*); **
  • [sqlite3_uri_parameter()], **
  • [sqlite3_uri_boolean()], **
  • [sqlite3_uri_int64()], -**
  • [sqlite3_uri_key()], +**
  • [sqlite3_uri_key()], **
  • [sqlite3_filename_database()], **
  • [sqlite3_filename_journal()], or **
  • [sqlite3_filename_wal()]. @@ -3907,7 +3911,7 @@ sqlite3_file *sqlite3_database_file_object(const char*); ** If the Y parameter to sqlite3_free_filename(Y) is anything other ** than a NULL pointer or a pointer previously acquired from ** sqlite3_create_filename(), then bad things such as heap -** corruption or segfaults may occur. The value Y should not be +** corruption or segfaults may occur. The value Y should not be ** used again after sqlite3_free_filename(Y) has been called. This means ** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y, ** then the corresponding [sqlite3_module.xClose() method should also be @@ -3926,12 +3930,12 @@ void sqlite3_free_filename(sqlite3_filename); ** CAPI3REF: Error Codes And Messages ** METHOD: sqlite3 ** -** ^If the most recent sqlite3_* API call associated with +** ^If the most recent sqlite3_* API call associated with ** [database connection] D failed, then the sqlite3_errcode(D) interface ** returns the numeric [result code] or [extended result code] for that ** API call. ** ^The sqlite3_extended_errcode() -** interface is the same except that it always returns the +** interface is the same except that it always returns the ** [extended result code] even when extended result codes are ** disabled. ** @@ -3997,7 +4001,7 @@ int sqlite3_error_offset(sqlite3 *db); ** has been compiled into binary form and is ready to be evaluated. ** ** Think of each SQL statement as a separate computer program. The -** original SQL text is source code. A prepared statement object +** original SQL text is source code. A prepared statement object ** is the compiled object code. All SQL must be converted into a ** prepared statement before it can be run. ** @@ -4027,7 +4031,7 @@ typedef struct sqlite3_stmt sqlite3_stmt; ** new limit for that construct.)^ ** ** ^If the new limit is a negative number, the limit is unchanged. -** ^(For each limit category SQLITE_LIMIT_NAME there is a +** ^(For each limit category SQLITE_LIMIT_NAME there is a ** [limits | hard upper bound] ** set at compile-time by a C preprocessor macro called ** [limits | SQLITE_MAX_NAME]. @@ -4035,7 +4039,7 @@ typedef struct sqlite3_stmt sqlite3_stmt; ** ^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 +** ^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. @@ -4140,7 +4144,7 @@ int sqlite3_limit(sqlite3*, int id, int newVal); **
    The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner ** that the prepared statement will be retained for a long time and ** probably reused many times.)^ ^Without this flag, [sqlite3_prepare_v3()] -** and [sqlite3_prepare16_v3()] assume that the prepared statement will +** and [sqlite3_prepare16_v3()] assume that the prepared statement will ** be used just once or at most a few times and then destroyed using ** [sqlite3_finalize()] relatively soon. The current implementation acts ** on this hint by avoiding the use of [lookaside memory] so as not to @@ -4247,12 +4251,12 @@ int sqlite3_limit(sqlite3*, int id, int newVal); **
  • ** **
  • -** ^If the specific value bound to a [parameter | host parameter] in the +** ^If the specific value bound to a [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 +** 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 a WHERE-clause [parameter] might influence the +** to the [sqlite3_bind_text | bindings] of that [parameter]. +** ^The specific value of a 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_STAT4] compile-time option is enabled. @@ -4366,8 +4370,8 @@ const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** 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 +** [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: ** @@ -4381,10 +4385,10 @@ const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** ^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 +** 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 +** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. ** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since ** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and @@ -4397,7 +4401,7 @@ const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** ^For example, an UPDATE statement might have a WHERE clause that ** makes it a no-op, but the sqlite3_stmt_readonly() result would still ** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a -** read-only no-op if the table already exists, but +** read-only no-op if the table already exists, but ** sqlite3_stmt_readonly() still returns false for such a statement. ** ** ^If prepared statement X is an [EXPLAIN] or [EXPLAIN QUERY PLAN] @@ -4418,23 +4422,58 @@ int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); */ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); +/* +** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN +** setting for [prepared statement] S. If E is zero, then S becomes +** a normal prepared statement. If E is 1, then S behaves as if +** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if +** its SQL text began with "[EXPLAIN QUERY PLAN]". +** +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared because it was created using [sqlite3_prepare()] instead of +** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and +** hence has no saved SQL text with which to reprepare. +** +** Changing the explain setting for a prepared statement does not change +** the original SQL text for the statement. Hence, if the SQL text originally +** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) +** is called to convert the statement into an ordinary statement, the EXPLAIN +** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) +** output, even though the statement now acts like a normal SQL statement. +** +** This routine returns SQLITE_OK if the explain mode is successfully +** changed, or an error code if the explain mode could not be changed. +** The explain mode cannot be changed while a statement is active. +** Hence, it is good practice to call [sqlite3_reset(S)] +** immediately prior to calling sqlite3_stmt_explain(S,E). +*/ +int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); + /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** METHOD: sqlite3_stmt ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the -** [prepared statement] S has been stepped at least once using +** [prepared statement] S has been stepped at least once using ** [sqlite3_step(S)] but has neither run to completion (returned ** [SQLITE_DONE] from [sqlite3_step(S)]) nor ** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S) -** interface returns false if S is a NULL pointer. If S is not a +** interface returns false if S is a NULL pointer. If S is not a ** NULL pointer and is not a pointer to a valid [prepared statement] ** object, then the behavior is undefined and probably undesirable. ** ** This interface can be used in combination [sqlite3_next_stmt()] -** to locate all prepared statements associated with a database +** to locate all prepared statements associated with a database ** connection that are in need of being reset. This can be used, -** for example, in diagnostic routines to search for prepared +** for example, in diagnostic routines to search for prepared ** statements that are holding a transaction open. */ int sqlite3_stmt_busy(sqlite3_stmt*); @@ -4453,7 +4492,7 @@ int sqlite3_stmt_busy(sqlite3_stmt*); ** will accept either a protected or an unprotected sqlite3_value. ** Every interface that accepts sqlite3_value arguments specifies ** whether or not it requires a protected sqlite3_value. The -** [sqlite3_value_dup()] interface can be used to construct a new +** [sqlite3_value_dup()] interface can be used to construct a new ** protected sqlite3_value from an unprotected sqlite3_value. ** ** The terms "protected" and "unprotected" refer to whether or not @@ -4461,7 +4500,7 @@ int sqlite3_stmt_busy(sqlite3_stmt*); ** sqlite3_value object but no mutex is held for an unprotected ** sqlite3_value object. If SQLite is compiled to be single-threaded ** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) -** or if SQLite is run in one of reduced mutex modes +** or if SQLite is run in one of reduced mutex modes ** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] ** then there is no distinction between protected and unprotected ** sqlite3_value objects and they can be used interchangeably. However, @@ -4552,7 +4591,7 @@ typedef struct sqlite3_context sqlite3_context; ** found in first character, which is removed, or in the absence of a BOM ** the byte order is the native byte order of the host ** machine for sqlite3_bind_text16() or the byte order specified in -** the 6th parameter for sqlite3_bind_text64().)^ +** the 6th parameter for sqlite3_bind_text64().)^ ** ^If UTF16 input text contains invalid unicode ** characters, then SQLite might change those invalid characters ** into the unicode replacement character: U+FFFD. @@ -4569,7 +4608,7 @@ typedef struct sqlite3_context sqlite3_context; ** or sqlite3_bind_text16() or sqlite3_bind_text64() then ** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL -** terminated. If any NUL characters occurs at byte offsets less than +** terminated. If any NUL characters occurs at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. @@ -4581,7 +4620,7 @@ typedef struct sqlite3_context sqlite3_context; ** with it may be passed. ^It is called to dispose of the BLOB or string even ** if the call to the bind API fails, except the destructor is not called if ** the third parameter is a NULL pointer or the fourth parameter is negative. -** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that +** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that ** the application remains responsible for disposing of the object. ^In this ** case, the object and the provided pointer to it must remain valid until ** either the prepared statement is finalized or the same SQL parameter is @@ -4735,7 +4774,7 @@ int sqlite3_clear_bindings(sqlite3_stmt*); ** METHOD: sqlite3_stmt ** ** ^Return the number of columns in the result set returned by the -** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement]. ^If this routine returns 0, that means the ** [prepared statement] returns no data (for example an [UPDATE]). ** ^However, just because this routine returns a positive number does not ** mean that one or more rows of data will be returned. ^A SELECT statement @@ -4917,7 +4956,7 @@ const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** 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_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] ([dateof:3.6.23.1], ** sqlite3_step() began @@ -5008,7 +5047,7 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); ** sqlite3_column_int64→64-bit INTEGER result ** sqlite3_column_text→UTF-8 TEXT result ** sqlite3_column_text16→UTF-16 TEXT result -** sqlite3_column_value→The result as an +** sqlite3_column_value→The result as an ** [sqlite3_value|unprotected sqlite3_value] object. **     ** sqlite3_column_bytes→Size of a BLOB @@ -5056,7 +5095,7 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); ** The return value of sqlite3_column_type() can be used to decide which ** of the first six interface should be used to extract the column value. ** The value returned by sqlite3_column_type() is only meaningful if no -** automatic type conversions have occurred for the value in question. +** automatic type conversions have occurred for the value in question. ** After a type conversion, the result of calling sqlite3_column_type() ** is undefined, though harmless. Future ** versions of SQLite may change the behavior of sqlite3_column_type() @@ -5084,7 +5123,7 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); ** 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 +** ^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 @@ -5107,7 +5146,7 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); ** to routines like [sqlite3_value_int()], [sqlite3_value_text()], ** or [sqlite3_value_bytes()], the behavior is not threadsafe. ** Hence, the sqlite3_column_value() interface -** is normally only useful within the implementation of +** is normally only useful within the implementation of ** [application-defined SQL functions] or [virtual tables], not within ** top-level application code. ** @@ -5260,14 +5299,26 @@ int sqlite3_finalize(sqlite3_stmt *pStmt); ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S ** back to the beginning of its program. ** -** ^If the most recent call to [sqlite3_step(S)] for the -** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], -** or if [sqlite3_step(S)] has never before been called on S, -** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** ^The return code from [sqlite3_reset(S)] indicates whether or not +** the previous evaluation of prepared statement S completed successfully. +** ^If [sqlite3_step(S)] has never before been called on S or if +** [sqlite3_step(S)] has not been called since the previous call +** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return +** [SQLITE_OK]. ** ** ^If the most recent call to [sqlite3_step(S)] for the ** [prepared statement] S indicated an error, then ** [sqlite3_reset(S)] returns an appropriate [error code]. +** ^The [sqlite3_reset(S)] interface might also return an [error code] +** if there were no prior errors but the process of resetting +** the prepared statement caused a new error. ^For example, if an +** [INSERT] statement with a [RETURNING] clause is only stepped one time, +** that one call to [sqlite3_step(S)] might return SQLITE_ROW but +** the overall statement might still fail and the [sqlite3_reset(S)] call +** might return SQLITE_BUSY if locking constraints prevent the +** database change from committing. Therefore, it is important that +** applications check the return code from [sqlite3_reset(S)] even if +** no prior call to [sqlite3_step(S)] indicated a problem. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. @@ -5282,8 +5333,8 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** ^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 differences between -** the three "sqlite3_create_function*" routines are the text encoding -** expected for the second parameter (the name of the function being +** the three "sqlite3_create_function*" routines are the text encoding +** expected for the second parameter (the name of the function being ** created) and the presence or absence of a destructor callback for ** the application data pointer. Function sqlite3_create_window_function() ** is similar, but allows the user to supply the extra callback functions @@ -5297,7 +5348,7 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** ^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. +** 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. ** @@ -5312,7 +5363,7 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** ^The fourth parameter, eTextRep, specifies what ** [SQLITE_UTF8 | text encoding] this SQL function prefers for ** its parameters. The application should set this parameter to -** [SQLITE_UTF16LE] if the function implementation invokes +** [SQLITE_UTF16LE] if the function implementation invokes ** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the ** implementation invokes [sqlite3_value_text16be()] on an input, or ** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8] @@ -5338,7 +5389,7 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for ** all application-defined SQL functions that do not need to be ** used inside of triggers, view, CHECK constraints, or other elements of -** the database schema. This flags is especially recommended for SQL +** the database schema. This flags is especially recommended for SQL ** functions that have side effects or reveal internal application state. ** Without this flag, an attacker might be able to modify the schema of ** a database file to include invocations of the function with parameters @@ -5358,21 +5409,21 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** SQL function or aggregate, pass NULL pointers for all three function ** callbacks. ** -** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue +** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue ** and xInverse) passed to sqlite3_create_window_function are pointers to ** C-language callbacks that implement the new function. xStep and xFinal ** must both be non-NULL. xValue and xInverse may either both be NULL, in -** which case a regular aggregate function is created, or must both be +** which case a regular aggregate function is created, or must both be ** non-NULL, in which case the new function may be used as either an aggregate ** or aggregate window function. More details regarding the implementation -** of aggregate window functions are +** of aggregate window functions are ** [user-defined window functions|available here]. ** ** ^(If the final parameter to sqlite3_create_function_v2() or ** sqlite3_create_window_function() 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 +** 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 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(). @@ -5385,7 +5436,7 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** nArg parameter is a better match than a function implementation with ** a negative nArg. ^A function where the preferred text encoding ** matches the database encoding is a better -** match than a function where the encoding is different. +** match than a function where the encoding is different. ** ^A function where the encoding difference is between UTF16le and UTF16be ** is a closer match than a function where the encoding difference is ** between UTF8 and UTF16. @@ -5457,7 +5508,7 @@ int sqlite3_create_window_function( /* ** CAPI3REF: Function Flags ** -** These constants may be ORed together with the +** These constants may be ORed together with the ** [SQLITE_UTF8 | preferred text encoding] as the fourth argument ** to [sqlite3_create_function()], [sqlite3_create_function16()], or ** [sqlite3_create_function_v2()]. @@ -5473,18 +5524,18 @@ int sqlite3_create_window_function( ** SQLite might also optimize deterministic functions by factoring them ** out of inner loops. **
  • -** +** ** [[SQLITE_DIRECTONLY]]
    SQLITE_DIRECTONLY
    ** The SQLITE_DIRECTONLY flag means that the function may only be invoked -** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in +** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in ** schema structures such as [CHECK constraints], [DEFAULT clauses], ** [expression indexes], [partial indexes], or [generated columns]. **

    -** The SQLITE_DIRECTONLY flag is recommended for any +** The SQLITE_DIRECTONLY flag is recommended for any ** [application-defined SQL function] ** that has side-effects or that could potentially leak sensitive information. ** This will prevent attacks in which an application is tricked -** into using a database file that has had its schema surreptiously +** into using a database file that has had its schema surreptitiously ** modified to invoke the application-defined function in ways that are ** harmful. **

    @@ -5540,7 +5591,7 @@ int sqlite3_create_window_function( ** DEPRECATED ** ** These functions are [deprecated]. In order to maintain -** backwards compatibility with older code, these functions continue +** backwards compatibility with older code, these functions continue ** to be supported. However, new applications should avoid ** the use of these functions. To encourage programmers to avoid ** these functions, we will not explain what they do. @@ -5608,11 +5659,11 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces ** extract UTF-16 strings as big-endian and little-endian respectively. ** -** ^If [sqlite3_value] object V was initialized +** ^If [sqlite3_value] object V was initialized ** using [sqlite3_bind_pointer(S,I,P,X,D)] or [sqlite3_result_pointer(C,P,X,D)] ** and if X and Y are strings that compare equal according to strcmp(X,Y), ** then sqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise, -** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() +** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. ** ** ^(The sqlite3_value_type(V) interface returns the @@ -5758,7 +5809,7 @@ void sqlite3_value_free(sqlite3_value*); ** Implementations of aggregate SQL functions use this ** routine to allocate memory for storing their state. ** -** ^The first time the sqlite3_aggregate_context(C,N) routine is called +** ^The first time the sqlite3_aggregate_context(C,N) routine is called ** for a particular aggregate function, SQLite allocates ** N bytes of memory, zeroes out that memory, and returns a pointer ** to the new memory. ^On second and subsequent calls to @@ -5771,7 +5822,7 @@ void sqlite3_value_free(sqlite3_value*); ** In those cases, sqlite3_aggregate_context() might be called for the ** first time from within xFinal().)^ ** -** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer +** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer ** when first called if N is less than or equal to zero or if a memory ** allocation error occurs. ** @@ -5780,10 +5831,10 @@ void sqlite3_value_free(sqlite3_value*); ** value of N in any subsequent call to sqlite3_aggregate_context() within ** the same aggregate function instance will not resize the memory ** allocation.)^ Within the xFinal callback, it is customary to set -** N=0 in calls to sqlite3_aggregate_context(C,N) so that no +** N=0 in calls to sqlite3_aggregate_context(C,N) so that no ** pointless memory allocations occur. ** -** ^SQLite automatically frees the memory allocated by +** ^SQLite automatically frees the memory allocated by ** sqlite3_aggregate_context() when the aggregate query concludes. ** ** The first parameter must be a copy of the @@ -5833,7 +5884,7 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** some circumstances the associated metadata may be preserved. An example ** of where this might be useful is in a regular-expression matching ** function. The compiled version of the regular expression can be stored as -** metadata associated with the pattern string. +** metadata associated with the pattern string. ** Then as long as the pattern string remains the same, ** the compiled regular expression can be reused on multiple ** invocations of the same function. @@ -5859,10 +5910,10 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** SQL statement)^, or **

  • ^(when sqlite3_set_auxdata() is invoked again on the same ** parameter)^, or -**
  • ^(during the original sqlite3_set_auxdata() call when a memory +**
  • ^(during the original sqlite3_set_auxdata() call when a memory ** allocation error occurs.)^ ** -** Note the last bullet in particular. The destructor X in +** Note the last bullet in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the @@ -6036,7 +6087,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** ** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an ** SQL NULL value, just like [sqlite3_result_null(C)], except that it -** also associates the host-language pointer P or type T with that +** also associates the host-language pointer P or type T with that ** NULL value such that the pointer can be retrieved within an ** [application-defined SQL function] using [sqlite3_value_pointer()]. ** ^If the D parameter is not NULL, then it is a pointer to a destructor @@ -6078,8 +6129,8 @@ int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); ** METHOD: sqlite3_context ** ** The sqlite3_result_subtype(C,T) function causes the subtype of -** the result from the [application-defined SQL function] with -** [sqlite3_context] C to be the value T. Only the lower 8 bits +** the result from the [application-defined SQL function] with +** [sqlite3_context] C to be the value T. Only the lower 8 bits ** of the subtype T are preserved in current versions of SQLite; ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase @@ -6126,7 +6177,7 @@ void sqlite3_result_subtype(sqlite3_context*,unsigned int); ** 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 +** ^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 two integer parameters to the collating ** function callback are the length of the two strings, in bytes. The collating @@ -6157,36 +6208,36 @@ void sqlite3_result_subtype(sqlite3_context*,unsigned int); ** calls to the collation creation functions or when the ** [database connection] is closed using [sqlite3_close()]. ** -** ^The xDestroy callback is not called if the +** ^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 +** 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 +** 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()]. */ int sqlite3_create_collation( - sqlite3*, - const char *zName, - int eTextRep, + sqlite3*, + const char *zName, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); int sqlite3_create_collation_v2( - sqlite3*, - const char *zName, - int eTextRep, + sqlite3*, + const char *zName, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDestroy)(void*) ); int sqlite3_create_collation16( - sqlite3*, + sqlite3*, const void *zName, - int eTextRep, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); @@ -6219,19 +6270,19 @@ int sqlite3_create_collation16( ** [sqlite3_create_collation_v2()]. */ int sqlite3_collation_needed( - sqlite3*, - void*, + sqlite3*, + void*, void(*)(void*,sqlite3*,int eTextRep,const char*) ); int sqlite3_collation_needed16( - sqlite3*, + sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); #ifdef SQLITE_ENABLE_CEROD /* -** Specify the activation key for a CEROD database. Unless +** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ void sqlite3_activate_cerod( @@ -6294,7 +6345,7 @@ int sqlite3_sleep(int); ** ^The [temp_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [temp_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be @@ -6351,7 +6402,7 @@ SQLITE_EXTERN char *sqlite3_temp_directory; ** ^The [data_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [data_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be @@ -6617,7 +6668,7 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** CAPI3REF: Autovacuum Compaction Amount Callback ** METHOD: sqlite3 ** -** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback ** function C that is invoked prior to each autovacuum of the database ** file. ^The callback is passed a copy of the generic data pointer (P), ** the schema-name of the attached database that is being autovacuumed, @@ -6729,7 +6780,7 @@ int sqlite3_autovacuum_pages( ** and [sqlite3_preupdate_hook()] interfaces. */ void *sqlite3_update_hook( - sqlite3*, + sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void* ); @@ -6748,7 +6799,7 @@ void *sqlite3_update_hook( ** [use of shared cache mode is discouraged]. ** ** ^Cache sharing is enabled and disabled for an entire process. -** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). ** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** @@ -6769,8 +6820,8 @@ void *sqlite3_update_hook( ** with the [SQLITE_OPEN_SHAREDCACHE] flag. ** ** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 -** and will always return SQLITE_MISUSE. On those systems, -** shared cache mode should be enabled per-database connection via +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via ** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. ** ** This interface is threadsafe on processors where writing a @@ -6823,7 +6874,7 @@ int sqlite3_db_release_memory(sqlite3*); ** 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 +** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** ** ^The sqlite3_hard_heap_limit64(N) interface sets a hard upper bound of @@ -6939,7 +6990,7 @@ SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table ** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no @@ -7005,7 +7056,7 @@ int sqlite3_table_column_metadata( ** prior to calling this API, ** otherwise an error will be returned. ** -** Security warning: It is recommended that the +** Security warning: It is recommended that the ** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this ** interface. The use of the [sqlite3_enable_load_extension()] interface ** should be avoided. This will keep the SQL function [load_extension()] @@ -7092,7 +7143,7 @@ int sqlite3_auto_extension(void(*xEntryPoint)(void)); ** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the ** initialization routine X that was registered using a prior call to ** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] -** routine returns 1 if initialization routine X was successfully +** routine returns 1 if initialization routine X was successfully ** unregistered and it returns 0 if X was not on the list of initialization ** routines. */ @@ -7118,8 +7169,8 @@ typedef struct sqlite3_module sqlite3_module; ** CAPI3REF: Virtual Table Object ** KEYWORDS: sqlite3_module {virtual table module} ** -** This structure, sometimes called a "virtual table module", -** defines the implementation of a [virtual table]. +** This structure, sometimes called a "virtual table module", +** defines the implementation of a [virtual table]. ** This structure consists mostly of methods for the module. ** ** ^A virtual table module is created by filling in a persistent @@ -7158,7 +7209,7 @@ struct sqlite3_module { void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg); int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); - /* The methods above are in version 1 of the sqlite_module object. Those + /* The methods above are in version 1 of the sqlite_module object. Those ** below are for version 2 and greater. */ int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int); @@ -7208,7 +7259,7 @@ struct sqlite3_module { ** required by SQLite. If the table has at least 64 columns and any column ** to the right of the first 63 is required, then bit 63 of colUsed is also ** set. In other words, column iCol may be required if the expression -** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to +** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to ** non-zero. ** ** The [xBestIndex] method must fill aConstraintUsage[] with information @@ -7235,17 +7286,17 @@ struct sqlite3_module { ** ** ^The estimatedCost value is an estimate of the cost of a particular ** strategy. A cost of N indicates that the cost of the strategy is similar -** to a linear scan of an SQLite table with N rows. A cost of log(N) +** to a linear scan of an SQLite table with N rows. A cost of log(N) ** indicates that the expense of the operation is similar to that of a ** binary search on a unique indexed field of an SQLite table with N rows. ** ** ^The estimatedRows value is an estimate of the number of rows that ** will be returned by the strategy. ** -** The xBestIndex method may optionally populate the idxFlags field with a +** The xBestIndex method may optionally populate the idxFlags field with a ** mask of SQLITE_INDEX_SCAN_* flags. Currently there is only one such flag - ** SQLITE_INDEX_SCAN_UNIQUE. If the xBestIndex method sets this flag, SQLite -** assumes that the strategy may visit at most one row. +** assumes that the strategy may visit at most one row. ** ** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then ** SQLite also assumes that if a call to the xUpdate() method is made as @@ -7258,14 +7309,14 @@ struct sqlite3_module { ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info -** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). ** If a virtual table extension is -** used with an SQLite version earlier than 3.8.2, the results of attempting -** to read or write the estimatedRows field are undefined (but are likely +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely ** to include crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field -** was added for [version 3.9.0] ([dateof:3.9.0]). +** was added for [version 3.9.0] ([dateof:3.9.0]). ** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. @@ -7305,7 +7356,7 @@ struct sqlite3_index_info { /* ** CAPI3REF: Virtual Table Scan Flags ** -** Virtual table implementations are allowed to set the +** Virtual table implementations are allowed to set the ** [sqlite3_index_info].idxFlags field to some combination of ** these bits. */ @@ -7377,7 +7428,7 @@ struct sqlite3_index_info { ** preexisting [virtual table] for the module. ** ** ^The module name is registered on the [database connection] specified -** by the first parameter. ^The name of the module is given by the +** by the first parameter. ^The name of the module is given by the ** second parameter. ^The third parameter is a pointer to ** the implementation of the [virtual table module]. ^The fourth ** parameter is an arbitrary client data pointer that is passed through @@ -7492,7 +7543,7 @@ int sqlite3_declare_vtab(sqlite3*, const char *zSQL); ** METHOD: sqlite3 ** ** ^(Virtual tables can provide alternative implementations of functions -** using the [xFindFunction] method of the [virtual table module]. +** using the [xFindFunction] method of the [virtual table module]. ** But global versions of those functions ** must exist in order to be overloaded.)^ ** @@ -7533,7 +7584,7 @@ typedef struct sqlite3_blob sqlite3_blob; ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** )^ ** -** ^(Parameter zDb is not the filename that contains the database, but +** ^(Parameter zDb is not the filename that contains the database, but ** rather the symbolic name of the database. For attached databases, this is ** the name that appears after the AS keyword in the [ATTACH] statement. ** For the main database file, the database name is "main". For TEMP @@ -7546,28 +7597,28 @@ typedef struct sqlite3_blob sqlite3_blob; ** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored ** in *ppBlob. Otherwise an [error code] is returned and, unless the error ** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided -** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** the API is not misused, it is always safe to call [sqlite3_blob_close()] ** on *ppBlob after this function it returns. ** ** This function fails with SQLITE_ERROR if any of the following are true: **
      -**
    • ^(Database zDb does not exist)^, -**
    • ^(Table zTable does not exist within database zDb)^, -**
    • ^(Table zTable is a WITHOUT ROWID table)^, +**
    • ^(Database zDb does not exist)^, +**
    • ^(Table zTable does not exist within database zDb)^, +**
    • ^(Table zTable is a WITHOUT ROWID table)^, **
    • ^(Column zColumn does not exist)^, **
    • ^(Row iRow is not present in the table)^, **
    • ^(The specified column of row iRow contains a value that is not ** a TEXT or BLOB value)^, -**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE ** constraint and the blob is being opened for read/write access)^, -**
    • ^([foreign key constraints | Foreign key constraints] are enabled, +**
    • ^([foreign key constraints | Foreign key constraints] are enabled, ** column zColumn is part of a [child key] definition and the blob is ** being opened for read/write access)^. **
    ** -** ^Unless it returns SQLITE_MISUSE, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** A BLOB referenced by sqlite3_blob_open() may be read using the ** [sqlite3_blob_read()] interface and modified by using @@ -7593,7 +7644,7 @@ typedef struct sqlite3_blob sqlite3_blob; ** blob. ** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function may be used to create a +** and the built-in [zeroblob] SQL function may be used to create a ** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually @@ -7643,7 +7694,7 @@ int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); ** DESTRUCTOR: sqlite3_blob ** ** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed -** unconditionally. Even if this routine returns an error code, the +** unconditionally. Even if this routine returns an error code, the ** handle is still closed.)^ ** ** ^If the blob handle being closed was opened for read-write access, and if @@ -7653,10 +7704,10 @@ int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); ** code is returned and the transaction rolled back. ** ** Calling this function with an argument that is not a NULL pointer or an -** open blob handle results in undefined behaviour. ^Calling this routine -** with a null pointer (such as would be returned by a failed call to +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to ** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function -** is passed a valid open blob handle, the values returned by the +** is passed a valid open blob handle, the values returned by the ** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. */ int sqlite3_blob_close(sqlite3_blob *); @@ -7665,7 +7716,7 @@ int sqlite3_blob_close(sqlite3_blob *); ** CAPI3REF: Return The Size Of An Open BLOB ** METHOD: sqlite3_blob ** -** ^Returns the size in bytes of the BLOB accessible via the +** ^Returns the size in bytes of the BLOB accessible via the ** successfully opened [BLOB handle] in its only argument. ^The ** incremental blob I/O routines can only read or overwriting existing ** blob content; they cannot change the size of a blob. @@ -7716,9 +7767,9 @@ int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** ** ^(On success, sqlite3_blob_write() returns SQLITE_OK. ** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ^Unless SQLITE_MISUSE is returned, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), @@ -7727,9 +7778,9 @@ int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. The size of the -** BLOB (and hence the maximum value of N+iOffset) can be determined -** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less ** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an @@ -8048,7 +8099,7 @@ int sqlite3_mutex_notheld(sqlite3_mutex*); ** CAPI3REF: Retrieve the mutex for a database connection ** METHOD: sqlite3 ** -** ^This interface returns a pointer the [sqlite3_mutex] object that +** ^This interface returns a pointer the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this @@ -8075,7 +8126,7 @@ sqlite3_mutex *sqlite3_db_mutex(sqlite3*); ** method becomes the return value of this routine. ** ** A few opcodes for [sqlite3_file_control()] are handled directly -** by the SQLite core and never invoke the +** by the SQLite core and never invoke the ** sqlite3_io_methods.xFileControl method. ** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes ** a pointer to the underlying [sqlite3_file] object to be written into @@ -8161,12 +8212,13 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_TRACEFLAGS 31 #define SQLITE_TESTCTRL_TUNE 32 #define SQLITE_TESTCTRL_LOGEST 33 -#define SQLITE_TESTCTRL_LAST 33 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking ** -** These routines provide access to the set of SQL language keywords +** These routines provide access to the set of SQL language keywords ** recognized by SQLite. Applications can uses these routines to determine ** whether or not a specific identifier needs to be escaped (for example, ** by enclosing in double-quotes) so as not to confuse the parser. @@ -8238,14 +8290,14 @@ typedef struct sqlite3_str sqlite3_str; ** ** ^The [sqlite3_str_new(D)] interface allocates and initializes ** a new [sqlite3_str] object. To avoid memory leaks, the object returned by -** [sqlite3_str_new()] must be freed by a subsequent call to +** [sqlite3_str_new()] must be freed by a subsequent call to ** [sqlite3_str_finish(X)]. ** ** ^The [sqlite3_str_new(D)] interface always returns a pointer to a ** valid [sqlite3_str] object, though in the event of an out-of-memory ** error the returned object might be a special singleton that will -** silently reject new text, always return SQLITE_NOMEM from -** [sqlite3_str_errcode()], always return 0 for +** silently reject new text, always return SQLITE_NOMEM from +** [sqlite3_str_errcode()], always return 0 for ** [sqlite3_str_length()], and always return NULL from ** [sqlite3_str_finish(X)]. It is always safe to use the value ** returned by [sqlite3_str_new(D)] as the sqlite3_str parameter @@ -8281,9 +8333,9 @@ char *sqlite3_str_finish(sqlite3_str*); ** These interfaces add content to an sqlite3_str object previously obtained ** from [sqlite3_str_new()]. ** -** ^The [sqlite3_str_appendf(X,F,...)] and +** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] -** functionality of SQLite to append formatted text onto the end of +** functionality of SQLite to append formatted text onto the end of ** [sqlite3_str] object X. ** ** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S @@ -8300,7 +8352,7 @@ char *sqlite3_str_finish(sqlite3_str*); ** ^This method can be used, for example, to add whitespace indentation. ** ** ^The [sqlite3_str_reset(X)] method resets the string under construction -** inside [sqlite3_str] object X back to zero bytes in length. +** inside [sqlite3_str] object X back to zero bytes in length. ** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a @@ -8402,7 +8454,7 @@ int sqlite3_status64( **
    This parameter records the largest memory allocation request ** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their ** internal equivalents). Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
    )^ ** ** [[SQLITE_STATUS_MALLOC_COUNT]] ^(
    SQLITE_STATUS_MALLOC_COUNT
    @@ -8411,11 +8463,11 @@ int sqlite3_status64( ** ** [[SQLITE_STATUS_PAGECACHE_USED]] ^(
    SQLITE_STATUS_PAGECACHE_USED
    **
    This parameter returns the number of pages used out of the -** [pagecache memory allocator] that was configured using +** [pagecache memory allocator] that was configured using ** [SQLITE_CONFIG_PAGECACHE]. The ** value returned is in pages, not in bytes.
    )^ ** -** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] +** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] ** ^(
    SQLITE_STATUS_PAGECACHE_OVERFLOW
    **
    This parameter returns the number of bytes of page cache ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] @@ -8428,7 +8480,7 @@ int sqlite3_status64( ** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(
    SQLITE_STATUS_PAGECACHE_SIZE
    **
    This parameter records the largest memory allocation request ** handed to the [pagecache memory allocator]. Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
    )^ ** ** [[SQLITE_STATUS_SCRATCH_USED]]
    SQLITE_STATUS_SCRATCH_USED
    @@ -8441,7 +8493,7 @@ int sqlite3_status64( **
    No longer used.
    ** ** [[SQLITE_STATUS_PARSER_STACK]] ^(
    SQLITE_STATUS_PARSER_STACK
    -**
    The *pHighwater parameter records the deepest parser stack. +**
    The *pHighwater parameter records the deepest parser stack. ** The *pCurrent value is undefined. The *pHighwater value is only ** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
    )^ **
  • @@ -8463,12 +8515,12 @@ int sqlite3_status64( ** CAPI3REF: Database Connection Status ** METHOD: sqlite3 ** -** ^This interface is used to retrieve runtime status information +** ^This interface is used to retrieve runtime status information ** about a single [database connection]. ^The first argument is the ** database connection object to be interrogated. ^The second argument ** is an integer constant, taken from the set of ** [SQLITE_DBSTATUS options], that -** determines the parameter to interrogate. The set of +** determines the parameter to interrogate. The set of ** [SQLITE_DBSTATUS options] is likely ** to grow in future releases of SQLite. ** @@ -8503,7 +8555,7 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); ** checked out.
    )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(
    SQLITE_DBSTATUS_LOOKASIDE_HIT
    -**
    This parameter returns the number of malloc attempts that were +**
    This parameter returns the number of malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; ** the current value is always zero.)^ ** @@ -8528,7 +8580,7 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. ** -** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] +** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] ** ^(
    SQLITE_DBSTATUS_CACHE_USED_SHARED
    **
    This parameter is similar to DBSTATUS_CACHE_USED, except that if a ** pager cache is shared between two or more connections the bytes of heap @@ -8543,7 +8595,7 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(
    SQLITE_DBSTATUS_SCHEMA_USED
    **
    This parameter returns the approximate number of bytes of heap ** memory used to store the schema for all databases associated -** with the connection - main, temp, and any [ATTACH]-ed databases.)^ +** with the connection - main, temp, and any [ATTACH]-ed databases.)^ ** ^The full amount of memory used by the schemas is reported, even if the ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. @@ -8558,13 +8610,13 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); ** ** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(
    SQLITE_DBSTATUS_CACHE_HIT
    **
    This parameter returns the number of pager cache hits that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT ** is always 0. **
    ** ** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(
    SQLITE_DBSTATUS_CACHE_MISS
    **
    This parameter returns the number of pager cache misses that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS ** is always 0. **
    ** @@ -8622,7 +8674,7 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); ** statements. For example, if the number of table steps greatly exceeds ** the number of table searches or result rows, that would tend to indicate ** that the prepared statement is using a full table scan rather than -** an index. +** an index. ** ** ^(This interface is used to retrieve and reset counter values from ** a [prepared statement]. The first argument is the prepared statement @@ -8649,7 +8701,7 @@ int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** [[SQLITE_STMTSTATUS_FULLSCAN_STEP]]
    SQLITE_STMTSTATUS_FULLSCAN_STEP
    **
    ^This is the number of times that SQLite has stepped forward in ** a table as part of a full table scan. Large numbers for this counter -** may indicate opportunities for performance improvement through +** may indicate opportunities for performance improvement through ** careful use of indices.
    ** ** [[SQLITE_STMTSTATUS_SORT]]
    SQLITE_STMTSTATUS_SORT
    @@ -8667,14 +8719,14 @@ int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** [[SQLITE_STMTSTATUS_VM_STEP]]
    SQLITE_STMTSTATUS_VM_STEP
    **
    ^This is the number of virtual machine operations executed ** by the prepared statement if that number is less than or equal -** to 2147483647. The number of virtual machine operations can be +** to 2147483647. The number of virtual machine operations can be ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 ** then the value returned by this statement status code is undefined. ** ** [[SQLITE_STMTSTATUS_REPREPARE]]
    SQLITE_STMTSTATUS_REPREPARE
    **
    ^This is the number of times that the prepare statement has been -** automatically regenerated due to schema changes or changes to +** automatically regenerated due to schema changes or changes to ** [bound parameters] that might affect the query plan. ** ** [[SQLITE_STMTSTATUS_RUN]]
    SQLITE_STMTSTATUS_RUN
    @@ -8685,7 +8737,7 @@ int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** cycle. ** ** [[SQLITE_STMTSTATUS_FILTER_MISS]] -** [[SQLITE_STMTSTATUS_FILTER HIT]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] **
    SQLITE_STMTSTATUS_FILTER_HIT
    ** SQLITE_STMTSTATUS_FILTER_MISS
    **
    ^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join @@ -8746,15 +8798,15 @@ struct sqlite3_pcache_page { ** KEYWORDS: {page cache} ** ** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can -** register an alternative page cache implementation by passing in an +** register an alternative page cache implementation by passing in an ** instance of the sqlite3_pcache_methods2 structure.)^ -** In many applications, most of the heap memory allocated by +** In many applications, most of the heap memory allocated by ** SQLite is used for the page cache. -** By implementing a +** 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 +** 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 @@ -8767,19 +8819,19 @@ struct sqlite3_pcache_page { ** [sqlite3_config()] returns.)^ ** ** [[the xInit() page cache method]] -** ^(The xInit() method is called once for each effective +** ^(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_methods2.pArg value.)^ -** 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 +** 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() page cache method]] ** ^The xShutdown() method is called by [sqlite3_shutdown()]. -** It can be used to clean up +** It can be used to clean up ** any outstanding resources before process shutdown, if required. ** ^The xShutdown() method may be NULL. ** @@ -8798,7 +8850,7 @@ struct sqlite3_pcache_page { ** 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 always a power of two. ^The -** second parameter szExtra is a number of bytes of extra storage +** second parameter szExtra is a number of bytes of extra storage ** associated with each page cache entry. ^The szExtra parameter will ** a number less than 250. SQLite will use the ** extra szExtra bytes on each page to store metadata about the underlying @@ -8811,7 +8863,7 @@ struct sqlite3_pcache_page { ** 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, calls to xUnpin() on a cache with bPurgeable set to -** false will always have the "discard" flag set to true. +** false will always have the "discard" flag set to true. ** ^Hence, a cache created with bPurgeable false will ** never contain any unpinned pages. ** @@ -8826,12 +8878,12 @@ struct sqlite3_pcache_page { ** [[the xPagecount() page cache methods]] ** The xPagecount() method must return the number of pages currently ** stored in the cache, both pinned and unpinned. -** +** ** [[the xFetch() page cache methods]] -** The xFetch() method locates a page in the cache and returns a pointer to +** The xFetch() method locates a page in the cache and returns a pointer to ** an sqlite3_pcache_page object associated with that page, or a NULL pointer. ** The pBuf element of the returned sqlite3_pcache_page object will be a -** pointer to a buffer of szPage bytes used to store the content of a +** pointer to a buffer of szPage bytes used to store the content of a ** single database page. The pExtra element of sqlite3_pcache_page will be ** a pointer to the szExtra bytes of extra storage that SQLite has requested ** for each entry in the page cache. @@ -8870,8 +8922,8 @@ struct sqlite3_pcache_page { ** page cache implementation. ^The page cache implementation ** may choose to evict unpinned pages at any time. ** -** The cache must not perform any reference counting. A single -** call to xUnpin() unpins the page regardless of the number of prior calls +** 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(). ** ** [[the xRekey() page cache methods]] @@ -8911,7 +8963,7 @@ struct sqlite3_pcache_methods2 { int (*xPagecount)(sqlite3_pcache*); sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); - void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, + void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, unsigned oldKey, unsigned newKey); void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); void (*xDestroy)(sqlite3_pcache*); @@ -8956,7 +9008,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** ** The backup API copies the content of one database into another. ** It is useful either for creating backups of databases or -** for copying in-memory databases to or from persistent files. +** for copying in-memory databases to or from persistent files. ** ** See Also: [Using the SQLite Online Backup API] ** @@ -8967,36 +9019,36 @@ typedef struct sqlite3_backup sqlite3_backup; ** ^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: +** +** ^(To perform a backup operation: **
      **
    1. sqlite3_backup_init() is called once to initialize the -** backup, -**
    2. sqlite3_backup_step() is called one or more times to transfer +** backup, +**
    3. sqlite3_backup_step() is called one or more times to transfer ** the data between the two databases, and finally -**
    4. sqlite3_backup_finish() is called to release all resources -** associated with the backup operation. +**
    5. sqlite3_backup_finish() is called to release all resources +** associated with the backup operation. **
    )^ ** There should be exactly one call to sqlite3_backup_finish() for each ** successful call to sqlite3_backup_init(). ** ** [[sqlite3_backup_init()]] sqlite3_backup_init() ** -** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the -** [database connection] associated with the destination database +** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the +** [database connection] associated with the destination database ** and the database name, respectively. ** ^The database name is "main" for the main database, "temp" for the ** temporary database, or the name specified after the AS keyword in ** an [ATTACH] statement for an attached database. -** ^The S and M arguments passed to +** ^The S and M arguments passed to ** sqlite3_backup_init(D,N,S,M) identify the [database connection] ** and database name of the source database, respectively. ** ^The source and destination [database connections] (parameters S and D) ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** -** ^A call to sqlite3_backup_init() will fail, returning NULL, if -** there is already a read or read-write transaction open on the +** ^A call to sqlite3_backup_init() will fail, returning NULL, if +** there is already a read or read-write transaction open on the ** destination database. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is @@ -9008,14 +9060,14 @@ typedef struct sqlite3_backup sqlite3_backup; ** ^A successful call to sqlite3_backup_init() returns a pointer to an ** [sqlite3_backup] object. ** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and -** sqlite3_backup_finish() functions to perform the specified backup +** sqlite3_backup_finish() functions to perform the specified backup ** operation. ** ** [[sqlite3_backup_step()]] sqlite3_backup_step() ** -** ^Function sqlite3_backup_step(B,N) will copy up to N pages between +** ^Function sqlite3_backup_step(B,N) will copy up to N pages between ** the source and destination databases specified by [sqlite3_backup] object B. -** ^If N is negative, all remaining source pages are copied. +** ^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 returns [SQLITE_OK]. ** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages @@ -9037,8 +9089,8 @@ typedef struct sqlite3_backup sqlite3_backup; ** ** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then ** the [sqlite3_busy_handler | busy-handler function] -** is invoked (if one is specified). ^If the -** busy-handler returns non-zero before the lock is available, then +** is invoked (if one is specified). ^If the +** busy-handler returns non-zero before the lock is available, then ** [SQLITE_BUSY] is returned to the caller. ^In this case the call to ** sqlite3_backup_step() can be retried later. ^If the source ** [database connection] @@ -9046,15 +9098,15 @@ typedef struct sqlite3_backup sqlite3_backup; ** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this ** case the call to sqlite3_backup_step() can be retried later on. ^(If ** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or -** [SQLITE_READONLY] is returned, then -** there is no point in retrying the call to sqlite3_backup_step(). These -** errors are considered fatal.)^ The application must accept -** that the backup operation has failed and pass the backup operation handle +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal.)^ The application must accept +** that the backup operation has failed and pass the backup operation handle ** to the sqlite3_backup_finish() to release associated resources. ** ** ^The first call to sqlite3_backup_step() obtains an exclusive lock -** on the destination file. ^The exclusive lock is not released until either -** sqlite3_backup_finish() is called or the backup operation is complete +** on the destination file. ^The exclusive lock is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete ** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to ** sqlite3_backup_step() obtains a [shared lock] on the source database that ** lasts for the duration of the sqlite3_backup_step() call. @@ -9063,18 +9115,18 @@ typedef struct sqlite3_backup sqlite3_backup; ** through the backup process. ^If the source database is modified by an ** external process or via a database connection other than the one being ** used by the backup operation, then the backup will be automatically -** restarted by the next call to sqlite3_backup_step(). ^If the source +** restarted by the next call to sqlite3_backup_step(). ^If the source ** database is modified by the using the same database connection as is used ** by the backup operation, then the backup database is automatically ** updated at the same time. ** ** [[sqlite3_backup_finish()]] sqlite3_backup_finish() ** -** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the ** application wishes to abandon the backup operation, the application ** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish(). ** ^The sqlite3_backup_finish() interfaces releases all -** resources associated with the [sqlite3_backup] object. +** resources associated with the [sqlite3_backup] object. ** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any ** active write-transaction on the destination database is rolled back. ** The [sqlite3_backup] object is invalid @@ -9114,8 +9166,8 @@ typedef struct sqlite3_backup sqlite3_backup; ** connections, then the source database connection may be used concurrently ** from within other threads. ** -** However, the application must guarantee that the destination -** [database connection] is not passed to any other API (by any thread) after +** However, the application must guarantee that the destination +** [database connection] is not passed to any other API (by any thread) after ** sqlite3_backup_init() is called and before the corresponding call to ** sqlite3_backup_finish(). SQLite does not currently check to see ** if the application incorrectly accesses the destination [database connection] @@ -9126,11 +9178,11 @@ typedef struct sqlite3_backup sqlite3_backup; ** If running in [shared cache mode], the application must ** guarantee that the shared cache used by the destination database ** is not accessed while the backup is running. In practice this means -** that the application must guarantee that the disk file being +** that the application must guarantee that the disk file being ** backed up to is not accessed by any connection within the process, ** not just the specific connection that was passed to sqlite3_backup_init(). ** -** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** The [sqlite3_backup] object itself is partially threadsafe. Multiple ** threads may safely make multiple concurrent calls to sqlite3_backup_step(). ** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** APIs are not strictly speaking threadsafe. If they are invoked at the @@ -9155,8 +9207,8 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** ^When running in shared-cache mode, a database operation may fail with ** an [SQLITE_LOCKED] error if the required locks on the shared-cache or ** individual tables within the shared-cache cannot be obtained. See -** [SQLite Shared-Cache Mode] for a description of shared-cache locking. -** ^This API may be used to register a callback that SQLite will invoke +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** ^This API may be used to register a callback that SQLite will invoke ** when the connection currently holding the required lock relinquishes it. ** ^This API is only available if the library was compiled with the ** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. @@ -9164,14 +9216,14 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** See Also: [Using the SQLite Unlock Notification Feature]. ** ** ^Shared-cache locks are released when a database connection concludes -** its current transaction, either by committing it or rolling it back. +** its current transaction, either by committing it or rolling it back. ** ** ^When a connection (known as the blocked connection) fails to obtain a ** shared-cache lock and SQLITE_LOCKED is returned to the caller, the ** identity of the database connection (the blocking connection) that -** has locked the required resource is stored internally. ^After an +** has locked the required resource is stored internally. ^After an ** application receives an SQLITE_LOCKED error, it may call the -** sqlite3_unlock_notify() method with the blocked connection handle as +** sqlite3_unlock_notify() method with the blocked connection handle as ** the first argument to register for a callback that will be invoked ** when the blocking connections current transaction is concluded. ^The ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] @@ -9185,16 +9237,16 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** ^If the blocked connection is attempting to obtain a write-lock on a ** shared-cache table, and more than one other connection currently holds -** a read-lock on the same table, then SQLite arbitrarily selects one of +** a read-lock on the same table, then SQLite arbitrarily selects one of ** the other connections to use as the blocking connection. ** -** ^(There may be at most one unlock-notify callback registered by a +** ^(There may be at most one unlock-notify callback registered by a ** blocked connection. If sqlite3_unlock_notify() is called when the ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing -** unlock-notify callback is canceled. ^The blocked connections -** unlock-notify callback may also be canceled by closing the blocked +** unlock-notify callback is cancelled. ^The blocked connections +** unlock-notify callback may also be cancelled by closing the blocked ** connection using [sqlite3_close()]. ** ** The unlock-notify callback is not reentrant. If an application invokes @@ -9206,7 +9258,7 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** Callback Invocation Details ** -** When an unlock-notify callback is registered, the application provides a +** When an unlock-notify callback is registered, the application provides a ** single void* pointer that is passed to the callback when it is invoked. ** However, the signature of the callback function allows SQLite to pass ** it an array of void* context pointers. The first argument passed to @@ -9219,12 +9271,12 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** same callback function, then instead of invoking the callback function ** multiple times, it is invoked once with the set of void* context pointers ** specified by the blocked connections bundled together into an array. -** This gives the application an opportunity to prioritize any actions +** This gives the application an opportunity to prioritize any actions ** related to the set of unblocked database connections. ** ** Deadlock Detection ** -** Assuming that after registering for an unlock-notify callback a +** Assuming that after registering for an unlock-notify callback a ** database waits for the callback to be issued before taking any further ** action (a reasonable assumption), then using this API may cause the ** application to deadlock. For example, if connection X is waiting for @@ -9247,7 +9299,7 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** The "DROP TABLE" Exception ** -** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost ** always appropriate to call sqlite3_unlock_notify(). There is however, ** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, ** SQLite checks if there are any currently executing SELECT statements @@ -9260,7 +9312,7 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** One way around this problem is to check the extended error code returned ** by an sqlite3_step() call. ^(If there is a blocking connection, then the ** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in -** the special "DROP TABLE/INDEX" case, the extended error code is just +** the special "DROP TABLE/INDEX" case, the extended error code is just ** SQLITE_LOCKED.)^ */ int sqlite3_unlock_notify( @@ -9351,8 +9403,8 @@ void sqlite3_log(int iErrCode, const char *zFormat, ...); ** ^The [sqlite3_wal_hook()] function is used to register a callback that ** is invoked each time data is committed to a database in wal mode. ** -** ^(The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released)^, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked @@ -9371,7 +9423,7 @@ void sqlite3_log(int iErrCode, const char *zFormat, ...); ** that does not correspond to any valid SQLite error code, the results ** are undefined. ** -** A single database handle may have at most a single write-ahead log callback +** A single database handle may have at most a single write-ahead log callback ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any ** previously registered write-ahead log callback. ^The return value is ** a copy of the third parameter from the previous call, if any, or 0. @@ -9380,7 +9432,7 @@ void sqlite3_log(int iErrCode, const char *zFormat, ...); ** overwrite any prior [sqlite3_wal_hook()] settings. */ void *sqlite3_wal_hook( - sqlite3*, + sqlite3*, int(*)(void *,sqlite3*,const char*,int), void* ); @@ -9393,7 +9445,7 @@ void *sqlite3_wal_hook( ** [sqlite3_wal_hook()] that causes any database on [database connection] D ** to automatically [checkpoint] ** after committing a transaction if there are N or -** more frames in the [write-ahead log] file. ^Passing zero or +** more frames in the [write-ahead log] file. ^Passing zero or ** a negative value as the nFrame parameter disables automatic ** checkpoints entirely. ** @@ -9423,7 +9475,7 @@ int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); ** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to ** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ ** -** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the ** [write-ahead log] for database X on [database connection] D to be ** transferred into the database file and for the write-ahead log to ** be reset. See the [checkpointing] documentation for addition @@ -9449,10 +9501,10 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** **
    **
    SQLITE_CHECKPOINT_PASSIVE
    -** ^Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish, then sync the database file if all frames +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames ** in the log were checkpointed. ^The [busy-handler callback] -** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. ** ^On the other hand, passive mode might leave the checkpoint unfinished ** if there are concurrent readers or writers. ** @@ -9466,9 +9518,9 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** **
    SQLITE_CHECKPOINT_RESTART
    ** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition -** that after checkpointing the log file it blocks (calls the +** that after checkpointing the log file it blocks (calls the ** [busy-handler callback]) -** until all readers are reading from the database file only. ^This ensures +** until all readers are reading from the database file only. ^This ensures ** that the next writer will restart the log file from the beginning. ** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new ** database writer attempts while it is pending, but does not impede readers. @@ -9490,31 +9542,31 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. ** ** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If -** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a ** busy-handler configured, it will not be invoked in this case. ** -** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the ** exclusive "writer" lock on the database file. ^If the writer lock cannot be ** obtained immediately, and a busy-handler is configured, it is invoked and ** the writer lock retried until either the busy-handler returns 0 or the lock ** is successfully obtained. ^The busy-handler is also invoked while waiting for ** database readers as described above. ^If the busy-handler returns 0 before ** the writer lock is obtained or while waiting for database readers, the -** checkpoint operation proceeds from that point in the same way as -** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible ** without blocking any further. ^SQLITE_BUSY is returned in this case. ** ** ^If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases [attached] to +** specified operation is attempted on all WAL databases [attached] to ** [database connection] db. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. ^If -** an SQLITE_BUSY error is encountered when processing one or more of the -** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned at the end. ^If any other -** error occurs while processing an attached database, processing is abandoned -** and the error code is returned to the caller immediately. ^If no error -** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached ** databases, SQLITE_OK is returned. ** ** ^If database zDb is the name of an attached database that is not in WAL @@ -9574,7 +9626,7 @@ int sqlite3_vtab_config(sqlite3*, int op, ...); /* ** CAPI3REF: Virtual Table Configuration Options -** KEYWORDS: {virtual table configuration options} +** KEYWORDS: {virtual table configuration options} ** KEYWORDS: {virtual table configuration option} ** ** These macros define the various options to the @@ -9597,27 +9649,27 @@ int sqlite3_vtab_config(sqlite3*, int op, ...); ** If X is non-zero, then the virtual table implementation guarantees ** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before ** any modifications to internal or persistent data structures have been made. -** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite +** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite ** is able to roll back a statement or database transaction, and abandon -** or continue processing the current SQL statement as appropriate. +** or continue processing the current SQL statement as appropriate. ** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns ** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode ** had been ABORT. ** ** Virtual table implementations that are required to handle OR REPLACE -** must do so within the [xUpdate] method. If a call to the -** [sqlite3_vtab_on_conflict()] function indicates that the current ON -** CONFLICT policy is REPLACE, the virtual table implementation should +** must do so within the [xUpdate] method. If a call to the +** [sqlite3_vtab_on_conflict()] function indicates that the current ON +** CONFLICT policy is REPLACE, the virtual table implementation should ** silently replace the appropriate rows within the xUpdate callback and ** return SQLITE_OK. Or, if this is not possible, it may return -** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT +** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT ** constraint handling. **
    ** ** [[SQLITE_VTAB_DIRECTONLY]]
    SQLITE_VTAB_DIRECTONLY
    **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the -** the [xConnect] or [xCreate] methods of a [virtual table] implmentation +** the [xConnect] or [xCreate] methods of a [virtual table] implementation ** prohibits that virtual table from being used from within triggers and ** views. **
    @@ -9797,7 +9849,7 @@ int sqlite3_vtab_distinct(sqlite3_index_info*); /* ** CAPI3REF: Identify and handle IN constraints in xBestIndex ** -** This interface may only be used from within an +** This interface may only be used from within an ** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. ** The result of invoking this interface from any other context is ** undefined and probably harmful. @@ -9807,7 +9859,7 @@ int sqlite3_vtab_distinct(sqlite3_index_info*); ** communicated to the xBestIndex method as a ** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use ** this constraint, it must set the corresponding -** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under ** the usual mode of handling IN operators, SQLite generates [bytecode] ** that invokes the [xFilter|xFilter() method] once for each value ** on the right-hand side of the IN operator.)^ Thus the virtual table @@ -9856,7 +9908,7 @@ int sqlite3_vtab_distinct(sqlite3_index_info*); **
  • The last call to sqlite3_vtab_in(P,N,F) for which F was ** non-negative had F>=1. ** )^ -** +** ** ^If either or both of the conditions above are false, then SQLite uses ** the traditional one-at-a-time processing strategy for the IN constraint. ** ^If both conditions are true, then the argvIndex-th parameter to the @@ -9931,7 +9983,7 @@ int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); ** that constraint if the right-hand operand is known. ^If the ** right-hand operand is not known, then *V is set to a NULL pointer. ** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if -** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) ** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th ** constraint is not available. ^The sqlite3_vtab_rhs_value() interface ** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if @@ -10021,14 +10073,14 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** [[SQLITE_SCANSTAT_SELECTID]]

    SQLITE_SCANSTAT_SELECTID
    **
    ^The "int" variable pointed to by the V parameter will be set to the ** id for the X-th query plan element. The id value is unique within the -** statement. The select-id is the same value as is output in the first +** statement. The select-id is the same value as is output in the first ** column of an [EXPLAIN QUERY PLAN] query. ** ** [[SQLITE_SCANSTAT_PARENTID]]
    SQLITE_SCANSTAT_PARENTID
    **
    The "int" variable pointed to by the V parameter will be set to the ** the id of the parent of the current query element, if applicable, or ** to zero if the query element has no parent. This is the same value as -** returned in the second column of an [EXPLAIN QUERY PLAN] query. +** returned in the second column of an [EXPLAIN QUERY PLAN] query. ** ** [[SQLITE_SCANSTAT_NCYCLE]]
    SQLITE_SCANSTAT_NCYCLE
    **
    The sqlite3_int64 output value is set to the number of cycles, @@ -10062,23 +10114,23 @@ int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); ** ** The "iScanStatusOp" parameter determines which status information to return. ** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior -** of this interface is undefined. ^The requested measurement is written into +** of this interface is undefined. ^The requested measurement is written into ** a variable pointed to by the "pOut" parameter. ** ** The "flags" parameter must be passed a mask of flags. At present only ** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX -** is specified, then status information is available for all elements +** is specified, then status information is available for all elements ** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If ** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements ** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of ** the EXPLAIN QUERY PLAN output) are available. Invoking API -** sqlite3_stmt_scanstatus() is equivalent to calling +** sqlite3_stmt_scanstatus() is equivalent to calling ** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. -** +** ** Parameter "idx" identifies the specific query element to retrieve statistics ** for. Query elements are numbered starting from zero. A value of -1 may be ** to query for statistics regarding the entire query. ^If idx is out of range -** - less than -1 or greater than or equal to the total number of query +** - less than -1 or greater than or equal to the total number of query ** elements used to implement the statement - a non-zero value is returned and ** the variable that pOut points to is unchanged. ** @@ -10089,14 +10141,14 @@ int sqlite3_stmt_scanstatus( int idx, /* Index of loop to report on */ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ void *pOut /* Result written here */ -); +); int sqlite3_stmt_scanstatus_v2( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ int idx, /* Index of loop to report on */ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ int flags, /* Mask of flags defined below */ void *pOut /* Result written here */ -); +); /* ** CAPI3REF: Prepared Statement Scan Status @@ -10121,15 +10173,15 @@ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); ** ** ^If a write-transaction is open on [database connection] D when the ** [sqlite3_db_cacheflush(D)] interface invoked, any dirty -** pages in the pager-cache that are not currently in use are written out +** pages in the pager-cache that are not currently in use are written out ** to disk. A dirty page may be in use if a database cursor created by an ** active SQL statement is reading from it, or if it is page 1 of a database ** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)] ** interface flushes caches for all schemas - "main", "temp", and ** any [attached] databases. ** -** ^If this function needs to obtain extra database locks before dirty pages -** can be flushed to disk, it does so. ^If those locks cannot be obtained +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained ** immediately and there is a busy-handler callback configured, it is invoked ** in the usual manner. ^If the required lock still cannot be obtained, then ** the database is skipped and an attempt made to flush any dirty pages @@ -10177,16 +10229,16 @@ int sqlite3_db_cacheflush(sqlite3*); ** kind of update operation that is about to occur. ** ^(The fourth parameter to the preupdate callback is the name of the ** database within the database connection that is being modified. This -** will be "main" for the main database or "temp" for TEMP tables or +** will be "main" for the main database or "temp" for TEMP tables or ** the name given after the AS keyword in the [ATTACH] statement for attached ** databases.)^ ** ^The fifth parameter to the preupdate callback is the name of the ** table that is being modified. ** ** For an UPDATE or DELETE operation on a [rowid table], the sixth -** parameter passed to the preupdate callback is the initial [rowid] of the +** parameter passed to the preupdate callback is the initial [rowid] of the ** row being modified or deleted. For an INSERT operation on a rowid table, -** or any operation on a WITHOUT ROWID table, the value of the sixth +** or any operation on a WITHOUT ROWID table, the value of the sixth ** parameter is undefined. For an INSERT or UPDATE on a rowid table the ** seventh parameter is the final rowid value of the row being inserted ** or updated. The value of the seventh parameter passed to the callback @@ -10229,14 +10281,14 @@ int sqlite3_db_cacheflush(sqlite3*); ** ** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate ** callback was invoked as a result of a direct insert, update, or delete -** operation; or 1 for inserts, updates, or deletes invoked by top-level +** operation; or 1 for inserts, updates, or deletes invoked by top-level ** triggers; or 2 for changes resulting from triggers called by top-level ** triggers; and so forth. ** ** When the [sqlite3_blob_write()] API is used to update a blob column, ** the pre-update hook is invoked with SQLITE_DELETE. This is because the ** in this case the new values are not available. In this case, when a -** callback made with op==SQLITE_DELETE is actuall a write using the +** callback made with op==SQLITE_DELETE is actually a write using the ** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns ** the index of the column being written. In other cases, where the ** pre-update hook is being invoked for some other reason, including a @@ -10274,7 +10326,7 @@ int sqlite3_preupdate_blobwrite(sqlite3 *); ** The return value is OS-dependent. For example, on unix systems, after ** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be ** called to get back the underlying "errno" that caused the problem, such -** as ENOSPC, EAUTH, EISDIR, and so forth. +** as ENOSPC, EAUTH, EISDIR, and so forth. */ int sqlite3_system_errno(sqlite3*); @@ -10312,12 +10364,12 @@ typedef struct sqlite3_snapshot { ** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly ** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. ** If there is not already a read-transaction open on schema S when -** this function is called, one is opened automatically. +** this function is called, one is opened automatically. ** ** The following must be true for this function to succeed. If any of ** the following statements are false when sqlite3_snapshot_get() is ** called, SQLITE_ERROR is returned. The final value of *P is undefined -** in this case. +** in this case. ** **
      **
    • The database handle must not be in [autocommit mode]. @@ -10329,13 +10381,13 @@ typedef struct sqlite3_snapshot { ** **
    • One or more transactions must have been written to the current wal ** file since it was created on disk (by any connection). This means -** that a snapshot cannot be taken on a wal mode database with no wal +** that a snapshot cannot be taken on a wal mode database with no wal ** file immediately after it is first opened. At least one transaction ** must be written to it first. **
    ** ** This function may also return SQLITE_NOMEM. If it is called with the -** database handle in autocommit mode but fails for some other reason, +** database handle in autocommit mode but fails for some other reason, ** whether or not a read transaction is opened on schema S is undefined. ** ** The [sqlite3_snapshot] object returned from a successful call to @@ -10355,38 +10407,38 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( ** CAPI3REF: Start a read transaction on an historical snapshot ** METHOD: sqlite3_snapshot ** -** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read -** transaction or upgrades an existing one for schema S of -** [database connection] D such that the read transaction refers to -** historical [snapshot] P, rather than the most recent change to the -** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK +** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read +** transaction or upgrades an existing one for schema S of +** [database connection] D such that the read transaction refers to +** historical [snapshot] P, rather than the most recent change to the +** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK ** on success or an appropriate [error code] if it fails. ** -** ^In order to succeed, the database connection must not be in +** ^In order to succeed, the database connection must not be in ** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there ** is already a read transaction open on schema S, then the database handle ** must have no active statements (SELECT statements that have been passed -** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). +** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). ** SQLITE_ERROR is returned if either of these conditions is violated, or ** if schema S does not exist, or if the snapshot object is invalid. ** ** ^A call to sqlite3_snapshot_open() will fail to open if the specified -** snapshot has been overwritten by a [checkpoint]. In this case +** snapshot has been overwritten by a [checkpoint]. In this case ** SQLITE_ERROR_SNAPSHOT is returned. ** -** If there is already a read transaction open when this function is +** If there is already a read transaction open when this function is ** invoked, then the same read transaction remains open (on the same ** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT ** is returned. If another error code - for example SQLITE_PROTOCOL or an ** SQLITE_IOERR error code - is returned, then the final state of the -** read transaction is undefined. If SQLITE_OK is returned, then the +** read transaction is undefined. If SQLITE_OK is returned, then the ** read transaction is now open on database snapshot P. ** ** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the ** database connection D does not know that the database file for ** schema S is in [WAL mode]. A database connection might not know ** that the database file is in [WAL mode] if there has been no prior -** I/O on that database connection, or if the database entered [WAL mode] +** I/O on that database connection, or if the database entered [WAL mode] ** after the most recent I/O on the database connection.)^ ** (Hint: Run "[PRAGMA application_id]" against a newly opened ** database connection in order to make it ready to use snapshots.) @@ -10418,17 +10470,17 @@ SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); ** METHOD: sqlite3_snapshot ** ** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages -** of two valid snapshot handles. +** of two valid snapshot handles. ** -** If the two snapshot handles are not associated with the same database -** file, the result of the comparison is undefined. +** If the two snapshot handles are not associated with the same database +** file, the result of the comparison is undefined. ** ** Additionally, the result of the comparison is only valid if both of the ** snapshot handles were obtained by calling sqlite3_snapshot_get() since the ** last time the wal file was deleted. The wal file is deleted when the ** database is changed back to rollback mode or when the number of database -** clients drops to zero. If either snapshot handle was obtained before the -** wal file was last deleted, the value returned by this function +** clients drops to zero. If either snapshot handle was obtained before the +** wal file was last deleted, the value returned by this function ** is undefined. ** ** Otherwise, this API returns a negative value if P1 refers to an older @@ -10493,7 +10545,7 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); ** representation of the database will usually only exist if there has ** been a prior call to [sqlite3_deserialize(D,S,...)] with the same ** values of D and S. -** The size of the database is written into *P even if the +** The size of the database is written into *P even if the ** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy ** of the database exists. ** @@ -10530,7 +10582,7 @@ unsigned char *sqlite3_serialize( /* ** CAPI3REF: Deserialize a database ** -** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the +** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the ** [database connection] D to disconnect from database S and then ** reopen S as an in-memory database based on the serialization contained ** in P. The serialized database P is N bytes in size. M is the size of @@ -10553,7 +10605,7 @@ unsigned char *sqlite3_serialize( ** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the ** function returns SQLITE_ERROR. ** -** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the +** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. ** diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 19e030028..711638099 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -361,6 +361,8 @@ struct sqlite3_api_routines { int (*value_encoding)(sqlite3_value*); /* Version 3.41.0 and later */ int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); }; /* @@ -689,6 +691,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_value_encoding sqlite3_api->value_encoding /* Version 3.41.0 and later */ #define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 2c893770b..c2a9f855a 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -25,20 +25,20 @@ ** used on lines of code that actually ** implement parts of coverage testing. ** -** OPTIMIZATION-IF-TRUE - This branch is allowed to alway be false +** OPTIMIZATION-IF-TRUE - This branch is allowed to always be false ** and the correct answer is still obtained, ** though perhaps more slowly. ** -** OPTIMIZATION-IF-FALSE - This branch is allowed to alway be true +** OPTIMIZATION-IF-FALSE - This branch is allowed to always be true ** and the correct answer is still obtained, ** though perhaps more slowly. ** ** PREVENTS-HARMLESS-OVERREAD - This branch prevents a buffer overread ** that would be harmless and undetectable -** if it did occur. +** if it did occur. ** ** In all cases, the special comment must be enclosed in the usual -** slash-asterisk...asterisk-slash comment marks, with no spaces between the +** slash-asterisk...asterisk-slash comment marks, with no spaces between the ** asterisks and the comment text. */ @@ -182,7 +182,7 @@ #endif /* Optionally #include a user-defined header, whereby compilation options -** may be set prior to where they take effect, but after platform setup. +** may be set prior to where they take effect, but after platform setup. ** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include ** file. */ @@ -230,7 +230,7 @@ #ifndef __has_extension # define __has_extension(x) 0 /* compatibility with non-clang compilers */ #endif -#if GCC_VERSION>=4007000 || __has_extension(c_atomic) +#if GCC_VERSION>=4007000 || __has_extension(c_atomic) # define SQLITE_ATOMIC_INTRINSICS 1 # define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED) # define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED) @@ -722,7 +722,7 @@ #endif /* -** The compile-time options SQLITE_MMAP_READWRITE and +** The compile-time options SQLITE_MMAP_READWRITE and ** SQLITE_ENABLE_BATCH_ATOMIC_WRITE are not compatible with one another. ** You must choose one or the other (or neither) but not both. */ @@ -889,8 +889,31 @@ typedef INT16_TYPE LogEst; ** the end of buffer S. This macro returns true if P points to something ** contained within the buffer S. */ -#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) +#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) +/* +** P is one byte past the end of a large buffer. Return true if a span of bytes +** between S..E crosses the end of that buffer. In other words, return true +** if the sub-buffer S..E-1 overflows the buffer whose last byte is P-1. +** +** S is the start of the span. E is one byte past the end of end of span. +** +** P +** |-----------------| FALSE +** |-------| +** S E +** +** P +** |-----------------| +** |-------| TRUE +** S E +** +** P +** |-----------------| +** |-------| FALSE +** S E +*/ +#define SQLITE_OVERFLOW(P,S,E) (((uptr)(S)<(uptr)(P))&&((uptr)(E)>(uptr)(P))) /* ** Macros to determine whether the machine is big or little endian, @@ -1124,7 +1147,7 @@ struct BusyHandler { /* ** Name of table that holds the database schema. ** -** The PREFERRED names are used whereever possible. But LEGACY is also +** The PREFERRED names are used wherever possible. But LEGACY is also ** used for backwards compatibility. ** ** 1. Queries can use either the PREFERRED or the LEGACY names @@ -1238,6 +1261,7 @@ typedef struct Schema Schema; typedef struct Expr Expr; typedef struct ExprList ExprList; typedef struct FKey FKey; +typedef struct FpDecode FpDecode; typedef struct FuncDestructor FuncDestructor; typedef struct FuncDef FuncDef; typedef struct FuncDefHash FuncDefHash; @@ -1256,6 +1280,7 @@ typedef struct Parse Parse; typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; typedef struct PrintfArguments PrintfArguments; +typedef struct RCStr RCStr; typedef struct RenameToken RenameToken; typedef struct Returning Returning; typedef struct RowSet RowSet; @@ -1342,7 +1367,7 @@ typedef int VList; /* ** Default synchronous levels. ** -** Note that (for historcal reasons) the PAGER_SYNCHRONOUS_* macros differ +** Note that (for historical reasons) the PAGER_SYNCHRONOUS_* macros differ ** from the SQLITE_DEFAULT_SYNCHRONOUS value by 1. ** ** PAGER_SYNCHRONOUS DEFAULT_SYNCHRONOUS @@ -1381,7 +1406,7 @@ struct Db { ** An instance of the following structure stores a database schema. ** ** Most Schema objects are associated with a Btree. The exception is -** the Schema for the TEMP databaes (sqlite3.aDb[1]) which is free-standing. +** the Schema for the TEMP database (sqlite3.aDb[1]) which is free-standing. ** In shared cache mode, a single Schema object can be shared by multiple ** Btrees that refer to the same underlying BtShared object. ** @@ -1492,7 +1517,7 @@ struct Lookaside { LookasideSlot *pInit; /* List of buffers not previously used */ LookasideSlot *pFree; /* List of available buffers */ #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE - LookasideSlot *pSmallInit; /* List of small buffers not prediously used */ + LookasideSlot *pSmallInit; /* List of small buffers not previously used */ LookasideSlot *pSmallFree; /* List of available small buffers */ void *pMiddle; /* First byte past end of full-size buffers and ** the first byte of LOOKASIDE_SMALL buffers */ @@ -1509,7 +1534,7 @@ struct LookasideSlot { #define EnableLookaside db->lookaside.bDisable--;\ db->lookaside.sz=db->lookaside.bDisable?0:db->lookaside.szTrue -/* Size of the smaller allocations in two-size lookside */ +/* Size of the smaller allocations in two-size lookaside */ #ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE # define LOOKASIDE_SMALL 0 #else @@ -1848,6 +1873,7 @@ struct sqlite3 { #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ #define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ #define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */ +#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -1930,6 +1956,7 @@ struct FuncDestructor { ** SQLITE_FUNC_ANYORDER == NC_OrderAgg == SF_OrderByReqd ** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG ** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG +** SQLITE_FUNC_BYTELEN == OPFLAG_BYTELENARG ** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API ** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API ** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS -- opposite meanings!!! @@ -1937,7 +1964,7 @@ struct FuncDestructor { ** ** Note that even though SQLITE_FUNC_UNSAFE and SQLITE_INNOCUOUS have the ** same bit value, their meanings are inverted. SQLITE_FUNC_UNSAFE is -** used internally and if set means tha the function has side effects. +** used internally and if set means that the function has side effects. ** SQLITE_INNOCUOUS is used by application code and means "not unsafe". ** See multiple instances of tag-20230109-1. */ @@ -1948,6 +1975,7 @@ struct FuncDestructor { #define SQLITE_FUNC_NEEDCOLL 0x0020 /* sqlite3GetFuncCollSeq() might be called*/ #define SQLITE_FUNC_LENGTH 0x0040 /* Built-in length() function */ #define SQLITE_FUNC_TYPEOF 0x0080 /* Built-in typeof() function */ +#define SQLITE_FUNC_BYTELEN 0x00c0 /* Built-in octet_length() function */ #define SQLITE_FUNC_COUNT 0x0100 /* Built-in count(*) aggregate */ /* 0x0200 -- available for reuse */ #define SQLITE_FUNC_UNLIKELY 0x0400 /* Built-in unlikely() function */ @@ -1970,7 +1998,7 @@ struct FuncDestructor { #define INLINEFUNC_coalesce 0 #define INLINEFUNC_implies_nonnull_row 1 #define INLINEFUNC_expr_implies_expr 2 -#define INLINEFUNC_expr_compare 3 +#define INLINEFUNC_expr_compare 3 #define INLINEFUNC_affinity 4 #define INLINEFUNC_iif 5 #define INLINEFUNC_sqlite_offset 6 @@ -2019,7 +2047,7 @@ struct FuncDestructor { ** PURE_DATE(zName, nArg, iArg, bNC, xFunc) ** Used for "pure" date/time functions, this macro is like DFUNCTION ** except that it does set the SQLITE_FUNC_CONSTANT flags. iArg is -** ignored and the user-data for these functions is set to an +** ignored and the user-data for these functions is set to an ** arbitrary non-NULL pointer. The bNC parameter is not used. ** ** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) @@ -2527,7 +2555,7 @@ struct FKey { ** foreign key. ** ** The OE_Default value is a place holder that means to use whatever -** conflict resolution algorthm is required from context. +** conflict resolution algorithm is required from context. ** ** The following symbolic values are used to record which type ** of conflict resolution action to take. @@ -2814,7 +2842,7 @@ struct AggInfo { /* ** Macros to compute aCol[] and aFunc[] register numbers. ** -** These macros should not be used prior to the call to +** These macros should not be used prior to the call to ** assignAggregateRegisters() that computes the value of pAggInfo->iFirstReg. ** The assert()s that are part of this macro verify that constraint. */ @@ -2941,7 +2969,7 @@ struct Expr { ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old ** EP_Unlikely: 134217728 times likelihood - ** TK_IN: ephemerial table holding RHS + ** TK_IN: ephemeral table holding RHS ** TK_SELECT_COLUMN: Number of columns on the LHS ** TK_SELECT: 1st register of result vector */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. @@ -3023,6 +3051,8 @@ struct Expr { */ #define ExprUseUToken(E) (((E)->flags&EP_IntValue)==0) #define ExprUseUValue(E) (((E)->flags&EP_IntValue)!=0) +#define ExprUseWOfst(E) (((E)->flags&(EP_InnerON|EP_OuterON))==0) +#define ExprUseWJoin(E) (((E)->flags&(EP_InnerON|EP_OuterON))!=0) #define ExprUseXList(E) (((E)->flags&EP_xIsSelect)==0) #define ExprUseXSelect(E) (((E)->flags&EP_xIsSelect)!=0) #define ExprUseYTab(E) (((E)->flags&(EP_WinFunc|EP_Subrtn))==0) @@ -3211,7 +3241,7 @@ struct SrcItem { unsigned notCte :1; /* This item may not match a CTE */ unsigned isUsing :1; /* u3.pUsing is valid */ unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ - unsigned isSynthUsing :1; /* u3.pUsing is synthensized from NATURAL */ + unsigned isSynthUsing :1; /* u3.pUsing is synthesized from NATURAL */ unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ } fg; int iCursor; /* The VDBE cursor number used to access this table */ @@ -3376,7 +3406,7 @@ struct NameContext { ** conflict-target clause.) The pUpsertTargetWhere is the optional ** WHERE clause used to identify partial unique indexes. ** -** pUpsertSet is the list of column=expr terms of the UPDATE statement. +** pUpsertSet is the list of column=expr terms of the UPDATE statement. ** The pUpsertSet field is NULL for a ON CONFLICT DO NOTHING. The ** pUpsertWhere is the WHERE clause for the UPDATE and is NULL if the ** WHERE clause is omitted. @@ -3747,6 +3777,9 @@ struct Parse { int regRoot; /* Register holding root page number for new objects */ int nMaxArg; /* Max args passed to user function by sub-program */ int nSelect; /* Number of SELECT stmts. Counter for Select.selId */ +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */ +#endif #ifndef SQLITE_OMIT_SHARED_CACHE int nTableLock; /* Number of locks in aTableLock */ TableLock *aTableLock; /* Required table locks for shared-cache mode */ @@ -3760,12 +3793,9 @@ struct Parse { int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */ Returning *pReturning; /* The RETURNING clause */ } u1; - u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u32 oldmask; /* Mask of old.* columns referenced */ u32 newmask; /* Mask of new.* columns referenced */ -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK - u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */ -#endif + LogEst nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ u8 bReturning; /* Coding a RETURNING trigger */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ @@ -3889,6 +3919,7 @@ struct AuthContext { #define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */ #define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */ #define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */ +#define OPFLAG_BYTELENARG 0xc0 /* OP_Column only for octet_length() */ #define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */ #define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */ #define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */ @@ -3897,7 +3928,7 @@ struct AuthContext { #define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */ #define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ #define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */ -#define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */ +#define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */ /* ** Each trigger present in the database schema is stored as an instance of @@ -4031,6 +4062,25 @@ struct sqlite3_str { #define isMalloced(X) (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0) +/* +** The following object is the header for an "RCStr" or "reference-counted +** string". An RCStr is passed around and used like any other char* +** that has been dynamically allocated. The important interface +** differences: +** +** 1. RCStr strings are reference counted. They are deallocated +** when the reference count reaches zero. +** +** 2. Use sqlite3RCStrUnref() to free an RCStr string rather than +** sqlite3_free() +** +** 3. Make a (read-only) copy of a read-only RCStr string using +** sqlite3RCStrRef(). +*/ +struct RCStr { + u64 nRCRef; /* Number of references */ + /* Total structure size should be a multiple of 8 bytes for alignment */ +}; /* ** A pointer to this structure is used to communicate information @@ -4057,7 +4107,7 @@ typedef struct { /* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled ** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning ** parameters are for temporary use during development, to help find -** optimial values for parameters in the query planner. The should not +** optimal values for parameters in the query planner. The should not ** be used on trunk check-ins. They are a temporary mechanism available ** for transient development builds only. ** @@ -4083,6 +4133,7 @@ struct Sqlite3Config { u8 bUseCis; /* Use covering indices for full-scans */ u8 bSmallMalloc; /* Avoid large memory allocations if true */ u8 bExtraSchemaChecks; /* Verify type,name,tbl_name in schema */ + u8 bUseLongDouble; /* Make use of long double */ int mxStrlen; /* Maximum string length */ int neverCorrupt; /* Database is always well-formed */ int szLookaside; /* Default lookaside buffer size */ @@ -4169,6 +4220,7 @@ struct Walker { void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */ int walkerDepth; /* Number of subqueries */ u16 eCode; /* A small processing code */ + u16 mWFlags; /* Use-dependent flags */ union { /* Extra data for callback */ NameContext *pNC; /* Naming context */ int n; /* A counter */ @@ -4208,6 +4260,7 @@ struct DbFixer { /* Forward declarations */ int sqlite3WalkExpr(Walker*, Expr*); +int sqlite3WalkExprNN(Walker*, Expr*); int sqlite3WalkExprList(Walker*, ExprList*); int sqlite3WalkSelect(Walker*, Select*); int sqlite3WalkSelectExpr(Walker*, Select*); @@ -4589,6 +4642,20 @@ struct PrintfArguments { sqlite3_value **apArg; /* The argument values */ }; +/* +** An instance of this object receives the decoding of a floating point +** value into an approximate decimal representation. +*/ +struct FpDecode { + char sign; /* '+' or '-' */ + char isSpecial; /* 1: Infinity 2: NaN */ + int n; /* Significant digits in the decode */ + int iDP; /* Location of the decimal point */ + char *z; /* Start of significant digits */ + char zBuf[24]; /* Storage for significant digits */ +}; + +void sqlite3FpDecode(FpDecode*,double,int,int); char *sqlite3MPrintf(sqlite3*,const char*, ...); char *sqlite3VMPrintf(sqlite3*,const char*, va_list); #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) @@ -4879,7 +4946,7 @@ int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int); int sqlite3ExprCompareSkip(Expr*,Expr*,int); int sqlite3ExprListCompare(const ExprList*,const ExprList*, int); int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int); -int sqlite3ExprImpliesNonNullRow(Expr*,int); +int sqlite3ExprImpliesNonNullRow(Expr*,int,int); void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*); void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); @@ -5028,6 +5095,7 @@ int sqlite3FixSrcList(DbFixer*, SrcList*); int sqlite3FixSelect(DbFixer*, Select*); int sqlite3FixExpr(DbFixer*, Expr*); int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); + int sqlite3RealSameAsInt(double,sqlite3_int64); i64 sqlite3RealToI64(double); int sqlite3Int64ToText(i64,char*); @@ -5132,6 +5200,7 @@ void sqlite3FileSuffix3(const char*, char*); u8 sqlite3GetBoolean(const char *z,u8); const void *sqlite3ValueText(sqlite3_value*, u8); +int sqlite3ValueIsOfClass(const sqlite3_value*, void(*)(void*)); int sqlite3ValueBytes(sqlite3_value*, u8); void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); @@ -5227,10 +5296,10 @@ int sqlite3KeyInfoIsWriteable(KeyInfo*); #endif int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, void (*)(sqlite3_context*,int,sqlite3_value **), - void (*)(sqlite3_context*,int,sqlite3_value **), + void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*), void (*)(sqlite3_context*), - void (*)(sqlite3_context*,int,sqlite3_value **), + void (*)(sqlite3_context*,int,sqlite3_value **), FuncDestructor *pDestructor ); void sqlite3NoopDestructor(void*); @@ -5239,6 +5308,11 @@ void sqlite3OomClear(sqlite3*); int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); +char *sqlite3RCStrRef(char*); +void sqlite3RCStrUnref(char*); +char *sqlite3RCStrNew(u64); +char *sqlite3RCStrResize(char*,u64); + void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); int sqlite3StrAccumEnlarge(StrAccum*, i64); char *sqlite3StrAccumFinish(StrAccum*); @@ -5490,6 +5564,7 @@ void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p); #define sqlite3SelectExprHeight(x) 0 #define sqlite3ExprCheckHeight(x,y) #endif +void sqlite3ExprSetErrorOffset(Expr*,int); u32 sqlite3Get4byte(const u8*); void sqlite3Put4byte(u8*, u32); diff --git a/src/sqliteLimit.h b/src/sqliteLimit.h index 08703cb73..bfb596da2 100644 --- a/src/sqliteLimit.h +++ b/src/sqliteLimit.h @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** +** ** This file defines various limits of what SQLite can process. */ @@ -57,9 +57,9 @@ #endif /* -** The maximum depth of an expression tree. This is limited to -** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might -** want to place more severe limits on the complexity of an +** The maximum depth of an expression tree. This is limited to +** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might +** want to place more severe limits on the complexity of an ** expression. A value of 0 means that there is no limit. */ #ifndef SQLITE_MAX_EXPR_DEPTH @@ -72,7 +72,7 @@ ** level of recursion for each term. A stack overflow can result ** if the number of terms is too large. In practice, most SQL ** never has more than 3 or 4 terms. Use a value of 0 to disable -** any limit on the number of terms in a compount SELECT. +** any limit on the number of terms in a compound SELECT. */ #ifndef SQLITE_MAX_COMPOUND_SELECT # define SQLITE_MAX_COMPOUND_SELECT 500 @@ -140,10 +140,10 @@ ** ** Earlier versions of SQLite allowed the user to change this value at ** compile time. This is no longer permitted, on the grounds that it creates -** a library that is technically incompatible with an SQLite library -** compiled with a different limit. If a process operating on a database -** with a page-size of 65536 bytes crashes, then an instance of SQLite -** compiled with the default page-size limit will not be able to rollback +** a library that is technically incompatible with an SQLite library +** compiled with a different limit. If a process operating on a database +** with a page-size of 65536 bytes crashes, then an instance of SQLite +** compiled with the default page-size limit will not be able to rollback ** the aborted transaction. This could lead to database corruption. */ #ifdef SQLITE_MAX_PAGE_SIZE @@ -202,7 +202,7 @@ ** Maximum depth of recursion for triggers. ** ** A value of 1 means that a trigger program will not be able to itself -** fire any triggers. A value of 0 means that no trigger programs at all +** fire any triggers. A value of 0 means that no trigger programs at all ** may be executed. */ #ifndef SQLITE_MAX_TRIGGER_DEPTH diff --git a/src/tclsqlite.c b/src/tclsqlite.c index c455c29f1..d91b2fa3f 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -55,6 +55,25 @@ # include # include typedef unsigned char u8; +# ifndef SQLITE_PTRSIZE +# if defined(__SIZEOF_POINTER__) +# define SQLITE_PTRSIZE __SIZEOF_POINTER__ +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(_M_ARM) || defined(__arm__) || defined(__x86) || \ + (defined(__APPLE__) && defined(__POWERPC__)) || \ + (defined(__TOS_AIX__) && !defined(__64BIT__)) +# define SQLITE_PTRSIZE 4 +# else +# define SQLITE_PTRSIZE 8 +# endif +# endif /* SQLITE_PTRSIZE */ +# if defined(HAVE_STDINT_H) + typedef uintptr_t uptr; +# elif SQLITE_PTRSIZE==4 + typedef unsigned int uptr; +# else + typedef sqlite3_uint64 uptr; +# endif #endif #include @@ -675,7 +694,7 @@ static int DbTraceV2Handler( pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); Tcl_IncrRefCount(pCmd); Tcl_ListObjAppendElement(pDb->interp, pCmd, - Tcl_NewWideIntObj((Tcl_WideInt)pStmt)); + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)pStmt)); Tcl_ListObjAppendElement(pDb->interp, pCmd, Tcl_NewStringObj(zSql, -1)); Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); @@ -690,7 +709,7 @@ static int DbTraceV2Handler( pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); Tcl_IncrRefCount(pCmd); Tcl_ListObjAppendElement(pDb->interp, pCmd, - Tcl_NewWideIntObj((Tcl_WideInt)pStmt)); + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)pStmt)); Tcl_ListObjAppendElement(pDb->interp, pCmd, Tcl_NewWideIntObj((Tcl_WideInt)ns)); Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); @@ -704,7 +723,7 @@ static int DbTraceV2Handler( pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); Tcl_IncrRefCount(pCmd); Tcl_ListObjAppendElement(pDb->interp, pCmd, - Tcl_NewWideIntObj((Tcl_WideInt)pStmt)); + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)pStmt)); Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); Tcl_DecrRefCount(pCmd); Tcl_ResetResult(pDb->interp); @@ -716,7 +735,7 @@ static int DbTraceV2Handler( pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); Tcl_IncrRefCount(pCmd); Tcl_ListObjAppendElement(pDb->interp, pCmd, - Tcl_NewWideIntObj((Tcl_WideInt)db)); + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)db)); Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); Tcl_DecrRefCount(pCmd); Tcl_ResetResult(pDb->interp); @@ -1780,7 +1799,7 @@ static Tcl_Obj *dbEvalColumnValue(DbEvalContext *p, int iCol){ /* ** If using Tcl version 8.6 or greater, use the NR functions to avoid -** recursive evalution of scripts by the [db eval] and [db trans] +** recursive evaluation of scripts by the [db eval] and [db trans] ** commands. Even if the headers used while compiling the extension ** are 8.6 or newer, the code still tests the Tcl version at runtime. ** This allows stubs-enabled builds to be used with older Tcl libraries. @@ -2441,7 +2460,7 @@ static int SQLITE_TCLAPI DbObjCmd( ** ** This command usage is equivalent to the sqlite2.x COPY statement, ** which imports file data into a table using the PostgreSQL COPY file format: - ** $db copy $conflit_algo $table_name $filename \t \\N + ** $db copy $conflict_algorithm $table_name $filename \t \\N */ case DB_COPY: { char *zTable; /* Insert data into this table */ @@ -3427,7 +3446,7 @@ static int SQLITE_TCLAPI DbObjCmd( ** Start a new transaction (if we are not already in the midst of a ** transaction) and execute the TCL script SCRIPT. After SCRIPT ** completes, either commit the transaction or roll it back if SCRIPT - ** throws an exception. Or if no new transation was started, do nothing. + ** throws an exception. Or if no new transaction was started, do nothing. ** pass the exception on up the stack. ** ** This command was inspired by Dave Thomas's talk on Ruby at the diff --git a/src/test1.c b/src/test1.c index 772b896ac..4dcf7bc11 100644 --- a/src/test1.c +++ b/src/test1.c @@ -991,6 +991,46 @@ static void intrealFunction( sqlite3_test_control(SQLITE_TESTCTRL_RESULT_INTREAL, context); } +/* +** SQL function: strtod(X) +** +** Use the C-library strtod() function to convert string X into a double. +** Used for comparing the accuracy of SQLite's internal text-to-float conversion +** routines against the C-library. +*/ +static void shellStrtod( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + char *z = (char*)sqlite3_value_text(apVal[0]); + UNUSED_PARAMETER(nVal); + if( z==0 ) return; + sqlite3_result_double(pCtx, strtod(z,0)); +} + +/* +** SQL function: dtostr(X) +** +** Use the C-library printf() function to convert real value X into a string. +** Used for comparing the accuracy of SQLite's internal float-to-text conversion +** routines against the C-library. +*/ +static void shellDtostr( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + double r = sqlite3_value_double(apVal[0]); + int n = nVal>=2 ? sqlite3_value_int(apVal[1]) : 26; + char z[400]; + if( n<1 ) n = 1; + if( n>350 ) n = 350; + sprintf(z, "%#+.*e", n, r); + sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); +} + + /* ** Usage: sqlite3_create_function DB ** @@ -1063,6 +1103,27 @@ static int SQLITE_TCLAPI test_create_function( 0, intrealFunction, 0, 0); } + /* Functions strtod() and dtostr() work as in the shell. These routines + ** use the standard C library to convert between floating point and + ** text. This is used to compare SQLite's internal conversion routines + ** against the standard library conversion routines. + ** + ** Both routines copy/pasted from the shell.c.in implementation + ** on 2023-07-03. + */ + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "strtod", 1, SQLITE_UTF8, 0, + shellStrtod, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "dtostr", 1, SQLITE_UTF8, 0, + shellDtostr, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "dtostr", 2, SQLITE_UTF8, 0, + shellDtostr, 0, 0); + } + #ifndef SQLITE_OMIT_UTF16 /* Use the sqlite3_create_function16() API here. Mainly for fun, but also ** because it is not tested anywhere else. */ @@ -2216,6 +2277,7 @@ static int SQLITE_TCLAPI test_stmt_scanstatus( int flags = 0; int iSelectId = 0; int iParentId = 0; + int bDebug = 0; if( objc==5 ){ struct Flag { @@ -2223,6 +2285,7 @@ static int SQLITE_TCLAPI test_stmt_scanstatus( int flag; } aTbl[] = { {"complex", SQLITE_SCANSTAT_COMPLEX}, + {"debug", -1}, {0, 0} }; @@ -2239,7 +2302,11 @@ static int SQLITE_TCLAPI test_stmt_scanstatus( interp, aFlag[ii], aTbl, sizeof(aTbl[0]), "flag", 0, &iVal ); if( res ) return TCL_ERROR; - flags |= aTbl[iVal].flag; + if( aTbl[iVal].flag==-1 ){ + bDebug = 1; + }else{ + flags |= aTbl[iVal].flag; + } } } @@ -2253,6 +2320,13 @@ static int SQLITE_TCLAPI test_stmt_scanstatus( return TCL_ERROR; } + if( bDebug && 0==(flags & SQLITE_SCANSTAT_COMPLEX) ){ + Tcl_SetObjResult(interp, + Tcl_NewStringObj("may not specify debug without complex", -1) + ); + return TCL_ERROR; + } + if( idx<0 ){ Tcl_Obj *pRet = Tcl_NewObj(); res = sqlite3_stmt_scanstatus_v2( @@ -2299,6 +2373,36 @@ static int SQLITE_TCLAPI test_stmt_scanstatus( pStmt, idx, SQLITE_SCANSTAT_NCYCLE, flags, (void*)&nCycle); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nCycle", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nCycle)); + + if( bDebug ){ + int ii; + ScanStatus *pScan = &((Vdbe*)pStmt)->aScan[idx]; + Tcl_Obj *pRange = Tcl_NewObj(); + Tcl_Obj *pCsr = Tcl_NewObj(); + + Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("debug_loop", -1)); + Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(pScan->addrLoop)); + Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("debug_visit", -1)); + Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(pScan->addrVisit)); + Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("debug_explain",-1)); + Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(pScan->addrExplain)); + for(ii=0; iiaAddrRange)/2; ii++){ + int iStart = pScan->aAddrRange[ii*2]; + int iEnd = pScan->aAddrRange[ii*2+1]; + if( iStart>0 ){ + Tcl_ListObjAppendElement(0, pRange, Tcl_NewIntObj(iStart)); + Tcl_ListObjAppendElement(0, pRange, Tcl_NewIntObj(iEnd)); + }else if( iStart<0 ){ + Tcl_ListObjAppendElement(0, pCsr, Tcl_NewIntObj(iEnd)); + } + } + + Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("debug_range", -1)); + Tcl_ListObjAppendElement(0, pRet, pRange); + Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("debug_csr", -1)); + Tcl_ListObjAppendElement(0, pRet, pCsr); + } + Tcl_SetObjResult(interp, pRet); }else{ Tcl_ResetResult(interp); @@ -2736,7 +2840,7 @@ static int SQLITE_TCLAPI test_atomic_batch_write( /* ** Usage: sqlite3_next_stmt DB STMT ** -** Return the next statment in sequence after STMT. +** Return the next statement in sequence after STMT. */ static int SQLITE_TCLAPI test_next_stmt( void * clientData, @@ -2818,6 +2922,34 @@ static int SQLITE_TCLAPI test_stmt_isexplain( return TCL_OK; } +/* +** Usage: sqlite3_stmt_explain STMT INT +** +** Set the explain to normal (0), EXPLAIN (1) or EXPLAIN QUERY PLAN (2). +*/ +static int SQLITE_TCLAPI test_stmt_explain( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_stmt *pStmt; + int eMode = 0; + int rc; + + if( objc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], 0), " STMT INT", 0); + return TCL_ERROR; + } + + if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[2], &eMode) ) return TCL_ERROR; + rc = sqlite3_stmt_explain(pStmt, eMode); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; +} + /* ** Usage: sqlite3_stmt_busy STMT ** @@ -3459,10 +3591,10 @@ static int SQLITE_TCLAPI test_function( /* ** Usage: sqlite3_test_errstr ** -** Test that the english language string equivalents for sqlite error codes +** Test that the English language string equivalents for sqlite error codes ** are sane. The parameter is an integer representing an sqlite error code. ** The result is a list of two elements, the string representation of the -** error code and the english language explanation. +** error code and the English language explanation. */ static int SQLITE_TCLAPI test_errstr( void * clientData, @@ -3648,7 +3780,7 @@ static int SQLITE_TCLAPI test_intarray_addr( } } } - Tcl_SetObjResult(interp, Tcl_NewWideIntObj((sqlite3_int64)p)); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((uptr)p)); return TCL_OK; } /* @@ -3684,7 +3816,7 @@ static int SQLITE_TCLAPI test_int64array_addr( p[i] = v; } } - Tcl_SetObjResult(interp, Tcl_NewWideIntObj((sqlite3_int64)p)); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((uptr)p)); return TCL_OK; } /* @@ -3718,7 +3850,7 @@ static int SQLITE_TCLAPI test_doublearray_addr( } } } - Tcl_SetObjResult(interp, Tcl_NewWideIntObj((sqlite3_int64)p)); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((uptr)p)); return TCL_OK; } /* @@ -3751,7 +3883,7 @@ static int SQLITE_TCLAPI test_textarray_addr( } } n = objc-1; - Tcl_SetObjResult(interp, Tcl_NewWideIntObj((sqlite3_int64)p)); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((uptr)p)); return TCL_OK; } @@ -6115,7 +6247,7 @@ static int SQLITE_TCLAPI vfs_unlink_test( assert( sqlite3_vfs_find("__two")==&two ); /* Calling sqlite_vfs_register with non-zero second parameter changes the - ** default VFS, even if the 1st parameter is an existig VFS that is + ** default VFS, even if the 1st parameter is an existing VFS that is ** previously registered as the non-default. */ sqlite3_vfs_register(&one, 1); @@ -6994,6 +7126,37 @@ static int SQLITE_TCLAPI extra_schema_checks( return TCL_OK; } +/* +** tclcmd: use_long_double BOOLEAN|"default" +** +** If no argument, report the current value of the use-long-double flag. +** +** If argument is "default", set the use-long-double flag to the default +** value for this build, based on the size of LONGDOUBLE_TYPE. +** +** If argument is a boolean, set the use-long-double flag accordingly. +** +** Return the new setting. +*/ +static int SQLITE_TCLAPI use_long_double( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + int i = -1; + if( objc==2 ){ + if( strcmp(Tcl_GetString(objv[1]),"default")==0 ){ + i = 2; + }else{ + if( Tcl_GetBooleanFromObj(interp,objv[1],&i) ) return TCL_ERROR; + } + } + i = sqlite3_test_control(SQLITE_TESTCTRL_USELONGDOUBLE, i); + Tcl_SetObjResult(interp, Tcl_NewIntObj(i)); + return TCL_OK; +} + /* ** tclcmd: database_may_be_corrupt ** @@ -7416,6 +7579,26 @@ static int testLocaltime(const void *aliasT, void *aliasTM){ return t==959609760; /* Special case: 2000-05-29 14:16:00 fails */ } +/* +** .treetrace N +*/ +static int SQLITE_TCLAPI test_treetrace( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + unsigned int v = 0; + if( objc>=2 ){ + if( Tcl_GetIntFromObj(interp, objv[1], (int*)&v)==TCL_OK ){ + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &v); + } + } + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &v); + Tcl_SetObjResult(interp, Tcl_NewIntObj((int)v)); + return TCL_OK; +} + /* ** sqlite3_test_control VERB ARGS... */ @@ -7826,6 +8009,7 @@ static int SQLITE_TCLAPI optimization_control( { "push-down", SQLITE_PushDown }, { "balanced-merge", SQLITE_BalancedMerge }, { "propagate-const", SQLITE_PropagateConst }, + { "one-pass", SQLITE_OnePass }, }; if( objc!=4 ){ @@ -8836,6 +9020,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_next_stmt", test_next_stmt ,0 }, { "sqlite3_stmt_readonly", test_stmt_readonly ,0 }, { "sqlite3_stmt_isexplain", test_stmt_isexplain,0 }, + { "sqlite3_stmt_explain", test_stmt_explain ,0 }, { "sqlite3_stmt_busy", test_stmt_busy ,0 }, { "uses_stmt_journal", uses_stmt_journal ,0 }, @@ -8862,6 +9047,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "reset_prng_state", reset_prng_state, 0 }, { "prng_seed", prng_seed, 0 }, { "extra_schema_checks", extra_schema_checks, 0}, + { "use_long_double", use_long_double, 0}, { "database_never_corrupt", database_never_corrupt, 0}, { "database_may_be_corrupt", database_may_be_corrupt, 0}, { "optimization_control", optimization_control,0}, @@ -8967,6 +9153,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "print_explain_query_plan", test_print_eqp, 0 }, #endif { "sqlite3_test_control", test_test_control }, + { ".treetrace", test_treetrace }, #if SQLITE_OS_UNIX { "getrusage", test_getrusage }, #endif diff --git a/src/test2.c b/src/test2.c index d5db3867b..c75fa2eba 100644 --- a/src/test2.c +++ b/src/test2.c @@ -608,7 +608,7 @@ static int faultSimCallback(int x){ zInt[i] = (x%10) + '0'; } if( isNeg ) zInt[i--] = '-'; - memcpy(faultSimScript+faultSimScriptSize, zInt+i+1, sizeof(zInt)-i); + memcpy(faultSimScript+faultSimScriptSize, zInt+i+1, sizeof(zInt)-i-1); } rc = Tcl_Eval(faultSimInterp, faultSimScript); if( rc ){ diff --git a/src/test3.c b/src/test3.c index d1626b6ef..7fd766247 100644 --- a/src/test3.c +++ b/src/test3.c @@ -422,7 +422,7 @@ static int SQLITE_TCLAPI btree_payload_size( ** routines, both for accuracy and for speed. ** ** An integer is written using putVarint() and read back with -** getVarint() and varified to be unchanged. This repeats COUNT +** getVarint() and verified to be unchanged. This repeats COUNT ** times. The first integer is START*MULTIPLIER. Each iteration ** increases the integer by INCREMENT. ** diff --git a/src/test6.c b/src/test6.c index aa94b9b5e..5d8e6b9be 100644 --- a/src/test6.c +++ b/src/test6.c @@ -33,25 +33,25 @@ typedef struct WriteBuffer WriteBuffer; /* ** Method: ** -** This layer is implemented as a wrapper around the "real" -** sqlite3_file object for the host system. Each time data is +** This layer is implemented as a wrapper around the "real" +** sqlite3_file object for the host system. Each time data is ** written to the file object, instead of being written to the -** underlying file, the write operation is stored in an in-memory +** underlying file, the write operation is stored in an in-memory ** structure (type WriteBuffer). This structure is placed at the ** end of a global ordered list (the write-list). ** ** When data is read from a file object, the requested region is -** first retrieved from the real file. The write-list is then -** traversed and data copied from any overlapping WriteBuffer +** first retrieved from the real file. The write-list is then +** traversed and data copied from any overlapping WriteBuffer ** structures to the output buffer. i.e. a read() operation following ** one or more write() operations works as expected, even if no ** data has actually been written out to the real file. ** -** When a fsync() operation is performed, an operating system crash -** may be simulated, in which case exit(-1) is called (the call to +** When a fsync() operation is performed, an operating system crash +** may be simulated, in which case exit(-1) is called (the call to ** xSync() never returns). Whether or not a crash is simulated, -** the data associated with a subset of the WriteBuffer structures -** stored in the write-list is written to the real underlying files +** the data associated with a subset of the WriteBuffer structures +** stored in the write-list is written to the real underlying files ** and the entries removed from the write-list. If a crash is simulated, ** a subset of the buffers may be corrupted before the data is written. ** @@ -63,13 +63,13 @@ typedef struct WriteBuffer WriteBuffer; ** Normal mode is used when the simulated device has none of the ** SQLITE_IOCAP_XXX flags set. ** -** In normal mode, if the fsync() is not a simulated crash, the +** In normal mode, if the fsync() is not a simulated crash, the ** write-list is traversed from beginning to end. Each WriteBuffer ** structure associated with the file handle used to call xSync() ** is written to the real file and removed from the write-list. ** -** If a crash is simulated, one of the following takes place for -** each WriteBuffer in the write-list, regardless of which +** If a crash is simulated, one of the following takes place for +** each WriteBuffer in the write-list, regardless of which ** file-handle it is associated with: ** ** 1. The buffer is correctly written to the file, just as if @@ -77,14 +77,14 @@ typedef struct WriteBuffer WriteBuffer; ** ** 2. Nothing is done. ** -** 3. Garbage data is written to all sectors of the file that +** 3. Garbage data is written to all sectors of the file that ** overlap the region specified by the WriteBuffer. Or garbage -** data is written to some contiguous section within the +** data is written to some contiguous section within the ** overlapped sectors. ** ** Device Characteristic flag handling: ** -** If the IOCAP_ATOMIC flag is set, then option (3) above is +** If the IOCAP_ATOMIC flag is set, then option (3) above is ** never selected. ** ** If the IOCAP_ATOMIC512 flag is set, and the WriteBuffer represents @@ -96,11 +96,11 @@ typedef struct WriteBuffer WriteBuffer; ** ** If either the IOCAP_SAFEAPPEND or IOCAP_SEQUENTIAL flags are set ** and a crash is being simulated, then an entry of the write-list is -** selected at random. Everything in the list after the selected entry +** selected at random. Everything in the list after the selected entry ** is discarded before processing begins. ** -** If IOCAP_SEQUENTIAL is set and a crash is being simulated, option -** (1) is selected for all write-list entries except the last. If a +** If IOCAP_SEQUENTIAL is set and a crash is being simulated, option +** (1) is selected for all write-list entries except the last. If a ** crash is not being simulated, then all entries in the write-list ** that occur before at least one write() on the file-handle specified ** as part of the xSync() are written to their associated real files. @@ -114,7 +114,7 @@ typedef struct WriteBuffer WriteBuffer; ** Each write operation in the write-list is represented by an instance ** of the following structure. ** -** If zBuf is 0, then this structure represents a call to xTruncate(), +** If zBuf is 0, then this structure represents a call to xTruncate(), ** not xWrite(). In that case, iOffset is the size that the file is ** truncated to. */ @@ -133,7 +133,7 @@ struct CrashFile { char *zName; int flags; /* Flags the file was opened with */ - /* Cache of the entire file. This is used to speed up OsRead() and + /* Cache of the entire file. This is used to speed up OsRead() and ** OsFileSize() calls. Although both could be done by traversing the ** write-list, in practice this is impractically slow. */ @@ -150,7 +150,7 @@ struct CrashGlobal { int iDeviceCharacteristics; /* Value of simulated device characteristics */ int iCrash; /* Crash on the iCrash'th call to xSync() */ - char zCrashFile[500]; /* Crash during an xSync() on this file */ + char zCrashFile[500]; /* Crash during an xSync() on this file */ }; static CrashGlobal g = {0, 0, SQLITE_DEFAULT_SECTOR_SIZE, 0, 0}; @@ -172,7 +172,7 @@ static void *crash_realloc(void *p, int n){ /* ** Wrapper around the sqlite3OsWrite() function that avoids writing to the -** 512 byte block begining at offset PENDING_BYTE. +** 512 byte block beginning at offset PENDING_BYTE. */ static int writeDbFile(CrashFile *p, u8 *z, i64 iAmt, i64 iOff){ int rc = SQLITE_OK; @@ -194,7 +194,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ WriteBuffer *pWrite; WriteBuffer **ppPtr; - /* If this is not a crash simulation, set pFinal to point to the + /* If this is not a crash simulation, set pFinal to point to the ** last element of the write-list that is associated with file handle ** pFile. ** @@ -242,7 +242,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ char random; sqlite3_randomness(1, &random); - /* Do not select option 3 (sector trashing) if the IOCAP_ATOMIC flag + /* Do not select option 3 (sector trashing) if the IOCAP_ATOMIC flag ** is set or this is an OsTruncate(), not an Oswrite(). */ if( (iDc&SQLITE_IOCAP_ATOMIC) || (pWrite->zBuf==0) ){ @@ -288,7 +288,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ *ppPtr = pWrite->pNext; #ifdef TRACE_CRASHTEST if( isCrash ){ - printf("Writing %d bytes @ %d (%s)\n", + printf("Writing %d bytes @ %d (%s)\n", pWrite->nBuf, (int)pWrite->iOffset, pWrite->pFile->zName ); } @@ -300,7 +300,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ ppPtr = &pWrite->pNext; #ifdef TRACE_CRASHTEST if( isCrash ){ - printf("Omiting %d bytes @ %d (%s)\n", + printf("Omiting %d bytes @ %d (%s)\n", pWrite->nBuf, (int)pWrite->iOffset, pWrite->pFile->zName ); } @@ -315,7 +315,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ assert(pWrite->zBuf); #ifdef TRACE_CRASHTEST - printf("Trashing %d sectors (%d bytes) @ %lld (sector %d) (%s)\n", + printf("Trashing %d sectors (%d bytes) @ %lld (sector %d) (%s)\n", 1+iLast-iFirst, (1+iLast-iFirst)*g.iSectorSize, pWrite->iOffset, iFirst, pWrite->pFile->zName ); @@ -325,7 +325,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ if( zGarbage ){ sqlite3_int64 i; for(i=iFirst; rc==SQLITE_OK && i<=iLast; i++){ - sqlite3_randomness(g.iSectorSize, zGarbage); + sqlite3_randomness(g.iSectorSize, zGarbage); rc = writeDbFile( pWrite->pFile, zGarbage, g.iSectorSize, i*g.iSectorSize ); @@ -389,7 +389,7 @@ static int writeListAppend( g.pWriteList = pNew; } g.pWriteListEnd = pNew; - + return SQLITE_OK; } @@ -407,9 +407,9 @@ static int cfClose(sqlite3_file *pFile){ ** Read data from a crash-file. */ static int cfRead( - sqlite3_file *pFile, - void *zBuf, - int iAmt, + sqlite3_file *pFile, + void *zBuf, + int iAmt, sqlite_int64 iOfst ){ CrashFile *pCrash = (CrashFile *)pFile; @@ -431,9 +431,9 @@ static int cfRead( ** Write data to a crash-file. */ static int cfWrite( - sqlite3_file *pFile, - const void *zBuf, - int iAmt, + sqlite3_file *pFile, + const void *zBuf, + int iAmt, sqlite_int64 iOfst ){ CrashFile *pCrash = (CrashFile *)pFile; @@ -604,7 +604,7 @@ struct crashAppData { ** ** The caller will have allocated pVfs->szOsFile bytes of space ** at pFile. This file uses this space for the CrashFile structure -** and allocates space for the "real" file structure using +** and allocates space for the "real" file structure using ** sqlite3_malloc(). The assumption here is (pVfs->szOsFile) is ** equal or greater than sizeof(CrashFile). */ @@ -667,17 +667,17 @@ static int cfDelete(sqlite3_vfs *pCfVfs, const char *zPath, int dirSync){ return pVfs->xDelete(pVfs, zPath, dirSync); } static int cfAccess( - sqlite3_vfs *pCfVfs, - const char *zPath, - int flags, + sqlite3_vfs *pCfVfs, + const char *zPath, + int flags, int *pResOut ){ sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData; return pVfs->xAccess(pVfs, zPath, flags, pResOut); } static int cfFullPathname( - sqlite3_vfs *pCfVfs, - const char *zPath, + sqlite3_vfs *pCfVfs, + const char *zPath, int nPathOut, char *zPathOut ){ @@ -754,11 +754,11 @@ static int processDevSymArgs( int nOpt; char *zOpt = Tcl_GetStringFromObj(objv[i], &nOpt); - if( (nOpt>11 || nOpt<2 || strncmp("-sectorsize", zOpt, nOpt)) + if( (nOpt>11 || nOpt<2 || strncmp("-sectorsize", zOpt, nOpt)) && (nOpt>16 || nOpt<2 || strncmp("-characteristics", zOpt, nOpt)) ){ - Tcl_AppendResult(interp, - "Bad option: \"", zOpt, + Tcl_AppendResult(interp, + "Bad option: \"", zOpt, "\" - must be \"-characteristics\" or \"-sectorsize\"", 0 ); return TCL_ERROR; @@ -786,7 +786,7 @@ static int processDevSymArgs( Tcl_Obj *pFlag = Tcl_DuplicateObj(apObj[j]); Tcl_IncrRefCount(pFlag); Tcl_UtfToLower(Tcl_GetString(pFlag)); - + rc = Tcl_GetIndexFromObjStruct( interp, pFlag, aFlag, sizeof(aFlag[0]), "no such flag", 0, &iChoice ); @@ -814,7 +814,7 @@ static int processDevSymArgs( /* ** tclcmd: sqlite3_crash_now ** -** Simulate a crash immediately. This function does not return +** Simulate a crash immediately. This function does not return ** (writeListSync() calls exit(-1)). */ static int SQLITE_TCLAPI crashNowCmd( @@ -853,7 +853,7 @@ static int SQLITE_TCLAPI crashEnableCmd( 0, /* pNext */ "crash", /* zName */ 0, /* pAppData */ - + cfOpen, /* xOpen */ cfDelete, /* xDelete */ cfAccess, /* xAccess */ @@ -909,7 +909,7 @@ static int SQLITE_TCLAPI crashEnableCmd( ** an argument. For -sectorsize, this is the simulated sector size in ** bytes. For -characteristics, the argument must be a list of io-capability ** flags to simulate. Valid flags are "atomic", "atomic512", "atomic1K", -** "atomic2K", "atomic4K", "atomic8K", "atomic16K", "atomic32K", +** "atomic2K", "atomic4K", "atomic8K", "atomic16K", "atomic32K", ** "atomic64K", "sequential" and "safe_append". ** ** Example: @@ -1046,7 +1046,7 @@ static int SQLITE_TCLAPI jtObjCmd( zParent = Tcl_GetString(objv[1]); if( objc==3 ){ if( strcmp(zParent, "-default") ){ - Tcl_AppendResult(interp, + Tcl_AppendResult(interp, "bad option \"", zParent, "\": must be -default", 0 ); return TCL_ERROR; diff --git a/src/test8.c b/src/test8.c index 7a532346e..f0f574310 100644 --- a/src/test8.c +++ b/src/test8.c @@ -29,7 +29,7 @@ typedef struct echo_cursor echo_cursor; /* ** The test module defined in this file uses four global Tcl variables to -** commicate with test-scripts: +** communicate with test-scripts: ** ** $::echo_module ** $::echo_module_sync_fail diff --git a/src/test_config.c b/src/test_config.c index a362dbc56..ee766a26d 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -408,18 +408,6 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "foreignkey", "1", TCL_GLOBAL_ONLY); #endif -#ifdef SQLITE_ENABLE_FTS1 - Tcl_SetVar2(interp, "sqlite_options", "fts1", "1", TCL_GLOBAL_ONLY); -#else - Tcl_SetVar2(interp, "sqlite_options", "fts1", "0", TCL_GLOBAL_ONLY); -#endif - -#ifdef SQLITE_ENABLE_FTS2 - Tcl_SetVar2(interp, "sqlite_options", "fts2", "1", TCL_GLOBAL_ONLY); -#else - Tcl_SetVar2(interp, "sqlite_options", "fts2", "0", TCL_GLOBAL_ONLY); -#endif - #ifdef SQLITE_ENABLE_FTS3 Tcl_SetVar2(interp, "sqlite_options", "fts3", "1", TCL_GLOBAL_ONLY); #else diff --git a/src/test_devsym.c b/src/test_devsym.c index 7847bc300..86b78f15a 100644 --- a/src/test_devsym.c +++ b/src/test_devsym.c @@ -187,7 +187,7 @@ static int devsymDeviceCharacteristics(sqlite3_file *pFile){ } /* -** Shared-memory methods are all pass-thrus. +** Shared-memory methods are all pass-throughs. */ static int devsymShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ devsym_file *p = (devsym_file *)pFile; diff --git a/src/test_init.c b/src/test_init.c index 58465785d..400ab9a2b 100644 --- a/src/test_init.c +++ b/src/test_init.c @@ -38,12 +38,12 @@ static struct Wrapped { sqlite3_mem_methods mem; sqlite3_mutex_methods mutex; - int mem_init; /* True if mem subsystem is initalized */ - int mem_fail; /* True to fail mem subsystem inialization */ - int mutex_init; /* True if mutex subsystem is initalized */ - int mutex_fail; /* True to fail mutex subsystem inialization */ - int pcache_init; /* True if pcache subsystem is initalized */ - int pcache_fail; /* True to fail pcache subsystem inialization */ + int mem_init; /* True if mem subsystem is initialized */ + int mem_fail; /* True to fail mem subsystem initialization */ + int mutex_init; /* True if mutex subsystem is initialized */ + int mutex_fail; /* True to fail mutex subsystem initialization */ + int pcache_init; /* True if pcache subsystem is initialized */ + int pcache_fail; /* True to fail pcache subsystem initialization */ } wrapped; static int wrMemInit(void *pAppData){ diff --git a/src/test_intarray.h b/src/test_intarray.h index 116e3bdc0..b68233b12 100644 --- a/src/test_intarray.h +++ b/src/test_intarray.h @@ -23,7 +23,7 @@ ** ** SELECT * FROM table WHERE x IN (?,?,?,...,?); ** -** And then binding indivdual integers to each of ? slots, a C-language +** And then binding individual integers to each of ? slots, a C-language ** application can create an intarray object (named "ex1" in the following ** example), prepare a statement like this: ** diff --git a/src/test_md5.c b/src/test_md5.c index b67002686..7903797db 100644 --- a/src/test_md5.c +++ b/src/test_md5.c @@ -295,7 +295,7 @@ static void MD5DigestToBase16(unsigned char *digest, char *zBuf){ /* -** Convert a 128-bit MD5 digest into sequency of eight 5-digit integers +** Convert a 128-bit MD5 digest into sequences of eight 5-digit integers ** each representing 16 bits of the digest and separated from each ** other by a "-" character. */ diff --git a/src/test_multiplex.c b/src/test_multiplex.c index 226131f75..d06ed2f79 100644 --- a/src/test_multiplex.c +++ b/src/test_multiplex.c @@ -28,9 +28,9 @@ ** ** The procedure call above will create and register a new VFS shim named ** "multiplex". The multiplex VFS will use the VFS named by zOrigVfsName to -** do the actual disk I/O. (The zOrigVfsName parameter may be NULL, in +** do the actual disk I/O. (The zOrigVfsName parameter may be NULL, in ** which case the default VFS at the moment sqlite3_multiplex_initialize() -** is called will be used as the underlying real VFS.) +** is called will be used as the underlying real VFS.) ** ** If the makeDefault parameter is TRUE then multiplex becomes the new ** default VFS. Otherwise, you can use the multiplex VFS by specifying @@ -39,7 +39,7 @@ ** URI. ** ** The multiplex VFS allows databases up to 32 GiB in size. But it splits -** the files up into smaller pieces, so that they will work even on +** the files up into smaller pieces, so that they will work even on ** filesystems that do not support large files. The default chunk size ** is 2147418112 bytes (which is 64KiB less than 2GiB) but this can be ** changed at compile-time by defining the SQLITE_MULTIPLEX_CHUNK_SIZE @@ -58,10 +58,10 @@ #endif #include "sqlite3ext.h" -/* -** These should be defined to be the same as the values in +/* +** These should be defined to be the same as the values in ** sqliteInt.h. They are defined separately here so that -** the multiplex VFS shim can be built as a loadable +** the multiplex VFS shim can be built as a loadable ** module. */ #define UNUSED_PARAMETER(x) (void)(x) @@ -83,7 +83,7 @@ #endif /* This is the limit on the chunk size. It may be changed by calling -** the xFileControl() interface. It will be rounded up to a +** the xFileControl() interface. It will be rounded up to a ** multiple of MAX_PAGE_SIZE. We default it here to 2GiB less 64KiB. */ #ifndef SQLITE_MULTIPLEX_CHUNK_SIZE @@ -130,7 +130,7 @@ struct multiplexGroup { /* ** An instance of the following object represents each open connection -** to a file that is multiplex'ed. This object is a +** 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. */ @@ -157,11 +157,11 @@ static struct { */ sqlite3_vfs sThisVfs; - /* The sIoMethods defines the methods used by sqlite3_file objects + /* 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 + ** 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 @@ -199,16 +199,16 @@ static int multiplexStrlen30(const char *z){ ** nul-terminator. ** ** If iChunk is 0 (or 400 - the number for the first journal file chunk), -** the output is a copy of the input string. Otherwise, if +** the output is a copy of the input string. Otherwise, if ** SQLITE_ENABLE_8_3_NAMES is not defined or the input buffer does not contain -** a "." character, then the output is a copy of the input string with the -** three-digit zero-padded decimal representation if iChunk appended to it. +** a "." character, then the output is a copy of the input string with the +** three-digit zero-padded decimal representation if iChunk appended to it. ** For example: ** ** zBase="test.db", iChunk=4 -> zOut="test.db004" ** ** Or, if SQLITE_ENABLE_8_3_NAMES is defined and the input buffer contains -** a "." character, then everything after the "." is replaced by the +** a "." character, then everything after the "." is replaced by the ** three-digit representation of iChunk. ** ** zBase="test.db", iChunk=4 -> zOut="test.004" @@ -232,12 +232,12 @@ static void multiplexFilename( if( i>=n-4 ) n = i+1; if( flags & SQLITE_OPEN_MAIN_JOURNAL ){ /* The extensions on overflow files for main databases are 001, 002, - ** 003 and so forth. To avoid name collisions, add 400 to the + ** 003 and so forth. To avoid name collisions, add 400 to the ** extensions of journal files so that they are 401, 402, 403, .... */ iChunk += SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET; }else if( flags & SQLITE_OPEN_WAL ){ - /* To avoid name collisions, add 700 to the + /* To avoid name collisions, add 700 to the ** extensions of WAL files so that they are 701, 702, 703, .... */ iChunk += SQLITE_MULTIPLEX_WAL_8_3_OFFSET; @@ -297,8 +297,8 @@ static sqlite3_file *multiplexSubOpen( sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ #ifdef SQLITE_ENABLE_8_3_NAMES - /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are - ** part of a database journal are named db.401, db.402, and so on. A + /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are + ** part of a database journal are named db.401, db.402, and so on. A ** database may therefore not grow to larger than 400 chunks. Attempting ** to open chunk 401 indicates the database is full. */ if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){ @@ -351,8 +351,8 @@ static sqlite3_file *multiplexSubOpen( /* ** Return the size, in bytes, of chunk number iChunk. If that chunk -** does not exist, then return 0. This function does not distingish between -** non-existant files and zero-length files. +** does not exist, then return 0. This function does not distinguish between +** non-existent files and zero-length files. */ static sqlite3_int64 multiplexSubSize( multiplexGroup *pGroup, /* The multiplexor group */ @@ -367,7 +367,7 @@ static sqlite3_int64 multiplexSubSize( if( pSub==0 ) return 0; *rc = pSub->pMethods->xFileSize(pSub, &sz); return sz; -} +} /* ** This is the implementation of the multiplex_control() SQL function. @@ -382,22 +382,22 @@ static void multiplexControlFunc( int op = 0; int iVal; - if( !db || argc!=2 ){ - rc = SQLITE_ERROR; + if( !db || argc!=2 ){ + rc = SQLITE_ERROR; }else{ /* extract params */ op = sqlite3_value_int(argv[0]); iVal = sqlite3_value_int(argv[1]); /* map function op to file_control op */ switch( op ){ - case 1: - op = MULTIPLEX_CTRL_ENABLE; + case 1: + op = MULTIPLEX_CTRL_ENABLE; break; - case 2: - op = MULTIPLEX_CTRL_SET_CHUNK_SIZE; + case 2: + op = MULTIPLEX_CTRL_SET_CHUNK_SIZE; break; - case 3: - op = MULTIPLEX_CTRL_SET_MAX_CHUNKS; + case 3: + op = MULTIPLEX_CTRL_SET_MAX_CHUNKS; break; default: rc = SQLITE_NOTFOUND; @@ -411,16 +411,16 @@ static void multiplexControlFunc( } /* -** This is the entry point to register the auto-extension for the +** This is the entry point to register the auto-extension for the ** multiplex_control() function. */ static int multiplexFuncInit( - sqlite3 *db, - char **pzErrMsg, + sqlite3 *db, + char **pzErrMsg, const sqlite3_api_routines *pApi ){ int rc; - rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY, + rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY, 0, multiplexControlFunc, 0, 0); return rc; } @@ -508,7 +508,7 @@ static int multiplexOpen( memset(pGroup, 0, sz); pMultiplexOpen->pGroup = pGroup; pGroup->bEnabled = (unsigned char)-1; - pGroup->bTruncate = (unsigned char)sqlite3_uri_boolean(zUri, "truncate", + pGroup->bTruncate = (unsigned char)sqlite3_uri_boolean(zUri, "truncate", (flags & SQLITE_OPEN_MAIN_DB)==0); pGroup->szChunk = (int)sqlite3_uri_int64(zUri, "chunksize", SQLITE_MULTIPLEX_CHUNK_SIZE); @@ -551,11 +551,11 @@ static int multiplexOpen( if( sz64==0 ){ if( flags & SQLITE_OPEN_MAIN_JOURNAL ){ /* If opening a main journal file and the first chunk is zero - ** bytes in size, delete any subsequent chunks from the + ** bytes in size, delete any subsequent chunks from the ** file-system. */ int iChunk = 1; do { - rc = pOrigVfs->xAccess(pOrigVfs, + rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists ); if( rc==SQLITE_OK && bExists ){ @@ -573,8 +573,8 @@ static int multiplexOpen( ** ** Or, if the first overflow file does not exist and the main file is ** larger than the chunk size, that means the chunk size is too small. - ** But we have no way of determining the intended chunk size, so - ** just disable the multiplexor all togethre. + ** But we have no way of determining the intended chunk size, so + ** just disable the multiplexor all together. */ rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z, SQLITE_ACCESS_EXISTS, &bExists); @@ -618,7 +618,7 @@ static int multiplexDelete( rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir); if( rc==SQLITE_OK ){ /* If the main chunk was deleted successfully, also delete any subsequent - ** chunks - starting with the last (highest numbered). + ** chunks - starting with the last (highest numbered). */ int nName = (int)strlen(zName); char *z; @@ -695,7 +695,7 @@ static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){ /* 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 +** The group structure for this file is unlinked from ** our list of groups and freed. */ static int multiplexClose(sqlite3_file *pConn){ @@ -1008,7 +1008,7 @@ static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){ /* ** PRAGMA multiplex_chunksize; ** - ** Return the chunksize for the multiplexor, or no-op if the + ** Return the chunksize for the multiplexor, or no-op if the ** multiplexor is not active. */ if( sqlite3_stricmp(aFcntl[1],"multiplex_chunksize")==0 @@ -1138,8 +1138,8 @@ static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){ /* ** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() ** -** Use the VFS named zOrigVfsName as the VFS that does the actual work. -** Use the default if zOrigVfsName==NULL. +** 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. diff --git a/src/test_multiplex.h b/src/test_multiplex.h index 790c778a3..065fac7ea 100644 --- a/src/test_multiplex.h +++ b/src/test_multiplex.h @@ -13,8 +13,8 @@ ** This file contains a VFS "shim" - a layer that sits in between the ** pager and the real VFS. ** -** This particular shim enforces a multiplex system on DB files. -** This shim shards/partitions a single DB file into smaller +** This particular shim enforces a multiplex system on DB files. +** This shim shards/partitions a single DB file into smaller ** "chunks" such that the total DB file size may exceed the maximum ** file size of the underlying file system. ** @@ -33,14 +33,14 @@ ** shim. ** ** MULTIPLEX_CTRL_SET_CHUNK_SIZE: -** This file control is used to set the maximum allowed chunk -** size for a multiplex file set. The chunk size should be +** This file control is used to set the maximum allowed chunk +** size for a multiplex file set. The chunk size should be ** a multiple of SQLITE_MAX_PAGE_SIZE, and will be rounded up ** if not. ** ** MULTIPLEX_CTRL_SET_MAX_CHUNKS: ** This file control is used to set the maximum number of chunks -** allowed to be used for a mutliplex file set. +** allowed to be used for a multiplex file set. */ #define MULTIPLEX_CTRL_ENABLE 214014 #define MULTIPLEX_CTRL_SET_CHUNK_SIZE 214015 @@ -53,26 +53,26 @@ extern "C" { /* ** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() ** -** Use the VFS named zOrigVfsName as the VFS that does the actual work. -** Use the default if zOrigVfsName==NULL. +** 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. ** -** An auto-extension is registered which will make the function +** An auto-extension is registered which will make the function ** multiplex_control() available to database connections. This -** function gives access to the xFileControl interface of the +** function gives access to the xFileControl interface of the ** multiplex VFS shim. ** ** SELECT multiplex_control(,); -** +** ** =1 MULTIPLEX_CTRL_ENABLE ** =0 disable ** =1 enable -** +** ** =2 MULTIPLEX_CTRL_SET_CHUNK_SIZE ** int, chunk size -** +** ** =3 MULTIPLEX_CTRL_SET_MAX_CHUNKS ** int, max chunks ** diff --git a/src/test_mutex.c b/src/test_mutex.c index 8bd9ff85a..a203208ab 100644 --- a/src/test_mutex.c +++ b/src/test_mutex.c @@ -45,7 +45,7 @@ struct sqlite3_mutex { /* State variables */ static struct test_mutex_globals { int isInstalled; /* True if installed */ - int disableInit; /* True to cause sqlite3_initalize() to fail */ + int disableInit; /* True to cause sqlite3_initialize() to fail */ int disableTry; /* True to force sqlite3_mutex_try() to fail */ int isInit; /* True if initialized */ sqlite3_mutex_methods m; /* Interface to "real" mutex system */ diff --git a/src/test_pcache.c b/src/test_pcache.c index 8fcfe7e26..5266d6769 100644 --- a/src/test_pcache.c +++ b/src/test_pcache.c @@ -9,17 +9,17 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** +** ** This file contains code used for testing the SQLite system. ** None of the code in this file goes into a deliverable build. -** +** ** This file contains an application-defined pager cache ** implementation that can be plugged in in place of the ** default pcache. This alternative pager cache will throw ** some errors that the default cache does not. ** ** This pagecache implementation is designed for simplicity -** not speed. +** not speed. */ #include "sqlite3.h" #include @@ -36,7 +36,7 @@ struct testpcacheGlobalType { int nInstance; /* Number of current instances */ unsigned discardChance; /* Chance of discarding on an unpin (0-100) */ unsigned prngSeed; /* Seed for the PRNG */ - unsigned highStress; /* Call xStress agressively */ + unsigned highStress; /* Call xStress aggressively */ }; static testpcacheGlobalType testpcacheGlobal; @@ -131,8 +131,8 @@ static unsigned testpcacheRandom(testpcache *p){ ** Allocate a new page cache instance. */ static sqlite3_pcache *testpcacheCreate( - int szPage, - int szExtra, + int szPage, + int szExtra, int bPurgeable ){ int nMem; @@ -225,7 +225,7 @@ static sqlite3_pcache_page *testpcacheFetch( return 0; } - /* Do not allocate if highStress is enabled and createFlag is not 2. + /* Do not allocate if highStress is enabled and createFlag is not 2. ** ** The highStress setting causes pagerStress() to be called much more ** often, which exercises the pager logic more intensely. @@ -428,7 +428,7 @@ void installTestPCache( int installFlag, /* True to install. False to uninstall. */ unsigned discardChance, /* 0-100. Chance to discard on unpin */ unsigned prngSeed, /* Seed for the PRNG */ - unsigned highStress /* Call xStress agressively */ + unsigned highStress /* Call xStress aggressively */ ){ static const sqlite3_pcache_methods2 testPcache = { 1, diff --git a/src/test_quota.c b/src/test_quota.c index e87e9772f..b436de466 100644 --- a/src/test_quota.c +++ b/src/test_quota.c @@ -999,7 +999,7 @@ size_t sqlite3_quota_fwrite( const void *pBuf, /* Take content to write from here */ size_t size, /* Size of each element */ size_t nmemb, /* Number of elements */ - quota_FILE *p /* Write to this quota_FILE objecct */ + quota_FILE *p /* Write to this quota_FILE object */ ){ sqlite3_int64 iOfst; sqlite3_int64 iEnd; @@ -1876,7 +1876,7 @@ static int SQLITE_TCLAPI test_quota_glob( Tcl_Obj *CONST objv[] ){ const char *zPattern; /* The glob pattern */ - const char *zText; /* Text to compare agains the pattern */ + const char *zText; /* Text to compare against the pattern */ int rc; if( objc!=3 ){ Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT"); diff --git a/src/test_vfstrace.c b/src/test_vfstrace.c index d0bc29f0c..8b7d2fec7 100644 --- a/src/test_vfstrace.c +++ b/src/test_vfstrace.c @@ -787,7 +787,7 @@ static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ } /* -** Return th3 emost recent error code and message +** Return th3 most recent error code and message */ static int vfstraceGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){ vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; diff --git a/src/test_windirent.h b/src/test_windirent.h index ada532253..28ce66778 100644 --- a/src/test_windirent.h +++ b/src/test_windirent.h @@ -140,7 +140,7 @@ struct DIR { #endif /* -** Provide the function prototype for the POSIX compatiable getenv() +** Provide the function prototype for the POSIX compatible getenv() ** function. This function is not thread-safe. */ diff --git a/src/tokenize.c b/src/tokenize.c index 8dac9ece5..f4d013dee 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -433,7 +433,7 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ testcase( z[0]=='0' ); testcase( z[0]=='1' ); testcase( z[0]=='2' ); testcase( z[0]=='3' ); testcase( z[0]=='4' ); testcase( z[0]=='5' ); testcase( z[0]=='6' ); testcase( z[0]=='7' ); testcase( z[0]=='8' ); - testcase( z[0]=='9' ); + testcase( z[0]=='9' ); testcase( z[0]=='.' ); *tokenType = TK_INTEGER; #ifndef SQLITE_OMIT_HEX_INTEGER if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ @@ -505,7 +505,8 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ return i; } case CC_KYWD0: { - for(i=1; aiClass[z[i]]<=CC_KYWD; i++){} + if( aiClass[z[1]]>CC_KYWD ){ i = 1; break; } + for(i=2; aiClass[z[i]]<=CC_KYWD; i++){} if( IdChar(z[i]) ){ /* This token started out using characters that can appear in keywords, ** but z[i] is a character not allowed within keywords, so this must diff --git a/src/treeview.c b/src/treeview.c index 9f630b156..d55adab38 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -643,7 +643,8 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ }; assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT ); assert( pExpr->pRight ); - assert( sqlite3ExprSkipCollate(pExpr->pRight)->op==TK_TRUEFALSE ); + assert( sqlite3ExprSkipCollateAndLikely(pExpr->pRight)->op + == TK_TRUEFALSE ); x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight); zUniOp = azOp[x]; break; diff --git a/src/update.c b/src/update.c index c1f136398..cd7d73f3f 100644 --- a/src/update.c +++ b/src/update.c @@ -30,10 +30,10 @@ static void updateVirtualTable( /* ** The most recently coded instruction was an OP_Column to retrieve the -** i-th column of table pTab. This routine sets the P4 parameter of the +** i-th column of table pTab. This routine sets the P4 parameter of the ** OP_Column to the default value, if any. ** -** The default value of a column is specified by a DEFAULT clause in the +** The default value of a column is specified by a DEFAULT clause in the ** column definition. This was either supplied by the user when the table ** was created, or added later to the table definition by an ALTER TABLE ** command. If the latter, then the row-records in the table btree on disk @@ -42,9 +42,9 @@ static void updateVirtualTable( ** If the former, then all row-records are guaranteed to include a value ** for the column and the P4 value is not required. ** -** Column definitions created by an ALTER TABLE command may only have +** Column definitions created by an ALTER TABLE command may only have ** literal default values specified: a number, null or a string. (If a more -** complicated default expression value was provided, it is evaluated +** complicated default expression value was provided, it is evaluated ** when the ALTER TABLE is executed and one of the literal values written ** into the sqlite_schema table.) ** @@ -69,8 +69,8 @@ void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ assert( !IsView(pTab) ); VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName)); assert( inCol ); - sqlite3ValueFromExpr(sqlite3VdbeDb(v), - sqlite3ColumnExpr(pTab,pCol), enc, + sqlite3ValueFromExpr(sqlite3VdbeDb(v), + sqlite3ColumnExpr(pTab,pCol), enc, pCol->affinity, &pValue); if( pValue ){ sqlite3VdbeAppendP4(v, pValue, P4_MEM); @@ -150,17 +150,17 @@ static Expr *exprRowColumn(Parse *pParse, int iCol){ ** Assuming both the pLimit and pOrderBy parameters are NULL, this function ** generates VM code to run the query: ** -** SELECT , pChanges FROM pTabList WHERE pWhere +** SELECT , pChanges FROM pTabList WHERE pWhere ** -** and write the results to the ephemeral table already opened as cursor -** iEph. None of pChanges, pTabList or pWhere are modified or consumed by +** and write the results to the ephemeral table already opened as cursor +** iEph. None of pChanges, pTabList or pWhere are modified or consumed by ** this function, they must be deleted by the caller. ** ** Or, if pLimit and pOrderBy are not NULL, and pTab is not a view: ** -** SELECT , pChanges FROM pTabList +** SELECT , pChanges FROM pTabList ** WHERE pWhere -** GROUP BY +** GROUP BY ** ORDER BY pOrderBy LIMIT pLimit ** ** If pTab is a view, the GROUP BY clause is omitted. @@ -178,11 +178,11 @@ static Expr *exprRowColumn(Parse *pParse, int iCol){ ** the view. The results are written to the ephemeral table iEph as records ** with automatically assigned integer keys. ** -** If the table is a virtual or ordinary intkey table, then +** If the table is a virtual or ordinary intkey table, then ** is its rowid. For a virtual table, the results are written to iEph as ** records with automatically assigned integer keys For intkey tables, the -** rowid value in is used as the integer key, and the -** remaining fields make up the table record. +** rowid value in is used as the integer key, and the +** remaining fields make up the table record. */ static void updateFromSelect( Parse *pParse, /* Parse context */ @@ -224,7 +224,7 @@ static void updateFromSelect( assert( pTabList->nSrc>1 ); if( pSrc ){ - pSrc->a[0].fg.notCte = 1; + assert( pSrc->a[0].fg.notCte ); pSrc->a[0].iCursor = -1; pSrc->a[0].pTab->nTabRef--; pSrc->a[0].pTab = 0; @@ -257,13 +257,13 @@ static void updateFromSelect( assert( pChanges!=0 || pParse->db->mallocFailed ); if( pChanges ){ for(i=0; inExpr; i++){ - pList = sqlite3ExprListAppend(pParse, pList, + pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) ); } } - pSelect = sqlite3SelectNew(pParse, pList, - pSrc, pWhere2, pGrp, 0, pOrderBy2, + pSelect = sqlite3SelectNew(pParse, pList, + pSrc, pWhere2, pGrp, 0, pOrderBy2, SF_UFSrcCheck|SF_IncludeHidden|SF_UpdateFrom, pLimit2 ); if( pSelect ) pSelect->selFlags |= SF_OrderByReqd; @@ -357,7 +357,7 @@ void sqlite3Update( } assert( db->mallocFailed==0 ); - /* Locate the table which we want to update. + /* Locate the table which we want to update. */ pTab = sqlite3SrcListLookup(pParse, pTabList); if( pTab==0 ) goto update_cleanup; @@ -435,7 +435,7 @@ void sqlite3Update( } pTabList->a[0].iCursor = iDataCur; - /* Allocate space for aXRef[], aRegIdx[], and aToOpen[]. + /* Allocate space for aXRef[], aRegIdx[], and aToOpen[]. ** Initialize aXRef[] and aToOpen[] to their default values. */ aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx+1) + nIdx+2 ); @@ -486,7 +486,7 @@ void sqlite3Update( else if( pTab->aCol[j].colFlags & COLFLAG_GENERATED ){ testcase( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ); testcase( pTab->aCol[j].colFlags & COLFLAG_STORED ); - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "cannot UPDATE generated column \"%s\"", pTab->aCol[j].zCnName); goto update_cleanup; @@ -529,11 +529,11 @@ void sqlite3Update( #ifndef SQLITE_OMIT_GENERATED_COLUMNS /* Mark generated columns as changing if their generator expressions - ** reference any changing column. The actual aXRef[] value for + ** reference any changing column. The actual aXRef[] value for ** generated expressions is not used, other than to check to see that it ** is non-negative, so the value of aXRef[] for generated columns can be ** set to any non-negative number. We use 99999 so that the value is - ** obvious when looking at aXRef[] in a symbolic debugger. + ** obvious when looking at aXRef[] in a symbolic debugger. */ if( pTab->tabFlags & TF_HasGenerated ){ int bProgress; @@ -556,7 +556,7 @@ void sqlite3Update( } #endif - /* The SET expressions are not actually used inside the WHERE loop. + /* The SET expressions are not actually used inside the WHERE loop. ** So reset the colUsed mask. Unless this is a virtual table. In that ** case, set all bits of the colUsed mask (to ensure that the virtual ** table implementation makes all columns available). @@ -595,7 +595,7 @@ void sqlite3Update( } aRegIdx[nAllIdx] = ++pParse->nMem; /* Register storing the table record */ if( bReplace ){ - /* If REPLACE conflict resolution might be invoked, open cursors on all + /* If REPLACE conflict resolution might be invoked, open cursors on all ** indexes in case they are needed to delete records. */ memset(aToOpen, 1, nIdx+1); } @@ -634,7 +634,7 @@ void sqlite3Update( */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( nChangeFrom==0 && isView ){ - sqlite3MaterializeView(pParse, pTab, + sqlite3MaterializeView(pParse, pTab, pWhere, pOrderBy, pLimit, iDataCur ); pOrderBy = 0; @@ -706,7 +706,7 @@ void sqlite3Update( } } } - + if( nChangeFrom ){ sqlite3MultiWrite(pParse); eOnePass = ONEPASS_OFF; @@ -724,7 +724,7 @@ void sqlite3Update( sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL); bFinishSeek = 0; }else{ - /* Begin the database scan. + /* Begin the database scan. ** ** Do not consider a single-pass strategy for a multi-row update if ** there is anything that might disrupt the cursor being used to do @@ -741,7 +741,7 @@ void sqlite3Update( && !hasFK && !chngKey && !bReplace - && (sNC.ncFlags & NC_Subquery)==0 + && (pWhere==0 || !ExprHasProperty(pWhere, EP_Subquery)) ){ flags |= WHERE_ONEPASS_MULTIROW; } @@ -787,7 +787,7 @@ void sqlite3Update( /* Read the PK of the current row into an array of registers. In ** ONEPASS_OFF mode, serialize the array into a record and store it in ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change - ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table + ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table ** is not required) and leave the PK fields in the array of registers. */ for(i=0; iaiColumn[i]>=0 ); @@ -810,26 +810,28 @@ void sqlite3Update( if( nChangeFrom==0 && eOnePass!=ONEPASS_MULTI ){ sqlite3WhereEnd(pWInfo); } - + if( !isView ){ int addrOnce = 0; - + int iNotUsed1 = 0; + int iNotUsed2 = 0; + /* Open every index that needs updating. */ if( eOnePass!=ONEPASS_OFF ){ if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0; if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0; } - + if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, - aToOpen, 0, 0); + aToOpen, &iNotUsed1, &iNotUsed2); if( addrOnce ){ sqlite3VdbeJumpHereOrPopInst(v, addrOnce); } } - + /* Top of the update loop */ if( eOnePass!=ONEPASS_OFF ){ if( aiCurOnePass[0]!=iDataCur @@ -902,7 +904,7 @@ void sqlite3Update( ** information is needed */ if( chngPk || hasFK || pTrigger ){ u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); - oldmask |= sqlite3TriggerColmask(pParse, + oldmask |= sqlite3TriggerColmask(pParse, pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError ); for(i=0; inCol; i++){ @@ -931,8 +933,8 @@ void sqlite3Update( ** If there are one or more BEFORE triggers, then do not populate the ** registers associated with columns that are (a) not modified by ** this UPDATE statement and (b) not accessed by new.* references. The - ** values for registers not modified by the UPDATE must be reloaded from - ** the database after the BEFORE triggers are fired anyway (as the trigger + ** values for registers not modified by the UPDATE must be reloaded from + ** the database after the BEFORE triggers are fired anyway (as the trigger ** may have modified them). So not loading those that are not going to ** be used eliminates some redundant opcodes. */ @@ -955,7 +957,7 @@ void sqlite3Update( sqlite3ExprCode(pParse, pChanges->a[j].pExpr, k); } }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask & MASKBIT32(i)) ){ - /* This branch loads the value of a column that will not be changed + /* This branch loads the value of a column that will not be changed ** into a register. This is done if there are no BEFORE triggers, or ** if there are one or more BEFORE triggers that use this value via ** a new.* reference in a trigger program. @@ -982,12 +984,12 @@ void sqlite3Update( */ if( tmask&TRIGGER_BEFORE ){ sqlite3TableAffinity(v, pTab, regNew); - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue); if( !isView ){ /* The row-trigger may have deleted the row being updated. In this - ** case, jump to the next row. No updates or AFTER triggers are + ** case, jump to the next row. No updates or AFTER triggers are ** required. This behavior - what happens when the row being updated ** is deleted or renamed by a BEFORE trigger - is left undefined in the ** documentation. @@ -1001,8 +1003,8 @@ void sqlite3Update( } /* After-BEFORE-trigger-reload-loop: - ** If it did not delete it, the BEFORE trigger may still have modified - ** some of the columns of the row being updated. Load the values for + ** If it did not delete it, the BEFORE trigger may still have modified + ** some of the columns of the row being updated. Load the values for ** all columns not modified by the update statement into their registers ** in case this has happened. Only unmodified columns are reloaded. ** The values computed for modified columns use the values before the @@ -1022,7 +1024,7 @@ void sqlite3Update( testcase( pTab->tabFlags & TF_HasStored ); sqlite3ComputeGeneratedColumns(pParse, regNew, pTab); } -#endif +#endif } } @@ -1066,7 +1068,7 @@ void sqlite3Update( ** to process, delete the old record. Otherwise, add a noop OP_Delete ** to invoke the pre-update hook. ** - ** That (regNew==regnewRowid+1) is true is also important for the + ** That (regNew==regnewRowid+1) is true is also important for the ** pre-update hook. If the caller invokes preupdate_new(), the returned ** value is copied from memory cell (regNewRowid+1+iCol), where iCol ** is the column index supplied by the user. @@ -1093,30 +1095,32 @@ void sqlite3Update( if( hasFK ){ sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey); } - + /* Insert the new index entries and the new record. */ sqlite3CompleteInsertion( - pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx, - OPFLAG_ISUPDATE | (eOnePass==ONEPASS_MULTI ? OPFLAG_SAVEPOSITION : 0), + pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx, + OPFLAG_ISUPDATE | (eOnePass==ONEPASS_MULTI ? OPFLAG_SAVEPOSITION : 0), 0, 0 ); /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key - ** to the row just updated. */ + ** to the row just updated. */ if( hasFK ){ sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey); } } - /* Increment the row counter + /* Increment the row counter */ if( regRowCount ){ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); + } /* Repeat the above with the next record to be updated, until ** all record selected by the WHERE clause have been updated. @@ -1154,7 +1158,7 @@ void sqlite3Update( sqlite3SrcListDelete(db, pTabList); sqlite3ExprListDelete(db, pChanges); sqlite3ExprDelete(db, pWhere); -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) sqlite3ExprListDelete(db, pOrderBy); sqlite3ExprDelete(db, pLimit); #endif @@ -1174,8 +1178,8 @@ void sqlite3Update( /* ** Generate code for an UPDATE of a virtual table. ** -** There are two possible strategies - the default and the special -** "onepass" strategy. Onepass is only used if the virtual table +** There are two possible strategies - the default and the special +** "onepass" strategy. Onepass is only used if the virtual table ** implementation indicates that pWhere may match at most one row. ** ** The default strategy is to create an ephemeral table that contains @@ -1211,7 +1215,7 @@ static void updateVirtualTable( int nArg = 2 + pTab->nCol; /* Number of arguments to VUpdate */ int regArg; /* First register in VUpdate arg array */ int regRec; /* Register in which to assemble record */ - int regRowid; /* Register for ephem table rowid */ + int regRowid; /* Register for ephemeral table rowid */ int iCsr = pSrc->a[0].iCursor; /* Cursor used for virtual table scan */ int aDummy[2]; /* Unused arg for sqlite3WhereOkOnePass() */ int eOnePass; /* True to use onepass strategy */ @@ -1255,7 +1259,9 @@ static void updateVirtualTable( sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0) ); }else{ - pList = sqlite3ExprListAppend(pParse, pList, exprRowColumn(pParse, i)); + Expr *pRowExpr = exprRowColumn(pParse, i); + if( pRowExpr ) pRowExpr->op2 = OPFLAG_NOCHNG; + pList = sqlite3ExprListAppend(pParse, pList, pRowExpr); } } @@ -1332,10 +1338,10 @@ static void updateVirtualTable( sqlite3WhereEnd(pWInfo); } - /* Begin scannning through the ephemeral table. */ + /* Begin scanning through the ephemeral table. */ addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v); - /* Extract arguments from the current row of the ephemeral table and + /* Extract arguments from the current row of the ephemeral table and ** invoke the VUpdate method. */ for(i=0; ia[jj].pExpr,pExpr,iCursor)<2 ){ + if( sqlite3ExprCompare(0,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){ break; /* Column ii of the index matches column jj of target */ } } diff --git a/src/util.c b/src/util.c index 3d4e01438..97deb64cf 100644 --- a/src/util.c +++ b/src/util.c @@ -23,8 +23,8 @@ /* ** Calls to sqlite3FaultSim() are used to simulate a failure during testing, -** or to bypass normal error detection during testing in order to let -** execute proceed futher downstream. +** or to bypass normal error detection during testing in order to let +** execute proceed further downstream. ** ** In deployment, sqlite3FaultSim() *always* return SQLITE_OK (0). The ** sqlite3FaultSim() function only returns non-zero during testing. @@ -82,7 +82,7 @@ int sqlite3Strlen30(const char *z){ } /* -** Return the declared type of a column. Or return zDflt if the column +** Return the declared type of a column. Or return zDflt if the column ** has no declared type. ** ** The column type is an extra string stored after the zero-terminator on @@ -141,6 +141,23 @@ void sqlite3ErrorClear(sqlite3 *db){ */ void sqlite3SystemError(sqlite3 *db, int rc){ if( rc==SQLITE_IOERR_NOMEM ) return; +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_IOERR_IN_PAGE ){ + int ii; + int iErr; + sqlite3BtreeEnterAll(db); + for(ii=0; iinDb; ii++){ + if( db->aDb[ii].pBt ){ + iErr = sqlite3PagerWalSystemErrno(sqlite3BtreePager(db->aDb[ii].pBt)); + if( iErr ){ + db->iSysErrno = iErr; + } + } + } + sqlite3BtreeLeaveAll(db); + return; + } +#endif rc &= 0xff; if( rc==SQLITE_CANTOPEN || rc==SQLITE_IOERR ){ db->iSysErrno = sqlite3OsGetLastError(db->pVfs); @@ -386,43 +403,40 @@ u8 sqlite3StrIHash(const char *z){ return h; } -/* -** Compute 10 to the E-th power. Examples: E==1 results in 10. -** E==2 results in 100. E==50 results in 1.0e50. +/* Double-Double multiplication. (x[0],x[1]) *= (y,yy) ** -** This routine only works for values of E between 1 and 341. +** Reference: +** T. J. Dekker, "A Floating-Point Technique for Extending the +** Available Precision". 1971-07-26. */ -static LONGDOUBLE_TYPE sqlite3Pow10(int E){ -#if defined(_MSC_VER) - static const LONGDOUBLE_TYPE x[] = { - 1.0e+001L, - 1.0e+002L, - 1.0e+004L, - 1.0e+008L, - 1.0e+016L, - 1.0e+032L, - 1.0e+064L, - 1.0e+128L, - 1.0e+256L - }; - LONGDOUBLE_TYPE r = 1.0; - int i; - assert( E>=0 && E<=307 ); - for(i=0; E!=0; i++, E >>=1){ - if( E & 1 ) r *= x[i]; - } - return r; -#else - LONGDOUBLE_TYPE x = 10.0; - LONGDOUBLE_TYPE r = 1.0; - while(1){ - if( E & 1 ) r *= x; - E >>= 1; - if( E==0 ) break; - x *= x; - } - return r; -#endif +static void dekkerMul2(volatile double *x, double y, double yy){ + /* + ** The "volatile" keywords on parameter x[] and on local variables + ** below are needed force intermediate results to be truncated to + ** binary64 rather than be carried around in an extended-precision + ** format. The truncation is necessary for the Dekker algorithm to + ** work. Intel x86 floating point might omit the truncation without + ** the use of volatile. + */ + volatile double tx, ty, p, q, c, cc; + double hx, hy; + u64 m; + memcpy(&m, (void*)&x[0], 8); + m &= 0xfffffffffc000000LL; + memcpy(&hx, &m, 8); + tx = x[0] - hx; + memcpy(&m, &y, 8); + m &= 0xfffffffffc000000LL; + memcpy(&hy, &m, 8); + ty = y - hy; + p = hx*hy; + q = hx*ty + tx*hy; + c = p+q; + cc = p - c + q + tx*ty; + cc = x[0]*yy + x[1]*y + cc; + x[0] = c + cc; + x[1] = c - x[0]; + x[1] += cc; } /* @@ -438,7 +452,7 @@ static LONGDOUBLE_TYPE sqlite3Pow10(int E){ ** 1 => The input string is a pure integer ** 2 or more => The input has a decimal point or eNNN clause ** 0 or less => The input string is not a valid number -** -1 => Not a valid number, but has a valid prefix which +** -1 => Not a valid number, but has a valid prefix which ** includes a decimal point and/or an eNNN clause ** ** Valid numbers are in one of these formats: @@ -463,12 +477,11 @@ int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ const char *zEnd; /* sign * significand * (10 ^ (esign * exponent)) */ int sign = 1; /* sign of significand */ - i64 s = 0; /* significand */ + u64 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 nDigit = 0; /* Number of digits processed */ int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ @@ -508,7 +521,7 @@ int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ while( z=((LARGEST_INT64-9)/10) ){ + if( s>=((LARGEST_UINT64-9)/10) ){ /* skip non-significant significand digits ** (increase exponent by d to shift decimal left) */ while( z=zEnd ) goto do_atof_calc; /*PREVENTS-HARMLESS-OVERREAD*/ @@ -563,79 +576,89 @@ int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ while( z0 ){ /*OPTIMIZATION-IF-TRUE*/ - if( esign>0 ){ - if( s>=(LARGEST_INT64/10) ) break; /*OPTIMIZATION-IF-FALSE*/ - s *= 10; - }else{ - if( s%10!=0 ) break; /*OPTIMIZATION-IF-FALSE*/ - s /= 10; - } - e--; - } - /* adjust the sign of significand */ - s = sign<0 ? -s : s; + /* Try to adjust the exponent to make it smaller */ + while( e>0 && s<(LARGEST_UINT64/10) ){ + s *= 10; + e--; + } + while( e<0 && (s%10)==0 ){ + s /= 10; + e++; + } - if( e==0 ){ /*OPTIMIZATION-IF-TRUE*/ - result = (double)s; + if( e==0 ){ + *pResult = s; + }else if( sqlite3Config.bUseLongDouble ){ + LONGDOUBLE_TYPE r = (LONGDOUBLE_TYPE)s; + if( e>0 ){ + while( e>=100 ){ e-=100; r *= 1.0e+100L; } + while( e>=10 ){ e-=10; r *= 1.0e+10L; } + while( e>=1 ){ e-=1; r *= 1.0e+01L; } }else{ - /* attempt to handle extremely small/large numbers better */ - if( e>307 ){ /*OPTIMIZATION-IF-TRUE*/ - if( e<342 ){ /*OPTIMIZATION-IF-TRUE*/ - LONGDOUBLE_TYPE scale = sqlite3Pow10(e-308); - if( esign<0 ){ - result = s / scale; - result /= 1.0e+308; - }else{ - result = s * scale; - result *= 1.0e+308; - } - }else{ assert( e>=342 ); - if( esign<0 ){ - result = 0.0*s; - }else{ + while( e<=-100 ){ e+=100; r *= 1.0e-100L; } + while( e<=-10 ){ e+=10; r *= 1.0e-10L; } + while( e<=-1 ){ e+=1; r *= 1.0e-01L; } + } + assert( r>=0.0 ); + if( r>+1.7976931348623157081452742373e+308L ){ #ifdef INFINITY - result = INFINITY*s; + *pResult = +INFINITY; #else - result = 1e308*1e308*s; /* Infinity */ + *pResult = 1.0e308*10.0; #endif - } - } - }else{ - LONGDOUBLE_TYPE scale = sqlite3Pow10(e); - if( esign<0 ){ - result = s / scale; - }else{ - result = s * scale; - } + }else{ + *pResult = (double)r; + } + }else{ + double rr[2]; + u64 s2; + rr[0] = (double)s; + s2 = (u64)rr[0]; + rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); + if( e>0 ){ + while( e>=100 ){ + e -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( e>=10 ){ + e -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( e>=1 ){ + e -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } + }else{ + while( e<=-100 ){ + e += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( e<=-10 ){ + e += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( e<=-1 ){ + e += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); } } + *pResult = rr[0]+rr[1]; + if( sqlite3IsNaN(*pResult) ) *pResult = 1e300*1e300; } + if( sign<0 ) *pResult = -*pResult; + assert( !sqlite3IsNaN(*pResult) ); - /* store the result */ - *pResult = result; - - /* return true if number and no extra non-whitespace chracters after */ +atof_return: + /* return true if number and no extra non-whitespace characters after */ if( z==zEnd && nDigit>0 && eValid && eType>0 ){ return eType; }else if( eType>=2 && (eType==3 || eValid) && nDigit>0 ){ @@ -771,7 +794,7 @@ int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){ /* This test and assignment is needed only to suppress UB warnings ** from clang and -fsanitize=undefined. This test and assignment make ** the code a little larger and slower, and no harm comes from omitting - ** them, but we must appaise the undefined-behavior pharisees. */ + ** them, but we must appease the undefined-behavior pharisees. */ *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; }else if( neg ){ *pNum = -(i64)u; @@ -849,7 +872,9 @@ int sqlite3DecOrHexToI64(const char *z, i64 *pOut){ }else #endif /* SQLITE_OMIT_HEX_INTEGER */ { - return sqlite3Atoi64(z, pOut, sqlite3Strlen30(z), SQLITE_UTF8); + int n = (int)(0x3fffffff&strspn(z,"+- \n\t0123456789")); + if( z[n] ) n++; + return sqlite3Atoi64(z, pOut, n, SQLITE_UTF8); } } @@ -928,6 +953,153 @@ int sqlite3Atoi(const char *z){ return x; } +/* +** Decode a floating-point value into an approximate decimal +** representation. +** +** Round the decimal representation to n significant digits if +** n is positive. Or round to -n signficant digits after the +** decimal point if n is negative. No rounding is performed if +** n is zero. +** +** The significant digits of the decimal representation are +** stored in p->z[] which is a often (but not always) a pointer +** into the middle of p->zBuf[]. There are p->n significant digits. +** The p->z[] array is *not* zero-terminated. +*/ +void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ + int i; + u64 v; + int e, exp = 0; + p->isSpecial = 0; + p->z = p->zBuf; + + /* Convert negative numbers to positive. Deal with Infinity, 0.0, and + ** NaN. */ + if( r<0.0 ){ + p->sign = '-'; + r = -r; + }else if( r==0.0 ){ + p->sign = '+'; + p->n = 1; + p->iDP = 1; + p->z = "0"; + return; + }else{ + p->sign = '+'; + } + memcpy(&v,&r,8); + e = v>>52; + if( (e&0x7ff)==0x7ff ){ + p->isSpecial = 1 + (v!=0x7ff0000000000000LL); + p->n = 0; + p->iDP = 0; + return; + } + + /* Multiply r by powers of ten until it lands somewhere in between + ** 1.0e+19 and 1.0e+17. + */ + if( sqlite3Config.bUseLongDouble ){ + LONGDOUBLE_TYPE rr = r; + if( rr>=1.0e+19 ){ + while( rr>=1.0e+119L ){ exp+=100; rr *= 1.0e-100L; } + while( rr>=1.0e+29L ){ exp+=10; rr *= 1.0e-10L; } + while( rr>=1.0e+19L ){ exp++; rr *= 1.0e-1L; } + }else{ + while( rr<1.0e-97L ){ exp-=100; rr *= 1.0e+100L; } + while( rr<1.0e+07L ){ exp-=10; rr *= 1.0e+10L; } + while( rr<1.0e+17L ){ exp--; rr *= 1.0e+1L; } + } + v = (u64)rr; + }else{ + /* If high-precision floating point is not available using "long double", + ** then use Dekker-style double-double computation to increase the + ** precision. + ** + ** The error terms on constants like 1.0e+100 computed using the + ** decimal extension, for example as follows: + ** + ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); + */ + double rr[2]; + rr[0] = r; + rr[1] = 0.0; + if( rr[0]>1.84e+19 ){ + while( rr[0]>1.84e+119 ){ + exp += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( rr[0]>1.84e+29 ){ + exp += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( rr[0]>1.84e+19 ){ + exp += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } + }else{ + while( rr[0]<1.84e-82 ){ + exp -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( rr[0]<1.84e+08 ){ + exp -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( rr[0]<1.84e+18 ){ + exp -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } + } + v = rr[1]<0.0 ? (u64)rr[0]-(u64)(-rr[1]) : (u64)rr[0]+(u64)rr[1]; + } + + + /* Extract significant digits. */ + i = sizeof(p->zBuf)-1; + assert( v>0 ); + while( v ){ p->zBuf[i--] = (v%10) + '0'; v /= 10; } + assert( i>=0 && izBuf)-1 ); + p->n = sizeof(p->zBuf) - 1 - i; + assert( p->n>0 ); + assert( p->nzBuf) ); + p->iDP = p->n + exp; + if( iRound<0 ){ + iRound = p->iDP - iRound; + if( iRound==0 && p->zBuf[i+1]>='5' ){ + iRound = 1; + p->zBuf[i--] = '0'; + p->n++; + p->iDP++; + } + } + if( iRound>0 && (iRoundn || p->n>mxRound) ){ + char *z = &p->zBuf[i+1]; + if( iRound>mxRound ) iRound = mxRound; + p->n = iRound; + if( z[iRound]>='5' ){ + int j = iRound-1; + while( 1 /*exit-by-break*/ ){ + z[j]++; + if( z[j]<='9' ) break; + z[j] = '0'; + if( j==0 ){ + p->z[i--] = '1'; + p->n++; + p->iDP++; + break; + }else{ + j--; + } + } + } + } + p->z = &p->zBuf[i+1]; + assert( i+p->n < sizeof(p->zBuf) ); + while( ALWAYS(p->n>0) && p->z[p->n-1]=='0' ){ p->n--; } +} + /* ** Try to convert z into an unsigned 32-bit integer. Return true on ** success and false if there is an error. @@ -986,7 +1158,7 @@ static int SQLITE_NOINLINE putVarint64(unsigned char *p, u64 v){ v >>= 7; } return 9; - } + } n = 0; do{ buf[n++] = (u8)((v & 0x7f) | 0x80); @@ -1186,8 +1358,8 @@ u8 sqlite3GetVarint(const unsigned char *p, u64 *v){ ** If the varint stored in p[0] is larger than can fit in a 32-bit unsigned ** integer, then set *v to 0xffffffff. ** -** A MACRO version, getVarint32, is provided which inlines the -** single-byte case. All code should use the MACRO version as +** A MACRO version, getVarint32, is provided which inlines the +** single-byte case. All code should use the MACRO version as ** this function assumes the single-byte case has already been handled. */ u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){ @@ -1404,7 +1576,7 @@ void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){ ** argument. The zType is a word like "NULL" or "closed" or "invalid". */ static void logBadConnection(const char *zType){ - sqlite3_log(SQLITE_MISUSE, + sqlite3_log(SQLITE_MISUSE, "API call with %s database connection pointer", zType ); @@ -1456,7 +1628,7 @@ int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ } /* -** Attempt to add, substract, or multiply the 64-bit signed value iB against +** Attempt to add, subtract, or multiply the 64-bit signed value iB against ** the other 64-bit signed integer at *pA and store the result in *pA. ** Return 0 on success. Or if the operation would have resulted in an ** overflow, leave *pA unchanged and return 1. @@ -1478,7 +1650,7 @@ int sqlite3AddInt64(i64 *pA, i64 iB){ if( iA<0 && -(iA + LARGEST_INT64) > iB + 1 ) return 1; } *pA += iB; - return 0; + return 0; #endif } int sqlite3SubInt64(i64 *pA, i64 iB){ @@ -1519,7 +1691,7 @@ int sqlite3MulInt64(i64 *pA, i64 iB){ } /* -** Compute the absolute value of a 32-bit signed integer, of possible. Or +** Compute the absolute value of a 32-bit signed integer, of possible. Or ** if the integer has a value of -2147483648, return +2147483647 */ int sqlite3AbsInt32(int x){ @@ -1559,11 +1731,11 @@ void sqlite3FileSuffix3(const char *zBaseFilename, char *z){ } #endif -/* +/* ** Find (an approximate) sum of two LogEst values. This computation is ** not a simple "+" operator because LogEst is stored as a logarithmic ** value. -** +** */ LogEst sqlite3LogEstAdd(LogEst a, LogEst b){ static const unsigned char x[] = { @@ -1661,8 +1833,8 @@ u64 sqlite3LogEstToInt(LogEst x){ ** Conceptually: ** ** struct VList { -** int nAlloc; // Number of allocated slots -** int nUsed; // Number of used slots +** int nAlloc; // Number of allocated slots +** int nUsed; // Number of used slots ** struct VListEntry { ** int iValue; // Value for this entry ** int nSlot; // Slots used by this entry @@ -1671,7 +1843,7 @@ u64 sqlite3LogEstToInt(LogEst x){ ** } ** ** During code generation, pointers to the variable names within the -** VList are taken. When that happens, nAlloc is set to zero as an +** VList are taken. When that happens, nAlloc is set to zero as an ** indication that the VList may never again be enlarged, since the ** accompanying realloc() would invalidate the pointers. */ @@ -1747,6 +1919,6 @@ int sqlite3VListNameToNum(VList *pIn, const char *zName, int nName){ */ #if defined(VDBE_PROFILE) \ || defined(SQLITE_PERFORMANCE_TRACE) \ - || defined(SQLITE_ENABLE_STMT_SCANSTATUS) + || defined(SQLITE_ENABLE_STMT_SCANSTATUS) # include "hwtime.h" #endif diff --git a/src/vacuum.c b/src/vacuum.c index 9908cf142..c0ae4bc1e 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -208,7 +208,7 @@ SQLITE_NOINLINE int sqlite3RunVacuum( ** (possibly synchronous) transaction opened on the main database before ** sqlite3BtreeCopyFile() is called. ** - ** An optimisation would be to use a non-journaled pager. + ** An optimization would be to use a non-journaled pager. ** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but ** that actually made the VACUUM run slower. Very little journalling ** actually occurs when doing a vacuum since the vacuum_db is initially diff --git a/src/vdbe.c b/src/vdbe.c index 2aa4e6df2..5899fa518 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -154,7 +154,7 @@ static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){ ** ** In other words, if M is 2, then I is either 0 (for fall-through) or ** 1 (for when the branch is taken). If M is 3, the I is 0 for an -** ordinary fall-through, I is 1 if the branch was taken, and I is 2 +** ordinary fall-through, I is 1 if the branch was taken, and I is 2 ** if the result of comparison is NULL. For M=3, I=2 the jump may or ** may not be taken, depending on the SQLITE_JUMPIFNULL flags in p5. ** When M is 4, that means that an OP_Jump is being run. I is 0, 1, or 2 @@ -247,7 +247,7 @@ static VdbeCursor *allocateCursor( u8 eCurType /* Type of the new cursor */ ){ /* Find the memory cell that will be used to store the blob of memory - ** required for this VdbeCursor structure. It is convenient to use a + ** required for this VdbeCursor structure. It is convenient to use a ** vdbe memory cell to manage the memory allocation required for a ** VdbeCursor structure for the following reasons: ** @@ -268,8 +268,8 @@ static VdbeCursor *allocateCursor( int nByte; VdbeCursor *pCx = 0; - nByte = - ROUND8P(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + + nByte = + ROUND8P(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0); assert( iCur>=0 && iCurnCursor ); @@ -368,7 +368,7 @@ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ ** SQLITE_AFF_INTEGER: ** SQLITE_AFF_REAL: ** SQLITE_AFF_NUMERIC: -** Try to convert pRec to an integer representation or a +** Try to convert pRec to an integer representation or a ** floating-point representation if an integer representation ** is not possible. Note that the integer representation is ** always preferred, even if the affinity is REAL, because @@ -403,7 +403,7 @@ static void applyAffinity( }else if( affinity==SQLITE_AFF_TEXT ){ /* Only attempt the conversion to TEXT if there is an integer or real ** representation (blob and NULL do not get converted) but no string - ** representation. It would be harmless to repeat the conversion if + ** representation. It would be harmless to repeat the conversion if ** there is already a string rep, but it is pointless to waste those ** CPU cycles. */ if( 0==(pRec->flags&MEM_Str) ){ /*OPTIMIZATION-IF-FALSE*/ @@ -435,12 +435,12 @@ int sqlite3_value_numeric_type(sqlite3_value *pVal){ } /* -** Exported version of applyAffinity(). This one works on sqlite3_value*, +** Exported version of applyAffinity(). This one works on sqlite3_value*, ** not the internal Mem* type. */ void sqlite3ValueApplyAffinity( - sqlite3_value *pVal, - u8 affinity, + sqlite3_value *pVal, + u8 affinity, u8 enc ){ applyAffinity((Mem *)pVal, affinity, enc); @@ -478,7 +478,7 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ /* ** Return the numeric type for pMem, either MEM_Int or MEM_Real or both or -** none. +** none. ** ** Unlike applyNumericAffinity(), this routine does not modify pMem->flags. ** But it does set pMem->u.r and pMem->u.i appropriately. @@ -556,6 +556,9 @@ void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr){ sqlite3_str_appendchar(pStr, 1, (c>=0x20&&c<=0x7f) ? c : '.'); } sqlite3_str_appendf(pStr, "]%s", encnames[pMem->enc]); + if( f & MEM_Term ){ + sqlite3_str_appendf(pStr, "(0-term)"); + } } } #endif @@ -628,9 +631,9 @@ void sqlite3VdbeRegisterDump(Vdbe *v){ /* ** This function is only called from within an assert() expression. It ** checks that the sqlite3.nTransaction variable is correctly set to -** the number of non-transaction savepoints currently in the +** the number of non-transaction savepoints currently in the ** linked list starting at sqlite3.pSavepoint. -** +** ** Usage: ** ** assert( checkSavepointCount(db) ); @@ -692,6 +695,93 @@ static u64 filterHash(const Mem *aMem, const Op *pOp){ return h; } + +/* +** For OP_Column, factor out the case where content is loaded from +** overflow pages, so that the code to implement this case is separate +** the common case where all content fits on the page. Factoring out +** the code reduces register pressure and helps the common case +** to run faster. +*/ +static SQLITE_NOINLINE int vdbeColumnFromOverflow( + VdbeCursor *pC, /* The BTree cursor from which we are reading */ + int iCol, /* The column to read */ + int t, /* The serial-type code for the column value */ + i64 iOffset, /* Offset to the start of the content value */ + u32 cacheStatus, /* Current Vdbe.cacheCtr value */ + u32 colCacheCtr, /* Current value of the column cache counter */ + Mem *pDest /* Store the value into this register. */ +){ + int rc; + sqlite3 *db = pDest->db; + int encoding = pDest->enc; + int len = sqlite3VdbeSerialTypeLen(t); + assert( pC->eCurType==CURTYPE_BTREE ); + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) return SQLITE_TOOBIG; + if( len > 4000 && pC->pKeyInfo==0 ){ + /* Cache large column values that are on overflow pages using + ** an RCStr (reference counted string) so that if they are reloaded, + ** that do not have to be copied a second time. The overhead of + ** creating and managing the cache is such that this is only + ** profitable for larger TEXT and BLOB values. + ** + ** Only do this on table-btrees so that writes to index-btrees do not + ** need to clear the cache. This buys performance in the common case + ** in exchange for generality. + */ + VdbeTxtBlbCache *pCache; + char *pBuf; + if( pC->colCache==0 ){ + pC->pCache = sqlite3DbMallocZero(db, sizeof(VdbeTxtBlbCache) ); + if( pC->pCache==0 ) return SQLITE_NOMEM; + pC->colCache = 1; + } + pCache = pC->pCache; + if( pCache->pCValue==0 + || pCache->iCol!=iCol + || pCache->cacheStatus!=cacheStatus + || pCache->colCacheCtr!=colCacheCtr + || pCache->iOffset!=sqlite3BtreeOffset(pC->uc.pCursor) + ){ + if( pCache->pCValue ) sqlite3RCStrUnref(pCache->pCValue); + pBuf = pCache->pCValue = sqlite3RCStrNew( len+3 ); + if( pBuf==0 ) return SQLITE_NOMEM; + rc = sqlite3BtreePayload(pC->uc.pCursor, iOffset, len, pBuf); + if( rc ) return rc; + pBuf[len] = 0; + pBuf[len+1] = 0; + pBuf[len+2] = 0; + pCache->iCol = iCol; + pCache->cacheStatus = cacheStatus; + pCache->colCacheCtr = colCacheCtr; + pCache->iOffset = sqlite3BtreeOffset(pC->uc.pCursor); + }else{ + pBuf = pCache->pCValue; + } + assert( t>=12 ); + sqlite3RCStrRef(pBuf); + if( t&1 ){ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, encoding, + (void(*)(void*))sqlite3RCStrUnref); + pDest->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, 0, + (void(*)(void*))sqlite3RCStrUnref); + } + }else{ + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, iOffset, len, pDest); + if( rc ) return rc; + sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); + if( (t&1)!=0 && encoding==SQLITE_UTF8 ){ + pDest->z[len] = 0; + pDest->flags |= MEM_Term; + } + } + pDest->flags &= ~MEM_Ephem; + return rc; +} + + /* ** Return the symbolic name for the data type of a pMem */ @@ -708,7 +798,7 @@ static const char *vdbeMemTypeName(Mem *pMem){ /* ** Execute as much of a VDBE program as we can. -** This is the core of sqlite3_step(). +** This is the core of sqlite3_step(). */ int sqlite3VdbeExec( Vdbe *p /* The VDBE */ @@ -734,6 +824,7 @@ int sqlite3VdbeExec( Mem *pIn2 = 0; /* 2nd input operand */ Mem *pIn3 = 0; /* 3rd input operand */ Mem *pOut = 0; /* Output operand */ + u32 colCacheCtr = 0; /* Column cache counter */ #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) u64 *pnCycle = 0; int bStmtScanStatus = IS_STMT_SCANSTATUS(db)!=0; @@ -822,7 +913,7 @@ int sqlite3VdbeExec( test_trace_breakpoint((int)(pOp - aOp),pOp,p); } #endif - + /* Check to see if we need to simulate an interrupt. This only happens ** if we have a special test build. @@ -876,7 +967,7 @@ int sqlite3VdbeExec( #ifdef SQLITE_DEBUG pOrigOp = pOp; #endif - + switch( pOp->opcode ){ /***************************************************************************** @@ -917,7 +1008,7 @@ int sqlite3VdbeExec( /* Opcode: Goto * P2 * * * ** ** An unconditional jump to address P2. -** The next instruction executed will be +** The next instruction executed will be ** the one at index P2 from the beginning of ** the program. ** @@ -929,8 +1020,8 @@ int sqlite3VdbeExec( case OP_Goto: { /* jump */ #ifdef SQLITE_DEBUG - /* In debuggging mode, when the p5 flags is set on an OP_Goto, that - ** means we should really jump back to the preceeding OP_ReleaseReg + /* In debugging mode, when the p5 flags is set on an OP_Goto, that + ** means we should really jump back to the preceding OP_ReleaseReg ** instruction. */ if( pOp->p5 ){ assert( pOp->p2 < (int)(pOp - aOp) ); @@ -947,7 +1038,7 @@ case OP_Goto: { /* jump */ /* Opcodes that are used as the bottom of a loop (OP_Next, OP_Prev, ** OP_VNext, or OP_SorterNext) all jump here upon ** completion. Check to see if sqlite3_interrupt() has been called - ** or if the progress callback needs to be invoked. + ** or if the progress callback needs to be invoked. ** ** This code uses unstructured "goto" statements and does not look clean. ** But that is not due to sloppy coding habits. The code is written this @@ -973,7 +1064,7 @@ case OP_Goto: { /* jump */ } } #endif - + break; } @@ -1131,14 +1222,14 @@ case OP_HaltIfNull: { /* in3 */ ** whether or not to rollback the current transaction. Do not rollback ** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort, ** then back out all changes that have occurred during this execution of the -** VDBE, but do not rollback the transaction. +** VDBE, but do not rollback the transaction. ** ** If P4 is not null then it is an error message string. ** ** P5 is a value between 0 and 4, inclusive, that modifies the P4 string. ** ** 0: (no change) -** 1: NOT NULL contraint failed: P4 +** 1: NOT NULL constraint failed: P4 ** 2: UNIQUE constraint failed: P4 ** 3: CHECK constraint failed: P4 ** 4: FOREIGN KEY constraint failed: P4 @@ -1171,7 +1262,7 @@ case OP_Halt: { sqlite3VdbeSetChanges(db, p->nChange); pcx = sqlite3VdbeFrameRestore(pFrame); if( pOp->p2==OE_Ignore ){ - /* Instruction pcx is the OP_Program that invoked the sub-program + /* Instruction pcx is the OP_Program that invoked the sub-program ** currently being halted. If the p2 instruction of this OP_Halt ** instruction is set to OE_Ignore, then the sub-program is throwing ** an IGNORE exception. In this case jump to the address specified @@ -1259,7 +1350,7 @@ case OP_Real: { /* same as TK_FLOAT, out2 */ /* Opcode: String8 * P2 * P4 * ** Synopsis: r[P2]='P4' ** -** P4 points to a nul terminated UTF-8 string. This opcode is transformed +** P4 points to a nul terminated UTF-8 string. This opcode is transformed ** into a String opcode before it is executed for the first time. During ** this transformation, the length of string P4 is computed and stored ** as the P1 parameter. @@ -1295,7 +1386,7 @@ case OP_String8: { /* same as TK_STRING, out2 */ /* Fall through to the next case, OP_String */ /* no break */ deliberate_fall_through } - + /* Opcode: String P1 P2 P3 P4 P5 ** Synopsis: r[P2]='P4' (len=P1) ** @@ -1579,7 +1670,7 @@ case OP_IntCopy: { /* out2 */ ** ** FK constraint violations are also checked when the prepared statement ** exits. This opcode is used to raise foreign key constraint errors prior -** to returning results such as a row change count or the result of a +** to returning results such as a row change count or the result of a ** RETURNING clause. */ case OP_FkCheck: { @@ -1727,15 +1818,15 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ ** Synopsis: r[P3]=r[P2]/r[P1] ** ** Divide the value in register P1 by the value in register P2 -** and store the result in register P3 (P3=P2/P1). If the value in -** register P1 is zero, then the result is NULL. If either input is +** and store the result in register P3 (P3=P2/P1). If the value in +** register P1 is zero, then the result is NULL. If either input is ** NULL, the result is NULL. */ /* Opcode: Remainder P1 P2 P3 * * ** Synopsis: r[P3]=r[P2]%r[P1] ** -** Compute the remainder after integer register P2 is divided by -** register P1 and store the result in register P3. +** Compute the remainder after integer register P2 is divided by +** register P1 and store the result in register P3. ** If the value in register P1 is zero the result is NULL. ** If either operand is NULL, the result is NULL. */ @@ -1932,7 +2023,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */ /* Opcode: AddImm P1 P2 * * * ** Synopsis: r[P1]=r[P1]+P2 -** +** ** Add the constant P2 to the value in register P1. ** The result is always an integer. ** @@ -1947,7 +2038,7 @@ case OP_AddImm: { /* in1 */ } /* Opcode: MustBeInt P1 P2 * * * -** +** ** Force the value in register P1 to be an integer. If the value ** in P1 is not an integer and cannot be converted into an integer ** without data loss, then jump immediately to P2, or if P2==0 @@ -1999,7 +2090,7 @@ case OP_RealAffinity: { /* in1 */ ** Synopsis: affinity(r[P1]) ** ** Force the value in register P1 to be the type defined by P2. -** +** **
      **
    • P2=='A' → BLOB **
    • P2=='B' → TEXT @@ -2033,17 +2124,17 @@ case OP_Cast: { /* in1 */ ** Synopsis: IF r[P3]==r[P1] ** ** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then -** jump to address P2. +** jump to address P2. ** ** 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 +** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made ** to coerce both inputs according to this affinity before the ** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric ** affinity is used. Note that the affinity conversions are stored ** back into the input registers P1 and P3. So this opcode can cause ** persistent changes to registers P1 and P3. ** -** Once any conversions have taken place, and neither value is NULL, +** Once any conversions have taken place, and neither value is NULL, ** the values are compared. If both values are blobs then memcmp() is ** used to determine the results of the comparison. If both values ** are text, then the appropriate collating function specified in @@ -2076,18 +2167,18 @@ case OP_Cast: { /* in1 */ ** jump to address P2. ** ** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or -** reg(P3) is NULL then the take the jump. If the SQLITE_JUMPIFNULL +** reg(P3) is NULL then the take the jump. If the SQLITE_JUMPIFNULL ** 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 +** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made ** to coerce both inputs according to this affinity before the ** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric ** affinity is used. Note that the affinity conversions are stored ** back into the input registers P1 and P3. So this opcode can cause ** persistent changes to registers P1 and P3. ** -** Once any conversions have taken place, and neither value is NULL, +** Once any conversions have taken place, and neither value is NULL, ** the values are compared. If both values are blobs then memcmp() is ** used to determine the results of the comparison. If both values ** are text, then the appropriate collating function specified in @@ -2267,12 +2358,12 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** This opcode must follow an OP_Lt or OP_Gt comparison operator. There ** can be zero or more OP_ReleaseReg opcodes intervening, but no other ** opcodes are allowed to occur between this instruction and the previous -** OP_Lt or OP_Gt. +** OP_Lt or OP_Gt. ** -** If result of an OP_Eq comparison on the same two operands as the -** prior OP_Lt or OP_Gt would have been true, then jump to P2. -** If the result of an OP_Eq comparison on the two previous -** operands would have been false or NULL, then fall through. +** If the result of an OP_Eq comparison on the same two operands as +** the prior OP_Lt or OP_Gt would have been true, then jump to P2. If +** the result of an OP_Eq comparison on the two previous operands +** would have been false or NULL, then fall through. */ case OP_ElseEq: { /* same as TK_ESCAPE, jump */ @@ -2383,7 +2474,7 @@ case OP_Compare: { iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl); VVA_ONLY( iCompareIsInit = 1; ) if( iCompare ){ - if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) + if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) && ((aMem[p1+idx].flags & MEM_Null) || (aMem[p2+idx].flags & MEM_Null)) ){ iCompare = -iCompare; @@ -2468,13 +2559,13 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */ ** IS NOT FALSE operators. ** ** Interpret the value in register P1 as a boolean value. Store that -** boolean (a 0 or 1) in register P2. Or if the value in register P1 is +** boolean (a 0 or 1) in register P2. Or if the value in register P1 is ** NULL, then the P3 is stored in register P2. Invert the answer if P4 ** is 1. ** ** The logic is summarized like this: ** -**
        +**
          **
        • If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE **
        • If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE **
        • If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE @@ -2494,7 +2585,7 @@ case OP_IsTrue: { /* in1, out2 */ ** Synopsis: r[P2]= !r[P1] ** ** Interpret the value in register P1 as a boolean value. Store the -** boolean complement in register P2. If the value in register P1 is +** boolean complement in register P2. If the value in register P1 is ** NULL, then a NULL is stored in P2. */ case OP_Not: { /* same as TK_NOT, in1, out2 */ @@ -2702,7 +2793,7 @@ case OP_IsType: { /* jump */ /* Opcode: ZeroOrNull P1 P2 P3 * * ** Synopsis: r[P2] = 0 OR NULL ** -** If all both registers P1 and P3 are NOT NULL, then store a zero in +** If both registers P1 and P3 are NOT NULL, then store a zero in ** register P2. If either registers P1 or P3 are NULL then put ** a NULL in register P2. */ @@ -2720,7 +2811,7 @@ case OP_ZeroOrNull: { /* in1, in2, out2, in3 */ /* Opcode: NotNull P1 P2 * * * ** Synopsis: if r[P1]!=NULL goto P2 ** -** Jump to P2 if the value in register P1 is not NULL. +** Jump to P2 if the value in register P1 is not NULL. */ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ pIn1 = &aMem[pOp->p1]; @@ -2796,7 +2887,7 @@ case OP_Offset: { /* out3 */ ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional ** information about the format of the data.) Extract the P2-th column -** from this record. If there are less than (P2+1) +** from this record. If there are less than (P2+1) ** values in the record, extract a NULL. ** ** The value extracted is stored in register P3. @@ -2942,7 +3033,7 @@ case OP_Column: { /* ncycle */ */ if( pC->nHdrParsed<=p2 ){ /* If there is more header available for parsing in the record, try - ** to extract additional fields up through the p2+1-th field + ** to extract additional fields up through the p2+1-th field */ if( pC->iHdrOffsetaRow; } - + /* Fill in pC->aType[i] and aOffset[i] values through the p2-th field. */ op_column_read_header: i = pC->nHdrParsed; @@ -3056,18 +3147,23 @@ case OP_Column: { /* ncycle */ pDest->flags = aFlag[t&1]; } }else{ + u8 p5; pDest->enc = encoding; + assert( pDest->db==db ); /* This branch happens only when content is on overflow pages */ - if( ((pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0 - && ((t>=12 && (t&1)==0) || (pOp->p5 & OPFLAG_TYPEOFARG)!=0)) - || (len = sqlite3VdbeSerialTypeLen(t))==0 + if( ((p5 = (pOp->p5 & OPFLAG_BYTELENARG))!=0 + && (p5==OPFLAG_TYPEOFARG + || (t>=12 && ((t&1)==0 || p5==OPFLAG_BYTELENARG)) + ) + ) + || sqlite3VdbeSerialTypeLen(t)==0 ){ /* Content is irrelevant for ** 1. the typeof() function, ** 2. the length(X) function if X is a blob, and ** 3. if the content length is zero. ** So we might as well use bogus content rather than reading - ** content from disk. + ** content from disk. ** ** Although sqlite3VdbeSerialGet() may read at most 8 bytes from the ** buffer passed to it, debugging function VdbeMemPrettyPrint() may @@ -3077,11 +3173,13 @@ case OP_Column: { /* ncycle */ */ sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); }else{ - if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; - rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest); - if( rc!=SQLITE_OK ) goto abort_due_to_error; - sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); - pDest->flags &= ~MEM_Ephem; + rc = vdbeColumnFromOverflow(pC, p2, t, aOffset[p2], + p->cacheCtr, colCacheCtr, pDest); + if( rc ){ + if( rc==SQLITE_NOMEM ) goto no_mem; + if( rc==SQLITE_TOOBIG ) goto too_big; + goto abort_due_to_error; + } } } @@ -3298,13 +3396,13 @@ case OP_MakeRecord: { ** like this: ** ** ------------------------------------------------------------------------ - ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | + ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | ** ------------------------------------------------------------------------ ** ** Data(0) is taken from register P1. Data(1) comes from register P1+1 ** and so forth. ** - ** Each type field is a varint representing the serial type of the + ** Each type field is a varint representing the serial type of the ** corresponding data element (see sqlite3VdbeSerialType()). The ** hdr-size field is also a varint which is the offset from the beginning ** of the record to data0. @@ -3494,7 +3592,7 @@ case OP_MakeRecord: { } nByte = nHdr+nData; - /* Make sure the output register has a buffer large enough to store + /* Make sure the output register has a buffer large enough to store ** the new record. The output register (pOp->p3) is not allowed to ** be one of the input registers (because the following call to ** sqlite3VdbeMemClearAndResize() could clobber the value before it is used). @@ -3589,12 +3687,12 @@ case OP_MakeRecord: { /* Opcode: Count P1 P2 P3 * * ** Synopsis: r[P2]=count() ** -** Store the number of entries (an integer value) in the table or index +** Store the number of entries (an integer value) in the table or index ** opened by cursor P1 in register P2. ** ** If P3==0, then an exact count is obtained, which involves visiting ** every btree page of the table. But if P3 is non-zero, an estimate -** is returned based on the current cursor position. +** is returned based on the current cursor position. */ case OP_Count: { /* out2 */ i64 nEntry; @@ -3636,7 +3734,7 @@ case OP_Savepoint: { zName = pOp->p4.z; /* Assert that the p1 parameter is valid. Also that if there is no open - ** transaction, then there cannot be any savepoints. + ** transaction, then there cannot be any savepoints. */ assert( db->pSavepoint==0 || db->autoCommit==0 ); assert( p1==SAVEPOINT_BEGIN||p1==SAVEPOINT_RELEASE||p1==SAVEPOINT_ROLLBACK ); @@ -3646,7 +3744,7 @@ case OP_Savepoint: { if( p1==SAVEPOINT_BEGIN ){ if( db->nVdbeWrite>0 ){ - /* A new savepoint cannot be created if there are active write + /* A new savepoint cannot be created if there are active write ** statements (i.e. open read/write incremental blob handles). */ sqlite3VdbeError(p, "cannot open savepoint - SQL statements in progress"); @@ -3670,7 +3768,7 @@ case OP_Savepoint: { if( pNew ){ pNew->zName = (char *)&pNew[1]; memcpy(pNew->zName, zName, nName+1); - + /* If there is no open transaction, then mark this as a special ** "transaction savepoint". */ if( db->autoCommit ){ @@ -3694,7 +3792,7 @@ case OP_Savepoint: { /* Find the named savepoint. If there is no such savepoint, then an ** an error is returned to the user. */ for( - pSavepoint = db->pSavepoint; + pSavepoint = db->pSavepoint; pSavepoint && sqlite3StrICmp(pSavepoint->zName, zName); pSavepoint = pSavepoint->pNext ){ @@ -3704,7 +3802,7 @@ case OP_Savepoint: { sqlite3VdbeError(p, "no such savepoint: %s", zName); rc = SQLITE_ERROR; }else if( db->nVdbeWrite>0 && p1==SAVEPOINT_RELEASE ){ - /* It is not possible to release (commit) a savepoint if there are + /* It is not possible to release (commit) a savepoint if there are ** active write statements. */ sqlite3VdbeError(p, "cannot release savepoint - " @@ -3713,8 +3811,8 @@ case OP_Savepoint: { }else{ /* Determine whether or not this is a transaction savepoint. If so, - ** and this is a RELEASE command, then the current transaction - ** is committed. + ** and this is a RELEASE command, then the current transaction + ** is committed. */ int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; if( isTransaction && p1==SAVEPOINT_RELEASE ){ @@ -3762,8 +3860,8 @@ case OP_Savepoint: { } } if( rc ) goto abort_due_to_error; - - /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all + + /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all ** savepoints nested inside of the savepoint being operated on. */ while( db->pSavepoint!=pSavepoint ){ pTmp = db->pSavepoint; @@ -3772,8 +3870,8 @@ case OP_Savepoint: { db->nSavepoint--; } - /* If it is a RELEASE, then destroy the savepoint being operated on - ** too. If it is a ROLLBACK TO, then set the number of deferred + /* If it is a RELEASE, then destroy the savepoint being operated on + ** too. If it is a ROLLBACK TO, then set the number of deferred ** constraint violations present in the database to the value stored ** when the savepoint was created. */ if( p1==SAVEPOINT_RELEASE ){ @@ -3830,7 +3928,7 @@ case OP_AutoCommit: { db->autoCommit = 1; }else if( desiredAutoCommit && db->nVdbeWrite>0 ){ /* If this instruction implements a COMMIT and other VMs are writing - ** return an error indicating that the other VMs must complete first. + ** return an error indicating that the other VMs must complete first. */ sqlite3VdbeError(p, "cannot commit transaction - " "SQL statements in progress"); @@ -3859,7 +3957,7 @@ case OP_AutoCommit: { (!desiredAutoCommit)?"cannot start a transaction within a transaction":( (iRollback)?"cannot rollback - no transaction is active": "cannot commit - no transaction is active")); - + rc = SQLITE_ERROR; goto abort_due_to_error; } @@ -3870,7 +3968,7 @@ case OP_AutoCommit: { ** ** Begin a transaction on database P1 if a transaction is not already ** active. -** If P2 is non-zero, then a write-transaction is started, or if a +** If P2 is non-zero, then a write-transaction is started, or if a ** read-transaction is already active, it is upgraded to a write-transaction. ** If P2 is zero, then a read-transaction is started. If P2 is 2 or more ** then an exclusive transaction is started. @@ -3941,12 +4039,12 @@ case OP_Transaction: { if( p->usesStmtJournal && pOp->p2 - && (db->autoCommit==0 || db->nVdbeRead>1) + && (db->autoCommit==0 || db->nVdbeRead>1) ){ assert( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ); if( p->iStatement==0 ){ assert( db->nStatement>=0 && db->nSavepoint>=0 ); - db->nStatement++; + db->nStatement++; p->iStatement = db->nSavepoint + db->nStatement; } @@ -3974,7 +4072,7 @@ case OP_Transaction: { */ sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); - /* If the schema-cookie from the database file matches the cookie + /* If the schema-cookie from the database file matches the cookie ** stored with the in-memory representation of the schema, do ** not reload the schema from the database file. ** @@ -3984,7 +4082,7 @@ case OP_Transaction: { ** prepared queries. If such a query is out-of-date, we do not want to ** discard the database schema, as the user code implementing the ** v-table would have to be ready for the sqlite3_vtab structure itself - ** to be invalidated whenever sqlite3_step() is called from within + ** to be invalidated whenever sqlite3_step() is called from within ** a v-table method. */ if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){ @@ -4037,8 +4135,8 @@ case OP_ReadCookie: { /* out2 */ ** ** Write the integer value P3 into cookie number P2 of database P1. ** P2==1 is the schema version. P2==2 is the database format. -** P2==3 is the recommended pager cache -** size, and so forth. P1==0 is the main database file and P1==1 is the +** P2==3 is the recommended pager cache +** size, and so forth. P1==0 is the main database file and P1==1 is the ** database file used to store temporary tables. ** ** A transaction must be started before executing this opcode. @@ -4084,8 +4182,8 @@ case OP_SetCookie: { ** Synopsis: root=P2 iDb=P3 ** ** Open a read-only cursor for the database table whose root page is -** P2 in a database file. The database file is determined by P3. -** P3==0 means the main database, P3==1 means the database used for +** P2 in a database file. The database file is determined by P3. +** P3==0 means the main database, P3==1 means the database used for ** temporary tables, and P3>1 means used the corresponding attached ** database. Give the new cursor an identifier of P1. The P1 ** values need not be contiguous but all P1 values should be small integers. @@ -4099,10 +4197,10 @@ case OP_SetCookie: { **
        ** ** The P4 value may be either an integer (P4_INT32) or a pointer to -** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo +** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo ** object, then table being opened must be an [index b-tree] where the -** KeyInfo object defines the content and collating -** sequence of that index b-tree. Otherwise, if P4 is an integer +** KeyInfo object defines the content and collating +** sequence of that index b-tree. Otherwise, if P4 is an integer ** value, then the table being opened must be a [table b-tree] with a ** number of columns no less than the value of P4. ** @@ -4138,10 +4236,10 @@ case OP_SetCookie: { ** OPFLAG_P2ISREG bit is set in P5 - see below). ** ** The P4 value may be either an integer (P4_INT32) or a pointer to -** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo +** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo ** object, then table being opened must be an [index b-tree] where the -** KeyInfo object defines the content and collating -** sequence of that index b-tree. Otherwise, if P4 is an integer +** KeyInfo object defines the content and collating +** sequence of that index b-tree. Otherwise, if P4 is an integer ** value, then the table being opened must be a [table b-tree] with a ** number of columns no less than the value of P4. ** @@ -4257,7 +4355,7 @@ case OP_OpenWrite: /* Set the VdbeCursor.isTable variable. Previous versions of ** SQLite used to check if the root-page flags were sane at this point ** and report database corruption if they were not, but this check has - ** since moved into the btree layer. */ + ** since moved into the btree layer. */ pCur->isTable = pOp->p4type!=P4_KEYINFO; open_cursor_set_hints: @@ -4298,7 +4396,7 @@ case OP_OpenDup: { /* ncycle */ pCx->ub.pBtx = pOrig->ub.pBtx; pCx->noReuse = 1; pOrig->noReuse = 1; - rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, pCx->pKeyInfo, pCx->uc.pCursor); /* The sqlite3BtreeCursor() routine can only fail for the first cursor ** opened for a database. Since there is already an open cursor when this @@ -4312,7 +4410,7 @@ case OP_OpenDup: { /* ncycle */ ** Synopsis: nColumn=P2 ** ** Open a new cursor P1 to a transient table. -** The cursor is always opened read/write even if +** The cursor is always opened read/write even if ** the main database is read-only. The ephemeral ** table is deleted automatically when the cursor is closed. ** @@ -4346,7 +4444,7 @@ case OP_OpenEphemeral: { /* ncycle */ VdbeCursor *pCx; KeyInfo *pKeyInfo; - static const int vfsFlags = + static const int vfsFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | @@ -4365,7 +4463,7 @@ case OP_OpenEphemeral: { /* ncycle */ } pCx = p->apCsr[pOp->p1]; if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){ - /* If the ephermeral table is already open and has no duplicates from + /* If the ephemeral table is already open and has no duplicates from ** OP_OpenDup, then erase all existing content so that the table is ** empty again, rather than creating a new table. */ assert( pCx->isEphemeral ); @@ -4376,7 +4474,7 @@ case OP_OpenEphemeral: { /* ncycle */ pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; pCx->isEphemeral = 1; - rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->ub.pBtx, + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->ub.pBtx, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ @@ -4390,7 +4488,7 @@ case OP_OpenEphemeral: { /* ncycle */ if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ assert( pOp->p4type==P4_KEYINFO ); rc = sqlite3BtreeCreateTable(pCx->ub.pBtx, &pCx->pgnoRoot, - BTREE_BLOBKEY | pOp->p5); + BTREE_BLOBKEY | pOp->p5); if( rc==SQLITE_OK ){ assert( pCx->pgnoRoot==SCHEMA_ROOT+1 ); assert( pKeyInfo->db==db ); @@ -4465,7 +4563,7 @@ case OP_SequenceTest: { ** ** Open a new cursor that points to a fake table that contains a single ** row of data. The content of that one row is the content of memory -** register P2. In other words, cursor P1 becomes an alias for the +** register P2. In other words, cursor P1 becomes an alias for the ** MEM_Blob content contained in register P2. ** ** A pseudo-table created by this opcode is used to hold a single @@ -4530,13 +4628,13 @@ case OP_ColumnsUsed: { /* Opcode: SeekGE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as the key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as the key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the smallest entry that -** is greater than or equal to the key value. If there are no records +** Reposition cursor P1 so that it points to the smallest entry that +** is greater than or equal to the key value. If there are no records ** greater than or equal to the key and P2 is not zero, then jump to P2. ** ** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this @@ -4544,7 +4642,7 @@ case OP_ColumnsUsed: { ** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, ** this opcode must be followed by an IdxLE opcode with the same arguments. ** The IdxGT opcode will be skipped if this opcode succeeds, but the -** IdxGT opcode will be used on subsequent loop iterations. The +** IdxGT opcode will be used on subsequent loop iterations. The ** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this ** is an equality search. ** @@ -4557,13 +4655,13 @@ case OP_ColumnsUsed: { /* Opcode: SeekGT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as a key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the smallest entry that -** is greater than the key value. If there are no records greater than +** Reposition cursor P1 so that it points to the smallest entry that +** is greater than the key value. If there are no records greater than ** the key and P2 is not zero, then jump to P2. ** ** This opcode leaves the cursor configured to move in forward order, @@ -4572,16 +4670,16 @@ case OP_ColumnsUsed: { ** ** See also: Found, NotFound, SeekLt, SeekGe, SeekLe */ -/* Opcode: SeekLT P1 P2 P3 P4 * +/* Opcode: SeekLT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as a key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the largest entry that -** is less than the key value. If there are no records less than +** Reposition cursor P1 so that it points to the largest entry that +** is less than the key value. If there are no records less than ** the key and P2 is not zero, then jump to P2. ** ** This opcode leaves the cursor configured to move in reverse order, @@ -4593,13 +4691,13 @@ case OP_ColumnsUsed: { /* Opcode: SeekLE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as a key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the largest entry that -** is less than or equal to the key value. If there are no records +** Reposition cursor P1 so that it points to the largest entry that +** is less than or equal to the key value. If there are no records ** less than or equal to the key and P2 is not zero, then jump to P2. ** ** This opcode leaves the cursor configured to move in reverse order, @@ -4611,7 +4709,7 @@ case OP_ColumnsUsed: { ** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, ** this opcode must be followed by an IdxLE opcode with the same arguments. ** The IdxGE opcode will be skipped if this opcode succeeds, but the -** IdxGE opcode will be used on subsequent loop iterations. The +** IdxGE opcode will be used on subsequent loop iterations. The ** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this ** is an equality search. ** @@ -4856,7 +4954,7 @@ case OP_SeekGT: { /* jump, in3, group, ncycle */ ** row. If This.P5 is false (0) then a jump is made to SeekGE.P2. If ** This.P5 is true (non-zero) then a jump is made to This.P2. The P5==0 ** case occurs when there are no inequality constraints to the right of -** the IN constraing. The jump to SeekGE.P2 ends the loop. The P5!=0 case +** the IN constraint. The jump to SeekGE.P2 ends the loop. The P5!=0 case ** occurs when there are inequality constraints to the right of the IN ** operator. In that case, the This.P2 will point either directly to or ** to setup code prior to the OP_IdxGT or OP_IdxGE opcode that checks for @@ -4864,7 +4962,7 @@ case OP_SeekGT: { /* jump, in3, group, ncycle */ ** ** Possible outcomes from this opcode:
          ** -**
        1. If the cursor is initally not pointed to any valid row, then +**
        2. If the cursor is initially not pointed to any valid row, then ** fall through into the subsequent OP_SeekGE opcode. ** **
        3. If the cursor is left pointing to a row that is before the target @@ -4881,7 +4979,7 @@ case OP_SeekGT: { /* jump, in3, group, ncycle */ ** (indicating that the target row definitely does not exist in the ** btree) then jump to SeekGE.P2, ending the loop. ** -**
        4. If the cursor ends up on a valid row that is past the target row +**
        5. If the cursor ends up on a valid row that is past the target row ** (indicating that the target row does not exist in the btree) then ** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0. **
        @@ -4904,7 +5002,7 @@ case OP_SeekScan: { /* ncycle */ assert( pOp[1].p1==aOp[pOp->p2-1].p1 ); assert( pOp[1].p2==aOp[pOp->p2-1].p2 ); assert( pOp[1].p3==aOp[pOp->p2-1].p3 ); - assert( aOp[pOp->p2-1].opcode==OP_IdxGT + assert( aOp[pOp->p2-1].opcode==OP_IdxGT || aOp[pOp->p2-1].opcode==OP_IdxGE ); testcase( aOp[pOp->p2-1].opcode==OP_IdxGE ); }else{ @@ -4923,7 +5021,7 @@ case OP_SeekScan: { /* ncycle */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("... cursor not valid - fall through\n"); - } + } #endif break; } @@ -4952,7 +5050,7 @@ case OP_SeekScan: { /* ncycle */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("... %d steps and then skip\n", pOp->p1 - nStep); - } + } #endif VdbeBranchTaken(1,3); pOp++; @@ -4963,7 +5061,7 @@ case OP_SeekScan: { /* ncycle */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("... %d steps and then success\n", pOp->p1 - nStep); - } + } #endif VdbeBranchTaken(2,3); goto jump_to_p2; @@ -4973,7 +5071,7 @@ case OP_SeekScan: { /* ncycle */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("... fall through after %d steps\n", pOp->p1); - } + } #endif VdbeBranchTaken(0,3); break; @@ -4990,7 +5088,7 @@ case OP_SeekScan: { /* ncycle */ } } } - + break; } @@ -5019,14 +5117,14 @@ case OP_SeekHit: { /* ncycle */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p2); - } + } #endif pC->seekHit = pOp->p2; }else if( pC->seekHit>pOp->p3 ){ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p3); - } + } #endif pC->seekHit = pOp->p3; } @@ -5074,9 +5172,9 @@ case OP_IfNotOpen: { /* jump */ ** If P4==0 then register P3 holds a blob constructed by MakeRecord. If ** P4>0 then register P3 is the first of P4 registers that form an unpacked ** record. -** +** ** Cursor P1 is on an index btree. If the record identified by P3 and P4 -** is not the prefix of any entry in P1 then a jump is made to P2. If P1 +** is not the prefix of any entry in P1 then a jump is made to P2. If P1 ** does contain an entry whose prefix matches the P3/P4 record then control ** falls through to the next instruction and P1 is left pointing at the ** matching entry. @@ -5096,13 +5194,13 @@ case OP_IfNotOpen: { /* jump */ ** operands to OP_NotFound and OP_IdxGT. ** ** This opcode is an optimization attempt only. If this opcode always -** falls through, the correct answer is still obtained, but extra works +** falls through, the correct answer is still obtained, but extra work ** is performed. ** ** A value of N in the seekHit flag of cursor P1 means that there exists ** a key P3:N that will match some record in the index. We want to know ** if it is possible for a record P3:P4 to match some record in the -** index. If it is not possible, we can skips some work. So if seekHit +** index. If it is not possible, we can skip some work. So if seekHit ** is less than P4, attempt to find out if a match is possible by running ** OP_NotFound. ** @@ -5124,7 +5222,7 @@ case OP_IfNotOpen: { /* jump */ ** If P4==0 then register P3 holds a blob constructed by MakeRecord. If ** P4>0 then register P3 is the first of P4 registers that form an unpacked ** record. -** +** ** Cursor P1 is on an index btree. If the record identified by P3 and P4 ** contains any NULL value, jump immediately to P2. If all terms of the ** record are not-NULL then a check is done to determine if any row in the @@ -5149,7 +5247,7 @@ case OP_IfNoHope: { /* jump, in3, ncycle */ #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("seekHit is %d\n", pC->seekHit); - } + } #endif if( pC->seekHit>=pOp->p4.i ) break; /* Fall through into OP_NotFound */ @@ -5245,9 +5343,9 @@ case OP_Found: { /* jump, in3, ncycle */ ** ** P1 is the index of a cursor open on an SQL table btree (with integer ** keys). If register P3 does not contain an integer or if P1 does not -** contain a record with rowid P3 then jump immediately to P2. +** contain a record with rowid P3 then jump immediately to P2. ** Or, if P2 is 0, raise an SQLITE_CORRUPT error. If P1 does contain -** a record with rowid P3 then +** a record with rowid P3 then ** leave the cursor pointing at that record and fall through to the next ** instruction. ** @@ -5270,7 +5368,7 @@ case OP_Found: { /* jump, in3, ncycle */ ** P1 is the index of a cursor open on an SQL table btree (with integer ** keys). P3 is an integer rowid. If P1 does not contain a record with ** rowid P3 then jump immediately to P2. Or, if P2 is 0, raise an -** SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then +** SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then ** leave the cursor pointing at that record and fall through to the next ** instruction. ** @@ -5354,7 +5452,7 @@ case OP_NotExists: /* jump, in3, ncycle */ ** Find the next available sequence number for cursor P1. ** Write the sequence number into register P2. ** The sequence number on the cursor is incremented after this -** instruction. +** instruction. */ case OP_Sequence: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); @@ -5374,9 +5472,9 @@ case OP_Sequence: { /* out2 */ ** table that cursor P1 points to. The new record number is written ** written to register P2. ** -** If P3>0 then P3 is a register in the root frame of this VDBE that holds +** If P3>0 then P3 is a register in the root frame of this VDBE that holds ** the largest previously generated record number. No new record numbers are -** allowed to be less than this value. When this value reaches its maximum, +** allowed to be less than this value. When this value reaches its maximum, ** an SQLITE_FULL error is generated. The P3 register is updated with the ' ** generated record number. This P3 mechanism is used to help implement the ** AUTOINCREMENT feature. @@ -5527,8 +5625,8 @@ case OP_NewRowid: { /* out2 */ ** is part of an INSERT operation. The difference is only important to ** the update hook. ** -** Parameter P4 may point to a Table structure, or may be NULL. If it is -** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked +** Parameter P4 may point to a Table structure, or may be NULL. If it is +** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked ** following a successful insert. ** ** (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically @@ -5609,11 +5707,12 @@ case OP_Insert: { x.pKey = 0; assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, - (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), seekResult ); pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; + colCacheCtr++; /* Invoke the update-hook if required. */ if( rc ) goto abort_due_to_error; @@ -5662,15 +5761,15 @@ case OP_RowCell: { ** the cursor will be left pointing at either the next or the previous ** record in the table. If it is left pointing at the next record, then ** the next Next instruction will be a no-op. As a result, in this case -** it is ok to delete a record from within a Next loop. If +** it is ok to delete a record from within a Next loop. If ** OPFLAG_SAVEPOSITION bit of P5 is clear, then the cursor will be ** left in an undefined state. ** ** If the OPFLAG_AUXDELETE bit is set on P5, that indicates that this -** delete one of several associated with deleting a table row and all its -** associated index entries. Exactly one of those deletes is the "primary" -** delete. The others are all on OPFLAG_FORDELETE cursors or else are -** marked with the AUXDELETE flag. +** delete is one of several associated with deleting a table row and +** all its associated index entries. Exactly one of those deletes is +** the "primary" delete. The others are all on OPFLAG_FORDELETE +** cursors or else are marked with the AUXDELETE flag. ** ** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row ** change count is incremented (otherwise not). @@ -5678,11 +5777,11 @@ case OP_RowCell: { ** P1 must not be pseudo-table. It has to be a real table with ** multiple rows. ** -** If P4 is not NULL then it points to a Table object. In this case either +** If P4 is not NULL then it points to a Table object. In this case either ** the update or pre-update hook, or both, may be invoked. The P1 cursor must -** have been positioned using OP_NotFound prior to invoking this opcode in -** this case. Specifically, if one is configured, the pre-update hook is -** invoked if P4 is not NULL. The update-hook is invoked if one is configured, +** have been positioned using OP_NotFound prior to invoking this opcode in +** this case. Specifically, if one is configured, the pre-update hook is +** invoked if P4 is not NULL. The update-hook is invoked if one is configured, ** P4 is not NULL, and the OPFLAG_NCHANGE flag is set in P2. ** ** If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address @@ -5721,7 +5820,7 @@ case OP_Delete: { /* If the update-hook or pre-update-hook will be invoked, set zDb to ** the name of the db to pass as to it. Also set local pTab to a copy ** of p4.pTab. Finally, if p5 is true, indicating that this cursor was - ** last moved with OP_Next or OP_Prev, not Seek or NotFound, set + ** last moved with OP_Next or OP_Prev, not Seek or NotFound, set ** VdbeCursor.movetoTarget to the current rowid. */ if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ assert( pC->iDb>=0 ); @@ -5740,20 +5839,20 @@ case OP_Delete: { /* Invoke the pre-update-hook if required. */ assert( db->xPreUpdateCallback==0 || pTab==pOp->p4.pTab ); if( db->xPreUpdateCallback && pTab ){ - assert( !(opflags & OPFLAG_ISUPDATE) - || HasRowid(pTab)==0 - || (aMem[pOp->p3].flags & MEM_Int) + assert( !(opflags & OPFLAG_ISUPDATE) + || HasRowid(pTab)==0 + || (aMem[pOp->p3].flags & MEM_Int) ); sqlite3VdbePreUpdateHook(p, pC, - (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, + (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, zDb, pTab, pC->movetoTarget, pOp->p3, -1 ); } if( opflags & OPFLAG_ISNOOP ) break; #endif - - /* Only flags that can be set are SAVEPOISTION and AUXDELETE */ + + /* Only flags that can be set are SAVEPOISTION and AUXDELETE */ assert( (pOp->p5 & ~(OPFLAG_SAVEPOSITION|OPFLAG_AUXDELETE))==0 ); assert( OPFLAG_SAVEPOSITION==BTREE_SAVEPOSITION ); assert( OPFLAG_AUXDELETE==BTREE_AUXDELETE ); @@ -5774,6 +5873,7 @@ case OP_Delete: { rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); pC->cacheStatus = CACHE_STALE; + colCacheCtr++; pC->seekResult = 0; if( rc ) goto abort_due_to_error; @@ -5806,7 +5906,7 @@ case OP_ResetCount: { ** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 ** ** P1 is a sorter cursor. This instruction compares a prefix of the -** record blob in register P3 against a prefix of the entry that +** record blob in register P3 against a prefix of the entry that ** the sorter cursor currently points to. Only the first P4 fields ** of r[P3] and the sorter record are compared. ** @@ -5841,13 +5941,13 @@ case OP_SorterCompare: { ** Write into register P2 the current sorter data for sorter cursor P1. ** Then clear the column header cache on cursor P3. ** -** This opcode is normally use to move a record out of the sorter and into +** This opcode is normally used to move a record out of the sorter and into ** a register that is the source for a pseudo-table cursor created using ** OpenPseudo. That pseudo-table cursor is the one that is identified by ** parameter P3. Clearing the P3 column cache as part of this opcode saves ** us from having to issue a separate NullRow instruction to clear that cache. */ -case OP_SorterData: { +case OP_SorterData: { /* ncycle */ VdbeCursor *pC; pOut = &aMem[pOp->p2]; @@ -5864,10 +5964,10 @@ case OP_SorterData: { /* Opcode: RowData P1 P2 P3 * * ** Synopsis: r[P2]=data ** -** Write into register P2 the complete row content for the row at +** Write into register P2 the complete row content for the row at ** which cursor P1 is currently pointing. -** There is no interpretation of the data. -** It is just copied onto the P2 register exactly as +** There is no interpretation of the data. +** It is just copied onto the P2 register exactly as ** it is found in the database file. ** ** If cursor P1 is an index, then the content is the key of the row. @@ -6028,7 +6128,7 @@ case OP_NullRow: { */ /* Opcode: Last P1 P2 * * * ** -** The next use of the Rowid or Column or Prev instruction for P1 +** The next use of the Rowid or Column or Prev instruction for P1 ** will refer to the last entry in the database table or index. ** If the table or index is empty and P2>0, then jump immediately to P2. ** If P2 is 0 or if the table or index is not empty, fall through @@ -6122,8 +6222,8 @@ case OP_IfSmaller: { /* jump */ ** regression tests can determine whether or not the optimizer is ** correctly optimizing out sorts. */ -case OP_SorterSort: /* jump */ -case OP_Sort: { /* jump */ +case OP_SorterSort: /* jump ncycle */ +case OP_Sort: { /* jump ncycle */ #ifdef SQLITE_TEST sqlite3_sort_count++; sqlite3_search_count--; @@ -6134,10 +6234,10 @@ case OP_Sort: { /* jump */ } /* Opcode: Rewind P1 P2 * * * ** -** The next use of the Rowid or Column or Next instruction for P1 +** The next use of the Rowid or Column or Next instruction for P1 ** will refer to the first entry in the database table or index. ** If the table or index is empty, jump immediately to P2. -** If the table or index is not empty, fall through to the following +** If the table or index is not empty, fall through to the following ** instruction. ** ** If P2 is zero, that is an assertion that the P1 table is never @@ -6314,7 +6414,7 @@ case OP_Next: /* jump, ncycle */ ** run faster by avoiding an unnecessary seek on cursor P1. However, ** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior ** seeks on the cursor or if the most recent seek used a key equivalent -** to P2. +** to P2. ** ** This instruction only works for indices. The equivalent instruction ** for tables is OP_Insert. @@ -6340,7 +6440,7 @@ case OP_IdxInsert: { /* in2 */ x.aMem = aMem + pOp->p3; x.nMem = (u16)pOp->p4.i; rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, - (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) ); assert( pC->deferredMoveto==0 ); @@ -6378,7 +6478,7 @@ case OP_SorterInsert: { /* in2 */ ** Synopsis: key=r[P2@P3] ** ** The content of P3 registers starting at register P2 form -** an unpacked index key. This opcode removes that entry from the +** an unpacked index key. This opcode removes that entry from the ** index opened by cursor P1. ** ** If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error @@ -6436,8 +6536,8 @@ case OP_IdxDelete: { ** ** P4 may be an array of integers (type P4_INTARRAY) containing ** one entry for each column in the P3 table. If array entry a(i) -** is non-zero, then reading column a(i)-1 from cursor P3 is -** equivalent to performing the deferred seek and then reading column i +** is non-zero, then reading column a(i)-1 from cursor P3 is +** equivalent to performing the deferred seek and then reading column i ** from P1. This information is stored in P3 and used to redirect ** reads against P3 over to P1, thus possibly avoiding the need to ** seek and read cursor P3. @@ -6509,7 +6609,7 @@ case OP_IdxRowid: { /* out2, ncycle */ } /* Opcode: FinishSeek P1 * * * * -** +** ** If cursor P1 was previously moved via OP_DeferredSeek, complete that ** seek operation now, without further delay. If the cursor seek has ** already occurred, this instruction is a no-op. @@ -6529,9 +6629,9 @@ case OP_FinishSeek: { /* ncycle */ /* Opcode: IdxGE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index -** key that omits the PRIMARY KEY. Compare this key value against the index -** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID ** fields at the end. ** ** If the P1 index entry is greater than or equal to the key value @@ -6540,9 +6640,9 @@ case OP_FinishSeek: { /* ncycle */ /* Opcode: IdxGT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index -** key that omits the PRIMARY KEY. Compare this key value against the index -** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID ** fields at the end. ** ** If the P1 index entry is greater than the key value @@ -6551,7 +6651,7 @@ case OP_FinishSeek: { /* ncycle */ /* Opcode: IdxLT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index +** The P4 register values beginning with P3 form an unpacked index ** key that omits the PRIMARY KEY or ROWID. Compare this key value against ** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ** ROWID on the P1 index. @@ -6562,7 +6662,7 @@ case OP_FinishSeek: { /* ncycle */ /* Opcode: IdxLE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index +** The P4 register values beginning with P3 form an unpacked index ** key that omits the PRIMARY KEY or ROWID. Compare this key value against ** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ** ROWID on the P1 index. @@ -6650,7 +6750,7 @@ case OP_IdxGE: { /* jump, ncycle */ ** file is given by P1. ** ** The table being destroyed is in the main database file if P3==0. If -** P3==1 then the table to be clear is in the auxiliary database file +** P3==1 then the table to be destroyed is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** ** If AUTOVACUUM is enabled then it is possible that another root page @@ -6658,15 +6758,15 @@ case OP_IdxGE: { /* jump, ncycle */ ** root pages contiguous at the beginning of the database. The former ** value of the root page that moved - its value before the move occurred - ** is stored in register P2. If no page movement was required (because the -** table being dropped was already the last one in the database) then a -** zero is stored in register P2. If AUTOVACUUM is disabled then a zero +** table being dropped was already the last one in the database) then a +** zero is stored in register P2. If AUTOVACUUM is disabled then a zero ** is stored in register P2. ** ** This opcode throws an error if there are any active reader VMs when -** it is invoked. This is done to avoid the difficulty associated with -** updating existing cursors when a root page is moved in an AUTOVACUUM -** database. This error is thrown even if the database is not an AUTOVACUUM -** db in order to avoid introducing an incompatibility between autovacuum +** it is invoked. This is done to avoid the difficulty associated with +** updating existing cursors when a root page is moved in an AUTOVACUUM +** database. This error is thrown even if the database is not an AUTOVACUUM +** db in order to avoid introducing an incompatibility between autovacuum ** and non-autovacuum modes. ** ** See also: Clear @@ -6710,8 +6810,8 @@ case OP_Destroy: { /* out2 */ ** in the database file is given by P1. But, unlike Destroy, do not ** remove the table or index from the database file. ** -** The table being clear is in the main database file if P2==0. If -** P2==1 then the table to be clear is in the auxiliary database file +** The table being cleared is in the main database file if P2==0. If +** P2==1 then the table to be cleared is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** ** If the P3 value is non-zero, then the row change count is incremented @@ -6723,7 +6823,7 @@ case OP_Destroy: { /* out2 */ */ case OP_Clear: { i64 nChange; - + sqlite3VdbeIncrWriteCounter(p, 0); nChange = 0; assert( p->readOnly==0 ); @@ -6751,7 +6851,7 @@ case OP_Clear: { */ case OP_ResetSorter: { VdbeCursor *pC; - + assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); @@ -6823,7 +6923,7 @@ case OP_ParseSchema: { InitData initData; /* Any prepared statement that invokes this opcode will hold mutexes - ** on every btree. This is a prerequisite for invoking + ** on every btree. This is a prerequisite for invoking ** sqlite3InitCallback(). */ #ifdef SQLITE_DEBUG @@ -6884,7 +6984,7 @@ case OP_ParseSchema: { } goto abort_due_to_error; } - break; + break; } #if !defined(SQLITE_OMIT_ANALYZE) @@ -6898,7 +6998,7 @@ case OP_LoadAnalysis: { assert( pOp->p1>=0 && pOp->p1nDb ); rc = sqlite3AnalysisLoad(db, pOp->p1); if( rc ) goto abort_due_to_error; - break; + break; } #endif /* !defined(SQLITE_OMIT_ANALYZE) */ @@ -6906,7 +7006,7 @@ case OP_LoadAnalysis: { ** ** Remove the internal (in-memory) data structures that describe ** the table named P4 in database P1. This is called after a table -** is dropped from disk (using the Destroy opcode) in order to keep +** is dropped from disk (using the Destroy opcode) in order to keep ** the internal representation of the ** schema consistent with what is on disk. */ @@ -6934,7 +7034,7 @@ case OP_DropIndex: { ** ** Remove the internal (in-memory) data structures that describe ** the trigger named P4 in database P1. This is called after a trigger -** is dropped from disk (using the Destroy opcode) in order to keep +** is dropped from disk (using the Destroy opcode) in order to keep ** the internal representation of the ** schema consistent with what is on disk. */ @@ -6954,7 +7054,7 @@ case OP_DropTrigger: { ** ** The register P3 contains one less than the maximum number of allowed errors. ** At most reg(P3) errors will be reported. -** In other words, the analysis stops as soon as reg(P1) errors are +** In other words, the analysis stops as soon as reg(P1) errors are ** seen. Reg(P1) is updated with the number of errors remaining. ** ** The root page numbers of all tables in the database are integers @@ -7035,7 +7135,7 @@ case OP_RowSetRead: { /* jump, in1, out3 */ pIn1 = &aMem[pOp->p1]; assert( (pIn1->flags & MEM_Blob)==0 || sqlite3VdbeMemIsRowSet(pIn1) ); - if( (pIn1->flags & MEM_Blob)==0 + if( (pIn1->flags & MEM_Blob)==0 || sqlite3RowSetNext((RowSet*)pIn1->z, &val)==0 ){ /* The boolean index is empty */ @@ -7107,13 +7207,13 @@ case OP_RowSetTest: { /* jump, in1, in3 */ /* Opcode: Program P1 P2 P3 P4 P5 ** -** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). +** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). ** -** P1 contains the address of the memory cell that contains the first memory -** cell in an array of values used as arguments to the sub-program. P2 -** contains the address to jump to if the sub-program throws an IGNORE -** exception using the RAISE() function. Register P3 contains the address -** of a memory cell in this (the parent) VM that is used to allocate the +** P1 contains the address of the memory cell that contains the first memory +** cell in an array of values used as arguments to the sub-program. P2 +** contains the address to jump to if the sub-program throws an IGNORE +** exception using the RAISE() function. Register P3 contains the address +** of a memory cell in this (the parent) VM that is used to allocate the ** memory required by the sub-vdbe at runtime. ** ** P4 is a pointer to the VM containing the trigger program. @@ -7133,17 +7233,17 @@ case OP_Program: { /* jump */ pProgram = pOp->p4.pProgram; pRt = &aMem[pOp->p3]; assert( pProgram->nOp>0 ); - - /* If the p5 flag is clear, then recursive invocation of triggers is + + /* If the p5 flag is clear, then recursive invocation of triggers is ** disabled for backwards compatibility (p5 is set if this sub-program ** is really a trigger, not a foreign key action, and the flag set ** and cleared by the "PRAGMA recursive_triggers" command is clear). - ** - ** It is recursive invocation of triggers, at the SQL level, that is - ** disabled. In some cases a single trigger may generate more than one - ** SubProgram (if the trigger may be executed with more than one different + ** + ** It is recursive invocation of triggers, at the SQL level, that is + ** disabled. In some cases a single trigger may generate more than one + ** SubProgram (if the trigger may be executed with more than one different ** ON CONFLICT algorithm). SubProgram structures associated with a - ** single trigger all have the same value for the SubProgram.token + ** single trigger all have the same value for the SubProgram.token ** variable. */ if( pOp->p5 ){ t = pProgram->token; @@ -7159,10 +7259,10 @@ case OP_Program: { /* jump */ /* Register pRt is used to store the memory required to save the state ** of the current program, and the memory required at runtime to execute - ** the trigger program. If this trigger has been fired before, then pRt + ** the trigger program. If this trigger has been fired before, then pRt ** is already allocated. Otherwise, it must be initialized. */ if( (pRt->flags&MEM_Blob)==0 ){ - /* SubProgram.nMem is set to the number of memory cells used by the + /* SubProgram.nMem is set to the number of memory cells used by the ** program stored in SubProgram.aOp. As well as these, one memory ** cell is required for each cursor used by the program. Set local ** variable nMem (and later, VdbeFrame.nChildMem) to this value. @@ -7207,7 +7307,7 @@ case OP_Program: { /* jump */ }else{ pFrame = (VdbeFrame*)pRt->z; assert( pRt->xDel==sqlite3VdbeFrameMemDel ); - assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem + assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem || (pProgram->nCsr==0 && pProgram->nMem+1==pFrame->nChildMem) ); assert( pProgram->nCsr==pFrame->nChildCsr ); assert( (int)(pOp - aOp)==pFrame->pc ); @@ -7248,10 +7348,10 @@ case OP_Program: { /* jump */ /* Opcode: Param P1 P2 * * * ** -** This opcode is only ever present in sub-programs called via the -** OP_Program instruction. Copy a value currently stored in a memory -** cell of the calling (parent) frame to cell P2 in the current frames -** address space. This is used by trigger programs to access the new.* +** This opcode is only ever present in sub-programs called via the +** OP_Program instruction. Copy a value currently stored in a memory +** cell of the calling (parent) frame to cell P2 in the current frames +** address space. This is used by trigger programs to access the new.* ** and old.* values. ** ** The address of the cell in the parent frame is determined by adding @@ -7263,7 +7363,7 @@ case OP_Param: { /* out2 */ Mem *pIn; pOut = out2Prerelease(p, pOp); pFrame = p->pFrame; - pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1]; + pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1]; sqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem); break; } @@ -7275,8 +7375,8 @@ case OP_Param: { /* out2 */ ** Synopsis: fkctr[P1]+=P2 ** ** Increment a "constraint counter" by P2 (P2 may be negative or positive). -** If P1 is non-zero, the database constraint counter is incremented -** (deferred foreign key constraints). Otherwise, if P1 is zero, the +** If P1 is non-zero, the database constraint counter is incremented +** (deferred foreign key constraints). Otherwise, if P1 is zero, the ** statement counter is incremented (immediate foreign key constraints). */ case OP_FkCounter: { @@ -7294,7 +7394,7 @@ case OP_FkCounter: { ** Synopsis: if fkctr[P1]==0 goto P2 ** ** This opcode tests if a foreign key constraint-counter is currently zero. -** If so, jump to instruction P2. Otherwise, fall through to the next +** If so, jump to instruction P2. Otherwise, fall through to the next ** instruction. ** ** If P1 is non-zero, then the jump is taken if the database constraint-counter @@ -7320,7 +7420,7 @@ case OP_FkIfZero: { /* jump */ ** ** P1 is a register in the root frame of this VM (the root frame is ** different from the current frame if this instruction is being executed -** within a sub-program). Set the value of register P1 to the maximum of +** within a sub-program). Set the value of register P1 to the maximum of ** its current value and the value in register P2. ** ** This instruction throws an error if the memory cell is not initially @@ -7380,7 +7480,7 @@ case OP_IfPos: { /* jump, in1 */ ** and r[P2] is set to be the value of the LIMIT, r[P1]. ** ** if r[P1] is zero or negative, that means there is no LIMIT -** and r[P2] is set to -1. +** and r[P2] is set to -1. ** ** Otherwise, r[P2] is set to the sum of r[P1] and r[P3]. */ @@ -7412,7 +7512,7 @@ case OP_OffsetLimit: { /* in1, out2, in3 */ ** ** Register P1 must contain an integer. If the content of register P1 is ** initially greater than zero, then decrement the value in register P1. -** If it is non-zero (negative or positive) and then also jump to P2. +** If it is non-zero (negative or positive) and then also jump to P2. ** If register P1 is initially zero, leave it unchanged and fall through. */ case OP_IfNotZero: { /* jump, in1 */ @@ -7446,7 +7546,7 @@ case OP_DecrJumpZero: { /* jump, in1 */ ** Synopsis: accum=r[P3] step(r[P2@P5]) ** ** Execute the xStep function for an aggregate. -** The function has P5 arguments. P4 is a pointer to the +** The function has P5 arguments. P4 is a pointer to the ** FuncDef structure that specifies the function. Register P3 is the ** accumulator. ** @@ -7457,7 +7557,7 @@ case OP_DecrJumpZero: { /* jump, in1 */ ** Synopsis: accum=r[P3] inverse(r[P2@P5]) ** ** Execute the xInverse function for an aggregate. -** The function has P5 arguments. P4 is a pointer to the +** The function has P5 arguments. P4 is a pointer to the ** FuncDef structure that specifies the function. Register P3 is the ** accumulator. ** @@ -7468,7 +7568,7 @@ case OP_DecrJumpZero: { /* jump, in1 */ ** Synopsis: accum=r[P3] step(r[P2@P5]) ** ** Execute the xStep (if P1==0) or xInverse (if P1!=0) function for an -** aggregate. The function has P5 arguments. P4 is a pointer to the +** aggregate. The function has P5 arguments. P4 is a pointer to the ** FuncDef structure that specifies the function. Register P3 is the ** accumulator. ** @@ -7537,7 +7637,7 @@ case OP_AggStep1: { /* If this function is inside of a trigger, the register array in aMem[] ** might change from one evaluation to the next. The next block of code ** checks to see if the register array has changed, and if so it - ** reinitializes the relavant parts of the sqlite3_context object */ + ** reinitializes the relevant parts of the sqlite3_context object */ if( pCtx->pMem != pMem ){ pCtx->pMem = pMem; for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; @@ -7586,7 +7686,7 @@ case OP_AggStep1: { ** Synopsis: accum=r[P1] N=P2 ** ** P1 is the memory location that is the accumulator for an aggregate -** or window function. Execute the finalizer function +** or window function. Execute the finalizer function ** for an aggregate and store the result in P1. ** ** P2 is the number of arguments that the step function takes and @@ -7625,7 +7725,7 @@ case OP_AggFinal: { { rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); } - + if( rc ){ sqlite3VdbeError(p, "%s", sqlite3_value_text(pMem)); goto abort_due_to_error; @@ -7669,9 +7769,9 @@ case OP_Checkpoint: { } for(i=0, pMem = &aMem[pOp->p3]; i<3; i++, pMem++){ sqlite3VdbeMemSetInt64(pMem, (i64)aRes[i]); - } + } break; -}; +}; #endif #ifndef SQLITE_OMIT_PRAGMA @@ -7697,9 +7797,9 @@ case OP_JournalMode: { /* out2 */ pOut = out2Prerelease(p, pOp); eNew = pOp->p3; - assert( eNew==PAGER_JOURNALMODE_DELETE - || eNew==PAGER_JOURNALMODE_TRUNCATE - || eNew==PAGER_JOURNALMODE_PERSIST + assert( eNew==PAGER_JOURNALMODE_DELETE + || eNew==PAGER_JOURNALMODE_TRUNCATE + || eNew==PAGER_JOURNALMODE_PERSIST || eNew==PAGER_JOURNALMODE_OFF || eNew==PAGER_JOURNALMODE_MEMORY || eNew==PAGER_JOURNALMODE_WAL @@ -7719,7 +7819,7 @@ case OP_JournalMode: { /* out2 */ zFilename = sqlite3PagerFilename(pPager, 1); /* Do not allow a transition to journal_mode=WAL for a database - ** in temporary storage or if the VFS does not support shared memory + ** in temporary storage or if the VFS does not support shared memory */ if( eNew==PAGER_JOURNALMODE_WAL && (sqlite3Strlen30(zFilename)==0 /* Temp file */ @@ -7739,12 +7839,12 @@ case OP_JournalMode: { /* out2 */ ); goto abort_due_to_error; }else{ - + if( eOld==PAGER_JOURNALMODE_WAL ){ /* If leaving WAL mode, close the log file. If successful, the call - ** to PagerCloseWal() checkpoints and deletes the write-ahead-log - ** file. An EXCLUSIVE lock may still be held on the database file - ** after a successful return. + ** to PagerCloseWal() checkpoints and deletes the write-ahead-log + ** file. An EXCLUSIVE lock may still be held on the database file + ** after a successful return. */ rc = sqlite3PagerCloseWal(pPager, db); if( rc==SQLITE_OK ){ @@ -7755,7 +7855,7 @@ case OP_JournalMode: { /* out2 */ ** as an intermediate */ sqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF); } - + /* Open a transaction on the database file. Regardless of the journal ** mode, this transaction always uses a rollback journal. */ @@ -7830,7 +7930,7 @@ case OP_IncrVacuum: { /* jump */ ** is executed using sqlite3_step() it will either automatically ** reprepare itself (if it was originally created using sqlite3_prepare_v2()) ** or it will fail with SQLITE_SCHEMA. -** +** ** If P1 is 0, then all SQL statements become expired. If P1 is non-zero, ** then only the currently executing statement is expired. ** @@ -7885,7 +7985,7 @@ case OP_CursorUnlock: { ** Synopsis: iDb=P1 root=P2 write=P3 ** ** Obtain a lock on a particular table. This instruction is only used when -** the shared-cache feature is enabled. +** the shared-cache feature is enabled. ** ** P1 is the index of the database in sqlite3.aDb[] of the database ** on which the lock is acquired. A readlock is obtained if P3==0 or @@ -7899,7 +7999,7 @@ case OP_CursorUnlock: { case OP_TableLock: { u8 isWriteLock = (u8)pOp->p3; if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){ - int p1 = pOp->p1; + int p1 = pOp->p1; assert( p1>=0 && p1nDb ); assert( DbMaskTest(p->btreeMask, p1) ); assert( isWriteLock==0 || isWriteLock==1 ); @@ -7919,7 +8019,7 @@ case OP_TableLock: { #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VBegin * * * P4 * ** -** P4 may be a pointer to an sqlite3_vtab structure. If so, call the +** P4 may be a pointer to an sqlite3_vtab structure. If so, call the ** xBegin method for that table. ** ** Also, whether or not P4 is set, check that this is not being called from @@ -7939,7 +8039,7 @@ case OP_VBegin: { #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VCreate P1 P2 * * * ** -** P2 is a register that holds the name of a virtual table in database +** P2 is a register that holds the name of a virtual table in database ** P1. Call the xCreate method for that table. */ case OP_VCreate: { @@ -8202,7 +8302,7 @@ case OP_VNext: { /* jump, ncycle */ /* Invoke the xNext() method of the module. There is no way for the ** underlying implementation to return an error if one occurs during - ** xNext(). Instead, if an error occurs, true is returned (indicating that + ** xNext(). Instead, if an error occurs, true is returned (indicating that ** data is available) and the error code returned when xColumn or ** some other method is next invoked on the save virtual table cursor. */ @@ -8230,7 +8330,7 @@ case OP_VRename: { sqlite3_vtab *pVtab; Mem *pName; int isLegacy; - + isLegacy = (db->flags & SQLITE_LegacyAlter); db->flags |= SQLITE_LegacyAlter; pVtab = pOp->p4.pVtab->pVtab; @@ -8260,23 +8360,23 @@ case OP_VRename: { ** ** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. ** This opcode invokes the corresponding xUpdate method. P2 values -** are contiguous memory cells starting at P3 to pass to the xUpdate -** invocation. The value in register (P3+P2-1) corresponds to the +** are contiguous memory cells starting at P3 to pass to the xUpdate +** invocation. The value in register (P3+P2-1) corresponds to the ** p2th element of the argv array passed to xUpdate. ** ** The xUpdate method will do a DELETE or an INSERT or both. ** The argv[0] element (which corresponds to memory cell P3) -** is the rowid of a row to delete. If argv[0] is NULL then no -** deletion occurs. The argv[1] element is the rowid of the new -** row. This can be NULL to have the virtual table select the new -** rowid for itself. The subsequent elements in the array are +** is the rowid of a row to delete. If argv[0] is NULL then no +** deletion occurs. The argv[1] element is the rowid of the new +** row. This can be NULL to have the virtual table select the new +** rowid for itself. The subsequent elements in the array are ** the values of columns in the new row. ** ** If P2==1 then no insert is performed. argv[0] is the rowid of ** a row to delete. ** ** P1 is a boolean flag. If it is set to true and the xUpdate call -** is successful, then the value returned by sqlite3_last_insert_rowid() +** is successful, then the value returned by sqlite3_last_insert_rowid() ** is set to the value of the rowid for the row just inserted. ** ** P5 is the error actions (OE_Replace, OE_Fail, OE_Ignore, etc) to @@ -8291,7 +8391,7 @@ case OP_VUpdate: { Mem **apArg; Mem *pX; - assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback + assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback || pOp->p5==OE_Abort || pOp->p5==OE_Ignore || pOp->p5==OE_Replace ); assert( p->readOnly==0 ); @@ -8386,7 +8486,7 @@ case OP_MaxPgcnt: { /* out2 */ ** The result of the function is stored ** in register P3. Register P3 must not be one of the function inputs. ** -** P1 is a 32-bit bitmask indicating whether or not each argument to the +** P1 is a 32-bit bitmask indicating whether or not each argument to the ** function was determined to be constant at compile time. If the first ** argument was constant then bit 0 of P1 is set. This is used to determine ** whether meta data associated with a user function argument using the @@ -8405,7 +8505,7 @@ case OP_MaxPgcnt: { /* out2 */ ** The result of the function is stored ** in register P3. Register P3 must not be one of the function inputs. ** -** P1 is a 32-bit bitmask indicating whether or not each argument to the +** P1 is a 32-bit bitmask indicating whether or not each argument to the ** function was determined to be constant at compile time. If the first ** argument was constant then bit 0 of P1 is set. This is used to determine ** whether meta data associated with a user function argument using the @@ -8415,7 +8515,7 @@ case OP_MaxPgcnt: { /* out2 */ ** This opcode works exactly like OP_Function. The only difference is in ** its name. This opcode is used in places where the function must be ** purely non-deterministic. Some built-in date/time functions can be -** either determinitic of non-deterministic, depending on their arguments. +** either deterministic of non-deterministic, depending on their arguments. ** When those function are used in a non-deterministic way, they will check ** to see if they were called using OP_PureFunc instead of OP_Function, and ** if they were, they throw an error. @@ -8433,7 +8533,7 @@ case OP_Function: { /* group */ /* If this function is inside of a trigger, the register array in aMem[] ** might change from one evaluation to the next. The next block of code ** checks to see if the register array has changed, and if so it - ** reinitializes the relavant parts of the sqlite3_context object */ + ** reinitializes the relevant parts of the sqlite3_context object */ pOut = &aMem[pOp->p3]; if( pCtx->pOut != pOut ){ pCtx->pVdbe = p; @@ -8465,7 +8565,7 @@ case OP_Function: { /* group */ if( rc ) goto abort_due_to_error; } - assert( (pOut->flags&MEM_Str)==0 + assert( (pOut->flags&MEM_Str)==0 || pOut->enc==encoding || db->mallocFailed ); assert( !sqlite3VdbeMemTooBig(pOut) ); @@ -8509,7 +8609,7 @@ case OP_FilterAdd: { printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); } #endif - h %= pIn1->n; + h %= (pIn1->n*8); pIn1->z[h/8] |= 1<<(h&7); break; } @@ -8545,7 +8645,7 @@ case OP_Filter: { /* jump */ printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); } #endif - h %= pIn1->n; + h %= (pIn1->n*8); if( (pIn1->z[h/8] & (1<<(h&7)))==0 ){ VdbeBranchTaken(1, 2); p->aCounter[SQLITE_STMTSTATUS_FILTER_HIT]++; @@ -8797,7 +8897,7 @@ default: { /* This is really OP_Noop, OP_Explain */ } if( opProperty==0xff ){ /* Never happens. This code exists to avoid a harmless linkage - ** warning aboud sqlite3VdbeRegisterDump() being defined but not + ** warning about sqlite3VdbeRegisterDump() being defined but not ** used. */ sqlite3VdbeRegisterDump(p); } @@ -8834,7 +8934,7 @@ default: { /* This is really OP_Noop, OP_Explain */ p->rc = rc; sqlite3SystemError(db, rc); testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(rc, "statement aborts at %d: [%s] %s", + sqlite3_log(rc, "statement aborts at %d: [%s] %s", (int)(pOp - aOp), p->zSql, p->zErrMsg); if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); @@ -8876,8 +8976,8 @@ default: { /* This is really OP_Noop, OP_Explain */ if( DbMaskNonZero(p->lockMask) ){ sqlite3VdbeLeave(p); } - assert( rc!=SQLITE_OK || nExtraDelete==0 - || sqlite3_strlike("DELETE%",p->zSql,0)!=0 + assert( rc!=SQLITE_OK || nExtraDelete==0 + || sqlite3_strlike("DELETE%",p->zSql,0)!=0 ); return rc; diff --git a/src/vdbe.h b/src/vdbe.h index d28837f94..f44f24f93 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -327,7 +327,7 @@ int sqlite3VdbeBytecodeVtabInit(sqlite3*); ** The VdbeCoverage macros are used to set a coverage testing point ** for VDBE branch instructions. The coverage testing points are line ** numbers in the sqlite3.c source file. VDBE branch coverage testing -** only works with an amalagmation build. That's ok since a VDBE branch +** only works with an amalgamation build. That's ok since a VDBE branch ** coverage build designed for testing the test suite only. No application ** should ever ship with VDBE branch coverage measuring turned on. ** @@ -345,7 +345,7 @@ int sqlite3VdbeBytecodeVtabInit(sqlite3*); ** // NULL option is not possible ** ** VdbeCoverageEqNe(v) // Previous OP_Jump is only interested -** // in distingishing equal and not-equal. +** // in distinguishing equal and not-equal. ** ** Every VDBE branch operation must be tagged with one of the macros above. ** If not, then when "make test" is run with -DSQLITE_VDBE_COVERAGE and @@ -355,7 +355,7 @@ int sqlite3VdbeBytecodeVtabInit(sqlite3*); ** During testing, the test application will invoke ** sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE,...) to set a callback ** routine that is invoked as each bytecode branch is taken. The callback -** contains the sqlite3.c source line number ov the VdbeCoverage macro and +** contains the sqlite3.c source line number of the VdbeCoverage macro and ** flags to indicate whether or not the branch was taken. The test application ** is responsible for keeping track of this and reporting byte-code branches ** that are never taken. diff --git a/src/vdbeInt.h b/src/vdbeInt.h index b901a0180..2a23c3f28 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -56,6 +56,9 @@ typedef struct VdbeSorter VdbeSorter; /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; +/* A cache of large TEXT or BLOB values in a VdbeCursor */ +typedef struct VdbeTxtBlbCache VdbeTxtBlbCache; + /* Types of VDBE cursors */ #define CURTYPE_BTREE 0 #define CURTYPE_SORTER 1 @@ -87,6 +90,7 @@ struct VdbeCursor { Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ + Bool colCache:1; /* pCache pointer is initialized and non-NULL */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ union { /* pBtx for isEphermeral. pAltMap otherwise */ Btree *pBtx; /* Separate file holding temporary table */ @@ -127,6 +131,7 @@ struct VdbeCursor { #ifdef SQLITE_ENABLE_COLUMN_USED_MASK u64 maskUsed; /* Mask of columns used by this cursor */ #endif + VdbeTxtBlbCache *pCache; /* Cache of large TEXT or BLOB values */ /* 2*nField extra array elements allocated for aType[], beyond the one ** static element declared in the structure. nField total array slots for @@ -139,17 +144,30 @@ struct VdbeCursor { #define IsNullCursor(P) \ ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) - /* ** A value for VdbeCursor.cacheStatus that means the cache is always invalid. */ #define CACHE_STALE 0 +/* +** Large TEXT or BLOB values can be slow to load, so we want to avoid +** loading them more than once. For that reason, large TEXT and BLOB values +** can be stored in a cache defined by this object, and attached to the +** VdbeCursor using the pCache field. +*/ +struct VdbeTxtBlbCache { + char *pCValue; /* A RCStr buffer to hold the value */ + i64 iOffset; /* File offset of the row being cached */ + int iCol; /* Column for which the cache is valid */ + u32 cacheStatus; /* Vdbe.cacheCtr value */ + u32 colCacheCtr; /* Column cache counter */ +}; + /* ** When a sub-program is executed (OP_Program), a structure of this type ** is allocated to store the current value of the program counter, as ** well as the current memory cell array and various other frame specific -** values stored in the Vdbe struct. When the sub-program is finished, +** values stored in the Vdbe struct. When the sub-program is finished, ** these values are copied back to the Vdbe from the VdbeFrame structure, ** restoring the state of the VM to as it was before the sub-program ** began executing. @@ -263,7 +281,7 @@ struct sqlite3_value { ** ** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus ** MEM.u.i extra 0x00 bytes at the end. -** +** ** * MEM_Int Integer stored in Mem.u.i. ** ** * MEM_Real Real stored in Mem.u.r. @@ -277,7 +295,7 @@ struct sqlite3_value { ** If the MEM_Str flag is set then Mem.z points at a string representation. ** Usually this is encoded in the same unicode encoding as the main ** 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 +** set, then the string is nul terminated. The MEM_Int and MEM_Real ** flags may coexist with the MEM_Str flag. */ #define MEM_Undefined 0x0000 /* Value is undefined */ @@ -330,7 +348,7 @@ struct sqlite3_value { ** Return true if a memory cell has been initialized and is valid. ** is for use inside assert() statements only. ** -** A Memory cell is initialized if at least one of the +** A Memory cell is initialized if at least one of the ** MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob, or MEM_IntReal bits ** is set. It is "undefined" if all those bits are zero. */ @@ -339,7 +357,7 @@ struct sqlite3_value { #endif /* -** Each auxiliary data pointer stored by a user defined function +** Each auxiliary data pointer stored by a user defined function ** implementation calling sqlite3_set_auxdata() is stored in an instance ** of this structure. All such structures associated with a single VM ** are stored in a linked list headed at Vdbe.pAuxData. All are destroyed @@ -388,7 +406,7 @@ typedef unsigned bft; /* Bit Field Type */ ** sqlite3_stmt_scanstatus() interface. ** ** aAddrRange[]: -** This array is used by ScanStatus elements associated with EQP +** This array is used by ScanStatus elements associated with EQP ** notes that make an SQLITE_SCANSTAT_NCYCLE value available. It is ** an array of up to 3 ranges of VM addresses for which the Vdbe.anCycle[] ** values should be summed to calculate the NCYCLE value. Each pair of @@ -465,16 +483,18 @@ struct Vdbe { u32 nWrite; /* Number of write operations that have occurred */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ + u16 nResAlloc; /* Column slots allocated to aColName[] */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ - bft explain:2; /* True if EXPLAIN present on SQL command */ + bft explain:2; /* 0: normal, 1: EXPLAIN, 2: EXPLAIN QUERY PLAN */ bft changeCntOn:1; /* True to update the change-counter */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ + bft haveEqpOps:1; /* Bytecode supports EXPLAIN QUERY PLAN */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ @@ -505,7 +525,7 @@ struct Vdbe { #define VDBE_HALT_STATE 3 /* Finished. Need reset() or finalize() */ /* -** Structure used to store the context required by the +** Structure used to store the context required by the ** sqlite3_preupdate_*() API functions. */ struct PreUpdate { @@ -521,7 +541,7 @@ struct PreUpdate { i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ Mem *aNew; /* Array of new.* values */ - Table *pTab; /* Schema object being upated */ + Table *pTab; /* Schema object being updated */ Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; @@ -611,6 +631,7 @@ int sqlite3VdbeMemSetZeroBlob(Mem*,int); int sqlite3VdbeMemIsRowSet(const Mem*); #endif int sqlite3VdbeMemSetRowSet(Mem*); +void sqlite3VdbeMemZeroTerminateIfAble(Mem*); int sqlite3VdbeMemMakeWriteable(Mem*); int sqlite3VdbeMemStringify(Mem*, u8, u8); int sqlite3IntFloatCompare(i64,double); @@ -667,7 +688,7 @@ void sqlite3VdbeValueListFree(void*); # define sqlite3VdbeAssertAbortable(V) #endif -#if !defined(SQLITE_OMIT_SHARED_CACHE) +#if !defined(SQLITE_OMIT_SHARED_CACHE) void sqlite3VdbeEnter(Vdbe*); #else # define sqlite3VdbeEnter(X) diff --git a/src/vdbeapi.c b/src/vdbeapi.c index d8fcda96d..79b5de9f0 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -364,7 +364,7 @@ sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){ void sqlite3_value_free(sqlite3_value *pOld){ sqlite3ValueFree(pOld); } - + /**************************** sqlite3_result_ ******************************* ** The following routines are used by user-defined functions to specify @@ -419,9 +419,9 @@ static int invokeValueDestructor( return SQLITE_TOOBIG; } void sqlite3_result_blob( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ assert( n>=0 ); @@ -429,8 +429,8 @@ void sqlite3_result_blob( setResultStrOrError(pCtx, z, n, 0, xDel); } void sqlite3_result_blob64( - sqlite3_context *pCtx, - const void *z, + sqlite3_context *pCtx, + const void *z, sqlite3_uint64 n, void (*xDel)(void *) ){ @@ -489,8 +489,8 @@ void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ pOut->flags |= MEM_Subtype; } void sqlite3_result_text( - sqlite3_context *pCtx, - const char *z, + sqlite3_context *pCtx, + const char *z, int n, void (*xDel)(void *) ){ @@ -498,8 +498,8 @@ void sqlite3_result_text( setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel); } void sqlite3_result_text64( - sqlite3_context *pCtx, - const char *z, + sqlite3_context *pCtx, + const char *z, sqlite3_uint64 n, void (*xDel)(void *), unsigned char enc @@ -514,31 +514,32 @@ void sqlite3_result_text64( (void)invokeValueDestructor(z, xDel, pCtx); }else{ setResultStrOrError(pCtx, z, (int)n, enc, xDel); + sqlite3VdbeMemZeroTerminateIfAble(pCtx->pOut); } } #ifndef SQLITE_OMIT_UTF16 void sqlite3_result_text16( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16NATIVE, xDel); } void sqlite3_result_text16be( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16BE, xDel); } void sqlite3_result_text16le( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); @@ -586,7 +587,7 @@ void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ void sqlite3_result_error_toobig(sqlite3_context *pCtx){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_TOOBIG; - sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, + sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, SQLITE_UTF8, SQLITE_STATIC); } @@ -603,7 +604,7 @@ void sqlite3_result_error_nomem(sqlite3_context *pCtx){ ** a MEM_IntReal value. See the SQLITE_TESTCTRL_RESULT_INTREAL ** test-control. */ -void sqlite3ResultIntReal(sqlite3_context *pCtx){ +void sqlite3ResultIntReal(sqlite3_context *pCtx){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); if( pCtx->pOut->flags & MEM_Int ){ pCtx->pOut->flags &= ~MEM_Int; @@ -614,7 +615,7 @@ void sqlite3ResultIntReal(sqlite3_context *pCtx){ /* -** This function is called after a transaction has been committed. It +** This function is called after a transaction has been committed. It ** invokes callbacks registered with sqlite3_wal_hook() as required. */ static int doWalCallbacks(sqlite3 *db){ @@ -643,7 +644,7 @@ static int doWalCallbacks(sqlite3 *db){ ** statement is completely executed or an error occurs. ** ** This routine implements the bulk of the logic behind the sqlite_step() -** API. The only thing omitted is the automatic recompile if a +** API. The only thing omitted is the automatic recompile if a ** schema change has occurred. That detail is handled by the ** outer sqlite3_step() wrapper procedure. */ @@ -660,11 +661,11 @@ static int sqlite3Step(Vdbe *p){ p->rc = SQLITE_SCHEMA; rc = SQLITE_ERROR; if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ - /* If this statement was prepared using saved SQL and an + /* If this statement was prepared using saved SQL and an ** error has occurred, then return the error code in p->rc to the ** caller. Set the error code in the database handle to the same ** value. - */ + */ rc = sqlite3VdbeTransferError(p); } goto end_of_step; @@ -678,7 +679,7 @@ static int sqlite3Step(Vdbe *p){ AtomicStore(&db->u1.isInterrupted, 0); } - assert( db->nVdbeWrite>0 || db->autoCommit==0 + assert( db->nVdbeWrite>0 || db->autoCommit==0 || (db->nDeferredCons==0 && db->nDeferredImmCons==0) ); @@ -703,15 +704,15 @@ static int sqlite3Step(Vdbe *p){ ** 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, + ** 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 + ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE ** returns, and those 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 + ** legacy behavior of returning SQLITE_MISUSE for cases where the ** previous sqlite3_step() returned something other than a SQLITE_LOCKED ** or SQLITE_BUSY error. */ @@ -761,10 +762,10 @@ static int sqlite3Step(Vdbe *p){ rc = SQLITE_ERROR; } }else if( rc!=SQLITE_DONE && (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ - /* If this statement was prepared using saved SQL and an + /* If this statement was prepared using saved SQL and an ** error has occurred, then return the error code in p->rc to the ** caller. Set the error code in the database handle to the same value. - */ + */ rc = sqlite3VdbeTransferError(p); } } @@ -778,7 +779,7 @@ static int sqlite3Step(Vdbe *p){ /* There are only a limited number of result codes allowed from the ** statements prepared using the legacy sqlite3_prepare() interface */ assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 - || rc==SQLITE_ROW || rc==SQLITE_DONE || rc==SQLITE_ERROR + || rc==SQLITE_ROW || rc==SQLITE_DONE || rc==SQLITE_ERROR || (rc&0xff)==SQLITE_BUSY || rc==SQLITE_MISUSE ); return (rc&db->errMask); @@ -805,15 +806,15 @@ int sqlite3_step(sqlite3_stmt *pStmt){ int savedPc = v->pc; rc = sqlite3Reprepare(v); if( rc!=SQLITE_OK ){ - /* This case occurs after failing to recompile an sql statement. - ** The error message from the SQL compiler has already been loaded - ** into the database handle. This block copies the error message + /* This case occurs after failing to recompile an sql statement. + ** The error message from the SQL compiler has already been loaded + ** into the database handle. This block copies the error message ** from the database handle into the statement and sets the statement - ** program counter to 0 to ensure that when the statement is + ** program counter to 0 to ensure that when the statement is ** finalized or reset the parser error message is available via ** sqlite3_errmsg() and sqlite3_errcode(). */ - const char *zErr = (const char *)sqlite3_value_text(db->pErr); + const char *zErr = (const char *)sqlite3_value_text(db->pErr); sqlite3DbFree(db, v->zErrMsg); if( !db->mallocFailed ){ v->zErrMsg = sqlite3DbStrDup(db, zErr); @@ -886,7 +887,7 @@ int sqlite3_vtab_nochange(sqlite3_context *p){ ** The destructor function for a ValueList object. This needs to be ** a separate function, unknowable to the application, to ensure that ** calls to sqlite3_vtab_in_first()/sqlite3_vtab_in_next() that are not -** preceeded by activation of IN processing via sqlite3_vtab_int() do not +** preceded by activation of IN processing via sqlite3_vtab_int() do not ** try to access a fake ValueList object inserted by a hostile extension. */ void sqlite3VdbeValueListFree(void *pToDelete){ @@ -1063,9 +1064,9 @@ void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ ** access code. */ void sqlite3_set_auxdata( - sqlite3_context *pCtx, - int iArg, - void *pAux, + sqlite3_context *pCtx, + int iArg, + void *pAux, void (*xDelete)(void*) ){ AuxData *pAuxData; @@ -1107,7 +1108,7 @@ void sqlite3_set_auxdata( #ifndef SQLITE_OMIT_DEPRECATED /* -** Return the number of times the Step function of an aggregate has been +** Return the number of times the Step function of an aggregate has been ** called. ** ** This function is deprecated. Do not use it for new code. It is @@ -1126,7 +1127,8 @@ int sqlite3_aggregate_count(sqlite3_context *p){ */ int sqlite3_column_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; - return pVm ? pVm->nResColumn : 0; + if( pVm==0 ) return 0; + return pVm->nResColumn; } /* @@ -1152,9 +1154,9 @@ static const Mem *columnNullValue(void){ ** these assert()s from failing, when building with SQLITE_DEBUG defined ** using gcc, we force nullMem to be 8-byte aligned using the magical ** __attribute__((aligned(8))) macro. */ - static const Mem nullMem + static const Mem nullMem #if defined(SQLITE_DEBUG) && defined(__GNUC__) - __attribute__((aligned(8))) + __attribute__((aligned(8))) #endif = { /* .u = */ {0}, @@ -1200,9 +1202,9 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ } /* -** This function is called after invoking an sqlite3_value_XXX function on a +** This function is called after invoking an sqlite3_value_XXX function on a ** column value (i.e. a value returned by evaluating an SQL expression in the -** select list of a SELECT statement) that may cause a malloc() failure. If +** select list of a SELECT statement) that may cause a malloc() failure. If ** malloc() has failed, the threads mallocFailed flag is cleared and the result ** code of statement pStmt set to SQLITE_NOMEM. ** @@ -1215,7 +1217,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ ** sqlite3_column_real() ** sqlite3_column_bytes() ** sqlite3_column_bytes16() -** sqiite3_column_blob() +** sqlite3_column_blob() */ static void columnMallocFailure(sqlite3_stmt *pStmt) { @@ -1241,8 +1243,8 @@ const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){ const void *val; val = sqlite3_value_blob( columnMem(pStmt,i) ); /* Even though there is no encoding conversion, value_blob() might - ** need to call malloc() to expand the result of a zeroblob() - ** expression. + ** need to call malloc() to expand the result of a zeroblob() + ** expression. */ columnMallocFailure(pStmt); return val; @@ -1299,6 +1301,32 @@ int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ return iType; } +/* +** Column names appropriate for EXPLAIN or EXPLAIN QUERY PLAN. +*/ +static const char * const azExplainColNames8[] = { + "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", /* EXPLAIN */ + "id", "parent", "notused", "detail" /* EQP */ +}; +static const u16 azExplainColNames16data[] = { + /* 0 */ 'a', 'd', 'd', 'r', 0, + /* 5 */ 'o', 'p', 'c', 'o', 'd', 'e', 0, + /* 12 */ 'p', '1', 0, + /* 15 */ 'p', '2', 0, + /* 18 */ 'p', '3', 0, + /* 21 */ 'p', '4', 0, + /* 24 */ 'p', '5', 0, + /* 27 */ 'c', 'o', 'm', 'm', 'e', 'n', 't', 0, + /* 35 */ 'i', 'd', 0, + /* 38 */ 'p', 'a', 'r', 'e', 'n', 't', 0, + /* 45 */ 'n', 'o', 't', 'u', 's', 'e', 'd', 0, + /* 53 */ 'd', 'e', 't', 'a', 'i', 'l', 0 +}; +static const u8 iExplainColNames16[] = { + 0, 5, 12, 15, 18, 21, 24, 27, + 35, 38, 45, 53 +}; + /* ** Convert the N-th element of pStmt->pColName[] into a string using ** xFunc() then return that string. If N is out of range, return 0. @@ -1331,15 +1359,29 @@ static const void *columnName( return 0; } #endif + if( N<0 ) return 0; ret = 0; p = (Vdbe *)pStmt; db = p->db; assert( db!=0 ); - n = sqlite3_column_count(pStmt); - if( N=0 ){ + sqlite3_mutex_enter(db->mutex); + + if( p->explain ){ + if( useType>0 ) goto columnName_end; + n = p->explain==1 ? 8 : 4; + if( N>=n ) goto columnName_end; + if( useUtf16 ){ + int i = iExplainColNames16[N + 8*p->explain - 8]; + ret = (void*)&azExplainColNames16data[i]; + }else{ + ret = (void*)azExplainColNames8[N + 8*p->explain - 8]; + } + goto columnName_end; + } + n = p->nResColumn; + if( NmallocFailed; N += useType*n; - sqlite3_mutex_enter(db->mutex); #ifndef SQLITE_OMIT_UTF16 if( useUtf16 ){ ret = sqlite3_value_text16((sqlite3_value*)&p->aColName[N]); @@ -1356,8 +1398,9 @@ static const void *columnName( sqlite3OomClear(db); ret = 0; } - sqlite3_mutex_leave(db->mutex); } +columnName_end: + sqlite3_mutex_leave(db->mutex); return ret; } @@ -1444,13 +1487,13 @@ const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ /******************************* sqlite3_bind_ *************************** -** +** ** Routines used to attach values to wildcards in a compiled SQL statement. */ /* -** Unbind the value bound to variable i in virtual machine p. This is the +** Unbind the value bound to variable i in virtual machine p. This is the ** the same as binding a NULL value to the column. If the "i" parameter is -** out of range, then SQLITE_RANGE is returned. Othewise SQLITE_OK. +** out of range, then SQLITE_RANGE is returned. Otherwise SQLITE_OK. ** ** A successful evaluation of this routine acquires the mutex on p. ** the mutex is released if any kind of error occurs. @@ -1467,7 +1510,7 @@ static int vdbeUnbind(Vdbe *p, unsigned int i){ if( p->eVdbeState!=VDBE_READY_STATE ){ sqlite3Error(p->db, SQLITE_MISUSE); sqlite3_mutex_leave(p->db->mutex); - sqlite3_log(SQLITE_MISUSE, + sqlite3_log(SQLITE_MISUSE, "bind on a busy prepared statement: [%s]", p->zSql); return SQLITE_MISUSE_BKPT; } @@ -1481,7 +1524,7 @@ static int vdbeUnbind(Vdbe *p, unsigned int i){ pVar->flags = MEM_Null; p->db->errCode = SQLITE_OK; - /* If the bit corresponding to this variable in Vdbe.expmask is set, then + /* 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-57496-20354 If the specific value bound to a host @@ -1537,10 +1580,10 @@ static int bindText( ** Bind a blob value to an SQL statement variable. */ int sqlite3_bind_blob( - sqlite3_stmt *pStmt, - int i, - const void *zData, - int nData, + sqlite3_stmt *pStmt, + int i, + const void *zData, + int nData, void (*xDel)(void*) ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -1549,10 +1592,10 @@ int sqlite3_bind_blob( return bindText(pStmt, i, zData, nData, xDel, 0); } int sqlite3_bind_blob64( - sqlite3_stmt *pStmt, - int i, - const void *zData, - sqlite3_uint64 nData, + sqlite3_stmt *pStmt, + int i, + const void *zData, + sqlite3_uint64 nData, void (*xDel)(void*) ){ assert( xDel!=SQLITE_DYNAMIC ); @@ -1608,20 +1651,20 @@ int sqlite3_bind_pointer( } return rc; } -int sqlite3_bind_text( - sqlite3_stmt *pStmt, - int i, - const char *zData, - int nData, +int sqlite3_bind_text( + sqlite3_stmt *pStmt, + int i, + const char *zData, + int nData, void (*xDel)(void*) ){ return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF8); } -int sqlite3_bind_text64( - sqlite3_stmt *pStmt, - int i, - const char *zData, - sqlite3_uint64 nData, +int sqlite3_bind_text64( + sqlite3_stmt *pStmt, + int i, + const char *zData, + sqlite3_uint64 nData, void (*xDel)(void*), unsigned char enc ){ @@ -1634,10 +1677,10 @@ int sqlite3_bind_text64( } #ifndef SQLITE_OMIT_UTF16 int sqlite3_bind_text16( - sqlite3_stmt *pStmt, - int i, - const void *zData, - int n, + sqlite3_stmt *pStmt, + int i, + const void *zData, + int n, void (*xDel)(void*) ){ return bindText(pStmt, i, zData, n & ~(u64)1, xDel, SQLITE_UTF16NATIVE); @@ -1652,7 +1695,7 @@ int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ } case SQLITE_FLOAT: { assert( pValue->flags & (MEM_Real|MEM_IntReal) ); - rc = sqlite3_bind_double(pStmt, i, + rc = sqlite3_bind_double(pStmt, i, (pValue->flags & MEM_Real) ? pValue->u.r : (double)pValue->u.i ); break; @@ -1708,7 +1751,7 @@ int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint64 n){ /* ** Return the number of wildcards that can be potentially bound to. -** This routine is added to support DBD::SQLite. +** This routine is added to support DBD::SQLite. */ int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; @@ -1814,6 +1857,39 @@ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->explain : 0; } +/* +** Set the explain mode for a statement. +*/ +int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ + Vdbe *v = (Vdbe*)pStmt; + int rc; + sqlite3_mutex_enter(v->db->mutex); + if( ((int)v->explain)==eMode ){ + rc = SQLITE_OK; + }else if( eMode<0 || eMode>2 ){ + rc = SQLITE_ERROR; + }else if( (v->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){ + rc = SQLITE_ERROR; + }else if( v->eVdbeState!=VDBE_READY_STATE ){ + rc = SQLITE_BUSY; + }else if( v->nMem>=10 && (eMode!=2 || v->haveEqpOps) ){ + /* No reprepare necessary */ + v->explain = eMode; + rc = SQLITE_OK; + }else{ + v->explain = eMode; + rc = sqlite3Reprepare(v); + v->haveEqpOps = eMode==2; + } + if( v->explain ){ + v->nResColumn = 12 - 4*v->explain; + }else{ + v->nResColumn = v->nResAlloc; + } + sqlite3_mutex_leave(v->db->mutex); + return rc; +} + /* ** Return true if the prepared statement is in need of being reset. */ @@ -1853,7 +1929,7 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ Vdbe *pVdbe = (Vdbe*)pStmt; u32 v; #ifdef SQLITE_ENABLE_API_ARMOR - if( !pStmt + if( !pStmt || (op!=SQLITE_STMTSTATUS_MEMUSED && (op<0||op>=ArraySize(pVdbe->aCounter))) ){ (void)SQLITE_MISUSE_BKPT; @@ -1934,8 +2010,8 @@ const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt){ ** if successful, or a NULL pointer if an OOM error is encountered. */ static UnpackedRecord *vdbeUnpackRecord( - KeyInfo *pKeyInfo, - int nKey, + KeyInfo *pKeyInfo, + int nKey, const void *pKey ){ UnpackedRecord *pRet; /* Return value */ @@ -2028,7 +2104,7 @@ int sqlite3_preupdate_count(sqlite3 *db){ ** only. It returns zero if the change that caused the callback was made ** immediately by a user SQL statement. Or, if the change was made by a ** trigger program, it returns the number of trigger programs currently -** on the stack (1 for a top-level trigger, 2 for a trigger fired by a +** on the stack (1 for a top-level trigger, 2 for a trigger fired by a ** top-level trigger etc.). ** ** For the purposes of the previous paragraph, a foreign key CASCADE, SET NULL @@ -2043,7 +2119,7 @@ int sqlite3_preupdate_depth(sqlite3 *db){ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* ** This function is designed to be called from within a pre-update callback -** only. +** only. */ int sqlite3_preupdate_blobwrite(sqlite3 *db){ PreUpdate *p = db->pPreUpdate; @@ -2167,7 +2243,7 @@ int sqlite3_stmt_scanstatus_v2( idx = iScan; pScan = &p->aScan[idx]; }else{ - /* If the COMPLEX flag is clear, then this function must ignore any + /* If the COMPLEX flag is clear, then this function must ignore any ** ScanStatus structures with ScanStatus.addrLoop set to 0. */ for(idx=0; idxnScan; idx++){ pScan = &p->aScan[idx]; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index ecbf2d892..225c8d12c 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -10,7 +10,7 @@ ** ************************************************************************* ** This file contains code used for creating, destroying, and populating -** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) +** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) */ #include "sqliteInt.h" #include "vdbeInt.h" @@ -152,13 +152,13 @@ void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ } /* -** Resize the Vdbe.aOp array so that it is at least nOp elements larger +** Resize the Vdbe.aOp array so that it is at least nOp elements larger ** than its current size. nOp is guaranteed to be less than or equal ** to 1024/sizeof(Op). ** ** If an out-of-memory error occurs while resizing the array, return -** SQLITE_NOMEM. In this case Vdbe.aOp and Vdbe.nOpAlloc remain -** unchanged (this is so that any opcodes already allocated can be +** SQLITE_NOMEM. In this case Vdbe.aOp and Vdbe.nOpAlloc remain +** unchanged (this is so that any opcodes already allocated can be ** correctly deallocated along with the rest of the Vdbe). */ static int growOpArray(Vdbe *v, int nOp){ @@ -166,7 +166,7 @@ static int growOpArray(Vdbe *v, int nOp){ Parse *p = v->pParse; /* The SQLITE_TEST_REALLOC_STRESS compile-time option is designed to force - ** more frequent reallocs and hence provide more opportunities for + ** more frequent reallocs and hence provide more opportunities for ** simulated OOM faults. SQLITE_TEST_REALLOC_STRESS is generally used ** during testing only. With SQLITE_TEST_REALLOC_STRESS grow the op array ** by the minimum* amount required until the size reaches 512. Normal @@ -218,6 +218,35 @@ static void test_addop_breakpoint(int pc, Op *pOp){ } #endif +/* +** Slow paths for sqlite3VdbeAddOp3() and sqlite3VdbeAddOp4Int() for the +** unusual case when we need to increase the size of the Vdbe.aOp[] array +** before adding the new opcode. +*/ +static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){ + assert( p->nOpAlloc<=p->nOp ); + if( growOpArray(p, 1) ) return 1; + assert( p->nOpAlloc>p->nOp ); + return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +} +static SQLITE_NOINLINE int addOp4IntSlow( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); + if( p->db->mallocFailed==0 ){ + VdbeOp *pOp = &p->aOp[addr]; + pOp->p4type = P4_INT32; + pOp->p4.i = p4; + } + return addr; +} + + /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. @@ -228,17 +257,16 @@ static void test_addop_breakpoint(int pc, Op *pOp){ ** ** op The opcode for this instruction ** -** p1, p2, p3 Operands -** -** Use the sqlite3VdbeResolveLabel() function to fix an address and -** the sqlite3VdbeChangeP4() function to change the value of the P4 -** operand. +** p1, p2, p3, p4 Operands */ -static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){ - assert( p->nOpAlloc<=p->nOp ); - if( growOpArray(p, 1) ) return 1; - assert( p->nOpAlloc>p->nOp ); - return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +int sqlite3VdbeAddOp0(Vdbe *p, int op){ + return sqlite3VdbeAddOp3(p, op, 0, 0, 0); +} +int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){ + return sqlite3VdbeAddOp3(p, op, p1, 0, 0); +} +int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){ + return sqlite3VdbeAddOp3(p, op, p1, p2, 0); } int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ int i; @@ -261,6 +289,9 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ pOp->p3 = p3; pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; + + /* Replicate this logic in sqlite3VdbeAddOp4Int() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOp->zComment = 0; #endif @@ -277,16 +308,59 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ #ifdef SQLITE_VDBE_COVERAGE pOp->iSrcLine = 0; #endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp4Int() */ + return i; } -int sqlite3VdbeAddOp0(Vdbe *p, int op){ - return sqlite3VdbeAddOp3(p, op, 0, 0, 0); -} -int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){ - return sqlite3VdbeAddOp3(p, op, p1, 0, 0); -} -int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){ - return sqlite3VdbeAddOp3(p, op, p1, p2, 0); +int sqlite3VdbeAddOp4Int( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int i; + VdbeOp *pOp; + + i = p->nOp; + if( p->nOpAlloc<=i ){ + return addOp4IntSlow(p, op, p1, p2, p3, p4); + } + p->nOp++; + pOp = &p->aOp[i]; + assert( pOp!=0 ); + pOp->opcode = (u8)op; + pOp->p5 = 0; + pOp->p1 = p1; + pOp->p2 = p2; + pOp->p3 = p3; + pOp->p4.i = p4; + pOp->p4type = P4_INT32; + + /* Replicate this logic in sqlite3VdbeAddOp3() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + pOp->zComment = 0; +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec = 0; + pOp->nCycle = 0; +#endif +#ifdef SQLITE_DEBUG + if( p->db->flags & SQLITE_VdbeAddopTrace ){ + sqlite3VdbePrintOp(0, i, &p->aOp[i]); + test_addop_breakpoint(i, &p->aOp[i]); + } +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOp->iSrcLine = 0; +#endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp3() */ + + return i; } /* Generate code for an unconditional jump to instruction iDest @@ -464,7 +538,7 @@ int sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ if( bPush){ pParse->addrExplain = iThis; } - sqlite3VdbeScanStatus(v, iThis, 0, 0, 0, 0); + sqlite3VdbeScanStatus(v, iThis, -1, -1, 0, 0); } return addr; } @@ -494,26 +568,6 @@ void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere, u16 p5){ sqlite3MayAbort(p->pParse); } -/* -** Add an opcode that includes the p4 value as an integer. -*/ -int sqlite3VdbeAddOp4Int( - Vdbe *p, /* Add the opcode to this VM */ - int op, /* The new opcode */ - int p1, /* The P1 operand */ - int p2, /* The P2 operand */ - int p3, /* The P3 operand */ - int p4 /* The P4 operand as an integer */ -){ - int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); - if( p->db->mallocFailed==0 ){ - VdbeOp *pOp = &p->aOp[addr]; - pOp->p4type = P4_INT32; - pOp->p4.i = p4; - } - return addr; -} - /* Insert the end of a co-routine */ void sqlite3VdbeEndCoroutine(Vdbe *v, int regYield){ @@ -624,19 +678,19 @@ void sqlite3VdbeReusable(Vdbe *p){ /* ** The following type and function are used to iterate through all opcodes -** in a Vdbe main program and each of the sub-programs (triggers) it may +** in a Vdbe main program and each of the sub-programs (triggers) it may ** invoke directly or indirectly. It should be used as follows: ** ** Op *pOp; ** VdbeOpIter sIter; ** ** memset(&sIter, 0, sizeof(sIter)); -** sIter.v = v; // v is of type Vdbe* +** sIter.v = v; // v is of type Vdbe* ** while( (pOp = opIterNext(&sIter)) ){ ** // Do something with pOp ** } ** sqlite3DbFree(v->db, sIter.apSub); -** +** */ typedef struct VdbeOpIter VdbeOpIter; struct VdbeOpIter { @@ -669,7 +723,7 @@ static Op *opIterNext(VdbeOpIter *p){ p->iSub++; p->iAddr = 0; } - + if( pRet->p4type==P4_SUBPROGRAM ){ int nByte = (p->nSub+1)*sizeof(SubProgram*); int j; @@ -703,7 +757,7 @@ static Op *opIterNext(VdbeOpIter *p){ ** * OP_VCreate ** * OP_VRename ** * OP_FkCounter with P2==0 (immediate foreign key constraint) -** * OP_CreateBtree/BTREE_INTKEY and OP_InitCoroutine +** * OP_CreateBtree/BTREE_INTKEY and OP_InitCoroutine ** (for CREATE TABLE AS SELECT ...) ** ** Then check that the value of Parse.mayAbort is true if an @@ -728,12 +782,12 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ while( (pOp = opIterNext(&sIter))!=0 ){ int opcode = pOp->opcode; - if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename + if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename || opcode==OP_VDestroy || opcode==OP_VCreate || opcode==OP_ParseSchema || opcode==OP_Function || opcode==OP_PureFunc - || ((opcode==OP_Halt || opcode==OP_HaltIfNull) + || ((opcode==OP_Halt || opcode==OP_HaltIfNull) && ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort)) ){ hasAbort = 1; @@ -742,7 +796,7 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ if( opcode==OP_CreateBtree && pOp->p3==BTREE_INTKEY ) hasCreateTable = 1; if( mayAbort ){ /* hasCreateIndex may also be set for some DELETE statements that use - ** OP_Clear. So this routine may end up returning true in the case + ** OP_Clear. So this routine may end up returning true in the case ** where a "DELETE FROM tbl" has a statement-journal but does not ** require one. This is not so bad - it is an inefficiency, not a bug. */ if( opcode==OP_CreateBtree && pOp->p3==BTREE_BLOBKEY ) hasCreateIndex = 1; @@ -826,7 +880,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ p->bIsReader = 0; pOp = &p->aOp[p->nOp-1]; assert( p->aOp[0].opcode==OP_Init ); - while( 1 /* Loop termates when it reaches the OP_Init opcode */ ){ + while( 1 /* Loop terminates when it reaches the OP_Init opcode */ ){ /* Only JUMP opcodes and the short list of special opcodes in the switch ** below need to be considered. The mkopcodeh.tcl generator script groups ** all these opcodes together near the front of the opcode list. Skip @@ -892,7 +946,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ ** have non-negative values for P2. */ assert( (sqlite3OpcodeProperty[pOp->opcode]&OPFLG_JUMP)==0 || pOp->p2>=0); } - assert( pOp>p->aOp ); + assert( pOp>p->aOp ); pOp--; } resolve_p2_values_loop_exit: @@ -1041,12 +1095,12 @@ void sqlite3VdbeVerifyAbortable(Vdbe *p, int onError){ /* ** This function returns a pointer to the array of opcodes associated with ** the Vdbe passed as the first argument. It is the callers responsibility -** to arrange for the returned array to be eventually freed using the +** to arrange for the returned array to be eventually freed using the ** vdbeFreeOpArray() function. ** ** Before returning, *pnOp is set to the number of entries in the returned -** array. Also, *pnMaxArg is set to the larger of its current value and -** the number of entries in the Vdbe.apArg[] array required to execute the +** array. Also, *pnMaxArg is set to the larger of its current value and +** the number of entries in the Vdbe.apArg[] array required to execute the ** returned program. */ VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){ @@ -1120,7 +1174,7 @@ VdbeOp *sqlite3VdbeAddOpList( void sqlite3VdbeScanStatus( Vdbe *p, /* VM to add scanstatus() to */ int addrExplain, /* Address of OP_Explain (or 0) */ - int addrLoop, /* Address of loop counter */ + int addrLoop, /* Address of loop counter */ int addrVisit, /* Address of rows visited counter */ LogEst nEst, /* Estimated number of output rows */ const char *zName /* Name of table or index being scanned */ @@ -1146,13 +1200,13 @@ void sqlite3VdbeScanStatus( ** Add the range of instructions from addrStart to addrEnd (inclusive) to ** the set of those corresponding to the sqlite3_stmt_scanstatus() counters ** associated with the OP_Explain instruction at addrExplain. The -** sum of the sqlite3Hwtime() values for each of these instructions +** sum of the sqlite3Hwtime() values for each of these instructions ** will be returned for SQLITE_SCANSTAT_NCYCLE requests. */ void sqlite3VdbeScanStatusRange( - Vdbe *p, - int addrExplain, - int addrStart, + Vdbe *p, + int addrExplain, + int addrStart, int addrEnd ){ if( IS_STMT_SCANSTATUS(p->db) ){ @@ -1182,9 +1236,9 @@ void sqlite3VdbeScanStatusRange( ** addrExplain. */ void sqlite3VdbeScanStatusCounters( - Vdbe *p, - int addrExplain, - int addrLoop, + Vdbe *p, + int addrExplain, + int addrLoop, int addrVisit ){ if( IS_STMT_SCANSTATUS(p->db) ){ @@ -1196,8 +1250,8 @@ void sqlite3VdbeScanStatusCounters( pScan = 0; } if( pScan ){ - pScan->addrLoop = addrLoop; - pScan->addrVisit = addrVisit; + if( addrLoop>0 ) pScan->addrLoop = addrLoop; + if( addrVisit>0 ) pScan->addrVisit = addrVisit; } } } @@ -1280,7 +1334,7 @@ void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){ /* ** If the input FuncDef structure is ephemeral, then free it. If -** the FuncDef is not ephermal, then do nothing. +** the FuncDef is not ephemeral, then do nothing. */ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ assert( db!=0 ); @@ -1346,8 +1400,8 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ /* ** Free the space allocated for aOp and any p4 values allocated for the -** opcodes contained within. If aOp is not NULL it is assumed to contain -** nOp entries. +** opcodes contained within. If aOp is not NULL it is assumed to contain +** nOp entries. */ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ assert( nOp>=0 ); @@ -1358,7 +1412,7 @@ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS sqlite3DbFree(db, pOp->zComment); -#endif +#endif if( pOp==aOp ) break; pOp--; } @@ -1444,7 +1498,6 @@ void sqlite3VdbeReleaseRegisters( } #endif /* SQLITE_DEBUG */ - /* ** Change the value of the P4 operand for a specific instruction. ** This routine is useful when a large program is loaded from a @@ -1455,7 +1508,7 @@ void sqlite3VdbeReleaseRegisters( ** the string is made into memory obtained from sqlite3_malloc(). ** A value of n==0 means copy bytes of zP4 up to and including the ** first null byte. If n>0 then copy n+1 bytes of zP4. -** +** ** Other values of n (P4_STATIC, P4_COLLSEQ etc.) indicate that zP4 points ** to a string or structure that is guaranteed to exist for the lifetime of ** the Vdbe. In these cases we can just copy the pointer. @@ -1516,7 +1569,7 @@ void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){ } /* -** Change the P4 operand of the most recently coded instruction +** Change the P4 operand of the most recently coded instruction ** to the value defined by the arguments. This is a high-speed ** version of sqlite3VdbeChangeP4(). ** @@ -1605,7 +1658,7 @@ void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){ ** routine, then a pointer to a dummy VdbeOp will be returned. That opcode ** is readable but not writable, though it is cast to a writable value. ** The return of a dummy opcode allows the call to continue functioning -** after an OOM fault without having to check to see if the return from +** after an OOM fault without having to check to see if the return from ** this routine is a valid pointer. But because the dummy.opcode is 0, ** dummy will never be written to. This is verified by code inspection and ** by running with Valgrind. @@ -1835,9 +1888,9 @@ char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){ CollSeq *pColl = pKeyInfo->aColl[j]; const char *zColl = pColl ? pColl->zName : ""; if( strcmp(zColl, "BINARY")==0 ) zColl = "B"; - sqlite3_str_appendf(&x, ",%s%s%s", - (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_DESC) ? "-" : "", - (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_BIGNULL)? "N." : "", + sqlite3_str_appendf(&x, ",%s%s%s", + (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_DESC) ? "-" : "", + (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_BIGNULL)? "N." : "", zColl); } sqlite3_str_append(&x, ")", 1); @@ -1960,13 +2013,13 @@ void sqlite3VdbeUsesBtree(Vdbe *p, int i){ ** ** If SQLite is not threadsafe but does support shared-cache mode, then ** sqlite3BtreeEnter() is invoked to set the BtShared.db variables -** of all of BtShared structures accessible via the database handle +** of all of BtShared structures accessible via the database handle ** associated with the VM. ** ** If SQLite is not threadsafe and does not support shared-cache mode, this ** function is a no-op. ** -** The p->btreeMask field is a bitmask of all btrees that the prepared +** The p->btreeMask field is a bitmask of all btrees that the prepared ** statement p will ever use. Let N be the number of bits in p->btreeMask ** corresponding to btrees that use shared cache. Then the runtime of ** this routine is N*N. But as N is rarely more than 1, this should not @@ -2034,8 +2087,8 @@ void sqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){ /* NB: The sqlite3OpcodeName() function is implemented by code created ** by the mkopcodeh.awk and mkopcodec.awk scripts which extract the ** information from the vdbe.c source text */ - fprintf(pOut, zFormat1, pc, - sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, + fprintf(pOut, zFormat1, pc, + sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, zP4 ? zP4 : "", pOp->p5, zCom ? zCom : "" ); @@ -2098,15 +2151,15 @@ static void releaseMemArray(Mem *p, int N){ assert( sqlite3VdbeCheckMemInvariants(p) ); /* This block is really an inlined version of sqlite3VdbeMemRelease() - ** that takes advantage of the fact that the memory cell value is + ** that takes advantage of the fact that the memory cell value is ** being set to NULL after releasing any dynamic resources. ** - ** The justification for duplicating code is that according to - ** callgrind, this causes a certain test case to hit the CPU 4.7 - ** percent less (x86 linux, gcc version 4.1.2, -O6) than if + ** The justification for duplicating code is that according to + ** callgrind, this causes a certain test case to hit the CPU 4.7 + ** percent less (x86 linux, gcc version 4.1.2, -O6) than if ** sqlite3MemRelease() were called from here. With -O2, this jumps - ** to 6.6 percent. The test case is inserting 1000 rows into a table - ** with no indexes using a single prepared INSERT statement, bind() + ** to 6.6 percent. The test case is inserting 1000 rows into a table + ** with no indexes using a single prepared INSERT statement, bind() ** and reset(). Inserts are grouped into a transaction. */ testcase( p->flags & MEM_Agg ); @@ -2256,7 +2309,7 @@ int sqlite3VdbeNextOpcode( Op *pOp = aOp + i; if( pOp->opcode==OP_OpenRead ) break; if( pOp->opcode==OP_OpenWrite && (pOp->p5 & OPFLAG_P2ISREG)==0 ) break; - if( pOp->opcode==OP_ReopenIdx ) break; + if( pOp->opcode==OP_ReopenIdx ) break; }else #endif { @@ -2364,8 +2417,8 @@ int sqlite3VdbeList( sqlite3VdbeMemSetInt64(pMem, pOp->p1); sqlite3VdbeMemSetInt64(pMem+1, pOp->p2); sqlite3VdbeMemSetInt64(pMem+2, pOp->p3); - sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); - p->nResColumn = 4; + sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); + assert( p->nResColumn==4 ); }else{ sqlite3VdbeMemSetInt64(pMem+0, i); sqlite3VdbeMemSetStr(pMem+1, (char*)sqlite3OpcodeName(pOp->opcode), @@ -2384,7 +2437,7 @@ int sqlite3VdbeList( sqlite3VdbeMemSetNull(pMem+7); #endif sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); - p->nResColumn = 8; + assert( p->nResColumn==8 ); } p->pResultRow = pMem; if( db->mallocFailed ){ @@ -2537,11 +2590,11 @@ void sqlite3VdbeRewind(Vdbe *p){ ** creating the virtual machine. This involves things such ** as allocating registers and initializing the program counter. ** After the VDBE has be prepped, it can be executed by one or more -** calls to sqlite3VdbeExec(). +** calls to sqlite3VdbeExec(). ** ** This function may be called exactly once on each virtual machine. ** After this routine is called the VM has been "packaged" and is ready -** to run. After this routine is called, further calls to +** to run. After this routine is called, further calls to ** sqlite3VdbeAddOp() functions are prohibited. This routine disconnects ** the Vdbe from the Parse object that helped generate it so that the ** the Vdbe becomes an independent entity and the Parse object can be @@ -2575,7 +2628,7 @@ void sqlite3VdbeMakeReady( nMem = pParse->nMem; nCursor = pParse->nTab; nArg = pParse->nMaxArg; - + /* Each cursor uses a memory cell. The first cursor (cursor 0) can ** use aMem[0] which is not otherwise used by the VDBE program. Allocate ** space at the end of aMem[] for cursors 1 and greater. @@ -2598,34 +2651,17 @@ void sqlite3VdbeMakeReady( resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain ){ - static const char * const azColName[] = { - "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", - "id", "parent", "notused", "detail" - }; - int iFirst, mx, i; if( nMem<10 ) nMem = 10; p->explain = pParse->explain; - if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(p, 4); - iFirst = 8; - mx = 12; - }else{ - sqlite3VdbeSetNumCols(p, 8); - iFirst = 0; - mx = 8; - } - for(i=iFirst; inResColumn = 12 - 4*p->explain; } p->expired = 0; /* Memory for registers, parameters, cursor, etc, is allocated in one or two - ** passes. On the first pass, we try to reuse unused memory at the + ** passes. On the first pass, we try to reuse unused memory at the ** end of the opcode array. If we are unable to satisfy all memory ** requirements by reusing the opcode array tail, then the second - ** pass will fill in the remainder using a fresh memory allocation. + ** pass will fill in the remainder using a fresh memory allocation. ** ** This two-pass approach that reuses as much memory as possible from ** the leftover memory at the end of the opcode array. This can significantly @@ -2663,13 +2699,29 @@ void sqlite3VdbeMakeReady( } /* -** Close a VDBE cursor and release all the resources that cursor +** Close a VDBE cursor and release all the resources that cursor ** happens to hold. */ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); } +static SQLITE_NOINLINE void freeCursorWithCache(Vdbe *p, VdbeCursor *pCx){ + VdbeTxtBlbCache *pCache = pCx->pCache; + assert( pCx->colCache ); + pCx->colCache = 0; + pCx->pCache = 0; + if( pCache->pCValue ){ + sqlite3RCStrUnref(pCache->pCValue); + pCache->pCValue = 0; + } + sqlite3DbFree(p->db, pCache); + sqlite3VdbeFreeCursorNN(p, pCx); +} void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ + if( pCx->colCache ){ + freeCursorWithCache(p, pCx); + return; + } switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); @@ -2733,7 +2785,7 @@ int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ /* ** Close all cursors. ** -** Also release any dynamic memory held by the VM in the Vdbe.aMem memory +** Also release any dynamic memory held by the VM in the Vdbe.aMem memory ** cell array. This is necessary as the memory cell array may contain ** pointers to VdbeFrame objects, which may in turn contain pointers to ** open cursors. @@ -2770,12 +2822,12 @@ void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ int n; sqlite3 *db = p->db; - if( p->nResColumn ){ - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + if( p->nResAlloc ){ + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbFree(db, p->aColName); } n = nResColumn*COLNAME_N; - p->nResColumn = (u16)nResColumn; + p->nResColumn = p->nResAlloc = (u16)nResColumn; p->aColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; initMemArray(p->aColName, n, db, MEM_Null); @@ -2800,14 +2852,14 @@ int sqlite3VdbeSetColName( ){ int rc; Mem *pColName; - assert( idxnResColumn ); + assert( idxnResAlloc ); assert( vardb->mallocFailed ){ assert( !zName || xDel!=SQLITE_DYNAMIC ); return SQLITE_NOMEM_BKPT; } assert( p->aColName!=0 ); - pColName = &(p->aColName[idx+var*p->nResColumn]); + pColName = &(p->aColName[idx+var*p->nResAlloc]); rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; @@ -2828,27 +2880,27 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ int needXcommit = 0; #ifdef SQLITE_OMIT_VIRTUALTABLE - /* With this option, sqlite3VtabSync() is defined to be simply - ** SQLITE_OK so p is not used. + /* With this option, sqlite3VtabSync() is defined to be simply + ** SQLITE_OK so p is not used. */ UNUSED_PARAMETER(p); #endif /* Before doing anything else, call the xSync() callback for any ** virtual module tables written in this transaction. This has to - ** be done before determining whether a super-journal file is + ** be done before determining whether a super-journal file is ** required, as an xSync() callback may add an attached database ** to the transaction. */ rc = sqlite3VtabSync(db, p); /* This loop determines (a) if the commit hook should be invoked and - ** (b) how many database files have open write transactions, not - ** including the temp database. (b) is important because if more than + ** (b) how many database files have open write transactions, not + ** including the temp database. (b) is important because if more than ** one database file has an open write transaction, a super-journal ** file is required for an atomic commit. - */ - for(i=0; rc==SQLITE_OK && inDb; i++){ + */ + for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){ /* Whether or not a database might need a super-journal depends upon @@ -2869,7 +2921,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ if( db->aDb[i].safety_level!=PAGER_SYNCHRONOUS_OFF && aMJNeeded[sqlite3PagerGetJournalMode(pPager)] && sqlite3PagerIsMemdb(pPager)==0 - ){ + ){ assert( i!=1 ); nTrans++; } @@ -2894,8 +2946,8 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ ** super-journal. ** ** If the return value of sqlite3BtreeGetFilename() is a zero length - ** string, it means the main database is :memory: or a temp file. In - ** that case we do not support atomic multi-file commits, so use the + ** string, it means the main database is :memory: or a temp file. In + ** that case we do not support atomic multi-file commits, so use the ** simple case then too. */ if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt)) @@ -2908,7 +2960,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ } } - /* Do the commit only if all databases successfully complete phase 1. + /* Do the commit only if all databases successfully complete phase 1. ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an ** IO error while deleting or truncating a journal file. It is unlikely, ** but could happen. In this case abandon processing and return the error. @@ -2967,7 +3019,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ }while( rc==SQLITE_OK && res ); if( rc==SQLITE_OK ){ /* Open the super-journal. */ - rc = sqlite3OsOpenMalloc(pVfs, zSuper, &pSuperJrnl, + rc = sqlite3OsOpenMalloc(pVfs, zSuper, &pSuperJrnl, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE| SQLITE_OPEN_EXCLUSIVE|SQLITE_OPEN_SUPER_JOURNAL, 0 ); @@ -2976,7 +3028,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ sqlite3DbFree(db, zSuper-4); return rc; } - + /* Write the name of each database file in the transaction into the new ** super-journal file. If an error occurs at this point close ** and delete the super-journal file. All the individual journal files @@ -3024,7 +3076,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ ** in case the super-journal file name was written into the journal ** file before the failure occurred. */ - for(i=0; rc==SQLITE_OK && inDb; i++){ + for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ rc = sqlite3BtreeCommitPhaseOne(pBt, zSuper); @@ -3057,7 +3109,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ */ disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); - for(i=0; inDb; i++){ + for(i=0; inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ sqlite3BtreeCommitPhaseTwo(pBt, 1); @@ -3073,7 +3125,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ return rc; } -/* +/* ** This routine checks that the sqlite3.nVdbeActive count variable ** matches the number of vdbe's in the list sqlite3.pVdbe that are ** currently active. An assertion fails if the two counts do not match. @@ -3109,10 +3161,10 @@ static void checkActiveVdbeCnt(sqlite3 *db){ ** If the Vdbe passed as the first argument opened a statement-transaction, ** close it now. Argument eOp must be either SAVEPOINT_ROLLBACK or ** SAVEPOINT_RELEASE. If it is SAVEPOINT_ROLLBACK, then the statement -** transaction is rolled back. If eOp is SAVEPOINT_RELEASE, then the +** transaction is rolled back. If eOp is SAVEPOINT_RELEASE, then the ** statement transaction is committed. ** -** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned. +** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned. ** Otherwise SQLITE_OK. */ static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ @@ -3125,7 +3177,7 @@ static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ assert( db->nStatement>0 ); assert( p->iStatement==(db->nStatement+db->nSavepoint) ); - for(i=0; inDb; i++){ + for(i=0; inDb; i++){ int rc2 = SQLITE_OK; Btree *pBt = db->aDb[i].pBt; if( pBt ){ @@ -3152,8 +3204,8 @@ static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ } } - /* If the statement transaction is being rolled back, also restore the - ** database handles deferred constraint counter to the value it had when + /* If the statement transaction is being rolled back, also restore the + ** database handles deferred constraint counter to the value it had when ** the statement transaction was opened. */ if( eOp==SAVEPOINT_ROLLBACK ){ db->nDeferredCons = p->nStmtDefCons; @@ -3170,20 +3222,20 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ /* -** This function is called when a transaction opened by the database -** handle associated with the VM passed as an argument is about to be +** This function is called when a transaction opened by the database +** handle associated with the VM passed as an argument is about to be ** committed. If there are outstanding deferred foreign key constraint ** violations, return SQLITE_ERROR. Otherwise, SQLITE_OK. ** -** If there are outstanding FK violations and this function returns +** If there are outstanding FK violations and this function returns ** SQLITE_ERROR, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY ** and write an error message to it. Then return SQLITE_ERROR. */ #ifndef SQLITE_OMIT_FOREIGN_KEY int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ sqlite3 *db = p->db; - if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) - || (!deferred && p->nFkConstraint>0) + if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) + || (!deferred && p->nFkConstraint>0) ){ p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; p->errorAction = OE_Abort; @@ -3214,7 +3266,7 @@ int sqlite3VdbeHalt(Vdbe *p){ /* This function contains the logic that determines if a statement or ** transaction will be committed or rolled back as a result of the - ** execution of this virtual machine. + ** execution of this virtual machine. ** ** If any of the following errors occur: ** @@ -3256,16 +3308,16 @@ int sqlite3VdbeHalt(Vdbe *p){ mrc = isSpecialError = 0; } if( isSpecialError ){ - /* If the query was read-only and the error code is SQLITE_INTERRUPT, - ** no rollback is necessary. Otherwise, at least a savepoint - ** transaction must be rolled back to restore the database to a + /* If the query was read-only and the error code is SQLITE_INTERRUPT, + ** no rollback is necessary. Otherwise, at least a savepoint + ** transaction must be rolled back to restore the database to a ** consistent state. ** ** Even if the statement is read-only, it is important to perform - ** a statement or transaction rollback operation. If the error + ** a statement or transaction rollback operation. If the error ** occurred while writing to the journal, sub-journal or database ** file as part of an effort to free up cache space (see function - ** pagerStress() in pager.c), the rollback is required to restore + ** pagerStress() in pager.c), the rollback is required to restore ** the pager to a consistent state. */ if( !p->readOnly || mrc!=SQLITE_INTERRUPT ){ @@ -3287,16 +3339,16 @@ int sqlite3VdbeHalt(Vdbe *p){ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ sqlite3VdbeCheckFk(p, 0); } - - /* If the auto-commit flag is set and this is the only active writer - ** VM, then we do either a commit or rollback of the current transaction. + + /* If the auto-commit flag is set and this is the only active writer + ** VM, then we do either a commit or rollback of the current transaction. ** - ** Note: This block also runs if one of the special errors handled - ** above has occurred. + ** Note: This block also runs if one of the special errors handled + ** above has occurred. */ - if( !sqlite3VtabInSync(db) - && db->autoCommit - && db->nVdbeWrite==(p->readOnly==0) + if( !sqlite3VtabInSync(db) + && db->autoCommit + && db->nVdbeWrite==(p->readOnly==0) ){ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ rc = sqlite3VdbeCheckFk(p, 1); @@ -3309,10 +3361,10 @@ int sqlite3VdbeHalt(Vdbe *p){ }else if( db->flags & SQLITE_CorruptRdOnly ){ rc = SQLITE_CORRUPT; db->flags &= ~SQLITE_CorruptRdOnly; - }else{ - /* The auto-commit flag is true, the vdbe program was successful + }else{ + /* The auto-commit flag is true, the vdbe program was successful ** or hit an 'OR FAIL' constraint and there are no deferred foreign - ** key constraints to hold up the transaction. This means a commit + ** key constraints to hold up the transaction. This means a commit ** is required. */ rc = vdbeCommit(db, p); } @@ -3320,6 +3372,7 @@ int sqlite3VdbeHalt(Vdbe *p){ sqlite3VdbeLeave(p); return SQLITE_BUSY; }else if( rc!=SQLITE_OK ){ + sqlite3SystemError(db, rc); p->rc = rc; sqlite3RollbackAll(db, SQLITE_OK); p->nChange = 0; @@ -3348,7 +3401,7 @@ int sqlite3VdbeHalt(Vdbe *p){ p->nChange = 0; } } - + /* If eStatementOp is non-zero, then a statement transaction needs to ** be committed or rolled back. Call sqlite3VdbeCloseStatement() to ** do so. If this operation returns an error, and the current statement @@ -3369,9 +3422,9 @@ int sqlite3VdbeHalt(Vdbe *p){ p->nChange = 0; } } - + /* If this was an INSERT, UPDATE or DELETE and no statement transaction - ** has been rolled back, update the database connection change-counter. + ** has been rolled back, update the database connection change-counter. */ if( p->changeCntOn ){ if( eStatementOp!=SAVEPOINT_ROLLBACK ){ @@ -3400,7 +3453,7 @@ int sqlite3VdbeHalt(Vdbe *p){ } /* If the auto-commit flag is set to true, then any locks that were held - ** by connection db have now been released. Call sqlite3ConnectionUnlocked() + ** by connection db have now been released. Call sqlite3ConnectionUnlocked() ** to invoke any required unlock-notify callbacks. */ if( db->autoCommit ){ @@ -3422,7 +3475,7 @@ void sqlite3VdbeResetStepResult(Vdbe *p){ /* ** Copy the error code and error message belonging to the VDBE passed -** as the first argument to its database handle (so that they will be +** as the first argument to its database handle (so that they will be ** returned by calls to sqlite3_errcode() and sqlite3_errmsg()). ** ** This function does not clear the VDBE error code or message, just @@ -3448,7 +3501,7 @@ int sqlite3VdbeTransferError(Vdbe *p){ #ifdef SQLITE_ENABLE_SQLLOG /* -** If an SQLITE_CONFIG_SQLLOG hook is registered and the VM has been run, +** If an SQLITE_CONFIG_SQLLOG hook is registered and the VM has been run, ** invoke it. */ static void vdbeInvokeSqllog(Vdbe *v){ @@ -3509,7 +3562,7 @@ int sqlite3VdbeReset(Vdbe *p){ /* Reset register contents and reclaim error message memory. */ #ifdef SQLITE_DEBUG - /* Execute assert() statements to ensure that the Vdbe.apCsr[] and + /* Execute assert() statements to ensure that the Vdbe.apCsr[] and ** Vdbe.aMem[] arrays have already been cleaned up. */ if( p->apCsr ) for(i=0; inCursor; i++) assert( p->apCsr[i]==0 ); if( p->aMem ){ @@ -3564,7 +3617,7 @@ int sqlite3VdbeReset(Vdbe *p){ #endif return p->rc & db->errMask; } - + /* ** Clean up and delete a VDBE after execution. Return an integer which is ** the result code. Write any error message text into *pzErrMsg. @@ -3588,8 +3641,8 @@ int sqlite3VdbeFinalize(Vdbe *p){ ** the first argument. ** ** Or, if iOp is greater than or equal to zero, then the destructor is -** only invoked for those auxiliary data pointers created by the user -** function invoked by the OP_Function opcode at instruction iOp of +** only invoked for those auxiliary data pointers created by the user +** function invoked by the OP_Function opcode at instruction iOp of ** VM pVdbe, and only then if: ** ** * the associated function parameter is the 32nd or later (counting @@ -3631,7 +3684,7 @@ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ assert( db!=0 ); assert( p->db==0 || p->db==db ); if( p->aColName ){ - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbNNFreeNN(db, p->aColName); } for(pSub=p->pProgram; pSub; pSub=pNext){ @@ -3861,7 +3914,7 @@ u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ ** The sizes for serial types less than 128 */ const u8 sqlite3SmallTypeSizes[128] = { - /* 0 1 2 3 4 5 6 7 8 9 */ + /* 0 1 2 3 4 5 6 7 8 9 */ /* 0 */ 0, 1, 2, 3, 4, 6, 8, 8, 0, 0, /* 10 */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, /* 20 */ 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, @@ -3884,19 +3937,19 @@ u32 sqlite3VdbeSerialTypeLen(u32 serial_type){ if( serial_type>=128 ){ return (serial_type-12)/2; }else{ - assert( serial_type<12 + assert( serial_type<12 || sqlite3SmallTypeSizes[serial_type]==(serial_type - 12)/2 ); return sqlite3SmallTypeSizes[serial_type]; } } u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ assert( serial_type<128 ); - return sqlite3SmallTypeSizes[serial_type]; + return sqlite3SmallTypeSizes[serial_type]; } /* -** If we are on an architecture with mixed-endian floating -** points (ex: ARM7) then swap the lower 4 bytes with the +** If we are on an architecture with mixed-endian floating +** points (ex: ARM7) then swap the lower 4 bytes with the ** upper 4 bytes. Return the result. ** ** For most architectures, this is a no-op. @@ -3918,7 +3971,7 @@ u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ ** (2007-08-30) Frank van Vugt has studied this problem closely ** and has send his findings to the SQLite developers. Frank ** writes that some Linux kernels offer floating point hardware -** emulation that uses only 32-bit mantissas instead of a full +** emulation that uses only 32-bit mantissas instead of a full ** 48-bits as required by the IEEE standard. (This is the ** CONFIG_FPE_FASTFPE option.) On such systems, floating point ** byte swapping becomes very complicated. To avoid problems, @@ -3962,7 +4015,7 @@ u64 sqlite3FloatSwap(u64 in){ ** The few cases that require local variables are broken out into a separate ** routine so that in most cases the overhead of moving the stack pointer ** is avoided. -*/ +*/ static void serialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ @@ -4045,7 +4098,7 @@ void sqlite3VdbeSerialGet( /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit ** twos-complement integer. */ pMem->u.i = FOUR_BYTE_INT(buf); -#ifdef __HP_cc +#ifdef __HP_cc /* Work around a sign-extension bug in the HP compiler for HP/UX */ if( buf[0]&0x80 ) pMem->u.i |= 0xffffffff80000000LL; #endif @@ -4098,7 +4151,7 @@ void sqlite3VdbeSerialGet( ** The space is either allocated using sqlite3DbMallocRaw() or from within ** the unaligned buffer passed via the second and third arguments (presumably ** stack space). If the former, then *ppFree is set to a pointer that should -** be eventually freed by the caller using sqlite3DbFree(). Or, if the +** be eventually freed by the caller using sqlite3DbFree(). Or, if the ** allocation comes from the pSpace/szSpace buffer, *ppFree is set to NULL ** before returning. ** @@ -4120,10 +4173,10 @@ UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( } /* -** Given the nKey-byte encoding of a record in pKey[], populate the +** Given the nKey-byte encoding of a record in pKey[], populate the ** UnpackedRecord structure indicated by the fourth argument with the ** contents of the decoded record. -*/ +*/ void sqlite3VdbeRecordUnpack( KeyInfo *pKeyInfo, /* Information about the record format */ int nKey, /* Size of the binary record */ @@ -4131,7 +4184,7 @@ void sqlite3VdbeRecordUnpack( UnpackedRecord *p /* Populate this structure before returning. */ ){ const unsigned char *aKey = (const unsigned char *)pKey; - u32 d; + u32 d; u32 idx; /* Offset in aKey[] to read from */ u16 u; /* Unsigned loop counter */ u32 szHdr; @@ -4158,7 +4211,7 @@ void sqlite3VdbeRecordUnpack( } if( d>(u32)nKey && u ){ assert( CORRUPT_DB ); - /* In a corrupt record entry, the last pMem might have been set up using + /* In a corrupt record entry, the last pMem might have been set up using ** uninitialized memory. Overwrite its value with NULL, to prevent ** warnings from MSAN. */ sqlite3VdbeMemSetNull(pMem-1); @@ -4202,13 +4255,13 @@ static int vdbeRecordCompareDebug( /* Compilers may complain that mem1.u.i is potentially uninitialized. ** We could initialize it, as shown here, to silence those complaints. - ** But in fact, mem1.u.i will never actually be used uninitialized, and doing + ** But in fact, mem1.u.i will never actually be used uninitialized, and doing ** the unnecessary initialization has a measurable negative performance ** impact, since this routine is a very high runner. And so, we choose ** to ignore the compiler warnings and leave this variable uninitialized. */ /* mem1.u.i = 0; // not needed, here to silence compiler warning */ - + idx1 = getVarint32(aKey1, szHdr1); if( szHdr1>98307 ) return SQLITE_CORRUPT; d1 = szHdr1; @@ -4229,8 +4282,17 @@ static int vdbeRecordCompareDebug( ** sqlite3VdbeSerialTypeLen() in the common case. */ if( d1+(u64)serial_type1+2>(u64)nKey1 - && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1 ){ + if( serial_type1>=1 + && serial_type1<=7 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)<=(u64)nKey1+8 + && CORRUPT_DB + ){ + return 1; /* corrupt record not detected by + ** sqlite3VdbeRecordCompareWithSkip(). Return true + ** to avoid firing the assert() */ + } break; } @@ -4246,7 +4308,7 @@ static int vdbeRecordCompareDebug( if( rc!=0 ){ assert( mem1.szMalloc==0 ); /* See comment below */ if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) - && ((mem1.flags & MEM_Null) || (pPKey2->aMem[i].flags & MEM_Null)) + && ((mem1.flags & MEM_Null) || (pPKey2->aMem[i].flags & MEM_Null)) ){ rc = -rc; } @@ -4292,7 +4354,7 @@ static int vdbeRecordCompareDebug( ** incorrectly. */ static void vdbeAssertFieldCountWithinLimits( - int nKey, const void *pKey, /* The record to verify */ + int nKey, const void *pKey, /* The record to verify */ const KeyInfo *pKeyInfo /* Compare size with this KeyInfo */ ){ int nField = 0; @@ -4318,7 +4380,7 @@ static void vdbeAssertFieldCountWithinLimits( /* ** Both *pMem1 and *pMem2 contain string values. Compare the two values ** using the collation sequence pColl. As usual, return a negative , zero -** or positive value if *pMem1 is less than, equal to or greater than +** or positive value if *pMem1 is less than, equal to or greater than ** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". */ static int vdbeCompareMemString( @@ -4445,7 +4507,7 @@ int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){ f2 = pMem2->flags; combined_flags = f1|f2; assert( !sqlite3VdbeMemIsRowSet(pMem1) && !sqlite3VdbeMemIsRowSet(pMem2) ); - + /* If one value is NULL, it is less than the other. If both values ** are NULL, return 0. */ @@ -4508,7 +4570,7 @@ int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){ } assert( pMem1->enc==pMem2->enc || pMem1->db->mallocFailed ); - assert( pMem1->enc==SQLITE_UTF8 || + assert( pMem1->enc==SQLITE_UTF8 || pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE ); /* The collation sequence must be defined at this point, even if @@ -4523,7 +4585,7 @@ int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){ /* If a NULL pointer was passed as the collate function, fall through ** to the blob case and use memcmp(). */ } - + /* Both values must be blobs. Compare using memcmp(). */ return sqlite3BlobCompare(pMem1, pMem2); } @@ -4531,7 +4593,7 @@ int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){ /* ** The first argument passed to this function is a serial-type that -** corresponds to an integer - all values between 1 and 9 inclusive +** corresponds to an integer - all values between 1 and 9 inclusive ** except 7. The second points to a buffer containing an integer value ** serialized according to serial_type. This function deserializes ** and returns the value. @@ -4573,7 +4635,7 @@ static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){ /* ** This function compares the two table rows or index records ** specified by {nKey1, pKey1} and pPKey2. It returns a negative, zero -** or positive integer if key1 is less than, equal to or +** or positive integer if key1 is less than, equal to or ** greater than key2. The {nKey1, pKey1} key must be a blob ** created by the OP_MakeRecord opcode of the VDBE. The pPKey2 ** key must be a parsed key such as obtained from @@ -4582,12 +4644,12 @@ static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){ ** If argument bSkip is non-zero, it is assumed that the caller has already ** determined that the first fields of the keys are equal. ** -** Key1 and Key2 do not have to contain the same number of fields. If all -** fields that appear in both keys are equal, then pPKey2->default_rc is +** Key1 and Key2 do not have to contain the same number of fields. If all +** fields that appear in both keys are equal, then pPKey2->default_rc is ** returned. ** -** If database corruption is discovered, set pPKey2->errCode to -** SQLITE_CORRUPT and return 0. If an OOM error is encountered, +** If database corruption is discovered, set pPKey2->errCode to +** SQLITE_CORRUPT and return 0. If an OOM error is encountered, ** pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the ** malloc-failed flag set on database handle (pPKey2->pKeyInfo->db). */ @@ -4629,13 +4691,13 @@ int sqlite3VdbeRecordCompareWithSkip( d1 = szHdr1; i = 0; } - if( d1>(unsigned)nKey1 ){ + if( d1>(unsigned)nKey1 ){ pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; return 0; /* Corruption */ } VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */ - assert( pPKey2->pKeyInfo->nAllField>=pPKey2->nField + assert( pPKey2->pKeyInfo->nAllField>=pPKey2->nField || CORRUPT_DB ); assert( pPKey2->pKeyInfo->aSortFlags!=0 ); assert( pPKey2->pKeyInfo->nKeyField>0 ); @@ -4672,9 +4734,9 @@ int sqlite3VdbeRecordCompareWithSkip( serial_type = aKey1[idx1]; if( serial_type>=10 ){ /* Serial types 12 or greater are strings and blobs (greater than - ** numbers). Types 10 and 11 are currently "reserved for future + ** numbers). Types 10 and 11 are currently "reserved for future ** use", so it doesn't really matter what the results of comparing - ** them to numberic values are. */ + ** them to numeric values are. */ rc = serial_type==10 ? -1 : +1; }else if( serial_type==0 ){ rc = -1; @@ -4720,7 +4782,7 @@ int sqlite3VdbeRecordCompareWithSkip( }else{ int nCmp = MIN(mem1.n, pRhs->n); rc = memcmp(&aKey1[d1], pRhs->z, nCmp); - if( rc==0 ) rc = mem1.n - pRhs->n; + if( rc==0 ) rc = mem1.n - pRhs->n; } } } @@ -4794,8 +4856,8 @@ int sqlite3VdbeRecordCompareWithSkip( /* rc==0 here means that one or both of the keys ran out of fields and ** all the fields up to that point were equal. Return the default_rc ** value. */ - assert( CORRUPT_DB - || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) + assert( CORRUPT_DB + || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) || pPKey2->pKeyInfo->db->mallocFailed ); pPKey2->eqSeen = 1; @@ -4810,8 +4872,8 @@ int sqlite3VdbeRecordCompare( /* -** This function is an optimized version of sqlite3VdbeRecordCompare() -** that (a) the first field of pPKey2 is an integer, and (b) the +** This function is an optimized version of sqlite3VdbeRecordCompare() +** that (a) the first field of pPKey2 is an integer, and (b) the ** size-of-header varint at the start of (pKey1/nKey1) fits in a single ** byte (i.e. is less than 128). ** @@ -4866,7 +4928,7 @@ static int vdbeRecordCompareInt( testcase( lhs<0 ); break; } - case 8: + case 8: lhs = 0; break; case 9: @@ -4874,11 +4936,11 @@ static int vdbeRecordCompareInt( break; /* This case could be removed without changing the results of running - ** this code. Including it causes gcc to generate a faster switch + ** this code. Including it causes gcc to generate a faster switch ** statement (since the range of switch targets now starts at zero and ** is contiguous) but does not cause any duplicate code to be generated - ** (as gcc is clever enough to combine the two like cases). Other - ** compilers might be similar. */ + ** (as gcc is clever enough to combine the two like cases). Other + ** compilers might be similar. */ case 0: case 7: return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); @@ -4893,7 +4955,7 @@ static int vdbeRecordCompareInt( }else if( vr2; }else if( pPKey2->nField>1 ){ - /* The first fields of the two keys are equal. Compare the trailing + /* The first fields of the two keys are equal. Compare the trailing ** fields. */ res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); }else{ @@ -4908,9 +4970,9 @@ static int vdbeRecordCompareInt( } /* -** This function is an optimized version of sqlite3VdbeRecordCompare() +** This function is an optimized version of sqlite3VdbeRecordCompare() ** that (a) the first field of pPKey2 is a string, that (b) the first field -** uses the collation sequence BINARY and (c) that the size-of-header varint +** uses the collation sequence BINARY and (c) that the size-of-header varint ** at the start of (pKey1/nKey1) fits in a single byte. */ static int vdbeRecordCompareString( @@ -4935,7 +4997,7 @@ static int vdbeRecordCompareString( assert( CORRUPT_DB ); } res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ - }else if( !(serial_type & 0x01) ){ + }else if( !(serial_type & 0x01) ){ res = pPKey2->r2; /* (pKey1/nKey1) is a blob */ }else{ int nCmp; @@ -4987,7 +5049,7 @@ RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ /* varintRecordCompareInt() and varintRecordCompareString() both assume ** that the size-of-header varint that occurs at the start of each record ** fits in a single byte (i.e. is 127 or less). varintRecordCompareInt() - ** also assumes that it is safe to overread a buffer by at least the + ** also assumes that it is safe to overread a buffer by at least the ** maximum possible legal header size plus 8 bytes. Because there is ** guaranteed to be at least 74 (but not 136) bytes of padding following each ** buffer passed to varintRecordCompareInt() this makes it convenient to @@ -5048,7 +5110,7 @@ int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* Get the size of the index entry. Only indices entries of less ** than 2GiB are support - anything large must be database corruption. ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so - ** this code can safely assume that nCellKey is 32-bits + ** this code can safely assume that nCellKey is 32-bits */ assert( sqlite3BtreeCursorIsValid(pCur) ); nCellKey = sqlite3BtreePayloadSize(pCur); @@ -5113,7 +5175,7 @@ int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ ** ** pUnpacked is either created without a rowid or is truncated so that it ** omits the rowid at the end. The rowid at the end of the index entry -** is ignored as well. Hence, this routine only compares the prefixes +** is ignored as well. Hence, this routine only compares the prefixes ** of the keys prior to the final rowid, not the entire key. */ int sqlite3VdbeIdxKeyCompare( @@ -5149,7 +5211,7 @@ int sqlite3VdbeIdxKeyCompare( /* ** This routine sets the value to be returned by subsequent calls to -** sqlite3_changes() on the database handle 'db'. +** sqlite3_changes() on the database handle 'db'. */ void sqlite3VdbeSetChanges(sqlite3 *db, i64 nChange){ assert( sqlite3_mutex_held(db->mutex) ); @@ -5206,7 +5268,7 @@ u8 sqlite3VdbePrepareFlags(Vdbe *v){ /* ** Return a pointer to an sqlite3_value structure containing the value bound -** parameter iVar of VM v. Except, if the value is an SQL NULL, return +** parameter iVar of VM v. Except, if the value is an SQL NULL, return ** 0 instead. Unless it is NULL, apply affinity aff (one of the SQLITE_AFF_* ** constants) to the value before returning it. ** @@ -5281,7 +5343,7 @@ int sqlite3NotPureFunc(sqlite3_context *pCtx){ #if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) /* ** This Walker callback is used to help verify that calls to -** sqlite3BtreeCursorHint() with opcode BTREE_HINT_RANGE have +** sqlite3BtreeCursorHint() with opcode BTREE_HINT_RANGE have ** byte-code register values correctly initialized. */ int sqlite3CursorRangeHintExprCheck(Walker *pWalker, Expr *pExpr){ @@ -5312,7 +5374,7 @@ void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* -** If the second argument is not NULL, release any allocations associated +** If the second argument is not NULL, release any allocations associated ** with the memory cells in the p->aMem[] array. Also free the UnpackedRecord ** structure itself, using sqlite3DbFree(). ** @@ -5380,7 +5442,7 @@ void sqlite3VdbePreUpdateHook( assert( pCsr!=0 ); assert( pCsr->eCurType==CURTYPE_BTREE ); - assert( pCsr->nField==nRealCol + assert( pCsr->nField==nRealCol || (pCsr->nField==nRealCol+1 && op==SQLITE_DELETE && iReg==-1) ); diff --git a/src/vdbemem.c b/src/vdbemem.c index d3cd55ba9..e25efc977 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -314,6 +314,40 @@ int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ return SQLITE_OK; } +/* +** If pMem is already a string, detect if it is a zero-terminated +** string, or make it into one if possible, and mark it as such. +** +** This is an optimization. Correct operation continues even if +** this routine is a no-op. +*/ +void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ + if( (pMem->flags & (MEM_Str|MEM_Term|MEM_Ephem|MEM_Static))!=MEM_Str ){ + /* pMem must be a string, and it cannot be an ephemeral or static string */ + return; + } + if( pMem->enc!=SQLITE_UTF8 ) return; + if( NEVER(pMem->z==0) ) return; + if( pMem->flags & MEM_Dyn ){ + if( pMem->xDel==sqlite3_free + && sqlite3_msize(pMem->z) >= (u64)(pMem->n+1) + ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return; + } + if( pMem->xDel==(void(*)(void*))sqlite3RCStrUnref ){ + /* Blindly assume that all RCStr objects are zero-terminated */ + pMem->flags |= MEM_Term; + return; + } + }else if( pMem->szMalloc >= pMem->n+1 ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return; + } +} + /* ** It is already known that pMem contains an unterminated string. ** Add the zero terminator. @@ -575,36 +609,6 @@ void sqlite3VdbeMemReleaseMalloc(Mem *p){ if( p->szMalloc ) vdbeMemClear(p); } -/* -** Convert a 64-bit IEEE double into a 64-bit signed integer. -** If the double is out of range of a 64-bit signed integer then -** return the closest available 64-bit signed integer. -*/ -static SQLITE_NOINLINE i64 doubleToInt64(double r){ -#ifdef SQLITE_OMIT_FLOATING_POINT - /* When floating-point is omitted, double and int64 are the same thing */ - return r; -#else - /* - ** Many compilers we encounter do not define constants for the - ** minimum and maximum 64-bit integers, or they define them - ** inconsistently. And many do not understand the "LL" notation. - ** So we define our own static constants here using nothing - ** larger than a 32-bit integer constant. - */ - static const i64 maxInt = LARGEST_INT64; - static const i64 minInt = SMALLEST_INT64; - - if( r<=(double)minInt ){ - return minInt; - }else if( r>=(double)maxInt ){ - return maxInt; - }else{ - return (i64)r; - } -#endif -} - /* ** Return some kind of integer value which is the best we can do ** at representing the value that *pMem describes as an integer. @@ -631,7 +635,7 @@ i64 sqlite3VdbeIntValue(const Mem *pMem){ testcase( flags & MEM_IntReal ); return pMem->u.i; }else if( flags & MEM_Real ){ - return doubleToInt64(pMem->u.r); + return sqlite3RealToI64(pMem->u.r); }else if( (flags & (MEM_Str|MEM_Blob))!=0 && pMem->z!=0 ){ return memIntValue(pMem); }else{ @@ -693,7 +697,7 @@ void sqlite3VdbeIntegerAffinity(Mem *pMem){ if( pMem->flags & MEM_IntReal ){ MemSetTypeFlag(pMem, MEM_Int); }else{ - i64 ix = doubleToInt64(pMem->u.r); + i64 ix = sqlite3RealToI64(pMem->u.r); /* Only mark the value as an integer if ** @@ -761,8 +765,8 @@ int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ ** from UBSAN. */ i64 sqlite3RealToI64(double r){ - if( r<=(double)SMALLEST_INT64 ) return SMALLEST_INT64; - if( r>=(double)LARGEST_INT64) return LARGEST_INT64; + if( r<-9223372036854774784.0 ) return SMALLEST_INT64; + if( r>+9223372036854774784.0 ) return LARGEST_INT64; return (i64)r; } @@ -833,6 +837,7 @@ int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ break; } default: { + int rc; assert( aff==SQLITE_AFF_TEXT ); assert( MEM_Str==(MEM_Blob>>3) ); pMem->flags |= (pMem->flags&MEM_Blob)>>3; @@ -840,7 +845,9 @@ int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal|MEM_Blob|MEM_Zero); if( encoding!=SQLITE_UTF8 ) pMem->n &= ~1; - return sqlite3VdbeChangeEncoding(pMem, encoding); + rc = sqlite3VdbeChangeEncoding(pMem, encoding); + if( rc ) return rc; + sqlite3VdbeMemZeroTerminateIfAble(pMem); } } return SQLITE_OK; @@ -1364,6 +1371,24 @@ const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ return valueToText(pVal, enc); } +/* Return true if sqlit3_value object pVal is a string or blob value +** that uses the destructor specified in the second argument. +** +** TODO: Maybe someday promote this interface into a published API so +** that third-party extensions can get access to it? +*/ +int sqlite3ValueIsOfClass(const sqlite3_value *pVal, void(*xFree)(void*)){ + if( ALWAYS(pVal!=0) + && ALWAYS((pVal->flags & (MEM_Str|MEM_Blob))!=0) + && (pVal->flags & MEM_Dyn)!=0 + && pVal->xDel==xFree + ){ + return 1; + }else{ + return 0; + } +} + /* ** Create a new sqlite3_value object. */ @@ -1431,6 +1456,7 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ } pRec->nField = p->iVal+1; + sqlite3VdbeMemSetNull(&pRec->aMem[p->iVal]); return &pRec->aMem[p->iVal]; } #else diff --git a/src/vdbesort.c b/src/vdbesort.c index 3958662cc..2b7da94f7 100644 --- a/src/vdbesort.c +++ b/src/vdbesort.c @@ -55,7 +55,7 @@ ** is like Close() followed by Init() only ** much faster. ** -** The interfaces above must be called in a particular order. Write() can +** The interfaces above must be called in a particular order. Write() can ** only occur in between Init()/Reset() and Rewind(). Next(), Rowkey(), and ** Compare() can only occur in between Rewind() and Close()/Reset(). i.e. ** @@ -63,16 +63,16 @@ ** for each record: Write() ** Rewind() ** Rowkey()/Compare() -** Next() +** Next() ** Close() ** ** Algorithm: ** -** Records passed to the sorter via calls to Write() are initially held +** Records passed to the sorter via calls to Write() are initially held ** unsorted in main memory. Assuming the amount of memory used never exceeds ** a threshold, when Rewind() is called the set of records is sorted using ** an in-memory merge sort. In this case, no temporary files are required -** and subsequent calls to Rowkey(), Next() and Compare() read records +** and subsequent calls to Rowkey(), Next() and Compare() read records ** directly from main memory. ** ** If the amount of space used to store records in main memory exceeds the @@ -82,10 +82,10 @@ ** of PMAs may be created by merging existing PMAs together - for example ** merging two or more level-0 PMAs together creates a level-1 PMA. ** -** The threshold for the amount of main memory to use before flushing +** The threshold for the amount of main memory to use before flushing ** records to a PMA is roughly the same as the limit configured for the -** page-cache of the main database. Specifically, the threshold is set to -** the value returned by "PRAGMA main.page_size" multipled by +** page-cache of the main database. Specifically, the threshold is set to +** the value returned by "PRAGMA main.page_size" multiplied by ** that returned by "PRAGMA main.cache_size", in bytes. ** ** If the sorter is running in single-threaded mode, then all PMAs generated @@ -102,28 +102,28 @@ ** than zero, and (b) worker threads have been enabled at runtime by calling ** "PRAGMA threads=N" with some value of N greater than 0. ** -** When Rewind() is called, any data remaining in memory is flushed to a +** When Rewind() is called, any data remaining in memory is flushed to a ** final PMA. So at this point the data is stored in some number of sorted ** PMAs within temporary files on disk. ** ** If there are fewer than SORTER_MAX_MERGE_COUNT PMAs in total and the ** sorter is running in single-threaded mode, then these PMAs are merged -** incrementally as keys are retreived from the sorter by the VDBE. The +** incrementally as keys are retrieved from the sorter by the VDBE. The ** MergeEngine object, described in further detail below, performs this ** merge. ** ** Or, if running in multi-threaded mode, then a background thread is ** launched to merge the existing PMAs. Once the background thread has -** merged T bytes of data into a single sorted PMA, the main thread +** merged T bytes of data into a single sorted PMA, the main thread ** begins reading keys from that PMA while the background thread proceeds ** with merging the next T bytes of data. And so on. ** -** Parameter T is set to half the value of the memory threshold used +** Parameter T is set to half the value of the memory threshold used ** by Write() above to determine when to create a new PMA. ** -** If there are more than SORTER_MAX_MERGE_COUNT PMAs in total when -** Rewind() is called, then a hierarchy of incremental-merges is used. -** First, T bytes of data from the first SORTER_MAX_MERGE_COUNT PMAs on +** If there are more than SORTER_MAX_MERGE_COUNT PMAs in total when +** Rewind() is called, then a hierarchy of incremental-merges is used. +** First, T bytes of data from the first SORTER_MAX_MERGE_COUNT PMAs on ** disk are merged together. Then T bytes of data from the second set, and ** so on, such that no operation ever merges more than SORTER_MAX_MERGE_COUNT ** PMAs at a time. This done is to improve locality. @@ -138,7 +138,7 @@ #include "sqliteInt.h" #include "vdbeInt.h" -/* +/* ** If SQLITE_DEBUG_SORTER_THREADS is defined, this module outputs various ** messages to stderr that may be helpful in understanding the performance ** characteristics of the sorter in multi-threaded mode. @@ -167,7 +167,7 @@ typedef struct SorterList SorterList; /* In-memory list of records */ typedef struct IncrMerger IncrMerger; /* Read & merge multiple PMAs */ /* -** A container for a temp file handle and the current amount of data +** A container for a temp file handle and the current amount of data ** stored in the file. */ struct SorterFile { @@ -207,17 +207,17 @@ struct SorterList { ** the MergeEngine.nTree variable. ** ** The final (N/2) elements of aTree[] contain the results of comparing -** pairs of PMA keys together. Element i contains the result of +** pairs of PMA keys together. Element i contains the result of ** comparing aReadr[2*i-N] and aReadr[2*i-N+1]. Whichever key is smaller, the -** aTree element is set to the index of it. +** aTree element is set to the index of it. ** ** For the purposes of this comparison, EOF is considered greater than any ** other key value. If the keys are equal (only possible with two EOF ** values), it doesn't matter which index is stored. ** -** The (N/4) elements of aTree[] that precede the final (N/2) described +** The (N/4) elements of aTree[] that precede the final (N/2) described ** above contains the index of the smallest of each block of 4 PmaReaders -** And so on. So that aTree[1] contains the index of the PmaReader that +** And so on. So that aTree[1] contains the index of the PmaReader that ** currently points to the smallest key value. aTree[0] is unused. ** ** Example: @@ -233,7 +233,7 @@ struct SorterList { ** ** aTree[] = { X, 5 0, 5 0, 3, 5, 6 } ** -** The current element is "Apple" (the value of the key indicated by +** The current element is "Apple" (the value of the key indicated by ** PmaReader 5). When the Next() operation is invoked, PmaReader 5 will ** be advanced to the next key in its segment. Say the next key is ** "Eggplant": @@ -271,11 +271,11 @@ struct MergeEngine { ** ** Essentially, this structure contains all those fields of the VdbeSorter ** structure for which each thread requires a separate instance. For example, -** each thread requries its own UnpackedRecord object to unpack records in +** each thread requeries its own UnpackedRecord object to unpack records in ** as part of comparison operations. ** -** Before a background thread is launched, variable bDone is set to 0. Then, -** right before it exits, the thread itself sets bDone to 1. This is used for +** Before a background thread is launched, variable bDone is set to 0. Then, +** right before it exits, the thread itself sets bDone to 1. This is used for ** two purposes: ** ** 1. When flushing the contents of memory to a level-0 PMA on disk, to @@ -306,7 +306,7 @@ struct SortSubtask { /* -** Main sorter structure. A single instance of this is allocated for each +** Main sorter structure. A single instance of this is allocated for each ** sorter cursor created by the VDBE. ** ** mxKeysize: @@ -343,7 +343,7 @@ struct VdbeSorter { ** PMA, in sorted order. The next key to be read is cached in nKey/aKey. ** aKey might point into aMap or into aBuffer. If neither of those locations ** contain a contiguous representation of the key, then aAlloc is allocated -** and the key is copied into aAlloc and aKey is made to poitn to aAlloc. +** and the key is copied into aAlloc and aKey is made to point to aAlloc. ** ** pFd==0 at EOF. */ @@ -362,21 +362,21 @@ struct PmaReader { }; /* -** Normally, a PmaReader object iterates through an existing PMA stored +** Normally, a PmaReader object iterates through an existing PMA stored ** within a temp file. However, if the PmaReader.pIncr variable points to ** an object of the following type, it may be used to iterate/merge through ** multiple PMAs simultaneously. ** -** There are two types of IncrMerger object - single (bUseThread==0) and -** multi-threaded (bUseThread==1). +** There are two types of IncrMerger object - single (bUseThread==0) and +** multi-threaded (bUseThread==1). ** -** A multi-threaded IncrMerger object uses two temporary files - aFile[0] -** and aFile[1]. Neither file is allowed to grow to more than mxSz bytes in -** size. When the IncrMerger is initialized, it reads enough data from -** pMerger to populate aFile[0]. It then sets variables within the -** corresponding PmaReader object to read from that file and kicks off -** a background thread to populate aFile[1] with the next mxSz bytes of -** sorted record data from pMerger. +** A multi-threaded IncrMerger object uses two temporary files - aFile[0] +** and aFile[1]. Neither file is allowed to grow to more than mxSz bytes in +** size. When the IncrMerger is initialized, it reads enough data from +** pMerger to populate aFile[0]. It then sets variables within the +** corresponding PmaReader object to read from that file and kicks off +** a background thread to populate aFile[1] with the next mxSz bytes of +** sorted record data from pMerger. ** ** When the PmaReader reaches the end of aFile[0], it blocks until the ** background thread has finished populating aFile[1]. It then exchanges @@ -387,7 +387,7 @@ struct PmaReader { ** ** A single-threaded IncrMerger does not open any temporary files of its ** own. Instead, it has exclusive access to mxSz bytes of space beginning -** at offset iStartOff of file pTask->file2. And instead of using a +** at offset iStartOff of file pTask->file2. And instead of using a ** background thread to prepare data for the PmaReader, with a single ** threaded IncrMerger the allocate part of pTask->file2 is "refilled" with ** keys from pMerger by the calling thread whenever the PmaReader runs out @@ -499,7 +499,7 @@ static int vdbePmaReadBlob( assert( p->aBuffer ); - /* If there is no more data to be read from the buffer, read the next + /* If there is no more data to be read from the buffer, read the next ** p->nBuffer bytes of data from the file into it. Or, if there are less ** than p->nBuffer bytes remaining in the PMA, read all remaining data. */ iBuf = p->iReadOff % p->nBuffer; @@ -520,11 +520,11 @@ static int vdbePmaReadBlob( assert( rc!=SQLITE_IOERR_SHORT_READ ); if( rc!=SQLITE_OK ) return rc; } - nAvail = p->nBuffer - iBuf; + nAvail = p->nBuffer - iBuf; if( nByte<=nAvail ){ /* The requested data is available in the in-memory buffer. In this - ** case there is no need to make a copy of the data, just return a + ** case there is no need to make a copy of the data, just return a ** pointer into the buffer to the caller. */ *ppOut = &p->aBuffer[iBuf]; p->iReadOff += nByte; @@ -603,7 +603,7 @@ static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){ /* ** Attempt to memory map file pFile. If successful, set *pp to point to the -** new mapping and return SQLITE_OK. If the mapping is not attempted +** new mapping and return SQLITE_OK. If the mapping is not attempted ** (because the file is too large or the VFS layer is configured not to use ** mmap), return SQLITE_OK and set *pp to NULL. ** @@ -624,7 +624,7 @@ static int vdbeSorterMapFile(SortSubtask *pTask, SorterFile *pFile, u8 **pp){ /* ** Attach PmaReader pReadr to file pFile (if it is not already attached to -** that file) and seek it to offset iOff within the file. Return SQLITE_OK +** that file) and seek it to offset iOff within the file. Return SQLITE_OK ** if successful, or an SQLite error code if an error occurs. */ static int vdbePmaReaderSeek( @@ -714,11 +714,11 @@ static int vdbePmaReaderNext(PmaReader *pReadr){ /* ** Initialize PmaReader pReadr to scan through the PMA stored in file pFile -** starting at offset iStart and ending at offset iEof-1. This function -** leaves the PmaReader pointing to the first key in the PMA (or EOF if the +** starting at offset iStart and ending at offset iEof-1. This function +** leaves the PmaReader pointing to the first key in the PMA (or EOF if the ** PMA is empty). ** -** If the pnByte parameter is NULL, then it is assumed that the file +** If the pnByte parameter is NULL, then it is assumed that the file ** contains a single PMA, and that that PMA omits the initial length varint. */ static int vdbePmaReaderInit( @@ -751,7 +751,7 @@ static int vdbePmaReaderInit( /* ** A version of vdbeSorterCompare() that assumes that it has already been -** determined that the first field of key1 is equal to the first field of +** determined that the first field of key1 is equal to the first field of ** key2. */ static int vdbeSorterCompareTail( @@ -769,7 +769,7 @@ static int vdbeSorterCompareTail( } /* -** Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2, +** Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2, ** size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences ** used by the comparison. Return the result of the comparison. ** @@ -915,7 +915,7 @@ static int vdbeSorterCompareInt( ** is non-zero and the sorter is able to guarantee a stable sort, nField ** is used instead. This is used when sorting records for a CREATE INDEX ** statement. In this case, keys are always delivered to the sorter in -** order of the primary key, which happens to be make up the final part +** order of the primary key, which happens to be make up the final part ** of the records being sorted. So if the sort is stable, there is never ** any reason to compare PK fields and they can be ignored for a small ** performance boost. @@ -1016,7 +1016,7 @@ int sqlite3VdbeSorterInit( } } - if( pKeyInfo->nAllField<13 + if( pKeyInfo->nAllField<13 && (pKeyInfo->aColl[0]==0 || pKeyInfo->aColl[0]==db->pDfltColl) && (pKeyInfo->aSortFlags[0] & KEYINFO_ORDER_BIGNULL)==0 ){ @@ -1041,7 +1041,7 @@ static void vdbeSorterRecordFree(sqlite3 *db, SorterRecord *pRecord){ } /* -** Free all resources owned by the object indicated by argument pTask. All +** Free all resources owned by the object indicated by argument pTask. All ** fields of *pTask are zeroed before returning. */ static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){ @@ -1141,7 +1141,7 @@ static int vdbeSorterCreateThread( } /* -** Join all outstanding threads launched by SorterWrite() to create +** Join all outstanding threads launched by SorterWrite() to create ** level-0 PMAs. */ static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){ @@ -1150,10 +1150,10 @@ static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){ /* This function is always called by the main user thread. ** - ** If this function is being called after SorterRewind() has been called, + ** If this function is being called after SorterRewind() has been called, ** it is possible that thread pSorter->aTask[pSorter->nTask-1].pThread ** is currently attempt to join one of the other threads. To avoid a race - ** condition where this thread also attempts to join the same object, join + ** condition where this thread also attempts to join the same object, join ** thread pSorter->aTask[pSorter->nTask-1].pThread first. */ for(i=pSorter->nTask-1; i>=0; i--){ SortSubtask *pTask = &pSorter->aTask[i]; @@ -1325,8 +1325,8 @@ static int vdbeSorterOpenTempFile( } /* -** If it has not already been allocated, allocate the UnpackedRecord -** structure at pTask->pUnpacked. Return SQLITE_OK if successful (or +** If it has not already been allocated, allocate the UnpackedRecord +** structure at pTask->pUnpacked. Return SQLITE_OK if successful (or ** if no allocation was required), or SQLITE_NOMEM otherwise. */ static int vdbeSortAllocUnpacked(SortSubtask *pTask){ @@ -1389,14 +1389,14 @@ static SorterCompare vdbeSorterGetCompare(VdbeSorter *p){ if( p->typeMask==SORTER_TYPE_INTEGER ){ return vdbeSorterCompareInt; }else if( p->typeMask==SORTER_TYPE_TEXT ){ - return vdbeSorterCompareText; + return vdbeSorterCompareText; } return vdbeSorterCompare; } /* -** Sort the linked list of records headed at pTask->pList. Return -** SQLITE_OK if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if +** Sort the linked list of records headed at pTask->pList. Return +** SQLITE_OK if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if ** an error occurs. */ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){ @@ -1441,8 +1441,8 @@ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){ } pList->pList = p; - assert( pTask->pUnpacked->errCode==SQLITE_OK - || pTask->pUnpacked->errCode==SQLITE_NOMEM + assert( pTask->pUnpacked->errCode==SQLITE_OK + || pTask->pUnpacked->errCode==SQLITE_NOMEM ); return pTask->pUnpacked->errCode; } @@ -1483,8 +1483,8 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ memcpy(&p->aBuffer[p->iBufEnd], &pData[nData-nRem], nCopy); p->iBufEnd += nCopy; if( p->iBufEnd==p->nBuffer ){ - p->eFWErr = sqlite3OsWrite(p->pFd, - &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, + p->eFWErr = sqlite3OsWrite(p->pFd, + &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); p->iBufStart = p->iBufEnd = 0; @@ -1499,7 +1499,7 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ /* ** Flush any buffered data to disk and clean up the PMA-writer object. ** The results of using the PMA-writer after this call are undefined. -** Return SQLITE_OK if flushing the buffered data succeeds or is not +** Return SQLITE_OK if flushing the buffered data succeeds or is not ** required. Otherwise, return an SQLite error code. ** ** Before returning, set *piEof to the offset immediately following the @@ -1508,8 +1508,8 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ int rc; if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ - p->eFWErr = sqlite3OsWrite(p->pFd, - &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, + p->eFWErr = sqlite3OsWrite(p->pFd, + &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); } @@ -1521,11 +1521,11 @@ static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ } /* -** Write value iVal encoded as a varint to the PMA. Return +** Write value iVal encoded as a varint to the PMA. Return ** SQLITE_OK if successful, or an SQLite error code if an error occurs. */ static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ - int nByte; + int nByte; u8 aByte[10]; nByte = sqlite3PutVarint(aByte, iVal); vdbePmaWriteBlob(p, aByte, nByte); @@ -1533,7 +1533,7 @@ static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ /* ** Write the current contents of in-memory linked-list pList to a level-0 -** PMA in the temp file belonging to sub-task pTask. Return SQLITE_OK if +** PMA in the temp file belonging to sub-task pTask. Return SQLITE_OK if ** successful, or an SQLite error code otherwise. ** ** The format of a PMA is: @@ -1541,8 +1541,8 @@ static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ ** * A varint. This varint contains the total number of bytes of content ** in the PMA (not including the varint itself). ** -** * One or more records packed end-to-end in order of ascending keys. -** Each record consists of a varint followed by a blob of data (the +** * One or more records packed end-to-end in order of ascending keys. +** Each record consists of a varint followed by a blob of data (the ** key). The varint is the number of bytes in the blob of data. */ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ @@ -1551,7 +1551,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ PmaWriter writer; /* Object used to write to the file */ #ifdef SQLITE_DEBUG - /* Set iSz to the expected size of file pTask->file after writing the PMA. + /* Set iSz to the expected size of file pTask->file after writing the PMA. ** This is used by an assert() statement at the end of this function. */ i64 iSz = pList->szPMA + sqlite3VarintLen(pList->szPMA) + pTask->file.iEof; #endif @@ -1704,7 +1704,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ SortSubtask *pTask = 0; /* Thread context used to create new PMA */ int nWorker = (pSorter->nTask-1); - /* Set the flag to indicate that at least one PMA has been written. + /* Set the flag to indicate that at least one PMA has been written. ** Or will be, anyhow. */ pSorter->bUsePMA = 1; @@ -1714,7 +1714,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ ** the background thread from a sub-tasks previous turn is still running, ** skip it. If the first (pSorter->nTask-1) sub-tasks are all still busy, ** fall back to using the final sub-task. The first (pSorter->nTask-1) - ** sub-tasks are prefered as they use background threads - the final + ** sub-tasks are preferred as they use background threads - the final ** sub-task uses the main thread. */ for(i=0; iiPrev + i + 1) % nWorker; @@ -1795,14 +1795,14 @@ int sqlite3VdbeSorterWrite( ** If using the single large allocation mode (pSorter->aMemory!=0), then ** flush the contents of memory to a new PMA if (a) at least one value is ** already in memory and (b) the new value will not fit in memory. - ** + ** ** Or, if using separate allocations for each record, flush the contents ** of memory to a PMA if either of the following are true: ** - ** * The total memory allocated for the in-memory list is greater + ** * The total memory allocated for the in-memory list is greater ** than (page-size * cache-size), or ** - ** * The total memory allocated for the in-memory list is greater + ** * The total memory allocated for the in-memory list is greater ** than (page-size * 10) and sqlite3HeapNearlyFull() returns true. */ nReq = pVal->n + sizeof(SorterRecord); @@ -1941,11 +1941,11 @@ static int vdbeIncrBgPopulate(IncrMerger *pIncr){ ** aFile[0] such that the PmaReader should start rereading it from the ** beginning. ** -** For single-threaded objects, this is accomplished by literally reading -** keys from pIncr->pMerger and repopulating aFile[0]. +** For single-threaded objects, this is accomplished by literally reading +** keys from pIncr->pMerger and repopulating aFile[0]. ** -** For multi-threaded objects, all that is required is to wait until the -** background thread is finished (if it is not already) and then swap +** For multi-threaded objects, all that is required is to wait until the +** background thread is finished (if it is not already) and then swap ** aFile[0] and aFile[1] in place. If the contents of pMerger have not ** been exhausted, this function also launches a new background thread ** to populate the new aFile[1]. @@ -2086,7 +2086,7 @@ static void vdbeMergeEngineCompare( #define INCRINIT_TASK 1 #define INCRINIT_ROOT 2 -/* +/* ** Forward reference required as the vdbeIncrMergeInit() and ** vdbePmaReaderIncrInit() routines are called mutually recursively when ** building a merge tree. @@ -2095,7 +2095,7 @@ static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode); /* ** Initialize the MergeEngine object passed as the second argument. Once this -** function returns, the first key of merged data may be read from the +** function returns, the first key of merged data may be read from the ** MergeEngine object in the usual fashion. ** ** If argument eMode is INCRINIT_ROOT, then it is assumed that any IncrMerge @@ -2105,8 +2105,8 @@ static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode); ** required is to call vdbePmaReaderNext() on each PmaReader to point it at ** its first key. ** -** Otherwise, if eMode is any value other than INCRINIT_ROOT, then use -** vdbePmaReaderIncrMergeInit() to initialize each PmaReader that feeds data +** Otherwise, if eMode is any value other than INCRINIT_ROOT, then use +** vdbePmaReaderIncrMergeInit() to initialize each PmaReader that feeds data ** to pMerger. ** ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. @@ -2161,19 +2161,19 @@ static int vdbeMergeEngineInit( ** object at (pReadr->pIncr). ** ** If argument eMode is set to INCRINIT_NORMAL, then all PmaReaders -** in the sub-tree headed by pReadr are also initialized. Data is then -** loaded into the buffers belonging to pReadr and it is set to point to +** in the sub-tree headed by pReadr are also initialized. Data is then +** loaded into the buffers belonging to pReadr and it is set to point to ** the first key in its range. ** ** If argument eMode is set to INCRINIT_TASK, then pReadr is guaranteed ** to be a multi-threaded PmaReader and this function is being called in a -** background thread. In this case all PmaReaders in the sub-tree are +** background thread. In this case all PmaReaders in the sub-tree are ** initialized as for INCRINIT_NORMAL and the aFile[1] buffer belonging to ** pReadr is populated. However, pReadr itself is not set up to point ** to its first key. A call to vdbePmaReaderNext() is still required to do -** that. +** that. ** -** The reason this function does not call vdbePmaReaderNext() immediately +** The reason this function does not call vdbePmaReaderNext() immediately ** in the INCRINIT_TASK case is that vdbePmaReaderNext() assumes that it has ** to block on thread (pTask->thread) before accessing aFile[1]. But, since ** this entire function is being run by thread (pTask->thread), that will @@ -2198,7 +2198,7 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ rc = vdbeMergeEngineInit(pTask, pIncr->pMerger, eMode); - /* Set up the required files for pIncr. A multi-theaded IncrMerge object + /* Set up the required files for pIncr. A multi-threaded IncrMerge object ** requires two temp files to itself, whereas a single-threaded object ** only requires a region of pTask->file2. */ if( rc==SQLITE_OK ){ @@ -2229,12 +2229,12 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ if( rc==SQLITE_OK && pIncr->bUseThread ){ /* Use the current thread to populate aFile[1], even though this ** PmaReader is multi-threaded. If this is an INCRINIT_TASK object, - ** then this function is already running in background thread - ** pIncr->pTask->thread. + ** then this function is already running in background thread + ** pIncr->pTask->thread. ** - ** If this is the INCRINIT_ROOT object, then it is running in the + ** If this is the INCRINIT_ROOT object, then it is running in the ** main VDBE thread. But that is Ok, as that thread cannot return - ** control to the VDBE or proceed with anything useful until the + ** control to the VDBE or proceed with anything useful until the ** first results are ready from this merger object anyway. */ assert( eMode==INCRINIT_ROOT || eMode==INCRINIT_TASK ); @@ -2251,7 +2251,7 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ #if SQLITE_MAX_WORKER_THREADS>0 /* -** The main routine for vdbePmaReaderIncrMergeInit() operations run in +** The main routine for vdbePmaReaderIncrMergeInit() operations run in ** background threads. */ static void *vdbePmaReaderBgIncrInit(void *pCtx){ @@ -2269,8 +2269,8 @@ static void *vdbePmaReaderBgIncrInit(void *pCtx){ ** (if pReadr->pIncr==0), then this function is a no-op. Otherwise, it invokes ** the vdbePmaReaderIncrMergeInit() function with the parameters passed to ** this routine to initialize the incremental merge. -** -** If the IncrMerger object is multi-threaded (IncrMerger.bUseThread==1), +** +** If the IncrMerger object is multi-threaded (IncrMerger.bUseThread==1), ** then a background thread is launched to call vdbePmaReaderIncrMergeInit(). ** Or, if the IncrMerger is single threaded, the same function is called ** using the current thread. @@ -2300,7 +2300,7 @@ static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode){ ** to NULL and return an SQLite error code. ** ** When this function is called, *piOffset is set to the offset of the -** first PMA to read from pTask->file. Assuming no error occurs, it is +** first PMA to read from pTask->file. Assuming no error occurs, it is ** set to the offset immediately following the last byte of the last ** PMA before returning. If an error does occur, then the final value of ** *piOffset is undefined. @@ -2410,12 +2410,12 @@ static int vdbeSorterAddToTree( /* ** This function is called as part of a SorterRewind() operation on a sorter ** that has already written two or more level-0 PMAs to one or more temp -** files. It builds a tree of MergeEngine/IncrMerger/PmaReader objects that +** files. It builds a tree of MergeEngine/IncrMerger/PmaReader objects that ** can be used to incrementally merge all PMAs on disk. ** ** If successful, SQLITE_OK is returned and *ppOut set to point to the ** MergeEngine object at the root of the tree before returning. Or, if an -** error occurs, an SQLite error code is returned and the final value +** error occurs, an SQLite error code is returned and the final value ** of *ppOut is undefined. */ static int vdbeSorterMergeTreeBuild( @@ -2427,8 +2427,8 @@ static int vdbeSorterMergeTreeBuild( int iTask; #if SQLITE_MAX_WORKER_THREADS>0 - /* If the sorter uses more than one task, then create the top-level - ** MergeEngine here. This MergeEngine will read data from exactly + /* If the sorter uses more than one task, then create the top-level + ** MergeEngine here. This MergeEngine will read data from exactly ** one PmaReader per sub-task. */ assert( pSorter->bUseThreads || pSorter->nTask==1 ); if( pSorter->nTask>1 ){ @@ -2537,7 +2537,7 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){ } for(iTask=0; rc==SQLITE_OK && iTasknTask; iTask++){ /* Check that: - ** + ** ** a) The incremental merge object is configured to use the ** right task, and ** b) If it is using task (nTask-1), it is configured to run @@ -2600,7 +2600,7 @@ int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ return rc; } - /* Write the current in-memory list to a PMA. When the VdbeSorterWrite() + /* Write the current in-memory list to a PMA. When the VdbeSorterWrite() ** function flushes the contents of memory to disk, it immediately always ** creates a new list consisting of a single key immediately afterwards. ** So the list is never empty at this point. */ @@ -2612,7 +2612,7 @@ int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ vdbeSorterRewindDebug("rewind"); - /* Assuming no errors have occurred, set up a merger structure to + /* Assuming no errors have occurred, set up a merger structure to ** incrementally read and merge all remaining PMAs. */ assert( pSorter->pReader==0 ); if( rc==SQLITE_OK ){ @@ -2666,7 +2666,7 @@ int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr){ } /* -** Return a pointer to a buffer owned by the sorter that contains the +** Return a pointer to a buffer owned by the sorter that contains the ** current key. */ static void *vdbeSorterRowkey( diff --git a/src/vdbevtab.c b/src/vdbevtab.c index 6557d8cb0..59030e0e1 100644 --- a/src/vdbevtab.c +++ b/src/vdbevtab.c @@ -69,6 +69,8 @@ static int bytecodevtabConnect( "p5 INT," "comment TEXT," "subprog TEXT," + "nexec INT," + "ncycle INT," "stmt HIDDEN" ");", @@ -231,7 +233,7 @@ static int bytecodevtabColumn( } } } - i += 10; + i += 20; } } switch( i ){ @@ -281,16 +283,31 @@ static int bytecodevtabColumn( } break; } - case 10: /* tables_used.type */ + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + case 9: /* nexec */ + sqlite3_result_int(ctx, pOp->nExec); + break; + case 10: /* ncycle */ + sqlite3_result_int(ctx, pOp->nCycle); + break; +#else + case 9: /* nexec */ + case 10: /* ncycle */ + sqlite3_result_int(ctx, 0); + break; +#endif + + case 20: /* tables_used.type */ sqlite3_result_text(ctx, pCur->zType, -1, SQLITE_STATIC); break; - case 11: /* tables_used.schema */ + case 21: /* tables_used.schema */ sqlite3_result_text(ctx, pCur->zSchema, -1, SQLITE_STATIC); break; - case 12: /* tables_used.name */ + case 22: /* tables_used.name */ sqlite3_result_text(ctx, pCur->zName, -1, SQLITE_STATIC); break; - case 13: /* tables_used.wr */ + case 23: /* tables_used.wr */ sqlite3_result_int(ctx, pOp->opcode==OP_OpenWrite); break; } @@ -364,7 +381,7 @@ static int bytecodevtabBestIndex( int rc = SQLITE_CONSTRAINT; struct sqlite3_index_constraint *p; bytecodevtab *pVTab = (bytecodevtab*)tab; - int iBaseCol = pVTab->bTablesUsed ? 4 : 8; + int iBaseCol = pVTab->bTablesUsed ? 4 : 10; pIdxInfo->estimatedCost = (double)100; pIdxInfo->estimatedRows = 100; pIdxInfo->idxNum = 0; diff --git a/src/vtab.c b/src/vtab.c index ad629bb03..741518991 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -17,7 +17,7 @@ /* ** Before a virtual table xCreate() or xConnect() method is invoked, the ** sqlite3.pVtabCtx member variable is set to point to an instance of -** this struct allocated on the stack. It is used by the implementation of +** this struct allocated on the stack. It is used by the implementation of ** the sqlite3_declare_vtab() and sqlite3_vtab_config() APIs, both of which ** are invoked only from within xCreate and xConnect methods. */ @@ -174,7 +174,7 @@ void sqlite3VtabModuleUnref(sqlite3 *db, Module *pMod){ /* ** Lock the virtual table so that it cannot be disconnected. ** Locks nest. Every lock should have a corresponding unlock. -** If an unlock is omitted, resources leaks will occur. +** If an unlock is omitted, resources leaks will occur. ** ** If a disconnect is attempted while a virtual table is locked, ** the disconnect is deferred until all locks have been removed. @@ -186,7 +186,7 @@ void sqlite3VtabLock(VTable *pVTab){ /* ** pTab is a pointer to a Table structure representing a virtual-table. -** Return a pointer to the VTable object used by connection db to access +** Return a pointer to the VTable object used by connection db to access ** this virtual-table, if one has been created, or NULL otherwise. */ VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){ @@ -222,7 +222,7 @@ void sqlite3VtabUnlock(VTable *pVTab){ /* ** Table p is a virtual table. This function moves all elements in the ** p->u.vtab.p list to the sqlite3.pDisconnect lists of their associated -** database connections to be disconnected at the next opportunity. +** database connections to be disconnected at the next opportunity. ** Except, if argument db is not NULL, then the entry associated with ** connection db is left in the p->u.vtab.p list. */ @@ -234,8 +234,8 @@ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ pVTable = p->u.vtab.p; p->u.vtab.p = 0; - /* Assert that the mutex (if any) associated with the BtShared database - ** that contains table p is held by the caller. See header comments + /* Assert that the mutex (if any) associated with the BtShared database + ** that contains table p is held by the caller. See header comments ** above function sqlite3VtabUnlockList() for an explanation of why ** this makes it safe to access the sqlite3.pDisconnect list of any ** database connection that may have an entry in the p->u.vtab.p list. @@ -291,7 +291,7 @@ void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ ** Disconnect all the virtual table objects in the sqlite3.pDisconnect list. ** ** This function may only be called when the mutexes associated with all -** shared b-tree databases opened using connection db are held by the +** shared b-tree databases opened using connection db are held by the ** caller. This is done to protect the sqlite3.pDisconnect list. The ** sqlite3.pDisconnect list is accessed only as follows: ** @@ -304,7 +304,7 @@ void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ ** or, if the virtual table is stored in a non-sharable database, then ** the database handle mutex is held. ** -** As a result, a sqlite3.pDisconnect cannot be accessed simultaneously +** As a result, a sqlite3.pDisconnect cannot be accessed simultaneously ** by multiple threads. It is thread-safe. */ void sqlite3VtabUnlockList(sqlite3 *db){ @@ -330,12 +330,12 @@ void sqlite3VtabUnlockList(sqlite3 *db){ ** record. ** ** Since it is a virtual-table, the Table structure contains a pointer -** to the head of a linked list of VTable structures. Each VTable +** to the head of a linked list of VTable structures. Each VTable ** structure is associated with a single sqlite3* user of the schema. -** The reference count of the VTable structure associated with database -** connection db is decremented immediately (which may lead to the +** The reference count of the VTable structure associated with database +** connection db is decremented immediately (which may lead to the ** structure being xDisconnected and free). Any other VTable structures -** in the list are moved to the sqlite3.pDisconnect list of the associated +** in the list are moved to the sqlite3.pDisconnect list of the associated ** database connection. */ void sqlite3VtabClear(sqlite3 *db, Table *p){ @@ -421,7 +421,7 @@ void sqlite3VtabBeginParse( if( pTable->u.vtab.azArg ){ int iDb = sqlite3SchemaToIndex(db, pTable->pSchema); assert( iDb>=0 ); /* The database the table is being created in */ - sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, + sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, pTable->u.vtab.azArg[0], pParse->db->aDb[iDb].zDbSName); } #endif @@ -454,7 +454,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ addArgumentToVtab(pParse); pParse->sArg.z = 0; if( pTab->u.vtab.nArg<1 ) return; - + /* If the CREATE VIRTUAL TABLE statement is being entered for the ** first time (in other words if the virtual table is actually being ** created now instead of just being read out of sqlite_schema) then @@ -476,12 +476,12 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ } zStmt = sqlite3MPrintf(db, "CREATE VIRTUAL TABLE %T", &pParse->sNameToken); - /* A slot for the record has already been allocated in the + /* A slot for the record has already been allocated in the ** schema table. We just need to update that slot with all - ** the information we've collected. + ** the information we've collected. ** ** The VM register number pParse->regRowid holds the rowid of an - ** entry in the sqlite_schema table tht was created for this vtab + ** entry in the sqlite_schema table that was created for this vtab ** by sqlite3StartTable(). */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); @@ -555,7 +555,7 @@ void sqlite3VtabArgExtend(Parse *pParse, Token *p){ ** to this procedure. */ static int vtabCallConstructor( - sqlite3 *db, + sqlite3 *db, Table *pTab, Module *pMod, int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**), @@ -577,7 +577,7 @@ static int vtabCallConstructor( /* Check that the virtual-table is not already being initialized */ for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){ if( pCtx->pTab==pTab ){ - *pzErr = sqlite3MPrintf(db, + *pzErr = sqlite3MPrintf(db, "vtable constructor called recursively: %s", pTab->zName ); return SQLITE_LOCKED; @@ -641,7 +641,7 @@ static int vtabCallConstructor( int iCol; u16 oooHidden = 0; /* If everything went according to plan, link the new VTable structure - ** into the linked list headed by pTab->u.vtab.p. Then loop through the + ** into the linked list headed by pTab->u.vtab.p. Then loop through the ** columns of the table to see if any of them contain the token "hidden". ** If so, set the Column COLFLAG_HIDDEN flag and remove the token from ** the type string. */ @@ -687,7 +687,7 @@ static int vtabCallConstructor( /* ** This function is invoked by the parser to call the xConnect() method -** of the virtual table pTab. If an error occurs, an error code is returned +** of the virtual table pTab. If an error occurs, an error code is returned ** and an error left in pParse. ** ** This call is a no-op if table pTab is not a virtual table. @@ -759,7 +759,7 @@ static void addToVTrans(sqlite3 *db, VTable *pVTab){ /* ** This function is invoked by the vdbe to call the xCreate method -** of the virtual table named zTab in database iDb. +** of the virtual table named zTab in database iDb. ** ** If an error occurs, *pzErr is set to point to an English language ** description of the error and an SQLITE_XXX error code is returned. @@ -778,8 +778,8 @@ int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){ zMod = pTab->u.vtab.azArg[0]; pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); - /* If the module has been registered and includes a Create method, - ** invoke it now. If the module has not been registered, return an + /* If the module has been registered and includes a Create method, + ** invoke it now. If the module has not been registered, return an ** error. Otherwise, do nothing. */ if( pMod==0 || pMod->pModule->xCreate==0 || pMod->pModule->xDestroy==0 ){ @@ -838,7 +838,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ initBusy = db->init.busy; db->init.busy = 0; sParse.nQueryLoop = 1; - if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) + if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) && ALWAYS(sParse.pNewTable!=0) && ALWAYS(!db->mallocFailed) && IsOrdinaryTable(sParse.pNewTable) @@ -942,7 +942,7 @@ int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){ ** called is identified by the second argument, "offset", which is ** the offset of the method to call in the sqlite3_module structure. ** -** The array is cleared after invoking the callbacks. +** The array is cleared after invoking the callbacks. */ static void callFinaliser(sqlite3 *db, int offset){ int i; @@ -991,7 +991,7 @@ int sqlite3VtabSync(sqlite3 *db, Vdbe *p){ } /* -** Invoke the xRollback method of all virtual tables in the +** Invoke the xRollback method of all virtual tables in the ** sqlite3.aVTrans array. Then clear the array itself. */ int sqlite3VtabRollback(sqlite3 *db){ @@ -1000,7 +1000,7 @@ int sqlite3VtabRollback(sqlite3 *db){ } /* -** Invoke the xCommit method of all virtual tables in the +** Invoke the xCommit method of all virtual tables in the ** sqlite3.aVTrans array. Then clear the array itself. */ int sqlite3VtabCommit(sqlite3 *db){ @@ -1022,7 +1022,7 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ /* Special case: If db->aVTrans is NULL and db->nVTrans is greater ** than zero, then this function is being called from within a - ** virtual module xSync() callback. It is illegal to write to + ** virtual module xSync() callback. It is illegal to write to ** virtual module tables in this case, so return SQLITE_LOCKED. */ if( sqlite3VtabInSync(db) ){ @@ -1030,7 +1030,7 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ } if( !pVTab ){ return SQLITE_OK; - } + } pModule = pVTab->pVtab->pModule; if( pModule->xBegin ){ @@ -1043,7 +1043,7 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ } } - /* Invoke the xBegin method. If successful, add the vtab to the + /* Invoke the xBegin method. If successful, add the vtab to the ** sqlite3.aVTrans[] array. */ rc = growVTrans(db); if( rc==SQLITE_OK ){ @@ -1067,11 +1067,11 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ ** as the second argument to the virtual table method invoked. ** ** If op is SAVEPOINT_BEGIN, the xSavepoint method is invoked. If it is -** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is +** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is ** SAVEPOINT_RELEASE, then the xRelease method of each virtual table with ** an open transaction is invoked. ** -** If any virtual table method returns an error code other than SQLITE_OK, +** If any virtual table method returns an error code other than SQLITE_OK, ** processing is abandoned and the error returned to the caller of this ** function immediately. If all calls to virtual table methods are successful, ** SQLITE_OK is returned. @@ -1123,7 +1123,7 @@ int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){ ** This routine is used to allow virtual table implementations to ** overload MATCH, LIKE, GLOB, and REGEXP operators. ** -** Return either the pDef argument (indicating no change) or a +** Return either the pDef argument (indicating no change) or a ** new FuncDef structure that is marked as ephemeral using the ** SQLITE_FUNC_EPHEM flag. */ @@ -1153,7 +1153,7 @@ FuncDef *sqlite3VtabOverloadFunction( assert( pVtab->pModule!=0 ); pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction==0 ) return pDef; - + /* Call the xFindFunction method on the virtual table implementation ** to see if the implementation wants to overload this function. ** @@ -1219,13 +1219,13 @@ void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ /* ** Check to see if virtual table module pMod can be have an eponymous ** virtual table instance. If it can, create one if one does not already -** exist. Return non-zero if either the eponymous virtual table instance +** exist. Return non-zero if either the eponymous virtual table instance ** exists when this routine returns or if an attempt to create it failed ** and an error message was left in pParse. ** ** An eponymous virtual table instance is one that is named after its ** module, and more importantly, does not require a CREATE VIRTUAL TABLE -** statement in order to come into existance. Eponymous virtual table +** statement in order to come into existence. Eponymous virtual table ** instances always exist. They cannot be DROP-ed. ** ** Any virtual table module for which xConnect and xCreate are the same @@ -1273,7 +1273,7 @@ void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){ Table *pTab = pMod->pEpoTab; if( pTab!=0 ){ /* Mark the table as Ephemeral prior to deleting it, so that the - ** sqlite3DeleteTable() routine will know that it is not stored in + ** sqlite3DeleteTable() routine will know that it is not stored in ** the schema. */ pTab->tabFlags |= TF_Ephemeral; sqlite3DeleteTable(db, pTab); @@ -1289,8 +1289,8 @@ void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){ ** within an xUpdate method. */ int sqlite3_vtab_on_conflict(sqlite3 *db){ - static const unsigned char aMap[] = { - SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE + static const unsigned char aMap[] = { + SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE }; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; @@ -1302,7 +1302,7 @@ int sqlite3_vtab_on_conflict(sqlite3 *db){ } /* -** Call from within the xCreate() or xConnect() methods to provide +** Call from within the xCreate() or xConnect() methods to provide ** the SQLite core with additional information about the behavior ** of the virtual table being implemented. */ diff --git a/src/wal.c b/src/wal.c index 46d284c1b..d63c13ffc 100644 --- a/src/wal.c +++ b/src/wal.c @@ -10,7 +10,7 @@ ** ************************************************************************* ** -** This file contains the implementation of a write-ahead log (WAL) used in +** This file contains the implementation of a write-ahead log (WAL) used in ** "journal_mode=WAL" mode. ** ** WRITE-AHEAD LOG (WAL) FILE FORMAT @@ -19,7 +19,7 @@ ** Each frame records the revised content of a single page from the ** database file. All changes to the database are recorded by writing ** frames into the WAL. Transactions commit when a frame is written that -** contains a commit marker. A single WAL can and usually does record +** contains a commit marker. A single WAL can and usually does record ** multiple transactions. Periodically, the content of the WAL is ** transferred back into the database file in an operation called a ** "checkpoint". @@ -45,11 +45,11 @@ ** ** Immediately following the wal-header are zero or more frames. Each ** frame consists of a 24-byte frame-header followed by a bytes -** of page data. The frame-header is six big-endian 32-bit unsigned +** of page data. The frame-header is six big-endian 32-bit unsigned ** integer values, as follows: ** ** 0: Page number. -** 4: For commit records, the size of the database image in pages +** 4: For commit records, the size of the database image in pages ** after the commit. For all other records, zero. ** 8: Salt-1 (copied from the header) ** 12: Salt-2 (copied from the header) @@ -75,7 +75,7 @@ ** the checksum. The checksum is computed by interpreting the input as ** an even number of unsigned 32-bit integers: x[0] through x[N]. The ** algorithm used for the checksum is as follows: -** +** ** for i from 0 to n-1 step 2: ** s0 += x[i] + s1; ** s1 += x[i+1] + s0; @@ -83,7 +83,7 @@ ** ** Note that s0 and s1 are both weighted checksums using fibonacci weights ** in reverse order (the largest fibonacci weight occurs on the first element -** of the sequence being summed.) The s1 value spans all 32-bit +** of the sequence being summed.) The s1 value spans all 32-bit ** terms of the sequence whereas s0 omits the final term. ** ** On a checkpoint, the WAL is first VFS.xSync-ed, then valid content of the @@ -116,19 +116,19 @@ ** multiple concurrent readers to view different versions of the database ** content simultaneously. ** -** The reader algorithm in the previous paragraphs works correctly, but +** The reader algorithm in the previous paragraphs works correctly, but ** because frames for page P can appear anywhere within the WAL, the ** reader has to scan the entire WAL looking for page P frames. If the ** WAL is large (multiple megabytes is typical) that scan can be slow, ** and read performance suffers. To overcome this problem, a separate ** data structure called the wal-index is maintained to expedite the ** search for frames of a particular page. -** +** ** WAL-INDEX FORMAT ** ** Conceptually, the wal-index is shared memory, though VFS implementations ** might choose to implement the wal-index using a mmapped file. Because -** the wal-index is shared memory, SQLite does not support journal_mode=WAL +** the wal-index is shared memory, SQLite does not support journal_mode=WAL ** on a network filesystem. All users of the database must be able to ** share memory. ** @@ -146,19 +146,19 @@ ** byte order of the host computer. ** ** The purpose of the wal-index is to answer this question quickly: Given -** a page number P and a maximum frame index M, return the index of the +** a page number P and a maximum frame index M, return the index of the ** last frame in the wal before frame M for page P in the WAL, or return ** NULL if there are no frames for page P in the WAL prior to M. ** ** The wal-index consists of a header region, followed by an one or -** more index blocks. +** more index blocks. ** ** The wal-index header contains the total number of frames within the WAL ** in the mxFrame field. ** -** Each index block except for the first contains information on +** Each index block except for the first contains information on ** HASHTABLE_NPAGE frames. The first index block contains information on -** HASHTABLE_NPAGE_ONE frames. The values of HASHTABLE_NPAGE_ONE and +** HASHTABLE_NPAGE_ONE frames. The values of HASHTABLE_NPAGE_ONE and ** HASHTABLE_NPAGE are selected so that together the wal-index header and ** first index block are the same size as all other index blocks in the ** wal-index. The values are: @@ -167,10 +167,10 @@ ** HASHTABLE_NPAGE_ONE 4062 ** ** Each index block contains two sections, a page-mapping that contains the -** database page number associated with each wal frame, and a hash-table +** database page number associated with each wal frame, and a hash-table ** that allows readers to query an index block for a specific page number. ** The page-mapping is an array of HASHTABLE_NPAGE (or HASHTABLE_NPAGE_ONE -** for the first index block) 32-bit page numbers. The first entry in the +** for the first index block) 32-bit page numbers. The first entry in the ** first index-block contains the database page number corresponding to the ** first frame in the WAL file. The first entry in the second index block ** in the WAL file corresponds to the (HASHTABLE_NPAGE_ONE+1)th frame in @@ -191,8 +191,8 @@ ** ** The hash table consists of HASHTABLE_NSLOT 16-bit unsigned integers. ** HASHTABLE_NSLOT = 2*HASHTABLE_NPAGE, and there is one entry in the -** hash table for each page number in the mapping section, so the hash -** table is never more than half full. The expected number of collisions +** hash table for each page number in the mapping section, so the hash +** table is never more than half full. The expected number of collisions ** prior to finding a match is 1. Each entry of the hash table is an ** 1-based index of an entry in the mapping section of the same ** index block. Let K be the 1-based index of the largest entry in @@ -211,12 +211,12 @@ ** reached) until an unused hash slot is found. Let the first unused slot ** be at index iUnused. (iUnused might be less than iKey if there was ** wrap-around.) Because the hash table is never more than half full, -** the search is guaranteed to eventually hit an unused entry. Let +** the search is guaranteed to eventually hit an unused entry. Let ** iMax be the value between iKey and iUnused, closest to iUnused, ** where aHash[iMax]==P. If there is no iMax entry (if there exists ** no hash slot such that aHash[i]==p) then page P is not in the ** current index block. Otherwise the iMax-th mapping entry of the -** current index block corresponds to the last entry that references +** current index block corresponds to the last entry that references ** page P. ** ** A hash search begins with the last index block and moves toward the @@ -241,7 +241,7 @@ ** if no values greater than K0 had ever been inserted into the hash table ** in the first place - which is what reader one wants. Meanwhile, the ** second reader using K1 will see additional values that were inserted -** later, which is exactly what reader two wants. +** later, which is exactly what reader two wants. ** ** When a rollback occurs, the value of K is decreased. Hash table entries ** that correspond to frames greater than the new K value are removed @@ -269,7 +269,7 @@ int sqlite3WalTrace = 0; ** values in the wal-header are correct and (b) the version field is not ** WAL_MAX_VERSION, recovery fails and SQLite returns SQLITE_CANTOPEN. ** -** Similarly, if a client successfully reads a wal-index header (i.e. the +** Similarly, if a client successfully reads a wal-index header (i.e. the ** checksum test is successful) and finds that the version field is not ** WALINDEX_MAX_VERSION, then no read-transaction is opened and SQLite ** returns SQLITE_CANTOPEN. @@ -284,7 +284,7 @@ int sqlite3WalTrace = 0; ** ** Technically, the various VFSes are free to implement these locks however ** they see fit. However, compatibility is encouraged so that VFSes can -** interoperate. The standard implemention used on both unix and windows +** interoperate. The standard implementation used on both unix and windows ** is for the index number to indicate a byte offset into the ** WalCkptInfo.aLock[] array in the wal-index header. In other words, all ** locks are on the shm file. The WALINDEX_LOCK_OFFSET constant (which @@ -316,7 +316,7 @@ typedef struct WalCkptInfo WalCkptInfo; ** ** The szPage value can be any power of 2 between 512 and 32768, inclusive. ** Or it can be 1 to represent a 65536-byte page. The latter case was -** added in 3.7.1 when support for 64K pages was added. +** added in 3.7.1 when support for 64K pages was added. */ struct WalIndexHdr { u32 iVersion; /* Wal-index version */ @@ -358,9 +358,9 @@ struct WalIndexHdr { ** There is one entry in aReadMark[] for each reader lock. If a reader ** holds read-lock K, then the value in aReadMark[K] is no greater than ** the mxFrame for that reader. The value READMARK_NOT_USED (0xffffffff) -** for any aReadMark[] means that entry is unused. aReadMark[0] is +** for any aReadMark[] means that entry is unused. aReadMark[0] is ** a special case; its value is never used and it exists as a place-holder -** to avoid having to offset aReadMark[] indexs by one. Readers holding +** to avoid having to offset aReadMark[] indexes by one. Readers holding ** WAL_READ_LOCK(0) always ignore the entire WAL and read all content ** directly from the database. ** @@ -378,7 +378,7 @@ struct WalIndexHdr { ** previous sentence is when nBackfill equals mxFrame (meaning that everything ** in the WAL has been backfilled into the database) then new readers ** will choose aReadMark[0] which has value 0 and hence such reader will -** get all their all content directly from the database file and ignore +** get all their all content directly from the database file and ignore ** the WAL. ** ** Writers normally append new frames to the end of the WAL. However, @@ -484,14 +484,14 @@ struct WalCkptInfo { ** big-endian format in the first 4 bytes of a WAL file. ** ** If the LSB is set, then the checksums for each frame within the WAL -** file are calculated by treating all data as an array of 32-bit -** big-endian words. Otherwise, they are calculated by interpreting +** file are calculated by treating all data as an array of 32-bit +** big-endian words. Otherwise, they are calculated by interpreting ** all data as 32-bit little-endian words. */ #define WAL_MAGIC 0x377f0682 /* -** Return the offset of frame iFrame in the write-ahead log file, +** Return the offset of frame iFrame in the write-ahead log file, ** assuming a database page size of szPage bytes. The offset returned ** is to the start of the write-ahead log frame-header. */ @@ -528,7 +528,15 @@ struct Wal { u32 iReCksum; /* On commit, recalculate checksums from here */ const char *zWalName; /* Name of WAL file */ u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ +#ifdef SQLITE_USE_SEH + u32 lockMask; /* Mask of locks held */ + void *pFree; /* Pointer to sqlite3_free() if exception thrown */ + u32 *pWiValue; /* Value to write into apWiData[iWiPg] */ + int iWiPg; /* Write pWiValue into apWiData[iWiPg] */ + int iSysErrno; /* System error code following exception */ +#endif #ifdef SQLITE_DEBUG + int nSehTry; /* Number of nested SEH_TRY{} blocks */ u8 lockError; /* True if a locking error has occurred */ #endif #ifdef SQLITE_ENABLE_SNAPSHOT @@ -543,7 +551,7 @@ struct Wal { ** Candidate values for Wal.exclusiveMode. */ #define WAL_NORMAL_MODE 0 -#define WAL_EXCLUSIVE_MODE 1 +#define WAL_EXCLUSIVE_MODE 1 #define WAL_HEAPMEMORY_MODE 2 /* @@ -562,7 +570,7 @@ typedef u16 ht_slot; /* ** This structure is used to implement an iterator that loops through ** all frames in the WAL in database page order. Where two or more frames -** correspond to the same database page, the iterator visits only the +** correspond to the same database page, the iterator visits only the ** frame most recently written to the WAL (in other words, the frame with ** the largest index). ** @@ -598,7 +606,7 @@ struct WalIterator { #define HASHTABLE_HASH_1 383 /* Should be prime */ #define HASHTABLE_NSLOT (HASHTABLE_NPAGE*2) /* Must be a power of 2 */ -/* +/* ** The block of page numbers associated with the first hash-table in a ** wal-index is smaller than usual. This is so that there is a complete ** hash-table on each aligned 32KB page of the wal-index. @@ -610,6 +618,113 @@ struct WalIterator { sizeof(ht_slot)*HASHTABLE_NSLOT + HASHTABLE_NPAGE*sizeof(u32) \ ) +/* +** Structured Exception Handling (SEH) is a Windows-specific technique +** for catching exceptions raised while accessing memory-mapped files. +** +** The -DSQLITE_USE_SEH compile-time option means to use SEH to catch and +** deal with system-level errors that arise during WAL -shm file processing. +** Without this compile-time option, any system-level faults that appear +** while accessing the memory-mapped -shm file will cause a process-wide +** signal to be deliver, which will more than likely cause the entire +** process to exit. +*/ +#ifdef SQLITE_USE_SEH +#include + +/* Beginning of a block of code in which an exception might occur */ +# define SEH_TRY __try { \ + assert( walAssertLockmask(pWal) && pWal->nSehTry==0 ); \ + VVA_ONLY(pWal->nSehTry++); + +/* The end of a block of code in which an exception might occur */ +# define SEH_EXCEPT(X) \ + VVA_ONLY(pWal->nSehTry--); \ + assert( pWal->nSehTry==0 ); \ + } __except( sehExceptionFilter(pWal, GetExceptionCode(), GetExceptionInformation() ) ){ X } + +/* Simulate a memory-mapping fault in the -shm file for testing purposes */ +# define SEH_INJECT_FAULT sehInjectFault(pWal) + +/* +** The second argument is the return value of GetExceptionCode() for the +** current exception. Return EXCEPTION_EXECUTE_HANDLER if the exception code +** indicates that the exception may have been caused by accessing the *-shm +** file mapping. Or EXCEPTION_CONTINUE_SEARCH otherwise. +*/ +static int sehExceptionFilter(Wal *pWal, int eCode, EXCEPTION_POINTERS *p){ + VVA_ONLY(pWal->nSehTry--); + if( eCode==EXCEPTION_IN_PAGE_ERROR ){ + if( p && p->ExceptionRecord && p->ExceptionRecord->NumberParameters>=3 ){ + /* From MSDN: For this type of exception, the first element of the + ** ExceptionInformation[] array is a read-write flag - 0 if the exception + ** was thrown while reading, 1 if while writing. The second element is + ** the virtual address being accessed. The "third array element specifies + ** the underlying NTSTATUS code that resulted in the exception". */ + pWal->iSysErrno = (int)p->ExceptionRecord->ExceptionInformation[2]; + } + return EXCEPTION_EXECUTE_HANDLER; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +/* +** If one is configured, invoke the xTestCallback callback with 650 as +** the argument. If it returns true, throw the same exception that is +** thrown by the system if the *-shm file mapping is accessed after it +** has been invalidated. +*/ +static void sehInjectFault(Wal *pWal){ + int res; + assert( pWal->nSehTry>0 ); + + res = sqlite3FaultSim(650); + if( res!=0 ){ + ULONG_PTR aArg[3]; + aArg[0] = 0; + aArg[1] = 0; + aArg[2] = (ULONG_PTR)res; + RaiseException(EXCEPTION_IN_PAGE_ERROR, 0, 3, (const ULONG_PTR*)aArg); + } +} + +/* +** There are two ways to use this macro. To set a pointer to be freed +** if an exception is thrown: +** +** SEH_FREE_ON_ERROR(0, pPtr); +** +** and to cancel the same: +** +** SEH_FREE_ON_ERROR(pPtr, 0); +** +** In the first case, there must not already be a pointer registered to +** be freed. In the second case, pPtr must be the registered pointer. +*/ +#define SEH_FREE_ON_ERROR(X,Y) \ + assert( (X==0 || Y==0) && pWal->pFree==X ); pWal->pFree = Y + +/* +** There are two ways to use this macro. To arrange for pWal->apWiData[iPg] +** to be set to pValue if an exception is thrown: +** +** SEH_SET_ON_ERROR(iPg, pValue); +** +** and to cancel the same: +** +** SEH_SET_ON_ERROR(0, 0); +*/ +#define SEH_SET_ON_ERROR(X,Y) pWal->iWiPg = X; pWal->pWiValue = Y + +#else +# define SEH_TRY VVA_ONLY(pWal->nSehTry++); +# define SEH_EXCEPT(X) VVA_ONLY(pWal->nSehTry--); assert( pWal->nSehTry==0 ); +# define SEH_INJECT_FAULT assert( pWal->nSehTry>0 ); +# define SEH_FREE_ON_ERROR(X,Y) +# define SEH_SET_ON_ERROR(X,Y) +#endif /* ifdef SQLITE_USE_SEH */ + + /* ** Obtain a pointer to the iPage'th page of the wal-index. The wal-index ** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are @@ -624,7 +739,7 @@ struct WalIterator { ** ** (1) rc==SQLITE_OK and *ppPage==Requested-Wal-Index-Page ** (2) rc>=SQLITE_ERROR and *ppPage==NULL -** (3) rc==SQLITE_OK and *ppPage==NULL // only if iPage==0 +** (3) rc==SQLITE_OK and *ppPage==NULL // only if iPage==0 ** ** Scenario (3) can only occur when pWal->writeLock is false and iPage==0 */ @@ -656,7 +771,7 @@ static SQLITE_NOINLINE int walIndexPageRealloc( pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ); if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT; }else{ - rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, + rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] ); assert( pWal->apWiData[iPage]!=0 @@ -682,6 +797,7 @@ static int walIndexPage( int iPage, /* The page we seek */ volatile u32 **ppPage /* Write the page pointer here */ ){ + SEH_INJECT_FAULT; if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){ return walIndexPageRealloc(pWal, iPage, ppPage); } @@ -693,6 +809,7 @@ static int walIndexPage( */ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalCkptInfo*)&(pWal->apWiData[0][sizeof(WalIndexHdr)/2]); } @@ -701,6 +818,7 @@ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ */ static volatile WalIndexHdr *walIndexHdr(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalIndexHdr*)pWal->apWiData[0]; } @@ -717,7 +835,7 @@ static volatile WalIndexHdr *walIndexHdr(Wal *pWal){ ) /* -** Generate or extend an 8 byte checksum based on the data in +** Generate or extend an 8 byte checksum based on the data in ** array aByte[] and the initial values of aIn[0] and aIn[1] (or ** initial values of 0 and 0 if aIn==NULL). ** @@ -829,11 +947,11 @@ static SQLITE_NO_TSAN void walIndexWriteHdr(Wal *pWal){ /* ** This function encodes a single frame header and writes it to a buffer -** supplied by the caller. A frame-header is made up of a series of +** supplied by the caller. A frame-header is made up of a series of ** 4-byte big-endian integers, as follows: ** ** 0: Page number. -** 4: For commit records, the size of the database image in pages +** 4: For commit records, the size of the database image in pages ** after the commit. For all other records, zero. ** 8: Salt-1 (copied from the wal-header) ** 12: Salt-2 (copied from the wal-header) @@ -884,13 +1002,13 @@ static int walDecodeFrame( assert( WAL_FRAME_HDRSIZE==24 ); /* A frame is only valid if the salt values in the frame-header - ** match the salt values in the wal-header. + ** match the salt values in the wal-header. */ if( memcmp(&pWal->hdr.aSalt, &aFrame[8], 8)!=0 ){ return 0; } - /* A frame is only valid if the page number is creater than zero. + /* A frame is only valid if the page number is greater than zero. */ pgno = sqlite3Get4byte(&aFrame[0]); if( pgno==0 ){ @@ -898,15 +1016,15 @@ static int walDecodeFrame( } /* A frame is only valid if a checksum of the WAL header, - ** all prior frams, the first 16 bytes of this frame-header, - ** and the frame-data matches the checksum in the last 8 + ** all prior frames, the first 16 bytes of this frame-header, + ** and the frame-data matches the checksum in the last 8 ** bytes of this frame-header. */ nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN); walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum); walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum); - if( aCksum[0]!=sqlite3Get4byte(&aFrame[16]) - || aCksum[1]!=sqlite3Get4byte(&aFrame[20]) + if( aCksum[0]!=sqlite3Get4byte(&aFrame[16]) + || aCksum[1]!=sqlite3Get4byte(&aFrame[20]) ){ /* Checksum failed. */ return 0; @@ -941,7 +1059,7 @@ static const char *walLockName(int lockIdx){ } } #endif /*defined(SQLITE_TEST) || defined(SQLITE_DEBUG) */ - + /* ** Set or release locks on the WAL. Locks are either shared or exclusive. @@ -958,12 +1076,18 @@ static int walLockShared(Wal *pWal, int lockIdx){ WALTRACE(("WAL%p: acquire SHARED-%s %s\n", pWal, walLockName(lockIdx), rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ) pWal->lockMask |= (1 << lockIdx); +#endif return rc; } static void walUnlockShared(Wal *pWal, int lockIdx){ if( pWal->exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1, SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(1 << lockIdx); +#endif WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx))); } static int walLockExclusive(Wal *pWal, int lockIdx, int n){ @@ -974,12 +1098,20 @@ static int walLockExclusive(Wal *pWal, int lockIdx, int n){ WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, walLockName(lockIdx), n, rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ){ + pWal->lockMask |= (((1<exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(((1<aHash to point to the start of the hash table -** in the wal-index file. Set pLoc->iZero to one less than the frame +** in the wal-index file. Set pLoc->iZero to one less than the frame ** number of the first frame indexed by this hash table. If a -** slot in the hash table is set to N, it refers to frame number +** slot in the hash table is set to N, it refers to frame number ** (pLoc->iZero+N) in the log. ** ** Finally, set pLoc->aPgno so that pLoc->aPgno[0] is the page number of the @@ -1051,7 +1183,7 @@ static int walHashGet( /* ** Return the number of the wal-index page that contains the hash-table ** and page-number array that contain entries corresponding to WAL frame -** iFrame. The wal-index is broken up into 32KB pages. Wal-index pages +** iFrame. The wal-index is broken up into 32KB pages. Wal-index pages ** are numbered starting from 0. */ static int walFramePage(u32 iFrame){ @@ -1071,6 +1203,7 @@ static int walFramePage(u32 iFrame){ */ static u32 walFramePgno(Wal *pWal, u32 iFrame){ int iHash = walFramePage(iFrame); + SEH_INJECT_FAULT; if( iHash==0 ){ return pWal->apWiData[0][WALINDEX_HDR_SIZE/sizeof(u32) + iFrame - 1]; } @@ -1102,7 +1235,7 @@ static void walCleanupHash(Wal *pWal){ if( pWal->hdr.mxFrame==0 ) return; - /* Obtain pointers to the hash-table and page-number array containing + /* Obtain pointers to the hash-table and page-number array containing ** the entry that corresponds to frame pWal->hdr.mxFrame. It is guaranteed ** that the page said hash-table and array reside on is already mapped.(1) */ @@ -1121,9 +1254,9 @@ static void walCleanupHash(Wal *pWal){ sLoc.aHash[i] = 0; } } - + /* Zero the entries in the aPgno array that correspond to frames with - ** frame numbers greater than pWal->hdr.mxFrame. + ** frame numbers greater than pWal->hdr.mxFrame. */ nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit]); assert( nByte>=0 ); @@ -1167,9 +1300,9 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ idx = iFrame - sLoc.iZero; assert( idx <= HASHTABLE_NSLOT/2 + 1 ); - + /* If this is the first entry to be added to this hash-table, zero the - ** entire hash table and aPgno[] array before proceeding. + ** entire hash table and aPgno[] array before proceeding. */ if( idx==1 ){ int nByte = (int)((u8*)&sLoc.aHash[HASHTABLE_NSLOT] - (u8*)sLoc.aPgno); @@ -1179,8 +1312,8 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ /* If the entry in aPgno[] is already set, then the previous writer ** must have exited unexpectedly in the middle of a transaction (after - ** writing one or more dirty pages to the WAL to free up memory). - ** Remove the remnants of that writers uncommitted transaction from + ** writing one or more dirty pages to the WAL to free up memory). + ** Remove the remnants of that writers uncommitted transaction from ** the hash-table before writing any new entries. */ if( sLoc.aPgno[idx-1] ){ @@ -1231,7 +1364,7 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ /* -** Recover the wal-index by reading the write-ahead log file. +** Recover the wal-index by reading the write-ahead log file. ** ** This routine first tries to establish an exclusive lock on the ** wal-index to prevent other threads/processes from doing anything @@ -1291,16 +1424,16 @@ static int walIndexRecover(Wal *pWal){ } /* If the database page size is not a power of two, or is greater than - ** SQLITE_MAX_PAGE_SIZE, conclude that the WAL file contains no valid + ** SQLITE_MAX_PAGE_SIZE, conclude that the WAL file contains no valid ** data. Similarly, if the 'magic' value is invalid, ignore the whole ** WAL file. */ magic = sqlite3Get4byte(&aBuf[0]); szPage = sqlite3Get4byte(&aBuf[8]); - if( (magic&0xFFFFFFFE)!=WAL_MAGIC - || szPage&(szPage-1) - || szPage>SQLITE_MAX_PAGE_SIZE - || szPage<512 + if( (magic&0xFFFFFFFE)!=WAL_MAGIC + || szPage&(szPage-1) + || szPage>SQLITE_MAX_PAGE_SIZE + || szPage<512 ){ goto finished; } @@ -1310,7 +1443,7 @@ static int walIndexRecover(Wal *pWal){ memcpy(&pWal->hdr.aSalt, &aBuf[16], 8); /* Verify that the WAL header checksum is correct */ - walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN, + walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN, aBuf, WAL_HDRSIZE-2*4, 0, pWal->hdr.aFrameCksum ); if( pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[24]) @@ -1330,6 +1463,7 @@ static int walIndexRecover(Wal *pWal){ /* Malloc a buffer to read frames into. */ szFrame = szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame + WALINDEX_PGSZ); + SEH_FREE_ON_ERROR(0, aFrame); if( !aFrame ){ rc = SQLITE_NOMEM_BKPT; goto recovery_error; @@ -1348,8 +1482,9 @@ static int walIndexRecover(Wal *pWal){ rc = walIndexPage(pWal, iPg, (volatile u32**)&aShare); assert( aShare!=0 || rc!=SQLITE_OK ); if( aShare==0 ) break; + SEH_SET_ON_ERROR(iPg, aShare); pWal->apWiData[iPg] = aPrivate; - + for(iFrame=iFirst; iFrame<=iLast; iFrame++){ i64 iOffset = walFrameOffset(iFrame, szPage); u32 pgno; /* Database page number for frame */ @@ -1375,6 +1510,7 @@ static int walIndexRecover(Wal *pWal){ } } pWal->apWiData[iPg] = aShare; + SEH_SET_ON_ERROR(0,0); nHdr = (iPg==0 ? WALINDEX_HDR_SIZE : 0); nHdr32 = nHdr / sizeof(u32); #ifndef SQLITE_SAFER_WALINDEX_RECOVERY @@ -1405,9 +1541,11 @@ static int walIndexRecover(Wal *pWal){ } } #endif + SEH_INJECT_FAULT; if( iFrame<=iLast ) break; } + SEH_FREE_ON_ERROR(aFrame, 0); sqlite3_free(aFrame); } @@ -1419,8 +1557,8 @@ static int walIndexRecover(Wal *pWal){ pWal->hdr.aFrameCksum[1] = aFrameCksum[1]; walIndexWriteHdr(pWal); - /* Reset the checkpoint-header. This is safe because this thread is - ** currently holding locks that exclude all other writers and + /* Reset the checkpoint-header. This is safe because this thread is + ** currently holding locks that exclude all other writers and ** checkpointers. Then set the values of read-mark slots 1 through N. */ pInfo = walCkptInfo(pWal); @@ -1435,6 +1573,7 @@ static int walIndexRecover(Wal *pWal){ }else{ pInfo->aReadMark[i] = READMARK_NOT_USED; } + SEH_INJECT_FAULT; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); }else if( rc!=SQLITE_BUSY ){ goto recovery_error; @@ -1476,8 +1615,8 @@ static void walIndexClose(Wal *pWal, int isDelete){ } } -/* -** Open a connection to the WAL file zWalName. The database file must +/* +** Open a connection to the WAL file zWalName. The database file must ** already be opened on connection pDbFd. The buffer that zWalName points ** to must remain valid for the lifetime of the returned Wal* handle. ** @@ -1487,7 +1626,7 @@ static void walIndexClose(Wal *pWal, int isDelete){ ** were to do this just after this client opened one of these files, the ** system would be badly broken. ** -** If the log file is successfully opened, SQLITE_OK is returned and +** If the log file is successfully opened, SQLITE_OK is returned and ** *ppWal is set to point to a new WAL handle. If an error occurs, ** an SQLite error code is returned and *ppWal is left unmodified. */ @@ -1592,7 +1731,7 @@ int sqlite3WalOpen( } /* -** Change the size to which the WAL file is trucated on each reset. +** Change the size to which the WAL file is truncated on each reset. */ void sqlite3WalLimit(Wal *pWal, i64 iLimit){ if( pWal ) pWal->mxWalSize = iLimit; @@ -1680,7 +1819,7 @@ static void walMerge( ht_slot logpage; Pgno dbpage; - if( (iLeft=nRight || aContent[aLeft[iLeft]]HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) + ); if( !p ){ return SQLITE_NOMEM_BKPT; } memset(p, 0, nByte); p->nSegment = nSegment; - - /* Allocate temporary space used by the merge-sort routine. This block - ** of memory will be freed before this function returns. - */ - aTmp = (ht_slot *)sqlite3_malloc64( - sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) - ); - if( !aTmp ){ - rc = SQLITE_NOMEM_BKPT; - } - + aTmp = (ht_slot*)&(((u8*)p)[nByte]); + SEH_FREE_ON_ERROR(0, p); for(i=walFramePage(nBackfill+1); rc==SQLITE_OK && iaSegment[p->nSegment])[sLoc.iZero]; sLoc.iZero++; - + for(j=0; jaSegment[i].aPgno = (u32 *)sLoc.aPgno; } } - sqlite3_free(aTmp); - if( rc!=SQLITE_OK ){ + SEH_FREE_ON_ERROR(p, 0); walIteratorFree(p); p = 0; } @@ -1875,8 +2006,8 @@ static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ #ifdef SQLITE_ENABLE_SETLK_TIMEOUT /* ** Attempt to enable blocking locks. Blocking locks are enabled only if (a) -** they are supported by the VFS, and (b) the database handle is configured -** with a busy-timeout. Return 1 if blocking locks are successfully enabled, +** they are supported by the VFS, and (b) the database handle is configured +** with a busy-timeout. Return 1 if blocking locks are successfully enabled, ** or 0 otherwise. */ static int walEnableBlocking(Wal *pWal){ @@ -1905,7 +2036,7 @@ static void walDisableBlocking(Wal *pWal){ /* ** If parameter bLock is true, attempt to enable blocking locks, take ** the WRITER lock, and then disable blocking locks. If blocking locks -** cannot be enabled, no attempt to obtain the WRITER lock is made. Return +** cannot be enabled, no attempt to obtain the WRITER lock is made. Return ** an SQLite error code if an error occurs, or SQLITE_OK otherwise. It is not ** an error if blocking locks can not be enabled. ** @@ -2002,8 +2133,8 @@ static int walPagesize(Wal *pWal){ ** client to write to the database (which may be this one) does so by ** writing frames into the start of the log file. ** -** The value of parameter salt1 is used as the aSalt[1] value in the -** new wal-index header. It should be passed a pseudo-random value (i.e. +** The value of parameter salt1 is used as the aSalt[1] value in the +** new wal-index header. It should be passed a pseudo-random value (i.e. ** one obtained from sqlite3_randomness()). */ static void walRestartHdr(Wal *pWal, u32 salt1){ @@ -2031,8 +2162,8 @@ static void walRestartHdr(Wal *pWal, u32 salt1){ ** that a concurrent reader might be using. ** ** All I/O barrier operations (a.k.a fsyncs) occur in this routine when -** SQLite is in WAL-mode in synchronous=NORMAL. That means that if -** checkpoints are always run by a background thread or background +** SQLite is in WAL-mode in synchronous=NORMAL. That means that if +** checkpoints are always run by a background thread or background ** process, foreground threads will never block on a lengthy fsync call. ** ** Fsync is called on the WAL before writing content out of the WAL and @@ -2045,7 +2176,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){ ** database file. ** ** This routine uses and updates the nBackfill field of the wal-index header. -** This is the only routine that will increase the value of nBackfill. +** This is the only routine that will increase the value of nBackfill. ** (A WAL reset or recovery will revert nBackfill to zero, but not increase ** its value.) ** @@ -2090,13 +2221,13 @@ static int walCheckpoint( mxSafeFrame = pWal->hdr.mxFrame; mxPage = pWal->hdr.nPage; for(i=1; iaReadMark+i); + u32 y = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; if( mxSafeFrame>y ){ assert( y<=pWal->hdr.mxFrame ); rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); if( rc==SQLITE_OK ){ u32 iMark = (i==1 ? mxSafeFrame : READMARK_NOT_USED); - AtomicStore(pInfo->aReadMark+i, iMark); + AtomicStore(pInfo->aReadMark+i, iMark); SEH_INJECT_FAULT; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); }else if( rc==SQLITE_BUSY ){ mxSafeFrame = y; @@ -2117,8 +2248,7 @@ static int walCheckpoint( && (rc = walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK ){ u32 nBackfill = pInfo->nBackfill; - - pInfo->nBackfillAttempted = mxSafeFrame; + pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT; /* Sync the WAL to disk */ rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); @@ -2135,7 +2265,7 @@ static int walCheckpoint( if( (nSize+65536+(i64)pWal->hdr.mxFrame*szPage)u1.isInterrupted) ){ rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; break; @@ -2178,7 +2309,7 @@ static int walCheckpoint( } } if( rc==SQLITE_OK ){ - AtomicStore(&pInfo->nBackfill, mxSafeFrame); + AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT; } } @@ -2194,12 +2325,13 @@ static int walCheckpoint( } /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the - ** entire wal file has been copied into the database file, then block - ** until all readers have finished using the wal file. This ensures that + ** entire wal file has been copied into the database file, then block + ** until all readers have finished using the wal file. This ensures that ** the next process to write to the database restarts the wal file. */ if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){ assert( pWal->writeLock ); + SEH_INJECT_FAULT; if( pInfo->nBackfillhdr.mxFrame ){ rc = SQLITE_BUSY; }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ @@ -2219,7 +2351,7 @@ static int walCheckpoint( ** writer clients should see that the entire log file has been ** checkpointed and behave accordingly. This seems unsafe though, ** as it would leave the system in a state where the contents of - ** the wal-index header do not match the contents of the + ** the wal-index header do not match the contents of the ** file-system. To avoid this, update the wal-index header to ** indicate that the log file contains zero valid frames. */ walRestartHdr(pWal, salt1); @@ -2231,6 +2363,7 @@ static int walCheckpoint( } walcheckpoint_out: + SEH_FREE_ON_ERROR(pIter, 0); walIteratorFree(pIter); return rc; } @@ -2253,6 +2386,93 @@ static void walLimitSize(Wal *pWal, i64 nMax){ } } +#ifdef SQLITE_USE_SEH +/* +** This is the "standard" exception handler used in a few places to handle +** an exception thrown by reading from the *-shm mapping after it has become +** invalid in SQLITE_USE_SEH builds. It is used as follows: +** +** SEH_TRY { ... } +** SEH_EXCEPT( rc = walHandleException(pWal); ) +** +** This function does three things: +** +** 1) Determines the locks that should be held, based on the contents of +** the Wal.readLock, Wal.writeLock and Wal.ckptLock variables. All other +** held locks are assumed to be transient locks that would have been +** released had the exception not been thrown and are dropped. +** +** 2) Frees the pointer at Wal.pFree, if any, using sqlite3_free(). +** +** 3) Set pWal->apWiData[pWal->iWiPg] to pWal->pWiValue if not NULL +** +** 4) Returns SQLITE_IOERR. +*/ +static int walHandleException(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<lockMask & ~( + (pWal->readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) + ); + for(ii=0; iipFree); + pWal->pFree = 0; + if( pWal->pWiValue ){ + pWal->apWiData[pWal->iWiPg] = pWal->pWiValue; + pWal->pWiValue = 0; + } + return SQLITE_IOERR_IN_PAGE; +} + +/* +** Assert that the Wal.lockMask mask, which indicates the locks held +** by the connenction, is consistent with the Wal.readLock, Wal.writeLock +** and Wal.ckptLock variables. To be used as: +** +** assert( walAssertLockmask(pWal) ); +*/ +static int walAssertLockmask(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) +#ifdef SQLITE_ENABLE_SNAPSHOT + | (pWal->pSnapshot ? (pWal->lockMask & (1 << WAL_CKPT_LOCK)) : 0) +#endif + ); + assert( mExpect==pWal->lockMask ); + } + return 1; +} + +/* +** Return and zero the "system error" field set when an +** EXCEPTION_IN_PAGE_ERROR exception is caught. +*/ +int sqlite3WalSystemErrno(Wal *pWal){ + int iRet = 0; + if( pWal ){ + iRet = pWal->iSysErrno; + pWal->iSysErrno = 0; + } + return iRet; +} + +#else +# define walAssertLockmask(x) 1 +#endif /* ifdef SQLITE_USE_SEH */ + /* ** Close a connection to a log file. */ @@ -2267,6 +2487,8 @@ int sqlite3WalClose( if( pWal ){ int isDelete = 0; /* True to unlink wal and wal-index files */ + assert( walAssertLockmask(pWal) ); + /* If an EXCLUSIVE lock can be obtained on the database file (using the ** ordinary, rollback-mode locking methods, this guarantees that the ** connection associated with this log file is the only connection to @@ -2281,7 +2503,7 @@ int sqlite3WalClose( if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = sqlite3WalCheckpoint(pWal, db, + rc = sqlite3WalCheckpoint(pWal, db, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0 ); if( rc==SQLITE_OK ){ @@ -2291,7 +2513,7 @@ int sqlite3WalClose( ); if( bPersist!=1 ){ /* Try to delete the WAL file if the checkpoint completed and - ** fsyned (rc==SQLITE_OK) and if we are not in persistent-wal + ** fsynced (rc==SQLITE_OK) and if we are not in persistent-wal ** mode (!bPersist) */ isDelete = 1; }else if( pWal->mxWalSize>=0 ){ @@ -2358,7 +2580,7 @@ static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){ ** give false-positive warnings about these accesses because the tools do not ** account for the double-read and the memory barrier. The use of mutexes ** here would be problematic as the memory being accessed is potentially - ** shared among multiple processes and not all mutex implementions work + ** shared among multiple processes and not all mutex implementations work ** reliably in that environment. */ aHdr = walIndexHdr(pWal); @@ -2368,7 +2590,7 @@ static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){ if( memcmp(&h1, &h2, sizeof(h1))!=0 ){ return 1; /* Dirty read */ - } + } if( h1.isInit==0 ){ return 1; /* Malformed header - probably all zeros */ } @@ -2404,7 +2626,7 @@ static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){ ** changed by this operation. If pWal->hdr is unchanged, set *pChanged ** to 0. ** -** If the wal-index header is successfully read, return SQLITE_OK. +** If the wal-index header is successfully read, return SQLITE_OK. ** Otherwise an SQLite error code. */ static int walIndexReadHdr(Wal *pWal, int *pChanged){ @@ -2412,7 +2634,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ int badHdr; /* True if a header read failed */ volatile u32 *page0; /* Chunk of wal-index containing header */ - /* Ensure that page 0 of the wal-index (the page that contains the + /* Ensure that page 0 of the wal-index (the page that contains the ** wal-index header) is mapped. Return early if an error occurs here. */ assert( pChanged ); @@ -2444,7 +2666,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ /* If the first page of the wal-index has been mapped, try to read the ** wal-index header immediately, without holding any lock. This usually - ** works, but may fail if the wal-index header is corrupt or currently + ** works, but may fail if the wal-index header is corrupt or currently ** being modified by another thread or process. */ badHdr = (page0 ? walIndexTryHdr(pWal, pChanged) : 1); @@ -2519,15 +2741,15 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ ** ** The *-wal file has been read and an appropriate wal-index has been ** constructed in pWal->apWiData[] using heap memory instead of shared -** memory. +** memory. ** ** If this function returns SQLITE_OK, then the read transaction has -** been successfully opened. In this case output variable (*pChanged) +** been successfully opened. In this case output variable (*pChanged) ** is set to true before returning if the caller should discard the -** contents of the page cache before proceeding. Or, if it returns -** WAL_RETRY, then the heap memory wal-index has been discarded and -** the caller should retry opening the read transaction from the -** beginning (including attempting to map the *-shm file). +** contents of the page cache before proceeding. Or, if it returns +** WAL_RETRY, then the heap memory wal-index has been discarded and +** the caller should retry opening the read transaction from the +** beginning (including attempting to map the *-shm file). ** ** If an error occurs, an SQLite error code is returned. */ @@ -2640,8 +2862,8 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** the caller. */ aSaveCksum[0] = pWal->hdr.aFrameCksum[0]; aSaveCksum[1] = pWal->hdr.aFrameCksum[1]; - for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->szPage); - iOffset+szFrame<=szWal; + for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->szPage); + iOffset+szFrame<=szWal; iOffset+=szFrame ){ u32 pgno; /* Database page number for frame */ @@ -2689,10 +2911,10 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** ** The useWal parameter is true to force the use of the WAL and disable ** the case where the WAL is bypassed because it has been completely -** checkpointed. If useWal==0 then this routine calls walIndexReadHdr() -** to make a copy of the wal-index header into pWal->hdr. If the -** wal-index header has changed, *pChanged is set to 1 (as an indication -** to the caller that the local page cache is obsolete and needs to be +** checkpointed. If useWal==0 then this routine calls walIndexReadHdr() +** to make a copy of the wal-index header into pWal->hdr. If the +** wal-index header has changed, *pChanged is set to 1 (as an indication +** to the caller that the local page cache is obsolete and needs to be ** flushed.) When useWal==1, the wal-index header is assumed to already ** be loaded and the pChanged parameter is unused. ** @@ -2707,7 +2929,7 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** bad luck when there is lots of contention for the wal-index, but that ** possibility is so small that it can be safely neglected, we believe. ** -** On success, this routine obtains a read lock on +** On success, this routine obtains a read lock on ** WAL_READ_LOCK(pWal->readLock). The pWal->readLock integer is ** in the range 0 <= pWal->readLock < WAL_NREADER. If pWal->readLock==(-1) ** that means the Wal does not hold any read lock. The reader must not @@ -2745,16 +2967,16 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** ** Circumstances that cause a RETRY should only last for the briefest ** instances of time. No I/O or other system calls are done while the - ** locks are held, so the locks should not be held for very long. But + ** locks are held, so the locks should not be held for very long. But ** if we are unlucky, another process that is holding a lock might get - ** paged out or take a page-fault that is time-consuming to resolve, + ** paged out or take a page-fault that is time-consuming to resolve, ** during the few nanoseconds that it is holding the lock. In that case, ** it might take longer than normal for the lock to free. ** ** After 5 RETRYs, we begin calling sqlite3OsSleep(). The first few ** calls to sqlite3OsSleep() have a delay of 1 microsecond. Really this ** is more of a scheduler yield than an actual delay. But on the 10th - ** an subsequent retries, the delays start becoming longer and longer, + ** an subsequent retries, the delays start becoming longer and longer, ** so that on the 100th (and last) RETRY we delay for 323 milliseconds. ** The total delay time before giving up is less than 10 seconds. */ @@ -2785,9 +3007,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ if( pWal->apWiData[0]==0 ){ /* This branch is taken when the xShmMap() method returns SQLITE_BUSY. ** We assume this is a transient condition, so return WAL_RETRY. The - ** xShmMap() implementation used by the default unix and win32 VFS - ** modules may return SQLITE_BUSY due to a race condition in the - ** code that determines whether or not the shared-memory region + ** xShmMap() implementation used by the default unix and win32 VFS + ** modules may return SQLITE_BUSY due to a race condition in the + ** code that determines whether or not the shared-memory region ** must be zeroed before the requested page is returned. */ rc = WAL_RETRY; @@ -2809,6 +3031,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ assert( pWal->nWiData>0 ); assert( pWal->apWiData[0]!=0 ); pInfo = walCkptInfo(pWal); + SEH_INJECT_FAULT; if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame #ifdef SQLITE_ENABLE_SNAPSHOT && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0) @@ -2828,7 +3051,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from ** happening, this is usually correct. ** - ** However, if frames have been appended to the log (or if the log + ** However, if frames have been appended to the log (or if the log ** is wrapped and written for that matter) before the READ_LOCK(0) ** is obtained, that is not necessarily true. A checkpointer may ** have started to backfill the appended frames but crashed before @@ -2858,7 +3081,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } #endif for(i=1; iaReadMark+i); + u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; if( mxReadMark<=thisMark && thisMark<=mxFrame ){ assert( thisMark!=READMARK_NOT_USED ); mxReadMark = thisMark; @@ -2910,9 +3133,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** to read any frames earlier than minFrame from the wal file - they ** can be safely read directly from the database file. ** - ** Because a ShmBarrier() call is made between taking the copy of + ** Because a ShmBarrier() call is made between taking the copy of ** nBackfill and checking that the wal-header in shared-memory still - ** matches the one cached in pWal->hdr, it is guaranteed that the + ** matches the one cached in pWal->hdr, it is guaranteed that the ** checkpointer that set nBackfill was not working with a wal-index ** header newer than that cached in pWal->hdr. If it were, that could ** cause a problem. The checkpointer could omit to checkpoint @@ -2924,7 +3147,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** we can guarantee that the checkpointer that set nBackfill could not ** see any pages past pWal->hdr.mxFrame, this problem does not come up. */ - pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; + pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; walShmBarrier(pWal); if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) @@ -2940,15 +3163,63 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ #ifdef SQLITE_ENABLE_SNAPSHOT /* -** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted +** This function does the work of sqlite3WalSnapshotRecover(). +*/ +static int walSnapshotRecover( + Wal *pWal, /* WAL handle */ + void *pBuf1, /* Temp buffer pWal->szPage bytes in size */ + void *pBuf2 /* Temp buffer pWal->szPage bytes in size */ +){ + int szPage = (int)pWal->szPage; + int rc; + i64 szDb; /* Size of db file in bytes */ + + rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + u32 i = pInfo->nBackfillAttempted; + for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ + WalHashLoc sLoc; /* Hash table location */ + u32 pgno; /* Page number in db file */ + i64 iDbOff; /* Offset of db file entry */ + i64 iWalOff; /* Offset of wal file entry */ + + rc = walHashGet(pWal, walFramePage(i), &sLoc); + if( rc!=SQLITE_OK ) break; + assert( i - sLoc.iZero - 1 >=0 ); + pgno = sLoc.aPgno[i-sLoc.iZero-1]; + iDbOff = (i64)(pgno-1) * szPage; + + if( iDbOff+szPage<=szDb ){ + iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; + rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); + + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); + } + + if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ + break; + } + } + + pInfo->nBackfillAttempted = i-1; + } + } + + return rc; +} + +/* +** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted ** variable so that older snapshots can be accessed. To do this, loop -** through all wal frames from nBackfillAttempted to (nBackfill+1), +** through all wal frames from nBackfillAttempted to (nBackfill+1), ** comparing their content to the corresponding page with the database ** file, if any. Set nBackfillAttempted to the frame number of the ** first frame for which the wal file content matches the db file. ** -** This is only really safe if the file-system is such that any page -** writes made by earlier checkpointers were atomic operations, which +** This is only really safe if the file-system is such that any page +** writes made by earlier checkpointers were atomic operations, which ** is not always true. It is also possible that nBackfillAttempted ** may be left set to a value larger than expected, if a wal frame ** contains content that duplicate of an earlier version of the same @@ -2964,50 +3235,21 @@ int sqlite3WalSnapshotRecover(Wal *pWal){ assert( pWal->readLock>=0 ); rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); if( rc==SQLITE_OK ){ - volatile WalCkptInfo *pInfo = walCkptInfo(pWal); - int szPage = (int)pWal->szPage; - i64 szDb; /* Size of db file in bytes */ - - rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); - if( rc==SQLITE_OK ){ - void *pBuf1 = sqlite3_malloc(szPage); - void *pBuf2 = sqlite3_malloc(szPage); - if( pBuf1==0 || pBuf2==0 ){ - rc = SQLITE_NOMEM; - }else{ - u32 i = pInfo->nBackfillAttempted; - for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ - WalHashLoc sLoc; /* Hash table location */ - u32 pgno; /* Page number in db file */ - i64 iDbOff; /* Offset of db file entry */ - i64 iWalOff; /* Offset of wal file entry */ - - rc = walHashGet(pWal, walFramePage(i), &sLoc); - if( rc!=SQLITE_OK ) break; - assert( i - sLoc.iZero - 1 >=0 ); - pgno = sLoc.aPgno[i-sLoc.iZero-1]; - iDbOff = (i64)(pgno-1) * szPage; - - if( iDbOff+szPage<=szDb ){ - iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; - rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); - - if( rc==SQLITE_OK ){ - rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); - } - - if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ - break; - } - } - - pInfo->nBackfillAttempted = i-1; - } + void *pBuf1 = sqlite3_malloc(pWal->szPage); + void *pBuf2 = sqlite3_malloc(pWal->szPage); + if( pBuf1==0 || pBuf2==0 ){ + rc = SQLITE_NOMEM; + }else{ + pWal->ckptLock = 1; + SEH_TRY { + rc = walSnapshotRecover(pWal, pBuf1, pBuf2); } - - sqlite3_free(pBuf1); - sqlite3_free(pBuf2); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + pWal->ckptLock = 0; } + + sqlite3_free(pBuf1); + sqlite3_free(pBuf2); walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); } @@ -3016,28 +3258,20 @@ int sqlite3WalSnapshotRecover(Wal *pWal){ #endif /* SQLITE_ENABLE_SNAPSHOT */ /* -** Begin a read transaction on the database. -** -** This routine used to be called sqlite3OpenSnapshot() and with good reason: -** it takes a snapshot of the state of the WAL and wal-index for the current -** instant in time. The current thread will continue to use this snapshot. -** Other threads might append new content to the WAL and wal-index but -** that extra content is ignored by the current thread. -** -** If the database contents have changes since the previous read -** transaction, then *pChanged is set to 1 before returning. The -** Pager layer will use this to know that its cache is stale and -** needs to be flushed. +** This function does the work of sqlite3WalBeginReadTransaction() (see +** below). That function simply calls this one inside an SEH_TRY{...} block. */ -int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ +static int walBeginReadTransaction(Wal *pWal, int *pChanged){ int rc; /* Return code */ int cnt = 0; /* Number of TryBeginRead attempts */ #ifdef SQLITE_ENABLE_SNAPSHOT + int ckptLock = 0; int bChanged = 0; WalIndexHdr *pSnapshot = pWal->pSnapshot; #endif assert( pWal->ckptLock==0 ); + assert( pWal->nSehTry>0 ); #ifdef SQLITE_ENABLE_SNAPSHOT if( pSnapshot ){ @@ -3045,13 +3279,13 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ bChanged = 1; } - /* It is possible that there is a checkpointer thread running + /* It is possible that there is a checkpointer thread running ** concurrent with this code. If this is the case, it may be that the - ** checkpointer has already determined that it will checkpoint - ** snapshot X, where X is later in the wal file than pSnapshot, but - ** has not yet set the pInfo->nBackfillAttempted variable to indicate + ** checkpointer has already determined that it will checkpoint + ** snapshot X, where X is later in the wal file than pSnapshot, but + ** has not yet set the pInfo->nBackfillAttempted variable to indicate ** its intent. To avoid the race condition this leads to, ensure that - ** there is no checkpointer process by taking a shared CKPT lock + ** there is no checkpointer process by taking a shared CKPT lock ** before checking pInfo->nBackfillAttempted. */ (void)walEnableBlocking(pWal); rc = walLockShared(pWal, WAL_CKPT_LOCK); @@ -3060,7 +3294,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ if( rc!=SQLITE_OK ){ return rc; } - pWal->ckptLock = 1; + ckptLock = 1; } #endif @@ -3112,7 +3346,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ /* A client using a non-current snapshot may not ignore any frames ** from the start of the wal file. This is because, for a system ** where (minFrame < iSnapshot < maxFrame), a checkpointer may - ** have omitted to checkpoint a frame earlier than minFrame in + ** have omitted to checkpoint a frame earlier than minFrame in ** the file because there exists a frame after iSnapshot that ** is the same database page. */ pWal->minFrame = 1; @@ -3124,15 +3358,37 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ } /* Release the shared CKPT lock obtained above. */ - if( pWal->ckptLock ){ + if( ckptLock ){ assert( pSnapshot ); walUnlockShared(pWal, WAL_CKPT_LOCK); - pWal->ckptLock = 0; } #endif return rc; } +/* +** Begin a read transaction on the database. +** +** This routine used to be called sqlite3OpenSnapshot() and with good reason: +** it takes a snapshot of the state of the WAL and wal-index for the current +** instant in time. The current thread will continue to use this snapshot. +** Other threads might append new content to the WAL and wal-index but +** that extra content is ignored by the current thread. +** +** If the database contents have changes since the previous read +** transaction, then *pChanged is set to 1 before returning. The +** Pager layer will use this to know that its cache is stale and +** needs to be flushed. +*/ +int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ + int rc; + SEH_TRY { + rc = walBeginReadTransaction(pWal, pChanged); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + /* ** Finish with a read transaction. All this does is release the ** read-lock. @@ -3153,7 +3409,7 @@ void sqlite3WalEndReadTransaction(Wal *pWal){ ** Return SQLITE_OK if successful, or an error code if an error occurs. If an ** error does occur, the final value of *piRead is undefined. */ -int sqlite3WalFindFrame( +static int walFindFrame( Wal *pWal, /* WAL handle */ Pgno pgno, /* Database page number to read data for */ u32 *piRead /* OUT: Frame number (or zero) */ @@ -3168,8 +3424,8 @@ int sqlite3WalFindFrame( /* If the "last page" field of the wal-index header snapshot is 0, then ** no data will be read from the wal under any circumstances. Return early - ** in this case as an optimization. Likewise, if pWal->readLock==0, - ** then the WAL is ignored by the reader so return early, as if the + ** in this case as an optimization. Likewise, if pWal->readLock==0, + ** then the WAL is ignored by the reader so return early, as if the ** WAL were empty. */ if( iLast==0 || (pWal->readLock==0 && pWal->bShmUnreliable==0) ){ @@ -3182,9 +3438,9 @@ int sqlite3WalFindFrame( ** hash table (each hash table indexes up to HASHTABLE_NPAGE frames). ** ** This code might run concurrently to the code in walIndexAppend() - ** that adds entries to the wal-index (and possibly to this hash - ** table). This means the value just read from the hash - ** slot (aHash[iKey]) may have been added before or after the + ** that adds entries to the wal-index (and possibly to this hash + ** table). This means the value just read from the hash + ** slot (aHash[iKey]) may have been added before or after the ** current read transaction was opened. Values added after the ** read transaction was opened may have been written incorrectly - ** i.e. these slots may contain garbage data. However, we assume @@ -3192,13 +3448,13 @@ int sqlite3WalFindFrame( ** opened remain unmodified. ** ** For the reasons above, the if(...) condition featured in the inner - ** loop of the following block is more stringent that would be required + ** loop of the following block is more stringent that would be required ** if we had exclusive access to the hash-table: ** - ** (aPgno[iFrame]==pgno): + ** (aPgno[iFrame]==pgno): ** This condition filters out normal hash-table collisions. ** - ** (iFrame<=iLast): + ** (iFrame<=iLast): ** This condition filters out entries that were added to the hash ** table after the current read-transaction had started. */ @@ -3216,6 +3472,7 @@ int sqlite3WalFindFrame( } nCollide = HASHTABLE_NSLOT; iKey = walHash(pgno); + SEH_INJECT_FAULT; while( (iH = AtomicLoad(&sLoc.aHash[iKey]))!=0 ){ u32 iFrame = iH + sLoc.iZero; if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH-1]==pgno ){ @@ -3252,6 +3509,30 @@ int sqlite3WalFindFrame( return SQLITE_OK; } +/* +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. +** +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. +** +** The difference between this function and walFindFrame() is that this +** function wraps walFindFrame() in an SEH_TRY{...} block. +*/ +int sqlite3WalFindFrame( + Wal *pWal, /* WAL handle */ + Pgno pgno, /* Database page number to read data for */ + u32 *piRead /* OUT: Frame number (or zero) */ +){ + int rc; + SEH_TRY { + rc = walFindFrame(pWal, pgno, piRead); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + return rc; +} + /* ** Read the contents of frame iRead from the wal file into buffer pOut ** (which is nOut bytes in size). Return SQLITE_OK if successful, or an @@ -3274,7 +3555,7 @@ int sqlite3WalReadFrame( return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); } -/* +/* ** Return the size of the database in pages (or zero, if unknown). */ Pgno sqlite3WalDbsize(Wal *pWal){ @@ -3285,7 +3566,7 @@ Pgno sqlite3WalDbsize(Wal *pWal){ } -/* +/* ** This function starts a write transaction on the WAL. ** ** A read transaction must have already been started by a prior call @@ -3333,12 +3614,17 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){ ** time the read transaction on this connection was started, then ** the write is disallowed. */ - if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + SEH_TRY { + if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + rc = SQLITE_BUSY_SNAPSHOT; + } + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + + if( rc!=SQLITE_OK ){ walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); pWal->writeLock = 0; - rc = SQLITE_BUSY_SNAPSHOT; } - return rc; } @@ -3373,39 +3659,42 @@ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){ if( ALWAYS(pWal->writeLock) ){ Pgno iMax = pWal->hdr.mxFrame; Pgno iFrame; - - /* Restore the clients cache of the wal-index header to the state it - ** was in before the client began writing to the database. - */ - memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); - for(iFrame=pWal->hdr.mxFrame+1; - ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; - iFrame++ - ){ - /* This call cannot fail. Unless the page for which the page number - ** is passed as the second argument is (a) in the cache and - ** (b) has an outstanding reference, then xUndo is either a no-op - ** (if (a) is false) or simply expels the page from the cache (if (b) - ** is false). - ** - ** If the upper layer is doing a rollback, it is guaranteed that there - ** are no outstanding references to any page other than page 1. And - ** page 1 is never written to the log until the transaction is - ** committed. As a result, the call to xUndo may not fail. + SEH_TRY { + /* Restore the clients cache of the wal-index header to the state it + ** was in before the client began writing to the database. */ - assert( walFramePgno(pWal, iFrame)!=1 ); - rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); + + for(iFrame=pWal->hdr.mxFrame+1; + ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; + iFrame++ + ){ + /* This call cannot fail. Unless the page for which the page number + ** is passed as the second argument is (a) in the cache and + ** (b) has an outstanding reference, then xUndo is either a no-op + ** (if (a) is false) or simply expels the page from the cache (if (b) + ** is false). + ** + ** If the upper layer is doing a rollback, it is guaranteed that there + ** are no outstanding references to any page other than page 1. And + ** page 1 is never written to the log until the transaction is + ** committed. As a result, the call to xUndo may not fail. + */ + assert( walFramePgno(pWal, iFrame)!=1 ); + rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + } + if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); } - if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) } return rc; } -/* -** Argument aWalData must point to an array of WAL_SAVEPOINT_NDATA u32 -** values. This function populates the array with values required to -** "rollback" the write position of the WAL handle back to the current +/* +** Argument aWalData must point to an array of WAL_SAVEPOINT_NDATA u32 +** values. This function populates the array with values required to +** "rollback" the write position of the WAL handle back to the current ** point in the event of a savepoint rollback (via WalSavepointUndo()). */ void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){ @@ -3416,7 +3705,7 @@ void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){ aWalData[3] = pWal->nCkpt; } -/* +/* ** Move the write position of the WAL back to the point identified by ** the values in the aWalData[] array. aWalData must point to an array ** of WAL_SAVEPOINT_NDATA u32 values that has been previously populated @@ -3441,7 +3730,10 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ pWal->hdr.mxFrame = aWalData[0]; pWal->hdr.aFrameCksum[0] = aWalData[1]; pWal->hdr.aFrameCksum[1] = aWalData[2]; - walCleanupHash(pWal); + SEH_TRY { + walCleanupHash(pWal); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) } return rc; @@ -3618,11 +3910,11 @@ static int walRewriteChecksums(Wal *pWal, u32 iLast){ return rc; } -/* +/* ** Write a set of frames to the log. The caller must hold the write-lock ** on the log file (obtained using sqlite3WalBeginWriteTransaction()). */ -int sqlite3WalFrames( +static int walFrames( Wal *pWal, /* Wal handle to write to */ int szPage, /* Database page-size in bytes */ PgHdr *pList, /* List of dirty pages to write */ @@ -3685,7 +3977,7 @@ int sqlite3WalFrames( walChecksumBytes(1, aWalHdr, WAL_HDRSIZE-2*4, 0, aCksum); sqlite3Put4byte(&aWalHdr[24], aCksum[0]); sqlite3Put4byte(&aWalHdr[28], aCksum[1]); - + pWal->szPage = szPage; pWal->hdr.bigEndCksum = SQLITE_BIGENDIAN; pWal->hdr.aFrameCksum[0] = aCksum[0]; @@ -3729,11 +4021,11 @@ int sqlite3WalFrames( /* Check if this page has already been written into the wal file by ** the current transaction. If so, overwrite the existing frame and - ** set Wal.writeLock to WAL_WRITELOCK_RECKSUM - indicating that + ** set Wal.writeLock to WAL_WRITELOCK_RECKSUM - indicating that ** checksums must be recomputed when the transaction is committed. */ if( iFirst && (p->pDirty || isCommit==0) ){ u32 iWrite = 0; - VVA_ONLY(rc =) sqlite3WalFindFrame(pWal, p->pgno, &iWrite); + VVA_ONLY(rc =) walFindFrame(pWal, p->pgno, &iWrite); assert( rc==SQLITE_OK || iWrite==0 ); if( iWrite>=iFirst ){ i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE; @@ -3813,7 +4105,7 @@ int sqlite3WalFrames( pWal->truncateOnCommit = 0; } - /* Append data to the wal-index. It is not necessary to lock the + /* Append data to the wal-index. It is not necessary to lock the ** wal-index to do this as the SQLITE_SHM_WRITE lock held on the wal-index ** guarantees that there are no other writers, and no data that may ** be in use by existing readers is being overwritten. @@ -3853,6 +4145,29 @@ int sqlite3WalFrames( } /* +** Write a set of frames to the log. The caller must hold the write-lock +** on the log file (obtained using sqlite3WalBeginWriteTransaction()). +** +** The difference between this function and walFrames() is that this +** function wraps walFrames() in an SEH_TRY{...} block. +*/ +int sqlite3WalFrames( + Wal *pWal, /* Wal handle to write to */ + int szPage, /* Database page-size in bytes */ + PgHdr *pList, /* List of dirty pages to write */ + 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 rc; + SEH_TRY { + rc = walFrames(pWal, szPage, pList, nTruncate, isCommit, sync_flags); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + +/* ** This routine is called to implement sqlite3_wal_checkpoint() and ** related interfaces. ** @@ -3894,7 +4209,7 @@ int sqlite3WalCheckpoint( sqlite3WalDb(pWal, db); (void)walEnableBlocking(pWal); - /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive + /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive ** "checkpoint" lock on the database file. ** EVIDENCE-OF: R-10421-19736 If any other process is running a ** checkpoint operation at the same time, the lock cannot be obtained and @@ -3931,33 +4246,36 @@ int sqlite3WalCheckpoint( /* Read the wal-index header. */ - if( rc==SQLITE_OK ){ - walDisableBlocking(pWal); - rc = walIndexReadHdr(pWal, &isChanged); - (void)walEnableBlocking(pWal); - if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ - sqlite3OsUnfetch(pWal->pDbFd, 0, 0); - } - } - - /* Copy data from the log to the database file. */ - if( rc==SQLITE_OK ){ - - if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ - rc = SQLITE_CORRUPT_BKPT; - }else{ - rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); + SEH_TRY { + if( rc==SQLITE_OK ){ + walDisableBlocking(pWal); + rc = walIndexReadHdr(pWal, &isChanged); + (void)walEnableBlocking(pWal); + if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ + sqlite3OsUnfetch(pWal->pDbFd, 0, 0); + } } + + /* Copy data from the log to the database file. */ + if( rc==SQLITE_OK ){ + if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); + } - /* If no error occurred, set the output variables. */ - if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ - if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; - if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + /* If no error occurred, set the output variables. */ + if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ + if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; + SEH_INJECT_FAULT; + if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) if( isChanged ){ - /* If a new wal-index header was loaded before the checkpoint was + /* If a new wal-index header was loaded before the checkpoint was ** performed, then the pager-cache associated with pWal is now ** out of date. So zero the cached wal-index header to ensure that ** next time the pager opens a snapshot on this database it knows that @@ -4008,7 +4326,7 @@ int sqlite3WalCallback(Wal *pWal){ ** operation must occur while the pager is still holding the exclusive ** lock on the main database file. ** -** If op is one, then change from locking_mode=NORMAL into +** If op is one, then change from locking_mode=NORMAL into ** locking_mode=EXCLUSIVE. This means that the pWal->readLock must ** be released. Return 1 if the transition is made and 0 if the ** WAL is already in exclusive-locking mode - meaning that this @@ -4025,13 +4343,15 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op){ 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 + /* pWal->readLock is usually set, but might be -1 if there was a + ** prior error while attempting to acquire are read-lock. This cannot ** happen if the connection is actually in exclusive mode (as no xShmLock ** locks are taken in this case). Nor should the pager attempt to ** upgrade to exclusive-mode following such an error. */ +#ifndef SQLITE_USE_SEH assert( pWal->readLock>=0 || pWal->lockError ); +#endif assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) ); if( op==0 ){ @@ -4057,10 +4377,10 @@ 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. +** WAL module is using shared-memory, return false. */ int sqlite3WalHeapMemory(Wal *pWal){ return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); @@ -4096,13 +4416,13 @@ int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){ /* Try to open on pSnapshot when the next read-transaction starts */ void sqlite3WalSnapshotOpen( - Wal *pWal, + Wal *pWal, sqlite3_snapshot *pSnapshot ){ pWal->pSnapshot = (WalIndexHdr*)pSnapshot; } -/* +/* ** Return a +ve value if snapshot p1 is newer than p2. A -ve value if ** p1 is older than p2 and zero if p1 and p2 are the same snapshot. */ @@ -4122,7 +4442,7 @@ int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ /* ** The caller currently has a read transaction open on the database. ** This function takes a SHARED lock on the CHECKPOINTER slot and then -** checks if the snapshot passed as the second argument is still +** checks if the snapshot passed as the second argument is still ** available. If so, SQLITE_OK is returned. ** ** If the snapshot is not available, SQLITE_ERROR is returned. Or, if @@ -4132,16 +4452,19 @@ int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ */ int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){ int rc; - rc = walLockShared(pWal, WAL_CKPT_LOCK); - if( rc==SQLITE_OK ){ - WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; - if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) - || pNew->mxFramenBackfillAttempted - ){ - rc = SQLITE_ERROR_SNAPSHOT; - walUnlockShared(pWal, WAL_CKPT_LOCK); + SEH_TRY { + rc = walLockShared(pWal, WAL_CKPT_LOCK); + if( rc==SQLITE_OK ){ + WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; + if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + || pNew->mxFramenBackfillAttempted + ){ + rc = SQLITE_ERROR_SNAPSHOT; + walUnlockShared(pWal, WAL_CKPT_LOCK); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) return rc; } diff --git a/src/wal.h b/src/wal.h index 02e2bab36..1b17d2dfb 100644 --- a/src/wal.h +++ b/src/wal.h @@ -45,6 +45,7 @@ # define sqlite3WalFramesize(z) 0 # define sqlite3WalFindFrame(x,y,z) 0 # define sqlite3WalFile(x) 0 +# undef SQLITE_USE_SEH #else #define WAL_SAVEPOINT_NDATA 4 @@ -151,5 +152,9 @@ int sqlite3WalWriteLock(Wal *pWal, int bLock); void sqlite3WalDb(Wal *pWal, sqlite3 *db); #endif +#ifdef SQLITE_USE_SEH +int sqlite3WalSystemErrno(Wal*); +#endif + #endif /* ifndef SQLITE_OMIT_WAL */ #endif /* SQLITE_WAL_H */ diff --git a/src/walker.c b/src/walker.c index f24c052d6..0fe4a1d37 100644 --- a/src/walker.c +++ b/src/walker.c @@ -61,7 +61,7 @@ static int walkWindowList(Walker *pWalker, Window *pList, int bOneOnly){ ** The return value from this routine is WRC_Abort to abandon the tree walk ** and WRC_Continue to continue. */ -static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ +SQLITE_NOINLINE int sqlite3WalkExprNN(Walker *pWalker, Expr *pExpr){ int rc; testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); testcase( ExprHasProperty(pExpr, EP_Reduced) ); @@ -70,7 +70,9 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ if( rc ) return rc & WRC_Abort; if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ assert( pExpr->x.pList==0 || pExpr->pRight==0 ); - if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; + if( pExpr->pLeft && sqlite3WalkExprNN(pWalker, pExpr->pLeft) ){ + return WRC_Abort; + } if( pExpr->pRight ){ assert( !ExprHasProperty(pExpr, EP_WinFunc) ); pExpr = pExpr->pRight; @@ -94,7 +96,7 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ - return pExpr ? walkExpr(pWalker,pExpr) : WRC_Continue; + return pExpr ? sqlite3WalkExprNN(pWalker,pExpr) : WRC_Continue; } /* @@ -220,7 +222,7 @@ int sqlite3WalkSelect(Walker *pWalker, Select *p){ } /* Increase the walkerDepth when entering a subquery, and -** descrease when leaving the subquery. +** decrease when leaving the subquery. */ int sqlite3WalkerDepthIncrease(Walker *pWalker, Select *pSelect){ UNUSED_PARAMETER(pSelect); diff --git a/src/where.c b/src/where.c index 25405cedf..213df4223 100644 --- a/src/where.c +++ b/src/where.c @@ -88,7 +88,7 @@ int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ ** be the continuation for the inner-most loop. ** ** It is always safe for this routine to return the continuation of the -** inner-most loop, in the sense that a correct answer will result. +** inner-most loop, in the sense that a correct answer will result. ** Returning the continuation the second inner loop is an optimization ** that might make the code run a little faster, but should not change ** the final answer. @@ -96,7 +96,7 @@ int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){ WhereLevel *pInner; if( !pWInfo->bOrderedInnerLoop ){ - /* The ORDER BY LIMIT optimization does not apply. Jump to the + /* The ORDER BY LIMIT optimization does not apply. Jump to the ** continuation of the inner-most loop. */ return pWInfo->iContinue; } @@ -153,7 +153,7 @@ int sqlite3WhereBreakLabel(WhereInfo *pWInfo){ ** operate directly on the rowids returned by a WHERE clause. Return ** ONEPASS_SINGLE (1) if the statement can operation directly because only ** a single row is to be changed. Return ONEPASS_MULTI (2) if the one-pass -** optimization can be used on multiple +** optimization can be used on multiple ** ** If the ONEPASS optimization is used (if this routine returns true) ** then also write the indices of open cursors used by ONEPASS @@ -478,7 +478,7 @@ static WhereTerm *whereScanInit( ** if pIdx!=0 and is one of the WO_xx operator codes specified by ** the op parameter. Return a pointer to the term. Return 0 if not found. ** -** If pIdx!=0 then it must be one of the indexes of table iCur. +** If pIdx!=0 then it must be one of the indexes of table iCur. ** Search for terms matching the iColumn-th column of pIdx ** rather than the iColumn-th column of table iCur. ** @@ -592,17 +592,17 @@ static int isDistinctRedundant( ){ Table *pTab; Index *pIdx; - int i; + int i; int iBase; /* If there is more than one table or sub-select in the FROM clause of - ** this query, then it will not be possible to show that the DISTINCT + ** this query, then it will not be possible to show that the DISTINCT ** clause is redundant. */ if( pTabList->nSrc!=1 ) return 0; iBase = pTabList->a[0].iCursor; pTab = pTabList->a[0].pTab; - /* If any of the expressions is an IPK column on table iBase, then return + /* If any of the expressions is an IPK column on table iBase, then return ** true. Note: The (p->iTable==iBase) part of this test may be false if the ** current SELECT is a correlated sub-query. */ @@ -656,7 +656,7 @@ static LogEst estLog(LogEst N){ ** Convert OP_Column opcodes to OP_Copy in previously generated code. ** ** This routine runs over generated VDBE code and translates OP_Column -** opcodes into OP_Copy when the table is being accessed via co-routine +** opcodes into OP_Copy when the table is being accessed via co-routine ** instead of via table lookup. ** ** If the iAutoidxCur is not zero, then any OP_Rowid instructions on @@ -777,7 +777,7 @@ static int constraintCompatibleWithOuterJoin( } return 1; } - + #ifndef SQLITE_OMIT_AUTOMATIC_INDEX @@ -822,7 +822,7 @@ static int termCanDriveIndex( ** ** This is only required if sqlite3_stmt_scanstatus() is enabled, to ** associate an SQLITE_SCANSTAT_NCYCLE and SQLITE_SCANSTAT_NLOOP -** values with. In order to avoid breaking legacy code and test cases, +** values with. In order to avoid breaking legacy code and test cases, ** the OP_Explain is not added if this is an EXPLAIN QUERY PLAN command. */ static void explainAutomaticIndex( @@ -1096,7 +1096,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( } sqlite3VdbeJumpHere(v, addrTop); sqlite3ReleaseTempReg(pParse, regRecord); - + /* Jump here when skipping the initialization */ sqlite3VdbeJumpHere(v, addrInit); sqlite3VdbeScanStatusRange(v, addrExp, addrExp, -1); @@ -1243,7 +1243,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( #ifndef SQLITE_OMIT_VIRTUALTABLE /* -** Allocate and populate an sqlite3_index_info structure. It is the +** Allocate and populate an sqlite3_index_info structure. It is the ** responsibility of the caller to eventually release the structure ** by passing the pointer returned by this function to freeIndexInfo(). */ @@ -1268,7 +1268,7 @@ static sqlite3_index_info *allocateIndexInfo( const Table *pTab; int eDistinct = 0; ExprList *pOrderBy = pWInfo->pOrderBy; - + assert( pSrc!=0 ); pTab = pSrc->pTab; assert( pTab!=0 ); @@ -1302,7 +1302,7 @@ static sqlite3_index_info *allocateIndexInfo( pTerm->wtFlags |= TERM_OK; } - /* If the ORDER BY clause contains only columns in the current + /* If the ORDER BY clause contains only columns in the current ** virtual table then allocate space for the aOrderBy part of ** the sqlite3_index_info structure. */ @@ -1411,7 +1411,7 @@ static sqlite3_index_info *allocateIndexInfo( assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_AUX) ); if( op & (WO_LT|WO_LE|WO_GT|WO_GE) - && sqlite3ExprIsVector(pTerm->pExpr->pRight) + && sqlite3ExprIsVector(pTerm->pExpr->pRight) ){ testcase( j!=i ); if( j<16 ) mNoOmit |= (1 << j); @@ -1513,8 +1513,8 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ ** Return the index of the sample that is the smallest sample that ** is greater than or equal to pRec. Note that this index is not an index ** into the aSample[] array - it is an index into a virtual set of samples -** based on the contents of aSample[] and the number of fields in record -** pRec. +** based on the contents of aSample[] and the number of fields in record +** pRec. */ static int whereKeyStats( Parse *pParse, /* Database connection */ @@ -1539,7 +1539,7 @@ static int whereKeyStats( assert( pRec!=0 ); assert( pIdx->nSample>0 ); assert( pRec->nField>0 ); - + /* Do a binary search to find the first sample greater than or equal ** to pRec. If pRec contains a single field, the set of samples to search @@ -1551,38 +1551,38 @@ static int whereKeyStats( ** consider prefixes of those samples. For example, if the set of samples ** in aSample is: ** - ** aSample[0] = (a, 5) - ** aSample[1] = (a, 10) - ** aSample[2] = (b, 5) - ** aSample[3] = (c, 100) + ** aSample[0] = (a, 5) + ** aSample[1] = (a, 10) + ** aSample[2] = (b, 5) + ** aSample[3] = (c, 100) ** aSample[4] = (c, 105) ** - ** Then the search space should ideally be the samples above and the - ** unique prefixes [a], [b] and [c]. But since that is hard to organize, + ** Then the search space should ideally be the samples above and the + ** unique prefixes [a], [b] and [c]. But since that is hard to organize, ** the code actually searches this set: ** - ** 0: (a) - ** 1: (a, 5) - ** 2: (a, 10) - ** 3: (a, 10) - ** 4: (b) - ** 5: (b, 5) - ** 6: (c) - ** 7: (c, 100) + ** 0: (a) + ** 1: (a, 5) + ** 2: (a, 10) + ** 3: (a, 10) + ** 4: (b) + ** 5: (b, 5) + ** 6: (c) + ** 7: (c, 100) ** 8: (c, 105) ** 9: (c, 105) ** ** For each sample in the aSample[] array, N samples are present in the - ** effective sample array. In the above, samples 0 and 1 are based on + ** effective sample array. In the above, samples 0 and 1 are based on ** sample aSample[0]. Samples 2 and 3 on aSample[1] etc. ** ** Often, sample i of each block of N effective samples has (i+1) fields. ** Except, each sample may be extended to ensure that it is greater than or - ** equal to the previous sample in the array. For example, in the above, - ** sample 2 is the first sample of a block of N samples, so at first it - ** appears that it should be 1 field in size. However, that would make it - ** smaller than sample 1, so the binary search would not work. As a result, - ** it is extended to two fields. The duplicates that this creates do not + ** equal to the previous sample in the array. For example, in the above, + ** sample 2 is the first sample of a block of N samples, so at first it + ** appears that it should be 1 field in size. However, that would make it + ** smaller than sample 1, so the binary search would not work. As a result, + ** it is extended to two fields. The duplicates that this creates do not ** cause any problems. */ if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ @@ -1601,7 +1601,7 @@ static int whereKeyStats( iSamp = iTest / nField; if( iSamp>0 ){ /* The proposed effective sample is a prefix of sample aSample[iSamp]. - ** Specifically, the shortest prefix of at least (1 + iTest%nField) + ** Specifically, the shortest prefix of at least (1 + iTest%nField) ** fields that is greater than the previous effective sample. */ for(n=(iTest % nField) + 1; nnSample ); assert( iCol==nField-1 ); pRec->nField = nField; - assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec) - || pParse->db->mallocFailed + assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec) + || pParse->db->mallocFailed ); }else{ /* Unless i==pIdx->nSample, indicating that pRec is larger than @@ -1645,7 +1645,7 @@ static int whereKeyStats( ** (iCol+1) field prefix of sample i. */ assert( i<=pIdx->nSample && i>=0 ); pRec->nField = iCol+1; - assert( i==pIdx->nSample + assert( i==pIdx->nSample || sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)>0 || pParse->db->mallocFailed ); @@ -1673,7 +1673,7 @@ static int whereKeyStats( aStat[0] = aSample[i].anLt[iCol]; aStat[1] = aSample[i].anEq[iCol]; }else{ - /* At this point, the (iCol+1) field prefix of aSample[i] is the first + /* At this point, the (iCol+1) field prefix of aSample[i] is the first ** sample that is greater than pRec. Or, if i==pIdx->nSample then pRec ** is larger than all samples in the array. */ tRowcnt iUpper, iGap; @@ -1705,7 +1705,7 @@ static int whereKeyStats( /* ** If it is not NULL, pTerm is a term that provides an upper or lower -** bound on a range scan. Without considering pTerm, it is estimated +** bound on a range scan. Without considering pTerm, it is estimated ** that the scan will visit nNew rows. This function returns the number ** estimated to be visited after taking pTerm into account. ** @@ -1743,18 +1743,18 @@ char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ #ifdef SQLITE_ENABLE_STAT4 -/* +/* ** This function is called to estimate the number of rows visited by a ** range-scan on a skip-scan index. For example: ** ** CREATE INDEX i1 ON t1(a, b, c); ** SELECT * FROM t1 WHERE a=? AND c BETWEEN ? AND ?; ** -** Value pLoop->nOut is currently set to the estimated number of rows -** visited for scanning (a=? AND b=?). This function reduces that estimate +** Value pLoop->nOut is currently set to the estimated number of rows +** visited for scanning (a=? AND b=?). This function reduces that estimate ** by some factor to account for the (c BETWEEN ? AND ?) expression based -** on the stat4 data for the index. this scan will be peformed multiple -** times (once for each (a,b) combination that matches a=?) is dealt with +** on the stat4 data for the index. this scan will be performed multiple +** times (once for each (a,b) combination that matches a=?) is dealt with ** by the caller. ** ** It does this by scanning through all stat4 samples, comparing values @@ -1775,7 +1775,7 @@ char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ ** estimate of the number of rows delivered remains unchanged), *pbDone ** is left as is. ** -** If an error occurs, an SQLite error code is returned. Otherwise, +** If an error occurs, an SQLite error code is returned. Otherwise, ** SQLITE_OK. */ static int whereRangeSkipScanEst( @@ -1793,7 +1793,7 @@ static int whereRangeSkipScanEst( int rc = SQLITE_OK; u8 aff = sqlite3IndexColumnAffinity(db, p, nEq); CollSeq *pColl; - + sqlite3_value *p1 = 0; /* Value extracted from pLower */ sqlite3_value *p2 = 0; /* Value extracted from pUpper */ sqlite3_value *pVal = 0; /* Value extracted from record */ @@ -1825,7 +1825,7 @@ static int whereRangeSkipScanEst( nDiff = (nUpper - nLower); if( nDiff<=0 ) nDiff = 1; - /* If there is both an upper and lower bound specified, and the + /* If there is both an upper and lower bound specified, and the ** comparisons indicate that they are close together, use the fallback ** method (assume that the scan visits 1/64 of the rows) for estimating ** the number of rows visited. Otherwise, estimate the number of rows @@ -1872,7 +1872,7 @@ static int whereRangeSkipScanEst( ** ** ... FROM t1 WHERE a = ? AND b > ? AND b < ? ... ** -** then nEq is set to 1 (as the range restricted column, b, is the second +** then nEq is set to 1 (as the range restricted column, b, is the second ** left-most column of the index). Or, if the query is: ** ** ... FROM t1 WHERE a > ? AND a < ? ... @@ -1880,13 +1880,13 @@ static int whereRangeSkipScanEst( ** then nEq is set to 0. ** ** When this function is called, *pnOut is set to the sqlite3LogEst() of the -** number of rows that the index scan is expected to visit without -** considering the range constraints. If nEq is 0, then *pnOut is the number of +** number of rows that the index scan is expected to visit without +** considering the range constraints. If nEq is 0, then *pnOut is the number of ** rows in the index. Assuming no error occurs, *pnOut is adjusted (reduced) ** to account for the range constraints pLower and pUpper. -** +** ** In the absence of sqlite_stat4 ANALYZE data, or if such data cannot be -** used, a single range inequality reduces the search space by a factor of 4. +** used, a single range inequality reduces the search space by a factor of 4. ** and a pair of constraints (x>? AND xu.btree.nBtm; int nTop = pLoop->u.btree.nTop; - /* Variable iLower will be set to the estimate of the number of rows in + /* Variable iLower will be set to the estimate of the number of rows in ** the index that are less than the lower bound of the range query. The ** lower bound being the concatenation of $P and $L, where $P is the ** key-prefix formed by the nEq values matched against the nEq left-most @@ -1923,7 +1923,7 @@ static int whereRangeScanEst( ** Or, if pLower is NULL or $L cannot be extracted from it (because it ** is not a simple variable or literal value), the lower bound of the ** range is $P. Due to a quirk in the way whereKeyStats() works, even - ** if $L is available, whereKeyStats() is called for both ($P) and + ** if $L is available, whereKeyStats() is called for both ($P) and ** ($P:$L) and the larger of the two returned values is used. ** ** Similarly, iUpper is to be set to the estimate of the number of rows @@ -1947,7 +1947,7 @@ static int whereRangeScanEst( iLower = 0; iUpper = p->nRowEst0; }else{ - /* Note: this call could be optimized away - since the same values must + /* Note: this call could be optimized away - since the same values must ** have been requested when testing key $P in whereEqualScanEst(). */ whereKeyStats(pParse, p, pRec, 0, a); iLower = a[0]; @@ -2035,7 +2035,7 @@ static int whereRangeScanEst( ** reduced by an additional 75%. This means that, by default, an open-ended ** range query (e.g. col > ?) is assumed to match 1/4 of the rows in the ** index. While a closed range (e.g. col BETWEEN ? AND ?) is estimated to - ** match 1/64 of the index. */ + ** match 1/64 of the index. */ if( pLower && pLower->truthProb>0 && pUpper && pUpper->truthProb>0 ){ nNew -= 20; } @@ -2062,7 +2062,7 @@ static int whereRangeScanEst( ** for that index. When pExpr==NULL that means the constraint is ** "x IS NULL" instead of "x=VALUE". ** -** Write the estimated row count into *pnRow and return SQLITE_OK. +** Write the estimated row count into *pnRow and return SQLITE_OK. ** If unable to make an estimate, leave *pnRow unchanged and return ** non-zero. ** @@ -2113,7 +2113,7 @@ static int whereEqualScanEst( WHERETRACE(0x20,("equality scan regions %s(%d): %d\n", p->zName, nEq-1, (int)a[1])); *pnRow = a[1]; - + return rc; } #endif /* SQLITE_ENABLE_STAT4 */ @@ -2126,7 +2126,7 @@ static int whereEqualScanEst( ** ** WHERE x IN (1,2,3,4) ** -** Write the estimated row count into *pnRow and return SQLITE_OK. +** Write the estimated row count into *pnRow and return SQLITE_OK. ** If unable to make an estimate, leave *pnRow unchanged and return ** non-zero. ** @@ -2188,7 +2188,7 @@ void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ sqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}", pTerm->leftCursor, pTerm->u.x.leftColumn); }else if( (pTerm->eOperator & WO_OR)!=0 && pTerm->u.pOrInfo!=0 ){ - sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%llx", + sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%llx", pTerm->u.pOrInfo->indexable); }else{ sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor); @@ -2390,7 +2390,7 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ /* ** Return TRUE if all of the following are true: ** -** (1) X has the same or lower cost, or returns the same or fewer rows, +** (1) X has the same or lower cost, or returns the same or fewer rows, ** than Y. ** (2) X uses fewer WHERE clause terms than Y ** (3) Every WHERE clause term used by X is also used by Y @@ -2399,7 +2399,7 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ ** ** Conditions (2) and (3) mean that X is a "proper subset" of Y. ** If X is a proper subset of Y then Y is a better choice and ought -** to have a lower cost. This routine returns TRUE when that cost +** to have a lower cost. This routine returns TRUE when that cost ** relationship is inverted and needs to be adjusted. Constraint (4) ** was added because if X uses skip-scan less than Y it still might ** deserve a lower cost even if it is a proper subset of Y. Constraint (5) @@ -2423,7 +2423,7 @@ static int whereLoopCheaperProperSubset( } if( j<0 ) return 0; /* X not a subset of Y since term X[i] not used by Y */ } - if( (pX->wsFlags&WHERE_IDX_ONLY)!=0 + if( (pX->wsFlags&WHERE_IDX_ONLY)!=0 && (pY->wsFlags&WHERE_IDX_ONLY)==0 ){ return 0; /* Constraint (5) */ } @@ -2450,10 +2450,10 @@ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ if( p->iTab!=pTemplate->iTab ) continue; if( (p->wsFlags & WHERE_INDEXED)==0 ) continue; if( whereLoopCheaperProperSubset(p, pTemplate) ){ - /* Adjust pTemplate cost downward so that it is cheaper than its + /* Adjust pTemplate cost downward so that it is cheaper than its ** subset p. */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, + pTemplate->rRun, pTemplate->nOut, MIN(p->rRun, pTemplate->rRun), MIN(p->nOut - 1, pTemplate->nOut))); pTemplate->rRun = MIN(p->rRun, pTemplate->rRun); @@ -2462,7 +2462,7 @@ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ /* Adjust pTemplate cost upward so that it is costlier than p since ** pTemplate is a proper subset of p */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, + pTemplate->rRun, pTemplate->nOut, MAX(p->rRun, pTemplate->rRun), MAX(p->nOut + 1, pTemplate->nOut))); pTemplate->rRun = MAX(p->rRun, pTemplate->rRun); @@ -2500,7 +2500,7 @@ static WhereLoop **whereLoopFindLesser( /* In the current implementation, the rSetup value is either zero ** or the cost of building an automatic index (NlogN) and the NlogN ** is the same for compatible WhereLoops. */ - assert( p->rSetup==0 || pTemplate->rSetup==0 + assert( p->rSetup==0 || pTemplate->rSetup==0 || p->rSetup==pTemplate->rSetup ); /* whereLoopAddBtree() always generates and inserts the automatic index @@ -2508,7 +2508,7 @@ static WhereLoop **whereLoopFindLesser( ** rSetup. Call this SETUP-INVARIANT */ assert( p->rSetup>=pTemplate->rSetup ); - /* Any loop using an appliation-defined index (or PRIMARY KEY or + /* Any loop using an application-defined index (or PRIMARY KEY or ** UNIQUE constraint) with one or more == constraints is better ** than an automatic index. Unless it is a skip-scan. */ if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 @@ -2535,7 +2535,7 @@ static WhereLoop **whereLoopFindLesser( /* If pTemplate is always better than p, then cause p to be overwritten ** with pTemplate. pTemplate is better than p if: - ** (1) pTemplate has no more dependences than p, and + ** (1) pTemplate has no more dependencies than p, and ** (2) pTemplate has an equal or lower cost than p. */ if( (p->prereq & pTemplate->prereq)==pTemplate->prereq /* (1) */ @@ -2565,7 +2565,7 @@ static WhereLoop **whereLoopFindLesser( ** ** When accumulating multiple loops (when pBuilder->pOrSet is NULL) we ** still might overwrite similar loops with the new template if the -** new template is better. Loops may be overwritten if the following +** new template is better. Loops may be overwritten if the following ** conditions are met: ** ** (1) They have the same iTab. @@ -2623,7 +2623,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ sqlite3WhereLoopPrint(pTemplate, pBuilder->pWC); } #endif - return SQLITE_OK; + return SQLITE_OK; }else{ p = *ppPrev; } @@ -2653,7 +2653,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ }else{ /* We will be overwriting WhereLoop p[]. But before we do, first ** go through the rest of the list and delete any other entries besides - ** p[] that are also supplated by pTemplate */ + ** p[] that are also supplanted by pTemplate */ WhereLoop **ppTail = &p->pNextLoop; WhereLoop *pToDel; while( *ppTail ){ @@ -2783,7 +2783,7 @@ static void whereLoopOutputAdjust( } } -/* +/* ** Term pTerm is a vector range comparison operation. The first comparison ** in the vector can be optimized using column nEq of the index. This ** function returns the total number of vector elements that can be used @@ -2812,7 +2812,7 @@ static int whereRangeVectorLen( nCmp = MIN(nCmp, (pIdx->nColumn - nEq)); for(i=1; iop!=TK_COLUMN - || pLhs->iTable!=iCur - || pLhs->iColumn!=pIdx->aiColumn[i+nEq] + if( pLhs->op!=TK_COLUMN + || pLhs->iTable!=iCur + || pLhs->iColumn!=pIdx->aiColumn[i+nEq] || pIdx->aSortOrder[i+nEq]!=pIdx->aSortOrder[nEq] ){ break; @@ -2853,7 +2853,7 @@ static int whereRangeVectorLen( } /* -** Adjust the cost C by the costMult facter T. This only occurs if +** Adjust the cost C by the costMult factor T. This only occurs if ** compiled with -DSQLITE_ENABLE_COSTMULT */ #ifdef SQLITE_ENABLE_COSTMULT @@ -2863,15 +2863,15 @@ static int whereRangeVectorLen( #endif /* -** We have so far matched pBuilder->pNew->u.btree.nEq terms of the +** We have so far matched pBuilder->pNew->u.btree.nEq terms of the ** index pIndex. Try to match one more. ** -** When this function is called, pBuilder->pNew->nOut contains the -** number of rows expected to be visited by filtering using the nEq -** terms only. If it is modified, this value is restored before this +** When this function is called, pBuilder->pNew->nOut contains the +** number of rows expected to be visited by filtering using the nEq +** terms only. If it is modified, this value is restored before this ** function returns. ** -** If pProbe->idxType==SQLITE_IDXTYPE_IPK, that means pIndex is +** If pProbe->idxType==SQLITE_IDXTYPE_IPK, that means pIndex is ** a fake index used for the INTEGER PRIMARY KEY. */ static int whereLoopAddBtreeIndex( @@ -2880,7 +2880,7 @@ static int whereLoopAddBtreeIndex( Index *pProbe, /* An index on pSrc */ LogEst nInMul /* log(Number of iterations due to IN) */ ){ - WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyse context */ + WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyze context */ Parse *pParse = pWInfo->pParse; /* Parsing context */ sqlite3 *db = pParse->db; /* Database connection malloc context */ WhereLoop *pNew; /* Template WhereLoop under construction */ @@ -2979,9 +2979,9 @@ static int whereLoopAddBtreeIndex( pNew->prereq = (saved_prereq | pTerm->prereqRight) & ~pNew->maskSelf; assert( nInMul==0 - || (pNew->wsFlags & WHERE_COLUMN_NULL)!=0 - || (pNew->wsFlags & WHERE_COLUMN_IN)!=0 - || (pNew->wsFlags & WHERE_SKIPSCAN)!=0 + || (pNew->wsFlags & WHERE_COLUMN_NULL)!=0 + || (pNew->wsFlags & WHERE_COLUMN_IN)!=0 + || (pNew->wsFlags & WHERE_SKIPSCAN)!=0 ); if( eOp & WO_IN ){ @@ -3008,7 +3008,7 @@ static int whereLoopAddBtreeIndex( /* Let: ** N = the total number of rows in the table ** K = the number of entries on the RHS of the IN operator - ** M = the number of rows in the table that match terms to the + ** M = the number of rows in the table that match terms to the ** to the left in the same index. If the IN operator is on ** the left-most index column, M==N. ** @@ -3053,11 +3053,11 @@ static int whereLoopAddBtreeIndex( int iCol = pProbe->aiColumn[saved_nEq]; pNew->wsFlags |= WHERE_COLUMN_EQ; assert( saved_nEq==pNew->u.btree.nEq ); - if( iCol==XN_ROWID + if( iCol==XN_ROWID || (iCol>=0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) ){ - if( iCol==XN_ROWID || pProbe->uniqNotNull - || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ) + if( iCol==XN_ROWID || pProbe->uniqNotNull + || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ) ){ pNew->wsFlags |= WHERE_ONEROW; }else{ @@ -3104,7 +3104,7 @@ static int whereLoopAddBtreeIndex( /* At this point pNew->nOut is set to the number of rows expected to ** be visited by the index scan before considering term pTerm, or the - ** values of nIn and nInMul. In other words, assuming that all + ** values of nIn and nInMul. In other words, assuming that all ** "x IN(...)" terms are replaced with "x = ?". This block updates ** the value of pNew->nOut to account for pTerm (but not nIn/nInMul). */ assert( pNew->nOut==saved_nOut ); @@ -3125,8 +3125,8 @@ static int whereLoopAddBtreeIndex( }else{ #ifdef SQLITE_ENABLE_STAT4 tRowcnt nOut = 0; - if( nInMul==0 - && pProbe->nSample + if( nInMul==0 + && pProbe->nSample && ALWAYS(pNew->u.btree.nEq<=pProbe->nSampleCol) && ((eOp & WO_IN)==0 || ExprUseXList(pTerm->pExpr)) && OptimizationEnabled(db, SQLITE_Stat4) @@ -3174,8 +3174,8 @@ static int whereLoopAddBtreeIndex( { pNew->nOut += (pProbe->aiRowLogEst[nEq] - pProbe->aiRowLogEst[nEq-1]); if( eOp & WO_ISNULL ){ - /* TUNING: If there is no likelihood() value, assume that a - ** "col IS NULL" expression matches twice as many rows + /* TUNING: If there is no likelihood() value, assume that a + ** "col IS NULL" expression matches twice as many rows ** as (col=?). */ pNew->nOut += 10; } @@ -3190,7 +3190,7 @@ static int whereLoopAddBtreeIndex( assert( pSrc->pTab->szTabRow>0 ); if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ /* The pProbe->szIdxRow is low for an IPK table since the interior - ** pages are small. Thuse szIdxRow gives a good estimate of seek cost. + ** pages are small. Thus szIdxRow gives a good estimate of seek cost. ** But the leaf pages are full-size, so pProbe->szIdxRow would badly ** under-estimate the scanning cost. */ rCostIdx = pNew->nOut + 16; @@ -3241,12 +3241,12 @@ static int whereLoopAddBtreeIndex( /* Consider using a skip-scan if there are no WHERE clause constraints ** available for the left-most terms of the index, and if the average - ** number of repeats in the left-most terms is at least 18. + ** number of repeats in the left-most terms is at least 18. ** ** The magic number 18 is selected on the basis that scanning 17 rows ** is almost always quicker than an index seek (even though if the index ** contains fewer than 2^17 rows we assume otherwise in other parts of - ** the code). And, even if it is not, it should not be too much slower. + ** the code). And, even if it is not, it should not be too much slower. ** On the other hand, the extra seeks could end up being significantly ** more expensive. */ assert( 42==sqlite3LogEst(18) ); @@ -3393,7 +3393,7 @@ struct CoveringIndexCheck { ** all columns less than 63 (really BMS-1) are covered, so we don't need ** to check them. But we do need to check any column at 63 or greater. ** -** If the index does not cover the column, then set pWalk->eCode to +** If the index does not cover the column, then set pWalk->eCode to ** non-zero and return WRC_Abort to stop the search. ** ** If this node does not disprove that the index can be a covering index, @@ -3511,18 +3511,18 @@ static SQLITE_NOINLINE u32 whereIsCoveringIndex( ** cost = nRow * K // scan of covering index ** cost = nRow * (K+3.0) // scan of non-covering index ** -** where K is a value between 1.1 and 3.0 set based on the relative +** where K is a value between 1.1 and 3.0 set based on the relative ** estimated average size of the index and table records. ** ** For an index scan, where nVisit is the number of index rows visited -** by the scan, and nSeek is the number of seek operations required on +** by the scan, and nSeek is the number of seek operations required on ** the index b-tree: ** ** cost = nSeek * (log(nRow) + K * nVisit) // covering index ** cost = nSeek * (log(nRow) + (K+3.0) * nVisit) // non-covering index ** -** Normally, nSeek is 1. nSeek values greater than 1 come about if the -** WHERE clause includes "x IN (....)" terms used in place of "x=?". Or when +** Normally, nSeek is 1. nSeek values greater than 1 come about if the +** WHERE clause includes "x IN (....)" terms used in place of "x=?". Or when ** implicit "x IN (SELECT x FROM tbl)" terms are added for skip-scans. ** ** The estimated values (nRow, nVisit, nSeek) often contain a large amount @@ -3535,7 +3535,7 @@ static SQLITE_NOINLINE u32 whereIsCoveringIndex( */ static int whereLoopAddBtree( WhereLoopBuilder *pBuilder, /* WHERE clause information */ - Bitmask mPrereq /* Extra prerequesites for using this table */ + Bitmask mPrereq /* Extra prerequisites for using this table */ ){ WhereInfo *pWInfo; /* WHERE analysis context */ Index *pProbe; /* An index we are evaluating */ @@ -3551,7 +3551,7 @@ static int whereLoopAddBtree( LogEst rSize; /* number of rows in the table */ WhereClause *pWC; /* The parsed WHERE clause */ Table *pTab; /* Table being queried */ - + pNew = pBuilder->pNew; pWInfo = pBuilder->pWInfo; pTabList = pWInfo->pTabList; @@ -3649,9 +3649,9 @@ static int whereLoopAddBtree( } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ - /* Loop over all indices. If there was an INDEXED BY clause, then only + /* Loop over all indices. If there was an INDEXED BY clause, then only ** consider index pProbe. */ - for(; rc==SQLITE_OK && pProbe; + for(; rc==SQLITE_OK && pProbe; pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++ ){ if( pProbe->pPartIdxWhere!=0 @@ -3788,7 +3788,7 @@ static int whereLoopAddBtree( if( pTerm->eOperator & (WO_EQ|WO_IS) ) nLookup -= 19; } } - + pNew->rRun = sqlite3LogEstAdd(pNew->rRun, nLookup); } ApplyCostMultiplier(pNew->rRun, pTab->costMult); @@ -3831,7 +3831,7 @@ static int whereLoopAddBtree( */ static int isLimitTerm(WhereTerm *pTerm){ assert( pTerm->eOperator==WO_AUX || pTerm->eMatchOp==0 ); - return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT + return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; } @@ -3881,13 +3881,13 @@ static int whereLoopAddVirtualOne( *pbIn = 0; pNew->prereq = mPrereq; - /* Set the usable flag on the subset of constraints identified by + /* Set the usable flag on the subset of constraints identified by ** arguments mUsable and mExclude. */ pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; for(i=0; ia[pIdxCons->iTermOffset]; pIdxCons->usable = 0; - if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight + if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight && (pTerm->eOperator & mExclude)==0 && (pbRetryLimit || !isLimitTerm(pTerm)) ){ @@ -3962,7 +3962,7 @@ static int whereLoopAddVirtualOne( pNew->u.vtab.bOmitOffset = 1; } } - if( SMASKBIT32(i) & pHidden->mHandleIn ){ + if( SMASKBIT32(i) & pHidden->mHandleIn ){ pNew->u.vtab.mHandleIn |= MASKBIT32(iTerm); }else if( (pTerm->eOperator & WO_IN)!=0 ){ /* A virtual table that is constrained by an IN clause may not @@ -4042,7 +4042,7 @@ static int whereLoopAddVirtualOne( ** ** Return a pointer to the collation name: ** -** 1. If there is an explicit COLLATE operator on the constaint, return it. +** 1. If there is an explicit COLLATE operator on the constraint, return it. ** ** 2. Else, if the column has an alternative collation, return that. ** @@ -4072,7 +4072,7 @@ int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; u32 m = SMASKBIT32(iCons); if( m & pHidden->mIn ){ - if( bHandle==0 ){ + if( bHandle==0 ){ pHidden->mHandleIn &= ~m; }else if( bHandle>0 ){ pHidden->mHandleIn |= m; @@ -4085,7 +4085,7 @@ int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ /* ** This interface is callable from within the xBestIndex callback only. ** -** If possible, set (*ppVal) to point to an object containing the value +** If possible, set (*ppVal) to point to an object containing the value ** on the right-hand-side of constraint iCons. */ int sqlite3_vtab_rhs_value( @@ -4158,8 +4158,8 @@ void sqlite3VtabUsesAllSchemas(Parse *pParse){ ** entries that occur before the virtual table in the FROM clause and are ** separated from it by at least one LEFT or CROSS JOIN. Similarly, the ** mUnusable mask contains all FROM clause entries that occur after the -** virtual table and are separated from it by at least one LEFT or -** CROSS JOIN. +** virtual table and are separated from it by at least one LEFT or +** CROSS JOIN. ** ** For example, if the query were: ** @@ -4167,9 +4167,9 @@ void sqlite3VtabUsesAllSchemas(Parse *pParse){ ** ** then mPrereq corresponds to (t1, t2) and mUnusable to (t5, t6). ** -** All the tables in mPrereq must be scanned before the current virtual -** table. So any terms for which all prerequisites are satisfied by -** mPrereq may be specified as "usable" in all calls to xBestIndex. +** All the tables in mPrereq must be scanned before the current virtual +** table. So any terms for which all prerequisites are satisfied by +** mPrereq may be specified as "usable" in all calls to xBestIndex. ** Conversely, all tables in mUnusable must be scanned after the current ** virtual table, so any terms for which the prerequisites overlap with ** mUnusable should always be configured as "not-usable" for xBestIndex. @@ -4226,7 +4226,7 @@ static int whereLoopAddVirtual( /* If the call to xBestIndex() with all terms enabled produced a plan ** that does not require any source tables (IOW: a plan with mBest==0) - ** and does not use an IN(...) operator, then there is no point in making + ** and does not use an IN(...) operator, then there is no point in making ** any further calls to xBestIndex() since they will all return the same ** result (if the xBestIndex() implementation is sane). */ if( rc==SQLITE_OK && ((mBest = (pNew->prereq & ~mPrereq))!=0 || bIn) ){ @@ -4249,7 +4249,7 @@ static int whereLoopAddVirtual( } } - /* Call xBestIndex once for each distinct value of (prereqRight & ~mPrereq) + /* Call xBestIndex once for each distinct value of (prereqRight & ~mPrereq) ** in the set of terms that apply to the current virtual table. */ while( rc==SQLITE_OK ){ int i; @@ -4306,8 +4306,8 @@ static int whereLoopAddVirtual( ** btrees or virtual tables. */ static int whereLoopAddOr( - WhereLoopBuilder *pBuilder, - Bitmask mPrereq, + WhereLoopBuilder *pBuilder, + Bitmask mPrereq, Bitmask mUnusable ){ WhereInfo *pWInfo = pBuilder->pWInfo; @@ -4320,7 +4320,7 @@ static int whereLoopAddOr( WhereLoopBuilder sSubBuild; WhereOrSet sSum, sCur; SrcItem *pItem; - + pWC = pBuilder->pWC; pWCEnd = pWC->a + pWC->nTerm; pNew = pBuilder->pNew; @@ -4333,14 +4333,14 @@ static int whereLoopAddOr( for(pTerm=pWC->a; pTermeOperator & WO_OR)!=0 - && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 + && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 ){ WhereClause * const pOrWC = &pTerm->u.pOrInfo->wc; WhereTerm * const pOrWCEnd = &pOrWC->a[pOrWC->nTerm]; WhereTerm *pOrTerm; int once = 1; int i, j; - + sSubBuild = *pBuilder; sSubBuild.pOrSet = &sCur; @@ -4361,7 +4361,7 @@ static int whereLoopAddOr( } sCur.n = 0; #ifdef WHERETRACE_ENABLED - WHERETRACE(0x400, ("OR-term %d of %p has %d subterms:\n", + WHERETRACE(0x400, ("OR-term %d of %p has %d subterms:\n", (int)(pOrTerm-pOrWC->a), pTerm, sSubBuild.pWC->nTerm)); if( sqlite3WhereTrace & 0x20000 ){ sqlite3WhereClausePrint(sSubBuild.pWC); @@ -4409,8 +4409,8 @@ static int whereLoopAddOr( /* TUNING: Currently sSum.a[i].rRun is set to the sum of the costs ** of all sub-scans required by the OR-scan. However, due to rounding ** errors, it may be that the cost of the OR-scan is equal to its - ** most expensive sub-scan. Add the smallest possible penalty - ** (equivalent to multiplying the cost by 1.07) to ensure that + ** most expensive sub-scan. Add the smallest possible penalty + ** (equivalent to multiplying the cost by 1.07) to ensure that ** this does not happen. Otherwise, for WHERE clauses such as the ** following where there is an index on "y": ** @@ -4430,7 +4430,7 @@ static int whereLoopAddOr( } /* -** Add all WhereLoop objects for all tables +** Add all WhereLoop objects for all tables */ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ WhereInfo *pWInfo = pBuilder->pWInfo; @@ -4462,7 +4462,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ pNew->iTab = iTab; pBuilder->iPlanLimit += SQLITE_QUERY_PLANNER_LIMIT_INCR; pNew->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor); - if( bFirstPastRJ + if( bFirstPastRJ || (pItem->fg.jointype & (JT_OUTER|JT_CROSS|JT_LTORJ))!=0 ){ /* Add prerequisites to prevent reordering of FROM clause terms @@ -4518,17 +4518,17 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ ** Examine a WherePath (with the addition of the extra WhereLoop of the 6th ** parameters) to see if it outputs rows in the requested ORDER BY ** (or GROUP BY) without requiring a separate sort operation. Return N: -** +** ** N>0: N terms of the ORDER BY clause are satisfied ** N==0: No terms of the ORDER BY clause are satisfied -** N<0: Unknown yet how many terms of ORDER BY might be satisfied. +** N<0: Unknown yet how many terms of ORDER BY might be satisfied. ** ** Note that processing for WHERE_GROUPBY and WHERE_DISTINCTBY is not as ** strict. With GROUP BY and DISTINCT the only requirement is that ** equivalent rows appear immediately adjacent to one another. GROUP BY ** and DISTINCT do not require rows to appear in any particular order as long ** as equivalent rows are grouped together. Thus for GROUP BY and DISTINCT -** the pOrderBy terms can be matched in any order. With ORDER BY, the +** the pOrderBy terms can be matched in any order. With ORDER BY, the ** pOrderBy terms must be matched in strict left-to-right order. */ static i8 wherePathSatisfiesOrderBy( @@ -4578,7 +4578,7 @@ static i8 wherePathSatisfiesOrderBy( ** row of the WhereLoop. Every one-row WhereLoop is automatically ** order-distinct. A WhereLoop that has no columns in the ORDER BY clause ** is not order-distinct. To be order-distinct is not quite the same as being - ** UNIQUE since a UNIQUE column or index can have multiple rows that + ** UNIQUE since a UNIQUE column or index can have multiple rows that ** are NULL and NULL values are equivalent for the purpose of order-distinct. ** To be order-distinct, the columns must be UNIQUE and NOT NULL. ** @@ -4610,7 +4610,7 @@ static i8 wherePathSatisfiesOrderBy( pLoop = pLast; } if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ - if( pLoop->u.vtab.isOrdered + if( pLoop->u.vtab.isOrdered && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) ){ obSat = obDone; @@ -4636,10 +4636,10 @@ static i8 wherePathSatisfiesOrderBy( ~ready, eqOpMask, 0); if( pTerm==0 ) continue; if( pTerm->eOperator==WO_IN ){ - /* IN terms are only valid for sorting in the ORDER BY LIMIT + /* IN terms are only valid for sorting in the ORDER BY LIMIT ** optimization, and then only if they are actually used ** by the query plan */ - assert( wctrlFlags & + assert( wctrlFlags & (WHERE_ORDERBY_LIMIT|WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX) ); for(j=0; jnLTerm && pTerm!=pLoop->aLTerm[j]; j++){} if( j>=pLoop->nLTerm ) continue; @@ -4686,7 +4686,7 @@ static i8 wherePathSatisfiesOrderBy( for(j=0; j=pLoop->u.btree.nEq + assert( j>=pLoop->u.btree.nEq || (pLoop->aLTerm[j]==0)==(jnSkip) ); if( ju.btree.nEq && j>=pLoop->nSkip ){ @@ -4698,7 +4698,7 @@ static i8 wherePathSatisfiesOrderBy( ** the loop need to be marked as not order-distinct because it can ** have repeated NULL rows. ** - ** If the current term is a column of an ((?,?) IN (SELECT...)) + ** If the current term is a column of an ((?,?) IN (SELECT...)) ** expression for which the SELECT returns more than one column, ** check that it is the only column used by this loop. Otherwise, ** if it is one of two or more, none of the columns can be @@ -4711,7 +4711,7 @@ static i8 wherePathSatisfiesOrderBy( testcase( isOrderDistinct ); isOrderDistinct = 0; } - continue; + continue; }else if( ALWAYS(eOp & WO_IN) ){ /* ALWAYS() justification: eOp is an equality operator due to the ** ju.btree.nEq constraint above. Any equality other @@ -4753,10 +4753,10 @@ static i8 wherePathSatisfiesOrderBy( if( iColumn==XN_EXPR ){ isOrderDistinct = 0; } - } + } /* Find the ORDER BY term that corresponds to the j-th column - ** of the index and mark that ORDER BY term off + ** of the index and mark that ORDER BY term off */ isMatch = 0; for(i=0; bOnce && ia[i].fg.sortFlags&KEYINFO_ORDER_DESC) ){ isMatch = 0; @@ -4899,7 +4899,7 @@ static const char *wherePathName(WherePath *pPath, int nLoop, WhereLoop *pLast){ #endif /* -** Return the cost of sorting nRow rows, assuming that the keys have +** Return the cost of sorting nRow rows, assuming that the keys have ** nOrderby columns and that the first nSorted columns are already in ** order. */ @@ -4909,13 +4909,13 @@ static LogEst whereSortingCost( int nOrderBy, /* Number of ORDER BY clause terms */ int nSorted /* Number of initial ORDER BY terms naturally in order */ ){ - /* Estimated cost of a full external sort, where N is + /* Estimated cost of a full external sort, where N is ** the number of rows to sort is: ** ** cost = (K * N * log(N)). - ** - ** Or, if the order-by clause has X terms but only the last Y - ** terms are out of order, then block-sorting will reduce the + ** + ** Or, if the order-by clause has X terms but only the last Y + ** terms are out of order, then block-sorting will reduce the ** sorting cost to: ** ** cost = (K * N * log(N)) * (Y/X) @@ -4923,8 +4923,8 @@ static LogEst whereSortingCost( ** The constant K is at least 2.0 but will be larger if there are a ** large number of columns to be sorted, as the sorting time is ** proportional to the amount of content to be sorted. The algorithm - ** does not currently distinguish between fat columns (BLOBs and TEXTs) - ** and skinny columns (INTs). It just uses the number of columns as + ** does not currently distinguish between fat columns (BLOBs and TEXTs) + ** and skinny columns (INTs). It just uses the number of columns as ** an approximation for the row width. ** ** And extra factor of 2.0 or 3.0 is added to the sorting cost if the sort @@ -5003,7 +5003,8 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** For joins of 3 or more tables, track the 10 best paths */ mxChoice = (nLoop<=1) ? 1 : (nLoop==2 ? 5 : 10); assert( nLoop<=pWInfo->pTabList->nSrc ); - WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d)\n", nRowEst)); + WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d, nQueryLoop=%d)\n", + nRowEst, pParse->nQueryLoop)); /* If nRowEst is zero and there is an ORDER BY clause, ignore it. In this ** case the purpose of this call is to estimate the number of rows returned @@ -5033,7 +5034,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** space for the aSortCost[] array. Each element of the aSortCost array ** is either zero - meaning it has not yet been initialized - or the ** cost of sorting nRowEst rows of data where the first X terms of - ** the ORDER BY clause are already in order, where X is the array + ** the ORDER BY clause are already in order, where X is the array ** index. */ aSortCost = (LogEst*)pX; memset(aSortCost, 0, sizeof(LogEst) * nOrderBy); @@ -5054,7 +5055,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** in this case the query may return a maximum of one row, the results ** are already in the requested order. Set isOrdered to nOrderBy to ** indicate this. Or, if nLoop is greater than zero, set isOrdered to - ** -1, indicating that the result set may or may not be ordered, + ** -1, indicating that the result set may or may not be ordered, ** depending on the loops added to the current plan. */ aFrom[0].isOrdered = nLoop>0 ? -1 : nOrderBy; } @@ -5084,7 +5085,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ continue; } - /* At this point, pWLoop is a candidate to be the next loop. + /* At this point, pWLoop is a candidate to be the next loop. ** Compute its cost */ rUnsorted = sqlite3LogEstAdd(pWLoop->rSetup,pWLoop->rRun + pFrom->nRow); rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted); @@ -5106,14 +5107,14 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ); } /* TUNING: Add a small extra penalty (3) to sorting as an - ** extra encouragment to the query planner to select a plan + ** extra encouragement to the query planner to select a plan ** where the rows emerge in the correct order without any sorting ** required. */ rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 3; WHERETRACE(0x002, ("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n", - aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy, + aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy, rUnsorted, rCost)); }else{ rCost = rUnsorted; @@ -5122,9 +5123,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ /* TUNING: A full-scan of a VIEW or subquery in the outer loop ** is not so bad. */ - if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 ){ + if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 && nLoop>1 ){ rCost += -10; nOut += -30; + WHERETRACE(0x80,("VIEWSCAN cost reduction for %c\n",pWLoop->cId)); } /* Check to see if pWLoop should be added to the set of @@ -5185,11 +5187,11 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** same set of loops and has the same isOrdered setting as the ** candidate path. Check to see if the candidate should replace ** pTo or if the candidate should be skipped. - ** + ** ** The conditional is an expanded vector comparison equivalent to: ** (pTo->rCost,pTo->nRow,pTo->rUnsorted) <= (rCost,nOut,rUnsorted) */ - if( pTo->rCostrCostrCost==rCost && (pTo->nRownRow==nOut && pTo->rUnsorted<=rUnsorted) @@ -5240,8 +5242,8 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ mxCost = aTo[0].rCost; mxUnsorted = aTo[0].nRow; for(jj=1, pTo=&aTo[1]; jjrCost>mxCost - || (pTo->rCost==mxCost && pTo->rUnsorted>mxUnsorted) + if( pTo->rCost>mxCost + || (pTo->rCost==mxCost && pTo->rUnsorted>mxUnsorted) ){ mxCost = pTo->rCost; mxUnsorted = pTo->rUnsorted; @@ -5280,7 +5282,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ sqlite3StackFreeNN(pParse->db, pSpace); return SQLITE_ERROR; } - + /* Find the lowest cost path. pFrom will be left pointing to that path */ pFrom = aFrom; for(ii=1; iinOBSat = 0; if( nLoop>0 ){ u32 wsFlags = pFrom->aLoop[nLoop-1]->wsFlags; - if( (wsFlags & WHERE_ONEROW)==0 + if( (wsFlags & WHERE_ONEROW)==0 && (wsFlags&(WHERE_IPK|WHERE_COLUMN_IN))!=(WHERE_IPK|WHERE_COLUMN_IN) ){ Bitmask m = 0; @@ -5348,7 +5350,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ && pWInfo->nOBSat==pWInfo->pOrderBy->nExpr && nLoop>0 ){ Bitmask revMask = 0; - int nOrder = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, + int nOrder = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, 0, nLoop-1, pFrom->aLoop[nLoop-1], &revMask ); assert( pWInfo->sorted==0 ); @@ -5375,7 +5377,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** times for the common case. ** ** Return non-zero on success, if this query can be handled by this -** no-frills query planner. Return zero if this query needs the +** no-frills query planner. Return zero if this query needs the ** general-purpose query planner. */ static int whereShortCut(WhereLoopBuilder *pBuilder){ @@ -5421,8 +5423,8 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ int opMask; assert( pLoop->aLTermSpace==pLoop->aLTerm ); if( !IsUniqueIndex(pIdx) - || pIdx->pPartIdxWhere!=0 - || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) + || pIdx->pPartIdxWhere!=0 + || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) ) continue; opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ; for(j=0; jnKeyCol; j++){ @@ -5482,8 +5484,8 @@ static int exprNodeIsDeterministic(Walker *pWalker, Expr *pExpr){ } /* -** Return true if the expression contains no non-deterministic SQL -** functions. Do not consider non-deterministic SQL functions that are +** Return true if the expression contains no non-deterministic SQL +** functions. Do not consider non-deterministic SQL functions that are ** part of sub-select statements. */ static int exprIsDeterministic(Expr *p){ @@ -5496,7 +5498,7 @@ static int exprIsDeterministic(Expr *p){ return w.eCode; } - + #ifdef WHERETRACE_ENABLED /* ** Display all WhereLoops in pWInfo @@ -5524,7 +5526,7 @@ static void showAllWhereLoops(WhereInfo *pWInfo, WhereClause *pWC){ ** 1) The query must not be an aggregate. ** 2) The table must be the RHS of a LEFT JOIN. ** 3) Either the query must be DISTINCT, or else the ON or USING clause -** must contain a constraint that limits the scan of the table to +** must contain a constraint that limits the scan of the table to ** at most a single row. ** 4) The table must not be referenced by any part of the query apart ** from its own USING or ON clause. @@ -5544,13 +5546,13 @@ static void showAllWhereLoops(WhereInfo *pWInfo, WhereClause *pWC){ ** ** then table t2 can be omitted from the following: ** -** SELECT v1, v3 FROM t1 +** SELECT v1, v3 FROM t1 ** LEFT JOIN t2 ON (t1.ipk=t2.ipk) ** LEFT JOIN t3 ON (t1.ipk=t3.ipk) ** ** or from: ** -** SELECT DISTINCT v1, v3 FROM t1 +** SELECT DISTINCT v1, v3 FROM t1 ** LEFT JOIN t2 ** LEFT JOIN t3 ON (t1.ipk=t3.ipk) */ @@ -5756,6 +5758,28 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( } } +/* +** Set the reverse-scan order mask to one for all tables in the query +** with the exception of MATERIALIZED common table expressions that have +** their own internal ORDER BY clauses. +** +** This implements the PRAGMA reverse_unordered_selects=ON setting. +** (Also SQLITE_DBCONFIG_REVERSE_SCANORDER). +*/ +static SQLITE_NOINLINE void whereReverseScanOrder(WhereInfo *pWInfo){ + int ii; + for(ii=0; iipTabList->nSrc; ii++){ + SrcItem *pItem = &pWInfo->pTabList->a[ii]; + if( !pItem->fg.isCte + || pItem->u2.pCteUse->eM10d!=M10d_Yes + || NEVER(pItem->pSelect==0) + || pItem->pSelect->pOrderBy==0 + ){ + pWInfo->revMask |= MASKBIT(ii); + } + } +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -5814,7 +5838,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( ** ** OUTER JOINS ** -** An outer join of tables t1 and t2 is conceptally coded as follows: +** An outer join of tables t1 and t2 is conceptually coded as follows: ** ** foreach row1 in t1 do ** flag = 0 @@ -5836,7 +5860,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( ** if there is one. If there is no ORDER BY clause or if this routine ** is called from an UPDATE or DELETE statement, then pOrderBy is NULL. ** -** The iIdxCur parameter is the cursor number of an index. If +** The iIdxCur parameter is the cursor number of an index. If ** WHERE_OR_SUBCLAUSE is set, iIdxCur is the cursor number of an index ** to use for OR clause processing. The WHERE clause should use this ** specific cursor. If WHERE_ONEPASS_DESIRED is set, then iIdxCur is @@ -5870,8 +5894,8 @@ WhereInfo *sqlite3WhereBegin( u8 bFordelete = 0; /* OPFLAG_FORDELETE or zero, as appropriate */ assert( (wctrlFlags & WHERE_ONEPASS_MULTIROW)==0 || ( - (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 - && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 )); /* Only one of WHERE_OR_SUBCLAUSE or WHERE_USE_LIMIT */ @@ -5887,7 +5911,7 @@ WhereInfo *sqlite3WhereBegin( if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0; /* The number of tables in the FROM clause is limited by the number of - ** bits in a Bitmask + ** bits in a Bitmask */ testcase( pTabList->nSrc==BMS ); if( pTabList->nSrc>BMS ){ @@ -5895,7 +5919,7 @@ WhereInfo *sqlite3WhereBegin( return 0; } - /* This function normally generates a nested loop for all tables in + /* This function normally generates a nested loop for all tables in ** pTabList. But if the WHERE_OR_SUBCLAUSE flag is set, then we should ** only generate code for the first table in pTabList and assume that ** any cursors associated with subsequent tables are uninitialized. @@ -5930,7 +5954,7 @@ WhereInfo *sqlite3WhereBegin( pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; pWInfo->pSelect = pSelect; - memset(&pWInfo->nOBSat, 0, + memset(&pWInfo->nOBSat, 0, offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ @@ -5953,7 +5977,7 @@ WhereInfo *sqlite3WhereBegin( */ sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo); sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND); - + /* Special case: No FROM clause */ if( nTabList==0 ){ @@ -5969,7 +5993,7 @@ WhereInfo *sqlite3WhereBegin( ** ** The N-th term of the FROM clause is assigned a bitmask of 1<sWC); if( pSelect && pSelect->pLimit ){ @@ -6099,7 +6123,7 @@ WhereInfo *sqlite3WhereBegin( ** loops will be built using the revised truthProb values. */ if( sWLB.bldFlags2 & SQLITE_BLDF2_2NDPASS ){ WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); - WHERETRACE(0xffffffff, + WHERETRACE(0xffffffff, ("**** Redo all loop computations due to" " TERM_HIGHTRUTH changes ****\n")); while( pWInfo->pLoops ){ @@ -6112,7 +6136,7 @@ WhereInfo *sqlite3WhereBegin( } #endif WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); - + wherePathSolver(pWInfo, 0); if( db->mallocFailed ) goto whereBeginError; if( pWInfo->pOrderBy ){ @@ -6120,8 +6144,9 @@ WhereInfo *sqlite3WhereBegin( if( db->mallocFailed ) goto whereBeginError; } } + assert( pWInfo->pTabList!=0 ); if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ - pWInfo->revMask = ALLBITS; + whereReverseScanOrder(pWInfo); } if( pParse->nErr ){ goto whereBeginError; @@ -6160,7 +6185,7 @@ WhereInfo *sqlite3WhereBegin( ** This query optimization is factored out into a separate "no-inline" ** procedure to keep the sqlite3WhereBegin() procedure from becoming ** too large. If sqlite3WhereBegin() becomes too large, that prevents - ** some C-compiler optimizers from in-lining the + ** some C-compiler optimizers from in-lining the ** sqlite3WhereCodeOneLoopStart() procedure, and it is important to ** in-line sqlite3WhereCodeOneLoopStart() for performance reasons. */ @@ -6221,6 +6246,7 @@ WhereInfo *sqlite3WhereBegin( 0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW) && !IsVirtual(pTabList->a[0].pTab) && (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK)) + && OptimizationEnabled(db, SQLITE_OnePass) )){ pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI; if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){ @@ -6269,7 +6295,7 @@ WhereInfo *sqlite3WhereBegin( assert( pTabItem->iCursor==pLevel->iTabCur ); testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS-1 ); testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS ); - if( pWInfo->eOnePass==ONEPASS_OFF + if( pWInfo->eOnePass==ONEPASS_OFF && pTab->nColtabFlags & (TF_HasGenerated|TF_WithoutRowid))==0 && (pLoop->wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))==0 @@ -6498,7 +6524,7 @@ static int cursorIsOpen(Vdbe *v, int iCur, int k){ #endif /* SQLITE_DEBUG */ /* -** Generate the end of the WHERE loop. See comments on +** Generate the end of the WHERE loop. See comments on ** sqlite3WhereBegin() for additional information. */ void sqlite3WhereEnd(WhereInfo *pWInfo){ @@ -6584,7 +6610,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeJumpHere(v, pIn->addrInTop+1); if( pIn->eEndLoopOp!=OP_Noop ){ if( pIn->nPrefix ){ - int bEarlyOut = + int bEarlyOut = (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && (pLoop->wsFlags & WHERE_IN_EARLYOUT)!=0; if( pLevel->iLeftJoin ){ @@ -6596,7 +6622,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ ** return the null-row. So, if the cursor is not open yet, ** jump over the OP_Next or OP_Prev instruction about to ** be coded. */ - sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur, + sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur, sqlite3VdbeCurrentAddr(v) + 2 + bEarlyOut); VdbeCoverage(v); } @@ -6605,7 +6631,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeCurrentAddr(v)+2, pIn->iBase, pIn->nPrefix); VdbeCoverage(v); - /* Retarget the OP_IsNull against the left operand of IN so + /* Retarget the OP_IsNull against the left operand of IN so ** it jumps past the OP_IfNoHope. This is because the ** OP_IsNull also bypasses the OP_Affinity opcode that is ** required by OP_IfNoHope. */ @@ -6646,8 +6672,8 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor ); sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } - if( (ws & WHERE_INDEXED) - || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) + if( (ws & WHERE_INDEXED) + || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) ){ if( ws & WHERE_MULTI_OR ){ Index *pIx = pLevel->u.pCoveringIdx; @@ -6702,7 +6728,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ ** from the index instead of from the table where possible. In some cases ** this optimization prevents the table from ever being read, which can ** yield a significant performance boost. - ** + ** ** Calls to the code generator in between sqlite3WhereBegin and ** sqlite3WhereEnd will have created code that references the table ** directly. This loop scans all that code looking for opcodes diff --git a/src/whereInt.h b/src/whereInt.h index b89a4513e..759e774e3 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -37,7 +37,7 @@ typedef struct WhereRightJoin WhereRightJoin; /* ** This object is a header on a block of allocated memory that will be -** automatically freed when its WInfo oject is destructed. +** automatically freed when its WInfo object is destructed. */ struct WhereMemBlock { WhereMemBlock *pNext; /* Next block in the chain */ @@ -98,7 +98,7 @@ struct WhereLevel { int iCur; /* The VDBE cursor used by this IN operator */ int addrInTop; /* Top of the IN loop */ int iBase; /* Base register of multi-key index record */ - int nPrefix; /* Number of prior entires in the key */ + int nPrefix; /* Number of prior entries in the key */ u8 eEndLoopOp; /* IN Loop terminator. OP_Next or OP_Prev */ } *aInLoop; /* Information about each nested IN operator */ } in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */ @@ -167,7 +167,7 @@ struct WhereLoop { /* This object holds the prerequisites and the cost of running a ** subquery on one operand of an OR operator in the WHERE clause. -** See WhereOrSet for additional information +** See WhereOrSet for additional information */ struct WhereOrCost { Bitmask prereq; /* Prerequisites */ @@ -219,7 +219,7 @@ struct WherePath { ** clause subexpression is separated from the others by AND operators, ** usually, or sometimes subexpressions separated by OR. ** -** All WhereTerms are collected into a single WhereClause structure. +** All WhereTerms are collected into a single WhereClause structure. ** The following identity holds: ** ** WhereTerm.pWC->a[WhereTerm.idx] == WhereTerm @@ -348,7 +348,7 @@ struct WhereClause { int nTerm; /* Number of terms */ int nSlot; /* Number of entries in a[] */ int nBase; /* Number of terms through the last non-Virtual */ - WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */ + WhereTerm *a; /* Each a[] describes a term of the WHERE clause */ #if defined(SQLITE_SMALL_STACK) WhereTerm aStatic[1]; /* Initial static space for a[] */ #else @@ -377,8 +377,8 @@ struct WhereAndInfo { ** An instance of the following structure keeps track of a mapping ** between VDBE cursor numbers and bits of the bitmasks in WhereTerm. ** -** The VDBE cursor numbers are small integers contained in -** SrcItem.iCursor and Expr.iTable fields. For any given WHERE +** The VDBE cursor numbers are small integers contained in +** SrcItem.iCursor and Expr.iTable fields. For any given WHERE ** clause, the cursor numbers might not begin with 0 and they might ** contain gaps in the numbering sequence. But we want to make maximum ** use of the bits in our bitmasks. This structure provides a mapping diff --git a/src/wherecode.c b/src/wherecode.c index a998c0a4f..47ce36ce3 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -71,7 +71,7 @@ static void explainAppendTerm( } /* -** Argument pLevel describes a strategy for scanning table pTab. This +** Argument pLevel describes a strategy for scanning table pTab. This ** function appends text to pStr that describes the subset of table ** rows scanned by the strategy in the form of an SQL expression. ** @@ -111,7 +111,7 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){ /* ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN -** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG +** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG ** was defined at compile-time. If it is not a no-op, a single OP_Explain ** opcode is added to the output to describe the table scan strategy in pLevel. ** @@ -279,11 +279,11 @@ int sqlite3WhereExplainBloomFilter( #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Configure the VM passed as the first argument with an -** sqlite3_stmt_scanstatus() entry corresponding to the scan used to -** implement level pLvl. Argument pSrclist is a pointer to the FROM +** sqlite3_stmt_scanstatus() entry corresponding to the scan used to +** implement level pLvl. Argument pSrclist is a pointer to the FROM ** clause that the scan reads data from. ** -** If argument addrExplain is not 0, it must be the address of an +** If argument addrExplain is not 0, it must be the address of an ** OP_Explain instruction that describes the same loop. */ void sqlite3WhereAddScanStatus( @@ -315,6 +315,12 @@ void sqlite3WhereAddScanStatus( if( wsFlags & WHERE_INDEXED ){ sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); } + }else{ + int addr = pSrclist->a[pLvl->iFrom].addrFillSub; + VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine ); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr ); + sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1); } } } @@ -354,7 +360,7 @@ void sqlite3WhereAddScanStatus( ** ** Only the parent term was in the original WHERE clause. The child1 ** and child2 terms were added by the LIKE optimization. If both of -** the virtual child terms are valid, then testing of the parent can be +** the virtual child terms are valid, then testing of the parent can be ** skipped. ** ** Usually the parent term is marked as TERM_CODED. But if the parent @@ -392,7 +398,7 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ /* ** Code an OP_Affinity opcode to apply the column affinity string zAff -** to the n registers starting at base. +** to the n registers starting at base. ** ** As an optimization, SQLITE_AFF_BLOB and SQLITE_AFF_NONE entries (which ** are no-ops) at the beginning and end of zAff are ignored. If all entries @@ -429,7 +435,7 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){ } /* -** Expression pRight, which is the RHS of a comparison operation, is +** Expression pRight, which is the RHS of a comparison operation, is ** either a vector of n elements or, if n==1, a scalar expression. ** Before the comparison operation, affinity zAff is to be applied ** to the pRight values. This function modifies characters within the @@ -540,12 +546,12 @@ static Expr *removeUnindexableInClauseTerms( pNew->pLeft = p; } if( pSelect->pOrderBy ){ - /* If the SELECT statement has an ORDER BY clause, zero the - ** iOrderByCol variables. These are set to non-zero when an - ** ORDER BY term exactly matches one of the terms of the + /* If the SELECT statement has an ORDER BY clause, zero the + ** iOrderByCol variables. These are set to non-zero when an + ** ORDER BY term exactly matches one of the terms of the ** result-set. Since the result-set of the SELECT statement may - ** have been modified or reordered, these variables are no longer - ** set correctly. Since setting them is just an optimization, + ** have been modified or reordered, these variables are no longer + ** set correctly. Since setting them is just an optimization, ** it's easiest just to zero them here. */ ExprList *pOrderBy = pSelect->pOrderBy; for(i=0; inExpr; i++){ @@ -567,7 +573,7 @@ static Expr *removeUnindexableInClauseTerms( /* ** Generate code for a single equality term of the WHERE clause. An equality -** term can be either X=expr or X IN (...). pTerm is the term to be +** term can be either X=expr or X IN (...). pTerm is the term to be ** coded. ** ** The current value for the constraint is left in a register, the index @@ -708,7 +714,7 @@ static int codeEqualityTerm( testcase( iEq>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 && (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ); - if( iEq>0 + if( iEq>0 && (pLoop->wsFlags & (WHERE_IN_SEEKSCAN|WHERE_VIRTUALTABLE))==0 ){ sqlite3VdbeAddOp3(v, OP_SeekHit, pLevel->iIdxCur, 0, iEq); @@ -745,7 +751,7 @@ static int codeEqualityTerm( ** For example, consider table t1(a,b,c,d,e,f) with index i1(a,b,c). ** Suppose the WHERE clause is this: a==5 AND b IN (1,2,3) AND c>5 AND c<10 ** The index has as many as three equality constraints, but in this -** example, the third "c" value is an inequality. So only two +** example, the third "c" value is an inequality. So only two ** constraints are coded. This routine will generate code to evaluate ** a==5 and b IN (1,2,3). The current values for a and b will be stored ** in consecutive registers and the index of the first register is returned. @@ -812,7 +818,7 @@ static int codeAllEqualityTerms( /* Figure out how many memory cells we will need then allocate them. */ regBase = pParse->nMem + 1; - nReg = pLoop->u.btree.nEq + nExtraReg; + nReg = nEq + nExtraReg; pParse->nMem += nReg; zAff = sqlite3DbStrDup(pParse->db,sqlite3IndexAffinityStr(pParse->db,pIdx)); @@ -837,7 +843,7 @@ static int codeAllEqualityTerms( testcase( pIdx->aiColumn[j]==XN_EXPR ); VdbeComment((v, "%s", explainIndexColumnName(pIdx, j))); } - } + } /* Evaluate the equality constraints */ @@ -846,7 +852,7 @@ static int codeAllEqualityTerms( int r1; pTerm = pLoop->aLTerm[j]; assert( pTerm!=0 ); - /* The following testcase is true for indices with redundant columns. + /* The following testcase is true for indices with redundant columns. ** Ex: CREATE INDEX i1 ON t1(a,b,a); SELECT * FROM t1 WHERE a=0 AND b=0; */ testcase( (pTerm->wtFlags & TERM_CODED)!=0 ); testcase( pTerm->wtFlags & TERM_VIRTUAL ); @@ -859,14 +865,11 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j); } } - } - for(j=nSkip; jaLTerm[j]; if( pTerm->eOperator & WO_IN ){ if( pTerm->pExpr->flags & EP_xIsSelect ){ /* No affinity ever needs to be (or should be) applied to a value - ** from the RHS of an "? IN (SELECT ...)" expression. The - ** sqlite3FindInIndex() routine has already ensured that the + ** from the RHS of an "? IN (SELECT ...)" expression. The + ** sqlite3FindInIndex() routine has already ensured that the ** affinity of the comparison has been applied to the value. */ if( zAff ) zAff[j] = SQLITE_AFF_BLOB; } @@ -894,7 +897,7 @@ static int codeAllEqualityTerms( #ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS /* ** If the most recently coded instruction is a constant range constraint -** (a string literal) that originated from the LIKE optimization, then +** (a string literal) that originated from the LIKE optimization, then ** set P3 and P5 on the OP_String opcode so that the string will be cast ** to a BLOB at appropriate times. ** @@ -919,7 +922,7 @@ static void whereLikeOptimizationStringFixup( assert( pLevel->iLikeRepCntr>0 ); pOp = sqlite3VdbeGetLastOp(v); assert( pOp!=0 ); - assert( pOp->opcode==OP_String8 + assert( pOp->opcode==OP_String8 || pTerm->pWC->pWInfo->pParse->db->mallocFailed ); pOp->p3 = (int)(pLevel->iLikeRepCntr>>1); /* Register holding counter */ pOp->p5 = (u8)(pLevel->iLikeRepCntr&1); /* ASC or DESC */ @@ -962,7 +965,7 @@ static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){ /* ** Test whether or not expression pExpr, which was part of a WHERE clause, ** should be included in the cursor-hint for a table that is on the rhs -** of a LEFT JOIN. Set Walker.eCode to non-zero before returning if the +** of a LEFT JOIN. Set Walker.eCode to non-zero before returning if the ** expression is not suitable. ** ** An expression is unsuitable if it might evaluate to non NULL even if @@ -975,9 +978,9 @@ static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){ ** CASE WHEN col THEN 0 ELSE 1 END */ static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_IS - || pExpr->op==TK_ISNULL || pExpr->op==TK_ISNOT - || pExpr->op==TK_NOTNULL || pExpr->op==TK_CASE + if( pExpr->op==TK_IS + || pExpr->op==TK_ISNULL || pExpr->op==TK_ISNOT + || pExpr->op==TK_NOTNULL || pExpr->op==TK_CASE ){ pWalker->eCode = 1; }else if( pExpr->op==TK_FUNCTION ){ @@ -998,13 +1001,13 @@ static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){ ** that accesses any table other than the one identified by ** CCurHint.iTabCur, then do the following: ** -** 1) allocate a register and code an OP_Column instruction to read +** 1) allocate a register and code an OP_Column instruction to read ** the specified column into the new register, and ** -** 2) transform the expression node to a TK_REGISTER node that reads +** 2) transform the expression node to a TK_REGISTER node that reads ** from the newly populated register. ** -** Also, if the node is a TK_COLUMN that does access the table idenified +** Also, if the node is a TK_COLUMN that does access the table identified ** by pCCurHint.iTabCur, and an index is being used (which we will ** know because CCurHint.pIdx!=0) then transform the TK_COLUMN into ** an access of the index rather than the original table. @@ -1073,18 +1076,18 @@ static void codeCursorHint( if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( pTerm->prereqAll & pLevel->notReady ) continue; - /* Any terms specified as part of the ON(...) clause for any LEFT + /* Any terms specified as part of the ON(...) clause for any LEFT ** JOIN for which the current table is not the rhs are omitted - ** from the cursor-hint. + ** from the cursor-hint. ** - ** If this table is the rhs of a LEFT JOIN, "IS" or "IS NULL" terms + ** If this table is the rhs of a LEFT JOIN, "IS" or "IS NULL" terms ** that were specified as part of the WHERE clause must be excluded. ** This is to address the following: ** ** SELECT ... t1 LEFT JOIN t2 ON (t1.a=t2.b) WHERE t2.c IS NULL; ** ** Say there is a single row in t2 that matches (t1.a=t2.b), but its - ** t2.c values is not NULL. If the (t2.c IS NULL) constraint is + ** t2.c values is not NULL. If the (t2.c IS NULL) constraint is ** pushed down to the cursor, this row is filtered out, causing ** SQLite to synthesize a row of NULL values. Which does match the ** WHERE clause, and so the query returns a row. Which is incorrect. @@ -1097,7 +1100,7 @@ static void codeCursorHint( */ if( pTabItem->fg.jointype & JT_LEFT ){ Expr *pExpr = pTerm->pExpr; - if( !ExprHasProperty(pExpr, EP_OuterON) + if( !ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin!=pTabItem->iCursor ){ sWalker.eCode = 0; @@ -1135,7 +1138,7 @@ static void codeCursorHint( if( pExpr!=0 ){ sWalker.xExprCallback = codeCursorHintFixExpr; if( pParse->nErr==0 ) sqlite3WalkExpr(&sWalker, pExpr); - sqlite3VdbeAddOp4(v, OP_CursorHint, + sqlite3VdbeAddOp4(v, OP_CursorHint, (sHint.pIdx ? sHint.iIdxCur : sHint.iTabCur), 0, 0, (const char*)pExpr, P4_EXPR); } @@ -1147,7 +1150,7 @@ static void codeCursorHint( /* ** Cursor iCur is open on an intkey b-tree (a table). Register iRowid contains ** a rowid value just read from cursor iIdxCur, open on index pIdx. This -** function generates code to do a deferred seek of cursor iCur to the +** function generates code to do a deferred seek of cursor iCur to the ** rowid stored in register iRowid. ** ** Normally, this is just: @@ -1181,7 +1184,7 @@ static void codeDeferredSeek( assert( iIdxCur>0 ); assert( pIdx->aiColumn[pIdx->nColumn-1]==-1 ); - + pWInfo->bDeferredSeek = 1; sqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur); if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) @@ -1518,7 +1521,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( } } - /* Generate code that will continue to the next row if + /* Generate code that will continue to the next row if ** the IN constraint is not satisfied */ pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0); @@ -1611,7 +1614,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( int r1, rTemp; /* Registers for holding the start boundary */ int op; /* Cursor seek operation */ - /* The following constant maps TK_xx codes into corresponding + /* The following constant maps TK_xx codes into corresponding ** seek opcodes. It depends on a particular ordering of TK_xx */ const u8 aMoveOp[] = { @@ -1622,7 +1625,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( }; assert( TK_LE==TK_GT+1 ); /* Make sure the ordering.. */ assert( TK_LT==TK_GT+2 ); /* ... of the TK_xx values... */ - assert( TK_GE==TK_GT+3 ); /* ... is correcct. */ + assert( TK_GE==TK_GT+3 ); /* ... is correct. */ assert( (pStart->wtFlags & TERM_VNULL)==0 ); testcase( pStart->wtFlags & TERM_VIRTUAL ); @@ -1667,8 +1670,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( testcase( pEnd->wtFlags & TERM_VIRTUAL ); memEndValue = ++pParse->nMem; codeExprOrVector(pParse, pX->pRight, memEndValue, 1); - if( 0==sqlite3ExprIsVector(pX->pRight) - && (pX->op==TK_LT || pX->op==TK_GT) + if( 0==sqlite3ExprIsVector(pX->pRight) + && (pX->op==TK_LT || pX->op==TK_GT) ){ testOp = bRev ? OP_Le : OP_Ge; }else{ @@ -1696,14 +1699,14 @@ Bitmask sqlite3WhereCodeOneLoopStart( }else if( pLoop->wsFlags & WHERE_INDEXED ){ /* Case 4: A scan using an index. ** - ** The WHERE clause may contain zero or more equality + ** The WHERE clause may contain zero or more equality ** terms ("==" or "IN" operators) that refer to the N ** left-most columns of the index. It may also contain ** inequality constraints (>, <, >= or <=) on the indexed - ** column that immediately follows the N equalities. Only + ** column that immediately follows the N equalities. Only ** the right-most column can be an inequality - the rest must - ** use the "==" and "IN" operators. For example, if the - ** index is on (x,y,z), then the following clauses are all + ** use the "==" and "IN" operators. For example, if the + ** index is on (x,y,z), then the following clauses are all ** optimized: ** ** x=5 @@ -1724,7 +1727,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** This case is also used when there are no WHERE clause ** constraints but an index is selected anyway, in order ** to force the output order to conform to an ORDER BY. - */ + */ static const u8 aStartOp[] = { 0, 0, @@ -1766,15 +1769,15 @@ Bitmask sqlite3WhereCodeOneLoopStart( iIdxCur = pLevel->iIdxCur; assert( nEq>=pLoop->nSkip ); - /* Find any inequality constraint terms for the start and end - ** of the range. + /* Find any inequality constraint terms for the start and end + ** of the range. */ j = nEq; if( pLoop->wsFlags & WHERE_BTM_LIMIT ){ pRangeStart = pLoop->aLTerm[j++]; nExtraReg = MAX(nExtraReg, pLoop->u.btree.nBtm); /* Like optimization range constraints always occur in pairs */ - assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || + assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || (pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 ); } if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ @@ -1807,7 +1810,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 ); /* If the WHERE_BIGNULL_SORT flag is set, then index column nEq uses - ** a non-default "big-null" sort (either ASC NULLS LAST or DESC NULLS + ** a non-default "big-null" sort (either ASC NULLS LAST or DESC NULLS ** FIRST). In both cases separate ordered scans are made of those ** index entries for which the column is null and for those for which ** it is not. For an ASC sort, the non-NULL entries are scanned first. @@ -1829,7 +1832,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( } /* If we are doing a reverse order scan on an ascending index, or - ** a forward order scan on a descending index, interchange the + ** a forward order scan on a descending index, interchange the ** start and end terms (pRangeStart and pRangeEnd). */ if( (nEqnColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) ){ @@ -1878,7 +1881,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( } if( zStartAff ){ updateRangeAffinityStr(pRight, nBtm, &zStartAff[nEq]); - } + } nConstraint += nBtm; testcase( pRangeStart->wtFlags & TERM_VIRTUAL ); if( sqlite3ExprIsVector(pRight)==0 ){ @@ -1926,7 +1929,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** of entries in the tree, so basing the number of steps to try ** on the estimated number of rows in the btree seems like a good ** guess. */ - addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan, + addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan, (pIdx->aiRowLogEst[0]+9)/10); if( pRangeStart || pRangeEnd ){ sqlite3VdbeChangeP5(v, 1); @@ -1951,7 +1954,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( assert( bStopAtNull==startEq ); sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); op = aStartOp[(nConstraint>1)*4 + 2 + bRev]; - sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, + sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint-startEq); VdbeCoverage(v); VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind ); @@ -2044,7 +2047,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( } /* Seek the table cursor, if required */ - omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 + omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 && (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0; if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ @@ -2079,7 +2082,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** a LEFT JOIN: */ assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ); } - + /* Record the instruction used to terminate the loop. */ if( pLoop->wsFlags & WHERE_ONEROW ){ pLevel->op = OP_Noop; @@ -2188,15 +2191,15 @@ Bitmask sqlite3WhereCodeOneLoopStart( pOrTab = pWInfo->pTabList; } - /* Initialize the rowset register to contain NULL. An SQL NULL is + /* Initialize the rowset register to contain NULL. An SQL NULL is ** equivalent to an empty rowset. Or, create an ephemeral index ** capable of holding primary keys in the case of a WITHOUT ROWID. ** - ** Also initialize regReturn to contain the address of the instruction + ** Also initialize regReturn to contain the address of the instruction ** immediately following the OP_Return at the bottom of the loop. This ** is required in a few obscure LEFT JOIN cases where control jumps - ** over the top of the loop into the body of it. In this case the - ** correct response for the end-of-loop code (the OP_Return) is to + ** over the top of the loop into the body of it. In this case the + ** correct response for the end-of-loop code (the OP_Return) is to ** fall through to the next instruction, just as an OP_Next does if ** called on an uninitialized cursor. */ @@ -2221,7 +2224,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** ** Actually, each subexpression is converted to "xN AND w" where w is ** the "interesting" terms of z - terms that did not originate in the - ** ON or USING clause of a LEFT JOIN, and terms that are usable as + ** ON or USING clause of a LEFT JOIN, and terms that are usable as ** indices. ** ** This optimization also only applies if the (x1 OR x2 OR ...) term @@ -2262,7 +2265,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( /* The extra 0x10000 bit on the opcode is masked off and does not ** become part of the new Expr.op. However, it does make the ** op==TK_AND comparison inside of sqlite3PExpr() false, and this - ** prevents sqlite3PExpr() from applying the AND short-circuit + ** prevents sqlite3PExpr() from applying the AND short-circuit ** optimization, which we do not want here. */ pAndExpr = sqlite3PExpr(pParse, TK_AND|0x10000, 0, pAndExpr); } @@ -2338,9 +2341,9 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** ** Use some of the same optimizations as OP_RowSetTest: If iSet ** is zero, assume that the key cannot already be present in - ** the temp table. And if iSet is -1, assume that there is no - ** need to insert the key into the temp table, as it will never - ** be tested for. */ + ** the temp table. And if iSet is -1, assume that there is no + ** need to insert the key into the temp table, as it will never + ** be tested for. */ if( iSet ){ jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, regRowset, 0, r, nPk); VdbeCoverage(v); @@ -2379,8 +2382,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** If the call to sqlite3WhereBegin() above resulted in a scan that ** uses an index, and this is either the first OR-connected term ** processed or the index is the same as that used by all previous - ** terms, set pCov to the candidate covering index. Otherwise, set - ** pCov to NULL to indicate that no candidate covering index will + ** terms, set pCov to the candidate covering index. Otherwise, set + ** pCov to NULL to indicate that no candidate covering index will ** be available. */ pSubLoop = pSubWInfo->a[0].pWLoop; @@ -2467,7 +2470,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** ** iLoop==1: Code only expressions that are entirely covered by pIdx. ** iLoop==2: Code remaining expressions that do not contain correlated - ** sub-queries. + ** sub-queries. ** iLoop==3: Code all remaining expressions. ** ** An effort is made to skip unnecessary iterations of the loop. @@ -2578,7 +2581,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( WO_EQ|WO_IN|WO_IS, 0); if( pAlt==0 ) continue; if( pAlt->wtFlags & (TERM_CODED) ) continue; - if( (pAlt->eOperator & WO_IN) + if( (pAlt->eOperator & WO_IN) && ExprUseXSelect(pAlt->pExpr) && (pAlt->pExpr->x.pSelect->pEList->nExpr>1) ){ @@ -2636,7 +2639,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( } /* For a LEFT OUTER JOIN, generate code that will record the fact that - ** at least one row of the right table has matched the left table. + ** at least one row of the right table has matched the left table. */ if( pLevel->iLeftJoin ){ pLevel->addrFirst = sqlite3VdbeCurrentAddr(v); diff --git a/src/whereexpr.c b/src/whereexpr.c index a979b6f2b..daf3d5d95 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -13,7 +13,7 @@ ** the WHERE clause of SQL statements. ** ** This file was originally part of where.c but was split out to improve -** readability and editabiliity. This file contains utility routines for +** readability and editability. This file contains utility routines for ** analyzing Expr objects in the WHERE clause. */ #include "sqliteInt.h" @@ -229,7 +229,7 @@ static int isLikeOrGlob( ** range search. The third is because the caller assumes that the pattern ** consists of at least one character after all escapes have been ** removed. */ - if( cnt!=0 && 255!=(u8)z[cnt-1] && (cnt>1 || z[0]!=wc[3]) ){ + if( (cnt>1 || (cnt>0 && z[0]!=wc[3])) && 255!=(u8)z[cnt-1] ){ Expr *pPrefix; /* A "complete" match if the pattern ends with "*" or "%" */ @@ -263,8 +263,8 @@ static int isLikeOrGlob( ** 2019-06-14 https://sqlite.org/src/info/ce8717f0885af975 ** 2019-09-03 https://sqlite.org/src/info/0f0428096f17252a */ - if( pLeft->op!=TK_COLUMN - || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT + if( pLeft->op!=TK_COLUMN + || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT || (ALWAYS( ExprUseYTab(pLeft) ) && ALWAYS(pLeft->y.pTab) && IsVirtual(pLeft->y.pTab)) /* Might be numeric */ @@ -302,7 +302,7 @@ static int isLikeOrGlob( ** function, then no OP_Variable will be added to the program. ** This causes problems for the sqlite3_bind_parameter_name() ** API. To work around them, add a dummy OP_Variable here. - */ + */ int r1 = sqlite3GetTempReg(pParse); sqlite3ExprCodeTarget(pParse, pRight, r1); sqlite3VdbeChangeP3(v, sqlite3VdbeCurrentAddr(v)-1, 0); @@ -339,7 +339,7 @@ static int isLikeOrGlob( ** 9. column IS NOT NULL SQLITE_INDEX_CONSTRAINT_ISNOTNULL ** ** In every case, "column" must be a column of a virtual table. If there -** is a match, set *ppLeft to the "column" expression, set *ppRight to the +** is a match, set *ppLeft to the "column" expression, set *ppRight to the ** "expr" expression (even though in forms (6) and (8) the column is on the ** right and the expression is on the left). Also set *peOp2 to the ** appropriate virtual table operator. The return value is 1 or 2 if there @@ -507,7 +507,7 @@ static WhereTerm *whereNthSubterm(WhereTerm *pTerm, int N){ ** ** The following is NOT generated: ** -** xy --> x!=y +** xy --> x!=y */ static void whereCombineDisjuncts( SrcList *pSrc, /* the FROM clause */ @@ -605,10 +605,10 @@ static void whereCombineDisjuncts( ** WhereTerm.u.pOrInfo->indexable |= the cursor number for table T ** ** A subterm is "indexable" if it is of the form -** "T.C " where C is any column of table T and +** "T.C " where C is any column of table T and ** is one of "=", "<", "<=", ">", ">=", "IS NULL", or "IN". ** A subterm is also indexable if it is an AND of two or more -** subsubterms at least one of which is indexable. Indexable AND +** subsubterms at least one of which is indexable. Indexable AND ** subterms have their eOperator set to WO_AND and they have ** u.pAndInfo set to a dynamically allocated WhereAndTerm object. ** @@ -700,7 +700,7 @@ static void exprAnalyzeOrTerm( if( !db->mallocFailed ){ for(j=0, pAndTerm=pAndWC->a; jnTerm; j++, pAndTerm++){ assert( pAndTerm->pExpr ); - if( allowedOp(pAndTerm->pExpr->op) + if( allowedOp(pAndTerm->pExpr->op) || pAndTerm->eOperator==WO_AUX ){ b |= sqlite3WhereGetMask(&pWInfo->sMaskSet, pAndTerm->leftCursor); @@ -802,7 +802,7 @@ static void exprAnalyzeOrTerm( pOrTerm->leftCursor))==0 ){ /* This term must be of the form t1.a==t2.b where t2 is in the ** chngToIN set but t1 is not. This term will be either preceded - ** or follwed by an inverted copy (t2.b==t1.a). Skip this term + ** or followed by an inverted copy (t2.b==t1.a). Skip this term ** and use its inversion. */ testcase( pOrTerm->wtFlags & TERM_COPIED ); testcase( pOrTerm->wtFlags & TERM_VIRTUAL ); @@ -833,7 +833,7 @@ static void exprAnalyzeOrTerm( assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); if( pOrTerm->leftCursor!=iCursor ){ pOrTerm->wtFlags &= ~TERM_OK; - }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR + }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR && sqlite3ExprCompare(pParse, pOrTerm->pExpr->pLeft, pLeft, -1) )){ okToChngToIN = 0; @@ -855,7 +855,7 @@ static void exprAnalyzeOrTerm( } /* At this point, okToChngToIN is true if original pTerm satisfies - ** case 1. In that case, construct a new virtual term that is + ** case 1. In that case, construct a new virtual term that is ** pTerm converted into an IN operator. */ if( okToChngToIN ){ @@ -988,7 +988,7 @@ static SQLITE_NOINLINE int exprMightBeIndexed2( for(i=0; inKeyCol; i++){ if( pIdx->aiColumn[i]!=XN_EXPR ) continue; assert( pIdx->bHasExpr ); - if( sqlite3ExprCompareSkip(pExpr,pIdx->aColExpr->a[i].pExpr,iCur)==0 + if( sqlite3ExprCompareSkip(pExpr,pIdx->aColExpr->a[i].pExpr,iCur)==0 && pExpr->op!=TK_STRING ){ aiCurCol[0] = iCur; @@ -1008,8 +1008,8 @@ static int exprMightBeIndexed( ){ int i; - /* If this expression is a vector to the left or right of a - ** inequality constraint (>, <, >= or <=), perform the processing + /* If this expression is a vector to the left or right of a + ** inequality constraint (>, <, >= or <=), perform the processing ** on the first element of the vector. */ assert( TK_GT+1==TK_LE && TK_GT+2==TK_LT && TK_GT+3==TK_GE ); assert( TK_ISpLeft */ - Bitmask prereqAll; /* Prerequesites of pExpr */ + Bitmask prereqLeft; /* Prerequisites of the pExpr->pLeft */ + Bitmask prereqAll; /* Prerequisites of pExpr */ Bitmask extraRight = 0; /* Extra dependencies on LEFT JOIN */ Expr *pStr1 = 0; /* RHS of LIKE/GLOB operator */ int isComplete = 0; /* RHS of LIKE/GLOB ends with wildcard */ @@ -1166,7 +1166,7 @@ static void exprAnalyze( pTerm->eOperator = operatorMask(op) & opMask; } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; - if( pRight + if( pRight && exprMightBeIndexed(pSrc, aiCurCol, pRight, op) && !ExprHasProperty(pRight, EP_FixedCol) ){ @@ -1205,7 +1205,7 @@ static void exprAnalyze( pNew->prereqRight = prereqLeft | extraRight; pNew->prereqAll = prereqAll; pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask; - }else + }else if( op==TK_ISNULL && !ExprHasProperty(pExpr,EP_OuterON) && 0==sqlite3ExprCanBeNull(pLeft) @@ -1246,7 +1246,7 @@ static void exprAnalyze( for(i=0; i<2; i++){ Expr *pNewExpr; int idxNew; - pNewExpr = sqlite3PExpr(pParse, ops[i], + pNewExpr = sqlite3PExpr(pParse, ops[i], sqlite3ExprDup(db, pExpr->pLeft, 0), sqlite3ExprDup(db, pList->a[i].pExpr, 0)); transferJoinMarkings(pNewExpr, pExpr); @@ -1284,11 +1284,11 @@ static void exprAnalyze( Expr *pLeft = pExpr->pLeft; int idxNew; WhereTerm *pNewTerm; - + pNewExpr = sqlite3PExpr(pParse, TK_GT, sqlite3ExprDup(db, pLeft, 0), sqlite3ExprAlloc(db, TK_NULL, 0, 0)); - + idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); if( idxNew ){ @@ -1338,7 +1338,7 @@ static void exprAnalyze( pStr2 = sqlite3ExprDup(db, pStr1, 0); assert( pStr1==0 || !ExprHasProperty(pStr1, EP_IntValue) ); assert( pStr2==0 || !ExprHasProperty(pStr2, EP_IntValue) ); - + /* Convert the lower bound to upper-case and the upper bound to ** lower-case (upper-case is less than lower-case in ASCII) so that @@ -1361,7 +1361,7 @@ static void exprAnalyze( if( noCase ){ /* The point is to increment the last character before the first ** wildcard. But if we increment '@', that will push it into the - ** alphabetic range where case conversions will mess up the + ** alphabetic range where case conversions will mess up the ** inequality. To avoid this, make sure to also run the full ** LIKE on all candidate expressions by clearing the isComplete flag */ @@ -1408,7 +1408,7 @@ static void exprAnalyze( if( (pExpr->op==TK_EQ || pExpr->op==TK_IS) && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 && sqlite3ExprVectorSize(pExpr->pRight)==nLeft - && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 + && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 || (pExpr->pRight->flags & EP_xIsSelect)==0) && pWC->op==TK_AND ){ @@ -1431,7 +1431,7 @@ static void exprAnalyze( /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create ** a virtual term for each vector component. The expression object - ** used by each such virtual term is pExpr (the full vector IN(...) + ** used by each such virtual term is pExpr (the full vector IN(...) ** expression). The WhereTerm.u.x.iField variable identifies the index within ** the vector on the LHS that the virtual term represents. ** @@ -1480,7 +1480,7 @@ static void exprAnalyze( prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft); if( (prereqExpr & prereqColumn)==0 ){ Expr *pNewExpr; - pNewExpr = sqlite3PExpr(pParse, TK_MATCH, + pNewExpr = sqlite3PExpr(pParse, TK_MATCH, 0, sqlite3ExprDup(db, pRight, 0)); if( ExprHasProperty(pExpr, EP_OuterON) && pNewExpr ){ ExprSetProperty(pNewExpr, EP_OuterON); @@ -1548,12 +1548,12 @@ void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){ } /* -** Add either a LIMIT (if eMatchOp==SQLITE_INDEX_CONSTRAINT_LIMIT) or -** OFFSET (if eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET) term to the +** Add either a LIMIT (if eMatchOp==SQLITE_INDEX_CONSTRAINT_LIMIT) or +** OFFSET (if eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET) term to the ** where-clause passed as the first argument. The value for the term ** is found in register iReg. ** -** In the common case where the value is a simple integer +** In the common case where the value is a simple integer ** (example: "LIMIT 5 OFFSET 10") then the expression codes as a ** TK_INTEGER so that it will be available to sqlite3_vtab_rhs_value(). ** If not, then it codes as a TK_REGISTER expression. @@ -1633,7 +1633,7 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ if( pWC->a[ii].nChild ){ /* If this term has child terms, then they are also part of the ** pWC->a[] array. So this term can be ignored, as a LIMIT clause - ** will only be added if each of the child terms passes the + ** will only be added if each of the child terms passes the ** (leftCursor==iCsr) test below. */ continue; } @@ -1798,7 +1798,7 @@ Bitmask sqlite3WhereExprListUsage(WhereMaskSet *pMaskSet, ExprList *pList){ /* -** Call exprAnalyze on all terms in a WHERE clause. +** Call exprAnalyze on all terms in a WHERE clause. ** ** Note that exprAnalyze() might add new virtual terms onto the ** end of the WHERE clause. We do not want to analyze these new @@ -1817,7 +1817,7 @@ void sqlite3WhereExprAnalyze( /* ** For table-valued-functions, transform the function arguments into -** new WHERE clause terms. +** new WHERE clause terms. ** ** Each function argument translates into an equality constraint against ** a HIDDEN column in the table. @@ -1853,7 +1853,7 @@ void sqlite3WhereTabFuncArgs( assert( ExprUseYTab(pColRef) ); pColRef->y.pTab = pTab; pItem->colUsed |= sqlite3ExprColUsed(pColRef); - pRhs = sqlite3PExpr(pParse, TK_UPLUS, + pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); if( pItem->fg.jointype & (JT_LEFT|JT_RIGHT) ){ diff --git a/src/window.c b/src/window.c index a8081aa24..d46eabc3b 100644 --- a/src/window.c +++ b/src/window.c @@ -40,12 +40,12 @@ ** (in this case max()) to process rows sorted in order of (c, d), which ** makes things easier for obvious reasons. More generally: ** -** * FROM, WHERE, GROUP BY and HAVING clauses are all moved to +** * FROM, WHERE, GROUP BY and HAVING clauses are all moved to ** the sub-query. ** ** * ORDER BY, LIMIT and OFFSET remain part of the parent query. ** -** * Terminals from each of the expression trees that make up the +** * Terminals from each of the expression trees that make up the ** select-list and ORDER BY expressions in the parent query are ** selected by the sub-query. For the purposes of the transformation, ** terminals are column references and aggregate functions. @@ -54,14 +54,14 @@ ** the same window declaration (the OVER bit), then a single scan may ** be used to process more than one window function. For example: ** -** SELECT max(b) OVER (PARTITION BY c ORDER BY d), -** min(e) OVER (PARTITION BY c ORDER BY d) +** SELECT max(b) OVER (PARTITION BY c ORDER BY d), +** min(e) OVER (PARTITION BY c ORDER BY d) ** FROM t1; ** ** is transformed in the same way as the example above. However: ** -** SELECT max(b) OVER (PARTITION BY c ORDER BY d), -** min(e) OVER (PARTITION BY a ORDER BY b) +** SELECT max(b) OVER (PARTITION BY c ORDER BY d), +** min(e) OVER (PARTITION BY a ORDER BY b) ** FROM t1; ** ** Must be transformed to: @@ -114,15 +114,15 @@ ** first_value(expr) ** last_value(expr) ** nth_value(expr, N) -** -** These are the same built-in window functions supported by Postgres. +** +** These are the same built-in window functions supported by Postgres. ** Although the behaviour of aggregate window functions (functions that -** can be used as either aggregates or window funtions) allows them to +** can be used as either aggregates or window functions) allows them to ** be implemented using an API, built-in window functions are much more -** esoteric. Additionally, some window functions (e.g. nth_value()) +** esoteric. Additionally, some window functions (e.g. nth_value()) ** may only be implemented by caching the entire partition in memory. ** As such, some built-in window functions use the same API as aggregate -** window functions and some are implemented directly using VDBE +** window functions and some are implemented directly using VDBE ** instructions. Additionally, for those functions that use the API, the ** window frame is sometimes modified before the SELECT statement is ** rewritten. For example, regardless of the specified window frame, the @@ -134,7 +134,7 @@ ** ** As well as some of the built-in window functions, aggregate window ** functions min() and max() are implemented using VDBE instructions if -** the start of the window frame is declared as anything other than +** the start of the window frame is declared as anything other than ** UNBOUNDED PRECEDING. */ @@ -145,7 +145,7 @@ ** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW */ static void row_numberStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -173,10 +173,10 @@ struct CallCount { ** Implementation of built-in window function dense_rank(). Assumes that ** the window frame has been set to: ** -** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW */ static void dense_rankStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -208,7 +208,7 @@ struct NthValueCtx { sqlite3_value *pValue; }; static void nth_valueStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -261,7 +261,7 @@ static void nth_valueFinalizeFunc(sqlite3_context *pCtx){ #define nth_valueValueFunc noopValueFunc static void first_valueStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -292,10 +292,10 @@ static void first_valueFinalizeFunc(sqlite3_context *pCtx){ ** Implementation of built-in window function rank(). Assumes that ** the window frame has been set to: ** -** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW */ static void rankStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -326,7 +326,7 @@ static void rankValueFunc(sqlite3_context *pCtx){ ** GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING */ static void percent_rankStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -339,7 +339,7 @@ static void percent_rankStepFunc( } } static void percent_rankInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -371,7 +371,7 @@ static void percent_rankValueFunc(sqlite3_context *pCtx){ ** GROUPS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING */ static void cume_distStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -384,7 +384,7 @@ static void cume_distStepFunc( } } static void cume_distInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -420,7 +420,7 @@ struct NtileCtx { ** ROWS CURRENT ROW AND UNBOUNDED FOLLOWING */ static void ntileStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -440,7 +440,7 @@ static void ntileStepFunc( } } static void ntileInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -486,7 +486,7 @@ struct LastValueCtx { ** Implementation of last_value(). */ static void last_valueStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -504,7 +504,7 @@ static void last_valueStepFunc( } } static void last_valueInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -647,7 +647,7 @@ static Window *windowFind(Parse *pParse, Window *pList, const char *zName){ ** is the Window object representing the associated OVER clause. This ** function updates the contents of pWin as follows: ** -** * If the OVER clause refered to a named window (as in "max(x) OVER win"), +** * If the OVER clause referred to a named window (as in "max(x) OVER win"), ** search list pList for a matching WINDOW definition, and update pWin ** accordingly. If no such WINDOW clause can be found, leave an error ** in pParse. @@ -657,7 +657,7 @@ static Window *windowFind(Parse *pParse, Window *pList, const char *zName){ ** of this file), pWin is updated here. */ void sqlite3WindowUpdate( - Parse *pParse, + Parse *pParse, Window *pList, /* List of named windows for this SELECT */ Window *pWin, /* Window frame to update */ FuncDef *pFunc /* Window function definition */ @@ -677,17 +677,17 @@ void sqlite3WindowUpdate( sqlite3WindowChain(pParse, pWin, pList); } if( (pWin->eFrmType==TK_RANGE) - && (pWin->pStart || pWin->pEnd) + && (pWin->pStart || pWin->pEnd) && (pWin->pOrderBy==0 || pWin->pOrderBy->nExpr!=1) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "RANGE with offset PRECEDING/FOLLOWING requires one ORDER BY expression" ); }else if( pFunc->funcFlags & SQLITE_FUNC_WINDOW ){ sqlite3 *db = pParse->db; if( pWin->pFilter ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "FILTER clause may only be used with aggregate window functions" ); }else{ @@ -697,14 +697,14 @@ void sqlite3WindowUpdate( int eStart; int eEnd; } aUp[] = { - { row_numberName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, - { dense_rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, - { rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, - { percent_rankName, TK_GROUPS, TK_CURRENT, TK_UNBOUNDED }, - { cume_distName, TK_GROUPS, TK_FOLLOWING, TK_UNBOUNDED }, - { ntileName, TK_ROWS, TK_CURRENT, TK_UNBOUNDED }, - { leadName, TK_ROWS, TK_UNBOUNDED, TK_UNBOUNDED }, - { lagName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, + { row_numberName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, + { dense_rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, + { rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, + { percent_rankName, TK_GROUPS, TK_CURRENT, TK_UNBOUNDED }, + { cume_distName, TK_GROUPS, TK_FOLLOWING, TK_UNBOUNDED }, + { ntileName, TK_ROWS, TK_CURRENT, TK_UNBOUNDED }, + { leadName, TK_ROWS, TK_UNBOUNDED, TK_UNBOUNDED }, + { lagName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, }; int i; for(i=0; iiEphCsr, where N is the number of elements in (*ppSub) after ** appending the new one. */ static void selectWindowRewriteEList( - Parse *pParse, + Parse *pParse, Window *pWin, SrcList *pSrc, ExprList *pEList, /* Rewrite expressions in this list */ @@ -950,7 +950,7 @@ static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ /* ** If the SELECT statement passed as the second argument does not invoke -** any SQL window functions, this function is a no-op. Otherwise, it +** any SQL window functions, this function is a no-op. Otherwise, it ** rewrites the SELECT statement so that window function xStep functions ** are invoked in the correct order as described under "SELECT REWRITING" ** at the top of this file. @@ -1023,8 +1023,8 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ selectWindowRewriteEList(pParse, pMWin, pSrc, p->pOrderBy, pTab, &pSublist); pMWin->nBufferCol = (pSublist ? pSublist->nExpr : 0); - /* Append the PARTITION BY and ORDER BY expressions to the to the - ** sub-select expression list. They are required to figure out where + /* Append the PARTITION BY and ORDER BY expressions to the to the + ** sub-select expression list. They are required to figure out where ** boundaries for partitions and sets of peer rows lie. */ pSublist = exprListAppendList(pParse, pSublist, pMWin->pPartition, 0); pSublist = exprListAppendList(pParse, pSublist, pMWin->pOrderBy, 0); @@ -1058,11 +1058,11 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ /* If there is no ORDER BY or PARTITION BY clause, and the window ** function accepts zero arguments, and there are no other columns ** selected (e.g. "SELECT row_number() OVER () FROM t1"), it is possible - ** that pSublist is still NULL here. Add a constant expression here to - ** keep everything legal in this case. + ** that pSublist is still NULL here. Add a constant expression here to + ** keep everything legal in this case. */ if( pSublist==0 ){ - pSublist = sqlite3ExprListAppend(pParse, 0, + pSublist = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_INTEGER, "0") ); } @@ -1248,10 +1248,10 @@ Window *sqlite3WindowAlloc( ** equivalent nul-terminated string. */ Window *sqlite3WindowAssemble( - Parse *pParse, - Window *pWin, - ExprList *pPartition, - ExprList *pOrderBy, + Parse *pParse, + Window *pWin, + ExprList *pPartition, + ExprList *pOrderBy, Token *pBase ){ if( pWin ){ @@ -1268,7 +1268,7 @@ Window *sqlite3WindowAssemble( } /* -** Window *pWin has just been created from a WINDOW clause. Tokne pBase +** Window *pWin has just been created from a WINDOW clause. Token pBase ** is the base window. Earlier windows from the same WINDOW clause are ** stored in the linked list starting at pWin->pNextWin. This function ** either updates *pWin according to the base specification, or else @@ -1289,7 +1289,7 @@ void sqlite3WindowChain(Parse *pParse, Window *pWin, Window *pList){ zErr = "frame specification"; } if( zErr ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "cannot override %s of window: %s", zErr, pWin->zBase ); }else{ @@ -1540,7 +1540,7 @@ struct WindowCsrAndReg { }; /* -** A single instance of this structure is allocated on the stack by +** A single instance of this structure is allocated on the stack by ** sqlite3WindowCodeStep() and a pointer to it passed to the various helper ** routines. This is to reduce the number of arguments required by each ** helper function. @@ -1574,7 +1574,7 @@ struct WindowCsrAndReg { ** ** (ORDER BY a, b GROUPS BETWEEN 2 PRECEDING AND 2 FOLLOWING) ** -** The windows functions implmentation caches the input rows in a temp +** The windows functions implementation caches the input rows in a temp ** table, sorted by "a, b" (it actually populates the cache lazily, and ** aggressively removes rows once they are no longer required, but that's ** a mere detail). It keeps three cursors open on the temp table. One @@ -1587,7 +1587,7 @@ struct WindowCsrAndReg { ** ** Each cursor (start, current and end) consists of a VDBE cursor ** (WindowCsrAndReg.csr) and an array of registers (starting at -** WindowCodeArg.reg) that always contains a copy of the peer values +** WindowCodeArg.reg) that always contains a copy of the peer values ** read from the corresponding cursor. ** ** Depending on the window-frame in question, all three cursors may not @@ -1632,8 +1632,8 @@ static void windowReadPeerValues( } /* -** Generate VM code to invoke either xStep() (if bInverse is 0) or -** xInverse (if bInverse is non-zero) for each window function in the +** Generate VM code to invoke either xStep() (if bInverse is 0) or +** xInverse (if bInverse is non-zero) for each window function in the ** linked list starting at pMWin. Or, for built-in window functions ** that do not use the standard function API, generate the required ** inline VM code. @@ -1682,7 +1682,7 @@ static void windowAggStep( regArg = reg; if( pMWin->regStartRowid==0 - && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) && (pWin->eStart!=TK_UNBOUNDED) ){ int addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regArg); @@ -1718,7 +1718,7 @@ static void windowAggStep( VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, regTmp); } - + if( pWin->bExprArgs ){ int iOp = sqlite3VdbeCurrentAddr(v); int iEnd; @@ -1742,7 +1742,7 @@ static void windowAggStep( pColl = sqlite3ExprNNCollSeq(pParse, pWin->pOwner->x.pList->a[0].pExpr); sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, + sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, bInverse, regArg, pWin->regAccum); sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nArg); @@ -1775,7 +1775,7 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ if( pMWin->regStartRowid==0 - && (pWin->pWFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pWin->pWFunc->funcFlags & SQLITE_FUNC_MINMAX) && (pWin->eStart!=TK_UNBOUNDED) ){ sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); @@ -1932,7 +1932,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ int lbl = sqlite3VdbeMakeLabel(pParse); int tmpReg = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); - + if( pFunc->zName==nth_valueName ){ sqlite3VdbeAddOp3(v, OP_Column,pMWin->iEphCsr,pWin->iArgCol+1,tmpReg); windowCheckValue(pParse, tmpReg, 2); @@ -1954,7 +1954,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ int lbl = sqlite3VdbeMakeLabel(pParse); int tmpReg = sqlite3GetTempReg(pParse); int iEph = pMWin->iEphCsr; - + if( nArg<3 ){ sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); }else{ @@ -1971,7 +1971,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ sqlite3VdbeAddOp3(v, op, tmpReg2, tmpReg, tmpReg); sqlite3ReleaseTempReg(pParse, tmpReg2); } - + sqlite3VdbeAddOp3(v, OP_SeekRowid, csr, lbl, tmpReg); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol, pWin->regResult); @@ -2017,7 +2017,7 @@ static int windowInitAccum(Parse *pParse, Window *pMWin){ return regArg; } -/* +/* ** Return true if the current frame should be cached in the ephemeral table, ** even if there are no xInverse() calls required. */ @@ -2041,9 +2041,9 @@ static int windowCacheFrame(Window *pMWin){ ** regOld and regNew are each the first register in an array of size ** pOrderBy->nExpr. This function generates code to compare the two ** arrays of registers using the collation sequences and other comparison -** parameters specified by pOrderBy. +** parameters specified by pOrderBy. ** -** If the two arrays are not equal, the contents of regNew is copied to +** If the two arrays are not equal, the contents of regNew is copied to ** regOld and control falls through. Otherwise, if the contents of the arrays ** are equal, an OP_Goto is executed. The address of the OP_Goto is returned. */ @@ -2060,7 +2060,7 @@ static void windowIfNewPeer( KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOrderBy, 0, 0); sqlite3VdbeAddOp3(v, OP_Compare, regOld, regNew, nVal); sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO); - sqlite3VdbeAddOp3(v, OP_Jump, + sqlite3VdbeAddOp3(v, OP_Jump, sqlite3VdbeCurrentAddr(v)+1, addr, sqlite3VdbeCurrentAddr(v)+1 ); VdbeCoverageEqNe(v); @@ -2094,7 +2094,7 @@ static void windowIfNewPeer( ** or subtraction is a a copy of csr1.peerVal. */ static void windowCodeRangeTest( - WindowCodeArg *p, + WindowCodeArg *p, int op, /* OP_Ge, OP_Gt, or OP_Le */ int csr1, /* Cursor number for cursor 1 */ int regVal, /* Register containing non-negative number */ @@ -2132,8 +2132,8 @@ static void windowCodeRangeTest( ((op==OP_Ge) ? ">=" : (op==OP_Le) ? "<=" : (op==OP_Gt) ? ">" : "<"), reg2 )); - /* If the BIGNULL flag is set for the ORDER BY, then it is required to - ** consider NULL values to be larger than all other values, instead of + /* If the BIGNULL flag is set for the ORDER BY, then it is required to + ** consider NULL values to be larger than all other values, instead of ** the usual smaller. The VDBE opcodes OP_Ge and so on do not handle this ** (and adding that capability causes a performance regression), so ** instead if the BIGNULL flag is set then cases where either reg1 or @@ -2148,23 +2148,23 @@ static void windowCodeRangeTest( ** if( op==OP_Le ) goto lbl; ** } ** - ** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is + ** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is ** not taken, control jumps over the comparison operator coded below this ** block. */ if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_BIGNULL ){ /* This block runs if reg1 contains a NULL. */ int addr = sqlite3VdbeAddOp1(v, OP_NotNull, reg1); VdbeCoverage(v); switch( op ){ - case OP_Ge: - sqlite3VdbeAddOp2(v, OP_Goto, 0, lbl); + case OP_Ge: + sqlite3VdbeAddOp2(v, OP_Goto, 0, lbl); break; - case OP_Gt: - sqlite3VdbeAddOp2(v, OP_NotNull, reg2, lbl); - VdbeCoverage(v); + case OP_Gt: + sqlite3VdbeAddOp2(v, OP_NotNull, reg2, lbl); + VdbeCoverage(v); break; - case OP_Le: - sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); - VdbeCoverage(v); + case OP_Le: + sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); + VdbeCoverage(v); break; default: assert( op==OP_Lt ); /* no-op */ break; } @@ -2221,7 +2221,7 @@ static void windowCodeRangeTest( /* ** Helper function for sqlite3WindowCodeStep(). Each call to this function -** generates VM code for a single RETURN_ROW, AGGSTEP or AGGINVERSE +** generates VM code for a single RETURN_ROW, AGGSTEP or AGGINVERSE ** operation. Refer to the header comment for sqlite3WindowCodeStep() for ** details. */ @@ -2280,8 +2280,8 @@ static int windowCodeOp( addrContinue = sqlite3VdbeCurrentAddr(v); /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or - ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the - ** start cursor does not advance past the end cursor within the + ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the + ** start cursor does not advance past the end cursor within the ** temporary table. It otherwise might, if (a>b). Also ensure that, ** if the input cursor is still finding new rows, that the end ** cursor does not go past it to EOF. */ @@ -2422,11 +2422,11 @@ Window *sqlite3WindowListDup(sqlite3 *db, Window *p){ } /* -** Return true if it can be determined at compile time that expression -** pExpr evaluates to a value that, when cast to an integer, is greater +** Return true if it can be determined at compile time that expression +** pExpr evaluates to a value that, when cast to an integer, is greater ** than zero. False otherwise. ** -** If an OOM error occurs, this function sets the Parse.db.mallocFailed +** If an OOM error occurs, this function sets the Parse.db.mallocFailed ** flag and returns zero. */ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ @@ -2442,11 +2442,11 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ } /* -** sqlite3WhereBegin() has already been called for the SELECT statement +** sqlite3WhereBegin() has already been called for the SELECT statement ** passed as the second argument when this function is invoked. It generates -** code to populate the Window.regResult register for each window function +** code to populate the Window.regResult register for each window function ** and invoke the sub-routine at instruction addrGosub once for each row. -** sqlite3WhereEnd() is always called before returning. +** sqlite3WhereEnd() is always called before returning. ** ** This function handles several different types of window frames, which ** require slightly different processing. The following pseudo code is @@ -2461,17 +2461,17 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** Gosub flush ** } ** Insert new row into eph table. -** +** ** if( first row of partition ){ ** // Rewind three cursors, all open on the eph table. ** Rewind(csrEnd); ** Rewind(csrStart); ** Rewind(csrCurrent); -** +** ** regEnd = // FOLLOWING expression ** regStart = // PRECEDING expression ** }else{ -** // First time this branch is taken, the eph table contains two +** // First time this branch is taken, the eph table contains two ** // rows. The first row in the partition, which all three cursors ** // currently point to, and the following row. ** AGGSTEP @@ -2500,17 +2500,17 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** with arguments read from the current row of cursor csrEnd, then ** step cursor csrEnd forward one row (i.e. sqlite3BtreeNext()). ** -** RETURN_ROW: return a row to the caller based on the contents of the -** current row of csrCurrent and the current state of all +** RETURN_ROW: return a row to the caller based on the contents of the +** current row of csrCurrent and the current state of all ** aggregates. Then step cursor csrCurrent forward one row. ** -** AGGINVERSE: invoke the aggregate xInverse() function for each window +** AGGINVERSE: invoke the aggregate xInverse() function for each window ** functions with arguments read from the current row of cursor ** csrStart. Then step csrStart forward one row. ** ** There are two other ROWS window frames that are handled significantly ** differently from the above - "BETWEEN PRECEDING AND PRECEDING" -** and "BETWEEN FOLLOWING AND FOLLOWING". These are special +** and "BETWEEN FOLLOWING AND FOLLOWING". These are special ** cases because they change the order in which the three cursors (csrStart, ** csrCurrent and csrEnd) iterate through the ephemeral table. Cases that ** use UNBOUNDED or CURRENT ROW are much simpler variations on one of these @@ -2583,7 +2583,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** ** For the most part, the patterns above are adapted to support UNBOUNDED by ** assuming that it is equivalent to "infinity PRECEDING/FOLLOWING" and -** CURRENT ROW by assuming that it is equivilent to "0 PRECEDING/FOLLOWING". +** CURRENT ROW by assuming that it is equivalent to "0 PRECEDING/FOLLOWING". ** This is optimized of course - branches that will never be taken and ** conditions that are always true are omitted from the VM code. The only ** exceptional case is: @@ -2660,15 +2660,15 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** regEnd = ** regStart = ** }else if( new group ){ -** ... +** ... ** } ** } ** -** 2. Instead of processing a single row, each RETURN_ROW, AGGSTEP or +** 2. Instead of processing a single row, each RETURN_ROW, AGGSTEP or ** AGGINVERSE step processes the current row of the relevant cursor and ** all subsequent rows belonging to the same group. ** -** RANGE window frames are a little different again. As for GROUPS, the +** RANGE window frames are a little different again. As for GROUPS, the ** main loop runs once per group only. And RETURN_ROW, AGGSTEP and AGGINVERSE ** deal in groups instead of rows. As for ROWS and GROUPS, there are three ** basic cases: @@ -2705,7 +2705,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** } ** } ** -** In the above notation, "csr.key" means the current value of the ORDER BY +** In the above notation, "csr.key" means the current value of the ORDER BY ** expression (there is only ever 1 for a RANGE that uses an FOLLOWING ** or PRECEDING */ int regEnd = 0; /* Value of FOLLOWING */ - assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_CURRENT - || pMWin->eStart==TK_FOLLOWING || pMWin->eStart==TK_UNBOUNDED + assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_CURRENT + || pMWin->eStart==TK_FOLLOWING || pMWin->eStart==TK_UNBOUNDED ); - assert( pMWin->eEnd==TK_FOLLOWING || pMWin->eEnd==TK_CURRENT - || pMWin->eEnd==TK_UNBOUNDED || pMWin->eEnd==TK_PRECEDING + assert( pMWin->eEnd==TK_FOLLOWING || pMWin->eEnd==TK_CURRENT + || pMWin->eEnd==TK_UNBOUNDED || pMWin->eEnd==TK_PRECEDING ); assert( pMWin->eExclude==0 || pMWin->eExclude==TK_CURRENT || pMWin->eExclude==TK_GROUP || pMWin->eExclude==TK_TIES @@ -2830,9 +2830,9 @@ void sqlite3WindowCodeStep( s.end.csr = s.current.csr+3; /* Figure out when rows may be deleted from the ephemeral table. There - ** are four options - they may never be deleted (eDelete==0), they may + ** are four options - they may never be deleted (eDelete==0), they may ** be deleted as soon as they are no longer part of the window frame - ** (eDelete==WINDOW_AGGINVERSE), they may be deleted as after the row + ** (eDelete==WINDOW_AGGINVERSE), they may be deleted as after the row ** has been returned to the caller (WINDOW_RETURN_ROW), or they may ** be deleted after they enter the frame (WINDOW_AGGSTEP). */ switch( pMWin->eStart ){ @@ -2862,7 +2862,7 @@ void sqlite3WindowCodeStep( } /* Allocate registers for the array of values from the sub-query, the - ** samve values in record form, and the rowid used to insert said record + ** same values in record form, and the rowid used to insert said record ** into the ephemeral table. */ regNew = pParse->nMem+1; pParse->nMem += nInput; @@ -2880,7 +2880,7 @@ void sqlite3WindowCodeStep( } /* If this is not a "ROWS BETWEEN ..." frame, then allocate arrays of - ** registers to store copies of the ORDER BY expressions (peer values) + ** registers to store copies of the ORDER BY expressions (peer values) ** for the main loop, and for each cursor (start, current and end). */ if( pMWin->eFrmType!=TK_ROWS ){ int nPeer = (pOrderBy ? pOrderBy->nExpr : 0); @@ -2901,7 +2901,7 @@ void sqlite3WindowCodeStep( sqlite3VdbeAddOp3(v, OP_MakeRecord, regNew, nInput, regRecord); /* An input row has just been read into an array of registers starting - ** at regNew. If the window has a PARTITION clause, this block generates + ** at regNew. If the window has a PARTITION clause, this block generates ** VM code to check if the input row is the start of a new partition. ** If so, it does an OP_Gosub to an address to be filled in later. The ** address of the OP_Gosub is stored in local variable addrGosubFlush. */ diff --git a/test/analyze.test b/test/analyze.test index ca6c9b096..f97c78aff 100644 --- a/test/analyze.test +++ b/test/analyze.test @@ -377,4 +377,23 @@ do_execsql_test analyze-6.1 { SELECT tbl FROM sqlite_stat1 WHERE idx IS NULL ORDER BY tbl; } {SQLiteDemo2 sqliteDemo t1} +# The following caused a small buffer overread in STAT4 processing prior +# to check-in [b99135288b157044]. +# +ifcapable stat4 { + reset_db + database_may_be_corrupt + do_execsql_test analyze-7.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER); + INSERT INTO t1 VALUES(1, 7223372036854775); + INSERT INTO t1 VALUES(2, 7223372036854776); + INSERT INTO t1 VALUES(3, 7223372036854777); + CREATE INDEX i1 ON t1(b); + ANALYZE; + UPDATE sqlite_stat4 SET sample = substr(sample, 0, 4); + ANALYZE sqlite_schema; + SELECT * FROM t1 WHERE b>7223372036854775 + } {2 7223372036854776 3 7223372036854777} +} + finish_test diff --git a/test/atof1.test b/test/atof1.test index 5959c5d5b..95f443ce0 100644 --- a/test/atof1.test +++ b/test/atof1.test @@ -15,18 +15,10 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -if {$::longdouble_size<=8} { - finish_test - return -} -if {$::tcl_platform(machine)!="x86_64"} { - finish_test - return -} - +set mxpow [expr {[use_long_double]?100:35}] expr srand(1) for {set i 1} {$i<20000} {incr i} { - set pow [expr {int((rand()-0.5)*100)}] + set pow [expr {int((rand()-0.5)*$mxpow)}] set x [expr {pow((rand()-0.5)*2*rand(),$pow)}] set xf [format %.32e $x] @@ -51,10 +43,16 @@ for {set i 1} {$i<20000} {incr i} { set y [db eval {SELECT $x=CAST(quote($x) AS real)}] if {!$y} { db eval {SELECT real2hex($x) a, real2hex(CAST(quote($x) AS real)) b} {} - puts "\nIN: $a $xf" - puts [format {QUOTE: %16s %s} {} [db eval {SELECT quote($x)}]] + puts "" + if {$x<0} { + puts "[format {!SCALE: %17s 1 23456789 123456789 123456789} {}]" + } else { + puts "[format {!SCALE: %16s 1 23456789 123456789 123456789} {}]" + } + puts "!IN: $a $xf" + puts [format {!QUOTE: %16s %s} {} [db eval {SELECT quote($x)}]] db eval {SELECT CAST(quote($x) AS real) c} {} - puts "OUT: $b [format %.32e $c]" + puts "!OUT: $b [format %.32e $c]" } set y } {1} diff --git a/test/autoindex4.test b/test/autoindex4.test index d9ab783e4..6af99f5e1 100644 --- a/test/autoindex4.test +++ b/test/autoindex4.test @@ -141,7 +141,7 @@ foreach {id data1 data2 jointype onclause whereclause answer} { {coalesce(y,4)==4} {3 4 3 4} - 5 + 5.1 VALUES(1,2),(3,4),(NULL,4) VALUES(1,2),(3,4) {LEFT JOIN} @@ -149,6 +149,22 @@ foreach {id data1 data2 jointype onclause whereclause answer} { {y=4 OR y IS NULL} {3 4 3 4 {} 4 {} {}} + 5.2 + VALUES(1,2),(3,4),(NULL,4) + VALUES(1,2),(3,4) + {LEFT JOIN} + a=x + {y NOT IN ()} + {1 2 1 2 3 4 3 4 {} 4 {} {}} + + 5.3 + VALUES(1,2),(3,4),(NULL,4) + VALUES(1,2),(3,4) + {LEFT JOIN} + a=x + {y NOT IN (SELECT 1 WHERE false)} + {1 2 1 2 3 4 3 4 {} 4 {} {}} + 6 VALUES(1,2),(3,4) VALUES(1,2),(3,4),(NULL,4) @@ -193,6 +209,12 @@ foreach {id data1 data2 jointype onclause whereclause answer} { db eval {PRAGMA automatic_index=OFF;} db eval $sql } $answer + do_test autoindex4-4.$id.3 { + db eval {PRAGMA automatic_index=ON;} + optimization_control db all 0 + db eval $sql + } $answer + optimization_control db all 1 } diff --git a/test/corrupt2.test b/test/corrupt2.test index 96d28490a..2e36cbd30 100644 --- a/test/corrupt2.test +++ b/test/corrupt2.test @@ -248,7 +248,7 @@ do_test corrupt2-5.1 { } set result } {{*** in database main *** -Tree 11 page 2 cell 0: 2nd reference to page 10 +Tree 2 page 2 cell 0: 2nd reference to page 10 Page 4: never used}} db2 close diff --git a/test/corruptL.test b/test/corruptL.test index 65d9821b3..cf3876441 100644 --- a/test/corruptL.test +++ b/test/corruptL.test @@ -1269,6 +1269,7 @@ do_test 15.0 { }]} {} extra_schema_checks 0 +optimization_control db one-pass off do_catchsql_test 15.1 { PRAGMA cell_size_check = 0; UPDATE c1 SET c= NOT EXISTS(SELECT 1 FROM c1 ORDER BY (SELECT 1 FROM c1 ORDER BY a)) +10 WHERE d BETWEEN 4 AND 7; diff --git a/test/decimal.test b/test/decimal.test index a2e57997d..cf4e06ad9 100644 --- a/test/decimal.test +++ b/test/decimal.test @@ -23,31 +23,34 @@ do_execsql_test 1000 { SELECT decimal(1); } {1} do_execsql_test 1010 { - SELECT decimal(1.0); + SELECT decimal('1.0'); } {1.0} do_execsql_test 1020 { - SELECT decimal(0001.0); + SELECT decimal('0001.0'); } {1.0} do_execsql_test 1030 { - SELECT decimal(+0001.0); + SELECT decimal('+0001.0'); } {1.0} do_execsql_test 1040 { - SELECT decimal(-0001.0); + SELECT decimal('-0001.0'); } {-1.0} do_execsql_test 1050 { - SELECT decimal(1.0e72); + SELECT decimal('1.0e72'); } {1000000000000000000000000000000000000000000000000000000000000000000000000} # 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 do_execsql_test 1060 { - SELECT decimal(1.0e-72); + SELECT decimal('1.0e-72'); } {0.0000000000000000000000000000000000000000000000000000000000000000000000010} # 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 do_execsql_test 1070 { - SELECT decimal(-123e-4); + SELECT decimal('-123e-4'); } {-0.0123} do_execsql_test 1080 { - SELECT decimal(+123e+4); -} {1230000.0} + SELECT decimal('+123e+4'); +} {1230000} +do_execsql_test 1081 { + SELECT decimal_exp('+123e+4'); +} {+1.23e+06} do_execsql_test 2000 { @@ -161,6 +164,10 @@ do_execsql_test 6010 { SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v) FROM pow2, c WHERE pow2.x=ieee754_exponent(c.n); } {0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625} +do_execsql_test 6011 { + WITH c(n) AS (SELECT ieee754_from_blob(x'0000000000000001')) +SELECT decimal(c.n) FROM c; +} {0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625} do_execsql_test 6020 { WITH c(n) AS (SELECT ieee754_from_blob(x'7fefffffffffffff')) SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v) diff --git a/test/fp-speed-1.c b/test/fp-speed-1.c new file mode 100644 index 000000000..5eeb95d34 --- /dev/null +++ b/test/fp-speed-1.c @@ -0,0 +1,164 @@ +/* +** Performance testing of floating-point binary-to-decimal conversion for +** SQLite versus the standard library. +** +** To compile: +** +** make sqlite3.c +** gcc -Os -c sqlite3.c +** gcc -I. -Os test/fp-speed-1.c sqlite3.o -ldl -lm -lpthread +** +** To run the test: +** +** time ./a.out 0 10000000 <-- standard library +** time ./a.out 1 10000000 <-- SQLite +** time ./a.out 2 10000000 <-- SQLite with long-double disabled +*/ +static double aVal[] = { + -1.0163830486285643089e+063, + +0.0049243807391586981e-019, + +7.3818732407343994867e-095, + +7.0678595219225717143e+000, + +9.2807266319850025655e+120, + +5.8871050861933394663e+135, + -2.2998023621259662805e-168, + -1.5903581924910847482e+181, + +2.4313441816844978216e-001, + -3.8290987328945399091e-147, + +1.8787914062744001349e-009, + +0.7270653560487146653e-014, + +0.0639577697913183665e+107, + +5.2292338879315861930e-103, + +6.3747682672872231319e-021, + +8.6972339538329106984e-129, + -9.5074486051597691937e-026, + -8.6480257845368753018e-005, + -3.5657595775797470332e+017, + -7.8323106179731761351e+161, + +7.7813875274120800372e+077, + -1.8739718928360196596e-050, + +8.6898145915593357218e-051, + +6.0566766359877837871e+002, + +4.1703379217148160678e+199, + +2.1283871288746651560e+150, + -6.8395236083891810637e+128, + -6.2114299763939529155e-147, + -2.0753525742614637350e-149, + +5.8727459803944290257e-007, + +8.5888991062002101817e+010, + +6.8624461031355917632e+026, + -3.3053986756670905167e-075, + -4.3596843152763444945e-108, + +0.0834139520104099673e+098, + -8.8581986548904222440e-192, + -3.6622095428727698202e+038, + -6.6965852297025063260e+005, + +1.8204169347406488441e-054, + +6.5234508038649000384e-065, + +1.5923006018219011453e+083, + +1.7362555291656389480e+018, + +7.2875431976854785882e+160, + +1.2835880105958926748e-146, + +8.0516253320320819420e-113, + +6.6324633399381145440e-030, + -1.7126500070280062058e-149, + +1.6995738341583712335e+042, + +7.6048766923738663725e-112, + +0.6159117235449455043e-004, + +5.7544894355415943289e-135, + +8.2970228592690581890e-023, + -6.5531925360163067447e+020, + +5.8321334606187030050e+120, + +5.6557166509571891727e+095, + +0.3322789708438408704e-114, + -7.1210648776698686623e-050, + -9.6721262526706343301e+179, + -3.4583916571377395084e-106, + +4.7896094323214750793e-172, + -9.6926028040004137875e-056, + +7.0683848275381385483e-198, + -5.2970114182162961964e-007, + -4.4287021200905393271e-170, + +0.0728891155732808799e-189, + -9.1855462025879447465e+175, + +3.7294126234131007796e+015, + +2.6857421882792719241e+003, + -4.7070607333624685339e+039, + +7.2175820768279334274e+136, + -8.3678412534261163481e-115, + +2.2174844304241822163e+019, + +0.1949824588606861016e+112, + -9.7334052955672071912e+151, + -9.7793887766936999879e-142, + -5.1561164587416931561e+139, + -7.5048993577765174789e-022, + +7.3556076568687784568e+107, + -5.0681628575533599865e-144, + +1.5209705642027747811e+165, + -7.5989782535048296040e-101, + +1.3654137203389775871e-016, + -1.6441720554651372066e+087, + -4.9042433196141125923e+000, + -7.7063611961649130777e+118, + +0.1699427460930766201e+174, + +8.3374317849572216870e-145, + -5.2355330480469580072e+081, + -3.8510045942194147919e+141, + -6.3513622544326339887e-147, + +2.3869303484454428988e+049, + +3.8352715871620360268e-165, + -3.1263120493136887902e+035, + -5.5794797002556490823e+051, + -8.8109874479595604379e+142, + -4.3727360120203216922e+070, + -3.1109951189668539974e+170, + -9.4841878031704268232e+011, + -3.7398451668304407277e+067, + +4.8984042008915959963e-091, +}; +#define NN (sizeof(aVal)/sizeof(aVal[0])) + +#include "sqlite3.h" +#include +#include + +int main(int argc, char **argv){ + int i; + int cnt; + int fg; + char zBuf[1000]; + + if( argc!=3 ){ + fprintf(stderr, "Usage: %s FLAG COUNT\n", argv[0]); + return 1; + } + cnt = atoi(argv[2]); + fg = atoi(argv[1]); + + switch( fg % 3 ){ + case 0: { + printf("Doing %d calls to C-lib sprintf()\n", cnt); + for(i=0; i> - CC02.06.02', 'The following expense report is ready for approval: - -Employee Name: Christopher F. Calger -Status last changed by: Mollie E. Gustafson Ms -Expense Report Name: CC02.06.02 -Report Total: $3,972.93 -Amount Due Employee: $3,972.93 - - -To approve this expense report, click on the following link for Concur Expense. -http://expensexms.enron.com'); -INSERT INTO email([from],[to],subject,body) VALUES('jeff.duff@enron.com', 'julie.johnson@enron.com', 'Work request', 'Julie, - -Could you print off the current work request report by 1:30 today? - -Gentlemen, - -I''d like to review this today at 1:30 in our office. Also, could you provide -me with your activity reports so I can have Julie enter this information. - -JD'); -INSERT INTO email([from],[to],subject,body) VALUES('v.weldon@enron.com', 'gary.l.carrier@usa.dupont.com, scott.joyce@bankofamerica.com', 'Enron News', 'This could turn into something big.... -http://biz.yahoo.com/rf/010129/n29305829.html'); -INSERT INTO email([from],[to],subject,body) VALUES('mark.haedicke@enron.com', 'paul.simons@enron.com', 'Re: First Polish Deal!', 'Congrats! Things seem to be building rapidly now on the Continent. Mark'); -INSERT INTO email([from],[to],subject,body) VALUES('e..carter@enron.com', 't..robinson@enron.com', 'FW: Producers Newsletter 9-24-2001', ' -The producer lumber pricing sheet. - -----Original Message----- -From: Johnson, Jay -Sent: Tuesday, October 16, 2001 3:42 PM -To: Carter, Karen E. -Subject: FW: Producers Newsletter 9-24-2001 - - - - -----Original Message----- -From: Daigre, Sergai -Sent: Friday, September 21, 2001 8:33 PM -Subject: Producers Newsletter 9-24-2001 - - '); -INSERT INTO email([from],[to],subject,body) VALUES('david.delainey@enron.com', 'kenneth.lay@enron.com', 'Greater Houston Partnership', 'Ken, in response to the letter from Mr Miguel San Juan, my suggestion would -be to offer up the Falcon for their use; however, given the tight time frame -and your recent visit with Mr. Fox that it would be difficult for either you -or me to participate. - -I spoke to Max and he agrees with this approach. - -I hope this meets with your approval. - -Regards -Delainey'); -INSERT INTO email([from],[to],subject,body) VALUES('lachandra.fenceroy@enron.com', 'lindy.donoho@enron.com', 'FW: Bus Applications Meeting Follow Up', 'Lindy, - -Here is the original memo we discussed earlier. Please provide any information that you may have. - -Your cooperation is greatly appreciated. - -Thanks, - -lachandra.fenceroy@enron.com -713.853.3884 -877.498.3401 Pager - - -----Original Message----- -From: Bisbee, Joanne -Sent: Wednesday, September 26, 2001 7:50 AM -To: Fenceroy, LaChandra -Subject: FW: Bus Applications Meeting Follow Up - -Lachandra, Please get with David Duff today and see what this is about. Who are our TW accounting business users? - - -----Original Message----- -From: Koh, Wendy -Sent: Tuesday, September 25, 2001 2:41 PM -To: Bisbee, Joanne -Subject: Bus Applications Meeting Follow Up - -Lisa brought up a TW change effective Nov 1. It involves eliminating a turnback surcharge. I have no other information, but you might check with the business folks for any system changes required. - -Wendy'); -INSERT INTO email([from],[to],subject,body) VALUES('danny.mccarty@enron.com', 'fran.fagan@enron.com', 'RE: worksheets', 'Fran, - If Julie''s merit needs to be lump sum, just move it over to that column. Also, send me Eric Gadd''s sheets as well. Thanks. -Dan - - -----Original Message----- -From: Fagan, Fran -Sent: Thursday, December 20, 2001 11:10 AM -To: McCarty, Danny -Subject: worksheets - -As discussed, attached are your sheets for bonus and merit. - -Thanks, - -Fran Fagan -Sr. HR Rep -713.853.5219 - - - << File: McCartyMerit.xls >> << File: mccartyBonusCommercial_UnP.xls >> - -'); -INSERT INTO email([from],[to],subject,body) VALUES('bert.meyers@enron.com', 'shift.dl-portland@enron.com', 'OCTOBER SCHEDULE', 'TEAM, - -PLEASE SEND ME ANY REQUESTS THAT YOU HAVE FOR OCTOBER. SO FAR I HAVE THEM FOR LEAF. I WOULD LIKE TO HAVE IT DONE BY THE 15TH OF THE MONTH. ANY QUESTIONS PLEASE GIVE ME A CALL. - -BERT'); -INSERT INTO email([from],[to],subject,body) VALUES('errol.mclaughlin@enron.com', 'john.arnold@enron.com, bilal.bajwa@enron.com, john.griffith@enron.com,', 'TRV Notification: (NG - PROPT P/L - 09/27/2001)', 'The report named: NG - PROPT P/L , published as of 09/27/2001 is now available for viewing on the website.'); -INSERT INTO email([from],[to],subject,body) VALUES('patrice.mims@enron.com', 'calvin.eakins@enron.com', 'Re: Small business supply assistance', 'Hi Calvin - - -I spoke with Rickey (boy, is he long-winded!!). Gave him the name of our -credit guy, Russell Diamond. - -Thank for your help!'); -INSERT INTO email([from],[to],subject,body) VALUES('legal <.hall@enron.com>', 'stephanie.panus@enron.com', 'Termination update', 'City of Vernon and Salt River Project terminated their contracts. I will fax these notices to you.'); -INSERT INTO email([from],[to],subject,body) VALUES('d..steffes@enron.com', 'richard.shapiro@enron.com', 'EES / ENA Government Affairs Staffing & Outside Services', 'Rick -- - -Here is the information on staffing and outside services. Call if you need anything else. - -Jim - - '); -INSERT INTO email([from],[to],subject,body) VALUES('gelliott@industrialinfo.com', 'pcopello@industrialinfo.com', 'ECAAR (Gavin), WSCC (Diablo Canyon), & NPCC (Seabrook)', 'Dear Power Outage Database Customer, -Attached you will find an excel document. The outages contained within are forced or rescheduled outages. Your daily delivery will still contain these outages. -In addition to the two excel documents, there is a dbf file that is formatted like your daily deliveries you receive nightly. This will enable you to load the data into your regular database. Any questions please let me know. Thanks. -Greg Elliott -IIR, Inc. -713-783-5147 x 3481 -outages@industrialinfo.com -THE INFORMATION CONTAINED IN THIS E-MAIL IS LEGALLY PRIVILEGED AND CONFIDENTIAL INFORMATION INTENDED ONLY FOR THE USE OF THE INDIVIDUAL OR ENTITY NAMED ABOVE. YOU ARE HEREBY NOTIFIED THAT ANY DISSEMINATION, DISTRIBUTION, OR COPY OF THIS E-MAIL TO UNAUTHORIZED ENTITIES IS STRICTLY PROHIBITED. IF YOU HAVE RECEIVED THIS -E-MAIL IN ERROR, PLEASE DELETE IT. - - OUTAGE.dbf - - 111201R.xls - - 111201.xls '); -INSERT INTO email([from],[to],subject,body) VALUES('enron.announcements@enron.com', 'all_ena_egm_eim@enron.com', 'EWS Brown Bag', 'MARK YOUR LUNCH CALENDARS NOW ! - -You are invited to attend the EWS Brown Bag Lunch Series - -Featuring: RAY BOWEN, COO - -Topic: Enron Industrial Markets - -Thursday, March 15, 2001 -11:30 am - 12:30 pm -EB 5 C2 - - -You bring your lunch, Limited Seating -We provide drinks and dessert. RSVP x 3-9610'); -INSERT INTO email([from],[to],subject,body) VALUES('chris.germany@enron.com', 'ingrid.immer@williams.com', 'Re: About St Pauls', 'Sounds good to me. I bet this is next to the Warick?? Hotel. - - - - -"Immer, Ingrid" on 12/21/2000 11:48:47 AM -To: "''chris.germany@enron.com''" -cc: -Subject: About St Pauls - - - - - <> -? -?http://www.stpaulshouston.org/about.html - -Chris, - -I like the looks of this place.? What do you think about going here Christmas -eve?? They have an 11:00 a.m. service and a candlelight service at 5:00 p.m., -among others. - -Let me know.?? ii - - - About St Pauls.url - -'); -INSERT INTO email([from],[to],subject,body) VALUES('nas@cpuc.ca.gov', 'skatz@sempratrading.com, kmccrea@sablaw.com, thompson@wrightlaw.com,', 'Reply Brief filed July 31, 2000', ' - CPUC01-#76371-v1-Revised_Reply_Brief__Due_today_7_31_.doc'); -INSERT INTO email([from],[to],subject,body) VALUES('gascontrol@aglresources.com', 'dscott4@enron.com, lcampbel@enron.com', 'Alert Posted 10:00 AM November 20,2000: E-GAS Request Reminder', 'Alert Posted 10:00 AM November 20,2000: E-GAS Request Reminder -As discussed in the Winter Operations Meeting on Sept.29,2000, -E-Gas(Emergency Gas) will not be offered this winter as a service from AGLC. -Marketers and Poolers can receive gas via Peaking and IBSS nominations(daisy -chain) from other marketers up to the 6 p.m. Same Day 2 nomination cycle. -'); -INSERT INTO email([from],[to],subject,body) VALUES('dutch.quigley@enron.com', 'rwolkwitz@powermerchants.com', '', ' - -Here is a goody for you'); -INSERT INTO email([from],[to],subject,body) VALUES('ryan.o''rourke@enron.com', 'k..allen@enron.com, randy.bhatia@enron.com, frank.ermis@enron.com,', 'TRV Notification: (West VaR - 11/07/2001)', 'The report named: West VaR , published as of 11/07/2001 is now available for viewing on the website.'); -INSERT INTO email([from],[to],subject,body) VALUES('mjones7@txu.com', 'cstone1@txu.com, ggreen2@txu.com, timpowell@txu.com,', 'Enron / HPL Actuals for July 10, 2000', 'Teco Tap 10.000 / Enron ; 110.000 / HPL IFERC - -LS HPL LSK IC 30.000 / Enron -'); -INSERT INTO email([from],[to],subject,body) VALUES('susan.pereira@enron.com', 'kkw816@aol.com', 'soccer practice', 'Kathy- - -Is it safe to assume that practice is cancelled for tonight?? - -Susan Pereira'); -INSERT INTO email([from],[to],subject,body) VALUES('mark.whitt@enron.com', 'barry.tycholiz@enron.com', 'Huber Internal Memo', 'Please look at this. I didn''t know how deep to go with the desk. Do you think this works. - - '); -INSERT INTO email([from],[to],subject,body) VALUES('m..forney@enron.com', 'george.phillips@enron.com', '', 'George, -Give me a call and we will further discuss opportunities on the 13st floor. - -Thanks, -JMForney -3-7160'); -INSERT INTO email([from],[to],subject,body) VALUES('brad.mckay@enron.com', 'angusmcka@aol.com', 'Re: (no subject)', 'not yet'); -INSERT INTO email([from],[to],subject,body) VALUES('adam.bayer@enron.com', 'jonathan.mckay@enron.com', 'FW: Curve Fetch File', 'Here is the curve fetch file sent to me. It has plenty of points in it. If you give me a list of which ones you need we may be able to construct a secondary worksheet to vlookup the values. - -adam -35227 - - - -----Original Message----- -From: Royed, Jeff -Sent: Tuesday, September 25, 2001 11:37 AM -To: Bayer, Adam -Subject: Curve Fetch File - -Let me know if it works. It may be required to have a certain version of Oracle for it to work properly. - - - -Jeff Royed -Enron -Energy Operations -Phone: 713-853-5295'); -INSERT INTO email([from],[to],subject,body) VALUES('matt.smith@enron.com', 'yan.wang@enron.com', 'Report Formats', 'Yan, - -The merged reports look great. I believe the only orientation changes are to -"unmerge" the following six reports: - -31 Keystone Receipts -15 Questar Pipeline -40 Rockies Production -22 West_2 -23 West_3 -25 CIG_WIC - -The orientation of the individual reports should be correct. Thanks. - -Mat - -PS. Just a reminder to add the "*" by the title of calculated points.'); -INSERT INTO email([from],[to],subject,body) VALUES('michelle.lokay@enron.com', 'jimboman@bigfoot.com', 'Egyptian Festival', '---------------------- Forwarded by Michelle Lokay/ET&S/Enron on 09/07/2000 -10:08 AM --------------------------- - - -"Karkour, Randa" on 09/07/2000 09:01:04 AM -To: "''Agheb (E-mail)" , "Leila Mankarious (E-mail)" -, "''Marymankarious (E-mail)" -, "Michelle lokay (E-mail)" , "Ramy -Mankarious (E-mail)" -cc: - -Subject: Egyptian Festival - - - <> - - http://www.egyptianfestival.com/ - - - Egyptian Festival.url -'); -INSERT INTO email([from],[to],subject,body) VALUES('errol.mclaughlin@enron.com', 'sherry.dawson@enron.com', 'Urgent!!! --- New EAST books', 'This has to be done.................................. - -Thanks ----------------------- Forwarded by Errol McLaughlin/Corp/Enron on 12/20/2000 -08:39 AM --------------------------- - - - - From: William Kelly @ ECT 12/20/2000 08:31 AM - - -To: Kam Keiser/HOU/ECT@ECT, Darron C Giron/HOU/ECT@ECT, David -Baumbach/HOU/ECT@ECT, Errol McLaughlin/Corp/Enron@ENRON -cc: Kimat Singla/HOU/ECT@ECT, Kulvinder Fowler/NA/Enron@ENRON, Kyle R -Lilly/HOU/ECT@ECT, Jeff Royed/Corp/Enron@ENRON, Alejandra -Chavez/NA/Enron@ENRON, Crystal Hyde/HOU/ECT@ECT - -Subject: New EAST books - -We have new book names in TAGG for our intramonth portfolios and it is -extremely important that any deal booked to the East is communicated quickly -to someone on my team. I know it will take some time for the new names to -sink in and I do not want us to miss any positions or P&L. - -Thanks for your help on this. - -New: -Scott Neal : East Northeast -Dick Jenkins: East Marketeast - -WK -'); -INSERT INTO email([from],[to],subject,body) VALUES('david.forster@enron.com', 'eol.wide@enron.com', 'Change to Stack Manager', 'Effective immediately, there is a change to the Stack Manager which will -affect any Inactive Child. - -An inactive Child with links to Parent products will not have their -calculated prices updated until the Child product is Activated. - -When the Child Product is activated, the price will be recalculated and -updated BEFORE it is displayed on the web. - -This means that if you are inputting a basis price on a Child product, you -will not see the final, calculated price until you Activate the product, at -which time the customer will also see it. - -If you have any questions, please contact the Help Desk on: - -Americas: 713 853 4357 -Europe: + 44 (0) 20 7783 7783 -Asia/Australia: +61 2 9229 2300 - -Dave'); -INSERT INTO email([from],[to],subject,body) VALUES('vince.kaminski@enron.com', 'jhh1@email.msn.com', 'Re: Light reading - see pieces beginning on page 7', 'John, - -I saw it. Very interesting. - -Vince - - - - - -"John H Herbert" on 07/28/2000 08:38:08 AM -To: "Vince J Kaminski" -cc: -Subject: Light reading - see pieces beginning on page 7 - - -Cheers and have a nice weekend, - - -JHHerbert - - - - - - gd000728.pdf - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('matthew.lenhart@enron.com', 'mmmarcantel@equiva.com', 'RE:', 'i will try to line up a pig for you '); -INSERT INTO email([from],[to],subject,body) VALUES('jae.black@enron.com', 'claudette.harvey@enron.com, chaun.roberts@enron.com, judy.martinez@enron.com,', 'Disaster Recovery Equipment', 'As a reminder...there are several pieces of equipment that are set up on the 30th Floor, as well as on our floor, for the Disaster Recovery Team. PLEASE DO NOT TAKE, BORROW OR USE this equipment. Should you need to use another computer system, other than yours, or make conference calls please work with your Assistant to help find or set up equipment for you to use. - -Thanks for your understanding in this matter. - -T.Jae Black -East Power Trading -Assistant to Kevin Presto -off. 713-853-5800 -fax 713-646-8272 -cell 713-539-4760'); -INSERT INTO email([from],[to],subject,body) VALUES('eric.bass@enron.com', 'dale.neuner@enron.com', '5 X 24', 'Dale, - -Have you heard anything more on the 5 X 24s? We would like to get this -product out ASAP. - - -Thanks, - -Eric'); -INSERT INTO email([from],[to],subject,body) VALUES('messenger@smartreminders.com', 'm..tholt@enron.com', '10% Coupon - PrintPal Printer Cartridges - 100% Guaranteed', '[IMAGE] -[IMAGE][IMAGE][IMAGE] -Dear SmartReminders Member, - [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] - - - - - - - - - - - - - - - - - - - - - -We respect your privacy and are a Certified Participant of the BBBOnLine - Privacy Program. To be removed from future offers,click here. -SmartReminders.com is a permission based service. To unsubscribe click here . '); -INSERT INTO email([from],[to],subject,body) VALUES('benjamin.rogers@enron.com', 'mark.bernstein@enron.com', '', 'The guy you are talking about left CIN under a "cloud of suspicion" sort of -speak. He was the one who got into several bad deals and PPA''s in California -for CIN, thus he left on a bad note. Let me know if you need more detail -than that, I felt this was the type of info you were looking for. Thanks! -Ben'); -INSERT INTO email([from],[to],subject,body) VALUES('enron_update@concureworkplace.com', 'michelle.cash@enron.com', 'Expense Report Receipts Not Received', 'Employee Name: Michelle Cash -Report Name: Houston Cellular 8-11-01 -Report Date: 12/13/01 -Report ID: 594D37C9ED2111D5B452 -Submitted On: 12/13/01 - -You are only allowed 2 reports with receipts outstanding. Your expense reports will not be paid until you meet this requirement.'); -INSERT INTO email([from],[to],subject,body) VALUES('susan.mara@enron.com', 'ray.alvarez@enron.com, mark.palmer@enron.com, karen.denne@enron.com,', 'CAISO Emergency Motion -- to discontinue market-based rates for', 'FYI. the latest broadside against the generators. - -Sue Mara -Enron Corp. -Tel: (415) 782-7802 -Fax:(415) 782-7854 ------ Forwarded by Susan J Mara/NA/Enron on 06/08/2001 12:24 PM ----- - - - "Milner, Marcie" 06/08/2001 11:13 AM To: "''smara@enron.com''" cc: Subject: CAISO Emergency Motion - - -Sue, did you see this emergency motion the CAISO filed today? Apparently -they are requesting that FERC discontinue market-based rates immediately and -grant refunds plus interest on the difference between cost-based rates and -market revenues received back to May 2000. They are requesting the -commission act within 14 days. Have you heard anything about what they are -doing? - -Marcie - -http://www.caiso.com/docs/2001/06/08/200106081005526469.pdf -'); -INSERT INTO email([from],[to],subject,body) VALUES('fletcher.sturm@enron.com', 'eloy.escobar@enron.com', 'Re: General Brinks Position Meeting', 'Eloy, - -Who is General Brinks? - -Fletch'); -INSERT INTO email([from],[to],subject,body) VALUES('nailia.dindarova@enron.com', 'richard.shapiro@enron.com', 'Documents for Mark Frevert (on EU developments and lessons from', 'Rick, - -Here are the documents that Peter has prepared for Mark Frevert. - -Nailia ----------------------- Forwarded by Nailia Dindarova/LON/ECT on 25/06/2001 -16:36 --------------------------- - - -Nailia Dindarova -25/06/2001 15:36 -To: Michael Brown/Enron@EUEnronXGate -cc: Ross Sankey/Enron@EUEnronXGate, Eric Shaw/ENRON@EUEnronXGate, Peter -Styles/LON/ECT@ECT - -Subject: Documents for Mark Frevert (on EU developments and lessons from -California) - -Michael, - - -These are the documents that Peter promised to give to you for Mark Frevert. -He has now handed them to him in person but asked me to transmit them -electronically to you, as well as Eric and Ross. - -Nailia - - - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('peggy.a.kostial@accenture.com', 'dave.samuels@enron.com', 'EOL-Accenture Deal Sheet', 'Dave - - -Attached are our comments and suggested changes. Please call to review. - -On the time line for completion, we have four critical steps to complete: - Finalize market analysis to refine business case, specifically - projected revenue stream - Complete counterparty surveying, including targeting 3 CPs for letters - of intent - Review Enron asset base for potential reuse/ licensing - Contract negotiations - -Joe will come back to us with an updated time line, but it is my -expectation that we are still on the same schedule (we just begun week -three) with possibly a week or so slippage.....contract negotiations will -probably be the critical path. - -We will send our cut at the actual time line here shortly. Thanks, - -Peggy - -(See attached file: accenture-dealpoints v2.doc) - - accenture-dealpoints v2.doc '); -INSERT INTO email([from],[to],subject,body) VALUES('thomas.martin@enron.com', 'thomas.martin@enron.com', 'Re: Guadalupe Power Partners LP', '---------------------- Forwarded by Thomas A Martin/HOU/ECT on 03/20/2001 -03:49 PM --------------------------- - - -Thomas A Martin -10/11/2000 03:55 PM -To: Patrick Wade/HOU/ECT@ECT -cc: -Subject: Re: Guadalupe Power Partners LP - -The deal is physically served at Oasis Waha or Oasis Katy and is priced at -either HSC, Waha or Katytailgate GD at buyers option three days prior to -NYMEX close. - -'); -INSERT INTO email([from],[to],subject,body) VALUES('judy.townsend@enron.com', 'dan.junek@enron.com, chris.germany@enron.com', 'Columbia Distribution''s Capacity Available for Release - Sum', '---------------------- Forwarded by Judy Townsend/HOU/ECT on 03/09/2001 11:04 -AM --------------------------- - - -agoddard@nisource.com on 03/08/2001 09:16:57 AM -To: " - *Koch, Kent" , " - -*Millar, Debra" , " - *Burke, Lynn" - -cc: " - *Heckathorn, Tom" -Subject: Columbia Distribution''s Capacity Available for Release - Sum - - -Attached is Columbia Distribution''s notice of capacity available for release -for -the summer of 2001 (Apr. 2001 through Oct. 2001). - -Please note that the deadline for bids is 3:00pm EST on March 20, 2001. - -If you have any questions, feel free to contact any of the representatives -listed -at the bottom of the attachment. - -Aaron Goddard - - - - - - 2001Summer.doc -'); -INSERT INTO email([from],[to],subject,body) VALUES('rhonda.denton@enron.com', 'tim.belden@enron.com, dana.davis@enron.com, genia.fitzgerald@enron.com,', 'Split Rock Energy LLC', 'We have received the executed EEI contract from this CP dated 12/12/2000. -Copies will be distributed to Legal and Credit.'); -INSERT INTO email([from],[to],subject,body) VALUES('kerrymcelroy@dwt.com', 'jack.speer@alcoa.com, crow@millernash.com, michaelearly@earthlink.net,', 'Oral Argument Request', ' - Oral Argument Request.doc'); -INSERT INTO email([from],[to],subject,body) VALUES('mike.carson@enron.com', 'rlmichaelis@hormel.com', '', 'Did you come in town this wk end..... My new number at our house is : -713-668-3712...... my cell # is 281-381-7332 - -the kid'); -INSERT INTO email([from],[to],subject,body) VALUES('cooper.richey@enron.com', 'trycooper@hotmail.com', 'FW: Contact Info', ' - ------Original Message----- -From: Punja, Karim -Sent: Thursday, December 13, 2001 2:35 PM -To: Richey, Cooper -Subject: Contact Info - - -Cooper, - -Its been a real pleasure working with you (even though it was for only a small amount of time) -I hope we can stay in touch. - -Home# 234-0249 -email: kpunja@hotmail.com - -Take Care, - -Karim. - '); -INSERT INTO email([from],[to],subject,body) VALUES('bjm30@earthlink.net', 'mcguinn.k@enron.com, mcguinn.ian@enron.com, mcguinn.stephen@enron.com,', 'email address change', 'Hello all. - -I haven''t talked to many of you via email recently but I do want to give you -my new address for your email file: - - bjm30@earthlink.net - -I hope all is well. - -Brian McGuinn'); -INSERT INTO email([from],[to],subject,body) VALUES('shelley.corman@enron.com', 'steve.hotte@enron.com', 'Flat Panels', 'Can you please advise what is going on with the flat panels that we had planned to distribute to our gas logistics team. It was in the budget and we had the okay, but now I''m hearing there is some hold-up & the units are stored on 44. - -Shelley'); -INSERT INTO email([from],[to],subject,body) VALUES('sara.davidson@enron.com', 'john.schwartzenburg@enron.com, scott.dieball@enron.com, recipients@enron.com,', '2001 Enron Law Conference (Distribution List 2)', ' Enron Law Conference - -San Antonio, Texas May 2-4, 2001 Westin Riverwalk - - See attached memo for more details!! - - -? Registration for the law conference this year will be handled through an -Online RSVP Form on the Enron Law Conference Website at -http://lawconference.corp.enron.com. The website is still under construction -and will not be available until Thursday, March 15, 2001. - -? We will send you another e-mail to confirm when the Law Conference Website -is operational. - -? Please complete the Online RSVP Form as soon as it is available and submit -it no later than Friday, March 30th. - - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('tori.kuykendall@enron.com', 'heath.b.taylor@accenture.com', 'Re:', 'hey - thats funny about john - he definitely remembers him - i''ll call pat -and let him know - we are coming on saturday - i just havent had a chance to -call you guys back -- looking forward to it -- i probably need the -directions again though'); -INSERT INTO email([from],[to],subject,body) VALUES('darron.giron@enron.com', 'bryce.baxter@enron.com', 'Re: Feedback for Audrey Cook', 'Bryce, - -I''ll get it done today. - -DG 3-9573 - - - - - - From: Bryce Baxter 06/12/2000 07:15 PM - - -To: Darron C Giron/HOU/ECT@ECT -cc: -Subject: Feedback for Audrey Cook - -You were identified as a reviewer for Audrey Cook. If possible, could you -complete her feedback by end of business Wednesday? It will really help me -in the PRC process to have your input. Thanks. - -'); -INSERT INTO email([from],[to],subject,body) VALUES('casey.evans@enron.com', 'stephanie.sever@enron.com', 'Gas EOL ID', 'Stephanie, - -In conjunction with the recent movement of several power traders, they are changing the names of their gas books as well. The names of the new gas books and traders are as follows: - -PWR-NG-LT-SPP: Mike Carson -PWR-NG-LT-SERC: Jeff King - -If you need to know their power desk to map their ID to their gas books, those desks are as follows: - -EPMI-LT-SPP: Mike Carson -EPMI-LT-SERC: Jeff King - -I will be in training this afternoon, but will be back when class is over. Let me know if you have any questions. - -Thanks for your help! -Casey'); -INSERT INTO email([from],[to],subject,body) VALUES('darrell.schoolcraft@enron.com', 'david.roensch@enron.com, kimberly.watson@enron.com, michelle.lokay@enron.com,', 'Postings', 'Please see the attached. - - -ds - - - - - '); -INSERT INTO email([from],[to],subject,body) VALUES('mcominsky@aol.com', 'cpatman@bracepatt.com, james_derrick@enron.com', 'Jurisprudence Luncheon', 'Carrin & Jim -- - -It was an honor and a pleasure to meet both of you yesterday. I know we will -have fun working together on this very special event. - -Jeff left the jurisprudence luncheon lists for me before he left on vacation. - I wasn''t sure whether he transmitted them to you as well. Would you please -advise me if you would like them sent to you? I can email the MS Excel files -or I can fax the hard copies to you. Please advise what is most convenient. - -I plan to be in town through the holidays and can be reached by phone, email, -or cell phone at any time. My cell phone number is 713/705-4829. - -Thanks again for your interest in the ADL''s work. Martin. - -Martin B. Cominsky -Director, Southwest Region -Anti-Defamation League -713/627-3490, ext. 122 -713/627-2011 (fax) -MCominsky@aol.com'); -INSERT INTO email([from],[to],subject,body) VALUES('phillip.love@enron.com', 'todagost@utmb.edu, gbsonnta@utmb.edu', 'New President', 'I had a little bird put a word in my ear. Is there any possibility for Ben -Raimer to be Bush''s secretary of HHS? Just curious about that infamous UTMB -rumor mill. Hope things are well, happy holidays. -PL'); -INSERT INTO email([from],[to],subject,body) VALUES('marie.heard@enron.com', 'ehamilton@fna.com', 'ISDA Master Agreement', 'Erin: - -Pursuant to your request, attached are the Schedule to the ISDA Master Agreement, together with Paragraph 13 to the ISDA Credit Support Annex. Please let me know if you need anything else. We look forward to hearing your comments. - -Marie - -Marie Heard -Senior Legal Specialist -Enron North America Corp. -Phone: (713) 853-3907 -Fax: (713) 646-3490 -marie.heard@enron.com - - '); -INSERT INTO email([from],[to],subject,body) VALUES('andrea.ring@enron.com', 'beverly.beaty@enron.com', 'Re: Tennessee Buy - Louis Dreyfus', 'Beverly - once again thanks so much for your help on this. - - - - '); -INSERT INTO email([from],[to],subject,body) VALUES('karolyn.criado@enron.com', 'j..bonin@enron.com, felicia.case@enron.com, b..clapp@enron.com,', 'Price List week of Oct. 8-9, 2001', ' -Please contact me if you have any questions regarding last weeks prices. - -Thank you, -Karolyn Criado -3-9441 - - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('kevin.presto@enron.com', 'edward.baughman@enron.com, billy.braddock@enron.com', 'Associated', 'Please begin working on filling our Associated short position in 02. I would like to take this risk off the books. - -In addition, please find out what a buy-out of VEPCO would cost us. With Rogers transitioning to run our retail risk management, I would like to clean up our customer positions. - -We also need to continue to explore a JEA buy-out. - -Thanks.'); -INSERT INTO email([from],[to],subject,body) VALUES('stacy.dickson@enron.com', 'gregg.penman@enron.com', 'RE: Constellation TC 5-7-01', 'Gregg, - -I am at home with a sick baby. (Lots of fun!) I will call you about this -tomorrow. - -Stacy'); -INSERT INTO email([from],[to],subject,body) VALUES('joe.quenet@enron.com', 'dfincher@utilicorp.com', '', 'hey big guy.....check this out..... - - w ww.gorelieberman-2000.com/'); -INSERT INTO email([from],[to],subject,body) VALUES('k..allen@enron.com', 'jacqestc@aol.com', '', 'Jacques, - -I sent you a fax of Kevin Kolb''s comments on the release. The payoff on the note would be $36,248 ($36090(principal) + $158 (accrued interest)). -This is assuming we wrap this up on Tuesday. - -Please email to confirm that their changes are ok so I can set up a meeting on Tuesday to reach closure. - -Phillip'); -INSERT INTO email([from],[to],subject,body) VALUES('kourtney.nelson@enron.com', 'mike.swerzbin@enron.com', 'Adjusted L/R Balance', 'Mike, - -I placed the adjusted L/R Balance on the Enronwest site. It is under the "Staff/Kourtney Nelson". There are two links: - -1) "Adj L_R" is the same data/format from the weekly strategy meeting. -2) "New Gen 2001_2002" link has all of the supply side info that is used to calculate the L/R balance - -Please note the Data Flag column, a value of "3" indicates the project was cancelled, on hold, etc and is not included in the calc. - -Both of these sheets are interactive Excel spreadsheets and thus you can play around with the data as you please. Also, James Bruce is working to get his gen report on the web. That will help with your access to information on new gen. - -Please let me know if you have any questions or feedback, - -Kourtney - - - -Kourtney Nelson -Fundamental Analysis -Enron North America -(503) 464-8280 -kourtney.nelson@enron.com'); -INSERT INTO email([from],[to],subject,body) VALUES('d..thomas@enron.com', 'naveed.ahmed@enron.com', 'FW: Current Enron TCC Portfolio', ' - ------Original Message----- -From: Grace, Rebecca M. -Sent: Monday, December 17, 2001 9:44 AM -To: Thomas, Paul D. -Cc: Cashion, Jim; Allen, Thresa A.; May, Tom -Subject: RE: Current Enron TCC Portfolio - - -Paul, - -I reviewed NY''s list. I agree with all of their contracts numbers and mw amounts. - -Call if you have any more questions. - -Rebecca - - - - -----Original Message----- -From: Thomas, Paul D. -Sent: Monday, December 17, 2001 9:08 AM -To: Grace, Rebecca M. -Subject: FW: Current Enron TCC Portfolio - - << File: enrontccs.xls >> -Rebecca, -Let me know if you see any differences. - -Paul -X 3-0403 ------Original Message----- -From: Thomas, Paul D. -Sent: Monday, December 17, 2001 9:04 AM -To: Ahmed, Naveed -Subject: FW: Current Enron TCC Portfolio - - - - ------Original Message----- -From: Thomas, Paul D. -Sent: Thursday, December 13, 2001 10:01 AM -To: Baughman, Edward D. -Subject: Current Enron TCC Portfolio - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('stephanie.panus@enron.com', 'william.bradford@enron.com, debbie.brackett@enron.com,', 'Coastal Merchant Energy/El Paso Merchant Energy', 'Coastal Merchant Energy, L.P. merged with and into El Paso Merchant Energy, -L.P., effective February 1, 2001, with the surviving entity being El Paso -Merchant Energy, L.P. We currently have ISDA Master Agreements with both -counterparties. Please see the attached memo regarding the existing Masters -and let us know which agreement should be terminated. - -Thanks, -Stephanie -'); -INSERT INTO email([from],[to],subject,body) VALUES('kam.keiser@enron.com', 'c..kenne@enron.com', 'RE: What about this too???', ' - - -----Original Message----- -From: Kenne, Dawn C. -Sent: Wednesday, February 06, 2002 11:50 AM -To: Keiser, Kam -Subject: What about this too??? - - - << File: Netco Trader Matrix.xls >> - '); -INSERT INTO email([from],[to],subject,body) VALUES('chris.meyer@enron.com', 'joe.parks@enron.com', 'Centana', 'Talked to Chip. We do need Cash Committe approval given the netting feature of your deal, which means Batch Funding Request. Please update per my previous e-mail and forward. - -Thanks - -chris -x31666'); -INSERT INTO email([from],[to],subject,body) VALUES('debra.perlingiere@enron.com', 'jworman@academyofhealth.com', '', 'Have a great weekend! Happy Fathers Day! - - -Debra Perlingiere -Enron North America Corp. -1400 Smith Street, EB 3885 -Houston, Texas 77002 -dperlin@enron.com -Phone 713-853-7658 -Fax 713-646-3490'); -INSERT INTO email([from],[to],subject,body) VALUES('outlook.team@enron.com', '', 'Demo by Martha Janousek of Dashboard & Pipeline Profile / Julia &', 'CALENDAR ENTRY: APPOINTMENT - -Description: - Demo by Martha Janousek of Dashboard & Pipeline Profile / Julia & Dir Rpts. - 4102 - -Date: 1/5/2001 -Time: 9:00 AM - 10:00 AM (Central Standard Time) - -Chairperson: Outlook Migration Team - -Detailed Description:'); -INSERT INTO email([from],[to],subject,body) VALUES('diana.seifert@enron.com', 'mark.taylor@enron.com', 'Guest access Chile', 'Hello Mark, - -Justin Boyd told me that your can help me with questions regarding Chile. -We got a request for guest access through MG. -The company is called Escondida and is a subsidiary of BHP Australia. - -Please advise if I can set up a guest account or not. -F.Y.I.: MG is planning to put a "in w/h Chile" contract for Copper on-line as -soon as Enron has done the due diligence for this country. -Thanks ! - - -Best regards - -Diana Seifert -EOL PCG'); -INSERT INTO email([from],[to],subject,body) VALUES('enron_update@concureworkplace.com', 'mark.whitt@enron.com', '<> - 121001', 'The Approval status has changed on the following report: - -Status last changed by: Barry L. Tycholiz -Expense Report Name: 121001 -Report Total: $198.98 -Amount Due Employee: $198.98 -Amount Approved: $198.98 -Amount Paid: $0.00 -Approval Status: Approved -Payment Status: Pending - - -To review this expense report, click on the following link for Concur Expense. -http://expensexms.enron.com'); -INSERT INTO email([from],[to],subject,body) VALUES('kevin.hyatt@enron.com', '', 'Technical Support', 'Outside the U.S., please refer to the list below: - -Australia: -1800 678-515 -support@palm-au.com - -Canada: -1905 305-6530 -support@palm.com - -New Zealand: -0800 446-398 -support@palm-nz.com - -U.K.: -0171 867 0108 -eurosupport@palm.3com.com - -Please refer to the Worldwide Customer Support card for a complete technical support contact list.'); -INSERT INTO email([from],[to],subject,body) VALUES('geoff.storey@enron.com', 'dutch.quigley@enron.com', 'RE:', 'duke contact? - - -----Original Message----- -From: Quigley, Dutch -Sent: Wednesday, October 31, 2001 10:14 AM -To: Storey, Geoff -Subject: RE: - -bp corp Albert LaMore 281-366-4962 - -running the reports now - - - -----Original Message----- -From: Storey, Geoff -Sent: Wednesday, October 31, 2001 10:10 AM -To: Quigley, Dutch -Subject: RE: - -give me a contact over there too -BP - - - -----Original Message----- -From: Quigley, Dutch -Sent: Wednesday, October 31, 2001 9:42 AM -To: Storey, Geoff -Subject: - -Coral Jeff Whitnah 713-767-5374 -Relaint Steve McGinn 713-207-4000'); -INSERT INTO email([from],[to],subject,body) VALUES('pete.davis@enron.com', 'pete.davis@enron.com', 'Start Date: 4/22/01; HourAhead hour: 3; ', 'Start Date: 4/22/01; HourAhead hour: 3; No ancillary schedules awarded. -Variances detected. -Variances detected in Load schedule. - - LOG MESSAGES: - -PARSING FILE -->> O:\Portland\WestDesk\California Scheduling\ISO Final -Schedules\2001042203.txt - ----- Load Schedule ---- -$$$ Variance found in table tblLoads. - Details: (Hour: 3 / Preferred: 1.92 / Final: 1.89) - TRANS_TYPE: FINAL - LOAD_ID: PGE4 - MKT_TYPE: 2 - TRANS_DATE: 4/22/01 - SC_ID: EPMI - -'); -INSERT INTO email([from],[to],subject,body) VALUES('john.postlethwaite@enron.com', 'john.zufferli@enron.com', 'Reference', 'John, hope things are going well up there for you. The big day is almost here for you and Jessica. I was wondering if I could use your name as a job reference if need be. I am just trying to get everything in order just in case something happens. - -John'); -INSERT INTO email([from],[to],subject,body) VALUES('jeffrey.shankman@enron.com', 'lschiffm@jonesday.com', 'Re:', 'I saw you called on the cell this a.m. Sorry I missed you. (I was in the -shower). I have had a shitty week--I suspect my silence (not only to you, -but others) after our phone call is a result of the week. I''m seeing Glen at -11:15....talk to you'); -INSERT INTO email([from],[to],subject,body) VALUES('litebytz@enron.com', '', 'Lite Bytz RSVP', ' -This week''s Lite Bytz presentation will feature the following TOOLZ speaker: - -Richard McDougall -Solaris 8 -Thursday, June 7, 2001 - -If you have not already signed up, please RSVP via email to litebytz@enron.com by the end of the day Tuesday, June 5, 2001. - -*Remember: this is now a Brown Bag Event--so bring your lunch and we will provide cookies and drinks. - -Click below for more details. - -http://home.enron.com:84/messaging/litebytztoolzprint.jpg'); - COMMIT; - } -} {} - -############################################################################### -# Everything above just builds an interesting test database. The actual -# tests come after this comment. -############################################################################### - -do_test fts1c-1.2 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark' - } -} {6 17 25 38 40 42 73 74} -do_test fts1c-1.3 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'susan' - } -} {24 40} -do_test fts1c-1.4 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark susan' - } -} {40} -do_test fts1c-1.5 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'susan mark' - } -} {40} -do_test fts1c-1.6 { - execsql { - SELECT rowid FROM email WHERE email MATCH '"mark susan"' - } -} {} -do_test fts1c-1.7 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark -susan' - } -} {6 17 25 38 42 73 74} -do_test fts1c-1.8 { - execsql { - SELECT rowid FROM email WHERE email MATCH '-mark susan' - } -} {24} -do_test fts1c-1.9 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark OR susan' - } -} {6 17 24 25 38 40 42 73 74} - -# Some simple tests of the automatic "offsets(email)" column. In the sample -# data set above, only one message, number 20, contains the words -# "gas" and "reminder" in both body and subject. -# -do_test fts1c-2.1 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE email MATCH 'gas reminder' - } -} {20 {2 0 42 3 2 1 54 8 3 0 42 3 3 1 54 8 3 0 129 3 3 0 143 3 3 0 240 3}} -do_test fts1c-2.2 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE email MATCH 'subject:gas reminder' - } -} {20 {2 0 42 3 2 1 54 8 3 1 54 8}} -do_test fts1c-2.3 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE email MATCH 'body:gas reminder' - } -} {20 {2 1 54 8 3 0 42 3 3 1 54 8 3 0 129 3 3 0 143 3 3 0 240 3}} -do_test fts1c-2.4 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE subject MATCH 'gas reminder' - } -} {20 {2 0 42 3 2 1 54 8}} -do_test fts1c-2.5 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE body MATCH 'gas reminder' - } -} {20 {3 0 42 3 3 1 54 8 3 0 129 3 3 0 143 3 3 0 240 3}} - -# Document 32 contains 5 instances of the world "child". But only -# 3 of them are paired with "product". Make sure only those instances -# that match the phrase appear in the offsets(email) list. -# -do_test fts1c-3.1 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE body MATCH 'child product' AND +rowid=32 - } -} {32 {3 0 94 5 3 0 114 5 3 0 207 5 3 1 213 7 3 0 245 5 3 1 251 7 3 0 409 5 3 1 415 7 3 1 493 7}} -do_test fts1c-3.2 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE body MATCH '"child product"' - } -} {32 {3 0 207 5 3 1 213 7 3 0 245 5 3 1 251 7 3 0 409 5 3 1 415 7}} - -# Snippet generator tests -# -do_test fts1c-4.1 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'subject:gas reminder' - } -} {{Alert Posted 10:00 AM November 20,2000: E-GAS Request Reminder}} -do_test fts1c-4.2 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'christmas candlelight' - } -} {{... place.? What do you think about going here Christmas -eve?? They have an 11:00 a.m. service and a candlelight service at 5:00 p.m., -among others. ...}} - -do_test fts1c-4.3 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'deal sheet potential reuse' - } -} {{EOL-Accenture Deal Sheet ... intent - Review Enron asset base for potential reuse/ licensing - Contract negotiations ...}} -do_test fts1c-4.4 { - execsql { - SELECT snippet(email,'<<<','>>>',' ') FROM email - WHERE email MATCH 'deal sheet potential reuse' - } -} {{EOL-Accenture <<>> <<>> intent - Review Enron asset base for <<>> <<>>/ licensing - Contract negotiations }} -do_test fts1c-4.5 { - execsql { - SELECT snippet(email,'<<<','>>>',' ') FROM email - WHERE email MATCH 'first things' - } -} {{Re: <<>> Polish Deal! Congrats! <<>> seem to be building rapidly now on the }} -do_test fts1c-4.6 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'chris is here' - } -} {{chris.germany@enron.com ... Sounds good to me. I bet this is next to the Warick?? Hotel. ... place.? What do you think about going here Christmas -eve?? They have an 11:00 a.m. ...}} -do_test fts1c-4.7 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH '"pursuant to"' - } -} {{Erin: - -Pursuant to your request, attached are the Schedule to ...}} -do_test fts1c-4.8 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'ancillary load davis' - } -} {{pete.davis@enron.com ... Start Date: 4/22/01; HourAhead hour: 3; No ancillary schedules awarded. -Variances detected. -Variances detected in Load schedule. - - LOG MESSAGES: - -PARSING ...}} - -# Combinations of AND and OR operators: -# -do_test fts1c-5.1 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'questar enron OR com' - } -} {{matt.smith@enron.com ... six reports: - -31 Keystone Receipts -15 Questar Pipeline -40 Rockies Production -22 West_2 ...}} -do_test fts1c-5.2 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'enron OR com questar' - } -} {{matt.smith@enron.com ... six reports: - -31 Keystone Receipts -15 Questar Pipeline -40 Rockies Production -22 West_2 ...}} - -finish_test diff --git a/test/fts1d.test b/test/fts1d.test deleted file mode 100644 index ea2303489..000000000 --- a/test/fts1d.test +++ /dev/null @@ -1,65 +0,0 @@ -# 2006 October 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 script is testing the FTS1 module, and in particular -# the Porter stemmer. -# -# $Id: fts1d.test,v 1.1 2006/10/01 18:41:21 drh Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -do_test fts1d-1.1 { - execsql { - CREATE VIRTUAL TABLE t1 USING fts1(content, tokenize porter); - INSERT INTO t1(rowid, content) VALUES(1, 'running and jumping'); - SELECT rowid FROM t1 WHERE content MATCH 'run jump'; - } -} {1} -do_test fts1d-1.2 { - execsql { - SELECT snippet(t1) FROM t1 WHERE t1 MATCH 'run jump'; - } -} {{running and jumping}} -do_test fts1d-1.3 { - execsql { - INSERT INTO t1(rowid, content) - VALUES(2, 'abcdefghijklmnopqrstuvwyxz'); - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH 'abcdefghijqrstuvwyxz' - } -} {2 abcdefghijklmnopqrstuvwyxz} -do_test fts1d-1.4 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH 'abcdefghijXXXXqrstuvwyxz' - } -} {2 abcdefghijklmnopqrstuvwyxz} -do_test fts1d-1.5 { - execsql { - INSERT INTO t1(rowid, content) - VALUES(3, 'The value is 123456789'); - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH '123789' - } -} {3 {The value is 123456789}} -do_test fts1d-1.6 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH '123000000789' - } -} {3 {The value is 123456789}} - - -finish_test diff --git a/test/fts1e.test b/test/fts1e.test deleted file mode 100644 index 479cfac91..000000000 --- a/test/fts1e.test +++ /dev/null @@ -1,85 +0,0 @@ -# 2006 October 19 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The -# focus of this script is testing deletions in the FTS1 module. -# -# $Id: fts1e.test,v 1.1 2006/10/19 23:28:35 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -# Construct a full-text search table containing keywords which are the -# ordinal numbers of the bit positions set for a sequence of integers, -# which are used for the rowid. There are a total of 30 INSERT and -# DELETE statements, so that we'll test both the segmentMerge() merge -# (over the first 16) and the termSelect() merge (over the level-1 -# segment and 14 level-0 segments). -db eval { - CREATE VIRTUAL TABLE t1 USING fts1(content); - INSERT INTO t1 (rowid, content) VALUES(1, 'one'); - INSERT INTO t1 (rowid, content) VALUES(2, 'two'); - INSERT INTO t1 (rowid, content) VALUES(3, 'one two'); - INSERT INTO t1 (rowid, content) VALUES(4, 'three'); - DELETE FROM t1 WHERE rowid = 1; - INSERT INTO t1 (rowid, content) VALUES(5, 'one three'); - INSERT INTO t1 (rowid, content) VALUES(6, 'two three'); - INSERT INTO t1 (rowid, content) VALUES(7, 'one two three'); - DELETE FROM t1 WHERE rowid = 4; - INSERT INTO t1 (rowid, content) VALUES(8, 'four'); - INSERT INTO t1 (rowid, content) VALUES(9, 'one four'); - INSERT INTO t1 (rowid, content) VALUES(10, 'two four'); - DELETE FROM t1 WHERE rowid = 7; - INSERT INTO t1 (rowid, content) VALUES(11, 'one two four'); - INSERT INTO t1 (rowid, content) VALUES(12, 'three four'); - INSERT INTO t1 (rowid, content) VALUES(13, 'one three four'); - DELETE FROM t1 WHERE rowid = 10; - INSERT INTO t1 (rowid, content) VALUES(14, 'two three four'); - INSERT INTO t1 (rowid, content) VALUES(15, 'one two three four'); - INSERT INTO t1 (rowid, content) VALUES(16, 'five'); - DELETE FROM t1 WHERE rowid = 13; - INSERT INTO t1 (rowid, content) VALUES(17, 'one five'); - INSERT INTO t1 (rowid, content) VALUES(18, 'two five'); - INSERT INTO t1 (rowid, content) VALUES(19, 'one two five'); - DELETE FROM t1 WHERE rowid = 16; - INSERT INTO t1 (rowid, content) VALUES(20, 'three five'); - INSERT INTO t1 (rowid, content) VALUES(21, 'one three five'); - INSERT INTO t1 (rowid, content) VALUES(22, 'two three five'); - DELETE FROM t1 WHERE rowid = 19; - DELETE FROM t1 WHERE rowid = 22; -} - -do_test fts1f-1.1 { - execsql {SELECT COUNT(*) FROM t1} -} {14} - -do_test fts1e-2.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one'} -} {3 5 9 11 15 17 21} - -do_test fts1e-2.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two'} -} {2 3 6 11 14 15 18} - -do_test fts1e-2.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three'} -} {5 6 12 14 15 20 21} - -do_test fts1e-2.4 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'four'} -} {8 9 11 12 14 15} - -do_test fts1e-2.5 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'five'} -} {17 18 20 21} - -finish_test diff --git a/test/fts1f.test b/test/fts1f.test deleted file mode 100644 index 19dea0a32..000000000 --- a/test/fts1f.test +++ /dev/null @@ -1,90 +0,0 @@ -# 2006 October 19 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The -# focus of this script is testing updates in the FTS1 module. -# -# $Id: fts1f.test,v 1.2 2007/02/23 00:14:06 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -# Construct a full-text search table containing keywords which are the -# ordinal numbers of the bit positions set for a sequence of integers, -# which are used for the rowid. There are a total of 31 INSERT, -# UPDATE, and DELETE statements, so that we'll test both the -# segmentMerge() merge (over the first 16) and the termSelect() merge -# (over the level-1 segment and 15 level-0 segments). -db eval { - CREATE VIRTUAL TABLE t1 USING fts1(content); - INSERT INTO t1 (rowid, content) VALUES(1, 'one'); - INSERT INTO t1 (rowid, content) VALUES(2, 'two'); - INSERT INTO t1 (rowid, content) VALUES(3, 'one two'); - INSERT INTO t1 (rowid, content) VALUES(4, 'three'); - INSERT INTO t1 (rowid, content) VALUES(5, 'one three'); - INSERT INTO t1 (rowid, content) VALUES(6, 'two three'); - INSERT INTO t1 (rowid, content) VALUES(7, 'one two three'); - DELETE FROM t1 WHERE rowid = 4; - INSERT INTO t1 (rowid, content) VALUES(8, 'four'); - UPDATE t1 SET content = 'update one three' WHERE rowid = 1; - INSERT INTO t1 (rowid, content) VALUES(9, 'one four'); - INSERT INTO t1 (rowid, content) VALUES(10, 'two four'); - DELETE FROM t1 WHERE rowid = 7; - INSERT INTO t1 (rowid, content) VALUES(11, 'one two four'); - INSERT INTO t1 (rowid, content) VALUES(12, 'three four'); - INSERT INTO t1 (rowid, content) VALUES(13, 'one three four'); - DELETE FROM t1 WHERE rowid = 10; - INSERT INTO t1 (rowid, content) VALUES(14, 'two three four'); - INSERT INTO t1 (rowid, content) VALUES(15, 'one two three four'); - UPDATE t1 SET content = 'update two five' WHERE rowid = 8; - INSERT INTO t1 (rowid, content) VALUES(16, 'five'); - DELETE FROM t1 WHERE rowid = 13; - INSERT INTO t1 (rowid, content) VALUES(17, 'one five'); - INSERT INTO t1 (rowid, content) VALUES(18, 'two five'); - INSERT INTO t1 (rowid, content) VALUES(19, 'one two five'); - DELETE FROM t1 WHERE rowid = 16; - INSERT INTO t1 (rowid, content) VALUES(20, 'three five'); - INSERT INTO t1 (rowid, content) VALUES(21, 'one three five'); - INSERT INTO t1 (rowid, content) VALUES(22, 'two three five'); - DELETE FROM t1 WHERE rowid = 19; - UPDATE t1 SET content = 'update' WHERE rowid = 15; -} - -do_test fts1f-1.1 { - execsql {SELECT COUNT(*) FROM t1} -} {16} - -do_test fts1f-2.0 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'update'} -} {1 8 15} - -do_test fts1f-2.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one'} -} {1 3 5 9 11 17 21} - -do_test fts1f-2.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two'} -} {2 3 6 8 11 14 18 22} - -do_test fts1f-2.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three'} -} {1 5 6 12 14 20 21 22} - -do_test fts1f-2.4 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'four'} -} {9 11 12 14} - -do_test fts1f-2.5 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'five'} -} {8 17 18 20 21 22} - -finish_test diff --git a/test/fts1i.test b/test/fts1i.test deleted file mode 100644 index 803b93bf2..000000000 --- a/test/fts1i.test +++ /dev/null @@ -1,88 +0,0 @@ -# 2007 January 17 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite fts1 library. The -# focus here is testing handling of UPDATE when using UTF-16-encoded -# databases. -# -# $Id: fts1i.test,v 1.2 2007/01/24 03:43:20 drh Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - - -# Return the UTF-16 representation of the supplied UTF-8 string $str. -# If $nt is true, append two 0x00 bytes as a nul terminator. -# NOTE(shess) Copied from capi3.test. -proc utf16 {str {nt 1}} { - set r [encoding convertto unicode $str] - if {$nt} { - append r "\x00\x00" - } - return $r -} - -db eval { - PRAGMA encoding = "UTF-16le"; - CREATE VIRTUAL TABLE t1 USING fts1(content); -} - -do_test fts1i-1.0 { - execsql {PRAGMA encoding} -} {UTF-16le} - -do_test fts1i-1.1 { - execsql {INSERT INTO t1 (rowid, content) VALUES(1, 'one')} - execsql {SELECT content FROM t1 WHERE rowid = 1} -} {one} - -do_test fts1i-1.2 { - set sql "INSERT INTO t1 (rowid, content) VALUES(2, 'two')" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 2} -} {two} - -do_test fts1i-1.3 { - set sql "INSERT INTO t1 (rowid, content) VALUES(3, 'three')" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - set sql "UPDATE t1 SET content = 'trois' WHERE rowid = 3" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 3} -} {trois} - -do_test fts1i-1.4 { - set sql16 [utf16 {INSERT INTO t1 (rowid, content) VALUES(4, 'four')}] - set STMT [sqlite3_prepare16 $DB $sql16 -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 4} -} {four} - -do_test fts1i-1.5 { - set sql16 [utf16 {INSERT INTO t1 (rowid, content) VALUES(5, 'five')}] - set STMT [sqlite3_prepare16 $DB $sql16 -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - set sql "UPDATE t1 SET content = 'cinq' WHERE rowid = 5" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 5} -} {cinq} - -finish_test diff --git a/test/fts1j.test b/test/fts1j.test deleted file mode 100644 index 4dac22abb..000000000 --- a/test/fts1j.test +++ /dev/null @@ -1,89 +0,0 @@ -# 2007 February 6 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. This -# tests creating fts1 tables in an attached database. -# -# $Id: fts1j.test,v 1.1 2007/02/07 01:01:18 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -# Clean up anything left over from a previous pass. -forcedelete test2.db -forcedelete test2.db-journal -sqlite3 db2 test2.db - -db eval { - CREATE VIRTUAL TABLE t3 USING fts1(content); - INSERT INTO t3 (rowid, content) VALUES(1, "hello world"); -} - -db2 eval { - CREATE VIRTUAL TABLE t1 USING fts1(content); - INSERT INTO t1 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t1 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t1 (rowid, content) VALUES(3, "cruel world"); -} - -# This has always worked because the t1_* tables used by fts1 will be -# the defaults. -do_test fts1j-1.1 { - execsql { - ATTACH DATABASE 'test2.db' AS two; - SELECT rowid FROM t1 WHERE t1 MATCH 'hello'; - DETACH DATABASE two; - } -} {1 2} -# Make certain we're detached if there was an error. -catch {db eval {DETACH DATABASE two}} - -# In older code, this appears to work fine, but the t2_* tables used -# by fts1 will be created in database 'main' instead of database -# 'two'. It appears to work fine because the tables end up being the -# defaults, but obviously is badly broken if you hope to use things -# other than in the exact same ATTACH setup. -do_test fts1j-1.2 { - execsql { - ATTACH DATABASE 'test2.db' AS two; - CREATE VIRTUAL TABLE two.t2 USING fts1(content); - INSERT INTO t2 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t2 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t2 (rowid, content) VALUES(3, "cruel world"); - SELECT rowid FROM t2 WHERE t2 MATCH 'hello'; - DETACH DATABASE two; - } -} {1 2} -catch {db eval {DETACH DATABASE two}} - -# In older code, this broke because the fts1 code attempted to create -# t3_* tables in database 'main', but they already existed. Normally -# this wouldn't happen without t3 itself existing, in which case the -# fts1 code would never be called in the first place. -do_test fts1j-1.3 { - execsql { - ATTACH DATABASE 'test2.db' AS two; - - CREATE VIRTUAL TABLE two.t3 USING fts1(content); - INSERT INTO two.t3 (rowid, content) VALUES(2, "hello there"); - INSERT INTO two.t3 (rowid, content) VALUES(3, "cruel world"); - SELECT rowid FROM two.t3 WHERE t3 MATCH 'hello'; - - DETACH DATABASE two; - } db2 -} {2} -catch {db eval {DETACH DATABASE two}} - -catch {db2 close} -forcedelete test2.db - -finish_test diff --git a/test/fts1k.test b/test/fts1k.test deleted file mode 100644 index 35b94d2ae..000000000 --- a/test/fts1k.test +++ /dev/null @@ -1,69 +0,0 @@ -# 2007 March 28 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The focus -# of this script is testing isspace/isalnum/tolower problems with the -# FTS1 module. Unfortunately, this code isn't a really principled set -# of tests, because it is impossible to know where new uses of these -# functions might appear. -# -# $Id: fts1k.test,v 1.2 2007/12/13 21:54:11 drh Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -# Tests that startsWith() (calls isspace, tolower, isalnum) can handle -# hi-bit chars. parseSpec() also calls isalnum here. -do_test fts1k-1.1 { - execsql "CREATE VIRTUAL TABLE t1 USING fts1(content, \x80)" -} {} - -# Additionally tests isspace() call in getToken(), and isalnum() call -# in tokenListToIdList(). -do_test fts1k-1.2 { - catch { - execsql "CREATE VIRTUAL TABLE t2 USING fts1(content, tokenize \x80)" - } - sqlite3_errmsg $DB -} "unknown tokenizer: \x80" - -# Additionally test final isalnum() in startsWith(). -do_test fts1k-1.3 { - execsql "CREATE VIRTUAL TABLE t3 USING fts1(content, tokenize\x80)" -} {} - -# The snippet-generation code has calls to isspace() which are sort of -# hard to get to. It finds convenient breakpoints by starting ~40 -# chars before and after the matched term, and scanning ~10 chars -# around that position for isspace() characters. The long word with -# embedded hi-bit chars causes one of these isspace() calls to be -# exercised. The version with a couple extra spaces should cause the -# other isspace() call to be exercised. [Both cases have been tested -# in the debugger, but I'm hoping to continue to catch it if simple -# constant changes change things slightly. -# -# The trailing and leading hi-bit chars help with code which tests for -# isspace() to coalesce multiple spaces. - -set word "\x80xxxxx\x80xxxxx\x80xxxxx\x80xxxxx\x80xxxxx\x80xxxxx\x80" -set phrase1 "$word $word $word target $word $word $word" -set phrase2 "$word $word $word target $word $word $word" - -db eval {CREATE VIRTUAL TABLE t4 USING fts1(content)} -db eval "INSERT INTO t4 (content) VALUES ('$phrase1')" -db eval "INSERT INTO t4 (content) VALUES ('$phrase2')" - -do_test fts1k-1.4 { - execsql {SELECT rowid, length(snippet(t4)) FROM t4 WHERE t4 MATCH 'target'} -} {1 111 2 117} - -finish_test diff --git a/test/fts1l.test b/test/fts1l.test deleted file mode 100644 index 924be3380..000000000 --- a/test/fts1l.test +++ /dev/null @@ -1,65 +0,0 @@ -# 2007 April 9 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. fts1 -# DELETE handling assumed all fields were non-null. This was not -# the intention at all. -# -# $Id: fts1l.test,v 1.1 2007/04/09 20:45:42 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -db eval { - CREATE VIRTUAL TABLE t1 USING fts1(col_a, col_b); - - INSERT INTO t1(rowid, col_a, col_b) VALUES(1, 'testing', 'testing'); - INSERT INTO t1(rowid, col_a, col_b) VALUES(2, 'only a', null); - INSERT INTO t1(rowid, col_a, col_b) VALUES(3, null, 'only b'); - INSERT INTO t1(rowid, col_a, col_b) VALUES(4, null, null); -} - -do_test fts1m-1.0 { - execsql { - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {2 2 4} - -do_test fts1m-1.1 { - execsql { - DELETE FROM t1 WHERE rowid = 1; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {1 1 3} - -do_test fts1m-1.2 { - execsql { - DELETE FROM t1 WHERE rowid = 2; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {0 1 2} - -do_test fts1m-1.3 { - execsql { - DELETE FROM t1 WHERE rowid = 3; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {0 0 1} - -do_test fts1m-1.4 { - execsql { - DELETE FROM t1 WHERE rowid = 4; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {0 0 0} - -finish_test diff --git a/test/fts1m.test b/test/fts1m.test deleted file mode 100644 index c2f8f915a..000000000 --- a/test/fts1m.test +++ /dev/null @@ -1,50 +0,0 @@ -# 2007 July 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 script is testing the FTS1 module, specifically snippet -# generation. Extracted from fts2o.test. -# -# $Id: fts1m.test,v 1.1 2007/07/25 00:25:20 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is not defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -#--------------------------------------------------------------------- -# These tests, fts1m-1.*, test that ticket #2429 is fixed. -# -db eval { - CREATE VIRTUAL TABLE t1 USING fts1(a, b, c); - INSERT INTO t1(a, b, c) VALUES('one three four', 'one four', 'one four two'); -} -do_test fts1m-1.1 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE c MATCH 'four'; - } -} {1 {one four two}} -do_test fts1m-1.2 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE b MATCH 'four'; - } -} {1 {one four}} -do_test fts1m-1.3 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE a MATCH 'four'; - } -} {1 {one three four}} - -finish_test diff --git a/test/fts1n.test b/test/fts1n.test deleted file mode 100644 index 2f102b4e2..000000000 --- a/test/fts1n.test +++ /dev/null @@ -1,45 +0,0 @@ -# 2007 July 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 regression tests for SQLite library. The focus -# of this script is testing the FTS1 module for errors in the handling -# of SQLITE_SCHEMA. -# -# $Id: fts1n.test,v 1.1 2007/07/25 00:38:06 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is not defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -do_test fts1m-1.1 { - execsql { - CREATE VIRTUAL TABLE t1 USING fts1(a, b, c); - INSERT INTO t1(a, b, c) VALUES('one three four', 'one four', 'one two'); - SELECT a, b, c FROM t1 WHERE c MATCH 'two'; - } -} {{one three four} {one four} {one two}} - -# This test was crashing at one point. -# -do_test fts1m-1.2 { - execsql { - SELECT a, b, c FROM t1 WHERE c MATCH 'two'; - CREATE TABLE t3(a, b, c); - SELECT a, b, c FROM t1 WHERE c MATCH 'two'; - } -} {{one three four} {one four} {one two} {one three four} {one four} {one two}} - -finish_test diff --git a/test/fts1o.test b/test/fts1o.test deleted file mode 100644 index 3ed095294..000000000 --- a/test/fts1o.test +++ /dev/null @@ -1,138 +0,0 @@ -# 2007 July 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 regression tests for SQLite library. The focus -# of this script is testing the FTS1 module rename functionality. Mostly -# copied from fts2o.test. -# -# $Id: fts1o.test,v 1.2 2007/08/30 20:01:33 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is not defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -db eval { - CREATE VIRTUAL TABLE t1 USING fts1(a, b, c); - INSERT INTO t1(a, b, c) VALUES('one three four', 'one four', 'one four two'); -} - -#--------------------------------------------------------------------- -# Test that it is possible to rename an fts1 table. -# -do_test fts1o-1.1 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {t1 t1_content t1_term} -do_test fts1o-1.2 { - execsql { ALTER TABLE t1 RENAME to fts_t1; } -} {} -do_test fts1o-1.3 { - execsql { SELECT rowid, snippet(fts_t1) FROM fts_t1 WHERE a MATCH 'four'; } -} {1 {one three four}} -do_test fts1o-1.4 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {fts_t1 fts_t1_content fts_t1_term} - -# See what happens when renaming the fts1 table fails. -# -do_test fts1o-2.1 { - catchsql { - CREATE TABLE t1_term(a, b, c); - ALTER TABLE fts_t1 RENAME to t1; - } -} {1 {SQL logic error}} -do_test fts1o-2.2 { - execsql { SELECT rowid, snippet(fts_t1) FROM fts_t1 WHERE a MATCH 'four'; } -} {1 {one three four}} -do_test fts1o-2.3 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {fts_t1 fts_t1_content fts_t1_term t1_term} - -# See what happens when renaming the fts1 table fails inside a transaction. -# -do_test fts1o-3.1 { - execsql { - BEGIN; - INSERT INTO fts_t1(a, b, c) VALUES('one two three', 'one four', 'one two'); - } -} {} -do_test fts1o-3.2 { - catchsql { - ALTER TABLE fts_t1 RENAME to t1; - } -} {1 {SQL logic error}} -# NOTE(shess) rowid AS rowid to defeat caching. Otherwise, this -# seg-faults, I suspect that there's something up with a stale -# virtual-table reference, but I'm not quite sure how it happens here -# but not for fts2o.test. -do_test fts1o-3.3 { - execsql { SELECT rowid AS rowid, snippet(fts_t1) FROM fts_t1 WHERE a MATCH 'four'; } -} {1 {one three four}} -do_test fts1o-3.4 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {fts_t1 fts_t1_content fts_t1_term t1_term} -do_test fts1o-3.5 { - execsql COMMIT - execsql {SELECT a FROM fts_t1} -} {{one three four} {one two three}} -do_test fts1o-3.6 { - execsql { SELECT a, b, c FROM fts_t1 WHERE c MATCH 'four'; } -} {{one three four} {one four} {one four two}} - -#--------------------------------------------------------------------- -# Test that it is possible to rename an fts1 table in an attached -# database. -# -forcedelete test2.db test2.db-journal - -do_test fts1o-4.1 { - execsql { - DROP TABLE t1_term; - ALTER TABLE fts_t1 RENAME to t1; - SELECT a, b, c FROM t1 WHERE c MATCH 'two'; - } -} {{one three four} {one four} {one four two} {one two three} {one four} {one two}} - -do_test fts1o-4.2 { - execsql { - ATTACH 'test2.db' AS aux; - CREATE VIRTUAL TABLE aux.t1 USING fts1(a, b, c); - INSERT INTO aux.t1(a, b, c) VALUES( - 'neung song sahm', 'neung see', 'neung see song' - ); - } -} {} - -do_test fts1o-4.3 { - execsql { SELECT a, b, c FROM aux.t1 WHERE a MATCH 'song'; } -} {{neung song sahm} {neung see} {neung see song}} - -do_test fts1o-4.4 { - execsql { SELECT a, b, c FROM t1 WHERE c MATCH 'two'; } -} {{one three four} {one four} {one four two} {one two three} {one four} {one two}} - -do_test fts1o-4.5 { - execsql { ALTER TABLE aux.t1 RENAME TO t2 } -} {} - -do_test fts1o-4.6 { - execsql { SELECT a, b, c FROM t2 WHERE a MATCH 'song'; } -} {{neung song sahm} {neung see} {neung see song}} - -do_test fts1o-4.7 { - execsql { SELECT a, b, c FROM t1 WHERE c MATCH 'two'; } -} {{one three four} {one four} {one four two} {one two three} {one four} {one two}} - -finish_test diff --git a/test/fts1porter.test b/test/fts1porter.test deleted file mode 100644 index 0ca87a01e..000000000 --- a/test/fts1porter.test +++ /dev/null @@ -1,23590 +0,0 @@ -# 2006 October 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 script is testing the FTS1 module, and in particular -# the Porter stemmer. -# -# $Id: fts1porter.test,v 1.5 2006/10/03 19:37:37 drh Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS1 is defined, omit this file. -ifcapable !fts1 { - finish_test - return -} - -# Test data for the Porter stemmer. The first word of each line -# is the input. The second word is the desired output. -# -# This test data is taken from http://www.tartarus.org/martin/PorterStemmer/ -# There is no claim of copyright made on that page, but you should -# probably contact the author (Martin Porter - the inventor of the -# Porter Stemmer algorithm) if you want to use this test data in a -# commerical product of some kind. The stemmer code in FTS1 is a -# complete rewrite from scratch based on the algorithm specification -# and does not contain any code under copyright. -# -set porter_test_data { - a a - aaron aaron - abaissiez abaissiez - abandon abandon - abandoned abandon - abase abas - abash abash - abate abat - abated abat - abatement abat - abatements abat - abates abat - abbess abbess - abbey abbei - abbeys abbei - abbominable abbomin - abbot abbot - abbots abbot - abbreviated abbrevi - abed ab - abel abel - aberga aberga - abergavenny abergavenni - abet abet - abetting abet - abhominable abhomin - abhor abhor - abhorr abhorr - abhorred abhor - abhorring abhor - abhors abhor - abhorson abhorson - abide abid - abides abid - abilities abil - ability abil - abject abject - abjectly abjectli - abjects abject - abjur abjur - abjure abjur - able abl - abler abler - aboard aboard - abode abod - aboded abod - abodements abod - aboding abod - abominable abomin - abominably abomin - abominations abomin - abortive abort - abortives abort - abound abound - abounding abound - about about - above abov - abr abr - abraham abraham - abram abram - abreast abreast - abridg abridg - abridge abridg - abridged abridg - abridgment abridg - abroach abroach - abroad abroad - abrogate abrog - abrook abrook - abrupt abrupt - abruption abrupt - abruptly abruptli - absence absenc - absent absent - absey absei - absolute absolut - absolutely absolut - absolv absolv - absolver absolv - abstains abstain - abstemious abstemi - abstinence abstin - abstract abstract - absurd absurd - absyrtus absyrtu - abundance abund - abundant abund - abundantly abundantli - abus abu - abuse abus - abused abus - abuser abus - abuses abus - abusing abus - abutting abut - aby abi - abysm abysm - ac ac - academe academ - academes academ - accent accent - accents accent - accept accept - acceptable accept - acceptance accept - accepted accept - accepts accept - access access - accessary accessari - accessible access - accidence accid - accident accid - accidental accident - accidentally accident - accidents accid - accite accit - accited accit - accites accit - acclamations acclam - accommodate accommod - accommodated accommod - accommodation accommod - accommodations accommod - accommodo accommodo - accompanied accompani - accompany accompani - accompanying accompani - accomplices accomplic - accomplish accomplish - accomplished accomplish - accomplishing accomplish - accomplishment accomplish - accompt accompt - accord accord - accordant accord - accorded accord - accordeth accordeth - according accord - accordingly accordingli - accords accord - accost accost - accosted accost - account account - accountant account - accounted account - accounts account - accoutred accoutr - accoutrement accoutr - accoutrements accoutr - accrue accru - accumulate accumul - accumulated accumul - accumulation accumul - accurs accur - accursed accurs - accurst accurst - accus accu - accusation accus - accusations accus - accusative accus - accusativo accusativo - accuse accus - accused accus - accuser accus - accusers accus - accuses accus - accuseth accuseth - accusing accus - accustom accustom - accustomed accustom - ace ac - acerb acerb - ache ach - acheron acheron - aches ach - achiev achiev - achieve achiev - achieved achiev - achievement achiev - achievements achiev - achiever achiev - achieves achiev - achieving achiev - achilles achil - aching ach - achitophel achitophel - acknowledg acknowledg - acknowledge acknowledg - acknowledged acknowledg - acknowledgment acknowledg - acknown acknown - acold acold - aconitum aconitum - acordo acordo - acorn acorn - acquaint acquaint - acquaintance acquaint - acquainted acquaint - acquaints acquaint - acquir acquir - acquire acquir - acquisition acquisit - acquit acquit - acquittance acquitt - acquittances acquitt - acquitted acquit - acre acr - acres acr - across across - act act - actaeon actaeon - acted act - acting act - action action - actions action - actium actium - active activ - actively activ - activity activ - actor actor - actors actor - acts act - actual actual - acture actur - acute acut - acutely acut - ad ad - adage adag - adallas adalla - adam adam - adamant adam - add add - added ad - adder adder - adders adder - addeth addeth - addict addict - addicted addict - addiction addict - adding ad - addition addit - additions addit - addle addl - address address - addressing address - addrest addrest - adds add - adhere adher - adheres adher - adieu adieu - adieus adieu - adjacent adjac - adjoin adjoin - adjoining adjoin - adjourn adjourn - adjudg adjudg - adjudged adjudg - adjunct adjunct - administer administ - administration administr - admir admir - admirable admir - admiral admir - admiration admir - admire admir - admired admir - admirer admir - admiring admir - admiringly admiringli - admission admiss - admit admit - admits admit - admittance admitt - admitted admit - admitting admit - admonish admonish - admonishing admonish - admonishment admonish - admonishments admonish - admonition admonit - ado ado - adonis adoni - adopt adopt - adopted adopt - adoptedly adoptedli - adoption adopt - adoptious adopti - adopts adopt - ador ador - adoration ador - adorations ador - adore ador - adorer ador - adores ador - adorest adorest - adoreth adoreth - adoring ador - adorn adorn - adorned adorn - adornings adorn - adornment adorn - adorns adorn - adown adown - adramadio adramadio - adrian adrian - adriana adriana - adriano adriano - adriatic adriat - adsum adsum - adulation adul - adulterate adulter - adulterates adulter - adulterers adulter - adulteress adulteress - adulteries adulteri - adulterous adulter - adultery adulteri - adultress adultress - advanc advanc - advance advanc - advanced advanc - advancement advanc - advancements advanc - advances advanc - advancing advanc - advantage advantag - advantageable advantag - advantaged advantag - advantageous advantag - advantages advantag - advantaging advantag - advent advent - adventur adventur - adventure adventur - adventures adventur - adventuring adventur - adventurous adventur - adventurously adventur - adversaries adversari - adversary adversari - adverse advers - adversely advers - adversities advers - adversity advers - advertis adverti - advertise advertis - advertised advertis - advertisement advertis - advertising advertis - advice advic - advis advi - advise advis - advised advis - advisedly advisedli - advises advis - advisings advis - advocate advoc - advocation advoc - aeacida aeacida - aeacides aeacid - aedile aedil - aediles aedil - aegeon aegeon - aegion aegion - aegles aegl - aemelia aemelia - aemilia aemilia - aemilius aemiliu - aeneas aenea - aeolus aeolu - aer aer - aerial aerial - aery aeri - aesculapius aesculapiu - aeson aeson - aesop aesop - aetna aetna - afar afar - afear afear - afeard afeard - affability affabl - affable affabl - affair affair - affaire affair - affairs affair - affect affect - affectation affect - affectations affect - affected affect - affectedly affectedli - affecteth affecteth - affecting affect - affection affect - affectionate affection - affectionately affection - affections affect - affects affect - affeer affeer - affianc affianc - affiance affianc - affianced affianc - affied affi - affin affin - affined affin - affinity affin - affirm affirm - affirmation affirm - affirmatives affirm - afflict afflict - afflicted afflict - affliction afflict - afflictions afflict - afflicts afflict - afford afford - affordeth affordeth - affords afford - affray affrai - affright affright - affrighted affright - affrights affright - affront affront - affronted affront - affy affi - afield afield - afire afir - afloat afloat - afoot afoot - afore afor - aforehand aforehand - aforesaid aforesaid - afraid afraid - afresh afresh - afric afric - africa africa - african african - afront afront - after after - afternoon afternoon - afterward afterward - afterwards afterward - ag ag - again again - against against - agamemmon agamemmon - agamemnon agamemnon - agate agat - agaz agaz - age ag - aged ag - agenor agenor - agent agent - agents agent - ages ag - aggravate aggrav - aggrief aggrief - agile agil - agincourt agincourt - agitation agit - aglet aglet - agnize agniz - ago ago - agone agon - agony agoni - agree agre - agreed agre - agreeing agre - agreement agreement - agrees agre - agrippa agrippa - aground aground - ague agu - aguecheek aguecheek - agued agu - agueface aguefac - agues agu - ah ah - aha aha - ahungry ahungri - ai ai - aialvolio aialvolio - aiaria aiaria - aid aid - aidance aidanc - aidant aidant - aided aid - aiding aid - aidless aidless - aids aid - ail ail - aim aim - aimed aim - aimest aimest - aiming aim - aims aim - ainsi ainsi - aio aio - air air - aired air - airless airless - airs air - airy airi - ajax ajax - akilling akil - al al - alabaster alabast - alack alack - alacrity alacr - alarbus alarbu - alarm alarm - alarms alarm - alarum alarum - alarums alarum - alas ala - alb alb - alban alban - albans alban - albany albani - albeit albeit - albion albion - alchemist alchemist - alchemy alchemi - alcibiades alcibiad - alcides alcid - alder alder - alderman alderman - aldermen aldermen - ale al - alecto alecto - alehouse alehous - alehouses alehous - alencon alencon - alengon alengon - aleppo aleppo - ales al - alewife alewif - alexander alexand - alexanders alexand - alexandria alexandria - alexandrian alexandrian - alexas alexa - alias alia - alice alic - alien alien - aliena aliena - alight alight - alighted alight - alights alight - aliis alii - alike alik - alisander alisand - alive aliv - all all - alla alla - allay allai - allayed allai - allaying allai - allayment allay - allayments allay - allays allai - allegation alleg - allegations alleg - allege alleg - alleged alleg - allegiance allegi - allegiant allegi - alley allei - alleys allei - allhallowmas allhallowma - alliance allianc - allicholy allicholi - allied alli - allies alli - alligant allig - alligator allig - allons allon - allot allot - allots allot - allotted allot - allottery allotteri - allow allow - allowance allow - allowed allow - allowing allow - allows allow - allur allur - allure allur - allurement allur - alluring allur - allusion allus - ally alli - allycholly allycholli - almain almain - almanac almanac - almanack almanack - almanacs almanac - almighty almighti - almond almond - almost almost - alms alm - almsman almsman - aloes alo - aloft aloft - alone alon - along along - alonso alonso - aloof aloof - aloud aloud - alphabet alphabet - alphabetical alphabet - alphonso alphonso - alps alp - already alreadi - also also - alt alt - altar altar - altars altar - alter alter - alteration alter - altered alter - alters alter - althaea althaea - although although - altitude altitud - altogether altogeth - alton alton - alway alwai - always alwai - am am - amaimon amaimon - amain amain - amaking amak - amamon amamon - amaz amaz - amaze amaz - amazed amaz - amazedly amazedli - amazedness amazed - amazement amaz - amazes amaz - amazeth amazeth - amazing amaz - amazon amazon - amazonian amazonian - amazons amazon - ambassador ambassador - ambassadors ambassador - amber amber - ambiguides ambiguid - ambiguities ambigu - ambiguous ambigu - ambition ambit - ambitions ambit - ambitious ambiti - ambitiously ambiti - amble ambl - ambled ambl - ambles ambl - ambling ambl - ambo ambo - ambuscadoes ambuscado - ambush ambush - amen amen - amend amend - amended amend - amendment amend - amends amend - amerce amerc - america america - ames am - amiable amiabl - amid amid - amidst amidst - amiens amien - amis ami - amiss amiss - amities amiti - amity amiti - amnipotent amnipot - among among - amongst amongst - amorous amor - amorously amor - amort amort - amount amount - amounts amount - amour amour - amphimacus amphimacu - ample ampl - ampler ampler - amplest amplest - amplified amplifi - amplify amplifi - amply ampli - ampthill ampthil - amurath amurath - amyntas amynta - an an - anatomiz anatomiz - anatomize anatom - anatomy anatomi - ancestor ancestor - ancestors ancestor - ancestry ancestri - anchises anchis - anchor anchor - anchorage anchorag - anchored anchor - anchoring anchor - anchors anchor - anchovies anchovi - ancient ancient - ancientry ancientri - ancients ancient - ancus ancu - and and - andirons andiron - andpholus andpholu - andren andren - andrew andrew - andromache andromach - andronici andronici - andronicus andronicu - anew anew - ang ang - angel angel - angelica angelica - angelical angel - angelo angelo - angels angel - anger anger - angerly angerli - angers anger - anges ang - angiers angier - angl angl - anglais anglai - angle angl - angler angler - angleterre angleterr - angliae anglia - angling angl - anglish anglish - angrily angrili - angry angri - anguish anguish - angus angu - animal anim - animals anim - animis animi - anjou anjou - ankle ankl - anna anna - annals annal - anne ann - annex annex - annexed annex - annexions annexion - annexment annex - annothanize annothan - announces announc - annoy annoi - annoyance annoy - annoying annoi - annual annual - anoint anoint - anointed anoint - anon anon - another anoth - anselmo anselmo - answer answer - answerable answer - answered answer - answerest answerest - answering answer - answers answer - ant ant - ante ant - antenor antenor - antenorides antenorid - anteroom anteroom - anthem anthem - anthems anthem - anthony anthoni - anthropophagi anthropophagi - anthropophaginian anthropophaginian - antiates antiat - antic antic - anticipate anticip - anticipates anticip - anticipatest anticipatest - anticipating anticip - anticipation anticip - antick antick - anticly anticli - antics antic - antidote antidot - antidotes antidot - antigonus antigonu - antiopa antiopa - antipathy antipathi - antipholus antipholu - antipholuses antipholus - antipodes antipod - antiquary antiquari - antique antiqu - antiquity antiqu - antium antium - antoniad antoniad - antonio antonio - antonius antoniu - antony antoni - antres antr - anvil anvil - any ani - anybody anybodi - anyone anyon - anything anyth - anywhere anywher - ap ap - apace apac - apart apart - apartment apart - apartments apart - ape ap - apemantus apemantu - apennines apennin - apes ap - apiece apiec - apish apish - apollinem apollinem - apollo apollo - apollodorus apollodoru - apology apolog - apoplex apoplex - apoplexy apoplexi - apostle apostl - apostles apostl - apostrophas apostropha - apoth apoth - apothecary apothecari - appal appal - appall appal - appalled appal - appals appal - apparel apparel - apparell apparel - apparelled apparel - apparent appar - apparently appar - apparition apparit - apparitions apparit - appeach appeach - appeal appeal - appeals appeal - appear appear - appearance appear - appeared appear - appeareth appeareth - appearing appear - appears appear - appeas appea - appease appeas - appeased appeas - appelant appel - appele appel - appelee appele - appeles appel - appelez appelez - appellant appel - appellants appel - appelons appelon - appendix appendix - apperil apperil - appertain appertain - appertaining appertain - appertainings appertain - appertains appertain - appertinent appertin - appertinents appertin - appetite appetit - appetites appetit - applaud applaud - applauded applaud - applauding applaud - applause applaus - applauses applaus - apple appl - apples appl - appletart appletart - appliance applianc - appliances applianc - applications applic - applied appli - applies appli - apply appli - applying appli - appoint appoint - appointed appoint - appointment appoint - appointments appoint - appoints appoint - apprehend apprehend - apprehended apprehend - apprehends apprehend - apprehension apprehens - apprehensions apprehens - apprehensive apprehens - apprendre apprendr - apprenne apprenn - apprenticehood apprenticehood - appris appri - approach approach - approachers approach - approaches approach - approacheth approacheth - approaching approach - approbation approb - approof approof - appropriation appropri - approv approv - approve approv - approved approv - approvers approv - approves approv - appurtenance appurten - appurtenances appurten - apricocks apricock - april april - apron apron - aprons apron - apt apt - apter apter - aptest aptest - aptly aptli - aptness apt - aqua aqua - aquilon aquilon - aquitaine aquitain - arabia arabia - arabian arabian - araise arais - arbitrate arbitr - arbitrating arbitr - arbitrator arbitr - arbitrement arbitr - arbors arbor - arbour arbour - arc arc - arch arch - archbishop archbishop - archbishopric archbishopr - archdeacon archdeacon - arched arch - archelaus archelau - archer archer - archers archer - archery archeri - archibald archibald - archidamus archidamu - architect architect - arcu arcu - arde ard - arden arden - ardent ardent - ardour ardour - are ar - argal argal - argier argier - argo argo - argosies argosi - argosy argosi - argu argu - argue argu - argued argu - argues argu - arguing argu - argument argument - arguments argument - argus argu - ariachne ariachn - ariadne ariadn - ariel ariel - aries ari - aright aright - arinado arinado - arinies arini - arion arion - arise aris - arises aris - ariseth ariseth - arising aris - aristode aristod - aristotle aristotl - arithmetic arithmet - arithmetician arithmetician - ark ark - arm arm - arma arma - armado armado - armadoes armado - armagnac armagnac - arme arm - armed arm - armenia armenia - armies armi - armigero armigero - arming arm - armipotent armipot - armor armor - armour armour - armourer armour - armourers armour - armours armour - armoury armouri - arms arm - army armi - arn arn - aroint aroint - arose aros - arouse arous - aroused arous - arragon arragon - arraign arraign - arraigned arraign - arraigning arraign - arraignment arraign - arrant arrant - arras arra - array arrai - arrearages arrearag - arrest arrest - arrested arrest - arrests arrest - arriv arriv - arrival arriv - arrivance arriv - arrive arriv - arrived arriv - arrives arriv - arriving arriv - arrogance arrog - arrogancy arrog - arrogant arrog - arrow arrow - arrows arrow - art art - artemidorus artemidoru - arteries arteri - arthur arthur - article articl - articles articl - articulate articul - artificer artific - artificial artifici - artillery artilleri - artire artir - artist artist - artists artist - artless artless - artois artoi - arts art - artus artu - arviragus arviragu - as as - asaph asaph - ascanius ascaniu - ascend ascend - ascended ascend - ascendeth ascendeth - ascends ascend - ascension ascens - ascent ascent - ascribe ascrib - ascribes ascrib - ash ash - asham asham - ashamed asham - asher asher - ashes ash - ashford ashford - ashore ashor - ashouting ashout - ashy ashi - asia asia - aside asid - ask ask - askance askanc - asked ask - asker asker - asketh asketh - asking ask - asks ask - aslant aslant - asleep asleep - asmath asmath - asp asp - aspect aspect - aspects aspect - aspen aspen - aspersion aspers - aspic aspic - aspicious aspici - aspics aspic - aspir aspir - aspiration aspir - aspire aspir - aspiring aspir - asquint asquint - ass ass - assail assail - assailable assail - assailant assail - assailants assail - assailed assail - assaileth assaileth - assailing assail - assails assail - assassination assassin - assault assault - assaulted assault - assaults assault - assay assai - assaying assai - assays assai - assemblance assembl - assemble assembl - assembled assembl - assemblies assembl - assembly assembl - assent assent - asses ass - assez assez - assign assign - assigned assign - assigns assign - assinico assinico - assist assist - assistance assist - assistances assist - assistant assist - assistants assist - assisted assist - assisting assist - associate associ - associated associ - associates associ - assuage assuag - assubjugate assubjug - assum assum - assume assum - assumes assum - assumption assumpt - assur assur - assurance assur - assure assur - assured assur - assuredly assuredli - assures assur - assyrian assyrian - astonish astonish - astonished astonish - astraea astraea - astray astrai - astrea astrea - astronomer astronom - astronomers astronom - astronomical astronom - astronomy astronomi - asunder asund - at at - atalanta atalanta - ate at - ates at - athenian athenian - athenians athenian - athens athen - athol athol - athversary athversari - athwart athwart - atlas atla - atomies atomi - atomy atomi - atone aton - atonement aton - atonements aton - atropos atropo - attach attach - attached attach - attachment attach - attain attain - attainder attaind - attains attain - attaint attaint - attainted attaint - attainture attaintur - attempt attempt - attemptable attempt - attempted attempt - attempting attempt - attempts attempt - attend attend - attendance attend - attendant attend - attendants attend - attended attend - attendents attend - attendeth attendeth - attending attend - attends attend - attent attent - attention attent - attentive attent - attentivenes attentiven - attest attest - attested attest - attir attir - attire attir - attired attir - attires attir - attorney attornei - attorneyed attornei - attorneys attornei - attorneyship attorneyship - attract attract - attraction attract - attractive attract - attracts attract - attribute attribut - attributed attribut - attributes attribut - attribution attribut - attributive attribut - atwain atwain - au au - aubrey aubrei - auburn auburn - aucun aucun - audacious audaci - audaciously audaci - audacity audac - audible audibl - audience audienc - audis audi - audit audit - auditor auditor - auditors auditor - auditory auditori - audre audr - audrey audrei - aufidius aufidiu - aufidiuses aufidius - auger auger - aught aught - augment augment - augmentation augment - augmented augment - augmenting augment - augurer augur - augurers augur - augures augur - auguring augur - augurs augur - augury auguri - august august - augustus augustu - auld auld - aumerle aumerl - aunchient aunchient - aunt aunt - aunts aunt - auricular auricular - aurora aurora - auspicious auspici - aussi aussi - austere auster - austerely auster - austereness auster - austerity auster - austria austria - aut aut - authentic authent - author author - authorities author - authority author - authorized author - authorizing author - authors author - autolycus autolycu - autre autr - autumn autumn - auvergne auvergn - avail avail - avails avail - avarice avaric - avaricious avarici - avaunt avaunt - ave av - aveng aveng - avenge aveng - avenged aveng - averring aver - avert avert - aves av - avez avez - avis avi - avoid avoid - avoided avoid - avoiding avoid - avoids avoid - avoirdupois avoirdupoi - avouch avouch - avouched avouch - avouches avouch - avouchment avouch - avow avow - aw aw - await await - awaits await - awak awak - awake awak - awaked awak - awaken awaken - awakened awaken - awakens awaken - awakes awak - awaking awak - award award - awards award - awasy awasi - away awai - awe aw - aweary aweari - aweless aweless - awful aw - awhile awhil - awkward awkward - awl awl - awooing awoo - awork awork - awry awri - axe ax - axle axl - axletree axletre - ay ay - aye ay - ayez ayez - ayli ayli - azur azur - azure azur - b b - ba ba - baa baa - babbl babbl - babble babbl - babbling babbl - babe babe - babes babe - babies babi - baboon baboon - baboons baboon - baby babi - babylon babylon - bacare bacar - bacchanals bacchan - bacchus bacchu - bach bach - bachelor bachelor - bachelors bachelor - back back - backbite backbit - backbitten backbitten - backing back - backs back - backward backward - backwardly backwardli - backwards backward - bacon bacon - bacons bacon - bad bad - bade bade - badge badg - badged badg - badges badg - badly badli - badness bad - baes bae - baffl baffl - baffle baffl - baffled baffl - bag bag - baggage baggag - bagot bagot - bagpipe bagpip - bags bag - bail bail - bailiff bailiff - baillez baillez - baily baili - baisant baisant - baisees baise - baiser baiser - bait bait - baited bait - baiting bait - baitings bait - baits bait - bajazet bajazet - bak bak - bake bake - baked bake - baker baker - bakers baker - bakes bake - baking bake - bal bal - balanc balanc - balance balanc - balcony balconi - bald bald - baldrick baldrick - bale bale - baleful bale - balk balk - ball ball - ballad ballad - ballads ballad - ballast ballast - ballasting ballast - ballet ballet - ballow ballow - balls ball - balm balm - balms balm - balmy balmi - balsam balsam - balsamum balsamum - balth balth - balthasar balthasar - balthazar balthazar - bames bame - ban ban - banbury banburi - band band - bandied bandi - banding band - bandit bandit - banditti banditti - banditto banditto - bands band - bandy bandi - bandying bandi - bane bane - banes bane - bang bang - bangor bangor - banish banish - banished banish - banishers banish - banishment banish - banister banist - bank bank - bankrout bankrout - bankrupt bankrupt - bankrupts bankrupt - banks bank - banner banner - bannerets banneret - banners banner - banning ban - banns bann - banquet banquet - banqueted banquet - banqueting banquet - banquets banquet - banquo banquo - bans ban - baptism baptism - baptista baptista - baptiz baptiz - bar bar - barbarian barbarian - barbarians barbarian - barbarism barbar - barbarous barbar - barbary barbari - barbason barbason - barbed barb - barber barber - barbermonger barbermong - bard bard - bardolph bardolph - bards bard - bare bare - bared bare - barefac barefac - barefaced barefac - barefoot barefoot - bareheaded barehead - barely bare - bareness bare - barful bar - bargain bargain - bargains bargain - barge barg - bargulus bargulu - baring bare - bark bark - barking bark - barkloughly barkloughli - barks bark - barky barki - barley barlei - barm barm - barn barn - barnacles barnacl - barnardine barnardin - barne barn - barnes barn - barnet barnet - barns barn - baron baron - barons baron - barony baroni - barr barr - barrabas barraba - barrel barrel - barrels barrel - barren barren - barrenly barrenli - barrenness barren - barricado barricado - barricadoes barricado - barrow barrow - bars bar - barson barson - barter barter - bartholomew bartholomew - bas ba - basan basan - base base - baseless baseless - basely base - baseness base - baser baser - bases base - basest basest - bashful bash - bashfulness bash - basilisco basilisco - basilisk basilisk - basilisks basilisk - basimecu basimecu - basin basin - basingstoke basingstok - basins basin - basis basi - bask bask - basket basket - baskets basket - bass bass - bassanio bassanio - basset basset - bassianus bassianu - basta basta - bastard bastard - bastardizing bastard - bastardly bastardli - bastards bastard - bastardy bastardi - basted bast - bastes bast - bastinado bastinado - basting bast - bat bat - batailles batail - batch batch - bate bate - bated bate - bates bate - bath bath - bathe bath - bathed bath - bathing bath - baths bath - bating bate - batler batler - bats bat - batt batt - battalia battalia - battalions battalion - batten batten - batter batter - battering batter - batters batter - battery batteri - battle battl - battled battl - battlefield battlefield - battlements battlement - battles battl - batty batti - bauble baubl - baubles baubl - baubling baubl - baulk baulk - bavin bavin - bawcock bawcock - bawd bawd - bawdry bawdri - bawds bawd - bawdy bawdi - bawl bawl - bawling bawl - bay bai - baying bai - baynard baynard - bayonne bayonn - bays bai - be be - beach beach - beached beach - beachy beachi - beacon beacon - bead bead - beaded bead - beadle beadl - beadles beadl - beads bead - beadsmen beadsmen - beagle beagl - beagles beagl - beak beak - beaks beak - beam beam - beamed beam - beams beam - bean bean - beans bean - bear bear - beard beard - bearded beard - beardless beardless - beards beard - bearer bearer - bearers bearer - bearest bearest - beareth beareth - bearing bear - bears bear - beast beast - beastliest beastliest - beastliness beastli - beastly beastli - beasts beast - beat beat - beated beat - beaten beaten - beating beat - beatrice beatric - beats beat - beau beau - beaufort beaufort - beaumond beaumond - beaumont beaumont - beauteous beauteou - beautied beauti - beauties beauti - beautified beautifi - beautiful beauti - beautify beautifi - beauty beauti - beaver beaver - beavers beaver - became becam - because becaus - bechanc bechanc - bechance bechanc - bechanced bechanc - beck beck - beckon beckon - beckons beckon - becks beck - becom becom - become becom - becomed becom - becomes becom - becoming becom - becomings becom - bed bed - bedabbled bedabbl - bedash bedash - bedaub bedaub - bedazzled bedazzl - bedchamber bedchamb - bedclothes bedcloth - bedded bed - bedeck bedeck - bedecking bedeck - bedew bedew - bedfellow bedfellow - bedfellows bedfellow - bedford bedford - bedlam bedlam - bedrench bedrench - bedrid bedrid - beds bed - bedtime bedtim - bedward bedward - bee bee - beef beef - beefs beef - beehives beehiv - been been - beer beer - bees bee - beest beest - beetle beetl - beetles beetl - beeves beev - befall befal - befallen befallen - befalls befal - befell befel - befits befit - befitted befit - befitting befit - befor befor - before befor - beforehand beforehand - befortune befortun - befriend befriend - befriended befriend - befriends befriend - beg beg - began began - beget beget - begets beget - begetting beget - begg begg - beggar beggar - beggared beggar - beggarly beggarli - beggarman beggarman - beggars beggar - beggary beggari - begging beg - begin begin - beginners beginn - beginning begin - beginnings begin - begins begin - begnawn begnawn - begone begon - begot begot - begotten begotten - begrimed begrim - begs beg - beguil beguil - beguile beguil - beguiled beguil - beguiles beguil - beguiling beguil - begun begun - behalf behalf - behalfs behalf - behav behav - behaved behav - behavedst behavedst - behavior behavior - behaviors behavior - behaviour behaviour - behaviours behaviour - behead behead - beheaded behead - beheld beheld - behest behest - behests behest - behind behind - behold behold - beholder behold - beholders behold - beholdest beholdest - beholding behold - beholds behold - behoof behoof - behooffull behoofful - behooves behoov - behove behov - behoves behov - behowls behowl - being be - bel bel - belarius belariu - belch belch - belching belch - beldam beldam - beldame beldam - beldams beldam - belee bele - belgia belgia - belie beli - belied beli - belief belief - beliest beliest - believ believ - believe believ - believed believ - believes believ - believest believest - believing believ - belike belik - bell bell - bellario bellario - belle bell - bellied belli - bellies belli - bellman bellman - bellona bellona - bellow bellow - bellowed bellow - bellowing bellow - bellows bellow - bells bell - belly belli - bellyful belly - belman belman - belmont belmont - belock belock - belong belong - belonging belong - belongings belong - belongs belong - belov belov - beloved belov - beloving belov - below below - belt belt - belzebub belzebub - bemadding bemad - bemet bemet - bemete bemet - bemoan bemoan - bemoaned bemoan - bemock bemock - bemoil bemoil - bemonster bemonst - ben ben - bench bench - bencher bencher - benches bench - bend bend - bended bend - bending bend - bends bend - bene bene - beneath beneath - benedicite benedicit - benedick benedick - benediction benedict - benedictus benedictu - benefactors benefactor - benefice benefic - beneficial benefici - benefit benefit - benefited benefit - benefits benefit - benetted benet - benevolence benevol - benevolences benevol - benied beni - benison benison - bennet bennet - bent bent - bentii bentii - bentivolii bentivolii - bents bent - benumbed benumb - benvolio benvolio - bepaint bepaint - bepray beprai - bequeath bequeath - bequeathed bequeath - bequeathing bequeath - bequest bequest - ber ber - berard berard - berattle berattl - beray berai - bere bere - bereave bereav - bereaved bereav - bereaves bereav - bereft bereft - bergamo bergamo - bergomask bergomask - berhym berhym - berhyme berhym - berkeley berkelei - bermoothes bermooth - bernardo bernardo - berod berod - berowne berown - berri berri - berries berri - berrord berrord - berry berri - bertram bertram - berwick berwick - bescreen bescreen - beseech beseech - beseeched beseech - beseechers beseech - beseeching beseech - beseek beseek - beseem beseem - beseemeth beseemeth - beseeming beseem - beseems beseem - beset beset - beshrew beshrew - beside besid - besides besid - besieg besieg - besiege besieg - besieged besieg - beslubber beslubb - besmear besmear - besmeared besmear - besmirch besmirch - besom besom - besort besort - besotted besot - bespake bespak - bespeak bespeak - bespice bespic - bespoke bespok - bespotted bespot - bess bess - bessy bessi - best best - bestained bestain - bested best - bestial bestial - bestir bestir - bestirr bestirr - bestow bestow - bestowed bestow - bestowing bestow - bestows bestow - bestraught bestraught - bestrew bestrew - bestrid bestrid - bestride bestrid - bestrides bestrid - bet bet - betake betak - beteem beteem - bethink bethink - bethought bethought - bethrothed bethroth - bethump bethump - betid betid - betide betid - betideth betideth - betime betim - betimes betim - betoken betoken - betook betook - betossed betoss - betray betrai - betrayed betrai - betraying betrai - betrays betrai - betrims betrim - betroth betroth - betrothed betroth - betroths betroth - bett bett - betted bet - better better - bettered better - bettering better - betters better - betting bet - bettre bettr - between between - betwixt betwixt - bevel bevel - beverage beverag - bevis bevi - bevy bevi - bewail bewail - bewailed bewail - bewailing bewail - bewails bewail - beware bewar - bewasted bewast - beweep beweep - bewept bewept - bewet bewet - bewhored bewhor - bewitch bewitch - bewitched bewitch - bewitchment bewitch - bewray bewrai - beyond beyond - bezonian bezonian - bezonians bezonian - bianca bianca - bianco bianco - bias bia - bibble bibbl - bickerings bicker - bid bid - bidden bidden - bidding bid - biddings bid - biddy biddi - bide bide - bides bide - biding bide - bids bid - bien bien - bier bier - bifold bifold - big big - bigamy bigami - biggen biggen - bigger bigger - bigness big - bigot bigot - bilberry bilberri - bilbo bilbo - bilboes bilbo - bilbow bilbow - bill bill - billeted billet - billets billet - billiards billiard - billing bill - billow billow - billows billow - bills bill - bin bin - bind bind - bindeth bindeth - binding bind - binds bind - biondello biondello - birch birch - bird bird - birding bird - birdlime birdlim - birds bird - birnam birnam - birth birth - birthday birthdai - birthdom birthdom - birthplace birthplac - birthright birthright - birthrights birthright - births birth - bis bi - biscuit biscuit - bishop bishop - bishops bishop - bisson bisson - bit bit - bitch bitch - bite bite - biter biter - bites bite - biting bite - bits bit - bitt bitt - bitten bitten - bitter bitter - bitterest bitterest - bitterly bitterli - bitterness bitter - blab blab - blabb blabb - blabbing blab - blabs blab - black black - blackamoor blackamoor - blackamoors blackamoor - blackberries blackberri - blackberry blackberri - blacker blacker - blackest blackest - blackfriars blackfriar - blackheath blackheath - blackmere blackmer - blackness black - blacks black - bladder bladder - bladders bladder - blade blade - bladed blade - blades blade - blains blain - blam blam - blame blame - blamed blame - blameful blame - blameless blameless - blames blame - blanc blanc - blanca blanca - blanch blanch - blank blank - blanket blanket - blanks blank - blaspheme blasphem - blaspheming blasphem - blasphemous blasphem - blasphemy blasphemi - blast blast - blasted blast - blasting blast - blastments blastment - blasts blast - blaz blaz - blaze blaze - blazes blaze - blazing blaze - blazon blazon - blazoned blazon - blazoning blazon - bleach bleach - bleaching bleach - bleak bleak - blear blear - bleared blear - bleat bleat - bleated bleat - bleats bleat - bled bled - bleed bleed - bleedest bleedest - bleedeth bleedeth - bleeding bleed - bleeds bleed - blemish blemish - blemishes blemish - blench blench - blenches blench - blend blend - blended blend - blent blent - bless bless - blessed bless - blessedly blessedli - blessedness blessed - blesses bless - blesseth blesseth - blessing bless - blessings bless - blest blest - blew blew - blind blind - blinded blind - blindfold blindfold - blinding blind - blindly blindli - blindness blind - blinds blind - blink blink - blinking blink - bliss bliss - blist blist - blister blister - blisters blister - blithe blith - blithild blithild - bloat bloat - block block - blockish blockish - blocks block - blois bloi - blood blood - blooded blood - bloodhound bloodhound - bloodied bloodi - bloodier bloodier - bloodiest bloodiest - bloodily bloodili - bloodless bloodless - bloods blood - bloodshed bloodsh - bloodshedding bloodshed - bloodstained bloodstain - bloody bloodi - bloom bloom - blooms bloom - blossom blossom - blossoming blossom - blossoms blossom - blot blot - blots blot - blotted blot - blotting blot - blount blount - blow blow - blowed blow - blowers blower - blowest blowest - blowing blow - blown blown - blows blow - blowse blows - blubb blubb - blubber blubber - blubbering blubber - blue blue - bluecaps bluecap - bluest bluest - blunt blunt - blunted blunt - blunter blunter - bluntest bluntest - blunting blunt - bluntly bluntli - bluntness blunt - blunts blunt - blur blur - blurr blurr - blurs blur - blush blush - blushes blush - blushest blushest - blushing blush - blust blust - bluster bluster - blusterer bluster - blusters bluster - bo bo - boar boar - board board - boarded board - boarding board - boards board - boarish boarish - boars boar - boast boast - boasted boast - boastful boast - boasting boast - boasts boast - boat boat - boats boat - boatswain boatswain - bob bob - bobb bobb - boblibindo boblibindo - bobtail bobtail - bocchus bocchu - bode bode - boded bode - bodements bodement - bodes bode - bodg bodg - bodied bodi - bodies bodi - bodiless bodiless - bodily bodili - boding bode - bodkin bodkin - body bodi - bodykins bodykin - bog bog - boggle boggl - boggler boggler - bogs bog - bohemia bohemia - bohemian bohemian - bohun bohun - boil boil - boiling boil - boils boil - boist boist - boisterous boister - boisterously boister - boitier boitier - bold bold - bolden bolden - bolder bolder - boldest boldest - boldly boldli - boldness bold - bolds bold - bolingbroke bolingbrok - bolster bolster - bolt bolt - bolted bolt - bolter bolter - bolters bolter - bolting bolt - bolts bolt - bombard bombard - bombards bombard - bombast bombast - bon bon - bona bona - bond bond - bondage bondag - bonded bond - bondmaid bondmaid - bondman bondman - bondmen bondmen - bonds bond - bondslave bondslav - bone bone - boneless boneless - bones bone - bonfire bonfir - bonfires bonfir - bonjour bonjour - bonne bonn - bonnet bonnet - bonneted bonnet - bonny bonni - bonos bono - bonto bonto - bonville bonvil - bood bood - book book - bookish bookish - books book - boon boon - boor boor - boorish boorish - boors boor - boot boot - booted boot - booties booti - bootless bootless - boots boot - booty booti - bor bor - bora bora - borachio borachio - bordeaux bordeaux - border border - bordered border - borderers border - borders border - bore bore - boreas borea - bores bore - boring bore - born born - borne born - borough borough - boroughs borough - borrow borrow - borrowed borrow - borrower borrow - borrowing borrow - borrows borrow - bosko bosko - boskos bosko - bosky boski - bosom bosom - bosoms bosom - boson boson - boss boss - bosworth bosworth - botch botch - botcher botcher - botches botch - botchy botchi - both both - bots bot - bottle bottl - bottled bottl - bottles bottl - bottom bottom - bottomless bottomless - bottoms bottom - bouciqualt bouciqualt - bouge boug - bough bough - boughs bough - bought bought - bounce bounc - bouncing bounc - bound bound - bounded bound - bounden bounden - boundeth boundeth - bounding bound - boundless boundless - bounds bound - bounteous bounteou - bounteously bounteous - bounties bounti - bountiful bounti - bountifully bountifulli - bounty bounti - bourbier bourbier - bourbon bourbon - bourchier bourchier - bourdeaux bourdeaux - bourn bourn - bout bout - bouts bout - bove bove - bow bow - bowcase bowcas - bowed bow - bowels bowel - bower bower - bowing bow - bowl bowl - bowler bowler - bowling bowl - bowls bowl - bows bow - bowsprit bowsprit - bowstring bowstr - box box - boxes box - boy boi - boyet boyet - boyish boyish - boys boi - brabant brabant - brabantio brabantio - brabble brabbl - brabbler brabbler - brac brac - brace brace - bracelet bracelet - bracelets bracelet - brach brach - bracy braci - brag brag - bragg bragg - braggardism braggard - braggards braggard - braggart braggart - braggarts braggart - bragged brag - bragging brag - bragless bragless - brags brag - braid braid - braided braid - brain brain - brained brain - brainford brainford - brainish brainish - brainless brainless - brains brain - brainsick brainsick - brainsickly brainsickli - brake brake - brakenbury brakenburi - brakes brake - brambles brambl - bran bran - branch branch - branches branch - branchless branchless - brand brand - branded brand - brandish brandish - brandon brandon - brands brand - bras bra - brass brass - brassy brassi - brat brat - brats brat - brav brav - brave brave - braved brave - bravely brave - braver braver - bravery braveri - braves brave - bravest bravest - braving brave - brawl brawl - brawler brawler - brawling brawl - brawls brawl - brawn brawn - brawns brawn - bray brai - braying brai - braz braz - brazen brazen - brazier brazier - breach breach - breaches breach - bread bread - breadth breadth - break break - breaker breaker - breakfast breakfast - breaking break - breaks break - breast breast - breasted breast - breasting breast - breastplate breastplat - breasts breast - breath breath - breathe breath - breathed breath - breather breather - breathers breather - breathes breath - breathest breathest - breathing breath - breathless breathless - breaths breath - brecknock brecknock - bred bred - breech breech - breeches breech - breeching breech - breed breed - breeder breeder - breeders breeder - breeding breed - breeds breed - breese brees - breeze breez - breff breff - bretagne bretagn - brethen brethen - bretheren bretheren - brethren brethren - brevis brevi - brevity breviti - brew brew - brewage brewag - brewer brewer - brewers brewer - brewing brew - brews brew - briareus briareu - briars briar - brib brib - bribe bribe - briber briber - bribes bribe - brick brick - bricklayer bricklay - bricks brick - bridal bridal - bride bride - bridegroom bridegroom - bridegrooms bridegroom - brides bride - bridge bridg - bridgenorth bridgenorth - bridges bridg - bridget bridget - bridle bridl - bridled bridl - brief brief - briefer briefer - briefest briefest - briefly briefli - briefness brief - brier brier - briers brier - brigandine brigandin - bright bright - brighten brighten - brightest brightest - brightly brightli - brightness bright - brim brim - brimful brim - brims brim - brimstone brimston - brinded brind - brine brine - bring bring - bringer bringer - bringeth bringeth - bringing bring - bringings bring - brings bring - brinish brinish - brink brink - brisk brisk - brisky briski - bristle bristl - bristled bristl - bristly bristli - bristol bristol - bristow bristow - britain britain - britaine britain - britaines britain - british british - briton briton - britons briton - brittany brittani - brittle brittl - broach broach - broached broach - broad broad - broader broader - broadsides broadsid - brocas broca - brock brock - brogues brogu - broil broil - broiling broil - broils broil - broke broke - broken broken - brokenly brokenli - broker broker - brokers broker - brokes broke - broking broke - brooch brooch - brooches brooch - brood brood - brooded brood - brooding brood - brook brook - brooks brook - broom broom - broomstaff broomstaff - broth broth - brothel brothel - brother brother - brotherhood brotherhood - brotherhoods brotherhood - brotherly brotherli - brothers brother - broths broth - brought brought - brow brow - brown brown - browner browner - brownist brownist - browny browni - brows brow - browse brows - browsing brows - bruis brui - bruise bruis - bruised bruis - bruises bruis - bruising bruis - bruit bruit - bruited bruit - brundusium brundusium - brunt brunt - brush brush - brushes brush - brute brute - brutish brutish - brutus brutu - bubble bubbl - bubbles bubbl - bubbling bubbl - bubukles bubukl - buck buck - bucket bucket - buckets bucket - bucking buck - buckingham buckingham - buckle buckl - buckled buckl - buckler buckler - bucklers buckler - bucklersbury bucklersburi - buckles buckl - buckram buckram - bucks buck - bud bud - budded bud - budding bud - budge budg - budger budger - budget budget - buds bud - buff buff - buffet buffet - buffeting buffet - buffets buffet - bug bug - bugbear bugbear - bugle bugl - bugs bug - build build - builded build - buildeth buildeth - building build - buildings build - builds build - built built - bulk bulk - bulks bulk - bull bull - bullcalf bullcalf - bullen bullen - bullens bullen - bullet bullet - bullets bullet - bullocks bullock - bulls bull - bully bulli - bulmer bulmer - bulwark bulwark - bulwarks bulwark - bum bum - bumbast bumbast - bump bump - bumper bumper - bums bum - bunch bunch - bunches bunch - bundle bundl - bung bung - bunghole bunghol - bungle bungl - bunting bunt - buoy buoi - bur bur - burbolt burbolt - burd burd - burden burden - burdened burden - burdening burden - burdenous burden - burdens burden - burgh burgh - burgher burgher - burghers burgher - burglary burglari - burgomasters burgomast - burgonet burgonet - burgundy burgundi - burial burial - buried buri - burier burier - buriest buriest - burly burli - burn burn - burned burn - burnet burnet - burneth burneth - burning burn - burnish burnish - burns burn - burnt burnt - burr burr - burrows burrow - burs bur - burst burst - bursting burst - bursts burst - burthen burthen - burthens burthen - burton burton - bury buri - burying buri - bush bush - bushels bushel - bushes bush - bushy bushi - busied busi - busily busili - busines busin - business busi - businesses busi - buskin buskin - busky buski - buss buss - busses buss - bussing buss - bustle bustl - bustling bustl - busy busi - but but - butcheed butche - butcher butcher - butchered butcher - butcheries butcheri - butcherly butcherli - butchers butcher - butchery butcheri - butler butler - butt butt - butter butter - buttered butter - butterflies butterfli - butterfly butterfli - butterwoman butterwoman - buttery butteri - buttock buttock - buttocks buttock - button button - buttonhole buttonhol - buttons button - buttress buttress - buttry buttri - butts butt - buxom buxom - buy bui - buyer buyer - buying bui - buys bui - buzz buzz - buzzard buzzard - buzzards buzzard - buzzers buzzer - buzzing buzz - by by - bye bye - byzantium byzantium - c c - ca ca - cabbage cabbag - cabileros cabilero - cabin cabin - cabins cabin - cable cabl - cables cabl - cackling cackl - cacodemon cacodemon - caddis caddi - caddisses caddiss - cade cade - cadence cadenc - cadent cadent - cades cade - cadmus cadmu - caduceus caduceu - cadwal cadwal - cadwallader cadwallad - caelius caeliu - caelo caelo - caesar caesar - caesarion caesarion - caesars caesar - cage cage - caged cage - cagion cagion - cain cain - caithness caith - caitiff caitiff - caitiffs caitiff - caius caiu - cak cak - cake cake - cakes cake - calaber calab - calais calai - calamities calam - calamity calam - calchas calcha - calculate calcul - calen calen - calendar calendar - calendars calendar - calf calf - caliban caliban - calibans caliban - calipolis calipoli - cality caliti - caliver caliv - call call - callat callat - called call - callet callet - calling call - calls call - calm calm - calmest calmest - calmly calmli - calmness calm - calms calm - calpurnia calpurnia - calumniate calumni - calumniating calumni - calumnious calumni - calumny calumni - calve calv - calved calv - calves calv - calveskins calveskin - calydon calydon - cam cam - cambio cambio - cambria cambria - cambric cambric - cambrics cambric - cambridge cambridg - cambyses cambys - came came - camel camel - camelot camelot - camels camel - camest camest - camillo camillo - camlet camlet - camomile camomil - camp camp - campeius campeiu - camping camp - camps camp - can can - canakin canakin - canaries canari - canary canari - cancel cancel - cancell cancel - cancelled cancel - cancelling cancel - cancels cancel - cancer cancer - candidatus candidatu - candied candi - candle candl - candles candl - candlesticks candlestick - candy candi - canidius canidiu - cank cank - canker canker - cankerblossom cankerblossom - cankers canker - cannibally cannib - cannibals cannib - cannon cannon - cannoneer cannon - cannons cannon - cannot cannot - canon canon - canoniz canoniz - canonize canon - canonized canon - canons canon - canopied canopi - canopies canopi - canopy canopi - canst canst - canstick canstick - canterbury canterburi - cantle cantl - cantons canton - canus canu - canvas canva - canvass canvass - canzonet canzonet - cap cap - capability capabl - capable capabl - capacities capac - capacity capac - caparison caparison - capdv capdv - cape cape - capel capel - capels capel - caper caper - capers caper - capet capet - caphis caphi - capilet capilet - capitaine capitain - capital capit - capite capit - capitol capitol - capitulate capitul - capocchia capocchia - capon capon - capons capon - capp capp - cappadocia cappadocia - capriccio capriccio - capricious caprici - caps cap - capt capt - captain captain - captains captain - captainship captainship - captious captiou - captivate captiv - captivated captiv - captivates captiv - captive captiv - captives captiv - captivity captiv - captum captum - capucius capuciu - capulet capulet - capulets capulet - car car - carack carack - caracks carack - carat carat - caraways carawai - carbonado carbonado - carbuncle carbuncl - carbuncled carbuncl - carbuncles carbuncl - carcanet carcanet - carcase carcas - carcases carcas - carcass carcass - carcasses carcass - card card - cardecue cardecu - carded card - carders carder - cardinal cardin - cardinally cardin - cardinals cardin - cardmaker cardmak - cards card - carduus carduu - care care - cared care - career career - careers career - careful care - carefully carefulli - careless careless - carelessly carelessli - carelessness careless - cares care - caret caret - cargo cargo - carl carl - carlisle carlisl - carlot carlot - carman carman - carmen carmen - carnal carnal - carnally carnal - carnarvonshire carnarvonshir - carnation carnat - carnations carnat - carol carol - carous carou - carouse carous - caroused carous - carouses carous - carousing carous - carp carp - carpenter carpent - carper carper - carpet carpet - carpets carpet - carping carp - carriage carriag - carriages carriag - carried carri - carrier carrier - carriers carrier - carries carri - carrion carrion - carrions carrion - carry carri - carrying carri - cars car - cart cart - carters carter - carthage carthag - carts cart - carv carv - carve carv - carved carv - carver carver - carves carv - carving carv - cas ca - casa casa - casaer casaer - casca casca - case case - casement casement - casements casement - cases case - cash cash - cashier cashier - casing case - cask cask - casket casket - casketed casket - caskets casket - casque casqu - casques casqu - cassado cassado - cassandra cassandra - cassibelan cassibelan - cassio cassio - cassius cassiu - cassocks cassock - cast cast - castalion castalion - castaway castawai - castaways castawai - casted cast - caster caster - castigate castig - castigation castig - castile castil - castiliano castiliano - casting cast - castle castl - castles castl - casts cast - casual casual - casually casual - casualties casualti - casualty casualti - cat cat - cataian cataian - catalogue catalogu - cataplasm cataplasm - cataracts cataract - catarrhs catarrh - catastrophe catastroph - catch catch - catcher catcher - catches catch - catching catch - cate cate - catechising catechis - catechism catech - catechize catech - cater cater - caterpillars caterpillar - caters cater - caterwauling caterwaul - cates cate - catesby catesbi - cathedral cathedr - catlike catlik - catling catl - catlings catl - cato cato - cats cat - cattle cattl - caucasus caucasu - caudle caudl - cauf cauf - caught caught - cauldron cauldron - caus cau - cause caus - caused caus - causeless causeless - causer causer - causes caus - causest causest - causeth causeth - cautel cautel - cautelous cautel - cautels cautel - cauterizing cauter - caution caution - cautions caution - cavaleiro cavaleiro - cavalery cavaleri - cavaliers cavali - cave cave - cavern cavern - caverns cavern - caves cave - caveto caveto - caviary caviari - cavil cavil - cavilling cavil - cawdor cawdor - cawdron cawdron - cawing caw - ce ce - ceas cea - cease ceas - ceases ceas - ceaseth ceaseth - cedar cedar - cedars cedar - cedius cediu - celebrate celebr - celebrated celebr - celebrates celebr - celebration celebr - celerity celer - celestial celesti - celia celia - cell cell - cellar cellar - cellarage cellarag - celsa celsa - cement cement - censer censer - censor censor - censorinus censorinu - censur censur - censure censur - censured censur - censurers censur - censures censur - censuring censur - centaur centaur - centaurs centaur - centre centr - cents cent - centuries centuri - centurion centurion - centurions centurion - century centuri - cerberus cerberu - cerecloth cerecloth - cerements cerement - ceremonial ceremoni - ceremonies ceremoni - ceremonious ceremoni - ceremoniously ceremoni - ceremony ceremoni - ceres cere - cerns cern - certain certain - certainer certain - certainly certainli - certainties certainti - certainty certainti - certes cert - certificate certif - certified certifi - certifies certifi - certify certifi - ces ce - cesario cesario - cess cess - cesse cess - cestern cestern - cetera cetera - cette cett - chaces chace - chaf chaf - chafe chafe - chafed chafe - chafes chafe - chaff chaff - chaffless chaffless - chafing chafe - chain chain - chains chain - chair chair - chairs chair - chalic chalic - chalice chalic - chalices chalic - chalk chalk - chalks chalk - chalky chalki - challeng challeng - challenge challeng - challenged challeng - challenger challeng - challengers challeng - challenges challeng - cham cham - chamber chamber - chamberers chamber - chamberlain chamberlain - chamberlains chamberlain - chambermaid chambermaid - chambermaids chambermaid - chambers chamber - chameleon chameleon - champ champ - champagne champagn - champain champain - champains champain - champion champion - champions champion - chanc chanc - chance chanc - chanced chanc - chancellor chancellor - chances chanc - chandler chandler - chang chang - change chang - changeable changeabl - changed chang - changeful chang - changeling changel - changelings changel - changer changer - changes chang - changest changest - changing chang - channel channel - channels channel - chanson chanson - chant chant - chanticleer chanticl - chanting chant - chantries chantri - chantry chantri - chants chant - chaos chao - chap chap - chape chape - chapel chapel - chapeless chapeless - chapels chapel - chaplain chaplain - chaplains chaplain - chapless chapless - chaplet chaplet - chapmen chapmen - chaps chap - chapter chapter - character charact - charactered charact - characterless characterless - characters charact - charactery characteri - characts charact - charbon charbon - chare chare - chares chare - charg charg - charge charg - charged charg - chargeful charg - charges charg - chargeth chargeth - charging charg - chariest chariest - chariness chari - charing chare - chariot chariot - chariots chariot - charitable charit - charitably charit - charities chariti - charity chariti - charlemain charlemain - charles charl - charm charm - charmed charm - charmer charmer - charmeth charmeth - charmian charmian - charming charm - charmingly charmingli - charms charm - charneco charneco - charnel charnel - charolois charoloi - charon charon - charter charter - charters charter - chartreux chartreux - chary chari - charybdis charybdi - chas cha - chase chase - chased chase - chaser chaser - chaseth chaseth - chasing chase - chaste chast - chastely chast - chastis chasti - chastise chastis - chastised chastis - chastisement chastis - chastity chastiti - chat chat - chatham chatham - chatillon chatillon - chats chat - chatt chatt - chattels chattel - chatter chatter - chattering chatter - chattles chattl - chaud chaud - chaunted chaunt - chaw chaw - chawdron chawdron - che che - cheap cheap - cheapen cheapen - cheaper cheaper - cheapest cheapest - cheaply cheapli - cheapside cheapsid - cheat cheat - cheated cheat - cheater cheater - cheaters cheater - cheating cheat - cheats cheat - check check - checked check - checker checker - checking check - checks check - cheek cheek - cheeks cheek - cheer cheer - cheered cheer - cheerer cheerer - cheerful cheer - cheerfully cheerfulli - cheering cheer - cheerless cheerless - cheerly cheerli - cheers cheer - cheese chees - chequer chequer - cher cher - cherish cherish - cherished cherish - cherisher cherish - cherishes cherish - cherishing cherish - cherries cherri - cherry cherri - cherrypit cherrypit - chertsey chertsei - cherub cherub - cherubims cherubim - cherubin cherubin - cherubins cherubin - cheshu cheshu - chess chess - chest chest - chester chester - chestnut chestnut - chestnuts chestnut - chests chest - chetas cheta - chev chev - cheval cheval - chevalier chevali - chevaliers chevali - cheveril cheveril - chew chew - chewed chew - chewet chewet - chewing chew - chez chez - chi chi - chick chick - chicken chicken - chickens chicken - chicurmurco chicurmurco - chid chid - chidden chidden - chide chide - chiders chider - chides chide - chiding chide - chief chief - chiefest chiefest - chiefly chiefli - chien chien - child child - childed child - childeric childer - childhood childhood - childhoods childhood - childing child - childish childish - childishness childish - childlike childlik - childness child - children children - chill chill - chilling chill - chime chime - chimes chime - chimney chimnei - chimneypiece chimneypiec - chimneys chimnei - chimurcho chimurcho - chin chin - china china - chine chine - chines chine - chink chink - chinks chink - chins chin - chipp chipp - chipper chipper - chips chip - chiron chiron - chirping chirp - chirrah chirrah - chirurgeonly chirurgeonli - chisel chisel - chitopher chitoph - chivalrous chivalr - chivalry chivalri - choice choic - choicely choic - choicest choicest - choir choir - choirs choir - chok chok - choke choke - choked choke - chokes choke - choking choke - choler choler - choleric choler - cholers choler - chollors chollor - choose choos - chooser chooser - chooses choos - chooseth chooseth - choosing choos - chop chop - chopine chopin - choplogic choplog - chopp chopp - chopped chop - chopping chop - choppy choppi - chops chop - chopt chopt - chor chor - choristers chorist - chorus choru - chose chose - chosen chosen - chough chough - choughs chough - chrish chrish - christ christ - christen christen - christendom christendom - christendoms christendom - christening christen - christenings christen - christian christian - christianlike christianlik - christians christian - christmas christma - christom christom - christopher christoph - christophero christophero - chronicle chronicl - chronicled chronicl - chronicler chronicl - chroniclers chronicl - chronicles chronicl - chrysolite chrysolit - chuck chuck - chucks chuck - chud chud - chuffs chuff - church church - churches church - churchman churchman - churchmen churchmen - churchyard churchyard - churchyards churchyard - churl churl - churlish churlish - churlishly churlishli - churls churl - churn churn - chus chu - cicatrice cicatric - cicatrices cicatric - cicely cice - cicero cicero - ciceter cicet - ciel ciel - ciitzens ciitzen - cilicia cilicia - cimber cimber - cimmerian cimmerian - cinable cinabl - cincture cinctur - cinders cinder - cine cine - cinna cinna - cinque cinqu - cipher cipher - ciphers cipher - circa circa - circe circ - circle circl - circled circl - circlets circlet - circling circl - circuit circuit - circum circum - circumcised circumcis - circumference circumfer - circummur circummur - circumscrib circumscrib - circumscribed circumscrib - circumscription circumscript - circumspect circumspect - circumstance circumst - circumstanced circumstanc - circumstances circumst - circumstantial circumstanti - circumvent circumv - circumvention circumvent - cistern cistern - citadel citadel - cital cital - cite cite - cited cite - cites cite - cities citi - citing cite - citizen citizen - citizens citizen - cittern cittern - city citi - civet civet - civil civil - civility civil - civilly civilli - clack clack - clad clad - claim claim - claiming claim - claims claim - clamb clamb - clamber clamber - clammer clammer - clamor clamor - clamorous clamor - clamors clamor - clamour clamour - clamours clamour - clang clang - clangor clangor - clap clap - clapp clapp - clapped clap - clapper clapper - clapping clap - claps clap - clare clare - clarence clarenc - claret claret - claribel claribel - clasp clasp - clasps clasp - clatter clatter - claud claud - claudio claudio - claudius claudiu - clause claus - claw claw - clawed claw - clawing claw - claws claw - clay clai - clays clai - clean clean - cleanliest cleanliest - cleanly cleanli - cleans clean - cleanse cleans - cleansing cleans - clear clear - clearer clearer - clearest clearest - clearly clearli - clearness clear - clears clear - cleave cleav - cleaving cleav - clef clef - cleft cleft - cleitus cleitu - clemency clemenc - clement clement - cleomenes cleomen - cleopatpa cleopatpa - cleopatra cleopatra - clepeth clepeth - clept clept - clerestories clerestori - clergy clergi - clergyman clergyman - clergymen clergymen - clerk clerk - clerkly clerkli - clerks clerk - clew clew - client client - clients client - cliff cliff - clifford clifford - cliffords clifford - cliffs cliff - clifton clifton - climate climat - climature climatur - climb climb - climbed climb - climber climber - climbeth climbeth - climbing climb - climbs climb - clime clime - cling cling - clink clink - clinking clink - clinquant clinquant - clip clip - clipp clipp - clipper clipper - clippeth clippeth - clipping clip - clipt clipt - clitus clitu - clo clo - cloak cloak - cloakbag cloakbag - cloaks cloak - clock clock - clocks clock - clod clod - cloddy cloddi - clodpole clodpol - clog clog - clogging clog - clogs clog - cloister cloister - cloistress cloistress - cloquence cloquenc - clos clo - close close - closed close - closely close - closeness close - closer closer - closes close - closest closest - closet closet - closing close - closure closur - cloten cloten - clotens cloten - cloth cloth - clothair clothair - clotharius clothariu - clothe cloth - clothes cloth - clothier clothier - clothiers clothier - clothing cloth - cloths cloth - clotpoles clotpol - clotpoll clotpol - cloud cloud - clouded cloud - cloudiness cloudi - clouds cloud - cloudy cloudi - clout clout - clouted clout - clouts clout - cloven cloven - clover clover - cloves clove - clovest clovest - clowder clowder - clown clown - clownish clownish - clowns clown - cloy cloi - cloyed cloi - cloying cloi - cloyless cloyless - cloyment cloyment - cloys cloi - club club - clubs club - cluck cluck - clung clung - clust clust - clusters cluster - clutch clutch - clyster clyster - cneius cneiu - cnemies cnemi - co co - coach coach - coaches coach - coachmakers coachmak - coact coact - coactive coactiv - coagulate coagul - coal coal - coals coal - coarse coars - coarsely coars - coast coast - coasting coast - coasts coast - coat coat - coated coat - coats coat - cobble cobbl - cobbled cobbl - cobbler cobbler - cobham cobham - cobloaf cobloaf - cobweb cobweb - cobwebs cobweb - cock cock - cockatrice cockatric - cockatrices cockatric - cockle cockl - cockled cockl - cockney cocknei - cockpit cockpit - cocks cock - cocksure cocksur - coctus coctu - cocytus cocytu - cod cod - codding cod - codling codl - codpiece codpiec - codpieces codpiec - cods cod - coelestibus coelestibu - coesar coesar - coeur coeur - coffer coffer - coffers coffer - coffin coffin - coffins coffin - cog cog - cogging cog - cogitation cogit - cogitations cogit - cognition cognit - cognizance cogniz - cogscomb cogscomb - cohabitants cohabit - coher coher - cohere coher - coherence coher - coherent coher - cohorts cohort - coif coif - coign coign - coil coil - coin coin - coinage coinag - coiner coiner - coining coin - coins coin - col col - colbrand colbrand - colchos colcho - cold cold - colder colder - coldest coldest - coldly coldli - coldness cold - coldspur coldspur - colebrook colebrook - colic colic - collar collar - collars collar - collateral collater - colleagued colleagu - collect collect - collected collect - collection collect - college colleg - colleges colleg - collied colli - collier collier - colliers collier - collop collop - collusion collus - colme colm - colmekill colmekil - coloquintida coloquintida - color color - colors color - colossus colossu - colour colour - colourable colour - coloured colour - colouring colour - colours colour - colt colt - colted colt - colts colt - columbine columbin - columbines columbin - colville colvil - com com - comagene comagen - comart comart - comb comb - combat combat - combatant combat - combatants combat - combated combat - combating combat - combin combin - combinate combin - combination combin - combine combin - combined combin - combless combless - combustion combust - come come - comedian comedian - comedians comedian - comedy comedi - comeliness comeli - comely come - comer comer - comers comer - comes come - comest comest - comet comet - cometh cometh - comets comet - comfect comfect - comfit comfit - comfits comfit - comfort comfort - comfortable comfort - comforted comfort - comforter comfort - comforting comfort - comfortless comfortless - comforts comfort - comic comic - comical comic - coming come - comings come - cominius cominiu - comma comma - command command - commande command - commanded command - commander command - commanders command - commanding command - commandment command - commandments command - commands command - comme comm - commenc commenc - commence commenc - commenced commenc - commencement commenc - commences commenc - commencing commenc - commend commend - commendable commend - commendation commend - commendations commend - commended commend - commending commend - commends commend - comment comment - commentaries commentari - commenting comment - comments comment - commerce commerc - commingled commingl - commiseration commiser - commission commiss - commissioners commission - commissions commiss - commit commit - commits commit - committ committ - committed commit - committing commit - commix commix - commixed commix - commixtion commixt - commixture commixtur - commodious commodi - commodities commod - commodity commod - common common - commonalty commonalti - commoner common - commoners common - commonly commonli - commons common - commonweal commonw - commonwealth commonwealth - commotion commot - commotions commot - commune commun - communicat communicat - communicate commun - communication commun - communities commun - community commun - comonty comonti - compact compact - companies compani - companion companion - companions companion - companionship companionship - company compani - compar compar - comparative compar - compare compar - compared compar - comparing compar - comparison comparison - comparisons comparison - compartner compartn - compass compass - compasses compass - compassing compass - compassion compass - compassionate compassion - compeers compeer - compel compel - compell compel - compelled compel - compelling compel - compels compel - compensation compens - competence compet - competency compet - competent compet - competitor competitor - competitors competitor - compil compil - compile compil - compiled compil - complain complain - complainer complain - complainest complainest - complaining complain - complainings complain - complains complain - complaint complaint - complaints complaint - complement complement - complements complement - complete complet - complexion complexion - complexioned complexion - complexions complexion - complices complic - complies compli - compliment compliment - complimental compliment - compliments compliment - complot complot - complots complot - complotted complot - comply compli - compos compo - compose compos - composed compos - composition composit - compost compost - composture compostur - composure composur - compound compound - compounded compound - compounds compound - comprehend comprehend - comprehended comprehend - comprehends comprehend - compremises compremis - compris compri - comprising compris - compromis compromi - compromise compromis - compt compt - comptible comptibl - comptrollers comptrol - compulsatory compulsatori - compulsion compuls - compulsive compuls - compunctious compuncti - computation comput - comrade comrad - comrades comrad - comutual comutu - con con - concave concav - concavities concav - conceal conceal - concealed conceal - concealing conceal - concealment conceal - concealments conceal - conceals conceal - conceit conceit - conceited conceit - conceitless conceitless - conceits conceit - conceiv conceiv - conceive conceiv - conceived conceiv - conceives conceiv - conceiving conceiv - conception concept - conceptions concept - conceptious concepti - concern concern - concernancy concern - concerneth concerneth - concerning concern - concernings concern - concerns concern - conclave conclav - conclud conclud - conclude conclud - concluded conclud - concludes conclud - concluding conclud - conclusion conclus - conclusions conclus - concolinel concolinel - concord concord - concubine concubin - concupiscible concupisc - concupy concupi - concur concur - concurring concur - concurs concur - condemn condemn - condemnation condemn - condemned condemn - condemning condemn - condemns condemn - condescend condescend - condign condign - condition condit - conditionally condition - conditions condit - condole condol - condolement condol - condoling condol - conduce conduc - conduct conduct - conducted conduct - conducting conduct - conductor conductor - conduit conduit - conduits conduit - conected conect - coney conei - confection confect - confectionary confectionari - confections confect - confederacy confederaci - confederate confeder - confederates confeder - confer confer - conference confer - conferr conferr - conferring confer - confess confess - confessed confess - confesses confess - confesseth confesseth - confessing confess - confession confess - confessions confess - confessor confessor - confidence confid - confident confid - confidently confid - confin confin - confine confin - confined confin - confineless confineless - confiners confin - confines confin - confining confin - confirm confirm - confirmation confirm - confirmations confirm - confirmed confirm - confirmer confirm - confirmers confirm - confirming confirm - confirmities confirm - confirms confirm - confiscate confisc - confiscated confisc - confiscation confisc - confixed confix - conflict conflict - conflicting conflict - conflicts conflict - confluence confluenc - conflux conflux - conform conform - conformable conform - confound confound - confounded confound - confounding confound - confounds confound - confront confront - confronted confront - confus confu - confused confus - confusedly confusedli - confusion confus - confusions confus - confutation confut - confutes confut - congeal congeal - congealed congeal - congealment congeal - congee conge - conger conger - congest congest - congied congi - congratulate congratul - congreeing congre - congreeted congreet - congregate congreg - congregated congreg - congregation congreg - congregations congreg - congruent congruent - congruing congru - conies coni - conjectural conjectur - conjecture conjectur - conjectures conjectur - conjoin conjoin - conjoined conjoin - conjoins conjoin - conjointly conjointli - conjunct conjunct - conjunction conjunct - conjunctive conjunct - conjur conjur - conjuration conjur - conjurations conjur - conjure conjur - conjured conjur - conjurer conjur - conjurers conjur - conjures conjur - conjuring conjur - conjuro conjuro - conn conn - connected connect - connive conniv - conqu conqu - conquer conquer - conquered conquer - conquering conquer - conqueror conqueror - conquerors conqueror - conquers conquer - conquest conquest - conquests conquest - conquring conqur - conrade conrad - cons con - consanguineous consanguin - consanguinity consanguin - conscienc conscienc - conscience conscienc - consciences conscienc - conscionable conscion - consecrate consecr - consecrated consecr - consecrations consecr - consent consent - consented consent - consenting consent - consents consent - consequence consequ - consequences consequ - consequently consequ - conserve conserv - conserved conserv - conserves conserv - consider consid - considerance consider - considerate consider - consideration consider - considerations consider - considered consid - considering consid - considerings consid - considers consid - consign consign - consigning consign - consist consist - consisteth consisteth - consisting consist - consistory consistori - consists consist - consolate consol - consolation consol - consonancy conson - consonant conson - consort consort - consorted consort - consortest consortest - conspectuities conspectu - conspir conspir - conspiracy conspiraci - conspirant conspir - conspirator conspir - conspirators conspir - conspire conspir - conspired conspir - conspirers conspir - conspires conspir - conspiring conspir - constable constabl - constables constabl - constance constanc - constancies constanc - constancy constanc - constant constant - constantine constantin - constantinople constantinopl - constantly constantli - constellation constel - constitution constitut - constrain constrain - constrained constrain - constraineth constraineth - constrains constrain - constraint constraint - constring constr - construction construct - construe constru - consul consul - consuls consul - consulship consulship - consulships consulship - consult consult - consulting consult - consults consult - consum consum - consume consum - consumed consum - consumes consum - consuming consum - consummate consumm - consummation consumm - consumption consumpt - consumptions consumpt - contagion contagion - contagious contagi - contain contain - containing contain - contains contain - contaminate contamin - contaminated contamin - contemn contemn - contemned contemn - contemning contemn - contemns contemn - contemplate contempl - contemplation contempl - contemplative contempl - contempt contempt - contemptible contempt - contempts contempt - contemptuous contemptu - contemptuously contemptu - contend contend - contended contend - contending contend - contendon contendon - content content - contenta contenta - contented content - contenteth contenteth - contention content - contentious contenti - contentless contentless - contento contento - contents content - contest contest - contestation contest - continence contin - continency contin - continent contin - continents contin - continu continu - continual continu - continually continu - continuance continu - continuantly continuantli - continuate continu - continue continu - continued continu - continuer continu - continues continu - continuing continu - contract contract - contracted contract - contracting contract - contraction contract - contradict contradict - contradicted contradict - contradiction contradict - contradicts contradict - contraries contrari - contrarieties contrarieti - contrariety contrarieti - contrarious contrari - contrariously contrari - contrary contrari - contre contr - contribution contribut - contributors contributor - contrite contrit - contriv contriv - contrive contriv - contrived contriv - contriver contriv - contrives contriv - contriving contriv - control control - controll control - controller control - controlling control - controlment control - controls control - controversy controversi - contumelious contumeli - contumeliously contumeli - contumely contum - contusions contus - convenience conveni - conveniences conveni - conveniency conveni - convenient conveni - conveniently conveni - convented convent - conventicles conventicl - convents convent - convers conver - conversant convers - conversation convers - conversations convers - converse convers - conversed convers - converses convers - conversing convers - conversion convers - convert convert - converted convert - convertest convertest - converting convert - convertite convertit - convertites convertit - converts convert - convey convei - conveyance convey - conveyances convey - conveyers convey - conveying convei - convict convict - convicted convict - convince convinc - convinced convinc - convinces convinc - convive conviv - convocation convoc - convoy convoi - convulsions convuls - cony coni - cook cook - cookery cookeri - cooks cook - cool cool - cooled cool - cooling cool - cools cool - coop coop - coops coop - cop cop - copatain copatain - cope cope - cophetua cophetua - copied copi - copies copi - copious copiou - copper copper - copperspur copperspur - coppice coppic - copulation copul - copulatives copul - copy copi - cor cor - coragio coragio - coral coral - coram coram - corambus corambu - coranto coranto - corantos coranto - corbo corbo - cord cord - corded cord - cordelia cordelia - cordial cordial - cordis cordi - cords cord - core core - corin corin - corinth corinth - corinthian corinthian - coriolanus coriolanu - corioli corioli - cork cork - corky corki - cormorant cormor - corn corn - cornelia cornelia - cornelius corneliu - corner corner - corners corner - cornerstone cornerston - cornets cornet - cornish cornish - corns corn - cornuto cornuto - cornwall cornwal - corollary corollari - coronal coron - coronation coron - coronet coronet - coronets coronet - corporal corpor - corporals corpor - corporate corpor - corpse corps - corpulent corpul - correct correct - corrected correct - correcting correct - correction correct - correctioner correction - corrects correct - correspondence correspond - correspondent correspond - corresponding correspond - corresponsive correspons - corrigible corrig - corrival corriv - corrivals corriv - corroborate corrobor - corrosive corros - corrupt corrupt - corrupted corrupt - corrupter corrupt - corrupters corrupt - corruptible corrupt - corruptibly corrupt - corrupting corrupt - corruption corrupt - corruptly corruptli - corrupts corrupt - corse cors - corses cors - corslet corslet - cosmo cosmo - cost cost - costard costard - costermongers costermong - costlier costlier - costly costli - costs cost - cot cot - cote cote - coted cote - cotsall cotsal - cotsole cotsol - cotswold cotswold - cottage cottag - cottages cottag - cotus cotu - couch couch - couched couch - couching couch - couchings couch - coude coud - cough cough - coughing cough - could could - couldst couldst - coulter coulter - council council - councillor councillor - councils council - counsel counsel - counsell counsel - counsellor counsellor - counsellors counsellor - counselor counselor - counselors counselor - counsels counsel - count count - counted count - countenanc countenanc - countenance counten - countenances counten - counter counter - counterchange counterchang - countercheck countercheck - counterfeit counterfeit - counterfeited counterfeit - counterfeiting counterfeit - counterfeitly counterfeitli - counterfeits counterfeit - countermand countermand - countermands countermand - countermines countermin - counterpart counterpart - counterpoints counterpoint - counterpois counterpoi - counterpoise counterpois - counters counter - countervail countervail - countess countess - countesses countess - counties counti - counting count - countless countless - countries countri - countrv countrv - country countri - countryman countryman - countrymen countrymen - counts count - county counti - couper couper - couple coupl - coupled coupl - couplement couplement - couples coupl - couplet couplet - couplets couplet - cour cour - courage courag - courageous courag - courageously courag - courages courag - courier courier - couriers courier - couronne couronn - cours cour - course cours - coursed cours - courser courser - coursers courser - courses cours - coursing cours - court court - courted court - courteous courteou - courteously courteous - courtesan courtesan - courtesies courtesi - courtesy courtesi - courtezan courtezan - courtezans courtezan - courtier courtier - courtiers courtier - courtlike courtlik - courtly courtli - courtney courtnei - courts court - courtship courtship - cousin cousin - cousins cousin - couterfeit couterfeit - coutume coutum - covenant coven - covenants coven - covent covent - coventry coventri - cover cover - covered cover - covering cover - coverlet coverlet - covers cover - covert covert - covertly covertli - coverture covertur - covet covet - coveted covet - coveting covet - covetings covet - covetous covet - covetously covet - covetousness covet - covets covet - cow cow - coward coward - cowarded coward - cowardice cowardic - cowardly cowardli - cowards coward - cowardship cowardship - cowish cowish - cowl cowl - cowslip cowslip - cowslips cowslip - cox cox - coxcomb coxcomb - coxcombs coxcomb - coy coi - coystrill coystril - coz coz - cozen cozen - cozenage cozenag - cozened cozen - cozener cozen - cozeners cozen - cozening cozen - coziers cozier - crab crab - crabbed crab - crabs crab - crack crack - cracked crack - cracker cracker - crackers cracker - cracking crack - cracks crack - cradle cradl - cradled cradl - cradles cradl - craft craft - crafted craft - craftied crafti - craftier craftier - craftily craftili - crafts craft - craftsmen craftsmen - crafty crafti - cram cram - cramm cramm - cramp cramp - cramps cramp - crams cram - cranking crank - cranks crank - cranmer cranmer - crannied cranni - crannies cranni - cranny cranni - crants crant - crare crare - crash crash - crassus crassu - crav crav - crave crave - craved crave - craven craven - cravens craven - craves crave - craveth craveth - craving crave - crawl crawl - crawling crawl - crawls crawl - craz craz - crazed craze - crazy crazi - creaking creak - cream cream - create creat - created creat - creates creat - creating creat - creation creation - creator creator - creature creatur - creatures creatur - credence credenc - credent credent - credible credibl - credit credit - creditor creditor - creditors creditor - credo credo - credulity credul - credulous credul - creed creed - creek creek - creeks creek - creep creep - creeping creep - creeps creep - crept crept - crescent crescent - crescive cresciv - cressets cresset - cressid cressid - cressida cressida - cressids cressid - cressy cressi - crest crest - crested crest - crestfall crestfal - crestless crestless - crests crest - cretan cretan - crete crete - crevice crevic - crew crew - crews crew - crib crib - cribb cribb - cribs crib - cricket cricket - crickets cricket - cried cri - criedst criedst - crier crier - cries cri - criest criest - crieth crieth - crime crime - crimeful crime - crimeless crimeless - crimes crime - criminal crimin - crimson crimson - cringe cring - cripple crippl - crisp crisp - crisped crisp - crispian crispian - crispianus crispianu - crispin crispin - critic critic - critical critic - critics critic - croak croak - croaking croak - croaks croak - crocodile crocodil - cromer cromer - cromwell cromwel - crone crone - crook crook - crookback crookback - crooked crook - crooking crook - crop crop - cropp cropp - crosby crosbi - cross cross - crossed cross - crosses cross - crossest crossest - crossing cross - crossings cross - crossly crossli - crossness cross - crost crost - crotchets crotchet - crouch crouch - crouching crouch - crow crow - crowd crowd - crowded crowd - crowding crowd - crowds crowd - crowflowers crowflow - crowing crow - crowkeeper crowkeep - crown crown - crowned crown - crowner crowner - crownet crownet - crownets crownet - crowning crown - crowns crown - crows crow - crudy crudi - cruel cruel - cruell cruell - crueller crueller - cruelly cruelli - cruels cruel - cruelty cruelti - crum crum - crumble crumbl - crumbs crumb - crupper crupper - crusadoes crusado - crush crush - crushed crush - crushest crushest - crushing crush - crust crust - crusts crust - crusty crusti - crutch crutch - crutches crutch - cry cry - crying cry - crystal crystal - crystalline crystallin - crystals crystal - cub cub - cubbert cubbert - cubiculo cubiculo - cubit cubit - cubs cub - cuckold cuckold - cuckoldly cuckoldli - cuckolds cuckold - cuckoo cuckoo - cucullus cucullu - cudgel cudgel - cudgeled cudgel - cudgell cudgel - cudgelling cudgel - cudgels cudgel - cue cue - cues cue - cuff cuff - cuffs cuff - cuique cuiqu - cull cull - culling cull - cullion cullion - cullionly cullionli - cullions cullion - culpable culpabl - culverin culverin - cum cum - cumber cumber - cumberland cumberland - cunning cun - cunningly cunningli - cunnings cun - cuore cuor - cup cup - cupbearer cupbear - cupboarding cupboard - cupid cupid - cupids cupid - cuppele cuppel - cups cup - cur cur - curan curan - curate curat - curb curb - curbed curb - curbing curb - curbs curb - curd curd - curdied curdi - curds curd - cure cure - cured cure - cureless cureless - curer curer - cures cure - curfew curfew - curing cure - curio curio - curiosity curios - curious curiou - curiously curious - curl curl - curled curl - curling curl - curls curl - currance curranc - currants currant - current current - currents current - currish currish - curry curri - curs cur - curse curs - cursed curs - curses curs - cursies cursi - cursing curs - cursorary cursorari - curst curst - curster curster - curstest curstest - curstness curst - cursy cursi - curtail curtail - curtain curtain - curtains curtain - curtal curtal - curtis curti - curtle curtl - curtsied curtsi - curtsies curtsi - curtsy curtsi - curvet curvet - curvets curvet - cushes cush - cushion cushion - cushions cushion - custalorum custalorum - custard custard - custody custodi - custom custom - customary customari - customed custom - customer custom - customers custom - customs custom - custure custur - cut cut - cutler cutler - cutpurse cutpurs - cutpurses cutpurs - cuts cut - cutter cutter - cutting cut - cuttle cuttl - cxsar cxsar - cyclops cyclop - cydnus cydnu - cygnet cygnet - cygnets cygnet - cym cym - cymbals cymbal - cymbeline cymbelin - cyme cyme - cynic cynic - cynthia cynthia - cypress cypress - cypriot cypriot - cyprus cypru - cyrus cyru - cytherea cytherea - d d - dabbled dabbl - dace dace - dad dad - daedalus daedalu - daemon daemon - daff daff - daffed daf - daffest daffest - daffodils daffodil - dagger dagger - daggers dagger - dagonet dagonet - daily daili - daintier daintier - dainties dainti - daintiest daintiest - daintily daintili - daintiness dainti - daintry daintri - dainty dainti - daisied daisi - daisies daisi - daisy daisi - dale dale - dalliance dallianc - dallied dalli - dallies dalli - dally dalli - dallying dalli - dalmatians dalmatian - dam dam - damage damag - damascus damascu - damask damask - damasked damask - dame dame - dames dame - damm damm - damn damn - damnable damnabl - damnably damnabl - damnation damnat - damned damn - damns damn - damoiselle damoisel - damon damon - damosella damosella - damp damp - dams dam - damsel damsel - damsons damson - dan dan - danc danc - dance danc - dancer dancer - dances danc - dancing danc - dandle dandl - dandy dandi - dane dane - dang dang - danger danger - dangerous danger - dangerously danger - dangers danger - dangling dangl - daniel daniel - danish danish - dank dank - dankish dankish - danskers dansker - daphne daphn - dappled dappl - dapples dappl - dar dar - dardan dardan - dardanian dardanian - dardanius dardaniu - dare dare - dared dare - dareful dare - dares dare - darest darest - daring dare - darius dariu - dark dark - darken darken - darkening darken - darkens darken - darker darker - darkest darkest - darkling darkl - darkly darkli - darkness dark - darling darl - darlings darl - darnel darnel - darraign darraign - dart dart - darted dart - darter darter - dartford dartford - darting dart - darts dart - dash dash - dashes dash - dashing dash - dastard dastard - dastards dastard - dat dat - datchet datchet - date date - dated date - dateless dateless - dates date - daub daub - daughter daughter - daughters daughter - daunt daunt - daunted daunt - dauntless dauntless - dauphin dauphin - daventry daventri - davy davi - daw daw - dawn dawn - dawning dawn - daws daw - day dai - daylight daylight - days dai - dazzle dazzl - dazzled dazzl - dazzling dazzl - de de - dead dead - deadly deadli - deaf deaf - deafing deaf - deafness deaf - deafs deaf - deal deal - dealer dealer - dealers dealer - dealest dealest - dealing deal - dealings deal - deals deal - dealt dealt - dean dean - deanery deaneri - dear dear - dearer dearer - dearest dearest - dearly dearli - dearness dear - dears dear - dearth dearth - dearths dearth - death death - deathbed deathb - deathful death - deaths death - deathsman deathsman - deathsmen deathsmen - debarred debar - debase debas - debate debat - debated debat - debatement debat - debateth debateth - debating debat - debauch debauch - debile debil - debility debil - debitor debitor - debonair debonair - deborah deborah - debosh debosh - debt debt - debted debt - debtor debtor - debtors debtor - debts debt - debuty debuti - decay decai - decayed decai - decayer decay - decaying decai - decays decai - deceas decea - decease deceas - deceased deceas - deceit deceit - deceitful deceit - deceits deceit - deceiv deceiv - deceivable deceiv - deceive deceiv - deceived deceiv - deceiver deceiv - deceivers deceiv - deceives deceiv - deceivest deceivest - deceiveth deceiveth - deceiving deceiv - december decemb - decent decent - deceptious decepti - decerns decern - decide decid - decides decid - decimation decim - decipher deciph - deciphers deciph - decision decis - decius deciu - deck deck - decking deck - decks deck - deckt deckt - declare declar - declares declar - declension declens - declensions declens - declin declin - decline declin - declined declin - declines declin - declining declin - decoct decoct - decorum decorum - decreas decrea - decrease decreas - decreasing decreas - decree decre - decreed decre - decrees decre - decrepit decrepit - dedicate dedic - dedicated dedic - dedicates dedic - dedication dedic - deed deed - deedless deedless - deeds deed - deem deem - deemed deem - deep deep - deeper deeper - deepest deepest - deeply deepli - deeps deep - deepvow deepvow - deer deer - deesse deess - defac defac - deface defac - defaced defac - defacer defac - defacers defac - defacing defac - defam defam - default default - defeat defeat - defeated defeat - defeats defeat - defeatures defeatur - defect defect - defective defect - defects defect - defence defenc - defences defenc - defend defend - defendant defend - defended defend - defender defend - defenders defend - defending defend - defends defend - defense defens - defensible defens - defensive defens - defer defer - deferr deferr - defiance defianc - deficient defici - defied defi - defies defi - defil defil - defile defil - defiler defil - defiles defil - defiling defil - define defin - definement defin - definite definit - definitive definit - definitively definit - deflow deflow - deflower deflow - deflowered deflow - deform deform - deformed deform - deformities deform - deformity deform - deftly deftli - defunct defunct - defunction defunct - defuse defus - defy defi - defying defi - degenerate degener - degraded degrad - degree degre - degrees degre - deified deifi - deifying deifi - deign deign - deigned deign - deiphobus deiphobu - deities deiti - deity deiti - deja deja - deject deject - dejected deject - delabreth delabreth - delay delai - delayed delai - delaying delai - delays delai - delectable delect - deliberate deliber - delicate delic - delicates delic - delicious delici - deliciousness delici - delight delight - delighted delight - delightful delight - delights delight - delinquents delinqu - deliv deliv - deliver deliv - deliverance deliver - delivered deliv - delivering deliv - delivers deliv - delivery deliveri - delphos delpho - deluded delud - deluding delud - deluge delug - delve delv - delver delver - delves delv - demand demand - demanded demand - demanding demand - demands demand - demean demean - demeanor demeanor - demeanour demeanour - demerits demerit - demesnes demesn - demetrius demetriu - demi demi - demigod demigod - demise demis - demoiselles demoisel - demon demon - demonstrable demonstr - demonstrate demonstr - demonstrated demonstr - demonstrating demonstr - demonstration demonstr - demonstrative demonstr - demure demur - demurely demur - demuring demur - den den - denay denai - deni deni - denial denial - denials denial - denied deni - denier denier - denies deni - deniest deniest - denis deni - denmark denmark - dennis denni - denny denni - denote denot - denoted denot - denotement denot - denounc denounc - denounce denounc - denouncing denounc - dens den - denunciation denunci - deny deni - denying deni - deo deo - depart depart - departed depart - departest departest - departing depart - departure departur - depeche depech - depend depend - dependant depend - dependants depend - depended depend - dependence depend - dependences depend - dependency depend - dependent depend - dependents depend - depender depend - depending depend - depends depend - deplore deplor - deploring deplor - depopulate depopul - depos depo - depose depos - deposed depos - deposing depos - depositaries depositari - deprav deprav - depravation deprav - deprave deprav - depraved deprav - depraves deprav - depress depress - depriv depriv - deprive depriv - depth depth - depths depth - deputation deput - depute deput - deputed deput - deputies deputi - deputing deput - deputy deputi - deracinate deracin - derby derbi - dercetas derceta - dere dere - derides derid - derision deris - deriv deriv - derivation deriv - derivative deriv - derive deriv - derived deriv - derives deriv - derogate derog - derogately derog - derogation derog - des de - desartless desartless - descant descant - descend descend - descended descend - descending descend - descends descend - descension descens - descent descent - descents descent - describe describ - described describ - describes describ - descried descri - description descript - descriptions descript - descry descri - desdemon desdemon - desdemona desdemona - desert desert - deserts desert - deserv deserv - deserve deserv - deserved deserv - deservedly deservedli - deserver deserv - deservers deserv - deserves deserv - deservest deservest - deserving deserv - deservings deserv - design design - designment design - designments design - designs design - desir desir - desire desir - desired desir - desirers desir - desires desir - desirest desirest - desiring desir - desirous desir - desist desist - desk desk - desolate desol - desolation desol - desp desp - despair despair - despairing despair - despairs despair - despatch despatch - desperate desper - desperately desper - desperation desper - despis despi - despise despis - despised despis - despiser despis - despiseth despiseth - despising despis - despite despit - despiteful despit - despoiled despoil - dest dest - destin destin - destined destin - destinies destini - destiny destini - destitute destitut - destroy destroi - destroyed destroi - destroyer destroy - destroyers destroy - destroying destroi - destroys destroi - destruction destruct - destructions destruct - det det - detain detain - detains detain - detect detect - detected detect - detecting detect - detection detect - detector detector - detects detect - detention detent - determin determin - determinate determin - determination determin - determinations determin - determine determin - determined determin - determines determin - detest detest - detestable detest - detested detest - detesting detest - detests detest - detract detract - detraction detract - detractions detract - deucalion deucalion - deuce deuc - deum deum - deux deux - devant devant - devesting devest - device devic - devices devic - devil devil - devilish devilish - devils devil - devis devi - devise devis - devised devis - devises devis - devising devis - devoid devoid - devonshire devonshir - devote devot - devoted devot - devotion devot - devour devour - devoured devour - devourers devour - devouring devour - devours devour - devout devout - devoutly devoutli - dew dew - dewberries dewberri - dewdrops dewdrop - dewlap dewlap - dewlapp dewlapp - dews dew - dewy dewi - dexter dexter - dexteriously dexteri - dexterity dexter - di di - diable diabl - diablo diablo - diadem diadem - dial dial - dialect dialect - dialogue dialogu - dialogued dialogu - dials dial - diameter diamet - diamond diamond - diamonds diamond - dian dian - diana diana - diaper diaper - dibble dibbl - dic dic - dice dice - dicers dicer - dich dich - dick dick - dickens dicken - dickon dickon - dicky dicki - dictator dictat - diction diction - dictynna dictynna - did did - diddle diddl - didest didest - dido dido - didst didst - die die - died di - diedst diedst - dies di - diest diest - diet diet - dieted diet - dieter dieter - dieu dieu - diff diff - differ differ - difference differ - differences differ - differency differ - different differ - differing differ - differs differ - difficile difficil - difficult difficult - difficulties difficulti - difficulty difficulti - diffidence diffid - diffidences diffid - diffus diffu - diffused diffus - diffusest diffusest - dig dig - digest digest - digested digest - digestion digest - digestions digest - digg digg - digging dig - dighton dighton - dignified dignifi - dignifies dignifi - dignify dignifi - dignities digniti - dignity digniti - digress digress - digressing digress - digression digress - digs dig - digt digt - dilate dilat - dilated dilat - dilations dilat - dilatory dilatori - dild dild - dildos dildo - dilemma dilemma - dilemmas dilemma - diligence dilig - diligent dilig - diluculo diluculo - dim dim - dimension dimens - dimensions dimens - diminish diminish - diminishing diminish - diminution diminut - diminutive diminut - diminutives diminut - dimm dimm - dimmed dim - dimming dim - dimpled dimpl - dimples dimpl - dims dim - din din - dine dine - dined dine - diner diner - dines dine - ding ding - dining dine - dinner dinner - dinners dinner - dinnertime dinnertim - dint dint - diomed diom - diomede diomed - diomedes diomed - dion dion - dip dip - dipp dipp - dipping dip - dips dip - dir dir - dire dire - direct direct - directed direct - directing direct - direction direct - directions direct - directitude directitud - directive direct - directly directli - directs direct - direful dire - direness dire - direst direst - dirge dirg - dirges dirg - dirt dirt - dirty dirti - dis di - disability disabl - disable disabl - disabled disabl - disabling disabl - disadvantage disadvantag - disagree disagre - disallow disallow - disanimates disanim - disannul disannul - disannuls disannul - disappointed disappoint - disarm disarm - disarmed disarm - disarmeth disarmeth - disarms disarm - disaster disast - disasters disast - disastrous disastr - disbench disbench - disbranch disbranch - disburdened disburden - disburs disbur - disburse disburs - disbursed disburs - discandy discandi - discandying discandi - discard discard - discarded discard - discase discas - discased discas - discern discern - discerner discern - discerning discern - discernings discern - discerns discern - discharg discharg - discharge discharg - discharged discharg - discharging discharg - discipled discipl - disciples discipl - disciplin disciplin - discipline disciplin - disciplined disciplin - disciplines disciplin - disclaim disclaim - disclaiming disclaim - disclaims disclaim - disclos disclo - disclose disclos - disclosed disclos - discloses disclos - discolour discolour - discoloured discolour - discolours discolour - discomfit discomfit - discomfited discomfit - discomfiture discomfitur - discomfort discomfort - discomfortable discomfort - discommend discommend - disconsolate disconsol - discontent discont - discontented discont - discontentedly discontentedli - discontenting discont - discontents discont - discontinue discontinu - discontinued discontinu - discord discord - discordant discord - discords discord - discourse discours - discoursed discours - discourser discours - discourses discours - discoursive discours - discourtesy discourtesi - discov discov - discover discov - discovered discov - discoverers discover - discoveries discoveri - discovering discov - discovers discov - discovery discoveri - discredit discredit - discredited discredit - discredits discredit - discreet discreet - discreetly discreetli - discretion discret - discretions discret - discuss discuss - disdain disdain - disdained disdain - disdaineth disdaineth - disdainful disdain - disdainfully disdainfulli - disdaining disdain - disdains disdain - disdnguish disdnguish - diseas disea - disease diseas - diseased diseas - diseases diseas - disedg disedg - disembark disembark - disfigure disfigur - disfigured disfigur - disfurnish disfurnish - disgorge disgorg - disgrac disgrac - disgrace disgrac - disgraced disgrac - disgraceful disgrac - disgraces disgrac - disgracing disgrac - disgracious disgraci - disguis disgui - disguise disguis - disguised disguis - disguiser disguis - disguises disguis - disguising disguis - dish dish - dishabited dishabit - dishclout dishclout - dishearten dishearten - disheartens dishearten - dishes dish - dishonest dishonest - dishonestly dishonestli - dishonesty dishonesti - dishonor dishonor - dishonorable dishonor - dishonors dishonor - dishonour dishonour - dishonourable dishonour - dishonoured dishonour - dishonours dishonour - disinherit disinherit - disinherited disinherit - disjoin disjoin - disjoining disjoin - disjoins disjoin - disjoint disjoint - disjunction disjunct - dislik dislik - dislike dislik - disliken disliken - dislikes dislik - dislimns dislimn - dislocate disloc - dislodg dislodg - disloyal disloy - disloyalty disloyalti - dismal dismal - dismantle dismantl - dismantled dismantl - dismask dismask - dismay dismai - dismayed dismai - dismemb dismemb - dismember dismemb - dismes dism - dismiss dismiss - dismissed dismiss - dismissing dismiss - dismission dismiss - dismount dismount - dismounted dismount - disnatur disnatur - disobedience disobedi - disobedient disobedi - disobey disobei - disobeys disobei - disorb disorb - disorder disord - disordered disord - disorderly disorderli - disorders disord - disparage disparag - disparagement disparag - disparagements disparag - dispark dispark - dispatch dispatch - dispensation dispens - dispense dispens - dispenses dispens - dispers disper - disperse dispers - dispersed dispers - dispersedly dispersedli - dispersing dispers - dispiteous dispit - displac displac - displace displac - displaced displac - displant displant - displanting displant - display displai - displayed displai - displeas displea - displease displeas - displeased displeas - displeasing displeas - displeasure displeasur - displeasures displeasur - disponge dispong - disport disport - disports disport - dispos dispo - dispose dispos - disposed dispos - disposer dispos - disposing dispos - disposition disposit - dispositions disposit - dispossess dispossess - dispossessing dispossess - disprais disprai - dispraise disprais - dispraising disprais - dispraisingly dispraisingli - dispropertied disproperti - disproportion disproport - disproportioned disproport - disprov disprov - disprove disprov - disproved disprov - dispursed dispurs - disputable disput - disputation disput - disputations disput - dispute disput - disputed disput - disputes disput - disputing disput - disquantity disquant - disquiet disquiet - disquietly disquietli - disrelish disrelish - disrobe disrob - disseat disseat - dissemble dissembl - dissembled dissembl - dissembler dissembl - dissemblers dissembl - dissembling dissembl - dissembly dissembl - dissension dissens - dissensions dissens - dissentious dissenti - dissever dissev - dissipation dissip - dissolute dissolut - dissolutely dissolut - dissolution dissolut - dissolutions dissolut - dissolv dissolv - dissolve dissolv - dissolved dissolv - dissolves dissolv - dissuade dissuad - dissuaded dissuad - distaff distaff - distaffs distaff - distain distain - distains distain - distance distanc - distant distant - distaste distast - distasted distast - distasteful distast - distemp distemp - distemper distemp - distemperature distemperatur - distemperatures distemperatur - distempered distemp - distempering distemp - distil distil - distill distil - distillation distil - distilled distil - distills distil - distilment distil - distinct distinct - distinction distinct - distinctly distinctli - distingue distingu - distinguish distinguish - distinguishes distinguish - distinguishment distinguish - distract distract - distracted distract - distractedly distractedli - distraction distract - distractions distract - distracts distract - distrain distrain - distraught distraught - distress distress - distressed distress - distresses distress - distressful distress - distribute distribut - distributed distribut - distribution distribut - distrust distrust - distrustful distrust - disturb disturb - disturbed disturb - disturbers disturb - disturbing disturb - disunite disunit - disvalued disvalu - disvouch disvouch - dit dit - ditch ditch - ditchers ditcher - ditches ditch - dites dite - ditties ditti - ditty ditti - diurnal diurnal - div div - dive dive - diver diver - divers diver - diversely divers - diversity divers - divert divert - diverted divert - diverts divert - dives dive - divest divest - dividable divid - dividant divid - divide divid - divided divid - divides divid - divideth divideth - divin divin - divination divin - divine divin - divinely divin - divineness divin - diviner divin - divines divin - divinest divinest - divining divin - divinity divin - division divis - divisions divis - divorc divorc - divorce divorc - divorced divorc - divorcement divorc - divorcing divorc - divulg divulg - divulge divulg - divulged divulg - divulging divulg - dizy dizi - dizzy dizzi - do do - doating doat - dobbin dobbin - dock dock - docks dock - doct doct - doctor doctor - doctors doctor - doctrine doctrin - document document - dodge dodg - doe doe - doer doer - doers doer - does doe - doest doest - doff doff - dog dog - dogberry dogberri - dogfish dogfish - dogg dogg - dogged dog - dogs dog - doigts doigt - doing do - doings do - doit doit - doits doit - dolabella dolabella - dole dole - doleful dole - doll doll - dollar dollar - dollars dollar - dolor dolor - dolorous dolor - dolour dolour - dolours dolour - dolphin dolphin - dolt dolt - dolts dolt - domestic domest - domestics domest - dominance domin - dominations domin - dominator domin - domine domin - domineer domin - domineering domin - dominical domin - dominion dominion - dominions dominion - domitius domitiu - dommelton dommelton - don don - donalbain donalbain - donation donat - donc donc - doncaster doncast - done done - dong dong - donn donn - donne donn - donner donner - donnerai donnerai - doom doom - doomsday doomsdai - door door - doorkeeper doorkeep - doors door - dorcas dorca - doreus doreu - doricles doricl - dormouse dormous - dorothy dorothi - dorset dorset - dorsetshire dorsetshir - dost dost - dotage dotag - dotant dotant - dotard dotard - dotards dotard - dote dote - doted dote - doters doter - dotes dote - doteth doteth - doth doth - doting dote - double doubl - doubled doubl - doubleness doubl - doubler doubler - doublet doublet - doublets doublet - doubling doubl - doubly doubli - doubt doubt - doubted doubt - doubtful doubt - doubtfully doubtfulli - doubting doubt - doubtless doubtless - doubts doubt - doug doug - dough dough - doughty doughti - doughy doughi - douglas dougla - dout dout - doute dout - douts dout - dove dove - dovehouse dovehous - dover dover - doves dove - dow dow - dowager dowag - dowdy dowdi - dower dower - dowerless dowerless - dowers dower - dowlas dowla - dowle dowl - down down - downfall downfal - downright downright - downs down - downstairs downstair - downtrod downtrod - downward downward - downwards downward - downy downi - dowries dowri - dowry dowri - dowsabel dowsabel - doxy doxi - dozed doze - dozen dozen - dozens dozen - dozy dozi - drab drab - drabbing drab - drabs drab - drachma drachma - drachmas drachma - draff draff - drag drag - dragg dragg - dragged drag - dragging drag - dragon dragon - dragonish dragonish - dragons dragon - drain drain - drained drain - drains drain - drake drake - dram dram - dramatis dramati - drank drank - draught draught - draughts draught - drave drave - draw draw - drawbridge drawbridg - drawer drawer - drawers drawer - draweth draweth - drawing draw - drawling drawl - drawn drawn - draws draw - drayman drayman - draymen draymen - dread dread - dreaded dread - dreadful dread - dreadfully dreadfulli - dreading dread - dreads dread - dream dream - dreamer dreamer - dreamers dreamer - dreaming dream - dreams dream - dreamt dreamt - drearning drearn - dreary dreari - dreg dreg - dregs dreg - drench drench - drenched drench - dress dress - dressed dress - dresser dresser - dressing dress - dressings dress - drest drest - drew drew - dribbling dribbl - dried dri - drier drier - dries dri - drift drift - drily drili - drink drink - drinketh drinketh - drinking drink - drinkings drink - drinks drink - driv driv - drive drive - drivelling drivel - driven driven - drives drive - driveth driveth - driving drive - drizzle drizzl - drizzled drizzl - drizzles drizzl - droit droit - drollery drolleri - dromio dromio - dromios dromio - drone drone - drones drone - droop droop - droopeth droopeth - drooping droop - droops droop - drop drop - dropheir dropheir - droplets droplet - dropp dropp - dropper dropper - droppeth droppeth - dropping drop - droppings drop - drops drop - dropsied dropsi - dropsies dropsi - dropsy dropsi - dropt dropt - dross dross - drossy drossi - drought drought - drove drove - droven droven - drovier drovier - drown drown - drowned drown - drowning drown - drowns drown - drows drow - drowse drows - drowsily drowsili - drowsiness drowsi - drowsy drowsi - drudge drudg - drudgery drudgeri - drudges drudg - drug drug - drugg drugg - drugs drug - drum drum - drumble drumbl - drummer drummer - drumming drum - drums drum - drunk drunk - drunkard drunkard - drunkards drunkard - drunken drunken - drunkenly drunkenli - drunkenness drunken - dry dry - dryness dryness - dst dst - du du - dub dub - dubb dubb - ducat ducat - ducats ducat - ducdame ducdam - duchess duchess - duchies duchi - duchy duchi - duck duck - ducking duck - ducks duck - dudgeon dudgeon - due due - duellist duellist - duello duello - duer duer - dues due - duff duff - dug dug - dugs dug - duke duke - dukedom dukedom - dukedoms dukedom - dukes duke - dulcet dulcet - dulche dulch - dull dull - dullard dullard - duller duller - dullest dullest - dulling dull - dullness dull - dulls dull - dully dulli - dulness dul - duly duli - dumain dumain - dumb dumb - dumbe dumb - dumbly dumbl - dumbness dumb - dump dump - dumps dump - dun dun - duncan duncan - dung dung - dungeon dungeon - dungeons dungeon - dunghill dunghil - dunghills dunghil - dungy dungi - dunnest dunnest - dunsinane dunsinan - dunsmore dunsmor - dunstable dunstabl - dupp dupp - durance duranc - during dure - durst durst - dusky duski - dust dust - dusted dust - dusty dusti - dutch dutch - dutchman dutchman - duteous duteou - duties duti - dutiful duti - duty duti - dwarf dwarf - dwarfish dwarfish - dwell dwell - dwellers dweller - dwelling dwell - dwells dwell - dwelt dwelt - dwindle dwindl - dy dy - dye dye - dyed dy - dyer dyer - dying dy - e e - each each - eager eager - eagerly eagerli - eagerness eager - eagle eagl - eagles eagl - eaning ean - eanlings eanl - ear ear - earing ear - earl earl - earldom earldom - earlier earlier - earliest earliest - earliness earli - earls earl - early earli - earn earn - earned earn - earnest earnest - earnestly earnestli - earnestness earnest - earns earn - ears ear - earth earth - earthen earthen - earthlier earthlier - earthly earthli - earthquake earthquak - earthquakes earthquak - earthy earthi - eas ea - ease eas - eased eas - easeful eas - eases eas - easier easier - easiest easiest - easiliest easiliest - easily easili - easiness easi - easing eas - east east - eastcheap eastcheap - easter easter - eastern eastern - eastward eastward - easy easi - eat eat - eaten eaten - eater eater - eaters eater - eating eat - eats eat - eaux eaux - eaves eav - ebb ebb - ebbing eb - ebbs ebb - ebon ebon - ebony eboni - ebrew ebrew - ecce ecc - echapper echapp - echo echo - echoes echo - eclips eclip - eclipse eclips - eclipses eclips - ecolier ecoli - ecoutez ecoutez - ecstacy ecstaci - ecstasies ecstasi - ecstasy ecstasi - ecus ecu - eden eden - edg edg - edgar edgar - edge edg - edged edg - edgeless edgeless - edges edg - edict edict - edicts edict - edifice edific - edifices edific - edified edifi - edifies edifi - edition edit - edm edm - edmund edmund - edmunds edmund - edmundsbury edmundsburi - educate educ - educated educ - education educ - edward edward - eel eel - eels eel - effect effect - effected effect - effectless effectless - effects effect - effectual effectu - effectually effectu - effeminate effemin - effigies effigi - effus effu - effuse effus - effusion effus - eftest eftest - egal egal - egally egal - eget eget - egeus egeu - egg egg - eggs egg - eggshell eggshel - eglamour eglamour - eglantine eglantin - egma egma - ego ego - egregious egregi - egregiously egregi - egress egress - egypt egypt - egyptian egyptian - egyptians egyptian - eie eie - eight eight - eighteen eighteen - eighth eighth - eightpenny eightpenni - eighty eighti - eisel eisel - either either - eject eject - eke ek - el el - elbe elb - elbow elbow - elbows elbow - eld eld - elder elder - elders elder - eldest eldest - eleanor eleanor - elect elect - elected elect - election elect - elegancy eleg - elegies elegi - element element - elements element - elephant eleph - elephants eleph - elevated elev - eleven eleven - eleventh eleventh - elf elf - elflocks elflock - eliads eliad - elinor elinor - elizabeth elizabeth - ell ell - elle ell - ellen ellen - elm elm - eloquence eloqu - eloquent eloqu - else els - elsewhere elsewher - elsinore elsinor - eltham eltham - elves elv - elvish elvish - ely eli - elysium elysium - em em - emballing embal - embalm embalm - embalms embalm - embark embark - embarked embark - embarquements embarqu - embassade embassad - embassage embassag - embassies embassi - embassy embassi - embattailed embattail - embattl embattl - embattle embattl - embay embai - embellished embellish - embers ember - emblaze emblaz - emblem emblem - emblems emblem - embodied embodi - embold embold - emboldens embolden - emboss emboss - embossed emboss - embounded embound - embowel embowel - embowell embowel - embrac embrac - embrace embrac - embraced embrac - embracement embrac - embracements embrac - embraces embrac - embracing embrac - embrasures embrasur - embroider embroid - embroidery embroideri - emhracing emhrac - emilia emilia - eminence emin - eminent emin - eminently emin - emmanuel emmanuel - emnity emniti - empale empal - emperal emper - emperess emperess - emperial emperi - emperor emperor - empery emperi - emphasis emphasi - empire empir - empirics empir - empiricutic empiricut - empleached empleach - employ emploi - employed emploi - employer employ - employment employ - employments employ - empoison empoison - empress empress - emptied empti - emptier emptier - empties empti - emptiness empti - empty empti - emptying empti - emulate emul - emulation emul - emulations emul - emulator emul - emulous emul - en en - enact enact - enacted enact - enacts enact - enactures enactur - enamell enamel - enamelled enamel - enamour enamour - enamoured enamour - enanmour enanmour - encamp encamp - encamped encamp - encave encav - enceladus enceladu - enchaf enchaf - enchafed enchaf - enchant enchant - enchanted enchant - enchanting enchant - enchantingly enchantingli - enchantment enchant - enchantress enchantress - enchants enchant - enchas encha - encircle encircl - encircled encircl - enclos enclo - enclose enclos - enclosed enclos - encloses enclos - encloseth encloseth - enclosing enclos - enclouded encloud - encompass encompass - encompassed encompass - encompasseth encompasseth - encompassment encompass - encore encor - encorporal encorpor - encount encount - encounter encount - encountered encount - encounters encount - encourage encourag - encouraged encourag - encouragement encourag - encrimsoned encrimson - encroaching encroach - encumb encumb - end end - endamage endamag - endamagement endamag - endanger endang - endart endart - endear endear - endeared endear - endeavour endeavour - endeavours endeavour - ended end - ender ender - ending end - endings end - endite endit - endless endless - endow endow - endowed endow - endowments endow - endows endow - ends end - endu endu - endue endu - endur endur - endurance endur - endure endur - endured endur - endures endur - enduring endur - endymion endymion - eneas enea - enemies enemi - enemy enemi - enernies enerni - enew enew - enfeebled enfeebl - enfeebles enfeebl - enfeoff enfeoff - enfetter enfett - enfoldings enfold - enforc enforc - enforce enforc - enforced enforc - enforcedly enforcedli - enforcement enforc - enforces enforc - enforcest enforcest - enfranched enfranch - enfranchis enfranchi - enfranchise enfranchis - enfranchised enfranchis - enfranchisement enfranchis - enfreed enfre - enfreedoming enfreedom - engag engag - engage engag - engaged engag - engagements engag - engaging engag - engaol engaol - engend engend - engender engend - engenders engend - engilds engild - engine engin - engineer engin - enginer engin - engines engin - engirt engirt - england england - english english - englishman englishman - englishmen englishmen - engluts englut - englutted englut - engraffed engraf - engraft engraft - engrafted engraft - engrav engrav - engrave engrav - engross engross - engrossed engross - engrossest engrossest - engrossing engross - engrossments engross - enguard enguard - enigma enigma - enigmatical enigmat - enjoin enjoin - enjoined enjoin - enjoy enjoi - enjoyed enjoi - enjoyer enjoy - enjoying enjoi - enjoys enjoi - enkindle enkindl - enkindled enkindl - enlard enlard - enlarg enlarg - enlarge enlarg - enlarged enlarg - enlargement enlarg - enlargeth enlargeth - enlighten enlighten - enlink enlink - enmesh enmesh - enmities enmiti - enmity enmiti - ennoble ennobl - ennobled ennobl - enobarb enobarb - enobarbus enobarbu - enon enon - enormity enorm - enormous enorm - enough enough - enow enow - enpatron enpatron - enpierced enpierc - enquir enquir - enquire enquir - enquired enquir - enrag enrag - enrage enrag - enraged enrag - enrages enrag - enrank enrank - enrapt enrapt - enrich enrich - enriched enrich - enriches enrich - enridged enridg - enrings enr - enrob enrob - enrobe enrob - enroll enrol - enrolled enrol - enrooted enroot - enrounded enround - enschedul enschedul - ensconce ensconc - ensconcing ensconc - enseamed enseam - ensear ensear - enseigne enseign - enseignez enseignez - ensemble ensembl - enshelter enshelt - enshielded enshield - enshrines enshrin - ensign ensign - ensigns ensign - enskied enski - ensman ensman - ensnare ensnar - ensnared ensnar - ensnareth ensnareth - ensteep ensteep - ensu ensu - ensue ensu - ensued ensu - ensues ensu - ensuing ensu - enswathed enswath - ent ent - entail entail - entame entam - entangled entangl - entangles entangl - entendre entendr - enter enter - entered enter - entering enter - enterprise enterpris - enterprises enterpris - enters enter - entertain entertain - entertained entertain - entertainer entertain - entertaining entertain - entertainment entertain - entertainments entertain - enthrall enthral - enthralled enthral - enthron enthron - enthroned enthron - entice entic - enticements entic - enticing entic - entire entir - entirely entir - entitle entitl - entitled entitl - entitling entitl - entomb entomb - entombed entomb - entrails entrail - entrance entranc - entrances entranc - entrap entrap - entrapp entrapp - entre entr - entreat entreat - entreated entreat - entreaties entreati - entreating entreat - entreatments entreat - entreats entreat - entreaty entreati - entrench entrench - entry entri - entwist entwist - envelop envelop - envenom envenom - envenomed envenom - envenoms envenom - envied envi - envies envi - envious enviou - enviously envious - environ environ - environed environ - envoy envoi - envy envi - envying envi - enwheel enwheel - enwombed enwomb - enwraps enwrap - ephesian ephesian - ephesians ephesian - ephesus ephesu - epicure epicur - epicurean epicurean - epicures epicur - epicurism epicur - epicurus epicuru - epidamnum epidamnum - epidaurus epidauru - epigram epigram - epilepsy epilepsi - epileptic epilept - epilogue epilogu - epilogues epilogu - epistles epistl - epistrophus epistrophu - epitaph epitaph - epitaphs epitaph - epithet epithet - epitheton epitheton - epithets epithet - epitome epitom - equal equal - equalities equal - equality equal - equall equal - equally equal - equalness equal - equals equal - equinoctial equinocti - equinox equinox - equipage equipag - equity equiti - equivocal equivoc - equivocate equivoc - equivocates equivoc - equivocation equivoc - equivocator equivoc - er er - erbear erbear - erbearing erbear - erbears erbear - erbeat erbeat - erblows erblow - erboard erboard - erborne erborn - ercame ercam - ercast ercast - ercharg ercharg - ercharged ercharg - ercharging ercharg - ercles ercl - ercome ercom - ercover ercov - ercrows ercrow - erdoing erdo - ere er - erebus erebu - erect erect - erected erect - erecting erect - erection erect - erects erect - erewhile erewhil - erflourish erflourish - erflow erflow - erflowing erflow - erflows erflow - erfraught erfraught - erga erga - ergalled ergal - erglanced erglanc - ergo ergo - ergone ergon - ergrow ergrow - ergrown ergrown - ergrowth ergrowth - erhang erhang - erhanging erhang - erhasty erhasti - erhear erhear - erheard erheard - eringoes eringo - erjoy erjoi - erleap erleap - erleaps erleap - erleavens erleaven - erlook erlook - erlooking erlook - ermaster ermast - ermengare ermengar - ermount ermount - ern ern - ernight ernight - eros ero - erpaid erpaid - erparted erpart - erpast erpast - erpays erpai - erpeer erpeer - erperch erperch - erpicturing erpictur - erpingham erpingham - erposting erpost - erpow erpow - erpress erpress - erpressed erpress - err err - errand errand - errands errand - errant errant - errate errat - erraught erraught - erreaches erreach - erred er - errest errest - erring er - erroneous erron - error error - errors error - errs err - errule errul - errun errun - erset erset - ershade ershad - ershades ershad - ershine ershin - ershot ershot - ersized ersiz - erskip erskip - erslips erslip - erspreads erspread - erst erst - erstare erstar - erstep erstep - erstunk erstunk - ersway erswai - ersways erswai - erswell erswel - erta erta - ertake ertak - erteemed erteem - erthrow erthrow - erthrown erthrown - erthrows erthrow - ertook ertook - ertop ertop - ertopping ertop - ertrip ertrip - erturn erturn - erudition erudit - eruption erupt - eruptions erupt - ervalues ervalu - erwalk erwalk - erwatch erwatch - erween erween - erweens erween - erweigh erweigh - erweighs erweigh - erwhelm erwhelm - erwhelmed erwhelm - erworn erworn - es es - escalus escalu - escap escap - escape escap - escaped escap - escapes escap - eschew eschew - escoted escot - esill esil - especial especi - especially especi - esperance esper - espials espial - espied espi - espies espi - espous espou - espouse espous - espy espi - esquire esquir - esquires esquir - essay essai - essays essai - essence essenc - essential essenti - essentially essenti - esses ess - essex essex - est est - establish establish - established establish - estate estat - estates estat - esteem esteem - esteemed esteem - esteemeth esteemeth - esteeming esteem - esteems esteem - estimable estim - estimate estim - estimation estim - estimations estim - estime estim - estranged estrang - estridge estridg - estridges estridg - et et - etc etc - etceteras etcetera - ete et - eternal etern - eternally etern - eterne etern - eternity etern - eterniz eterniz - etes et - ethiop ethiop - ethiope ethiop - ethiopes ethiop - ethiopian ethiopian - etna etna - eton eton - etre etr - eunuch eunuch - eunuchs eunuch - euphrates euphrat - euphronius euphroniu - euriphile euriphil - europa europa - europe europ - ev ev - evade evad - evades evad - evans evan - evasion evas - evasions evas - eve ev - even even - evening even - evenly evenli - event event - eventful event - events event - ever ever - everlasting everlast - everlastingly everlastingli - evermore evermor - every everi - everyone everyon - everything everyth - everywhere everywher - evidence evid - evidences evid - evident evid - evil evil - evilly evilli - evils evil - evitate evit - ewe ew - ewer ewer - ewers ewer - ewes ew - exact exact - exacted exact - exactest exactest - exacting exact - exaction exact - exactions exact - exactly exactli - exacts exact - exalt exalt - exalted exalt - examin examin - examination examin - examinations examin - examine examin - examined examin - examines examin - exampl exampl - example exampl - exampled exampl - examples exampl - exasperate exasper - exasperates exasper - exceed exce - exceeded exceed - exceedeth exceedeth - exceeding exceed - exceedingly exceedingli - exceeds exce - excel excel - excelled excel - excellence excel - excellencies excel - excellency excel - excellent excel - excellently excel - excelling excel - excels excel - except except - excepted except - excepting except - exception except - exceptions except - exceptless exceptless - excess excess - excessive excess - exchang exchang - exchange exchang - exchanged exchang - exchequer exchequ - exchequers exchequ - excite excit - excited excit - excitements excit - excites excit - exclaim exclaim - exclaims exclaim - exclamation exclam - exclamations exclam - excludes exclud - excommunicate excommun - excommunication excommun - excrement excrement - excrements excrement - excursion excurs - excursions excurs - excus excu - excusable excus - excuse excus - excused excus - excuses excus - excusez excusez - excusing excus - execrable execr - execrations execr - execute execut - executed execut - executing execut - execution execut - executioner execution - executioners execution - executor executor - executors executor - exempt exempt - exempted exempt - exequies exequi - exercise exercis - exercises exercis - exeter exet - exeunt exeunt - exhal exhal - exhalation exhal - exhalations exhal - exhale exhal - exhales exhal - exhaust exhaust - exhibit exhibit - exhibiters exhibit - exhibition exhibit - exhort exhort - exhortation exhort - exigent exig - exil exil - exile exil - exiled exil - exion exion - exist exist - exists exist - exit exit - exits exit - exorciser exorcis - exorcisms exorc - exorcist exorcist - expect expect - expectance expect - expectancy expect - expectation expect - expectations expect - expected expect - expecters expect - expecting expect - expects expect - expedience expedi - expedient expedi - expediently expedi - expedition expedit - expeditious expediti - expel expel - expell expel - expelling expel - expels expel - expend expend - expense expens - expenses expens - experienc experienc - experience experi - experiences experi - experiment experi - experimental experiment - experiments experi - expert expert - expertness expert - expiate expiat - expiation expiat - expir expir - expiration expir - expire expir - expired expir - expires expir - expiring expir - explication explic - exploit exploit - exploits exploit - expos expo - expose expos - exposing expos - exposition exposit - expositor expositor - expostulate expostul - expostulation expostul - exposture expostur - exposure exposur - expound expound - expounded expound - express express - expressed express - expresseth expresseth - expressing express - expressive express - expressly expressli - expressure expressur - expuls expul - expulsion expuls - exquisite exquisit - exsufflicate exsuffl - extant extant - extemporal extempor - extemporally extempor - extempore extempor - extend extend - extended extend - extends extend - extent extent - extenuate extenu - extenuated extenu - extenuates extenu - extenuation extenu - exterior exterior - exteriorly exteriorli - exteriors exterior - extermin extermin - extern extern - external extern - extinct extinct - extincted extinct - extincture extinctur - extinguish extinguish - extirp extirp - extirpate extirp - extirped extirp - extol extol - extoll extol - extolment extol - exton exton - extort extort - extorted extort - extortion extort - extortions extort - extra extra - extract extract - extracted extract - extracting extract - extraordinarily extraordinarili - extraordinary extraordinari - extraught extraught - extravagancy extravag - extravagant extravag - extreme extrem - extremely extrem - extremes extrem - extremest extremest - extremities extrem - extremity extrem - exuent exuent - exult exult - exultation exult - ey ey - eyas eya - eyases eyas - eye ey - eyeball eyebal - eyeballs eyebal - eyebrow eyebrow - eyebrows eyebrow - eyed ei - eyeless eyeless - eyelid eyelid - eyelids eyelid - eyes ey - eyesight eyesight - eyestrings eyestr - eying ei - eyne eyn - eyrie eyri - fa fa - fabian fabian - fable fabl - fables fabl - fabric fabric - fabulous fabul - fac fac - face face - faced face - facere facer - faces face - faciant faciant - facile facil - facility facil - facinerious facineri - facing face - facit facit - fact fact - faction faction - factionary factionari - factions faction - factious factiou - factor factor - factors factor - faculties faculti - faculty faculti - fade fade - faded fade - fadeth fadeth - fadge fadg - fading fade - fadings fade - fadom fadom - fadoms fadom - fagot fagot - fagots fagot - fail fail - failing fail - fails fail - fain fain - faint faint - fainted faint - fainter fainter - fainting faint - faintly faintli - faintness faint - faints faint - fair fair - fairer fairer - fairest fairest - fairies fairi - fairing fair - fairings fair - fairly fairli - fairness fair - fairs fair - fairwell fairwel - fairy fairi - fais fai - fait fait - faites fait - faith faith - faithful faith - faithfull faithful - faithfully faithfulli - faithless faithless - faiths faith - faitors faitor - fal fal - falchion falchion - falcon falcon - falconbridge falconbridg - falconer falcon - falconers falcon - fall fall - fallacy fallaci - fallen fallen - falleth falleth - falliable falliabl - fallible fallibl - falling fall - fallow fallow - fallows fallow - falls fall - fally falli - falorous falor - false fals - falsehood falsehood - falsely fals - falseness fals - falser falser - falsify falsifi - falsing fals - falstaff falstaff - falstaffs falstaff - falter falter - fam fam - fame fame - famed fame - familiar familiar - familiarity familiar - familiarly familiarli - familiars familiar - family famili - famine famin - famish famish - famished famish - famous famou - famoused famous - famously famous - fan fan - fanatical fanat - fancies fanci - fancy fanci - fane fane - fanes fane - fang fang - fangled fangl - fangless fangless - fangs fang - fann fann - fanning fan - fans fan - fantasied fantasi - fantasies fantasi - fantastic fantast - fantastical fantast - fantastically fantast - fantasticoes fantastico - fantasy fantasi - fap fap - far far - farborough farborough - farced farc - fardel fardel - fardels fardel - fare fare - fares fare - farewell farewel - farewells farewel - fariner farin - faring fare - farm farm - farmer farmer - farmhouse farmhous - farms farm - farre farr - farrow farrow - farther farther - farthest farthest - farthing farth - farthingale farthingal - farthingales farthingal - farthings farth - fartuous fartuou - fas fa - fashion fashion - fashionable fashion - fashioning fashion - fashions fashion - fast fast - fasted fast - fasten fasten - fastened fasten - faster faster - fastest fastest - fasting fast - fastly fastli - fastolfe fastolf - fasts fast - fat fat - fatal fatal - fatally fatal - fate fate - fated fate - fates fate - father father - fathered father - fatherless fatherless - fatherly fatherli - fathers father - fathom fathom - fathomless fathomless - fathoms fathom - fatigate fatig - fatness fat - fats fat - fatted fat - fatter fatter - fattest fattest - fatting fat - fatuus fatuu - fauconbridge fauconbridg - faulconbridge faulconbridg - fault fault - faultiness faulti - faultless faultless - faults fault - faulty faulti - fausse fauss - fauste faust - faustuses faustus - faut faut - favor favor - favorable favor - favorably favor - favors favor - favour favour - favourable favour - favoured favour - favouredly favouredli - favourer favour - favourers favour - favouring favour - favourite favourit - favourites favourit - favours favour - favout favout - fawn fawn - fawneth fawneth - fawning fawn - fawns fawn - fay fai - fe fe - fealty fealti - fear fear - feared fear - fearest fearest - fearful fear - fearfull fearful - fearfully fearfulli - fearfulness fear - fearing fear - fearless fearless - fears fear - feast feast - feasted feast - feasting feast - feasts feast - feat feat - feated feat - feater feater - feather feather - feathered feather - feathers feather - featly featli - feats feat - featur featur - feature featur - featured featur - featureless featureless - features featur - february februari - fecks feck - fed fed - fedary fedari - federary federari - fee fee - feeble feebl - feebled feebl - feebleness feebl - feebling feebl - feebly feebli - feed feed - feeder feeder - feeders feeder - feedeth feedeth - feeding feed - feeds feed - feel feel - feeler feeler - feeling feel - feelingly feelingli - feels feel - fees fee - feet feet - fehemently fehement - feign feign - feigned feign - feigning feign - feil feil - feith feith - felicitate felicit - felicity felic - fell fell - fellest fellest - fellies felli - fellow fellow - fellowly fellowli - fellows fellow - fellowship fellowship - fellowships fellowship - fells fell - felon felon - felonious feloni - felony feloni - felt felt - female femal - females femal - feminine feminin - fen fen - fenc fenc - fence fenc - fencer fencer - fencing fenc - fends fend - fennel fennel - fenny fenni - fens fen - fenton fenton - fer fer - ferdinand ferdinand - fere fere - fernseed fernse - ferrara ferrara - ferrers ferrer - ferret ferret - ferry ferri - ferryman ferryman - fertile fertil - fertility fertil - fervency fervenc - fervour fervour - fery feri - fest fest - feste fest - fester fester - festinate festin - festinately festin - festival festiv - festivals festiv - fet fet - fetch fetch - fetches fetch - fetching fetch - fetlock fetlock - fetlocks fetlock - fett fett - fetter fetter - fettering fetter - fetters fetter - fettle fettl - feu feu - feud feud - fever fever - feverous fever - fevers fever - few few - fewer fewer - fewest fewest - fewness few - fickle fickl - fickleness fickl - fico fico - fiction fiction - fiddle fiddl - fiddler fiddler - fiddlestick fiddlestick - fidele fidel - fidelicet fidelicet - fidelity fidel - fidius fidiu - fie fie - field field - fielded field - fields field - fiend fiend - fiends fiend - fierce fierc - fiercely fierc - fierceness fierc - fiery fieri - fife fife - fifes fife - fifteen fifteen - fifteens fifteen - fifteenth fifteenth - fifth fifth - fifty fifti - fiftyfold fiftyfold - fig fig - fight fight - fighter fighter - fightest fightest - fighteth fighteth - fighting fight - fights fight - figo figo - figs fig - figur figur - figure figur - figured figur - figures figur - figuring figur - fike fike - fil fil - filberts filbert - filch filch - filches filch - filching filch - file file - filed file - files file - filial filial - filius filiu - fill fill - filled fill - fillet fillet - filling fill - fillip fillip - fills fill - filly filli - film film - fils fil - filth filth - filths filth - filthy filthi - fin fin - finally final - finch finch - find find - finder finder - findeth findeth - finding find - findings find - finds find - fine fine - fineless fineless - finely fine - finem finem - fineness fine - finer finer - fines fine - finest finest - fing fing - finger finger - fingering finger - fingers finger - fingre fingr - fingres fingr - finical finic - finish finish - finished finish - finisher finish - finless finless - finn finn - fins fin - finsbury finsburi - fir fir - firago firago - fire fire - firebrand firebrand - firebrands firebrand - fired fire - fires fire - firework firework - fireworks firework - firing fire - firk firk - firm firm - firmament firmament - firmly firmli - firmness firm - first first - firstlings firstl - fish fish - fisher fisher - fishermen fishermen - fishers fisher - fishes fish - fishified fishifi - fishmonger fishmong - fishpond fishpond - fisnomy fisnomi - fist fist - fisting fist - fists fist - fistula fistula - fit fit - fitchew fitchew - fitful fit - fitly fitli - fitment fitment - fitness fit - fits fit - fitted fit - fitter fitter - fittest fittest - fitteth fitteth - fitting fit - fitzwater fitzwat - five five - fivepence fivep - fives five - fix fix - fixed fix - fixes fix - fixeth fixeth - fixing fix - fixture fixtur - fl fl - flag flag - flagging flag - flagon flagon - flagons flagon - flags flag - flail flail - flakes flake - flaky flaki - flam flam - flame flame - flamen flamen - flamens flamen - flames flame - flaming flame - flaminius flaminiu - flanders flander - flannel flannel - flap flap - flaring flare - flash flash - flashes flash - flashing flash - flask flask - flat flat - flatly flatli - flatness flat - flats flat - flatt flatt - flatter flatter - flattered flatter - flatterer flatter - flatterers flatter - flatterest flatterest - flatteries flatteri - flattering flatter - flatters flatter - flattery flatteri - flaunts flaunt - flavio flavio - flavius flaviu - flaw flaw - flaws flaw - flax flax - flaxen flaxen - flay flai - flaying flai - flea flea - fleance fleanc - fleas flea - flecked fleck - fled fled - fledge fledg - flee flee - fleec fleec - fleece fleec - fleeces fleec - fleer fleer - fleering fleer - fleers fleer - fleet fleet - fleeter fleeter - fleeting fleet - fleming fleme - flemish flemish - flesh flesh - fleshes flesh - fleshly fleshli - fleshment fleshment - fleshmonger fleshmong - flew flew - flexible flexibl - flexure flexur - flibbertigibbet flibbertigibbet - flickering flicker - flidge flidg - fliers flier - flies fli - flieth flieth - flight flight - flights flight - flighty flighti - flinch flinch - fling fling - flint flint - flints flint - flinty flinti - flirt flirt - float float - floated float - floating float - flock flock - flocks flock - flood flood - floodgates floodgat - floods flood - floor floor - flora flora - florence florenc - florentine florentin - florentines florentin - florentius florentiu - florizel florizel - flote flote - floulish floulish - flour flour - flourish flourish - flourishes flourish - flourisheth flourisheth - flourishing flourish - flout flout - flouted flout - flouting flout - flouts flout - flow flow - flowed flow - flower flower - flowerets floweret - flowers flower - flowing flow - flown flown - flows flow - fluellen fluellen - fluent fluent - flung flung - flush flush - flushing flush - fluster fluster - flute flute - flutes flute - flutter flutter - flux flux - fluxive fluxiv - fly fly - flying fly - fo fo - foal foal - foals foal - foam foam - foamed foam - foaming foam - foams foam - foamy foami - fob fob - focative foc - fodder fodder - foe foe - foeman foeman - foemen foemen - foes foe - fog fog - foggy foggi - fogs fog - foh foh - foi foi - foil foil - foiled foil - foils foil - foin foin - foining foin - foins foin - fois foi - foison foison - foisons foison - foist foist - foix foix - fold fold - folded fold - folds fold - folio folio - folk folk - folks folk - follies folli - follow follow - followed follow - follower follow - followers follow - followest followest - following follow - follows follow - folly folli - fond fond - fonder fonder - fondly fondli - fondness fond - font font - fontibell fontibel - food food - fool fool - fooleries fooleri - foolery fooleri - foolhardy foolhardi - fooling fool - foolish foolish - foolishly foolishli - foolishness foolish - fools fool - foot foot - football footbal - footboy footboi - footboys footboi - footed foot - footfall footfal - footing foot - footman footman - footmen footmen - footpath footpath - footsteps footstep - footstool footstool - fopp fopp - fopped fop - foppery fopperi - foppish foppish - fops fop - for for - forage forag - foragers forag - forbade forbad - forbear forbear - forbearance forbear - forbears forbear - forbid forbid - forbidden forbidden - forbiddenly forbiddenli - forbids forbid - forbod forbod - forborne forborn - forc forc - force forc - forced forc - forceful forc - forceless forceless - forces forc - forcible forcibl - forcibly forcibl - forcing forc - ford ford - fordid fordid - fordo fordo - fordoes fordo - fordone fordon - fore fore - forecast forecast - forefather forefath - forefathers forefath - forefinger forefing - forego forego - foregone foregon - forehand forehand - forehead forehead - foreheads forehead - forehorse forehors - foreign foreign - foreigner foreign - foreigners foreign - foreknowing foreknow - foreknowledge foreknowledg - foremost foremost - forenamed forenam - forenoon forenoon - forerun forerun - forerunner forerunn - forerunning forerun - foreruns forerun - foresaid foresaid - foresaw foresaw - foresay foresai - foresee forese - foreseeing forese - foresees forese - foreshow foreshow - foreskirt foreskirt - forespent foresp - forest forest - forestall forestal - forestalled forestal - forester forest - foresters forest - forests forest - foretell foretel - foretelling foretel - foretells foretel - forethink forethink - forethought forethought - foretold foretold - forever forev - foreward foreward - forewarn forewarn - forewarned forewarn - forewarning forewarn - forfeit forfeit - forfeited forfeit - forfeiters forfeit - forfeiting forfeit - forfeits forfeit - forfeiture forfeitur - forfeitures forfeitur - forfend forfend - forfended forfend - forg forg - forgave forgav - forge forg - forged forg - forgeries forgeri - forgery forgeri - forges forg - forget forget - forgetful forget - forgetfulness forget - forgetive forget - forgets forget - forgetting forget - forgive forgiv - forgiven forgiven - forgiveness forgiv - forgo forgo - forgoing forgo - forgone forgon - forgot forgot - forgotten forgotten - fork fork - forked fork - forks fork - forlorn forlorn - form form - formal formal - formally formal - formed form - former former - formerly formerli - formless formless - forms form - fornication fornic - fornications fornic - fornicatress fornicatress - forres forr - forrest forrest - forsake forsak - forsaken forsaken - forsaketh forsaketh - forslow forslow - forsook forsook - forsooth forsooth - forspent forspent - forspoke forspok - forswear forswear - forswearing forswear - forswore forswor - forsworn forsworn - fort fort - forted fort - forth forth - forthcoming forthcom - forthlight forthlight - forthright forthright - forthwith forthwith - fortification fortif - fortifications fortif - fortified fortifi - fortifies fortifi - fortify fortifi - fortinbras fortinbra - fortitude fortitud - fortnight fortnight - fortress fortress - fortresses fortress - forts fort - fortun fortun - fortuna fortuna - fortunate fortun - fortunately fortun - fortune fortun - fortuned fortun - fortunes fortun - fortward fortward - forty forti - forum forum - forward forward - forwarding forward - forwardness forward - forwards forward - forwearied forweari - fosset fosset - fost fost - foster foster - fostered foster - fought fought - foughten foughten - foul foul - fouler fouler - foulest foulest - foully foulli - foulness foul - found found - foundation foundat - foundations foundat - founded found - founder founder - fount fount - fountain fountain - fountains fountain - founts fount - four four - fourscore fourscor - fourteen fourteen - fourth fourth - foutra foutra - fowl fowl - fowler fowler - fowling fowl - fowls fowl - fox fox - foxes fox - foxship foxship - fracted fract - fraction fraction - fractions fraction - fragile fragil - fragment fragment - fragments fragment - fragrant fragrant - frail frail - frailer frailer - frailties frailti - frailty frailti - fram fram - frame frame - framed frame - frames frame - frampold frampold - fran fran - francais francai - france franc - frances franc - franchise franchis - franchised franchis - franchisement franchis - franchises franchis - franciae francia - francis franci - francisca francisca - franciscan franciscan - francisco francisco - frank frank - franker franker - frankfort frankfort - franklin franklin - franklins franklin - frankly frankli - frankness frank - frantic frantic - franticly franticli - frateretto frateretto - fratrum fratrum - fraud fraud - fraudful fraud - fraught fraught - fraughtage fraughtag - fraughting fraught - fray frai - frays frai - freckl freckl - freckled freckl - freckles freckl - frederick frederick - free free - freed freed - freedom freedom - freedoms freedom - freehearted freeheart - freelier freelier - freely freeli - freeman freeman - freemen freemen - freeness freeness - freer freer - frees free - freestone freeston - freetown freetown - freeze freez - freezes freez - freezing freez - freezings freez - french french - frenchman frenchman - frenchmen frenchmen - frenchwoman frenchwoman - frenzy frenzi - frequent frequent - frequents frequent - fresh fresh - fresher fresher - freshes fresh - freshest freshest - freshly freshli - freshness fresh - fret fret - fretful fret - frets fret - fretted fret - fretten fretten - fretting fret - friar friar - friars friar - friday fridai - fridays fridai - friend friend - friended friend - friending friend - friendless friendless - friendliness friendli - friendly friendli - friends friend - friendship friendship - friendships friendship - frieze friez - fright fright - frighted fright - frightened frighten - frightful fright - frighting fright - frights fright - fringe fring - fringed fring - frippery fripperi - frisk frisk - fritters fritter - frivolous frivol - fro fro - frock frock - frog frog - frogmore frogmor - froissart froissart - frolic frolic - from from - front front - fronted front - frontier frontier - frontiers frontier - fronting front - frontlet frontlet - fronts front - frost frost - frosts frost - frosty frosti - froth froth - froward froward - frown frown - frowning frown - frowningly frowningli - frowns frown - froze froze - frozen frozen - fructify fructifi - frugal frugal - fruit fruit - fruiterer fruiter - fruitful fruit - fruitfully fruitfulli - fruitfulness fruit - fruition fruition - fruitless fruitless - fruits fruit - frush frush - frustrate frustrat - frutify frutifi - fry fry - fubb fubb - fuel fuel - fugitive fugit - fulfil fulfil - fulfill fulfil - fulfilling fulfil - fulfils fulfil - full full - fullam fullam - fuller fuller - fullers fuller - fullest fullest - fullness full - fully fulli - fulness ful - fulsome fulsom - fulvia fulvia - fum fum - fumble fumbl - fumbles fumbl - fumblest fumblest - fumbling fumbl - fume fume - fumes fume - fuming fume - fumiter fumit - fumitory fumitori - fun fun - function function - functions function - fundamental fundament - funeral funer - funerals funer - fur fur - furbish furbish - furies furi - furious furiou - furlongs furlong - furnace furnac - furnaces furnac - furnish furnish - furnished furnish - furnishings furnish - furniture furnitur - furnival furniv - furor furor - furr furr - furrow furrow - furrowed furrow - furrows furrow - furth furth - further further - furtherance further - furtherer further - furthermore furthermor - furthest furthest - fury furi - furze furz - furzes furz - fust fust - fustian fustian - fustilarian fustilarian - fusty fusti - fut fut - future futur - futurity futur - g g - gabble gabbl - gaberdine gaberdin - gabriel gabriel - gad gad - gadding gad - gads gad - gadshill gadshil - gag gag - gage gage - gaged gage - gagg gagg - gaging gage - gagne gagn - gain gain - gained gain - gainer gainer - gaingiving gaingiv - gains gain - gainsaid gainsaid - gainsay gainsai - gainsaying gainsai - gainsays gainsai - gainst gainst - gait gait - gaited gait - galathe galath - gale gale - galen galen - gales gale - gall gall - gallant gallant - gallantly gallantli - gallantry gallantri - gallants gallant - galled gall - gallery galleri - galley gallei - galleys gallei - gallia gallia - gallian gallian - galliard galliard - galliasses galliass - gallimaufry gallimaufri - galling gall - gallons gallon - gallop gallop - galloping gallop - gallops gallop - gallow gallow - galloway gallowai - gallowglasses gallowglass - gallows gallow - gallowses gallows - galls gall - gallus gallu - gam gam - gambol gambol - gambold gambold - gambols gambol - gamboys gamboi - game game - gamers gamer - games game - gamesome gamesom - gamester gamest - gaming game - gammon gammon - gamut gamut - gan gan - gangren gangren - ganymede ganymed - gaol gaol - gaoler gaoler - gaolers gaoler - gaols gaol - gap gap - gape gape - gapes gape - gaping gape - gar gar - garb garb - garbage garbag - garboils garboil - garcon garcon - gard gard - garde gard - garden garden - gardener garden - gardeners garden - gardens garden - gardez gardez - gardiner gardin - gardon gardon - gargantua gargantua - gargrave gargrav - garish garish - garland garland - garlands garland - garlic garlic - garment garment - garments garment - garmet garmet - garner garner - garners garner - garnish garnish - garnished garnish - garret garret - garrison garrison - garrisons garrison - gart gart - garter garter - garterd garterd - gartering garter - garters garter - gascony gasconi - gash gash - gashes gash - gaskins gaskin - gasp gasp - gasping gasp - gasted gast - gastness gast - gat gat - gate gate - gated gate - gates gate - gath gath - gather gather - gathered gather - gathering gather - gathers gather - gatories gatori - gatory gatori - gaud gaud - gaudeo gaudeo - gaudy gaudi - gauge gaug - gaul gaul - gaultree gaultre - gaunt gaunt - gauntlet gauntlet - gauntlets gauntlet - gav gav - gave gave - gavest gavest - gawded gawd - gawds gawd - gawsey gawsei - gay gai - gayness gay - gaz gaz - gaze gaze - gazed gaze - gazer gazer - gazers gazer - gazes gaze - gazeth gazeth - gazing gaze - gear gear - geck geck - geese gees - geffrey geffrei - geld geld - gelded geld - gelding geld - gelida gelida - gelidus gelidu - gelt gelt - gem gem - geminy gemini - gems gem - gen gen - gender gender - genders gender - general gener - generally gener - generals gener - generation gener - generations gener - generative gener - generosity generos - generous gener - genitive genit - genitivo genitivo - genius geniu - gennets gennet - genoa genoa - genoux genoux - gens gen - gent gent - gentilhomme gentilhomm - gentility gentil - gentle gentl - gentlefolks gentlefolk - gentleman gentleman - gentlemanlike gentlemanlik - gentlemen gentlemen - gentleness gentl - gentler gentler - gentles gentl - gentlest gentlest - gentlewoman gentlewoman - gentlewomen gentlewomen - gently gentli - gentry gentri - george georg - gerard gerard - germaines germain - germains germain - german german - germane german - germans german - germany germani - gertrude gertrud - gest gest - gests gest - gesture gestur - gestures gestur - get get - getrude getrud - gets get - getter getter - getting get - ghastly ghastli - ghost ghost - ghosted ghost - ghostly ghostli - ghosts ghost - gi gi - giant giant - giantess giantess - giantlike giantlik - giants giant - gib gib - gibber gibber - gibbet gibbet - gibbets gibbet - gibe gibe - giber giber - gibes gibe - gibing gibe - gibingly gibingli - giddily giddili - giddiness giddi - giddy giddi - gift gift - gifts gift - gig gig - giglets giglet - giglot giglot - gilbert gilbert - gild gild - gilded gild - gilding gild - gilliams gilliam - gillian gillian - gills gill - gillyvors gillyvor - gilt gilt - gimmal gimmal - gimmers gimmer - gin gin - ging ging - ginger ginger - gingerbread gingerbread - gingerly gingerli - ginn ginn - gins gin - gioucestershire gioucestershir - gipes gipe - gipsies gipsi - gipsy gipsi - gird gird - girded gird - girdle girdl - girdled girdl - girdles girdl - girdling girdl - girl girl - girls girl - girt girt - girth girth - gis gi - giv giv - give give - given given - giver giver - givers giver - gives give - givest givest - giveth giveth - giving give - givings give - glad glad - gladded glad - gladding glad - gladly gladli - gladness glad - glamis glami - glanc glanc - glance glanc - glanced glanc - glances glanc - glancing glanc - glanders glander - glansdale glansdal - glare glare - glares glare - glass glass - glasses glass - glassy glassi - glaz glaz - glazed glaze - gleams gleam - glean glean - gleaned glean - gleaning glean - gleeful gleeful - gleek gleek - gleeking gleek - gleeks gleek - glend glend - glendower glendow - glib glib - glide glide - glided glide - glides glide - glideth glideth - gliding glide - glimmer glimmer - glimmering glimmer - glimmers glimmer - glimpse glimps - glimpses glimps - glist glist - glistening glisten - glister glister - glistering glister - glisters glister - glitt glitt - glittering glitter - globe globe - globes globe - glooming gloom - gloomy gloomi - glories glori - glorified glorifi - glorify glorifi - glorious gloriou - gloriously glorious - glory glori - glose glose - gloss gloss - glosses gloss - glou glou - glouceste gloucest - gloucester gloucest - gloucestershire gloucestershir - glove glove - glover glover - gloves glove - glow glow - glowed glow - glowing glow - glowworm glowworm - gloz gloz - gloze gloze - glozes gloze - glu glu - glue glue - glued glu - glues glue - glut glut - glutt glutt - glutted glut - glutton glutton - gluttoning glutton - gluttony gluttoni - gnarled gnarl - gnarling gnarl - gnat gnat - gnats gnat - gnaw gnaw - gnawing gnaw - gnawn gnawn - gnaws gnaw - go go - goad goad - goaded goad - goads goad - goal goal - goat goat - goatish goatish - goats goat - gobbets gobbet - gobbo gobbo - goblet goblet - goblets goblet - goblin goblin - goblins goblin - god god - godded god - godden godden - goddess goddess - goddesses goddess - goddild goddild - godfather godfath - godfathers godfath - godhead godhead - godlike godlik - godliness godli - godly godli - godmother godmoth - gods god - godson godson - goer goer - goers goer - goes goe - goest goest - goeth goeth - goffe goff - gogs gog - going go - gold gold - golden golden - goldenly goldenli - goldsmith goldsmith - goldsmiths goldsmith - golgotha golgotha - goliases golias - goliath goliath - gon gon - gondola gondola - gondolier gondoli - gone gone - goneril goneril - gong gong - gonzago gonzago - gonzalo gonzalo - good good - goodfellow goodfellow - goodlier goodlier - goodliest goodliest - goodly goodli - goodman goodman - goodness good - goodnight goodnight - goodrig goodrig - goods good - goodwife goodwif - goodwill goodwil - goodwin goodwin - goodwins goodwin - goodyear goodyear - goodyears goodyear - goose goos - gooseberry gooseberri - goosequills goosequil - goot goot - gor gor - gorbellied gorbelli - gorboduc gorboduc - gordian gordian - gore gore - gored gore - gorg gorg - gorge gorg - gorgeous gorgeou - gorget gorget - gorging gorg - gorgon gorgon - gormandize gormand - gormandizing gormand - gory gori - gosling gosl - gospel gospel - gospels gospel - goss goss - gossamer gossam - gossip gossip - gossiping gossip - gossiplike gossiplik - gossips gossip - got got - goth goth - goths goth - gotten gotten - gourd gourd - gout gout - gouts gout - gouty gouti - govern govern - governance govern - governed govern - governess gover - government govern - governor governor - governors governor - governs govern - gower gower - gown gown - gowns gown - grac grac - grace grace - graced grace - graceful grace - gracefully gracefulli - graceless graceless - graces grace - gracing grace - gracious graciou - graciously gracious - gradation gradat - graff graff - graffing graf - graft graft - grafted graft - grafters grafter - grain grain - grained grain - grains grain - gramercies gramerci - gramercy gramerci - grammar grammar - grand grand - grandam grandam - grandame grandam - grandchild grandchild - grande grand - grandeur grandeur - grandfather grandfath - grandjurors grandjuror - grandmother grandmoth - grandpre grandpr - grandsir grandsir - grandsire grandsir - grandsires grandsir - grange grang - grant grant - granted grant - granting grant - grants grant - grape grape - grapes grape - grapple grappl - grapples grappl - grappling grappl - grasp grasp - grasped grasp - grasps grasp - grass grass - grasshoppers grasshopp - grassy grassi - grate grate - grated grate - grateful grate - grates grate - gratiano gratiano - gratify gratifi - gratii gratii - gratillity gratil - grating grate - gratis grati - gratitude gratitud - gratulate gratul - grav grav - grave grave - gravediggers gravedigg - gravel gravel - graveless graveless - gravell gravel - gravely grave - graven graven - graveness grave - graver graver - graves grave - gravest gravest - gravestone graveston - gravities graviti - gravity graviti - gravy gravi - gray grai - graymalkin graymalkin - graz graz - graze graze - grazed graze - grazing graze - grease greas - greases greas - greasily greasili - greasy greasi - great great - greater greater - greatest greatest - greatly greatli - greatness great - grecian grecian - grecians grecian - gree gree - greece greec - greed greed - greedily greedili - greediness greedi - greedy greedi - greeing gree - greek greek - greekish greekish - greeks greek - green green - greener greener - greenly greenli - greens green - greensleeves greensleev - greenwich greenwich - greenwood greenwood - greet greet - greeted greet - greeting greet - greetings greet - greets greet - greg greg - gregory gregori - gremio gremio - grew grew - grey grei - greybeard greybeard - greybeards greybeard - greyhound greyhound - greyhounds greyhound - grief grief - griefs grief - griev griev - grievance grievanc - grievances grievanc - grieve griev - grieved griev - grieves griev - grievest grievest - grieving griev - grievingly grievingli - grievous grievou - grievously grievous - griffin griffin - griffith griffith - grim grim - grime grime - grimly grimli - grin grin - grind grind - grinding grind - grindstone grindston - grinning grin - grip grip - gripe gripe - gripes gripe - griping gripe - grise grise - grisly grisli - grissel grissel - grize grize - grizzle grizzl - grizzled grizzl - groan groan - groaning groan - groans groan - groat groat - groats groat - groin groin - groom groom - grooms groom - grop grop - groping grope - gros gro - gross gross - grosser grosser - grossly grossli - grossness gross - ground ground - grounded ground - groundlings groundl - grounds ground - grove grove - grovel grovel - grovelling grovel - groves grove - grow grow - groweth groweth - growing grow - grown grown - grows grow - growth growth - grub grub - grubb grubb - grubs grub - grudge grudg - grudged grudg - grudges grudg - grudging grudg - gruel gruel - grumble grumbl - grumblest grumblest - grumbling grumbl - grumblings grumbl - grumio grumio - grund grund - grunt grunt - gualtier gualtier - guard guard - guardage guardag - guardant guardant - guarded guard - guardian guardian - guardians guardian - guards guard - guardsman guardsman - gud gud - gudgeon gudgeon - guerdon guerdon - guerra guerra - guess guess - guesses guess - guessingly guessingli - guest guest - guests guest - guiana guiana - guichard guichard - guide guid - guided guid - guider guider - guiderius guideriu - guides guid - guiding guid - guidon guidon - guienne guienn - guil guil - guildenstern guildenstern - guilders guilder - guildford guildford - guildhall guildhal - guile guil - guiled guil - guileful guil - guilfords guilford - guilt guilt - guiltian guiltian - guiltier guiltier - guiltily guiltili - guiltiness guilti - guiltless guiltless - guilts guilt - guilty guilti - guinea guinea - guinever guinev - guise guis - gul gul - gules gule - gulf gulf - gulfs gulf - gull gull - gulls gull - gum gum - gumm gumm - gums gum - gun gun - gunner gunner - gunpowder gunpowd - guns gun - gurnet gurnet - gurney gurnei - gust gust - gusts gust - gusty gusti - guts gut - gutter gutter - guy gui - guynes guyn - guysors guysor - gypsy gypsi - gyve gyve - gyved gyve - gyves gyve - h h - ha ha - haberdasher haberdash - habiliment habili - habiliments habili - habit habit - habitation habit - habited habit - habits habit - habitude habitud - hack hack - hacket hacket - hackney hacknei - hacks hack - had had - hadst hadst - haec haec - haeres haer - hag hag - hagar hagar - haggard haggard - haggards haggard - haggish haggish - haggled haggl - hags hag - hail hail - hailed hail - hailstone hailston - hailstones hailston - hair hair - hairless hairless - hairs hair - hairy hairi - hal hal - halberd halberd - halberds halberd - halcyon halcyon - hale hale - haled hale - hales hale - half half - halfcan halfcan - halfpence halfpenc - halfpenny halfpenni - halfpennyworth halfpennyworth - halfway halfwai - halidom halidom - hall hall - halloa halloa - halloing hallo - hallond hallond - halloo halloo - hallooing halloo - hallow hallow - hallowed hallow - hallowmas hallowma - hallown hallown - hals hal - halt halt - halter halter - halters halter - halting halt - halts halt - halves halv - ham ham - hames hame - hamlet hamlet - hammer hammer - hammered hammer - hammering hammer - hammers hammer - hamper hamper - hampton hampton - hams ham - hamstring hamstr - hand hand - handed hand - handful hand - handicraft handicraft - handicraftsmen handicraftsmen - handing hand - handiwork handiwork - handkercher handkerch - handkerchers handkerch - handkerchief handkerchief - handle handl - handled handl - handles handl - handless handless - handlest handlest - handling handl - handmaid handmaid - handmaids handmaid - hands hand - handsaw handsaw - handsome handsom - handsomely handsom - handsomeness handsom - handwriting handwrit - handy handi - hang hang - hanged hang - hangers hanger - hangeth hangeth - hanging hang - hangings hang - hangman hangman - hangmen hangmen - hangs hang - hannibal hannib - hap hap - hapless hapless - haply hapli - happ happ - happen happen - happened happen - happier happier - happies happi - happiest happiest - happily happili - happiness happi - happy happi - haps hap - harbinger harbing - harbingers harbing - harbor harbor - harbour harbour - harbourage harbourag - harbouring harbour - harbours harbour - harcourt harcourt - hard hard - harder harder - hardest hardest - hardiest hardiest - hardiment hardiment - hardiness hardi - hardly hardli - hardness hard - hardocks hardock - hardy hardi - hare hare - harelip harelip - hares hare - harfleur harfleur - hark hark - harlot harlot - harlotry harlotri - harlots harlot - harm harm - harmed harm - harmful harm - harming harm - harmless harmless - harmonious harmoni - harmony harmoni - harms harm - harness har - harp harp - harper harper - harpier harpier - harping harp - harpy harpi - harried harri - harrow harrow - harrows harrow - harry harri - harsh harsh - harshly harshli - harshness harsh - hart hart - harts hart - harum harum - harvest harvest - has ha - hast hast - haste hast - hasted hast - hasten hasten - hastes hast - hastily hastili - hasting hast - hastings hast - hasty hasti - hat hat - hatch hatch - hatches hatch - hatchet hatchet - hatching hatch - hatchment hatchment - hate hate - hated hate - hateful hate - hater hater - haters hater - hates hate - hateth hateth - hatfield hatfield - hath hath - hating hate - hatred hatr - hats hat - haud haud - hauf hauf - haught haught - haughtiness haughti - haughty haughti - haunch haunch - haunches haunch - haunt haunt - haunted haunt - haunting haunt - haunts haunt - hautboy hautboi - hautboys hautboi - have have - haven haven - havens haven - haver haver - having have - havings have - havior havior - haviour haviour - havoc havoc - hawk hawk - hawking hawk - hawks hawk - hawthorn hawthorn - hawthorns hawthorn - hay hai - hazard hazard - hazarded hazard - hazards hazard - hazel hazel - hazelnut hazelnut - he he - head head - headborough headborough - headed head - headier headier - heading head - headland headland - headless headless - headlong headlong - heads head - headsman headsman - headstrong headstrong - heady headi - heal heal - healed heal - healing heal - heals heal - health health - healthful health - healths health - healthsome healthsom - healthy healthi - heap heap - heaping heap - heaps heap - hear hear - heard heard - hearer hearer - hearers hearer - hearest hearest - heareth heareth - hearing hear - hearings hear - heark heark - hearken hearken - hearkens hearken - hears hear - hearsay hearsai - hearse hears - hearsed hears - hearst hearst - heart heart - heartache heartach - heartbreak heartbreak - heartbreaking heartbreak - hearted heart - hearten hearten - hearth hearth - hearths hearth - heartily heartili - heartiness hearti - heartless heartless - heartlings heartl - heartly heartli - hearts heart - heartsick heartsick - heartstrings heartstr - hearty hearti - heat heat - heated heat - heath heath - heathen heathen - heathenish heathenish - heating heat - heats heat - heauties heauti - heav heav - heave heav - heaved heav - heaven heaven - heavenly heavenli - heavens heaven - heaves heav - heavier heavier - heaviest heaviest - heavily heavili - heaviness heavi - heaving heav - heavings heav - heavy heavi - hebona hebona - hebrew hebrew - hecate hecat - hectic hectic - hector hector - hectors hector - hecuba hecuba - hedg hedg - hedge hedg - hedgehog hedgehog - hedgehogs hedgehog - hedges hedg - heed heed - heeded heed - heedful heed - heedfull heedful - heedfully heedfulli - heedless heedless - heel heel - heels heel - hefted heft - hefts heft - heifer heifer - heifers heifer - heigh heigh - height height - heighten heighten - heinous heinou - heinously heinous - heir heir - heiress heiress - heirless heirless - heirs heir - held held - helen helen - helena helena - helenus helenu - helias helia - helicons helicon - hell hell - hellespont hellespont - hellfire hellfir - hellish hellish - helm helm - helmed helm - helmet helmet - helmets helmet - helms helm - help help - helper helper - helpers helper - helpful help - helping help - helpless helpless - helps help - helter helter - hem hem - heme heme - hemlock hemlock - hemm hemm - hemp hemp - hempen hempen - hems hem - hen hen - hence henc - henceforth henceforth - henceforward henceforward - henchman henchman - henri henri - henricus henricu - henry henri - hens hen - hent hent - henton henton - her her - herald herald - heraldry heraldri - heralds herald - herb herb - herbert herbert - herblets herblet - herbs herb - herculean herculean - hercules hercul - herd herd - herds herd - herdsman herdsman - herdsmen herdsmen - here here - hereabout hereabout - hereabouts hereabout - hereafter hereaft - hereby herebi - hereditary hereditari - hereford hereford - herefordshire herefordshir - herein herein - hereof hereof - heresies heresi - heresy heresi - heretic heret - heretics heret - hereto hereto - hereupon hereupon - heritage heritag - heritier heriti - hermes herm - hermia hermia - hermione hermion - hermit hermit - hermitage hermitag - hermits hermit - herne hern - hero hero - herod herod - herods herod - heroes hero - heroic heroic - heroical heroic - herring her - herrings her - hers her - herself herself - hesperides hesperid - hesperus hesperu - hest hest - hests hest - heure heur - heureux heureux - hew hew - hewgh hewgh - hewing hew - hewn hewn - hews hew - hey hei - heyday heydai - hibocrates hibocr - hic hic - hiccups hiccup - hick hick - hid hid - hidden hidden - hide hide - hideous hideou - hideously hideous - hideousness hideous - hides hide - hidest hidest - hiding hide - hie hie - hied hi - hiems hiem - hies hi - hig hig - high high - higher higher - highest highest - highly highli - highmost highmost - highness high - hight hight - highway highwai - highways highwai - hilding hild - hildings hild - hill hill - hillo hillo - hilloa hilloa - hills hill - hilt hilt - hilts hilt - hily hili - him him - himself himself - hinc hinc - hinckley hincklei - hind hind - hinder hinder - hindered hinder - hinders hinder - hindmost hindmost - hinds hind - hing hing - hinge hing - hinges hing - hint hint - hip hip - hipp hipp - hipparchus hipparchu - hippolyta hippolyta - hips hip - hir hir - hire hire - hired hire - hiren hiren - hirtius hirtiu - his hi - hisperia hisperia - hiss hiss - hisses hiss - hissing hiss - hist hist - historical histor - history histori - hit hit - hither hither - hitherto hitherto - hitherward hitherward - hitherwards hitherward - hits hit - hitting hit - hive hive - hives hive - hizzing hizz - ho ho - hoa hoa - hoar hoar - hoard hoard - hoarded hoard - hoarding hoard - hoars hoar - hoarse hoars - hoary hoari - hob hob - hobbididence hobbidid - hobby hobbi - hobbyhorse hobbyhors - hobgoblin hobgoblin - hobnails hobnail - hoc hoc - hod hod - hodge hodg - hog hog - hogs hog - hogshead hogshead - hogsheads hogshead - hois hoi - hoise hois - hoist hoist - hoisted hoist - hoists hoist - holborn holborn - hold hold - holden holden - holder holder - holdeth holdeth - holdfast holdfast - holding hold - holds hold - hole hole - holes hole - holidam holidam - holidame holidam - holiday holidai - holidays holidai - holier holier - holiest holiest - holily holili - holiness holi - holla holla - holland holland - hollander holland - hollanders holland - holloa holloa - holloaing holloa - hollow hollow - hollowly hollowli - hollowness hollow - holly holli - holmedon holmedon - holofernes holofern - holp holp - holy holi - homage homag - homager homag - home home - homely home - homes home - homespuns homespun - homeward homeward - homewards homeward - homicide homicid - homicides homicid - homily homili - hominem hominem - hommes homm - homo homo - honest honest - honester honest - honestest honestest - honestly honestli - honesty honesti - honey honei - honeycomb honeycomb - honeying honei - honeyless honeyless - honeysuckle honeysuckl - honeysuckles honeysuckl - honi honi - honneur honneur - honor honor - honorable honor - honorably honor - honorato honorato - honors honor - honour honour - honourable honour - honourably honour - honoured honour - honourest honourest - honourible honour - honouring honour - honours honour - hoo hoo - hood hood - hooded hood - hoodman hoodman - hoods hood - hoodwink hoodwink - hoof hoof - hoofs hoof - hook hook - hooking hook - hooks hook - hoop hoop - hoops hoop - hoot hoot - hooted hoot - hooting hoot - hoots hoot - hop hop - hope hope - hopeful hope - hopeless hopeless - hopes hope - hopest hopest - hoping hope - hopkins hopkin - hoppedance hopped - hor hor - horace horac - horatio horatio - horizon horizon - horn horn - hornbook hornbook - horned horn - horner horner - horning horn - hornpipes hornpip - horns horn - horologe horolog - horrible horribl - horribly horribl - horrid horrid - horrider horrid - horridly horridli - horror horror - horrors horror - hors hor - horse hors - horseback horseback - horsed hors - horsehairs horsehair - horseman horseman - horsemanship horsemanship - horsemen horsemen - horses hors - horseway horsewai - horsing hors - hortensio hortensio - hortensius hortensiu - horum horum - hose hose - hospitable hospit - hospital hospit - hospitality hospit - host host - hostage hostag - hostages hostag - hostess hostess - hostile hostil - hostility hostil - hostilius hostiliu - hosts host - hot hot - hotly hotli - hotspur hotspur - hotter hotter - hottest hottest - hound hound - hounds hound - hour hour - hourly hourli - hours hour - hous hou - house hous - household household - householder household - householders household - households household - housekeeper housekeep - housekeepers housekeep - housekeeping housekeep - houseless houseless - houses hous - housewife housewif - housewifery housewiferi - housewives housew - hovel hovel - hover hover - hovered hover - hovering hover - hovers hover - how how - howbeit howbeit - howe how - howeer howeer - however howev - howl howl - howled howl - howlet howlet - howling howl - howls howl - howsoe howso - howsoever howsoev - howsome howsom - hoxes hox - hoy hoi - hoyday hoydai - hubert hubert - huddled huddl - huddling huddl - hue hue - hued hu - hues hue - hug hug - huge huge - hugely huge - hugeness huge - hugg hugg - hugger hugger - hugh hugh - hugs hug - hujus huju - hulk hulk - hulks hulk - hull hull - hulling hull - hullo hullo - hum hum - human human - humane human - humanely human - humanity human - humble humbl - humbled humbl - humbleness humbl - humbler humbler - humbles humbl - humblest humblest - humbling humbl - humbly humbl - hume hume - humh humh - humidity humid - humility humil - humming hum - humor humor - humorous humor - humors humor - humour humour - humourists humourist - humours humour - humphrey humphrei - humphry humphri - hums hum - hundred hundr - hundreds hundr - hundredth hundredth - hung hung - hungarian hungarian - hungary hungari - hunger hunger - hungerford hungerford - hungerly hungerli - hungry hungri - hunt hunt - hunted hunt - hunter hunter - hunters hunter - hunteth hunteth - hunting hunt - huntington huntington - huntress huntress - hunts hunt - huntsman huntsman - huntsmen huntsmen - hurdle hurdl - hurl hurl - hurling hurl - hurls hurl - hurly hurli - hurlyburly hurlyburli - hurricano hurricano - hurricanoes hurricano - hurried hurri - hurries hurri - hurry hurri - hurt hurt - hurting hurt - hurtled hurtl - hurtless hurtless - hurtling hurtl - hurts hurt - husband husband - husbanded husband - husbandless husbandless - husbandry husbandri - husbands husband - hush hush - hushes hush - husht husht - husks husk - huswife huswif - huswifes huswif - hutch hutch - hybla hybla - hydra hydra - hyen hyen - hymen hymen - hymenaeus hymenaeu - hymn hymn - hymns hymn - hyperboles hyperbol - hyperbolical hyperbol - hyperion hyperion - hypocrisy hypocrisi - hypocrite hypocrit - hypocrites hypocrit - hyrcan hyrcan - hyrcania hyrcania - hyrcanian hyrcanian - hyssop hyssop - hysterica hysterica - i i - iachimo iachimo - iaculis iaculi - iago iago - iament iament - ibat ibat - icarus icaru - ice ic - iceland iceland - ici ici - icicle icicl - icicles icicl - icy ici - idea idea - ideas idea - idem idem - iden iden - ides id - idiot idiot - idiots idiot - idle idl - idleness idl - idles idl - idly idli - idol idol - idolatrous idolatr - idolatry idolatri - ield ield - if if - ifs if - ignis igni - ignoble ignobl - ignobly ignobl - ignominious ignomini - ignominy ignomini - ignomy ignomi - ignorance ignor - ignorant ignor - ii ii - iii iii - iiii iiii - il il - ilbow ilbow - ild ild - ilion ilion - ilium ilium - ill ill - illegitimate illegitim - illiterate illiter - illness ill - illo illo - ills ill - illume illum - illumin illumin - illuminate illumin - illumineth illumineth - illusion illus - illusions illus - illustrate illustr - illustrated illustr - illustrious illustri - illyria illyria - illyrian illyrian - ils il - im im - image imag - imagery imageri - images imag - imagin imagin - imaginary imaginari - imagination imagin - imaginations imagin - imagine imagin - imagining imagin - imaginings imagin - imbar imbar - imbecility imbecil - imbrue imbru - imitari imitari - imitate imit - imitated imit - imitation imit - imitations imit - immaculate immacul - immanity imman - immask immask - immaterial immateri - immediacy immediaci - immediate immedi - immediately immedi - imminence immin - imminent immin - immoderate immoder - immoderately immoder - immodest immodest - immoment immoment - immortal immort - immortaliz immortaliz - immortally immort - immur immur - immured immur - immures immur - imogen imogen - imp imp - impaint impaint - impair impair - impairing impair - impale impal - impaled impal - impanelled impanel - impart impart - imparted impart - impartial imparti - impartment impart - imparts impart - impasted impast - impatience impati - impatient impati - impatiently impati - impawn impawn - impeach impeach - impeached impeach - impeachment impeach - impeachments impeach - impedes imped - impediment impedi - impediments impedi - impenetrable impenetr - imperator imper - imperceiverant imperceiver - imperfect imperfect - imperfection imperfect - imperfections imperfect - imperfectly imperfectli - imperial imperi - imperious imperi - imperiously imperi - impertinency impertin - impertinent impertin - impeticos impetico - impetuosity impetuos - impetuous impetu - impieties impieti - impiety impieti - impious impiou - implacable implac - implements implement - implies impli - implor implor - implorators implor - implore implor - implored implor - imploring implor - impon impon - import import - importance import - importancy import - important import - importantly importantli - imported import - importeth importeth - importing import - importless importless - imports import - importun importun - importunacy importunaci - importunate importun - importune importun - importunes importun - importunity importun - impos impo - impose impos - imposed impos - imposition imposit - impositions imposit - impossibilities imposs - impossibility imposs - impossible imposs - imposthume imposthum - impostor impostor - impostors impostor - impotence impot - impotent impot - impounded impound - impregnable impregn - imprese impres - impress impress - impressed impress - impressest impressest - impression impress - impressure impressur - imprimendum imprimendum - imprimis imprimi - imprint imprint - imprinted imprint - imprison imprison - imprisoned imprison - imprisoning imprison - imprisonment imprison - improbable improb - improper improp - improve improv - improvident improvid - impudence impud - impudency impud - impudent impud - impudently impud - impudique impudiqu - impugn impugn - impugns impugn - impure impur - imputation imput - impute imput - in in - inaccessible inaccess - inaidable inaid - inaudible inaud - inauspicious inauspici - incaged incag - incantations incant - incapable incap - incardinate incardin - incarnadine incarnadin - incarnate incarn - incarnation incarn - incens incen - incense incens - incensed incens - incensement incens - incenses incens - incensing incens - incertain incertain - incertainties incertainti - incertainty incertainti - incessant incess - incessantly incessantli - incest incest - incestuous incestu - inch inch - incharitable incharit - inches inch - incidency incid - incident incid - incision incis - incite incit - incites incit - incivil incivil - incivility incivil - inclin inclin - inclinable inclin - inclination inclin - incline inclin - inclined inclin - inclines inclin - inclining inclin - inclips inclip - include includ - included includ - includes includ - inclusive inclus - incomparable incompar - incomprehensible incomprehens - inconsiderate inconsider - inconstancy inconst - inconstant inconst - incontinency incontin - incontinent incontin - incontinently incontin - inconvenience inconveni - inconveniences inconveni - inconvenient inconveni - incony inconi - incorporate incorpor - incorps incorp - incorrect incorrect - increas increa - increase increas - increases increas - increaseth increaseth - increasing increas - incredible incred - incredulous incredul - incur incur - incurable incur - incurr incurr - incurred incur - incursions incurs - ind ind - inde ind - indebted indebt - indeed inde - indent indent - indented indent - indenture indentur - indentures indentur - index index - indexes index - india india - indian indian - indict indict - indicted indict - indictment indict - indies indi - indifferency indiffer - indifferent indiffer - indifferently indiffer - indigent indig - indigest indigest - indigested indigest - indign indign - indignation indign - indignations indign - indigne indign - indignities indign - indignity indign - indirect indirect - indirection indirect - indirections indirect - indirectly indirectli - indiscreet indiscreet - indiscretion indiscret - indispos indispo - indisposition indisposit - indissoluble indissolubl - indistinct indistinct - indistinguish indistinguish - indistinguishable indistinguish - indited indit - individable individ - indrench indrench - indu indu - indubitate indubit - induc induc - induce induc - induced induc - inducement induc - induction induct - inductions induct - indue indu - indued indu - indues indu - indulgence indulg - indulgences indulg - indulgent indulg - indurance indur - industrious industri - industriously industri - industry industri - inequality inequ - inestimable inestim - inevitable inevit - inexecrable inexecr - inexorable inexor - inexplicable inexplic - infallible infal - infallibly infal - infamonize infamon - infamous infam - infamy infami - infancy infanc - infant infant - infants infant - infect infect - infected infect - infecting infect - infection infect - infections infect - infectious infecti - infectiously infecti - infects infect - infer infer - inference infer - inferior inferior - inferiors inferior - infernal infern - inferr inferr - inferreth inferreth - inferring infer - infest infest - infidel infidel - infidels infidel - infinite infinit - infinitely infinit - infinitive infinit - infirm infirm - infirmities infirm - infirmity infirm - infixed infix - infixing infix - inflam inflam - inflame inflam - inflaming inflam - inflammation inflamm - inflict inflict - infliction inflict - influence influenc - influences influenc - infold infold - inform inform - informal inform - information inform - informations inform - informed inform - informer inform - informs inform - infortunate infortun - infring infr - infringe infring - infringed infring - infus infu - infuse infus - infused infus - infusing infus - infusion infus - ingener ingen - ingenious ingeni - ingeniously ingeni - inglorious inglori - ingots ingot - ingraffed ingraf - ingraft ingraft - ingrate ingrat - ingrated ingrat - ingrateful ingrat - ingratitude ingratitud - ingratitudes ingratitud - ingredient ingredi - ingredients ingredi - ingross ingross - inhabit inhabit - inhabitable inhabit - inhabitants inhabit - inhabited inhabit - inhabits inhabit - inhearse inhears - inhearsed inhears - inherent inher - inherit inherit - inheritance inherit - inherited inherit - inheriting inherit - inheritor inheritor - inheritors inheritor - inheritrix inheritrix - inherits inherit - inhibited inhibit - inhibition inhibit - inhoop inhoop - inhuman inhuman - iniquities iniqu - iniquity iniqu - initiate initi - injointed injoint - injunction injunct - injunctions injunct - injur injur - injure injur - injurer injur - injuries injuri - injurious injuri - injury injuri - injustice injustic - ink ink - inkhorn inkhorn - inkle inkl - inkles inkl - inkling inkl - inky inki - inlaid inlaid - inland inland - inlay inlai - inly inli - inmost inmost - inn inn - inner inner - innkeeper innkeep - innocence innoc - innocency innoc - innocent innoc - innocents innoc - innovation innov - innovator innov - inns inn - innumerable innumer - inoculate inocul - inordinate inordin - inprimis inprimi - inquir inquir - inquire inquir - inquiry inquiri - inquisition inquisit - inquisitive inquisit - inroads inroad - insane insan - insanie insani - insatiate insati - insconce insconc - inscrib inscrib - inscription inscript - inscriptions inscript - inscroll inscrol - inscrutable inscrut - insculp insculp - insculpture insculptur - insensible insens - inseparable insepar - inseparate insepar - insert insert - inserted insert - inset inset - inshell inshel - inshipp inshipp - inside insid - insinewed insinew - insinuate insinu - insinuateth insinuateth - insinuating insinu - insinuation insinu - insisted insist - insisting insist - insisture insistur - insociable insoci - insolence insol - insolent insol - insomuch insomuch - inspir inspir - inspiration inspir - inspirations inspir - inspire inspir - inspired inspir - install instal - installed instal - instalment instal - instance instanc - instances instanc - instant instant - instantly instantli - instate instat - instead instead - insteeped insteep - instigate instig - instigated instig - instigation instig - instigations instig - instigator instig - instinct instinct - instinctively instinct - institute institut - institutions institut - instruct instruct - instructed instruct - instruction instruct - instructions instruct - instructs instruct - instrument instrument - instrumental instrument - instruments instrument - insubstantial insubstanti - insufficience insuffici - insufficiency insuffici - insult insult - insulted insult - insulting insult - insultment insult - insults insult - insupportable insupport - insuppressive insuppress - insurrection insurrect - insurrections insurrect - int int - integer integ - integritas integrita - integrity integr - intellect intellect - intellects intellect - intellectual intellectu - intelligence intellig - intelligencer intelligenc - intelligencing intelligenc - intelligent intellig - intelligis intelligi - intelligo intelligo - intemperance intemper - intemperate intemper - intend intend - intended intend - intendeth intendeth - intending intend - intendment intend - intends intend - intenible inten - intent intent - intention intent - intentively intent - intents intent - inter inter - intercept intercept - intercepted intercept - intercepter intercept - interception intercept - intercepts intercept - intercession intercess - intercessors intercessor - interchained interchain - interchang interchang - interchange interchang - interchangeably interchang - interchangement interchang - interchanging interchang - interdiction interdict - interest interest - interim interim - interims interim - interior interior - interjections interject - interjoin interjoin - interlude interlud - intermingle intermingl - intermission intermiss - intermissive intermiss - intermit intermit - intermix intermix - intermixed intermix - interpose interpos - interposer interpos - interposes interpos - interpret interpret - interpretation interpret - interpreted interpret - interpreter interpret - interpreters interpret - interprets interpret - interr interr - interred inter - interrogatories interrogatori - interrupt interrupt - interrupted interrupt - interrupter interrupt - interruptest interruptest - interruption interrupt - interrupts interrupt - intertissued intertissu - intervallums intervallum - interview interview - intestate intest - intestine intestin - intil intil - intimate intim - intimation intim - intitled intitl - intituled intitul - into into - intolerable intoler - intoxicates intox - intreasured intreasur - intreat intreat - intrench intrench - intrenchant intrench - intricate intric - intrinse intrins - intrinsicate intrins - intrude intrud - intruder intrud - intruding intrud - intrusion intrus - inundation inund - inure inur - inurn inurn - invade invad - invades invad - invasion invas - invasive invas - invectively invect - invectives invect - inveigled inveigl - invent invent - invented invent - invention invent - inventions invent - inventor inventor - inventorially inventori - inventoried inventori - inventors inventor - inventory inventori - inverness inver - invert invert - invest invest - invested invest - investing invest - investments invest - inveterate inveter - invincible invinc - inviolable inviol - invised invis - invisible invis - invitation invit - invite invit - invited invit - invites invit - inviting invit - invitis inviti - invocate invoc - invocation invoc - invoke invok - invoked invok - invulnerable invulner - inward inward - inwardly inwardli - inwardness inward - inwards inward - ionia ionia - ionian ionian - ipse ips - ipswich ipswich - ira ira - irae ira - iras ira - ire ir - ireful ir - ireland ireland - iris iri - irish irish - irishman irishman - irishmen irishmen - irks irk - irksome irksom - iron iron - irons iron - irreconcil irreconcil - irrecoverable irrecover - irregular irregular - irregulous irregul - irreligious irreligi - irremovable irremov - irreparable irrepar - irresolute irresolut - irrevocable irrevoc - is is - isabel isabel - isabella isabella - isbel isbel - isbels isbel - iscariot iscariot - ise is - ish ish - isidore isidor - isis isi - island island - islander island - islanders island - islands island - isle isl - isles isl - israel israel - issu issu - issue issu - issued issu - issueless issueless - issues issu - issuing issu - ist ist - ista ista - it it - italian italian - italy itali - itch itch - itches itch - itching itch - item item - items item - iteration iter - ithaca ithaca - its it - itself itself - itshall itshal - iv iv - ivory ivori - ivy ivi - iwis iwi - ix ix - j j - jacet jacet - jack jack - jackanapes jackanap - jacks jack - jacksauce jacksauc - jackslave jackslav - jacob jacob - jade jade - jaded jade - jades jade - jail jail - jakes jake - jamany jamani - james jame - jamy jami - jane jane - jangled jangl - jangling jangl - january januari - janus janu - japhet japhet - jaquenetta jaquenetta - jaques jaqu - jar jar - jarring jar - jars jar - jarteer jarteer - jasons jason - jaunce jaunc - jauncing jaunc - jaundice jaundic - jaundies jaundi - jaw jaw - jawbone jawbon - jaws jaw - jay jai - jays jai - jc jc - je je - jealous jealou - jealousies jealousi - jealousy jealousi - jeer jeer - jeering jeer - jelly jelli - jenny jenni - jeopardy jeopardi - jephtha jephtha - jephthah jephthah - jerkin jerkin - jerkins jerkin - jerks jerk - jeronimy jeronimi - jerusalem jerusalem - jeshu jeshu - jesses jess - jessica jessica - jest jest - jested jest - jester jester - jesters jester - jesting jest - jests jest - jesu jesu - jesus jesu - jet jet - jets jet - jew jew - jewel jewel - jeweller jewel - jewels jewel - jewess jewess - jewish jewish - jewry jewri - jews jew - jezebel jezebel - jig jig - jigging jig - jill jill - jills jill - jingling jingl - joan joan - job job - jockey jockei - jocund jocund - jog jog - jogging jog - john john - johns john - join join - joinder joinder - joined join - joiner joiner - joineth joineth - joins join - joint joint - jointed joint - jointing joint - jointly jointli - jointress jointress - joints joint - jointure jointur - jollity jolliti - jolly jolli - jolt jolt - joltheads jolthead - jordan jordan - joseph joseph - joshua joshua - jot jot - jour jour - jourdain jourdain - journal journal - journey journei - journeying journei - journeyman journeyman - journeymen journeymen - journeys journei - jove jove - jovem jovem - jovial jovial - jowl jowl - jowls jowl - joy joi - joyed joi - joyful joy - joyfully joyfulli - joyless joyless - joyous joyou - joys joi - juan juan - jud jud - judas juda - judases judas - jude jude - judg judg - judge judg - judged judg - judgement judgement - judges judg - judgest judgest - judging judg - judgment judgment - judgments judgment - judicious judici - jug jug - juggle juggl - juggled juggl - juggler juggler - jugglers juggler - juggling juggl - jugs jug - juice juic - juiced juic - jul jul - jule jule - julia julia - juliet juliet - julietta julietta - julio julio - julius juliu - july juli - jump jump - jumpeth jumpeth - jumping jump - jumps jump - june june - junes june - junior junior - junius juniu - junkets junket - juno juno - jupiter jupit - jure jure - jurement jurement - jurisdiction jurisdict - juror juror - jurors juror - jury juri - jurymen jurymen - just just - justeius justeiu - justest justest - justice justic - justicer justic - justicers justic - justices justic - justification justif - justified justifi - justify justifi - justle justl - justled justl - justles justl - justling justl - justly justli - justness just - justs just - jutting jut - jutty jutti - juvenal juven - kam kam - kate kate - kated kate - kates kate - katharine katharin - katherina katherina - katherine katherin - kecksies kecksi - keech keech - keel keel - keels keel - keen keen - keenness keen - keep keep - keepdown keepdown - keeper keeper - keepers keeper - keepest keepest - keeping keep - keeps keep - keiser keiser - ken ken - kendal kendal - kennel kennel - kent kent - kentish kentish - kentishman kentishman - kentishmen kentishmen - kept kept - kerchief kerchief - kerely kere - kern kern - kernal kernal - kernel kernel - kernels kernel - kerns kern - kersey kersei - kettle kettl - kettledrum kettledrum - kettledrums kettledrum - key kei - keys kei - kibe kibe - kibes kibe - kick kick - kicked kick - kickshaws kickshaw - kickshawses kickshaws - kicky kicki - kid kid - kidney kidnei - kikely kike - kildare kildar - kill kill - killed kill - killer killer - killeth killeth - killing kill - killingworth killingworth - kills kill - kiln kiln - kimbolton kimbolton - kin kin - kind kind - kinder kinder - kindest kindest - kindle kindl - kindled kindl - kindless kindless - kindlier kindlier - kindling kindl - kindly kindli - kindness kind - kindnesses kind - kindred kindr - kindreds kindr - kinds kind - kine kine - king king - kingdom kingdom - kingdoms kingdom - kingly kingli - kings king - kinred kinr - kins kin - kinsman kinsman - kinsmen kinsmen - kinswoman kinswoman - kirtle kirtl - kirtles kirtl - kiss kiss - kissed kiss - kisses kiss - kissing kiss - kitchen kitchen - kitchens kitchen - kite kite - kites kite - kitten kitten - kj kj - kl kl - klll klll - knack knack - knacks knack - knapp knapp - knav knav - knave knave - knaveries knaveri - knavery knaveri - knaves knave - knavish knavish - knead knead - kneaded knead - kneading knead - knee knee - kneel kneel - kneeling kneel - kneels kneel - knees knee - knell knell - knew knew - knewest knewest - knife knife - knight knight - knighted knight - knighthood knighthood - knighthoods knighthood - knightly knightli - knights knight - knit knit - knits knit - knitters knitter - knitteth knitteth - knives knive - knobs knob - knock knock - knocking knock - knocks knock - knog knog - knoll knoll - knot knot - knots knot - knotted knot - knotty knotti - know know - knower knower - knowest knowest - knowing know - knowingly knowingli - knowings know - knowledge knowledg - known known - knows know - l l - la la - laban laban - label label - labell label - labienus labienu - labio labio - labor labor - laboring labor - labors labor - labour labour - laboured labour - labourer labour - labourers labour - labouring labour - labours labour - laboursome laboursom - labras labra - labyrinth labyrinth - lac lac - lace lace - laced lace - lacedaemon lacedaemon - laces lace - lacies laci - lack lack - lackbeard lackbeard - lacked lack - lackey lackei - lackeying lackei - lackeys lackei - lacking lack - lacks lack - lad lad - ladder ladder - ladders ladder - lade lade - laden laden - ladies ladi - lading lade - lads lad - lady ladi - ladybird ladybird - ladyship ladyship - ladyships ladyship - laer laer - laertes laert - lafeu lafeu - lag lag - lagging lag - laid laid - lain lain - laissez laissez - lake lake - lakes lake - lakin lakin - lam lam - lamb lamb - lambert lambert - lambkin lambkin - lambkins lambkin - lambs lamb - lame lame - lamely lame - lameness lame - lament lament - lamentable lament - lamentably lament - lamentation lament - lamentations lament - lamented lament - lamenting lament - lamentings lament - laments lament - lames lame - laming lame - lammas lamma - lammastide lammastid - lamound lamound - lamp lamp - lampass lampass - lamps lamp - lanc lanc - lancaster lancast - lance lanc - lances lanc - lanceth lanceth - lanch lanch - land land - landed land - landing land - landless landless - landlord landlord - landmen landmen - lands land - lane lane - lanes lane - langage langag - langley langlei - langton langton - language languag - languageless languageless - languages languag - langues langu - languish languish - languished languish - languishes languish - languishing languish - languishings languish - languishment languish - languor languor - lank lank - lantern lantern - lanterns lantern - lanthorn lanthorn - lap lap - lapis lapi - lapland lapland - lapp lapp - laps lap - lapse laps - lapsed laps - lapsing laps - lapwing lapw - laquais laquai - larded lard - larder larder - larding lard - lards lard - large larg - largely larg - largeness larg - larger larger - largess largess - largest largest - lark lark - larks lark - larron larron - lartius lartiu - larum larum - larums larum - las la - lascivious lascivi - lash lash - lass lass - lasses lass - last last - lasted last - lasting last - lastly lastli - lasts last - latch latch - latches latch - late late - lated late - lately late - later later - latest latest - lath lath - latin latin - latten latten - latter latter - lattice lattic - laud laud - laudable laudabl - laudis laudi - laugh laugh - laughable laughabl - laughed laugh - laugher laugher - laughest laughest - laughing laugh - laughs laugh - laughter laughter - launce launc - launcelot launcelot - launces launc - launch launch - laund laund - laundress laundress - laundry laundri - laur laur - laura laura - laurel laurel - laurels laurel - laurence laurenc - laus lau - lavache lavach - lave lave - lavee lave - lavender lavend - lavina lavina - lavinia lavinia - lavish lavish - lavishly lavishli - lavolt lavolt - lavoltas lavolta - law law - lawful law - lawfully lawfulli - lawless lawless - lawlessly lawlessli - lawn lawn - lawns lawn - lawrence lawrenc - laws law - lawyer lawyer - lawyers lawyer - lay lai - layer layer - layest layest - laying lai - lays lai - lazar lazar - lazars lazar - lazarus lazaru - lazy lazi - lc lc - ld ld - ldst ldst - le le - lead lead - leaden leaden - leader leader - leaders leader - leadest leadest - leading lead - leads lead - leaf leaf - leagu leagu - league leagu - leagued leagu - leaguer leaguer - leagues leagu - leah leah - leak leak - leaky leaki - lean lean - leander leander - leaner leaner - leaning lean - leanness lean - leans lean - leap leap - leaped leap - leaping leap - leaps leap - leapt leapt - lear lear - learn learn - learned learn - learnedly learnedli - learning learn - learnings learn - learns learn - learnt learnt - leas lea - lease leas - leases leas - leash leash - leasing leas - least least - leather leather - leathern leathern - leav leav - leave leav - leaven leaven - leavening leaven - leaver leaver - leaves leav - leaving leav - leavy leavi - lecher lecher - lecherous lecher - lechers lecher - lechery lecheri - lecon lecon - lecture lectur - lectures lectur - led led - leda leda - leech leech - leeches leech - leek leek - leeks leek - leer leer - leers leer - lees lee - leese lees - leet leet - leets leet - left left - leg leg - legacies legaci - legacy legaci - legate legat - legatine legatin - lege lege - legerity leger - leges lege - legg legg - legion legion - legions legion - legitimate legitim - legitimation legitim - legs leg - leicester leicest - leicestershire leicestershir - leiger leiger - leigers leiger - leisure leisur - leisurely leisur - leisures leisur - leman leman - lemon lemon - lena lena - lend lend - lender lender - lending lend - lendings lend - lends lend - length length - lengthen lengthen - lengthens lengthen - lengths length - lenity leniti - lennox lennox - lent lent - lenten lenten - lentus lentu - leo leo - leon leon - leonardo leonardo - leonati leonati - leonato leonato - leonatus leonatu - leontes leont - leopard leopard - leopards leopard - leper leper - leperous leper - lepidus lepidu - leprosy leprosi - lequel lequel - lers ler - les le - less less - lessen lessen - lessens lessen - lesser lesser - lesson lesson - lessoned lesson - lessons lesson - lest lest - lestrake lestrak - let let - lethargied lethargi - lethargies lethargi - lethargy lethargi - lethe leth - lets let - lett lett - letter letter - letters letter - letting let - lettuce lettuc - leur leur - leve leve - level level - levell level - levelled level - levels level - leven leven - levers lever - leviathan leviathan - leviathans leviathan - levied levi - levies levi - levity leviti - levy levi - levying levi - lewd lewd - lewdly lewdli - lewdness lewd - lewdsters lewdster - lewis lewi - liable liabl - liar liar - liars liar - libbard libbard - libelling libel - libels libel - liberal liber - liberality liber - liberte libert - liberties liberti - libertine libertin - libertines libertin - liberty liberti - library librari - libya libya - licence licenc - licens licen - license licens - licentious licenti - lichas licha - licio licio - lick lick - licked lick - licker licker - lictors lictor - lid lid - lids lid - lie lie - lied li - lief lief - liefest liefest - liege lieg - liegeman liegeman - liegemen liegemen - lien lien - lies li - liest liest - lieth lieth - lieu lieu - lieutenant lieuten - lieutenantry lieutenantri - lieutenants lieuten - lieve liev - life life - lifeblood lifeblood - lifeless lifeless - lifelings lifel - lift lift - lifted lift - lifter lifter - lifteth lifteth - lifting lift - lifts lift - lig lig - ligarius ligariu - liggens liggen - light light - lighted light - lighten lighten - lightens lighten - lighter lighter - lightest lightest - lightly lightli - lightness light - lightning lightn - lightnings lightn - lights light - lik lik - like like - liked like - likeliest likeliest - likelihood likelihood - likelihoods likelihood - likely like - likeness like - liker liker - likes like - likest likest - likewise likewis - liking like - likings like - lilies lili - lily lili - lim lim - limander limand - limb limb - limbeck limbeck - limbecks limbeck - limber limber - limbo limbo - limbs limb - lime lime - limed lime - limehouse limehous - limekilns limekiln - limit limit - limitation limit - limited limit - limits limit - limn limn - limp limp - limping limp - limps limp - lin lin - lincoln lincoln - lincolnshire lincolnshir - line line - lineal lineal - lineally lineal - lineament lineament - lineaments lineament - lined line - linen linen - linens linen - lines line - ling ling - lingare lingar - linger linger - lingered linger - lingers linger - linguist linguist - lining line - link link - links link - linsey linsei - linstock linstock - linta linta - lion lion - lionel lionel - lioness lioness - lions lion - lip lip - lipp lipp - lips lip - lipsbury lipsburi - liquid liquid - liquor liquor - liquorish liquorish - liquors liquor - lirra lirra - lisbon lisbon - lisp lisp - lisping lisp - list list - listen listen - listening listen - lists list - literatured literatur - lither lither - litter litter - little littl - littlest littlest - liv liv - live live - lived live - livelier liveli - livelihood livelihood - livelong livelong - lively live - liver liver - liveries liveri - livers liver - livery liveri - lives live - livest livest - liveth liveth - livia livia - living live - livings live - lizard lizard - lizards lizard - ll ll - lll lll - llous llou - lnd lnd - lo lo - loa loa - loach loach - load load - loaden loaden - loading load - loads load - loaf loaf - loam loam - loan loan - loath loath - loathe loath - loathed loath - loather loather - loathes loath - loathing loath - loathly loathli - loathness loath - loathsome loathsom - loathsomeness loathsom - loathsomest loathsomest - loaves loav - lob lob - lobbies lobbi - lobby lobbi - local local - lochaber lochab - lock lock - locked lock - locking lock - lockram lockram - locks lock - locusts locust - lode lode - lodg lodg - lodge lodg - lodged lodg - lodgers lodger - lodges lodg - lodging lodg - lodgings lodg - lodovico lodovico - lodowick lodowick - lofty lofti - log log - logger logger - loggerhead loggerhead - loggerheads loggerhead - loggets logget - logic logic - logs log - loins loin - loiter loiter - loiterer loiter - loiterers loiter - loitering loiter - lolling loll - lolls loll - lombardy lombardi - london london - londoners london - lone lone - loneliness loneli - lonely lone - long long - longaville longavil - longboat longboat - longed long - longer longer - longest longest - longeth longeth - longing long - longings long - longly longli - longs long - longtail longtail - loo loo - loof loof - look look - looked look - looker looker - lookers looker - lookest lookest - looking look - looks look - loon loon - loop loop - loos loo - loose loos - loosed loos - loosely loos - loosen loosen - loosing loos - lop lop - lopp lopp - loquitur loquitur - lord lord - lorded lord - lording lord - lordings lord - lordliness lordli - lordly lordli - lords lord - lordship lordship - lordships lordship - lorenzo lorenzo - lorn lorn - lorraine lorrain - lorship lorship - los lo - lose lose - loser loser - losers loser - loses lose - losest losest - loseth loseth - losing lose - loss loss - losses loss - lost lost - lot lot - lots lot - lott lott - lottery lotteri - loud loud - louder louder - loudly loudli - lour lour - loureth loureth - louring lour - louse lous - louses lous - lousy lousi - lout lout - louted lout - louts lout - louvre louvr - lov lov - love love - loved love - lovedst lovedst - lovel lovel - lovelier loveli - loveliness loveli - lovell lovel - lovely love - lover lover - lovered lover - lovers lover - loves love - lovest lovest - loveth loveth - loving love - lovingly lovingli - low low - lowe low - lower lower - lowest lowest - lowing low - lowliness lowli - lowly lowli - lown lown - lowness low - loyal loyal - loyally loyal - loyalties loyalti - loyalty loyalti - lozel lozel - lt lt - lubber lubber - lubberly lubberli - luc luc - luccicos luccico - luce luce - lucentio lucentio - luces luce - lucetta lucetta - luciana luciana - lucianus lucianu - lucifer lucif - lucifier lucifi - lucilius luciliu - lucina lucina - lucio lucio - lucius luciu - luck luck - luckier luckier - luckiest luckiest - luckily luckili - luckless luckless - lucky lucki - lucre lucr - lucrece lucrec - lucretia lucretia - lucullius luculliu - lucullus lucullu - lucy luci - lud lud - ludlow ludlow - lug lug - lugg lugg - luggage luggag - luke luke - lukewarm lukewarm - lull lull - lulla lulla - lullaby lullabi - lulls lull - lumbert lumbert - lump lump - lumpish lumpish - luna luna - lunacies lunaci - lunacy lunaci - lunatic lunat - lunatics lunat - lunes lune - lungs lung - lupercal luperc - lurch lurch - lure lure - lurk lurk - lurketh lurketh - lurking lurk - lurks lurk - luscious lusciou - lush lush - lust lust - lusted lust - luster luster - lustful lust - lustier lustier - lustiest lustiest - lustig lustig - lustihood lustihood - lustily lustili - lustre lustr - lustrous lustrou - lusts lust - lusty lusti - lute lute - lutes lute - lutestring lutestr - lutheran lutheran - luxurious luxuri - luxuriously luxuri - luxury luxuri - ly ly - lycaonia lycaonia - lycurguses lycurgus - lydia lydia - lye lye - lyen lyen - lying ly - lym lym - lymoges lymog - lynn lynn - lysander lysand - m m - ma ma - maan maan - mab mab - macbeth macbeth - maccabaeus maccabaeu - macdonwald macdonwald - macduff macduff - mace mace - macedon macedon - maces mace - machiavel machiavel - machination machin - machinations machin - machine machin - mack mack - macmorris macmorri - maculate macul - maculation macul - mad mad - madam madam - madame madam - madams madam - madcap madcap - madded mad - madding mad - made made - madeira madeira - madly madli - madman madman - madmen madmen - madness mad - madonna madonna - madrigals madrig - mads mad - maecenas maecena - maggot maggot - maggots maggot - magic magic - magical magic - magician magician - magistrate magistr - magistrates magistr - magnanimity magnanim - magnanimous magnanim - magni magni - magnifi magnifi - magnificence magnific - magnificent magnific - magnifico magnifico - magnificoes magnifico - magnus magnu - mahomet mahomet - mahu mahu - maid maid - maiden maiden - maidenhead maidenhead - maidenheads maidenhead - maidenhood maidenhood - maidenhoods maidenhood - maidenliest maidenliest - maidenly maidenli - maidens maiden - maidhood maidhood - maids maid - mail mail - mailed mail - mails mail - maim maim - maimed maim - maims maim - main main - maincourse maincours - maine main - mainly mainli - mainmast mainmast - mains main - maintain maintain - maintained maintain - maintains maintain - maintenance mainten - mais mai - maison maison - majestas majesta - majestee majeste - majestic majest - majestical majest - majestically majest - majesties majesti - majesty majesti - major major - majority major - mak mak - make make - makeless makeless - maker maker - makers maker - makes make - makest makest - maketh maketh - making make - makings make - mal mal - mala mala - maladies maladi - malady maladi - malapert malapert - malcolm malcolm - malcontent malcont - malcontents malcont - male male - maledictions maledict - malefactions malefact - malefactor malefactor - malefactors malefactor - males male - malevolence malevol - malevolent malevol - malhecho malhecho - malice malic - malicious malici - maliciously malici - malign malign - malignancy malign - malignant malign - malignantly malignantli - malkin malkin - mall mall - mallard mallard - mallet mallet - mallows mallow - malmsey malmsei - malt malt - maltworms maltworm - malvolio malvolio - mamillius mamilliu - mammering mammer - mammet mammet - mammets mammet - mammock mammock - man man - manacle manacl - manacles manacl - manage manag - managed manag - manager manag - managing manag - manakin manakin - manchus manchu - mandate mandat - mandragora mandragora - mandrake mandrak - mandrakes mandrak - mane mane - manent manent - manes mane - manet manet - manfully manfulli - mangle mangl - mangled mangl - mangles mangl - mangling mangl - mangy mangi - manhood manhood - manhoods manhood - manifest manifest - manifested manifest - manifests manifest - manifold manifold - manifoldly manifoldli - manka manka - mankind mankind - manlike manlik - manly manli - mann mann - manna manna - manner manner - mannerly mannerli - manners manner - manningtree manningtre - mannish mannish - manor manor - manors manor - mans man - mansion mansion - mansionry mansionri - mansions mansion - manslaughter manslaught - mantle mantl - mantled mantl - mantles mantl - mantua mantua - mantuan mantuan - manual manual - manure manur - manured manur - manus manu - many mani - map map - mapp mapp - maps map - mar mar - marble marbl - marbled marbl - marcade marcad - marcellus marcellu - march march - marches march - marcheth marcheth - marching march - marchioness marchio - marchpane marchpan - marcians marcian - marcius marciu - marcus marcu - mardian mardian - mare mare - mares mare - marg marg - margarelon margarelon - margaret margaret - marge marg - margent margent - margery margeri - maria maria - marian marian - mariana mariana - maries mari - marigold marigold - mariner marin - mariners marin - maritime maritim - marjoram marjoram - mark mark - marked mark - market market - marketable market - marketplace marketplac - markets market - marking mark - markman markman - marks mark - marl marl - marle marl - marmoset marmoset - marquess marquess - marquis marqui - marr marr - marriage marriag - marriages marriag - married marri - marries marri - marring mar - marrow marrow - marrowless marrowless - marrows marrow - marry marri - marrying marri - mars mar - marseilles marseil - marsh marsh - marshal marshal - marshalsea marshalsea - marshalship marshalship - mart mart - marted mart - martem martem - martext martext - martial martial - martin martin - martino martino - martius martiu - martlemas martlema - martlet martlet - marts mart - martyr martyr - martyrs martyr - marullus marullu - marv marv - marvel marvel - marvell marvel - marvellous marvel - marvellously marvel - marvels marvel - mary mari - mas ma - masculine masculin - masham masham - mask mask - masked mask - masker masker - maskers masker - masking mask - masks mask - mason mason - masonry masonri - masons mason - masque masqu - masquers masquer - masques masqu - masquing masqu - mass mass - massacre massacr - massacres massacr - masses mass - massy massi - mast mast - mastcr mastcr - master master - masterdom masterdom - masterest masterest - masterless masterless - masterly masterli - masterpiece masterpiec - masters master - mastership mastership - mastic mastic - mastiff mastiff - mastiffs mastiff - masts mast - match match - matches match - matcheth matcheth - matching match - matchless matchless - mate mate - mated mate - mater mater - material materi - mates mate - mathematics mathemat - matin matin - matron matron - matrons matron - matter matter - matters matter - matthew matthew - mattock mattock - mattress mattress - mature matur - maturity matur - maud maud - maudlin maudlin - maugre maugr - maul maul - maund maund - mauri mauri - mauritania mauritania - mauvais mauvai - maw maw - maws maw - maxim maxim - may mai - mayday maydai - mayest mayest - mayor mayor - maypole maypol - mayst mayst - maz maz - maze maze - mazed maze - mazes maze - mazzard mazzard - me me - meacock meacock - mead mead - meadow meadow - meadows meadow - meads mead - meagre meagr - meal meal - meals meal - mealy meali - mean mean - meanders meander - meaner meaner - meanest meanest - meaneth meaneth - meaning mean - meanings mean - meanly meanli - means mean - meant meant - meantime meantim - meanwhile meanwhil - measles measl - measur measur - measurable measur - measure measur - measured measur - measureless measureless - measures measur - measuring measur - meat meat - meats meat - mechanic mechan - mechanical mechan - mechanicals mechan - mechanics mechan - mechante mechant - med med - medal medal - meddle meddl - meddler meddler - meddling meddl - mede mede - medea medea - media media - mediation mediat - mediators mediat - medice medic - medicinal medicin - medicine medicin - medicines medicin - meditate medit - meditates medit - meditating medit - meditation medit - meditations medit - mediterranean mediterranean - mediterraneum mediterraneum - medlar medlar - medlars medlar - meed meed - meeds meed - meek meek - meekly meekli - meekness meek - meet meet - meeter meeter - meetest meetest - meeting meet - meetings meet - meetly meetli - meetness meet - meets meet - meg meg - mehercle mehercl - meilleur meilleur - meiny meini - meisen meisen - melancholies melancholi - melancholy melancholi - melford melford - mell mell - mellifluous melliflu - mellow mellow - mellowing mellow - melodious melodi - melody melodi - melt melt - melted melt - melteth melteth - melting melt - melts melt - melun melun - member member - members member - memento memento - memorable memor - memorandums memorandum - memorial memori - memorials memori - memories memori - memoriz memoriz - memorize memor - memory memori - memphis memphi - men men - menac menac - menace menac - menaces menac - menaphon menaphon - menas mena - mend mend - mended mend - mender mender - mending mend - mends mend - menecrates menecr - menelaus menelau - menenius meneniu - mental mental - menteith menteith - mention mention - mentis menti - menton menton - mephostophilus mephostophilu - mer mer - mercatante mercatant - mercatio mercatio - mercenaries mercenari - mercenary mercenari - mercer mercer - merchandise merchandis - merchandized merchand - merchant merchant - merchants merchant - mercies merci - merciful merci - mercifully mercifulli - merciless merciless - mercurial mercuri - mercuries mercuri - mercury mercuri - mercutio mercutio - mercy merci - mere mere - mered mere - merely mere - merest merest - meridian meridian - merit merit - merited merit - meritorious meritori - merits merit - merlin merlin - mermaid mermaid - mermaids mermaid - merops merop - merrier merrier - merriest merriest - merrily merrili - merriman merriman - merriment merriment - merriments merriment - merriness merri - merry merri - mervailous mervail - mes me - mesh mesh - meshes mesh - mesopotamia mesopotamia - mess mess - message messag - messages messag - messala messala - messaline messalin - messenger messeng - messengers messeng - messes mess - messina messina - met met - metal metal - metals metal - metamorphis metamorphi - metamorphoses metamorphos - metaphor metaphor - metaphysical metaphys - metaphysics metaphys - mete mete - metellus metellu - meteor meteor - meteors meteor - meteyard meteyard - metheglin metheglin - metheglins metheglin - methink methink - methinks methink - method method - methods method - methought methought - methoughts methought - metre metr - metres metr - metropolis metropoli - mette mett - mettle mettl - mettled mettl - meus meu - mew mew - mewed mew - mewling mewl - mexico mexico - mi mi - mice mice - michael michael - michaelmas michaelma - micher micher - miching mich - mickle mickl - microcosm microcosm - mid mid - midas mida - middest middest - middle middl - middleham middleham - midnight midnight - midriff midriff - midst midst - midsummer midsumm - midway midwai - midwife midwif - midwives midwiv - mienne mienn - might might - mightful might - mightier mightier - mightiest mightiest - mightily mightili - mightiness mighti - mightst mightst - mighty mighti - milan milan - milch milch - mild mild - milder milder - mildest mildest - mildew mildew - mildews mildew - mildly mildli - mildness mild - mile mile - miles mile - milford milford - militarist militarist - military militari - milk milk - milking milk - milkmaid milkmaid - milks milk - milksops milksop - milky milki - mill mill - mille mill - miller miller - milliner millin - million million - millioned million - millions million - mills mill - millstones millston - milo milo - mimic mimic - minc minc - mince minc - minces minc - mincing minc - mind mind - minded mind - minding mind - mindless mindless - minds mind - mine mine - mineral miner - minerals miner - minerva minerva - mines mine - mingle mingl - mingled mingl - mingling mingl - minikin minikin - minim minim - minime minim - minimo minimo - minimus minimu - mining mine - minion minion - minions minion - minist minist - minister minist - ministers minist - ministration ministr - minnow minnow - minnows minnow - minola minola - minority minor - minos mino - minotaurs minotaur - minstrel minstrel - minstrels minstrel - minstrelsy minstrelsi - mint mint - mints mint - minute minut - minutely minut - minutes minut - minx minx - mio mio - mir mir - mirable mirabl - miracle miracl - miracles miracl - miraculous miracul - miranda miranda - mire mire - mirror mirror - mirrors mirror - mirth mirth - mirthful mirth - miry miri - mis mi - misadventur misadventur - misadventure misadventur - misanthropos misanthropo - misapplied misappli - misbecame misbecam - misbecom misbecom - misbecome misbecom - misbegot misbegot - misbegotten misbegotten - misbeliever misbeliev - misbelieving misbeliev - misbhav misbhav - miscall miscal - miscalled miscal - miscarried miscarri - miscarries miscarri - miscarry miscarri - miscarrying miscarri - mischance mischanc - mischances mischanc - mischief mischief - mischiefs mischief - mischievous mischiev - misconceived misconceiv - misconst misconst - misconster misconst - misconstruction misconstruct - misconstrued misconstru - misconstrues misconstru - miscreant miscreant - miscreate miscreat - misdeed misde - misdeeds misde - misdemean misdemean - misdemeanours misdemeanour - misdoubt misdoubt - misdoubteth misdoubteth - misdoubts misdoubt - misenum misenum - miser miser - miserable miser - miserably miser - misericorde misericord - miseries miseri - misers miser - misery miseri - misfortune misfortun - misfortunes misfortun - misgive misgiv - misgives misgiv - misgiving misgiv - misgoverned misgovern - misgovernment misgovern - misgraffed misgraf - misguide misguid - mishap mishap - mishaps mishap - misheard misheard - misinterpret misinterpret - mislead mislead - misleader mislead - misleaders mislead - misleading mislead - misled misl - mislike mislik - misord misord - misplac misplac - misplaced misplac - misplaces misplac - mispris mispri - misprised mispris - misprision mispris - misprizing mispriz - misproud misproud - misquote misquot - misreport misreport - miss miss - missed miss - misses miss - misshap misshap - misshapen misshapen - missheathed missheath - missing miss - missingly missingli - missions mission - missive missiv - missives missiv - misspoke misspok - mist mist - mista mista - mistak mistak - mistake mistak - mistaken mistaken - mistakes mistak - mistaketh mistaketh - mistaking mistak - mistakings mistak - mistemp mistemp - mistempered mistemp - misterm misterm - mistful mist - misthink misthink - misthought misthought - mistletoe mistleto - mistook mistook - mistreadings mistread - mistress mistress - mistresses mistress - mistresss mistresss - mistriship mistriship - mistrust mistrust - mistrusted mistrust - mistrustful mistrust - mistrusting mistrust - mists mist - misty misti - misus misu - misuse misus - misused misus - misuses misus - mites mite - mithridates mithrid - mitigate mitig - mitigation mitig - mix mix - mixed mix - mixture mixtur - mixtures mixtur - mm mm - mnd mnd - moan moan - moans moan - moat moat - moated moat - mobled mobl - mock mock - mockable mockabl - mocker mocker - mockeries mockeri - mockers mocker - mockery mockeri - mocking mock - mocks mock - mockvater mockvat - mockwater mockwat - model model - modena modena - moderate moder - moderately moder - moderation moder - modern modern - modest modest - modesties modesti - modestly modestli - modesty modesti - modicums modicum - modo modo - module modul - moe moe - moi moi - moiety moieti - moist moist - moisten moisten - moisture moistur - moldwarp moldwarp - mole mole - molehill molehil - moles mole - molest molest - molestation molest - mollification mollif - mollis molli - molten molten - molto molto - mome mome - moment moment - momentary momentari - moming mome - mon mon - monachum monachum - monarch monarch - monarchies monarchi - monarchize monarch - monarcho monarcho - monarchs monarch - monarchy monarchi - monast monast - monastery monasteri - monastic monast - monday mondai - monde mond - money monei - moneys monei - mong mong - monger monger - mongers monger - monging mong - mongrel mongrel - mongrels mongrel - mongst mongst - monk monk - monkey monkei - monkeys monkei - monks monk - monmouth monmouth - monopoly monopoli - mons mon - monsieur monsieur - monsieurs monsieur - monster monster - monsters monster - monstrous monstrou - monstrously monstrous - monstrousness monstrous - monstruosity monstruos - montacute montacut - montage montag - montague montagu - montagues montagu - montano montano - montant montant - montez montez - montferrat montferrat - montgomery montgomeri - month month - monthly monthli - months month - montjoy montjoi - monument monument - monumental monument - monuments monument - mood mood - moods mood - moody moodi - moon moon - moonbeams moonbeam - moonish moonish - moonlight moonlight - moons moon - moonshine moonshin - moonshines moonshin - moor moor - moorfields moorfield - moors moor - moorship moorship - mop mop - mope mope - moping mope - mopping mop - mopsa mopsa - moral moral - moraler moral - morality moral - moralize moral - mordake mordak - more more - moreover moreov - mores more - morgan morgan - mori mori - morisco morisco - morn morn - morning morn - mornings morn - morocco morocco - morris morri - morrow morrow - morrows morrow - morsel morsel - morsels morsel - mort mort - mortal mortal - mortality mortal - mortally mortal - mortals mortal - mortar mortar - mortgaged mortgag - mortified mortifi - mortifying mortifi - mortimer mortim - mortimers mortim - mortis morti - mortise mortis - morton morton - mose mose - moss moss - mossgrown mossgrown - most most - mote mote - moth moth - mother mother - mothers mother - moths moth - motion motion - motionless motionless - motions motion - motive motiv - motives motiv - motley motlei - mots mot - mought mought - mould mould - moulded mould - mouldeth mouldeth - moulds mould - mouldy mouldi - moult moult - moulten moulten - mounch mounch - mounseur mounseur - mounsieur mounsieur - mount mount - mountain mountain - mountaineer mountain - mountaineers mountain - mountainous mountain - mountains mountain - mountant mountant - mountanto mountanto - mountebank mountebank - mountebanks mountebank - mounted mount - mounteth mounteth - mounting mount - mounts mount - mourn mourn - mourned mourn - mourner mourner - mourners mourner - mournful mourn - mournfully mournfulli - mourning mourn - mourningly mourningli - mournings mourn - mourns mourn - mous mou - mouse mous - mousetrap mousetrap - mousing mous - mouth mouth - mouthed mouth - mouths mouth - mov mov - movables movabl - move move - moveable moveabl - moveables moveabl - moved move - mover mover - movers mover - moves move - moveth moveth - moving move - movingly movingli - movousus movousu - mow mow - mowbray mowbrai - mower mower - mowing mow - mows mow - moy moi - moys moi - moyses moys - mrs mr - much much - muck muck - mud mud - mudded mud - muddied muddi - muddy muddi - muffins muffin - muffl muffl - muffle muffl - muffled muffl - muffler muffler - muffling muffl - mugger mugger - mugs mug - mulberries mulberri - mulberry mulberri - mule mule - mules mule - muleteers mulet - mulier mulier - mulieres mulier - muliteus muliteu - mull mull - mulmutius mulmutiu - multiplied multipli - multiply multipli - multiplying multipli - multipotent multipot - multitude multitud - multitudes multitud - multitudinous multitudin - mum mum - mumble mumbl - mumbling mumbl - mummers mummer - mummy mummi - mun mun - munch munch - muniments muniment - munition munit - murd murd - murder murder - murdered murder - murderer murder - murderers murder - murdering murder - murderous murder - murders murder - mure mure - murk murk - murkiest murkiest - murky murki - murmur murmur - murmurers murmur - murmuring murmur - murrain murrain - murray murrai - murrion murrion - murther murther - murtherer murther - murtherers murther - murthering murther - murtherous murther - murthers murther - mus mu - muscadel muscadel - muscovites muscovit - muscovits muscovit - muscovy muscovi - muse muse - muses muse - mush mush - mushrooms mushroom - music music - musical music - musician musician - musicians musician - musics music - musing muse - musings muse - musk musk - musket musket - muskets musket - muskos musko - muss muss - mussel mussel - mussels mussel - must must - mustachio mustachio - mustard mustard - mustardseed mustardse - muster muster - mustering muster - musters muster - musty musti - mutability mutabl - mutable mutabl - mutation mutat - mutations mutat - mute mute - mutes mute - mutest mutest - mutine mutin - mutineer mutin - mutineers mutin - mutines mutin - mutinies mutini - mutinous mutin - mutiny mutini - mutius mutiu - mutter mutter - muttered mutter - mutton mutton - muttons mutton - mutual mutual - mutualities mutual - mutually mutual - muzzl muzzl - muzzle muzzl - muzzled muzzl - mv mv - mww mww - my my - mynheers mynheer - myrmidon myrmidon - myrmidons myrmidon - myrtle myrtl - myself myself - myst myst - mysteries mysteri - mystery mysteri - n n - nag nag - nage nage - nags nag - naiads naiad - nail nail - nails nail - nak nak - naked nake - nakedness naked - nal nal - nam nam - name name - named name - nameless nameless - namely name - names name - namest namest - naming name - nan nan - nance nanc - nap nap - nape nape - napes nape - napkin napkin - napkins napkin - naples napl - napless napless - napping nap - naps nap - narbon narbon - narcissus narcissu - narines narin - narrow narrow - narrowly narrowli - naso naso - nasty nasti - nathaniel nathaniel - natifs natif - nation nation - nations nation - native nativ - nativity nativ - natur natur - natural natur - naturalize natur - naturally natur - nature natur - natured natur - natures natur - natus natu - naught naught - naughtily naughtili - naughty naughti - navarre navarr - nave nave - navel navel - navigation navig - navy navi - nay nai - nayward nayward - nayword nayword - nazarite nazarit - ne ne - neaf neaf - neamnoins neamnoin - neanmoins neanmoin - neapolitan neapolitan - neapolitans neapolitan - near near - nearer nearer - nearest nearest - nearly nearli - nearness near - neat neat - neatly neatli - neb neb - nebour nebour - nebuchadnezzar nebuchadnezzar - nec nec - necessaries necessari - necessarily necessarili - necessary necessari - necessitied necess - necessities necess - necessity necess - neck neck - necklace necklac - necks neck - nectar nectar - ned ned - nedar nedar - need need - needed need - needer needer - needful need - needfull needful - needing need - needle needl - needles needl - needless needless - needly needli - needs need - needy needi - neer neer - neeze neez - nefas nefa - negation negat - negative neg - negatives neg - neglect neglect - neglected neglect - neglecting neglect - neglectingly neglectingli - neglection neglect - negligence neglig - negligent neglig - negotiate negoti - negotiations negoti - negro negro - neigh neigh - neighbors neighbor - neighbour neighbour - neighbourhood neighbourhood - neighbouring neighbour - neighbourly neighbourli - neighbours neighbour - neighing neigh - neighs neigh - neither neither - nell nell - nemean nemean - nemesis nemesi - neoptolemus neoptolemu - nephew nephew - nephews nephew - neptune neptun - ner ner - nereides nereid - nerissa nerissa - nero nero - neroes nero - ners ner - nerve nerv - nerves nerv - nervii nervii - nervy nervi - nessus nessu - nest nest - nestor nestor - nests nest - net net - nether nether - netherlands netherland - nets net - nettle nettl - nettled nettl - nettles nettl - neuter neuter - neutral neutral - nev nev - never never - nevil nevil - nevils nevil - new new - newborn newborn - newer newer - newest newest - newgate newgat - newly newli - newness new - news new - newsmongers newsmong - newt newt - newts newt - next next - nibbling nibbl - nicanor nicanor - nice nice - nicely nice - niceness nice - nicer nicer - nicety niceti - nicholas nichola - nick nick - nickname nicknam - nicks nick - niece niec - nieces niec - niggard niggard - niggarding niggard - niggardly niggardli - nigh nigh - night night - nightcap nightcap - nightcaps nightcap - nighted night - nightgown nightgown - nightingale nightingal - nightingales nightingal - nightly nightli - nightmare nightmar - nights night - nightwork nightwork - nihil nihil - nile nile - nill nill - nilus nilu - nimble nimbl - nimbleness nimbl - nimbler nimbler - nimbly nimbl - nine nine - nineteen nineteen - ning ning - ningly ningli - ninny ninni - ninth ninth - ninus ninu - niobe niob - niobes niob - nip nip - nipp nipp - nipping nip - nipple nippl - nips nip - nit nit - nly nly - nnight nnight - nnights nnight - no no - noah noah - nob nob - nobility nobil - nobis nobi - noble nobl - nobleman nobleman - noblemen noblemen - nobleness nobl - nobler nobler - nobles nobl - noblesse nobless - noblest noblest - nobly nobli - nobody nobodi - noces noce - nod nod - nodded nod - nodding nod - noddle noddl - noddles noddl - noddy noddi - nods nod - noes noe - nointed noint - nois noi - noise nois - noiseless noiseless - noisemaker noisemak - noises nois - noisome noisom - nole nole - nominate nomin - nominated nomin - nomination nomin - nominativo nominativo - non non - nonage nonag - nonce nonc - none none - nonino nonino - nonny nonni - nonpareil nonpareil - nonsuits nonsuit - nony noni - nook nook - nooks nook - noon noon - noonday noondai - noontide noontid - nor nor - norbery norberi - norfolk norfolk - norman norman - normandy normandi - normans norman - north north - northampton northampton - northamptonshire northamptonshir - northerly northerli - northern northern - northgate northgat - northumberland northumberland - northumberlands northumberland - northward northward - norway norwai - norways norwai - norwegian norwegian - norweyan norweyan - nos no - nose nose - nosegays nosegai - noseless noseless - noses nose - noster noster - nostra nostra - nostril nostril - nostrils nostril - not not - notable notabl - notably notabl - notary notari - notch notch - note note - notebook notebook - noted note - notedly notedli - notes note - notest notest - noteworthy noteworthi - nothing noth - nothings noth - notice notic - notify notifi - noting note - notion notion - notorious notori - notoriously notori - notre notr - notwithstanding notwithstand - nought nought - noun noun - nouns noun - nourish nourish - nourished nourish - nourisher nourish - nourishes nourish - nourisheth nourisheth - nourishing nourish - nourishment nourish - nous nou - novel novel - novelties novelti - novelty novelti - noverbs noverb - novi novi - novice novic - novices novic - novum novum - now now - nowhere nowher - noyance noyanc - ns ns - nt nt - nubibus nubibu - numa numa - numb numb - number number - numbered number - numbering number - numberless numberless - numbers number - numbness numb - nun nun - nuncio nuncio - nuncle nuncl - nunnery nunneri - nuns nun - nuntius nuntiu - nuptial nuptial - nurs nur - nurse nurs - nursed nurs - nurser nurser - nursery nurseri - nurses nurs - nurseth nurseth - nursh nursh - nursing nurs - nurtur nurtur - nurture nurtur - nut nut - nuthook nuthook - nutmeg nutmeg - nutmegs nutmeg - nutriment nutriment - nuts nut - nutshell nutshel - ny ny - nym nym - nymph nymph - nymphs nymph - o o - oak oak - oaken oaken - oaks oak - oared oar - oars oar - oatcake oatcak - oaten oaten - oath oath - oathable oathabl - oaths oath - oats oat - ob ob - obduracy obduraci - obdurate obdur - obedience obedi - obedient obedi - obeisance obeis - oberon oberon - obey obei - obeyed obei - obeying obei - obeys obei - obidicut obidicut - object object - objected object - objections object - objects object - oblation oblat - oblations oblat - obligation oblig - obligations oblig - obliged oblig - oblique obliqu - oblivion oblivion - oblivious oblivi - obloquy obloqui - obscene obscen - obscenely obscen - obscur obscur - obscure obscur - obscured obscur - obscurely obscur - obscures obscur - obscuring obscur - obscurity obscur - obsequies obsequi - obsequious obsequi - obsequiously obsequi - observ observ - observance observ - observances observ - observancy observ - observant observ - observants observ - observation observ - observe observ - observed observ - observer observ - observers observ - observing observ - observingly observingli - obsque obsqu - obstacle obstacl - obstacles obstacl - obstinacy obstinaci - obstinate obstin - obstinately obstin - obstruct obstruct - obstruction obstruct - obstructions obstruct - obtain obtain - obtained obtain - obtaining obtain - occasion occas - occasions occas - occident occid - occidental occident - occulted occult - occupat occupat - occupation occup - occupations occup - occupied occupi - occupies occupi - occupy occupi - occurrence occurr - occurrences occurr - occurrents occurr - ocean ocean - oceans ocean - octavia octavia - octavius octaviu - ocular ocular - od od - odd odd - oddest oddest - oddly oddli - odds odd - ode od - odes od - odious odiou - odoriferous odorifer - odorous odor - odour odour - odours odour - ods od - oeillades oeillad - oes oe - oeuvres oeuvr - of of - ofephesus ofephesu - off off - offal offal - offence offenc - offenceful offenc - offences offenc - offend offend - offended offend - offendendo offendendo - offender offend - offenders offend - offendeth offendeth - offending offend - offendress offendress - offends offend - offense offens - offenseless offenseless - offenses offens - offensive offens - offer offer - offered offer - offering offer - offerings offer - offers offer - offert offert - offic offic - office offic - officed offic - officer offic - officers offic - offices offic - official offici - officious offici - offspring offspr - oft oft - often often - oftener often - oftentimes oftentim - oh oh - oil oil - oils oil - oily oili - old old - oldcastle oldcastl - olden olden - older older - oldest oldest - oldness old - olive oliv - oliver oliv - olivers oliv - olives oliv - olivia olivia - olympian olympian - olympus olympu - oman oman - omans oman - omen omen - ominous omin - omission omiss - omit omit - omittance omitt - omitted omit - omitting omit - omne omn - omnes omn - omnipotent omnipot - on on - once onc - one on - ones on - oneyers oney - ongles ongl - onion onion - onions onion - only onli - onset onset - onward onward - onwards onward - oo oo - ooze ooz - oozes ooz - oozy oozi - op op - opal opal - ope op - open open - opener open - opening open - openly openli - openness open - opens open - operant oper - operate oper - operation oper - operations oper - operative oper - opes op - oph oph - ophelia ophelia - opinion opinion - opinions opinion - opportune opportun - opportunities opportun - opportunity opportun - oppos oppo - oppose oppos - opposed oppos - opposeless opposeless - opposer oppos - opposers oppos - opposes oppos - opposing oppos - opposite opposit - opposites opposit - opposition opposit - oppositions opposit - oppress oppress - oppressed oppress - oppresses oppress - oppresseth oppresseth - oppressing oppress - oppression oppress - oppressor oppressor - opprest opprest - opprobriously opprobri - oppugnancy oppugn - opulency opul - opulent opul - or or - oracle oracl - oracles oracl - orange orang - oration orat - orator orat - orators orat - oratory oratori - orb orb - orbed orb - orbs orb - orchard orchard - orchards orchard - ord ord - ordain ordain - ordained ordain - ordaining ordain - order order - ordered order - ordering order - orderless orderless - orderly orderli - orders order - ordinance ordin - ordinant ordin - ordinaries ordinari - ordinary ordinari - ordnance ordnanc - ords ord - ordure ordur - ore or - organ organ - organs organ - orgillous orgil - orient orient - orifex orifex - origin origin - original origin - orisons orison - ork ork - orlando orlando - orld orld - orleans orlean - ornament ornament - ornaments ornament - orodes orod - orphan orphan - orphans orphan - orpheus orpheu - orsino orsino - ort ort - orthography orthographi - orts ort - oscorbidulchos oscorbidulcho - osier osier - osiers osier - osprey osprei - osr osr - osric osric - ossa ossa - ost ost - ostent ostent - ostentare ostentar - ostentation ostent - ostents ostent - ostler ostler - ostlers ostler - ostrich ostrich - osw osw - oswald oswald - othello othello - other other - othergates otherg - others other - otherwhere otherwher - otherwhiles otherwhil - otherwise otherwis - otter otter - ottoman ottoman - ottomites ottomit - oublie oubli - ouches ouch - ought ought - oui oui - ounce ounc - ounces ounc - ouphes ouph - our our - ours our - ourself ourself - ourselves ourselv - ousel ousel - out out - outbids outbid - outbrave outbrav - outbraves outbrav - outbreak outbreak - outcast outcast - outcries outcri - outcry outcri - outdar outdar - outdare outdar - outdares outdar - outdone outdon - outfac outfac - outface outfac - outfaced outfac - outfacing outfac - outfly outfli - outfrown outfrown - outgo outgo - outgoes outgo - outgrown outgrown - outjest outjest - outlaw outlaw - outlawry outlawri - outlaws outlaw - outliv outliv - outlive outliv - outlives outliv - outliving outliv - outlook outlook - outlustres outlustr - outpriz outpriz - outrage outrag - outrageous outrag - outrages outrag - outran outran - outright outright - outroar outroar - outrun outrun - outrunning outrun - outruns outrun - outscold outscold - outscorn outscorn - outsell outsel - outsells outsel - outside outsid - outsides outsid - outspeaks outspeak - outsport outsport - outstare outstar - outstay outstai - outstood outstood - outstretch outstretch - outstretched outstretch - outstrike outstrik - outstrip outstrip - outstripped outstrip - outswear outswear - outvenoms outvenom - outward outward - outwardly outwardli - outwards outward - outwear outwear - outweighs outweigh - outwent outwent - outworn outworn - outworths outworth - oven oven - over over - overawe overaw - overbear overbear - overblown overblown - overboard overboard - overbold overbold - overborne overborn - overbulk overbulk - overbuys overbui - overcame overcam - overcast overcast - overcharg overcharg - overcharged overcharg - overcome overcom - overcomes overcom - overdone overdon - overearnest overearnest - overfar overfar - overflow overflow - overflown overflown - overglance overgl - overgo overgo - overgone overgon - overgorg overgorg - overgrown overgrown - overhead overhead - overhear overhear - overheard overheard - overhold overhold - overjoyed overjoi - overkind overkind - overland overland - overleather overleath - overlive overl - overlook overlook - overlooking overlook - overlooks overlook - overmaster overmast - overmounting overmount - overmuch overmuch - overpass overpass - overpeer overp - overpeering overp - overplus overplu - overrul overrul - overrun overrun - overscutch overscutch - overset overset - overshades overshad - overshine overshin - overshines overshin - overshot overshot - oversights oversight - overspread overspread - overstain overstain - overswear overswear - overt overt - overta overta - overtake overtak - overtaketh overtaketh - overthrow overthrow - overthrown overthrown - overthrows overthrow - overtook overtook - overtopp overtopp - overture overtur - overturn overturn - overwatch overwatch - overween overween - overweening overween - overweigh overweigh - overwhelm overwhelm - overwhelming overwhelm - overworn overworn - ovid ovid - ovidius ovidiu - ow ow - owe ow - owed ow - owedst owedst - owen owen - owes ow - owest owest - oweth oweth - owing ow - owl owl - owls owl - own own - owner owner - owners owner - owning own - owns own - owy owi - ox ox - oxen oxen - oxford oxford - oxfordshire oxfordshir - oxlips oxlip - oyes oy - oyster oyster - p p - pabble pabbl - pabylon pabylon - pac pac - pace pace - paced pace - paces pace - pacified pacifi - pacify pacifi - pacing pace - pack pack - packet packet - packets packet - packhorses packhors - packing pack - packings pack - packs pack - packthread packthread - pacorus pacoru - paction paction - pad pad - paddle paddl - paddling paddl - paddock paddock - padua padua - pagan pagan - pagans pagan - page page - pageant pageant - pageants pageant - pages page - pah pah - paid paid - pail pail - pailfuls pail - pails pail - pain pain - pained pain - painful pain - painfully painfulli - pains pain - paint paint - painted paint - painter painter - painting paint - paintings paint - paints paint - pair pair - paired pair - pairs pair - pajock pajock - pal pal - palabras palabra - palace palac - palaces palac - palamedes palamed - palate palat - palates palat - palatine palatin - palating palat - pale pale - paled pale - paleness pale - paler paler - pales pale - palestine palestin - palfrey palfrei - palfreys palfrei - palisadoes palisado - pall pall - pallabris pallabri - pallas palla - pallets pallet - palm palm - palmer palmer - palmers palmer - palms palm - palmy palmi - palpable palpabl - palsied palsi - palsies palsi - palsy palsi - palt palt - palter palter - paltry paltri - paly pali - pamp pamp - pamper pamper - pamphlets pamphlet - pan pan - pancackes pancack - pancake pancak - pancakes pancak - pandar pandar - pandars pandar - pandarus pandaru - pander pander - panderly panderli - panders pander - pandulph pandulph - panel panel - pang pang - panging pang - pangs pang - pannier pannier - pannonians pannonian - pansa pansa - pansies pansi - pant pant - pantaloon pantaloon - panted pant - pantheon pantheon - panther panther - panthino panthino - panting pant - pantingly pantingli - pantler pantler - pantry pantri - pants pant - pap pap - papal papal - paper paper - papers paper - paphlagonia paphlagonia - paphos papho - papist papist - paps pap - par par - parable parabl - paracelsus paracelsu - paradise paradis - paradox paradox - paradoxes paradox - paragon paragon - paragons paragon - parallel parallel - parallels parallel - paramour paramour - paramours paramour - parapets parapet - paraquito paraquito - parasite parasit - parasites parasit - parca parca - parcel parcel - parcell parcel - parcels parcel - parch parch - parched parch - parching parch - parchment parchment - pard pard - pardon pardon - pardona pardona - pardoned pardon - pardoner pardon - pardoning pardon - pardonne pardonn - pardonner pardonn - pardonnez pardonnez - pardons pardon - pare pare - pared pare - parel parel - parent parent - parentage parentag - parents parent - parfect parfect - paring pare - parings pare - paris pari - parish parish - parishioners parishion - parisians parisian - paritors paritor - park park - parks park - parle parl - parler parler - parles parl - parley parlei - parlez parlez - parliament parliament - parlors parlor - parlour parlour - parlous parlou - parmacity parmac - parolles parol - parricide parricid - parricides parricid - parrot parrot - parrots parrot - parsley parslei - parson parson - part part - partake partak - partaken partaken - partaker partak - partakers partak - parted part - parthia parthia - parthian parthian - parthians parthian - parti parti - partial partial - partialize partial - partially partial - participate particip - participation particip - particle particl - particular particular - particularities particular - particularize particular - particularly particularli - particulars particular - parties parti - parting part - partisan partisan - partisans partisan - partition partit - partizan partizan - partlet partlet - partly partli - partner partner - partners partner - partridge partridg - parts part - party parti - pas pa - pash pash - pashed pash - pashful pash - pass pass - passable passabl - passado passado - passage passag - passages passag - passant passant - passed pass - passenger passeng - passengers passeng - passes pass - passeth passeth - passing pass - passio passio - passion passion - passionate passion - passioning passion - passions passion - passive passiv - passport passport - passy passi - past past - paste past - pasterns pastern - pasties pasti - pastime pastim - pastimes pastim - pastoral pastor - pastorals pastor - pastors pastor - pastry pastri - pasture pastur - pastures pastur - pasty pasti - pat pat - patay patai - patch patch - patchery patcheri - patches patch - pate pate - pated pate - patent patent - patents patent - paternal patern - pates pate - path path - pathetical pathet - paths path - pathway pathwai - pathways pathwai - patience patienc - patient patient - patiently patient - patients patient - patines patin - patrician patrician - patricians patrician - patrick patrick - patrimony patrimoni - patroclus patroclu - patron patron - patronage patronag - patroness patro - patrons patron - patrum patrum - patter patter - pattern pattern - patterns pattern - pattle pattl - pauca pauca - paucas pauca - paul paul - paulina paulina - paunch paunch - paunches paunch - pause paus - pauser pauser - pauses paus - pausingly pausingli - pauvres pauvr - pav pav - paved pave - pavement pavement - pavilion pavilion - pavilions pavilion - pavin pavin - paw paw - pawn pawn - pawns pawn - paws paw - pax pax - pay pai - payest payest - paying pai - payment payment - payments payment - pays pai - paysan paysan - paysans paysan - pe pe - peace peac - peaceable peaceabl - peaceably peaceabl - peaceful peac - peacemakers peacemak - peaces peac - peach peach - peaches peach - peacock peacock - peacocks peacock - peak peak - peaking peak - peal peal - peals peal - pear pear - peard peard - pearl pearl - pearls pearl - pears pear - peas pea - peasant peasant - peasantry peasantri - peasants peasant - peascod peascod - pease peas - peaseblossom peaseblossom - peat peat - peaten peaten - peating peat - pebble pebbl - pebbled pebbl - pebbles pebbl - peck peck - pecks peck - peculiar peculiar - pecus pecu - pedant pedant - pedantical pedant - pedascule pedascul - pede pede - pedestal pedest - pedigree pedigre - pedlar pedlar - pedlars pedlar - pedro pedro - peds ped - peel peel - peep peep - peeped peep - peeping peep - peeps peep - peer peer - peereth peereth - peering peer - peerless peerless - peers peer - peesel peesel - peevish peevish - peevishly peevishli - peflur peflur - peg peg - pegasus pegasu - pegs peg - peise peis - peised peis - peize peiz - pelf pelf - pelican pelican - pelion pelion - pell pell - pella pella - pelleted pellet - peloponnesus peloponnesu - pelt pelt - pelting pelt - pembroke pembrok - pen pen - penalties penalti - penalty penalti - penance penanc - pence penc - pencil pencil - pencill pencil - pencils pencil - pendant pendant - pendent pendent - pendragon pendragon - pendulous pendul - penelope penelop - penetrable penetr - penetrate penetr - penetrative penetr - penitence penit - penitent penit - penitential penitenti - penitently penit - penitents penit - penker penker - penknife penknif - penn penn - penned pen - penning pen - pennons pennon - penny penni - pennyworth pennyworth - pennyworths pennyworth - pens pen - pense pens - pension pension - pensioners pension - pensive pensiv - pensived pensiv - pensively pensiv - pent pent - pentecost pentecost - penthesilea penthesilea - penthouse penthous - penurious penuri - penury penuri - peopl peopl - people peopl - peopled peopl - peoples peopl - pepin pepin - pepper pepper - peppercorn peppercorn - peppered pepper - per per - peradventure peradventur - peradventures peradventur - perceiv perceiv - perceive perceiv - perceived perceiv - perceives perceiv - perceiveth perceiveth - perch perch - perchance perchanc - percies perci - percussion percuss - percy perci - perdie perdi - perdita perdita - perdition perdit - perdonato perdonato - perdu perdu - perdurable perdur - perdurably perdur - perdy perdi - pere pere - peregrinate peregrin - peremptorily peremptorili - peremptory peremptori - perfect perfect - perfected perfect - perfecter perfect - perfectest perfectest - perfection perfect - perfections perfect - perfectly perfectli - perfectness perfect - perfidious perfidi - perfidiously perfidi - perforce perforc - perform perform - performance perform - performances perform - performed perform - performer perform - performers perform - performing perform - performs perform - perfum perfum - perfume perfum - perfumed perfum - perfumer perfum - perfumes perfum - perge perg - perhaps perhap - periapts periapt - perigort perigort - perigouna perigouna - peril peril - perilous peril - perils peril - period period - periods period - perish perish - perished perish - perishest perishest - perisheth perisheth - perishing perish - periwig periwig - perjur perjur - perjure perjur - perjured perjur - perjuries perjuri - perjury perjuri - perk perk - perkes perk - permafoy permafoi - permanent perman - permission permiss - permissive permiss - permit permit - permitted permit - pernicious pernici - perniciously pernici - peroration peror - perpend perpend - perpendicular perpendicular - perpendicularly perpendicularli - perpetual perpetu - perpetually perpetu - perpetuity perpetu - perplex perplex - perplexed perplex - perplexity perplex - pers per - persecuted persecut - persecutions persecut - persecutor persecutor - perseus perseu - persever persev - perseverance persever - persevers persev - persia persia - persian persian - persist persist - persisted persist - persistency persist - persistive persist - persists persist - person person - personae persona - personage personag - personages personag - personal person - personally person - personate person - personated person - personates person - personating person - persons person - perspective perspect - perspectively perspect - perspectives perspect - perspicuous perspicu - persuade persuad - persuaded persuad - persuades persuad - persuading persuad - persuasion persuas - persuasions persuas - pert pert - pertain pertain - pertaining pertain - pertains pertain - pertaunt pertaunt - pertinent pertin - pertly pertli - perturb perturb - perturbation perturb - perturbations perturb - perturbed perturb - perus peru - perusal perus - peruse perus - perused perus - perusing perus - perverse pervers - perversely pervers - perverseness pervers - pervert pervert - perverted pervert - peseech peseech - pest pest - pester pester - pestiferous pestifer - pestilence pestil - pestilent pestil - pet pet - petar petar - peter peter - petit petit - petition petit - petitionary petitionari - petitioner petition - petitioners petition - petitions petit - peto peto - petrarch petrarch - petruchio petruchio - petter petter - petticoat petticoat - petticoats petticoat - pettiness petti - pettish pettish - pettitoes pettito - petty petti - peu peu - pew pew - pewter pewter - pewterer pewter - phaethon phaethon - phaeton phaeton - phantasime phantasim - phantasimes phantasim - phantasma phantasma - pharamond pharamond - pharaoh pharaoh - pharsalia pharsalia - pheasant pheasant - pheazar pheazar - phebe phebe - phebes phebe - pheebus pheebu - pheeze pheez - phibbus phibbu - philadelphos philadelpho - philario philario - philarmonus philarmonu - philemon philemon - philip philip - philippan philippan - philippe philipp - philippi philippi - phillida phillida - philo philo - philomel philomel - philomela philomela - philosopher philosoph - philosophers philosoph - philosophical philosoph - philosophy philosophi - philostrate philostr - philotus philotu - phlegmatic phlegmat - phoebe phoeb - phoebus phoebu - phoenicia phoenicia - phoenicians phoenician - phoenix phoenix - phorbus phorbu - photinus photinu - phrase phrase - phraseless phraseless - phrases phrase - phrygia phrygia - phrygian phrygian - phrynia phrynia - physic physic - physical physic - physician physician - physicians physician - physics physic - pia pia - pibble pibbl - pible pibl - picardy picardi - pick pick - pickaxe pickax - pickaxes pickax - pickbone pickbon - picked pick - pickers picker - picking pick - pickle pickl - picklock picklock - pickpurse pickpurs - picks pick - pickt pickt - pickthanks pickthank - pictur pictur - picture pictur - pictured pictur - pictures pictur - pid pid - pie pie - piec piec - piece piec - pieces piec - piecing piec - pied pi - piedness pied - pier pier - pierc pierc - pierce pierc - pierced pierc - pierces pierc - pierceth pierceth - piercing pierc - piercy pierci - piers pier - pies pi - piety pieti - pig pig - pigeon pigeon - pigeons pigeon - pight pight - pigmy pigmi - pigrogromitus pigrogromitu - pike pike - pikes pike - pil pil - pilate pilat - pilates pilat - pilchers pilcher - pile pile - piles pile - pilf pilf - pilfering pilfer - pilgrim pilgrim - pilgrimage pilgrimag - pilgrims pilgrim - pill pill - pillage pillag - pillagers pillag - pillar pillar - pillars pillar - pillicock pillicock - pillory pillori - pillow pillow - pillows pillow - pills pill - pilot pilot - pilots pilot - pimpernell pimpernel - pin pin - pinch pinch - pinched pinch - pinches pinch - pinching pinch - pindarus pindaru - pine pine - pined pine - pines pine - pinfold pinfold - pining pine - pinion pinion - pink pink - pinn pinn - pinnace pinnac - pins pin - pinse pins - pint pint - pintpot pintpot - pioned pion - pioneers pioneer - pioner pioner - pioners pioner - pious piou - pip pip - pipe pipe - piper piper - pipers piper - pipes pipe - piping pipe - pippin pippin - pippins pippin - pirate pirat - pirates pirat - pisa pisa - pisanio pisanio - pish pish - pismires pismir - piss piss - pissing piss - pistol pistol - pistols pistol - pit pit - pitch pitch - pitched pitch - pitcher pitcher - pitchers pitcher - pitchy pitchi - piteous piteou - piteously piteous - pitfall pitfal - pith pith - pithless pithless - pithy pithi - pitie piti - pitied piti - pities piti - pitiful piti - pitifully pitifulli - pitiless pitiless - pits pit - pittance pittanc - pittie pitti - pittikins pittikin - pity piti - pitying piti - pius piu - plac plac - place place - placed place - placentio placentio - places place - placeth placeth - placid placid - placing place - plack plack - placket placket - plackets placket - plagu plagu - plague plagu - plagued plagu - plagues plagu - plaguing plagu - plaguy plagui - plain plain - plainer plainer - plainest plainest - plaining plain - plainings plain - plainly plainli - plainness plain - plains plain - plainsong plainsong - plaintful plaint - plaintiff plaintiff - plaintiffs plaintiff - plaints plaint - planched planch - planet planet - planetary planetari - planets planet - planks plank - plant plant - plantage plantag - plantagenet plantagenet - plantagenets plantagenet - plantain plantain - plantation plantat - planted plant - planteth planteth - plants plant - plash plash - plashy plashi - plast plast - plaster plaster - plasterer plaster - plat plat - plate plate - plated plate - plates plate - platform platform - platforms platform - plats plat - platted plat - plausible plausibl - plausive plausiv - plautus plautu - play plai - played plai - player player - players player - playeth playeth - playfellow playfellow - playfellows playfellow - playhouse playhous - playing plai - plays plai - plea plea - pleach pleach - pleached pleach - plead plead - pleaded plead - pleader pleader - pleaders pleader - pleading plead - pleads plead - pleas plea - pleasance pleasanc - pleasant pleasant - pleasantly pleasantli - please pleas - pleased pleas - pleaser pleaser - pleasers pleaser - pleases pleas - pleasest pleasest - pleaseth pleaseth - pleasing pleas - pleasure pleasur - pleasures pleasur - plebeians plebeian - plebeii plebeii - plebs pleb - pledge pledg - pledges pledg - pleines plein - plenitude plenitud - plenteous plenteou - plenteously plenteous - plenties plenti - plentiful plenti - plentifully plentifulli - plenty plenti - pless pless - plessed pless - plessing pless - pliant pliant - plied pli - plies pli - plight plight - plighted plight - plighter plighter - plod plod - plodded plod - plodders plodder - plodding plod - plods plod - plood plood - ploody ploodi - plot plot - plots plot - plotted plot - plotter plotter - plough plough - ploughed plough - ploughman ploughman - ploughmen ploughmen - plow plow - plows plow - pluck pluck - plucked pluck - plucker plucker - plucking pluck - plucks pluck - plue plue - plum plum - plume plume - plumed plume - plumes plume - plummet plummet - plump plump - plumpy plumpi - plums plum - plung plung - plunge plung - plunged plung - plural plural - plurisy plurisi - plus plu - pluto pluto - plutus plutu - ply ply - po po - pocket pocket - pocketing pocket - pockets pocket - pocky pocki - pody podi - poem poem - poesy poesi - poet poet - poetical poetic - poetry poetri - poets poet - poictiers poictier - poinards poinard - poins poin - point point - pointblank pointblank - pointed point - pointing point - points point - pois poi - poise pois - poising pois - poison poison - poisoned poison - poisoner poison - poisoning poison - poisonous poison - poisons poison - poke poke - poking poke - pol pol - polack polack - polacks polack - poland poland - pold pold - pole pole - poleaxe poleax - polecat polecat - polecats polecat - polemon polemon - poles pole - poli poli - policies polici - policy polici - polish polish - polished polish - politic polit - politician politician - politicians politician - politicly politicli - polixenes polixen - poll poll - polluted pollut - pollution pollut - polonius poloniu - poltroons poltroon - polusion polus - polydamus polydamu - polydore polydor - polyxena polyxena - pomander pomand - pomegranate pomegran - pomewater pomewat - pomfret pomfret - pomgarnet pomgarnet - pommel pommel - pomp pomp - pompeius pompeiu - pompey pompei - pompion pompion - pompous pompou - pomps pomp - pond pond - ponder ponder - ponderous ponder - ponds pond - poniard poniard - poniards poniard - pont pont - pontic pontic - pontifical pontif - ponton ponton - pooh pooh - pool pool - poole pool - poop poop - poor poor - poorer poorer - poorest poorest - poorly poorli - pop pop - pope pope - popedom popedom - popilius popiliu - popingay popingai - popish popish - popp popp - poppy poppi - pops pop - popular popular - popularity popular - populous popul - porch porch - porches porch - pore pore - poring pore - pork pork - porn porn - porpentine porpentin - porridge porridg - porringer porring - port port - portable portabl - portage portag - portal portal - portance portanc - portcullis portculli - portend portend - portends portend - portent portent - portentous portent - portents portent - porter porter - porters porter - portia portia - portion portion - portly portli - portotartarossa portotartarossa - portrait portrait - portraiture portraitur - ports port - portugal portug - pose pose - posied posi - posies posi - position posit - positive posit - positively posit - posse poss - possess possess - possessed possess - possesses possess - possesseth possesseth - possessing possess - possession possess - possessions possess - possessor possessor - posset posset - possets posset - possibilities possibl - possibility possibl - possible possibl - possibly possibl - possitable possit - post post - poste post - posted post - posterior posterior - posteriors posterior - posterity poster - postern postern - posterns postern - posters poster - posthorse posthors - posthorses posthors - posthumus posthumu - posting post - postmaster postmast - posts post - postscript postscript - posture postur - postures postur - posy posi - pot pot - potable potabl - potations potat - potato potato - potatoes potato - potch potch - potency potenc - potent potent - potentates potent - potential potenti - potently potent - potents potent - pothecary pothecari - pother pother - potion potion - potions potion - potpan potpan - pots pot - potter potter - potting pot - pottle pottl - pouch pouch - poulter poulter - poultice poultic - poultney poultnei - pouncet pouncet - pound pound - pounds pound - pour pour - pourest pourest - pouring pour - pourquoi pourquoi - pours pour - pout pout - poverty poverti - pow pow - powd powd - powder powder - power power - powerful power - powerfully powerfulli - powerless powerless - powers power - pox pox - poys poi - poysam poysam - prabbles prabbl - practic practic - practice practic - practiced practic - practicer practic - practices practic - practicing practic - practis practi - practisants practis - practise practis - practiser practis - practisers practis - practises practis - practising practis - praeclarissimus praeclarissimu - praemunire praemunir - praetor praetor - praetors praetor - pragging prag - prague pragu - prain prain - prains prain - prais prai - praise prais - praised prais - praises prais - praisest praisest - praiseworthy praiseworthi - praising prais - prancing pranc - prank prank - pranks prank - prat prat - prate prate - prated prate - prater prater - prating prate - prattle prattl - prattler prattler - prattling prattl - prave prave - prawls prawl - prawns prawn - pray prai - prayer prayer - prayers prayer - praying prai - prays prai - pre pre - preach preach - preached preach - preachers preacher - preaches preach - preaching preach - preachment preachment - pread pread - preambulate preambul - precedence preced - precedent preced - preceding preced - precept precept - preceptial precepti - precepts precept - precinct precinct - precious preciou - preciously precious - precipice precipic - precipitating precipit - precipitation precipit - precise precis - precisely precis - preciseness precis - precisian precisian - precor precor - precurse precurs - precursors precursor - predeceased predeceas - predecessor predecessor - predecessors predecessor - predestinate predestin - predicament predica - predict predict - prediction predict - predictions predict - predominance predomin - predominant predomin - predominate predomin - preeches preech - preeminence preemin - preface prefac - prefer prefer - preferment prefer - preferments prefer - preferr preferr - preferreth preferreth - preferring prefer - prefers prefer - prefiguring prefigur - prefix prefix - prefixed prefix - preformed preform - pregnancy pregnanc - pregnant pregnant - pregnantly pregnantli - prejudicates prejud - prejudice prejudic - prejudicial prejudici - prelate prelat - premeditated premedit - premeditation premedit - premised premis - premises premis - prenez prenez - prenominate prenomin - prentice prentic - prentices prentic - preordinance preordin - prepar prepar - preparation prepar - preparations prepar - prepare prepar - prepared prepar - preparedly preparedli - prepares prepar - preparing prepar - prepost prepost - preposterous preposter - preposterously preposter - prerogatifes prerogatif - prerogative prerog - prerogatived prerogativ - presage presag - presagers presag - presages presag - presageth presageth - presaging presag - prescience prescienc - prescribe prescrib - prescript prescript - prescription prescript - prescriptions prescript - prescripts prescript - presence presenc - presences presenc - present present - presentation present - presented present - presenter present - presenters present - presenteth presenteth - presenting present - presently present - presentment present - presents present - preserv preserv - preservation preserv - preservative preserv - preserve preserv - preserved preserv - preserver preserv - preservers preserv - preserving preserv - president presid - press press - pressed press - presser presser - presses press - pressing press - pressure pressur - pressures pressur - prest prest - prester prester - presume presum - presumes presum - presuming presum - presumption presumpt - presumptuous presumptu - presuppos presuppo - pret pret - pretence pretenc - pretences pretenc - pretend pretend - pretended pretend - pretending pretend - pretense pretens - pretext pretext - pretia pretia - prettier prettier - prettiest prettiest - prettily prettili - prettiness pretti - pretty pretti - prevail prevail - prevailed prevail - prevaileth prevaileth - prevailing prevail - prevailment prevail - prevails prevail - prevent prevent - prevented prevent - prevention prevent - preventions prevent - prevents prevent - prey prei - preyful prey - preys prei - priam priam - priami priami - priamus priamu - pribbles pribbl - price price - prick prick - pricked prick - pricket pricket - pricking prick - pricks prick - pricksong pricksong - pride pride - prides pride - pridge pridg - prie prie - pried pri - prief prief - pries pri - priest priest - priesthood priesthood - priests priest - prig prig - primal primal - prime prime - primer primer - primero primero - primest primest - primitive primit - primo primo - primogenity primogen - primrose primros - primroses primros - primy primi - prince princ - princely princ - princes princ - princess princess - principal princip - principalities princip - principality princip - principle principl - principles principl - princox princox - prings pring - print print - printed print - printing print - printless printless - prints print - prioress prioress - priories priori - priority prioriti - priory priori - priscian priscian - prison prison - prisoner prison - prisoners prison - prisonment prison - prisonnier prisonni - prisons prison - pristine pristin - prithe prith - prithee prithe - privacy privaci - private privat - privately privat - privates privat - privilage privilag - privileg privileg - privilege privileg - privileged privileg - privileges privileg - privilegio privilegio - privily privili - privity priviti - privy privi - priz priz - prize prize - prized prize - prizer prizer - prizes prize - prizest prizest - prizing prize - pro pro - probable probabl - probal probal - probation probat - proceed proce - proceeded proceed - proceeders proceed - proceeding proceed - proceedings proceed - proceeds proce - process process - procession process - proclaim proclaim - proclaimed proclaim - proclaimeth proclaimeth - proclaims proclaim - proclamation proclam - proclamations proclam - proconsul proconsul - procrastinate procrastin - procreant procreant - procreants procreant - procreation procreat - procrus procru - proculeius proculeiu - procur procur - procurator procur - procure procur - procured procur - procures procur - procuring procur - prodigal prodig - prodigality prodig - prodigally prodig - prodigals prodig - prodigies prodigi - prodigious prodigi - prodigiously prodigi - prodigy prodigi - proditor proditor - produc produc - produce produc - produced produc - produces produc - producing produc - proface profac - profan profan - profanation profan - profane profan - profaned profan - profanely profan - profaneness profan - profaners profan - profaning profan - profess profess - professed profess - professes profess - profession profess - professions profess - professors professor - proffer proffer - proffered proffer - profferer proffer - proffers proffer - proficient profici - profit profit - profitable profit - profitably profit - profited profit - profiting profit - profitless profitless - profits profit - profound profound - profoundest profoundest - profoundly profoundli - progenitors progenitor - progeny progeni - progne progn - prognosticate prognost - prognostication prognost - progress progress - progression progress - prohibit prohibit - prohibition prohibit - project project - projection project - projects project - prolixious prolixi - prolixity prolix - prologue prologu - prologues prologu - prolong prolong - prolongs prolong - promethean promethean - prometheus prometheu - promis promi - promise promis - promised promis - promises promis - promiseth promiseth - promising promis - promontory promontori - promotion promot - promotions promot - prompt prompt - prompted prompt - promptement promptement - prompter prompter - prompting prompt - prompts prompt - prompture promptur - promulgate promulg - prone prone - prononcer prononc - prononcez prononcez - pronoun pronoun - pronounc pronounc - pronounce pronounc - pronounced pronounc - pronouncing pronounc - pronouns pronoun - proof proof - proofs proof - prop prop - propagate propag - propagation propag - propend propend - propension propens - proper proper - properer proper - properly properli - propertied properti - properties properti - property properti - prophecies propheci - prophecy propheci - prophesied prophesi - prophesier prophesi - prophesy prophesi - prophesying prophesi - prophet prophet - prophetess prophetess - prophetic prophet - prophetically prophet - prophets prophet - propinquity propinqu - propontic propont - proportion proport - proportionable proportion - proportions proport - propos propo - propose propos - proposed propos - proposer propos - proposes propos - proposing propos - proposition proposit - propositions proposit - propounded propound - propp propp - propre propr - propriety proprieti - props prop - propugnation propugn - prorogue prorogu - prorogued prorogu - proscription proscript - proscriptions proscript - prose prose - prosecute prosecut - prosecution prosecut - proselytes proselyt - proserpina proserpina - prosp prosp - prospect prospect - prosper prosper - prosperity prosper - prospero prospero - prosperous prosper - prosperously prosper - prospers prosper - prostitute prostitut - prostrate prostrat - protect protect - protected protect - protection protect - protector protector - protectors protector - protectorship protectorship - protectress protectress - protects protect - protest protest - protestation protest - protestations protest - protested protest - protester protest - protesting protest - protests protest - proteus proteu - protheus protheu - protract protract - protractive protract - proud proud - prouder prouder - proudest proudest - proudlier proudlier - proudly proudli - prouds proud - prov prov - provand provand - prove prove - proved prove - provender provend - proverb proverb - proverbs proverb - proves prove - proveth proveth - provide provid - provided provid - providence provid - provident provid - providently provid - provider provid - provides provid - province provinc - provinces provinc - provincial provinci - proving prove - provision provis - proviso proviso - provocation provoc - provok provok - provoke provok - provoked provok - provoker provok - provokes provok - provoketh provoketh - provoking provok - provost provost - prowess prowess - prudence prudenc - prudent prudent - prun prun - prune prune - prunes prune - pruning prune - pry pry - prying pry - psalm psalm - psalmist psalmist - psalms psalm - psalteries psalteri - ptolemies ptolemi - ptolemy ptolemi - public public - publican publican - publication public - publicly publicli - publicola publicola - publish publish - published publish - publisher publish - publishing publish - publius publiu - pucelle pucel - puck puck - pudder pudder - pudding pud - puddings pud - puddle puddl - puddled puddl - pudency pudenc - pueritia pueritia - puff puff - puffing puf - puffs puff - pugging pug - puis pui - puissance puissanc - puissant puissant - puke puke - puking puke - pulcher pulcher - puling pule - pull pull - puller puller - pullet pullet - pulling pull - pulls pull - pulpit pulpit - pulpiter pulpit - pulpits pulpit - pulse puls - pulsidge pulsidg - pump pump - pumpion pumpion - pumps pump - pun pun - punched punch - punish punish - punished punish - punishes punish - punishment punish - punishments punish - punk punk - punto punto - puny puni - pupil pupil - pupils pupil - puppet puppet - puppets puppet - puppies puppi - puppy puppi - pur pur - purblind purblind - purchas purcha - purchase purchas - purchased purchas - purchases purchas - purchaseth purchaseth - purchasing purchas - pure pure - purely pure - purer purer - purest purest - purg purg - purgation purgat - purgative purg - purgatory purgatori - purge purg - purged purg - purgers purger - purging purg - purifies purifi - purifying purifi - puritan puritan - purity puriti - purlieus purlieu - purple purpl - purpled purpl - purples purpl - purport purport - purpos purpo - purpose purpos - purposed purpos - purposely purpos - purposes purpos - purposeth purposeth - purposing purpos - purr purr - purs pur - purse purs - pursents pursent - purses purs - pursu pursu - pursue pursu - pursued pursu - pursuers pursuer - pursues pursu - pursuest pursuest - pursueth pursueth - pursuing pursu - pursuit pursuit - pursuivant pursuiv - pursuivants pursuiv - pursy pursi - purus puru - purveyor purveyor - push push - pushes push - pusillanimity pusillanim - put put - putrefy putrefi - putrified putrifi - puts put - putter putter - putting put - puttock puttock - puzzel puzzel - puzzle puzzl - puzzled puzzl - puzzles puzzl - py py - pygmalion pygmalion - pygmies pygmi - pygmy pygmi - pyramid pyramid - pyramides pyramid - pyramids pyramid - pyramis pyrami - pyramises pyramis - pyramus pyramu - pyrenean pyrenean - pyrrhus pyrrhu - pythagoras pythagora - qu qu - quadrangle quadrangl - quae quae - quaff quaff - quaffing quaf - quagmire quagmir - quail quail - quailing quail - quails quail - quaint quaint - quaintly quaintli - quak quak - quake quak - quakes quak - qualification qualif - qualified qualifi - qualifies qualifi - qualify qualifi - qualifying qualifi - qualite qualit - qualities qualiti - quality qualiti - qualm qualm - qualmish qualmish - quam quam - quand quand - quando quando - quantities quantiti - quantity quantiti - quare quar - quarrel quarrel - quarrell quarrel - quarreller quarrel - quarrelling quarrel - quarrelous quarrel - quarrels quarrel - quarrelsome quarrelsom - quarries quarri - quarry quarri - quart quart - quarter quarter - quartered quarter - quartering quarter - quarters quarter - quarts quart - quasi quasi - quat quat - quatch quatch - quay quai - que que - quean quean - queas quea - queasiness queasi - queasy queasi - queen queen - queens queen - quell quell - queller queller - quench quench - quenched quench - quenching quench - quenchless quenchless - quern quern - quest quest - questant questant - question question - questionable question - questioned question - questioning question - questionless questionless - questions question - questrists questrist - quests quest - queubus queubu - qui qui - quick quick - quicken quicken - quickens quicken - quicker quicker - quicklier quicklier - quickly quickli - quickness quick - quicksand quicksand - quicksands quicksand - quicksilverr quicksilverr - quid quid - quiddities quidditi - quiddits quiddit - quier quier - quiet quiet - quieter quieter - quietly quietli - quietness quiet - quietus quietu - quill quill - quillets quillet - quills quill - quilt quilt - quinapalus quinapalu - quince quinc - quinces quinc - quintain quintain - quintessence quintess - quintus quintu - quip quip - quips quip - quire quir - quiring quir - quirk quirk - quirks quirk - quis qui - quit quit - quite quit - quits quit - quittance quittanc - quitted quit - quitting quit - quiver quiver - quivering quiver - quivers quiver - quo quo - quod quod - quoifs quoif - quoint quoint - quoit quoit - quoits quoit - quondam quondam - quoniam quoniam - quote quot - quoted quot - quotes quot - quoth quoth - quotidian quotidian - r r - rabbit rabbit - rabble rabbl - rabblement rabblement - race race - rack rack - rackers racker - racket racket - rackets racket - racking rack - racks rack - radiance radianc - radiant radiant - radish radish - rafe rafe - raft raft - rag rag - rage rage - rages rage - rageth rageth - ragg ragg - ragged rag - raggedness ragged - raging rage - ragozine ragozin - rags rag - rah rah - rail rail - railed rail - railer railer - railest railest - raileth raileth - railing rail - rails rail - raiment raiment - rain rain - rainbow rainbow - raineth raineth - raining rain - rainold rainold - rains rain - rainy raini - rais rai - raise rais - raised rais - raises rais - raising rais - raisins raisin - rak rak - rake rake - rakers raker - rakes rake - ral ral - rald rald - ralph ralph - ram ram - rambures rambur - ramm ramm - rampallian rampallian - rampant rampant - ramping ramp - rampir rampir - ramps ramp - rams ram - ramsey ramsei - ramston ramston - ran ran - rance ranc - rancorous rancor - rancors rancor - rancour rancour - random random - rang rang - range rang - ranged rang - rangers ranger - ranges rang - ranging rang - rank rank - ranker ranker - rankest rankest - ranking rank - rankle rankl - rankly rankli - rankness rank - ranks rank - ransack ransack - ransacking ransack - ransom ransom - ransomed ransom - ransoming ransom - ransomless ransomless - ransoms ransom - rant rant - ranting rant - rap rap - rape rape - rapes rape - rapier rapier - rapiers rapier - rapine rapin - raps rap - rapt rapt - rapture raptur - raptures raptur - rar rar - rare rare - rarely rare - rareness rare - rarer rarer - rarest rarest - rarities rariti - rarity rariti - rascal rascal - rascalliest rascalliest - rascally rascal - rascals rascal - rased rase - rash rash - rasher rasher - rashly rashli - rashness rash - rat rat - ratcatcher ratcatch - ratcliff ratcliff - rate rate - rated rate - rately rate - rates rate - rather rather - ratherest ratherest - ratified ratifi - ratifiers ratifi - ratify ratifi - rating rate - rational ration - ratolorum ratolorum - rats rat - ratsbane ratsban - rattle rattl - rattles rattl - rattling rattl - rature ratur - raught raught - rav rav - rave rave - ravel ravel - raven raven - ravening raven - ravenous raven - ravens raven - ravenspurgh ravenspurgh - raves rave - ravin ravin - raving rave - ravish ravish - ravished ravish - ravisher ravish - ravishing ravish - ravishments ravish - raw raw - rawer rawer - rawly rawli - rawness raw - ray rai - rayed rai - rays rai - raz raz - raze raze - razed raze - razes raze - razeth razeth - razing raze - razor razor - razorable razor - razors razor - razure razur - re re - reach reach - reaches reach - reacheth reacheth - reaching reach - read read - reader reader - readiest readiest - readily readili - readiness readi - reading read - readins readin - reads read - ready readi - real real - really realli - realm realm - realms realm - reap reap - reapers reaper - reaping reap - reaps reap - rear rear - rears rear - rearward rearward - reason reason - reasonable reason - reasonably reason - reasoned reason - reasoning reason - reasonless reasonless - reasons reason - reave reav - rebate rebat - rebato rebato - rebeck rebeck - rebel rebel - rebell rebel - rebelling rebel - rebellion rebellion - rebellious rebelli - rebels rebel - rebound rebound - rebuk rebuk - rebuke rebuk - rebukeable rebuk - rebuked rebuk - rebukes rebuk - rebus rebu - recall recal - recant recant - recantation recant - recanter recant - recanting recant - receipt receipt - receipts receipt - receiv receiv - receive receiv - received receiv - receiver receiv - receives receiv - receivest receivest - receiveth receiveth - receiving receiv - receptacle receptacl - rechate rechat - reciprocal reciproc - reciprocally reciproc - recite recit - recited recit - reciterai reciterai - reck reck - recking reck - reckless reckless - reckon reckon - reckoned reckon - reckoning reckon - reckonings reckon - recks reck - reclaim reclaim - reclaims reclaim - reclusive reclus - recognizance recogniz - recognizances recogniz - recoil recoil - recoiling recoil - recollected recollect - recomforted recomfort - recomforture recomfortur - recommend recommend - recommended recommend - recommends recommend - recompens recompen - recompense recompens - reconcil reconcil - reconcile reconcil - reconciled reconcil - reconcilement reconcil - reconciler reconcil - reconciles reconcil - reconciliation reconcili - record record - recordation record - recorded record - recorder record - recorders record - records record - recount recount - recounted recount - recounting recount - recountments recount - recounts recount - recourse recours - recov recov - recover recov - recoverable recover - recovered recov - recoveries recoveri - recovers recov - recovery recoveri - recreant recreant - recreants recreant - recreate recreat - recreation recreat - rectify rectifi - rector rector - rectorship rectorship - recure recur - recured recur - red red - redbreast redbreast - redder redder - reddest reddest - rede rede - redeem redeem - redeemed redeem - redeemer redeem - redeeming redeem - redeems redeem - redeliver redeliv - redemption redempt - redime redim - redness red - redoubled redoubl - redoubted redoubt - redound redound - redress redress - redressed redress - redresses redress - reduce reduc - reechy reechi - reed reed - reeds reed - reek reek - reeking reek - reeks reek - reeky reeki - reel reel - reeleth reeleth - reeling reel - reels reel - refell refel - refer refer - reference refer - referr referr - referred refer - refigured refigur - refin refin - refined refin - reflect reflect - reflecting reflect - reflection reflect - reflex reflex - reform reform - reformation reform - reformed reform - refractory refractori - refrain refrain - refresh refresh - refreshing refresh - reft reft - refts reft - refuge refug - refus refu - refusal refus - refuse refus - refused refus - refusest refusest - refusing refus - reg reg - regal regal - regalia regalia - regan regan - regard regard - regardance regard - regarded regard - regardfully regardfulli - regarding regard - regards regard - regenerate regener - regent regent - regentship regentship - regia regia - regiment regiment - regiments regiment - regina regina - region region - regions region - regist regist - register regist - registers regist - regreet regreet - regreets regreet - regress regress - reguerdon reguerdon - regular regular - rehears rehear - rehearsal rehears - rehearse rehears - reign reign - reigned reign - reignier reignier - reigning reign - reigns reign - rein rein - reinforc reinforc - reinforce reinforc - reinforcement reinforc - reins rein - reiterate reiter - reject reject - rejected reject - rejoic rejoic - rejoice rejoic - rejoices rejoic - rejoiceth rejoiceth - rejoicing rejoic - rejoicingly rejoicingli - rejoindure rejoindur - rejourn rejourn - rel rel - relapse relaps - relate relat - relates relat - relation relat - relations relat - relative rel - releas relea - release releas - released releas - releasing releas - relent relent - relenting relent - relents relent - reliances relianc - relics relic - relief relief - reliev reliev - relieve reliev - relieved reliev - relieves reliev - relieving reliev - religion religion - religions religion - religious religi - religiously religi - relinquish relinquish - reliques reliqu - reliquit reliquit - relish relish - relume relum - rely reli - relying reli - remain remain - remainder remaind - remainders remaind - remained remain - remaineth remaineth - remaining remain - remains remain - remark remark - remarkable remark - remediate remedi - remedied remedi - remedies remedi - remedy remedi - rememb rememb - remember rememb - remembered rememb - remembers rememb - remembrance remembr - remembrancer remembranc - remembrances remembr - remercimens remercimen - remiss remiss - remission remiss - remissness remiss - remit remit - remnant remnant - remnants remnant - remonstrance remonstr - remorse remors - remorseful remors - remorseless remorseless - remote remot - remotion remot - remov remov - remove remov - removed remov - removedness removed - remover remov - removes remov - removing remov - remunerate remuner - remuneration remuner - rence renc - rend rend - render render - rendered render - renders render - rendezvous rendezv - renegado renegado - renege reneg - reneges reneg - renew renew - renewed renew - renewest renewest - renounce renounc - renouncement renounc - renouncing renounc - renowmed renowm - renown renown - renowned renown - rent rent - rents rent - repaid repaid - repair repair - repaired repair - repairing repair - repairs repair - repass repass - repast repast - repasture repastur - repay repai - repaying repai - repays repai - repeal repeal - repealing repeal - repeals repeal - repeat repeat - repeated repeat - repeating repeat - repeats repeat - repel repel - repent repent - repentance repent - repentant repent - repented repent - repenting repent - repents repent - repetition repetit - repetitions repetit - repin repin - repine repin - repining repin - replant replant - replenish replenish - replenished replenish - replete replet - replication replic - replied repli - replies repli - repliest repliest - reply repli - replying repli - report report - reported report - reporter report - reportest reportest - reporting report - reportingly reportingli - reports report - reposal repos - repose repos - reposeth reposeth - reposing repos - repossess repossess - reprehend reprehend - reprehended reprehend - reprehending reprehend - represent repres - representing repres - reprieve repriev - reprieves repriev - reprisal repris - reproach reproach - reproaches reproach - reproachful reproach - reproachfully reproachfulli - reprobate reprob - reprobation reprob - reproof reproof - reprov reprov - reprove reprov - reproveable reprov - reproves reprov - reproving reprov - repugn repugn - repugnancy repugn - repugnant repugn - repulse repuls - repulsed repuls - repurchas repurcha - repured repur - reputation reput - repute reput - reputed reput - reputeless reputeless - reputes reput - reputing reput - request request - requested request - requesting request - requests request - requiem requiem - requir requir - require requir - required requir - requires requir - requireth requireth - requiring requir - requisite requisit - requisites requisit - requit requit - requital requit - requite requit - requited requit - requites requit - rer rer - rere rere - rers rer - rescu rescu - rescue rescu - rescued rescu - rescues rescu - rescuing rescu - resemblance resembl - resemble resembl - resembled resembl - resembles resembl - resembleth resembleth - resembling resembl - reserv reserv - reservation reserv - reserve reserv - reserved reserv - reserves reserv - reside resid - residence resid - resident resid - resides resid - residing resid - residue residu - resign resign - resignation resign - resist resist - resistance resist - resisted resist - resisting resist - resists resist - resolute resolut - resolutely resolut - resolutes resolut - resolution resolut - resolv resolv - resolve resolv - resolved resolv - resolvedly resolvedli - resolves resolv - resolveth resolveth - resort resort - resorted resort - resounding resound - resounds resound - respeaking respeak - respect respect - respected respect - respecting respect - respective respect - respectively respect - respects respect - respice respic - respite respit - respites respit - responsive respons - respose respos - ress ress - rest rest - rested rest - resteth resteth - restful rest - resting rest - restitution restitut - restless restless - restor restor - restoration restor - restorative restor - restore restor - restored restor - restores restor - restoring restor - restrain restrain - restrained restrain - restraining restrain - restrains restrain - restraint restraint - rests rest - resty resti - resum resum - resume resum - resumes resum - resurrections resurrect - retail retail - retails retail - retain retain - retainers retain - retaining retain - retell retel - retention retent - retentive retent - retinue retinu - retir retir - retire retir - retired retir - retirement retir - retires retir - retiring retir - retold retold - retort retort - retorts retort - retourne retourn - retract retract - retreat retreat - retrograde retrograd - rets ret - return return - returned return - returnest returnest - returneth returneth - returning return - returns return - revania revania - reveal reveal - reveals reveal - revel revel - reveler revel - revell revel - reveller revel - revellers revel - revelling revel - revelry revelri - revels revel - reveng reveng - revenge reveng - revenged reveng - revengeful reveng - revengement reveng - revenger reveng - revengers reveng - revenges reveng - revenging reveng - revengingly revengingli - revenue revenu - revenues revenu - reverb reverb - reverberate reverber - reverbs reverb - reverenc reverenc - reverence rever - reverend reverend - reverent rever - reverently rever - revers rever - reverse revers - reversion revers - reverted revert - review review - reviewest reviewest - revil revil - revile revil - revisits revisit - reviv reviv - revive reviv - revives reviv - reviving reviv - revok revok - revoke revok - revokement revok - revolt revolt - revolted revolt - revolting revolt - revolts revolt - revolution revolut - revolutions revolut - revolve revolv - revolving revolv - reward reward - rewarded reward - rewarder reward - rewarding reward - rewards reward - reword reword - reworded reword - rex rex - rey rei - reynaldo reynaldo - rford rford - rful rful - rfull rfull - rhapsody rhapsodi - rheims rheim - rhenish rhenish - rhesus rhesu - rhetoric rhetor - rheum rheum - rheumatic rheumat - rheums rheum - rheumy rheumi - rhinoceros rhinocero - rhodes rhode - rhodope rhodop - rhubarb rhubarb - rhym rhym - rhyme rhyme - rhymers rhymer - rhymes rhyme - rhyming rhyme - rialto rialto - rib rib - ribald ribald - riband riband - ribands riband - ribaudred ribaudr - ribb ribb - ribbed rib - ribbon ribbon - ribbons ribbon - ribs rib - rice rice - rich rich - richard richard - richer richer - riches rich - richest richest - richly richli - richmond richmond - richmonds richmond - rid rid - riddance riddanc - ridden ridden - riddle riddl - riddles riddl - riddling riddl - ride ride - rider rider - riders rider - rides ride - ridest ridest - rideth rideth - ridge ridg - ridges ridg - ridiculous ridicul - riding ride - rids rid - rien rien - ries ri - rifle rifl - rift rift - rifted rift - rig rig - rigg rigg - riggish riggish - right right - righteous righteou - righteously righteous - rightful right - rightfully rightfulli - rightly rightli - rights right - rigol rigol - rigorous rigor - rigorously rigor - rigour rigour - ril ril - rim rim - rin rin - rinaldo rinaldo - rind rind - ring ring - ringing ring - ringleader ringlead - ringlets ringlet - rings ring - ringwood ringwood - riot riot - rioter rioter - rioting riot - riotous riotou - riots riot - rip rip - ripe ripe - ripely ripe - ripen ripen - ripened ripen - ripeness ripe - ripening ripen - ripens ripen - riper riper - ripest ripest - riping ripe - ripp ripp - ripping rip - rise rise - risen risen - rises rise - riseth riseth - rish rish - rising rise - rite rite - rites rite - rivage rivag - rival rival - rivality rival - rivall rival - rivals rival - rive rive - rived rive - rivelled rivel - river river - rivers river - rivet rivet - riveted rivet - rivets rivet - rivo rivo - rj rj - rless rless - road road - roads road - roam roam - roaming roam - roan roan - roar roar - roared roar - roarers roarer - roaring roar - roars roar - roast roast - roasted roast - rob rob - roba roba - robas roba - robb robb - robbed rob - robber robber - robbers robber - robbery robberi - robbing rob - robe robe - robed robe - robert robert - robes robe - robin robin - robs rob - robustious robusti - rochester rochest - rochford rochford - rock rock - rocks rock - rocky rocki - rod rod - rode rode - roderigo roderigo - rods rod - roe roe - roes roe - roger roger - rogero rogero - rogue rogu - roguery rogueri - rogues rogu - roguish roguish - roi roi - roisting roist - roll roll - rolled roll - rolling roll - rolls roll - rom rom - romage romag - roman roman - romano romano - romanos romano - romans roman - rome rome - romeo romeo - romish romish - rondure rondur - ronyon ronyon - rood rood - roof roof - roofs roof - rook rook - rooks rook - rooky rooki - room room - rooms room - root root - rooted root - rootedly rootedli - rooteth rooteth - rooting root - roots root - rope rope - ropery roperi - ropes rope - roping rope - ros ro - rosalind rosalind - rosalinda rosalinda - rosalinde rosalind - rosaline rosalin - roscius rosciu - rose rose - rosed rose - rosemary rosemari - rosencrantz rosencrantz - roses rose - ross ross - rosy rosi - rot rot - rote rote - roted rote - rother rother - rotherham rotherham - rots rot - rotted rot - rotten rotten - rottenness rotten - rotting rot - rotundity rotund - rouen rouen - rough rough - rougher rougher - roughest roughest - roughly roughli - roughness rough - round round - rounded round - roundel roundel - rounder rounder - roundest roundest - rounding round - roundly roundli - rounds round - roundure roundur - rous rou - rouse rous - roused rous - rousillon rousillon - rously rousli - roussi roussi - rout rout - routed rout - routs rout - rove rove - rover rover - row row - rowel rowel - rowland rowland - rowlands rowland - roy roi - royal royal - royalize royal - royally royal - royalties royalti - royalty royalti - roynish roynish - rs rs - rt rt - rub rub - rubb rubb - rubbing rub - rubbish rubbish - rubies rubi - rubious rubiou - rubs rub - ruby rubi - rud rud - rudand rudand - rudder rudder - ruddiness ruddi - ruddock ruddock - ruddy ruddi - rude rude - rudely rude - rudeness rude - ruder ruder - rudesby rudesbi - rudest rudest - rudiments rudiment - rue rue - rued ru - ruff ruff - ruffian ruffian - ruffians ruffian - ruffle ruffl - ruffling ruffl - ruffs ruff - rug rug - rugby rugbi - rugemount rugemount - rugged rug - ruin ruin - ruinate ruinat - ruined ruin - ruining ruin - ruinous ruinou - ruins ruin - rul rul - rule rule - ruled rule - ruler ruler - rulers ruler - rules rule - ruling rule - rumble rumbl - ruminaies ruminai - ruminat ruminat - ruminate rumin - ruminated rumin - ruminates rumin - rumination rumin - rumor rumor - rumour rumour - rumourer rumour - rumours rumour - rump rump - run run - runagate runag - runagates runag - runaway runawai - runaways runawai - rung rung - runn runn - runner runner - runners runner - running run - runs run - rupture ruptur - ruptures ruptur - rural rural - rush rush - rushes rush - rushing rush - rushling rushl - rushy rushi - russet russet - russia russia - russian russian - russians russian - rust rust - rusted rust - rustic rustic - rustically rustic - rustics rustic - rustle rustl - rustling rustl - rusts rust - rusty rusti - rut rut - ruth ruth - ruthful ruth - ruthless ruthless - rutland rutland - ruttish ruttish - ry ry - rye rye - rything ryth - s s - sa sa - saba saba - sabbath sabbath - sable sabl - sables sabl - sack sack - sackbuts sackbut - sackcloth sackcloth - sacked sack - sackerson sackerson - sacks sack - sacrament sacrament - sacred sacr - sacrific sacrif - sacrifice sacrific - sacrificers sacrific - sacrifices sacrific - sacrificial sacrifici - sacrificing sacrif - sacrilegious sacrilegi - sacring sacr - sad sad - sadder sadder - saddest saddest - saddle saddl - saddler saddler - saddles saddl - sadly sadli - sadness sad - saf saf - safe safe - safeguard safeguard - safely safe - safer safer - safest safest - safeties safeti - safety safeti - saffron saffron - sag sag - sage sage - sagittary sagittari - said said - saidst saidst - sail sail - sailing sail - sailmaker sailmak - sailor sailor - sailors sailor - sails sail - sain sain - saint saint - sainted saint - saintlike saintlik - saints saint - saith saith - sake sake - sakes sake - sala sala - salad salad - salamander salamand - salary salari - sale sale - salerio salerio - salicam salicam - salique saliqu - salisbury salisburi - sall sall - sallet sallet - sallets sallet - sallies salli - sallow sallow - sally salli - salmon salmon - salmons salmon - salt salt - salter salter - saltiers saltier - saltness salt - saltpetre saltpetr - salutation salut - salutations salut - salute salut - saluted salut - salutes salut - saluteth saluteth - salv salv - salvation salvat - salve salv - salving salv - same same - samingo samingo - samp samp - sampire sampir - sample sampl - sampler sampler - sampson sampson - samson samson - samsons samson - sancta sancta - sanctified sanctifi - sanctifies sanctifi - sanctify sanctifi - sanctimonies sanctimoni - sanctimonious sanctimoni - sanctimony sanctimoni - sanctities sanctiti - sanctity sanctiti - sanctuarize sanctuar - sanctuary sanctuari - sand sand - sandal sandal - sandbag sandbag - sanded sand - sands sand - sandy sandi - sandys sandi - sang sang - sanguine sanguin - sanguis sangui - sanity saniti - sans san - santrailles santrail - sap sap - sapient sapient - sapit sapit - sapless sapless - sapling sapl - sapphire sapphir - sapphires sapphir - saracens saracen - sarcenet sarcenet - sard sard - sardians sardian - sardinia sardinia - sardis sardi - sarum sarum - sat sat - satan satan - satchel satchel - sate sate - sated sate - satiate satiat - satiety satieti - satin satin - satire satir - satirical satir - satis sati - satisfaction satisfact - satisfied satisfi - satisfies satisfi - satisfy satisfi - satisfying satisfi - saturday saturdai - saturdays saturdai - saturn saturn - saturnine saturnin - saturninus saturninu - satyr satyr - satyrs satyr - sauc sauc - sauce sauc - sauced sauc - saucers saucer - sauces sauc - saucily saucili - sauciness sauci - saucy sauci - sauf sauf - saunder saunder - sav sav - savage savag - savagely savag - savageness savag - savagery savageri - savages savag - save save - saved save - saves save - saving save - saviour saviour - savory savori - savour savour - savouring savour - savours savour - savoury savouri - savoy savoi - saw saw - sawed saw - sawest sawest - sawn sawn - sawpit sawpit - saws saw - sawyer sawyer - saxons saxon - saxony saxoni - saxton saxton - say sai - sayest sayest - saying sai - sayings sai - says sai - sayst sayst - sblood sblood - sc sc - scab scab - scabbard scabbard - scabs scab - scaffold scaffold - scaffoldage scaffoldag - scal scal - scald scald - scalded scald - scalding scald - scale scale - scaled scale - scales scale - scaling scale - scall scall - scalp scalp - scalps scalp - scaly scali - scamble scambl - scambling scambl - scamels scamel - scan scan - scandal scandal - scandaliz scandaliz - scandalous scandal - scandy scandi - scann scann - scant scant - scanted scant - scanter scanter - scanting scant - scantling scantl - scants scant - scap scap - scape scape - scaped scape - scapes scape - scapeth scapeth - scar scar - scarce scarc - scarcely scarc - scarcity scarciti - scare scare - scarecrow scarecrow - scarecrows scarecrow - scarf scarf - scarfed scarf - scarfs scarf - scaring scare - scarlet scarlet - scarr scarr - scarre scarr - scars scar - scarus scaru - scath scath - scathe scath - scathful scath - scatt scatt - scatter scatter - scattered scatter - scattering scatter - scatters scatter - scelera scelera - scelerisque scelerisqu - scene scene - scenes scene - scent scent - scented scent - scept scept - scepter scepter - sceptre sceptr - sceptred sceptr - sceptres sceptr - schedule schedul - schedules schedul - scholar scholar - scholarly scholarli - scholars scholar - school school - schoolboy schoolboi - schoolboys schoolboi - schoolfellows schoolfellow - schooling school - schoolmaster schoolmast - schoolmasters schoolmast - schools school - sciatica sciatica - sciaticas sciatica - science scienc - sciences scienc - scimitar scimitar - scion scion - scions scion - scissors scissor - scoff scoff - scoffer scoffer - scoffing scof - scoffs scoff - scoggin scoggin - scold scold - scolding scold - scolds scold - sconce sconc - scone scone - scope scope - scopes scope - scorch scorch - scorched scorch - score score - scored score - scores score - scoring score - scorn scorn - scorned scorn - scornful scorn - scornfully scornfulli - scorning scorn - scorns scorn - scorpion scorpion - scorpions scorpion - scot scot - scotch scotch - scotches scotch - scotland scotland - scots scot - scottish scottish - scoundrels scoundrel - scour scour - scoured scour - scourg scourg - scourge scourg - scouring scour - scout scout - scouts scout - scowl scowl - scrap scrap - scrape scrape - scraping scrape - scraps scrap - scratch scratch - scratches scratch - scratching scratch - scream scream - screams scream - screech screech - screeching screech - screen screen - screens screen - screw screw - screws screw - scribbl scribbl - scribbled scribbl - scribe scribe - scribes scribe - scrimers scrimer - scrip scrip - scrippage scrippag - scripture scriptur - scriptures scriptur - scrivener scriven - scroll scroll - scrolls scroll - scroop scroop - scrowl scrowl - scroyles scroyl - scrubbed scrub - scruple scrupl - scruples scrupl - scrupulous scrupul - scuffles scuffl - scuffling scuffl - scullion scullion - sculls scull - scum scum - scurril scurril - scurrility scurril - scurrilous scurril - scurvy scurvi - scuse scuse - scut scut - scutcheon scutcheon - scutcheons scutcheon - scylla scylla - scythe scyth - scythed scyth - scythia scythia - scythian scythian - sdeath sdeath - se se - sea sea - seacoal seacoal - seafaring seafar - seal seal - sealed seal - sealing seal - seals seal - seam seam - seamen seamen - seamy seami - seaport seaport - sear sear - searce searc - search search - searchers searcher - searches search - searcheth searcheth - searching search - seared sear - seas sea - seasick seasick - seaside seasid - season season - seasoned season - seasons season - seat seat - seated seat - seats seat - sebastian sebastian - second second - secondarily secondarili - secondary secondari - seconded second - seconds second - secrecy secreci - secret secret - secretaries secretari - secretary secretari - secretly secretli - secrets secret - sect sect - sectary sectari - sects sect - secundo secundo - secure secur - securely secur - securing secur - security secur - sedg sedg - sedge sedg - sedges sedg - sedgy sedgi - sedition sedit - seditious sediti - seduc seduc - seduce seduc - seduced seduc - seducer seduc - seducing seduc - see see - seed seed - seeded seed - seedness seed - seeds seed - seedsman seedsman - seein seein - seeing see - seek seek - seeking seek - seeks seek - seel seel - seeling seel - seely seeli - seem seem - seemed seem - seemers seemer - seemest seemest - seemeth seemeth - seeming seem - seemingly seemingli - seemly seemli - seems seem - seen seen - seer seer - sees see - seese sees - seest seest - seethe seeth - seethes seeth - seething seeth - seeting seet - segregation segreg - seigneur seigneur - seigneurs seigneur - seiz seiz - seize seiz - seized seiz - seizes seiz - seizeth seizeth - seizing seiz - seizure seizur - seld seld - seldom seldom - select select - seleucus seleucu - self self - selfsame selfsam - sell sell - seller seller - selling sell - sells sell - selves selv - semblable semblabl - semblably semblabl - semblance semblanc - semblances semblanc - semblative sembl - semi semi - semicircle semicircl - semiramis semirami - semper semper - sempronius semproniu - senate senat - senator senat - senators senat - send send - sender sender - sendeth sendeth - sending send - sends send - seneca seneca - senior senior - seniory seniori - senis seni - sennet sennet - senoys senoi - sense sens - senseless senseless - senses sens - sensible sensibl - sensibly sensibl - sensual sensual - sensuality sensual - sent sent - sentenc sentenc - sentence sentenc - sentences sentenc - sententious sententi - sentinel sentinel - sentinels sentinel - separable separ - separate separ - separated separ - separates separ - separation separ - septentrion septentrion - sepulchre sepulchr - sepulchres sepulchr - sepulchring sepulchr - sequel sequel - sequence sequenc - sequent sequent - sequest sequest - sequester sequest - sequestration sequestr - sere sere - serenis sereni - serge serg - sergeant sergeant - serious seriou - seriously serious - sermon sermon - sermons sermon - serpent serpent - serpentine serpentin - serpents serpent - serpigo serpigo - serv serv - servant servant - servanted servant - servants servant - serve serv - served serv - server server - serves serv - serveth serveth - service servic - serviceable servic - services servic - servile servil - servility servil - servilius serviliu - serving serv - servingman servingman - servingmen servingmen - serviteur serviteur - servitor servitor - servitors servitor - servitude servitud - sessa sessa - session session - sessions session - sestos sesto - set set - setebos setebo - sets set - setter setter - setting set - settle settl - settled settl - settlest settlest - settling settl - sev sev - seven seven - sevenfold sevenfold - sevennight sevennight - seventeen seventeen - seventh seventh - seventy seventi - sever sever - several sever - severally sever - severals sever - severe sever - severed sever - severely sever - severest severest - severing sever - severity sever - severn severn - severs sever - sew sew - seward seward - sewer sewer - sewing sew - sex sex - sexes sex - sexton sexton - sextus sextu - seymour seymour - seyton seyton - sfoot sfoot - sh sh - shackle shackl - shackles shackl - shade shade - shades shade - shadow shadow - shadowed shadow - shadowing shadow - shadows shadow - shadowy shadowi - shady shadi - shafalus shafalu - shaft shaft - shafts shaft - shag shag - shak shak - shake shake - shaked shake - shaken shaken - shakes shake - shaking shake - shales shale - shall shall - shallenge shalleng - shallow shallow - shallowest shallowest - shallowly shallowli - shallows shallow - shalt shalt - sham sham - shambles shambl - shame shame - shamed shame - shameful shame - shamefully shamefulli - shameless shameless - shames shame - shamest shamest - shaming shame - shank shank - shanks shank - shap shap - shape shape - shaped shape - shapeless shapeless - shapen shapen - shapes shape - shaping shape - shar shar - shard shard - sharded shard - shards shard - share share - shared share - sharers sharer - shares share - sharing share - shark shark - sharp sharp - sharpen sharpen - sharpened sharpen - sharpens sharpen - sharper sharper - sharpest sharpest - sharply sharpli - sharpness sharp - sharps sharp - shatter shatter - shav shav - shave shave - shaven shaven - shaw shaw - she she - sheaf sheaf - sheal sheal - shear shear - shearers shearer - shearing shear - shearman shearman - shears shear - sheath sheath - sheathe sheath - sheathed sheath - sheathes sheath - sheathing sheath - sheaved sheav - sheaves sheav - shed shed - shedding shed - sheds shed - sheen sheen - sheep sheep - sheepcote sheepcot - sheepcotes sheepcot - sheeps sheep - sheepskins sheepskin - sheer sheer - sheet sheet - sheeted sheet - sheets sheet - sheffield sheffield - shelf shelf - shell shell - shells shell - shelt shelt - shelter shelter - shelters shelter - shelves shelv - shelving shelv - shelvy shelvi - shent shent - shepherd shepherd - shepherdes shepherd - shepherdess shepherdess - shepherdesses shepherdess - shepherds shepherd - sher sher - sheriff sheriff - sherris sherri - shes she - sheweth sheweth - shield shield - shielded shield - shields shield - shift shift - shifted shift - shifting shift - shifts shift - shilling shill - shillings shill - shin shin - shine shine - shines shine - shineth shineth - shining shine - shins shin - shiny shini - ship ship - shipboard shipboard - shipman shipman - shipmaster shipmast - shipmen shipmen - shipp shipp - shipped ship - shipping ship - ships ship - shipt shipt - shipwreck shipwreck - shipwrecking shipwreck - shipwright shipwright - shipwrights shipwright - shire shire - shirley shirlei - shirt shirt - shirts shirt - shive shive - shiver shiver - shivering shiver - shivers shiver - shoal shoal - shoals shoal - shock shock - shocks shock - shod shod - shoe shoe - shoeing shoe - shoemaker shoemak - shoes shoe - shog shog - shone shone - shook shook - shoon shoon - shoot shoot - shooter shooter - shootie shooti - shooting shoot - shoots shoot - shop shop - shops shop - shore shore - shores shore - shorn shorn - short short - shortcake shortcak - shorten shorten - shortened shorten - shortens shorten - shorter shorter - shortly shortli - shortness short - shot shot - shotten shotten - shoughs shough - should should - shoulder shoulder - shouldering shoulder - shoulders shoulder - shouldst shouldst - shout shout - shouted shout - shouting shout - shouts shout - shov shov - shove shove - shovel shovel - shovels shovel - show show - showed show - shower shower - showers shower - showest showest - showing show - shown shown - shows show - shreds shred - shrew shrew - shrewd shrewd - shrewdly shrewdli - shrewdness shrewd - shrewish shrewish - shrewishly shrewishli - shrewishness shrewish - shrews shrew - shrewsbury shrewsburi - shriek shriek - shrieking shriek - shrieks shriek - shrieve shriev - shrift shrift - shrill shrill - shriller shriller - shrills shrill - shrilly shrilli - shrimp shrimp - shrine shrine - shrink shrink - shrinking shrink - shrinks shrink - shriv shriv - shrive shrive - shriver shriver - shrives shrive - shriving shrive - shroud shroud - shrouded shroud - shrouding shroud - shrouds shroud - shrove shrove - shrow shrow - shrows shrow - shrub shrub - shrubs shrub - shrug shrug - shrugs shrug - shrunk shrunk - shudd shudd - shudders shudder - shuffl shuffl - shuffle shuffl - shuffled shuffl - shuffling shuffl - shun shun - shunless shunless - shunn shunn - shunned shun - shunning shun - shuns shun - shut shut - shuts shut - shuttle shuttl - shy shy - shylock shylock - si si - sibyl sibyl - sibylla sibylla - sibyls sibyl - sicil sicil - sicilia sicilia - sicilian sicilian - sicilius siciliu - sicils sicil - sicily sicili - sicinius siciniu - sick sick - sicken sicken - sickens sicken - sicker sicker - sickle sickl - sicklemen sicklemen - sicklied sickli - sickliness sickli - sickly sickli - sickness sick - sicles sicl - sicyon sicyon - side side - sided side - sides side - siege sieg - sieges sieg - sienna sienna - sies si - sieve siev - sift sift - sifted sift - sigeia sigeia - sigh sigh - sighed sigh - sighing sigh - sighs sigh - sight sight - sighted sight - sightless sightless - sightly sightli - sights sight - sign sign - signal signal - signet signet - signieur signieur - significant signific - significants signific - signified signifi - signifies signifi - signify signifi - signifying signifi - signior signior - signiories signiori - signiors signior - signiory signiori - signor signor - signories signori - signs sign - signum signum - silenc silenc - silence silenc - silenced silenc - silencing silenc - silent silent - silently silent - silius siliu - silk silk - silken silken - silkman silkman - silks silk - silliest silliest - silliness silli - silling sill - silly silli - silva silva - silver silver - silvered silver - silverly silverli - silvia silvia - silvius silviu - sima sima - simile simil - similes simil - simois simoi - simon simon - simony simoni - simp simp - simpcox simpcox - simple simpl - simpleness simpl - simpler simpler - simples simpl - simplicity simplic - simply simpli - simular simular - simulation simul - sin sin - since sinc - sincere sincer - sincerely sincer - sincerity sincer - sinel sinel - sinew sinew - sinewed sinew - sinews sinew - sinewy sinewi - sinful sin - sinfully sinfulli - sing sing - singe sing - singeing sing - singer singer - singes sing - singeth singeth - singing sing - single singl - singled singl - singleness singl - singly singli - sings sing - singular singular - singulariter singularit - singularities singular - singularity singular - singuled singul - sinister sinist - sink sink - sinking sink - sinks sink - sinn sinn - sinner sinner - sinners sinner - sinning sin - sinon sinon - sins sin - sip sip - sipping sip - sir sir - sire sire - siren siren - sirrah sirrah - sirs sir - sist sist - sister sister - sisterhood sisterhood - sisterly sisterli - sisters sister - sit sit - sith sith - sithence sithenc - sits sit - sitting sit - situate situat - situation situat - situations situat - siward siward - six six - sixpence sixpenc - sixpences sixpenc - sixpenny sixpenni - sixteen sixteen - sixth sixth - sixty sixti - siz siz - size size - sizes size - sizzle sizzl - skains skain - skamble skambl - skein skein - skelter skelter - skies ski - skilful skil - skilfully skilfulli - skill skill - skilless skilless - skillet skillet - skillful skill - skills skill - skim skim - skimble skimbl - skin skin - skinker skinker - skinny skinni - skins skin - skip skip - skipp skipp - skipper skipper - skipping skip - skirmish skirmish - skirmishes skirmish - skirr skirr - skirted skirt - skirts skirt - skittish skittish - skulking skulk - skull skull - skulls skull - sky sky - skyey skyei - skyish skyish - slab slab - slack slack - slackly slackli - slackness slack - slain slain - slake slake - sland sland - slander slander - slandered slander - slanderer slander - slanderers slander - slandering slander - slanderous slander - slanders slander - slash slash - slaught slaught - slaughter slaughter - slaughtered slaughter - slaughterer slaughter - slaughterman slaughterman - slaughtermen slaughtermen - slaughterous slaughter - slaughters slaughter - slave slave - slaver slaver - slavery slaveri - slaves slave - slavish slavish - slay slai - slayeth slayeth - slaying slai - slays slai - sleave sleav - sledded sled - sleek sleek - sleekly sleekli - sleep sleep - sleeper sleeper - sleepers sleeper - sleepest sleepest - sleeping sleep - sleeps sleep - sleepy sleepi - sleeve sleev - sleeves sleev - sleid sleid - sleided sleid - sleight sleight - sleights sleight - slender slender - slenderer slender - slenderly slenderli - slept slept - slew slew - slewest slewest - slice slice - slid slid - slide slide - slides slide - sliding slide - slight slight - slighted slight - slightest slightest - slightly slightli - slightness slight - slights slight - slily slili - slime slime - slimy slimi - slings sling - slink slink - slip slip - slipp slipp - slipper slipper - slippers slipper - slippery slipperi - slips slip - slish slish - slit slit - sliver sliver - slobb slobb - slomber slomber - slop slop - slope slope - slops slop - sloth sloth - slothful sloth - slough slough - slovenly slovenli - slovenry slovenri - slow slow - slower slower - slowly slowli - slowness slow - slubber slubber - slug slug - sluggard sluggard - sluggardiz sluggardiz - sluggish sluggish - sluic sluic - slumb slumb - slumber slumber - slumbers slumber - slumbery slumberi - slunk slunk - slut slut - sluts slut - sluttery slutteri - sluttish sluttish - sluttishness sluttish - sly sly - slys sly - smack smack - smacking smack - smacks smack - small small - smaller smaller - smallest smallest - smallness small - smalus smalu - smart smart - smarting smart - smartly smartli - smatch smatch - smatter smatter - smear smear - smell smell - smelling smell - smells smell - smelt smelt - smil smil - smile smile - smiled smile - smiles smile - smilest smilest - smilets smilet - smiling smile - smilingly smilingli - smirch smirch - smirched smirch - smit smit - smite smite - smites smite - smith smith - smithfield smithfield - smock smock - smocks smock - smok smok - smoke smoke - smoked smoke - smokes smoke - smoking smoke - smoky smoki - smooth smooth - smoothed smooth - smoothing smooth - smoothly smoothli - smoothness smooth - smooths smooth - smote smote - smoth smoth - smother smother - smothered smother - smothering smother - smug smug - smulkin smulkin - smutch smutch - snaffle snaffl - snail snail - snails snail - snake snake - snakes snake - snaky snaki - snap snap - snapp snapp - snapper snapper - snar snar - snare snare - snares snare - snarl snarl - snarleth snarleth - snarling snarl - snatch snatch - snatchers snatcher - snatches snatch - snatching snatch - sneak sneak - sneaking sneak - sneap sneap - sneaping sneap - sneck sneck - snip snip - snipe snipe - snipt snipt - snore snore - snores snore - snoring snore - snorting snort - snout snout - snow snow - snowballs snowbal - snowed snow - snowy snowi - snuff snuff - snuffs snuff - snug snug - so so - soak soak - soaking soak - soaks soak - soar soar - soaring soar - soars soar - sob sob - sobbing sob - sober sober - soberly soberli - sobriety sobrieti - sobs sob - sociable sociabl - societies societi - society societi - socks sock - socrates socrat - sod sod - sodden sodden - soe soe - soever soever - soft soft - soften soften - softens soften - softer softer - softest softest - softly softli - softness soft - soil soil - soiled soil - soilure soilur - soit soit - sojourn sojourn - sol sol - sola sola - solace solac - solanio solanio - sold sold - soldat soldat - solder solder - soldest soldest - soldier soldier - soldiers soldier - soldiership soldiership - sole sole - solely sole - solem solem - solemn solemn - solemness solem - solemnities solemn - solemnity solemn - solemniz solemniz - solemnize solemn - solemnized solemn - solemnly solemnli - soles sole - solicit solicit - solicitation solicit - solicited solicit - soliciting solicit - solicitings solicit - solicitor solicitor - solicits solicit - solid solid - solidares solidar - solidity solid - solinus solinu - solitary solitari - solomon solomon - solon solon - solum solum - solus solu - solyman solyman - some some - somebody somebodi - someone someon - somerset somerset - somerville somervil - something someth - sometime sometim - sometimes sometim - somever somev - somewhat somewhat - somewhere somewher - somewhither somewhith - somme somm - son son - sonance sonanc - song song - songs song - sonnet sonnet - sonneting sonnet - sonnets sonnet - sons son - sont sont - sonties sonti - soon soon - sooner sooner - soonest soonest - sooth sooth - soothe sooth - soothers soother - soothing sooth - soothsay soothsai - soothsayer soothsay - sooty sooti - sop sop - sophister sophist - sophisticated sophist - sophy sophi - sops sop - sorcerer sorcer - sorcerers sorcer - sorceress sorceress - sorceries sorceri - sorcery sorceri - sore sore - sorel sorel - sorely sore - sorer sorer - sores sore - sorrier sorrier - sorriest sorriest - sorrow sorrow - sorrowed sorrow - sorrowest sorrowest - sorrowful sorrow - sorrowing sorrow - sorrows sorrow - sorry sorri - sort sort - sortance sortanc - sorted sort - sorting sort - sorts sort - sossius sossiu - sot sot - soto soto - sots sot - sottish sottish - soud soud - sought sought - soul soul - sould sould - soulless soulless - souls soul - sound sound - sounded sound - sounder sounder - soundest soundest - sounding sound - soundless soundless - soundly soundli - soundness sound - soundpost soundpost - sounds sound - sour sour - source sourc - sources sourc - sourest sourest - sourly sourli - sours sour - sous sou - souse sous - south south - southam southam - southampton southampton - southerly southerli - southern southern - southward southward - southwark southwark - southwell southwel - souviendrai souviendrai - sov sov - sovereign sovereign - sovereignest sovereignest - sovereignly sovereignli - sovereignty sovereignti - sovereignvours sovereignvour - sow sow - sowing sow - sowl sowl - sowter sowter - space space - spaces space - spacious spaciou - spade spade - spades spade - spain spain - spak spak - spake spake - spakest spakest - span span - spangle spangl - spangled spangl - spaniard spaniard - spaniel spaniel - spaniels spaniel - spanish spanish - spann spann - spans span - spar spar - spare spare - spares spare - sparing spare - sparingly sparingli - spark spark - sparkle sparkl - sparkles sparkl - sparkling sparkl - sparks spark - sparrow sparrow - sparrows sparrow - sparta sparta - spartan spartan - spavin spavin - spavins spavin - spawn spawn - speak speak - speaker speaker - speakers speaker - speakest speakest - speaketh speaketh - speaking speak - speaks speak - spear spear - speargrass speargrass - spears spear - special special - specialities special - specially special - specialties specialti - specialty specialti - specify specifi - speciously specious - spectacle spectacl - spectacled spectacl - spectacles spectacl - spectators spectat - spectatorship spectatorship - speculation specul - speculations specul - speculative specul - sped sped - speech speech - speeches speech - speechless speechless - speed speed - speeded speed - speedier speedier - speediest speediest - speedily speedili - speediness speedi - speeding speed - speeds speed - speedy speedi - speens speen - spell spell - spelling spell - spells spell - spelt spelt - spencer spencer - spend spend - spendest spendest - spending spend - spends spend - spendthrift spendthrift - spent spent - sperato sperato - sperm sperm - spero spero - sperr sperr - spher spher - sphere sphere - sphered sphere - spheres sphere - spherical spheric - sphery spheri - sphinx sphinx - spice spice - spiced spice - spicery spiceri - spices spice - spider spider - spiders spider - spied spi - spies spi - spieth spieth - spightfully spightfulli - spigot spigot - spill spill - spilling spill - spills spill - spilt spilt - spilth spilth - spin spin - spinii spinii - spinners spinner - spinster spinster - spinsters spinster - spire spire - spirit spirit - spirited spirit - spiritless spiritless - spirits spirit - spiritual spiritu - spiritualty spiritualti - spirt spirt - spit spit - spital spital - spite spite - spited spite - spiteful spite - spites spite - spits spit - spitted spit - spitting spit - splay splai - spleen spleen - spleenful spleen - spleens spleen - spleeny spleeni - splendour splendour - splenitive splenit - splinter splinter - splinters splinter - split split - splits split - splitted split - splitting split - spoil spoil - spoils spoil - spok spok - spoke spoke - spoken spoken - spokes spoke - spokesman spokesman - sponge spong - spongy spongi - spoon spoon - spoons spoon - sport sport - sportful sport - sporting sport - sportive sportiv - sports sport - spot spot - spotless spotless - spots spot - spotted spot - spousal spousal - spouse spous - spout spout - spouting spout - spouts spout - sprag sprag - sprang sprang - sprat sprat - sprawl sprawl - spray sprai - sprays sprai - spread spread - spreading spread - spreads spread - sprighted spright - sprightful spright - sprightly sprightli - sprigs sprig - spring spring - springe spring - springes spring - springeth springeth - springhalt springhalt - springing spring - springs spring - springtime springtim - sprinkle sprinkl - sprinkles sprinkl - sprite sprite - sprited sprite - spritely sprite - sprites sprite - spriting sprite - sprout sprout - spruce spruce - sprung sprung - spun spun - spur spur - spurio spurio - spurn spurn - spurns spurn - spurr spurr - spurrer spurrer - spurring spur - spurs spur - spy spy - spying spy - squabble squabbl - squadron squadron - squadrons squadron - squand squand - squar squar - square squar - squarer squarer - squares squar - squash squash - squeak squeak - squeaking squeak - squeal squeal - squealing squeal - squeezes squeez - squeezing squeez - squele squel - squier squier - squints squint - squiny squini - squire squir - squires squir - squirrel squirrel - st st - stab stab - stabb stabb - stabbed stab - stabbing stab - stable stabl - stableness stabl - stables stabl - stablish stablish - stablishment stablish - stabs stab - stacks stack - staff staff - stafford stafford - staffords stafford - staffordshire staffordshir - stag stag - stage stage - stages stage - stagger stagger - staggering stagger - staggers stagger - stags stag - staid staid - staider staider - stain stain - stained stain - staines stain - staineth staineth - staining stain - stainless stainless - stains stain - stair stair - stairs stair - stake stake - stakes stake - stale stale - staled stale - stalk stalk - stalking stalk - stalks stalk - stall stall - stalling stall - stalls stall - stamford stamford - stammer stammer - stamp stamp - stamped stamp - stamps stamp - stanch stanch - stanchless stanchless - stand stand - standard standard - standards standard - stander stander - standers stander - standest standest - standeth standeth - standing stand - stands stand - staniel staniel - stanley stanlei - stanze stanz - stanzo stanzo - stanzos stanzo - staple stapl - staples stapl - star star - stare stare - stared stare - stares stare - staring stare - starings stare - stark stark - starkly starkli - starlight starlight - starling starl - starr starr - starry starri - stars star - start start - started start - starting start - startingly startingli - startle startl - startles startl - starts start - starv starv - starve starv - starved starv - starvelackey starvelackei - starveling starvel - starveth starveth - starving starv - state state - statelier stateli - stately state - states state - statesman statesman - statesmen statesmen - statilius statiliu - station station - statist statist - statists statist - statue statu - statues statu - stature statur - statures statur - statute statut - statutes statut - stave stave - staves stave - stay stai - stayed stai - stayest stayest - staying stai - stays stai - stead stead - steaded stead - steadfast steadfast - steadier steadier - steads stead - steal steal - stealer stealer - stealers stealer - stealing steal - steals steal - stealth stealth - stealthy stealthi - steed steed - steeds steed - steel steel - steeled steel - steely steeli - steep steep - steeped steep - steeple steepl - steeples steepl - steeps steep - steepy steepi - steer steer - steerage steerag - steering steer - steers steer - stelled stell - stem stem - stemming stem - stench stench - step step - stepdame stepdam - stephano stephano - stephen stephen - stepmothers stepmoth - stepp stepp - stepping step - steps step - sterile steril - sterility steril - sterling sterl - stern stern - sternage sternag - sterner sterner - sternest sternest - sternness stern - steterat steterat - stew stew - steward steward - stewards steward - stewardship stewardship - stewed stew - stews stew - stick stick - sticking stick - stickler stickler - sticks stick - stiff stiff - stiffen stiffen - stiffly stiffli - stifle stifl - stifled stifl - stifles stifl - stigmatic stigmat - stigmatical stigmat - stile stile - still still - stiller stiller - stillest stillest - stillness still - stilly stilli - sting sting - stinging sting - stingless stingless - stings sting - stink stink - stinking stink - stinkingly stinkingli - stinks stink - stint stint - stinted stint - stints stint - stir stir - stirr stirr - stirred stir - stirrer stirrer - stirrers stirrer - stirreth stirreth - stirring stir - stirrup stirrup - stirrups stirrup - stirs stir - stitchery stitcheri - stitches stitch - stithied stithi - stithy stithi - stoccadoes stoccado - stoccata stoccata - stock stock - stockfish stockfish - stocking stock - stockings stock - stockish stockish - stocks stock - stog stog - stogs stog - stoics stoic - stokesly stokesli - stol stol - stole stole - stolen stolen - stolest stolest - stomach stomach - stomachers stomach - stomaching stomach - stomachs stomach - ston ston - stone stone - stonecutter stonecutt - stones stone - stonish stonish - stony stoni - stood stood - stool stool - stools stool - stoop stoop - stooping stoop - stoops stoop - stop stop - stope stope - stopp stopp - stopped stop - stopping stop - stops stop - stor stor - store store - storehouse storehous - storehouses storehous - stores store - stories stori - storm storm - stormed storm - storming storm - storms storm - stormy stormi - story stori - stoup stoup - stoups stoup - stout stout - stouter stouter - stoutly stoutli - stoutness stout - stover stover - stow stow - stowage stowag - stowed stow - strachy strachi - stragglers straggler - straggling straggl - straight straight - straightest straightest - straightway straightwai - strain strain - strained strain - straining strain - strains strain - strait strait - straited strait - straiter straiter - straitly straitli - straitness strait - straits strait - strand strand - strange strang - strangely strang - strangeness strang - stranger stranger - strangers stranger - strangest strangest - strangle strangl - strangled strangl - strangler strangler - strangles strangl - strangling strangl - strappado strappado - straps strap - stratagem stratagem - stratagems stratagem - stratford stratford - strato strato - straw straw - strawberries strawberri - strawberry strawberri - straws straw - strawy strawi - stray strai - straying strai - strays strai - streak streak - streaks streak - stream stream - streamers streamer - streaming stream - streams stream - streching strech - street street - streets street - strength strength - strengthen strengthen - strengthened strengthen - strengthless strengthless - strengths strength - stretch stretch - stretched stretch - stretches stretch - stretching stretch - strew strew - strewing strew - strewings strew - strewments strewment - stricken stricken - strict strict - stricter stricter - strictest strictest - strictly strictli - stricture strictur - stride stride - strides stride - striding stride - strife strife - strifes strife - strik strik - strike strike - strikers striker - strikes strike - strikest strikest - striking strike - string string - stringless stringless - strings string - strip strip - stripes stripe - stripling stripl - striplings stripl - stripp stripp - stripping strip - striv striv - strive strive - strives strive - striving strive - strok strok - stroke stroke - strokes stroke - strond strond - stronds strond - strong strong - stronger stronger - strongest strongest - strongly strongli - strooke strook - strossers strosser - strove strove - strown strown - stroy stroi - struck struck - strucken strucken - struggle struggl - struggles struggl - struggling struggl - strumpet strumpet - strumpeted strumpet - strumpets strumpet - strung strung - strut strut - struts strut - strutted strut - strutting strut - stubble stubbl - stubborn stubborn - stubbornest stubbornest - stubbornly stubbornli - stubbornness stubborn - stuck stuck - studded stud - student student - students student - studied studi - studies studi - studious studiou - studiously studious - studs stud - study studi - studying studi - stuff stuff - stuffing stuf - stuffs stuff - stumble stumbl - stumbled stumbl - stumblest stumblest - stumbling stumbl - stump stump - stumps stump - stung stung - stupefy stupefi - stupid stupid - stupified stupifi - stuprum stuprum - sturdy sturdi - sty sty - styga styga - stygian stygian - styl styl - style style - styx styx - su su - sub sub - subcontracted subcontract - subdu subdu - subdue subdu - subdued subdu - subduements subduement - subdues subdu - subduing subdu - subject subject - subjected subject - subjection subject - subjects subject - submerg submerg - submission submiss - submissive submiss - submit submit - submits submit - submitting submit - suborn suborn - subornation suborn - suborned suborn - subscrib subscrib - subscribe subscrib - subscribed subscrib - subscribes subscrib - subscription subscript - subsequent subsequ - subsidies subsidi - subsidy subsidi - subsist subsist - subsisting subsist - substance substanc - substances substanc - substantial substanti - substitute substitut - substituted substitut - substitutes substitut - substitution substitut - subtile subtil - subtilly subtilli - subtle subtl - subtleties subtleti - subtlety subtleti - subtly subtli - subtractors subtractor - suburbs suburb - subversion subvers - subverts subvert - succedant succed - succeed succe - succeeded succeed - succeeders succeed - succeeding succeed - succeeds succe - success success - successantly successantli - successes success - successful success - successfully successfulli - succession success - successive success - successively success - successor successor - successors successor - succour succour - succours succour - such such - suck suck - sucker sucker - suckers sucker - sucking suck - suckle suckl - sucks suck - sudden sudden - suddenly suddenli - sue sue - sued su - suerly suerli - sues sue - sueth sueth - suff suff - suffer suffer - sufferance suffer - sufferances suffer - suffered suffer - suffering suffer - suffers suffer - suffic suffic - suffice suffic - sufficed suffic - suffices suffic - sufficeth sufficeth - sufficiency suffici - sufficient suffici - sufficiently suffici - sufficing suffic - sufficit sufficit - suffigance suffig - suffocate suffoc - suffocating suffoc - suffocation suffoc - suffolk suffolk - suffrage suffrag - suffrages suffrag - sug sug - sugar sugar - sugarsop sugarsop - suggest suggest - suggested suggest - suggesting suggest - suggestion suggest - suggestions suggest - suggests suggest - suis sui - suit suit - suitable suitabl - suited suit - suiting suit - suitor suitor - suitors suitor - suits suit - suivez suivez - sullen sullen - sullens sullen - sullied sulli - sullies sulli - sully sulli - sulph sulph - sulpherous sulpher - sulphur sulphur - sulphurous sulphur - sultan sultan - sultry sultri - sum sum - sumless sumless - summ summ - summa summa - summary summari - summer summer - summers summer - summit summit - summon summon - summoners summon - summons summon - sumpter sumpter - sumptuous sumptuou - sumptuously sumptuous - sums sum - sun sun - sunbeams sunbeam - sunburning sunburn - sunburnt sunburnt - sund sund - sunday sundai - sundays sundai - sunder sunder - sunders sunder - sundry sundri - sung sung - sunk sunk - sunken sunken - sunny sunni - sunrising sunris - suns sun - sunset sunset - sunshine sunshin - sup sup - super super - superficial superfici - superficially superfici - superfluity superflu - superfluous superflu - superfluously superflu - superflux superflux - superior superior - supernal supern - supernatural supernatur - superpraise superprais - superscript superscript - superscription superscript - superserviceable superservic - superstition superstit - superstitious superstiti - superstitiously superstiti - supersubtle supersubtl - supervise supervis - supervisor supervisor - supp supp - supper supper - suppers supper - suppertime suppertim - supping sup - supplant supplant - supple suppl - suppler suppler - suppliance supplianc - suppliant suppliant - suppliants suppliant - supplicant supplic - supplication supplic - supplications supplic - supplie suppli - supplied suppli - supplies suppli - suppliest suppliest - supply suppli - supplyant supplyant - supplying suppli - supplyment supplyment - support support - supportable support - supportance support - supported support - supporter support - supporters support - supporting support - supportor supportor - suppos suppo - supposal suppos - suppose suppos - supposed suppos - supposes suppos - supposest supposest - supposing suppos - supposition supposit - suppress suppress - suppressed suppress - suppresseth suppresseth - supremacy supremaci - supreme suprem - sups sup - sur sur - surance suranc - surcease surceas - surd surd - sure sure - surecard surecard - surely sure - surer surer - surest surest - sureties sureti - surety sureti - surfeit surfeit - surfeited surfeit - surfeiter surfeit - surfeiting surfeit - surfeits surfeit - surge surg - surgeon surgeon - surgeons surgeon - surgere surger - surgery surgeri - surges surg - surly surli - surmis surmi - surmise surmis - surmised surmis - surmises surmis - surmount surmount - surmounted surmount - surmounts surmount - surnam surnam - surname surnam - surnamed surnam - surpasseth surpasseth - surpassing surpass - surplice surplic - surplus surplu - surpris surpri - surprise surpris - surprised surpris - surrender surrend - surrey surrei - surreys surrei - survey survei - surveyest surveyest - surveying survei - surveyor surveyor - surveyors surveyor - surveys survei - survive surviv - survives surviv - survivor survivor - susan susan - suspect suspect - suspected suspect - suspecting suspect - suspects suspect - suspend suspend - suspense suspens - suspicion suspicion - suspicions suspicion - suspicious suspici - suspiration suspir - suspire suspir - sust sust - sustain sustain - sustaining sustain - sutler sutler - sutton sutton - suum suum - swabber swabber - swaddling swaddl - swag swag - swagg swagg - swagger swagger - swaggerer swagger - swaggerers swagger - swaggering swagger - swain swain - swains swain - swallow swallow - swallowed swallow - swallowing swallow - swallows swallow - swam swam - swan swan - swans swan - sward sward - sware sware - swarm swarm - swarming swarm - swart swart - swarth swarth - swarths swarth - swarthy swarthi - swashers swasher - swashing swash - swath swath - swathing swath - swathling swathl - sway swai - swaying swai - sways swai - swear swear - swearer swearer - swearers swearer - swearest swearest - swearing swear - swearings swear - swears swear - sweat sweat - sweaten sweaten - sweating sweat - sweats sweat - sweaty sweati - sweep sweep - sweepers sweeper - sweeps sweep - sweet sweet - sweeten sweeten - sweetens sweeten - sweeter sweeter - sweetest sweetest - sweetheart sweetheart - sweeting sweet - sweetly sweetli - sweetmeats sweetmeat - sweetness sweet - sweets sweet - swell swell - swelling swell - swellings swell - swells swell - swelter swelter - sweno sweno - swept swept - swerve swerv - swerver swerver - swerving swerv - swift swift - swifter swifter - swiftest swiftest - swiftly swiftli - swiftness swift - swill swill - swills swill - swim swim - swimmer swimmer - swimmers swimmer - swimming swim - swims swim - swine swine - swineherds swineherd - swing swing - swinge swing - swinish swinish - swinstead swinstead - switches switch - swits swit - switzers switzer - swol swol - swoll swoll - swoln swoln - swoon swoon - swooned swoon - swooning swoon - swoons swoon - swoop swoop - swoopstake swoopstak - swor swor - sword sword - sworder sworder - swords sword - swore swore - sworn sworn - swounded swound - swounds swound - swum swum - swung swung - sy sy - sycamore sycamor - sycorax sycorax - sylla sylla - syllable syllabl - syllables syllabl - syllogism syllog - symbols symbol - sympathise sympathis - sympathiz sympathiz - sympathize sympath - sympathized sympath - sympathy sympathi - synagogue synagogu - synod synod - synods synod - syracuse syracus - syracusian syracusian - syracusians syracusian - syria syria - syrups syrup - t t - ta ta - taber taber - table tabl - tabled tabl - tables tabl - tablet tablet - tabor tabor - taborer tabor - tabors tabor - tabourines tabourin - taciturnity taciturn - tack tack - tackle tackl - tackled tackl - tackles tackl - tackling tackl - tacklings tackl - taddle taddl - tadpole tadpol - taffeta taffeta - taffety taffeti - tag tag - tagrag tagrag - tah tah - tail tail - tailor tailor - tailors tailor - tails tail - taint taint - tainted taint - tainting taint - taints taint - tainture taintur - tak tak - take take - taken taken - taker taker - takes take - takest takest - taketh taketh - taking take - tal tal - talbot talbot - talbotites talbotit - talbots talbot - tale tale - talent talent - talents talent - taleporter taleport - tales tale - talk talk - talked talk - talker talker - talkers talker - talkest talkest - talking talk - talks talk - tall tall - taller taller - tallest tallest - tallies talli - tallow tallow - tally talli - talons talon - tam tam - tambourines tambourin - tame tame - tamed tame - tamely tame - tameness tame - tamer tamer - tames tame - taming tame - tamora tamora - tamworth tamworth - tan tan - tang tang - tangle tangl - tangled tangl - tank tank - tanlings tanl - tann tann - tanned tan - tanner tanner - tanquam tanquam - tanta tanta - tantaene tantaen - tap tap - tape tape - taper taper - tapers taper - tapestries tapestri - tapestry tapestri - taphouse taphous - tapp tapp - tapster tapster - tapsters tapster - tar tar - tardied tardi - tardily tardili - tardiness tardi - tardy tardi - tarentum tarentum - targe targ - targes targ - target target - targets target - tarpeian tarpeian - tarquin tarquin - tarquins tarquin - tarr tarr - tarre tarr - tarriance tarrianc - tarried tarri - tarries tarri - tarry tarri - tarrying tarri - tart tart - tartar tartar - tartars tartar - tartly tartli - tartness tart - task task - tasker tasker - tasking task - tasks task - tassel tassel - taste tast - tasted tast - tastes tast - tasting tast - tatt tatt - tatter tatter - tattered tatter - tatters tatter - tattle tattl - tattling tattl - tattlings tattl - taught taught - taunt taunt - taunted taunt - taunting taunt - tauntingly tauntingli - taunts taunt - taurus tauru - tavern tavern - taverns tavern - tavy tavi - tawdry tawdri - tawny tawni - tax tax - taxation taxat - taxations taxat - taxes tax - taxing tax - tc tc - te te - teach teach - teacher teacher - teachers teacher - teaches teach - teachest teachest - teacheth teacheth - teaching teach - team team - tear tear - tearful tear - tearing tear - tears tear - tearsheet tearsheet - teat teat - tedious tediou - tediously tedious - tediousness tedious - teem teem - teeming teem - teems teem - teen teen - teeth teeth - teipsum teipsum - telamon telamon - telamonius telamoniu - tell tell - teller teller - telling tell - tells tell - tellus tellu - temp temp - temper temper - temperality temper - temperance temper - temperate temper - temperately temper - tempers temper - tempest tempest - tempests tempest - tempestuous tempestu - temple templ - temples templ - temporal tempor - temporary temporari - temporiz temporiz - temporize tempor - temporizer tempor - temps temp - tempt tempt - temptation temptat - temptations temptat - tempted tempt - tempter tempter - tempters tempter - tempteth tempteth - tempting tempt - tempts tempt - ten ten - tenable tenabl - tenant tenant - tenantius tenantiu - tenantless tenantless - tenants tenant - tench tench - tend tend - tendance tendanc - tended tend - tender tender - tendered tender - tenderly tenderli - tenderness tender - tenders tender - tending tend - tends tend - tenedos tenedo - tenement tenement - tenements tenement - tenfold tenfold - tennis tenni - tenour tenour - tenours tenour - tens ten - tent tent - tented tent - tenth tenth - tenths tenth - tents tent - tenure tenur - tenures tenur - tercel tercel - tereus tereu - term term - termagant termag - termed term - terminations termin - termless termless - terms term - terra terra - terrace terrac - terram terram - terras terra - terre terr - terrene terren - terrestrial terrestri - terrible terribl - terribly terribl - territories territori - territory territori - terror terror - terrors terror - tertian tertian - tertio tertio - test test - testament testament - tested test - tester tester - testern testern - testify testifi - testimonied testimoni - testimonies testimoni - testimony testimoni - testiness testi - testril testril - testy testi - tetchy tetchi - tether tether - tetter tetter - tevil tevil - tewksbury tewksburi - text text - tgv tgv - th th - thaes thae - thames thame - than than - thane thane - thanes thane - thank thank - thanked thank - thankful thank - thankfully thankfulli - thankfulness thank - thanking thank - thankings thank - thankless thankless - thanks thank - thanksgiving thanksgiv - thasos thaso - that that - thatch thatch - thaw thaw - thawing thaw - thaws thaw - the the - theatre theatr - theban theban - thebes thebe - thee thee - theft theft - thefts theft - thein thein - their their - theirs their - theise theis - them them - theme theme - themes theme - themselves themselv - then then - thence thenc - thenceforth thenceforth - theoric theoric - there there - thereabout thereabout - thereabouts thereabout - thereafter thereaft - thereat thereat - thereby therebi - therefore therefor - therein therein - thereof thereof - thereon thereon - thereto thereto - thereunto thereunto - thereupon thereupon - therewith therewith - therewithal therewith - thersites thersit - these these - theseus theseu - thessalian thessalian - thessaly thessali - thetis theti - thews thew - they thei - thick thick - thicken thicken - thickens thicken - thicker thicker - thickest thickest - thicket thicket - thickskin thickskin - thief thief - thievery thieveri - thieves thiev - thievish thievish - thigh thigh - thighs thigh - thimble thimbl - thimbles thimbl - thin thin - thine thine - thing thing - things thing - think think - thinkest thinkest - thinking think - thinkings think - thinks think - thinkst thinkst - thinly thinli - third third - thirdly thirdli - thirds third - thirst thirst - thirsting thirst - thirsts thirst - thirsty thirsti - thirteen thirteen - thirties thirti - thirtieth thirtieth - thirty thirti - this thi - thisby thisbi - thisne thisn - thistle thistl - thistles thistl - thither thither - thitherward thitherward - thoas thoa - thomas thoma - thorn thorn - thorns thorn - thorny thorni - thorough thorough - thoroughly thoroughli - those those - thou thou - though though - thought thought - thoughtful thought - thoughts thought - thousand thousand - thousands thousand - thracian thracian - thraldom thraldom - thrall thrall - thralled thrall - thralls thrall - thrash thrash - thrasonical thrason - thread thread - threadbare threadbar - threaden threaden - threading thread - threat threat - threaten threaten - threatening threaten - threatens threaten - threatest threatest - threats threat - three three - threefold threefold - threepence threepenc - threepile threepil - threes three - threescore threescor - thresher thresher - threshold threshold - threw threw - thrice thrice - thrift thrift - thriftless thriftless - thrifts thrift - thrifty thrifti - thrill thrill - thrilling thrill - thrills thrill - thrive thrive - thrived thrive - thrivers thriver - thrives thrive - thriving thrive - throat throat - throats throat - throbbing throb - throbs throb - throca throca - throe throe - throes throe - thromuldo thromuldo - thron thron - throne throne - throned throne - thrones throne - throng throng - thronging throng - throngs throng - throstle throstl - throttle throttl - through through - throughfare throughfar - throughfares throughfar - throughly throughli - throughout throughout - throw throw - thrower thrower - throwest throwest - throwing throw - thrown thrown - throws throw - thrum thrum - thrumm thrumm - thrush thrush - thrust thrust - thrusteth thrusteth - thrusting thrust - thrusts thrust - thumb thumb - thumbs thumb - thump thump - thund thund - thunder thunder - thunderbolt thunderbolt - thunderbolts thunderbolt - thunderer thunder - thunders thunder - thunderstone thunderston - thunderstroke thunderstrok - thurio thurio - thursday thursdai - thus thu - thwack thwack - thwart thwart - thwarted thwart - thwarting thwart - thwartings thwart - thy thy - thyme thyme - thymus thymu - thyreus thyreu - thyself thyself - ti ti - tib tib - tiber tiber - tiberio tiberio - tibey tibei - ticed tice - tick tick - tickl tickl - tickle tickl - tickled tickl - tickles tickl - tickling tickl - ticklish ticklish - tiddle tiddl - tide tide - tides tide - tidings tide - tidy tidi - tie tie - tied ti - ties ti - tiff tiff - tiger tiger - tigers tiger - tight tight - tightly tightli - tike tike - til til - tile tile - till till - tillage tillag - tilly tilli - tilt tilt - tilter tilter - tilth tilth - tilting tilt - tilts tilt - tiltyard tiltyard - tim tim - timandra timandra - timber timber - time time - timeless timeless - timelier timeli - timely time - times time - timon timon - timor timor - timorous timor - timorously timor - tinct tinct - tincture tinctur - tinctures tinctur - tinder tinder - tingling tingl - tinker tinker - tinkers tinker - tinsel tinsel - tiny tini - tip tip - tipp tipp - tippling tippl - tips tip - tipsy tipsi - tiptoe tipto - tir tir - tire tire - tired tire - tires tire - tirest tirest - tiring tire - tirra tirra - tirrits tirrit - tis ti - tish tish - tisick tisick - tissue tissu - titan titan - titania titania - tithe tith - tithed tith - tithing tith - titinius titiniu - title titl - titled titl - titleless titleless - titles titl - tittle tittl - tittles tittl - titular titular - titus titu - tn tn - to to - toad toad - toads toad - toadstool toadstool - toast toast - toasted toast - toasting toast - toasts toast - toaze toaz - toby tobi - tock tock - tod tod - today todai - todpole todpol - tods tod - toe toe - toes toe - tofore tofor - toge toge - toged toge - together togeth - toil toil - toiled toil - toiling toil - toils toil - token token - tokens token - told told - toledo toledo - tolerable toler - toll toll - tolling toll - tom tom - tomb tomb - tombe tomb - tombed tomb - tombless tombless - tomboys tomboi - tombs tomb - tomorrow tomorrow - tomyris tomyri - ton ton - tongs tong - tongu tongu - tongue tongu - tongued tongu - tongueless tongueless - tongues tongu - tonight tonight - too too - took took - tool tool - tools tool - tooth tooth - toothache toothach - toothpick toothpick - toothpicker toothpick - top top - topas topa - topful top - topgallant topgal - topless topless - topmast topmast - topp topp - topping top - topple toppl - topples toppl - tops top - topsail topsail - topsy topsi - torch torch - torchbearer torchbear - torchbearers torchbear - torcher torcher - torches torch - torchlight torchlight - tore tore - torment torment - tormenta tormenta - tormente torment - tormented torment - tormenting torment - tormentors tormentor - torments torment - torn torn - torrent torrent - tortive tortiv - tortoise tortois - tortur tortur - torture tortur - tortured tortur - torturer tortur - torturers tortur - tortures tortur - torturest torturest - torturing tortur - toryne toryn - toss toss - tossed toss - tosseth tosseth - tossing toss - tot tot - total total - totally total - tott tott - tottered totter - totters totter - tou tou - touch touch - touched touch - touches touch - toucheth toucheth - touching touch - touchstone touchston - tough tough - tougher tougher - toughness tough - touraine tourain - tournaments tournament - tours tour - tous tou - tout tout - touze touz - tow tow - toward toward - towardly towardli - towards toward - tower tower - towering tower - towers tower - town town - towns town - township township - townsman townsman - townsmen townsmen - towton towton - toy toi - toys toi - trace trace - traces trace - track track - tract tract - tractable tractabl - trade trade - traded trade - traders trader - trades trade - tradesman tradesman - tradesmen tradesmen - trading trade - tradition tradit - traditional tradit - traduc traduc - traduced traduc - traducement traduc - traffic traffic - traffickers traffick - traffics traffic - tragedian tragedian - tragedians tragedian - tragedies tragedi - tragedy tragedi - tragic tragic - tragical tragic - trail trail - train train - trained train - training train - trains train - trait trait - traitor traitor - traitorly traitorli - traitorous traitor - traitorously traitor - traitors traitor - traitress traitress - traject traject - trammel trammel - trample trampl - trampled trampl - trampling trampl - tranc tranc - trance tranc - tranio tranio - tranquil tranquil - tranquillity tranquil - transcendence transcend - transcends transcend - transferred transfer - transfigur transfigur - transfix transfix - transform transform - transformation transform - transformations transform - transformed transform - transgress transgress - transgresses transgress - transgressing transgress - transgression transgress - translate translat - translated translat - translates translat - translation translat - transmigrates transmigr - transmutation transmut - transparent transpar - transport transport - transportance transport - transported transport - transporting transport - transports transport - transpose transpos - transshape transshap - trap trap - trapp trapp - trappings trap - traps trap - trash trash - travail travail - travails travail - travel travel - traveler travel - traveling travel - travell travel - travelled travel - traveller travel - travellers travel - travellest travellest - travelling travel - travels travel - travers traver - traverse travers - tray trai - treacherous treacher - treacherously treacher - treachers treacher - treachery treacheri - tread tread - treading tread - treads tread - treason treason - treasonable treason - treasonous treason - treasons treason - treasure treasur - treasurer treasur - treasures treasur - treasuries treasuri - treasury treasuri - treat treat - treaties treati - treatise treatis - treats treat - treaty treati - treble trebl - trebled trebl - trebles trebl - trebonius treboniu - tree tree - trees tree - tremble trembl - trembled trembl - trembles trembl - tremblest tremblest - trembling trembl - tremblingly tremblingli - tremor tremor - trempling trempl - trench trench - trenchant trenchant - trenched trench - trencher trencher - trenchering trencher - trencherman trencherman - trenchers trencher - trenches trench - trenching trench - trent trent - tres tre - trespass trespass - trespasses trespass - tressel tressel - tresses tress - treys trei - trial trial - trials trial - trib trib - tribe tribe - tribes tribe - tribulation tribul - tribunal tribun - tribune tribun - tribunes tribun - tributaries tributari - tributary tributari - tribute tribut - tributes tribut - trice trice - trick trick - tricking trick - trickling trickl - tricks trick - tricksy tricksi - trident trident - tried tri - trier trier - trifle trifl - trifled trifl - trifler trifler - trifles trifl - trifling trifl - trigon trigon - trill trill - trim trim - trimly trimli - trimm trimm - trimmed trim - trimming trim - trims trim - trinculo trinculo - trinculos trinculo - trinkets trinket - trip trip - tripartite tripartit - tripe tripe - triple tripl - triplex triplex - tripoli tripoli - tripolis tripoli - tripp tripp - tripping trip - trippingly trippingli - trips trip - tristful trist - triton triton - triumph triumph - triumphant triumphant - triumphantly triumphantli - triumpher triumpher - triumphers triumpher - triumphing triumph - triumphs triumph - triumvir triumvir - triumvirate triumvir - triumvirs triumvir - triumviry triumviri - trivial trivial - troat troat - trod trod - trodden trodden - troiant troiant - troien troien - troilus troilu - troiluses troilus - trojan trojan - trojans trojan - troll troll - tromperies tromperi - trompet trompet - troop troop - trooping troop - troops troop - trop trop - trophies trophi - trophy trophi - tropically tropic - trot trot - troth troth - trothed troth - troths troth - trots trot - trotting trot - trouble troubl - troubled troubl - troubler troubler - troubles troubl - troublesome troublesom - troublest troublest - troublous troublou - trough trough - trout trout - trouts trout - trovato trovato - trow trow - trowel trowel - trowest trowest - troy troi - troyan troyan - troyans troyan - truant truant - truce truce - truckle truckl - trudge trudg - true true - trueborn trueborn - truepenny truepenni - truer truer - truest truest - truie truie - trull trull - trulls trull - truly truli - trump trump - trumpery trumperi - trumpet trumpet - trumpeter trumpet - trumpeters trumpet - trumpets trumpet - truncheon truncheon - truncheoners truncheon - trundle trundl - trunk trunk - trunks trunk - trust trust - trusted trust - truster truster - trusters truster - trusting trust - trusts trust - trusty trusti - truth truth - truths truth - try try - ts ts - tu tu - tuae tuae - tub tub - tubal tubal - tubs tub - tuck tuck - tucket tucket - tuesday tuesdai - tuft tuft - tufts tuft - tug tug - tugg tugg - tugging tug - tuition tuition - tullus tullu - tully tulli - tumble tumbl - tumbled tumbl - tumbler tumbler - tumbling tumbl - tumult tumult - tumultuous tumultu - tun tun - tune tune - tuneable tuneabl - tuned tune - tuners tuner - tunes tune - tunis tuni - tuns tun - tupping tup - turban turban - turbans turban - turbulence turbul - turbulent turbul - turd turd - turf turf - turfy turfi - turk turk - turkey turkei - turkeys turkei - turkish turkish - turks turk - turlygod turlygod - turmoil turmoil - turmoiled turmoil - turn turn - turnbull turnbul - turncoat turncoat - turncoats turncoat - turned turn - turneth turneth - turning turn - turnips turnip - turns turn - turph turph - turpitude turpitud - turquoise turquois - turret turret - turrets turret - turtle turtl - turtles turtl - turvy turvi - tuscan tuscan - tush tush - tut tut - tutor tutor - tutored tutor - tutors tutor - tutto tutto - twain twain - twang twang - twangling twangl - twas twa - tway twai - tweaks tweak - tween tween - twelfth twelfth - twelve twelv - twelvemonth twelvemonth - twentieth twentieth - twenty twenti - twere twere - twice twice - twig twig - twiggen twiggen - twigs twig - twilight twilight - twill twill - twilled twill - twin twin - twine twine - twink twink - twinkle twinkl - twinkled twinkl - twinkling twinkl - twinn twinn - twins twin - twire twire - twist twist - twisted twist - twit twit - twits twit - twitting twit - twixt twixt - two two - twofold twofold - twopence twopenc - twopences twopenc - twos two - twould twould - tyb tyb - tybalt tybalt - tybalts tybalt - tyburn tyburn - tying ty - tyke tyke - tymbria tymbria - type type - types type - typhon typhon - tyrannical tyrann - tyrannically tyrann - tyrannize tyrann - tyrannous tyrann - tyranny tyranni - tyrant tyrant - tyrants tyrant - tyrian tyrian - tyrrel tyrrel - u u - ubique ubiqu - udders udder - udge udg - uds ud - uglier uglier - ugliest ugliest - ugly ugli - ulcer ulcer - ulcerous ulcer - ulysses ulyss - um um - umber umber - umbra umbra - umbrage umbrag - umfrevile umfrevil - umpire umpir - umpires umpir - un un - unable unabl - unaccommodated unaccommod - unaccompanied unaccompani - unaccustom unaccustom - unaching unach - unacquainted unacquaint - unactive unact - unadvis unadvi - unadvised unadvis - unadvisedly unadvisedli - unagreeable unagre - unanel unanel - unanswer unansw - unappeas unappea - unapproved unapprov - unapt unapt - unaptness unapt - unarm unarm - unarmed unarm - unarms unarm - unassail unassail - unassailable unassail - unattainted unattaint - unattempted unattempt - unattended unattend - unauspicious unauspici - unauthorized unauthor - unavoided unavoid - unawares unawar - unback unback - unbak unbak - unbanded unband - unbar unbar - unbarb unbarb - unbashful unbash - unbated unbat - unbatter unbatt - unbecoming unbecom - unbefitting unbefit - unbegot unbegot - unbegotten unbegotten - unbelieved unbeliev - unbend unbend - unbent unbent - unbewail unbewail - unbid unbid - unbidden unbidden - unbind unbind - unbinds unbind - unbitted unbit - unbless unbless - unblest unblest - unbloodied unbloodi - unblown unblown - unbodied unbodi - unbolt unbolt - unbolted unbolt - unbonneted unbonnet - unbookish unbookish - unborn unborn - unbosom unbosom - unbound unbound - unbounded unbound - unbow unbow - unbowed unbow - unbrac unbrac - unbraced unbrac - unbraided unbraid - unbreathed unbreath - unbred unbr - unbreech unbreech - unbridled unbridl - unbroke unbrok - unbruis unbrui - unbruised unbruis - unbuckle unbuckl - unbuckles unbuckl - unbuckling unbuckl - unbuild unbuild - unburden unburden - unburdens unburden - unburied unburi - unburnt unburnt - unburthen unburthen - unbutton unbutton - unbuttoning unbutton - uncapable uncap - uncape uncap - uncase uncas - uncasing uncas - uncaught uncaught - uncertain uncertain - uncertainty uncertainti - unchain unchain - unchanging unchang - uncharge uncharg - uncharged uncharg - uncharitably uncharit - unchary unchari - unchaste unchast - uncheck uncheck - unchilded unchild - uncivil uncivil - unclaim unclaim - unclasp unclasp - uncle uncl - unclean unclean - uncleanliness uncleanli - uncleanly uncleanli - uncleanness unclean - uncles uncl - unclew unclew - unclog unclog - uncoined uncoin - uncolted uncolt - uncomeliness uncomeli - uncomfortable uncomfort - uncompassionate uncompassion - uncomprehensive uncomprehens - unconfinable unconfin - unconfirm unconfirm - unconfirmed unconfirm - unconquer unconqu - unconquered unconqu - unconsidered unconsid - unconstant unconst - unconstrain unconstrain - unconstrained unconstrain - uncontemn uncontemn - uncontroll uncontrol - uncorrected uncorrect - uncounted uncount - uncouple uncoupl - uncourteous uncourt - uncouth uncouth - uncover uncov - uncovered uncov - uncropped uncrop - uncross uncross - uncrown uncrown - unction unction - unctuous unctuou - uncuckolded uncuckold - uncurable uncur - uncurbable uncurb - uncurbed uncurb - uncurls uncurl - uncurrent uncurr - uncurse uncurs - undaunted undaunt - undeaf undeaf - undeck undeck - undeeded undeed - under under - underbearing underbear - underborne underborn - undercrest undercrest - underfoot underfoot - undergo undergo - undergoes undergo - undergoing undergo - undergone undergon - underground underground - underhand underhand - underlings underl - undermine undermin - underminers undermin - underneath underneath - underprizing underpr - underprop underprop - understand understand - understandeth understandeth - understanding understand - understandings understand - understands understand - understood understood - underta underta - undertake undertak - undertakeing undertak - undertaker undertak - undertakes undertak - undertaking undertak - undertakings undertak - undertook undertook - undervalu undervalu - undervalued undervalu - underwent underw - underwrit underwrit - underwrite underwrit - undescried undescri - undeserved undeserv - undeserver undeserv - undeservers undeserv - undeserving undeserv - undetermin undetermin - undid undid - undinted undint - undiscernible undiscern - undiscover undiscov - undishonoured undishonour - undispos undispo - undistinguishable undistinguish - undistinguished undistinguish - undividable undivid - undivided undivid - undivulged undivulg - undo undo - undoes undo - undoing undo - undone undon - undoubted undoubt - undoubtedly undoubtedli - undream undream - undress undress - undressed undress - undrown undrown - unduteous undut - undutiful unduti - une un - uneared unear - unearned unearn - unearthly unearthli - uneasines uneasin - uneasy uneasi - uneath uneath - uneducated uneduc - uneffectual uneffectu - unelected unelect - unequal unequ - uneven uneven - unexamin unexamin - unexecuted unexecut - unexpected unexpect - unexperienc unexperienc - unexperient unexperi - unexpressive unexpress - unfair unfair - unfaithful unfaith - unfallible unfal - unfam unfam - unfashionable unfashion - unfasten unfasten - unfather unfath - unfathered unfath - unfed unf - unfeed unfe - unfeeling unfeel - unfeigned unfeign - unfeignedly unfeignedli - unfellowed unfellow - unfelt unfelt - unfenced unfenc - unfilial unfili - unfill unfil - unfinish unfinish - unfirm unfirm - unfit unfit - unfitness unfit - unfix unfix - unfledg unfledg - unfold unfold - unfolded unfold - unfoldeth unfoldeth - unfolding unfold - unfolds unfold - unfool unfool - unforc unforc - unforced unforc - unforfeited unforfeit - unfortified unfortifi - unfortunate unfortun - unfought unfought - unfrequented unfrequ - unfriended unfriend - unfurnish unfurnish - ungain ungain - ungalled ungal - ungart ungart - ungarter ungart - ungenitur ungenitur - ungentle ungentl - ungentleness ungentl - ungently ungent - ungird ungird - ungodly ungodli - ungor ungor - ungot ungot - ungotten ungotten - ungovern ungovern - ungracious ungraci - ungrateful ungrat - ungravely ungrav - ungrown ungrown - unguarded unguard - unguem unguem - unguided unguid - unhack unhack - unhair unhair - unhallow unhallow - unhallowed unhallow - unhand unhand - unhandled unhandl - unhandsome unhandsom - unhang unhang - unhappied unhappi - unhappily unhappili - unhappiness unhappi - unhappy unhappi - unhardened unharden - unharm unharm - unhatch unhatch - unheard unheard - unhearts unheart - unheedful unheed - unheedfully unheedfulli - unheedy unheedi - unhelpful unhelp - unhidden unhidden - unholy unholi - unhop unhop - unhopefullest unhopefullest - unhorse unhors - unhospitable unhospit - unhous unhou - unhoused unhous - unhurtful unhurt - unicorn unicorn - unicorns unicorn - unimproved unimprov - uninhabitable uninhabit - uninhabited uninhabit - unintelligent unintellig - union union - unions union - unite unit - united unit - unity uniti - universal univers - universe univers - universities univers - university univers - unjointed unjoint - unjust unjust - unjustice unjustic - unjustly unjustli - unkennel unkennel - unkept unkept - unkind unkind - unkindest unkindest - unkindly unkindli - unkindness unkind - unking unk - unkinglike unkinglik - unkiss unkiss - unknit unknit - unknowing unknow - unknown unknown - unlace unlac - unlaid unlaid - unlawful unlaw - unlawfully unlawfulli - unlearn unlearn - unlearned unlearn - unless unless - unlesson unlesson - unletter unlett - unlettered unlett - unlick unlick - unlike unlik - unlikely unlik - unlimited unlimit - unlineal unlin - unlink unlink - unload unload - unloaded unload - unloading unload - unloads unload - unlock unlock - unlocks unlock - unlook unlook - unlooked unlook - unloos unloo - unloose unloos - unlov unlov - unloving unlov - unluckily unluckili - unlucky unlucki - unmade unmad - unmake unmak - unmanly unmanli - unmann unmann - unmanner unmann - unmannerd unmannerd - unmannerly unmannerli - unmarried unmarri - unmask unmask - unmasked unmask - unmasking unmask - unmasks unmask - unmast unmast - unmatch unmatch - unmatchable unmatch - unmatched unmatch - unmeasurable unmeasur - unmeet unmeet - unmellowed unmellow - unmerciful unmerci - unmeritable unmerit - unmeriting unmerit - unminded unmind - unmindfull unmindful - unmingled unmingl - unmitigable unmitig - unmitigated unmitig - unmix unmix - unmoan unmoan - unmov unmov - unmoved unmov - unmoving unmov - unmuffles unmuffl - unmuffling unmuffl - unmusical unmus - unmuzzle unmuzzl - unmuzzled unmuzzl - unnatural unnatur - unnaturally unnatur - unnaturalness unnatur - unnecessarily unnecessarili - unnecessary unnecessari - unneighbourly unneighbourli - unnerved unnerv - unnoble unnobl - unnoted unnot - unnumb unnumb - unnumber unnumb - unowed unow - unpack unpack - unpaid unpaid - unparagon unparagon - unparallel unparallel - unpartial unparti - unpath unpath - unpaved unpav - unpay unpai - unpeaceable unpeac - unpeg unpeg - unpeople unpeopl - unpeopled unpeopl - unperfect unperfect - unperfectness unperfect - unpick unpick - unpin unpin - unpink unpink - unpitied unpiti - unpitifully unpitifulli - unplagu unplagu - unplausive unplaus - unpleas unplea - unpleasant unpleas - unpleasing unpleas - unpolicied unpolici - unpolish unpolish - unpolished unpolish - unpolluted unpollut - unpossess unpossess - unpossessing unpossess - unpossible unposs - unpractis unpracti - unpregnant unpregn - unpremeditated unpremedit - unprepar unprepar - unprepared unprepar - unpress unpress - unprevailing unprevail - unprevented unprev - unpriz unpriz - unprizable unpriz - unprofitable unprofit - unprofited unprofit - unproper unprop - unproperly unproperli - unproportion unproport - unprovide unprovid - unprovided unprovid - unprovident unprovid - unprovokes unprovok - unprun unprun - unpruned unprun - unpublish unpublish - unpurged unpurg - unpurpos unpurpo - unqualitied unqual - unqueen unqueen - unquestion unquest - unquestionable unquestion - unquiet unquiet - unquietly unquietli - unquietness unquiet - unraised unrais - unrak unrak - unread unread - unready unreadi - unreal unreal - unreasonable unreason - unreasonably unreason - unreclaimed unreclaim - unreconciled unreconcil - unreconciliable unreconcili - unrecounted unrecount - unrecuring unrecur - unregarded unregard - unregist unregist - unrelenting unrel - unremovable unremov - unremovably unremov - unreprievable unrepriev - unresolv unresolv - unrespected unrespect - unrespective unrespect - unrest unrest - unrestor unrestor - unrestrained unrestrain - unreveng unreveng - unreverend unreverend - unreverent unrever - unrevers unrev - unrewarded unreward - unrighteous unright - unrightful unright - unripe unrip - unripp unripp - unrivall unrival - unroll unrol - unroof unroof - unroosted unroost - unroot unroot - unrough unrough - unruly unruli - unsafe unsaf - unsaluted unsalut - unsanctified unsanctifi - unsatisfied unsatisfi - unsavoury unsavouri - unsay unsai - unscalable unscal - unscann unscann - unscarr unscarr - unschool unschool - unscorch unscorch - unscour unscour - unscratch unscratch - unseal unseal - unseam unseam - unsearch unsearch - unseason unseason - unseasonable unseason - unseasonably unseason - unseasoned unseason - unseconded unsecond - unsecret unsecret - unseduc unseduc - unseeing unse - unseeming unseem - unseemly unseemli - unseen unseen - unseminar unseminar - unseparable unsepar - unserviceable unservic - unset unset - unsettle unsettl - unsettled unsettl - unsever unsev - unsex unsex - unshak unshak - unshaked unshak - unshaken unshaken - unshaped unshap - unshapes unshap - unsheath unsheath - unsheathe unsheath - unshorn unshorn - unshout unshout - unshown unshown - unshrinking unshrink - unshrubb unshrubb - unshunn unshunn - unshunnable unshunn - unsifted unsift - unsightly unsightli - unsinew unsinew - unsisting unsist - unskilful unskil - unskilfully unskilfulli - unskillful unskil - unslipping unslip - unsmirched unsmirch - unsoil unsoil - unsolicited unsolicit - unsorted unsort - unsought unsought - unsound unsound - unsounded unsound - unspeak unspeak - unspeakable unspeak - unspeaking unspeak - unsphere unspher - unspoke unspok - unspoken unspoken - unspotted unspot - unsquar unsquar - unstable unstabl - unstaid unstaid - unstain unstain - unstained unstain - unstanched unstanch - unstate unstat - unsteadfast unsteadfast - unstooping unstoop - unstringed unstring - unstuff unstuff - unsubstantial unsubstanti - unsuitable unsuit - unsuiting unsuit - unsullied unsulli - unsunn unsunn - unsur unsur - unsure unsur - unsuspected unsuspect - unsway unswai - unswayable unsway - unswayed unswai - unswear unswear - unswept unswept - unsworn unsworn - untainted untaint - untalk untalk - untangle untangl - untangled untangl - untasted untast - untaught untaught - untempering untemp - untender untend - untent untent - untented untent - unthankful unthank - unthankfulness unthank - unthink unthink - unthought unthought - unthread unthread - unthrift unthrift - unthrifts unthrift - unthrifty unthrifti - untie unti - untied unti - until until - untimber untimb - untimely untim - untir untir - untirable untir - untired untir - untitled untitl - unto unto - untold untold - untouch untouch - untoward untoward - untowardly untowardli - untraded untrad - untrain untrain - untrained untrain - untread untread - untreasur untreasur - untried untri - untrimmed untrim - untrod untrod - untrodden untrodden - untroubled untroubl - untrue untru - untrussing untruss - untruth untruth - untruths untruth - untucked untuck - untun untun - untune untun - untuneable untun - untutor untutor - untutored untutor - untwine untwin - unurg unurg - unus unu - unused unus - unusual unusu - unvalued unvalu - unvanquish unvanquish - unvarnish unvarnish - unveil unveil - unveiling unveil - unvenerable unvener - unvex unvex - unviolated unviol - unvirtuous unvirtu - unvisited unvisit - unvulnerable unvulner - unwares unwar - unwarily unwarili - unwash unwash - unwatch unwatch - unwearied unweari - unwed unw - unwedgeable unwedg - unweeded unweed - unweighed unweigh - unweighing unweigh - unwelcome unwelcom - unwept unwept - unwhipp unwhipp - unwholesome unwholesom - unwieldy unwieldi - unwilling unwil - unwillingly unwillingli - unwillingness unwilling - unwind unwind - unwiped unwip - unwise unwis - unwisely unwis - unwish unwish - unwished unwish - unwitted unwit - unwittingly unwittingli - unwonted unwont - unwooed unwoo - unworthier unworthi - unworthiest unworthiest - unworthily unworthili - unworthiness unworthi - unworthy unworthi - unwrung unwrung - unyok unyok - unyoke unyok - up up - upbraid upbraid - upbraided upbraid - upbraidings upbraid - upbraids upbraid - uphoarded uphoard - uphold uphold - upholdeth upholdeth - upholding uphold - upholds uphold - uplift uplift - uplifted uplift - upmost upmost - upon upon - upper upper - uprear uprear - upreared uprear - upright upright - uprighteously upright - uprightness upright - uprise upris - uprising upris - uproar uproar - uproars uproar - uprous uprou - upshoot upshoot - upshot upshot - upside upsid - upspring upspr - upstairs upstair - upstart upstart - upturned upturn - upward upward - upwards upward - urchin urchin - urchinfield urchinfield - urchins urchin - urg urg - urge urg - urged urg - urgent urgent - urges urg - urgest urgest - urging urg - urinal urin - urinals urin - urine urin - urn urn - urns urn - urs ur - ursa ursa - ursley urslei - ursula ursula - urswick urswick - us us - usage usag - usance usanc - usances usanc - use us - used us - useful us - useless useless - user user - uses us - usest usest - useth useth - usher usher - ushered usher - ushering usher - ushers usher - using us - usual usual - usually usual - usurer usur - usurers usur - usuries usuri - usuring usur - usurp usurp - usurpation usurp - usurped usurp - usurper usurp - usurpers usurp - usurping usurp - usurpingly usurpingli - usurps usurp - usury usuri - ut ut - utensil utensil - utensils utensil - utility util - utmost utmost - utt utt - utter utter - utterance utter - uttered utter - uttereth uttereth - uttering utter - utterly utterli - uttermost uttermost - utters utter - uy uy - v v - va va - vacancy vacanc - vacant vacant - vacation vacat - vade vade - vagabond vagabond - vagabonds vagabond - vagram vagram - vagrom vagrom - vail vail - vailed vail - vailing vail - vaillant vaillant - vain vain - vainer vainer - vainglory vainglori - vainly vainli - vainness vain - vais vai - valanc valanc - valance valanc - vale vale - valence valenc - valentine valentin - valentinus valentinu - valentio valentio - valeria valeria - valerius valeriu - vales vale - valiant valiant - valiantly valiantli - valiantness valiant - validity valid - vallant vallant - valley vallei - valleys vallei - vally valli - valor valor - valorous valor - valorously valor - valour valour - valu valu - valuation valuat - value valu - valued valu - valueless valueless - values valu - valuing valu - vane vane - vanish vanish - vanished vanish - vanishes vanish - vanishest vanishest - vanishing vanish - vanities vaniti - vanity vaniti - vanquish vanquish - vanquished vanquish - vanquisher vanquish - vanquishest vanquishest - vanquisheth vanquisheth - vant vant - vantage vantag - vantages vantag - vantbrace vantbrac - vapians vapian - vapor vapor - vaporous vapor - vapour vapour - vapours vapour - vara vara - variable variabl - variance varianc - variation variat - variations variat - varied vari - variest variest - variety varieti - varld varld - varlet varlet - varletry varletri - varlets varlet - varletto varletto - varnish varnish - varrius varriu - varro varro - vary vari - varying vari - vassal vassal - vassalage vassalag - vassals vassal - vast vast - vastidity vastid - vasty vasti - vat vat - vater vater - vaudemont vaudemont - vaughan vaughan - vault vault - vaultages vaultag - vaulted vault - vaulting vault - vaults vault - vaulty vaulti - vaumond vaumond - vaunt vaunt - vaunted vaunt - vaunter vaunter - vaunting vaunt - vauntingly vauntingli - vaunts vaunt - vauvado vauvado - vaux vaux - vaward vaward - ve ve - veal veal - vede vede - vehemence vehem - vehemency vehem - vehement vehement - vehor vehor - veil veil - veiled veil - veiling veil - vein vein - veins vein - vell vell - velure velur - velutus velutu - velvet velvet - vendible vendibl - venerable vener - venereal vener - venetia venetia - venetian venetian - venetians venetian - veneys venei - venge veng - vengeance vengeanc - vengeances vengeanc - vengeful veng - veni veni - venial venial - venice venic - venison venison - venit venit - venom venom - venomous venom - venomously venom - vent vent - ventages ventag - vented vent - ventidius ventidiu - ventricle ventricl - vents vent - ventur ventur - venture ventur - ventured ventur - ventures ventur - venturing ventur - venturous ventur - venue venu - venus venu - venuto venuto - ver ver - verb verb - verba verba - verbal verbal - verbatim verbatim - verbosity verbos - verdict verdict - verdun verdun - verdure verdur - vere vere - verefore verefor - verg verg - verge verg - vergers verger - verges verg - verier verier - veriest veriest - verified verifi - verify verifi - verily verili - veritable verit - verite verit - verities veriti - verity veriti - vermilion vermilion - vermin vermin - vernon vernon - verona verona - veronesa veronesa - versal versal - verse vers - verses vers - versing vers - vert vert - very veri - vesper vesper - vessel vessel - vessels vessel - vestal vestal - vestments vestment - vesture vestur - vetch vetch - vetches vetch - veux veux - vex vex - vexation vexat - vexations vexat - vexed vex - vexes vex - vexest vexest - vexeth vexeth - vexing vex - vi vi - via via - vial vial - vials vial - viand viand - viands viand - vic vic - vicar vicar - vice vice - vicegerent viceger - vicentio vicentio - viceroy viceroi - viceroys viceroi - vices vice - vici vici - vicious viciou - viciousness vicious - vict vict - victims victim - victor victor - victoress victoress - victories victori - victorious victori - victors victor - victory victori - victual victual - victuall victual - victuals victual - videlicet videlicet - video video - vides vide - videsne videsn - vidi vidi - vie vie - vied vi - vienna vienna - view view - viewest viewest - vieweth vieweth - viewing view - viewless viewless - views view - vigil vigil - vigilance vigil - vigilant vigil - vigitant vigit - vigour vigour - vii vii - viii viii - vile vile - vilely vile - vileness vile - viler viler - vilest vilest - vill vill - village villag - villager villag - villagery villageri - villages villag - villain villain - villainies villaini - villainous villain - villainously villain - villains villain - villainy villaini - villanies villani - villanous villan - villany villani - villiago villiago - villian villian - villianda villianda - villians villian - vinaigre vinaigr - vincentio vincentio - vincere vincer - vindicative vindic - vine vine - vinegar vinegar - vines vine - vineyard vineyard - vineyards vineyard - vint vint - vintner vintner - viol viol - viola viola - violate violat - violated violat - violates violat - violation violat - violator violat - violence violenc - violent violent - violenta violenta - violenteth violenteth - violently violent - violet violet - violets violet - viper viper - viperous viper - vipers viper - vir vir - virgilia virgilia - virgin virgin - virginal virgin - virginalling virginal - virginity virgin - virginius virginiu - virgins virgin - virgo virgo - virtue virtu - virtues virtu - virtuous virtuou - virtuously virtuous - visag visag - visage visag - visages visag - visard visard - viscount viscount - visible visibl - visibly visibl - vision vision - visions vision - visit visit - visitation visit - visitations visit - visited visit - visiting visit - visitings visit - visitor visitor - visitors visitor - visits visit - visor visor - vita vita - vitae vita - vital vital - vitement vitement - vitruvio vitruvio - vitx vitx - viva viva - vivant vivant - vive vive - vixen vixen - viz viz - vizaments vizament - vizard vizard - vizarded vizard - vizards vizard - vizor vizor - vlouting vlout - vocation vocat - vocativo vocativo - vocatur vocatur - voce voce - voic voic - voice voic - voices voic - void void - voided void - voiding void - voke voke - volable volabl - volant volant - volivorco volivorco - volley vollei - volquessen volquessen - volsce volsc - volsces volsc - volscian volscian - volscians volscian - volt volt - voltemand voltemand - volubility volubl - voluble volubl - volume volum - volumes volum - volumnia volumnia - volumnius volumniu - voluntaries voluntari - voluntary voluntari - voluptuously voluptu - voluptuousness voluptu - vomissement vomiss - vomit vomit - vomits vomit - vor vor - vore vore - vortnight vortnight - vot vot - votaries votari - votarist votarist - votarists votarist - votary votari - votre votr - vouch vouch - voucher voucher - vouchers voucher - vouches vouch - vouching vouch - vouchsaf vouchsaf - vouchsafe vouchsaf - vouchsafed vouchsaf - vouchsafes vouchsaf - vouchsafing vouchsaf - voudrais voudrai - vour vour - vous vou - voutsafe voutsaf - vow vow - vowed vow - vowel vowel - vowels vowel - vowing vow - vows vow - vox vox - voyage voyag - voyages voyag - vraiment vraiment - vulcan vulcan - vulgar vulgar - vulgarly vulgarli - vulgars vulgar - vulgo vulgo - vulnerable vulner - vulture vultur - vultures vultur - vurther vurther - w w - wad wad - waddled waddl - wade wade - waded wade - wafer wafer - waft waft - waftage waftag - wafting waft - wafts waft - wag wag - wage wage - wager wager - wagers wager - wages wage - wagging wag - waggish waggish - waggling waggl - waggon waggon - waggoner waggon - wagon wagon - wagoner wagon - wags wag - wagtail wagtail - wail wail - wailful wail - wailing wail - wails wail - wain wain - wainropes wainrop - wainscot wainscot - waist waist - wait wait - waited wait - waiter waiter - waiteth waiteth - waiting wait - waits wait - wak wak - wake wake - waked wake - wakefield wakefield - waken waken - wakened waken - wakes wake - wakest wakest - waking wake - wales wale - walk walk - walked walk - walking walk - walks walk - wall wall - walled wall - wallet wallet - wallets wallet - wallon wallon - walloon walloon - wallow wallow - walls wall - walnut walnut - walter walter - wan wan - wand wand - wander wander - wanderer wander - wanderers wander - wandering wander - wanders wander - wands wand - wane wane - waned wane - wanes wane - waning wane - wann wann - want want - wanted want - wanteth wanteth - wanting want - wanton wanton - wantonly wantonli - wantonness wanton - wantons wanton - wants want - wappen wappen - war war - warble warbl - warbling warbl - ward ward - warded ward - warden warden - warder warder - warders warder - wardrobe wardrob - wardrop wardrop - wards ward - ware ware - wares ware - warily warili - warkworth warkworth - warlike warlik - warm warm - warmed warm - warmer warmer - warming warm - warms warm - warmth warmth - warn warn - warned warn - warning warn - warnings warn - warns warn - warp warp - warped warp - warr warr - warrant warrant - warranted warrant - warranteth warranteth - warrantise warrantis - warrantize warrant - warrants warrant - warranty warranti - warren warren - warrener warren - warring war - warrior warrior - warriors warrior - wars war - wart wart - warwick warwick - warwickshire warwickshir - wary wari - was wa - wash wash - washed wash - washer washer - washes wash - washford washford - washing wash - wasp wasp - waspish waspish - wasps wasp - wassail wassail - wassails wassail - wast wast - waste wast - wasted wast - wasteful wast - wasters waster - wastes wast - wasting wast - wat wat - watch watch - watched watch - watchers watcher - watches watch - watchful watch - watching watch - watchings watch - watchman watchman - watchmen watchmen - watchword watchword - water water - waterdrops waterdrop - watered water - waterfly waterfli - waterford waterford - watering water - waterish waterish - waterpots waterpot - waterrugs waterrug - waters water - waterton waterton - watery wateri - wav wav - wave wave - waved wave - waver waver - waverer waver - wavering waver - waves wave - waving wave - waw waw - wawl wawl - wax wax - waxed wax - waxen waxen - waxes wax - waxing wax - way wai - waylaid waylaid - waylay waylai - ways wai - wayward wayward - waywarder wayward - waywardness wayward - we we - weak weak - weaken weaken - weakens weaken - weaker weaker - weakest weakest - weakling weakl - weakly weakli - weakness weak - weal weal - wealsmen wealsmen - wealth wealth - wealthiest wealthiest - wealthily wealthili - wealthy wealthi - wealtlly wealtlli - wean wean - weapon weapon - weapons weapon - wear wear - wearer wearer - wearers wearer - wearied weari - wearies weari - weariest weariest - wearily wearili - weariness weari - wearing wear - wearisome wearisom - wears wear - weary weari - weasel weasel - weather weather - weathercock weathercock - weathers weather - weav weav - weave weav - weaver weaver - weavers weaver - weaves weav - weaving weav - web web - wed wed - wedded wed - wedding wed - wedg wedg - wedged wedg - wedges wedg - wedlock wedlock - wednesday wednesdai - weed weed - weeded weed - weeder weeder - weeding weed - weeds weed - weedy weedi - week week - weeke week - weekly weekli - weeks week - ween ween - weening ween - weep weep - weeper weeper - weeping weep - weepingly weepingli - weepings weep - weeps weep - weet weet - weigh weigh - weighed weigh - weighing weigh - weighs weigh - weight weight - weightier weightier - weightless weightless - weights weight - weighty weighti - weird weird - welcom welcom - welcome welcom - welcomer welcom - welcomes welcom - welcomest welcomest - welfare welfar - welkin welkin - well well - wells well - welsh welsh - welshman welshman - welshmen welshmen - welshwomen welshwomen - wench wench - wenches wench - wenching wench - wend wend - went went - wept wept - weraday weradai - were were - wert wert - west west - western western - westminster westminst - westmoreland westmoreland - westward westward - wet wet - wether wether - wetting wet - wezand wezand - whale whale - whales whale - wharf wharf - wharfs wharf - what what - whate whate - whatever whatev - whatsoe whatso - whatsoever whatsoev - whatsome whatsom - whe whe - wheat wheat - wheaten wheaten - wheel wheel - wheeling wheel - wheels wheel - wheer wheer - wheeson wheeson - wheezing wheez - whelk whelk - whelks whelk - whelm whelm - whelp whelp - whelped whelp - whelps whelp - when when - whenas whena - whence whenc - whencesoever whencesoev - whene whene - whenever whenev - whensoever whensoev - where where - whereabout whereabout - whereas wherea - whereat whereat - whereby wherebi - wherefore wherefor - wherein wherein - whereinto whereinto - whereof whereof - whereon whereon - whereout whereout - whereso whereso - wheresoe whereso - wheresoever wheresoev - wheresome wheresom - whereto whereto - whereuntil whereuntil - whereunto whereunto - whereupon whereupon - wherever wherev - wherewith wherewith - wherewithal wherewith - whet whet - whether whether - whetstone whetston - whetted whet - whew whew - whey whei - which which - whiff whiff - whiffler whiffler - while while - whiles while - whilst whilst - whin whin - whine whine - whined whine - whinid whinid - whining whine - whip whip - whipp whipp - whippers whipper - whipping whip - whips whip - whipster whipster - whipstock whipstock - whipt whipt - whirl whirl - whirled whirl - whirligig whirligig - whirling whirl - whirlpool whirlpool - whirls whirl - whirlwind whirlwind - whirlwinds whirlwind - whisp whisp - whisper whisper - whispering whisper - whisperings whisper - whispers whisper - whist whist - whistle whistl - whistles whistl - whistling whistl - whit whit - white white - whitehall whitehal - whitely white - whiteness white - whiter whiter - whites white - whitest whitest - whither whither - whiting white - whitmore whitmor - whitsters whitster - whitsun whitsun - whittle whittl - whizzing whizz - who who - whoa whoa - whoe whoe - whoever whoever - whole whole - wholesom wholesom - wholesome wholesom - wholly wholli - whom whom - whoobub whoobub - whoop whoop - whooping whoop - whor whor - whore whore - whoremaster whoremast - whoremasterly whoremasterli - whoremonger whoremong - whores whore - whoreson whoreson - whoresons whoreson - whoring whore - whorish whorish - whose whose - whoso whoso - whosoe whoso - whosoever whosoev - why why - wi wi - wick wick - wicked wick - wickednes wickedn - wickedness wicked - wicket wicket - wicky wicki - wid wid - wide wide - widens widen - wider wider - widow widow - widowed widow - widower widow - widowhood widowhood - widows widow - wield wield - wife wife - wight wight - wights wight - wild wild - wildcats wildcat - wilder wilder - wilderness wilder - wildest wildest - wildfire wildfir - wildly wildli - wildness wild - wilds wild - wiles wile - wilful wil - wilfull wilful - wilfully wilfulli - wilfulnes wilfuln - wilfulness wil - will will - willed will - willers willer - willeth willeth - william william - williams william - willing will - willingly willingli - willingness willing - willoughby willoughbi - willow willow - wills will - wilt wilt - wiltshire wiltshir - wimpled wimpl - win win - wince winc - winch winch - winchester winchest - wincot wincot - wind wind - winded wind - windgalls windgal - winding wind - windlasses windlass - windmill windmil - window window - windows window - windpipe windpip - winds wind - windsor windsor - windy windi - wine wine - wing wing - winged wing - wingfield wingfield - wingham wingham - wings wing - wink wink - winking wink - winks wink - winner winner - winners winner - winning win - winnow winnow - winnowed winnow - winnows winnow - wins win - winter winter - winterly winterli - winters winter - wip wip - wipe wipe - wiped wipe - wipes wipe - wiping wipe - wire wire - wires wire - wiry wiri - wisdom wisdom - wisdoms wisdom - wise wise - wiselier wiseli - wisely wise - wiser wiser - wisest wisest - wish wish - wished wish - wisher wisher - wishers wisher - wishes wish - wishest wishest - wisheth wisheth - wishful wish - wishing wish - wishtly wishtli - wisp wisp - wist wist - wit wit - witb witb - witch witch - witchcraft witchcraft - witches witch - witching witch - with with - withal withal - withdraw withdraw - withdrawing withdraw - withdrawn withdrawn - withdrew withdrew - wither wither - withered wither - withering wither - withers wither - withheld withheld - withhold withhold - withholds withhold - within within - withold withold - without without - withstand withstand - withstanding withstand - withstood withstood - witless witless - witness wit - witnesses wit - witnesseth witnesseth - witnessing wit - wits wit - witted wit - wittenberg wittenberg - wittiest wittiest - wittily wittili - witting wit - wittingly wittingli - wittol wittol - wittolly wittolli - witty witti - wiv wiv - wive wive - wived wive - wives wive - wiving wive - wizard wizard - wizards wizard - wo wo - woe woe - woeful woeful - woefull woeful - woefullest woefullest - woes woe - woful woful - wolf wolf - wolfish wolfish - wolsey wolsei - wolves wolv - wolvish wolvish - woman woman - womanhood womanhood - womanish womanish - womankind womankind - womanly womanli - womb womb - wombs womb - womby wombi - women women - won won - woncot woncot - wond wond - wonder wonder - wondered wonder - wonderful wonder - wonderfully wonderfulli - wondering wonder - wonders wonder - wondrous wondrou - wondrously wondrous - wont wont - wonted wont - woo woo - wood wood - woodbine woodbin - woodcock woodcock - woodcocks woodcock - wooden wooden - woodland woodland - woodman woodman - woodmonger woodmong - woods wood - woodstock woodstock - woodville woodvil - wooed woo - wooer wooer - wooers wooer - wooes wooe - woof woof - wooing woo - wooingly wooingli - wool wool - woollen woollen - woolly woolli - woolsack woolsack - woolsey woolsei - woolward woolward - woos woo - wor wor - worcester worcest - word word - words word - wore wore - worins worin - work work - workers worker - working work - workings work - workman workman - workmanly workmanli - workmanship workmanship - workmen workmen - works work - worky worki - world world - worldlings worldl - worldly worldli - worlds world - worm worm - worms worm - wormwood wormwood - wormy wormi - worn worn - worried worri - worries worri - worry worri - worrying worri - worse wors - worser worser - worship worship - worshipful worship - worshipfully worshipfulli - worshipp worshipp - worshipper worshipp - worshippers worshipp - worshippest worshippest - worships worship - worst worst - worsted worst - wort wort - worth worth - worthied worthi - worthier worthier - worthies worthi - worthiest worthiest - worthily worthili - worthiness worthi - worthless worthless - worths worth - worthy worthi - worts wort - wot wot - wots wot - wotting wot - wouid wouid - would would - wouldest wouldest - wouldst wouldst - wound wound - wounded wound - wounding wound - woundings wound - woundless woundless - wounds wound - wouns woun - woven woven - wow wow - wrack wrack - wrackful wrack - wrangle wrangl - wrangler wrangler - wranglers wrangler - wrangling wrangl - wrap wrap - wrapp wrapp - wraps wrap - wrapt wrapt - wrath wrath - wrathful wrath - wrathfully wrathfulli - wraths wrath - wreak wreak - wreakful wreak - wreaks wreak - wreath wreath - wreathed wreath - wreathen wreathen - wreaths wreath - wreck wreck - wrecked wreck - wrecks wreck - wren wren - wrench wrench - wrenching wrench - wrens wren - wrest wrest - wrested wrest - wresting wrest - wrestle wrestl - wrestled wrestl - wrestler wrestler - wrestling wrestl - wretch wretch - wretchcd wretchcd - wretched wretch - wretchedness wretched - wretches wretch - wring wring - wringer wringer - wringing wring - wrings wring - wrinkle wrinkl - wrinkled wrinkl - wrinkles wrinkl - wrist wrist - wrists wrist - writ writ - write write - writer writer - writers writer - writes write - writhled writhl - writing write - writings write - writs writ - written written - wrong wrong - wronged wrong - wronger wronger - wrongful wrong - wrongfully wrongfulli - wronging wrong - wrongly wrongli - wrongs wrong - wronk wronk - wrote wrote - wroth wroth - wrought wrought - wrung wrung - wry wry - wrying wry - wt wt - wul wul - wye wye - x x - xanthippe xanthipp - xi xi - xii xii - xiii xiii - xiv xiv - xv xv - y y - yard yard - yards yard - yare yare - yarely yare - yarn yarn - yaughan yaughan - yaw yaw - yawn yawn - yawning yawn - ycleped yclepe - ycliped yclipe - ye ye - yea yea - yead yead - year year - yearly yearli - yearn yearn - yearns yearn - years year - yeas yea - yeast yeast - yedward yedward - yell yell - yellow yellow - yellowed yellow - yellowing yellow - yellowness yellow - yellows yellow - yells yell - yelping yelp - yeoman yeoman - yeomen yeomen - yerk yerk - yes ye - yesterday yesterdai - yesterdays yesterdai - yesternight yesternight - yesty yesti - yet yet - yew yew - yicld yicld - yield yield - yielded yield - yielder yielder - yielders yielder - yielding yield - yields yield - yok yok - yoke yoke - yoked yoke - yokefellow yokefellow - yokes yoke - yoketh yoketh - yon yon - yond yond - yonder yonder - yongrey yongrei - yore yore - yorick yorick - york york - yorkists yorkist - yorks york - yorkshire yorkshir - you you - young young - younger younger - youngest youngest - youngling youngl - younglings youngl - youngly youngli - younker younker - your your - yours your - yourself yourself - yourselves yourselv - youth youth - youthful youth - youths youth - youtli youtli - zanies zani - zany zani - zeal zeal - zealous zealou - zeals zeal - zed zed - zenelophon zenelophon - zenith zenith - zephyrs zephyr - zir zir - zo zo - zodiac zodiac - zodiacs zodiac - zone zone - zounds zound - zwagger zwagger -} - -# Create a full-text index to use for testing the stemmer. -# -db close -sqlite3 db :memory: -db eval { - CREATE VIRTUAL TABLE t1 USING fts1(word, tokenize Porter); -} - -foreach {pfrom pto} $porter_test_data { - do_test fts1porter-$pfrom { - execsql { - DELETE FROM t1_term; - DELETE FROM t1_content; - INSERT INTO t1(word) VALUES($pfrom); - SELECT term FROM t1_term; - } - } $pto -} - -finish_test diff --git a/test/fts2.test b/test/fts2.test deleted file mode 100644 index b1e295936..000000000 --- a/test/fts2.test +++ /dev/null @@ -1,67 +0,0 @@ -# 2008 July 22 -# -# 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 runs all tests. -# -# $Id: fts2.test,v 1.2 2008/07/23 18:17:32 drh Exp $ - -proc lshift {lvar} { - upvar $lvar l - set ret [lindex $l 0] - set l [lrange $l 1 end] - return $ret -} -while {[set arg [lshift argv]] != ""} { - switch -- $arg { - -sharedpagercache { - sqlite3_enable_shared_cache 1 - } - -soak { - set G(issoak) 1 - } - default { - set argv [linsert $argv 0 $arg] - break - } - } -} - -set testdir [file dirname $argv0] -source $testdir/tester.tcl -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - return -} -rename finish_test really_finish_test -proc finish_test {} {} -set G(isquick) 1 - -set EXCLUDE { - fts2.test -} - -# Files to include in the test. If this list is empty then everything -# that is not in the EXCLUDE list is run. -# -set INCLUDE { -} - -foreach testfile [lsort -dictionary [glob $testdir/fts2*.test]] { - set tail [file tail $testfile] - if {[lsearch -exact $EXCLUDE $tail]>=0} continue - if {[llength $INCLUDE]>0 && [lsearch -exact $INCLUDE $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 -really_finish_test diff --git a/test/fts2a.test b/test/fts2a.test deleted file mode 100644 index 2d1566fcc..000000000 --- a/test/fts2a.test +++ /dev/null @@ -1,202 +0,0 @@ -# 2006 September 9 -# -# 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 is testing the FTS2 module. -# -# $Id: fts2a.test,v 1.2 2007/05/21 21:59:18 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Construct a full-text search table containing five keywords: -# one, two, three, four, and five, in various combinations. The -# rowid for each will be a bitmask for the elements it contains. -# -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(content); - INSERT INTO t1(content) VALUES('one'); - INSERT INTO t1(content) VALUES('two'); - INSERT INTO t1(content) VALUES('one two'); - INSERT INTO t1(content) VALUES('three'); - INSERT INTO t1(content) VALUES('one three'); - INSERT INTO t1(content) VALUES('two three'); - INSERT INTO t1(content) VALUES('one two three'); - INSERT INTO t1(content) VALUES('four'); - INSERT INTO t1(content) VALUES('one four'); - INSERT INTO t1(content) VALUES('two four'); - INSERT INTO t1(content) VALUES('one two four'); - INSERT INTO t1(content) VALUES('three four'); - INSERT INTO t1(content) VALUES('one three four'); - INSERT INTO t1(content) VALUES('two three four'); - INSERT INTO t1(content) VALUES('one two three four'); - INSERT INTO t1(content) VALUES('five'); - INSERT INTO t1(content) VALUES('one five'); - INSERT INTO t1(content) VALUES('two five'); - INSERT INTO t1(content) VALUES('one two five'); - INSERT INTO t1(content) VALUES('three five'); - INSERT INTO t1(content) VALUES('one three five'); - INSERT INTO t1(content) VALUES('two three five'); - INSERT INTO t1(content) VALUES('one two three five'); - INSERT INTO t1(content) VALUES('four five'); - INSERT INTO t1(content) VALUES('one four five'); - INSERT INTO t1(content) VALUES('two four five'); - INSERT INTO t1(content) VALUES('one two four five'); - INSERT INTO t1(content) VALUES('three four five'); - INSERT INTO t1(content) VALUES('one three four five'); - INSERT INTO t1(content) VALUES('two three four five'); - INSERT INTO t1(content) VALUES('one two three four five'); -} - -do_test fts2a-1.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one'} -} {1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31} -do_test fts2a-1.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one two'} -} {3 7 11 15 19 23 27 31} -do_test fts2a-1.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two one'} -} {3 7 11 15 19 23 27 31} -do_test fts2a-1.4 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one two three'} -} {7 15 23 31} -do_test fts2a-1.5 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one three two'} -} {7 15 23 31} -do_test fts2a-1.6 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two three one'} -} {7 15 23 31} -do_test fts2a-1.7 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two one three'} -} {7 15 23 31} -do_test fts2a-1.8 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three one two'} -} {7 15 23 31} -do_test fts2a-1.9 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three two one'} -} {7 15 23 31} -do_test fts2a-1.10 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one two THREE'} -} {7 15 23 31} -do_test fts2a-1.11 { - execsql {SELECT rowid FROM t1 WHERE content MATCH ' ONE Two three '} -} {7 15 23 31} - -do_test fts2a-2.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one"'} -} {1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31} -do_test fts2a-2.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one two"'} -} {3 7 11 15 19 23 27 31} -do_test fts2a-2.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"two one"'} -} {} -do_test fts2a-2.4 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one two three"'} -} {7 15 23 31} -do_test fts2a-2.5 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one three two"'} -} {} -do_test fts2a-2.6 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one two three four"'} -} {15 31} -do_test fts2a-2.7 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one three two four"'} -} {} -do_test fts2a-2.8 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one three five"'} -} {21} -do_test fts2a-2.9 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one three" five'} -} {21 29} -do_test fts2a-2.10 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'five "one three"'} -} {21 29} -do_test fts2a-2.11 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'five "one three" four'} -} {29} -do_test fts2a-2.12 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'five four "one three"'} -} {29} -do_test fts2a-2.13 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one three" four five'} -} {29} - -do_test fts2a-3.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one'} -} {1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31} -do_test fts2a-3.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one -two'} -} {1 5 9 13 17 21 25 29} -do_test fts2a-3.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '-two one'} -} {1 5 9 13 17 21 25 29} - -do_test fts2a-4.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one OR two'} -} {1 2 3 5 6 7 9 10 11 13 14 15 17 18 19 21 22 23 25 26 27 29 30 31} -do_test fts2a-4.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH '"one two" OR three'} -} {3 4 5 6 7 11 12 13 14 15 19 20 21 22 23 27 28 29 30 31} -do_test fts2a-4.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three OR "one two"'} -} {3 4 5 6 7 11 12 13 14 15 19 20 21 22 23 27 28 29 30 31} -do_test fts2a-4.4 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one two OR three'} -} {3 5 7 11 13 15 19 21 23 27 29 31} -do_test fts2a-4.5 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three OR two one'} -} {3 5 7 11 13 15 19 21 23 27 29 31} -do_test fts2a-4.6 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one two OR three OR four'} -} {3 5 7 9 11 13 15 19 21 23 25 27 29 31} -do_test fts2a-4.7 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two OR three OR four one'} -} {3 5 7 9 11 13 15 19 21 23 25 27 29 31} - -# Test the ability to handle NULL content -# -do_test fts2a-5.1 { - execsql {INSERT INTO t1(content) VALUES(NULL)} -} {} -do_test fts2a-5.2 { - set rowid [db last_insert_rowid] - execsql {SELECT content FROM t1 WHERE rowid=$rowid} -} {{}} -do_test fts2a-5.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH NULL} -} {} - -# Test the ability to handle non-positive rowids -# -do_test fts2a-6.0 { - execsql {INSERT INTO t1(rowid, content) VALUES(0, 'four five')} -} {} -do_test fts2a-6.1 { - execsql {SELECT content FROM t1 WHERE rowid = 0} -} {{four five}} -do_test fts2a-6.2 { - execsql {INSERT INTO t1(rowid, content) VALUES(-1, 'three four')} -} {} -do_test fts2a-6.3 { - execsql {SELECT content FROM t1 WHERE rowid = -1} -} {{three four}} -do_test fts2a-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} - -finish_test diff --git a/test/fts2b.test b/test/fts2b.test deleted file mode 100644 index 169cd8a0a..000000000 --- a/test/fts2b.test +++ /dev/null @@ -1,147 +0,0 @@ -# 2006 September 13 -# -# 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 is testing the FTS2 module. -# -# $Id: fts2b.test,v 1.1 2006/10/19 23:36:26 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Fill the full-text index "t1" with phrases in english, spanish, -# and german. For the i-th row, fill in the names for the bits -# that are set in the value of i. The least significant bit is -# 1. For example, the value 5 is 101 in binary which will be -# converted to "one three" in english. -# -proc fill_multilanguage_fulltext_t1 {} { - set english {one two three four five} - set spanish {un dos tres cuatro cinco} - set german {eine zwei drei vier funf} - - for {set i 1} {$i<=31} {incr i} { - set cmd "INSERT INTO t1 VALUES" - set vset {} - foreach lang {english spanish german} { - set words {} - for {set j 0; set k 1} {$j<5} {incr j; incr k $k} { - if {$k&$i} {lappend words [lindex [set $lang] $j]} - } - lappend vset "'$words'" - } - set sql "INSERT INTO t1(english,spanish,german) VALUES([join $vset ,])" - # puts $sql - db eval $sql - } -} - -# Construct a full-text search table containing five keywords: -# one, two, three, four, and five, in various combinations. The -# rowid for each will be a bitmask for the elements it contains. -# -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(english,spanish,german); -} -fill_multilanguage_fulltext_t1 - -do_test fts2b-1.1 { - execsql {SELECT rowid FROM t1 WHERE english MATCH 'one'} -} {1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31} -do_test fts2b-1.2 { - execsql {SELECT rowid FROM t1 WHERE spanish MATCH 'one'} -} {} -do_test fts2b-1.3 { - execsql {SELECT rowid FROM t1 WHERE german MATCH 'one'} -} {} -do_test fts2b-1.4 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'one'} -} {1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31} -do_test fts2b-1.5 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'one dos drei'} -} {7 15 23 31} -do_test fts2b-1.6 { - execsql {SELECT english, spanish, german FROM t1 WHERE rowid=1} -} {one un eine} -do_test fts2b-1.7 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH '"one un"'} -} {} - -do_test fts2b-2.1 { - execsql { - CREATE VIRTUAL TABLE t2 USING fts2(from,to); - INSERT INTO t2([from],[to]) VALUES ('one two three', 'four five six'); - SELECT [from], [to] FROM t2 - } -} {{one two three} {four five six}} - - -# Compute an SQL string that contains the words one, two, three,... to -# describe bits set in the value $i. Only the lower 5 bits are examined. -# -proc wordset {i} { - set x {} - for {set j 0; set k 1} {$j<5} {incr j; incr k $k} { - if {$k&$i} {lappend x [lindex {one two three four five} $j]} - } - return '$x' -} - -# Create a new FTS table with three columns: -# -# norm: words for the bits of rowid -# plusone: words for the bits of rowid+1 -# invert: words for the bits of ~rowid -# -db eval { - CREATE VIRTUAL TABLE t4 USING fts2([norm],'plusone',"invert"); -} -for {set i 1} {$i<=15} {incr i} { - set vset [list [wordset $i] [wordset [expr {$i+1}]] [wordset [expr {~$i}]]] - db eval "INSERT INTO t4(norm,plusone,invert) VALUES([join $vset ,]);" -} - -do_test fts2b-4.1 { - execsql {SELECT rowid FROM t4 WHERE t4 MATCH 'norm:one'} -} {1 3 5 7 9 11 13 15} -do_test fts2b-4.2 { - execsql {SELECT rowid FROM t4 WHERE norm MATCH 'one'} -} {1 3 5 7 9 11 13 15} -do_test fts2b-4.3 { - execsql {SELECT rowid FROM t4 WHERE t4 MATCH 'one'} -} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15} -do_test fts2b-4.4 { - execsql {SELECT rowid FROM t4 WHERE t4 MATCH 'plusone:one'} -} {2 4 6 8 10 12 14} -do_test fts2b-4.5 { - execsql {SELECT rowid FROM t4 WHERE plusone MATCH 'one'} -} {2 4 6 8 10 12 14} -do_test fts2b-4.6 { - execsql {SELECT rowid FROM t4 WHERE t4 MATCH 'norm:one plusone:two'} -} {1 5 9 13} -do_test fts2b-4.7 { - execsql {SELECT rowid FROM t4 WHERE t4 MATCH 'norm:one two'} -} {1 3 5 7 9 11 13 15} -do_test fts2b-4.8 { - execsql {SELECT rowid FROM t4 WHERE t4 MATCH 'plusone:two norm:one'} -} {1 5 9 13} -do_test fts2b-4.9 { - execsql {SELECT rowid FROM t4 WHERE t4 MATCH 'two norm:one'} -} {1 3 5 7 9 11 13 15} - - -finish_test diff --git a/test/fts2c.test b/test/fts2c.test deleted file mode 100644 index cc6c9bbb5..000000000 --- a/test/fts2c.test +++ /dev/null @@ -1,1213 +0,0 @@ -# 2006 September 14 -# -# 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 is testing the FTS2 module. -# -# $Id: fts2c.test,v 1.1 2006/10/19 23:36:26 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Create a table of sample email data. The data comes from email -# archives of Enron executives that was published as part of the -# litigation against that company. -# -do_test fts2c-1.1 { - db eval { - CREATE VIRTUAL TABLE email USING fts2([from],[to],subject,body); - BEGIN TRANSACTION; -INSERT INTO email([from],[to],subject,body) VALUES('savita.puthigai@enron.com', 'traders.eol@enron.com, traders.eol@enron.com', 'EnronOnline- Change to Autohedge', 'Effective Monday, October 22, 2001 the following changes will be made to the Autohedge functionality on EnronOnline. - -The volume on the hedge will now respect the minimum volume and volume increment settings on the parent product. See rules below: - -? If the transaction volume on the child is less than half of the parent''s minimum volume no hedge will occur. -? If the transaction volume on the child is more than half the parent''s minimum volume but less than half the volume increment on the parent, the hedge will volume will be the parent''s minimum volume. -? For all other volumes, the same rounding rules will apply based on the volume increment on the parent product. - -Please see example below: - -Parent''s Settings: -Minimum: 5000 -Increment: 1000 - -Volume on Autohedge transaction Volume Hedged -1 - 2499 0 -2500 - 5499 5000 -5500 - 6499 6000'); -INSERT INTO email([from],[to],subject,body) VALUES('dana.davis@enron.com', 'laynie.east@enron.com, lisa.king@enron.com, lisa.best@enron.com,', 'Leaving Early', 'FYI: -If it''s ok with everyone''s needs, I would like to leave @4pm. If you think -you will need my assistance past the 4 o''clock hour just let me know; I''ll -be more than willing to stay.'); -INSERT INTO email([from],[to],subject,body) VALUES('enron_update@concureworkplace.com', 'louise.kitchen@enron.com', '<> - CC02.06.02', 'The following expense report is ready for approval: - -Employee Name: Christopher F. Calger -Status last changed by: Mollie E. Gustafson Ms -Expense Report Name: CC02.06.02 -Report Total: $3,972.93 -Amount Due Employee: $3,972.93 - - -To approve this expense report, click on the following link for Concur Expense. -http://expensexms.enron.com'); -INSERT INTO email([from],[to],subject,body) VALUES('jeff.duff@enron.com', 'julie.johnson@enron.com', 'Work request', 'Julie, - -Could you print off the current work request report by 1:30 today? - -Gentlemen, - -I''d like to review this today at 1:30 in our office. Also, could you provide -me with your activity reports so I can have Julie enter this information. - -JD'); -INSERT INTO email([from],[to],subject,body) VALUES('v.weldon@enron.com', 'gary.l.carrier@usa.dupont.com, scott.joyce@bankofamerica.com', 'Enron News', 'This could turn into something big.... -http://biz.yahoo.com/rf/010129/n29305829.html'); -INSERT INTO email([from],[to],subject,body) VALUES('mark.haedicke@enron.com', 'paul.simons@enron.com', 'Re: First Polish Deal!', 'Congrats! Things seem to be building rapidly now on the Continent. Mark'); -INSERT INTO email([from],[to],subject,body) VALUES('e..carter@enron.com', 't..robinson@enron.com', 'FW: Producers Newsletter 9-24-2001', ' -The producer lumber pricing sheet. - -----Original Message----- -From: Johnson, Jay -Sent: Tuesday, October 16, 2001 3:42 PM -To: Carter, Karen E. -Subject: FW: Producers Newsletter 9-24-2001 - - - - -----Original Message----- -From: Daigre, Sergai -Sent: Friday, September 21, 2001 8:33 PM -Subject: Producers Newsletter 9-24-2001 - - '); -INSERT INTO email([from],[to],subject,body) VALUES('david.delainey@enron.com', 'kenneth.lay@enron.com', 'Greater Houston Partnership', 'Ken, in response to the letter from Mr Miguel San Juan, my suggestion would -be to offer up the Falcon for their use; however, given the tight time frame -and your recent visit with Mr. Fox that it would be difficult for either you -or me to participate. - -I spoke to Max and he agrees with this approach. - -I hope this meets with your approval. - -Regards -Delainey'); -INSERT INTO email([from],[to],subject,body) VALUES('lachandra.fenceroy@enron.com', 'lindy.donoho@enron.com', 'FW: Bus Applications Meeting Follow Up', 'Lindy, - -Here is the original memo we discussed earlier. Please provide any information that you may have. - -Your cooperation is greatly appreciated. - -Thanks, - -lachandra.fenceroy@enron.com -713.853.3884 -877.498.3401 Pager - - -----Original Message----- -From: Bisbee, Joanne -Sent: Wednesday, September 26, 2001 7:50 AM -To: Fenceroy, LaChandra -Subject: FW: Bus Applications Meeting Follow Up - -Lachandra, Please get with David Duff today and see what this is about. Who are our TW accounting business users? - - -----Original Message----- -From: Koh, Wendy -Sent: Tuesday, September 25, 2001 2:41 PM -To: Bisbee, Joanne -Subject: Bus Applications Meeting Follow Up - -Lisa brought up a TW change effective Nov 1. It involves eliminating a turnback surcharge. I have no other information, but you might check with the business folks for any system changes required. - -Wendy'); -INSERT INTO email([from],[to],subject,body) VALUES('danny.mccarty@enron.com', 'fran.fagan@enron.com', 'RE: worksheets', 'Fran, - If Julie''s merit needs to be lump sum, just move it over to that column. Also, send me Eric Gadd''s sheets as well. Thanks. -Dan - - -----Original Message----- -From: Fagan, Fran -Sent: Thursday, December 20, 2001 11:10 AM -To: McCarty, Danny -Subject: worksheets - -As discussed, attached are your sheets for bonus and merit. - -Thanks, - -Fran Fagan -Sr. HR Rep -713.853.5219 - - - << File: McCartyMerit.xls >> << File: mccartyBonusCommercial_UnP.xls >> - -'); -INSERT INTO email([from],[to],subject,body) VALUES('bert.meyers@enron.com', 'shift.dl-portland@enron.com', 'OCTOBER SCHEDULE', 'TEAM, - -PLEASE SEND ME ANY REQUESTS THAT YOU HAVE FOR OCTOBER. SO FAR I HAVE THEM FOR LEAF. I WOULD LIKE TO HAVE IT DONE BY THE 15TH OF THE MONTH. ANY QUESTIONS PLEASE GIVE ME A CALL. - -BERT'); -INSERT INTO email([from],[to],subject,body) VALUES('errol.mclaughlin@enron.com', 'john.arnold@enron.com, bilal.bajwa@enron.com, john.griffith@enron.com,', 'TRV Notification: (NG - PROPT P/L - 09/27/2001)', 'The report named: NG - PROPT P/L , published as of 09/27/2001 is now available for viewing on the website.'); -INSERT INTO email([from],[to],subject,body) VALUES('patrice.mims@enron.com', 'calvin.eakins@enron.com', 'Re: Small business supply assistance', 'Hi Calvin - - -I spoke with Rickey (boy, is he long-winded!!). Gave him the name of our -credit guy, Russell Diamond. - -Thank for your help!'); -INSERT INTO email([from],[to],subject,body) VALUES('legal <.hall@enron.com>', 'stephanie.panus@enron.com', 'Termination update', 'City of Vernon and Salt River Project terminated their contracts. I will fax these notices to you.'); -INSERT INTO email([from],[to],subject,body) VALUES('d..steffes@enron.com', 'richard.shapiro@enron.com', 'EES / ENA Government Affairs Staffing & Outside Services', 'Rick -- - -Here is the information on staffing and outside services. Call if you need anything else. - -Jim - - '); -INSERT INTO email([from],[to],subject,body) VALUES('gelliott@industrialinfo.com', 'pcopello@industrialinfo.com', 'ECAAR (Gavin), WSCC (Diablo Canyon), & NPCC (Seabrook)', 'Dear Power Outage Database Customer, -Attached you will find an excel document. The outages contained within are forced or rescheduled outages. Your daily delivery will still contain these outages. -In addition to the two excel documents, there is a dbf file that is formatted like your daily deliveries you receive nightly. This will enable you to load the data into your regular database. Any questions please let me know. Thanks. -Greg Elliott -IIR, Inc. -713-783-5147 x 3481 -outages@industrialinfo.com -THE INFORMATION CONTAINED IN THIS E-MAIL IS LEGALLY PRIVILEGED AND CONFIDENTIAL INFORMATION INTENDED ONLY FOR THE USE OF THE INDIVIDUAL OR ENTITY NAMED ABOVE. YOU ARE HEREBY NOTIFIED THAT ANY DISSEMINATION, DISTRIBUTION, OR COPY OF THIS E-MAIL TO UNAUTHORIZED ENTITIES IS STRICTLY PROHIBITED. IF YOU HAVE RECEIVED THIS -E-MAIL IN ERROR, PLEASE DELETE IT. - - OUTAGE.dbf - - 111201R.xls - - 111201.xls '); -INSERT INTO email([from],[to],subject,body) VALUES('enron.announcements@enron.com', 'all_ena_egm_eim@enron.com', 'EWS Brown Bag', 'MARK YOUR LUNCH CALENDARS NOW ! - -You are invited to attend the EWS Brown Bag Lunch Series - -Featuring: RAY BOWEN, COO - -Topic: Enron Industrial Markets - -Thursday, March 15, 2001 -11:30 am - 12:30 pm -EB 5 C2 - - -You bring your lunch, Limited Seating -We provide drinks and dessert. RSVP x 3-9610'); -INSERT INTO email([from],[to],subject,body) VALUES('chris.germany@enron.com', 'ingrid.immer@williams.com', 'Re: About St Pauls', 'Sounds good to me. I bet this is next to the Warick?? Hotel. - - - - -"Immer, Ingrid" on 12/21/2000 11:48:47 AM -To: "''chris.germany@enron.com''" -cc: -Subject: About St Pauls - - - - - <> -? -?http://www.stpaulshouston.org/about.html - -Chris, - -I like the looks of this place.? What do you think about going here Christmas -eve?? They have an 11:00 a.m. service and a candlelight service at 5:00 p.m., -among others. - -Let me know.?? ii - - - About St Pauls.url - -'); -INSERT INTO email([from],[to],subject,body) VALUES('nas@cpuc.ca.gov', 'skatz@sempratrading.com, kmccrea@sablaw.com, thompson@wrightlaw.com,', 'Reply Brief filed July 31, 2000', ' - CPUC01-#76371-v1-Revised_Reply_Brief__Due_today_7_31_.doc'); -INSERT INTO email([from],[to],subject,body) VALUES('gascontrol@aglresources.com', 'dscott4@enron.com, lcampbel@enron.com', 'Alert Posted 10:00 AM November 20,2000: E-GAS Request Reminder', 'Alert Posted 10:00 AM November 20,2000: E-GAS Request Reminder -As discussed in the Winter Operations Meeting on Sept.29,2000, -E-Gas(Emergency Gas) will not be offered this winter as a service from AGLC. -Marketers and Poolers can receive gas via Peaking and IBSS nominations(daisy -chain) from other marketers up to the 6 p.m. Same Day 2 nomination cycle. -'); -INSERT INTO email([from],[to],subject,body) VALUES('dutch.quigley@enron.com', 'rwolkwitz@powermerchants.com', '', ' - -Here is a goody for you'); -INSERT INTO email([from],[to],subject,body) VALUES('ryan.o''rourke@enron.com', 'k..allen@enron.com, randy.bhatia@enron.com, frank.ermis@enron.com,', 'TRV Notification: (West VaR - 11/07/2001)', 'The report named: West VaR , published as of 11/07/2001 is now available for viewing on the website.'); -INSERT INTO email([from],[to],subject,body) VALUES('mjones7@txu.com', 'cstone1@txu.com, ggreen2@txu.com, timpowell@txu.com,', 'Enron / HPL Actuals for July 10, 2000', 'Teco Tap 10.000 / Enron ; 110.000 / HPL IFERC - -LS HPL LSK IC 30.000 / Enron -'); -INSERT INTO email([from],[to],subject,body) VALUES('susan.pereira@enron.com', 'kkw816@aol.com', 'soccer practice', 'Kathy- - -Is it safe to assume that practice is cancelled for tonight?? - -Susan Pereira'); -INSERT INTO email([from],[to],subject,body) VALUES('mark.whitt@enron.com', 'barry.tycholiz@enron.com', 'Huber Internal Memo', 'Please look at this. I didn''t know how deep to go with the desk. Do you think this works. - - '); -INSERT INTO email([from],[to],subject,body) VALUES('m..forney@enron.com', 'george.phillips@enron.com', '', 'George, -Give me a call and we will further discuss opportunities on the 13st floor. - -Thanks, -JMForney -3-7160'); -INSERT INTO email([from],[to],subject,body) VALUES('brad.mckay@enron.com', 'angusmcka@aol.com', 'Re: (no subject)', 'not yet'); -INSERT INTO email([from],[to],subject,body) VALUES('adam.bayer@enron.com', 'jonathan.mckay@enron.com', 'FW: Curve Fetch File', 'Here is the curve fetch file sent to me. It has plenty of points in it. If you give me a list of which ones you need we may be able to construct a secondary worksheet to vlookup the values. - -adam -35227 - - - -----Original Message----- -From: Royed, Jeff -Sent: Tuesday, September 25, 2001 11:37 AM -To: Bayer, Adam -Subject: Curve Fetch File - -Let me know if it works. It may be required to have a certain version of Oracle for it to work properly. - - - -Jeff Royed -Enron -Energy Operations -Phone: 713-853-5295'); -INSERT INTO email([from],[to],subject,body) VALUES('matt.smith@enron.com', 'yan.wang@enron.com', 'Report Formats', 'Yan, - -The merged reports look great. I believe the only orientation changes are to -"unmerge" the following six reports: - -31 Keystone Receipts -15 Questar Pipeline -40 Rockies Production -22 West_2 -23 West_3 -25 CIG_WIC - -The orientation of the individual reports should be correct. Thanks. - -Mat - -PS. Just a reminder to add the "*" by the title of calculated points.'); -INSERT INTO email([from],[to],subject,body) VALUES('michelle.lokay@enron.com', 'jimboman@bigfoot.com', 'Egyptian Festival', '---------------------- Forwarded by Michelle Lokay/ET&S/Enron on 09/07/2000 -10:08 AM --------------------------- - - -"Karkour, Randa" on 09/07/2000 09:01:04 AM -To: "''Agheb (E-mail)" , "Leila Mankarious (E-mail)" -, "''Marymankarious (E-mail)" -, "Michelle lokay (E-mail)" , "Ramy -Mankarious (E-mail)" -cc: - -Subject: Egyptian Festival - - - <> - - http://www.egyptianfestival.com/ - - - Egyptian Festival.url -'); -INSERT INTO email([from],[to],subject,body) VALUES('errol.mclaughlin@enron.com', 'sherry.dawson@enron.com', 'Urgent!!! --- New EAST books', 'This has to be done.................................. - -Thanks ----------------------- Forwarded by Errol McLaughlin/Corp/Enron on 12/20/2000 -08:39 AM --------------------------- - - - - From: William Kelly @ ECT 12/20/2000 08:31 AM - - -To: Kam Keiser/HOU/ECT@ECT, Darron C Giron/HOU/ECT@ECT, David -Baumbach/HOU/ECT@ECT, Errol McLaughlin/Corp/Enron@ENRON -cc: Kimat Singla/HOU/ECT@ECT, Kulvinder Fowler/NA/Enron@ENRON, Kyle R -Lilly/HOU/ECT@ECT, Jeff Royed/Corp/Enron@ENRON, Alejandra -Chavez/NA/Enron@ENRON, Crystal Hyde/HOU/ECT@ECT - -Subject: New EAST books - -We have new book names in TAGG for our intramonth portfolios and it is -extremely important that any deal booked to the East is communicated quickly -to someone on my team. I know it will take some time for the new names to -sink in and I do not want us to miss any positions or P&L. - -Thanks for your help on this. - -New: -Scott Neal : East Northeast -Dick Jenkins: East Marketeast - -WK -'); -INSERT INTO email([from],[to],subject,body) VALUES('david.forster@enron.com', 'eol.wide@enron.com', 'Change to Stack Manager', 'Effective immediately, there is a change to the Stack Manager which will -affect any Inactive Child. - -An inactive Child with links to Parent products will not have their -calculated prices updated until the Child product is Activated. - -When the Child Product is activated, the price will be recalculated and -updated BEFORE it is displayed on the web. - -This means that if you are inputting a basis price on a Child product, you -will not see the final, calculated price until you Activate the product, at -which time the customer will also see it. - -If you have any questions, please contact the Help Desk on: - -Americas: 713 853 4357 -Europe: + 44 (0) 20 7783 7783 -Asia/Australia: +61 2 9229 2300 - -Dave'); -INSERT INTO email([from],[to],subject,body) VALUES('vince.kaminski@enron.com', 'jhh1@email.msn.com', 'Re: Light reading - see pieces beginning on page 7', 'John, - -I saw it. Very interesting. - -Vince - - - - - -"John H Herbert" on 07/28/2000 08:38:08 AM -To: "Vince J Kaminski" -cc: -Subject: Light reading - see pieces beginning on page 7 - - -Cheers and have a nice weekend, - - -JHHerbert - - - - - - gd000728.pdf - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('matthew.lenhart@enron.com', 'mmmarcantel@equiva.com', 'RE:', 'i will try to line up a pig for you '); -INSERT INTO email([from],[to],subject,body) VALUES('jae.black@enron.com', 'claudette.harvey@enron.com, chaun.roberts@enron.com, judy.martinez@enron.com,', 'Disaster Recovery Equipment', 'As a reminder...there are several pieces of equipment that are set up on the 30th Floor, as well as on our floor, for the Disaster Recovery Team. PLEASE DO NOT TAKE, BORROW OR USE this equipment. Should you need to use another computer system, other than yours, or make conference calls please work with your Assistant to help find or set up equipment for you to use. - -Thanks for your understanding in this matter. - -T.Jae Black -East Power Trading -Assistant to Kevin Presto -off. 713-853-5800 -fax 713-646-8272 -cell 713-539-4760'); -INSERT INTO email([from],[to],subject,body) VALUES('eric.bass@enron.com', 'dale.neuner@enron.com', '5 X 24', 'Dale, - -Have you heard anything more on the 5 X 24s? We would like to get this -product out ASAP. - - -Thanks, - -Eric'); -INSERT INTO email([from],[to],subject,body) VALUES('messenger@smartreminders.com', 'm..tholt@enron.com', '10% Coupon - PrintPal Printer Cartridges - 100% Guaranteed', '[IMAGE] -[IMAGE][IMAGE][IMAGE] -Dear SmartReminders Member, - [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] [IMAGE] - - - - - - - - - - - - - - - - - - - - - -We respect your privacy and are a Certified Participant of the BBBOnLine - Privacy Program. To be removed from future offers,click here. -SmartReminders.com is a permission based service. To unsubscribe click here . '); -INSERT INTO email([from],[to],subject,body) VALUES('benjamin.rogers@enron.com', 'mark.bernstein@enron.com', '', 'The guy you are talking about left CIN under a "cloud of suspicion" sort of -speak. He was the one who got into several bad deals and PPA''s in California -for CIN, thus he left on a bad note. Let me know if you need more detail -than that, I felt this was the type of info you were looking for. Thanks! -Ben'); -INSERT INTO email([from],[to],subject,body) VALUES('enron_update@concureworkplace.com', 'michelle.cash@enron.com', 'Expense Report Receipts Not Received', 'Employee Name: Michelle Cash -Report Name: Houston Cellular 8-11-01 -Report Date: 12/13/01 -Report ID: 594D37C9ED2111D5B452 -Submitted On: 12/13/01 - -You are only allowed 2 reports with receipts outstanding. Your expense reports will not be paid until you meet this requirement.'); -INSERT INTO email([from],[to],subject,body) VALUES('susan.mara@enron.com', 'ray.alvarez@enron.com, mark.palmer@enron.com, karen.denne@enron.com,', 'CAISO Emergency Motion -- to discontinue market-based rates for', 'FYI. the latest broadside against the generators. - -Sue Mara -Enron Corp. -Tel: (415) 782-7802 -Fax:(415) 782-7854 ------ Forwarded by Susan J Mara/NA/Enron on 06/08/2001 12:24 PM ----- - - - "Milner, Marcie" 06/08/2001 11:13 AM To: "''smara@enron.com''" cc: Subject: CAISO Emergency Motion - - -Sue, did you see this emergency motion the CAISO filed today? Apparently -they are requesting that FERC discontinue market-based rates immediately and -grant refunds plus interest on the difference between cost-based rates and -market revenues received back to May 2000. They are requesting the -commission act within 14 days. Have you heard anything about what they are -doing? - -Marcie - -http://www.caiso.com/docs/2001/06/08/200106081005526469.pdf -'); -INSERT INTO email([from],[to],subject,body) VALUES('fletcher.sturm@enron.com', 'eloy.escobar@enron.com', 'Re: General Brinks Position Meeting', 'Eloy, - -Who is General Brinks? - -Fletch'); -INSERT INTO email([from],[to],subject,body) VALUES('nailia.dindarova@enron.com', 'richard.shapiro@enron.com', 'Documents for Mark Frevert (on EU developments and lessons from', 'Rick, - -Here are the documents that Peter has prepared for Mark Frevert. - -Nailia ----------------------- Forwarded by Nailia Dindarova/LON/ECT on 25/06/2001 -16:36 --------------------------- - - -Nailia Dindarova -25/06/2001 15:36 -To: Michael Brown/Enron@EUEnronXGate -cc: Ross Sankey/Enron@EUEnronXGate, Eric Shaw/ENRON@EUEnronXGate, Peter -Styles/LON/ECT@ECT - -Subject: Documents for Mark Frevert (on EU developments and lessons from -California) - -Michael, - - -These are the documents that Peter promised to give to you for Mark Frevert. -He has now handed them to him in person but asked me to transmit them -electronically to you, as well as Eric and Ross. - -Nailia - - - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('peggy.a.kostial@accenture.com', 'dave.samuels@enron.com', 'EOL-Accenture Deal Sheet', 'Dave - - -Attached are our comments and suggested changes. Please call to review. - -On the time line for completion, we have four critical steps to complete: - Finalize market analysis to refine business case, specifically - projected revenue stream - Complete counterparty surveying, including targeting 3 CPs for letters - of intent - Review Enron asset base for potential reuse/ licensing - Contract negotiations - -Joe will come back to us with an updated time line, but it is my -expectation that we are still on the same schedule (we just begun week -three) with possibly a week or so slippage.....contract negotiations will -probably be the critical path. - -We will send our cut at the actual time line here shortly. Thanks, - -Peggy - -(See attached file: accenture-dealpoints v2.doc) - - accenture-dealpoints v2.doc '); -INSERT INTO email([from],[to],subject,body) VALUES('thomas.martin@enron.com', 'thomas.martin@enron.com', 'Re: Guadalupe Power Partners LP', '---------------------- Forwarded by Thomas A Martin/HOU/ECT on 03/20/2001 -03:49 PM --------------------------- - - -Thomas A Martin -10/11/2000 03:55 PM -To: Patrick Wade/HOU/ECT@ECT -cc: -Subject: Re: Guadalupe Power Partners LP - -The deal is physically served at Oasis Waha or Oasis Katy and is priced at -either HSC, Waha or Katytailgate GD at buyers option three days prior to -NYMEX close. - -'); -INSERT INTO email([from],[to],subject,body) VALUES('judy.townsend@enron.com', 'dan.junek@enron.com, chris.germany@enron.com', 'Columbia Distribution''s Capacity Available for Release - Sum', '---------------------- Forwarded by Judy Townsend/HOU/ECT on 03/09/2001 11:04 -AM --------------------------- - - -agoddard@nisource.com on 03/08/2001 09:16:57 AM -To: " - *Koch, Kent" , " - -*Millar, Debra" , " - *Burke, Lynn" - -cc: " - *Heckathorn, Tom" -Subject: Columbia Distribution''s Capacity Available for Release - Sum - - -Attached is Columbia Distribution''s notice of capacity available for release -for -the summer of 2001 (Apr. 2001 through Oct. 2001). - -Please note that the deadline for bids is 3:00pm EST on March 20, 2001. - -If you have any questions, feel free to contact any of the representatives -listed -at the bottom of the attachment. - -Aaron Goddard - - - - - - 2001Summer.doc -'); -INSERT INTO email([from],[to],subject,body) VALUES('rhonda.denton@enron.com', 'tim.belden@enron.com, dana.davis@enron.com, genia.fitzgerald@enron.com,', 'Split Rock Energy LLC', 'We have received the executed EEI contract from this CP dated 12/12/2000. -Copies will be distributed to Legal and Credit.'); -INSERT INTO email([from],[to],subject,body) VALUES('kerrymcelroy@dwt.com', 'jack.speer@alcoa.com, crow@millernash.com, michaelearly@earthlink.net,', 'Oral Argument Request', ' - Oral Argument Request.doc'); -INSERT INTO email([from],[to],subject,body) VALUES('mike.carson@enron.com', 'rlmichaelis@hormel.com', '', 'Did you come in town this wk end..... My new number at our house is : -713-668-3712...... my cell # is 281-381-7332 - -the kid'); -INSERT INTO email([from],[to],subject,body) VALUES('cooper.richey@enron.com', 'trycooper@hotmail.com', 'FW: Contact Info', ' - ------Original Message----- -From: Punja, Karim -Sent: Thursday, December 13, 2001 2:35 PM -To: Richey, Cooper -Subject: Contact Info - - -Cooper, - -Its been a real pleasure working with you (even though it was for only a small amount of time) -I hope we can stay in touch. - -Home# 234-0249 -email: kpunja@hotmail.com - -Take Care, - -Karim. - '); -INSERT INTO email([from],[to],subject,body) VALUES('bjm30@earthlink.net', 'mcguinn.k@enron.com, mcguinn.ian@enron.com, mcguinn.stephen@enron.com,', 'email address change', 'Hello all. - -I haven''t talked to many of you via email recently but I do want to give you -my new address for your email file: - - bjm30@earthlink.net - -I hope all is well. - -Brian McGuinn'); -INSERT INTO email([from],[to],subject,body) VALUES('shelley.corman@enron.com', 'steve.hotte@enron.com', 'Flat Panels', 'Can you please advise what is going on with the flat panels that we had planned to distribute to our gas logistics team. It was in the budget and we had the okay, but now I''m hearing there is some hold-up & the units are stored on 44. - -Shelley'); -INSERT INTO email([from],[to],subject,body) VALUES('sara.davidson@enron.com', 'john.schwartzenburg@enron.com, scott.dieball@enron.com, recipients@enron.com,', '2001 Enron Law Conference (Distribution List 2)', ' Enron Law Conference - -San Antonio, Texas May 2-4, 2001 Westin Riverwalk - - See attached memo for more details!! - - -? Registration for the law conference this year will be handled through an -Online RSVP Form on the Enron Law Conference Website at -http://lawconference.corp.enron.com. The website is still under construction -and will not be available until Thursday, March 15, 2001. - -? We will send you another e-mail to confirm when the Law Conference Website -is operational. - -? Please complete the Online RSVP Form as soon as it is available and submit -it no later than Friday, March 30th. - - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('tori.kuykendall@enron.com', 'heath.b.taylor@accenture.com', 'Re:', 'hey - thats funny about john - he definitely remembers him - i''ll call pat -and let him know - we are coming on saturday - i just havent had a chance to -call you guys back -- looking forward to it -- i probably need the -directions again though'); -INSERT INTO email([from],[to],subject,body) VALUES('darron.giron@enron.com', 'bryce.baxter@enron.com', 'Re: Feedback for Audrey Cook', 'Bryce, - -I''ll get it done today. - -DG 3-9573 - - - - - - From: Bryce Baxter 06/12/2000 07:15 PM - - -To: Darron C Giron/HOU/ECT@ECT -cc: -Subject: Feedback for Audrey Cook - -You were identified as a reviewer for Audrey Cook. If possible, could you -complete her feedback by end of business Wednesday? It will really help me -in the PRC process to have your input. Thanks. - -'); -INSERT INTO email([from],[to],subject,body) VALUES('casey.evans@enron.com', 'stephanie.sever@enron.com', 'Gas EOL ID', 'Stephanie, - -In conjunction with the recent movement of several power traders, they are changing the names of their gas books as well. The names of the new gas books and traders are as follows: - -PWR-NG-LT-SPP: Mike Carson -PWR-NG-LT-SERC: Jeff King - -If you need to know their power desk to map their ID to their gas books, those desks are as follows: - -EPMI-LT-SPP: Mike Carson -EPMI-LT-SERC: Jeff King - -I will be in training this afternoon, but will be back when class is over. Let me know if you have any questions. - -Thanks for your help! -Casey'); -INSERT INTO email([from],[to],subject,body) VALUES('darrell.schoolcraft@enron.com', 'david.roensch@enron.com, kimberly.watson@enron.com, michelle.lokay@enron.com,', 'Postings', 'Please see the attached. - - -ds - - - - - '); -INSERT INTO email([from],[to],subject,body) VALUES('mcominsky@aol.com', 'cpatman@bracepatt.com, james_derrick@enron.com', 'Jurisprudence Luncheon', 'Carrin & Jim -- - -It was an honor and a pleasure to meet both of you yesterday. I know we will -have fun working together on this very special event. - -Jeff left the jurisprudence luncheon lists for me before he left on vacation. - I wasn''t sure whether he transmitted them to you as well. Would you please -advise me if you would like them sent to you? I can email the MS Excel files -or I can fax the hard copies to you. Please advise what is most convenient. - -I plan to be in town through the holidays and can be reached by phone, email, -or cell phone at any time. My cell phone number is 713/705-4829. - -Thanks again for your interest in the ADL''s work. Martin. - -Martin B. Cominsky -Director, Southwest Region -Anti-Defamation League -713/627-3490, ext. 122 -713/627-2011 (fax) -MCominsky@aol.com'); -INSERT INTO email([from],[to],subject,body) VALUES('phillip.love@enron.com', 'todagost@utmb.edu, gbsonnta@utmb.edu', 'New President', 'I had a little bird put a word in my ear. Is there any possibility for Ben -Raimer to be Bush''s secretary of HHS? Just curious about that infamous UTMB -rumor mill. Hope things are well, happy holidays. -PL'); -INSERT INTO email([from],[to],subject,body) VALUES('marie.heard@enron.com', 'ehamilton@fna.com', 'ISDA Master Agreement', 'Erin: - -Pursuant to your request, attached are the Schedule to the ISDA Master Agreement, together with Paragraph 13 to the ISDA Credit Support Annex. Please let me know if you need anything else. We look forward to hearing your comments. - -Marie - -Marie Heard -Senior Legal Specialist -Enron North America Corp. -Phone: (713) 853-3907 -Fax: (713) 646-3490 -marie.heard@enron.com - - '); -INSERT INTO email([from],[to],subject,body) VALUES('andrea.ring@enron.com', 'beverly.beaty@enron.com', 'Re: Tennessee Buy - Louis Dreyfus', 'Beverly - once again thanks so much for your help on this. - - - - '); -INSERT INTO email([from],[to],subject,body) VALUES('karolyn.criado@enron.com', 'j..bonin@enron.com, felicia.case@enron.com, b..clapp@enron.com,', 'Price List week of Oct. 8-9, 2001', ' -Please contact me if you have any questions regarding last weeks prices. - -Thank you, -Karolyn Criado -3-9441 - - - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('kevin.presto@enron.com', 'edward.baughman@enron.com, billy.braddock@enron.com', 'Associated', 'Please begin working on filling our Associated short position in 02. I would like to take this risk off the books. - -In addition, please find out what a buy-out of VEPCO would cost us. With Rogers transitioning to run our retail risk management, I would like to clean up our customer positions. - -We also need to continue to explore a JEA buy-out. - -Thanks.'); -INSERT INTO email([from],[to],subject,body) VALUES('stacy.dickson@enron.com', 'gregg.penman@enron.com', 'RE: Constellation TC 5-7-01', 'Gregg, - -I am at home with a sick baby. (Lots of fun!) I will call you about this -tomorrow. - -Stacy'); -INSERT INTO email([from],[to],subject,body) VALUES('joe.quenet@enron.com', 'dfincher@utilicorp.com', '', 'hey big guy.....check this out..... - - w ww.gorelieberman-2000.com/'); -INSERT INTO email([from],[to],subject,body) VALUES('k..allen@enron.com', 'jacqestc@aol.com', '', 'Jacques, - -I sent you a fax of Kevin Kolb''s comments on the release. The payoff on the note would be $36,248 ($36090(principal) + $158 (accrued interest)). -This is assuming we wrap this up on Tuesday. - -Please email to confirm that their changes are ok so I can set up a meeting on Tuesday to reach closure. - -Phillip'); -INSERT INTO email([from],[to],subject,body) VALUES('kourtney.nelson@enron.com', 'mike.swerzbin@enron.com', 'Adjusted L/R Balance', 'Mike, - -I placed the adjusted L/R Balance on the Enronwest site. It is under the "Staff/Kourtney Nelson". There are two links: - -1) "Adj L_R" is the same data/format from the weekly strategy meeting. -2) "New Gen 2001_2002" link has all of the supply side info that is used to calculate the L/R balance - -Please note the Data Flag column, a value of "3" indicates the project was cancelled, on hold, etc and is not included in the calc. - -Both of these sheets are interactive Excel spreadsheets and thus you can play around with the data as you please. Also, James Bruce is working to get his gen report on the web. That will help with your access to information on new gen. - -Please let me know if you have any questions or feedback, - -Kourtney - - - -Kourtney Nelson -Fundamental Analysis -Enron North America -(503) 464-8280 -kourtney.nelson@enron.com'); -INSERT INTO email([from],[to],subject,body) VALUES('d..thomas@enron.com', 'naveed.ahmed@enron.com', 'FW: Current Enron TCC Portfolio', ' - ------Original Message----- -From: Grace, Rebecca M. -Sent: Monday, December 17, 2001 9:44 AM -To: Thomas, Paul D. -Cc: Cashion, Jim; Allen, Thresa A.; May, Tom -Subject: RE: Current Enron TCC Portfolio - - -Paul, - -I reviewed NY''s list. I agree with all of their contracts numbers and mw amounts. - -Call if you have any more questions. - -Rebecca - - - - -----Original Message----- -From: Thomas, Paul D. -Sent: Monday, December 17, 2001 9:08 AM -To: Grace, Rebecca M. -Subject: FW: Current Enron TCC Portfolio - - << File: enrontccs.xls >> -Rebecca, -Let me know if you see any differences. - -Paul -X 3-0403 ------Original Message----- -From: Thomas, Paul D. -Sent: Monday, December 17, 2001 9:04 AM -To: Ahmed, Naveed -Subject: FW: Current Enron TCC Portfolio - - - - ------Original Message----- -From: Thomas, Paul D. -Sent: Thursday, December 13, 2001 10:01 AM -To: Baughman, Edward D. -Subject: Current Enron TCC Portfolio - - -'); -INSERT INTO email([from],[to],subject,body) VALUES('stephanie.panus@enron.com', 'william.bradford@enron.com, debbie.brackett@enron.com,', 'Coastal Merchant Energy/El Paso Merchant Energy', 'Coastal Merchant Energy, L.P. merged with and into El Paso Merchant Energy, -L.P., effective February 1, 2001, with the surviving entity being El Paso -Merchant Energy, L.P. We currently have ISDA Master Agreements with both -counterparties. Please see the attached memo regarding the existing Masters -and let us know which agreement should be terminated. - -Thanks, -Stephanie -'); -INSERT INTO email([from],[to],subject,body) VALUES('kam.keiser@enron.com', 'c..kenne@enron.com', 'RE: What about this too???', ' - - -----Original Message----- -From: Kenne, Dawn C. -Sent: Wednesday, February 06, 2002 11:50 AM -To: Keiser, Kam -Subject: What about this too??? - - - << File: Netco Trader Matrix.xls >> - '); -INSERT INTO email([from],[to],subject,body) VALUES('chris.meyer@enron.com', 'joe.parks@enron.com', 'Centana', 'Talked to Chip. We do need Cash Committe approval given the netting feature of your deal, which means Batch Funding Request. Please update per my previous e-mail and forward. - -Thanks - -chris -x31666'); -INSERT INTO email([from],[to],subject,body) VALUES('debra.perlingiere@enron.com', 'jworman@academyofhealth.com', '', 'Have a great weekend! Happy Fathers Day! - - -Debra Perlingiere -Enron North America Corp. -1400 Smith Street, EB 3885 -Houston, Texas 77002 -dperlin@enron.com -Phone 713-853-7658 -Fax 713-646-3490'); -INSERT INTO email([from],[to],subject,body) VALUES('outlook.team@enron.com', '', 'Demo by Martha Janousek of Dashboard & Pipeline Profile / Julia &', 'CALENDAR ENTRY: APPOINTMENT - -Description: - Demo by Martha Janousek of Dashboard & Pipeline Profile / Julia & Dir Rpts. - 4102 - -Date: 1/5/2001 -Time: 9:00 AM - 10:00 AM (Central Standard Time) - -Chairperson: Outlook Migration Team - -Detailed Description:'); -INSERT INTO email([from],[to],subject,body) VALUES('diana.seifert@enron.com', 'mark.taylor@enron.com', 'Guest access Chile', 'Hello Mark, - -Justin Boyd told me that your can help me with questions regarding Chile. -We got a request for guest access through MG. -The company is called Escondida and is a subsidiary of BHP Australia. - -Please advise if I can set up a guest account or not. -F.Y.I.: MG is planning to put a "in w/h Chile" contract for Copper on-line as -soon as Enron has done the due diligence for this country. -Thanks ! - - -Best regards - -Diana Seifert -EOL PCG'); -INSERT INTO email([from],[to],subject,body) VALUES('enron_update@concureworkplace.com', 'mark.whitt@enron.com', '<> - 121001', 'The Approval status has changed on the following report: - -Status last changed by: Barry L. Tycholiz -Expense Report Name: 121001 -Report Total: $198.98 -Amount Due Employee: $198.98 -Amount Approved: $198.98 -Amount Paid: $0.00 -Approval Status: Approved -Payment Status: Pending - - -To review this expense report, click on the following link for Concur Expense. -http://expensexms.enron.com'); -INSERT INTO email([from],[to],subject,body) VALUES('kevin.hyatt@enron.com', '', 'Technical Support', 'Outside the U.S., please refer to the list below: - -Australia: -1800 678-515 -support@palm-au.com - -Canada: -1905 305-6530 -support@palm.com - -New Zealand: -0800 446-398 -support@palm-nz.com - -U.K.: -0171 867 0108 -eurosupport@palm.3com.com - -Please refer to the Worldwide Customer Support card for a complete technical support contact list.'); -INSERT INTO email([from],[to],subject,body) VALUES('geoff.storey@enron.com', 'dutch.quigley@enron.com', 'RE:', 'duke contact? - - -----Original Message----- -From: Quigley, Dutch -Sent: Wednesday, October 31, 2001 10:14 AM -To: Storey, Geoff -Subject: RE: - -bp corp Albert LaMore 281-366-4962 - -running the reports now - - - -----Original Message----- -From: Storey, Geoff -Sent: Wednesday, October 31, 2001 10:10 AM -To: Quigley, Dutch -Subject: RE: - -give me a contact over there too -BP - - - -----Original Message----- -From: Quigley, Dutch -Sent: Wednesday, October 31, 2001 9:42 AM -To: Storey, Geoff -Subject: - -Coral Jeff Whitnah 713-767-5374 -Relaint Steve McGinn 713-207-4000'); -INSERT INTO email([from],[to],subject,body) VALUES('pete.davis@enron.com', 'pete.davis@enron.com', 'Start Date: 4/22/01; HourAhead hour: 3; ', 'Start Date: 4/22/01; HourAhead hour: 3; No ancillary schedules awarded. -Variances detected. -Variances detected in Load schedule. - - LOG MESSAGES: - -PARSING FILE -->> O:\Portland\WestDesk\California Scheduling\ISO Final -Schedules\2001042203.txt - ----- Load Schedule ---- -$$$ Variance found in table tblLoads. - Details: (Hour: 3 / Preferred: 1.92 / Final: 1.89) - TRANS_TYPE: FINAL - LOAD_ID: PGE4 - MKT_TYPE: 2 - TRANS_DATE: 4/22/01 - SC_ID: EPMI - -'); -INSERT INTO email([from],[to],subject,body) VALUES('john.postlethwaite@enron.com', 'john.zufferli@enron.com', 'Reference', 'John, hope things are going well up there for you. The big day is almost here for you and Jessica. I was wondering if I could use your name as a job reference if need be. I am just trying to get everything in order just in case something happens. - -John'); -INSERT INTO email([from],[to],subject,body) VALUES('jeffrey.shankman@enron.com', 'lschiffm@jonesday.com', 'Re:', 'I saw you called on the cell this a.m. Sorry I missed you. (I was in the -shower). I have had a shitty week--I suspect my silence (not only to you, -but others) after our phone call is a result of the week. I''m seeing Glen at -11:15....talk to you'); -INSERT INTO email([from],[to],subject,body) VALUES('litebytz@enron.com', '', 'Lite Bytz RSVP', ' -This week''s Lite Bytz presentation will feature the following TOOLZ speaker: - -Richard McDougall -Solaris 8 -Thursday, June 7, 2001 - -If you have not already signed up, please RSVP via email to litebytz@enron.com by the end of the day Tuesday, June 5, 2001. - -*Remember: this is now a Brown Bag Event--so bring your lunch and we will provide cookies and drinks. - -Click below for more details. - -http://home.enron.com:84/messaging/litebytztoolzprint.jpg'); - COMMIT; - } -} {} - -############################################################################### -# Everything above just builds an interesting test database. The actual -# tests come after this comment. -############################################################################### - -do_test fts2c-1.2 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark' - } -} {6 17 25 38 40 42 73 74} -do_test fts2c-1.3 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'susan' - } -} {24 40} -do_test fts2c-1.4 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark susan' - } -} {40} -do_test fts2c-1.5 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'susan mark' - } -} {40} -do_test fts2c-1.6 { - execsql { - SELECT rowid FROM email WHERE email MATCH '"mark susan"' - } -} {} -do_test fts2c-1.7 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark -susan' - } -} {6 17 25 38 42 73 74} -do_test fts2c-1.8 { - execsql { - SELECT rowid FROM email WHERE email MATCH '-mark susan' - } -} {24} -do_test fts2c-1.9 { - execsql { - SELECT rowid FROM email WHERE email MATCH 'mark OR susan' - } -} {6 17 24 25 38 40 42 73 74} - -# Some simple tests of the automatic "offsets(email)" column. In the sample -# data set above, only one message, number 20, contains the words -# "gas" and "reminder" in both body and subject. -# -do_test fts2c-2.1 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE email MATCH 'gas reminder' - } -} {20 {2 0 42 3 2 1 54 8 3 0 42 3 3 1 54 8 3 0 129 3 3 0 143 3 3 0 240 3}} -do_test fts2c-2.2 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE email MATCH 'subject:gas reminder' - } -} {20 {2 0 42 3 2 1 54 8 3 1 54 8}} -do_test fts2c-2.3 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE email MATCH 'body:gas reminder' - } -} {20 {2 1 54 8 3 0 42 3 3 1 54 8 3 0 129 3 3 0 143 3 3 0 240 3}} -do_test fts2c-2.4 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE subject MATCH 'gas reminder' - } -} {20 {2 0 42 3 2 1 54 8}} -do_test fts2c-2.5 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE body MATCH 'gas reminder' - } -} {20 {3 0 42 3 3 1 54 8 3 0 129 3 3 0 143 3 3 0 240 3}} - -# Document 32 contains 5 instances of the world "child". But only -# 3 of them are paired with "product". Make sure only those instances -# that match the phrase appear in the offsets(email) list. -# -do_test fts2c-3.1 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE body MATCH 'child product' AND +rowid=32 - } -} {32 {3 0 94 5 3 0 114 5 3 0 207 5 3 1 213 7 3 0 245 5 3 1 251 7 3 0 409 5 3 1 415 7 3 1 493 7}} -do_test fts2c-3.2 { - execsql { - SELECT rowid, offsets(email) FROM email - WHERE body MATCH '"child product"' - } -} {32 {3 0 207 5 3 1 213 7 3 0 245 5 3 1 251 7 3 0 409 5 3 1 415 7}} - -# Snippet generator tests -# -do_test fts2c-4.1 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'subject:gas reminder' - } -} {{Alert Posted 10:00 AM November 20,2000: E-GAS Request Reminder}} -do_test fts2c-4.2 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'christmas candlelight' - } -} {{... place.? What do you think about going here Christmas -eve?? They have an 11:00 a.m. service and a candlelight service at 5:00 p.m., -among others. ...}} - -do_test fts2c-4.3 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'deal sheet potential reuse' - } -} {{EOL-Accenture Deal Sheet ... intent - Review Enron asset base for potential reuse/ licensing - Contract negotiations ...}} -do_test fts2c-4.4 { - execsql { - SELECT snippet(email,'<<<','>>>',' ') FROM email - WHERE email MATCH 'deal sheet potential reuse' - } -} {{EOL-Accenture <<>> <<>> intent - Review Enron asset base for <<>> <<>>/ licensing - Contract negotiations }} -do_test fts2c-4.5 { - execsql { - SELECT snippet(email,'<<<','>>>',' ') FROM email - WHERE email MATCH 'first things' - } -} {{Re: <<>> Polish Deal! Congrats! <<>> seem to be building rapidly now on the }} -do_test fts2c-4.6 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'chris is here' - } -} {{chris.germany@enron.com ... Sounds good to me. I bet this is next to the Warick?? Hotel. ... place.? What do you think about going here Christmas -eve?? They have an 11:00 a.m. ...}} -do_test fts2c-4.7 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH '"pursuant to"' - } -} {{Erin: - -Pursuant to your request, attached are the Schedule to ...}} -do_test fts2c-4.8 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'ancillary load davis' - } -} {{pete.davis@enron.com ... Start Date: 4/22/01; HourAhead hour: 3; No ancillary schedules awarded. -Variances detected. -Variances detected in Load schedule. - - LOG MESSAGES: - -PARSING ...}} - -# Combinations of AND and OR operators: -# -do_test fts2c-5.1 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'questar enron OR com' - } -} {{matt.smith@enron.com ... six reports: - -31 Keystone Receipts -15 Questar Pipeline -40 Rockies Production -22 West_2 ...}} -do_test fts2c-5.2 { - execsql { - SELECT snippet(email) FROM email - WHERE email MATCH 'enron OR com questar' - } -} {{matt.smith@enron.com ... six reports: - -31 Keystone Receipts -15 Questar Pipeline -40 Rockies Production -22 West_2 ...}} - -finish_test diff --git a/test/fts2d.test b/test/fts2d.test deleted file mode 100644 index d8090d8f0..000000000 --- a/test/fts2d.test +++ /dev/null @@ -1,65 +0,0 @@ -# 2006 October 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 script is testing the FTS2 module, and in particular -# the Porter stemmer. -# -# $Id: fts2d.test,v 1.1 2006/10/19 23:36:26 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -do_test fts2d-1.1 { - execsql { - CREATE VIRTUAL TABLE t1 USING fts2(content, tokenize porter); - INSERT INTO t1(rowid, content) VALUES(1, 'running and jumping'); - SELECT rowid FROM t1 WHERE content MATCH 'run jump'; - } -} {1} -do_test fts2d-1.2 { - execsql { - SELECT snippet(t1) FROM t1 WHERE t1 MATCH 'run jump'; - } -} {{running and jumping}} -do_test fts2d-1.3 { - execsql { - INSERT INTO t1(rowid, content) - VALUES(2, 'abcdefghijklmnopqrstuvwyxz'); - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH 'abcdefghijqrstuvwyxz' - } -} {2 abcdefghijklmnopqrstuvwyxz} -do_test fts2d-1.4 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH 'abcdefghijXXXXqrstuvwyxz' - } -} {2 abcdefghijklmnopqrstuvwyxz} -do_test fts2d-1.5 { - execsql { - INSERT INTO t1(rowid, content) - VALUES(3, 'The value is 123456789'); - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH '123789' - } -} {3 {The value is 123456789}} -do_test fts2d-1.6 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE t1 MATCH '123000000789' - } -} {3 {The value is 123456789}} - - -finish_test diff --git a/test/fts2e.test b/test/fts2e.test deleted file mode 100644 index 71845acdd..000000000 --- a/test/fts2e.test +++ /dev/null @@ -1,85 +0,0 @@ -# 2006 October 19 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The -# focus of this script is testing deletions in the FTS2 module. -# -# $Id: fts2e.test,v 1.1 2006/10/19 23:36:26 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Construct a full-text search table containing keywords which are the -# ordinal numbers of the bit positions set for a sequence of integers, -# which are used for the rowid. There are a total of 30 INSERT and -# DELETE statements, so that we'll test both the segmentMerge() merge -# (over the first 16) and the termSelect() merge (over the level-1 -# segment and 14 level-0 segments). -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(content); - INSERT INTO t1 (rowid, content) VALUES(1, 'one'); - INSERT INTO t1 (rowid, content) VALUES(2, 'two'); - INSERT INTO t1 (rowid, content) VALUES(3, 'one two'); - INSERT INTO t1 (rowid, content) VALUES(4, 'three'); - DELETE FROM t1 WHERE rowid = 1; - INSERT INTO t1 (rowid, content) VALUES(5, 'one three'); - INSERT INTO t1 (rowid, content) VALUES(6, 'two three'); - INSERT INTO t1 (rowid, content) VALUES(7, 'one two three'); - DELETE FROM t1 WHERE rowid = 4; - INSERT INTO t1 (rowid, content) VALUES(8, 'four'); - INSERT INTO t1 (rowid, content) VALUES(9, 'one four'); - INSERT INTO t1 (rowid, content) VALUES(10, 'two four'); - DELETE FROM t1 WHERE rowid = 7; - INSERT INTO t1 (rowid, content) VALUES(11, 'one two four'); - INSERT INTO t1 (rowid, content) VALUES(12, 'three four'); - INSERT INTO t1 (rowid, content) VALUES(13, 'one three four'); - DELETE FROM t1 WHERE rowid = 10; - INSERT INTO t1 (rowid, content) VALUES(14, 'two three four'); - INSERT INTO t1 (rowid, content) VALUES(15, 'one two three four'); - INSERT INTO t1 (rowid, content) VALUES(16, 'five'); - DELETE FROM t1 WHERE rowid = 13; - INSERT INTO t1 (rowid, content) VALUES(17, 'one five'); - INSERT INTO t1 (rowid, content) VALUES(18, 'two five'); - INSERT INTO t1 (rowid, content) VALUES(19, 'one two five'); - DELETE FROM t1 WHERE rowid = 16; - INSERT INTO t1 (rowid, content) VALUES(20, 'three five'); - INSERT INTO t1 (rowid, content) VALUES(21, 'one three five'); - INSERT INTO t1 (rowid, content) VALUES(22, 'two three five'); - DELETE FROM t1 WHERE rowid = 19; - DELETE FROM t1 WHERE rowid = 22; -} - -do_test fts2f-1.1 { - execsql {SELECT COUNT(*) FROM t1} -} {14} - -do_test fts2e-2.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one'} -} {3 5 9 11 15 17 21} - -do_test fts2e-2.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two'} -} {2 3 6 11 14 15 18} - -do_test fts2e-2.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three'} -} {5 6 12 14 15 20 21} - -do_test fts2e-2.4 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'four'} -} {8 9 11 12 14 15} - -do_test fts2e-2.5 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'five'} -} {17 18 20 21} - -finish_test diff --git a/test/fts2f.test b/test/fts2f.test deleted file mode 100644 index 49cff1466..000000000 --- a/test/fts2f.test +++ /dev/null @@ -1,90 +0,0 @@ -# 2006 October 19 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The -# focus of this script is testing updates in the FTS2 module. -# -# $Id: fts2f.test,v 1.2 2007/02/23 00:14:06 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Construct a full-text search table containing keywords which are the -# ordinal numbers of the bit positions set for a sequence of integers, -# which are used for the rowid. There are a total of 31 INSERT, -# UPDATE, and DELETE statements, so that we'll test both the -# segmentMerge() merge (over the first 16) and the termSelect() merge -# (over the level-1 segment and 15 level-0 segments). -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(content); - INSERT INTO t1 (rowid, content) VALUES(1, 'one'); - INSERT INTO t1 (rowid, content) VALUES(2, 'two'); - INSERT INTO t1 (rowid, content) VALUES(3, 'one two'); - INSERT INTO t1 (rowid, content) VALUES(4, 'three'); - INSERT INTO t1 (rowid, content) VALUES(5, 'one three'); - INSERT INTO t1 (rowid, content) VALUES(6, 'two three'); - INSERT INTO t1 (rowid, content) VALUES(7, 'one two three'); - DELETE FROM t1 WHERE rowid = 4; - INSERT INTO t1 (rowid, content) VALUES(8, 'four'); - UPDATE t1 SET content = 'update one three' WHERE rowid = 1; - INSERT INTO t1 (rowid, content) VALUES(9, 'one four'); - INSERT INTO t1 (rowid, content) VALUES(10, 'two four'); - DELETE FROM t1 WHERE rowid = 7; - INSERT INTO t1 (rowid, content) VALUES(11, 'one two four'); - INSERT INTO t1 (rowid, content) VALUES(12, 'three four'); - INSERT INTO t1 (rowid, content) VALUES(13, 'one three four'); - DELETE FROM t1 WHERE rowid = 10; - INSERT INTO t1 (rowid, content) VALUES(14, 'two three four'); - INSERT INTO t1 (rowid, content) VALUES(15, 'one two three four'); - UPDATE t1 SET content = 'update two five' WHERE rowid = 8; - INSERT INTO t1 (rowid, content) VALUES(16, 'five'); - DELETE FROM t1 WHERE rowid = 13; - INSERT INTO t1 (rowid, content) VALUES(17, 'one five'); - INSERT INTO t1 (rowid, content) VALUES(18, 'two five'); - INSERT INTO t1 (rowid, content) VALUES(19, 'one two five'); - DELETE FROM t1 WHERE rowid = 16; - INSERT INTO t1 (rowid, content) VALUES(20, 'three five'); - INSERT INTO t1 (rowid, content) VALUES(21, 'one three five'); - INSERT INTO t1 (rowid, content) VALUES(22, 'two three five'); - DELETE FROM t1 WHERE rowid = 19; - UPDATE t1 SET content = 'update' WHERE rowid = 15; -} - -do_test fts2f-1.1 { - execsql {SELECT COUNT(*) FROM t1} -} {16} - -do_test fts2f-2.0 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'update'} -} {1 8 15} - -do_test fts2f-2.1 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'one'} -} {1 3 5 9 11 17 21} - -do_test fts2f-2.2 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'two'} -} {2 3 6 8 11 14 18 22} - -do_test fts2f-2.3 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'three'} -} {1 5 6 12 14 20 21 22} - -do_test fts2f-2.4 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'four'} -} {9 11 12 14} - -do_test fts2f-2.5 { - execsql {SELECT rowid FROM t1 WHERE content MATCH 'five'} -} {8 17 18 20 21 22} - -finish_test diff --git a/test/fts2g.test b/test/fts2g.test deleted file mode 100644 index 176c619aa..000000000 --- a/test/fts2g.test +++ /dev/null @@ -1,93 +0,0 @@ -# 2006 October 19 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The focus -# of this script is testing handling of edge cases for various doclist -# merging functions in the FTS2 module query logic. -# -# $Id: fts2g.test,v 1.3 2007/11/16 00:23:08 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(content); - INSERT INTO t1 (rowid, content) VALUES(1, 'this is a test'); - INSERT INTO t1 (rowid, content) VALUES(2, 'also a test'); -} - -# No hits at all. Returns empty doclists from termSelect(). -do_test fts2g-1.1 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'something'} -} {} - -# Empty left in docListExceptMerge(). -do_test fts2g-1.2 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH '-this something'} -} {} - -# Empty right in docListExceptMerge(). -do_test fts2g-1.3 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'this -something'} -} {1} - -# Empty left in docListPhraseMerge(). -do_test fts2g-1.4 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH '"this something"'} -} {} - -# Empty right in docListPhraseMerge(). -do_test fts2g-1.5 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH '"something is"'} -} {} - -# Empty left in docListOrMerge(). -do_test fts2g-1.6 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'something OR this'} -} {1} - -# Empty right in docListOrMerge(). -do_test fts2g-1.7 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'this OR something'} -} {1} - -# Empty left in docListAndMerge(). -do_test fts2g-1.8 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'something this'} -} {} - -# Empty right in docListAndMerge(). -do_test fts2g-1.9 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'this something'} -} {} - -# No support for all-except queries. -do_test fts2g-1.10 { - catchsql {SELECT rowid FROM t1 WHERE t1 MATCH '-this -something'} -} {1 {SQL logic error}} - -# Test that docListOrMerge() correctly handles reaching the end of one -# doclist before it reaches the end of the other. -do_test fts2g-1.11 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'this OR also'} -} {1 2} -do_test fts2g-1.12 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'also OR this'} -} {1 2} - -# Empty left and right in docListOrMerge(). Each term matches neither -# row, and when combined there was an assertion failure. -do_test fts2g-1.13 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'something OR nothing'} -} {} - -finish_test diff --git a/test/fts2h.test b/test/fts2h.test deleted file mode 100644 index 72561d85b..000000000 --- a/test/fts2h.test +++ /dev/null @@ -1,76 +0,0 @@ -# 2006 October 31 (scaaarey) -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The focus -# here is testing correct handling of excessively long terms. -# -# $Id: fts2h.test,v 1.1 2006/11/29 21:03:01 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - 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] - } - return $doc -} - -set len 5000 -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] - -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(content); - INSERT INTO t1 (rowid, content) VALUES(1, $doc1); - INSERT INTO t1 (rowid, content) VALUES(2, $doc2); - INSERT INTO t1 (rowid, content) VALUES(3, $doc3); -} - -# No hits at all. Returns empty doclists from termSelect(). -do_test fts2h-1.1 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'something'} -} {} - -do_test fts2h-1.2 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH $aterm} -} {1 3} - -do_test fts2h-1.2 { - execsql {SELECT rowid FROM t1 WHERE t1 MATCH $xterm} -} {} - -do_test fts2h-1.3 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH '$aterm -$xterm'" -} {1 3} - -do_test fts2h-1.4 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH '\"$aterm $bterm\"'" -} {1} - -finish_test diff --git a/test/fts2i.test b/test/fts2i.test deleted file mode 100644 index e732e6a8a..000000000 --- a/test/fts2i.test +++ /dev/null @@ -1,87 +0,0 @@ -# 2007 January 17 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite fts2 library. The -# focus here is testing handling of UPDATE when using UTF-16-encoded -# databases. -# -# $Id: fts2i.test,v 1.2 2007/01/24 03:46:35 drh Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Return the UTF-16 representation of the supplied UTF-8 string $str. -# If $nt is true, append two 0x00 bytes as a nul terminator. -# NOTE(shess) Copied from capi3.test. -proc utf16 {str {nt 1}} { - set r [encoding convertto unicode $str] - if {$nt} { - append r "\x00\x00" - } - return $r -} - -db eval { - PRAGMA encoding = "UTF-16le"; - CREATE VIRTUAL TABLE t1 USING fts2(content); -} - -do_test fts2i-1.0 { - execsql {PRAGMA encoding} -} {UTF-16le} - -do_test fts2i-1.1 { - execsql {INSERT INTO t1 (rowid, content) VALUES(1, 'one')} - execsql {SELECT content FROM t1 WHERE rowid = 1} -} {one} - -do_test fts2i-1.2 { - set sql "INSERT INTO t1 (rowid, content) VALUES(2, 'two')" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 2} -} {two} - -do_test fts2i-1.3 { - set sql "INSERT INTO t1 (rowid, content) VALUES(3, 'three')" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - set sql "UPDATE t1 SET content = 'trois' WHERE rowid = 3" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 3} -} {trois} - -do_test fts2i-1.4 { - set sql16 [utf16 {INSERT INTO t1 (rowid, content) VALUES(4, 'four')}] - set STMT [sqlite3_prepare16 $DB $sql16 -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 4} -} {four} - -do_test fts2i-1.5 { - set sql16 [utf16 {INSERT INTO t1 (rowid, content) VALUES(5, 'five')}] - set STMT [sqlite3_prepare16 $DB $sql16 -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - set sql "UPDATE t1 SET content = 'cinq' WHERE rowid = 5" - set STMT [sqlite3_prepare $DB $sql -1 TAIL] - sqlite3_step $STMT - sqlite3_finalize $STMT - execsql {SELECT content FROM t1 WHERE rowid = 5} -} {cinq} - -finish_test diff --git a/test/fts2j.test b/test/fts2j.test deleted file mode 100644 index a8a2c07c1..000000000 --- a/test/fts2j.test +++ /dev/null @@ -1,89 +0,0 @@ -# 2007 February 6 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. This -# tests creating fts2 tables in an attached database. -# -# $Id: fts2j.test,v 1.1 2007/02/07 01:01:18 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Clean up anything left over from a previous pass. -forcedelete test2.db -forcedelete test2.db-journal -sqlite3 db2 test2.db - -db eval { - CREATE VIRTUAL TABLE t3 USING fts2(content); - INSERT INTO t3 (rowid, content) VALUES(1, "hello world"); -} - -db2 eval { - CREATE VIRTUAL TABLE t1 USING fts2(content); - INSERT INTO t1 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t1 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t1 (rowid, content) VALUES(3, "cruel world"); -} - -# This has always worked because the t1_* tables used by fts2 will be -# the defaults. -do_test fts2j-1.1 { - execsql { - ATTACH DATABASE 'test2.db' AS two; - SELECT rowid FROM t1 WHERE t1 MATCH 'hello'; - DETACH DATABASE two; - } -} {1 2} -# Make certain we're detached if there was an error. -catch {db eval {DETACH DATABASE two}} - -# In older code, this appears to work fine, but the t2_* tables used -# by fts2 will be created in database 'main' instead of database -# 'two'. It appears to work fine because the tables end up being the -# defaults, but obviously is badly broken if you hope to use things -# other than in the exact same ATTACH setup. -do_test fts2j-1.2 { - execsql { - ATTACH DATABASE 'test2.db' AS two; - CREATE VIRTUAL TABLE two.t2 USING fts2(content); - INSERT INTO t2 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t2 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t2 (rowid, content) VALUES(3, "cruel world"); - SELECT rowid FROM t2 WHERE t2 MATCH 'hello'; - DETACH DATABASE two; - } -} {1 2} -catch {db eval {DETACH DATABASE two}} - -# In older code, this broke because the fts2 code attempted to create -# t3_* tables in database 'main', but they already existed. Normally -# this wouldn't happen without t3 itself existing, in which case the -# fts2 code would never be called in the first place. -do_test fts2j-1.3 { - execsql { - ATTACH DATABASE 'test2.db' AS two; - - CREATE VIRTUAL TABLE two.t3 USING fts2(content); - INSERT INTO two.t3 (rowid, content) VALUES(2, "hello there"); - INSERT INTO two.t3 (rowid, content) VALUES(3, "cruel world"); - SELECT rowid FROM two.t3 WHERE t3 MATCH 'hello'; - - DETACH DATABASE two; - } db2 -} {2} -catch {db eval {DETACH DATABASE two}} - -catch {db2 close} -forcedelete test2.db - -finish_test diff --git a/test/fts2k.test b/test/fts2k.test deleted file mode 100644 index e7d5f0dff..000000000 --- a/test/fts2k.test +++ /dev/null @@ -1,105 +0,0 @@ -# 2007 March 9 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. These -# make sure that fts2 insertion buffering is fully transparent when -# using transactions. -# -# $Id: fts2k.test,v 1.2 2007/08/10 23:47:04 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(content); - INSERT INTO t1 (rowid, content) VALUES(1, "hello world"); - INSERT INTO t1 (rowid, content) VALUES(2, "hello there"); - INSERT INTO t1 (rowid, content) VALUES(3, "cruel world"); -} - -# Test that possibly-buffered inserts went through after commit. -do_test fts2k-1.1 { - execsql { - BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(4, "false world"); - INSERT INTO t1 (rowid, content) VALUES(5, "false door"); - COMMIT TRANSACTION; - SELECT rowid FROM t1 WHERE t1 MATCH 'world'; - } -} {1 3 4} - -# Test that buffered inserts are seen by selects in the same -# transaction. -do_test fts2k-1.2 { - execsql { - BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(6, "another world"); - INSERT INTO t1 (rowid, content) VALUES(7, "another test"); - SELECT rowid FROM t1 WHERE t1 MATCH 'world'; - COMMIT TRANSACTION; - } -} {1 3 4 6} - -# Test that buffered inserts are seen within a transaction. This is -# really the same test as 1.2. -do_test fts2k-1.3 { - execsql { - BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(8, "second world"); - INSERT INTO t1 (rowid, content) VALUES(9, "second sight"); - SELECT rowid FROM t1 WHERE t1 MATCH 'world'; - ROLLBACK TRANSACTION; - } -} {1 3 4 6 8} - -# Double-check that the previous result doesn't persist past the -# rollback! -do_test fts2k-1.4 { - execsql { - SELECT rowid FROM t1 WHERE t1 MATCH 'world'; - } -} {1 3 4 6} - -# Test it all together. -do_test fts2k-1.5 { - execsql { - BEGIN TRANSACTION; - INSERT INTO t1 (rowid, content) VALUES(10, "second world"); - INSERT INTO t1 (rowid, content) VALUES(11, "second sight"); - ROLLBACK TRANSACTION; - SELECT rowid FROM t1 WHERE t1 MATCH 'world'; - } -} {1 3 4 6} - -# Test that the obvious case works. -do_test fts2k-1.6 { - execsql { - BEGIN; - INSERT INTO t1 (rowid, content) VALUES(12, "third world"); - COMMIT; - SELECT rowid FROM t1 WHERE t1 MATCH 'third'; - } -} {12} - -# This is exactly the same as the previous test, except that older -# code loses the INSERT due to an SQLITE_SCHEMA error. -do_test fts2k-1.7 { - execsql { - BEGIN; - INSERT INTO t1 (rowid, content) VALUES(13, "third dimension"); - CREATE TABLE x (c); - COMMIT; - SELECT rowid FROM t1 WHERE t1 MATCH 'dimension'; - } -} {13} - -finish_test diff --git a/test/fts2l.test b/test/fts2l.test deleted file mode 100644 index 42f5ba134..000000000 --- a/test/fts2l.test +++ /dev/null @@ -1,69 +0,0 @@ -# 2007 March 28 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The focus -# of this script is testing isspace/isalnum/tolower problems with the -# FTS2 module. Unfortunately, this code isn't a really principled set -# of tests, because it is impossible to know where new uses of these -# functions might appear. -# -# $Id: fts2l.test,v 1.2 2007/12/13 21:54:11 drh Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# Tests that startsWith() (calls isspace, tolower, isalnum) can handle -# hi-bit chars. parseSpec() also calls isalnum here. -do_test fts2l-1.1 { - execsql "CREATE VIRTUAL TABLE t1 USING fts2(content, \x80)" -} {} - -# Additionally tests isspace() call in getToken(), and isalnum() call -# in tokenListToIdList(). -do_test fts2l-1.2 { - catch { - execsql "CREATE VIRTUAL TABLE t2 USING fts2(content, tokenize \x80)" - } - sqlite3_errmsg $DB -} "unknown tokenizer: \x80" - -# Additionally test final isalnum() in startsWith(). -do_test fts2l-1.3 { - execsql "CREATE VIRTUAL TABLE t3 USING fts2(content, tokenize\x80)" -} {} - -# The snippet-generation code has calls to isspace() which are sort of -# hard to get to. It finds convenient breakpoints by starting ~40 -# chars before and after the matched term, and scanning ~10 chars -# around that position for isspace() characters. The long word with -# embedded hi-bit chars causes one of these isspace() calls to be -# exercised. The version with a couple extra spaces should cause the -# other isspace() call to be exercised. [Both cases have been tested -# in the debugger, but I'm hoping to continue to catch it if simple -# constant changes change things slightly. -# -# The trailing and leading hi-bit chars help with code which tests for -# isspace() to coalesce multiple spaces. - -set word "\x80xxxxx\x80xxxxx\x80xxxxx\x80xxxxx\x80xxxxx\x80xxxxx\x80" -set phrase1 "$word $word $word target $word $word $word" -set phrase2 "$word $word $word target $word $word $word" - -db eval {CREATE VIRTUAL TABLE t4 USING fts2(content)} -db eval "INSERT INTO t4 (content) VALUES ('$phrase1')" -db eval "INSERT INTO t4 (content) VALUES ('$phrase2')" - -do_test fts2l-1.4 { - execsql {SELECT rowid, length(snippet(t4)) FROM t4 WHERE t4 MATCH 'target'} -} {1 111 2 117} - -finish_test diff --git a/test/fts2m.test b/test/fts2m.test deleted file mode 100644 index 6552637a6..000000000 --- a/test/fts2m.test +++ /dev/null @@ -1,65 +0,0 @@ -# 2007 April 9 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements regression tests for SQLite library. fts2 -# DELETE handling assumed all fields were non-null. This was not -# the intention at all. -# -# $Id: fts2m.test,v 1.1 2007/04/09 20:45:42 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(col_a, col_b); - - INSERT INTO t1(rowid, col_a, col_b) VALUES(1, 'testing', 'testing'); - INSERT INTO t1(rowid, col_a, col_b) VALUES(2, 'only a', null); - INSERT INTO t1(rowid, col_a, col_b) VALUES(3, null, 'only b'); - INSERT INTO t1(rowid, col_a, col_b) VALUES(4, null, null); -} - -do_test fts2m-1.0 { - execsql { - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {2 2 4} - -do_test fts2m-1.1 { - execsql { - DELETE FROM t1 WHERE rowid = 1; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {1 1 3} - -do_test fts2m-1.2 { - execsql { - DELETE FROM t1 WHERE rowid = 2; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {0 1 2} - -do_test fts2m-1.3 { - execsql { - DELETE FROM t1 WHERE rowid = 3; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {0 0 1} - -do_test fts2m-1.4 { - execsql { - DELETE FROM t1 WHERE rowid = 4; - SELECT COUNT(col_a), COUNT(col_b), COUNT(*) FROM t1; - } -} {0 0 0} - -finish_test diff --git a/test/fts2n.test b/test/fts2n.test deleted file mode 100644 index ca0b4fe9f..000000000 --- a/test/fts2n.test +++ /dev/null @@ -1,196 +0,0 @@ -# 2007 April 26 -# -# The author disclaims copyright to this source code. -# -#************************************************************************* -# This file implements tests for prefix-searching in the fts2 -# component of the SQLite library. -# -# $Id: fts2n.test,v 1.2 2007/12/13 21:54:11 drh Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -# A large string to prime the pump with. -set text { - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas - iaculis mollis ipsum. Praesent rhoncus placerat justo. Duis non quam - sed turpis posuere placerat. Curabitur et lorem in lorem porttitor - aliquet. Pellentesque bibendum tincidunt diam. Vestibulum blandit - ante nec elit. In sapien diam, facilisis eget, dictum sed, viverra - at, felis. Vestibulum magna. Sed magna dolor, vestibulum rhoncus, - ornare vel, vulputate sit amet, felis. Integer malesuada, tellus at - luctus gravida, diam nunc porta nibh, nec imperdiet massa metus eu - lectus. Aliquam nisi. Nunc fringilla nulla at lectus. Suspendisse - potenti. Cum sociis natoque penatibus et magnis dis parturient - montes, nascetur ridiculus mus. Pellentesque odio nulla, feugiat eu, - suscipit nec, consequat quis, risus. -} - -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(c); - - INSERT INTO t1(rowid, c) VALUES(1, $text); - INSERT INTO t1(rowid, c) VALUES(2, 'Another lovely row'); -} - -# Exact match -do_test fts2n-1.1 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'lorem'" -} {1} - -# And a prefix -do_test fts2n-1.2 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'lore*'" -} {1} - -# Prefix includes exact match -do_test fts2n-1.3 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'lorem*'" -} {1} - -# Make certain everything isn't considered a prefix! -do_test fts2n-1.4 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'lore'" -} {} - -# Prefix across multiple rows. -do_test fts2n-1.5 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'lo*'" -} {1 2} - -# Likewise, with multiple hits in one document. -do_test fts2n-1.6 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'l*'" -} {1 2} - -# Prefix which should only hit one document. -do_test fts2n-1.7 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'lov*'" -} {2} - -# * not at end is dropped. -do_test fts2n-1.8 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH 'lo *'" -} {} - -# Stand-alone * is dropped. -do_test fts2n-1.9 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH '*'" -} {} - -# Phrase-query prefix. -do_test fts2n-1.10 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH '\"lovely r*\"'" -} {2} -do_test fts2n-1.11 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH '\"lovely r\"'" -} {} - -# Phrase query with multiple prefix matches. -do_test fts2n-1.12 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH '\"a* l*\"'" -} {1 2} - -# Phrase query with multiple prefix matches. -do_test fts2n-1.13 { - execsql "SELECT rowid FROM t1 WHERE t1 MATCH '\"a* l* row\"'" -} {2} - - - - -# Test across updates (and, by implication, deletes). - -# Version of text without "lorem". -regsub -all {[Ll]orem} $text '' ntext - -db eval { - CREATE VIRTUAL TABLE t2 USING fts2(c); - - INSERT INTO t2(rowid, c) VALUES(1, $text); - INSERT INTO t2(rowid, c) VALUES(2, 'Another lovely row'); - UPDATE t2 SET c = $ntext WHERE rowid = 1; -} - -# Can't see lorem as an exact match. -do_test fts2n-2.1 { - execsql "SELECT rowid FROM t2 WHERE t2 MATCH 'lorem'" -} {} - -# Can't see a prefix of lorem, either. -do_test fts2n-2.2 { - execsql "SELECT rowid FROM t2 WHERE t2 MATCH 'lore*'" -} {} - -# Can see lovely in the other document. -do_test fts2n-2.3 { - execsql "SELECT rowid FROM t2 WHERE t2 MATCH 'lo*'" -} {2} - -# Can still see other hits. -do_test fts2n-2.4 { - execsql "SELECT rowid FROM t2 WHERE t2 MATCH 'l*'" -} {1 2} - -# Prefix which should only hit one document. -do_test fts2n-2.5 { - execsql "SELECT rowid FROM t2 WHERE t2 MATCH 'lov*'" -} {2} - - - -# Test with a segment which will have multiple levels in the tree. - -# Build a big document with lots of unique terms. -set bigtext $text -foreach c {a b c d e} { - regsub -all {[A-Za-z]+} $bigtext "&$c" t - append bigtext $t -} - -# Populate a table with many copies of the big document, so that we -# can test the number of hits found. Populate $ret with the expected -# hit counts for each row. offsets() returns 4 elements for every -# hit. We'll have 6 hits for row 1, 1 for row 2, and 6*(2^5)==192 for -# $bigtext. -set ret {6 1} -db eval { - BEGIN; - CREATE VIRTUAL TABLE t3 USING fts2(c); - - INSERT INTO t3(rowid, c) VALUES(1, $text); - INSERT INTO t3(rowid, c) VALUES(2, 'Another lovely row'); -} -for {set i 0} {$i<100} {incr i} { - db eval {INSERT INTO t3(rowid, c) VALUES(3+$i, $bigtext)} - lappend ret 192 -} -db eval {COMMIT;} - -# Test that we get the expected number of hits. -do_test fts2n-3.1 { - set t {} - db eval {SELECT offsets(t3) as o FROM t3 WHERE t3 MATCH 'l*'} { - set l [llength $o] - lappend t [expr {$l/4}] - } - set t -} $ret - -# TODO(shess) It would be useful to test a couple edge cases, but I -# don't know if we have the precision to manage it from here at this -# time. Prefix hits can cross leaves, which the code above _should_ -# hit by virtue of size. There are two variations on this. If the -# tree is 2 levels high, the code will find the leaf-node extent -# directly, but if its higher, the code will have to follow two -# separate interior branches down the tree. Both should be tested. - -finish_test diff --git a/test/fts2o.test b/test/fts2o.test deleted file mode 100644 index de319ea96..000000000 --- a/test/fts2o.test +++ /dev/null @@ -1,169 +0,0 @@ -# 2007 June 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. The -# focus of this script is testing the FTS2 module. -# -# $Id: fts2o.test,v 1.4 2007/07/02 10:16:50 danielk1977 Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is not defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -#--------------------------------------------------------------------- -# These tests, fts2o-1.*, test that ticket #2429 is fixed. -# -db eval { - CREATE VIRTUAL TABLE t1 USING fts2(a, b, c); - INSERT INTO t1(a, b, c) VALUES('one three four', 'one four', 'one four two'); -} -do_test fts2o-1.1 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE c MATCH 'four'; - } -} {1 {one four two}} -do_test fts2o-1.2 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE b MATCH 'four'; - } -} {1 {one four}} -do_test fts2o-1.3 { - execsql { - SELECT rowid, snippet(t1) FROM t1 WHERE a MATCH 'four'; - } -} {1 {one three four}} - -#--------------------------------------------------------------------- -# Test that it is possible to rename an fts2 table. -# -do_test fts2o-2.1 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {t1 t1_content t1_segments t1_segdir} -do_test fts2o-2.2 { - execsql { ALTER TABLE t1 RENAME to fts_t1; } -} {} -do_test fts2o-2.3 { - execsql { SELECT rowid, snippet(fts_t1) FROM fts_t1 WHERE a MATCH 'four'; } -} {1 {one three four}} -do_test fts2o-2.4 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {fts_t1 fts_t1_content fts_t1_segments fts_t1_segdir} - -# See what happens when renaming the fts2 table fails. -# -do_test fts2o-2.5 { - catchsql { - CREATE TABLE t1_segdir(a, b, c); - ALTER TABLE fts_t1 RENAME to t1; - } -} {1 {SQL logic error}} -do_test fts2o-2.6 { - execsql { SELECT rowid, snippet(fts_t1) FROM fts_t1 WHERE a MATCH 'four'; } -} {1 {one three four}} -do_test fts2o-2.7 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {fts_t1 fts_t1_content fts_t1_segments fts_t1_segdir t1_segdir} - -# See what happens when renaming the fts2 table fails inside a transaction. -# -do_test fts2o-2.8 { - execsql { - BEGIN; - INSERT INTO fts_t1(a, b, c) VALUES('one two three', 'one four', 'one two'); - } -} {} -do_test fts2o-2.9 { - catchsql { - ALTER TABLE fts_t1 RENAME to t1; - } -} {1 {SQL logic error}} -do_test fts2o-2.10 { - execsql { SELECT rowid, snippet(fts_t1) FROM fts_t1 WHERE a MATCH 'four'; } -} {1 {one three four}} -do_test fts2o-2.11 { - execsql { SELECT tbl_name FROM sqlite_master WHERE type = 'table'} -} {fts_t1 fts_t1_content fts_t1_segments fts_t1_segdir t1_segdir} -do_test fts2o-2.12 { - execsql COMMIT - execsql {SELECT a FROM fts_t1} -} {{one three four} {one two three}} -do_test fts2o-2.12 { - execsql { SELECT a, b, c FROM fts_t1 WHERE c MATCH 'four'; } -} {{one three four} {one four} {one four two}} - -#------------------------------------------------------------------- -# Close, delete and reopen the database. The following test should -# be run on an initially empty db. -# -db close -forcedelete test.db test.db-journal -sqlite3 db test.db - -do_test fts2o-3.1 { - execsql { - CREATE VIRTUAL TABLE t1 USING fts2(a, b, c); - INSERT INTO t1(a, b, c) VALUES('one three four', 'one four', 'one two'); - SELECT a, b, c FROM t1 WHERE c MATCH 'two'; - } -} {{one three four} {one four} {one two}} - -# This test was crashing at one point. -# -do_test fts2o-3.2 { - execsql { - SELECT a, b, c FROM t1 WHERE c MATCH 'two'; - CREATE TABLE t3(a, b, c); - SELECT a, b, c FROM t1 WHERE c MATCH 'two'; - } -} {{one three four} {one four} {one two} {one three four} {one four} {one two}} - -#--------------------------------------------------------------------- -# Test that it is possible to rename an fts2 table in an attached -# database. -# -forcedelete test2.db test2.db-journal - -do_test fts2o-3.1 { - execsql { - ATTACH 'test2.db' AS aux; - CREATE VIRTUAL TABLE aux.t1 USING fts2(a, b, c); - INSERT INTO aux.t1(a, b, c) VALUES( - 'neung song sahm', 'neung see', 'neung see song' - ); - } -} {} - -do_test fts2o-3.2 { - execsql { SELECT a, b, c FROM aux.t1 WHERE a MATCH 'song'; } -} {{neung song sahm} {neung see} {neung see song}} - -do_test fts2o-3.3 { - execsql { SELECT a, b, c FROM t1 WHERE c MATCH 'two'; } -} {{one three four} {one four} {one two}} - -do_test fts2o-3.4 { - execsql { ALTER TABLE aux.t1 RENAME TO t2 } -} {} - -do_test fts2o-3.2 { - execsql { SELECT a, b, c FROM t2 WHERE a MATCH 'song'; } -} {{neung song sahm} {neung see} {neung see song}} - -do_test fts2o-3.3 { - execsql { SELECT a, b, c FROM t1 WHERE c MATCH 'two'; } -} {{one three four} {one four} {one two}} - -finish_test diff --git a/test/fts2p.test b/test/fts2p.test deleted file mode 100644 index 38a8079d8..000000000 --- a/test/fts2p.test +++ /dev/null @@ -1,357 +0,0 @@ -# 2008 June 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. -# -#************************************************************************* -# This file exercises some new testing functions in the FTS2 module, -# and then uses them to do some basic tests that FTS2 is internally -# working as expected. -# -# $Id: fts2p.test,v 1.1 2008/07/22 23:32:28 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is not defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -#************************************************************************* -# Probe to see if support for these functions is compiled in. -# TODO(shess): Change main.mk to do the right thing and remove this test. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'x'); -} - -set s {SELECT dump_terms(t1, 1) FROM t1 LIMIT 1} -set r {1 {unable to use function dump_terms in the requested context}} -if {[catchsql $s]==$r} { - finish_test - return -} - -#************************************************************************* -# Test that the new functions give appropriate errors. -do_test fts2p-0.0 { - catchsql { - SELECT dump_terms(t1, 1) FROM t1 LIMIT 1; - } -} {1 {dump_terms: incorrect arguments}} - -do_test fts2p-0.1 { - catchsql { - SELECT dump_terms(t1, 0, 0, 0) FROM t1 LIMIT 1; - } -} {1 {dump_terms: incorrect arguments}} - -do_test fts2p-0.2 { - catchsql { - SELECT dump_terms(1, t1) FROM t1 LIMIT 1; - } -} {1 {unable to use function dump_terms in the requested context}} - -do_test fts2p-0.3 { - catchsql { - SELECT dump_terms(t1, 16, 16) FROM t1 LIMIT 1; - } -} {1 {dump_terms: segment not found}} - -do_test fts2p-0.4 { - catchsql { - SELECT dump_doclist(t1) FROM t1 LIMIT 1; - } -} {1 {dump_doclist: incorrect arguments}} - -do_test fts2p-0.5 { - catchsql { - SELECT dump_doclist(t1, NULL) FROM t1 LIMIT 1; - } -} {1 {dump_doclist: empty second argument}} - -do_test fts2p-0.6 { - catchsql { - SELECT dump_doclist(t1, '') FROM t1 LIMIT 1; - } -} {1 {dump_doclist: empty second argument}} - -do_test fts2p-0.7 { - catchsql { - SELECT dump_doclist(t1, 'a', 0) FROM t1 LIMIT 1; - } -} {1 {dump_doclist: incorrect arguments}} - -do_test fts2p-0.8 { - catchsql { - SELECT dump_doclist(t1, 'a', 0, 0, 0) FROM t1 LIMIT 1; - } -} {1 {dump_doclist: incorrect arguments}} - -do_test fts2p-0.9 { - catchsql { - SELECT dump_doclist(t1, 'a', 16, 16) FROM t1 LIMIT 1; - } -} {1 {dump_doclist: segment not found}} - -#************************************************************************* -# Utility function to check for the expected terms in the segment -# level/index. _all version does same but for entire index. -proc check_terms {test level index terms} { - # TODO(shess): Figure out why uplevel in do_test can't catch - # $level and $index directly. - set ::level $level - set ::index $index - do_test $test.terms { - execsql { - SELECT dump_terms(t1, $::level, $::index) FROM t1 LIMIT 1; - } - } [list $terms] -} -proc check_terms_all {test terms} { - do_test $test.terms { - execsql { - SELECT dump_terms(t1) FROM t1 LIMIT 1; - } - } [list $terms] -} - -# Utility function to check for the expected doclist for the term in -# segment level/index. _all version does same for entire index. -proc check_doclist {test level index term doclist} { - # TODO(shess): Again, why can't the non-:: versions work? - set ::term $term - set ::level $level - set ::index $index - do_test $test { - execsql { - SELECT dump_doclist(t1, $::term, $::level, $::index) FROM t1 LIMIT 1; - } - } [list $doclist] -} -proc check_doclist_all {test term doclist} { - set ::term $term - do_test $test { - execsql { - SELECT dump_doclist(t1, $::term) FROM t1 LIMIT 1; - } - } [list $doclist] -} - -#************************************************************************* -# Test the segments resulting from straight-forward inserts. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); -} - -# Check for expected segments and expected matches. -do_test fts2p-1.0.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0 0 1 0 2} -do_test fts2p-1.0.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} [list {0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4} \ - {0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4} \ - {0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4}] - -# Check the specifics of the segments constructed. -# Logical view of entire index. -check_terms_all fts2p-1.0.1 {a is test that this was} -check_doclist_all fts2p-1.0.1.1 a {[1 0[2]] [2 0[2]] [3 0[2]]} -check_doclist_all fts2p-1.0.1.2 is {[1 0[1]] [3 0[1]]} -check_doclist_all fts2p-1.0.1.3 test {[1 0[3]] [2 0[3]] [3 0[3]]} -check_doclist_all fts2p-1.0.1.4 that {[2 0[0]]} -check_doclist_all fts2p-1.0.1.5 this {[1 0[0]] [3 0[0]]} -check_doclist_all fts2p-1.0.1.6 was {[2 0[1]]} - -# Segment 0,0 -check_terms fts2p-1.0.2 0 0 {a is test this} -check_doclist fts2p-1.0.2.1 0 0 a {[1 0[2]]} -check_doclist fts2p-1.0.2.2 0 0 is {[1 0[1]]} -check_doclist fts2p-1.0.2.3 0 0 test {[1 0[3]]} -check_doclist fts2p-1.0.2.4 0 0 this {[1 0[0]]} - -# Segment 0,1 -check_terms fts2p-1.0.3 0 1 {a test that was} -check_doclist fts2p-1.0.3.1 0 1 a {[2 0[2]]} -check_doclist fts2p-1.0.3.2 0 1 test {[2 0[3]]} -check_doclist fts2p-1.0.3.3 0 1 that {[2 0[0]]} -check_doclist fts2p-1.0.3.4 0 1 was {[2 0[1]]} - -# Segment 0,2 -check_terms fts2p-1.0.4 0 2 {a is test this} -check_doclist fts2p-1.0.4.1 0 2 a {[3 0[2]]} -check_doclist fts2p-1.0.4.2 0 2 is {[3 0[1]]} -check_doclist fts2p-1.0.4.3 0 2 test {[3 0[3]]} -check_doclist fts2p-1.0.4.4 0 2 this {[3 0[0]]} - -#************************************************************************* -# Test the segments resulting from inserts followed by a delete. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); - DELETE FROM t1 WHERE rowid = 1; -} - -do_test fts2p-1.1.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0 0 1 0 2 0 3} -do_test fts2p-1.1.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} {{0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4} {0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4}} - -check_terms_all fts2p-1.1.1 {a is test that this was} -check_doclist_all fts2p-1.1.1.1 a {[2 0[2]] [3 0[2]]} -check_doclist_all fts2p-1.1.1.2 is {[3 0[1]]} -check_doclist_all fts2p-1.1.1.3 test {[2 0[3]] [3 0[3]]} -check_doclist_all fts2p-1.1.1.4 that {[2 0[0]]} -check_doclist_all fts2p-1.1.1.5 this {[3 0[0]]} -check_doclist_all fts2p-1.1.1.6 was {[2 0[1]]} - -check_terms fts2p-1.1.2 0 0 {a is test this} -check_doclist fts2p-1.1.2.1 0 0 a {[1 0[2]]} -check_doclist fts2p-1.1.2.2 0 0 is {[1 0[1]]} -check_doclist fts2p-1.1.2.3 0 0 test {[1 0[3]]} -check_doclist fts2p-1.1.2.4 0 0 this {[1 0[0]]} - -check_terms fts2p-1.1.3 0 1 {a test that was} -check_doclist fts2p-1.1.3.1 0 1 a {[2 0[2]]} -check_doclist fts2p-1.1.3.2 0 1 test {[2 0[3]]} -check_doclist fts2p-1.1.3.3 0 1 that {[2 0[0]]} -check_doclist fts2p-1.1.3.4 0 1 was {[2 0[1]]} - -check_terms fts2p-1.1.4 0 2 {a is test this} -check_doclist fts2p-1.1.4.1 0 2 a {[3 0[2]]} -check_doclist fts2p-1.1.4.2 0 2 is {[3 0[1]]} -check_doclist fts2p-1.1.4.3 0 2 test {[3 0[3]]} -check_doclist fts2p-1.1.4.4 0 2 this {[3 0[0]]} - -check_terms fts2p-1.1.5 0 3 {a is test this} -check_doclist fts2p-1.1.5.1 0 3 a {[1]} -check_doclist fts2p-1.1.5.2 0 3 is {[1]} -check_doclist fts2p-1.1.5.3 0 3 test {[1]} -check_doclist fts2p-1.1.5.4 0 3 this {[1]} - -#************************************************************************* -# Test results when all references to certain tokens are deleted. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); - DELETE FROM t1 WHERE rowid IN (1,3); -} - -# Still 4 segments because 0,3 will contain deletes for rowid 1 and 3. -do_test fts2p-1.2.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0 0 1 0 2 0 3} -do_test fts2p-1.2.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} {{0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4}} - -check_terms_all fts2p-1.2.1 {a is test that this was} -check_doclist_all fts2p-1.2.1.1 a {[2 0[2]]} -check_doclist_all fts2p-1.2.1.2 is {} -check_doclist_all fts2p-1.2.1.3 test {[2 0[3]]} -check_doclist_all fts2p-1.2.1.4 that {[2 0[0]]} -check_doclist_all fts2p-1.2.1.5 this {} -check_doclist_all fts2p-1.2.1.6 was {[2 0[1]]} - -check_terms fts2p-1.2.2 0 0 {a is test this} -check_doclist fts2p-1.2.2.1 0 0 a {[1 0[2]]} -check_doclist fts2p-1.2.2.2 0 0 is {[1 0[1]]} -check_doclist fts2p-1.2.2.3 0 0 test {[1 0[3]]} -check_doclist fts2p-1.2.2.4 0 0 this {[1 0[0]]} - -check_terms fts2p-1.2.3 0 1 {a test that was} -check_doclist fts2p-1.2.3.1 0 1 a {[2 0[2]]} -check_doclist fts2p-1.2.3.2 0 1 test {[2 0[3]]} -check_doclist fts2p-1.2.3.3 0 1 that {[2 0[0]]} -check_doclist fts2p-1.2.3.4 0 1 was {[2 0[1]]} - -check_terms fts2p-1.2.4 0 2 {a is test this} -check_doclist fts2p-1.2.4.1 0 2 a {[3 0[2]]} -check_doclist fts2p-1.2.4.2 0 2 is {[3 0[1]]} -check_doclist fts2p-1.2.4.3 0 2 test {[3 0[3]]} -check_doclist fts2p-1.2.4.4 0 2 this {[3 0[0]]} - -check_terms fts2p-1.2.5 0 3 {a is test this} -check_doclist fts2p-1.2.5.1 0 3 a {[1] [3]} -check_doclist fts2p-1.2.5.2 0 3 is {[1] [3]} -check_doclist fts2p-1.2.5.3 0 3 test {[1] [3]} -check_doclist fts2p-1.2.5.4 0 3 this {[1] [3]} - -#************************************************************************* -# Test results when everything is optimized manually. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); - DELETE FROM t1 WHERE rowid IN (1,3); - DROP TABLE IF EXISTS t1old; - ALTER TABLE t1 RENAME TO t1old; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) SELECT rowid, c FROM t1old; - DROP TABLE t1old; -} - -# Should be a single optimal segment with the same logical results. -do_test fts2p-1.3.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0} -do_test fts2p-1.3.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} {{0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4}} - -check_terms_all fts2p-1.3.1 {a test that was} -check_doclist_all fts2p-1.3.1.1 a {[2 0[2]]} -check_doclist_all fts2p-1.3.1.2 test {[2 0[3]]} -check_doclist_all fts2p-1.3.1.3 that {[2 0[0]]} -check_doclist_all fts2p-1.3.1.4 was {[2 0[1]]} - -check_terms fts2p-1.3.2 0 0 {a test that was} -check_doclist fts2p-1.3.2.1 0 0 a {[2 0[2]]} -check_doclist fts2p-1.3.2.2 0 0 test {[2 0[3]]} -check_doclist fts2p-1.3.2.3 0 0 that {[2 0[0]]} -check_doclist fts2p-1.3.2.4 0 0 was {[2 0[1]]} - -finish_test diff --git a/test/fts2q.test b/test/fts2q.test deleted file mode 100644 index cba78d583..000000000 --- a/test/fts2q.test +++ /dev/null @@ -1,346 +0,0 @@ -# 2008 June 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. -# -#************************************************************************* -# This file implements regression tests for SQLite library. The focus -# of this script is testing the FTS2 module's optimize() function. -# -# $Id: fts2q.test,v 1.2 2008/07/22 23:49:44 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is not defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -#************************************************************************* -# Probe to see if support for the FTS2 dump_* functions is compiled in. -# TODO(shess): Change main.mk to do the right thing and remove this test. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'x'); -} - -set s {SELECT dump_terms(t1, 1) FROM t1 LIMIT 1} -set r {1 {unable to use function dump_terms in the requested context}} -if {[catchsql $s]==$r} { - finish_test - return -} - -#************************************************************************* -# Utility function to check for the expected terms in the segment -# level/index. _all version does same but for entire index. -proc check_terms {test level index terms} { - # TODO(shess): Figure out why uplevel in do_test can't catch - # $level and $index directly. - set ::level $level - set ::index $index - do_test $test.terms { - execsql { - SELECT dump_terms(t1, $::level, $::index) FROM t1 LIMIT 1; - } - } [list $terms] -} -proc check_terms_all {test terms} { - do_test $test.terms { - execsql { - SELECT dump_terms(t1) FROM t1 LIMIT 1; - } - } [list $terms] -} - -# Utility function to check for the expected doclist for the term in -# segment level/index. _all version does same for entire index. -proc check_doclist {test level index term doclist} { - # TODO(shess): Again, why can't the non-:: versions work? - set ::term $term - set ::level $level - set ::index $index - do_test $test { - execsql { - SELECT dump_doclist(t1, $::term, $::level, $::index) FROM t1 LIMIT 1; - } - } [list $doclist] -} -proc check_doclist_all {test term doclist} { - set ::term $term - do_test $test { - execsql { - SELECT dump_doclist(t1, $::term) FROM t1 LIMIT 1; - } - } [list $doclist] -} - -#************************************************************************* -# Test results when all rows are deleted and one is added back. -# Previously older segments would continue to exist, but now the index -# should be dropped when the table is empty. The results should look -# exactly like we never added the earlier rows in the first place. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); - DELETE FROM t1 WHERE 1=1; -- Delete each row rather than dropping table. - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); -} - -# Should be a single initial segment. -do_test fts2q-1.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0} -do_test fts2q-1.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} {{0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4}} - -check_terms_all fts2q-1.1 {a is test this} -check_doclist_all fts2q-1.1.1 a {[1 0[2]]} -check_doclist_all fts2q-1.1.2 is {[1 0[1]]} -check_doclist_all fts2q-1.1.3 test {[1 0[3]]} -check_doclist_all fts2q-1.1.4 this {[1 0[0]]} - -check_terms fts2q-1.2 0 0 {a is test this} -check_doclist fts2q-1.2.1 0 0 a {[1 0[2]]} -check_doclist fts2q-1.2.2 0 0 is {[1 0[1]]} -check_doclist fts2q-1.2.3 0 0 test {[1 0[3]]} -check_doclist fts2q-1.2.4 0 0 this {[1 0[0]]} - -#************************************************************************* -# Test results when everything is optimized manually. -# NOTE(shess): This is a copy of fts2c-1.3. I've pulled a copy here -# because fts2q-2 and fts2q-3 should have identical results. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); - DELETE FROM t1 WHERE rowid IN (1,3); - DROP TABLE IF EXISTS t1old; - ALTER TABLE t1 RENAME TO t1old; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) SELECT rowid, c FROM t1old; - DROP TABLE t1old; -} - -# Should be a single optimal segment with the same logical results. -do_test fts2q-2.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0} -do_test fts2q-2.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} {{0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4}} - -check_terms_all fts2q-2.1 {a test that was} -check_doclist_all fts2q-2.1.1 a {[2 0[2]]} -check_doclist_all fts2q-2.1.2 test {[2 0[3]]} -check_doclist_all fts2q-2.1.3 that {[2 0[0]]} -check_doclist_all fts2q-2.1.4 was {[2 0[1]]} - -check_terms fts2q-2.2 0 0 {a test that was} -check_doclist fts2q-2.2.1 0 0 a {[2 0[2]]} -check_doclist fts2q-2.2.2 0 0 test {[2 0[3]]} -check_doclist fts2q-2.2.3 0 0 that {[2 0[0]]} -check_doclist fts2q-2.2.4 0 0 was {[2 0[1]]} - -#************************************************************************* -# Test results when everything is optimized via optimize(). -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); - DELETE FROM t1 WHERE rowid IN (1,3); - SELECT OPTIMIZE(t1) FROM t1 LIMIT 1; -} - -# Should be a single optimal segment with the same logical results. -do_test fts2q-3.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0} -do_test fts2q-3.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} {{0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4}} - -check_terms_all fts2q-3.1 {a test that was} -check_doclist_all fts2q-3.1.1 a {[2 0[2]]} -check_doclist_all fts2q-3.1.2 test {[2 0[3]]} -check_doclist_all fts2q-3.1.3 that {[2 0[0]]} -check_doclist_all fts2q-3.1.4 was {[2 0[1]]} - -check_terms fts2q-3.2 0 0 {a test that was} -check_doclist fts2q-3.2.1 0 0 a {[2 0[2]]} -check_doclist fts2q-3.2.2 0 0 test {[2 0[3]]} -check_doclist fts2q-3.2.3 0 0 that {[2 0[0]]} -check_doclist fts2q-3.2.4 0 0 was {[2 0[1]]} - -#************************************************************************* -# Test optimize() against a table involving segment merges. -# NOTE(shess): Since there's no transaction, each of the INSERT/UPDATE -# statements generates a segment. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); - - UPDATE t1 SET c = 'This is a test one' WHERE rowid = 1; - UPDATE t1 SET c = 'That was a test one' WHERE rowid = 2; - UPDATE t1 SET c = 'This is a test one' WHERE rowid = 3; - - UPDATE t1 SET c = 'This is a test two' WHERE rowid = 1; - UPDATE t1 SET c = 'That was a test two' WHERE rowid = 2; - UPDATE t1 SET c = 'This is a test two' WHERE rowid = 3; - - UPDATE t1 SET c = 'This is a test three' WHERE rowid = 1; - UPDATE t1 SET c = 'That was a test three' WHERE rowid = 2; - UPDATE t1 SET c = 'This is a test three' WHERE rowid = 3; - - UPDATE t1 SET c = 'This is a test four' WHERE rowid = 1; - UPDATE t1 SET c = 'That was a test four' WHERE rowid = 2; - UPDATE t1 SET c = 'This is a test four' WHERE rowid = 3; - - UPDATE t1 SET c = 'This is a test' WHERE rowid = 1; - UPDATE t1 SET c = 'That was a test' WHERE rowid = 2; - UPDATE t1 SET c = 'This is a test' WHERE rowid = 3; -} - -# 2 segments in level 0, 1 in level 1 (18 segments created, 16 -# merged). -do_test fts2q-4.segments { - execsql { - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {0 0 0 1 1 0} - -do_test fts2q-4.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} [list {0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4} \ - {0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4} \ - {0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4}] - -check_terms_all fts2q-4.1 {a four is one test that this three two was} -check_doclist_all fts2q-4.1.1 a {[1 0[2]] [2 0[2]] [3 0[2]]} -check_doclist_all fts2q-4.1.2 four {} -check_doclist_all fts2q-4.1.3 is {[1 0[1]] [3 0[1]]} -check_doclist_all fts2q-4.1.4 one {} -check_doclist_all fts2q-4.1.5 test {[1 0[3]] [2 0[3]] [3 0[3]]} -check_doclist_all fts2q-4.1.6 that {[2 0[0]]} -check_doclist_all fts2q-4.1.7 this {[1 0[0]] [3 0[0]]} -check_doclist_all fts2q-4.1.8 three {} -check_doclist_all fts2q-4.1.9 two {} -check_doclist_all fts2q-4.1.10 was {[2 0[1]]} - -check_terms fts2q-4.2 0 0 {a four test that was} -check_doclist fts2q-4.2.1 0 0 a {[2 0[2]]} -check_doclist fts2q-4.2.2 0 0 four {[2]} -check_doclist fts2q-4.2.3 0 0 test {[2 0[3]]} -check_doclist fts2q-4.2.4 0 0 that {[2 0[0]]} -check_doclist fts2q-4.2.5 0 0 was {[2 0[1]]} - -check_terms fts2q-4.3 0 1 {a four is test this} -check_doclist fts2q-4.3.1 0 1 a {[3 0[2]]} -check_doclist fts2q-4.3.2 0 1 four {[3]} -check_doclist fts2q-4.3.3 0 1 is {[3 0[1]]} -check_doclist fts2q-4.3.4 0 1 test {[3 0[3]]} -check_doclist fts2q-4.3.5 0 1 this {[3 0[0]]} - -check_terms fts2q-4.4 1 0 {a four is one test that this three two was} -check_doclist fts2q-4.4.1 1 0 a {[1 0[2]] [2 0[2]] [3 0[2]]} -check_doclist fts2q-4.4.2 1 0 four {[1] [2 0[4]] [3 0[4]]} -check_doclist fts2q-4.4.3 1 0 is {[1 0[1]] [3 0[1]]} -check_doclist fts2q-4.4.4 1 0 one {[1] [2] [3]} -check_doclist fts2q-4.4.5 1 0 test {[1 0[3]] [2 0[3]] [3 0[3]]} -check_doclist fts2q-4.4.6 1 0 that {[2 0[0]]} -check_doclist fts2q-4.4.7 1 0 this {[1 0[0]] [3 0[0]]} -check_doclist fts2q-4.4.8 1 0 three {[1] [2] [3]} -check_doclist fts2q-4.4.9 1 0 two {[1] [2] [3]} -check_doclist fts2q-4.4.10 1 0 was {[2 0[1]]} - -# Optimize should leave the result in the level of the highest-level -# prior segment. -do_test fts2q-4.5 { - execsql { - SELECT OPTIMIZE(t1) FROM t1 LIMIT 1; - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {{Index optimized} 1 0} - -# Identical to fts2q-4.matches. -do_test fts2q-4.5.matches { - execsql { - SELECT OFFSETS(t1) FROM t1 - WHERE t1 MATCH 'this OR that OR was OR a OR is OR test' ORDER BY rowid; - } -} [list {0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4} \ - {0 1 0 4 0 2 5 3 0 3 9 1 0 5 11 4} \ - {0 0 0 4 0 4 5 2 0 3 8 1 0 5 10 4}] - -check_terms_all fts2q-4.5.1 {a is test that this was} -check_doclist_all fts2q-4.5.1.1 a {[1 0[2]] [2 0[2]] [3 0[2]]} -check_doclist_all fts2q-4.5.1.2 is {[1 0[1]] [3 0[1]]} -check_doclist_all fts2q-4.5.1.3 test {[1 0[3]] [2 0[3]] [3 0[3]]} -check_doclist_all fts2q-4.5.1.4 that {[2 0[0]]} -check_doclist_all fts2q-4.5.1.5 this {[1 0[0]] [3 0[0]]} -check_doclist_all fts2q-4.5.1.6 was {[2 0[1]]} - -check_terms fts2q-4.5.2 1 0 {a is test that this was} -check_doclist fts2q-4.5.2.1 1 0 a {[1 0[2]] [2 0[2]] [3 0[2]]} -check_doclist fts2q-4.5.2.2 1 0 is {[1 0[1]] [3 0[1]]} -check_doclist fts2q-4.5.2.3 1 0 test {[1 0[3]] [2 0[3]] [3 0[3]]} -check_doclist fts2q-4.5.2.4 1 0 that {[2 0[0]]} -check_doclist fts2q-4.5.2.5 1 0 this {[1 0[0]] [3 0[0]]} -check_doclist fts2q-4.5.2.6 1 0 was {[2 0[1]]} - -# Re-optimizing does nothing. -do_test fts2q-5.0 { - execsql { - SELECT OPTIMIZE(t1) FROM t1 LIMIT 1; - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {{Index already optimal} 1 0} - -# Even if we move things around, still does nothing. -do_test fts2q-5.1 { - execsql { - UPDATE t1_segdir SET level = 2 WHERE level = 1 AND idx = 0; - SELECT OPTIMIZE(t1) FROM t1 LIMIT 1; - SELECT level, idx FROM t1_segdir ORDER BY level, idx; - } -} {{Index already optimal} 2 0} - -finish_test diff --git a/test/fts2r.test b/test/fts2r.test deleted file mode 100644 index c0be36711..000000000 --- a/test/fts2r.test +++ /dev/null @@ -1,121 +0,0 @@ -# 2008 July 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. -# -#************************************************************************* -# These tests exercise the various types of fts2 cursors. -# -# $Id: fts2r.test,v 1.1 2008/07/29 20:38:18 shess Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is not defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -#************************************************************************* -# Test table scan (QUERY_GENERIC). This kind of query happens for -# queries with no WHERE clause, or for WHERE clauses which cannot be -# satisfied by an index. -db eval { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts2(c); - INSERT INTO t1 (rowid, c) VALUES (1, 'This is a test'); - INSERT INTO t1 (rowid, c) VALUES (2, 'That was a test'); - INSERT INTO t1 (rowid, c) VALUES (3, 'This is a test'); -} - -do_test fts2e-1.1 { - execsql { - SELECT rowid FROM t1 ORDER BY rowid; - } -} {1 2 3} - -do_test fts2e-1.2 { - execsql { - SELECT rowid FROM t1 WHERE c LIKE '%test' ORDER BY rowid; - } -} {1 2 3} - -do_test fts2e-1.3 { - execsql { - SELECT rowid FROM t1 WHERE c LIKE 'That%' ORDER BY rowid; - } -} {2} - -#************************************************************************* -# Test lookup by rowid (QUERY_ROWID). This kind of query happens for -# queries which select by the rowid implicit index. -db eval { - DROP TABLE IF EXISTS t1; - DROP TABLE IF EXISTS t2; - CREATE VIRTUAL TABLE t1 USING fts2(c); - CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, weight INTEGER UNIQUE); - INSERT INTO t2 VALUES (null, 10); - INSERT INTO t1 (rowid, c) VALUES (last_insert_rowid(), 'This is a test'); - INSERT INTO t2 VALUES (null, 5); - INSERT INTO t1 (rowid, c) VALUES (last_insert_rowid(), 'That was a test'); - INSERT INTO t2 VALUES (null, 20); - INSERT INTO t1 (rowid, c) VALUES (last_insert_rowid(), 'This is a test'); -} - -# TODO(shess): This actually is doing QUERY_GENERIC? I'd have -# expected QUERY_ROWID in this case, as for a very large table the -# full scan is less efficient. -do_test fts2e-2.1 { - execsql { - SELECT rowid FROM t1 WHERE rowid in (1, 2, 10); - } -} {1 2} - -do_test fts2e-2.2 { - execsql { - SELECT t1.rowid, weight FROM t1, t2 WHERE t2.id = t1.rowid ORDER BY weight; - } -} {2 5 1 10 3 20} - -do_test fts2e-2.3 { - execsql { - SELECT t1.rowid, weight FROM t1, t2 - WHERE t2.weight>5 AND t2.id = t1.rowid ORDER BY weight; - } -} {1 10 3 20} - -#************************************************************************* -# Test lookup by MATCH (QUERY_FULLTEXT). This is the fulltext index. -db eval { - DROP TABLE IF EXISTS t1; - DROP TABLE IF EXISTS t2; - CREATE VIRTUAL TABLE t1 USING fts2(c); - CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, weight INTEGER UNIQUE); - INSERT INTO t2 VALUES (null, 10); - INSERT INTO t1 (rowid, c) VALUES (last_insert_rowid(), 'This is a test'); - INSERT INTO t2 VALUES (null, 5); - INSERT INTO t1 (rowid, c) VALUES (last_insert_rowid(), 'That was a test'); - INSERT INTO t2 VALUES (null, 20); - INSERT INTO t1 (rowid, c) VALUES (last_insert_rowid(), 'This is a test'); -} - -do_test fts2e-3.1 { - execsql { - SELECT rowid FROM t1 WHERE t1 MATCH 'this' ORDER BY rowid; - } -} {1 3} - -do_test fts2e-3.2 { - execsql { - SELECT t1.rowid, weight FROM t1, t2 - WHERE t1 MATCH 'this' AND t1.rowid = t2.id ORDER BY weight; - } -} {1 10 3 20} - -finish_test diff --git a/test/fts2token.test b/test/fts2token.test deleted file mode 100644 index de5f94d7f..000000000 --- a/test/fts2token.test +++ /dev/null @@ -1,174 +0,0 @@ -# 2007 June 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. The focus -# of this script is testing the pluggable tokeniser feature of the -# FTS2 module. -# -# $Id: fts2token.test,v 1.3 2007/06/25 12:05:40 danielk1977 Exp $ -# - -set testdir [file dirname $argv0] -source $testdir/tester.tcl - -# If SQLITE_ENABLE_FTS2 is defined, omit this file. -ifcapable !fts2 { - finish_test - return -} - -proc escape_string {str} { - set out "" - foreach char [split $str ""] { - scan $char %c i - if {$i<=127} { - append out $char - } else { - append out [format {\x%.4x} $i] - } - } - set out -} - -#-------------------------------------------------------------------------- -# Test cases fts2token-1.* are the warm-body test for the SQL scalar -# function fts2_tokenizer(). The procedure is as follows: -# -# 1: Verify that there is no such fts2 tokenizer as 'blah'. -# -# 2: Query for the built-in tokenizer 'simple'. Insert a copy of the -# retrieved value as tokenizer 'blah'. -# -# 3: Test that the value returned for tokenizer 'blah' is now the -# same as that retrieved for 'simple'. -# -# 4: Test that it is now possible to create an fts2 table using -# tokenizer 'blah' (it was not possible in step 1). -# -# 5: Test that the table created to use tokenizer 'blah' is usable. -# -do_test fts2token-1.1 { - catchsql { - CREATE VIRTUAL TABLE t1 USING fts2(content, tokenize blah); - } -} {1 {unknown tokenizer: blah}} -do_test fts2token-1.2 { - execsql { - SELECT fts2_tokenizer('blah', fts2_tokenizer('simple')) IS NULL; - } -} {0} -do_test fts2token-1.3 { - execsql { - SELECT fts2_tokenizer('blah') == fts2_tokenizer('simple'); - } -} {1} -do_test fts2token-1.4 { - catchsql { - CREATE VIRTUAL TABLE t1 USING fts2(content, tokenize blah); - } -} {0 {}} -do_test fts2token-1.5 { - execsql { - INSERT INTO t1(content) VALUES('There was movement at the station'); - INSERT INTO t1(content) VALUES('For the word has passed around'); - INSERT INTO t1(content) VALUES('That the colt from ol regret had got away'); - SELECT content FROM t1 WHERE content MATCH 'movement' - } -} {{There was movement at the station}} - -#-------------------------------------------------------------------------- -# Test cases fts2token-2.* test error cases in the scalar function based -# API for getting and setting tokenizers. -# -do_test fts2token-2.1 { - catchsql { - SELECT fts2_tokenizer('nosuchtokenizer'); - } -} {1 {unknown tokenizer: nosuchtokenizer}} - -#-------------------------------------------------------------------------- -# Test cases fts2token-3.* test the three built-in tokenizers with a -# simple input string via the built-in test function. This is as much -# to test the test function as the tokenizer implementations. -# -do_test fts2token-3.1 { - execsql { - SELECT fts2_tokenizer_test('simple', 'I don''t see how'); - } -} {{0 i I 1 don don 2 t t 3 see see 4 how how}} -do_test fts2token-3.2 { - execsql { - SELECT fts2_tokenizer_test('porter', 'I don''t see how'); - } -} {{0 i I 1 don don 2 t t 3 see see 4 how how}} -ifcapable icu { - do_test fts2token-3.3 { - execsql { - SELECT fts2_tokenizer_test('icu', 'I don''t see how'); - } - } {{0 i I 1 don't don't 2 see see 3 how how}} -} - -#-------------------------------------------------------------------------- -# Test cases fts2token-4.* test the ICU tokenizer. In practice, this -# tokenizer only has two modes - "thai" and "everybody else". Some other -# Asian languages (Lao, Khmer etc.) require the same special treatment as -# Thai, but ICU doesn't support them yet. -# -ifcapable icu { - - proc do_icu_test {name locale input output} { - set ::out [db eval { SELECT fts2_tokenizer_test('icu', $locale, $input) }] - do_test $name { - lindex $::out 0 - } $output - } - - do_icu_test fts2token-4.1 en_US {} {} - do_icu_test fts2token-4.2 en_US {Test cases fts2} [list \ - 0 test Test 1 cases cases 2 fts2 fts2 - ] - - # The following test shows that ICU is smart enough to recognise - # Thai chararacters, even when the locale is set to English/United - # States. - # - set input "\u0e2d\u0e30\u0e44\u0e23\u0e19\u0e30\u0e04\u0e23\u0e31\u0e1a" - set output "0 \u0e2d\u0e30\u0e44\u0e23 \u0e2d\u0e30\u0e44\u0e23 " - append output "1 \u0e19\u0e30 \u0e19\u0e30 " - append output "2 \u0e04\u0e23\u0e31\u0e1a \u0e04\u0e23\u0e31\u0e1a" - - do_icu_test fts2token-4.3 th_TH $input $output - do_icu_test fts2token-4.4 en_US $input $output - - # ICU handles an unknown locale by falling back to the default. - # So this is not an error. - do_icu_test fts2token-4.5 MiddleOfTheOcean $input $output - - set longtoken "AReallyReallyLongTokenOneThatWillSurelyRequire" - append longtoken "AReallocInTheIcuTokenizerCode" - - set input "short tokens then " - append input $longtoken - set output "0 short short " - append output "1 tokens tokens " - append output "2 then then " - append output "3 [string tolower $longtoken] $longtoken" - - do_icu_test fts2token-4.6 MiddleOfTheOcean $input $output - do_icu_test fts2token-4.7 th_TH $input $output - do_icu_test fts2token-4.8 en_US $input $output -} - -do_test fts2token-internal { - execsql { SELECT fts2_tokenizer_internal_test() } -} {ok} - -finish_test diff --git a/test/func.test b/test/func.test index df9d4dacf..3d7106046 100644 --- a/test/func.test +++ b/test/func.test @@ -42,6 +42,10 @@ do_test func-0.1 { do_test func-1.0 { execsql {SELECT length(t1) FROM tbl1 ORDER BY t1} } {4 2 7 8 4} +set isutf16 [regexp 16 [db one {PRAGMA encoding}]] +do_execsql_test func-1.0b { + SELECT octet_length(t1) FROM tbl1 ORDER BY t1; +} [expr {$isutf16?"8 4 14 16 8":"4 2 7 8 4"}] do_test func-1.1 { set r [catch {execsql {SELECT length(*) FROM tbl1 ORDER BY t1}} msg] lappend r $msg @@ -57,9 +61,29 @@ do_test func-1.3 { do_test func-1.4 { execsql {SELECT coalesce(length(a),-1) FROM t2} } {1 -1 3 -1 5} +do_execsql_test func-1.5 { + SELECT octet_length(12345); +} [expr {(1+($isutf16!=0))*5}] +db null NULL +do_execsql_test func-1.6 { + SELECT octet_length(NULL); +} {NULL} +do_execsql_test func-1.7 { + SELECT octet_length(7.5); +} [expr {(1+($isutf16!=0))*3}] +do_execsql_test func-1.8 { + SELECT octet_length(x'30313233'); +} {4} +do_execsql_test func-1.9 { + WITH c(x) AS (VALUES(char(350,351,352,353,354))) + SELECT length(x), octet_length(x) FROM c; +} {5 10} + + # Check out the substr() function # +db null {} do_test func-2.0 { execsql {SELECT substr(t1,1,2) FROM tbl1 ORDER BY t1} } {fr is pr so th} @@ -839,30 +863,13 @@ do_test func-18.11 { } } integer ifcapable floatingpoint { - do_test func-18.12 { - catchsql { - INSERT INTO t6 VALUES(1<<62); - SELECT sum(x) - ((1<<62)*2.0+1) from t6; - } - } {1 {integer overflow}} - do_test func-18.13 { - execsql { - SELECT total(x) - ((1<<62)*2.0+1) FROM t6 - } - } 0.0 -} -ifcapable !floatingpoint { - do_test func-18.12 { - catchsql { - INSERT INTO t6 VALUES(1<<62); - SELECT sum(x) - ((1<<62)*2+1) from t6; - } + do_catchsql_test func-18.12 { + INSERT INTO t6 VALUES(1<<62); + SELECT sum(x) - ((1<<62)*2.0+1) from t6; } {1 {integer overflow}} - do_test func-18.13 { - execsql { - SELECT total(x) - ((1<<62)*2+1) FROM t6 - } - } 0.0 + do_catchsql_test func-18.13 { + SELECT total(x) - ((1<<62)*2.0+1) FROM t6 + } {0 0.0} } if {[working_64bit_int]} { do_test func-18.14 { @@ -1520,6 +1527,22 @@ do_execsql_test func-36.110 { SELECT 123 ->> 456 } {123->>456} - +# 2023-06-26 +# Enhanced precision of SUM(). +# +reset_db +do_catchsql_test func-37.100 { + WITH c(x) AS (VALUES(9223372036854775807),(9223372036854775807), + (123),(-9223372036854775807),(-9223372036854775807)) + SELECT sum(x) FROM c; +} {1 {integer overflow}} +do_catchsql_test func-37.110 { + WITH c(x) AS (VALUES(9223372036854775807),(1)) + SELECT sum(x) FROM c; +} {1 {integer overflow}} +do_catchsql_test func-37.120 { + WITH c(x) AS (VALUES(9223372036854775807),(10000),(-10010)) + SELECT sum(x) FROM c; +} {1 {integer overflow}} finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index 59bddc96a..23200a5f0 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -1130,6 +1130,44 @@ static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){ return sqlite3_finalize(pStmt); } +/* Mappings into dbconfig settings for bits taken from bytes 72..75 of +** the input database. +** +** This should be the same as in dbsqlfuzz.c. Make sure those codes stay +** in sync. +*/ +static const struct { + unsigned int mask; + int iSetting; + char *zName; +} aDbConfigSettings[] = { + { 0x0001, SQLITE_DBCONFIG_ENABLE_FKEY, "enable_fkey" }, + { 0x0002, SQLITE_DBCONFIG_ENABLE_TRIGGER, "enable_trigger" }, + { 0x0004, SQLITE_DBCONFIG_ENABLE_VIEW, "enable_view" }, + { 0x0008, SQLITE_DBCONFIG_ENABLE_QPSG, "enable_qpsg" }, + { 0x0010, SQLITE_DBCONFIG_TRIGGER_EQP, "trigger_eqp" }, + { 0x0020, SQLITE_DBCONFIG_DEFENSIVE, "defensive" }, + { 0x0040, SQLITE_DBCONFIG_WRITABLE_SCHEMA, "writable_schema" }, + { 0x0080, SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, "legacy_alter_table" }, + { 0x0100, SQLITE_DBCONFIG_STMT_SCANSTATUS, "stmt_scanstatus" }, + { 0x0200, SQLITE_DBCONFIG_REVERSE_SCANORDER, "reverse_scanorder" }, +#ifdef SQLITE_DBCONFIG_STRICT_AGGREGATE + { 0x0400, SQLITE_DBCONFIG_STRICT_AGGREGATE, "strict_aggregate" }, +#endif + { 0x0800, SQLITE_DBCONFIG_DQS_DML, "dqs_dml" }, + { 0x1000, SQLITE_DBCONFIG_DQS_DDL, "dqs_ddl" }, + { 0x2000, SQLITE_DBCONFIG_TRUSTED_SCHEMA, "trusted_schema" }, +}; + +/* Toggle a dbconfig setting +*/ +static void toggleDbConfig(sqlite3 *db, int iSetting){ + int v = 0; + sqlite3_db_config(db, iSetting, -1, &v); + v = !v; + sqlite3_db_config(db, iSetting, v, 0); +} + /* Invoke this routine to run a single test case */ int runCombinedDbSqlInput( const uint8_t *aData, /* Combined DB+SQL content */ @@ -1148,6 +1186,9 @@ int runCombinedDbSqlInput( int nSql; /* Bytes of SQL text */ FuzzCtx cx; /* Fuzzing context */ unsigned int btsFlags = 0; /* Parsing flags */ + unsigned int dbFlags = 0; /* Flag values from db offset 72..75 */ + unsigned int dbOpt = 0; /* Flag values from db offset 76..79 */ + if( nByte<10 ) return 0; if( sqlite3_initialize() ) return 0; @@ -1163,6 +1204,14 @@ int runCombinedDbSqlInput( memset(&cx, 0, sizeof(cx)); iSql = decodeDatabase((unsigned char*)aData, (int)nByte, &aDb, &nDb); if( iSql<0 ) return 0; + if( nDb>=75 ){ + dbFlags = ((unsigned int)aDb[72]<<24) + ((unsigned int)aDb[73]<<16) + + ((unsigned int)aDb[74]<<8) + (unsigned int)aDb[75]; + } + if( nDb>=79 ){ + dbOpt = ((unsigned int)aDb[76]<<24) + ((unsigned int)aDb[77]<<16) + + ((unsigned int)aDb[78]<<8) + (unsigned int)aDb[79]; + } nSql = (int)(nByte - iSql); if( bScript ){ char zName[100]; @@ -1183,7 +1232,12 @@ int runCombinedDbSqlInput( sqlite3_free(aDb); return 1; } - sqlite3_db_config(cx.db, SQLITE_DBCONFIG_STMT_SCANSTATUS, 1, 0); + sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, cx.db, dbOpt); + for(i=0; i)y&iT&0XXEkqc)WekkJ!|VrAC$tZ~IkuREZ)bN-I%=5@nRAQHh$As9A|x zPDiaT9<~M7Q4-xj748Ub#9Z3CI>=KLWn!ovo6^Nly>K!~M_^1rC2+VMm0ChuJD=KC zR$UHPhAf*P{-K4#JKnVpjL`+R!4PLF&>dVAXD=48no76Sw-d%j*ir}$u?#~3E1~<2 z#R=^rZ1djK5Q|eowp}ZOC#XP7zUqy*UUmBI3Gp+rM(D8}v)nRO89JyF`-?Kqz-Y$i zFe`P5I)+L>Yl@o#qkp!2i?;{44g_B{@*z`QV&T2m!Ov}j$h;)6fT8*gLlLe<3u(P+ zOM7|G+Z0F5rvi;~pXcgnfqUJ)$D1D?>+69pBqGmxrs$^Zk(GtncYr=zbxe3EjIZU#iRdFflm25%*DBza<0?8 zU5Td{>UlUn&W*xkU|CK$z7f|3vN3e5ZkyT)*EIg zvkEDviZYuKR*Lu^S#qjMU4L~hzfqVd<`3Wn^6_nfMDAKF$h(RU@_G0b5DbRjNs$X93^*Q z*Z;%zl|q=V-G{f&lFBt%AhN%KYh~O*NWWqGSvD5`>mkADw8;rlq9a#gi?Ca^6MH$* zlJ=#KERQJReZFj=&AWsppmAWTids54*LKj`RO$-H(~E(MYe+ zCI+M)sX?`Ofu016|5CEz*dA%U+UfcEDaCz+LR^|R%_U9X)90jCWU5L2kf4xSU*N`} z20=ik#Z!X>EiXwHJls#rCMV6+J&91vcy&^0Q7I4TQnDjsO=*MD)x*sI6TIiyv>?v8CTfxtazFCvYX6r8fm&& zHEjZca8xsGxt<^Qad7S%C&URI@a7Oq+>_9tGFGt_jrZ zp_%|XsR?L{u6f$Ca^jRGuvkw36ZCEg^OB2jeL||#o(vp1D{WV&qfRewa+*z(H*ev6 zc}ue92JO5nM4n!G@*-jTm}Ga|v#^sZrj!<|3tN5F$==GnF-=qGxVKncHwn*2F4?hGJj65X3UiYA16}+w85$ab z8=NL`(FeAkx!^28jG7}Hd7m$bmOajE>c;}V`;>nKsy^b&@t>m3a=*5x4cW5EKZCP( z`Vt$65v?+$?E|%7va<$7^)?K&O+y~fj$BD!+S!*b=u5lKq}?b)TVDdhn7a(Uf-A<{ zKeY%K-+T9Sv;%J^T+Qh*)J(45~$$18E^SLzTKWL+)V|?oubqwUKbwZ z4zXWbmzk5s8%!O25((t5FN1}M_qz42ZYewg>4&@@!aJMT`Q9Bc}7&H(?Y ueCc$7{My|gq3$&YXTyzJ5;){*Fl8b!R&|>uk_8#`j_^{vKTKqp;w#Og< delta 3034 zcmai$X>e0j6vy99UbDR|H?PS{UPzjbLR%li<%xG&=;>r5g z&Fu*t!+<_yDMi1MvXJG?d{c`O-M}*8DbPQ`=7x1$7QuY!>_lj}Xp*6Eucdx#XSaoP zc5@pSdV3kq8uc@0wf$MyZEcgjktT~_?vUw%VZ1)b^mY{{H$!idvw>g~iy@)%zZueE z$lxH?f%G+Pv)~_tICV&<|646|P1{UZd(*H`!#OVQ6zIR^nHh{{rn*6S^@PYDQP_yT zZ7vkP$)_^?+~eZ&0H5db!P+kQNc7 zo;tOI@JV5dN^RKDND2qUBE*iRtq18m=SX{y7%MD6~r# z8{q?Dv#O8yA16P(FO{GyqNXBvY%}&!QO=yj2o+y}pzKlX(Fz%(A+g6N8JyJSYIVEv ziQQ#ukPh-cS!Nhp*mrbT^Xvj5I4tcJsQo*2q_9G&M__$RIzGg{JzE=gNu*(y^{K;Z zfbj&?Q_ezr#P*g|6zVOzP22K5);pO%S9z!k0#%qNB?Dps)e*&p%N-ENi`DpEr1!_J zN;i3UcvUUl=XO#*=tL-|?LOOm(oz0*OO8eIs}0Of=?HlFdEXl85ht> z<8v}86H0Q1s}Own8(*x86=z5GHeb>EK#x1+zAlnlCf5R!PBkMDa4Mk<+s_GKKKlaRbs%k4*z> zo9#NNKg^vO)^CcLNMc92-BWWFGm?!l9MC4BA}d0Co#|oqXAP%^>dUTliYYl6Gnu;w z8!k=O{FqHoDRw*?(_>tAN14?5R$iOFJL?L-ylta zj$QJfU^~yrmp1_VupK4z>)XCs+m$AdAY}Pw6phQ#cz!f)KN)u*Uur{4IEvAeq%`4p z(9E{n-#r0t3@baijMWl+?>LH}_pGA;d>sxe>3-L-W@GATl~PKNmQ^WDEZb`AaYJX9 z;|{25a;$(KPdP16e?d8#x#NtVuYB>xM7r3>NMyWA$Gq%TT+PaNj=KEkWC?GQ&Wa28 zeU=~0wZ>T9AUj)k42g8fOcJr$dDd)fod|cbOcVxBD-}>b$=RNfdTU~llOz_os+XjM z#a-9y7cwh5tL?Bd?iyrU8us1>54`5umT`n6`(1_0WU6K?^Ab~VwYSdGGzQ$g>KiBW m)x)Z(<@P5guuSVTKrS`RXZTSJgUznGt$l8l^ts&)SN{tsuf1ge diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db index a4969047aa677cdad6e9786fd825d28a6630661b..6b434f6bbfd84dd6c3d92c38740c9359f1d2f980 100644 GIT binary patch delta 60515 zcmaHT2V9or_xE$%;~DH{@5N9=#Ro-25d>Fm!Mz3d7SzlQJP(N?#J@?@ArS-*U#rZ>OHP`uIrrhJ>Toet@3iYHD0dJs%|Kg zg~iDJ^Cz^ZNei)|EOz>b6=Sz3*z0?Pl;82H2Knt|GJd&F=6e$O+LJ&=7e^OQmq3?D zmz*vIT}rxCbOq2Qy&FVVFg^(kiE;0ZRGsADL9-AW+W=Lj7zf07LauvcGP%QNm&t?; zUaCcS$D2TMmA56yH@z)L=6d5uF7`GfxxgEXZQ?@uv79*XB>GY&6B~FZl8o?(Z|1fhmw5c9Ypdc??93dyaPzy_4X%u$J>wO_ujrFZ+O#4 zUibDPdBxkC5+mY^}Hd*>p-bY!D&y6{2TD%XQ^p3#dKRm@ zrYMVO+yvQs^Whw)O^5sny%x?K(!LW&&0M%i?QJT^PJnt+QX?ph(72#B$g0FWx7F#K zKrbx9xcQcm3<^8j6QJf|Xai`vPumcpTWU=(Z>u(jn`eX>KN?!Vms_>VA$yy4BE&Yb z27~8gZ3vVq6iKjohjsv!B$L6Eo!SP_2eq$5X}u-{uCHTC=#!>U;O(8-4GhkG9_E01 zJwrmJk6(i5J=%c~H^8A4W;c%>4PgtoF0dh49|Bo9%)|OZW+kCN>jf zZ^TPI91P``Lq+`jo_f7pj1y!v{h!~hiO>W}UCvwK_Id=_TByznPJu99YliZ;uu!}{ zGRe+x)UWDNCerK(UM#Fqw`bV#vAT+5KB#*Vq=%Y7F&MJ8aVAWDsy@bx`JT9OZJ?-4 zY)=Rp!##oxq9y@zna0PlSE24%oE2OXxHBak8!KV4h5Ht+P2{Q&dkrh9)EWle zE~!UC?zzN|u~~@b0%J=dwXLQATy<&}^ggIoz{khbW6`13e8;iZu}U75!XT`vMuBN3 z)Gb)a?Z|h*Wz^hYZ(wymR2+k+{oBljte?UL;xn`67p2%jke%!PVhDm@M1)48T()(# zw>dq4%xwMNgFxfcOk6@8Uk-H<8Y?;@H8**-0?r*(Ct~|(%~75eK;DzcrWn^mbDn2~ zpf+nFA>~=53gbMQ^9uGBg#4i%f+G?%n|W3Y^>fs%plX#tz~pwCV?0|4WeWn_&}?%+ z5R`fY_o?EuXLj;(?0 zY;^=S9IR=eWC_?iL(`m7t$Tfrx3wTE&U#*7N?%7KSbr`k0CZzC(^YHVp5$#M$SMZ< zpBC4)H_8K~UFGe{v6`}Av7I10vRHaF1f>;DOSC^G|rnS$O^|x55@%1+ht38 zdD{xIBFBq6rArgNZRj2U;>>(r=K9Ln8eYlXgV^r^rhw-Y%{DYG()`G=jSyO?NyGlR znms(*1on?KCAcPEGfBa=VEy-+Fb1^=AtH=A5)g;omuvQOY%A2?A!SOs7NCR-pT+}= zH)#U#QlX|P&$dDReN8Uruhi^Oh)II%%;upD8UtF6YHsta5*A<943HWyUdd9isz%eE7eAo4_Cx7+n$5WVf@TQM zKEl%5n(OS};}D85S2b?A*pcSWKk0dpyN8#u~Mn+#v%7KmNu2*1WWNAFtGD4+^X+9x`cDdGRy4brQlX z+GwcitbK@fyY>?$>xQ*aS|`U&R7PvtiL5&&HPwE?iKpr18d%Up+X}J*wJ%|wN9*MS z&Sf?k-DFI1Qe1PWzRY#Rw0P}ao}EYc)!P=3Exf%TX_)pCcByi>mUQeI3K`mGJo_4jvC`k) zpfFLJsuKE0My{yJPSbc+YlGlCwHhn4wcR-JD{4R;xL?&az?j+ED?Ixa&MnlYK>6{| zIgm1}iHL{iYNM4x8fhh{D^+ojwM^Rv2jpqDb7C!h{2dH0)DFU7`Py2A*jJEkCWCWE zn}Mavw0)TBhm|sKx**#s_-_K;ya9;?+N0u4`t%kQdbM%TeqTUCq(c4*68Z_UHQ-(w z;csU+R;|;v=h*GaQtc|a*k6#X>M9lU_a^h#NZoB|AN-N7`%rZ;wAFu%Gm>$xRf=SJ zx6=lC=I^D1WcOnk1YG5#ASIYP6F^_yqw8VQrg-*3XhVi%-OVa-AoXIT^x4Z;bx7Nu zvHHp??P7rq!knYpT%P@0`I$CC6oyK=nt4V$7HSVBN5Y=d+U?@wg6ZBNF!GFcvhue# z8hZ!7Q0Ls!+Hfep5-LL4S?wP27pnGmNDov;!r^7w1k9+>z8A>;fTFPqYTF>)R(va1 zXXfzEH(E1ePw`Ngt^*Hew;Sfbfg9SF*q`XmWRV*X(G?6y|m z*fYp%*1(Le0Wq~4_E|`al2(K$gQ7jQDVP-!Q%Em9)bboBGCUM1m0fkd(MQrd93G06 z{{JjSw+$Zrq17=SHaynGVC+-vMJfie8wsX;Hl0BwA12E3K58co)O>KwO>Tjko9hPB zE}SR?LwQS`3M@go9nus%p>h?FdX1-jD@aHCmJ;HwYrDdV>)M%6xRDRV(_y++JX1mD z2}KHo?l82Hy4eX2>2#fW764s-(0+s-G3FEpHFvagq;>{ksx9U)4_WmMjNp2pZ6MtY z!c%4hinTEvOWqU%AnQi70{(hv zRDu1AMmO+{-?az0$|kyeQ5+%2YRM#=(T)kGg2PUZ^$w@*0{IgEv%5o>DEZ;)=TljA zCV3W?&P^s`T-DvN3_~HX zz*)=3c#i2Hdrrtis80(Cz&RwnpT<6L9%~2R&@paw;g>w`^kbHsf4LONAGo;+t z%|TBeU4$r(6lA+Q`MZZSoA()34b+kH8!89ubb^o}$X2ha3upm_71|_7%TKm~VT<;A z$m*yrgp4=H@Rq6!0Z`vNN`$5(b*W10o1%A=AS(g4DKrIK`N=(Ae4v-+%3PVDqb-wMme=|YukG*nH~O%|QSoxPdZY`U(7i8d<1 zg+i8&b~!f+v!%bofj#8h7&TWHsT9WuvK3_4&S;0=mR#L%o<;fR2&40LawVIH&6eq& zGO=-?+&ck0E2#8FOBZ;@!-f?)uQGaRfOj0ZCE$L;;J181@N$99s%%);**lgTw&z{) zJI@a~G*T+!J>;^9k(Grya$I5{wW@(0b}Z9{!P?md9VWf4JFZ|&plr3S3wAHn?dMrj zcsMxB1dHF%c_Cv+m;q{k;Z%5Vqpl0jVj*t5?iWbeqzi%aExI%)+@UimZ1e$tq5lZVx7xO$#aS znnSRvN=H6Ib82r(Ska|zYfL<<`;2F;AU#NiHv zUMCY&T)f!oztXgP`U< zCx^7j4kzYb)A@MT4%QT!2IA0K-8P;jLDBH=UrSb}q=P57eH3PWrz_@IG8D~s#DU{? zJ_A?X(oN^d8_ON4Xoo3xbw_xX0(m19F;JT3Nc?-T)$7LdEEUd;F*Jj(qYMpj$Rpit zg)ogg-RJR9?M%H{@`LYIEUtL3Q*l`sN+0=*0F zoz-jco?CC=STA^UzCIZ{MCz+~@)=-id^kOt-b<-DcVh?&c2-p^_4*vQ` z{}*lBq6$ji*Uv@hrJu^N!C2c@?_$tokbW5Er0eqo*f6O0GC>c`el#tDlt1(bAnSsu z6z3h%zs6wRDE%93I2`^)PoY|dobLfI}}2UoK7 zy`W~9d?}9Xu1_V9puK!BE}5fW#Aw6H42$?569P0LjUi!!qZzyuuD^l%=IMKLYz!2B zp`U^?7V1~>Y%FFs(knT*H^r%dwtHiO(dE_OU~C+y-{vwP>pr&!T&uZs7#c@9IckM7 z0CsQZdPC?s{U2DfMBhSW6CnGR+zvT|OCpkUvBwBBT(j0vTgo#cQ*mCvVLg?+uEj$Sxk0;I(A5dKM zi4EHKbH?KST)mNlG!_vA1$Vh#aIbN&0?)3|$BS$#)MW}f24%Fm-&A;C!R(LpE0~x?#m~aVPxR-Q zq(0ryaY+9iBXuAJdJRc^QW>7A*0<)^%dqOIsSsBl)z46{SN3L3@^V0{t_!$Ui8G<< zC1n5{c!hS}e@5891`TX33N_MFnOA&nI&C5g^{t_1D{W`9F6avtY{BzNdtK94@@yec zO(*eetv;R?7kOHEnnT-h`eJ~OX?IS{72s#l@qCw+`Um@mkl zz`2*5{-qd!J09!%bD{*o`G8$DIN|JUQ!!RPrKZXk(y}e=3TkE?Ev@8NOOm|{1lcZ` zbZ3NQZ^K!HLU=CSJ zCr;gSlBe=^ii8Yb5dH%xH?#{eS7#u5@CLRT zXgI;}jM>nVV{byMp@!j@VKcnPvt=;Z`bFldu#2*lhbhAIVHgsJg{L%g_xxhXc{_lqQ*sr~zP!w|{r%=L?hCteEo+8$GgZf3U*An6;u3A0CyI7Fz^(!zf3S$%L zrKQp%f^fu=I8RF`8?0#jzXm6W;dk8(4vqmV-)JzSy{F-Op1qAlTcw5+>I^On$>U9a z&we!)?KEs>xO;$MfQ0a5MHnamA8eoioWkt=1_#`=8WhX99GPxh&q3~LV#Z%7{1rAh0vnxVTJ-YV>b+wX6;)F ze9+K>y^kqwLlVQ({f19Cwii;OB*i!&DaHqo(#TMa$Epn5RgA~16NUmN?x3MU$ZTTV z0xMdX$lz2PJ7M3`hCl@qaO4F;8824SD<5IpXT~EOetOl=j`1HS#ADu7<9LQszcv8J z4nX#Ms-BouYaq0Pke)f=>MO>>ctmMT;n}AU<}xN= zyCCDIJo^mXh3Y7r@|_`rVc`dALW!5P)ii?PQ6V8v_@F^+Sa^%(Z^Jjn22gd&0F^o; z#U&>o^`fB>B)2ql08ff(C!~qSP)uxPsAk|fWtasct;S94B%Db!G>5O(7(<~)Qv+?s z2_bSUv>9jd>=c|sQ!}!F#sXO0*fbDk#2Lub?`^cgo>m&N;gcc_IM;0~Qwd5kR{r2B zt+8>B_&N3OEaW{4h=Dz?$An>W6XPPKs1js5XGGZ_7k=8#7^)Hj1lfnJkiHCI(t^=4X+Y1>bqT5;8Jgks%f{9W zCwDW}$;Chl0{j2x-7-$RNH1Q2hl0_L?*2v!pRYp2AY%gz>u!9V#xvU(&Y(2Vm;nWG z#ymKC$k+~)ql_Vv`Th#jy<-}}gYWGW#Vl@<4z91lR>Oo&%z`;1jQJe926+R?n{j>< zHW**dFg8@OU|cuRSjCjz?2!9{1lc7h`^hldZ{DHiG9LtKUmABp)+zZIsSlUwx(?~H z^g8fdGIYbtsm6DCR*R*bOuIRJf2J{Al<=%QipE&A)My|Vt=d?P^7+O;8M^^R%?xqa za-lIpDN<_>CP=;BPjETP)1W?uW-joOd@FeR8NE_nb##3X-|$8Uj2Q5@YD=Yd$lxsTnaHo}WHNEsq0(5**iYadX-t8|1%&V{KWwx~2@oTcpC|KCbCgj`uQOV( z{6k|u0@2`HcjIUOtv^Wm6VKQqs2Zn^#lmSuIm13zjA{-F-ZVBZ8Df03@~E^rf5kA9 z=`RkslSanaZ;-v$Sb(0v#%&z7J8N935Hy19gXb2vcB1i-)GdPC#3xwxy>S#R;FHE0 zC>&^Ngyr3g0>g~2jM1W~6J*z8BwOZ>Se~w0=+g?a9Si?!jDC{}dra4#kk{DQ73!Y` z1Y+8&MlS=`M$>q#EvJ!?Ch9NA78&vI8!;3dq+By>^7o-~i1D=l@iQo^G~R}==Eek= z*opPT?0d!{j+4tEuD7u*Cf6H}@*H(vcw~I0AP_FSpJ@l?UNkHutJ1-Ak!p|&P@ypP zfs{4I00?_C=B<4XOos^+!aYh;EK{l83G*2QS!JZufwvjWyIlVEWFLVq6@#T-&Rl5{ zq*4@+lVTcyw7<3FAZ4%VWvJb%5FpoT%7w5`j1=9;m(@Ay}of$#d^| zqH#N#(@h7N()6y{XAxvapJ$t0v-#0bKiOC;wOdP<86U=*Dj9w>z|@Un7Eli{l4&1o z`d-0oP32bOY;WdkOr|vXhytM0_=p0a4E8M`8Q`NBMDdr8hyaQwzE?>8>LWUU zqSp5^$y2_WBoF&$kgW7gBe};nh2%EhWRmath!vqI_Dv+Y!Z(3ru5TR41wNujDCYPw zNzU?(A~`w7NBjsyhL56o#R%UJl7oH3j8OFS4Ir81BOZhz)|W=Ip|3Ydldl&^m9Gbl z!mqw=B!BX~MDo6`3(4<&ok?Ezbs~A$M~n#haUU@vIuPw;}UmKFke8l{aFY&b|x!Bi|$VRlb`fmrn&LpX579 z-pO~6Je^O7tnxs0Cs zo=;8kmX9hHZ~7#_;%gt(n8y!wwF~Ob|4(J=4z| zOTp=SE2Z+X+6D(;yN9MvIm&)dH(TH0;QD%7G#>iZ)S0mkINffYz;McA)7u>D2-DqG zidCMPuJf!D6d09_AZxm<8`MTwH-an0-WWSGb1#KRnWR4*QX5k6OQkuQ7p3+45|rIA zQz#Z>-k@R)w>;CEIZ;~s)c@I{4oxQW5tSHCna+5~t}}Olsp*MIj0rbiWUO0dr1_#K zHloq$Dn0B9+e{%L=!r3(Wnxb%qYuVCwmxC_S5vddu{79lUjk}9W&&!{VGpwr=ONxq zoQJ-+N3>DGPSwWzgk$}ne7}Wa*mmX(5*}N3(2|OsQ_K$(LX03I--F;XD6cbX;97)6 z3C~^*ljGF&_N5G6-ObmTNZXox5aexEx*+*m^Pb}JUK`=A(HsU2D_ZL5_MWk7tyIPu ze|;l>DJ;)kls*&=zLppXVVp&WROBIPeT!U9HBP4+b=R3>kr zG}!6SyI(WI+)Nxut;zt`0IMEyY_^{vEHoexN^e=d!>8G11;;XBM!c2G>})fc+0mfZ zDRfZpF{`oBT=P|4f@|Nm062Ktx0sc0_xXD{7KL0ht=e%=*2y{nr{tU8=Gk~W7h-G1 zN;;Uk;o;lXRB}dmYe#8NCg8bFW*37kf^`ZOEH}@Vvq^B~T{BT9R+=f16Gw@hky5({ zOPI4+@@HlgPWBOFt8DO#+Xm_Ow55Z5#QG{~{`@v2DFSK#l22XM&=-s6%FMAGn_Rik zOv`&Z93JPQ%F+E4*~){1PKyb$PMX`} zut(;tJo8FgYKOvGK|OHl?`Ghc56=xSQwEn*+KHsSyZwF0#|m#^AKF0sn?II1n}zjx ziC+JsSFvcax(Ac`ThFk)!cr@+*60ed+-2fxANTRKLUoAczMob{xdL}DGv)2mb_S|( zMTj}bU&8C)PB&Yj%4unUoplz{>o<$bCb$0It8hMt^irT)OrV{n|BEpRk+2da_G(~x zpNVhKaIAn(llhzCs@_3(KiaZK$=cxhI7=qSmRH7GE(>B?VgPn{@p%MJXk&Ss6AS6f zVkj$483i>Rl@>_b>ntx$>k)=YNtOvpfyi9jo@Z}prlK*di{$|m-=c3y;9;(#``(Oe zrF?PjMvA;-a)5{Vj>~&%7JC?k8XXTIx4%UTVSOw=6M&i5-9n+nH|q)7#8>-T7BjXQ z@_Jhe;MpZdIEKCJSi<&}d(&8PRTd=&Wb!qjj(6(zmfn5J;QDc1y?4>Uy$qT;9kk!d zR~2W$6cpBhS7N2X;HG26z($9r0i^LQw{aA?sv)I&`AT_1o2l(M^ zOAiUtR3Fn3qpxGzrjT^Xq{H!ZEmcZ+JKB3F8TgOB>TzqHWe~?!RpwiM6C|eDTH;XD znMdQu*DVCEY@{x1DK4WtT^6TY#wuF{XHwq^EEm{T6bdc0#I`}rPo_I~f2Bn)A(u73 zlC5s@w&esd%b@0osR?@4T0T~Z$%5>M*^oKNI?Qi;V1Lix1~_lCWr?4-Cf^O`thUBb zKPXuNrP=1OxMr(m63_O)y_QxlF4<{W#1ksnv$@#;8}thOzF1p52aY7`6={qspl2)d zdHg)t+M1Q@4O~_HN zkpxbTEVK{BkyVyWJlh8&SK3!XL2s(qa?Em@BOcqibSq-oWlIKw;b$$qancFP8paSt zU9wPUcFIDb*@sZJS(OT5r!Beopj53VrpY6{2ddg9v_-mkz`vCCLs6Z<4LJ|>{h&%s zU`_5#j!3g-Ey?)p1&f==a8SJPZIcQ;E8qLDsZFmvUzgOT7$gSst1RHt z6VrVu^kmgYNhK!0{qdn8lKnSH;Gg^;U7tbvGk)T8t)sBtRs~h9g15jZt#u>&9Iltx zF5-ql3nM41-t-BsHIOI8PT=)5c|zp~SCeY1@`#7HZ7h-V;sY6#h*Xe-6JHMnBEonU7g zOEZ%cbdkU0bCpl5gBi@&VJU(KcP(TXusT?Ce6YyBsE4+qEQ}@L)mg`wjZ~~>j$lN4!T!c zr%{|Czw+5aUk92EC|jXRl~iYx#Qc+A#q4tHa2_&-S>Iz{;o)Oe$-5h2eIjRH1G}ME zPVJ0w;mK4RF|q5%S+yMd2HML5qp;CL>kg$vqCD_-V87hj3D$=Ox^Q8()yIj~sPsBW z-DCS0i0#!DAJ4W15VsFKg_bOWzlJ52K$gMUL~@{2*nYnCs04_5R$2}L&FTnrF0$U_ z*iAV6l`WOFj9?{tO!jXWHuhRCGxi-;jY}k=SC!kEfN#BO4N$VqxNf<1w4V_uzXfr7 zY$v69&m;NlWC3!|+onNsvGo|cO&D@8qXZ?|MnKH3rZ5~e-@3v-%XiQ{IG8NLo{@>& z2&pv(mcW@`Ou_im8mpSIAHh95_u)_uy~)(rG@m0*~%ILi<$KWLTvwd!Xo^>=V=uwRmNK7zqvj;PA2 zdG_o0Y$g%^B!`r+B$@m%OE3rE2Xb3a2G5$> zZsUY+t!o+k6ArdBFDQw#HN)LErDgLM(cLW`JQQN<@WQw4BykPNyRCxWoy~{*%K~Xx zEFi1-z`Bo9$WW-a5`M`+*%6l(a?geYqtjulW03QOHShn@E0MaZ;E`50eUtMgz4MjF zT>bAp(U}dILcpwCdm#_&_FB3^%_9p%qcVjkdFHQigpQjVNj>Vrm>g5{%!esu-_D#& zop~m`rYK1^(`s%`s~Ov^usiA8Kv%0978cm^SLIlV2QO17|Lwh3VQ;O?Cdzxz%)Zt2 z#mpv>E3ODQ7Vhg#_uqI9vS;kHb(LoQegDKthG94Dxe9_W6oJSrw&@IK+HFM~3;KVJ z!F;5dT_&vxMKESBwo~A~ZjYrSoUFIJh9h@{z2|=vg4tgGBN_mt0T|Apbz|Ep77A={ zSg~ZHZO^iV&EZoFoE`CXF+F;LZMF>1o2Kp!307X-*T6i#-wy&Od6gEhiY7K#mfWT=%V zYo$H0Y@lr<6Sef31xg#(ci^7Ew*EY`V)Yy=W!b|<*a%awLG^s=Z&1I`7KDmS+f$C& zv3jw!8JYICEoN}7CC_G5o~=Y$I0^^mjZL&NbWXC-M(Kp=B~~NaH{0?Urnu}INzSkx zk+9XQ;J{L;x^&_nowreTO{NG(mnM+bHdXsNl;2mR;@NpN!dxOiy~cKzI5##e&mu8& ztnHyhT6+Fb6sX^|Q4TiWMjlH8RByH||GO%ns;Zz_mMySyneBvJ>`jB@-+K1`Ge8YR zLhBTb@SLC=K-JzeeTuFU8*z&$*`ZXfg0y?Mq3R1; z3gjdNA4ROB&51RKqER-YqJ3z)!4qnwzG~Zq&2l9Rlf2d%hPMyeEE058G{L6DlMZW3 z3K#x%Ku$2$64c+??m^;TrZz-hmL}!1lixmGc~rBJOx_Wg%;8o za+<<%z(w0`jzF;Nm6k=%-6bG*X%kjnwe@0zhq2L#L}$5X^GSGE_FBu=SXXNkmGZu{ zndHVuj+?}|Q6%pR@uds0J1%M4*^3ztWn%^jKiWPO2`*N&m(nIQD0ihnNkR2n)~3YD zY-)dxgV46YcFELIx=}`I#^zg}k?|)hFEysUe-r6|Y_avha~((0@zG>+@~m%3A9TdH zk-;bFku&hTWMN17Kj;K(iFG7qj1FExy_}SI6nAp=L5y{V^dCaWkrV9X$i0Mdh1Osm zYCo_=!m`%El)=5P7$gI`W?q}XpL z*g!}hV1EU!4YaRAQ#bp2fg+9g#-QgT;nK8^gAoJmIa05NEQ|IH#F_#2W}GOcJ%(c3 z96QZ)xy~I7rGF$@A?q#T__?agDlF)3r(y3i#_qr^!|cNa;#O9lcE#g{Irb6%*Drv+ z7==lr?PUr=2d_`EpI421E7?a!AvVeW-Io2*=nSizYEM*&L+RwlLg{f2eDh`dY)+K& zG@~K!2g?Go2?-j=%_hu+&9ie75SQn+eTUQTS&|txTx_Rcb1bOmnTeo~YbS!jI8-k( zKOju1(jvfu-z=ft_7`%3mjhZbgy(WscywvR>BvFio_+f|FL+i84XF`3)nBE5@fl z!CHF*)r_Jf9~~Zecd^u)PQb0R=PIWaHunt|WTnroQ|WZG63(u(hpVQX9PN`%QNVN0 zfp8TSc3f{iz{E`I{!|o7?c_U6hm5V}dAMk+eF*`0MERC(&V>5Yt~HQ5DR>f;R=XVF zsdkOVNt1(tm3)=d9|lhiPJoPhmw>xJwqF<6C|prx&*sH!D)?oHYwbRX8F!MJkw#An z=D5nw>~2BK6l7Zhs3%=;>M46ZQ@&E1QT0&{OVe&O)xS9Y(+#t{F>uI7{r3l{rEQ^$+T#}9`38}?99%Iqyd$6fnO zhI78NFP9Ug@!<*u9gMqc-=-AD39_3Vs0scsN(qyiIbtB|7kjZPuXw6&EVXYqJ@kX4 zjr=TX4EWZb!r+VF>`lbQRJ0c!RyfY%rQhvz#?J@UF5>8BS)2~YSnv1=Gyk-|%dvc@ zcq`E=0ZnA6KYiWt7Ar2eD!!+9rYZ0V{gSYBSWj%af_&l#eEuA6n}H? z&_CD}1-N${4)X4o758aYLBtrn39FQj@m%r!r>CV>EHBQk83Bc#IRc7DWzhM#i5eBQ zh;rB!q@3wZ9EF^+VE=sIctQ3vuqbs(asBuNKL99=R!P8XR6=1~LR|AUVD03fBdS>~ zgRqUqaY8zx>dA2w;i?2jrAnMAozs0q;JLDoR&Y{HT#6=p0+E~RMj)nr=ZG6fK)N7vr}1j?`R z0n*8ressQQw8agfF+v2~>*FvG3OKBtj^NTq>5fp2@V~>%mW#W_IcVi&40OE5h{ITR zp!HxJKG?BE;xMKbw)qbFsM`kM&%+&I5{I#iIG@N%p*7@d*A$Mw+5_&atq~O@aj(3>2k~+U}Z`?%UQYGd& zKI7OXe7MeWjN#|6I@)qWYt&NV0JlhQq$2l7mCmyPRQ5T3ZRX&V%zQ3{4?Of$f z$A^M2MUY*Sjx44S)WJ@Xp&KlZ_qSm?l}3jmUUAxs>*oex(+?b%c=rC@)LSE1v1joa zs4tZ0;e9QApm(XGAKv=NVI>H#*wqm6;{%Sa5(0S6O7!=vW4x;PdiGkpT;-tMmUszH zx?6gJ7mCYwmKA3_`3?^rbD&&0gmJ~+63Ct7bm8SwjtEY~im|?_C1)Maq@%Ek59t(* za~T%aIA-x|KRg`nB)aJZ$LGBKqp_{xJYz7pyK^WxJV*I3oOOlP4fk=2;)*8WP=B1? z04I7ocj2tB9du~-Am$Z1d4k(QorRFL+1U?L_BSHxj7)I|(i`v|jKAgRuM}oTsS5w0 zfcgf`)aOg??I`C<(s>ybgl1|)s1JwL!$4Hz9Mb+wszUh>-T?najoX^oA z=%Z;L0BQG~3#92iKvy+X40S~nr5RqBmRS0`V+_HCkX@XpEw1XXLf2Er4PJiK)7sM_4k~&%XYE_)e2s8JC_Tw{ z#Fqr;0R^!mXSn(Ar2TpZfhNb2xB7jF&!A|#vxYGEq4`E_N3)}0AzRxuv;JH z?4MBh%A27>6F|MO{TFy)RFr{9nE(Xo-6cEDAod_X^Pk}) z@7VbT$7;aw%udexB}WVF<8gk#vvZjKmtzkr{_}ta4)t=5U$xK~S)8&x2-B`P2%MSm zIiCpE$2$oek}1x^!_Rp#Gzre83U&eN?rJ?Rnnl**B4+;T*g(qHRcXhn&d!dUc*)bc zO&qwN+4WML7w}4M!d0)(4>g+~XRbOYNNR-RCpByvr zz&vLF9Y!DGT*9%hAXO3M`A0hj;l|<4Q9KbUX_IkcK{ZctZL>`01&)x!vsWGaaQ|2* zL9*Wz$5ln*gA=@(K~P_3D$bwee4S(0;q14LNQ|E93{Z)bLaCb%dDol;l6L-3ZuQNE zGcP+Qi*+AI_-0|!9OoxYyh4w@1@*%O%2dyF-r?De;_3x<=yibK0Xf5*c~G+=v70o| z#5vu#*g1-^o5f{)>9kp{^D~}(S6rP%KtjHgfQ0X{YP>UrhzH*|;%M_1oNuzqWlqXl z-om;+c`MNme{zmvkYDV~VYgwFoBu`9Tm>&p!X0pP!ZoaY+Zn-25aIM_N{y^_p5WPC zOzqU3aGk=t$#*dLUFWYNyN^Xz>{BTBb}(rKWDKH%l7*L&XMk&qQ(1D}X$J2OXC8Y1 z*_)lsrS)%ytT5-;e;y~|uREQ=Jh342nh8X$FQ?<0gdVy>BU(b*54v(F{Wd5F9Lbhf zOKv+T;qbn57wlP;5(}QZC;@g|(JOFzrE`@;ku0-1qoB$vq+stLfr6y^=6p1y1PJ6O z$rMD3Jmw_3cECw??GeW9cb+A_T}yr(>=uP2a2V!F4sn2?KS6K|Bil-zWW_ge|8>*(oRYp zrfhe1pkVLIWcu}l^UnQ@J%+l+$&~bd$Em~_7o5RLO8PFk=JfL7Z}jO?sQx3lJ=8p= zrzuyQJxJ9=!16j^eZUhmw)`+EkpOlTv$%VYW@r@&D8r}Gbv`0sW1obNGwBgjR|uNm*U zKn!;GV<%;2Yohq$Qi)Q;s+6PqPw8fG17{Mt0$n!*BDcDmxccHcw~H|Gc#Z2Uqod0+ zqFl4^6`kvWLL_?KAp@k3bN&4j4$Wd*n{c_^HItJQyGR*CKOfN4(U>AU*F2R#XKA)Q z2ek^vx#FK^59k9r<0?_3LLqf(BReK_a2+9KelO8h8SA3{g+cW+S0uFS;;MiRmtz!I z>v7Q%Py;&_IIJW#G@h)cYY5(rcfF<%7t-&D%$AxnA3_GX_T!Eu7ajH?G8D~vZIa8z zp{s+7;2=HZS;Huy8Q}`TxK1uQI!7GG210+JB}K=DUu&x%P|oEC<>F2ScKKH|r3_!` z?J_Cp1Wn;U*9uLby5w7;|aja{& z^sgX1H`UdgXD-NW8hQs@`@@=}E8BI0BM>hAd?5Wo!EDz^r959I$bJ!|!I%qB# z(rIFdJZ`&}!Oj9#f2lFQGuBLMj*UFNUDB9&pvINB6?c}q2JwW;mA$4N14Ya}lce^42Zq2cZpG|`Wf=3#lY>x`V%Va{i+TwWv^oU%2z zTf3w2bSpPSofLag%uwIPVvqvY2U4RFC=;4YT&Eh>GnGh-yRseR-Tr$C;o_F=hSIjB zBtJo!h;^UXt@Qb6*Apzi=lVf%V3h5kj!$+%=nf>o=qcO7_112?)Q$osmC=t_Wl*Mb6Pi-*y1}zlUsYz>TXHs_DG&r9i?2~j<=C*4A{St%AJxOXGk;RmqpvLaLiPvu>o#bFI?@puq zn7Y~(O01`U+6C0EPIy4>eu=RzQ2QXs1vP&r1!6O$`+|aXg`#SEKdcQ%wy}~U_NPca zE%JA=8|0mJb%42DEN)yL>z+ktFS6_!ysLK)QHV;)Fm3I{G&DuXlS=eEq{rwp1^)|{ejdOfoHuiC)SVNDx%3!=4QYHBzo-P0a~lTnS2XoH4i z_dP}ox6BGRF*Z`%#Ml@BsolaGVbe}-ib4iLts~+w{@K;tRUrgQXC0&jeF-0KgH37f zF5=*Qv-1P6KFuAOSBIT;GG8oHpoeYhM-?}j_@ z$zZpN_-c?B6#g@$@hUpkH&2O>^||r^6kd!x2Wb<+Uk;0F6W^+3e9IOs;#(3|Q8^s) z!oq1o%W!{1Cy$}7dw4Xa4UV`)6tJJ&Gr%{&oy$gIUGH$(Pw4o&9up?HPs&*ark{%J z%Ky6wu~0h8J%(jsdQId}D&TzhdKmwTdmS5%=@%l&y4CbinV|MWL`zBi$6)&9NJ1S_ zvLp7OhV<96XD#thI-$PO z{UOgLW5wt0WI7D8#!ZJoreMWscO)eoED@hv2>AZUbX;Ag`dI2TNaeAE)@MWd!j{oqZ#+pgts=uo9W_707#A)ey>? zhEIc(3E@>(9u`i!ohvJ%J6=5Fj%7rKtLPR!nE5B1L8mX=huJ&|!ooX2mRA{s{V%vR zO6ly+y~R+pHe!}Pg2c9093-!6K0z%w+wX_}yX;Uu5^q`1Ro_-`sMfjmi zxd_w4A|$_Nn46MeGUZ}S*F_M?_pe6nn#0-S5mzxcBs`Zqg^S@50HDmlbW_BB4iDaU zcVR3S&S~0ZLvF*U##mbK9>cRd$|1UG13c@Fh~psZuf$l$u!YlL%9LKH{+7n_;Ir^x zZ2h}?f|6&IQ%^YTaFb?Bl-|{{JB{LR30;p&ivFo{_ZJ4`Xiv7iIPRkMj%+%)qb-?i;hO49~2YE8~`_DY&JU38kjy0;YY; zT%cy!H8ACBrGlAhse#+k)C4ozEU~oGG$G9_OQE!^wEpjVXEZJQ{(qm(i?Qe2?cDR+ z=bn4k%j8K3&^Ko2{Ss}vMb|F|amI~jbjf(ZvObYzbkm)b7P0#K_3a5?M+y+nR}E!F z_v*f;iQRQeC227o+^2`RsXSd1L)nK7T5O#6>lV`Yy>(D9ma&5$=>Oy&P8PXt*CC9R zQR+6VR8~1uHH+5Wp@U=E5|+AC%jm@%-E8XNtFR|1Eh9_Cx3?%Wm`r?d7{>8htrNXW<@qsh=Cp zU)!=A;+-+Pvn#CI9BnIFJW)4Af|f4Duey&_yE>P%jBVP9H2OE)b7G)!%Kl0RQ*=%% zT_vRD5h`S5cuSkb${y5BhT5)imcEc?OxG>oet8ItZ$WKl>b~W%#=GON{q&lxdke9~ zEI_GuvFvAWS;%TX>?E_gTBL>!Z4(N+|1!pH($cu+bkPjS*f$ z9n0CLUB>b{X`zFTQoTww)3vWC{@x|v+#RJ_Lp3wCgT))D{CjiQ$BWid&1^0BznsDM)6r~r-n-Hz0el?pjC@axs*3}ld1~zDsak4x(ax<>tX1N zQthCs>3XB|PpZ?0Cv;^)Jc-7LSqzWCZ#AjACmuBr(ysiyoK{af&Zije$!fpHDx57Fa;<(t=yAHoA*n*MOp zJuEOr8{pemD@5P@rdy?eb70|N?HvlrKBS){@%_0sTm&;FX#*@JF2=}`jd}-LJ6$(B zoOYw7r2k$4{a-zjMIioO=YkFObhBsJcMVOy<9Q;|S&v6_yM#X^%lW0LOqb*IaWT*l z@JL7hPuam*Z9T&a z8blf8_?lSlc|9D;i?y%u;zR7(yN>VQWu46keZE zRfVpff@%x2Rbtf7S>s2#T9%-y_<{Ntp)PDZsKfpO`p>54 z2I;3u(h+JrEI^yJWvu)hWF5SLj-ubA)Ods!)%Mpxd3H>fDxhCd<8j?ld{?b>!^nG& z{(w?C{(Sv}=9KZBuA_p6j?-_D;C~24w4Z9n>#@rt_S)8YJMEgJ?~Sl)mh0>c>8U^u z>8YMo3{mAFL85LvKEBJb+{HrmHS%2G0G&4v+VaXz^*Jnimv#xQdsu&GZ1gGredJoG zeG#s$r?hFbxfnbcj)s95+E@kMy-&X?%;Ok%+yLI!X%=``+l8$kpvq+_Nu6Ql`plr^ zystlHt<%Gyp~*A)M(JCsNbOvK50CG-we$6mFV3()v34N)@w`5nb!)GWE4x*tVa=zg zWO}7Ue^0dZ9R)&KtYn-9ye#=^2qAZWrgu~4GJU;LYRB5Wq@Sof`_|xKTblopep}w*Ez>Op(fJx6V3TnU&$H{8w&ZPw7k-JmiwW~&Zl>X>ml(KEo0w)q+iJ1`B8tF zvNr0GK3AM5n$!9QJxp#>NWU2R#{4AY-OD?yFNbsEoBG?8P%AdB(&Z?qyk5Ufy!neP zaG$Cr71ipx31Yu0Nx!hhbviwp`mMh78Ww`I&xjA{SJqgm%cbl&+7gMasMIfF<&$*a zl;zq#;p3-&OuEFXhN@g_&UyV~Y|18mECY8_)4R3$&e76u)VNKz1}7j;mq6V|W2N?V z;+Q@oS_y|p)nzJOsO=;c!oxRG?KOez6{h`GdxBM-k5yCoJNi6$lj8IPEv$YMToe!f z6x)t;XY}>aa7+xCj9plElg>X0LJ^sg9w*cuf-s4p#MDEalzK4J1El zUcu2;D=84vv;b@BYWSYXKGVDOQLMoa|3nseQUiAUzizQqt}@_I9>?qM1 z(r^*x8Ybm~G;paYYj2n=X{fl`%mZw6hF>KqkrnSXJIQS@92ChBKTb#HSc_pqw3JL$ zpQI1O@uAZEKl&ika5R?3AO0x(0U%(vyC_P+9Frh^3xH z*+m*?YB&~D(9wWA0%^?kfaU-dRa$OQu=s9<-*D)3P34fy7DyuZb~jXTRitD#>|MRw z05(@*>L3W%j7fIGU#wwr*Lj=*wFIop_>KamC${!C6f1e0;&*)nEeLqE6F8*;rKd7T20Jrkkg+@>k7ytZ1|?hW;FBQD8s(!&J#q(ya5? ziN)%sEMv6cq|}C7`%Fs6q)9`g151qVx&oxvV6iUR+s5X<>!Cihk+ z#-f(ZEmPGoGspt;LR7NU@E~_4%-@H_^T*obGa=@GY#vN^ zt};xEk1=uoPM&S}>e~7Y>TagtIqFuNBJt5%_Zr@bQJUef zn0<{bN8!sHrOv|N^YycOBaLRlE|z^l`#L{-&xQTqfZ-65Y1jXt>nvo%w-wT@tiC~4 zFGNFvXb6K``YdgDFX+hXFX(!5UYQ}$_#=iF74Qa3*`|g4`k3K;?hRNxT=lu&l+>_O zyH_HpYVMMd_b_8X9Nxew19)s#E!A_W?m-#TdE zgV$jQb^IX(S%>O=Gw69B?bX3>{H)ubs-x}kMtokH(bqSClj0X^t*&L-X1fkC&nqERa2YMu~r;9rKS8@nox zh2Ls!x^bd%;6`WAO4aGc+ojmsi=Q_JEtK5M*dXGN)qRmbN%ss(+1NgZ%S><5(w4@F z3V1A6)a$_@+8Dtg`ZL!U7%AFi7{7tGlQlNeKx{M^AvVJ2ueTOK-N^=)8fVbFJmasBSOyx;#ws)6MK+sNR7HowgV~CpF%bqJ^YREZk`!m}C9>qXo}L0t=TZ!k5Xlk1Ib6tR=g ztl$=w+kgXn-Bx1?YnN>LnvVWtG)Z*FM&niqTFT>1OnQlq{vu#oISgvbJJQ?;fHXDI z(ccC9O%8vMj<-TxcJxnU8g<-dd|!dQg9COLE##;+ejY8&pzn32X4uZ21K#c5F~SD* z5c}R_`ir99H_CA_(Dzn{v$BinmDC!O;%Dx=HRy#{{VZ}sRYecqdeDf2z*M09aM*pd zs0JmhAEkyiZ->ELagl`<0hgQ zQ~x;W3A+Crf0?#K(-CAVWGM|sxRIool2-q06w4uus!4*6VCth8ZRyuvjA|5Y#T;%# z@PhO-+ug>5RFZ!f>;6WPfaGAF)DQ~<84KE&PVl6LB~=++X?Trs4bp<^Oi=I5rz^3h zXc$HJ@mGuW(Nc-LhOP_dR#Z2~G#?_zP*WW}<223W3Q28af{*1bGmU5M=d06M^#siz zma)1PF82G0jzB*^Jt+Qe_Qs7lvBU)kC8 zcAT`BNpnp|Rvu@y(x9HEDtHJo*B>fmi0WfPhA75df2qdOg#IREmtDf7OwA$sBF6-u zFWgHp#{3)(-ru%s$0|QJ59Is;ms0dI-%I0$o0i3K=RSROCu(Rg?SL3iZk~K2%HOi` z5Z(h?Epy|phr*TS=WwhZn6#QYk2Ae1LDd)_&2H9^Zhnv-<~lRaUmfAnJll{-Z%!~F zxy}lf+fR*T`jbqrMJvl)UbmkGCa62piHjCDPE{=s_`SH$G=;kZ<}Wo@(&p)=eR2Gj zi=T$`l|PCdn{67eetB2(AY!6_cMBN3Hpetnp?ncotzz10Rck8QVuFA2sAAJbss}%FB32~rnSpA@`M<}PrGE}+_I|l5a zRGVoY!AZB4Ww)~SVg;XfcG0Xwrl;Z%7o0yjx;@J~sY<18ea&SOoBNIG46BUSe8lFh zFa@P`R58W`SzDZ!QYM>^u(lraEiCzE(*Cd2E#l=CKW|_=@ROHgKUC`9ap0 z^avQHn02N_su%<^{}^_L%exm=lPjA|ifARg1l1d<#$iHY&b%Y077BKHhpDZ!iB+si zKsw%?CioO=W~EBQt%QZPkNf{=TdHel>M2tni4ymjevuFh9Qd^JVrJ`NYDpX4Gv#7| zv%u$_;X1P4G*zYS21C!?oZ^#c*kRM1l4|>#&LCo+ad6}rh~;>5CewXxIKEBR8BpR?4m>eZd6a-!{^{0ZiE3R>`%2@bvGS5%$YffFXLw3G5DqXj=QjuOYE zU992{(`c5rSgqk`F5*cKOFuAo;|O+V<*%8hu%lf}lPT{z)620^HB0>w{&VGr%|jIQ z`cJ0el2pTrU8Vsv=mP&l_ON1~$xZ%CrjNMWUPYlsPemp}FQi($VnV9Ly;OXG%Nz}V zCOl6|CG%iaOg7@W!wC{FH)IUi4eZ}#sZP@@%8@P25ar=CuVnsY^GC>Z7+7hZ$xif8 zx1l8|<_DtT!dKJJl*RJS!V4`Y-HdPe1J<>*We>}KP~&I2A4`C#e4pvFYjVsSD(`CX zi-~_o^+lEdWY4=y9cWHF^Wzc%dZnf2``H_MOD@ZM!myAAXw4c`On~Q-6dYS!Y zEun^jm+JpF^OPQy-wzM$-%8b1o#Hd_jT(UdsOr4V2f#@ES~CKGQtD3Qxle zuQt~#|_c|cRZCDA=n@-4_= z?8g5+GCT~_U$FY#>NJ+O(eeiE%Q1I}mX5H(d1@RShnR72JjxmebwxVU;pUg(l)dnR za4kSj^Q^K#hQw=Aze9pmOns`ld$1>GkFk8s-piTdv5kF$Jz}E1iDZ}WW$tvX9Rtb6 zu?_c`b9vfD_3=n(&loe9;8!elhv_iMmn{piV0S};n%ddm6R!$-xGKx1^!X&SgQq`? zDKKO2sK-r3npOA?FIaHYD0i#c(RUA-5zq}aTw62relyMZoKLdc#EiDAZ!Bcqy}xVP z((>76xOIHP>es3Vu@aNoKpERj`|viNFux67z5Jg#LzF6z&7*`I`7O)U8FKi0`<$w$ znBm5U4`;DJ>I~)oj=C&mxnUh)1KBLWeXnu7t`+6j3=c%MGyeX1Nn4kgq5eD@*4x%? zGp~Uu(yT^W_eXIG;xFw7l2R?;s(Hsud%)H-nkP8)M=E@pZ~a9}Za{$ng;-vGjtUo` zoHAxx`YHGx2Hi!J`aBgb6wo3|2mxXjEoV5ifeOobUzNY5_Y}*CiZ+^$DtRFA56*wT z+}Vl*w^%4YK_lIpYovSdt;fn&`}g>RXZ}f*m;lZswa=;tQpN@t!Puzj7Cm$BHXoKQ zuu`{aB$Yg6E`ZlWGcX>G#zj`@HyvknUa+q9C(Lj}-q86;%Gzg!ZtoXX-%7m;`Tq^G z5D$9T(v4<)V17zM_CqO41HDLrX%yvsWX3M^8x>vvhb>#yzAb04gQef8@RGS7R>4mR zEd>tr%n|cU1%iTew;1kZQ<|7n^j=d-D+N85ZIC5C^f2i%%iV4egTsJyewyYns{7ik z=W)Tgy9|r2VemLqFd=mb{dn5kOp^YjLX`zWpKafagt72BLn?4&1GlS;=YD zC+xr@2`(xcV+z-arivJY14g<(&7VqA469HX9%jyF=B{OP471r+&CKs_Ral}G@cJw6 zVwtC)>Smg3X>myxaJDGi&s7}Yz{-v09IT4vY6tz5V2Rf<1Jmxq*@uU4jzdi-$mgcW`>EXJE}V zKg5>xvOG@D7%j>;sV|i}EK{PDI4Q-au*PE3oh;>bd`s7G7i8m##rRZKIMF=d8Uc4g z>-DQCmQ8J9#$w!P`HI(R!V0IDA;EUAKyQPKOw5K=Wy=k=|KcL|NjzZKZa8r>joquf zLn!%nCERw6os_fcd(8QS2|N=Q(~t8Mk;xD|xA6O}Wyqc%Is_ z_4m%`r}rq!E*=Oh4eq*~AG$sf+;0G@TyKJIF)F?-%WGvd@{?8xYgiIJh!VzHSez1` z7{A8fxQ;oVE(yDReETk;>kSeWdCkxzov*bR`IZr{r{W>zU$B(+nf4L%kv$~n{0j3d z?V{QG6&7Sc+1mLTUeCyCLWVSI9<=-rCz+}7g4rqY;XdXYdAx;{TGd|K^}eNt0=8*G zNc3%`#>?i-P_SQBiLwm=LtlomU*L^wNG)zaw9FFAyF71Q=_7C`#l|!Tr*XTX?%I3b z&Jh{Lm47vc6g!lzPsTCQ>!CGzkI9ln$XkDpr zr}+h#^(xGZ*&A*NnijG?x0}Y3yvhuAS}@ZxP`qa(0o!Xn z$#NE{)sVU@Y0*-E@|Eg&68j_9w2U%-v7F@T^bQVH!yi8{&r~m3^++A4? zqL>cW1@SS6EQUmb`^)IF?pCX$L zNVg5K?&MC5jeaYxtsHLM8;8t#F?rTf6%PhppAC1+Zhv>hO7=hx$dl|->j~CV3guns zZZvcGtnbrDldQW`N<{Rn9mX3G#&;={_n^YPRQZh+=ELf?>FdN6cc0fSdvEoQdH$fT z8EI!*Gu6@v8W^%xDtH)c{5W|2SmF7z#JZOaOt4kaUq7ZT5ew*kminRfJk`Erl_kvQpMymX&EAS5!=1T+KN@ySap=N*xIO2LJt@}k!ptUtIQTHwN6#Q z->_!5ty?s^@)NGO%edP%pJkk|rt@_)iPhX~%VRChTLUbwuPuQp%dLZ>rOB*xr45-j zS6MH`Aupk8ofRL_1k^8JrG?E}TsP$KO6%*~5izxG`gu(J1H1s!1W`RT2$D$FQTin(*4=3sdlej3Yj%9qGrsg>d=I}W@5^j&=O;(wDd}Xax zN^`=a@;8RifvEVoRP+U~VA-jy#q&H?`a_zXRgY|wLfwAgYpj@6J#1}CIVHB9IByNJ zi3BC_k5cWqG@K^+c$6~2_O|#|9z!~_wA=X$veHABtuLsgJY;OPol_`_FxKM~*kFa$ zCp$x9rJN_NEkv6qSn2k*&8g&w6^CUkml@L56kENL2P|*PxJFgNYB?aKpqG#`S{F54 zq#RDMvA3RW;bqw^Y&36psAp7E@{mvu{N{!NNaia3m;5hy^6jB))af09J9l!|5Vosi zd#E#hjUhOXC$|dSg5M?~xV0xIgpl1e>2k=0-?JeYh?2euW#YFk%#=)de z2&aUkqEIvZJ{W3>--#h)xJ?=x!i1CV3E@1HG%|#sj-){$2%bqjLpWw9bqK*Io8$>$ zttHt)^KcSOGK7@)Z5zUyO==NBR@ zPW;{(gheBMaFFkM1A~L{+b=i>zdeJw`0X6z+pRY^5Wkth0r+hfyaT_@g8lHD6zq$% z!|K0+w?n#U4Bm#{FN3}Cdnni)zaIs$8r1Iu@z&Mbg5B_28N^$U>ddeI{=akj@`TE$ z^RIl$DH);hxMI9n=zjcaLgSEjI7Jb{?2|8r#^Co{2sB9kGITF~4~9U43$iHwqvSlr>HfKo}(+q61-*)d-gKd0_@;;a$dS3hbSlwV-d-a0lErR!g zb9t}9`B!;F9NC{n&(DRe5d~+`N?@hd+MvN7YJ;sQDjqJg_uCFoyJ5DkfzE7eKe5G@ z!dt+0fK@JPWBMm11WZ;bp_Jy=XcBttfU`+g>^*RqMOTnivTcx57V{q7qCGurFmXUd zJ=JDmPGkBbto@(Xp>(;AZDw4|*qEsOA~y{ayq2>bs?M?1DpX6>WCh3I8trf>vfxR4 zo;>em{8DmVupWSa)44QQ@1o*gV7XtbGFa?HTOI|6+vf4O_55NR?tZ-6HY^T4u|6$!S{oC8a6vW)D@ z&kIVgVin1}s4=Y-w(JP|tYZb|tqWLfADe^CtFR7XIk($XyrWmB{9@W@sO>t~=Hd$U zI5mAAvbE&O?EG2$+P$Z>anP>$)GQ|+*CX8Xr0tbBB(%(R+TK7ik)gJF_D!~}n7xr; z%jL{$0Neb$ZJ@N4a@{s1Qs6DN{jPv-Y;JEG&c;h@I2*qXmTYTa886#ZbonJ4#qm22 zS88q`j)o4J|^nubxRYJ`p)pKvaC7zyr)ApdGd;`O6VkPUpX0HtvHyD=o z+n}@ChC9RfzuV!FW5dmGhit3kVy5t$%EgNY4$WD?F`Hhsd!;)#nJ)ky*GXR-1JzVJ zR>cP=;Zl#VUhz77x0!Wp-alR{WVuc36JuM2=C=tJup0cg z^1Y?m!KrYNwjWUL1%~f4>4O$X-jHrT4W%`uuFX7;Fs?7$;gi>gII?H1r?kt?fkIN) zPpMU+LS3Z7N0hon08T{!A5-e~Oh_;7ZT3@=1a0&#QT#)sxE29g0N_$q_iFY>lPr9C zQSk>U^_@)IB$M7OOSpl?f5KAV&xCougB|95cz&+1XR!l4ye@j~eYp@PpYgI?d`s~9 ze3=*B=5^ufRoP1NMi&xCPLx|Sxu-pe8njunM8(6nEY#JT3JzzM!0l#$y(zASwI3<# zVMnOcZFUbbQXYIA9aSF5w6p9t>>3<_GBXv#`r0Q+5>(l5@KFY1l*(N$Ema$Ogd96!=#h5-xXw;ciBIazGTu^1&p+J+o7L6#>(&K72Da{ zvGQ%18d{KNpMk5WS@{FrJlZi|?ghod7p^-+uV1l>xprjZIO$HLd8Kl$L=R7}Kg|Qt z8+T^n7PTFjz;2VQVdoz7Hen4mKKNn`j!UQ_J=C%C|9KGtR$xa6SUszGNe0qNOp=a1 zWQWc7YdD?raVx~QKSWxcWaW_y(y7{ynZj;|KX|3FXc3*J?6f|mqeYGT= zVdV^6*S2&f(tF$G4$vCvoh~}MkdF%&>=$_#yF|w-cbGcgv+@-jn;Q96TDHVKj>ona z=gV4F^p2cGLte5YV&(_t>fyD}pX*(4IhMQG6WH$i++2HLW z0Ou$$7662%dp~36?pLVG4$F{AqTyLQ0K+)RH@r{JMm8qqm+T7!wNAVIZvY+lz$ZbUJM5dkV%4UBpcAubSG4gRP zy(J_ePm2>-*KLj%%CI=FS0uC2H=P}+(6no$X%pJL*3FHT9UbY^y1VaH(JGBq1$@|_ zI`c8pS$@Eg&K9(nr@h6f){-&v0kl(jqix|}y8MPTJe8t|3m6+uM zR%dZEr4@Y~W1=P8>9mUvmeI%Iq#rZo`C_mZ>|mA*sw8?}k*J?b&JuHP$%;2Rv+1vQ z+&FNR-|oOwQNtXYlu$c|+1uR?u(vjxz3rCh?R-Zz(oWvU+Y;LSgSREL{|9eN$hgYe zra8ddwEy633A&r`HkfB`C; zoAv+ZZ3$34-<-F>zViRd+Y%iA!P{PTdf=GOK9 zW^M`H{=wW5vj2m*B?SJ#+!DGoFt^FDhdLHUOFeGJ*b;jFgRv#_iZHgAOfa_I|H;@A zkoNN5j4h$hjf^ef_J1(8guee^Yzh5tU~KI&v4!`)8DmSh;|9iN$^>H@a5Ki1fGcMH z&DauPJoqQY$I>V z*l<1gHO9s-mU&9>#Mi+SbB^1=M4yc?(NX_qZTI{)Ya4wt)|U4#)|P)$)^_iIv$p&G ze^}d?o3ggC*R!^9{}*e6lKm#EZT!ty+k~64wuv`oZ4dmLwJpzt+wUaW{l2@Yf?BT3 z{9cJr|MrL6_0g#W$Dd8%=K^YfOg!7d&xI`CikDQN$SQ%qf&w|!YQJ(N z(C#gnJrVa$?N5oy?r`M?sr?!8><>RrM;1x(+%fz-g9YwYq_J|PQ^g9JWIe+wPouBe z2Dc0Zx`yjKMC~uQUyx||`^)Hl z4^#1G-o*Kuoomz+pm5*DD|T}t2~syFv`0}1k1*+0r-$Wy=#8VP)8*1=mQx+>b~ZwB zc2hpCX23Z}s-%-7cd1>7r(J zxaMNYpDCXAhM&t=RjwVeh^I4k)Uekz64$8M-CwhjZ-!B2RP1m^D=2P&^Rfib*wPw1 zWR2OLS}e&Aye;?$OPT8fC>8D~@@xX|V{eN%0AGNI8gmVF`l$M2Z#H~VUiCDkwIL5o z!K+3(kzfrMeZOnhvxe_w98f#E1IQXN+8Km1ZDF1hhu}OX4l>JFaYwt8`j&XMf-aY2 zG2C)$v&eG_BYhnneg(^4ZWrRyUI0&rf#ob;?{m|DmpmJzS>7#~qGbiHK+Uw$h9T}j zpj4%&DQk1i3msHc0#`wLy3;+YJS$QCSG%bGGPPO52a->RpI5P(*&ZQ0y@P>%4-fPT zLgGEQ(pzKP2+pg1$Z2GbKfQPJK3B8ATu%V0rWwRz8T8$2#tFn{F~g}YqGsw*6;?}c+kGWg0@>afmVbSfoRnAuM zo252~_)IfeI^x***PZFOXs2dpmS8>)!DHLtdq9W>m6ZKwRyDp=i?6c)R3WvMuPu#I z$S=c*Wst84-Q)1tMA1f8XmZ%7!Q@DhX!lYffn+$;xIxx8lOwx{r5YS=HsFlsTbA>+ zvn37q&a)Bnen*EO{$|QflB?0_K4)82^ou8kCU@|)7CmoalE;B}+rZx~WGYdAD=R(g z!Mb^muN#7ov3!=!?{}v2i+CD!$f8;Bozucnx;YwHO1=+LQhP{AR|P9!woBN~vUT#U zEJY6@w0qcP*iz&L9ZLp=5qgtp`?}1OSKulKad(D^yMxuFd$Dd*UaXrUL%8Nn%60;E z>OVX}rClsA+|iEiamy>=sWjJ>hC9-IRxy{iSVfMb16AMS!#9%6+~=3Z*NT@);e6ud)?cL>0e2w)#I=E`$mskPbb5jwlf zwVZeOE;SAn)ygB)-lN750I-}@t}_IEB<@vvpBhK;I%&qfRCtgvH)z006wP1Liq~ba9{fbaNNOAJaJ_rbdGXU zj@kigoFN)iM;g>pShx zJr${T1j%>>;7kO7AovOa_#py7{QD~c(BOI(u{oo&tk7vTu9bxza1GaF)~u7$k*(K@ zuLMdzF@vwDu}U<$9O>l*HEsc5OO89dGV0jDKfq82ED^1Cg&t+n}A%LZeEf-51UTH z2VU46^7`79EbpA0%xYiG^5D*%fS=c>tFtGw1$R29UDFMcSVrfVYkwAQJzA4>G+Khb zva1+qw6mCN2gSu-2@S0N4;dP7w+f+1qO*$52Dpoona2jPld}bQeYS_=RAJ%-I)>k+4}Uny{p=zbnYDxy$a zCXl*BfqO;IZ+H@^_F$Gr0r%d(I1b{>k-!`efu6mbv8*<>Ni+7qCA)>cj^A0$Pj(+G z`7owjmkT^CI=)_%dk7wKM?7}kG@1en z+>0byawY2l7N&XkMY@HURpDA}=k&r*VF5_*SN<~r?ZO?fZ4%VF^Z zD4~7 z;%dun3fv3S|8C!A9O%G3S!GkNV`~Tbr`XsB!8=#Pu==1&WU)?V8R_;%ImfI98tGKk z%sx<}6;HYH6>R?JZuq=LX_``1D*>I)q4HBQWU(*Zg09U7`Sf{%Qc*M?hugPKxVea^ zX--u-6tP-0M3&kGvWoKVkY83HGf$OCG+8Q|ES7cbjc?sH-lQc}*+kJYG5p!^@U5uI zDWDai!7@Jl`S9?qS(V$4kf?|}sOBaIO&eA}8g=S^cE?iNJQ*a-2J!r8_&JMmE{NyH?fv0#uc3jLTvgF*{%syn-9iD(G#}F z&3Zu$xQh?yWmRv1o7}V9yAn9maFCC{oY4x>r0CSoPhGV_niO+Ddee_Ht#n|e-SvMj z4EPU6X*w`%0BVkszTN_Ga+A9UtG!pznVpy=x26MY?4R?z1R5sUATq!rjCFH^30MdZ znvN`0&gyWzQlaEwin5VisLk;hQk77VY87rsxjm z6RhCM^jce)tNys>Afoux{Y#%@a94aUH7sFVaHL(ff4w|t|fV2RxNn* z1BzB<_q#JFtBDKYG`F+H3~szmcVVOK%SwmYJF>h7ovpYnZ;j}(ALWeUT?!#|=O&Mt zciEq%Y8A-Bli@nVa|UoG%PDhB_Cz4U02X+b%N-Wp^FY?v(Sf|B4%c_l$Rto&1Hr7V z>;f5bDQC8r@QUz+2eJG|-Da|PaD9P90+bWvg;&|2pf9wnuRGl=Z?r3h+8*-k6Ic#q zrD}(soqOHc(pX5tWym} zhl;H@>ETRVGn`7NWKO+7&Mf~nO#Tr}yTh4A?c(LH(d$v@7V366&+=1&W+at5(JSq) z;L_>5x5E>=i%Q*mtQ^0iEtQONJ*a?Iw$#r-b?>@Vbk9YPxH4Qbib^|*=IeR$l&ug{ zUpnCA-eH=1sI)6@aHFWX^-s@9G4N;BvR1~*JBFlUMd~HLFI9>k*&y*^XXWkH%<`jJ}Mo=i)y=qcTc^|fq0;6 zHa!NLUSMmH>&qDBbTCOK?&PG5kn4m(nFi#?vC1c02%>({g=5(LOb*G})J5lgTVj*i z*n4g5EpHZ+7*AJ>-e}P6Q&(GhD#LzEKqoL6%#oZnFXDx&|IEZGrOfrSgk-B%GDYn# zT}kYG3G`ldaakGk#`es&MY{)>3=T={-CoF<^G-lxQT?6!BT+Po$xAs3evZO{7iA?? zkB~2jqRFhLjUtUwI`Y%+yl-7?Mb}g}Nfp4!12fl|`zSETsI=PxsgD)!uuH4^6sP#WK@RBn?UGMcmR{-%ejPgvnGM3Z$ z28MV*%;w?61GC(eH4%~+=ZE3?v*^lXQGbdfneDgP)984q{JE(A2ut0R3Bpd}guTib z_lu(0tSTX^70db_Uw_w!1OiLZVJue89Qi)hXz;Xvw4kImv$$N*;Kr_aWSRV>Xgik; zgbtRn<_bJ2B6!TBE5#g-t9~vgB_%p5@-poouGUDZz&EC1y7Cn2aHp0q@{iJ$`2tWC z0X&AQ3h<$)ECkB5V@m`YWGTnpEvasDRtD&&b%s}4NN`iiiE(3zKhBDmLgxK)MwSs( ztdS~D(3J}B9}@kQ=#JrO6N>9HxeNaG46EVXw0#@*DV`}o+r^3F@YSPJnZ$)64O^jikCEn+T+Lewb$gxJ zt(O+CnwTcwQx-S))N^5>mLSv)69|>Cz+LWc?5%;$?pGP${bFM&W%C9@SgW1o%%Xlp zp8f*3kj=Xjz^jfe4+-G&xDW%tx}W6?mi@lB6<5yojE0kHU>WxX$##FromA)D$EDd4 z&m*Eu8KT+k?b-7Co%gcrhW(`GE>1~k6GZxSpG|P4|V+7yAOQym_1rRm#}$L zc~>t*x`HxUufRA%_&9J;z0=*C+MV-a@8%LJK|;l5@*>mr;-$`U?iPy+$z2mN zJF$i*AbJk`#aktn;TTFq#m>J(@iI2=F)ptrL->HYwwLf~J!>~n=AYhABQj}GmB&r{ zw|G7jEmm-9A_37e&cF>!Q(UTvP^Ed#am;r{Fh}+Ur^|}CTHgSIR2mYLS$U&7%zZq? zYwCNRG-}nvcN$6%jU!BkRjlv>PY3GO%!g#8Im0u<&{v=%RWxM_9&smeN0{p+P=W;d zp1^W7uBGL89E#xaDl2@!>7`CO-#cJpV=`M(vjx!GAUi|hEEhaLu4JVt=-6?Oa8=Q) zWrcHHeQ2P~S0xeT&RBS|(r%>;_EU5l6wjkyaIcc2*J-z!gRb&Vkrzs{j%f>=aA5KK zzCqgx-gZ5Af(6NgVB;H`DmpsA~<{Ky} zAZ`-G&GVvZB`Ym+x20okWdnwXBMg`*6D3uws+Ysg3c5R^0k2{m<~1g6R<(!kYx+BWG7&wyS$1vp`OXOPu9)Fl_^lZh1)k zNdvnhH9K+Sl_}=!J6Dgz>8R2Y(%`fTOMJ7nnV|j*<5W zkwf(JHdU;&kNyWoz0S{+i=jNfq-HNwtmYN}7Cwt456(O9u!`60mxQ}%!9Z^b!dn%w z+{H9}M%+gg8$|clJDBzpo6NgZv5^;DWww1Ks!sCskdT6+^cA~>OB%4Q3^;DLGB8?! z!}~1%jJ+urM5{4_{o$E@Kn3@L5}f1q!u)H)&=09#9H-CUZ1=6Uo?Z%RKNU5;a&_yFW2$uVjrM}^UBBGN$m2xg|Nu!{*SI5`NG1BgrPe9I{B)8?y z$4OhupL5!{#q(E?-sI2zy=Dp&?8;1GP}H0t?H8hpnc*(#@ToX%<*Ts?4J`0%=E*6v zb)r*8xYMtxU=NTJ5-33eJ%jt`Nqa_A@rJ9Mg7D+%O?`_z+i`&uX&Xdg=Wro3+!w@i zcK8{3?MvdhXZRUv?aSi1Px$$}Em{}mRCKFd%;|g5#_;EyoN)13k-lI;ii zRyWI?wbCGyYr1He7w+jiX^dNrV0xMQSG1Ru&0}=8f zX=TxDcDUIuOzU-xru*LWlt)XylD2~=42BDF+0%tA+;7mzmxqn+s^XQiJ*0p7jkMXK z^0VQ}zmv8ne=g5(2nzi{+CHLuQMmjvY5Vi%l=hAkmbb($Siluz3vx7L1$IbB|1wiT ze7q0&S!?!T6X+5Te^K&%=lK}d$CGxr=y{dsIWdZ~cZ=sW;b)2E?*{jU9r8WShY}T} z9mC5jZDMgHMw50tf6fkLnixacN&NZ1aSybIpF6oRGf_#}0@27HZiGvnrt{~>^b+Gp zJCi@>>q)|SZ4t_gX$I8K=C@z8q64~X*Au#hKv`9Mu*l5Ecek)YpXwGHmQyoaZ-`Fe7u$_*kUe>)%~<0E1@wv&WLZHunlxw=N3q-N90_XFK0%etyu9Zb{yUJ zxN!Wx<`F|sV})ten&rQUF9DiOaPb?06&?}2wz)1&fxu(Jt=fj!3xU0a0qw5iEY9)X z!_l}=N=QcSCwWgrD>Lrv;DXbo4RRL5H$CQhEYeVWO>zr|B80os(T=6qX({Jaq#6{m zoxIK2ijMI1fuv?e(V0k*;qQoqaAV^&_K~NcS&w_4!j)2lI0W9`1vJ9qN;J#kWF#Ma z&z@G6=8U2C&jL%h&$`<2D2A4ZVqn>3E^jaNJ_gTOE~@q72FUOLHrlbB^8pcc039a= zK&pl-@E)(-C0?W|QqjQ`oqSl0`wfY0xad|v0!_>u=jRfegtxTLB?QGpC$4#s2Ldo& zUN6X=g_~XkU{VBtJg^4=;5_5!tBW9fx?nh6wBd^pDO(SVHq#?*+*EZ$0A@x29;!Od z0XY_Tco;8L)eC?l0`O7QH!@6C1zJT*DtO2HyaK+i(n)(Rl~>#Ipfw!qjAsQu$^j@- z6Irg`-4ucQPB5yg0@{OI&6>z+#=DzQ%UUnC*ITLTq8KAPGDb(L`b_|OMgX0t>WTpL zi2#tS^%E{g^>sHuWpce)ak!vP7phXa_&jnV4Z5=W427=j5ggzLobhgnmby_@qNp)E zQX`wHQUMTbg=;8FMMnXuYAUMbMXGhDs+IyUCIaX|Rc!@ed<4*wsQtplrBcOlNFa`c0;-o z*JApjzt0{m4TsOG=xb-BFF3Hy;C}GjR`g6Aq$h#=^`PJ1X>CTJV2Nd%y21eh5jt zhi%xK9%NZPI%cvi`&+!sZ2dZ>(E}5FOZZJwF$F$&s^nA5bp9`*0vG!H5OW^)EnwxP zuu=a0uurX!aLM{yUwg_P=7#}lz_TD3|7yojMbN)lLE9en^^089FQg@y4ABbjIbA6d*OMFUFmiYeUS4LIM^4-rG7Wzi;v0@Q! zGY2EdbA8PPRVMP0o8qP(f2~9%l|G@!ON6)ST%U*6>W8~>9`*Hs-S;713LmdO0|rMq zdXw0}B3~9ew$P^w7hpmy=zPeRs-O>6`fin^$y-wWmC#Kr!7@4bw66(nNu7)7^Tt!C zvD`maU^9shzvjb*PE*6ElDI`~= z57RE0>x<>}`AI!-+Tv9IJFIVm-@xH*;5p>`RZ+GVomE@>hbeiN?*fv*Qt{XRc@Wd) z`cioRHOTLj=U>O$&p^Uk{~BImpxV7Y50shY+RKN@nB>poHU7uZF!N^1k@D=cuTJzM zFhDMrsKlA%+V4Z67SToZK_5iahgs?*e-FF~e>{~O_G4o#clqJy_qlH+(niF5!Pmzl zEO!HfZqQ1HGnoHPF~!+oy5PA^%rlM;Fegk0%*MB$oUgfIF`eXj~IL*xQ;V$I4zP*e5+E?)} zR&BLEm!|#ZD^*C3!J6;;nLsdM%uNh(bX*+g4+YL4%H8Z61A3kIE#V_P&T@b7-NCWQ z;BTWV&a$8Q1f`z9WmH(8?a%t^S;}D$ydcpJ*776^O!JRrZO{8o5|D#$?Ng+k>$?SM z>6ZB3QIuu)U6dl-0xBNwrD|_olUv~wk?PR*>_9scm`w9fxa|DMVe13`VDbejhD&6Uy3UA0mKEy(T$n5)0 z{+76%D*Fpxbu{aL#n+klyy#|2eK9_<^Ei*6_Hm&gv5Zp7uo$nyo}k9RC&4j`Dx4 zfV*q%Azuk6MQcv)A>8K`B;*2wKP*5jr`-2^ZLkm?@Sj8~SfF71djTxvc$Ev9T(n(*zfi%Vi~MOEljEFviP2?2zn6ChqYLQPhgGidt7+U^ z|1t^JPaSlEl0Yqk&|p69-+bE#=VE+X{J#(-Jl7{O)F-||shfSEA*u=*uI32gB9^O5 zL&>pxm8Bl@P2=l1iRPT~&lanE4Xc{%+s?3`w79k$p5!x0qR%(_t#FXTwng~j(u)NZbC zA6#+7jGpva#6Xpl+QFZQG^aP8l0%Z$>QBp0A`_@j8Swxs^A8>hC>2L^k3N9r-K&03m^= zeV6!Dwo%}Mub%BaZlA!=mgi?O<|IF{9%J=$LKv7ie8^t@` z%R9!!X3yd);LD;X6-D3jSd6rf?YG=AksV06C57gyZVCN=ZCwX+RMi&Eo0&HyWlB0B zWfD^8nE=TvGxKIb69gquP((^X5u`|hfT$orh|)<2UZfFHu={U}-m73Spa^(nEUTBV-E!%R-+tfv$U{I4bBN8IbfX8Ch+r)I~2G zVTP8{O}%&&{X(?2la#>H+Dc!+sYq3ABsij760U5Jn_6v$Bd3#uo5siB48u=ABe2XV zflJmSP0vwdfn$abTJM+4Ghnw zbjeBeDN-UGYA;!7TxThQY%Qd4Mm?mldhi4^mP$AU_m;lbTaI@c)+rlAw@4%CBe|F- zPudT1TW09XvHY7Hky1Kq-CsJPhl|jjg&`_vBeiEe2TJep5`Tx8H&lRzkPh2_3@}}v zfKe`(BIU4|!=)0?)H1^;-qhE$dy14oD~4nG7EO>W%rh2GGB;%BjF38Y>O{perA($4 zNgwj+{eovCl=4rMQ0@2)?VcrN(d02wH0w1*!t(r<>hG80D#qU@Qszj^e#@y6m?yrY z@)2kstHwkP6EH4PiF6o_K@z4*aF5NB;2!&d&Q6q&Hac5E+USo|zTSXDsQJ#b)5OqN41r<5EN8|nk)15 zILB?5j*5Hnp8Y~uQ`AY6m#!XySf3_QNu6X~dLb59-F2}BN_1RJ2NN z&z2kH;XF}vMUq@VKYb%*vzN{CXfDxIJVp7FRj10&fV#pe&lNe+8-h+wh1!cU238^33WaR-oKV>=Or^Z$@@Bq|TOo=*R6+hjyu3$`+WM?@ za%a}vDevU&!Wps*b?8a5k>#bzjV5C$>Vj#AB?encNNA^TYB5k+YY@F-M$?9kKphwHr;{BR|T%eMENZ zP#Ax#zdDC47${FhVLas?lA(Z5AiK;41IXUEkVF7iE*+gEKfqc)F8377i7O}a;PBqt zsD13}Xb7EfC3$NSOFku~f$4dr{5sn|PWI>oCraH_)BqRBi%mib<@eSn2d0}a-S5(c z9TMcszLl)3Y@t$wQ_xoBM?Oz2XiF;<-ixVnl3B#e?dr{YH=O~9OD`tl8|jo)qCw^W z{4jb5UM8QGG?sT&g1dc6jgrdtEs(uPo1`Zyl*LqXL_tbIks3`$M=1kYWVw7n7t(6N zgb`0n95V6oaU-bwRV9kGSRxO$7;wD`5Kskro(7Mgwatd7N{y&!kgYl~4S#P-#Urz$ zsByMpffGmmftjo1Kan>{f+%-q%WCA|h{=AThf3O2X*_#soBTCDYFpb=?lcuP@eTO{ zRI{`4U2?F%tefP^M(C{+K9QIOn(mcyGG4X4x0)F#KvSvY5%o#l#*-EU?kL+hk7WQ^ z7X)$SMS=)6G;1~;D^Nh*v`2nX52u{Nu0qJ=02V9>KS(Zm#i#@`?_qg_Squg<;zT}@ zb1CaH*=tc&KV6FKsrBqXjNI*AO#oX>M!J$Y{Ln;Ki5ri?>7k=n^C2N2{bNLW$eIN*{y@KttwQh6KKI#GL8r9ln6H0 zstiSBd72Dr-wSeu;HP5|Y65L;kgY7UQT|jfm9+bgfLkD`5u&AnBSatME2|ll7o{F%wU=Z+8hu`!jr{>Od+v;A z5C#1zkEX_%AY~n#sjNYMFdOh=rP{cA9K2*M*&yf!L=m0jnizPz9Eu2Hl?4p6>5 zr;s_8)QwbaR(i1cgB6f|ftAlX5GuRjAXoiB_OQ3A@GhDhY7gQF6F) z5LE(-M%Wq($kh{X!P`$O`P_9^SeMd=7R`wgDd|iC(&wittMm|qzZk3sUGED@1Lk46 zGL}t#Ub!xcb|`raXL#8{bZq8aWokg>-HNjD@rf5`MQ{CH+Pp(rO+{%+yTH72v-qSS zjA9oPWfR`T5sEqR9uWrK#K)2Jf@!;lTzkVUR4^>qK$wd>daE4& z>bfb-X7x*zUyO!uLlES1x9V?MIL=%-W{pznFa#Z(%#rO4SO8>xS;qzp9SF2k_*3ei zNW?7_5I1mRHja+X)j(XkTFKE1Pe6-CYQZwzP)?e-oc!zR7vw)9AA=IG{0QH|hOpsp zE6aKI;g)-p1e)GojiT{PjbgptRdDE>K(*6UNGfboel>9s=Dl4xjSpD)+h`Nnwa*j?+C76@C-ph@{BdQ5-tz2YfBm7n@oGMXQz>9OPb%F_ z7R1N+pAK1+e^lCV=RxT%8ywH`wmwHQ>?$FHTH8!zU6lcB^;u7qGjBHxY~4v!5wuG(aMo(dn4GpO;hWM&J>kbgOg z3d`hNs{NPp79>}sSiTm<{Cy4gW(7KRyHSiW1RYA{Lw+(a^)t};i>y3V?Wm(yo{;+> z3(_6V#-}M6NI;q|2lIUm&$X!$!faNXC`Sr(U$Oiww7T$~xhBXD(|M=dj~dq+jYxt~ z%OawjTTq9Wg|OirchQ_2H@J~LRacXSYHIu@@Y5*n*^ zu^8Y5I1xB(@V<8sAbbG`7f_+B{>EUq58@3AQOf>qCwZ=+<7cTA9Sv2W0?-t;o%PwO zz&lfYM)?)OK8d~Vz)QfpjL*>`D!1dyhXiekfD6>her6;;u=;B-}S!KtL;1#&d5sA@mRUdGzl^d9OgJrtlC z`&xUl`+BQ2CgEke_`aIQ`u0^p@A3*|JuO2RufK{^kXLEOA$2+}>V_K#dU*|LKV>+r zZkUcH{YTVqsoZZBCAn>Ur@cbmeuZTFTIX z8>Ker;n{buvD$jDyC+8KoCD;PhfQ9sf(w1V^ zsFM+7$C=;$h}xR%TBFVi9FzMsMdVT(Y0rBwn-X_AZRj<&2jA-|q@~MFNjvH5 z=CX-4&r|cnDm1l@vQ{d4_}}5D-YSbMZh?AMC%nP3)~Ze{mT#o~Y-+jsq7JUSxkcjkiya5Vrd#tjTQMN_C*XYu)E+ zIE}AXzvjwN<=3%=6uqHh^W02Z^5iHwvQ#Z*b2q9_o8Xhn9W7_FK1cpKdfV8@t?Duz zTyMvioJlu6R3NWA*R|`s= z%>KT5K@h8fdMg!5>Z^aZljq2vu6}v@S`2#}WUXPjKPbDA@=~UJOh1fMZIm}vmdSNq zK0vh#a3;oV_UB(@lz&Y+5|~m}|B>3!2p&!xtHFlxsrnzi2f@RU>ucc06S_c}tw{ zvwc6SP!+0W-dWC>I`-lvbt;#NDv3#lf8|#d2ekJobFK4rw(@tikV{1+Hg|tR)^g_s z_Sto{J(QwoON2Xu3-Z9GhLjM3a z?g1;AAh4O)?h+kDp?q!CN7)eBJ=-FtgZMNQAyJj3cXpou!DnSxcULaqy6_o%Dzh0~ z-RBJ`Su5)4US=}D8;!c`0TD9w$aiO2j?bT3it4qOr{b#;WBKkLI`MNLf{ou4UkPcs zu#+`9GvbbG#(8`JYw&M!EpC36o$K$8=8iJ|#aIxP40OZloh1L|SlnL=+_=ASRrDLQ#}KzpljH8YBSBVvGR00@`y*e&l$L@Y=4B8?l~CoHFJ)0yG{#aD2Af7ktno@<-G z2<&LN5w4M5lIK!-{m0=3a@cII?%tdC9MY?_@VVb&Go856a&Z zJOan(VDM)gcUPjUnjCLJ6vp-p)F#R@%~8ZY{=plJzS4-*t8T6?yb%` zb1QC>ufNf;6f&swqcNe`d>FI1SIv5(!Z8}V8ls=$TNQG8VUz>+TA8N+_$FIhQ~B-=L;2dO#y+WL5ht*zoes z8~f2C2P}~sill?ji8~%mc=Xp)X!ERuKvLBaO9Y+&#T}0Ge+s+{zq*T&Uiz_x z$AD}W+R`zl4Hb0o^kcIkJ=66o)^5QnGg(JdC!;1JJBK746Z zAC~eihkY4Yc7GeQRk(2f1#~ojyKQz>yEVB&Anz6TL=Lt3Zsukd9n~CpP8J< z9?bD{GKwhHJr}@8z-Xpdhd_k!506LGi+FHXVHBO# zQgUwh02Drhx_Q>>glKj#*n|1|8{Q@}%!ipLpW9;SqSaGGo?qO1ZeyR%!#}oIx)|=s zr;>~`=il+K`2Y0B2YT6zU5w@rLvIS`XmX3g%=eH-H5w3%{+_?z9a;524+Ity%lnk# zWV4DN_Oq$$V9!cR{ENd&vtgrnUP?BlqSNN)|1y@Mp8XQ#pNF#G{{~~Y5K&X$ft^YK zf43fSkl`M@WJ%1~&)rpEJ;r&q>q4BeONO0sbx?+Zb1)9YW9MVrQSB%3ewr?a6|r8$ zo^3i)vLr*BuOqnzxqoCACwjuUFj9T<<}N5##J8kfzr}W8%cppT>xC9npTs|28Gj$G z8{u}d9aB9ogor8xmLCJ&ESfUglWoqJ|4=Ck(O>4fP)BxVwkJbpNuM*kR5k=v@NRXW zd*^w!3aK^oIsBH)Q0Bqr+L|sVBe?R>Y$)eW+Zr53Un+?froQCaU=}?vjI+54Wfp5* z#7NrfXdFoSROEYS-tR$TAd4_W6e9+}fiI(DbR z0KV&i0qnrCf?L9`3%PlPg}mp1>TgGyUaC~lRFiiW6?|X{Q*jVcv}r!Hod$35fpIyYRR_rm9RNc4s>-$~CN?y#%cXc)4z zR%d2o8ayv?F39E6GYa9?vKpv+)XM}3-mtHpcp&0e%6%@IMb-)q4AsY0IuKff0p@68NO?@gWs zXGrD$zaIfaFBVuHZMb5~qxE`wID5?H-C`DV5rg3t#>ILo-gk5&7kGG(>ax91BU`95 z(U4M|iDi1caHV!ZUKl^axY&nXy`KnTzr_!hc4k%Gy@LgjYeYOm^E}?k?3o_kCqsp< z?0jFZLoYmB)8G4`k*io%@^$ZGvj=;hg4?U6z>D;@?uMXwTn0Rg^(^#4>7@s=KJG0L z%!B8NrT5?`e0OWjR*d!z5kxLkF{Dz6I?hgx^&;R}K;K(f6yo&8hto?=~h*^WMccDe-$r=`TlLGQWwo!OB`3OEvpRe*F7jcPqsYSdOe(8beO$NL0n%Ax~js&!^ z*+}3)5_Dskz6~2!=Jn$;_f=#VE35K8sT0QWUygzf>l*JUlL&vv`~3ImbaNxx8^7$~ z(q7OM@4%ZRG-+WUZSr=|iI6w36|vlOgIQpqTfA3v@F&%Mnc$|po!;3MwXu0DW~=vS z5EoM2illn>={E1~F!6p^qKRlJm3Hj&cD78KGpY1GSe+;S_fb6Ev){W-d=hO>rYdHM zqN4@DBU#A-@AVL&4}0!Y?+m^86#n%Y%uGTD+>)Iw+2G^eHMb1GZ)fYAJ03-(8hS3I1|5rk*Xw93QG^wo;QmR8G=^x(RQ+rBDDy? zGXG^uX+JzTntw+43=zI)ZJc?|;t8dF4MA)8?v%#XM{E6rF!)_;b4Lst(=uaXRtDVk z2@xzQR{Ktm%&Vz$!e9>LH8KecsKkxXlIxfpvo0r93*~B9%Z+Hh8SCrPHt5BLXr`QU z3(|blE~{1TIz1A?EQ^N?%N``>bVTHRPFg(mIvpEI@dMN1*!{AGwA~6iebI}USGvu_ z5e198RBuYn zpoOEff3xBF+H#%nGCLjD3Y&=iH}fubx}TP!gRiC7W|>C?@1;vL-5lC@+iW&tc=!pC z^8+?dop(1pg9PMG<-d>)zrt`04rFMSj7p!&@H_+{mb2nbT!+v;PMZ()EP8FCHd?IO zG_Z643!S9>5>Udnt)$aStgR44(4GW$xi*X!YvvCyJ;)xOs%_K@t65dGf=w`UhW07f z$eyt!0#)*}w0BIxIwV=C`WT*iE_X?t|S zMrK%{jk6dB8iKACWRv4He`jhK71N70&5gcRxk0;#Mkn z@TxUjeBt9@+HNYDl=v7G{Y%4cxK+cY;cW!<BS+@c=}lo9D;+@OCDCI_}n_nZ~)td|B(HYXm0SV%tHj2C)lZYUpmrz?u7W!KOm<00P#puh)0`3Z0p0Y)i;Ic^8Lak%yuZgE;{i5 zkbUybjL2D_`kLsA5o(Oi0FA!wQz{PJ-FR}VUer}>zCOXa8Z6Zv`_Ah3_8`Lei!BmZF1WLojPHkPnz`_dwo6v_Ojv2JT- z`LOr=aM!i*<3HBMnLC&8**hoTr@K$Uf0|Cf&$lPQ%X$oF{d#v)mz$#c?KZ0az3f+-qFRkr%eG(lb=8R%fbEaFV*B$S*sk4) z?fRYAZrmN)&A+fk`eBHInqu?&@kT-F`TvF}-k%d|#?`20v_(@eWva zQxjOBe%K=OKVUihSY4K;u!5Vyisi6En!*aa=#Q}-X@0!us2j4Qa<7TqZsmvFw*A9y z+wD#5w#yH@?YOJmwuj%c+p-^aJEAGB$fmdg!xhyOS66?d**KEF)&5&HGGw@fdy*Y0 zfOb%(9Z%RB*j4PPK1tWZBN}9HM#3&{E`Og>bZq29{$+vyb>xVoq{{b`?x(Ly{R)4h z;aPldh96{B)xidhsvq?~sBaEBhWroADYy0;s=u0KCjW^fL^Xo!@iZeQ39%MD@MksdoU*rM?c4e`Aepw@-&ZHJLYh!y9Q_a8 nPJ!tv{RWfqag6n=sf7LLHRhV^KaBhPn-*h5b>s}*M)H3Fo|bi; delta 60284 zcmaHT2Y6Lg)9zWjoRWTeZ%GItKsccW(rMC3Xi`H$?<9ywPjXH~R7xeogk0_ zK?n*eJwZW4N+PIqf{G#{+}Q`^`~CiVug|k5vdb#7X00{z&bv;2m6yw}^m0X3b)-xd z7A^bFe<4d6H4`Icv7>J88u6imy|p_?`2(+tEKHEe_=Vm)-;=<%o&++wIJ$Vc1iD1J z3QRFkm6+lpkdw>inTy-i8Z^?FFo_QsN&+MPMinj;JZ@k?}R(rdWJmu|7@`SgO4Ajj+EP02$?dae8y=_VE^`?^Ct=&VZIc}tzaRs5D;GV2tg6q8 zyHyP}fq~SMyhUzr6G65bj^qTVLRg^I4CQfQp?GanlAYnuXX;WW&Y}16v8Ym=!mu6F zRC25UYM%t@p(;=ehMdiu36llQF<$g~;zqWHC26tUAZR%EE36CF#ACinhyAP9yu)M%9NY?|tgqX&>ZL;trDNIy-)LhAUtP}@*r zMQ0<;4W2E4>Z9reOz~)r@@yd#aP=BvTyxDio-G2kTN4SXih3%Hi`Se}up$WIH3M)+ zs%9h4mO$M!bxWvRV-PU8J?V>Y%a*kG%wpHR}X60 ze26qjmTnyAZ6?T0$N%RU%*#y*!O}|g6HZBQET(H2EWQ`y!qd0ZO*m+>R(%nw)|kUE z;Z@C-0t3wLubxTjy57nRlh>$4Ts=fHnq$i$H&-2j^|LflO4bT{jL|gZRIA>e=4~m+ zigRAnmpahV0M=Fq1%Ph6W|C^<^0D3)f^1hm|I^}{6r(&~5Y=B&F+o!nEG7uD&*n;x z#zT3cAq1Q=HN~pZ`ElMhf~;ti^k8@py}fRJcW-M!w#4!B&f572-gtUvrgSI!MVYHB zW@~sQD}&e{0>*>qgl03E-qhUT*!vKANRy6z=4*EH>;tfWswu&h3p8UDYy;Na*Mu>s zi4PHB=w|_O*kze!KgTvg-Opr9NtXkZkoB&{19P`)0`bCfO=F&Y2z9?{^09EWW~V|- z6l7vGQ9@2E#cUHC`sJr#@@}_bE*V_T8k}!LzMUR-ag2!&2CQIK@Hdqp8FaDT25(h@oW#w zti}=zx5D`G2d(4ahkv?0PWkZ~gEvi(eGJ)qdH*bF`|meKW7ciWIwn?7eFxy-Ud;u(bXU`WX9wZX z#Q-Np)M;vX_6gX&2pA3B&uQwz$`#bupMKT2gM@a1Y!y@w(~iI#rd`ajLluJdlw4>p z$d*FJ7|j4ETCdTPHK`piFi^XJRaOLPTM1$ZLAFkSv=-W#SP`b}%(G7`blQ%B*in$J z9rf~lD_)>B9j&lwKM~j~c*vzqP%_()4IZ<0D!$r9yNQXX zsh+Q3#cV^SbhjJs=%($fVCSJ~wxK@Koi+?d_SLTC*afgR)zV1!*G>&!-QmnI?Ktt` zie}z!kThKTDf_x&gqCdV5(;Cq&w2J83KOKimr=;krm2J;((bY3OFIqYH(DD67pT=( zF+kMj>VhvW>LiT+n7==~8V^rut#2%$qLAHVOWlKsT>L z0%(tl-_xf*LeV?gI7rzWP#>v~XF)m&T_49Ch1+7yocRIy3BOfL2oWXn29 z#r(aF^ECE~ioZ}RegpQ6H4+Xl)y8AiMePTH>2+ofZ~v$@Gxh`znRIP=IQx-d8XUZ%eTDsr?gIWA2hUF{;=%erdy_py=7{-| z;q!-Dfn$F`cB?2ex`xEmaM-huCaN?`p1~5djmI2AOcA~KhnD9!;W-{^B$Zuxslmte zjwr)J9_ioDo9Z^h174?N4AwDS48{t&^E~4qw}D_nn^I>`$p?zEf{zm=P0a*XesVM1 z*hbfnmgWQ@7|N4$DzNBuTcvS(Oa+UOcA2NeOsAv8Ob&5BX*FJ3N@m4LLc&JBWG<NToPTkS(IE?t*pzemGw@m}e3GA;RbdI=PaK z#wJU3Pnp=DNbVg4o)Rj({`^_qk+7~r=T%0{5Acp4PX*lX8T^}J5WEiLgx4$T;2lmr z+>0*x{pkl?8YmUbJ>C z?dMrTc-TM81am*oc_C{+m;q`YaVq>|o310z8bRDf-6Kfdt_y+kow{@=+O0D}&3at` zBfjG7KMGfqCjyBH7Xv;X6oC+mCtnHtZ~IrU0XpMOB>}R>2`ne z;C11R2W<&I3L{xDQcG^7>vhKQdF!Ph> zCQ!F6VxTl+El~JcQglmrcqy?COZV2F&d{?ldJcp9;Za8El~(ZZYGQ0jy9gb2ysZ0# z503Y=Y&o*!sFuxI#X+w9_vQ&ECs5^8=AnC#)c>y z@Kkq8Axt3e_eIQ9GgEJtyc0FKv|_u(!@c9lC+YH!N&}^pA?=afJC0U{+ll_$;PyoKCgF~lut-+-5JI>=nrUSpN!kwds2Yu!*=XN}t8Vj#N;W;$0uKFV5-N5TDG|%V_{sxznHq z^e(vfjb4lQ8t4ri`6uss^~u<_k^TtJxsRuu z2MY0e8r+^x{Y}$YIB-K}!ET9q9t-kbR|dj+===iENQ2ijx8=x-?U;|*+wRk-=`NcE`QhCEc z$hmAP#TlRL-(oOhwEi775Dx#Sr}%A*p5nJbxbBobn}_@#O{*j$9gOR$^x+&vP0_bf zu-71ShW?~DqP(ql3dGLTk5guD>FUiDWalBbMi~cLGYru{+qV(IX6YY@{i%SVP_~`d z!M8K?-JxoMd_HFO(x)*fN|E=+d2{u17|VieQ^fxm5TF5R2=N~}n!qbD`s=v2K;MI- zg{|m2{dk;Qs9(ynVVE1IS8{OgHKzjF9Eb@<*E{;_j133%axM#U?sGfAwSvolfvw3X zhn6@4V8<4&2ZV0W|A|$L^vy&z5^`_K?U2`BKK{R&M4{n7781cm!KDWJ%{=iQk0;L4 zJfL{Vr#46#bu=p<59qu&@R^Zw9_02^#4r;Rn9fbAOzlG~I>3>lP zQw7{I<R=}DsL25Poh<%P~Qrw%4t1&?Xtc|!DhUuwEGQx z1ZtFOePb@~*Z&4I8-`eAT6FFX<| zuUa}lO^0{`A`VJ_*AHiNvFb1C5>z#8ZHKd~k^^D6Z;wHQTHzt3P_&7+bKLUd_|xTq~6ia#(bNB zHiY@uGShH^;TgA~1;^fomctB#F)PyW0ngrnnZLD~2RZu;q43weHofppw86x)ccG|X zn-B2AMux!(LU^h#$qi8V)G!EIw=)D{ese>mg3ZRXM8hFoTtHnRlqbMA3-eM9g@p28 zVhh7j4pN30)ywW1XJYSH3`L?iS8@s^Y-s?bPvxa3)}Vgb>D7e7iN!0%d*=wU-G2L} zIbm!hy)<8XM6iyyG|tlk%K9rB{x97GG5jIJ;NaLYSh&q#MtdK_k31{ECFN2(ifjfK zh7|B7$+H&$m+UcYVYp+6p|6Dc;?*_GJUjEom)yu*47}uHV|lCqu`3 zhFT`BrP4Qm`idb7GY1&ga*$tUEQ0sIkjplfj5ir+2cg+;>8v~ix*JVOOj>UEoEJVE z)T$K}1(}B9xYdRgJlh15OR0nrrG|A1wgut}lC?0Whan$kA5jGV%UTCu+-Ac$jv$~$ zdc%j9I+L$taJ8@D2sGMh_=asO>A^c;pxI!Bnu&aUIGx4!fgW@Do|tz_n?$X%8N#6N zZo>|?9cSOww&pOY!tgm`J0N$cVXwsYMkK+9{bX;Ma4MGIcHeR z#Lc9xy^!79_#rGxHVucO%1XQ!FbHPY$)T!J@m>xjH@z!#^D#=8xk46KfXB@ zd~Y1ZaKaA;;Mm8I`+=$(rr$IW3PK3b+;DQM?i$FgItaBf%!QG4h8Y1&z_m{d*-S(# z@G!*fH%@`7or*?K)Yh;WyZ&X^$FWLuM;V7PSQu|ehMaf96;L&?gHq`=W7Fb4iUx;>^PihXJ`sn z-ZzFq*A@m^k>f+;SQKe|jb|sI`mm`9EdJHF7#21&^@GW+4dm|kGg@J1vWB+t+(-jX zZ(uA_2@2Y;{1H}qGviM23+mr#D0m(a13Q<*gkf@X;~b@^6lB|`N}9Qc+mnpFIPny{ zR)x6(jqlP1UuIl^(QS?2F;)$^D-0hH%w%%m7wwIqDp4iK_O(R%GK5K!M$06Ho~7$+ zIP%oc1h3T?TQMA$VXT#l0Tc=L`Omv$ocI;J_zgS^GTPBS*hmrdMc6ge7=>ZIjBk@Z zXBfj7lxmGxu(-9c0M34HOa$d$|WOFuFanVBSb$ zA;&I}TTTv*^J-Xse0_|uzLEvunjB*#Q(oRG_XP^F3sCmNFx9`qL)Aq-2-2?^w?WP+ z`EaQZ7wEbI8ME{{@LV-?#_Xxa_jz^|OS_qNaQM+oV}>Z@d(Pj z#=jV=g(a;FaoD2Jn57hh1=%O@Qm^+DjE?ejsOv-{7kELw2|R<0Ua78Yblrfffkp=m z8S=MjOQp8s*$+^C-Pi=uR~qz~97XkDlOK#d$jblMpoi=kKjR|G5FtAt>Yx6-U4>sI1Fl1O}@Wtd%nRaaQy988bGbCVK`hG;0nV*O#KGK zoturl6+)OG`}yyfCdW8a+Q#p$kMxBKvM;;+R~za}ZTN|tOBP}Hr#$p1( zAT8KffN$d!(66k9CBY78Dozi_keLRda{h0IZXVrW}3i-MwEM$b!aWk`s$_>^`c4`h91EPYwYE&o)p_&4mopzM(G7KF7i#>1HQtQ+P& zGA`jb@fpPRGq%Cx$Ht>PCq75vsqwjjF~}HX+KTyC46|uJ>0&xhHOM*G6>97WsqY&D zAgnm%J$#@v9ab;{?hG@Rgn2(T_ib$#L%NY&1Tv$HNr=zH>ztq7g)CbDM$_Rtq^&w6?Y2AoYOhb*R~-5FkIoln-Gij1&pU)HVa%hX;j)-c1a2*2Eh=m#8UrOB zS!h%T>y_m19 zr0x*FD-T6=uyN{t^~7JrR;edpa4pi91XYoyby6)63WD26uNU!hCsTbw+Auf67|s31 zCath|l_3d=3QQ)b>}X7Yn$JUMsg}ugP`l9dDx_bQE8rasuY=WHlI!8BbW@Q+ut=$t z7tVE#aSNIUm_A`j!~1HVS&$ulk=k`l<%dDt6l1Z}ZZ%y-e3)peVEFM6Q)iBuK%H%* zZ9U8Mqk>tW^pt!ht{r0<$rEB$cEC6k$k1EBsXv25Ahw<{1Am`jvZw?bW!yl$B8;5+ z9AiAWm~#8dK|ZS>+xwr^0JVnPcbVK=F0F;plIYHHi4p^uRBSD zuNz5)uM276S6?TRbv{C2XYo~iz3<1SC3?ZFOp;nUj)f!zHpL_d~T8vJ|~UAu)-#J z^k+z6EXjU_RGg|~Ar+@;R!Bvt8WmCzL?EFeRQ5tDLZvUHB2*!TRD?=V=prdAq=J-> z3&|>!Hw*0~PZnB9eq3lIxw}wLa&w`MBot~%78H_&E2kF{V4%z`43_1|lp_j*=pTIx zY04^l6q1Q4I~6KOrWT4M6AF2ftqM7k%?cSwPoa#Y%STO@yL?oo+$Ql)yQR6os zRVm)_g^~QxM-_|T`y{;XJ0I1XC!Y6F#o{@iisTs|RWF|OQT5^xpPb|;K7nL~j~XHF z_E96m4}8=JS)`Y$|NnD9RI>|dSXHkV(jclP6w>gi#un1>sYVt`!x!Zbj$xxIM2(i^ zCQ60WM`5*zV1j5AN~OOIVBH6%<`}=;bW%Y`arQG}5~QXXvT@!vQ=U?AOQEDcy+;rl zWg7RI)`*RZr}|tK6{hD*jG;PWG2@D1D~GgE5Hh&;fT;&-LZF9nG3C3|#&J+}(&&*^ z@~>D$rL+RXLB=*y3u*Z-VR-hKsX51*Le@!RTkQCysh3I&7i9a%AD^ku!28uEHOD*^ zUz@%b#Rx&R+z*C!#mGyh@0cjrEyY;8VFD7OQp_^~rGL`XDpq5n;G{K7KCSPGy>FPx zB{+BW1N{gI*!UK*+BQ&Nr(33Mr5Gv5zLiN;b;Um(n&g})O^76LFKh5NR1LHFFzk`( z7mg+4B)gR|dpT`_12FLq(-#~|!AWlGdmLQbXp6=}&rBT{CCMk%vyNsso|%_(EDa_# zuu_~Nn6L4yEi5)F8$ix9TW6?gY+VnoPWFb_L1pf)5bFuDzZ_B#6t0Fd-FO8j;%drNy84E%nuYo13~ueh2K+t%dCOR z4K+%5{(6`kCv3FOXXxr>zQ)9ERB>M@*r;?t@@@0Z;_~k6;jYsh29*QkjliQb|5TiR zO@#7~bxy3QGPjky7I{C2yB@R_D$ki$VbTEe=Nx+#)S;FKkbWm95b|F$&%whB65f=2 zR5>{rH-nr|6=W%f(urmRp2{+};@JS)^+Qlk4zjM8zwzIP)Y0b8*g)Ksq^H|EN2s+@ z87uwu4FVQwdH%BWf$+)Vgh&Vru;`GAJfxs($Y5N7jLIVP!w9fJaKAmA!aBL_{PPEcCJKId# zY!;|>3LVtNo7LDL-+YOe(AtkJ0FFT0_n5Q5?(_F@7z*>vG;4=LSvTuw9KXQ4oM$7j z+F)zKO1hXkQpG-7G59knk+gVQob@DWv^2%Nb2`ARA-y75*)=_%=qUevy!nlu=ZAv zlS3M&EB;ZOj%8EKnG{t8ShRR6S1m!qW6e`5ZkQ*C0ui5X`CS$yW9`X_uTgcgFwJ98 zau{^qJjhR*+G3MhIU33r@^aX|oids+m4?d}H zF+t8(<`f+C)Vzsj`I42|q3C8%*S!-gz_S9Z9%7~hE?Kk_$#{48yWquL?eWK zfYjFwo0KCF9gxnbh4nhAE+z8iBP$m(FsRiEke0>1%;%gXO-TX(ykD?!IP9 z+ox{}RO2FpImlnaTi_mGwnAlnOB8mnS;(y4E-o9_>VHqdnHzsw6~$ zNtD<<*76Y(=aV)TK&Z=nwYaiJ5Z?D#b}Cssu5E3}=GeOxiI$6kNW`qwZC`#Kfuqwb z%QAlv*RcS8dejbS}3513d)-z#a3q=xFHxjam zZw|D~rRW9<`dJpk^Q(?<4BO(E$99){(^+w44&nYX`EpPvI(55C?>=R4?Q>qed(J1j z88oTypcPdnUjaEqPUG&P`8j;?ny3B`R_@L{HIQJAv6c-)b{pQjxu?ZQj|Zw#9a?lw zun^a8H8B>oG$nE@v;nUH_1nyd)~S}8JX?!Py!D+7&d#;W!=h2iDTG1~Pi_RdS=8Ni zkk+aG9^5zE(v>GjQ+--TB)_h08bi|8CLNB-w^Sei)}6GS$HD$d*pJ?kw8RAQ1K`)Mj<548^R?>(@;TW}Q4*kPIH=d#JS zL3Mcx3`EOU4QB)hdSJ7Aq&p~qOIt&Rgn2kWS~l=(H)O81FNMYZsAkJ)%Po%Wf$9NP#Pk|V7K6dxSbE^t zbC#8i?S-LNEfku4ZK2St0?Ib3(je@-B_AJ@s`U(Np6Wf6e~E8{bn~EpE+H(rWpG2@ zANt-aDdLuDEHLE{e%CxPM4+R4SAXrMUn6WrG?gZ?wNj5pXZFyo?3eGta7hl zr6E6p1q;j*h}xpC7PC*GpvZh2Pb;lNkogP?meM8%SvK1_yb)|&C>K+yO>e!JnAD~i zB)agMEa2FQNxl?%vMf`wiP3O>RA`8_|C=P>Pkw-|&mrSEKjwwmQP^**gvyq|AHq

        <~JXVTx z&)}ksc7mKqmu4ne=p28^XDc{ce+H9xTb96sUoCQ?CE3*Qyn*d?oRMsOhq0=STf?ZG zZ`t3#S!ve!9IM8hckC91WWU5EKKGBw8gNG^YhRvy_0mqirDbBN-OFH3PwPDPHC&Zj z3vuD+me<%zgWc-a*m+M}(>U~4Y*nnJhdBsRmYLI~Q5b||#wbeE%7bz!TV~DmW7c@> znAOffm*dunQWXBpm$Q9oG#XG=qDzxZXQ;#mlwZW$eb&J|WDU1|z`n)9r>&BAH`4k< z&b|Y7U9phb8RNo}oo&RL?2G)3dZ=p3n$=c(ZW!7PS zmZ1Dch(p^+sorx)K09pz`Il@HA$gVc82bs`eS(QZR(jYr1Y(|e9y2wAuH__cc zn6`wSqY^q3N^1@*fir)Zg7N3~t!l<@fqQW9k8t+6DGoA!SBysQ25SMwZbO%ea<59t3_hD8ogVP7BEeXRsa!X~0 zO~bAICH1Fq`0*j@3c18FC1RUtp@jL(O^XUge{e{d})TFicr%%K$7tW|jM`>LHc-8#q3+Uyy7* zg27>q=*y}C`!#$%lZbzkLrUn4O#VCMeHc~%j@_0FXxP&H0X$E#&V(6)tv7bOYW;+< z#}KAd=p}cRvJ_}(4pptmhfFUpS7Z41)>=l~!o?q3mcR5~y?%e_2`oNjiG=5=<^bFi zV(Z4>c?;Vu9DUomnz29OllJCWC9Q2uaK~L~+B_u+yw!t;47Ro}ecSdD*O0v1O6bwu ze8@j7kfy~f+BSc;?&ajqPPv@`6G7TxFcrV3fOZ4$#N z^=wNx7V!U4!c3&0T_8;hMIh$Rvs2{01}*L2x!?{6XhX~k5l4R+pAguruUpns@!u;ewX79LEY1+3d`g$+`tT0uNu?pkXDZZ|P>>Eha1t&qx zUTZRBZQ+AqwX`ahW!f^CNQjWa1f{X|t++GG)`w?iJTlixdG@f8HbNCFaKvl<9qI~g zL8usOd&)5@9+_utLYsT3#SE_XN1Fcbje2fESbWEE=?fKZmRZeD8H{r!?Oi8LS5XTe&2SN zNH;bu&%!Zug6*NiT6*zO1gN*zC`qGZ zFFw2fQBZx6kUB+utQM4gsoF=TFVMBtM&zPsP%D+oApNoF4$w9*1OFsOF^lA7_)J|8D3 zYj41?@qXI{jx_=GCEIhD0~Q}t z{bp(mmEYM?Aum4oC=wO88)F_=GTKI5v`X7`9y8`y*ZpT~V)527n?-`ImQ1#3@nn>> z1;q=0J0K?*YX<7uwtJ8uH@AlJBHJR#0nt(7y_3DUG$zdJX?|%Q@h57rP41s?6ijSZ%v2Cnlu&XRQfRb@q?&%tc!Zp3d0hF1L`y zkkb^7eXrPda0G(ouC~m1;VuEWOB=A_ds}x#co-X(Kx~#9HlKut<*v6}!P=WPQ7O-$ zwIn}Aa@-`sjUsVxh>wW%w_Vbzvm3J<%7zT$f3bZbwxFj;Qrd)6@++x?fHCz-Yhz+% zwy=M|L1>#`yR_9(nop)kLFTnSr_G;sd8sie{xu{O**xom7g|KJa7xTie%tz<^g$ZN zWd@(5N6x@=(hfV+{{b-=7g;kgYgq6+>Sb=iQQQ__f0eOzknvL}IdVaEa^yN-+;VF$ z4>bpEk+7guFlBG=D_)ff?T8tDf}gSqi=F4#E11zgco~D_6&3}S+U>(RI-}v9Zw=ru z&mL%Ikjk0o!_EkMA3ChzUSQ3oJO9(hyfA5p%|J&Nf-B+B9h;*f+Wv+h9RheiK#H+w z|NGNHY!K;W&7e$sfTYXbhX?!UbiwmPNxM>xPtkSHIA1RUA^N}6u8-W0Uu>Od%~rIh ztJ@3a@ZoO3(-`ItNg!UBYnaXL*G6|JKVVCL={cAEm*2cd>T(ZA8!h>}@pkfedlE&# z_6VEIm;9pS_0}+KoopxTOo#F>sGuIqY{$)QgI}c~jJC%6SDFkwa?6&){%yIB{nht^ zv^S*b(8+#X!TLbP5c?Z&In%xdO&RtN0>yrUY<nT(!cv-+*~`2bh*tP45d7AyK`0&#n1JnS%r&x*-7m^$J-tF;c)w4fvA;7&byl9 zy1Dit|JN^oz8H#04^yz0`L;P5 zFBaJw@@yoQb+=w-ShLI?!4XxmEW^47%9jvAw`hg^b&lY=vcA^)FfrKr298~0C%Oa? z=htNx?;4c~i`UzuR1=pZ`RMS#`*WqbHv+WB)9ZGxNMzQ$TXr?4oVj?eFPl1ETt2El)ZMX% zif_>Ov(WLYeG0>A_v{Ph#AtlDNI{3?eznut8{*yHXiH7-hfzux+sY9GIe*!URe8k| ze8Z`IgXy6k9BuG|MICV4p32~>=k`V-B@-3-@NmE5+};ZgI`&rpN1}Xk=)0J8lv& zvAAc$UBy@L9r_2mqWJc9l!Ki6cZz#9*+rZfy$LJB9HY46`%h0xt$4RMw`vF!RXGBR zhh`lHTaHGB%^ExG3N{KenmdX(<-+|leIo_gFTm=lQ;X|H#rpw3Nm(TUZ&C@1;^O0) zwgziA2c24--XaKF$2(3)r&c|0IhNqER7ZtM94#H+B3%|h`YM|WFLiO$V-g`zQ4Ce7 z4jm9swgTp)IojZzZVpP>E-j8r%K}ep;RIA^bOn%;sUt$qB03VAwJ5oig29E!{iW6~ zBRY>R#s37#FY^J?!I$22yl9-o4WThY1l)VoVIUN6P_EkE^z_wrrjdW$k;oC=SN4pu5~!Hr zAb)Qicn-87zh}1Nwo*QZCg$zLmns5MksW>=%sU|GQqzWzKOsyB-CH1 zoqvCK+!&%!CA{tUl4Bnf$4&U9xMw8cW(yr{IJO=iZgza;H#w?!13cXBNCh&s0PL~U zv6rV4tPgiNKKt;C<&FU9=*Er2-MdD4q%cuYM%QM{I3eb+;_~t7aF%z(VQQu00W0oV zk8=Jp#TE#aJErWuQZIrlo;>L+dGpsy}V=NMDbqhY6Lr|U0o?r*~;Ds3km$#dF^>!t@`O%I$e12Yexhp?%g%Q zH@@T>$8+g?tfB%QW;qvN(M88=G=%VQgp>HDmmSA>`Mwb?<2=JLxR-Mvc|1q?Fnq0+ zW)8Q%S@Et$;ZXNEzYb3Hb8g4iesIu9-UC>$+{qK*W^@)o`VMDrNIlZPASv)5WJK{E zY+mQ+qZB4dxeEWGfVx;`+KWlIys`5Y>A;K%LdR-EsMExRLrzCa5Tb~dEVb-sXypr<7z0MdVR&XR`rW4aE*u3@fv#pz`snBa9DqwW77PorIAP?bP} z89XhVQ>5lpf_kvr1=mJUrIY75TkI`$4kx@2a#tm2i!1x6&?Pvp^YTwUtvt=*U{_z~ zYdBl!e2aiXC_Txy!&icw2Ngt(ob2X*kQVI8gqoaA-sE>CK8GbAIjabTPwtKN&Cbgl z!GLAad~3+FJI|xW?tDl`XQ9C0q%^3@Nj&1?@US1>55fk855zr@&P=61T%qq@9JHq~ zqg81kA3%r02(eq+*x6oHy{m1NIt`vnj=pfX&>0Nr^>j_4 zoOcKi`w2hgA9a#@?EH#jXTYIwkoSJo(F}XWJNNMHEM~}^J6Z8x2Q_f0uX7ZT@*;~< zw*+DO4F|z9laKQWaBY&4pd6W^3LYNk&*6nsXJZ9B2erRyJullu8^~9f{men}iKmm& zj+Nb=?KtskPpj5(;8xIaJ*xC;e3| z2unQsy<;!#pWq}=_HuDtWh6d0!4qpMXrMCU#~&%yEtruN{c+O~ItO&QF>64Lzy>^&jz+tIl`c=2>m=ky&==evsb^ zdBdFrP*sx9S<*ByPS?+K4rT27;<8??Fk!y)OP*aXK9WN~!U87&2{*8Ek~5X42R}OE zX#EdzzRN0>Iw^DcBi8=KTM6iS;vC7KaFsKU{RBhZ{3FS96}&VEH^I#bm$9bQ8No{+ z;k0PVj;wc{;Mr|V>z3jqSN_-J+ZepX`J2e@;*whXcuKz=OBw=ML+Pw!(beS1;M(a_ zmRxe0!Mod8!0tir4rfzo0Wd?3$vNVm#|ik`9%nF59LRzu0+H+Y(TPpM58cLy7Lfix zR}Q7$2L*wnqvg$#hYm_PeC*r~JJ+Pfg6Hje0&K6PSr*}}2bESK6?+5; z6eiU*<)a}rKo}IU*QlaeyjY07qI+yUz1UfsXDx_j|)q zlYp8eNK55kt6TWqAg1ypB;!lsq+5T`_3Z4~vuBFk4l`6er;|vfe>w*5&2shj8^CjX zxZX){?g_z_49`oQTPvP9DYTSf#$)GB-rq5(VFIN*WlDw_Pn`lorRe&TBYu4CBj*PU z-_f~f_B9D~ea(1uGZ*D&tLpKeOC?Gnt5QI>DBTQ>bta)p>$)y5Ik=m*!3E|VRJoDh%*G)Ap>Mgbp8Fi4o#Z6HsHc~t|^>cVmAfQZwj<<&}qwP z*9?_FWW3EUFs-7AuI4Y&2lN4*awT$-G8odPHn3w-7uRQG%Ucs{6)j!VzYsX`x+@YA zd$@MNx|$dT*2KGL2GBv|V}XN8VndtLuIU&^&Qq>9bK?h{=faiT;bv%&0Uu6)=u+0_T~TeyPp)y}SXhW0709~r@I4_msT zpuC|g1rD@y4VL~d2&<>Mn)1v6*^NVQgKK|SQ*_O6UFQgc%QzQEKU*-{m8q2b@&wr< zK~lzaa2#_rgK=-V%0>6q&c3{Q{3V%UF;Ypa~_w=<9045Tb(y)c}#-nEQC zI5_i1oBdLi6z#-vQF1u_XB#!@wzwWh_*5R^XMkj2-ZTnoW@&wD3E!GZ& z=bvl#g6n}v4?VBC$WQsiHIjgRIwIuCHv*a0Ae%y721{m|IT=~36 z3^-*AaHqJV@pQ7A!cGc4DQc)ovlyhv^?}r=mXr!jCbH8-*K?If&__ig6g>QU3gO%& zcYSHqQWD8b*&1t4*sb*WdDjyxf8_c}a$uBgpf)hs385!lYRKxQkVDvSNkh@|*fozs z=N;D&InD1m4_#hPB+8{S39hBM?NU1yGpUT;L@R?bo15U2$FA$rxnSn7r9jkO*SmP; zscVfwB)t05NU2GyrKyv)p?{%3zxqMvWFER^(N8f%yT?lHBes~bEv+E#8+ffQ=_Cic z1KsJY9jcGJLW%bDPrHEH)gBLqxL;wcJ=FZ3SIlea zF7Vf2fKx^Fg5cc{cVjqq($yGzSL`~}$GY!}tUHm-f{nPkfjfwZ(?RZUSw+12i6EU; z**fiIYhd;221=+B+-l58a?`orZd6@590}(g7`D>AgJD&gyBYD?Fs*YqW!=+%52rmf zF`_jZI=b&MqPS)6cN1r$lbbjjy&)|lya6`u=B6m557b0O{DFU^yE`caI=u3;KSN&< z7;b|N{oNhKetV}Ds<5uVJCqlR)2nre%aOFZRrOSvpypge3(5Sm zF{37uaEH_x5qn9Q^Jpd#E|&dU#CWKzbZ21Ye0MvEsFwXCVl#$KaaS&) z>k~mI)K|Or@oWt4s&*&SNs#y5bP{AN?mF*|qi9W;9kiw3DR;)kQUZnRgq(; z+2y9FVj>)w9FYQbCnA2v7JJ=iIHIZTIukJor|fetAwnANIv3#}$7Wyn2%Ptcdk#nF z*RBKMJ)zW7?=GesacfjUups+UIzCl`kx{Voj4l`toN%{i;&^KCYgoI>O((( zw?(Xj3o%Nyq}(h>9UDFX@6jPV#->66)c0WN9QWrGe1qv@0XLKl2YbgZAIK53@HY3JY%!IbLND z_POlVD5Zlx_vXTq^%1Z6BS^e>Mi))vbyd}fJg92jE(mF$S4;Depf=@9%+N)Y^7PA5 zDgqT{%2}9Uije%8;ciOv$dt1&!xkZD@GLH|DV#kMaS8Jc;rZk#TnU!|0OcIaa7FCr z@RQ%%9T}q^?y@B2LVlBa4YBmGdpI%iDTnB$1@LTxh&T>%i>Yd8y!Z0aC0c9NoH>3xKwaR}X#CugAr;3@^jV&hBJF(wiw?!OBPO3;s3oEg}>ur$Xt>h*I3~*nQJq&U{d> zA&11198TU|)l)h7ax=o6{zq>E`&K0#9uEIN!QRDPEh2gndR%Q*LHTFOwzxJnd?aHF za98UHav@W{jXY2C5=~&Kc?)4zQpENbhLV3d{3+U7hLeY~2;x?$nm|oj#0-2k*3EI4 zwJf5S|BJ=A#8WSWtg(K?L-0%pB*ZZ~q9wz9DdAlOI%oW_El;qgv=UGsbpx*IA3jw{E?`)8_|K~42L=>s z1ljK`rJ>x6dxnK~^^^USD^Pt^b%&-$wNCBV-AYh@6FDFL4`W{f7iHD`&ojfo3^ND_ z?x@VZGCZ@Q<^t}yf|XghfR&kGqiNau|Gn>rvFF_F-1FS$o_o$cOVQnk;&I`{`M6H5Cd06k@0#iK*<^!_ z+uWrcGo8M;Wx2#VV|ZuhS+B6ZJuRDNm?A+>ml{;x%xb%KuVPuX`bjiSGA&Ho+9Mtq~VauNrnh-RHSl-Ap-? z3;XJ0#~nY1q!n9y89R-kS||3h7m< z{7pXm=H> z%g|E+tzYp+3KOfEi({_*ptU1S-fbAGNoa+AVnycP)8e+v>UH?rKxqdN1BqpwFwiZL-BIzk?)hHBzXIHvQ3M_72B#FzLsEItj#Fgr;Df3Z0AJEYGrTqCXd z%zzX5COyszv1wi{rn#H+Z4C*q8xQM$;nnxBl0UC{`63fCaWP)K_u@|2R;_}`)2|aa z)ipf&rTXyZlu&y%#cO;;^}(is5JX0wajJUXrv4$g1~x~{G3!?wO}y?-{JqB_iOXf zfPPBNCk%)2T^S4`$v?q(KqY%1`>t2lykMFqk%Z!jOzGC?c^~2c@ zzZz3mFRL+ec~`ZLwV9%p>A4lgu}YXI<}cD?CF3;UXDQ!72pRpk(M#P|85>nlJak%Z zoTU18+clv~TD00YO{r=GGT*W0pF6jrn@$+36^{;Y$tu3q-^8#ff66iao_{Xruq4MY zg*7b3KxNk%zrjcKjyh@id&WV$&Po0WwUjQMXu)Y?4Qo1TD&>>X^GZLk!gtk9KD`gQ z8WLa8O7`_f#z)z%pN;1zXNwUrbx-r}s13b*)`&~DDcg<9pl&Q~p=?b>M~zjmZrowK zS_Q3Q^Gk*T1yy}(d`Z0dpIH9Q>ULCi$k0m=`*}$^!#$Kg_+vsRGBg<*m9R|Acbj{#y!PFH6oeuWs)s_Y{?j9S z5weK39WwS-@%AZ^?9QqkvqEnIfY?ID#3ND`rj{l0N;C{ zs?;bbf1(btjyu~mH?h5bGFO^k~>p6~PrP`ech^3iKd1X3iYB&~D(%Xa}0%^>1hwcECZL;MmSkeH~Z!jzL zOy!U_ZIDF94>DDARiu<-+PP+p32bhKrHde729tcIzgW}VJ?C-?)Dy6>;yb$9^5Dj6 zP30=?sQB$bK??$2odixPz^M(TP1NigVk2PbDF+LDJUg@MGToL--WbsQ zRiK&0Dktk|FD{)e)HL1%X`rI5eF0wls}_^|zRC@+e8y4P>*8Ka`7@23jhKF)B9Z2L}m#6Nzh|CW-@8xL{Syq{@oWD_+4r(lPu!qU1 zU=2nSc6gI%gTTs4X`^kYl}zrhNrv03#^#kNa@-2C0KG6(RGaSNwuHs|v3TxZZCb$9 zmTsrjtz zXA_q!G`-m?Kc+r+ap;p0eV=4*%Z&r|QrGEh%CXc(FX?_#viWfZl%Gq2EqlQ-qicH_ zlWJbWqaY4XG)FG)s){Sc3p34=R6}2Lhip`vX}(%Y7*PJ0Ib@}jP9O&NKGwj&_GZJw zEOkrg0xl?Rp~v;+Neby|R{gCJT)|`pR~W=Rx2SRVXfxyPaWHFct%I!SGDB7z!U~7! zaoI1M4w{A(4 zLm@VTd3Kow)9`_2Tqs||JbO$tY0eOHI8nNe4S(ExH_e@B{uN@%_2%yrR4&+#&0^IN zWz;E{J^hF|8OF?-8!EenaIg1_u<&6G5v7K4r(s?`5z1%H^%Y}74mgG8@wUBLQ$m88 zJ^qNf9sgY5=BgRRDo&d9R5sDPMxu9%&0P{;w^!4}-hmaIG#7LDyhNxW3+9+1OV{`6 z*@oUNH6Klgck|GfJkTB=6Zh(mn{9l!vCD>pvZ2k*q2bC-Rv*0ys-$AI`v8O zA~tS`xk^=xE^lFxk>)pOdxg2PT9WD53Uih+@n#g<=0P;cbM8!L|J+&#~))dV!ZgBll`xxx0!X0^x_5S`5^UN2@O z3hLUJccY~xeYwG$sYsZFW>a!m;aQy88@8EKS*JFZFX-^^W{X5ax0p9dP)#0fZ!t=A z_)h`b#$nJ+UYG8L-=n3O4l69Y?hXzwrK1MaWrq_i>C|nPd5;3Y28X?6wvlVM`4gpd z7k%fjAV3imA1dnno*5TU)7f`!%U`5CXqFS>afGfFn0ox4VXXkfk3% zRH%q@)W%QE;Qn_4?R%p}tL@gAzm!P(xfvHcGgwojc?Fxg%ACxeZZx-}9S!DurF0*~ zA2&lKd%tMGSJ(ooIcc7oh|NhpW3JIWS|f+LAR6*5tU1j(gav*!w@-Scc0wo@$#>qV-g zZaD+pZ)QdBm?1prE%&j{G!_SCt1K`ye}K}=meUH>evf%KbMG=IvcnUVS{$hjYIekC zNnyvemd?^V)^V@-bt0W*NTL*`+kW9M+0n}KDMA#o)U#&TjQaigOQECs%PYPHHp1vA4 z0|j}O*Li-6NQMqd%D$Gj5~atOG~a>{K&DZ89W*}4zu&?pNGy%?2Fwl2YG`*f#Htmc??IOi8gPjQWPJWVLFtW4xKeMaR~ z)O6PJ2G0I3Sns?X<*!-QBD@E*deV!m52fp@OK_GR-eL`PpJI7Of_^cdblX`|ru8m< zg6qzFiC4jpIb_PFEu|KO)LF?22Wt>kf4XIzQnkwC_Xb)1?V9d%?7Yp(k5cgWUUr{l z3bz6*uC~_D`dOB@6M4RiA4l<(KZYF%Ste?#x3&qv75aCtfYG|J?KyNvOdp-KVd~wkQ$2-e!>*L-m+935C3J@$B3}FlWzUb#V6Cqqn7I>HEnT+;PQ;q;>YQE zOm1n-W`<*yqpDYd%O)zm)A}@2lzgel%vR58U+F%T6KyjUms&e1Xz|+?*zs1KS6{^r zd~NYdTd4SMwBQHEG2*!N2CG(D$FZVi8XZS-DGzg4`M$LmN3b`mdd)J09qwnjlZu)x z&nCd3H|=K_%~c(>UaO!Nf47X1;MQB-)iR7moa3KJEi3O~@oorO-&0C;th!WZq%yau zA7U%2tcb0+gUZiwIiu-M?PK(~#(Ir9z8Ac?qfrqswF8oBE0EvC(t?(mRM5rN8m=8d z>oY9a#`+#Y8|JUG&S1y-XfkOXEs9cm3O*F*I@b+r}~8w!zyA47KH?>$QU&GLl!no9{NpOIj6mpa$f zL`D5f>u}xG!!nmI!mnU?SAT<5#ycB{RXD$=6)}|SS?Yr(xWwdH4NB=C%MWWtM85`V zDm38$@##~RwM<^A>A!KHbzi)cPwx$}dX+pRqUJ-~a#%jXX0ya<sv6-EB?JZdcf#0b&csI!v3*dg2i6*219!)@S5(3 z?PmP_^^-PMS)ua$Hma*_sI|U;>qn~wvD_cRA&9@U??`HA15+(JZrK5@rqeyhq2E*K zqkQKttGWyY1{7fA5T`jwrOQ!HSs~kC1>eG;6o}LOK&4OcR`TaImR8eUyfwe>@@E(rFPpq=1ntTBh6Ms{x{8pALvnAFPizjbs-OVDCOv&51C;ZLq#81u@U`B zrRTt8mp|9JBj>T#NM|>c+F+Ph^`o|(K!F}^w9Zh#8Mtt(>3TM$y;V)SJKEYSXvsj6 zEb);?O24zhI+GY1_eQ6u>E=IIcfPfVBs|U1%H1D$%`&u z6GBCu6vql5(d=iS>38hFXpPJ&zJiAR!2Mbel})xp>qu0sGr4d#mtgxq!i8FOs_8!F z?quz`JZzf9KJR4RvoXo0R7grH?`NBfkSeWp5S%O2J-|=m5ZSo=Y7VX0VlBX`Sfz2% zU+K1ZZu3|8qUJ>^YqBB8A;wOpciP*!!|;#tyI7&j1TjCShAp-%tkq2GNZyy46@ITy zXL$oPU$DFtS|^ihtOE(CypKBVHClGH(Y8g>u*!1HfGcdtMNrzb)~(<@A>&6_lZ%bC z%-TdLNj*Xm-;l3`=I-FE_7Ag$L)LrP%AvOT^l-LKl?ZRSO22K2QUznfq!z4sq2+p( z`XWA!i^vPYagAb93M-vv9d?m~Z$jPm7p%*swzpz6Zn1sN>!czfi4{_8o(&2cq%g@b ztzN#yx5|MV^8S+X>cU!UP^pjE6q__h<}5FqXe} zNzPczTpyE!9X6>oN!u*A5bJb<4FMk7u*TbU9>Te{0VFyIZEt-$>nc`po$e++T3gn5 zr;fpl6({_rW!iyss>s%gYx~OQYHS-7Jc&&~Yc^}b+%!&uD*3p90c zNgY^`!EWZKtqOQLC`XX?P8&;9!5#V6q{~+_$I~NW%TMaKWn^eDj18kQ+hvpC%Y7%G zODp^#*4Ri5!jjLhjYcvPRyW&(%yP4AP=sW$x=PIspwBPbJK@r(Ug0io4qOFsXz5Kil@lUpOf0)-Zvbsg4bZYa6?PQ{4rsi{2 zx5S6r&v)}A3#+ti{IvC;t&aki(x$NJ+e*#xw)N1gU(kiJSFSbC{wo{zdMUtwEV!|7zgUpDMf9G$5CPpd469S%o3ST$_`-Y{466j-jMyxrCp_`P0I z4)a`#6U^xwtOy&Mr?l2yqQ7lwX&T4t$RvH7@fQx?>#^yv8{V?5lRP9H&ghTW5xZ>Q z@xyTS4vF-iJbo4Irkwf=mbXT`f#c@FpP!`LEfX;NjkaqzDf=s=09ALfJ}S}jk8F1; z;3?ersAUGtI%IoL2`AzFHKuM7^*n0Z1^-~G?rVhva^an=BI_46_%wERe^A$#2DY`op+wG;x(!-C%SzX+p!!TZ zt{D+-VYwQTM3#NGn&%z+UI=6bIEmUL3RNErF_@K|HkUE^gt{ZEebbIaMAt)IlQkX5 zj>c7*Thga4yMycZnw!%Q+CIm=iIp^`jUuPV-ZoJ+0@m82xgMh*E$U*&l^%lfB@IUi zDEn-x7-PRrq9H@<3F2cZp!hud;-q+(5o{32%jn}lcDtk+h8EYby0P|wye`2ek>U&N zrRw_tT z=vJ2Yf&DbqueQq)8JF7w3Ebg#n^$0&ldV!SxhXwM1Sw+M_uAXDnmu*{wODCyR;Zx* zOPWY^qj}DQ>l6^(Fc zspbXyHA?9YR=LiBkeV;q&vG((*4yzR-G=(O(z~y9+e?NVwaNY>H#kf)XPm~w-^UB! z{gzOgJp(QPHDB3}(Dt|NgH`d!Z54~;TeR1HA4hnuW@dG(5G+AzU#pr{H70ZuTk@{Gglp=O?qZQW_WN;HV27DBbl6GQ1FP(B2?{b$a3!^R-+oGx z?xyCIjuZtQTVn?k==_QOcsz^?F)l&~9I?++!VkCH?3jyFbSxoS1Izj`UBiPD%;s~r zFWMean(Q+5`O03al47IsH#Un`K~D|9g5|aAAf9Kl$}{OsRy#g3m3p1wYiuryl-pZT z!3sxT9I(bZL?Du+GOGV29mhvL9;FsL-V)zRIRclZU(H{To$iTuJfVgmYg)47ltMKJ zV}&SxqaBK#yeyra3M%YvMVm0Itn1i@DjMxL9AmlMEp2S;XjE~}v4I3MnJPz41FdoQKe@;(sasf~O1!_d=b%;U4(y z9)|IDiam@?E5#V@ir=A7( z!7!XfT6n_HkF~Ic=i)Z5g&~Z0+M+{PiQm>?TnV>m83q+v=t6Vxn;61dNIoB$jo)8G z_v80W2rAg*)1mwDdt+z@eiK7?$HnP3gr?(ndJ#`!=dr`ogXT~?*k#eOO%Gj;&*cBM*L0)@r`JFXcT_O zhOWo&O`(zay)JYeehWj_;CE<?nEk(l z_wvZesPnJD$*G;fw;?NcMtA~#lft(mzHn+>7_(3LC5+uGXb+rdu_^#;hXTgDh#TmJQ@Z)QWk|liIn+aP$Fes7&J(k69x@ZW`;5Q zlzYOMeaf^jW}h-SJQ%+d!&l??mN2HBl73To0RFil+z-E_!kB&1LaGZo5JqhH7)Jzh zamcQV2lOGT>jvoZ!sMU%7w$w@tmXDLti}0s6C2mlF@B@VF)>~>9ybqtFMfTj;U-6C z%_FPYhKj+t{1@T;tNc`)&=;WRMbQh0lBVUb|AAeanvKrnfX_ppt2 z3~}IA16t{O9X969&bXg-PH+=+p$#&nx-TY3vuGH}gl+jfDoINbj!n+;!C8{Psy@`x&VA&U# zAp+IRam-gg{fs39Pye0Kh|d=azgGi^C-7Ir>Na6J&N*s7h|^$<@8UQUmsHKF(;eeD z2ZjWEVPAesFtcY^bsJvvTY7tJ*)jInz)F6#FJ|=x4i}rd)_yH3D0HZKN6%8#`Sfwn zoa8y?u&Li8g4K769PPONx_BmhWXc{zyqt==0vcA3l%%Dx6^`c;r8QI-bi9hFA!8hk z?8HDvIeRtTQOKFuFt+|l$8hO+D(vP^A=ceW$L|W*z!na3;A~vwz}a{$ShAyuWxeQ7 z)4A0SO61uMpV3`L939iX?C7F-@#U=0ZSir3FIk#cg8QlX6~`(`^&F6Yi8k+W^a1iV zT&WF-I4A)|t^*>DiELa`dkxzZaokFWH#xLw)g%y^`>OO}pB=|qoeZaWWE5n z`z9Sa4yvg)uTBcxfy^CIUE(G9)>;kh#yZD%+WVCwAn~j|Nn5D-tm9{>q9*8CQ_6P^ zXkd}O=bR&lRXmj$q}xwAa8dK-hGyq468+xn&_J1uh;1CT?X0%U4o8-A4mh&ZvWQac zV?|a?`i4Qy&B}zjXdLsxF?kjytJ|r_AMeDa`CAmPbE5H1icfV8Ny1%dVSDGKgtp;D znW3qy4*#us`|-Tc6xcyK52$tk!FQPSep^Io$aH=MJvF68vT^Y0+~AEKy!NoP4cRYK zdVJ1sAt>ynw9TTzH?a!uQrcDlI2i-%qqMqg2rr$z&f}5<1@taad?r@BAAVQ>u+C?_ zYuF#{bFhBmk`7SXd)deqlhG+hSbHYbv$TWRxX{mY;zAz=p3ggT*nz=*4=p(;m*U`4 zB0I&W1mn&ZdC_pchZW6{?IdsUAVB0axdW4jI9pJYJ!ht<_yMwldiqnz(d-IX*Nk+w zVzsY1KV3f936H4ZPM`E4JNP;}s%gx2vOIF?a01HCRuCKEoGeMuUK1bXCX7qW0eSoM4-!ft%;O{Tez%Y_o% zSL%F-yPG%f%tp4fx3YoV8?ug_y5HXte$agIl^7g}reZyO4%=}*JiTT(;pugZ)vcC+ zbTTGMhvztP%iDmQgnZn!V%#&aR*kIcVIVnYf}%B5wQ?7s_IzIZ3s$uR@HZlIDm#2z zjs-Dd-LQh1n3E!w*O#p7G2Yn*bhe?u8=b>(+{km_X89}(G#_=sIPwIOB63^WuJYz9 zXv}iw8VP2lRZpYmdc8ZDcGt;Wpd)m;J#_d9J}mAopXFWb5=f@TI{t=LJC3O2Sv+&L=ROWP(A8_BnjevW zl9S>O^33MYnx5Gi(2)4r3FtZUl#5O?J&BZ;>9GjtdGdq>G~Lsgu3GPFA)rXpv(SBv zzC7!Mvw6~A!BmFy~7dg@@aQDt61soKviX4p~}|Av&v`Ot!T`9zGp$$_JO{9aIK0WVbS1u zG*~qN@qHRvyFir$iY(;t)B`>vIShe^MD0Y}uy_&TIn#v@&uWTPil(olX~796##$O1 zOT!||ytmTJjlK=gt6E$qBq<4cSkP_G3Eu;Fjj|l1u}N~K=vB+~^F4X&ua(|ov{EM@ z<!mBu1h z2e3i)kSm_sm8NY)Am65~V$|%|s2QyEWoK(v zkt3I|2HDk$o-T0Rg3yz+eHR}rtH9-^9|H0sF<5JMFjoduTKS)oXmD3KN6ftqE8pVI zqrcws;=ol^=+aWbSl8<+Xqlt@?G_jKTYJvmwoCNZ?XEmTm%N<0X*2)9+_WA4!Q8Z+ zE-<&5E-<&w|6p#~tSc}#t^Oa(O>2lTw*@XRH{*XYH?8Ua$lSE%|H0gt%O_d>gSkE7 z0&}zeZ|0_jruoXuO>6%@GdC@=K3|c!z3BQF2B=Zy_7Yz^u75GN8W)&b_9e`Xfw`S$ zYsfVQTIYXpx6Lkax19eIcY|E6WzDT}kiE59&Y z=e7O+lf%In=-(Vp8@!Ce9d?1kkt_XQue>yio9*&6?xn3|K@FLvSI2wiMAi~wo*{Lb=lvk;M3pv zh__M6N*sPT@WpBG~*&BVH%8ETWfRrTVz*M3t3Bl9qzw&5l`_}Bf zaPX(jCq?CM(aNRNxk)_tj6P38yh!ofJNkSV%O9^uXI05=HLlik9!6MY^i_Y>D+7W4 z(K^$q^EvMm60JIzeX9cc^z!wdxyru})eMJfm=MQl@1gQdD7h%cv9exnT++m8@1^oL zc@y_*POehdLgQY`D-LiY*wFwtluL2i`ehsWn+u+RGC zKoJqAokQh^JTRdf@>@13U@u#KL{!n`sJLVXtE4GT=n3MqWmMiEN>X!-tZbU|Mh+}T zfG-a+SH-)(SHd2){2NiDZBEAWbsim?J=2Msj5zH)DnBU-GIP9a$sFfk4h&Iw69}#nxB8%FUYu%mB)LLXWQfs!Qz$5 z)x2=b6VbX4Q@Kt&uZliDLglIaxnY=$Agbj#cXNamQF#Uj%IiI%$Hi1US$-O%-{cXq zSi*`+WVpC@cTZGG703}Q3TvZs&Bv&? zOg!(4J}+gF8=P=DJe6&rrhT3pkub&Q{es=NBZ}%WD))Pp3Q8R5J}1E%wsMaX;zr0< zk0trOAHJ*L|IG6~)Cxlv_+AI_V}D!d@3kvnmc~5Sy8~4Fu^*->HLv5 z??xasr1U=EG_s~2=SB7{|-BaKOt zpM}lJje(Xl)*o<)q8e7}b~&iY?Mjtsd$o{2Y%UFw#Rg_@WFstXdbXDhYw~@~3cho< zqhZay*C6rtb_u$^MtQ9<6by>o9a-6*zIeJbFVI2s{5q4aa^c;cSP^n5) zaSy!^OSL*cKEhU&r@=s8@6QCGEdCDAplk2I-*#ki^~4GY1z<&UKk&F%?NM(Mr*$ng zUoEF7s9>q*2O<5{QG9$3G@LuAxlj~56)Sj)ny(Xpsu*A=HQyl5##OD?*$5xm=RB); zhr6hGjHvcPtlHbuTnqqmf_c6nXe5zA?Hy{qmDfq{l+%QyJFL7B-`=VBrPLyd50(Cg`5HIuTk&xfMt4 zJ!+mU8tjfW*iX&n0&mHJc-{&;-7~b(wYRA(0JR>c?jS zy`PxD5o+En8pV5~E8!?LZxsM_4DdNSsPv*)m1wpr+UyuL?-I3AW3?Knc@F@rAj3O> zX`gle%qlwPM0I{_+q|59QX}an$;V(0{<2?9Ij{I(g<00sEf~@lq@RK!uKw#NzJV`E zKTQDMi~)|5{vH9S^>l{YNmT#$GpExD($5qnJ7ZP9BK;fz*c}5PInM(C@MFnPuK$De z^+I6*mbL~dk$#XF@9JC14+6^OUhghnp38M;A)kmLt3}YwZoDM#L2NtP_x-qe zC>r5Zv7%q(6juLYj!%NZvL~<6@QpKtEgt8dc2Pe_VjZ1io)2@7;HW0&uu_7~vcDMU zHg`GK5Q-dM+Ec7i>48?P!T&4{L`R&0G*Dg+<5dfub9SVLtvPU6gywRDXnhxyT^Ce^ zPl-{QDSsp{s(;o8i-lAECR|p16Yc0n%D+*xx*x4B=nV@{ellAA6XlQP<(vx=xZrWj z)8iS+AMYIsx5;K_76NycxjoD8bH=kd5uZ4A{Y?22ME4JJq+&|Nl>(_> zD1VaZnS9Aq|7ng-0fXNBDICPvBY{1B2zm~6C$Rb!En6?U!)4=d<1DND-5FpNALO)w z;yveerSu!+-@{wg^l@Q!EdrPwm_ZgRQM*jM#qX3q(~CGYLJ2wc+nnbV(jSyRN1%17 z60%yLb&m2M;6hF=Lu#? zV5T5T?MVz>JI2$Mw@@+9kG_s{d-niCH*2HaB(Su6m!7lG$8hL`Mv_4z2{*|&T_PR% z&1X_@-DCx(Hz=TDTRY4Bk@mi>7Y3G)oh)2Y%PGeb_&IdT#+=l*IQlyh} zxI|A^dWscn(J?P9VdHe^6fp?sG7gn1We8+ndIfDWC}I&skBcIlNZGcpy+e#_2k<#$~Q{1i=Gm#Imd1Mo}}16_0Y}QrTF4nM6xY;PQyq)pM;D^3xQ! zJ5t$r6tRYr*{!HPEMvnkuzZUL&Uy>@3NfWZ^^L>@5983zwoBh=qdiy zU^JIr<%xr$&dkbpX1AfDY5ppS-{}ohNEUvp2igpe36XZ*kChdl@*3!lnSP{kxo~}l z+Hsg?svE6H+jNRTz54UOy*q*0rbr`d{*A& zAeN?AAkvP_gGf7rFB0aW+zFq9AuN9>7dd3!^HA2@+lAPqe$Tf`1P-X&1EH*A7fy!Z zR1o6b&wV~R;R06tkk?AiJkKGnTP=7*kT=Ev1$m)geckP4MYnn4spApf+hXF^u*x)- zk)3+o-SXnK9mGqp!=Rw+wNz~S)KH-}npx05QgG1#79^6c=^ny_~WxLeu z>F<4ECjvQ>T{l7wGj1s1+J^xxW|7bQDzvMXR-3GM)Qw zbYeG9Wj8)nK~Gmls<_p2mjafyl|4DA;eba?W6%4-0;!v*vbSiyfj3Xx1|jvSPu$!x zOgEY;`|}2uiyN|DX%>0G&(jB@8dA&e= z0-ewHD?zQ#JRNDF&3QyXZ(}kTA-TPNI0V%uxN$03<@rfM7}fIuQTt0z3wC-16kQD| zIa%~-UG`g|-R*FDlnvCmn;(MZ+^?adsBQMXCyFL9xtgQUlcR9pSy@H3#qt?ZbO!`( zMLMPS=BM4cr#&4-*VJ&U{GlkH%ohFOy$i<~E(A}`b7oS_dGAqCbSEo3AS0F72idT2 z_{f<+WrGAuIw=1p%BPTfofC7oj6p(tn94lIW!R^`>Dj4-El~4NcRzUP_!};o$YfB% zQCWx%x>DvjCL?OwvYY}1HJ!*7#%j80$T%;1sc4H62e%6rE^Wj@x{JBDIdQ<-=fMGQ zIx7x(GC(H?22DM3QWYRcj9Pau8=jL-Jtz608-O?(#rPgNe;~z%m=s}(bNrl;iu2ZpNY1!*l;LaDQCXGV{HtN z*>rv($K!%&%a@W0oz3wxz0%tOkrViKG?&g-q7JuXi6UP{=a&h<<`|$HISBB1r#=Cc z>5Z*1=mRYEgtr|v+?|sJ`q|yl)i#goVzI<^UM%qtE3byu__NtLW>oRSs)XtMTK^x& z+S$q*&toH$f9JPS!61JPjx7gevv^$(A~A)t4do5P5$8(AQi(}E*w5+~AoGc*Ug+2b zQ(MUDIOpta@*anBnCbhuasItSxVMhvl79-}Fw-?)Xy}S1XrgxjzOqQ^@H$gV5f54qXgBSRQ2w(-9I}X4L zwk-DuU?mb=0NC({oW=4E`rC75Y+og;QuCj4LT@@B`bYaf!i`xyxQrbZr}Oe2J<}bR0`;FHuqlM)#_MRP#zl< z7-uve2R5nWyltq{FMe#;Tp%R~q}WRyXZj&rB0ebIY1qA8tSv-sP0zlHH7$XpIs8w5 zL|Tp$CzUO9|0Rl_WOE`86K1SGIW3f(5cnV_f|hOed1>EP-@Bs4N>0r@ zw*4`8KBwkIPPtrQzKYFd9P^zq%n@NB=&{2oG%$=HgoXuWsxG&HxtYgqO{@2%Q~UOT zub}hDa7D@R3@d%#*M)j@3e*8wlpTdW%hF7WR&4P+Zwqewa;d;c5IA=WELXF+uW~$& z#PE2Il|JM4(^ZbZF6{S{v)fVYHo9+-Tn3q*X8tx55 zB!ZZk0Mk|4o-BjqlnsZbdE7JJ)snQ9wx@B>1*R!tKk3#n{R}rOR(b|bpzT`T_C;>> zf{H~~fir#ko9`8Y;!CXbSD%e?`Ujx*xgb>bp(E#GNW8o(%`YrpPCz9A;gTR+o)#@% zVU?@A9qEW!HeqxazJPHuQSvH_40SnK$spH~%eiM8LHHWxvAGH<)h%QHx?ukDIF1dn zE19$92m;!0YnvN^JIlMzV_~2BAdEFtX8S0mrGJ4K<24p(;}=#koMf&1QIfsR8n1C? zu#`fNkA{s8yoNn)G>n7Feuyo!^|ZYQr$N;&uZxkoa-X_84$V|f5aO9fT2ND7nmFhJo6QqSTrn;(Z1xtlFJ-e(2Nl#r@!I|mHhYZ%CbphGao^E%vb;kG z8KR#$s$S^&vFg{HXN93@$@TsUR@7dR zKrk+i9uRj@^+wVCrKY8W#V5Oqs<-f>3p{s_1V?FK9|`d$DqnWmxRilwm0>5Gb_RYc zaCnCmH#u8z*|Qci_%J%t-BdCOl;E7VALf533f)5`Q#gJ8X209azJ3a6FO^IawYeE7 zx3m#dc$Z4<5w$Pny+)KDkCyMFl9{6XqWvg?{itaA9;?5H@5O!}_Q?HI@_?up(igZL?0*CQGMZ*YOxMmfhpDp@S*Mnv6n(Yo(b$)f_wjYpRictr;v zP|0!u+$^fbN1-25$rA#)RY28I=toFb2%g-tmM;JpkBSaHrjlnwRc<=U3R0t0KcSK} z0=h>)GosK#RI-jkmlpfrCHE;yBM)>BeVl0<3Vb!V?-}Md@TGE?^dHK{AlOcqJM!nx zNPmbw7nr=_`3NL8`Qv{tnGyv%UncV_Bi&KbH;69EqFsEBFU8fBFUFQMe1;E!&yJD) z8_}sh+Gzuo>;-N@_#_CQ-yq>U>6=8A?$Ih=K=ASPr$GyRn~@)i^k+q3-)JH9+ULac z!07Xd4SEm9c}VmTy6kxITo8SRDqGE;%krIKPT!DTC(1`f%fCfTR`ERA+mY+mzLUFp zSz*}MgByt!fajicP8Od5g15Hi6(Ltky(dOp_I)pU$5TobW+kc+AaLWU`X=T0~L#_Lx8}_V!-z!4W+m*m`Wj(Y4p>JE}advYApIn8<%6ZO445DEN_jbvkvP@^0 zl0o$e&y=`iByq=uA#yqSvBPLDvLN_K!ke5|(6Qogg{Hs{Hj<2-_MWRQTDs&bxSh-0 zq8@a#E9BM}MZH3tD%{Q_XguUQM0rY7)RVjOfl_I57iTtz1dS$xnafT-QrhKW9j*(n z>hIspqcME`q8}-ZZkMrXUbt3zRdfn_*a3Wh9Kt)zV9mEHph+y1Wd#+y8NEg#9WWZ8g{-YzeRlzqTOZD8`aooso-R+8dR`-{B4i| z9Y#Ko*Q_XNiWM3Dc14IbHeTc&MGBg^*uM}dqX-TNyuWj3gvFI?mM6%FGg$9TU*5qT zPo0+nOPI}iI&t>}J=_;qUJjSKpYYFv#Vi-tcx&Wa5=61l8+Duy2!{h`H8BE0GN^Ju z$6ex0A`_OV=-{eN-ZI1cs>EJ7?^Qz(O?FNQa=}f)Yg+FS@?x?Jd0gb-0F=ltiV}M+WoELzBF+dlJNFI*CkQksVMN|S%5Cb46>tQZTjqo->YjWvL;wV9x zt0Dq?^^EI;B#FiTqi z!7K+;WT+@z5i1>n)Hnk0R17ebBG&;R7z}~Ih&#~aVTe}Dp#^KRTT;iC@{>zDh^fzLlrjEA-gUYk21tC*Rk?d*}V|c#IuYJT^n#JrI9dn6`k#jbq1@|*}SuY zQco^*%JAPK8jhk!xu~`;R_z8Dw+cW#02?O;=EWz>P{loRWC%P8d$M;X2WD%=JT@S7 zFVbAMiH}Js{;Aa!YQ1bjh8)^9p+>$;pKLC?~p22#FDmDd# zG7m}2fsc<@8jMUh4+RF|u6Ir#m5(=s0fWPQgh}k+fBO zK0ZR$^q`H_mUVRk;o1F?Yi1uReLO%FC|WJ1SjCEu1)f2mi1-HnqV8jb8{uYyRt7%y7BRb-QIg=fK}<1`4{(2!1enve z0)a0j7^s2@H!CRhaNu!4-q|d_HrR`0)di7Ob$%d;lg9ZE1is;&&0#~@1(DkOY~XpW zs%$(S?2L0vIDjn2^8?}>Q-ko5Bdxrjbj71$--;{r~DY)aMSf|y791e56%L-0CDg7xbB zfRPWQQ*iv=la^-%p|OY|_=Z66VOAZEA{?cvnq&D02YUIer(N<^`D>mgD7uK%%@2If zDGb(w{@VUcQr6Te_#y9OF_Vr4VAT5yzOZdS2YM?waqpy`y9HZFi0%s1I&;*nRq%-{ z6x~)(<<8(#7(gBg!2b~>X5aM*wv#F;uOU#YWJ6TJ?!4zmue8h`?r#TUm#S5*b zw7g&%?0a?x2eO*ygBqGJKe$q2%Cca3^xcE%hmc_N-9Ug(#lZgy{=xGDbEM^=p#S3f z*d)4J&2d4Zl?z`TQccDsKgZIJ2d42w-GXK}1!swxYgi-{*vzn-w7s|{7Vt^5pij00 z?XZi(zD4-%(=cXIXy+uVw}J2Zyrnw*%YyZ9y3)a7Aug+PNy&3yG+3 zgBZkJU{+ahks!iaDs2klo4PQ=YvR{+^?0fBcwo5lAJgI!;IaV z4_T_BQ_HLk;*J`DXu|<6Uvj zPdw37arfZU&=f5UjNxG8*+2ou-b8mo$t2gwxX`4+`USKvz^!{cLl2$23)c$a<%lbEf*P$ z--Uq?a9KPccN#n2FSk7_D#(pzRfgOIs&(b|QA+zLUe5hXsd^9dn!swh=XNBsKlgJe z6%Jh$Pm>ZXEqAo!v`FI3 zEm!P%Ew6gTo0hgHv}lXV|2LlSx#9G)$mB{$V}kmBiZhUB2}4@*%Lai0{) z5W_JJ67p2PNWw{YfON@hKh=0E49YTyWBHU^t)*nvAxAoG z21V#pFhn_BrOvGP2FguxD+1WdPPV3czTr407cIH?ddX_+ODH+7D7 z7fMM~J`U5jWQJsC-pP29?Z4eKMoQhfb)) zl%Q(!1??%5rm*Q{(tNXUnX+GzO4-Zh(la~(KYf>}Ik~G%ooH*d=?#RFVs69MNk5s8 zihtp>fHI12N?)6WuMp)AXL|F^{6F9XtE2@2Te4l6Y!beq@G5CBTlTh;&t;k_4uevu z+ags6?7erT7ItyE7!ugSD_r`hrZl1nwZ7WaR+??baqdIuxcDL7vj$2pRHsl@XZ0}D z_zZ~(Kax;3&K8Ti?zUJ9#qUyLSLDbqv5xztPXx$0rO#HEq0-<-5-JUThgcsam32KL zUAJ;=@#CD$@=?Mq#m=UjM32RKJmNYEkVw&X%ip2uz+)PMUvRN;7o-vsS9cHp39S2N*GozkDBE1}`HH%y;hz^?r)IR)`YG;xO#536UG z`;>GR&Zug&g6fCHTge$N_n`H6q`{#fX`^xxJ-BDH8cWA(mA-!jG==Ro$v(jwip0f6L)k+Xd6!i%)AomyKH!bb$ZOancV=sv$|HDuXnBg9LtlO=b!V%b@>s6H zlwYX)!YVt;Gf=c5MxHN-q3-< zt$rhvb_!Q1tBt%(G@;fOQa8pb2!rs*d(B9yPv0ir&mL?iS989w$S*%gbu;BqmeoDWNj@;~91yP#exD?Zxt7&fNn%{}b;1M;mk5P^4{P{*_UY4S3w;HK=p=EUH16Q=uZy0%wZPUT-o4z{#Z$-^{p^_wo z%MY@$59AT>&3i=H_C~4)sFt!%xD4XNrk!UB;Lj(y9M_@r0>K=1d;WC7e`(qh`jh+S2{69LDi-s z@*FcrIajO-(Uw{)SfM-RpHfokWrq^R^po;Pn}`_Xe@^COIiJ$c$+}&7eR=>_ppT~V z&t77!5@i#2>^e|p&mpt$kqU0emFMMlY|{mKAXiCp^;X~+x+udl1eN#P_^#XZoTMp87G8oN)($PqYQ&{IUu*Cyc==}wBAp& zQsZdbW!b?}f0PmD=Ob5xBr)x#d=R0G?1ZeOnkk}dDM>2(yITpo-;*!GY_`Fd8ETUJljKlNB$L zCk5Y&RF3&FTO6rO5l|F0d$$@7yFQLBj#j3aAp*arrP`b7`(fD9ew2riu|v&Z6Iv+C zO$cnPI4bR@!UT0B)nq8IvZHZIhRM_`ofqtW^5TGAW+4v{LgIdF~E%x=^q?shGBNvgr-m$QFaH5usR;4#LxMuEK-SJN-qV+x5ubx zo07z5b0VVu`zeV2A3&}-Y8zJAQYpl%yw?~@{?#z2%^y|17NEJ5`z11y^9Cp-oVP39 z@4_ADs!55&SAQFfUV`uhW+jD7K7U=yCEJt^)C5jQy z?HIw(n?&dFO_4k|2y?4au?<@8WpH z7JQ8e10TV)U9J`xi1i93E9Jy4*wtU#I;Hw zl2?l3UwnYOZXDzBchWt8?E-Z^uKXK8i9ieah*|~m5mTUTq(Twoq_W)#dM@9mB1zq; zT)&Gpp4~jBK)h}mqMpak=Z_d)Ng+%>v=UB!>Q)5>Wj*QR{P(B71+|cq*L}M z(x!WlN`JA%@hERc0TsonAYI-uw$akQ%2Vw124$#8m`Sdm|Ptr^suYo&rXOpA&*%oizlrJPAMdz7tI zF3PcdDLnhfCESv$!pIBVwqs5w*_%NT3kx5PLV1z{OUd~=Tuy$q_J*2)#E%=F@IJ}H1*#YlYnHj zm{7`jL`A4YZxu9KNl8TvZa95a+;Emt{t7t;cU1Lp2wTQF*{m#eHkW&WC`VCO4 zU?r&GkebB?3{p|mWhJE-$xy+|Q4tKXii-ZF&Y~s#a0NjxZzAqzC623`=AcRcDfKhT zvC6}$#;7esF8zGqe!`+S&2EfWn+f9Til+lD$u&W}Dwd)vWt6^HhTvPCT4x5W2Ro}% zAt^bmVzN5SEW8T$hkD0qE$1&scZrG>Z6UeuNKVF9s1_`wt4c?T-J(vVIx7?%oVjWT z_Q4i)cJQoJ!79F#h6XpjFsi9k7*$&22~_!lavOJKMWOf0R8DO=*0P2&H48h)esv}# zyp3MeWGl%me7@Q{RD|#ISZCgYxfH+89f4kBckr!OAs)T-l2lFS+sIbhR-$H!YtYm> zO5cdx1^)+KT`h~O+(UWS%gC|W^*9_ z;!eu8$j5?H%4+|ub`3=~G|ts1gK<{<%4~lJHO|twG%ytls-N|(Q<+tGkFuvanvv1l zF`X^DtiE6s+AvqWx?HgBTK;e#35C;nAx?vR)u2Y3#CP$OYSQPr|H<}$uj*#h)6^Hb zUox>dKdS{?7pgEJ83fBs73Z@WO5N&yb>~_wm+L~sx6%Gbj#ch!?BAl+8M08cy_J?q zLsPU0cG9M$nuFCfHB=GZ2aVCT-Kg&YcO@PBK^;raz3#q2sW;RU5S4b@$XM_GFYoPs zs#xR(9~P^D4?93bDO!7G6uUuckG-whpe0iR<->V*)aN;l&-rg0B*zN3L~()^MMJl_ zE7&!+7R|Xdj*EaxWu4k;5YRhBd(6o`)}x(PV>TatU?An6kBX-H$6aB}(^1Q@iyr77 z&Ewm>&h~fL6qC3Y&7(H!g9;SLe!qSXVsWa5s(8mK{S)(}+_(p=WSqcW(zQYpB%plV z)Th}{zc$w{LLByBQ@BM{mi(}G8nr!3`)WPKWJ}268TeFYMSZoa7DPwnWouD@YUQcSN_EQTB0_`jzs|xT{iLocOUMBG*5PbrOtxKUJ7k;+NWP|T<-4w4cXbnE4 zw3l05Vc+CvF`Oy${}hW7B_lLgz5kH^RxB>CV>Db~&yZhek;vBEAD#=h<81FB%IO(C zf$j@7kFK&NKHEO|yzfV_m`v&Sp?)ftF@JU)nwZ3Vrk6 zg7`vbY9O4>RuyVElwTke%Js1J`&+yqu=N3LunE-I(pfqQJgY+Q9O8=l^S@NFQ~$jfYW=KcG_&ejKz*MKMc`t ze@l3e?Jw6}w2GZEcRP5+*$g^%O$(z%Z)&fKUv7~Domkoi?YK$2h(~=zw|@*j#Exy& zx^o81cPo64iB|8>Ty)jpXtKiXNWxYJ)}Z}20;rZNrp{Ej&Q(MkPiWX$Lmc0bYolc; zTmH5-fwN>oelej&;XB$)vv7?zm>o#P-=)227NB1KzJ@bijdqsD$<>89#;|dFHR5q{ zbqt0sY<7kS9d5gd^HTv+4VRR21oG$r5U{i&o(} zd;f}Su)uOpYikh~N6xM$o&1@R)$I0JO*9KXQudaxk(4(JT0rCLv{K|#lWRNFUm|9N z!APgrI#JF`VWXJ+lD5`nN#oZ%+>bNZ%nn{$TittX?YT|<<;|`EBsJBJ#e{a}!tBQ9?@6P>xCrI;MIDSxjA+9G|E%BV0V$wH2hQ~|DY#|jIOr!%zsnE-9CgprFz%Hcd#chlvN7e$4sa^DNIiq zhf9g*EeQqlmHvP&lRx@Ds7c}QqJW@qd9NG5P!nGK1j?|C%g z(dQ|*xpyr#&WdC9D7yNK7K!7366l4ST0R0yKelr(Py{MYYDxD|j_Q4!&2H_T#mi0# zKexfD*f}wAE{BsZu+TPM-72Qz@Z#ko#7FN%fwN^1P1b(}J|b^*;y|vij6k;EyxFeN7M|*zq`TI=tlz664uphPPX&*adf|AVva48?7D<#l@d{ zdPOsj2aSkig6QcF?&ZA%H>g`i;+#N?7P?)Ct+dj|LOLK8BYHAH$Ngg@eK zG{bzDdGvWiGbj^#^T_*)w)Zafh1~xW5kn0vz1dWl+SdI?{N?{Y{jGz&Y|a|u`NPng z96H{<%@O7s;#ET}IK{wHM2B}}l_R`RQ*2$_KhWJ0@)|$ucc-4Cyld?(=Zpw+gN@>` zDc#9~PTN|%8yY~i^0$uDmI5l*X$GC2PWUW>3O9LY zi7A+-``C#j&wcQydOVY17amG##wul~%2IuqhbK=*bYv$w*i*4|1~mOw=`F8^b=cv> zzSfCyCm5&+{4m^= zI?Y<7R8T>LKAUpRhc=;)E}3FzO%Cdq4&CQ{Jybx<<3{gf=3eEggXj8^ZM~S*4LE3f zSktX+d#(3T&fVoYJUF+X^y1u}!Ez(Hf_j=o4=VpfNGvzTGf7}!)!uo*r6^NwOGrYN z)_KR9O-c$SKJ4g>{0Y4&^Ii1r+um8AaPD(D-Kj5%X?S8Mu zUjN!#UceUL8{I3D1R4Bg(54#^S+vpYjAWx^ zeY;Kc!}r1sOB(B&slR0sxt2pV?a}o(%2{l(($Ij(%2IpkAW-i|NEko5q_L0s>i-hN z$Cf=7$Yd3d=tBe%k{A&KXhBbXDw~m|PiiXkU{?p}F0=4>RgV5xD3_`%<;y*d%^jsb z53;LjjE=~*2P`2AxYTz#>-~%lX_tqXBVW%EY=ahvfd}y`d{gVd$|veW1(B;#3@)`d z`;?uXti#bdgo-P4xW}jIpIJdu?OvtB=UJ%3=b1|vR_h5gx~nUKE^X3VQRg!K9VQ0! z+a{1x``75WhR)S-4INJTAJ`MA{2zJ*Ih!?)W7!M!#de_=t6QNb1oea>ct9B+!O?VT zla4sVGJSXW$IbV#@hf#d?r@(+hqI*{^hqYf zgyA>GKx}o3KE^8c0Ts=EKbh{ljrPWuJrQ^qy5U`TlY}Pi?C4Iti%EP2?LNmclPxxZ zHGNmVVFHV^=d(DCvi9k7OKM`XSo3QAYZNM^J#Qw|vXeFX?r^am4A5k>1^!x3T z=1mEJbt!r7_m5iBL&x=Z#C)_pnJSpQ86D3F8_f!9^;=DZN7xHz^&+!41wWmJnThLy z%dxv18&apQ`&Fh}pK25PTSBUTZEvZ|Xvy;Q#Dz#9`&NGoWK_iS_*=lr^~6Dl_xV=G zem0Xe)ajoH?931P&QJtN7T?s5n{2aIH3>ksYX|qkwIwv) zQ!anCCKS!LU;|`hlUZDdW|mOqn6^IZq_nSj!>k)-`_kdVyAPH#y29gL+_p9Kxf0uy zT90Vkl0D)#5VBiL7k<*=>q?HWviP2c-v*tlp?!@TCR<5`EARxC5V`X8XqwpHSR^jS z1C~><&jX&TjR$Xq$MX+?It()cpiI3@+4`}@Q`myPu)(7g5`nO&o}Pg$XPf~Yx$^N% zoy_r^@hz?nU;X;?QmPp48A`Pgo)lV~XZ(kac+yy7f_(Ocr1sc5oY!q1unU8YBoio> z{0RF3%K2xqM6+y7>+c%O77PzRE%JJRZ9EJ93eR8x^#QXNCxcEHXS^27?#xS`&+t6B z9bRGiJGsuFbBeJLa#{3BfiYHGvt>}=N!E0x@m)~uI-;B|EO)f0np4IkR$gcfHwzV% z{gm}F_QY&svsqZnD&A4B_odD=PI4XWqIaW^LA=m-%POoRXGYXRtj}@-6zqD+d*0lf znzo28poAA={>!GUGEn{U4a!~I3>Vijy}j4Aob-R4`v5yj4jr%L6(qnW4qtMAId*xPiNZ>8d+v>Gado{;@j4^G<_Y98d~*7 zjVKd@u5+)N$FK=M8pUSounYy?+DW-{TEgbf@NHwYCylNsyjOCfKjdPQ658zS=X=X+ z&FGSW@39pw%Lq2+tbrTdJ9NPt4N0>)WcoCdeFSjr!e7Mv#M{#Tf5^gv5Ksf}9+V#uP@ZPzJicTE_1uH-@ShNN@&VzI z#t4r#M%c}VK|9tMq3rvP3zqH5^z|@_`vL64TSd{cQP!)#*IcM_xO0OCl8-5WtbZ?C zGsu4s!ORc);4^{L#|+;w!bC1+IsN!!Pu_#>-+w|E9KTbI(S6bwU2sZ2ZHz9*H^nOC zGGmNynh^G%BXNP8d&jaCkk3$h58vUp3ZnzR;VyN!S8yYAmeQ}qAE({x5=Qe$-ya;~ zbExF0LrC!#qmcYFe8>4(92We^f5|!AUr(Kq5& zNc3;Z*Tx=x(+9WnSB-~%GhZCTep?)$VL-3`CAe#U0#|=8xQ2Vcef!tozH1Eb`@7(N zf8BrhBRHvv_zgh)_?Mu5`V*+1?*;YCJ)my>HK<#SLEZins5^~8@z;sHf6mv_B;J5Z zn(42gIn9lsHTA<5S?+=66z@V~{i6LaMvz#)#~8KnV})6N!}h%o_R-cDo7<09-QF16 zON z6V(_`Uw^$VbQFJ~{l6N=CL<)=)12^2I%C-Oy10G8%>;v(m2BqO2hQds?AJ5-tDIzF zqlfs*1OcMQtr8MS4<$T8=NI`E{xX9{d~co~<*F*fECy8$_djNCfhr8yAKH?F+YHqT zAdmeQ65vY+akiwQgar8YExrM4))@b6R7XP!i0>Rbv)rG+<4&EeS@vw-c!52;%D;r$ z1ZUi@y>K2DQ&^wL{`>91XtsE!Ki?!a!%y98``7Wt@6c;r@NMP4gH$?eljz?pXkEbH z1~tss_LD|c5NvOg(1seOC&v8|pojma2O$u~WCFHo*1gydk91qsxz6|)G2bs4oQQHJ zv(DXon-Dp>%0Ivar3u%G=$7<5x@v)$UiDk7p<^)8t38Bm=oOasg8vBa>Kp8#C6&=d Iyp4qa1FX_Rwg3PC diff --git a/test/join5.test b/test/join5.test index 85e09c57d..44c8b71a8 100644 --- a/test/join5.test +++ b/test/join5.test @@ -302,14 +302,34 @@ do_execsql_test 7.3 { ANALYZE sqlite_schema; } +# If both sides of the OR reference the right-hand side of the LEFT JOIN +# then simplify the LEFT JOIN. +# do_eqp_test 7.4 { SELECT * FROM t3 LEFT JOIN t4 ON (t4.x = t3.x) WHERE (t4.y = ? OR t4.z = ?); +} { + QUERY PLAN + |--SCAN t4 + `--SEARCH t3 USING COVERING INDEX t3x (x=?) +} +# If only one side of the OR references the right-hand side of the LEFT JOIN +# then do not do the simplification +# +do_eqp_test 7.4b { + SELECT * FROM t3 LEFT JOIN t4 ON (t4.x = t3.x) WHERE (t4.y = ? OR t3.x = ?); } { QUERY PLAN |--SCAN t3 `--SEARCH t4 USING INDEX t4xz (x=?) LEFT-JOIN } -do_eqp_test 7.4b { +do_eqp_test 7.4c { + SELECT * FROM t3 LEFT JOIN t4 ON (t4.x = t3.x) WHERE (t3.x = ? OR t4.z = ?); +} { + QUERY PLAN + |--SCAN t3 + `--SEARCH t4 USING INDEX t4xz (x=?) LEFT-JOIN +} +do_eqp_test 7.4d { SELECT * FROM t3 CROSS JOIN t4 ON (t4.x = t3.x) WHERE (+t4.y = ? OR t4.z = ?); } { QUERY PLAN diff --git a/test/joinA.test b/test/joinA.test index d6bb678c5..04d1e68cf 100644 --- a/test/joinA.test +++ b/test/joinA.test @@ -210,5 +210,68 @@ foreach {id schema} { 18 28 38 48 - - - 19 - 29 39 - 49 - } + + # Verified by PG-14 + do_execsql_test joinA-$id.201 { + SELECT a,b,c,d,t2.e,f,t3.e,t1.a + FROM t1 + FULL JOIN t2 USING(c,d) + FULL JOIN t3 USING(a,b,f) + FULL JOIN t4 USING(a,c,d,f) + WHERE t1.a!=0 + ORDER BY 1 nulls first, 3 nulls first; + } { + 11 21 31 41 - - - 11 + 12 22 32 42 - - - 12 + 15 25 35 45 - - - 15 + 18 28 38 48 - - - 18 + } + + # Verified by PG-14 + do_execsql_test joinA-$id.202 { + SELECT a,b,c,d,t2.e,f,t3.e,t3.a + FROM t1 + FULL JOIN t2 USING(c,d) + FULL JOIN t3 USING(a,b,f) + FULL JOIN t4 USING(a,c,d,f) + WHERE t3.a!=0 + ORDER BY 1 nulls first, 3 nulls first; + } { + 14 24 - - - 44 34 14 + 15 25 - - - 45 35 15 + 16 26 - - - 46 36 16 + } + + # Verified by PG-14 + do_execsql_test joinA-$id.203 { + SELECT a,b,c,d,t2.e,f,t3.e,t4.a + FROM t1 + FULL JOIN t2 USING(c,d) + FULL JOIN t3 USING(a,b,f) + FULL JOIN t4 USING(a,c,d,f) + WHERE t4.a!=0 + ORDER BY 1 nulls first, 3 nulls first; + } { + 11 - 21 31 - 41 - 11 + 13 - 23 33 - 43 - 13 + 16 - 26 36 - 46 - 16 + 19 - 29 39 - 49 - 19 + } + + # Verified by PG-14 + do_execsql_test joinA-$id.204 { + SELECT a,b,c,d,t2.e,f,t3.e + FROM t1 + FULL JOIN t2 USING(c,d) + FULL JOIN t3 USING(a,b,f) + FULL JOIN t4 USING(a,c,d,f) + WHERE t2.e!=0 + ORDER BY 1 nulls first, 3 nulls first; + } { + - - 12 22 32 42 - + - - 13 23 33 43 - + - - 15 25 35 45 - + - - 17 27 37 47 - + } } finish_test diff --git a/test/joinH.test b/test/joinH.test index 78d155629..edba26de2 100644 --- a/test/joinH.test +++ b/test/joinH.test @@ -120,4 +120,16 @@ do_execsql_test 5.5 { SELECT * FROM t0 RIGHT JOIN t1 INNER JOIN t2 ON (0) } {} + +reset_db +db null NULL +do_execsql_test 6.0 { + CREATE TABLE t1(a INT); + CREATE TABLE t2(b INT); + INSERT INTO t1 VALUES(3); + SELECT CASE WHEN t2.b THEN 0 ELSE 1 END FROM t1 LEFT JOIN t2 ON true; +} {1} +do_execsql_test 6.1 { + SELECT * FROM t1 LEFT JOIN t2 ON true WHERE CASE WHEN t2.b THEN 0 ELSE 1 END; +} {3 NULL} finish_test diff --git a/test/json/README.md b/test/json/README.md index 6a1611492..55044ffca 100644 --- a/test/json/README.md +++ b/test/json/README.md @@ -1,5 +1,6 @@ The files in this subdirectory are used to help measure the performance -of the SQLite JSON parser. +of the SQLite JSON functions, especially in relation to handling large +JSON inputs. # 1.0 Prerequisites @@ -7,6 +8,8 @@ of the SQLite JSON parser. 2. Fossil + 3. tclsh + # 2.0 Setup 1. Run: "`tclsh json-generator.tcl | sqlite3 json100mb.db`" to create @@ -14,14 +17,27 @@ of the SQLite JSON parser. file lands in the directory from which you will run tests, not in the test/json subdirectory of the source tree. - 2. Build the baseline sqlite3.c file. ("`make sqlite3.c`") + 2. Build the baseline sqlite3.c file with sqlite3.h and shell.c. + ("`CFLAGS='-Os -g' make -e clean sqlite3.c`") - 3. Run "`sh json-speed-check-1.sh trunk`". This creates the baseline + 3. Run "`sh json-speed-check.sh trunk`". This creates the baseline profile in "jout-trunk.txt". # 3.0 Testing - 1. Build the sqlite3.c to be tested. + 1. Build the sqlite3.c (with sqlite3.h and shell.c) to be tested. - 2. Run "`sh json-speed-check-1.sh x1`". The profile output will appear + 2. Run "`sh json-speed-check.sh x1`". The profile output will appear in jout-x1.txt. Substitute any label you want in place of "x1". + + 3. Run the script shown below in the CLI. + Divide 2500 by the real elapse time from this test + to get an estimate for number of MB/s that the JSON parser is + able to process. + +> ~~~~ +.open json100mb.db +.timer on +WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<25) +SELECT sum(json_valid(x)) FROM c, data1; +~~~~ diff --git a/test/json/json-q1.txt b/test/json/json-q1.txt index 0395f0c06..d122a2d82 100644 --- a/test/json/json-q1.txt +++ b/test/json/json-q1.txt @@ -2,3 +2,23 @@ .timer on .param set $label 'q87' SELECT rowid, x->>$label FROM data1 WHERE x->>$label IS NOT NULL; + +CREATE TEMP TABLE t2(x JSON TEXT); +WITH RECURSIVE + c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<25000), + array1(y) AS ( + SELECT json_group_array( + json_object('x',x,'y',random(),'z',hex(randomblob(50))) + ) + FROM c + ), + c2(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c2 WHERE n<5) +INSERT INTO t2(x) + SELECT json_object('a',n,'b',n*2,'c',y,'d',3,'e',5,'f',6) FROM array1, c2; +CREATE INDEX t2x1 ON t2(x->>'a'); +CREATE INDEX t2x2 ON t2(x->>'b'); +CREATE INDEX t2x3 ON t2(x->>'e'); +CREATE INDEX t2x4 ON t2(x->>'f'); +UPDATE t2 SET x=json_replace(x,'$.f',(x->>'f')+1); +UPDATE t2 SET x=json_set(x,'$.e',(x->>'f')-1); +UPDATE t2 SET x=json_remove(x,'$.d'); diff --git a/test/json101.test b/test/json101.test index 543e4c71e..4da1d132c 100644 --- a/test/json101.test +++ b/test/json101.test @@ -122,14 +122,14 @@ do_execsql_test json101-4.8 { # json_extract(JSON,'$') will return objects and arrays without change. # -do_execsql_test json-4.10 { +do_execsql_test json101-4.10 { SELECT count(*) FROM j1 WHERE json_type(x) IN ('object','array'); SELECT x FROM j1 WHERE json_extract(x,'$')<>x AND json_type(x) IN ('object','array'); } {4} -do_execsql_test json-5.1 { +do_execsql_test json101-5.1 { CREATE TABLE j2(id INTEGER PRIMARY KEY, json, src); INSERT INTO j2(id,json,src) VALUES(1,'{ @@ -257,7 +257,7 @@ do_execsql_test json-5.1 { SELECT count(*) FROM j2; } {3} -do_execsql_test json-5.2 { +do_execsql_test json101-5.2 { SELECT id, json_valid(json), json_type(json), '|' FROM j2 ORDER BY id; } {1 1 object | 2 1 object | 3 1 array |} @@ -268,13 +268,13 @@ ifcapable !vtab { # fullkey is always the same as path+key (with appropriate formatting) # -do_execsql_test json-5.3 { +do_execsql_test json101-5.3 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' ELSE '.'||key END); } {} -do_execsql_test json-5.4 { +do_execsql_test json101-5.4 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' @@ -285,58 +285,58 @@ do_execsql_test json-5.4 { # Verify that the json_each.json and json_tree.json output is always the # same as input. # -do_execsql_test json-5.5 { +do_execsql_test json101-5.5 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE jx.json<>j2.json; } {} -do_execsql_test json-5.6 { +do_execsql_test json101-5.6 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE jx.json<>j2.json; } {} -do_execsql_test json-5.7 { +do_execsql_test json101-5.7 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); } {} -do_execsql_test json-5.8 { +do_execsql_test json101-5.8 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); } {} -do_execsql_test json-6.1 { +do_execsql_test json101-6.1 { SELECT json_valid('{"a":55,"b":72,}'); } {0} -do_execsql_test json-6.2 { +do_execsql_test json101-6.2 { SELECT json_error_position('{"a":55,"b":72,}'); } {0} -do_execsql_test json-6.3 { +do_execsql_test json101-6.3 { SELECT json_valid(json('{"a":55,"b":72,}')); } {1} -do_execsql_test json-6.4 { +do_execsql_test json101-6.4 { SELECT json_valid('{"a":55,"b":72 , }'); } {0} -do_execsql_test json-6.5 { +do_execsql_test json101-6.5 { SELECT json_error_position('{"a":55,"b":72 , }'); } {0} -do_execsql_test json-6.6 { +do_execsql_test json101-6.6 { SELECT json_error_position('{"a":55,"b":72,,}'); } {16} -do_execsql_test json-6.7 { +do_execsql_test json101-6.7 { SELECT json_valid('{"a":55,"b":72}'); } {1} -do_execsql_test json-6.8 { +do_execsql_test json101-6.8 { SELECT json_error_position('["a",55,"b",72,]'); } {0} -do_execsql_test json-6.9 { +do_execsql_test json101-6.9 { SELECT json_error_position('["a",55,"b",72 , ]'); } {0} -do_execsql_test json-6.10 { +do_execsql_test json101-6.10 { SELECT json_error_position('["a",55,"b",72,,]'); } {16} -do_execsql_test json-6.11 { +do_execsql_test json101-6.11 { SELECT json_valid('["a",55,"b",72]'); } {1} @@ -352,7 +352,7 @@ foreach {tn isvalid ws} { 7.6 1 char(0x20,0x09,0x0a,0x0d,0x20) 7.7 0 char(0x20,0x09,0x0a,0x0c,0x0d,0x20) } { - do_execsql_test json-$tn.1 \ + do_execsql_test json101-$tn.1 \ "SELECT json_valid(printf('%s{%s\"x\"%s:%s9%s}%s', $::ws,$::ws,$::ws,$::ws,$::ws,$::ws));" \ $isvalid @@ -361,23 +361,23 @@ foreach {tn isvalid ws} { # Ticket https://www.sqlite.org/src/info/ad2559db380abf8e # Control characters must be escaped in JSON strings. # -do_execsql_test json-8.1 { +do_execsql_test json101-8.1 { DROP TABLE IF EXISTS t8; CREATE TABLE t8(a,b); INSERT INTO t8(a) VALUES('abc' || char(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) || 'xyz'); UPDATE t8 SET b=json_array(a); SELECT b FROM t8; } {{["abc\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#xyz"]}} -do_execsql_test json-8.2 { +do_execsql_test json101-8.2 { SELECT a=json_extract(b,'$[0]') FROM t8; } {1} # 2017-04-12. Regression reported on the mailing list by Rolf Ade # -do_execsql_test json-8.3 { +do_execsql_test json101-8.3 { SELECT json_valid(char(0x22,0xe4,0x22)); } {1} -do_execsql_test json-8.4 { +do_execsql_test json101-8.4 { SELECT unicode(json_extract(char(0x22,228,0x22),'$')); } {228} @@ -385,331 +385,331 @@ do_execsql_test json-8.4 { # String values are quoted and interior quotes are escaped. NULL values # are rendered as the unquoted string "null". # -do_execsql_test json-9.1 { +do_execsql_test json101-9.1 { SELECT json_quote('abc"xyz'); } {{"abc\"xyz"}} -do_execsql_test json-9.2 { +do_execsql_test json101-9.2 { SELECT json_quote(3.14159); } {3.14159} -do_execsql_test json-9.3 { +do_execsql_test json101-9.3 { SELECT json_quote(12345); } {12345} -do_execsql_test json-9.4 { +do_execsql_test json101-9.4 { SELECT json_quote(null); } {"null"} -do_catchsql_test json-9.5 { +do_catchsql_test json101-9.5 { SELECT json_quote(x'30313233'); } {1 {JSON cannot hold BLOB values}} -do_catchsql_test json-9.6 { +do_catchsql_test json101-9.6 { SELECT json_quote(123,456) } {1 {wrong number of arguments to function json_quote()}} -do_catchsql_test json-9.7 { +do_catchsql_test json101-9.7 { SELECT json_quote() } {1 {wrong number of arguments to function json_quote()}} # Make sure only valid backslash-escapes are accepted. # -do_execsql_test json-10.1 { +do_execsql_test json101-10.1 { SELECT json_valid('" \ "'); } {0} -do_execsql_test json-10.2 { +do_execsql_test json101-10.2 { SELECT json_valid('" \! "'); } {0} -do_execsql_test json-10.3 { +do_execsql_test json101-10.3 { SELECT json_valid('" \" "'); } {1} -do_execsql_test json-10.4 { +do_execsql_test json101-10.4 { SELECT json_valid('" \# "'); } {0} -do_execsql_test json-10.5 { +do_execsql_test json101-10.5 { SELECT json_valid('" \$ "'); } {0} -do_execsql_test json-10.6 { +do_execsql_test json101-10.6 { SELECT json_valid('" \% "'); } {0} -do_execsql_test json-10.7 { +do_execsql_test json101-10.7 { SELECT json_valid('" \& "'); } {0} -do_execsql_test json-10.8 { +do_execsql_test json101-10.8 { SELECT json_valid('" \'' "'); } {0} -do_execsql_test json-10.9 { +do_execsql_test json101-10.9 { SELECT json_valid('" \( "'); } {0} -do_execsql_test json-10.10 { +do_execsql_test json101-10.10 { SELECT json_valid('" \) "'); } {0} -do_execsql_test json-10.11 { +do_execsql_test json101-10.11 { SELECT json_valid('" \* "'); } {0} -do_execsql_test json-10.12 { +do_execsql_test json101-10.12 { SELECT json_valid('" \+ "'); } {0} -do_execsql_test json-10.13 { +do_execsql_test json101-10.13 { SELECT json_valid('" \, "'); } {0} -do_execsql_test json-10.14 { +do_execsql_test json101-10.14 { SELECT json_valid('" \- "'); } {0} -do_execsql_test json-10.15 { +do_execsql_test json101-10.15 { SELECT json_valid('" \. "'); } {0} -do_execsql_test json-10.16 { +do_execsql_test json101-10.16 { SELECT json_valid('" \/ "'); } {1} -do_execsql_test json-10.17 { +do_execsql_test json101-10.17 { SELECT json_valid('" \0 "'); } {0} -do_execsql_test json-10.18 { +do_execsql_test json101-10.18 { SELECT json_valid('" \1 "'); } {0} -do_execsql_test json-10.19 { +do_execsql_test json101-10.19 { SELECT json_valid('" \2 "'); } {0} -do_execsql_test json-10.20 { +do_execsql_test json101-10.20 { SELECT json_valid('" \3 "'); } {0} -do_execsql_test json-10.21 { +do_execsql_test json101-10.21 { SELECT json_valid('" \4 "'); } {0} -do_execsql_test json-10.22 { +do_execsql_test json101-10.22 { SELECT json_valid('" \5 "'); } {0} -do_execsql_test json-10.23 { +do_execsql_test json101-10.23 { SELECT json_valid('" \6 "'); } {0} -do_execsql_test json-10.24 { +do_execsql_test json101-10.24 { SELECT json_valid('" \7 "'); } {0} -do_execsql_test json-10.25 { +do_execsql_test json101-10.25 { SELECT json_valid('" \8 "'); } {0} -do_execsql_test json-10.26 { +do_execsql_test json101-10.26 { SELECT json_valid('" \9 "'); } {0} -do_execsql_test json-10.27 { +do_execsql_test json101-10.27 { SELECT json_valid('" \: "'); } {0} -do_execsql_test json-10.28 { +do_execsql_test json101-10.28 { SELECT json_valid('" \; "'); } {0} -do_execsql_test json-10.29 { +do_execsql_test json101-10.29 { SELECT json_valid('" \< "'); } {0} -do_execsql_test json-10.30 { +do_execsql_test json101-10.30 { SELECT json_valid('" \= "'); } {0} -do_execsql_test json-10.31 { +do_execsql_test json101-10.31 { SELECT json_valid('" \> "'); } {0} -do_execsql_test json-10.32 { +do_execsql_test json101-10.32 { SELECT json_valid('" \? "'); } {0} -do_execsql_test json-10.33 { +do_execsql_test json101-10.33 { SELECT json_valid('" \@ "'); } {0} -do_execsql_test json-10.34 { +do_execsql_test json101-10.34 { SELECT json_valid('" \A "'); } {0} -do_execsql_test json-10.35 { +do_execsql_test json101-10.35 { SELECT json_valid('" \B "'); } {0} -do_execsql_test json-10.36 { +do_execsql_test json101-10.36 { SELECT json_valid('" \C "'); } {0} -do_execsql_test json-10.37 { +do_execsql_test json101-10.37 { SELECT json_valid('" \D "'); } {0} -do_execsql_test json-10.38 { +do_execsql_test json101-10.38 { SELECT json_valid('" \E "'); } {0} -do_execsql_test json-10.39 { +do_execsql_test json101-10.39 { SELECT json_valid('" \F "'); } {0} -do_execsql_test json-10.40 { +do_execsql_test json101-10.40 { SELECT json_valid('" \G "'); } {0} -do_execsql_test json-10.41 { +do_execsql_test json101-10.41 { SELECT json_valid('" \H "'); } {0} -do_execsql_test json-10.42 { +do_execsql_test json101-10.42 { SELECT json_valid('" \I "'); } {0} -do_execsql_test json-10.43 { +do_execsql_test json101-10.43 { SELECT json_valid('" \J "'); } {0} -do_execsql_test json-10.44 { +do_execsql_test json101-10.44 { SELECT json_valid('" \K "'); } {0} -do_execsql_test json-10.45 { +do_execsql_test json101-10.45 { SELECT json_valid('" \L "'); } {0} -do_execsql_test json-10.46 { +do_execsql_test json101-10.46 { SELECT json_valid('" \M "'); } {0} -do_execsql_test json-10.47 { +do_execsql_test json101-10.47 { SELECT json_valid('" \N "'); } {0} -do_execsql_test json-10.48 { +do_execsql_test json101-10.48 { SELECT json_valid('" \O "'); } {0} -do_execsql_test json-10.49 { +do_execsql_test json101-10.49 { SELECT json_valid('" \P "'); } {0} -do_execsql_test json-10.50 { +do_execsql_test json101-10.50 { SELECT json_valid('" \Q "'); } {0} -do_execsql_test json-10.51 { +do_execsql_test json101-10.51 { SELECT json_valid('" \R "'); } {0} -do_execsql_test json-10.52 { +do_execsql_test json101-10.52 { SELECT json_valid('" \S "'); } {0} -do_execsql_test json-10.53 { +do_execsql_test json101-10.53 { SELECT json_valid('" \T "'); } {0} -do_execsql_test json-10.54 { +do_execsql_test json101-10.54 { SELECT json_valid('" \U "'); } {0} -do_execsql_test json-10.55 { +do_execsql_test json101-10.55 { SELECT json_valid('" \V "'); } {0} -do_execsql_test json-10.56 { +do_execsql_test json101-10.56 { SELECT json_valid('" \W "'); } {0} -do_execsql_test json-10.57 { +do_execsql_test json101-10.57 { SELECT json_valid('" \X "'); } {0} -do_execsql_test json-10.58 { +do_execsql_test json101-10.58 { SELECT json_valid('" \Y "'); } {0} -do_execsql_test json-10.59 { +do_execsql_test json101-10.59 { SELECT json_valid('" \Z "'); } {0} -do_execsql_test json-10.60 { +do_execsql_test json101-10.60 { SELECT json_valid('" \[ "'); } {0} -do_execsql_test json-10.61 { +do_execsql_test json101-10.61 { SELECT json_valid('" \\ "'); } {1} -do_execsql_test json-10.62 { +do_execsql_test json101-10.62 { SELECT json_valid('" \] "'); } {0} -do_execsql_test json-10.63 { +do_execsql_test json101-10.63 { SELECT json_valid('" \^ "'); } {0} -do_execsql_test json-10.64 { +do_execsql_test json101-10.64 { SELECT json_valid('" \_ "'); } {0} -do_execsql_test json-10.65 { +do_execsql_test json101-10.65 { SELECT json_valid('" \` "'); } {0} -do_execsql_test json-10.66 { +do_execsql_test json101-10.66 { SELECT json_valid('" \a "'); } {0} -do_execsql_test json-10.67 { +do_execsql_test json101-10.67 { SELECT json_valid('" \b "'); } {1} -do_execsql_test json-10.68 { +do_execsql_test json101-10.68 { SELECT json_valid('" \c "'); } {0} -do_execsql_test json-10.69 { +do_execsql_test json101-10.69 { SELECT json_valid('" \d "'); } {0} -do_execsql_test json-10.70 { +do_execsql_test json101-10.70 { SELECT json_valid('" \e "'); } {0} -do_execsql_test json-10.71 { +do_execsql_test json101-10.71 { SELECT json_valid('" \f "'); } {1} -do_execsql_test json-10.72 { +do_execsql_test json101-10.72 { SELECT json_valid('" \g "'); } {0} -do_execsql_test json-10.73 { +do_execsql_test json101-10.73 { SELECT json_valid('" \h "'); } {0} -do_execsql_test json-10.74 { +do_execsql_test json101-10.74 { SELECT json_valid('" \i "'); } {0} -do_execsql_test json-10.75 { +do_execsql_test json101-10.75 { SELECT json_valid('" \j "'); } {0} -do_execsql_test json-10.76 { +do_execsql_test json101-10.76 { SELECT json_valid('" \k "'); } {0} -do_execsql_test json-10.77 { +do_execsql_test json101-10.77 { SELECT json_valid('" \l "'); } {0} -do_execsql_test json-10.78 { +do_execsql_test json101-10.78 { SELECT json_valid('" \m "'); } {0} -do_execsql_test json-10.79 { +do_execsql_test json101-10.79 { SELECT json_valid('" \n "'); } {1} -do_execsql_test json-10.80 { +do_execsql_test json101-10.80 { SELECT json_valid('" \o "'); } {0} -do_execsql_test json-10.81 { +do_execsql_test json101-10.81 { SELECT json_valid('" \p "'); } {0} -do_execsql_test json-10.82 { +do_execsql_test json101-10.82 { SELECT json_valid('" \q "'); } {0} -do_execsql_test json-10.83 { +do_execsql_test json101-10.83 { SELECT json_valid('" \r "'); } {1} -do_execsql_test json-10.84 { +do_execsql_test json101-10.84 { SELECT json_valid('" \s "'); } {0} -do_execsql_test json-10.85 { +do_execsql_test json101-10.85 { SELECT json_valid('" \t "'); } {1} -do_execsql_test json-10.86.0 { +do_execsql_test json101-10.86.0 { SELECT json_valid('" \u "'); } {0} -do_execsql_test json-10.86.1 { +do_execsql_test json101-10.86.1 { SELECT json_valid('" \ua "'); } {0} -do_execsql_test json-10.86.2 { +do_execsql_test json101-10.86.2 { SELECT json_valid('" \uab "'); } {0} -do_execsql_test json-10.86.3 { +do_execsql_test json101-10.86.3 { SELECT json_valid('" \uabc "'); } {0} -do_execsql_test json-10.86.4 { +do_execsql_test json101-10.86.4 { SELECT json_valid('" \uabcd "'); } {1} -do_execsql_test json-10.86.5 { +do_execsql_test json101-10.86.5 { SELECT json_valid('" \uFEDC "'); } {1} -do_execsql_test json-10.86.6 { +do_execsql_test json101-10.86.6 { SELECT json_valid('" \u1234 "'); } {1} -do_execsql_test json-10.87 { +do_execsql_test json101-10.87 { SELECT json_valid('" \v "'); } {0} -do_execsql_test json-10.88 { +do_execsql_test json101-10.88 { SELECT json_valid('" \w "'); } {0} -do_execsql_test json-10.89 { +do_execsql_test json101-10.89 { SELECT json_valid('" \x "'); } {0} -do_execsql_test json-10.90 { +do_execsql_test json101-10.90 { SELECT json_valid('" \y "'); } {0} -do_execsql_test json-10.91 { +do_execsql_test json101-10.91 { SELECT json_valid('" \z "'); } {0} -do_execsql_test json-10.92 { +do_execsql_test json101-10.92 { SELECT json_valid('" \{ "'); } {0} -do_execsql_test json-10.93 { +do_execsql_test json101-10.93 { SELECT json_valid('" \| "'); } {0} -do_execsql_test json-10.94 { +do_execsql_test json101-10.94 { SELECT json_valid('" \} "'); } {0} -do_execsql_test json-10.95 { +do_execsql_test json101-10.95 { SELECT json_valid('" \~ "'); } {0} @@ -719,20 +719,20 @@ do_execsql_test json-10.95 { # # The following tests confirm that deeply nested JSON is considered invalid. # -do_execsql_test json-11.0 { +do_execsql_test json101-11.0 { /* Shallow enough to be parsed */ SELECT json_valid(printf('%.1000c0%.1000c','[',']')); } {1} -do_execsql_test json-11.1 { +do_execsql_test json101-11.1 { /* Too deep by one */ SELECT json_valid(printf('%.1001c0%.1001c','[',']')); } {0} -do_execsql_test json-11.2 { +do_execsql_test json101-11.2 { /* Shallow enough to be parsed { */ SELECT json_valid(replace(printf('%.1000c0%.1000c','[','}'),'[','{"a":')); /* } */ } {1} -do_execsql_test json-11.3 { +do_execsql_test json101-11.3 { /* Too deep by one { */ SELECT json_valid(replace(printf('%.1001c0%.1001c','[','}'),'[','{"a":')); /* } */ @@ -742,7 +742,7 @@ do_execsql_test json-11.3 { # a json structure even though the element name constains a "." # character, by quoting the element name in the path. # -do_execsql_test json-12.100 { +do_execsql_test json101-12.100 { CREATE TABLE t12(x); INSERT INTO t12(x) VALUES( '{"settings": @@ -766,11 +766,11 @@ do_execsql_test json-12.100 { } }'); } {} -do_execsql_test json-12.110 { +do_execsql_test json101-12.110 { SELECT json_remove(x, '$.settings.layer2."dis.legomenon".forceDisplay') FROM t12; } {{{"settings":{"layer2":{"hapax.legomenon":{"forceDisplay":true,"transliterate":true,"add.footnote":true,"summary.report":true},"dis.legomenon":{"transliterate":false,"add.footnote":false,"summary.report":true},"tris.legomenon":{"forceDisplay":true,"transliterate":false,"add.footnote":false,"summary.report":false}}}}}} -do_execsql_test json-12.120 { +do_execsql_test json101-12.120 { SELECT json_extract(x, '$.settings.layer2."tris.legomenon"."summary.report"') FROM t12; } {0} @@ -779,7 +779,7 @@ do_execsql_test json-12.120 { # ticket https://www.sqlite.org/src/tktview/80177f0c226ff54f6ddd41 # Make sure the query planner knows about the arguments to table-valued functions. # -do_execsql_test json-13.100 { +do_execsql_test json101-13.100 { DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1(id, json); @@ -794,7 +794,7 @@ do_execsql_test json-13.100 { WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z WHERE Z.value==t2.id); } {1 {{"items":[3,5]}} 3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}}} -do_execsql_test json-13.110 { +do_execsql_test json101-13.110 { SELECT * FROM t2 CROSS JOIN t1 WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z WHERE Z.value==t2.id); @@ -804,28 +804,28 @@ do_execsql_test json-13.110 { # Incorrect fullkey output from json_each() # when the input JSON is not an array or object. # -do_execsql_test json-14.100 { +do_execsql_test json101-14.100 { SELECT fullkey FROM json_each('123'); } {$} -do_execsql_test json-14.110 { +do_execsql_test json101-14.110 { SELECT fullkey FROM json_each('123.56'); } {$} -do_execsql_test json-14.120 { +do_execsql_test json101-14.120 { SELECT fullkey FROM json_each('"hello"'); } {$} -do_execsql_test json-14.130 { +do_execsql_test json101-14.130 { SELECT fullkey FROM json_each('null'); } {$} -do_execsql_test json-14.140 { +do_execsql_test json101-14.140 { SELECT fullkey FROM json_tree('123'); } {$} -do_execsql_test json-14.150 { +do_execsql_test json101-14.150 { SELECT fullkey FROM json_tree('123.56'); } {$} -do_execsql_test json-14.160 { +do_execsql_test json101-14.160 { SELECT fullkey FROM json_tree('"hello"'); } {$} -do_execsql_test json-14.170 { +do_execsql_test json101-14.170 { SELECT fullkey FROM json_tree('null'); } {$} @@ -835,16 +835,16 @@ do_execsql_test json-14.170 { # # Bug reported via private email. See TH3 for more information. # -do_execsql_test json-15.100 { +do_execsql_test json101-15.100 { SELECT * FROM JSON_EACH('{"a":1, "b":2}'); } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.110 { +do_execsql_test json101-15.110 { SELECT xyz.* FROM JSON_EACH('{"a":1, "b":2}') AS xyz; } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.120 { +do_execsql_test json101-15.120 { SELECT * FROM (JSON_EACH('{"a":1, "b":2}')); } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.130 { +do_execsql_test json101-15.130 { SELECT xyz.* FROM (JSON_EACH('{"a":1, "b":2}')) AS xyz; } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} @@ -852,18 +852,18 @@ do_execsql_test json-15.130 { # Mailing list bug report on the handling of surrogate pairs # in JSON. # -do_execsql_test json-16.10 { +do_execsql_test json101-16.10 { SELECT length(json_extract('"abc\uD834\uDD1Exyz"','$')); } {7} -do_execsql_test json-16.20 { +do_execsql_test json101-16.20 { SELECT length(json_extract('"\uD834\uDD1E"','$')); } {1} -do_execsql_test json-16.30 { +do_execsql_test json101-16.30 { SELECT unicode(json_extract('"\uD834\uDD1E"','$')); } {119070} # 2022-01-30 dbsqlfuzz 4678cf825d27f87c9b8343720121e12cf944b71a -do_execsql_test json-17.1 { +do_execsql_test json101-17.1 { DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1(a,b,c); @@ -872,19 +872,19 @@ do_execsql_test json-17.1 { } {} # 2022-04-04 forum post https://sqlite.org/forum/forumpost/c082aeab43 -do_execsql_test json-18.1 { +do_execsql_test json101-18.1 { SELECT json_valid('{"":5}'); } {1} -do_execsql_test json-18.2 { +do_execsql_test json101-18.2 { SELECT json_extract('{"":5}', '$.""'); } {5} -do_execsql_test json-18.3 { +do_execsql_test json101-18.3 { SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1].hi'); } {6} -do_execsql_test json-18.4 { +do_execsql_test json101-18.4 { SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1]."hi"'); } {6} -do_catchsql_test json-18.5 { +do_catchsql_test json101-18.5 { SELECT json_extract('{"":8}', '$.'); } {1 {JSON path error near ''}} @@ -893,28 +893,28 @@ do_catchsql_test json-18.5 { # a problem with transaction control. But the json() function makes # the problem more easily accessible, so it is tested here. # -do_execsql_test json-19.1 { +do_execsql_test json101-19.1 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(x); } {} -do_catchsql_test json-19.2 { +do_catchsql_test json101-19.2 { BEGIN; INSERT INTO t1 VALUES(0), (json('not-valid-json')); } {1 {malformed JSON}} -do_execsql_test json-19.3 { +do_execsql_test json101-19.3 { COMMIT; SELECT * FROM t1; } {} # 2023-03-17 positive and negative infinities # -do_execsql_test json-20.1 { +do_execsql_test json101-20.1 { SELECT json_object('a',2e370,'b',-3e380); } {{{"a":9.0e+999,"b":-9.0e+999}}} -do_execsql_test json-20.2 { +do_execsql_test json101-20.2 { SELECT json_object('a',2e370,'b',-3e380)->>'a'; } Inf -do_execsql_test json-20.3 { +do_execsql_test json101-20.3 { SELECT json_object('a',2e370,'b',-3e380)->>'b'; } {-Inf} @@ -923,86 +923,92 @@ do_execsql_test json-20.3 { # a NULL value as the JSON input. # db null NULL -do_execsql_test json-21.1 { - SELECT json_valid(NULL); -} NULL -do_execsql_test json-21.2 { +if {[db exists {SELECT * FROM pragma_compile_options WHERE compile_options LIKE '%legacy_json_valid%'}]} { + do_execsql_test json101-21.1-legacy { + SELECT json_valid(NULL); + } 0 +} else { + do_execsql_test json101-21.1-correct { + SELECT json_valid(NULL); + } NULL +} +do_execsql_test json101-21.2 { SELECT json_error_position(NULL); } NULL -do_execsql_test json-21.3 { +do_execsql_test json101-21.3 { SELECT json(NULL); } NULL -do_execsql_test json-21.4 { +do_execsql_test json101-21.4 { SELECT json_array(NULL); } {[null]} -do_execsql_test json-21.5 { +do_execsql_test json101-21.5 { SELECT json_extract(NULL); } NULL -do_execsql_test json-21.6 { +do_execsql_test json101-21.6 { SELECT json_insert(NULL,'$',123); } NULL -do_execsql_test json-21.7 { +do_execsql_test json101-21.7 { SELECT NULL->0; } NULL -do_execsql_test json-21.8 { +do_execsql_test json101-21.8 { SELECT NULL->>0; } NULL -do_execsql_test json-21.9 { +do_execsql_test json101-21.9 { SELECT '{a:5}'->NULL; } NULL -do_execsql_test json-21.10 { +do_execsql_test json101-21.10 { SELECT '{a:5}'->>NULL; } NULL -do_catchsql_test json-21.11 { +do_catchsql_test json101-21.11 { SELECT json_object(NULL,5); } {1 {json_object() labels must be TEXT}} -do_execsql_test json-21.12 { +do_execsql_test json101-21.12 { SELECT json_patch(NULL,'{a:5}'); } NULL -do_execsql_test json-21.13 { +do_execsql_test json101-21.13 { SELECT json_patch('{a:5}',NULL); } NULL -do_execsql_test json-21.14 { +do_execsql_test json101-21.14 { SELECT json_patch(NULL,NULL); } NULL -do_execsql_test json-21.15 { +do_execsql_test json101-21.15 { SELECT json_remove(NULL,'$'); } NULL -do_execsql_test json-21.16 { +do_execsql_test json101-21.16 { SELECT json_remove('{a:5,b:7}',NULL); } NULL -do_execsql_test json-21.17 { +do_execsql_test json101-21.17 { SELECT json_replace(NULL,'$.a',123); } NULL -do_execsql_test json-21.18 { +do_execsql_test json101-21.18 { SELECT json_replace('{a:5,b:7}',NULL,NULL); } {{{"a":5,"b":7}}} -do_execsql_test json-21.19 { +do_execsql_test json101-21.19 { SELECT json_set(NULL,'$.a',123); } NULL -do_execsql_test json-21.20 { +do_execsql_test json101-21.20 { SELECT json_set('{a:5,b:7}',NULL,NULL); } {{{"a":5,"b":7}}} -do_execsql_test json-21.21 { +do_execsql_test json101-21.21 { SELECT json_type(NULL); } NULL -do_execsql_test json-21.22 { +do_execsql_test json101-21.22 { SELECT json_type('{a:5,b:7}',NULL); } NULL -do_execsql_test json-21.23 { +do_execsql_test json101-21.23 { SELECT json_quote(NULL); } null -do_execsql_test json-21.24 { +do_execsql_test json101-21.24 { SELECT count(*) FROM json_each(NULL); } 0 -do_execsql_test json-21.25 { +do_execsql_test json101-21.25 { SELECT count(*) FROM json_tree(NULL); } 0 -do_execsql_test json-21.26 { +do_execsql_test json101-21.26 { WITH c(x) AS (VALUES(1),(2.0),(NULL),('three')) SELECT json_group_array(x) FROM c; } {[1,2.0,null,"three"]} -do_execsql_test json-21.27 { +do_execsql_test json101-21.27 { WITH c(x,y) AS (VALUES('a',1),('b',2.0),('c',NULL),(NULL,'three'),('e','four')) SELECT json_group_object(x,y) FROM c; } {{{"a":1,"b":2.0,"c":null,:"three","e":"four"}}} diff --git a/test/json102.test b/test/json102.test index bfd5e7ed0..276c1816a 100644 --- a/test/json102.test +++ b/test/json102.test @@ -397,4 +397,22 @@ do_execsql_test json102-1610 { 6 {} null {} null {} null ] +reset_db +do_execsql_test json102-1700 { + CREATE TABLE t1(a1 DATE, a2 INTEGER PRIMARY KEY, a3 INTEGER, memo TEXT); + CREATE INDEX t1x1 ON t1(a3, a1, memo->>'y'); + INSERT INTO t1(a2,a1,a3,memo) VALUES (876, '2023-08-03', 5, '{"x":77,"y":4}'); +} +do_execsql_test json102-1710 { + UPDATE t1 SET memo = JSON_REMOVE(memo, '$.y'); + PRAGMA integrity_check; + SELECT * FROM t1; +} {ok 2023-08-03 876 5 {{"x":77}}} +do_execsql_test json102-1720 { + UPDATE t1 SET memo = JSON_SET(memo, '$.y', 6) + WHERE a2 IN (876) AND JSON_TYPE(memo, '$.y') IS NULL; + PRAGMA integrity_check; + SELECT * FROM t1; +} {ok 2023-08-03 876 5 {{"x":77,"y":6}}} + finish_test diff --git a/test/kvtest.c b/test/kvtest.c index 04dc01045..624c80b74 100644 --- a/test/kvtest.c +++ b/test/kvtest.c @@ -161,7 +161,7 @@ static const char zHelp[] = #endif /* -** Show thqe help text and quit. +** Show the help text and quit. */ static void showHelp(void){ fprintf(stdout, "%s", zHelp); diff --git a/test/like.test b/test/like.test index e8662dc6c..d314e96a1 100644 --- a/test/like.test +++ b/test/like.test @@ -1140,4 +1140,24 @@ do_execsql_test 17.1 { } {1} +# 2023-08-15 https://sqlite.org/forum/forumpost/925dc9f67804c540 +# +reset_db +sqlite3_db_config db DEFENSIVE 1 +db eval {PRAGMA trusted_schema=OFF} +do_execsql_test 18.0 { + CREATE TABLE t1(x INT, y TEXT); + INSERT INTO t1 VALUES(1,'abc'),(2,'ABC'),(3,'Abc'); + CREATE VIEW t2 AS SELECT * FROM t1 WHERE y LIKE 'a%'; + SELECT * FROM t2; +} {1 abc 2 ABC 3 Abc} +do_execsql_test 18.1 { + PRAGMA case_sensitive_like=OFF; + SELECT * FROM t2; +} {1 abc 2 ABC 3 Abc} +do_execsql_test 18.2 { + PRAGMA case_sensitive_like=ON; + SELECT * FROM t2; +} {1 abc} + finish_test diff --git a/test/permutations.test b/test/permutations.test index eb173f79b..25aa7de01 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -10,9 +10,11 @@ #*********************************************************************** # -set testdir [file dirname $argv0] -source $testdir/tester.tcl -db close +if {[info vars ::trd::tcltest]==""} { + set testdir [file dirname $argv0] + source $testdir/tester.tcl + db close +} #------------------------------------------------------------------------- # test_suite NAME OPTIONS @@ -813,117 +815,111 @@ test_suite "inmemory_journal" -description { recoverpgsz.test }] -ifcapable mem3 { - test_suite "memsys3" -description { - Run tests using the allocator in mem3.c. - } -files [test_set $::allquicktests -exclude { - autovacuum.test delete3.test manydb.test - bigrow.test incrblob2.test memdb.test - bitvec.test index2.test memsubsys1.test - capi3c.test ioerr.test memsubsys2.test - capi3.test join3.test pagesize.test - collate5.test limit.test backup_ioerr.test - backup_malloc.test - }] -initialize { - catch {db close} - sqlite3_reset_auto_extension - sqlite3_shutdown - sqlite3_config_heap 25000000 0 - sqlite3_config_lookaside 0 0 - ifcapable mem5 { - # If both memsys3 and memsys5 are enabled in the build, the call to - # [sqlite3_config_heap] will initialize the system to use memsys5. - # The following overrides this preference and installs the memsys3 - # allocator. - sqlite3_install_memsys3 - } - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 0 0 - sqlite3_config_lookaside 100 500 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions +test_suite "memsys3" -description { + Run tests using the allocator in mem3.c. +} -files [test_set $::allquicktests -exclude { + autovacuum.test delete3.test manydb.test + bigrow.test incrblob2.test memdb.test + bitvec.test index2.test memsubsys1.test + capi3c.test ioerr.test memsubsys2.test + capi3.test join3.test pagesize.test + collate5.test limit.test backup_ioerr.test + backup_malloc.test +}] -initialize { + catch {db close} + sqlite3_reset_auto_extension + sqlite3_shutdown + sqlite3_config_heap 25000000 0 + sqlite3_config_lookaside 0 0 + ifcapable mem5 { + # If both memsys3 and memsys5 are enabled in the build, the call to + # [sqlite3_config_heap] will initialize the system to use memsys5. + # The following overrides this preference and installs the memsys3 + # allocator. + sqlite3_install_memsys3 } + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 0 0 + sqlite3_config_lookaside 100 500 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions } -ifcapable mem5 { - test_suite "memsys5" -description { - Run tests using the allocator in mem5.c. - } -files [test_set $::allquicktests -exclude { - autovacuum.test delete3.test manydb.test - bigrow.test incrblob2.test memdb.test - bitvec.test index2.test memsubsys1.test - capi3c.test ioerr.test memsubsys2.test - capi3.test join3.test pagesize.test - collate5.test limit.test zeroblob.test - }] -initialize { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 25000000 64 - sqlite3_config_lookaside 0 0 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 0 0 - sqlite3_config_lookaside 100 500 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } +test_suite "memsys5" -description { + Run tests using the allocator in mem5.c. +} -files [test_set $::allquicktests -exclude { + autovacuum.test delete3.test manydb.test + bigrow.test incrblob2.test memdb.test + bitvec.test index2.test memsubsys1.test + capi3c.test ioerr.test memsubsys2.test + capi3.test join3.test pagesize.test + collate5.test limit.test zeroblob.test +}] -initialize { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 25000000 64 + sqlite3_config_lookaside 0 0 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 0 0 + sqlite3_config_lookaside 100 500 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} - test_suite "memsys5-2" -description { - Run tests using the allocator in mem5.c in a different configuration. - } -files { - select1.test - } -initialize { - catch {db close} - sqlite3_shutdown - sqlite3_config_memstatus 0 - sqlite3_config_heap 40000000 16 - sqlite3_config_lookaside 0 0 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 0 0 - sqlite3_config_lookaside 100 500 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } +test_suite "memsys5-2" -description { + Run tests using the allocator in mem5.c in a different configuration. +} -files { + select1.test +} -initialize { + catch {db close} + sqlite3_shutdown + sqlite3_config_memstatus 0 + sqlite3_config_heap 40000000 16 + sqlite3_config_lookaside 0 0 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 0 0 + sqlite3_config_lookaside 100 500 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions } -ifcapable threadsafe { - test_suite "no_mutex_try" -description { - The sqlite3_mutex_try() interface always fails - } -files [ - test_set $::allquicktests -exclude mutex1.test mutex2.test - ] -initialize { - catch {db close} - sqlite3_shutdown - install_mutex_counters 1 - set ::disable_mutex_try 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - catch {db2 close} - catch {db3 close} - sqlite3_shutdown - install_mutex_counters 0 - sqlite3_initialize - autoinstall_test_functions - } +test_suite "no_mutex_try" -description { + The sqlite3_mutex_try() interface always fails +} -files [ + test_set $::allquicktests -exclude mutex1.test mutex2.test +] -initialize { + catch {db close} + sqlite3_shutdown + install_mutex_counters 1 + set ::disable_mutex_try 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + catch {db2 close} + catch {db3 close} + sqlite3_shutdown + install_mutex_counters 0 + sqlite3_initialize + autoinstall_test_functions } # run_tests "crash_safe_append" -description { diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl index 2f4e71e8c..4ed57a9c8 100644 --- a/test/releasetest_data.tcl +++ b/test/releasetest_data.tcl @@ -53,6 +53,12 @@ array set ::Configs [strip_comments { --enable-session -DSQLITE_ENABLE_RBU } + "All-Debug" { + --enable-debug --enable-all + } + "All-O0" { + -O0 --enable-all + } "Sanitize" { CC=clang -fsanitize=address,undefined -DSQLITE_ENABLE_STAT4 @@ -257,6 +263,7 @@ array set ::Configs [strip_comments { -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DLONGDOUBLE_TYPE=double -DCONFIG_SLOWDOWN_FACTOR=8.0 } @@ -671,6 +678,12 @@ proc main_trscript {args} { lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK lappend opts -DSQLITE_ENABLE_SESSION } + --enable-all { + } + --enable-debug { + # lappend makeOpts OPTIMIZATIONS=0 + lappend opts -DSQLITE_DEBUG + } default { error "Cannot translate $param for MSVC" } @@ -716,7 +729,12 @@ proc main_trscript {args} { puts " \$SRCDIR/configure --with-tcl=\$TCL $configOpts" puts {fi} puts {} - puts {OPTS=" -DSQLITE_NO_SYNC=1"} + if {[info exists ::env(OPTS)]} { + puts "# From environment variable:" + puts "OPTS=$::env(OPTS)" + puts "" + } + puts {OPTS="$OPTS -DSQLITE_NO_SYNC=1"} foreach o $opts { puts "OPTS=\"\$OPTS $o\"" } diff --git a/test/round1.test b/test/round1.test index 2244a399a..2857593be 100644 --- a/test/round1.test +++ b/test/round1.test @@ -34,8 +34,8 @@ for {set iTest 1} {$iTest<=50000} {incr iTest} { set r2 $x1.$x4 set ans [string trimright $r2 0] if {[string match *. $ans]} {set ans ${ans}0} - do_test $iTest/$n/${r}5=>$ans { - set x [db one "SELECT round(${r}5,$n)"] + do_test $iTest/$n/${r}6=>$ans { + set x [db one "SELECT round(${r}6,$n)"] } $ans } diff --git a/test/scanstatus2.test b/test/scanstatus2.test index fb62af210..cbffee018 100644 --- a/test/scanstatus2.test +++ b/test/scanstatus2.test @@ -55,11 +55,12 @@ proc get_cycles {stmt} { dict get $r nCycle } -proc foreach_scan {varname stmt body} { +proc foreach_scan {varname stmt body {debug 0}} { upvar $varname var - for {set ii 0} {1} {incr ii} { - set r [sqlite3_stmt_scanstatus -flags complex $stmt $ii] + set f "complex" + if {$debug} { set f "complex debug" } + set r [sqlite3_stmt_scanstatus -flags $f $stmt $ii] if {[llength $r]==0} break array set var $r uplevel $body @@ -102,6 +103,15 @@ proc puts_graph {sql} { puts [string trim [get_graph $stmt]] } +proc puts_debug_info {sql} { + db eval $sql + set stmt [db version -last-stmt-ptr] + foreach_scan X $stmt { + puts -nonewline "$X(debug_explain) $X(zExplain): " + puts -nonewline "loop=$X(debug_loop) visit=$X(debug_visit) " + puts "csr=$X(debug_csr) range=$X(debug_range)" + } 1 +} do_zexplain_test 0 1.1 { SELECT (SELECT a FROM t1 WHERE b=x) FROM t2 WHERE y=2 @@ -235,7 +245,7 @@ do_graph_test 4.5 { QUERY (nCycle=nnn) --CO-ROUTINE v1 ----SCAN rt2 (nCycle=nnn) -----USE TEMP B-TREE FOR GROUP BY +----USE TEMP B-TREE FOR GROUP BY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON v1(x1, cnt) (nCycle=nnn) --BLOOM FILTER ON v1 (x1=?) @@ -270,6 +280,57 @@ ifcapable trace { } {{SCAN t1} {SCAN t1} {SCAN t1}} } +#------------------------------------------------------------------------- +reset_db +sqlite3_db_config db STMT_SCANSTATUS 1 + +do_execsql_test 6.0 { + 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'); + INSERT INTO t1 VALUES(6, 'six'); + INSERT INTO t1 VALUES(7, 'seven'); + INSERT INTO t1 VALUES(8, 'eight'); +} + +do_graph_test 6.1 { + SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 +} { +QUERY (nCycle=nnn) +--SCAN t1 (nCycle=nnn) +--USE TEMP B-TREE FOR GROUP BY (nCycle=nnn) +} + +set sql { + WITH xy(x, y) AS ( SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1) + SELECT * FROM xy WHERE x=1 +} +do_graph_test 6.2 $sql { +QUERY (nCycle=nnn) +--CO-ROUTINE xy +----SCAN t1 (nCycle=nnn) +----USE TEMP B-TREE FOR GROUP BY (nCycle=nnn) +--SCAN xy (nCycle=nnn) +} + +do_graph_test 6.3 { + WITH xy(x, y) AS ( SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1) + SELECT * FROM xy, xy AS xy2 +} { +QUERY (nCycle=nnn) +--MATERIALIZE xy (nCycle=nnn) +----SCAN t1 (nCycle=nnn) +----USE TEMP B-TREE FOR GROUP BY (nCycle=nnn) +--SCAN xy (nCycle=nnn) +--SCAN xy2 (nCycle=nnn) +} + +#explain_i { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 } +#puts_debug_info { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 } + finish_test diff --git a/test/shell2.test b/test/shell2.test index 3fad4bd66..16ed33c44 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -262,5 +262,14 @@ do_test shell2-1.4.11 { SELECT count(*) FROM t;}]] } {0 1} +# Bug from forum post 7cbe081746dd3803 +# Keywords as column names were producing an error message. +do_test shell2-1.4.12 { + set res [catchcmd :memory: [string trim { + CREATE TABLE "group"("order" text); + INSERT INTO "group" VALUES ('ABC'); +.sha3sum}]] +} {0 ca08bc02b7e95c7df431a3a4b1cc0f8d8743914793473f55b5558e03} + finish_test diff --git a/test/snapshot_fault.test b/test/snapshot_fault.test index 2adb79365..10c509459 100644 --- a/test/snapshot_fault.test +++ b/test/snapshot_fault.test @@ -23,6 +23,7 @@ set testprefix snapshot_fault # checkpointing the db. # do_faultsim_test 1.0 -prep { + catch { db2 close } faultsim_delete_and_reopen sqlite3 db2 test.db db2 eval { diff --git a/test/sort2.test b/test/sort2.test index f686654d5..9fe4b4ccf 100644 --- a/test/sort2.test +++ b/test/sort2.test @@ -69,7 +69,7 @@ foreach {tn script} { # Because it uses so much data, this test can take 12-13 seconds even on # a modern workstation. So it is omitted from "veryquick" and other # permutations.test tests. - if {[isquick]==0} { + if {[isquick]==0 && [clang_sanitize_address]==0} { do_execsql_test $tn.3 { PRAGMA cache_size = 5; WITH r(x,y) AS ( diff --git a/test/speedtest1.c b/test/speedtest1.c index 6aff89b37..4f3258098 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -21,6 +21,7 @@ static const char zHelp[] = " --memdb Use an in-memory database\n" " --mmap SZ MMAP the first SZ bytes of the database file\n" " --multithread Set multithreaded mode\n" + " --nolongdouble Disable the use of long double\n" " --nomemstat Disable memory statistics\n" " --nomutex Open db with SQLITE_OPEN_NOMUTEX\n" " --nosync Set PRAGMA synchronous=OFF\n" @@ -2307,6 +2308,10 @@ int main(int argc, char **argv){ ARGC_VALUE_CHECK(1); mmapSize = integerValue(argv[++i]); #endif + }else if( strcmp(z,"nolongdouble")==0 ){ +#ifdef SQLITE_TESTCTRL_USELONGDOUBLE + sqlite3_test_control(SQLITE_TESTCTRL_USELONGDOUBLE, 0); +#endif }else if( strcmp(z,"nomutex")==0 ){ openFlags |= SQLITE_OPEN_NOMUTEX; }else if( strcmp(z,"nosync")==0 ){ diff --git a/test/sqldiff1.test b/test/sqldiff1.test index 4ea5efbbf..5a3c11cb9 100644 --- a/test/sqldiff1.test +++ b/test/sqldiff1.test @@ -63,4 +63,26 @@ CREATE TABLE t3(a,b,c); INSERT INTO t3(rowid,a,b,c) VALUES(1,111,222,333); DROP TABLE t4;} +db close +forcedelete test.db test2.db +sqlite3 db test.db + +do_test sqldiff-2.0 { + db eval { + CREATE TABLE t1(a INTEGER PRIMARY KEY); + } + db close + sqlite3 db test2.db + db eval { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + } + db close + set line "exec $PROG test.db test2.db" + unset -nocomplain ::MSG + catch {eval $line} ::MSG +} {0} +do_test sqldiff-2.1 { + set ::MSG +} {ALTER TABLE t1 ADD COLUMN b;} + finish_test diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 4e21ce08a..22e3b17bf 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -1,6 +1,6 @@ set dir [pwd] -set testdir [file dirname $argv0] +set testdir [file normalize [file dirname $argv0]] set saved $argv set argv [list] source [file join $testdir testrunner_data.tcl] @@ -8,6 +8,42 @@ source [file join $testdir permutations.test] set argv $saved cd $dir +# This script requires an interpreter that supports [package require sqlite3] +# to run. If this is not such an intepreter, see if there is a [testfixture] +# in the current directory. If so, run the command using it. If not, +# recommend that the user build one. +# +proc find_interpreter {} { + set interpreter [file tail [info nameofexec]] + set rc [catch { package require sqlite3 }] + if {$rc} { + if { [string match -nocase testfixture* $interpreter]==0 + && [file executable ./testfixture] + } { + puts "Failed to find tcl package sqlite3. Restarting with ./testfixture.." + set status [catch { + exec ./testfixture [info script] {*}$::argv >@ stdout + } msg] + exit $status + } + } + if {$rc} { + puts stderr "Failed to find tcl package sqlite3" + puts stderr "Run \"make testfixture\" and then try again..." + exit 1 + } +} +find_interpreter + +# Usually this script is run by [testfixture]. But it can also be run +# by a regular [tclsh]. For these cases, emulate the [clock_milliseconds] +# command. +if {[info commands clock_milliseconds]==""} { + proc clock_milliseconds {} { + clock milliseconds + } +} + #------------------------------------------------------------------------- # Usage: # @@ -74,16 +110,20 @@ proc guess_number_of_cores {} { if {[catch {number_of_cores} ret]} { set ret 4 - if {$::tcl_platform(os)=="Darwin"} { - set cmd "sysctl -n hw.logicalcpu" + if {$::tcl_platform(platform)=="windows"} { + catch { set ret $::env(NUMBER_OF_PROCESSORS) } } else { - set cmd "nproc" - } - catch { - set fd [open "|$cmd" r] - set ret [gets $fd] - close $fd - set ret [expr $ret] + if {$::tcl_platform(os)=="Darwin"} { + set cmd "sysctl -n hw.logicalcpu" + } else { + set cmd "nproc" + } + catch { + set fd [open "|$cmd" r] + set ret [gets $fd] + close $fd + set ret [expr $ret] + } } } return $ret @@ -152,10 +192,8 @@ set TRG(schema) { state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ), time INTEGER, -- Time in ms output TEXT, -- full output of test script - priority AS ((config='make') + ((config='build')*2) + (slow*4)), - jobtype AS ( - CASE WHEN config IN ('build', 'make') THEN config ELSE 'script' END - ), + priority INTEGER, + jobtype TEXT CHECK( jobtype IN ('script', 'build', 'make') ), PRIMARY KEY(build, config, filename) ); @@ -181,10 +219,11 @@ if {[llength $argv]==2 set script [file normalize [lindex $argv 1]] set ::argv [list] + set testdir [file dirname $argv0] + source $::testdir/tester.tcl + if {$permutation=="full"} { - set testdir [file dirname $argv0] - source $::testdir/tester.tcl unset -nocomplain ::G(isquick) reset_db @@ -241,6 +280,22 @@ if {([llength $argv]==2 || [llength $argv]==1) } #-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- +# Check if this is the "script" command: +# +if {[string compare -nocase script [lindex $argv 0]]==0} { + if {[llength $argv]!=2 && !([llength $argv]==3&&[lindex $argv 1]=="-msvc")} { + usage + } + + set bMsvc [expr ([llength $argv]==3)] + set config [lindex $argv [expr [llength $argv]-1]] + + puts [trd_buildscript $config [file dirname $testdir] $bMsvc] + exit +} + + #-------------------------------------------------------------------------- # Check if this is the "status" command: # @@ -289,9 +344,13 @@ if {[llength $argv]==1 set cmdline [mydb one { SELECT value FROM config WHERE name='cmdline' }] set nJob [mydb one { SELECT value FROM config WHERE name='njob' }] - set tm [expr [clock_milliseconds] - [mydb one { - SELECT value FROM config WHERE name='start' - }]] + + set now [clock_milliseconds] + set tm [mydb one { + SELECT + COALESCE((SELECT value FROM config WHERE name='end'), $now) - + (SELECT value FROM config WHERE name='start') + }] set total 0 foreach s {"" ready running done failed} { set S($s) 0 } @@ -315,7 +374,6 @@ if {[llength $argv]==1 set srcdir [file dirname [file dirname $TRG(info_script)]] if {$S(running)>0} { puts "Running: " - set now [clock_milliseconds] mydb eval { SELECT build, config, filename, time FROM script WHERE state='running' ORDER BY time @@ -411,8 +469,6 @@ proc dirs_allocDir {} { return $iRet } -set testdir [file dirname $argv0] - # Check that directory $dir exists. If it does not, create it. If # it does, delete its contents. # @@ -449,7 +505,18 @@ proc testset_patternlist {patternlist} { set first [lindex $patternlist 0] - if {$first=="release"} { + if {$first=="sdevtest" || $first=="mdevtest"} { + set CONFIGS(sdevtest) {All-Debug All-Sanitize} + set CONFIGS(mdevtest) {All-Debug All-O0} + + set patternlist [lrange $patternlist 1 end] + + foreach b $CONFIGS($first) { + lappend testset [list $b build testfixture] + lappend testset [list $b make fuzztest] + testset_append testset $b veryquick $patternlist + } + } elseif {$first=="release"} { set platform $::TRG(platform) set patternlist [lrange $patternlist 1 end] @@ -634,9 +701,20 @@ proc make_new_testset {} { set state "" } + set priority [expr {$slow*2}] + if {$c=="make"} { incr priority 3 } + if {$c=="build"} { incr priority 1 } + + if {$c=="make" || $c=="build"} { + set jobtype $c + } else { + set jobtype "script" + } + trdb eval { - INSERT INTO script(build, config, filename, slow, state) - VALUES ($b, $c, $s, $slow, $state) + INSERT INTO script + (build, config, filename, slow, state, priority, jobtype) + VALUES ($b, $c, $s, $slow, $state, $priority, $jobtype) } } } @@ -721,12 +799,7 @@ proc launch_another_job {iJob} { if {$b=="Zipvfs"} { set script [zipvfs_testrunner_script] } else { - set cmd [info nameofexec] - lappend cmd [file join $testdir releasetest_data.tcl] - lappend cmd trscript - if {$TRG(platform)=="win"} { lappend cmd -msvc } - lappend cmd $b $srcdir - set script [exec {*}$cmd] + set script [trd_buildscript $b $srcdir [expr {$TRG(platform)=="win"}]] } set fd [open [file join $builddir $TRG(make)] w] @@ -865,6 +938,8 @@ proc run_testset {} { one_line_report r_write_db { + set tm [clock_milliseconds] + trdb eval { REPLACE INTO config VALUES('end', $tm ); } set nErr [trdb one {SELECT count(*) FROM script WHERE state='failed'}] if {$nErr>0} { puts "$nErr failures:" diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index f74ee146c..ce2ce01dd 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -5,6 +5,7 @@ namespace eval trd { variable tcltest variable extra variable all_configs + variable build # Tcl tests to run for various builds. @@ -35,6 +36,7 @@ namespace eval trd { set tcltest(win.Have-Not) veryquick set tcltest(win.Windows-Memdebug) veryquick set tcltest(win.Windows-Win32Heap) veryquick + set tcltest(win.Windows-Sanitize) veryquick set tcltest(win.Default) full # Extra [make xyz] tests that should be run for various builds. @@ -63,6 +65,7 @@ namespace eval trd { set extra(win.Stdcall) {fuzztest sourcetest} set extra(win.Windows-Memdebug) {fuzztest sourcetest} set extra(win.Windows-Win32Heap) {fuzztest sourcetest} + set extra(win.Windows-Sanitize) fuzztest set extra(win.Have-Not) {fuzztest sourcetest} # The following mirrors the set of test suites invoked by "all.test". @@ -75,6 +78,247 @@ namespace eval trd { inmemory_journal pcache0 pcache10 pcache50 pcache90 pcache100 prepare mmap } + + #----------------------------------------------------------------------- + # Start of build() definitions. + # + set build(Default) { + -O2 + --disable-amalgamation --disable-shared + --enable-session + -DSQLITE_ENABLE_RBU + } + + # These two are used by [testrunner.tcl mdevtest] (All-O0) and + # [testrunner.tcl sdevtest] (All-Sanitize). + # + set build(All-Debug) { + --enable-debug --enable-all + } + set build(All-O0) { + -O0 --enable-all + } + set build(All-Sanitize) { --enable-all -fsanitize=address,undefined } + + set build(Sanitize) { + CC=clang -fsanitize=address,undefined + -DSQLITE_ENABLE_STAT4 + -DCONFIG_SLOWDOWN_FACTOR=5.0 + --enable-debug + --enable-all + } + set build(Stdcall) { + -DUSE_STDCALL=1 + -O2 + } + + # The "Have-Not" configuration sets all possible -UHAVE_feature options + # in order to verify that the code works even on platforms that lack + # these support services. + set build(Have-Not) { + -DHAVE_FDATASYNC=0 + -DHAVE_GMTIME_R=0 + -DHAVE_ISNAN=0 + -DHAVE_LOCALTIME_R=0 + -DHAVE_LOCALTIME_S=0 + -DHAVE_MALLOC_USABLE_SIZE=0 + -DHAVE_STRCHRNUL=0 + -DHAVE_USLEEP=0 + -DHAVE_UTIME=0 + } + set build(Unlock-Notify) { + -O2 + -DSQLITE_ENABLE_UNLOCK_NOTIFY + -DSQLITE_THREADSAFE + -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 + } + set build(User-Auth) { + -O2 + -DSQLITE_USER_AUTHENTICATION=1 + } + set build(Secure-Delete) { + -O2 + -DSQLITE_SECURE_DELETE=1 + -DSQLITE_SOUNDEX=1 + } + set build(Update-Delete-Limit) { + -O2 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 + -DSQLITE_ENABLE_STMT_SCANSTATUS + -DSQLITE_LIKE_DOESNT_MATCH_BLOBS + -DSQLITE_ENABLE_CURSOR_HINTS + } + set build(Check-Symbols) { + -DSQLITE_MEMDEBUG=1 + -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_MEMSYS5=1 + -DSQLITE_ENABLE_MEMSYS3=1 + -DSQLITE_ENABLE_COLUMN_METADATA=1 + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 + -DSQLITE_SECURE_DELETE=1 + -DSQLITE_SOUNDEX=1 + -DSQLITE_ENABLE_ATOMIC_WRITE=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_STMT_SCANSTATUS + --enable-fts5 --enable-session + } + set build(Debug-One) { + --disable-shared + -O2 -funsigned-char + -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_COLUMN_METADATA=1 + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DSQLITE_MAX_ATTACHED=125 + -DSQLITE_MUTATION_TEST + --enable-fts5 + } + set build(Debug-Two) { + -DSQLITE_DEFAULT_MEMSTATUS=0 + -DSQLITE_MAX_EXPR_DEPTH=0 + --enable-debug + } + set build(Fast-One) { + -O6 + -DSQLITE_ENABLE_FTS4=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_RBU + -DSQLITE_MAX_ATTACHED=125 + -DSQLITE_MAX_MMAP_SIZE=12884901888 + -DSQLITE_ENABLE_SORTER_MMAP=1 + -DLONGDOUBLE_TYPE=double + --enable-session + } + set build(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_ENABLE_HIDDEN_COLUMNS + -DSQLITE_TEMP_STORE=3 + } + set build(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 + --enable-fts5 --enable-session + } + set build(Locking-Style) { + -O2 + -DSQLITE_ENABLE_LOCKING_STYLE=1 + } + set build(Apple) { + -Os + -DHAVE_GMTIME_R=1 + -DHAVE_ISNAN=1 + -DHAVE_LOCALTIME_R=1 + -DHAVE_PREAD=1 + -DHAVE_PWRITE=1 + -DHAVE_UTIME=1 + -DSQLITE_DEFAULT_CACHE_SIZE=1000 + -DSQLITE_DEFAULT_CKPTFULLFSYNC=1 + -DSQLITE_DEFAULT_MEMSTATUS=1 + -DSQLITE_DEFAULT_PAGE_SIZE=1024 + -DSQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS=1 + -DSQLITE_ENABLE_API_ARMOR=1 + -DSQLITE_ENABLE_AUTO_PROFILE=1 + -DSQLITE_ENABLE_FLOCKTIMEOUT=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 + -DSQLITE_ENABLE_FTS3_TOKENIZER=1 + -DSQLITE_ENABLE_PERSIST_WAL=1 + -DSQLITE_ENABLE_PURGEABLE_PCACHE=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_SNAPSHOT=1 + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 + -DSQLITE_MAX_LENGTH=2147483645 + -DSQLITE_MAX_VARIABLE_NUMBER=500000 + -DSQLITE_NO_SYNC=1 + -DSQLITE_OMIT_AUTORESET=1 + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DSQLITE_PREFER_PROXY_LOCKING=1 + -DSQLITE_SERIES_CONSTRAINT_VERIFY=1 + -DSQLITE_THREADSAFE=2 + -DSQLITE_USE_URI=1 + -DSQLITE_WRITE_WALFRAME_PREBUFFERED=1 + -DUSE_GUARDED_FD=1 + -DUSE_PREAD=1 + --enable-fts5 + } + set build(Extra-Robustness) { + -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 + -DSQLITE_MAX_ATTACHED=62 + } + set build(Devkit) { + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_MAX_ATTACHED=30 + -DSQLITE_ENABLE_COLUMN_METADATA + -DSQLITE_ENABLE_FTS4 + -DSQLITE_ENABLE_FTS5 + -DSQLITE_ENABLE_FTS4_PARENTHESIS + -DSQLITE_DISABLE_FTS4_DEFERRED + -DSQLITE_ENABLE_RTREE + --enable-fts5 + } + set build(No-lookaside) { + -DSQLITE_TEST_REALLOC_STRESS=1 + -DSQLITE_OMIT_LOOKASIDE=1 + } + set build(Valgrind) { + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_FTS4 + -DSQLITE_ENABLE_RTREE + -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DLONGDOUBLE_TYPE=double + -DCONFIG_SLOWDOWN_FACTOR=8.0 + } + + set build(Windows-Memdebug) { + MEMDEBUG=1 + DEBUG=3 + } + set build(Windows-Win32Heap) { + WIN32HEAP=1 + DEBUG=4 + } + set build(Windows-Sanitize) { + ASAN=1 + } + } @@ -84,6 +328,7 @@ proc trd_import {} { variable ::trd::tcltest variable ::trd::extra variable ::trd::all_configs + variable ::trd::build } } @@ -106,13 +351,13 @@ proc trd_builds {platform} { set ret } -proc trd_configs {platform build} { +proc trd_configs {platform bld} { trd_import set clist [list] - if {[info exists tcltest($platform.$build)]} { - set clist $tcltest($platform.$build) + if {[info exists tcltest($platform.$bld)]} { + set clist $tcltest($platform.$bld) if {$clist=="all"} { set clist $all_configs } elseif {$clist=="all_plus_autovacuum_crash"} { @@ -123,12 +368,12 @@ proc trd_configs {platform build} { set clist } -proc trd_extras {platform build} { +proc trd_extras {platform bld} { trd_import set elist [list] - if {[info exists extra($platform.$build)]} { - set elist $extra($platform.$build) + if {[info exists extra($platform.$bld)]} { + set elist $extra($platform.$bld) } set elist @@ -139,5 +384,180 @@ proc trd_all_configs {} { set all_configs } +proc trimscript {text} { + set text [string map {"\n " "\n"} [string trim $text]] +} + +proc make_sh_script {srcdir opts cflags makeOpts configOpts} { + + set tcldir [::tcl::pkgconfig get libdir,install] + set myopts "" + if {[info exists ::env(OPTS)]} { + append myopts "# From environment variable:\n" + append myopts "OPTS=$::env(OPTS)\n" + } + foreach o [lsort $opts] { + append myopts "OPTS=\"\$OPTS $o\"\n" + } + + return [trimscript [subst -nocommands { + set -e + if [ "\$#" -ne 1 ] ; then + echo "Usage: \$0 " + exit -1 + fi + + SRCDIR="$srcdir" + TCLDIR="$tcldir" + + if [ ! -f Makefile ] ; then + \$SRCDIR/configure --with-tcl=\$TCL $configOpts + fi + + $myopts + CFLAGS="$cflags" + + make \$1 "CFLAGS=\$CFLAGS" "OPTS=\$OPTS" $makeOpts + }]] +} + +# Generate the text of a *.bat script. +# +proc make_bat_file {srcdir opts cflags makeOpts} { + set srcdir [file nativename [file normalize $srcdir]] + + return [trimscript [subst -nocommands { + set TARGET=%1 + set TMP=%CD% + nmake /f $srcdir\\Makefile.msc TOP="$srcdir" %TARGET% "CCOPTS=$cflags" "OPTS=$opts" $makeOpts + }]] +} + + +# Generate the text of a shell script. +# +proc make_script {cfg srcdir bMsvc} { + set opts [list] ;# OPTS value + set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value + set makeOpts [list] ;# Extra args for [make] + set configOpts [list] ;# Extra args for [configure] + + # Define either SQLITE_OS_WIN or SQLITE_OS_UNIX, as appropriate. + if {$::tcl_platform(platform)=="windows"} { + lappend opts -DSQLITE_OS_WIN=1 + } else { + lappend opts -DSQLITE_OS_UNIX=1 + } + + # Unless the configuration specifies -DHAVE_USLEEP=0, set -DHAVE_USLEEP=1. + # + if {[lsearch $cfg "-DHAVE_USLEEP=0"]<0} { + lappend cfg -DHAVE_USLEEP=1 + } + + # Loop through the parameters of the nominated configuration, updating + # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as + # follows: + # + # 1. If the parameter begins with "-D", add it to $opts. + # + # 2. If the parameter begins with "--" add it to $configOpts. Unless + # this command is preparing a script for MSVC - then add an + # equivalent to $makeOpts or $opts. + # + # 3. If the parameter begins with "-" add it to $cflags. If in MSVC + # mode and the parameter is an -O option, instead add + # an OPTIMIZATIONS= switch to $makeOpts. + # + # 4. If none of the above apply, add the parameter to $makeOpts + # + foreach param $cfg { + + if {[string range $param 0 1]=="-D"} { + lappend opts $param + continue + } + + if {[string range $param 0 1]=="--"} { + if {$bMsvc==0} { + lappend configOpts $param + } else { + + switch -- $param { + --disable-amalgamation { + lappend makeOpts USE_AMALGAMATION=0 + } + --disable-shared { + lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 + } + --enable-fts5 { + lappend opts -DSQLITE_ENABLE_FTS5 + } + --enable-shared { + lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 + } + --enable-session { + lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK + lappend opts -DSQLITE_ENABLE_SESSION + } + --enable-all { + } + --enable-debug { + # lappend makeOpts OPTIMIZATIONS=0 + lappend opts -DSQLITE_DEBUG + } + default { + error "Cannot translate $param for MSVC" + } + + } + } + + continue + } + + if {[string range $param 0 0]=="-"} { + if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { + lappend makeOpts OPTIMIZATIONS=$level + } else { + lappend cflags $param + } + continue + } + + lappend makeOpts $param + } + + if {$bMsvc==0} { + set zRet [make_sh_script $srcdir $opts $cflags $makeOpts $configOpts] + } else { + set zRet [make_bat_file $srcdir $opts $cflags $makeOpts] + } +} + +# Usage: +# +# trd_buildscript CONFIG SRCDIR MSVC +# +# This command returns the full text of a script (either a shell script or +# an ms-dos bat file) that may be used to build SQLite source code according +# to a nominated configuration. +# +# Parameter CONFIG must be a configuration defined above in the ::trd::build +# array. SRCDIR is the root directory of an SQLite source tree (the parent +# directory of that containing this script). MSVC is a boolean - true to +# use the MSVC compiler, false otherwise. +# +proc trd_buildscript {config srcdir bMsvc} { + trd_import + + # Ensure that the named configuration exists. + if {![info exists build($config)]} { + error "No such build config: $config" + } + + # Generate and return the script. + return [make_script $build($config) $srcdir $bMsvc] +} diff --git a/test/timediff1.test b/test/timediff1.test new file mode 100644 index 000000000..f8176d881 --- /dev/null +++ b/test/timediff1.test @@ -0,0 +1,222 @@ +# 2023-05-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. The +# focus of this file is testing date and time functions. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Skip this whole file if date and time functions are omitted +# at compile-time +# +ifcapable {!datetime} { + finish_test + return +} + +proc datetest {tnum expr result} { + do_test timediff-$tnum [subst { + execsql "SELECT coalesce($expr,'NULL')" + }] [list $result] +} +set tcl_precision 15 + + +# February overflow on a leap year +datetest 1.1 {datetime('2000-01-31','+1 month')} {2000-03-02 00:00:00} +datetest 1.2 {datetime('2004-01-29','+1 month')} {2004-02-29 00:00:00} +datetest 1.3 {datetime('2000-03-01','-1 day')} {2000-02-29 00:00:00} +datetest 1.4 {datetime('2000-03-31','-1 month')} {2000-03-02 00:00:00} +datetest 1.5 {datetime('2000-03-30','-1 month')} {2000-03-01 00:00:00} +datetest 1.6 {datetime('2000-03-29','-1 month')} {2000-02-29 00:00:00} +datetest 1.7 {datetime('2000-03-28','-1 month')} {2000-02-28 00:00:00} +datetest 1.8 {datetime('2000-02-29','+1 year')} {2001-03-01 00:00:00} +datetest 1.9 {datetime('2000-02-29','+4 years')} {2004-02-29 00:00:00} + +datetest 1.10 {datetime('1998-11-10','+0001-03-19 12:34:56')} \ + {2000-02-29 12:34:56} +datetest 1.11 {datetime('2000-01-31','+0004-01-00 12:34:56')} \ + {2004-03-02 12:34:56} +datetest 1.12 {datetime('2000-01-29','+0008-01-00 12:34:56')} \ + {2008-02-29 12:34:56} +datetest 1.13 {datetime('2001-03-31','-0001-01-00 06:10')} \ + {2000-03-01 17:50:00} + + +# February overflow on a non-leap year +datetest 2.1 {datetime('2001-01-31','+1 month')} {2001-03-03 00:00:00} +datetest 2.2 {datetime('2005-01-29','+1 month')} {2005-03-01 00:00:00} +datetest 2.3 {datetime('2001-03-01','-1 day')} {2001-02-28 00:00:00} +datetest 2.4 {datetime('2001-03-31','-1 month')} {2001-03-03 00:00:00} +datetest 2.5 {datetime('2001-03-30','-1 month')} {2001-03-02 00:00:00} +datetest 2.6 {datetime('2001-03-29','-1 month')} {2001-03-01 00:00:00} +datetest 2.7 {datetime('2001-03-28','-1 month')} {2001-02-28 00:00:00} + +datetest 2.10 {datetime('1999-11-10','+0001-03-19 12:34:56')} \ + {2001-03-01 12:34:56} +datetest 2.11 {datetime('2000-01-31','+0005-01-00 12:34:56')} \ + {2005-03-03 12:34:56} +datetest 2.12 {datetime('2000-01-29','+0009-01-00 12:34:56')} \ + {2009-03-01 12:34:56} +datetest 2.13 {datetime('2002-03-31','-0001-01-00 06:10')} \ + {2001-03-02 17:50:00} + +# timediff +datetest 3.1 {timediff('2000-03-02','2000-01-31')} {+0000-01-00 00:00:00.000} +datetest 3.2 {timediff('2000-01-31','2000-03-02')} {-0000-01-02 00:00:00.000} +datetest 3.3 {timediff('2000-03-02','1999-01-31')} {+0001-01-00 00:00:00.000} +datetest 3.4 {timediff('1999-01-31','2000-03-02')} {-0001-01-02 00:00:00.000} + +unset -nocomplain p1 +unset -nocomplain p2 +set p1 { + 0 {-4713-11-24 12:00:00} + 1 {-2000-04-30 05:19:26} + 2 {0000-01-01 12:34:56} + 3 {1776-07-04 13:00:00} + 4 {1969-07-20 20:17} + 5 {2440587.5} + 6 {2000-05-29 14:26} + 7 {2023-05-29 18:11} + 8 {2050-05-29 14:26} + 9 {4796-02-29 11:23:55.46} +} +set p2 { + A {1066-10-14} + B {1900-02-28 11:00} + C {1900-03-01 12:00} + D {1904-02-29 11:25} + E {2000-02-29 13:00} + E {2000-03-01 14:00} + F {2001-03-31 15:15} + G {2002-04-01 16:59} + H {2003-04-30 17:00} + I {2004-05-01 23:59:59} + J {2005-06-01} + K {2006-06-30 01:23:45} + L {2007-12-31 02:00} + M {2008-01-01 01:59} + N {3152-07-04 12:00} + P {9999-12-31 23:59:59} +} + +foreach {x1 d1} $p1 { + foreach {x2 d2} $p2 { + set r1 [db one {SELECT datetime($d1)}] + do_execsql_test timediff-4-$x1$x2 { + SELECT datetime($d2, timediff($d1,$d2)); + } [list $r1] + set r2 [db one {SELECT datetime($d2)}] + do_execsql_test timediff-4-$x2$x1 { + SELECT datetime($d1, timediff($d2,$d1)); + } [list $r2] + } +} + +# Partial time-diffs as modifiers +# +datetest 5-1 {datetime('2000-01-01','+0001-02-03')} {2001-03-04 00:00:00} +datetest 5-2 {datetime('2000-01-01','+0001-02-03x')} {NULL} +datetest 5-3 {datetime('2000-01-01','+0001-11-03')} {2001-12-04 00:00:00} +datetest 5-4 {datetime('2000-01-01','+0001-12-03')} {NULL} +datetest 5-5 {datetime('2000-01-01','+0001-02-30')} {2001-03-31 00:00:00} +datetest 5-6 {datetime('2000-01-01','+0001-02-31')} {NULL} +datetest 5-7 {datetime('2000-01-01','+0001-02-03 0')} {NULL} +datetest 5-8 {datetime('2000-01-01','+0001-02-03 01')} {NULL} +datetest 5-9 {datetime('2000-01-01','+0001-02-03 01:')} {NULL} +datetest 5-10 {datetime('2000-01-01','+0001-02-03 01:0')} {NULL} +datetest 5-11 {datetime('2000-01-01','+0001-02-03 01:02')} {2001-03-04 01:02:00} +datetest 5-12 {datetime('2000-01-01','+0001-02-03 01:02:')} {NULL} +datetest 5-13 {datetime('2000-01-01','+0001-02-03 01:02:0')} {NULL} +datetest 5-14 {datetime('2000-01-01','+0001-02-03 01:02:03')} \ + {2001-03-04 01:02:03} +datetest 5-15 {datetime('2000-01-01','+0001-02-03 01:02:03.')} NULL +datetest 5-16 {datetime('2000-01-01','+0001-02-03 01:02:03.5')} \ + {2001-03-04 01:02:03} +datetest 5-17 {datetime('2000-01-01','+0001-02-03 01:02:03.50')} \ + {2001-03-04 01:02:03} +datetest 5-18 {datetime('2000-01-01','+0001-02-03 01:02:03.500')} \ + {2001-03-04 01:02:03} +datetest 5-19 {datetime('2000-01-01','+0001-02-03 01:02:03.500x')} {NULL} +datetest 5-20 {datetime('2000-01-01','+0001-02-03 01:02:03.500 x')} {NULL} + +unset -nocomplain p1 +unset -nocomplain p2 +set p1 { + a {2000-01-01 00:00:00} + b {2000-01-31 23:59:59} + c {2000-02-01 00:00:00} + d {2000-02-29 23:59:59} + e {2000-03-01 00:00:00} + f {2000-03-31 23:59:59} + g {2000-04-01 00:00:00} + h {2000-04-30 23:59:59} + i {2000-05-01 00:00:00} + j {2000-05-31 23:59:59} + k {2000-06-01 00:00:00} + l {2000-06-30 23:59:59} + m {2000-07-01 00:00:00} + n {2000-07-31 23:59:59} + o {2000-08-01 00:00:00} + p {2000-08-31 23:59:59} + q {2000-09-01 00:00:00} + r {2000-09-30 23:59:59} + s {2000-10-01 00:00:00} + t {2000-10-31 23:59:59} + u {2000-11-01 00:00:00} + v {2000-11-30 23:59:59} + w {2000-12-01 00:00:00} + x {2000-12-31 23:59:59} +} +set p2 { + A {2001-01-01 00:00:00} + B {2001-01-31 23:59:59} + C {2001-02-01 00:00:00} + D {2001-02-28 23:59:59} + E {2001-03-01 00:00:00} + F {2001-03-31 23:59:59} + G {2001-04-01 00:00:00} + H {2001-04-30 23:59:59} + I {2001-05-01 00:00:00} + J {2001-05-31 23:59:59} + K {2001-06-01 00:00:00} + L {2001-06-30 23:59:59} + M {2001-07-01 00:00:00} + N {2001-07-31 23:59:59} + O {2001-08-01 00:00:00} + P {2001-08-31 23:59:59} + Q {2001-09-01 00:00:00} + R {2001-09-30 23:59:59} + S {2001-10-01 00:00:00} + T {2001-10-31 23:59:59} + U {2001-11-01 00:00:00} + V {2001-11-30 23:59:59} + W {2001-12-01 00:00:00} + X {2001-12-31 23:59:59} +} + +foreach {x1 d1} $p1 { + foreach {x2 d2} $p2 { + set r1 [db one {SELECT datetime($d1)}] + do_execsql_test timediff-6-$x1$x2 { + SELECT datetime($d2, timediff($d1,$d2)); + } [list $r1] + set r2 [db one {SELECT datetime($d2)}] + do_execsql_test timediff-6-$x2$x1 { + SELECT datetime($d1, timediff($d2,$d1)); + } [list $r2] + } +} + + + +finish_test diff --git a/test/update.test b/test/update.test index 47b6029be..bf7666662 100644 --- a/test/update.test +++ b/test/update.test @@ -764,5 +764,27 @@ do_execsql_test update-21.4 { SELECT * FROM t1 ORDER BY vkey, c5; ROLLBACK; } {6 -54 100 NULL} +# Follow-up on 2023-07-31 (forum post https://sqlite.org/forum/forumpost/8ab195fd44e75ed0): +# Only disable one-pass if the subquery is in the WHERE clause. The SET expressions +# do not count. +do_execsql_test update-21.11 { + DROP TABLE t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); + CREATE TABLE t2(d INT); +} +do_eqp_test update-21.12 { + WITH t3(x,y) AS (SELECT d, row_number()OVER() FROM t2) + UPDATE t1 SET b=(SELECT y FROM t3 WHERE t1.a=t3.x); +} { + QUERY PLAN + |--SCAN t1 + `--CORRELATED SCALAR SUBQUERY xxxxxx + |--CO-ROUTINE t3 + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN t2 + | `--SCAN (subquery-xxxxxx) + |--BLOOM FILTER ON t3 (x=?) + `--SEARCH t3 USING AUTOMATIC COVERING INDEX (x=?) +} finish_test diff --git a/test/upfrom4.test b/test/upfrom4.test index b20e9638a..222823628 100644 --- a/test/upfrom4.test +++ b/test/upfrom4.test @@ -127,4 +127,30 @@ do_execsql_test 400 { 1000 - - - } +# Forum post https://sqlite.org/forum/forumpost/36ff78b2a3 +# +ifcapable update_delete_limit { + reset_db + do_execsql_test 500 { + CREATE TABLE t1(abc INT, def INT); + INSERT INTO t1 VALUES(0,0); + INSERT INTO t1 VALUES(0,0); + INSERT INTO t1 VALUES(0,0); + CREATE TABLE dual(dummy TEXT); + INSERT INTO dual(dummy) VALUES('X'); + } {} + + do_execsql_test 510 { + UPDATE t1 + SET (abc, def)=(SELECT x, 123) + FROM dual LEFT JOIN (SELECT 789 AS 'x' FROM dual) AS d2 + LIMIT 2 + } + + do_execsql_test 520 { + SELECT * FROM t1 + } {789 123 789 123 0 0} +} + + finish_test diff --git a/test/upsert1.test b/test/upsert1.test index a321d6171..781831133 100644 --- a/test/upsert1.test +++ b/test/upsert1.test @@ -255,4 +255,17 @@ do_execsql_test upsert1-1100 { SELECT * FROM t1; } {1 22} +# 2023-08-17 dbsqlfuzz 9983e2c77634a8ccf33b5c91fa9982599de5f9e9 +# Bound parameters in the ON CONFLICT clause of an UPSERT. +# +reset_db +do_execsql_test upsert1-1200 { + CREATE TABLE t1(a INT, b INT); + CREATE UNIQUE INDEX t1x ON t1(b+3); +} +sqlite3_db_config db ENABLE_QPSG 1 +do_catchsql_test upsert1-1210 { + INSERT INTO t1(a,b) VALUES(1,2) ON CONFLICT(b+?1) DO NOTHING; +} {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}} + finish_test diff --git a/test/walseh1.test b/test/walseh1.test new file mode 100644 index 000000000..c3a655f53 --- /dev/null +++ b/test/walseh1.test @@ -0,0 +1,150 @@ +# 2021 August 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix walseh1 + +set ::seh_countdown 0 +set ::seh_errno 10 +proc seh_faultsim_callback {iFault} { + if {$iFault==650} { + incr ::seh_countdown -1 + if {$::seh_countdown==0} { return $::seh_errno } + } + return 0 +} + +proc seh_injectinstall {} { + sqlite3_test_control_fault_install seh_faultsim_callback +} +proc seh_injectuninstall {} { + sqlite3_test_control_fault_install +} +proc seh_injectstart {iFail} { + set ::seh_errno [expr 2+$iFail*10] + set ::seh_countdown $iFail +} +proc seh_injectstop {} { + set res [expr $::seh_countdown<=0] + set ::seh_countdown 0 + set res +} + +set FAULTSIM(seh) [list \ + -injectinstall seh_injectinstall \ + -injectstart seh_injectstart \ + -injectstop seh_injectstop \ + -injecterrlist {{1 {disk I/O error}}} \ + -injectuninstall seh_injectuninstall \ +] + +proc test_system_errno {db expect} { + set serrno [sqlite3_system_errno $db] + if {$serrno!=$expect} { + error "surprising system_errno. Expected $expect, got $serrno" + } +} + +do_execsql_test 1.0 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); +} {wal} +faultsim_save_and_close + +do_faultsim_test 1 -faults seh -prep { + catch { db2 close } + faultsim_restore_and_reopen + execsql { SELECT * FROM sqlite_schema } + sqlite3 db2 test.db +} -body { + execsql { SELECT * FROM t1 } db2 +} -test { + faultsim_test_result {0 {1 2 3 4}} + if {$testrc} { test_system_errno db2 $::seh_errno } +} +catch { db2 close } + +faultsim_save_and_close + +do_faultsim_test 2 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4}} + if {$testrc} { test_system_errno db $::seh_errno } +} + +do_faultsim_test 3 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { INSERT INTO t1 VALUES(5, 6) } + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4 5 6}} + if {$testrc} { test_system_errno db $::seh_errno } +} + +do_faultsim_test 4 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { PRAGMA wal_checkpoint } + execsql { INSERT INTO t1 VALUES(7, 8) } + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4 7 8}} + if {$testrc} { test_system_errno db $::seh_errno } +} +catch { db close } + +do_faultsim_test 5 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen + execsql { + PRAGMA cache_size = 5; + BEGIN; + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50 + ) + INSERT INTO t1 SELECT randomblob(500), randomblob(500) FROM s; + } +} -body { + execsql ROLLBACK +} -test { + faultsim_test_result {0 {}} + if {$testrc} { test_system_errno db $::seh_errno } +} +catch { db close } + +do_faultsim_test 6 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { PRAGMA wal_checkpoint = TRUNCATE } + execsql { INSERT INTO t1 VALUES(7, 8) } + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4 7 8}} + if {$testrc} { test_system_errno db $::seh_errno } +} +catch { db close } + +finish_test + + diff --git a/test/where7.test b/test/where7.test index f76e8aba8..681684b80 100644 --- a/test/where7.test +++ b/test/where7.test @@ -47,18 +47,33 @@ do_test where7-1.1 { SELECT * FROM t1; } } {1 2 3 4 2 3 4 5 3 4 6 8 4 5 10 15 5 10 100 1000} -do_execsql_test where7-1.1.1 { - CREATE TABLE t(a); - CREATE INDEX ta ON t(a); - INSERT INTO t(a) VALUES(1),(2); - SELECT * FROM t ORDER BY a; - SELECT * FROM t WHERE a<2 OR a<3 ORDER BY a; - PRAGMA count_changes=ON; - DELETE FROM t WHERE a<2 OR a<3; - SELECT * FROM t; - PRAGMA count_changes=OFF; - DROP TABLE t; -} {1 2 1 2 2} +if {[permutation] != "no_optimization"} { + do_execsql_test where7-1.1.1 { + CREATE TABLE t(a); + CREATE INDEX ta ON t(a); + INSERT INTO t(a) VALUES(1),(2); + SELECT * FROM t ORDER BY a; + SELECT * FROM t WHERE a<2 OR a<3 ORDER BY a; + PRAGMA count_changes=ON; + DELETE FROM t WHERE a<2 OR a<3; + SELECT * FROM t; + PRAGMA count_changes=OFF; + DROP TABLE t; + } {1 2 1 2 2} +} else { + do_execsql_test where7-1.1.1-noopt { + CREATE TABLE t(a); + CREATE INDEX ta ON t(a); + INSERT INTO t(a) VALUES(1),(2); + SELECT * FROM t ORDER BY a; + SELECT * FROM t WHERE a<2 OR a<3 ORDER BY a; + PRAGMA count_changes=ON; + DELETE FROM t WHERE a<2 OR a<3; + SELECT * FROM t; + PRAGMA count_changes=OFF; + DROP TABLE t; + } {1 2 1 2 3} +} do_test where7-1.2 { count_steps { SELECT a FROM t1 WHERE b=3 OR c=6 ORDER BY a diff --git a/test/wherelimit2.test b/test/wherelimit2.test index 8e39127ac..57288bf64 100644 --- a/test/wherelimit2.test +++ b/test/wherelimit2.test @@ -299,5 +299,35 @@ do_test 5.5 { set ::log } {ax a bx b cx c dx d ex a} +#----------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t2(x); + INSERT INTO t2(x) VALUES(1),(2),(3),(5),(8),(13); +} {} + +do_execsql_test 6.1 { + WITH t2 AS MATERIALIZED (VALUES(5)) + DELETE FROM t2 ORDER BY rank()OVER() LIMIT 2; +} + +do_execsql_test 6.2 { + SELECT * FROM t2; +} {3 5 8 13} + +#------------------------------------------------------------------------- + +do_execsql_test 7.0 { + CREATE TABLE t1(a INT); INSERT INTO t1(a) VALUES(0); +} {} + +do_execsql_test 7.1 { + WITH t1(b) AS (SELECT * FROM (SELECT * FROM (VALUES(2)))) + UPDATE t1 SET a=3 LIMIT 1; +} + +do_execsql_test 7.2 { + SELECT * FROM t1; +} {3} finish_test diff --git a/test/window1.test b/test/window1.test index 783a739e3..37a5183f9 100644 --- a/test/window1.test +++ b/test/window1.test @@ -2363,4 +2363,17 @@ do_execsql_test 76.5 { SELECT (SELECT max(y)+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y GROUP BY x; } {100 {} 400} +# 2023-05-23 https://sqlite.org/forum/forumpost/fbfe330a20 +# +reset_db +do_execsql_test 77.1 { + CREATE TABLE t1(x INT); + CREATE INDEX t1x ON t1(likely(x)); + INSERT INTO t1 VALUES(1),(2),(4),(8); +} +do_execsql_test 77.2 { + SELECT max(~likely(x)) FILTER (WHERE true) FROM t1 INDEXED BY t1x GROUP BY x; +} {-2 -3 -5 -9} + + finish_test diff --git a/tool/build-shell.sh b/tool/build-shell.sh index 6a48299d7..a57f75303 100644 --- a/tool/build-shell.sh +++ b/tool/build-shell.sh @@ -16,7 +16,6 @@ gcc -o sqlite3 -g -Os -I. \ -DSQLITE_ENABLE_FTS4 \ -DSQLITE_ENABLE_RTREE \ -DHAVE_READLINE \ - -DHAVE_USLEEP=1 \ ../sqlite/src/shell.c \ ../sqlite/src/test_vfstrace.c \ sqlite3.c -ldl -lreadline -lncurses diff --git a/tool/custom.txt b/tool/custom.txt new file mode 100644 index 000000000..b9c8a9858 --- /dev/null +++ b/tool/custom.txt @@ -0,0 +1,1246 @@ +aa +aaa +abc +abcdefg +abd +abf +Abortable +acc +accessor +accum +acd +activecpu +Adaptor +Additionallly +addop +addoptrace +addr +adjustements +af +aff +afp +afterward +Agg +agg +agginfo +alikes +Alloc +alloc +alloca +allocator +allocators +alphabetics +alphanumerics +alternateform +altertab +altform +amalgamator +amongst +analyse +antipenultimate +ap +api +appdef +appendall +appendchar +appendf +ar +arg +argc +argcount +arglist +argn +args +argv +arrayname +ascii +asm +async +atoi +atomics +auth +authorizer +autocheckpoint +autocommit +autoconf +autoext +autoextension +autoinc +autoincrement +autoincremented +autoindex +autoinstall +autovac +autovacuum +autovacuumed +autovacuuming +auxdata +awk +Ax +backend +backends +backfill +backfilled +backfilling +backtrace +backtraces +backtracing +bb +bba +bcb +bcc +beginthreadex +behavior +behavioral +behaviors +benigncnt +bg +bigblob +bitcount +bitfield +bitmask +bitmasks +bitset +bitvec +bitvecs +bitwise +blobwrite +blockquote +Bloomfilter +bom +boolean +booleans +Borland +br +breadthfirstsearch +breakpoint +bt +btree +btrees +buf +bufpt +butoindex +bytearray +bytecode +bytecodevtab +byteorder +cacheflush +cachegrind +Cachesize +calc'ing +callgrind +cardinalities +cardinality +carray +cb +cd +cdbaa +ce +ceil +cellpadding +cellspacing +center +chacha +changecounter +changeset +characgter +characterset +checkpointed +Checkpointer +checkpointer +checkpointers +checkpointing +checksummed +checksums +chmod +chng +chown +chroot +chsize +chunksize +cid +cis +ckalloc'd +ckpt +cksum +cksumvfs +clientdata +closedir +clownshoe +cmd +cmp +cmpaff +Cmpr +cnt +codec +codepage +collseq +colname +compileoption +concat +config +confstr +connetion +consective +considertion +const +coredump +coroutine +coroutines +cov +crashparams +csr +csv +Cte +ctime +Ctrl +ctrl +ctx +ctype +cume +Cx +cx +Cygwin +cygwin +dan +darkstar +databasename +databse +datasource +datatypes +datetime +dbfuzz +dbinfo +dbname +dbpage +dbpagevfs +dbs +dbsize +dbsqlfuzz +dbstat +dbtotxt +De +de +deadman +deallocate +deallocated +deallocates +deallocating +deallocation +decltype +decrementing +defense +defenses +defn +defragment +defragmentation +defragmented +deinitialization +Deinitialize +deinitialize +demovfs +dependences +dequote +dequoted +dequoting +dereferenced +dereferences +desc +deserialization +deserialize +deserialized +deserializes +deserializing +dest +destructor +destructors +deterministically +dev +devsim +devsym +df +Dfdatasync +dflt +dir +directonly +dirent +dirs +disjunct +disjunction +disjuncts +diskfull +divy +dl +dll +dlopen +dlsym +docid +docids +dont +dontcache +dotfile +dotlock +doublearray +drh +dryrun +dstr +dt +Duint +dup +Durian +dword +Dx +dylib +Dyn +Ec +ec +ecel +editability +ef +efc +eg +Ei +elif +emcc +emscripten +encodings +endeavor +endfor +endian +endianness +endif +endthreadex +enum +eof +eph +Ephem +eq +eqp +equaling +equalities +errcode +errmsg +errno +errorcode +erroroffset +errstr +esc +esign +et +etfs +etilqs +eval +exe +expander +explainer +expmask +expr +exprlist +extern +fakeheap +fallocate +fanout +faultsim +favor +favors +fb +fc +fchmod +fchown +fclose +fcntl +fd +fdatasync +feb +fef +Feijoa +ferror +ffff +ffffeff +ffffffe +fffffff +ffffffff +fffffffffffffff +fflush +fg +fgets +fi +fibonacci +fid +Fifo +filecount +filectrl +filemapping +filesize +filesystem +filesystems +finalised +finalizer +findfirst +findnext +fixup +fk +fkctr +fkey +flattener +fmt +fopen +foreach +formatter +fprintf +fputs +fread +fred +fred's +freeblock +freeblocks +freelist +freepage +freespace +frombind +fs +fsanitize +fsctl +fsdir +fseek +fstat +fstree +fsync +fsynced +fsyncs +ftell +ftruncate +fts +fullfsync +fullname +fullschema +fullshm +fullsync +fullsyncs +func +funcs +fuzzer +fuzzers +fwrite +Fx +gcc +gcov +gdb +getcwd +getenv +gethostuuid +getpagesize +getpid +getrusage +getsubtype +getter +getters +gibabyte +gid +glibc +globbing +gmtime +Gosub +Goto +goto +groupbyparent +Groupid +growable +grp +hdl +hdr +hexdb +hexdouble +hexio +highwater +hijklmno +honor +honored +honoring +hostid +href +html +htsize +hw +Hwtime +icecube +ideographical +idx +idxaff +idxnum +idxstr +idx'th +ieee +ifdef +iff +ifndef +imm +impl +incr +incrblob +incrementing +indexable +indexname +infop +ing +init +initfail +initializer +initializers +initiallly +inlined +inlines +inlining +ino +inode +inodes +inopertune +installable +intarray +inteface +interoperate +interp +interpretable +intkey +intptr +intreal +intrinsics +invalidations +invariants +io +ioerr +iotrace +ipk +iplan +isalnum +isalpha +isatty +isdigit +isempty +isexplain +ismemdb +isnan +isspace +isxdigit +i'th +iversion +jfd +jj +jointype +jointypes +journaled +journaling +journalled +journalling +journalmode +jrnl +jsl +json +jt +julian +julianday +keyinfo +keywordhash +kibibytes +kvstorage +kvvfs +lappend +lasterrno +lbl +ldl +le +leafdata +leftjustify +len +leveling +lexeme +lexemes +lhs +li +libm +libversion +lifecycle +lindex +lineno +Linenoise +linux +lised +lld +llvm +lm +ln +lncurses +loadext +localhost +localtime +lockd +lockdata +lockingcontext +lockless +lockproxy +locktype +logmsg +longvalue +longwords +lookahead +Lookaside +lookaside +lookups +losslessly +lpthread +lrc +lreadline +lru +lseek +lt +lvalue +lwr +makefile +makefiles +malloc +malloc'd +malloced +mallocs +manditory +manpage +matchinfo +materializations +mathfuncs +maxsize +mbcs +Mcafee +md +Meeus +mem +memcmp +memcpy +memdb +memdebug +memget +memmove +mempool +memset +memstatus +memsys +memvfs +mergeable +mergepatch +middleware +millisec +mincore +mingw +mis +miscoded +mj +mkctimec +mkdir +mkkeywordhash +mkopcodec +mkopcodeh +mkpragmatab +mmap +mmapped +mno +modeof +Movepage +mprintf +msd +msdos +msec +msg +msgs +msize +msvc +mtime +mult +multibyte +multiplex'ed +multiplexor +multithreaded +multiwrite +mutex +mutexes +mutexing +mx +mxpathname +myprefix +nal +namecontext +namespace +natively +nbr +nbsp +ncell +ncol +ncycle +nd +ndlt +neighbors +neq +nestable +newrowid +nfs +nlt +nnn +nocase +nochange +nochng +nofollow +nolock +nomem +nomutex +nonblocking +nonroot +noop +noshm +notational +notheld +notnull +nowrap +nr +ntile +nul +nullable +nullif +nullvalue +Num +objproc +oc +offsetof +ofst +ogham +oid +Ok +ok +ol +onecolumn +onepass +onoff +onwards +oom +opcodesnever +openable +opendir +optimizers +optimizible +orconf +orderby +orderbylist +os +Oswrite +overread +overreads +overrideable +oversize +overwriteable +ovewrite +ovfl +pagecache +pagecaches +pagecount +pagelist +pageno +pagesize +pagetype +Param +params +passwd +patchset +pathname +pathnames +pc +pcache +pclose +pcx +pgno +pgoffset +pgsize +pgsz +pid +pluggable +pmasz +pn +popen +pos +posix +Postgres +Powersafe +powersafe +pq +pqr +pqrstuvw +pragma +pragmaed +pragma's +pragmas +pre +pread +preallocate +preallocated +precisions +precompiled +precomputed +prefetch +prefetched +preformated +preformatted +prepend +prepended +prepending +prepends +prepopulate +preprocess +preprocessed +preprocessing +preprocessor +prereq +prereqs +preupdate +primarykey +printf +printfs +prng +proc +procs +profiler +proleptic +proxying +proxys +pseudocode +pseudotable +psow +psz +pthread +pthreads +ptr +ptrmap +ptrs +purgeable +putsnl +pwrite +pz +qbox +Qcircle +quotaed +quotefix +radix +randomblob +randstr +rarr +rc +rcauth +Rcvr +rds +readdir +readline +readlock +readonly +Readr +realloc +reallocs +realvalue +rebalance +rebalanced +recoverability +redefinable +redux +reenabled +reentrant +refcount +refcounts +regs +reindexed +reinitializer +reinitializes +rekey +rekeyed +relevancies +relink +relink +relock +renormalize +reoptimize +reparse +reparse +reparsed +reparsing +reportable +reprepare +reprepare +reprepared +reprepares +representable +repurpose +Req'd +requeries +requote +reregister +reseek +reservebytes +resumable +retarget +retargeted +retrys +returntype +rfc +rhs +Ri +Rivest +ro +rootpage +rowid +rowids +Rowkey +rownumber +rowset +runtime +rw +rwc +samplelib +sampleno +sandboxed +sandboxing +savepoint +savepoints +scanf +scanstats +scanstatus +schemas +sectorsize +selecttrace +selftest +setrlimit +setsubtype +settitle +sharable +shm +shmlock +sibs +sig +signaling +significand +sizehint +Sizeof +sizeof +snprintf +Solaris +sorterref +soundex +sourceid +speedtest +sprintf +sql +sqlar +sqlite +sqliteplocks +sqliterc +sqllog +sqllogglobal +sqlthread +sqr +sqrt +src +srcck +statfs +stderr +stdin +stdout +stmt +stmts +str +strace +strcasecmp +strcmp +strdup +strerror +strftime +strglob +stricmp +stringify +strlen +strlike +strncmp +strncpy +strnicmp +stronly +strstr +struct +structs +subbitmap +subcases +subclassed +subclauses +subcomponents +subdirectory +subelement +subexpression +subexpressions +subfunction +subfunctions +subitem +subjournals +sublevels +subnode +suboptimal +subpages +subprocedures +subprog +subprogs +subq +subqueries +subquery +Subrtn +subsec +subsecond +subsequence +substr +substring +substrings +subsubstructure +subsubterms +Subtask +subtasks +subterm +subterms +subtransaction +subtransactions +subtree +subtrees +subtype +subtypes +sumint +superclass +superlock +superset +superunlock +symlinks +synching +sys +syscall +sz +szosfile +tablename +tailval +tailvar +tbl +tblname +Tcl +tcl +Tclcmd +tclcmd +tclsh +tclsqlite +tclvar +td +tempfilename +testcase +testctrl +testfixture +testtag +testvfs +textarray +th +threadid +threadsafe +threadsafety +throughs +tht +timediff +tkt +tm +tmp +tmpdir +tmpfs +tnum +Todo +tokenize +tokenizer +tokenizing +tolower +toobig +toupper +treetrace +treeview +trimleft +trimright +truesize +trys +Tsd +Ts'o +tunable +tvfs +txn +txt +Typecheck +typedef +typedefed +typedefs +typename +typenames +typeof +tz +uber +uid +uint +ul +umask +Un +un +unallocated +unanalyzed +unary +unbuffered +unclosed +uncompiled +uncomputed +undefining +underfull +unexpanded +unfinalized +unfreed +unhex +unicode +unindexed +uninit +uninitialize +unintuitive +unioned +unissued +unix +unixepoch +unlink +unlinked +unlinking +unlinks +unmap +unmapped +unmapping +unoptimized +unparsed +unreduced +unref +unreferenced +unrefs +unregister +unregistering +unregisters +unresolvable +unsynced +unterminated +untracked +untrusted +Upfrom +uppercasing +upr +Upsert +upsert +upto +uptr +uri +userauth +userdata +Userid +usleep +utc +Utf +utf +util +uuu +uuuuu +uuzzzz +va +valgrind +vanishingly +vappendf +vararg +varargs +varint +varints +varname +vcolumn +vdbe +vdbeapi +vdbe's +vdbes +vdbesort +ve +verifications +vfs +vfslog +vfsname +vfs's +vfstrace +vm +vmprintf +vmstep +vsnprintf +vt +vtab +vtabs +Vugt +vvv +vvvv +vvvvv +vvvvvv +vwait +vxworks +wal +wasm +wherecode +whereexpr +wheretrace +whitespace +Willmann +withoutrowid +wr +wrapup +writeable +writecrash +writefile +wsd +ww +wwww +wwzzzzyy +wxyz +xa +xac +xb +xbf +xc +xd +xdg +xe +xf +xfe +xfer +xff +xfff +xfffd +xfffe +xffffffff +x'hhhhhh +xinfo +xlc +xtype +xxxx +xxxxx +xxxxxx +xxxxxxx +xxxxxxxx +xyz +xyzzy +yy +yyxxxxxx +yyy +yyyyy +yyyyyy +zeroblob +Zeroblobs +zerodata +zeropad +zipfile +zipvfs +zplan +zulu +zzzz +zzzzyyyy diff --git a/tool/lemon.c b/tool/lemon.c index fb81292d4..7804837a0 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -426,7 +426,8 @@ struct lemon { int printPreprocessed; /* Show preprocessor output on stdout */ int has_fallback; /* True if any %fallback is seen in the grammar */ int nolinenosflag; /* True if #line statements should not be printed */ - char *argv0; /* Name of the program */ + int argc; /* Number of command-line arguments */ + char **argv; /* Command-line arguments */ }; #define MemoryCheck(X) if((X)==0){ \ @@ -1516,8 +1517,10 @@ void memory_error(void){ exit(1); } -static int nDefine = 0; /* Number of -D options on the command line */ -static char **azDefine = 0; /* Name of the -D macros */ +static int nDefine = 0; /* Number of -D options on the command line */ +static int nDefineUsed = 0; /* Number of -D options actually used */ +static char **azDefine = 0; /* Name of the -D macros */ +static char *bDefineUsed = 0; /* True for every -D macro actually used */ /* This routine is called with the argument to each -D command-line option. ** Add the macro defined to the azDefine array. @@ -1530,6 +1533,12 @@ static void handle_D_option(char *z){ fprintf(stderr,"out of memory\n"); exit(1); } + bDefineUsed = (char*)realloc(bDefineUsed, nDefine); + if( bDefineUsed==0 ){ + fprintf(stderr,"out of memory\n"); + exit(1); + } + bDefineUsed[nDefine-1] = 0; paz = &azDefine[nDefine-1]; *paz = (char *) malloc( lemonStrlen(z)+1 ); if( *paz==0 ){ @@ -1666,7 +1675,6 @@ int main(int argc, char **argv){ struct lemon lem; struct rule *rp; - (void)argc; OptInit(argv,options,stderr); if( version ){ printf("Lemon version 1.0\n"); @@ -1683,7 +1691,8 @@ int main(int argc, char **argv){ Strsafe_init(); Symbol_init(); State_init(); - lem.argv0 = argv[0]; + lem.argv = argv; + lem.argc = argc; lem.filename = OptArg(0); lem.basisflag = basisflag; lem.nolinenosflag = nolinenosflag; @@ -2856,6 +2865,10 @@ static int eval_preprocessor_boolean(char *z, int lineno){ res = 0; for(j=0; jargv0,templatename,0); + toFree = tpltname = pathsearch(lemp->argv[0],templatename,0); } if( tpltname==0 ){ fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", @@ -4396,7 +4409,17 @@ void ReportTable( fprintf(out, "/* This file is automatically generated by Lemon from input grammar\n" - "** source file \"%s\". */\n", lemp->filename); lineno += 2; + "** source file \"%s\"", lemp->filename); lineno++; + if( nDefineUsed==0 ){ + fprintf(out, ".\n*/\n"); lineno += 2; + }else{ + fprintf(out, " with these options:\n**\n"); lineno += 2; + for(i=0; i=2 ){\n"); - printf(" i = ((charMap(z[0])*%d) %c", HASH_C0, HASH_CC); + printf(" assert( n>=2 );\n"); + printf(" i = ((charMap(z[0])*%d) %c", HASH_C0, HASH_CC); printf(" (charMap(z[n-1])*%d) %c", HASH_C1, HASH_CC); printf(" n*%d) %% %d;\n", HASH_C2, bestSize); - printf(" for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){\n"); - printf(" if( aKWLen[i]!=n ) continue;\n"); - printf(" zKW = &zKWText[aKWOffset[i]];\n"); + printf(" for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){\n"); + printf(" if( aKWLen[i]!=n ) continue;\n"); + printf(" zKW = &zKWText[aKWOffset[i]];\n"); printf("#ifdef SQLITE_ASCII\n"); - printf(" if( (z[0]&~0x20)!=zKW[0] ) continue;\n"); - printf(" if( (z[1]&~0x20)!=zKW[1] ) continue;\n"); - printf(" j = 2;\n"); - printf(" while( j=2 ) keywordCode((char*)z, n, &id);\n"); printf(" return id;\n"); printf("}\n"); printf("#define SQLITE_N_KEYWORD %d\n", nKeyword); diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index bc1aadd19..6def8fa44 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -106,7 +106,30 @@ puts $out [subst \ ** if you want a wrapper to interface SQLite with your choice of programming ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. -*/ +**}] +set srcroot [file dirname [file dirname [info script]]] +if {$tcl_platform(platform)=="windows"} { + set vsrcprog src-verify.exe +} else { + set vsrcprog ./src-verify +} +if {[file executable $vsrcprog] && [file readable $srcroot/manifest]} { + set res [string trim [split [exec $vsrcprog -x $srcroot]] \n] + puts $out "** The content in this amalgamation comes from Fossil check-in" + puts -nonewline $out "** [string range [lindex $res 0] 1 35]" + if {[llength $res]==1} { + puts $out "." + } else { + puts $out " with changes in files:\n**" + foreach f [lrange $res 1 end] { + puts $out "** $f" + } + } +} else { + puts $out "** The origin of the sources used to build this amalgamation" + puts $out "** is unknown." +} +puts $out [subst {*/ #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1}] if {$addstatic} { diff --git a/tool/speed-check.sh b/tool/speed-check.sh index 06cbd3574..4cc25794a 100644 --- a/tool/speed-check.sh +++ b/tool/speed-check.sh @@ -99,6 +99,9 @@ while test "$1" != ""; do --stmtcache) shift; SPEEDTEST_OPTS="$SPEEDTEST_OPTS --stmtcache $1" ;; + --nolongdouble) + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --nolongdouble" + ;; --checkpoint) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --checkpoint" ;; diff --git a/tool/spellsift.tcl b/tool/spellsift.tcl new file mode 100755 index 000000000..4e67c3e26 --- /dev/null +++ b/tool/spellsift.tcl @@ -0,0 +1,74 @@ +#!/usr/bin/tclsh + +set usage { + Usage: spellsift.tcl + The named .c and .h source files comment blocks are spell-checked. +} + +if {[llength $argv] == 0} { + puts stderr $usage + exit 0 +} + +# Want a Tcl version with 3-argument close. +package require Tcl 8.6 + +set ::spellchk "aspell --extra-dicts ./custom.rws list" + +# Run text through aspell with custom dictionary, return finds. +proc misspelled {text} { + set spellerr [open "|$::spellchk" r+] + puts $spellerr $text + flush $spellerr + close $spellerr write + set huhq [regsub {\s*$} [read $spellerr] {}] + close $spellerr read + return [split $huhq "\n"] +} + +# Eliminate some common patterns that need not be well spelled. +proc decruft {text} { + set nopp [regsub -all "\n *#\[^\n\]*\n" $text "\n\n" ] + set noticket [regsub -all {Ticket \[?[0-9a-f]+\]?} $nopp "" ] + return $noticket +} + +# Sift out common variable spellings not in normal dictionaries. +proc varsift {words} { + set rv [list] + foreach w $words { + set n [string length $w] + set cr [string range $w 1 end] + if {[string tolower $cr] ne $cr} continue + lappend rv $w; + } + return $rv +} + +foreach fname $argv { + set ich [open $fname r] + set dtext [decruft [read $ich]] + close $ich + set cbounds [regexp -indices -inline -all {(/\*)|(\*/)} $dtext] + set ccb -1 + set cblocks [list] + foreach {ap cb ce} $cbounds { + set cib [lindex $cb 1] + set cie [lindex $ce 0] + if {$cie != -1} { + if {$ccb != -1} { + set cce [expr $cie - 1] + set destar [string map [list * " "] [string range $dtext $ccb $cce]] + lappend cblocks $destar + set ccb -1 + } else continue + } elseif {$cib != -1} { + set ccb [expr $cib + 1] + } + } + set oddspells [varsift [misspelled [join $cblocks "\n"]]] + if {[llength $oddspells] > 0} { + puts "!? Misspellings from $fname:" + puts [join [lsort -nocase -unique $oddspells] "\n"] + } +} diff --git a/tool/sqldiff.c b/tool/sqldiff.c index 0a017037c..0d27ff89e 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -596,7 +596,9 @@ static void diff_one_table(const char *zTab, FILE *out){ /* Build the comparison query */ for(n2=n; az2[n2]; n2++){ - fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, safeId(az2[n2])); + char *zTab = safeId(az2[n2]); + fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zTab); + sqlite3_free(zTab); } nQ = nPk2+1+2*(n2-nPk2); if( n2>nPk2 ){ @@ -605,18 +607,18 @@ static void diff_one_table(const char *zTab, FILE *out){ strPrintf(&sql, "%sB.%s", zSep, az[i]); zSep = ", "; } - strPrintf(&sql, ", 1%s -- changed row\n", nPk==n ? "" : ","); + strPrintf(&sql, ", 1 /* changed row */"); while( az[i] ){ - strPrintf(&sql, " A.%s IS NOT B.%s, B.%s%s\n", - az[i], az2[i], az2[i], az2[i+1]==0 ? "" : ","); + strPrintf(&sql, ", A.%s IS NOT B.%s, B.%s", + az[i], az2[i], az2[i]); i++; } while( az2[i] ){ - strPrintf(&sql, " B.%s IS NOT NULL, B.%s%s\n", - az2[i], az2[i], az2[i+1]==0 ? "" : ","); + strPrintf(&sql, ", B.%s IS NOT NULL, B.%s", + az2[i], az2[i]); i++; } - strPrintf(&sql, " FROM main.%s A, aux.%s B\n", zId, zId); + strPrintf(&sql, "\n FROM main.%s A, aux.%s B\n", zId, zId); zSep = " WHERE"; for(i=0; i +#include +#include +#if !defined(_WIN32) +# include +#else +# include +# ifndef R_OK +# define R_OK 04 +# endif +# ifndef access +# define access(f,m) _access((f),(m)) +# endif +#endif +typedef unsigned long long int u64; + +/* +** The SHA1 implementation below is adapted from: +** +** $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $ +** $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $ +** +** SHA-1 in C +** By Steve Reid +** 100% Public Domain +*/ +typedef struct SHA1Context SHA1Context; +struct SHA1Context { + unsigned int state[5]; + unsigned int count[2]; + unsigned char buffer[64]; +}; + +/* + * blk0() and blk() perform the initial expand. + * I got the idea of expanding during the round function from SSLeay + * + * blk0le() for little-endian and blk0be() for big-endian. + */ +#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r)) +#define rol(x,k) SHA_ROT(x,k,32-(k)) +#define ror(x,k) SHA_ROT(x,32-(k),k) +#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00) \ + |(rol(block[i],8)&0x00FF00FF)) +#define blk0be(i) block[i] +#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \ + ^block[(i+2)&15]^block[i&15],1)) + +/* + * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 + * + * Rl0() for little-endian and Rb0() for big-endian. Endianness is + * determined at run-time. + */ +#define Rl0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define Rb0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define R1(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define R2(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=ror(w,2); +#define R3(v,w,x,y,z,i) \ + z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=ror(w,2); +#define R4(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=ror(w,2); + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ +#define a qq[0] +#define b qq[1] +#define c qq[2] +#define d qq[3] +#define e qq[4] + +void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) +{ + unsigned int qq[5]; /* a, b, c, d, e; */ + static int one = 1; + unsigned int block[16]; + memcpy(block, buffer, 64); + memcpy(qq,state,5*sizeof(unsigned int)); + + /* Copy context->state[] to working vars */ + /* + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + */ + + /* 4 rounds of 20 operations each. Loop unrolled. */ + if( 1 == *(unsigned char*)&one ){ + Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3); + Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7); + Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11); + Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15); + }else{ + Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3); + Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7); + Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11); + Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15); + } + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} + +/* + * SHA1Init - Initialize new context + */ +static void SHA1Init(SHA1Context *context){ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* + * Run your data through this. + */ +static void SHA1Update( + SHA1Context *context, + const unsigned char *data, + unsigned int len +){ + unsigned int i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1] += (len>>29)+1; + j = (j >> 3) & 63; + if ((j + len) > 63) { + (void)memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + SHA1Transform(context->state, &data[i]); + j = 0; + } else { + i = 0; + } + (void)memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* + * Add padding and return the message digest. + */ +static void SHA1Final(unsigned char *digest, SHA1Context *context){ + unsigned int i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (const unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) + SHA1Update(context, (const unsigned char *)"\0", 1); + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + + if (digest) { + for (i = 0; i < 20; i++) + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} + + +/* +** Macros to determine whether the machine is big or little endian, +** and whether or not that determination is run-time or compile-time. +** +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSHA3_BYTEORDER=0 is set, then byte-order is determined +** at run-time. +*/ +#ifndef SHA3_BYTEORDER +# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__arm__) +# define SHA3_BYTEORDER 1234 +# elif defined(sparc) || defined(__ppc__) +# define SHA3_BYTEORDER 4321 +# else +# define SHA3_BYTEORDER 0 +# endif +#endif + + +/* +** State structure for a SHA3 hash in progress +*/ +typedef struct SHA3Context SHA3Context; +struct SHA3Context { + union { + u64 s[25]; /* Keccak state. 5x5 lines of 64 bits each */ + unsigned char x[1600]; /* ... or 1600 bytes */ + } u; + unsigned nRate; /* Bytes of input accepted per Keccak iteration */ + unsigned nLoaded; /* Input bytes loaded into u.x[] so far this cycle */ + unsigned ixMask; /* Insert next input into u.x[nLoaded^ixMask]. */ +}; + +/* +** A single step of the Keccak mixing function for a 1600-bit state +*/ +static void KeccakF1600Step(SHA3Context *p){ + int i; + u64 B0, B1, B2, B3, B4; + u64 C0, C1, C2, C3, C4; + u64 D0, D1, D2, D3, D4; + static const u64 RC[] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, + 0x800000000000808aULL, 0x8000000080008000ULL, + 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x000000000000008aULL, 0x0000000000000088ULL, + 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, + 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, + 0x0000000080000001ULL, 0x8000000080008008ULL + }; +# define A00 (p->u.s[0]) +# define A01 (p->u.s[1]) +# define A02 (p->u.s[2]) +# define A03 (p->u.s[3]) +# define A04 (p->u.s[4]) +# define A10 (p->u.s[5]) +# define A11 (p->u.s[6]) +# define A12 (p->u.s[7]) +# define A13 (p->u.s[8]) +# define A14 (p->u.s[9]) +# define A20 (p->u.s[10]) +# define A21 (p->u.s[11]) +# define A22 (p->u.s[12]) +# define A23 (p->u.s[13]) +# define A24 (p->u.s[14]) +# define A30 (p->u.s[15]) +# define A31 (p->u.s[16]) +# define A32 (p->u.s[17]) +# define A33 (p->u.s[18]) +# define A34 (p->u.s[19]) +# define A40 (p->u.s[20]) +# define A41 (p->u.s[21]) +# define A42 (p->u.s[22]) +# define A43 (p->u.s[23]) +# define A44 (p->u.s[24]) +# define ROL64(a,x) ((a<>(64-x))) + + for(i=0; i<24; i+=4){ + C0 = A00^A10^A20^A30^A40; + C1 = A01^A11^A21^A31^A41; + C2 = A02^A12^A22^A32^A42; + C3 = A03^A13^A23^A33^A43; + C4 = A04^A14^A24^A34^A44; + D0 = C4^ROL64(C1, 1); + D1 = C0^ROL64(C2, 1); + D2 = C1^ROL64(C3, 1); + D3 = C2^ROL64(C4, 1); + D4 = C3^ROL64(C0, 1); + + B0 = (A00^D0); + B1 = ROL64((A11^D1), 44); + B2 = ROL64((A22^D2), 43); + B3 = ROL64((A33^D3), 21); + B4 = ROL64((A44^D4), 14); + A00 = B0 ^((~B1)& B2 ); + A00 ^= RC[i]; + A11 = B1 ^((~B2)& B3 ); + A22 = B2 ^((~B3)& B4 ); + A33 = B3 ^((~B4)& B0 ); + A44 = B4 ^((~B0)& B1 ); + + B2 = ROL64((A20^D0), 3); + B3 = ROL64((A31^D1), 45); + B4 = ROL64((A42^D2), 61); + B0 = ROL64((A03^D3), 28); + B1 = ROL64((A14^D4), 20); + A20 = B0 ^((~B1)& B2 ); + A31 = B1 ^((~B2)& B3 ); + A42 = B2 ^((~B3)& B4 ); + A03 = B3 ^((~B4)& B0 ); + A14 = B4 ^((~B0)& B1 ); + + B4 = ROL64((A40^D0), 18); + B0 = ROL64((A01^D1), 1); + B1 = ROL64((A12^D2), 6); + B2 = ROL64((A23^D3), 25); + B3 = ROL64((A34^D4), 8); + A40 = B0 ^((~B1)& B2 ); + A01 = B1 ^((~B2)& B3 ); + A12 = B2 ^((~B3)& B4 ); + A23 = B3 ^((~B4)& B0 ); + A34 = B4 ^((~B0)& B1 ); + + B1 = ROL64((A10^D0), 36); + B2 = ROL64((A21^D1), 10); + B3 = ROL64((A32^D2), 15); + B4 = ROL64((A43^D3), 56); + B0 = ROL64((A04^D4), 27); + A10 = B0 ^((~B1)& B2 ); + A21 = B1 ^((~B2)& B3 ); + A32 = B2 ^((~B3)& B4 ); + A43 = B3 ^((~B4)& B0 ); + A04 = B4 ^((~B0)& B1 ); + + B3 = ROL64((A30^D0), 41); + B4 = ROL64((A41^D1), 2); + B0 = ROL64((A02^D2), 62); + B1 = ROL64((A13^D3), 55); + B2 = ROL64((A24^D4), 39); + A30 = B0 ^((~B1)& B2 ); + A41 = B1 ^((~B2)& B3 ); + A02 = B2 ^((~B3)& B4 ); + A13 = B3 ^((~B4)& B0 ); + A24 = B4 ^((~B0)& B1 ); + + C0 = A00^A20^A40^A10^A30; + C1 = A11^A31^A01^A21^A41; + C2 = A22^A42^A12^A32^A02; + C3 = A33^A03^A23^A43^A13; + C4 = A44^A14^A34^A04^A24; + D0 = C4^ROL64(C1, 1); + D1 = C0^ROL64(C2, 1); + D2 = C1^ROL64(C3, 1); + D3 = C2^ROL64(C4, 1); + D4 = C3^ROL64(C0, 1); + + B0 = (A00^D0); + B1 = ROL64((A31^D1), 44); + B2 = ROL64((A12^D2), 43); + B3 = ROL64((A43^D3), 21); + B4 = ROL64((A24^D4), 14); + A00 = B0 ^((~B1)& B2 ); + A00 ^= RC[i+1]; + A31 = B1 ^((~B2)& B3 ); + A12 = B2 ^((~B3)& B4 ); + A43 = B3 ^((~B4)& B0 ); + A24 = B4 ^((~B0)& B1 ); + + B2 = ROL64((A40^D0), 3); + B3 = ROL64((A21^D1), 45); + B4 = ROL64((A02^D2), 61); + B0 = ROL64((A33^D3), 28); + B1 = ROL64((A14^D4), 20); + A40 = B0 ^((~B1)& B2 ); + A21 = B1 ^((~B2)& B3 ); + A02 = B2 ^((~B3)& B4 ); + A33 = B3 ^((~B4)& B0 ); + A14 = B4 ^((~B0)& B1 ); + + B4 = ROL64((A30^D0), 18); + B0 = ROL64((A11^D1), 1); + B1 = ROL64((A42^D2), 6); + B2 = ROL64((A23^D3), 25); + B3 = ROL64((A04^D4), 8); + A30 = B0 ^((~B1)& B2 ); + A11 = B1 ^((~B2)& B3 ); + A42 = B2 ^((~B3)& B4 ); + A23 = B3 ^((~B4)& B0 ); + A04 = B4 ^((~B0)& B1 ); + + B1 = ROL64((A20^D0), 36); + B2 = ROL64((A01^D1), 10); + B3 = ROL64((A32^D2), 15); + B4 = ROL64((A13^D3), 56); + B0 = ROL64((A44^D4), 27); + A20 = B0 ^((~B1)& B2 ); + A01 = B1 ^((~B2)& B3 ); + A32 = B2 ^((~B3)& B4 ); + A13 = B3 ^((~B4)& B0 ); + A44 = B4 ^((~B0)& B1 ); + + B3 = ROL64((A10^D0), 41); + B4 = ROL64((A41^D1), 2); + B0 = ROL64((A22^D2), 62); + B1 = ROL64((A03^D3), 55); + B2 = ROL64((A34^D4), 39); + A10 = B0 ^((~B1)& B2 ); + A41 = B1 ^((~B2)& B3 ); + A22 = B2 ^((~B3)& B4 ); + A03 = B3 ^((~B4)& B0 ); + A34 = B4 ^((~B0)& B1 ); + + C0 = A00^A40^A30^A20^A10; + C1 = A31^A21^A11^A01^A41; + C2 = A12^A02^A42^A32^A22; + C3 = A43^A33^A23^A13^A03; + C4 = A24^A14^A04^A44^A34; + D0 = C4^ROL64(C1, 1); + D1 = C0^ROL64(C2, 1); + D2 = C1^ROL64(C3, 1); + D3 = C2^ROL64(C4, 1); + D4 = C3^ROL64(C0, 1); + + B0 = (A00^D0); + B1 = ROL64((A21^D1), 44); + B2 = ROL64((A42^D2), 43); + B3 = ROL64((A13^D3), 21); + B4 = ROL64((A34^D4), 14); + A00 = B0 ^((~B1)& B2 ); + A00 ^= RC[i+2]; + A21 = B1 ^((~B2)& B3 ); + A42 = B2 ^((~B3)& B4 ); + A13 = B3 ^((~B4)& B0 ); + A34 = B4 ^((~B0)& B1 ); + + B2 = ROL64((A30^D0), 3); + B3 = ROL64((A01^D1), 45); + B4 = ROL64((A22^D2), 61); + B0 = ROL64((A43^D3), 28); + B1 = ROL64((A14^D4), 20); + A30 = B0 ^((~B1)& B2 ); + A01 = B1 ^((~B2)& B3 ); + A22 = B2 ^((~B3)& B4 ); + A43 = B3 ^((~B4)& B0 ); + A14 = B4 ^((~B0)& B1 ); + + B4 = ROL64((A10^D0), 18); + B0 = ROL64((A31^D1), 1); + B1 = ROL64((A02^D2), 6); + B2 = ROL64((A23^D3), 25); + B3 = ROL64((A44^D4), 8); + A10 = B0 ^((~B1)& B2 ); + A31 = B1 ^((~B2)& B3 ); + A02 = B2 ^((~B3)& B4 ); + A23 = B3 ^((~B4)& B0 ); + A44 = B4 ^((~B0)& B1 ); + + B1 = ROL64((A40^D0), 36); + B2 = ROL64((A11^D1), 10); + B3 = ROL64((A32^D2), 15); + B4 = ROL64((A03^D3), 56); + B0 = ROL64((A24^D4), 27); + A40 = B0 ^((~B1)& B2 ); + A11 = B1 ^((~B2)& B3 ); + A32 = B2 ^((~B3)& B4 ); + A03 = B3 ^((~B4)& B0 ); + A24 = B4 ^((~B0)& B1 ); + + B3 = ROL64((A20^D0), 41); + B4 = ROL64((A41^D1), 2); + B0 = ROL64((A12^D2), 62); + B1 = ROL64((A33^D3), 55); + B2 = ROL64((A04^D4), 39); + A20 = B0 ^((~B1)& B2 ); + A41 = B1 ^((~B2)& B3 ); + A12 = B2 ^((~B3)& B4 ); + A33 = B3 ^((~B4)& B0 ); + A04 = B4 ^((~B0)& B1 ); + + C0 = A00^A30^A10^A40^A20; + C1 = A21^A01^A31^A11^A41; + C2 = A42^A22^A02^A32^A12; + C3 = A13^A43^A23^A03^A33; + C4 = A34^A14^A44^A24^A04; + D0 = C4^ROL64(C1, 1); + D1 = C0^ROL64(C2, 1); + D2 = C1^ROL64(C3, 1); + D3 = C2^ROL64(C4, 1); + D4 = C3^ROL64(C0, 1); + + B0 = (A00^D0); + B1 = ROL64((A01^D1), 44); + B2 = ROL64((A02^D2), 43); + B3 = ROL64((A03^D3), 21); + B4 = ROL64((A04^D4), 14); + A00 = B0 ^((~B1)& B2 ); + A00 ^= RC[i+3]; + A01 = B1 ^((~B2)& B3 ); + A02 = B2 ^((~B3)& B4 ); + A03 = B3 ^((~B4)& B0 ); + A04 = B4 ^((~B0)& B1 ); + + B2 = ROL64((A10^D0), 3); + B3 = ROL64((A11^D1), 45); + B4 = ROL64((A12^D2), 61); + B0 = ROL64((A13^D3), 28); + B1 = ROL64((A14^D4), 20); + A10 = B0 ^((~B1)& B2 ); + A11 = B1 ^((~B2)& B3 ); + A12 = B2 ^((~B3)& B4 ); + A13 = B3 ^((~B4)& B0 ); + A14 = B4 ^((~B0)& B1 ); + + B4 = ROL64((A20^D0), 18); + B0 = ROL64((A21^D1), 1); + B1 = ROL64((A22^D2), 6); + B2 = ROL64((A23^D3), 25); + B3 = ROL64((A24^D4), 8); + A20 = B0 ^((~B1)& B2 ); + A21 = B1 ^((~B2)& B3 ); + A22 = B2 ^((~B3)& B4 ); + A23 = B3 ^((~B4)& B0 ); + A24 = B4 ^((~B0)& B1 ); + + B1 = ROL64((A30^D0), 36); + B2 = ROL64((A31^D1), 10); + B3 = ROL64((A32^D2), 15); + B4 = ROL64((A33^D3), 56); + B0 = ROL64((A34^D4), 27); + A30 = B0 ^((~B1)& B2 ); + A31 = B1 ^((~B2)& B3 ); + A32 = B2 ^((~B3)& B4 ); + A33 = B3 ^((~B4)& B0 ); + A34 = B4 ^((~B0)& B1 ); + + B3 = ROL64((A40^D0), 41); + B4 = ROL64((A41^D1), 2); + B0 = ROL64((A42^D2), 62); + B1 = ROL64((A43^D3), 55); + B2 = ROL64((A44^D4), 39); + A40 = B0 ^((~B1)& B2 ); + A41 = B1 ^((~B2)& B3 ); + A42 = B2 ^((~B3)& B4 ); + A43 = B3 ^((~B4)& B0 ); + A44 = B4 ^((~B0)& B1 ); + } +} + +/* +** Initialize a new hash. iSize determines the size of the hash +** in bits and should be one of 224, 256, 384, or 512. Or iSize +** can be zero to use the default hash size of 256 bits. +*/ +static void SHA3Init(SHA3Context *p, int iSize){ + memset(p, 0, sizeof(*p)); + if( iSize>=128 && iSize<=512 ){ + p->nRate = (1600 - ((iSize + 31)&~31)*2)/8; + }else{ + p->nRate = (1600 - 2*256)/8; + } +#if SHA3_BYTEORDER==1234 + /* Known to be little-endian at compile-time. No-op */ +#elif SHA3_BYTEORDER==4321 + p->ixMask = 7; /* Big-endian */ +#else + { + static unsigned int one = 1; + if( 1==*(unsigned char*)&one ){ + /* Little endian. No byte swapping. */ + p->ixMask = 0; + }else{ + /* Big endian. Byte swap. */ + p->ixMask = 7; + } + } +#endif +} + +/* +** Make consecutive calls to the SHA3Update function to add new content +** to the hash +*/ +static void SHA3Update( + SHA3Context *p, + const unsigned char *aData, + unsigned int nData +){ + unsigned int i = 0; +#if SHA3_BYTEORDER==1234 + if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){ + for(; i+7u.s[p->nLoaded/8] ^= *(u64*)&aData[i]; + p->nLoaded += 8; + if( p->nLoaded>=p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } + } +#endif + for(; iu.x[p->nLoaded] ^= aData[i]; +#elif SHA3_BYTEORDER==4321 + p->u.x[p->nLoaded^0x07] ^= aData[i]; +#else + p->u.x[p->nLoaded^p->ixMask] ^= aData[i]; +#endif + p->nLoaded++; + if( p->nLoaded==p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } +} + +/* +** After all content has been added, invoke SHA3Final() to compute +** the final hash. The function returns a pointer to the binary +** hash value. +*/ +static unsigned char *SHA3Final(SHA3Context *p){ + unsigned int i; + if( p->nLoaded==p->nRate-1 ){ + const unsigned char c1 = 0x86; + SHA3Update(p, &c1, 1); + }else{ + const unsigned char c2 = 0x06; + const unsigned char c3 = 0x80; + SHA3Update(p, &c2, 1); + p->nLoaded = p->nRate - 1; + SHA3Update(p, &c3, 1); + } + for(i=0; inRate; i++){ + p->u.x[i+p->nRate] = p->u.x[i^p->ixMask]; + } + return &p->u.x[p->nRate]; +} + +/* +** Convert a digest into base-16. +*/ +static void DigestToBase16(unsigned char *digest, char *zBuf, int nByte){ + static const char zEncode[] = "0123456789abcdef"; + int ix; + + for(ix=0; ix>4)&0xf]; + *zBuf++ = zEncode[*digest++ & 0xf]; + } + *zBuf = '\0'; +} + +/* +** Compute the SHA3-256 checksum of a file on disk. Store the resulting +** checksum in the zCksum. +** +** Return the number of errors. +*/ +void sha3sum_file(const char *zFilename, char *zCksum){ + FILE *in; + SHA3Context ctx; + char zBuf[10240]; + + in = fopen(zFilename,"rb"); + if( in==0 ){ + zCksum[0] = 0; + return; + } + SHA3Init(&ctx, 256); + for(;;){ + size_t n; + n = fread(zBuf, 1, sizeof(zBuf), in); + if( n<=0 ) break; + SHA3Update(&ctx, (unsigned char*)zBuf, (unsigned)n); + } + fclose(in); + DigestToBase16(SHA3Final(&ctx), zCksum, 32); +} + +/* +** Compute the SHA1 checksum of a file on disk. Store the resulting +** checksum in the zCksum. +** +** Return the number of errors. +*/ +void sha1sum_file(const char *zFilename, char *zCksum){ + FILE *in; + SHA1Context ctx; + unsigned char zResult[20]; + char zBuf[10240]; + + in = fopen(zFilename,"rb"); + if( in==0 ){ + zCksum[0] = 0; + return; + } + SHA1Init(&ctx); + for(;;){ + size_t n; + n = fread(zBuf, 1, sizeof(zBuf), in); + if( n<=0 ) break; + SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n); + } + fclose(in); + SHA1Final(zResult, &ctx); + DigestToBase16(zResult, zCksum, 20); +} + +/* +** Decode a fossilized string in-place. +*/ +void defossilize(char *z){ + int i, j, cc; + char *zSlash = strchr(z, '\\'); + if( zSlash==0 ) return; + i = zSlash - z; + for(j=i; (cc=z[i])!=0; i++){ + if( cc=='\\' && z[i+1] ){ + i++; + switch( z[i] ){ + case 'n': cc = '\n'; break; + case 's': cc = ' '; break; + case 't': cc = '\t'; break; + case 'r': cc = '\r'; break; + case 'v': cc = '\v'; break; + case 'f': cc = '\f'; break; + case '0': cc = 0; break; + case '\\': cc = '\\'; break; + default: cc = z[i]; break; + } + } + z[j++] = cc; + } + if( z[j] ) z[j] = 0; +} + +/* +** Report that a single file is incorrect. +*/ +static void errorMsg(int *pnErr, const char *zVers, const char *zFile){ + if( *pnErr==0 ){ + printf("Derived from %.25s with changes to:\n", zVers); + } + printf(" %s\n", zFile); + (*pnErr)++; +} +static void errorMsgNH(int *pnErr, const char *zVers, const char *zFile){ + if( *pnErr==0 ){ + printf("%s\n", zVers); + } + printf("%s\n", zFile); + (*pnErr)++; +} + +int main(int argc, char **argv){ + int i, j; + int nDir; + FILE *in; + int bDebug = 0; + int bNonHuman = 0; + int bSeenManifestErr = 0; + int nErr = 0; + SHA3Context ctx3; + const char *zDir = 0; + void (*xErr)(int*,const char*,const char*); + char zHash[100]; + char zCk[100]; + char zVers[100]; + char zLine[40000]; + char zFile[40000]; + xErr = errorMsg; + for(i=1; i1000 ){ + fprintf(stderr, "Directory argument too big: [%s]\n", zDir); + return 1; + } + nDir = (int)strlen(zDir); + if( nDir<0 ){ + fprintf(stderr, "Directory argument too short.\n"); + return 1; + } + memcpy(zFile, zDir, nDir); + if( zFile[nDir-1]!='/' ){ + zFile[nDir++] = '/'; + } + memcpy(&zFile[nDir], "manifest", 9); + if( bDebug ){ + printf("manifest file: [%s]\n", zFile); + } + in = fopen(zFile, "rb"); + if( in==0 ){ + fprintf(stderr, "missing manifest: \"%s\"\n", zFile); + return 1; + } + SHA3Init(&ctx3, 256); + while( fgets(zLine, sizeof(zLine), in) ){ + if( zLine[0]=='#' ) break; + SHA3Update(&ctx3, (unsigned char*)zLine, (int)strlen(zLine)); + } + DigestToBase16(SHA3Final(&ctx3), zVers, 32); + + rewind(in); + while( fgets(zLine, sizeof(zLine), in) ){ + if( zLine[0]!='F' ) continue; + if( zLine[1]!=' ' ) continue; + for(i=2, j=nDir; zLine[i]!=0 && zLine[i]!=' '; i++, j++){ + if( j='0' && zLine[i]<='f'; i++, j++){ + if( j Date: Tue, 12 Sep 2023 09:31:00 -0400 Subject: [PATCH 109/340] Snapshot of upstream SQLite 3.43.1 --- VERSION | 2 +- configure | 18 +-- ext/fts3/fts3_write.c | 2 +- ext/fts5/fts5_index.c | 127 +++++++++--------- ext/fts5/test/fts5secure.test | 13 ++ ext/session/sqlite3session.c | 18 ++- ext/wasm/GNUmakefile | 4 +- ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js | 2 + ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 25 ++-- manifest | 45 +++---- manifest.uuid | 2 +- src/func.c | 10 +- src/json.c | 4 +- src/select.c | 10 +- test/func.test | 8 ++ test/json102.test | 3 + test/selectH.test | 29 +++- test/testrunner_data.tcl | 5 +- 18 files changed, 200 insertions(+), 127 deletions(-) diff --git a/VERSION b/VERSION index a9184766b..9291fe6ef 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.43.0 +3.43.1 diff --git a/configure b/configure index 6b0c584a4..ec753e7ee 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.69 for sqlite 3.43.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.43.1. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.43.0' -PACKAGE_STRING='sqlite 3.43.0' +PACKAGE_VERSION='3.43.1' +PACKAGE_STRING='sqlite 3.43.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1470,7 +1470,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.43.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.43.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1535,7 +1535,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.43.0:";; + short | recursive ) echo "Configuration of sqlite 3.43.1:";; esac cat <<\_ACEOF @@ -1665,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.43.0 +sqlite configure 3.43.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2084,7 +2084,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.43.0, which was +It was created by sqlite $as_me 3.43.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12457,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.43.0, which was +This file was extended by sqlite $as_me 3.43.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12523,7 +12523,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.43.0 +sqlite config.status 3.43.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 32b483b34..de07c52c1 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -5218,7 +5218,7 @@ static u64 fts3ChecksumIndex( int rc; u64 cksum = 0; - assert( *pRc==SQLITE_OK ); + if( *pRc ) return 0; memset(&filter, 0, sizeof(filter)); memset(&csr, 0, sizeof(csr)); diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 267489a7e..e729b13f2 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -5198,80 +5198,79 @@ static void fts5DoSecureDelete( } } }else if( iStart==4 ){ - int iPgno; - - assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno ); - /* The entry being removed may be the only position list in - ** its doclist. */ - for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ - Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); - int bEmpty = (pPg && pPg->nn==4); - fts5DataRelease(pPg); - if( bEmpty==0 ) break; - } - - if( iPgno==pSeg->iTermLeafPgno ){ - i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); - Fts5Data *pTerm = fts5DataRead(p, iId); - if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ - u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; - int nTermIdx = pTerm->nn - pTerm->szLeaf; - int iTermIdx = 0; - int iTermOff = 0; - - while( 1 ){ - u32 iVal = 0; - int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal); - iTermOff += iVal; - if( (iTermIdx+nByte)>=nTermIdx ) break; - iTermIdx += nByte; - } - nTermIdx = iTermIdx; + int iPgno; + + assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno ); + /* The entry being removed may be the only position list in + ** its doclist. */ + for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ + Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); + int bEmpty = (pPg && pPg->nn==4); + fts5DataRelease(pPg); + if( bEmpty==0 ) break; + } + + if( iPgno==pSeg->iTermLeafPgno ){ + i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); + Fts5Data *pTerm = fts5DataRead(p, iId); + if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ + u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; + int nTermIdx = pTerm->nn - pTerm->szLeaf; + int iTermIdx = 0; + int iTermOff = 0; + + while( 1 ){ + u32 iVal = 0; + int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal); + iTermOff += iVal; + if( (iTermIdx+nByte)>=nTermIdx ) break; + iTermIdx += nByte; + } + nTermIdx = iTermIdx; - memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); - fts5PutU16(&pTerm->p[2], iTermOff); + memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); + fts5PutU16(&pTerm->p[2], iTermOff); - fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); - if( nTermIdx==0 ){ - fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); - } + fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); + if( nTermIdx==0 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); } - fts5DataRelease(pTerm); } + fts5DataRelease(pTerm); } + } - if( p->rc==SQLITE_OK ){ - const int nMove = nPg - iNextOff; - int nShift = 0; - - memmove(&aPg[iOff], &aPg[iNextOff], nMove); - iPgIdx -= (iNextOff - iOff); - nPg = iPgIdx; - fts5PutU16(&aPg[2], iPgIdx); - - nShift = iNextOff - iOff; - for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdxiOff ){ - iKeyOff -= nShift; - nShift = 0; - } - nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff); - iPrevKeyOff = iKeyOff; - } - } + if( p->rc==SQLITE_OK ){ + const int nMove = nPg - iNextOff; /* Number of bytes to move */ + int nShift = iNextOff - iOff; /* Distance to move them */ + + int iPrevKeyOut = 0; + int iKeyIn = 0; - if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ - fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); + memmove(&aPg[iOff], &aPg[iNextOff], nMove); + iPgIdx -= nShift; + nPg = iPgIdx; + fts5PutU16(&aPg[2], iPgIdx); + + for(iIdx=0; iIdxiOff ? nShift : 0)); + nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut); + iPrevKeyOut = iKeyOut; } + } - assert_nc( nPg>4 || fts5GetU16(aPg)==0 ); - fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg); + if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); } - sqlite3_free(aIdx); + + assert_nc( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg); + } + sqlite3_free(aIdx); } /* diff --git a/ext/fts5/test/fts5secure.test b/ext/fts5/test/fts5secure.test index 50d84cef7..7e8aea3ba 100644 --- a/ext/fts5/test/fts5secure.test +++ b/ext/fts5/test/fts5secure.test @@ -273,6 +273,19 @@ do_execsql_test 5.3 { do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2 do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2 +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE fts USING fts5(content); + INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1); + INSERT INTO fts(rowid, content) VALUES + (3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really'); + DELETE FROM fts WHERE rowid IS 3407; + INSERT INTO fts(fts) VALUES ('integrity-check'); +} + + finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 9f862f246..049154923 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -3236,15 +3236,19 @@ static int sessionReadRecord( } } if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - sqlite3_int64 v = sessionGetI64(aVal); - if( eType==SQLITE_INTEGER ){ - sqlite3VdbeMemSetInt64(apOut[i], v); + if( (pIn->nData-pIn->iNext)<8 ){ + rc = SQLITE_CORRUPT_BKPT; }else{ - double d; - memcpy(&d, &v, 8); - sqlite3VdbeMemSetDouble(apOut[i], d); + sqlite3_int64 v = sessionGetI64(aVal); + if( eType==SQLITE_INTEGER ){ + sqlite3VdbeMemSetInt64(apOut[i], v); + }else{ + double d; + memcpy(&d, &v, 8); + sqlite3VdbeMemSetDouble(apOut[i], d); + } + pIn->iNext += 8; } - pIn->iNext += 8; } } } diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 080c42704..a001d15bb 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -845,11 +845,11 @@ sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-frien sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\ - $(c-pp.D.bundler-friendly))) + $(c-pp.D.sqlite3-bundler-friendly))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\ $(sqlite3-worker1-promiser-bundler-friendly.js),\ - $(c-pp.D.bundler-friendly))) + $(c-pp.D.sqlite3-bundler-friendly))) $(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \ $(sqlite3-worker1-promiser-bundler-friendly.js) $(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js index 709d3414c..d16254143 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -890,6 +890,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ this.setAssociatedPath(sah, '', 0); toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); }else{ + sah.write(new Uint8Array([1,1]), {at: HEADER_OFFSET_DATA+18} + /* force db out of WAL mode */); this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB); } } diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index 93482505a..de11f3a23 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -1192,16 +1192,25 @@ const installOpfsVfs = function callee(options){ toss("Input does not contain an SQLite database header."); } } + let sah; const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true); - const hFile = await hDir.getFileHandle(fnamePart, {create:true}); - const sah = await hFile.createSyncAccessHandle(); - sah.truncate(0); - const nWrote = sah.write(bytes, {at: 0}); - sah.close(); - if(nWrote != n){ - toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); + try { + const hFile = await hDir.getFileHandle(fnamePart, {create:true}); + sah = await hFile.createSyncAccessHandle(); + sah.truncate(0); + const nWrote = sah.write(bytes, {at: 0}); + if(nWrote != n){ + toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); + } + sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */; + return nWrote; + }catch(e){ + if( sah ){ await sah.close(); sah = undefined; } + await hDir.removeEntry( fnamePart ).catch(()=>{}); + throw e; + }finally{ + if( sah ) await sah.close(); } - return nWrote; }; if(sqlite3.oo1){ diff --git a/manifest b/manifest index c84c400b7..d65c54a67 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.43.0 -D 2023-08-24T12:36:59.632 +C Version\s3.43.1 +D 2023-09-11T12:01:27.641 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in 1e9105ffed727b1557ce59f0941c5d271e276ec00bc9823f03d77a89e131b918 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 F Makefile.msc 26c2d196391a285c279adb10fd6001774d9b243af94b700b681e4a49cd476684 F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8 -F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa +F VERSION 536457fc6444f62ad9e5082a82e4e190f7ba3f4d903a63b835835230f7a8f853 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 9dc3300339f4d6b3c3b108de60cc6ae6b3c547e25c7e6df280b4775db4de3a1b x +F configure 8f9bd15e5edda5730621f630be36c7273a2ad7dfa080bfdcb7abb8f063967c52 x F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -77,7 +77,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c d28d9ef383ef848a7b77df4b9964abcc90d67a2b584120c0ad465972dce416e6 +F ext/fts3/fts3_write.c b28f4cde90ed560245ecb76a882b45aa62da16ff6f61e9884eae5c7c5eff16ea F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -93,7 +93,7 @@ F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b7292 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d -F ext/fts5/fts5_index.c b484322421cbb421d22bb2cd304001b80596d671cd626367c8c806b889de4b42 +F ext/fts5/fts5_index.c bb405cb68fc5306107133d29ea8f86337bba360fe1c6029043d16a3026a26767 F ext/fts5/fts5_main.c 7070031993ba5b5d89b13206ec4ef624895f2f7c0ec72725913d301e4d382445 F ext/fts5/fts5_storage.c 3c9b41fce41b6410f2e8f82eb035c6a29b2560483f773e6dc98cf3cb2e4ddbb5 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae @@ -197,7 +197,7 @@ F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde4 F ext/fts5/test/fts5restart.test 835ecc8f449e3919f72509ab58056d0cedca40d1fe04108ccf8ac4c2ba41f415 F ext/fts5/test/fts5rowid.test b8790ec170a8dc1942a15aef3db926a5f3061b1ff171013003d8297203a20ad6 F ext/fts5/test/fts5savepoint.test fc02929f238d02a22df4172625704e029f7c1e0e92e332d654375690f8e6e43f -F ext/fts5/test/fts5secure.test 214a561519d1b1817f146efd1057e2a97cc896e75c2accc77157d874154bda64 +F ext/fts5/test/fts5secure.test 833f987e6902a9ab20fabbaa7ca0662c6187fa368cbc7a8082fdf2bf22ee0304 F ext/fts5/test/fts5secure2.test 2e961d7eef939f294c56b5d895cac7f1c3a60b934ee2cfd5e5e620bdf1ba6bbc F ext/fts5/test/fts5secure3.test c7e1080a6912f2a3ac68f2e05b88b72a99de38543509b2bbf427cac5c9c1c610 F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b9846cf7fc8337b67c97 @@ -522,14 +522,14 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c 1971b61ca45babf0d9e4bb669a65b0903135e9828af2fcd4f0c8f1b7acf36b6f +F ext/session/sqlite3session.c 0fe9107318140cefa1b50f2e1e0f330ab359022599e5976820db349f33efae11 F ext/session/sqlite3session.h 653e9d49c4edae231df8a4c8d69c2145195aedb32462d4b44229dbee7d2680fb F ext/session/test_session.c 5285482f83cd92b4c1fe12fcf88210566a18312f4f2aa110f6399dae46aeccbb F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile d02f3c8798b754f68b1f6b422ccff894a10bf352fc9c4eb8945baeace1acac28 +F ext/wasm/GNUmakefile 7eea788d3cde04135da82451a0afcd22f264157544ba69464a9285319a44b41d F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab @@ -549,8 +549,8 @@ F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b17386 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 -F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js abb69b5e008961026bf5ff433d7116cb046359af92a5daf73208af2e7ac80ae7 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js e04fc2fda6a0200ef80efdbb4ddfa0254453558adb17ec3a230f93d2bf1d711c +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js c7caf5ccd85d6e92bee4285871d51bc0e3e635b3e2d76f50515f5482a0ec0239 +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js cf0a2a182d16e71c5ccb1a870bfdba0d0da5e2fe5542907b036e92b3db5982c3 F ext/wasm/api/sqlite3-wasm.c d4d4c2b349b43b7b861e6d2994299630fb79e07573ea6b61e28e8071b7d16b61 F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75 @@ -637,14 +637,14 @@ F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 F src/expr.c 1affe0cc049683ef0ef3545d9b6901508556b0ef7e2892a344c3df6d7288d79d F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 -F src/func.c dd1ecd1be6aaa67c9fa723f841e05e1536314c6aaa0509e25289b310f64dbb9c +F src/func.c 3c8e02958a3d4272659fcb234133b7572794ece60571779e88dd96ec16bae0d6 F src/global.c 29f56a330ed9d1b5cd9b79ac0ca36f97ac3afc730ff8bfa987b0db9e559d684d F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c ae840f87b418f039f5d336b488933d09396bd31e6b31e855b93055ccaee4e255 +F src/json.c 51141f1c09ccb177057e5813e6302a5e32e5ba88cc4a756318a35081010fc6df F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 98cfba10989b3da6f1807ad42444017742db7f100a54f1032af7a8b1295912c0 F src/main.c fde8f13c876a658b4e8b74b77d875ca887915c174ea6a2f3122d80966f93d865 @@ -684,7 +684,7 @@ F src/printf.c e3ba080e2f409f9bfcc8d34724e6fc160e9c718dc92d0548f6b71b8b6f860ce2 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 5f545a2c8702d4d3430bbb188cfec47d6c122d899061ef00cbe56af14591c574 +F src/select.c e9fb48546ab1882639a3a960383f6342dddb776c0227615f8e19de51f0102f68 F src/shell.c.in 2f9be25294b68b07e7e81f0adcec4475aba6011b64f160e414efe226910c4d7b F src/sqlite.h.in 73a366c1c45d5ac9888cfe81c458826a44498531d106cfb4f328193ab5f6f17d F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 @@ -1156,7 +1156,7 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test 2acd982669b2c15ceccc046385b458e985f90c9ca58b14d0ef75eec79c32f45b +F test/func.test cbcf086273529d4a99b7199086da637a99039d2cad81dd7d7c4c9e25258ae164 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 @@ -1274,7 +1274,7 @@ F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd286 F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x F test/json101.test dc9d5a2a5b1fd1b54dbd71c538b17933cc98d84b4c1f821ead754933663dca55 -F test/json102.test 24f6f204f9cde45b971016691d0b92a9b4c58040d699e36d6b12cb165f9083ff +F test/json102.test 4c69694773a470f1fda34e5f4ba24920b35184fb66050b450fc2ef9ab5ad310b F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d @@ -1501,7 +1501,7 @@ F test/selectD.test 6d1909b49970bf92f45ce657505befcef5fc7cbc13544e18103a316d3218 F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3 F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840 -F test/selectH.test 88237ded5925adfb3f27fdafb5428c2ffc4d61e313bceb854e225ffc3ef0d206 +F test/selectH.test 0b54599f1917d99568c9b929df22ec6261ed7b6d2f02a46b5945ef81b7871aac F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb F test/sessionfuzz.c 5eef09af01eeff6f20250ae4c0112c2e576e4d2f2026cc9a49dc5be6886fa6ee @@ -1604,7 +1604,7 @@ F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede F test/testrunner.tcl a9fee4df57276bc9e446961b160068c269da5902cc8ffc3e8852d77626b7594c -F test/testrunner_data.tcl c448693eb6fdbadb78cb26f6253d4f335666f9836f988afa575de960b666b19f +F test/testrunner_data.tcl 968cfee74688eb698b6917b7e53aba2993a1789430192d5efd27d2173c2e36c6 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -2092,11 +2092,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 3f5bec9a684f6b3d88383617831862fc5595a51ad99ec12430ac6650b704ccbb -R 0c01ee5f49ac8bbf7d5b3e02bf525f93 -T +sym-major-release * +P fb24ca17fa1be739395fc471a8d4c9f876874c1709d41803925027b56587cf83 +R 00850c800fbae2990176d31bf7ab6581 T +sym-release * -T +sym-version-3.43.0 * +T +sym-version-3.43.1 * U drh -Z 2bd48930e1a5ff814b90ece6e1f4c0d0 +Z afe4b484bf2298bff989f42abbf408e9 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3b7fefa60..4b65e95ca 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0f80b798b3f4b81a7bb4233c58294edd0f1156f36b6ecf5ab8e83631d468778c +2d3a40c05c49e1a49264912b1a05bc2143ac0e7c3df588276ce80a4cbc9bd1b0 diff --git a/src/func.c b/src/func.c index ca0075edc..e551bba81 100644 --- a/src/func.c +++ b/src/func.c @@ -1816,8 +1816,10 @@ static void sumFinalize(sqlite3_context *context){ if( p->approx ){ if( p->ovrfl ){ sqlite3_result_error(context,"integer overflow",-1); - }else{ + }else if( !sqlite3IsNaN(p->rErr) ){ sqlite3_result_double(context, p->rSum+p->rErr); + }else{ + sqlite3_result_double(context, p->rSum); } }else{ sqlite3_result_int64(context, p->iSum); @@ -1830,7 +1832,8 @@ static void avgFinalize(sqlite3_context *context){ if( p && p->cnt>0 ){ double r; if( p->approx ){ - r = p->rSum+p->rErr; + r = p->rSum; + if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } @@ -1843,7 +1846,8 @@ static void totalFinalize(sqlite3_context *context){ p = sqlite3_aggregate_context(context, 0); if( p ){ if( p->approx ){ - r = p->rSum+p->rErr; + r = p->rSum; + if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } diff --git a/src/json.c b/src/json.c index f8d4aa2a7..253fce9f4 100644 --- a/src/json.c +++ b/src/json.c @@ -2484,7 +2484,9 @@ static void jsonArrayLengthFunc( } if( pNode->eType==JSON_ARRAY ){ while( 1 /*exit-by-break*/ ){ - for(i=1; i<=pNode->n; n++){ + i = 1; + while( i<=pNode->n ){ + if( (pNode[i].jnFlags & JNODE_REMOVE)==0 ) n++; i += jsonNodeSize(&pNode[i]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; diff --git a/src/select.c b/src/select.c index 321badd07..90b284036 100644 --- a/src/select.c +++ b/src/select.c @@ -5301,12 +5301,12 @@ static int disableUnusedSubqueryResultColumns(SrcItem *pItem){ assert( pItem->pSelect!=0 ); pSub = pItem->pSelect; assert( pSub->pEList->nExpr==pTab->nCol ); - if( (pSub->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){ - testcase( pSub->selFlags & SF_Distinct ); - testcase( pSub->selFlags & SF_Aggregate ); - return 0; - } for(pX=pSub; pX; pX=pX->pPrior){ + if( (pX->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){ + testcase( pX->selFlags & SF_Distinct ); + testcase( pX->selFlags & SF_Aggregate ); + return 0; + } if( pX->pPrior && pX->op!=TK_ALL ){ /* This optimization does not work for compound subqueries that ** use UNION, INTERSECT, or EXCEPT. Only UNION ALL is allowed. */ diff --git a/test/func.test b/test/func.test index 3d7106046..aea372e08 100644 --- a/test/func.test +++ b/test/func.test @@ -1545,4 +1545,12 @@ do_catchsql_test func-37.120 { SELECT sum(x) FROM c; } {1 {integer overflow}} +# 2023-08-28 forum post https://sqlite.org/forum/forumpost/1c06ddcacc86032a +# Incorrect handling of infinity by SUM(). +# +do_execsql_test func-38.100 { + WITH t1(x) AS (VALUES(9e+999)) SELECT sum(x), avg(x), total(x) FROM t1; + WITH t1(x) AS (VALUES(-9e+999)) SELECT sum(x), avg(x), total(x) FROM t1; +} {Inf Inf Inf -Inf -Inf -Inf} + finish_test diff --git a/test/json102.test b/test/json102.test index 276c1816a..ce901efeb 100644 --- a/test/json102.test +++ b/test/json102.test @@ -48,6 +48,9 @@ do_execsql_test json102-180 { do_execsql_test json102-190 { SELECT json_array_length('[1,2,3,4]'); } {{4}} +do_execsql_test json102-191 { + SELECT json_array_length( json_remove('[1,2,3,4]','$[2]') ); +} {{3}} do_execsql_test json102-200 { SELECT json_array_length('[1,2,3,4]', '$'); } {{4}} diff --git a/test/selectH.test b/test/selectH.test index a97679bda..41f0999fe 100644 --- a/test/selectH.test +++ b/test/selectH.test @@ -113,6 +113,33 @@ do_execsql_test 4.1 { CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); SELECT 1 FROM (SELECT DISTINCT name COLLATE rtrim FROM sqlite_schema UNION ALL SELECT a FROM t1); -} 1 +} {1 1} + +do_execsql_test 4.2 { + SELECT DISTINCT name COLLATE rtrim FROM sqlite_schema + UNION ALL + SELECT a FROM t1 +} {v1 t1} + +#------------------------------------------------------------------------- +# forum post https://sqlite.org/forum/forumpost/b83c7b2168 +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1 (val1); + INSERT INTO t1 VALUES(4); + INSERT INTO t1 VALUES(5); + CREATE TABLE t2 (val2); +} +do_execsql_test 5.1 { + SELECT DISTINCT val1 FROM t1 UNION ALL SELECT val2 FROM t2; +} { + 4 5 +} +do_execsql_test 5.2 { + SELECT count(1234) FROM ( + SELECT DISTINCT val1 FROM t1 UNION ALL SELECT val2 FROM t2 + ) +} {2} finish_test diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index ce2ce01dd..2493970ea 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -98,7 +98,10 @@ namespace eval trd { set build(All-O0) { -O0 --enable-all } - set build(All-Sanitize) { --enable-all -fsanitize=address,undefined } + set build(All-Sanitize) { + -DSQLITE_OMIT_LOOKASIDE=1 + --enable-all -fsanitize=address,undefined + } set build(Sanitize) { CC=clang -fsanitize=address,undefined From a1204dd3795bd9bd9d7ad7a78e568543d337fc6a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 9 Oct 2023 14:17:11 -0400 Subject: [PATCH 110/340] add pragma page_size compatibility so it will operate on encrypted databases --- src/crypto.c | 9 +++++++- test/sqlcipher-core.test | 46 ++++++++++++++++++++++++++++++++++++- test/sqlcipher-pragmas.test | 29 +++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 9c722fd42..fcb1d9d6c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -260,7 +260,12 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlcipher_vdbe_return_string(pParse, "rekey_kdf_iter", message, P4_TRANSIENT); sqlite3_log(SQLITE_WARNING, message); }else - if( sqlite3_stricmp(zLeft,"cipher_page_size")==0 ){ + if( sqlite3_stricmp(zLeft,"page_size")==0 || sqlite3_stricmp(zLeft,"cipher_page_size")==0 ){ + /* PRAGMA cipher_page_size will alter the size of the database pages while ensuring that the + required reserve space is allocated at the end of each page. This will also override the + standard SQLite PRAGMA page_size behavior if a codec context is attached to the database handle. + If PRAGMA page_size is invoked but a codec context is not attached (i.e. dealing with a standard + unencrypted database) then return early and allow the standard PRAGMA page_size logic to apply. */ if(ctx) { if( zRight ) { int size = atoi(zRight); @@ -272,6 +277,8 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef char * page_size = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_pagesize(ctx)); sqlcipher_vdbe_return_string(pParse, "cipher_page_size", page_size, P4_DYNAMIC); } + } else { + return 0; /* return early so that the PragTyp_PAGE_SIZE case logic in pragma.c will take effect */ } }else if( sqlite3_stricmp(zLeft,"cipher_default_page_size")==0 ){ diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index 5aed9fb66..c4b5e3bf4 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -498,7 +498,7 @@ file delete -force test.db # key and page size # 4. verify that the table is readable # and the data just inserted is visible -do_test custom-pagesize { +do_test custom-pagesize-pragma-cipher-page-size { sqlite_orig db test.db execsql { @@ -528,11 +528,55 @@ do_test custom-pagesize { } {ok 1000} db close +file delete -force test.db + +# run the same logic as previous test but use +# pragma page_size instead +do_test custom-pagesize-pragma-pagesize { + sqlite_orig db test.db + + execsql { + PRAGMA key = 'testkey'; + PRAGMA page_size = 8192; + CREATE table t1(a,b); + BEGIN; + } + + for {set i 1} {$i<=1000} {incr i} { + set r [expr {int(rand()*500000)}] + execsql "INSERT INTO t1 VALUES($i,'value $r');" + } + + execsql { + COMMIT; + } + + db close + sqlite_orig db test.db + + execsql { + PRAGMA key = 'testkey'; + PRAGMA page_size = 8192; + SELECT count(*) FROM t1; + } + +} {ok 1000} +db close +file delete -force test.db # open the database with the default page size ## and verfiy that it is not readable do_test custom-pagesize-must-match { sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_page_size = 8192; + CREATE table t1(a,b); + } + + db close + sqlite_orig db test.db + catchsql { PRAGMA key = 'testkey'; SELECT name FROM sqlite_schema WHERE type='table'; diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index daf7f8a9b..c7087bb9e 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -202,6 +202,35 @@ do_test verify-pragma-cipher-page-size-changed { db close file delete -force test.db +# verify that a call to pragma page_size +# will report change via both page_size and cipher_page_size +# when there is an attached codec +do_test verify-pragma-page-size-encrypted { + sqlite_orig db test.db + execsql { + PRAGMA key = 'test'; + PRAGMA page_size = 8192; + PRAGMA page_size; + PRAGMA cipher_page_size; + } +} {ok 8192 8192} +db close +file delete -force test.db + +# verify that a call to pragma page_size +# will not report a change to cipher_page_size for an +# unencrypted database +do_test verify-pragma-page-size-plaintext { + sqlite_orig db test.db + execsql { + PRAGMA page_size = 8192; + PRAGMA page_size; + PRAGMA cipher_page_size; + } +} {8192} +db close +file delete -force test.db + # verify setting cipher_store_pass before key # does not cause segfault do_test verify-cipher-store-pass-before-key-does-not-segfault { From d35c5179996b980e67908376a479703cf7676b8c Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 12 Oct 2023 16:41:17 -0400 Subject: [PATCH 111/340] Snapshot of upstream SQLite 3.43.2 --- Makefile.in | 14 ++++-- VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 ++++---- ext/fts5/fts5_index.c | 1 - ext/fts5/fts5_main.c | 7 ++- ext/fts5/test/fts5misc.test | 4 +- ext/fts5/test/fts5rank.test | 24 ++++++++++ ext/wasm/api/sqlite3-api-glue.js | 6 +-- manifest | 45 +++++++++--------- manifest.uuid | 2 +- src/btree.c | 2 + src/json.c | 6 ++- src/shell.c.in | 2 +- src/util.c | 14 +++--- src/vdbeblob.c | 3 +- test/json101.test | 27 ++++++++++- tool/mkautoconfamal.sh | 8 ++++ tool/srctree-check.tcl | 79 ++++++++++++++++++++++++++++++++ 19 files changed, 207 insertions(+), 59 deletions(-) create mode 100644 tool/srctree-check.tcl diff --git a/Makefile.in b/Makefile.in index e938cec52..9ad135b3d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1312,17 +1312,23 @@ testrunner: testfixture$(TEXE) # Runs both fuzztest and testrunner, consecutively. # -devtest: testfixture$(TEXE) fuzztest testrunner +devtest: srctree-check testfixture$(TEXE) fuzztest testrunner -mdevtest: +mdevtest: srctree-check $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest sdevtest: $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest +# Validate that various generated files in the source tree +# are up-to-date. +# +srctree-check: $(TOP)/tool/srctree-check.tcl + $(TCLSH_CMD) $(TOP)/tool/srctree-check.tcl + # Testing for a release # -releasetest: testfixture$(TEXE) +releasetest: srctree-check testfixture$(TEXE) ./testfixture$(TEXE) $(TOP)/test/testrunner.tcl release # Minimal testing that runs in less than 3 minutes @@ -1333,7 +1339,7 @@ quicktest: ./testfixture$(TEXE) # This is the common case. Run many tests that do not take too long, # including fuzzcheck, sqlite3_analyzer, and sqldiff tests. # -test: fuzztest sourcetest $(TESTPROGS) tcltest +test: srctree-check fuzztest sourcetest $(TESTPROGS) tcltest # Run a test using valgrind. This can take a really long time # because valgrind is so much slower than a native machine. diff --git a/VERSION b/VERSION index 9291fe6ef..a14006064 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.43.1 +3.43.2 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index 093c99d67..032d20a4c 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.43.0]) +AC_INIT([sqlite],[3.43.2]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index ec753e7ee..90046959f 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.69 for sqlite 3.43.1. +# Generated by GNU Autoconf 2.69 for sqlite 3.43.2. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.43.1' -PACKAGE_STRING='sqlite 3.43.1' +PACKAGE_VERSION='3.43.2' +PACKAGE_STRING='sqlite 3.43.2' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1470,7 +1470,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.43.1 to adapt to many kinds of systems. +\`configure' configures sqlite 3.43.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1535,7 +1535,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.43.1:";; + short | recursive ) echo "Configuration of sqlite 3.43.2:";; esac cat <<\_ACEOF @@ -1665,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.43.1 +sqlite configure 3.43.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2084,7 +2084,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.43.1, which was +It was created by sqlite $as_me 3.43.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12457,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.43.1, which was +This file was extended by sqlite $as_me 3.43.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12523,7 +12523,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.43.1 +sqlite config.status 3.43.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index e729b13f2..d6461fcb6 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -5057,7 +5057,6 @@ static void fts5DoSecureDelete( int iIdx = 0; int iStart = 0; int iKeyOff = 0; - int iPrevKeyOff = 0; int iDelKeyOff = 0; /* Offset of deleted key, if any */ nIdx = nPg-iPgIdx; diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index c34a5a332..db44dfa2a 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -1329,6 +1329,9 @@ static int fts5FilterMethod( pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64); } + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + if( rc!=SQLITE_OK ) goto filter_out; + if( pTab->pSortCsr ){ /* If pSortCsr is non-NULL, then this call is being made as part of ** processing for a "... MATCH ORDER BY rank" query (ePlan is @@ -1351,7 +1354,9 @@ static int fts5FilterMethod( pCsr->pExpr = pTab->pSortCsr->pExpr; rc = fts5CursorFirst(pTab, pCsr, bDesc); }else if( pCsr->pExpr ){ - rc = fts5CursorParseRank(pConfig, pCsr, pRank); + if( rc==SQLITE_OK ){ + rc = fts5CursorParseRank(pConfig, pCsr, pRank); + } if( rc==SQLITE_OK ){ if( bOrderByRank ){ pCsr->ePlan = FTS5_PLAN_SORTED_MATCH; diff --git a/ext/fts5/test/fts5misc.test b/ext/fts5/test/fts5misc.test index da3f65269..adab15780 100644 --- a/ext/fts5/test/fts5misc.test +++ b/ext/fts5/test/fts5misc.test @@ -44,12 +44,12 @@ do_catchsql_test 1.2.2 { do_catchsql_test 1.3.1 { SELECT highlight(t1, 4, '', '') FROM t1('*reads'); -} {1 {no such cursor: 1}} +} {1 {no such cursor: 2}} do_catchsql_test 1.3.2 { SELECT a FROM t1 WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*reads')); -} {1 {no such cursor: 1}} +} {1 {no such cursor: 2}} db close sqlite3 db test.db diff --git a/ext/fts5/test/fts5rank.test b/ext/fts5/test/fts5rank.test index 22534e8e0..8cf223f44 100644 --- a/ext/fts5/test/fts5rank.test +++ b/ext/fts5/test/fts5rank.test @@ -180,4 +180,28 @@ do_execsql_test 6.1 { {table table table} {the table names.} {rank on an fts5 table} } + +#------------------------------------------------------------------------- +# forum post: https://sqlite.org/forum/forumpost/a2dd636330 +# +reset_db +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t USING fts5 (a, b); + INSERT INTO t (a, b) VALUES ('data1', 'sentence1'), ('data2', 'sentence2'); + INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)'); +} + +sqlite3 db2 test.db +do_execsql_test -db db2 1.1 { + SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK; +} {data1 sentence1 1 data2 sentence2 1} + +do_execsql_test 1.2 { + INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)'); +} +do_execsql_test -db db2 1.3 { + SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK; +} {data1 sentence1 1 data2 sentence2 1} +db2 close + finish_test diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 60050461c..f23a02366 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -888,9 +888,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ consistency with non-special-case wrappings. */ const __dbArgcMismatch = (pDb,f,n)=>{ - return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, - f+"() requires "+n+" argument"+ - (1===n?"":'s')+"."); + return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, + f+"() requires "+n+" argument"+ + (1===n?"":'s')+"."); }; /** Code duplication reducer for functions which take an encoding diff --git a/manifest b/manifest index d65c54a67..9b302487f 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.43.1 -D 2023-09-11T12:01:27.641 +C Version\s3.43.2 +D 2023-10-10T12:14:04.911 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 1e9105ffed727b1557ce59f0941c5d271e276ec00bc9823f03d77a89e131b918 +F Makefile.in 0f17b4b3550aae10deb5475429a2388a0960ebe85a29d1dd97aa0a2444b0bf48 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 F Makefile.msc 26c2d196391a285c279adb10fd6001774d9b243af94b700b681e4a49cd476684 F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8 -F VERSION 536457fc6444f62ad9e5082a82e4e190f7ba3f4d903a63b835835230f7a8f853 +F VERSION 56b287eac8404526c4faa0bdfa7e31baed534068fa3acece479b5d6c35cfca1e F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -22,7 +22,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac fce5eca183cf47853f7d5b81c1e7bfa41c021fb460939ec67ef83653ffc6a531 +F autoconf/tea/configure.ac c968ba373f2afaad123e59e14baf20aa423ac0e5668ca8b40092b96b408d9938 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 8f9bd15e5edda5730621f630be36c7273a2ad7dfa080bfdcb7abb8f063967c52 x +F configure 2c538b418bb71f7e8bb62193eae709a1f7dd4342f74f6530891bd1e937c03df2 x F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -93,8 +93,8 @@ F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b7292 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d -F ext/fts5/fts5_index.c bb405cb68fc5306107133d29ea8f86337bba360fe1c6029043d16a3026a26767 -F ext/fts5/fts5_main.c 7070031993ba5b5d89b13206ec4ef624895f2f7c0ec72725913d301e4d382445 +F ext/fts5/fts5_index.c bf9c3b2fc1cbc2b996693e393bb708756d770de2e931602102c2f0adbf75db2c +F ext/fts5/fts5_main.c 8a16865b1652667c5a80592a4860a8fd40de283869d28da347a671c1c441933f F ext/fts5/fts5_storage.c 3c9b41fce41b6410f2e8f82eb035c6a29b2560483f773e6dc98cf3cb2e4ddbb5 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee @@ -178,7 +178,7 @@ F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fc F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2 -F ext/fts5/test/fts5misc.test 416ec0ffbc79320a0760ec32d6684866e3ccd3fbce09f9bcd62d9aee4c666b43 +F ext/fts5/test/fts5misc.test 3947a481486156536f417a015343f05f9f367a17f282a4e02bdb95bb5bd43293 F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd @@ -192,7 +192,7 @@ F ext/fts5/test/fts5porter2.test 0d251a673f02fa13ca7f011654873b3add20745f7402f10 F ext/fts5/test/fts5prefix.test a0fa67b06650f2deaa7bf27745899d94e0fb547ad9ecbd08bfad98c04912c056 F ext/fts5/test/fts5prefix2.test 3847ce46f70b82d61c6095103a9d7c53f2952c40a4704157bc079c04d9c8b18b F ext/fts5/test/fts5query.test ac363b17a442620bb0780e93c24f16a5f963dfe2f23dc85647b869efcfada728 -F ext/fts5/test/fts5rank.test c9fd4a1e36b4fa92d572ec13d846469b97da249d1c2f7fd3ee7e017ce46f2416 +F ext/fts5/test/fts5rank.test 30f29e278cd7fb8831ba4f082feb74d8eb90c463bf07113ae200afc2b467ef32 F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde40a46fc5d785b451e7 F ext/fts5/test/fts5restart.test 835ecc8f449e3919f72509ab58056d0cedca40d1fe04108ccf8ac4c2ba41f415 F ext/fts5/test/fts5rowid.test b8790ec170a8dc1942a15aef3db926a5f3061b1ff171013003d8297203a20ad6 @@ -542,7 +542,7 @@ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219 F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e -F ext/wasm/api/sqlite3-api-glue.js b65e546568f1dfb35205b9792feb5146a6323d71b55cda58e2ed30def6dd52f3 +F ext/wasm/api/sqlite3-api-glue.js 26aedfb27915f4f316f6eac84078443e4f0d2dfe5f012310014923ed4b77b2b6 F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8 F ext/wasm/api/sqlite3-api-prologue.js 5f283b096b98bfb1ee2f2201e7ff0489dff00e29e1030c30953bdb4f5b87f4bd F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b1738645c0134562bb84e88e2fec @@ -623,7 +623,7 @@ F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 -F src/btree.c 7a37bdf09f338561880860681cb03499a60c3bb0869e539c58bc1d2cdd705ff2 +F src/btree.c 15611f72bd65aef23b3ae4026164f86e2466791be5b9745f8421d0eed3c5238a F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 F src/btreeInt.h 91a9e0c41a0e71fa91a742ec285c63dd8dcb38b73d14fae0ed7209174ff0fdc1 F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4 @@ -644,7 +644,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c 51141f1c09ccb177057e5813e6302a5e32e5ba88cc4a756318a35081010fc6df +F src/json.c 5c25f82e1c0f3eb44fb9a23feff6b7b952fd40122d9e3ab601a84ea83710a980 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 98cfba10989b3da6f1807ad42444017742db7f100a54f1032af7a8b1295912c0 F src/main.c fde8f13c876a658b4e8b74b77d875ca887915c174ea6a2f3122d80966f93d865 @@ -685,7 +685,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c e9fb48546ab1882639a3a960383f6342dddb776c0227615f8e19de51f0102f68 -F src/shell.c.in 2f9be25294b68b07e7e81f0adcec4475aba6011b64f160e414efe226910c4d7b +F src/shell.c.in d381ea090c17db5d50049e6c06e9e175d8d712c7f9bc7a0b8a51616af44f060c F src/sqlite.h.in 73a366c1c45d5ac9888cfe81c458826a44498531d106cfb4f328193ab5f6f17d F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 2f30b2671f4c03cd27a43f039e11251391066c97d11385f5f963bb40b03038ac @@ -752,14 +752,14 @@ F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c 278b81c3b33db1b5a5f3859adf8905c165b910080043061d44d3c5a25b4b406d +F src/util.c 79f084901f52d9d7025200a177f7d1d66f83a73333fe486a2626490eaa49bc4c F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 F src/vdbe.c 346d848a0bf8128e3e3722c5406f4bde6c32d7093b93402c6f8e0718d19305c3 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c F src/vdbeapi.c 37341acd781fda162e8cf4d9fc2eaea2febad3b365877a9d7233b8c6d0960d85 F src/vdbeaux.c e3aa5c46827cd95e0fc4d0f302fa3e901ab5f07258fdbb42709eeef40f63018d -F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce +F src/vdbeblob.c 4cf5aa130e96e3b52ba3fb54b7f9606c942ab988dbb32cb19cff4db24e06aeec F src/vdbemem.c 317b9f48708139db6239ade40c7980b4bc8233168383690d588dad6d8437f722 F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 @@ -1273,7 +1273,7 @@ F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x -F test/json101.test dc9d5a2a5b1fd1b54dbd71c538b17933cc98d84b4c1f821ead754933663dca55 +F test/json101.test 4d78048b185ffb9ae37645fa6934d48fc182473ae0bae088a1e2f7ba483c3a71 F test/json102.test 4c69694773a470f1fda34e5f4ba24920b35184fb66050b450fc2ef9ab5ad310b F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 @@ -2012,7 +2012,7 @@ F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c c34e5944318415de513d29a6098df247a9618c96d83c38d4abd88641fe46e669 F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 -F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 +F tool/mkautoconfamal.sh 7077151b62ecb2848c5b2d36cdbcd8caa0b58f2c12dcdac6e982da39d29d213c F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x F tool/mkctimec.tcl aca4b83e49aecf10368cd5d11bc4847061041ade026db5bd8da17ef201f1403b x F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd @@ -2060,6 +2060,7 @@ F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaa F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f +F tool/srctree-check.tcl cef630bc4ff21a460d72479c43a42bf1c1ed61897659305c35c8d72e91bcb176 F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37 F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d @@ -2092,10 +2093,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P fb24ca17fa1be739395fc471a8d4c9f876874c1709d41803925027b56587cf83 -R 00850c800fbae2990176d31bf7ab6581 +P 88a118905b95bd82e15c3c1e63a7620071ab9ab3517b77820553fcd7e600b0c8 +R 066b49c963926ac90c34ea41a60520db T +sym-release * -T +sym-version-3.43.1 * +T +sym-version-3.43.2 * U drh -Z afe4b484bf2298bff989f42abbf408e9 +Z 8f5f6e60777c4954f5d2670202697dfe # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4b65e95ca..c15794dc0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2d3a40c05c49e1a49264912b1a05bc2143ac0e7c3df588276ce80a4cbc9bd1b0 +4310099cce5a487035fa535dd3002c59ac7f1d1bec68d7cf317fd3e769484790 diff --git a/src/btree.c b/src/btree.c index c23f86e1d..9b59f3ff8 100644 --- a/src/btree.c +++ b/src/btree.c @@ -7493,6 +7493,7 @@ static int rebuildPage( int k; /* Current slot in pCArray->apEnd[] */ u8 *pSrcEnd; /* Current pCArray->apEnd[k] value */ + assert( nCell>0 ); assert( i(u32)usableSize) ){ j = 0; } @@ -7799,6 +7800,7 @@ static int editPage( return SQLITE_OK; editpage_fail: /* Unable to edit this page. Rebuild it from scratch instead. */ + if( nNew<1 ) return SQLITE_CORRUPT_BKPT; populateCellCache(pCArray, iNew, nNew); return rebuildPage(pCArray, iNew, nNew, pPg); } diff --git a/src/json.c b/src/json.c index 253fce9f4..2e81948e7 100644 --- a/src/json.c +++ b/src/json.c @@ -2902,6 +2902,7 @@ static void jsonReplaceFunc( } pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); if( pParse==0 ) return; + pParse->nJPRef++; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); pParse->useMod = 1; @@ -2914,6 +2915,7 @@ static void jsonReplaceFunc( jsonReturnJson(pParse, pParse->aNode, ctx, 1); replace_err: jsonDebugPrintParse(pParse); + jsonParseFree(pParse); } @@ -2948,6 +2950,7 @@ static void jsonSetFunc( } pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); if( pParse==0 ) return; + pParse->nJPRef++; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); bApnd = 0; @@ -2964,9 +2967,8 @@ static void jsonSetFunc( } jsonDebugPrintParse(pParse); jsonReturnJson(pParse, pParse->aNode, ctx, 1); - jsonSetDone: - /* no cleanup required */; + jsonParseFree(pParse); } /* diff --git a/src/shell.c.in b/src/shell.c.in index 07d92d014..f6ee64fc3 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -1242,7 +1242,7 @@ static void shellDtostr( char z[400]; if( n<1 ) n = 1; if( n>350 ) n = 350; - sprintf(z, "%#+.*e", n, r); + snprintf(z, sizeof(z)-1, "%#+.*e", n, r); sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); } diff --git a/src/util.c b/src/util.c index 97deb64cf..590a06523 100644 --- a/src/util.c +++ b/src/util.c @@ -1025,29 +1025,29 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ double rr[2]; rr[0] = r; rr[1] = 0.0; - if( rr[0]>1.84e+19 ){ - while( rr[0]>1.84e+119 ){ + if( rr[0]>9.223372036854774784e+18 ){ + while( rr[0]>9.223372036854774784e+118 ){ exp += 100; dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); } - while( rr[0]>1.84e+29 ){ + while( rr[0]>9.223372036854774784e+28 ){ exp += 10; dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); } - while( rr[0]>1.84e+19 ){ + while( rr[0]>9.223372036854774784e+18 ){ exp += 1; dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); } }else{ - while( rr[0]<1.84e-82 ){ + while( rr[0]<9.223372036854774784e-83 ){ exp -= 100; dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); } - while( rr[0]<1.84e+08 ){ + while( rr[0]<9.223372036854774784e+07 ){ exp -= 10; dekkerMul2(rr, 1.0e+10, 0.0); } - while( rr[0]<1.84e+18 ){ + while( rr[0]<9.22337203685477478e+17 ){ exp -= 1; dekkerMul2(rr, 1.0e+01, 0.0); } diff --git a/src/vdbeblob.c b/src/vdbeblob.c index 32987da13..ea6f016e3 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -59,8 +59,7 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ /* Set the value of register r[1] in the SQL statement to integer iRow. ** This is done directly as a performance optimization */ - v->aMem[1].flags = MEM_Int; - v->aMem[1].u.i = iRow; + sqlite3VdbeMemSetInt64(&v->aMem[1], iRow); /* If the statement has been run before (and is paused at the OP_ResultRow) ** then back it up to the point where it does the OP_NotExists. This could diff --git a/test/json101.test b/test/json101.test index 4da1d132c..459033074 100644 --- a/test/json101.test +++ b/test/json101.test @@ -1013,8 +1013,31 @@ do_execsql_test json101-21.27 { SELECT json_group_object(x,y) FROM c; } {{{"a":1,"b":2.0,"c":null,:"three","e":"four"}}} - - +# 2023-10-09 https://sqlite.org/forum/forumpost/b25edc1d46 +# UAF due to JSON cache overflow +# +do_execsql_test json101-22.1 { + SELECT json_set( + '{}', + '$.a', json('1'), + '$.a', json('2'), + '$.b', json('3'), + '$.b', json('4'), + '$.c', json('5'), + '$.c', json('6') + ); +} {{{"a":2,"b":4,"c":6}}} +do_execsql_test json101-22.2 { + SELECT json_replace( + '{"a":7,"b":8,"c":9}', + '$.a', json('1'), + '$.a', json('2'), + '$.b', json('3'), + '$.b', json('4'), + '$.c', json('5'), + '$.c', json('6') + ); +} {{{"a":2,"b":4,"c":6}}} finish_test diff --git a/tool/mkautoconfamal.sh b/tool/mkautoconfamal.sh index eacd9fa51..b7483189d 100644 --- a/tool/mkautoconfamal.sh +++ b/tool/mkautoconfamal.sh @@ -25,6 +25,14 @@ VERSION=`cat $TOP/VERSION` HASH=`sed 's/^\(..........\).*/\1/' $TOP/manifest.uuid` DATETIME=`grep '^D' $TOP/manifest | sed -e 's/[^0-9]//g' -e 's/\(............\).*/\1/'` +# Verify that the version number in the TEA autoconf file is correct. +# Fail with an error if not. +# +if grep $VERSION autoconf/tea/configure.ac +then echo "TEA version number ok" +else echo "TEA version number mismatch. Should be $VERSION"; exit 1 +fi + # If this script is given an argument of --snapshot, then generate a # snapshot tarball named for the current checkout SHA1 hash, rather than # the version number. diff --git a/tool/srctree-check.tcl b/tool/srctree-check.tcl new file mode 100644 index 000000000..234e70fae --- /dev/null +++ b/tool/srctree-check.tcl @@ -0,0 +1,79 @@ +#!/usr/bin/tclsh +# +# Run this script from the top of the source tree in order to confirm that +# various aspects of the source tree are up-to-date. Items checked include: +# +# * Makefile.msc and autoconf/Makefile.msc agree +# * src/ctime.tcl is consistent with tool/mkctimec.tcl +# * VERSION agrees with autoconf/tea/configure.ac +# * src/pragma.h agrees with tool/mkpragmatab.tcl +# +# Other tests might be added later. +# +# Error messages are printed and the process exists non-zero if problems +# are found. If everything is ok, no output is generated and the process +# exits with 0. +# + +# Read an entire file. +# +proc readfile {filename} { + set fd [open $filename rb] + set txt [read $fd] + close $fd + return $txt +} + +# Find the root of the tree. +# +set ROOT [file dir [file dir [file normalize $argv0]]] +cd $ROOT + +# Name of the TCL interpreter +# +set TCLSH [info nameofexe] + +######################### autoconf/tea/configure.ac ########################### + +set confac [readfile $ROOT/autoconf/tea/configure.ac] +set vers [readfile $ROOT/VERSION] +set pattern {AC_INIT([sqlite],[} +append pattern [string trim $vers] +append pattern {])} +if {[string first $pattern $confac]<=0} { + puts "ERROR: ./autoconf/tea/configure.ac does not agree with ./VERSION" + exit 1 +} + +######################### autoconf/Makefile.msc ############################### + +set f1 [readfile $ROOT/autoconf/Makefile.msc] +exec mv $ROOT/autoconf/Makefile.msc $ROOT/autoconf/Makefile.msc.tmp +exec $TCLSH $ROOT/tool/mkmsvcmin.tcl +set f2 [readfile $ROOT/autoconf/Makefile.msc] +exec mv $ROOT/autoconf/Makefile.msc.tmp $ROOT/autoconf/Makefile.msc +if {$f1 != $f2} { + puts "ERROR: ./autoconf/Makefile.msc does not agree with ./Makefile.msc" +} + +######################### src/pragma.h ######################################## + +set f1 [readfile $ROOT/src/pragma.h] +exec mv $ROOT/src/pragma.h $ROOT/src/pragma.h.tmp +exec $TCLSH $ROOT/tool/mkpragmatab.tcl +set f2 [readfile $ROOT/src/pragma.h] +exec mv $ROOT/src/pragma.h.tmp $ROOT/src/pragma.h +if {$f1 != $f2} { + puts "ERROR: ./src/pragma.h does not agree with ./tool/mkpragmatab.tcl" +} + +######################### src/ctime.c ######################################## + +set f1 [readfile $ROOT/src/ctime.c] +exec mv $ROOT/src/ctime.c $ROOT/src/ctime.c.tmp +exec $TCLSH $ROOT/tool/mkctimec.tcl +set f2 [readfile $ROOT/src/ctime.c] +exec mv $ROOT/src/ctime.c.tmp $ROOT/src/ctime.c +if {$f1 != $f2} { + puts "ERROR: ./src/ctime.c does not agree with ./tool/mkctimec.tcl" +} From cc74415468013fa2f5a25079438c944a45ea96cd Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 3 Nov 2023 09:50:08 -0400 Subject: [PATCH 112/340] Snapshot of upstream SQLite 3.44.0 --- Makefile.in | 65 +- Makefile.msc | 27 +- VERSION | 2 +- autoconf/Makefile.msc | 14 +- autoconf/tea/configure.ac | 2 +- configure | 72 +- configure.ac | 19 +- doc/compile-for-windows.md | 53 +- doc/testrunner.md | 284 + ext/expert/expert1.test | 19 + ext/expert/sqlite3expert.c | 105 +- ext/fts3/fts3.c | 98 +- ext/fts3/fts3Int.h | 1 + ext/fts3/fts3_aux.c | 3 +- ext/fts3/fts3_term.c | 3 +- ext/fts3/fts3_tokenize_vtab.c | 3 +- ext/fts3/fts3_write.c | 26 +- ext/fts5/fts5_aux.c | 51 +- ext/fts5/fts5_index.c | 120 +- ext/fts5/fts5_main.c | 124 +- ext/fts5/fts5_storage.c | 4 +- ext/fts5/fts5_test_tok.c | 3 +- ext/fts5/fts5_vocab.c | 3 +- ext/fts5/test/fts5aa.test | 7 +- ext/fts5/test/fts5aux.test | 28 +- ext/fts5/test/fts5conflict.test | 40 + ext/fts5/test/fts5content.test | 1 - ext/fts5/test/fts5contentless.test | 1 - ext/fts5/test/fts5contentless2.test | 1 - ext/fts5/test/fts5contentless3.test | 1 - ext/fts5/test/fts5contentless4.test | 1 - ext/fts5/test/fts5contentless5.test | 1 - ext/fts5/test/fts5corrupt.test | 4 + ext/fts5/test/fts5corrupt2.test | 3 + ext/fts5/test/fts5faultG.test | 50 + ext/fts5/test/fts5integrity.test | 38 + ext/fts5/test/fts5misc.test | 6 +- ext/fts5/test/fts5optimize2.test | 21 +- ext/fts5/test/fts5optimize3.test | 45 + ext/fts5/test/fts5savepoint.test | 2 +- ext/fts5/test/fts5secure.test | 59 +- ext/fts5/test/fts5secure6.test | 69 +- ext/fts5/test/fts5secure7.test | 116 + ext/fts5/test/fts5trigram.test | 38 + ext/jni/GNUmakefile | 360 +- ext/jni/README.md | 185 +- ext/jni/jar-dist.make | 13 +- ext/jni/src/c/sqlite3-jni.c | 6705 ++++++++++------- ext/jni/src/c/sqlite3-jni.h | 3022 ++++---- ext/jni/src/org/sqlite/jni/Authorizer.java | 32 - ext/jni/src/org/sqlite/jni/AutoExtension.java | 31 - ext/jni/src/org/sqlite/jni/BusyHandler.java | 45 - ext/jni/src/org/sqlite/jni/Fts5Function.java | 27 - ext/jni/src/org/sqlite/jni/OutputPointer.java | 165 - ext/jni/src/org/sqlite/jni/ResultCode.java | 155 - ext/jni/src/org/sqlite/jni/SQLFunction.java | 172 - ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 1645 ---- ext/jni/src/org/sqlite/jni/Tester1.java | 1163 --- ext/jni/src/org/sqlite/jni/TesterFts5.java | 87 - ext/jni/src/org/sqlite/jni/Tracer.java | 62 - .../org/sqlite/jni/annotation/NotNull.java | 59 + .../org/sqlite/jni/annotation/Nullable.java | 32 + .../sqlite/jni/annotation/package-info.java | 17 + .../jni/capi/AbstractCollationCallback.java | 34 + .../sqlite/jni/capi/AggregateFunction.java | 72 + .../sqlite/jni/capi/AuthorizerCallback.java | 28 + .../jni/capi/AutoExtensionCallback.java | 40 + .../sqlite/jni/capi/BusyHandlerCallback.java | 26 + ext/jni/src/org/sqlite/jni/capi/CApi.java | 2449 ++++++ .../org/sqlite/jni/capi/CallbackProxy.java | 44 + .../sqlite/jni/capi/CollationCallback.java | 35 + .../CollationNeededCallback.java} | 12 +- .../CommitHookCallback.java} | 14 +- .../sqlite/jni/capi/ConfigLogCallback.java | 25 + .../sqlite/jni/capi/ConfigSqllogCallback.java | 25 + .../jni/{ => capi}/NativePointerHolder.java | 19 +- .../org/sqlite/jni/capi/OutputPointer.java | 231 + .../sqlite/jni/capi/PrepareMultiCallback.java | 78 + .../PreupdateHookCallback.java} | 18 +- .../ProgressHandlerCallback.java} | 14 +- .../src/org/sqlite/jni/capi/ResultCode.java | 155 + .../RollbackHookCallback.java} | 14 +- .../src/org/sqlite/jni/capi/SQLFunction.java | 103 + .../jni/{tester => capi}/SQLTester.java | 108 +- .../org/sqlite/jni/capi/ScalarFunction.java | 33 + .../sqlite/jni/capi/TableColumnMetadata.java | 35 + ext/jni/src/org/sqlite/jni/capi/Tester1.java | 1976 +++++ .../org/sqlite/jni/capi/TraceV2Callback.java | 50 + .../sqlite/jni/capi/UpdateHookCallback.java | 25 + .../sqlite/jni/{ => capi}/ValueHolder.java | 8 +- .../org/sqlite/jni/capi/WindowFunction.java | 39 + .../org/sqlite/jni/capi/XDestroyCallback.java | 37 + .../src/org/sqlite/jni/capi/package-info.java | 89 + .../org/sqlite/jni/{ => capi}/sqlite3.java | 14 +- .../org/sqlite/jni/capi/sqlite3_backup.java | 31 + .../src/org/sqlite/jni/capi/sqlite3_blob.java | 31 + .../org/sqlite/jni/capi/sqlite3_context.java | 79 + .../sqlite/jni/{ => capi}/sqlite3_stmt.java | 9 +- .../sqlite/jni/{ => capi}/sqlite3_value.java | 2 +- .../src/org/sqlite/jni/{ => fts5}/Fts5.java | 12 +- .../sqlite/jni/{ => fts5}/Fts5Context.java | 3 +- .../jni/{ => fts5}/Fts5ExtensionApi.java | 73 +- .../sqlite/jni/{ => fts5}/Fts5PhraseIter.java | 3 +- .../sqlite/jni/{ => fts5}/Fts5Tokenizer.java | 3 +- .../src/org/sqlite/jni/fts5/TesterFts5.java | 832 ++ .../XTokenizeCallback.java} | 15 +- .../org/sqlite/jni/{ => fts5}/fts5_api.java | 51 +- .../{ => fts5}/fts5_extension_function.java | 24 +- .../sqlite/jni/{ => fts5}/fts5_tokenizer.java | 8 +- .../src/org/sqlite/jni/sqlite3_context.java | 66 - .../{tester => }/test-script-interpreter.md | 0 .../jni/wrapper1/AggregateFunction.java | 82 + .../sqlite/jni/wrapper1/ScalarFunction.java | 37 + .../org/sqlite/jni/wrapper1/SqlFunction.java | 301 + .../src/org/sqlite/jni/wrapper1/Sqlite.java | 218 + .../sqlite/jni/wrapper1/SqliteException.java | 82 + .../src/org/sqlite/jni/wrapper1/Tester2.java | 584 ++ .../org/sqlite/jni/wrapper1/ValueHolder.java | 25 + ext/jni/src/tests/000-000-sanity.test | 3 +- ext/jni/src/tests/900-001-fts.test | 12 + ext/lsm1/lsm_vtab.c | 5 + ext/misc/amatch.c | 3 +- ext/misc/btreeinfo.c | 3 +- ext/misc/carray.c | 5 + ext/misc/closure.c | 3 +- ext/misc/completion.c | 3 +- ext/misc/csv.c | 10 + ext/misc/explain.c | 1 + ext/misc/fileio.c | 1 + ext/misc/fossildelta.c | 3 +- ext/misc/fuzzer.c | 5 + ext/misc/memstat.c | 1 + ext/misc/mmapwarm.c | 7 +- ext/misc/prefixes.c | 3 +- ext/misc/qpvtab.c | 3 +- ext/misc/series.c | 3 +- ext/misc/spellfix.c | 5 + ext/misc/stmt.c | 1 + ext/misc/templatevtab.c | 3 +- ext/misc/unionvtab.c | 3 +- ext/misc/vfsstat.c | 5 + ext/misc/vtablog.c | 1 + ext/misc/wholenumber.c | 5 + ext/misc/zipfile.c | 4 +- ext/recover/dbdata.c | 3 +- ext/repair/checkindex.c | 2 + ext/rtree/geopoly.c | 12 +- ext/rtree/rtree.c | 357 +- ext/rtree/rtree1.test | 29 +- ext/rtree/rtree8.test | 2 +- ext/rtree/rtreeA.test | 4 +- ext/rtree/rtree_util.tcl | 4 +- ext/rtree/rtreecheck.test | 30 +- ext/rtree/rtreedoc.test | 20 +- ext/rtree/rtreefuzz001.test | 2 +- ext/session/session3.test | 8 +- ext/session/sessionalter.test | 238 + ext/session/sessionfault3.test | 59 + ext/session/sessionnoact.test | 110 + ext/session/sqlite3session.c | 729 +- ext/session/sqlite3session.h | 70 +- ext/session/test_session.c | 138 +- ext/wasm/GNUmakefile | 7 - ext/wasm/SQLTester/GNUmakefile | 55 + ext/wasm/SQLTester/SQLTester.mjs | 1339 ++++ ext/wasm/SQLTester/SQLTester.run.mjs | 148 + ext/wasm/SQLTester/index.html | 127 + ext/wasm/SQLTester/touint8array.c | 29 + ext/wasm/api/sqlite3-api-prologue.js | 75 +- ext/wasm/api/sqlite3-api-worker1.js | 3 +- ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js | 101 +- ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 77 +- ext/wasm/api/sqlite3-wasm.c | 128 +- ext/wasm/api/sqlite3-worker1.c-pp.js | 2 +- ext/wasm/tester1.c-pp.js | 46 +- main.mk | 16 +- manifest | 543 +- manifest.uuid | 2 +- src/alter.c | 7 +- src/btree.c | 22 +- src/btreeInt.h | 2 +- src/build.c | 39 +- src/ctime.c | 6 + src/date.c | 51 +- src/dbpage.c | 3 +- src/dbstat.c | 3 +- src/expr.c | 351 +- src/fkey.c | 10 +- src/func.c | 86 +- src/json.c | 23 +- src/loadext.c | 16 +- src/main.c | 150 +- src/malloc.c | 2 +- src/mutex.c | 2 +- src/mutex_unix.c | 2 +- src/notify.c | 3 + src/os_unix.c | 9 - src/pager.c | 66 +- src/parse.y | 9 + src/pragma.c | 34 +- src/prepare.c | 2 - src/printf.c | 2 +- src/resolve.c | 115 +- src/select.c | 229 +- src/shell.c.in | 283 +- src/sqlite.h.in | 125 +- src/sqlite3ext.h | 6 + src/sqliteInt.h | 69 +- src/test1.c | 57 +- src/test8.c | 11 +- src/test_bestindex.c | 5 + src/test_fs.c | 15 + src/test_intarray.c | 5 + src/test_osinst.c | 7 +- src/test_schema.c | 5 + src/test_tclvar.c | 5 + src/treeview.c | 19 +- src/trigger.c | 15 +- src/util.c | 139 +- src/vdbe.c | 110 +- src/vdbeapi.c | 180 +- src/vdbeaux.c | 32 +- src/vdbeblob.c | 2 +- src/vdbemem.c | 3 +- src/vdbesort.c | 8 +- src/vdbevtab.c | 3 +- src/vtab.c | 2 +- src/where.c | 149 +- src/whereInt.h | 2 +- src/window.c | 3 +- test/aggnested.test | 8 +- test/aggorderby.test | 122 + test/alter.test | 19 + test/altercol.test | 15 + test/altermalloc3.test | 1 + test/alterqf.test | 4 +- test/altertrig.test | 1 - test/bestindex9.test | 4 - test/bestindexA.test | 3 - test/bestindexB.test | 87 + test/changes.test | 2 - test/changes2.test | 1 - test/date.test | 24 +- test/date4.test | 38 + test/dbfuzz001.test | 23 + test/dbpagefault.test | 2 - test/distinctagg.test | 3 +- test/e_expr.test | 2 +- test/e_select.test | 2 +- test/filter2.test | 2 +- test/fts3conf.test | 6 +- test/fts3corrupt4.test | 13 +- test/fts3fault3.test | 54 + test/fts3fuzz001.test | 27 + test/fts4check.test | 10 +- test/fts4intck1.test | 58 + test/fts4langid.test | 3 +- test/fts4merge.test | 14 +- test/func.test | 14 +- test/func9.test | 40 + test/fuzzdata8.db | Bin 4237312 -> 4239360 bytes test/gcfault.test | 4 +- test/gencol1.test | 5 +- test/indexA.test | 350 + test/joinH.test | 119 + test/json101.test | 16 + test/memdb2.test | 1 - test/memjournal2.test | 2 - test/mutex1.test | 8 + test/pendingrace.test | 3 - test/quickcheck.test | 1 - test/rowvalue9.test | 2 - test/rowvalueA.test | 1 - test/scanstatus2.test | 4 +- test/select3.test | 1 - test/sessionfuzz.c | 8 - test/shell1.test | 10 +- test/statfault.test | 1 - test/subquery.test | 43 +- test/table.test | 6 +- test/testrunner.tcl | 738 +- test/testrunner_data.tcl | 90 +- test/thread3.test | 1 - test/tkt-cbd054fa6b.test | 2 +- test/trigger2.test | 1 - test/unhex.test | 4 +- test/vacuum-into.test | 3 - test/vt02.c | 11 +- test/walseh1.test | 2 - test/window1.test | 10 +- test/window3.test | 8 +- test/with3.test | 4 +- tool/cktclsh.sh | 11 + tool/fuzzershell.c | 5 + tool/mkautoconfamal.sh | 2 +- tool/mkctimec.tcl | 2 + tool/mksqlite3c.tcl | 2 +- tool/mktoolzip.tcl | 69 + tool/showdb.c | 4 + tool/showwal.c | 23 +- tool/spaceanal.tcl | 1 + tool/sqldiff.c | 6 +- 302 files changed, 23434 insertions(+), 9903 deletions(-) create mode 100644 doc/testrunner.md create mode 100644 ext/fts5/test/fts5faultG.test create mode 100644 ext/fts5/test/fts5optimize3.test create mode 100644 ext/fts5/test/fts5secure7.test delete mode 100644 ext/jni/src/org/sqlite/jni/Authorizer.java delete mode 100644 ext/jni/src/org/sqlite/jni/AutoExtension.java delete mode 100644 ext/jni/src/org/sqlite/jni/BusyHandler.java delete mode 100644 ext/jni/src/org/sqlite/jni/Fts5Function.java delete mode 100644 ext/jni/src/org/sqlite/jni/OutputPointer.java delete mode 100644 ext/jni/src/org/sqlite/jni/ResultCode.java delete mode 100644 ext/jni/src/org/sqlite/jni/SQLFunction.java delete mode 100644 ext/jni/src/org/sqlite/jni/SQLite3Jni.java delete mode 100644 ext/jni/src/org/sqlite/jni/Tester1.java delete mode 100644 ext/jni/src/org/sqlite/jni/TesterFts5.java delete mode 100644 ext/jni/src/org/sqlite/jni/Tracer.java create mode 100644 ext/jni/src/org/sqlite/jni/annotation/NotNull.java create mode 100644 ext/jni/src/org/sqlite/jni/annotation/Nullable.java create mode 100644 ext/jni/src/org/sqlite/jni/annotation/package-info.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/CApi.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/CollationCallback.java rename ext/jni/src/org/sqlite/jni/{CollationNeeded.java => capi/CollationNeededCallback.java} (64%) rename ext/jni/src/org/sqlite/jni/{UpdateHook.java => capi/CommitHookCallback.java} (60%) create mode 100644 ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java rename ext/jni/src/org/sqlite/jni/{ => capi}/NativePointerHolder.java (68%) create mode 100644 ext/jni/src/org/sqlite/jni/capi/OutputPointer.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java rename ext/jni/src/org/sqlite/jni/{Collation.java => capi/PreupdateHookCallback.java} (55%) rename ext/jni/src/org/sqlite/jni/{ProgressHandler.java => capi/ProgressHandlerCallback.java} (58%) create mode 100644 ext/jni/src/org/sqlite/jni/capi/ResultCode.java rename ext/jni/src/org/sqlite/jni/{RollbackHook.java => capi/RollbackHookCallback.java} (62%) create mode 100644 ext/jni/src/org/sqlite/jni/capi/SQLFunction.java rename ext/jni/src/org/sqlite/jni/{tester => capi}/SQLTester.java (93%) create mode 100644 ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/Tester1.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java rename ext/jni/src/org/sqlite/jni/{ => capi}/ValueHolder.java (77%) create mode 100644 ext/jni/src/org/sqlite/jni/capi/WindowFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/package-info.java rename ext/jni/src/org/sqlite/jni/{ => capi}/sqlite3.java (75%) create mode 100644 ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java create mode 100644 ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java rename ext/jni/src/org/sqlite/jni/{ => capi}/sqlite3_stmt.java (83%) rename ext/jni/src/org/sqlite/jni/{ => capi}/sqlite3_value.java (95%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/Fts5.java (67%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/Fts5Context.java (92%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/Fts5ExtensionApi.java (54%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/Fts5PhraseIter.java (90%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/Fts5Tokenizer.java (92%) create mode 100644 ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java rename ext/jni/src/org/sqlite/jni/{CommitHook.java => fts5/XTokenizeCallback.java} (64%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/fts5_api.java (80%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/fts5_extension_function.java (61%) rename ext/jni/src/org/sqlite/jni/{ => fts5}/fts5_tokenizer.java (90%) delete mode 100644 ext/jni/src/org/sqlite/jni/sqlite3_context.java rename ext/jni/src/org/sqlite/jni/{tester => }/test-script-interpreter.md (100%) create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java create mode 100644 ext/jni/src/tests/900-001-fts.test create mode 100644 ext/session/sessionalter.test create mode 100644 ext/session/sessionfault3.test create mode 100644 ext/session/sessionnoact.test create mode 100644 ext/wasm/SQLTester/GNUmakefile create mode 100644 ext/wasm/SQLTester/SQLTester.mjs create mode 100644 ext/wasm/SQLTester/SQLTester.run.mjs create mode 100644 ext/wasm/SQLTester/index.html create mode 100644 ext/wasm/SQLTester/touint8array.c mode change 100644 => 100755 src/btree.c create mode 100644 test/aggorderby.test create mode 100644 test/bestindexB.test create mode 100644 test/date4.test create mode 100644 test/fts3fault3.test create mode 100644 test/fts4intck1.test create mode 100644 test/func9.test create mode 100644 test/indexA.test create mode 100644 tool/cktclsh.sh create mode 100644 tool/mktoolzip.tcl diff --git a/Makefile.in b/Makefile.in index 9ad135b3d..c521586a1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -57,6 +57,7 @@ LIBTCL = @TCL_LIB_SPEC@ # READLINE_FLAGS = -DHAVE_READLINE=@TARGET_HAVE_READLINE@ @TARGET_READLINE_INC@ READLINE_FLAGS += -DHAVE_EDITLINE=@TARGET_HAVE_EDITLINE@ +READLINE_FLAGS += -DHAVE_LINENOISE=@TARGET_HAVE_LINENOISE@ # The library that programs using readline() must link against. # @@ -706,6 +707,9 @@ fuzzcheck$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) fuzzcheck-asan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) $(LTLINK) -o $@ -fsanitize=address $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) +fuzzcheck-ubsan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) + $(LTLINK) -o $@ -fsanitize=undefined $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) + ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \ $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) @@ -765,13 +769,22 @@ mptest: mptester$(TEXE) $(MPTEST2) --journalmode DELETE +has_tclsh84: + sh $(TOP)/tool/cktclsh.sh 8.4 $(TCLSH_CMD) + touch has_tclsh84 + +has_tclsh85: + sh $(TOP)/tool/cktclsh.sh 8.5 $(TCLSH_CMD) + touch has_tclsh85 + + # This target creates a directory named "tsrc" and fills it with # copies of all of the C source code and header files needed to # build on the target system. Some of the C source code and header # files are automatically generated. This target takes care of # all that automatic generation. # -.target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl fts5.c +.target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl has_tclsh84 fts5.c rm -rf tsrc mkdir tsrc cp -f $(SRC) tsrc @@ -781,15 +794,15 @@ mptest: mptester$(TEXE) cp fts5.c fts5.h tsrc touch .target_source -sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify +sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . -sqlite3r.h: sqlite3.h +sqlite3r.h: sqlite3.h has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h -sqlite3r.c: sqlite3.c sqlite3r.h +sqlite3r.c: sqlite3.c sqlite3r.h has_tclsh84 cp $(TOP)/ext/recover/sqlite3recover.c tsrc/ cp $(TOP)/ext/recover/sqlite3recover.h tsrc/ cp $(TOP)/ext/recover/dbdata.c tsrc/ @@ -804,7 +817,7 @@ tclsqlite3.c: sqlite3.c echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c -sqlite3-all.c: sqlite3.c $(TOP)/tool/split-sqlite3c.tcl +sqlite3-all.c: sqlite3.c $(TOP)/tool/split-sqlite3c.tcl has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/split-sqlite3c.tcl # Rule to build the amalgamation @@ -1092,10 +1105,10 @@ tclsqlite3$(TEXE): tclsqlite-shell.lo libsqlite3.la # Rules to build opcodes.c and opcodes.h # -opcodes.c: opcodes.h $(TOP)/tool/mkopcodec.tcl +opcodes.c: opcodes.h $(TOP)/tool/mkopcodec.tcl has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mkopcodec.tcl opcodes.h >opcodes.c -opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl +opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl has_tclsh84 cat parse.h $(TOP)/src/vdbe.c | $(TCLSH_CMD) $(TOP)/tool/mkopcodeh.tcl >opcodes.h # Rules to build parse.c and parse.h - the outputs of lemon. @@ -1106,10 +1119,10 @@ parse.c: $(TOP)/src/parse.y lemon$(BEXE) cp $(TOP)/src/parse.y . ./lemon$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) -S parse.y -sqlite3.h: $(TOP)/src/sqlite.h.in $(TOP)/manifest mksourceid$(BEXE) $(TOP)/VERSION +sqlite3.h: $(TOP)/src/sqlite.h.in $(TOP)/manifest mksourceid$(BEXE) $(TOP)/VERSION has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) >sqlite3.h -sqlite3rc.h: $(TOP)/src/sqlite3.rc $(TOP)/VERSION +sqlite3rc.h: $(TOP)/src/sqlite3.rc $(TOP)/VERSION has_tclsh84 echo '#ifndef SQLITE_RESOURCE_VERSION' >$@ echo -n '#define SQLITE_RESOURCE_VERSION ' >>$@ cat $(TOP)/VERSION | $(TCLSH_CMD) $(TOP)/tool/replace.tcl exact . , >>$@ @@ -1145,7 +1158,7 @@ SHELL_SRC = \ $(TOP)/ext/recover/sqlite3recover.h \ $(TOP)/src/test_windirent.c -shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl +shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c @@ -1233,7 +1246,7 @@ fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon$(BEXE) fts5parse.h: fts5parse.c -fts5.c: $(FTS5_SRC) +fts5.c: $(FTS5_SRC) has_tclsh84 $(TCLSH_CMD) $(TOP)/ext/fts5/tool/mkfts5c.tcl cp $(TOP)/ext/fts5/fts5.h . @@ -1267,7 +1280,7 @@ TESTFIXTURE_SRC1 = sqlite3.c TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION)) -testfixture$(TEXE): $(TESTFIXTURE_SRC) +testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC) $(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \ -o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS) @@ -1291,11 +1304,17 @@ fulltestonly: $(TESTPROGS) fuzztest ./testfixture$(TEXE) $(TOP)/test/full.test # Fuzz testing -fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db +# +# WARNING: When the "fuzztest" target is run by the testrunner.tcl script, +# it does not actually run this code. Instead, it schedules equivalent +# commands. Therefore, if this target is updated, then code in +# testrunner_data.tcl (search for "trd_fuzztest_data") must also be updated. +# +fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) ./fuzzcheck$(TEXE) $(FUZZDATA) ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db -valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db +valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M $(FUZZDATA) valgrind ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db @@ -1314,10 +1333,10 @@ testrunner: testfixture$(TEXE) # devtest: srctree-check testfixture$(TEXE) fuzztest testrunner -mdevtest: srctree-check +mdevtest: srctree-check has_tclsh85 $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest -sdevtest: +sdevtest: has_tclsh85 $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest # Validate that various generated files in the source tree @@ -1357,13 +1376,13 @@ smoketest: $(TESTPROGS) fuzzcheck$(TEXE) shelltest: $(TESTPROGS) ./testfixture$(TEXT) $(TOP)/test/permutations.test shell -sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in +sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in has_tclsh85 $(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in >sqlite3_analyzer.c sqlite3_analyzer$(TEXE): sqlite3_analyzer.c $(LTLINK) sqlite3_analyzer.c -o $@ $(LIBTCL) $(TLIBS) -sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in +sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in has_tclsh85 $(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c sqltclsh$(TEXE): sqltclsh.c @@ -1382,7 +1401,7 @@ CHECKER_DEPS =\ $(TOP)/ext/misc/btreeinfo.c \ $(TOP)/ext/repair/sqlite3_checker.c.in -sqlite3_checker.c: $(CHECKER_DEPS) +sqlite3_checker.c: $(CHECKER_DEPS) has_tclsh85 $(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/ext/repair/sqlite3_checker.c.in >$@ sqlite3_checker$(TEXE): sqlite3_checker.c @@ -1468,6 +1487,11 @@ amalgamation-tarball: sqlite3.c sqlite3rc.h snapshot-tarball: sqlite3.c sqlite3rc.h TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --snapshot +# Build a ZIP archive containing various command-line tools. +# +tool-zip: testfixture sqlite3 sqldiff sqlite3_analyzer $(TOP)/tool/mktoolzip.tcl + ./testfixture $(TOP)/tool/mktoolzip.tcl + # 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. @@ -1543,6 +1567,7 @@ clean: rm -f threadtest5 rm -f src-verify rm -f custom.rws + rm -f has_tclsh84 has_tclsh85 distclean: clean rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \ @@ -1587,7 +1612,7 @@ fiddle: sqlite3.c shell.c @echo 'Updating custom dictionary from tool/custom.txt' aspell --lang=en create master ./custom.rws < $< -misspell: ./custom.rws +misspell: ./custom.rws has_tclsh84 $(TCLSH_CMD) ./tool/spellsift.tcl ./src/*.c ./src/*.h ./src/*.in # diff --git a/Makefile.msc b/Makefile.msc index 3179e301c..4339be891 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -52,8 +52,8 @@ MINIMAL_AMALGAMATION = 0 USE_STDCALL = 0 !ENDIF -# Set this non-0 to use structured exception handling (SEH) for WAL mode -# in the core library. +# Use the USE_SEH=0 option on the nmake command line to omit structured +# exception handling (SEH) support. SEH is on by default. # !IFNDEF USE_SEH USE_SEH = 1 @@ -374,6 +374,7 @@ SQLITE_TCL_DEP = !IFNDEF OPT_FEATURE_FLAGS !IF $(MINIMAL_AMALGAMATION)==0 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_GEOPOLY=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1 @@ -403,10 +404,11 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1 !ENDIF # Should structured exception handling (SEH) be enabled for WAL mode in -# the core library? +# the core library? It is on by default. Only omit it if the +# USE_SEH=0 option is provided on the nmake command-line. # -!IF $(USE_SEH)!=0 -OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1 +!IF $(USE_SEH)==0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_OMIT_SEH=1 !ENDIF # These are the "extended" SQLite compilation options used when compiling for @@ -1592,8 +1594,7 @@ TESTEXT = \ $(TOP)\ext\rtree\test_rtreedoc.c \ $(TOP)\ext\recover\sqlite3recover.c \ $(TOP)\ext\recover\test_recover.c \ - $(TOP)\ext\recover\dbdata.c \ - fts5.c + $(TOP)\ext\recover\dbdata.c # If use of zlib is enabled, add the "zipfile.c" source file. # @@ -1609,7 +1610,8 @@ TESTSRC2 = \ $(SRC01) \ $(SRC07) \ $(SRC10) \ - $(TOP)\ext\async\sqlite3async.c + $(TOP)\ext\async\sqlite3async.c \ + fts5.c # Header files used by all library source files. # @@ -1688,6 +1690,8 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_DQS=0 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1 !ENDIF # <> @@ -1819,8 +1823,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT /link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) # <> -sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H) - $(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) +sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) + $(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) @@ -2466,6 +2470,9 @@ extensiontest: testfixture.exe testloadext.dll @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS) +tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe $(TOP)\tool\mktoolzip.tcl + .\testfixture.exe $(TOP)\tool\mktoolzip.tcl + coretestprogs: $(TESTPROGS) testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe diff --git a/VERSION b/VERSION index a14006064..faf0dcbb0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.43.2 +3.44.0 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 13663d877..280bb95de 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -52,8 +52,8 @@ MINIMAL_AMALGAMATION = 0 USE_STDCALL = 0 !ENDIF -# Set this non-0 to use structured exception handling (SEH) for WAL mode -# in the core library. +# Use the USE_SEH=0 option on the nmake command line to omit structured +# exception handling (SEH) support. SEH is on by default. # !IFNDEF USE_SEH USE_SEH = 1 @@ -296,6 +296,7 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb !IFNDEF OPT_FEATURE_FLAGS !IF $(MINIMAL_AMALGAMATION)==0 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_GEOPOLY=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1 @@ -325,10 +326,11 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1 !ENDIF # Should structured exception handling (SEH) be enabled for WAL mode in -# the core library? +# the core library? It is on by default. Only omit it if the +# USE_SEH=0 option is provided on the nmake command-line. # -!IF $(USE_SEH)!=0 -OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1 +!IF $(USE_SEH)==0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_OMIT_SEH=1 !ENDIF # These are the "extended" SQLite compilation options used when compiling for @@ -986,6 +988,8 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_DQS=0 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1 !ENDIF diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index 032d20a4c..e34bda9f2 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.43.2]) +AC_INIT([sqlite],[3.44.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 90046959f..7b31f6f7e 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.69 for sqlite 3.43.2. +# Generated by GNU Autoconf 2.69 for sqlite 3.44.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.43.2' -PACKAGE_STRING='sqlite 3.43.2' +PACKAGE_VERSION='3.44.0' +PACKAGE_STRING='sqlite 3.44.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -776,6 +776,7 @@ OPT_FEATURE_FLAGS HAVE_ZLIB USE_AMALGAMATION TARGET_DEBUG +TARGET_HAVE_LINENOISE TARGET_HAVE_EDITLINE TARGET_HAVE_READLINE TARGET_READLINE_INC @@ -903,6 +904,7 @@ enable_editline enable_readline with_readline_lib with_readline_inc +with_linenoise enable_debug enable_amalgamation enable_load_extension @@ -1470,7 +1472,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.43.2 to adapt to many kinds of systems. +\`configure' configures sqlite 3.44.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1535,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.43.2:";; + short | recursive ) echo "Configuration of sqlite 3.44.0:";; esac cat <<\_ACEOF @@ -1587,6 +1589,7 @@ Optional Packages: (tclConfig.sh) --with-readline-lib specify readline library --with-readline-inc specify readline include paths + --with-linenoise=DIR source directory for linenoise library Some influential environment variables: CC C compiler command @@ -1665,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.43.2 +sqlite configure 3.44.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2084,7 +2087,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.43.2, which was +It was created by sqlite $as_me 3.44.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3942,13 +3945,13 @@ if ${lt_cv_nm_interface+:} false; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3945: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3948: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3948: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3951: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3951: output\"" >&5) + (eval echo "\"\$as_me:3954: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -5154,7 +5157,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5157 "configure"' > conftest.$ac_ext + echo '#line 5160 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6679,11 +6682,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:6682: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6685: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6686: \$? = $ac_status" >&5 + echo "$as_me:6689: \$? = $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. @@ -7018,11 +7021,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:7021: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7024: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7025: \$? = $ac_status" >&5 + echo "$as_me:7028: \$? = $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. @@ -7123,11 +7126,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:7126: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7129: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7130: \$? = $ac_status" >&5 + echo "$as_me:7133: \$? = $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 @@ -7178,11 +7181,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:7181: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7184: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7185: \$? = $ac_status" >&5 + echo "$as_me:7188: \$? = $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 @@ -9558,7 +9561,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9561 "configure" +#line 9564 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -9654,7 +9657,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9657 "configure" +#line 9660 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -11245,6 +11248,27 @@ fi fi fi +# Check whether --with-linenoise was given. +if test "${with_linenoise+set}" = set; then : + withval=$with_linenoise; with_linenoise=$withval +else + with_linenoise="no" +fi + +if test "x$with_linenoise" != "xno"; then + TARGET_HAVE_READLINE=0 + TARGET_HAVE_EDITLINE=0 + TARGET_HAVE_LINENOISE=1 + TARGET_READLINE_INC="-I${with_linenoise}" + TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c" + echo "using linenoise source code at ${with_linenoise}" +else + TARGET_HAVE_LINENOISE=0 + echo "not using linenoise" +fi + + + @@ -11321,7 +11345,7 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking build type" >&5 $as_echo_n "checking build type... " >&6; } if test "${enable_debug}" = "yes" ; then - TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0" + TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall" { $as_echo "$as_me:${as_lineno-$LINENO}: result: debug" >&5 $as_echo "debug" >&6; } else @@ -12457,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.43.2, which was +This file was extended by sqlite $as_me 3.44.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12523,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.43.2 +sqlite config.status 3.44.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 53be0a686..837d2fb01 100644 --- a/configure.ac +++ b/configure.ac @@ -598,11 +598,28 @@ if test x"$with_readline" != xno; then TARGET_HAVE_READLINE=1 fi fi +AC_ARG_WITH([linenoise], + [AS_HELP_STRING([--with-linenoise=DIR],[source directory for linenoise library])], + [with_linenoise=$withval], + [with_linenoise="no"]) +if test "x$with_linenoise" != "xno"; then + TARGET_HAVE_READLINE=0 + TARGET_HAVE_EDITLINE=0 + TARGET_HAVE_LINENOISE=1 + TARGET_READLINE_INC="-I${with_linenoise}" + TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c" + echo "using linenoise source code at ${with_linenoise}" +else + TARGET_HAVE_LINENOISE=0 + echo "not using linenoise" +fi AC_SUBST(TARGET_READLINE_LIBS) AC_SUBST(TARGET_READLINE_INC) AC_SUBST(TARGET_HAVE_READLINE) AC_SUBST(TARGET_HAVE_EDITLINE) +AC_SUBST(TARGET_HAVE_LINENOISE) + ########## # Figure out what C libraries are required to compile programs @@ -615,7 +632,7 @@ AC_SEARCH_LIBS(fdatasync, [rt]) AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[enable debugging & verbose explain])) AC_MSG_CHECKING([build type]) if test "${enable_debug}" = "yes" ; then - TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0" + TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall" AC_MSG_RESULT([debug]) else TARGET_DEBUG="-DNDEBUG" diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md index 0bd39d21f..20f79afa9 100644 --- a/doc/compile-for-windows.md +++ b/doc/compile-for-windows.md @@ -16,7 +16,7 @@ canonical source on a new Windows 11 PC, as of 2023-08-16: a 32-bit build.) The subsequent steps will not work in a vanilla DOS prompt. Nor will they work in PowerShell. - 3. Install TCL development libraries. This note assumes that you wil + 3. Install TCL development libraries. This note assumes that you will install the TCL development libraries in the "`c:\Tcl`" directory. Make adjustments if you want TCL installed somewhere else. SQLite needs both the @@ -83,3 +83,54 @@ following minor changes:

        • `set PATH=c:\tcl32\bin;%PATH%`
        + +## Statically Linking The TCL Library + +Some utility programs associated with SQLite need to be linked +with TCL in order to function. The [sqlite3_analyzer.exe program](https://sqlite.org/sqlanalyze.html) +is an example. You can build as described above, and then +enter: + +> ~~~~ +nmake /f Makefile.msc sqlite3_analyzer.exe +~~~~ + +And you will end up with a working executable. However, that executable +will depend on having the "tcl86.dll" library somewhere on your %PATH%. +Use the following steps to build an executable that has the TCL library +statically linked so that it does not depend on separate DLL: + + 1. Use the appropriate "Command Prompt" window - either x86 or + x64, depending on whether you want a 32-bit or 64-bit executable. + + 2. Untar the TCL source tarball into a fresh directory. CD into + the "win/" subfolder. + + 3. Run: `nmake /f makefile.vc OPTS=nothreads,static shell` + + + 4. CD into the "Release*" subfolder that is created (note the + wildcard - the full name of the directory might vary). There + you will find the "tcl86s.lib" file. Copy this file into the + same directory that you put the "tcl86.lib" on your initial + installation. (In this document, that directory is + "`C:\Tcl32\lib`" for 32-bit builds and + "`C:\Tcl\lib`" for 64-bit builds.) + + 5. CD into your SQLite source code directory and build the desired + utility program, but add the following extra arguments to the + nmake command line: +
        +      CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib"
        +      
        +

        So, for example, to build a statically linked version of + sqlite3_analyzer.exe, you might type: +

        +      nmake /f Makefile.msc CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib" sqlite3_analyzer.exe
        +      
        + + 6. After your executable is built, you can verify that it does not + depend on the TCL DLL by running: +
        +      dumpbin /dependents sqlite3_analyzer.exe
        +      
        diff --git a/doc/testrunner.md b/doc/testrunner.md new file mode 100644 index 000000000..d828fd76d --- /dev/null +++ b/doc/testrunner.md @@ -0,0 +1,284 @@ + + +# The testrunner.tcl Script + +# 1. Overview + +testrunner.tcl is a Tcl script used to run multiple SQLite tests using +multiple jobs. It supports the following types of tests: + + * Tcl test scripts. + + * Tests run with [make] commands. Specifically, at time of writing, + [make fuzztest], [make mptest], [make sourcetest] and [make threadtest]. + +testrunner.tcl pipes the output of all tests and builds run into log file +**testrunner.log**, created in the cwd directory. Searching this file for +"failed" is a good way to find the output of a failed test. + +testrunner.tcl also populates SQLite database **testrunner.db**. This database +contains details of all tests run, running and to be run. A useful query +might be: + +``` + SELECT * FROM script WHERE state='failed' +``` + +Running the command: + +``` + ./testfixture $(TESTDIR)/testrunner.tcl status +``` + +in the directory containing the testrunner.db database runs various queries +to produce a succinct report on the state of a running testrunner.tcl script. +Running: + +``` + watch ./testfixture $(TESTDIR)/testrunner.tcl status +``` + +in another terminal is a good way to keep an eye on a long running test. + +Sometimes testrunner.tcl uses the [testfixture] binary that it is run with +to run tests (see "Binary Tests" below). Sometimes it builds testfixture and +other binaries in specific configurations to test (see "Source Tests"). + +# 2. Binary Tests + +The commands described in this section all run various combinations of the Tcl +test scripts using the [testfixture] binary used to run the testrunner.tcl +script (i.e. they do not invoke the compiler to build new binaries, or the +[make] command to run tests that are not Tcl scripts). The procedure to run +these tests is therefore: + + 1. Build the "testfixture" (or "testfixture.exe" for windows) binary using + whatever method seems convenient. + + 2. Test the binary built in step 1 by running testrunner.tcl with it, + perhaps with various options. + +The following sub-sections describe the various options that can be +passed to testrunner.tcl to test binary testfixture builds. + +## 2.1. Organization of Tcl Tests + +Tcl tests are stored in files that match the pattern *\*.test*. They are +found in both the $TOP/test/ directory, and in the various sub-directories +of the $TOP/ext/ directory of the source tree. Not all *\*.test* files +contain Tcl tests - a handful are Tcl scripts designed to invoke other +*\*.test* files. + +The **veryquick** set of tests is a subset of all Tcl test scripts in the +source tree. In includes most tests, but excludes some that are very slow. +Almost all fault-injection tests (those that test the response of the library +to OOM or IO errors) are excluded. It is defined in source file +*test/permutations.test*. + +The **full** set of tests includes all Tcl test scripts in the source tree. +To run a "full" test is to run all Tcl test scripts that can be found in the +source tree. + +File *permutations.test* defines various test "permutations". A permutation +consists of: + + * A subset of Tcl test scripts, and + + * Runtime configuration to apply before running each test script + (e.g. enabling auto-vacuum, or disable lookaside). + +Running **all** tests is to run all tests in the full test set, plus a dozen +or so permutations. The specific permutations that are run as part of "all" +are defined in file *testrunner_data.tcl*. + +## 2.2. Commands to Run Tests + +To run the "veryquick" test set, use either of the following: + +``` + ./testfixture $TESTDIR/testrunner.tcl + ./testfixture $TESTDIR/testrunner.tcl veryquick +``` + +To run the "full" test suite: + +``` + ./testfixture $TESTDIR/testrunner.tcl full +``` + +To run the subset of the "full" test suite for which the test file name matches +a specified pattern (e.g. all tests that start with "fts5"), either of: + +``` + ./testfixture $TESTDIR/testrunner.tcl fts5% + ./testfixture $TESTDIR/testrunner.tcl 'fts5*' +``` + +To run "all" tests (full + permutations): + +``` + ./testfixture $TESTDIR/testrunner.tcl all +``` + + +## 2.3. Investigating Binary Test Failures + +If a test fails, testrunner.tcl reports name of the Tcl test script and, if +applicable, the name of the permutation, to stdout. This information can also +be retrieved from either *testrunner.log* or *testrunner.db*. + +If there is no permutation, the individual test script may be run with: + +``` + ./testfixture $PATH_TO_SCRIPT +``` + +Or, if the failure occured as part of a permutation: + +``` + ./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT +``` + +TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT? + +# 3. Source Code Tests + +The commands described in this section invoke the C compiler to build +binaries from the source tree, then use those binaries to run Tcl and +other tests. The advantages of this are that: + + * it is possible to test multiple build configurations with a single + command, and + + * it ensures that tests are always run using binaries created with the + same set of compiler options. + +The testrunner.tcl commands described in this section may be run using +either a *testfixture* (or testfixture.exe) build, or with any other Tcl +shell that supports SQLite 3.31.1 or newer via "package require sqlite3". + +TODO: ./configure + Makefile.msc build systems. + +## Commands to Run SQLite Tests + +The **mdevtest** command is equivalent to running the veryquick tests and +the [make fuzztest] target once for each of two --enable-all builds - one +with debugging enabled and one without: + +``` + tclsh $TESTDIR/testrunner.tcl mdevtest +``` + +In other words, it is equivalent to running: + +``` + $TOP/configure --enable-all --enable-debug + make fuzztest + make testfixture + ./testfixture $TOP/test/testrunner.tcl veryquick + + # Then, after removing files created by the tests above: + $TOP/configure --enable-all OPTS="-O0" + make fuzztest + make testfixture + ./testfixture $TOP/test/testrunner.tcl veryquick +``` + +The **sdevtest** command is identical to the mdevtest command, except that the +second of the two builds is a sanitizer build. Specifically, this means that +OPTS="-fsanitize=address,undefined" is specified instead of OPTS="-O0": + +``` + tclsh $TESTDIR/testrunner.tcl sdevtest +``` + +The **release** command runs lots of tests under lots of builds. It runs +different combinations of builds and tests depending on whether it is run +on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details +of the specific tests run. + +``` + tclsh $TESTDIR/testrunner.tcl release +``` + +## Running ZipVFS Tests + +testrunner.tcl can build a zipvfs-enabled testfixture and use it to run +tests from the Zipvfs project with the following command: + +``` + tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS +``` + +This can be combined with any of "mdevtest", "sdevtest" or "release" to +test both SQLite and Zipvfs with a single command: + +``` + tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest +``` + +## Investigating Source Code Test Failures + +Investigating a test failure that occurs during source code testing is a +two step process: + + 1. Recreating the build configuration in which the test failed, and + + 2. Re-running the actual test. + +To recreate a build configuration, use the testrunner.tcl **script** command +to create a build script. A build script is a bash script on Linux or OSX, or +a dos \*.bat file on windows. For example: + +``` + # Create a script that recreates build configuration "Device-One" on + # Linux or OSX: + tclsh $TESTDIR/testrunner.tcl script Device-One > make.sh + + # Create a script that recreates build configuration "Have-Not" on Windows: + tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat +``` + +The generated bash or \*.bat file script accepts a single argument - a makefile +target to build. This may be used either to run a [make] command test directly, +or else to build a testfixture (or testfixture.exe) binary with which to +run a Tcl test script, as described above. + + + +# 4. Controlling CPU Core Utilization + +When running either binary or source code tests, testrunner.tcl reports the +number of jobs it intends to use to stdout. e.g. + +``` + $ ./testfixture $TESTDIR/testrunner.tcl + splitting work across 16 jobs + ... more output ... +``` + +By default, testfixture.tcl attempts to set the number of jobs to the number +of real cores on the machine. This can be overridden using the "--jobs" (or -j) +switch: + +``` + $ ./testfixture $TESTDIR/testrunner.tcl --jobs 8 + splitting work across 8 jobs + ... more output ... +``` + +The number of jobs may also be changed while an instance of testrunner.tcl is +running by exucuting the following command from the directory containing the +testrunner.log and testrunner.db files: + +``` + $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS +``` + + + + + + + + diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index dee4eb9ec..453334234 100644 --- a/ext/expert/expert1.test +++ b/ext/expert/expert1.test @@ -464,4 +464,23 @@ do_execsql_test 5.3 { t2 t2_idx_0001295b {100 20 5} } +if 0 { +do_test expert1-6.0 { + catchcmd :memory: { +.expert +select base64(''); +.expert +select name from pragma_collation_list order by name collate uint; +} +} {0 {(no new indexes) + +SCAN CONSTANT ROW + +(no new indexes) + +SCAN pragma_collation_list VIRTUAL TABLE INDEX 0: +USE TEMP B-TREE FOR ORDER BY +}} +} + finish_test diff --git a/ext/expert/sqlite3expert.c b/ext/expert/sqlite3expert.c index c01feff58..33d62226f 100644 --- a/ext/expert/sqlite3expert.c +++ b/ext/expert/sqlite3expert.c @@ -32,7 +32,7 @@ #endif /* !defined(SQLITE_AMALGAMATION) */ -#ifndef SQLITE_OMIT_VIRTUALTABLE +#ifndef SQLITE_OMIT_VIRTUALTABLE typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; @@ -662,6 +662,7 @@ static int idxRegisterVtab(sqlite3expert *p){ 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ + 0, /* xIntegrity */ }; return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p); @@ -1818,6 +1819,88 @@ static int idxPopulateStat1(sqlite3expert *p, char **pzErr){ return rc; } +/* +** Define and possibly pretend to use a useless collation sequence. +** This pretense allows expert to accept SQL using custom collations. +*/ +int dummyCompare(void *up1, int up2, const void *up3, int up4, const void *up5){ + (void)up1; + (void)up2; + (void)up3; + (void)up4; + (void)up5; + assert(0); /* VDBE should never be run. */ + return 0; +} +/* And a callback to register above upon actual need */ +void useDummyCS(void *up1, sqlite3 *db, int etr, const char *zName){ + (void)up1; + sqlite3_create_collation_v2(db, zName, etr, 0, dummyCompare, 0); +} + +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \ + && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) +/* +** dummy functions for no-op implementation of UDFs during expert's work +*/ +void dummyUDF(sqlite3_context *up1, int up2, sqlite3_value **up3){ + (void)up1; + (void)up2; + (void)up3; + assert(0); /* VDBE should never be run. */ +} +void dummyUDFvalue(sqlite3_context *up1){ + (void)up1; + assert(0); /* VDBE should never be run. */ +} + +/* +** Register UDFs from user database with another. +*/ +int registerUDFs(sqlite3 *dbSrc, sqlite3 *dbDst){ + sqlite3_stmt *pStmt; + int rc = sqlite3_prepare_v2(dbSrc, + "SELECT name,type,enc,narg,flags " + "FROM pragma_function_list() " + "WHERE builtin==0", -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ + int nargs = sqlite3_column_int(pStmt,3); + int flags = sqlite3_column_int(pStmt,4); + const char *name = (char*)sqlite3_column_text(pStmt,0); + const char *type = (char*)sqlite3_column_text(pStmt,1); + const char *enc = (char*)sqlite3_column_text(pStmt,2); + if( name==0 || type==0 || enc==0 ){ + /* no-op. Only happens on OOM */ + }else{ + int ienc = SQLITE_UTF8; + int rcf = SQLITE_ERROR; + if( strcmp(enc,"utf16le")==0 ) ienc = SQLITE_UTF16LE; + else if( strcmp(enc,"utf16be")==0 ) ienc = SQLITE_UTF16BE; + ienc |= (flags & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY)); + if( strcmp(type,"w")==0 ){ + rcf = sqlite3_create_window_function(dbDst,name,nargs,ienc,0, + dummyUDF,dummyUDFvalue,0,0,0); + }else if( strcmp(type,"a")==0 ){ + rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0, + 0,dummyUDF,dummyUDFvalue); + }else if( strcmp(type,"s")==0 ){ + rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0, + dummyUDF,0,0); + } + if( rcf!=SQLITE_OK ){ + rc = rcf; + break; + } + } + } + sqlite3_finalize(pStmt); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } + return rc; +} +#endif + /* ** Allocate a new sqlite3expert object. */ @@ -1844,7 +1927,21 @@ sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){ sqlite3_db_config(pNew->dbm, SQLITE_DBCONFIG_TRIGGER_EQP, 1, (int*)0); } } - + + /* Allow custom collations to be dealt with through prepare. */ + if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbm,0,useDummyCS); + if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbv,0,useDummyCS); + +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \ + && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) + /* Register UDFs from database [db] with [dbm] and [dbv]. */ + if( rc==SQLITE_OK ){ + rc = registerUDFs(pNew->db, pNew->dbm); + } + if( rc==SQLITE_OK ){ + rc = registerUDFs(pNew->db, pNew->dbv); + } +#endif /* Copy the entire schema of database [db] into [dbm]. */ if( rc==SQLITE_OK ){ @@ -1920,6 +2017,10 @@ int sqlite3_expert_sql( while( rc==SQLITE_OK && zStmt && zStmt[0] ){ sqlite3_stmt *pStmt = 0; + /* Ensure that the provided statement compiles against user's DB. */ + rc = idxPrepareStmt(p->db, &pStmt, pzErr, zStmt); + if( rc!=SQLITE_OK ) break; + sqlite3_finalize(pStmt); rc = sqlite3_prepare_v2(p->dbv, zStmt, -1, &pStmt, &zStmt); if( rc==SQLITE_OK ){ if( pStmt ){ diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 43a9daf60..65852804a 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -640,6 +640,7 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){ zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid"); sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + sqlite3_vtab_config(p->db, SQLITE_VTAB_INNOCUOUS); /* Create a list of user columns for the virtual table */ zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]); @@ -3889,6 +3890,8 @@ static int fts3RenameMethod( rc = sqlite3Fts3PendingTermsFlush(p); } + p->bIgnoreSavepoint = 1; + if( p->zContentTbl==0 ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", @@ -3916,6 +3919,8 @@ static int fts3RenameMethod( "ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';", p->zDb, p->zName, zName ); + + p->bIgnoreSavepoint = 0; return rc; } @@ -3926,12 +3931,28 @@ static int fts3RenameMethod( */ static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ int rc = SQLITE_OK; - UNUSED_PARAMETER(iSavepoint); - assert( ((Fts3Table *)pVtab)->inTransaction ); - assert( ((Fts3Table *)pVtab)->mxSavepoint <= iSavepoint ); - TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint ); - if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){ - rc = fts3SyncMethod(pVtab); + Fts3Table *pTab = (Fts3Table*)pVtab; + assert( pTab->inTransaction ); + assert( pTab->mxSavepoint<=iSavepoint ); + TESTONLY( pTab->mxSavepoint = iSavepoint ); + + if( pTab->bIgnoreSavepoint==0 ){ + if( fts3HashCount(&pTab->aIndex[0].hPending)>0 ){ + char *zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')", + pTab->zDb, pTab->zName, pTab->zName + ); + if( zSql ){ + pTab->bIgnoreSavepoint = 1; + rc = sqlite3_exec(pTab->db, zSql, 0, 0, 0); + pTab->bIgnoreSavepoint = 0; + sqlite3_free(zSql); + }else{ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + pTab->iSavepoint = iSavepoint+1; + } } return rc; } @@ -3942,12 +3963,11 @@ static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ ** This is a no-op. */ static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ - TESTONLY( Fts3Table *p = (Fts3Table*)pVtab ); - UNUSED_PARAMETER(iSavepoint); - UNUSED_PARAMETER(pVtab); - assert( p->inTransaction ); - assert( p->mxSavepoint >= iSavepoint ); - TESTONLY( p->mxSavepoint = iSavepoint-1 ); + Fts3Table *pTab = (Fts3Table*)pVtab; + assert( pTab->inTransaction ); + assert( pTab->mxSavepoint >= iSavepoint ); + TESTONLY( pTab->mxSavepoint = iSavepoint-1 ); + pTab->iSavepoint = iSavepoint; return SQLITE_OK; } @@ -3957,11 +3977,13 @@ static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ ** Discard the contents of the pending terms table. */ static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ - Fts3Table *p = (Fts3Table*)pVtab; + Fts3Table *pTab = (Fts3Table*)pVtab; UNUSED_PARAMETER(iSavepoint); - assert( p->inTransaction ); - TESTONLY( p->mxSavepoint = iSavepoint ); - sqlite3Fts3PendingTermsClear(p); + assert( pTab->inTransaction ); + TESTONLY( pTab->mxSavepoint = iSavepoint ); + if( (iSavepoint+1)<=pTab->iSavepoint ){ + sqlite3Fts3PendingTermsClear(pTab); + } return SQLITE_OK; } @@ -3980,8 +4002,49 @@ static int fts3ShadowName(const char *zName){ return 0; } +/* +** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual +** table. +*/ +static int fts3Integrity( + sqlite3_vtab *pVtab, /* The virtual table to be checked */ + const char *zSchema, /* Name of schema in which pVtab lives */ + const char *zTabname, /* Name of the pVTab table */ + int isQuick, /* True if this is a quick_check */ + char **pzErr /* Write error message here */ +){ + Fts3Table *p = (Fts3Table*)pVtab; + char *zSql; + int rc; + char *zErr = 0; + + assert( pzErr!=0 ); + assert( *pzErr==0 ); + UNUSED_PARAMETER(isQuick); + zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", + zSchema, zTabname, zTabname); + if( zSql==0 ){ + return SQLITE_NOMEM; + } + rc = sqlite3_exec(p->db, zSql, 0, 0, &zErr); + sqlite3_free(zSql); + if( (rc&0xff)==SQLITE_CORRUPT ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s", + p->bFts4 ? 4 : 3, zSchema, zTabname); + }else if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("unable to validate the inverted index for" + " FTS%d table %s.%s: %s", + p->bFts4 ? 4 : 3, zSchema, zTabname, zErr); + } + sqlite3_free(zErr); + return SQLITE_OK; +} + + + static const sqlite3_module fts3Module = { - /* iVersion */ 3, + /* iVersion */ 4, /* xCreate */ fts3CreateMethod, /* xConnect */ fts3ConnectMethod, /* xBestIndex */ fts3BestIndexMethod, @@ -4005,6 +4068,7 @@ static const sqlite3_module fts3Module = { /* xRelease */ fts3ReleaseMethod, /* xRollbackTo */ fts3RollbackToMethod, /* xShadowName */ fts3ShadowName, + /* xIntegrity */ fts3Integrity, }; /* diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index 3a8a884f9..5a5b123b4 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -265,6 +265,7 @@ struct Fts3Table { int nPgsz; /* Page size for host database */ char *zSegmentsTbl; /* Name of %_segments table */ sqlite3_blob *pSegments; /* Blob handle open on %_segments table */ + int iSavepoint; /* ** The following array of hash tables is used to buffer pending index diff --git a/ext/fts3/fts3_aux.c b/ext/fts3/fts3_aux.c index d3b194c94..439d57936 100644 --- a/ext/fts3/fts3_aux.c +++ b/ext/fts3/fts3_aux.c @@ -545,7 +545,8 @@ int sqlite3Fts3InitAux(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc; /* Return code */ diff --git a/ext/fts3/fts3_term.c b/ext/fts3/fts3_term.c index 47e244e22..f3a9746a0 100644 --- a/ext/fts3/fts3_term.c +++ b/ext/fts3/fts3_term.c @@ -362,7 +362,8 @@ int sqlite3Fts3InitTerm(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc; /* Return code */ diff --git a/ext/fts3/fts3_tokenize_vtab.c b/ext/fts3/fts3_tokenize_vtab.c index 65d7eef4c..7e8d09bd4 100644 --- a/ext/fts3/fts3_tokenize_vtab.c +++ b/ext/fts3/fts3_tokenize_vtab.c @@ -445,7 +445,8 @@ int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash, void(*xDestroy)(void*)){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc; /* Return code */ diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index de07c52c1..0f894943d 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -3325,7 +3325,6 @@ int sqlite3Fts3PendingTermsFlush(Fts3Table *p){ rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING); if( rc==SQLITE_DONE ) rc = SQLITE_OK; } - sqlite3Fts3PendingTermsClear(p); /* Determine the auto-incr-merge setting if unknown. If enabled, ** estimate the number of leaf blocks of content to be written @@ -3347,6 +3346,10 @@ int sqlite3Fts3PendingTermsFlush(Fts3Table *p){ rc = sqlite3_reset(pStmt); } } + + if( rc==SQLITE_OK ){ + sqlite3Fts3PendingTermsClear(p); + } return rc; } @@ -3978,6 +3981,8 @@ static int fts3AppendToNode( blobGrowBuffer(pPrev, nTerm, &rc); if( rc!=SQLITE_OK ) return rc; + assert( pPrev!=0 ); + assert( pPrev->a!=0 ); nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm); nSuffix = nTerm - nPrefix; @@ -4034,9 +4039,13 @@ static int fts3IncrmergeAppend( nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; /* If the current block is not empty, and if adding this term/doclist - ** to the current block would make it larger than Fts3Table.nNodeSize - ** bytes, write this block out to the database. */ - if( pLeaf->block.n>0 && (pLeaf->block.n + nSpace)>p->nNodeSize ){ + ** to the current block would make it larger than Fts3Table.nNodeSize bytes, + ** and if there is still room for another leaf page, write this block out to + ** the database. */ + if( pLeaf->block.n>0 + && (pLeaf->block.n + nSpace)>p->nNodeSize + && pLeaf->iBlock < (pWriter->iStart + pWriter->nLeafEst) + ){ rc = fts3WriteSegment(p, pLeaf->iBlock, pLeaf->block.a, pLeaf->block.n); pWriter->nWork++; @@ -4368,7 +4377,7 @@ static int fts3IncrmergeLoad( rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0); blobGrowBuffer(&pNode->block, MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc - ); + ); if( rc==SQLITE_OK ){ memcpy(pNode->block.a, aBlock, nBlock); pNode->block.n = nBlock; @@ -5433,8 +5442,11 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ rc = fts3DoIncrmerge(p, &zVal[6]); }else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){ rc = fts3DoAutoincrmerge(p, &zVal[10]); + }else if( nVal==5 && 0==sqlite3_strnicmp(zVal, "flush", 5) ){ + rc = sqlite3Fts3PendingTermsFlush(p); + } #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) - }else{ + else{ int v; if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){ v = atoi(&zVal[9]); @@ -5452,8 +5464,8 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ if( v>=4 && v<=FTS3_MERGE_COUNT && (v&1)==0 ) p->nMergeCount = v; rc = SQLITE_OK; } -#endif } +#endif return rc; } diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c index b178f4733..fa58b9aac 100644 --- a/ext/fts5/fts5_aux.c +++ b/ext/fts5/fts5_aux.c @@ -110,15 +110,19 @@ static int fts5CInstIterInit( */ typedef struct HighlightContext HighlightContext; struct HighlightContext { - CInstIter iter; /* Coalesced Instance Iterator */ - int iPos; /* Current token offset in zIn[] */ + /* Constant parameters to fts5HighlightCb() */ int iRangeStart; /* First token to include */ int iRangeEnd; /* If non-zero, last token to include */ const char *zOpen; /* Opening highlight */ const char *zClose; /* Closing highlight */ const char *zIn; /* Input text */ int nIn; /* Size of input text in bytes */ - int iOff; /* Current offset within zIn[] */ + + /* Variables modified by fts5HighlightCb() */ + CInstIter iter; /* Coalesced Instance Iterator */ + int iPos; /* Current token offset in zIn[] */ + int iOff; /* Have copied up to this offset in zIn[] */ + int bOpen; /* True if highlight is open */ char *zOut; /* Output value */ }; @@ -151,8 +155,8 @@ static int fts5HighlightCb( int tflags, /* Mask of FTS5_TOKEN_* flags */ const char *pToken, /* Buffer containing token */ int nToken, /* Size of token in bytes */ - int iStartOff, /* Start offset of token */ - int iEndOff /* End offset of token */ + int iStartOff, /* Start byte offset of token */ + int iEndOff /* End byte offset of token */ ){ HighlightContext *p = (HighlightContext*)pContext; int rc = SQLITE_OK; @@ -168,30 +172,47 @@ static int fts5HighlightCb( if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff; } - if( iPos==p->iter.iStart ){ + /* If the parenthesis is open, and this token is not part of the current + ** phrase, and the starting byte offset of this token is past the point + ** that has currently been copied into the output buffer, close the + ** parenthesis. */ + if( p->bOpen + && (iPos<=p->iter.iStart || p->iter.iStart<0) + && iStartOff>p->iOff + ){ + fts5HighlightAppend(&rc, p, p->zClose, -1); + p->bOpen = 0; + } + + /* If this is the start of a new phrase, and the highlight is not open: + ** + ** * copy text from the input up to the start of the phrase, and + ** * open the highlight. + */ + if( iPos==p->iter.iStart && p->bOpen==0 ){ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff); fts5HighlightAppend(&rc, p, p->zOpen, -1); p->iOff = iStartOff; + p->bOpen = 1; } if( iPos==p->iter.iEnd ){ - if( p->iRangeEnd>=0 && p->iter.iStartiRangeStart ){ + if( p->bOpen==0 ){ + assert( p->iRangeEnd>=0 ); fts5HighlightAppend(&rc, p, p->zOpen, -1); + p->bOpen = 1; } fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); - fts5HighlightAppend(&rc, p, p->zClose, -1); p->iOff = iEndOff; + if( rc==SQLITE_OK ){ rc = fts5CInstIterNext(&p->iter); } } - if( p->iRangeEnd>=0 && iPos==p->iRangeEnd ){ + if( iPos==p->iRangeEnd ){ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); p->iOff = iEndOff; - if( iPos>=p->iter.iStart && iPositer.iEnd ){ - fts5HighlightAppend(&rc, p, p->zClose, -1); - } } return rc; @@ -232,6 +253,9 @@ static void fts5HighlightFunction( if( rc==SQLITE_OK ){ rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); } + if( ctx.bOpen ){ + fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1); + } fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff); if( rc==SQLITE_OK ){ @@ -510,6 +534,9 @@ static void fts5SnippetFunction( if( rc==SQLITE_OK ){ rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); } + if( ctx.bOpen ){ + fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1); + } if( ctx.iRangeEnd>=(nColSize-1) ){ fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff); }else{ diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index d6461fcb6..4e6afb281 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -2904,7 +2904,6 @@ static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ assert_nc( i2!=0 ); pRes->bTermEq = 1; if( p1->iRowid==p2->iRowid ){ - p1->bDel = p2->bDel; return i2; } res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1; @@ -3272,7 +3271,7 @@ static Fts5Iter *fts5MultiIterAlloc( int nSeg ){ Fts5Iter *pNew; - int nSlot; /* Power of two >= nSeg */ + i64 nSlot; /* Power of two >= nSeg */ for(nSlot=2; nSlotpLeaf->szLeaf; u64 iDelta = 0; - u64 iNextDelta = 0; int iNextOff = 0; int iOff = 0; int nIdx = 0; @@ -5056,7 +5054,6 @@ static void fts5DoSecureDelete( int bLastInDoclist = 0; int iIdx = 0; int iStart = 0; - int iKeyOff = 0; int iDelKeyOff = 0; /* Offset of deleted key, if any */ nIdx = nPg-iPgIdx; @@ -5081,10 +5078,21 @@ static void fts5DoSecureDelete( ** This block sets the following variables: ** ** iStart: + ** The offset of the first byte of the rowid or delta-rowid + ** value for the doclist entry being removed. + ** ** iDelta: + ** The value of the rowid or delta-rowid value for the doclist + ** entry being removed. + ** + ** iNextOff: + ** The offset of the next entry following the position list + ** for the one being removed. If the position list for this + ** entry overflows onto the next leaf page, this value will be + ** greater than pLeaf->szLeaf. */ { - int iSOP; + int iSOP; /* Start-Of-Position-list */ if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){ iStart = pSeg->iTermLeafOffset; }else{ @@ -5120,47 +5128,75 @@ static void fts5DoSecureDelete( } iOff = iStart; - if( iNextOff>=iPgIdx ){ - int pgno = pSeg->iLeafPgno+1; - fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); - iNextOff = iPgIdx; - }else{ - /* Set bLastInDoclist to true if the entry being removed is the last - ** in its doclist. */ - for(iIdx=0, iKeyOff=0; iIdxbDel==0 ){ + if( iNextOff>=iPgIdx ){ + int pgno = pSeg->iLeafPgno+1; + fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); + iNextOff = iPgIdx; + }else{ + /* Loop through the page-footer. If iNextOff (offset of the + ** entry following the one we are removing) is equal to the + ** offset of a key on this page, then the entry is the last + ** in its doclist. */ + int iKeyOff = 0; + for(iIdx=0; iIdxbDel ){ + iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta); + aPg[iOff++] = 0x01; + }else if( bLastInDoclist==0 ){ if( iNextOff!=iPgIdx ){ + u64 iNextDelta = 0; iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta); iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta); } }else if( - iStart==pSeg->iTermLeafOffset && pSeg->iLeafPgno==pSeg->iTermLeafPgno + pSeg->iLeafPgno==pSeg->iTermLeafPgno + && iStart==pSeg->iTermLeafOffset ){ /* The entry being removed was the only position list in its ** doclist. Therefore the term needs to be removed as well. */ int iKey = 0; - for(iIdx=0, iKeyOff=0; iIdx(u32)iStart ) break; iKeyOff += iVal; } + assert_nc( iKey>=1 ); + /* Set iDelKeyOff to the value of the footer entry to remove from + ** the page. */ iDelKeyOff = iOff = iKeyOff; + if( iNextOff!=iPgIdx ){ + /* This is the only position-list associated with the term, and there + ** is another term following it on this page. So the subsequent term + ** needs to be moved to replace the term associated with the entry + ** being removed. */ int nPrefix = 0; int nSuffix = 0; int nPrefix2 = 0; @@ -5239,6 +5275,15 @@ static void fts5DoSecureDelete( } } + /* Assuming no error has occurred, this block does final edits to the + ** leaf page before writing it back to disk. Input variables are: + ** + ** nPg: Total initial size of leaf page. + ** iPgIdx: Initial offset of page footer. + ** + ** iOff: Offset to move data to + ** iNextOff: Offset to move data from + */ if( p->rc==SQLITE_OK ){ const int nMove = nPg - iNextOff; /* Number of bytes to move */ int nShift = iNextOff - iOff; /* Distance to move them */ @@ -5439,10 +5484,16 @@ static void fts5FlushOneHash(Fts5Index *p){ fts5WriteFlushLeaf(p, &writer); } }else{ - int bDummy; - int nPos; - int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); - nCopy += nPos; + int bDel = 0; + int nPos = 0; + int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDel); + if( bDel && bSecureDelete ){ + fts5BufferAppendVarint(&p->rc, pBuf, nPos*2); + iOff += nCopy; + nCopy = nPos; + }else{ + nCopy += nPos; + } if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ /* The entire poslist will fit on the current leaf. So copy ** it in one go. */ @@ -5480,7 +5531,6 @@ static void fts5FlushOneHash(Fts5Index *p){ assert( pBuf->n<=pBuf->nSpace ); if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); } - sqlite3Fts5HashClear(pHash); fts5WriteFinish(p, &writer, &pgnoLast); assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); @@ -5513,7 +5563,6 @@ static void fts5FlushOneHash(Fts5Index *p){ fts5IndexCrisismerge(p, &pStruct); fts5StructureWrite(p, pStruct); fts5StructureRelease(pStruct); - p->nContentlessDelete = 0; } /* @@ -5524,8 +5573,12 @@ static void fts5IndexFlush(Fts5Index *p){ if( p->nPendingData || p->nContentlessDelete ){ assert( p->pHash ); fts5FlushOneHash(p); - p->nPendingData = 0; - p->nPendingRow = 0; + if( p->rc==SQLITE_OK ){ + sqlite3Fts5HashClear(p->pHash); + p->nPendingData = 0; + p->nPendingRow = 0; + p->nContentlessDelete = 0; + } } } @@ -8267,7 +8320,8 @@ int sqlite3Fts5IndexInit(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0); } diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index db44dfa2a..6e86ca595 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -117,6 +117,8 @@ struct Fts5FullTable { Fts5Storage *pStorage; /* Document store */ Fts5Global *pGlobal; /* Global (connection wide) data */ Fts5Cursor *pSortCsr; /* Sort data from this cursor */ + int iSavepoint; /* Successful xSavepoint()+1 */ + int bInSavepoint; #ifdef SQLITE_DEBUG struct Fts5TransactionState ts; #endif @@ -405,6 +407,13 @@ static int fts5InitVtab( pConfig->pzErrmsg = 0; } + if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ + rc = sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, (int)1); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + } + if( rc!=SQLITE_OK ){ fts5FreeVtab(pTab); pTab = 0; @@ -1354,9 +1363,8 @@ static int fts5FilterMethod( pCsr->pExpr = pTab->pSortCsr->pExpr; rc = fts5CursorFirst(pTab, pCsr, bDesc); }else if( pCsr->pExpr ){ - if( rc==SQLITE_OK ){ - rc = fts5CursorParseRank(pConfig, pCsr, pRank); - } + assert( rc==SQLITE_OK ); + rc = fts5CursorParseRank(pConfig, pCsr, pRank); if( rc==SQLITE_OK ){ if( bOrderByRank ){ pCsr->ePlan = FTS5_PLAN_SORTED_MATCH; @@ -1527,6 +1535,7 @@ static int fts5SpecialInsert( Fts5Config *pConfig = pTab->p.pConfig; int rc = SQLITE_OK; int bError = 0; + int bLoadConfig = 0; if( 0==sqlite3_stricmp("delete-all", zCmd) ){ if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ @@ -1538,6 +1547,7 @@ static int fts5SpecialInsert( }else{ rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage); } + bLoadConfig = 1; }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){ if( pConfig->eContent==FTS5_CONTENT_NONE ){ fts5SetVtabError(pTab, @@ -1547,6 +1557,7 @@ static int fts5SpecialInsert( }else{ rc = sqlite3Fts5StorageRebuild(pTab->pStorage); } + bLoadConfig = 1; }else if( 0==sqlite3_stricmp("optimize", zCmd) ){ rc = sqlite3Fts5StorageOptimize(pTab->pStorage); }else if( 0==sqlite3_stricmp("merge", zCmd) ){ @@ -1559,6 +1570,8 @@ static int fts5SpecialInsert( }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){ pConfig->bPrefixIndex = sqlite3_value_int(pVal); #endif + }else if( 0==sqlite3_stricmp("flush", zCmd) ){ + rc = sqlite3Fts5FlushToDisk(&pTab->p); }else{ rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); if( rc==SQLITE_OK ){ @@ -1572,6 +1585,12 @@ static int fts5SpecialInsert( } } } + + if( rc==SQLITE_OK && bLoadConfig ){ + pTab->p.pConfig->iCookie--; + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + } + return rc; } @@ -1690,7 +1709,7 @@ static int fts5UpdateMethod( assert( nArg!=1 || eType0==SQLITE_INTEGER ); /* Filter out attempts to run UPDATE or DELETE on contentless tables. - ** This is not suported. Except - DELETE is supported if the CREATE + ** This is not suported. Except - they are both supported if the CREATE ** VIRTUAL TABLE statement contained "contentless_delete=1". */ if( eType0==SQLITE_INTEGER && pConfig->eContent==FTS5_CONTENT_NONE @@ -1719,7 +1738,8 @@ static int fts5UpdateMethod( } else if( eType0!=SQLITE_INTEGER ){ - /* If this is a REPLACE, first remove the current entry (if any) */ + /* An INSERT statement. If the conflict-mode is REPLACE, first remove + ** the current entry (if any). */ if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){ i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); @@ -2593,8 +2613,12 @@ static int fts5RenameMethod( sqlite3_vtab *pVtab, /* Virtual table handle */ const char *zName /* New name of table */ ){ + int rc; Fts5FullTable *pTab = (Fts5FullTable*)pVtab; - return sqlite3Fts5StorageRename(pTab->pStorage, zName); + pTab->bInSavepoint = 1; + rc = sqlite3Fts5StorageRename(pTab->pStorage, zName); + pTab->bInSavepoint = 0; + return rc; } int sqlite3Fts5FlushToDisk(Fts5Table *pTab){ @@ -2608,9 +2632,29 @@ int sqlite3Fts5FlushToDisk(Fts5Table *pTab){ ** Flush the contents of the pending-terms table to disk. */ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ - UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ - fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_SAVEPOINT, iSavepoint); - return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + int rc = SQLITE_OK; + char *zSql = 0; + fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint); + + if( pTab->bInSavepoint==0 ){ + zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')", + pTab->p.pConfig->zDb, pTab->p.pConfig->zName, pTab->p.pConfig->zName + ); + if( zSql ){ + pTab->bInSavepoint = 1; + rc = sqlite3_exec(pTab->p.pConfig->db, zSql, 0, 0, 0); + pTab->bInSavepoint = 0; + sqlite3_free(zSql); + }else{ + rc = SQLITE_NOMEM; + } + if( rc==SQLITE_OK ){ + pTab->iSavepoint = iSavepoint+1; + } + } + + return rc; } /* @@ -2619,9 +2663,16 @@ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ ** This is a no-op. */ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ - UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ - fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_RELEASE, iSavepoint); - return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + int rc = SQLITE_OK; + fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint); + if( (iSavepoint+1)iSavepoint ){ + rc = sqlite3Fts5FlushToDisk(&pTab->p); + if( rc==SQLITE_OK ){ + pTab->iSavepoint = iSavepoint; + } + } + return rc; } /* @@ -2631,11 +2682,14 @@ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ */ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; - UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ + int rc = SQLITE_OK; fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint); fts5TripCursors(pTab); pTab->p.pConfig->pgsz = 0; - return sqlite3Fts5StorageRollback(pTab->pStorage); + if( (iSavepoint+1)<=pTab->iSavepoint ){ + rc = sqlite3Fts5StorageRollback(pTab->pStorage); + } + return rc; } /* @@ -2855,9 +2909,46 @@ static int fts5ShadowName(const char *zName){ return 0; } +/* +** Run an integrity check on the FTS5 data structures. Return a string +** if anything is found amiss. Return a NULL pointer if everything is +** OK. +*/ +static int fts5Integrity( + sqlite3_vtab *pVtab, /* the FTS5 virtual table to check */ + const char *zSchema, /* Name of schema in which this table lives */ + const char *zTabname, /* Name of the table itself */ + int isQuick, /* True if this is a quick-check */ + char **pzErr /* Write error message here */ +){ + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + Fts5Config *pConfig = pTab->p.pConfig; + char *zSql; + char *zErr = 0; + int rc; + assert( pzErr!=0 && *pzErr==0 ); + UNUSED_PARAM(isQuick); + zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", + zSchema, zTabname, pConfig->zName); + if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr); + sqlite3_free(zSql); + if( (rc&0xff)==SQLITE_CORRUPT ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", + zSchema, zTabname); + }else if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("unable to validate the inverted index for" + " FTS5 table %s.%s: %s", + zSchema, zTabname, zErr); + } + sqlite3_free(zErr); + return SQLITE_OK; +} + static int fts5Init(sqlite3 *db){ static const sqlite3_module fts5Mod = { - /* iVersion */ 3, + /* iVersion */ 4, /* xCreate */ fts5CreateMethod, /* xConnect */ fts5ConnectMethod, /* xBestIndex */ fts5BestIndexMethod, @@ -2880,7 +2971,8 @@ static int fts5Init(sqlite3 *db){ /* xSavepoint */ fts5SavepointMethod, /* xRelease */ fts5ReleaseMethod, /* xRollbackTo */ fts5RollbackToMethod, - /* xShadowName */ fts5ShadowName + /* xShadowName */ fts5ShadowName, + /* xIntegrity */ fts5Integrity }; int rc; diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index 0a0af9d4b..9480da7c5 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -1184,7 +1184,9 @@ int sqlite3Fts5StorageSync(Fts5Storage *p){ i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db); if( p->bTotalsValid ){ rc = fts5StorageSaveTotals(p); - p->bTotalsValid = 0; + if( rc==SQLITE_OK ){ + p->bTotalsValid = 0; + } } if( rc==SQLITE_OK ){ rc = sqlite3Fts5IndexSync(p->pIndex); diff --git a/ext/fts5/fts5_test_tok.c b/ext/fts5/fts5_test_tok.c index a5d839da6..994d304dc 100644 --- a/ext/fts5/fts5_test_tok.c +++ b/ext/fts5/fts5_test_tok.c @@ -472,7 +472,8 @@ int sqlite3Fts5TestRegisterTok(sqlite3 *db, fts5_api *pApi){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc; /* Return code */ diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c index 18774c4e4..d738ada31 100644 --- a/ext/fts5/fts5_vocab.c +++ b/ext/fts5/fts5_vocab.c @@ -783,7 +783,8 @@ int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){ /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, - /* xShadowName */ 0 + /* xShadowName */ 0, + /* xIntegrity */ 0 }; void *p = (void*)pGlobal; diff --git a/ext/fts5/test/fts5aa.test b/ext/fts5/test/fts5aa.test index 59ce4f6a1..e1551fc51 100644 --- a/ext/fts5/test/fts5aa.test +++ b/ext/fts5/test/fts5aa.test @@ -65,7 +65,9 @@ foreach w {a b c d e f} { do_execsql_test 2.4 { INSERT INTO t1(t1) VALUES('integrity-check'); -} + PRAGMA integrity_check; + PRAGMA integrity_check(t1); +} {ok ok} #------------------------------------------------------------------------- @@ -88,6 +90,7 @@ foreach {i x y} { } { do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) } do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') } + do_execsql_test 3.$i.3 { PRAGMA integrity_check(t1) } ok if {[set_test_counter errors]} break } @@ -135,7 +138,7 @@ foreach {i x y} { 10 {ddd abcde dddd dd c} {dddd c c d abcde} } { do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) } - do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') } + do_execsql_test 5.$i.2 { PRAGMA integrity_check(t1) } ok if {[set_test_counter errors]} break } diff --git a/ext/fts5/test/fts5aux.test b/ext/fts5/test/fts5aux.test index 561067c4b..b5a13aea1 100644 --- a/ext/fts5/test/fts5aux.test +++ b/ext/fts5/test/fts5aux.test @@ -307,5 +307,31 @@ do_catchsql_test 10.1.4 { SELECT group_concat(firstcol(t1), '.') FROM t1 GROUP BY rowid } {1 {unable to use function firstcol in the requested context}} -finish_test +#------------------------------------------------------------------------- +# Test that xInstCount() works from within an xPhraseQuery() callback. +# +reset_db + +proc xCallback {cmd} { + incr ::hitcount [$cmd xInstCount] + return SQLITE_OK +} +proc fts5_hitcount {cmd} { + set ::hitcount 0 + $cmd xQueryPhrase 0 xCallback + return $::hitcount +} +sqlite3_fts5_create_function db fts5_hitcount fts5_hitcount + +do_execsql_test 11.1 { + CREATE VIRTUAL TABLE x1 USING fts5(z); + INSERT INTO x1 VALUES('one two three'); + INSERT INTO x1 VALUES('one two one three one'); + INSERT INTO x1 VALUES('one two three'); +} +do_execsql_test 11.2 { + SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1; +} {5} + +finish_test diff --git a/ext/fts5/test/fts5conflict.test b/ext/fts5/test/fts5conflict.test index 644db53a1..b5bf0a116 100644 --- a/ext/fts5/test/fts5conflict.test +++ b/ext/fts5/test/fts5conflict.test @@ -65,4 +65,44 @@ do_execsql_test 2.1 { INSERT INTO fts_idx(fts_idx) VALUES('integrity-check'); } +#------------------------------------------------------------------------- +# Tests for OR IGNORE conflict handling. +# +reset_db +foreach_detail_mode $::testprefix { + + do_execsql_test 3.0 { + CREATE VIRTUAL TABLE t1 USING fts5(xyz, detail=%DETAIL%); + + BEGIN; + INSERT INTO t1(rowid, xyz) VALUES(13, 'thirteen documents'); + INSERT INTO t1(rowid, xyz) VALUES(14, 'fourteen documents'); + INSERT INTO t1(rowid, xyz) VALUES(15, 'fifteen documents'); + COMMIT; + } + + set db_cksum [cksum] + foreach {tn sql} { + 1 { + INSERT OR IGNORE INTO t1(rowid, xyz) VALUES(14, 'new text'); + } + 2 { + UPDATE OR IGNORE t1 SET rowid=13 WHERE rowid=15; + } + 3 { + INSERT OR IGNORE INTO t1(rowid, xyz) + SELECT 13, 'some text' + UNION ALL + SELECT 14, 'some text' + UNION ALL + SELECT 15, 'some text' + } + } { + do_execsql_test 3.1.$tn.1 $sql + do_test 3.1.$tn.2 { cksum } $db_cksum + } + +} + + finish_test diff --git a/ext/fts5/test/fts5content.test b/ext/fts5/test/fts5content.test index 74a74e2ad..ca2726a90 100644 --- a/ext/fts5/test/fts5content.test +++ b/ext/fts5/test/fts5content.test @@ -294,4 +294,3 @@ do_catchsql_test 7.2.5 { } {1 {recursively defined fts5 content table}} finish_test - diff --git a/ext/fts5/test/fts5contentless.test b/ext/fts5/test/fts5contentless.test index f75ccb44c..48cfd10ff 100644 --- a/ext/fts5/test/fts5contentless.test +++ b/ext/fts5/test/fts5contentless.test @@ -268,4 +268,3 @@ do_execsql_test 8.2 { } {} finish_test - diff --git a/ext/fts5/test/fts5contentless2.test b/ext/fts5/test/fts5contentless2.test index fbb857ab3..fdd7a60fc 100644 --- a/ext/fts5/test/fts5contentless2.test +++ b/ext/fts5/test/fts5contentless2.test @@ -205,4 +205,3 @@ foreach {tn step} { finish_test - diff --git a/ext/fts5/test/fts5contentless3.test b/ext/fts5/test/fts5contentless3.test index a44311e45..76119dc59 100644 --- a/ext/fts5/test/fts5contentless3.test +++ b/ext/fts5/test/fts5contentless3.test @@ -193,4 +193,3 @@ do_execsql_test 3.7 { finish_test - diff --git a/ext/fts5/test/fts5contentless4.test b/ext/fts5/test/fts5contentless4.test index 1c2666dcf..702b33a9d 100644 --- a/ext/fts5/test/fts5contentless4.test +++ b/ext/fts5/test/fts5contentless4.test @@ -245,4 +245,3 @@ do_execsql_test 4.3 { } finish_test - diff --git a/ext/fts5/test/fts5contentless5.test b/ext/fts5/test/fts5contentless5.test index 1541b0c68..a20134d1e 100644 --- a/ext/fts5/test/fts5contentless5.test +++ b/ext/fts5/test/fts5contentless5.test @@ -56,4 +56,3 @@ foreach {tn up err} { } finish_test - diff --git a/ext/fts5/test/fts5corrupt.test b/ext/fts5/test/fts5corrupt.test index 5f13513ec..9aa84a0ef 100644 --- a/ext/fts5/test/fts5corrupt.test +++ b/ext/fts5/test/fts5corrupt.test @@ -48,6 +48,10 @@ do_test 1.3 { } catchsql { INSERT INTO t1(t1) VALUES('integrity-check') } } {1 {database disk image is malformed}} +do_execsql_test 1.3b { + PRAGMA integrity_check(t1); +} {{malformed inverted index for FTS5 table main.t1}} + do_test 1.4 { db_restore_and_reopen diff --git a/ext/fts5/test/fts5corrupt2.test b/ext/fts5/test/fts5corrupt2.test index a815320b7..06e2e7425 100644 --- a/ext/fts5/test/fts5corrupt2.test +++ b/ext/fts5/test/fts5corrupt2.test @@ -167,6 +167,9 @@ foreach {tn hdr} { do_test 3.$tn.$tn2.2 { catchsql { INSERT INTO x3(x3) VALUES('integrity-check') } } {1 {database disk image is malformed}} + do_execsql_test 3.$tn.$tn2.3 { + PRAGMA integrity_check(x3); + } {{malformed inverted index for FTS5 table main.x3}} } execsql ROLLBACK diff --git a/ext/fts5/test/fts5faultG.test b/ext/fts5/test/fts5faultG.test new file mode 100644 index 000000000..bdcc153ad --- /dev/null +++ b/ext/fts5/test/fts5faultG.test @@ -0,0 +1,50 @@ +# 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] fts5_common.tcl] +source $testdir/malloc_common.tcl +set testprefix fts5faultG + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +set ::testprefix fts5faultG + + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a); + INSERT INTO t1 VALUES('test renaming the table'); + INSERT INTO t1 VALUES(' after it has been written'); + INSERT INTO t1 VALUES(' actually other stuff instead'); +} +faultsim_save_and_close +do_faultsim_test 1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + BEGIN; + DELETE FROM t1 WHERE rowid=2; + } +} -body { + execsql { + DELETE FROM t1; + } +} -test { + catchsql { COMMIT } + faultsim_integrity_check + faultsim_test_result {0 {}} +} + + +finish_test diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test index 403883086..f9851dc15 100644 --- a/ext/fts5/test/fts5integrity.test +++ b/ext/fts5/test/fts5integrity.test @@ -77,6 +77,9 @@ do_catchsql_test 4.2 { UPDATE aa_docsize SET sz = X'44' WHERE rowid = 3; INSERT INTO aa(aa) VALUES('integrity-check'); } {1 {database disk image is malformed}} +do_execsql_test 4.2.1 { + PRAGMA integrity_check(aa); +} {{malformed inverted index for FTS5 table main.aa}} do_catchsql_test 4.3 { ROLLBACK; @@ -317,4 +320,39 @@ do_catchsql_test 10.5.3 { INSERT INTO vt0(vt0) VALUES('integrity-check'); } {0 {}} +reset_db +proc slang {in} {return [string map {th d e eh} $in]} +db function slang -deterministic -innocuous slang +do_execsql_test 11.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT AS (slang(b))); + INSERT INTO t1(b) VALUES('the quick fox jumps over the lazy brown dog'); + SELECT c FROM t1; +} {{deh quick fox jumps ovehr deh lazy brown dog}} + +do_execsql_test 11.1 { + CREATE VIRTUAL TABLE t2 USING fts5(content="t1", c); + INSERT INTO t2(t2) VALUES('rebuild'); + SELECT rowid FROM t2 WHERE t2 MATCH 'deh'; +} {1} + +do_execsql_test 11.2 { + PRAGMA integrity_check(t2); +} {ok} +db close +sqlite3 db test.db + +# FIX ME? +# +# FTS5 integrity-check does not care if the content table is unreadable or +# does not exist. It only looks for internal inconsistencies in the +# inverted index. +# +do_execsql_test 11.3 { + PRAGMA integrity_check(t2); +} {ok} +do_execsql_test 11.4 { + DROP TABLE t1; + PRAGMA integrity_check(t2); +} {ok} + finish_test diff --git a/ext/fts5/test/fts5misc.test b/ext/fts5/test/fts5misc.test index adab15780..d67d79e29 100644 --- a/ext/fts5/test/fts5misc.test +++ b/ext/fts5/test/fts5misc.test @@ -424,10 +424,12 @@ do_execsql_test -db db2 15.3 { SAVEPOINT one; } {} do_execsql_test 15.4 END -do_test 15.4 { +do_test 15.5 { list [catch { db2 eval COMMIT } msg] $msg } {0 {}} +db2 close + #------------------------------------------------------------------------- reset_db forcedelete test.db2 @@ -469,6 +471,8 @@ do_execsql_test -db db2 16.6 { SELECT * FROM x1 } {abc def} +db2 close + #------------------------------------------------------------------------- reset_db do_execsql_test 17.1 { diff --git a/ext/fts5/test/fts5optimize2.test b/ext/fts5/test/fts5optimize2.test index a0782ee79..b0b28874c 100644 --- a/ext/fts5/test/fts5optimize2.test +++ b/ext/fts5/test/fts5optimize2.test @@ -9,7 +9,7 @@ # #*********************************************************************** # -# TESTRUNNER: slow +# TESTRUNNER: superslow # source [file join [file dirname [info script]] fts5_common.tcl] @@ -42,23 +42,4 @@ do_execsql_test 1.2 { SELECT count(*) FROM t1('mno') } $nLoop -do_execsql_test 2.0 { - CREATE VIRTUAL TABLE t2 USING fts5(x); - INSERT INTO t2(t2, rank) VALUES('pgsz', 32); -} - -do_test 2.1 { - for {set ii 0} {$ii < $nLoop} {incr ii} { - execsql { - INSERT INTO t2 VALUES('abc def ghi'); - INSERT INTO t2 VALUES('jkl mno pqr'); - INSERT INTO t2(t2, rank) VALUES('merge', -1); - } - } -} {} - -do_execsql_test 2.2 { - SELECT count(*) FROM t2('mno') -} $nLoop - finish_test diff --git a/ext/fts5/test/fts5optimize3.test b/ext/fts5/test/fts5optimize3.test new file mode 100644 index 000000000..7b11b9402 --- /dev/null +++ b/ext/fts5/test/fts5optimize3.test @@ -0,0 +1,45 @@ +# 2023 Aug 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. +# +#*********************************************************************** +# +# TESTRUNNER: superslow +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5optimize2 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +set nLoop 2500 + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t2 USING fts5(x); + INSERT INTO t2(t2, rank) VALUES('pgsz', 32); +} + +do_test 1.1 { + for {set ii 0} {$ii < $nLoop} {incr ii} { + execsql { + INSERT INTO t2 VALUES('abc def ghi'); + INSERT INTO t2 VALUES('jkl mno pqr'); + INSERT INTO t2(t2, rank) VALUES('merge', -1); + } + } +} {} + +do_execsql_test 1.2 { + SELECT count(*) FROM t2('mno') +} $nLoop + +finish_test diff --git a/ext/fts5/test/fts5savepoint.test b/ext/fts5/test/fts5savepoint.test index e431f9f5f..112622275 100644 --- a/ext/fts5/test/fts5savepoint.test +++ b/ext/fts5/test/fts5savepoint.test @@ -71,7 +71,7 @@ ifcapable fts3 { do_catchsql_test 3.2 { DROP TABLE vt1; - } {1 {SQL logic error}} + } {0 {}} do_execsql_test 3.3 { SAVEPOINT x; diff --git a/ext/fts5/test/fts5secure.test b/ext/fts5/test/fts5secure.test index 7e8aea3ba..731494616 100644 --- a/ext/fts5/test/fts5secure.test +++ b/ext/fts5/test/fts5secure.test @@ -274,8 +274,9 @@ do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2 do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2 #------------------------------------------------------------------------- +# Tests for the bug fixed by https://sqlite.org/src/info/4b60a1c3 +# reset_db - do_execsql_test 6.0 { CREATE VIRTUAL TABLE fts USING fts5(content); INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1); @@ -285,6 +286,62 @@ do_execsql_test 6.0 { INSERT INTO fts(fts) VALUES ('integrity-check'); } +foreach {tn detail} { + 1 full + 2 column + 3 none +} { + do_execsql_test 6.1.$detail " + DROP TABLE IF EXISTS t1; + CREATE VIRTUAL TABLE t1 USING fts5(x, detail=$detail); + " + + do_execsql_test 6.2.$detail { + INSERT INTO t1(t1, rank) VALUES('secure-delete', 1); + } + + for {set ii 1} {$ii < 100} {incr ii} { + do_execsql_test 6.3.$detail.$ii.1 { + BEGIN; + INSERT INTO t1(rowid, x) VALUES(10, 'word1'); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i $@ -$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) -$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) - $(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \ + @cat $(sqlite3-jni.h.in) > $@.tmp + @if cmp $@ $@.tmp >/dev/null; then \ + rm -f $@.tmp; \ + echo "$@ not modified"; \ + else \ + mv $@.tmp $@; \ + echo "Updated $@"; \ + fi + @if [ x1 != x$(enable.fts5) ]; then \ + echo "*** REMINDER:"; \ + echo "*** enable.fts5=0, so please do not check in changes to $@."; \ + fi + +$(package.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) +$(package.dll): $(sqlite3-jni.c) $(MAKEFILE) + $(CC) $(package.dll.cflags) $(SQLITE_OPT) \ $(sqlite3-jni.c) -shared -o $@ -all: $(sqlite3-jni.dll) - -.PHONY: test -test.flags ?= -v -test: $(SQLite3Jni.class) $(sqlite3-jni.dll) - $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ - $(java.flags) -cp $(classpath) \ - org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) +all: $(package.dll) + +.PHONY: test test-one +Tester1.flags ?= +Tester2.flags ?= +test.flags.jvm = -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) +test.deps := $(CLASS_FILES) $(package.dll) +test-one: $(test.deps) + $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags) + $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags) +test-sqllog: $(test.deps) + @echo "Testing with -sqllog..." + $(bin.java) $(test.flags.jvm) -sqllog +test-mt: $(test.deps) + @echo "Testing in multi-threaded mode:"; + $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \ + -t 7 -r 50 -shuffle $(Tester1.flags) + $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 \ + -t 7 -r 50 -shuffle $(Tester2.flags) + +test: test-one test-mt +tests: test test-sqllog tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose .PHONY: tester tester-local tester-ext ifeq (1,$(enable.tester)) -tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll) +tester-local: $(CLASS_FILES.tester) $(package.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ - org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts) + org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.scripts) tester: tester-local else tester: - @echo "SQLTester support is disabled. Build with enable.tester=1 to enable it." + @echo "SQLTester support is disabled." endif -tester.extdir.default := src/tests/ext +tester.extdir.default := $(dir.tests)/ext tester.extdir ?= $(tester.extdir.default) tester.extern-scripts := $(wildcard $(tester.extdir)/*.test) ifneq (,$(tester.extern-scripts)) tester-ext: $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ - org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts) + org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.extern-scripts) else tester-ext: @echo "******************************************************"; \ @@ -267,25 +361,79 @@ endif tester-ext: tester-local tester: tester-ext -tests: test tester +tests: tester +######################################################################## +# Build each SQLITE_THREADMODE variant and run all tests against them. +multitest: clean +define MULTIOPT +multitest: multitest-$(1) +multitest-$(1): + $$(MAKE) opt.debug=$$(opt.debug) $(patsubst %,opt.%,$(2)) \ + tests clean enable.fts5=1 +endef + +$(eval $(call MULTIOPT,01,threadsafe=0 oom=1)) +$(eval $(call MULTIOPT,00,threadsafe=0 oom=0)) +$(eval $(call MULTIOPT,11,threadsafe=1 oom=1)) +$(eval $(call MULTIOPT,10,threadsafe=1 oom=0)) +$(eval $(call MULTIOPT,21,threadsafe=2 oom=1)) +$(eval $(call MULTIOPT,20,threadsafe=2 oom=0)) + + +######################################################################## +# jar bundle... package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) -$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) - cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@ +JAVA_FILES.jar := $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.package.info) +CLASS_FILES.jar := $(filter-out %/package-info.class,$(JAVA_FILES.jar:.java=.class)) +$(package.jar.in): $(package.dll) $(MAKEFILE) + ls -1 \ + $(dir.src.jni)/*/*.java $(dir.src.jni)/*/*.class \ + | sed -e 's,^$(dir.src)/,,' | sort > $@ + +$(package.jar): $(CLASS_FILES.jar) $(MAKEFILE) $(package.jar.in) + @rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ + cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.capi.Tester1 @$(package.jar.in) @ls -la $@ - @echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag." - @echo "e.g. java -jar $@ -Djava.library.path=bld" - -$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in) - rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ - cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in) + @echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag." + @echo "e.g. java -Djava.library.path=bld -jar $@" jar: $(package.jar) +run-jar: $(package.jar) $(package.dll) + $(bin.java) -Djava.library.path=$(dir.bld) -jar $(package.jar) $(run-jar.flags) +######################################################################## +# javadoc... +dir.doc := $(dir.jni)/javadoc +doc.index := $(dir.doc)/index.html +javadoc.exclude := -exclude org.sqlite.jni.fts5 +# ^^^^ 2023-09-13: elide the fts5 parts from the public docs for +# the time being, as it's not clear where the Java bindings for +# those bits are going. +# javadoc.exclude += -exclude org.sqlite.jni.capi +# ^^^^ exclude the capi API only for certain builds (TBD) +$(doc.index): $(JAVA_FILES.main) $(MAKEFILE) + @if [ -d $(dir.doc) ]; then rm -fr $(dir.doc)/*; fi + $(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet \ + -subpackages org.sqlite.jni $(javadoc.exclude) + @echo "javadoc output is in $@" + +.PHONY: doc javadoc docserve +.FORCE: doc +doc: $(doc.index) +javadoc: $(doc.index) +# Force rebild of docs +redoc: + @rm -f $(doc.index) + @$(MAKE) doc +docserve: $(doc.index) + cd $(dir.doc) && althttpd -max-age 1 -page index.html +######################################################################## +# Clean up... CLEAN_FILES += $(dir.bld.c)/* \ $(dir.src.jni)/*.class \ - $(dir.src.jni.tester)/*.class \ - $(sqlite3-jni.dll) \ + $(dir.src.jni)/*/*.class \ + $(package.dll) \ hs_err_pid*.log .PHONY: clean distclean @@ -293,7 +441,7 @@ clean: -rm -f $(CLEAN_FILES) distclean: clean -rm -f $(DISTCLEAN_FILES) - -rm -fr $(dir.bld.c) + -rm -fr $(dir.bld.c) $(dir.doc) ######################################################################## # disttribution bundle rules... @@ -317,6 +465,10 @@ dist: \ $(bin.version-info) $(sqlite3.canonical.c) \ $(package.jar) $(MAKEFILE) @echo "Making end-user deliverables..." + @echo "****************************************************************************"; \ + echo "*** WARNING: be sure to build this with JDK8 (javac 1.8) for compatibility."; \ + echo "*** reasons!"; $$($(bin.javac) -version); \ + echo "****************************************************************************" @rm -fr $(dist-dir.top) @mkdir -p $(dist-dir.src) @cp -p $(dist.top.extras) $(dist-dir.top)/. diff --git a/ext/jni/README.md b/ext/jni/README.md index cb51a21cd..f2811fddb 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -15,7 +15,10 @@ Technical support is available in the forum: > **FOREWARNING:** this subproject is very much in development and subject to any number of changes. Please do not rely on any - information about its API until this disclaimer is removed. + information about its API until this disclaimer is removed. The JNI + bindings released with version 3.43 are a "tech preview" and 3.44 + will be "final," at which point strong backward compatibility + guarantees will apply. Project goals/requirements: @@ -40,13 +43,41 @@ Non-goals: - Creation of high-level OO wrapper APIs. Clients are free to create them off of the C-style API. - -Significant TODOs -======================================================================== - -- The initial beta release with version 3.43 has severe threading - limitations. Namely, two threads cannot call into the JNI-bound API - at once. This limitation will be remove in a subsequent release. +- Support for mixed-mode operation, where client code accesses SQLite + both via the Java-side API and the C API via their own native + code. In such cases, proxy functionalities (primarily callback + handler wrappers of all sorts) may fail because the C-side use of + the SQLite APIs will bypass those proxies. + + +Hello World +----------------------------------------------------------------------- + +```java +import org.sqlite.jni.*; +import static org.sqlite.jni.CApi.*; + +... + +final sqlite3 db = sqlite3_open(":memory:"); +try { + final int rc = sqlite3_errcode(db); + if( 0 != rc ){ + if( null != db ){ + System.out.print("Error opening db: "+sqlite3_errmsg(db)); + }else{ + System.out.print("Error opening db: rc="+rc); + } + ... handle error ... + } + // ... else use the db ... +}finally{ + // ALWAYS close databases using sqlite3_close() or sqlite3_close_v2() + // when done with them. All of their active statement handles must + // first have been passed to sqlite3_finalize(). + sqlite3_close_v2(db); +} +``` Building @@ -60,55 +91,97 @@ The canonical builds assumes a Linux-like environment and requires: Put simply: -``` +```console $ export JAVA_HOME=/path/to/jdk/root $ make $ make test $ make clean ``` +The jar distribution can be created with `make jar`, but note that it +does not contain the binary DLL file. A different DLL is needed for +each target platform. + + One-to-One(-ish) Mapping to C ======================================================================== This JNI binding aims to provide as close to a 1-to-1 experience with -the C API as cross-language semantics allow. Exceptions are +the C API as cross-language semantics allow. Interface changes are necessarily made where cross-language semantics do not allow a 1-to-1, and judiciously made where a 1-to-1 mapping would be unduly cumbersome -to use in Java. - -Golden Rule: _Never_ Throw from Callbacks +to use in Java. In all cases, this binding makes every effort to +provide semantics compatible with the C API documentation even if the +interface to those semantics is slightly different. Any cases which +deviate from those semantics (either removing or adding semantics) are +clearly documented. + +Where it makes sense to do so for usability, Java-side overloads are +provided which accept or return data in alternative forms or provide +sensible default argument values. In all such cases they are thin +proxies around the corresponding C APIs and do not introduce new +semantics. + +In some very few cases, Java-specific capabilities have been added in +new APIs, all of which have "_java" somewhere in their names. +Examples include: + +- `sqlite3_result_java_object()` +- `sqlite3_column_java_object()` +- `sqlite3_column_java_casted()` +- `sqlite3_value_java_object()` +- `sqlite3_value_java_casted()` + +which, as one might surmise, collectively enable the passing of +arbitrary Java objects from user-defined SQL functions through to the +caller. + + +Golden Rule: Garbage Collection Cannot Free SQLite Resources ------------------------------------------------------------------------ -JNI bindings which accept client-defined functions _must never throw -exceptions_ unless _very explicitly documented_ as being -throw-safe. Exceptions are generally reserved for higher-level -bindings which are constructed to specifically deal with them and -ensure that they do not leak C-level resources. Some of the JNI -bindings are provided as Java functions which expect this rule to -always hold. - -UTF-8(-ish) ------------------------------------------------------------------------- +It is important that all databases and prepared statement handles get +cleaned up by client code. A database cannot be closed if it has open +statement handles. `sqlite3_close()` fails if the db cannot be closed +whereas `sqlite3_close_v2()` recognizes that case and marks the db as +a "zombie," pending finalization when the library detects that all +pending statements have been closed. Be aware that Java garbage +collection _cannot_ close a database or finalize a prepared statement. +Those things require explicit API calls. -SQLite internally uses UTF-8 encoding, whereas Java natively uses -UTF-16. Java JNI has routines for converting to and from UTF-8, _but_ -Java uses what its docs call "[modified UTF-8][modutf8]." Care must be -taken when converting Java strings to UTF-8 to ensure that the proper -conversion is performed. In short, -`String.getBytes(StandardCharsets.UTF_8)` performs the proper -conversion in Java, and there is no JNI C API for that conversion -(JNI's `NewStringUTF()` returns MUTF-8). -Known consequences and limitations of this discrepancy include: - -- Names of databases, tables, and collations must not contain - characters which differ in MUTF-8 and UTF-8, or certain APIs will - mis-translate them on their way between languages. APIs which - transfer other client-side data to Java take extra care to - convert the data at the cost of performance. +Golden Rule #2: _Never_ Throw from Callbacks (Unless...) +------------------------------------------------------------------------ -[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 +All routines in this API, barring explicitly documented exceptions, +retain C-like semantics. For example, they are not permitted to throw +or propagate exceptions and must return error information (if any) via +result codes or `null`. The only cases where the C-style APIs may +throw is through client-side misuse, e.g. passing in a null where it +shouldn't be used. The APIs clearly mark function parameters which +should not be null, but does not actively defend itself against such +misuse. Some C-style APIs explicitly accept `null` as a no-op for +usability's sake, and some of the JNI APIs deliberately return an +error code, instead of segfaulting, when passed a `null`. + +Client-defined callbacks _must never throw exceptions_ unless _very +explicitly documented_ as being throw-safe. Exceptions are generally +reserved for higher-level bindings which are constructed to +specifically deal with them and ensure that they do not leak C-level +resources. In some cases, callback handlers are permitted to throw, in +which cases they get translated to C-level result codes and/or +messages. If a callback which is not permitted to throw throws, its +exception may trigger debug output but will otherwise be suppressed. + +The reason some callbacks are permitted to throw and others not is +because all such callbacks act as proxies for C function callback +interfaces and some of those interfaces have no error-reporting +mechanism. Those which are capable of propagating errors back through +the library convert exceptions from callbacks into corresponding +C-level error information. Those which cannot propagate errors +necessarily suppress any exceptions in order to maintain the C-style +semantics of the APIs. Unwieldy Constructs are Re-mapped @@ -126,7 +199,7 @@ A prime example of where interface changes for Java are necessary for usability is [registration of a custom collation](https://sqlite.org/c3ref/create_collation.html): -``` +```c // C: int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep, void *pUserData, @@ -145,7 +218,7 @@ passed that object as their first argument. That data is passed around bind that part as-is to Java, the result would be awkward to use (^Yes, we tried this.): -``` +```java // Java: int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, Object pUserData, xCompareType xCompare); @@ -160,20 +233,20 @@ for callbacks and (B) having their internal state provided separately, which is ill-fitting in Java. For the sake of usability, C APIs which follow that pattern use a slightly different Java interface: -``` +```java int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, - Collation collation); + SomeCallbackType collation); ``` -Where the `Collation` class has an abstract `xCompare()` method and +Where the `Collation` class has an abstract `call()` method and no-op `xDestroy()` method which can be overridden if needed, leading to a much more Java-esque usage: -``` -int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){ +```java +int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new SomeCallbackType(){ // Required comparison function: - @Override public int xCompare(byte[] lhs, byte[] rhs){ ... } + @Override public int call(byte[] lhs, byte[] rhs){ ... } // Optional finalizer function: @Override public void xDestroy(){ ... } @@ -188,8 +261,8 @@ int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation( Noting that: -- It is still possible to bind in call-scope-local state via closures, - if desired. +- It is possible to bind in call-scope-local state via closures, if + desired, as opposed to packing it into the Collation object. - No capabilities of the C API are lost or unduly obscured via the above API reshaping, so power users need not make any compromises. @@ -200,6 +273,7 @@ Noting that: overriding the `xDestroy()` method effectively gives it v2 semantics. + ### User-defined SQL Functions (a.k.a. UDFs) The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html) @@ -207,12 +281,13 @@ family of APIs make heavy use of function pointers to provide client-defined callbacks, necessitating interface changes in the JNI binding. The Java API has only one core function-registration function: -``` +```java int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, int encoding, SQLFunction func); ``` -> Design question: does the encoding argument serve any purpose in JS? +> Design question: does the encoding argument serve any purpose in + Java? That's as-yet undetermined. If not, it will be removed. `SQLFunction` is not used directly, but is instead instantiated via one of its three subclasses: @@ -230,5 +305,9 @@ Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for Reminder: see the disclaimer at the top of this document regarding the in-flux nature of this API. -[jsrc]: /file/ -[www]: https://sqlite.org +### And so on... + +Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and +`sqlite3_update_hook()`, use interfaces similar to those shown above. +Despite the changes in signature, the JNI layer makes every effort to +provide the same semantics as the C API documentation suggests. diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make index 23a26e4a8..7596c99f3 100644 --- a/ext/jni/jar-dist.make +++ b/ext/jni/jar-dist.make @@ -6,7 +6,9 @@ # proper top-level JDK directory and, depending on the platform, add a # platform-specific -I directory. It should build as-is with any # 2020s-era version of gcc or clang. It requires JDK version 8 or -# higher. +# higher and that JAVA_HOME points to the top-most installation +# directory of that JDK. On Ubuntu-style systems the JDK is typically +# installed under /usr/lib/jvm/java-VERSION-PLATFORM. default: all @@ -31,10 +33,11 @@ SQLITE_OPT = \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_SHARED_CACHE \ - -DSQLITE_THREADSAFE=0 \ + -DSQLITE_THREADSAFE=1 \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_USE_URI=1 \ - -DSQLITE_ENABLE_FTS5 + -DSQLITE_ENABLE_FTS5 \ + -DSQLITE_DEBUG sqlite3-jni.dll = libsqlite3-jni.so $(sqlite3-jni.dll): @@ -46,8 +49,10 @@ $(sqlite3-jni.dll): src/sqlite3-jni.c -shared -o $@ @echo "Now try running it with: make test" +test.flags = -Djava.library.path=. sqlite3-jni-*.jar test: $(sqlite3-jni.dll) - java -jar -Djava.library.path=. sqlite3-jni-*.jar + java -jar $(test.flags) + java -jar $(test.flags) -t 7 -r 10 -shuffle clean: -rm -f $(sqlite3-jni.dll) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index b28ea7114..245ce4f9e 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -10,18 +10,18 @@ ** ************************************************************************* ** This file implements the JNI bindings declared in -** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated). +** org.sqlite.jni.capi.CApi (from which sqlite3-jni.h is generated). */ -/** - If you found this comment by searching the code for - CallStaticObjectMethod then you're the victim of an OpenJDK bug: - - https://bugs.openjdk.org/browse/JDK-8130659 - - It's known to happen with OpenJDK v8 but not with v19. - - This code does not use JNI's CallStaticObjectMethod(). +/* +** If you found this comment by searching the code for +** CallStaticObjectMethod then you're the victim of an OpenJDK bug: +** +** https://bugs.openjdk.org/browse/JDK-8130659 +** +** It's known to happen with OpenJDK v8 but not with v19. +** +** This code does not use JNI's CallStaticObjectMethod(). */ /* @@ -43,6 +43,14 @@ /**********************************************************************/ /* SQLITE_ENABLE_... */ +/* +** Unconditionally enable API_ARMOR in the JNI build. It ensures that +** public APIs behave predictable in the face of passing illegal NULLs +** or ranges which might otherwise invoke undefined behavior. +*/ +#undef SQLITE_ENABLE_API_ARMOR +#define SQLITE_ENABLE_API_ARMOR 1 + #ifndef SQLITE_ENABLE_BYTECODE_VTAB # define SQLITE_ENABLE_BYTECODE_VTAB 1 #endif @@ -61,9 +69,6 @@ #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif -#ifndef SQLITE_ENABLE_PREUPDATE_HOOK -# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ -#endif #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif @@ -77,6 +82,14 @@ //# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION //#endif +/**********************************************************************/ +/* SQLITE_J... */ +#ifdef SQLITE_JNI_FATAL_OOM +#if !SQLITE_JNI_FATAL_OOM +#undef SQLITE_JNI_FATAL_OOM +#endif +#endif + /**********************************************************************/ /* SQLITE_M... */ #ifndef SQLITE_MAX_ALLOCATION_SIZE @@ -105,7 +118,7 @@ # define SQLITE_TEMP_STORE 2 #endif #ifndef SQLITE_THREADSAFE -# define SQLITE_THREADSAFE 0 +# define SQLITE_THREADSAFE 1 #endif /**********************************************************************/ @@ -119,9 +132,11 @@ ** Which sqlite3.c we're using needs to be configurable to enable ** building against a custom copy, e.g. the SEE variant. We have to ** include sqlite3.c, as opposed to sqlite3.h, in order to get access -** to SQLITE_MAX_... and friends. This increases the rebuild time -** considerably but we need this in order to keep the exported values -** of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C build. +** to some interal details like SQLITE_MAX_... and friends. This +** increases the rebuild time considerably but we need this in order +** to access some internal functionality and keep the to-Java-exported +** values of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C +** build. */ #ifndef SQLITE_C # define SQLITE_C sqlite3.c @@ -133,9 +148,14 @@ #undef INC__STRINGIFY #undef SQLITE_C +/* +** End of the sqlite3 lib setup. What follows is JNI-specific. +*/ + #include "sqlite3-jni.h" -#include /* only for testing/debugging */ #include +#include /* only for testing/debugging */ +#include /* intptr_t for 32-bit builds */ /* Only for debugging */ #define MARKER(pfexp) \ @@ -143,494 +163,763 @@ printf pfexp; \ } while(0) -/* Creates a verbose JNI function name. */ -#define JFuncName(Suffix) \ - Java_org_sqlite_jni_SQLite3Jni_sqlite3_ ## Suffix +/* +** Creates a verbose JNI function name. Suffix must be +** the JNI-mangled form of the function's name, minus the +** prefix seen in this macro. +*/ +#define JniFuncName(Suffix) \ + Java_org_sqlite_jni_capi_CApi_sqlite3_ ## Suffix -/* Prologue for JNI functions. */ -#define JDECL(ReturnType,Suffix) \ - JNIEXPORT ReturnType JNICALL \ - JFuncName(Suffix) -/** - Shortcuts for the first 2 parameters to all JNI bindings. +/* Prologue for JNI function declarations and definitions. */ +#define JniDecl(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL JniFuncName(Suffix) - The type of the jSelf arg differs, but no docs seem to mention - this: for static methods it's of type jclass and for non-static - it's jobject. jobject actually works for all funcs, in the sense - that it compiles and runs so long as we don't use jSelf (which is - only rarely needed in this code), but to be pedantically correct we - need the proper type in the signature. +/* +** S3JniApi's intent is that CFunc be the C API func(s) the +** being-declared JNI function is wrapping, making it easier to find +** that function's JNI-side entry point. The other args are for JniDecl. +** See the many examples in this file. +*/ +#define S3JniApi(CFunc,ReturnType,Suffix) JniDecl(ReturnType,Suffix) - Not even the official docs mention this discrepancy: +/* +** S3JniCast_L2P and P2L cast jlong (64-bit) to/from pointers. This is +** required for casting warning-free on 32-bit builds, where we +** otherwise get complaints that we're casting between different-sized +** int types. +** +** This use of intptr_t is the _only_ reason we require +** which, in turn, requires building with -std=c99 (or later). +*/ +#define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr)) +#define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR)) - https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers +/* +** Shortcuts for the first 2 parameters to all JNI bindings. +** +** The type of the jSelf arg differs, but no docs seem to mention +** this: for static methods it's of type jclass and for non-static +** it's jobject. jobject actually works for all funcs, in the sense +** that it compiles and runs so long as we don't use jSelf (which is +** only rarely needed in this code), but to be pedantically correct we +** need the proper type in the signature. +** +** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers +*/ +#define JniArgsEnvObj JNIEnv * const env, jobject jSelf +#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz +/* +** Helpers to account for -Xcheck:jni warnings about not having +** checked for exceptions. */ -#define JENV_OSELF JNIEnv * const env, jobject jSelf -#define JENV_CSELF JNIEnv * const env, jclass jKlazz -/* Helpers to account for -Xcheck:jni warnings about not having - checked for exceptions. */ -#define IFTHREW if((*env)->ExceptionCheck(env)) -#define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) -#define EXCEPTION_CLEAR (*env)->ExceptionClear(env) -#define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) -#define EXCEPTION_WARN_CALLBACK_THREW(STR) \ +#define S3JniIfThrew if( (*env)->ExceptionCheck(env) ) +#define S3JniExceptionClear (*env)->ExceptionClear(env) +#define S3JniExceptionReport (*env)->ExceptionDescribe(env) +#define S3JniExceptionIgnore S3JniIfThrew S3JniExceptionClear +#define S3JniExceptionWarnIgnore \ + S3JniIfThrew {S3JniExceptionReport; S3JniExceptionClear;}(void)0 +#define S3JniExceptionWarnCallbackThrew(STR) \ MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \ (*env)->ExceptionDescribe(env) -#define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT -#define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR /** To be used for cases where we're _really_ not expecting an exception, e.g. looking up well-defined Java class members. */ -#define EXCEPTION_IS_FATAL(MSG) IFTHREW {\ - EXCEPTION_REPORT; EXCEPTION_CLEAR; \ +#define S3JniExceptionIsFatal(MSG) S3JniIfThrew {\ + S3JniExceptionReport; S3JniExceptionClear; \ (*env)->FatalError(env, MSG); \ } -/** Helpers for extracting pointers from jobjects, noting that the - corresponding Java interfaces have already done the type-checking. - */ -#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3) -#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_stmt) -#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_value) -#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_context) +/* +** Declares local var env = s3jni_env(). All JNI calls involve a +** JNIEnv somewhere, always named env, and many of our macros assume +** env is in scope. Where it's not, but should be, use this to make it +** so. +*/ +#define S3JniDeclLocal_env JNIEnv * const env = s3jni_env() + +/* Fail fatally with an OOM message. */ +static inline void s3jni_oom(JNIEnv * const env){ + (*env)->FatalError(env, "SQLite3 JNI is out of memory.") /* does not return */; +} + +/* +** sqlite3_malloc() proxy which fails fatally on OOM. This should +** only be used for routines which manage global state and have no +** recovery strategy for OOM. For sqlite3 API which can reasonably +** return SQLITE_NOMEM, s3jni_malloc() should be used instead. +*/ +static void * s3jni_malloc_or_die(JNIEnv * const env, size_t n){ + void * const rv = sqlite3_malloc(n); + if( n && !rv ) s3jni_oom(env); + return rv; +} + +/* +** Works like sqlite3_malloc() unless built with SQLITE_JNI_FATAL_OOM, +** in which case it calls s3jni_oom() on OOM. +*/ +#ifdef SQLITE_JNI_FATAL_OOM +#define s3jni_malloc(SIZE) s3jni_malloc_or_die(env, SIZE) +#else +#define s3jni_malloc(SIZE) sqlite3_malloc(((void)env,(SIZE))) +/* the ((void)env) trickery here is to avoid ^^^^^^ an otherwise + unused arg in at least one place. */ +#endif + +/* +** Works like sqlite3_realloc() unless built with SQLITE_JNI_FATAL_OOM, +** in which case it calls s3jni_oom() on OOM. +*/ +#ifdef SQLITE_JNI_FATAL_OOM +static void * s3jni_realloc_or_die(JNIEnv * const env, void * p, size_t n){ + void * const rv = sqlite3_realloc(p, (int)n); + if( n && !rv ) s3jni_oom(env); + return rv; +} +#define s3jni_realloc(MEM,SIZE) s3jni_realloc_or_die(env, (MEM), (SIZE)) +#else +#define s3jni_realloc(MEM,SIZE) sqlite3_realloc((MEM), ((void)env, (SIZE))) +#endif + +/* Fail fatally if !EXPR. */ +#define s3jni_oom_fatal(EXPR) if( !(EXPR) ) s3jni_oom(env) +/* Maybe fail fatally if !EXPR. */ +#ifdef SQLITE_JNI_FATAL_OOM +#define s3jni_oom_check s3jni_oom_fatal +#else +#define s3jni_oom_check(EXPR) +#endif +//#define S3JniDb_oom(pDb,EXPR) ((EXPR) ? sqlite3OomFault(pDb) : 0) + +#define s3jni_db_oom(pDb) (void)((pDb) ? ((pDb)->mallocFailed=1) : 0) + /* Helpers for Java value reference management. */ -static inline jobject new_global_ref(JNIEnv * const env, jobject const v){ - return v ? (*env)->NewGlobalRef(env, v) : NULL; +static jobject s3jni_ref_global(JNIEnv * const env, jobject const v){ + jobject const rv = v ? (*env)->NewGlobalRef(env, v) : NULL; + s3jni_oom_fatal( v ? !!rv : 1 ); + return rv; } -static inline void delete_global_ref(JNIEnv * const env, jobject const v){ - if(v) (*env)->DeleteGlobalRef(env, v); +static jobject s3jni_ref_local(JNIEnv * const env, jobject const v){ + jobject const rv = v ? (*env)->NewLocalRef(env, v) : NULL; + s3jni_oom_fatal( v ? !!rv : 1 ); + return rv; +} +static inline void s3jni_unref_global(JNIEnv * const env, jobject const v){ + if( v ) (*env)->DeleteGlobalRef(env, v); } -static inline void delete_local_ref(JNIEnv * const env, jobject const v){ - if(v) (*env)->DeleteLocalRef(env, v); +static inline void s3jni_unref_local(JNIEnv * const env, jobject const v){ + if( v ) (*env)->DeleteLocalRef(env, v); } -#define REF_G(VAR) new_global_ref(env, (VAR)) -#define REF_L(VAR) (*env)->NewLocalRef(env, VAR) -#define UNREF_G(VAR) delete_global_ref(env,(VAR)) -#define UNREF_L(VAR) delete_local_ref(env,(VAR)) +#define S3JniRefGlobal(VAR) s3jni_ref_global(env, (VAR)) +#define S3JniRefLocal(VAR) s3jni_ref_local(env, (VAR)) +#define S3JniUnrefGlobal(VAR) s3jni_unref_global(env, (VAR)) +#define S3JniUnrefLocal(VAR) s3jni_unref_local(env, (VAR)) -/** - Constant string class names used as keys for S3JniGlobal_nph_cache(), -S3Jni - and - friends. +/* +** Lookup key type for use with s3jni_nphop() and a cache of a +** frequently-needed Java-side class reference and one or two Java +** class member IDs. +*/ +typedef struct S3JniNphOp S3JniNphOp; +struct S3JniNphOp { + const int index /* index into S3JniGlobal.nph[] */; + const char * const zName /* Full Java name of the class */; + const char * const zMember /* Name of member property */; + const char * const zTypeSig /* JNI type signature of zMember */; + /* + ** klazz is a global ref to the class represented by pRef. + ** + ** According to: + ** + ** https://developer.ibm.com/articles/j-jni/ + ** + ** > ... the IDs returned for a given class don't change for the + ** lifetime of the JVM process. But the call to get the field or + ** method can require significant work in the JVM, because fields + ** and methods might have been inherited from superclasses, making + ** the JVM walk up the class hierarchy to find them. Because the + ** IDs are the same for a given class, you should look them up + ** once and then reuse them. Similarly, looking up class objects + ** can be expensive, so they should be cached as well. + */ + jclass klazz; + volatile jfieldID fidValue /* NativePointerHolder.nativePointer or + ** OutputPointer.T.value */; + volatile jmethodID midCtor /* klazz's no-arg constructor. Used by + ** NativePointerHolder_new(). */; +}; + +/* +** Cache keys for each concrete NativePointerHolder subclasses and +** OutputPointer.T types. The members are to be used with s3jni_nphop() +** and friends, and each one's member->index corresponds to its index +** in the S3JniGlobal.nph[] array. */ static const struct { - const char * const sqlite3; - const char * const sqlite3_stmt; - const char * const sqlite3_context; - const char * const sqlite3_value; - const char * const OutputPointer_Int32; - const char * const OutputPointer_Int64; - const char * const OutputPointer_String; - const char * const OutputPointer_ByteArray; - const char * const OutputPointer_sqlite3; - const char * const OutputPointer_sqlite3_stmt; + const S3JniNphOp sqlite3; + const S3JniNphOp sqlite3_backup; + const S3JniNphOp sqlite3_blob; + const S3JniNphOp sqlite3_context; + const S3JniNphOp sqlite3_stmt; + const S3JniNphOp sqlite3_value; + const S3JniNphOp OutputPointer_Bool; + const S3JniNphOp OutputPointer_Int32; + const S3JniNphOp OutputPointer_Int64; + const S3JniNphOp OutputPointer_sqlite3; + const S3JniNphOp OutputPointer_sqlite3_blob; + const S3JniNphOp OutputPointer_sqlite3_stmt; + const S3JniNphOp OutputPointer_sqlite3_value; + const S3JniNphOp OutputPointer_String; #ifdef SQLITE_ENABLE_FTS5 - const char * const Fts5Context; - const char * const Fts5ExtensionApi; - const char * const fts5_api; - const char * const fts5_tokenizer; - const char * const Fts5Tokenizer; + const S3JniNphOp OutputPointer_ByteArray; + const S3JniNphOp Fts5Context; + const S3JniNphOp Fts5ExtensionApi; + const S3JniNphOp fts5_api; + const S3JniNphOp fts5_tokenizer; + const S3JniNphOp Fts5Tokenizer; #endif -} S3JniClassNames = { - "org/sqlite/jni/sqlite3", - "org/sqlite/jni/sqlite3_stmt", - "org/sqlite/jni/sqlite3_context", - "org/sqlite/jni/sqlite3_value", - "org/sqlite/jni/OutputPointer$Int32", - "org/sqlite/jni/OutputPointer$Int64", - "org/sqlite/jni/OutputPointer$String", - "org/sqlite/jni/OutputPointer$ByteArray", - "org/sqlite/jni/OutputPointer$sqlite3", - "org/sqlite/jni/OutputPointer$sqlite3_stmt", +} S3JniNphOps = { +#define MkRef(INDEX, KLAZZ, MEMBER, SIG) \ + { INDEX, "org/sqlite/jni/" KLAZZ, MEMBER, SIG } +/* NativePointerHolder ref */ +#define RefN(INDEX, KLAZZ) MkRef(INDEX, KLAZZ, "nativePointer", "J") +/* OutputPointer.T ref */ +#define RefO(INDEX, KLAZZ, SIG) MkRef(INDEX, KLAZZ, "value", SIG) + RefN(0, "capi/sqlite3"), + RefN(1, "capi/sqlite3_backup"), + RefN(2, "capi/sqlite3_blob"), + RefN(3, "capi/sqlite3_context"), + RefN(4, "capi/sqlite3_stmt"), + RefN(5, "capi/sqlite3_value"), + RefO(6, "capi/OutputPointer$Bool", "Z"), + RefO(7, "capi/OutputPointer$Int32", "I"), + RefO(8, "capi/OutputPointer$Int64", "J"), + RefO(9, "capi/OutputPointer$sqlite3", + "Lorg/sqlite/jni/capi/sqlite3;"), + RefO(10, "capi/OutputPointer$sqlite3_blob", + "Lorg/sqlite/jni/capi/sqlite3_blob;"), + RefO(11, "capi/OutputPointer$sqlite3_stmt", + "Lorg/sqlite/jni/capi/sqlite3_stmt;"), + RefO(12, "capi/OutputPointer$sqlite3_value", + "Lorg/sqlite/jni/capi/sqlite3_value;"), + RefO(13, "capi/OutputPointer$String", "Ljava/lang/String;"), #ifdef SQLITE_ENABLE_FTS5 - "org/sqlite/jni/Fts5Context", - "org/sqlite/jni/Fts5ExtensionApi", - "org/sqlite/jni/fts5_api", - "org/sqlite/jni/fts5_tokenizer", - "org/sqlite/jni/Fts5Tokenizer" + RefO(14, "capi/OutputPointer$ByteArray", "[B"), + RefN(15, "fts5/Fts5Context"), + RefN(16, "fts5/Fts5ExtensionApi"), + RefN(17, "fts5/fts5_api"), + RefN(18, "fts5/fts5_tokenizer"), + RefN(19, "fts5/Fts5Tokenizer") #endif +#undef MkRef +#undef RefN +#undef RefO }; -/** Create a trivial JNI wrapper for (int CName(void)). */ -#define WRAP_INT_VOID(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF){ \ - return (jint)CName(); \ - } - -/** Create a trivial JNI wrapper for (int CName(int)). */ -#define WRAP_INT_INT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jint arg){ \ - return (jint)CName((int)arg); \ - } - -/** Create a trivial JNI wrapper for (const mutf8_string * - CName(void)). This is only valid for functions which are known to - return ASCII or text which is equivalent in UTF-8 and MUTF-8. */ -#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ - JDECL(jstring,JniNameSuffix)(JENV_CSELF){ \ - return (*env)->NewStringUTF( env, CName() ); \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ -#define WRAP_INT_STMT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpStmt){ \ - jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \ - EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \ - return rc; \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ -#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint n){ \ - return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ - } -/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ -#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ - JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ - return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ -#define WRAP_INT_DB(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ - return (jint)CName(PtrGet_sqlite3(pDb)); \ - } -/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ -#define WRAP_INT64_DB(JniNameSuffix,CName) \ - JDECL(jlong,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ - return (jlong)CName(PtrGet_sqlite3(pDb)); \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ -#define WRAP_INT_SVALUE(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpSValue){ \ - return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \ - } - -/* Helpers for jstring and jbyteArray. */ -#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) -#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) -#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) -#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) - -/* Marker for code which needs(?) to be made thread-safe. REASON is a - terse reminder about why that function requires a mutex. -*/ -#define FIXME_THREADING(REASON) +#define S3JniNph(T) &S3JniNphOps.T enum { - /** - Size of the NativePointerHolder cache. Need enough space for - (only) the library's NativePointerHolder types, a fixed count - known at build-time. If we add more than this a fatal error will - be triggered with a reminder to increase this. This value needs - to be exactly the number of entries in the S3JniClassNames - object. The S3JniClassNames entries are the keys for this particular - cache. + /* + ** Size of the NativePointerHolder cache. Need enough space for + ** (only) the library's NativePointerHolder and OutputPointer types, + ** a fixed count known at build-time. This value needs to be + ** exactly the number of S3JniNphOp entries in the S3JniNphOps + ** object. */ - NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *) + S3Jni_NphCache_size = sizeof(S3JniNphOps) / sizeof(S3JniNphOp) }; -/** - Cache entry for NativePointerHolder lookups. +/* +** State for binding C callbacks to Java methods. */ -typedef struct S3JniNphCache S3JniNphCache; -struct S3JniNphCache { - const char * zClassName /* "full/class/Name". Must be a static - string pointer from the S3JniClassNames - struct. */; - jclass klazz /* global ref to the concrete - NativePointerHolder subclass represented by - zClassName */; - jmethodID midCtor /* klazz's no-arg constructor. Used by - new_NativePointerHolder_object(). */; - jfieldID fidValue /* NativePointerHolder.nativePointer and - OutputPointer.X.value */; - jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only - by the sqlite3_context binding. */; +typedef struct S3JniHook S3JniHook; +struct S3JniHook{ + jobject jObj /* global ref to Java instance */; + jmethodID midCallback /* callback method. Signature depends on + ** jObj's type */; + /* We lookup the jObj.xDestroy() method as-needed for contexts which + ** support custom finalizers. Fundamentally we can support them for + ** any Java type, but we only want to expose support for them where + ** the C API does. */ + jobject jExtra /* Global ref to a per-hook-type value */; + int doXDestroy /* If true then S3JniHook_unref() will call + jObj->xDestroy() if it's available. */; + S3JniHook * pNext /* Next entry in S3Global.hooks.aFree */; }; +/* For clean bitwise-copy init of local instances. */ +static const S3JniHook S3JniHook_empty = {0,0,0,0,0}; -/** - Cache for per-JNIEnv data. - - Potential TODO: move the jclass entries to global space because, - per https://developer.android.com/training/articles/perf-jni: - - > once you have a valid jclass global reference you can use it from - any attached thread. - - Whereas we cache new refs for each thread. +/* +** Per-(sqlite3*) state for various JNI bindings. This state is +** allocated as needed, cleaned up in sqlite3_close(_v2)(), and +** recycled when possible. +** +** Trivia: vars and parameters of this type are often named "ps" +** because this class used to have a name for which that abbreviation +** made sense. */ -typedef struct S3JniEnvCache S3JniEnvCache; -struct S3JniEnvCache { - JNIEnv *env /* env in which this cache entry was created */; - //! The various refs to global classes might be cacheable a single - // time globally. Information online seems inconsistent on that - // point. +typedef struct S3JniDb S3JniDb; +struct S3JniDb { + sqlite3 *pDb /* The associated db handle */; + jobject jDb /* A global ref of the output object which gets + returned from sqlite3_open(_v2)(). We need this in + order to have an object to pass to routines like + sqlite3_collation_needed()'s callback, or else we + have to dynamically create one for that purpose, + which would be fine except that it would be a + different instance (and maybe even a different + class) than the one the user may expect to + receive. */; + char * zMainDbName /* Holds the string allocated on behalf of + SQLITE_DBCONFIG_MAINDBNAME. */; struct { - jclass cObj /* global ref to java.lang.Object */; - jclass cLong /* global ref to java.lang.Long */; - jclass cString /* global ref to java.lang.String */; - jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; - jmethodID ctorLong1 /* the Long(long) constructor */; - jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; - jmethodID stringGetBytes /* the String.getBytes(Charset) method */; - } g /* refs to global Java state */; + S3JniHook busyHandler; + S3JniHook collationNeeded; + S3JniHook commit; + S3JniHook progress; + S3JniHook rollback; + S3JniHook trace; + S3JniHook update; + S3JniHook auth; +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + S3JniHook preUpdate; +#endif + } hooks; #ifdef SQLITE_ENABLE_FTS5 - jobject jFtsExt /* Global ref to Java singleton for the - Fts5ExtensionApi instance. */; + /* FTS5-specific state */ struct { - jclass klazz; - jfieldID fidA; - jfieldID fidB; - } jPhraseIter; + jobject jApi /* global ref to s3jni_fts5_api_from_db() */; + } fts; #endif - S3JniEnvCache * pPrev /* Previous entry in the linked list */; - S3JniEnvCache * pNext /* Next entry in the linked list */; - /** TODO?: S3JniNphCache *pNphHit; + S3JniDb * pNext /* Next entry in SJG.perDb.aFree */; +}; - and always set it to the most recent cache search result. +static const char * const S3JniDb_clientdata_key = "S3JniDb"; +#define S3JniDb_from_clientdata(pDb) \ + (pDb ? sqlite3_get_clientdata(pDb, S3JniDb_clientdata_key) : 0) - The intent would be to help fast-track cache lookups and would - speed up, e.g., the sqlite3_value-to-Java-array loop in a - multi-threaded app. +/* +** Cache for per-JNIEnv (i.e. per-thread) data. +** +** Trivia: vars and parameters of this type are often named "jc" +** because this class used to have a name for which that abbreviation +** made sense. +*/ +typedef struct S3JniEnv S3JniEnv; +struct S3JniEnv { + JNIEnv *env /* JNIEnv in which this cache entry was created */; + /* + ** pdbOpening is used to coordinate the Java/DB connection of a + ** being-open()'d db in the face of auto-extensions. + ** Auto-extensions run before we can bind the C db to its Java + ** representation, but auto-extensions require that binding to pass + ** on to their Java-side callbacks. We handle this as follows: + ** + ** - In the JNI side of sqlite3_open(), allocate the Java side of + ** that connection and set pdbOpening to point to that + ** object. + ** + ** - Call sqlite3_open(), which triggers the auto-extension + ** handler. That handler uses pdbOpening to connect the native + ** db handle which it receives with pdbOpening. + ** + ** - When sqlite3_open() returns, check whether pdbOpening->pDb is + ** NULL. If it isn't, auto-extension handling set it up. If it + ** is, complete the Java/C binding unless sqlite3_open() returns + ** a NULL db, in which case free pdbOpening. */ - S3JniNphCache nph[NphCache_SIZE]; + S3JniDb * pdbOpening; + S3JniEnv * pNext /* Next entry in SJG.envCache.aHead or + SJG.envCache.aFree */; }; -static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){ - UNREF_G(p->klazz); - memset(p, 0, sizeof(S3JniNphCache)); -} +/* +** State for proxying sqlite3_auto_extension() in Java. This was +** initially a separate class from S3JniHook and now the older name is +** retained for readability in the APIs which use this, as well as for +** its better code-searchability. +*/ +typedef S3JniHook S3JniAutoExtension; -#define S3JNI_ENABLE_AUTOEXT 1 -#if S3JNI_ENABLE_AUTOEXT /* - Whether auto extensions are feasible here is currently unknown due - to... - - 1) JNIEnv/threading issues. A db instance is mapped to a specific - JNIEnv object but auto extensions may be added from any thread. In - such contexts, which JNIEnv do we use for the JNI APIs? - - 2) a chicken/egg problem involving the Java/C mapping of the db: - when auto extensions are run, the db has not yet been connected to - Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave - properly because they have a different jobject and the API - guarantees the user that _that_ object is the one the API will bind - the native to. - - If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 - instead of the client passing in an instance, we could work around - (2). +** Type IDs for SQL function categories. */ -typedef struct S3JniAutoExtension S3JniAutoExtension; -typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*); -struct S3JniAutoExtension { - jobject jObj; - jmethodID midFunc; - S3JniAutoExtension_xEntryPoint xEntryPoint; - S3JniAutoExtension *pNext /* next linked-list entry */; - S3JniAutoExtension *pPrev /* previous linked-list entry */; +enum UDFType { + UDF_UNKNOWN_TYPE = 0/*for error propagation*/, + UDF_SCALAR, + UDF_AGGREGATE, + UDF_WINDOW }; -#endif -/** State for various hook callbacks. */ -typedef struct S3JniHook S3JniHook; -struct S3JniHook{ - jobject jObj /* global ref to Java instance */; - jmethodID midCallback /* callback method. Signature depends on - jObj's type */; - jclass klazz /* global ref to jObj's class. Only needed - by hooks which have an xDestroy() method, - as lookup of that method is deferred - until the object requires cleanup. */; +/* +** State for binding Java-side UDFs. +*/ +typedef struct S3JniUdf S3JniUdf; +struct S3JniUdf { + jobject jObj /* SQLFunction instance */; + char * zFuncName /* Only for error reporting and debug logging */; + enum UDFType type /* UDF type */; + /** Method IDs for the various UDF methods. */ + jmethodID jmidxFunc /* xFunc method (scalar) */; + jmethodID jmidxStep /* xStep method (aggregate/window) */; + jmethodID jmidxFinal /* xFinal method (aggregate/window) */; + jmethodID jmidxValue /* xValue method (window) */; + jmethodID jmidxInverse /* xInverse method (window) */; + S3JniUdf * pNext /* Next entry in SJG.udf.aFree. */; }; -/** - Per-(sqlite3*) state for various JNI bindings. This state is - allocated as needed, cleaned up in sqlite3_close(_v2)(), and - recycled when possible. It is freed during sqlite3_shutdown(). +#if defined(SQLITE_JNI_ENABLE_METRICS) && 0==SQLITE_JNI_ENABLE_METRICS +# undef SQLITE_JNI_ENABLE_METRICS +#endif + +/* +** If true, modifying S3JniGlobal.metrics is protected by a mutex, +** else it isn't. */ -typedef struct S3JniDb S3JniDb; -struct S3JniDb { - JNIEnv *env /* The associated JNIEnv handle */; - sqlite3 *pDb /* The associated db handle */; - jobject jDb /* A global ref of the object which was passed to - sqlite3_open(_v2)(). We need this in order to have - an object to pass to sqlite3_collation_needed()'s - callback, or else we have to dynamically create one - for that purpose, which would be fine except that - it would be a different instance (and maybe even a - different class) than the one the user may expect - to receive. */; - char * zMainDbName /* Holds any string allocated on behave of - SQLITE_DBCONFIG_MAINDBNAME. */; - S3JniHook busyHandler; - S3JniHook collation; - S3JniHook collationNeeded; - S3JniHook commitHook; - S3JniHook progress; - S3JniHook rollbackHook; - S3JniHook trace; - S3JniHook updateHook; - S3JniHook authHook; -#ifdef SQLITE_ENABLE_FTS5 - jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; +#ifdef SQLITE_DEBUG +# define S3JNI_METRICS_MUTEX SQLITE_THREADSAFE +#else +# define S3JNI_METRICS_MUTEX 0 +#endif +#ifndef SQLITE_JNI_ENABLE_METRICS +# undef S3JNI_METRICS_MUTEX +# define S3JNI_METRICS_MUTEX 0 #endif - S3JniDb * pNext /* Next entry in the available/free list */; - S3JniDb * pPrev /* Previous entry in the available/free list */; -}; -/** - Global state, e.g. caches and metrics. +/* +** Global state, e.g. caches and metrics. */ -static struct { - /** - According to: https://developer.ibm.com/articles/j-jni/ - - > A thread can get a JNIEnv by calling GetEnv() using the JNI - invocation interface through a JavaVM object. The JavaVM object - itself can be obtained by calling the JNI GetJavaVM() method - using a JNIEnv object and can be cached and shared across - threads. Caching a copy of the JavaVM object enables any thread - with access to the cached object to get access to its own - JNIEnv when necessary. +typedef struct S3JniGlobalType S3JniGlobalType; +struct S3JniGlobalType { + /* + ** According to: https://developer.ibm.com/articles/j-jni/ + ** + ** > A thread can get a JNIEnv by calling GetEnv() using the JNI + ** invocation interface through a JavaVM object. The JavaVM object + ** itself can be obtained by calling the JNI GetJavaVM() method + ** using a JNIEnv object and can be cached and shared across + ** threads. Caching a copy of the JavaVM object enables any thread + ** with access to the cached object to get access to its own + ** JNIEnv when necessary. */ JavaVM * jvm; + /* + ** Global mutex. It must not be used for anything which might call + ** back into the JNI layer. + */ + sqlite3_mutex * mutex; + /* + ** Cache of references to Java classes and method IDs for + ** NativePointerHolder subclasses and OutputPointer.T types. + */ + struct { + S3JniNphOp list[S3Jni_NphCache_size]; + sqlite3_mutex * mutex; /* mutex for this->list */ + volatile void const * locker; /* sanity-checking-only context object + for this->mutex */ + } nph; + /* + ** Cache of per-thread state. + */ struct { - S3JniEnvCache * aHead /* Linked list of in-use instances */; - S3JniEnvCache * aFree /* Linked list of free instances */; + S3JniEnv * aHead /* Linked list of in-use instances */; + S3JniEnv * aFree /* Linked list of free instances */; + sqlite3_mutex * mutex /* mutex for aHead and aFree. */; + volatile void const * locker /* env mutex is held on this + object's behalf. Used only for + sanity checking. */; } envCache; + /* + ** Per-db state. This can move into the core library once we can tie + ** client-defined state to db handles there. + */ struct { - S3JniDb * aUsed /* Linked list of in-use instances */; S3JniDb * aFree /* Linked list of free instances */; + sqlite3_mutex * mutex /* mutex for aHead and aFree */; + volatile void const * locker + /* perDb mutex is held on this object's behalf. Used only for + sanity checking. Note that the mutex is at the class level, not + instance level. */; } perDb; struct { - unsigned nphCacheHits; - unsigned nphCacheMisses; - unsigned envCacheHits; - unsigned envCacheMisses; - unsigned nDestroy /* xDestroy() calls across all types */; + S3JniUdf * aFree /* Head of the free-item list. Guarded by global + mutex. */; + } udf; + /* + ** Refs to global classes and methods. Obtained during static init + ** and never released. + */ + struct { + jclass cLong /* global ref to java.lang.Long */; + jclass cString /* global ref to java.lang.String */; + jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; + jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + jmethodID stringGetBytes /* the String.getBytes(Charset) method */; + } g; + /* + ** The list of Java-side auto-extensions + ** (org.sqlite.jni.capi.AutoExtensionCallback objects). + */ + struct { + S3JniAutoExtension *aExt /* The auto-extension list. It is + maintained such that all active + entries are in the first contiguous + nExt array elements. */; + int nAlloc /* number of entries allocated for aExt, + as distinct from the number of active + entries. */; + int nExt /* number of active entries in aExt, all in the + first nExt'th array elements. */; + sqlite3_mutex * mutex /* mutex for manipulation/traversal of aExt */; + volatile const void * locker /* object on whose behalf the mutex + is held. Only for sanity checking + in debug builds. */; + } autoExt; +#ifdef SQLITE_ENABLE_FTS5 + struct { + volatile jobject jExt /* Global ref to Java singleton for the + Fts5ExtensionApi instance. */; + struct { + jfieldID fidA /* Fts5Phrase::a member */; + jfieldID fidB /* Fts5Phrase::b member */; + } jPhraseIter; + } fts5; +#endif + struct { +#ifdef SQLITE_ENABLE_SQLLOG + S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */; +#endif + S3JniHook configlog /* sqlite3_config(SQLITE_CONFIG_LOG) callback */; + S3JniHook * aFree /* free-item list, for recycling. */; + sqlite3_mutex * mutex /* mutex for aFree */; + volatile const void * locker /* object on whose behalf the mutex + is held. Only for sanity checking + in debug builds. */; + } hook; +#ifdef SQLITE_JNI_ENABLE_METRICS + /* Internal metrics. */ + struct { + volatile unsigned nEnvHit; + volatile unsigned nEnvMiss; + volatile unsigned nEnvAlloc; + volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for + a S3JniEnv operation. */; + volatile unsigned nMutexNph /* number of times SJG.mutex was entered */; + volatile unsigned nMutexHook /* number of times SJG.mutex hooks.was entered */; + volatile unsigned nMutexPerDb /* number of times perDb.mutex was entered */; + volatile unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */; + volatile unsigned nMutexGlobal /* number of times global mutex was entered. */; + volatile unsigned nMutexUdf /* number of times global mutex was entered + for UDFs. */; + volatile unsigned nDestroy /* xDestroy() calls across all types */; + volatile unsigned nPdbAlloc /* Number of S3JniDb alloced. */; + volatile unsigned nPdbRecycled /* Number of S3JniDb reused. */; + volatile unsigned nUdfAlloc /* Number of S3JniUdf alloced. */; + volatile unsigned nUdfRecycled /* Number of S3JniUdf reused. */; + volatile unsigned nHookAlloc /* Number of S3JniHook alloced. */; + volatile unsigned nHookRecycled /* Number of S3JniHook reused. */; struct { /* Number of calls for each type of UDF callback. */ - unsigned nFunc; - unsigned nStep; - unsigned nFinal; - unsigned nValue; - unsigned nInverse; + volatile unsigned nFunc; + volatile unsigned nStep; + volatile unsigned nFinal; + volatile unsigned nValue; + volatile unsigned nInverse; } udf; + unsigned nMetrics /* Total number of mutex-locked + metrics increments. */; +#if S3JNI_METRICS_MUTEX + sqlite3_mutex * mutex; +#endif } metrics; -#if S3JNI_ENABLE_AUTOEXT - struct { - S3JniAutoExtension *pHead /* Head of the auto-extension list */; - S3JniDb * psOpening /* handle to the being-opened db. We - need this so that auto extensions - can have a consistent view of the - cross-language db connection and - behave property if they call further - db APIs. */; - int isRunning /* True while auto extensions are - running. This is used to prohibit - manipulation of the auto-extension - list while extensions are - running. */; - } autoExt; +#endif /* SQLITE_JNI_ENABLE_METRICS */ +}; +static S3JniGlobalType S3JniGlobal = {}; +#define SJG S3JniGlobal + +/* Increments *p, possibly protected by a mutex. */ +#ifndef SQLITE_JNI_ENABLE_METRICS +#define s3jni_incr(PTR) +#elif S3JNI_METRICS_MUTEX +static void s3jni_incr( volatile unsigned int * const p ){ + sqlite3_mutex_enter(SJG.metrics.mutex); + ++SJG.metrics.nMetrics; + ++(*p); + sqlite3_mutex_leave(SJG.metrics.mutex); +} +#else +#define s3jni_incr(PTR) ++(*(PTR)) #endif -} S3JniGlobal; -#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) -static void s3jni_oom(JNIEnv * const env){ - (*env)->FatalError(env, "Out of memory.") /* does not return */; +/* Helpers for working with specific mutexes. */ +#if SQLITE_THREADSAFE +#define s3jni_mutex_enter2(M, Metric) \ + sqlite3_mutex_enter( M ); \ + s3jni_incr( &SJG.metrics.Metric ) +#define s3jni_mutex_leave2(M) \ + sqlite3_mutex_leave( M ) + +#define s3jni_mutex_enter(M, L, Metric) \ + assert( (void*)env != (void*)L && "Invalid use of " #L); \ + s3jni_mutex_enter2( M, Metric ); \ + L = env +#define s3jni_mutex_leave(M, L) \ + assert( (void*)env == (void*)L && "Invalid use of " #L); \ + L = 0; \ + s3jni_mutex_leave2( M ) + +#define S3JniEnv_mutex_assertLocked \ + assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) +#define S3JniEnv_mutex_assertLocker \ + assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) +#define S3JniEnv_mutex_assertNotLocker \ + assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) + +#define S3JniEnv_mutex_enter \ + s3jni_mutex_enter( SJG.envCache.mutex, SJG.envCache.locker, nMutexEnv ) +#define S3JniEnv_mutex_leave \ + s3jni_mutex_leave( SJG.envCache.mutex, SJG.envCache.locker ) + +#define S3JniAutoExt_mutex_enter \ + s3jni_mutex_enter( SJG.autoExt.mutex, SJG.autoExt.locker, nMutexAutoExt ) +#define S3JniAutoExt_mutex_leave \ + s3jni_mutex_leave( SJG.autoExt.mutex, SJG.autoExt.locker ) +#define S3JniAutoExt_mutex_assertLocker \ + assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" ) + +#define S3JniGlobal_mutex_enter \ + s3jni_mutex_enter2( SJG.mutex, nMutexGlobal ) +#define S3JniGlobal_mutex_leave \ + s3jni_mutex_leave2( SJG.mutex ) + +#define S3JniHook_mutex_enter \ + s3jni_mutex_enter( SJG.hook.mutex, SJG.hook.locker, nMutexHook ) +#define S3JniHook_mutex_leave \ + s3jni_mutex_leave( SJG.hook.mutex, SJG.hook.locker ) + +#define S3JniNph_mutex_enter \ + s3jni_mutex_enter( SJG.nph.mutex, SJG.nph.locker, nMutexNph ) +#define S3JniNph_mutex_leave \ + s3jni_mutex_leave( SJG.nph.mutex, SJG.nph.locker ) + +#define S3JniDb_mutex_assertLocker \ + assert( (env) == SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) +#define S3JniDb_mutex_enter \ + s3jni_mutex_enter( SJG.perDb.mutex, SJG.perDb.locker, nMutexPerDb ) +#define S3JniDb_mutex_leave \ + s3jni_mutex_leave( SJG.perDb.mutex, SJG.perDb.locker ) + +#else /* SQLITE_THREADSAFE==0 */ +#define S3JniAutoExt_mutex_assertLocker +#define S3JniAutoExt_mutex_enter +#define S3JniAutoExt_mutex_leave +#define S3JniDb_mutex_assertLocker +#define S3JniDb_mutex_enter +#define S3JniDb_mutex_leave +#define S3JniEnv_mutex_assertLocked +#define S3JniEnv_mutex_assertLocker +#define S3JniEnv_mutex_assertNotLocker +#define S3JniEnv_mutex_enter +#define S3JniEnv_mutex_leave +#define S3JniGlobal_mutex_enter +#define S3JniGlobal_mutex_leave +#define S3JniHook_mutex_enter +#define S3JniHook_mutex_leave +#define S3JniNph_mutex_enter +#define S3JniNph_mutex_leave +#endif + +/* Helpers for jstring and jbyteArray. */ +static const char * s3jni__jstring_to_mutf8(JNIEnv * const env, jstring v ){ + const char *z = v ? (*env)->GetStringUTFChars(env, v, NULL) : 0; + s3jni_oom_check( v ? !!z : !z ); + return z; } -/** - sqlite3_malloc() proxy which fails fatally on OOM. This should - only be used for routines which manage global state and have no - recovery strategy for OOM. For sqlite3 API which can reasonably - return SQLITE_NOMEM, sqlite3_malloc() should be used instead. -*/ -static void * s3jni_malloc(JNIEnv * const env, size_t n){ - void * const rv = sqlite3_malloc(n); - if(n && !rv) s3jni_oom(env); +#define s3jni_jstring_to_mutf8(ARG) s3jni__jstring_to_mutf8(env, (ARG)) +#define s3jni_mutf8_release(ARG,VAR) if( VAR ) (*env)->ReleaseStringUTFChars(env, ARG, VAR) + +/* +** If jBA is not NULL then its GetByteArrayElements() value is +** returned. If jBA is not NULL and nBA is not NULL then *nBA is set +** to the GetArrayLength() of jBA. If GetByteArrayElements() requires +** an allocation and that allocation fails then this function either +** fails fatally or returns 0, depending on build-time options. + */ +static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsize * nBA ){ + jbyte * const rv = jBA ? (*env)->GetByteArrayElements(env, jBA, NULL) : 0; + s3jni_oom_check( jBA ? !!rv : 1 ); + if( jBA && nBA ) *nBA = (*env)->GetArrayLength(env, jBA); return rv; } -/** - Fetches the S3JniGlobal.envCache row for the given env, allocing - a row if needed. When a row is allocated, its state is initialized - insofar as possible. Calls (*env)->FatalError() if allocation of - an entry fails. That's hypothetically possible but "shouldn't happen." +#define s3jni_jbyteArray_bytes2(jByteArray,ptrToSz) \ + s3jni__jbyteArray_bytes2(env, (jByteArray), (ptrToSz)) +#define s3jni_jbyteArray_bytes(jByteArray) s3jni__jbyteArray_bytes2(env, (jByteArray), 0) +#define s3jni_jbyteArray_release(jByteArray,jBytes) \ + if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_ABORT) +#define s3jni_jbyteArray_commit(jByteArray,jBytes) \ + if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT) + +/* +** Returns the current JNIEnv object. Fails fatally if it cannot find +** the object. +*/ +static JNIEnv * s3jni_env(void){ + JNIEnv * env = 0; + if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env, + JNI_VERSION_1_8) ){ + fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n"); + abort(); + } + return env; +} + +/* +** Fetches the S3JniGlobal.envCache row for the given env, allocing a +** row if needed. When a row is allocated, its state is initialized +** insofar as possible. Calls (*env)->FatalError() if allocation of an +** entry fails. That's hypothetically possible but "shouldn't happen." */ -FIXME_THREADING(S3JniEnvCache) -static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ - struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; +static S3JniEnv * S3JniEnv__get(JNIEnv * const env){ + struct S3JniEnv * row; + S3JniEnv_mutex_enter; + row = SJG.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ - ++S3JniGlobal.metrics.envCacheHits; + s3jni_incr( &SJG.metrics.nEnvHit ); + S3JniEnv_mutex_leave; return row; } } - ++S3JniGlobal.metrics.envCacheMisses; - row = S3JniGlobal.envCache.aFree; + s3jni_incr( &SJG.metrics.nEnvMiss ); + row = SJG.envCache.aFree; if( row ){ - assert(!row->pPrev); - S3JniGlobal.envCache.aFree = row->pNext; - if( row->pNext ) row->pNext->pPrev = 0; + SJG.envCache.aFree = row->pNext; }else{ - row = sqlite3_malloc(sizeof(S3JniEnvCache)); - if( !row ){ - (*env)->FatalError(env, "Maintenance required: S3JniEnvCache is full.") - /* Does not return, but cc doesn't know that */; - return NULL; - } + row = s3jni_malloc_or_die(env, sizeof(*row)); + s3jni_incr( &SJG.metrics.nEnvAlloc ); } memset(row, 0, sizeof(*row)); - row->pNext = S3JniGlobal.envCache.aHead; - if(row->pNext) row->pNext->pPrev = row; - S3JniGlobal.envCache.aHead = row; + row->pNext = SJG.envCache.aHead; + SJG.envCache.aHead = row; row->env = env; - /* Grab references to various global classes and objects... */ - row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); - EXCEPTION_IS_FATAL("Error getting reference to Object class."); - - row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); - EXCEPTION_IS_FATAL("Error getting reference to Long class."); - row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, - "", "(J)V"); - EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); - - row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); - EXCEPTION_IS_FATAL("Error getting reference to String class."); - row->g.ctorStringBA = - (*env)->GetMethodID(env, row->g.cString, - "", "([BLjava/nio/charset/Charset;)V"); - EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); - row->g.stringGetBytes = - (*env)->GetMethodID(env, row->g.cString, - "getBytes", "(Ljava/nio/charset/Charset;)[B"); - EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset)."); - - { /* StandardCharsets.UTF_8 */ - jfieldID fUtf8; - jclass const klazzSC = - (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); - EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); - fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", - "Ljava/nio/charset/Charset;"); - EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); - row->g.oCharsetUtf8 = - REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); - EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); - } + S3JniEnv_mutex_leave; return row; } +#define S3JniEnv_get() S3JniEnv__get(env) + /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own Java/JNI bindings. ** ** For purposes of certain hand-crafted JNI function bindings, we ** need a way of reporting errors which is consistent with the rest of -** the C API, as opposed to throwing JS exceptions. To that end, this +** the C API, as opposed to throwing Java exceptions. To that end, this ** internal-use-only function is a thin proxy around ** sqlite3ErrorWithMessage(). The intent is that it only be used from ** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not @@ -638,813 +927,741 @@ static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ ** ** Returns err_code. */ -static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zMsg){ +static int s3jni_db_error(sqlite3* const db, int err_code, + const char * const zMsg){ if( db!=0 ){ if( 0==zMsg ){ sqlite3Error(db, err_code); }else{ const int nMsg = sqlite3Strlen30(zMsg); + sqlite3_mutex_enter(sqlite3_db_mutex(db)); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); } } return err_code; } -/** - Creates a new jByteArray of length nP, copies p's contents into it, and - returns that byte array. - */ -static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ +/* +** Creates a new jByteArray of length nP, copies p's contents into it, +** and returns that byte array (NULL on OOM unless fail-fast alloc +** errors are enabled). p may be NULL, in which case the array is +** created but no bytes are filled. +*/ +static jbyteArray s3jni__new_jbyteArray(JNIEnv * const env, + const void * const p, int nP){ jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); - if(jba){ + + s3jni_oom_check( jba ); + if( jba && p ){ (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); } return jba; } -/** - Uses the java.lang.String(byte[],Charset) constructor to create a - new String from UTF-8 string z. n is the number of bytes to - copy. If n<0 then sqlite3Strlen30() is used to calculate it. +#define s3jni_new_jbyteArray(P,n) s3jni__new_jbyteArray(env, P, n) - Returns NULL if z is NULL or on OOM, else returns a new jstring - owned by the caller. - Sidebar: this is a painfully inefficient way to convert from - standard UTF-8 to a Java string, but JNI offers only algorithms for - working with MUTF-8, not UTF-8. +/* +** Uses the java.lang.String(byte[],Charset) constructor to create a +** new String from UTF-8 string z. n is the number of bytes to +** copy. If n<0 then sqlite3Strlen30() is used to calculate it. +** +** Returns NULL if z is NULL or on OOM, else returns a new jstring +** owned by the caller. +** +** Sidebar: this is a painfully inefficient way to convert from +** standard UTF-8 to a Java string, but JNI offers only algorithms for +** working with MUTF-8, not UTF-8. */ -static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, - const char * const z, int n){ +static jstring s3jni__utf8_to_jstring(JNIEnv * const env, + const char * const z, int n){ jstring rv = NULL; - JNIEnv * const env = jc->env; if( 0==n || (n<0 && z && !z[0]) ){ /* Fast-track the empty-string case via the MUTF-8 API. We could hypothetically do this for any strings where n<4 and z is NUL-terminated and none of z[0..3] are NUL bytes. */ rv = (*env)->NewStringUTF(env, ""); + s3jni_oom_check( rv ); }else if( z ){ jbyteArray jba; if( n<0 ) n = sqlite3Strlen30(z); - jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n); + jba = s3jni_new_jbyteArray((unsigned const char *)z, n); if( jba ){ - rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA, - jba, jc->g.oCharsetUtf8); - UNREF_L(jba); + rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA, + jba, SJG.g.oCharsetUtf8); + S3JniIfThrew{ + S3JniExceptionReport; + S3JniExceptionClear; + } + S3JniUnrefLocal(jba); } + s3jni_oom_check( rv ); } return rv; } +#define s3jni_utf8_to_jstring(CStr,n) s3jni__utf8_to_jstring(env, CStr, n) -/** - Converts the given java.lang.String object into a NUL-terminated - UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). - Returns NULL if jstr is NULL or on allocation error. If jstr is not - NULL and nLen is not NULL then nLen is set to the length of the - returned string, not including the terminating NUL. If jstr is not - NULL and it returns NULL, this indicates an allocation error. In - that case, if nLen is not NULL then it is either set to 0 (if - fetching of jstr's bytes fails to allocate) or set to what would - have been the length of the string had C-string allocation - succeeded. - - The returned memory is allocated from sqlite3_malloc() and - ownership is transferred to the caller. +/* +** Converts the given java.lang.String object into a NUL-terminated +** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). +** Returns NULL if jstr is NULL or on allocation error. If jstr is not +** NULL and nLen is not NULL then nLen is set to the length of the +** returned string, not including the terminating NUL. If jstr is not +** NULL and it returns NULL, this indicates an allocation error. In +** that case, if nLen is not NULL then it is either set to 0 (if +** fetching of jstr's bytes fails to allocate) or set to what would +** have been the length of the string had C-string allocation +** succeeded. +** +** The returned memory is allocated from sqlite3_malloc() and +** ownership is transferred to the caller. */ -static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc, +static char * s3jni__jstring_to_utf8(JNIEnv * const env, jstring jstr, int *nLen){ - JNIEnv * const env = jc->env; jbyteArray jba; - jsize nBa; + jsize nBA; char *rv; - if(!jstr) return 0; - jba = (*env)->CallObjectMethod(env, jstr, jc->g.stringGetBytes, - jc->g.oCharsetUtf8); + if( !jstr ) return 0; + jba = (*env)->CallObjectMethod(env, jstr, SJG.g.stringGetBytes, + SJG.g.oCharsetUtf8); + if( (*env)->ExceptionCheck(env) || !jba /* order of these checks is significant for -Xlint:jni */ ) { - EXCEPTION_REPORT; + S3JniExceptionReport; + s3jni_oom_check( jba ); if( nLen ) *nLen = 0; return 0; } - nBa = (*env)->GetArrayLength(env, jba); - if( nLen ) *nLen = (int)nBa; - rv = sqlite3_malloc( nBa + 1 ); + nBA = (*env)->GetArrayLength(env, jba); + if( nLen ) *nLen = (int)nBA; + rv = s3jni_malloc( nBA + 1 ); if( rv ){ - (*env)->GetByteArrayRegion(env, jba, 0, nBa, (jbyte*)rv); - rv[nBa] = 0; + (*env)->GetByteArrayRegion(env, jba, 0, nBA, (jbyte*)rv); + rv[nBA] = 0; } - UNREF_L(jba); + S3JniUnrefLocal(jba); return rv; } +#define s3jni_jstring_to_utf8(JStr,n) s3jni__jstring_to_utf8(env, JStr, n) -/** - Expects to be passed a pointer from sqlite3_column_text16() or - sqlite3_value_text16() and a byte-length value from - sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a - Java String of exactly half that character length, returning NULL - if !p or (*env)->NewString() fails. +/* +** Expects to be passed a pointer from sqlite3_column_text16() or +** sqlite3_value_text16() and a byte-length value from +** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a +** Java String of exactly half that character length, returning NULL +** if !p or (*env)->NewString() fails. */ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ - return p + jstring const rv = p ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) : NULL; + s3jni_oom_check( p ? !!rv : 1 ); + return rv; } -/** - Requires jx to be a Throwable. Calls its toString() method and - returns its value converted to a UTF-8 string. The caller owns the - returned string and must eventually sqlite3_free() it. Returns 0 - if there is a problem fetching the info or on OOM. - - Design note: we use toString() instead of getMessage() because the - former includes the exception type's name: - - Exception e = new RuntimeException("Hi"); - System.out.println(e.toString()); // java.lang.RuntimeException: Hi - System.out.println(e.getMessage()); // Hi - } +/* +** Requires jx to be a Throwable. Calls its toString() method and +** returns its value converted to a UTF-8 string. The caller owns the +** returned string and must eventually sqlite3_free() it. Returns 0 +** if there is a problem fetching the info or on OOM. +** +** Design note: we use toString() instead of getMessage() because the +** former includes the exception type's name: +** +** Exception e = new RuntimeException("Hi"); +** System.out.println(e.toString()); // java.lang.RuntimeException: Hi +** System.out.println(e.getMessage()); // Hi */ -FIXME_THREADING(S3JniEnvCache) -static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); +static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx){ jmethodID mid; jstring msg; char * zMsg; jclass const klazz = (*env)->GetObjectClass(env, jx); mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;"); - IFTHREW{ - EXCEPTION_REPORT; - EXCEPTION_CLEAR; + S3JniUnrefLocal(klazz); + S3JniIfThrew{ + S3JniExceptionReport; + S3JniExceptionClear; return 0; } msg = (*env)->CallObjectMethod(env, jx, mid); - IFTHREW{ - EXCEPTION_REPORT; - EXCEPTION_CLEAR; + S3JniIfThrew{ + S3JniExceptionReport; + S3JniExceptionClear; return 0; } - zMsg = s3jni_jstring_to_utf8(jc, msg, 0); - UNREF_L(msg); + zMsg = s3jni_jstring_to_utf8( msg, 0); + S3JniUnrefLocal(msg); return zMsg; } -/** - Extracts the current JNI exception, sets ps->pDb's error message to - its message string, and clears the exception. If errCode is non-0, - it is used as-is, else SQLITE_ERROR is assumed. If there's a - problem extracting the exception's message, it's treated as - non-fatal and zDfltMsg is used in its place. - - This must only be called if a JNI exception is pending. - - Returns errCode unless it is 0, in which case SQLITE_ERROR is - returned. +/* +** Extracts env's current exception, sets ps->pDb's error message to +** its message string, and clears the exception. If errCode is non-0, +** it is used as-is, else SQLITE_ERROR is assumed. If there's a +** problem extracting the exception's message, it's treated as +** non-fatal and zDfltMsg is used in its place. +** +** Locks the global S3JniDb mutex. +** +** This must only be called if a JNI exception is pending. +** +** Returns errCode unless it is 0, in which case SQLITE_ERROR is +** returned. */ -static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, +static int s3jni__db_exception(JNIEnv * const env, sqlite3 * const pDb, int errCode, const char *zDfltMsg){ jthrowable const ex = (*env)->ExceptionOccurred(env); if( 0==errCode ) errCode = SQLITE_ERROR; if( ex ){ char * zMsg; - EXCEPTION_CLEAR; + S3JniExceptionClear; zMsg = s3jni_exception_error_msg(env, ex); - s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg); + s3jni_db_error(pDb, errCode, zMsg ? zMsg : zDfltMsg); sqlite3_free(zMsg); - UNREF_L(ex); + S3JniUnrefLocal(ex); + }else if( zDfltMsg ){ + s3jni_db_error(pDb, errCode, zDfltMsg); } - return errCode; + return errCode; } +#define s3jni_db_exception(pDb,ERRCODE,DFLTMSG) \ + s3jni__db_exception(env, (pDb), (ERRCODE), (DFLTMSG) ) -/** - Extracts the (void xDestroy()) method from the given jclass and - applies it to jobj. If jObj is NULL, this is a no-op. If klazz is - NULL then it's derived from jobj. The lack of an xDestroy() method - is silently ignored and any exceptions thrown by the method trigger - a warning to stdout or stderr and then the exception is suppressed. +/* +** Extracts the (void xDestroy()) method from jObj and applies it to +** jObj. If jObj is NULL, this is a no-op. The lack of an xDestroy() +** method is silently ignored. Any exceptions thrown by xDestroy() +** trigger a warning to stdout or stderr and then the exception is +** suppressed. */ -static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ - if(jObj){ - jmethodID method; - if(!klazz){ - klazz = (*env)->GetObjectClass(env, jObj); - assert(klazz); - } - method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); - //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); - if(method){ - ++S3JniGlobal.metrics.nDestroy; +static void s3jni__call_xDestroy(JNIEnv * const env, jobject jObj){ + if( jObj ){ + jclass const klazz = (*env)->GetObjectClass(env, jObj); + jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); + + S3JniUnrefLocal(klazz); + if( method ){ + s3jni_incr( &SJG.metrics.nDestroy ); (*env)->CallVoidMethod(env, jObj, method); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); - EXCEPTION_CLEAR; + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("xDestroy() callback"); + S3JniExceptionClear; } }else{ - EXCEPTION_CLEAR; + /* Non-fatal. */ + S3JniExceptionClear; } } } +#define s3jni_call_xDestroy(JOBJ) s3jni__call_xDestroy(env, (JOBJ)) -/** - Removes any Java references from s and clears its state. If - doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's - s is passed to s3jni_call_xDestroy() before any references are - cleared. It is legal to call this when the object has no Java - references. +/* +** Internal helper for many hook callback impls. Locks the S3JniDb +** mutex, makes a copy of src into dest, with a some differences: (1) +** if src->jObj or src->jExtra are not NULL then dest will be a new +** LOCAL ref to it instead of a copy of the prior GLOBAL ref. (2) +** dest->doXDestroy is always false. +** +** If dest->jObj is not NULL when this returns then the caller is +** obligated to eventually free the new ref by passing *dest to +** S3JniHook_localundup(). The dest pointer must NOT be passed to +** S3JniHook_unref(), as that routine assumes that dest->jObj/jExtra +** are GLOBAL refs (it's illegal to try to unref the wrong ref type). +** +** Background: when running a hook we need a call-local copy lest +** another thread modify the hook while we're running it. That copy +** has to have its own Java reference, but it need only be call-local. */ -static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){ - if(doXDestroy && s->klazz && s->jObj){ - s3jni_call_xDestroy(env, s->jObj, s->klazz); - } - UNREF_G(s->jObj); - UNREF_G(s->klazz); - memset(s, 0, sizeof(*s)); +static void S3JniHook__localdup( JNIEnv * const env, S3JniHook const * const src, + S3JniHook * const dest ){ + S3JniHook_mutex_enter; + *dest = *src; + if(src->jObj) dest->jObj = S3JniRefLocal(src->jObj); + if(src->jExtra) dest->jExtra = S3JniRefLocal(src->jExtra); + dest->doXDestroy = 0; + S3JniHook_mutex_leave; } +#define S3JniHook_localdup(src,dest) S3JniHook__localdup(env,src,dest) -/** - Clears s's state and moves it to the free-list. -*/ -FIXME_THREADING(perDb) -static void S3JniDb_set_aside(S3JniDb * const s){ - if(s){ - JNIEnv * const env = s->env; - assert(s->pDb && "Else this object is already in the free-list."); - //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); - assert(s->pPrev != s); - assert(s->pNext != s); - assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); - if(s->pNext) s->pNext->pPrev = s->pPrev; - if(s->pPrev) s->pPrev->pNext = s->pNext; - else if(S3JniGlobal.perDb.aUsed == s){ - assert(!s->pPrev); - S3JniGlobal.perDb.aUsed = s->pNext; - } - sqlite3_free( s->zMainDbName ); -#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY) - UNHOOK(trace, 0); - UNHOOK(progress, 0); - UNHOOK(commitHook, 0); - UNHOOK(rollbackHook, 0); - UNHOOK(updateHook, 0); - UNHOOK(authHook, 0); - UNHOOK(collation, 1); - UNHOOK(collationNeeded, 1); - UNHOOK(busyHandler, 1); -#undef UNHOOK - UNREF_G(s->jDb); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(s->jFtsApi); -#endif - memset(s, 0, sizeof(S3JniDb)); - s->pNext = S3JniGlobal.perDb.aFree; - if(s->pNext) s->pNext->pPrev = s; - S3JniGlobal.perDb.aFree = s; - //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); - //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); - } +static void S3JniHook__localundup( JNIEnv * const env, S3JniHook * const h ){ + S3JniUnrefLocal(h->jObj); + S3JniUnrefLocal(h->jExtra); + *h = S3JniHook_empty; } +#define S3JniHook_localundup(HOOK) S3JniHook__localundup(env, &(HOOK)) -/** - Requires that p has been snipped from any linked list it is - in. Clears all Java refs p holds and zeroes out p. +/* +** Removes any Java references from s and clears its state. If +** doXDestroy is true and s->jObj is not NULL, s->jObj +** is passed to s3jni_call_xDestroy() before any references are +** cleared. It is legal to call this when the object has no Java +** references. s must not be NULL. */ -static void S3JniEnvCache_clear(S3JniEnvCache * const p){ - JNIEnv * const env = p->env; - if(env){ - int i; - UNREF_G(p->g.cObj); - UNREF_G(p->g.cLong); - UNREF_G(p->g.cString); - UNREF_G(p->g.oCharsetUtf8); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(p->jFtsExt); - UNREF_G(p->jPhraseIter.klazz); -#endif - for( i = 0; i < NphCache_SIZE; ++i ){ - S3JniNphCache_clear(env, &p->nph[i]); +static void S3JniHook__unref(JNIEnv * const env, S3JniHook * const s){ + if( s->jObj ){ + if( s->doXDestroy ){ + s3jni_call_xDestroy(s->jObj); } - memset(p, 0, sizeof(S3JniEnvCache)); + S3JniUnrefGlobal(s->jObj); + S3JniUnrefGlobal(s->jExtra); + }else{ + assert( !s->jExtra ); } + *s = S3JniHook_empty; } +#define S3JniHook_unref(hook) S3JniHook__unref(env, (hook)) -/** - Cleans up all state in S3JniGlobal.perDb for th given JNIEnv. - Results are undefined if a Java-side db uses the API - from the given JNIEnv after this call. +/* +** Allocates one blank S3JniHook object from the recycling bin, if +** available, else from the heap. Returns NULL or dies on OOM, +** depending on build options. Locks on SJG.hooks.mutex. */ -FIXME_THREADING(perDb) -static void S3JniDb_free_for_env(JNIEnv *env){ - S3JniDb * ps = S3JniGlobal.perDb.aUsed; - S3JniDb * pNext = 0; - for( ; ps; ps = pNext ){ - pNext = ps->pNext; - if(ps->env == env){ - S3JniDb * const pPrev = ps->pPrev; - S3JniDb_set_aside(ps); - assert(pPrev ? pPrev->pNext!=ps : 1); - pNext = pPrev; +static S3JniHook *S3JniHook__alloc(JNIEnv * const env){ + S3JniHook * p = 0; + S3JniHook_mutex_enter; + if( SJG.hook.aFree ){ + p = SJG.hook.aFree; + SJG.hook.aFree = p->pNext; + p->pNext = 0; + s3jni_incr(&SJG.metrics.nHookRecycled); + } + S3JniHook_mutex_leave; + if( 0==p ){ + p = s3jni_malloc(sizeof(S3JniHook)); + if( p ){ + s3jni_incr(&SJG.metrics.nHookAlloc); } } + if( p ){ + *p = S3JniHook_empty; + } + return p; } +#define S3JniHook_alloc() S3JniHook__alloc(env) -/** - Uncache any state for the given JNIEnv, clearing all Java - references the cache owns. Returns true if env was cached and false - if it was not found in the cache. - - Also passes env to S3JniDb_free_for_env() to free up - what would otherwise be stale references. +/* +** The rightful fate of all results from S3JniHook_alloc(). Locks on +** SJG.hook.mutex. */ -static int S3JniGlobal_env_uncache(JNIEnv * const env){ - struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; - for( ; row; row = row->pNext ){ - if( row->env == env ){ - break; - } +static void S3JniHook__free(JNIEnv * const env, S3JniHook * const p){ + if(p){ + assert( !p->pNext ); + S3JniHook_unref(p); + S3JniHook_mutex_enter; + p->pNext = SJG.hook.aFree; + SJG.hook.aFree = p; + S3JniHook_mutex_leave; } - if( !row ) return 0; - if( row->pNext ) row->pNext->pPrev = row->pPrev; - if( row->pPrev ) row->pPrev->pNext = row->pNext; - if( S3JniGlobal.envCache.aHead == row ){ - assert( !row->pPrev ); - S3JniGlobal.envCache.aHead = row->pNext; - } - S3JniEnvCache_clear(row); - assert( !row->pNext ); - assert( !row->pPrev ); - row->pNext = S3JniGlobal.envCache.aFree; - if( row->pNext ) row->pNext->pPrev = row; - S3JniGlobal.envCache.aFree = row; - S3JniDb_free_for_env(env); - return 1; } +#define S3JniHook_free(hook) S3JniHook__free(env, hook) -static void S3JniGlobal_S3JniEnvCache_clear(void){ - while( S3JniGlobal.envCache.aHead ){ - S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); +#if 0 +/* S3JniHook__free() without the lock: caller must hold the global mutex */ +static void S3JniHook__free_unlocked(JNIEnv * const env, S3JniHook * const p){ + if(p){ + assert( !p->pNext ); + assert( p->pNext != SJG.hook.aFree ); + S3JniHook_unref(p); + p->pNext = SJG.hook.aFree; + SJG.hook.aFree = p; } } +#define S3JniHook_free_unlocked(hook) S3JniHook__free_unlocked(env, hook) +#endif -/** - Searches the NativePointerHolder cache for the given combination. - If it finds one, it returns it as-is. If it doesn't AND the cache - has a free slot, it populates that slot with (env, zClassName, - klazz) and returns it. If the cache is full with no match it - returns NULL. - - It is up to the caller to populate the other members of the returned - object if needed. - - zClassName must be a static string so we can use its address as a - cache key. - - This simple cache catches >99% of searches in the current - (2023-07-31) tests. +/* +** Clears all of s's state. Requires that that the caller has locked +** S3JniGlobal.perDb.mutex. Make sure to do anything needed with +** s->pNext and s->pPrev before calling this, as this clears them. */ -FIXME_THREADING(S3JniEnvCache) -static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ - /** - According to: - - https://developer.ibm.com/articles/j-jni/ - - > ... the IDs returned for a given class don't change for the - lifetime of the JVM process. But the call to get the field or - method can require significant work in the JVM, because - fields and methods might have been inherited from - superclasses, making the JVM walk up the class hierarchy to - find them. Because the IDs are the same for a given class, - you should look them up once and then reuse them. Similarly, - looking up class objects can be expensive, so they should be - cached as well. - */ - struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); - S3JniNphCache * freeSlot = 0; - S3JniNphCache * pCache = 0; - int i; - assert(envRow); - for( i = 0; i < NphCache_SIZE; ++i ){ - pCache = &envRow->nph[i]; - if(zClassName == pCache->zClassName){ - ++S3JniGlobal.metrics.nphCacheHits; -#define DUMP_NPH_CACHES 0 -#if DUMP_NPH_CACHES - MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3JniGlobal.metrics.nphCacheHits, zClassName, pCache->klazz, pCache->fidValue, - pCache->midCtor)); -#endif - assert(pCache->klazz); - return pCache; - }else if(!freeSlot && !pCache->zClassName){ - freeSlot = pCache; - } - } - if(freeSlot){ - freeSlot->zClassName = zClassName; - freeSlot->klazz = (*env)->FindClass(env, zClassName); - EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); - freeSlot->klazz = REF_G(freeSlot->klazz); - ++S3JniGlobal.metrics.nphCacheMisses; -#if DUMP_NPH_CACHES - static unsigned int cacheMisses = 0; - MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3JniGlobal.metrics.nphCacheMisses, zClassName, freeSlot->klazz, - freeSlot->fidValue, freeSlot->midCtor)); +static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){ + S3JniDb_mutex_assertLocker; + sqlite3_free( s->zMainDbName ); +#define UNHOOK(MEMBER) \ + S3JniHook_unref(&s->hooks.MEMBER) + UNHOOK(auth); + UNHOOK(busyHandler); + UNHOOK(collationNeeded); + UNHOOK(commit); + UNHOOK(progress); + UNHOOK(rollback); + UNHOOK(trace); + UNHOOK(update); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + UNHOOK(preUpdate); #endif -#undef DUMP_NPH_CACHES - }else{ - (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low."); +#undef UNHOOK + S3JniUnrefGlobal(s->jDb); + memset(s, 0, sizeof(S3JniDb)); +} + +/* +** Clears s's state and moves it to the free-list. Requires that +** S3JniGlobal.perDb.mutex is locked. +*/ +static void S3JniDb__set_aside_unlocked(JNIEnv * const env, S3JniDb * const s){ + assert( s ); + S3JniDb_mutex_assertLocker; + if( s ){ + S3JniDb_clear(env, s); + s->pNext = SJG.perDb.aFree; + SJG.perDb.aFree = s; } - return freeSlot; } +#define S3JniDb_set_aside_unlocked(JniDb) S3JniDb__set_aside_unlocked(env, JniDb) -/** - Returns the ID of the "nativePointer" field from the given - NativePointerHolder class. - */ -static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ - jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); - EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); - return rv; +static void S3JniDb__set_aside(JNIEnv * const env, S3JniDb * const s){ + S3JniDb_mutex_enter; + S3JniDb_set_aside_unlocked(s); + S3JniDb_mutex_leave; } +#define S3JniDb_set_aside(JNIDB) S3JniDb__set_aside(env, JNIDB) -/** - Sets a native ptr value in NativePointerHolder object ppOut. - zClassName must be a static string so we can use its address - as a cache key. +/* +** Uncache any state for the given JNIEnv, clearing all Java +** references the cache owns. Returns true if env was cached and false +** if it was not found in the cache. Ownership of the S3JniEnv object +** associated with the given argument is transferred to this function, +** which makes it free for re-use. +** +** Requires that the env mutex be locked. */ -static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, - const char *zClassName){ - jfieldID setter = 0; - S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->klazz && pCache->fidValue){ - assert(zClassName == pCache->zClassName); - setter = pCache->fidValue; - assert(setter); - }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut); - setter = NativePointerHolder_getField(env, klazz); - if(pCache){ - assert(pCache->klazz); - assert(!pCache->fidValue); - assert(zClassName == pCache->zClassName); - pCache->fidValue = setter; +static int S3JniEnv_uncache(JNIEnv * const env){ + struct S3JniEnv * row; + struct S3JniEnv * pPrev = 0; + + S3JniEnv_mutex_assertLocked; + row = SJG.envCache.aHead; + for( ; row; pPrev = row, row = row->pNext ){ + if( row->env == env ){ + break; } } - (*env)->SetLongField(env, ppOut, setter, (jlong)p); - EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); + if( !row ){ + return 0; + } + if( pPrev) pPrev->pNext = row->pNext; + else{ + assert( SJG.envCache.aHead == row ); + SJG.envCache.aHead = row->pNext; + } + memset(row, 0, sizeof(S3JniEnv)); + row->pNext = SJG.envCache.aFree; + SJG.envCache.aFree = row; + return 1; } -/** - Fetches a native ptr value from NativePointerHolder object ppOut. - zClassName must be a static string so we can use its address as a - cache key. +/* +** Fetches the given nph-ref from cache the cache and returns the +** object with its klazz member set. This is an O(1) operation except +** on the first call for a given pRef, during which pRef->klazz and +** pRef->pRef are initialized thread-safely. In the latter case it's +** still effectively O(1), but with a much longer 1. +** +** It is up to the caller to populate the other members of the +** returned object if needed, taking care to lock the modification +** with S3JniNph_mutex_enter/leave. +** +** This simple cache catches >99% of searches in the current +** (2023-07-31) tests. */ -static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){ - if( pObj ){ - jfieldID getter = 0; - void * rv = 0; - S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->fidValue){ - getter = pCache->fidValue; - }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, pObj); - getter = NativePointerHolder_getField(env, klazz); - if(pCache){ - assert(pCache->klazz); - assert(zClassName == pCache->zClassName); - pCache->fidValue = getter; - } +static S3JniNphOp * s3jni__nphop(JNIEnv * const env, S3JniNphOp const* pRef){ + S3JniNphOp * const pNC = &SJG.nph.list[pRef->index]; + + assert( (void*)pRef>=(void*)&S3JniNphOps && (void*)pRef<(void*)(&S3JniNphOps + 1) + && "pRef is out of range" ); + assert( pRef->index>=0 + && (pRef->index < (sizeof(S3JniNphOps) / sizeof(S3JniNphOp))) + && "pRef->index is out of range" ); + if( !pNC->klazz ){ + S3JniNph_mutex_enter; + if( !pNC->klazz ){ + jclass const klazz = (*env)->FindClass(env, pRef->zName); + //printf("FindClass %s\n", pRef->zName); + S3JniExceptionIsFatal("FindClass() unexpectedly threw"); + pNC->klazz = S3JniRefGlobal(klazz); } - rv = (void*)(*env)->GetLongField(env, pObj, getter); - IFTHREW_REPORT; - return rv; - }else{ - return 0; + S3JniNph_mutex_leave; } + assert( pNC->klazz ); + return pNC; } -/** - Extracts the new S3JniDb instance from the free-list, or - allocates one if needed, associats it with pDb, and returns. - Returns NULL on OOM. pDb MUST be associated with jDb via - NativePointerHolder_set(). +#define s3jni_nphop(PRef) s3jni__nphop(env, PRef) + +/* +** Common code for accessor functions for NativePointerHolder and +** OutputPointer types. pRef must be a pointer from S3JniNphOps. jOut +** must be an instance of that class (Java's type safety takes care of +** that requirement). If necessary, this fetches the jfieldID for +** jOut's pRef->zMember, which must be of the type represented by the +** JNI type signature pRef->zTypeSig, and stores it in +** S3JniGlobal.nph.list[pRef->index]. Fails fatally if the pRef->zMember +** property is not found, as that presents a serious internal misuse. +** +** Property lookups are cached on a per-pRef basis. */ -static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, - jobject jDb){ - S3JniDb * rv; - if(S3JniGlobal.perDb.aFree){ - rv = S3JniGlobal.perDb.aFree; - //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); - //MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext)); - S3JniGlobal.perDb.aFree = rv->pNext; - assert(rv->pNext != rv); - assert(rv->pPrev != rv); - assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1); - if(rv->pNext){ - assert(rv->pNext->pPrev == rv); - assert(rv->pPrev ? (rv->pNext == rv->pPrev->pNext) : 1); - rv->pNext->pPrev = 0; - rv->pNext = 0; - } - }else{ - rv = s3jni_malloc(env, sizeof(S3JniDb)); - //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb)); - if(rv){ - memset(rv, 0, sizeof(S3JniDb)); +static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphOp const* pRef){ + S3JniNphOp * const pNC = s3jni_nphop(pRef); + + if( !pNC->fidValue ){ + S3JniNph_mutex_enter; + if( !pNC->fidValue ){ + pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, + pRef->zMember, pRef->zTypeSig); + S3JniExceptionIsFatal("Code maintenance required: missing " + "required S3JniNphOp::fidValue."); } + S3JniNph_mutex_leave; } - if(rv){ - rv->pNext = S3JniGlobal.perDb.aUsed; - S3JniGlobal.perDb.aUsed = rv; - if(rv->pNext){ - assert(!rv->pNext->pPrev); - rv->pNext->pPrev = rv; - } - rv->jDb = REF_G(jDb); - rv->pDb = pDb; - rv->env = env; + assert( pNC->fidValue ); + return pNC->fidValue; +} + +/* +** Sets a native ptr value in NativePointerHolder object jNph, +** which must be of the native type described by pRef. jNph +** may not be NULL. +*/ +static void NativePointerHolder__set(JNIEnv * const env, S3JniNphOp const* pRef, + jobject jNph, const void * p){ + assert( jNph ); + (*env)->SetLongField(env, jNph, s3jni_nphop_field(env, pRef), + S3JniCast_P2L(p)); + S3JniExceptionIsFatal("Could not set NativePointerHolder.nativePointer."); +} + +#define NativePointerHolder_set(PREF,JNPH,P) \ + NativePointerHolder__set(env, PREF, JNPH, P) + +/* +** Fetches a native ptr value from NativePointerHolder object jNph, +** which must be of the native type described by pRef. This is a +** no-op if jNph is NULL. +*/ +static void * NativePointerHolder__get(JNIEnv * env, jobject jNph, + S3JniNphOp const* pRef){ + void * rv = 0; + if( jNph ){ + rv = S3JniCast_L2P( + (*env)->GetLongField(env, jNph, s3jni_nphop_field(env, pRef)) + ); + S3JniExceptionIsFatal("Cannot fetch NativePointerHolder.nativePointer."); } return rv; } -#if 0 -static void S3JniDb_dump(S3JniDb *s){ - MARKER(("S3JniDb->env @ %p\n", s->env)); - MARKER(("S3JniDb->pDb @ %p\n", s->pDb)); - MARKER(("S3JniDb->trace.jObj @ %p\n", s->trace.jObj)); - MARKER(("S3JniDb->progress.jObj @ %p\n", s->progress.jObj)); - MARKER(("S3JniDb->commitHook.jObj @ %p\n", s->commitHook.jObj)); - MARKER(("S3JniDb->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); - MARKER(("S3JniDb->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); - MARKER(("S3JniDb->env @ %p\n", s->env)); -} -#endif +#define NativePointerHolder_get(JOBJ,NPHREF) \ + NativePointerHolder__get(env, (JOBJ), (NPHREF)) -/** - Returns the S3JniDb object for the given db. If allocIfNeeded is - true then a new instance will be allocated if no mapping currently - exists, else NULL is returned if no mapping is found. - - The 3rd and 4th args should normally only be non-0 for - sqlite3_open(_v2)(). For most other cases, they must be 0, in which - case the db handle will be fished out of the jDb object and NULL is - returned if jDb does not have any associated S3JniDb. - - If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST - be false and it will look for a matching db object. That usage is - required for functions, like sqlite3_context_db_handle(), which - return a (sqlite3*) but do not take one as an argument. +/* +** Helpers for extracting pointers from jobjects, noting that we rely +** on the corresponding Java interfaces having already done the +** type-checking. OBJ must be a jobject referring to a +** NativePointerHolder, where T matches PtrGet_T. Don't use these +** in contexts where that's not the case. Note that these aren't +** type-safe in the strictest sense: +** +** sqlite3 * s = PtrGet_sqlite3_stmt(...) +** +** will work, despite the incorrect macro name, so long as the +** argument is a Java sqlite3 object, as this operation only has void +** pointers to work with. */ -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, - sqlite3 *pDb, int allocIfNeeded){ - S3JniDb * s = S3JniGlobal.perDb.aUsed; - if(!jDb){ - if(pDb){ - assert(!allocIfNeeded); - }else{ - return 0; +#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T)) +#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ) +#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ) +#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ) +#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ) +#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ) +#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ) +/* +** S3JniLongPtr_T(X,Y) expects X to be an unqualified sqlite3 struct +** type name and Y to be a native pointer to such an object in the +** form of a jlong value. The jlong is simply cast to (X*). This +** approach is, as of 2023-09-27, supplanting the former approach. We +** now do the native pointer extraction in the Java side, rather than +** the C side, because it's reportedly significantly faster. The +** intptr_t part here is necessary for compatibility with (at least) +** ARM32. +*/ +#define S3JniLongPtr_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr)) +#define S3JniLongPtr_sqlite3(JLongAsPtr) S3JniLongPtr_T(sqlite3,JLongAsPtr) +#define S3JniLongPtr_sqlite3_backup(JLongAsPtr) S3JniLongPtr_T(sqlite3_backup,JLongAsPtr) +#define S3JniLongPtr_sqlite3_blob(JLongAsPtr) S3JniLongPtr_T(sqlite3_blob,JLongAsPtr) +#define S3JniLongPtr_sqlite3_stmt(JLongAsPtr) S3JniLongPtr_T(sqlite3_stmt,JLongAsPtr) +#define S3JniLongPtr_sqlite3_value(JLongAsPtr) S3JniLongPtr_T(sqlite3_value,JLongAsPtr) +/* +** Extracts the new S3JniDb instance from the free-list, or allocates +** one if needed, associates it with pDb, and returns. Returns NULL +** on OOM. The returned object MUST, on success of the calling +** operation, subsequently be associated with jDb via +** NativePointerHolder_set() or freed using S3JniDb_set_aside(). +*/ +static S3JniDb * S3JniDb_alloc(JNIEnv * const env, jobject jDb){ + S3JniDb * rv = 0; + S3JniDb_mutex_enter; + if( SJG.perDb.aFree ){ + rv = SJG.perDb.aFree; + SJG.perDb.aFree = rv->pNext; + rv->pNext = 0; + s3jni_incr( &SJG.metrics.nPdbRecycled ); + } + S3JniDb_mutex_leave; + if( 0==rv ){ + rv = s3jni_malloc(sizeof(S3JniDb)); + if( rv ){ + s3jni_incr( &SJG.metrics.nPdbAlloc ); } } - assert(allocIfNeeded ? !!pDb : 1); - if(!allocIfNeeded && !pDb){ - pDb = PtrGet_sqlite3(jDb); - } - for( ; pDb && s; s = s->pNext){ - if(s->pDb == pDb) return s; - } - if(allocIfNeeded){ - s = S3JniDb_alloc(env, pDb, jDb); + if( rv ){ + memset(rv, 0, sizeof(S3JniDb)); + rv->jDb = S3JniRefGlobal(jDb); } - return s; + return rv; } -#if 0 -/** - An alternative form which searches for the S3JniDb instance for - pDb with no JNIEnv-specific info. This can be (but _should_ it be?) - called from the context of a separate JNIEnv than the one mapped - to in the returned object. Returns 0 if no match is found. +/* +** Returns the S3JniDb object for the given org.sqlite.jni.capi.sqlite3 +** object, or NULL if jDb is NULL, no pointer can be extracted +** from it, or no matching entry can be found. */ -FIXME_THREADING(perDb) -static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){ - S3JniDb * s = S3JniGlobal.perDb.aUsed; - for( ; pDb && s; s = s->pNext){ - if(s->pDb == pDb) return s; - } - return 0; +static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){ + sqlite3 * const pDb = jDb ? PtrGet_sqlite3(jDb) : 0; + return pDb ? S3JniDb_from_clientdata(pDb) : 0; } -#endif +#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject)) -#if S3JNI_ENABLE_AUTOEXT -/** - Unlink ax from S3JniGlobal.autoExt and free it. +/* +** S3JniDb finalizer for use with sqlite3_set_clientdata(). */ -static void S3JniAutoExtension_free(JNIEnv * const env, - S3JniAutoExtension * const ax){ - if( ax ){ - if( ax->pNext ) ax->pNext->pPrev = ax->pPrev; - if( ax == S3JniGlobal.autoExt.pHead ){ - assert( !ax->pNext ); - S3JniGlobal.autoExt.pHead = ax->pNext; - }else if( ax->pPrev ){ - ax->pPrev->pNext = ax->pNext; - } - ax->pNext = ax->pPrev = 0; - UNREF_G(ax->jObj); - sqlite3_free(ax); - } +static void S3JniDb_xDestroy(void *p){ + S3JniDeclLocal_env; + S3JniDb * const ps = p; + assert( !ps->pNext && "Else ps is already in the free-list."); + S3JniDb_set_aside(ps); } -/** - Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt. - Returns 0 on OOM or if there is an error collecting the required - state from jAutoExt (which must be an AutoExtension object). +/* +** Evaluates to the S3JniDb object for the given sqlite3 object, or +** NULL if pDb is NULL or was not initialized via the JNI interfaces. */ -static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, - jobject const jAutoExt){ - S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax)); - if( ax ){ - jclass klazz; - memset(ax, 0, sizeof(*ax)); - klazz = (*env)->GetObjectClass(env, jAutoExt); - if(!klazz){ - S3JniAutoExtension_free(env, ax); - return 0; - } - ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", - "(Lorg/sqlite/jni/sqlite3;)I"); - if(!ax->midFunc){ - MARKER(("Error getting xEntryPoint(sqlite3) from object.")); - S3JniAutoExtension_free(env, ax); - return 0; - } - ax->jObj = REF_G(jAutoExt); - ax->pNext = S3JniGlobal.autoExt.pHead; - if( ax->pNext ) ax->pNext->pPrev = ax; - S3JniGlobal.autoExt.pHead = ax; - } - return ax; -} -#endif /* S3JNI_ENABLE_AUTOEXT */ +#define S3JniDb_from_c(sqlite3Ptr) \ + ((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0) +#define S3JniDb_from_jlong(sqlite3PtrAsLong) \ + S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong)) -/** - Requires that jCx be a Java-side sqlite3_context wrapper for pCx. - This function calls sqlite3_aggregate_context() to allocate a tiny - sliver of memory, the address of which is set in - jCx->aggregateContext. The memory is only used as a key for - mapping client-side results of aggregate result sets across - calls to the UDF's callbacks. - - isFinal must be 1 for xFinal() calls and 0 for all others, the - difference being that the xFinal() invocation will not allocate - new memory if it was not already, resulting in a value of 0 - for jCx->aggregateContext. - - Returns 0 on success. Returns SQLITE_NOMEM on allocation error, - noting that it will not allocate when isFinal is true. It returns - SQLITE_ERROR if there's a serious internal error in dealing with - the JNI state. +/* +** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out +** AX. */ -static int udf_setAggregateContext(JNIEnv * env, jobject jCx, - sqlite3_context * pCx, - int isFinal){ - jfieldID member; - void * pAgg; - int rc = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context); - if(pCache && pCache->klazz && pCache->fidSetAgg){ - member = pCache->fidSetAgg; - assert(member); - }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx); - member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J"); - if( !member ){ - IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; } - return s3jni_db_error(sqlite3_context_db_handle(pCx), - SQLITE_ERROR, - "Internal error: cannot find " - "sqlite3_context::aggregateContext field."); - } - if(pCache){ - assert(pCache->klazz); - assert(!pCache->fidSetAgg); - pCache->fidSetAgg = member; - } - } - pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); - if( pAgg || isFinal ){ - (*env)->SetLongField(env, jCx, member, (jlong)pAgg); - }else{ - assert(!pAgg); - rc = SQLITE_NOMEM; - } - return rc; -} +#define S3JniAutoExtension_clear(AX) S3JniHook_unref(AX); -/** - Common init for OutputPointer_set_Int32() and friends. zClassName must be a - pointer from S3JniClassNames. jOut must be an instance of that - class. Fetches the jfieldID for jOut's [value] property, which must - be of the type represented by the JNI type signature zTypeSig, and - stores it in pFieldId. Fails fatally if the property is not found, - as that presents a serious internal misuse. - - Property lookups are cached on a per-zClassName basis. Do not use - this routine with the same zClassName but different zTypeSig: it - will misbehave. +/* +** Initializes a pre-allocated S3JniAutoExtension object. Returns +** non-0 if there is an error collecting the required state from +** jAutoExt (which must be an AutoExtensionCallback object). On error, +** it passes ax to S3JniAutoExtension_clear(). */ -static void setupOutputPointer(JNIEnv * const env, const char *zClassName, - const char * const zTypeSig, - jobject const jOut, jfieldID * const pFieldId){ - jfieldID setter = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->klazz && pCache->fidValue){ - setter = pCache->fidValue; - }else{ - const jclass klazz = (*env)->GetObjectClass(env, jOut); - /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/ - setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); - EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); - if(pCache){ - assert(!pCache->fidValue); - pCache->fidValue = setter; - } +static int S3JniAutoExtension_init(JNIEnv *const env, + S3JniAutoExtension * const ax, + jobject const jAutoExt){ + jclass const klazz = (*env)->GetObjectClass(env, jAutoExt); + + S3JniAutoExt_mutex_assertLocker; + *ax = S3JniHook_empty; + ax->midCallback = (*env)->GetMethodID(env, klazz, "call", + "(Lorg/sqlite/jni/capi/sqlite3;)I"); + S3JniUnrefLocal(klazz); + S3JniExceptionWarnIgnore; + if( !ax->midCallback ){ + S3JniAutoExtension_clear(ax); + return SQLITE_ERROR; } - *pFieldId = setter; + ax->jObj = S3JniRefGlobal(jAutoExt); + return 0; } -/* Sets the value property of the OutputPointer.Int32 jOut object - to v. */ -static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_Int32, "I", jOut, &setter); - (*env)->SetIntField(env, jOut, setter, (jint)v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); +/* +** Sets the value property of the OutputPointer.Bool jOut object to +** v. +*/ +static void OutputPointer_set_Bool(JNIEnv * const env, jobject const jOut, + int v){ + (*env)->SetBooleanField(env, jOut, s3jni_nphop_field( + env, S3JniNph(OutputPointer_Bool) + ), v ? JNI_TRUE : JNI_FALSE ); + S3JniExceptionIsFatal("Cannot set OutputPointer.Bool.value"); } -/* Sets the value property of the OutputPointer.Int64 jOut object - to v. */ -static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); - (*env)->SetLongField(env, jOut, setter, v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); +/* +** Sets the value property of the OutputPointer.Int32 jOut object to +** v. +*/ +static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, + int v){ + (*env)->SetIntField(env, jOut, s3jni_nphop_field( + env, S3JniNph(OutputPointer_Int32) + ), (jint)v); + S3JniExceptionIsFatal("Cannot set OutputPointer.Int32.value"); } -static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, - jobject jDb){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3, - "Lorg/sqlite/jni/sqlite3;", jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, jDb); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); +/* +** Sets the value property of the OutputPointer.Int64 jOut object to +** v. +*/ +static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, + jlong v){ + (*env)->SetLongField(env, jOut, s3jni_nphop_field( + env, S3JniNph(OutputPointer_Int64) + ), v); + S3JniExceptionIsFatal("Cannot set OutputPointer.Int64.value"); } -static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, - jobject jStmt){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3_stmt, - "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, jStmt); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); +/* +** Internal helper for OutputPointer_set_TYPE() where TYPE is an +** Object type. +*/ +static void OutputPointer_set_obj(JNIEnv * const env, + S3JniNphOp const * const pRef, + jobject const jOut, + jobject v){ + (*env)->SetObjectField(env, jOut, s3jni_nphop_field(env, pRef), v); + S3JniExceptionIsFatal("Cannot set OutputPointer.T.value"); } #ifdef SQLITE_ENABLE_FTS5 #if 0 -/* Sets the value property of the OutputPointer.ByteArray jOut object - to v. */ +/* +** Sets the value property of the OutputPointer.ByteArray jOut object +** to v. +*/ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, - jbyteArray const v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_ByteArray, "[B", - jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); + jbyteArray const v){ + OutputPointer_set_obj(env, S3JniNph(OutputPointer_ByteArray), jOut, v); } #endif -/* Sets the value property of the OutputPointer.String jOut object - to v. */ +#endif /* SQLITE_ENABLE_FTS5 */ + +/* +** Sets the value property of the OutputPointer.String jOut object to +** v. +*/ static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, - jstring const v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_String, - "Ljava/lang/String;", jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); + jstring const v){ + OutputPointer_set_obj(env, S3JniNph(OutputPointer_String), jOut, v); } -#endif /* SQLITE_ENABLE_FTS5 */ +/* +** Returns true if eTextRep is a valid sqlite3 encoding constant, else +** returns false. +*/ static int encodingTypeIsValid(int eTextRep){ - switch(eTextRep){ + switch( eTextRep ){ case SQLITE_UTF8: case SQLITE_UTF16: case SQLITE_UTF16LE: case SQLITE_UTF16BE: return 1; @@ -1453,217 +1670,129 @@ static int encodingTypeIsValid(int eTextRep){ } } -static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, - int nRhs, const void *rhs){ - S3JniDb * const ps = pArg; - JNIEnv * env = ps->env; - jint rc = 0; - jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); - jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; - //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); - if(!jbaRhs){ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); - return 0; - //(*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); - } - (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); - (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); - rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback, - jbaLhs, jbaRhs); - EXCEPTION_IGNORE; - UNREF_L(jbaLhs); - UNREF_L(jbaRhs); - return (int)rc; -} - -/* Collation finalizer for use by the sqlite3 internals. */ -static void CollationState_xDestroy(void *pArg){ - S3JniDb * const ps = pArg; - S3JniHook_unref( ps->env, &ps->collation, 1 ); -} - -/* State for sqlite3_result_java_object() and - sqlite3_value_java_object(). */ -typedef struct { - /* The JNI docs say: - - https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html - - > The VM is guaranteed to pass the same interface pointer to a - native method when it makes multiple calls to the native method - from the same Java thread. - - Per the accompanying diagram, the "interface pointer" is the - pointer-to-pointer which is passed to all JNI calls - (`JNIEnv *env`), implying that we need to be caching that. The - verbiage "interface pointer" implies, however, that we should be - storing the dereferenced `(*env)` pointer. - - This posts claims it's unsafe to cache JNIEnv at all, even when - it's always used in the same thread: - - https://stackoverflow.com/questions/12420463 - - And this one seems to contradict that: - - https://stackoverflow.com/questions/13964608 - - For later reference: - - https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242 - - https://developer.android.com/training/articles/perf-jni - - The later has the following say about caching: - - > If performance is important, it's useful to look the - [class/method ID] values up once and cache the results in your - native code. Because there is a limit of one JavaVM per - process, it's reasonable to store this data in a static local - structure. ... The class references, field IDs, and method IDs - are guaranteed valid until the class is unloaded. Classes are - only unloaded if all classes associated with a ClassLoader can - be garbage collected, which is rare but will not be impossible - in Android. Note however that the jclass is a class reference - and must be protected with a call to NewGlobalRef (see the next - section). - */ - JNIEnv * env; - jobject jObj; -} ResultJavaVal; - /* For use with sqlite3_result/value_pointer() */ -#define RESULT_JAVA_VAL_STRING "ResultJavaVal" - -static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ - ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); - if(rv){ - rv->env = env; - rv->jObj = jObj ? REF_G(jObj) : 0; - } - return rv; -} +static const char * const ResultJavaValuePtrStr = "org.sqlite.jni.capi.ResultJavaVal"; -static void ResultJavaVal_finalizer(void *v){ - if(v){ - ResultJavaVal * const rv = (ResultJavaVal*)v; - if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj); - sqlite3_free(rv); +/* +** If v is not NULL, it must be a jobject global reference. Its +** reference is relinquished. +*/ +static void S3Jni_jobject_finalizer(void *v){ + if( v ){ + S3JniDeclLocal_env; + S3JniUnrefGlobal((jobject)v); } } - - -/** - Returns a new Java instance of the class named by zClassName, which - MUST be interface-compatible with NativePointerHolder and MUST have - a no-arg constructor. The NativePointerHolder_set() method is - passed the new Java object and pNative. Hypothetically returns NULL - if Java fails to allocate, but the JNI docs are not entirely clear - on that detail. - - Always use a static string pointer from S3JniClassNames for the 2nd - argument so that we can use its address as a cache key. +/* +** Returns a new Java instance of the class referred to by pRef, which +** MUST be interface-compatible with NativePointerHolder and MUST have +** a no-arg constructor. The NativePointerHolder_set() method is +** passed the new Java object (which must not be NULL) and pNative +** (which may be NULL). Hypothetically returns NULL if Java fails to +** allocate, but the JNI docs are not entirely clear on that detail. +** +** Always use a static pointer from the S3JniNphOps struct for the +** 2nd argument. */ -static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, - const void * pNative){ +static jobject NativePointerHolder_new(JNIEnv * const env, + S3JniNphOp const * pRef, + const void * pNative){ jobject rv = 0; - jclass klazz = 0; - jmethodID ctor = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->midCtor){ - assert( pCache->klazz ); - klazz = pCache->klazz; - ctor = pCache->midCtor; - }else{ - klazz = pCache - ? pCache->klazz - : (*env)->FindClass(env, zClassName); - ctor = klazz ? (*env)->GetMethodID(env, klazz, "", "()V") : 0; - EXCEPTION_IS_FATAL("Cannot find constructor for class."); - if(pCache){ - assert(zClassName == pCache->zClassName); - assert(pCache->klazz); - assert(!pCache->midCtor); - pCache->midCtor = ctor; + S3JniNphOp * const pNC = s3jni_nphop(pRef); + if( !pNC->midCtor ){ + S3JniNph_mutex_enter; + if( !pNC->midCtor ){ + pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "", "()V"); + S3JniExceptionIsFatal("Cannot find constructor for class."); } + S3JniNph_mutex_leave; } - assert(klazz); - assert(ctor); - rv = (*env)->NewObject(env, klazz, ctor); - EXCEPTION_IS_FATAL("No-arg constructor threw."); - if(rv) NativePointerHolder_set(env, rv, pNative, zClassName); + rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor); + S3JniExceptionIsFatal("No-arg constructor threw."); + s3jni_oom_check(rv); + if( rv ) NativePointerHolder_set(pRef, rv, pNative); return rv; } -static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3, sv); +static inline jobject new_java_sqlite3(JNIEnv * const env, sqlite3 *sv){ + return NativePointerHolder_new(env, S3JniNph(sqlite3), sv); } -static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_context, sv); +static inline jobject new_java_sqlite3_backup(JNIEnv * const env, sqlite3_backup *sv){ + return NativePointerHolder_new(env, S3JniNph(sqlite3_backup), sv); } -static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_stmt, sv); +static inline jobject new_java_sqlite3_blob(JNIEnv * const env, sqlite3_blob *sv){ + return NativePointerHolder_new(env, S3JniNph(sqlite3_blob), sv); } -static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_value, sv); +static inline jobject new_java_sqlite3_context(JNIEnv * const env, sqlite3_context *sv){ + return NativePointerHolder_new(env, S3JniNph(sqlite3_context), sv); +} +static inline jobject new_java_sqlite3_stmt(JNIEnv * const env, sqlite3_stmt *sv){ + return NativePointerHolder_new(env, S3JniNph(sqlite3_stmt), sv); +} +static inline jobject new_java_sqlite3_value(JNIEnv * const env, sqlite3_value *sv){ + return NativePointerHolder_new(env, S3JniNph(sqlite3_value), sv); } -enum UDFType { - UDF_SCALAR = 1, - UDF_AGGREGATE, - UDF_WINDOW, - UDF_UNKNOWN_TYPE/*for error propagation*/ -}; - +/* Helper typedefs for UDF callback types. */ typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**); typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**); typedef void (*udf_xFinal_f)(sqlite3_context*); /*typedef void (*udf_xValue_f)(sqlite3_context*);*/ /*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/ -/** - State for binding Java-side UDFs. +/* +** Allocate a new S3JniUdf (User-defined Function) and associate it +** with the SQLFunction-type jObj. Returns NULL on OOM. If the +** returned object's type==UDF_UNKNOWN_TYPE then the type of UDF was +** not unambiguously detected based on which callback members it has, +** which falls into the category of user error. +** +** The caller must arrange for the returned object to eventually be +** passed to S3JniUdf_free(). */ -typedef struct S3JniUdf S3JniUdf; -struct S3JniUdf { - JNIEnv * env; /* env registered from */; - jobject jObj /* SQLFunction instance */; - jclass klazz /* jObj's class */; - char * zFuncName /* Only for error reporting and debug logging */; - enum UDFType type; - /** Method IDs for the various UDF methods. */ - jmethodID jmidxFunc; - jmethodID jmidxStep; - jmethodID jmidxFinal; - jmethodID jmidxValue; - jmethodID jmidxInverse; -}; - static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ - S3JniUdf * const s = sqlite3_malloc(sizeof(S3JniUdf)); - if(s){ + S3JniUdf * s = 0; + + S3JniGlobal_mutex_enter; + s3jni_incr(&SJG.metrics.nMutexUdf); + if( SJG.udf.aFree ){ + s = SJG.udf.aFree; + SJG.udf.aFree = s->pNext; + s->pNext = 0; + s3jni_incr(&SJG.metrics.nUdfRecycled); + } + S3JniGlobal_mutex_leave; + if( !s ){ + s = s3jni_malloc( sizeof(*s)); + s3jni_incr(&SJG.metrics.nUdfAlloc); + } + if( s ){ const char * zFSI = /* signature for xFunc, xStep, xInverse */ - "(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V"; + "(Lorg/sqlite/jni/capi/sqlite3_context;[Lorg/sqlite/jni/capi/sqlite3_value;)V"; const char * zFV = /* signature for xFinal, xValue */ - "(Lorg/sqlite/jni/sqlite3_context;)V"; - memset(s, 0, sizeof(S3JniUdf)); - s->env = env; - s->jObj = REF_G(jObj); - s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); -#define FGET(FuncName,FuncType,Field) \ - s->Field = (*env)->GetMethodID(env, s->klazz, FuncName, FuncType); \ - if(!s->Field) (*env)->ExceptionClear(env) + "(Lorg/sqlite/jni/capi/sqlite3_context;)V"; + jclass const klazz = (*env)->GetObjectClass(env, jObj); + + memset(s, 0, sizeof(*s)); + s->jObj = S3JniRefGlobal(jObj); + +#define FGET(FuncName,FuncSig,Field) \ + s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncSig); \ + if( !s->Field ) (*env)->ExceptionClear(env) + FGET("xFunc", zFSI, jmidxFunc); FGET("xStep", zFSI, jmidxStep); FGET("xFinal", zFV, jmidxFinal); FGET("xValue", zFV, jmidxValue); FGET("xInverse", zFSI, jmidxInverse); #undef FGET - if(s->jmidxFunc) s->type = UDF_SCALAR; - else if(s->jmidxStep && s->jmidxFinal){ - s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE; + + S3JniUnrefLocal(klazz); + if( s->jmidxFunc ) s->type = UDF_SCALAR; + else if( s->jmidxStep && s->jmidxFinal ){ + s->type = (s->jmidxValue && s->jmidxInverse) + ? UDF_WINDOW : UDF_AGGREGATE; }else{ s->type = UDF_UNKNOWN_TYPE; } @@ -1671,187 +1800,321 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ return s; } -static void S3JniUdf_free(S3JniUdf * s){ - JNIEnv * const env = s->env; - if(env){ - //MARKER(("UDF cleanup: %s\n", s->zFuncName)); - s3jni_call_xDestroy(env, s->jObj, s->klazz); - UNREF_G(s->jObj); - UNREF_G(s->klazz); +/* +** Frees up all resources owned by s, clears its state, then either +** caches it for reuse (if cacheIt is true) or frees it. The former +** requires locking the global mutex, so it must not be held when this +** is called. +*/ +static void S3JniUdf_free(JNIEnv * const env, S3JniUdf * const s, + int cacheIt){ + assert( !s->pNext ); + if( s->jObj ){ + s3jni_call_xDestroy(s->jObj); + S3JniUnrefGlobal(s->jObj); + sqlite3_free(s->zFuncName); + assert( !s->pNext ); + memset(s, 0, sizeof(*s)); + } + if( cacheIt ){ + S3JniGlobal_mutex_enter; + s->pNext = S3JniGlobal.udf.aFree; + S3JniGlobal.udf.aFree = s; + S3JniGlobal_mutex_leave; + }else{ + sqlite3_free( s ); } - sqlite3_free(s->zFuncName); - sqlite3_free(s); } +/* Finalizer for sqlite3_create_function() and friends. */ static void S3JniUdf_finalizer(void * s){ - //MARKER(("UDF finalizer @ %p\n", s)); - if(s) S3JniUdf_free((S3JniUdf*)s); + S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1); } -/** - Helper for processing args to UDF handlers - with signature (sqlite3_context*,int,sqlite3_value**). +/* +** Helper for processing args to UDF handlers with signature +** (sqlite3_context*,int,sqlite3_value**). */ typedef struct { - jobject jcx; - jobjectArray jargv; + jobject jcx /* sqlite3_context */; + jobjectArray jargv /* sqlite3_value[] */; } udf_jargs; -/** - Converts the given (cx, argc, argv) into arguments for the given - UDF, placing the result in the final argument. Returns 0 on - success, SQLITE_NOMEM on allocation error. - - TODO: see what we can do to optimize the - new_sqlite3_value_wrapper() call. e.g. find the ctor a single time - and call it here, rather than looking it up repeatedly. +/* +** Converts the given (cx, argc, argv) into arguments for the given +** UDF, writing the result (Java wrappers for cx and argv) in the +** final 2 arguments. Returns 0 on success, SQLITE_NOMEM on allocation +** error. On error *jCx and *jArgv will be set to 0. The output +** objects are of type org.sqlite.jni.capi.sqlite3_context and +** array-of-org.sqlite.jni.capi.sqlite3_value, respectively. */ static int udf_args(JNIEnv *env, sqlite3_context * const cx, int argc, sqlite3_value**argv, jobject * jCx, jobjectArray *jArgv){ jobjectArray ja = 0; - jobject jcx = new_sqlite3_context_wrapper(env, cx); + jobject jcx = new_java_sqlite3_context(env, cx); jint i; *jCx = 0; *jArgv = 0; - if(!jcx) goto error_oom; - ja = (*env)->NewObjectArray(env, argc, - S3JniGlobal_env_cache(env)->g.cObj, - NULL); - if(!ja) goto error_oom; + if( !jcx ) goto error_oom; + ja = (*env)->NewObjectArray( + env, argc, s3jni_nphop(S3JniNph(sqlite3_value))->klazz, + NULL); + s3jni_oom_check( ja ); + if( !ja ) goto error_oom; for(i = 0; i < argc; ++i){ - jobject jsv = new_sqlite3_value_wrapper(env, argv[i]); - if(!jsv) goto error_oom; + jobject jsv = new_java_sqlite3_value(env, argv[i]); + if( !jsv ) goto error_oom; (*env)->SetObjectArrayElement(env, ja, i, jsv); - UNREF_L(jsv)/*array has a ref*/; + S3JniUnrefLocal(jsv)/*ja has a ref*/; } *jCx = jcx; *jArgv = ja; return 0; error_oom: - sqlite3_result_error_nomem(cx); - UNREF_L(jcx); - UNREF_L(ja); + S3JniUnrefLocal(jcx); + S3JniUnrefLocal(ja); return SQLITE_NOMEM; } -static int udf_report_exception(sqlite3_context * cx, - const char *zFuncName, - const char *zFuncType){ - int rc; - char * z = - sqlite3_mprintf("Client-defined function %s.%s() threw. It should " - "not do that.", - zFuncName ? zFuncName : "", zFuncType); - if(z){ - sqlite3_result_error(cx, z, -1); - sqlite3_free(z); - rc = SQLITE_ERROR; +/* +** Requires that jCx and jArgv are sqlite3_context +** resp. array-of-sqlite3_value values initialized by udf_args(). This +** function zeroes out the nativePointer member of jCx and each entry +** in jArgv. This is a safety-net precaution to avoid undefined +** behavior if a Java-side UDF holds a reference to one of its +** arguments. This MUST be called from any function which successfully +** calls udf_args(), after calling the corresponding UDF and checking +** its exception status. It MUST NOT be called in any other case. +*/ +static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){ + int i = 0; + assert(jCx); + NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0); + for( ; i < argc; ++i ){ + jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i); + assert(jsv); + NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0); + } +} + + +/* +** Must be called immediately after a Java-side UDF callback throws. +** If translateToErr is true then it sets the exception's message in +** the result error using sqlite3_result_error(). If translateToErr is +** false then it emits a warning that the function threw but should +** not do so. In either case, it clears the exception state. +** +** Returns SQLITE_NOMEM if an allocation fails, else SQLITE_ERROR. In +** the former case it calls sqlite3_result_error_nomem(). +*/ +static int udf_report_exception(JNIEnv * const env, int translateToErr, + sqlite3_context * cx, + const char *zFuncName, const char *zFuncType ){ + jthrowable const ex = (*env)->ExceptionOccurred(env); + int rc = SQLITE_ERROR; + + assert(ex && "This must only be called when a Java exception is pending."); + if( translateToErr ){ + char * zMsg; + char * z; + + S3JniExceptionClear; + zMsg = s3jni_exception_error_msg(env, ex); + z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s", + zFuncName ? zFuncName : "", zFuncType, + zMsg ? zMsg : "Unknown exception" ); + sqlite3_free(zMsg); + if( z ){ + sqlite3_result_error(cx, z, -1); + sqlite3_free(z); + }else{ + sqlite3_result_error_nomem(cx); + rc = SQLITE_NOMEM; + } }else{ - sqlite3_result_error_nomem(cx); - rc = SQLITE_NOMEM; + S3JniExceptionWarnCallbackThrew("client-defined SQL function"); + S3JniExceptionClear; } + S3JniUnrefLocal(ex); return rc; } -/** - Sets up the state for calling a Java-side xFunc/xStep/xInverse() - UDF, calls it, and returns 0 on success. +/* +** Sets up the state for calling a Java-side xFunc/xStep/xInverse() +** UDF, calls it, and returns 0 on success. */ -static int udf_xFSI(sqlite3_context* pCx, int argc, - sqlite3_value** argv, - S3JniUdf * s, - jmethodID xMethodID, - const char * zFuncType){ - JNIEnv * const env = s->env; +static int udf_xFSI(sqlite3_context* const pCx, int argc, + sqlite3_value** const argv, S3JniUdf * const s, + jmethodID xMethodID, const char * const zFuncType){ + S3JniDeclLocal_env; udf_jargs args = {0,0}; - int rc = udf_args(s->env, pCx, argc, argv, &args.jcx, &args.jargv); - //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx)); - if(rc) return rc; - //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); - if( UDF_SCALAR != s->type ){ - rc = udf_setAggregateContext(env, args.jcx, pCx, 0); - } + int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv); + if( 0 == rc ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); - IFTHREW{ - rc = udf_report_exception(pCx, s->zFuncName, zFuncType); + S3JniIfThrew{ + rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx, + s->zFuncName, zFuncType); } + udf_unargs(env, args.jcx, argc, args.jargv); } - UNREF_L(args.jcx); - UNREF_L(args.jargv); + S3JniUnrefLocal(args.jcx); + S3JniUnrefLocal(args.jargv); return rc; } -/** - Sets up the state for calling a Java-side xFinal/xValue() UDF, - calls it, and returns 0 on success. +/* +** Sets up the state for calling a Java-side xFinal/xValue() UDF, +** calls it, and returns 0 on success. */ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, jmethodID xMethodID, const char *zFuncType){ - JNIEnv * const env = s->env; - jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + S3JniDeclLocal_env; + jobject jcx = new_java_sqlite3_context(env, cx); int rc = 0; - //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx)); - if(!jcx){ - sqlite3_result_error_nomem(cx); - return SQLITE_NOMEM; - } - //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); - if( UDF_SCALAR != s->type ){ - rc = udf_setAggregateContext(env, jcx, cx, 1); - } - if( 0 == rc ){ + int const isFinal = 'F'==zFuncType[1]/*xFinal*/; + + if( jcx ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); - IFTHREW{ - rc = udf_report_exception(cx,s->zFuncName, zFuncType); + S3JniIfThrew{ + rc = udf_report_exception(env, isFinal, cx, s->zFuncName, + zFuncType); } + S3JniUnrefLocal(jcx); + }else{ + if( isFinal ) sqlite3_result_error_nomem(cx); + rc = SQLITE_NOMEM; } - UNREF_L(jcx); return rc; } +/* Proxy for C-to-Java xFunc. */ static void udf_xFunc(sqlite3_context* cx, int argc, sqlite3_value** argv){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); - ++S3JniGlobal.metrics.udf.nFunc; + s3jni_incr( &SJG.metrics.udf.nFunc ); udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc"); } +/* Proxy for C-to-Java xStep. */ static void udf_xStep(sqlite3_context* cx, int argc, sqlite3_value** argv){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); - ++S3JniGlobal.metrics.udf.nStep; + s3jni_incr( &SJG.metrics.udf.nStep ); udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep"); } +/* Proxy for C-to-Java xFinal. */ static void udf_xFinal(sqlite3_context* cx){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); - ++S3JniGlobal.metrics.udf.nFinal; + s3jni_incr( &SJG.metrics.udf.nFinal ); udf_xFV(cx, s, s->jmidxFinal, "xFinal"); } +/* Proxy for C-to-Java xValue. */ static void udf_xValue(sqlite3_context* cx){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); - ++S3JniGlobal.metrics.udf.nValue; + s3jni_incr( &SJG.metrics.udf.nValue ); udf_xFV(cx, s, s->jmidxValue, "xValue"); } +/* Proxy for C-to-Java xInverse. */ static void udf_xInverse(sqlite3_context* cx, int argc, sqlite3_value** argv){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); - ++S3JniGlobal.metrics.udf.nInverse; + s3jni_incr( &SJG.metrics.udf.nInverse ); udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse"); } //////////////////////////////////////////////////////////////////////// // What follows is the JNI/C bindings. They are in alphabetical order -// except for this macro-generated subset which are kept together here -// at the front... +// except for this macro-generated subset which are kept together +// (alphabetized) here at the front... //////////////////////////////////////////////////////////////////////// -WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) -WRAP_INT_DB(1changes, sqlite3_changes) -WRAP_INT64_DB(1changes64, sqlite3_changes64) -WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings) + +/** Create a trivial JNI wrapper for (int CName(void)). */ +#define WRAP_INT_VOID(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass){ \ + return (jint)CName(); \ + } +/** Create a trivial JNI wrapper for (int CName(int)). */ +#define WRAP_INT_INT(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jint arg){ \ + return (jint)CName((int)arg); \ + } +/* +** Create a trivial JNI wrapper for (const mutf8_string * +** CName(void)). This is only valid for functions which are known to +** return ASCII or text which is equivalent in UTF-8 and MUTF-8. +*/ +#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ + JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass){ \ + jstring const rv = (*env)->NewStringUTF( env, CName() ); \ + s3jni_oom_check(rv); \ + return rv; \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ +#define WRAP_INT_STMT(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \ + return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ +#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \ + return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)n); \ + } +/** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */ +#define WRAP_BOOL_STMT(JniNameSuffix,CName) \ + JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jobject jStmt){ \ + return CName(PtrGet_sqlite3_stmt(jStmt)) ? JNI_TRUE : JNI_FALSE; \ + } +/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ +#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ + JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \ + return s3jni_utf8_to_jstring( \ + CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx), \ + -1); \ + } +/** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */ +#define WRAP_BOOL_DB(JniNameSuffix,CName) \ + JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ + return CName(S3JniLongPtr_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ +#define WRAP_INT_DB(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ + return (jint)CName(S3JniLongPtr_sqlite3(jpDb)); \ + } +/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ +#define WRAP_INT64_DB(JniNameSuffix,CName) \ + JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ + return (jlong)CName(S3JniLongPtr_sqlite3(jpDb)); \ + } +/** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */ +#define WRAP_STR_DB_INT(JniNameSuffix,CName) \ + JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \ + return s3jni_utf8_to_jstring( \ + CName(S3JniLongPtr_sqlite3(jpDb), (int)ndx), \ + -1); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ +#define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \ + return (jint)(sv ? CName(sv): DfltOnNull); \ + } +/** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */ +#define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull) \ + JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \ + return (jint)(sv ? CName(sv) : DfltOnNull) \ + ? JNI_TRUE : JNI_FALSE; \ + } + +WRAP_INT_DB(1changes, sqlite3_changes) +WRAP_INT64_DB(1changes64, sqlite3_changes64) +WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings) WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes) WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16) WRAP_INT_STMT(1column_1count, sqlite3_column_count) @@ -1862,280 +2125,637 @@ WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name) WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name) WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) WRAP_INT_STMT(1data_1count, sqlite3_data_count) +WRAP_STR_DB_INT(1db_1name, sqlite3_db_name) WRAP_INT_DB(1error_1offset, sqlite3_error_offset) WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) +WRAP_BOOL_DB(1get_1autocommit, sqlite3_get_autocommit) WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) +WRAP_INT_VOID(1keyword_1count, sqlite3_keyword_count) +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +WRAP_INT_DB(1preupdate_1blobwrite, sqlite3_preupdate_blobwrite) +WRAP_INT_DB(1preupdate_1count, sqlite3_preupdate_count) +WRAP_INT_DB(1preupdate_1depth, sqlite3_preupdate_depth) +#endif +WRAP_INT_INT(1release_1memory, sqlite3_release_memory) WRAP_INT_INT(1sleep, sqlite3_sleep) WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid) +WRAP_BOOL_STMT(1stmt_1busy, sqlite3_stmt_busy) +WRAP_INT_STMT_INT(1stmt_1explain, sqlite3_stmt_explain) +WRAP_INT_STMT(1stmt_1isexplain, sqlite3_stmt_isexplain) +WRAP_BOOL_STMT(1stmt_1readonly, sqlite3_stmt_readonly) +WRAP_INT_DB(1system_1errno, sqlite3_system_errno) WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe) WRAP_INT_DB(1total_1changes, sqlite3_total_changes) WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64) -WRAP_INT_SVALUE(1value_1bytes, sqlite3_value_bytes) -WRAP_INT_SVALUE(1value_1bytes16, sqlite3_value_bytes16) -WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding) -WRAP_INT_SVALUE(1value_1frombind, sqlite3_value_frombind) -WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange) -WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) -WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) -WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) - -#if S3JNI_ENABLE_AUTOEXT +WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding,SQLITE_UTF8) +WRAP_BOOL_SVALUE(1value_1frombind, sqlite3_value_frombind,0) +WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange,0) +WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type,SQLITE_NULL) +WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype,0) +WRAP_INT_SVALUE(1value_1type, sqlite3_value_type,SQLITE_NULL) + +#undef WRAP_BOOL_DB +#undef WRAP_BOOL_STMT +#undef WRAP_BOOL_SVALUE +#undef WRAP_INT64_DB +#undef WRAP_INT_DB +#undef WRAP_INT_INT +#undef WRAP_INT_STMT +#undef WRAP_INT_STMT_INT +#undef WRAP_INT_SVALUE +#undef WRAP_INT_VOID +#undef WRAP_MUTF8_VOID +#undef WRAP_STR_STMT_INT +#undef WRAP_STR_DB_INT + +S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)( + JniArgsEnvClass, jobject jCx, jboolean initialize +){ + sqlite3_context * const pCx = PtrGet_sqlite3_context(jCx); + void * const p = pCx + ? sqlite3_aggregate_context(pCx, (int)(initialize + ? (int)sizeof(void*) + : 0)) + : 0; + return S3JniCast_P2L(p); +} + /* Central auto-extension handler. */ -FIXME_THREADING(autoExt) -static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, - const struct sqlite3_api_routines *ignored){ - S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; - int rc; +static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ + int rc = 0; + unsigned i, go = 1; JNIEnv * env = 0; - S3JniDb * const ps = S3JniGlobal.autoExt.psOpening; - //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); - S3JniGlobal.autoExt.psOpening = 0; - if( !pAX ){ - assert( 0==S3JniGlobal.autoExt.isRunning ); - return 0; + S3JniDb * ps; + S3JniEnv * jc; + + if( 0==SJG.autoExt.nExt ) return 0; + env = s3jni_env(); + jc = S3JniEnv_get(); + S3JniDb_mutex_enter; + ps = jc->pdbOpening ? jc->pdbOpening : S3JniDb_from_c(pDb); + if( !ps ){ + *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in " + "auto-extension runner."); + S3JniDb_mutex_leave; + return SQLITE_ERROR; } - else if( S3JniGlobal.autoExt.isRunning ){ - /* Necessary to avoid certain endless loop/stack overflow cases. */ - *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " - "auto-extensions are running."); - return SQLITE_MISUSE; + assert( ps->jDb ); + if( !ps->pDb ){ + assert( jc->pdbOpening == ps ); + rc = sqlite3_set_clientdata(pDb, S3JniDb_clientdata_key, + ps, 0/* we'll re-set this after open() + completes. */); + if( rc ){ + S3JniDb_mutex_leave; + return rc; + } } - else if(!ps){ - MARKER(("Internal error: cannot find S3JniDb for auto-extension\n")); - return SQLITE_ERROR; - }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){ - assert(!"Cannot get JNIEnv"); - *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); - return SQLITE_ERROR; + else{ + assert( ps == jc->pdbOpening ); + jc->pdbOpening = 0; } - assert( !ps->pDb /* it's still being opened */ ); + S3JniDb_mutex_leave; + NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, pDb) + /* As of here, the Java/C connection is complete except for the + (temporary) lack of finalizer for the ps object. */; ps->pDb = pDb; - assert( ps->jDb ); - NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); - ++S3JniGlobal.autoExt.isRunning; - for( ; pAX; pAX = pAX->pNext ){ - rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); - IFTHREW { - jthrowable const ex = (*env)->ExceptionOccurred(env); - char * zMsg; - EXCEPTION_CLEAR; - zMsg = s3jni_exception_error_msg(env, ex); - UNREF_L(ex); - *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); - sqlite3_free(zMsg); - rc = rc ? rc : SQLITE_ERROR; - break; - }else if( rc ){ - break; + for( i = 0; go && 0==rc; ++i ){ + S3JniAutoExtension ax = S3JniHook_empty + /* We need a copy of the auto-extension object, with our own + ** local reference to it, to avoid a race condition with another + ** thread manipulating the list during the call and invaliding + ** what ax references. */; + S3JniAutoExt_mutex_enter; + if( i >= SJG.autoExt.nExt ){ + go = 0; + }else{ + S3JniHook_localdup(&SJG.autoExt.aExt[i], &ax); + } + S3JniAutoExt_mutex_leave; + if( ax.jObj ){ + rc = (*env)->CallIntMethod(env, ax.jObj, ax.midCallback, ps->jDb); + S3JniHook_localundup(ax); + S3JniIfThrew { + jthrowable const ex = (*env)->ExceptionOccurred(env); + char * zMsg; + S3JniExceptionClear; + zMsg = s3jni_exception_error_msg(env, ex); + S3JniUnrefLocal(ex); + *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); + sqlite3_free(zMsg); + rc = SQLITE_ERROR; + } } } - --S3JniGlobal.autoExt.isRunning; return rc; } -FIXME_THREADING(autoExt) -JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ - static int once = 0; - S3JniAutoExtension * ax; +S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)( + JniArgsEnvClass, jobject jAutoExt +){ + int i; + S3JniAutoExtension * ax = 0; + int rc = 0; if( !jAutoExt ) return SQLITE_MISUSE; - else if( 0==once && ++once ){ - sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension ); + S3JniAutoExt_mutex_enter; + for( i = 0; i < SJG.autoExt.nExt; ++i ){ + /* Look for a match. */ + ax = &SJG.autoExt.aExt[i]; + if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + /* same object, so this is a no-op. */ + S3JniAutoExt_mutex_leave; + return 0; + } } - ax = S3JniGlobal.autoExt.pHead; - for( ; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - return 0 /* C API treats this as a no-op. */; + if( i == SJG.autoExt.nExt ){ + assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc ); + if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){ + /* Allocate another slot. */ + unsigned n = 1 + SJG.autoExt.nAlloc; + S3JniAutoExtension * const aNew = + s3jni_realloc( SJG.autoExt.aExt, n * sizeof(*ax) ); + if( !aNew ){ + rc = SQLITE_NOMEM; + }else{ + SJG.autoExt.aExt = aNew; + ++SJG.autoExt.nAlloc; + } + } + if( 0==rc ){ + ax = &SJG.autoExt.aExt[SJG.autoExt.nExt]; + rc = S3JniAutoExtension_init(env, ax, jAutoExt); + assert( rc ? (0==ax->jObj && 0==ax->midCallback) + : (0!=ax->jObj && 0!=ax->midCallback) ); } } - return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM; + if( 0==rc ){ + static int once = 0; + if( 0==once && ++once ){ + rc = sqlite3_auto_extension( + (void(*)(void))s3jni_run_java_auto_extensions + /* Reminder: the JNI binding of sqlite3_reset_auto_extension() + ** does not call the core-lib impl. It only clears Java-side + ** auto-extensions. */ + ); + if( rc ){ + assert( ax ); + S3JniAutoExtension_clear(ax); + } + } + if( 0==rc ){ + ++SJG.autoExt.nExt; + } + } + S3JniAutoExt_mutex_leave; + return rc; +} + +S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)( + JniArgsEnvClass, jlong jpBack +){ + int rc = 0; + if( jpBack!=0 ){ + rc = sqlite3_backup_finish( S3JniLongPtr_sqlite3_backup(jpBack) ); + } + return rc; +} + +S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)( + JniArgsEnvClass, jlong jpDbDest, jstring jTDest, + jlong jpDbSrc, jstring jTSrc +){ + sqlite3 * const pDest = S3JniLongPtr_sqlite3(jpDbDest); + sqlite3 * const pSrc = S3JniLongPtr_sqlite3(jpDbSrc); + char * const zDest = s3jni_jstring_to_utf8(jTDest, 0); + char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0); + jobject rv = 0; + + if( pDest && pSrc && zDest && zSrc ){ + sqlite3_backup * const pB = + sqlite3_backup_init(pDest, zDest, pSrc, zSrc); + if( pB ){ + rv = new_java_sqlite3_backup(env, pB); + if( !rv ){ + sqlite3_backup_finish( pB ); + } + } + } + sqlite3_free(zDest); + sqlite3_free(zSrc); + return rv; +} + +S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)( + JniArgsEnvClass, jlong jpBack +){ + return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack)); +} + +S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)( + JniArgsEnvClass, jlong jpBack +){ + return sqlite3_backup_remaining(S3JniLongPtr_sqlite3_backup(jpBack)); } -#endif /* S3JNI_ENABLE_AUTOEXT */ -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, - jint ndx, jbyteArray baData, jint nMax){ +S3JniApi(sqlite3_backup_step(),jint,1backup_1step)( + JniArgsEnvClass, jlong jpBack, jint nPage +){ + return sqlite3_backup_step(S3JniLongPtr_sqlite3_backup(jpBack), (int)nPage); +} + +S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax +){ + jsize nBA = 0; + jbyte * const pBuf = baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0; int rc; - if(!baData){ - rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx); + if( pBuf ){ + if( nMax>nBA ){ + nMax = nBA; + } + rc = sqlite3_bind_blob(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + pBuf, (int)nMax, SQLITE_TRANSIENT); + s3jni_jbyteArray_release(baData, pBuf); }else{ - jbyte * const pBuf = JBA_TOC(baData); - rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, - SQLITE_TRANSIENT); - JBA_RELEASE(baData,pBuf); + rc = baData + ? SQLITE_NOMEM + : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx ); } return (jint)rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, - jint ndx, jdouble val){ - return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); +S3JniApi(sqlite3_bind_double(),jint,1bind_1double)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val +){ + return (jint)sqlite3_bind_double(S3JniLongPtr_sqlite3_stmt(jpStmt), + (int)ndx, (double)val); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, - jint ndx, jint val){ - return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); +S3JniApi(sqlite3_bind_int(),jint,1bind_1int)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jint val +){ + return (jint)sqlite3_bind_int(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, - jint ndx, jlong val){ - return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); +S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val +){ + return (jint)sqlite3_bind_int64(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); +} + +/* +** Bind a new global ref to Object `val` using sqlite3_bind_pointer(). +*/ +S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val +){ + sqlite3_stmt * const pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt); + int rc = SQLITE_MISUSE; + + if(pStmt){ + jobject const rv = S3JniRefGlobal(val); + if( rv ){ + rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr, + S3Jni_jobject_finalizer); + }else if(val){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_bind_null(pStmt, ndx); + } + } + return rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, - jint ndx){ - return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +S3JniApi(sqlite3_bind_null(),jint,1bind_1null)( + JniArgsEnvClass, jlong jpStmt, jint ndx +){ + return (jint)sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ +S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)( + JniArgsEnvClass, jlong jpStmt +){ + return (jint)sqlite3_bind_parameter_count(S3JniLongPtr_sqlite3_stmt(jpStmt)); +} + +S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)( + JniArgsEnvClass, jlong jpStmt, jbyteArray jName +){ int rc = 0; - jbyte * const pBuf = JBA_TOC(jName); - if(pBuf){ - rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt), + jbyte * const pBuf = s3jni_jbyteArray_bytes(jName); + if( pBuf ){ + rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt), (const char *)pBuf); - JBA_RELEASE(jName, pBuf); + s3jni_jbyteArray_release(jName, pBuf); } return rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, - jint ndx, jbyteArray baData, jint nMax){ - if(baData){ - jbyte * const pBuf = JBA_TOC(baData); - int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, - (int)nMax, SQLITE_TRANSIENT); - JBA_RELEASE(baData, pBuf); - return (jint)rc; +S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)( + JniArgsEnvClass, jlong jpStmt, jint ndx +){ + const char *z = + sqlite3_bind_parameter_name(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx); + return z ? s3jni_utf8_to_jstring(z, -1) : 0; +} + +/* +** Impl of sqlite3_bind_text/text16(). +*/ +static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx, + jbyteArray baData, jint nMax){ + jsize nBA = 0; + jbyte * const pBuf = + baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0; + int rc; + if( pBuf ){ + if( nMax>nBA ){ + nMax = nBA; + } + /* Note that we rely on the Java layer having assured that baData + is NUL-terminated if nMax is negative. In order to avoid UB for + such cases, we do not expose the byte-limit arguments in the + public API. */ + rc = is16 + ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + pBuf, (int)nMax, SQLITE_TRANSIENT) + : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + (const char *)pBuf, + (int)nMax, SQLITE_TRANSIENT); + }else{ + rc = baData + ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx) + : SQLITE_NOMEM; + } + s3jni_jbyteArray_release(baData, pBuf); + return (jint)rc; + +} + +S3JniApi(sqlite3_bind_text(),jint,1bind_1text)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax +){ + return s3jni__bind_text(0, env, jpStmt, ndx, baData, nMax); +} + +S3JniApi(sqlite3_bind_text16(),jint,1bind_1text16)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax +){ + return s3jni__bind_text(1, env, jpStmt, ndx, baData, nMax); +} + +S3JniApi(sqlite3_bind_value(),jint,1bind_1value)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue +){ + int rc = 0; + sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt); + if( pStmt ){ + sqlite3_value *v = S3JniLongPtr_sqlite3_value(jpValue); + if( v ){ + rc = sqlite3_bind_value(pStmt, (int)ndx, v); + }else{ + rc = sqlite3_bind_null(pStmt, (int)ndx); + } }else{ - return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); + rc = SQLITE_MISUSE; } + return (jint)rc; +} + +S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jint n +){ + return (jint)sqlite3_bind_zeroblob(S3JniLongPtr_sqlite3_stmt(jpStmt), + (int)ndx, (int)n); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, - jint ndx, jint n){ - return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); +S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)( + JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n +){ + return (jint)sqlite3_bind_zeroblob64(S3JniLongPtr_sqlite3_stmt(jpStmt), + (int)ndx, (sqlite3_uint64)n); +} + +S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)( + JniArgsEnvClass, jlong jpBlob +){ + return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob)); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, - jint ndx, jlong n){ - return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); +S3JniApi(sqlite3_blob_close(),jint,1blob_1close)( + JniArgsEnvClass, jlong jpBlob +){ + sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob); + return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE; } +S3JniApi(sqlite3_blob_open(),jint,1blob_1open)( + JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol, + jlong jRowId, jint flags, jobject jOut +){ + sqlite3 * const db = S3JniLongPtr_sqlite3(jpDb); + sqlite3_blob * pBlob = 0; + char * zDbName = 0, * zTableName = 0, * zColumnName = 0; + int rc; + + if( !db || !jDbName || !jTbl || !jCol ) return SQLITE_MISUSE; + zDbName = s3jni_jstring_to_utf8(jDbName,0); + zTableName = zDbName ? s3jni_jstring_to_utf8(jTbl,0) : 0; + zColumnName = zTableName ? s3jni_jstring_to_utf8(jCol,0) : 0; + rc = zColumnName + ? sqlite3_blob_open(db, zDbName, zTableName, zColumnName, + (sqlite3_int64)jRowId, (int)flags, &pBlob) + : SQLITE_NOMEM; + if( 0==rc ){ + jobject rv = new_java_sqlite3_blob(env, pBlob); + if( !rv ){ + sqlite3_blob_close(pBlob); + rc = SQLITE_NOMEM; + } + OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_blob), jOut, rv); + } + sqlite3_free(zDbName); + sqlite3_free(zTableName); + sqlite3_free(zColumnName); + return rc; +} + +S3JniApi(sqlite3_blob_read(),jint,1blob_1read)( + JniArgsEnvClass, jlong jpBlob, jbyteArray jTgt, jint iOffset +){ + jbyte * const pBa = s3jni_jbyteArray_bytes(jTgt); + int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE; + if( pBa ){ + jsize const nTgt = (*env)->GetArrayLength(env, jTgt); + rc = sqlite3_blob_read(S3JniLongPtr_sqlite3_blob(jpBlob), pBa, + (int)nTgt, (int)iOffset); + if( 0==rc ){ + s3jni_jbyteArray_commit(jTgt, pBa); + }else{ + s3jni_jbyteArray_release(jTgt, pBa); + } + } + return rc; +} + +S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)( + JniArgsEnvClass, jlong jpBlob, jlong iNewRowId +){ + return (jint)sqlite3_blob_reopen(S3JniLongPtr_sqlite3_blob(jpBlob), + (sqlite3_int64)iNewRowId); +} + +S3JniApi(sqlite3_blob_write(),jint,1blob_1write)( + JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset +){ + sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob); + jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0; + const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0; + int rc = SQLITE_MISUSE; + if(b && pBuf){ + rc = sqlite3_blob_write( b, pBuf, (int)nBA, (int)iOffset ); + } + s3jni_jbyteArray_release(jBa, pBuf); + return (jint)rc; +} + +/* Central C-to-Java busy handler proxy. */ static int s3jni_busy_handler(void* pState, int n){ S3JniDb * const ps = (S3JniDb *)pState; int rc = 0; - if( ps->busyHandler.jObj ){ - JNIEnv * const env = ps->env; - rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, - ps->busyHandler.midCallback, (jint)n); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("busy-handler callback"); - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw."); + S3JniDeclLocal_env; + S3JniHook hook; + + S3JniHook_localdup(&ps->hooks.busyHandler, &hook); + if( hook.jObj ){ + rc = (*env)->CallIntMethod(env, hook.jObj, + hook.midCallback, (jint)n); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("sqlite3_busy_handler() callback"); + rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, + "sqlite3_busy_handler() callback threw."); } + S3JniHook_localundup(hook); } return rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)( + JniArgsEnvClass, jlong jpDb, jobject jBusy +){ + S3JniDb * const ps = S3JniDb_from_jlong(jpDb); + S3JniHook * const pHook = ps ? &ps->hooks.busyHandler : 0; + S3JniHook hook = S3JniHook_empty; int rc = 0; - if(!ps) return (jint)SQLITE_NOMEM; - if(jBusy){ - S3JniHook * const pHook = &ps->busyHandler; - if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ + + if( !ps ) return (jint)SQLITE_MISUSE; + S3JniDb_mutex_enter; + if( jBusy ){ + if( pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy) ){ /* Same object - this is a no-op. */ - return 0; - } - S3JniHook_unref(env, pHook, 1); - pHook->jObj = REF_G(jBusy); - pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); - pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); - IFTHREW { - S3JniHook_unref(env, pHook, 0); - rc = SQLITE_ERROR; + }else{ + jclass const klazz = (*env)->GetObjectClass(env, jBusy); + hook.jObj = S3JniRefGlobal(jBusy); + hook.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + rc = SQLITE_ERROR; + } } - if(rc){ - return rc; + } + if( 0==rc ){ + if( jBusy ){ + if( hook.jObj ){ /* Replace handler */ + rc = sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps); + if( 0==rc ){ + S3JniHook_unref(pHook); + *pHook = hook /* transfer Java ref ownership */; + hook = S3JniHook_empty; + } + }/* else no-op */ + }else{ /* Clear handler */ + rc = sqlite3_busy_handler(ps->pDb, 0, 0); + if( 0==rc ){ + S3JniHook_unref(pHook); + } } - }else{ - S3JniHook_unref(env, &ps->busyHandler, 1); } - return jBusy - ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) - : sqlite3_busy_handler(ps->pDb, 0, 0); + S3JniHook_unref(&hook); + S3JniDb_mutex_leave; + return rc; } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +S3JniApi(sqlite3_busy_timeout(),jint,1busy_1timeout)( + JniArgsEnvClass, jlong jpDb, jint ms +){ + S3JniDb * const ps = S3JniDb_from_jlong(jpDb); + int rc = SQLITE_MISUSE; if( ps ){ - S3JniHook_unref(env, &ps->busyHandler, 1); - return sqlite3_busy_timeout(ps->pDb, (int)ms); + S3JniDb_mutex_enter; + S3JniHook_unref(&ps->hooks.busyHandler); + rc = sqlite3_busy_timeout(ps->pDb, (int)ms); + S3JniDb_mutex_leave; } - return SQLITE_MISUSE; + return rc; } -#if S3JNI_ENABLE_AUTOEXT -FIXME_THREADING(autoExt) -JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ - S3JniAutoExtension * ax;; - if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE; - for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - S3JniAutoExtension_free(env, ax); - return JNI_TRUE; +S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)( + JniArgsEnvClass, jobject jAutoExt +){ + S3JniAutoExtension * ax; + jboolean rc = JNI_FALSE; + int i; + + if( !jAutoExt ){ + return rc; + } + S3JniAutoExt_mutex_enter; + /* This algo corresponds to the one in the core. */ + for( i = SJG.autoExt.nExt-1; i >= 0; --i ){ + ax = &SJG.autoExt.aExt[i]; + if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_clear(ax); + /* Move final entry into this slot. */ + --SJG.autoExt.nExt; + *ax = SJG.autoExt.aExt[SJG.autoExt.nExt]; + SJG.autoExt.aExt[SJG.autoExt.nExt] = S3JniHook_empty; + assert( !SJG.autoExt.aExt[SJG.autoExt.nExt].jObj ); + rc = JNI_TRUE; + break; } } - return JNI_FALSE; + S3JniAutoExt_mutex_leave; + return rc; } -#endif /* S3JNI_ENABLE_AUTOEXT */ - -/** - Wrapper for sqlite3_close(_v2)(). -*/ -static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ +/* Wrapper for sqlite3_close(_v2)(). */ +static jint s3jni_close_db(JNIEnv * const env, jlong jpDb, int version){ int rc = 0; - S3JniDb * ps = 0; + S3JniDb * const ps = S3JniDb_from_jlong(jpDb); + assert(version == 1 || version == 2); - ps = S3JniDb_for_db(env, jDb, 0, 0); - if(ps){ - //MARKER(("close()ing db@%p\n", ps->pDb)); - rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); - S3JniDb_set_aside(ps) - /* MUST come after close() because of ps->trace. */; - NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3); + if( ps ){ + rc = 1==version + ? (jint)sqlite3_close(ps->pDb) + : (jint)sqlite3_close_v2(ps->pDb); } return (jint)rc; } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ - return s3jni_close_db(env, pDb, 2); +S3JniApi(sqlite3_close(),jint,1close)(JniArgsEnvClass, jlong pDb){ + return s3jni_close_db(env, pDb, 1); } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1close)(JENV_CSELF, jobject pDb){ - return s3jni_close_db(env, pDb, 1); +S3JniApi(sqlite3_close_v2(),jint,1close_1v2)(JniArgsEnvClass, jlong pDb){ + return s3jni_close_db(env, pDb, 2); } -/** - Assumes z is an array of unsigned short and returns the index in - that array of the first element with the value 0. +/* +** Assumes z is an array of unsigned short and returns the index in +** that array of the first element with the value 0. */ static unsigned int s3jni_utf16_strlen(void const * z){ unsigned int i = 0; @@ -2144,280 +2764,559 @@ static unsigned int s3jni_utf16_strlen(void const * z){ return i; } -/** - sqlite3_collation_needed16() hook impl. - */ +/* Descriptive alias for use with sqlite3_collation_needed(). */ +typedef S3JniHook S3JniCollationNeeded; + +/* Central C-to-Java sqlite3_collation_needed16() hook impl. */ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, int eTextRep, const void * z16Name){ - S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; - unsigned int const nName = s3jni_utf16_strlen(z16Name); - jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); - IFTHREW{ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); - EXCEPTION_CLEAR; - }else{ - (*env)->CallVoidMethod(env, ps->collationNeeded.jObj, - ps->collationNeeded.midCallback, - ps->jDb, (jint)eTextRep, jName); - IFTHREW{ - s3jni_db_exception(env, ps, 0, "collation-needed callback threw"); + S3JniCollationNeeded * const pHook = pState; + S3JniDeclLocal_env; + S3JniHook hook; + + S3JniHook_localdup(pHook, &hook); + if( hook.jObj ){ + unsigned int const nName = s3jni_utf16_strlen(z16Name); + jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); + + s3jni_oom_check( jName ); + assert( hook.jExtra ); + S3JniIfThrew{ + S3JniExceptionClear; + }else if( hook.jExtra ){ + (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, + hook.jExtra, (jint)eTextRep, jName); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback"); + } } + S3JniUnrefLocal(jName); + S3JniHook_localundup(hook); } - UNREF_L(jName); } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; - jobject pOld = 0; - jmethodID xCallback; - S3JniHook * const pHook = &ps->collationNeeded; - int rc; +S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)( + JniArgsEnvClass, jlong jpDb, jobject jHook +){ + S3JniDb * ps; + S3JniCollationNeeded * pHook; + int rc = 0; - if( !ps ) return SQLITE_MISUSE; - pOld = pHook->jObj; - if(pOld && jHook && - (*env)->IsSameObject(env, pOld, jHook)){ - return 0; - } - if( !jHook ){ - UNREF_G(pOld); - memset(pHook, 0, sizeof(S3JniHook)); - sqlite3_collation_needed(ps->pDb, 0, 0); - return 0; + S3JniDb_mutex_enter; + ps = S3JniDb_from_jlong(jpDb); + if( !ps ){ + S3JniDb_mutex_leave; + return SQLITE_MISUSE; } - klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded", - "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I"); - IFTHREW { - rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, - "Cannot not find matching callback on " - "collation-needed hook object."); + pHook = &ps->hooks.collationNeeded; + if( pHook->jObj && jHook && + (*env)->IsSameObject(env, pHook->jObj, jHook) ){ + /* no-op */ + }else if( !jHook ){ + rc = sqlite3_collation_needed(ps->pDb, 0, 0); + if( 0==rc ){ + S3JniHook_unref(pHook); + } }else{ - pHook->midCallback = xCallback; - pHook->jObj = REF_G(jHook); - UNREF_G(pOld); - rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16); + jclass const klazz = (*env)->GetObjectClass(env, jHook); + jmethodID const xCallback = (*env)->GetMethodID( + env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I" + ); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + rc = s3jni_db_exception(ps->pDb, SQLITE_MISUSE, + "Cannot not find matching call() in " + "CollationNeededCallback object."); + }else{ + rc = sqlite3_collation_needed16(ps->pDb, pHook, + s3jni_collation_needed_impl16); + if( 0==rc ){ + S3JniHook_unref(pHook); + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jHook); + pHook->jExtra = S3JniRefGlobal(ps->jDb); + } + } } + S3JniDb_mutex_leave; return rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_blob(),jbyteArray,1column_1blob)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); void const * const p = sqlite3_column_blob(pStmt, (int)ndx); int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0; - if( 0==p ) return NULL; - else{ - jbyteArray const jba = (*env)->NewByteArray(env, n); - (*env)->SetByteArrayRegion(env, jba, 0, n, (const jbyte *)p); - return jba; - } + + return p ? s3jni_new_jbyteArray(p, n) : 0; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_double(),jdouble,1column_1double)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_int(),jint,1column_1int)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); - const int n = sqlite3_column_bytes(stmt, (int)ndx); - const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx); - return s3jni_new_jbyteArray(env, p, n); + const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0; + const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0; + return p ? s3jni_new_jbyteArray(p, n) : NULL; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, - jint ndx){ +#if 0 +// this impl might prove useful. +S3JniApi(sqlite3_column_text(),jstring,1column_1text)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); - const int n = sqlite3_column_bytes16(stmt, (int)ndx); - const void * const p = sqlite3_column_text16(stmt, (int)ndx); - return s3jni_text16_to_jstring(env, p, n); + const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0; + const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0; + return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0; } +#endif -FIXME_THREADING(S3JniEnvCache) -JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, - jint ndx){ - sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); - return new_sqlite3_value_wrapper(env, sv); +S3JniApi(sqlite3_column_text16(),jstring,1column_1text16)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const void * const p = stmt ? sqlite3_column_text16(stmt, (int)ndx) : 0; + const int n = p ? sqlite3_column_bytes16(stmt, (int)ndx) : 0; + return s3jni_text16_to_jstring(env, p, n); } +S3JniApi(sqlite3_column_value(),jobject,1column_1value)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ + sqlite3_value * const sv = + sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx) + /* reminder: returns an SQL NULL if jpStmt==NULL */; + return new_java_sqlite3_value(env, sv); +} +/* +** Impl for commit hooks (if isCommit is true) or rollback hooks. +*/ static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ - JNIEnv * const env = ps->env; - int rc = isCommit - ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, - ps->commitHook.midCallback) - : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj, - ps->rollbackHook.midCallback), 0); - IFTHREW{ - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw."); + S3JniDeclLocal_env; + int rc = 0; + S3JniHook hook; + + S3JniHook_localdup(isCommit + ? &ps->hooks.commit : &ps->hooks.rollback, + &hook); + if( hook.jObj ){ + rc = isCommit + ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback) + : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0); + S3JniIfThrew{ + rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw"); + } + S3JniHook_localundup(hook); } return rc; } +/* C-to-Java commit hook wrapper. */ static int s3jni_commit_hook_impl(void *pP){ return s3jni_commit_rollback_hook_impl(1, pP); } +/* C-to-Java rollback hook wrapper. */ static void s3jni_rollback_hook_impl(void *pP){ (void)s3jni_commit_rollback_hook_impl(0, pP); } -FIXME_THREADING(perDb) -static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, - jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; - jobject pOld = 0; - jmethodID xCallback; - S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; - if(!ps){ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); +/* +** Proxy for sqlite3_commit_hook() (if isCommit is true) or +** sqlite3_rollback_hook(). +*/ +static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env, + jlong jpDb, jobject jHook){ + S3JniDb * ps; + jobject pOld = 0; /* previous hoook */ + S3JniHook * pHook; /* ps->hooks.commit|rollback */ + + S3JniDb_mutex_enter; + ps = S3JniDb_from_jlong(jpDb); + if( !ps ){ + s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); + S3JniDb_mutex_leave; return 0; } + pHook = isCommit ? &ps->hooks.commit : &ps->hooks.rollback; pOld = pHook->jObj; - if(pOld && jHook && - (*env)->IsSameObject(env, pOld, jHook)){ - return pOld; - } - if( !jHook ){ - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); + if( pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook) ){ + /* No-op. */ + }else if( !jHook ){ + if( pOld ){ + jobject tmp = S3JniRefLocal(pOld); + S3JniUnrefGlobal(pOld); pOld = tmp; } - memset(pHook, 0, sizeof(S3JniHook)); + *pHook = S3JniHook_empty; if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0); else sqlite3_rollback_hook(ps->pDb, 0, 0); - return pOld; - } - klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, - isCommit ? "xCommitHook" : "xRollbackHook", - isCommit ? "()I" : "()V"); - IFTHREW { - EXCEPTION_REPORT; - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching callback on " - "hook object."); }else{ - pHook->midCallback = xCallback; - pHook->jObj = REF_G(jHook); - if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps); - else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps); - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); - pOld = tmp; + jclass const klazz = (*env)->GetObjectClass(env, jHook); + jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", + isCommit ? "()I" : "()V"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionReport; + S3JniExceptionClear; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching call() method in" + "hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jHook); + if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps); + else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps); + if( pOld ){ + jobject tmp = S3JniRefLocal(pOld); + S3JniUnrefGlobal(pOld); + pOld = tmp; + } } } + S3JniDb_mutex_leave; return pOld; } -JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ - return s3jni_commit_rollback_hook(1, env, jDb, jHook); +S3JniApi(sqlite3_commit_hook(),jobject,1commit_1hook)( + JniArgsEnvClass, jlong jpDb, jobject jHook +){ + return s3jni_commit_rollback_hook(1, env, jpDb, jHook); } - -JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){ - return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); +S3JniApi(sqlite3_compileoption_get(),jstring,1compileoption_1get)( + JniArgsEnvClass, jint n +){ + const char * z = sqlite3_compileoption_get(n); + jstring const rv = z ? (*env)->NewStringUTF( env, z ) : 0; + /* We know these to be ASCII, so MUTF-8 is fine. */; + s3jni_oom_check(z ? !!rv : 1); + return rv; } -JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ - const char *zUtf8 = JSTR_TOC(name); +S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)( + JniArgsEnvClass, jstring name +){ + const char *zUtf8 = s3jni_jstring_to_mutf8(name) + /* We know these to be ASCII, so MUTF-8 is fine (and + hypothetically faster to convert). */; const jboolean rc = 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; - JSTR_RELEASE(name, zUtf8); + s3jni_mutf8_release(name, zUtf8); + return rc; +} + +S3JniApi(sqlite3_complete(),int,1complete)( + JniArgsEnvClass, jbyteArray jSql +){ + jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql); + const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jSql) : 0; + int rc; + + assert( (nBA>0 ? 0==pBuf[nBA-1] : (pBuf ? 0==*pBuf : 1)) + && "Byte array is not NUL-terminated." ); + rc = (pBuf && 0==pBuf[(nBA ? nBA-1 : 0)]) + ? sqlite3_complete( (const char *)pBuf ) + : (jSql ? SQLITE_NOMEM : SQLITE_MISUSE); + s3jni_jbyteArray_release(jSql, pBuf); + return rc; +} + +S3JniApi(sqlite3_config() /*for a small subset of options.*/, + jint,1config__I)(JniArgsEnvClass, jint n){ + switch( n ){ + case SQLITE_CONFIG_SINGLETHREAD: + case SQLITE_CONFIG_MULTITHREAD: + case SQLITE_CONFIG_SERIALIZED: + return sqlite3_config( n ); + default: + return SQLITE_MISUSE; + } +} +/* C-to-Java SQLITE_CONFIG_LOG wrapper. */ +static void s3jni_config_log(void *ignored, int errCode, const char *z){ + S3JniDeclLocal_env; + S3JniHook hook = S3JniHook_empty; + + S3JniHook_localdup(&SJG.hook.configlog, &hook); + if( hook.jObj ){ + jstring const jArg1 = z ? s3jni_utf8_to_jstring(z, -1) : 0; + if( z ? !!jArg1 : 1 ){ + (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, errCode, jArg1); + } + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_LOG callback"); + S3JniExceptionClear; + } + S3JniHook_localundup(hook); + S3JniUnrefLocal(jArg1); + } +} + +S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */, + jint, 1config__Lorg_sqlite_jni_ConfigLogCallback_2 +)(JniArgsEnvClass, jobject jLog){ + S3JniHook * const pHook = &SJG.hook.configlog; + int rc = 0; + + S3JniGlobal_mutex_enter; + if( !jLog ){ + rc = sqlite3_config( SQLITE_CONFIG_LOG, NULL, NULL ); + if( 0==rc ){ + S3JniHook_unref(pHook); + } + }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){ + /* No-op */ + }else { + jclass const klazz = (*env)->GetObjectClass(env, jLog); + jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call", + "(ILjava/lang/String;)V"); + S3JniUnrefLocal(klazz); + if( midCallback ){ + rc = sqlite3_config( SQLITE_CONFIG_LOG, s3jni_config_log, NULL ); + if( 0==rc ){ + S3JniHook_unref(pHook); + pHook->midCallback = midCallback; + pHook->jObj = S3JniRefGlobal(jLog); + } + }else{ + S3JniExceptionWarnIgnore; + rc = SQLITE_ERROR; + } + } + S3JniGlobal_mutex_leave; return rc; } -FIXME_THREADING(perDb) -JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ - sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); - S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0; - return ps ? ps->jDb : 0; +#ifdef SQLITE_ENABLE_SQLLOG +/* C-to-Java SQLITE_CONFIG_SQLLOG wrapper. */ +static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int op){ + jobject jArg0 = 0; + jstring jArg1 = 0; + S3JniDeclLocal_env; + S3JniDb * const ps = S3JniDb_from_c(pDb); + S3JniHook hook = S3JniHook_empty; + + if( ps ){ + S3JniHook_localdup(&SJG.hook.sqllog, &hook); + } + if( !hook.jObj ) return; + jArg0 = S3JniRefLocal(ps->jDb); + switch( op ){ + case 0: /* db opened */ + case 1: /* SQL executed */ + jArg1 = s3jni_utf8_to_jstring( z, -1); + break; + case 2: /* db closed */ + break; + default: + (*env)->FatalError(env, "Unhandled 4th arg to SQLITE_CONFIG_SQLLOG."); + break; + } + (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, jArg0, jArg1, op); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_SQLLOG callback"); + S3JniExceptionClear; + } + S3JniHook_localundup(hook); + S3JniUnrefLocal(jArg0); + S3JniUnrefLocal(jArg1); +} +//! Requirement of SQLITE_CONFIG_SQLLOG. +void sqlite3_init_sqllog(void){ + sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 ); +} +#endif + +S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */, + jint, 1config__Lorg_sqlite_jni_ConfigSqllogCallback_2)( + JniArgsEnvClass, jobject jLog){ +#ifndef SQLITE_ENABLE_SQLLOG + return SQLITE_MISUSE; +#else + S3JniHook * const pHook = &SJG.hook.sqllog; + int rc = 0; + + S3JniGlobal_mutex_enter; + if( !jLog ){ + rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, NULL ); + if( 0==rc ){ + S3JniHook_unref(pHook); + } + }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){ + /* No-op */ + }else { + jclass const klazz = (*env)->GetObjectClass(env, jLog); + jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call", + "(Lorg/sqlite/jni/capi/sqlite3;" + "Ljava/lang/String;" + "I)V"); + S3JniUnrefLocal(klazz); + if( midCallback ){ + rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, NULL ); + if( 0==rc ){ + S3JniHook_unref(pHook); + pHook->midCallback = midCallback; + pHook->jObj = S3JniRefGlobal(jLog); + } + }else{ + S3JniExceptionWarnIgnore; + rc = SQLITE_ERROR; + } + } + S3JniGlobal_mutex_leave; + return rc; +#endif +} + +S3JniApi(sqlite3_context_db_handle(),jobject,1context_1db_1handle)( + JniArgsEnvClass, jobject jpCx +){ + sqlite3_context * const pCx = PtrGet_sqlite3_context(jpCx); + sqlite3 * const pDb = pCx ? sqlite3_context_db_handle(pCx) : 0; + S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0; + return ps ? ps->jDb : 0; +} + +/* +** State for CollationCallbacks. This used to be its own separate +** type, but has since been consolidated with S3JniHook. It retains +** its own typedef for code legibility and searchability reasons. +*/ +typedef S3JniHook S3JniCollationCallback; + +/* +** Proxy for Java-side CollationCallback.xCompare() callbacks. +*/ +static int CollationCallback_xCompare(void *pArg, int nLhs, const void *lhs, + int nRhs, const void *rhs){ + S3JniCollationCallback * const pCC = pArg; + S3JniDeclLocal_env; + jint rc = 0; + if( pCC->jObj ){ + jbyteArray jbaLhs = s3jni_new_jbyteArray(lhs, (jint)nLhs); + jbyteArray jbaRhs = jbaLhs + ? s3jni_new_jbyteArray(rhs, (jint)nRhs) : 0; + if( !jbaRhs ){ + S3JniUnrefLocal(jbaLhs); + /* We have no recovery strategy here. */ + s3jni_oom_check( jbaRhs ); + return 0; + } + rc = (*env)->CallIntMethod(env, pCC->jObj, pCC->midCallback, + jbaLhs, jbaRhs); + S3JniExceptionIgnore; + S3JniUnrefLocal(jbaLhs); + S3JniUnrefLocal(jbaRhs); + } + return (int)rc; +} + +/* CollationCallback finalizer for use by the sqlite3 internals. */ +static void CollationCallback_xDestroy(void *pArg){ + S3JniCollationCallback * const pCC = pArg; + S3JniDeclLocal_env; + S3JniHook_free(pCC); } -JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, - jstring name, jint eTextRep, - jobject oCollation){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; +S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(), + jint,1create_1collation +)(JniArgsEnvClass, jobject jDb, jstring name, jint eTextRep, + jobject oCollation){ int rc; - const char *zName; - S3JniHook * pHook; - if(!ps) return (jint)SQLITE_NOMEM; - pHook = &ps->collation; - klazz = (*env)->GetObjectClass(env, oCollation); - pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare", - "([B[B)I"); - IFTHREW{ - EXCEPTION_REPORT; - return s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Could not get xCompare() method for object."); - } - zName = JSTR_TOC(name); - rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, - ps, CollationState_xCompare, - CollationState_xDestroy); - JSTR_RELEASE(name, zName); - if( 0==rc ){ - pHook->jObj = REF_G(oCollation); - pHook->klazz = REF_G(klazz); + S3JniDb * ps; + + if( !jDb || !name || !encodingTypeIsValid(eTextRep) ){ + return (jint)SQLITE_MISUSE; + } + S3JniDb_mutex_enter; + ps = S3JniDb_from_java(jDb); + jclass const klazz = (*env)->GetObjectClass(env, oCollation); + jmethodID const midCallback = + (*env)->GetMethodID(env, klazz, "call", "([B[B)I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew{ + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Could not get call() method from " + "CollationCallback object."); }else{ - S3JniHook_unref(env, pHook, 1); + char * const zName = s3jni_jstring_to_utf8(name, 0); + S3JniCollationCallback * const pCC = + zName ? S3JniHook_alloc() : 0; + if( pCC ){ + rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, + pCC, CollationCallback_xCompare, + CollationCallback_xDestroy); + if( 0==rc ){ + pCC->midCallback = midCallback; + pCC->jObj = S3JniRefGlobal(oCollation); + pCC->doXDestroy = 1; + }else{ + CollationCallback_xDestroy(pCC); + } + }else{ + rc = SQLITE_NOMEM; + } + sqlite3_free(zName); } + S3JniDb_mutex_leave; return (jint)rc; } -static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, - jint nArg, jint eTextRep, jobject jFunctor){ +S3JniApi(sqlite3_create_function() sqlite3_create_function_v2() + sqlite3_create_window_function(), + jint,1create_1function +)(JniArgsEnvClass, jobject jDb, jstring jFuncName, jint nArg, + jint eTextRep, jobject jFunctor){ S3JniUdf * s = 0; int rc; sqlite3 * const pDb = PtrGet_sqlite3(jDb); - const char * zFuncName = 0; + char * zFuncName = 0; - if( !encodingTypeIsValid(eTextRep) ){ + if( !pDb || !jFuncName ){ + return SQLITE_MISUSE; + }else if( !encodingTypeIsValid(eTextRep) ){ return s3jni_db_error(pDb, SQLITE_FORMAT, - "Invalid function encoding option."); + "Invalid function encoding option."); } s = S3JniUdf_alloc(env, jFunctor); if( !s ) return SQLITE_NOMEM; - else if( UDF_UNKNOWN_TYPE==s->type ){ + + if( UDF_UNKNOWN_TYPE==s->type ){ rc = s3jni_db_error(pDb, SQLITE_MISUSE, "Cannot unambiguously determine function type."); - S3JniUdf_free(s); + S3JniUdf_free(env, s, 1); goto error_cleanup; } - zFuncName = JSTR_TOC(jFuncName); - if(!zFuncName){ + zFuncName = s3jni_jstring_to_utf8(jFuncName,0); + if( !zFuncName ){ rc = SQLITE_NOMEM; - S3JniUdf_free(s); + S3JniUdf_free(env, s, 1); goto error_cleanup; } + s->zFuncName = zFuncName /* pass on ownership */; if( UDF_WINDOW == s->type ){ rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s, udf_xStep, udf_xFinal, udf_xValue, @@ -2436,34 +3335,26 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, xFunc, xStep, xFinal, S3JniUdf_finalizer); } - if( 0==rc ){ - s->zFuncName = sqlite3_mprintf("%s", zFuncName) - /* OOM here is non-fatal. Ignore it. Handling it would require - re-calling the appropriate create_function() func with 0 - for all xAbc args so that s would be finalized. */; - } error_cleanup: - JSTR_RELEASE(jFuncName, zFuncName); - /* on create_function() error, s will be destroyed via create_function() */ + /* Reminder: on sqlite3_create_function() error, s will be + ** destroyed via create_function(). */ return (jint)rc; } -JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, - jint nArg, jint eTextRep, jobject jFunctor){ - return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); -} -/* sqlite3_db_config() for (int,const char *) */ -JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( - JENV_CSELF, jobject jDb, jint op, jstring jStr -){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +S3JniApi(sqlite3_db_config() /*for MAINDBNAME*/, + jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2 +)(JniArgsEnvClass, jobject jDb, jint op, jstring jStr){ + S3JniDb * const ps = S3JniDb_from_java(jDb); int rc; char *zStr; switch( (ps && jStr) ? op : 0 ){ case SQLITE_DBCONFIG_MAINDBNAME: - zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); + S3JniDb_mutex_enter + /* Protect against a race in modifying/freeing + ps->zMainDbName. */; + zStr = s3jni_jstring_to_utf8( jStr, 0); if( zStr ){ rc = sqlite3_db_config(ps->pDb, (int)op, zStr); if( rc ){ @@ -2475,21 +3366,25 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( }else{ rc = SQLITE_NOMEM; } + S3JniDb_mutex_leave; break; + case 0: default: rc = SQLITE_MISUSE; } return rc; } -FIXME_THREADING(perDb) -/* sqlite3_db_config() for (int,int*) */ -/* ACHTUNG: openjdk v19 creates a different mangled name for this - function than openjdk v8 does. */ -JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( - JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +S3JniApi( + sqlite3_db_config(), + /* WARNING: openjdk v19 creates a different mangled name for this + ** function than openjdk v8 does. We account for that by exporting + ** both versions of the name. */ + jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2 +)( + JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut ){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_from_java(jDb); int rc; switch( ps ? op : 0 ){ case SQLITE_DBCONFIG_ENABLE_FKEY: @@ -2517,29 +3412,31 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer } break; } + case 0: default: rc = SQLITE_MISUSE; } return (jint)rc; } -/** - This is a workaround for openjdk v19 (and possibly others) encoding - this function's name differently than JDK v8 does. If we do not - install both names for this function then Java will not be able to - find the function in both environments. +/* +** This is a workaround for openjdk v19 (and possibly others) encoding +** this function's name differently than JDK v8 does. If we do not +** install both names for this function then Java will not be able to +** find the function in both environments. */ -JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( - JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +JniDecl(jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_00024Int32_2)( + JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut ){ - return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + return JniFuncName(1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2)( env, jKlazz, jDb, op, onOff, jOut ); } -JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); +S3JniApi(sqlite3_db_filename(),jstring,1db_1filename)( + JniArgsEnvClass, jobject jDb, jstring jDbName +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); char *zDbName; jstring jRv = 0; int nStr = 0; @@ -2547,20 +3444,48 @@ JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ if( !ps || !jDbName ){ return 0; } - zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr); + zDbName = s3jni_jstring_to_utf8( jDbName, &nStr); if( zDbName ){ char const * zRv = sqlite3_db_filename(ps->pDb, zDbName); sqlite3_free(zDbName); if( zRv ){ - jRv = s3jni_utf8_to_jstring(jc, zRv, -1); + jRv = s3jni_utf8_to_jstring( zRv, -1); } } return jRv; } +S3JniApi(sqlite3_db_handle(),jobject,1db_1handle)( + JniArgsEnvClass, jobject jpStmt +){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + sqlite3 * const pDb = pStmt ? sqlite3_db_handle(pStmt) : 0; + S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0; + return ps ? ps->jDb : 0; +} + +S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)( + JniArgsEnvClass, jobject jDb, jstring jDbName +){ + int rc = 0; + S3JniDb * const ps = S3JniDb_from_java(jDb); + char *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0 ) : 0; + rc = sqlite3_db_readonly(ps ? ps->pDb : 0, zDbName); + sqlite3_free(zDbName); + return (jint)rc; +} + +S3JniApi(sqlite3_db_release_memory(),int,1db_1release_1memory)( + JniArgsEnvClass, jobject jDb +){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + return pDb ? sqlite3_db_release_memory(pDb) : SQLITE_MISUSE; +} -JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent, - jobject jOutHigh, jboolean reset ){ +S3JniApi(sqlite3_db_status(),jint,1db_1status)( + JniArgsEnvClass, jobject jDb, jint op, jobject jOutCurrent, + jobject jOutHigh, jboolean reset +){ int iCur = 0, iHigh = 0; sqlite3 * const pDb = PtrGet_sqlite3(jDb); int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset ); @@ -2571,326 +3496,710 @@ JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent, return (jint)rc; } - -JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ +S3JniApi(sqlite3_errcode(),jint,1errcode)( + JniArgsEnvClass, jobject jpDb +){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE; } -JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ +S3JniApi(sqlite3_errmsg(),jstring,1errmsg)( + JniArgsEnvClass, jobject jpDb +){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); - S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; - return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; + return pDb ? s3jni_utf8_to_jstring( sqlite3_errmsg(pDb), -1) : 0 + /* We don't use errmsg16() directly only because it would cause an + additional level of internal encoding in sqlite3. The end + effect should be identical to using errmsg16(), however. */; +} + +S3JniApi(sqlite3_errstr(),jstring,1errstr)( + JniArgsEnvClass, jint rcCode +){ + jstring const rv = (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) + /* We know these values to be plain ASCII, so pose no MUTF-8 + ** incompatibility */; + s3jni_oom_check( rv ); + return rv; } -JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ - return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) - /* We know these values to be plain ASCII, so pose no - MUTF-8 incompatibility */; +#ifndef SQLITE_ENABLE_NORMALIZE +/* Dummy stub for sqlite3_normalized_sql(). Never called. */ +static const char * sqlite3_normalized_sql(sqlite3_stmt *s){ + S3JniDeclLocal_env; + (*env)->FatalError(env, "dummy sqlite3_normalized_sql() was " + "impossibly called.") /* does not return */; + return 0; } +#endif -JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ - sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); +/* +** Impl for sqlite3_expanded_sql() (if isExpanded is true) and +** sqlite3_normalized_sql(). +*/ +static jstring s3jni_xn_sql(int isExpanded, JNIEnv *env, jobject jpStmt){ jstring rv = 0; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + if( pStmt ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - char * zSql = sqlite3_expanded_sql(pStmt); - OOM_CHECK(zSql); + char * zSql = isExpanded + ? sqlite3_expanded_sql(pStmt) + : (char*)sqlite3_normalized_sql(pStmt); + s3jni_oom_fatal(zSql); if( zSql ){ - rv = s3jni_utf8_to_jstring(jc, zSql, -1); - sqlite3_free(zSql); + rv = s3jni_utf8_to_jstring(zSql, -1); + if( isExpanded ) sqlite3_free(zSql); } } return rv; } -JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb, - jboolean onoff){ - int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0); +S3JniApi(sqlite3_expanded_sql(),jstring,1expanded_1sql)( + JniArgsEnvClass, jobject jpStmt +){ + return s3jni_xn_sql(1, env, jpStmt); +} + +S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)( + JniArgsEnvClass, jobject jpStmt +){ +#ifdef SQLITE_ENABLE_NORMALIZE + return s3jni_xn_sql(0, env, jpStmt); +#else + return 0; +#endif +} + +S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)( + JniArgsEnvClass, jobject jpDb, jboolean onoff +){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + int const rc = pDb ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) : 0; return rc ? JNI_TRUE : JNI_FALSE; } -JDECL(jint,1initialize)(JENV_CSELF){ +S3JniApi(sqlite3_finalize(),jint,1finalize)( + JniArgsEnvClass, jlong jpStmt +){ + return jpStmt + ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt)) + : 0; +} + +S3JniApi(sqlite3_get_auxdata(),jobject,1get_1auxdata)( + JniArgsEnvClass, jobject jCx, jint n +){ + return sqlite3_get_auxdata(PtrGet_sqlite3_context(jCx), (int)n); +} + +S3JniApi(sqlite3_initialize(),jint,1initialize)( + JniArgsEnvClass +){ return sqlite3_initialize(); } -JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ +S3JniApi(sqlite3_interrupt(),void,1interrupt)( + JniArgsEnvClass, jobject jpDb +){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + if( pDb ){ + sqlite3_interrupt(pDb); + } +} + +S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)( + JniArgsEnvClass, jobject jpDb +){ int rc = 0; - sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); - if( pStmt ){ - rc = sqlite3_finalize(pStmt); - NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt); + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + if( pDb ){ + rc = sqlite3_is_interrupted(pDb); } - return rc; + return rc ? JNI_TRUE : JNI_FALSE; +} + +/* +** Uncaches the current JNIEnv from the S3JniGlobal state, clearing +** any resources owned by that cache entry and making that slot +** available for re-use. +*/ +JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){ + int rc; + S3JniEnv_mutex_enter; + rc = S3JniEnv_uncache(env); + S3JniEnv_mutex_leave; + return rc ? JNI_TRUE : JNI_FALSE; +} + +S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)( + JniArgsEnvClass, jstring jWord +){ + int nWord = 0; + char * zWord = s3jni_jstring_to_utf8(jWord, &nWord); + int rc = 0; + + s3jni_oom_check(jWord ? !!zWord : 1); + if( zWord && nWord ){ + rc = sqlite3_keyword_check(zWord, nWord); + } + sqlite3_free(zWord); + return rc ? JNI_TRUE : JNI_FALSE; +} + +S3JniApi(sqlite3_keyword_name(),jstring,1keyword_1name)( + JniArgsEnvClass, jint ndx +){ + const char * zWord = 0; + int n = 0; + jstring rv = 0; + + if( 0==sqlite3_keyword_name(ndx, &zWord, &n) ){ + rv = s3jni_utf8_to_jstring(zWord, n); + } + return rv; } -JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ +S3JniApi(sqlite3_last_insert_rowid(),jlong,1last_1insert_1rowid)( + JniArgsEnvClass, jobject jpDb +){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); } -//! Pre-open() code common to sqlite3_open(_v2)(). -static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, +S3JniApi(sqlite3_limit(),jint,1limit)( + JniArgsEnvClass, jobject jpDb, jint id, jint newVal +){ + jint rc = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + if( pDb ){ + rc = sqlite3_limit( pDb, (int)id, (int)newVal ); + } + return rc; +} + +/* Pre-open() code common to sqlite3_open[_v2](). */ +static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, jstring jDbName, char **zDbName, - S3JniDb ** ps, jobject *jDb){ - *jc = S3JniGlobal_env_cache(env); - if(!*jc) return SQLITE_NOMEM; - *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; - if(jDbName && !*zDbName) return SQLITE_NOMEM; - *jDb = new_sqlite3_wrapper(env, 0); - if( !*jDb ){ + S3JniDb ** ps){ + int rc = 0; + jobject jDb = 0; + + *jc = S3JniEnv_get(); + if( !*jc ){ + rc = SQLITE_NOMEM; + goto end; + } + *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0) : 0; + if( jDbName && !*zDbName ){ + rc = SQLITE_NOMEM; + goto end; + } + jDb = new_java_sqlite3(env, 0); + if( !jDb ){ sqlite3_free(*zDbName); *zDbName = 0; - return SQLITE_NOMEM; + rc = SQLITE_NOMEM; + goto end; } - *ps = S3JniDb_alloc(env, 0, *jDb); -#if S3JNI_ENABLE_AUTOEXT - if(*ps){ - assert(!S3JniGlobal.autoExt.psOpening); - S3JniGlobal.autoExt.psOpening = *ps; + *ps = S3JniDb_alloc(env, jDb); + if( *ps ){ + (*jc)->pdbOpening = *ps; + }else{ + S3JniUnrefLocal(jDb); + rc = SQLITE_NOMEM; } -#endif - //MARKER(("pre-open ps@%p\n", *ps)); - return *ps ? 0 : SQLITE_NOMEM; +end: + return rc; } -/** - Post-open() code common to both the sqlite3_open() and - sqlite3_open_v2() bindings. ps->jDb must be the - org.sqlite.jni.sqlite3 object which will hold the db's native - pointer. theRc must be the result code of the open() op. If - *ppDb is NULL then ps is set aside and its state cleared, - else ps is associated with *ppDb. If *ppDb is not NULL then - ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). - - Returns theRc. +/* +** Post-open() code common to both the sqlite3_open() and +** sqlite3_open_v2() bindings. ps->jDb must be the +** org.sqlite.jni.capi.sqlite3 object which will hold the db's native +** pointer. theRc must be the result code of the open() op. If +** *ppDb is NULL then ps is set aside and its state cleared, +** else ps is associated with *ppDb. If *ppDb is not NULL then +** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). +** +** Must be called if s3jni_open_pre() succeeds and must not be called +** if it doesn't. +** +** Returns theRc. */ -static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, - sqlite3 **ppDb, jobject jOut, int theRc){ - //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); -#if S3JNI_ENABLE_AUTOEXT - assert( S3JniGlobal.autoExt.pHead ? ps!=S3JniGlobal.autoExt.psOpening : 1 ); - S3JniGlobal.autoExt.psOpening = 0; -#endif - if(*ppDb){ +static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc, + S3JniDb * ps, sqlite3 **ppDb, + jobject jOut, int theRc){ + int rc = 0; + jc->pdbOpening = 0; + if( *ppDb ){ assert(ps->jDb); -#if S3JNI_ENABLE_AUTOEXT - //MARKER(("*autoExt.pHead=%p, ppDb=%p, ps->pDb=%p\n", S3JniGlobal.autoExt.pHead, *ppDb, ps->pDb)); - // invalid when an autoext triggers another open(): - // assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); -#endif - ps->pDb = *ppDb; - NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); + if( 0==ps->pDb ){ + ps->pDb = *ppDb; + NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, *ppDb); + }else{ + assert( ps->pDb==*ppDb + && "Set up via s3jni_run_java_auto_extensions()" ); + } + rc = sqlite3_set_clientdata(ps->pDb, S3JniDb_clientdata_key, + ps, S3JniDb_xDestroy) + /* As of here, the Java/C connection is complete */; }else{ S3JniDb_set_aside(ps); ps = 0; } - OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); - return theRc; + OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3), + jOut, ps ? ps->jDb : 0); + return theRc ? theRc : rc; } -JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ +S3JniApi(sqlite3_open(),jint,1open)( + JniArgsEnvClass, jstring strName, jobject jOut +){ sqlite3 * pOut = 0; char *zName = 0; - jobject jDb = 0; S3JniDb * ps = 0; - S3JniEnvCache * jc = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; - int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + S3JniEnv * jc = 0; + int rc; + + if( 0==jOut ) return SQLITE_MISUSE; + rc = s3jni_open_pre(env, &jc, strName, &zName, &ps); if( 0==rc ){ - rc = sqlite3_open(zName, &pOut); - //MARKER(("env=%p, *env=%p\n", env, *env)); - //MARKER(("open() ps@%p db@%p\n", ps, pOut)); - rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + rc = s3jni_open_post(env, jc, ps, &pOut, jOut, + sqlite3_open(zName, &pOut)); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); } - S3JniGlobal.autoExt.psOpening = prevOpening; return (jint)rc; } -JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, - jobject jOut, jint flags, jstring strVfs){ +S3JniApi(sqlite3_open_v2(),jint,1open_1v2)( + JniArgsEnvClass, jstring strName, + jobject jOut, jint flags, jstring strVfs +){ sqlite3 * pOut = 0; char *zName = 0; - jobject jDb = 0; S3JniDb * ps = 0; - S3JniEnvCache * jc = 0; + S3JniEnv * jc = 0; char *zVfs = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; - int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); - if( 0==rc && strVfs ){ - zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); - if( !zVfs ){ - rc = SQLITE_NOMEM; - } - } + int rc; + + if( 0==jOut ) return SQLITE_MISUSE; + rc = s3jni_open_pre(env, &jc, strName, &zName, &ps); if( 0==rc ){ - rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + if( strVfs ){ + zVfs = s3jni_jstring_to_utf8( strVfs, 0); + if( !zVfs ){ + rc = SQLITE_NOMEM; + } + } + if( 0==rc ){ + rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + } + rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc); } - //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut)); - /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", - zName, zVfs, pOut, (int)flags, nrc));*/ - rc = s3jni_open_post(env, ps, &pOut, jOut, rc); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); sqlite3_free(zVfs); - S3JniGlobal.autoExt.psOpening = prevOpening; return (jint)rc; } /* Proxy for the sqlite3_prepare[_v2/3]() family. */ -static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self, - jobject jDb, jbyteArray baSql, - jint nMax, jint prepFlags, - jobject jOutStmt, jobject outTail){ +jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self, + jlong jpDb, jbyteArray baSql, + jint nMax, jint prepFlags, + jobject jOutStmt, jobject outTail){ sqlite3_stmt * pStmt = 0; jobject jStmt = 0; const char * zTail = 0; - jbyte * const pBuf = JBA_TOC(baSql); + sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb); + jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql) : 0; int rc = SQLITE_ERROR; + assert(prepVersion==1 || prepVersion==2 || prepVersion==3); - if( !pBuf ){ - rc = baSql ? SQLITE_MISUSE : SQLITE_NOMEM; - goto end; + if( !pDb || !jOutStmt ){ + rc = SQLITE_MISUSE; + goto end; + }else if( !pBuf ){ + rc = baSql ? SQLITE_NOMEM : SQLITE_MISUSE; + goto end; } - jStmt = new_sqlite3_stmt_wrapper(env, 0); + jStmt = new_java_sqlite3_stmt(env, 0); if( !jStmt ){ rc = SQLITE_NOMEM; goto end; } switch( prepVersion ){ - case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jDb), (const char *)pBuf, + case 1: rc = sqlite3_prepare(pDb, (const char *)pBuf, (int)nMax, &pStmt, &zTail); break; - case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jDb), (const char *)pBuf, + case 2: rc = sqlite3_prepare_v2(pDb, (const char *)pBuf, (int)nMax, &pStmt, &zTail); break; - case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jDb), (const char *)pBuf, + case 3: rc = sqlite3_prepare_v3(pDb, (const char *)pBuf, (int)nMax, (unsigned int)prepFlags, &pStmt, &zTail); break; default: - assert(0 && "Invalid prepare() version"); + assert(!"Invalid prepare() version"); } end: - JBA_RELEASE(baSql,pBuf); + s3jni_jbyteArray_release(baSql,pBuf); if( 0==rc ){ if( 0!=outTail ){ - /* Noting that pBuf is deallocated now but its address is all we need. */ + /* Noting that pBuf is deallocated now but its address is all we need for + ** what follows... */ assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1); assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); - OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); + OutputPointer_set_Int32( + env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0) + ); } if( pStmt ){ - NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); + NativePointerHolder_set(S3JniNph(sqlite3_stmt), jStmt, pStmt); }else{ - /* Happens for comments and whitespace */ - UNREF_L(jStmt); + /* Happens for comments and whitespace. */ + S3JniUnrefLocal(jStmt); jStmt = 0; } }else{ - UNREF_L(jStmt); + S3JniUnrefLocal(jStmt); jStmt = 0; } -#if 0 - if( 0!=rc ){ - MARKER(("prepare rc = %d\n", rc)); + if( jOutStmt ){ + OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_stmt), + jOutStmt, jStmt); } -#endif - OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); return (jint)rc; } -JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, - jint nMax, jobject jOutStmt, jobject outTail){ - return sqlite3_jni_prepare_v123(1, env, self, jDb, baSql, nMax, 0, +S3JniApi(sqlite3_prepare(),jint,1prepare)( + JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail +){ + return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, - jint nMax, jobject jOutStmt, jobject outTail){ - return sqlite3_jni_prepare_v123(2, env, self, jDb, baSql, nMax, 0, +S3JniApi(sqlite3_prepare_v2(),jint,1prepare_1v2)( + JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail +){ + return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, - jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ - return sqlite3_jni_prepare_v123(3, env, self, jDb, baSql, nMax, +S3JniApi(sqlite3_prepare_v3(),jint,1prepare_1v3)( + JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql, + jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail +){ + return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax, prepFlags, jOutStmt, outTail); } +/* +** Impl for C-to-Java of the callbacks for both sqlite3_update_hook() +** and sqlite3_preupdate_hook(). The differences are that for +** update_hook(): +** +** - pDb is NULL +** - iKey1 is the row ID +** - iKey2 is unused +*/ +static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId, + const char *zDb, const char *zTable, + sqlite3_int64 iKey1, sqlite3_int64 iKey2){ + S3JniDb * const ps = pState; + S3JniDeclLocal_env; + jstring jDbName; + jstring jTable; + const int isPre = 0!=pDb; + S3JniHook hook; -static int s3jni_progress_handler_impl(void *pP){ - S3JniDb * const ps = (S3JniDb *)pP; - JNIEnv * const env = ps->env; - int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, - ps->progress.midCallback); - IFTHREW{ - rc = s3jni_db_exception(env, ps, rc, - "sqlite3_progress_handler() callback threw"); + S3JniHook_localdup(isPre ? +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + &ps->hooks.preUpdate +#else + &S3JniHook_empty +#endif + : &ps->hooks.update, &hook); + if( !hook.jObj ){ + return; } - return rc; + jDbName = s3jni_utf8_to_jstring( zDb, -1); + jTable = jDbName ? s3jni_utf8_to_jstring( zTable, -1) : 0; + S3JniIfThrew { + S3JniExceptionClear; + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + }else{ + assert( hook.jObj ); + assert( hook.midCallback ); + assert( ps->jDb ); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( isPre ) (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, + ps->jDb, (jint)opId, jDbName, jTable, + (jlong)iKey1, (jlong)iKey2); + else +#endif + (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, + (jint)opId, jDbName, jTable, (jlong)iKey1); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("sqlite3_(pre)update_hook() callback"); + s3jni_db_exception(ps->pDb, 0, + "sqlite3_(pre)update_hook() callback threw"); + } + } + S3JniUnrefLocal(jDbName); + S3JniUnrefLocal(jTable); + S3JniHook_localundup(hook); +} + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId, + const char *zDb, const char *zTable, + sqlite3_int64 iKey1, sqlite3_int64 iKey2){ + return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable, + iKey1, iKey2); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, + const char *zTable, sqlite3_int64 nRowid){ + return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0); } -JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){ - S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0); +#if !defined(SQLITE_ENABLE_PREUPDATE_HOOK) +/* We need no-op impls for preupdate_{count,depth,blobwrite}() */ +S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)( + JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } +S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)( + JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } +S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)( + JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } +#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */ + +/* +** JNI wrapper for both sqlite3_update_hook() and +** sqlite3_preupdate_hook() (if isPre is true). +*/ +static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jlong jpDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_from_jlong(jpDb); jclass klazz; + jobject pOld = 0; jmethodID xCallback; - if( n<1 || !jProgress ){ - if(ps){ - UNREF_G(ps->progress.jObj); - memset(&ps->progress, 0, sizeof(ps->progress)); - } - sqlite3_progress_handler(ps->pDb, 0, 0, 0); - return; + S3JniHook * pHook; + + if( !ps ) return 0; + S3JniDb_mutex_enter; + pHook = isPre ? +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + &ps->hooks.preUpdate +#else + 0 +#endif + : &ps->hooks.update; + if( !pHook ){ + goto end; } - if(!ps){ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); - return; + pOld = pHook->jObj; + if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){ + goto end; + } + if( !jHook ){ + if( pOld ){ + jobject tmp = S3JniRefLocal(pOld); + S3JniUnrefGlobal(pOld); + pOld = tmp; + } + *pHook = S3JniHook_empty; +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0); + else +#endif + sqlite3_update_hook(ps->pDb, 0, 0); + goto end; } - klazz = (*env)->GetObjectClass(env, jProgress); - xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); - IFTHREW { - EXCEPTION_CLEAR; + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = isPre + ? (*env)->GetMethodID(env, klazz, "call", + "(Lorg/sqlite/jni/capi/sqlite3;" + "I" + "Ljava/lang/String;" + "Ljava/lang/String;" + "JJ)V") + : (*env)->GetMethodID(env, klazz, "call", + "(ILjava/lang/String;Ljava/lang/String;J)V"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionClear; s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching xCallback() on " - "ProgressHandler object."); + "Cannot not find matching callback on " + "(pre)update hook object."); }else{ - UNREF_G(ps->progress.jObj); - ps->progress.midCallback = xCallback; - ps->progress.jObj = REF_G(jProgress); - sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jHook); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps); + else +#endif + sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); + if( pOld ){ + jobject tmp = S3JniRefLocal(pOld); + S3JniUnrefGlobal(pOld); + pOld = tmp; + } + } +end: + S3JniDb_mutex_leave; + return pOld; +} + + +S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)( + JniArgsEnvClass, jlong jpDb, jobject jHook +){ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + return s3jni_updatepre_hook(env, 1, jpDb, jHook); +#else + return NULL; +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +} + +/* Impl for sqlite3_preupdate_{new,old}(). */ +static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb, + jint iCol, jobject jOut){ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb); + int rc = SQLITE_MISUSE; + if( pDb ){ + sqlite3_value * pOut = 0; + int (*fOrig)(sqlite3*,int,sqlite3_value**) = + isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old; + rc = fOrig(pDb, (int)iCol, &pOut); + if( 0==rc ){ + jobject pWrap = new_java_sqlite3_value(env, pOut); + if( !pWrap ){ + rc = SQLITE_NOMEM; + } + OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_value), + jOut, pWrap); + S3JniUnrefLocal(pWrap); + } } + return rc; +#else + return SQLITE_MISUSE; +#endif +} + +S3JniApi(sqlite3_preupdate_new(),jint,1preupdate_1new)( + JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut +){ + return s3jni_preupdate_newold(env, 1, jpDb, iCol, jOut); +} + +S3JniApi(sqlite3_preupdate_old(),jint,1preupdate_1old)( + JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut +){ + return s3jni_preupdate_newold(env, 0, jpDb, iCol, jOut); } -JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ +/* Central C-to-Java sqlite3_progress_handler() proxy. */ +static int s3jni_progress_handler_impl(void *pP){ + S3JniDb * const ps = (S3JniDb *)pP; int rc = 0; - sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); - if( pStmt ){ - rc = sqlite3_reset(pStmt); + S3JniDeclLocal_env; + S3JniHook hook; + + S3JniHook_localdup(&ps->hooks.progress, &hook); + if( hook.jObj ){ + rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback); + S3JniIfThrew{ + rc = s3jni_db_exception(ps->pDb, rc, + "sqlite3_progress_handler() callback threw"); + } + S3JniHook_localundup(hook); } return rc; } -#if S3JNI_ENABLE_AUTOEXT -JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ - if(!S3JniGlobal.autoExt.isRunning){ - while( S3JniGlobal.autoExt.pHead ){ - S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); - } +S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)( + JniArgsEnvClass,jobject jDb, jint n, jobject jProgress +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + S3JniHook * const pHook = ps ? &ps->hooks.progress : 0; + + if( !ps ) return; + S3JniDb_mutex_enter; + if( n<1 || !jProgress ){ + S3JniHook_unref(pHook); + sqlite3_progress_handler(ps->pDb, 0, 0, 0); + }else{ + jclass const klazz = (*env)->GetObjectClass(env, jProgress); + jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", "()I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionClear; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on " + "ProgressHandler object."); + }else{ + S3JniUnrefGlobal(pHook->jObj); + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jProgress); + sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); + } + } + S3JniDb_mutex_leave; +} + +S3JniApi(sqlite3_randomness(),void,1randomness)( + JniArgsEnvClass, jbyteArray jTgt +){ + jbyte * const jba = s3jni_jbyteArray_bytes(jTgt); + if( jba ){ + jsize const nTgt = (*env)->GetArrayLength(env, jTgt); + sqlite3_randomness( (int)nTgt, jba ); + s3jni_jbyteArray_commit(jTgt, jba); + } +} + + +S3JniApi(sqlite3_reset(),jint,1reset)( + JniArgsEnvClass, jobject jpStmt +){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + return pStmt ? sqlite3_reset(pStmt) : SQLITE_MISUSE; +} + +/* Clears all entries from S3JniGlobal.autoExt. */ +static void s3jni_reset_auto_extension(JNIEnv *env){ + int i; + S3JniAutoExt_mutex_enter; + for( i = 0; i < SJG.autoExt.nExt; ++i ){ + S3JniAutoExtension_clear( &SJG.autoExt.aExt[i] ); } + SJG.autoExt.nExt = 0; + S3JniAutoExt_mutex_leave; +} + +S3JniApi(sqlite3_reset_auto_extension(),void,1reset_1auto_1extension)( + JniArgsEnvClass +){ + s3jni_reset_auto_extension(env); } -#endif /* S3JNI_ENABLE_AUTOEXT */ -/* sqlite3_result_text/blob() and friends. */ -static void result_blob_text(int asBlob, int as64, - int eTextRep/*only for (asBlob=0)*/, +/* Impl for sqlite3_result_text/blob() and friends. */ +static void result_blob_text(int as64 /* true for text64/blob64() mode */, + int eTextRep /* 0 for blobs, else SQLITE_UTF... */, JNIEnv * const env, sqlite3_context *pCx, jbyteArray jBa, jlong nMax){ - if(jBa){ - jbyte * const pBuf = JBA_TOC(jBa); - jsize nBa = (*env)->GetArrayLength(env, jBa); - if( nMax>=0 && nBa>(jsize)nMax ){ - nBa = (jsize)nMax; + int const asBlob = 0==eTextRep; + if( !pCx ){ + /* We should arguably emit a warning here. But where to log it? */ + return; + }else if( jBa ){ + jbyte * const pBuf = s3jni_jbyteArray_bytes(jBa); + jsize nBA = (*env)->GetArrayLength(env, jBa); + if( nMax>=0 && nBA>(jsize)nMax ){ + nBA = (jsize)nMax; /** From the sqlite docs: @@ -2902,24 +4211,23 @@ static void result_blob_text(int asBlob, int as64, Note that the text64() interfaces take an unsigned value for the length, which Java does not support. This binding takes the approach of passing on negative values to the C API, - which will, in turn fail with SQLITE_TOOBIG at some later + which will in turn fail with SQLITE_TOOBIG at some later point (recall that the sqlite3_result_xyz() family do not have result values). */ } - if(as64){ /* 64-bit... */ + if( as64 ){ /* 64-bit... */ static const jsize nLimit64 = - SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary!*/ - /* jsize is int32, not int64! */; - if(nBa > nLimit64){ + SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary*/; + if( nBA > nLimit64 ){ sqlite3_result_error_toobig(pCx); - }else if(asBlob){ - sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBa, + }else if( asBlob ){ + sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBA, SQLITE_TRANSIENT); }else{ /* text64... */ - if(encodingTypeIsValid(eTextRep)){ + if( encodingTypeIsValid(eTextRep) ){ sqlite3_result_text64(pCx, (const char *)pBuf, - (sqlite3_uint64)nBa, + (sqlite3_uint64)nBA, SQLITE_TRANSIENT, eTextRep); }else{ sqlite3_result_error_code(pCx, SQLITE_FORMAT); @@ -2927,64 +4235,71 @@ static void result_blob_text(int asBlob, int as64, } }else{ /* 32-bit... */ static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE; - if(nBa > nLimit){ + if( nBA > nLimit ){ sqlite3_result_error_toobig(pCx); - }else if(asBlob){ - sqlite3_result_blob(pCx, pBuf, (int)nBa, + }else if( asBlob ){ + sqlite3_result_blob(pCx, pBuf, (int)nBA, SQLITE_TRANSIENT); }else{ - switch(eTextRep){ + switch( eTextRep ){ case SQLITE_UTF8: - sqlite3_result_text(pCx, (const char *)pBuf, (int)nBa, + sqlite3_result_text(pCx, (const char *)pBuf, (int)nBA, SQLITE_TRANSIENT); break; case SQLITE_UTF16: - sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBa, + sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBA, SQLITE_TRANSIENT); break; case SQLITE_UTF16LE: - sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBa, + sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBA, SQLITE_TRANSIENT); break; case SQLITE_UTF16BE: - sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBa, + sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBA, SQLITE_TRANSIENT); break; } } - JBA_RELEASE(jBa, pBuf); + s3jni_jbyteArray_release(jBa, pBuf); } }else{ sqlite3_result_null(pCx); } } -JDECL(void,1result_1blob)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ - return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +S3JniApi(sqlite3_result_blob(),void,1result_1blob)( + JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax +){ + return result_blob_text(0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1blob64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax){ - return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +S3JniApi(sqlite3_result_blob64(),void,1result_1blob64)( + JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax +){ + return result_blob_text(1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1double)(JENV_CSELF, jobject jpCx, jdouble v){ +S3JniApi(sqlite3_result_double(),void,1result_1double)( + JniArgsEnvClass, jobject jpCx, jdouble v +){ sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v); } -JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, - int eTextRep){ +S3JniApi(sqlite3_result_error(),void,1result_1error)( + JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep +){ const char * zUnspecified = "Unspecified error."; jsize const baLen = (*env)->GetArrayLength(env, baMsg); - jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL; - switch(pjBuf ? eTextRep : SQLITE_UTF8){ + jbyte * const pjBuf = baMsg ? s3jni_jbyteArray_bytes(baMsg) : NULL; + switch( pjBuf ? eTextRep : SQLITE_UTF8 ){ case SQLITE_UTF8: { const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified; - sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); + int const n = pjBuf ? (int)baLen : (int)sqlite3Strlen30(zMsg); + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, n); break; } case SQLITE_UTF16: { - const void *zMsg = pjBuf - ? (const void *)pjBuf : (const void *)zUnspecified; + const void *zMsg = pjBuf; sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); break; } @@ -2994,35 +4309,49 @@ JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, "to sqlite3_result_error().", -1); break; } - JBA_RELEASE(baMsg,pjBuf); + s3jni_jbyteArray_release(baMsg,pjBuf); } -JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){ - sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR); +S3JniApi(sqlite3_result_error_code(),void,1result_1error_1code)( + JniArgsEnvClass, jobject jpCx, jint v +){ + sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), (int)v); } -JDECL(void,1result_1error_1nomem)(JENV_CSELF, jobject jpCx){ +S3JniApi(sqlite3_result_error_nomem(),void,1result_1error_1nomem)( + JniArgsEnvClass, jobject jpCx +){ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1error_1toobig)(JENV_CSELF, jobject jpCx){ +S3JniApi(sqlite3_result_error_toobig(),void,1result_1error_1toobig)( + JniArgsEnvClass, jobject jpCx +){ sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1int)(JENV_CSELF, jobject jpCx, jint v){ +S3JniApi(sqlite3_result_int(),void,1result_1int)( + JniArgsEnvClass, jobject jpCx, jint v +){ sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v); } -JDECL(void,1result_1int64)(JENV_CSELF, jobject jpCx, jlong v){ +S3JniApi(sqlite3_result_int64(),void,1result_1int64)( + JniArgsEnvClass, jobject jpCx, jlong v +){ sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); } -JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ - if(v){ - ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v); - if(rjv){ - sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, RESULT_JAVA_VAL_STRING, - ResultJavaVal_finalizer); +S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)( + JniArgsEnvClass, jobject jpCx, jobject v +){ + sqlite3_context * pCx = PtrGet_sqlite3_context(jpCx); + if( !pCx ) return; + else if( v ){ + jobject const rjv = S3JniRefGlobal(v); + if( rjv ){ + sqlite3_result_pointer(pCx, rjv, + ResultJavaValuePtrStr, S3Jni_jobject_finalizer); }else{ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } @@ -3031,108 +4360,206 @@ JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ } } -JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){ +S3JniApi(sqlite3_result_null(),void,1result_1null)( + JniArgsEnvClass, jobject jpCx +){ sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ - return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +S3JniApi(sqlite3_result_text(),void,1result_1text)( + JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax +){ + return result_blob_text(0, SQLITE_UTF8, env, + PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax, - jint eTextRep){ - return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +S3JniApi(sqlite3_result_text64(),void,1result_1text64)( + JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax, + jint eTextRep +){ + return result_blob_text(1, eTextRep, env, + PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){ - sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal)); +S3JniApi(sqlite3_result_value(),void,1result_1value)( + JniArgsEnvClass, jobject jpCx, jobject jpSVal +){ + sqlite3_result_value(PtrGet_sqlite3_context(jpCx), + PtrGet_sqlite3_value(jpSVal)); } -JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){ +S3JniApi(sqlite3_result_zeroblob(),void,1result_1zeroblob)( + JniArgsEnvClass, jobject jpCx, jint v +){ sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v); } -JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){ - return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +S3JniApi(sqlite3_result_zeroblob64(),jint,1result_1zeroblob64)( + JniArgsEnvClass, jobject jpCx, jlong v +){ + return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), + (sqlite3_int64)v); } -JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ - return s3jni_commit_rollback_hook(0, env, jDb, jHook); +S3JniApi(sqlite3_rollback_hook(),jobject,1rollback_1hook)( + JniArgsEnvClass, jlong jpDb, jobject jHook +){ + return s3jni_commit_rollback_hook(0, env, jpDb, jHook); } -/* sqlite3_set_authorizer() callback proxy. */ -static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, - const char*z2,const char*z3){ +/* Callback for sqlite3_set_authorizer(). */ +int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, + const char*z2,const char*z3){ S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; - jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0; - jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; - jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; - jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; - S3JniHook const * const pHook = &ps->authHook; - int rc; + S3JniDeclLocal_env; + S3JniHook hook; + int rc = 0; - assert( pHook->jObj ); - rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op, - s0, s1, s3, s3); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback"); - EXCEPTION_CLEAR; - } - UNREF_L(s0); - UNREF_L(s1); - UNREF_L(s2); - UNREF_L(s3); + S3JniHook_localdup(&ps->hooks.auth, &hook ); + if( hook.jObj ){ + jstring const s0 = z0 ? s3jni_utf8_to_jstring( z0, -1) : 0; + jstring const s1 = z1 ? s3jni_utf8_to_jstring( z1, -1) : 0; + jstring const s2 = z2 ? s3jni_utf8_to_jstring( z2, -1) : 0; + jstring const s3 = z3 ? s3jni_utf8_to_jstring( z3, -1) : 0; + + rc = (*env)->CallIntMethod(env, hook.jObj, hook.midCallback, (jint)op, + s0, s1, s3, s3); + S3JniIfThrew{ + rc = s3jni_db_exception(ps->pDb, rc, "sqlite3_set_authorizer() callback"); + } + S3JniUnrefLocal(s0); + S3JniUnrefLocal(s1); + S3JniUnrefLocal(s2); + S3JniUnrefLocal(s3); + S3JniHook_localundup(hook); + } return rc; } -JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - S3JniHook * const pHook = ps ? &ps->authHook : 0; +S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)( + JniArgsEnvClass,jobject jDb, jobject jHook +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + S3JniHook * const pHook = ps ? &ps->hooks.auth : 0; + int rc = 0; if( !ps ) return SQLITE_MISUSE; - else if( !jHook ){ - S3JniHook_unref(env, pHook, 0); - return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); + S3JniDb_mutex_enter; + if( !jHook ){ + S3JniHook_unref(pHook); + rc = sqlite3_set_authorizer( ps->pDb, 0, 0 ); }else{ - int rc = 0; + jclass klazz; if( pHook->jObj ){ if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){ /* Same object - this is a no-op. */ + S3JniDb_mutex_leave; return 0; } - S3JniHook_unref(env, pHook, 0); + S3JniHook_unref(pHook); } - pHook->jObj = REF_G(jHook); - pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); - pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, - "xAuth", + pHook->jObj = S3JniRefGlobal(jHook); + klazz = (*env)->GetObjectClass(env, jHook); + pHook->midCallback = (*env)->GetMethodID(env, klazz, + "call", "(I" "Ljava/lang/String;" "Ljava/lang/String;" "Ljava/lang/String;" "Ljava/lang/String;" ")I"); - IFTHREW { - S3JniHook_unref(env, pHook, 0); - return s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Error setting up Java parts of authorizer hook."); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Error setting up Java parts of authorizer hook."); + }else{ + rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); } - rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); - if( rc ) S3JniHook_unref(env, pHook, 0); - return rc; + if( rc ) S3JniHook_unref(pHook); } + S3JniDb_mutex_leave; + return rc; } +S3JniApi(sqlite3_set_auxdata(),void,1set_1auxdata)( + JniArgsEnvClass, jobject jCx, jint n, jobject jAux +){ + sqlite3_set_auxdata(PtrGet_sqlite3_context(jCx), (int)n, + S3JniRefGlobal(jAux), S3Jni_jobject_finalizer); +} -JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ - sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), +S3JniApi(sqlite3_set_last_insert_rowid(),void,1set_1last_1insert_1rowid)( + JniArgsEnvClass, jobject jpDb, jlong rowId +){ + sqlite3_set_last_insert_rowid(PtrGet_sqlite3(jpDb), (sqlite3_int64)rowId); } -FIXME_THREADING(nphCache) -JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, - jboolean reset ){ +S3JniApi(sqlite3_shutdown(),jint,1shutdown)( + JniArgsEnvClass +){ + s3jni_reset_auto_extension(env); +#ifdef SQLITE_ENABLE_SQLLOG + S3JniHook_unref(&SJG.hook.sqllog); +#endif + S3JniHook_unref(&SJG.hook.configlog); + /* Free up S3JniDb recycling bin. */ + S3JniDb_mutex_enter; { + while( S3JniGlobal.perDb.aFree ){ + S3JniDb * const d = S3JniGlobal.perDb.aFree; + S3JniGlobal.perDb.aFree = d->pNext; + S3JniDb_clear(env, d); + sqlite3_free(d); + } + } S3JniDb_mutex_leave; + S3JniGlobal_mutex_enter; { + /* Free up S3JniUdf recycling bin. */ + while( S3JniGlobal.udf.aFree ){ + S3JniUdf * const u = S3JniGlobal.udf.aFree; + S3JniGlobal.udf.aFree = u->pNext; + u->pNext = 0; + S3JniUdf_free(env, u, 0); + } + } S3JniGlobal_mutex_leave; + S3JniHook_mutex_enter; { + /* Free up S3JniHook recycling bin. */ + while( S3JniGlobal.hook.aFree ){ + S3JniHook * const u = S3JniGlobal.hook.aFree; + S3JniGlobal.hook.aFree = u->pNext; + u->pNext = 0; + assert( !u->doXDestroy ); + assert( !u->jObj ); + assert( !u->jExtra ); + sqlite3_free( u ); + } + } S3JniHook_mutex_leave; + /* Free up env cache. */ + S3JniEnv_mutex_enter; { + while( SJG.envCache.aHead ){ + S3JniEnv_uncache( SJG.envCache.aHead->env ); + } + } S3JniEnv_mutex_leave; +#if 0 + /* + ** Is automatically closing any still-open dbs a good idea? We will + ** get rid of the perDb list once sqlite3 gets a per-db client + ** state, at which point we won't have a central list of databases + ** to close. + */ + S3JniDb_mutex_enter; + while( SJG.perDb.pHead ){ + s3jni_close_db(env, SJG.perDb.pHead->jDb, 2); + } + S3JniDb_mutex_leave; +#endif + /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */ + return sqlite3_shutdown(); +} + +S3JniApi(sqlite3_status(),jint,1status)( + JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset +){ int iCur = 0, iHigh = 0; int rc = sqlite3_status( op, &iCur, &iHigh, reset ); if( 0==rc ){ @@ -3142,9 +4569,10 @@ JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, return (jint)rc; } -FIXME_THREADING(nphCache) -JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, - jboolean reset ){ +S3JniApi(sqlite3_status64(),jint,1status64)( + JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset +){ sqlite3_int64 iCur = 0, iHigh = 0; int rc = sqlite3_status64( op, &iCur, &iHigh, reset ); if( 0==rc ){ @@ -3154,12 +4582,19 @@ JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh return (jint)rc; } +S3JniApi(sqlite3_stmt_status(),jint,1stmt_1status)( + JniArgsEnvClass, jobject jStmt, jint op, jboolean reset +){ + return sqlite3_stmt_status(PtrGet_sqlite3_stmt(jStmt), + (int)op, reset ? 1 : 0); +} + + static int s3jni_strlike_glob(int isLike, JNIEnv *const env, jbyteArray baG, jbyteArray baT, jint escLike){ int rc = 0; - jbyte * const pG = JBA_TOC(baG); - jbyte * const pT = pG ? JBA_TOC(baT) : 0; - OOM_CHECK(pT); + jbyte * const pG = s3jni_jbyteArray_bytes(baG); + jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0; /* Note that we're relying on the byte arrays having been NUL-terminated on the Java side. */ @@ -3167,296 +4602,328 @@ static int s3jni_strlike_glob(int isLike, JNIEnv *const env, ? sqlite3_strlike((const char *)pG, (const char *)pT, (unsigned int)escLike) : sqlite3_strglob((const char *)pG, (const char *)pT); - JBA_RELEASE(baG, pG); - JBA_RELEASE(baT, pT); + s3jni_jbyteArray_release(baG, pG); + s3jni_jbyteArray_release(baT, pT); return rc; } -JDECL(jint,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ +S3JniApi(sqlite3_strglob(),jint,1strglob)( + JniArgsEnvClass, jbyteArray baG, jbyteArray baT +){ return s3jni_strlike_glob(0, env, baG, baT, 0); } -JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ +S3JniApi(sqlite3_strlike(),jint,1strlike)( + JniArgsEnvClass, jbyteArray baG, jbyteArray baT, jint escChar +){ return s3jni_strlike_glob(1, env, baG, baT, escChar); } -JDECL(jint,1shutdown)(JENV_CSELF){ - S3JniGlobal_S3JniEnvCache_clear(); - /* Do not clear S3JniGlobal.jvm: it's legal to call - sqlite3_initialize() again to restart the lib. */ - return sqlite3_shutdown(); -} - -JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){ +S3JniApi(sqlite3_sql(),jstring,1sql)( + JniArgsEnvClass, jobject jpStmt +){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); jstring rv = 0; if( pStmt ){ const char * zSql = 0; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); zSql = sqlite3_sql(pStmt); - rv = s3jni_utf8_to_jstring(jc, zSql, -1); - OOM_CHECK(rv); + rv = s3jni_utf8_to_jstring( zSql, -1); } return rv; } -JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ - int rc = SQLITE_MISUSE; +S3JniApi(sqlite3_step(),jint,1step)( + JniArgsEnvClass,jobject jStmt +){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); - if(pStmt){ - rc = sqlite3_step(pStmt); + return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE; +} + +S3JniApi(sqlite3_table_column_metadata(),int,1table_1column_1metadata)( + JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName, + jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull, + jobject jPrimaryKey, jobject jAutoinc +){ + sqlite3 * const db = PtrGet_sqlite3(jDb); + char * zDbName = 0, * zTableName = 0, * zColumnName = 0; + const char * pzCollSeq = 0; + const char * pzDataType = 0; + int pNotNull = 0, pPrimaryKey = 0, pAutoinc = 0; + int rc; + + if( !db || !jDbName || !jTableName ) return SQLITE_MISUSE; + zDbName = s3jni_jstring_to_utf8(jDbName,0); + zTableName = zDbName ? s3jni_jstring_to_utf8(jTableName,0) : 0; + zColumnName = (zTableName && jColumnName) + ? s3jni_jstring_to_utf8(jColumnName,0) : 0; + rc = zTableName + ? sqlite3_table_column_metadata(db, zDbName, zTableName, + zColumnName, &pzDataType, &pzCollSeq, + &pNotNull, &pPrimaryKey, &pAutoinc) + : SQLITE_NOMEM; + if( 0==rc ){ + jstring jseq = jCollSeq + ? (pzCollSeq ? s3jni_utf8_to_jstring(pzCollSeq, -1) : 0) + : 0; + jstring jdtype = jDataType + ? (pzDataType ? s3jni_utf8_to_jstring(pzDataType, -1) : 0) + : 0; + if( (jCollSeq && pzCollSeq && !jseq) + || (jDataType && pzDataType && !jdtype) ){ + rc = SQLITE_NOMEM; + }else{ + if( jNotNull ) OutputPointer_set_Bool(env, jNotNull, pNotNull); + if( jPrimaryKey ) OutputPointer_set_Bool(env, jPrimaryKey, pPrimaryKey); + if( jAutoinc ) OutputPointer_set_Bool(env, jAutoinc, pAutoinc); + if( jCollSeq ) OutputPointer_set_String(env, jCollSeq, jseq); + if( jDataType ) OutputPointer_set_String(env, jDataType, jdtype); + } + S3JniUnrefLocal(jseq); + S3JniUnrefLocal(jdtype); } + sqlite3_free(zDbName); + sqlite3_free(zTableName); + sqlite3_free(zColumnName); return rc; } static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ S3JniDb * const ps = (S3JniDb *)pC; - JNIEnv * const env = ps->env; + S3JniDeclLocal_env; jobject jX = NULL /* the tracer's X arg */; jobject jP = NULL /* the tracer's P arg */; jobject jPUnref = NULL /* potentially a local ref to jP */; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - int rc; - int createStmt = 0; - switch(traceflag){ + int rc = 0; + S3JniHook hook; + + S3JniHook_localdup(&ps->hooks.trace, &hook ); + if( !hook.jObj ){ + return 0; + } + switch( traceflag ){ case SQLITE_TRACE_STMT: - jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); - if(!jX) return SQLITE_NOMEM; - /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/ - createStmt = 1; + jX = s3jni_utf8_to_jstring( (const char *)pX, -1); + if( !jX ) rc = SQLITE_NOMEM; break; case SQLITE_TRACE_PROFILE: - jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1, + jX = (*env)->NewObject(env, SJG.g.cLong, SJG.g.ctorLong1, (jlong)*((sqlite3_int64*)pX)); // hmm. ^^^ (*pX) really is zero. // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); - if(!jX) return SQLITE_NOMEM; - createStmt = 1; + s3jni_oom_check( jX ); + if( !jX ) rc = SQLITE_NOMEM; break; case SQLITE_TRACE_ROW: - createStmt = 1; break; case SQLITE_TRACE_CLOSE: - jP = ps->jDb; + jP = jPUnref = S3JniRefLocal(ps->jDb); break; default: - assert(!"cannot happen - unkown trace flag"); - return SQLITE_ERROR; - } - if( createStmt ){ - jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); - if(!jP){ - UNREF_L(jX); - return SQLITE_NOMEM; - } + assert(!"cannot happen - unknown trace flag"); + rc = SQLITE_ERROR; } - assert(jP); - rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, - ps->trace.midCallback, - (jint)traceflag, jP, jX); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); - EXCEPTION_CLEAR; - rc = SQLITE_ERROR; + if( 0==rc ){ + if( !jP ){ + /* Create a new temporary sqlite3_stmt wrapper */ + jP = jPUnref = new_java_sqlite3_stmt(env, pP); + if( !jP ){ + rc = SQLITE_NOMEM; + } + } + if( 0==rc ){ + assert(jP); + rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback, + (jint)traceflag, jP, jX); + S3JniIfThrew{ + rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, + "sqlite3_trace_v2() callback threw."); + } + } } - UNREF_L(jPUnref); - UNREF_L(jX); + S3JniUnrefLocal(jPUnref); + S3JniUnrefLocal(jX); + S3JniHook_localundup(hook); return rc; } -JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; +S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)( + JniArgsEnvClass,jobject jDb, jint traceMask, jobject jTracer +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + int rc; + + if( !ps ) return SQLITE_MISUSE; if( !traceMask || !jTracer ){ - if(ps){ - UNREF_G(ps->trace.jObj); - memset(&ps->trace, 0, sizeof(ps->trace)); + S3JniDb_mutex_enter; + rc = (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); + S3JniHook_unref(&ps->hooks.trace); + S3JniDb_mutex_leave; + }else{ + jclass const klazz = (*env)->GetObjectClass(env, jTracer); + S3JniHook hook = S3JniHook_empty; + hook.midCallback = (*env)->GetMethodID( + env, klazz, "call", "(ILjava/lang/Object;Ljava/lang/Object;)I" + ); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionClear; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching call() on " + "TracerCallback object."); + }else{ + hook.jObj = S3JniRefGlobal(jTracer); + S3JniDb_mutex_enter; + rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); + if( 0==rc ){ + S3JniHook_unref(&ps->hooks.trace); + ps->hooks.trace = hook /* transfer ownership of reference */; + }else{ + S3JniHook_unref(&hook); + } + S3JniDb_mutex_leave; } - return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); - } - if(!ps) return SQLITE_NOMEM; - klazz = (*env)->GetObjectClass(env, jTracer); - ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", - "(ILjava/lang/Object;Ljava/lang/Object;)I"); - IFTHREW { - EXCEPTION_CLEAR; - return s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching xCallback() on Tracer object."); } - ps->trace.jObj = REF_G(jTracer); - return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); + return rc; } -static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, - const char *zTable, sqlite3_int64 nRowid){ - S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; - /* ACHTUNG: this will break if zDb or zTable contain chars which are - different in MUTF-8 than UTF-8. That seems like a low risk, - but it's possible. */ - jstring jDbName; - jstring jTable; - jDbName = (*env)->NewStringUTF(env, zDb); - jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0; - IFTHREW { - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); - }else{ - (*env)->CallVoidMethod(env, ps->updateHook.jObj, - ps->updateHook.midCallback, - (jint)opId, jDbName, jTable, (jlong)nRowid); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("update hook"); - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); +S3JniApi(sqlite3_txn_state(),jint,1txn_1state)( + JniArgsEnvClass,jobject jDb, jstring jSchema +){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + int rc = SQLITE_MISUSE; + if( pDb ){ + char * zSchema = jSchema + ? s3jni_jstring_to_utf8(jSchema, 0) + : 0; + if( !jSchema || (zSchema && jSchema) ){ + rc = sqlite3_txn_state(pDb, zSchema); + sqlite3_free(zSchema); + }else{ + rc = SQLITE_NOMEM; } } - UNREF_L(jDbName); - UNREF_L(jTable); + return rc; } - -JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; - jobject pOld = 0; - jmethodID xCallback; - S3JniHook * const pHook = &ps->updateHook; - if(!ps){ - s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); - return 0; - } - pOld = pHook->jObj; - if(pOld && jHook && - (*env)->IsSameObject(env, pOld, jHook)){ - return pOld; - } - if( !jHook ){ - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); - pOld = tmp; - } - memset(pHook, 0, sizeof(S3JniHook)); - sqlite3_update_hook(ps->pDb, 0, 0); - return pOld; - } - klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook", - "(ILjava/lang/String;Ljava/lang/String;J)V"); - IFTHREW { - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching callback on " - "update hook object."); - }else{ - pHook->midCallback = xCallback; - pHook->jObj = REF_G(jHook); - sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); - pOld = tmp; - } - } - return pOld; +S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)( + JniArgsEnvClass, jlong jpDb, jobject jHook +){ + return s3jni_updatepre_hook(env, 0, jpDb, jHook); } -JDECL(jbyteArray,1value_1blob)(JENV_CSELF, jobject jpSVal){ - sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); - int const nLen = sqlite3_value_bytes(sv); - const jbyte * pBytes = sqlite3_value_blob(sv); - jbyteArray const jba = pBytes - ? (*env)->NewByteArray(env, (jsize)nLen) +S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0; + int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0; + + s3jni_oom_check( nLen ? !!pBytes : 1 ); + return pBytes + ? s3jni_new_jbyteArray(pBytes, nLen) : NULL; - if(jba){ - (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); - } - return jba; } - -JDECL(jdouble,1value_1double)(JENV_CSELF, jobject jpSVal){ - return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal)); +S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + return sv ? sqlite3_value_bytes(sv) : 0; } - -JDECL(jobject,1value_1dup)(JENV_CSELF, jobject jpSVal){ - sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal)); - return sv ? new_sqlite3_value_wrapper(env, sv) : 0; +S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + return sv ? sqlite3_value_bytes16(sv) : 0; } -JDECL(void,1value_1free)(JENV_CSELF, jobject jpSVal){ - sqlite3_value_free(PtrGet_sqlite3_value(jpSVal)); -} -JDECL(jint,1value_1int)(JENV_CSELF, jobject jpSVal){ - return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal)); +S3JniApi(sqlite3_value_double(),jdouble,1value_1double)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0); } -JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){ - return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal)); + +S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0; + jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0; + if( sd && !rv ) { + /* OOM */ + sqlite3_value_free(sd); + } + return rv; } -JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){ - ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING); - return rv ? rv->jObj : NULL; +S3JniApi(sqlite3_value_free(),void,1value_1free)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + if( sv ){ + sqlite3_value_free(sv); + } } -JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){ - sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); - int const n = sqlite3_value_bytes16(sv); - const void * const p = sqlite3_value_text16(sv); - return s3jni_text16_to_jstring(env, p, n); +S3JniApi(sqlite3_value_int(),jint,1value_1int)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + return (jint) (sv ? sqlite3_value_int(sv) : 0); } -JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){ - sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); - int const n = sqlite3_value_bytes(sv); - const unsigned char * const p = sqlite3_value_text(sv); - return s3jni_new_jbyteArray(env, p, n); +S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL); } -static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ - int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal)); - jbyteArray jba; - const jbyte * pBytes; - switch(mode){ - case SQLITE_UTF16: - pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal)); - break; - case SQLITE_UTF16LE: - pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal)); - break; - case SQLITE_UTF16BE: - pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal)); - break; - default: - assert(!"not possible"); - return NULL; - } - jba = pBytes - ? (*env)->NewByteArray(env, (jsize)nLen) - : NULL; - if(jba){ - (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); - } - return jba; +S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + return sv + ? sqlite3_value_pointer(sv, ResultJavaValuePtrStr) + : 0; } -JDECL(jbyteArray,1value_1text16)(JENV_CSELF, jobject jpSVal){ - return value_text16(SQLITE_UTF16, env, jpSVal); +S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0; + int const n = p ? sqlite3_value_bytes(sv) : 0; + return p ? s3jni_new_jbyteArray(p, n) : 0; } -JDECL(jbyteArray,1value_1text16le)(JENV_CSELF, jobject jpSVal){ - return value_text16(SQLITE_UTF16LE, env, jpSVal); +#if 0 +// this impl might prove useful. +S3JniApi(sqlite3_value_text(),jstring,1value_1text)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0; + int const n = p ? sqlite3_value_bytes(sv) : 0; + return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0; } +#endif -JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){ - return value_text16(SQLITE_UTF16BE, env, jpSVal); +S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)( + JniArgsEnvClass, jlong jpSVal +){ + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + const int n = sv ? sqlite3_value_bytes16(sv) : 0; + const void * const p = sv ? sqlite3_value_text16(sv) : 0; + return p ? s3jni_text16_to_jstring(env, p, n) : 0; } -JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ +JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){ MARKER(("\nVarious bits of internal info:\n")); puts("FTS5 is " #ifdef SQLITE_ENABLE_FTS5 @@ -3469,29 +4936,63 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ puts("sizeofs:"); #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); - SO(S3JniEnvCache); + SO(jmethodID); + SO(jfieldID); + SO(S3JniEnv); SO(S3JniHook); SO(S3JniDb); - SO(S3JniClassNames); - printf("\t(^^^ %u NativePointerHolder subclasses)\n", - (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *))); + SO(S3JniNphOps); + printf("\t(^^^ %u NativePointerHolder/OutputPointer.T types)\n", + (unsigned)S3Jni_NphCache_size); SO(S3JniGlobal); + SO(S3JniGlobal.nph); + SO(S3JniGlobal.metrics); SO(S3JniAutoExtension); SO(S3JniUdf); +#undef SO +#ifdef SQLITE_JNI_ENABLE_METRICS printf("Cache info:\n"); - printf("\tNativePointerHolder cache: %u misses, %u hits\n", - S3JniGlobal.metrics.nphCacheMisses, - S3JniGlobal.metrics.nphCacheHits); - printf("\tJNIEnv cache %u misses, %u hits\n", - S3JniGlobal.metrics.envCacheMisses, - S3JniGlobal.metrics.envCacheHits); + printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n", + SJG.metrics.nEnvAlloc, SJG.metrics.nEnvMiss, + SJG.metrics.nEnvHit); + printf("Mutex entry:" + "\n\tglobal = %u" + "\n\tenv = %u" + "\n\tnph = %u for S3JniNphOp init" + "\n\thook = %u" + "\n\tperDb = %u" + "\n\tautoExt list = %u" + "\n\tS3JniUdf = %u (free-list)" + "\n\tmetrics = %u\n", + SJG.metrics.nMutexGlobal, SJG.metrics.nMutexEnv, + SJG.metrics.nMutexNph, SJG.metrics.nMutexHook, + SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt, + SJG.metrics.nMutexUdf, SJG.metrics.nMetrics); + puts("Allocs:"); + printf("\tS3JniDb: %u alloced (*%u = %u bytes), %u recycled\n", + SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb), + (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)), + SJG.metrics.nPdbRecycled); + printf("\tS3JniUdf: %u alloced (*%u = %u bytes), %u recycled\n", + SJG.metrics.nUdfAlloc, (unsigned) sizeof(S3JniUdf), + (unsigned)(SJG.metrics.nUdfAlloc * sizeof(S3JniUdf)), + SJG.metrics.nUdfRecycled); + printf("\tS3JniHook: %u alloced (*%u = %u bytes), %u recycled\n", + SJG.metrics.nHookAlloc, (unsigned) sizeof(S3JniHook), + (unsigned)(SJG.metrics.nHookAlloc * sizeof(S3JniHook)), + SJG.metrics.nHookRecycled); + printf("\tS3JniEnv: %u alloced (*%u = %u bytes)\n", + SJG.metrics.nEnvAlloc, (unsigned) sizeof(S3JniEnv), + (unsigned)(SJG.metrics.nEnvAlloc * sizeof(S3JniEnv))); puts("Java-side UDF calls:"); -#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T) +#define UDF(T) printf("\t%-8s = %u\n", "x" #T, SJG.metrics.udf.n##T) UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); #undef UDF printf("xDestroy calls across all callback types: %u\n", - S3JniGlobal.metrics.nDestroy); -#undef SO + SJG.metrics.nDestroy); +#else + puts("Built without SQLITE_JNI_ENABLE_METRICS."); +#endif } //////////////////////////////////////////////////////////////////////// @@ -3500,36 +5001,35 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ #ifdef SQLITE_ENABLE_FTS5 /* Creates a verbose JNI Fts5 function name. */ -#define JFuncNameFtsXA(Suffix) \ - Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix -#define JFuncNameFtsApi(Suffix) \ - Java_org_sqlite_jni_fts5_1api_ ## Suffix -#define JFuncNameFtsTok(Suffix) \ - Java_org_sqlite_jni_fts5_tokenizer_ ## Suffix - -#define JDECLFtsXA(ReturnType,Suffix) \ +#define JniFuncNameFtsXA(Suffix) \ + Java_org_sqlite_jni_fts5_Fts5ExtensionApi_ ## Suffix +#define JniFuncNameFtsApi(Suffix) \ + Java_org_sqlite_jni_fts5_fts5_1api_ ## Suffix +#define JniFuncNameFtsTok(Suffix) \ + Java_org_sqlite_jni_fts5_fts5_tokenizer_ ## Suffix + +#define JniDeclFtsXA(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ - JFuncNameFtsXA(Suffix) -#define JDECLFtsApi(ReturnType,Suffix) \ + JniFuncNameFtsXA(Suffix) +#define JniDeclFtsApi(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ - JFuncNameFtsApi(Suffix) -#define JDECLFtsTok(ReturnType,Suffix) \ + JniFuncNameFtsApi(Suffix) +#define JniDeclFtsTok(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ - JFuncNameFtsTok(Suffix) + JniFuncNameFtsTok(Suffix) -#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api) -#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer) -#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context) -#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer) -#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() +#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_api)) +#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_tokenizer)) +#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Context)) +#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Tokenizer)) +#define s3jni_ftsext() &sFts5Api/*singleton from sqlite3.c*/ +#define Fts5ExtDecl Fts5ExtensionApi const * const ext = s3jni_ftsext() /** State for binding Java-side FTS5 auxiliary functions. */ typedef struct { - JNIEnv * env; /* env registered from */; jobject jObj /* functor instance */; - jclass klazz /* jObj's class */; jobject jUserData /* 2nd arg to JNI binding of xCreateFunction(), ostensibly the 3rd arg to the lib-level xCreateFunction(), except @@ -3540,38 +5040,38 @@ typedef struct { } Fts5JniAux; static void Fts5JniAux_free(Fts5JniAux * const s){ - JNIEnv * const env = s->env; - if(env){ + S3JniDeclLocal_env; + if( env ){ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ - s3jni_call_xDestroy(env, s->jObj, s->klazz); - UNREF_G(s->jObj); - UNREF_G(s->klazz); - UNREF_G(s->jUserData); + s3jni_call_xDestroy(s->jObj); + S3JniUnrefGlobal(s->jObj); + S3JniUnrefGlobal(s->jUserData); } sqlite3_free(s->zFuncName); sqlite3_free(s); } static void Fts5JniAux_xDestroy(void *p){ - if(p) Fts5JniAux_free(p); + if( p ) Fts5JniAux_free(p); } static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ - Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux)); - if(s){ - const char * zSig = - "(Lorg/sqlite/jni/Fts5ExtensionApi;" - "Lorg/sqlite/jni/Fts5Context;" - "Lorg/sqlite/jni/sqlite3_context;" - "[Lorg/sqlite/jni/sqlite3_value;)V"; + Fts5JniAux * s = s3jni_malloc( sizeof(Fts5JniAux)); + + if( s ){ + jclass klazz; memset(s, 0, sizeof(Fts5JniAux)); - s->env = env; - s->jObj = REF_G(jObj); - s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); - s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); - IFTHREW{ - EXCEPTION_REPORT; - EXCEPTION_CLEAR; + s->jObj = S3JniRefGlobal(jObj); + klazz = (*env)->GetObjectClass(env, jObj); + s->jmid = (*env)->GetMethodID(env, klazz, "call", + "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;" + "Lorg/sqlite/jni/fts5/Fts5Context;" + "Lorg/sqlite/jni/capi/sqlite3_context;" + "[Lorg/sqlite/jni/capi/sqlite3_value;)V"); + S3JniUnrefLocal(klazz); + S3JniIfThrew{ + S3JniExceptionReport; + S3JniExceptionClear; Fts5JniAux_free(s); s = 0; } @@ -3579,34 +5079,37 @@ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ return s; } -static inline Fts5ExtensionApi const * s3jni_ftsext(void){ - return &sFts5Api/*singleton from sqlite3.c*/; -} - -static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.Fts5Context, sv); +static inline jobject new_java_Fts5Context(JNIEnv * const env, Fts5Context *sv){ + return NativePointerHolder_new(env, S3JniNph(Fts5Context), sv); } -static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.fts5_api, sv); +static inline jobject new_java_fts5_api(JNIEnv * const env, fts5_api *sv){ + return NativePointerHolder_new(env, S3JniNph(fts5_api), sv); } -/** - Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton - instance, or NULL on OOM. +/* +** Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton +** instance, or NULL on OOM. */ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ - S3JniEnvCache * const row = S3JniGlobal_env_cache(env); - if( !row->jFtsExt ){ - row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi, - s3jni_ftsext()); - if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); + if( !SJG.fts5.jExt ){ + S3JniGlobal_mutex_enter; + if( !SJG.fts5.jExt ){ + jobject const pNPH = NativePointerHolder_new( + env, S3JniNph(Fts5ExtensionApi), s3jni_ftsext() + ); + if( pNPH ){ + SJG.fts5.jExt = S3JniRefGlobal(pNPH); + S3JniUnrefLocal(pNPH); + } + } + S3JniGlobal_mutex_leave; } - return row->jFtsExt; + return SJG.fts5.jExt; } /* -** Return a pointer to the fts5_api instance for database connection -** db. If an error occurs, return NULL and leave an error in the +** Returns a pointer to the fts5_api instance for database connection +** db. If an error occurs, returns NULL and leaves an error in the ** database handle (accessible using sqlite3_errcode()/errmsg()). */ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ @@ -3620,54 +5123,68 @@ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ return pRet; } -JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +JniDeclFtsApi(jobject,getInstanceForDb)(JniArgsEnvClass,jobject jDb){ + S3JniDb * const ps = S3JniDb_from_java(jDb); +#if 0 jobject rv = 0; - if(!ps) return 0; - else if(ps->jFtsApi){ - rv = ps->jFtsApi; + if( !ps ) return 0; + else if( ps->fts.jApi ){ + rv = ps->fts.jApi; }else{ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb); if( pApi ){ - rv = new_fts5_api_wrapper(env, pApi); - ps->jFtsApi = rv ? REF_G(rv) : 0; + rv = new_java_fts5_api(env, pApi); + ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0; } } return rv; +#else + if( ps && !ps->fts.jApi ){ + S3JniDb_mutex_enter; + if( !ps->fts.jApi ){ + fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb); + if( pApi ){ + jobject const rv = new_java_fts5_api(env, pApi); + ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0; + } + } + S3JniDb_mutex_leave; + } + return ps ? ps->fts.jApi : 0; +#endif } -JDECLFtsXA(jobject,getInstance)(JENV_CSELF){ +JniDeclFtsXA(jobject,getInstance)(JniArgsEnvClass){ return s3jni_getFts5ExensionApi(env); } -JDECLFtsXA(jint,xColumnCount)(JENV_OSELF,jobject jCtx){ +JniDeclFtsXA(jint,xColumnCount)(JniArgsEnvObj,jobject jCtx){ Fts5ExtDecl; - return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); + return (jint)ext->xColumnCount(PtrGet_Fts5Context(jCtx)); } -JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32){ +JniDeclFtsXA(jint,xColumnSize)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOut32){ Fts5ExtDecl; int n1 = 0; - int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); + int const rc = ext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1); return rc; } -JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, +JniDeclFtsXA(jint,xColumnText)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut){ Fts5ExtDecl; const char *pz = 0; int pn = 0; - int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, + int rc = ext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, &pz, &pn); if( 0==rc ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; + jstring jstr = pz ? s3jni_utf8_to_jstring( pz, pn) : 0; if( pz ){ if( jstr ){ OutputPointer_set_String(env, jOut, jstr); - UNREF_L(jstr)/*jOut has a reference*/; + S3JniUnrefLocal(jstr)/*jOut has a reference*/; }else{ rc = SQLITE_NOMEM; } @@ -3676,17 +5193,17 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, return (jint)rc; } -JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut64){ +JniDeclFtsXA(jint,xColumnTotalSize)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; - int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); + int const rc = ext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); return (jint)rc; } -/** - Proxy for fts5_extension_function instances plugged in via - fts5_api::xCreateFunction(). +/* +** Proxy for fts5_extension_function instances plugged in via +** fts5_api::xCreateFunction(). */ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, Fts5Context *pFts, @@ -3694,46 +5211,48 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, int argc, sqlite3_value **argv){ Fts5JniAux * const pAux = pApi->xUserData(pFts); - JNIEnv *env; jobject jpCx = 0; jobjectArray jArgv = 0; jobject jpFts = 0; jobject jFXA; int rc; + S3JniDeclLocal_env; + assert(pAux); - env = pAux->env; jFXA = s3jni_getFts5ExensionApi(env); if( !jFXA ) goto error_oom; - jpFts = new_Fts5Context_wrapper(env, pFts); - if(!jpFts) goto error_oom; + jpFts = new_java_Fts5Context(env, pFts); + if( !jpFts ) goto error_oom; rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv); - if(rc) goto error_oom; + if( rc ) goto error_oom; (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid, jFXA, jpFts, jpCx, jArgv); - IFTHREW{ - EXCEPTION_CLEAR; - udf_report_exception(pCx, pAux->zFuncName, "xFunction"); + S3JniIfThrew{ + udf_report_exception(env, 1, pCx, pAux->zFuncName, "call"); } - UNREF_L(jpFts); - UNREF_L(jpCx); - UNREF_L(jArgv); + udf_unargs(env, jpCx, argc, jArgv); + S3JniUnrefLocal(jpFts); + S3JniUnrefLocal(jpCx); + S3JniUnrefLocal(jArgv); return; error_oom: + s3jni_db_oom( sqlite3_context_db_handle(pCx) ); assert( !jArgv ); assert( !jpCx ); - UNREF_L(jpFts); + S3JniUnrefLocal(jpFts); sqlite3_result_error_nomem(pCx); return; } -JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, +JniDeclFtsApi(jint,xCreateFunction)(JniArgsEnvObj, jstring jName, jobject jUserData, jobject jFunc){ fts5_api * const pApi = PtrGet_fts5_api(jSelf); int rc; - char const * zName; + char * zName; Fts5JniAux * pAux; + assert(pApi); - zName = JSTR_TOC(jName); + zName = s3jni_jstring_to_utf8( jName, 0); if(!zName) return SQLITE_NOMEM; pAux = Fts5JniAux_alloc(env, jFunc); if( pAux ){ @@ -3744,42 +5263,45 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, rc = SQLITE_NOMEM; } if( 0==rc ){ - pAux->jUserData = jUserData ? REF_G(jUserData) : 0; - pAux->zFuncName = sqlite3_mprintf("%s", zName) - /* OOM here is non-fatal. Ignore it. */; + pAux->jUserData = jUserData ? S3JniRefGlobal(jUserData) : 0; + pAux->zFuncName = zName; + }else{ + sqlite3_free(zName); } - JSTR_RELEASE(jName, zName); return (jint)rc; } -typedef struct s3jni_fts5AuxData s3jni_fts5AuxData; -struct s3jni_fts5AuxData { - JNIEnv *env; +typedef struct S3JniFts5AuxData S3JniFts5AuxData; +/* +** TODO: this middle-man struct is no longer necessary. Conider +** removing it and passing around jObj itself instead. +*/ +struct S3JniFts5AuxData { jobject jObj; }; -static void s3jni_fts5AuxData_xDestroy(void *x){ - if(x){ - s3jni_fts5AuxData * const p = x; - if(p->jObj){ - JNIEnv *env = p->env; - s3jni_call_xDestroy(env, p->jObj, 0); - UNREF_G(p->jObj); +static void S3JniFts5AuxData_xDestroy(void *x){ + if( x ){ + S3JniFts5AuxData * const p = x; + if( p->jObj ){ + S3JniDeclLocal_env; + s3jni_call_xDestroy(p->jObj); + S3JniUnrefGlobal(p->jObj); } sqlite3_free(x); } } -JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ +JniDeclFtsXA(jobject,xGetAuxdata)(JniArgsEnvObj,jobject jCtx, jboolean bClear){ Fts5ExtDecl; jobject rv = 0; - s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); - if(pAux){ - if(bClear){ + S3JniFts5AuxData * const pAux = ext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); + if( pAux ){ + if( bClear ){ if( pAux->jObj ){ - rv = REF_L(pAux->jObj); - UNREF_G(pAux->jObj); + rv = S3JniRefLocal(pAux->jObj); + S3JniUnrefGlobal(pAux->jObj); } /* Note that we do not call xDestroy() in this case. */ sqlite3_free(pAux); @@ -3790,11 +5312,11 @@ JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ return rv; } -JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, +JniDeclFtsXA(jint,xInst)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOutPhrase, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; int n1 = 0, n2 = 2, n3 = 0; - int const rc = fext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3); + int const rc = ext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3); if( 0==rc ){ OutputPointer_set_Int32(env, jOutPhrase, n1); OutputPointer_set_Int32(env, jOutCol, n2); @@ -3803,135 +5325,117 @@ JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, return rc; } -JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){ +JniDeclFtsXA(jint,xInstCount)(JniArgsEnvObj,jobject jCtx, jobject jOut32){ Fts5ExtDecl; int nOut = 0; - int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); + int const rc = ext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut); return (jint)rc; } -JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){ +JniDeclFtsXA(jint,xPhraseCount)(JniArgsEnvObj,jobject jCtx){ Fts5ExtDecl; - return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); + return (jint)ext->xPhraseCount(PtrGet_Fts5Context(jCtx)); } -/** - Initializes jc->jPhraseIter if it needed it. -*/ -static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc, +/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ +static void s3jni_phraseIter_NToJ(JNIEnv *const env, + Fts5PhraseIter const * const pSrc, jobject jIter){ - if(!jc->jPhraseIter.klazz){ - jclass klazz = (*env)->GetObjectClass(env, jIter); - jc->jPhraseIter.klazz = REF_G(klazz); - jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J"); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); - jc->jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J"); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); - } + S3JniGlobalType * const g = &S3JniGlobal; + assert(g->fts5.jPhraseIter.fidA); + (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA, + S3JniCast_P2L(pSrc->a)); + S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.a field."); + (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB, + S3JniCast_P2L(pSrc->b)); + S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.b field."); } -/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ -static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const jc, - Fts5PhraseIter const * const pSrc, - jobject jIter){ - assert(jc->jPhraseIter.klazz); - (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidA, (jlong)pSrc->a); - EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field."); - (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b); - EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field."); +/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ +static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter, + Fts5PhraseIter * const pDest){ + S3JniGlobalType * const g = &S3JniGlobal; + assert(g->fts5.jPhraseIter.fidA); + pDest->a = S3JniCast_L2P( + (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA) + ); + S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field."); + pDest->b = S3JniCast_L2P( + (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB) + ); + S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field."); } -/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ -static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnvCache const * const jc, - jobject jIter, Fts5PhraseIter * const pDest){ - assert(jc->jPhraseIter.klazz); - pDest->a = - (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidA); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); - pDest->b = - (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); -} - -JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, +JniDeclFtsXA(jint,xPhraseFirst)(JniArgsEnvObj,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0, iOff = 0; - s3jni_phraseIter_init(env, jc, jIter); - rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase, + rc = ext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase, &iter, &iCol, &iOff); if( 0==rc ){ OutputPointer_set_Int32(env, jOutCol, iCol); OutputPointer_set_Int32(env, jOutOff, iOff); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } return rc; } -JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, +JniDeclFtsXA(jint,xPhraseFirstColumn)(JniArgsEnvObj,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0; - s3jni_phraseIter_init(env, jc, jIter); - rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase, + rc = ext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase, &iter, &iCol); if( 0==rc ){ OutputPointer_set_Int32(env, jOutCol, iCol); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } return rc; } -JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, +JniDeclFtsXA(void,xPhraseNext)(JniArgsEnvObj,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0, iOff = 0; - if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; - s3jni_phraseIter_JToN(env, jc, jIter, &iter); - fext->xPhraseNext(PtrGet_Fts5Context(jCtx), - &iter, &iCol, &iOff); + s3jni_phraseIter_JToN(env, jIter, &iter); + ext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff); OutputPointer_set_Int32(env, jOutCol, iCol); OutputPointer_set_Int32(env, jOutOff, iOff); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } -JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, +JniDeclFtsXA(void,xPhraseNextColumn)(JniArgsEnvObj,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0; - if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; - s3jni_phraseIter_JToN(env, jc, jIter, &iter); - fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol); + s3jni_phraseIter_JToN(env, jIter, &iter); + ext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol); OutputPointer_set_Int32(env, jOutCol, iCol); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } -JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ +JniDeclFtsXA(jint,xPhraseSize)(JniArgsEnvObj,jobject jCtx, jint iPhrase){ Fts5ExtDecl; - return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); + return (jint)ext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); } -/** - State for use with xQueryPhrase() and xTokenize(). -*/ +/* State for use with xQueryPhrase() and xTokenize(). */ struct s3jni_xQueryPhraseState { - JNIEnv *env; - Fts5ExtensionApi const * fext; - S3JniEnvCache const * jc; - jmethodID midCallback; - jobject jCallback; - jobject jFcx; + Fts5ExtensionApi const * ext; + jmethodID midCallback; /* jCallback->call() method */ + jobject jCallback; /* Fts5ExtensionApi.XQueryPhraseCallback instance */ + jobject jFcx; /* (Fts5Context*) for xQueryPhrase() + callback. This is NOT the instance that is + passed to xQueryPhrase(), it's the one + created by xQueryPhrase() for use by its + callback. */ /* State for xTokenize() */ struct { const char * zPrev; @@ -3942,138 +5446,138 @@ struct s3jni_xQueryPhraseState { static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, Fts5Context * pFcx, void *pData){ - /* TODO: confirm that the Fts5Context passed to this function is - guaranteed to be the same one passed to xQueryPhrase(). If it's - not, we'll have to create a new wrapper object on every call. */ - struct s3jni_xQueryPhraseState const * s = pData; - JNIEnv * const env = s->env; + struct s3jni_xQueryPhraseState * const s = pData; + S3JniDeclLocal_env; + + if( !s->jFcx ){ + s->jFcx = new_java_Fts5Context(env, pFcx); + if( !s->jFcx ) return SQLITE_NOMEM; + } int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, - s->jc->jFtsExt, s->jFcx); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback"); - EXCEPTION_CLEAR; + SJG.fts5.jExt, s->jFcx); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("xQueryPhrase() callback"); + S3JniExceptionClear; rc = SQLITE_ERROR; } return rc; } -JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, +JniDeclFtsXA(jint,xQueryPhrase)(JniArgsEnvObj,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + int rc; struct s3jni_xQueryPhraseState s; jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; - s.env = env; - s.jc = jc; s.jCallback = jCallback; - s.jFcx = jFcx; - s.fext = fext; - s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", - "(Lorg.sqlite.jni.Fts5ExtensionApi;" - "Lorg.sqlite.jni.Fts5Context;)I"); - EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method."); - return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s, - s3jni_xQueryPhrase); + s.jFcx = 0; + s.ext = ext; + s.midCallback = (*env)->GetMethodID(env, klazz, "call", + "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;" + "Lorg/sqlite/jni/fts5/Fts5Context;)I"); + S3JniUnrefLocal(klazz); + S3JniExceptionIsFatal("Could not extract xQueryPhraseCallback.call() method."); + rc = ext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s, + s3jni_xQueryPhrase); + S3JniUnrefLocal(s.jFcx); + return (jint)rc; } -JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){ +JniDeclFtsXA(jint,xRowCount)(JniArgsEnvObj,jobject jCtx, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; - int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); + int const rc = ext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); return (jint)rc; } -JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){ +JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){ Fts5ExtDecl; - return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); + return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx)); } -JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ +JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; - s3jni_fts5AuxData * pAux; - pAux = sqlite3_malloc(sizeof(*pAux)); - if(!pAux){ - if(jAux){ - // Emulate how xSetAuxdata() behaves when it cannot alloc - // its auxdata wrapper. - s3jni_call_xDestroy(env, jAux, 0); + S3JniFts5AuxData * pAux; + + pAux = s3jni_malloc( sizeof(*pAux)); + if( !pAux ){ + if( jAux ){ + /* Emulate how xSetAuxdata() behaves when it cannot alloc + ** its auxdata wrapper. */ + s3jni_call_xDestroy(jAux); } return SQLITE_NOMEM; } - pAux->env = env; - pAux->jObj = REF_G(jAux); - rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, - s3jni_fts5AuxData_xDestroy); + pAux->jObj = S3JniRefGlobal(jAux); + rc = ext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, + S3JniFts5AuxData_xDestroy); return rc; } -/** - xToken() impl for xTokenize(). -*/ +/* xToken() impl for xTokenize(). */ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, int nZ, int iStart, int iEnd){ int rc; + S3JniDeclLocal_env; struct s3jni_xQueryPhraseState * const s = p; - JNIEnv * const env = s->env; jbyteArray jba; - if( s->tok.zPrev == z && s->tok.nPrev == nZ ){ - jba = s->tok.jba; - }else{ - if(s->tok.jba){ - UNREF_L(s->tok.jba); - } - s->tok.zPrev = z; - s->tok.nPrev = nZ; - s->tok.jba = (*env)->NewByteArray(env, (jint)nZ); - if( !s->tok.jba ) return SQLITE_NOMEM; - jba = s->tok.jba; - (*env)->SetByteArrayRegion(env, jba, 0, (jint)nZ, (const jbyte*)z); - } + + S3JniUnrefLocal(s->tok.jba); + s->tok.zPrev = z; + s->tok.nPrev = nZ; + s->tok.jba = s3jni_new_jbyteArray(z, nZ); + if( !s->tok.jba ) return SQLITE_NOMEM; + jba = s->tok.jba; rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, (jint)tFlags, jba, (jint)iStart, (jint)iEnd); + S3JniIfThrew { + S3JniExceptionWarnCallbackThrew("xTokenize() callback"); + rc = SQLITE_ERROR; + } return rc; } -/** - Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() +/* +** Proxy for Fts5ExtensionApi.xTokenize() and +** fts5_tokenizer.xTokenize() */ -static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, +static jint s3jni_fts5_xTokenize(JniArgsEnvObj, S3JniNphOp const *pRef, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); struct s3jni_xQueryPhraseState s; int rc = 0; - jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0; + jbyte * const pText = jCallback ? s3jni_jbyteArray_bytes(jbaText) : 0; jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0; jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; memset(&s, 0, sizeof(s)); - s.env = env; - s.jc = jc; s.jCallback = jCallback; s.jFcx = jFcx; - s.fext = fext; - s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I"); - IFTHREW { - EXCEPTION_REPORT; - EXCEPTION_CLEAR; - JBA_RELEASE(jbaText, pText); + s.ext = ext; + s.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I[BII)I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionReport; + S3JniExceptionClear; + s3jni_jbyteArray_release(jbaText, pText); return SQLITE_ERROR; } - s.tok.jba = REF_L(jbaText); + s.tok.jba = S3JniRefLocal(jbaText); s.tok.zPrev = (const char *)pText; s.tok.nPrev = (int)nText; - if( zClassName == S3JniClassNames.Fts5ExtensionApi ){ - rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), + if( pRef == S3JniNph(Fts5ExtensionApi) ){ + rc = ext->xTokenize(PtrGet_Fts5Context(jFcx), (const char *)pText, (int)nText, &s, s3jni_xTokenize_xToken); - }else if( zClassName == S3JniClassNames.fts5_tokenizer ){ + }else if( pRef == S3JniNph(fts5_tokenizer) ){ fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf); rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags, (const char *)pText, (int)nText, @@ -4081,30 +5585,30 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, }else{ (*env)->FatalError(env, "This cannot happen. Maintenance required."); } - if(s.tok.jba){ + if( s.tok.jba ){ assert( s.tok.zPrev ); - UNREF_L(s.tok.jba); + S3JniUnrefLocal(s.tok.jba); } - JBA_RELEASE(jbaText, pText); + s3jni_jbyteArray_release(jbaText, pText); return (jint)rc; } -JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText, - jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5ExtensionApi, +JniDeclFtsXA(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jbyteArray jbaText, + jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5ExtensionApi), 0, jFcx, jbaText, jCallback); } -JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags, - jbyteArray jbaText, jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5Tokenizer, +JniDeclFtsTok(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jint tokFlags, + jbyteArray jbaText, jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5Tokenizer), tokFlags, jFcx, jbaText, jCallback); } -JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ +JniDeclFtsXA(jobject,xUserData)(JniArgsEnvObj,jobject jFcx){ Fts5ExtDecl; - Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx)); + Fts5JniAux * const pAux = ext->xUserData(PtrGet_Fts5Context(jFcx)); return pAux ? pAux->jUserData : 0; } @@ -4114,7 +5618,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ // End of the main API bindings. Start of SQLTester bits... //////////////////////////////////////////////////////////////////////// -#ifdef S3JNI_ENABLE_SQLTester +#ifdef SQLITE_JNI_ENABLE_SQLTester typedef struct SQLTesterJni SQLTesterJni; struct SQLTesterJni { sqlite3_int64 nDup; @@ -4151,9 +5655,10 @@ static void SQLTester_dup_func( char *z; int n = sqlite3_value_bytes(argv[0]); SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context); + S3JniDeclLocal_env; ++p->nDup; - if( n>0 && (pOut = sqlite3_malloc( (n+16)&~7 ))!=0 ){ + if( n>0 && (pOut = s3jni_malloc( (n+16)&~7 ))!=0 ){ pOut[0] = 0x2bbf4b7c; z = (char*)&pOut[1]; memcpy(z, sqlite3_value_text(argv[0]), n); @@ -4279,19 +5784,19 @@ static int SQLTester_strnotglob(const char *zGlob, const char *z){ } JNIEXPORT jint JNICALL -Java_org_sqlite_jni_tester_SQLTester_strglob( - JENV_CSELF, jbyteArray baG, jbyteArray baT +Java_org_sqlite_jni_capi_SQLTester_strglob( + JniArgsEnvClass, jbyteArray baG, jbyteArray baT ){ int rc = 0; - jbyte * const pG = JBA_TOC(baG); - jbyte * const pT = pG ? JBA_TOC(baT) : 0; - OOM_CHECK(pT); + jbyte * const pG = s3jni_jbyteArray_bytes(baG); + jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0; + s3jni_oom_fatal(pT); /* Note that we're relying on the byte arrays having been NUL-terminated on the Java side. */ rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT); - JBA_RELEASE(baG, pG); - JBA_RELEASE(baT, pT); + s3jni_jbyteArray_release(baG, pG); + s3jni_jbyteArray_release(baT, pT); return rc; } @@ -4306,115 +5811,89 @@ static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, } JNIEXPORT void JNICALL -Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ +Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions(JniArgsEnvClass){ sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); } -#endif /* S3JNI_ENABLE_SQLTester */ +#endif /* SQLITE_JNI_ENABLE_SQLTester */ //////////////////////////////////////////////////////////////////////// // End of SQLTester bindings. Start of lower-level bits. //////////////////////////////////////////////////////////////////////// - -/** - Uncaches the current JNIEnv from the S3JniGlobal state, clearing any - resources owned by that cache entry and making that slot available - for re-use. It is important that the Java-side decl of this - function be declared as synchronous. -*/ -JNIEXPORT jboolean JNICALL -Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ - return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; -} - -/** - Called during static init of the SQLite3Jni class to sync certain - compile-time constants to Java-space. - - This routine is part of the reason why we have to #include - sqlite3.c instead of sqlite3.h. +/* +** Called during static init of the CApi class to set up global +** state. */ JNIEXPORT void JNICALL -Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ - enum JType { - JTYPE_INT, - JTYPE_BOOL - }; - typedef struct { - const char *zName; - enum JType jtype; - int value; - } ConfigFlagEntry; - const ConfigFlagEntry aLimits[] = { - {"SQLITE_ENABLE_FTS5", JTYPE_BOOL, -#ifdef SQLITE_ENABLE_FTS5 - 1 -#else - 0 -#endif - }, - {"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE}, - {"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH}, - {"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH}, - {"SQLITE_LIMIT_SQL_LENGTH", JTYPE_INT, SQLITE_LIMIT_SQL_LENGTH}, - {"SQLITE_MAX_SQL_LENGTH", JTYPE_INT, SQLITE_MAX_SQL_LENGTH}, - {"SQLITE_LIMIT_COLUMN", JTYPE_INT, SQLITE_LIMIT_COLUMN}, - {"SQLITE_MAX_COLUMN", JTYPE_INT, SQLITE_MAX_COLUMN}, - {"SQLITE_LIMIT_EXPR_DEPTH", JTYPE_INT, SQLITE_LIMIT_EXPR_DEPTH}, - {"SQLITE_MAX_EXPR_DEPTH", JTYPE_INT, SQLITE_MAX_EXPR_DEPTH}, - {"SQLITE_LIMIT_COMPOUND_SELECT", JTYPE_INT, SQLITE_LIMIT_COMPOUND_SELECT}, - {"SQLITE_MAX_COMPOUND_SELECT", JTYPE_INT, SQLITE_MAX_COMPOUND_SELECT}, - {"SQLITE_LIMIT_VDBE_OP", JTYPE_INT, SQLITE_LIMIT_VDBE_OP}, - {"SQLITE_MAX_VDBE_OP", JTYPE_INT, SQLITE_MAX_VDBE_OP}, - {"SQLITE_LIMIT_FUNCTION_ARG", JTYPE_INT, SQLITE_LIMIT_FUNCTION_ARG}, - {"SQLITE_MAX_FUNCTION_ARG", JTYPE_INT, SQLITE_MAX_FUNCTION_ARG}, - {"SQLITE_LIMIT_ATTACHED", JTYPE_INT, SQLITE_LIMIT_ATTACHED}, - {"SQLITE_MAX_ATTACHED", JTYPE_INT, SQLITE_MAX_ATTACHED}, - {"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_LIMIT_LIKE_PATTERN_LENGTH}, - {"SQLITE_MAX_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_MAX_LIKE_PATTERN_LENGTH}, - {"SQLITE_LIMIT_VARIABLE_NUMBER", JTYPE_INT, SQLITE_LIMIT_VARIABLE_NUMBER}, - {"SQLITE_MAX_VARIABLE_NUMBER", JTYPE_INT, SQLITE_MAX_VARIABLE_NUMBER}, - {"SQLITE_LIMIT_TRIGGER_DEPTH", JTYPE_INT, SQLITE_LIMIT_TRIGGER_DEPTH}, - {"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH}, - {"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS}, - {"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS}, - {0,0} - }; - jfieldID fieldId; - const ConfigFlagEntry * pConfFlag; +Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){ + jclass klazz; memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); - if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){ + if( (*env)->GetJavaVM(env, &SJG.jvm) ){ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); return; } -#if 0 - /* Just for sanity checking... */ - (void)S3JniGlobal_env_cache(env); - if( !S3JniGlobal.envCache.aHead ){ - (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); - return; + + /* Grab references to various global classes and objects... */ + SJG.g.cLong = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Long")); + S3JniExceptionIsFatal("Error getting reference to Long class."); + SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong, + "", "(J)V"); + S3JniExceptionIsFatal("Error getting reference to Long constructor."); + + SJG.g.cString = S3JniRefGlobal((*env)->FindClass(env,"java/lang/String")); + S3JniExceptionIsFatal("Error getting reference to String class."); + SJG.g.ctorStringBA = + (*env)->GetMethodID(env, SJG.g.cString, + "", "([BLjava/nio/charset/Charset;)V"); + S3JniExceptionIsFatal("Error getting reference to String(byte[],Charset) ctor."); + SJG.g.stringGetBytes = + (*env)->GetMethodID(env, SJG.g.cString, + "getBytes", "(Ljava/nio/charset/Charset;)[B"); + S3JniExceptionIsFatal("Error getting reference to String.getBytes(Charset)."); + + { /* java.nio.charset.StandardCharsets.UTF_8 */ + jfieldID fUtf8; + klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); + S3JniExceptionIsFatal("Error getting reference to StandardCharsets class."); + fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8", + "Ljava/nio/charset/Charset;"); + S3JniExceptionIsFatal("Error getting StandardCharsets.UTF_8 field."); + SJG.g.oCharsetUtf8 = + S3JniRefGlobal((*env)->GetStaticObjectField(env, klazz, fUtf8)); + S3JniExceptionIsFatal("Error getting reference to StandardCharsets.UTF_8."); + S3JniUnrefLocal(klazz); } - assert( 1 == S3JniGlobal.metrics.envCacheMisses ); - assert( env == S3JniGlobal.envCache.aHead->env ); - assert( 0 != S3JniGlobal.envCache.aHead->g.cObj ); + +#ifdef SQLITE_ENABLE_FTS5 + klazz = (*env)->FindClass(env, "org/sqlite/jni/fts5/Fts5PhraseIter"); + S3JniExceptionIsFatal("Error getting reference to org.sqlite.jni.fts5.Fts5PhraseIter."); + SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J"); + S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field."); + SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J"); + S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field."); + S3JniUnrefLocal(klazz); #endif - for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ - char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; - fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig); - EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); - //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); - assert(fieldId); - switch(pConfFlag->jtype){ - case JTYPE_INT: - (*env)->SetStaticIntField(env, jKlazz, fieldId, (jint)pConfFlag->value); - break; - case JTYPE_BOOL: - (*env)->SetStaticBooleanField(env, jKlazz, fieldId, - pConfFlag->value ? JNI_TRUE : JNI_FALSE); - break; - } - EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed."); - } + SJG.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.mutex ); + SJG.hook.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.hook.mutex ); + SJG.nph.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.nph.mutex ); + SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.envCache.mutex ); + SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.perDb.mutex ); + SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.autoExt.mutex ); + +#if S3JNI_METRICS_MUTEX + SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.metrics.mutex ); +#endif + + sqlite3_shutdown() + /* So that it becomes legal for Java-level code to call + ** sqlite3_config(). */; } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index bcb55c4f1..bf6df7ac9 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1,1734 +1,2151 @@ /* DO NOT EDIT THIS FILE - it is machine generated */ #include -/* Header for class org_sqlite_jni_SQLite3Jni */ +/* Header for class org_sqlite_jni_capi_CApi */ -#ifndef _Included_org_sqlite_jni_SQLite3Jni -#define _Included_org_sqlite_jni_SQLite3Jni +#ifndef _Included_org_sqlite_jni_capi_CApi +#define _Included_org_sqlite_jni_capi_CApi #ifdef __cplusplus extern "C" { #endif -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS -#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS 0L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ -#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DENY -#define org_sqlite_jni_SQLite3Jni_SQLITE_DENY 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW 6L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER 7L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DELETE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DELETE 9L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX 10L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE 11L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX 12L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE 13L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER 14L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW 15L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW 17L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INSERT -#define org_sqlite_jni_SQLite3Jni_SQLITE_INSERT 18L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA -#define org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA 19L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READ -#define org_sqlite_jni_SQLite3Jni_SQLITE_READ 20L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SELECT -#define org_sqlite_jni_SQLite3Jni_SQLITE_SELECT 21L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION -#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION 22L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE -#define org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE 23L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH -#define org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH 24L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETACH -#define org_sqlite_jni_SQLite3Jni_SQLITE_DETACH 25L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE 26L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX 27L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE 28L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE 29L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE 30L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION -#define org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION 31L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT -#define org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT 32L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE -#define org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE 33L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATIC 0LL -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -1LL -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT 0L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH 6L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE 7L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS 9L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX 10L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX 11L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE 13L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE 14L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE 15L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI 17L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 18L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 19L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG 21L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE 22L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE 23L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ 24L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ 25L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL 26L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC 27L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE 28L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE 29L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER -#define org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT -#define org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TEXT -#define org_sqlite_jni_SQLite3Jni_SQLITE_TEXT 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_BLOB -#define org_sqlite_jni_SQLite3Jni_SQLITE_BLOB 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NULL -#define org_sqlite_jni_SQLite3Jni_SQLITE_NULL 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME 1000L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE 1001L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY 1002L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG 1007L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP 1008L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE 1009L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE 1010L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML 1013L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL 1014L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW 1015L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX 1019L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED 0L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT 7L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE 9L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS 10L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL 12L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX -#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX 12L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 -#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE -#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE -#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 -#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED -#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE 6L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER 7L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY 9L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL 10L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE 11L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME 12L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA 14L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER 15L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE 18L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE 19L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED 20L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC 21L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO 22L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE 23L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK 24L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS 25L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU 26L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER 27L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER 28L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE 29L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB 30L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT 34L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION 35L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT 36L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE 37L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES 38L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START 39L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER 40L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE 41L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE -#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE 42L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE 0L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K 32L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K 64L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K 128L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K 256L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND 512L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL 1024L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE 8192L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC 16384L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI 64L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY 128L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX 32768L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX 65536L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE 131072L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE 262144L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE 33554432L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW 16777216L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB 256L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL 2048L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB 512L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL 4096L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB 1024L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL 8192L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL 16384L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL 524288L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE -#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT -#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB -#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK -#define org_sqlite_jni_SQLite3Jni_SQLITE_OK 0L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR -#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_PERM -#define org_sqlite_jni_SQLite3Jni_SQLITE_PERM 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT -#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY -#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED 6L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM -#define org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM 7L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY -#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT -#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT 9L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR 10L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT 11L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND -#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND 12L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FULL -#define org_sqlite_jni_SQLite3Jni_SQLITE_FULL 13L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN -#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN 14L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL -#define org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL 15L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY -#define org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA -#define org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA 17L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG -#define org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG 18L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT 19L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH -#define org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH 20L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE -#define org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE 21L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS -#define org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS 22L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH -#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH 23L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT -#define org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT 24L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_RANGE -#define org_sqlite_jni_SQLite3Jni_SQLITE_RANGE 25L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB -#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB 26L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE -#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE 27L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING -#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING 28L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROW -#define org_sqlite_jni_SQLite3Jni_SQLITE_ROW 100L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DONE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DONE 101L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ -#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ 257L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY -#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY 513L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT -#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT 769L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ 266L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ 522L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE 778L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC 1034L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC 1290L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE 1546L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT 1802L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK 2058L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK 2314L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE 2570L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED 2826L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM 3082L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS 3338L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK 3850L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE 4106L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE 4362L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN 4618L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE 4874L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK 5130L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP 5386L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK 5642L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT 5898L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP 6154L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH 6410L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH 6666L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE 6922L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH 7178L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC 7434L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC 7690L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA 8202L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS -#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS 8458L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE 262L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB -#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB 518L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY -#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY 261L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT -#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT 517L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT -#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT 773L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR -#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR 270L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR -#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR 526L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH -#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH 782L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH -#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH 1038L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK -#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK 1550L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB -#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB 267L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE 523L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX 779L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY -#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY 264L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK -#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK 520L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK -#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK 776L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED -#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED 1032L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT -#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT 1288L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY -#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY 1544L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK -#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK 516L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK 275L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK 531L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY 787L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION 1043L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL 1299L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY 1555L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER 1811L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE 2067L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB 2323L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID 2579L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED 2835L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE -#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE 3091L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL 283L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK -#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK 539L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX 284L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER -#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER 279L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY -#define org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY 256L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY -#define org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY -#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE -#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED 0L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK 6L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE 7L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT 9L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE 5L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN 6L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS 7L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED -#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED 99L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL -#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL -#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY -#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT -#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE -#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW -#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE -#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE -#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE 0L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ -#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE -#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC 2048L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY -#define org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY 524288L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS -#define org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS 2097152L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE 8L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT 16L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE 32L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH 64L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE 65L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB 66L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP 67L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE 68L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT 69L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL 71L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS 72L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT 73L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET 74L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION -#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT -#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS -#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS 2L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY -#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS -#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS 4L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK -#define org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK 1L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_FAIL -#define org_sqlite_jni_SQLite3Jni_SQLITE_FAIL 3L -#undef org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE -#define org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE 5L -/* - * Class: org_sqlite_jni_SQLite3Jni +#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS +#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE +#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ +#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_DENY +#define org_sqlite_jni_capi_CApi_SQLITE_DENY 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_IGNORE +#define org_sqlite_jni_capi_CApi_SQLITE_IGNORE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_DELETE +#define org_sqlite_jni_capi_CApi_SQLITE_DELETE 9L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX 10L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE 11L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX 12L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE 13L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER 14L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW 15L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER 16L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW 17L +#undef org_sqlite_jni_capi_CApi_SQLITE_INSERT +#define org_sqlite_jni_capi_CApi_SQLITE_INSERT 18L +#undef org_sqlite_jni_capi_CApi_SQLITE_PRAGMA +#define org_sqlite_jni_capi_CApi_SQLITE_PRAGMA 19L +#undef org_sqlite_jni_capi_CApi_SQLITE_READ +#define org_sqlite_jni_capi_CApi_SQLITE_READ 20L +#undef org_sqlite_jni_capi_CApi_SQLITE_SELECT +#define org_sqlite_jni_capi_CApi_SQLITE_SELECT 21L +#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION +#define org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION 22L +#undef org_sqlite_jni_capi_CApi_SQLITE_UPDATE +#define org_sqlite_jni_capi_CApi_SQLITE_UPDATE 23L +#undef org_sqlite_jni_capi_CApi_SQLITE_ATTACH +#define org_sqlite_jni_capi_CApi_SQLITE_ATTACH 24L +#undef org_sqlite_jni_capi_CApi_SQLITE_DETACH +#define org_sqlite_jni_capi_CApi_SQLITE_DETACH 25L +#undef org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE +#define org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE 26L +#undef org_sqlite_jni_capi_CApi_SQLITE_REINDEX +#define org_sqlite_jni_capi_CApi_SQLITE_REINDEX 27L +#undef org_sqlite_jni_capi_CApi_SQLITE_ANALYZE +#define org_sqlite_jni_capi_CApi_SQLITE_ANALYZE 28L +#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE +#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE 29L +#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE +#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE 30L +#undef org_sqlite_jni_capi_CApi_SQLITE_FUNCTION +#define org_sqlite_jni_capi_CApi_SQLITE_FUNCTION 31L +#undef org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT +#define org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT 32L +#undef org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE +#define org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE 33L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATIC +#define org_sqlite_jni_capi_CApi_SQLITE_STATIC 0LL +#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT +#define org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT -1LL +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT +#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS 9L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX 10L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX 11L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE 13L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE 14L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE 15L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG 16L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI 17L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2 +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2 18L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2 +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2 19L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG 21L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE 22L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE 23L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ 24L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ 25L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL 26L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC 27L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE 28L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE +#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE 29L +#undef org_sqlite_jni_capi_CApi_SQLITE_INTEGER +#define org_sqlite_jni_capi_CApi_SQLITE_INTEGER 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_FLOAT +#define org_sqlite_jni_capi_CApi_SQLITE_FLOAT 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_TEXT +#define org_sqlite_jni_capi_CApi_SQLITE_TEXT 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_BLOB +#define org_sqlite_jni_capi_CApi_SQLITE_BLOB 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_NULL +#define org_sqlite_jni_capi_CApi_SQLITE_NULL 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME 1000L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE 1001L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY 1002L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG 1007L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP 1008L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE 1009L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE 1010L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML 1013L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL 1014L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW 1015L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX +#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX 1019L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE 9L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS 10L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL 12L +#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX +#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 12L +#undef org_sqlite_jni_capi_CApi_SQLITE_UTF8 +#define org_sqlite_jni_capi_CApi_SQLITE_UTF8 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16LE +#define org_sqlite_jni_capi_CApi_SQLITE_UTF16LE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16BE +#define org_sqlite_jni_capi_CApi_SQLITE_UTF16BE 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16 +#define org_sqlite_jni_capi_CApi_SQLITE_UTF16 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED +#define org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY 9L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL 10L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE 11L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME 12L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA 14L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER 15L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME 16L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE 18L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE 19L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED 20L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC 21L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO 22L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE 23L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK 24L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS 25L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU 26L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER 27L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER 28L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE 29L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB 30L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT 34L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION 35L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT 36L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE 37L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES 38L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START 39L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER 40L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE 41L +#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE +#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE 42L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE +#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED +#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED +#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING +#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE +#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512 +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K 16L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K 32L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K 64L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K 128L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K 256L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND 512L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL 1024L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE 8192L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC +#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC 16384L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER 9L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH 10L +#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS +#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS 11L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI 64L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY 128L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX 32768L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX 65536L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE 131072L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE 262144L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW 16777216L +#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE +#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L +#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT +#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE +#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB +#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_OK +#define org_sqlite_jni_capi_CApi_SQLITE_OK 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR +#define org_sqlite_jni_capi_CApi_SQLITE_ERROR 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_INTERNAL +#define org_sqlite_jni_capi_CApi_SQLITE_INTERNAL 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_PERM +#define org_sqlite_jni_capi_CApi_SQLITE_PERM 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT +#define org_sqlite_jni_capi_CApi_SQLITE_ABORT 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY +#define org_sqlite_jni_capi_CApi_SQLITE_BUSY 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED +#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_NOMEM +#define org_sqlite_jni_capi_CApi_SQLITE_NOMEM 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY +#define org_sqlite_jni_capi_CApi_SQLITE_READONLY 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT +#define org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT 9L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR 10L +#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT +#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT 11L +#undef org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND +#define org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND 12L +#undef org_sqlite_jni_capi_CApi_SQLITE_FULL +#define org_sqlite_jni_capi_CApi_SQLITE_FULL 13L +#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN +#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN 14L +#undef org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL +#define org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL 15L +#undef org_sqlite_jni_capi_CApi_SQLITE_EMPTY +#define org_sqlite_jni_capi_CApi_SQLITE_EMPTY 16L +#undef org_sqlite_jni_capi_CApi_SQLITE_SCHEMA +#define org_sqlite_jni_capi_CApi_SQLITE_SCHEMA 17L +#undef org_sqlite_jni_capi_CApi_SQLITE_TOOBIG +#define org_sqlite_jni_capi_CApi_SQLITE_TOOBIG 18L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT 19L +#undef org_sqlite_jni_capi_CApi_SQLITE_MISMATCH +#define org_sqlite_jni_capi_CApi_SQLITE_MISMATCH 20L +#undef org_sqlite_jni_capi_CApi_SQLITE_MISUSE +#define org_sqlite_jni_capi_CApi_SQLITE_MISUSE 21L +#undef org_sqlite_jni_capi_CApi_SQLITE_NOLFS +#define org_sqlite_jni_capi_CApi_SQLITE_NOLFS 22L +#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH +#define org_sqlite_jni_capi_CApi_SQLITE_AUTH 23L +#undef org_sqlite_jni_capi_CApi_SQLITE_FORMAT +#define org_sqlite_jni_capi_CApi_SQLITE_FORMAT 24L +#undef org_sqlite_jni_capi_CApi_SQLITE_RANGE +#define org_sqlite_jni_capi_CApi_SQLITE_RANGE 25L +#undef org_sqlite_jni_capi_CApi_SQLITE_NOTADB +#define org_sqlite_jni_capi_CApi_SQLITE_NOTADB 26L +#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE +#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE 27L +#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING +#define org_sqlite_jni_capi_CApi_SQLITE_WARNING 28L +#undef org_sqlite_jni_capi_CApi_SQLITE_ROW +#define org_sqlite_jni_capi_CApi_SQLITE_ROW 100L +#undef org_sqlite_jni_capi_CApi_SQLITE_DONE +#define org_sqlite_jni_capi_CApi_SQLITE_DONE 101L +#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ +#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ 257L +#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY +#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY 513L +#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT +#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT 769L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ 266L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ 522L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE 778L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC 1034L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC 1290L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE 1546L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT 1802L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK 2058L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK 2314L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE 2570L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED 2826L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM 3082L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS 3338L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK 3850L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE 4106L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE 4362L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN 4618L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE 4874L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK 5130L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP 5386L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK 5642L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT 5898L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP 6154L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH 6410L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH 6666L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE 6922L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH 7178L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC 7434L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC 7690L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA 8202L +#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS +#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS 8458L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE +#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE 262L +#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB +#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB 518L +#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY +#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY 261L +#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT +#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT 517L +#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT +#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT 773L +#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR +#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR 270L +#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR +#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR 526L +#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH +#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH 782L +#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH +#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH 1038L +#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK +#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK 1550L +#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB +#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB 267L +#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE +#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE 523L +#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX +#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX 779L +#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY +#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY 264L +#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK +#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK 520L +#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK +#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK 776L +#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED +#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED 1032L +#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT +#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT 1288L +#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY +#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY 1544L +#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK +#define org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK 516L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK 275L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK 531L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY 787L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION 1043L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL 1299L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY 1555L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER 1811L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE 2067L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB 2323L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID 2579L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED 2835L +#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE +#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE 3091L +#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL +#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL 283L +#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK +#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK 539L +#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX +#define org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX 284L +#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER +#define org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER 279L +#undef org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY +#define org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY 256L +#undef org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY +#define org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE +#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY +#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE +#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE +#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED +#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED +#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW +#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK +#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE +#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT +#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT 9L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE 5L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN 6L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS 7L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED +#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED 99L +#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL +#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL +#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY +#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY 16L +#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT +#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE +#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW +#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE +#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE +#define org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE 0L +#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_READ +#define org_sqlite_jni_capi_CApi_SQLITE_TXN_READ 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE +#define org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC +#define org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC 2048L +#undef org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY +#define org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY 524288L +#undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS +#define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE 8L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT 16L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE 32L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH 64L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE 65L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB 66L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP 67L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE 68L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT 69L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL 71L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS 72L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT 73L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET 74L +#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION +#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L +#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT +#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS +#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS 2L +#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY +#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS +#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS 4L +#undef org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK +#define org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK 1L +#undef org_sqlite_jni_capi_CApi_SQLITE_FAIL +#define org_sqlite_jni_capi_CApi_SQLITE_FAIL 3L +#undef org_sqlite_jni_capi_CApi_SQLITE_REPLACE +#define org_sqlite_jni_capi_CApi_SQLITE_REPLACE 5L +/* + * Class: org_sqlite_jni_capi_CApi * Method: init * Signature: ()V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: uncacheJniEnv + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_java_uncache_thread * Signature: ()Z */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_aggregate_context + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Z)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1aggregate_1context + (JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_auto_extension - * Signature: (Lorg/sqlite/jni/AutoExtension;)I + * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1auto_1extension +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1auto_1extension (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_backup_finish + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1finish + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_backup_init + * Signature: (JLjava/lang/String;JLjava/lang/String;)Lorg/sqlite/jni/capi/sqlite3_backup; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1init + (JNIEnv *, jclass, jlong, jstring, jlong, jstring); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_backup_pagecount + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1pagecount + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_backup_remaining + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1remaining + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_backup_step + * Signature: (JI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1step + (JNIEnv *, jclass, jlong, jint); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_blob - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + * Signature: (JI[BI)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1blob - (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1blob + (JNIEnv *, jclass, jlong, jint, jbyteArray, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_double - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;ID)I + * Signature: (JID)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1double - (JNIEnv *, jclass, jobject, jint, jdouble); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1double + (JNIEnv *, jclass, jlong, jint, jdouble); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_int - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + * Signature: (JII)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int - (JNIEnv *, jclass, jobject, jint, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int + (JNIEnv *, jclass, jlong, jint, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_int64 - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + * Signature: (JIJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64 + (JNIEnv *, jclass, jlong, jint, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_bind_java_object + * Signature: (JILjava/lang/Object;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int64 - (JNIEnv *, jclass, jobject, jint, jlong); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object + (JNIEnv *, jclass, jlong, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_null - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + * Signature: (JI)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1null - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1null + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_parameter_count - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1count - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1count + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_parameter_index - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;[B)I + * Signature: (J[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1index + (JNIEnv *, jclass, jlong, jbyteArray); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_bind_parameter_name + * Signature: (JI)Ljava/lang/String; */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1index - (JNIEnv *, jclass, jobject, jbyteArray); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1name + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_text - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + * Signature: (JI[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text + (JNIEnv *, jclass, jlong, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_bind_text16 + * Signature: (JI[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text16 + (JNIEnv *, jclass, jlong, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_bind_value + * Signature: (JIJ)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text - (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1value + (JNIEnv *, jclass, jlong, jint, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_zeroblob - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + * Signature: (JII)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob - (JNIEnv *, jclass, jobject, jint, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob + (JNIEnv *, jclass, jlong, jint, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_zeroblob64 - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + * Signature: (JIJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob64 + (JNIEnv *, jclass, jlong, jint, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_bytes + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1bytes + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_close + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1close + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_open + * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;JILorg/sqlite/jni/capi/OutputPointer/sqlite3_blob;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64 - (JNIEnv *, jclass, jobject, jint, jlong); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open + (JNIEnv *, jclass, jlong, jstring, jstring, jstring, jlong, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_read + * Signature: (J[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read + (JNIEnv *, jclass, jlong, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_reopen + * Signature: (JJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen + (JNIEnv *, jclass, jlong, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_write + * Signature: (J[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write + (JNIEnv *, jclass, jlong, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_busy_handler - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/BusyHandler;)I + * Signature: (JLorg/sqlite/jni/capi/BusyHandlerCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1handler - (JNIEnv *, jclass, jobject, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1handler + (JNIEnv *, jclass, jlong, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_busy_timeout - * Signature: (Lorg/sqlite/jni/sqlite3;I)I + * Signature: (JI)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1timeout - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1timeout + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_cancel_auto_extension - * Signature: (Lorg/sqlite/jni/AutoExtension;)Z + * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)Z */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1cancel_1auto_1extension +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1cancel_1auto_1extension (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_changes - * Signature: (Lorg/sqlite/jni/sqlite3;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_changes64 - * Signature: (Lorg/sqlite/jni/sqlite3;)J + * Signature: (J)J */ -JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes64 - (JNIEnv *, jclass, jobject); +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes64 + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_clear_bindings - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1clear_1bindings - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1clear_1bindings + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_close - * Signature: (Lorg/sqlite/jni/sqlite3;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_close_v2 - * Signature: (Lorg/sqlite/jni/sqlite3;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close_1v2 - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close_1v2 + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_blob - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1blob +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1blob (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_bytes - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + * Signature: (JI)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_bytes16 - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + * Signature: (JI)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes16 - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16 + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_count - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1count - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_column_decltype + * Signature: (JI)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1decltype + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_double - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)D + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)D */ -JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1double +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1double (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_int - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_int64 - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)J + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)J */ -JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int64 +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int64 (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_name - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + * Signature: (JI)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1name - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_database_name - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + * Signature: (JI)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1database_1name - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_origin_name - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + * Signature: (JI)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1origin_1name - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1origin_1name + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_table_name - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + * Signature: (JI)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_1name - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1table_1name + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_column_text16 - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_column_text + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_column_text - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_column_text16 + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/lang/String; */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text16 (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_type - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + * Signature: (JI)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1type - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1type + (JNIEnv *, jclass, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_value - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Lorg/sqlite/jni/sqlite3_value; + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Lorg/sqlite/jni/capi/sqlite3_value; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1value (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_collation_needed - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CollationNeeded;)I + * Signature: (JLorg/sqlite/jni/capi/CollationNeededCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1collation_1needed - (JNIEnv *, jclass, jobject, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1collation_1needed + (JNIEnv *, jclass, jlong, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_context_db_handle - * Signature: (Lorg/sqlite/jni/sqlite3_context;)Lorg/sqlite/jni/sqlite3; - */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1context_1db_1handle - (JNIEnv *, jclass, jobject); - -/* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_commit_hook - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CommitHook;)Lorg/sqlite/jni/CommitHook; + * Signature: (JLorg/sqlite/jni/capi/CommitHookCallback;)Lorg/sqlite/jni/capi/CommitHookCallback; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1commit_1hook - (JNIEnv *, jclass, jobject, jobject); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1commit_1hook + (JNIEnv *, jclass, jlong, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_compileoption_get * Signature: (I)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1get +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1get (JNIEnv *, jclass, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_compileoption_used * Signature: (Ljava/lang/String;)Z */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1used +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1used (JNIEnv *, jclass, jstring); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_complete + * Signature: ([B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_config + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_config + * Signature: (Lorg/sqlite/jni/capi/ConfigSqllogCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigSqllogCallback_2 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_config + * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_context_db_handle + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)Lorg/sqlite/jni/capi/sqlite3; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1context_1db_1handle + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_create_collation - * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/Collation;)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/capi/CollationCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1collation +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1collation (JNIEnv *, jclass, jobject, jstring, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_create_function - * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/SQLFunction;)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/capi/SQLFunction;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1function +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1function (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_data_count - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1data_1count + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/capi/sqlite3;IILorg/sqlite/jni/capi/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2 + (JNIEnv *, jclass, jobject, jint, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2 + (JNIEnv *, jclass, jobject, jint, jstring); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_db_name + * Signature: (JI)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1name + (JNIEnv *, jclass, jlong, jint); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_db_filename - * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;)Ljava/lang/String; + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1filename (JNIEnv *, jclass, jobject, jstring); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_db_config - * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_db_handle + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Lorg/sqlite/jni/capi/sqlite3; */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2 - (JNIEnv *, jclass, jobject, jint, jint, jobject); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1handle + (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_db_config - * Signature: (Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_db_readonly + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2 - (JNIEnv *, jclass, jobject, jint, jstring); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1readonly + (JNIEnv *, jclass, jobject, jstring); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_db_release_memory + * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1release_1memory + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_db_status - * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1status +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1status (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_errcode - * Signature: (Lorg/sqlite/jni/sqlite3;)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errcode +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errcode (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_errmsg + * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errmsg + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_error_offset + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1error_1offset + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_errstr + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errstr + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_expanded_sql - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1expanded_1sql +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1expanded_1sql (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_extended_errcode - * Signature: (Lorg/sqlite/jni/sqlite3;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1errcode - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_extended_result_codes - * Signature: (Lorg/sqlite/jni/sqlite3;Z)Z + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1result_1codes +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes (JNIEnv *, jclass, jobject, jboolean); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_errmsg - * Signature: (Lorg/sqlite/jni/sqlite3;)Ljava/lang/String; + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_get_autocommit + * Signature: (J)Z */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errmsg - (JNIEnv *, jclass, jobject); +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1autocommit + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_errstr - * Signature: (I)Ljava/lang/String; + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_get_auxdata + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)Ljava/lang/Object; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errstr - (JNIEnv *, jclass, jint); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1auxdata + (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_error_offset - * Signature: (Lorg/sqlite/jni/sqlite3;)I + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_finalize + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1error_1offset +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1finalize + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_initialize + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1initialize + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_interrupt + * Signature: (Lorg/sqlite/jni/capi/sqlite3;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1interrupt (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_finalize - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_is_interrupted + * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Z */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1finalize +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1is_1interrupted (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_initialize + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_keyword_check + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1check + (JNIEnv *, jclass, jstring); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_keyword_count * Signature: ()I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1count (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_keyword_name + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1name + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_last_insert_rowid - * Signature: (Lorg/sqlite/jni/sqlite3;)J + * Signature: (Lorg/sqlite/jni/capi/sqlite3;)J */ -JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1last_1insert_1rowid +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1last_1insert_1rowid (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_libversion * Signature: ()Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_libversion_number * Signature: ()I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion_1number +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion_1number (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_limit + * Signature: (Lorg/sqlite/jni/capi/sqlite3;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1limit + (JNIEnv *, jclass, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_normalized_sql + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1normalized_1sql + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_open - * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;)I + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open (JNIEnv *, jclass, jstring, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_open_v2 - * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;ILjava/lang/String;)I + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;ILjava/lang/String;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open_1v2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open_1v2 (JNIEnv *, jclass, jstring, jobject, jint, jstring); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_prepare - * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare - (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare + (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_prepare_v2 - * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 - (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v2 + (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_prepare_v3 - * Signature: (Lorg/sqlite/jni/sqlite3;[BIILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (J[BIILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v3 + (JNIEnv *, jclass, jlong, jbyteArray, jint, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_preupdate_blobwrite + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1blobwrite + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_preupdate_count + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1count + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_preupdate_depth + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1depth + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_preupdate_hook + * Signature: (JLorg/sqlite/jni/capi/PreupdateHookCallback;)Lorg/sqlite/jni/capi/PreupdateHookCallback; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1hook + (JNIEnv *, jclass, jlong, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_preupdate_new + * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1new + (JNIEnv *, jclass, jlong, jint, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_preupdate_old + * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 - (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1old + (JNIEnv *, jclass, jlong, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_progress_handler - * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/ProgressHandler;)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/ProgressHandlerCallback;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1progress_1handler +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1progress_1handler (JNIEnv *, jclass, jobject, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_randomness + * Signature: ([B)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1randomness + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_release_memory + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1release_1memory + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_reset - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_reset_auto_extension * Signature: ()V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset_1auto_1extension +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset_1auto_1extension (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_double - * Signature: (Lorg/sqlite/jni/sqlite3_context;D)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;D)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1double +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1double (JNIEnv *, jclass, jobject, jdouble); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_error - * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error (JNIEnv *, jclass, jobject, jbyteArray, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_error_toobig - * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1toobig +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1toobig (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_error_nomem - * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1nomem +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1nomem (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_error_code - * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1code +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1code (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_null - * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1null +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_int - * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_int64 - * Signature: (Lorg/sqlite/jni/sqlite3_context;J)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int64 +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64 (JNIEnv *, jclass, jobject, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_java_object - * Signature: (Lorg/sqlite/jni/sqlite3_context;Ljava/lang/Object;)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/lang/Object;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1java_1object +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object (JNIEnv *, jclass, jobject, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_value - * Signature: (Lorg/sqlite/jni/sqlite3_context;Lorg/sqlite/jni/sqlite3_value;)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Lorg/sqlite/jni/capi/sqlite3_value;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1value +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1value (JNIEnv *, jclass, jobject, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_zeroblob - * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob (JNIEnv *, jclass, jobject, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_zeroblob64 - * Signature: (Lorg/sqlite/jni/sqlite3_context;J)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob64 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob64 (JNIEnv *, jclass, jobject, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_blob - * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob (JNIEnv *, jclass, jobject, jbyteArray, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_blob64 - * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJ)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJ)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob64 +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob64 (JNIEnv *, jclass, jobject, jbyteArray, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_text - * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text (JNIEnv *, jclass, jobject, jbyteArray, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_text64 - * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJI)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJI)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text64 (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_status - * Signature: (ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status - (JNIEnv *, jclass, jint, jobject, jobject, jboolean); - -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_status64 - * Signature: (ILorg/sqlite/jni/OutputPointer/Int64;Lorg/sqlite/jni/OutputPointer/Int64;Z)I + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_rollback_hook + * Signature: (JLorg/sqlite/jni/capi/RollbackHookCallback;)Lorg/sqlite/jni/capi/RollbackHookCallback; */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64 - (JNIEnv *, jclass, jint, jobject, jobject, jboolean); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1rollback_1hook + (JNIEnv *, jclass, jlong, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_rollback_hook - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/RollbackHook;)Lorg/sqlite/jni/RollbackHook; + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_set_authorizer + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Lorg/sqlite/jni/capi/AuthorizerCallback;)I */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1authorizer (JNIEnv *, jclass, jobject, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_set_authorizer - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/Authorizer;)I + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_set_auxdata + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;ILjava/lang/Object;)V */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1authorizer - (JNIEnv *, jclass, jobject, jobject); +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1auxdata + (JNIEnv *, jclass, jobject, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_set_last_insert_rowid - * Signature: (Lorg/sqlite/jni/sqlite3;J)V + * Signature: (Lorg/sqlite/jni/capi/sqlite3;J)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1last_1insert_1rowid +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1last_1insert_1rowid (JNIEnv *, jclass, jobject, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_shutdown + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1shutdown + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_sleep * Signature: (I)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sleep +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sleep (JNIEnv *, jclass, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_sourceid * Signature: ()Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sourceid +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sourceid (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_sql - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sql +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sql (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_status + * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_status64 + * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int64;Lorg/sqlite/jni/capi/OutputPointer/Int64;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64 + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_step - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1step +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_stmt_busy + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1busy + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_stmt_explain + * Signature: (JI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1explain + (JNIEnv *, jclass, jlong, jint); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_stmt_isexplain + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1isexplain + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_stmt_readonly + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1readonly + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_stmt_status + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;IZ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1status + (JNIEnv *, jclass, jobject, jint, jboolean); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_strglob * Signature: ([B[B)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strglob +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strglob (JNIEnv *, jclass, jbyteArray, jbyteArray); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_strlike * Signature: ([B[BI)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strlike +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strlike (JNIEnv *, jclass, jbyteArray, jbyteArray, jint); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_system_errno + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1system_1errno + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_table_column_metadata + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1table_1column_1metadata + (JNIEnv *, jclass, jobject, jstring, jstring, jstring, jobject, jobject, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_threadsafe * Signature: ()I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1threadsafe +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1threadsafe (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_total_changes - * Signature: (Lorg/sqlite/jni/sqlite3;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_total_changes64 - * Signature: (Lorg/sqlite/jni/sqlite3;)J + * Signature: (J)J */ -JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64 - (JNIEnv *, jclass, jobject); +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes64 + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_trace_v2 - * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/Tracer;)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/TraceV2Callback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1trace_1v2 (JNIEnv *, jclass, jobject, jint, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_txn_state + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1txn_1state + (JNIEnv *, jclass, jobject, jstring); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_update_hook - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHook;)Lorg/sqlite/jni/UpdateHook; + * Signature: (JLorg/sqlite/jni/capi/UpdateHookCallback;)Lorg/sqlite/jni/capi/UpdateHookCallback; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1update_1hook - (JNIEnv *, jclass, jobject, jobject); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1update_1hook + (JNIEnv *, jclass, jlong, jobject); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_blob - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + * Signature: (J)[B */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1blob - (JNIEnv *, jclass, jobject); +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1blob + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_bytes - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_bytes16 - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes16 - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes16 + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_double - * Signature: (Lorg/sqlite/jni/sqlite3_value;)D + * Signature: (J)D */ -JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1double - (JNIEnv *, jclass, jobject); +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1double + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_dupe - * Signature: (Lorg/sqlite/jni/sqlite3_value;)Lorg/sqlite/jni/sqlite3_value; + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_dup + * Signature: (J)Lorg/sqlite/jni/capi/sqlite3_value; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1dupe - (JNIEnv *, jclass, jobject); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1dup + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_encoding - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1encoding - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1encoding + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_free - * Signature: (Lorg/sqlite/jni/sqlite3_value;)V + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1free - (JNIEnv *, jclass, jobject); +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1free + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_frombind + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1frombind + (JNIEnv *, jclass, jlong); + +/* + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_int - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_int64 - * Signature: (Lorg/sqlite/jni/sqlite3_value;)J + * Signature: (J)J */ -JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int64 - (JNIEnv *, jclass, jobject); +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64 + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_java_object - * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/Object; + * Signature: (J)Ljava/lang/Object; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1object - (JNIEnv *, jclass, jobject); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text - * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_nochange + * Signature: (J)I */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nochange + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text_utf8 - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_numeric_type + * Signature: (J)I */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text_1utf8 - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1numeric_1type + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text16 - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_subtype + * Signature: (J)I */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16 - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1subtype + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text16le - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_text + * Signature: (J)[B */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16le - (JNIEnv *, jclass, jobject); +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text16be - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_text16 + * Signature: (J)Ljava/lang/String; */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16be - (JNIEnv *, jclass, jobject); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text16 + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni + * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_type - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1type - (JNIEnv *, jclass, jobject); - -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_numeric_type - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + * Signature: (J)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1numeric_1type - (JNIEnv *, jclass, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1type + (JNIEnv *, jclass, jlong); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_nochange - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1nochange - (JNIEnv *, jclass, jobject); - -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_frombind - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_jni_internal_details + * Signature: ()V */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1frombind - (JNIEnv *, jclass, jobject); +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1internal_1details + (JNIEnv *, jclass); -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_subtype - * Signature: (Lorg/sqlite/jni/sqlite3_value;)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype - (JNIEnv *, jclass, jobject); +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_capi_SQLTester */ +#ifndef _Included_org_sqlite_jni_capi_SQLTester +#define _Included_org_sqlite_jni_capi_SQLTester +#ifdef __cplusplus +extern "C" { +#endif /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_shutdown - * Signature: ()I + * Class: org_sqlite_jni_capi_SQLTester + * Method: strglob + * Signature: ([B[B)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown - (JNIEnv *, jclass); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_SQLTester_strglob + (JNIEnv *, jclass, jbyteArray, jbyteArray); /* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_do_something_for_developer + * Class: org_sqlite_jni_capi_SQLTester + * Method: installCustomExtensions * Signature: ()V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1for_1developer +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions (JNIEnv *, jclass); #ifdef __cplusplus @@ -1737,171 +2154,171 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1fo #endif /* DO NOT EDIT THIS FILE - it is machine generated */ #include -/* Header for class org_sqlite_jni_Fts5ExtensionApi */ +/* Header for class org_sqlite_jni_fts5_Fts5ExtensionApi */ -#ifndef _Included_org_sqlite_jni_Fts5ExtensionApi -#define _Included_org_sqlite_jni_Fts5ExtensionApi +#ifndef _Included_org_sqlite_jni_fts5_Fts5ExtensionApi +#define _Included_org_sqlite_jni_fts5_Fts5ExtensionApi #ifdef __cplusplus extern "C" { #endif /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: getInstance - * Signature: ()Lorg/sqlite/jni/Fts5ExtensionApi; + * Signature: ()Lorg/sqlite/jni/fts5/Fts5ExtensionApi; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_getInstance +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_getInstance (JNIEnv *, jclass); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xColumnCount - * Signature: (Lorg/sqlite/jni/Fts5Context;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnCount +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnCount (JNIEnv *, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xColumnSize - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnSize +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnSize (JNIEnv *, jobject, jobject, jint, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xColumnText - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/String;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/String;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnText +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnText (JNIEnv *, jobject, jobject, jint, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xColumnTotalSize - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int64;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int64;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnTotalSize +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnTotalSize (JNIEnv *, jobject, jobject, jint, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xGetAuxdata - * Signature: (Lorg/sqlite/jni/Fts5Context;Z)Ljava/lang/Object; + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Z)Ljava/lang/Object; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xGetAuxdata +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xGetAuxdata (JNIEnv *, jobject, jobject, jboolean); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xInst - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInst +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInst (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xInstCount - * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInstCount +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInstCount (JNIEnv *, jobject, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xPhraseCount - * Signature: (Lorg/sqlite/jni/Fts5Context;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseCount (JNIEnv *, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xPhraseFirst - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirst +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirst (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xPhraseFirstColumn - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirstColumn +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirstColumn (JNIEnv *, jobject, jobject, jint, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xPhraseNext - * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)V + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNext +JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNext (JNIEnv *, jobject, jobject, jobject, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xPhraseNextColumn - * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)V + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNextColumn +JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNextColumn (JNIEnv *, jobject, jobject, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xPhraseSize - * Signature: (Lorg/sqlite/jni/Fts5Context;I)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;I)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseSize +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseSize (JNIEnv *, jobject, jobject, jint); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xQueryPhrase - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5ExtensionApi/xQueryPhraseCallback;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5ExtensionApi/XQueryPhraseCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xQueryPhrase +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xQueryPhrase (JNIEnv *, jobject, jobject, jint, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xRowCount - * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int64;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int64;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowCount (JNIEnv *, jobject, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xRowid - * Signature: (Lorg/sqlite/jni/Fts5Context;)J + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)J */ -JNIEXPORT jlong JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowid +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowid (JNIEnv *, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xSetAuxdata - * Signature: (Lorg/sqlite/jni/Fts5Context;Ljava/lang/Object;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Ljava/lang/Object;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xSetAuxdata (JNIEnv *, jobject, jobject, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xTokenize - * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xTokenize (JNIEnv *, jobject, jobject, jbyteArray, jobject); /* - * Class: org_sqlite_jni_Fts5ExtensionApi + * Class: org_sqlite_jni_fts5_Fts5ExtensionApi * Method: xUserData - * Signature: (Lorg/sqlite/jni/Fts5Context;)Ljava/lang/Object; + * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)Ljava/lang/Object; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xUserData +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xUserData (JNIEnv *, jobject, jobject); #ifdef __cplusplus @@ -1910,27 +2327,29 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xUserData #endif /* DO NOT EDIT THIS FILE - it is machine generated */ #include -/* Header for class org_sqlite_jni_fts5_api */ +/* Header for class org_sqlite_jni_fts5_fts5_api */ -#ifndef _Included_org_sqlite_jni_fts5_api -#define _Included_org_sqlite_jni_fts5_api +#ifndef _Included_org_sqlite_jni_fts5_fts5_api +#define _Included_org_sqlite_jni_fts5_fts5_api #ifdef __cplusplus extern "C" { #endif +#undef org_sqlite_jni_fts5_fts5_api_iVersion +#define org_sqlite_jni_fts5_fts5_api_iVersion 2L /* - * Class: org_sqlite_jni_fts5_api + * Class: org_sqlite_jni_fts5_fts5_api * Method: getInstanceForDb - * Signature: (Lorg/sqlite/jni/sqlite3;)Lorg/sqlite/jni/fts5_api; + * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Lorg/sqlite/jni/fts5/fts5_api; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_fts5_1api_getInstanceForDb (JNIEnv *, jclass, jobject); /* - * Class: org_sqlite_jni_fts5_api + * Class: org_sqlite_jni_fts5_fts5_api * Method: xCreateFunction - * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5_extension_function;)I + * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5/fts5_extension_function;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1api_xCreateFunction (JNIEnv *, jobject, jstring, jobject, jobject); #ifdef __cplusplus @@ -1939,51 +2358,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction #endif /* DO NOT EDIT THIS FILE - it is machine generated */ #include -/* Header for class org_sqlite_jni_fts5_tokenizer */ +/* Header for class org_sqlite_jni_fts5_fts5_tokenizer */ -#ifndef _Included_org_sqlite_jni_fts5_tokenizer -#define _Included_org_sqlite_jni_fts5_tokenizer +#ifndef _Included_org_sqlite_jni_fts5_fts5_tokenizer +#define _Included_org_sqlite_jni_fts5_fts5_tokenizer #ifdef __cplusplus extern "C" { #endif /* - * Class: org_sqlite_jni_fts5_tokenizer + * Class: org_sqlite_jni_fts5_fts5_tokenizer * Method: xTokenize - * Signature: (Lorg/sqlite/jni/Fts5Tokenizer;I[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + * Signature: (Lorg/sqlite/jni/fts5/Fts5Tokenizer;I[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1tokenizer_xTokenize (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject); #ifdef __cplusplus } #endif #endif -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_sqlite_jni_tester_SQLTester */ - -#ifndef _Included_org_sqlite_jni_tester_SQLTester -#define _Included_org_sqlite_jni_tester_SQLTester -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: org_sqlite_jni_tester_SQLTester - * Method: strglob - * Signature: ([B[B)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_tester_SQLTester_strglob - (JNIEnv *, jclass, jbyteArray, jbyteArray); - -/* - * Class: org_sqlite_jni_tester_SQLTester - * Method: installCustomExtensions - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions - (JNIEnv *, jclass); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/ext/jni/src/org/sqlite/jni/Authorizer.java b/ext/jni/src/org/sqlite/jni/Authorizer.java deleted file mode 100644 index 114c27fc6..000000000 --- a/ext/jni/src/org/sqlite/jni/Authorizer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* -** 2023-08-05 -** -** 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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - A callback for use with sqlite3_set_authorizer(). -*/ -public interface Authorizer { - /** - Must functions as described for the sqlite3_set_authorizer() - callback, with one caveat: the string values passed here were - initially (at the C level) encoded in standard UTF-8. If they - contained any constructs which are not compatible with MUTF-8, - these strings will not have the expected values. For further - details, see the documentation for the SQLite3Jni class. - - Must not throw. - */ - int xAuth(int opId, @Nullable String s1, @Nullable String s2, - @Nullable String s3, @Nullable String s4); -} diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java deleted file mode 100644 index a2ab6a0f7..000000000 --- a/ext/jni/src/org/sqlite/jni/AutoExtension.java +++ /dev/null @@ -1,31 +0,0 @@ -/* -** 2023-08-05 -** -** 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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - A callback for use with sqlite3_auto_extension(). -*/ -public interface AutoExtension { - /** - Must function as described for the sqlite3_auto_extension(), - with the caveat that the signature is more limited. - - As an exception (as it were) to the callbacks-must-not-throw - rule, AutoExtensions may do so and the exception's error message - will be set as the db's error string. - - Results are undefined if db is closed by an auto-extension. - */ - int xEntryPoint(sqlite3 db); -} diff --git a/ext/jni/src/org/sqlite/jni/BusyHandler.java b/ext/jni/src/org/sqlite/jni/BusyHandler.java deleted file mode 100644 index 8ce729c90..000000000 --- a/ext/jni/src/org/sqlite/jni/BusyHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -/* -** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - Callback proxy for use with sqlite3_busy_handler(). -*/ -public abstract class BusyHandler { - /** - Must function as documented for the sqlite3_busy_handler() - callback argument, minus the (void*) argument the C-level - function requires. - - Any exceptions thrown by this callback are suppressed in order to - retain the C-style API semantics of the JNI bindings. - */ - public abstract int xCallback(int n); - - /** - Optionally override to perform any cleanup when this busy - handler is destroyed. It is destroyed when: - - - The associated db is passed to sqlite3_close() or - sqlite3_close_v2(). - - - sqlite3_busy_handler() is called to replace the handler, - whether it's passed a null handler or any other instance of - this class. - - - sqlite3_busy_timeout() is called, which implicitly installs - a busy handler. - */ - public void xDestroy(){} -} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Function.java b/ext/jni/src/org/sqlite/jni/Fts5Function.java deleted file mode 100644 index 463ec034f..000000000 --- a/ext/jni/src/org/sqlite/jni/Fts5Function.java +++ /dev/null @@ -1,27 +0,0 @@ -/* -** 2023-08-04 -** -** 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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - Fts5Function is used in conjunction with the - sqlite3_create_fts_function() JNI-bound API to give that native code - access to the callback functions needed in order to implement - FTS5 auxiliary functions in Java. -*/ -public abstract class Fts5Function { - - public abstract void xFunction(Fts5ExtensionApi pApi, Fts5Context pFts, - sqlite3_context pCtx, sqlite3_value argv[]); - public void xDestroy() {} -} diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java deleted file mode 100644 index 82a90c918..000000000 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ /dev/null @@ -1,165 +0,0 @@ -/* -** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - Helper classes for handling JNI output pointers. - - We do not use a generic OutputPointer because working with those - from the native JNI code is unduly quirky due to a lack of - autoboxing at that level. - - The usage is similar for all of thes types: - - ``` - OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); - assert( null==out.get() ); - int rc = sqlite3_open(":memory:", out); - if( 0!=rc ) ... error; - assert( null!=out.get() ); - sqlite3 db = out.take(); - assert( null==out.get() ); - ``` - - With the minor exception that the primitive types permit direct - access to the object's value via the `value` property, whereas the - JNI-level opaque types do not permit client-level code to set that - property. -*/ -public final class OutputPointer { - - /** - Output pointer for use with routines, such as sqlite3_open(), - which return a database handle via an output pointer. These - pointers can only be set by the JNI layer, not by client-level - code. - */ - public static final class sqlite3 { - private org.sqlite.jni.sqlite3 value; - //! Initializes with a null value. - public sqlite3(){value = null;} - //! Sets the current value to null. - public void clear(){value = null;} - //! Returns the current value. - public final org.sqlite.jni.sqlite3 get(){return value;} - //! Equivalent to calling get() then clear(). - public final org.sqlite.jni.sqlite3 take(){ - final org.sqlite.jni.sqlite3 v = value; - value = null; - return v; - } - } - - /** - Output pointer for use with routines, such as sqlite3_prepare(), - which return a statement handle via an output pointer. These - pointers can only be set by the JNI layer, not by client-level - code. - */ - public static final class sqlite3_stmt { - private org.sqlite.jni.sqlite3_stmt value; - //! Initializes with a null value. - public sqlite3_stmt(){value = null;} - //! Sets the current value to null. - public void clear(){value = null;} - //! Returns the current value. - public final org.sqlite.jni.sqlite3_stmt get(){return value;} - //! Equivalent to calling get() then clear(). - public final org.sqlite.jni.sqlite3_stmt take(){ - final org.sqlite.jni.sqlite3_stmt v = value; - value = null; - return v; - } - } - - /** - Output pointer for use with native routines which return integers via - output pointers. - */ - public static final class Int32 { - /** - This is public for ease of use. Accessors are provided for - consistency with the higher-level types. - */ - public int value; - //! Initializes with the value 0. - public Int32(){this(0);} - //! Initializes with the value v. - public Int32(int v){value = v;} - //! Returns the current value. - public final int get(){return value;} - //! Sets the current value to v. - public final void set(int v){value = v;} - } - - /** - Output pointer for use with native routines which return 64-bit integers - via output pointers. - */ - public static final class Int64 { - /** - This is public for ease of use. Accessors are provided for - consistency with the higher-level types. - */ - public long value; - //! Initializes with the value 0. - public Int64(){this(0);} - //! Initializes with the value v. - public Int64(long v){value = v;} - //! Returns the current value. - public final long get(){return value;} - //! Sets the current value. - public final void set(long v){value = v;} - } - - /** - Output pointer for use with native routines which return strings via - output pointers. - */ - public static final class String { - /** - This is public for ease of use. Accessors are provided for - consistency with the higher-level types. - */ - public java.lang.String value; - //! Initializes with a null value. - public String(){this(null);} - //! Initializes with the value v. - public String(java.lang.String v){value = v;} - //! Returns the current value. - public final java.lang.String get(){return value;} - //! Sets the current value. - public final void set(java.lang.String v){value = v;} - } - - /** - Output pointer for use with native routines which return byte - arrays via output pointers. - */ - public static final class ByteArray { - /** - This is public for ease of use. Accessors are provided for - consistency with the higher-level types. - */ - public byte[] value; - //! Initializes with the value null. - public ByteArray(){this(null);} - //! Initializes with the value v. - public ByteArray(byte[] v){value = v;} - //! Returns the current value. - public final byte[] get(){return value;} - //! Sets the current value. - public final void set(byte[] v){value = v;} - } -} diff --git a/ext/jni/src/org/sqlite/jni/ResultCode.java b/ext/jni/src/org/sqlite/jni/ResultCode.java deleted file mode 100644 index 0989bc744..000000000 --- a/ext/jni/src/org/sqlite/jni/ResultCode.java +++ /dev/null @@ -1,155 +0,0 @@ -/* -** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - This enum contains all of the core and "extended" result codes used - by the sqlite3 library. It is provided not for use with the C-style - API (with which it won't work) but for higher-level code which may - find it useful to map SQLite result codes to human-readable names. -*/ -public enum ResultCode { - SQLITE_OK(SQLite3Jni.SQLITE_OK), - SQLITE_ERROR(SQLite3Jni.SQLITE_ERROR), - SQLITE_INTERNAL(SQLite3Jni.SQLITE_INTERNAL), - SQLITE_PERM(SQLite3Jni.SQLITE_PERM), - SQLITE_ABORT(SQLite3Jni.SQLITE_ABORT), - SQLITE_BUSY(SQLite3Jni.SQLITE_BUSY), - SQLITE_LOCKED(SQLite3Jni.SQLITE_LOCKED), - SQLITE_NOMEM(SQLite3Jni.SQLITE_NOMEM), - SQLITE_READONLY(SQLite3Jni.SQLITE_READONLY), - SQLITE_INTERRUPT(SQLite3Jni.SQLITE_INTERRUPT), - SQLITE_IOERR(SQLite3Jni.SQLITE_IOERR), - SQLITE_CORRUPT(SQLite3Jni.SQLITE_CORRUPT), - SQLITE_NOTFOUND(SQLite3Jni.SQLITE_NOTFOUND), - SQLITE_FULL(SQLite3Jni.SQLITE_FULL), - SQLITE_CANTOPEN(SQLite3Jni.SQLITE_CANTOPEN), - SQLITE_PROTOCOL(SQLite3Jni.SQLITE_PROTOCOL), - SQLITE_EMPTY(SQLite3Jni.SQLITE_EMPTY), - SQLITE_SCHEMA(SQLite3Jni.SQLITE_SCHEMA), - SQLITE_TOOBIG(SQLite3Jni.SQLITE_TOOBIG), - SQLITE_CONSTRAINT(SQLite3Jni.SQLITE_CONSTRAINT), - SQLITE_MISMATCH(SQLite3Jni.SQLITE_MISMATCH), - SQLITE_MISUSE(SQLite3Jni.SQLITE_MISUSE), - SQLITE_NOLFS(SQLite3Jni.SQLITE_NOLFS), - SQLITE_AUTH(SQLite3Jni.SQLITE_AUTH), - SQLITE_FORMAT(SQLite3Jni.SQLITE_FORMAT), - SQLITE_RANGE(SQLite3Jni.SQLITE_RANGE), - SQLITE_NOTADB(SQLite3Jni.SQLITE_NOTADB), - SQLITE_NOTICE(SQLite3Jni.SQLITE_NOTICE), - SQLITE_WARNING(SQLite3Jni.SQLITE_WARNING), - SQLITE_ROW(SQLite3Jni.SQLITE_ROW), - SQLITE_DONE(SQLite3Jni.SQLITE_DONE), - SQLITE_ERROR_MISSING_COLLSEQ(SQLite3Jni.SQLITE_ERROR_MISSING_COLLSEQ), - SQLITE_ERROR_RETRY(SQLite3Jni.SQLITE_ERROR_RETRY), - SQLITE_ERROR_SNAPSHOT(SQLite3Jni.SQLITE_ERROR_SNAPSHOT), - SQLITE_IOERR_READ(SQLite3Jni.SQLITE_IOERR_READ), - SQLITE_IOERR_SHORT_READ(SQLite3Jni.SQLITE_IOERR_SHORT_READ), - SQLITE_IOERR_WRITE(SQLite3Jni.SQLITE_IOERR_WRITE), - SQLITE_IOERR_FSYNC(SQLite3Jni.SQLITE_IOERR_FSYNC), - SQLITE_IOERR_DIR_FSYNC(SQLite3Jni.SQLITE_IOERR_DIR_FSYNC), - SQLITE_IOERR_TRUNCATE(SQLite3Jni.SQLITE_IOERR_TRUNCATE), - SQLITE_IOERR_FSTAT(SQLite3Jni.SQLITE_IOERR_FSTAT), - SQLITE_IOERR_UNLOCK(SQLite3Jni.SQLITE_IOERR_UNLOCK), - SQLITE_IOERR_RDLOCK(SQLite3Jni.SQLITE_IOERR_RDLOCK), - SQLITE_IOERR_DELETE(SQLite3Jni.SQLITE_IOERR_DELETE), - SQLITE_IOERR_BLOCKED(SQLite3Jni.SQLITE_IOERR_BLOCKED), - SQLITE_IOERR_NOMEM(SQLite3Jni.SQLITE_IOERR_NOMEM), - SQLITE_IOERR_ACCESS(SQLite3Jni.SQLITE_IOERR_ACCESS), - SQLITE_IOERR_CHECKRESERVEDLOCK(SQLite3Jni.SQLITE_IOERR_CHECKRESERVEDLOCK), - SQLITE_IOERR_LOCK(SQLite3Jni.SQLITE_IOERR_LOCK), - SQLITE_IOERR_CLOSE(SQLite3Jni.SQLITE_IOERR_CLOSE), - SQLITE_IOERR_DIR_CLOSE(SQLite3Jni.SQLITE_IOERR_DIR_CLOSE), - SQLITE_IOERR_SHMOPEN(SQLite3Jni.SQLITE_IOERR_SHMOPEN), - SQLITE_IOERR_SHMSIZE(SQLite3Jni.SQLITE_IOERR_SHMSIZE), - SQLITE_IOERR_SHMLOCK(SQLite3Jni.SQLITE_IOERR_SHMLOCK), - SQLITE_IOERR_SHMMAP(SQLite3Jni.SQLITE_IOERR_SHMMAP), - SQLITE_IOERR_SEEK(SQLite3Jni.SQLITE_IOERR_SEEK), - SQLITE_IOERR_DELETE_NOENT(SQLite3Jni.SQLITE_IOERR_DELETE_NOENT), - SQLITE_IOERR_MMAP(SQLite3Jni.SQLITE_IOERR_MMAP), - SQLITE_IOERR_GETTEMPPATH(SQLite3Jni.SQLITE_IOERR_GETTEMPPATH), - SQLITE_IOERR_CONVPATH(SQLite3Jni.SQLITE_IOERR_CONVPATH), - SQLITE_IOERR_VNODE(SQLite3Jni.SQLITE_IOERR_VNODE), - SQLITE_IOERR_AUTH(SQLite3Jni.SQLITE_IOERR_AUTH), - SQLITE_IOERR_BEGIN_ATOMIC(SQLite3Jni.SQLITE_IOERR_BEGIN_ATOMIC), - SQLITE_IOERR_COMMIT_ATOMIC(SQLite3Jni.SQLITE_IOERR_COMMIT_ATOMIC), - SQLITE_IOERR_ROLLBACK_ATOMIC(SQLite3Jni.SQLITE_IOERR_ROLLBACK_ATOMIC), - SQLITE_IOERR_DATA(SQLite3Jni.SQLITE_IOERR_DATA), - SQLITE_IOERR_CORRUPTFS(SQLite3Jni.SQLITE_IOERR_CORRUPTFS), - SQLITE_LOCKED_SHAREDCACHE(SQLite3Jni.SQLITE_LOCKED_SHAREDCACHE), - SQLITE_LOCKED_VTAB(SQLite3Jni.SQLITE_LOCKED_VTAB), - SQLITE_BUSY_RECOVERY(SQLite3Jni.SQLITE_BUSY_RECOVERY), - SQLITE_BUSY_SNAPSHOT(SQLite3Jni.SQLITE_BUSY_SNAPSHOT), - SQLITE_BUSY_TIMEOUT(SQLite3Jni.SQLITE_BUSY_TIMEOUT), - SQLITE_CANTOPEN_NOTEMPDIR(SQLite3Jni.SQLITE_CANTOPEN_NOTEMPDIR), - SQLITE_CANTOPEN_ISDIR(SQLite3Jni.SQLITE_CANTOPEN_ISDIR), - SQLITE_CANTOPEN_FULLPATH(SQLite3Jni.SQLITE_CANTOPEN_FULLPATH), - SQLITE_CANTOPEN_CONVPATH(SQLite3Jni.SQLITE_CANTOPEN_CONVPATH), - SQLITE_CANTOPEN_SYMLINK(SQLite3Jni.SQLITE_CANTOPEN_SYMLINK), - SQLITE_CORRUPT_VTAB(SQLite3Jni.SQLITE_CORRUPT_VTAB), - SQLITE_CORRUPT_SEQUENCE(SQLite3Jni.SQLITE_CORRUPT_SEQUENCE), - SQLITE_CORRUPT_INDEX(SQLite3Jni.SQLITE_CORRUPT_INDEX), - SQLITE_READONLY_RECOVERY(SQLite3Jni.SQLITE_READONLY_RECOVERY), - SQLITE_READONLY_CANTLOCK(SQLite3Jni.SQLITE_READONLY_CANTLOCK), - SQLITE_READONLY_ROLLBACK(SQLite3Jni.SQLITE_READONLY_ROLLBACK), - SQLITE_READONLY_DBMOVED(SQLite3Jni.SQLITE_READONLY_DBMOVED), - SQLITE_READONLY_CANTINIT(SQLite3Jni.SQLITE_READONLY_CANTINIT), - SQLITE_READONLY_DIRECTORY(SQLite3Jni.SQLITE_READONLY_DIRECTORY), - SQLITE_ABORT_ROLLBACK(SQLite3Jni.SQLITE_ABORT_ROLLBACK), - SQLITE_CONSTRAINT_CHECK(SQLite3Jni.SQLITE_CONSTRAINT_CHECK), - SQLITE_CONSTRAINT_COMMITHOOK(SQLite3Jni.SQLITE_CONSTRAINT_COMMITHOOK), - SQLITE_CONSTRAINT_FOREIGNKEY(SQLite3Jni.SQLITE_CONSTRAINT_FOREIGNKEY), - SQLITE_CONSTRAINT_FUNCTION(SQLite3Jni.SQLITE_CONSTRAINT_FUNCTION), - SQLITE_CONSTRAINT_NOTNULL(SQLite3Jni.SQLITE_CONSTRAINT_NOTNULL), - SQLITE_CONSTRAINT_PRIMARYKEY(SQLite3Jni.SQLITE_CONSTRAINT_PRIMARYKEY), - SQLITE_CONSTRAINT_TRIGGER(SQLite3Jni.SQLITE_CONSTRAINT_TRIGGER), - SQLITE_CONSTRAINT_UNIQUE(SQLite3Jni.SQLITE_CONSTRAINT_UNIQUE), - SQLITE_CONSTRAINT_VTAB(SQLite3Jni.SQLITE_CONSTRAINT_VTAB), - SQLITE_CONSTRAINT_ROWID(SQLite3Jni.SQLITE_CONSTRAINT_ROWID), - SQLITE_CONSTRAINT_PINNED(SQLite3Jni.SQLITE_CONSTRAINT_PINNED), - SQLITE_CONSTRAINT_DATATYPE(SQLite3Jni.SQLITE_CONSTRAINT_DATATYPE), - SQLITE_NOTICE_RECOVER_WAL(SQLite3Jni.SQLITE_NOTICE_RECOVER_WAL), - SQLITE_NOTICE_RECOVER_ROLLBACK(SQLite3Jni.SQLITE_NOTICE_RECOVER_ROLLBACK), - SQLITE_WARNING_AUTOINDEX(SQLite3Jni.SQLITE_WARNING_AUTOINDEX), - SQLITE_AUTH_USER(SQLite3Jni.SQLITE_AUTH_USER), - SQLITE_OK_LOAD_PERMANENTLY(SQLite3Jni.SQLITE_OK_LOAD_PERMANENTLY); - - public final int value; - - ResultCode(int rc){ - value = rc; - ResultCodeMap.set(rc, this); - } - - /** - Returns the entry from this enum for the given result code, or - null if no match is found. - */ - public static ResultCode getEntryForInt(int rc){ - return ResultCodeMap.get(rc); - } - - /** - Internal level of indirection required because we cannot initialize - static enum members in an enum before the enum constructor is - invoked. - */ - private static final class ResultCodeMap { - private static final java.util.Map i2e - = new java.util.HashMap<>(); - private static void set(int rc, ResultCode e){ i2e.put(rc, e); } - private static ResultCode get(int rc){ return i2e.get(rc); } - } - -} diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java deleted file mode 100644 index 21e5fe622..000000000 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ /dev/null @@ -1,172 +0,0 @@ -/* -** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - SQLFunction is used in conjunction with the - sqlite3_create_function() JNI-bound API to give that native code - access to the callback functions needed in order to implement SQL - functions in Java. - - This class is not used by itself, but is a marker base class. The - three UDF types are modelled by the inner classes Scalar, - Aggregate, and Window. Most simply, clients may create - anonymous classes from those to implement UDFs. Clients are free to - create their own classes for use with UDFs, so long as they conform - to the public interfaces defined by those three classes. The JNI - layer only actively relies on the SQLFunction base class. -*/ -public abstract class SQLFunction { - - /** - PerContextState assists aggregate and window functions in - managinga their accumulator state across calls to the UDF's - callbacks. - - If a given aggregate or window function is called multiple times - in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., - then the clients need some way of knowing which call is which so - that they can map their state between their various UDF callbacks - and reset it via xFinal(). This class takes care of such - mappings. - - This class works by mapping - sqlite3_context.getAggregateContext() to a single piece of - state, of a client-defined type (the T part of this class), which - persists across a "matching set" of the UDF's callbacks. - - This class is a helper providing commonly-needed functionality - - it is not required for use with aggregate or window functions. - Client UDFs are free to perform such mappings using custom - approaches. The provided Aggregate and Window classes - use this. - */ - public static final class PerContextState { - private final java.util.Map> map - = new java.util.HashMap<>(); - - /** - Should be called from a UDF's xStep(), xValue(), and xInverse() - methods, passing it that method's first argument and an initial - value for the persistent state. If there is currently no - mapping for cx.getAggregateContext() within the map, one is - created using the given initial value, else the existing one is - used and the 2nd argument is ignored. It returns a - ValueHolder which can be used to modify that state directly - without requiring that the client update the underlying map's - entry. - - T must be of a type which can be legally stored as a value in - java.util.HashMap. - */ - public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ - ValueHolder rc = map.get(cx.getAggregateContext()); - if(null == rc){ - map.put(cx.getAggregateContext(), rc = new ValueHolder<>(initialValue)); - } - return rc; - } - - /** - Should be called from a UDF's xFinal() method and passed that - method's first argument. This function removes the value - associated with cx.getAggregateContext() from the map and - returns it, returning null if no other UDF method has been - called to set up such a mapping. The latter condition will be - the case if a UDF is used in a statement which has no result - rows. - */ - public T takeAggregateState(sqlite3_context cx){ - final ValueHolder h = map.remove(cx.getAggregateContext()); - return null==h ? null : h.value; - } - } - - //! Subclass for creating scalar functions. - public static abstract class Scalar extends SQLFunction { - - //! As for the xFunc() argument of the C API's sqlite3_create_function() - public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); - - /** - Optionally override to be notified when the UDF is finalized by - SQLite. - */ - public void xDestroy() {} - } - - /** - SQLFunction Subclass for creating aggregate functions. Its T is - the data type of its "accumulator" state, an instance of which is - intended to be be managed using the getAggregateState() and - takeAggregateState() methods. - */ - public static abstract class Aggregate extends SQLFunction { - - //! As for the xStep() argument of the C API's sqlite3_create_function() - public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); - - //! As for the xFinal() argument of the C API's sqlite3_create_function() - public abstract void xFinal(sqlite3_context cx); - - /** - Optionally override to be notified when the UDF is finalized by - SQLite. - */ - public void xDestroy() {} - - //! Per-invocation state for the UDF. - private final PerContextState map = new PerContextState<>(); - - /** - To be called from the implementation's xStep() method, as well - as the xValue() and xInverse() methods of the Window - subclass, to fetch the current per-call UDF state. On the - first call to this method for any given sqlite3_context - argument, the context is set to the given initial value. On all other - calls, the 2nd argument is ignored. - - @see PerContextState#takeAggregateState() - */ - protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ - return map.getAggregateState(cx, initialValue); - } - - /** - To be called from the implementation's xFinal() method to fetch - the final state of the UDF and remove its mapping. - - @see PerContextState#takeAggregateState() - */ - protected final T takeAggregateState(sqlite3_context cx){ - return map.takeAggregateState(cx); - } - } - - /** - An SQLFunction subclass for creating window functions. Note that - Window inherits from Aggregate and each instance is - required to implement the inherited abstract methods from that - class. See Aggregate for information on managing the UDF's - invocation-specific state. - */ - public static abstract class Window extends Aggregate { - - //! As for the xInverse() argument of the C API's sqlite3_create_window_function() - public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); - - //! As for the xValue() argument of the C API's sqlite3_create_window_function() - public abstract void xValue(sqlite3_context cx); - } -} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java deleted file mode 100644 index 9b2a17650..000000000 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ /dev/null @@ -1,1645 +0,0 @@ -/* -** 2023-07-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 declares JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; -import java.nio.charset.StandardCharsets; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; - -/** - This annotation is for flagging parameters which may legally be - null, noting that they may behave differently if passed null but - are prepared to expect null as a value. - - This annotation is solely for the reader's information. -*/ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.PARAMETER) -@interface Nullable{} - -/** - This annotation is for flagging parameters which may not legally be - null. Note that the C-style API does _not_ throw any - NullPointerExceptions on its own because it has a no-throw policy - in order to retain its C-style semantics. - - This annotation is solely for the reader's information. No policy - is in place to programmatically ensure that NotNull is conformed to - in client code. -*/ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.PARAMETER) -@interface NotNull{} - -/** - This class contains the entire sqlite3 JNI API binding. For - client-side use, a static import is recommended: - - ``` - import static org.sqlite.jni.SQLite3Jni.*; - ``` - - The C-side part can be found in sqlite3-jni.c. - - - Only functions which materially differ from their C counterparts - are documented here. The C documetation is otherwise applicable - here: - - https://sqlite.org/c3ref/intro.html - - A handful of Java-specific APIs have been added. - - - ****************************************************************** - *** Warning regarding Java's Modified UTF-8 vs standard UTF-8: *** - ****************************************************************** - - SQLite internally uses UTF-8 encoding, whereas Java natively uses - UTF-16. Java JNI has routines for converting to and from UTF-8, - _but_ JNI uses what its docs call modified UTF-8 (see links below) - Care must be taken when converting Java strings to or from standard - UTF-8 to ensure that the proper conversion is performed. In short, - Java's `String.getBytes(StandardCharsets.UTF_8)` performs the proper - conversion in Java, and there are no JNI C APIs for that conversion - (JNI's `NewStringUTF()` requires its input to be in MUTF-8). - - The known consequences and limitations this discrepancy places on - the SQLite3 JNI binding include: - - - Any functions which return client-side data from a database - take extra care to perform proper conversion, at the cost of - efficiency. - - - Functions which return database identifiers require those - identifiers to have identical representations in UTF-8 and - MUTF-8. They do not perform such conversions (A) because of the - much lower risk of an encoding discrepancy and (B) to avoid - significant extra code involved (see both the Java- and C-side - implementations of sqlite3_db_filename() for an example). Names - of databases, tables, columns, collations, and functions MUST NOT - contain characters which differ in MUTF-8 and UTF-8, or certain - APIs will mis-translate them on their way between languages - (possibly leading to a crash). - - - C functions which take C-style strings without a length argument - require special care when taking input from Java. In particular, - Java strings converted to byte arrays for encoding purposes are - not NUL-terminated, and conversion to a Java byte array must be - careful to add one. Functions which take a length do not require - this. Search the SQLite3Jni class for "\0" for many examples. - - - Similarly, C-side code which deals with strings which might not be - NUL-terminated (e.g. while tokenizing in FTS5-related code) cannot - use JNI's new-string functions to return them to Java because none - of those APIs take a string-length argument. Such cases must - return byte arrays instead of strings. - - Further reading: - - - https://stackoverflow.com/questions/57419723 - - https://stackoverflow.com/questions/7921016 - - https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/ - - https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode - - https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 - -*/ -public final class SQLite3Jni { - static { - System.loadLibrary("sqlite3-jni"); - } - //! Not used - private SQLite3Jni(){} - //! Called from static init code. - private static native void init(); - - /** - Each thread which uses the SQLite3 JNI APIs should call - uncacheJniEnv() when it is done with the library - either right - before it terminates or when it is finished using the SQLite API. - This will clean up any cached per-JNIEnv info. Calling into the - library again after that "should" re-initialize the cache on - demand, but that's untested. - - This call forcibly wipes out all cached information for the - current JNIEnv, a side-effect of which is that behavior is - undefined if any database objects are (A) still active at the - time it is called _and_ (B) calls are subsequently made into the - library with such a database. Doing so will, at best, lead to a - crash. Azt worst, it will lead to the db possibly misbehaving - because some of its Java-bound state has been cleared. There is - no immediate harm in (A) so long as condition (B) is not met. - This process does _not_ actually close any databases or finalize - any prepared statements. For proper library behavior, and to - avoid C-side leaks, be sure to close them before calling this - function. - - Calling this from the main application thread is not strictly - required but is "polite." Additional threads must call this - before ending or they will leak cache entries in the C heap, - which in turn may keep numerous Java-side global references - active. - - This routine returns false without side effects if the current - JNIEnv is not cached, else returns true, but this information is - primarily for testing of the JNI bindings and is not information - which client-level code should use to make any informed - decisions. - */ - public static synchronized native boolean uncacheJniEnv(); - - ////////////////////////////////////////////////////////////////////// - // Maintenance reminder: please keep the sqlite3_.... functions - // alphabetized. The SQLITE_... values. on the other hand, are - // grouped by category. - - - /** - Functions almost as documented for the C API, with these - exceptions: - - - The callback interface is more limited because of - cross-language differences. Specifically, auto-extensions do - not have access to the sqlite3_api object which native - auto-extensions do. - - - If an auto-extension opens a db, thereby triggering recursion - in the auto-extension handler, it will fail with a message - explaining that recursion is not permitted. - - - All of the other auto extension routines will fail without side - effects if invoked from within the execution of an - auto-extension. i.e. auto extensions can neither be added, - removed, nor cleared while one registered with this function is - running. Auto-extensions registered directly with the library - via C code, as opposed to indirectly via Java, do not have that - limitation. - - See the AutoExtension class docs for more information. - - Achtung: it is as yet unknown whether auto extensions registered - from one JNIEnv (thread) can be safely called from another. - */ - public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); - - public static int sqlite3_bind_blob( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data - ){ - return (null == data) - ? sqlite3_bind_null(stmt, ndx) - : sqlite3_bind_blob(stmt, ndx, data, data.length); - } - - private static synchronized native int sqlite3_bind_blob( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n - ); - - public static synchronized native int sqlite3_bind_double( - @NotNull sqlite3_stmt stmt, int ndx, double v - ); - - public static synchronized native int sqlite3_bind_int( - @NotNull sqlite3_stmt stmt, int ndx, int v - ); - - public static synchronized native int sqlite3_bind_int64( - @NotNull sqlite3_stmt stmt, int ndx, long v - ); - - public static synchronized native int sqlite3_bind_null( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native int sqlite3_bind_parameter_count( - @NotNull sqlite3_stmt stmt - ); - - - /** A level of indirection required to ensure that the input to the - C-level function of the same name is a NUL-terminated UTF-8 - string. */ - private static synchronized native int sqlite3_bind_parameter_index( - @NotNull sqlite3_stmt stmt, byte[] paramName - ); - - public static int sqlite3_bind_parameter_index( - @NotNull sqlite3_stmt stmt, @NotNull String paramName - ){ - final byte[] utf8 = (paramName+"\0").getBytes(StandardCharsets.UTF_8); - return sqlite3_bind_parameter_index(stmt, utf8); - } - - public static int sqlite3_bind_text( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data - ){ - if(null == data) return sqlite3_bind_null(stmt, ndx); - final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8); - return sqlite3_bind_text(stmt, ndx, utf8, utf8.length); - } - - public static int sqlite3_bind_text( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data - ){ - return (null == data) - ? sqlite3_bind_null(stmt, ndx) - : sqlite3_bind_text(stmt, ndx, data, data.length); - } - - /** - Works like the C-level sqlite3_bind_text() but (A) assumes - SQLITE_TRANSIENT for the final parameter and (B) behaves like - sqlite3_bind_null() if the data argument is null. - */ - private static synchronized native int sqlite3_bind_text( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes - ); - - public static synchronized native int sqlite3_bind_zeroblob( - @NotNull sqlite3_stmt stmt, int ndx, int n - ); - - public static synchronized native int sqlite3_bind_zeroblob64( - @NotNull sqlite3_stmt stmt, int ndx, long n - ); - - /** - As for the C-level function of the same name, with a BusyHandler - instance in place of a callback function. Pass it a null handler - to clear the busy handler. Calling this multiple times with the - same object is a no-op on the second and subsequent calls. - */ - public static synchronized native int sqlite3_busy_handler( - @NotNull sqlite3 db, @Nullable BusyHandler handler - ); - - public static synchronized native int sqlite3_busy_timeout( - @NotNull sqlite3 db, int ms - ); - - /** - Works like the C API except that it returns false, without side - effects, if auto extensions are currently running. (The JNI-level - list of extensions cannot be manipulated while it is being traversed.) - */ - public static synchronized native boolean sqlite3_cancel_auto_extension( - @NotNull AutoExtension ax - ); - - public static synchronized native int sqlite3_changes( - @NotNull sqlite3 db - ); - - public static synchronized native long sqlite3_changes64( - @NotNull sqlite3 db - ); - - public static synchronized native int sqlite3_clear_bindings( - @NotNull sqlite3_stmt stmt - ); - - public static synchronized native int sqlite3_close( - @NotNull sqlite3 db - ); - - public static synchronized native int sqlite3_close_v2( - @NotNull sqlite3 db - ); - - public static synchronized native byte[] sqlite3_column_blob( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native int sqlite3_column_bytes( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native int sqlite3_column_bytes16( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native int sqlite3_column_count( - @NotNull sqlite3_stmt stmt - ); - - public static synchronized native double sqlite3_column_double( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native int sqlite3_column_int( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native long sqlite3_column_int64( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native String sqlite3_column_name( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native String sqlite3_column_database_name( - @NotNull sqlite3_stmt stmt, int ndx - ); - - /** - Column counterpart of sqlite3_value_java_object(). - */ - public static Object sqlite3_column_java_object( - @NotNull sqlite3_stmt stmt, int ndx - ){ - Object rv = null; - sqlite3_value v = sqlite3_column_value(stmt, ndx); - if(null!=v){ - v = sqlite3_value_dupe(v) /* we need a "protected" value */; - if(null!=v){ - rv = sqlite3_value_java_object(v); - sqlite3_value_free(v); - } - } - return rv; - } - - /** - Column counterpart of sqlite3_value_java_casted(). - */ - @SuppressWarnings("unchecked") - public static T sqlite3_column_java_casted( - @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class type - ){ - final Object o = sqlite3_column_java_object(stmt, ndx); - return type.isInstance(o) ? (T)o : null; - } - - public static synchronized native String sqlite3_column_origin_name( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native String sqlite3_column_table_name( - @NotNull sqlite3_stmt stmt, int ndx - ); - - /** - To extract _standard_ UTF-8, use sqlite3_column_text(). - This API includes no functions for working with Java's Modified - UTF-8. - */ - public static synchronized native String sqlite3_column_text16( - @NotNull sqlite3_stmt stmt, int ndx - ); - - /** - Returns the given column's contents as UTF-8-encoded (not MUTF-8) text. - Use sqlite3_column_text16() to fetch the text - */ - public static synchronized native byte[] sqlite3_column_text( - @NotNull sqlite3_stmt stmt, int ndx - ); - - // The real utility of this function is questionable. - // /** - // Returns a Java value representation based on the value of - // sqlite_value_type(). For integer types it returns either Integer - // or Long, depending on whether the value will fit in an - // Integer. For floating-point values it always returns type Double. - - // If the column was bound using sqlite3_result_java_object() then - // that value, as an Object, is returned. - // */ - // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt, - // int ndx){ - // sqlite3_value v = sqlite3_column_value(stmt, ndx); - // Object rv = null; - // if(null == v) return v; - // v = sqlite3_value_dup(v)/*need a protected value*/; - // if(null == v) return v /* OOM error in C */; - // if(112/* 'p' */ == sqlite3_value_subtype(v)){ - // rv = sqlite3_value_java_object(v); - // }else{ - // switch(sqlite3_value_type(v)){ - // case SQLITE_INTEGER: { - // final long i = sqlite3_value_int64(v); - // rv = (i<=0x7fffffff && i>=-0x7fffffff-1) - // ? new Integer((int)i) : new Long(i); - // break; - // } - // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break; - // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break; - // case SQLITE_TEXT: rv = sqlite3_value_text(v); break; - // default: break; - // } - // } - // sqlite3_value_free(v); - // return rv; - // } - - public static synchronized native int sqlite3_column_type( - @NotNull sqlite3_stmt stmt, int ndx - ); - - public static synchronized native sqlite3_value sqlite3_column_value( - @NotNull sqlite3_stmt stmt, int ndx - ); - - /** - This functions like C's sqlite3_collation_needed16() because - Java's string type is compatible with that interface. - */ - public static synchronized native int sqlite3_collation_needed( - @NotNull sqlite3 db, @Nullable CollationNeeded callback - ); - - /** - Returns the db handle passed to sqlite3_open() or - sqlite3_open_v2(), as opposed to a new wrapper object. - */ - public static synchronized native sqlite3 sqlite3_context_db_handle( - @NotNull sqlite3_context cx - ); - - public static synchronized native CommitHook sqlite3_commit_hook( - @NotNull sqlite3 db, @Nullable CommitHook hook - ); - - public static native String sqlite3_compileoption_get( - int n - ); - - public static native boolean sqlite3_compileoption_used( - @NotNull String optName - ); - - public static synchronized native int sqlite3_create_collation( - @NotNull sqlite3 db, @NotNull String name, int eTextRep, - @NotNull Collation col - ); - - /** - The Java counterpart to the C-native sqlite3_create_function(), - sqlite3_create_function_v2(), and - sqlite3_create_window_function(). Which one it behaves like - depends on which methods the final argument implements. See - SQLFunction's inner classes (Scalar, Aggregate, and Window) - for details. - */ - public static synchronized native int sqlite3_create_function( - @NotNull sqlite3 db, @NotNull String functionName, - int nArg, int eTextRep, @NotNull SQLFunction func - ); - - public static synchronized native int sqlite3_data_count( - @NotNull sqlite3_stmt stmt - ); - - public static synchronized native String sqlite3_db_filename( - @NotNull sqlite3 db, @NotNull String dbName - ); - - /** - Overload for sqlite3_db_config() calls which take (int,int*) - variadic arguments. Returns SQLITE_MISUSE if op is not one of the - SQLITE_DBCONFIG_... options which uses this call form. - */ - public static synchronized native int sqlite3_db_config( - @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out - ); - - /** - Overload for sqlite3_db_config() calls which take a (const char*) - variadic argument. As of SQLite3 v3.43 the only such option is - SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not - SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be - extended in future versions. - */ - public static synchronized native int sqlite3_db_config( - @NotNull sqlite3 db, int op, @NotNull String val - ); - - public static synchronized native int sqlite3_db_status( - @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent, - @NotNull OutputPointer.Int32 pHighwater, boolean reset - ); - - public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); - - public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); - - public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db); - - public static synchronized native boolean sqlite3_extended_result_codes( - @NotNull sqlite3 db, boolean onoff - ); - - public static synchronized native String sqlite3_errmsg(@NotNull sqlite3 db); - - public static synchronized native String sqlite3_errstr(int resultCode); - - /** - Note that the offset values assume UTF-8-encoded SQL. - */ - public static synchronized native int sqlite3_error_offset(@NotNull sqlite3 db); - - public static synchronized native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); - - public static synchronized native int sqlite3_initialize(); - - public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); - - public static synchronized native String sqlite3_libversion(); - - public static synchronized native int sqlite3_libversion_number(); - - /** - Works like its C counterpart and makes the native pointer of the - underling (sqlite3*) object available via - ppDb.getNativePointer(). That pointer is necessary for looking up - the JNI-side native, but clients need not pay it any - heed. Passing the object to sqlite3_close() or sqlite3_close_v2() - will clear that pointer mapping. - - Recall that even if opening fails, the output pointer might be - non-null. Any error message about the failure will be in that - object and it is up to the caller to sqlite3_close() that - db handle. - - Pedantic note: though any number of Java-level sqlite3 objects - may refer to/wrap a single C-level (sqlite3*), the JNI internals - take a reference to the object which is passed to sqlite3_open() - or sqlite3_open_v2() so that they have a predictible object to - pass to, e.g., the sqlite3_collation_needed() callback. - */ - public static synchronized native int sqlite3_open( - @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb - ); - - public static synchronized native int sqlite3_open_v2( - @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, - int flags, @Nullable String zVfs - ); - - /** - The sqlite3_prepare() family of functions require slightly - different signatures than their native counterparts, but - overloading allows us to install several convenience forms. - - All of them which take their SQL in the form of a byte[] require - that it be in UTF-8 encoding unless explicitly noted otherwise. - - The forms which take a "tail" output pointer return (via that - output object) the index into their SQL byte array at which the - end of the first SQL statement processed by the call was - found. That's fundamentally how the C APIs work but making use of - that value requires more copying of the input SQL into - consecutively smaller arrays in order to consume all of - it. (There is an example of doing that in this project's Tester1 - class.) For that vast majority of uses, that capability is not - necessary, however, and overloads are provided which gloss over - that. - */ - private static synchronized native int sqlite3_prepare( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset - ); - - public static int sqlite3_prepare( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset - ){ - return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); - } - - public static int sqlite3_prepare( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt - ){ - return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, null); - } - - public static int sqlite3_prepare( - @NotNull sqlite3 db, @NotNull String sql, - @NotNull OutputPointer.sqlite3_stmt outStmt - ){ - final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); - return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); - } - - private static synchronized native int sqlite3_prepare_v2( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset - ); - - public static int sqlite3_prepare_v2( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset - ){ - return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); - } - - public static int sqlite3_prepare_v2( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt - ){ - return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, null); - } - - public static int sqlite3_prepare_v2( - @NotNull sqlite3 db, @NotNull String sql, - @NotNull OutputPointer.sqlite3_stmt outStmt - ){ - final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); - return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); - } - - private static synchronized native int sqlite3_prepare_v3( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, - int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset - ); - - public static int sqlite3_prepare_v3( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset - ){ - return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, pTailOffset); - } - - public static int sqlite3_prepare_v3( - @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, - @NotNull OutputPointer.sqlite3_stmt outStmt - ){ - return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, null); - } - - public static int sqlite3_prepare_v3( - @NotNull sqlite3 db, @NotNull String sql, int prepFlags, - @NotNull OutputPointer.sqlite3_stmt outStmt - ){ - final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); - return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); - } - - public static synchronized native void sqlite3_progress_handler( - @NotNull sqlite3 db, int n, @Nullable ProgressHandler h - ); - - //TODO??? void *sqlite3_preupdate_hook(...) and friends - - public static synchronized native int sqlite3_reset(@NotNull sqlite3_stmt stmt); - - /** - Works like the C API except that it has no side effects if auto - extensions are currently running. (The JNI-level list of - extensions cannot be manipulated while it is being traversed.) - */ - public static synchronized native void sqlite3_reset_auto_extension(); - - public static synchronized native void sqlite3_result_double( - @NotNull sqlite3_context cx, double v - ); - - /** - The main sqlite3_result_error() impl of which all others are - proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and - msg must be encoded correspondingly. Any other eTextRep value - results in the C-level sqlite3_result_error() being called with - a complaint about the invalid argument. - */ - private static synchronized native void sqlite3_result_error( - @NotNull sqlite3_context cx, @Nullable byte[] msg, - int eTextRep - ); - - public static void sqlite3_result_error( - @NotNull sqlite3_context cx, @NotNull byte[] utf8 - ){ - sqlite3_result_error(cx, utf8, SQLITE_UTF8); - } - - public static void sqlite3_result_error( - @NotNull sqlite3_context cx, @NotNull String msg - ){ - final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_8); - sqlite3_result_error(cx, utf8, SQLITE_UTF8); - } - - public static void sqlite3_result_error16( - @NotNull sqlite3_context cx, @Nullable byte[] utf16 - ){ - sqlite3_result_error(cx, utf16, SQLITE_UTF16); - } - - public static void sqlite3_result_error16( - @NotNull sqlite3_context cx, @NotNull String msg - ){ - final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_16); - sqlite3_result_error(cx, utf8, SQLITE_UTF16); - } - - public static void sqlite3_result_error( - @NotNull sqlite3_context cx, @NotNull Exception e - ){ - sqlite3_result_error(cx, e.getMessage()); - } - - public static void sqlite3_result_error16( - @NotNull sqlite3_context cx, @NotNull Exception e - ){ - sqlite3_result_error16(cx, e.getMessage()); - } - - public static synchronized native void sqlite3_result_error_toobig( - @NotNull sqlite3_context cx - ); - - public static synchronized native void sqlite3_result_error_nomem( - @NotNull sqlite3_context cx - ); - - public static synchronized native void sqlite3_result_error_code( - @NotNull sqlite3_context cx, int c - ); - - public static synchronized native void sqlite3_result_null( - @NotNull sqlite3_context cx - ); - - public static synchronized native void sqlite3_result_int( - @NotNull sqlite3_context cx, int v - ); - - public static synchronized native void sqlite3_result_int64( - @NotNull sqlite3_context cx, long v - ); - - /** - Binds the SQL result to the given object, or - sqlite3_result_null() if o is null. Use - sqlite3_value_java_object() or sqlite3_column_java_object() to - fetch it. - - This is implemented in terms of sqlite3_result_pointer(), but - that function is not exposed to JNI because its 3rd argument must - be a constant string (the library does not copy it), which we - cannot implement cross-language here unless, in the JNI layer, we - allocate such strings and store them somewhere for long-term use - (leaking them more likely than not). Even then, passing around a - pointer via Java like that has little practical use. - - Note that there is no sqlite3_bind_java_object() counterpart. - */ - public static synchronized native void sqlite3_result_java_object( - @NotNull sqlite3_context cx, @NotNull Object o - ); - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Integer v - ){ - sqlite3_result_int(cx, v); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, int v - ){ - sqlite3_result_int(cx, v); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Boolean v - ){ - sqlite3_result_int(cx, v ? 1 : 0); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, boolean v - ){ - sqlite3_result_int(cx, v ? 1 : 0); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Long v - ){ - sqlite3_result_int64(cx, v); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, long v - ){ - sqlite3_result_int64(cx, v); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Double v - ){ - sqlite3_result_double(cx, v); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, double v - ){ - sqlite3_result_double(cx, v); - } - - public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @Nullable String v - ){ - sqlite3_result_text(cx, v); - } - - public static synchronized native void sqlite3_result_value( - @NotNull sqlite3_context cx, @NotNull sqlite3_value v - ); - - public static synchronized native void sqlite3_result_zeroblob( - @NotNull sqlite3_context cx, int n - ); - - public static synchronized native int sqlite3_result_zeroblob64( - @NotNull sqlite3_context cx, long n - ); - - private static synchronized native void sqlite3_result_blob( - @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen - ); - - public static void sqlite3_result_blob( - @NotNull sqlite3_context cx, @Nullable byte[] blob - ){ - sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); - } - - /** - Binds the given text using C's sqlite3_result_blob64() unless: - - - blob is null ==> sqlite3_result_null() - - - blob is too large ==> sqlite3_result_error_toobig() - - If maxLen is larger than blob.length, it is truncated to that - value. If it is negative, results are undefined. - */ - private static synchronized native void sqlite3_result_blob64( - @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen - ); - - public static void sqlite3_result_blob64( - @NotNull sqlite3_context cx, @Nullable byte[] blob - ){ - sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); - } - - private static synchronized native void sqlite3_result_text( - @NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen - ); - - public static void sqlite3_result_text( - @NotNull sqlite3_context cx, @Nullable byte[] text - ){ - sqlite3_result_text(cx, text, null==text ? 0 : text.length); - } - - public static void sqlite3_result_text( - @NotNull sqlite3_context cx, @Nullable String text - ){ - if(null == text) sqlite3_result_null(cx); - else{ - final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8); - sqlite3_result_text(cx, utf8, utf8.length); - } - } - - /** - Binds the given text using C's sqlite3_result_text64() unless: - - - text is null ==> sqlite3_result_null() - - - text is too large ==> sqlite3_result_error_toobig() - - - The `encoding` argument has an invalid value ==> - sqlite3_result_error_code() with SQLITE_FORMAT - - If maxLength (in bytes, not characters) is larger than - text.length, it is silently truncated to text.length. If it is - negative, results are undefined. - */ - private static synchronized native void sqlite3_result_text64( - @NotNull sqlite3_context cx, @Nullable byte[] text, - long maxLength, int encoding - ); - - public static synchronized native int sqlite3_status( - int op, @NotNull OutputPointer.Int32 pCurrent, - @NotNull OutputPointer.Int32 pHighwater, boolean reset - ); - - public static synchronized native int sqlite3_status64( - int op, @NotNull OutputPointer.Int64 pCurrent, - @NotNull OutputPointer.Int64 pHighwater, boolean reset - ); - - /** - Sets the current UDF result to the given bytes, which are assumed - be encoded in UTF-16 using the platform's byte order. - */ - public static void sqlite3_result_text16( - @NotNull sqlite3_context cx, @Nullable byte[] text - ){ - sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16); - } - - public static void sqlite3_result_text16( - @NotNull sqlite3_context cx, @Nullable String text - ){ - if(null == text) sqlite3_result_null(cx); - else{ - final byte[] b = text.getBytes(StandardCharsets.UTF_16); - sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16); - } - } - - /** - Sets the current UDF result to the given bytes, which are assumed - be encoded in UTF-16LE. - */ - public static void sqlite3_result_text16le( - @NotNull sqlite3_context cx, @Nullable String text - ){ - if(null == text) sqlite3_result_null(cx); - else{ - final byte[] b = text.getBytes(StandardCharsets.UTF_16LE); - sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16LE); - } - } - - /** - Sets the current UDF result to the given bytes, which are assumed - be encoded in UTF-16BE. - */ - public static void sqlite3_result_text16be( - @NotNull sqlite3_context cx, @Nullable byte[] text - ){ - sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16BE); - } - - public static void sqlite3_result_text16be( - @NotNull sqlite3_context cx, @NotNull String text - ){ - final byte[] b = text.getBytes(StandardCharsets.UTF_16BE); - sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); - } - - public static synchronized native RollbackHook sqlite3_rollback_hook( - @NotNull sqlite3 db, @Nullable RollbackHook hook - ); - - //! Sets or unsets (if auth is null) the current authorizer. - public static synchronized native int sqlite3_set_authorizer( - @NotNull sqlite3 db, @Nullable Authorizer auth - ); - - public static synchronized native void sqlite3_set_last_insert_rowid( - @NotNull sqlite3 db, long rowid - ); - - public static synchronized native int sqlite3_sleep(int ms); - - public static synchronized native String sqlite3_sourceid(); - - public static synchronized native String sqlite3_sql(@NotNull sqlite3_stmt stmt); - - public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt); - - /** - Internal impl of the public sqlite3_strglob() method. Neither argument - may be NULL and both _MUST_ be NUL-terminated. - */ - private static synchronized native int sqlite3_strglob( - @NotNull byte[] glob, @NotNull byte[] txt - ); - - public static int sqlite3_strglob( - @NotNull String glob, @NotNull String txt - ){ - return sqlite3_strglob( - (glob+"\0").getBytes(StandardCharsets.UTF_8), - (txt+"\0").getBytes(StandardCharsets.UTF_8) - ); - } - - /** - Internal impl of the public sqlite3_strlike() method. Neither - argument may be NULL and both _MUST_ be NUL-terminated. - */ - private static synchronized native int sqlite3_strlike( - @NotNull byte[] glob, @NotNull byte[] txt, int escChar - ); - - public static int sqlite3_strlike( - @NotNull String glob, @NotNull String txt, char escChar - ){ - return sqlite3_strlike( - (glob+"\0").getBytes(StandardCharsets.UTF_8), - (txt+"\0").getBytes(StandardCharsets.UTF_8), - (int)escChar - ); - } - - public static synchronized native int sqlite3_threadsafe(); - - public static synchronized native int sqlite3_total_changes(@NotNull sqlite3 db); - - public static synchronized native long sqlite3_total_changes64(@NotNull sqlite3 db); - - /** - Works like C's sqlite3_trace_v2() except that the 3rd argument to that - function is elided here because the roles of that functions' 3rd and 4th - arguments are encapsulated in the final argument to this function. - - Unlike the C API, which is documented as always returning 0, this - implementation returns SQLITE_NOMEM if allocation of per-db - mapping state fails and SQLITE_ERROR if the given callback object - cannot be processed propertly (i.e. an internal error). - */ - public static synchronized native int sqlite3_trace_v2( - @NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer - ); - - public static synchronized native UpdateHook sqlite3_update_hook( - sqlite3 db, UpdateHook hook - ); - - public static synchronized native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); - - public static synchronized native int sqlite3_value_bytes(@NotNull sqlite3_value v); - - public static synchronized native int sqlite3_value_bytes16(@NotNull sqlite3_value v); - - public static synchronized native double sqlite3_value_double(@NotNull sqlite3_value v); - - public static synchronized native sqlite3_value sqlite3_value_dupe( - @NotNull sqlite3_value v - ); - - public static synchronized native int sqlite3_value_encoding(@NotNull sqlite3_value v); - - public static synchronized native void sqlite3_value_free(@Nullable sqlite3_value v); - - public static synchronized native int sqlite3_value_int(@NotNull sqlite3_value v); - - public static synchronized native long sqlite3_value_int64(@NotNull sqlite3_value v); - - /** - If the given value was set using sqlite3_result_java_value() then - this function returns that object, else it returns null. - - It is up to the caller to inspect the object to determine its - type, and cast it if necessary. - */ - public static synchronized native Object sqlite3_value_java_object( - @NotNull sqlite3_value v - ); - - /** - A variant of sqlite3_value_java_object() which returns the - fetched object cast to T if the object is an instance of the - given Class. It returns null in all other cases. - */ - @SuppressWarnings("unchecked") - public static T sqlite3_value_java_casted(@NotNull sqlite3_value v, - @NotNull Class type){ - final Object o = sqlite3_value_java_object(v); - return type.isInstance(o) ? (T)o : null; - } - - /** - See sqlite3_column_text() for notes about encoding conversions. - See sqlite3_value_text_utf8() for how to extract text in standard - UTF-8. - */ - public static synchronized native String sqlite3_value_text(@NotNull sqlite3_value v); - - /** - The sqlite3_value counterpart of sqlite3_column_text_utf8(). - */ - public static synchronized native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); - - public static synchronized native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); - - public static synchronized native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); - - public static synchronized native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); - - public static synchronized native int sqlite3_value_type(@NotNull sqlite3_value v); - - public static synchronized native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); - - public static synchronized native int sqlite3_value_nochange(@NotNull sqlite3_value v); - - public static synchronized native int sqlite3_value_frombind(@NotNull sqlite3_value v); - - public static synchronized native int sqlite3_value_subtype(@NotNull sqlite3_value v); - - /** - Cleans up all per-JNIEnv and per-db state managed by the library - then calls the C-native sqlite3_shutdown(). - */ - public static synchronized native int sqlite3_shutdown(); - - /** - This is NOT part of the public API. It exists solely as a place - to hook in arbitrary C-side code during development and testing - of this library. - */ - public static synchronized native void sqlite3_do_something_for_developer(); - - ////////////////////////////////////////////////////////////////////// - // SQLITE_... constants follow... - - // version info - public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number(); - public static final String SQLITE_VERSION = sqlite3_libversion(); - public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); - - //! Feature flags which are initialized at lib startup. Necessarily - // non-final so that lib init can fill out the proper values, - // but modifying them from client code has no effect. - public static boolean SQLITE_ENABLE_FTS5 = false; - - // access - public static final int SQLITE_ACCESS_EXISTS = 0; - public static final int SQLITE_ACCESS_READWRITE = 1; - public static final int SQLITE_ACCESS_READ = 2; - - // authorizer - public static final int SQLITE_DENY = 1; - public static final int SQLITE_IGNORE = 2; - public static final int SQLITE_CREATE_INDEX = 1; - public static final int SQLITE_CREATE_TABLE = 2; - public static final int SQLITE_CREATE_TEMP_INDEX = 3; - public static final int SQLITE_CREATE_TEMP_TABLE = 4; - public static final int SQLITE_CREATE_TEMP_TRIGGER = 5; - public static final int SQLITE_CREATE_TEMP_VIEW = 6; - public static final int SQLITE_CREATE_TRIGGER = 7; - public static final int SQLITE_CREATE_VIEW = 8; - public static final int SQLITE_DELETE = 9; - public static final int SQLITE_DROP_INDEX = 10; - public static final int SQLITE_DROP_TABLE = 11; - public static final int SQLITE_DROP_TEMP_INDEX = 12; - public static final int SQLITE_DROP_TEMP_TABLE = 13; - public static final int SQLITE_DROP_TEMP_TRIGGER = 14; - public static final int SQLITE_DROP_TEMP_VIEW = 15; - public static final int SQLITE_DROP_TRIGGER = 16; - public static final int SQLITE_DROP_VIEW = 17; - public static final int SQLITE_INSERT = 18; - public static final int SQLITE_PRAGMA = 19; - public static final int SQLITE_READ = 20; - public static final int SQLITE_SELECT = 21; - public static final int SQLITE_TRANSACTION = 22; - public static final int SQLITE_UPDATE = 23; - public static final int SQLITE_ATTACH = 24; - public static final int SQLITE_DETACH = 25; - public static final int SQLITE_ALTER_TABLE = 26; - public static final int SQLITE_REINDEX = 27; - public static final int SQLITE_ANALYZE = 28; - public static final int SQLITE_CREATE_VTABLE = 29; - public static final int SQLITE_DROP_VTABLE = 30; - public static final int SQLITE_FUNCTION = 31; - public static final int SQLITE_SAVEPOINT = 32; - public static final int SQLITE_RECURSIVE = 33; - - // blob finalizers: these should, because they are treated as - // special pointer values in C, ideally have the same sizeof() as - // the platform's (void*), but we can't know that size from here. - public static final long SQLITE_STATIC = 0; - public static final long SQLITE_TRANSIENT = -1; - - // changeset - public static final int SQLITE_CHANGESETSTART_INVERT = 2; - public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1; - public static final int SQLITE_CHANGESETAPPLY_INVERT = 2; - public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4; - public static final int SQLITE_CHANGESET_DATA = 1; - public static final int SQLITE_CHANGESET_NOTFOUND = 2; - public static final int SQLITE_CHANGESET_CONFLICT = 3; - public static final int SQLITE_CHANGESET_CONSTRAINT = 4; - public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5; - public static final int SQLITE_CHANGESET_OMIT = 0; - public static final int SQLITE_CHANGESET_REPLACE = 1; - public static final int SQLITE_CHANGESET_ABORT = 2; - - // config - public static final int SQLITE_CONFIG_SINGLETHREAD = 1; - public static final int SQLITE_CONFIG_MULTITHREAD = 2; - public static final int SQLITE_CONFIG_SERIALIZED = 3; - public static final int SQLITE_CONFIG_MALLOC = 4; - public static final int SQLITE_CONFIG_GETMALLOC = 5; - public static final int SQLITE_CONFIG_SCRATCH = 6; - public static final int SQLITE_CONFIG_PAGECACHE = 7; - public static final int SQLITE_CONFIG_HEAP = 8; - public static final int SQLITE_CONFIG_MEMSTATUS = 9; - public static final int SQLITE_CONFIG_MUTEX = 10; - public static final int SQLITE_CONFIG_GETMUTEX = 11; - public static final int SQLITE_CONFIG_LOOKASIDE = 13; - public static final int SQLITE_CONFIG_PCACHE = 14; - public static final int SQLITE_CONFIG_GETPCACHE = 15; - public static final int SQLITE_CONFIG_LOG = 16; - public static final int SQLITE_CONFIG_URI = 17; - public static final int SQLITE_CONFIG_PCACHE2 = 18; - public static final int SQLITE_CONFIG_GETPCACHE2 = 19; - public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20; - public static final int SQLITE_CONFIG_SQLLOG = 21; - public static final int SQLITE_CONFIG_MMAP_SIZE = 22; - public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23; - public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24; - public static final int SQLITE_CONFIG_PMASZ = 25; - public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26; - public static final int SQLITE_CONFIG_SMALL_MALLOC = 27; - public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28; - public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29; - - // data types - public static final int SQLITE_INTEGER = 1; - public static final int SQLITE_FLOAT = 2; - public static final int SQLITE_TEXT = 3; - public static final int SQLITE_BLOB = 4; - public static final int SQLITE_NULL = 5; - - // db config - public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000; - public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001; - public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002; - public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003; - public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004; - public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005; - public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006; - public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007; - public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008; - public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009; - public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010; - public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011; - public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012; - public static final int SQLITE_DBCONFIG_DQS_DML = 1013; - public static final int SQLITE_DBCONFIG_DQS_DDL = 1014; - public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015; - public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016; - public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017; - public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018; - public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019; - public static final int SQLITE_DBCONFIG_MAX = 1019; - - // db status - public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0; - public static final int SQLITE_DBSTATUS_CACHE_USED = 1; - public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2; - public static final int SQLITE_DBSTATUS_STMT_USED = 3; - public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4; - public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5; - public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6; - public static final int SQLITE_DBSTATUS_CACHE_HIT = 7; - public static final int SQLITE_DBSTATUS_CACHE_MISS = 8; - public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9; - public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10; - public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11; - public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12; - public static final int SQLITE_DBSTATUS_MAX = 12; - - // encodings - public static final int SQLITE_UTF8 = 1; - public static final int SQLITE_UTF16LE = 2; - public static final int SQLITE_UTF16BE = 3; - public static final int SQLITE_UTF16 = 4; - public static final int SQLITE_UTF16_ALIGNED = 8; - - // fcntl - public static final int SQLITE_FCNTL_LOCKSTATE = 1; - public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2; - public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3; - public static final int SQLITE_FCNTL_LAST_ERRNO = 4; - public static final int SQLITE_FCNTL_SIZE_HINT = 5; - public static final int SQLITE_FCNTL_CHUNK_SIZE = 6; - public static final int SQLITE_FCNTL_FILE_POINTER = 7; - public static final int SQLITE_FCNTL_SYNC_OMITTED = 8; - public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9; - public static final int SQLITE_FCNTL_PERSIST_WAL = 10; - public static final int SQLITE_FCNTL_OVERWRITE = 11; - public static final int SQLITE_FCNTL_VFSNAME = 12; - public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13; - public static final int SQLITE_FCNTL_PRAGMA = 14; - public static final int SQLITE_FCNTL_BUSYHANDLER = 15; - public static final int SQLITE_FCNTL_TEMPFILENAME = 16; - public static final int SQLITE_FCNTL_MMAP_SIZE = 18; - public static final int SQLITE_FCNTL_TRACE = 19; - public static final int SQLITE_FCNTL_HAS_MOVED = 20; - public static final int SQLITE_FCNTL_SYNC = 21; - public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22; - public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23; - public static final int SQLITE_FCNTL_WAL_BLOCK = 24; - public static final int SQLITE_FCNTL_ZIPVFS = 25; - public static final int SQLITE_FCNTL_RBU = 26; - public static final int SQLITE_FCNTL_VFS_POINTER = 27; - public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28; - public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29; - public static final int SQLITE_FCNTL_PDB = 30; - public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31; - public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32; - public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33; - public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34; - public static final int SQLITE_FCNTL_DATA_VERSION = 35; - public static final int SQLITE_FCNTL_SIZE_LIMIT = 36; - public static final int SQLITE_FCNTL_CKPT_DONE = 37; - public static final int SQLITE_FCNTL_RESERVE_BYTES = 38; - public static final int SQLITE_FCNTL_CKPT_START = 39; - public static final int SQLITE_FCNTL_EXTERNAL_READER = 40; - public static final int SQLITE_FCNTL_CKSM_FILE = 41; - public static final int SQLITE_FCNTL_RESET_CACHE = 42; - - // flock - public static final int SQLITE_LOCK_NONE = 0; - public static final int SQLITE_LOCK_SHARED = 1; - public static final int SQLITE_LOCK_RESERVED = 2; - public static final int SQLITE_LOCK_PENDING = 3; - public static final int SQLITE_LOCK_EXCLUSIVE = 4; - - // iocap - public static final int SQLITE_IOCAP_ATOMIC = 1; - public static final int SQLITE_IOCAP_ATOMIC512 = 2; - public static final int SQLITE_IOCAP_ATOMIC1K = 4; - public static final int SQLITE_IOCAP_ATOMIC2K = 8; - public static final int SQLITE_IOCAP_ATOMIC4K = 16; - public static final int SQLITE_IOCAP_ATOMIC8K = 32; - public static final int SQLITE_IOCAP_ATOMIC16K = 64; - public static final int SQLITE_IOCAP_ATOMIC32K = 128; - public static final int SQLITE_IOCAP_ATOMIC64K = 256; - public static final int SQLITE_IOCAP_SAFE_APPEND = 512; - public static final int SQLITE_IOCAP_SEQUENTIAL = 1024; - public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048; - public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096; - public static final int SQLITE_IOCAP_IMMUTABLE = 8192; - public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384; - - // limits. These get injected at init-time so that they stay in sync - // with the compile-time options. This unfortunately means they are - // not final, but keeping them in sync with their C values seems - // more important than protecting users from assigning to these - // (with unpredictable results). - public static int SQLITE_MAX_ALLOCATION_SIZE = -1; - public static int SQLITE_LIMIT_LENGTH = -1; - public static int SQLITE_MAX_LENGTH = -1; - public static int SQLITE_LIMIT_SQL_LENGTH = -1; - public static int SQLITE_MAX_SQL_LENGTH = -1; - public static int SQLITE_LIMIT_COLUMN = -1; - public static int SQLITE_MAX_COLUMN = -1; - public static int SQLITE_LIMIT_EXPR_DEPTH = -1; - public static int SQLITE_MAX_EXPR_DEPTH = -1; - public static int SQLITE_LIMIT_COMPOUND_SELECT = -1; - public static int SQLITE_MAX_COMPOUND_SELECT = -1; - public static int SQLITE_LIMIT_VDBE_OP = -1; - public static int SQLITE_MAX_VDBE_OP = -1; - public static int SQLITE_LIMIT_FUNCTION_ARG = -1; - public static int SQLITE_MAX_FUNCTION_ARG = -1; - public static int SQLITE_LIMIT_ATTACHED = -1; - public static int SQLITE_MAX_ATTACHED = -1; - public static int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = -1; - public static int SQLITE_MAX_LIKE_PATTERN_LENGTH = -1; - public static int SQLITE_LIMIT_VARIABLE_NUMBER = -1; - public static int SQLITE_MAX_VARIABLE_NUMBER = -1; - public static int SQLITE_LIMIT_TRIGGER_DEPTH = -1; - public static int SQLITE_MAX_TRIGGER_DEPTH = -1; - public static int SQLITE_LIMIT_WORKER_THREADS = -1; - public static int SQLITE_MAX_WORKER_THREADS = -1; - - // open flags - public static final int SQLITE_OPEN_READONLY = 1; - public static final int SQLITE_OPEN_READWRITE = 2; - public static final int SQLITE_OPEN_CREATE = 4; - public static final int SQLITE_OPEN_URI = 64; - public static final int SQLITE_OPEN_MEMORY = 128; - public static final int SQLITE_OPEN_NOMUTEX = 32768; - public static final int SQLITE_OPEN_FULLMUTEX = 65536; - public static final int SQLITE_OPEN_SHAREDCACHE = 131072; - public static final int SQLITE_OPEN_PRIVATECACHE = 262144; - public static final int SQLITE_OPEN_EXRESCODE = 33554432; - public static final int SQLITE_OPEN_NOFOLLOW = 16777216; - public static final int SQLITE_OPEN_MAIN_DB = 256; - public static final int SQLITE_OPEN_MAIN_JOURNAL = 2048; - public static final int SQLITE_OPEN_TEMP_DB = 512; - public static final int SQLITE_OPEN_TEMP_JOURNAL = 4096; - public static final int SQLITE_OPEN_TRANSIENT_DB = 1024; - public static final int SQLITE_OPEN_SUBJOURNAL = 8192; - public static final int SQLITE_OPEN_SUPER_JOURNAL = 16384; - public static final int SQLITE_OPEN_WAL = 524288; - public static final int SQLITE_OPEN_DELETEONCLOSE = 8; - public static final int SQLITE_OPEN_EXCLUSIVE = 16; - - // prepare flags - public static final int SQLITE_PREPARE_PERSISTENT = 1; - public static final int SQLITE_PREPARE_NORMALIZE = 2; - public static final int SQLITE_PREPARE_NO_VTAB = 4; - - // result codes - public static final int SQLITE_OK = 0; - public static final int SQLITE_ERROR = 1; - public static final int SQLITE_INTERNAL = 2; - public static final int SQLITE_PERM = 3; - public static final int SQLITE_ABORT = 4; - public static final int SQLITE_BUSY = 5; - public static final int SQLITE_LOCKED = 6; - public static final int SQLITE_NOMEM = 7; - public static final int SQLITE_READONLY = 8; - public static final int SQLITE_INTERRUPT = 9; - public static final int SQLITE_IOERR = 10; - public static final int SQLITE_CORRUPT = 11; - public static final int SQLITE_NOTFOUND = 12; - public static final int SQLITE_FULL = 13; - public static final int SQLITE_CANTOPEN = 14; - public static final int SQLITE_PROTOCOL = 15; - public static final int SQLITE_EMPTY = 16; - public static final int SQLITE_SCHEMA = 17; - public static final int SQLITE_TOOBIG = 18; - public static final int SQLITE_CONSTRAINT = 19; - public static final int SQLITE_MISMATCH = 20; - public static final int SQLITE_MISUSE = 21; - public static final int SQLITE_NOLFS = 22; - public static final int SQLITE_AUTH = 23; - public static final int SQLITE_FORMAT = 24; - public static final int SQLITE_RANGE = 25; - public static final int SQLITE_NOTADB = 26; - public static final int SQLITE_NOTICE = 27; - public static final int SQLITE_WARNING = 28; - public static final int SQLITE_ROW = 100; - public static final int SQLITE_DONE = 101; - public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257; - public static final int SQLITE_ERROR_RETRY = 513; - public static final int SQLITE_ERROR_SNAPSHOT = 769; - public static final int SQLITE_IOERR_READ = 266; - public static final int SQLITE_IOERR_SHORT_READ = 522; - public static final int SQLITE_IOERR_WRITE = 778; - public static final int SQLITE_IOERR_FSYNC = 1034; - public static final int SQLITE_IOERR_DIR_FSYNC = 1290; - public static final int SQLITE_IOERR_TRUNCATE = 1546; - public static final int SQLITE_IOERR_FSTAT = 1802; - public static final int SQLITE_IOERR_UNLOCK = 2058; - public static final int SQLITE_IOERR_RDLOCK = 2314; - public static final int SQLITE_IOERR_DELETE = 2570; - public static final int SQLITE_IOERR_BLOCKED = 2826; - public static final int SQLITE_IOERR_NOMEM = 3082; - public static final int SQLITE_IOERR_ACCESS = 3338; - public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594; - public static final int SQLITE_IOERR_LOCK = 3850; - public static final int SQLITE_IOERR_CLOSE = 4106; - public static final int SQLITE_IOERR_DIR_CLOSE = 4362; - public static final int SQLITE_IOERR_SHMOPEN = 4618; - public static final int SQLITE_IOERR_SHMSIZE = 4874; - public static final int SQLITE_IOERR_SHMLOCK = 5130; - public static final int SQLITE_IOERR_SHMMAP = 5386; - public static final int SQLITE_IOERR_SEEK = 5642; - public static final int SQLITE_IOERR_DELETE_NOENT = 5898; - public static final int SQLITE_IOERR_MMAP = 6154; - public static final int SQLITE_IOERR_GETTEMPPATH = 6410; - public static final int SQLITE_IOERR_CONVPATH = 6666; - public static final int SQLITE_IOERR_VNODE = 6922; - public static final int SQLITE_IOERR_AUTH = 7178; - public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434; - public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690; - public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946; - public static final int SQLITE_IOERR_DATA = 8202; - public static final int SQLITE_IOERR_CORRUPTFS = 8458; - public static final int SQLITE_LOCKED_SHAREDCACHE = 262; - public static final int SQLITE_LOCKED_VTAB = 518; - public static final int SQLITE_BUSY_RECOVERY = 261; - public static final int SQLITE_BUSY_SNAPSHOT = 517; - public static final int SQLITE_BUSY_TIMEOUT = 773; - public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270; - public static final int SQLITE_CANTOPEN_ISDIR = 526; - public static final int SQLITE_CANTOPEN_FULLPATH = 782; - public static final int SQLITE_CANTOPEN_CONVPATH = 1038; - public static final int SQLITE_CANTOPEN_SYMLINK = 1550; - public static final int SQLITE_CORRUPT_VTAB = 267; - public static final int SQLITE_CORRUPT_SEQUENCE = 523; - public static final int SQLITE_CORRUPT_INDEX = 779; - public static final int SQLITE_READONLY_RECOVERY = 264; - public static final int SQLITE_READONLY_CANTLOCK = 520; - public static final int SQLITE_READONLY_ROLLBACK = 776; - public static final int SQLITE_READONLY_DBMOVED = 1032; - public static final int SQLITE_READONLY_CANTINIT = 1288; - public static final int SQLITE_READONLY_DIRECTORY = 1544; - public static final int SQLITE_ABORT_ROLLBACK = 516; - public static final int SQLITE_CONSTRAINT_CHECK = 275; - public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531; - public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787; - public static final int SQLITE_CONSTRAINT_FUNCTION = 1043; - public static final int SQLITE_CONSTRAINT_NOTNULL = 1299; - public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555; - public static final int SQLITE_CONSTRAINT_TRIGGER = 1811; - public static final int SQLITE_CONSTRAINT_UNIQUE = 2067; - public static final int SQLITE_CONSTRAINT_VTAB = 2323; - public static final int SQLITE_CONSTRAINT_ROWID = 2579; - public static final int SQLITE_CONSTRAINT_PINNED = 2835; - public static final int SQLITE_CONSTRAINT_DATATYPE = 3091; - public static final int SQLITE_NOTICE_RECOVER_WAL = 283; - public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539; - public static final int SQLITE_WARNING_AUTOINDEX = 284; - public static final int SQLITE_AUTH_USER = 279; - public static final int SQLITE_OK_LOAD_PERMANENTLY = 256; - - // serialize - public static final int SQLITE_SERIALIZE_NOCOPY = 1; - public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1; - public static final int SQLITE_DESERIALIZE_READONLY = 4; - public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2; - - // session - public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1; - public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1; - - // sqlite3 status - public static final int SQLITE_STATUS_MEMORY_USED = 0; - public static final int SQLITE_STATUS_PAGECACHE_USED = 1; - public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2; - public static final int SQLITE_STATUS_MALLOC_SIZE = 5; - public static final int SQLITE_STATUS_PARSER_STACK = 6; - public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7; - public static final int SQLITE_STATUS_MALLOC_COUNT = 9; - - // stmt status - public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1; - public static final int SQLITE_STMTSTATUS_SORT = 2; - public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3; - public static final int SQLITE_STMTSTATUS_VM_STEP = 4; - public static final int SQLITE_STMTSTATUS_REPREPARE = 5; - public static final int SQLITE_STMTSTATUS_RUN = 6; - public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7; - public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8; - public static final int SQLITE_STMTSTATUS_MEMUSED = 99; - - // sync flags - public static final int SQLITE_SYNC_NORMAL = 2; - public static final int SQLITE_SYNC_FULL = 3; - public static final int SQLITE_SYNC_DATAONLY = 16; - - // tracing flags - public static final int SQLITE_TRACE_STMT = 1; - public static final int SQLITE_TRACE_PROFILE = 2; - public static final int SQLITE_TRACE_ROW = 4; - public static final int SQLITE_TRACE_CLOSE = 8; - - // transaction state - public static final int SQLITE_TXN_NONE = 0; - public static final int SQLITE_TXN_READ = 1; - public static final int SQLITE_TXN_WRITE = 2; - - // udf flags - public static final int SQLITE_DETERMINISTIC = 2048; - public static final int SQLITE_DIRECTONLY = 524288; - public static final int SQLITE_INNOCUOUS = 2097152; - - // virtual tables - public static final int SQLITE_INDEX_SCAN_UNIQUE = 1; - public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2; - public static final int SQLITE_INDEX_CONSTRAINT_GT = 4; - public static final int SQLITE_INDEX_CONSTRAINT_LE = 8; - public static final int SQLITE_INDEX_CONSTRAINT_LT = 16; - public static final int SQLITE_INDEX_CONSTRAINT_GE = 32; - public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64; - public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65; - public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66; - public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67; - public static final int SQLITE_INDEX_CONSTRAINT_NE = 68; - public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69; - public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70; - public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71; - public static final int SQLITE_INDEX_CONSTRAINT_IS = 72; - public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73; - public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74; - public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150; - public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1; - public static final int SQLITE_VTAB_INNOCUOUS = 2; - public static final int SQLITE_VTAB_DIRECTONLY = 3; - public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4; - public static final int SQLITE_ROLLBACK = 1; - public static final int SQLITE_FAIL = 3; - public static final int SQLITE_REPLACE = 5; - static { - // This MUST come after the SQLITE_MAX_... values or else - // attempting to modify them silently fails. - init(); - } -} diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java deleted file mode 100644 index dca49faf6..000000000 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ /dev/null @@ -1,1163 +0,0 @@ -/* -** 2023-07-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 contains a set of tests for the sqlite3 JNI bindings. -*/ -package org.sqlite.jni; -import static org.sqlite.jni.SQLite3Jni.*; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -public class Tester1 { - private static final class Metrics { - int dbOpen; - } - - static final Metrics metrics = new Metrics(); - private static final OutputPointer.sqlite3_stmt outStmt - = new OutputPointer.sqlite3_stmt(); - - public static void out(Object val){ - System.out.print(val); - } - - public static void outln(Object val){ - System.out.println(val); - } - - @SuppressWarnings("unchecked") - public static void out(Object... vals){ - int n = 0; - for(Object v : vals) out((n++>0 ? " " : "")+v); - } - - @SuppressWarnings("unchecked") - public static void outln(Object... vals){ - out(vals); out("\n"); - } - - static int affirmCount = 0; - public static void affirm(Boolean v){ - ++affirmCount; - assert( v /* prefer assert over exception if it's enabled because - the JNI layer sometimes has to suppress exceptions. */); - if( !v ) throw new RuntimeException("Assertion failed."); - } - - private static void test1(){ - outln("libversion_number:", - sqlite3_libversion_number() - + "\n" - + sqlite3_libversion() - + "\n" - + SQLITE_SOURCE_ID); - affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); - //outln("threadsafe = "+sqlite3_threadsafe()); - affirm(SQLITE_MAX_LENGTH > 0); - affirm(SQLITE_MAX_TRIGGER_DEPTH>0); - } - - public static sqlite3 createNewDb(){ - final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); - int rc = sqlite3_open(":memory:", out); - ++metrics.dbOpen; - sqlite3 db = out.take(); - if( 0!=rc ){ - final String msg = db.getNativePointer()==0 - ? sqlite3_errstr(rc) - : sqlite3_errmsg(db); - throw new RuntimeException("Opening db failed: "+msg); - } - affirm( null == out.get() ); - affirm( 0 != db.getNativePointer() ); - rc = sqlite3_busy_timeout(db, 2000); - affirm( 0 == rc ); - return db; - } - - public static void execSql(sqlite3 db, String[] sql){ - execSql(db, String.join("", sql)); - } - - public static int execSql(sqlite3 db, boolean throwOnError, String sql){ - OutputPointer.Int32 oTail = new OutputPointer.Int32(); - final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); - int pos = 0, n = 1; - byte[] sqlChunk = sqlUtf8; - int rc = 0; - sqlite3_stmt stmt = null; - while(pos < sqlChunk.length){ - if(pos > 0){ - sqlChunk = Arrays.copyOfRange(sqlChunk, pos, - sqlChunk.length); - } - if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); - if(throwOnError) affirm(0 == rc); - else if( 0!=rc ) break; - pos = oTail.value; - stmt = outStmt.take(); - if( null == stmt ){ - // empty statement was parsed. - continue; - } - affirm(0 != stmt.getNativePointer()); - while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ - } - sqlite3_finalize(stmt); - affirm(0 == stmt.getNativePointer()); - if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ - break; - } - } - sqlite3_finalize(stmt); - if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; - if( 0!=rc && throwOnError){ - throw new RuntimeException("db op failed with rc=" - +rc+": "+sqlite3_errmsg(db)); - } - return rc; - } - - public static void execSql(sqlite3 db, String sql){ - execSql(db, true, sql); - } - - public static sqlite3_stmt prepare(sqlite3 db, String sql){ - outStmt.clear(); - int rc = sqlite3_prepare(db, sql, outStmt); - affirm( 0 == rc ); - final sqlite3_stmt rv = outStmt.take(); - affirm( null == outStmt.get() ); - affirm( 0 != rv.getNativePointer() ); - return rv; - } - - private static void testCompileOption(){ - int i = 0; - String optName; - outln("compile options:"); - for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){ - outln("\t"+optName+"\t (used="+ - sqlite3_compileoption_used(optName)+")"); - } - - } - - private static void testOpenDb1(){ - final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); - int rc = sqlite3_open(":memory:", out); - ++metrics.dbOpen; - sqlite3 db = out.get(); - affirm(0 == rc); - affirm(0 < db.getNativePointer()); - sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null) - /* This function has different mangled names in jdk8 vs jdk19, - and this call is here to ensure that the build fails - if it cannot find both names. */; - sqlite3_close_v2(db); - affirm(0 == db.getNativePointer()); - } - - private static void testOpenDb2(){ - final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); - int rc = sqlite3_open_v2(":memory:", out, - SQLITE_OPEN_READWRITE - | SQLITE_OPEN_CREATE, null); - ++metrics.dbOpen; - affirm(0 == rc); - sqlite3 db = out.get(); - affirm(0 < db.getNativePointer()); - sqlite3_close_v2(db); - affirm(0 == db.getNativePointer()); - } - - private static void testPrepare123(){ - sqlite3 db = createNewDb(); - int rc; - rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt); - affirm(0 == rc); - sqlite3_stmt stmt = outStmt.get(); - affirm(0 != stmt.getNativePointer()); - rc = sqlite3_step(stmt); - affirm(SQLITE_DONE == rc); - sqlite3_finalize(stmt); - affirm(0 == stmt.getNativePointer()); - - { /* Demonstrate how to use the "zTail" option of - sqlite3_prepare() family of functions. */ - OutputPointer.Int32 oTail = new OutputPointer.Int32(); - final byte[] sqlUtf8 = - "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)" - .getBytes(StandardCharsets.UTF_8); - int pos = 0, n = 1; - byte[] sqlChunk = sqlUtf8; - while(pos < sqlChunk.length){ - if(pos > 0){ - sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); - } - //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos); - if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); - affirm(0 == rc); - stmt = outStmt.get(); - pos = oTail.value; - /*outln("SQL tail pos = "+pos+". Chunk = "+ - (new String(Arrays.copyOfRange(sqlChunk,0,pos), - StandardCharsets.UTF_8)));*/ - switch(n){ - case 1: affirm(19 == pos); break; - case 2: affirm(36 == pos); break; - default: affirm( false /* can't happen */ ); - - } - ++n; - affirm(0 != stmt.getNativePointer()); - rc = sqlite3_step(stmt); - affirm(SQLITE_DONE == rc); - sqlite3_finalize(stmt); - affirm(0 == stmt.getNativePointer()); - } - } - - - rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", - SQLITE_PREPARE_NORMALIZE, outStmt); - affirm(0 == rc); - stmt = outStmt.get(); - affirm(0 != stmt.getNativePointer()); - sqlite3_finalize(stmt); - affirm(0 == stmt.getNativePointer() ); - sqlite3_close_v2(db); - } - - private static void testBindFetchInt(){ - sqlite3 db = createNewDb(); - execSql(db, "CREATE TABLE t(a)"); - - sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);"); - affirm(1 == sqlite3_bind_parameter_count(stmt)); - final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a"); - affirm(1 == paramNdx); - int total1 = 0; - long rowid = -1; - int changes = sqlite3_changes(db); - int changesT = sqlite3_total_changes(db); - long changes64 = sqlite3_changes64(db); - long changesT64 = sqlite3_total_changes64(db); - int rc; - for(int i = 99; i < 102; ++i ){ - total1 += i; - rc = sqlite3_bind_int(stmt, paramNdx, i); - affirm(0 == rc); - rc = sqlite3_step(stmt); - sqlite3_reset(stmt); - affirm(SQLITE_DONE == rc); - long x = sqlite3_last_insert_rowid(db); - affirm(x > rowid); - rowid = x; - } - sqlite3_finalize(stmt); - affirm(300 == total1); - affirm(sqlite3_changes(db) > changes); - affirm(sqlite3_total_changes(db) > changesT); - affirm(sqlite3_changes64(db) > changes64); - affirm(sqlite3_total_changes64(db) > changesT64); - stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); - int total2 = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - total2 += sqlite3_column_int(stmt, 0); - sqlite3_value sv = sqlite3_column_value(stmt, 0); - affirm( null != sv ); - affirm( 0 != sv.getNativePointer() ); - affirm( SQLITE_INTEGER == sqlite3_value_type(sv) ); - } - sqlite3_finalize(stmt); - affirm(total1 == total2); - sqlite3_close_v2(db); - affirm(0 == db.getNativePointer()); - } - - private static void testBindFetchInt64(){ - sqlite3 db = createNewDb(); - execSql(db, "CREATE TABLE t(a)"); - sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); - long total1 = 0; - for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){ - total1 += i; - sqlite3_bind_int64(stmt, 1, i); - sqlite3_step(stmt); - sqlite3_reset(stmt); - } - sqlite3_finalize(stmt); - stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); - long total2 = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - total2 += sqlite3_column_int64(stmt, 0); - } - sqlite3_finalize(stmt); - affirm(total1 == total2); - sqlite3_close_v2(db); - } - - private static void testBindFetchDouble(){ - sqlite3 db = createNewDb(); - execSql(db, "CREATE TABLE t(a)"); - sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); - double total1 = 0; - for(double i = 1.5; i < 5.0; i = i + 1.0 ){ - total1 += i; - sqlite3_bind_double(stmt, 1, i); - sqlite3_step(stmt); - sqlite3_reset(stmt); - } - sqlite3_finalize(stmt); - stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); - double total2 = 0; - int counter = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - ++counter; - total2 += sqlite3_column_double(stmt, 0); - } - affirm(4 == counter); - sqlite3_finalize(stmt); - affirm(total2<=total1+0.01 && total2>=total1-0.01); - sqlite3_close_v2(db); - } - - private static void testBindFetchText(){ - sqlite3 db = createNewDb(); - execSql(db, "CREATE TABLE t(a)"); - sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); - String[] list1 = { "hell🤩", "w😃rld", "!" }; - int rc; - for( String e : list1 ){ - rc = sqlite3_bind_text(stmt, 1, e); - affirm(0 == rc); - rc = sqlite3_step(stmt); - affirm(SQLITE_DONE==rc); - sqlite3_reset(stmt); - } - sqlite3_finalize(stmt); - stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); - StringBuilder sbuf = new StringBuilder(); - int n = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - String txt = sqlite3_column_text16(stmt, 0); - //outln("txt = "+txt); - sbuf.append( txt ); - ++n; - } - sqlite3_finalize(stmt); - affirm(3 == n); - affirm("w😃rldhell🤩!".equals(sbuf.toString())); - sqlite3_close_v2(db); - } - - private static void testBindFetchBlob(){ - sqlite3 db = createNewDb(); - execSql(db, "CREATE TABLE t(a)"); - sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); - byte[] list1 = { 0x32, 0x33, 0x34 }; - int rc = sqlite3_bind_blob(stmt, 1, list1); - affirm( 0==rc ); - rc = sqlite3_step(stmt); - affirm(SQLITE_DONE == rc); - sqlite3_finalize(stmt); - stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); - int n = 0; - int total = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - byte[] blob = sqlite3_column_blob(stmt, 0); - affirm(3 == blob.length); - int i = 0; - for(byte b : blob){ - affirm(b == list1[i++]); - total += b; - } - ++n; - } - sqlite3_finalize(stmt); - affirm(1 == n); - affirm(total == 0x32 + 0x33 + 0x34); - sqlite3_close_v2(db); - } - - private static void testSql(){ - sqlite3 db = createNewDb(); - sqlite3_stmt stmt = prepare(db, "SELECT 1"); - affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); - sqlite3_finalize(stmt); - stmt = prepare(db, "SELECT ?"); - sqlite3_bind_text(stmt, 1, "hell😃"); - affirm( "SELECT 'hell😃'".equals(sqlite3_expanded_sql(stmt)) ); - sqlite3_finalize(stmt); - } - - private static void testCollation(){ - final sqlite3 db = createNewDb(); - execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); - final ValueHolder xDestroyCalled = new ValueHolder<>(false); - final Collation myCollation = new Collation() { - private String myState = - "this is local state. There is much like it, but this is mine."; - @Override - // Reverse-sorts its inputs... - public int xCompare(byte[] lhs, byte[] rhs){ - int len = lhs.length > rhs.length ? rhs.length : lhs.length; - int c = 0, i = 0; - for(i = 0; i < len; ++i){ - c = lhs[i] - rhs[i]; - if(0 != c) break; - } - if(0==c){ - if(i < lhs.length) c = 1; - else if(i < rhs.length) c = -1; - } - return -c; - } - @Override - public void xDestroy() { - // Just demonstrates that xDestroy is called. - xDestroyCalled.value = true; - } - }; - final CollationNeeded collLoader = new CollationNeeded(){ - public int xCollationNeeded(sqlite3 dbArg, int eTextRep, String collationName){ - affirm(dbArg == db/* as opposed to a temporary object*/); - return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); - } - }; - int rc = sqlite3_collation_needed(db, collLoader); - affirm( 0 == rc ); - rc = sqlite3_collation_needed(db, collLoader); - affirm( 0 == rc /* Installing the same object again is a no-op */); - sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi"); - int counter = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - final String val = sqlite3_column_text16(stmt, 0); - ++counter; - //outln("REVERSI'd row#"+counter+": "+val); - switch(counter){ - case 1: affirm("c".equals(val)); break; - case 2: affirm("b".equals(val)); break; - case 3: affirm("a".equals(val)); break; - } - } - affirm(3 == counter); - sqlite3_finalize(stmt); - stmt = prepare(db, "SELECT a FROM t ORDER BY a"); - counter = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - final String val = sqlite3_column_text16(stmt, 0); - ++counter; - //outln("Non-REVERSI'd row#"+counter+": "+val); - switch(counter){ - case 3: affirm("c".equals(val)); break; - case 2: affirm("b".equals(val)); break; - case 1: affirm("a".equals(val)); break; - } - } - affirm(3 == counter); - sqlite3_finalize(stmt); - affirm(!xDestroyCalled.value); - rc = sqlite3_collation_needed(db, null); - affirm( 0 == rc ); - sqlite3_close_v2(db); - affirm(xDestroyCalled.value); - } - - private static void testToUtf8(){ - /** - Java docs seem contradictory, claiming to use "modified UTF-8" - encoding while also claiming to export using RFC 2279: - - https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html - - Let's ensure that we can convert to standard UTF-8 in Java code - (noting that the JNI native API has no way to do this). - */ - final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8); - affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); - } - - private static void testStatus(){ - final OutputPointer.Int64 cur64 = new OutputPointer.Int64(); - final OutputPointer.Int64 high64 = new OutputPointer.Int64(); - final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); - final OutputPointer.Int32 high32 = new OutputPointer.Int32(); - final sqlite3 db = createNewDb(); - execSql(db, "create table t(a); insert into t values(1),(2),(3)"); - - int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false); - affirm( 0 == rc ); - affirm( cur32.value > 0 ); - affirm( high32.value >= cur32.value ); - - rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false); - affirm( 0 == rc ); - affirm( cur64.value > 0 ); - affirm( high64.value >= cur64.value ); - - cur32.value = 0; - high32.value = 1; - rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false); - affirm( 0 == rc ); - affirm( cur32.value > 0 ); - affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ ); - - sqlite3_close_v2(db); - } - - private static void testUdf1(){ - final sqlite3 db = createNewDb(); - // These ValueHolders are just to confirm that the func did what we want... - final ValueHolder xDestroyCalled = new ValueHolder<>(false); - final ValueHolder xFuncAccum = new ValueHolder<>(0); - - // Create an SQLFunction instance using one of its 3 subclasses: - // Scalar, Aggregate, or Window: - SQLFunction func = - // Each of the 3 subclasses requires a different set of - // functions, all of which must be implemented. Anonymous - // classes are a convenient way to implement these. - new SQLFunction.Scalar(){ - public void xFunc(sqlite3_context cx, sqlite3_value[] args){ - affirm(db == sqlite3_context_db_handle(cx)); - int result = 0; - for( sqlite3_value v : args ) result += sqlite3_value_int(v); - xFuncAccum.value += result;// just for post-run testing - sqlite3_result_int(cx, result); - } - /* OPTIONALLY override xDestroy... */ - public void xDestroy(){ - xDestroyCalled.value = true; - } - }; - - // Register and use the function... - int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); - affirm(0 == rc); - affirm(0 == xFuncAccum.value); - final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)"); - int n = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - affirm( 6 == sqlite3_column_int(stmt, 0) ); - ++n; - } - sqlite3_finalize(stmt); - affirm(1 == n); - affirm(6 == xFuncAccum.value); - affirm( !xDestroyCalled.value ); - sqlite3_close_v2(db); - affirm( xDestroyCalled.value ); - } - - private static void testUdfJavaObject(){ - final sqlite3 db = createNewDb(); - final ValueHolder testResult = new ValueHolder<>(db); - final SQLFunction func = new SQLFunction.Scalar(){ - public void xFunc(sqlite3_context cx, sqlite3_value args[]){ - sqlite3_result_java_object(cx, testResult.value); - } - }; - int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); - affirm(0 == rc); - final sqlite3_stmt stmt = prepare(db, "select myfunc()"); - affirm( 0 != stmt.getNativePointer() ); - affirm( testResult.value == db ); - int n = 0; - if( SQLITE_ROW == sqlite3_step(stmt) ){ - final sqlite3_value v = sqlite3_column_value(stmt, 0); - affirm( testResult.value == sqlite3_value_java_object(v) ); - affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) ); - affirm( testResult.value == - sqlite3_value_java_casted(v, testResult.value.getClass()) ); - affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) ); - affirm( null == sqlite3_value_java_casted(v, String.class) ); - ++n; - } - sqlite3_finalize(stmt); - affirm( 1 == n ); - sqlite3_close_v2(db); - } - - private static void testUdfAggregate(){ - final sqlite3 db = createNewDb(); - final ValueHolder xFinalNull = - // To confirm that xFinal() is called with no aggregate state - // when the corresponding result set is empty. - new ValueHolder<>(false); - SQLFunction func = new SQLFunction.Aggregate(){ - @Override - public void xStep(sqlite3_context cx, sqlite3_value[] args){ - this.getAggregateState(cx, 0).value += sqlite3_value_int(args[0]); - } - @Override - public void xFinal(sqlite3_context cx){ - final Integer v = this.takeAggregateState(cx); - if(null == v){ - xFinalNull.value = true; - sqlite3_result_null(cx); - }else{ - sqlite3_result_int(cx, v); - } - } - }; - execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)"); - int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func); - affirm(0 == rc); - sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t"); - int n = 0; - if( SQLITE_ROW == sqlite3_step(stmt) ){ - final int v = sqlite3_column_int(stmt, 0); - affirm( 6 == v ); - ++n; - } - affirm(!xFinalNull.value); - sqlite3_reset(stmt); - // Ensure that the accumulator is reset... - n = 0; - if( SQLITE_ROW == sqlite3_step(stmt) ){ - final int v = sqlite3_column_int(stmt, 0); - affirm( 6 == v ); - ++n; - } - sqlite3_finalize(stmt); - affirm( 1==n ); - - stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a"); - n = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - final int c0 = sqlite3_column_int(stmt, 0); - final int c1 = sqlite3_column_int(stmt, 1); - ++n; - affirm( 6 == c0 ); - affirm( 12 == c1 ); - } - affirm( 1 == n ); - affirm(!xFinalNull.value); - sqlite3_finalize(stmt); - - execSql(db, "SELECT myfunc(1) WHERE 0"); - affirm(xFinalNull.value); - sqlite3_close_v2(db); - } - - private static void testUdfWindow(){ - final sqlite3 db = createNewDb(); - /* Example window function, table, and results taken from: - https://sqlite.org/windowfunctions.html#udfwinfunc */ - final SQLFunction func = new SQLFunction.Window(){ - - private void xStepInverse(sqlite3_context cx, int v){ - this.getAggregateState(cx,0).value += v; - } - @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ - this.xStepInverse(cx, sqlite3_value_int(args[0])); - } - @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){ - this.xStepInverse(cx, -sqlite3_value_int(args[0])); - } - - private void xFinalValue(sqlite3_context cx, Integer v){ - if(null == v) sqlite3_result_null(cx); - else sqlite3_result_int(cx, v); - } - @Override public void xFinal(sqlite3_context cx){ - xFinalValue(cx, this.takeAggregateState(cx)); - } - @Override public void xValue(sqlite3_context cx){ - xFinalValue(cx, this.getAggregateState(cx,null).value); - } - }; - int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func); - affirm( 0 == rc ); - execSql(db, new String[] { - "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", - "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" - }); - final sqlite3_stmt stmt = prepare(db, - "SELECT x, winsumint(y) OVER ("+ - "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+ - ") AS sum_y "+ - "FROM twin ORDER BY x;"); - affirm( 0 == rc ); - int n = 0; - while( SQLITE_ROW == sqlite3_step(stmt) ){ - final String s = sqlite3_column_text16(stmt, 0); - final int i = sqlite3_column_int(stmt, 1); - switch(++n){ - case 1: affirm( "a".equals(s) && 9==i ); break; - case 2: affirm( "b".equals(s) && 12==i ); break; - case 3: affirm( "c".equals(s) && 16==i ); break; - case 4: affirm( "d".equals(s) && 12==i ); break; - case 5: affirm( "e".equals(s) && 9==i ); break; - default: affirm( false /* cannot happen */ ); - } - } - sqlite3_finalize(stmt); - affirm( 5 == n ); - sqlite3_close_v2(db); - } - - private static void listBoundMethods(){ - if(false){ - final java.lang.reflect.Field[] declaredFields = - SQLite3Jni.class.getDeclaredFields(); - outln("Bound constants:\n"); - for(java.lang.reflect.Field field : declaredFields) { - if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) { - outln("\t"+field.getName()); - } - } - } - final java.lang.reflect.Method[] declaredMethods = - SQLite3Jni.class.getDeclaredMethods(); - final java.util.List funcList = new java.util.ArrayList<>(); - for(java.lang.reflect.Method m : declaredMethods){ - if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){ - final String name = m.getName(); - if(name.startsWith("sqlite3_")){ - funcList.add(name); - } - } - } - int count = 0; - java.util.Collections.sort(funcList); - for(String n : funcList){ - ++count; - outln("\t"+n+"()"); - } - outln(count+" functions named sqlite3_*."); - } - - private static void testTrace(){ - final sqlite3 db = createNewDb(); - final ValueHolder counter = new ValueHolder<>(0); - /* Ensure that characters outside of the UTF BMP survive the trip - from Java to sqlite3 and back to Java. (At no small efficiency - penalty.) */ - final String nonBmpChar = "😃"; - sqlite3_trace_v2( - db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE - | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, - new Tracer(){ - public int xCallback(int traceFlag, Object pNative, Object x){ - ++counter.value; - //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); - switch(traceFlag){ - case SQLITE_TRACE_STMT: - affirm(pNative instanceof sqlite3_stmt); - //outln("TRACE_STMT sql = "+x); - affirm(x instanceof String); - affirm( ((String)x).indexOf(nonBmpChar) > 0 ); - break; - case SQLITE_TRACE_PROFILE: - affirm(pNative instanceof sqlite3_stmt); - affirm(x instanceof Long); - //outln("TRACE_PROFILE time = "+x); - break; - case SQLITE_TRACE_ROW: - affirm(pNative instanceof sqlite3_stmt); - affirm(null == x); - //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); - break; - case SQLITE_TRACE_CLOSE: - affirm(pNative instanceof sqlite3); - affirm(null == x); - break; - default: - affirm(false /*cannot happen*/); - break; - } - return 0; - } - }); - execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ - "SELECT 'w"+nonBmpChar+"orld'"); - affirm( 6 == counter.value ); - sqlite3_close_v2(db); - affirm( 7 == counter.value ); - } - - private static void testBusy(){ - final String dbName = "_busy-handler.db"; - final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); - - int rc = sqlite3_open(dbName, outDb); - ++metrics.dbOpen; - affirm( 0 == rc ); - final sqlite3 db1 = outDb.get(); - execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); - rc = sqlite3_open(dbName, outDb); - ++metrics.dbOpen; - affirm( 0 == rc ); - affirm( outDb.get() != db1 ); - final sqlite3 db2 = outDb.get(); - rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); - affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); - - final ValueHolder xDestroyed = new ValueHolder<>(false); - final ValueHolder xBusyCalled = new ValueHolder<>(0); - BusyHandler handler = new BusyHandler(){ - @Override public int xCallback(int n){ - //outln("busy handler #"+n); - return n > 2 ? 0 : ++xBusyCalled.value; - } - @Override public void xDestroy(){ - xDestroyed.value = true; - } - }; - rc = sqlite3_busy_handler(db2, handler); - affirm(0 == rc); - - // Force a locked condition... - execSql(db1, "BEGIN EXCLUSIVE"); - affirm(!xDestroyed.value); - rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); - affirm( SQLITE_BUSY == rc); - assert( null == outStmt.get() ); - affirm( 3 == xBusyCalled.value ); - sqlite3_close_v2(db1); - affirm(!xDestroyed.value); - sqlite3_close_v2(db2); - affirm(xDestroyed.value); - try{ - final java.io.File f = new java.io.File(dbName); - f.delete(); - }catch(Exception e){ - /* ignore */ - } - } - - private static void testProgress(){ - final sqlite3 db = createNewDb(); - final ValueHolder counter = new ValueHolder<>(0); - sqlite3_progress_handler(db, 1, new ProgressHandler(){ - public int xCallback(){ - ++counter.value; - return 0; - } - }); - execSql(db, "SELECT 1; SELECT 2;"); - affirm( counter.value > 0 ); - int nOld = counter.value; - sqlite3_progress_handler(db, 0, null); - execSql(db, "SELECT 1; SELECT 2;"); - affirm( nOld == counter.value ); - sqlite3_close_v2(db); - } - - private static void testCommitHook(){ - final sqlite3 db = createNewDb(); - final ValueHolder counter = new ValueHolder<>(0); - final ValueHolder hookResult = new ValueHolder<>(0); - final CommitHook theHook = new CommitHook(){ - public int xCommitHook(){ - ++counter.value; - return hookResult.value; - } - }; - CommitHook oldHook = sqlite3_commit_hook(db, theHook); - affirm( null == oldHook ); - execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); - affirm( 2 == counter.value ); - execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;"); - affirm( 2 == counter.value /* NOT invoked if no changes are made */ ); - execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;"); - affirm( 3 == counter.value ); - oldHook = sqlite3_commit_hook(db, theHook); - affirm( theHook == oldHook ); - execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); - affirm( 4 == counter.value ); - oldHook = sqlite3_commit_hook(db, null); - affirm( theHook == oldHook ); - execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); - affirm( 4 == counter.value ); - oldHook = sqlite3_commit_hook(db, null); - affirm( null == oldHook ); - execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); - affirm( 4 == counter.value ); - - final CommitHook newHook = new CommitHook(){ - public int xCommitHook(){return 0;} - }; - oldHook = sqlite3_commit_hook(db, newHook); - affirm( null == oldHook ); - execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); - affirm( 4 == counter.value ); - oldHook = sqlite3_commit_hook(db, theHook); - affirm( newHook == oldHook ); - execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); - affirm( 5 == counter.value ); - hookResult.value = SQLITE_ERROR; - int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); - affirm( SQLITE_CONSTRAINT == rc ); - affirm( 6 == counter.value ); - sqlite3_close_v2(db); - } - - private static void testUpdateHook(){ - final sqlite3 db = createNewDb(); - final ValueHolder counter = new ValueHolder<>(0); - final ValueHolder expectedOp = new ValueHolder<>(0); - final UpdateHook theHook = new UpdateHook(){ - @SuppressWarnings("unchecked") - public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ - ++counter.value; - if( 0!=expectedOp.value ){ - affirm( expectedOp.value == opId ); - } - } - }; - UpdateHook oldHook = sqlite3_update_hook(db, theHook); - affirm( null == oldHook ); - expectedOp.value = SQLITE_INSERT; - execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); - affirm( 3 == counter.value ); - expectedOp.value = SQLITE_UPDATE; - execSql(db, "update t set a='d' where a='c';"); - affirm( 4 == counter.value ); - oldHook = sqlite3_update_hook(db, theHook); - affirm( theHook == oldHook ); - expectedOp.value = SQLITE_DELETE; - execSql(db, "DELETE FROM t where a='d'"); - affirm( 5 == counter.value ); - oldHook = sqlite3_update_hook(db, null); - affirm( theHook == oldHook ); - execSql(db, "update t set a='e' where a='b';"); - affirm( 5 == counter.value ); - oldHook = sqlite3_update_hook(db, null); - affirm( null == oldHook ); - - final UpdateHook newHook = new UpdateHook(){ - public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ - } - }; - oldHook = sqlite3_update_hook(db, newHook); - affirm( null == oldHook ); - execSql(db, "update t set a='h' where a='a'"); - affirm( 5 == counter.value ); - oldHook = sqlite3_update_hook(db, theHook); - affirm( newHook == oldHook ); - expectedOp.value = SQLITE_UPDATE; - execSql(db, "update t set a='i' where a='h'"); - affirm( 6 == counter.value ); - sqlite3_close_v2(db); - } - - private static void testRollbackHook(){ - final sqlite3 db = createNewDb(); - final ValueHolder counter = new ValueHolder<>(0); - final RollbackHook theHook = new RollbackHook(){ - public void xRollbackHook(){ - ++counter.value; - } - }; - RollbackHook oldHook = sqlite3_rollback_hook(db, theHook); - affirm( null == oldHook ); - execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); - affirm( 0 == counter.value ); - execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;"); - affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ ); - - final RollbackHook newHook = new RollbackHook(){ - public void xRollbackHook(){return;} - }; - oldHook = sqlite3_rollback_hook(db, newHook); - affirm( theHook == oldHook ); - execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); - affirm( 1 == counter.value ); - oldHook = sqlite3_rollback_hook(db, theHook); - affirm( newHook == oldHook ); - execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); - affirm( 2 == counter.value ); - int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); - affirm( 0 == rc ); - affirm( 3 == counter.value ); - sqlite3_close_v2(db); - } - - /** - If FTS5 is available, runs FTS5 tests, else returns with no side - effects. If it is available but loading of the FTS5 bits fails, - it throws. - */ - @SuppressWarnings("unchecked") - private static void testFts5() throws Exception { - if( !SQLITE_ENABLE_FTS5 ){ - outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); - return; - } - Exception err = null; - try { - Class t = Class.forName("org.sqlite.jni.TesterFts5"); - java.lang.reflect.Constructor ctor = t.getConstructor(); - ctor.setAccessible(true); - ctor.newInstance() /* will run all tests */; - }catch(ClassNotFoundException e){ - outln("FTS5 classes not loaded."); - err = e; - }catch(NoSuchMethodException e){ - outln("FTS5 tester ctor not found."); - err = e; - }catch(Exception e){ - outln("Instantiation of FTS5 tester threw."); - err = e; - } - if( null != err ){ - outln("Exception: "+err); - err.printStackTrace(); - throw err; - } - } - - private static void testAuthorizer(){ - final sqlite3 db = createNewDb(); - final ValueHolder counter = new ValueHolder<>(0); - final ValueHolder authRc = new ValueHolder<>(0); - final Authorizer auth = new Authorizer(){ - public int xAuth(int op, String s0, String s1, String s2, String s3){ - ++counter.value; - //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3); - return authRc.value; - } - }; - execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); - sqlite3_set_authorizer(db, auth); - execSql(db, "UPDATE t SET a=1"); - affirm( 1 == counter.value ); - authRc.value = SQLITE_DENY; - int rc = execSql(db, false, "UPDATE t SET a=2"); - affirm( SQLITE_AUTH==rc ); - // TODO: expand these tests considerably - sqlite3_close(db); - } - - private static void testAutoExtension(){ - final ValueHolder val = new ValueHolder<>(0); - final ValueHolder toss = new ValueHolder<>(null); - final AutoExtension ax = new AutoExtension(){ - public synchronized int xEntryPoint(sqlite3 db){ - ++val.value; - if( null!=toss.value ){ - throw new RuntimeException(toss.value); - } - return 0; - } - }; - int rc = sqlite3_auto_extension( ax ); - affirm( 0==rc ); - sqlite3_close(createNewDb()); - affirm( 1==val.value ); - sqlite3_close(createNewDb()); - affirm( 2==val.value ); - sqlite3_reset_auto_extension(); - sqlite3_close(createNewDb()); - affirm( 2==val.value ); - rc = sqlite3_auto_extension( ax ); - affirm( 0==rc ); - // Must not add a new entry - rc = sqlite3_auto_extension( ax ); - affirm( 0==rc ); - sqlite3_close( createNewDb() ); - affirm( 3==val.value ); - affirm( sqlite3_cancel_auto_extension(ax) ); - affirm( !sqlite3_cancel_auto_extension(ax) ); - sqlite3_close(createNewDb()); - affirm( 3==val.value ); - rc = sqlite3_auto_extension( ax ); - affirm( 0==rc ); - Exception err = null; - toss.value = "Throwing from AutoExtension."; - try{ - createNewDb(); - }catch(Exception e){ - err = e; - } - affirm( err!=null ); - affirm( err.getMessage().indexOf(toss.value)>0 ); - affirm( sqlite3_cancel_auto_extension(ax) ); - } - - private static void testSleep(){ - out("Sleeping briefly... "); - sqlite3_sleep(600); - outln("Woke up."); - } - - public static void main(String[] args) throws Exception { - final long timeStart = System.nanoTime(); - test1(); - if(false) testCompileOption(); - final java.util.List liArgs = - java.util.Arrays.asList(args); - testOpenDb1(); - testOpenDb2(); - testPrepare123(); - testBindFetchInt(); - testBindFetchInt64(); - testBindFetchDouble(); - testBindFetchText(); - testBindFetchBlob(); - testSql(); - testCollation(); - testToUtf8(); - testStatus(); - testUdf1(); - testUdfJavaObject(); - testUdfAggregate(); - testUdfWindow(); - testTrace(); - testBusy(); - testProgress(); - testCommitHook(); - testRollbackHook(); - testUpdateHook(); - testAuthorizer(); - testFts5(); - testAutoExtension(); - //testSleep(); - if(liArgs.indexOf("-v")>0){ - sqlite3_do_something_for_developer(); - //listBoundMethods(); - } - final long timeEnd = System.nanoTime(); - affirm( SQLite3Jni.uncacheJniEnv() ); - affirm( !SQLite3Jni.uncacheJniEnv() ); - outln("Tests done. Metrics:"); - outln("\tAssertions checked: "+affirmCount); - outln("\tDatabases opened: "+metrics.dbOpen); - - int nMethods = 0; - int nNatives = 0; - final java.lang.reflect.Method[] declaredMethods = - SQLite3Jni.class.getDeclaredMethods(); - for(java.lang.reflect.Method m : declaredMethods){ - int mod = m.getModifiers(); - if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){ - final String name = m.getName(); - if(name.startsWith("sqlite3_")){ - ++nMethods; - if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){ - ++nNatives; - } - } - } - } - outln("\tSQLite3Jni sqlite3_*() methods: "+ - nNatives+" native methods and "+ - (nMethods - nNatives)+" Java impls"); - outln("\tTotal time = " - +((timeEnd - timeStart)/1000000.0)+"ms"); - } -} diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java deleted file mode 100644 index 6439768e2..000000000 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ /dev/null @@ -1,87 +0,0 @@ -/* -** 2023-08-04 -** -** 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 a set of tests for the sqlite3 JNI bindings. -*/ -package org.sqlite.jni; -import static org.sqlite.jni.SQLite3Jni.*; -import static org.sqlite.jni.Tester1.*; - -public class TesterFts5 { - - private static void test1(){ - Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); - affirm( null != fea ); - affirm( fea.getNativePointer() != 0 ); - affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/; - - sqlite3 db = createNewDb(); - fts5_api fApi = fts5_api.getInstanceForDb(db); - affirm( fApi != null ); - affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ ); - - execSql(db, new String[] { - "CREATE VIRTUAL TABLE ft USING fts5(a, b);", - "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');", - "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');" - }); - - final String pUserData = "This is pUserData"; - ValueHolder xDestroyCalled = new ValueHolder<>(false); - ValueHolder xFuncCount = new ValueHolder<>(0); - final fts5_extension_function func = new fts5_extension_function(){ - public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, - sqlite3_context pCx, sqlite3_value argv[]){ - int nCols = ext.xColumnCount(fCx); - affirm( 2 == nCols ); - affirm( nCols == argv.length ); - affirm( ext.xUserData(fCx) == pUserData ); - if(true){ - OutputPointer.String op = new OutputPointer.String(); - for(int i = 0; i < nCols; ++i ){ - int rc = ext.xColumnText(fCx, i, op); - affirm( 0 == rc ); - final String val = op.value; - affirm( val.equals(sqlite3_value_text(argv[i])) ); - //outln("xFunction col "+i+": "+val); - } - } - ++xFuncCount.value; - } - public void xDestroy(){ - xDestroyCalled.value = true; - } - }; - - int rc = fApi.xCreateFunction("myaux", pUserData, func); - affirm( 0==rc ); - - affirm( 0==xFuncCount.value ); - execSql(db, "select myaux(ft,a,b) from ft;"); - affirm( 2==xFuncCount.value ); - affirm( !xDestroyCalled.value ); - sqlite3_close_v2(db); - affirm( xDestroyCalled.value ); - } - - public TesterFts5(){ - int oldAffirmCount = Tester1.affirmCount; - Tester1.affirmCount = 0; - final long timeStart = System.nanoTime(); - test1(); - final long timeEnd = System.nanoTime(); - outln("FTS5 Tests done. Metrics:"); - outln("\tAssertions checked: "+Tester1.affirmCount); - outln("\tTotal time = " - +((timeEnd - timeStart)/1000000.0)+"ms"); - Tester1.affirmCount = oldAffirmCount; - } -} diff --git a/ext/jni/src/org/sqlite/jni/Tracer.java b/ext/jni/src/org/sqlite/jni/Tracer.java deleted file mode 100644 index fa62edbfa..000000000 --- a/ext/jni/src/org/sqlite/jni/Tracer.java +++ /dev/null @@ -1,62 +0,0 @@ -/* -** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - Callback proxy for use with sqlite3_trace_v2(). -*/ -public interface Tracer { - /** - Achtung: this interface is subject to change because the current - approach to mapping the passed-in natives back to Java is - uncomfortably quirky. - - Called by sqlite3 for various tracing operations, as per - sqlite3_trace_v2(). Note that this interface elides the 2nd - argument to the native trace callback, as that role is better - filled by instance-local state. - - The 2nd argument to this function, if non-0, will be a native - pointer to either an sqlite3 or sqlite3_stmt object, depending on - the first argument (see below). Client code can pass it to the - sqlite3 resp. sqlite3_stmt constructor to create a wrapping - object, if necessary. This API does not do so by default because - tracing can be called frequently, creating such a wrapper for - each call is comparatively expensive, and the objects are - probably only seldom useful. - - The final argument to this function is the "X" argument - documented for sqlite3_trace() and sqlite3_trace_v2(). Its type - depends on value of the first argument: - - - SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a string - containing the prepared SQL, with one caveat: JNI only provides - us with the ability to convert that string to MUTF-8, as - opposed to standard UTF-8, and is cannot be ruled out that that - difference may be significant for certain inputs. The - alternative would be that we first convert it to UTF-16 before - passing it on, but there's no readily-available way to do that - without calling back into the db to peform the conversion - (which would lead to further tracing). - - - SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long - holding an approximate number of nanoseconds the statement took - to run. - - - SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null. - - - SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. - */ - int xCallback(int traceFlag, Object pNative, Object pX); -} diff --git a/ext/jni/src/org/sqlite/jni/annotation/NotNull.java b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java new file mode 100644 index 000000000..3b4c1c7af --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java @@ -0,0 +1,59 @@ +/* +** 2023-09-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 houses the NotNull annotaion for the sqlite3 C API. +*/ +package org.sqlite.jni.annotation; + +/** + This annotation is for flagging parameters which may not legally be + null or point to closed/finalized C-side resources. + +

        In the case of Java types which map directly to C struct types + (e.g. {@link org.sqlite.jni.sqlite3}, {@link + org.sqlite.jni.sqlite3_stmt}, and {@link + org.sqlite.jni.sqlite3_context}), a closed/finalized resource is + also considered to be null for purposes this annotation because the + C-side effect of passing such a handle is the same as if null is + passed.

        + +

        When used in the context of Java interfaces which are called + from the C APIs, this annotation communicates that the C API will + never pass a null value to the callback for that parameter.

        + +

        Passing a null, for this annotation's definition of null, for + any parameter marked with this annoation specifically invokes + undefined behavior.

        + +

        Passing 0 (i.e. C NULL) or a negative value for any long-type + parameter marked with this annoation specifically invokes undefined + behavior. Such values are treated as C pointers in the JNI + layer.

        + +

        Note that the C-style API does not throw any exceptions on its + own because it has a no-throw policy in order to retain its C-style + semantics, but it may trigger NullPointerExceptions (or similar) if + passed a null for a parameter flagged with this annotation.

        + +

        This annotation is informational only. No policy is in place to + programmatically ensure that NotNull is conformed to in client + code.

        + +

        This annotation is solely for the use by the classes in the + org.sqlite package and subpackages, but is made public so that + javadoc will link to it from the annotated functions. It is not + part of the public API and client-level code must not rely on + it.

        +*/ +@java.lang.annotation.Documented +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) +@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) +public @interface NotNull{} diff --git a/ext/jni/src/org/sqlite/jni/annotation/Nullable.java b/ext/jni/src/org/sqlite/jni/annotation/Nullable.java new file mode 100644 index 000000000..ddc8502d6 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/Nullable.java @@ -0,0 +1,32 @@ +/* +** 2023-09-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 houses the Nullable annotaion for the sqlite3 C API. +*/ +package org.sqlite.jni.annotation; + +/** + This annotation is for flagging parameters which may legally be + null, noting that they may behave differently if passed null but + are prepared to expect null as a value. When used in the context of + callback methods which are called into from the C APIs, this + annotation communicates that the C API may pass a null value to the + callback. + +

        This annotation is solely for the use by the classes in this + package but is made public so that javadoc will link to it from the + annotated functions. It is not part of the public API and + client-level code must not rely on it. +*/ +@java.lang.annotation.Documented +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) +@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) +public @interface Nullable{} diff --git a/ext/jni/src/org/sqlite/jni/annotation/package-info.java b/ext/jni/src/org/sqlite/jni/annotation/package-info.java new file mode 100644 index 000000000..20ac7a301 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/package-info.java @@ -0,0 +1,17 @@ +/* +** 2023-09-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 package houses annotations specific to the JNI bindings of the + SQLite3 C API. +*/ +package org.sqlite.jni.annotation; diff --git a/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java b/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java new file mode 100644 index 000000000..925536636 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java @@ -0,0 +1,34 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; +import org.sqlite.jni.annotation.NotNull; + +/** + An implementation of {@link CollationCallback} which provides a + no-op xDestroy() method. +*/ +public abstract class AbstractCollationCallback + implements CollationCallback, XDestroyCallback { + /** + Must compare the given byte arrays and return the result using + {@code memcmp()} semantics. + */ + public abstract int call(@NotNull byte[] lhs, @NotNull byte[] rhs); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. This implementation does nothing. + */ + public void xDestroy(){} +} diff --git a/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java new file mode 100644 index 000000000..89c4f2742 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java @@ -0,0 +1,72 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + + +/** + A SQLFunction implementation for aggregate functions. Its T is the + data type of its "accumulator" state, an instance of which is + intended to be be managed using the getAggregateState() and + takeAggregateState() methods. +*/ +public abstract class AggregateFunction implements SQLFunction { + + /** + As for the xStep() argument of the C API's + sqlite3_create_function(). If this function throws, the + exception is not propagated and a warning might be emitted to a + debugging channel. + */ + public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); + + /** + As for the xFinal() argument of the C API's sqlite3_create_function(). + If this function throws, it is translated into an sqlite3_result_error(). + */ + public abstract void xFinal(sqlite3_context cx); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. + */ + public void xDestroy() {} + + /** Per-invocation state for the UDF. */ + private final SQLFunction.PerContextState map = + new SQLFunction.PerContextState<>(); + + /** + To be called from the implementation's xStep() method, as well + as the xValue() and xInverse() methods of the {@link WindowFunction} + subclass, to fetch the current per-call UDF state. On the + first call to this method for any given sqlite3_context + argument, the context is set to the given initial value. On all other + calls, the 2nd argument is ignored. + + @see SQLFunction.PerContextState#getAggregateState + */ + protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + return map.getAggregateState(cx, initialValue); + } + + /** + To be called from the implementation's xFinal() method to fetch + the final state of the UDF and remove its mapping. + + see SQLFunction.PerContextState#takeAggregateState + */ + protected final T takeAggregateState(sqlite3_context cx){ + return map.takeAggregateState(cx); + } +} diff --git a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java new file mode 100644 index 000000000..ce7c6fca6 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java @@ -0,0 +1,28 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; +import org.sqlite.jni.annotation.*; + +/** + Callback for use with {@link CApi#sqlite3_set_authorizer}. +*/ +public interface AuthorizerCallback extends CallbackProxy { + /** + Must function as described for the C-level + sqlite3_set_authorizer() callback. + */ + int call(int opId, @Nullable String s1, @Nullable String s2, + @Nullable String s3, @Nullable String s4); + +} diff --git a/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java b/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java new file mode 100644 index 000000000..7a54132d2 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java @@ -0,0 +1,40 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + Callback for use with the {@link CApi#sqlite3_auto_extension} + family of APIs. +*/ +public interface AutoExtensionCallback extends CallbackProxy { + /** + Must function as described for a C-level + sqlite3_auto_extension() callback. + +

        This callback may throw and the exception's error message will + be set as the db's error string. + +

        Tips for implementations: + +

        - Opening a database from an auto-extension handler will lead to + an endless recursion of the auto-handler triggering itself + indirectly for each newly-opened database. + +

        - If this routine is stateful, it may be useful to make the + overridden method synchronized. + +

        - Results are undefined if the given db is closed by an auto-extension. + */ + int call(sqlite3 db); +} diff --git a/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java b/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java new file mode 100644 index 000000000..00223f0b6 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java @@ -0,0 +1,26 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + Callback for use with {@link CApi#sqlite3_busy_handler}. +*/ +public interface BusyHandlerCallback extends CallbackProxy { + /** + Must function as documented for the C-level + sqlite3_busy_handler() callback argument, minus the (void*) + argument the C-level function requires. + */ + int call(int n); +} diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java new file mode 100644 index 000000000..302cdb760 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java @@ -0,0 +1,2449 @@ +/* +** 2023-07-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 declares JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; +import java.nio.charset.StandardCharsets; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import org.sqlite.jni.annotation.*; +import java.util.Arrays; + +/** + This class contains the entire C-style sqlite3 JNI API binding, + minus a few bits and pieces declared in other files. For client-side + use, a static import is recommended: + +

        {@code
        +  import static org.sqlite.jni.capi.CApi.*;
        +  }
        + +

        The C-side part can be found in sqlite3-jni.c. + +

        This class is package-private in order to keep Java clients from + having direct access to the low-level C-style APIs, a design + decision made by Java developers based on the C-style API being + riddled with opportunities for Java developers to proverbially shoot + themselves in the foot with. Third-party copies of this code may + eliminate that guard by simply changing this class from + package-private to public. Its methods which are intended to be + exposed that way are all public. + +

        Only functions which materially differ from their C counterparts + are documented here, and only those material differences are + documented. The C documentation is otherwise applicable for these + APIs: + +

        https://sqlite.org/c3ref/intro.html + +

        A handful of Java-specific APIs have been added which are + documented here. A number of convenience overloads are provided + which are not documented but whose semantics map 1-to-1 in an + intuitive manner. e.g. {@link + #sqlite3_result_set(sqlite3_context,int)} is equivalent to {@link + #sqlite3_result_int}, and sqlite3_result_set() has many + type-specific overloads. + +

        Notes regarding Java's Modified UTF-8 vs standard UTF-8: + +

        SQLite internally uses UTF-8 encoding, whereas Java natively uses + UTF-16. Java JNI has routines for converting to and from UTF-8, + but JNI uses what its docs call modified UTF-8 (see links below) + Care must be taken when converting Java strings to or from standard + UTF-8 to ensure that the proper conversion is performed. In short, + Java's {@code String.getBytes(StandardCharsets.UTF_8)} performs the proper + conversion in Java, and there are no JNI C APIs for that conversion + (JNI's {@code NewStringUTF()} requires its input to be in MUTF-8). + +

        The known consequences and limitations this discrepancy places on + the SQLite3 JNI binding include: + +

          + +
        • C functions which take C-style strings without a length argument + require special care when taking input from Java. In particular, + Java strings converted to byte arrays for encoding purposes are not + NUL-terminated, and conversion to a Java byte array must sometimes + be careful to add one. Functions which take a length do not require + this so long as the length is provided. Search the CApi class + for "\0" for many examples. + +
        + +

        Further reading: + +

        https://stackoverflow.com/questions/57419723 +

        https://stackoverflow.com/questions/7921016 +

        https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/ +

        https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode +

        https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 + +*/ +public final class CApi { + static { + System.loadLibrary("sqlite3-jni"); + } + //! Not used + private CApi(){} + //! Called from static init code. + private static native void init(); + + /** + Returns a nul-terminated copy of s as a UTF-8-encoded byte array, + or null if s is null. + */ + private static byte[] nulTerminateUtf8(String s){ + return null==s ? null : (s+"\0").getBytes(StandardCharsets.UTF_8); + } + + /** + Each thread which uses the SQLite3 JNI APIs should call + sqlite3_jni_uncache_thread() when it is done with the library - + either right before it terminates or when it finishes using the + SQLite API. This will clean up any cached per-thread info. + +

        This process does not close any databases or finalize + any prepared statements because their ownership does not depend on + a given thread. For proper library behavior, and to + avoid C-side leaks, be sure to finalize all statements and close + all databases before calling this function. + +

        Calling this from the main application thread is not strictly + required. Additional threads must call this before ending or they + will leak cache entries in the C heap, which in turn may keep + numerous Java-side global references active. + +

        This routine returns false without side effects if the current + JNIEnv is not cached, else returns true, but this information is + primarily for testing of the JNI bindings and is not information + which client-level code can use to make any informed decisions. + */ + public static native boolean sqlite3_java_uncache_thread(); + + ////////////////////////////////////////////////////////////////////// + // Maintenance reminder: please keep the sqlite3_.... functions + // alphabetized. The SQLITE_... values. on the other hand, are + // grouped by category. + + /** + Functions exactly like the native form except that (A) the 2nd + argument is a boolean instead of an int and (B) the returned + value is not a pointer address and is only intended for use as a + per-UDF-call lookup key in a higher-level data structure. + +

        Passing a true second argument is analogous to passing some + unspecified small, non-0 positive value to the C API and passing + false is equivalent to passing 0 to the C API. + +

        Like the C API, it returns 0 if allocation fails or if + initialize is false and no prior aggregate context was allocated + for cx. If initialize is true then it returns 0 only on + allocation error. In all casses, 0 is considered the sentinel + "not a key" value. + */ + public static native long sqlite3_aggregate_context(sqlite3_context cx, boolean initialize); + + /** + Functions almost as documented for the C API, with these + exceptions: + +

        - The callback interface is shorter because of + cross-language differences. Specifically, 3rd argument to the C + auto-extension callback interface is unnecessary here. + +

        The C API docs do not specifically say so, but if the list of + auto-extensions is manipulated from an auto-extension, it is + undefined which, if any, auto-extensions will subsequently + execute for the current database. That is, doing so will result + in unpredictable, but not undefined, behavior. + +

        See the AutoExtension class docs for more information. + */ + public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback); + + static native int sqlite3_backup_finish(@NotNull long ptrToBackup); + + public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){ + return sqlite3_backup_finish(b.clearNativePointer()); + } + + static native sqlite3_backup sqlite3_backup_init( + @NotNull long ptrToDbDest, @NotNull String destTableName, + @NotNull long ptrToDbSrc, @NotNull String srcTableName + ); + + public static sqlite3_backup sqlite3_backup_init( + @NotNull sqlite3 dbDest, @NotNull String destTableName, + @NotNull sqlite3 dbSrc, @NotNull String srcTableName + ){ + return sqlite3_backup_init( dbDest.getNativePointer(), destTableName, + dbSrc.getNativePointer(), srcTableName ); + } + + static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup); + + public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){ + return sqlite3_backup_pagecount(b.getNativePointer()); + } + + static native int sqlite3_backup_remaining(@NotNull long ptrToBackup); + + public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){ + return sqlite3_backup_remaining(b.getNativePointer()); + } + + static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage); + + public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){ + return sqlite3_backup_step(b.getNativePointer(), nPage); + } + + static native int sqlite3_bind_blob( + @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n + ); + + /** + If n is negative, SQLITE_MISUSE is returned. If n>data.length + then n is silently truncated to data.length. + */ + static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n + ){ + return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n); + } + + public static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ + return (null==data) + ? sqlite3_bind_null(stmt.getNativePointer(), ndx) + : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length); + } + + static native int sqlite3_bind_double( + @NotNull long ptrToStmt, int ndx, double v + ); + + public static int sqlite3_bind_double( + @NotNull sqlite3_stmt stmt, int ndx, double v + ){ + return sqlite3_bind_double(stmt.getNativePointer(), ndx, v); + } + + static native int sqlite3_bind_int( + @NotNull long ptrToStmt, int ndx, int v + ); + + public static int sqlite3_bind_int( + @NotNull sqlite3_stmt stmt, int ndx, int v + ){ + return sqlite3_bind_int(stmt.getNativePointer(), ndx, v); + } + + static native int sqlite3_bind_int64( + @NotNull long ptrToStmt, int ndx, long v + ); + + public static int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, int ndx, long v){ + return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v ); + } + + static native int sqlite3_bind_java_object( + @NotNull long ptrToStmt, int ndx, @Nullable Object o + ); + + /** + Binds the given object at the given index. If o is null then this behaves like + sqlite3_bind_null(). + + @see #sqlite3_result_java_object + */ + public static int sqlite3_bind_java_object( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable Object o + ){ + return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o); + } + + static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx); + + public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_bind_null(stmt.getNativePointer(), ndx); + } + + static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt); + + public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){ + return sqlite3_bind_parameter_count(stmt.getNativePointer()); + } + + /** + Requires that paramName be a NUL-terminated UTF-8 string. + + This overload is private because: (A) to keep users from + inadvertently passing non-NUL-terminated byte arrays (an easy + thing to do). (B) it is cheaper to NUL-terminate the + String-to-byte-array conversion in the public-facing Java-side + overload than to do that in C, so that signature is the + public-facing one. + */ + private static native int sqlite3_bind_parameter_index( + @NotNull long ptrToStmt, @NotNull byte[] paramName + ); + + public static int sqlite3_bind_parameter_index( + @NotNull sqlite3_stmt stmt, @NotNull String paramName + ){ + final byte[] utf8 = nulTerminateUtf8(paramName); + return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8); + } + + static native String sqlite3_bind_parameter_name( + @NotNull long ptrToStmt, int index + ); + + public static String sqlite3_bind_parameter_name(@NotNull sqlite3_stmt stmt, int index){ + return sqlite3_bind_parameter_name(stmt.getNativePointer(), index); + } + + static native int sqlite3_bind_text( + @NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes + ); + + /** + Works like the C-level sqlite3_bind_text() but assumes + SQLITE_TRANSIENT for the final C API parameter. The byte array is + assumed to be in UTF-8 encoding. + +

        If data is not null and maxBytes>utf8.length then maxBytes is + silently truncated to utf8.length. If maxBytes is negative then + results are undefined if data is not null and does not contain a + NUL byte. + */ + static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8, int maxBytes + ){ + return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, maxBytes); + } + + /** + Converts data, if not null, to a UTF-8-encoded byte array and + binds it as such, returning the result of the C-level + sqlite3_bind_null() or sqlite3_bind_text(). + */ + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data + ){ + if( null==data ) return sqlite3_bind_null(stmt.getNativePointer(), ndx); + final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8); + return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length); + } + + /** + Requires that utf8 be null or in UTF-8 encoding. + */ + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8 + ){ + return ( null==utf8 ) + ? sqlite3_bind_null(stmt.getNativePointer(), ndx) + : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length); + } + + static native int sqlite3_bind_text16( + @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes + ); + + /** + Identical to the sqlite3_bind_text() overload with the same + signature but requires that its input be encoded in UTF-16 in + platform byte order. + */ + static int sqlite3_bind_text16( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes + ){ + return sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, maxBytes); + } + + /** + Converts its string argument to UTF-16 and binds it as such, returning + the result of the C-side function of the same name. The 3rd argument + may be null. + */ + public static int sqlite3_bind_text16( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data + ){ + if(null == data) return sqlite3_bind_null(stmt, ndx); + final byte[] bytes = data.getBytes(StandardCharsets.UTF_16); + return sqlite3_bind_text16(stmt.getNativePointer(), ndx, bytes, bytes.length); + } + + /** + Requires that data be null or in UTF-16 encoding in platform byte + order. Returns the result of the C-level sqlite3_bind_null() or + sqlite3_bind_text16(). + */ + public static int sqlite3_bind_text16( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ + return (null == data) + ? sqlite3_bind_null(stmt.getNativePointer(), ndx) + : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length); + } + + static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue); + + /** + Functions like the C-level sqlite3_bind_value(), or + sqlite3_bind_null() if val is null. + */ + public static int sqlite3_bind_value(@NotNull sqlite3_stmt stmt, int ndx, sqlite3_value val){ + return sqlite3_bind_value(stmt.getNativePointer(), ndx, + null==val ? 0L : val.getNativePointer()); + } + + static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n); + + public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){ + return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n); + } + + static native int sqlite3_bind_zeroblob64( + @NotNull long ptrToStmt, int ndx, long n + ); + + public static int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n){ + return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n); + } + + static native int sqlite3_blob_bytes(@NotNull long ptrToBlob); + + public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){ + return sqlite3_blob_bytes(blob.getNativePointer()); + } + + static native int sqlite3_blob_close(@Nullable long ptrToBlob); + + public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){ + return sqlite3_blob_close(blob.clearNativePointer()); + } + + static native int sqlite3_blob_open( + @NotNull long ptrToDb, @NotNull String dbName, + @NotNull String tableName, @NotNull String columnName, + long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out + ); + + public static int sqlite3_blob_open( + @NotNull sqlite3 db, @NotNull String dbName, + @NotNull String tableName, @NotNull String columnName, + long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out + ){ + return sqlite3_blob_open(db.getNativePointer(), dbName, tableName, + columnName, iRow, flags, out); + } + + /** + Convenience overload. + */ + public static sqlite3_blob sqlite3_blob_open( + @NotNull sqlite3 db, @NotNull String dbName, + @NotNull String tableName, @NotNull String columnName, + long iRow, int flags ){ + final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob(); + sqlite3_blob_open(db.getNativePointer(), dbName, tableName, columnName, + iRow, flags, out); + return out.take(); + }; + + static native int sqlite3_blob_read( + @NotNull long ptrToBlob, @NotNull byte[] target, int iOffset + ); + + public static int sqlite3_blob_read( + @NotNull sqlite3_blob b, @NotNull byte[] target, int iOffset + ){ + return sqlite3_blob_read(b.getNativePointer(), target, iOffset); + } + + static native int sqlite3_blob_reopen( + @NotNull long ptrToBlob, long newRowId + ); + + public static int sqlite3_blob_reopen(@NotNull sqlite3_blob b, long newRowId){ + return sqlite3_blob_reopen(b.getNativePointer(), newRowId); + } + + static native int sqlite3_blob_write( + @NotNull long ptrToBlob, @NotNull byte[] bytes, int iOffset + ); + + public static int sqlite3_blob_write( + @NotNull sqlite3_blob b, @NotNull byte[] bytes, int iOffset + ){ + return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset); + } + + static native int sqlite3_busy_handler( + @NotNull long ptrToDb, @Nullable BusyHandlerCallback handler + ); + + /** + As for the C-level function of the same name, with a + BusyHandlerCallback instance in place of a callback + function. Pass it a null handler to clear the busy handler. + */ + public static int sqlite3_busy_handler( + @NotNull sqlite3 db, @Nullable BusyHandlerCallback handler + ){ + return sqlite3_busy_handler(db.getNativePointer(), handler); + } + + static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms); + + public static int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms){ + return sqlite3_busy_timeout(db.getNativePointer(), ms); + } + + public static native boolean sqlite3_cancel_auto_extension( + @NotNull AutoExtensionCallback ax + ); + + static native int sqlite3_changes(@NotNull long ptrToDb); + + public static int sqlite3_changes(@NotNull sqlite3 db){ + return sqlite3_changes(db.getNativePointer()); + } + + static native long sqlite3_changes64(@NotNull long ptrToDb); + + public static long sqlite3_changes64(@NotNull sqlite3 db){ + return sqlite3_changes64(db.getNativePointer()); + } + + static native int sqlite3_clear_bindings(@NotNull long ptrToStmt); + + public static int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt){ + return sqlite3_clear_bindings(stmt.getNativePointer()); + } + + static native int sqlite3_close(@Nullable long ptrToDb); + + public static int sqlite3_close(@Nullable sqlite3 db){ + int rc = 0; + if( null!=db ){ + rc = sqlite3_close(db.getNativePointer()); + if( 0==rc ) db.clearNativePointer(); + } + return rc; + } + + static native int sqlite3_close_v2(@Nullable long ptrToDb); + + public static int sqlite3_close_v2(@Nullable sqlite3 db){ + return db==null ? 0 : sqlite3_close_v2(db.clearNativePointer()); + } + + public static native byte[] sqlite3_column_blob( + @NotNull sqlite3_stmt stmt, int ndx + ); + + static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx); + + public static int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_bytes(stmt.getNativePointer(), ndx); + } + + static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx); + + public static int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_bytes16(stmt.getNativePointer(), ndx); + } + + static native int sqlite3_column_count(@NotNull long ptrToStmt); + + public static int sqlite3_column_count(@NotNull sqlite3_stmt stmt){ + return sqlite3_column_count(stmt.getNativePointer()); + } + + static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx); + + public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_decltype(stmt.getNativePointer(), ndx); + } + + public static native double sqlite3_column_double( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static native int sqlite3_column_int( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static native long sqlite3_column_int64( + @NotNull sqlite3_stmt stmt, int ndx + ); + + static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx); + + public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_name(stmt.getNativePointer(), ndx); + } + + static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx); + + public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_database_name(stmt.getNativePointer(), ndx); + } + + static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx); + + public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_origin_name(stmt.getNativePointer(), ndx); + } + + static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx); + + public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_table_name(stmt.getNativePointer(), ndx); + } + + /** + Functions identially to the C API, and this note is just to + stress that the returned bytes are encoded as UTF-8. It returns + null if the underlying C-level sqlite3_column_text() returns NULL + or on allocation error. + + @see #sqlite3_column_text16(sqlite3_stmt,int) + */ + public static native byte[] sqlite3_column_text( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static native String sqlite3_column_text16( + @NotNull sqlite3_stmt stmt, int ndx + ); + + // The real utility of this function is questionable. + // /** + // Returns a Java value representation based on the value of + // sqlite_value_type(). For integer types it returns either Integer + // or Long, depending on whether the value will fit in an + // Integer. For floating-point values it always returns type Double. + + // If the column was bound using sqlite3_result_java_object() then + // that value, as an Object, is returned. + // */ + // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt, + // int ndx){ + // sqlite3_value v = sqlite3_column_value(stmt, ndx); + // Object rv = null; + // if(null == v) return v; + // v = sqlite3_value_dup(v)/*need a protected value*/; + // if(null == v) return v /* OOM error in C */; + // if(112/* 'p' */ == sqlite3_value_subtype(v)){ + // rv = sqlite3_value_java_object(v); + // }else{ + // switch(sqlite3_value_type(v)){ + // case SQLITE_INTEGER: { + // final long i = sqlite3_value_int64(v); + // rv = (i<=0x7fffffff && i>=-0x7fffffff-1) + // ? new Integer((int)i) : new Long(i); + // break; + // } + // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break; + // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break; + // case SQLITE_TEXT: rv = sqlite3_value_text16(v); break; + // default: break; + // } + // } + // sqlite3_value_free(v); + // return rv; + // } + + static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx); + + public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_type(stmt.getNativePointer(), ndx); + } + + public static native sqlite3_value sqlite3_column_value( + @NotNull sqlite3_stmt stmt, int ndx + ); + + static native int sqlite3_collation_needed( + @NotNull long ptrToDb, @Nullable CollationNeededCallback callback + ); + + /** + This functions like C's sqlite3_collation_needed16() because + Java's string type is inherently compatible with that interface. + */ + public static int sqlite3_collation_needed( + @NotNull sqlite3 db, @Nullable CollationNeededCallback callback + ){ + return sqlite3_collation_needed(db.getNativePointer(), callback); + } + + static native CommitHookCallback sqlite3_commit_hook( + @NotNull long ptrToDb, @Nullable CommitHookCallback hook + ); + + public static CommitHookCallback sqlite3_commit_hook( + @NotNull sqlite3 db, @Nullable CommitHookCallback hook + ){ + return sqlite3_commit_hook(db.getNativePointer(), hook); + } + + public static native String sqlite3_compileoption_get(int n); + + public static native boolean sqlite3_compileoption_used(String optName); + + /** + This implementation is private because it's too easy to pass it + non-NUL-terminated byte arrays from client code. + */ + private static native int sqlite3_complete( + @NotNull byte[] nulTerminatedUtf8Sql + ); + + /** + Unlike the C API, this returns SQLITE_MISUSE if its argument is + null (as opposed to invoking UB). + */ + public static int sqlite3_complete(@NotNull String sql){ + return sqlite3_complete( nulTerminateUtf8(sql) ); + } + + + /** +

        Works like in the C API with the exception that it only supports + the following subset of configution flags: + +

        SQLITE_CONFIG_SINGLETHREAD + SQLITE_CONFIG_MULTITHREAD + SQLITE_CONFIG_SERIALIZED + +

        Others may be added in the future. It returns SQLITE_MISUSE if + given an argument it does not handle. + +

        Note that sqlite3_config() is not threadsafe with regards to + the rest of the library. This must not be called when any other + library APIs are being called. + */ + public static native int sqlite3_config(int op); + + /** + If the native library was built with SQLITE_ENABLE_SQLLOG defined + then this acts as a proxy for C's + sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the + logger. If installation of a logger fails, any previous logger is + retained. + +

        If not built with SQLITE_ENABLE_SQLLOG defined, this returns + SQLITE_MISUSE. + +

        Note that sqlite3_config() is not threadsafe with regards to + the rest of the library. This must not be called when any other + library APIs are being called. + */ + public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger ); + + /** + The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG + option. + */ + public static native int sqlite3_config( @Nullable ConfigLogCallback logger ); + + /** + Unlike the C API, this returns null if its argument is + null (as opposed to invoking UB). + */ + public static native sqlite3 sqlite3_context_db_handle( + @NotNull sqlite3_context cx + ); + + public static native int sqlite3_create_collation( + @NotNull sqlite3 db, @NotNull String name, int eTextRep, + @NotNull CollationCallback col + ); + + /** + The Java counterpart to the C-native sqlite3_create_function(), + sqlite3_create_function_v2(), and + sqlite3_create_window_function(). Which one it behaves like + depends on which methods the final argument implements. See + SQLFunction's subclasses (ScalarFunction, AggregateFunction, + and WindowFunction) for details. + +

        Unlike the C API, this returns SQLITE_MISUSE null if its db or + functionName arguments are null (as opposed to invoking UB). + */ + public static native int sqlite3_create_function( + @NotNull sqlite3 db, @NotNull String functionName, + int nArg, int eTextRep, @NotNull SQLFunction func + ); + + static native int sqlite3_data_count(@NotNull long ptrToStmt); + + public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){ + return sqlite3_data_count(stmt.getNativePointer()); + } + + /** + Overload for sqlite3_db_config() calls which take (int,int*) + variadic arguments. Returns SQLITE_MISUSE if op is not one of the + SQLITE_DBCONFIG_... options which uses this call form. + +

        Unlike the C API, this returns SQLITE_MISUSE if its db argument + are null (as opposed to invoking UB). + */ + public static native int sqlite3_db_config( + @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out + ); + + /** + Overload for sqlite3_db_config() calls which take a (const char*) + variadic argument. As of SQLite3 v3.43 the only such option is + SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not + SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be + extended in future versions. + */ + public static native int sqlite3_db_config( + @NotNull sqlite3 db, int op, @NotNull String val + ); + + private static native String sqlite3_db_name(@NotNull long ptrToDb, int ndx); + + public static String sqlite3_db_name(@NotNull sqlite3 db, int ndx){ + return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx); + } + + + public static native String sqlite3_db_filename( + @NotNull sqlite3 db, @NotNull String dbName + ); + + public static native sqlite3 sqlite3_db_handle(@NotNull sqlite3_stmt stmt); + + public static native int sqlite3_db_readonly(@NotNull sqlite3 db, String dbName); + + public static native int sqlite3_db_release_memory(sqlite3 db); + + public static native int sqlite3_db_status( + @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + public static native int sqlite3_errcode(@NotNull sqlite3 db); + + public static native String sqlite3_errmsg(@NotNull sqlite3 db); + + static native int sqlite3_error_offset(@NotNull long ptrToDb); + + /** + Note that the returned byte offset values assume UTF-8-encoded + inputs, so won't always match character offsets in Java Strings. + */ + public static int sqlite3_error_offset(@NotNull sqlite3 db){ + return sqlite3_error_offset(db.getNativePointer()); + } + + public static native String sqlite3_errstr(int resultCode); + + public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); + + static native int sqlite3_extended_errcode(@NotNull long ptrToDb); + + public static int sqlite3_extended_errcode(@NotNull sqlite3 db){ + return sqlite3_extended_errcode(db.getNativePointer()); + } + + public static native boolean sqlite3_extended_result_codes( + @NotNull sqlite3 db, boolean onoff + ); + + static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb); + + public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){ + return sqlite3_get_autocommit(db.getNativePointer()); + } + + public static native Object sqlite3_get_auxdata( + @NotNull sqlite3_context cx, int n + ); + + static native int sqlite3_finalize(long ptrToStmt); + + public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){ + return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer()); + } + + public static native int sqlite3_initialize(); + + public static native void sqlite3_interrupt(@NotNull sqlite3 db); + + public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db); + + public static native boolean sqlite3_keyword_check(@NotNull String word); + + public static native int sqlite3_keyword_count(); + + public static native String sqlite3_keyword_name(int index); + + + public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); + + public static native String sqlite3_libversion(); + + public static native int sqlite3_libversion_number(); + + public static native int sqlite3_limit(@NotNull sqlite3 db, int id, int newVal); + + /** + Only available if built with SQLITE_ENABLE_NORMALIZE. If not, it always + returns null. + */ + public static native String sqlite3_normalized_sql(@NotNull sqlite3_stmt stmt); + + /** + Works like its C counterpart and makes the native pointer of the + underling (sqlite3*) object available via + ppDb.getNativePointer(). That pointer is necessary for looking up + the JNI-side native, but clients need not pay it any + heed. Passing the object to sqlite3_close() or sqlite3_close_v2() + will clear that pointer mapping. + +

        Recall that even if opening fails, the output pointer might be + non-null. Any error message about the failure will be in that + object and it is up to the caller to sqlite3_close() that + db handle. + */ + public static native int sqlite3_open( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb + ); + + /** + Convenience overload which returns its db handle directly. The returned + object might not have been successfully opened: use sqlite3_errcode() to + check whether it is in an error state. + +

        Ownership of the returned value is passed to the caller, who must eventually + pass it to sqlite3_close() or sqlite3_close_v2(). + */ + public static sqlite3 sqlite3_open(@Nullable String filename){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + sqlite3_open(filename, out); + return out.take(); + }; + + public static native int sqlite3_open_v2( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, + int flags, @Nullable String zVfs + ); + + /** + Has the same semantics as the sqlite3-returning sqlite3_open() + but uses sqlite3_open_v2() instead of sqlite3_open(). + */ + public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags, + @Nullable String zVfs){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + sqlite3_open_v2(filename, out, flags, zVfs); + return out.take(); + }; + + /** + The sqlite3_prepare() family of functions require slightly + different signatures than their native counterparts, but (A) they + retain functionally equivalent semantics and (B) overloading + allows us to install several convenience forms. + +

        All of them which take their SQL in the form of a byte[] require + that it be in UTF-8 encoding unless explicitly noted otherwise. + +

        The forms which take a "tail" output pointer return (via that + output object) the index into their SQL byte array at which the + end of the first SQL statement processed by the call was + found. That's fundamentally how the C APIs work but making use of + that value requires more copying of the input SQL into + consecutively smaller arrays in order to consume all of + it. (There is an example of doing that in this project's Tester1 + class.) For that vast majority of uses, that capability is not + necessary, however, and overloads are provided which gloss over + that. + +

        Results are undefined if maxBytes>sqlUtf8.length. + +

        This routine is private because its maxBytes value is not + strictly necessary in the Java interface, as sqlUtf8.length tells + us the information we need. Making this public would give clients + more ways to shoot themselves in the foot without providing any + real utility. + */ + private static native int sqlite3_prepare( + @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + /** + Works like the canonical sqlite3_prepare() but its "tail" output + argument is returned as the index offset into the given + UTF-8-encoded byte array at which SQL parsing stopped. The + semantics are otherwise identical to the C API counterpart. + +

        Several overloads provided simplified call signatures. + */ + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length, + outStmt, pTailOffset); + } + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length, + outStmt, null); + } + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare(db.getNativePointer(), utf8, utf8.length, + outStmt, null); + } + + /** + Convenience overload which returns its statement handle directly, + or null on error or when reading only whitespace or + comments. sqlite3_errcode() can be used to determine whether + there was an error or the input was empty. Ownership of the + returned object is passed to the caller, who must eventually pass + it to sqlite3_finalize(). + */ + public static sqlite3_stmt sqlite3_prepare( + @NotNull sqlite3 db, @NotNull String sql + ){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + sqlite3_prepare(db, sql, out); + return out.take(); + } + /** + @see #sqlite3_prepare + */ + private static native int sqlite3_prepare_v2( + @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + /** + Works like the canonical sqlite3_prepare_v2() but its "tail" + output paramter is returned as the index offset into the given + byte array at which SQL parsing stopped. + */ + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length, + outStmt, pTailOffset); + } + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length, + outStmt, null); + } + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v2(db.getNativePointer(), utf8, utf8.length, + outStmt, null); + } + + /** + Works identically to the sqlite3_stmt-returning sqlite3_prepare() + but uses sqlite3_prepare_v2(). + */ + public static sqlite3_stmt sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull String sql + ){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + sqlite3_prepare_v2(db, sql, out); + return out.take(); + } + + /** + @see #sqlite3_prepare + */ + private static native int sqlite3_prepare_v3( + @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes, + int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + /** + Works like the canonical sqlite3_prepare_v2() but its "tail" + output paramter is returned as the index offset into the given + byte array at which SQL parsing stopped. + */ + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length, + prepFlags, outStmt, pTailOffset); + } + + /** + Convenience overload which elides the seldom-used pTailOffset + parameter. + */ + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length, + prepFlags, outStmt, null); + } + + /** + Convenience overload which elides the seldom-used pTailOffset + parameter and converts the given string to UTF-8 before passing + it on. + */ + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull String sql, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v3(db.getNativePointer(), utf8, utf8.length, + prepFlags, outStmt, null); + } + + /** + Works identically to the sqlite3_stmt-returning sqlite3_prepare() + but uses sqlite3_prepare_v3(). + */ + public static sqlite3_stmt sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull String sql, int prepFlags + ){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + sqlite3_prepare_v3(db, sql, prepFlags, out); + return out.take(); + } + + /** + A convenience wrapper around sqlite3_prepare_v3() which accepts + an arbitrary amount of input provided as a UTF-8-encoded byte + array. It loops over the input bytes looking for + statements. Each one it finds is passed to p.call(), passing + ownership of it to that function. If p.call() returns 0, looping + continues, else the loop stops. + +

        If p.call() throws, the exception is propagated. + +

        How each statement is handled, including whether it is finalized + or not, is up to the callback object. e.g. the callback might + collect them for later use. If it does not collect them then it + must finalize them. See PrepareMultiCallback.Finalize for a + simple proxy which does that. + */ + public static int sqlite3_prepare_multi( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + int preFlags, + @NotNull PrepareMultiCallback p){ + final OutputPointer.Int32 oTail = new OutputPointer.Int32(); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + while(0==rc && pos 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail); + if( 0!=rc ) break; + pos = oTail.value; + stmt = outStmt.take(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + rc = p.call(stmt); + } + return rc; + } + + /** + Convenience overload which accepts its SQL as a String and uses + no statement-preparation flags. + */ + public static int sqlite3_prepare_multi( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull PrepareMultiCallback p){ + return sqlite3_prepare_multi(db, sqlUtf8, 0, p); + } + + /** + Convenience overload which accepts its SQL as a String. + */ + public static int sqlite3_prepare_multi( + @NotNull sqlite3 db, @NotNull String sql, int prepFlags, + @NotNull PrepareMultiCallback p){ + return sqlite3_prepare_multi( + db, sql.getBytes(StandardCharsets.UTF_8), prepFlags, p + ); + } + + /** + Convenience overload which accepts its SQL as a String and uses + no statement-preparation flags. + */ + public static int sqlite3_prepare_multi( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull PrepareMultiCallback p){ + return sqlite3_prepare_multi(db, sql, 0, p); + } + + /** + Convenience overload which accepts its SQL as a String + array. They will be concatenated together as-is, with no + separator, and passed on to one of the other overloads. + */ + public static int sqlite3_prepare_multi( + @NotNull sqlite3 db, @NotNull String[] sql, int prepFlags, + @NotNull PrepareMultiCallback p){ + return sqlite3_prepare_multi(db, String.join("",sql), prepFlags, p); + } + + /** + Convenience overload which uses no statement-preparation flags. + */ + public static int sqlite3_prepare_multi( + @NotNull sqlite3 db, @NotNull String[] sql, + @NotNull PrepareMultiCallback p){ + return sqlite3_prepare_multi(db, sql, 0, p); + } + + static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns + SQLITE_MISUSE with no side effects. + */ + public static int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db){ + return sqlite3_preupdate_blobwrite(db.getNativePointer()); + } + + static native int sqlite3_preupdate_count(@NotNull long ptrToDb); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_count(), else it returns + SQLITE_MISUSE with no side effects. + */ + public static int sqlite3_preupdate_count(@NotNull sqlite3 db){ + return sqlite3_preupdate_count(db.getNativePointer()); + } + + static native int sqlite3_preupdate_depth(@NotNull long ptrToDb); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_depth(), else it returns + SQLITE_MISUSE with no side effects. + */ + public static int sqlite3_preupdate_depth(@NotNull sqlite3 db){ + return sqlite3_preupdate_depth(db.getNativePointer()); + } + + static native PreupdateHookCallback sqlite3_preupdate_hook( + @NotNull long ptrToDb, @Nullable PreupdateHookCallback hook + ); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_hook(), else it returns null + with no side effects. + */ + public static PreupdateHookCallback sqlite3_preupdate_hook( + @NotNull sqlite3 db, @Nullable PreupdateHookCallback hook + ){ + return sqlite3_preupdate_hook(db.getNativePointer(), hook); + } + + static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col, + @NotNull OutputPointer.sqlite3_value out); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, + this acts as a proxy for C's sqlite3_preupdate_new(), else it + returns SQLITE_MISUSE with no side effects. + */ + public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col, + @NotNull OutputPointer.sqlite3_value out){ + return sqlite3_preupdate_new(db.getNativePointer(), col, out); + } + + /** + Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns + null on error. + */ + public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){ + final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value(); + sqlite3_preupdate_new(db.getNativePointer(), col, out); + return out.take(); + } + + static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col, + @NotNull OutputPointer.sqlite3_value out); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, + this acts as a proxy for C's sqlite3_preupdate_old(), else it + returns SQLITE_MISUSE with no side effects. + */ + public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col, + @NotNull OutputPointer.sqlite3_value out){ + return sqlite3_preupdate_old(db.getNativePointer(), col, out); + } + + /** + Convenience wrapper for the 3-arg sqlite3_preupdate_old() which returns + null on error. + */ + public static sqlite3_value sqlite3_preupdate_old(@NotNull sqlite3 db, int col){ + final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value(); + sqlite3_preupdate_old(db.getNativePointer(), col, out); + return out.take(); + } + + public static native void sqlite3_progress_handler( + @NotNull sqlite3 db, int n, @Nullable ProgressHandlerCallback h + ); + + public static native void sqlite3_randomness(byte[] target); + + public static native int sqlite3_release_memory(int n); + + public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + + /** + Works like the C API except that it has no side effects if auto + extensions are currently running. (The JNI-level list of + extensions cannot be manipulated while it is being traversed.) + */ + public static native void sqlite3_reset_auto_extension(); + + public static native void sqlite3_result_double( + @NotNull sqlite3_context cx, double v + ); + + /** + The main sqlite3_result_error() impl of which all others are + proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and + msg must be encoded correspondingly. Any other eTextRep value + results in the C-level sqlite3_result_error() being called with a + complaint about the invalid argument. + */ + static native void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep + ); + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull byte[] utf8 + ){ + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull String msg + ){ + final byte[] utf8 = msg.getBytes(StandardCharsets.UTF_8); + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull byte[] utf16 + ){ + sqlite3_result_error(cx, utf16, SQLITE_UTF16); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull String msg + ){ + final byte[] utf16 = msg.getBytes(StandardCharsets.UTF_16); + sqlite3_result_error(cx, utf16, SQLITE_UTF16); + } + + /** + Equivalent to passing e.toString() to {@link + #sqlite3_result_error(sqlite3_context,String)}. Note that + toString() is used instead of getMessage() because the former + prepends the exception type name to the message. + */ + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull Exception e + ){ + sqlite3_result_error(cx, e.toString()); + } + + public static native void sqlite3_result_error_toobig( + @NotNull sqlite3_context cx + ); + + public static native void sqlite3_result_error_nomem( + @NotNull sqlite3_context cx + ); + + public static native void sqlite3_result_error_code( + @NotNull sqlite3_context cx, int c + ); + + public static native void sqlite3_result_null( + @NotNull sqlite3_context cx + ); + + public static native void sqlite3_result_int( + @NotNull sqlite3_context cx, int v + ); + + public static native void sqlite3_result_int64( + @NotNull sqlite3_context cx, long v + ); + + /** + Binds the SQL result to the given object, or {@link + #sqlite3_result_null} if {@code o} is null. Use {@link + #sqlite3_value_java_object} to fetch it. + +

        This is implemented in terms of C's sqlite3_result_pointer(), + but that function is not exposed to JNI because (A) + cross-language semantic mismatch and (B) Java doesn't need that + argument for its intended purpose (type safety). + +

        Note that there is no sqlite3_column_java_object(), as the + C-level API has no sqlite3_column_pointer() to proxy. + + @see #sqlite3_value_java_object + @see #sqlite3_bind_java_object + */ + public static native void sqlite3_result_java_object( + @NotNull sqlite3_context cx, @NotNull Object o + ); + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Boolean v + ){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, boolean v + ){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Double v + ){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, double v + ){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Integer v + ){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, int v){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Long v + ){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, long v + ){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @Nullable String v + ){ + if( null==v ) sqlite3_result_null(cx); + else sqlite3_result_text(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ + if( null==blob ) sqlite3_result_null(cx); + else sqlite3_result_blob(cx, blob, blob.length); + } + + public static native void sqlite3_result_value( + @NotNull sqlite3_context cx, @NotNull sqlite3_value v + ); + + public static native void sqlite3_result_zeroblob( + @NotNull sqlite3_context cx, int n + ); + + public static native int sqlite3_result_zeroblob64( + @NotNull sqlite3_context cx, long n + ); + + /** + This overload is private because its final parameter is arguably + unnecessary in Java. + */ + private static native void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen + ); + + public static void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ + sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); + } + + /** + Binds the given text using C's sqlite3_result_blob64() unless: + +

          + +
        • @param blob is null: translates to sqlite3_result_null()
        • + +
        • @param blob is too large: translates to + sqlite3_result_error_toobig()
        • + +
        + +

        If @param maxLen is larger than blob.length, it is truncated + to that value. If it is negative, results are undefined.

        + +

        This overload is private because its final parameter is + arguably unnecessary in Java.

        + */ + private static native void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen + ); + + public static void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ + sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); + } + + /** + This overload is private because its final parameter is + arguably unnecessary in Java. + */ + private static native void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] utf8, int maxLen + ); + + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] utf8 + ){ + sqlite3_result_text(cx, utf8, null==utf8 ? 0 : utf8.length); + } + + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8); + sqlite3_result_text(cx, utf8, utf8.length); + } + } + + /** + Binds the given text using C's sqlite3_result_text64() unless: + +
          + +
        • text is null: translates to a call to sqlite3_result_null()
        • + +
        • text is too large: translates to a call to + {@link #sqlite3_result_error_toobig}
        • + +
        • The @param encoding argument has an invalid value: translates to + {@link sqlite3_result_error_code} with code SQLITE_FORMAT.
        • + +
        + + If maxLength (in bytes, not characters) is larger than + text.length, it is silently truncated to text.length. If it is + negative, results are undefined. If text is null, the subsequent + arguments are ignored. + + This overload is private because its maxLength parameter is + arguably unnecessary in Java. + */ + private static native void sqlite3_result_text64( + @NotNull sqlite3_context cx, @Nullable byte[] text, + long maxLength, int encoding + ); + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16 using the platform's byte order. + */ + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable byte[] utf16 + ){ + if(null == utf16) sqlite3_result_null(cx); + else sqlite3_result_text64(cx, utf16, utf16.length, SQLITE_UTF16); + } + + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] b = text.getBytes(StandardCharsets.UTF_16); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16); + } + } + + static native RollbackHookCallback sqlite3_rollback_hook( + @NotNull long ptrToDb, @Nullable RollbackHookCallback hook + ); + + public static RollbackHookCallback sqlite3_rollback_hook( + @NotNull sqlite3 db, @Nullable RollbackHookCallback hook + ){ + return sqlite3_rollback_hook(db.getNativePointer(), hook); + } + + public static native int sqlite3_set_authorizer( + @NotNull sqlite3 db, @Nullable AuthorizerCallback auth + ); + + public static native void sqlite3_set_auxdata( + @NotNull sqlite3_context cx, int n, @Nullable Object data + ); + + public static native void sqlite3_set_last_insert_rowid( + @NotNull sqlite3 db, long rowid + ); + + + /** + In addition to calling the C-level sqlite3_shutdown(), the JNI + binding also cleans up all stale per-thread state managed by the + library, as well as any registered auto-extensions, and frees up + various bits of memory. Calling this while database handles or + prepared statements are still active will leak resources. Trying + to use those objects after this routine is called invoked + undefined behavior. + */ + public static synchronized native int sqlite3_shutdown(); + + public static native int sqlite3_sleep(int ms); + + public static native String sqlite3_sourceid(); + + public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt); + + //! Consider removing this. We can use sqlite3_status64() instead, + // or use that one's impl with this one's name. + public static native int sqlite3_status( + int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + public static native int sqlite3_status64( + int op, @NotNull OutputPointer.Int64 pCurrent, + @NotNull OutputPointer.Int64 pHighwater, boolean reset + ); + + public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); + + public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt); + + static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op); + + public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){ + return sqlite3_stmt_explain(stmt.getNativePointer(), op); + } + + static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt); + + public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){ + return sqlite3_stmt_isexplain(stmt.getNativePointer()); + } + + public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt); + + public static native int sqlite3_stmt_status( + @NotNull sqlite3_stmt stmt, int op, boolean reset + ); + + /** + Internal impl of the public sqlite3_strglob() method. Neither + argument may be null and both must be NUL-terminated UTF-8. + + This overload is private because: (A) to keep users from + inadvertently passing non-NUL-terminated byte arrays (an easy + thing to do). (B) it is cheaper to NUL-terminate the + String-to-byte-array conversion in the Java implementation + (sqlite3_strglob(String,String)) than to do that in C, so that + signature is the public-facing one. + */ + private static native int sqlite3_strglob( + @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8 + ); + + public static int sqlite3_strglob( + @NotNull String glob, @NotNull String txt + ){ + return sqlite3_strglob(nulTerminateUtf8(glob), + nulTerminateUtf8(txt)); + } + + /** + The LIKE counterpart of the private sqlite3_strglob() method. + */ + private static native int sqlite3_strlike( + @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8, + int escChar + ); + + public static int sqlite3_strlike( + @NotNull String glob, @NotNull String txt, char escChar + ){ + return sqlite3_strlike(nulTerminateUtf8(glob), + nulTerminateUtf8(txt), + (int)escChar); + } + + static native int sqlite3_system_errno(@NotNull long ptrToDb); + + public static int sqlite3_system_errno(@NotNull sqlite3 db){ + return sqlite3_system_errno(db.getNativePointer()); + } + + public static native int sqlite3_table_column_metadata( + @NotNull sqlite3 db, @NotNull String zDbName, + @NotNull String zTableName, @NotNull String zColumnName, + @Nullable OutputPointer.String pzDataType, + @Nullable OutputPointer.String pzCollSeq, + @Nullable OutputPointer.Bool pNotNull, + @Nullable OutputPointer.Bool pPrimaryKey, + @Nullable OutputPointer.Bool pAutoinc + ); + + /** + Convenience overload which returns its results via a single + output object. If this function returns non-0 (error), the the + contents of the output object are not modified. + */ + public static int sqlite3_table_column_metadata( + @NotNull sqlite3 db, @NotNull String zDbName, + @NotNull String zTableName, @NotNull String zColumnName, + @NotNull TableColumnMetadata out){ + return sqlite3_table_column_metadata( + db, zDbName, zTableName, zColumnName, + out.pzDataType, out.pzCollSeq, out.pNotNull, + out.pPrimaryKey, out.pAutoinc); + } + + /** + Convenience overload which returns the column metadata object on + success and null on error. + */ + public static TableColumnMetadata sqlite3_table_column_metadata( + @NotNull sqlite3 db, @NotNull String zDbName, + @NotNull String zTableName, @NotNull String zColumnName){ + final TableColumnMetadata out = new TableColumnMetadata(); + return 0==sqlite3_table_column_metadata( + db, zDbName, zTableName, zColumnName, out + ) ? out : null; + } + + public static native int sqlite3_threadsafe(); + + static native int sqlite3_total_changes(@NotNull long ptrToDb); + + public static int sqlite3_total_changes(@NotNull sqlite3 db){ + return sqlite3_total_changes(db.getNativePointer()); + } + + static native long sqlite3_total_changes64(@NotNull long ptrToDb); + + public static long sqlite3_total_changes64(@NotNull sqlite3 db){ + return sqlite3_total_changes64(db.getNativePointer()); + } + + /** + Works like C's sqlite3_trace_v2() except that the 3rd argument to that + function is elided here because the roles of that functions' 3rd and 4th + arguments are encapsulated in the final argument to this function. + +

        Unlike the C API, which is documented as always returning 0, + this implementation returns non-0 if initialization of the tracer + mapping state fails (e.g. on OOM). + */ + public static native int sqlite3_trace_v2( + @NotNull sqlite3 db, int traceMask, @Nullable TraceV2Callback tracer + ); + + public static native int sqlite3_txn_state( + @NotNull sqlite3 db, @Nullable String zSchema + ); + + static native UpdateHookCallback sqlite3_update_hook( + @NotNull long ptrToDb, @Nullable UpdateHookCallback hook + ); + + public static UpdateHookCallback sqlite3_update_hook( + @NotNull sqlite3 db, @Nullable UpdateHookCallback hook + ){ + return sqlite3_update_hook(db.getNativePointer(), hook); + } + + /* + Note that: + + void * sqlite3_user_data(sqlite3_context*) + + Is not relevant in the JNI binding, as its feature is replaced by + the ability to pass an object, including any relevant state, to + sqlite3_create_function(). + */ + + static native byte[] sqlite3_value_blob(@NotNull long ptrToValue); + + public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){ + return sqlite3_value_blob(v.getNativePointer()); + } + + static native int sqlite3_value_bytes(@NotNull long ptrToValue); + + public static int sqlite3_value_bytes(@NotNull sqlite3_value v){ + return sqlite3_value_bytes(v.getNativePointer()); + } + + static native int sqlite3_value_bytes16(@NotNull long ptrToValue); + + public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){ + return sqlite3_value_bytes16(v.getNativePointer()); + } + + static native double sqlite3_value_double(@NotNull long ptrToValue); + + public static double sqlite3_value_double(@NotNull sqlite3_value v){ + return sqlite3_value_double(v.getNativePointer()); + } + + static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue); + + public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){ + return sqlite3_value_dup(v.getNativePointer()); + } + + static native int sqlite3_value_encoding(@NotNull long ptrToValue); + + public static int sqlite3_value_encoding(@NotNull sqlite3_value v){ + return sqlite3_value_encoding(v.getNativePointer()); + } + + static native void sqlite3_value_free(@Nullable long ptrToValue); + + public static void sqlite3_value_free(@Nullable sqlite3_value v){ + sqlite3_value_free(v.getNativePointer()); + } + + static native boolean sqlite3_value_frombind(@NotNull long ptrToValue); + + public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){ + return sqlite3_value_frombind(v.getNativePointer()); + } + + static native int sqlite3_value_int(@NotNull long ptrToValue); + + public static int sqlite3_value_int(@NotNull sqlite3_value v){ + return sqlite3_value_int(v.getNativePointer()); + } + + static native long sqlite3_value_int64(@NotNull long ptrToValue); + + public static long sqlite3_value_int64(@NotNull sqlite3_value v){ + return sqlite3_value_int64(v.getNativePointer()); + } + + static native Object sqlite3_value_java_object(@NotNull long ptrToValue); + + /** + If the given value was set using {@link + #sqlite3_result_java_object} then this function returns that + object, else it returns null. + +

        It is up to the caller to inspect the object to determine its + type, and cast it if necessary. + */ + public static Object sqlite3_value_java_object(@NotNull sqlite3_value v){ + return sqlite3_value_java_object(v.getNativePointer()); + } + + /** + A variant of sqlite3_value_java_object() which returns the + fetched object cast to T if the object is an instance of the + given Class, else it returns null. + */ + @SuppressWarnings("unchecked") + public static T sqlite3_value_java_casted(@NotNull sqlite3_value v, + @NotNull Class type){ + final Object o = sqlite3_value_java_object(v); + return type.isInstance(o) ? (T)o : null; + } + + static native int sqlite3_value_nochange(@NotNull long ptrToValue); + + public static int sqlite3_value_nochange(@NotNull sqlite3_value v){ + return sqlite3_value_nochange(v.getNativePointer()); + } + + static native int sqlite3_value_numeric_type(@NotNull long ptrToValue); + + public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){ + return sqlite3_value_numeric_type(v.getNativePointer()); + } + + static native int sqlite3_value_subtype(@NotNull long ptrToValue); + + public static int sqlite3_value_subtype(@NotNull sqlite3_value v){ + return sqlite3_value_subtype(v.getNativePointer()); + } + + static native byte[] sqlite3_value_text(@NotNull long ptrToValue); + + /** + Functions identially to the C API, and this note is just to + stress that the returned bytes are encoded as UTF-8. It returns + null if the underlying C-level sqlite3_value_text() returns NULL + or on allocation error. + */ + public static byte[] sqlite3_value_text(@NotNull sqlite3_value v){ + return sqlite3_value_text(v.getNativePointer()); + } + + static native String sqlite3_value_text16(@NotNull long ptrToValue); + + public static String sqlite3_value_text16(@NotNull sqlite3_value v){ + return sqlite3_value_text16(v.getNativePointer()); + } + + static native int sqlite3_value_type(@NotNull long ptrToValue); + + public static int sqlite3_value_type(@NotNull sqlite3_value v){ + return sqlite3_value_type(v.getNativePointer()); + } + + /** + This is NOT part of the public API. It exists solely as a place + for this code's developers to collect internal metrics and such. + It has no stable interface. It may go way or change behavior at + any time. + */ + public static native void sqlite3_jni_internal_details(); + + ////////////////////////////////////////////////////////////////////// + // SQLITE_... constants follow... + + // version info + public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number(); + public static final String SQLITE_VERSION = sqlite3_libversion(); + public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); + + // access + public static final int SQLITE_ACCESS_EXISTS = 0; + public static final int SQLITE_ACCESS_READWRITE = 1; + public static final int SQLITE_ACCESS_READ = 2; + + // authorizer + public static final int SQLITE_DENY = 1; + public static final int SQLITE_IGNORE = 2; + public static final int SQLITE_CREATE_INDEX = 1; + public static final int SQLITE_CREATE_TABLE = 2; + public static final int SQLITE_CREATE_TEMP_INDEX = 3; + public static final int SQLITE_CREATE_TEMP_TABLE = 4; + public static final int SQLITE_CREATE_TEMP_TRIGGER = 5; + public static final int SQLITE_CREATE_TEMP_VIEW = 6; + public static final int SQLITE_CREATE_TRIGGER = 7; + public static final int SQLITE_CREATE_VIEW = 8; + public static final int SQLITE_DELETE = 9; + public static final int SQLITE_DROP_INDEX = 10; + public static final int SQLITE_DROP_TABLE = 11; + public static final int SQLITE_DROP_TEMP_INDEX = 12; + public static final int SQLITE_DROP_TEMP_TABLE = 13; + public static final int SQLITE_DROP_TEMP_TRIGGER = 14; + public static final int SQLITE_DROP_TEMP_VIEW = 15; + public static final int SQLITE_DROP_TRIGGER = 16; + public static final int SQLITE_DROP_VIEW = 17; + public static final int SQLITE_INSERT = 18; + public static final int SQLITE_PRAGMA = 19; + public static final int SQLITE_READ = 20; + public static final int SQLITE_SELECT = 21; + public static final int SQLITE_TRANSACTION = 22; + public static final int SQLITE_UPDATE = 23; + public static final int SQLITE_ATTACH = 24; + public static final int SQLITE_DETACH = 25; + public static final int SQLITE_ALTER_TABLE = 26; + public static final int SQLITE_REINDEX = 27; + public static final int SQLITE_ANALYZE = 28; + public static final int SQLITE_CREATE_VTABLE = 29; + public static final int SQLITE_DROP_VTABLE = 30; + public static final int SQLITE_FUNCTION = 31; + public static final int SQLITE_SAVEPOINT = 32; + public static final int SQLITE_RECURSIVE = 33; + + // blob finalizers: these should, because they are treated as + // special pointer values in C, ideally have the same sizeof() as + // the platform's (void*), but we can't know that size from here. + public static final long SQLITE_STATIC = 0; + public static final long SQLITE_TRANSIENT = -1; + + // changeset + public static final int SQLITE_CHANGESETSTART_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1; + public static final int SQLITE_CHANGESETAPPLY_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4; + public static final int SQLITE_CHANGESET_DATA = 1; + public static final int SQLITE_CHANGESET_NOTFOUND = 2; + public static final int SQLITE_CHANGESET_CONFLICT = 3; + public static final int SQLITE_CHANGESET_CONSTRAINT = 4; + public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5; + public static final int SQLITE_CHANGESET_OMIT = 0; + public static final int SQLITE_CHANGESET_REPLACE = 1; + public static final int SQLITE_CHANGESET_ABORT = 2; + + // config + public static final int SQLITE_CONFIG_SINGLETHREAD = 1; + public static final int SQLITE_CONFIG_MULTITHREAD = 2; + public static final int SQLITE_CONFIG_SERIALIZED = 3; + public static final int SQLITE_CONFIG_MALLOC = 4; + public static final int SQLITE_CONFIG_GETMALLOC = 5; + public static final int SQLITE_CONFIG_SCRATCH = 6; + public static final int SQLITE_CONFIG_PAGECACHE = 7; + public static final int SQLITE_CONFIG_HEAP = 8; + public static final int SQLITE_CONFIG_MEMSTATUS = 9; + public static final int SQLITE_CONFIG_MUTEX = 10; + public static final int SQLITE_CONFIG_GETMUTEX = 11; + public static final int SQLITE_CONFIG_LOOKASIDE = 13; + public static final int SQLITE_CONFIG_PCACHE = 14; + public static final int SQLITE_CONFIG_GETPCACHE = 15; + public static final int SQLITE_CONFIG_LOG = 16; + public static final int SQLITE_CONFIG_URI = 17; + public static final int SQLITE_CONFIG_PCACHE2 = 18; + public static final int SQLITE_CONFIG_GETPCACHE2 = 19; + public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20; + public static final int SQLITE_CONFIG_SQLLOG = 21; + public static final int SQLITE_CONFIG_MMAP_SIZE = 22; + public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23; + public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24; + public static final int SQLITE_CONFIG_PMASZ = 25; + public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26; + public static final int SQLITE_CONFIG_SMALL_MALLOC = 27; + public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28; + public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29; + + // data types + public static final int SQLITE_INTEGER = 1; + public static final int SQLITE_FLOAT = 2; + public static final int SQLITE_TEXT = 3; + public static final int SQLITE_BLOB = 4; + public static final int SQLITE_NULL = 5; + + // db config + public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000; + public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001; + public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002; + public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003; + public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004; + public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005; + public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006; + public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007; + public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008; + public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009; + public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010; + public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011; + public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012; + public static final int SQLITE_DBCONFIG_DQS_DML = 1013; + public static final int SQLITE_DBCONFIG_DQS_DDL = 1014; + public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015; + public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016; + public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017; + public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018; + public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019; + public static final int SQLITE_DBCONFIG_MAX = 1019; + + // db status + public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0; + public static final int SQLITE_DBSTATUS_CACHE_USED = 1; + public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2; + public static final int SQLITE_DBSTATUS_STMT_USED = 3; + public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6; + public static final int SQLITE_DBSTATUS_CACHE_HIT = 7; + public static final int SQLITE_DBSTATUS_CACHE_MISS = 8; + public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9; + public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10; + public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11; + public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12; + public static final int SQLITE_DBSTATUS_MAX = 12; + + // encodings + public static final int SQLITE_UTF8 = 1; + public static final int SQLITE_UTF16LE = 2; + public static final int SQLITE_UTF16BE = 3; + public static final int SQLITE_UTF16 = 4; + public static final int SQLITE_UTF16_ALIGNED = 8; + + // fcntl + public static final int SQLITE_FCNTL_LOCKSTATE = 1; + public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2; + public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3; + public static final int SQLITE_FCNTL_LAST_ERRNO = 4; + public static final int SQLITE_FCNTL_SIZE_HINT = 5; + public static final int SQLITE_FCNTL_CHUNK_SIZE = 6; + public static final int SQLITE_FCNTL_FILE_POINTER = 7; + public static final int SQLITE_FCNTL_SYNC_OMITTED = 8; + public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9; + public static final int SQLITE_FCNTL_PERSIST_WAL = 10; + public static final int SQLITE_FCNTL_OVERWRITE = 11; + public static final int SQLITE_FCNTL_VFSNAME = 12; + public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13; + public static final int SQLITE_FCNTL_PRAGMA = 14; + public static final int SQLITE_FCNTL_BUSYHANDLER = 15; + public static final int SQLITE_FCNTL_TEMPFILENAME = 16; + public static final int SQLITE_FCNTL_MMAP_SIZE = 18; + public static final int SQLITE_FCNTL_TRACE = 19; + public static final int SQLITE_FCNTL_HAS_MOVED = 20; + public static final int SQLITE_FCNTL_SYNC = 21; + public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22; + public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23; + public static final int SQLITE_FCNTL_WAL_BLOCK = 24; + public static final int SQLITE_FCNTL_ZIPVFS = 25; + public static final int SQLITE_FCNTL_RBU = 26; + public static final int SQLITE_FCNTL_VFS_POINTER = 27; + public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28; + public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29; + public static final int SQLITE_FCNTL_PDB = 30; + public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31; + public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32; + public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33; + public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34; + public static final int SQLITE_FCNTL_DATA_VERSION = 35; + public static final int SQLITE_FCNTL_SIZE_LIMIT = 36; + public static final int SQLITE_FCNTL_CKPT_DONE = 37; + public static final int SQLITE_FCNTL_RESERVE_BYTES = 38; + public static final int SQLITE_FCNTL_CKPT_START = 39; + public static final int SQLITE_FCNTL_EXTERNAL_READER = 40; + public static final int SQLITE_FCNTL_CKSM_FILE = 41; + public static final int SQLITE_FCNTL_RESET_CACHE = 42; + + // flock + public static final int SQLITE_LOCK_NONE = 0; + public static final int SQLITE_LOCK_SHARED = 1; + public static final int SQLITE_LOCK_RESERVED = 2; + public static final int SQLITE_LOCK_PENDING = 3; + public static final int SQLITE_LOCK_EXCLUSIVE = 4; + + // iocap + public static final int SQLITE_IOCAP_ATOMIC = 1; + public static final int SQLITE_IOCAP_ATOMIC512 = 2; + public static final int SQLITE_IOCAP_ATOMIC1K = 4; + public static final int SQLITE_IOCAP_ATOMIC2K = 8; + public static final int SQLITE_IOCAP_ATOMIC4K = 16; + public static final int SQLITE_IOCAP_ATOMIC8K = 32; + public static final int SQLITE_IOCAP_ATOMIC16K = 64; + public static final int SQLITE_IOCAP_ATOMIC32K = 128; + public static final int SQLITE_IOCAP_ATOMIC64K = 256; + public static final int SQLITE_IOCAP_SAFE_APPEND = 512; + public static final int SQLITE_IOCAP_SEQUENTIAL = 1024; + public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048; + public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096; + public static final int SQLITE_IOCAP_IMMUTABLE = 8192; + public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384; + + // limits + public static final int SQLITE_LIMIT_LENGTH = 0; + public static final int SQLITE_LIMIT_SQL_LENGTH = 1; + public static final int SQLITE_LIMIT_COLUMN = 2; + public static final int SQLITE_LIMIT_EXPR_DEPTH = 3; + public static final int SQLITE_LIMIT_COMPOUND_SELECT = 4; + public static final int SQLITE_LIMIT_VDBE_OP = 5; + public static final int SQLITE_LIMIT_FUNCTION_ARG = 6; + public static final int SQLITE_LIMIT_ATTACHED = 7; + public static final int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8; + public static final int SQLITE_LIMIT_VARIABLE_NUMBER = 9; + public static final int SQLITE_LIMIT_TRIGGER_DEPTH = 10; + public static final int SQLITE_LIMIT_WORKER_THREADS = 11; + + // open flags + + public static final int SQLITE_OPEN_READONLY = 0x00000001 /* Ok for sqlite3_open_v2() */; + public static final int SQLITE_OPEN_READWRITE = 0x00000002 /* Ok for sqlite3_open_v2() */; + public static final int SQLITE_OPEN_CREATE = 0x00000004 /* Ok for sqlite3_open_v2() */; + //public static final int SQLITE_OPEN_DELETEONCLOSE = 0x00000008 /* VFS only */; + //public static final int SQLITE_OPEN_EXCLUSIVE = 0x00000010 /* VFS only */; + //public static final int SQLITE_OPEN_AUTOPROXY = 0x00000020 /* VFS only */; + public static final int SQLITE_OPEN_URI = 0x00000040 /* Ok for sqlite3_open_v2() */; + public static final int SQLITE_OPEN_MEMORY = 0x00000080 /* Ok for sqlite3_open_v2() */; + //public static final int SQLITE_OPEN_MAIN_DB = 0x00000100 /* VFS only */; + //public static final int SQLITE_OPEN_TEMP_DB = 0x00000200 /* VFS only */; + //public static final int SQLITE_OPEN_TRANSIENT_DB = 0x00000400 /* VFS only */; + //public static final int SQLITE_OPEN_MAIN_JOURNAL = 0x00000800 /* VFS only */; + //public static final int SQLITE_OPEN_TEMP_JOURNAL = 0x00001000 /* VFS only */; + //public static final int SQLITE_OPEN_SUBJOURNAL = 0x00002000 /* VFS only */; + //public static final int SQLITE_OPEN_SUPER_JOURNAL = 0x00004000 /* VFS only */; + public static final int SQLITE_OPEN_NOMUTEX = 0x00008000 /* Ok for sqlite3_open_v2() */; + public static final int SQLITE_OPEN_FULLMUTEX = 0x00010000 /* Ok for sqlite3_open_v2() */; + public static final int SQLITE_OPEN_SHAREDCACHE = 0x00020000 /* Ok for sqlite3_open_v2() */; + public static final int SQLITE_OPEN_PRIVATECACHE = 0x00040000 /* Ok for sqlite3_open_v2() */; + //public static final int SQLITE_OPEN_WAL = 0x00080000 /* VFS only */; + public static final int SQLITE_OPEN_NOFOLLOW = 0x01000000 /* Ok for sqlite3_open_v2() */; + public static final int SQLITE_OPEN_EXRESCODE = 0x02000000 /* Extended result codes */; + + // prepare flags + public static final int SQLITE_PREPARE_PERSISTENT = 1; + public static final int SQLITE_PREPARE_NORMALIZE = 2; + public static final int SQLITE_PREPARE_NO_VTAB = 4; + + // result codes + public static final int SQLITE_OK = 0; + public static final int SQLITE_ERROR = 1; + public static final int SQLITE_INTERNAL = 2; + public static final int SQLITE_PERM = 3; + public static final int SQLITE_ABORT = 4; + public static final int SQLITE_BUSY = 5; + public static final int SQLITE_LOCKED = 6; + public static final int SQLITE_NOMEM = 7; + public static final int SQLITE_READONLY = 8; + public static final int SQLITE_INTERRUPT = 9; + public static final int SQLITE_IOERR = 10; + public static final int SQLITE_CORRUPT = 11; + public static final int SQLITE_NOTFOUND = 12; + public static final int SQLITE_FULL = 13; + public static final int SQLITE_CANTOPEN = 14; + public static final int SQLITE_PROTOCOL = 15; + public static final int SQLITE_EMPTY = 16; + public static final int SQLITE_SCHEMA = 17; + public static final int SQLITE_TOOBIG = 18; + public static final int SQLITE_CONSTRAINT = 19; + public static final int SQLITE_MISMATCH = 20; + public static final int SQLITE_MISUSE = 21; + public static final int SQLITE_NOLFS = 22; + public static final int SQLITE_AUTH = 23; + public static final int SQLITE_FORMAT = 24; + public static final int SQLITE_RANGE = 25; + public static final int SQLITE_NOTADB = 26; + public static final int SQLITE_NOTICE = 27; + public static final int SQLITE_WARNING = 28; + public static final int SQLITE_ROW = 100; + public static final int SQLITE_DONE = 101; + public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257; + public static final int SQLITE_ERROR_RETRY = 513; + public static final int SQLITE_ERROR_SNAPSHOT = 769; + public static final int SQLITE_IOERR_READ = 266; + public static final int SQLITE_IOERR_SHORT_READ = 522; + public static final int SQLITE_IOERR_WRITE = 778; + public static final int SQLITE_IOERR_FSYNC = 1034; + public static final int SQLITE_IOERR_DIR_FSYNC = 1290; + public static final int SQLITE_IOERR_TRUNCATE = 1546; + public static final int SQLITE_IOERR_FSTAT = 1802; + public static final int SQLITE_IOERR_UNLOCK = 2058; + public static final int SQLITE_IOERR_RDLOCK = 2314; + public static final int SQLITE_IOERR_DELETE = 2570; + public static final int SQLITE_IOERR_BLOCKED = 2826; + public static final int SQLITE_IOERR_NOMEM = 3082; + public static final int SQLITE_IOERR_ACCESS = 3338; + public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594; + public static final int SQLITE_IOERR_LOCK = 3850; + public static final int SQLITE_IOERR_CLOSE = 4106; + public static final int SQLITE_IOERR_DIR_CLOSE = 4362; + public static final int SQLITE_IOERR_SHMOPEN = 4618; + public static final int SQLITE_IOERR_SHMSIZE = 4874; + public static final int SQLITE_IOERR_SHMLOCK = 5130; + public static final int SQLITE_IOERR_SHMMAP = 5386; + public static final int SQLITE_IOERR_SEEK = 5642; + public static final int SQLITE_IOERR_DELETE_NOENT = 5898; + public static final int SQLITE_IOERR_MMAP = 6154; + public static final int SQLITE_IOERR_GETTEMPPATH = 6410; + public static final int SQLITE_IOERR_CONVPATH = 6666; + public static final int SQLITE_IOERR_VNODE = 6922; + public static final int SQLITE_IOERR_AUTH = 7178; + public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434; + public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690; + public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946; + public static final int SQLITE_IOERR_DATA = 8202; + public static final int SQLITE_IOERR_CORRUPTFS = 8458; + public static final int SQLITE_LOCKED_SHAREDCACHE = 262; + public static final int SQLITE_LOCKED_VTAB = 518; + public static final int SQLITE_BUSY_RECOVERY = 261; + public static final int SQLITE_BUSY_SNAPSHOT = 517; + public static final int SQLITE_BUSY_TIMEOUT = 773; + public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270; + public static final int SQLITE_CANTOPEN_ISDIR = 526; + public static final int SQLITE_CANTOPEN_FULLPATH = 782; + public static final int SQLITE_CANTOPEN_CONVPATH = 1038; + public static final int SQLITE_CANTOPEN_SYMLINK = 1550; + public static final int SQLITE_CORRUPT_VTAB = 267; + public static final int SQLITE_CORRUPT_SEQUENCE = 523; + public static final int SQLITE_CORRUPT_INDEX = 779; + public static final int SQLITE_READONLY_RECOVERY = 264; + public static final int SQLITE_READONLY_CANTLOCK = 520; + public static final int SQLITE_READONLY_ROLLBACK = 776; + public static final int SQLITE_READONLY_DBMOVED = 1032; + public static final int SQLITE_READONLY_CANTINIT = 1288; + public static final int SQLITE_READONLY_DIRECTORY = 1544; + public static final int SQLITE_ABORT_ROLLBACK = 516; + public static final int SQLITE_CONSTRAINT_CHECK = 275; + public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531; + public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787; + public static final int SQLITE_CONSTRAINT_FUNCTION = 1043; + public static final int SQLITE_CONSTRAINT_NOTNULL = 1299; + public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555; + public static final int SQLITE_CONSTRAINT_TRIGGER = 1811; + public static final int SQLITE_CONSTRAINT_UNIQUE = 2067; + public static final int SQLITE_CONSTRAINT_VTAB = 2323; + public static final int SQLITE_CONSTRAINT_ROWID = 2579; + public static final int SQLITE_CONSTRAINT_PINNED = 2835; + public static final int SQLITE_CONSTRAINT_DATATYPE = 3091; + public static final int SQLITE_NOTICE_RECOVER_WAL = 283; + public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539; + public static final int SQLITE_WARNING_AUTOINDEX = 284; + public static final int SQLITE_AUTH_USER = 279; + public static final int SQLITE_OK_LOAD_PERMANENTLY = 256; + + // serialize + public static final int SQLITE_SERIALIZE_NOCOPY = 1; + public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1; + public static final int SQLITE_DESERIALIZE_READONLY = 4; + public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2; + + // session + public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1; + public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1; + + // sqlite3 status + public static final int SQLITE_STATUS_MEMORY_USED = 0; + public static final int SQLITE_STATUS_PAGECACHE_USED = 1; + public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2; + public static final int SQLITE_STATUS_MALLOC_SIZE = 5; + public static final int SQLITE_STATUS_PARSER_STACK = 6; + public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7; + public static final int SQLITE_STATUS_MALLOC_COUNT = 9; + + // stmt status + public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1; + public static final int SQLITE_STMTSTATUS_SORT = 2; + public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3; + public static final int SQLITE_STMTSTATUS_VM_STEP = 4; + public static final int SQLITE_STMTSTATUS_REPREPARE = 5; + public static final int SQLITE_STMTSTATUS_RUN = 6; + public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7; + public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8; + public static final int SQLITE_STMTSTATUS_MEMUSED = 99; + + // sync flags + public static final int SQLITE_SYNC_NORMAL = 2; + public static final int SQLITE_SYNC_FULL = 3; + public static final int SQLITE_SYNC_DATAONLY = 16; + + // tracing flags + public static final int SQLITE_TRACE_STMT = 1; + public static final int SQLITE_TRACE_PROFILE = 2; + public static final int SQLITE_TRACE_ROW = 4; + public static final int SQLITE_TRACE_CLOSE = 8; + + // transaction state + public static final int SQLITE_TXN_NONE = 0; + public static final int SQLITE_TXN_READ = 1; + public static final int SQLITE_TXN_WRITE = 2; + + // udf flags + public static final int SQLITE_DETERMINISTIC = 0x000000800; + public static final int SQLITE_DIRECTONLY = 0x000080000; + public static final int SQLITE_INNOCUOUS = 0x000200000; + + // virtual tables + public static final int SQLITE_INDEX_SCAN_UNIQUE = 1; + public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2; + public static final int SQLITE_INDEX_CONSTRAINT_GT = 4; + public static final int SQLITE_INDEX_CONSTRAINT_LE = 8; + public static final int SQLITE_INDEX_CONSTRAINT_LT = 16; + public static final int SQLITE_INDEX_CONSTRAINT_GE = 32; + public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64; + public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65; + public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66; + public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67; + public static final int SQLITE_INDEX_CONSTRAINT_NE = 68; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70; + public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71; + public static final int SQLITE_INDEX_CONSTRAINT_IS = 72; + public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73; + public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74; + public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150; + public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1; + public static final int SQLITE_VTAB_INNOCUOUS = 2; + public static final int SQLITE_VTAB_DIRECTONLY = 3; + public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4; + public static final int SQLITE_ROLLBACK = 1; + public static final int SQLITE_FAIL = 3; + public static final int SQLITE_REPLACE = 5; + static { + // This MUST come after the SQLITE_MAX_... values or else + // attempting to modify them silently fails. + init(); + } +} diff --git a/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java new file mode 100644 index 000000000..049570256 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java @@ -0,0 +1,44 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; +/** + This marker interface exists soley for use as a documentation and + class-grouping tool. It should be applied to interfaces or + classes which have a call() method implementing some specific + callback interface on behalf of the C library. + +

        Unless very explicitely documented otherwise, callbacks must + never throw. Any which do throw but should not might trigger debug + output regarding the error, but the exception will not be + propagated. For callback interfaces which support returning error + info to the core, the JNI binding will convert any exceptions to + C-level error information. For callback interfaces which do not + support, all exceptions will necessarily be suppressed in order to + retain the C-style no-throw semantics. + +

        Callbacks of this style follow a common naming convention: + +

        1) They use the UpperCamelCase form of the C function they're + proxying for, minus the {@code sqlite3_} prefix, plus a {@code + Callback} suffix. e.g. {@code sqlite3_busy_handler()}'s callback is + named {@code BusyHandlerCallback}. Exceptions are made where that + would potentially be ambiguous, e.g. {@link ConfigSqllogCallback} + instead of {@code ConfigCallback} because the {@code + sqlite3_config()} interface may need to support more callback types + in the future. + +

        2) They all have a {@code call()} method but its signature is + callback-specific. +*/ +public interface CallbackProxy {} diff --git a/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java b/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java new file mode 100644 index 000000000..ed8bd0947 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java @@ -0,0 +1,35 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; +import org.sqlite.jni.annotation.NotNull; + +/** + Callback for use with {@link CApi#sqlite3_create_collation}. + + @see AbstractCollationCallback +*/ +public interface CollationCallback + extends CallbackProxy, XDestroyCallback { + /** + Must compare the given byte arrays and return the result using + {@code memcmp()} semantics. + */ + int call(@NotNull byte[] lhs, @NotNull byte[] rhs); + + /** + Called by SQLite when the collation is destroyed. If a collation + requires custom cleanup, override this method. + */ + void xDestroy(); +} diff --git a/ext/jni/src/org/sqlite/jni/CollationNeeded.java b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java similarity index 64% rename from ext/jni/src/org/sqlite/jni/CollationNeeded.java rename to ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java index 85214a1d2..fe61fe506 100644 --- a/ext/jni/src/org/sqlite/jni/CollationNeeded.java +++ b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-30 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -11,18 +11,18 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** - Callback proxy for use with sqlite3_collation_needed(). + Callback for use with {@link CApi#sqlite3_collation_needed}. */ -public interface CollationNeeded { +public interface CollationNeededCallback extends CallbackProxy { /** Has the same semantics as the C-level sqlite3_create_collation() callback. - If it throws, the exception message is passed on to the db and +

        If it throws, the exception message is passed on to the db and the exception is suppressed. */ - int xCollationNeeded(sqlite3 db, int eTextRep, String collationName); + int call(sqlite3 db, int eTextRep, String collationName); } diff --git a/ext/jni/src/org/sqlite/jni/UpdateHook.java b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java similarity index 60% rename from ext/jni/src/org/sqlite/jni/UpdateHook.java rename to ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java index 171e2bdb4..24373bdf2 100644 --- a/ext/jni/src/org/sqlite/jni/UpdateHook.java +++ b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -11,15 +11,15 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** - Callback proxy for use with sqlite3_update_hook(). + Callback for use with {@link CApi#sqlite3_commit_hook}. */ -public interface UpdateHook { +public interface CommitHookCallback extends CallbackProxy { /** - Works as documented for the sqlite3_update_hook() callback. - Must not throw. + Works as documented for the C-level sqlite3_commit_hook() + callback. Must not throw. */ - void xUpdateHook(int opId, String dbName, String tableName, long rowId); + int call(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java new file mode 100644 index 000000000..6513b0730 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java @@ -0,0 +1,25 @@ +/* +** 2023-08-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. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + A callback for use with sqlite3_config(). +*/ +public interface ConfigLogCallback { + /** + Must function as described for a C-level callback for + {@link CApi#sqlite3_config(ConfigLogCallback)}, with the slight signature change. + */ + void call(int errCode, String msg); +} diff --git a/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java b/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java new file mode 100644 index 000000000..df753e651 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java @@ -0,0 +1,25 @@ +/* +** 2023-08-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. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + A callback for use with sqlite3_config(). +*/ +public interface ConfigSqllogCallback { + /** + Must function as described for a C-level callback for + {@link CApi#sqlite3_config(ConfigSqllogCallback)}, with the slight signature change. + */ + void call(sqlite3 db, String msg, int msgType ); +} diff --git a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java similarity index 68% rename from ext/jni/src/org/sqlite/jni/NativePointerHolder.java rename to ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java index afe2618a0..e82909e42 100644 --- a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java +++ b/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java @@ -11,7 +11,7 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** A helper for passing pointers between JNI C code and Java, in @@ -23,11 +23,24 @@ class which is doing the subclassing. The intent of the ContextType NativePointerHolder is not inadvertently passed to an incompatible function signature. - These objects do not _own_ the pointer they refer to. They are + These objects do not own the pointer they refer to. They are intended simply to communicate that pointer between C and Java. */ public class NativePointerHolder { //! Only set from JNI, where access permissions don't matter. - private long nativePointer = 0; + private volatile long nativePointer = 0; + /** + For use ONLY by package-level APIs which act as proxies for + close/finalize operations. Such ops must call this to zero out + the pointer so that this object is not carrying a stale + pointer. This function returns the prior value of the pointer and + sets it to 0. + */ + final long clearNativePointer() { + final long rv = nativePointer; + nativePointer= 0; + return rv; + } + public final long getNativePointer(){ return nativePointer; } } diff --git a/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java b/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java new file mode 100644 index 000000000..60b902538 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java @@ -0,0 +1,231 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + Helper classes for handling JNI output pointers. + +

        We do not use a generic OutputPointer because working with those + from the native JNI code is unduly quirky due to a lack of + autoboxing at that level. + +

        The usage is similar for all of thes types: + +

        {@code
        +   OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
        +   assert( null==out.get() );
        +   int rc = sqlite3_open(":memory:", out);
        +   if( 0!=rc ) ... error;
        +   assert( null!=out.get() );
        +   sqlite3 db = out.take();
        +   assert( null==out.get() );
        +   }
        + +

        With the minor exception that the primitive types permit direct + access to the object's value via the `value` property, whereas the + JNI-level opaque types do not permit client-level code to set that + property. + +

        Warning: do not share instances of these classes across + threads. Doing so may lead to corrupting sqlite3-internal state. +*/ +public final class OutputPointer { + + /** + Output pointer for use with routines, such as sqlite3_open(), + which return a database handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3 { + private org.sqlite.jni.capi.sqlite3 value; + /** Initializes with a null value. */ + public sqlite3(){value = null;} + /** Sets the current value to null. */ + public void clear(){value = null;} + /** Returns the current value. */ + public final org.sqlite.jni.capi.sqlite3 get(){return value;} + /** Equivalent to calling get() then clear(). */ + public final org.sqlite.jni.capi.sqlite3 take(){ + final org.sqlite.jni.capi.sqlite3 v = value; + value = null; + return v; + } + } + + /** + Output pointer for sqlite3_blob_open(). These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3_blob { + private org.sqlite.jni.capi.sqlite3_blob value; + /** Initializes with a null value. */ + public sqlite3_blob(){value = null;} + /** Sets the current value to null. */ + public void clear(){value = null;} + /** Returns the current value. */ + public final org.sqlite.jni.capi.sqlite3_blob get(){return value;} + /** Equivalent to calling get() then clear(). */ + public final org.sqlite.jni.capi.sqlite3_blob take(){ + final org.sqlite.jni.capi.sqlite3_blob v = value; + value = null; + return v; + } + } + + /** + Output pointer for use with routines, such as sqlite3_prepare(), + which return a statement handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3_stmt { + private org.sqlite.jni.capi.sqlite3_stmt value; + /** Initializes with a null value. */ + public sqlite3_stmt(){value = null;} + /** Sets the current value to null. */ + public void clear(){value = null;} + /** Returns the current value. */ + public final org.sqlite.jni.capi.sqlite3_stmt get(){return value;} + /** Equivalent to calling get() then clear(). */ + public final org.sqlite.jni.capi.sqlite3_stmt take(){ + final org.sqlite.jni.capi.sqlite3_stmt v = value; + value = null; + return v; + } + } + + /** + Output pointer for use with routines, such as sqlite3_prepupdate_new(), + which return a sqlite3_value handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3_value { + private org.sqlite.jni.capi.sqlite3_value value; + /** Initializes with a null value. */ + public sqlite3_value(){value = null;} + /** Sets the current value to null. */ + public void clear(){value = null;} + /** Returns the current value. */ + public final org.sqlite.jni.capi.sqlite3_value get(){return value;} + /** Equivalent to calling get() then clear(). */ + public final org.sqlite.jni.capi.sqlite3_value take(){ + final org.sqlite.jni.capi.sqlite3_value v = value; + value = null; + return v; + } + } + + /** + Output pointer for use with native routines which return booleans + via integer output pointers. + */ + public static final class Bool { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public boolean value; + /** Initializes with the value 0. */ + public Bool(){this(false);} + /** Initializes with the value v. */ + public Bool(boolean v){value = v;} + /** Returns the current value. */ + public final boolean get(){return value;} + /** Sets the current value to v. */ + public final void set(boolean v){value = v;} + } + + /** + Output pointer for use with native routines which return integers via + output pointers. + */ + public static final class Int32 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public int value; + /** Initializes with the value 0. */ + public Int32(){this(0);} + /** Initializes with the value v. */ + public Int32(int v){value = v;} + /** Returns the current value. */ + public final int get(){return value;} + /** Sets the current value to v. */ + public final void set(int v){value = v;} + } + + /** + Output pointer for use with native routines which return 64-bit integers + via output pointers. + */ + public static final class Int64 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public long value; + /** Initializes with the value 0. */ + public Int64(){this(0);} + /** Initializes with the value v. */ + public Int64(long v){value = v;} + /** Returns the current value. */ + public final long get(){return value;} + /** Sets the current value. */ + public final void set(long v){value = v;} + } + + /** + Output pointer for use with native routines which return strings via + output pointers. + */ + public static final class String { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public java.lang.String value; + /** Initializes with a null value. */ + public String(){this(null);} + /** Initializes with the value v. */ + public String(java.lang.String v){value = v;} + /** Returns the current value. */ + public final java.lang.String get(){return value;} + /** Sets the current value. */ + public final void set(java.lang.String v){value = v;} + } + + /** + Output pointer for use with native routines which return byte + arrays via output pointers. + */ + public static final class ByteArray { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public byte[] value; + /** Initializes with the value null. */ + public ByteArray(){this(null);} + /** Initializes with the value v. */ + public ByteArray(byte[] v){value = v;} + /** Returns the current value. */ + public final byte[] get(){return value;} + /** Sets the current value. */ + public final void set(byte[] v){value = v;} + } +} diff --git a/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java b/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java new file mode 100644 index 000000000..1c805a9b1 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java @@ -0,0 +1,78 @@ +/* +** 2023-09-13 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + Callback for use with {@link CApi#sqlite3_prepare_multi}. +*/ +public interface PrepareMultiCallback extends CallbackProxy { + + /** + Gets passed a sqlite3_stmt which it may handle in arbitrary ways, + transfering ownership of it to this function. + + sqlite3_prepare_multi() will _not_ finalize st - it is up + to the call() implementation how st is handled. + + Must return 0 on success or an SQLITE_... code on error. + + See the {@link Finalize} class for a wrapper which finalizes the + statement after calling a proxy PrepareMultiCallback. + */ + int call(sqlite3_stmt st); + + /** + A PrepareMultiCallback impl which wraps a separate impl and finalizes + any sqlite3_stmt passed to its callback. + */ + public static final class Finalize implements PrepareMultiCallback { + private PrepareMultiCallback p; + /** + p is the proxy to call() when this.call() is called. + */ + public Finalize( PrepareMultiCallback p ){ + this.p = p; + } + /** + Calls the call() method of the proxied callback and either returns its + result or propagates an exception. Either way, it passes its argument to + sqlite3_finalize() before returning. + */ + @Override public int call(sqlite3_stmt st){ + try { + return this.p.call(st); + }finally{ + CApi.sqlite3_finalize(st); + } + } + } + + /** + A PrepareMultiCallback impl which steps entirely through a result set, + ignoring all non-error results. + */ + public static final class StepAll implements PrepareMultiCallback { + public StepAll(){} + /** + Calls sqlite3_step() on st until it returns something other than + SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned, + else the result of the final step is returned. + */ + @Override public int call(sqlite3_stmt st){ + int rc = CApi.SQLITE_DONE; + while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(st)) ){} + return CApi.SQLITE_DONE==rc ? 0 : rc; + } + } +} diff --git a/ext/jni/src/org/sqlite/jni/Collation.java b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java similarity index 55% rename from ext/jni/src/org/sqlite/jni/Collation.java rename to ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java index a05b8ef9e..99d3fb035 100644 --- a/ext/jni/src/org/sqlite/jni/Collation.java +++ b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -11,18 +11,16 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** + Callback for use with {@link CApi#sqlite3_preupdate_hook}. */ -public abstract class Collation { +public interface PreupdateHookCallback extends CallbackProxy { /** - Must compare the given byte arrays using memcmp() semantics. + Must function as described for the C-level sqlite3_preupdate_hook() + callback. */ - public abstract int xCompare(byte[] lhs, byte[] rhs); - /** - Called by SQLite when the collation is destroyed. If a Collation - requires custom cleanup, override this method. - */ - public void xDestroy() {} + void call(sqlite3 db, int op, String dbName, String dbTable, + long iKey1, long iKey2 ); } diff --git a/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java similarity index 58% rename from ext/jni/src/org/sqlite/jni/ProgressHandler.java rename to ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java index c806eebca..464baa2e3 100644 --- a/ext/jni/src/org/sqlite/jni/ProgressHandler.java +++ b/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -11,17 +11,17 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** - Callback proxy for use with sqlite3_progress_handler(). + Callback for use with {@link CApi#sqlite3_progress_handler}. */ -public interface ProgressHandler { +public interface ProgressHandlerCallback extends CallbackProxy { /** - Works as documented for the sqlite3_progress_handler() callback. + Works as documented for the C-level sqlite3_progress_handler() callback. - If it throws, the exception message is passed on to the db and +

        If it throws, the exception message is passed on to the db and the exception is suppressed. */ - int xCallback(); + int call(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/ResultCode.java b/ext/jni/src/org/sqlite/jni/capi/ResultCode.java new file mode 100644 index 000000000..5a8b2e6a1 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/ResultCode.java @@ -0,0 +1,155 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + This enum contains all of the core and "extended" result codes used + by the sqlite3 library. It is provided not for use with the C-style + API (with which it won't work) but for higher-level code which may + find it useful to map SQLite result codes to human-readable names. +*/ +public enum ResultCode { + SQLITE_OK(CApi.SQLITE_OK), + SQLITE_ERROR(CApi.SQLITE_ERROR), + SQLITE_INTERNAL(CApi.SQLITE_INTERNAL), + SQLITE_PERM(CApi.SQLITE_PERM), + SQLITE_ABORT(CApi.SQLITE_ABORT), + SQLITE_BUSY(CApi.SQLITE_BUSY), + SQLITE_LOCKED(CApi.SQLITE_LOCKED), + SQLITE_NOMEM(CApi.SQLITE_NOMEM), + SQLITE_READONLY(CApi.SQLITE_READONLY), + SQLITE_INTERRUPT(CApi.SQLITE_INTERRUPT), + SQLITE_IOERR(CApi.SQLITE_IOERR), + SQLITE_CORRUPT(CApi.SQLITE_CORRUPT), + SQLITE_NOTFOUND(CApi.SQLITE_NOTFOUND), + SQLITE_FULL(CApi.SQLITE_FULL), + SQLITE_CANTOPEN(CApi.SQLITE_CANTOPEN), + SQLITE_PROTOCOL(CApi.SQLITE_PROTOCOL), + SQLITE_EMPTY(CApi.SQLITE_EMPTY), + SQLITE_SCHEMA(CApi.SQLITE_SCHEMA), + SQLITE_TOOBIG(CApi.SQLITE_TOOBIG), + SQLITE_CONSTRAINT(CApi.SQLITE_CONSTRAINT), + SQLITE_MISMATCH(CApi.SQLITE_MISMATCH), + SQLITE_MISUSE(CApi.SQLITE_MISUSE), + SQLITE_NOLFS(CApi.SQLITE_NOLFS), + SQLITE_AUTH(CApi.SQLITE_AUTH), + SQLITE_FORMAT(CApi.SQLITE_FORMAT), + SQLITE_RANGE(CApi.SQLITE_RANGE), + SQLITE_NOTADB(CApi.SQLITE_NOTADB), + SQLITE_NOTICE(CApi.SQLITE_NOTICE), + SQLITE_WARNING(CApi.SQLITE_WARNING), + SQLITE_ROW(CApi.SQLITE_ROW), + SQLITE_DONE(CApi.SQLITE_DONE), + SQLITE_ERROR_MISSING_COLLSEQ(CApi.SQLITE_ERROR_MISSING_COLLSEQ), + SQLITE_ERROR_RETRY(CApi.SQLITE_ERROR_RETRY), + SQLITE_ERROR_SNAPSHOT(CApi.SQLITE_ERROR_SNAPSHOT), + SQLITE_IOERR_READ(CApi.SQLITE_IOERR_READ), + SQLITE_IOERR_SHORT_READ(CApi.SQLITE_IOERR_SHORT_READ), + SQLITE_IOERR_WRITE(CApi.SQLITE_IOERR_WRITE), + SQLITE_IOERR_FSYNC(CApi.SQLITE_IOERR_FSYNC), + SQLITE_IOERR_DIR_FSYNC(CApi.SQLITE_IOERR_DIR_FSYNC), + SQLITE_IOERR_TRUNCATE(CApi.SQLITE_IOERR_TRUNCATE), + SQLITE_IOERR_FSTAT(CApi.SQLITE_IOERR_FSTAT), + SQLITE_IOERR_UNLOCK(CApi.SQLITE_IOERR_UNLOCK), + SQLITE_IOERR_RDLOCK(CApi.SQLITE_IOERR_RDLOCK), + SQLITE_IOERR_DELETE(CApi.SQLITE_IOERR_DELETE), + SQLITE_IOERR_BLOCKED(CApi.SQLITE_IOERR_BLOCKED), + SQLITE_IOERR_NOMEM(CApi.SQLITE_IOERR_NOMEM), + SQLITE_IOERR_ACCESS(CApi.SQLITE_IOERR_ACCESS), + SQLITE_IOERR_CHECKRESERVEDLOCK(CApi.SQLITE_IOERR_CHECKRESERVEDLOCK), + SQLITE_IOERR_LOCK(CApi.SQLITE_IOERR_LOCK), + SQLITE_IOERR_CLOSE(CApi.SQLITE_IOERR_CLOSE), + SQLITE_IOERR_DIR_CLOSE(CApi.SQLITE_IOERR_DIR_CLOSE), + SQLITE_IOERR_SHMOPEN(CApi.SQLITE_IOERR_SHMOPEN), + SQLITE_IOERR_SHMSIZE(CApi.SQLITE_IOERR_SHMSIZE), + SQLITE_IOERR_SHMLOCK(CApi.SQLITE_IOERR_SHMLOCK), + SQLITE_IOERR_SHMMAP(CApi.SQLITE_IOERR_SHMMAP), + SQLITE_IOERR_SEEK(CApi.SQLITE_IOERR_SEEK), + SQLITE_IOERR_DELETE_NOENT(CApi.SQLITE_IOERR_DELETE_NOENT), + SQLITE_IOERR_MMAP(CApi.SQLITE_IOERR_MMAP), + SQLITE_IOERR_GETTEMPPATH(CApi.SQLITE_IOERR_GETTEMPPATH), + SQLITE_IOERR_CONVPATH(CApi.SQLITE_IOERR_CONVPATH), + SQLITE_IOERR_VNODE(CApi.SQLITE_IOERR_VNODE), + SQLITE_IOERR_AUTH(CApi.SQLITE_IOERR_AUTH), + SQLITE_IOERR_BEGIN_ATOMIC(CApi.SQLITE_IOERR_BEGIN_ATOMIC), + SQLITE_IOERR_COMMIT_ATOMIC(CApi.SQLITE_IOERR_COMMIT_ATOMIC), + SQLITE_IOERR_ROLLBACK_ATOMIC(CApi.SQLITE_IOERR_ROLLBACK_ATOMIC), + SQLITE_IOERR_DATA(CApi.SQLITE_IOERR_DATA), + SQLITE_IOERR_CORRUPTFS(CApi.SQLITE_IOERR_CORRUPTFS), + SQLITE_LOCKED_SHAREDCACHE(CApi.SQLITE_LOCKED_SHAREDCACHE), + SQLITE_LOCKED_VTAB(CApi.SQLITE_LOCKED_VTAB), + SQLITE_BUSY_RECOVERY(CApi.SQLITE_BUSY_RECOVERY), + SQLITE_BUSY_SNAPSHOT(CApi.SQLITE_BUSY_SNAPSHOT), + SQLITE_BUSY_TIMEOUT(CApi.SQLITE_BUSY_TIMEOUT), + SQLITE_CANTOPEN_NOTEMPDIR(CApi.SQLITE_CANTOPEN_NOTEMPDIR), + SQLITE_CANTOPEN_ISDIR(CApi.SQLITE_CANTOPEN_ISDIR), + SQLITE_CANTOPEN_FULLPATH(CApi.SQLITE_CANTOPEN_FULLPATH), + SQLITE_CANTOPEN_CONVPATH(CApi.SQLITE_CANTOPEN_CONVPATH), + SQLITE_CANTOPEN_SYMLINK(CApi.SQLITE_CANTOPEN_SYMLINK), + SQLITE_CORRUPT_VTAB(CApi.SQLITE_CORRUPT_VTAB), + SQLITE_CORRUPT_SEQUENCE(CApi.SQLITE_CORRUPT_SEQUENCE), + SQLITE_CORRUPT_INDEX(CApi.SQLITE_CORRUPT_INDEX), + SQLITE_READONLY_RECOVERY(CApi.SQLITE_READONLY_RECOVERY), + SQLITE_READONLY_CANTLOCK(CApi.SQLITE_READONLY_CANTLOCK), + SQLITE_READONLY_ROLLBACK(CApi.SQLITE_READONLY_ROLLBACK), + SQLITE_READONLY_DBMOVED(CApi.SQLITE_READONLY_DBMOVED), + SQLITE_READONLY_CANTINIT(CApi.SQLITE_READONLY_CANTINIT), + SQLITE_READONLY_DIRECTORY(CApi.SQLITE_READONLY_DIRECTORY), + SQLITE_ABORT_ROLLBACK(CApi.SQLITE_ABORT_ROLLBACK), + SQLITE_CONSTRAINT_CHECK(CApi.SQLITE_CONSTRAINT_CHECK), + SQLITE_CONSTRAINT_COMMITHOOK(CApi.SQLITE_CONSTRAINT_COMMITHOOK), + SQLITE_CONSTRAINT_FOREIGNKEY(CApi.SQLITE_CONSTRAINT_FOREIGNKEY), + SQLITE_CONSTRAINT_FUNCTION(CApi.SQLITE_CONSTRAINT_FUNCTION), + SQLITE_CONSTRAINT_NOTNULL(CApi.SQLITE_CONSTRAINT_NOTNULL), + SQLITE_CONSTRAINT_PRIMARYKEY(CApi.SQLITE_CONSTRAINT_PRIMARYKEY), + SQLITE_CONSTRAINT_TRIGGER(CApi.SQLITE_CONSTRAINT_TRIGGER), + SQLITE_CONSTRAINT_UNIQUE(CApi.SQLITE_CONSTRAINT_UNIQUE), + SQLITE_CONSTRAINT_VTAB(CApi.SQLITE_CONSTRAINT_VTAB), + SQLITE_CONSTRAINT_ROWID(CApi.SQLITE_CONSTRAINT_ROWID), + SQLITE_CONSTRAINT_PINNED(CApi.SQLITE_CONSTRAINT_PINNED), + SQLITE_CONSTRAINT_DATATYPE(CApi.SQLITE_CONSTRAINT_DATATYPE), + SQLITE_NOTICE_RECOVER_WAL(CApi.SQLITE_NOTICE_RECOVER_WAL), + SQLITE_NOTICE_RECOVER_ROLLBACK(CApi.SQLITE_NOTICE_RECOVER_ROLLBACK), + SQLITE_WARNING_AUTOINDEX(CApi.SQLITE_WARNING_AUTOINDEX), + SQLITE_AUTH_USER(CApi.SQLITE_AUTH_USER), + SQLITE_OK_LOAD_PERMANENTLY(CApi.SQLITE_OK_LOAD_PERMANENTLY); + + public final int value; + + ResultCode(int rc){ + value = rc; + ResultCodeMap.set(rc, this); + } + + /** + Returns the entry from this enum for the given result code, or + null if no match is found. + */ + public static ResultCode getEntryForInt(int rc){ + return ResultCodeMap.get(rc); + } + + /** + Internal level of indirection required because we cannot initialize + static enum members in an enum before the enum constructor is + invoked. + */ + private static final class ResultCodeMap { + private static final java.util.Map i2e + = new java.util.HashMap<>(); + private static void set(int rc, ResultCode e){ i2e.put(rc, e); } + private static ResultCode get(int rc){ return i2e.get(rc); } + } + +} diff --git a/ext/jni/src/org/sqlite/jni/RollbackHook.java b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java similarity index 62% rename from ext/jni/src/org/sqlite/jni/RollbackHook.java rename to ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java index 4ce3cb93e..5ce17e718 100644 --- a/ext/jni/src/org/sqlite/jni/RollbackHook.java +++ b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -11,15 +11,15 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** - Callback proxy for use with sqlite3_rollback_hook(). + Callback for use with {@link CApi#sqlite3_rollback_hook}. */ -public interface RollbackHook { +public interface RollbackHookCallback extends CallbackProxy { /** - Works as documented for the sqlite3_rollback_hook() callback. - Must not throw. + Works as documented for the C-level sqlite3_rollback_hook() + callback. */ - void xRollbackHook(); + void call(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java new file mode 100644 index 000000000..4806e2fc0 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java @@ -0,0 +1,103 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + SQLFunction is used in conjunction with the + sqlite3_create_function() JNI-bound API to give that native code + access to the callback functions needed in order to implement SQL + functions in Java. + +

        + + This class is not used by itself, but is a marker base class. The + three UDF types are modelled by the inner classes Scalar, + Aggregate, and Window. Most simply, clients may subclass + those, or create anonymous classes from them, to implement + UDFs. Clients are free to create their own classes for use with + UDFs, so long as they conform to the public interfaces defined by + those three classes. The JNI layer only actively relies on the + SQLFunction base class and the method names and signatures used by + the UDF callback interfaces. +*/ +public interface SQLFunction { + + /** + PerContextState assists aggregate and window functions in + managing their accumulator state across calls to the UDF's + callbacks. + +

        T must be of a type which can be legally stored as a value in + java.util.HashMap. + +

        If a given aggregate or window function is called multiple times + in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., + then the clients need some way of knowing which call is which so + that they can map their state between their various UDF callbacks + and reset it via xFinal(). This class takes care of such + mappings. + +

        This class works by mapping + sqlite3_context.getAggregateContext() to a single piece of + state, of a client-defined type (the T part of this class), which + persists across a "matching set" of the UDF's callbacks. + +

        This class is a helper providing commonly-needed functionality + - it is not required for use with aggregate or window functions. + Client UDFs are free to perform such mappings using custom + approaches. The provided {@link AggregateFunction} and {@link + WindowFunction} classes use this. + */ + public static final class PerContextState { + private final java.util.Map> map + = new java.util.HashMap<>(); + + /** + Should be called from a UDF's xStep(), xValue(), and xInverse() + methods, passing it that method's first argument and an initial + value for the persistent state. If there is currently no + mapping for the given context within the map, one is created + using the given initial value, else the existing one is used + and the 2nd argument is ignored. It returns a ValueHolder + which can be used to modify that state directly without + requiring that the client update the underlying map's entry. + +

        The caller is obligated to eventually call + takeAggregateState() to clear the mapping. + */ + public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + final Long key = cx.getAggregateContext(true); + ValueHolder rc = null==key ? null : map.get(key); + if( null==rc ){ + map.put(key, rc = new ValueHolder<>(initialValue)); + } + return rc; + } + + /** + Should be called from a UDF's xFinal() method and passed that + method's first argument. This function removes the value + associated with cx.getAggregateContext() from the map and + returns it, returning null if no other UDF method has been + called to set up such a mapping. The latter condition will be + the case if a UDF is used in a statement which has no result + rows. + */ + public T takeAggregateState(sqlite3_context cx){ + final ValueHolder h = map.remove(cx.getAggregateContext(false)); + return null==h ? null : h.value; + } + } + +} diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/capi/SQLTester.java similarity index 93% rename from ext/jni/src/org/sqlite/jni/tester/SQLTester.java rename to ext/jni/src/org/sqlite/jni/capi/SQLTester.java index ffdb867d9..81d6106be 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/capi/SQLTester.java @@ -12,16 +12,13 @@ ** This file contains the main application entry pointer for the ** SQLTester framework. */ -package org.sqlite.jni.tester; +package org.sqlite.jni.capi; import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.nio.charset.StandardCharsets; import java.util.regex.*; -import org.sqlite.jni.*; -import static org.sqlite.jni.SQLite3Jni.*; -import org.sqlite.jni.sqlite3; - +import static org.sqlite.jni.capi.CApi.*; /** Modes for how to escape (or not) column values and names from @@ -150,14 +147,17 @@ int getVerbosity(){ } /** - This class provides an application which aims to implement the +

        This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying - test-script-interpreter.md. - - This is a work in progress. + {@code test-script-interpreter.md}. +

        This class is an internal testing tool, not part of the public + interface but is (A) in the same package as the library because + access permissions require it to be so and (B) the JDK8 javadoc + offers no way to filter individual classes out of the doc + generation process (it can only exclude packages, but see (A)). - An instance of this application provides a core set of services +

        An instance of this application provides a core set of services which TestScript instances use for processing testing logic. TestScripts, in turn, delegate the concrete test work to Command objects, which the TestScript parses on their behalf. @@ -181,7 +181,7 @@ public class SQLTester { private int nTestFile = 0; //! Number of scripts which were aborted. private int nAbortedScript = 0; - //! Per-script test counter. + //! Incremented by test case handlers private int nTest = 0; //! True to enable column name output from execSql() private boolean emitColNames; @@ -250,14 +250,14 @@ private static String nextStartEmoji(){ } public void runTests() throws Exception { - final long tStart = System.nanoTime(); + final long tStart = System.currentTimeMillis(); for(String f : listInFiles){ reset(); ++nTestFile; final TestScript ts = new TestScript(f); outln(nextStartEmoji(), " starting [",f,"]"); boolean threw = false; - final long timeStart = System.nanoTime(); + final long timeStart = System.currentTimeMillis(); try{ ts.run(this); }catch(SQLTesterException e){ @@ -267,14 +267,13 @@ public void runTests() throws Exception { if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); else if( e.isFatal() ) throw e; }finally{ - final long timeEnd = System.nanoTime(); + final long timeEnd = System.currentTimeMillis(); outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ", - ((timeEnd-timeStart)/1000000.0),"ms."); - //ts.getFilename()); + (timeEnd-timeStart),"ms."); } } - final long tEnd = System.nanoTime(); - outln("Total run-time: ",((tEnd-tStart)/1000000.0),"ms"); + final long tEnd = System.currentTimeMillis(); + outln("Total run-time: ",(tEnd-tStart),"ms"); Util.unlink(initialDbName); } @@ -336,7 +335,9 @@ SQLTester affirmDbId(int n) throws IndexOutOfBoundsException { } sqlite3 setCurrentDb(int n) throws Exception{ - return affirmDbId(n).aDb[n]; + affirmDbId(n); + iCurrentDb = n; + return this.aDb[n]; } sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; } @@ -399,7 +400,7 @@ void reset(){ nullView = "nil"; emitColNames = false; iCurrentDb = 0; - dbInitSql.append("SELECT 1;"); + //dbInitSql.append("SELECT 1;"); } void setNullValue(String v){nullView = v;} @@ -456,7 +457,7 @@ String escapeSqlValue(String v){ } private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){ - sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)).append(' '); + sb.append(org.sqlite.jni.capi.ResultCode.getEntryForInt(rc)).append(' '); final String msg = escapeSqlValue(sqlite3_errmsg(db)); if( '{' == msg.charAt(0) ){ sb.append(msg); @@ -474,12 +475,12 @@ If db is null, getCurrentDb() is assumed. If throwOnError is true then the db's result code. appendMode specifies how/whether to append results to the result - buffer. lineMode specifies whether to output all results in a + buffer. rowMode specifies whether to output all results in a single line or one line per row. If appendMode is - ResultBufferMode.NONE then lineMode is ignored and may be null. + ResultBufferMode.NONE then rowMode is ignored and may be null. */ public int execSql(sqlite3 db, boolean throwOnError, - ResultBufferMode appendMode, ResultRowMode lineMode, + ResultBufferMode appendMode, ResultRowMode rowMode, String sql) throws SQLTesterException { if( null==db && null==aDb[0] ){ // Delay opening of the initial db to enable tests to change its @@ -561,7 +562,7 @@ public int execSql(sqlite3 db, boolean throwOnError, throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode); } } - if( ResultRowMode.NEWLINE == lineMode ){ + if( ResultRowMode.NEWLINE == rowMode ){ spacing = 0; sb.append('\n'); } @@ -580,6 +581,10 @@ else if( rc!=0 ){ } } }finally{ + sqlite3_reset(stmt + /* In order to trigger an exception in the + INSERT...RETURNING locking scenario: + https://sqlite.org/forum/forumpost/36f7a2e7494897df */); sqlite3_finalize(stmt); } if( 0!=rc && throwOnError ){ @@ -609,9 +614,9 @@ public static void main(String[] argv) throws Exception{ } t.addTestScript(a); } - final AutoExtension ax = new AutoExtension() { + final AutoExtensionCallback ax = new AutoExtensionCallback() { private final SQLTester tester = t; - public int xEntryPoint(sqlite3 db){ + @Override public int call(sqlite3 db){ final String init = tester.getDbInitSql(); if( !init.isEmpty() ){ tester.execSql(db, true, ResultBufferMode.NONE, null, init); @@ -629,7 +634,7 @@ public int xEntryPoint(sqlite3 db){ t.outln("Aborted ",t.nAbortedScript," script(s)."); } if( dumpInternals ){ - sqlite3_do_something_for_developer(); + sqlite3_jni_internal_details(); } } } @@ -663,7 +668,7 @@ not be called until main() is triggered so that it does not static { System.loadLibrary("sqlite3-jni") /* Interestingly, when SQLTester is the main app, we have to - load that lib from here. The same load from SQLite3Jni does + load that lib from here. The same load from CApi does not happen early enough. Without this, installCustomExtensions() is an unresolved symbol. */; } @@ -924,8 +929,8 @@ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ final sqlite3 db = (1==argv.length) ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); final String sql = t.takeInputBuffer(); - int rc = t.execSql(db, false, ResultBufferMode.NONE, - ResultRowMode.ONELINE, sql); + final int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); if( 0!=rc && t.isVerbose() ){ String msg = sqlite3_errmsg(db); ts.verbose1(argv[0]," non-fatal command error #",rc,": ", @@ -948,8 +953,7 @@ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ if( !body.endsWith("\n--end") ){ ts.toss(argv[0], " must be terminated with --end."); }else{ - int n = body.length(); - body = body.substring(0, n-6); + body = body.substring(0, body.length()-6); } final String[] globs = body.split("\\s*\\n\\s*"); if( globs.length < 1 ){ @@ -1124,8 +1128,9 @@ public void setVerbosity(int level){ } public String getOutputPrefix(){ - String rc = "["+(moduleName==null ? filename : moduleName)+"]"; + String rc = "["+(moduleName==null ? "" : moduleName)+"]"; if( null!=testCaseName ) rc += "["+testCaseName+"]"; + if( null!=filename ) rc += "["+filename+"]"; return rc + " line "+ cur.lineNo; } @@ -1238,14 +1243,15 @@ String peekLine(){ final int oldPB = cur.putbackPos; final int oldPBL = cur.putbackLineNo; final int oldLine = cur.lineNo; - final String rc = getLine(); - cur.peekedPos = cur.pos; - cur.peekedLineNo = cur.lineNo; - cur.pos = oldPos; - cur.lineNo = oldLine; - cur.putbackPos = oldPB; - cur.putbackLineNo = oldPBL; - return rc; + try{ return getLine(); } + finally{ + cur.peekedPos = cur.pos; + cur.peekedLineNo = cur.lineNo; + cur.pos = oldPos; + cur.lineNo = oldLine; + cur.putbackPos = oldPB; + cur.putbackLineNo = oldPBL; + } } /** @@ -1268,6 +1274,7 @@ void putbackLine(){ } private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{ + if( true ) return false; int nOk = 0; for(String rp : props){ verbose1("REQUIRED_PROPERTIES: ",rp); @@ -1288,6 +1295,12 @@ private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLT t.appendDbInitSql("pragma temp_store=0;"); ++nOk; break; + case "AUTOVACUUM": + t.appendDbInitSql("pragma auto_vacuum=full;"); + ++nOk; + case "INCRVACUUM": + t.appendDbInitSql("pragma auto_vacuum=incremental;"); + ++nOk; default: break; } @@ -1326,9 +1339,9 @@ private void checkForDirective( m = patternRequiredProperties.matcher(line); if( m.find() ){ final String rp = m.group(1); - //if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){ - throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); - //} + if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); + } } m = patternMixedModuleName.matcher(line); if( m.find() ){ @@ -1372,11 +1385,10 @@ String fetchCommandBody(SQLTester tester){ String line; while( (null != (line = peekLine())) ){ checkForDirective(tester, line); - if( !isCommandLine(line, true) ){ + if( isCommandLine(line, true) ) break; + else { sb.append(line).append("\n"); consumePeeked(); - }else{ - break; } } line = sb.toString(); diff --git a/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java b/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java new file mode 100644 index 000000000..95541bdcb --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java @@ -0,0 +1,33 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + + +/** + A SQLFunction implementation for scalar functions. +*/ +public abstract class ScalarFunction implements SQLFunction { + /** + As for the xFunc() argument of the C API's + sqlite3_create_function(). If this function throws, it is + translated into an sqlite3_result_error(). + */ + public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. This default implementation does nothing. + */ + public void xDestroy() {} +} diff --git a/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java b/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java new file mode 100644 index 000000000..d8b6226ac --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java @@ -0,0 +1,35 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + A wrapper object for use with sqlite3_table_column_metadata(). + They are populated only via that interface. +*/ +public final class TableColumnMetadata { + OutputPointer.Bool pNotNull = new OutputPointer.Bool(); + OutputPointer.Bool pPrimaryKey = new OutputPointer.Bool(); + OutputPointer.Bool pAutoinc = new OutputPointer.Bool(); + OutputPointer.String pzCollSeq = new OutputPointer.String(); + OutputPointer.String pzDataType = new OutputPointer.String(); + + public TableColumnMetadata(){ + } + + public String getDataType(){ return pzDataType.value; } + public String getCollation(){ return pzCollSeq.value; } + public boolean isNotNull(){ return pNotNull.value; } + public boolean isPrimaryKey(){ return pPrimaryKey.value; } + public boolean isAutoincrement(){ return pAutoinc.value; } +} diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java new file mode 100644 index 000000000..6fb28e65b --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java @@ -0,0 +1,1976 @@ +/* +** 2023-07-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 contains a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni.capi; +import static org.sqlite.jni.capi.CApi.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + An annotation for Tester1 tests which we do not want to run in + reflection-driven test mode because either they are not suitable + for multi-threaded threaded mode or we have to control their execution + order. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface ManualTest{} +/** + Annotation for Tester1 tests which mark those which must be skipped + in multi-threaded mode. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface SingleThreadOnly{} + +public class Tester1 implements Runnable { + //! True when running in multi-threaded mode. + private static boolean mtMode = false; + //! True to sleep briefly between tests. + private static boolean takeNaps = false; + //! True to shuffle the order of the tests. + private static boolean shuffle = false; + //! True to dump the list of to-run tests to stdout. + private static boolean listRunTests = false; + //! True to squelch all out() and outln() output. + private static boolean quietMode = false; + //! Total number of runTests() calls. + private static int nTestRuns = 0; + //! List of test*() methods to run. + private static List testMethods = null; + //! List of exceptions collected by run() + private static List listErrors = new ArrayList<>(); + private static final class Metrics { + //! Number of times createNewDb() (or equivalent) is invoked. + volatile int dbOpen = 0; + } + + private Integer tId; + + Tester1(Integer id){ + tId = id; + } + + static final Metrics metrics = new Metrics(); + + public static synchronized void outln(){ + if( !quietMode ){ + System.out.println(""); + } + } + + public static synchronized void outPrefix(){ + if( !quietMode ){ + System.out.print(Thread.currentThread().getName()+": "); + } + } + + public static synchronized void outln(Object val){ + if( !quietMode ){ + outPrefix(); + System.out.println(val); + } + } + + public static synchronized void out(Object val){ + if( !quietMode ){ + System.out.print(val); + } + } + + @SuppressWarnings("unchecked") + public static synchronized void out(Object... vals){ + if( !quietMode ){ + outPrefix(); + for(Object v : vals) out(v); + } + } + + @SuppressWarnings("unchecked") + public static synchronized void outln(Object... vals){ + if( !quietMode ){ + out(vals); out("\n"); + } + } + + static volatile int affirmCount = 0; + public static synchronized int affirm(Boolean v, String comment){ + ++affirmCount; + if( false ) assert( v /* prefer assert over exception if it's enabled because + the JNI layer sometimes has to suppress exceptions, + so they might be squelched on their way back to the + top. */); + if( !v ) throw new RuntimeException(comment); + return affirmCount; + } + + public static void affirm(Boolean v){ + affirm(v, "Affirmation failed."); + } + + @SingleThreadOnly /* because it's thread-agnostic */ + private void test1(){ + affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); + } + + public static sqlite3 createNewDb(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); + ++metrics.dbOpen; + sqlite3 db = out.take(); + if( 0!=rc ){ + final String msg = + null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db); + sqlite3_close(db); + throw new RuntimeException("Opening db failed: "+msg); + } + affirm( null == out.get() ); + affirm( 0 != db.getNativePointer() ); + rc = sqlite3_busy_timeout(db, 2000); + affirm( 0 == rc ); + return db; + } + + public static void execSql(sqlite3 db, String[] sql){ + execSql(db, String.join("", sql)); + } + + public static int execSql(sqlite3 db, boolean throwOnError, String sql){ + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + if(throwOnError) affirm(0 == rc); + else if( 0!=rc ) break; + pos = oTail.value; + stmt = outStmt.take(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + affirm(0 != stmt.getNativePointer()); + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + } + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ + break; + } + } + sqlite3_finalize(stmt); + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + if( 0!=rc && throwOnError){ + throw new RuntimeException("db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + } + return rc; + } + + public static void execSql(sqlite3 db, String sql){ + execSql(db, true, sql); + } + + public static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){ + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + int rc = sqlite3_prepare_v2(db, sql, outStmt); + if( throwOnError ){ + affirm( 0 == rc ); + } + final sqlite3_stmt rv = outStmt.take(); + affirm( null == outStmt.get() ); + if( throwOnError ){ + affirm( 0 != rv.getNativePointer() ); + } + return rv; + } + + public static sqlite3_stmt prepare(sqlite3 db, String sql){ + return prepare(db, true, sql); + } + + private void showCompileOption(){ + int i = 0; + String optName; + outln("compile options:"); + for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){ + outln("\t"+optName+"\t (used="+ + sqlite3_compileoption_used(optName)+")"); + } + } + + private void testCompileOption(){ + int i = 0; + String optName; + for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){ + } + affirm( i > 10 ); + affirm( null==sqlite3_compileoption_get(-1) ); + } + + private void testOpenDb1(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); + ++metrics.dbOpen; + sqlite3 db = out.get(); + affirm(0 == rc); + affirm(db.getNativePointer()!=0); + sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null) + /* This function has different mangled names in jdk8 vs jdk19, + and this call is here to ensure that the build fails + if it cannot find both names. */; + + affirm( 0==sqlite3_db_readonly(db,"main") ); + affirm( 0==sqlite3_db_readonly(db,null) ); + affirm( 0>sqlite3_db_readonly(db,"nope") ); + affirm( 0>sqlite3_db_readonly(null,null) ); + affirm( 0==sqlite3_last_insert_rowid(null) ); + + // These interrupt checks are only to make sure that the JNI binding + // has the proper exported symbol names. They don't actually test + // anything useful. + affirm( !sqlite3_is_interrupted(db) ); + sqlite3_interrupt(db); + affirm( sqlite3_is_interrupted(db) ); + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private void testOpenDb2(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open_v2(":memory:", out, + SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE, null); + ++metrics.dbOpen; + affirm(0 == rc); + sqlite3 db = out.get(); + affirm(0 != db.getNativePointer()); + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private void testPrepare123(){ + sqlite3 db = createNewDb(); + int rc; + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt); + affirm(0 == rc); + sqlite3_stmt stmt = outStmt.take(); + affirm(0 != stmt.getNativePointer()); + affirm( !sqlite3_stmt_readonly(stmt) ); + affirm( db == sqlite3_db_handle(stmt) ); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + affirm( null == sqlite3_db_handle(stmt) ); + affirm(0 == stmt.getNativePointer()); + + { /* Demonstrate how to use the "zTail" option of + sqlite3_prepare() family of functions. */ + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = + "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)" + .getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); + } + //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos); + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + affirm(0 == rc); + stmt = outStmt.get(); + pos = oTail.value; + /*outln("SQL tail pos = "+pos+". Chunk = "+ + (new String(Arrays.copyOfRange(sqlChunk,0,pos), + StandardCharsets.UTF_8)));*/ + switch(n){ + case 1: affirm(19 == pos); break; + case 2: affirm(36 == pos); break; + default: affirm( false /* can't happen */ ); + + } + ++n; + affirm(0 != stmt.getNativePointer()); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + } + } + + + rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", + SQLITE_PREPARE_NORMALIZE, outStmt); + affirm(0 == rc); + stmt = outStmt.get(); + affirm(0 != stmt.getNativePointer()); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer() ); + + affirm( 0==sqlite3_errcode(db) ); + stmt = sqlite3_prepare(db, "intentional error"); + affirm( null==stmt ); + affirm( 0!=sqlite3_errcode(db) ); + affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") ); + sqlite3_finalize(stmt); + stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only"); + affirm( null==stmt ); + affirm( 0==sqlite3_errcode(db) ); + sqlite3_close_v2(db); + } + + private void testBindFetchInt(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);"); + affirm(1 == sqlite3_bind_parameter_count(stmt)); + final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a"); + affirm(1 == paramNdx); + affirm( ":a".equals(sqlite3_bind_parameter_name(stmt, paramNdx))); + int total1 = 0; + long rowid = -1; + int changes = sqlite3_changes(db); + int changesT = sqlite3_total_changes(db); + long changes64 = sqlite3_changes64(db); + long changesT64 = sqlite3_total_changes64(db); + int rc; + for(int i = 99; i < 102; ++i ){ + total1 += i; + rc = sqlite3_bind_int(stmt, paramNdx, i); + affirm(0 == rc); + rc = sqlite3_step(stmt); + sqlite3_reset(stmt); + affirm(SQLITE_DONE == rc); + long x = sqlite3_last_insert_rowid(db); + affirm(x > rowid); + rowid = x; + } + sqlite3_finalize(stmt); + affirm(300 == total1); + affirm(sqlite3_changes(db) > changes); + affirm(sqlite3_total_changes(db) > changesT); + affirm(sqlite3_changes64(db) > changes64); + affirm(sqlite3_total_changes64(db) > changesT64); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + affirm( sqlite3_stmt_readonly(stmt) ); + affirm( !sqlite3_stmt_busy(stmt) ); + int total2 = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + affirm( sqlite3_stmt_busy(stmt) ); + total2 += sqlite3_column_int(stmt, 0); + sqlite3_value sv = sqlite3_column_value(stmt, 0); + affirm( null != sv ); + affirm( 0 != sv.getNativePointer() ); + affirm( SQLITE_INTEGER == sqlite3_value_type(sv) ); + } + affirm( !sqlite3_stmt_busy(stmt) ); + sqlite3_finalize(stmt); + affirm(total1 == total2); + + // sqlite3_value_frombind() checks... + stmt = prepare(db, "SELECT 1, ?"); + sqlite3_bind_int(stmt, 1, 2); + rc = sqlite3_step(stmt); + affirm( SQLITE_ROW==rc ); + affirm( !sqlite3_value_frombind(sqlite3_column_value(stmt, 0)) ); + affirm( sqlite3_value_frombind(sqlite3_column_value(stmt, 1)) ); + sqlite3_finalize(stmt); + + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private void testBindFetchInt64(){ + try (sqlite3 db = createNewDb()){ + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + long total1 = 0; + for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){ + total1 += i; + sqlite3_bind_int64(stmt, 1, i); + sqlite3_step(stmt); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + long total2 = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + total2 += sqlite3_column_int64(stmt, 0); + } + sqlite3_finalize(stmt); + affirm(total1 == total2); + //sqlite3_close_v2(db); + } + } + + private void testBindFetchDouble(){ + try (sqlite3 db = createNewDb()){ + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + double total1 = 0; + for(double i = 1.5; i < 5.0; i = i + 1.0 ){ + total1 += i; + sqlite3_bind_double(stmt, 1, i); + sqlite3_step(stmt); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + double total2 = 0; + int counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + ++counter; + total2 += sqlite3_column_double(stmt, 0); + } + affirm(4 == counter); + sqlite3_finalize(stmt); + affirm(total2<=total1+0.01 && total2>=total1-0.01); + //sqlite3_close_v2(db); + } + } + + private void testBindFetchText(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + String[] list1 = { "hell🤩", "w😃rld", "!🤩" }; + int rc; + int n = 0; + for( String e : list1 ){ + rc = (0==n) + ? sqlite3_bind_text(stmt, 1, e) + : sqlite3_bind_text16(stmt, 1, e); + affirm(0 == rc); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE==rc); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + StringBuilder sbuf = new StringBuilder(); + n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0)); + final String txt = sqlite3_column_text16(stmt, 0); + sbuf.append( txt ); + affirm( txt.equals(new String( + sqlite3_column_text(stmt, 0), + StandardCharsets.UTF_8 + )) ); + affirm( txt.length() < sqlite3_value_bytes(sv) ); + affirm( txt.equals(new String( + sqlite3_value_text(sv), + StandardCharsets.UTF_8)) ); + affirm( txt.length() == sqlite3_value_bytes16(sv)/2 ); + affirm( txt.equals(sqlite3_value_text16(sv)) ); + sqlite3_value_free(sv); + ++n; + } + sqlite3_finalize(stmt); + affirm(3 == n); + affirm("w😃rldhell🤩!🤩".equals(sbuf.toString())); + + try( sqlite3_stmt stmt2 = prepare(db, "SELECT ?, ?") ){ + rc = sqlite3_bind_text(stmt2, 1, ""); + affirm( 0==rc ); + rc = sqlite3_bind_text(stmt2, 2, (String)null); + affirm( 0==rc ); + rc = sqlite3_step(stmt2); + affirm( SQLITE_ROW==rc ); + byte[] colBa = sqlite3_column_text(stmt2, 0); + affirm( 0==colBa.length ); + colBa = sqlite3_column_text(stmt2, 1); + affirm( null==colBa ); + //sqlite3_finalize(stmt); + } + + if(true){ + sqlite3_close_v2(db); + }else{ + // Let the Object.finalize() override deal with it. + } + } + + private void testBindFetchBlob(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + byte[] list1 = { 0x32, 0x33, 0x34 }; + int rc = sqlite3_bind_blob(stmt, 1, list1); + affirm( 0==rc ); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + int n = 0; + int total = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + byte[] blob = sqlite3_column_blob(stmt, 0); + affirm(3 == blob.length); + int i = 0; + for(byte b : blob){ + affirm(b == list1[i++]); + total += b; + } + ++n; + } + sqlite3_finalize(stmt); + affirm(1 == n); + affirm(total == 0x32 + 0x33 + 0x34); + sqlite3_close_v2(db); + } + + private void testSql(){ + sqlite3 db = createNewDb(); + sqlite3_stmt stmt = prepare(db, "SELECT 1"); + affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT ?"); + sqlite3_bind_text(stmt, 1, "hell😃"); + final String expect = "SELECT 'hell😃'"; + affirm( expect.equals(sqlite3_expanded_sql(stmt)) ); + String n = sqlite3_normalized_sql(stmt); + affirm( null==n || "SELECT?;".equals(n) ); + sqlite3_finalize(stmt); + sqlite3_close(db); + } + + private void testCollation(){ + final sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + final ValueHolder xDestroyCalled = new ValueHolder<>(0); + final CollationCallback myCollation = new CollationCallback() { + private String myState = + "this is local state. There is much like it, but this is mine."; + @Override + // Reverse-sorts its inputs... + public int call(byte[] lhs, byte[] rhs){ + int len = lhs.length > rhs.length ? rhs.length : lhs.length; + int c = 0, i = 0; + for(i = 0; i < len; ++i){ + c = lhs[i] - rhs[i]; + if(0 != c) break; + } + if(0==c){ + if(i < lhs.length) c = 1; + else if(i < rhs.length) c = -1; + } + return -c; + } + @Override + public void xDestroy() { + // Just demonstrates that xDestroy is called. + ++xDestroyCalled.value; + } + }; + final CollationNeededCallback collLoader = new CollationNeededCallback(){ + @Override + public int call(sqlite3 dbArg, int eTextRep, String collationName){ + affirm(dbArg == db/* as opposed to a temporary object*/); + return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); + } + }; + int rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc ); + rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc /* Installing the same object again is a no-op */); + sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi"); + int counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String val = sqlite3_column_text16(stmt, 0); + ++counter; + //outln("REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 1: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 3: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a"); + counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String val = sqlite3_column_text16(stmt, 0); + ++counter; + //outln("Non-REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 3: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 1: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + sqlite3_finalize(stmt); + affirm( 0 == xDestroyCalled.value ); + rc = sqlite3_collation_needed(db, null); + affirm( 0 == rc ); + sqlite3_close_v2(db); + affirm( 0 == db.getNativePointer() ); + affirm( 1 == xDestroyCalled.value ); + } + + @SingleThreadOnly /* because it's thread-agnostic */ + private void testToUtf8(){ + /** + https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html + + Let's ensure that we can convert to standard UTF-8 in Java code + (noting that the JNI native API has no way to do this). + */ + final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8); + affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); + } + + private void testStatus(){ + final OutputPointer.Int64 cur64 = new OutputPointer.Int64(); + final OutputPointer.Int64 high64 = new OutputPointer.Int64(); + final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); + final OutputPointer.Int32 high32 = new OutputPointer.Int32(); + final sqlite3 db = createNewDb(); + execSql(db, "create table t(a); insert into t values(1),(2),(3)"); + + int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.value > 0 ); + affirm( high32.value >= cur32.value ); + + rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false); + affirm( 0 == rc ); + affirm( cur64.value > 0 ); + affirm( high64.value >= cur64.value ); + + cur32.value = 0; + high32.value = 1; + rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.value > 0 ); + affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ ); + + sqlite3_close_v2(db); + } + + private void testUdf1(){ + final sqlite3 db = createNewDb(); + // These ValueHolders are just to confirm that the func did what we want... + final ValueHolder xDestroyCalled = new ValueHolder<>(false); + final ValueHolder xFuncAccum = new ValueHolder<>(0); + final ValueHolder neverEverDoThisInClientCode = new ValueHolder<>(null); + final ValueHolder neverEverDoThisInClientCode2 = new ValueHolder<>(null); + + // Create an SQLFunction instance using one of its 3 subclasses: + // Scalar, Aggregate, or Window: + SQLFunction func = + // Each of the 3 subclasses requires a different set of + // functions, all of which must be implemented. Anonymous + // classes are a convenient way to implement these. + new ScalarFunction(){ + public void xFunc(sqlite3_context cx, sqlite3_value[] args){ + affirm(db == sqlite3_context_db_handle(cx)); + if( null==neverEverDoThisInClientCode.value ){ + /* !!!NEVER!!! hold a reference to an sqlite3_value or + sqlite3_context object like this in client code! They + are ONLY legal for the duration of their single + call. We do it here ONLY to test that the defenses + against clients doing this are working. */ + neverEverDoThisInClientCode2.value = cx; + neverEverDoThisInClientCode.value = args; + } + int result = 0; + for( sqlite3_value v : args ) result += sqlite3_value_int(v); + xFuncAccum.value += result;// just for post-run testing + sqlite3_result_int(cx, result); + } + /* OPTIONALLY override xDestroy... */ + public void xDestroy(){ + xDestroyCalled.value = true; + } + }; + + // Register and use the function... + int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); + final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)"); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + affirm( 6 == sqlite3_column_int(stmt, 0) ); + ++n; + } + sqlite3_finalize(stmt); + affirm(1 == n); + affirm(6 == xFuncAccum.value); + affirm( !xDestroyCalled.value ); + affirm( null!=neverEverDoThisInClientCode.value ); + affirm( null!=neverEverDoThisInClientCode2.value ); + affirm( 0 xFuncAccum = new ValueHolder<>(0); + + SQLFunction funcAgg = new AggregateFunction(){ + @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ + /** Throwing from here should emit loud noise on stdout or stderr + but the exception is supressed because we have no way to inform + sqlite about it from these callbacks. */ + //throw new RuntimeException("Throwing from an xStep"); + } + @Override public void xFinal(sqlite3_context cx){ + throw new RuntimeException("Throwing from an xFinal"); + } + }; + int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); + sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)"); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + affirm( 0 != rc ); + affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 ); + + SQLFunction funcSc = new ScalarFunction(){ + @Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){ + throw new RuntimeException("Throwing from an xFunc"); + } + }; + rc = sqlite3_create_function(db, "mysca", 0, SQLITE_UTF8, funcSc); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); + stmt = prepare(db, "SELECT mysca()"); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + affirm( 0 != rc ); + affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 ); + rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc); + affirm( SQLITE_FORMAT==rc, "invalid encoding value." ); + sqlite3_close_v2(db); + } + + @SingleThreadOnly + private void testUdfJavaObject(){ + affirm( !mtMode ); + final sqlite3 db = createNewDb(); + final ValueHolder testResult = new ValueHolder<>(db); + final ValueHolder boundObj = new ValueHolder<>(42); + final SQLFunction func = new ScalarFunction(){ + public void xFunc(sqlite3_context cx, sqlite3_value args[]){ + sqlite3_result_java_object(cx, testResult.value); + affirm( sqlite3_value_java_object(args[0]) == boundObj ); + } + }; + int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); + affirm(0 == rc); + sqlite3_stmt stmt = prepare(db, "select myfunc(?)"); + affirm( 0 != stmt.getNativePointer() ); + affirm( testResult.value == db ); + rc = sqlite3_bind_java_object(stmt, 1, boundObj); + affirm( 0==rc ); + int n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final sqlite3_value v = sqlite3_column_value(stmt, 0); + affirm( testResult.value == sqlite3_value_java_object(v) ); + affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) ); + affirm( testResult.value == + sqlite3_value_java_casted(v, testResult.value.getClass()) ); + affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) ); + affirm( null == sqlite3_value_java_casted(v, String.class) ); + ++n; + } + sqlite3_finalize(stmt); + affirm( 1 == n ); + affirm( 0==sqlite3_db_release_memory(db) ); + sqlite3_close_v2(db); + } + + private void testUdfAggregate(){ + final sqlite3 db = createNewDb(); + final ValueHolder xFinalNull = + // To confirm that xFinal() is called with no aggregate state + // when the corresponding result set is empty. + new ValueHolder<>(false); + SQLFunction func = new AggregateFunction(){ + @Override + public void xStep(sqlite3_context cx, sqlite3_value[] args){ + final ValueHolder agg = this.getAggregateState(cx, 0); + agg.value += sqlite3_value_int(args[0]); + affirm( agg == this.getAggregateState(cx, 0) ); + } + @Override + public void xFinal(sqlite3_context cx){ + final Integer v = this.takeAggregateState(cx); + if(null == v){ + xFinalNull.value = true; + sqlite3_result_null(cx); + }else{ + sqlite3_result_int(cx, v); + } + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)"); + int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func); + affirm(0 == rc); + sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t"); + affirm( 0==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) ); + int n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + int v = sqlite3_column_int(stmt, 0); + affirm( 6 == v ); + int v2 = sqlite3_column_int(stmt, 1); + affirm( 30+v == v2 ); + ++n; + } + affirm( 1==n ); + affirm(!xFinalNull.value); + sqlite3_reset(stmt); + affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) ); + // Ensure that the accumulator is reset on subsequent calls... + n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final int v = sqlite3_column_int(stmt, 0); + affirm( 6 == v ); + ++n; + } + sqlite3_finalize(stmt); + affirm( 1==n ); + + stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a"); + n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final int c0 = sqlite3_column_int(stmt, 0); + final int c1 = sqlite3_column_int(stmt, 1); + ++n; + affirm( 6 == c0 ); + affirm( 12 == c1 ); + } + sqlite3_finalize(stmt); + affirm( 1 == n ); + affirm(!xFinalNull.value); + + execSql(db, "SELECT myfunc(1) WHERE 0"); + affirm(xFinalNull.value); + sqlite3_close_v2(db); + } + + private void testUdfWindow(){ + final sqlite3 db = createNewDb(); + /* Example window function, table, and results taken from: + https://sqlite.org/windowfunctions.html#udfwinfunc */ + final SQLFunction func = new WindowFunction(){ + + private void xStepInverse(sqlite3_context cx, int v){ + this.getAggregateState(cx,0).value += v; + } + @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ + this.xStepInverse(cx, sqlite3_value_int(args[0])); + } + @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){ + this.xStepInverse(cx, -sqlite3_value_int(args[0])); + } + + private void xFinalValue(sqlite3_context cx, Integer v){ + if(null == v) sqlite3_result_null(cx); + else sqlite3_result_int(cx, v); + } + @Override public void xFinal(sqlite3_context cx){ + xFinalValue(cx, this.takeAggregateState(cx)); + } + @Override public void xValue(sqlite3_context cx){ + xFinalValue(cx, this.getAggregateState(cx,null).value); + } + }; + int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func); + affirm( 0 == rc ); + execSql(db, new String[] { + "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", + "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" + }); + final sqlite3_stmt stmt = prepare(db, + "SELECT x, winsumint(y) OVER ("+ + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+ + ") AS sum_y "+ + "FROM twin ORDER BY x;"); + affirm( 0 == rc ); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String s = sqlite3_column_text16(stmt, 0); + final int i = sqlite3_column_int(stmt, 1); + switch(++n){ + case 1: affirm( "a".equals(s) && 9==i ); break; + case 2: affirm( "b".equals(s) && 12==i ); break; + case 3: affirm( "c".equals(s) && 16==i ); break; + case 4: affirm( "d".equals(s) && 12==i ); break; + case 5: affirm( "e".equals(s) && 9==i ); break; + default: affirm( false /* cannot happen */ ); + } + } + sqlite3_finalize(stmt); + affirm( 5 == n ); + sqlite3_close_v2(db); + } + + private void listBoundMethods(){ + if(false){ + final java.lang.reflect.Field[] declaredFields = + CApi.class.getDeclaredFields(); + outln("Bound constants:\n"); + for(java.lang.reflect.Field field : declaredFields) { + if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + outln("\t",field.getName()); + } + } + } + final java.lang.reflect.Method[] declaredMethods = + CApi.class.getDeclaredMethods(); + final java.util.List funcList = new java.util.ArrayList<>(); + for(java.lang.reflect.Method m : declaredMethods){ + if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + funcList.add(name); + } + } + } + int count = 0; + java.util.Collections.sort(funcList); + for(String n : funcList){ + ++count; + outln("\t",n,"()"); + } + outln(count," functions named sqlite3_*."); + } + + private void testTrace(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + /* Ensure that characters outside of the UTF BMP survive the trip + from Java to sqlite3 and back to Java. (At no small efficiency + penalty.) */ + final String nonBmpChar = "😃"; + int rc = sqlite3_trace_v2( + db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE + | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, + new TraceV2Callback(){ + @Override public int call(int traceFlag, Object pNative, Object x){ + ++counter.value; + //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); + switch(traceFlag){ + case SQLITE_TRACE_STMT: + affirm(pNative instanceof sqlite3_stmt); + //outln("TRACE_STMT sql = "+x); + affirm(x instanceof String); + affirm( ((String)x).indexOf(nonBmpChar) > 0 ); + break; + case SQLITE_TRACE_PROFILE: + affirm(pNative instanceof sqlite3_stmt); + affirm(x instanceof Long); + //outln("TRACE_PROFILE time = "+x); + break; + case SQLITE_TRACE_ROW: + affirm(pNative instanceof sqlite3_stmt); + affirm(null == x); + //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); + break; + case SQLITE_TRACE_CLOSE: + affirm(pNative instanceof sqlite3); + affirm(null == x); + break; + default: + affirm(false /*cannot happen*/); + break; + } + return 0; + } + }); + affirm( 0==rc ); + execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ + "SELECT 'w"+nonBmpChar+"orld'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + affirm( 7 == counter.value ); + } + + @SingleThreadOnly /* because threads inherently break this test */ + private static void testBusy(){ + final String dbName = "_busy-handler.db"; + final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + + int rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + final sqlite3 db1 = outDb.get(); + execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); + rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + affirm( outDb.get() != db1 ); + final sqlite3 db2 = outDb.get(); + + affirm( "main".equals( sqlite3_db_name(db1, 0) ) ); + rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); + affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); + affirm( "foo".equals( sqlite3_db_name(db1, 0) ) ); + + final ValueHolder xBusyCalled = new ValueHolder<>(0); + BusyHandlerCallback handler = new BusyHandlerCallback(){ + @Override public int call(int n){ + //outln("busy handler #"+n); + return n > 2 ? 0 : ++xBusyCalled.value; + } + }; + rc = sqlite3_busy_handler(db2, handler); + affirm(0 == rc); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); + affirm( SQLITE_BUSY == rc); + affirm( null == outStmt.get() ); + affirm( 3 == xBusyCalled.value ); + sqlite3_close_v2(db1); + sqlite3_close_v2(db2); + try{ + final java.io.File f = new java.io.File(dbName); + f.delete(); + }catch(Exception e){ + /* ignore */ + } + } + + private void testProgress(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + sqlite3_progress_handler(db, 1, new ProgressHandlerCallback(){ + @Override public int call(){ + ++counter.value; + return 0; + } + }); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( counter.value > 0 ); + int nOld = counter.value; + sqlite3_progress_handler(db, 0, null); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( nOld == counter.value ); + sqlite3_close_v2(db); + } + + private void testCommitHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder hookResult = new ValueHolder<>(0); + final CommitHookCallback theHook = new CommitHookCallback(){ + @Override public int call(){ + ++counter.value; + return hookResult.value; + } + }; + CommitHookCallback oldHook = sqlite3_commit_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 2 == counter.value ); + execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;"); + affirm( 2 == counter.value /* NOT invoked if no changes are made */ ); + execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;"); + affirm( 3 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); + affirm( 4 == counter.value ); + + final CommitHookCallback newHook = new CommitHookCallback(){ + @Override public int call(){return 0;} + }; + oldHook = sqlite3_commit_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); + affirm( 5 == counter.value ); + hookResult.value = SQLITE_ERROR; + int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); + affirm( SQLITE_CONSTRAINT == rc ); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + private void testUpdateHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final UpdateHookCallback theHook = new UpdateHookCallback(){ + @Override + public void call(int opId, String dbName, String tableName, long rowId){ + ++counter.value; + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + UpdateHookCallback oldHook = sqlite3_update_hook(db, theHook); + affirm( null == oldHook ); + expectedOp.value = SQLITE_INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( theHook == oldHook ); + expectedOp.value = SQLITE_DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( null == oldHook ); + + final UpdateHookCallback newHook = new UpdateHookCallback(){ + @Override public void call(int opId, String dbName, String tableName, long rowId){ + } + }; + oldHook = sqlite3_update_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( newHook == oldHook ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + /** + This test is functionally identical to testUpdateHook(), only with a + different callback type. + */ + private void testPreUpdateHook(){ + if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){ + //outln("Skipping testPreUpdateHook(): no pre-update hook support."); + return; + } + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final PreupdateHookCallback theHook = new PreupdateHookCallback(){ + @Override + public void call(sqlite3 db, int opId, String dbName, String dbTable, + long iKey1, long iKey2 ){ + ++counter.value; + switch( opId ){ + case SQLITE_UPDATE: + affirm( 0 < sqlite3_preupdate_count(db) ); + affirm( null != sqlite3_preupdate_new(db, 0) ); + affirm( null != sqlite3_preupdate_old(db, 0) ); + break; + case SQLITE_INSERT: + affirm( null != sqlite3_preupdate_new(db, 0) ); + break; + case SQLITE_DELETE: + affirm( null != sqlite3_preupdate_old(db, 0) ); + break; + default: + break; + } + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + PreupdateHookCallback oldHook = sqlite3_preupdate_hook(db, theHook); + affirm( null == oldHook ); + expectedOp.value = SQLITE_INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, theHook); + affirm( theHook == oldHook ); + expectedOp.value = SQLITE_DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, null); + affirm( null == oldHook ); + + final PreupdateHookCallback newHook = new PreupdateHookCallback(){ + @Override + public void call(sqlite3 db, int opId, String dbName, + String tableName, long iKey1, long iKey2){ + } + }; + oldHook = sqlite3_preupdate_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, theHook); + affirm( newHook == oldHook ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + + sqlite3_close_v2(db); + } + + private void testRollbackHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final RollbackHookCallback theHook = new RollbackHookCallback(){ + @Override public void call(){ + ++counter.value; + } + }; + RollbackHookCallback oldHook = sqlite3_rollback_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 0 == counter.value ); + execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;"); + affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ ); + + final RollbackHookCallback newHook = new RollbackHookCallback(){ + @Override public void call(){return;} + }; + oldHook = sqlite3_rollback_hook(db, newHook); + affirm( theHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 1 == counter.value ); + oldHook = sqlite3_rollback_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 2 == counter.value ); + int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 0 == rc ); + affirm( 3 == counter.value ); + sqlite3_close_v2(db); + } + + /** + If FTS5 is available, runs FTS5 tests, else returns with no side + effects. If it is available but loading of the FTS5 bits fails, + it throws. + */ + @SuppressWarnings("unchecked") + @SingleThreadOnly /* because the Fts5 parts are not yet known to be + thread-safe */ + private void testFts5() throws Exception { + if( !sqlite3_compileoption_used("ENABLE_FTS5") ){ + //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); + return; + } + Exception err = null; + try { + Class t = Class.forName("org.sqlite.jni.fts5.TesterFts5"); + java.lang.reflect.Constructor ctor = t.getConstructor(); + ctor.setAccessible(true); + final long timeStart = System.currentTimeMillis(); + ctor.newInstance() /* will run all tests */; + final long timeEnd = System.currentTimeMillis(); + outln("FTS5 Tests done in ",(timeEnd - timeStart),"ms"); + }catch(ClassNotFoundException e){ + outln("FTS5 classes not loaded."); + err = e; + }catch(NoSuchMethodException e){ + outln("FTS5 tester ctor not found."); + err = e; + }catch(Exception e){ + outln("Instantiation of FTS5 tester threw."); + err = e; + } + if( null != err ){ + outln("Exception: "+err); + err.printStackTrace(); + throw err; + } + } + + private void testAuthorizer(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder authRc = new ValueHolder<>(0); + final AuthorizerCallback auth = new AuthorizerCallback(){ + public int call(int op, String s0, String s1, String s2, String s3){ + ++counter.value; + //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3); + return authRc.value; + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + sqlite3_set_authorizer(db, auth); + execSql(db, "UPDATE t SET a=1"); + affirm( 1 == counter.value ); + authRc.value = SQLITE_DENY; + int rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( SQLITE_AUTH==rc ); + // TODO: expand these tests considerably + sqlite3_close(db); + } + + @SingleThreadOnly /* because multiple threads legitimately make these + results unpredictable */ + private synchronized void testAutoExtension(){ + final ValueHolder val = new ValueHolder<>(0); + final ValueHolder toss = new ValueHolder<>(null); + final AutoExtensionCallback ax = new AutoExtensionCallback(){ + @Override public int call(sqlite3 db){ + ++val.value; + if( null!=toss.value ){ + throw new RuntimeException(toss.value); + } + return 0; + } + }; + int rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close(createNewDb()); + affirm( 1==val.value ); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + sqlite3_reset_auto_extension(); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + // Must not add a new entry + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close( createNewDb() ); + affirm( 3==val.value ); + + sqlite3 db = createNewDb(); + affirm( 4==val.value ); + execSql(db, "ATTACH ':memory:' as foo"); + affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." ); + sqlite3_close(db); + db = null; + + affirm( sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + sqlite3_close(createNewDb()); + affirm( 4==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + Exception err = null; + toss.value = "Throwing from auto_extension."; + try{ + sqlite3_close(createNewDb()); + }catch(Exception e){ + err = e; + } + affirm( err!=null ); + affirm( err.getMessage().indexOf(toss.value)>0 ); + toss.value = null; + + val.value = 0; + final AutoExtensionCallback ax2 = new AutoExtensionCallback(){ + @Override public synchronized int call(sqlite3 db){ + ++val.value; + return 0; + } + }; + rc = sqlite3_auto_extension( ax2 ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 2 == val.value ); + affirm( sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + sqlite3_close(createNewDb()); + affirm( 3 == val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 5 == val.value ); + affirm( sqlite3_cancel_auto_extension(ax2) ); + affirm( !sqlite3_cancel_auto_extension(ax2) ); + sqlite3_close(createNewDb()); + affirm( 6 == val.value ); + rc = sqlite3_auto_extension( ax2 ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); + + sqlite3_reset_auto_extension(); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax2) ); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); + } + + + private void testColumnMetadata(){ + final sqlite3 db = createNewDb(); + execSql(db, new String[] { + "CREATE TABLE t(a duck primary key not null collate noCase); ", + "INSERT INTO t(a) VALUES(1),(2),(3);" + }); + OutputPointer.Bool bNotNull = new OutputPointer.Bool(); + OutputPointer.Bool bPrimaryKey = new OutputPointer.Bool(); + OutputPointer.Bool bAutoinc = new OutputPointer.Bool(); + OutputPointer.String zCollSeq = new OutputPointer.String(); + OutputPointer.String zDataType = new OutputPointer.String(); + int rc = sqlite3_table_column_metadata( + db, "main", "t", "a", zDataType, zCollSeq, + bNotNull, bPrimaryKey, bAutoinc); + affirm( 0==rc ); + affirm( bPrimaryKey.value ); + affirm( !bAutoinc.value ); + affirm( bNotNull.value ); + affirm( "noCase".equals(zCollSeq.value) ); + affirm( "duck".equals(zDataType.value) ); + + TableColumnMetadata m = + sqlite3_table_column_metadata(db, "main", "t", "a"); + affirm( null != m ); + affirm( bPrimaryKey.value == m.isPrimaryKey() ); + affirm( bAutoinc.value == m.isAutoincrement() ); + affirm( bNotNull.value == m.isNotNull() ); + affirm( zCollSeq.value.equals(m.getCollation()) ); + affirm( zDataType.value.equals(m.getDataType()) ); + + affirm( null == sqlite3_table_column_metadata(db, "nope", "t", "a") ); + affirm( null == sqlite3_table_column_metadata(db, "main", "nope", "a") ); + + m = sqlite3_table_column_metadata(db, "main", "t", null) + /* Check only for existence of table */; + affirm( null != m ); + affirm( m.isPrimaryKey() ); + affirm( !m.isAutoincrement() ); + affirm( !m.isNotNull() ); + affirm( "BINARY".equalsIgnoreCase(m.getCollation()) ); + affirm( "INTEGER".equalsIgnoreCase(m.getDataType()) ); + + sqlite3_close_v2(db); + } + + private void testTxnState(){ + final sqlite3 db = createNewDb(); + affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) ); + affirm( sqlite3_get_autocommit(db) ); + execSql(db, "BEGIN;"); + affirm( !sqlite3_get_autocommit(db) ); + affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) ); + execSql(db, "SELECT * FROM sqlite_schema;"); + affirm( SQLITE_TXN_READ == sqlite3_txn_state(db, "main") ); + execSql(db, "CREATE TABLE t(a);"); + affirm( SQLITE_TXN_WRITE == sqlite3_txn_state(db, null) ); + execSql(db, "ROLLBACK;"); + affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) ); + sqlite3_close_v2(db); + } + + + private void testExplain(){ + final sqlite3 db = createNewDb(); + sqlite3_stmt stmt = prepare(db,"SELECT 1"); + + affirm( 0 == sqlite3_stmt_isexplain(stmt) ); + int rc = sqlite3_stmt_explain(stmt, 1); + affirm( 1 == sqlite3_stmt_isexplain(stmt) ); + rc = sqlite3_stmt_explain(stmt, 2); + affirm( 2 == sqlite3_stmt_isexplain(stmt) ); + sqlite3_finalize(stmt); + sqlite3_close_v2(db); + } + + private void testLimit(){ + final sqlite3 db = createNewDb(); + int v; + + v = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1); + affirm( v > 0 ); + affirm( v == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, v-1) ); + affirm( v-1 == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1) ); + sqlite3_close_v2(db); + } + + private void testComplete(){ + affirm( 0==sqlite3_complete("select 1") ); + affirm( 0!=sqlite3_complete("select 1;") ); + affirm( 0!=sqlite3_complete("nope 'nope' 'nope' 1;"), "Yup" ); + } + + private void testKeyword(){ + final int n = sqlite3_keyword_count(); + affirm( n>0 ); + affirm( !sqlite3_keyword_check("_nope_") ); + affirm( sqlite3_keyword_check("seLect") ); + affirm( null!=sqlite3_keyword_name(0) ); + affirm( null!=sqlite3_keyword_name(n-1) ); + affirm( null==sqlite3_keyword_name(n) ); + } + + private void testBackup(){ + final sqlite3 dbDest = createNewDb(); + + try (sqlite3 dbSrc = createNewDb()) { + execSql(dbSrc, new String[]{ + "pragma page_size=512; VACUUM;", + "create table t(a);", + "insert into t(a) values(1),(2),(3);" + }); + affirm( null==sqlite3_backup_init(dbSrc,"main",dbSrc,"main") ); + try (sqlite3_backup b = sqlite3_backup_init(dbDest,"main",dbSrc,"main")) { + affirm( null!=b ); + affirm( b.getNativePointer()!=0 ); + int rc; + while( SQLITE_DONE!=(rc = sqlite3_backup_step(b, 1)) ){ + affirm( 0==rc ); + } + affirm( sqlite3_backup_pagecount(b) > 0 ); + rc = sqlite3_backup_finish(b); + affirm( 0==rc ); + affirm( b.getNativePointer()==0 ); + } + } + + try (sqlite3_stmt stmt = prepare(dbDest,"SELECT sum(a) from t")) { + sqlite3_step(stmt); + affirm( sqlite3_column_int(stmt,0) == 6 ); + } + sqlite3_close_v2(dbDest); + } + + private void testRandomness(){ + byte[] foo = new byte[20]; + int i = 0; + for( byte b : foo ){ + i += b; + } + affirm( i==0 ); + sqlite3_randomness(foo); + for( byte b : foo ){ + if(b!=0) ++i; + } + affirm( i!=0, "There's a very slight chance that 0 is actually correct." ); + } + + private void testBlobOpen(){ + final sqlite3 db = createNewDb(); + + execSql(db, "CREATE TABLE T(a BLOB);" + +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');" + ); + final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob(); + int rc = sqlite3_blob_open(db, "main", "t", "a", + sqlite3_last_insert_rowid(db), 1, pOut); + affirm( 0==rc ); + sqlite3_blob b = pOut.take(); + affirm( null!=b ); + affirm( 0!=b.getNativePointer() ); + affirm( 3==sqlite3_blob_bytes(b) ); + rc = sqlite3_blob_write( b, new byte[] {100, 101, 102 /*"DEF"*/}, 0); + affirm( 0==rc ); + rc = sqlite3_blob_close(b); + affirm( 0==rc ); + rc = sqlite3_blob_close(b); + affirm( 0!=rc ); + affirm( 0==b.getNativePointer() ); + sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t ORDER BY a"); + affirm( SQLITE_ROW == sqlite3_step(stmt) ); + affirm( 3 == sqlite3_column_int(stmt,0) ); + affirm( "def".equals(sqlite3_column_text16(stmt,1)) ); + sqlite3_finalize(stmt); + + b = sqlite3_blob_open(db, "main", "t", "a", + sqlite3_last_insert_rowid(db), 1); + affirm( null!=b ); + rc = sqlite3_blob_reopen(b, 2); + affirm( 0==rc ); + final byte[] tgt = new byte[3]; + rc = sqlite3_blob_read(b, tgt, 0); + affirm( 0==rc ); + affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" ); + rc = sqlite3_blob_close(b); + affirm( 0==rc ); + sqlite3_close_v2(db); + } + + private void testPrepareMulti(){ + final sqlite3 db = createNewDb(); + final String[] sql = { + "create table t(","a)", + "; insert into t(a) values(1),(2),(3);", + "select a from t;" + }; + final List liStmt = new ArrayList(); + final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll(); + PrepareMultiCallback m = new PrepareMultiCallback() { + @Override public int call(sqlite3_stmt st){ + liStmt.add(st); + return proxy.call(st); + } + }; + int rc = sqlite3_prepare_multi(db, sql, m); + affirm( 0==rc ); + affirm( liStmt.size() == 3 ); + for( sqlite3_stmt st : liStmt ){ + sqlite3_finalize(st); + } + sqlite3_close_v2(db); + } + + /* Copy/paste/rename this to add new tests. */ + private void _testTemplate(){ + final sqlite3 db = createNewDb(); + sqlite3_stmt stmt = prepare(db,"SELECT 1"); + sqlite3_finalize(stmt); + sqlite3_close_v2(db); + } + + + @ManualTest /* we really only want to run this test manually */ + private void testSleep(){ + out("Sleeping briefly... "); + sqlite3_sleep(600); + outln("Woke up."); + } + + private void nap() throws InterruptedException { + if( takeNaps ){ + Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0); + } + } + + @ManualTest /* because we only want to run this test on demand */ + private void testFail(){ + affirm( false, "Intentional failure." ); + } + + private void runTests(boolean fromThread) throws Exception { + if(false) showCompileOption(); + List mlist = testMethods; + affirm( null!=mlist ); + if( shuffle ){ + mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); + java.util.Collections.shuffle(mlist); + } + if( listRunTests ){ + synchronized(this.getClass()){ + if( !fromThread ){ + out("Initial test"," list: "); + for(java.lang.reflect.Method m : testMethods){ + out(m.getName()+" "); + } + outln(); + outln("(That list excludes some which are hard-coded to run.)"); + } + out("Running"," tests: "); + for(java.lang.reflect.Method m : mlist){ + out(m.getName()+" "); + } + outln(); + } + } + for(java.lang.reflect.Method m : mlist){ + nap(); + try{ + m.invoke(this); + }catch(java.lang.reflect.InvocationTargetException e){ + outln("FAILURE: ",m.getName(),"(): ", e.getCause()); + throw e; + } + } + synchronized( this.getClass() ){ + ++nTestRuns; + } + } + + public void run() { + try { + runTests(0!=this.tId); + }catch(Exception e){ + synchronized( listErrors ){ + listErrors.add(e); + } + }finally{ + affirm( sqlite3_java_uncache_thread() ); + affirm( !sqlite3_java_uncache_thread() ); + } + } + + /** + Runs the basic sqlite3 JNI binding sanity-check suite. + + CLI flags: + + -q|-quiet: disables most test output. + + -t|-thread N: runs the tests in N threads + concurrently. Default=1. + + -r|-repeat N: repeats the tests in a loop N times, each one + consisting of the -thread value's threads. + + -shuffle: randomizes the order of most of the test functions. + + -naps: sleep small random intervals between tests in order to add + some chaos for cross-thread contention. + + -list-tests: outputs the list of tests being run, minus some + which are hard-coded. This is noisy in multi-threaded mode. + + -fail: forces an exception to be thrown during the test run. Use + with -shuffle to make its appearance unpredictable. + + -v: emit some developer-mode info at the end. + */ + public static void main(String[] args) throws Exception { + Integer nThread = 1; + boolean doSomethingForDev = false; + Integer nRepeat = 1; + boolean forceFail = false; + boolean sqlLog = false; + boolean configLog = false; + boolean squelchTestOutput = false; + for( int i = 0; i < args.length; ){ + String arg = args[i++]; + if(arg.startsWith("-")){ + arg = arg.replaceFirst("-+",""); + if(arg.equals("v")){ + doSomethingForDev = true; + //listBoundMethods(); + }else if(arg.equals("t") || arg.equals("thread")){ + nThread = Integer.parseInt(args[i++]); + }else if(arg.equals("r") || arg.equals("repeat")){ + nRepeat = Integer.parseInt(args[i++]); + }else if(arg.equals("shuffle")){ + shuffle = true; + }else if(arg.equals("list-tests")){ + listRunTests = true; + }else if(arg.equals("fail")){ + forceFail = true; + }else if(arg.equals("sqllog")){ + sqlLog = true; + }else if(arg.equals("configlog")){ + configLog = true; + }else if(arg.equals("naps")){ + takeNaps = true; + }else if(arg.equals("q") || arg.equals("quiet")){ + squelchTestOutput = true; + }else{ + throw new IllegalArgumentException("Unhandled flag:"+arg); + } + } + } + + if( sqlLog ){ + if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){ + final ConfigSqllogCallback log = new ConfigSqllogCallback() { + @Override public void call(sqlite3 db, String msg, int op){ + switch(op){ + case 0: outln("Opening db: ",db); break; + case 1: outln("SQL ",db,": ",msg); break; + case 2: outln("Closing db: ",db); break; + } + } + }; + int rc = sqlite3_config( log ); + affirm( 0==rc ); + rc = sqlite3_config( (ConfigSqllogCallback)null ); + affirm( 0==rc ); + rc = sqlite3_config( log ); + affirm( 0==rc ); + }else{ + outln("WARNING: -sqllog is not active because library was built ", + "without SQLITE_ENABLE_SQLLOG."); + } + } + if( configLog ){ + final ConfigLogCallback log = new ConfigLogCallback() { + @Override public void call(int code, String msg){ + outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg); + }; + }; + int rc = sqlite3_config( log ); + affirm( 0==rc ); + rc = sqlite3_config( (ConfigLogCallback)null ); + affirm( 0==rc ); + rc = sqlite3_config( log ); + affirm( 0==rc ); + } + + quietMode = squelchTestOutput; + outln("If you just saw warning messages regarding CallStaticObjectMethod, ", + "you are very likely seeing the side effects of a known openjdk8 ", + "bug. It is unsightly but does not affect the library."); + + { + // Build list of tests to run from the methods named test*(). + testMethods = new ArrayList<>(); + int nSkipped = 0; + for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){ + final String name = m.getName(); + if( name.equals("testFail") ){ + if( forceFail ){ + testMethods.add(m); + } + }else if( !m.isAnnotationPresent( ManualTest.class ) ){ + if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){ + if( 0==nSkipped++ ){ + out("Skipping tests in multi-thread mode:"); + } + out(" "+name+"()"); + }else if( name.startsWith("test") ){ + testMethods.add(m); + } + } + } + if( nSkipped>0 ) out("\n"); + } + + final long timeStart = System.currentTimeMillis(); + int nLoop = 0; + switch( sqlite3_threadsafe() ){ /* Sanity checking */ + case 0: + affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ), + "Could not switch to single-thread mode." ); + affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ), + "Could switch to multithread mode." ); + affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SERIALIZED ), + "Could not switch to serialized threading mode." ); + outln("This is a single-threaded build. Not using threads."); + nThread = 1; + break; + case 1: + case 2: + affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ), + "Could not switch to single-thread mode." ); + affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ), + "Could not switch to multithread mode." ); + affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ), + "Could not switch to serialized threading mode." ); + break; + default: + affirm( false, "Unhandled SQLITE_THREADSAFE value." ); + } + outln("libversion_number: ", + sqlite3_libversion_number(),"\n", + sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n", + "SQLITE_THREADSAFE=",sqlite3_threadsafe()); + final boolean showLoopCount = (nRepeat>1 && nThread>1); + if( showLoopCount ){ + outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); + } + if( takeNaps ) outln("Napping between tests is enabled."); + for( int n = 0; n < nRepeat; ++n ){ + ++nLoop; + if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop); + if( nThread<=1 ){ + new Tester1(0).runTests(false); + continue; + } + Tester1.mtMode = true; + final ExecutorService ex = Executors.newFixedThreadPool( nThread ); + for( int i = 0; i < nThread; ++i ){ + ex.submit( new Tester1(i), i ); + } + ex.shutdown(); + try{ + ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS); + ex.shutdownNow(); + }catch (InterruptedException ie){ + ex.shutdownNow(); + Thread.currentThread().interrupt(); + } + if( !listErrors.isEmpty() ){ + quietMode = false; + outln("TEST ERRORS:"); + Exception err = null; + for( Exception e : listErrors ){ + e.printStackTrace(); + if( null==err ) err = e; + } + if( null!=err ) throw err; + } + } + if( showLoopCount ) outln(); + quietMode = false; + + final long timeEnd = System.currentTimeMillis(); + outln("Tests done. Metrics across ",nTestRuns," total iteration(s):"); + outln("\tAssertions checked: ",affirmCount); + outln("\tDatabases opened: ",metrics.dbOpen); + if( doSomethingForDev ){ + sqlite3_jni_internal_details(); + } + affirm( 0==sqlite3_release_memory(1) ); + sqlite3_shutdown(); + int nMethods = 0; + int nNatives = 0; + final java.lang.reflect.Method[] declaredMethods = + CApi.class.getDeclaredMethods(); + for(java.lang.reflect.Method m : declaredMethods){ + final int mod = m.getModifiers(); + if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + ++nMethods; + if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){ + ++nNatives; + } + } + } + } + outln("\tCApi.sqlite3_*() methods: "+ + nMethods+" total, with "+ + nNatives+" native, "+ + (nMethods - nNatives)+" Java" + ); + outln("\tTotal test time = " + +(timeEnd - timeStart)+"ms"); + } +} diff --git a/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java b/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java new file mode 100644 index 000000000..56465a2c0 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java @@ -0,0 +1,50 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; +import org.sqlite.jni.annotation.Nullable; + +/** + Callback for use with {@link CApi#sqlite3_trace_v2}. +*/ +public interface TraceV2Callback extends CallbackProxy { + /** + Called by sqlite3 for various tracing operations, as per + sqlite3_trace_v2(). Note that this interface elides the 2nd + argument to the native trace callback, as that role is better + filled by instance-local state. + +

        These callbacks may throw, in which case their exceptions are + converted to C-level error information. + +

        The 2nd argument to this function, if non-null, will be a an + sqlite3 or sqlite3_stmt object, depending on the first argument + (see below). + +

        The final argument to this function is the "X" argument + documented for sqlite3_trace() and sqlite3_trace_v2(). Its type + depends on value of the first argument: + +

        - SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a String + containing the prepared SQL. + +

        - SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long + holding an approximate number of nanoseconds the statement took + to run. + +

        - SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null. + +

        - SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. + */ + int call(int traceFlag, Object pNative, @Nullable Object pX); +} diff --git a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java new file mode 100644 index 000000000..33d72a5dd --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java @@ -0,0 +1,25 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + Callback for use with {@link CApi#sqlite3_update_hook}. +*/ +public interface UpdateHookCallback extends CallbackProxy { + /** + Must function as described for the C-level sqlite3_update_hook() + callback. + */ + void call(int opId, String dbName, String tableName, long rowId); +} diff --git a/ext/jni/src/org/sqlite/jni/ValueHolder.java b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java similarity index 77% rename from ext/jni/src/org/sqlite/jni/ValueHolder.java rename to ext/jni/src/org/sqlite/jni/capi/ValueHolder.java index 7f6a463ba..b3f03ac86 100644 --- a/ext/jni/src/org/sqlite/jni/ValueHolder.java +++ b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java @@ -1,5 +1,5 @@ /* -** 2023-07-21 +** 2023-10-16 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -9,12 +9,12 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file is part of the JNI bindings for the sqlite3 C API. +** This file contains a set of tests for the sqlite3 JNI bindings. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** - A helper class which simply holds a single value. Its current use + A helper class which simply holds a single value. Its primary use is for communicating values out of anonymous classes, as doing so requires a "final" reference. */ diff --git a/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java b/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java new file mode 100644 index 000000000..eaf1bb9a3 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java @@ -0,0 +1,39 @@ +/* +** 2023-08-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + + +/** + A SQLFunction implementation for window functions. Note that + WindowFunction inherits from {@link AggregateFunction} and each + instance is required to implement the inherited abstract methods + from that class. See {@link AggregateFunction} for information on + managing the UDF's invocation-specific state. +*/ +public abstract class WindowFunction extends AggregateFunction { + + /** + As for the xInverse() argument of the C API's + sqlite3_create_window_function(). If this function throws, the + exception is not propagated and a warning might be emitted + to a debugging channel. + */ + public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); + + /** + As for the xValue() argument of the C API's sqlite3_create_window_function(). + See xInverse() for the fate of any exceptions this throws. + */ + public abstract void xValue(sqlite3_context cx); +} diff --git a/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java b/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java new file mode 100644 index 000000000..372e4ec8d --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java @@ -0,0 +1,37 @@ +/* +** 2023-07-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 declares JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + Callback for a hook called by SQLite when certain client-provided + state are destroyed. It gets its name from the pervasive use of + the symbol name xDestroy() for this purpose in the C API + documentation. +*/ +public interface XDestroyCallback { + /** + Must perform any cleanup required by this object. Must not + throw. Must not call back into the sqlite3 API, else it might + invoke a deadlock. + + WARNING: as a rule, it is never safe to register individual + instances with this interface multiple times in the + library. e.g., do not register the same CollationCallback with + multiple arities or names using sqlite3_create_collation(). If + this rule is violated, the library will eventually try to free + each individual reference, leading to memory corruption or a + crash via duplicate free(). + */ + public void xDestroy(); +} diff --git a/ext/jni/src/org/sqlite/jni/capi/package-info.java b/ext/jni/src/org/sqlite/jni/capi/package-info.java new file mode 100644 index 000000000..127f38067 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/package-info.java @@ -0,0 +1,89 @@ +/** + This package houses a JNI binding to the SQLite3 C API. + +

        The primary interfaces are in {@link + org.sqlite.jni.capi.CApi}.

        + +

        API Goals and Requirements

        + +
          + +
        • A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar + as cross-language semantics allow for. A closely-related goal is + that the C + documentation should be usable as-is, insofar as possible, + for most of the JNI binding. As a rule, undocumented symbols in + the Java interface behave as documented for their C API + counterpart. Only semantic differences and Java-specific features + are documented here.
        • + +
        • Support Java as far back as version 8 (2014).
        • + +
        • Environment-independent. Should work everywhere both Java and + SQLite3 do.
        • + +
        • No 3rd-party dependencies beyond the JDK. That includes no + build-level dependencies for specific IDEs and toolchains. We + welcome the addition of build files for arbitrary environments + insofar as they neither interfere with each other nor become a + maintenance burden for the sqlite developers.
        • + +
        + +

        Non-Goals

        + +
          + +
        • Creation of high-level OO wrapper APIs. Clients are free to + create them off of the C-style API.
        • + +
        • Support for mixed-mode operation, where client code accesses + SQLite both via the Java-side API and the C API via their own + native code. In such cases, proxy functionalities (primarily + callback handler wrappers of all sorts) may fail because the + C-side use of the SQLite APIs will bypass those proxies.
        • + +
        + +

        State of this API

        + +

        As of version 3.43, this software is in "tech preview" form. We + tentatively plan to stamp it as stable with the 3.44 release.

        + +

        Threading Considerations

        + +

        This API is, if built with SQLITE_THREADSAFE set to 1 or 2, + thread-safe, insofar as the C API guarantees, with some addenda:

        + +
          + +
        • It is not legal to use Java-facing SQLite3 resource handles + (sqlite3, sqlite3_stmt, etc) from multiple threads concurrently, + nor to use any database-specific resources concurrently in a + thread separate from the one the database is currently in use + in. i.e. do not use a sqlite3_stmt in thread #2 when thread #1 is + using the database which prepared that handle. + +
          Violating this will eventually corrupt the JNI-level bindings + between Java's and C's view of the database. This is a limitation + of the JNI bindings, not the lower-level library. +
        • + +
        • It is legal to use a given handle, and database-specific + resources, across threads, so long as no two threads pass + resources owned by the same database into the library + concurrently. +
        • + +
        + +

        Any number of threads may, of course, create and use any number + of database handles they wish. Care only needs to be taken when + those handles or their associated resources cross threads, or...

        + +

        When built with SQLITE_THREADSAFE=0 then no threading guarantees + are provided and multi-threaded use of the library will provoke + undefined behavior.

        + +*/ +package org.sqlite.jni.capi; diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java similarity index 75% rename from ext/jni/src/org/sqlite/jni/sqlite3.java rename to ext/jni/src/org/sqlite/jni/capi/sqlite3.java index cfc6c08d4..901317f0e 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java @@ -11,7 +11,7 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** A wrapper for communicating C-level (sqlite3*) instances with @@ -19,19 +19,25 @@ simply provide a type-safe way to communicate it between Java and C via JNI. */ -public final class sqlite3 extends NativePointerHolder { +public final class sqlite3 extends NativePointerHolder + implements AutoCloseable { + // Only invoked from JNI private sqlite3(){} public String toString(){ - long ptr = getNativePointer(); + final long ptr = getNativePointer(); if( 0==ptr ){ return sqlite3.class.getSimpleName()+"@null"; } - String fn = SQLite3Jni.sqlite3_db_filename(this, "main"); + final String fn = CApi.sqlite3_db_filename(this, "main"); return sqlite3.class.getSimpleName() +"@"+String.format("0x%08x",ptr) +"["+((null == fn) ? "" : fn)+"]" ; } + + @Override public void close(){ + CApi.sqlite3_close_v2(this.clearNativePointer()); + } } diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java new file mode 100644 index 000000000..0ef75c17e --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java @@ -0,0 +1,31 @@ +/* +** 2023-09-03 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + A wrapper for passing C-level (sqlite3_backup*) instances around in + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class sqlite3_backup extends NativePointerHolder + implements AutoCloseable { + // Only invoked from JNI. + private sqlite3_backup(){} + + @Override public void close(){ + CApi.sqlite3_backup_finish(this); + } + +} diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java new file mode 100644 index 000000000..1b96c18b0 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java @@ -0,0 +1,31 @@ +/* +** 2023-09-03 +** +** 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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + A wrapper for passing C-level (sqlite3_blob*) instances around in + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class sqlite3_blob extends NativePointerHolder + implements AutoCloseable { + // Only invoked from JNI. + private sqlite3_blob(){} + + @Override public void close(){ + CApi.sqlite3_blob_close(this.clearNativePointer()); + } + +} diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java new file mode 100644 index 000000000..82ec49af1 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java @@ -0,0 +1,79 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni.capi; + +/** + sqlite3_context instances are used in conjunction with user-defined + SQL functions (a.k.a. UDFs). +*/ +public final class sqlite3_context extends NativePointerHolder { + private Long aggregateContext = null; + + /** + getAggregateContext() corresponds to C's + sqlite3_aggregate_context(), with a slightly different interface + to account for cross-language differences. It serves the same + purposes in a slightly different way: it provides a key which is + stable across invocations of a UDF's callbacks, such that all + calls into those callbacks can determine which "set" of those + calls they belong to. + +

        Note that use of this method is not a requirement for proper use + of this class. sqlite3_aggregate_context() can also be used. + +

        If the argument is true and the aggregate context has not yet + been set up, it will be initialized and fetched on demand, else it + won't. The intent is that xStep(), xValue(), and xInverse() + methods pass true and xFinal() methods pass false. + +

        This function treats numeric 0 as null, always returning null instead + of 0. + +

        If this object is being used in the context of an aggregate or + window UDF, this function returns a non-0 value which is distinct + for each set of UDF callbacks from a single invocation of the + UDF, otherwise it returns 0. The returned value is only only + valid within the context of execution of a single SQL statement, + and must not be re-used by future invocations of the UDF in + different SQL statements. + +

        Consider this SQL, where MYFUNC is a user-defined aggregate function: + +

        {@code
        +     SELECT MYFUNC(A), MYFUNC(B) FROM T;
        +     }
        + +

        The xStep() and xFinal() methods of the callback need to be able + to differentiate between those two invocations in order to + perform their work properly. The value returned by + getAggregateContext() will be distinct for each of those + invocations of MYFUNC() and is intended to be used as a lookup + key for mapping callback invocations to whatever client-defined + state is needed by the UDF. + +

        There is one case where this will return null in the context + of an aggregate or window function: if the result set has no + rows, the UDF's xFinal() will be called without any other x...() + members having been called. In that one case, no aggregate + context key will have been generated. xFinal() implementations + need to be prepared to accept that condition as legal. + */ + public synchronized Long getAggregateContext(boolean initIfNeeded){ + if( aggregateContext==null ){ + aggregateContext = CApi.sqlite3_aggregate_context(this, initIfNeeded); + if( !initIfNeeded && null==aggregateContext ) aggregateContext = 0L; + } + return (null==aggregateContext || 0!=aggregateContext) ? aggregateContext : null; + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java similarity index 83% rename from ext/jni/src/org/sqlite/jni/sqlite3_stmt.java rename to ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java index d67230137..3b8b71f8a 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java @@ -11,7 +11,7 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; /** A wrapper for communicating C-level (sqlite3_stmt*) instances with @@ -19,7 +19,12 @@ simply provide a type-safe way to communicate it between Java and C via JNI. */ -public final class sqlite3_stmt extends NativePointerHolder { +public final class sqlite3_stmt extends NativePointerHolder + implements AutoCloseable { // Only invoked from JNI. private sqlite3_stmt(){} + + @Override public void close(){ + CApi.sqlite3_finalize(this.clearNativePointer()); + } } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_value.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java similarity index 95% rename from ext/jni/src/org/sqlite/jni/sqlite3_value.java rename to ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java index 2cfb32ff1..a4772f0f6 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_value.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java @@ -11,7 +11,7 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.capi; public final class sqlite3_value extends NativePointerHolder { //! Invoked only from JNI. diff --git a/ext/jni/src/org/sqlite/jni/Fts5.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5.java similarity index 67% rename from ext/jni/src/org/sqlite/jni/Fts5.java rename to ext/jni/src/org/sqlite/jni/fts5/Fts5.java index 102cf575a..0dceeafd2 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5.java +++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5.java @@ -11,24 +11,18 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; /** INCOMPLETE AND COMPLETELY UNTESTED. - A wrapper for communicating C-level (fts5_api*) instances with - Java. These wrappers do not own their associated pointer, they - simply provide a type-safe way to communicate it between Java and C - via JNI. + A utility object for holding FTS5-specific types and constants + which are used by multiple FTS5 classes. */ public final class Fts5 { /* Not used */ private Fts5(){} - //! Callback type for use with xTokenize() variants - public static interface xTokenizeCallback { - int xToken(int tFlags, byte txt[], int iStart, int iEnd); - } public static final int FTS5_TOKENIZE_QUERY = 0x0001; public static final int FTS5_TOKENIZE_PREFIX = 0x0002; diff --git a/ext/jni/src/org/sqlite/jni/Fts5Context.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java similarity index 92% rename from ext/jni/src/org/sqlite/jni/Fts5Context.java rename to ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java index e78f67d55..439b47791 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5Context.java +++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java @@ -11,7 +11,8 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; +import org.sqlite.jni.capi.*; /** A wrapper for communicating C-level (Fts5Context*) instances with diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java similarity index 54% rename from ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java rename to ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java index ac041e300..594f3eaad 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java @@ -11,76 +11,87 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; import java.nio.charset.StandardCharsets; +import org.sqlite.jni.capi.*; +import org.sqlite.jni.annotation.*; /** - ALMOST COMPLETELY UNTESTED. - - FAR FROM COMPLETE and the feasibility of binding this to Java - is still undetermined. This might be removed. */ public final class Fts5ExtensionApi extends NativePointerHolder { //! Only called from JNI private Fts5ExtensionApi(){} - private int iVersion = 2; + private final int iVersion = 2; /* Callback type for used by xQueryPhrase(). */ - public static interface xQueryPhraseCallback { - int xCallback(Fts5ExtensionApi fapi, Fts5Context cx); + public static interface XQueryPhraseCallback { + int call(Fts5ExtensionApi fapi, Fts5Context cx); } /** Returns the singleton instance of this class. */ - public static synchronized native Fts5ExtensionApi getInstance(); + public static native Fts5ExtensionApi getInstance(); + + public native int xColumnCount(@NotNull Fts5Context fcx); - public synchronized native int xColumnCount(@NotNull Fts5Context fcx); - public synchronized native int xColumnSize(@NotNull Fts5Context cx, int iCol, + public native int xColumnSize(@NotNull Fts5Context cx, int iCol, @NotNull OutputPointer.Int32 pnToken); - public synchronized native int xColumnText(@NotNull Fts5Context cx, int iCol, + + public native int xColumnText(@NotNull Fts5Context cx, int iCol, @NotNull OutputPointer.String txt); - public synchronized native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol, + + public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol, @NotNull OutputPointer.Int64 pnToken); - public synchronized native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt); - public synchronized native int xInst(@NotNull Fts5Context cx, int iIdx, + + public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt); + + public native int xInst(@NotNull Fts5Context cx, int iIdx, @NotNull OutputPointer.Int32 piPhrase, @NotNull OutputPointer.Int32 piCol, @NotNull OutputPointer.Int32 piOff); - public synchronized native int xInstCount(@NotNull Fts5Context fcx, + + public native int xInstCount(@NotNull Fts5Context fcx, @NotNull OutputPointer.Int32 pnInst); - public synchronized native int xPhraseCount(@NotNull Fts5Context fcx); - public synchronized native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase, + + public native int xPhraseCount(@NotNull Fts5Context fcx); + + public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase, @NotNull Fts5PhraseIter iter, @NotNull OutputPointer.Int32 iCol, @NotNull OutputPointer.Int32 iOff); - public synchronized native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase, + + public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase, @NotNull Fts5PhraseIter iter, @NotNull OutputPointer.Int32 iCol); - public synchronized native void xPhraseNext(@NotNull Fts5Context cx, + public native void xPhraseNext(@NotNull Fts5Context cx, @NotNull Fts5PhraseIter iter, @NotNull OutputPointer.Int32 iCol, @NotNull OutputPointer.Int32 iOff); - public synchronized native void xPhraseNextColumn(@NotNull Fts5Context cx, + public native void xPhraseNextColumn(@NotNull Fts5Context cx, @NotNull Fts5PhraseIter iter, @NotNull OutputPointer.Int32 iCol); - public synchronized native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase); - public synchronized native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase, - @NotNull xQueryPhraseCallback callback); - public synchronized native int xRowCount(@NotNull Fts5Context fcx, + public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase); + + public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase, + @NotNull XQueryPhraseCallback callback); + public native int xRowCount(@NotNull Fts5Context fcx, @NotNull OutputPointer.Int64 nRow); - public synchronized native long xRowid(@NotNull Fts5Context cx); + + public native long xRowid(@NotNull Fts5Context cx); /* Note that the JNI binding lacks the C version's xDelete() callback argument. Instead, if pAux has an xDestroy() method, it is called if the FTS5 API finalizes the aux state (including if allocation of storage for the auxdata fails). Any reference to pAux held by the JNI layer will be relinquished regardless of whether pAux has an xDestroy() method. */ - public synchronized native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux); - public synchronized native int xTokenize(@NotNull Fts5Context cx, @NotNull byte pText[], - @NotNull Fts5.xTokenizeCallback callback); - public synchronized native Object xUserData(Fts5Context cx); + public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux); + + public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText, + @NotNull XTokenizeCallback callback); + + public native Object xUserData(Fts5Context cx); //^^^ returns the pointer passed as the 3rd arg to the C-level - // fts5_api::xCreateFunction. + // fts5_api::xCreateFunction(). } diff --git a/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java similarity index 90% rename from ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java rename to ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java index eb4e05fdf..5774eb593 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java +++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java @@ -11,7 +11,8 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; +import org.sqlite.jni.capi.NativePointerHolder; /** A wrapper for C-level Fts5PhraseIter. They are only modified and diff --git a/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java similarity index 92% rename from ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java rename to ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java index 0d266a13d..b72e5d0fc 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java +++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java @@ -11,7 +11,8 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; +import org.sqlite.jni.capi.NativePointerHolder; /** INCOMPLETE AND COMPLETELY UNTESTED. diff --git a/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java b/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java new file mode 100644 index 000000000..c4264c541 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java @@ -0,0 +1,832 @@ +/* +** 2023-08-04 +** +** 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 a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni.fts5; +import static org.sqlite.jni.capi.CApi.*; +import static org.sqlite.jni.capi.Tester1.*; +import org.sqlite.jni.capi.*; +import org.sqlite.jni.fts5.*; + +import java.util.*; + +public class TesterFts5 { + + private static void test1(){ + final Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); + affirm( null != fea ); + affirm( fea.getNativePointer() != 0 ); + affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/; + + sqlite3 db = createNewDb(); + fts5_api fApi = fts5_api.getInstanceForDb(db); + affirm( fApi != null ); + affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ ); + + execSql(db, new String[] { + "CREATE VIRTUAL TABLE ft USING fts5(a, b);", + "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');", + "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');" + }); + + final String pUserData = "This is pUserData"; + final int outputs[] = {0, 0}; + final fts5_extension_function func = new fts5_extension_function(){ + @Override public void call(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]){ + final int nCols = ext.xColumnCount(fCx); + affirm( 2 == nCols ); + affirm( nCols == argv.length ); + affirm( ext.xUserData(fCx) == pUserData ); + final OutputPointer.String op = new OutputPointer.String(); + final OutputPointer.Int32 colsz = new OutputPointer.Int32(); + final OutputPointer.Int64 colTotalSz = new OutputPointer.Int64(); + for(int i = 0; i < nCols; ++i ){ + int rc = ext.xColumnText(fCx, i, op); + affirm( 0 == rc ); + final String val = op.value; + affirm( val.equals(sqlite3_value_text16(argv[i])) ); + rc = ext.xColumnSize(fCx, i, colsz); + affirm( 0==rc ); + affirm( 3==sqlite3_value_bytes(argv[i]) ); + rc = ext.xColumnTotalSize(fCx, i, colTotalSz); + affirm( 0==rc ); + } + ++outputs[0]; + } + public void xDestroy(){ + outputs[1] = 1; + } + }; + + int rc = fApi.xCreateFunction("myaux", pUserData, func); + affirm( 0==rc ); + + affirm( 0==outputs[0] ); + execSql(db, "select myaux(ft,a,b) from ft;"); + affirm( 2==outputs[0] ); + affirm( 0==outputs[1] ); + sqlite3_close_v2(db); + affirm( 1==outputs[1] ); + } + + /* + ** Argument sql is a string containing one or more SQL statements + ** separated by ";" characters. This function executes each of these + ** statements against the database passed as the first argument. If + ** no error occurs, the results of the SQL script are returned as + ** an array of strings. If an error does occur, a RuntimeException is + ** thrown. + */ + private static String[] sqlite3_exec(sqlite3 db, String sql) { + List aOut = new ArrayList(); + + /* Iterate through the list of SQL statements. For each, step through + ** it and add any results to the aOut[] array. */ + int rc = sqlite3_prepare_multi(db, sql, new PrepareMultiCallback() { + @Override public int call(sqlite3_stmt pStmt){ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + int ii; + for(ii=0; ii, ); + */ + class fts5_aux implements fts5_extension_function { + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length>1 ){ + throw new RuntimeException("fts5_aux: wrong number of args"); + } + + boolean bClear = (argv.length==1); + Object obj = ext.xGetAuxdata(fCx, bClear); + if( obj instanceof String ){ + sqlite3_result_text16(pCx, (String)obj); + } + + if( argv.length==1 ){ + String val = sqlite3_value_text16(argv[0]); + if( !val.equals("") ){ + ext.xSetAuxdata(fCx, val); + } + } + } + public void xDestroy(){ } + }; + + /* + ** fts5_inst(); + ** + ** This is used to test the xInstCount() and xInst() APIs. It returns a + ** text value containing a Tcl list with xInstCount() elements. Each + ** element is itself a list of 3 integers - the phrase number, column + ** number and token offset returned by each call to xInst(). + */ + fts5_extension_function fts5_inst = new fts5_extension_function(){ + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length!=0 ){ + throw new RuntimeException("fts5_inst: wrong number of args"); + } + + OutputPointer.Int32 pnInst = new OutputPointer.Int32(); + OutputPointer.Int32 piPhrase = new OutputPointer.Int32(); + OutputPointer.Int32 piCol = new OutputPointer.Int32(); + OutputPointer.Int32 piOff = new OutputPointer.Int32(); + String ret = new String(); + + int rc = ext.xInstCount(fCx, pnInst); + int nInst = pnInst.get(); + int ii; + + for(ii=0; rc==SQLITE_OK && ii0 ) ret += " "; + ret += "{"+piPhrase.get()+" "+piCol.get()+" "+piOff.get()+"}"; + } + + sqlite3_result_text(pCx, ret); + } + public void xDestroy(){ } + }; + + /* + ** fts5_pinst(); + ** + ** Like SQL function fts5_inst(), except using the following + ** + ** xPhraseCount + ** xPhraseFirst + ** xPhraseNext + */ + fts5_extension_function fts5_pinst = new fts5_extension_function(){ + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length!=0 ){ + throw new RuntimeException("fts5_pinst: wrong number of args"); + } + + OutputPointer.Int32 piCol = new OutputPointer.Int32(); + OutputPointer.Int32 piOff = new OutputPointer.Int32(); + String ret = new String(); + int rc = SQLITE_OK; + + int nPhrase = ext.xPhraseCount(fCx); + int ii; + + for(ii=0; rc==SQLITE_OK && ii=0; + ext.xPhraseNext(fCx, pIter, piCol, piOff) + ){ + if( !ret.equals("") ) ret += " "; + ret += "{"+ii+" "+piCol.get()+" "+piOff.get()+"}"; + } + } + + if( rc!=SQLITE_OK ){ + throw new RuntimeException("fts5_pinst: rc=" + rc); + }else{ + sqlite3_result_text(pCx, ret); + } + } + public void xDestroy(){ } + }; + + /* + ** fts5_pcolinst(); + ** + ** Like SQL function fts5_pinst(), except using the following + ** + ** xPhraseFirstColumn + ** xPhraseNextColumn + */ + fts5_extension_function fts5_pcolinst = new fts5_extension_function(){ + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length!=0 ){ + throw new RuntimeException("fts5_pcolinst: wrong number of args"); + } + + OutputPointer.Int32 piCol = new OutputPointer.Int32(); + String ret = new String(); + int rc = SQLITE_OK; + + int nPhrase = ext.xPhraseCount(fCx); + int ii; + + for(ii=0; rc==SQLITE_OK && ii=0; + ext.xPhraseNextColumn(fCx, pIter, piCol) + ){ + if( !ret.equals("") ) ret += " "; + ret += "{"+ii+" "+piCol.get()+"}"; + } + } + + if( rc!=SQLITE_OK ){ + throw new RuntimeException("fts5_pcolinst: rc=" + rc); + }else{ + sqlite3_result_text(pCx, ret); + } + } + public void xDestroy(){ } + }; + + /* + ** fts5_rowcount(); + */ + fts5_extension_function fts5_rowcount = new fts5_extension_function(){ + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length!=0 ){ + throw new RuntimeException("fts5_rowcount: wrong number of args"); + } + OutputPointer.Int64 pnRow = new OutputPointer.Int64(); + + int rc = ext.xRowCount(fCx, pnRow); + if( rc==SQLITE_OK ){ + sqlite3_result_int64(pCx, pnRow.get()); + }else{ + throw new RuntimeException("fts5_rowcount: rc=" + rc); + } + } + public void xDestroy(){ } + }; + + /* + ** fts5_phrasesize(); + */ + fts5_extension_function fts5_phrasesize = new fts5_extension_function(){ + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length!=1 ){ + throw new RuntimeException("fts5_phrasesize: wrong number of args"); + } + int iPhrase = sqlite3_value_int(argv[0]); + + int sz = ext.xPhraseSize(fCx, iPhrase); + sqlite3_result_int(pCx, sz); + } + public void xDestroy(){ } + }; + + /* + ** fts5_phrasehits(, ); + ** + ** Use the xQueryPhrase() API to determine how many hits, in total, + ** there are for phrase in the database. + */ + fts5_extension_function fts5_phrasehits = new fts5_extension_function(){ + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length!=1 ){ + throw new RuntimeException("fts5_phrasesize: wrong number of args"); + } + int iPhrase = sqlite3_value_int(argv[0]); + int rc = SQLITE_OK; + + class MyCallback implements Fts5ExtensionApi.XQueryPhraseCallback { + public int nRet = 0; + public int getRet() { return nRet; } + + @Override + public int call(Fts5ExtensionApi fapi, Fts5Context cx){ + OutputPointer.Int32 pnInst = new OutputPointer.Int32(); + int rc = fapi.xInstCount(cx, pnInst); + nRet += pnInst.get(); + return rc; + } + }; + + MyCallback xCall = new MyCallback(); + rc = ext.xQueryPhrase(fCx, iPhrase, xCall); + if( rc!=SQLITE_OK ){ + throw new RuntimeException("fts5_phrasehits: rc=" + rc); + } + sqlite3_result_int(pCx, xCall.getRet()); + } + public void xDestroy(){ } + }; + + /* + ** fts5_tokenize(, ) + */ + fts5_extension_function fts5_tokenize = new fts5_extension_function(){ + @Override public void call( + Fts5ExtensionApi ext, + Fts5Context fCx, + sqlite3_context pCx, + sqlite3_value argv[] + ){ + if( argv.length!=1 ){ + throw new RuntimeException("fts5_tokenize: wrong number of args"); + } + byte[] utf8 = sqlite3_value_text(argv[0]); + int rc = SQLITE_OK; + + class MyCallback implements XTokenizeCallback { + private List myList = new ArrayList(); + + public String getval() { + return String.join("+", myList); + } + + @Override + public int call(int tFlags, byte[] txt, int iStart, int iEnd){ + try { + String str = new String(txt, "UTF-8"); + myList.add(str); + } catch (Exception e) { + } + return SQLITE_OK; + } + }; + + MyCallback xCall = new MyCallback(); + ext.xTokenize(fCx, utf8, xCall); + sqlite3_result_text16(pCx, xCall.getval()); + + if( rc!=SQLITE_OK ){ + throw new RuntimeException("fts5_tokenize: rc=" + rc); + } + } + public void xDestroy(){ } + }; + + fts5_api api = fts5_api.getInstanceForDb(db); + api.xCreateFunction("fts5_rowid", fts5_rowid); + api.xCreateFunction("fts5_columncount", fts5_columncount); + api.xCreateFunction("fts5_columnsize", fts5_columnsize); + api.xCreateFunction("fts5_columntext", fts5_columntext); + api.xCreateFunction("fts5_columntotalsize", fts5_columntsize); + + api.xCreateFunction("fts5_aux1", new fts5_aux()); + api.xCreateFunction("fts5_aux2", new fts5_aux()); + + api.xCreateFunction("fts5_inst", fts5_inst); + api.xCreateFunction("fts5_pinst", fts5_pinst); + api.xCreateFunction("fts5_pcolinst", fts5_pcolinst); + api.xCreateFunction("fts5_rowcount", fts5_rowcount); + api.xCreateFunction("fts5_phrasesize", fts5_phrasesize); + api.xCreateFunction("fts5_phrasehits", fts5_phrasehits); + api.xCreateFunction("fts5_tokenize", fts5_tokenize); + } + /* + ** Test of various Fts5ExtensionApi methods + */ + private static void test2(){ + + /* Open db and populate an fts5 table */ + sqlite3 db = createNewDb(); + do_execsql_test(db, + "CREATE VIRTUAL TABLE ft USING fts5(a, b);" + + "INSERT INTO ft(rowid, a, b) VALUES(-9223372036854775808, 'x', 'x');" + + "INSERT INTO ft(rowid, a, b) VALUES(0, 'x', 'x');" + + "INSERT INTO ft(rowid, a, b) VALUES(1, 'x y z', 'x y z');" + + "INSERT INTO ft(rowid, a, b) VALUES(2, 'x y z', 'x z');" + + "INSERT INTO ft(rowid, a, b) VALUES(3, 'x y z', 'x y z');" + + "INSERT INTO ft(rowid, a, b) VALUES(9223372036854775807, 'x', 'x');" + ); + + create_test_functions(db); + + /* Test that fts5_rowid() seems to work */ + do_execsql_test(db, + "SELECT rowid==fts5_rowid(ft) FROM ft('x')", + "[1, 1, 1, 1, 1, 1]" + ); + + /* Test fts5_columncount() */ + do_execsql_test(db, + "SELECT fts5_columncount(ft) FROM ft('x')", + "[2, 2, 2, 2, 2, 2]" + ); + + /* Test fts5_columnsize() */ + do_execsql_test(db, + "SELECT fts5_columnsize(ft, 0) FROM ft('x') ORDER BY rowid", + "[1, 1, 3, 3, 3, 1]" + ); + do_execsql_test(db, + "SELECT fts5_columnsize(ft, 1) FROM ft('x') ORDER BY rowid", + "[1, 1, 3, 2, 3, 1]" + ); + do_execsql_test(db, + "SELECT fts5_columnsize(ft, -1) FROM ft('x') ORDER BY rowid", + "[2, 2, 6, 5, 6, 2]" + ); + + /* Test that xColumnSize() returns SQLITE_RANGE if the column number + ** is out-of range */ + try { + do_execsql_test(db, + "SELECT fts5_columnsize(ft, 2) FROM ft('x') ORDER BY rowid" + ); + } catch( RuntimeException e ){ + affirm( e.getMessage().matches(".*column index out of range") ); + } + + /* Test fts5_columntext() */ + do_execsql_test(db, + "SELECT fts5_columntext(ft, 0) FROM ft('x') ORDER BY rowid", + "[x, x, x y z, x y z, x y z, x]" + ); + do_execsql_test(db, + "SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid", + "[x, x, x y z, x z, x y z, x]" + ); + do_execsql_test(db, + "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid", + "[null, null, null, null, null, null]" + ); + + /* Test fts5_columntotalsize() */ + do_execsql_test(db, + "SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid", + "[12, 12, 12, 12, 12, 12]" + ); + do_execsql_test(db, + "SELECT fts5_columntotalsize(ft, 1) FROM ft('x') ORDER BY rowid", + "[11, 11, 11, 11, 11, 11]" + ); + do_execsql_test(db, + "SELECT fts5_columntotalsize(ft, -1) FROM ft('x') ORDER BY rowid", + "[23, 23, 23, 23, 23, 23]" + ); + + /* Test that xColumnTotalSize() returns SQLITE_RANGE if the column + ** number is out-of range */ + try { + do_execsql_test(db, + "SELECT fts5_columntotalsize(ft, 2) FROM ft('x') ORDER BY rowid" + ); + } catch( RuntimeException e ){ + affirm( e.getMessage().matches(".*column index out of range") ); + } + + do_execsql_test(db, + "SELECT rowid, fts5_rowcount(ft) FROM ft('z')", + "[1, 6, 2, 6, 3, 6]" + ); + + sqlite3_close_v2(db); + } + + /* + ** Test of various Fts5ExtensionApi methods + */ + private static void test3(){ + + /* Open db and populate an fts5 table */ + sqlite3 db = createNewDb(); + do_execsql_test(db, + "CREATE VIRTUAL TABLE ft USING fts5(a, b);" + + "INSERT INTO ft(a, b) VALUES('the one', 1);" + + "INSERT INTO ft(a, b) VALUES('the two', 2);" + + "INSERT INTO ft(a, b) VALUES('the three', 3);" + + "INSERT INTO ft(a, b) VALUES('the four', '');" + ); + create_test_functions(db); + + /* Test fts5_aux1() + fts5_aux2() - users of xGetAuxdata and xSetAuxdata */ + do_execsql_test(db, + "SELECT fts5_aux1(ft, a) FROM ft('the')", + "[null, the one, the two, the three]" + ); + do_execsql_test(db, + "SELECT fts5_aux2(ft, b) FROM ft('the')", + "[null, 1, 2, 3]" + ); + do_execsql_test(db, + "SELECT fts5_aux1(ft, a), fts5_aux2(ft, b) FROM ft('the')", + "[null, null, the one, 1, the two, 2, the three, 3]" + ); + do_execsql_test(db, + "SELECT fts5_aux1(ft, b), fts5_aux1(ft) FROM ft('the')", + "[null, 1, 1, 2, 2, 3, 3, null]" + ); + } + + /* + ** Test of various Fts5ExtensionApi methods + */ + private static void test4(){ + + /* Open db and populate an fts5 table */ + sqlite3 db = createNewDb(); + create_test_functions(db); + do_execsql_test(db, + "CREATE VIRTUAL TABLE ft USING fts5(a, b);" + + "INSERT INTO ft(a, b) VALUES('one two three', 'two three four');" + + "INSERT INTO ft(a, b) VALUES('two three four', 'three four five');" + + "INSERT INTO ft(a, b) VALUES('three four five', 'four five six');" + ); + + + do_execsql_test(db, + "SELECT fts5_inst(ft) FROM ft('two')", + "[{0 0 1} {0 1 0}, {0 0 0}]" + ); + do_execsql_test(db, + "SELECT fts5_inst(ft) FROM ft('four')", + "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]" + ); + + do_execsql_test(db, + "SELECT fts5_inst(ft) FROM ft('a OR b OR four')", + "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]" + ); + do_execsql_test(db, + "SELECT fts5_inst(ft) FROM ft('two four')", + "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]" + ); + + do_execsql_test(db, + "SELECT fts5_pinst(ft) FROM ft('two')", + "[{0 0 1} {0 1 0}, {0 0 0}]" + ); + do_execsql_test(db, + "SELECT fts5_pinst(ft) FROM ft('four')", + "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]" + ); + do_execsql_test(db, + "SELECT fts5_pinst(ft) FROM ft('a OR b OR four')", + "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]" + ); + do_execsql_test(db, + "SELECT fts5_pinst(ft) FROM ft('two four')", + "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]" + ); + + do_execsql_test(db, + "SELECT fts5_pcolinst(ft) FROM ft('two')", + "[{0 0} {0 1}, {0 0}]" + ); + do_execsql_test(db, + "SELECT fts5_pcolinst(ft) FROM ft('four')", + "[{0 1}, {0 0} {0 1}, {0 0} {0 1}]" + ); + do_execsql_test(db, + "SELECT fts5_pcolinst(ft) FROM ft('a OR b OR four')", + "[{2 1}, {2 0} {2 1}, {2 0} {2 1}]" + ); + do_execsql_test(db, + "SELECT fts5_pcolinst(ft) FROM ft('two four')", + "[{0 0} {0 1} {1 1}, {0 0} {1 0} {1 1}]" + ); + + do_execsql_test(db, + "SELECT fts5_phrasesize(ft, 0) FROM ft('four five six') LIMIT 1;", + "[1]" + ); + do_execsql_test(db, + "SELECT fts5_phrasesize(ft, 0) FROM ft('four + five + six') LIMIT 1;", + "[3]" + ); + + + sqlite3_close_v2(db); + } + + private static void test5(){ + /* Open db and populate an fts5 table */ + sqlite3 db = createNewDb(); + create_test_functions(db); + do_execsql_test(db, + "CREATE VIRTUAL TABLE ft USING fts5(x, b);" + + "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" + + "INSERT INTO ft(x) VALUES('one two one four one six one eight');" + + "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" + ); + + do_execsql_test(db, + "SELECT fts5_phrasehits(ft, 0) FROM ft('one') LIMIT 1", + "[6]" + ); + + sqlite3_close_v2(db); + } + + private static void test6(){ + sqlite3 db = createNewDb(); + create_test_functions(db); + do_execsql_test(db, + "CREATE VIRTUAL TABLE ft USING fts5(x, b);" + + "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" + ); + + do_execsql_test(db, + "SELECT fts5_tokenize(ft, 'abc def ghi') FROM ft('one')", + "[abc+def+ghi]" + ); + do_execsql_test(db, + "SELECT fts5_tokenize(ft, 'it''s BEEN a...') FROM ft('one')", + "[it+s+been+a]" + ); + + sqlite3_close_v2(db); + } + + private static synchronized void runTests(){ + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + } + + public TesterFts5(){ + runTests(); + } +} diff --git a/ext/jni/src/org/sqlite/jni/CommitHook.java b/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java similarity index 64% rename from ext/jni/src/org/sqlite/jni/CommitHook.java rename to ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java index eaa75a004..3aa514f31 100644 --- a/ext/jni/src/org/sqlite/jni/CommitHook.java +++ b/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-04 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -11,15 +11,12 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; + /** - Callback proxy for use with sqlite3_commit_hook(). + Callback type for use with xTokenize() variants. */ -public interface CommitHook { - /** - Works as documented for the sqlite3_commit_hook() callback. - Must not throw. - */ - int xCommitHook(); +public interface XTokenizeCallback { + int call(int tFlags, byte[] txt, int iStart, int iEnd); } diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java similarity index 80% rename from ext/jni/src/org/sqlite/jni/fts5_api.java rename to ext/jni/src/org/sqlite/jni/fts5/fts5_api.java index 43b3d62de..d7d2da430 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java @@ -11,11 +11,11 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; +import org.sqlite.jni.annotation.*; +import org.sqlite.jni.capi.*; /** - INCOMPLETE AND COMPLETELY UNTESTED. - A wrapper for communicating C-level (fts5_api*) instances with Java. These wrappers do not own their associated pointer, they simply provide a type-safe way to communicate it between Java and C @@ -24,7 +24,8 @@ public final class fts5_api extends NativePointerHolder { /* Only invoked from JNI */ private fts5_api(){} - public final int iVersion = 2; + + public static final int iVersion = 2; /** Returns the fts5_api instance associated with the given db, or @@ -32,6 +33,30 @@ private fts5_api(){} */ public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db); + public synchronized native int xCreateFunction(@NotNull String name, + @Nullable Object userData, + @NotNull fts5_extension_function xFunction); + + /** + Convenience overload which passes null as the 2nd argument to the + 3-parameter form. + */ + public int xCreateFunction(@NotNull String name, + @NotNull fts5_extension_function xFunction){ + return xCreateFunction(name, null, xFunction); + } + + // /* Create a new auxiliary function */ + // int (*xCreateFunction)( + // fts5_api *pApi, + // const char *zName, + // void *pContext, + // fts5_extension_function xFunction, + // void (*xDestroy)(void*) + // ); + + // Still potentially todo: + // int (*xCreateTokenizer)( // fts5_api *pApi, // const char *zName, @@ -48,22 +73,4 @@ private fts5_api(){} // fts5_tokenizer *pTokenizer // ); - // /* Create a new auxiliary function */ - // int (*xCreateFunction)( - // fts5_api *pApi, - // const char *zName, - // void *pContext, - // fts5_extension_function xFunction, - // void (*xDestroy)(void*) - // ); - - public synchronized native int xCreateFunction(@NotNull String name, - @Nullable Object userData, - @NotNull fts5_extension_function xFunction); - - public int xCreateFunction(@NotNull String name, - @NotNull fts5_extension_function xFunction){ - return xCreateFunction(name, null, xFunction); - } - } diff --git a/ext/jni/src/org/sqlite/jni/fts5_extension_function.java b/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java similarity index 61% rename from ext/jni/src/org/sqlite/jni/fts5_extension_function.java rename to ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java index 0e273119f..5e47633ba 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_extension_function.java +++ b/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java @@ -11,13 +11,14 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; +import org.sqlite.jni.capi.sqlite3_context; +import org.sqlite.jni.capi.sqlite3_value; /** JNI-level wrapper for C's fts5_extension_function type. - */ -public abstract class fts5_extension_function { +public interface fts5_extension_function { // typedef void (*fts5_extension_function)( // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ // Fts5Context *pFts, /* First arg to pass to pApi functions */ @@ -30,8 +31,17 @@ public abstract class fts5_extension_function { The callback implementation, corresponding to the xFunction argument of C's fts5_api::xCreateFunction(). */ - public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, - sqlite3_context pCx, sqlite3_value argv[]); - //! Optionally override - public void xDestroy(){} + void call(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]); + /** + Is called when this function is destroyed by sqlite3. Typically + this function will be empty. + */ + void xDestroy(); + + public static abstract class Abstract implements fts5_extension_function { + @Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]); + @Override public void xDestroy(){} + } } diff --git a/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java similarity index 90% rename from ext/jni/src/org/sqlite/jni/fts5_tokenizer.java rename to ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java index 097a0cc05..f4ada4dc3 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java +++ b/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java @@ -11,11 +11,11 @@ ************************************************************************* ** This file is part of the JNI bindings for the sqlite3 C API. */ -package org.sqlite.jni; +package org.sqlite.jni.fts5; +import org.sqlite.jni.capi.NativePointerHolder; +import org.sqlite.jni.annotation.NotNull; /** - INCOMPLETE AND COMPLETELY UNTESTED. - A wrapper for communicating C-level (fts5_tokenizer*) instances with Java. These wrappers do not own their associated pointer, they simply provide a type-safe way to communicate it between Java and C @@ -30,7 +30,7 @@ private fts5_tokenizer(){} public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags, @NotNull byte pText[], - @NotNull Fts5.xTokenizeCallback callback); + @NotNull XTokenizeCallback callback); // int (*xTokenize)(Fts5Tokenizer*, diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java deleted file mode 100644 index a61ff21c7..000000000 --- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java +++ /dev/null @@ -1,66 +0,0 @@ -/* -** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - sqlite3_context instances are used in conjunction with user-defined - SQL functions (a.k.a. UDFs). -*/ -public final class sqlite3_context extends NativePointerHolder { - /** - For use only by the JNI layer. It's permitted to set this even - though it's private. - */ - private long aggregateContext = 0; - - /** - getAggregateContext() corresponds to C's - sqlite3_aggregate_context(), with a slightly different interface - to account for cross-language differences. It serves the same - purposes in a slightly different way: it provides a key which is - stable across invocations of "matching sets" of a UDF's callbacks, - such that all calls into those callbacks can determine which "set" - of those calls they belong to. - - If this object is being used in the context of an aggregate or - window UDF, this function returns a non-0 value which is distinct - for each set of UDF callbacks from a single invocation of the - UDF, otherwise it returns 0. The returned value is only only - valid within the context of execution of a single SQL statement, - and may be re-used by future invocations of the UDF in different - SQL statements. - - Consider this SQL, where MYFUNC is a user-defined aggregate function: - - SELECT MYFUNC(A), MYFUNC(B) FROM T; - - The xStep() and xFinal() methods of the callback need to be able - to differentiate between those two invocations in order to - perform their work properly. The value returned by - getAggregateContext() will be distinct for each of those - invocations of MYFUNC() and is intended to be used as a lookup - key for mapping callback invocations to whatever client-defined - state is needed by the UDF. - - There is one case where this will return 0 in the context of an - aggregate or window function: if the result set has no rows, - the UDF's xFinal() will be called without any other x...() members - having been called. In that one case, no aggregate context key will - have been generated. xFinal() implementations need to be prepared to - accept that condition as legal. - */ - public long getAggregateContext(){ - return aggregateContext; - } -} diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/test-script-interpreter.md similarity index 100% rename from ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md rename to ext/jni/src/org/sqlite/jni/test-script-interpreter.md diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java new file mode 100644 index 000000000..173d775e6 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java @@ -0,0 +1,82 @@ +/* +** 2023-10-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 is part of the wrapper1 interface for sqlite3. +*/ +package org.sqlite.jni.wrapper1; +import org.sqlite.jni.capi.CApi; +import org.sqlite.jni.annotation.*; +import org.sqlite.jni.capi.sqlite3_context; +import org.sqlite.jni.capi.sqlite3_value; + +/** + EXPERIMENTAL/INCOMPLETE/UNTESTED + + A SqlFunction implementation for aggregate functions. The T type + represents the type of data accumulated by this aggregate while it + works. e.g. a SUM()-like UDF might use Integer or Long and a + CONCAT()-like UDF might use a StringBuilder or a List. +*/ +public abstract class AggregateFunction implements SqlFunction { + + /** + As for the xStep() argument of the C API's + sqlite3_create_function(). If this function throws, the + exception is reported via sqlite3_result_error(). + */ + public abstract void xStep(SqlFunction.Arguments args); + + /** + As for the xFinal() argument of the C API's + sqlite3_create_function(). If this function throws, it is + translated into sqlite3_result_error(). + + Note that the passed-in object will not actually contain any + arguments for xFinal() but will contain the context object needed + for setting the call's result or error state. + */ + public abstract void xFinal(SqlFunction.Arguments args); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. + */ + public void xDestroy() {} + + /** Per-invocation state for the UDF. */ + private final SqlFunction.PerContextState map = + new SqlFunction.PerContextState<>(); + + /** + To be called from the implementation's xStep() method, as well + as the xValue() and xInverse() methods of the {@link WindowFunction} + subclass, to fetch the current per-call UDF state. On the + first call to this method for any given sqlite3_context + argument, the context is set to the given initial value. On all other + calls, the 2nd argument is ignored. + + @see SQLFunction.PerContextState#getAggregateState + */ + protected final ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){ + return map.getAggregateState(args, initialValue); + } + + /** + To be called from the implementation's xFinal() method to fetch + the final state of the UDF and remove its mapping. + + see SQLFunction.PerContextState#takeAggregateState + */ + protected final T takeAggregateState(SqlFunction.Arguments args){ + return map.takeAggregateState(args); + } + +} diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java new file mode 100644 index 000000000..067a6983e --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java @@ -0,0 +1,37 @@ +/* +** 2023-10-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 is part of the wrapper1 interface for sqlite3. +*/ +package org.sqlite.jni.wrapper1; +import org.sqlite.jni.capi.CApi; +import org.sqlite.jni.annotation.*; +import org.sqlite.jni.capi.sqlite3_context; +import org.sqlite.jni.capi.sqlite3_value; + +/** + The SqlFunction type for scalar SQL functions. +*/ +public abstract class ScalarFunction implements SqlFunction { + /** + As for the xFunc() argument of the C API's + sqlite3_create_function(). If this function throws, it is + translated into an sqlite3_result_error(). + */ + public abstract void xFunc(SqlFunction.Arguments args); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. This default implementation does nothing. + */ + public void xDestroy() {} + +} diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java new file mode 100644 index 000000000..d6acda5aa --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java @@ -0,0 +1,301 @@ +/* +** 2023-10-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 is part of the wrapper1 interface for sqlite3. +*/ +package org.sqlite.jni.wrapper1; +import org.sqlite.jni.capi.CApi; +import org.sqlite.jni.capi.sqlite3_context; +import org.sqlite.jni.capi.sqlite3_value; + +/** + Base marker interface for SQLite's three types of User-Defined SQL + Functions (UDFs): Scalar, Aggregate, and Window functions. +*/ +public interface SqlFunction { + + /** + The Arguments type is an abstraction on top of the lower-level + UDF function argument types. It provides _most_ of the functionality + of the lower-level interface, insofar as possible without "leaking" + those types into this API. + */ + public final static class Arguments implements Iterable{ + private final sqlite3_context cx; + private final sqlite3_value args[]; + public final int length; + + /** + Must be passed the context and arguments for the UDF call this + object is wrapping. Intended to be used by internal proxy + classes which "convert" the lower-level interface into this + package's higher-level interface, e.g. ScalarAdapter and + AggregateAdapter. + + Passing null for the args is equivalent to passing a length-0 + array. + */ + Arguments(sqlite3_context cx, sqlite3_value args[]){ + this.cx = cx; + this.args = args==null ? new sqlite3_value[0] : args;; + this.length = this.args.length; + } + + /** + Wrapper for a single SqlFunction argument. Primarily intended + for use with the Arguments class's Iterable interface. + */ + public final static class Arg { + private final Arguments a; + private final int ndx; + /* Only for use by the Arguments class. */ + private Arg(Arguments a, int ndx){ + this.a = a; + this.ndx = ndx; + } + /** Returns this argument's index in its parent argument list. */ + public int getIndex(){return ndx;} + public int getInt(){return a.getInt(ndx);} + public long getInt64(){return a.getInt64(ndx);} + public double getDouble(){return a.getDouble(ndx);} + public byte[] getBlob(){return a.getBlob(ndx);} + public byte[] getText(){return a.getText(ndx);} + public String getText16(){return a.getText16(ndx);} + public int getBytes(){return a.getBytes(ndx);} + public int getBytes16(){return a.getBytes16(ndx);} + public Object getObject(){return a.getObject(ndx);} + public T getObjectCasted(Class type){ return a.getObjectCasted(ndx, type); } + public int getType(){return a.getType(ndx);} + public Object getAuxData(){return a.getAuxData(ndx);} + public void setAuxData(Object o){a.setAuxData(ndx, o);} + } + + @Override + public java.util.Iterator iterator(){ + final Arg[] proxies = new Arg[args.length]; + for( int i = 0; i < args.length; ++i ){ + proxies[i] = new Arg(this, i); + } + return java.util.Arrays.stream(proxies).iterator(); + } + + /** + Returns the sqlite3_value at the given argument index or throws + an IllegalArgumentException exception if ndx is out of range. + */ + private sqlite3_value valueAt(int ndx){ + if(ndx<0 || ndx>=args.length){ + throw new IllegalArgumentException( + "SQL function argument index "+ndx+" is out of range." + ); + } + return args[ndx]; + } + + sqlite3_context getContext(){return cx;} + + public int getArgCount(){ return args.length; } + + public int getInt(int arg){return CApi.sqlite3_value_int(valueAt(arg));} + public long getInt64(int arg){return CApi.sqlite3_value_int64(valueAt(arg));} + public double getDouble(int arg){return CApi.sqlite3_value_double(valueAt(arg));} + public byte[] getBlob(int arg){return CApi.sqlite3_value_blob(valueAt(arg));} + public byte[] getText(int arg){return CApi.sqlite3_value_text(valueAt(arg));} + public String getText16(int arg){return CApi.sqlite3_value_text16(valueAt(arg));} + public int getBytes(int arg){return CApi.sqlite3_value_bytes(valueAt(arg));} + public int getBytes16(int arg){return CApi.sqlite3_value_bytes16(valueAt(arg));} + public Object getObject(int arg){return CApi.sqlite3_value_java_object(valueAt(arg));} + public T getObjectCasted(int arg, Class type){ + return CApi.sqlite3_value_java_casted(valueAt(arg), type); + } + + public int getType(int arg){return CApi.sqlite3_value_type(valueAt(arg));} + public int getSubtype(int arg){return CApi.sqlite3_value_subtype(valueAt(arg));} + public int getNumericType(int arg){return CApi.sqlite3_value_numeric_type(valueAt(arg));} + public int getNoChange(int arg){return CApi.sqlite3_value_nochange(valueAt(arg));} + public boolean getFromBind(int arg){return CApi.sqlite3_value_frombind(valueAt(arg));} + public int getEncoding(int arg){return CApi.sqlite3_value_encoding(valueAt(arg));} + + public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); } + public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); } + public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); } + public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);} + public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);} + public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);} + public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);} + public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);} + public void resultNull(){CApi.sqlite3_result_null(cx);} + public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));} + public void resultZeroBlob(long n){ + // Throw on error? If n is too big, + // sqlite3_result_error_toobig() is automatically called. + CApi.sqlite3_result_zeroblob64(cx, n); + } + + public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);} + public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);} + public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);} + public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);} + public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);} + + public void setAuxData(int arg, Object o){ + /* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html + + The value of the N parameter to these interfaces should be + non-negative. Future enhancements may make use of negative N + values to define new kinds of function caching behavior. + */ + valueAt(arg); + CApi.sqlite3_set_auxdata(cx, arg, o); + } + + public Object getAuxData(int arg){ + valueAt(arg); + return CApi.sqlite3_get_auxdata(cx, arg); + } + } + + /** + PerContextState assists aggregate and window functions in + managing their accumulator state across calls to the UDF's + callbacks. + +

        T must be of a type which can be legally stored as a value in + java.util.HashMap. + +

        If a given aggregate or window function is called multiple times + in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., + then the clients need some way of knowing which call is which so + that they can map their state between their various UDF callbacks + and reset it via xFinal(). This class takes care of such + mappings. + +

        This class works by mapping + sqlite3_context.getAggregateContext() to a single piece of + state, of a client-defined type (the T part of this class), which + persists across a "matching set" of the UDF's callbacks. + +

        This class is a helper providing commonly-needed functionality + - it is not required for use with aggregate or window functions. + Client UDFs are free to perform such mappings using custom + approaches. The provided {@link AggregateFunction} and {@link + WindowFunction} classes use this. + */ + public static final class PerContextState { + private final java.util.Map> map + = new java.util.HashMap<>(); + + /** + Should be called from a UDF's xStep(), xValue(), and xInverse() + methods, passing it that method's first argument and an initial + value for the persistent state. If there is currently no + mapping for the given context within the map, one is created + using the given initial value, else the existing one is used + and the 2nd argument is ignored. It returns a ValueHolder + which can be used to modify that state directly without + requiring that the client update the underlying map's entry. + +

        The caller is obligated to eventually call + takeAggregateState() to clear the mapping. + */ + public ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){ + final Long key = args.getContext().getAggregateContext(true); + ValueHolder rc = null==key ? null : map.get(key); + if( null==rc ){ + map.put(key, rc = new ValueHolder<>(initialValue)); + } + return rc; + } + + /** + Should be called from a UDF's xFinal() method and passed that + method's first argument. This function removes the value + associated with with the arguments' aggregate context from the + map and returns it, returning null if no other UDF method has + been called to set up such a mapping. The latter condition will + be the case if a UDF is used in a statement which has no result + rows. + */ + public T takeAggregateState(SqlFunction.Arguments args){ + final ValueHolder h = map.remove(args.getContext().getAggregateContext(false)); + return null==h ? null : h.value; + } + } + + /** + Internal-use adapter for wrapping this package's ScalarFunction + for use with the org.sqlite.jni.capi.ScalarFunction interface. + */ + static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction { + final ScalarFunction impl; + ScalarAdapter(ScalarFunction impl){ + this.impl = impl; + } + /** + Proxies this.impl.xFunc(), adapting the call arguments to that + function's signature. If the proxy throws, it's translated to + sqlite_result_error() with the exception's message. + */ + public void xFunc(sqlite3_context cx, sqlite3_value[] args){ + try{ + impl.xFunc( new SqlFunction.Arguments(cx, args) ); + }catch(Exception e){ + CApi.sqlite3_result_error(cx, e); + } + } + + public void xDestroy(){ + impl.xDestroy(); + } + } + + /** + Internal-use adapter for wrapping this package's AggregateFunction + for use with the org.sqlite.jni.capi.AggregateFunction interface. + */ + static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction { + final AggregateFunction impl; + AggregateAdapter(AggregateFunction impl){ + this.impl = impl; + } + + /** + Proxies this.impl.xStep(), adapting the call arguments to that + function's signature. If the proxied function throws, it is + translated to sqlite_result_error() with the exception's + message. + */ + public void xStep(sqlite3_context cx, sqlite3_value[] args){ + try{ + impl.xStep( new SqlFunction.Arguments(cx, args) ); + }catch(Exception e){ + CApi.sqlite3_result_error(cx, e); + } + } + + /** + As for the xFinal() argument of the C API's sqlite3_create_function(). + If the proxied function throws, it is translated into a sqlite3_result_error(). + */ + public void xFinal(sqlite3_context cx){ + try{ + impl.xFinal( new SqlFunction.Arguments(cx, null) ); + }catch(Exception e){ + CApi.sqlite3_result_error(cx, e); + } + } + + public void xDestroy(){ + impl.xDestroy(); + } + } + +} diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java new file mode 100644 index 000000000..bcf97b239 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java @@ -0,0 +1,218 @@ +/* +** 2023-10-09 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the wrapper1 interface for sqlite3. +*/ +package org.sqlite.jni.wrapper1; +import java.nio.charset.StandardCharsets; +import static org.sqlite.jni.capi.CApi.*; +import org.sqlite.jni.capi.CApi; +import org.sqlite.jni.capi.sqlite3; +import org.sqlite.jni.capi.sqlite3_stmt; +import org.sqlite.jni.capi.OutputPointer; + +/** + This class represents a database connection, analog to the C-side + sqlite3 class but with added argument validation, exceptions, and + similar "smoothing of sharp edges" to make the API safe to use from + Java. It also acts as a namespace for other types for which + individual instances are tied to a specific database connection. +*/ +public final class Sqlite implements AutoCloseable { + private sqlite3 db; + + //! Used only by the open() factory functions. + private Sqlite(sqlite3 db){ + this.db = db; + } + + /** + Returns a newly-opened db connection or throws SqliteException if + opening fails. All arguments are as documented for + sqlite3_open_v2(). + + Design question: do we want static factory functions or should + this be reformulated as a constructor? + */ + public static Sqlite open(String filename, int flags, String vfsName){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + final int rc = sqlite3_open_v2(filename, out, flags, vfsName); + final sqlite3 n = out.take(); + if( 0!=rc ){ + if( null==n ) throw new SqliteException(rc); + final SqliteException ex = new SqliteException(n); + n.close(); + throw ex; + } + return new Sqlite(n); + } + + public static Sqlite open(String filename, int flags){ + return open(filename, flags, null); + } + + public static Sqlite open(String filename){ + return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null); + } + + @Override public void close(){ + if(null!=this.db){ + this.db.close(); + this.db = null; + } + } + + /** + Returns this object's underlying native db handle, or null if + this instance has been closed. This is very specifically not + public. + */ + sqlite3 nativeHandle(){ return this.db; } + + private sqlite3 affirmOpen(){ + if( null==db || 0==db.getNativePointer() ){ + throw new IllegalArgumentException("This database instance is closed."); + } + return this.db; + } + + // private byte[] stringToUtf8(String s){ + // return s==null ? null : s.getBytes(StandardCharsets.UTF_8); + // } + + private void affirmRcOk(int rc){ + if( 0!=rc ){ + throw new SqliteException(db); + } + } + + /** + Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to + create new instances. + */ + public final class Stmt implements AutoCloseable { + private Sqlite _db = null; + private sqlite3_stmt stmt = null; + /** Only called by the prepare() factory functions. */ + Stmt(Sqlite db, sqlite3_stmt stmt){ + this._db = db; + this.stmt = stmt; + } + + sqlite3_stmt nativeHandle(){ + return stmt; + } + + private sqlite3_stmt affirmOpen(){ + if( null==stmt || 0==stmt.getNativePointer() ){ + throw new IllegalArgumentException("This Stmt has been finalized."); + } + return stmt; + } + + /** + Corresponds to sqlite3_finalize(), but we cannot override the + name finalize() here because this one requires a different + signature. It does not throw on error here because "destructors + do not throw." If it returns non-0, the object is still + finalized. + */ + public int finalizeStmt(){ + int rc = 0; + if( null!=stmt ){ + sqlite3_finalize(stmt); + stmt = null; + } + return rc; + } + + @Override public void close(){ + finalizeStmt(); + } + + /** + Throws if rc is any value other than 0, SQLITE_ROW, or + SQLITE_DONE, else returns rc. + */ + private int checkRc(int rc){ + switch(rc){ + case 0: + case SQLITE_ROW: + case SQLITE_DONE: return rc; + default: + throw new SqliteException(this); + } + } + + /** + Works like sqlite3_step() but throws SqliteException for any + result other than 0, SQLITE_ROW, or SQLITE_DONE. + */ + public int step(){ + return checkRc(sqlite3_step(affirmOpen())); + } + + public Sqlite db(){ return this._db; } + + /** + Works like sqlite3_reset() but throws on error. + */ + public void reset(){ + checkRc(sqlite3_reset(affirmOpen())); + } + + public void clearBindings(){ + sqlite3_clear_bindings( affirmOpen() ); + } + } + + + /** + prepare() TODOs include: + + - overloads taking byte[] and ByteBuffer. + + - multi-statement processing, like CApi.sqlite3_prepare_multi() + but using a callback specific to the higher-level Stmt class + rather than the sqlite3_stmt class. + */ + public Stmt prepare(String sql, int prepFlags){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + final int rc = sqlite3_prepare_v3(affirmOpen(), sql, prepFlags, out); + affirmRcOk(rc); + return new Stmt(this, out.take()); + } + + public Stmt prepare(String sql){ + return prepare(sql, 0); + } + + public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f ){ + int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep, + new SqlFunction.ScalarAdapter(f)); + if( 0!=rc ) throw new SqliteException(db); + } + + public void createFunction(String name, int nArg, ScalarFunction f){ + this.createFunction(name, nArg, CApi.SQLITE_UTF8, f); + } + + public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f ){ + int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep, + new SqlFunction.AggregateAdapter(f)); + if( 0!=rc ) throw new SqliteException(db); + } + + public void createFunction(String name, int nArg, AggregateFunction f){ + this.createFunction(name, nArg, CApi.SQLITE_UTF8, f); + } + +} diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java new file mode 100644 index 000000000..111f004db --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java @@ -0,0 +1,82 @@ +/* +** 2023-10-09 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the wrapper1 interface for sqlite3. +*/ +package org.sqlite.jni.wrapper1; +import static org.sqlite.jni.capi.CApi.*; +import org.sqlite.jni.capi.sqlite3; + +/** + A wrapper for communicating C-level (sqlite3*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java + and C via JNI. +*/ +public final class SqliteException extends java.lang.RuntimeException { + int errCode = SQLITE_ERROR; + int xerrCode = SQLITE_ERROR; + int errOffset = -1; + int sysErrno = 0; + + /** + Records the given error string and uses SQLITE_ERROR for both the + error code and extended error code. + */ + public SqliteException(String msg){ + super(msg); + } + + /** + Uses sqlite3_errstr(sqlite3ResultCode) for the error string and + sets both the error code and extended error code to the given + value. + */ + public SqliteException(int sqlite3ResultCode){ + super(sqlite3_errstr(sqlite3ResultCode)); + errCode = xerrCode = sqlite3ResultCode; + } + + /** + Records the current error state of db (which must not be null and + must refer to an opened db object). Note that this does NOT close + the db. + + Design note: closing the db on error is likely only useful during + a failed db-open operation, and the place(s) where that can + happen are inside this library, not client-level code. + */ + SqliteException(sqlite3 db){ + super(sqlite3_errmsg(db)); + errCode = sqlite3_errcode(db); + xerrCode = sqlite3_extended_errcode(db); + errOffset = sqlite3_error_offset(db); + sysErrno = sqlite3_system_errno(db); + } + + /** + Records the current error state of db (which must not be null and + must refer to an open database). + */ + public SqliteException(Sqlite db){ + this(db.nativeHandle()); + } + + public SqliteException(Sqlite.Stmt stmt){ + this( stmt.db() ); + } + + public int errcode(){ return errCode; } + public int extendedErrcode(){ return xerrCode; } + public int errorOffset(){ return errOffset; } + public int systemErrno(){ return sysErrno; } + +} diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java new file mode 100644 index 000000000..f5fd5f84e --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java @@ -0,0 +1,584 @@ +/* +** 2023-10-09 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni.wrapper1; +//import static org.sqlite.jni.capi.CApi.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.sqlite.jni.capi.*; + +/** + An annotation for Tester2 tests which we do not want to run in + reflection-driven test mode because either they are not suitable + for multi-threaded threaded mode or we have to control their execution + order. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface ManualTest{} +/** + Annotation for Tester2 tests which mark those which must be skipped + in multi-threaded mode. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface SingleThreadOnly{} + +public class Tester2 implements Runnable { + //! True when running in multi-threaded mode. + private static boolean mtMode = false; + //! True to sleep briefly between tests. + private static boolean takeNaps = false; + //! True to shuffle the order of the tests. + private static boolean shuffle = false; + //! True to dump the list of to-run tests to stdout. + private static boolean listRunTests = false; + //! True to squelch all out() and outln() output. + private static boolean quietMode = false; + //! Total number of runTests() calls. + private static int nTestRuns = 0; + //! List of test*() methods to run. + private static List testMethods = null; + //! List of exceptions collected by run() + private static List listErrors = new ArrayList<>(); + private static final class Metrics { + //! Number of times createNewDb() (or equivalent) is invoked. + volatile int dbOpen = 0; + } + + //! Instance ID. + private Integer tId; + + Tester2(Integer id){ + tId = id; + } + + static final Metrics metrics = new Metrics(); + + public static synchronized void outln(){ + if( !quietMode ){ + System.out.println(""); + } + } + + public static synchronized void outPrefix(){ + if( !quietMode ){ + System.out.print(Thread.currentThread().getName()+": "); + } + } + + public static synchronized void outln(Object val){ + if( !quietMode ){ + outPrefix(); + System.out.println(val); + } + } + + public static synchronized void out(Object val){ + if( !quietMode ){ + System.out.print(val); + } + } + + @SuppressWarnings("unchecked") + public static synchronized void out(Object... vals){ + if( !quietMode ){ + outPrefix(); + for(Object v : vals) out(v); + } + } + + @SuppressWarnings("unchecked") + public static synchronized void outln(Object... vals){ + if( !quietMode ){ + out(vals); out("\n"); + } + } + + static volatile int affirmCount = 0; + public static synchronized int affirm(Boolean v, String comment){ + ++affirmCount; + if( false ) assert( v /* prefer assert over exception if it's enabled because + the JNI layer sometimes has to suppress exceptions, + so they might be squelched on their way back to the + top. */); + if( !v ) throw new RuntimeException(comment); + return affirmCount; + } + + public static void affirm(Boolean v){ + affirm(v, "Affirmation failed."); + } + + + public static void execSql(Sqlite db, String[] sql){ + execSql(db, String.join("", sql)); + } + + public static int execSql(Sqlite dbw, boolean throwOnError, String sql){ + final sqlite3 db = dbw.nativeHandle(); + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + if(throwOnError) affirm(0 == rc); + else if( 0!=rc ) break; + pos = oTail.value; + stmt = outStmt.take(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + affirm(0 != stmt.getNativePointer()); + while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(stmt)) ){ + } + CApi.sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + if(0!=rc && CApi.SQLITE_ROW!=rc && CApi.SQLITE_DONE!=rc){ + break; + } + } + CApi.sqlite3_finalize(stmt); + if(CApi.SQLITE_ROW==rc || CApi.SQLITE_DONE==rc) rc = 0; + if( 0!=rc && throwOnError){ + throw new SqliteException(db); + } + return rc; + } + + static void execSql(Sqlite db, String sql){ + execSql(db, true, sql); + } + + @SingleThreadOnly /* because it's thread-agnostic */ + private void test1(){ + affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER); + } + + /* Copy/paste/rename this to add new tests. */ + private void _testTemplate(){ + //final sqlite3 db = createNewDb(); + //sqlite3_stmt stmt = prepare(db,"SELECT 1"); + //sqlite3_finalize(stmt); + //sqlite3_close_v2(db); + } + + private void nap() throws InterruptedException { + if( takeNaps ){ + Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0); + } + } + + Sqlite openDb(String name){ + final Sqlite db = Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE| + CApi.SQLITE_OPEN_CREATE| + CApi.SQLITE_OPEN_EXRESCODE); + ++metrics.dbOpen; + return db; + } + + Sqlite openDb(){ return openDb(":memory:"); } + + void testOpenDb1(){ + Sqlite db = openDb(); + affirm( 0!=db.nativeHandle().getNativePointer() ); + db.close(); + affirm( null==db.nativeHandle() ); + + SqliteException ex = null; + try { + db = openDb("/no/such/dir/.../probably"); + }catch(SqliteException e){ + ex = e; + } + affirm( ex!=null ); + affirm( ex.errcode() != 0 ); + affirm( ex.extendedErrcode() != 0 ); + affirm( ex.errorOffset() < 0 ); + // there's no reliable way to predict what ex.systemErrno() might be + } + + void testPrepare1(){ + try (Sqlite db = openDb()) { + Sqlite.Stmt stmt = db.prepare("SELECT 1"); + affirm( null!=stmt.nativeHandle() ); + affirm( CApi.SQLITE_ROW == stmt.step() ); + affirm( CApi.SQLITE_DONE == stmt.step() ); + stmt.reset(); + affirm( CApi.SQLITE_ROW == stmt.step() ); + affirm( CApi.SQLITE_DONE == stmt.step() ); + affirm( 0 == stmt.finalizeStmt() ); + affirm( null==stmt.nativeHandle() ); + + stmt = db.prepare("SELECT 1"); + affirm( CApi.SQLITE_ROW == stmt.step() ); + affirm( 0 == stmt.finalizeStmt() ) + /* getting a non-0 out of sqlite3_finalize() is tricky */; + affirm( null==stmt.nativeHandle() ); + } + } + + void testUdfScalar(){ + final ValueHolder xDestroyCalled = new ValueHolder<>(0); + try (Sqlite db = openDb()) { + execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)"); + final ValueHolder vh = new ValueHolder<>(0); + final ScalarFunction f = new ScalarFunction(){ + public void xFunc(SqlFunction.Arguments args){ + for( SqlFunction.Arguments.Arg arg : args ){ + vh.value += arg.getInt(); + } + } + public void xDestroy(){ + ++xDestroyCalled.value; + } + }; + db.createFunction("myfunc", -1, f); + execSql(db, "select myfunc(1,2,3)"); + affirm( 6 == vh.value ); + vh.value = 0; + execSql(db, "select myfunc(-1,-2,-3)"); + affirm( -6 == vh.value ); + affirm( 0 == xDestroyCalled.value ); + } + affirm( 1 == xDestroyCalled.value ); + } + + void testUdfAggregate(){ + final ValueHolder xDestroyCalled = new ValueHolder<>(0); + final ValueHolder vh = new ValueHolder<>(0); + try (Sqlite db = openDb()) { + execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)"); + final AggregateFunction f = new AggregateFunction(){ + public void xStep(SqlFunction.Arguments args){ + final ValueHolder agg = this.getAggregateState(args, 0); + for( SqlFunction.Arguments.Arg arg : args ){ + agg.value += arg.getInt(); + } + } + public void xFinal(SqlFunction.Arguments args){ + final Integer v = this.takeAggregateState(args); + if( null==v ) args.resultNull(); + else args.resultInt(v); + vh.value = v; + } + public void xDestroy(){ + ++xDestroyCalled.value; + } + }; + db.createFunction("myagg", -1, f); + execSql(db, "select myagg(a) from t"); + affirm( 6 == vh.value ); + affirm( 0 == xDestroyCalled.value ); + } + affirm( 1 == xDestroyCalled.value ); + } + + private void runTests(boolean fromThread) throws Exception { + List mlist = testMethods; + affirm( null!=mlist ); + if( shuffle ){ + mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); + java.util.Collections.shuffle(mlist); + } + if( listRunTests ){ + synchronized(this.getClass()){ + if( !fromThread ){ + out("Initial test"," list: "); + for(java.lang.reflect.Method m : testMethods){ + out(m.getName()+" "); + } + outln(); + outln("(That list excludes some which are hard-coded to run.)"); + } + out("Running"," tests: "); + for(java.lang.reflect.Method m : mlist){ + out(m.getName()+" "); + } + outln(); + } + } + for(java.lang.reflect.Method m : mlist){ + nap(); + try{ + m.invoke(this); + }catch(java.lang.reflect.InvocationTargetException e){ + outln("FAILURE: ",m.getName(),"(): ", e.getCause()); + throw e; + } + } + synchronized( this.getClass() ){ + ++nTestRuns; + } + } + + public void run() { + try { + runTests(0!=this.tId); + }catch(Exception e){ + synchronized( listErrors ){ + listErrors.add(e); + } + }finally{ + affirm( CApi.sqlite3_java_uncache_thread() ); + affirm( !CApi.sqlite3_java_uncache_thread() ); + } + } + + /** + Runs the basic sqlite3 JNI binding sanity-check suite. + + CLI flags: + + -q|-quiet: disables most test output. + + -t|-thread N: runs the tests in N threads + concurrently. Default=1. + + -r|-repeat N: repeats the tests in a loop N times, each one + consisting of the -thread value's threads. + + -shuffle: randomizes the order of most of the test functions. + + -naps: sleep small random intervals between tests in order to add + some chaos for cross-thread contention. + + -list-tests: outputs the list of tests being run, minus some + which are hard-coded. This is noisy in multi-threaded mode. + + -fail: forces an exception to be thrown during the test run. Use + with -shuffle to make its appearance unpredictable. + + -v: emit some developer-mode info at the end. + */ + public static void main(String[] args) throws Exception { + Integer nThread = 1; + boolean doSomethingForDev = false; + Integer nRepeat = 1; + boolean forceFail = false; + boolean sqlLog = false; + boolean configLog = false; + boolean squelchTestOutput = false; + for( int i = 0; i < args.length; ){ + String arg = args[i++]; + if(arg.startsWith("-")){ + arg = arg.replaceFirst("-+",""); + if(arg.equals("v")){ + doSomethingForDev = true; + //listBoundMethods(); + }else if(arg.equals("t") || arg.equals("thread")){ + nThread = Integer.parseInt(args[i++]); + }else if(arg.equals("r") || arg.equals("repeat")){ + nRepeat = Integer.parseInt(args[i++]); + }else if(arg.equals("shuffle")){ + shuffle = true; + }else if(arg.equals("list-tests")){ + listRunTests = true; + }else if(arg.equals("fail")){ + forceFail = true; + }else if(arg.equals("sqllog")){ + sqlLog = true; + }else if(arg.equals("configlog")){ + configLog = true; + }else if(arg.equals("naps")){ + takeNaps = true; + }else if(arg.equals("q") || arg.equals("quiet")){ + squelchTestOutput = true; + }else{ + throw new IllegalArgumentException("Unhandled flag:"+arg); + } + } + } + + if( sqlLog ){ + if( CApi.sqlite3_compileoption_used("ENABLE_SQLLOG") ){ + final ConfigSqllogCallback log = new ConfigSqllogCallback() { + @Override public void call(sqlite3 db, String msg, int op){ + switch(op){ + case 0: outln("Opening db: ",db); break; + case 1: outln("SQL ",db,": ",msg); break; + case 2: outln("Closing db: ",db); break; + } + } + }; + int rc = CApi.sqlite3_config( log ); + affirm( 0==rc ); + rc = CApi.sqlite3_config( (ConfigSqllogCallback)null ); + affirm( 0==rc ); + rc = CApi.sqlite3_config( log ); + affirm( 0==rc ); + }else{ + outln("WARNING: -sqllog is not active because library was built ", + "without SQLITE_ENABLE_SQLLOG."); + } + } + if( configLog ){ + final ConfigLogCallback log = new ConfigLogCallback() { + @Override public void call(int code, String msg){ + outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg); + }; + }; + int rc = CApi.sqlite3_config( log ); + affirm( 0==rc ); + rc = CApi.sqlite3_config( (ConfigLogCallback)null ); + affirm( 0==rc ); + rc = CApi.sqlite3_config( log ); + affirm( 0==rc ); + } + + quietMode = squelchTestOutput; + outln("If you just saw warning messages regarding CallStaticObjectMethod, ", + "you are very likely seeing the side effects of a known openjdk8 ", + "bug. It is unsightly but does not affect the library."); + + { + // Build list of tests to run from the methods named test*(). + testMethods = new ArrayList<>(); + int nSkipped = 0; + for(final java.lang.reflect.Method m : Tester2.class.getDeclaredMethods()){ + final String name = m.getName(); + if( name.equals("testFail") ){ + if( forceFail ){ + testMethods.add(m); + } + }else if( !m.isAnnotationPresent( ManualTest.class ) ){ + if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){ + if( 0==nSkipped++ ){ + out("Skipping tests in multi-thread mode:"); + } + out(" "+name+"()"); + }else if( name.startsWith("test") ){ + testMethods.add(m); + } + } + } + if( nSkipped>0 ) out("\n"); + } + + final long timeStart = System.currentTimeMillis(); + int nLoop = 0; + switch( CApi.sqlite3_threadsafe() ){ /* Sanity checking */ + case 0: + affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ), + "Could not switch to single-thread mode." ); + affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ), + "Could switch to multithread mode." ); + affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ), + "Could not switch to serialized threading mode." ); + outln("This is a single-threaded build. Not using threads."); + nThread = 1; + break; + case 1: + case 2: + affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ), + "Could not switch to single-thread mode." ); + affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ), + "Could not switch to multithread mode." ); + affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ), + "Could not switch to serialized threading mode." ); + break; + default: + affirm( false, "Unhandled SQLITE_THREADSAFE value." ); + } + outln("libversion_number: ", + CApi.sqlite3_libversion_number(),"\n", + CApi.sqlite3_libversion(),"\n",CApi.SQLITE_SOURCE_ID,"\n", + "SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe()); + final boolean showLoopCount = (nRepeat>1 && nThread>1); + if( showLoopCount ){ + outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); + } + if( takeNaps ) outln("Napping between tests is enabled."); + for( int n = 0; n < nRepeat; ++n ){ + ++nLoop; + if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop); + if( nThread<=1 ){ + new Tester2(0).runTests(false); + continue; + } + Tester2.mtMode = true; + final ExecutorService ex = Executors.newFixedThreadPool( nThread ); + for( int i = 0; i < nThread; ++i ){ + ex.submit( new Tester2(i), i ); + } + ex.shutdown(); + try{ + ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS); + ex.shutdownNow(); + }catch (InterruptedException ie){ + ex.shutdownNow(); + Thread.currentThread().interrupt(); + } + if( !listErrors.isEmpty() ){ + quietMode = false; + outln("TEST ERRORS:"); + Exception err = null; + for( Exception e : listErrors ){ + e.printStackTrace(); + if( null==err ) err = e; + } + if( null!=err ) throw err; + } + } + if( showLoopCount ) outln(); + quietMode = false; + + final long timeEnd = System.currentTimeMillis(); + outln("Tests done. Metrics across ",nTestRuns," total iteration(s):"); + outln("\tAssertions checked: ",affirmCount); + outln("\tDatabases opened: ",metrics.dbOpen); + if( doSomethingForDev ){ + CApi.sqlite3_jni_internal_details(); + } + affirm( 0==CApi.sqlite3_release_memory(1) ); + CApi.sqlite3_shutdown(); + int nMethods = 0; + int nNatives = 0; + int nCanonical = 0; + final java.lang.reflect.Method[] declaredMethods = + CApi.class.getDeclaredMethods(); + for(java.lang.reflect.Method m : declaredMethods){ + final int mod = m.getModifiers(); + if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + ++nMethods; + if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){ + ++nNatives; + } + } + } + } + outln("\tCApi.sqlite3_*() methods: "+ + nMethods+" total, with "+ + nNatives+" native, "+ + (nMethods - nNatives)+" Java" + ); + outln("\tTotal test time = " + +(timeEnd - timeStart)+"ms"); + } +} diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java b/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java new file mode 100644 index 000000000..009936a43 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java @@ -0,0 +1,25 @@ +/* +** 2023-10-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 contains a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni.wrapper1; + +/** + A helper class which simply holds a single value. Its primary use + is for communicating values out of anonymous classes, as doing so + requires a "final" reference. +*/ +public class ValueHolder { + public T value; + public ValueHolder(){} + public ValueHolder(T v){value = v;} +} diff --git a/ext/jni/src/tests/000-000-sanity.test b/ext/jni/src/tests/000-000-sanity.test index 2bfacb1ce..4ccbece31 100644 --- a/ext/jni/src/tests/000-000-sanity.test +++ b/ext/jni/src/tests/000-000-sanity.test @@ -6,7 +6,8 @@ ** xMODULE_NAME: module-name ** xREQUIRED_PROPERTIES: small fast reliable ** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS -** xREQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE +** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM +** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM ** */ --print starting up 😃 diff --git a/ext/jni/src/tests/900-001-fts.test b/ext/jni/src/tests/900-001-fts.test new file mode 100644 index 000000000..65285e86b --- /dev/null +++ b/ext/jni/src/tests/900-001-fts.test @@ -0,0 +1,12 @@ +/* +** SCRIPT_MODULE_NAME: fts5-sanity-checks +** xREQUIRED_PROPERTIES: FTS5 +** +*/ + +--testcase 1.0 +CREATE VIRTUAL TABLE email USING fts5(sender, title, body); +insert into email values('fred','Help!','Dear Sir...'); +insert into email values('barney','Assistance','Dear Madam...'); +select * from email where email match 'assistance'; +--result barney Assistance {Dear Madam...} diff --git a/ext/lsm1/lsm_vtab.c b/ext/lsm1/lsm_vtab.c index bb1460297..8c21923e1 100644 --- a/ext/lsm1/lsm_vtab.c +++ b/ext/lsm1/lsm_vtab.c @@ -1061,6 +1061,11 @@ static sqlite3_module lsm1Module = { lsm1Rollback, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; diff --git a/ext/misc/amatch.c b/ext/misc/amatch.c index bafa43283..dd9bee53d 100644 --- a/ext/misc/amatch.c +++ b/ext/misc/amatch.c @@ -1475,7 +1475,8 @@ static sqlite3_module amatchModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/btreeinfo.c b/ext/misc/btreeinfo.c index 22f826813..02f8c0319 100644 --- a/ext/misc/btreeinfo.c +++ b/ext/misc/btreeinfo.c @@ -411,7 +411,8 @@ int sqlite3BinfoRegister(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; return sqlite3_create_module(db, "sqlite_btreeinfo", &binfo_module, 0); } diff --git a/ext/misc/carray.c b/ext/misc/carray.c index 709c894f2..b1caa98c3 100644 --- a/ext/misc/carray.c +++ b/ext/misc/carray.c @@ -409,6 +409,11 @@ static sqlite3_module carrayModule = { 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadow */ + 0 /* xIntegrity */ }; /* diff --git a/ext/misc/closure.c b/ext/misc/closure.c index db9b2b739..79a5a21d1 100644 --- a/ext/misc/closure.c +++ b/ext/misc/closure.c @@ -939,7 +939,8 @@ static sqlite3_module closureModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/completion.c b/ext/misc/completion.c index d9e7b8597..987595a3d 100644 --- a/ext/misc/completion.c +++ b/ext/misc/completion.c @@ -470,7 +470,8 @@ static sqlite3_module completionModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/csv.c b/ext/misc/csv.c index 870a0cf60..b38500f4b 100644 --- a/ext/misc/csv.c +++ b/ext/misc/csv.c @@ -897,6 +897,11 @@ static sqlite3_module CsvModule = { 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #ifdef SQLITE_TEST @@ -929,6 +934,11 @@ static sqlite3_module CsvModuleFauxWrite = { 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_TEST */ diff --git a/ext/misc/explain.c b/ext/misc/explain.c index 009519457..726af76b9 100644 --- a/ext/misc/explain.c +++ b/ext/misc/explain.c @@ -293,6 +293,7 @@ static sqlite3_module explainModule = { 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c index 7cdbd5968..70546adfc 100644 --- a/ext/misc/fileio.c +++ b/ext/misc/fileio.c @@ -983,6 +983,7 @@ static int fsdirRegister(sqlite3 *db){ 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); diff --git a/ext/misc/fossildelta.c b/ext/misc/fossildelta.c index 6a597e0d7..e638737d2 100644 --- a/ext/misc/fossildelta.c +++ b/ext/misc/fossildelta.c @@ -1058,7 +1058,8 @@ static sqlite3_module deltaparsevtabModule = { /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, - /* xShadowName */ 0 + /* xShadowName */ 0, + /* xIntegrity */ 0 }; diff --git a/ext/misc/fuzzer.c b/ext/misc/fuzzer.c index 65d9d8df6..92b7c0dae 100644 --- a/ext/misc/fuzzer.c +++ b/ext/misc/fuzzer.c @@ -1165,6 +1165,11 @@ static sqlite3_module fuzzerModule = { 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/memstat.c b/ext/misc/memstat.c index 800a86e7a..c56af9f29 100644 --- a/ext/misc/memstat.c +++ b/ext/misc/memstat.c @@ -396,6 +396,7 @@ static sqlite3_module memstatModule = { 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/mmapwarm.c b/ext/misc/mmapwarm.c index 5afa47bf7..851f8b0eb 100644 --- a/ext/misc/mmapwarm.c +++ b/ext/misc/mmapwarm.c @@ -38,7 +38,7 @@ int sqlite3_mmap_warm(sqlite3 *db, const char *zDb){ int rc = SQLITE_OK; char *zSql = 0; int pgsz = 0; - int nTotal = 0; + unsigned int nTotal = 0; if( 0==sqlite3_get_autocommit(db) ) return SQLITE_MISUSE; @@ -86,8 +86,8 @@ int sqlite3_mmap_warm(sqlite3 *db, const char *zDb){ rc = p->xFetch(pFd, pgsz*iPg, pgsz, (void**)&pMap); if( rc!=SQLITE_OK || pMap==0 ) break; - nTotal += pMap[0]; - nTotal += pMap[pgsz-1]; + nTotal += (unsigned int)pMap[0]; + nTotal += (unsigned int)pMap[pgsz-1]; rc = p->xUnfetch(pFd, pgsz*iPg, (void*)pMap); if( rc!=SQLITE_OK ) break; @@ -103,5 +103,6 @@ int sqlite3_mmap_warm(sqlite3 *db, const char *zDb){ if( rc==SQLITE_OK ) rc = rc2; } + (void)nTotal; return rc; } diff --git a/ext/misc/prefixes.c b/ext/misc/prefixes.c index 3f053b7f1..e6517e719 100644 --- a/ext/misc/prefixes.c +++ b/ext/misc/prefixes.c @@ -248,7 +248,8 @@ static sqlite3_module prefixesModule = { /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, - /* xShadowName */ 0 + /* xShadowName */ 0, + /* xIntegrity */ 0 }; /* diff --git a/ext/misc/qpvtab.c b/ext/misc/qpvtab.c index fb0c155a2..b7c2a0512 100644 --- a/ext/misc/qpvtab.c +++ b/ext/misc/qpvtab.c @@ -439,7 +439,8 @@ static sqlite3_module qpvtabModule = { /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, - /* xShadowName */ 0 + /* xShadowName */ 0, + /* xIntegrity */ 0 }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/series.c b/ext/misc/series.c index 3bd567b2e..abd6af7ad 100644 --- a/ext/misc/series.c +++ b/ext/misc/series.c @@ -557,7 +557,8 @@ static sqlite3_module seriesModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/spellfix.c b/ext/misc/spellfix.c index 3b78d9f07..a0c5aafd1 100644 --- a/ext/misc/spellfix.c +++ b/ext/misc/spellfix.c @@ -3009,6 +3009,11 @@ static sqlite3_module spellfix1Module = { 0, /* xRollback */ 0, /* xFindMethod */ spellfix1Rename, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; /* diff --git a/ext/misc/stmt.c b/ext/misc/stmt.c index 4819cbe67..cc1aaa849 100644 --- a/ext/misc/stmt.c +++ b/ext/misc/stmt.c @@ -314,6 +314,7 @@ static sqlite3_module stmtModule = { 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/templatevtab.c b/ext/misc/templatevtab.c index d7efa2b40..5865f5214 100644 --- a/ext/misc/templatevtab.c +++ b/ext/misc/templatevtab.c @@ -249,7 +249,8 @@ static sqlite3_module templatevtabModule = { /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, - /* xShadowName */ 0 + /* xShadowName */ 0, + /* xIntegrity */ 0 }; diff --git a/ext/misc/unionvtab.c b/ext/misc/unionvtab.c index 6ac7ca6e9..506ad5ddb 100644 --- a/ext/misc/unionvtab.c +++ b/ext/misc/unionvtab.c @@ -1351,7 +1351,8 @@ static int createUnionVtab(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc; diff --git a/ext/misc/vfsstat.c b/ext/misc/vfsstat.c index 83a7a3df7..ba22115ab 100644 --- a/ext/misc/vfsstat.c +++ b/ext/misc/vfsstat.c @@ -775,6 +775,11 @@ static sqlite3_module VfsStatModule = { 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; /* diff --git a/ext/misc/vtablog.c b/ext/misc/vtablog.c index 424b3457f..e414e7afa 100644 --- a/ext/misc/vtablog.c +++ b/ext/misc/vtablog.c @@ -493,6 +493,7 @@ static sqlite3_module vtablogModule = { 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ + 0 /* xIntegrity */ }; #ifdef _WIN32 diff --git a/ext/misc/wholenumber.c b/ext/misc/wholenumber.c index 03d6e6902..4c955925d 100644 --- a/ext/misc/wholenumber.c +++ b/ext/misc/wholenumber.c @@ -254,6 +254,11 @@ static sqlite3_module wholenumberModule = { 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index 4dbf80b19..db327809f 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -29,6 +29,7 @@ SQLITE_EXTENSION_INIT1 #include #include #include +#include #include @@ -2200,7 +2201,8 @@ static int zipfileRegister(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollback */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0); diff --git a/ext/recover/dbdata.c b/ext/recover/dbdata.c index e3bec33d8..25a6e9fd6 100644 --- a/ext/recover/dbdata.c +++ b/ext/recover/dbdata.c @@ -933,7 +933,8 @@ static int sqlite3DbdataRegister(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc = sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0); diff --git a/ext/repair/checkindex.c b/ext/repair/checkindex.c index 080a51530..5f6e646e4 100644 --- a/ext/repair/checkindex.c +++ b/ext/repair/checkindex.c @@ -907,6 +907,8 @@ static int ciInit(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; return sqlite3_create_module(db, "incremental_index_check", &cidx_module, 0); } diff --git a/ext/rtree/geopoly.c b/ext/rtree/geopoly.c index 640cb86b2..3e9c2a271 100644 --- a/ext/rtree/geopoly.c +++ b/ext/rtree/geopoly.c @@ -1252,24 +1252,28 @@ static int geopolyInit( (void)pAux; sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); /* Allocate the sqlite3_vtab structure */ nDb = strlen(argv[1]); nName = strlen(argv[2]); - pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); + pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8); if( !pRtree ){ return SQLITE_NOMEM; } - memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2); + memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8); pRtree->nBusy = 1; pRtree->base.pModule = &rtreeModule; pRtree->zDb = (char *)&pRtree[1]; pRtree->zName = &pRtree->zDb[nDb+1]; + pRtree->zNodeName = &pRtree->zName[nName+1]; pRtree->eCoordType = RTREE_COORD_REAL32; pRtree->nDim = 2; pRtree->nDim2 = 4; memcpy(pRtree->zDb, argv[1], nDb); memcpy(pRtree->zName, argv[2], nName); + memcpy(pRtree->zNodeName, argv[2], nName); + memcpy(&pRtree->zNodeName[nName], "_node", 6); /* Create/Connect to the underlying relational database schema. If @@ -1683,7 +1687,6 @@ static int geopolyUpdate( } if( rc==SQLITE_OK ){ int rc2; - pRtree->iReinsertHeight = -1; rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0); rc2 = nodeRelease(pRtree, pLeaf); if( rc==SQLITE_OK ){ @@ -1780,7 +1783,8 @@ static sqlite3_module geopolyModule = { rtreeSavepoint, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - rtreeShadowName /* xShadowName */ + rtreeShadowName, /* xShadowName */ + rtreeIntegrity /* xIntegrity */ }; static int sqlite3_geopoly_init(sqlite3 *db){ diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 4e85cc8ae..1705abd51 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -166,6 +166,7 @@ struct Rtree { int iDepth; /* Current depth of the r-tree structure */ char *zDb; /* Name of database containing r-tree table */ char *zName; /* Name of r-tree table */ + char *zNodeName; /* Name of the %_node table */ u32 nBusy; /* Current number of users of this structure */ i64 nRowEst; /* Estimated number of rows in this table */ u32 nCursor; /* Number of open cursors */ @@ -178,7 +179,6 @@ struct Rtree { ** headed by the node (leaf nodes have RtreeNode.iNode==0). */ RtreeNode *pDeleted; - int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */ /* Blob I/O on xxx_node */ sqlite3_blob *pNodeBlob; @@ -200,7 +200,7 @@ struct Rtree { /* Statement for writing to the "aux:" fields, if there are any */ sqlite3_stmt *pWriteAux; - RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ + RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ }; /* Possible values for Rtree.eCoordType: */ @@ -475,15 +475,20 @@ struct RtreeMatchArg { ** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined ** at run-time. */ -#ifndef SQLITE_BYTEORDER -# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ +#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */ +# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__ +# define SQLITE_BYTEORDER 4321 +# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ +# define SQLITE_BYTEORDER 1234 +# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1 +# define SQLITE_BYTEORDER 4321 +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64) -# define SQLITE_BYTEORDER 1234 -# elif defined(sparc) || defined(__ppc__) || \ - defined(__ARMEB__) || defined(__AARCH64EB__) -# define SQLITE_BYTEORDER 4321 +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__) +# define SQLITE_BYTEORDER 4321 # else # define SQLITE_BYTEORDER 0 # endif @@ -732,11 +737,9 @@ static int nodeAcquire( } } if( pRtree->pNodeBlob==0 ){ - char *zTab = sqlite3_mprintf("%s_node", pRtree->zName); - if( zTab==0 ) return SQLITE_NOMEM; - rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0, + rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName, + "data", iNode, 0, &pRtree->pNodeBlob); - sqlite3_free(zTab); } if( rc ){ nodeBlobReset(pRtree); @@ -2077,8 +2080,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ pIdxInfo->idxNum = 2; pIdxInfo->needToFreeIdxStr = 1; - if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){ - return SQLITE_NOMEM; + if( iIdx>0 ){ + pIdxInfo->idxStr = sqlite3_malloc( iIdx+1 ); + if( pIdxInfo->idxStr==0 ){ + return SQLITE_NOMEM; + } + memcpy(pIdxInfo->idxStr, zIdxStr, iIdx+1); } nRow = pRtree->nRowEst >> (iIdx/2); @@ -2157,31 +2164,22 @@ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ */ static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ int ii; - int isInt = (pRtree->eCoordType==RTREE_COORD_INT32); - for(ii=0; iinDim2; ii+=2){ - RtreeCoord *a1 = &p1->aCoord[ii]; - RtreeCoord *a2 = &p2->aCoord[ii]; - if( (!isInt && (a2[0].fa1[1].f)) - || ( isInt && (a2[0].ia1[1].i)) - ){ - return 0; + if( pRtree->eCoordType==RTREE_COORD_INT32 ){ + for(ii=0; iinDim2; ii+=2){ + RtreeCoord *a1 = &p1->aCoord[ii]; + RtreeCoord *a2 = &p2->aCoord[ii]; + if( a2[0].ia1[1].i ) return 0; + } + }else{ + for(ii=0; iinDim2; ii+=2){ + RtreeCoord *a1 = &p1->aCoord[ii]; + RtreeCoord *a2 = &p2->aCoord[ii]; + if( a2[0].fa1[1].f ) return 0; } } return 1; } -/* -** Return the amount cell p would grow by if it were unioned with pCell. -*/ -static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){ - RtreeDValue area; - RtreeCell cell; - memcpy(&cell, p, sizeof(RtreeCell)); - area = cellArea(pRtree, &cell); - cellUnion(pRtree, &cell, pCell); - return (cellArea(pRtree, &cell)-area); -} - static RtreeDValue cellOverlap( Rtree *pRtree, RtreeCell *p, @@ -2228,38 +2226,52 @@ static int ChooseLeaf( for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){ int iCell; sqlite3_int64 iBest = 0; - + int bFound = 0; RtreeDValue fMinGrowth = RTREE_ZERO; RtreeDValue fMinArea = RTREE_ZERO; - int nCell = NCELL(pNode); - RtreeCell cell; RtreeNode *pChild = 0; - RtreeCell *aCell = 0; - - /* Select the child node which will be enlarged the least if pCell - ** is inserted into it. Resolve ties by choosing the entry with - ** the smallest area. + /* First check to see if there is are any cells in pNode that completely + ** contains pCell. If two or more cells in pNode completely contain pCell + ** then pick the smallest. */ for(iCell=0; iCell1 ){ - int iLeft = 0; - int iRight = 0; - - int nLeft = nIdx/2; - int nRight = nIdx-nLeft; - int *aLeft = aIdx; - int *aRight = &aIdx[nLeft]; - - SortByDistance(aLeft, nLeft, aDistance, aSpare); - SortByDistance(aRight, nRight, aDistance, aSpare); - - memcpy(aSpare, aLeft, sizeof(int)*nLeft); - aLeft = aSpare; - - while( iLeftnDim; iDim++){ - aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]); - aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]); - } - } - for(iDim=0; iDimnDim; iDim++){ - aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2)); - } - - for(ii=0; iinDim; iDim++){ - RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) - - DCOORD(aCell[ii].aCoord[iDim*2])); - aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]); - } - } - - SortByDistance(aOrder, nCell, aDistance, aSpare); - nodeZero(pRtree, pNode); - - for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){ - RtreeCell *p = &aCell[aOrder[ii]]; - nodeInsertCell(pRtree, pNode, p); - if( p->iRowid==pCell->iRowid ){ - if( iHeight==0 ){ - rc = rowidWrite(pRtree, p->iRowid, pNode->iNode); - }else{ - rc = parentWrite(pRtree, p->iRowid, pNode->iNode); - } - } - } - if( rc==SQLITE_OK ){ - rc = fixBoundingBox(pRtree, pNode); - } - for(; rc==SQLITE_OK && iiiNode currently contains - ** the height of the sub-tree headed by the cell. - */ - RtreeNode *pInsert; - RtreeCell *p = &aCell[aOrder[ii]]; - rc = ChooseLeaf(pRtree, p, iHeight, &pInsert); - if( rc==SQLITE_OK ){ - int rc2; - rc = rtreeInsertCell(pRtree, pInsert, p, iHeight); - rc2 = nodeRelease(pRtree, pInsert); - if( rc==SQLITE_OK ){ - rc = rc2; - } - } - } - - sqlite3_free(aCell); - return rc; -} - + /* ** Insert cell pCell into node pNode. Node pNode is the head of a ** subtree iHeight high (leaf nodes have iHeight==0). @@ -3008,12 +2848,7 @@ static int rtreeInsertCell( } } if( nodeInsertCell(pRtree, pNode, pCell) ){ - if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){ - rc = SplitNode(pRtree, pNode, pCell, iHeight); - }else{ - pRtree->iReinsertHeight = iHeight; - rc = Reinsert(pRtree, pNode, pCell, iHeight); - } + rc = SplitNode(pRtree, pNode, pCell, iHeight); }else{ rc = AdjustTree(pRtree, pNode, pCell); if( ALWAYS(rc==SQLITE_OK) ){ @@ -3356,7 +3191,6 @@ static int rtreeUpdate( } if( rc==SQLITE_OK ){ int rc2; - pRtree->iReinsertHeight = -1; rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0); rc2 = nodeRelease(pRtree, pLeaf); if( rc==SQLITE_OK ){ @@ -3497,8 +3331,11 @@ static int rtreeShadowName(const char *zName){ return 0; } +/* Forward declaration */ +static int rtreeIntegrity(sqlite3_vtab*, const char*, const char*, int, char**); + static sqlite3_module rtreeModule = { - 3, /* iVersion */ + 4, /* iVersion */ rtreeCreate, /* xCreate - create a table */ rtreeConnect, /* xConnect - connect to an existing table */ rtreeBestIndex, /* xBestIndex - Determine search strategy */ @@ -3521,7 +3358,8 @@ static sqlite3_module rtreeModule = { rtreeSavepoint, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - rtreeShadowName /* xShadowName */ + rtreeShadowName, /* xShadowName */ + rtreeIntegrity /* xIntegrity */ }; static int rtreeSqlInit( @@ -3777,22 +3615,27 @@ static int rtreeInit( } sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + /* Allocate the sqlite3_vtab structure */ nDb = (int)strlen(argv[1]); nName = (int)strlen(argv[2]); - pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); + pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8); if( !pRtree ){ return SQLITE_NOMEM; } - memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2); + memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8); pRtree->nBusy = 1; pRtree->base.pModule = &rtreeModule; pRtree->zDb = (char *)&pRtree[1]; pRtree->zName = &pRtree->zDb[nDb+1]; + pRtree->zNodeName = &pRtree->zName[nName+1]; pRtree->eCoordType = (u8)eCoordType; memcpy(pRtree->zDb, argv[1], nDb); memcpy(pRtree->zName, argv[2], nName); + memcpy(pRtree->zNodeName, argv[2], nName); + memcpy(&pRtree->zNodeName[nName], "_node", 6); /* Create/Connect to the underlying relational database schema. If @@ -4289,7 +4132,6 @@ static int rtreeCheckTable( ){ RtreeCheck check; /* Common context for various routines */ sqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */ - int bEnd = 0; /* True if transaction should be closed */ int nAux = 0; /* Number of extra columns. */ /* Initialize the context object */ @@ -4298,14 +4140,6 @@ static int rtreeCheckTable( check.zDb = zDb; check.zTab = zTab; - /* If there is not already an open transaction, open one now. This is - ** to ensure that the queries run as part of this integrity-check operate - ** on a consistent snapshot. */ - if( sqlite3_get_autocommit(db) ){ - check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0); - bEnd = 1; - } - /* Find the number of auxiliary columns */ if( check.rc==SQLITE_OK ){ pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); @@ -4346,15 +4180,34 @@ static int rtreeCheckTable( sqlite3_finalize(check.aCheckMapping[0]); sqlite3_finalize(check.aCheckMapping[1]); - /* If one was opened, close the transaction */ - if( bEnd ){ - int rc = sqlite3_exec(db, "END", 0, 0, 0); - if( check.rc==SQLITE_OK ) check.rc = rc; - } *pzReport = check.zReport; return check.rc; } +/* +** Implementation of the xIntegrity method for Rtree. +*/ +static int rtreeIntegrity( + sqlite3_vtab *pVtab, /* The virtual table to check */ + const char *zSchema, /* Schema in which the virtual table lives */ + const char *zName, /* Name of the virtual table */ + int isQuick, /* True for a quick_check */ + char **pzErr /* Write results here */ +){ + Rtree *pRtree = (Rtree*)pVtab; + int rc; + assert( pzErr!=0 && *pzErr==0 ); + UNUSED_PARAMETER(zSchema); + UNUSED_PARAMETER(zName); + UNUSED_PARAMETER(isQuick); + rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr); + if( rc==SQLITE_OK && *pzErr ){ + *pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z", + pRtree->zDb, pRtree->zName, *pzErr); + } + return rc; +} + /* ** Usage: ** diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index 633d0a5d5..61664e152 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -774,14 +774,27 @@ do_execsql_test 21.1 { # Round-off error associated with using large integer constraints on # a rtree search. # +if {$tcl_platform(machine)!="i686" || $tcl_platform(os)!="Linux"} { + reset_db + do_execsql_test 22.0 { + CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 ); + INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800); + SELECT id FROM t1 WHERE x0 > 9223372036854775807; + } {123} + do_execsql_test 22.1 { + SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1; + } {123 1} +} + +# 2023-10-14 dbsqlfuzz --sql-fuzz find. rtreecheck() should not call +# BEGIN/COMMIT because that causes problems with statement transactions, +# and it is unnecessary. +# reset_db -do_execsql_test 22.0 { - CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 ); - INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800); - SELECT id FROM t1 WHERE x0 > 9223372036854775807; -} {123} -do_execsql_test 22.1 { - SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1; -} {123 1} +do_test 23.0 { + db eval {CREATE TABLE t1(a,b,c);} + catch {db eval {CREATE TABLE t2 AS SELECT rtreecheck('t1') AS y;}} + db eval {PRAGMA integrity_check;} +} {ok} finish_test diff --git a/ext/rtree/rtree8.test b/ext/rtree/rtree8.test index 12e75a685..51bd40416 100644 --- a/ext/rtree/rtree8.test +++ b/ext/rtree/rtree8.test @@ -75,7 +75,7 @@ do_test rtree8-1.2.2 { nested_select 1 } {51} # populate_t1 1500 do_rtree_integrity_test rtree8-1.3.0 t1 -do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164} +do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {183} do_test rtree8-1.3.2 { set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}] set stmt_list [list] diff --git a/ext/rtree/rtreeA.test b/ext/rtree/rtreeA.test index 301cd4fc6..0b52070c4 100644 --- a/ext/rtree/rtreeA.test +++ b/ext/rtree/rtreeA.test @@ -113,7 +113,7 @@ do_execsql_test rtreeA-1.1.1 { SELECT rtreecheck('main', 't1') } {{Node 1 missing from database Wrong number of entries in %_rowid table - expected 0, actual 500 -Wrong number of entries in %_parent table - expected 0, actual 23}} +Wrong number of entries in %_parent table - expected 0, actual 25}} do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {} do_corruption_tests rtreeA-1.2 -error "database disk image is malformed" { @@ -191,7 +191,7 @@ do_execsql_test rtreeA-3.3.3.4 { SELECT rtreecheck('main', 't1') } {{Rtree depth out of range (65535) Wrong number of entries in %_rowid table - expected 0, actual 499 -Wrong number of entries in %_parent table - expected 0, actual 23}} +Wrong number of entries in %_parent table - expected 0, actual 25}} #------------------------------------------------------------------------- # Set the "number of entries" field on some nodes incorrectly. diff --git a/ext/rtree/rtree_util.tcl b/ext/rtree/rtree_util.tcl index afa588e4e..5640baf4e 100644 --- a/ext/rtree/rtree_util.tcl +++ b/ext/rtree/rtree_util.tcl @@ -192,6 +192,6 @@ proc rtree_treedump {db zTab} { } proc do_rtree_integrity_test {tn tbl} { - uplevel [list do_execsql_test $tn "SELECT rtreecheck('$tbl')" ok] + uplevel [list do_execsql_test $tn.1 "SELECT rtreecheck('$tbl')" ok] + uplevel [list do_execsql_test $tn.2 "PRAGMA integrity_check" ok] } - diff --git a/ext/rtree/rtreecheck.test b/ext/rtree/rtreecheck.test index ff5397e1e..7a98f9bf4 100644 --- a/ext/rtree/rtreecheck.test +++ b/ext/rtree/rtreecheck.test @@ -78,6 +78,11 @@ do_execsql_test 2.3 { SELECT rtreecheck('r1') } {{Dimension 0 of cell 0 on node 1 is corrupt Dimension 1 of cell 3 on node 1 is corrupt}} +do_execsql_test 2.3b { + PRAGMA integrity_check; +} {{In RTree main.r1: +Dimension 0 of cell 0 on node 1 is corrupt +Dimension 1 of cell 3 on node 1 is corrupt}} setup_simple_db do_execsql_test 2.4 { @@ -85,12 +90,21 @@ do_execsql_test 2.4 { SELECT rtreecheck('r1') } {{Mapping (3 -> 1) missing from %_rowid table Wrong number of entries in %_rowid table - expected 5, actual 4}} +do_execsql_test 2.4b { + PRAGMA integrity_check +} {{In RTree main.r1: +Mapping (3 -> 1) missing from %_rowid table +Wrong number of entries in %_rowid table - expected 5, actual 4}} setup_simple_db do_execsql_test 2.5 { UPDATE r1_rowid SET nodeno=2 WHERE rowid=3; SELECT rtreecheck('r1') } {{Found (3 -> 2) in %_rowid table, expected (3 -> 1)}} +do_execsql_test 2.5b { + PRAGMA integrity_check +} {{In RTree main.r1: +Found (3 -> 2) in %_rowid table, expected (3 -> 1)}} reset_db do_execsql_test 3.0 { @@ -104,14 +118,16 @@ do_execsql_test 3.0 { INSERT INTO r1 VALUES(7, 5, 0x00000080); INSERT INTO r1 VALUES(8, 5, 0x40490fdb); INSERT INTO r1 VALUES(9, 0x7f800000, 0x7f900000); - SELECT rtreecheck('r1') -} {ok} + SELECT rtreecheck('r1'); + PRAGMA integrity_check; +} {ok ok} do_execsql_test 3.1 { CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2); INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5); - SELECT rtreecheck('r2') -} {ok} + SELECT rtreecheck('r2'); + PRAGMA integrity_check; +} {ok ok} sqlite3_db_config db DEFENSIVE 0 do_execsql_test 3.2 { @@ -125,6 +141,11 @@ do_execsql_test 3.3 { UPDATE r2_node SET data = X'00001234'; SELECT rtreecheck('r2')!='ok'; } {1} +do_execsql_test 3.4 { + PRAGMA integrity_check; +} {{In RTree main.r2: +Node 1 is too small for cell count of 4660 (4 bytes) +Wrong number of entries in %_rowid table - expected 0, actual 1}} do_execsql_test 4.0 { CREATE TABLE notanrtree(i); @@ -181,4 +202,3 @@ if {[permutation]=="inmemory_journal"} { } finish_test - diff --git a/ext/rtree/rtreedoc.test b/ext/rtree/rtreedoc.test index b64faa2e9..4e610db8a 100644 --- a/ext/rtree/rtreedoc.test +++ b/ext/rtree/rtreedoc.test @@ -601,11 +601,21 @@ do_execsql_test 2.5.2 { SELECT A.id FROM demo_index AS A, demo_index AS B WHERE A.maxX>=B.minX AND A.minX<=B.maxX AND A.maxY>=B.minY AND A.minY<=B.maxY - AND B.id=28269; + AND B.id=28269 ORDER BY +A.id; } { - 28293 28216 28322 28286 28269 - 28215 28336 28262 28291 28320 - 28313 28298 28287 + 28215 + 28216 + 28262 + 28269 + 28286 + 28287 + 28291 + 28293 + 28298 + 28313 + 28320 + 28322 + 28336 } # EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all @@ -1575,7 +1585,7 @@ execsql BEGIN do_test 3.6 { execsql { INSERT INTO rt2_parent VALUES(1000, 1000) } execsql { SELECT rtreecheck('rt2') } -} {{Wrong number of entries in %_parent table - expected 9, actual 10}} +} {{Wrong number of entries in %_parent table - expected 10, actual 11}} execsql ROLLBACK diff --git a/ext/rtree/rtreefuzz001.test b/ext/rtree/rtreefuzz001.test index 58fd179ab..c81c41da3 100644 --- a/ext/rtree/rtreefuzz001.test +++ b/ext/rtree/rtreefuzz001.test @@ -1043,7 +1043,7 @@ do_test rtreefuzz001-500 { | end crash-2e81f5dce5cbd4.db}] execsql { PRAGMA writable_schema = 1;} catchsql {UPDATE t1 SET ex= ex ISNULL} -} {1 {database disk image is malformed}} +} {0 {}} do_test rtreefuzz001-600 { sqlite3 db {} diff --git a/ext/session/session3.test b/ext/session/session3.test index ba316348e..ee955f137 100644 --- a/ext/session/session3.test +++ b/ext/session/session3.test @@ -135,8 +135,8 @@ do_test 2.2.2 { DROP TABLE t2; CREATE TABLE t2(a, b PRIMARY KEY, c, d); } - list [catch { S changeset } msg] $msg -} {1 SQLITE_SCHEMA} + catch { S changeset } +} {0} do_test 2.2.3 { S delete sqlite3session S db main @@ -167,8 +167,8 @@ do_test 2.2.4 { CREATE TABLE t2(a, b PRIMARY KEY, c, d); INSERT INTO t2 VALUES(4, 5, 6, 7); } - list [catch { S changeset } msg] $msg -} {1 SQLITE_SCHEMA} + catch { S changeset } +} {0} do_test 2.3 { S delete diff --git a/ext/session/sessionalter.test b/ext/session/sessionalter.test new file mode 100644 index 000000000..34424cf27 --- /dev/null +++ b/ext/session/sessionalter.test @@ -0,0 +1,238 @@ +# 2023 October 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 that the sessions module interacts well with +# the ALTER TABLE ADD COLUMN command. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl + +ifcapable !session {finish_test; return} +set testprefix sessionalter + + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); +} + +do_execsql_test -db db2 1.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c DEFAULT 1234); +} + +do_then_apply_sql { + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); +} + +do_execsql_test -db db2 1.2 { + SELECT * FROM t1 +} { + 1 one 1234 + 2 two 1234 +} + +do_then_apply_sql { + UPDATE t1 SET b='four' WHERE a=2; +} + +do_execsql_test -db db2 1.3 { + SELECT * FROM t1 +} { + 1 one 1234 + 2 four 1234 +} + +do_then_apply_sql { + DELETE FROM t1 WHERE a=1; +} + +do_execsql_test -db db2 1.4 { + SELECT * FROM t1 +} { + 2 four 1234 +} + +db2 close + +#-------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); +} + +do_test 2.1 { + sqlite3session S db main + S attach t1 + set {} {} +} {} +do_execsql_test 2.2 { + INSERT INTO t1 VALUES(1, 2); + ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcd'; + INSERT INTO t1 VALUES(2, 3, 4); +} +do_changeset_test 2.3 S { + {INSERT t1 0 X.. {} {i 1 i 2 t abcd}} + {INSERT t1 0 X.. {} {i 2 i 3 i 4}} +} + +do_iterator_test 2.4 {} { + DELETE FROM t1 WHERE a=2; + ALTER TABLE t1 ADD COLUMN d DEFAULT 'abcd'; + ALTER TABLE t1 ADD COLUMN e DEFAULT 5; + ALTER TABLE t1 ADD COLUMN f DEFAULT 7.2; + -- INSERT INTO t1 VALUES(9, 9, 9, 9); +} { + {DELETE t1 0 X..... {i 2 i 3 i 4 t abcd i 5 f 7.2} {}} +} + +#------------------------------------------------------------------------- +# Tests of the sqlite3changegroup_xxx() APIs. +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y); + CREATE TABLE t2(x PRIMARY KEY, y); + CREATE TABLE t3(x, y); + CREATE TABLE t4(y PRIMARY KEY, x) WITHOUT ROWID; + + INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6); + INSERT INTO t2 VALUES('one', 'two'), ('three', 'four'), ('five', 'six'); + INSERT INTO t3 VALUES(1, 2), (3, 4), (5, 6); + + INSERT INTO t4(x, y) VALUES(1, 2), (3, 4), (5, 6); +} + +db_save_and_close +foreach {tn sql1 at sql2} { + 1 { + INSERT INTO t1(x, y) VALUES(7, 8); + } { + ALTER TABLE t1 ADD COLUMN z DEFAULT 10; + } { + UPDATE t1 SET y=11 WHERE x=7; + } + + 2 { + UPDATE t2 SET y='two.two' WHERE x='one'; + DELETE FROM t2 WHERE x='five'; + INSERT INTO t2(x, y) VALUES('seven', 'eight'); + } { + ALTER TABLE t2 ADD COLUMN z; + ALTER TABLE t2 ADD COLUMN zz; + } { + } + + 3 { + DELETE FROM t2 WHERE x='five'; + } { + ALTER TABLE t2 ADD COLUMN z DEFAULT 'xyz'; + } { + } + + 4 { + UPDATE t2 SET y='two.two' WHERE x='three'; + } { + ALTER TABLE t2 ADD COLUMN z; + } { + UPDATE t2 SET z='abc' WHERE x='one'; + } + + 5* { + UPDATE t2 SET y='two.two' WHERE x='three'; + } { + ALTER TABLE t2 ADD COLUMN z DEFAULT 'defu1'; + } { + } + + 6* { + INSERT INTO t2(x, y) VALUES('nine', 'ten'); + } { + ALTER TABLE t2 ADD COLUMN z; + ALTER TABLE t2 ADD COLUMN a DEFAULT 'eelve'; + ALTER TABLE t2 ADD COLUMN b DEFAULT x'1234abcd'; + ALTER TABLE t2 ADD COLUMN c DEFAULT 4.2; + ALTER TABLE t2 ADD COLUMN d DEFAULT NULL; + } { + } + + 7 { + INSERT INTO t3(x, y) VALUES(7, 8); + UPDATE t3 SET y='fourteen' WHERE x=1; + DELETE FROM t3 WHERE x=3; + } { + ALTER TABLE t3 ADD COLUMN c; + } { + INSERT INTO t3(x, y, c) VALUES(9, 10, 11); + } + + 8 { + INSERT INTO t4(x, y) VALUES(7, 8); + UPDATE t4 SET y='fourteen' WHERE x=1; + DELETE FROM t4 WHERE x=3; + } { + ALTER TABLE t4 ADD COLUMN c; + } { + INSERT INTO t4(x, y, c) VALUES(9, 10, 11); + } +} { + foreach {tn2 cmd} { + 1 changeset_from_sql + 2 patchset_from_sql + } { + db_restore_and_reopen + + set C1 [$cmd $sql1] + execsql $at + set C2 [$cmd $sql2] + + sqlite3changegroup grp + grp schema db main + grp add $C1 + grp add $C2 + set T1 [grp output] + grp delete + + db_restore_and_reopen + execsql $at + set T2 [$cmd "$sql1 ; $sql2"] + + if {[string range $tn end end]!="*"} { + do_test 3.1.$tn.$tn2.1 { changeset_to_list $T1 } [changeset_to_list $T2] + set testname "$tn.$tn2" + } else { + set testname "[string range $tn 0 end-1].$tn2" + } + + db_restore_and_reopen + proc xConflict {args} { return "REPLACE" } + sqlite3changeset_apply_v2 db $T1 xConflict + set S1 [scksum db main] + + db_restore_and_reopen + sqlite3changeset_apply_v2 db $T2 xConflict + set S2 [scksum db main] + + # if { $tn==7 } { puts [changeset_to_list $T1] } + + do_test 3.1.$tn.2 { set S1 } $S2 + } +} + + +finish_test + diff --git a/ext/session/sessionfault3.test b/ext/session/sessionfault3.test new file mode 100644 index 000000000..af5a4cdb4 --- /dev/null +++ b/ext/session/sessionfault3.test @@ -0,0 +1,59 @@ +# 2016 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. +# +#*********************************************************************** +# +# The focus of this file is testing the session module. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} +set testprefix sessionfault3 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, PRIMARY KEY(a)); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES('five', 'six'); +} + +set C1 [changeset_from_sql { + INSERT INTO t1 VALUES('seven', 'eight'); + UPDATE t1 SET b=6 WHERE a='five'; + DELETE FROM t1 WHERE a=1; +}] + +do_execsql_test 1.1 { + ALTER TABLE t1 ADD COLUMN d DEFAULT 123; + ALTER TABLE t1 ADD COLUMN e DEFAULT 'string'; +} + +set C2 [changeset_from_sql { + UPDATE t1 SET e='new value' WHERE a='seven'; + INSERT INTO t1 VALUES(0, 0, 0, 0); +}] + +do_faultsim_test 1 -faults oom* -prep { + sqlite3changegroup G +} -body { + G schema db main + G add $::C1 + G add $::C2 + G output + set {} {} +} -test { + catch { G delete } + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} + +finish_test diff --git a/ext/session/sessionnoact.test b/ext/session/sessionnoact.test new file mode 100644 index 000000000..1274ecb14 --- /dev/null +++ b/ext/session/sessionnoact.test @@ -0,0 +1,110 @@ +# 2023 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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionnoact + +do_execsql_test 1.0 { + CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE); + INSERT INTO p1 VALUES(1, 1, 'one'); + INSERT INTO p1 VALUES(2, 2, 'two'); + INSERT INTO p1 VALUES(3, 3, 'three'); + INSERT INTO p1 VALUES(4, 4, 'four'); +} + +db_save + +set C [changeset_from_sql { + DELETE FROM p1 WHERE a=2; + UPDATE p1 SET c='six' WHERE a=3; + INSERT INTO p1 VALUES(5, 5, 'two'); + INSERT INTO p1 VALUES(6, 6, 'three'); +}] + +db_restore_and_reopen + +do_execsql_test 1.1 { + CREATE TABLE c1(x INTEGER PRIMARY KEY, y, + FOREIGN KEY(y) REFERENCES p1(c) ON DELETE CASCADE ON UPDATE SET NULL + ); + + INSERT INTO c1 VALUES(10, 'one'); + INSERT INTO c1 VALUES(20, 'two'); + INSERT INTO c1 VALUES(30, 'three'); + INSERT INTO c1 VALUES(40, 'four'); +} + +db_save + +do_execsql_test 1.2 { + PRAGMA foreign_keys = 1; +} + +set ::nConflict 0 +proc conflict {args} { + incr ::nConflict + return "OMIT" +} + +sqlite3changeset_apply_v2 db $C conflict + +do_execsql_test 1.3 { + SELECT * FROM c1 +} { + 10 one + 30 {} + 40 four +} + +db_restore_and_reopen + +do_execsql_test 1.4 { + PRAGMA foreign_keys = 1; +} + +do_execsql_test 1.5 { + UPDATE p1 SET c=12345 WHERE a = 45; +} + +sqlite3changeset_apply_v2 -noaction db $C conflict +do_execsql_test 1.6 { + SELECT * FROM c1 +} { + 10 one + 20 two + 30 three + 40 four +} + +do_execsql_test 1.7 { + PRAGMA foreign_keys = 1; + UPDATE p1 SET c = 'ten' WHERE c='two'; + SELECT * FROM c1; +} { + 10 one + 20 {} + 30 three + 40 four +} + +do_execsql_test 1.8 { + PRAGMA foreign_key_check +} + +finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 049154923..4f682a13d 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -119,6 +119,18 @@ struct sqlite3_changeset_iter { ** The data associated with each hash-table entry is a structure containing ** a subset of the initial values that the modified row contained at the ** start of the session. Or no initial values if the row was inserted. +** +** pDfltStmt: +** This is only used by the sqlite3changegroup_xxx() APIs, not by +** regular sqlite3_session objects. It is a SELECT statement that +** selects the default value for each table column. For example, +** if the table is +** +** CREATE TABLE xx(a DEFAULT 1, b, c DEFAULT 'abc') +** +** then this variable is the compiled version of: +** +** SELECT 1, NULL, 'abc' */ struct SessionTable { SessionTable *pNext; @@ -127,10 +139,12 @@ struct SessionTable { int bStat1; /* True if this is sqlite_stat1 */ int bRowid; /* True if this table uses rowid for PK */ const char **azCol; /* Column names */ + const char **azDflt; /* Default value expressions */ u8 *abPK; /* Array of primary key flags */ int nEntry; /* Total number of entries in hash table */ int nChange; /* Size of apChange[] array */ SessionChange **apChange; /* Hash table buckets */ + sqlite3_stmt *pDfltStmt; }; /* @@ -299,6 +313,7 @@ struct SessionTable { struct SessionChange { u8 op; /* One of UPDATE, DELETE, INSERT */ u8 bIndirect; /* True if this change is "indirect" */ + u16 nRecordField; /* Number of fields in aRecord[] */ int nMaxSize; /* Max size of eventual changeset record */ int nRecord; /* Number of bytes in buffer aRecord[] */ u8 *aRecord; /* Buffer containing old.* record */ @@ -324,7 +339,7 @@ static int sessionVarintLen(int iVal){ ** Read a varint value from aBuf[] into *piVal. Return the number of ** bytes read. */ -static int sessionVarintGet(u8 *aBuf, int *piVal){ +static int sessionVarintGet(const u8 *aBuf, int *piVal){ return getVarint32(aBuf, *piVal); } @@ -587,9 +602,11 @@ static int sessionPreupdateHash( ** Return the number of bytes of space occupied by the value (including ** the type byte). */ -static int sessionSerialLen(u8 *a){ - int e = *a; +static int sessionSerialLen(const u8 *a){ + int e; int n; + assert( a!=0 ); + e = *a; if( e==0 || e==0xFF ) return 1; if( e==SQLITE_NULL ) return 1; if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9; @@ -994,13 +1011,14 @@ static int sessionGrowHash( ** ** For example, if the table is declared as: ** -** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z)); +** CREATE TABLE tbl1(w, x DEFAULT 'abc', y, z, PRIMARY KEY(w, z)); ** -** Then the four output variables are populated as follows: +** Then the five output variables are populated as follows: ** ** *pnCol = 4 ** *pzTab = "tbl1" ** *pazCol = {"w", "x", "y", "z"} +** *pazDflt = {NULL, 'abc', NULL, NULL} ** *pabPK = {1, 0, 0, 1} ** ** All returned buffers are part of the same single allocation, which must @@ -1014,6 +1032,7 @@ static int sessionTableInfo( int *pnCol, /* OUT: number of columns */ const char **pzTab, /* OUT: Copy of zThis */ const char ***pazCol, /* OUT: Array of column names for table */ + const char ***pazDflt, /* OUT: Array of default value expressions */ u8 **pabPK, /* OUT: Array of booleans - true for PK col */ int *pbRowid /* OUT: True if only PK is a rowid */ ){ @@ -1026,11 +1045,18 @@ static int sessionTableInfo( int i; u8 *pAlloc = 0; char **azCol = 0; + char **azDflt = 0; u8 *abPK = 0; int bRowid = 0; /* Set to true to use rowid as PK */ assert( pazCol && pabPK ); + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + if( pazDflt ) *pazDflt = 0; + nThis = sqlite3Strlen30(zThis); if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){ rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0); @@ -1044,39 +1070,28 @@ static int sessionTableInfo( }else if( rc==SQLITE_ERROR ){ zPragma = sqlite3_mprintf(""); }else{ - *pazCol = 0; - *pabPK = 0; - *pnCol = 0; - if( pzTab ) *pzTab = 0; return rc; } }else{ zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); } if( !zPragma ){ - *pazCol = 0; - *pabPK = 0; - *pnCol = 0; - if( pzTab ) *pzTab = 0; return SQLITE_NOMEM; } rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); sqlite3_free(zPragma); if( rc!=SQLITE_OK ){ - *pazCol = 0; - *pabPK = 0; - *pnCol = 0; - if( pzTab ) *pzTab = 0; return rc; } nByte = nThis + 1; bRowid = (pbRowid!=0); while( SQLITE_ROW==sqlite3_step(pStmt) ){ - nByte += sqlite3_column_bytes(pStmt, 1); + nByte += sqlite3_column_bytes(pStmt, 1); /* name */ + nByte += sqlite3_column_bytes(pStmt, 4); /* dflt_value */ nDbCol++; - if( sqlite3_column_int(pStmt, 5) ) bRowid = 0; + if( sqlite3_column_int(pStmt, 5) ) bRowid = 0; /* pk */ } if( nDbCol==0 ) bRowid = 0; nDbCol += bRowid; @@ -1084,15 +1099,18 @@ static int sessionTableInfo( rc = sqlite3_reset(pStmt); if( rc==SQLITE_OK ){ - nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1); + nByte += nDbCol * (sizeof(const char *)*2 + sizeof(u8) + 1 + 1); pAlloc = sessionMalloc64(pSession, nByte); if( pAlloc==0 ){ rc = SQLITE_NOMEM; + }else{ + memset(pAlloc, 0, nByte); } } if( rc==SQLITE_OK ){ azCol = (char **)pAlloc; - pAlloc = (u8 *)&azCol[nDbCol]; + azDflt = (char**)&azCol[nDbCol]; + pAlloc = (u8 *)&azDflt[nDbCol]; abPK = (u8 *)pAlloc; pAlloc = &abPK[nDbCol]; if( pzTab ){ @@ -1112,11 +1130,21 @@ static int sessionTableInfo( } while( SQLITE_ROW==sqlite3_step(pStmt) ){ int nName = sqlite3_column_bytes(pStmt, 1); + int nDflt = sqlite3_column_bytes(pStmt, 4); const unsigned char *zName = sqlite3_column_text(pStmt, 1); + const unsigned char *zDflt = sqlite3_column_text(pStmt, 4); + if( zName==0 ) break; memcpy(pAlloc, zName, nName+1); azCol[i] = (char *)pAlloc; pAlloc += nName+1; + if( zDflt ){ + memcpy(pAlloc, zDflt, nDflt+1); + azDflt[i] = (char *)pAlloc; + pAlloc += nDflt+1; + }else{ + azDflt[i] = 0; + } abPK[i] = sqlite3_column_int(pStmt, 5); i++; } @@ -1127,14 +1155,11 @@ static int sessionTableInfo( ** free any allocation made. An error code will be returned in this case. */ if( rc==SQLITE_OK ){ - *pazCol = (const char **)azCol; + *pazCol = (const char**)azCol; + if( pazDflt ) *pazDflt = (const char**)azDflt; *pabPK = abPK; *pnCol = nDbCol; }else{ - *pazCol = 0; - *pabPK = 0; - *pnCol = 0; - if( pzTab ) *pzTab = 0; sessionFree(pSession, azCol); } if( pbRowid ) *pbRowid = bRowid; @@ -1143,10 +1168,9 @@ static int sessionTableInfo( } /* -** This function is only called from within a pre-update handler for a -** write to table pTab, part of session pSession. If this is the first -** write to this table, initalize the SessionTable.nCol, azCol[] and -** abPK[] arrays accordingly. +** This function is called to initialize the SessionTable.nCol, azCol[] +** abPK[] and azDflt[] members of SessionTable object pTab. If these +** fields are already initilialized, this function is a no-op. ** ** If an error occurs, an error code is stored in sqlite3_session.rc and ** non-zero returned. Or, if no error occurs but the table has no primary @@ -1154,15 +1178,22 @@ static int sessionTableInfo( ** indicate that updates on this table should be ignored. SessionTable.abPK ** is set to NULL in this case. */ -static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ +static int sessionInitTable( + sqlite3_session *pSession, /* Optional session handle */ + SessionTable *pTab, /* Table object to initialize */ + sqlite3 *db, /* Database handle to read schema from */ + const char *zDb /* Name of db - "main", "temp" etc. */ +){ + int rc = SQLITE_OK; + if( pTab->nCol==0 ){ u8 *abPK; assert( pTab->azCol==0 || pTab->abPK==0 ); - pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, - pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK, - (pSession->bImplicitPK ? &pTab->bRowid : 0) + rc = sessionTableInfo(pSession, db, zDb, + pTab->zName, &pTab->nCol, 0, &pTab->azCol, &pTab->azDflt, &abPK, + ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0) ); - if( pSession->rc==SQLITE_OK ){ + if( rc==SQLITE_OK ){ int i; for(i=0; inCol; i++){ if( abPK[i] ){ @@ -1174,14 +1205,321 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ pTab->bStat1 = 1; } - if( pSession->bEnableSize ){ + if( pSession && pSession->bEnableSize ){ pSession->nMaxChangesetSize += ( 1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1 ); } } } - return (pSession->rc || pTab->abPK==0); + + if( pSession ){ + pSession->rc = rc; + return (rc || pTab->abPK==0); + } + return rc; +} + +/* +** Re-initialize table object pTab. +*/ +static int sessionReinitTable(sqlite3_session *pSession, SessionTable *pTab){ + int nCol = 0; + const char **azCol = 0; + const char **azDflt = 0; + u8 *abPK = 0; + int bRowid = 0; + + assert( pSession->rc==SQLITE_OK ); + + pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, + pTab->zName, &nCol, 0, &azCol, &azDflt, &abPK, + (pSession->bImplicitPK ? &bRowid : 0) + ); + if( pSession->rc==SQLITE_OK ){ + if( pTab->nCol>nCol || pTab->bRowid!=bRowid ){ + pSession->rc = SQLITE_SCHEMA; + }else{ + int ii; + int nOldCol = pTab->nCol; + for(ii=0; iinCol ){ + if( pTab->abPK[ii]!=abPK[ii] ){ + pSession->rc = SQLITE_SCHEMA; + } + }else if( abPK[ii] ){ + pSession->rc = SQLITE_SCHEMA; + } + } + + if( pSession->rc==SQLITE_OK ){ + const char **a = pTab->azCol; + pTab->azCol = azCol; + pTab->nCol = nCol; + pTab->azDflt = azDflt; + pTab->abPK = abPK; + azCol = a; + } + if( pSession->bEnableSize ){ + pSession->nMaxChangesetSize += (nCol - nOldCol); + pSession->nMaxChangesetSize += sessionVarintLen(nCol); + pSession->nMaxChangesetSize -= sessionVarintLen(nOldCol); + } + } + } + + sqlite3_free((char*)azCol); + return pSession->rc; +} + +/* +** Session-change object (*pp) contains an old.* record with fewer than +** nCol fields. This function updates it with the default values for +** the missing fields. +*/ +static void sessionUpdateOneChange( + sqlite3_session *pSession, /* For memory accounting */ + int *pRc, /* IN/OUT: Error code */ + SessionChange **pp, /* IN/OUT: Change object to update */ + int nCol, /* Number of columns now in table */ + sqlite3_stmt *pDflt /* SELECT */ +){ + SessionChange *pOld = *pp; + + while( pOld->nRecordFieldnRecordField; + int eType = sqlite3_column_type(pDflt, iField); + switch( eType ){ + case SQLITE_NULL: + nIncr = 1; + break; + case SQLITE_INTEGER: + case SQLITE_FLOAT: + nIncr = 9; + break; + default: { + int n = sqlite3_column_bytes(pDflt, iField); + nIncr = 1 + sessionVarintLen(n) + n; + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + break; + } + } + + nByte = nIncr + (sizeof(SessionChange) + pOld->nRecord); + pNew = sessionMalloc64(pSession, nByte); + if( pNew==0 ){ + *pRc = SQLITE_NOMEM; + return; + }else{ + memcpy(pNew, pOld, sizeof(SessionChange)); + pNew->aRecord = (u8*)&pNew[1]; + memcpy(pNew->aRecord, pOld->aRecord, pOld->nRecord); + pNew->aRecord[pNew->nRecord++] = (u8)eType; + switch( eType ){ + case SQLITE_INTEGER: { + i64 iVal = sqlite3_column_int64(pDflt, iField); + sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); + pNew->nRecord += 8; + break; + } + + case SQLITE_FLOAT: { + double rVal = sqlite3_column_double(pDflt, iField); + i64 iVal = 0; + memcpy(&iVal, &rVal, sizeof(rVal)); + sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); + pNew->nRecord += 8; + break; + } + + case SQLITE_TEXT: { + int n = sqlite3_column_bytes(pDflt, iField); + const char *z = (const char*)sqlite3_column_text(pDflt, iField); + pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); + memcpy(&pNew->aRecord[pNew->nRecord], z, n); + pNew->nRecord += n; + break; + } + + case SQLITE_BLOB: { + int n = sqlite3_column_bytes(pDflt, iField); + const u8 *z = (const u8*)sqlite3_column_blob(pDflt, iField); + pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); + memcpy(&pNew->aRecord[pNew->nRecord], z, n); + pNew->nRecord += n; + break; + } + + default: + assert( eType==SQLITE_NULL ); + break; + } + + sessionFree(pSession, pOld); + *pp = pOld = pNew; + pNew->nRecordField++; + pNew->nMaxSize += nIncr; + if( pSession ){ + pSession->nMaxChangesetSize += nIncr; + } + } + } +} + +/* +** Ensure that there is room in the buffer to append nByte bytes of data. +** If not, use sqlite3_realloc() to grow the buffer so that there is. +** +** If successful, return zero. Otherwise, if an OOM condition is encountered, +** set *pRc to SQLITE_NOMEM and return non-zero. +*/ +static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){ +#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) + i64 nReq = p->nBuf + nByte; + if( *pRc==SQLITE_OK && nReq>p->nAlloc ){ + u8 *aNew; + i64 nNew = p->nAlloc ? p->nAlloc : 128; + + do { + nNew = nNew*2; + }while( nNewSESSION_MAX_BUFFER_SZ ){ + nNew = SESSION_MAX_BUFFER_SZ; + if( nNewaBuf, nNew); + if( 0==aNew ){ + *pRc = SQLITE_NOMEM; + }else{ + p->aBuf = aNew; + p->nAlloc = nNew; + } + } + return (*pRc!=SQLITE_OK); +} + + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a string to the buffer. All bytes in the string +** up to (but not including) the nul-terminator are written to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendStr( + SessionBuffer *p, + const char *zStr, + int *pRc +){ + int nStr = sqlite3Strlen30(zStr); + if( 0==sessionBufferGrow(p, nStr+1, pRc) ){ + memcpy(&p->aBuf[p->nBuf], zStr, nStr); + p->nBuf += nStr; + p->aBuf[p->nBuf] = 0x00; + } +} + +/* +** Format a string using printf() style formatting and then append it to the +** buffer using sessionAppendString(). +*/ +static void sessionAppendPrintf( + SessionBuffer *p, /* Buffer to append to */ + int *pRc, + const char *zFmt, + ... +){ + if( *pRc==SQLITE_OK ){ + char *zApp = 0; + va_list ap; + va_start(ap, zFmt); + zApp = sqlite3_vmprintf(zFmt, ap); + if( zApp==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + sessionAppendStr(p, zApp, pRc); + } + va_end(ap); + sqlite3_free(zApp); + } +} + +/* +** Prepare a statement against database handle db that SELECTs a single +** row containing the default values for each column in table pTab. For +** example, if pTab is declared as: +** +** CREATE TABLE pTab(a PRIMARY KEY, b DEFAULT 123, c DEFAULT 'abcd'); +** +** Then this function prepares and returns the SQL statement: +** +** SELECT NULL, 123, 'abcd'; +*/ +static int sessionPrepareDfltStmt( + sqlite3 *db, /* Database handle */ + SessionTable *pTab, /* Table to prepare statement for */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + SessionBuffer sql = {0,0,0}; + int rc = SQLITE_OK; + const char *zSep = " "; + int ii = 0; + + *ppStmt = 0; + sessionAppendPrintf(&sql, &rc, "SELECT"); + for(ii=0; iinCol; ii++){ + const char *zDflt = pTab->azDflt[ii] ? pTab->azDflt[ii] : "NULL"; + sessionAppendPrintf(&sql, &rc, "%s%s", zSep, zDflt); + zSep = ", "; + } + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (const char*)sql.aBuf, -1, ppStmt, 0); + } + sqlite3_free(sql.aBuf); + + return rc; +} + +/* +** Table pTab has one or more existing change-records with old.* records +** with fewer than pTab->nCol columns. This function updates all such +** change-records with the default values for the missing columns. +*/ +static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){ + sqlite3_stmt *pStmt = 0; + int rc = pSession->rc; + + rc = sessionPrepareDfltStmt(pSession->db, pTab, &pStmt); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + int ii = 0; + SessionChange **pp = 0; + for(ii=0; iinChange; ii++){ + for(pp=&pTab->apChange[ii]; *pp; pp=&((*pp)->pNext)){ + if( (*pp)->nRecordField!=pTab->nCol ){ + sessionUpdateOneChange(pSession, &rc, pp, pTab->nCol, pStmt); + } + } + } + } + + pSession->rc = rc; + rc = sqlite3_finalize(pStmt); + if( pSession->rc==SQLITE_OK ) pSession->rc = rc; + return pSession->rc; } /* @@ -1344,16 +1682,22 @@ static void sessionPreupdateOneChange( int iHash; int bNull = 0; int rc = SQLITE_OK; + int nExpect = 0; SessionStat1Ctx stat1 = {{0,0,0,0,0},0}; if( pSession->rc ) return; /* Load table details if required */ - if( sessionInitTable(pSession, pTab) ) return; + if( sessionInitTable(pSession, pTab, pSession->db, pSession->zDb) ) return; /* Check the number of columns in this xPreUpdate call matches the ** number of columns in the table. */ - if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){ + nExpect = pSession->hook.xCount(pSession->hook.pCtx); + if( (pTab->nCol-pTab->bRowid)nCol-pTab->bRowid)!=nExpect ){ pSession->rc = SQLITE_SCHEMA; return; } @@ -1430,7 +1774,7 @@ static void sessionPreupdateOneChange( } /* Allocate the change object */ - pC = (SessionChange *)sessionMalloc64(pSession, nByte); + pC = (SessionChange*)sessionMalloc64(pSession, nByte); if( !pC ){ rc = SQLITE_NOMEM; goto error_out; @@ -1463,6 +1807,7 @@ static void sessionPreupdateOneChange( if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){ pC->bIndirect = 1; } + pC->nRecordField = pTab->nCol; pC->nRecord = nByte; pC->op = op; pC->pNext = pTab->apChange[iHash]; @@ -1842,7 +2187,7 @@ int sqlite3session_diff( /* Locate and if necessary initialize the target table object */ rc = sessionFindTable(pSession, zTbl, &pTo); if( pTo==0 ) goto diff_out; - if( sessionInitTable(pSession, pTo) ){ + if( sessionInitTable(pSession, pTo, pSession->db, pSession->zDb) ){ rc = pSession->rc; goto diff_out; } @@ -1855,7 +2200,7 @@ int sqlite3session_diff( int bRowid = 0; u8 *abPK; const char **azCol = 0; - rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK, + rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, 0, &abPK, pSession->bImplicitPK ? &bRowid : 0 ); if( rc==SQLITE_OK ){ @@ -1970,6 +2315,7 @@ static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){ sessionFree(pSession, p); } } + sqlite3_finalize(pTab->pDfltStmt); sessionFree(pSession, (char*)pTab->azCol); /* cast works around VC++ bug */ sessionFree(pSession, pTab->apChange); sessionFree(pSession, pTab); @@ -2004,7 +2350,7 @@ void sqlite3session_delete(sqlite3_session *pSession){ /* Assert that all allocations have been freed and then free the ** session object itself. */ - assert( pSession->nMalloc==0 ); + // assert( pSession->nMalloc==0 ); sqlite3_free(pSession); } @@ -2075,48 +2421,6 @@ int sqlite3session_attach( return rc; } -/* -** Ensure that there is room in the buffer to append nByte bytes of data. -** If not, use sqlite3_realloc() to grow the buffer so that there is. -** -** If successful, return zero. Otherwise, if an OOM condition is encountered, -** set *pRc to SQLITE_NOMEM and return non-zero. -*/ -static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){ -#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) - i64 nReq = p->nBuf + nByte; - if( *pRc==SQLITE_OK && nReq>p->nAlloc ){ - u8 *aNew; - i64 nNew = p->nAlloc ? p->nAlloc : 128; - - do { - nNew = nNew*2; - }while( nNewSESSION_MAX_BUFFER_SZ ){ - nNew = SESSION_MAX_BUFFER_SZ; - if( nNewaBuf, nNew); - if( 0==aNew ){ - *pRc = SQLITE_NOMEM; - }else{ - p->aBuf = aNew; - p->nAlloc = nNew; - } - } - return (*pRc!=SQLITE_OK); -} - /* ** Append the value passed as the second argument to the buffer passed ** as the first. @@ -2185,27 +2489,6 @@ static void sessionAppendBlob( } } -/* -** This function is a no-op if *pRc is other than SQLITE_OK when it is -** called. Otherwise, append a string to the buffer. All bytes in the string -** up to (but not including) the nul-terminator are written to the buffer. -** -** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before -** returning. -*/ -static void sessionAppendStr( - SessionBuffer *p, - const char *zStr, - int *pRc -){ - int nStr = sqlite3Strlen30(zStr); - if( 0==sessionBufferGrow(p, nStr+1, pRc) ){ - memcpy(&p->aBuf[p->nBuf], zStr, nStr); - p->nBuf += nStr; - p->aBuf[p->nBuf] = 0x00; - } -} - /* ** This function is a no-op if *pRc is other than SQLITE_OK when it is ** called. Otherwise, append the string representation of integer iVal @@ -2224,27 +2507,6 @@ static void sessionAppendInteger( sessionAppendStr(p, aBuf, pRc); } -static void sessionAppendPrintf( - SessionBuffer *p, /* Buffer to append to */ - int *pRc, - const char *zFmt, - ... -){ - if( *pRc==SQLITE_OK ){ - char *zApp = 0; - va_list ap; - va_start(ap, zFmt); - zApp = sqlite3_vmprintf(zFmt, ap); - if( zApp==0 ){ - *pRc = SQLITE_NOMEM; - }else{ - sessionAppendStr(p, zApp, pRc); - } - va_end(ap); - sqlite3_free(zApp); - } -} - /* ** This function is a no-op if *pRc is other than SQLITE_OK when it is ** called. Otherwise, append the string zStr enclosed in quotes (") and @@ -2735,26 +2997,16 @@ static int sessionGenerateChangeset( for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ if( pTab->nEntry ){ const char *zName = pTab->zName; - int nCol = 0; /* Number of columns in table */ - u8 *abPK = 0; /* Primary key array */ - const char **azCol = 0; /* Table columns */ int i; /* Used to iterate through hash buckets */ sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */ int nRewind = buf.nBuf; /* Initial size of write buffer */ int nNoop; /* Size of buffer after writing tbl header */ - int bRowid = 0; + int nOldCol = pTab->nCol; /* Check the table schema is still Ok. */ - rc = sessionTableInfo( - 0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK, - (pSession->bImplicitPK ? &bRowid : 0) - ); - if( rc==SQLITE_OK && ( - pTab->nCol!=nCol - || pTab->bRowid!=bRowid - || memcmp(abPK, pTab->abPK, nCol) - )){ - rc = SQLITE_SCHEMA; + rc = sessionReinitTable(pSession, pTab); + if( rc==SQLITE_OK && pTab->nCol!=nOldCol ){ + rc = sessionUpdateChanges(pSession, pTab); } /* Write a table header */ @@ -2762,8 +3014,8 @@ static int sessionGenerateChangeset( /* Build and compile a statement to execute: */ if( rc==SQLITE_OK ){ - rc = sessionSelectStmt( - db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel + rc = sessionSelectStmt(db, 0, pSession->zDb, + zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel ); } @@ -2772,22 +3024,22 @@ static int sessionGenerateChangeset( SessionChange *p; /* Used to iterate through changes */ for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){ - rc = sessionSelectBind(pSel, nCol, abPK, p); + rc = sessionSelectBind(pSel, pTab->nCol, pTab->abPK, p); if( rc!=SQLITE_OK ) continue; if( sqlite3_step(pSel)==SQLITE_ROW ){ if( p->op==SQLITE_INSERT ){ int iCol; sessionAppendByte(&buf, SQLITE_INSERT, &rc); sessionAppendByte(&buf, p->bIndirect, &rc); - for(iCol=0; iColnCol; iCol++){ sessionAppendCol(&buf, pSel, iCol, &rc); } }else{ - assert( abPK!=0 ); /* Because sessionSelectStmt() returned ok */ - rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK); + assert( pTab->abPK!=0 ); + rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, pTab->abPK); } }else if( p->op!=SQLITE_INSERT ){ - rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK); + rc = sessionAppendDelete(&buf, bPatchset, p, pTab->nCol,pTab->abPK); } if( rc==SQLITE_OK ){ rc = sqlite3_reset(pSel); @@ -2812,7 +3064,6 @@ static int sessionGenerateChangeset( if( buf.nBuf==nNoop ){ buf.nBuf = nRewind; } - sqlite3_free((char*)azCol); /* cast works around VC++ bug */ } } @@ -4941,7 +5192,7 @@ static int sessionChangesetApply( sqlite3changeset_pk(pIter, &abPK, 0); rc = sessionTableInfo(0, db, "main", zNew, - &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid + &sApply.nCol, &zTab, &sApply.azCol, 0, &sApply.abPK, &sApply.bRowid ); if( rc!=SQLITE_OK ) break; for(i=0; iflags & SQLITE_FkNoAction; + + if( flags & SQLITE_CHANGESETAPPLY_FKNOACTION ){ + db->flags |= ((u64)SQLITE_FkNoAction); + db->aDb[0].pSchema->schema_cookie -= 32; + } + if( rc==SQLITE_OK ){ rc = sessionChangesetApply( db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags ); } + + if( (flags & SQLITE_CHANGESETAPPLY_FKNOACTION) && savedFlag==0 ){ + assert( db->flags & SQLITE_FkNoAction ); + db->flags &= ~((u64)SQLITE_FkNoAction); + db->aDb[0].pSchema->schema_cookie -= 32; + } return rc; } @@ -5165,6 +5429,9 @@ struct sqlite3_changegroup { int rc; /* Error code */ int bPatch; /* True to accumulate patchsets */ SessionTable *pList; /* List of tables in current patch */ + + sqlite3 *db; /* Configured by changegroup_schema() */ + char *zDb; /* Configured by changegroup_schema() */ }; /* @@ -5185,6 +5452,7 @@ static int sessionChangeMerge( ){ SessionChange *pNew = 0; int rc = SQLITE_OK; + assert( aRec!=0 ); if( !pExist ){ pNew = (SessionChange *)sqlite3_malloc64(sizeof(SessionChange) + nRec); @@ -5350,6 +5618,114 @@ static int sessionChangeMerge( return rc; } +/* +** Check if a changeset entry with nCol columns and the PK array passed +** as the final argument to this function is compatible with SessionTable +** pTab. If so, return 1. Otherwise, if they are incompatible in some way, +** return 0. +*/ +static int sessionChangesetCheckCompat( + SessionTable *pTab, + int nCol, + u8 *abPK +){ + if( pTab->azCol && nColnCol ){ + int ii; + for(ii=0; iinCol; ii++){ + u8 bPK = (ii < nCol) ? abPK[ii] : 0; + if( pTab->abPK[ii]!=bPK ) return 0; + } + return 1; + } + return (pTab->nCol==nCol && 0==memcmp(abPK, pTab->abPK, nCol)); +} + +static int sessionChangesetExtendRecord( + sqlite3_changegroup *pGrp, + SessionTable *pTab, + int nCol, + int op, + const u8 *aRec, + int nRec, + SessionBuffer *pOut +){ + int rc = SQLITE_OK; + int ii = 0; + + assert( pTab->azCol ); + assert( nColnCol ); + + pOut->nBuf = 0; + if( op==SQLITE_INSERT || (op==SQLITE_DELETE && pGrp->bPatch==0) ){ + /* Append the missing default column values to the record. */ + sessionAppendBlob(pOut, aRec, nRec, &rc); + if( rc==SQLITE_OK && pTab->pDfltStmt==0 ){ + rc = sessionPrepareDfltStmt(pGrp->db, pTab, &pTab->pDfltStmt); + } + for(ii=nCol; rc==SQLITE_OK && iinCol; ii++){ + int eType = sqlite3_column_type(pTab->pDfltStmt, ii); + sessionAppendByte(pOut, eType, &rc); + switch( eType ){ + case SQLITE_FLOAT: + case SQLITE_INTEGER: { + i64 iVal; + if( eType==SQLITE_INTEGER ){ + iVal = sqlite3_column_int64(pTab->pDfltStmt, ii); + }else{ + double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii); + memcpy(&iVal, &rVal, sizeof(i64)); + } + if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){ + sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal); + } + break; + } + + case SQLITE_BLOB: + case SQLITE_TEXT: { + int n = sqlite3_column_bytes(pTab->pDfltStmt, ii); + sessionAppendVarint(pOut, n, &rc); + if( eType==SQLITE_TEXT ){ + const u8 *z = (const u8*)sqlite3_column_text(pTab->pDfltStmt, ii); + sessionAppendBlob(pOut, z, n, &rc); + }else{ + const u8 *z = (const u8*)sqlite3_column_blob(pTab->pDfltStmt, ii); + sessionAppendBlob(pOut, z, n, &rc); + } + break; + } + + default: + assert( eType==SQLITE_NULL ); + break; + } + } + }else if( op==SQLITE_UPDATE ){ + /* Append missing "undefined" entries to the old.* record. And, if this + ** is an UPDATE, to the new.* record as well. */ + int iOff = 0; + if( pGrp->bPatch==0 ){ + for(ii=0; iinCol-nCol); ii++){ + sessionAppendByte(pOut, 0x00, &rc); + } + } + + sessionAppendBlob(pOut, &aRec[iOff], nRec-iOff, &rc); + for(ii=0; ii<(pTab->nCol-nCol); ii++){ + sessionAppendByte(pOut, 0x00, &rc); + } + }else{ + assert( op==SQLITE_DELETE && pGrp->bPatch ); + sessionAppendBlob(pOut, aRec, nRec, &rc); + } + + return rc; +} + /* ** Add all changes in the changeset traversed by the iterator passed as ** the first argument to the changegroup hash tables. @@ -5363,6 +5739,7 @@ static int sessionChangesetToHash( int nRec; int rc = SQLITE_OK; SessionTable *pTab = 0; + SessionBuffer rec = {0, 0, 0}; while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ const char *zNew; @@ -5374,6 +5751,9 @@ static int sessionChangesetToHash( SessionChange *pExist = 0; SessionChange **pp; + /* Ensure that only changesets, or only patchsets, but not a mixture + ** of both, are being combined. It is an error to try to combine a + ** changeset and a patchset. */ if( pGrp->pList==0 ){ pGrp->bPatch = pIter->bPatchset; }else if( pIter->bPatchset!=pGrp->bPatch ){ @@ -5406,18 +5786,38 @@ static int sessionChangesetToHash( pTab->zName = (char*)&pTab->abPK[nCol]; memcpy(pTab->zName, zNew, nNew+1); + if( pGrp->db ){ + pTab->nCol = 0; + rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); + if( rc ){ + assert( pTab->azCol==0 ); + sqlite3_free(pTab); + break; + } + } + /* The new object must be linked on to the end of the list, not ** simply added to the start of it. This is to ensure that the ** tables within the output of sqlite3changegroup_output() are in ** the right order. */ for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext); *ppTab = pTab; - }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){ + } + + if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ rc = SQLITE_SCHEMA; break; } } + if( nColnCol ){ + assert( pGrp->db ); + rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec); + if( rc ) break; + aRec = rec.aBuf; + nRec = rec.nBuf; + } + if( sessionGrowHash(0, pIter->bPatchset, pTab) ){ rc = SQLITE_NOMEM; break; @@ -5455,6 +5855,7 @@ static int sessionChangesetToHash( } } + sqlite3_free(rec.aBuf); if( rc==SQLITE_OK ) rc = pIter->rc; return rc; } @@ -5541,6 +5942,31 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp){ return rc; } +/* +** Provide a database schema to the changegroup object. +*/ +int sqlite3changegroup_schema( + sqlite3_changegroup *pGrp, + sqlite3 *db, + const char *zDb +){ + int rc = SQLITE_OK; + + if( pGrp->pList || pGrp->db ){ + /* Cannot add a schema after one or more calls to sqlite3changegroup_add(), + ** or after sqlite3changegroup_schema() has already been called. */ + rc = SQLITE_MISUSE; + }else{ + pGrp->zDb = sqlite3_mprintf("%s", zDb); + if( pGrp->zDb==0 ){ + rc = SQLITE_NOMEM; + }else{ + pGrp->db = db; + } + } + return rc; +} + /* ** Add the changeset currently stored in buffer pData, size nData bytes, ** to changeset-group p. @@ -5604,6 +6030,7 @@ int sqlite3changegroup_output_strm( */ void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ if( pGrp ){ + sqlite3_free(pGrp->zDb); sessionDeleteTable(0, pGrp->pList); sqlite3_free(pGrp); } diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 1ea90dce4..160ea8786 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -884,6 +884,18 @@ int sqlite3changeset_concat( ); +/* +** CAPI3REF: Upgrade the Schema of a Changeset/Patchset +*/ +int sqlite3changeset_upgrade( + sqlite3 *db, + const char *zDb, + int nIn, const void *pIn, /* Input changeset */ + int *pnOut, void **ppOut /* OUT: Inverse of input */ +); + + + /* ** CAPI3REF: Changegroup Handle ** @@ -930,6 +942,38 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; */ int sqlite3changegroup_new(sqlite3_changegroup **pp); +/* +** CAPI3REF: Add a Schema to a Changegroup +** METHOD: sqlite3_changegroup_schema +** +** This method may be used to optionally enforce the rule that the changesets +** added to the changegroup handle must match the schema of database zDb +** ("main", "temp", or the name of an attached database). If +** sqlite3changegroup_add() is called to add a changeset that is not compatible +** with the configured schema, SQLITE_SCHEMA is returned and the changegroup +** object is left in an undefined state. +** +** A changeset schema is considered compatible with the database schema in +** the same way as for sqlite3changeset_apply(). Specifically, for each +** table in the changeset, there exists a database table with: +** +**

          +**
        • The name identified by the changeset, and +**
        • at least as many columns as recorded in the changeset, and +**
        • the primary key columns in the same position as recorded in +** the changeset. +**
        +** +** The output of the changegroup object always has the same schema as the +** database nominated using this function. In cases where changesets passed +** to sqlite3changegroup_add() have fewer columns than the corresponding table +** in the database schema, these are filled in using the default column +** values from the database schema. This makes it possible to combined +** changesets that have different numbers of columns for a single table +** within a changegroup, provided that they are otherwise compatible. +*/ +int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb); + /* ** CAPI3REF: Add A Changeset To A Changegroup ** METHOD: sqlite3_changegroup @@ -998,13 +1042,18 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); ** If the new changeset contains changes to a table that is already present ** in the changegroup, then the number of columns and the position of the ** primary key columns for the table must be consistent. If this is not the -** case, this function fails with SQLITE_SCHEMA. If the input changeset -** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is -** returned. Or, if an out-of-memory condition occurs during processing, this -** function returns SQLITE_NOMEM. In all cases, if an error occurs the state -** of the final contents of the changegroup is undefined. -** -** If no error occurs, SQLITE_OK is returned. +** case, this function fails with SQLITE_SCHEMA. Except, if the changegroup +** object has been configured with a database schema using the +** sqlite3changegroup_schema() API, then it is possible to combine changesets +** with different numbers of columns for a single table, provided that +** they are otherwise compatible. +** +** If the input changeset appears to be corrupt and the corruption is +** detected, SQLITE_CORRUPT is returned. Or, if an out-of-memory condition +** occurs during processing, this function returns SQLITE_NOMEM. +** +** In all cases, if an error occurs the state of the final contents of the +** changegroup is undefined. If no error occurs, SQLITE_OK is returned. */ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); @@ -1269,10 +1318,17 @@ int sqlite3changeset_apply_v2( **
      • an insert change if all fields of the conflicting row match ** the row being inserted. **
      +** +**
      SQLITE_CHANGESETAPPLY_FKNOACTION
      +** If this flag it set, then all foreign key constraints in the target +** database behave as if they were declared with "ON UPDATE NO ACTION ON +** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL +** or SET DEFAULT. */ #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 #define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 +#define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008 /* ** CAPI3REF: Constants Passed To The Conflict Handler diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 0836238b5..af42351ba 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -392,7 +392,6 @@ static int SQLITE_TCLAPI test_session_cmd( }; size_t sz = sizeof(aOpt[0]); - int rc; int iArg; int iOpt; if( Tcl_GetIndexFromObjStruct(interp,objv[2],aOpt,sz,"option",0,&iOpt) ){ @@ -812,9 +811,12 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( while( objc>1 ){ const char *z1 = Tcl_GetString(objv[1]); int n = strlen(z1); - if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){ + if( n>3 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){ flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT; } + else if( n>3 && n<=9 && 0==sqlite3_strnicmp("-noaction", z1, n) ){ + flags |= SQLITE_CHANGESETAPPLY_FKNOACTION; + } else if( n>2 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ flags |= SQLITE_CHANGESETAPPLY_INVERT; } @@ -1452,12 +1454,144 @@ static int SQLITE_TCLAPI test_sqlite3session_config( return TCL_OK; } +typedef struct TestChangegroup TestChangegroup; +struct TestChangegroup { + sqlite3_changegroup *pGrp; +}; + +/* +** Destructor for Tcl changegroup command object. +*/ +static void test_changegroup_del(void *clientData){ + TestChangegroup *pGrp = (TestChangegroup*)clientData; + sqlite3changegroup_delete(pGrp->pGrp); + ckfree(pGrp); +} + +/* +** Tclcmd: $changegroup schema DB DBNAME +** Tclcmd: $changegroup add CHANGESET +** Tclcmd: $changegroup output +** Tclcmd: $changegroup delete +*/ +static int SQLITE_TCLAPI test_changegroup_cmd( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + TestChangegroup *p = (TestChangegroup*)clientData; + static struct ChangegroupCmd { + const char *zSub; + int nArg; + const char *zMsg; + int iSub; + } aSub[] = { + { "schema", 2, "DB DBNAME", }, /* 0 */ + { "add", 1, "CHANGESET", }, /* 1 */ + { "output", 0, "", }, /* 2 */ + { "delete", 0, "", }, /* 3 */ + { 0 } + }; + int rc = TCL_OK; + int iSub = 0; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); + return TCL_ERROR; + } + rc = Tcl_GetIndexFromObjStruct(interp, + objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub + ); + if( rc!=TCL_OK ) return rc; + if( objc!=2+aSub[iSub].nArg ){ + Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); + return TCL_ERROR; + } + + switch( iSub ){ + case 0: { /* schema */ + sqlite3 *db = 0; + const char *zDb = Tcl_GetString(objv[3]); + if( dbHandleFromObj(interp, objv[2], &db) ){ + return TCL_ERROR; + } + rc = sqlite3changegroup_schema(p->pGrp, db, zDb); + if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0); + break; + }; + + case 1: { /* add */ + int nByte = 0; + const u8 *aByte = Tcl_GetByteArrayFromObj(objv[2], &nByte); + rc = sqlite3changegroup_add(p->pGrp, nByte, (void*)aByte); + if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0); + break; + }; + + case 2: { /* output */ + int nByte = 0; + u8 *aByte = 0; + rc = sqlite3changegroup_output(p->pGrp, &nByte, (void**)&aByte); + if( rc!=SQLITE_OK ){ + rc = test_session_error(interp, rc, 0); + }else{ + Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(aByte, nByte)); + } + sqlite3_free(aByte); + break; + }; + + default: { /* delete */ + assert( iSub==3 ); + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + break; + } + } + + return rc; +} + +/* +** Tclcmd: sqlite3changegroup CMD +*/ +static int SQLITE_TCLAPI test_sqlite3changegroup( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; /* sqlite3changegroup_new() return code */ + TestChangegroup *p; /* New wrapper object */ + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "CMD"); + return TCL_ERROR; + } + + p = (TestChangegroup*)ckalloc(sizeof(TestChangegroup)); + memset(p, 0, sizeof(TestChangegroup)); + rc = sqlite3changegroup_new(&p->pGrp); + if( rc!=SQLITE_OK ){ + ckfree((char*)p); + return test_session_error(interp, rc, 0); + } + + Tcl_CreateObjCommand( + interp, Tcl_GetString(objv[1]), test_changegroup_cmd, (ClientData)p, + test_changegroup_del + ); + Tcl_SetObjResult(interp, objv[1]); + return TCL_OK; +} + int TestSession_Init(Tcl_Interp *interp){ struct Cmd { const char *zCmd; Tcl_ObjCmdProc *xProc; } aCmd[] = { { "sqlite3session", test_sqlite3session }, + { "sqlite3changegroup", test_sqlite3changegroup }, { "sqlite3session_foreach", test_sqlite3session_foreach }, { "sqlite3changeset_invert", test_sqlite3changeset_invert }, { "sqlite3changeset_concat", test_sqlite3changeset_concat }, diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index a001d15bb..c0cab212d 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -456,13 +456,6 @@ emcc.exportedRuntimeMethods := \ emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY -#emcc.jsflags += -sASYNCIFY=2 -# ^^^ ASYNCIFY=2 is for experimental JSPI support -# (https://v8.dev/blog/jspi), but enabling it causes the lib-level -# init code to throw inexplicable complaints about C-level function -# signatures not matching what we expect them to be. JSPI requires, as of -# this writing, requires an experimental Chrome flag: -# chrome://flags/#enable-experimental-webassembly-stack-switching emcc.jsflags += -sSTRICT_JS=0 # STRICT_JS disabled due to: # https://github.com/emscripten-core/emscripten/issues/18610 diff --git a/ext/wasm/SQLTester/GNUmakefile b/ext/wasm/SQLTester/GNUmakefile new file mode 100644 index 000000000..8fa124713 --- /dev/null +++ b/ext/wasm/SQLTester/GNUmakefile @@ -0,0 +1,55 @@ +#!/this/is/make +# +# This makefile compiles SQLTester test files into something +# we can readily import into JavaScript. +all: + +SHELL := $(shell which bash 2>/dev/null) +MAKEFILE := $(lastword $(MAKEFILE_LIST)) +CLEAN_FILES := +DISTCLEAN_FILES := ./--dummy-- *~ + +test-list.mjs := test-list.mjs +test-list.mjs.gz := $(test-list.mjs).gz +CLEAN_FILES += $(test-list.mjs) + +tests.dir := $(firstword $(wildcard tests ../../jni/src/tests)) +$(info test script dir=$(tests.dir)) + +tests.all := $(wildcard $(tests.dir)/*.test) + +bin.touint8array := ./touint8array +$(bin.touint8array): $(bin.touint8array).c $(MAKEFILE) + $(CC) -o $@ $< +CLEAN_FILES += $(bin.touint8array) + +ifneq (,$(tests.all)) +$(test-list.mjs): $(bin.touint8array) $(tests.all) $(MAKEFILE) + @{\ + echo 'export default ['; \ + sep=''; \ + for f in $(sort $(tests.all)); do \ + echo -en $$sep'{"name": "'$${f##*/}'", "content":'; \ + $(bin.touint8array) < $$f; \ + echo -n '}'; \ + sep=',\n'; \ + done; \ + echo '];'; \ + } > $@ + @echo "Created $@" +$(test-list.mjs.gz): $(test-list.mjs) + gzip -c $< > $@ +CLEAN_FILES += $(test-list.mjs.gz) +all: $(test-list.mjs.gz) +else + @echo "Cannot build $(test-list.mjs) for lack of input test files."; \ + echo "Symlink ./tests to a directory containing SQLTester-format "; \ + echo "test scripts named *.test, then try again"; \ + exit 1 +endif + +.PHONY: clean distclean +clean: + -rm -f $(CLEAN_FILES) +distclean: clean + -rm -f $(DISTCLEAN_FILES) diff --git a/ext/wasm/SQLTester/SQLTester.mjs b/ext/wasm/SQLTester/SQLTester.mjs new file mode 100644 index 000000000..a72399aef --- /dev/null +++ b/ext/wasm/SQLTester/SQLTester.mjs @@ -0,0 +1,1339 @@ +/* +** 2023-08-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 contains the main application entry pointer for the JS +** implementation of the SQLTester framework. +** +** This version is not well-documented because it's a direct port of +** the Java immplementation, which is documented: in the main SQLite3 +** source tree, see ext/jni/src/org/sqlite/jni/tester/SQLite3Tester.java. +*/ + +import sqlite3ApiInit from '/jswasm/sqlite3.mjs'; + +const sqlite3 = await sqlite3ApiInit(); + +const log = (...args)=>{ + console.log('SQLTester:',...args); +}; + +/** + Try to install vfsName as the new default VFS. Once this succeeds + (returns true) then it becomes a no-op on future calls. Throws if + vfs registration as the default VFS fails but has no side effects + if vfsName is not currently registered. +*/ +const tryInstallVfs = function f(vfsName){ + if(f.vfsName) return false; + const pVfs = sqlite3.capi.sqlite3_vfs_find(vfsName); + if(pVfs){ + log("Installing",'"'+vfsName+'"',"as default VFS."); + const rc = sqlite3.capi.sqlite3_vfs_register(pVfs, 1); + if(rc){ + sqlite3.SQLite3Error.toss(rc,"While trying to register",vfsName,"vfs."); + } + f.vfsName = vfsName; + } + return !!pVfs; +}; +tryInstallVfs.vfsName = undefined; + +if( 0 && globalThis.WorkerGlobalScope ){ + // Try OPFS storage, if available... + if( 0 && sqlite3.oo1.OpfsDb ){ + /* Really slow with these tests */ + tryInstallVfs("opfs"); + } + if( sqlite3.installOpfsSAHPoolVfs ){ + await sqlite3.installOpfsSAHPoolVfs({ + clearOnInit: true, + initialCapacity: 15, + name: 'opfs-SQLTester' + }).then(pool=>{ + tryInstallVfs(pool.vfsName); + }).catch(e=>{ + log("OpfsSAHPool could not load:",e); + }); + } +} + +const wPost = (function(){ + return (('undefined'===typeof WorkerGlobalScope) + ? ()=>{} + : (type, payload)=>{ + postMessage({type, payload}); + }); +})(); +//log("WorkerGlobalScope",globalThis.WorkerGlobalScope); + +// Return a new enum entry value +const newE = ()=>Object.create(null); + +const newObj = (props)=>Object.assign(newE(), props); + +/** + Modes for how to escape (or not) column values and names from + SQLTester.execSql() to the result buffer output. +*/ +const ResultBufferMode = Object.assign(Object.create(null),{ + //! Do not append to result buffer + NONE: newE(), + //! Append output escaped. + ESCAPED: newE(), + //! Append output as-is + ASIS: newE() +}); + +/** + Modes to specify how to emit multi-row output from + SQLTester.execSql() to the result buffer. +*/ +const ResultRowMode = newObj({ + //! Keep all result rows on one line, space-separated. + ONLINE: newE(), + //! Add a newline between each result row. + NEWLINE: newE() +}); + +class SQLTesterException extends globalThis.Error { + constructor(testScript, ...args){ + if(testScript){ + super( [testScript.getOutputPrefix()+": ", ...args].join('') ); + }else{ + super( args.join('') ); + } + this.name = 'SQLTesterException'; + } + isFatal() { return false; } +} + +SQLTesterException.toss = (...args)=>{ + throw new SQLTesterException(...args); +} + +class DbException extends SQLTesterException { + constructor(testScript, pDb, rc, closeDb=false){ + super(testScript, "DB error #"+rc+": "+sqlite3.capi.sqlite3_errmsg(pDb)); + this.name = 'DbException'; + if( closeDb ) sqlite3.capi.sqlite3_close_v2(pDb); + } + isFatal() { return true; } +} + +class TestScriptFailed extends SQLTesterException { + constructor(testScript, ...args){ + super(testScript,...args); + this.name = 'TestScriptFailed'; + } + isFatal() { return true; } +} + +class UnknownCommand extends SQLTesterException { + constructor(testScript, cmdName){ + super(testScript, cmdName); + this.name = 'UnknownCommand'; + } + isFatal() { return true; } +} + +class IncompatibleDirective extends SQLTesterException { + constructor(testScript, ...args){ + super(testScript,...args); + this.name = 'IncompatibleDirective'; + } +} + +//! For throwing where an expression is required. +const toss = (errType, ...args)=>{ + throw new errType(...args); +}; + +const __utf8Decoder = new TextDecoder(); +const __utf8Encoder = new TextEncoder('utf-8'); +//! Workaround for Util.utf8Decode() +const __SAB = ('undefined'===typeof globalThis.SharedArrayBuffer) + ? function(){} : globalThis.SharedArrayBuffer; + + +/* Frequently-reused regexes. */ +const Rx = newObj({ + requiredProperties: / REQUIRED_PROPERTIES:[ \t]*(\S.*)\s*$/, + scriptModuleName: / SCRIPT_MODULE_NAME:[ \t]*(\S+)\s*$/, + mixedModuleName: / ((MIXED_)?MODULE_NAME):[ \t]*(\S+)\s*$/, + command: /^--(([a-z-]+)( .*)?)$/, + //! "Special" characters - we have to escape output if it contains any. + special: /[\x00-\x20\x22\x5c\x7b\x7d]/, + squiggly: /[{}]/ +}); + +const Util = newObj({ + toss, + + unlink: function(fn){ + return 0==sqlite3.wasm.sqlite3_wasm_vfs_unlink(0,fn); + }, + + argvToString: (list)=>{ + const m = [...list]; + m.shift() /* strip command name */; + return m.join(" ") + }, + + utf8Decode: function(arrayBuffer, begin, end){ + return __utf8Decoder.decode( + (arrayBuffer.buffer instanceof __SAB) + ? arrayBuffer.slice(begin, end) + : arrayBuffer.subarray(begin, end) + ); + }, + + utf8Encode: (str)=>__utf8Encoder.encode(str), + + strglob: sqlite3.wasm.xWrap('sqlite3_wasm_SQLTester_strglob','int', + ['string','string']) +})/*Util*/; + +class Outer { + #lnBuf = []; + #verbosity = 0; + #logger = console.log.bind(console); + + constructor(func){ + if(func) this.setFunc(func); + } + + logger(...args){ + if(args.length){ + this.#logger = args[0]; + return this; + } + return this.#logger; + } + + out(...args){ + if( this.getOutputPrefix && !this.#lnBuf.length ){ + this.#lnBuf.push(this.getOutputPrefix()); + } + this.#lnBuf.push(...args); + return this; + } + + #outlnImpl(vLevel, ...args){ + if( this.getOutputPrefix && !this.#lnBuf.length ){ + this.#lnBuf.push(this.getOutputPrefix()); + } + this.#lnBuf.push(...args,'\n'); + const msg = this.#lnBuf.join(''); + this.#lnBuf.length = 0; + this.#logger(msg); + return this; + } + + outln(...args){ + return this.#outlnImpl(0,...args); + } + + outputPrefix(){ + if( 0==arguments.length ){ + return (this.getOutputPrefix + ? (this.getOutputPrefix() ?? '') : ''); + }else{ + this.getOutputPrefix = arguments[0]; + return this; + } + } + + static #verboseLabel = ["🔈",/*"🔉",*/"🔊","📢"]; + verboseN(lvl, args){ + if( this.#verbosity>=lvl ){ + this.#outlnImpl(lvl, Outer.#verboseLabel[lvl-1],': ',...args); + } + } + verbose1(...args){ return this.verboseN(1,args); } + verbose2(...args){ return this.verboseN(2,args); } + verbose3(...args){ return this.verboseN(3,args); } + + verbosity(){ + const rc = this.#verbosity; + if(arguments.length) this.#verbosity = +arguments[0]; + return rc; + } + +}/*Outer*/ + +class SQLTester { + + //! Console output utility. + #outer = new Outer().outputPrefix( ()=>'SQLTester: ' ); + //! List of input scripts. + #aScripts = []; + //! Test input buffer. + #inputBuffer = []; + //! Test result buffer. + #resultBuffer = []; + //! Output representation of SQL NULL. + #nullView; + metrics = newObj({ + //! Total tests run + nTotalTest: 0, + //! Total test script files run + nTestFile: 0, + //! Test-case count for to the current TestScript + nTest: 0, + //! Names of scripts which were aborted. + failedScripts: [] + }); + #emitColNames = false; + //! True to keep going regardless of how a test fails. + #keepGoing = false; + #db = newObj({ + //! The list of available db handles. + list: new Array(7), + //! Index into this.list of the current db. + iCurrentDb: 0, + //! Name of the default db, re-created for each script. + initialDbName: "test.db", + //! Buffer for REQUIRED_PROPERTIES pragmas. + initSql: ['select 1;'], + //! (sqlite3*) to the current db. + currentDb: function(){ + return this.list[this.iCurrentDb]; + } + }); + + constructor(){ + this.reset(); + } + + outln(...args){ return this.#outer.outln(...args); } + out(...args){ return this.#outer.out(...args); } + outer(...args){ + if(args.length){ + this.#outer = args[0]; + return this; + } + return this.#outer; + } + verbose1(...args){ return this.#outer.verboseN(1,args); } + verbose2(...args){ return this.#outer.verboseN(2,args); } + verbose3(...args){ return this.#outer.verboseN(3,args); } + verbosity(...args){ + const rc = this.#outer.verbosity(...args); + return args.length ? this : rc; + } + setLogger(func){ + this.#outer.logger(func); + return this; + } + + incrementTestCounter(){ + ++this.metrics.nTotalTest; + ++this.metrics.nTest; + } + + reset(){ + this.clearInputBuffer(); + this.clearResultBuffer(); + this.#clearBuffer(this.#db.initSql); + this.closeAllDbs(); + this.metrics.nTest = 0; + this.#nullView = "nil"; + this.emitColNames = false; + this.#db.iCurrentDb = 0; + //this.#db.initSql.push("SELECT 1;"); + } + + appendInput(line, addNL){ + this.#inputBuffer.push(line); + if( addNL ) this.#inputBuffer.push('\n'); + } + appendResult(line, addNL){ + this.#resultBuffer.push(line); + if( addNL ) this.#resultBuffer.push('\n'); + } + appendDbInitSql(sql){ + this.#db.initSql.push(sql); + if( this.currentDb() ){ + this.execSql(null, true, ResultBufferMode.NONE, null, sql); + } + } + + #runInitSql(pDb){ + let rc = 0; + for(const sql of this.#db.initSql){ + this.#outer.verbose2("RUNNING DB INIT CODE: ",sql); + rc = this.execSql(pDb, false, ResultBufferMode.NONE, null, sql); + if( rc ) break; + } + return rc; + } + +#clearBuffer(buffer){ + buffer.length = 0; + return buffer; + } + + clearInputBuffer(){ return this.#clearBuffer(this.#inputBuffer); } + clearResultBuffer(){return this.#clearBuffer(this.#resultBuffer); } + + getInputText(){ return this.#inputBuffer.join(''); } + getResultText(){ return this.#resultBuffer.join(''); } + + #takeBuffer(buffer){ + const s = buffer.join(''); + buffer.length = 0; + return s; + } + + takeInputBuffer(){ + return this.#takeBuffer(this.#inputBuffer); + } + takeResultBuffer(){ + return this.#takeBuffer(this.#resultBuffer); + } + + nullValue(){ + return (0==arguments.length) + ? this.#nullView + : (this.#nullView = ''+arguments[0]); + } + + outputColumnNames(){ + return (0==arguments.length) + ? this.#emitColNames + : (this.#emitColNames = !!arguments[0]); + } + + currentDbId(){ + return (0==arguments.length) + ? this.#db.iCurrentDb + : (this.#affirmDbId(arguments[0]).#db.iCurrentDb = arguments[0]); + } + + #affirmDbId(id){ + if(id<0 || id>=this.#db.list.length){ + toss(SQLTesterException, "Database index ",id," is out of range."); + } + return this; + } + + currentDb(...args){ + if( 0!=args.length ){ + this.#affirmDbId(id).#db.iCurrentDb = id; + } + return this.#db.currentDb(); + } + + getDbById(id){ + return this.#affirmDbId(id).#db.list[id]; + } + + getCurrentDb(){ return this.#db.list[this.#db.iCurrentDb]; } + + + closeDb(id) { + if( 0==arguments.length ){ + id = this.#db.iCurrentDb; + } + const pDb = this.#affirmDbId(id).#db.list[id]; + if( pDb ){ + sqlite3.capi.sqlite3_close_v2(pDb); + this.#db.list[id] = null; + } + } + + closeAllDbs(){ + for(let i = 0; i 0){ + rc = this.#runInitSql(pDb); + } + if( 0!=rc ){ + sqlite3.SQLite3Error.toss( + rc, + "sqlite3 result code",rc+":", + (pDb ? sqlite3.capi.sqlite3_errmsg(pDb) + : sqlite3.capi.sqlite3_errstr(rc)) + ); + } + return this.#db.list[this.#db.iCurrentDb] = pDb; + }catch(e){ + sqlite3.capi.sqlite3_close_v2(pDb); + throw e; + } + } + + addTestScript(ts){ + if( 2===arguments.length ){ + ts = new TestScript(arguments[0], arguments[1]); + }else if(ts instanceof Uint8Array){ + ts = new TestScript('', ts); + }else if('string' === typeof arguments[1]){ + ts = new TestScript('', Util.utf8Encode(arguments[1])); + } + if( !(ts instanceof TestScript) ){ + Util.toss(SQLTesterException, "Invalid argument type for addTestScript()"); + } + this.#aScripts.push(ts); + return this; + } + + runTests(){ + const tStart = (new Date()).getTime(); + let isVerbose = this.verbosity(); + this.metrics.failedScripts.length = 0; + this.metrics.nTotalTest = 0; + this.metrics.nTestFile = 0; + for(const ts of this.#aScripts){ + this.reset(); + ++this.metrics.nTestFile; + let threw = false; + const timeStart = (new Date()).getTime(); + let msgTail = ''; + try{ + ts.run(this); + }catch(e){ + if(e instanceof SQLTesterException){ + threw = true; + this.outln("🔥EXCEPTION: ",e); + this.metrics.failedScripts.push({script: ts.filename(), message:e.toString()}); + if( this.#keepGoing ){ + this.outln("Continuing anyway because of the keep-going option."); + }else if( e.isFatal() ){ + throw e; + } + }else{ + throw e; + } + }finally{ + const timeEnd = (new Date()).getTime(); + this.out("🏁", (threw ? "❌" : "✅"), " ", + this.metrics.nTest, " test(s) in ", + (timeEnd-timeStart),"ms. "); + const mod = ts.moduleName(); + if( mod ){ + this.out( "[",mod,"] " ); + } + this.outln(ts.filename()); + } + } + const tEnd = (new Date()).getTime(); + Util.unlink(this.#db.initialDbName); + this.outln("Took ",(tEnd-tStart),"ms. Test count = ", + this.metrics.nTotalTest,", script count = ", + this.#aScripts.length,( + this.metrics.failedScripts.length + ? ", failed scripts = "+this.metrics.failedScripts.length + : "" + ) + ); + return this; + } + + #setupInitialDb(){ + if( !this.#db.list[0] ){ + Util.unlink(this.#db.initialDbName); + this.openDb(0, this.#db.initialDbName, true); + }else{ + this.#outer.outln("WARNING: setupInitialDb() was unexpectedly ", + "triggered while it is opened."); + } + } + + #escapeSqlValue(v){ + if( !v ) return "{}"; + if( !Rx.special.test(v) ){ + return v /* no escaping needed */; + } + if( !Rx.squiggly.test(v) ){ + return "{"+v+"}"; + } + const sb = ["\""]; + const n = v.length; + for(let i = 0; i < n; ++i){ + const ch = v.charAt(i); + switch(ch){ + case '\\': sb.push("\\\\"); break; + case '"': sb.push("\\\""); break; + default:{ + //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch)); + const ccode = ch.charCodeAt(i); + if( ccode < 32 ) sb.push('\\',ccode.toString(8),'o'); + else sb.push(ch); + break; + } + } + } + sb.append("\""); + return sb.join(''); + } + + #appendDbErr(pDb, sb, rc){ + sb.push(sqlite3.capi.sqlite3_js_rc_str(rc), ' '); + const msg = this.#escapeSqlValue(sqlite3.capi.sqlite3_errmsg(pDb)); + if( '{' === msg.charAt(0) ){ + sb.push(msg); + }else{ + sb.push('{', msg, '}'); + } + } + + #checkDbRc(pDb,rc){ + sqlite3.oo1.DB.checkRc(pDb, rc); + } + + execSql(pDb, throwOnError, appendMode, rowMode, sql){ + if( !pDb && !this.#db.list[0] ){ + this.#setupInitialDb(); + } + if( !pDb ) pDb = this.#db.currentDb(); + const wasm = sqlite3.wasm, capi = sqlite3.capi; + sql = (sql instanceof Uint8Array) + ? sql + : Util.utf8Encode(capi.sqlite3_js_sql_to_string(sql)); + const self = this; + const sb = (ResultBufferMode.NONE===appendMode) ? null : this.#resultBuffer; + let rc = 0; + wasm.scopedAllocCall(function(){ + let sqlByteLen = sql.byteLength; + const ppStmt = wasm.scopedAlloc( + /* output (sqlite3_stmt**) arg and pzTail */ + (2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */) + ); + const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */; + let pSql = pzTail + wasm.ptrSizeof; + const pSqlEnd = pSql + sqlByteLen; + wasm.heap8().set(sql, pSql); + wasm.poke8(pSql + sqlByteLen, 0/*NUL terminator*/); + let pos = 0, n = 1, spacing = 0; + while( pSql && wasm.peek8(pSql) ){ + wasm.pokePtr([ppStmt, pzTail], 0); + rc = capi.sqlite3_prepare_v3( + pDb, pSql, sqlByteLen, 0, ppStmt, pzTail + ); + if( 0!==rc ){ + if(throwOnError){ + throw new DbException(self, pDb, rc); + }else if( sb ){ + self.#appendDbErr(pDb, sb, rc); + } + break; + } + const pStmt = wasm.peekPtr(ppStmt); + pSql = wasm.peekPtr(pzTail); + sqlByteLen = pSqlEnd - pSql; + if(!pStmt) continue /* only whitespace or comments */; + if( sb ){ + const nCol = capi.sqlite3_column_count(pStmt); + let colName, val; + while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) { + for( let i=0; i < nCol; ++i ){ + if( spacing++ > 0 ) sb.push(' '); + if( self.#emitColNames ){ + colName = capi.sqlite3_column_name(pStmt, i); + switch(appendMode){ + case ResultBufferMode.ASIS: sb.push( colName ); break; + case ResultBufferMode.ESCAPED: + sb.push( self.#escapeSqlValue(colName) ); + break; + default: + self.toss("Unhandled ResultBufferMode."); + } + sb.push(' '); + } + val = capi.sqlite3_column_text(pStmt, i); + if( null===val ){ + sb.push( self.#nullView ); + continue; + } + switch(appendMode){ + case ResultBufferMode.ASIS: sb.push( val ); break; + case ResultBufferMode.ESCAPED: + sb.push( self.#escapeSqlValue(val) ); + break; + } + }/* column loop */ + }/* row loop */ + if( ResultRowMode.NEWLINE === rowMode ){ + spacing = 0; + sb.push('\n'); + } + }else{ // no output but possibly other side effects + while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) {} + } + capi.sqlite3_finalize(pStmt); + if( capi.SQLITE_ROW===rc || capi.SQLITE_DONE===rc) rc = 0; + else if( rc!=0 ){ + if( sb ){ + self.#appendDbErr(db, sb, rc); + } + break; + } + }/* SQL script loop */; + })/*scopedAllocCall()*/; + return rc; + } + +}/*SQLTester*/ + +class Command { + constructor(){ + } + + process(sqlTester,testScript,argv){ + SQLTesterException.toss("process() must be overridden"); + } + + argcCheck(testScript,argv,min,max){ + const argc = argv.length-1; + if(argc=0 && argc>max)){ + if( min==max ){ + testScript.toss(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ + testScript.toss(argv[0]," requires ",min,"-",max," arguments."); + }else{ + testScript.toss(argv[0]," requires at least ",min," arguments."); + } + } + } +} + +class Cursor { + src; + sb = []; + pos = 0; + //! Current line number. Starts at 0 for internal reasons and will + // line up with 1-based reality once parsing starts. + lineNo = 0 /* yes, zero */; + //! Putback value for this.pos. + putbackPos = 0; + //! Putback line number + putbackLineNo = 0; + //! Peeked-to pos, used by peekLine() and consumePeeked(). + peekedPos = 0; + //! Peeked-to line number. + peekedLineNo = 0; + + constructor(){ + } + + //! Restore parsing state to the start of the stream. + rewind(){ + this.sb.length = this.pos = this.lineNo + = this.putbackPos = this.putbackLineNo + = this.peekedPos = this.peekedLineNo = 0; + } +} + +class TestScript { + #cursor = new Cursor(); + #moduleName = null; + #filename = null; + #testCaseName = null; + #outer = new Outer().outputPrefix( ()=>this.getOutputPrefix()+': ' ); + + constructor(...args){ + let content, filename; + if( 2 == args.length ){ + filename = args[0]; + content = args[1]; + }else if( 1 == args.length ){ + if(args[0] instanceof Object){ + const o = args[0]; + filename = o.name; + content = o.content; + }else{ + content = args[0]; + } + } + if(!(content instanceof Uint8Array)){ + if('string' === typeof content){ + content = Util.utf8Encode(content); + }else if((content instanceof ArrayBuffer) + ||(content instanceof Array)){ + content = new Uint8Array(content); + }else{ + toss(Error, "Invalid content type for TestScript constructor."); + } + } + this.#filename = filename; + this.#cursor.src = content; + } + + moduleName(){ + return (0==arguments.length) + ? this.#moduleName : (this.#moduleName = arguments[0]); + } + + testCaseName(){ + return (0==arguments.length) + ? this.#testCaseName : (this.#testCaseName = arguments[0]); + } + filename(){ + return (0==arguments.length) + ? this.#filename : (this.#filename = arguments[0]); + } + + getOutputPrefix() { + let rc = "["+(this.#moduleName || '')+"]"; + if( this.#testCaseName ) rc += "["+this.#testCaseName+"]"; + if( this.#filename ) rc += '['+this.#filename+']'; + return rc + " line "+ this.#cursor.lineNo; + } + + reset(){ + this.#testCaseName = null; + this.#cursor.rewind(); + return this; + } + + toss(...args){ + throw new TestScriptFailed(this,...args); + } + + verbose1(...args){ return this.#outer.verboseN(1,args); } + verbose2(...args){ return this.#outer.verboseN(2,args); } + verbose3(...args){ return this.#outer.verboseN(3,args); } + verbosity(...args){ + const rc = this.#outer.verbosity(...args); + return args.length ? this : rc; + } + + #checkRequiredProperties(tester, props){ + if(true) return false; + let nOk = 0; + for(const rp of props){ + this.verbose2("REQUIRED_PROPERTIES: ",rp); + switch(rp){ + case "RECURSIVE_TRIGGERS": + tester.appendDbInitSql("pragma recursive_triggers=on;"); + ++nOk; + break; + case "TEMPSTORE_FILE": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + tester.appendDbInitSql("pragma temp_store=1;"); + ++nOk; + break; + case "TEMPSTORE_MEM": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + tester.appendDbInitSql("pragma temp_store=0;"); + ++nOk; + break; + case "AUTOVACUUM": + tester.appendDbInitSql("pragma auto_vacuum=full;"); + ++nOk; + break; + case "INCRVACUUM": + tester.appendDbInitSql("pragma auto_vacuum=incremental;"); + ++nOk; + default: + break; + } + } + return props.length == nOk; + } + + #checkForDirective(tester,line){ + if(line.startsWith("#")){ + throw new IncompatibleDirective(this, "C-preprocessor input: "+line); + }else if(line.startsWith("---")){ + throw new IncompatibleDirective(this, "triple-dash: ",line); + } + let m = Rx.scriptModuleName.exec(line); + if( m ){ + this.#moduleName = m[1]; + return; + } + m = Rx.requiredProperties.exec(line); + if( m ){ + const rp = m[1]; + if( !this.#checkRequiredProperties( tester, rp.split(/\s+/).filter(v=>!!v) ) ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); + } + } + + m = Rx.mixedModuleName.exec(line); + if( m ){ + throw new IncompatibleDirective(this, m[1]+": "+m[3]); + } + if( line.indexOf("\n|")>=0 ){ + throw new IncompatibleDirective(this, "newline-pipe combination."); + } + + } + + #getCommandArgv(line){ + const m = Rx.command.exec(line); + return m ? m[1].trim().split(/\s+/) : null; + } + + + #isCommandLine(line, checkForImpl){ + let m = Rx.command.exec(line); + if( m && checkForImpl ){ + m = !!CommandDispatcher.getCommandByName(m[2]); + } + return !!m; + } + + fetchCommandBody(tester){ + const sb = []; + let line; + while( (null !== (line = this.peekLine())) ){ + this.#checkForDirective(tester, line); + if( this.#isCommandLine(line, true) ) break; + sb.push(line,"\n"); + this.consumePeeked(); + } + line = sb.join(''); + return !!line.trim() ? line : null; + } + + run(tester){ + this.reset(); + this.#outer.verbosity( tester.verbosity() ); + this.#outer.logger( tester.outer().logger() ); + let line, directive, argv = []; + while( null != (line = this.getLine()) ){ + this.verbose3("run() input line: ",line); + this.#checkForDirective(tester, line); + argv = this.#getCommandArgv(line); + if( argv ){ + this.#processCommand(tester, argv); + continue; + } + tester.appendInput(line,true); + } + return true; + } + + #processCommand(tester, argv){ + this.verbose2("processCommand(): ",argv[0], " ", Util.argvToString(argv)); + if(this.#outer.verbosity()>1){ + const input = tester.getInputText(); + this.verbose3("processCommand() input buffer = ",input); + } + CommandDispatcher.dispatch(tester, this, argv); + } + + getLine(){ + const cur = this.#cursor; + if( cur.pos==cur.src.byteLength ){ + return null/*EOF*/; + } + cur.putbackPos = cur.pos; + cur.putbackLineNo = cur.lineNo; + cur.sb.length = 0; + let b = 0, prevB = 0, i = cur.pos; + let doBreak = false; + let nChar = 0 /* number of bytes in the aChar char */; + const end = cur.src.byteLength; + for(; i < end && !doBreak; ++i){ + b = cur.src[i]; + switch( b ){ + case 13/*CR*/: continue; + case 10/*NL*/: + ++cur.lineNo; + if(cur.sb.length>0) doBreak = true; + // Else it's an empty string + break; + default:{ + /* Multi-byte chars need to be gathered up and appended at + one time so that we can get them as string objects. */ + nChar = 1; + switch( b & 0xF0 ){ + case 0xC0: nChar = 2; break; + case 0xE0: nChar = 3; break; + case 0xF0: nChar = 4; break; + default: + if( b > 127 ) this.toss("Invalid character (#"+b+")."); + break; + } + if( 1==nChar ){ + cur.sb.push(String.fromCharCode(b)); + }else{ + const aChar = [] /* multi-byte char buffer */; + for(let x = 0; (x < nChar) && (i+x < end); ++x) aChar[x] = cur.src[i+x]; + cur.sb.push( + Util.utf8Decode( new Uint8Array(aChar) ) + ); + i += nChar-1; + } + break; + } + } + } + cur.pos = i; + const rv = cur.sb.join(''); + if( i==cur.src.byteLength && 0==rv.length ){ + return null /* EOF */; + } + return rv; + }/*getLine()*/ + + /** + Fetches the next line then resets the cursor to its pre-call + state. consumePeeked() can be used to consume this peeked line + without having to re-parse it. + */ + peekLine(){ + const cur = this.#cursor; + const oldPos = cur.pos; + const oldPB = cur.putbackPos; + const oldPBL = cur.putbackLineNo; + const oldLine = cur.lineNo; + try { + return this.getLine(); + }finally{ + cur.peekedPos = cur.pos; + cur.peekedLineNo = cur.lineNo; + cur.pos = oldPos; + cur.lineNo = oldLine; + cur.putbackPos = oldPB; + cur.putbackLineNo = oldPBL; + } + } + + + /** + Only valid after calling peekLine() and before calling getLine(). + This places the cursor to the position it would have been at had + the peekLine() had been fetched with getLine(). + */ + consumePeeked(){ + const cur = this.#cursor; + cur.pos = cur.peekedPos; + cur.lineNo = cur.peekedLineNo; + } + + /** + Restores the cursor to the position it had before the previous + call to getLine(). + */ + putbackLine(){ + const cur = this.#cursor; + cur.pos = cur.putbackPos; + cur.lineNo = cur.putbackLineNo; + } + +}/*TestScript*/; + +//! --close command +class CloseDbCommand extends Command { + process(t, ts, argv){ + this.argcCheck(ts,argv,0,1); + let id; + if(argv.length>1){ + const arg = argv[1]; + if( "all" === arg ){ + t.closeAllDbs(); + return; + } + else{ + id = parseInt(arg); + } + }else{ + id = t.currentDbId(); + } + t.closeDb(id); + } +} + +//! --column-names command +class ColumnNamesCommand extends Command { + process( st, ts, argv ){ + this.argcCheck(ts,argv,1); + st.outputColumnNames( !!parseInt(argv[1]) ); + } +} + +//! --db command +class DbCommand extends Command { + process(t, ts, argv){ + this.argcCheck(ts,argv,1); + t.currentDbId( parseInt(argv[1]) ); + } +} + +//! --glob command +class GlobCommand extends Command { + #negate = false; + constructor(negate=false){ + super(); + this.#negate = negate; + } + + process(t, ts, argv){ + this.argcCheck(ts,argv,1,-1); + t.incrementTestCounter(); + const sql = t.takeInputBuffer(); + let rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + const result = t.getResultText(); + const sArgs = Util.argvToString(argv); + //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + const glob = Util.argvToString(argv); + rc = Util.strglob(glob, result); + if( (this.#negate && 0===rc) || (!this.#negate && 0!==rc) ){ + ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); + } + } +} + +//! --notglob command +class NotGlobCommand extends GlobCommand { + constructor(){super(true);} +} + +//! --open command +class OpenDbCommand extends Command { + #createIfNeeded = false; + constructor(createIfNeeded=false){ + super(); + this.#createIfNeeded = createIfNeeded; + } + process(t, ts, argv){ + this.argcCheck(ts,argv,1); + t.openDb(argv[1], this.#createIfNeeded); + } +} + +//! --new command +class NewDbCommand extends OpenDbCommand { + constructor(){ super(true); } +} + +//! Placeholder dummy/no-op commands +class NoopCommand extends Command { + process(t, ts, argv){} +} + +//! --null command +class NullCommand extends Command { + process(st, ts, argv){ + this.argcCheck(ts,argv,1); + st.nullValue( argv[1] ); + } +} + +//! --print command +class PrintCommand extends Command { + process(st, ts, argv){ + st.out(ts.getOutputPrefix(),': '); + if( 1==argv.length ){ + st.out( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + } +} + +//! --result command +class ResultCommand extends Command { + #bufferMode; + constructor(resultBufferMode = ResultBufferMode.ESCAPED){ + super(); + this.#bufferMode = resultBufferMode; + } + process(t, ts, argv){ + this.argcCheck(ts,argv,0,-1); + t.incrementTestCounter(); + const sql = t.takeInputBuffer(); + //ts.verbose2(argv[0]," SQL =\n",sql); + t.execSql(null, false, this.#bufferMode, ResultRowMode.ONELINE, sql); + const result = t.getResultText().trim(); + const sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( result !== sArgs ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nExpected result:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --json command +class JsonCommand extends ResultCommand { + constructor(){ super(ResultBufferMode.ASIS); } +} + +//! --run command +class RunCommand extends Command { + process(t, ts, argv){ + this.argcCheck(ts,argv,0,1); + const pDb = (1==argv.length) + ? t.currentDb() : t.getDbById( parseInt(argv[1]) ); + const sql = t.takeInputBuffer(); + const rc = t.execSql(pDb, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!==rc && t.verbosity()>0 ){ + const msg = sqlite3.capi.sqlite3_errmsg(pDb); + ts.verbose2(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand extends Command { + #jsonMode; + constructor(jsonMode=false){ + super(); + this.#jsonMode = jsonMode; + } + process(t, ts, argv){ + this.argcCheck(ts,argv,0); + t.incrementTestCounter(); + let body = ts.fetchCommandBody(t); + if( null===body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end\\n"); + }else{ + body = body.substring(0, body.length-6); + } + const globs = body.split(/\s*\n\s*/); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (this.#jsonMode ? "json snippets" : "globs"),"."); + } + const sql = t.takeInputBuffer(); + t.execSql(null, true, + this.#jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + const rbuf = t.getResultText().trim(); + const res = rbuf.split(/\r?\n/); + if( res.length !== globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(let i = 0; i < res.length; ++i){ + const glob = globs[i].replaceAll(/\s+/g," ").trim(); + //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( this.#jsonMode ){ + if( glob!==res[i] ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0!=Util.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --json-block command +class JsonBlockCommand extends TableResultCommand { + constructor(){ super(true); } +} + +//! --testcase command +class TestCaseCommand extends Command { + process(tester, script, argv){ + this.argcCheck(script, argv,1); + script.testCaseName(argv[1]); + tester.clearResultBuffer(); + tester.clearInputBuffer(); + } +} + + +//! --verbosity command +class VerbosityCommand extends Command { + process(t, ts, argv){ + this.argcCheck(ts,argv,1); + ts.verbosity( parseInt(argv[1]) ); + } +} + +class CommandDispatcher { + static map = newObj(); + + static getCommandByName(name){ + let rv = CommandDispatcher.map[name]; + if( rv ) return rv; + switch(name){ + case "close": rv = new CloseDbCommand(); break; + case "column-names": rv = new ColumnNamesCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; + case "tableresult": rv = new TableResultCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; + case "verbosity": rv = new VerbosityCommand(); break; + } + if( rv ){ + CommandDispatcher.map[name] = rv; + } + return rv; + } + + static dispatch(tester, testScript, argv){ + const cmd = CommandDispatcher.getCommandByName(argv[0]); + if( !cmd ){ + toss(UnknownCommand,testScript,argv[0]); + } + cmd.process(tester, testScript, argv); + } +}/*CommandDispatcher*/ + +const namespace = newObj({ + Command, + DbException, + IncompatibleDirective, + Outer, + SQLTester, + SQLTesterException, + TestScript, + TestScriptFailed, + UnknownCommand, + Util, + sqlite3 +}); + +export {namespace as default}; diff --git a/ext/wasm/SQLTester/SQLTester.run.mjs b/ext/wasm/SQLTester/SQLTester.run.mjs new file mode 100644 index 000000000..735fe4dcd --- /dev/null +++ b/ext/wasm/SQLTester/SQLTester.run.mjs @@ -0,0 +1,148 @@ +/* +** 2023-08-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 contains a test application for SQLTester.js. +*/ +import {default as ns} from './SQLTester.mjs'; +import {default as allTests} from './test-list.mjs'; + +globalThis.sqlite3 = ns.sqlite3; +const log = function f(...args){ + console.log('SQLTester.run:',...args); + return f; +}; + +const out = function f(...args){ return f.outer.out(...args) }; +out.outer = new ns.Outer(); +out.outer.getOutputPrefix = ()=>'SQLTester.run: '; +const outln = (...args)=>{ return out.outer.outln(...args) }; + +const affirm = function(expr, msg){ + if( !expr ){ + throw new Error(arguments[1] + ? ("Assertion failed: "+arguments[1]) + : "Assertion failed"); + } +} + +let ts = new ns.TestScript('/foo.test',` +/* +** This is a comment. There are many like it but this one is mine. +** +** SCRIPT_MODULE_NAME: sanity-check-0 +** xMIXED_MODULE_NAME: mixed-module +** xMODULE_NAME: module-name +** xREQUIRED_PROPERTIES: small fast reliable +** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS +** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM +** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM +** +*/ +/* --verbosity 3 */ +/* ---must-fail */ +/* # must fail */ +/* --verbosity 0 */ +--print Hello, world. +--close all +--oom +--db 0 +--new my.db +--null zilch +--testcase 1.0 +SELECT 1, null; +--result 1 zilch +--glob *zil* +--notglob *ZIL* +SELECT 1, 2; +intentional error; +--run +/* ---intentional-failure */ +--testcase json-1 +SELECT json_array(1,2,3) +--json [1,2,3] +--testcase tableresult-1 + select 1, 'a'; + select 2, 'b'; +--tableresult + # [a-z] + 2 b +--end +--testcase json-block-1 + select json_array(1,2,3); + select json_object('a',1,'b',2); +--json-block + [1,2,3] + {"a":1,"b":2} +--end +--testcase col-names-on +--column-names 1 + select 1 as 'a', 2 as 'b'; +--result a 1 b 2 +--testcase col-names-off +--column-names 0 + select 1 as 'a', 2 as 'b'; +--result 1 2 +--close +--print Until next time +`); + +const sqt = new ns.SQLTester() + .setLogger(console.log.bind(console)) + .verbosity(1) + .addTestScript(ts); +sqt.outer().outputPrefix(''); + +const runTests = function(){ + try{ + if( 0 ){ + affirm( !sqt.getCurrentDb(), 'sqt.getCurrentDb()' ); + sqt.openDb('/foo.db', true); + affirm( !!sqt.getCurrentDb(),'sqt.getCurrentDb()' ); + affirm( 'zilch' !== sqt.nullValue() ); + ts.run(sqt); + affirm( 'zilch' === sqt.nullValue() ); + sqt.addTestScript(ts); + sqt.runTests(); + }else{ + for(const t of allTests){ + sqt.addTestScript( new ns.TestScript(t) ); + } + allTests.length = 0; + sqt.runTests(); + } + }finally{ + //log( "Metrics:", sqt.metrics ); + sqt.reset(); + } +}; + +if( globalThis.WorkerGlobalScope ){ + const wPost = (type,payload)=>globalThis.postMessage({type, payload}); + globalThis.onmessage = function({data}){ + switch(data.type){ + case 'run-tests':{ + try{ runTests(); } + finally{ wPost('tests-end', sqt.metrics); } + break; + } + default: + log("unhandled onmessage: ",data); + break; + } + }; + sqt.setLogger((msg)=>{ + wPost('stdout', {message: msg}); + }); + wPost('is-ready'); + //globalThis.onmessage({data:{type:'run-tests'}}); +}else{ + runTests(); +} diff --git a/ext/wasm/SQLTester/index.html b/ext/wasm/SQLTester/index.html new file mode 100644 index 000000000..1dffad63e --- /dev/null +++ b/ext/wasm/SQLTester/index.html @@ -0,0 +1,127 @@ + + + + + + + + + SQLTester + + + +

      SQLTester for JS/WASM

      +

      This app reads in a build-time-defined set of SQLTester test + scripts and runs them through the test suite. +

      +
      + Options + + + + + +
      +
      Test output will go here.
      + + + + diff --git a/ext/wasm/SQLTester/touint8array.c b/ext/wasm/SQLTester/touint8array.c new file mode 100644 index 000000000..b03ad42f0 --- /dev/null +++ b/ext/wasm/SQLTester/touint8array.c @@ -0,0 +1,29 @@ +/* +** 2023-08-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 contains a tool for writing out the contents of stdin as +** a comma-separated list of numbers, one per byte. +*/ + +#include +int main(int argc, char const **argv){ + int i; + int rc = 0, colWidth = 30; + int ch; + printf("["); + for( i=0; EOF!=(ch = fgetc(stdin)); ++i ){ + if( 0!=i ) printf(","); + if( i && 0==(i%colWidth) ) puts(""); + printf("%d",ch); + } + printf("]"); + return rc; +} diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index ca5d1c44f..ef1154f6b 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -772,8 +772,43 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( isSharedTypedArray, toss: function(...args){throw new Error(args.join(' '))}, toss3, - typedArrayPart - }; + typedArrayPart, + /** + Given a byte array or ArrayBuffer, this function throws if the + lead bytes of that buffer do not hold a SQLite3 database header, + else it returns without side effects. + + Added in 3.44. + */ + affirmDbHeader: function(bytes){ + if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + const header = "SQLite format 3"; + if( header.length > bytes.byteLength ){ + toss3("Input does not contain an SQLite3 database header."); + } + for(let i = 0; i < header.length; ++i){ + if( header.charCodeAt(i) !== bytes[i] ){ + toss3("Input does not contain an SQLite3 database header."); + } + } + }, + /** + Given a byte array or ArrayBuffer, this function throws if the + database does not, at a cursory glance, appear to be an SQLite3 + database. It only examines the size and header, but further + checks may be added in the future. + + Added in 3.44. + */ + affirmIsDb: function(bytes){ + if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + const n = bytes.byteLength; + if(n<512 || n%512!==0) { + toss3("Byte array size",n,"is invalid for an SQLite3 db."); + } + util.affirmDbHeader(bytes); + } + }/*util*/; Object.assign(wasm, { /** @@ -1100,7 +1135,23 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return 1===n ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof) : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof); + }, + + /** + Records the current pstack position, calls the given function, + passing it the sqlite3 object, then restores the pstack + regardless of whether the function throws. Returns the result + of the call or propagates an exception on error. + + Added in 3.44. + */ + call: function(f){ + const stackPos = wasm.pstack.pointer; + try{ return f(sqlite3) } finally{ + wasm.pstack.restore(stackPos); + } } + })/*wasm.pstack*/; Object.defineProperties(wasm.pstack, { /** @@ -1508,6 +1559,26 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( } }; + /** + Converts SQL input from a variety of convenient formats + to plain strings. + + If v is a string, it is returned as-is. If it is-a Array, its + join("") result is returned. If is is a Uint8Array, Int8Array, + or ArrayBuffer, it is assumed to hold UTF-8-encoded text and is + decoded to a string. If it looks like a WASM pointer, + wasm.cstrToJs(sql) is returned. Else undefined is returned. + + Added in 3.44 + */ + capi.sqlite3_js_sql_to_string = (sql)=>{ + if('string' === typeof sql){ + return sql; + } + const x = flexibleString(v); + return x===v ? undefined : x; + } + if( util.isUIThread() ){ /* Features specific to the main window thread... */ diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index 9a386c13e..29f7d2be6 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -333,7 +333,6 @@ sqlite3.initWorker1API = function(){ if(!(globalThis.WorkerGlobalScope instanceof Function)){ toss("initWorker1API() must be run from a Worker thread."); } - const self = this.self; const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object."); const DB = sqlite3.oo1.DB; @@ -657,5 +656,5 @@ sqlite3.initWorker1API = function(){ }, wState.xfer); }; globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'}); -}.bind({self, sqlite3}); +}.bind({sqlite3}); }); diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js index d16254143..870073cc0 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -59,6 +59,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const toss3 = sqlite3.util.toss3; const initPromises = Object.create(null); const capi = sqlite3.capi; + const util = sqlite3.util; const wasm = sqlite3.wasm; // Config opts for the VFS... const SECTOR_SIZE = 4096; @@ -154,8 +155,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ pool.deletePath(file.path); } }catch(e){ - pool.storeErr(e); - return capi.SQLITE_IOERR; + return pool.storeErr(e, capi.SQLITE_IOERR); } } return 0; @@ -199,8 +199,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } return 0; }catch(e){ - pool.storeErr(e); - return capi.SQLITE_IOERR; + return pool.storeErr(e, capi.SQLITE_IOERR); } }, xSectorSize: function(pFile){ @@ -216,8 +215,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ file.sah.flush(); return 0; }catch(e){ - pool.storeErr(e); - return capi.SQLITE_IOERR; + return pool.storeErr(e, capi.SQLITE_IOERR); } }, xTruncate: function(pFile,sz64){ @@ -230,8 +228,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ file.sah.truncate(HEADER_OFFSET_DATA + Number(sz64)); return 0; }catch(e){ - pool.storeErr(e); - return capi.SQLITE_IOERR; + return pool.storeErr(e, capi.SQLITE_IOERR); } }, xUnlock: function(pFile,lockType){ @@ -251,10 +248,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ wasm.heap8u().subarray(pSrc, pSrc+n), { at: HEADER_OFFSET_DATA + Number(offset64) } ); - return nBytes === n ? 0 : capi.SQLITE_IOERR; + return n===nBytes ? 0 : toss("Unknown write() failure."); }catch(e){ - pool.storeErr(e); - return capi.SQLITE_IOERR; + return pool.storeErr(e, capi.SQLITE_IOERR); } } }/*ioMethods*/; @@ -313,8 +309,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }, xGetLastError: function(pVfs,nOut,pOut){ const pool = getPoolForVfs(pVfs); - pool.log(`xGetLastError ${nOut}`); const e = pool.popErr(); + pool.log(`xGetLastError ${nOut} e =`,e); if(e){ const scope = wasm.scopedAllocPush(); try{ @@ -327,7 +323,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ wasm.scopedAllocPop(scope); } } - return 0; + return e ? (e.sqlite3Rc || capi.SQLITE_IOERR) : 0; }, //xSleep is optionally defined below xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){ @@ -761,12 +757,20 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } /** - Sets e as this object's current error. Pass a falsy - (or no) value to clear it. + Sets e (an Error object) as this object's current error. Pass a + falsy (or no) value to clear it. If code is truthy it is + assumed to be an SQLITE_xxx result code, defaulting to + SQLITE_IOERR if code is falsy. + + Returns the 2nd argument. */ - storeErr(e){ - if(e) this.error(e); - return this.$error = e; + storeErr(e,code){ + if(e){ + e.sqlite3Rc = code || capi.SQLITE_IOERR; + this.error(e); + } + this.$error = e; + return code; } /** Pops this object's Error object and returns @@ -869,9 +873,49 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return b; } + //! Impl for importDb() when its 2nd arg is a function. + async importDbChunked(name, callback){ + const sah = this.#mapFilenameToSAH.get(name) + || this.nextAvailableSAH() + || toss("No available handles to import to."); + sah.truncate(0); + let nWrote = 0, chunk, checkedHeader = false, err = false; + try{ + while( undefined !== (chunk = await callback()) ){ + if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk); + if( 0===nWrote && chunk.byteLength>=15 ){ + util.affirmDbHeader(chunk); + checkedHeader = true; + } + sah.write(chunk, {at: HEADER_OFFSET_DATA + nWrote}); + nWrote += chunk.byteLength; + } + if( nWrote < 512 || 0!==nWrote % 512 ){ + toss("Input size",nWrote,"is not correct for an SQLite database."); + } + if( !checkedHeader ){ + const header = new Uint8Array(20); + sah.read( header, {at: 0} ); + util.affirmDbHeader( header ); + } + sah.write(new Uint8Array([1,1]), { + at: HEADER_OFFSET_DATA + 18 + }/*force db out of WAL mode*/); + }catch(e){ + this.setAssociatedPath(sah, '', 0); + throw e; + } + this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB); + return nWrote; + } + //! Documented elsewhere in this file. importDb(name, bytes){ - if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + if( bytes instanceof ArrayBuffer ) bytes = new Uint8Array(bytes); + else if( bytes instanceof Function ) return this.importDbChunked(name, bytes); + const sah = this.#mapFilenameToSAH.get(name) + || this.nextAvailableSAH() + || toss("No available handles to import to."); const n = bytes.byteLength; if(n<512 || n%512!=0){ toss("Byte array size is invalid for an SQLite db."); @@ -882,18 +926,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ toss("Input does not contain an SQLite database header."); } } - const sah = this.#mapFilenameToSAH.get(name) - || this.nextAvailableSAH() - || toss("No available handles to import to."); const nWrote = sah.write(bytes, {at: HEADER_OFFSET_DATA}); if(nWrote != n){ this.setAssociatedPath(sah, '', 0); toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); }else{ sah.write(new Uint8Array([1,1]), {at: HEADER_OFFSET_DATA+18} - /* force db out of WAL mode */); + /* force db out of WAL mode */); this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB); } + return nWrote; } }/*class OpfsSAHPool*/; @@ -1100,6 +1142,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ automatically clean up any non-database files so importing them is pointless. + If passed a function for its second argument, its behavior + changes to asynchronous and it imports its data in chunks fed to + it by the given callback function. It calls the callback (which + may be async) repeatedly, expecting either a Uint8Array or + ArrayBuffer (to denote new input) or undefined (to denote + EOF). For so long as the callback continues to return + non-undefined, it will append incoming data to the given + VFS-hosted database file. The result of the resolved Promise when + called this way is the size of the resulting database. + + On succes this routine rewrites the database header bytes in the + output file (not the input array) to force disabling of WAL mode. + On a write error, the handle is removed from the pool and made available for re-use. diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index de11f3a23..af89f216f 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -136,6 +136,7 @@ const installOpfsVfs = function callee(options){ const error = (...args)=>logImpl(0, ...args); const toss = sqlite3.util.toss; const capi = sqlite3.capi; + const util = sqlite3.util; const wasm = sqlite3.wasm; const sqlite3_vfs = capi.sqlite3_vfs; const sqlite3_file = capi.sqlite3_file; @@ -1168,37 +1169,89 @@ const installOpfsVfs = function callee(options){ doDir(opt.directory, 0); }; + /** + impl of importDb() when it's given a function as its second + argument. + */ + const importDbChunked = async function(filename, callback){ + const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true); + const hFile = await hDir.getFileHandle(fnamePart, {create:true}); + let sah = await hFile.createSyncAccessHandle(); + let nWrote = 0, chunk, checkedHeader = false, err = false; + try{ + sah.truncate(0); + while( undefined !== (chunk = await callback()) ){ + if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk); + if( 0===nWrote && chunk.byteLength>=15 ){ + util.affirmDbHeader(chunk); + checkedHeader = true; + } + sah.write(chunk, {at: nWrote}); + nWrote += chunk.byteLength; + } + if( nWrote < 512 || 0!==nWrote % 512 ){ + toss("Input size",nWrote,"is not correct for an SQLite database."); + } + if( !checkedHeader ){ + const header = new Uint8Array(20); + sah.read( header, {at: 0} ); + util.affirmDbHeader( header ); + } + sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/); + return nWrote; + }catch(e){ + await sah.close(); + sah = undefined; + await hDir.removeEntry( fnamePart ).catch(()=>{}); + throw e; + }finally { + if( sah ) await sah.close(); + } + }; + /** Asynchronously imports the given bytes (a byte array or ArrayBuffer) into the given database file. + If passed a function for its second argument, its behaviour + changes to async and it imports its data in chunks fed to it by + the given callback function. It calls the callback (which may + be async) repeatedly, expecting either a Uint8Array or + ArrayBuffer (to denote new input) or undefined (to denote + EOF). For so long as the callback continues to return + non-undefined, it will append incoming data to the given + VFS-hosted database file. When called this way, the resolved + value of the returned Promise is the number of bytes written to + the target file. + It very specifically requires the input to be an SQLite3 database and throws if that's not the case. It does so in order to prevent this function from taking on a larger scope than it is specifically intended to. i.e. we do not want it to become a convenience for importing arbitrary files into OPFS. - Throws on error. Resolves to the number of bytes written. + This routine rewrites the database header bytes in the output + file (not the input array) to force disabling of WAL mode. + + On error this throws and the state of the input file is + undefined (it depends on where the exception was triggered). + + On success, resolves to the number of bytes written. */ opfsUtil.importDb = async function(filename, bytes){ + if( bytes instanceof Function ){ + return importDbChunked(filename, bytes); + } if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + util.affirmIsDb(bytes); const n = bytes.byteLength; - if(n<512 || n%512!=0){ - toss("Byte array size is invalid for an SQLite db."); - } - const header = "SQLite format 3"; - for(let i = 0; i < header.length; ++i){ - if( header.charCodeAt(i) !== bytes[i] ){ - toss("Input does not contain an SQLite database header."); - } - } - let sah; const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true); + let sah, err, nWrote = 0; try { const hFile = await hDir.getFileHandle(fnamePart, {create:true}); sah = await hFile.createSyncAccessHandle(); sah.truncate(0); - const nWrote = sah.write(bytes, {at: 0}); + nWrote = sah.write(bytes, {at: 0}); if(nWrote != n){ toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); } diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index fcfbc0692..18d27bdf0 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -84,6 +84,14 @@ /**********************************************************************/ /* SQLITE_ENABLE_... */ +/* +** Unconditionally enable API_ARMOR in the WASM build. It ensures that +** public APIs behave predictable in the face of passing illegal NULLs +** or ranges which might otherwise invoke undefined behavior. +*/ +#undef SQLITE_ENABLE_API_ARMOR +#define SQLITE_ENABLE_API_ARMOR 1 + #ifndef SQLITE_ENABLE_BYTECODE_VTAB # define SQLITE_ENABLE_BYTECODE_VTAB 1 #endif @@ -121,12 +129,6 @@ # define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION #endif -/**********************************************************************/ -/* SQLITE_M... */ -#ifndef SQLITE_MAX_ALLOCATION_SIZE -# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff -#endif - /**********************************************************************/ /* SQLITE_O... */ #ifndef SQLITE_OMIT_DEPRECATED @@ -352,7 +354,9 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ if( db!=0 ){ if( 0!=zMsg ){ const int nMsg = sqlite3Strlen30(zMsg); + sqlite3_mutex_enter(sqlite3_db_mutex(db)); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); }else{ sqlite3ErrorWithMsg(db, err_code, NULL); } @@ -1799,6 +1803,118 @@ char * sqlite3_wasm_test_str_hello(int fail){ } return s; } + +/* +** For testing using SQLTester scripts. +** +** Return non-zero if string z matches glob pattern zGlob and zero if the +** pattern does not match. +** +** To repeat: +** +** zero == no match +** non-zero == match +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front, or a hexadecimal +** literal of the form 0x... +*/ +static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; + typedef int (*recurse_f)(const char *,const char *); + static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob; + + 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 && recurse(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( recurse(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=='#' ){ + if( z[0]=='0' + && (z[1]=='x' || z[1]=='X') + && sqlite3Isxdigit(z[2]) + ){ + z += 3; + while( sqlite3Isxdigit(z[0]) ){ z++; } + }else{ + if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++; + if( !sqlite3Isdigit(z[0]) ) return 0; + z++; + while( sqlite3Isdigit(z[0]) ){ z++; } + } + }else{ + if( c!=(*(z++)) ) return 0; + } + } + return *z==0; +} + +SQLITE_WASM_EXPORT +int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){ + return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z); +} + + #endif /* SQLITE_WASM_TESTS */ #undef SQLITE_WASM_EXPORT diff --git a/ext/wasm/api/sqlite3-worker1.c-pp.js b/ext/wasm/api/sqlite3-worker1.c-pp.js index f26042230..220722ffe 100644 --- a/ext/wasm/api/sqlite3-worker1.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1.c-pp.js @@ -37,7 +37,7 @@ import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs'; "use strict"; { const urlParams = globalThis.location - ? new URL(self.location.href).searchParams + ? new URL(globalThis.location.href).searchParams : new URLSearchParams(); let theJs = 'sqlite3.js'; if(urlParams.has('sqlite3.dir')){ diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index bd945dcab..92d763f1b 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -2939,8 +2939,27 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let db; try { const exp = this.opfsDbExport; + const filename = this.opfsDbFile; delete this.opfsDbExport; - this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(this.opfsDbFile, exp); + this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp); + db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); + T.assert(6 === db.selectValue('select count(*) from p')). + assert( this.opfsImportSize == exp.byteLength ); + db.close(); + this.opfsUnlink(filename); + T.assert(!(await sqlite3.opfs.entryExists(filename))); + // Try again with a function as an input source: + let cursor = 0; + const blockSize = 512, end = exp.byteLength; + const reader = async function(){ + if(cursor >= exp.byteLength){ + return undefined; + } + const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize); + cursor += blockSize; + return rv; + }; + this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader); db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); T.assert(6 === db.selectValue('select count(*) from p')). assert( this.opfsImportSize == exp.byteLength ); @@ -3059,8 +3078,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const dbytes = u1.exportFile(dbName); T.assert(dbytes.length >= 4096); const dbName2 = '/exported.db'; - u1.importDb(dbName2, dbytes); - T.assert( 2 == u1.getFileCount() ); + let nWrote = u1.importDb(dbName2, dbytes); + T.assert( 2 == u1.getFileCount() ) + .assert( dbytes.byteLength == nWrote ); let db2 = new u1.OpfsSAHPoolDb(dbName2); T.assert(db2 instanceof sqlite3.oo1.DB) .assert(3 === db2.selectValue('select count(*) from t')); @@ -3069,6 +3089,25 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .assert(false === u1.unlink(dbName2)) .assert(1 === u1.getFileCount()) .assert(1 === u1.getFileNames().length); + // Try again with a function as an input source: + let cursor = 0; + const blockSize = 1024, end = dbytes.byteLength; + const reader = async function(){ + if(cursor >= dbytes.byteLength){ + return undefined; + } + const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize); + cursor += blockSize; + return rv; + }; + nWrote = await u1.importDb(dbName2, reader); + T.assert( 2 == u1.getFileCount() ); + db2 = new u1.OpfsSAHPoolDb(dbName2); + T.assert(db2 instanceof sqlite3.oo1.DB) + .assert(3 === db2.selectValue('select count(*) from t')); + db2.close(); + T.assert(true === u1.unlink(dbName2)) + .assert(dbytes.byteLength == nWrote); } T.assert(true === u1.unlink(dbName)) @@ -3214,4 +3253,3 @@ globalThis.sqlite3InitModule = sqlite3InitModule; TestUtil.runTests(sqlite3); }); })(self); - diff --git a/main.mk b/main.mk index 75730b1a6..fdfdb7529 100644 --- a/main.mk +++ b/main.mk @@ -597,7 +597,17 @@ dbfuzz2$(EXE): $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c $(TLIBS) $(THREADLIB) fuzzcheck$(EXE): $(FUZZSRC) sqlite3.c sqlite3.h $(FUZZDEP) - $(TCCX) -o fuzzcheck$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ + $(TCCX) -o $@ -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \ + $(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB) + +fuzzcheck-asan$(EXE): $(FUZZSRC) sqlite3.c sqlite3.h $(FUZZDEP) + $(TCCX) -fsanitize=address -o $W -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \ + $(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB) + +fuzzcheck-ubsan$(EXE): $(FUZZSRC) sqlite3.c sqlite3.h $(FUZZDEP) + $(TCCX) -fsanitize=undefined -o $@ -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \ $(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB) @@ -917,11 +927,11 @@ fulltestonly: $(TESTPROGS) fuzztest queryplantest: testfixture$(EXE) sqlite3$(EXE) ./testfixture$(EXE) $(TOP)/test/permutations.test queryplanner $(TESTOPTS) -fuzztest: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db +fuzztest: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) ./fuzzcheck$(EXE) $(FUZZDATA) ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db -valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db +valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M $(FUZZDATA) valgrind ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db diff --git a/manifest b/manifest index 9b302487f..603321be0 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.43.2 -D 2023-10-10T12:14:04.911 +C Version\s3.44.0 +D 2023-11-01T11:23:50.173 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 0f17b4b3550aae10deb5475429a2388a0960ebe85a29d1dd97aa0a2444b0bf48 +F Makefile.in 8b59912fc1538f96a08555605c5886cdcc733696ae7f22e374b2a4752196ca20 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 -F Makefile.msc 26c2d196391a285c279adb10fd6001774d9b243af94b700b681e4a49cd476684 +F Makefile.msc f0cf219350d9af4fba411b4f6306dce2adc897484e8f446de1fb4f40de674d00 F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8 -F VERSION 56b287eac8404526c4faa0bdfa7e31baed534068fa3acece479b5d6c35cfca1e +F VERSION 4c09b629c03b8ae32317cb336a32f3aa3252841d6dcd51184cecc4278d08f21e F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -15,14 +15,14 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc 3248809e70cf439a13e9faf82a4e12cbdb7b042006300ac67175fc5125b5c031 +F autoconf/Makefile.msc 34f8c222846db1b354720c8a78a4fa7dec9176ea447c0fffdf442b7f74e8b1df F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac c968ba373f2afaad123e59e14baf20aa423ac0e5668ca8b40092b96b408d9938 +F autoconf/tea/configure.ac 1ee185f2f8c2e9bed69d0113222724202f962d04eafd57197e65c662d2d9b180 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,14 +33,15 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 2c538b418bb71f7e8bb62193eae709a1f7dd4342f74f6530891bd1e937c03df2 x -F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 +F configure 1d9cbcb416cb5387b3f750ae6db63cfedb703a2e46bf8ca61daecff31d208252 x +F configure.ac de31fea7d975bb7ebafbe0e2190a855cc80d48558bf0c9a6578a1836daf1cd3a F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd -F doc/compile-for-windows.md c52f2903f1cb11b2308798feecca2e44701b037b78f467a538ac5c46c28ee250 +F doc/compile-for-windows.md 922ba580d210a1f1bd3ef9d0413121556f9b5714fb5c01e664d980f85fa4ac8c F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f F doc/lemon.html 44a53a1d2b42d7751f7b2f478efb23c978e258d794bfd172442307a755b9fa44 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 +F doc/testrunner.md 2434864be2219d4f0b6ffc99d0a2172d531c4ca4345340776f67ad4edd90dc90 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a @@ -51,33 +52,33 @@ F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test 95b00567ce0775126a1b788af2d055255014714ecfddc97913864d2f9266e583 -F ext/expert/sqlite3expert.c a912efbad597eafdb0ce934ebc11039f3190b2d479685d89184e107f65d856e1 +F ext/expert/expert1.test 0dd5cb096d66bed593e33053a3b364f6ef52ed72064bf5cf298364636dbf3cd6 +F ext/expert/sqlite3expert.c 90446bb1183429308c68ceb23e00cb8252f43b8886cf5efa3fc7e833b5fa24c8 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c7cc3bf59ee F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 66d2ced306ac88c39c00d81184f2c60f338696af6ae8cc26ed7c361b157b09f7 +F ext/fts3/fts3.c d01dfb641fc04efeeadcb94d7a8342eb07d71c1a3a3852ec8ab5e64c1fcdfff9 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h e573c6d881f7238d77cc3fd2396cbb9b2fe13efef7d2ad295a155151c4e7efbd -F ext/fts3/fts3_aux.c f0dc9bd98582615b7750218899bd0c729879b6bbf94d1be57ca1833ff49afc6f +F ext/fts3/fts3Int.h be688580701d41340de73384e3acc8c55be12a438583207444bd5e20f9ef426c +F ext/fts3/fts3_aux.c 7eab82a9cf0830f6551ba3abfdbe73ed39e322a4d3940ee82fbf723674ecd9f3 F ext/fts3/fts3_expr.c 903bfb9433109fffb10e910d7066c49cbf8eeae316adc93f0499c4da7dfc932a F ext/fts3/fts3_hash.c 8b6e31bfb0844c27dc6092c2620bdb1fca17ed613072db057d96952c6bdb48b7 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf F ext/fts3/fts3_icu.c 305ce7fb6036484085b5556a9c8e62acdc7763f0f4cdf5fd538212a9f3720116 F ext/fts3/fts3_porter.c e19807ce0ae31c1c6e9898e89ecc93183d7ec224ea101af039722a4f49e5f2b8 F ext/fts3/fts3_snippet.c 4d6523e3eddeb7b46e7a82b3476a0a86a0c04821e0e2b8dd40f45ee28057cb13 -F ext/fts3/fts3_term.c f45a1e7c6ef464abb1231245d123dae12266b69e05cc56e14045b76591ae92d1 +F ext/fts3/fts3_term.c 845f0e2456b1be42f7f1bec1da1dfc05bc347531eff90775ffc6698902c281de F ext/fts3/fts3_test.c d8d7b2734f894e8a489987447658e374cdd3a3bc8575c401decf1911cb7c6454 -F ext/fts3/fts3_tokenize_vtab.c a95feda3590f3c3e17672fe35b67ea6112471aeea4c07ef7744a6606b66549aa +F ext/fts3/fts3_tokenize_vtab.c 7fd9ef364f257b97218b9c331f2378e307375c592f70fd541f714e747d944962 F ext/fts3/fts3_tokenizer.c 6d8fc150c48238955d5182bf661498db0dd473c8a2a80e00c16994a646fa96e7 F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c b28f4cde90ed560245ecb76a882b45aa62da16ff6f61e9884eae5c7c5eff16ea +F ext/fts3/fts3_write.c 5bb4721330ca589f906e72bb824dd4080b313c6d4c4231fa541e9db32dc67982 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -88,25 +89,25 @@ F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a0 F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 F ext/fts5/fts5.h 05501612cc655504c5dce8ba765ab621d50fc478490089beaa0d75e00b23e520 F ext/fts5/fts5Int.h 78a63cc0795186cde5384816a9403a68c65774b35d952e05b81a1b4b158e07c8 -F ext/fts5/fts5_aux.c 572d5ec92ba7301df2fea3258576332f2f4d2dfd66d8263afd157d9deceac480 +F ext/fts5/fts5_aux.c 35c4101613eff86902877a4dedd9400b07922e412cbdd637b45041dce2fd5388 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d -F ext/fts5/fts5_index.c bf9c3b2fc1cbc2b996693e393bb708756d770de2e931602102c2f0adbf75db2c -F ext/fts5/fts5_main.c 8a16865b1652667c5a80592a4860a8fd40de283869d28da347a671c1c441933f -F ext/fts5/fts5_storage.c 3c9b41fce41b6410f2e8f82eb035c6a29b2560483f773e6dc98cf3cb2e4ddbb5 +F ext/fts5/fts5_index.c 730c9c32ada18ce1eb7ff847b36507f4b005d88d47af7b47db521e695a8ea4c7 +F ext/fts5/fts5_main.c a07ed863b8bd9e6fefb62db2fd40a3518eb30a5f7dcfda5be915dd2db45efa2f +F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca56d6601426d F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee -F ext/fts5/fts5_test_tok.c a2bed8edb25f6432e8cdb62aad5916935c19dba8dac2b8324950cfff397e25ff +F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b F ext/fts5/fts5_tokenize.c 5e251efb0f1af99a25ed50010ba6b1ad1250aca5921af1988fdcabe5ebc3cb43 F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80 -F ext/fts5/fts5_vocab.c 12138e84616b56218532e3e8feb1d3e0e7ae845e33408dbe911df520424dc9d6 +F ext/fts5/fts5_vocab.c aed56169ae5c1aa9b8189c779ffeef04ed516d3c712c06914e6d91a6759f4e4a F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba F ext/fts5/test/fts5_common.tcl a9de9c2209cc4e7ae3c753e783504e67206c6c1467d08f209cd0c5923d3e8d8b -F ext/fts5/test/fts5aa.test 5bd43427b7d08ce2e19c488a26534be450538b9232d4d5305049e8de236e9aa9 +F ext/fts5/test/fts5aa.test ba5158eba7d61359becdfca895ef471072c7bf7b20e5e60dcb4d024c8419c926 F ext/fts5/test/fts5ab.test bd932720c748383277456b81f91bc00453de2174f9762cd05f95d0495dc50390 F ext/fts5/test/fts5ac.test a7aa7e1fefc6e1918aa4d3111d5c44a09177168e962c5fd2cca9620de8a7ed6d F ext/fts5/test/fts5ad.test e8cf959dfcd57c8e46d6f5f25665686f3b6627130a9a981371dafdf6482790de @@ -120,7 +121,7 @@ F ext/fts5/test/fts5ak.test f459a64c9d38698af72a7c657ab6349bca96150241dd69fcce75 F ext/fts5/test/fts5al.test 00c4c1c6a1366b73aa48ce2068c634520867c3cf7f5d1676ebbb775ee1f35734 F ext/fts5/test/fts5alter.test 5565f7e4605512b69171ac18ca84398603f9f6456dbe377beeca97e83cc242cd F ext/fts5/test/fts5auto.test 78989e6527ce69c9eddbef7392fea5c10b0010cd2b2ae68eec7bc869c471e691 -F ext/fts5/test/fts5aux.test ebf6f2ff7cb556e83f66991b7f12bff016d3c83d4eab36704b649dd6b1437318 +F ext/fts5/test/fts5aux.test 3f194345fcd581f49f7fbb2e5495400efcc7d2835b77816328d8283c942f41b8 F ext/fts5/test/fts5auxdata.test eacc97ff04892f1a5f3d4df5a73f8bcbc3955ea1d12c9f24137eb1fc079e7611 F ext/fts5/test/fts5bigid.test 2860854c2561a57594192b00c33a29f91cb85e25f3d6c03b5c2b8f62708f39dd F ext/fts5/test/fts5bigpl.test 6466c89b38439f0aba26ac09e232a6b963f29b1cbe1304f6a664fe1e7a8f5fd3 @@ -130,16 +131,16 @@ F ext/fts5/test/fts5circref.test f880dfd0d99f6fb73b88ccacb0927d18e833672fd906cc4 F ext/fts5/test/fts5colset.test 7031ce84fb4d312df5a99fc4e7b324e660ccb513c97eccdef469bfd52d3d0f8f F ext/fts5/test/fts5columnsize.test 45459ce4dd9fd853b6044cdc9674921bff89e3d840f348ca8c1630f9edbf5482 F ext/fts5/test/fts5config.test 60094712debc59286c59aef0e6cf511c37d866802776a825ce437d26afe0817f -F ext/fts5/test/fts5conflict.test 655925678e630d3cdf145d18725a558971806416f453ac8410ca8c04d934238d +F ext/fts5/test/fts5conflict.test bf6030a77dbb1bedfcc42e589ed7980846c995765d77460551e448b56d741244 F ext/fts5/test/fts5connect.test 08030168fc96fc278fa81f28654fb7e90566f33aff269c073e19b3ae9126b2f4 -F ext/fts5/test/fts5content.test 213506436fb2c87567b8e31f6d43ab30aab99354cec74ed679f22aad0cdbf283 -F ext/fts5/test/fts5contentless.test eb31159d62811b3a32fb1cfb36be20f9d9db75637c7fe6aa7f5c533db2d00576 -F ext/fts5/test/fts5contentless2.test 12c778d134a121b8bad000fbf3ae900d53226fee840ce36fe941b92737f1fda7 -F ext/fts5/test/fts5contentless3.test 487dce16b6677f68b44d7cbd158b9b7275d25e2c14d713f9188d9645bb699286 -F ext/fts5/test/fts5contentless4.test 0f43ededc2874f65d7da99b641a82239854d98d3fa43db729f284b723f23b69f -F ext/fts5/test/fts5contentless5.test 216962d51376b62e4ff45e8db00aa3e1ab548cb36b7fd450633e73b2d678f88e -F ext/fts5/test/fts5corrupt.test 77ae6f41a7eba10620efb921cf7dbe218b0ef232b04519deb43581cb17a57ebe -F ext/fts5/test/fts5corrupt2.test 7453752ba12ce91690c469a6449d412561cc604b1dec994e16ab132952e7805f +F ext/fts5/test/fts5content.test 219a4e49386b9b197b9b7cadca97ea10ddff858ecd8b763a1cb8bb07575afc2a +F ext/fts5/test/fts5contentless.test 1cd1237894eeff11feb1ff8180044eac0b17dde22c181f7a722f2dcbfdb3377c +F ext/fts5/test/fts5contentless2.test 14c83bdacf8230f5f7ca74ecf2926b87d8a7cb788a69ce9937020428ac4fe192 +F ext/fts5/test/fts5contentless3.test 353d871c5ea08992aed3e2ebda0b1bdc35116cd24fe330fe7cf05be1e2b49fd7 +F ext/fts5/test/fts5contentless4.test dd33ead36b048c9447b81ec358bd4a27166c49ffaac65a54e95eabf59f338947 +F ext/fts5/test/fts5contentless5.test 96041cbf5ef781a68a5d0f0d18a88030c47a52b156b17876ed6ce36e80e27a7e +F ext/fts5/test/fts5corrupt.test b6d4034b682bb3387bc44c510c71b3c67d4349e4df139490fc0b69e6a972b99f +F ext/fts5/test/fts5corrupt2.test 99e7e23a58b4d89eb7167c6de1669cbc595cd3c79ab333e0eb56405473319e77 F ext/fts5/test/fts5corrupt3.test 7da9895dafa404efd20728f66ff4b94399788bdc042c36fe2689801bba2ccd78 F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3 F ext/fts5/test/fts5corrupt5.test eb6ba5ca28ef7c4c6b01e850d388cdb3dacc8c4c2f383f79d0a98128257742b4 @@ -166,11 +167,12 @@ F ext/fts5/test/fts5faultB.test d606bdb8e81aaeb6f41de3fc9fc7ae315733f0903fbff05c F ext/fts5/test/fts5faultD.test e7ed7895abfe6bc98a5e853826f6b74956e7ba7f594f1860bbf9e504b9647996 F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e4710c77eb8ce7075 F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1 +F ext/fts5/test/fts5faultG.test 340e59d2c2c1c7c379224f3968ee8d09b0f64bf56c5194217d1ded887b9d47c4 F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b996159f6909dc8079 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e F ext/fts5/test/fts5hash.test dc7bc7e0cdeb42cfce31294ad2f8fcf43192bfd0145bb7f3ecc5465d8c72696f -F ext/fts5/test/fts5integrity.test 62147a1e85405b986691177e0312be5a64ec9e67b17994e83892d9afa6247600 +F ext/fts5/test/fts5integrity.test 0d249d351163e17e2227aa9850e68193f88a7813d16cc7d51287e554bf915b3d F ext/fts5/test/fts5interrupt.test 09613247b273a99889808ef852898177e671406fe71fdde7ea00e78ea283d227 F ext/fts5/test/fts5lastrowid.test be98fe3e03235296585b72daad7aed5717ba0062bae5e5c18dd6e04e194c6b28 F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc2782680740513c4d1fc114b43d4ad @@ -178,13 +180,14 @@ F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fc F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2 -F ext/fts5/test/fts5misc.test 3947a481486156536f417a015343f05f9f367a17f282a4e02bdb95bb5bd43293 +F ext/fts5/test/fts5misc.test 5ca82f2a5ee016b0842043155d1382f98a34d0d86b2791165a44d7807f6e0f54 F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd F ext/fts5/test/fts5onepass.test f9b7d9b2c334900c6542a869760290e2ab5382af8fbd618834bf1fcc3e7b84da F ext/fts5/test/fts5optimize.test 36a752d24c818792032e4ff502936fc9cc5ef938721696396fdc79214b2717f1 -F ext/fts5/test/fts5optimize2.test c7c97693abe8a2cb572acfb1f252d78f03d3984094cfc5eb2285a76d8a702a92 +F ext/fts5/test/fts5optimize2.test 93e742c36b487d8874621360af5b1ce4d39b04fb9e71ce9bc34015c5fc811785 +F ext/fts5/test/fts5optimize3.test bf9c91bb927d0fb2b9a06318a217a0419183ac5913842e062c7e0b98ea5d0fca F ext/fts5/test/fts5phrase.test 13e5d8e9083077b3d9c74315b3c92ec723cc6eb37c8155e0bfe1bba00559f07b F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd116514f79c49c5a F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15 @@ -196,13 +199,14 @@ F ext/fts5/test/fts5rank.test 30f29e278cd7fb8831ba4f082feb74d8eb90c463bf07113ae2 F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde40a46fc5d785b451e7 F ext/fts5/test/fts5restart.test 835ecc8f449e3919f72509ab58056d0cedca40d1fe04108ccf8ac4c2ba41f415 F ext/fts5/test/fts5rowid.test b8790ec170a8dc1942a15aef3db926a5f3061b1ff171013003d8297203a20ad6 -F ext/fts5/test/fts5savepoint.test fc02929f238d02a22df4172625704e029f7c1e0e92e332d654375690f8e6e43f -F ext/fts5/test/fts5secure.test 833f987e6902a9ab20fabbaa7ca0662c6187fa368cbc7a8082fdf2bf22ee0304 +F ext/fts5/test/fts5savepoint.test 050796b24929325cdbbb2fbfe2794816ae95d298e940ae15032200c2f4a73725 +F ext/fts5/test/fts5secure.test a02f771742fb2b1b9bdcb4bf523bcf2d0aa1ff597831d40fe3e72aaa6d0ec40f F ext/fts5/test/fts5secure2.test 2e961d7eef939f294c56b5d895cac7f1c3a60b934ee2cfd5e5e620bdf1ba6bbc F ext/fts5/test/fts5secure3.test c7e1080a6912f2a3ac68f2e05b88b72a99de38543509b2bbf427cac5c9c1c610 F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b9846cf7fc8337b67c97 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a -F ext/fts5/test/fts5secure6.test a0a28cfb9bf9721408b65b5d7c7ce369af3d688e273da24d101c25d60cdce05c +F ext/fts5/test/fts5secure6.test 74bf04733cc523bccca519bb03d3b4e2ed6f6e3db7c59bf6be82c88a0ac857fd +F ext/fts5/test/fts5secure7.test fd03d0868d64340a1db8615b02e5508fea409de13910114e4f19eaefc120777a F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b F ext/fts5/test/fts5simple2.test 258a1b0c590409bfa5271e872c79572b319d2a56554d0585f68f146a0da603f0 @@ -212,7 +216,7 @@ F ext/fts5/test/fts5synonym2.test 8f891fc49cc1e8daed727051e77e1f42849c784a6a54be F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 -F ext/fts5/test/fts5trigram.test c76acc1913a06182e791a0dfdae285b9cdd67327a1a35b34cabf0a6aa09cf05e +F ext/fts5/test/fts5trigram.test 6c4e37864f3e7d90673db5563d9736d7e40080ab94d10ebdffa94c1b77941da0 F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602 F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9 @@ -232,46 +236,71 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 3deba6bc0bf37c1ee5f15d1ff3c3512ae2f3cf44a2b8ae7b4af92690514b0cb4 -F ext/jni/README.md 5c60e4580aa5c94ff74d7bef1fb6231e578f7764e831a07b5981b6ab62b35560 -F ext/jni/jar-dist.make 93da95f8fe01ef22fccacc27f2e805938058e91e8c72c0532558d3a812a42e74 -F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 -F ext/jni/src/c/sqlite3-jni.h 28565de9efc971195c684095ba0d184b90401290698c987f7ea3f54e47ff4f2f -F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 -F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 -F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c -F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 -F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8 -F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a -F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 -F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 01f890105c6b7edbbad1c0f5635f783cea62c4b2ae694a71e76514a936ee03ec -F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc -F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 -F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 -F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java d81f8bd43d2296ae373692370cfad16ddde76f5c14cd2760f7b4e1113ef56d4c -F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc -F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86 -F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 -F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65 -F ext/jni/src/org/sqlite/jni/Tester1.java 4253dc7bcff64500a9388f1a17d3d39dbe4eb9d7db9fc035ce6e2380d45ad5fc -F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 -F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d -F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d -F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8af53f70c4af6db24aab8d53d9ba -F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c -F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b -F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc -F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 -F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc -F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1f1286428fab38dfefe328e72b5735f533b19af8dd17712dd3df7e044d21c8b8 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e -F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d +F ext/jni/GNUmakefile 5c3ac326bf3853486ebe0d70819abc790cc65c412182ce4ebd5012b008d9b059 +F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4 +F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa +F ext/jni/src/c/sqlite3-jni.c 6f6df9657989e9ca2cfdcc2fe9a71c279de56d5c941adfd09a0f24256de35c8f +F ext/jni/src/c/sqlite3-jni.h b4c413a0d0c734683da1049cfcf89e35ae2719759d0656ec0f8c57188f18cab8 +F ext/jni/src/org/sqlite/jni/annotation/NotNull.java a99341e88154e70447596b1af6a27c586317df41a7e0f246fd41370cd7b723b2 +F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba +F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca +F ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1afa90d3f236f79cc7fcd2497e111992644f7596fbc8e8bcf7f1908ae00acd6c +F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java bc29e986c866c2ddbbb9f935f5b7264c1c1026864e50a4a735192864f75e37c0 +F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013 +F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 +F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca +F ext/jni/src/org/sqlite/jni/capi/CApi.java bccb442ca81cd4decb1adae99006a60b7a9f54e5153842e738c01104e97d1de0 +F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 0bfd6e56e8265c2f05c9207665707285534d78f8466ef0e0430c65677f00943d +F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a +F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95 +F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 29c002f3c638cc80f7db1594564a262d1beb32637824c3dca2d60a224d1f71d7 +F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4 +F ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 701f2e4d8bdeb27cfbeeb56315d15b13d8752b0fdbca705f31bd4366c58d8a33 +F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61 +F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 68f60aec7aeb5cd4e5fb83449037f668c63cb99f682ee1036cc226d0cbd909b9 +F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java aca8f9fa72e3b6602bc9a7dd3ae9f5b2808103fbbee9b2749dc96c19cdc261a1 +F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 819d938e26208adde17ca4b7ddde1d8cd6915b6ab7b708249a9787beca6bd6b6 +F ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 01bc0c238eed2d5f93c73522cb7849a445cc9098c2ed1e78248fa20ed1cfde5b +F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc2537a25ad1628f3638398d8a60cacefa7f +F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 105e324d09c207100485e7667ad172e64322c62426bb49b547e9b0dc9c33f5f0 +F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java fef556adbc3624292423083a648bdf97fa8a4f6b3b6577c9660dd7bd6a6d3c4a +F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1 +F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615 +F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f +F ext/jni/src/org/sqlite/jni/capi/Tester1.java ca195521b6bda3e0cd00e76bb71ec8060d1fab76a2f13b1af9feea40789f44bb +F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723 +F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4 +F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 9f9e151f1da017b706c0ee5f40f4c86b54e773d6ae4339723e0cc85a456251ab +F ext/jni/src/org/sqlite/jni/capi/WindowFunction.java caf4396f91b2567904cf94bc538a069fd62260d975bd037d15a02a890ed1ef9e +F ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java f3abb8dd7381f53ebba909437090caf68200f06717b8a7d6aa96fa3e8133117d +F ext/jni/src/org/sqlite/jni/capi/package-info.java 08ff986a65d2be9162442c82d28a65ce431d826f188520717c2ecb1484d0a50e +F ext/jni/src/org/sqlite/jni/capi/sqlite3.java 4010bbebc5bf44e2044e610786088cdee7dc155da2b333c0551492ff1cedf33b +F ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 6742b431cd4d77e8000c1f92ec66265a58414c86bf3b0b5fbcb1164e08477227 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java f204ab6ab1263e119fe43730141a00662d80972129a5351dfb11aae5d282df36 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java f0ef982009c335c4393ffcb68051809ca1711e4f47bcb8d1d46952f22c01bc22 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java ff579621e9bd5ffbc6b2ef9f996c12db4df6e0c8cc5697c91273e5fca279fcf8 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java e1d62a257c13504b46d39d5c21c49cf157ad73fda00cc5f34c931aa008c37049 +F ext/jni/src/org/sqlite/jni/fts5/Fts5.java e94681023785f1eff5399f0ddc82f46b035977d350f14838db659236ebdf6b41 +F ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 338637e6e5a2cc385d962b220f3c1f475cc371d12ae43d18ef27327b6e6225f7 +F ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java 7da0fbb5728f7c056a43e6407f13dd0c7c9c445221267786a109b987f5fc8a9d +F ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java 28045042d593a1f1b9b80d54ec77cbf1d8a1bc95e442eceefa9a3a6f56600b0e +F ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java 3c8f677ffb85b8782f865d6fcbc16200b3375d0e3c29ed541a494fde3011bf49 +F ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java eaee4d641229a098eb704b96a45c9a23c6514dc39009d3611e265bab33834deb +F ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java 1efd1220ea328a32f2d2a1b16c735864159e929480f71daad4de9d5944839167 +F ext/jni/src/org/sqlite/jni/fts5/fts5_api.java a8e88c3783d21cec51b0748568a96653fead88f8f4953376178d9c7385b197ea +F ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 9e2b954d210d572552b28aca523b272fae14bd41e318921b22f65b728d5bf978 +F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ade23d329843f809cd0d0f4f1a2856da6e6b4d90 +F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e +F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 5ad99bd74c85f56bbef324d9ec29b4048f4620547c9a80093d8586c3557f9f9a +F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03 +F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 004394eeb944baa56e36cd7ae69ba6d4a52b52db3c49439db16e98270b861421 +F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java a9ddc6a9e8c113168cc67592ae24c0e56d30dd06226eeab012f2761a0889d7bb +F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1386f7b753134fc12253ce2fbbc448ba8c970567fac01a3356cb672e14408d73 +F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java c24b510ebe801c30533cc62efdf69a4a5e2da9ec4b49f8d403f2060693f060a0 +F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af +F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 +F ext/jni/src/tests/900-001-fts.test bf0ce17a8d082773450e91f2388f5bbb2dfa316d0b676c313c637a91198090f0 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -312,70 +341,70 @@ F ext/lsm1/lsm_str.c 65e361b488c87b10bf3e5c0070b14ffc602cf84f094880bece77bbf6678 F ext/lsm1/lsm_tree.c 682679d7ef2b8b6f2fe77aeb532c8d29695bca671c220b0abac77069de5fb9fb F ext/lsm1/lsm_unix.c 11e0a5c19d754a4e1d93dfad06de8cc201f10f886b8e61a4c599ed34e334fc24 F ext/lsm1/lsm_varint.c fe134ad7b2db1ecd99b6a155d2f3625cfd497730e227ae18892452e457b73327 -F ext/lsm1/lsm_vtab.c e57aa3eb456bf2b98064014027e097c9402d6dec7b59564ddbfa1c0ead8f96c5 +F ext/lsm1/lsm_vtab.c 0bc7d2702150e9d5513118f23fdb5d7f3642884e6c0dde332da08b016857887a F ext/lsm1/lsm_win32.c 0a4acbd7e8d136dd3a5753f0a9e7a9802263a9d96cef3278cf120bcaa724db7c F ext/lsm1/test/lsm1_common.tcl 5ed4bab07c93be2e4f300ebe46007ecf4b3e20bc5fbe1dedaf04a8774a6d8d82 F ext/lsm1/test/lsm1_simple.test a04d08e8661ae6fc53786c67f0bd102c6692f003e859dde03ed9ac3f12e066e5 F ext/lsm1/tool/mklsm1c.tcl f31561bbee5349f0a554d1ad7236ac1991fc09176626f529f6078e07335398b0 F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f238c240 -F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358 +F ext/misc/amatch.c 5001711cbecdd57b288cb613386789f3034e5beb58fbe0c79f2b3d643ffd4e03 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824 F ext/misc/base64.c a71b131e50300c654a66c469a25b62874481f3d1cb3beb56aca9a68edd812e0d F ext/misc/base85.c 073054111988db593ef5fdb87ab8c459df1ea0c3aaaddf0f5bfa3d72b7e6280a F ext/misc/basexx.c 89ad6b76558efbceb627afd5e2ef1d84b2e96d9aaf9b7ecb20e3d00b51be6fcf F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a -F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9 -F ext/misc/carray.c 0ba03f1e6647785d4e05b51be567f5652f06941314ff9d3d3763900aa353b6b5 +F ext/misc/btreeinfo.c cb952620eedf5c0b7625b678f0f08e54d2ec0011d4e50efda5ebdc97f3df7d04 +F ext/misc/carray.c 34fac63770971611c5285de0a9f0ac67d504eaf66be891f637add9290f1c76a5 F ext/misc/carray.h 503209952ccf2431c7fd899ebb92bf46bf7635b38aace42ec8aa1b8d7b6e98a5 F ext/misc/cksumvfs.c 9224e33cc0cb6aa61ff1d7d7b8fd6fe56beca9f9c47954fa4ae0a69bef608f69 -F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c85c243 -F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9 +F ext/misc/closure.c 0e04f52d93e678dd6f950f195f365992edf3c380df246f3d80425cba4c13891e +F ext/misc/completion.c ef78835483b43ac18c96be312b90b615d8368189909be03513ab7a9338131298 F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 -F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73 +F ext/misc/csv.c 575c2c05fba0a451586a4d42c2c81e711780c41e797126f198d8d9e0a308dcdb F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 F ext/misc/decimal.c 172cf81a8634e6a0f0bedaf71a8372fee63348cf5a3c4e1b78bb233c35889fdc F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 -F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe -F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720 -F ext/misc/fossildelta.c 1240b2d3e52eab1d50c160c7fe1902a9bd210e052dc209200a750bbf885402d5 -F ext/misc/fuzzer.c eae560134f66333e9e1ca4c8ffea75df42056e2ce8456734565dbe1c2a92bf3d +F ext/misc/explain.c 606100185fb90d6a1eade1ed0414d53503c86820d8956a06e3b0a56291894f2b +F ext/misc/fileio.c d88e60f63557d76d4e38acffda5556b2ab42e98f5d830897f22aba65930d975c +F ext/misc/fossildelta.c 8c026e086e406e2b69947f1856fa3b848fff5379962276430d10085b8756b05a +F ext/misc/fuzzer.c 8b28acf1a7e95d50e332bdd47e792ff27054ad99d3f9bc2e91273814d4b31a5a F ext/misc/ieee754.c 62a90978204d2c956d5036eb89e548e736ca5fac0e965912867ddd7bb833256d -F ext/misc/memstat.c 3017a0832c645c0f8c773435620d663855f04690172316bd127270d1a7523d4d +F ext/misc/memstat.c 5b284b78be431c1f5fa154b18eade2407e42c65ed32ec9e9fbf195d114778d7d F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2d5537b F ext/misc/memvfs.c 7dffa8cc89c7f2d73da4bd4ccea1bcbd2bd283e3bb4cea398df7c372a197291b -F ext/misc/mmapwarm.c 347caa99915fb254e8949ec131667b7fae99e2a9ce91bd468efb6dc372d9b7a9 +F ext/misc/mmapwarm.c a81af4aaec00f24f308e2f4c19bf1d88f3ac3ce848c36daa7a4cd38145c4080d F ext/misc/nextchar.c 7877914c2a80c2f181dd04c3dbef550dfb54c93495dc03da2403b5dd58f34edd F ext/misc/noop.c 81efe4cad9ec740e64388b14281cb983e6e2c223fed43eb77ab3e34946e0c1ab F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d09ea61f F ext/misc/pcachetrace.c f4227ce03fb16aa8d6f321b72dd051097419d7a028a9853af048bee7645cb405 F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 -F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196 -F ext/misc/qpvtab.c 09738419e25f603a35c0ac8bd0a04daab794f48d08a9bc07a6085b9057b99009 +F ext/misc/prefixes.c 82645f79229877afab08c8b08ca1e7fa31921280906b90a61c294e4f540cd2a6 +F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47cc06c F ext/misc/randomjson.c 7dd13664155319d47b9facc0d8dbf45e13062966a47168e54e3f26d48240d7ea F ext/misc/regexp.c 4bdd0045912f81c84908bd535ec5ad3b1c8540b4287c70ab84070963624047db F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 -F ext/misc/series.c dde5ba69cb9053ff32b5afd64e8d202472325bc052301e31e4d9c0d87e4fff50 +F ext/misc/series.c 80692f675de989629ee20796f75010ce87ca826cb383131040e84f7e1bea5d7a F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d F ext/misc/shathree.c 543af7ce71d391cd3a9ab6924a6a1124efc63211fd0f2e240dc4b56077ba88ac F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 -F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f +F ext/misc/spellfix.c c0aa7b80d6df45f7da59d912b38752bcac1af53a5766966160e6c5cdd397dbea F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7 -F ext/misc/stmt.c bc30d60d55e70d0133f10ac6103fe9336543f673740b73946f98758a2bb16dd7 -F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4 +F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321 +F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b -F ext/misc/unionvtab.c 36237f0607ca954ac13a4a0e2d2ac40c33bc6e032a5f55f431713061ef1625f9 +F ext/misc/unionvtab.c 716d385256d5fb4beea31b0efede640807e423e85c9784d21d22f0cce010a785 F ext/misc/urifuncs.c f71360d14fa9e7626b563f1f781c6148109462741c5235ac63ae0f8917b9c751 F ext/misc/uuid.c 5bb2264c1b64d163efa46509544fd7500cb8769cb7c16dd52052da8d961505cf F ext/misc/vfslog.c 3932ab932eeb2601dbc4447cb14d445aaa9fbe43b863ef5f014401c3420afd20 -F ext/misc/vfsstat.c 474d08efc697b8eba300082cb1eb74a5f0f3df31ed257db1cb07e72ab0e53dfb -F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae +F ext/misc/vfsstat.c a85df08654743922a19410d7b1e3111de41bb7cd07d20dd16eda4e2b808d269d +F ext/misc/vtablog.c f2c9d41afe00b51b2c8307b79f508eb0c2dcd57bae074807944e73376fcf15b7 F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd -F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546 -F ext/misc/zipfile.c b1f36004c19fb5f949fb166fc4ab88e96a86f66629e9ddb4736a45b63fc3d553 +F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f65b4fc0668 +F ext/misc/zipfile.c 64cb3d98b6316586e6056d182051aa9d28fdedfbf4b908e6b7a7d70209b1db11 F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 @@ -424,7 +453,7 @@ F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69 F ext/rbu/sqlite3rbu.c d4ddf8f0e93772556e452a6c2814063cf47efb760a0834391a9d0cd9859fa4b9 F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 -F ext/recover/dbdata.c 81661e3a98cabb70be8f2760a67a8d6d5bf7aaa7a4055a53ff915ac884221a64 +F ext/recover/dbdata.c fc7147a68422cbbbaa481ee92ae1752cc25f5a24302bece1c70dcb76345bd736 F ext/recover/recover1.test c484d01502239f11b61f23c1cee9f5dd19fa17617f8974e42e74d64639c524cf F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a F ext/recover/recoverbuild.test c74170e0f7b02456af41838afeb5353fdb985a48cc2331d661bbabbca7c6b8e3 @@ -443,7 +472,7 @@ F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0 F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 -F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890 +F ext/repair/checkindex.c af5c66463f51462d8a6f796b2c44ef8cfa1116bbdc35a15da07c67a705388bfd F ext/repair/sqlite3_checker.c.in 445118c5f7fea958b36fba1b2c464283e60ed4842039ddee3265f1698115ebf7 F ext/repair/sqlite3_checker.tcl a9a2caa9660567257c177a91124d8c0dccdfa341e25c51e6da7f1fd9e601eafa F ext/repair/test/README.md 34b2f542cf5be7bffe479242b33ee3492cea30711e447cc4a1a86cb5915f419e @@ -451,19 +480,19 @@ F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc782 F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c335096108c12c01bddbadcec F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/geopoly.c 971e0b5bd9adaf0811feb8c0842a310811159da10319eb0e74fdb42bf26b99ca -F ext/rtree/rtree.c 6954f4a3ca51c2e3db35c52e0513f3520999eb7a967f3d53b71db7ebddd8b3a5 +F ext/rtree/geopoly.c 0dd4775e896cee6067979d67aff7c998e75c2c9d9cd8d62a1a790c09cde7adca +F ext/rtree/rtree.c 2e1452a9338fe4db057fa677277bed86b65c667ed48b9b59144adae99f85a7cb F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 -F ext/rtree/rtree1.test 877d40b8b61b1f88cec9d4dc0ff8334f5b05299fac12a35141532e2881860e9d +F ext/rtree/rtree1.test 2b5b8c719c6a4abe377f57766f428a49af36a93061cb146cccfdc3b30000c0a4 F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d F ext/rtree/rtree3.test 272594f88c344e973864008bbe4c71fd3a41a264c097d568593ee7886d83d409 F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2e55d3142 F ext/rtree/rtree6.test 2f5ffc69670395c1a84fad7924e2d49e82a25460c5293fb1e54e1aa906f04945 F ext/rtree/rtree7.test c8fb2e555b128dd0f0bdb520c61380014f497f8a23c40f2e820acc9f9e4fdce5 -F ext/rtree/rtree8.test 2d99006a1386663978c9e1df167554671e4f711c419175b39f332719deb1ce0e +F ext/rtree/rtree8.test 4da84c7f328bbdca15052fa13da6e8b8d426433347bf75fc85574c2f5a411a02 F ext/rtree/rtree9.test fd3c9384ef8aabbc127b3878764070398f136eebc551cd20484b570f2cc1956a -F ext/rtree/rtreeA.test a7fd235d8194115fa2e14d300337931eb2e960fe8a46cdfb66add2206412ea41 +F ext/rtree/rtreeA.test 14e67fccc5b41efbad7ea99d21d11aaa66d2067da7d5b296ee86e4de64391d82 F ext/rtree/rtreeB.test 4cec297f8e5c588654bbf3c6ed0903f10612be8a2878055dd25faf8c71758bc9 F ext/rtree/rtreeC.test 2978b194d09b13e106bdb0e1c5b408b9d42eb338c1082bf43c87ef43bd626147 F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc @@ -473,14 +502,14 @@ F ext/rtree/rtreeG.test 1b9ca6e3effb48f4161edaa463ddeaa8fca4b2526d084f9cbf5dbe4e F ext/rtree/rtreeH.test 0885151ee8429242625600ae47142cca935332c70a06737f35af53a7bd7aaf90 F ext/rtree/rtreeI.test 608e77f7fde9be5a12eae316baef640fffaafcfa90a3d67443e78123e19c4ca4 F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195 -F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b587936f5f6bceed -F ext/rtree/rtreecheck.test 4e859a9cd49d2353ff10c122f72183ec37b400e35d2b0349b2e9696649b6a00e +F ext/rtree/rtree_util.tcl 202ca70df1f0645ef9d5a2170e62d378a28098d9407f0569e85c9c1cf1bd020a +F ext/rtree/rtreecheck.test 934546ad9b563e090ee0c5cbdc69ad014189ad76e5df7320526797a9a345661f F ext/rtree/rtreecirc.test aec664eb21ae943aeb344191407afff5d392d3ae9d12b9a112ced0d9c5de298e F ext/rtree/rtreeconnect.test 225ad3fcb483d36cbee423a25052a6bbae762c9576ae9268332360c68c170d3d -F ext/rtree/rtreedoc.test 27a5703cb1200f6f69051de68da546cef3dfdcf59be73afadfc50b9f9c9960d9 +F ext/rtree/rtreedoc.test d633982d61542f3bc0a0a2df0382a02cc699ac56cbda01130cde6da44a228490 F ext/rtree/rtreedoc2.test 194ebb7d561452dcdc10bf03f44e30c082c2f0c14efeb07f5e02c7daf8284d93 F ext/rtree/rtreedoc3.test 555a878c4d79c4e37fa439a1c3b02ee65d3ebaf75d9e8d96a9c55d66db3efbf8 -F ext/rtree/rtreefuzz001.test 0fc793f67897c250c5fde96cefee455a5e2fb92f4feeabde5b85ea02040790ee +F ext/rtree/rtreefuzz001.test 44f680a23dbe00d1061dbde381d711119099846d166580c4381e402b9d62cb74 F ext/rtree/sqlite3rtree.h 03c8db3261e435fbddcfc961471795cbf12b24e03001d0015b2636b0f3881373 F ext/rtree/test_rtreedoc.c de76b3472bc74b788d079342fdede22ff598796dd3d97acffe46e09228af83a3 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de @@ -492,7 +521,7 @@ F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8 F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a F ext/session/session1.test e94f764fbfb672147c0ef7026b195988133b371dc8cf9e52423eba6cad69717e F ext/session/session2.test ee83bb973b9ce17ccce4db931cdcdae65eb40bbb22089b2fe6aa4f6be3b9303f -F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479 +F ext/session/session3.test 2cc1629cfb880243aec1a7251145e07b78411d851b39b2aa1390704550db8e6a F ext/session/session4.test 6778997065b44d99c51ff9cece047ff9244a32856b328735ae27ddef68979c40 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 F ext/session/session6.test 35279f2ec45448cd2e24a61688219dc6cf7871757716063acf4a8b5455e1e926 @@ -508,13 +537,16 @@ F ext/session/sessionG.test 3efe388282d641b65485b5462e67851002cd91a282dc95b685d0 F ext/session/sessionH.test 71bbff6b1abb2c4ac62b84dee53273c37e0b21e5fde3aed80929403e091ef859 F ext/session/session_common.tcl e5598096425486b363718e2cda48ee85d660c96b4f8ea9d9d7a4c3ef514769da F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3 +F ext/session/sessionalter.test 460bdac2832a550519f6bc32e5db2c0cee94f335870aaf25a3a403a81ab20e17 F ext/session/sessionat.test 00c8badb35e43a2f12a716d2734a44d614ff62361979b6b85419035bc04b45ee F ext/session/sessionbig.test 47c381e7acfabeef17d98519a3080d69151723354d220afa2053852182ca7adf F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test 573bf027fb870d57bd4e7cf50822a3e4b17b2b923407438747aaa918dec57a09 F ext/session/sessionfault2.test b0d6a7c1d7398a7e800d84657404909c7d385965ea8576dc79ed344c46fbf41c +F ext/session/sessionfault3.test 7c7547202775de268f3fe6f074c4d0d165151829710b4e64f90d4a01645ba9e7 F ext/session/sessioninvert.test 04075517a9497a80d39c495ba6b44f3982c7371129b89e2c52219819bc105a25 F ext/session/sessionmem.test f2a735db84a3e9e19f571033b725b0b2daf847f3f28b1da55a0c1a4e74f1de09 +F ext/session/sessionnoact.test 506526a5fe29421ecc50d371774ef1bb04cbd9d906a8a468f0556cdbde184c22 F ext/session/sessionnoop.test a9366a36a95ef85f8a3687856ebef46983df399541174cb1ede2ee53b8011bc7 F ext/session/sessionnoop2.test de4672dce88464396ec9f30ed08c6c01643a69c53ae540fadbbf6d30642d64e8 F ext/session/sessionrebase.test 702378bdcb5062f1106e74457beca8797d09c113a81768734a58b197b5b334e2 @@ -522,16 +554,21 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c 0fe9107318140cefa1b50f2e1e0f330ab359022599e5976820db349f33efae11 -F ext/session/sqlite3session.h 653e9d49c4edae231df8a4c8d69c2145195aedb32462d4b44229dbee7d2680fb -F ext/session/test_session.c 5285482f83cd92b4c1fe12fcf88210566a18312f4f2aa110f6399dae46aeccbb +F ext/session/sqlite3session.c 7b0f9bf6479aebd49e5f34cdaeb1479cde843cf5189bca6f32db4f36c130b414 +F ext/session/sqlite3session.h 4cf19a51975746d7cff2fdd74db8b769c570958e1c3639ac150d824ac1553b3e +F ext/session/test_session.c 7b94ad945cd4afe6c73ee935aeb3d44b4446186e1729362af616c7695a5283d9 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 7eea788d3cde04135da82451a0afcd22f264157544ba69464a9285319a44b41d +F ext/wasm/GNUmakefile 0e362f3fc04eab6628cbe4f1e35f4ab4a200881f6b5f753b27fb45eabeddd9d2 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 +F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff +F ext/wasm/SQLTester/SQLTester.mjs ec2f6ba63a0f2f0562941a0fb8e46b7dc55589711513f1952349785966edfe50 +F ext/wasm/SQLTester/SQLTester.run.mjs c72b7fe2072d05992f7a3d8c6a1d34e95712513ceabe40849784e24e41c84638 +F ext/wasm/SQLTester/index.html 3f8a016df0776be76605abf20e815ecaafbe055abac0e1fe5ea080e7846b760d +F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 @@ -544,16 +581,16 @@ F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057af F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e F ext/wasm/api/sqlite3-api-glue.js 26aedfb27915f4f316f6eac84078443e4f0d2dfe5f012310014923ed4b77b2b6 F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8 -F ext/wasm/api/sqlite3-api-prologue.js 5f283b096b98bfb1ee2f2201e7ff0489dff00e29e1030c30953bdb4f5b87f4bd -F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b1738645c0134562bb84e88e2fec +F ext/wasm/api/sqlite3-api-prologue.js 9aeba7b45cf41b3a26d34d7fb2525633cd1adfc544888c1ea8dbb077496f4ce9 +F ext/wasm/api/sqlite3-api-worker1.js f941382f21006b4a817754184e2661b0a63ce650201f3419cd60f4758b6fd60e F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 -F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js c7caf5ccd85d6e92bee4285871d51bc0e3e635b3e2d76f50515f5482a0ec0239 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js cf0a2a182d16e71c5ccb1a870bfdba0d0da5e2fe5542907b036e92b3db5982c3 -F ext/wasm/api/sqlite3-wasm.c d4d4c2b349b43b7b861e6d2994299630fb79e07573ea6b61e28e8071b7d16b61 +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 595953994aa3ae2287c889c4da39ab3d6f17b6461ecf4bec334b7a3faafddb02 +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 46c4afa6c50d7369252c104f274ad977a97e91ccfafc38b400fe36e90bdda88e +F ext/wasm/api/sqlite3-wasm.c 038de1b6d40b2cc0f41a143a0451db60b2a6f1b5bc06de67da255c54ea1661b7 F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f -F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75 +F ext/wasm/api/sqlite3-worker1.c-pp.js a541112aa51e16705f13a99bb943c64efe178aa28c86704a955f8fd9afe4ba37 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4 @@ -596,7 +633,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 -F ext/wasm/tester1.c-pp.js 64eb0ee6e695d5638d0f758f31a0ca2231e627ca5d768de3d8b44f9f494de8d4 +F ext/wasm/tester1.c-pp.js fb20d9e1c308ea34a29d8afdda1a6c5edc406241b5560fa23c19c14747ee41bc F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -604,7 +641,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk e18c03071dbb9da424212ba2a1ea331ae0e8f6d7d51283b7ad610c52ea11229c +F main.mk 51fd5fc8565007266e6bfd9e299199c752b171707a667b8914a204b38258e1b1 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -616,39 +653,39 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 -F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5 +F src/alter.c 30c2333b8bb3af71e4eb9adeadee8aa20edb15917ed44b8422e5cd15f3dfcddc F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39 F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 -F src/btree.c 15611f72bd65aef23b3ae4026164f86e2466791be5b9745f8421d0eed3c5238a +F src/btree.c f3b09c5414de3a11db73e11e1d66f4c5e53c9e89876ff3b531a887ab656ca303 x F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 -F src/btreeInt.h 91a9e0c41a0e71fa91a742ec285c63dd8dcb38b73d14fae0ed7209174ff0fdc1 -F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4 +F src/btreeInt.h ef12a72b708677e48d6bc8dcd66fed25434740568b89e2cfa368093cfc5b9d15 +F src/build.c 189e4517d67f09f0a3e0d8e1faa6e2ef0c2e95f6ac82e33c912cb7efa2a359cc F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c cff2b493de894383832347c3a3f936a05f4e089a92b42366838da1735f3848b5 -F src/date.c f73f203b3877cef866c60ab402aec2bf89597219b60635cf50cbe3c5e4533e94 -F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387 -F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef +F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b +F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574 +F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 +F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 -F src/expr.c 1affe0cc049683ef0ef3545d9b6901508556b0ef7e2892a344c3df6d7288d79d +F src/expr.c 433f12e1237524482b0b2681c07da3cd54ddada2a625237cecde419f3e3a2553 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 -F src/func.c 3c8e02958a3d4272659fcb234133b7572794ece60571779e88dd96ec16bae0d6 +F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 +F src/func.c 472f6dcfa39cf54f89a6aec76c79c225fb880a6c14469c15d361331662b9bf43 F src/global.c 29f56a330ed9d1b5cd9b79ac0ca36f97ac3afc730ff8bfa987b0db9e559d684d F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c 5c25f82e1c0f3eb44fb9a23feff6b7b952fd40122d9e3ab601a84ea83710a980 +F src/json.c d69c6e28ff7b602877bda68cd20583b8487c059759aa4d154dd21b3fd99c6238 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 98cfba10989b3da6f1807ad42444017742db7f100a54f1032af7a8b1295912c0 -F src/main.c fde8f13c876a658b4e8b74b77d875ca887915c174ea6a2f3122d80966f93d865 -F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 +F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 +F src/main.c e1bc8864834697503d370d94613be945d05ca1c5ebdda43e7d5c8ee8c48d433c +F src/malloc.c f016922435dc7d1f1f5083a03338a3e91f8c67ce2c5bdcfa4cdef62e612f5fcc F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 @@ -657,66 +694,66 @@ F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff F src/memdb.c 559c42e61eb70cd6d4bc692b042497133c6d96c09a3d514d92f3dac72268e223 F src/memjournal.c c283c6c95d940eb9dc70f1863eef3ee40382dbd35e5a1108026e7817c206e8a0 F src/msvc.h 80b35f95d93bf996ccb3e498535255f2ef1118c78764719a7cd15ab4106ccac9 -F src/mutex.c 5e3409715552348732e97b9194abe92fdfcd934cfb681df4ba0ab87ac6c18d25 +F src/mutex.c 1b4c7e5e3621b510e0c18397210be27cd54c8084141144fbbafd003fde948e88 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 -F src/mutex_unix.c bd52ec50e44a41fe1e3deb5a6e3fe98edb6f2059da3e46d196363d0fa3192cda +F src/mutex_unix.c f7ee5a2061a4c11815a2bf4fc0e2bfa6fb8d9dc89390eb613ca0cec32fc9a3d1 F src/mutex_w32.c 38b56d0bc8d54c17c20cbaaad3719b0c36b92fd07a7e34360d0c6a18d5589912 -F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 +F src/notify.c 57c2d1a2805d6dee32acd5d250d928ab94e02d76369ae057dee7d445fd64e878 F src/os.c 509452169d5ea739723e213b8e2481cf0e587f0e88579a912d200db5269f5f6d F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c 2e8b12107f75d1bd16412f312b4c5d5103191807a37836d3b81beb26436ad81b +F src/os_unix.c cb116fde9e3ca3c1bbfdf89d6928f776a2a34da168e2667426523a4db353b271 F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 993445a19b611d473ca007542ab3149840661a4c7e9f2d9e1ec008b7cc2abe78 +F src/pager.c 699aab8dfc88056d796b03b40c0ab979040d58dfc3ae9db207f1be91e4880bbf F src/pager.h f4d33fec8052603758792045493423b8871a996da2d0973927b7d36cd6070473 -F src/parse.y aeb7760d41cfa86465e3adba506500c021597049fd55f82a30e5b7045862c28c +F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 -F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a +F src/pragma.c b3b4ad9c0298d63098a067acca613c21a5f56b4d176d5842922bcd0b07b7164e F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c 80548297dc0e1fb3139cdebffb5a1bcac3dfac66d791012dd74838e70445072d -F src/printf.c e3ba080e2f409f9bfcc8d34724e6fc160e9c718dc92d0548f6b71b8b6f860ce2 +F src/prepare.c bde74add20fc0e8ce0c4e937a1f70a36d17413afe4f71d3e103f5cb74b17c8d9 +F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 +F src/resolve.c 31229276a8eb5b5de1428cd2d80f6f1cf8ffc5248be25e47cf575df12f1b8f23 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c e9fb48546ab1882639a3a960383f6342dddb776c0227615f8e19de51f0102f68 -F src/shell.c.in d381ea090c17db5d50049e6c06e9e175d8d712c7f9bc7a0b8a51616af44f060c -F src/sqlite.h.in 73a366c1c45d5ac9888cfe81c458826a44498531d106cfb4f328193ab5f6f17d +F src/select.c a19daa26e95f7245106a31f288b2f50c72d1f2cc156703f04c8c91450e111515 +F src/shell.c.in 3e9371ca6a93294931a8ed8b098bc3cb15d57567aa9a1f2ade72db7f5c572795 +F src/sqlite.h.in ef0e41e83ad1ac0dcc9ec9939bf541a44b1c5de821bee2d6c61754c3252f3276 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h 2f30b2671f4c03cd27a43f039e11251391066c97d11385f5f963bb40b03038ac -F src/sqliteInt.h 025ed58a41968ef80d64cdc194caa8dd207b0256b147253d762fdac7a62408f9 +F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 +F src/sqliteInt.h 567e317f8631883897b7d3da43fce778b7c30dd0dd7f714558c9725fc1c1196c F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd -F src/test1.c ebba2473874a23add4a10881b90bd445cfa7f59f90749434938aec14239c6486 +F src/test1.c f9620e8f0d0fa4edb239201a732c4dd1562f0cdd9741955c89332d49e14a5edd F src/test2.c 54520d0565ef2b9bf0f8f1dcac43dc4d06baf4ffe13d10905f8d8c3ad3e4b9ab F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d F src/test6.c e53bc69dc3cb3815fb74df74f38159ec05ba6dd5273216062e26bc797f925530 -F src/test8.c ccc5d3e2a2bf7248f7da185e2afc4c08b4c6840447f5eb4dd106db165fddbdbc +F src/test8.c 303c2e3bcf7795e888810a7ef03809602b851f0ebec8d6e06a429ed85cafd9a2 F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 -F src/test_bestindex.c 68c62586d2ae9f032903fe53be743657d0c2aac0a850b880938b668e1161d516 +F src/test_bestindex.c f6af1e41cb7901edafb065a8198e4a0192dd42432b642d038965be5e628dec12 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c f0cc1f517deaa96dd384822ae2bb91534fa56aa458528b439830d709941d3932 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f F src/test_demovfs.c 38a459d1c78fd9afa770445b224c485e079018d6ac07332ff9bd07b54d2b8ce9 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 -F src/test_fs.c ba1e1dc18fd3159fdba0b9c4256f14032159785320dfbd6776eb9973cb75d480 +F src/test_fs.c 56cc17e4fdc57efa61695026e2ba96e910b17060d7ee01d775ec048791522e2f F src/test_func.c 24df3a346c012b1fc9e1001d346db6054deb426db0a7437e92490630e71c9b0a F src/test_hexio.c 9478e56a0f08e07841a014a93b20e4ba2709ab56d039d1ca8020e26846aa19bd F src/test_init.c f2cc4774b7c9140f76e45ecbb2ae219f68e3acbbe248c0179db666a70eae9f08 -F src/test_intarray.c 39b4181662a0f33a427748d87218e7578d913e683dc27eab7098bb41617cac71 +F src/test_intarray.c 26ffba666beb658d73cd925d9b4fb56913a3ca9aaeac122b3691436abb192b92 F src/test_intarray.h 6c3534641108cd1bea517a8e117dcba237081310a29a4c35bd2190caa8972293 F src/test_journal.c a0b9709b2f12b1ec819eea8a1176f283bca6d688a6d4a502bd6fd79786f4e287 F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd @@ -726,17 +763,17 @@ F src/test_multiplex.c 70479161239d65af2a231550b270e9d11ece717ad7bf0e13ef4220658 F src/test_multiplex.h f0ff5b6f4462bfd46dac165d6375b9530d08089b7bcbe75e88e0926110db5363 F src/test_mutex.c cd5bac43f2fd168f43c4326b1febe0966439217fac52afb270a6b8215f94cb40 F src/test_onefile.c f31e52e891c5fef6709b9fcef54ce660648a34172423a9cbdf4cbce3ba0049f4 -F src/test_osinst.c d341f9d7613e007c8c3f7eba6cd307230047506aa8f97858c1fd21f5069616bd +F src/test_osinst.c 8e11faf10f5d4df10d3450ecee0b8f4cfa2b62e0f341fafbeb480a08cefeaec4 F src/test_pcache.c 3960cd2c1350adc992c4bf7adcfb0d1ac0574733012bd1a5f94e195928577599 F src/test_quota.c ea44c05f29b995bdb71c55eb0c602604884e55681d59b7736e604bbcc68b0464 F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b -F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b +F src/test_schema.c cbfd7a9a9b6b40d4377d0c76a6c5b2a58387385977f26edab4e77eb5f90a14ce F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 9fdb13b1df05e639808d44fcb8f6064aaded32b6565c00b215cfd05a060d1aca F src/test_tclsh.c 3ff5d188a72f00807425954ea3b493dfd3a4b890ecc6700ea83bad2fd1332ecf -F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc +F src/test_tclvar.c 3273f9d59395b336e381b53cfc68ec6ebdaada4e93106a2e976ffb0550504e1c F src/test_thread.c 7ddcf0c8b79fa3c1d172f82f322302c963d923cdb503c6171f3c8081586d0b01 F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43 F src/test_vfs.c 193c18da3dbf62a0e33ae7a240bbef938a50846672ee947664512b77d853fe81 @@ -747,48 +784,49 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 -F src/treeview.c 1d52fbc4e97161e65858d36e3424ea6e3fc045dd8a679c82b4b9593dc30de3bd -F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0 +F src/treeview.c 62fafcd31eea60b718f8daf448116b7b19f90134ebc6c20777ddbb07f56a3d28 +F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c 79f084901f52d9d7025200a177f7d1d66f83a73333fe486a2626490eaa49bc4c +F src/util.c b22cc9f203a8c0b9ee5338a67f8860347d14845864c10248bebe84518a781677 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 -F src/vdbe.c 346d848a0bf8128e3e3722c5406f4bde6c32d7093b93402c6f8e0718d19305c3 +F src/vdbe.c 14479441337135eed8e290fb1d4abb7db657d93217a3b1ea8a2f031d3895536a F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c -F src/vdbeapi.c 37341acd781fda162e8cf4d9fc2eaea2febad3b365877a9d7233b8c6d0960d85 -F src/vdbeaux.c e3aa5c46827cd95e0fc4d0f302fa3e901ab5f07258fdbb42709eeef40f63018d -F src/vdbeblob.c 4cf5aa130e96e3b52ba3fb54b7f9606c942ab988dbb32cb19cff4db24e06aeec -F src/vdbemem.c 317b9f48708139db6239ade40c7980b4bc8233168383690d588dad6d8437f722 -F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 +F src/vdbeapi.c fe654b1f54e1feebcaed6c2ae3ed035cc65bfeb9a1169bed866abc42bfc63ff6 +F src/vdbeaux.c dffcf79e7e415fcd6e4c8ac1ec7124cae5257018443adf09551c807655b04993 +F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5 +F src/vdbemem.c c936e9002af4993b84c4eb7133d6b1190efe46d391cc86117ecd67ba17b1a04b +F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 -F src/vdbevtab.c 57fa8f56478e5b5cb558cb425e7878515e0a105c54f96f1d1bbf4b9433529254 -F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 +F src/vdbevtab.c 2143db7db0ceed69b21422581f434baffc507a08d831565193a7a02882a1b6a7 +F src/vtab.c 154725ebecd3bc02f7fbd7ad3974334f73fff76e02a964e828e48a7c5fb7efff F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c b8917792f1e0dbfa28fb29e6cd3d560060d69667be0ba4c491cbc772363264f5 -F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a +F src/where.c 313ce81270d2a414672370e1ee74e65949ad620519193d4cac2986d073cbc8a0 +F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 -F src/window.c b7ad9cff3ce8ae6f8cc25e18e1a258426cb6bd2999aace6f5248d781b2a74098 +F src/window.c ad21e2b73ec75acc79dde2576c573f54a338b0c49e9de847ce984f9b9595b5e2 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggfault.test 777f269d0da5b0c2524c7ff6d99ae9a93db4f1b1839a914dd2a12e3035c29829 -F test/aggnested.test 7269d07ac879fce161cb26c8fabe65cba5715742fac8a1fccac570dcdaf28f00 +F test/aggnested.test 2e738bfe2980df301a782f6e7bbf9459266f64f7e72f58f3b5c843bf897c568c +F test/aggorderby.test e6b98dbbf3ababa96892435d387de2dcf602ef02c2b848d2d817473066f154ba F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 -F test/alter.test 313073774ab5c3f2ef1d3f0d03757c9d3a81284ae7e1b4a6ca34db088f886896 +F test/alter.test 403a7f8842457044a994d0ffb42963d6e84fcfbf5e8f54556063b25d966cd454 F test/alter2.test a966ccfcddf9ce0a4e0e6ff1aca9e6e7948e0e242cd7e43fc091948521807687 F test/alter3.test ffc4ab29ce78a3517a66afd69b2730667e3471622509c283b2bd4c46f680fba3 F test/alter4.test 716caa071dd8a3c6d57225778d15d3c3cbf5e34b2e84ae44199aeb2bbf50a707 F test/alterauth.test 63442ba61ceb0c1eeb63aac1f4f5cebfa509d352276059d27106ae256bafc959 F test/alterauth2.test 48967abae0494d9a300d1c92473d99fcb66edfcc23579c89322f033f49410adc -F test/altercol.test 8465ca659c2c55a359cf16cc261df4fcb5c45a5f104a50827c337ae66c09dc15 +F test/altercol.test 29fed774747777fbbaacdd865b4413ed2d0844a4c824f8af531b5c7d4a832087 F test/altercorrupt.test 2e1d705342cf9d7de884518ddbb053fd52d7e60d2b8869b7b63b2fda68435c12 F test/alterdropcol.test a653a3945f964d26845ec0cd0a8e74189f46de3119a984c5bc45457da392612e F test/alterdropcol2.test 527fce683b200d620f560f666c44ae33e22728e990a10a48a543280dfd4b4d41 @@ -796,12 +834,12 @@ F test/alterfault.test 289067108947bedca27534edd4ff251bcd298cf84402d7b24eaa37493 F test/alterlegacy.test f38c6d06cda39e1f7b955bbce57f2e3ef5b7cb566d3d1234502093e228c15811 F test/altermalloc.test 167a47de41b5c638f5f5c6efb59784002b196fff70f98d9b4ed3cd74a3fb80c9 F test/altermalloc2.test 17fb3724c4b004c469c27dc4ef181608aa644555fbd3f3236767584f73747c81 -F test/altermalloc3.test 8531b3086f0a7889f43971a579a8c81832d5123f4703d8c86b89ba1136c63b9a -F test/alterqf.test ff6c6f881485c29ed699b8ef4774864ca1b0c01a6c08f5cdd624a008e4b40fca +F test/altermalloc3.test 8040e486368403f2fdd6fc3998258b499bd4cc2f3ddbb5f8f874cd436f076e81 +F test/alterqf.test 8ec03d776de9c391daa0078ea8f838903bdcfb11dfae4ba3576b48436834ccba F test/altertab.test 8a2712f9076da5012a002d0b5cc0a421398a5bf61c25bab41b77c427586a7a27 F test/altertab2.test 62597b6fd08feaba1b6bfe7d31dac6117c67e06dc9ce9c478a3abe75b5926de0 F test/altertab3.test 6c432fbb9963e0bd6549bf1422f6861d744ee5a80cb3298564e81e556481df16 -F test/altertrig.test fb5951d21a2c954be3b8a8cf8e10b5c0fa20687c53fd67d63cea88d08dd058d5 +F test/altertrig.test aacc980b657354fe2d3d4d3a004f07d04ccc1a93e5ef82d68a79088c274ddc6b F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 2fb21d7d64748636384e6cb8998dbf83968caf644c07fcb4f76c18f2e7ede94b F test/analyze3.test 03f4b3d794760cf15da2d85a52df9bae300e51c8fefe9c36cfae1f86dc10d23f @@ -865,8 +903,9 @@ F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4 F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e F test/bestindex8.test 333ad8c6a554b885a49b68c019166eda92b05f493a92b36b0acdf7f766d04dad -F test/bestindex9.test bf2eb8556e8d5c00ef3ee18c521751cd03c1b55454b6e7683b4c6742e3131b23 -F test/bestindexA.test dd7b7439a46169b45d0305c4cbbb14fc20c7044acc2055c767d2f838b3479c3f +F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0 +F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f +F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -904,8 +943,8 @@ F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe F test/carray01.test 23ed7074307c4a829ba5ff2970993a9d87db7c5cdbbe1a2cbef672d0df6d6e31 F test/cast.test af2286fdd28f3470b7dcad23977282b8cc117747ad55acff74a770dad3b19398 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef -F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b -F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9 +F test/changes.test 4377d202a487f66fc2822c1bf57c46798c8b2caf7446f4f701723b1dbb6b86f6 +F test/changes2.test 07949edcc732af28cb54276bfb7d99723bccc1e905a423648bf57ac5cb0dc792 F test/check.test 56e4ed457e9f8683b9fc56f5b964f461f6e8a8dd5a13f3d495408215d66419ed F test/checkfault.test da6cb3d50247169efcb20bdf57863a3ccfa1d27d9e55cd324f0680096970f014 F test/chunksize.test 427d87791743486cbf0c3b8c625002f3255cb3a89c6eba655a98923b1387b760 @@ -974,16 +1013,17 @@ F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c47 F test/cursorhint.test 05cf0febe5c5f8a31f199401fd1c9322249e753950d55f26f9d5aca61408a270 F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8 -F test/date.test 1d44557f668298b10d3335b22ab8feb133267b67ec4d85538908fe4dfebd2611 +F test/date.test c0d17cdfd89395bc78087b131e3538d96f864b5029c335318011accc7c0d0934 F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1 F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5 +F test/date4.test 8aeb3de5b5e9fda968baa9357e4c0fae573724b7904943410195a19e96e31b6a F test/dbdata.test 042f49acff3438f940eeba5868d3af080ae64ddf26ae78f80c92bec3ca7d8603 F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e -F test/dbfuzz001.test 55e1a3504f8dea84155e09912fe3b1c3ad77e0b1a938ec42ca03b8e51b321e30 +F test/dbfuzz001.test 6c9a4622029d69dc38926f115864b055cb2f39badd25ec22cbfb130c8ba8e9c3 F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23 F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e887d -F test/dbpagefault.test d9111a62f3601d3efc6841ace3940181937342d245f92a1cca6cba8206d4f58a +F test/dbpagefault.test 35f06cfb2ef100a9b19d25754e8141b9cba9b7daabd4c60fa5af93fcce884435 F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test ef731887b43ee32ef86e1c8fddb61a40789f988332c029c601dcf2c319277e9e @@ -999,7 +1039,7 @@ F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a199 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e F test/distinct.test 691c9e850b0d0b56b66e7e235453198cb4cf0760e324b7403d3c5abbeab0a014 F test/distinct2.test bb71cc7b5e58e895787f9910a788c254f679928d324732d063fe9bc202ecbe71 -F test/distinctagg.test 14ec5026e684eddd414c61c08692b43773e224ac92efbed6ec08c6994bc39723 +F test/distinctagg.test ad2b4cf1483cd4cf24867dfafbfa0abb61184d92085fcc9784cea0592b278d64 F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075 F test/e_blobopen.test 29f6055ee453b8e679fe9570c4d3acfedbef821622c5dad16875148c5952ef50 @@ -1009,13 +1049,13 @@ F test/e_createtable.test 31b9bcb6ac8876bc7ec342d86d9c231a84c62b442093a6651dfd0f F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7 -F test/e_expr.test 27e905ed17266c745bffe65f56b809c13ae6e225e56aeda1aaec926b32439286 +F test/e_expr.test b950818a48269506d75a41c819003bd77a0893bc4a4f2fdee191bc74109c1a87 F test/e_fkey.test feeba6238aeff9d809fb6236b351da8df4ae9bda89e088e54526b31a0cbfeec5 F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e F test/e_reindex.test 2b0e29344497d9a8a999453a003cb476b6b1d2eef2d6c120f83c2d3a429f3164 F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 -F test/e_select.test 89fa483f68d868f1be3d6f56180ed42d979979c266e051bf8b5e66a251e6b44a +F test/e_select.test 327a15f14068bbd6f647cedc67210f8680fcb2f05e481a0a855fccd2abfa1292 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f3b30e9b41e4 F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 @@ -1048,7 +1088,7 @@ F test/filectrl.test 6e871c2d35dead1d9a88e176e8d2ca094fec6bb3 F test/filefmt.test f393e80c4b8d493b7a7f8f3809a8425bbf4292af1f5140f01cb1427798a2bbd4 F test/filter1.test 590f8ba9a0cd0823b80d89ac75c5ce72276189cef9225d2436adaf1ee87f3727 F test/filter2.tcl 44e525497ce07382915f01bd29ffd0fa49dab3adb87253b5e5103ba8f93393e8 -F test/filter2.test 485cf95d1f6d6ceee5632201ca52a71868599836f430cdee42e5f7f14666e30a +F test/filter2.test 3cc20eaea2ea1ab245197cc4a62468deb460b78f5aa9bd7d5d3353c2fe569bae F test/filterfault.test c08fb491d698e8df6c122c98f7db1c65ffcfcad2c1ab0e07fa8a5be1b34eaa8b F test/fkey1.test e563bcb4cb108ce3f40363cda4f84009dc89a39e2973076e5057ba99fca35378 F test/fkey2.test 1063d65e5923c054cfb8f0555a92a3ae0fa8c067275a33ee1715bd856cdb304c @@ -1088,11 +1128,11 @@ F test/fts3aux2.test 2459e7fa3e22734aed237d1e2ae192f5541c4d8b218956ad2d90754977b F test/fts3b.test c15c4a9d04e210d0be67e54ce6a87b927168fbf9c1e3faec8c1a732c366fd491 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c -F test/fts3conf.test c84bbaec81281c1788aa545ac6e78a6bd6cde2bdbbce2da261690e3659f5a76b +F test/fts3conf.test c9cd45433b6787d48a43e84949aa2eb8b3b3d242bac7276731c1476290d31f29 F test/fts3corrupt.test 6732477c5ace050c5758a40a8b5706c8c0cccd416b9c558e0e15224805a40e57 F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0 F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cdb764b491f -F test/fts3corrupt4.test 589e043d1114ea02c83530e459ef5c2d6adce63094e72826ed3bceb02e795116 +F test/fts3corrupt4.test 48bd57baed9654e511709a02dbef2d22ee54c012ad466e8648f0f825233faa08 F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5 F test/fts3corrupt6.test f417c910254f32c0bc9ead7affa991a1d5aec35b3b32a183ffb05eea78289525 F test/fts3cov.test 7eacdbefd756cfa4dc2241974e3db2834e9b372ca215880e00032222f32194cf @@ -1111,8 +1151,9 @@ F test/fts3expr5.test a5b9a053becbdb8e973fbf4d6d3abaabeb42d511d1848bd57931f3e0a1 F test/fts3f.test 8c438d5e1cab526b0021988fb1dc70cf3597b006a33ffd6c955ee89929077fe3 F test/fts3fault.test f4e1342acfe6d216a001490e8cd52afac1f9ffe4a11bbcdcb296129a45c5df45 F test/fts3fault2.test 7b2741e5095367238380b0fcdb837f36c24484c7a5f353659b387df63cf039ec +F test/fts3fault3.test 4a39a1618546776255dc1de306213b600aef87eca589ca8428a70c00fd11961b F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641 -F test/fts3fuzz001.test e3c7b0ce9b04cc02281dcc96812a277f02df03cd7dc082055d87e11eb18aaf56 +F test/fts3fuzz001.test c78afcd8ad712ea0b8d2ed50851a8aab3bc9dc52c64a536291e07112f519357c F test/fts3join.test 1a4d786539b2b79a41c28ef2ac22cacd92a8ee830249b68a7dee4a020848e3bb F test/fts3malloc.test b0e4c133b8d61d4f6d112d8110f8320e9e453ef6 F test/fts3matchinfo.test aa66cc50615578b30f6df9984819ae5b702511cf8a94251ec7c594096a703a4a @@ -1133,15 +1174,16 @@ F test/fts3tok1.test a663f4cac22a9505400bc22aacb818d7055240409c28729669ea7d4cc21 F test/fts3tok_err.test 52273cd193b9036282f7bacb43da78c6be87418d F test/fts3varint.test 0b84a3fd4eba8a39f3687523804d18f3b322e6d4539a55bf342079c3614f2ada F test/fts4aa.test 0e6bfd6a81695a39b23e448dda25d864e63dda75bde6949c45ddc95426c6c3f5 -F test/fts4check.test 6259f856604445d7b684c9b306b2efb6346834c3f50e8fc4a59a2ca6d5319ad0 +F test/fts4check.test f0ea5e5581951d8ef7a341eea14486daf6c5f516a2f3273b0d5e8cb8a6cd3bd2 F test/fts4content.test 73bbb123420d2c46ef2fb3b24761e9acdb78b0877179d3a5d7d57aada08066f6 F test/fts4docid.test e33c383cfbdff0284685604d256f347a18fdbf01 F test/fts4growth.test 289833c34ad45a5e6e6133b53b6a71647231fb89d36ddcb8d9c87211b6721d7f F test/fts4growth2.test 13ad4e76451af6e6906c95cdc725d01b00044269 F test/fts4incr.test 4e353a0bd886ea984e56fce9e77724fc923b8d0d -F test/fts4langid.test 89e623218935507bca69d076ca254a7a8969dfc681c282b6374feaea8c7de784 +F test/fts4intck1.test 43774c641fdf6607c6ee90c3db8af065a37434d55d6eaf13bafe515e8b0c5729 +F test/fts4langid.test 4be912f42454998e239a2e877600263e0394afbaba03e06cedcc5a08693a345a F test/fts4lastrowid.test 185835895948d5325c7710649824042373b2203149abe8024a9319d25234dfd7 -F test/fts4merge.test e2b2ec21e287d54ec09824ccfb41e66896eeca568fc818ba0e0eb2efd94c35d2 +F test/fts4merge.test 57d093660a5093ae6e9fbd2d17592a88b45bbd66db2703c4b640b28828dbe38b F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891 F test/fts4merge3.test 8d9ccb4a3d41c4c617a149d6c4b13ad02de797d0 F test/fts4merge4.test 66fce89934cd9508cbdc67de486558c34912ffb2e8ffe5c9a1bbb9b8a4408ba7 @@ -1156,7 +1198,7 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test cbcf086273529d4a99b7199086da637a99039d2cad81dd7d7c4c9e25258ae164 +F test/func.test 3a29323b640c0552f6e9f1577407ced3a68e7d8c0bc04b61dd6040fa593a3a02 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 @@ -1164,6 +1206,7 @@ F test/func5.test 863e6d1bd0013d09c17236f8a13ea34008dd857d87d85a13a673960e4c25d8 F test/func6.test 9cc9b1f43b435af34fe1416eb1e318c8920448ea7a6962f2121972f5215cb9b0 F test/func7.test adbfc910385a6ffd15dc47be3c619ef070c542fcb7488964badb17b2d9a4d080 F test/func8.test c4e2ecacf9f16e47a245e7a25fbabcc7e78f9c7c41a80f158527cdfdc6dd299d +F test/func9.test b32d313f679aa9698d52f39519d301c3941823cb72b4e23406c210eadd82c824 F test/fuzz-oss1.test 514dcabb24687818ea949fa6760229eaacad74ca70157743ef36d35bbe01ffb0 F test/fuzz.test 4608c1310cff4c3014a84bcced6278139743e080046e5f6784b0de7b069371d8 F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1 @@ -1179,13 +1222,13 @@ F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e4 F test/fuzzdata5.db e35f64af17ec48926481cfaf3b3855e436bd40d1cfe2d59a9474cb4b748a52a5 F test/fuzzdata6.db b8725a5f5cf7a3b7241a9038e57ca7e7cc8c3f4d86b44bd770617bda245ab2b0 F test/fuzzdata7.db 0166b56fd7a6b9636a1d60ef0a060f86ddaecf99400a666bb6e5bbd7199ad1f2 -F test/fuzzdata8.db 40c85daae47da64387c3dab7bbd99c21e425c0bfdb4b149cb685b1ab474a2cb4 +F test/fuzzdata8.db 4a53b6d077c6a5c23b609d8d3ac66996fa55ba3f8d02f9b6efdd0214a767a35a F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc F test/fuzzinvariants.c b34530e8431f2cf3591eff588fc7684d6fdef466916fb46141c8c5374a3d8099 -F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c -F test/gencol1.test aef8b0670abd4b1ae4cae786b15a43758d86f6cd9f12b381d45d96bb51e597c9 +F test/gcfault.test 4ea410ac161e685f17b19e1f606f58514a2850e806c65b846d05f60d436c5b0d +F test/gencol1.test e169bdfa11c7ed5e9f322a98a7db3afe9e66235750b68c923efee8e1876b46ec F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 F test/having.test a89236dd8d55aa50c4805f82ac9daf64d477a44d712d8209c118978d0ca21ec9 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 @@ -1221,6 +1264,7 @@ F test/index6.test b376a648e85aa71c50074382784e6cb0c126ec46e43d1ad15af9a4d234c52 F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6eb3 F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3 F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997 +F test/indexA.test 11d84f6995e6e5b9d8315953fb1b6d29772ee7c7803ee9112715e7e4dd3e4974 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 F test/indexexpr1.test 62558b1cfd7ccbe7bc015849cc6d1a13ef124e80cbd5b3a98dc66c3c9cce0cf4 F test/indexexpr2.test 1c382e81ef996d8ae8b834a74f2a9013dddf59214c32201d7c8a656d739f999a @@ -1262,7 +1306,7 @@ F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f2 F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28 F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 -F test/joinH.test c9550bb6a0257cf99668a28485bb309bac542081702e89261b95542ab5f676b1 +F test/joinH.test 84198ea42bf78b79fe399c0567218cd6df36c50c6dd27d9c4aab221acaad929e F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e @@ -1273,7 +1317,7 @@ F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x -F test/json101.test 4d78048b185ffb9ae37645fa6934d48fc182473ae0bae088a1e2f7ba483c3a71 +F test/json101.test bc05d2476fd6f7ead31ec05b43d1b24b2b193ae112fd8f0d2ed56d9a904f9fa5 F test/json102.test 4c69694773a470f1fda34e5f4ba24920b35184fb66050b450fc2ef9ab5ad310b F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 @@ -1331,9 +1375,9 @@ F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7 F test/memdb1.test 2c4e9cc10d21c6bf4e217d72b7f6b8ba9b2605971bb2c5e6df76018e189f98f5 -F test/memdb2.test 7789975b96b0726032a22c1afc026592c7ff175bf05be11f1d640791541a77ea +F test/memdb2.test 4ba1fc09e2f51df80d148a540e4a3fa66d0462e91167b27497084de4d1f6b5b4 F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c -F test/memjournal2.test 6b9083cfaab9a3281ec545c3da2487999e8025fb7501bbae10f713f80c56454c +F test/memjournal2.test dbc2c5cb5f7b38950f4f6dc3e73fcecf0fcbed3fc32c7ce913bba164d288da1e F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 F test/memsubsys1.test 86b8158752af9188ed5b32a30674a1ef71183e6bc4e6808e815cd658ca9058a6 F test/memsubsys2.test 774b93cb09ca50d1b759bb7c645baa2a9ce172edc3a3da67d5150a26a9fc2a08 @@ -1362,7 +1406,7 @@ F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4d F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7 F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4 -F test/mutex1.test 4d7ecb155ae5ba9ca6f1817c1c74d51b5bb284d7f599de1859a9f2c9a1ca0d38 +F test/mutex1.test 42cb5e244c3a77bb0ef2b967e06fa5e7ba7d32d90a9b20bed98f6f5ede185a25 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test 437d40e6d0778b050d7750726c0cbd2c9936b81962926e8f8c48ca698f00f4d1 F test/nockpt.test 8c43b25af63b0bd620cf1b003529e37b6f1dc53bd22690e96a1bd73f78dde53a @@ -1409,7 +1453,7 @@ F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305 F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3 F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 -F test/pendingrace.test cbdf0f74bc939fb43cebad64dda7a0b5a3941a10b7e9cc2b596ff3e423a18156 +F test/pendingrace.test 6aa33756b950c4529f79c4f3817a9a1e4025bd0d9961571a05c0279bd183d9c6 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff F test/permutations.test f7caf8dd5c7b1da74842a48df116f7f193399c656d4ffc805cd0d9658568c675 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f @@ -1427,7 +1471,7 @@ F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 1495a09837a1cedfc0adf07ba42dc6b83be05a2c15de331b67c39a0e22078238 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 -F test/quickcheck.test f86b25b33455af0189b4d3fe7bd6e553115e80b2d7ec9bbe9a6b37fce0881bfe +F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a F test/quota-glob.test 32901e9eed6705d68ca3faee2a06b73b57cb3c26 F test/quota.test bfb269ce81ea52f593f9648316cd5013d766dd2a F test/quota2.test 7dc12e08b11cbc4c16c9ba2aa2e040ea8d8ab4b8 @@ -1460,8 +1504,8 @@ F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 F test/rowvalue7.test c1cbdbf407029db01f87764097c6ac02a1c5a37efd2776eff32a9cdfdf6f2dba F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 -F test/rowvalue9.test 138252b53b835208a5712e01595403a0ae32b4bc58284d9fe6bea10e58203fe4 -F test/rowvalueA.test 51f79b6098c193f838168752c9640f4eae6c63346bf64b5bed4f4e22fe2c71d0 +F test/rowvalue9.test 7499a8fd7ca3a3f0e19d94e135355439aa2b596f86b775ca8de79672da2ca378 +F test/rowvalueA.test be8d6ad8b476eb24c151bb20bfd487e0d50c5e99618b7b0e656035069d2fc2cf F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972d511c54fff F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 @@ -1474,7 +1518,7 @@ F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7 F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test b249328caf4d317e71058006872b8012598a5fa045b30bf24a81eeff650ab49e -F test/scanstatus2.test d0434bc3b356fb9d948f3417846b2ed5bbc4bd4cc49bddb38ac86469f754bfb0 +F test/scanstatus2.test 317670daf7f3eef48a9598cb7800ba8eccab51949cf52bca3f7da3b83a0c1c8c F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce @@ -1487,7 +1531,7 @@ F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 F test/seekscan1.test 31af16e3bb3203d153aea320939c5da97ec44705c2710d153c06a01397d45b09 F test/select1.test 692e84cfa29c405854c69e8a4027183d64c22952866a123fabbce741a379e889 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 -F test/select3.test 8d04b66df7475275a65f7e4a786d6a724c30bd9929f8ae5bd59c8d3d6e75e6cd +F test/select3.test 180223af31e1ca5537dd395ef9708ae18e651a233777fd366fd0d75469fc19c6 F test/select4.test f0684d3da3bccacbe2a1ebadf6fb49d9df6f53acb4c6ebc228a88d0d6054cc7b F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f390ae F test/select6.test 9b2fb4ffedf52e1b5703cfcae1212e7a4a063f014c0458d78d29aca3db766d1f @@ -1504,7 +1548,7 @@ F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae F test/selectH.test 0b54599f1917d99568c9b929df22ec6261ed7b6d2f02a46b5945ef81b7871aac F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb -F test/sessionfuzz.c 5eef09af01eeff6f20250ae4c0112c2e576e4d2f2026cc9a49dc5be6886fa6ee +F test/sessionfuzz.c 666b47e177c7b25f01ba645d41fb9131d2d54ae673f0d81c08f5af2b3e6ecbda F test/shared.test f022874d9d299fe913529dc10f52ad5a386e4e7ff709270b9b1111b3a0f3420a F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879 F test/shared3.test f8cd07c1a2b7cdb315c01671a0b2f8e3830b11ef31da6baa9a9cd8da88965403 @@ -1517,7 +1561,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 300b77328aaafb9f3e7a53a26e4162fbf92181d92251d259ff105a2275ff998d +F test/shell1.test 2191c892d8256b1b6cccb4fca894cfc2e748450cf422e6ec80a320a0d538b6df F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f @@ -1568,13 +1612,13 @@ F test/sqllimits1.test b28e5cc8d337aaf290614d96a47e8fbfb720bb7ad35620c9d5432996f F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805 F test/stat.test 123212a20ceb496893d5254a5f6c76442ce549fdc08d1702d8288a2bbaac8408 -F test/statfault.test 55f86055f9cd7b2d962a621b8a04215c1cebd4eaaecde92d279442327fe648a0 +F test/statfault.test 064f43379e4992b5221b7d9ac887c313b3191f85cce605d78e416fc4045da64e F test/stmt.test 54ed2cc0764bf3e48a058331813c3dbd19fc1d0827c3d8369914a5d8f564ec75 F test/stmtvtab1.test 6873dfb24f8e79cbb5b799b95c2e4349060eb7a3b811982749a84b359468e2d5 F test/strict1.test 4d2b492152b984fd7e8196d23eb88e2ccb0ef9e46ca2f96c2ce7147ceef9d168 F test/strict2.test b22c7a98b5000aef937f1990776497f0e979b1a23bc4f63e2d53b00e59b20070 F test/subjournal.test 8d4e2572c0ee9a15549f0d8e40863161295107e52f07a3e8012a2e1fdd093c49 -F test/subquery.test 3a1a5b600b8d4f504d2a2c61f33db820983dba94a0ef3e4aedca8f0165eaecb8 +F test/subquery.test 312c5d26304b0e93a56ba2cb9d4480b8a1c8217e3b2b8f8be2bfb0b2458ac3a7 F test/subquery2.test 90cf944b9de8204569cf656028391e4af1ccc8c0cc02d4ef38ee3be8de1ffb12 F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303 F test/substr.test a673e3763e247e9b5e497a6cacbaf3da2bd8ec8921c0677145c109f2e633f36b @@ -1591,7 +1635,7 @@ F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef20 F test/syscall.test a39d9a36f852ae6e4800f861bc2f2e83f68bbc2112d9399931ecfadeabd2d69d F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 F test/tabfunc01.test 54f27eacd054aa528a8b6e3331192c484104f30aaee351ad035f2b39a00f87c4 -F test/table.test eb3463b7add9f16a5bb836badf118cf391b809d09fdccd1f79684600d07ec132 +F test/table.test 7862a00b58b5541511a26757ea9c5c7c3f8298766e98aa099deec703d9c0a8e0 F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 F test/tclsqlite.test ad0bbd92edabe64cc91d990a0748142fe5ab962d74ac71fa3bfa94d50d2f4c87 @@ -1603,8 +1647,8 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede -F test/testrunner.tcl a9fee4df57276bc9e446961b160068c269da5902cc8ffc3e8852d77626b7594c -F test/testrunner_data.tcl 968cfee74688eb698b6917b7e53aba2993a1789430192d5efd27d2173c2e36c6 +F test/testrunner.tcl 14c8b8ece841b1dd17516a0dc9c7ad9b5f4d4db7987974d3fdf66ae56b2a71fa +F test/testrunner_data.tcl 7f73f93634d32dafc857ed491b840f371113d09fde6a8bfb9e47b938d47b8c85 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1612,7 +1656,7 @@ F test/thread004.test f51dfc3936184aaf73ee85f315224baad272a87f F test/thread005.test 50d10b5684399676174bd96c94ad4250b1a2c8b6 F test/thread1.test df115faa10a4ba1d456e9d4d9ec165016903eae4 F test/thread2.test f35d2106452b77523b3a2b7d1dcde2e5ee8f9e46 -F test/thread3.test 5f53b6a8e7391d8653116fd0bee4f9774efee4410e039990821de39c6b4375a9 +F test/thread3.test a12656a56cdf67acb6a2ff7638826c6d6a645f79909d86df521045ad31cf547d F test/thread_common.tcl 334639cadcb9f912bf82aa73f49efd5282e6cadd F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b F test/threadtest2.c a70a8e94bef23339d34226eb9521015ef99f4df8 @@ -1670,7 +1714,7 @@ F test/tkt-bd484a090c.test 60460bf946f79a79712b71f202eda501ca99b898 F test/tkt-bdc6bbbb38.test fc38bb09bdd440e3513a1f5f98fc60a075182d7d F test/tkt-c48d99d690.test ba61977d62ab612fc515b3c488a6fbd6464a2447 F test/tkt-c694113d5.test 82c461924ada5c14866c47e85535b0b0923ba16a2e907e370061a5ca77f65d77 -F test/tkt-cbd054fa6b.test 708475ef4d730a6853512c8ce363bcbd3becf0e26826e1f4cd46e2f52ff38edf +F test/tkt-cbd054fa6b.test 6ec9f1a5721fba74a83397683c50f472df68a0a749d193a537264eda3ad6d113 F test/tkt-d11f09d36e.test d999b548fef885d1d1afa49a0e8544ecf436869d F test/tkt-d635236375.test 9d37e988b47d87505bc9445be0ca447002df5d09 F test/tkt-d82e3f3721.test bcc0dfba658d15bab30fd4a9320c9e35d214ce30 @@ -1775,7 +1819,7 @@ F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76 F test/trans3.test 91a100e5412b488e22a655fe423a14c26403ab94 F test/transitive1.test f8ee983600b33d167da1885657f064aec404e1c0d0bc8765fdf163f4c749237a F test/trigger1.test 02cc64dc98278816c1c1ed8e472e18db8edbad88f37018bf46223e9614831963 -F test/trigger2.test 6e35bd7321c49e63d540aee980eb95dec63e1d1caca175224101045bcc80871f +F test/trigger2.test 30fcb3a6aa6782020d47968735ee6086ed795f73a7affa9406c8d5a36e7b5265 F test/trigger3.test aa640bb2bbb03edd5ff69c055117ea088f121945 F test/trigger4.test 74700b76ebf3947b2f7a92405141eb2cf2a5d359 F test/trigger5.test 619391a3e9fc194081d22cefd830d811e7badf83 @@ -1801,7 +1845,7 @@ F test/tt3_vacuum.c 71b254cde1fc49d6c8c44efd54f4668f3e57d7b3a8f4601ade069f75a999 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a -F test/unhex.test 47b547f4b35e4f6525ecac7c7839bd3ae4eb4613d4e8932592eff55da83308f1 +F test/unhex.test b7f1b806207cb77fa31c3e434fe92fba524464e3e9356809bfcc28f15af1a8b7 F test/unionall.test eb9afa030897af75fd2f0dd28354ef63c8a5897b6c76aa1f15acae61a12eabcf F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1 F test/unionallfault.test 652bfbb630e6c43135965dc1e8f0a9a791da83aec885d626a632fe1909c56f73 @@ -1829,7 +1873,7 @@ F test/uri.test c1abaaaa28e9422d61e5f3f9cbc8ef993ec49fe802f581520731708561d49384 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 F test/utf16align.test 9fde0bb5d3a821594aa68c6829ab9c5453a084384137ebb9f6153e2d678039da -F test/vacuum-into.test e0e3406845be4cf1b44db354179e5d9437e38bc267e4ac8e8dc617f9c3c903ab +F test/vacuum-into.test 35dc6f79b563f91c61822f61797363e97fed1bf28f1f722688b98d43f1980d76 F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d F test/vacuum2.test 9fd45ce6ce29f5614c249e03938d3567c06a9e772d4f155949f8eafe2d8af520 F test/vacuum3.test d9d9a04ee58c485b94694fd4f68cffaba49c32234fdefe1ac1a622c5e17d4ce3 @@ -1842,7 +1886,7 @@ F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 F test/view.test d4c4281e1679245829db35597817282f60dc513fc39cc5439078f009bd118487 F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 -F test/vt02.c f4a357c8180d71120ca2b466a8df48d9c40fc50873694840327d9647450485f3 +F test/vt02.c 5b44ac67b1a283fedecf2d6e2ceda61e7a157f01d44dcb4490dcb1e87d057060 F test/vtab1.test 09a72330d0f31eda2ffaa828b06a6b917fb86250ee72de0301570af725774c07 F test/vtab2.test 14d4ab26cee13ba6cf5c5601b158e4f57552d3b055cdd9406cf7f711e9c84082 F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e @@ -1899,7 +1943,7 @@ F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db8 F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20 F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 -F test/walseh1.test 82da37763b0d87942dccd191e58321532ce3d44b87ef36e04ff9ce13f382bbae +F test/walseh1.test bae700eb99519b6d5cd3f893c04759accc5a59c391d4189fe4dd6995a533442b F test/walsetlk.test 34c901443b31ab720afc463f5b236c86ca5c4134402573dce91aa0761de8db5a F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370dbc7a3 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f @@ -1938,11 +1982,11 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2 F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test 1e7e13d36235b9a08fcb9790f2b05383f2f8c9538532b027f455766686926114 +F test/window1.test ccfeaf116afc6a8f748a8122a4f1ee6b69e6bbc5acee61197d3c17167338b100 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 -F test/window3.test e9959a993c8a71e96433be8daaa1827d78b8921e4f12debd7bdbeb3c856ef3cb +F test/window3.test 330733bcca73aba4ddae7a1011f2a2120ef7a0c68d8155854e08677417b8dbd0 F test/window4.tcl 6f85307eb67242b654d051f7da32a996a66aee039a09c5ae358541aa61720742 F test/window4.test fbead87f681400ac07ef3555e0488b544a47d35491f8bf09a7474b6f76ce9b4e F test/window5.test d328dd18221217c49c144181975eea17339eaeaf0e9aa558cee3afb84652821e @@ -1963,7 +2007,7 @@ F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df F test/with1.test b93833890e5d2a368e78747f124503a0159aa029b98e9ed4795ebf630b2efd3d F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df -F test/with3.test fe15975c0b53c9098a757902a908e3f8d6d80ce47c5363ac600f28a79ef8c0ca +F test/with3.test e30369ea27aa27eb1bda4c5e510c8a9f782c8afd2ab99d1a02b8a7f25a5d3e65 F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8 F test/with6.test e097a03e5c898a8cd8f3a2d6a994ec510ea4376b5d484c2b669a41001e7758c8 @@ -1990,6 +2034,7 @@ F tool/build-all-msvc.bat c817b716e0edeecaf265a6775b63e5f45c34a6544f1d4114a22270 F tool/build-shell.sh f193b5e3eb4afcb4abbf96bf1475be6cfb74763ee2e50c82bc7ca105e8a136c5 F tool/cg_anno.tcl c1f875f5a4c9caca3d59937b16aff716f8b1883935f1b4c9ae23124705bc8099 x F tool/checkSpacing.c 810e51703529a204fc4e1eb060e9ab663e3c06d2 +F tool/cktclsh.sh 6075eef9c6b9ba4b38fef2ca2a66d25f2311bd3c610498d18a9b01f861629cca F tool/custom.txt 6cdf298f43e1db4bb91406d14777669b8fb1df790837823fa6754c4308decc27 F tool/dbhash.c 5da0c61032d23d74f2ab84ffc5740f0e8abec94f2c45c0b4306be7eb3ae96df0 F tool/dbtotxt.c ca48d34eaca6d6b6e4bd6a7be2b72caf34475869054240244c60fa7e69a518d6 @@ -1999,7 +2044,7 @@ F tool/extract-sqlite3h.tcl 069ceab0cee26cba99952bfa08c0b23e35941c837acabe143f0c F tool/extract.c 054069d81b095fbdc189a6f5d4466e40380505e2 F tool/fast_vacuum.c c129ae2924a48310c7b766810391da9e8fda532b9f6bd3f9a9e3a799a1b42af9 F tool/fragck.tcl 5265a95126abcf6ab357f7efa544787e5963f439 -F tool/fuzzershell.c e1d90a03ca790d7c331c2aae08ca46ff435f1ae1faa6cb9cc48f4687c18fdc6e +F tool/fuzzershell.c 41480c8a1e4749351f381431ecfdfceba645396c5d836f8d26b51a33c4a21b33 F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4 F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a5a4f F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce @@ -2012,9 +2057,9 @@ F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c c34e5944318415de513d29a6098df247a9618c96d83c38d4abd88641fe46e669 F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 -F tool/mkautoconfamal.sh 7077151b62ecb2848c5b2d36cdbcd8caa0b58f2c12dcdac6e982da39d29d213c +F tool/mkautoconfamal.sh cbdcf993fa83dccbef7fb77b39cdeb31ef9f77d9d88c9e343b58d35ca3898a6a F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl aca4b83e49aecf10368cd5d11bc4847061041ade026db5bd8da17ef201f1403b x +F tool/mkctimec.tcl 372452e24267dfe1b496eec3992d10c6e5e7d7870a152560cdcfe5404bc8cc04 x F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef @@ -2025,9 +2070,10 @@ F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f -F tool/mksqlite3c.tcl 49e39b1e616abc92fd8c24445f2b0a38881825f764541e0026f72371f0d84b65 +F tool/mksqlite3c.tcl 2c760ab786cb509b47f00c96fea82994866cb99f5e046df81c768288f57897b4 F tool/mksqlite3h.tcl d391cff7cad0a372ee1406faee9ccc7dad9cb80a0c95cae0f73d10dd26e06762 F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b +F tool/mktoolzip.tcl c7a9b685f5131d755e7d941cec50cee7f34178b9e34c9a89811eeb08617f8423 F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845 F tool/omittest-msvc.tcl d6b8f501ac1d7798c4126065030f89812379012cad98a1735d6d7221492abc08 @@ -2038,14 +2084,14 @@ F tool/replace.tcl 937c931ad560688e85bdd6258bdc754371bb1e2732e1fb28ef441e44c9228 F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076 -F tool/showdb.c 495a43b759ae37a0c4561a557a70090cb79b8c1601204e5a77e8b5360e65a954 +F tool/showdb.c 0f74b54cc67076c76cba9b2b7f54d3e05b78d130c70ffc394eb84c5b41bab017 F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818 F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564 F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809 F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c2a1 -F tool/showwal.c 65ecabae3a2dcff4116301d5a8dbb8c4964814da1b2aff6d85c806a88b71fa4e +F tool/showwal.c 11eca547980a066b081f512636151233350ac679f29ecf4ebfce7f4530230b3d F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe -F tool/spaceanal.tcl 1b5be34c6223cb1af06da2a10fb77863eb869b1962d055820b0a11cf2336ab45 +F tool/spaceanal.tcl 70c87c04cfd2e77b3e6f21c33ca768296aa8f67d4ab4874786ac8fbb28433477 F tool/speed-check.sh 72dc85b2c0484af971ee3e7d10775f72b4e771e27e162c2099b3bf25517c25fb F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e @@ -2054,7 +2100,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x F tool/split-sqlite3c.tcl 5aa60643afca558bc732b1444ae81a522326f91e1dc5665b369c54f09e20de60 -F tool/sqldiff.c efe73c6706b0ead9b4e415c684e4abf439e5f405793ae6ba50796e0e4a030349 +F tool/sqldiff.c fcccbc07da942b4534d0c769e9fcc21c67cbd7086ddc1c8f13372c40a83d4634 F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 @@ -2093,10 +2139,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 88a118905b95bd82e15c3c1e63a7620071ab9ab3517b77820553fcd7e600b0c8 -R 066b49c963926ac90c34ea41a60520db +P d1895dd8f5757a339f619f22b29c8a739398ded673bb9c93f1b8eb8a4b38f510 +R 06db090922d30265c1e1381238ea6702 +T +sym-major-release * T +sym-release * -T +sym-version-3.43.2 * +T +sym-version-3.44.0 * U drh -Z 8f5f6e60777c4954f5d2670202697dfe +Z 14f36413d8197ea0c35026cb3842c9f7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c15794dc0..c618d9af7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4310099cce5a487035fa535dd3002c59ac7f1d1bec68d7cf317fd3e769484790 +17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301 diff --git a/src/alter.c b/src/alter.c index d0b1f7f69..ec45e1433 100644 --- a/src/alter.c +++ b/src/alter.c @@ -446,14 +446,19 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ /* Verify that constraints are still satisfied */ if( pNew->pCheck!=0 || (pCol->notNull && (pCol->colFlags & COLFLAG_GENERATED)!=0) + || (pTab->tabFlags & TF_Strict)!=0 ){ sqlite3NestedParse(pParse, "SELECT CASE WHEN quick_check GLOB 'CHECK*'" " THEN raise(ABORT,'CHECK constraint failed')" + " WHEN quick_check GLOB 'non-* value in*'" + " THEN raise(ABORT,'type mismatch on DEFAULT')" " ELSE raise(ABORT,'NOT NULL constraint failed')" " END" " FROM pragma_quick_check(%Q,%Q)" - " WHERE quick_check GLOB 'CHECK*' OR quick_check GLOB 'NULL*'", + " WHERE quick_check GLOB 'CHECK*'" + " OR quick_check GLOB 'NULL*'" + " OR quick_check GLOB 'non-* value in*'", zTab, zDb ); } diff --git a/src/btree.c b/src/btree.c old mode 100644 new mode 100755 index 9b59f3ff8..5be30d562 --- a/src/btree.c +++ b/src/btree.c @@ -1,4 +1,3 @@ - /* ** 2004 April 6 ** @@ -7496,7 +7495,7 @@ static int rebuildPage( assert( nCell>0 ); assert( i(u32)usableSize) ){ j = 0; } + if( j>(u32)usableSize ){ j = 0; } memcpy(&pTmp[j], &aData[j], usableSize - j); for(k=0; ALWAYS(kixNx[k]<=i; k++){} @@ -10460,7 +10459,8 @@ static void checkAppendMsg( ** corresponds to page iPg is already set. */ static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){ - assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + assert( pCheck->aPgRef!=0 ); + assert( iPg<=pCheck->nCkPage && sizeof(pCheck->aPgRef[0])==1 ); return (pCheck->aPgRef[iPg/8] & (1 << (iPg & 0x07))); } @@ -10468,7 +10468,8 @@ static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){ ** Set the bit in the IntegrityCk.aPgRef[] array that corresponds to page iPg. */ static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){ - assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + assert( pCheck->aPgRef!=0 ); + assert( iPg<=pCheck->nCkPage && sizeof(pCheck->aPgRef[0])==1 ); pCheck->aPgRef[iPg/8] |= (1 << (iPg & 0x07)); } @@ -10482,7 +10483,7 @@ static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){ ** Also check that the page number is in bounds. */ static int checkRef(IntegrityCk *pCheck, Pgno iPage){ - if( iPage>pCheck->nPage || iPage==0 ){ + if( iPage>pCheck->nCkPage || iPage==0 ){ checkAppendMsg(pCheck, "invalid page number %u", iPage); return 1; } @@ -10709,6 +10710,7 @@ static int checkTreePage( if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){ checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); + if( rc==SQLITE_IOERR_NOMEM ) pCheck->rc = SQLITE_NOMEM; goto end_of_check; } @@ -10979,15 +10981,15 @@ int sqlite3BtreeIntegrityCheck( sCheck.db = db; sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; - sCheck.nPage = btreePagecount(sCheck.pBt); + sCheck.nCkPage = btreePagecount(sCheck.pBt); sCheck.mxErr = mxErr; sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH); sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL; - if( sCheck.nPage==0 ){ + if( sCheck.nCkPage==0 ){ goto integrity_ck_cleanup; } - sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1); + sCheck.aPgRef = sqlite3MallocZero((sCheck.nCkPage / 8)+ 1); if( !sCheck.aPgRef ){ checkOom(&sCheck); goto integrity_ck_cleanup; @@ -10999,7 +11001,7 @@ int sqlite3BtreeIntegrityCheck( } i = PENDING_BYTE_PAGE(pBt); - if( i<=sCheck.nPage ) setPageReferenced(&sCheck, i); + if( i<=sCheck.nCkPage ) setPageReferenced(&sCheck, i); /* Check the integrity of the freelist */ @@ -11050,7 +11052,7 @@ int sqlite3BtreeIntegrityCheck( /* Make sure every page in the file is referenced */ if( !bPartial ){ - for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ + for(i=1; i<=sCheck.nCkPage && sCheck.mxErr; i++){ #ifdef SQLITE_OMIT_AUTOVACUUM if( getPageReferenced(&sCheck, i)==0 ){ checkAppendMsg(&sCheck, "Page %u: never used", i); diff --git a/src/btreeInt.h b/src/btreeInt.h index 26a0bc686..563e15f8a 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -695,7 +695,7 @@ struct IntegrityCk { BtShared *pBt; /* The tree being checked out */ Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */ u8 *aPgRef; /* 1 bit per page in the db (see above) */ - Pgno nPage; /* Number of pages in the database */ + Pgno nCkPage; /* Pages in the database. 0 for partial check */ int mxErr; /* Stop accumulating errors when this reaches zero */ int nErr; /* Number of messages written to zErrMsg so far */ int rc; /* SQLITE_OK, SQLITE_NOMEM, or SQLITE_INTERRUPT */ diff --git a/src/build.c b/src/build.c index fe3a781d6..3d0679235 100644 --- a/src/build.c +++ b/src/build.c @@ -246,19 +246,14 @@ void sqlite3FinishCoding(Parse *pParse){ */ if( pParse->pAinc ) sqlite3AutoincrementBegin(pParse); - /* Code constant expressions that where factored out of inner loops. - ** - ** The pConstExpr list might also contain expressions that we simply - ** want to keep around until the Parse object is deleted. Such - ** expressions have iConstExprReg==0. Do not generate code for - ** those expressions, of course. + /* Code constant expressions that were factored out of inner loops. */ if( pParse->pConstExpr ){ ExprList *pEL = pParse->pConstExpr; pParse->okConstFactor = 0; for(i=0; inExpr; i++){ - int iReg = pEL->a[i].u.iConstExprReg; - sqlite3ExprCode(pParse, pEL->a[i].pExpr, iReg); + assert( pEL->a[i].u.iConstExprReg>0 ); + sqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg); } } @@ -1412,20 +1407,13 @@ void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ } #endif -/* -** Name of the special TEMP trigger used to implement RETURNING. The -** name begins with "sqlite_" so that it is guaranteed not to collide -** with any application-generated triggers. -*/ -#define RETURNING_TRIGGER_NAME "sqlite_returning" - /* ** Clean up the data structures associated with the RETURNING clause. */ static void sqlite3DeleteReturning(sqlite3 *db, Returning *pRet){ Hash *pHash; pHash = &(db->aDb[1].pSchema->trigHash); - sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, 0); + sqlite3HashInsert(pHash, pRet->zName, 0); sqlite3ExprListDelete(db, pRet->pReturnEL); sqlite3DbFree(db, pRet); } @@ -1468,7 +1456,9 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){ (void(*)(sqlite3*,void*))sqlite3DeleteReturning, pRet); testcase( pParse->earlyCleanup ); if( db->mallocFailed ) return; - pRet->retTrig.zName = RETURNING_TRIGGER_NAME; + sqlite3_snprintf(sizeof(pRet->zName), pRet->zName, + "sqlite_returning_%p", pParse); + pRet->retTrig.zName = pRet->zName; pRet->retTrig.op = TK_RETURNING; pRet->retTrig.tr_tm = TRIGGER_AFTER; pRet->retTrig.bReturning = 1; @@ -1479,9 +1469,9 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){ pRet->retTStep.pTrig = &pRet->retTrig; pRet->retTStep.pExprList = pList; pHash = &(db->aDb[1].pSchema->trigHash); - assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 + assert( sqlite3HashFind(pHash, pRet->zName)==0 || pParse->nErr || pParse->ifNotExists ); - if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig) + if( sqlite3HashInsert(pHash, pRet->zName, &pRet->retTrig) ==&pRet->retTrig ){ sqlite3OomFault(db); } @@ -2925,6 +2915,17 @@ void sqlite3EndTable( /* Reparse everything to update our internal data structures */ sqlite3VdbeAddParseSchemaOp(v, iDb, sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName),0); + + /* Test for cycles in generated columns and illegal expressions + ** in CHECK constraints and in DEFAULT clauses. */ + if( p->tabFlags & TF_HasGenerated ){ + sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0, + sqlite3MPrintf(db, "SELECT*FROM\"%w\".\"%w\"", + db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); + } + sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0, + sqlite3MPrintf(db, "PRAGMA \"%w\".integrity_check(%Q)", + db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); } /* Add the table to the in-memory representation of the database. diff --git a/src/ctime.c b/src/ctime.c index 368724168..cf761299f 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -355,6 +355,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS "EXPLAIN_ESTIMATED_ROWS", #endif +#ifdef SQLITE_EXTRA_AUTOEXT + "EXTRA_AUTOEXT=" CTIMEOPT_VAL(SQLITE_EXTRA_AUTOEXT), +#endif #ifdef SQLITE_EXTRA_IFNULLROW "EXTRA_IFNULLROW", #endif @@ -636,6 +639,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS "OMIT_SCHEMA_VERSION_PRAGMAS", #endif +#ifdef SQLITE_OMIT_SEH + "OMIT_SEH", +#endif #ifdef SQLITE_OMIT_SHARED_CACHE "OMIT_SHARED_CACHE", #endif diff --git a/src/date.c b/src/date.c index 648cdeb88..f16308544 100644 --- a/src/date.c +++ b/src/date.c @@ -1270,13 +1270,16 @@ static void strftimeFunc( computeJD(&x); computeYMD_HMS(&x); for(i=j=0; zFmt[i]; i++){ + char cf; if( zFmt[i]!='%' ) continue; if( j12 ) h -= 12; + if( h==0 ) h = 12; + sqlite3_str_appendf(&sRes, cf=='I' ? "%02d" : "%2d", h); break; } case 'W': /* Fall thru */ @@ -1298,7 +1314,7 @@ static void strftimeFunc( y.D = 1; computeJD(&y); nDay = (int)((x.iJD-y.iJD+43200000)/86400000); - if( zFmt[i]=='W' ){ + if( cf=='W' ){ int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ wd = (int)(((x.iJD+43200000)/86400000)%7); sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7); @@ -1319,6 +1335,19 @@ static void strftimeFunc( sqlite3_str_appendf(&sRes,"%02d",x.m); break; } + case 'p': /* Fall thru */ + case 'P': { + if( x.h>=12 ){ + sqlite3_str_append(&sRes, cf=='p' ? "PM" : "pm", 2); + }else{ + sqlite3_str_append(&sRes, cf=='p' ? "AM" : "am", 2); + } + break; + } + case 'R': { + sqlite3_str_appendf(&sRes, "%02d:%02d", x.h, x.m); + break; + } case 's': { if( x.useSubsec ){ sqlite3_str_appendf(&sRes,"%.3f", @@ -1333,9 +1362,15 @@ static void strftimeFunc( sqlite3_str_appendf(&sRes,"%02d",(int)x.s); break; } + case 'T': { + sqlite3_str_appendf(&sRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s); + break; + } + case 'u': /* Fall thru */ case 'w': { - sqlite3_str_appendchar(&sRes, 1, - (char)(((x.iJD+129600000)/86400000) % 7) + '0'); + char c = (char)(((x.iJD+129600000)/86400000) % 7) + '0'; + if( c=='0' && cf=='u' ) c = '7'; + sqlite3_str_appendchar(&sRes, 1, c); break; } case 'Y': { diff --git a/src/dbpage.c b/src/dbpage.c index 32a9ce55b..73c31f0da 100644 --- a/src/dbpage.c +++ b/src/dbpage.c @@ -425,7 +425,8 @@ int sqlite3DbpageRegister(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; return sqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0); } diff --git a/src/dbstat.c b/src/dbstat.c index 0a89d0524..c70d80637 100644 --- a/src/dbstat.c +++ b/src/dbstat.c @@ -895,7 +895,8 @@ int sqlite3DbstatRegister(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; return sqlite3_create_module(db, "dbstat", &dbstat_module, 0); } diff --git a/src/expr.c b/src/expr.c index d96f36285..0a975ae2e 100644 --- a/src/expr.c +++ b/src/expr.c @@ -591,6 +591,7 @@ Expr *sqlite3ExprForVectorField( */ pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0); if( pRet ){ + ExprSetProperty(pRet, EP_FullSize); pRet->iTable = nField; pRet->iColumn = iField; pRet->pLeft = pVector; @@ -1181,6 +1182,69 @@ Expr *sqlite3ExprFunction( return pNew; } +/* +** Report an error when attempting to use an ORDER BY clause within +** the arguments of a non-aggregate function. +*/ +void sqlite3ExprOrderByAggregateError(Parse *pParse, Expr *p){ + sqlite3ErrorMsg(pParse, + "ORDER BY may not be used with non-aggregate %#T()", p + ); +} + +/* +** Attach an ORDER BY clause to a function call. +** +** functionname( arguments ORDER BY sortlist ) +** \_____________________/ \______/ +** pExpr pOrderBy +** +** The ORDER BY clause is inserted into a new Expr node of type TK_ORDER +** and added to the Expr.pLeft field of the parent TK_FUNCTION node. +*/ +void sqlite3ExprAddFunctionOrderBy( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The function call to which ORDER BY is to be added */ + ExprList *pOrderBy /* The ORDER BY clause to add */ +){ + Expr *pOB; + sqlite3 *db = pParse->db; + if( NEVER(pOrderBy==0) ){ + assert( db->mallocFailed ); + return; + } + if( pExpr==0 ){ + assert( db->mallocFailed ); + sqlite3ExprListDelete(db, pOrderBy); + return; + } + assert( pExpr->op==TK_FUNCTION ); + assert( pExpr->pLeft==0 ); + assert( ExprUseXList(pExpr) ); + if( pExpr->x.pList==0 || NEVER(pExpr->x.pList->nExpr==0) ){ + /* Ignore ORDER BY on zero-argument aggregates */ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprListDelete, + pOrderBy); + return; + } + if( IsWindowFunc(pExpr) ){ + sqlite3ExprOrderByAggregateError(pParse, pExpr); + sqlite3ExprListDelete(db, pOrderBy); + return; + } + + pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0); + if( pOB==0 ){ + sqlite3ExprListDelete(db, pOrderBy); + return; + } + pOB->x.pList = pOrderBy; + assert( ExprUseXList(pOB) ); + pExpr->pLeft = pOB; + ExprSetProperty(pOB, EP_FullSize); +} + /* ** Check to see if a function is usable according to current access ** rules: @@ -1434,11 +1498,7 @@ static int dupedExprStructSize(const Expr *p, int flags){ assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 ); - if( 0==flags || p->op==TK_SELECT_COLUMN -#ifndef SQLITE_OMIT_WINDOWFUNC - || ExprHasProperty(p, EP_WinFunc) -#endif - ){ + if( 0==flags || ExprHasProperty(p, EP_FullSize) ){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); @@ -1469,56 +1529,93 @@ static int dupedExprNodeSize(const Expr *p, int flags){ /* ** Return the number of bytes required to create a duplicate of the -** expression passed as the first argument. The second argument is a -** mask containing EXPRDUP_XXX flags. +** expression passed as the first argument. ** ** The value returned includes space to create a copy of the Expr struct ** itself and the buffer referred to by Expr.u.zToken, if any. ** -** If the EXPRDUP_REDUCE flag is set, then the return value includes -** space to duplicate all Expr nodes in the tree formed by Expr.pLeft -** and Expr.pRight variables (but not for any structures pointed to or -** descended from the Expr.x.pList or Expr.x.pSelect variables). +** The return value includes space to duplicate all Expr nodes in the +** tree formed by Expr.pLeft and Expr.pRight, but not any other +** substructure such as Expr.x.pList, Expr.x.pSelect, and Expr.y.pWin. */ -static int dupedExprSize(const Expr *p, int flags){ - int nByte = 0; - if( p ){ - nByte = dupedExprNodeSize(p, flags); - if( flags&EXPRDUP_REDUCE ){ - nByte += dupedExprSize(p->pLeft, flags) + dupedExprSize(p->pRight, flags); - } - } +static int dupedExprSize(const Expr *p){ + int nByte; + assert( p!=0 ); + nByte = dupedExprNodeSize(p, EXPRDUP_REDUCE); + if( p->pLeft ) nByte += dupedExprSize(p->pLeft); + if( p->pRight ) nByte += dupedExprSize(p->pRight); + assert( nByte==ROUND8(nByte) ); return nByte; } /* -** This function is similar to sqlite3ExprDup(), except that if pzBuffer -** is not NULL then *pzBuffer is assumed to point to a buffer large enough -** to store the copy of expression p, the copies of p->u.zToken -** (if applicable), and the copies of the p->pLeft and p->pRight expressions, -** if any. Before returning, *pzBuffer is set to the first byte past the -** portion of the buffer copied into by this function. +** An EdupBuf is a memory allocation used to stored multiple Expr objects +** together with their Expr.zToken content. This is used to help implement +** compression while doing sqlite3ExprDup(). The top-level Expr does the +** allocation for itself and many of its decendents, then passes an instance +** of the structure down into exprDup() so that they decendents can have +** access to that memory. */ -static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ +typedef struct EdupBuf EdupBuf; +struct EdupBuf { + u8 *zAlloc; /* Memory space available for storage */ +#ifdef SQLITE_DEBUG + u8 *zEnd; /* First byte past the end of memory */ +#endif +}; + +/* +** This function is similar to sqlite3ExprDup(), except that if pEdupBuf +** is not NULL then it points to memory that can be used to store a copy +** of the input Expr p together with its p->u.zToken (if any). pEdupBuf +** is updated with the new buffer tail prior to returning. +*/ +static Expr *exprDup( + sqlite3 *db, /* Database connection (for memory allocation) */ + const Expr *p, /* Expr tree to be duplicated */ + int dupFlags, /* EXPRDUP_REDUCE for compression. 0 if not */ + EdupBuf *pEdupBuf /* Preallocated storage space, or NULL */ +){ Expr *pNew; /* Value to return */ - u8 *zAlloc; /* Memory space from which to build Expr object */ + EdupBuf sEdupBuf; /* Memory space from which to build Expr object */ u32 staticFlag; /* EP_Static if space not obtained from malloc */ + int nToken = -1; /* Space needed for p->u.zToken. -1 means unknown */ assert( db!=0 ); assert( p ); assert( dupFlags==0 || dupFlags==EXPRDUP_REDUCE ); - assert( pzBuffer==0 || dupFlags==EXPRDUP_REDUCE ); + assert( pEdupBuf==0 || dupFlags==EXPRDUP_REDUCE ); /* Figure out where to write the new Expr structure. */ - if( pzBuffer ){ - zAlloc = *pzBuffer; + if( pEdupBuf ){ + sEdupBuf.zAlloc = pEdupBuf->zAlloc; +#ifdef SQLITE_DEBUG + sEdupBuf.zEnd = pEdupBuf->zEnd; +#endif staticFlag = EP_Static; - assert( zAlloc!=0 ); + assert( sEdupBuf.zAlloc!=0 ); + assert( dupFlags==EXPRDUP_REDUCE ); }else{ - zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags)); + int nAlloc; + if( dupFlags ){ + nAlloc = dupedExprSize(p); + }else if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + nToken = sqlite3Strlen30NN(p->u.zToken)+1; + nAlloc = ROUND8(EXPR_FULLSIZE + nToken); + }else{ + nToken = 0; + nAlloc = ROUND8(EXPR_FULLSIZE); + } + assert( nAlloc==ROUND8(nAlloc) ); + sEdupBuf.zAlloc = sqlite3DbMallocRawNN(db, nAlloc); +#ifdef SQLITE_DEBUG + sEdupBuf.zEnd = sEdupBuf.zAlloc ? sEdupBuf.zAlloc+nAlloc : 0; +#endif + staticFlag = 0; } - pNew = (Expr *)zAlloc; + pNew = (Expr *)sEdupBuf.zAlloc; + assert( EIGHT_BYTE_ALIGNMENT(pNew) ); if( pNew ){ /* Set nNewSize to the size allocated for the structure pointed to @@ -1527,22 +1624,27 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ ** by the copy of the p->u.zToken string (if any). */ const unsigned nStructSize = dupedExprStructSize(p, dupFlags); - const int nNewSize = nStructSize & 0xfff; - int nToken; - if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ - nToken = sqlite3Strlen30(p->u.zToken) + 1; - }else{ - nToken = 0; + int nNewSize = nStructSize & 0xfff; + if( nToken<0 ){ + if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + nToken = sqlite3Strlen30(p->u.zToken) + 1; + }else{ + nToken = 0; + } } if( dupFlags ){ + assert( (int)(sEdupBuf.zEnd - sEdupBuf.zAlloc) >= nNewSize+nToken ); assert( ExprHasProperty(p, EP_Reduced)==0 ); - memcpy(zAlloc, p, nNewSize); + memcpy(sEdupBuf.zAlloc, p, nNewSize); }else{ u32 nSize = (u32)exprStructSize(p); - memcpy(zAlloc, p, nSize); + assert( (int)(sEdupBuf.zEnd - sEdupBuf.zAlloc) >= + (int)EXPR_FULLSIZE+nToken ); + memcpy(sEdupBuf.zAlloc, p, nSize); if( nSizeu.zToken string, if any. */ - if( nToken ){ - char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize]; + assert( nToken>=0 ); + if( nToken>0 ){ + char *zToken = pNew->u.zToken = (char*)&sEdupBuf.zAlloc[nNewSize]; memcpy(zToken, p->u.zToken, nToken); + nNewSize += nToken; } + sEdupBuf.zAlloc += ROUND8(nNewSize); + + if( ((p->flags|pNew->flags)&(EP_TokenOnly|EP_Leaf))==0 ){ - if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){ /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ if( ExprUseXSelect(p) ){ pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags); }else{ - pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags); + pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, + p->op!=TK_ORDER ? dupFlags : 0); } - } - /* Fill in pNew->pLeft and pNew->pRight. */ - if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly|EP_WinFunc) ){ - zAlloc += dupedExprNodeSize(p, dupFlags); - if( !ExprHasProperty(pNew, EP_TokenOnly|EP_Leaf) ){ - pNew->pLeft = p->pLeft ? - exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc) : 0; - pNew->pRight = p->pRight ? - exprDup(db, p->pRight, EXPRDUP_REDUCE, &zAlloc) : 0; - } #ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(p, EP_WinFunc) ){ pNew->y.pWin = sqlite3WindowDup(db, pNew, p->y.pWin); assert( ExprHasProperty(pNew, EP_WinFunc) ); } #endif /* SQLITE_OMIT_WINDOWFUNC */ - if( pzBuffer ){ - *pzBuffer = zAlloc; - } - }else{ - if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ - if( pNew->op==TK_SELECT_COLUMN ){ + + /* Fill in pNew->pLeft and pNew->pRight. */ + if( dupFlags ){ + if( p->op==TK_SELECT_COLUMN ){ + pNew->pLeft = p->pLeft; + assert( p->pRight==0 + || p->pRight==p->pLeft + || ExprHasProperty(p->pLeft, EP_Subquery) ); + }else{ + pNew->pLeft = p->pLeft ? + exprDup(db, p->pLeft, EXPRDUP_REDUCE, &sEdupBuf) : 0; + } + pNew->pRight = p->pRight ? + exprDup(db, p->pRight, EXPRDUP_REDUCE, &sEdupBuf) : 0; + }else{ + if( p->op==TK_SELECT_COLUMN ){ pNew->pLeft = p->pLeft; - assert( p->pRight==0 || p->pRight==p->pLeft - || ExprHasProperty(p->pLeft, EP_Subquery) ); + assert( p->pRight==0 + || p->pRight==p->pLeft + || ExprHasProperty(p->pLeft, EP_Subquery) ); }else{ pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); } @@ -1600,6 +1708,8 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ } } } + if( pEdupBuf ) memcpy(pEdupBuf, &sEdupBuf, sizeof(sEdupBuf)); + assert( sEdupBuf.zAlloc <= sEdupBuf.zEnd ); return pNew; } @@ -1864,11 +1974,7 @@ Select *sqlite3SelectDup(sqlite3 *db, const Select *p, int flags){ ** initially NULL, then create a new expression list. ** ** The pList argument must be either NULL or a pointer to an ExprList -** obtained from a prior call to sqlite3ExprListAppend(). This routine -** may not be used with an ExprList obtained from sqlite3ExprListDup(). -** Reason: This routine assumes that the number of slots in pList->a[] -** is a power of two. That is true for sqlite3ExprListAppend() returns -** but is not necessarily true from the return value of sqlite3ExprListDup(). +** obtained from a prior call to sqlite3ExprListAppend(). ** ** If a memory allocation error occurs, the entire list is freed and ** NULL is returned. If non-NULL is returned, then it is guaranteed @@ -2694,6 +2800,27 @@ int sqlite3IsRowid(const char *z){ return 0; } +/* +** Return a pointer to a buffer containing a usable rowid alias for table +** pTab. An alias is usable if there is not an explicit user-defined column +** of the same name. +*/ +const char *sqlite3RowidAlias(Table *pTab){ + const char *azOpt[] = {"_ROWID_", "ROWID", "OID"}; + int ii; + assert( VisibleRowid(pTab) ); + for(ii=0; iinCol; iCol++){ + if( sqlite3_stricmp(azOpt[ii], pTab->aCol[iCol].zCnName)==0 ) break; + } + if( iCol==pTab->nCol ){ + return azOpt[ii]; + } + } + return 0; +} + /* ** pX is the RHS of an IN operator. If pX is a SELECT statement ** that can be simplified to a direct table access, then return @@ -4231,6 +4358,41 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup( } +/* +** Expresion pExpr is guaranteed to be a TK_COLUMN or equivalent. This +** function checks the Parse.pIdxPartExpr list to see if this column +** can be replaced with a constant value. If so, it generates code to +** put the constant value in a register (ideally, but not necessarily, +** register iTarget) and returns the register number. +** +** Or, if the TK_COLUMN cannot be replaced by a constant, zero is +** returned. +*/ +static int exprPartidxExprLookup(Parse *pParse, Expr *pExpr, int iTarget){ + IndexedExpr *p; + for(p=pParse->pIdxPartExpr; p; p=p->pIENext){ + if( pExpr->iColumn==p->iIdxCol && pExpr->iTable==p->iDataCur ){ + Vdbe *v = pParse->pVdbe; + int addr = 0; + int ret; + + if( p->bMaybeNullRow ){ + addr = sqlite3VdbeAddOp1(v, OP_IfNullRow, p->iIdxCur); + } + ret = sqlite3ExprCodeTarget(pParse, p->pExpr, iTarget); + sqlite3VdbeAddOp4(pParse->pVdbe, OP_Affinity, ret, 1, 0, + (const char*)&p->aff, 1); + if( addr ){ + sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeChangeP3(v, addr, ret); + } + return ret; + } + } + return 0; +} + + /* ** Generate code into the current Vdbe to evaluate the given ** expression. Attempt to store the results in register "target". @@ -4267,6 +4429,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); op = pExpr->op; } + assert( op!=TK_ORDER ); switch( op ){ case TK_AGG_COLUMN: { AggInfo *pAggInfo = pExpr->pAggInfo; @@ -4280,7 +4443,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ #ifdef SQLITE_VDBE_COVERAGE /* Verify that the OP_Null above is exercised by tests ** tag-20230325-2 */ - sqlite3VdbeAddOp2(v, OP_NotNull, target, 1); + sqlite3VdbeAddOp3(v, OP_NotNull, target, 1, 20230325); VdbeCoverageNeverTaken(v); #endif break; @@ -4388,6 +4551,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ iTab = pParse->iSelfTab - 1; } } + else if( pParse->pIdxPartExpr + && 0!=(r1 = exprPartidxExprLookup(pParse, pExpr, target)) + ){ + return r1; + } assert( ExprUseYTab(pExpr) ); assert( pExpr->y.pTab!=0 ); iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab, @@ -5048,7 +5216,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** once. If no functions are involved, then factor the code out and put it at ** the end of the prepared statement in the initialization section. ** -** If regDest>=0 then the result is always stored in that register and the +** If regDest>0 then the result is always stored in that register and the ** result is not reusable. If regDest<0 then this routine is free to ** store the value wherever it wants. The register where the expression ** is stored is returned. When regDest<0, two identical expressions might @@ -5063,6 +5231,7 @@ int sqlite3ExprCodeRunJustOnce( ){ ExprList *p; assert( ConstFactorOk(pParse) ); + assert( regDest!=0 ); p = pParse->pConstExpr; if( regDest<0 && p ){ struct ExprList_item *pItem; @@ -6347,6 +6516,12 @@ int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ assert( pExpr->op==TK_AGG_FUNCTION ); assert( ExprUseXList(pExpr) ); sqlite3WalkExprList(&w, pExpr->x.pList); + if( pExpr->pLeft ){ + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + assert( pExpr->pLeft->x.pList!=0 ); + sqlite3WalkExprList(&w, pExpr->pLeft->x.pList); + } #ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(pExpr, EP_WinFunc) ){ sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter); @@ -6611,14 +6786,42 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ u8 enc = ENC(pParse->db); i = addAggInfoFunc(pParse->db, pAggInfo); if( i>=0 ){ + int nArg; assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); pItem = &pAggInfo->aFunc[i]; pItem->pFExpr = pExpr; assert( ExprUseUToken(pExpr) ); + nArg = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; pItem->pFunc = sqlite3FindFunction(pParse->db, - pExpr->u.zToken, - pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); - if( pExpr->flags & EP_Distinct ){ + pExpr->u.zToken, nArg, enc, 0); + assert( pItem->bOBUnique==0 ); + if( pExpr->pLeft + && (pItem->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL)==0 + ){ + /* The NEEDCOLL test above causes any ORDER BY clause on + ** aggregate min() or max() to be ignored. */ + ExprList *pOBList; + assert( nArg>0 ); + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + pItem->iOBTab = pParse->nTab++; + pOBList = pExpr->pLeft->x.pList; + assert( pOBList->nExpr>0 ); + assert( pItem->bOBUnique==0 ); + if( pOBList->nExpr==1 + && nArg==1 + && sqlite3ExprCompare(0,pOBList->a[0].pExpr, + pExpr->x.pList->a[0].pExpr,0)==0 + ){ + pItem->bOBPayload = 0; + pItem->bOBUnique = ExprHasProperty(pExpr, EP_Distinct); + }else{ + pItem->bOBPayload = 1; + } + }else{ + pItem->iOBTab = -1; + } + if( ExprHasProperty(pExpr, EP_Distinct) && !pItem->bOBUnique ){ pItem->iDistinct = pParse->nTab++; }else{ pItem->iDistinct = -1; diff --git a/src/fkey.c b/src/fkey.c index 3142e0ca6..bace1ae5e 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -858,6 +858,7 @@ static int isSetNullAction(Parse *pParse, FKey *pFKey){ if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull) || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull) ){ + assert( (pTop->db->flags & SQLITE_FkNoAction)==0 ); return 1; } } @@ -1052,6 +1053,8 @@ void sqlite3FkCheck( } if( regOld!=0 ){ int eAction = pFKey->aAction[aChange!=0]; + if( (db->flags & SQLITE_FkNoAction) ) eAction = OE_None; + fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1); /* If this is a deferred FK constraint, or a CASCADE or SET NULL ** action applies, then any foreign key violations caused by @@ -1167,7 +1170,11 @@ int sqlite3FkRequired( /* Check if any parent key columns are being modified. */ for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ if( fkParentIsModified(pTab, p, aChange, chngRowid) ){ - if( p->aAction[1]!=OE_None ) return 2; + if( (pParse->db->flags & SQLITE_FkNoAction)==0 + && p->aAction[1]!=OE_None + ){ + return 2; + } bHaveFK = 1; } } @@ -1217,6 +1224,7 @@ static Trigger *fkActionTrigger( int iAction = (pChanges!=0); /* 1 for UPDATE, 0 for DELETE */ action = pFKey->aAction[iAction]; + if( (db->flags & SQLITE_FkNoAction) ) action = OE_None; if( action==OE_Restrict && (db->flags & SQLITE_DeferFKs) ){ return 0; } diff --git a/src/func.c b/src/func.c index e551bba81..58ef4fef9 100644 --- a/src/func.c +++ b/src/func.c @@ -1256,7 +1256,8 @@ static void hexFunc( *(z++) = hexdigits[c&0xf]; } *z = 0; - sqlite3_result_text(context, zHex, n*2, sqlite3_free); + sqlite3_result_text64(context, zHex, (u64)(z-zHex), + sqlite3_free, SQLITE_UTF8); } } @@ -1550,6 +1551,81 @@ static void trimFunc( sqlite3_result_text(context, (char*)zIn, nIn, SQLITE_TRANSIENT); } +/* The core implementation of the CONCAT(...) and CONCAT_WS(SEP,...) +** functions. +** +** Return a string value that is the concatenation of all non-null +** entries in argv[]. Use zSep as the separator. +*/ +static void concatFuncCore( + sqlite3_context *context, + int argc, + sqlite3_value **argv, + int nSep, + const char *zSep +){ + i64 j, k, n = 0; + int i; + char *z; + for(i=0; i0 ){ + const char *v = (const char*)sqlite3_value_text(argv[i]); + if( v!=0 ){ + if( j>0 && nSep>0 ){ + memcpy(&z[j], zSep, nSep); + j += nSep; + } + memcpy(&z[j], v, k); + j += k; + } + } + } + z[j] = 0; + assert( j<=n ); + sqlite3_result_text64(context, z, j, sqlite3_free, SQLITE_UTF8); +} + +/* +** The CONCAT(...) function. Generate a string result that is the +** concatentation of all non-null arguments. +*/ +static void concatFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + concatFuncCore(context, argc, argv, 0, ""); +} + +/* +** The CONCAT_WS(separator, ...) function. +** +** Generate a string that is the concatenation of 2nd through the Nth +** argument. Use the first argument (which must be non-NULL) as the +** separator. +*/ +static void concatwsFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int nSep = sqlite3_value_bytes(argv[0]); + const char *zSep = (const char*)sqlite3_value_text(argv[0]); + if( zSep==0 ) return; + concatFuncCore(context, argc-1, argv+1, nSep, zSep); +} + #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION /* @@ -1971,6 +2047,7 @@ static void minMaxFinalize(sqlite3_context *context){ /* ** group_concat(EXPR, ?SEPARATOR?) +** string_agg(EXPR, SEPARATOR) ** ** The SEPARATOR goes before the EXPR string. This is tragic. The ** groupConcatInverse() implementation would have been easier if the @@ -2561,6 +2638,11 @@ void sqlite3RegisterBuiltinFunctions(void){ FUNCTION(hex, 1, 0, 0, hexFunc ), FUNCTION(unhex, 1, 0, 0, unhexFunc ), FUNCTION(unhex, 2, 0, 0, unhexFunc ), + FUNCTION(concat, -1, 0, 0, concatFunc ), + FUNCTION(concat, 0, 0, 0, 0 ), + FUNCTION(concat_ws, -1, 0, 0, concatwsFunc ), + FUNCTION(concat_ws, 0, 0, 0, 0 ), + FUNCTION(concat_ws, 1, 0, 0, 0 ), INLINE_FUNC(ifnull, 2, INLINEFUNC_coalesce, 0 ), VFUNCTION(random, 0, 0, 0, randomFunc ), VFUNCTION(randomblob, 1, 0, 0, randomBlob ), @@ -2590,6 +2672,8 @@ void sqlite3RegisterBuiltinFunctions(void){ groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), + WAGGREGATE(string_agg, 2, 0, 0, groupConcatStep, + groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), #ifdef SQLITE_CASE_SENSITIVE_LIKE diff --git a/src/json.c b/src/json.c index 2e81948e7..c2129a026 100644 --- a/src/json.c +++ b/src/json.c @@ -602,7 +602,7 @@ static void jsonResult(JsonString *p){ }else if( jsonForceRCStr(p) ){ sqlite3RCStrRef(p->zBuf); sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, - (void(*)(void*))sqlite3RCStrUnref, + sqlite3RCStrUnref, SQLITE_UTF8); } } @@ -1942,7 +1942,7 @@ static JsonParse *jsonParseCached( /* The input JSON was not found anywhere in the cache. We will need ** to parse it ourselves and generate a new JsonParse object. */ - bJsonRCStr = sqlite3ValueIsOfClass(pJson,(void(*)(void*))sqlite3RCStrUnref); + bJsonRCStr = sqlite3ValueIsOfClass(pJson,sqlite3RCStrUnref); p = sqlite3_malloc64( sizeof(*p) + (bJsonRCStr ? 0 : nJson+1) ); if( p==0 ){ sqlite3_result_error_nomem(pCtx); @@ -2156,6 +2156,7 @@ static JsonNode *jsonLookupStep( if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i--; j += jsonNodeSize(&pRoot[j]); } + if( i==0 && j<=pRoot->n ) break; if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; if( pParse->useMod==0 ) break; assert( pRoot->eU==2 ); @@ -2843,11 +2844,13 @@ static void jsonReplaceNode( break; } if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){ - char *zCopy = sqlite3DbStrDup(0, z); + char *zCopy = sqlite3_malloc64( n+1 ); int k; if( zCopy ){ + memcpy(zCopy, z, n); + zCopy[n] = 0; jsonParseAddCleanup(p, sqlite3_free, zCopy); - }else{ + }else{ p->oom = 1; sqlite3_result_error_nomem(pCtx); } @@ -3124,7 +3127,7 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, pStr->bStatic ? SQLITE_TRANSIENT : - (void(*)(void*))sqlite3RCStrUnref); + sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); @@ -3233,7 +3236,7 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, pStr->bStatic ? SQLITE_TRANSIENT : - (void(*)(void*))sqlite3RCStrUnref); + sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); @@ -3665,7 +3668,7 @@ static int jsonEachFilter( if( z==0 ) return SQLITE_OK; memset(&p->sParse, 0, sizeof(p->sParse)); p->sParse.nJPRef = 1; - if( sqlite3ValueIsOfClass(argv[0], (void(*)(void*))sqlite3RCStrUnref) ){ + if( sqlite3ValueIsOfClass(argv[0], sqlite3RCStrUnref) ){ p->sParse.zJson = sqlite3RCStrRef((char*)z); }else{ n = sqlite3_value_bytes(argv[0]); @@ -3760,7 +3763,8 @@ static sqlite3_module jsonEachModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; /* The methods of the json_tree virtual table. */ @@ -3788,7 +3792,8 @@ static sqlite3_module jsonTreeModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ #endif /* !defined(SQLITE_OMIT_JSON) */ diff --git a/src/loadext.c b/src/loadext.c index e792fa5a9..7e0ae2543 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -514,7 +514,10 @@ static const sqlite3_api_routines sqlite3Apis = { /* Version 3.41.0 and later */ sqlite3_is_interrupted, /* Version 3.43.0 and later */ - sqlite3_stmt_explain + sqlite3_stmt_explain, + /* Version 3.44.0 and later */ + sqlite3_get_clientdata, + sqlite3_set_clientdata }; /* True if x is the directory separator character @@ -730,6 +733,9 @@ void sqlite3CloseExtensions(sqlite3 *db){ ** default so as not to open security holes in older applications. */ int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); if( onoff ){ db->flags |= SQLITE_LoadExtension|SQLITE_LoadExtFunc; @@ -751,7 +757,7 @@ int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ */ typedef struct sqlite3AutoExtList sqlite3AutoExtList; static SQLITE_WSD struct sqlite3AutoExtList { - u32 nExt; /* Number of entries in aExt[] */ + u32 nExt; /* Number of entries in aExt[] */ void (**aExt)(void); /* Pointers to the extension init functions */ } sqlite3Autoext = { 0, 0 }; @@ -779,6 +785,9 @@ int sqlite3_auto_extension( void (*xInit)(void) ){ int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_API_ARMOR + if( xInit==0 ) return SQLITE_MISUSE_BKPT; +#endif #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); if( rc ){ @@ -831,6 +840,9 @@ int sqlite3_cancel_auto_extension( int i; int n = 0; wsdAutoextInit; +#ifdef SQLITE_ENABLE_API_ARMOR + if( xInit==0 ) return 0; +#endif sqlite3_mutex_enter(mutex); for(i=(int)wsdAutoext.nExt-1; i>=0; i--){ if( wsdAutoext.aExt[i]==xInit ){ diff --git a/src/main.c b/src/main.c index cd0200890..fbe00f5fa 100644 --- a/src/main.c +++ b/src/main.c @@ -47,7 +47,9 @@ int sqlite3Fts5Init(sqlite3*); #ifdef SQLITE_ENABLE_STMTVTAB int sqlite3StmtVtabInit(sqlite3*); #endif - +#ifdef SQLITE_EXTRA_AUTOEXT +int SQLITE_EXTRA_AUTOEXT(sqlite3*); +#endif /* ** An array of pointers to extension initializer functions for ** built-in extensions. @@ -81,6 +83,9 @@ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { #ifdef SQLITE_ENABLE_BYTECODE_VTAB sqlite3VdbeBytecodeVtabInit, #endif +#ifdef SQLITE_EXTRA_AUTOEXT + SQLITE_EXTRA_AUTOEXT, +#endif }; #ifndef SQLITE_AMALGAMATION @@ -154,6 +159,32 @@ char *sqlite3_temp_directory = 0; */ char *sqlite3_data_directory = 0; +/* +** Determine whether or not high-precision (long double) floating point +** math works correctly on CPU currently running. +*/ +static SQLITE_NOINLINE int hasHighPrecisionDouble(int rc){ + if( sizeof(LONGDOUBLE_TYPE)<=8 ){ + /* If the size of "long double" is not more than 8, then + ** high-precision math is not possible. */ + return 0; + }else{ + /* Just because sizeof(long double)>8 does not mean that the underlying + ** hardware actually supports high-precision floating point. For example, + ** clearing the 0x100 bit in the floating-point control word on Intel + ** processors will make long double work like double, even though long + ** double takes up more space. The only way to determine if long double + ** actually works is to run an experiment. */ + LONGDOUBLE_TYPE a, b, c; + rc++; + a = 1.0+rc*0.1; + b = 1.0e+18+rc*25.0; + c = a+b; + return b!=c; + } +} + + /* ** Initialize SQLite. ** @@ -349,6 +380,12 @@ int sqlite3_initialize(void){ } #endif + /* Experimentally determine if high-precision floating point is + ** available. */ +#ifndef SQLITE_OMIT_WSD + sqlite3Config.bUseLongDouble = hasHighPrecisionDouble(rc); +#endif + return rc; } @@ -919,6 +956,10 @@ int sqlite3_db_cacheflush(sqlite3 *db){ int sqlite3_db_config(sqlite3 *db, int op, ...){ va_list ap; int rc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); va_start(ap, op); switch( op ){ @@ -1248,6 +1289,14 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ } #endif + while( db->pDbData ){ + DbClientData *p = db->pDbData; + db->pDbData = p->pNext; + assert( p->pData!=0 ); + if( p->xDestructor ) p->xDestructor(p->pData); + sqlite3_free(p); + } + /* Convert the connection into a zombie and then close it. */ db->eOpenState = SQLITE_STATE_ZOMBIE; @@ -2322,6 +2371,12 @@ void *sqlite3_preupdate_hook( void *pArg /* First callback argument */ ){ void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( db==0 ){ + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pPreUpdateArg; db->xPreUpdateCallback = xCallback; @@ -2468,7 +2523,7 @@ int sqlite3_wal_checkpoint_v2( if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint ** mode: */ - return SQLITE_MISUSE; + return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(db->mutex); @@ -3705,6 +3760,69 @@ int sqlite3_collation_needed16( } #endif /* SQLITE_OMIT_UTF16 */ +/* +** Find existing client data. +*/ +void *sqlite3_get_clientdata(sqlite3 *db, const char *zName){ + DbClientData *p; + sqlite3_mutex_enter(db->mutex); + for(p=db->pDbData; p; p=p->pNext){ + if( strcmp(p->zName, zName)==0 ){ + void *pResult = p->pData; + sqlite3_mutex_leave(db->mutex); + return pResult; + } + } + sqlite3_mutex_leave(db->mutex); + return 0; +} + +/* +** Add new client data to a database connection. +*/ +int sqlite3_set_clientdata( + sqlite3 *db, /* Attach client data to this connection */ + const char *zName, /* Name of the client data */ + void *pData, /* The client data itself */ + void (*xDestructor)(void*) /* Destructor */ +){ + DbClientData *p, **pp; + sqlite3_mutex_enter(db->mutex); + pp = &db->pDbData; + for(p=db->pDbData; p && strcmp(p->zName,zName); p=p->pNext){ + pp = &p->pNext; + } + if( p ){ + assert( p->pData!=0 ); + if( p->xDestructor ) p->xDestructor(p->pData); + if( pData==0 ){ + *pp = p->pNext; + sqlite3_free(p); + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + } + }else if( pData==0 ){ + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + }else{ + size_t n = strlen(zName); + p = sqlite3_malloc64( sizeof(DbClientData)+n+1 ); + if( p==0 ){ + if( xDestructor ) xDestructor(pData); + sqlite3_mutex_leave(db->mutex); + return SQLITE_NOMEM; + } + memcpy(p->zName, zName, n+1); + p->pNext = db->pDbData; + db->pDbData = p; + } + p->pData = pData; + p->xDestructor = xDestructor; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + + #ifndef SQLITE_OMIT_DEPRECATED /* ** This function is now an anachronism. It used to be used to recover from a @@ -4054,6 +4172,28 @@ int sqlite3_test_control(int op, ...){ } #endif + /* sqlite3_test_control(SQLITE_TESTCTRL_FK_NO_ACTION, sqlite3 *db, int b); + ** + ** If b is true, then activate the SQLITE_FkNoAction setting. If b is + ** false then clearn that setting. If the SQLITE_FkNoAction setting is + ** abled, all foreign key ON DELETE and ON UPDATE actions behave as if + ** they were NO ACTION, regardless of how they are defined. + ** + ** NB: One must usually run "PRAGMA writable_schema=RESET" after + ** using this test-control, before it will take full effect. failing + ** to reset the schema can result in some unexpected behavior. + */ + case SQLITE_TESTCTRL_FK_NO_ACTION: { + sqlite3 *db = va_arg(ap, sqlite3*); + int b = va_arg(ap, int); + if( b ){ + db->flags |= SQLITE_FkNoAction; + }else{ + db->flags &= ~SQLITE_FkNoAction; + } + break; + } + /* ** sqlite3_test_control(BITVEC_TEST, size, program) ** @@ -4478,11 +4618,11 @@ int sqlite3_test_control(int op, ...){ ** X<0 Make no changes to the bUseLongDouble. Just report value. ** X==0 Disable bUseLongDouble ** X==1 Enable bUseLongDouble - ** X==2 Set bUseLongDouble to its default value for this platform + ** X>=2 Set bUseLongDouble to its default value for this platform */ case SQLITE_TESTCTRL_USELONGDOUBLE: { int b = va_arg(ap, int); - if( b==2 ) b = sizeof(LONGDOUBLE_TYPE)>8; + if( b>=2 ) b = hasHighPrecisionDouble(b); if( b>=0 ) sqlite3Config.bUseLongDouble = b>0; rc = sqlite3Config.bUseLongDouble!=0; break; @@ -4896,7 +5036,7 @@ int sqlite3_compileoption_used(const char *zOptName){ int nOpt; const char **azCompileOpt; -#if SQLITE_ENABLE_API_ARMOR +#ifdef SQLITE_ENABLE_API_ARMOR if( zOptName==0 ){ (void)SQLITE_MISUSE_BKPT; return 0; diff --git a/src/malloc.c b/src/malloc.c index 48c460060..356750682 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -896,5 +896,5 @@ int sqlite3ApiExit(sqlite3* db, int rc){ if( db->mallocFailed || rc ){ return apiHandleError(db, rc); } - return rc & db->errMask; + return 0; } diff --git a/src/mutex.c b/src/mutex.c index 13a9fca15..381ffbdfd 100644 --- a/src/mutex.c +++ b/src/mutex.c @@ -133,7 +133,7 @@ static void checkMutexFree(sqlite3_mutex *p){ assert( SQLITE_MUTEX_FAST<2 ); assert( SQLITE_MUTEX_WARNONCONTENTION<2 ); -#if SQLITE_ENABLE_API_ARMOR +#ifdef SQLITE_ENABLE_API_ARMOR if( ((CheckMutex*)p)->iType<2 ) #endif { diff --git a/src/mutex_unix.c b/src/mutex_unix.c index ac4331a67..beae877f9 100644 --- a/src/mutex_unix.c +++ b/src/mutex_unix.c @@ -223,7 +223,7 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){ */ static void pthreadMutexFree(sqlite3_mutex *p){ assert( p->nRef==0 ); -#if SQLITE_ENABLE_API_ARMOR +#ifdef SQLITE_ENABLE_API_ARMOR if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ) #endif { diff --git a/src/notify.c b/src/notify.c index 4960ab76b..6a4cab875 100644 --- a/src/notify.c +++ b/src/notify.c @@ -152,6 +152,9 @@ int sqlite3_unlock_notify( ){ int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); enterMutex(); diff --git a/src/os_unix.c b/src/os_unix.c index 59f67d142..a33e6f4df 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -3150,9 +3150,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { unixInodeInfo *pInode; afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; int skipShared = 0; -#ifdef SQLITE_TEST - int h = pFile->h; -#endif assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (afp)\n", pFile->h, eFileLock, @@ -3168,9 +3165,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { assert( pInode->nShared!=0 ); if( pFile->eFileLock>SHARED_LOCK ){ assert( pInode->eFileLock==pFile->eFileLock ); - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); #ifdef SQLITE_DEBUG /* When reducing a lock such that other processes can start @@ -3219,9 +3213,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { unsigned long long sharedLockByte = SHARED_FIRST+pInode->sharedByte; pInode->nShared--; if( pInode->nShared==0 ){ - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); if( !skipShared ){ rc = afpSetLock(context->dbPath, pFile, sharedLockByte, 1, 0); } diff --git a/src/pager.c b/src/pager.c index a53dc1889..e54750424 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1492,9 +1492,32 @@ static int writeJournalHdr(Pager *pPager){ memset(zHeader, 0, sizeof(aJournalMagic)+4); } + + /* The random check-hash initializer */ - sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); + if( pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){ + sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); + } +#ifdef SQLITE_DEBUG + else{ + /* The Pager.cksumInit variable is usually randomized above to protect + ** against there being existing records in the journal file. This is + ** dangerous, as following a crash they may be mistaken for records + ** written by the current transaction and rolled back into the database + ** file, causing corruption. The following assert statements verify + ** that this is not required in "journal_mode=memory" mode, as in that + ** case the journal file is always 0 bytes in size at this point. + ** It is advantageous to avoid the sqlite3_randomness() call if possible + ** as it takes the global PRNG mutex. */ + i64 sz = 0; + sqlite3OsFileSize(pPager->jfd, &sz); + assert( sz==0 ); + assert( pPager->journalOff==journalHdrOffset(pPager) ); + assert( sqlite3JournalIsInMemory(pPager->jfd) ); + } +#endif put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit); + /* The initial database size */ put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbOrigSize); /* The assumed sector size for this process */ @@ -2138,6 +2161,9 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ return (rc==SQLITE_OK?rc2:rc); } +/* Forward reference */ +static int pager_playback(Pager *pPager, int isHot); + /* ** Execute a rollback if a transaction is active and unlock the ** database file. @@ -2166,6 +2192,21 @@ static void pagerUnlockAndRollback(Pager *pPager){ assert( pPager->eState==PAGER_READER ); pager_end_transaction(pPager, 0, 0); } + }else if( pPager->eState==PAGER_ERROR + && pPager->journalMode==PAGER_JOURNALMODE_MEMORY + && isOpen(pPager->jfd) + ){ + /* Special case for a ROLLBACK due to I/O error with an in-memory + ** journal: We have to rollback immediately, before the journal is + ** closed, because once it is closed, all content is forgotten. */ + int errCode = pPager->errCode; + u8 eLock = pPager->eLock; + pPager->eState = PAGER_OPEN; + pPager->errCode = SQLITE_OK; + pPager->eLock = EXCLUSIVE_LOCK; + pager_playback(pPager, 1); + pPager->errCode = errCode; + pPager->eLock = eLock; } pager_unlock(pPager); } @@ -5658,8 +5699,20 @@ int sqlite3PagerGet( DbPage **ppPage, /* Write a pointer to the page here */ int flags /* PAGER_GET_XXX flags */ ){ - /* printf("PAGE %u\n", pgno); fflush(stdout); */ +#if 0 /* Trace page fetch by setting to 1 */ + int rc; + printf("PAGE %u\n", pgno); + fflush(stdout); + rc = pPager->xGet(pPager, pgno, ppPage, flags); + if( rc ){ + printf("PAGE %u failed with 0x%02x\n", pgno, rc); + fflush(stdout); + } + return rc; +#else + /* Normal, high-speed version of sqlite3PagerGet() */ return pPager->xGet(pPager, pgno, ppPage, flags); +#endif } /* @@ -6535,6 +6588,13 @@ int sqlite3PagerCommitPhaseOne( rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, 0); if( rc==SQLITE_OK ){ rc = pager_write_pagelist(pPager, pList); + if( rc==SQLITE_OK && pPager->dbSize>pPager->dbFileSize ){ + char *pTmp = pPager->pTmpSpace; + int szPage = (int)pPager->pageSize; + memset(pTmp, 0, szPage); + rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, + ((i64)pPager->dbSize*pPager->pageSize)-szPage); + } if( rc==SQLITE_OK ){ rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0); } @@ -7346,7 +7406,7 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ } assert( state==pPager->eState ); } - }else if( eMode==PAGER_JOURNALMODE_OFF ){ + }else if( eMode==PAGER_JOURNALMODE_OFF || eMode==PAGER_JOURNALMODE_MEMORY ){ sqlite3OsClose(pPager->jfd); } } diff --git a/src/parse.y b/src/parse.y index 867b62aa7..19491192e 100644 --- a/src/parse.y +++ b/src/parse.y @@ -1144,6 +1144,10 @@ expr(A) ::= CAST LP expr(E) AS typetoken(T) RP. { expr(A) ::= idj(X) LP distinct(D) exprlist(Y) RP. { A = sqlite3ExprFunction(pParse, Y, &X, D); } +expr(A) ::= idj(X) LP distinct(D) exprlist(Y) ORDER BY sortlist(O) RP. { + A = sqlite3ExprFunction(pParse, Y, &X, D); + sqlite3ExprAddFunctionOrderBy(pParse, A, O); +} expr(A) ::= idj(X) LP STAR RP. { A = sqlite3ExprFunction(pParse, 0, &X, 0); } @@ -1153,6 +1157,11 @@ expr(A) ::= idj(X) LP distinct(D) exprlist(Y) RP filter_over(Z). { A = sqlite3ExprFunction(pParse, Y, &X, D); sqlite3WindowAttach(pParse, A, Z); } +expr(A) ::= idj(X) LP distinct(D) exprlist(Y) ORDER BY sortlist(O) RP filter_over(Z). { + A = sqlite3ExprFunction(pParse, Y, &X, D); + sqlite3WindowAttach(pParse, A, Z); + sqlite3ExprAddFunctionOrderBy(pParse, A, O); +} expr(A) ::= idj(X) LP STAR RP filter_over(Z). { A = sqlite3ExprFunction(pParse, 0, &X, 0); sqlite3WindowAttach(pParse, A, Z); diff --git a/src/pragma.c b/src/pragma.c index f15c1be27..90076b0c2 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1124,7 +1124,11 @@ void sqlite3Pragma( #endif if( sqlite3GetBoolean(zRight, 0) ){ - db->flags |= mask; + if( (mask & SQLITE_WriteSchema)==0 + || (db->flags & SQLITE_Defensive)==0 + ){ + db->flags |= mask; + } }else{ db->flags &= ~mask; if( mask==SQLITE_DeferFKs ) db->nDeferredImmCons = 0; @@ -1757,8 +1761,31 @@ void sqlite3Pragma( int r2; /* Previous key for WITHOUT ROWID tables */ int mxCol; /* Maximum non-virtual column number */ - if( !IsOrdinaryTable(pTab) ) continue; if( pObjTab && pObjTab!=pTab ) continue; + if( !IsOrdinaryTable(pTab) ){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_vtab *pVTab; + int a1; + if( !IsVirtual(pTab) ) continue; + if( pTab->nCol<=0 ){ + const char *zMod = pTab->u.vtab.azArg[0]; + if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; + } + sqlite3ViewGetColumnNames(pParse, pTab); + if( pTab->u.vtab.p==0 ) continue; + pVTab = pTab->u.vtab.p->pVtab; + if( NEVER(pVTab==0) ) continue; + if( NEVER(pVTab->pModule==0) ) continue; + if( pVTab->pModule->iVersion<4 ) continue; + if( pVTab->pModule->xIntegrity==0 ) continue; + sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); +#endif + continue; + } if( isQuick || HasRowid(pTab) ){ pPk = 0; r2 = 0; @@ -2884,7 +2911,8 @@ static const sqlite3_module pragmaVtabModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; /* diff --git a/src/prepare.c b/src/prepare.c index 9f843faa8..d3e134e76 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -598,8 +598,6 @@ void sqlite3ParseObjectReset(Parse *pParse){ db->lookaside.sz = db->lookaside.bDisable ? 0 : db->lookaside.szTrue; assert( pParse->db->pParse==pParse ); db->pParse = pParse->pOuterParse; - pParse->db = 0; - pParse->disableLookaside = 0; } /* diff --git a/src/printf.c b/src/printf.c index 87ad91f79..3c0b182d3 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1389,7 +1389,7 @@ char *sqlite3RCStrRef(char *z){ ** Decrease the reference count by one. Free the string when the ** reference count reaches zero. */ -void sqlite3RCStrUnref(char *z){ +void sqlite3RCStrUnref(void *z){ RCStr *p = (RCStr*)z; assert( p!=0 ); p--; diff --git a/src/resolve.c b/src/resolve.c index 7fc0151ad..0072f6b6a 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -104,21 +104,36 @@ static void resolveAlias( } /* -** Subqueries stores the original database, table and column names for their -** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN". -** Check to see if the zSpan given to this routine matches the zDb, zTab, -** and zCol. If any of zDb, zTab, and zCol are NULL then those fields will -** match anything. +** Subqueries store the original database, table and column names for their +** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN", +** and mark the expression-list item by setting ExprList.a[].fg.eEName +** to ENAME_TAB. +** +** Check to see if the zSpan/eEName of the expression-list item passed to this +** routine matches the zDb, zTab, and zCol. If any of zDb, zTab, and zCol are +** NULL then those fields will match anything. Return true if there is a match, +** or false otherwise. +** +** SF_NestedFrom subqueries also store an entry for the implicit rowid (or +** _rowid_, or oid) column by setting ExprList.a[].fg.eEName to ENAME_ROWID, +** and setting zSpan to "DATABASE.TABLE.". This type of pItem +** argument matches if zCol is a rowid alias. If it is not NULL, (*pbRowid) +** is set to 1 if there is this kind of match. */ int sqlite3MatchEName( const struct ExprList_item *pItem, const char *zCol, const char *zTab, - const char *zDb + const char *zDb, + int *pbRowid ){ int n; const char *zSpan; - if( pItem->fg.eEName!=ENAME_TAB ) return 0; + int eEName = pItem->fg.eEName; + if( eEName!=ENAME_TAB && (eEName!=ENAME_ROWID || NEVER(pbRowid==0)) ){ + return 0; + } + assert( pbRowid==0 || *pbRowid==0 ); zSpan = pItem->zEName; for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){} if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){ @@ -130,9 +145,11 @@ int sqlite3MatchEName( return 0; } zSpan += n+1; - if( zCol && sqlite3StrICmp(zSpan, zCol)!=0 ){ - return 0; + if( zCol ){ + if( eEName==ENAME_TAB && sqlite3StrICmp(zSpan, zCol)!=0 ) return 0; + if( eEName==ENAME_ROWID && sqlite3IsRowid(zCol)==0 ) return 0; } + if( eEName==ENAME_ROWID ) *pbRowid = 1; return 1; } @@ -265,7 +282,7 @@ static int lookupName( ){ int i, j; /* Loop counters */ int cnt = 0; /* Number of matching column names */ - int cntTab = 0; /* Number of matching table names */ + int cntTab = 0; /* Number of potential "rowid" matches */ int nSubquery = 0; /* How many levels of subquery */ sqlite3 *db = pParse->db; /* The database connection */ SrcItem *pItem; /* Use for looping over pSrcList items */ @@ -342,39 +359,49 @@ static int lookupName( assert( pEList!=0 ); assert( pEList->nExpr==pTab->nCol ); for(j=0; jnExpr; j++){ - if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){ + int bRowid = 0; /* True if possible rowid match */ + if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb, &bRowid) ){ continue; } - if( cnt>0 ){ - if( pItem->fg.isUsing==0 - || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 - ){ - /* Two or more tables have the same column name which is - ** not joined by USING. This is an error. Signal as much - ** by clearing pFJMatch and letting cnt go above 1. */ - sqlite3ExprListDelete(db, pFJMatch); - pFJMatch = 0; - }else - if( (pItem->fg.jointype & JT_RIGHT)==0 ){ - /* An INNER or LEFT JOIN. Use the left-most table */ - continue; - }else - if( (pItem->fg.jointype & JT_LEFT)==0 ){ - /* A RIGHT JOIN. Use the right-most table */ - cnt = 0; - sqlite3ExprListDelete(db, pFJMatch); - pFJMatch = 0; - }else{ - /* For a FULL JOIN, we must construct a coalesce() func */ - extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + if( bRowid==0 ){ + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } } + cnt++; + hit = 1; + }else if( cnt>0 ){ + /* This is a potential rowid match, but there has already been + ** a real match found. So this can be ignored. */ + continue; } - cnt++; - cntTab = 2; + cntTab++; pMatch = pItem; pExpr->iColumn = j; pEList->a[j].fg.bUsed = 1; - hit = 1; + + /* rowid cannot be part of a USING clause - assert() this. */ + assert( bRowid==0 || pEList->a[j].fg.bUsingTerm==0 ); if( pEList->a[j].fg.bUsingTerm ) break; } if( hit || zTab==0 ) continue; @@ -569,10 +596,10 @@ static int lookupName( && pMatch && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && sqlite3IsRowid(zCol) - && ALWAYS(VisibleRowid(pMatch->pTab)) + && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom) ){ cnt = 1; - pExpr->iColumn = -1; + if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1; pExpr->affExpr = SQLITE_AFF_INTEGER; } @@ -1025,6 +1052,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ Window *pWin = (IsWindowFunc(pExpr) ? pExpr->y.pWin : 0); #endif assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); + assert( pExpr->pLeft==0 || pExpr->pLeft->op==TK_ORDER ); zId = pExpr->u.zToken; pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); if( pDef==0 ){ @@ -1166,6 +1194,10 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ pNC->nNcErr++; } #endif + else if( is_agg==0 && pExpr->pLeft ){ + sqlite3ExprOrderByAggregateError(pParse, pExpr); + pNC->nNcErr++; + } if( is_agg ){ /* Window functions may not be arguments of aggregate functions. ** Or arguments of other window functions. But aggregate functions @@ -1184,6 +1216,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #endif sqlite3WalkExprList(pWalker, pList); if( is_agg ){ + if( pExpr->pLeft ){ + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + sqlite3WalkExprList(pWalker, pExpr->pLeft->x.pList); + } #ifndef SQLITE_OMIT_WINDOWFUNC if( pWin ){ Select *pSel = pNC->pWinSelect; @@ -1747,10 +1784,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ while( p ){ assert( (p->selFlags & SF_Expanded)!=0 ); assert( (p->selFlags & SF_Resolved)==0 ); - assert( db->suppressErr==0 ); /* SF_Resolved not set if errors suppressed */ p->selFlags |= SF_Resolved; - /* Resolve the expressions in the LIMIT and OFFSET clauses. These ** are not allowed to refer to any names, so pass an empty NameContext. */ diff --git a/src/select.c b/src/select.c index 90b284036..7a79385e0 100644 --- a/src/select.c +++ b/src/select.c @@ -454,6 +454,7 @@ static void unsetJoinExpr(Expr *p, int iTable, int nullable){ } if( p->op==TK_FUNCTION ){ assert( ExprUseXList(p) ); + assert( p->pLeft==0 ); if( p->x.pList ){ int i; for(i=0; ix.pList->nExpr; i++){ @@ -6114,6 +6115,7 @@ static int selectExpander(Walker *pWalker, Select *p){ char *zTName = 0; /* text of name of TABLE */ int iErrOfst; if( pE->op==TK_DOT ){ + assert( (selFlags & SF_NestedFrom)==0 ); assert( pE->pLeft!=0 ); assert( !ExprHasProperty(pE->pLeft, EP_IntValue) ); zTName = pE->pLeft->u.zToken; @@ -6124,6 +6126,7 @@ static int selectExpander(Walker *pWalker, Select *p){ iErrOfst = pE->w.iOfst; } for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ + int nAdd; /* Number of cols including rowid */ Table *pTab = pFrom->pTab; /* Table for this data source */ ExprList *pNestedFrom; /* Result-set of a nested FROM clause */ char *zTabName; /* AS name for this data source */ @@ -6141,6 +6144,7 @@ static int selectExpander(Walker *pWalker, Select *p){ pNestedFrom = pFrom->pSelect->pEList; assert( pNestedFrom!=0 ); assert( pNestedFrom->nExpr==pTab->nCol ); + assert( VisibleRowid(pTab)==0 ); }else{ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; @@ -6171,33 +6175,48 @@ static int selectExpander(Walker *pWalker, Select *p){ }else{ pUsing = 0; } - for(j=0; jnCol; j++){ - char *zName = pTab->aCol[j].zCnName; + + nAdd = pTab->nCol + (VisibleRowid(pTab) && (selFlags&SF_NestedFrom)); + for(j=0; ja[j], 0, zTName, 0)==0 - ){ - continue; - } + if( j==pTab->nCol ){ + zName = sqlite3RowidAlias(pTab); + if( zName==0 ) continue; + }else{ + zName = pTab->aCol[j].zCnName; - /* If a column is marked as 'hidden', omit it from the expanded - ** result-set list unless the SELECT has the SF_IncludeHidden - ** bit set. - */ - if( (p->selFlags & SF_IncludeHidden)==0 - && IsHiddenColumn(&pTab->aCol[j]) - ){ - continue; - } - if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 - && zTName==0 - && (selFlags & (SF_NestedFrom))==0 - ){ - continue; + /* If pTab is actually an SF_NestedFrom sub-select, do not + ** expand any ENAME_ROWID columns. */ + if( pNestedFrom && pNestedFrom->a[j].fg.eEName==ENAME_ROWID ){ + continue; + } + + if( zTName + && pNestedFrom + && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0, 0)==0 + ){ + continue; + } + + /* If a column is marked as 'hidden', omit it from the expanded + ** result-set list unless the SELECT has the SF_IncludeHidden + ** bit set. + */ + if( (p->selFlags & SF_IncludeHidden)==0 + && IsHiddenColumn(&pTab->aCol[j]) + ){ + continue; + } + if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + && zTName==0 + && (selFlags & (SF_NestedFrom))==0 + ){ + continue; + } } + assert( zName ); tableSeen = 1; if( i>0 && zTName==0 && (selFlags & SF_NestedFrom)==0 ){ @@ -6247,11 +6266,11 @@ static int selectExpander(Walker *pWalker, Select *p){ zSchemaName, zTabName, zName); testcase( pX->zEName==0 ); } - pX->fg.eEName = ENAME_TAB; + pX->fg.eEName = (j==pTab->nCol ? ENAME_ROWID : ENAME_TAB); if( (pFrom->fg.isUsing && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0) || (pUsing && sqlite3IdListIndex(pUsing, zName)>=0) - || (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + || (jnCol && (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)) ){ pX->fg.bNoExpand = 1; } @@ -6472,8 +6491,14 @@ static void analyzeAggFuncArgs( pNC->ncFlags |= NC_InAggFunc; for(i=0; inFunc; i++){ Expr *pExpr = pAggInfo->aFunc[i].pFExpr; + assert( pExpr->op==TK_FUNCTION || pExpr->op==TK_AGG_FUNCTION ); assert( ExprUseXList(pExpr) ); sqlite3ExprAnalyzeAggList(pNC, pExpr->x.pList); + if( pExpr->pLeft ){ + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + sqlite3ExprAnalyzeAggList(pNC, pExpr->pLeft->x.pList); + } #ifndef SQLITE_OMIT_WINDOWFUNC assert( !IsWindowFunc(pExpr) ); if( ExprHasProperty(pExpr, EP_WinFunc) ){ @@ -6628,6 +6653,32 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ pFunc->pFunc->zName)); } } + if( pFunc->iOBTab>=0 ){ + ExprList *pOBList; + KeyInfo *pKeyInfo; + int nExtra = 0; + assert( pFunc->pFExpr->pLeft!=0 ); + assert( pFunc->pFExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pFunc->pFExpr->pLeft) ); + pOBList = pFunc->pFExpr->pLeft->x.pList; + if( !pFunc->bOBUnique ){ + nExtra++; /* One extra column for the OP_Sequence */ + } + if( pFunc->bOBPayload ){ + /* extra columns for the function arguments */ + assert( ExprUseXList(pFunc->pFExpr) ); + nExtra += pFunc->pFExpr->x.pList->nExpr; + } + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOBList, 0, nExtra); + if( !pFunc->bOBUnique && pParse->nErr==0 ){ + pKeyInfo->nKeyField++; + } + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + pFunc->iOBTab, pOBList->nExpr+nExtra, 0, + (char*)pKeyInfo, P4_KEYINFO); + ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(ORDER BY)", + pFunc->pFunc->zName)); + } } } @@ -6643,13 +6694,46 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ ExprList *pList; assert( ExprUseXList(pF->pFExpr) ); pList = pF->pFExpr->x.pList; + if( pF->iOBTab>=0 ){ + /* For an ORDER BY aggregate, calls to OP_AggStep where deferred and + ** all content was stored in emphermal table pF->iOBTab. Extract that + ** content now (in ORDER BY order) and make all calls to OP_AggStep + ** before doing the OP_AggFinal call. */ + int iTop; /* Start of loop for extracting columns */ + int nArg; /* Number of columns to extract */ + int nKey; /* Key columns to be skipped */ + int regAgg; /* Extract into this array */ + int j; /* Loop counter */ + + nArg = pList->nExpr; + regAgg = sqlite3GetTempRange(pParse, nArg); + + if( pF->bOBPayload==0 ){ + nKey = 0; + }else{ + assert( pF->pFExpr->pLeft!=0 ); + assert( ExprUseXList(pF->pFExpr->pLeft) ); + assert( pF->pFExpr->pLeft->x.pList!=0 ); + nKey = pF->pFExpr->pLeft->x.pList->nExpr; + if( ALWAYS(!pF->bOBUnique) ) nKey++; + } + iTop = sqlite3VdbeAddOp1(v, OP_Rewind, pF->iOBTab); VdbeCoverage(v); + for(j=nArg-1; j>=0; j--){ + sqlite3VdbeAddOp3(v, OP_Column, pF->iOBTab, nKey+j, regAgg+j); + } + sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u8)nArg); + sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, iTop); + sqlite3ReleaseTempRange(pParse, regAgg, nArg); + } sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), pList ? pList->nExpr : 0); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } } - /* ** Generate code that will update the accumulator memory cells for an ** aggregate based on the current cursor position. @@ -6658,6 +6742,13 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ ** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator ** registers if register regAcc contains 0. The caller will take care ** of setting and clearing regAcc. +** +** For an ORDER BY aggregate, the actual accumulator memory cell update +** is deferred until after all input rows have been received, so that they +** can be run in the requested order. In that case, instead of invoking +** OP_AggStep to update the accumulator, just add the arguments that would +** have been passed into OP_AggStep into the sorting ephemeral table +** (along with the appropriate sort key). */ static void updateAccumulator( Parse *pParse, @@ -6679,6 +6770,8 @@ static void updateAccumulator( int nArg; int addrNext = 0; int regAgg; + int regAggSz = 0; + int regDistinct = 0; ExprList *pList; assert( ExprUseXList(pF->pFExpr) ); assert( !IsWindowFunc(pF->pFExpr) ); @@ -6705,9 +6798,44 @@ static void updateAccumulator( addrNext = sqlite3VdbeMakeLabel(pParse); sqlite3ExprIfFalse(pParse, pFilter, addrNext, SQLITE_JUMPIFNULL); } - if( pList ){ + if( pF->iOBTab>=0 ){ + /* Instead of invoking AggStep, we must push the arguments that would + ** have been passed to AggStep onto the sorting table. */ + int jj; /* Registered used so far in building the record */ + ExprList *pOBList; /* The ORDER BY clause */ + assert( pList!=0 ); + nArg = pList->nExpr; + assert( nArg>0 ); + assert( pF->pFExpr->pLeft!=0 ); + assert( pF->pFExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pF->pFExpr->pLeft) ); + pOBList = pF->pFExpr->pLeft->x.pList; + assert( pOBList!=0 ); + assert( pOBList->nExpr>0 ); + regAggSz = pOBList->nExpr; + if( !pF->bOBUnique ){ + regAggSz++; /* One register for OP_Sequence */ + } + if( pF->bOBPayload ){ + regAggSz += nArg; + } + regAggSz++; /* One extra register to hold result of MakeRecord */ + regAgg = sqlite3GetTempRange(pParse, regAggSz); + regDistinct = regAgg; + sqlite3ExprCodeExprList(pParse, pOBList, regAgg, 0, SQLITE_ECEL_DUP); + jj = pOBList->nExpr; + if( !pF->bOBUnique ){ + sqlite3VdbeAddOp2(v, OP_Sequence, pF->iOBTab, regAgg+jj); + jj++; + } + if( pF->bOBPayload ){ + regDistinct = regAgg+jj; + sqlite3ExprCodeExprList(pParse, pList, regDistinct, 0, SQLITE_ECEL_DUP); + } + }else if( pList ){ nArg = pList->nExpr; regAgg = sqlite3GetTempRange(pParse, nArg); + regDistinct = regAgg; sqlite3ExprCodeExprList(pParse, pList, regAgg, 0, SQLITE_ECEL_DUP); }else{ nArg = 0; @@ -6718,26 +6846,37 @@ static void updateAccumulator( addrNext = sqlite3VdbeMakeLabel(pParse); } pF->iDistinct = codeDistinct(pParse, eDistinctType, - pF->iDistinct, addrNext, pList, regAgg); - } - if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ - CollSeq *pColl = 0; - struct ExprList_item *pItem; - int j; - assert( pList!=0 ); /* pList!=0 if pF->pFunc has NEEDCOLL */ - for(j=0, pItem=pList->a; !pColl && jpExpr); - } - if( !pColl ){ - pColl = pParse->db->pDfltColl; + pF->iDistinct, addrNext, pList, regDistinct); + } + if( pF->iOBTab>=0 ){ + /* Insert a new record into the ORDER BY table */ + sqlite3VdbeAddOp3(v, OP_MakeRecord, regAgg, regAggSz-1, + regAgg+regAggSz-1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pF->iOBTab, regAgg+regAggSz-1, + regAgg, regAggSz-1); + sqlite3ReleaseTempRange(pParse, regAgg, regAggSz); + }else{ + /* Invoke the AggStep function */ + if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ + CollSeq *pColl = 0; + struct ExprList_item *pItem; + int j; + assert( pList!=0 ); /* pList!=0 if pF->pFunc has NEEDCOLL */ + for(j=0, pItem=pList->a; !pColl && jpExpr); + } + if( !pColl ){ + pColl = pParse->db->pDfltColl; + } + if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; + sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, + (char *)pColl, P4_COLLSEQ); } - if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; - sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); + sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u8)nArg); + sqlite3ReleaseTempRange(pParse, regAgg, nArg); } - sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); - sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, (u8)nArg); - sqlite3ReleaseTempRange(pParse, regAgg, nArg); if( addrNext ){ sqlite3VdbeResolveLabel(v, addrNext); } diff --git a/src/shell.c.in b/src/shell.c.in index f6ee64fc3..ad16d7204 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -449,20 +449,25 @@ static int bail_on_error = 0; */ static int stdin_is_interactive = 1; +/* +** If build is for non-RT Windows, without 3rd-party line editing, +** console input and output may be done in a UTF-8 compatible way, +** if the OS is capable of it and the --no-utf8 option is not seen. +*/ #if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \ - && !defined(SHELL_OMIT_WIN_UTF8) + && !defined(SHELL_OMIT_WIN_UTF8) && !SQLITE_OS_WINRT # define SHELL_WIN_UTF8_OPT 1 +/* Record whether to do UTF-8 console I/O translation per stream. */ + static int console_utf8_in = 0; + static int console_utf8_out = 0; +/* Record whether can do UTF-8 or --no-utf8 seen in invocation. */ + static int mbcs_opted = 1; /* Assume cannot do until shown otherwise. */ #else +# define console_utf8_in 0 +# define console_utf8_out 0 # define SHELL_WIN_UTF8_OPT 0 #endif -#if SHELL_WIN_UTF8_OPT -/* -** Setup console for UTF-8 input/output when following variable true. -*/ -static int console_utf8 = 0; -#endif - /* ** On Windows systems we have to know if standard output is a console ** in order to translate UTF-8 into MBCS. The following variable is @@ -596,13 +601,13 @@ static char *dynamicContinuePrompt(void){ #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ #if SHELL_WIN_UTF8_OPT -/* Following struct is used for -utf8 operation. */ +/* Following struct is used for UTF-8 console I/O. */ static struct ConsoleState { int stdinEof; /* EOF has been seen on console input */ int infsMode; /* Input file stream mode upon shell start */ UINT inCodePage; /* Input code page upon shell start */ UINT outCodePage; /* Output code page upon shell start */ - HANDLE hConsoleIn; /* Console input handle */ + HANDLE hConsole; /* Console input or output handle */ DWORD consoleMode; /* Console mode upon shell start */ } conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; @@ -611,50 +616,123 @@ static struct ConsoleState { #endif /* -** Prepare console, (if known to be a WIN32 console), for UTF-8 -** input (from either typing or suitable paste operations) and for -** UTF-8 rendering. This may "fail" with a message to stderr, where -** the preparation is not done and common "code page" issues occur. +** If given stream number is a console, return 1 and get some attributes, +** else return 0 and set the output attributes to invalid values. */ -static void console_prepare(void){ - HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE); - DWORD consoleMode = 0; - if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR - && GetConsoleMode( hCI, &consoleMode) ){ - if( !IsValidCodePage(CP_UTF8) ){ - fprintf(stderr, "Cannot use UTF-8 code page.\n"); - console_utf8 = 0; - return; - } - conState.hConsoleIn = hCI; - conState.consoleMode = consoleMode; - conState.inCodePage = GetConsoleCP(); - conState.outCodePage = GetConsoleOutputCP(); - SetConsoleCP(CP_UTF8); +static short console_attrs(unsigned stnum, HANDLE *pH, DWORD *pConsMode){ + static int stid[3] = { STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE }; + HANDLE h; + *pH = INVALID_HANDLE_VALUE; + *pConsMode = 0; + if( stnum > 2 ) return 0; + h = GetStdHandle(stid[stnum]); + if( h!=*pH && GetFileType(h)==FILE_TYPE_CHAR && GetConsoleMode(h,pConsMode) ){ + *pH = h; + return 1; + } + return 0; +} + +/* +** Perform a runtime test of Windows console to determine if it can +** do char-stream I/O correctly when the code page is set to CP_UTF8. +** Returns are: 1 => yes it can, 0 => no it cannot +** +** The console's output code page is momentarily set, then restored. +** So this should only be run when the process is given use of the +** console for either input or output. +*/ +static short ConsoleDoesUTF8(void){ + UINT ocp = GetConsoleOutputCP(); + const char TrialUtf8[] = { '\xC8', '\xAB' }; /* "ȫ" or 2 MBCS characters */ + WCHAR aReadBack[1] = { 0 }; /* Read back as 0x022B when decoded as UTF-8. */ + CONSOLE_SCREEN_BUFFER_INFO csbInfo = {0}; + /* Create an inactive screen buffer with which to do the experiment. */ + HANDLE hCSB = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE, 0, 0, + CONSOLE_TEXTMODE_BUFFER, NULL); + if( hCSB!=INVALID_HANDLE_VALUE ){ + COORD cpos = {0,0}; + DWORD rbc; + SetConsoleCursorPosition(hCSB, cpos); SetConsoleOutputCP(CP_UTF8); - consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; - SetConsoleMode(conState.hConsoleIn, consoleMode); + /* Write 2 chars which are a single character in UTF-8 but more in MBCS. */ + WriteConsoleA(hCSB, TrialUtf8, sizeof(TrialUtf8), NULL, NULL); + ReadConsoleOutputCharacterW(hCSB, &aReadBack[0], 1, cpos, &rbc); + GetConsoleScreenBufferInfo(hCSB, &csbInfo); + SetConsoleOutputCP(ocp); + CloseHandle(hCSB); + } + /* Return 1 if cursor advanced by 1 position, else 0. */ + return (short)(csbInfo.dwCursorPosition.X == 1 && aReadBack[0] == 0x022B); +} + +static short in_console = 0; +static short out_console = 0; + +/* +** Determine whether either normal I/O stream is the console, +** and whether it can do UTF-8 translation, setting globals +** in_console, out_console and mbcs_opted accordingly. +*/ +static void probe_console(void){ + HANDLE h; + DWORD cMode; + in_console = console_attrs(0, &h, &cMode); + out_console = console_attrs(1, &h, &cMode); + if( in_console || out_console ) mbcs_opted = !ConsoleDoesUTF8(); +} + +/* +** If console is used for normal I/O, absent a --no-utf8 option, +** prepare console for UTF-8 input (from either typing or suitable +** paste operations) and/or for UTF-8 output rendering. +** +** The console state upon entry is preserved, in conState, so that +** console_restore() can later restore the same console state. +** +** The globals console_utf8_in and console_utf8_out are set, for +** later use in selecting UTF-8 or MBCS console I/O translations. +** This routine depends upon globals set by probe_console(). +*/ +static void console_prepare_utf8(void){ + struct ConsoleState csWork = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; + + console_utf8_in = console_utf8_out = 0; + if( (!in_console && !out_console) || mbcs_opted ) return; + console_attrs((in_console)? 0 : 1, &conState.hConsole, &conState.consoleMode); + conState.inCodePage = GetConsoleCP(); + conState.outCodePage = GetConsoleOutputCP(); + if( in_console ){ + SetConsoleCP(CP_UTF8); + DWORD newConsoleMode = conState.consoleMode + | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; + SetConsoleMode(conState.hConsole, newConsoleMode); conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); - console_utf8 = 1; - }else{ - console_utf8 = 0; + console_utf8_in = 1; + } + if( out_console ){ + SetConsoleOutputCP(CP_UTF8); + console_utf8_out = 1; } } /* -** Undo the effects of console_prepare(), if any. +** Undo the effects of console_prepare_utf8(), if any. */ static void SQLITE_CDECL console_restore(void){ - if( console_utf8 && conState.inCodePage!=0 - && conState.hConsoleIn!=INVALID_HANDLE_VALUE ){ - _setmode(_fileno(stdin), conState.infsMode); - SetConsoleCP(conState.inCodePage); - SetConsoleOutputCP(conState.outCodePage); - SetConsoleMode(conState.hConsoleIn, conState.consoleMode); + if( (console_utf8_in||console_utf8_out) + && conState.hConsole!=INVALID_HANDLE_VALUE ){ + if( console_utf8_in ){ + SetConsoleCP(conState.inCodePage); + _setmode(_fileno(stdin), conState.infsMode); + } + if( console_utf8_out ) SetConsoleOutputCP(conState.outCodePage); + SetConsoleMode(conState.hConsole, conState.consoleMode); /* Avoid multiple calls. */ - conState.hConsoleIn = INVALID_HANDLE_VALUE; + conState.hConsole = INVALID_HANDLE_VALUE; conState.consoleMode = 0; - console_utf8 = 0; + console_utf8_in = 0; + console_utf8_out = 0; } } @@ -662,11 +740,11 @@ static void SQLITE_CDECL console_restore(void){ ** Collect input like fgets(...) with special provisions for input ** from the Windows console to get around its strange coding issues. ** Defers to plain fgets() when input is not interactive or when the -** startup option, -utf8, has not been provided or taken effect. +** UTF-8 input is unavailable or opted out. */ static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ if( fin==0 ) fin = stdin; - if( fin==stdin && stdin_is_interactive && console_utf8 ){ + if( fin==stdin && stdin_is_interactive && console_utf8_in ){ # define SQLITE_IALIM 150 wchar_t wbuf[SQLITE_IALIM]; int lend = 0; @@ -679,7 +757,7 @@ static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ ? SQLITE_IALIM : (ncmax-1 - noc)/4; # undef SQLITE_IALIM DWORD nbr = 0; - BOOL bRC = ReadConsoleW(conState.hConsoleIn, wbuf, na, &nbr, 0); + BOOL bRC = ReadConsoleW(conState.hConsole, wbuf, na, &nbr, 0); if( !bRC || (noc==0 && nbr==0) ) return 0; if( nbr > 0 ){ int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, @@ -725,20 +803,16 @@ static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ /* ** Render output like fprintf(). Except, if the output is going to the -** console and if this is running on a Windows machine, and if the -utf8 -** option is unavailable or (available and inactive), translate the +** console and if this is running on a Windows machine, and if UTF-8 +** output unavailable (or available but opted out), translate the ** output from UTF-8 into MBCS for output through 8-bit stdout stream. -** (With -utf8 active, no translation is needed and must not be done.) +** (Without -no-utf8, no translation is needed and must not be done.) */ #if defined(_WIN32) || defined(WIN32) void utf8_printf(FILE *out, const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); - if( stdout_is_console && (out==stdout || out==stderr) -# if SHELL_WIN_UTF8_OPT - && !console_utf8 -# endif - ){ + if( stdout_is_console && (out==stdout || out==stderr) && !console_utf8_out ){ char *z1 = sqlite3_vmprintf(zFormat, ap); char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); sqlite3_free(z1); @@ -949,14 +1023,10 @@ static char *local_getline(char *zLine, FILE *in){ } } #if defined(_WIN32) || defined(WIN32) - /* For interactive input on Windows systems, without -utf8, + /* For interactive input on Windows systems, with -no-utf8, ** translate the multi-byte characterset characters into UTF-8. - ** This is the translation that predates the -utf8 option. */ - if( stdin_is_interactive && in==stdin -# if SHELL_WIN_UTF8_OPT - && !console_utf8 -# endif /* SHELL_WIN_UTF8_OPT */ - ){ + ** This is the translation that predates console UTF-8 input. */ + if( stdin_is_interactive && in==stdin && !console_utf8_in ){ char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); if( zTrans ){ i64 nTrans = strlen(zTrans)+1; @@ -3371,7 +3441,7 @@ static void display_explain_scanstats( if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ break; } - n = strlen(z) + scanStatsHeight(p, ii)*3; + n = (int)strlen(z) + scanStatsHeight(p, ii)*3; if( n>nWidth ) nWidth = n; } nWidth += 4; @@ -3383,12 +3453,12 @@ static void display_explain_scanstats( i64 nCycle = 0; int iId = 0; int iPid = 0; - const char *z = 0; + const char *zo = 0; const char *zName = 0; char *zText = 0; double rEst = 0.0; - if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){ break; } sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst); @@ -3399,7 +3469,7 @@ static void display_explain_scanstats( sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName); - zText = sqlite3_mprintf("%s", z); + zText = sqlite3_mprintf("%s", zo); if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ char *z = 0; if( nCycle>=0 && nTotal>0 ){ @@ -8047,7 +8117,6 @@ static int do_meta_command(char *zLine, ShellState *p){ azArg[nArg++] = &zLine[h]; while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } if( zLine[h] ) zLine[h++] = 0; - resolve_backslashes(azArg[nArg-1]); } } azArg[nArg] = 0; @@ -8786,8 +8855,10 @@ static int do_meta_command(char *zLine, ShellState *p){ "SELECT rowid FROM sqlite_schema" " WHERE name GLOB 'sqlite_stat[134]'", -1, &pStmt, 0); - doStats = sqlite3_step(pStmt)==SQLITE_ROW; - sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ){ + doStats = sqlite3_step(pStmt)==SQLITE_ROW; + sqlite3_finalize(pStmt); + } } if( doStats==0 ){ raw_printf(p->out, "/* No STAT tables available */\n"); @@ -9076,6 +9147,14 @@ static int do_meta_command(char *zLine, ShellState *p){ ** the remaining columns. */ if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + /* + ** For CSV mode, per RFC 4180, accept EOF in lieu of final + ** record terminator but only for last field of multi-field row. + ** (If there are too few fields, it's not valid CSV anyway.) + */ + if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){ + z = ""; + } sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); if( idb, opt); @@ -11937,6 +12018,9 @@ static const char zOptions[] = " -multiplex enable the multiplexor VFS\n" #endif " -newline SEP set output row separator. Default: '\\n'\n" +#if SHELL_WIN_UTF8_OPT + " -no-utf8 do not try to set up UTF-8 output (for legacy)\n" +#endif " -nofollow refuse to open symbolic links to database files\n" " -nonce STRING set the safe-mode escape nonce\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" @@ -11953,7 +12037,7 @@ static const char zOptions[] = " -table set output mode to 'table'\n" " -tabs set output mode to 'tabs'\n" " -unsafe-testing allow unsafe commands and modes for testing\n" -#if SHELL_WIN_UTF8_OPT +#if SHELL_WIN_UTF8_OPT && 0 /* Option is accepted, but is now the default. */ " -utf8 setup interactive console code page for UTF-8\n" #endif " -version show SQLite version\n" @@ -12104,7 +12188,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdout_is_console = isatty(1); #endif #if SHELL_WIN_UTF8_OPT - atexit(console_restore); /* Needs revision for CLI as library call */ + probe_console(); /* Check for console I/O and UTF-8 capability. */ + if( !mbcs_opted ) atexit(console_restore); #endif atexit(sayAbnormalExit); #ifdef SQLITE_DEBUG @@ -12191,8 +12276,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ /* 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. + ** the size of the alternative malloc heap, options affecting commands + ** or SQL run from the command line, and the first command to execute. */ #ifndef SQLITE_SHELL_FIDDLE verify_uninitialized(); @@ -12226,12 +12311,26 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ (void)cmdline_option_value(argc, argv, ++i); }else if( cli_strcmp(z,"-init")==0 ){ zInitFile = cmdline_option_value(argc, argv, ++i); + }else if( cli_strcmp(z,"-interactive")==0 ){ + /* Need to check for interactive override here to so that it can + ** affect console setup (for Windows only) and testing thereof. + */ + stdin_is_interactive = 1; }else if( cli_strcmp(z,"-batch")==0 ){ /* Need to check for batch mode here to so we can avoid printing ** informational messages (like from process_sqliterc) before ** we do the actual processing of arguments later in a second pass. */ stdin_is_interactive = 0; + }else if( cli_strcmp(z,"-utf8")==0 ){ +#if SHELL_WIN_UTF8_OPT + /* Option accepted, but is ignored except for this diagnostic. */ + if( mbcs_opted ) fprintf(stderr, "Cannot do UTF-8 at this console.\n"); +#endif /* SHELL_WIN_UTF8_OPT */ + }else if( cli_strcmp(z,"-no-utf8")==0 ){ +#if SHELL_WIN_UTF8_OPT + mbcs_opted = 1; +#endif /* SHELL_WIN_UTF8_OPT */ }else if( cli_strcmp(z,"-heap")==0 ){ #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) const char *zSize; @@ -12370,6 +12469,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ exit(1); } } +#if SHELL_WIN_UTF8_OPT + /* Get indicated Windows console setup done before running invocation commands. */ + if( in_console || out_console ){ + console_prepare_utf8(); + } + if( !in_console ){ + setBinaryMode(stdin, 0); + } +#endif if( data.pAuxDb->zDbFilename==0 ){ #ifndef SQLITE_OMIT_MEMORYDB @@ -12497,13 +12605,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ - stdin_is_interactive = 1; + /* already handled */ }else if( cli_strcmp(z,"-batch")==0 ){ - stdin_is_interactive = 0; + /* already handled */ }else if( cli_strcmp(z,"-utf8")==0 ){ -#if SHELL_WIN_UTF8_OPT - console_utf8 = 1; -#endif /* SHELL_WIN_UTF8_OPT */ + /* already handled */ + }else if( cli_strcmp(z,"-no-utf8")==0 ){ + /* already handled */ }else if( cli_strcmp(z,"-heap")==0 ){ i++; }else if( cli_strcmp(z,"-pagecache")==0 ){ @@ -12585,14 +12693,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } data.cMode = data.mode; } -#if SHELL_WIN_UTF8_OPT - if( console_utf8 && stdin_is_interactive ){ - console_prepare(); - }else{ - setBinaryMode(stdin, 0); - console_utf8 = 0; - } -#endif if( !readStdin ){ /* Run all arguments that do not begin with '-' as if they were separate @@ -12628,11 +12728,20 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( stdin_is_interactive ){ char *zHome; char *zHistory; + const char *zCharset = ""; int nHistory; +#if SHELL_WIN_UTF8_OPT + switch( console_utf8_in+2*console_utf8_out ){ + default: case 0: break; + case 1: zCharset = " (utf8 in)"; break; + case 2: zCharset = " (utf8 out)"; break; + case 3: zCharset = " (utf8 I/O)"; break; + } +#endif printf( - "SQLite version %s %.19s\n" /*extra-version-info*/ + "SQLite version %s %.19s%s\n" /*extra-version-info*/ "Enter \".help\" for usage hints.\n", - sqlite3_libversion(), sqlite3_sourceid() + sqlite3_libversion(), sqlite3_sourceid(), zCharset ); if( warnInmemoryDb ){ printf("Connected to a "); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 48009b145..01a7f4d4e 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -2127,7 +2127,7 @@ struct sqlite3_mem_methods { ** is stored in each sorted record and the required column values loaded ** from the database as records are returned in sorted order. The default ** value for this option is to never use this optimization. Specifying a -** negative value for this option restores the default behaviour. +** negative value for this option restores the default behavior. ** This option is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option. ** @@ -2302,7 +2302,7 @@ struct sqlite3_mem_methods { ** database handle, SQLite checks if this will mean that there are now no ** connections at all to the database. If so, it performs a checkpoint ** operation before closing the connection. This option may be used to -** override this behaviour. The first parameter passed to this operation +** override this behavior. The first parameter passed to this operation ** is an integer - positive to disable checkpoints-on-close, or zero (the ** default) to enable them, and negative to leave the setting unchanged. ** The second parameter is a pointer to an integer @@ -3955,6 +3955,7 @@ void sqlite3_free_filename(sqlite3_filename); ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language ** text that describes the error, as either UTF-8 or UTF-16 respectively. +** (See how SQLite handles [invalid UTF] for exceptions to this rule.) ** ^(Memory to hold the error message string is managed internally. ** The application does not need to worry about freeing the result. ** However, the error string might be overwritten or deallocated by @@ -5325,6 +5326,7 @@ int sqlite3_finalize(sqlite3_stmt *pStmt); */ int sqlite3_reset(sqlite3_stmt *pStmt); + /* ** CAPI3REF: Create Or Redefine SQL Functions ** KEYWORDS: {function creation routines} @@ -5879,32 +5881,32 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** METHOD: sqlite3_context ** ** These functions may be used by (non-aggregate) SQL functions to -** associate metadata with argument values. If the same value is passed to -** multiple invocations of the same SQL function during query execution, under -** some circumstances the associated metadata may be preserved. An example -** of where this might be useful is in a regular-expression matching -** function. The compiled version of the regular expression can be stored as -** metadata associated with the pattern string. +** associate auxiliary data with argument values. If the same argument +** value is passed to multiple invocations of the same SQL function during +** query execution, under some circumstances the associated auxiliary data +** might be preserved. An example of where this might be useful is in a +** regular-expression matching function. The compiled version of the regular +** expression can be stored as auxiliary data associated with the pattern string. ** Then as long as the pattern string remains the same, ** the compiled regular expression can be reused on multiple ** invocations of the same function. ** -** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the metadata +** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the auxiliary data ** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument ** value to the application-defined function. ^N is zero for the left-most -** function argument. ^If there is no metadata +** function argument. ^If there is no auxiliary data ** associated with the function argument, the sqlite3_get_auxdata(C,N) interface ** returns a NULL pointer. ** -** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th -** argument of the application-defined function. ^Subsequent +** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as auxiliary data for the +** N-th argument of the application-defined function. ^Subsequent ** calls to sqlite3_get_auxdata(C,N) return P from the most recent -** sqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or -** NULL if the metadata has been discarded. +** sqlite3_set_auxdata(C,N,P,X) call if the auxiliary data is still valid or +** NULL if the auxiliary data has been discarded. ** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL, ** SQLite will invoke the destructor function X with parameter P exactly -** once, when the metadata is discarded. -** SQLite is free to discard the metadata at any time, including:
        +** once, when the auxiliary data is discarded. +** SQLite is free to discard the auxiliary data at any time, including:
          **
        • ^(when the corresponding function parameter changes)^, or **
        • ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the ** SQL statement)^, or @@ -5920,7 +5922,7 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** function implementation should not make any use of P after ** sqlite3_set_auxdata() has been called. ** -** ^(In practice, metadata is preserved between function calls for +** ^(In practice, auxiliary data is preserved between function calls for ** function parameters that are compile-time constants, including literal ** values and [parameters] and expressions composed from the same.)^ ** @@ -5930,10 +5932,67 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** ** These routines must be called from the same thread in which ** the SQL function is running. +** +** See also: [sqlite3_get_clientdata()] and [sqlite3_set_clientdata()]. */ void *sqlite3_get_auxdata(sqlite3_context*, int N); void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); +/* +** CAPI3REF: Database Connection Client Data +** METHOD: sqlite3 +** +** These functions are used to associate one or more named pointers +** with a [database connection]. +** A call to sqlite3_set_clientdata(D,N,P,X) causes the pointer P +** to be attached to [database connection] D using name N. Subsequent +** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P +** or a NULL pointer if there were no prior calls to +** sqlite3_set_clientdata() with the same values of D and N. +** Names are compared using strcmp() and are thus case sensitive. +** +** If P and X are both non-NULL, then the destructor X is invoked with +** argument P on the first of the following occurrences: +**
            +**
          • An out-of-memory error occurs during the call to +** sqlite3_set_clientdata() which attempts to register pointer P. +**
          • A subsequent call to sqlite3_set_clientdata(D,N,P,X) is made +** with the same D and N parameters. +**
          • The database connection closes. SQLite does not make any guarantees +** about the order in which destructors are called, only that all +** destructors will be called exactly once at some point during the +** database connection closing process. +**
          +** +** SQLite does not do anything with client data other than invoke +** destructors on the client data at the appropriate time. The intended +** use for client data is to provide a mechanism for wrapper libraries +** to store additional information about an SQLite database connection. +** +** There is no limit (other than available memory) on the number of different +** client data pointers (with different names) that can be attached to a +** single database connection. However, the implementation is optimized +** for the case of having only one or two different client data names. +** Applications and wrapper libraries are discouraged from using more than +** one client data name each. +** +** There is no way to enumerate the client data pointers +** associated with a database connection. The N parameter can be thought +** of as a secret key such that only code that knows the secret key is able +** to access the associated data. +** +** Security Warning: These interfaces should not be exposed in scripting +** languages or in other circumstances where it might be possible for an +** an attacker to invoke them. Any agent that can invoke these interfaces +** can probably also take control of the process. +** +** Database connection client data is only available for SQLite +** version 3.44.0 ([dateof:3.44.0]) and later. +** +** See also: [sqlite3_set_auxdata()] and [sqlite3_get_auxdata()]. +*/ +void *sqlite3_get_clientdata(sqlite3*,const char*); +int sqlite3_set_clientdata(sqlite3*, const char*, void*, void(*)(void*)); /* ** CAPI3REF: Constants Defining Special Destructor Behavior @@ -6566,7 +6625,7 @@ int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); int sqlite3_txn_state(sqlite3*,const char *zSchema); /* -** CAPI3REF: Allowed return values from [sqlite3_txn_state()] +** CAPI3REF: Allowed return values from sqlite3_txn_state() ** KEYWORDS: {transaction state} ** ** These constants define the current transaction state of a database file. @@ -6698,7 +6757,7 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** ^Each call to the sqlite3_autovacuum_pages() interface overrides all ** previous invocations for that database connection. ^If the callback ** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, -** then the autovacuum steps callback is cancelled. The return value +** then the autovacuum steps callback is canceled. The return value ** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might ** be some other error code if something goes wrong. The current ** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other @@ -7217,6 +7276,10 @@ struct sqlite3_module { /* The methods above are in versions 1 and 2 of the sqlite_module object. ** Those below are for version 3 and greater. */ int (*xShadowName)(const char*); + /* The methods above are in versions 1 through 3 of the sqlite_module object. + ** Those below are for version 4 and greater. */ + int (*xIntegrity)(sqlite3_vtab *pVTab, const char *zSchema, + const char *zTabName, int mFlags, char **pzErr); }; /* @@ -7704,7 +7767,7 @@ int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); ** code is returned and the transaction rolled back. ** ** Calling this function with an argument that is not a NULL pointer or an -** open blob handle results in undefined behaviour. ^Calling this routine +** open blob handle results in undefined behavior. ^Calling this routine ** with a null pointer (such as would be returned by a failed call to ** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function ** is passed a valid open blob handle, the values returned by the @@ -8184,6 +8247,7 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_PRNG_SAVE 5 #define SQLITE_TESTCTRL_PRNG_RESTORE 6 #define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */ +#define SQLITE_TESTCTRL_FK_NO_ACTION 7 #define SQLITE_TESTCTRL_BITVEC_TEST 8 #define SQLITE_TESTCTRL_FAULT_INSTALL 9 #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 @@ -9245,8 +9309,8 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing -** unlock-notify callback is cancelled. ^The blocked connections -** unlock-notify callback may also be cancelled by closing the blocked +** unlock-notify callback is canceled. ^The blocked connections +** unlock-notify callback may also be canceled by closing the blocked ** connection using [sqlite3_close()]. ** ** The unlock-notify callback is not reentrant. If an application invokes @@ -10549,6 +10613,13 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); ** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy ** of the database exists. ** +** After the call, if the SQLITE_SERIALIZE_NOCOPY bit had been set, +** the returned buffer content will remain accessible and unchanged +** until either the next write operation on the connection or when +** the connection is closed, and applications must not modify the +** buffer. If the bit had been clear, the returned buffer will not +** be accessed by SQLite after the call. +** ** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory ** allocation error occurs. @@ -10597,6 +10668,9 @@ unsigned char *sqlite3_serialize( ** SQLite will try to increase the buffer size using sqlite3_realloc64() ** if writes on the database cause it to grow larger than M bytes. ** +** Applications must not modify the buffer P or invalidate it before +** the database connection D is closed. +** ** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the ** database is currently in a read transaction or is involved in a backup ** operation. @@ -10605,6 +10679,13 @@ unsigned char *sqlite3_serialize( ** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the ** function returns SQLITE_ERROR. ** +** The deserialized database should not be in [WAL mode]. If the database +** is in WAL mode, then any attempt to use the database file will result +** in an [SQLITE_CANTOPEN] error. The application can set the +** [file format version numbers] (bytes 18 and 19) of the input database P +** to 0x01 prior to invoking sqlite3_deserialize(D,S,P,N,M,F) to force the +** database file into rollback mode and work around this limitation. +** ** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 711638099..ae0949baf 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -363,6 +363,9 @@ struct sqlite3_api_routines { int (*is_interrupted)(sqlite3*); /* Version 3.43.0 and later */ int (*stmt_explain)(sqlite3_stmt*,int); + /* Version 3.44.0 and later */ + void *(*get_clientdata)(sqlite3*,const char*); + int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); }; /* @@ -693,6 +696,9 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_is_interrupted sqlite3_api->is_interrupted /* Version 3.43.0 and later */ #define sqlite3_stmt_explain sqlite3_api->stmt_explain +/* Version 3.44.0 and later */ +#define sqlite3_get_clientdata sqlite3_api->get_clientdata +#define sqlite3_set_clientdata sqlite3_api->set_clientdata #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c2a9f855a..dbf01dd13 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -318,6 +318,16 @@ # endif #endif +/* +** Enable SQLITE_USE_SEH by default on MSVC builds. Only omit +** SEH support if the -DSQLITE_OMIT_SEH option is given. +*/ +#if defined(_MSC_VER) && !defined(SQLITE_OMIT_SEH) +# define SQLITE_USE_SEH 1 +#else +# undef SQLITE_USE_SEH +#endif + /* ** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2. ** 0 means mutexes are permanently disable and the library is never @@ -923,16 +933,33 @@ typedef INT16_TYPE LogEst; ** using C-preprocessor macros. If that is unsuccessful, or if ** -DSQLITE_BYTEORDER=0 is set, then byte-order is determined ** at run-time. +** +** If you are building SQLite on some obscure platform for which the +** following ifdef magic does not work, you can always include either: +** +** -DSQLITE_BYTEORDER=1234 +** +** or +** +** -DSQLITE_BYTEORDER=4321 +** +** to cause the build to work for little-endian or big-endian processors, +** respectively. */ -#ifndef SQLITE_BYTEORDER -# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ +#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */ +# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__ +# define SQLITE_BYTEORDER 4321 +# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ +# define SQLITE_BYTEORDER 1234 +# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1 +# define SQLITE_BYTEORDER 4321 +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64) -# define SQLITE_BYTEORDER 1234 -# elif defined(sparc) || defined(__ppc__) || \ - defined(__ARMEB__) || defined(__AARCH64EB__) -# define SQLITE_BYTEORDER 4321 +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__) +# define SQLITE_BYTEORDER 4321 # else # define SQLITE_BYTEORDER 0 # endif @@ -1256,6 +1283,7 @@ typedef struct Column Column; typedef struct Cte Cte; typedef struct CteUse CteUse; typedef struct Db Db; +typedef struct DbClientData DbClientData; typedef struct DbFixer DbFixer; typedef struct Schema Schema; typedef struct Expr Expr; @@ -1734,6 +1762,7 @@ struct sqlite3 { i64 nDeferredCons; /* Net deferred constraints this transaction. */ i64 nDeferredImmCons; /* Net deferred immediate constraints */ int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ + DbClientData *pDbData; /* sqlite3_set_clientdata() content */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY /* The following variables are all protected by the STATIC_MAIN ** mutex, not by sqlite3.mutex. They are used by code in notify.c. @@ -1816,6 +1845,7 @@ struct sqlite3 { /* the count using a callback. */ #define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */ #define SQLITE_ReadUncommit HI(0x00004) /* READ UNCOMMITTED in shared-cache */ +#define SQLITE_FkNoAction HI(0x00008) /* Treat all FK as NO ACTION */ /* Flags used only if debugging */ #ifdef SQLITE_DEBUG @@ -2831,6 +2861,9 @@ struct AggInfo { FuncDef *pFunc; /* The aggregate function implementation */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ int iDistAddr; /* Address of OP_OpenEphemeral */ + int iOBTab; /* Ephemeral table to implement ORDER BY */ + u8 bOBPayload; /* iOBTab has payload columns separate from key */ + u8 bOBUnique; /* Enforce uniqueness on iOBTab keys */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ u32 selId; /* Select to which this AggInfo belongs */ @@ -3015,7 +3048,7 @@ struct Expr { #define EP_Reduced 0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ #define EP_Win 0x008000 /* Contains window functions */ #define EP_TokenOnly 0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ - /* 0x020000 // Available for reuse */ +#define EP_FullSize 0x020000 /* Expr structure must remain full sized */ #define EP_IfNullRow 0x040000 /* The TK_IF_NULL_ROW opcode */ #define EP_Unlikely 0x080000 /* unlikely() or likelihood() function */ #define EP_ConstFunc 0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ @@ -3045,6 +3078,7 @@ struct Expr { #define ExprClearProperty(E,P) (E)->flags&=~(P) #define ExprAlwaysTrue(E) (((E)->flags&(EP_OuterON|EP_IsTrue))==EP_IsTrue) #define ExprAlwaysFalse(E) (((E)->flags&(EP_OuterON|EP_IsFalse))==EP_IsFalse) +#define ExprIsFullSize(E) (((E)->flags&(EP_Reduced|EP_TokenOnly))==0) /* Macros used to ensure that the correct members of unions are accessed ** in Expr. @@ -3162,6 +3196,7 @@ struct ExprList { #define ENAME_NAME 0 /* The AS clause of a result set */ #define ENAME_SPAN 1 /* Complete text of the result set expression */ #define ENAME_TAB 2 /* "DB.TABLE.NAME" for the result set */ +#define ENAME_ROWID 3 /* "DB.TABLE._rowid_" for * expansion of rowid */ /* ** An instance of this structure can hold a simple list of identifiers, @@ -3770,6 +3805,7 @@ struct Parse { int *aLabel; /* Space to hold the labels */ ExprList *pConstExpr;/* Constant expressions */ IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */ + IndexedExpr *pIdxPartExpr; /* Exprs constrained by index WHERE clauses */ Token constraintName;/* Name of the constraint currently being parsed */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ @@ -4041,6 +4077,7 @@ struct Returning { int iRetCur; /* Transient table holding RETURNING results */ int nRetCol; /* Number of in pReturnEL after expansion */ int iRetReg; /* Register array for holding a row of RETURNING */ + char zName[40]; /* Name of trigger: "sqlite_returning_%p" */ }; /* @@ -4341,6 +4378,16 @@ struct CteUse { }; +/* Client data associated with sqlite3_set_clientdata() and +** sqlite3_get_clientdata(). +*/ +struct DbClientData { + DbClientData *pNext; /* Next in a linked list */ + void *pData; /* The data */ + void (*xDestructor)(void*); /* Destructor. Might be NULL */ + char zName[1]; /* Name of this client data. MUST BE LAST */ +}; + #ifdef SQLITE_DEBUG /* ** An instance of the TreeView object is used for printing the content of @@ -4745,6 +4792,8 @@ void sqlite3PExprAddSelect(Parse*, Expr*, Select*); Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*); Expr *sqlite3ExprSimplifiedAndOr(Expr*); Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int); +void sqlite3ExprAddFunctionOrderBy(Parse*,Expr*,ExprList*); +void sqlite3ExprOrderByAggregateError(Parse*,Expr*); void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); void sqlite3ExprDelete(sqlite3*, Expr*); @@ -4981,6 +5030,7 @@ int sqlite3ExprIsInteger(const Expr*, int*); int sqlite3ExprCanBeNull(const Expr*); int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); int sqlite3IsRowid(const char*); +const char *sqlite3RowidAlias(Table *pTab); void sqlite3GenerateRowDelete( Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int); void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int); @@ -5252,7 +5302,8 @@ int sqlite3MatchEName( const struct ExprList_item*, const char*, const char*, - const char* + const char*, + int* ); Bitmask sqlite3ExprColUsed(Expr*); u8 sqlite3StrIHash(const char*); @@ -5309,7 +5360,7 @@ int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); char *sqlite3RCStrRef(char*); -void sqlite3RCStrUnref(char*); +void sqlite3RCStrUnref(void*); char *sqlite3RCStrNew(u64); char *sqlite3RCStrResize(char*,u64); diff --git a/src/test1.c b/src/test1.c index 4dcf7bc11..55be0596b 100644 --- a/src/test1.c +++ b/src/test1.c @@ -4222,9 +4222,11 @@ static int SQLITE_TCLAPI test_bind_value_from_preupdate( sqlite3_stmt *pStmt; int idx; int bidx; +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK const char *z3 = 0; sqlite3 *db = 0; sqlite3_value *pVal = 0; +#endif if( objc!=5 ){ Tcl_WrongNumArgs(interp, 1, objv, "STMT N NEW|OLD IDX"); @@ -4233,11 +4235,11 @@ static int SQLITE_TCLAPI test_bind_value_from_preupdate( if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR; - z3 = Tcl_GetString(objv[3]); if( Tcl_GetIntFromObj(interp, objv[4], &bidx) ) return TCL_ERROR; - db = sqlite3_db_handle(pStmt); #ifdef SQLITE_ENABLE_PREUPDATE_HOOK + z3 = Tcl_GetString(objv[3]); + db = sqlite3_db_handle(pStmt); if( z3[0]=='n' ){ sqlite3_preupdate_new(db, bidx, &pVal); }else if( z3[0]=='o' ){ @@ -7579,6 +7581,41 @@ static int testLocaltime(const void *aliasT, void *aliasTM){ return t==959609760; /* Special case: 2000-05-29 14:16:00 fails */ } +/* +** TCLCMD: strftime FORMAT UNIXTIMESTAMP +** +** Access to the C-library strftime() routine, so that its results +** can be compared against SQLite's internal strftime() SQL function +** implementation. +*/ +static int SQLITE_TCLAPI strftime_cmd( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + Tcl_WideInt ts; + time_t t; + struct tm *pTm; + const char *zFmt; + size_t n; + char zBuf[1000]; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "FORMAT UNIXTIMESTAMP"); + return TCL_ERROR; + } + if( Tcl_GetWideIntFromObj(interp, objv[2], &ts) ) return TCL_ERROR; + zFmt = Tcl_GetString(objv[1]); + t = (time_t)ts; + pTm = gmtime(&t); + n = strftime(zBuf, sizeof(zBuf)-1, zFmt, pTm); + if( n>=0 && npFilter, 0); sqlite3TreeViewPop(&pView); + if( pWin->eFrmType==TK_FILTER ) return; } sqlite3TreeViewPush(&pView, more); if( pWin->zName ){ @@ -421,7 +422,7 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ } if( pWin->zBase ) nElement++; if( pWin->pOrderBy ) nElement++; - if( pWin->eFrmType ) nElement++; + if( pWin->eFrmType!=0 && pWin->eFrmType!=TK_FILTER ) nElement++; if( pWin->eExclude ) nElement++; if( pWin->zBase ){ sqlite3TreeViewPush(&pView, (--nElement)>0); @@ -434,7 +435,7 @@ void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ if( pWin->pOrderBy ){ sqlite3TreeViewExprList(pView, pWin->pOrderBy, (--nElement)>0, "ORDER-BY"); } - if( pWin->eFrmType ){ + if( pWin->eFrmType!=0 && pWin->eFrmType!=TK_FILTER ){ char zBuf[30]; const char *zFrmType = "ROWS"; if( pWin->eFrmType==TK_RANGE ) zFrmType = "RANGE"; @@ -682,7 +683,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ assert( ExprUseXList(pExpr) ); pFarg = pExpr->x.pList; #ifndef SQLITE_OMIT_WINDOWFUNC - pWin = ExprHasProperty(pExpr, EP_WinFunc) ? pExpr->y.pWin : 0; + pWin = IsWindowFunc(pExpr) ? pExpr->y.pWin : 0; #else pWin = 0; #endif @@ -708,7 +709,13 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewLine(pView, "FUNCTION %Q%s", pExpr->u.zToken, zFlgs); } if( pFarg ){ - sqlite3TreeViewExprList(pView, pFarg, pWin!=0, 0); + sqlite3TreeViewExprList(pView, pFarg, pWin!=0 || pExpr->pLeft, 0); + if( pExpr->pLeft ){ + Expr *pOB = pExpr->pLeft; + assert( pOB->op==TK_ORDER ); + assert( ExprUseXList(pOB) ); + sqlite3TreeViewExprList(pView, pOB->x.pList, pWin!=0, "ORDERBY"); + } } #ifndef SQLITE_OMIT_WINDOWFUNC if( pWin ){ @@ -717,6 +724,10 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ #endif break; } + case TK_ORDER: { + sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, "ORDERBY"); + break; + } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: { assert( ExprUseXSelect(pExpr) ); diff --git a/src/trigger.c b/src/trigger.c index bcb2132f0..97ca249be 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -183,6 +183,10 @@ void sqlite3BeginTrigger( sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables"); goto trigger_orphan_error; } + if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){ + sqlite3ErrorMsg(pParse, "cannot create triggers on shadow tables"); + goto trigger_orphan_error; + } /* Check that the trigger name is not reserved and that no trigger of the ** specified name exists */ @@ -966,10 +970,17 @@ static void codeReturningTrigger( SrcList sFrom; assert( v!=0 ); - assert( pParse->bReturning ); + if( !pParse->bReturning ){ + /* This RETURNING trigger must be for a different statement as + ** this statement lacks a RETURNING clause. */ + return; + } assert( db->pParse==pParse ); pReturning = pParse->u1.pReturning; - assert( pTrigger == &(pReturning->retTrig) ); + if( pTrigger != &(pReturning->retTrig) ){ + /* This RETURNING trigger is for a different statement */ + return; + } memset(&sSelect, 0, sizeof(sSelect)); memset(&sFrom, 0, sizeof(sFrom)); sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); diff --git a/src/util.c b/src/util.c index 590a06523..e9c7cccb0 100644 --- a/src/util.c +++ b/src/util.c @@ -202,12 +202,16 @@ void sqlite3ProgressCheck(Parse *p){ p->rc = SQLITE_INTERRUPT; } #ifndef SQLITE_OMIT_PROGRESS_CALLBACK - if( db->xProgress && (++p->nProgressSteps)>=db->nProgressOps ){ - if( db->xProgress(db->pProgressArg) ){ - p->nErr++; - p->rc = SQLITE_INTERRUPT; + if( db->xProgress ){ + if( p->rc==SQLITE_INTERRUPT ){ + p->nProgressSteps = 0; + }else if( (++p->nProgressSteps)>=db->nProgressOps ){ + if( db->xProgress(db->pProgressArg) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } + p->nProgressSteps = 0; } - p->nProgressSteps = 0; } #endif } @@ -1363,121 +1367,32 @@ u8 sqlite3GetVarint(const unsigned char *p, u64 *v){ ** this function assumes the single-byte case has already been handled. */ u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){ - u32 a,b; + u64 v64; + u8 n; - /* The 1-byte case. Overwhelmingly the most common. Handled inline - ** by the getVarin32() macro */ - a = *p; - /* a: p0 (unmasked) */ -#ifndef getVarint32 - if (!(a&0x80)) - { - /* Values between 0 and 127 */ - *v = a; - return 1; - } -#endif + /* Assume that the single-byte case has already been handled by + ** the getVarint32() macro */ + assert( (p[0] & 0x80)!=0 ); - /* The 2-byte case */ - p++; - b = *p; - /* b: p1 (unmasked) */ - if (!(b&0x80)) - { - /* Values between 128 and 16383 */ - a &= 0x7f; - a = a<<7; - *v = a | b; + if( (p[1] & 0x80)==0 ){ + /* This is the two-byte case */ + *v = ((p[0]&0x7f)<<7) | p[1]; return 2; } - - /* The 3-byte case */ - p++; - a = a<<14; - a |= *p; - /* a: p0<<14 | p2 (unmasked) */ - if (!(a&0x80)) - { - /* Values between 16384 and 2097151 */ - a &= (0x7f<<14)|(0x7f); - b &= 0x7f; - b = b<<7; - *v = a | b; + if( (p[2] & 0x80)==0 ){ + /* This is the three-byte case */ + *v = ((p[0]&0x7f)<<14) | ((p[1]&0x7f)<<7) | p[2]; return 3; } - - /* A 32-bit varint is used to store size information in btrees. - ** Objects are rarely larger than 2MiB limit of a 3-byte varint. - ** A 3-byte varint is sufficient, for example, to record the size - ** of a 1048569-byte BLOB or string. - ** - ** We only unroll the first 1-, 2-, and 3- byte cases. The very - ** rare larger cases can be handled by the slower 64-bit varint - ** routine. - */ -#if 1 - { - u64 v64; - u8 n; - - n = sqlite3GetVarint(p-2, &v64); - assert( n>3 && n<=9 ); - if( (v64 & SQLITE_MAX_U32)!=v64 ){ - *v = 0xffffffff; - }else{ - *v = (u32)v64; - } - return n; - } - -#else - /* For following code (kept for historical record only) shows an - ** unrolling for the 3- and 4-byte varint cases. This code is - ** slightly faster, but it is also larger and much harder to test. - */ - p++; - b = b<<14; - b |= *p; - /* b: p1<<14 | p3 (unmasked) */ - if (!(b&0x80)) - { - /* Values between 2097152 and 268435455 */ - b &= (0x7f<<14)|(0x7f); - a &= (0x7f<<14)|(0x7f); - a = a<<7; - *v = a | b; - return 4; - } - - p++; - a = a<<14; - a |= *p; - /* a: p0<<28 | p2<<14 | p4 (unmasked) */ - if (!(a&0x80)) - { - /* Values between 268435456 and 34359738367 */ - a &= SLOT_4_2_0; - b &= SLOT_4_2_0; - b = b<<7; - *v = a | b; - return 5; - } - - /* We can only reach this point when reading a corrupt database - ** file. In that case we are not in any hurry. Use the (relatively - ** slow) general-purpose sqlite3GetVarint() routine to extract the - ** value. */ - { - u64 v64; - u8 n; - - p -= 4; - n = sqlite3GetVarint(p, &v64); - assert( n>5 && n<=9 ); + /* four or more bytes */ + n = sqlite3GetVarint(p, &v64); + assert( n>3 && n<=9 ); + if( (v64 & SQLITE_MAX_U32)!=v64 ){ + *v = 0xffffffff; + }else{ *v = (u32)v64; - return n; } -#endif + return n; } /* diff --git a/src/vdbe.c b/src/vdbe.c index 5899fa518..221e8847d 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -762,11 +762,11 @@ static SQLITE_NOINLINE int vdbeColumnFromOverflow( sqlite3RCStrRef(pBuf); if( t&1 ){ rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, encoding, - (void(*)(void*))sqlite3RCStrUnref); + sqlite3RCStrUnref); pDest->flags |= MEM_Term; }else{ rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, 0, - (void(*)(void*))sqlite3RCStrUnref); + sqlite3RCStrUnref); } }else{ rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, iOffset, len, pDest); @@ -3641,7 +3641,6 @@ case OP_MakeRecord: { /* NULL value. No change in zPayload */ }else{ u64 v; - u32 i; if( serial_type==7 ){ assert( sizeof(v)==sizeof(pRec->u.r) ); memcpy(&v, &pRec->u.r, sizeof(v)); @@ -3649,12 +3648,17 @@ case OP_MakeRecord: { }else{ v = pRec->u.i; } - len = i = sqlite3SmallTypeSizes[serial_type]; - assert( i>0 ); - while( 1 /*exit-by-break*/ ){ - zPayload[--i] = (u8)(v&0xFF); - if( i==0 ) break; - v >>= 8; + len = sqlite3SmallTypeSizes[serial_type]; + assert( len>=1 && len<=8 && len!=5 && len!=7 ); + switch( len ){ + default: zPayload[7] = (u8)(v&0xff); v >>= 8; + zPayload[6] = (u8)(v&0xff); v >>= 8; + case 6: zPayload[5] = (u8)(v&0xff); v >>= 8; + zPayload[4] = (u8)(v&0xff); v >>= 8; + case 4: zPayload[3] = (u8)(v&0xff); v >>= 8; + case 3: zPayload[2] = (u8)(v&0xff); v >>= 8; + case 2: zPayload[1] = (u8)(v&0xff); v >>= 8; + case 1: zPayload[0] = (u8)(v&0xff); } zPayload += len; } @@ -5771,8 +5775,13 @@ case OP_RowCell: { ** the "primary" delete. The others are all on OPFLAG_FORDELETE ** cursors or else are marked with the AUXDELETE flag. ** -** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row -** change count is incremented (otherwise not). +** If the OPFLAG_NCHANGE (0x01) flag of P2 (NB: P2 not P5) is set, then +** the row change count is incremented (otherwise not). +** +** If the OPFLAG_ISNOOP (0x40) flag of P2 (not P5!) is set, then the +** pre-update-hook for deletes is run, but the btree is otherwise unchanged. +** This happens when the OP_Delete is to be shortly followed by an OP_Insert +** with the same key, causing the btree entry to be overwritten. ** ** P1 must not be pseudo-table. It has to be a real table with ** multiple rows. @@ -6897,13 +6906,41 @@ case OP_CreateBtree: { /* out2 */ /* Opcode: SqlExec * * * P4 * ** ** Run the SQL statement or statements specified in the P4 string. +** Disable Auth and Trace callbacks while those statements are running if +** P1 is true. */ case OP_SqlExec: { + char *zErr; +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth; +#endif + u8 mTrace; + sqlite3VdbeIncrWriteCounter(p, 0); db->nSqlExec++; - rc = sqlite3_exec(db, pOp->p4.z, 0, 0, 0); + zErr = 0; +#ifndef SQLITE_OMIT_AUTHORIZATION + xAuth = db->xAuth; +#endif + mTrace = db->mTrace; + if( pOp->p1 ){ +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = 0; +#endif + db->mTrace = 0; + } + rc = sqlite3_exec(db, pOp->p4.z, 0, 0, &zErr); db->nSqlExec--; - if( rc ) goto abort_due_to_error; +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + db->mTrace = mTrace; + if( zErr || rc ){ + sqlite3VdbeError(p, "%s", zErr); + sqlite3_free(zErr); + if( rc==SQLITE_NOMEM ) goto no_mem; + goto abort_due_to_error; + } break; } @@ -8124,6 +8161,53 @@ case OP_VOpen: { /* ncycle */ } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VCheck P1 P2 P3 P4 * +** +** P4 is a pointer to a Table object that is a virtual table in schema P1 +** that supports the xIntegrity() method. This opcode runs the xIntegrity() +** method for that virtual table, using P3 as the integer argument. If +** an error is reported back, the table name is prepended to the error +** message and that message is stored in P2. If no errors are seen, +** register P2 is set to NULL. +*/ +case OP_VCheck: { /* out2 */ + Table *pTab; + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + char *zErr = 0; + + pOut = &aMem[pOp->p2]; + sqlite3VdbeMemSetNull(pOut); /* Innocent until proven guilty */ + assert( pOp->p4type==P4_TABLE ); + pTab = pOp->p4.pTab; + assert( pTab!=0 ); + assert( IsVirtual(pTab) ); + assert( pTab->u.vtab.p!=0 ); + pVtab = pTab->u.vtab.p->pVtab; + assert( pVtab!=0 ); + pModule = pVtab->pModule; + assert( pModule!=0 ); + assert( pModule->iVersion>=4 ); + assert( pModule->xIntegrity!=0 ); + pTab->nTabRef++; + sqlite3VtabLock(pTab->u.vtab.p); + assert( pOp->p1>=0 && pOp->p1nDb ); + rc = pModule->xIntegrity(pVtab, db->aDb[pOp->p1].zDbSName, pTab->zName, + pOp->p3, &zErr); + sqlite3VtabUnlock(pTab->u.vtab.p); + sqlite3DeleteTable(db, pTab); + if( rc ){ + sqlite3_free(zErr); + goto abort_due_to_error; + } + if( zErr ){ + sqlite3VdbeMemSetStr(pOut, zErr, -1, SQLITE_UTF8, sqlite3_free); + } + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VInitIn P1 P2 P3 * * ** Synopsis: r[P2]=ValueList(P1,P3) diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 79b5de9f0..31ebbc697 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -375,7 +375,7 @@ void sqlite3_value_free(sqlite3_value *pOld){ ** is too big or if an OOM occurs. ** ** The invokeValueDestructor(P,X) routine invokes destructor function X() -** on value P is not going to be used and need to be destroyed. +** on value P if P is not going to be used and need to be destroyed. */ static void setResultStrOrError( sqlite3_context *pCtx, /* Function context */ @@ -405,7 +405,7 @@ static void setResultStrOrError( static int invokeValueDestructor( const void *p, /* Value to destroy */ void (*xDel)(void*), /* The destructor */ - sqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if no NULL */ + sqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if not NULL */ ){ assert( xDel!=SQLITE_DYNAMIC ); if( xDel==0 ){ @@ -415,7 +415,14 @@ static int invokeValueDestructor( }else{ xDel((void*)p); } +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx!=0 ){ + sqlite3_result_error_toobig(pCtx); + } +#else + assert( pCtx!=0 ); sqlite3_result_error_toobig(pCtx); +#endif return SQLITE_TOOBIG; } void sqlite3_result_blob( @@ -424,6 +431,12 @@ void sqlite3_result_blob( int n, void (*xDel)(void *) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 || n<0 ){ + invokeValueDestructor(z, xDel, pCtx); + return; + } +#endif assert( n>=0 ); assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n, 0, xDel); @@ -434,8 +447,14 @@ void sqlite3_result_blob64( sqlite3_uint64 n, void (*xDel)(void *) ){ - assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(z, xDel, 0); + return; + } +#endif + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); if( n>0x7fffffff ){ (void)invokeValueDestructor(z, xDel, pCtx); }else{ @@ -443,30 +462,48 @@ void sqlite3_result_blob64( } } void sqlite3_result_double(sqlite3_context *pCtx, double rVal){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetDouble(pCtx->pOut, rVal); } void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_ERROR; sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT); } #ifndef SQLITE_OMIT_UTF16 void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_ERROR; sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT); } #endif void sqlite3_result_int(sqlite3_context *pCtx, int iVal){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetInt64(pCtx->pOut, (i64)iVal); } void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetInt64(pCtx->pOut, iVal); } void sqlite3_result_null(sqlite3_context *pCtx){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); } @@ -476,14 +513,25 @@ void sqlite3_result_pointer( const char *zPType, void (*xDestructor)(void*) ){ - Mem *pOut = pCtx->pOut; + Mem *pOut; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(pPtr, xDestructor, 0); + return; + } +#endif + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); sqlite3VdbeMemRelease(pOut); pOut->flags = MEM_Null; sqlite3VdbeMemSetPointer(pOut, pPtr, zPType, xDestructor); } void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ - Mem *pOut = pCtx->pOut; + Mem *pOut; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); pOut->eSubtype = eSubtype & 0xff; pOut->flags |= MEM_Subtype; @@ -494,6 +542,12 @@ void sqlite3_result_text( int n, void (*xDel)(void *) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(z, xDel, 0); + return; + } +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel); } @@ -504,6 +558,12 @@ void sqlite3_result_text64( void (*xDel)(void *), unsigned char enc ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(z, xDel, 0); + return; + } +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); if( enc!=SQLITE_UTF8 ){ @@ -547,7 +607,16 @@ void sqlite3_result_text16le( } #endif /* SQLITE_OMIT_UTF16 */ void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ - Mem *pOut = pCtx->pOut; + Mem *pOut; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; + if( pValue==0 ){ + sqlite3_result_null(pCtx); + return; + } +#endif + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemCopy(pOut, pValue); sqlite3VdbeChangeEncoding(pOut, pCtx->enc); @@ -559,7 +628,12 @@ void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){ sqlite3_result_zeroblob64(pCtx, n>0 ? n : 0); } int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ - Mem *pOut = pCtx->pOut; + Mem *pOut; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return SQLITE_MISUSE_BKPT; +#endif + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){ sqlite3_result_error_toobig(pCtx); @@ -573,6 +647,9 @@ int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ #endif } void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif pCtx->isError = errCode ? errCode : -1; #ifdef SQLITE_DEBUG if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode; @@ -585,6 +662,9 @@ void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ /* Force an SQLITE_TOOBIG error. */ void sqlite3_result_error_toobig(sqlite3_context *pCtx){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_TOOBIG; sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, @@ -593,6 +673,9 @@ void sqlite3_result_error_toobig(sqlite3_context *pCtx){ /* An SQLITE_NOMEM error. */ void sqlite3_result_error_nomem(sqlite3_context *pCtx){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); pCtx->isError = SQLITE_NOMEM_BKPT; @@ -845,7 +928,11 @@ int sqlite3_step(sqlite3_stmt *pStmt){ ** pointer to it. */ void *sqlite3_user_data(sqlite3_context *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return 0; +#else assert( p && p->pFunc ); +#endif return p->pFunc->pUserData; } @@ -860,7 +947,11 @@ void *sqlite3_user_data(sqlite3_context *p){ ** application defined function. */ sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return 0; +#else assert( p && p->pOut ); +#endif return p->pOut->db; } @@ -879,7 +970,11 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ ** value, as a signal to the xUpdate routine that the column is unchanged. */ int sqlite3_vtab_nochange(sqlite3_context *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return 0; +#else assert( p ); +#endif return sqlite3_value_nochange(p->pOut); } @@ -907,7 +1002,7 @@ static int valueFromValueList( ValueList *pRhs; *ppOut = 0; - if( pVal==0 ) return SQLITE_MISUSE; + if( pVal==0 ) return SQLITE_MISUSE_BKPT; if( (pVal->flags & MEM_Dyn)==0 || pVal->xDel!=sqlite3VdbeValueListFree ){ return SQLITE_ERROR; }else{ @@ -1038,6 +1133,9 @@ void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){ void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ AuxData *pAuxData; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return 0; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); #if SQLITE_ENABLE_STAT4 if( pCtx->pVdbe==0 ) return 0; @@ -1070,8 +1168,12 @@ void sqlite3_set_auxdata( void (*xDelete)(void*) ){ AuxData *pAuxData; - Vdbe *pVdbe = pCtx->pVdbe; + Vdbe *pVdbe; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif + pVdbe= pCtx->pVdbe; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); #ifdef SQLITE_ENABLE_STAT4 if( pVdbe==0 ) goto failed; @@ -1508,7 +1610,7 @@ static int vdbeUnbind(Vdbe *p, unsigned int i){ } sqlite3_mutex_enter(p->db->mutex); if( p->eVdbeState!=VDBE_READY_STATE ){ - sqlite3Error(p->db, SQLITE_MISUSE); + sqlite3Error(p->db, SQLITE_MISUSE_BKPT); sqlite3_mutex_leave(p->db->mutex); sqlite3_log(SQLITE_MISUSE, "bind on a busy prepared statement: [%s]", p->zSql); @@ -1737,6 +1839,9 @@ int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint64 n){ int rc; Vdbe *p = (Vdbe *)pStmt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(p->db->mutex); if( n>(u64)p->db->aLimit[SQLITE_LIMIT_LENGTH] ){ rc = SQLITE_TOOBIG; @@ -1863,6 +1968,9 @@ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ Vdbe *v = (Vdbe*)pStmt; int rc; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(v->db->mutex); if( ((int)v->explain)==eMode ){ rc = SQLITE_OK; @@ -2029,10 +2137,16 @@ static UnpackedRecord *vdbeUnpackRecord( ** a field of the row currently being updated or deleted. */ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; Mem *pMem; int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_API_ARMOR + if( db==0 || ppValue==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + p = db->pPreUpdate; /* Test that this call is being made from within an SQLITE_DELETE or ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */ if( !p || p->op==SQLITE_INSERT ){ @@ -2093,7 +2207,12 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ ** the number of columns in the row being updated, deleted or inserted. */ int sqlite3_preupdate_count(sqlite3 *db){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; +#ifdef SQLITE_ENABLE_API_ARMOR + p = db!=0 ? db->pPreUpdate : 0; +#else + p = db->pPreUpdate; +#endif return (p ? p->keyinfo.nKeyField : 0); } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -2111,7 +2230,12 @@ int sqlite3_preupdate_count(sqlite3 *db){ ** or SET DEFAULT action is considered a trigger. */ int sqlite3_preupdate_depth(sqlite3 *db){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; +#ifdef SQLITE_ENABLE_API_ARMOR + p = db!=0 ? db->pPreUpdate : 0; +#else + p = db->pPreUpdate; +#endif return (p ? p->v->nFrame : 0); } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -2122,7 +2246,12 @@ int sqlite3_preupdate_depth(sqlite3 *db){ ** only. */ int sqlite3_preupdate_blobwrite(sqlite3 *db){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; +#ifdef SQLITE_ENABLE_API_ARMOR + p = db!=0 ? db->pPreUpdate : 0; +#else + p = db->pPreUpdate; +#endif return (p ? p->iBlobWrite : -1); } #endif @@ -2133,10 +2262,16 @@ int sqlite3_preupdate_blobwrite(sqlite3 *db){ ** a field of the row currently being updated or inserted. */ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; int rc = SQLITE_OK; Mem *pMem; +#ifdef SQLITE_ENABLE_API_ARMOR + if( db==0 || ppValue==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + p = db->pPreUpdate; if( !p || p->op==SQLITE_DELETE ){ rc = SQLITE_MISUSE_BKPT; goto preupdate_new_out; @@ -2215,11 +2350,20 @@ int sqlite3_stmt_scanstatus_v2( void *pOut /* OUT: Write the answer here */ ){ Vdbe *p = (Vdbe*)pStmt; - VdbeOp *aOp = p->aOp; - int nOp = p->nOp; + VdbeOp *aOp; + int nOp; ScanStatus *pScan = 0; int idx; +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 || pOut==0 + || iScanStatusOpSQLITE_SCANSTAT_NCYCLE ){ + return 1; + } +#endif + aOp = p->aOp; + nOp = p->nOp; if( p->pFrame ){ VdbeFrame *pFrame; for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); @@ -2366,7 +2510,7 @@ int sqlite3_stmt_scanstatus( void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; int ii; - for(ii=0; iinOp; ii++){ + for(ii=0; p!=0 && iinOp; ii++){ Op *pOp = &p->aOp[ii]; pOp->nExec = 0; pOp->nCycle = 0; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 225c8d12c..54317b816 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -1002,6 +1002,10 @@ void sqlite3VdbeNoJumpsOutsideSubrtn( int iDest = pOp->p2; /* Jump destination */ if( iDest==0 ) continue; if( pOp->opcode==OP_Gosub ) continue; + if( pOp->p3==20230325 && pOp->opcode==OP_NotNull ){ + /* This is a deliberately taken illegal branch. tag-20230325-2 */ + continue; + } if( iDest<0 ){ int j = ADDR(iDest); assert( j>=0 ); @@ -4461,20 +4465,33 @@ SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){ return n1 - n2; } +/* The following two functions are used only within testcase() to prove +** test coverage. These functions do no exist for production builds. +** We must use separate SQLITE_NOINLINE functions here, since otherwise +** optimizer code movement causes gcov to become very confused. +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +static int SQLITE_NOINLINE doubleLt(double a, double b){ return a8 ){ + if( sqlite3IsNaN(r) ){ + /* SQLite considers NaN to be a NULL. And all integer values are greater + ** than NULL */ + return 1; + } + if( sqlite3Config.bUseLongDouble ){ LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i; testcase( xr ); testcase( x==r ); - if( xr ) return +1; /*NO_TEST*/ /* work around bugs in gcov */ - return 0; /*NO_TEST*/ /* work around bugs in gcov */ + return (xr); }else{ i64 y; double s; @@ -4484,9 +4501,10 @@ int sqlite3IntFloatCompare(i64 i, double r){ if( iy ) return +1; s = (double)i; - if( sr ) return +1; - return 0; + testcase( doubleLt(s,r) ); + testcase( doubleLt(r,s) ); + testcase( doubleEq(r,s) ); + return (sr); } } diff --git a/src/vdbeblob.c b/src/vdbeblob.c index ea6f016e3..522447dbc 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -142,7 +142,7 @@ int sqlite3_blob_open( #endif *ppBlob = 0; #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) || zTable==0 ){ + if( !sqlite3SafetyCheckOk(db) || zTable==0 || zColumn==0 ){ return SQLITE_MISUSE_BKPT; } #endif diff --git a/src/vdbemem.c b/src/vdbemem.c index e25efc977..eee828143 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -336,7 +336,7 @@ void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ pMem->flags |= MEM_Term; return; } - if( pMem->xDel==(void(*)(void*))sqlite3RCStrUnref ){ + if( pMem->xDel==sqlite3RCStrUnref ){ /* Blindly assume that all RCStr objects are zero-terminated */ pMem->flags |= MEM_Term; return; @@ -1716,6 +1716,7 @@ static int valueFromExpr( if( pVal ){ pVal->flags = MEM_Int; pVal->u.i = pExpr->u.zToken[4]==0; + sqlite3ValueApplyAffinity(pVal, affinity, enc); } } diff --git a/src/vdbesort.c b/src/vdbesort.c index 2b7da94f7..008369030 100644 --- a/src/vdbesort.c +++ b/src/vdbesort.c @@ -186,7 +186,7 @@ struct SorterFile { struct SorterList { SorterRecord *pList; /* Linked list of records */ u8 *aMemory; /* If non-NULL, bulk memory to hold pList */ - int szPMA; /* Size of pList as PMA in bytes */ + i64 szPMA; /* Size of pList as PMA in bytes */ }; /* @@ -295,10 +295,10 @@ typedef int (*SorterCompare)(SortSubtask*,int*,const void*,int,const void*,int); struct SortSubtask { SQLiteThread *pThread; /* Background thread, if any */ int bDone; /* Set if thread is finished but not joined */ + int nPMA; /* Number of PMAs currently in file */ VdbeSorter *pSorter; /* Sorter that owns this sub-task */ UnpackedRecord *pUnpacked; /* Space to unpack a record */ SorterList list; /* List for thread to write to a PMA */ - int nPMA; /* Number of PMAs currently in file */ SorterCompare xCompare; /* Compare function to use */ SorterFile file; /* Temp file for level-0 PMAs */ SorterFile file2; /* Space for other PMAs */ @@ -1772,8 +1772,8 @@ int sqlite3VdbeSorterWrite( int rc = SQLITE_OK; /* Return Code */ SorterRecord *pNew; /* New list element */ int bFlush; /* True to flush contents of memory to PMA */ - int nReq; /* Bytes of memory required */ - int nPMA; /* Bytes of PMA space required */ + i64 nReq; /* Bytes of memory required */ + i64 nPMA; /* Bytes of PMA space required */ int t; /* serial type of first record field */ assert( pCsr->eCurType==CURTYPE_SORTER ); diff --git a/src/vdbevtab.c b/src/vdbevtab.c index 59030e0e1..b295dff7b 100644 --- a/src/vdbevtab.c +++ b/src/vdbevtab.c @@ -428,7 +428,8 @@ static sqlite3_module bytecodevtabModule = { /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, - /* xShadowName */ 0 + /* xShadowName */ 0, + /* xIntegrity */ 0 }; diff --git a/src/vtab.c b/src/vtab.c index 741518991..a9cfcb9d7 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -821,7 +821,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ sqlite3_mutex_enter(db->mutex); pCtx = db->pVtabCtx; if( !pCtx || pCtx->bDeclared ){ - sqlite3Error(db, SQLITE_MISUSE); + sqlite3Error(db, SQLITE_MISUSE_BKPT); sqlite3_mutex_leave(db->mutex); return SQLITE_MISUSE_BKPT; } diff --git a/src/where.c b/src/where.c index 213df4223..05ae24f7b 100644 --- a/src/where.c +++ b/src/where.c @@ -1142,13 +1142,17 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( WhereLoop *pLoop = pLevel->pWLoop; /* The loop being coded */ int iCur; /* Cursor for table getting the filter */ IndexedExpr *saved_pIdxEpr; /* saved copy of Parse.pIdxEpr */ + IndexedExpr *saved_pIdxPartExpr; /* saved copy of Parse.pIdxPartExpr */ saved_pIdxEpr = pParse->pIdxEpr; + saved_pIdxPartExpr = pParse->pIdxPartExpr; pParse->pIdxEpr = 0; + pParse->pIdxPartExpr = 0; assert( pLoop!=0 ); assert( v!=0 ); assert( pLoop->wsFlags & WHERE_BLOOMFILTER ); + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 ); addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); do{ @@ -1238,6 +1242,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( }while( iLevel < pWInfo->nLevel ); sqlite3VdbeJumpHere(v, addrOnce); pParse->pIdxEpr = saved_pIdxEpr; + pParse->pIdxPartExpr = saved_pIdxPartExpr; } @@ -3497,6 +3502,100 @@ static SQLITE_NOINLINE u32 whereIsCoveringIndex( return rc; } +/* +** This is an sqlite3ParserAddCleanup() callback that is invoked to +** free the Parse->pIdxEpr list when the Parse object is destroyed. +*/ +static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ + IndexedExpr **pp = (IndexedExpr**)pObject; + while( *pp!=0 ){ + IndexedExpr *p = *pp; + *pp = p->pIENext; + sqlite3ExprDelete(db, p->pExpr); + sqlite3DbFreeNN(db, p); + } +} + +/* +** This function is called for a partial index - one with a WHERE clause - in +** two scenarios. In both cases, it determines whether or not the WHERE +** clause on the index implies that a column of the table may be safely +** replaced by a constant expression. For example, in the following +** SELECT: +** +** CREATE INDEX i1 ON t1(b, c) WHERE a=; +** SELECT a, b, c FROM t1 WHERE a= AND b=?; +** +** The "a" in the select-list may be replaced by , iff: +** +** (a) is a constant expression, and +** (b) The (a=) comparison uses the BINARY collation sequence, and +** (c) Column "a" has an affinity other than NONE or BLOB. +** +** If argument pItem is NULL, then pMask must not be NULL. In this case this +** function is being called as part of determining whether or not pIdx +** is a covering index. This function clears any bits in (*pMask) +** corresponding to columns that may be replaced by constants as described +** above. +** +** Otherwise, if pItem is not NULL, then this function is being called +** as part of coding a loop that uses index pIdx. In this case, add entries +** to the Parse.pIdxPartExpr list for each column that can be replaced +** by a constant. +*/ +static void wherePartIdxExpr( + Parse *pParse, /* Parse context */ + Index *pIdx, /* Partial index being processed */ + Expr *pPart, /* WHERE clause being processed */ + Bitmask *pMask, /* Mask to clear bits in */ + int iIdxCur, /* Cursor number for index */ + SrcItem *pItem /* The FROM clause entry for the table */ +){ + assert( pItem==0 || (pItem->fg.jointype & JT_RIGHT)==0 ); + assert( (pItem==0 || pMask==0) && (pMask!=0 || pItem!=0) ); + + if( pPart->op==TK_AND ){ + wherePartIdxExpr(pParse, pIdx, pPart->pRight, pMask, iIdxCur, pItem); + pPart = pPart->pLeft; + } + + if( (pPart->op==TK_EQ || pPart->op==TK_IS) ){ + Expr *pLeft = pPart->pLeft; + Expr *pRight = pPart->pRight; + u8 aff; + + if( pLeft->op!=TK_COLUMN ) return; + if( !sqlite3ExprIsConstant(pRight) ) return; + if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pParse, pPart)) ) return; + if( pLeft->iColumn<0 ) return; + aff = pIdx->pTable->aCol[pLeft->iColumn].affinity; + if( aff>=SQLITE_AFF_TEXT ){ + if( pItem ){ + sqlite3 *db = pParse->db; + IndexedExpr *p = (IndexedExpr*)sqlite3DbMallocRaw(db, sizeof(*p)); + if( p ){ + int bNullRow = (pItem->fg.jointype&(JT_LEFT|JT_LTORJ))!=0; + p->pExpr = sqlite3ExprDup(db, pRight, 0); + p->iDataCur = pItem->iCursor; + p->iIdxCur = iIdxCur; + p->iIdxCol = pLeft->iColumn; + p->bMaybeNullRow = bNullRow; + p->pIENext = pParse->pIdxPartExpr; + p->aff = aff; + pParse->pIdxPartExpr = p; + if( p->pIENext==0 ){ + void *pArg = (void*)&pParse->pIdxPartExpr; + sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg); + } + } + }else if( pLeft->iColumn<(BMS-1) ){ + *pMask &= ~((Bitmask)1 << pLeft->iColumn); + } + } + } +} + + /* ** Add all WhereLoop objects for a single table of the join where the table ** is identified by pBuilder->pNew->iTab. That table is guaranteed to be @@ -3700,9 +3799,6 @@ static int whereLoopAddBtree( #else pNew->rRun = rSize + 16; #endif - if( IsView(pTab) || (pTab->tabFlags & TF_Ephemeral)!=0 ){ - pNew->wsFlags |= WHERE_VIEWSCAN; - } ApplyCostMultiplier(pNew->rRun, pTab->costMult); whereLoopOutputAdjust(pWC, pNew, rSize); rc = whereLoopInsert(pBuilder, pNew); @@ -3715,6 +3811,11 @@ static int whereLoopAddBtree( pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; }else{ m = pSrc->colUsed & pProbe->colNotIdxed; + if( pProbe->pPartIdxWhere ){ + wherePartIdxExpr( + pWInfo->pParse, pProbe, pProbe->pPartIdxWhere, &m, 0, 0 + ); + } pNew->wsFlags = WHERE_INDEXED; if( m==TOPBIT || (pProbe->bHasExpr && !pProbe->bHasVCol && m!=0) ){ u32 isCov = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor); @@ -4097,7 +4198,7 @@ int sqlite3_vtab_rhs_value( sqlite3_value *pVal = 0; int rc = SQLITE_OK; if( iCons<0 || iCons>=pIdxInfo->nConstraint ){ - rc = SQLITE_MISUSE; /* EV: R-30545-25046 */ + rc = SQLITE_MISUSE_BKPT; /* EV: R-30545-25046 */ }else{ if( pH->aRhs[iCons]==0 ){ WhereTerm *pTerm = &pH->pWC->a[pIdxInfo->aConstraint[iCons].iTermOffset]; @@ -5121,14 +5222,6 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */ } - /* TUNING: A full-scan of a VIEW or subquery in the outer loop - ** is not so bad. */ - if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 && nLoop>1 ){ - rCost += -10; - nOut += -30; - WHERETRACE(0x80,("VIEWSCAN cost reduction for %c\n",pWLoop->cId)); - } - /* Check to see if pWLoop should be added to the set of ** mxChoice best-so-far paths. ** @@ -5678,20 +5771,6 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( } } -/* -** This is an sqlite3ParserAddCleanup() callback that is invoked to -** free the Parse->pIdxEpr list when the Parse object is destroyed. -*/ -static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ - Parse *pParse = (Parse*)pObject; - while( pParse->pIdxEpr!=0 ){ - IndexedExpr *p = pParse->pIdxEpr; - pParse->pIdxEpr = p->pIENext; - sqlite3ExprDelete(db, p->pExpr); - sqlite3DbFreeNN(db, p); - } -} - /* ** The index pIdx is used by a query and contains one or more expressions. ** In other words pIdx is an index on an expression. iIdxCur is the cursor @@ -5753,7 +5832,8 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( #endif pParse->pIdxEpr = p; if( p->pIENext==0 ){ - sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse); + void *pArg = (void*)&pParse->pIdxEpr; + sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg); } } } @@ -6143,6 +6223,16 @@ WhereInfo *sqlite3WhereBegin( wherePathSolver(pWInfo, pWInfo->nRowOut+1); if( db->mallocFailed ) goto whereBeginError; } + + /* TUNING: Assume that a DISTINCT clause on a subquery reduces + ** the output size by a factor of 8 (LogEst -30). + */ + if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){ + WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n", + pWInfo->nRowOut, pWInfo->nRowOut-30)); + pWInfo->nRowOut -= 30; + } + } assert( pWInfo->pTabList!=0 ); if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ @@ -6355,6 +6445,11 @@ WhereInfo *sqlite3WhereBegin( if( pIx->bHasExpr && OptimizationEnabled(db, SQLITE_IndexedExpr) ){ whereAddIndexedExpr(pParse, pIx, iIndexCur, pTabItem); } + if( pIx->pPartIdxWhere && (pTabItem->fg.jointype & JT_RIGHT)==0 ){ + wherePartIdxExpr( + pParse, pIx, pIx->pPartIdxWhere, 0, iIndexCur, pTabItem + ); + } } pLevel->iIdxCur = iIndexCur; assert( pIx!=0 ); diff --git a/src/whereInt.h b/src/whereInt.h index 759e774e3..343aca9f3 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -633,7 +633,7 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); #define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ #define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ #define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ -#define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */ + /* 0x02000000 -- available for reuse */ #define WHERE_EXPRIDX 0x04000000 /* Uses an index-on-expressions */ #endif /* !defined(SQLITE_WHEREINT_H) */ diff --git a/src/window.c b/src/window.c index d46eabc3b..2c449592d 100644 --- a/src/window.c +++ b/src/window.c @@ -1312,8 +1312,9 @@ void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){ if( p ){ assert( p->op==TK_FUNCTION ); assert( pWin ); + assert( ExprIsFullSize(p) ); p->y.pWin = pWin; - ExprSetProperty(p, EP_WinFunc); + ExprSetProperty(p, EP_WinFunc|EP_FullSize); pWin->pOwner = p; if( (p->flags & EP_Distinct) && pWin->eFrmType!=TK_FILTER ){ sqlite3ErrorMsg(pParse, diff --git a/test/aggnested.test b/test/aggnested.test index 1b8b60880..5f033a246 100644 --- a/test/aggnested.test +++ b/test/aggnested.test @@ -25,19 +25,19 @@ do_test aggnested-1.1 { INSERT INTO t1 VALUES(1), (2), (3); CREATE TABLE t2(b1 INTEGER); INSERT INTO t2 VALUES(4), (5); - SELECT (SELECT group_concat(a1,'x') FROM t2) FROM t1; + SELECT (SELECT string_agg(a1,'x') FROM t2) FROM t1; } } {1x2x3} do_test aggnested-1.2 { db eval { SELECT - (SELECT group_concat(a1,'x') || '-' || group_concat(b1,'y') FROM t2) + (SELECT string_agg(a1,'x') || '-' || string_agg(b1,'y') FROM t2) FROM t1; } } {1x2x3-4y5} do_test aggnested-1.3 { db eval { - SELECT (SELECT group_concat(b1,a1) FROM t2) FROM t1; + SELECT (SELECT string_agg(b1,a1) FROM t2) FROM t1; } } {415 425 435} do_test aggnested-1.4 { @@ -309,7 +309,7 @@ do_execsql_test 5.4 { do_execsql_test 5.5 { CREATE TABLE a(b); WITH c AS(SELECT a) - SELECT(SELECT(SELECT group_concat(b, b) + SELECT(SELECT(SELECT string_agg(b, b) LIMIT(SELECT 0.100000 * AVG(DISTINCT(SELECT 0 FROM a ORDER BY b, b, b)))) FROM a GROUP BY b, diff --git a/test/aggorderby.test b/test/aggorderby.test new file mode 100644 index 000000000..f94c5ec89 --- /dev/null +++ b/test/aggorderby.test @@ -0,0 +1,122 @@ +# 2023-10-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 tests for ORDER BY on aggregate functions. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test aggorderby-1.1 { + CREATE TABLE t1(a TEXT,b INT,c INT,d INT); + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<9) + INSERT INTO t1(a,b,c,d) SELECT printf('%d',(x*7)%10),1,x,10-x FROM c; + INSERT INTO t1(a,b,c,d) SELECT a, 2, c, 10-d FROM t1; + CREATE INDEX t1b ON t1(b); +} +do_catchsql_test aggorderby-1.2 { + SELECT b, group_concat(a ORDER BY max(d)) FROM t1 GROUP BY b; +} {1 {misuse of aggregate function max()}} +do_catchsql_test aggorderby-1.3 { + SELECT abs(a ORDER BY max(d)) FROM t1; +} {1 {ORDER BY may not be used with non-aggregate abs()}} + +do_execsql_test aggorderby-2.0 { + SELECT group_concat(a ORDER BY a) FROM t1 WHERE b=1; +} {0,1,2,3,4,5,6,7,8,9} +do_execsql_test aggorderby-2.1 { + SELECT group_concat(a ORDER BY c) FROM t1 WHERE b=1; +} {0,7,4,1,8,5,2,9,6,3} +do_execsql_test aggorderby-2.2 { + SELECT group_concat(a ORDER BY b, d) FROM t1; +} {3,6,9,2,5,8,1,4,7,0,0,7,4,1,8,5,2,9,6,3} +do_execsql_test aggorderby-2.3 { + SELECT string_agg(a, ',' ORDER BY b DESC, d) FROM t1; +} {0,7,4,1,8,5,2,9,6,3,3,6,9,2,5,8,1,4,7,0} +do_execsql_test aggorderby-2.4 { + SELECT b, group_concat(a ORDER BY d) FROM t1 GROUP BY b ORDER BY b; +} {1 3,6,9,2,5,8,1,4,7,0 2 0,7,4,1,8,5,2,9,6,3} + +do_execsql_test aggorderby-3.0 { + SELECT group_concat(DISTINCT a ORDER BY a) FROM t1; +} {0,1,2,3,4,5,6,7,8,9} +do_execsql_test aggorderby-3.1 { + SELECT group_concat(DISTINCT a ORDER BY c) FROM t1; +} {0,7,4,1,8,5,2,9,6,3} + +do_execsql_test aggorderby-4.0 { + SELECT count(ORDER BY a) FROM t1; +} 20 +do_execsql_test aggorderby-4.1 { + SELECT c, max(a ORDER BY a) FROM t1; +} {7 9} + + +do_execsql_test aggorderby-5.0 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t3; + CREATE TABLE t1(a TEXT); INSERT INTO t1 VALUES('aaa'),('bbb'); + CREATE TABLE t3(d TEXT); INSERT INTO t3 VALUES('/'),('-'); + SELECT (SELECT string_agg(a,d) FROM t3) FROM t1; +} {aaa-aaa bbb-bbb} +do_execsql_test aggorderby-5.1 { + SELECT (SELECT group_concat(a,d ORDER BY d) FROM t3) FROM t1; +} {aaa/aaa bbb/bbb} +do_execsql_test aggorderby-5.2 { + SELECT (SELECT string_agg(a,d ORDER BY d DESC) FROM t3) FROM t1; +} {aaa-aaa bbb-bbb} +do_execsql_test aggorderby-5.3 { + SELECT (SELECT string_agg(a,'#' ORDER BY d) FROM t3) FROM t1; +} {aaa#aaa bbb#bbb} + +# COLLATE works on the ORDER BY. +# +do_execsql_test aggorderby-6.0 { + WITH c(x) AS (VALUES('abc'),('DEF'),('xyz'),('ABC'),('XYZ')) + SELECT string_agg(x,',' ORDER BY x COLLATE nocase), + string_agg(x,',' ORDER BY x) FROM c; +} {abc,ABC,DEF,xyz,XYZ ABC,DEF,XYZ,abc,xyz} +do_execsql_test aggorderby-6.1 { + WITH c(x,y) AS (VALUES(1,'a'),(2,'B'),(3,'c'),(4,'D')) + SELECT group_concat(x ORDER BY y COLLATE nocase), + group_concat(x ORDER BY y COLLATE binary) FROM c; +} {1,2,3,4 2,4,1,3} + +# NULLS FIRST and NULLS LAST work on the ORDER BY +# +do_execsql_test aggorderby-7.0 { + WITH c(x) AS (VALUES(1),(NULL),(2.5),(NULL),('three')) + SELECT json_group_array(x ORDER BY x NULLS FIRST), + json_group_array(x ORDER BY x NULLS LAST) FROM c; +} {[null,null,1,2.5,"three"] [1,2.5,"three",null,null]} +do_execsql_test aggorderby-7.1 { + WITH c(x,y) AS (VALUES(1,9),(2,null),(3,5),(4,null),(5,1)) + SELECT json_group_array(x ORDER BY y NULLS FIRST, x), + json_group_array(x ORDER BY y NULLS LAST, x) FROM c; +} {[2,4,5,3,1] [5,3,1,2,4]} + +# The DISTINCT only applies to the function arguments, not to the +# ORDER BY arguments. +# +do_execsql_test aggorderby-8.0 { + WITH c(x,y,z) AS (VALUES('a',4,5),('b',3,6),('c',2,7),('c',1,8)) + SELECT group_concat(DISTINCT x ORDER BY y, z) FROM c; +} {c,b,a} +do_execsql_test aggorderby-8.1 { + WITH c(x,y,z) AS (VALUES('a',4,5),('b',3,6),('b',2,7),('c',1,8)) + SELECT group_concat(DISTINCT x ORDER BY y, z) FROM c; +} {c,b,a} +do_execsql_test aggorderby-8.2 { + WITH c(x,y) AS (VALUES(1,1),(2,2),(3,3),(3,4),(3,5),(3,6)) + SELECT sum(DISTINCT x ORDER BY y) FROM c; +} 6 + + +finish_test diff --git a/test/alter.test b/test/alter.test index 0088858a1..ee8e6c0b9 100644 --- a/test/alter.test +++ b/test/alter.test @@ -934,5 +934,24 @@ do_execsql_test alter-19.3 { SELECT name FROM sqlite_schema WHERE sql LIKE '%t3%' ORDER BY name; } {r1 t3} +# 2023-10-14 +# On an ALTER TABLE ADD COLUMN with a DEFAULT clause on a STRICT table +# make sure that the DEFAULT has a compatible type. +# +reset_db +do_execsql_test alter-20.1 { + CREATE TABLE t1(a INT) STRICT; + INSERT INTO t1(a) VALUES(45); +} {} +do_catchsql_test alter-20.2 { + ALTER TABLE t1 ADD COLUMN b TEXT DEFAULT x'313233'; +} {1 {type mismatch on DEFAULT}} +do_execsql_test alter-20.2 { + DELETE FROM t1; + ALTER TABLE t1 ADD COLUMN b TEXT DEFAULT x'313233'; +} {} +do_catchsql_test alter-20.3 { + INSERT INTO t1(a) VALUES(45); +} {1 {cannot store BLOB value in TEXT column t1.b}} finish_test diff --git a/test/altercol.test b/test/altercol.test index e39793aa9..f44aa2e06 100644 --- a/test/altercol.test +++ b/test/altercol.test @@ -343,6 +343,21 @@ do_catchsql_test 8.4.5 { ALTER TABLE b1 RENAME a TO aaa; } {1 {error in view zzz: no such column: george}} +do_execsql_test 8.5 { + DROP VIEW zzz; + CREATE TABLE t5(a TEXT, b INT); + INSERT INTO t5(a,b) VALUES('aaa',7),('bbb',3),('ccc',4); + CREATE VIEW vt5(x) AS SELECT group_concat(a ORDER BY b) FROM t5; + SELECT x FROM vt5; +} {bbb,ccc,aaa} +do_execsql_test 8.5.1 { + ALTER TABLE t5 RENAME COLUMN b TO bbb; + SELECT sql FROM sqlite_schema WHERE name='vt5'; +} {{CREATE VIEW vt5(x) AS SELECT group_concat(a ORDER BY bbb) FROM t5}} +do_execsql_test 8.5.2 { + SELECT x FROM vt5; +} {bbb,ccc,aaa} + #------------------------------------------------------------------------- # More triggers. # diff --git a/test/altermalloc3.test b/test/altermalloc3.test index 2dc0b46f2..47efebe22 100644 --- a/test/altermalloc3.test +++ b/test/altermalloc3.test @@ -26,6 +26,7 @@ set ::TMPDBERROR [list 1 \ {unable to open a temporary database file for storing temporary tables} ] +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DDL 1 do_execsql_test 1.0 { CREATE TABLE x1( one, two, three, PRIMARY KEY(one), diff --git a/test/alterqf.test b/test/alterqf.test index 423a9fa86..b248c7c7f 100644 --- a/test/alterqf.test +++ b/test/alterqf.test @@ -65,7 +65,7 @@ foreach {tn before after} { 11 {CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN SELECT max("str", new."a") FROM t1 - WHERE group_concat("b", ",") OVER (ORDER BY c||"str"); + WHERE string_agg("b", ",") OVER (ORDER BY c||"str"); UPDATE t1 SET c= b + "str"; DELETE FROM t1 WHERE EXISTS ( SELECT 1 FROM t1 AS o WHERE o."a" = "o.a" AND t1.b IN("t1.b") @@ -73,7 +73,7 @@ foreach {tn before after} { END; } {CREATE TRIGGER ott AFTER UPDATE ON t1 BEGIN SELECT max('str', new."a") FROM t1 - WHERE group_concat("b", ',') OVER (ORDER BY c||'str'); + WHERE string_agg("b", ',') OVER (ORDER BY c||'str'); UPDATE t1 SET c= b + 'str'; DELETE FROM t1 WHERE EXISTS ( SELECT 1 FROM t1 AS o WHERE o."a" = 'o.a' AND t1.b IN('t1.b') diff --git a/test/altertrig.test b/test/altertrig.test index 934a63666..556dc3fea 100644 --- a/test/altertrig.test +++ b/test/altertrig.test @@ -160,4 +160,3 @@ foreach {tn alter update final} { } finish_test - diff --git a/test/bestindex9.test b/test/bestindex9.test index 94b9da0d3..d591b30ef 100644 --- a/test/bestindex9.test +++ b/test/bestindex9.test @@ -102,7 +102,3 @@ do_bestindex9_test 4 { finish_test - - - - diff --git a/test/bestindexA.test b/test/bestindexA.test index 650404eaa..197698647 100644 --- a/test/bestindexA.test +++ b/test/bestindexA.test @@ -133,6 +133,3 @@ do_xbestindex_test 1.9 { finish_test - - - diff --git a/test/bestindexB.test b/test/bestindexB.test new file mode 100644 index 000000000..b50e74fee --- /dev/null +++ b/test/bestindexB.test @@ -0,0 +1,87 @@ +# 2023-10-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 bestindexB + +ifcapable !vtab { + finish_test + return +} + +register_tcl_module db + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(a, b, c)" + } + + xBestIndex { + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + + if {[info exists ::xbestindex_sql]} { + explain_i $::xbestindex_sql + set ::xbestindex_res [ execsql $::xbestindex_sql ] + } + + return "cost 1000000 rows 1000000 idxnum 0 idxstr hello" + } + + xFilter { + return "sql {SELECT 0, 1, 2, 3}" + } + } + + return {} +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); + CREATE TABLE y1(a, b); + CREATE TABLE y2(a, b); +} {} + +do_execsql_test 1.1 { + SELECT * FROM x1 +} {1 2 3} + +do_execsql_test 1.2 { + INSERT INTO y1 VALUES(1, 2) RETURNING rowid; +} {1} + +do_execsql_test 1.3 { + CREATE TRIGGER y1tr BEFORE INSERT ON y1 BEGIN + SELECT * FROM x1; + END; + INSERT INTO y1 VALUES(3, 4) RETURNING rowid; +} {2} + + +# This time, rig the xBestIndex() method of the vtab to invoke an SQL +# statement that uses RETURNING. +set ::xbestindex_sql { + INSERT INTO y2 VALUES(NULL, NULL) RETURNING rowid; +} +do_execsql_test 1.4 { + INSERT INTO y1 VALUES(5, 6) RETURNING rowid; +} {3} + +do_test 1.5 { + set ::xbestindex_res +} {1} + +finish_test diff --git a/test/changes.test b/test/changes.test index 21db075f9..b3a2ae1ee 100644 --- a/test/changes.test +++ b/test/changes.test @@ -86,5 +86,3 @@ foreach {tn nRow wor} { } finish_test - - diff --git a/test/changes2.test b/test/changes2.test index 46e25c03a..5b2684a8d 100644 --- a/test/changes2.test +++ b/test/changes2.test @@ -92,4 +92,3 @@ do_execsql_test 2.4 { } {{2 changes} {2 changes}} finish_test - diff --git a/test/date.test b/test/date.test index 3e9318189..fb76dac8a 100644 --- a/test/date.test +++ b/test/date.test @@ -207,15 +207,33 @@ datetest 3.16 "strftime('[repeat 200 %Y]','2003-10-31')" [repeat 200 2003] datetest 3.17 "strftime('[repeat 200 abc%m123]','2003-10-31')" \ [repeat 200 abc10123] -foreach c {a b c e g h i k l n o p q r t v x y z - A B C D E F G I K L N O P Q R T U V Z +foreach c {a b c g h i n o q r t v x y z + A B C D E G K L N O Q V Z 0 1 2 3 4 5 6 6 7 9 _} { datetest 3.18.$c "strftime('%$c','2003-10-31')" NULL } +datetest 3.20 {strftime('%e','2023-08-09')} { 9} +datetest 3.21 {strftime('%F %T','2023-08-09 01:23')} {2023-08-09 01:23:00} +datetest 3.22 {strftime('%k','2023-08-09 04:59:59')} { 4} +datetest 3.23 {strftime('%I%P','2023-08-09 11:59:59')} {11am} +datetest 3.24 {strftime('%I%p','2023-08-09 12:00:00')} {12PM} +datetest 3.25 {strftime('%I%P','2023-08-09 12:59:59.9')} {12pm} +datetest 3.26 {strftime('%I%p','2023-08-09 13:00:00')} {01PM} +datetest 3.27 {strftime('%I%P','2023-08-09 23:59:59')} {11pm} +datetest 3.28 {strftime('%I%p','2023-08-09 00:00:00')} {12AM} +datetest 3.29 {strftime('%l:%M%P','2023-08-09 13:00:00')} { 1:00pm} +datetest 3.30 {strftime('%F %R','2023-08-09 12:34:56')} {2023-08-09 12:34} +datetest 3.31 {strftime('%w %u','2023-01-01')} {0 7} +datetest 3.32 {strftime('%w %u','2023-01-02')} {1 1} +datetest 3.33 {strftime('%w %u','2023-01-03')} {2 2} +datetest 3.34 {strftime('%w %u','2023-01-04')} {3 3} +datetest 3.35 {strftime('%w %u','2023-01-05')} {4 4} +datetest 3.36 {strftime('%w %u','2023-01-06')} {5 5} +datetest 3.37 {strftime('%w %u','2023-01-07')} {6 6} # Ticket #2276. Make sure leading zeros are inserted where appropriate. # -datetest 3.20 \ +datetest 3.40 \ {strftime('%d/%f/%H/%W/%j/%m/%M/%S/%Y','0421-01-02 03:04:05.006')} \ 02/05.006/03/00/002/01/04/05/0421 diff --git a/test/date4.test b/test/date4.test new file mode 100644 index 000000000..0d820a0a4 --- /dev/null +++ b/test/date4.test @@ -0,0 +1,38 @@ +# 2023-08-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. +# +#*********************************************************************** +# +# Test cases for the strftime() SQL function. Comparisons against the +# C-library strftime() function. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Skip this whole file if date and time functions are omitted +# at compile-time +# +ifcapable {!datetime} { + finish_test + return +} + +if {$tcl_platform(os)=="Linux"} { + set FMT {%d,%e,%F,%H,%k,%I,%l,%j,%m,%M,%u,%w,%W,%Y,%%,%P,%p} +} else { + set FMT {%d,%e,%F,%H,%I,%j,%p,%R,%u,%w,%W,%%} +} +for {set i 0} {$i<=24854} {incr i} { + set TS [expr {$i*86401}] + do_execsql_test date4-$i { + SELECT strftime($::FMT,$::TS,'unixepoch'); + } [list [strftime $FMT $TS]] +} + +finish_test diff --git a/test/dbfuzz001.test b/test/dbfuzz001.test index 2a430de12..228dd16db 100644 --- a/test/dbfuzz001.test +++ b/test/dbfuzz001.test @@ -371,4 +371,27 @@ do_catchsql_test dbfuzz001-330 { } {1 {database disk image is malformed}} extra_schema_checks 1 +#------------------------------------------------------------------------- +reset_db + +do_execsql_test dbfuzz001-430 { + CREATE TABLE t1(a INTEGER, b INT, c DEFAULT 0); +} + +do_execsql_test dbfuzz001-420 { + PRAGMA locking_mode=EXCLUSIVE; + PRAGMA journal_mode = memory; + INSERT INTO t1 VALUES(1,2,3); + PRAGMA journal_mode=PERSIST; +} {exclusive memory persist} + +do_execsql_test dbfuzz001-430 { + INSERT INTO t1 VALUES(4, 5, 6); +} + +do_execsql_test dbfuzz001-440 { + PRAGMA journal_mode=MEMORY; + INSERT INTO t1 VALUES(7, 8, 9); +} {memory} + finish_test diff --git a/test/dbpagefault.test b/test/dbpagefault.test index 544d279ce..f27741cba 100644 --- a/test/dbpagefault.test +++ b/test/dbpagefault.test @@ -84,5 +84,3 @@ do_catchsql_test 3.2 { finish_test - - diff --git a/test/distinctagg.test b/test/distinctagg.test index 6e46c8861..199ca0666 100644 --- a/test/distinctagg.test +++ b/test/distinctagg.test @@ -56,7 +56,7 @@ do_test distinctagg-2.1 { } {1 {DISTINCT aggregates must have exactly one argument}} do_test distinctagg-2.2 { catchsql { - SELECT group_concat(distinct a,b) FROM t1; + SELECT string_agg(distinct a,b) FROM t1; } } {1 {DISTINCT aggregates must have exactly one argument}} @@ -215,4 +215,3 @@ do_execsql_test 7.0 { finish_test - diff --git a/test/e_expr.test b/test/e_expr.test index 5ad5993bb..0db63a8ac 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -1930,7 +1930,7 @@ foreach {tn expr restype resval} { 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 + 8 { ( SELECT string_agg(y,'') FROM t4 ) } text onetwothree 9 { ( SELECT max(x) FROM t4 WHERE y LIKE '___') } integer 2 } { diff --git a/test/e_select.test b/test/e_select.test index 5a3f0d30d..e2e969dcf 100644 --- a/test/e_select.test +++ b/test/e_select.test @@ -943,7 +943,7 @@ do_select_tests e_select-4.6 { 4 "SELECT *, count(*) FROM a1 JOIN a2" {1 1 1 1 16} 5 "SELECT *, sum(three) FROM a1 NATURAL JOIN a2" {1 1 1 3} 6 "SELECT *, sum(three) FROM a1 NATURAL JOIN a2" {1 1 1 3} - 7 "SELECT group_concat(three, ''), a1.* FROM a1 NATURAL JOIN a2" {12 1 1} + 7 "SELECT string_agg(three, ''), a1.* FROM a1 NATURAL JOIN a2" {12 1 1} } # EVIDENCE-OF: R-04486-07266 Or, if the dataset contains zero rows, then diff --git a/test/filter2.test b/test/filter2.test index 21ee6659f..06cfd2a4c 100644 --- a/test/filter2.test +++ b/test/filter2.test @@ -113,7 +113,7 @@ do_execsql_test 1.12 { do_execsql_test 1.13 { SELECT - group_concat(CAST(b AS TEXT), '_') FILTER (WHERE b%2!=0), + string_agg(CAST(b AS TEXT), '_') FILTER (WHERE b%2!=0), group_concat(CAST(b AS TEXT), '_') FILTER (WHERE b%2!=1), count(*) FILTER (WHERE b%2!=0), count(*) FILTER (WHERE b%2!=1) diff --git a/test/fts3conf.test b/test/fts3conf.test index 6ceef2c47..cd4829019 100644 --- a/test/fts3conf.test +++ b/test/fts3conf.test @@ -198,7 +198,8 @@ do_execsql_test 4.1.2 { do_execsql_test 4.1.3 { SELECT * FROM t0 WHERE t0 MATCH 'abc'; INSERT INTO t0(t0) VALUES('integrity-check'); -} {} + PRAGMA integrity_check; +} {ok} do_execsql_test 4.2.1 { CREATE VIRTUAL TABLE t01 USING fts4; @@ -211,7 +212,8 @@ do_execsql_test 4.2.1 { do_execsql_test 4.2.2 { SELECT * FROM t01 WHERE t01 MATCH 'b'; INSERT INTO t01(t01) VALUES('integrity-check'); -} {} + PRAGMA integrity_check; +} {ok} do_execsql_test 4.3.1 { CREATE VIRTUAL TABLE t02 USING fts4; diff --git a/test/fts3corrupt4.test b/test/fts3corrupt4.test index f8e89b3a7..433a48635 100644 --- a/test/fts3corrupt4.test +++ b/test/fts3corrupt4.test @@ -2595,17 +2595,13 @@ do_execsql_test 17.1 { UPDATE t1 SET b=quote(zeroblob(200)) WHERE a MATCH 'thread*'; } -do_catchsql_test 17.2 { - DROP TABLE IF EXISTS t1; -} {1 {SQL logic error}} - -do_execsql_test 17.3 { +do_execsql_test 17.2 { INSERT INTO t1(t1) VALUES('optimize'); } -do_catchsql_test 17.4 { +do_catchsql_test 17.3 { DROP TABLE IF EXISTS t1; -} {1 {SQL logic error}} +} {0 {}} #------------------------------------------------------------------------- reset_db @@ -5812,6 +5808,9 @@ do_execsql_test 35.0 { do_catchsql_test 35.1 { INSERT INTO f(f) VALUES ('integrity-check'); } {1 {database disk image is malformed}} +do_execsql_test 35.2 { + PRAGMA integrity_check; +} {{malformed inverted index for FTS3 table main.f}} reset_db do_catchsql_test 36.0 { diff --git a/test/fts3fault3.test b/test/fts3fault3.test new file mode 100644 index 000000000..6e1c0cd67 --- /dev/null +++ b/test/fts3fault3.test @@ -0,0 +1,54 @@ +# 2023 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 + +set ::testprefix fts3fault + +# If SQLITE_ENABLE_FTS3 is not defined, omit this file. +ifcapable !fts3 { finish_test ; return } + +set ::TMPDBERROR [list 1 \ + {unable to open a temporary database file for storing temporary tables} +] + + +# 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. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts3(a); + INSERT INTO t1 VALUES('test renaming the table'); + INSERT INTO t1 VALUES(' after it has been written'); + INSERT INTO t1 VALUES(' actually other stuff instead'); +} +faultsim_save_and_close +do_faultsim_test 1 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + BEGIN; + DELETE FROM t1 WHERE rowid=2; + } +} -body { + execsql { + DELETE FROM t1; + } +} -test { + catchsql { COMMIT } + faultsim_integrity_check + faultsim_test_result {0 {}} +} + + +finish_test diff --git a/test/fts3fuzz001.test b/test/fts3fuzz001.test index 41b22d33d..6b1ae90ee 100644 --- a/test/fts3fuzz001.test +++ b/test/fts3fuzz001.test @@ -13,6 +13,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix fts3fuzz001 ifcapable !deserialize||!fts3 { finish_test @@ -110,5 +111,31 @@ do_test fts3fuzz001-121 { } } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 200 { + CREATE VIRTUAL TABLE x1 USING fts3(x); + + INSERT INTO x1 VALUES('braes brag bragged bragger bragging'); + INSERT INTO x1 VALUES('brags braid braided braiding braids'); + INSERT INTO x1 VALUES('brain brainchild brained braining brains'); + INSERT INTO x1 VALUES('brainstem brainstems brainstorm brainstorms'); + INSERT INTO x1(x1) VALUES('nodesize=24'); +} + +do_execsql_test 210 { + PRAGMA integrity_check; +} {ok} + +do_execsql_test 220 { + INSERT INTO x1(x1) VALUES('merge=10,2') +} + +do_execsql_test 220 { + PRAGMA integrity_check; +} {ok} + + + finish_test diff --git a/test/fts4check.test b/test/fts4check.test index c94c35910..7f1004d8b 100644 --- a/test/fts4check.test +++ b/test/fts4check.test @@ -71,7 +71,10 @@ foreach {tn disruption} { do_catchsql_test 1.2.2.$tn { INSERT INTO t1 (t1) VALUES('integrity-check') } {1 {database disk image is malformed}} - do_execsql_test 1.2.3.$tn "ROLLBACK" + do_execsql_test 1.2.3.$tn { + PRAGMA integrity_check; + } {{malformed inverted index for FTS4 table main.t1}} + do_execsql_test 1.2.4.$tn "ROLLBACK" } do_test 1.3 { fts_integrity db t1 } {ok} @@ -106,7 +109,10 @@ foreach {tn disruption} { do_catchsql_test 2.2.2.$tn { INSERT INTO t2 (t2) VALUES('integrity-check') } {1 {database disk image is malformed}} - do_execsql_test 2.2.3.$tn "ROLLBACK" + do_test 2.2.3.$tn { + db eval {PRAGMA integrity_check(t2);} + } {{malformed inverted index for FTS4 table main.t2}} + do_execsql_test 2.2.4.$tn "ROLLBACK" } diff --git a/test/fts4intck1.test b/test/fts4intck1.test new file mode 100644 index 000000000..abdc46bf5 --- /dev/null +++ b/test/fts4intck1.test @@ -0,0 +1,58 @@ +# 2023-10-23 +# +# 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 PRAGMA integrity_check against and FTS3/FTS4 table. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !fts3 { finish_test ; return } + +set ::testprefix fts4intck1 + +proc slang {in} { + return [string map {th d e eh} $in] +} + +db function slang -deterministic -innocuous slang +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT AS (slang(b))); + INSERT INTO t1(b) VALUES('the quick fox jumps over the lazy brown dog'); + SELECT c FROM t1; +} {{deh quick fox jumps ovehr deh lazy brown dog}} + +do_execsql_test 1.1 { + CREATE VIRTUAL TABLE t2 USING fts4(content="t1", c); + INSERT INTO t2(t2) VALUES('rebuild'); + SELECT docid FROM t2 WHERE t2 MATCH 'deh'; +} {1} + +do_execsql_test 1.2 { + PRAGMA integrity_check(t2); +} {ok} + +db close +sqlite3 db test.db +do_execsql_test 2.1 { + PRAGMA integrity_check(t2); +} {{unable to validate the inverted index for FTS4 table main.t2: SQL logic error}} + +db function slang -deterministic -innocuous slang +do_execsql_test 2.2 { + PRAGMA integrity_check(t2); +} {ok} + +proc slang {in} {return $in} +do_execsql_test 2.3 { + PRAGMA integrity_check(t2); +} {{malformed inverted index for FTS4 table main.t2}} + + +finish_test diff --git a/test/fts4langid.test b/test/fts4langid.test index 45e851f94..7be594bd5 100644 --- a/test/fts4langid.test +++ b/test/fts4langid.test @@ -499,7 +499,8 @@ do_execsql_test 6.0 { } do_execsql_test 6.1 { INSERT INTO vt0(vt0) VALUES('integrity-check'); -} + PRAGMA integrity_check; +} {ok} do_execsql_test 6.2 { COMMIT; INSERT INTO vt0(vt0) VALUES('integrity-check'); diff --git a/test/fts4merge.test b/test/fts4merge.test index 3cd693209..ffef0e933 100644 --- a/test/fts4merge.test +++ b/test/fts4merge.test @@ -37,7 +37,7 @@ foreach mod {fts3 fts4} { do_test 1.0 { fts3_build_db_1 -module $mod 1004 } {} do_test 1.1 { fts3_integrity_check t1 } {ok} do_execsql_test 1.1 { - SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level + SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level } { 0 {0 1 2 3 4 5 6 7 8 9 10 11} 1 {0 1 2 3 4 5 6 7 8 9 10 11 12 13} @@ -67,7 +67,7 @@ foreach mod {fts3 fts4} { } do_execsql_test 1.5 { - SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level + SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level } { 3 0 } @@ -103,7 +103,7 @@ foreach mod {fts3 fts4} { do_test 3.1 { fts3_integrity_check t2 } {ok} do_execsql_test 3.2 { - SELECT level, group_concat(idx, ' ') FROM t2_segdir GROUP BY level + SELECT level, string_agg(idx, ' ') FROM t2_segdir GROUP BY level } { 0 {0 1 2 3 4 5 6} 1 {0 1 2 3 4} @@ -132,7 +132,7 @@ foreach mod {fts3 fts4} { foreach x {a c b d e f g h i j k l m n o p} { execsql "INSERT INTO t4 VALUES('[string repeat $x 600]')" } - execsql {SELECT level, group_concat(idx, ' ') FROM t4_segdir GROUP BY level} + execsql {SELECT level, string_agg(idx, ' ') FROM t4_segdir GROUP BY level} } {0 {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15}} foreach {tn expect} { @@ -160,7 +160,7 @@ foreach mod {fts3 fts4} { do_execsql_test 4.4.2 { DELETE FROM t4_stat WHERE rowid=1; INSERT INTO t4(t4) VALUES('merge=1,12'); - SELECT level, group_concat(idx, ' ') FROM t4_segdir GROUP BY level; + SELECT level, string_agg(idx, ' ') FROM t4_segdir GROUP BY level; } "0 {0 1 2 3 4 5} 1 0" @@ -194,7 +194,7 @@ foreach mod {fts3 fts4} { do_execsql_test 5.3 { INSERT INTO t1(t1) VALUES('merge=1,5'); INSERT INTO t1(t1) VALUES('merge=1,5'); - SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level; + SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level; } { 1 {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14} 2 {0 1 2 3} @@ -249,7 +249,7 @@ foreach mod {fts3 fts4} { do_execsql_test 5.11 { INSERT INTO t1(t1) VALUES('merge=1,6'); - SELECT level, group_concat(idx, ' ') FROM t1_segdir GROUP BY level; + SELECT level, string_agg(idx, ' ') FROM t1_segdir GROUP BY level; SELECT quote(value) from t1_stat WHERE rowid=1; } { 1 {0 1} 2 0 3 0 X'010E' diff --git a/test/func.test b/test/func.test index aea372e08..883950a0c 100644 --- a/test/func.test +++ b/test/func.test @@ -1136,18 +1136,18 @@ ifcapable deprecated { } {3} } -# The group_concat() function. +# The group_concat() and string_agg() functions. # do_test func-24.1 { execsql { - SELECT group_concat(t1) FROM tbl1 + SELECT group_concat(t1), string_agg(t1,',') FROM tbl1 } -} {this,program,is,free,software} +} {this,program,is,free,software this,program,is,free,software} do_test func-24.2 { execsql { - SELECT group_concat(t1,' ') FROM tbl1 + SELECT group_concat(t1,' '), string_agg(t1,' ') FROM tbl1 } -} {{this program is free software}} +} {{this program is free software} {this program is free software}} do_test func-24.3 { execsql { SELECT group_concat(t1,' ' || rowid || ' ') FROM tbl1 @@ -1160,9 +1160,9 @@ do_test func-24.4 { } {{}} do_test func-24.5 { execsql { - SELECT group_concat(t1,NULL) FROM tbl1 + SELECT group_concat(t1,NULL), string_agg(t1,NULL) FROM tbl1 } -} {thisprogramisfreesoftware} +} {thisprogramisfreesoftware thisprogramisfreesoftware} do_test func-24.6 { execsql { SELECT 'BEGIN-'||group_concat(t1) FROM tbl1 diff --git a/test/func9.test b/test/func9.test new file mode 100644 index 000000000..6cf9fc31e --- /dev/null +++ b/test/func9.test @@ -0,0 +1,40 @@ +# 2023-08-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. +# +#************************************************************************* +# +# Test cases for SQL newer functions +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test func9-100 { + SELECT concat('abc',123,null,'xyz'); +} {abc123xyz} +do_execsql_test func9-110 { + SELECT typeof(concat(null)); +} {text} +do_catchsql_test func9-120 { + SELECT concat(); +} {1 {wrong number of arguments to function concat()}} +do_execsql_test func9-130 { + SELECT concat_ws(',',1,2,3,4,5,6,7,8,NULL,9,10,11,12); +} {1,2,3,4,5,6,7,8,9,10,11,12} +do_execsql_test func9-140 { + SELECT concat_ws(NULL,1,2,3,4,5,6,7,8,NULL,9,10,11,12); +} {{}} +do_catchsql_test func9-150 { + SELECT concat_ws(); +} {1 {wrong number of arguments to function concat_ws()}} +do_catchsql_test func9-160 { + SELECT concat_ws(','); +} {1 {wrong number of arguments to function concat_ws()}} + + +finish_test diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db index 6b434f6bbfd84dd6c3d92c38740c9359f1d2f980..3e34180071ede5e023878d8528fe65a8fd9d24b9 100644 GIT binary patch delta 77749 zcmeFZcX(7q^f$h9%igg zL14#135tq}n5ZB~3u2cN6g$FuW}{f%@9+8j^ZxPv@$x+PEV*~?%$YMYXXc#qIk&vt z!<9FBxQdqMSe44uM)mhUQ)QbZF;*2fwff+w=Owaiolf4lsDvBL_2YVRUAf1&Y%YyU z;97H$Tqqa7Svfr?afJL%ek6CuH{=GnLOvl4?l*S=(jVM$NN>7PIE%P1-L3JDMmI`}JLhhNwB8+!^nG^}(zo1^ zNMCm&^IV-f4C!&V6X{X61L;fdP^8begOOIa1Cg$B`y+kI-2&-Sw;$5QZadONZX43M zZVS?BZWGc{w~^hS?AGJY6K);S;chL`0d5V_-fkIbfm=e_$*o43=@yZ;aq~zc7a_4O zM54??!p~8u$nUd}d_NOO(+niHrz81x8j>%|kbFJ`$@M3YT$_aC(nKWZ#-senM`Q87 z^<$8{HwwvHBapmNjO68ENRA9fa(EDu+5t%RKaON?eikNgnTrq&to$>CzraPCk+Z99YuQfkR4yaV$y8c1UbEp2Um;NsKA$ zI&1gnBbTsoUb$F>bj9Ka+GP*41TPLRo<7hD>KM8$MUY9yBv+yf|0O4mWGB_3g-?pvd=+m*F<70gtQ>V-tKdMbhn~^|v@%}KZV~$1X(6vwB zu08Q#65RcU`1?|*+PqoaEyX3XmtmNIZq`yNFTk|z&NDM*iF-GTg1 zf};P2mlCF6f2YM_5$skEN5=t$1^HcjI{N1q^y}CsCLyg&ZbF-duB7CQ#MH#Zq@=`F znQ*0RbOIDlGRXR=Q%8^P)!Q*)+LXx&(N#g_$)cPhsJ4OGt8s#>lMn&o0jmjar3XR*`4orE2PSiM~Y zUcg~*H$Z4fLkon@dE!rnLg67#0;tdzf2BYdn~@pt{zxH$#tjhiWzvq877J^6;^!SD z>=cDeLA7bab;ZY83wnLL5We8ZD)TDe)6LcsC75JVkYgffoxJ%UzlV|VYI5`L!-tA(ctyBi6W&kEnt+e-vH zPoik$E>?EDA8~PmCvBkY6Co1rGzzn5b+s^(CvD-*X(0`6?-Xpb*AC$@ zPvW44h`!cQeDSQ&$%IyaOy(0Pt`NeZEyvwq?YqEfGL=E?_JCw?8BD=&mFL31^gX`Q zbSsN+Pnb7Rx!drwSgZUZZj;~LEl3hqwpA=?tBn4{E3oXI5U8tuh zUmH(Sp?bSuhpkJ53TRv^Xdv}TVLFX^N4TOU>2zt6kjg{5kA!zf23>kjn9D=CTkwaV zUxW#$)k!;70vz0Wg&tmSx1(N_|n1~_#hi91Tl<8 zeJ;Eu3ph6SplF=MH-G$~;uS$dUAF{@$hogY<_W5wdNLV|r@LG(n_XPu8e@~dgllEY)7W^r)_7zVaW4w4MaHK{-+( zPUA^O#?W|J^pl{_S%SDqBAuzA6CY^A_JZp9F|6ez+RjgWlZZ_JcLQf%@f^MAFV2#& zvLdHAK`249nPk7Ro3VEzVKRDP+v_oNgQK8`-#73UI(QDO~GhQacQ;%q1%EJj1?N#cBP4HgZw?RfEdS?DCFeu1(^**Cl< zs+lVOL`1d)m(Y{4I+ur>eDyK5JVwxyI<=8N_crQCnpdbk%~dTC!=SN5JqAuJ5@lL3 zA4d%M&nH)crqFi#;W^4Xn^_wPYj^g#C=KVBB*YId6UTrA5@BdVEk04 z&^1%llgYZKagFrEeld@baaFZqg4kV9t&3%yN(a|;UV=NazXTP>#7@xmu-F+7V5Dq$ zn{Hq_h?BPTNvD-;N-6+j}*%?;4NgGxK8VI$#Bi`l6ELt{GTuZ#)h)?*WF`GI& zsIA2qlOa*^{5{adg$QJn~mSalK9ZBpmJlO(A2415Ko`TS0@nGhbK93%obZPCYDq895MgzUL5kt zk){!%2Xz`Z==)kBURT{tGQ-&m!qY0U<4 zI7geF6u0`uX$>{Ust5et$`)TMYuTJld(_IyJgH#q2C_RjtdVsZRC7%k>NWDyFO4V? zR9|%YduFi?>&Uv|IWcG_kd=6?qe7M%_1=0YNeB#w;-1=e^wN4Uo1+y5^#~sy8>p*L z{Tv6i^TkVSCE~6zPaIcOh!Y3@!t2bHqw$w>Usk8De9_6WIbKqM?FO#n}LM$k%#ge zXE|D&r`Gs5dWMqs#S}s-3e|6ORpZoAtob0EO7q3zJ|S&|k_F+>uiFNWlHnz_}r3d*z?OOUsfjOw7SRLk5 za3-dKFZ#N+gRvM#6U)^NJlRQcvM2mi#Gs~x-e0PgMDjc>^ACySq19UTr(`!A9L@*R z+B4!(pGXFBRa?|u+1T`^&#YIE zwTx+|DEizE^+!bPhf*2#XzDn?)QfLL@9$FcgzWO}R<}}%j|(brtUEX+n)chT-b2Vf zZ>_pY6b9f46;77wV*qa_(I1bf6;3>WQh139FSEIJ2r7vIPYg%Z-DPo*pn9?Zu19DR z=+;x%Su&7*@TS_uk%LvI)uYA1f@)2=?=jptuQt#ZPOAs;ghI*c7M!E_`C3gNvfZONQOXXK8<_{56 zHS_=R;;kqBBiZWk;`J(2&hnoK)o0W#>52>LIXrm{61Mo^_*_;uXvuMUU5IGOgXsRs$dKl8YSk3esemjDRURX@T(e z8o7iv=_DgBzKIN+2KU=7BWS%rX+z+HAVsD_%+m8}@-8imkdio>7AR@dWE9PJNJoiq zX5F-rl!QqSi1-e^{616$%PBOtjil${K$a2=Em}$|>FsFAE|3qXBSMPeX>7c-TrG|f zRF(ai%+ldbij*ujtm`ovPjDfRZYlJMROx9#&U(|O(Hdba4vw!Mr9rX=no=Ye9o|ja ziZlB$X)XEC+d=9lh~ov7*Y8mcYB;gpnojGxNFhWzze+t0H$o`$M_K%0#$~VXK7QRWR=LLaIz#X9voT15L%mU ziy&}dWTd>VF-xR%10^Df6R|UY_fJhr=ST;Lg6&*>y4?gpwS%pbzZnq$pZkMtr1YJ5 z5er?RjZ-C5M~>0bSuL4F@03a=o?P`#m)eTt2^u#?YT(31EO-MB=CwOX-<&Un%HkA3 zwca<=eKJ+UQ>(Df+$W{CJ zR|YtDg#WY3{XNmuac8?^62Ha{+=S{zzbu+pElt%4(*@O5aQYjYi_@mPI4j95Z>>}# z2s7BTk|X?IRqd6W-XqdOj@`j+Q@Rp5gQk{@mPvGg@B{N_qg(DamoX~YGzgv8l`YF~$cNPCM>>!8aUB@Hj% zuj6OoL82V)9!4F~JQep!G92H>2bh$>qfO&N`R{(#pXP&ujPR$BA9G(sW|p+(}vfB0$|a^%)xZ_FVaOx zm?NmJ{!@Y}k_aaX^c6IM$d@?r0XpRh$~`V`V%Yfhxt z=1c+=FN9{mlx%eXYD%`_ayn?tH;to{95P76K+9IilL;((QBHq37oGu<6&4*RwU&0l%Ig&#TpSNwg@xQi!M2qSDO<@C%KZry)Hb7W+lsS^TYxt z^?V==NtegT;zB`nEv|Wdw&%;Oaba?SwP_0kRUEK-CjG}Eu$)E7rkVIE+(`G&mizI1_{bD+_L6NhaK4NVod{a7YvfXbZkmz{Vc6FS*4I{0aycxNhOd{=?u~_pz_9ML z*G3t=F0G-#Ew`gNTjbM{GDJnrknXDYR72L8yA%k^=DB$&*&$C5Gir<79MtcS=ZQ(R zC2j(RJLUOe+ru`uiq`Lx<2f-3S&5@}g$OMVjUB`8vZ)gf4V}U|HSe1H_hR?ZcT+UV z*@as$S zUn|cRCpyhJv;h9aZrA^^+wI@%`s$D7Np~n)8uJ3HJYGgc;**Jp?s={!l)RSRn)Ux< zp7f%3Cq<#?u=AgD=yypN6|w2xtVZxqcT0_eRB=WBn*`D|7f@{>(@>V!yw%7sWw%C6 zYa8V&=%$l(0_#T)y!uk}spy}vrPUhICl~bAOw^^&j1HQ2nWN_60!<*Pa)t_!BxSaM z`!Tv~D0ORl(5Ceo0S%1DV*Kf~Zkh-}9)|`odMn-T(UcH~-=z5kDi%h!1miO8xAb8z zjZq{TYV414K?jSJt*F2y}do=G5+L)&aBJkBV|KF zM;m;HwvP39AYLVu^w*$$|ETFmW+iYVXsgv4v>gJ5YOX-JHX^|%0-{cI;lq*O*5l+x zMn2;pZjt6383i@B3WGs~QhxM%55&sJ!@ z;L$x(S*q&?u0L&N215a!A@MIPg^_1;2;Ed*5 zp3H`vKQhBPv!ubsGR}Vg0p1m{#f&aBv=I1bH05Ns(;W7ft*`Pdq}&YacSlq zp6xaZs&A$PiB^*6b5}G&I2q$<+WnEi6!hcY z)1Z6S10|UOR@&kh&08|DAGxYHd9fS|J;|(r7I1=77Qk(@WT*QCWr;?>Y56=m$t1y= z&lLkq85CloOAX2#A}+y~mcw09L2s!^$&g74I@w>TxC*;ZkJljqpB z`;R&R>v)mzw*s#`p(mB+(D32qD+vt?8tSu6jblP-GINO^h~kx43yVrm(u$q z6rPBHZ)}BQ#mXFZ*Tec9L-#LNF8b8mHmLb1I}Y0RRx06WmV$nT97TlTt561U9ZhA_ zv|5?vE4Upl_ETCzag?lw6Kj=x*dHZ_)9OjeNS;(fQ5|no&|pw;HPrc% zvIFg(uQQiXlu9wK+j&@RW2utBo<9p^J(OhF-pNt{*K(~+bc`zwdd;mkIVkRGSp;>@ zW9eS&73LS?o`aJ^l;w2QdeXvX(t7gYtu|J^@FbwHMSzBWuXR?@+g1IqO}* zq`d3jeZ~X8^Kddy3ZTKOlrMSoZ~WB@9InD#Ypst7Kqi1S`U04DDY3NL!p@5mnraP_s|5(QBV&IteuG zZ?ynwcPfjZe3+u4O>-3!VRBgpE9Wb%S@FH#9**m&8kAyMI$a6%ZOHq;y;s@!ABiz$ zPtwd?%4tIO!`=O@=F{@s%3_`zz@VFgiu_Txm~0i!Tg&8f5U#&tu|x3%>n?iYuu`YN zAYb`$WJ7akZK*p%P;DR8tSQh^4%Z+Eb^0~ z?o&%V{kBdC;K>m*ofNbcywJ4C@)FGd$P!JjoL2BCOyTZ1G;KnzE0J{283p|oFH@mj zc_@)rY4z(&$Q5TTCOZA1vX(=4NcCGvd)n!;VwOdRpgNxHAV7zUKfUy$62XxZ-d~huf*6k0;GbQ?648(+_{;S7 z`-;GeC$WpC=+$9CRzh14?M;rn3DUu*#_G-(g<P@O;YFN9$~OCEYPNL#4I&}e*&ww@QyV2vL@gHgiMQ5&tlEJh2e z{X-Zt?PxbYy`5}m)UTznsoHZyJd0&Nq?>za$DkVe7ERY_?X(qyd_*@FX@S7xu7MJ? zt4B3i(chrZx3aa_9QhdIRN8Qj&`MCf&P@6Qc-U1tmf1PycR1ZKg6cGLy(Pi0uG-Ob zOgHT&A~xW|PuTyf$hs{(wRC3>?J$m9pcC5R9oE)(j$DL#eY+Sa&(%sasjs$PBA4Kx zK5H`6?+fcePYls^;K^m0Z^`OPXpa)Dmm^p3Fq8E>eQ}g_0QxqdC`EpWddF#d@Z=g4 zWys^8ad4Ic%8Q&Qpf)tCC*AafHb@d;1=UR`IdAnbsDX=TZ4oqJhW1NN{1mzTj5<$6 zf8{&h*?s!#2ArIz3x(vl=xD0=BT9j_>$GK%R}>{fMP`%^@R(9eyDiYZ!jaFR@}Z1| zOt}^fnJ?h_dD}i{1dRi#ZMtO0-lBEVs3*1OB>|W2CFTM2?N>5fPKa(r8=s6?jn1f{ zQQv{-MU8{$@9TJdO|Q??grZ6;!Z6UoIa)N!%O^#ZFfOK(Z{Ye|&2-i}TDrxt5eMnk zr!^x{MRd{N5nRQ63k~l^wTFj}m`FNtqjn=tz5{cY$P9|S+>*pLXr(QrWVQBptaMz| zhpf%7@VX88$D(G@dAqgCd2$EHuX0N$-WfFx@O);2y79W6^qalfK%O*#IWW32?OvI^Q6)!IzDv`(w=^1TCt z-EGm7hK2>9|BwdbJWUjJ zlio$#$2Q2OLa5ZS)1n|zl$anT+4N@(tE#X(Wk{z^QT#8 z3B3P@wudAp;(Q8WY|`vEbuVyCdJpk6HIxkJL!lT=Mv68J>J|wq3#0ne!h70XzBXm3 z>>HKGDn3rpRC&N=JgXL_~J+ zx^)Lf#(YgYXi&OWq#ShuiS|8rfcZN)g0)o7gX?gZ@1_$P z2IhPTb%&!bvT;h{ph?mw-q|`?6w|Qr_J1}0ny+yL{P7XApU&%kEJE2M0H`0y4`AgmYlHKbZiT9q?Z5K!utv;jMBZ%1;L!S&MZ|gGPwS|5L z$aR`DbbEs?MHX|={_Dmb<$>eZI1LT^RCk4t^s39cp9MtttYTQQ1gOxOWQcvoVy3|x zZRoiuzN&kUkSr+6QSX48p#dhS8>q%E+5+qx{q-}QLJ&lA_rn0RVm{aPVpx$nU8yfW%h%dzzk$`?^GkIswL zFDIlIRDPC*W^x<-w-PCWviRVYw74Xw8h5ZygVJGWAN?ja%DusHTS8Arx*k0teW=UP zvK66iv-RgW(idD2Ez4laQ$bs(G*o|wV@jr9g3Fbf}0cQ--xWwXUyt~G@)2!w&Tz8!R5PknD9B9N3H05vM>M*Tk_4g zfwZC|b2@?j!7W#Vv7J7a#`o8MOvoUp7@gUh7H8=z3H@rIK8QnaPR01l6qpjxGVu|E z_c`?I)URymg1RR%D;aMI2*vV~74IlLp3sNEfm~+~TKs)>JfY>|_1O~X0mn=AFKdd| z&v7HxKh$GTVzwR~4DyVbBhA7+;ZXq|Do61dEw_l#DCPBmx zgMhnThHQQX{~KI&#QMPlmqD`#LwPEdI$$v(nuV<%g!K#HNkzKalZ@2kNkm%iK`*zk zz!Q&jwx=!9X`VJn$9q~M9p}MAiZI#}gLI?^<4wW{PbAWTo(QDG-Q$lm)r096LXyXZw3Ww#G}>cC>hKtl278cg zp``~kg<$a@^iME)aKQ>%506yiK|u(j2RkP4?uAHIZd}Ox12^K%_+Q=gkpAqRjr2$N zqFMO!gB#N^`0w4AkHOz{qXU`0<(`K0Yj+vaM)zc-m)(<)e&n8r^d0wjq^I3ukiPC_ z`rhjvfpo9C80ib{VMuqo*|zh%doa?SZcNPJtKAr1;J3IRM_TE|+zfuLyALLa@K3pW zZJ<=nde5AFWJfwR)IY?_f*~p&%;y|1((qbGV@sGtgM&fUatw^8tpq#~}9+b1V z*n<)lr+MZgE%Ts&#VH;Xuvp?j0gI1&Fgzd@c%~rj;6c%fDIO+Ncd?tDEB?+o`ahO( zBqn(#V9l!$r*EvKhl32gc(Mix=Nq$WzQa(W5fFCv6oXylL0y&6kKS)(5IAqLVS*s` zN2gCLxT}lVIqVfLbJYtxDl#Vt^Vu2R*SViy=s~pVQrhByvjHb3!7o**Jdjf z+Zlc%QUw-R=W;>y{LBCvnq~N0LMQp{35LORcn8A{<|MBiz~w@{$fwdN{S1}pb?t5F z00-6>bhsmOXeaz`(?Gpsc0o=bg9}X83C1Q=Jb>9R2AyXGh9eR>%5&!#PQrVg4Jp(( z!EhJR0AmbGp?0BREi1bj8jg?%Si8p1=6|fSzI8NKyxK69E*xlhk|WPR{wpyUau{M* zEHU8O+0?)K>yv}?5&tjxOs$RB#5M#X+e2uVxrUd?BR=jCvK@mxhK}HR+))4j)3Lvp zc$_YtY?!PT2MVf_d4IKQVREk7D!UQy_G+_TQN7TwCmWT&xv3)T_^=hPXX(jNhF*mB znPaHs5D@qu!B7-TAHwYz0px*kA(P<(-n+!mUt|Ee75)EeG#*NyHVE{~rwn+&L2TT{ zN7Esm;*+(A&8u2(SV)G_MeEsLL#VOJ;KqY0rhFKt67nK+*XfxH%5Xy>#nidOP(b8; z8#p&2&GxWJkZ;lj(6c)Y37ohG`QE>w)QDJ;-3G*x9Dw>R##GvFui+h@)WX3C0~+hK z?BsJ0N)#h{0}mSp@Zw87JKV*9Td{E{IJ6lu^txs^NJCyRe8C~~uk0nmSlGLXli_B8 z!2*uf8R0PDnO08N_L{>4O|6VF7=JQMp@qi`hd6Q=rhIAmhTf_(JgY^_dc_&TRm8BY z6x}6)s)~V)is|{YhE_y8f~>y0;jYoiSk-Ve?nA>lLSCUoU5(Rl{yt&64i^s?qM<(B z7zD_(26lX67>)qH{Ha>B^e!0Cn0*aCxMT>XFMeU@$H6RWD57s)HDnQTlrjGqN2gpg z1p3?##~|;RVG(R^XLQ5F!1 zjY}9@d|+52zfq&OM>78}`pbOx9q?VxY%@F@V~k?c?Kobipu}a&gX#{(v$SH4F%#Xk zoY70jo3td^_$B(AyBlXPhH4l?Z$U}AaR=Qk8$CLP?OPeg=35D^up3u%7zGaTH*VB? zushW~PEggCvS%aN@6yI50|s)Ih8q_k<_p$Ot43_ak*Q$$PaBaBHrISY5+RnT&X zF`~s;4>dash^KNHNARQp%qxv|AbzhwOG6hJyK>Mu&3Kf22)$f{tO2%+Pe!@I4*S-uNOR z=V;{&;{-w{cQkI`$axw(-&n`PsqV&H@(Gw;LJ&#lJ;P>tzQEX4A{VK9rg0vjrHMvF zcw(pTkxyB-9q4a5Z&?0_F*FcW{3ZHpf$;@|c^@+vps+X(k)4-Ad($byj8<8kB&go> zE&j2TaYV!m_{LRQIoF5@S4}I77?S!+0?l5&YqWA9K7*z;#+mKs|C*w^RA8G)$7oK7jeMOx$#xw#CorZq2VzqIyM9SdddgBz$w@)o} zmm*$L#TcHlq0!{eqUCeNZ#GYJPoWE{jED&M!n=`G{~a7$jec0BHmk-O7tvi?jHt=;tba5m85i$MDVT6i32J!}lB+^cMjK@Uu zz1PI*T`=jf>_zlYtx=~z)3@#wV=m$utBxA;&;ZUqYV-=i%ttOKtRa+kKWRKHky+k% zjEH=^=RIRgRx_+2D6AWu=%Npe<%Im?{n*$?U~s(c2pP2c7PtiXtc3;rB*`v3{PMpq z8{tN9-r6p{7umyLHjjT>-N`*iP(AagGrsrGtD0`m(AtZ}UL5(wdzne)K9EmZwg9|m zqOBW^Lk$83KrvE|?a?#0jY)+3;cYU$DF_Iv+rl7#*-*HH7pU^1@kdTX|F*zU;TPjL znJhf=gsGC}1mYD;QKH&|$J^ZnP!eeBLFZ^pSBXabRFE4XX-{Xdn_XahXO{_jylRx- zgw~WN;y4QuoV1vF!HMdC@$j&S!wgTO$x5&3Oa?thTy6)LCdkUlmL62SRV-AIN2>!( z!*N=AgG?s{kwD4S&#Xtys(_x?|5=?m<(Rq_iKPrO65aYf`Tg7-&JkzuW z7@sOdp}+;}vgsZS_FzuN96NgQ$D4xSL6uErh5$OX1sj|!R%SekZug~#WH`(bJCv`o-?$+O^S=H(b*dyL6K=N6itVra#hzNQ&;@DS576p~5^1ov+Q zckBVCZ8GknCyGsZzO+))O@Sy(&O_EU=4w$P2qCM3JQ@tsW^(6LTRG))+aWVvz>{I?pm-!$5pDz~`IOq1e-~lCb4{JvuPYG))klD`tDNw0x#% z5h0=8*(RKU;Z?!rPwCb9rbr2sOx<>KD5RB`^whP)6sQx8nD;x3H9pN(C9SV8A(SJ^ zyWUhFFa!>&qEdYxRBSZO5!%6A(@?oAplto_ipPg11 zJOQZL9nIakItashi?rr)(!r}U2WW&KcHXEw9qe0e!E~0MP|UdNygtA{YX*PDJ@U`E)*0L=F`N68`r5l$n(RzY<;Wq|oJq8YR<#Dlx% zuDs^bdk2~O5RsYEgW-Bx%K->|!`y~m8)A+?pdmG13BUl^yX_Ft4J8qs@oZWH>F^YEB~Tt#?=mNGOKu+Q3lAH3pu9!tZoiD4!Ovh*g6pni7f? z`4Jj8!|bOak(j1uK1;~3s&eyNVifLTGa0k(sorgVLn1M>VYwL<@R(Ij58@j(`W7P% zTw(r#h@-Hfv2gZ+^%{&hqV%O{tIZLTh;W591+0or)UnB&Na%$PW;Z8dEWV+G?>69V z8A8vQ!(fL^vVs6OoaS%=5o4Zi+K@GCZWS0Q-*%4HLH2Zz3sOODh``7 zSTCP|qAvrR=qpFeojIufnd<}*8w#WRONJ^pXam!q&UJ@`jgXPWh%No&hM=Dw!Rb(c&KO1OZkeB#M2ya_NM_w00JRs4 zB7Nsa^ENdLhS&FPzFXhg!dwx;;x$eWM)$Wo;yeIq6HG>WZA^ABp~D}TF@?vC<(ANj zIy2K2#Dd!5Nx1%-&JOijQ4ggraQ-w$u)N8OOI-=0GoUg*t^jl+xqGl(Z%L=+8p~pi zEQ9M(VoPw0<<3@hZ!N>ZK<+cRJdUfQV6aRQ$y4-To!LU5>v{7qD8HEb8jbg}U?gY- zhQx_RwQ^C%h;U_oH6tMNQMXSibW`#cM2&_*0t) zrzQ`@Go`R`*B}oD=ig8N`xz`PLxg2@o%uKBR1wzUwF%bV(mCk+-vS!=H@g5AJAMEdYif4-!YK3_jH+v^pYV<4w{072k73)N(&B73H z=v>P>&5q>>o-Ev1#;^w?nI3s=c@Iw}ZZ08@?mW9Z(}UsVin;7gNpqdm-uV^`$?k-> zFO-Skde`y-^?%ZWt8W(=k6Q|9-{qDA43ilAx}}P4US%0Ckr!ywZ3`M-c(O(y<{L@^ z?Y6;!D5O2m^gT|d?8}M_#Vai?Sh&xkrRO(VFn8faX!^;*^q1v;!~)f4r~jh^2$-|M zcVAn^(z=V5B0?AJvUrHN8~d;yoM$XWwBK&ae&&IxX|yELf%TS`3BA3~QlZ8zchOCQR} z;`Z_d)lr{*p8`!)MxQWkwBQ}fAx?Y=8KB_0jeCCJ8Ot|_1%!o-7KBaLTgG4zo7P>h zAZpi(r4R{t%d(5^xojE8qsOHBuH_o}w;YT#_A`r9Eq1`M^9{Ni$`2q;=C7_#kdJTm z^E}49X+Ap~S4sPSml#WnzqM>9;;Y!+3AlU2a-M#A)6$BiSsuTjVB$)XrIDps2EDC} zg&rSTqG9tUWcI6j7N<_Y2>CP2z!^nLi4~!XuX_dS88!Q2!g>Y=8`yj}sLe8mFdWfN za>}c-rU~rh1=|J4PqEIWUX!&ePu}v{tf=-eIKFMnqx)&T1NLcekoA~Ay3*qgYd$Z& zi)_A+SzXo`sOw?9OM@e=ugjzxZER}|<;WSY%bF!(pgTLo`Z_0mfQ3JVc|EPEP-?Yy zrtWmBhgUu-X)~hD$eiqiL^yeYdyE!lSr73D0(924YENjVd}|boI6u+Fx>7#BbBYHY z3h(*)ShbF~!?2$sHBdF*uLN8H*6H-|Zr0sCG@$SaY?|*kjNL7u`wOi7&|?Ah^ZlYJ z-f2hZ=zi91941KyCt7i&2Uw>o82LXt!a6~`yeY}k6S7BIUndv5qpUc`u2Nx~6@#+Z z@bfe5w~-2^)_e_q)dn{e-#N|ni8ToD++e2OIo7V6h@J%D1~^w+qiF1W>qQ=sdkss_ z%&mP3Jr;Q*+hB@Wxiv->if~poTi>PYtr@i6Q`SA4_!++Z6%5&EeVh(nWo?wiK6qxr z4d=YIm{za1_9B{Z)~h_d(FPTKHvwmKpcR)^bm)2HpW0FHk9r8R&cgzmV%tvwGPyyQ0%j(%tDOde9F zo42c7O%U!^X6>>12* z``$W}F{-Ee5wRzDDE%d>1vq}T;$c~ZpJU07i48+<-}2aZp{cdaDjPErQqmGKFlr&h z`cY>a8%F(2aBxTLV^u3fL~VQ*Bf*1TTWBEoQmY$BR@jbW$j7$Cm)72h`^0(cNIlkT zd2f_wDE1ccfbq}X4kRM>4?kt%8_T-$In!u$c@FN-b-hDYLH}IaK4Js&Wgc_6@@)Ni zoM_JOwrfzgM=e6=FSc$Qa#~36;(!nrgf{Xx0Xy2paF`QWHq$T;nhFtoJhO{!HA{?K zH6WyyuMA)rg;o~Wrb_5%i2EEdDMP1)AXt60q=9W(TNE_)4Y>z#2HUEtZ>&*Je2vE) zai-*l-vr_j(L7VqLC(i~PgwMrEdcU~h6329GMuZhqVibua_)^AoKqI(kO? z;IjVc;rE>-?{sS=OFq}280Fn)!^2D)$UhxrhZon|Ot59XpN(c$+uo80em$$khMASS zZLi?hv*7Lk^qeky&gOyQfhIp_{E^epV|#6#m=hymm+eQ$+h;RC?EzaMR2;HljADmP zK}fO9K#OJc-Dw-18habC1Q~ zXOQ-W4`L&hL4}W*oZG?OPqUh6^^xe=gt~UcpbMgWWK;`QBoppl&5EmfEZl~f3ASUr zKFgJwIy!YsYEo)K8e$OdW1T1FhxdR3i^4;|;mB$M0Uyd*xaS#lCSH7QTg==cn>YI7_Z+^qy}*-PRFq*qRn_DbA=WWBl9t}Gt>f@KR_O`B z{E1)qV!ENpHkl{+P(Db)EctsjH2KlZ}_ z-nV@t3Fx9f*laU3&b0@y(}@|6v|{JABR%M=-`?#niE5c*==fu>XA&xgZ?k;2nXmd! zS7`C4{`jq)<7aFc;E?Rs#8fQZiA};y9Y`-LtE^s$Ok3K7t9eq}a8)0FKMv0hD?Roc+P;nbB#-H#C8Gl|pyRS56s!mmu5fWszrvnI$0yr2;|IE^ zkZ#Ap?E?*;B*wvOpW6ayL6#k}bo&As8-V_!Jo^p&N*5J6uwwmT_+}2La~JG`FxBUX zJT6j%h4)!>bfdCE|X3KI%52*d&B3O%JLrx%;yURW@OmKNF9a?BTmUuYo*tTuS? zy8Qt(h2vIH{i=O7g?{!)92rC#2iYAM78!0IOcxEbFHtbb^We2~JH-FszX0-hzoSrk z*}t03dCR_xz?`x66=WEk_|lH{+c-PgZ^P;KGxich4}R(Y44b6IbbGx$jH6Lg?P(I3 z0mJ6l-xJ5xw)aejxViQT@`yd%Js6od*X(;Po`Vqx+`e0YX`cO#h_DNBBxWn|Hn=#) z-UI4ash87Xz3qtStH@Okpi39p7Z3&{^bh~XfB+6iYe;`SBpy1)+HcS!ZhKD-6GAI4 z*%8{h#EuDiV`y1|UFP7{v`|bwc`a5?9V_fN2qub}H*&>LdYe0dt{P0^9h8BSa(Gp! z0{i!1KCp48{dZcw#-1c%ys+$RbucU%pq}_2me4f(YlehlD(R=K>^cADraOK=@RA*s zyh@x1pSHEff#b4$C~aC|AJ0KfS*QzsbJ<%!^)fqtC`})VAH-X3?}`u{aZ-{i9Y52F zNd?Nf%GZM+cW`JNePy}51vi~;#{<6;xJdj zJ`FL)>GC?q0U3f%+DqiwHT^wv{*2$Ed11CCfa!Jn5OMlS$upaly>4GaM20?@L0fZv z4Tv?r?BA7!yk*Da*qLksEu%Pn@(Dfjjy;XJf;N2SUqRQOu}_i6oI@q!JsjYw>jaJs z;#8>XEGuwy5vtz5Bic~#t&K8N5JJpVG&SwHLxQl z^QZekX^a2G*LT2GS-$=AoP8Jqif8YM8{7jBOij%JZp{H|rRD(BQgeU@O-mCZxzrp` zX6A%cE=MY)=1RrNGIKy%b3oI|@&DcjvtPgWeg7Xn=T^^ip69vueU0z+z4YN!uGc#` z>R!WFh4roBSZM)Q-b0gM@91Eqvu`AY(X-1MYT5K>!^Udi<@z(|(-CXd&08ehK$+m< zQZ6LvEAV%mont8AGKDTrt(uEvo7aP)wfZq)CBIadv;aPbG7usuRoC5y<# z)x|;1R>3uy@}9mSZF^03m#WIu??T8?JO4iBpDF14c{9HKJdR5$Y@nMA6H(So`B{lS zX{d{nNiTE4U>z>?Ii^-q(j>VR>XGgf7=UjEakSNbSEV~kEFG(91+5tlAt>%R|2V{gq0^$|02&%*5$&=!wGG4M`YA_j6QnBE&XgB@VIWd zQWXdhLZL7&4WrcQ@&>{v68)&vYhZ>`(yC_{zzw%EIDxS!^i9Q&7K=-;>nK;o$AO$; zsJhAj=$vqgzfIHGWI*&R+p2>rW1#LkguZfqiKs}PMd#ppIdVf#uz!Q|cj-2RMPa0F zkTCmXHi6gEovwpczLw5a=xlVut4rk3<8&M3VB;z-*A-F8V);>;J*XqhuZ!eiG^&a2 zP0>U5DdByfA`+g}9h25`@ey65ROr{$r~A!yo#_+?pUS@oRq*x6x|A@)iHE#l5fdS( zsYUbFS-Jr-Wf9u-7Fl*C0kSejcS3}uR=m?oYM_(tEh*o`(-C`mS(h$&sMY}k zU#!og*GQKoZ7zJ)pvMV<)o}5wsvh;xBYrV)r4E5HTb^tO^0sgTe3EN)tK`yFnp%V> zj4RRs^uC?qbCMd++`hVOnp2^U_}6S5#_{j!K9<1*)U=`Q9ZsGtFPG@bAYBDD-J$zj zDkH}`O(&P-L4<$gMyE*~fear*K6YYh@nJBu!g2JO^smr8lgjRqZLoLHv&P?F%= z9Iaa}=5I@&=Ev!j)M<=vKCN}>8qkRSx_1O*XRK~Ho$=~`4;!cZ3aZZ`T`Or9CpXe1 zN}N)mLxjd|IKaf*I3ngo870T-Kx=edw;6nGocXbCxuo2N?%GQkt@ZEFt4W5jly^ek zmHVI9g$Y;es7tzHxpEJF@;=8`>JQ8K*jKs)Nq(SxYtH#f|Fp!DzSWUTI!IY>X%VaW zgAP^@Sb*laAXeSbL98mL>c$diT55E&LM0Wi`%{-8Dfi=nhbexqei~KoP&cK#F1mO5 ziF>*|GU*6AeflvHEo-k!qRdxZYO0*vMkRD(HSdx1#VVx+gR!ktD;(eh!02U;a)vKQL*~=oDic2iOorJ z=0zhK@TM*uOl`ZOVLe{jrG6tC;RW;~?{{B2np1=ETmPNjN98p-DzoUJn|wqmmvxOO zDN)yj;(8diQfh=gk~<~oD!^j*sqQ%%<<`FqD9xG2bS>%foBBw4qKytaa(m#3@;v(I z1d8U|AwvsV{)>J&Eo)^MOjA4PAngyQxy6Bmkye(1RU#6_Y&pzd>;rdr_z!&RRacmd; zm%z+W)+*gwFf$n(e5|WJQmYJuzpO3e&U%tqGz|o_MEMCm7pUT{t_5GM(zla%Vw%2M zC3qpW5B%r1#WLmR_~DmS8?LvqbA%oW^d&Ig>3tm4U%v$XIZKZ~>Y@hvbXwj)pF?Lq z)F+VU89n^fA@_6+P2cGV%PU^tm!bu;z?{Kx4lWHI!$J z*ZVb6JzkrsFPAh`TT}r!xjz%2is``I3xF5~xR>(i9N&b33`gv;~vI!I|J^cAdHpuY#83gx%c#dDineY!?C zz7Mn)t=@(FS(7fse#8~+?K0nZvFujdNj(j+^#Zw=XBPR9S*ZLNe zx?0zelYDp&xA<1y7qk3-47!rrlm3=c5N7Px{nz`Bi?<*aRe6J?bNct_c9=fm->+IP z{E5n&B%Rlfp{is<;NjaIyscNr|&$WN3Kq0ixkd-U_<66k(b z>FkBC7$Dm9HW*~w<)FSo0#BIpX?-%yTP|s6*)hFEI7WbwxQG*^@(656*%rNtOUm^F zU`V5L{q@KHd;T!yPivrYsQjtu=A1V}uR<_gwLV&g=wE%y!VLYwvX8{-yu(or!+jac zKG92(^ao|_*DvR|bp5+BPWW8ESgnAge^=e1Ta~H*MKlX$H|1R}zNJUNY1}9JO3E8$ zXv8IHdIdt!zShSol`u_QZ7j~Spwe=G#hd_mLAK2KuRi)+JnS!g9teHTM|q`sU}hio zR+_L#t3q%R#Zmev`l3fKxfWb1K_DYEw&H{OYZTQ{-=4;Im3nj5ulm%0&M|WOf_Ik-3H82bko*lirY0 zXX=-M=*JKx-2#2C80W7(%I+RBC#;K9s>Vy^_5AMyerCEszrX9*WkP`ps+{W;h^e zOm9X9%!-gBb^cz*bom&nnWirg^#*z#ESI$jhBArwjWqNG;D@3!^f=Vh4d1CHn_$6Q z&FjV+o|3~{R=i(7nlR1V&_{no)T8EJeH#Bh$zakdz)O)Y_+SKyh`v4KOOI0}R-v977>9CZU<}F`fpnCU z0*Hc8kC`8UQ&K%TFdXIZz)+NZ1A|d^4h%pU7eKg!x={ci616GN7iCzWHyUy$0ON`3 zmp~7cHG#)b{t)PnvO3TW<;6f(l*a>IP#z2h1`<#f1m<_b zU-^NKC|?aATt>Ax&>m%W0A?Q5+&~+Y&j(;;Q%w)FLOD6m5@mXzIm#h{CMf#_8l&tJ zK+ue;S0EPU;{iX)&H*3F4grMDsM-ZQDB}Wdl(7LPN>9L!G04b`n~%SS=i+hNUb%Rj zwp}hBp^eMMBeb!(c!buOi$`dUxp;)Oel8xN4a;?*ROI48n!C9Uls9tmIL+ByE6Rhp zMwFbZN4YImhw_bFOij(g+y*G;bq9G_bcnMgFlaxoz^{c}T6_RiI!?3$}a z**RBei6WX=PN%8V2Ue04&e35vjMzbc{HFwSrJg7+!sJ4lpH`MlrhEu?bP{w`W2$EBD8N6t#VZKJ;7J6jRxd*Kse#YH~waWN{=>b<+nc;z? z6tk-(r(M=A?ZJqVXqESvht9O5JK`D>m;! z3G}~))-tM?seg>SA2;;Z3b52(NaC{_rtz(FhG?18y6jWKr%J)|f?#ia{{(w38ord2 zV(NFIJh^TX!g{$b^0Qt?va1g8dNgD_NPiA=ZZ#4sB(-Yz~JxvStfPiDONLldYK&~LOJ0N!!f|y zc#6{uJl55XmRLS`-_T8xy7CmS`5B3ym5eK8Qa75?$P5idVZ17rx&!X1X+)XNTY6Ge zGxG*=^sqMJZdxM;DfXQQR-;TQwvXOa zJY9(hS%>kkR-mjlwWh3V#x68zSSJlPZfryl#1myrjpvn0;Hg&j5EpyUyM}u8IIgAf ztfcIVXQXkwWcgF#`)!O$8KO_u|0XQ7?TxU|4xk-c3lIqJIE)Ol7#KfQlv?b^KAfu^pKCxb)lbegJS? zHhv!bo|2z29!7vnX<|csy<;50r0|T@!S{_ODbn=d(bI?1f#sb%6cuWUVLbAn@TZKP zVf;%u@x(KMW<2_NV?8M}qkLkZDa#t}Qmh$D6!MaQUPsHVL!nj2*s5x5OI?LHHcEzI&2XD+n{ zf~|a-6^o5nvCmL(Z}T(!>{8=Oxip^7=`1az!d}Lne5lr(0zo9q{FvyJ34E@%5mxMX z!p+a}@)brzls`*nHX8w?SYw0>rzKoCqeQ)j3S+YoJDe&jZ(<-GUk`oswN89Jb@7k@ zFktyD>%NA2A`IJ}YRXc7paq|M*Vr84TO18TEe8XUh0ov~q;}JxUD?SzN zp3Tu2#w)Nx$xTN7>!ML30sB^cE!-|+4AbS{C_-+YW(0%{Uo_xrS<%8MJi$1<>>J}G zrJ@ZO`-0*MW3u*>DIqGT=5tD%Nh9O%TgE4YfT_(EQOjqjWEt=(+qS`#iKXRZ^>4;I zk`e-lYC%E9cS-+HNBqzE8{}tG1?Yg^WT4DXjGg((yT(m&DOb!=E9LzV{>1J{CPEB3 zpBrg}=N14>J7e;04>n;Dm%iMoANI}>#{HtLnOw7^lR(6Ws9xgyiP29;qPaB)Opi2G zDZm8ch%w!eluP#a3$){Co#|E(W2btVor8>U*r#p{i{@8##_-@1ULof&qnXP6CLedR zm@uuE6%EoJ7}^*i$xXUYrK-VNTc zZfDj!BwJFnt!?&2Cmq6w>PdEJ_ zm)_+31%5lo+vk}Uao*EOone0-o79wIUPN=>0>zGh7w?&4dO{d8qff^G-uFbuW|a7; zA%>sMHkE5siP(DJ1^j!uHsno9OhaT6l`S>>t`M-ZH41TY43Ek;!KShiO?bDU82-5+ zcSc#;hzzuiOrJ`d!4qM^3fn@Je;SYhYK^I(FjrRIH-K=p$aE4gGpdvs5l^wfv|lUo zCw)I1oJZy-gJ%!z?H%FcS=&vEf+#js37vCWno!N?BsCSyGLGZbC8h}?Zt!NJ8To>C znUF8&J?h=jXruKF!B5#-W2upmt(*A^(aAfhcM`CxC%T*4NrgM3UZ? zA<`LT86Z*LC(Q3r(T`>u`#&&Ul3dLJ~crt+egJ4wJ8*J-jvO^ zi=rC>VRE-2h(R82-;wbp2=!6Z{gi)A=cM_6G#o(XMwn=_zme6W%+F0p{M99s6VN!S z2)H^^`diuv@jlow4^WmKhPjfnrc`oVGlAXlJpNU6ui<%NBWwgCZ?)-A*$<|fN@X$z z_m8^ho}$sv_|IR`Mf22+)`if1`x}MasoH@LjG-P&e(-!@oG)VqCXNujz{7try^GWX z6ty)9aLhX<5S$(cg21wkOYfTc%cUc`6=n?iQO;RvoCHjZ+FT%gNI7}N6MR}@1~TRt z=e&+Xj?yibkNKMj^D>pP3+nVz-NHnj8Z$CvER;e|oSYI!!Ic%G#GD9*+SL>1zd@M% zREO|6Nofz{P_nx6gu;)()MK6NrwT$u+lCmb%!+8{EhK~C&zq-*1V>9~F2xhzz)7;>bKPi)0kcVaJPE$VJ z{>7vM9?61e%SM(NJS)lkvLt=FaZ42H`H~g9Z7Jr(GDHhzzHBvt>9(60DB{olp0YaL z-orddj)=j(=jj*NCSJELKr)?v=0(y4x}q}Y^0E(2&qFHgu8;T9yMEK)a!(KGSm~*m&fM>9>Zu*abZjf zVVFh>RzTGy&e~%h0Ve9P=C_cw;LvHaknWx`|EU6rpY*kQ8S2^C!KZp!fXl9#Y;GWv zs;ILn3{*0i<}DflseJ#TQoq97mDUNG{5iAC0h#g&p8hqZuw_31<<*h@m}3qF(vRa- znlfSZ8r!K5RKez^LV(tC=LO~?!VDU>#&nP{sw3IH&-ZtmRB(U|_!%6`Sw;;l{=3UW4w>fTxrMtwvwwVVBex;(_ z=Fu|cHFU^NjPWRwai1~f1`?g#Z*B|gWyLkEl{Sqv4;9^?BIA7r&8t)bT?#~-bC=~M zF_Mm8?gQ0tTs+deQ(``0?k$sk|K~Za3eTGDw0W#~uz1MpYE>_c^@irb`>E=@dAH_| z)q?{)g$bs(gJ~F-95n;`j~e}o&!1#_$NHI=^KJy8$^cx}=2)-tgV})m34s*mq7c3Oe-HruM2|G>wQ{&(Bz>9$;MFLRnO8Jv7T)#*Z7(1<~5S^fDUvuzF647 z(t@|&Fb|Z=)FD8>o8$PP&eG+PaQm1{rUpG>n7N$#_Awp|E(~Nauz&!%&+q0kNyI6d zVZKyQ@hL|G%062!g6%#_HTVd=H0S&;%~GPhY9(WNW1h^vir;+M-iUyi|7;W@F+yNf z%v@~ElhfM$rXEyz*90{+M6DL0`Aaew#f^#nA?fpg#KH8FXz5yi$$sGjj?^L$D^_B1Yukqe$*9s z0^h$}XIxL;W$7Wt{k`DOilbpWMq3T@gE|5UUo2B3p60dW%cOe$S0~J7jO|jfF4U2n zwa5y!eJz_Fqf>WGFY%~tQEvq=MRC^4!As}?Mh}dTs9h_|M^ZFNJEIE3A#JG}R>rUb zupEEb0+taXv^}epQQ1WzhJxUJ^i{)*p7Jwy<6n)jSf>#Z+G!L?E z!L=ab?$OE9SCnf8(}g!IMr^Wb^Ok9WK8ldf>%wKLcAJwBB_xlaVD^yWQI=7X5{4|i zps2ZZ3-3s`43r~stzw=T9`>lGEHEqBsA7TncdE&?gtK~r1>|FPu2^Jlfz!RnWF*H1 z2sfjbSPI43Q9C$iQYSOWQnM`BE1gvFs#(w0?Uo#glVhzLP`+R}EKIAJ5n)B*b!osJ zU9`Xj7ozsCqkb5cH(tAhN^YrB_-u})wM_C-^qZC&z}r|Fh-{yclPt9YuvB-GpQ1Ng z;Cx+bfzT4m(c3M{9=?k3s&Z;E(-Kzpy5*!w*$+u3gS*epzdOjUgv}4muX75`AiVZh z!!dTOvjA7rl%h476_k3%@FU?In8o*sEh-rdP|@KUxJ@@$;5Kc}(NUUMoRJF>+7Ys$ z7-_A~+?>{TiP*^_H(NGJFi{;^YW{?hPoWxrAN28}Gwv$^GPCTm?AOGXX#xPH-v~m= zIcm4%whU>lL)0xfzPVNK)9tlPs{GB+jLN^XAix4lb_@`3Z;{|L z{}~HlXyul#t(;(&>pqCTW8?imx5Dm}E#ekv|myqNjZ3Hc%NJ${;5tq@9Sq zZn;UFRK^Zel4p5U$RIKBy7#amXeb02>i8B)1B7ZPf{@O5OjO>?A4z$=^i8OrA-Wz_ zt&`d4bT7oJp4EbjP=C&HLj@RQ^iK_t+bhQU9-p~jX@g)-&RS{0oPpfr;z5@!+hwqW zWvwyIt&^At64TpU_LZfNU^SE`b^?^;8%sc##IiP+K-lnuMX6B@z;2S=SjcVyZKF=! zT`$ldSyUWi-`UCOHcbi;R&mGb@PauPn?q$h-x_Ab$s#i@rX6nU zK;#;AE;VQ2oBy0;3uwxAiwwM9wCzr+shH&LbE0V$t!~aJh;?#8ze3E&!J|NRm`uwzSbf^8{KUX0Ou>1g zHC;*ewl!NbHLqo0BzU6gPN$;hjT$=pw$-Jbd}>TU1W-_&=m6V_mLJ<_eP2?JMe_v% zq!rRrCZ(4c!Pr=0T?88*@ZRFf=~Q#xv6{epIf05!IBXPm!ZC&?OpHL5q^}bP($Gl} z?J50^L&4h*Tdyjlbbj@?HA}914iA2T;yXJ}{llCola*CkoeJd`@R$mYxET6?n%=k&3B+iOz6@*Bc6tbVDWbQ3U5 z;kLtq6*4;3=906of$g#aaKv+aZB1mzP&y56d$Vl}fQZ#&?RF}6D;iVs08@E^ZL*3R zSZy^jY3a_=$$E*+PTLPaB^LB+Qd)53=E1+sS5R@U`)rW!UoGg@q7;Cbh6XN=vOO&; zxOMlmsKxSvtjdv;S7{3^7@dBIESdGS+^U()s)lJft+g#rrpezsJMfGm z?SnQz(OHF|OYnr(kej|`2Qv4zA!K!Cn{e*X-gZ)itj4`$%jXrzwlb|q__uK&`Z9-7 z->_);VlSIl5@19%QDw3%hF~jOMRQYZ9r?%JHaL4%7R0BdQ(OndNvf>2}$ z=`X`4>k$`0RBKV!#G{&}1&O28NM zJ4S{)fgvIrVak5Vk)1c*3;SH>aiR*D5fVy zeufP+3U6}peK;mqJP zA{V7Co|DUMx21x9UO){()Y~aiWt+D1vezvum^$Si&FODPKuCxhA**XTA#Q7{?Ytlj ztDS836?8gSN~13UELAcVTi-(i>SkHlF54aj(%2&xRFH`#u09ftfv-f>I#`FTc-l^? zm~Xch)XWU$X7AfRlS{jHru;BcDu|nRhH6#{Z1`|fKkB>5Hh_Qlz-ERSu)yJG-haf_ zLs$XNxq*GpJSx`~T+Ld;7mnMo-7=l?az#PO*5ZQnKfmPzr){hfAsAl-YeL!c z><+&0sm(1@?kyb`m|Xa|?STl6RqvzPbURW&U9deT2*hf~*#S1aWIG{Oy+5vPeB4-$ z=x-kmiRXwsil3{-%8?ytRZ!a0MKvGF*VDE_F9^#ypb~$WrdV3zFZnqDh z4K#d|(xGREeG&RlbDRql(Wi$KZB3edEkrV1GsiuBoBx34r zLi+6<_OSvkq5fdioWN8#ZRb$RJbRsmi29j2hf?Zq_7}zQ9>nJ`m5ybViIq2#*ymaa!dz;-w_OUP-Qq~)t8WfZd)Urci z|5~p4Fs@x(E6{@vvOmXjH1?NZJEWph^2hk`aQpje;3B6w<==`8TM${DPTCX{CJ-|E zp1l%=_@n{sH`*@=7O~=3c?X(rwV&tuRy&ehe?&PtJ3OHdJ21qjsdlh@FhxD-8qT{s z_EAAHr?2Xw>@jB8m6pjv5f}!e-MVJ>uG;gZZ31Y;S5rhs0NDATS5qS3Z&#mvFE#+U z(KiDh{UVC0TG;E;uom`>%H#M&CB-dhgjw{khu@bK)JzDc=5ZzyC5^FP6@7G$;x5_- z(V<*>1f_aoT2hJJrl95rIr!#x;n*5ZcpLaS@tC|shZASw;?oc zx;<1Y5`tFGrkro=%fm!pfhvr6O~`@cL%0(;yn8YlJZMj`i7$aXb*RFw*5Nt zeiT%^Xr(^y%eT<{vGyFQEbP=%^fch68y4BY8TDO3aeoAxEw&$*OWzk%WWtoN)DBa^ z4_rRQo($~4ceZ%!|Kavmq_WqM&_}w?)%WCP8E5`!e@ddwKjMi_Sv`5jKpsc;Ff+H_x(vJf7uU#s!$Wd$?w^_K;65V1VV+2_Pvty7gbB0ffj$uu3_-v zMQFeuKKC2@0=e=J+Q(ce!0D?Qy7ykQ6_fJd-z&*_Uzd+1x_1yJ02hmspv1; zh~4Rq0b&AZBFynA(kwb92VAt3UeGe)i9%)7XxOO~Uq&>yC$gh~<7+|K z9#~a-5YAwmh|&<2io8;<&9=zKCQ@$Z9&-@sQM^MN7xqyv*yECQ}KF zR6P(1w6QgTiszW6736R4)>*akCOcZ!c@S^|B3uz%sEMYO>5Z(M*voMkb9qZAOIce7 z+E#G{8xvISbD*!>rN5h^hY#!Mfr>0{V-$-!0q*j7Q?(}p~o!L}x zv|}%C>E=MBj{tM&xT>4OB4bA{2kb$5$}va5tJ%a6&hfn+h?@h((We+lSWyTTUfxj+ ztDGZfm5cuVgB=EN(sAA>$JC&?O=IC+JssU7(NS2yH52NGQPcvvwXiM3 zf_r0tGmxz0obJHBGtS}Bf=|BBWXF3lr5QDI(41C|Y}z!{F_5y`I3jpRPe*%+t$zc?2T&4j#-YcWw64feH;ePf;kT4 zr@~S9D`>kWJ7$vYsG}84T<9oKdbjk<4PpBtN0p=$XgD8VZW}c3EpfoS=jUo&C+sAa zcO2i){9}#?`tpyCTeT;z_>WbNP#Lnjrrqt>oO_fxd=d{`>)4`#0q)GN9f!k@ z>5bwk8yqWOg`+cnblfXm31z3b%%`H$e|3!Jn9Yvc!T=Yq@EPgWy^eDpz5F_@EpTK& zDfGtDT(>V&yuX_2t#JG(rgR*ilRE8`Xe&7eipt!PqLqqC!#Q`zp08(tj!e4yEF-op%il?^a$i~iA7-uPI7MGlG zoKZ<~o?q$6mMh!fatCsDcE4f5XTU4qeoS{j8J6&)*&?*(c z{3cLN?ZXws$l>4@o0bMJGfg5_pR}5B`+3KoT=J{qdm+DQlEAQ)1e()Rj%Z5ntyWRg zZ;7Kh?v7)TjO{-&-u87_}{AE6CI#@P731|QO-}*QZLFs zVI9C#^^+`8;VJ7~q^o!22OIeW<$UhwLNoiCoV={9^Em`v%(T|$8&>BqwNeWwQx7OC zPY`S<5&Wjt*-+B-S=~Ap;WWGIg>Wq{t2jHboQbvrsKMx+^+$Ivo{++ORBIV8W zJE-Jwmx_vhaoPCKbf;D%lg){7{YR^5p_(}{avw~n(qDqD6qerQz&au*1PP^biv|sW?*E)K7zH=?CD`}TJINU1xX$@5Mu^TX| z5Y1DZR^@@&A$gX2H#&0>RwB$|8Na!o6&ah;xO}nmF#%P}_|yF^M@@5QN=A$J^95$Z~(p@kqm$hLXwacZG+ha{|Wnu&DoZFep&Ug>-h ziA+Fq=|*(=8s{GH!g1+2XA+`7-gF`gWHOhYcY5G(aJf@>!rRW(GVs7DHaUewi>t4Q z%)0eUolq*WsA8(SGu51Q|HN%}JI@HVx6(82DLieDGhZY)DgD@OgIu%6HI5e@aL$#% z_*J^!)sKqeyf?UWg|og^0TcIkB6O;dJwDoTCMJUSpLBMWlv7dR7r46A2_t4?TUhkV zx4PHUXN_^LqI1(Jd4g*g-$VeOB+aB8^2c$}T<3>SzY^VmnA(TCGpR=B8ZSC{7Uite z`l+OiYceHIb(M38!G+zs;9o6fV1j{;$d5G>3byj1T@GkAJ>U0R^jFjp5Ls4S|8&yB{{3&x4Y%@+HMmaeySx1fRH zE|X}G2orcocq1(%QzF4oG9{%9V*~EaCW;F2EN4)xv;a zj*D;WD(3Q@&LlVxT4^5V@?V{w1^38T04dZ=r=r>JBHn(-`9ttI$Ue0exntv!ToCpu z@2Vim&2rg;mzI(Ag;oR(yWUbu%el0TyC00lB3FCKVNDla*WC4#B;|2w2e$^x>kH3$ zlozppMfLKjG||1SZlYvga@}QXTNebB*C_rCZ40VOanIuW6Pz+oAFgos58hG0`ElMf z%tpVvmf|Lb!QdF{ZY%Mg&aNjEh%v6MaYbT)+q)yk@wn@2Fv(RHqNQ8*Z6v07c7h`>D9%=B{{=p#zM}mR%i~;3bg=K%QS|4Y#gsk+3?6?ETq|LcD~WL(5xZss{&JElR-o>puX=7j z+OnDk$BZy$_o(wir-@fgb4`(?Vv1_30oO~1gd%d3J16pJ$-O-IqBlADI~QEhsV-Lu zbmd_hFfx4TY(a%Dx+cSJSG&NS#WUu(Kt#Gh1jU1%DBJbD2sAzviS4IbuInHIjRA^@ zev*=3Z}%Eie$pw9sw&}iJ$p;qgp5IvH2PDiDK~oAbx{dhUCvTXeHo4Z1Pn$PhAK)u z<6KMWk2%MQQGb^!<~ZM${j*Cn65np-ifreA;0TobvpIYyE^Xn8Tqn-|ICBDA8DjmV zu5;~_0O_k(?5v@Q``yTKImiXj0}cfu493K=4KAfp2_w{x$&68O;sDrZcTikYSE*P` z@8E+3w%<92WAi9=zJ!fvt_NUHgS#)K>~MVmYhQd%O)a;W=sJh-#%L6N>4IV(qA8{L zKAJ)#(sB(1mx7p^HD#^_A{aU44c8&cE^yE1E_+>It=!4^e>kT?YDsg3@w|hsg#z%G ze@}ds+|>OG#eHv$q*Dp?V<_Y*an~rVN{@YFrRASy3G}jNJ3f0k`W)zwkhR)1gqA(-dhSubw-8m|N2Qrq3%7f? zfG`mf0z*Q0FSxAsbIl&-7}yGaacYB`a=y6XeJ-8p*5IU^?t%q4!wtr=5X}KDo#XaO z|K00+>U-By3dzXvH(cS8_Rwxut^r=x9{*G`*!wd?UH9Nq&Karh^>ShSgKsB9Q$ZO6 z-8qj|K^)wl$29fDl@B;$sQVzGP!GHOda%ZaIb(zy3s!7i|6T~s;?Qhcck2;KndD3n z+n5Q#canPpa_(~0WoI85Ctq?;lEnVp9WX)1bY}`hsTE#|Z02rC@6K_}siWL5YTegm zF#lD9C<4Lvu6X!RZ|Pp4Yj+x66C4pS^jN@kfCMCxQtvm6OAmYWA&G4&Urd_!C6fhp#JRGgw;fqzq^4c zU*g;>E`LVnE;+l1#j^mrbZc)Mr8-@0INjw!zy7_8g_ZSo%N5cEE`8Vagp4a^I!l9Z z{Q}vbU6tgB_P|g1P%aG$g+aOHHsLI~MdOFK-+-~O_6ryG7xe!eUOwDCU6Q`!+RMSO z-gg#K(QnXoFajNe&%MO8)#6g+Ko?AB-?*ZK-(Ke0Z(SF$u3WCc-1=$vS*3Kfpn8Iy z9ks5GG9EO=ykVfE-n9g0 zFtCOxbDb&~k2vGrP_8=i1CLhy;7M^zZ62GYtOQ^o|pAAjD z?Jl93%bqOJD5v;QEv22(AhY!@_ou=NiB+_gzCZ7NjgJ26zRNB4xxuIVvsg!Z-gd_g zpWBo}?iH{$X4C`aUixKs5%7&4xO*yLRIJ_VN|kZZHTTxw$p20$V>L$3zToN{?Cf_W z=??PIxZE`Lwmbh(ErMP9N^l|lLA85aX`H;!nJ3ZuJ?_`3Xu1n02jQ% zf)ioZzk6+Su(l7#dE0rFN^YyPTy)x<4nQfSAJ|~s@5!_wbnYjWja@bFD!B{?K}vIP zA|;y=ehPLKqpOl+YFT_-%ec6BK6t_1MJ{IHF7&TfPAP4@0djnyO?))^QLMS=cBw^V z-~B0sEh!W3R?Yt$>Bq(915>3|#VO;%L3C_yqskb^8t4?db+Cx%lXZ=LRlM1k+VF;GNs!Y+a|*~O!iMw26|Q4_FXX(w@Vb~sEmq86Ol#RCR`h7|v7 z_*u^06WdNkp^tn1kaQG3RebY)EL4#(y*$Oj6e+dv9ND4tl;}&VrAG6<^B?iGB=t}S665og5=uswPfS?Gd?Ah z(ucXCAMm_Qo*98cIh)9%NB`T>C3mAHA{35O3arK;4i@?iU&p)cnO$dC+S~Vd@ zbB-8Q5is1C&n0@A$R4GL-W{w}6OJAcdmAa4yK5s5aI0{vF84HkN@}r)(7@R=}_|N~FLphCz{2;qc_S>UYO01~MBuB)p! zAsrHrulBql0|qdv+zF@kH=Yv$1DG>Z^Lg-m1|*EF|7{lQ$)&>IM)(AT#b6|KOR3Z!n;Za z>*w;e^*aNZ(ZR$cqr4jh1!6z7xAfnSDpCnUxy3tCnf6}u+{RpP@%EHdPvjJMa~pA_ z4V@tZkhQ(Rm*jedqW0NSg{HKRS2?{CWl|q1zUIcM;PF<=rM?tDP6OX#tQWq=epIUq zhqBnz3uQ4C$?2SM_Q!e8su7ACO0^pqjq zA63fcz>dwK;!=5CTrjO)>Wu)LSvxYnV{S8m$m((nSCt|)y@(qY*T;)p_Q||axlIul znk??^LNzLthE^^08pU!Miq8njyWw?m_IU3`iBDyC+pB=vD{0@zOsO}#8RGkZNI{o6 z&kJR`vU8H2Pi1*8s}u+-Eq%zBQVN=#=BvE1;&o$I^v;C>w#6b|_XNK%%ez>n9E}TO zDSEtj3fpFT;RwUipwf<`=t)l?9Of?0!O>0MKcObS`9rQ+#P4D}PFaGKEA*0(qZHPzmgGKVlnnLr+pU&-o z_vZ8szE||2BS?8JChy|?k-6;^A-_GO`FuDQ4hAK9OG%TH=g&omMNysgFnbibJo|#z zCL(#YFi;vB2oH=GJ6%BZF(Pgr^>8Ce%B7t56#(PkflJ-~dq+fp9P~m0y;ajOj7EDJ z_#gE;e!Gy+?_(+bm>1fU(>II0)cP88Go=qu^F4f(G)!jgh`8ex z=8kVag#S(c$^WkM_f)hD5Y<@x&j|@G zfm*3M0mz)|X~z4qd|=cmrnD!t;MAY)+bmbU8Q;2PTS}RrP2j8dVp|GP3L)PsX8EQF z6ktZNAK78&_&!vNWEVdT6*GS%UCQ;1*S>u~pNnYdKU)UB|yZ^K2zh^XKEYN^-?!M0>>JC>wl%dQ>|??O;mF_74+Xw}p~jjk;0h)r5GSy~ejhjfmik(Q-Sbf31n;&i(y` z63zP-dU8o<_-RC1`EsT0Ts+AK4Lc|;Ma}SkLDo+Gb`-hA_X~&(i!*(2if#45DON%` zO8-05Z?(S-SC#nO2o*-sdEe_A1wxo_KIFum2w%y)_kA+Cax1E{iz{0DK*gC}?K8;e zyTd*!Vt|XchJk?hhz~%5GRlweAouBU--npH(sFngF=Sr@pBgWis45BSeo+;=z}FLnO`qea&+b^$ckdyigQZ-`|W} zSACb2J5iVSIb*VaJ#3U>s>M-kC6#Yam?}E$0B2w8OunBM4L z0h%ki>>nuOEr0rkN{H;u>EP?j{qKq;a)fdceJyvl^`914z2dBJH)l8ZbOW)X(hp+A z4>{+q&@*c8hZXQDt$%<`M* z!<5bbnRN9rt%X+_{Ljh(+p8GhYeDHh0(h2c@nZ>pL`m-04=DM$@V0a)5C;GF8Q*7* zbeFkY)IGLsu-&J*dU0$D^v$up_B_|;UnC)rS1R^DLpz$prcwHG&uiSbkw09c=px8t zkpniFqWbvT7CK>jp1L6n4eJAyMTjBC@j1uQfB04hS92Dl%D+ee-9^p!)Npz?&t~|$ z5`FWWD@yUJnyIE>j~%)&)Ueh?^sN>ML)5E7H@cgdyml(JCUziu^fO_~P+#20IP z?^4`aZTGSs{*M&kfH~dU-%>945VaTJ=J1`zX0zN6l_Eq7AnAng6i$|6A++Z`8=fh& z&kv-?b|5Woi9fC4fIie-r0VY429&-pb_ahr)SoDqzND=ATF8wf{E! z*Qk}h!MLcV>-C*Q~4 z>Ct_JE_<4`KkH8uB#YXs594_z`EdqTQS@Qod5%*wdJXgS7__FTT|I4rLj~j8-q=t0 z^XdMkg6vQ+(~qs=YmRRkz5&bdZY(5?qLvyPUz_JgAUAApUO$X|*?z3%Zz-*z!%DsC z`P1phpW#+sm+J?X<9n)pSKFWRd|HI4Rro%{Xe{?120Sn0&jjdEneF`}gSz82N^9&% z7320fmp|_Zau4ffMX*xWIYX8R&|}m_&sN6%u}cDCuXeY~#HlSk&pzDE#Q1fS%8LB3 zdETru)>f7KH^Kkt*Mi9XWe7oH&~CA0jK!gv{*CVgoND3WFNtq&bJlBO=g%(sR}~0V zP>Jg`oK=YH?8uFM0-1JK`(Ypn(bjU-YH?TGl~_Q7T6x|e?5jVBD?f48I&q~$0)4ZX zQk=cde^DudfNwxs{qF&uBMWO8oGib<6b-eemHRQV`!o_W{sun3B6M35Yy`xT%B9+V z>?nixi$>0gbyM?W{_`L;%ySuYu0n974u2pD9B;aM}US5Sn80YxuY|)+FQQJv?!ec=KQh^g$J# z;Je{Cx;-sCz*X1%ZV?ikcGUCwqY5m7@Uo&N8>6^lURPuBj4-^hci7yo-w1dY_l&6&1T zdxp@d*XisCZ5-u&r}>!9z8DtI*^_*Azfp2=xTh(+bE?=+Bng4}QJz`U+~!XzT0|uHqz$8al{cJGJ>!pwId?D0p3OA(bn?ayE|;1#u^w>V*fT!uJd z{@E_Y3F8%pnb2r_Oe`Jr!8K+Z77MZmGgVIrk7o$DRp_E~u=-XS{p%=iP&netLXdc? zdUE*xW9&`fs;svEVV-k1%%k9p%0U#)z~MRb04U^`Bb3b!U}{#HU}|L!U>s9R1*c_Y z3TBp-3Z*W~2}(`P6q^UVVrkdP6mFB1*8AQ2plR3pd;fkuKKiUZt-YRo_S$RhH4yAs zanx^G9@(GI7pAusYQM7C;py8nIq0YF*z{e(7g)Kc$D85=_JQPuL#SqJcPKuCkf5|M z#n4S0yDH%FpuL47C#F-V4tEm2MqIz51)f#~dw+!ft=r3Ct?_d}c)M;gV%hH%b3xp_ zh!m>6JsiQwA59;Qj3!h+GZp#eW~4(Sl1lZJ;cp{RO<4GPQ>wzH*=bNLUHB}$o7g-e zx^da?v|q54j&$G8&`9=Dp!F*mpWazZ8(&ICER@~ZPlJb4 zrYiki5xK7Nao7=Ki<*g}xjhw^MJ>A8Hw7ZM3-#Z@)YTFJq!=-3LuuRM^Z``(O*(GO zCd349doBH?fCuvpwukkCPU-k-g=LDdGdKL1rYdr)!x<0uOU5W-G_1RfwY&! z4Ig4DTZiE&bK$Nu1dq+trqx}e$Zcws3g5k;2nQ>lB>sg{_!jJbbk{rSuP9DdzDVqg zI1>BP!R+&K36F$#|KtU$VL@fmCsOYE$gKi56@NXf+~0j1X1^`{Rzb?X8pX@ip0p(j zulP89qDJx2sU_VX~6M3F1sY&EBSl@8~|9FG5` zdxFAk&!o2xRS+=lT>25M&W+J>xnytJv$)9HkhYxuD(ap@g-=9PQNzE(;>E=BsO)n0 zCDgy5`*>DgO9L-0E=?aQKPw_Y9Zvh2@|T5&L*hzLfKm|dapAKS`s1$d&vWvh>0gT& zddF@Hhy8uw`0l4L8bD=FM)l_Y?F>7RLYC^cMmi`t zA!;QzBpGnwIEeHs!XQ#48xDkt>|@_c$t)j7QbVmq*AGJsrP2%CN=ZEv)`{wN8X^$D z1bUm~DWOp`sn=5e~{QlPg9T9#eSGAVY}!v4|wJhD8y<@Fm_VQ<(850}KX5cMDqFLG>dI{Y704 zB%L*sgayNtye4p7J~Gn~Pc`?$drV{wijkRvBJN%tSq@gQGay`5M8?I!#(T~uVc*8V=2g|Rc^=#SMKJa3k@|I;kX(x9=1LP*!ol%_R`PDHcZmM!mz&3*i%cFe@B+PBc)qFDJ4QT-TWA+`I}kVl2L z7$dpnWy7snWfE1sVnk@o*9;d!6?i>wG2n;vAnH%1%E#KYyJpBcw;Ns+riby}5-(ul zAK(Lsey4D}ArZa+HQyMH^PYDM19ibja0Ra{k-@d_kl`_b@a*te)ve31wY+V)ZeDkb zO=5a=oAQx#>X2cBZd%od@)5N7Jwvfj*+o1;wFeE4Ntoy#Y+1%cq9Z!7ww#xQHci-0*}J4!LFBjI(i^Zpn()NXh>} z%34Gzm?`EkGteFpn+z)V`o>VFgWUM-a0Fq;MW%H1M=w z$;K_SwRgte?(ZEG6nm@xCOilF^MYFb z{5L$dzaM)`Y_7ito*DieJk$J$%p05J$BjU2q93G;jrQAvf@1#kBZOIb6P};?anTg>i9a2IZDT(6r{Sf+-yP3A{%&|~^>@YdHGeXmFZny;x!T_e z&n14iE5t1HgM-IB>2HVU96#dc#!U6M!E=Hi7mzXc`Egc>DfGwTIoyvhwlTx}F?ja% zLtKpU`Eg2!ar$vci!uALNM^@$_v1b-ri)*PX9quG$HcVpYw(QoD|klwL5&!te0ES! z^dIFj@w`}$=*!VRmOqZ?$nxoUMwTO(V$_@Eup*5bS3VWb#PTUYL6P<4590$LET4?$ z&T^Q?M!Zoz0nbh4P?ATiEgz5P>}Sfy;h)9jWAJ>cd^DbO%VGK(F}1u9&*Jjocs@{m z51!-7@5Xa4;j4H>~L&RO>ci?$j`E7U>ln=vmaQUrx_AM8CV~_FzJYD7acxILl z#WT5F?6U332jLl4J`hi3cKHBE0za1b$MZyaUpzl4?}g{la;%2%cgwLF!rv;-#q;%Y zeD&~k<@oABu5x_!|2uH6h?t1^Z`X_OZYFaT^ia z>NL65If6{*~d=zWA?Gr{Fr@gv41e06a1KW?07$>9XrZ@GZs$l2!B63hx;-6 z*t`9GK$iKOx6J_6Xl`5J_LcCzRSvPpjN>F2)G|hwj|~$0$xTu$JR@RqcCdvB>@+7K@qE{ z;H$7?3YlyyvtiZCeWouq4`Pq7^ZYzp@OXz@IyM==`Qt_{b zMbwaQG}G)2hGCRnUMfj4y^Fl4XI*xET>dMi@`gnf}Hy+MHl45d3TiZF$Z(RC$#Pd`2BY-#u^q zT?2#If+0p6kE@J09&ZF!Ha1c6tHv=iytH=9I%nfBmfMaEaij-!?w1=xQyfkRs=K5gt8R=?I({*YJzd5O_E{Ml(xEo|4ii1LX@;Sta)UW0F4S_<7=Zye2s zzA-u#k=`d_H#c82{tUI$xTrQ9`<)TmSfuf}WXzO}b%5zjZOA^E?+ejqa=Vin)?UYsU=uFT z-{s&a6B-}j;5gHu2xT%AbTB;-lH^~QR1TL9{9kwIncVVX7)6_o>h=S{_elAm9U?X) znZAL}n&WFTF2HrG(H1y+y-)F5Gd6NUaOO}cD;(kYH)VyhEftP({4NPN*8*tZ`1%aU zFI{Y=(~1IR^gdbqQ%ms&aLNLJx?Hrar$0Jm;>Q&f@gc{*pMkV7iJdd0>1V`86n`uO zm;1RUT<*i#^CeRz9UbVf^5SD^3C=#ns!9HoVCng)C>rXpQsFGs!0KC8#EYD)b|iI> zDTbR2nbT#(Pmvwe+Lwz@WK_V?=5|wSs(an^`HF!ixJL~&*%65H*c<4m=2V7>a@iDx zy*(pC!*q*jf}%i^&0>^$FiOontDb94sW_Rdr7c)|LC1EWylJ^PfpZ62>R@tyF5`DG zu`iK_2qpOiCd4tDX}=vI$LAu^8qQ`WWQc7twc*@(YDbCTajLq{QAkalO&NT4zKRQ- z{Ew{VlyR@=6XgUcV>P&<9bESCFk~`q`^{5GMUtd%GIu%GqW0L&EEE8^gPa`>@7K!cl5s5^pQEam1TqIzFF#*p8Yi6D%kEe8RQkS}$-~x};3RXNtOFhC{O49H ztc=xurbyZ|NzLQ@?bf-Hz!y00e)}GnmTYt6NI(}LCJKsZ z=DZSFb9YP4|8U+^33$5&@FV9vCYz4e1hcw2(yHxi0eCCj*lY9DQWNU)egsaugbvjv5_O+ox(ADPuH?_A*yiF&H%=dNMC=+WZtTpKh(ni z)Vkb)l-`{}%-=vU&9#r&zJ?K*17n;IH@6lV>DrmLp@@9B#!^NV&s#cjRjExXwIjo* za;>E`k9gny0_fVo*;|es&b5Bo;3YIz-4CID8rzsblnAb!FW_-U?OoZJ;#?qWN8+-@ zhJepWW(0hW;@V2t^bIu4KV!mJ%UZ^YrrPDUvApq=eJeDp-OXndB?g*UkZsl(`y6~l zRdw;m2sKIe8cX`Q)?E7QdD}^TK1w|yj-`=t)Ldvmuyva`n3GjA_KJ9_eB0vVie@_w zs>MB=(i^Ffbf3tU{#$L{3YW&mQfj9kTzXeVE zwlq!TJ+=7FQRz+QMD94i`I2nanrd%$Vt?u_#%x1HH=7e_QJOj#mH<{981tP48vbj5 zIarvO#_vK{-=L z{LcO6T!fmup0`CN|AV(h>i@ypB2%vLw&`Z@wyyu+ZIP)r;B5#Ud_8ZA?B2rL=9$6U z(*7rJi%kE22XA}U4Blq?zj<3Ebj>&BZIK!O&%7-X{u4LkZLgX? zfh}r)x4kCT4&qGwgSXX~!P~6Y@HPT(yG-lZJOX;>|6*=$n!()c|F6sqip~IYt22YS zIj>`G`_14Rt{XA8NcVp;wotJ{BO3lJ_8275AmL3w$>VM|4PPpI=J?CIc__prDCI_qc{kZ2mwyKTLSNsxyxz! zZVS9WOhs0lJ&GeTD`2M(fe>m9-&iAg&#sK#@bl*`=VWDHpz>7i(j?z|1m341YNUL> zDe(RX<&D-PP*t=g4EO4pPa(WA`f9jnQ-MI=K%Gaq%O%^h3a>tvF;)X*df681Z0+C2 zX~J144Cw~_mcbfW&6XXPRiZL= zLMDT4(nBVwDuN=*xvWu^#ASA+(#fW~1<;Q$Usj~B3buT&g;i|XSy`i9X5xxhtWh*` zx@m-{^(2>_lLbkcHd;K(G*AHNaaof9j+hfbZa$a&B;V%--WPD$MftuU@ctB+U6SvM z0`E_6EwiEdqO*oIWwsg`osy>eNYC>-%@pzdNWi<0lF1Md}F7AM{tho}gp zT9!FgAXLd^i2|r@vC1BoaM1*H4M@M;Drd2jii%Zux@TL)Yn5e46Dter0)@-DELAkl z{oU+Djs#6GV4GIm74yew&gv&ZFz_iai66e zX0#>aEjR#=x8M-7hRSX-S$M!o`!?-z)hb7;;-ppf(-`Qx!06RfwB95|r@a844*=Iv zk;!S}A?xg~YpJkjhHUu)m3?nA@TTFmTR^Do_STg2iv!xSph%&7wBN)dU$d`8^}kHA z`iq=YBL-5>2j16HeYsrLF!49v{dtBprZ|vh@{rpk8$S=>sQcEJZcj`P$FNUV~^O3y!laE0m$(j$nh#Q zhnqVfOP{R>kTrn3Mnw^3DOnW)*TeQ!)V_mLn1@Dgq@)IWKUh~-arc!!FVN*Cs_Sn> zqMes5kl;4cv8Qb9s4B&|KOnb#`RJ z`Jj{xUgz8pwGKa3)tM~;Zy=DBvlEYqP+x$-%H7UZJksGb%Aze)Vlf-J$zqOEcu%zy zKGMzM$QSE;R3N*R;-_TTXh@U&-<1EIr9BU6w!aQp|0c7f>o(49gP~weWa&huf7*k2 zVy?5J?3qc)GUMA`6yFx&l&rs@uyEoWSU9ETK+WBp>yl(DY$4Mg$}2Q?=8-;iEzFYUS`&~A-D!}Mse@Co z*~4|?ocN98uCa-g?|fU~yMMIw)9D`81ub~FCEsd4^*F6Ai-(!K-jM`Cb$4`x?mQA# zV3F@2m8-R-fKdQeH2XuVnd(m1A_T4Xa`VkkXA z?{f2P5>V9wIKa(!sWWj;YcrjKtL%%`)uO|L+&n^7d%30BA#N@L0Lj6uXBoOkq*8m2 zo5zYe37s>WkbQ^BPT{wA{(ZF-)O{1wdWpyT+&oeCv%RICBivjf0Xtg&N4a^b1k|+v z8o2o}^)hU*Z!z5uj(ZQZPC}6geWTi1pmvO#XUYZ#S{i)7&1DjBxCQVbH~Rs&UUOJ0 z@j*<>`J(8GzVJh;dQhmf7OL=G35xufn-|KiK5Oaf6K<}Qfa5KIPq}%y1T?k)KI7(R zB;a%l;B#DGOTbxcsY1{NLj4^S2?zLU2|d?R4eswRN{9(9pKACi&?)#O+;{B2Yx2d_{kYGa`T(AQLrtr5>9dRE(r*00enTr zv^F%WlFjx7ntjd9`(&-SmRhH|`5*w4pJ*FLkuR8jriw0^0c{`cej>Ava)$K}s3&0* zzR?lJnVTFiI4t#ABt!a!^$(#)DE>Oh-@w0FKUo5HwgAquewqZ-S-ZgbB%t~GS?;Sl&r&9IgEHAyn{i zX`r?oBC6)UXzIj`yE5Up2%Y6Evh~Bzb6rsqu9Bnt%z3wqqJ|gjFjqM5Xu>ts*+56X zaNgar)#GS&MPrzc@^gXmUpa52C>LCi!39raF29+O=5Dl2o*pl9-e}ukI8Qd4k`cbM z)M8!nm?@ZM)!OB;>o?9DC%b=AAl0Hyd|o2;JLf$hduDqyH+-IH*TAed?;!ya?2*A9 zKLR}mTSBNIrd6Be51P}(*SJLWznh#?@nL3LsN6H(&?=WXZ<=UT)60z6#W*oLFoR?& z4l}2U7JqWybQ{9eNd4o;Z!=%iD1UL@EQ!{&`p0U4R#Xt@%@IX{VKNxzzd__gvmiQ* zQ1a%=R#%mf`M+dBFwk(`0s+6Gg!}|elFTkD%(3d|=;!uu(OAoQi$tZyH!N5Qn=O+C zE0tdu5smVe$bPTYP)>t=+$pds;4x)D~dXc11<|JXb6{n<`JHyZ-o zL{NO5SuZ%~Qk*%VizMhG;ky|W70Jhcv!`l=R& zXCjo46(3t1qD3?nJ#OpF4-9gy0B`JP*3(~|9mp?IVr?&qW2mf^qX#d@cebCF5B%m7DmOYHaqF(TfLlXR^Jis`ehv)V0x+wz8y~`s@cx7hdayA_rKL}YCLX1 zyuUK5q;)GgHPGAzIcXd!nc6rm3YFz{jgmm8hU2;jEVUZPIEtkP%T@mq5;6r0 zbWJdwdfF-@SI1Lwl4*`$n{NY`HeB1;G*sa=mDVB+Ej($1$!<_oTdqx!(B%TER;m!l zzP3r?w&U9FvgjFEgcB+4{#>C}Cw z)A)xiggcE_p_=W&`CF_tf6Iiu;ksXNCr<5)UNOOK=zrbt2?4RI=uuCnMr+ck>TE`5 zuG?h7xujySsXgW1qXu*SAk$r9&yGsr{5lMBMQViaYEV>HIbf>)TR=DiV6DuT* zivDHG<%*B2#eYkV9??fS6@6t(;NcHB)`8Jndx<9xictnC+ndpr3nx3O6mgr^U!xes zeI95tv_*(Ch<;2|bl#T24^DR=nah=nJ0y`ZQ_+9K$YSAAr9<_FL+!G< zo%4gmJFev9yMyz?!O+U#n@X=RH*8h8D3>CF~q`w);j*43x#bD!9f> z^a!v1Es9Fyds4~UxK%AZB$i9*ZI&mg;~e{8%6Hpp1Q(Ze5d8prq z0v{=YzV@me?!-Y+11RqYlb+QhnGIme4{AEBm}E=l%nnv~&qh$=^# zd{p?jrGvP{lXl`!gE@bK=u%3cJ9pUAM3+M-UavvKopdWA?hL(3n5%*(JPz_HZ<&xe zRMB$*HQ!`Lj8cd7TP@-TR33y>*2yEta4YARi|%K?6qxWZDq3JmV^gm6GlUO7S}#f7 z78WSU3;pUF78@1bXAR~~$L;&&#P6WWcym`e|AwX2)hj#5mtcr4N!L5Mvb%W_909(y zr}4})_Hhz=7grhuRGnwVt<>{2+)6cNn8Rqz_jbv@@8(Ji%C8kNt1bk{e-G*3u_SQk zR_a&i^(*KV8uwYgm&VBe>Eh#>($u?!*;itq*Hpj$GM8fEtfl z!+7LnyUY+CHHs^5lFhe@=5f0rrLOwaB87cQe;jdjSa?q06EMf6#Ew>g?iXIh`oAc|_`?E+L4t9GL`ZncPy zw|0a|ijC#UyG2n$Uoh@TL(Omjx_X&MxXjDjHqZKHux=`tBgq_`oV?I_Qlo1P?8j5h zVk>;7E37z!-B0R5HJ5XYj&~HAkZS6)ZHT%>&f)>SoZ--da9>(G@%(htaS45pR4_%h z*c|W-std8;h_c%Hqk_n)mz}cq*VY)iuo6nI#@Nhc-dvyYj%+s(j*w~!ckwwOWX}FK z^c8i@w)bVxL!?#<6nY30j=rGkxUNY3Nfu3l@U2PUxSPbuclHHqC)qVOj#WRB<;Aq{ z58J~y+X#VpLar%^YcAVP$f6P|II1GK*oPUgbNJX4!leTwdpf56Cd((YOR zDDf~^PpdFg-)Y^eMWlr0!InO7*KssnHIzx@#uKU(Bci5~^`weGZp$Z28d4 zJnr#;19}38rU8tn^W}+x#Mkk`qjEBj)6lR?8)r_26ejp!pur5jJXJQBW{#!9Hd6wh zct#Cu;WH_IM+OKxT@dyPLp&_|nnkq?hv2w#Ba^An2>Uq961W_xF92-(LrtdKV~!3&G22@U z+tj=lP0%6sw|%IEZR+d?g&@1q{vfQt=I5^Ab+y^E$A)`IFSpk&| zVltG>vBL60rzU9l_!+x2QHgq$O6FSo^H7_!R$<7TAuwR&J<%$-Pw7x-oky*;ty7eZ zyeD3OuJBJ84Jv9A=^wSgaHWUy4BBoGZ8rE;5Cv#8j=u2{i5gA;^mjusqxD?;1|qno>dJLNuR?N0x)LojQq z%&>E8E5|%J#x|;L>yXAWf@G~70g^GDy493Ou?1E;4;k%z9ednx*a(&W(6Z3B^PU_* zgQ|TtGdDF`9#ClUQ`T&)lhy-0Izrc!DesOz|hwEC+?f6Jbw;dUH*-lgi-O&6uPY4IJ73M%ZN31OI+2F{8HxO$uH{#pamf$}GNkgMMk zMOS$400o}Y_Ff7iP*iR-r3--rS1d!$m<$AdD{**_ikeKVg&bOk8GIC&>0vH@0F)4% zw-4t3SpfPz7e6HE^Edn5-Ob)dqa5Ml$+EUECKU!Zk_tz;c$%zzE${7$^3#Fx1}>g1 z%dc9I60AsN(__>yP3*-EJNC#AxOk4N*TQGh&_z?A-iKT~R}L(!NeQB;Z2b}96Nm;^ zm~DV_Am#ibS+`c!y%ebX2^TMsP+>y4Jl`fe_>_xRNZ^~YYH$Gh85ch*p}QnBECBr+ z2@JuLd(?>q025N#!53V-R#p`Tq?8{QsQM)rub0q+5}Fu*9_Qj$1aw)E9qw``D4wm* zL-aDmZ_T&Y;O=LLBSkEgf3f}}^(2JcDQYM2evIigtnu`wsHz|iQTIowik?F}d7%YMp_tWTHKrUa@Xv$ffJnU#9G zMyvcxr+lzIY`@nI?}1;aEFNm1P6zCeB!6YSM>Z=DG`mRpZ0jh#x4~YeReocAt}J{y zP>AeKmokykAjMgTeeR0xRd7GVe)@y;{bl84fy$RyKS;b+rJE&%F0(#gmahtw|H=Ac z;yo_S97ly!HpvP8La3mOHdJhfjPzeum5?GwYoddw{s=aKoK~K%ct3i^ z4(;Jdi&&G*6vp});=N^p(c!Ew74P|nZJl7-6HU!KEiNA66L+!llJP=& z$G#AeK5M~Bgu`u>EBMWE{N^gGX@cpzg*MT2?2spsOOs5v>4Y8>-aA3j$n1{WL!@_d zV2g>p%7fr7X_eO-(Q#Dtt<)Mgz(?Yd=-!%j)$AotBaB|A3Kkj-h3!hU%~e^i0LKc8 zDFWIL^+8#l5RmoMY%x$GPw<#BKqII(2{tc#MGvj7m+NqMnAO*DK*VME*Q<^cK5@T_ zUGvHv(`MNz>|*i3yuYp>zL{&AMzq1AZ_TtoSr;~@Qao20G4r#9J5=)`E zU3@}%NX$^5k{x#x1}+dLs`g~w&Dov-mzVDOkgt1bcd!eB1}K=L)5#Zv6}SgYs*0#cVLj?-FQ%Do<|o$756 zw;;uJ#avIfH0*j+fD5=H1iDM*9o5)rIsaTsHRxk|IocvGIxK!5u%)4>simm<-yR8p z#%WjCN1=wNFLKOB8YzY!g6QuO8ewro8`N|Ay1ap^Vz!K)O*3QCCU^y!!HqkoF78t?IJL&}^kbVbHabg4nXV7JWj_}q&MI^_P)UyaZr)ZgI z+pN&0%eF8`qS2Y-TtaeF@R_z)rN9`C$gaoKp#T)C8)Zu?(z{5&loo)EYd;4-aE%|Y ztb)Mtf^fV@5~~sMT2II}Gg{g>xb~C;l(qnzTzf_U@>N@47#G)mD*=ucfSYT7P;qTl ztk<;X;`bd(H86HnzBd(c)d5o>)Q9(2BB=OhHBSua<=UTR>)e*sKCb;u0(!RqvbgrL z1oUqKWOJ=z6&MU^0pxJ4P6F~<0EpB2g^;Fhu{A+$a_zO^osu#=xi(rhxU;1}E}cr& zq^x)Xr}!aFj;&fHk84}Y8ig%2ZsOYZ07#}H)RdL7qh4IwNmd)*Qmr@F>Lp-83!o3z zc9VeO7C>LFHAuje7C=9)&5(dcTLAsJ)(QaS&p;B};zO25!B`%VeS3(@FX5WrYK^EYI);!ds9=OnJTa_tBS9pDV+*f#10 z2^}WPBq8AEIw9cS#qTm z6LHZPGRnU=q5M(OPjdcChp0-?g|z8#VmW0F^kq=aN9|r9uf8jGC_>o8o7B~4GLFmcS>zuH0(|1I_;v? zKxD^R;Ovi^-&xK$G2S2o244vD9-?FOoSAg|S!YV1026A*7Zy6>HGJq5r&m!XaeSDo z1}cgwER^$0oUM=+buQ*FS{HNk%dW8!oeBK$>rRV;WKl<;G!#46ePiV$oZ>g#bNYT zr@B7o*nQ5Q5CWFVzH`lnm^RlLCkCiTK&SDp*F*=8Al$9%Wl@sK4Tqg}=rq}SSd5cA z#g!pyASD$B6U|m5;@Np;qwGhbP$0^uv-Kk)>OOZuM134)W3FEK7On`cIN`z( zzRK%@F6X3kEdoabH;NT9lM1%M=?1M*a0GL`CFeLRKp4C?%87=G0cHmXfth_P5%^kx z%_?YduZH8ka6Ti6Tt<0yF2t~|cOl>ETxWzJkm(-?oE4qTp+W6k$oYNI`I69Bo;vO7 zg42xOiS);FoqJb=x%4mLKUla2Tm?K?aaC#% zvDE2T=hqDEU|{*qS%2uoMx+^1QQ?y_=RxEo|V(aROhn^Ii2DG1>TEV_yTkP#2K zNkx40BF@|H-1GM$5w}*+i>cc0OcV$Qs2V1K6#{4zvz~m-R~4UmvZQDw)z5W)FDMKK zgt@f)o21m#+Vzp>V+koIoUrr#1wXXiKRa*I2;xrUpM9f`%;94>__Lh*lN_aeV3g-5cP41-9PT;-RVL@vxB{eW zCFirXW=xx#teT-Wmol1?3Sfs{Zknb80Nda;+l#27y=@jv(p&vnZ9^wBSI z{7xrmh^mr?FAIc_e&x#A&aP;I&MO~Q`S?&^A&CuS1E zpT6ZXz)TK17vnddpuiw@)6e4ju5!IW$$z?Tru@fUgX#M{K)7MIOT`&n<8H@$SG#&5 zJr(W~UAP%cE_E%Gl-S56O)mVNu1xaQ;7wkujVn((hid;ZFEK-5rV+haOhYfG@uftz z28k)e$HY>}5#_bqd9HImEOO+ODxB$Zq}Mq<*Ath>N>s^pb;}5$2qk(m#^|EuRSW2)X#`lo&*YLm5KUI|NS_5U#eCG(!@qW=U@|i8e&p@n;-dq~#ej->oK!;WL19Y^VJC0|?x&7LR zgD$HZ$ueDz2rB5}PN$kK?j*{0xse=ZygQUfCc0nLLYY`=cXy}gaqeguk>WvkifVoLicdb*9fE1evVZrW$NVPa)-cR~bNG^*%?4v#0f(?sms z>K^X4+%egGR-~+Nbi3P9-6#aqx1_sUvp3a!NQ)S^<9oSRQpFJ9l2PG~UGC%VK!y44V5&-Shj5+Q-Ak)9aIotBORGDEd5xo*Yvlb0qz8fHMv_c zEIKA2uRJv^bmJ^M%zZ{1{)OXShZCW0VNr00TjlIK-R~m2Ee8)5xE!bWBzJc<7P>#z zLQ5Fu1&IpnZVTT$#{HJ?@{f-b9gRmvu!_C&Vc>LmI;dP&>UQxH6WpZ;RLjA|qN`K1 zr_`N7RTJ>}7EgDFbM_RB6!w+VHP-ELIH=?)cP9HDbsrQC{DpJe@aCW4hQs4&+ViyA zO4A;8x8Z@MZY<9;)Ns4I!_rBEb(A>?Uq8Lvjo=gCP~}AQk5v;&jnhFF_dNGuCqWH7o>aFYHjz?;%HD4>y$_bR&dZSUX6&o7)9b=bhem%L_eEuzA1xQ$=KsFZV4$)WL&pL>>GU zmOtK1wjXx?5-Jjo9~Jtnp48>CH=L(_gc<98!VteB5{Wx8Ja{HjJqWV~Ss|V$oN_PK zh?MWqmpS;f_{x8hzR(9x+NC|(B8Q?kYuj}1NNnI^J>`@;(fcS>Z1xsY?k}F##RdTt z;dD*-64Gd&@kZ}v2Yicked?iAfkG5FSOrWxX z-qAE_n5P>*mF>Z%l1N4OdpgnLyevQEhI;gLp7*pG}sr*YH z&*hL{aFH)Xj%fMOeOx@)gCxx&Y*{z@4EWMn!K`Qu~stUu%KrqIb(JgfQLsh(_&k^*mc-$X8X)UzZMAyo?oY4!4R z6F&DAy0F*1jHeyJ$`EM2fDdWq53(#DWZpm z!-HWRHI8Soq7#wz+;nWb7r~njd!E!nDHq?)hdeF~SgwkfN&0jByZ+5#m z>yqaH%o_Qa$D668SbHlIWoCPCqAIQTURXFP&Q^$deJDPecHHfKozDe(_l4?kg8!*F zFu&Yr@4I3Bb`0}J1qb~~)ywpfaDy1s7^{fDs4>Vfy3q5m{8eNMoX_&2R^F)!!lM@K z@pZ;gJ)^0|>pmAxYVTd9fsteN`|kZz+Qm1P>RjH{ z{C+2|OQRWNrRLY#w&%{tUa;@Mq@0SgTt&Z-N?om747~dwfz|u*$n8`-!mFpcBk`%* z(!B`KK9uw`L-oA4mA3@z!4nfoY8Ez>#!MeH>Zd(DkjcOc4Rk(L=Vajsp?YzID1gPX zFOBj$`3`g4@19(Ay1+LN`~iFRg86L_E9jEv0cv`xTO5sadtZd9Zc=yO~tTDHf{u^;7Yvqk)Z+|ZiZ+B4HW^ao4nv>xRFxU%U zfIBJvNnckk?cgoN4|#8o2PpSB?4@mQ@t#wVhNL&Z;eC-Afd-pe{&JW_d#Vf@%4 zHT=X9?+lsTJqF(6k9593RfDwqX!Bn8Ix5caX2|a@w#A1PqqMQt-`7BUW9GOx5%eW&!AKd^~~dj=e<9M1SbRs z?GaJ213R4HX%k=c`dbAD9hxRE+b4H`@c=(0H#)@7@X*J&Cp$IruyWaOHIs~5=+|3-M)TCYD* z1dJ0E`-rQXm=gSbz!n)L8obCT@fgw_`H&>!BX4ae6mum#eehV1 z^Zs%b+s=IHgctejX29&pH;d;qddsxoGsm_3q5MaD1t6!dkawN-_6-f6Ed#NsHY*i; z>VEZ~2c;V@IAQS}Q%Ys+d<0|a9-FCRkoRt0*X$joQOYR(H_v`5z7t2P+<-&%_cdstmWyBMg&}6J4~Cd?sc5AqkLv2Z zo2W|XX(yJ#gV!(NL0lN&+YzFJnEhU=AmnlR<)1*`^SRROvuo(tX`Z1lhs}+{`L@!NN;PM^x5z0oHm^4{NYr#J)rY&#WM8lFVEE^J zF3%cb>U|}K^MK)ED$4c!!q~PC;RlxBt+;-h6jq1vKe-KbG|`K||JR}&$3u5{4friJ zd4Hm+(#`|_fiD5yRpJv>Qe`_FnlIFPM+)rQ(w{v%H?2tUL<{`Cy^63-v(4kXJ2)5) zHNVIMzCd)iT$4**c+ihn`8|ApD!N7JaT&#DwDNI}T;D#C;iBA0oW0B5RokiSY`3um*Xa%0BeXrp1FHfMAxFVED5V zht*e~M3=dr_#hjGdPZ*>?`y9U>7d_*v%w|r=lttLUu#9TrurU#62(vQo!32!sZ^15 zkq0^8ihYe*X!fwr=1?hl3%PoVZ?slfi|Y^HU!kGv#E--2rb=(t)0F&|JC1p|FBl8y zyw^B7?a`kuaQIhdRP+g6esV3MniE3tf|Ad6>_0o=2 zKB3f%SD?k`FpoO#OOC~?!5w<8_aUKl z#ToZ5I@#3|N}HGZ@^vqus|{p*#k*I$Ls7lUqvHmyE^oo%>Fn@r>_D z4ODscV|}=dS>;<9rb~uYH%<(4uh>U7z8LK9f_*gV2E*1Vx|dO9)78Tx65IVZTGRNY zSA4@2;qtzu2M*(}`A&+=P?b6#cxR0dyt9^S@AR~$BhULvc)=FmW1&#V9##r=+|@HA>Rc>_d2jPzycZ!N{QbDlii2S77@UhPxf3u-(jDrROFo>>_EP4i-vji}y5wIe^H<+7B$`eRqaN#%MFx+ULsY#m8QQRRSQQQS;(n( zgzB~3dvdSztU9gsXrDW&Atx&!F`11{?8 zMN+aW!uN3QIybsCxsrDAJxyxO$lfqBrWR+@=6{@GhcMp%s&6*dk!#&#p5f0YJ z-3MfSju4+K24(dYNn96A2h_u5gR;&CBhy83LDs6!U^tC73VF&y{qM@k4FBTk8Gb0J zpP7NGZsxnP`fGHb0V7DkTa#8GwOsV^ZJIFLJ-#?KyQka@j+6Rq$8Y(E!mKtzPnP>z zJH#s)lZ9RHUzB^f9mLo1SrA`OQm)co&ueaqF2c2AdG>HB>>WLcO#bZGR(zWTi{eRH zKLiU++0ld?>i0pyNlu=cl@+SPmEK|zch*HKW@M@1U#~FuVetOi?GnF`nORUbHEt`- z!lC>OAzp4O8}_%KrSN)x)(8z0V->S=py8R9)hR-;aKVzSO^WU`rt>Z3nc8=U(`R-x z)y(N)mWx>ME(8{dLS|fB{)xlkGdS8|Gxit(ITXucy0XS}+sPuQD9!ceX;CMK7B z-FqZbab8R`k3EpJS{wFjpX8$CVMCLtxfn)LT@GiR)@m-^2|wGI4&0?7>vX8{JMZ{5 zeuTnBpJ%OwVI0NTH93@<*J>C4c|1#}RW4D%#)z?0JUaqmG#ay3z`2^@Ymo;gc6tOh z>9nv6Dtsbh9EYFDS{oK@6Jj3ZV;9dfWgenH{G@!1Xyefeuvx}u?yy{X7DGALygm9|L@ z5?%Jv5U9PZeZum@$X_5nMNBs02!?U>{&4WS{PQ8V(On0^69pn}4HRE_&HB&(F*0G~ zlT_3;d#!@ZtVfVK?ff5Et#JBJfo|baRtXGDKL{6BK+tE_W~G>@(3gE1mnUV<7SSh_ z-@D>a95-1%PoPtxa7fqeoKPJcVSmXM1QNuXqX35wjVzJvxVcq! zta3fR*L+|>W?_9E+a9@!vx_PFkF32{aerD|{={~m<__5fRGQf>`Fi}T{{H+)a+V#r zxw9AyvniybhOURX=g4edNU%rbro+$M&NXAQkzX;na;P5{)GvsmzLR>7%U&Da`N=VU zoOKop(-bGAV$xxq-VE_Ok<5z<&Lgwn6=IB0T5lVljcuw6RhDRR3o;=azcJhZj>zh* zaQ~^e#G* zbMuVsRw5NrL)VTfl2;_9)9ydp_2M<9*<-W{zI2)ZzmhbVUYeMd#XHNh=SJ$XkY9Nm z@U_yT71_=(!_&9>ec18^6SDK!itIE^c>4S?emJKu71OfOfTh`6mG0Y?3G|H|yfPcS z)lAI>T&=ucfyB8pw?;(MmwBDT*;keQYM3qu`{F#2gwo0#H)MCxgjp)xeq58U6}o&Y z0TxT8uV&BInedr1`B+M-3D>EqsZ+2m^h;^YH6G-drKVL#%Z&$XA8UJo7cEFw4*a01EZIuqa zE|Sc9Ir8mnr2Tc$?1kQHnh~2*PK75!BI)26O&eNM2p#aKec9_m6gSnsl0Ai!pGj>* zq>3|P>qArraPc5aQtC#oZOFbw=ca|0 z?8=@m*JCynbwH-b6^+>wwVIqX(hq>D^1bMsDDHVWd#_N~RlgQIYI&U|j2}Fky;8)3 zT=QgF5jFLR38seq*;%~qT=tA`r3dSO$^Mt3gAy?ICbot}^+4WeL-;)iDUsd8YDL<^ za#8of-j{Z1+vRMna6Z18*l250PJ)Q|SkjPoNLjHpt%@HC$yu&w`Z{2GoxTW8?3$>F z;^)(kN!l zj!^QbF(u~#-Jr^D{#+W`HK#^*GinZ|+HUdK56{M9H%`sLZajp8({pfjGL(bO;@^B+ z9p+$h-9oSa8k*~Xzs?s?3-nq<-fHN#3Bmy(Q2x@DB%7?4x z+>7!&ruao_rt1fY1&d;!_>rpy$+)@@iYP~lR@fxJOCvd260n(BYNCXYUE?%>BX!1K&M_j1qu-S?jJo_l`pd*0tjL|-)T62-?QkQ!0eBqOG>MnZyFO0lSC za~z_c4@x8;QL%k@hxYjr#XlZLJ`?0=nK3xboz^Da+(7Dm zxoFAGj(kc+8kenK;+Oz`v+%*8AU66mIV3MZ+sj!MH}+zuQ+lNHye9Ii7rM#n zizHu?U%=UwcrU?&u{^f*kA zk5`h=nIgwAOlkSNy_I|+@C!eagPz!%UUHY577c64y&O;JJl@vAaD5pXQrO9RWQYuv z8B+m|?jZ0?*AoK$S=7h2JpRf}l7N6#=+m`23SYUl>_qV0S|d$2XewH}gS3a^XybWN zhFFbhJ*$e-hw%steaRqC(|K&ytZn0`f~cQhEG{=Xp2m&+wPOs&5ZMc%S%$(*iyTvR z9@W~s)R*rLp;HB)b!e>U*|honB6Fi7!OjQ!ajT7fD4l%%} z^YH;YV*g0JH7TrZo@l-|pAt{^Nb*KBQ_2!D`YLSTu>K7@Btj+Q=Crh!S@E$$5OFW* z8^n?>KjzJX=JgHa6I6<%-7T!`4neFd!ldURiBvU!Jf1U--VzP0%GWU+U5G&j%EA^+ zq%X^xaD%O^M6rYE3b!L^#BN_JumN+a1ElE)Z~jImodHMxy#WC%4--}s#a>%UGv(L!&2;>kd#|E5oD$fk?W}Ec&;G3899_&S^Jtp3 zUo&}zelnj{K=(?*8@%e!Fid}g*3)K996SH8A2tye(Hgz5ifjxsSn-0~sHgQTZ&k>1EW44$^VB+u)xtZh>a(Cg z*4iF7PCGn{f7C$t>3g8geoJP|J^Znp7GpGj>o}bx$@_5&Fc=*{19{Yy5R}xqev0~u zP`^&SDrNBJZ_^SAiqR(labBaG& znh+lG08On%R`DZZdfX5q=fMr(Km`b!ToImeML1M} z(ayRej8|?Lgj9YcK^Y~=p8(kBHTjkTRQXz|1PHY~ZK=-Bk#j63Q$5PJ%~X#goq4Q+ zsL2;dBUL_Sc#*|CWxRIVFWu0c?}E;0KWI`vdZia!(K%n~MOSnw$`ZYh#%YGKT@RsKX5 zF!b0dY(u@(!qv;@v$aJErkzXOc8tqiI>ry~-SZP!jCYSN*m|pYiNbwsHx;-bBt|}RLASNhB)MXHWjdZTwyr}c{)EbP{kle zDGf7r>$y1WXcZ3Iw~NCz|H0K^$E$GIe%(22v!&Bvt129JFIQOpuCSce3UGxrPQ7mM z%+N-(+UXs==EY&1W;Wr6B5AF`A3A3fGq#*WNFYW%%zc<}j3j7dIZWi~v(*YgfC6$K ztF`PC>jd`o%jz&~G=oZfcfE@0Rr`DBDBGW=PL_gEi6Qv|Lzr`(VNC+~W3|nSIE;rm zgymbU2<_{XDg4C@wE)%9unRL1OZ)(?IH=W+ga^HovI@EB~G~)-FX{{6N7!gIy z+Fu9_{yjkO?jGiT$bz7`4A|;;Oo@u1bbtOtD?N+M?=@5-qs&+|3|00Zb+%lcA_|Wq tDaaDY9-ylexcCoMr}xZ+rC+r(%tNp7sMYF8tk-uLJxljn^0hX?{|`bZ8WjKl delta 77161 zcmeFZcX(CB_CI`Pm$Uaiy(fW`b3#Z$OCg~Il8_+1CQ<~YBs6J4B2AEzb3l4YC|QD( zL_icNK_Ls3k|=sn5fU$YMOsiqM1*h!YzV(K2gU3Ce&6SP-ap>|emu`!hwMFjX3d(J zHM7?GtX)(W5Q=I7LUBWToK9zJs{8AowWN899H&c|ba3CO9SU2r#-whCHu07T^ZqO& z&hz)x>BQ0g-b9D`dlBvL??JSuzZ=mW{w_qj_&XC#_diCotv{1!8-E7TR{nINss8pv zTl(7(ZSHSNG}hmSsMp_`Xaj#MqAq_bQHwu?sOoR2o2L^6f3i*|{OnI6dduH}=rw;c zqL=;D1L1Bk}ejm|8elO9t{ZT|K{EJK5h$nPP#!0#sN_q%kudBPmOlm2+lZzo#lw-TM?w-BA^HxZrSHxeD{HxM1@ zSBd8M6{0=-dZL~DGSM`@M6{`2Alhgy5&Ik>>MSDcIh~IEGJ{C{G$J>q61hHw$kk_w zTrQygv#%!6e=klXa()7lGvkT;b1aeCF+@&|Ci3A(BFFNHR1YUoIh4qoPZD`!Fp)h^ z5ZN`5$j$*oHuWR2DwjxrMwC5E1Ih||5_yJ(lTGYOWHb#X8%krz5(ji5+MNcJb;%}@ zp2cs{$g)TpQr3{hl7*!aanN`&YwI;#jXTY;3;1xam``&@Tt5H4anXGvqf4Mm#QR1` zACFSHG)IzJOS*NC?|1tksgKkQ%DxayQ2%3u36he{68?}S*_dQoHlr1?j?#U`ER~(4 zO&V(pn|n*+Rp+vaZ6qBGfm^qw){r;f;l`)>NZS~*SLR5f#N5y>Pilx!{iVwy^Pn_H zdLXh8l!nl+5}K5f)G{dzKA9lJU_zdht+F&M7%Qz3nX7W5v|X0kNxJoLNi_s{3&Bf; zQlua^piV}hG*#NDv2-Y(BaN4$){JhC*11wWlW8_FFI z3PcIQHTcHzgZ&I-$`czk?TilF8SjL6#ILRF@B=6+94+C#BPj>g#Q(g&)PCFyQMZh<2> zeLPTHEoqo^M^c!Y@lN9olJ0IFE`o9R>RssxrjY7mnMtkJ_`D6!`?EwN)B)z6l>)F+ zk}bIQo-|oxSzv!v3Ox`me=D+V&PxD^lQ|;m2y?%sqAeas1}s+PDI$A}Gc*q7-KRS< zO!9JtbwM&3u;=(Mr@QM*D0(I*nV}oFtn}wy~z5~!sf+t;ImG05~gO! zr$m+ui5=v4Y~D%!m&hJRsT(iR5B5DFyCJ8CY`{jn7((i)0mx7tpxDzPoZW$SRgs zW7T5$F%iTCaugKLm)k&;Pk$e>7RpaS(k{6I%lhlvF_@FCA62?oPT=)ygMuPig1XoA z2XI-59Hq)#h{Z{eykBk&3uozDV#o&hDwBs1PfvqygnldDbm;92iP1a;&$rND8JJis3&Dx-0jz4LZwcO(`dp|g*Jr}y9dZd5;WzBL|3!%31#6v&3B_`1aL_+R z!xi}#nLUps$0D1GKm++H7wl}PcIrDk98shZeNs?bCecZX&B5e_`l5$p!KYdb!-~6d zKgQ;Qy-0TPp?HdqW>HNbM zp#R;-=h>TC;|my{k1obuSW_}55ufRnP*vbk%XWKk#<)Fw`@^%~WJDI$ZH5%!;`fUA5wfKz@Ig;+1Zhy@z z-eD74lQ%P-4v1_CT_q@W(vuck4f$ye8bRJbV>>*%QO*#s*rOj2II@D-ZyG+rr4%AzxaEvfpj$|d?R2CJUY&xL3E=}XwUHRX5QSTRYT z!!VEO6Bs1yB2_^zT!r0F=m&zBqfg}P+``q;dkT9ISR=9Vn_1qI0_JtoYe6p7Bl}El z#jtpg{vSa>Yye-KJR0i8>CYgsb|iT7CCGn49{d*@NNh|MKpd-Y6l`G!waR!Wnqyv} zzOy3rBLPAAM&BRnvFGz z*mAMHR%Ba|CVKLpMLc?BcyFa%k=b@E^hP!op$X`}WUs)!;bH_uku<$IXio~T!vs=x&9o9WTUEF@w!UH$ythX$GPbRLnPfB{4kUZH!kQG(r!2VDM?p8`#)a%VMSDOz zc;vRev&fD>_C()6eDY`gYa%-e*#*86SaVOGBC;y1Ki{aBLDg|zbF5;DOOu96y474i zC4**EUQphxczl?oyTniaL2flTWmJ;bJC!D7lFIU7vP+pGy}we;!+Muu73KGcgAc%e zs$mS)cnr-MoQg51I3!Hjp=ZZ2r@4|MV4G+~)3cG79j_c>(no8ij6l{@xzFT((90h~ zNn^DYMx-ic5%zX6L_kO*Wf|T`RNNB#1ij6bSP`4HQ5Ngv(UPuYAQxI&xS64(s;AcU z9!2N5$Vayf_)Mm)l~LU21s0| zRC*p&p@*Xzoo%ruM~P(0>E-&TX(xn2FA3vsb(AsrQ)2!z*tgc+5_Vs5+Thg`pA%{> zIg@c|e}x9^b0`_CL_(>j;Yq9?q&OA!1yqmAN(65wX$V$ycEvE*JE5_E^s(ghh zv}fyf{MA7{7WkFDj7)3x@l5TBqt_Ovxhb3#N&JHV- zf2d!t8UGA1H0VEMvuZ%wD4eiTS<6^mWwFvlk_seUG1p}O$re=+uY9wx=VVE@f@`xh zK3orLM(aA|ww`?p?3;`SC~i_}RrV~<#OR12uPC#a@?G)JLRuT(+tuhltK8E^427FJ z6q|g7I&clju6oij>s94hlQc!rZ33Uy`mi|G@26?WzOOu}luKl&(zE;n;-6LRSA3O+ zl?MX*p|VPuCCSt1`P@JHw_l0=`}*yVS??=H1^Fhm_;1J#^{m9z$CW~n{fN>D<$);N zZV6}e#4gZhuBc)E8fCA@?!cvyo+!}nI6atlTKPy6?zSm?Qg&h37s@qJ_{jjL>J$$) zJf~b0rJs{p8ORV-A~1#w300mU>2`Pe4{2`j79st|#q2sasw&D;4b{}l9 z4d>&cpOxq7uLodj+VDA?eNR~=G9A=@6dnfirnGzVME@fDjoRAe&VDh#L7wcD-U5SSisO$(=8G%Za1NMH)w zni)e~ zyORu1*WET2C&sIwFcTK8RVOi+_lcH`Zziib0y6{4jZVe3sp`ifvw*MKmk7h!r#FVI zf$B4mw@i~^LR7RB&$LyWih66y(J4u(;D4!63Z|#4{}7oCs?A~pjLcHUs`4C3cQN7N z@NDU>Hsda@(26#*C0!jqy9Y-_1^$G)#2g3M^OEkGK3E>X4jHTN5XFYA+JbM9FBY3mRR1L!!&@e$rlz)RmC~velwXOC!&U|ANl}VO zX`2kW6~4!?$5iz#g+)R6I`v0Lx)?JK^X8~q1<{*28VY{$6+^{OdJQUOHL~KKB6WZ$ z`bM?_-xI0}8!S-CyAzG&*P}D(ne?!?lZ(}2QI4eEHb#49qnk8vXTmfn z>JtsPrc^Cp}Ii>AK5$om=x`In=ku+c`9tl)U4jgIb#ealpG!8C!|C2Bj&+@>B^ z3`2G76V^+2LN|1cy<2Z-(er)*@^-1?%4_J_}aYah<-FK)a3H@DsX&TX*% zIFWUS!j%MSaDT7PmVg9N#gzJN!R0OKN0s6wX{x? zgJL0v%@k8BOzWk6#C1{5qY_Qv{+n#7={XM8ALd|ZoR zYyi}n<2K=zrCL6N#B%LtC|(%X5Ui_=*YLp;npI{hT8C=&f=oVXVG!7dY4YBH0)TQ9oCqkbe48U8@6w}Uz2oS2RC&xymuL^c#p|LE(}LDOe3TIQPYjNc_8Fr zlXV&y4Pm3T^HAiBNe+sD>09!6L_YZ4G2J*LUkQ+~Nc)V9fbu(P56;a2k&Wd1EX9?} zwP^|)4fz9NUn~7S_E|1e8on{KPnk%$zNmd8l24}OInw}0TBIqMw^7?FvawLR+&BQd zTjR#V+{s!18Z6hQLRoQq6kKQ)7YzlmS_sw@X!GefYs(l89}UvXoZ)d$TWlPNEe31H z7)HLLeZ$yzlwQ>mRM{x$?hoV3D-)^bM}mDl!PuzE{hG`qlcZbr=!B(W#{=5ODs38; zBieX*^3v!1W}I_G3o!OfWtDbKldW_zj70wt?Rf^_)!J$<#{#;hKw)O1_K?>%Jp>1y z&~}S#D&#({4#2@R+D?&8gWQ3t8^ca(*F-iQGUfC}825#CPGmEHO)*Y`i;dG&^q-F(ciy$<>6!<19Sr2jzo(Ihwg~bc3v(U_F?^sh7ha%9fhf4A{KW-~@UgRrWWKUM__W@v0jd zOfnq8&juM1LqGkV#ArB zn%fBFXEPF@#gm2-IMm5N4uvk%$Kh*92noGyg=kx6m=P?v3C;{PG=sbZ)eJ|$kPUkh z)J9k~)i6?Io1tW+p*>b?GvtsuE;G=HUMFlp-`j?5Wb@RwUxXx;JX*K&soJ{d49Wa? z859mMq{5ayj$*jj)#)SOxWS7Tml%As&*wVkLDgO=-FKsbJHv!>s2*iljLR1oW{B)% zDBNpU#h(>I(OXUa1>SDXzEF`!tPu~tLDfdX$;#zi%G>_lXWI6+Lv^$gh7oHGS447b z{Mic{uAHvUiXam}OaO0m2iW%*nqt{f&ivnaDukNZh6KF6*$~3mt9a>ELt6$F{S3Vz zYp&s0m~*xLHptpmqXus&e0F)o)y zuxx>$885yQ{7=(*D%)bn!-Cm{sNjbDI{5b+w*QyJIJ4E*evjceW4qv1Ws})hRAHDe zvfUJRGms*G)GaPsgHU;p%VjTIs&%*_ug>`j9zA5J(pV%GeL!qz`&JeBBP89HQ4ci* zS;vhuVrUOtZ=^tes5=t+<|;OrecLe%mVD}nhB=EoDpY;tNW^Q$4PhdClguRp83_BK zzTEK!%s%Ug!}B!;I{zMkTj$A~iTuvc7@s_CAa6w_N?#ZrDC{7XeZ+-a{JF!1Q!g1- z3FHJRJ89^MT`n8!svIThj-&=T!j0GYl*7t;LzTq5Sbf`&DU!e9XTu0t_K^o5;UVKU z*L%W&+$^DZ_IE>!zz**>E|TPEG64VR85M|wEK{h8zez?(l#fym-^B}K!kr95G~;!F zy$6?WH>H@(4~iLMO~&D>93$zzEcin({mt!Bb{qQ$+WYH6{f)@w^N>LxGj60@O|^Xh z`O!^#VNR&=O_BWr>V}wUqYO9FMtK}6mN%RXeeR>`Ezm9QKb z_CN{Xg&1Rwku|2Xkg-OTKcX5xf!Yv-4o9iRP*skVbbE$yX4+woNVA(}kZN9qO*4(3 zG5G`)uE7lhjAKX*T_baKN=IWcW3{+pun`zc%59)PyBe}5iicSY_(5l5hQL0hP?fQf zCdEs-cexp#3=eXRPje&Y^fsSAPSPFc4!0B-mTMf1V;(oIXYwg}_!<9yDO&S=T4slH3y=5!tyML}wSyi3J$ zoB>w7WGsZN!Lce7KNf2OI;P}dj~9$@3G6E@{3UWY=j{P2NZ)#qyzc12t1uxCjqDUnU zrr6H|zmej`MN?z*ITusOU2-X)P35hVd7IZVW*=_appB#f&e8N-J6wg@Ph&g6gZTKy zIHAn=qR74jdrsptBwl_{WYQoP<|12X{EaFti2ane`3+syA^ZK<8TfpKak0pL0LHY2 zkheQ_EYSJP1yzNnKKR{!V*`;Q(L9B zhN4yRA=tRe=u_nulI{epi*?5KxbV2qAgce~`;@;q+0Rh>LezgqgLCpdT|a`o+RzYf z9~t!wvQ8UE!0!QX625)Ln8U#Qnei9i%iCZV^*5p7l(9Q>IA!dIWs?3^7Ay%=vMVoh z2iccKjUl~rS8wbQu(WXH{sND=ZuNYn0FRPRN3FLko6YLS}F0(7}q|@nvsBevR@?C27SI8=| z(LwjBkq)}|u-9t4k746m#`AhXxDVOgv{>{mHhmt{+7Gb0SIlt2Hnh^ZLGkbL7qMui zX*xe^i#o{8(ON@NvFYstw@u{6Vrc&=oeY6bWK(ZNrfu?UB1nftYj8_zG`L)I-UT*eqV<5hNq!KA-CpH?00Vb{bTzj2}<&mAIdI z{f&i6uPH{BTT&y2U?W2tn_di%GQqKa0r`6cbXPJ_xcx zK9&EfJ;&5QmQ(+SxI}_4iR4=88q*BwnvtdKZ64!b*!L~d)}UNM&>!C9iT~=;++d$V zp*kccfwxIM6sjfc&yg8z>a57EsHucMM`j-%8PQEwIIStdlhDxDiBNM>Rr#9{nEi5m z@BeC~h*W|W7knNG_8-+4-cpSSNr$3?H>037Kl2Jy9f~`{2P;K@daGurEHbIGOemtW z9sg|nonYe)shw!7?btYxz&Ba3VVL!nX@9VbjllP2+(qy{VVVB-p_|WG4EQjP^jfir z^jc%6{XBN`-{;5+`zF&3K_*<87?1g5GY15vxFgs^6D%){?e{-As833! z(oLa!Lu^x^Hr~bhS4~br-ux-a-KK1XwMFXz6WPLqeGwBN|9h4AImr1TkIbOh)_h`D z(3$c_)f1eU_B_+qydIkf^+wIW<(vG!i|V*3OJeOH`-JJJma==KKaHf;<4>u*E3g)o zHKr{RYflg#6QNGhDPlees_&Z8;GKmY3v_5=({RffQ!7=@Aj7W*f0PAYmZ)L$SElof zwJyDE`bi>i&vLLI(UJ+CWK*GOt;3EHWiE1A;~bBx|;QP?qYj9vEZZq!3c(lF1eZefC&< zn5mlI=SZ1}HYFAhry55vTy8ec(6i1sM>883THNMD4xGshH&-*Q>zV?8Co zwkaEqrW?<}@F;Vc+=;~A4Re3VSSR56X!CeR?wnhhzF{~b#{8tpy5QmF<`_xtMmlsl zWDhj=f|kkV+gh*r1N@}57bOJm;qxuciy7+;CD+=JeVl5(rm((H*d}5b=8X$4qpj}lL^1z00 zAIH2-<`RbA=9|L>a^n;iwr>TKn>TFvh{1aTIdp2)G)#i3Y3)lmZ^?whic}4i6U}rq z9|C*3`g&vDuNjF9iweyd3ZamWJZCU8cJ}>-AIvhRG5INCcQ{J3 z%@n1`iwFCcW{N-*naSgjkJ19VOMaUCBJ(MjmCQ^OMl#DV$ZvifS1mD5)w5BMm6+*+ zytQT{DFd^nN(2?z!(Ag;9B^WQf{)vpVN0~lgk>+9hl}z^YVK)Bo@s83O*WbdY&RCA zGTzEK+&G(MGx%sB%fc77o97BaUkraV0i|91cOps^{P!7@_VeFKC>`X#lTkXve+wvn z%YO?|I>vvWMd>*IokCUx|DB3b4gZ}6bya5WSK+;!PG$tZIRods=9nm8!3Fag#-2l| zj_wLGqrrE{+!EVdHlGmLEbyMv6M*ZgnE+hRqja6WI2-=8OQ^@&^=8_I=iuB8?r98X z-Z3u~2s4uOotY3-Kb!9>a$iYzZ#FH74*w+>Ai=aeaMbdiU?}n*xA_SMGQXI69sgT` zyr7xy7X)S%{gC{UIRwvDwvS=hLAD$dL*Ie5`)@dbCaC4}j;R^a~TmcAmvMRFEcGcY^JlCMdGOjyE$a#>Kd z+3LZ2X%uW%u>X=rH^i!x7Zs?+Hs=SS#t(v|?ByQP-`6q}LO1qit+EejCH zOezY{`9_)>NF@4vAc5%gKy#vnfo4RX4p0r!m_R(ykpVj0Ncn-rMDqeMM4t#WBHBMd z=P9XQz)Q4mAc|mU^bS;Gi2pcHb0tD)n%mLa!B`rV!R7nXiqN0BuQRbgR3;DjE*0Xre zPySx;r z^^Yg|5C2%AANWTTt@4i|y5FBqbfXmK>D8?cEJ3uu`HvCfE`1%yA$pOg1xRNCwB<|x4D=#;G9dS$ zpA(=qvqa3R4s@bF-U&QL6a(2r-wb3C-4p0QbY~z#r_+Vbr$N$N=Tl~dUY<{bB>y&_ z21#BUAW6!_0TQ&lAV7ka{Q(lOTo{;1baH?sEI$(<3CqI+Bw=|#ppa;fz$Bs_16;O? zhWK;o=l_Rn6ged@zB1i1UXllp!>1|uH(Ljlu5J(uKV(^AwAIDTPvERF{h#-Z{8UxE ze2Sm2Z0n(Dvvn{Pw!`*%yA$P(mS35&k_wb0B|+JO_F)*+$#PjC2lEl=U_UY2d# zL0&RQ=m0gc*cvAfwUm&@^+`)7*js8bQJ6;{BSCa)P-C?xLFQAIB(Pm#6td8X>%blv zPoA^>mIDeo$ve!qRKv%8Ev?Y{tmPJk|0h`%Ld8PMDqi+Qs69Y-&8kvM^Z&KZnv^)I zc%9{GoSScXL1614yE>l24Wlgc6%I8!+4|4^2Ib&;#Q*C-JFAqM*a*qH+>zKVU^&Ph z@o}56O%&*{bcUqimYV;Uj{V8R6F7gmWs+VVBVHDgz5UR%C|l_1 z!$;*WZb}(HLcBy*8CFlU^kvx3Z>bPi`6Gt@5Db!F`Z3|<5y14exm<>G#LDHCfij20 zEgkr0qXdlHV3F`jiG|KMgobUHbJ-A_w~_z#6k0c1{1O{V2_Kec z8G8+Sy3E}DWcgYlNVsp8r596QUnlqpTeg!&fr7_~Fg&^2k}Sxt65qR^K(!Kjq{2ez zk=;;}V{MJ?_FFy_*&f)JY9UkoAV2-=g?xvVT!DuygG9NaPp4bcpm3~pD0rP|D)jx< zvJWGxE#C-)_A7kb@-*yPFQ{<6zr_LGl(a@LerppSY<|ycgZgBv3fB9U$(ZxLE8&)E^>JP`0TaHo zd`5m6%+0k;B@Sw`tl>aRYq0I2WjKNQvS%5|+^e&YE&CRn`q~nKum4~f zAi#{HmR$VD6-zo}Z*%Uy5^(Y*OM{@-;T_0&-!c!jbhP?m!UvW;*yON9WZ3K*OHWo= zZz1d8Fw~YRRPSwG?-BInShtFt3mQ(Hcocm7tp}ju9ft%r!0LeF5>E)!rCRiO{hr0C zQsh&%PG;)+Nq>4gL|_`|I1_3Eua@_X9KR@L-ZPmQT1t zbiE7tZLL{Q*2{Vli~ZL2?63;-Wmng;nrJN_p^nyLqiD%lJ-fb_rn=V)uKVppV{5tUr{?gz28X>#iw^$%& zY}N>vd^zfIoIJ+rROJbh?)~8Me;PSQO#YnSIEN(xnjcud+DZYbKPB+c$#))07Sc1Q zFSSnpU!*XBRX%5ZQIZKkcyv^7AWc-^#Z6=V6D8d{{exoREm;^eudvMGQ-=kW*jXx8 z2i1y|jQb)h8TXec?}z4AO}w>IfXV$x<}{lc@eX|j_FdNg(4oI|5>$+|=9DH_DHUaa zbsssszNSr}<~i#^Sn0GzR4%vDQn`%2?bZVnmg{4^4&xFn!0nr3yrOT=_nNg0g9pi$ z0a(1wI!R##@L;2LvUYXJT>oT3Na{GllPRKN3+2J`S@O3VruZk}oXu7O1bkgt#_RtM z_N@@3f||2*vUMK5vdv1m>>6AeW~F?oSFK%C=~)i8Ef+$9i>VPbB2ZAt`9 z9yy+9cZkBCA6oY-Y6MU~3)Vx4?xY(C& z!gnrNI|=M=Hq5*1l)jHH75jbvLuu(j5*P6oEZWMNsfZ8&54${1U<#KAx-ba1P{#t%nL;NpoeJ@#&5s}v+- z%cL=|eu8Zzj&E*TFOp*=f0>I^bCQh$ni3|pwM8+_vMD@3ruN(Eyo$jP^@-PlN7HOs z0y9*m+wK@73h2D?FxpmJXiMb!)&k=)l~|IxWM4EjK$xplJZF@A`h~V|xWCz@k}8(S zvn@HGdaPmEqX>6@BsjxzXW zgKSGUOtE~3Z5lo`%C?AlsFOm$|0|=7JI}URWeOf0Ys(5|m)fpNOyhDMvZk(sk<@|| zz9Kw8Zte}~kXvROjqexQ+6k;d9hJ zW72Y41Cwl#beo6q#-{|U#F`gv1m}pU+-U11aTLy1Jkps3#bvgca?`al0yeai+X8|X zzi4K_N&_-I$e z0|?d<1+x>v3!yHIV(6bd6&8&Vdu&%2YmQ|L!Ui(9xIDrD6HMVwoUzZgoZQslUmTVJ zS*^m`;q&`#6u6@;ba@!Ts1Di)MwJMsdOHZsaLDF2F(+<0Zo4IDEf+r-AVg0|+aQ-L zsq%y^QX|Yx)MvIjN*S&E!j>lS6r(r0aS<$qiJ3|spHm&)^#(kcbl&zD)7mW41>CeE zq=!Ww#MsY5R=TYrFUM`*wvZcXCwxks?O!70h?dvd?p1zc6DdWM@P=u5*zUINa{(U2 zQbOpa#*TJy?w6RJQ1`v95qZ5k#5vx^BiC)6MM^48eh_mCjz%`Nz_EL_8aUM4;lY30 zvZV_w0}JoQtYUchJKG$BUjAhJkuZ*hEz-(x+t0S)3V9f_yV*K0e2m$v1eRU;B!?3y?IW15$jm#vWIC>Lw@Qg zsfh@#wg{XRVZW=VIb9fIUnx>TZ@m3?i4vmkH@7o_Fjgkn|Dl&7sk&feD1ij9BSbEn8dp{;~t9US6YUS7qQ6Jlzf%*#3dsn6|R% zoY{_OUSg-rtT?PKw$tfg)N)^dKn^bk7b7-UZU2VJWcf>@;bfijB8)j~=#OpI*<%zr zo)UO^@hZBYx7^;6;TN0ienF;qd~K)TZJ^sKM4h)sbNw@RVbcJ?1(pwb^vc%)8wc6v z?M1j?n|+?h#*^=#l7)UfXxEF-;jU*n6d$su@m@}V+#3z*@vX!5t^$<(By@s#d+iIz ztzEUT%@E2Yd&9m}W|N?BxMLQSEss;+&<=Yini-m%g1>GruTmp#Hzfb+j>DX*4OTK< zR}X z`z_`i)}6LrQzVMlpLnSEY)+^4Fb|C+M)6v5_(;$3Hv3|~I(vo4WOob(2PQ6{v)Y;qZ0m24+1ooL3O`t5r;eA*wbs|TQXHd12s=2hm zFarJzh-eEn=dH0=b;rI#krT;0P37Gl1Qm5w89)5pzFE&B;x+vr-mOV-aF>MSU+D`_ zgn#2B&cmQ0&1S`mlQJS04%a&H zcpMXDwg~qfw>ubgzhEB*MVH#YgNcz23JNWT>>iF);Qh|-g+A|-x_Rdl`zVa`Iml{V zf>oZzS)|)C917-qN~@{#mzbBq8|V0%Eyd~vjguMN8#0O0rcT_or=5&uo zA~3QFjZN~Nzn8K4OLCvE?F1`B5r z%(Cpb{a5Z>kxJ+SSarwb#ZPY86aSjzHf%rKaa>~Su`tg*i^TPPn=qKX-7e$C(T=AD zwgC!f+6h%V))A{xK)FwWqots2S~5M*hO(y%9=1nE>?W}JjL875DUM0n#uvv0D2~2w zVDKrOEkg7L?mT?4a;l@;%%j2Y&E=0KKt(5t21f-PYqXaaCkNWo&N7BS7|HcW`QqMz zcC@)fKDtx3xP5@sMDZ+sC;wrcn=2PMC_1|p5`HjDfTT|yuVLs42d%zsVEw?6gZ+yg zdpRyK;v+{XZdmIWr?4GZf7d}KSlOg@ddT~jkg`2CIS7gLD%AfKu2OPzE`Y^-1LyqzC-pxWF6K@=H*hNagta8v1Yaduw z@`-hfCYIriq|s@kTcwN|)r#_Q4B^mVwPQAJKkgvxUY4Xg6x8pNp?}9IxQc%Z|Y!`Ay1x zaa?47mxC3XUUT^L@?$i1!9n*!(O$yC{Mq&K>Y+`ZKu7LX3%WgMm2~`jiKj8|-;OOz zK1l5yhFganpW~Oe9Zh(G<&ioAWvu+{pp2EH5Pr(=H1z(`5eFN}iP>-OIeaFGg5@u9 z17{Q#XikDJR#lpuC-nTo32Qkdi~_;@a8S|79?7vp+u6I7E@vBwf3;u>0eUi=v#>JM z*HRqnUUNQ zs=p8(!<tDIY7bZkAr95oii4jE^wX^3B6aloNV2SljOI^n$Vnb%N9H1RVjyN z3UVF2{0$5#b3TDjt##HZGKE23p$+GPGY`u)I{Py1+qJqtF4>?` z@FvjBuc7@G=P~&*y?PajS38rT!(l@lQYH6IJn@?{$axr*_>9J?-A=OPzpJcpuG90} z=XKq9HGe3X`;%PXaXt7wT{ocWyp`1L9s4-=$Hxs*Vc1>!3LN#8^NzyZ^*c$6UT{8K zPk8dAGl7BOOOevt%)&_nz5WATw_3EoQ|GL4gfYBhkAuDa$k01+wqYu6df)k)%AMV> z4CA#-Ljpf^V648f#yMAFPvE@M<>Bto*{+NS4SMyq2RXM?u{unK7{UymP1g%c@C% z!7%KCbG-V~(q;iVAO11Li_SYKr}C0jhMbGez49I6;%Dv&Ae)n~#9*c^L5{Z?dCuZQ zh4_8i^(wB8aD@qY?S?aq5tiviXix8#FM-T zKd?A61z+jtB6BUPokW=P&Mplagu8fNg7hm@tU&fTk!L)*NTsQeeAC$tR^D_z3&q<+ zD}Lg2(MMP`$p2Ky0_!`T*1V&g@JNh{3?c(``N{bs_QGB1G#Tk)5l8J z$OPi}F$;bcvvhKU4W72R}BUz@P#2zvWsB;9>%e=v`YK)5(fTwb-%Oy#9e|!ilg+5~BB)`Vw3y_|RvRvp1C&-Dj2jR}Y;9)GM=y{Gt&_tl z3tXhjA|QV-iR!FB)LGiab^1{}PT|+pO@6npW__Z#*O8A__jjnr4 zZdR-hJPk>ksfi|wpAU?IEt_2dHGZ)nFq#f658eK>FPdQ57FRDb--nT3UuW_@jEVxS?qEd2w?28VBQ{= zo8e1uyT-~aiLgPgVxvqh_42k*9&mSp^B=pSSz_f0S4W9E;5Sd=w+GRI&Iws>gjukv z#ziNHmNW{jVCBGeZLs}m*KwYgncGF*6q5!wsu95UnQt`Y-S)X)*8A~^P`|$sX+53X z2BmXc*yKj}D!m@d4#z#mFlkRbIU|ZD#FGC+C#S)!E9nWPJsY_wPr-FqG`CA?-FkHE zF|AWtC$~v~S}EEEM;A2e4SN?giUeOB5entW-*b`iM-jy_N>eDyi)Fb9b%hSOu9P*sTPuV&bic3g ze9(8@1UZj!_cIVCY)gVWkICJrj$Ui_ZSAzCpl>t$b)lQk;ze71*@V7#d*O$RZYTbb z?Dhx*B3!oAorxV&-PIx;Ci0(YK%t$sZbF9jh2-->0@SQ_x507g?hW*5`Yx-apO1*fo{xE!lKodEK`XG_to}954=r}>L zis51!{NiJQ0+OJC%Yz#Q^1#n>0+UHZuRnx;QNegD80=mpaDR~e1Tb{E;QmMM`%vFZ z)}ZWN_j3qC-4g}+C`sK2x0eDWPrIMOdHL=I21@qa_ibA@B>omU2eOQwLr`!zvy&Fu(g_ zc}zvez!XRbxW}t`J9`EwMDf`}|8t(-Lunf4ogrYG@4hJ$ibAIEcI_5jaK`WM4K-`^ zi*eYKZi4p}chwKVg$vzt7{?WQ8~w+C01ZepXuCHu5xTZ?e~ky0xcdlfBou$`CZy|f zHzn$g!ooDSD!>oVM^O^Wdri&gUG4swQNpPGMIjFgZU}q9yI#nJC$nij4&NMQfIT~f zK49JL{tatN-6=9Xn1 z#vRK!)$W!LF{e=NzQTE#Paj6ehM}-cxnlB zemS%|MxJz2lI&DIffgZ6pKOLFYTa$PJ80dt&|+MB+C5oeGv3S}7Z89}T^I0flAndD zzN!HZEh44+r-XxR&=bU2@})9!P3+kF+o*Sh!1VDXC-;y9=J!cBCL@;-%u8IN?HV*+L~#E3G3V%HV`~b6&~*TH5k|7 zYqk--u6gF}=>cyl^!lu+8x z^9;F(3ZD~CG5lwQhdw{F7}JM&=uz)eiUOGv#RxKyJpUxe5M=%6q(r_L4{ZfYvGo|w zM+{Fk@w5`yGH5;CGX(RRd$x*fIn27>W+4<*d93jJopz7o_eq{mN`C=^DH1w52j~$jtS6V ze25)xs3EhkUtiBESs$3k4~5*rG#GNGirkj<*dKL!Bl+Uwb?YYtC>Hl-a0b(=2x-m> zE#{BNxg(b*C()-V@|0%(Lpmmg-wgId3Iy+2vEM`Sj3J&Ylmm+;2RSFjF&-~k{9+WM({ccFN=?DE)HI<( zW`hc$EGq{nE6WKSSeX;3Svi8GrKN(Yr8z)ZRt3aRC29XQQjFbV)pVif)ZuLU`-vx?(C_DF2k^jO+>wc7Z&M zMzzzuBbw+gCGH17BJmmBXA<21hjo!sQ5#)jy4z9LjSj6!hP84yRKZtf>Qcj$@DILi z5hEdZT#M%Qxw=PX%2lXW30Za|gT9id`$EK~))ptf%&#rf&4RoZ{M1db=e@4Op0|cK zE!4GwKl+{IM%=AH*HWVZZhjnU7&=TYK|n`HVsqvD>mCUpko_%Pt)wi*(-DMvQI{cP zs8)d!|Disg-dL^61{8GcC4&tb1bQW%{#wHM%$D z(nmCHEgms;tqv^s^%Va~aub?2RF^|@zfec~YrGEU_%hvb8GxXcR^11jGDlt^(bR4EfFKQEok&pbV^E?q?=DGo9mj;h<&;b1b=6;ZV4T2r2|`RitaqlKA-8@ zNt-z(UY8_s>K8hMY;1uWObm|0VsLDwlrB1m8dd2&(n@m9I;~qGDc7OBm@*UfAJF1n z2JAZ3`W`&!j4n(#WJg`lmB^Ll_{k?6|BZg1j6b`gOO)h0I=AQCEBf&gPr0TenY5F# z*J%-7Ri^_M0vu?83ntY~-9CW>)i#%)((=1*b|@roR>}04l5#s9SV{4f`sq}~>XwxM zi0(t~tJHrillHQ+wSJ65i&AvSlvU_bQ`O84Dse(q^T%3!iAt%)0BB1kv-HnB7y&Mv zB9;4BJ{eGPM6~{AnX&@^|7p=~)i^E=H(2EKl3xFUbbu-^>-X?JgFZ#XmPWB$jeIzIJ(p5sp?mOKxG^&OEJwTh7SGek>W;|)Fa zgJjC1_}OvJ9;1H^TmFaoH#znZ{kP!LP zsK@oMpnY@o2(d0U>oaIcsy>&F9n~k2W||%j<`Ct#(DrEVryIXnajIF|B^@l4s5fas z56Q&ypVsHe5ZjtN9wv_cQq(Y>nWgt@qzGO)OJ5;r&aP7h;QBr(_!cvQdp^Xz^ma+a zvMSF}dZ1|x#a+_%;mo=EcjQtH6jBWvWxV-CeY#R0UaW4x70dNH*lN}KN>;t9zXRS1 z6?D_ZbBEXU85-dN-_=>vbUOg`J*qUSOG9^somH)c?yLSq(MGlSyF_Q3=xsD|-2LG# zp8Aj+5vtWc>Dy4+JG!Qv+#2uUHrMn6Fw*}|Pb<0eYW;Pkkk8nm`>*$XF5ZI3ROJ-UhuwCcpY{Gpe<;aM9^ZM-)Gyjebtz zO=WuM48n*1^Zi53Ed3m@nO|Sy2}Hpy+V8*0;1^}MjK3QcwOchr%rQLzNOtQHKvGAk zP4&6_Qic8nxl~V?B|3W%8DQWIHW*~w{eZqwl71%VRPcLVSOTDD(J{S6xJba2IFD_l zYCpm=b3A$zmwv8)6p$KKKd%4$zsC$?%(MqNhbo?qZq51g^eV*GUDij-5DTntTlAFv zrSh-D4E>FxVhwj?Ec;e3NzyIK-lt!}aTE0)$~f_yexX_cm;cs=owsI|{-&rF0668J zT=JV9;iqxm>Z>S!yrCJF4%aIXpZ1eJR;i3ZjBazWsRfTNx0lTgfO5NT?tit>E%C5F z@OP&HS;%gHm_A7nTny67%#TCIzxO)nXeDQcg-PLynAu=DOw$pQUkit4WK zOcQ%Z{W$xM{tcNFLh+;YUAHA0;7XEMp)i0nBd7Ey4eS1X@(DayrJypsejqs}>O(2& z?dESVLX!3X5#mh-Lu*N^eLE_kgQTBFlzF*?u2!j*PYM8dDTB67w0=6ut^y561xJ6H zVI*T+>mZ|)eFg*&m8un#)54HLQOET-+J&ef#By9eA8x~y&3nrb3n_Id_ZZOjpR>OF%sn9D?@fL=F6h_$(8fY+FKuSx`;zD`u4F`a9vPok=phSlONZZ%*wbk;ZgU7EoUgpIR@>tkhq4?{C8 zc~_T2`L7rZR53uGKs84jVP6V?(OCPY;W0|PfVO+JG0uT21|@s>?O}#|wIE7-a^I?+ zrT>VHnTB1G#_&#bz@!M--{9_b%#e?vx;gqnQEmh)sEl0S!%!~qC*ur#!1tl(r}Ws( zCmJrPC5w>1e2Z6Q8OF&0l$Gq$k0y+u4s=+KU8{8~eLCNoVK8YGkftaQk}yI?MBf>f zrON)+69EhOVt<#0P#ReMAu4luj_5rnNl)tE=GA$I%^E@1_$X-NPvHYA4IR;uXH`N4 zO69x)gnfZv- z;#w1!iR1`LUka3NC?%=0Ae6i>jTNSz863w zgzDWuBCbmV^Aqr|fcyR3yaO2uK;KJ1#KsRc~=EcF~ zuN|F-*c|P!JUmX@I}eZ3cF4mcw5{{-2(2#rl@CdCw50B7B<>3)pZJrZXSsosw z`7_Us>y-E0A8uQ7Kq06#{gceybusT#hC!!J74*A054X46$r((DuCB3j|A{~<-UL#*F6Cx zt{(?b2<5f_3ZZ;2fI@_L0=WGD$E|2z$V0bkpUXohYNzI*`?Qnu(0$qod7}GbgQsG8 z-_akLq7TyHp}1~#0j3Ug9QA6+Id*lL)sQMkkj zGU(gGfgNtfU51s)Hia_+&hp)cdy=vx-Vw*?7j^4ojNJ#2;I@5+0a85B4*e21yNvot zRQ07E(yuF?hcl)lx_L9E>WUd;(kx8T#x0;R>h{h?(c%+${d)FH>HG=JQDx~3&fBa!k$%N*gsv9|1HhYDt zCRqXu>CRtdQVLJ?n&H;V>K+lwiIVX%8QkGho0&n$I=9gh%ez&^o|4p^r?xUrka)7z zxLk(d?y2!+98bcH=j2ikFg`WSDC-4FAFAnKUPF#FYYXlfZG^A{97uO;q6{hgwZRxG zSBm-jFqQmb#OcdoT&Hkn_ZDCFrVkyB8gX1#<1tCu56>9L@mkBD65mZSDrLx)u==h5Xj6;; zXwzv^v;`!H9!8KL2Jxn*7Nx}6KE^*~(xX)Rg$c*je#X@ThpjzgO68tu#v4JQmAeh5 zPHDZdF@4vrv4-x=i&F8Fjn;({J03Tl3&wD(9;4ii8V4o+Y}`~>+W#Mc_=G_G9}08M zDXDaCjGb$~F?JW|f@&DW{}jH8Doz{U=A=yH0TIF-tvB6xz-XJtdzL1=BrF@%;}l&7 z37M)GQy~@4H0t>9L}Moi^l{m>@PXiPT{M0j{G3vz8TTP}rmSaEe7tE&bQAH6w}S5* zNs``l@4?d_r(Klbp{T~D7{()ai}^8np7B4*iC;_zwB*q*7#m5Uqbnu_S^$j69V!|- zy`lP5^NnppMg~;|ImVfr()^Z|Unoi+8b-yxn=bP0T%%ehLC?8|8QW~05!>t-ingd@ zs4m4A&CM1X5yp)J^Ij8?t%>D4XDzh`g0&pWihLtx?KmnKY@WcAml~JLrSV*Cv$TsGt=@X1>*4Wum>OO#K%uBd@KMOSV7x{kKvdI!}_I~^146Jnybr99Gr?Y* z!zFW!V6owYCVY8zw7`V3jWf!x8mA}~U_C*e2PO++unv?dXX150-#VK#G7kUE_(YI1 z^^rxCasrhuf}?!H26!?twS26;Yy4eO!g^5U6lPvZ{#!`m|Bm0FOq(i~cC|4XDC=8e zH-184S}T`c6rOg`@EF=1T4R#-Br)Bml-92w(BVJc;3tTclkv`HfYk7&4m zN7H6W`6`-YF-682&lXk;2Fh#H&5teS}s%O z%(pizL`^Ja{ZiXETdH1&Lm-adR!=!&O-(3jwCNY%mbs6caEds)5r`(gG{*FT zgh0dGQKlty?~={MQJZZGq%EZ{rb&erS#S!3sNMnv*dDW``08yizz@iyx6IqM1pzp1 zY*=qYRBuz(a=U&@{=zJI;mX^=8&+(|KKwY~jBL|;pt?QTzI|MWadB}}-P_iL?K4bZ z`mF+A5u((lStjhjt10>woS4jWO+U(|VlH^qZ-;{J0@DJ{pPJka`19oCmJ|~}bxWYN zyLDfS^%$&APawZ_y` zfR$A$BZODinht_$MpdCk#8j*|?a+!$NHKMF@rer>U!Q=!zjw)Ysb)$@R+6eJJpqM)-+YTJA^lKAzRO(Goc|3#v zs!{4r2!ya1Al}QSJ>_+#SxRL$bnYJwVLrtZ%s%?Kv!YSMwo2Gmg$1eFfImQ?EiDDX z4TiD7JSli_ga`(H{I=;sBp#rstx@i6elv7VD?uHwY~V75d5|2Cs6t~#m+#};rN$|8 z%8E1>O8Y5yx$z4=5@iN4=2OmHfnAO=JeK2pp{aS1N|}l>z1%P{QKsgM6d5l`p`RU` z8c4x6%SMSY5e&Kumee2sKGiP#eNO53#=X442TMvi$mc$S1wzNxnCzT&&;s(%0Hv{au;C8{O)rA!wTQR3G(RGu2+wV{ zT;ndsEuTp=dZTFwMYJ>jCLN)38=SK$_8%MhN76 zY?>E*a&>vA1vyxz?JyP4joT&_*hm(HUB+8x^6XybS0(BA`&*)53@))kzO9dWp$uVz zS#MZP@DubmUqsm8Un5q>8`I1~Sx0TQ#L?=AK7{#mM zej4Wt)v3?t2D+m&sAN@4DxsG~3voczY0j=NkANQaWb?a7Sg_}WSy*>ZoBvcn%TM}A zy$I!O?%+e|7ErV6W}2JGq_fnmQ5b|}W|`M%1gmoA{e%5Va}Qc26!PchngcTBcX)a& zrS7rpAaK08@*jETP>}sNuGo|Xv}XErkKC)}2*|7satLSM;e%Jh|beWwaA|yB_KeLRa<|<IM~Zq*qIT59bwbd zggyXfonb|ERB4RhpFc7q+~Fs3j*YlP$JEAnA^$jmU)*5Ml}Qlo^M{pz9d1FWZC#mY zp=%sB&(cfcN4A-V2(_i+UFOj;@#-+Y*|^awbI(j=3%1w zQ)T?g0rQ(GK`;fM&AHR^vgk=iFztcrCYOveZ;_a*&HZH3uYW(MUD4NOJAE|SJXAbn zsan+seZ2;$@NhrXoH1|H+WY3x%7w`^naA-7ySKBwoTTPV$8b{jT#E> zvi240IllKI2!FyL1(p<|`U7zkQMqK>VM?bK$;Nl-UQhEJnjLO=lzUw=?~)Kx7-dm6 z73M0O6MIbUsVWsVWZEmnYIa>Q*CM2lmh3exe_*}77_?q*(~<)w58dl+4CSplOFsmC zCRr}?grChTBSuhuDAm%2H{LW4mdixau}E_q@3vXGKQL||lF3x}gne5<1BMuP z2PX!y7g(SJ-T$7sTtcX#1>mKOO1^Y7p`5Q9MXt6?vLeEE(~alM(ZPj@urS>tnWQ0aH>>(Lb1$@tY$ot%J&2Hye0(Xk&POG@7CG&AS#O)>yT1-Sj{|Mab6;0kbu` z%*mATkvu{O&N{ecyk(T6Y=WPesJNqb9dDXw87!B~T)Dsu_j=UR7N80is(jUai|Sst zgtI!^0wpplS1vZU0kXZ;WF*IWm^PyeEJb4Ks3CSfBf$*G)Y%p+m3FEuGV9q|ZpoE6 zCC<7A*ZG!x0$t5&8dfY`mk#F9c?(=f)y8k1SWg zwy`u3SwACZSn35`so^FsMQ^si@w(Iklf}o;<(5VFUqyIT1+~esgq5$b98@U>Alqbc z@!9gAh5SkYcIswa9j+OI_uesl#*X(bpcTQr5v5tSEY0{6VH=pucRsYJWWb=JV>ED? zuD8Ht+LEIUnpkX+uO@a{HcMlqm95RKY4sx!TX^JV%li@z;d_>vzoe8eQHsB|`FPP5 zca=ahEITbbG;PW?0kG1q207&%waaotri?|2;yAveRcO^!T29KOc#1x6xkvL>n*vmI z*U*Y8zPF^%e5l$nAHeE_Jf8*AEa0GhZuwpg*ed#p3>@xhR-8dY)JR6rNojyZ?L-vP zNsoytI`|_ge~`Wf4Rk~|qMB7Q8yy*hVAW&M&?VHLvRqYx{TTgc6D0PsSU2<08cPR6 zdvbQMsf{q2Ts-8WWur`}9Ii3VYcQ7x=F)pye#O#X2pme&6F_6RY6%EDEPK5PvJG_> zrA9RfYe`OXVYdmUjXHT-qd+>+r#Qr_vxPH!nidju{*UD|CE#LpH>!N8i=Ly5#)oj` zb~SH{&GVk+8DM15@0eSGlbK{i;y4KLS;ejkM`H-aoAj#r9_#>Y_@Z3hg6m0lmKK|D zGz@meU>}3+r@;J%xZxp=A0KfDU-`q1i_La)@P-~F6`4nI#-xY^u)by|?C153t&d4k zPfEWL37am)3Y+d>jxRPNnN`hxi-%tC7BNEz1wJMo3XQ}O5x1psmsKv4`fz$i#4_|_ ziAl}HUh9)G1ZOz&&7m@$?+r6bloD!ONSj(%2P6BabE!ESpZs&Yy+~8bEjolSL{!l3 zUo5urIO}sk?Vfj#R7k)nX>z;1A;RW~@zzk$tV8yU2q4Pf9%ADsn1zQ=-GA$(0Adm@ z4|~wi0W{`+EWO3@4YW@^puxkJ_O4|i7aNVR1sWP0W{&WXsUfqz&yx1QjxPDP6}?#e zB#lz13F|k-3hOr=JO#_I{~W?ydRZ}42SL{t&l#XKTKU6AA|698#+f?@*PTbXvfh#; z{d*Yy9_)|7lsaF`jx;NDW`U2~uL*Bx42P>*JdGT0oi8dj z{PoztP_7zhMRFM71bH0sr&e@!smB>X#bF?LXT1ZapQFmC(eeYIvD)~9$<`4< zkFoNMqdl))U>*5C?;zYUnvhH`J2p2r5!xiRJB8nak>#VQl9TyDJOB0qh{lq|7-u@xuJ zu{hA9<(&CeFcQXb^djR8AXEoT3VL2`j-(sCjWL9iKyMnJZ%m|BWF04sr`kS7HI-Q|D=ew0l<~1~HqZOmx&Uwd z@fJZ4^C>E>cGxJc+A)TonvRrDML#7CreQN8Ixov`DtO}|>p6uqnip4Dv*jXvsd^^G zKkPj8x1y#@R{o9EsZeG@-&9B+_u?tvS@R^#bA|H*8GxOH-ara>K5P94Bz1fC6`?EJ~Z%Bnpt_n}F`lZ6Mwcs|z*!Bsz$mlqmOU^#C?TiA%#Og|03)z;G zNz)}Naofg#l2|+0Zl?;jqB*5JVX7#!J+0y(W><}*{BLZbg`+bNT{o+- zmfLl(S=AVu>4~;{nP$oMIf3ztkY9q5q=j|kI|tD~(ON~JFXIXM@txz_b|Ld%8v*K1+u*=0DvVFfptw}UL8>yvaP; zd8i0~K?0_V=_V&dHdnam+Gv{&kl+*jVt}3A%(O)c)v-O%mK@$N(}szcG2ZsB1Ri6_ z;Z7p6m+b?=V@xgXa*+l)yN2>FQ*6lUw}J|~M?;@9%N8kz(pc@QNpYlp()P!L2Mv`0 z@r%3C9GlWC8(dLPR-B>M)3Gt?Cpm6pOds_8JR3w6in&D98KYO7YlHPyLUH@L!V1c> zUD2qX!o<9i_@IdhO=P8;Bjg|%o^RU%^7Ifm?w@2!;jxQt$QiQ~m0zP22?e&# zWzu_v@l$>&9Owavw#)`WkN3I0%(gE$m*B_!@Pmzsm)3==Q zx@Cparrx2skK6Hx5H$i=*CZf-Ypd;qP!g-3Y4;T->@K6x7eJUQos6aLK9u^AtbC{K zV})X}$POjMa5%29bQr))hN^wA3?JcXh>LjHZY`{v70#{p+fK@*&0A7`94QsXy>OK3 ziUmu2jA^*ta@8keJI2R7$q|p+ABWMiUmnHJT*l0i?Pyn6*3w0F2jr{i z;3)eBe&(7DLA|>;x7aQR`ewK1Q(C$GQA+uu8CWzS>fMy?lOr+D@3z4j#Wdl$2xfw) z>u67Hm~_iK*dG=VGg^wwZmgs_TXUpl8ej_Nlr;O3f&!tgcyn%GDtx$eDRqIp0W6|{ zu5O`}cGv#A=-!?9tE93?4sT&vNh3~p)&42A|DAI5cD_zqv^v2^aY^>+qBwggdaTMp z=ceGL(-zx1aIwbzB+x_3em9{>VZ~rAJHqYX%T@d1I>og^?)s7TXLxRu{bj(2kX?~K z#1F^Vcd9{pjST!3M7{})kI)5+eLhIg{RuRMTX9HqEd zn_(E;Z{nS@!n&uzsdZj&(0HE0l#jwW?WzrE&9pD% z33TpRJJ6yK^>M2ILXOR*w;fVsUs3JtCUFnSg&pJsXR7S0G5pdrR<0Oge@Lc08P};x zJUJr~wTG8}#r4DNjpfqUNZe>&2y8pj?htQgytyYLf;oG+y$`^vG4^sLXqNFZ`!wG9 ziXGbI^PaLVkcqq?k>PQFjgQB8%@q4+kst)ijGdQM%5ltXlWqS>259lv727u6KEn=- z_H1E%g@`cHknC7%cO&g7IbVo{uVlUbpj^7j zse`)#=*hpGe1#)6+kbgktbz<>7}AS zY;ZvAwQrzJ@1?Y+xB{<&He7D1=BWql%LGNTq=nr}6=p>W4+vM_s8rWh4%M?z1#G1d zHE5A1?AY55+p)LZ$+QiK@SM6JZYx*_Z_dBJ^9ebc(h`g35 zpV+(O$oE5XJU?>YzFm_3pjvG=c){1%H4L%52o1cz^RC)olq+xH*4tDW)(rxt$l}5& zm+gHq)a_V5{#rl(ZoQuL{4W^bcPQTAn9k4Mv?CiMLU`^yC=#Ja3b9t+ThSpf3;g7% zjKP;A6CeEtB%qLE$BrE(=krz$gvLtc}s$&JXur^d0FQn;!;doiCoC}qa8KO9IsL=$+c3#)tfruY4qtN3u{T&t=I|exb3^k=(cNDyv z?Hu78KiF|xNS@XD6oU!V2|>fuRSkg75d`SszY6R1uh$ey{M;yqK?v04k9SN90&f}< zKa%d~DT#){?5)dg97a*E+O0*&FbeKW0^vY1lXI>Ei_R2>M=QO+{bxEh%alfx%tmt) z967Z1ImckiNpeK+W9g305?kjupbciH`ecWXO4~WQ(T-%t2=QOxTs_y(RxUXxvqR(+ za#TjOWk;^#dl~S!^wVL`FUWHsVO5AC|&2-En+Yv`QnzYDKs&udG6PVBT#f}^;dafvPJR}mi*18g!(j>nxj%|vB zelkj^z*%geDj`4}-*z04{1o5FhZt{vLJ=Qa;pl=4uN?M?<5!9QC~<_!km5C6kGNoeEIg(K}rqhdtoW1 zW9to3sLt?;CWH4>2ftXcG_Vmg30!;JYR2tn9Dj1@9mfyB zg&+}XTgebU{n8Ol8H3d-in^OLdRv@xfsF0HI7X^4;phF~cu}SVS5MOo8TOo3QH&)> zY3EHgDx53TN)=RKJ)%G>uq0_7dT&@Bu)oNrz;#rHL~Qp0em_MgkYVf-N#=6@XjVbnz-63xy}cIF1(prgafz2Wql&e@7S{nF8j0vD|@ z+_a%WP7-~ zux^0!ngj}5W~CF9jx;AI9YZK}xT_hr8tlZmWGL0NcK^Y5hBLM+ zh2G0>_EHYpHX|>T>oS~?a%DPRI-D{s?m;|staF3NE{hP}-XfpzB+Ae6JE(M^OGU-E zT{ix6qEjm}&gNQNzff8jG|V>SYM8RBH8&{#qNkeDo^#ENitmEVb?rN}YuCO5$c&m1 zlwkwHj&Q|>(5c}OK?aQb(i z-tvDJp*0l;oKH%boL=MEj|ZH0t)lTSJ6B3$IQ@bLJ6_deS_9Rbc6ShS>Pb%j(E}tR zCD*+Tjky@p6G&O+UH4?F_}rP!75UDG0GCmw%<~~f&2g4XwDxCDKi*U%IyZt>t-xf} zPT1Kf53x7DJE*d;D}#ouf*wMS&)bO8Ryj9G0;tm5^Pe~c$i=mnMEc#v+nqROOry%@ z+})_|xcg`Bu*-Q=$ibDJbWi2!70v>Y45jR}+XhRg!Znr`eCnJh10Ghk&oz*WJA1Ek zw=bNHwF*S^TndI!747l(Xw%7<2;Onr*+Wu3jS|n~+U-sNn~}+Y{42J)SJTP1nrP9w z8I+Rk8qU{FI(3rtEak5D$8qsX&I34-^>l-QY9Hgy!ZFVU03}589OV{k{ZyLddYV$6 zb5(Gu!-X}_k>h@ZPn>nO7D-pihP#F#aTI!7q8{Hn_eisGE_3yutXDPRJotjMu|@;~ zU3-BF*1HkUEO=etoQ#P@hh5UPgiUoSri>cJH;on(74Vy8E+Yeav0UVN2c{UJna4;6U*L3)8#Lg%T@u^Uv6Jr8oMUt4zJHMiCa*F+FA5*OI960{j#zlKbfv4i4GoTQnM8#| z2*680e}<6F4Gn(D_!8vT1(bb6yO-`g>z+p|+iJRSRhBDHAmW;rIlik4tcHKpFd($$ z;*(t^T#@cfhF>8;^DtN3ah?n=o{$$V(afOY7v06%HoATcKIT=5eh&#|<9fMZ0ahv0 zu0xsz6O~+tt zwkA4D@SpPJAxboUEf1$uJ$>%oiRJ2Q1GWTLnvpu z>pV{y%KVf#9iir-k-3p36=UGS@^C0Z;SHHCrC|#+=wO_27P59dxu2@0Ii~hlLsQH1mrF%S@R@Sq$U=V<8$XE9<6mR3BIV9Q?9$7s?MA4CxH9zB+yp0B%hBM2Fkndm1d zWkshqsOsB)ZrdLsqESd05CpXQ_pRtyQWm)u%X!#-6WN8b>zp1E{_|qeyB?!O16|KNX!kav=pR$rEX;))X)f?j zgoVJc5H<&=)po9{aE<|baNDU3uE_=BhEKRG$F0FmIoDM}6;HY0{tMCU;IcfoSNiW( z=R-GK;}nvC<8QjcCGD5vc3NQJP-wl8xvNnUH_g4ld)+gH{80HQfD|*v5J`x zn5T8GL1JFczUb^PBBtN+y*%gW3>B<40vEI zL?jTG?}~?G^*7yX%P#9K3;Bo`dgwLRt{_{I(p0)QzO8qw6@Uzdh!U5EdtkYXVaFJD zw%|Pc9CT3EIn+(7wTilZ!A^_oQR)5fT`%|=o-Y1Qp_Z(y5|PlyukU9 z`1UKRzToUGCeDjkp%cAvl-A7Efis%B(5io}U}5Eh-2lH%a#@+HuZ*j5oMpk6evNd| zt}1dEJ#bszw@HHrVbEl`LD+|`)5Ov4w*ej2*SfH_p!MhSk}>WXl5~peF9tu_?<}I? zyEy%z{ksOAdz$Mni?6CixB#SGbwvk1{g&&mxlUtFHFpi=_EX)*l+u~P+NbpF_`}sz z#zUTUZA=s3joCw!+jvEK6T9|~I49F?ECSu|# z)PjDYIS=g%7|$6?fSs3q?@ptXBhKZ#{uTFQD*1(uPjdV_&bL8(z3FViTMMxNf`yp+ zl2aw)5hvYi8umBB{x(27>k_5D;!L6qLpA9XWlMzH>ypPQ_Pu`GW~UnzhsJNa>!lyK z#FEqz)yXxLYKripoRPvZOgW|rEjrrJUkNHz@r}4RT-abblo@^urnV8}#WN_ib*o*Zq|QbQ0Ete%l5QJaAJ# zb1wt*nAr$Qd>I$r#UMT&boWsLVXWWkN|SMMy?cGIZ))VR6@+xi1Z`VBd2 zTyC0n%U$rG5W$vxEjW#CQGJCgol_P$^Cenc;a*C`b6waZOPzNq=bSqr{SJ#)6HoK* zxdSwHpIb%P*tGmvjXO~;{lWF0yVk<^LE|zS_eGUt=W|!x4!II!NzHAf)Nv*R$HDA> zH`+(R(%vEGE$2BZy`|D}@d*@Q9 zkLWxdL0+WP{BUf=FLzUMvB3j_TuqrLeJ6sMXc^gxyC6jO9c`X?hKB0rB_ki>_c439 zhttzbEfmnNbrk2cVHBC`F_t&;+>_GeJewB`!FJ1JI%^)Sp|N>T1+^Xc45ZBueg^L!6Q{P>eTCC;PwGfLm% z7fL-rk~65rmGhD}iW0^{L)D{_`0`8-IBU>I{?IR0PS1I@h^xFk>OoPF{ue>DS|^n( z4FBXGJ%(c53Kr8v1vA1c9t@pNxNedMCxLuNvxQ<|&-x$b$F_Q=hbj=3c{8X@{MXnz3HV&=#~)Qb ztWEgGSZbJC7||nwC5h*<8NS!BMNEjz2o{HLf9gTFUt4m{_n#BfYPBSa5KL{YZO4^^ zJ-ISbg6AbMS19{1l67Yd)0#w!gv!gm@_edL`axz)dC)AUv2KKDMLcz3?VMf^*9_Z5 zDL0(!gyH=H{0ZkgUqO1i_O>fAcs|@Gle#Rcb=L+@g^W`nd~-3cIvbXPu2idV_Yhm9 zL*j{-J#Wjv6pZ@Z31{|I&liFzn46*bI=E4C-C^f`iGk8QAwdSwF;tB$_NE70Y%)pJ z(I5qedg~xNnACVC=ltoxnYdedBO$5&mu&j?G%vOBP%gE4BZEzqB63Zf+;-aAOfF#0 zIifp0(x|ERI?ZferSOiEBWrTy4^hBkv|YmCqv|qPU41L>g$?}FtC-Fz2s zH+eC+!mmfAQqCREvtoGlppAb_9`7XOz|F1mnsJ54+ecFM$u0Ee#d4$% zZJ`8YtbGJ>O0L%^YOfu*WP8^q{AM%nBpJBOCG~D>4=ug5a;YE1BOH)zab9>N`%}Fw z90$hsUK|()Ad{UF9DvT=V`|V_VFx3V)*ep}o{-{QAt-(Em7aSa$a{K)WMcfM9xxty zd%<{kl;Xee02S-!1u8a};*WUx^1w7N+{{BLzS=W~Ul{CtNi97_153O!d487n7S1Z8 zy+5gxpu4Z0OC@FUhR9)Bz0?~4caZjpg06Wj!7XdZMO;%R*9Iet#rG2dOlybdkII8L zumoUsnY=sIHBxEl&81$Wn7Bjn_c-O>^g1~w%lp2>ho16wR)Nk}n$pZnX*a!@;`4wS zSY+A)FAmgI4=3yS&}{D+l>#TNvWEC#yc6E`5^t<{UBCB^d(jII8J>n?Euw2@cw=bln_lq7cl(swdZD*TxDs)$tFjty$5UUys@%QV zT1puY?0DL{-bFN;yu~8FvUWU`jq>i`JzpAKu;Y82ak!h1mmQ1VJg-#wO}^2VtZ0D3DL1l)tQ%dhb^3Y~SJ zOI~}Nr(cIaRGEBOd94>#z%-P4b_1I=1`$X1>|toL_qeAEqLj-CD zEq&*N&|!Y1BbkR)d)GoLysZz&-XgxE^U2{OJ?Z@(6vK`_qY_$&@rqaz*DUfa!ii&? zuZCYs_RSJlr1Ouk4wSLMH<9eiv@ukX9X^O0dp)1?u~Z-85(=ntL2Ml*Rr!ET9qt^; zQ9XUX$iN`Hkq-YrT5sPLkpiR45)P=cpKqU9T1L`hpO;ef&6@F`!M-vO1}R>yg;>-u z-wSf+7{;r$Z1=r6lPuN!H-+@y=ssHJJm-q)NUWQ)4O)J}mLiA#@e+i80#~QuQiO>2zlK?WN z?g`%mAC&(YVag>^yT}Can_u^|;!kG#pu4l0(jV1Acz>?%Be}9TzJ1${l$xzgJq@FR%B^5seExnzb9hwPxC6gAI(imVU$J5l5o-%Us!mdx_O zC$`lGpV&sqjqra!1K;sOHRNNT8)3xAPw!i*Q6z~2K#&ucF&&1r?_nRv1zV}0sRubyt9%Cp4bbV-R`Rr)zTOf?ob>%B zAwW3w`^06`yuZ)Lo4@g;V`x+A4~gK9eCL~{QFeoOXM||%DLnL|?=eZU{gc*th=_*8 zDD;TqO#W8n`oVWbS%$KF!kIJus{v7pK^C_CMv;PO?*`cf)D1c!drJWSo}pGD{TYAw9n;h!uAk+1RzUmME!1yr;&j~}!5ASJhm-AgIYhj*ks zuZ6*Be$sdH!2xC-7Y~T-7_9gquFa24#i4VuuQShU?Oz}v%vbu*KY=#2i%q8t^1Q(V z;{D+oMXFGiMXuNsiW=hYSkw&I^R)G0sMrvkTZ9P$G){Yrqb2`a!I_-RDDq87s-mcu zJ$0PX-}4dNT|IsC#VEWaRdZFQKMtKX3d3+gFF(|kk5kkV4`OC|`(1J<8>TMO4r-VQ zTsPbUdr?7``gNeG@|w-+#J3WSZBx&OFl&VHC1Par*eum z7Up{XGvQg{2)b8_>;y&Qw)m%ATu1!s8>$_kZ9*A)V>j`E41bbbIz`zpYhg2v^}}X7 zP4#1vA#OU+ze26-i4H=JfWiJbMPob>531f@f+tLSp`v?U53wt%#F!l;_QLNrKa%&5 zBIM$InfW20i2QXQp~=S6#%KKLLT^!frXk?RF)wxu;!no;fo4tXN zdkkl&Y5hDMf_(-gt}^yZ{(7#zr4U0@z0EW*mGUiX9Pagl1vw1X(WwbsLqFMJ2kE#%(^=3b^O z35Thy6#vNJN%99uZ|g}D9r!g@yx<3^4>M?4uuwm8W+;A3Q5!w$87s+F31q(dT`m)+ zb@fcXzodzd>?xJ6^#cI?sR6jH`P{!2j>tAz2)(}nQ%LmQRhFEw*jO{J`u1XHiwS>O ze0q(u-xAAz&f0$!flvexI6}17IeQhpXUFTYeUX{>9Y4^E5bX`len;FDcPSRcp#;y{ zgvIqo@l73Pzc0Qi)k5T1j4jUD>p!g&vBFnd|KFaTD?;DRqV5mN`G>;Cblm|!k=NrU>W$e%l=t1#0{r^>UoT&y8Igc z+#73>@sc!8oFra7RJu*+UwDEqhYRV(obUkG{Oos&(BbqWo}~{;umC{JB5gij`_11} zl4O8>x0$GRW;jv)yD;$UR)+QF>l0&}3fwREWo>MEXcC^&>*?5CB1~9% zGI_hOyp9L=!2wjd%LjNdB-Bb7&6|0Jxs^|K%jJVOY-TK}mEd9A3jOyO7UhhWu&0BGk>fWBtEjD4p~jU_g=&Nr3&bvtn&>+OR1WA}I$FUlYGW zUX*2?Cahl?dsQTvtXIUgmc;9hij^EGatq?M?RnR!*oS0rO?zGnKD!y$hsSOO$o>~9 zN*mKXj}YGdmbj@o6vsUXpjjLHnb5T>2!LaXRcRhf@&r%KgMQyG3NlpSf7PB!8Qv^+pl^U4Zs)DJ<(iBU}$_2|x%THlxy9zC4 zi}`=wJELj!`Tl>sUJgCyZs(roKKI;n?peCwUnZw{XXvb;e$MD!HTl!rBVGCKt;9PUQusZ_G=0tU`zecs&)FFMmsAFa2)5Oa- zj_<+-NVmS*;|0aW%IAsw5pv>C4{(0SEZ@!j=j&brb1GXJzVl5{+XS*^{ByEW+-(xD z+1=x2LEHWsM2@P->Apd9^o@a)DOj|$h~+)e3Tg?paqu^(>5Tr;@4 z6&rEzC9dJ_-}Zp>qmL8*?AA)*wr6{^hk^>1)jbYtb%;h7*^`S8c7GCz+Pdy5=<=Q2 zlBnRZ=xS;>AD$p4){9DY-IvjTTf0qWb!&I<)1n1EhRXMhAYsS4e?fVxA|kdm>03cL z2=BRwc?$hr*ljhZhU&i%(e{pxjevE2K~c9;vQZx@+#1zQaJ7|)D{*JH0n`+u&*J{E zdL>+!53%2VJ(0w6A1GwTJ-Te5)1N9Q8{k;g7RS~>?ewr7yMam{i=M{?4c+I6``Jij z;a(cumj|TkcOu^`?R_oEPN^NES8_wT-lkOsQrenu$QM2I2f{_FvhSpXmWSi1zD}d- zhYoI{l3%)(ka{*endY6J1;K3A^4P6n@RWCLj*JOTUAPeoYv~ zMvuN-m=2Z#yT=HnMn7JXqsL|6VDy-exKVmoJx|onQh4xieW?6+M8;Zud1MH@jUn0~ z-x+^6Os`jT2rwEsg7!|*4-j=ZkfByz93C<=!9%RkVtagFi;F9;03i!t&gvuTylxF~F?>4w-thKAmrvpx-4-8k_s;k>+x; zeqR^@?}ZfUE5n6%@Wvb%WA^&1E2?-8xzg$?=mI0B@ZJd`zuDu@>!)jAZMb)`p_i5}X%dqV zW#6!bQqSuX#X6cwd#4%-sNH3KE)|S6L~(7c{${N*jViYo5QuZD{$d!y7Fu@c@g3cX z`bAV(+NRw#LyoG~zbMQQ6EYIdW8xp<1&Ds{;RH(}oCRvn=}+>WclCpGA=p8J@qY{6 z)6WtJ&x@E{)4B{^!>^mKo7ZTuM?6r|rVKWs@9Ce{&8Qw*HinkIuP+k1J0v!*JF0&O zcL#b{RNDbS4$0^AZ%7IfD7czif2=>Jz(b>Xy&+D+pKa2EDWrU+KOF*#LyU{S1E=*5 zYvHh4nqioyxspAtky3v{dRqiEm?Ng}P@p+NJLy&Kb538cQ|1H)6)$YIs4|yJz6KJM zlh#o_&!fseS{bQ+N>V)c`a>+SQmR|1Z_RnthQ2s%O)|*vC6V*F;m=k$NQ&_|{!YUi z@>6*j!Ojwq?s=*~&-X?BJLR-9Y@OGl zMcgQVU;GaD_rY(zKNrzn<$wQ#$KB}9L7o17Sb4|w^kc({Gy1de+tm+~^0*Fu97E$; z`eExH7v)Fz*VsS&7W{ta$0bqh8Gi#3&_9OOg>ii?%a(t?Pa*G!6 zZTy&ae6)YwB>d6%G5ffS{t5U!=LZeqKJnj%-=lueAnq+cXb|_BANy?F%l@(W-RK{K z-)cXo68E?t^oU#P2PNVb_(6#{zaKP+EAfK{akKrHecS_n%sy_0AG43U+Ygo%H^q-> z$4&Czh=mh3);|Eh|MB<7?`S_}ANd&X?P);xv7r+Ub&!%5>tX!eB62bB?G0$vtua4~ zkKBlGT5I%eDOQ)5PUHIh^ae7)Y$s zK`AYB{Z)w0>h+0hp&}T9?FS~a=ntYwo&loD3enAOetozBmmF1OnP=!hW=G;floD$g z!GDZ0JP;N#QDpy4*d1XZ$!!Jm;raqYgC>0CbD3omka#=boGiJLn5PJg8TkZTLJUV> z)+&eW*FcYv2;vPpv1$Vkyw zc;$D3r>w%iwY0Y$J96e3{ZSkTgPa#fq!y9SP)&+qyx_qKj4Xp&^Kwb3- z*tLTUw2_J~>&vNOl)*&v>h!}Y?^Z*&=x9Ax>k`L9k21h8m!|(17Qx@HFtisc?ZO8| zD!wNXW~bsIM+D`yjEv%OD-G+z5b-j^G6@S6s;dwEEsJ)Z7i;#3k&x4vyS5wT@?b{S0VH_W{bmq=dx zj^SQKw;98|OhpePRY?1T2KdcxrQFX9Ft}+jz~E*Zha5BD&W#an*8r{E4i0G)e_w%B zjsYp-P8&9ah1@N2o68ps9NJRR_l7Rvd)8XZrilfRn-r7H$G->F!gsEVESriX9|0BP zHTWLr-kCfGelS2Ui(Ee;#!Mt>O>**`KN)ay zvzwd4jTaRD?N>ttl-%1wjKFL!)i1zFvqy*#{w=RlT~xS@3NoPv85nMSMH~7?ApUv& z#GG!V#<$n8J=%zS^!*%?C|bYCA?=KVBb8#x?`)hB+QGjhsjLV-K*lM$15f0XO^1E7 z@rZ685PX}IkJ}-hL%Q)xsINKUm5lT7?fTjpIC{NH39n^r zGKt+Xr5$MGdz5e@16TY5jJV>5>E~w9>&VS^3okvP7USsifNGRq66`)-7Dcz%EmZJ` zs%Q0e3*t&XsCFcExG|QSESV3=iXR|-sHHy_oyn+x;mtT>YpQ?Mc%tfNBOIk}G1?HY z^5{-FiRbGf=8h{bU_`Uc9I zR-0OJ&IofojL(0{_)SaOBL*)k*XiH!vUg7Ok?j%hcu1+==pmw>aeVVEX@SU%y@$~WZOr(ZNGs8ftC^Js3t}|5q zl<4d=*;%+XFo`d5OD~{%)r&CRe9{QZ$*)LxMQz7>Vyw9u9=pc4K~cV@>Sxh)gWep& z2i{iQP#W6I7XEmx7#AkkFN!V>$&T9vJN}lcHw$czsvchboNfrZI#d$t6e62>{{mTnSynJr5sw4)`3eYq>qOr8jcpsW ztY*_01u-oXYCyEPXKnNG3f-!c$F)?GWbGJAE4Sp(<@MIjcy*%sUm?6j#Ug5p86nxd zrVvhbo3K;FQRO>kFIW6(gE&{z-!V)!j;G3HRwULtVQzy!uhw`C?@3d4%Ay3SRP3Fo zagZaG8bj?Jc#~ouhNeS;O$Ib@>kLPJ_jmiBy!5QE@V2QY@U|qu+x95@MzJXe5h<@{Y*C&5#@M1#{>Ip%Qm-(!`6e*7w7)U7sLuan zY`CPop0P!x2N_$135>1l|72`Y-TogLTU7V|gRwnrvMD|O2V+}n0%Ozv-;52J@%{&6 z!`;gND`Sf?{SU_Wvgt$Er3M(=R?ob7WH zIGg7>&i17VoXz_`INP^kT;IQOwy3OYIa`wnoGtrW&i0cDoUP~o&Do-Q{f)Cl<@^uM z7M1%q&KA|1z}cqZA~a)}R_XI^TrH~a-?&;-zaUpj%m7#G|3A4}6yjk1gR4aixSp#; z-S{`I7B%p1TrFzQbzIGyfen1{zj3vwo37((&J1w1A^*nJqL6*&pIj{p_XhvuYEk+B z%GF@zdX=l){CBQ~*iC)gO7j8Y3nI1 z6S)zE){*1nG_~(?L zWaZw0%J*{0Mfu!6@H_({CFS$L!1GMXy-m}Ks#}}Gamk+f7{V}HqP8{IRE$3)P~|>O z39&t?@R}1D6E#qzm+rF6)BbgqCY+@$0`CX7v>qi_#WyOs!Hk=p7E!afbf0Kqe$^jKUSdAas+ldjLAj`+l zB-~VCdl|q_?d`$jw#)==q>Mm=wuU>$}_p?X_uO8V0dwNr~Ip>?$N z=5bqXYf(MSB&$EeNjtII{GUPpTu+oZ^}9|J`?Byc^v;B6k>J;Ms0y!uDXUC9*d z7$f@JK)L0%T!;mkt+;5PtwKw+zXv*cR#K7F1Gb~w$zk~%nu!uC?BiQRJx539Sq;3o z#F`I!JOg?>Pt6IY4#;e2Ed=CQKwhB2mL@4v6#&;`wwBbsvqRX3M!iT$$89&j%EE$6 zue^#tmoHI$o&~a7tr@c0MmoCO+K#FXjyD4m+c#Uy9bh%fNvFk3Q}w1WI=|DLsBDIM zI#aTp`(UYUcibhVf?CdrRqOGYx*b^(P=^Rtj$|I&Qhf%7E4MjX^0+LAK^ASHVyDT# zO-@t1!h4>Vf`@8~Kt5T=0|MEXDZy^C(vXX`uPE;ab9)~0t8EK}{DA@DZ{?g$7z$QJ z=42`fwTJMu0gjHc=WV3)F&=5)kVERIabv}@ix$)(?SSZCGmF9v0{7xp;sww zw52;2lvt`k+=Bt)?xelxcC4FrcC4Ecd!Xhn&gm(sG&xA6*C=C_5Kuz*njCN%F8i;3#bSR)0gU+3n0HBJNJ*m73Nd~a|_bS89~ z`?&d5S+Fixu%Da9O2BhLz?r~PpZZ`w2f2Bc1ndg} z4sr7w2{;f09OmXy^%7q3XycvWwYNjtBs7RnG^(uyYDc))FB>!j8#Hk9LJ2q)1RUk& zB>-HnGOUyMyvNOzqUegE@O`SjSE#dAsPOh^5%oSduaI3e2D?IF%qJw^bP(_XH?Nj} zuY-UOxw%FHz7GOE!nL&oG+9a%f)Ws_@D@?Ew&hvhf>X})OuY!G`HCuaf6oc+<=$OtA|m#RuLO!dIe|~Pd9Umw+8S60pKKB_Hj6EVG~T z1*hGkeg?DfZT4`^+-`>rW=XDD@}tw7b{~p_zAssR0%thwK?&Fw1boS9b0y$_B?X=* z0fpbsf=pj=+I(4ZFj)0#PJ2WG8iIgtIBgLC;y`kfQ24=q@>|aJTA`%?N85nhNk6M3 zy84cDdjd-H#+ys4dI_!BVw;RE8-;M3y+ujRQdx9RDOr~o+}DLmaFI-Sx#mm5z>y#kF^SPltbh=)6JzqCAdpgX`)k67U;pg zwm*ZD&}pNj43w24Mb*5G#$;~Xp9!x;Xevj`)(=4txS|_eCA;{MbH|CIh8JxxQn+Am z!sS#`prfBS_cqz;VYIrUD9l6oPl58EId_sM7c7v$0#9QqiU~>ZrdZ{%@dD>gvEBsF z$zP4Bi0HY%Y^f?WhS1zsZ1TMI3+LV~yI(4h3ThEoOQbGx?me>SJ+>HbIF)JBz??Vt zJ^>QEk--~}ft(}Eq14c(WgEIH+|)z7jo)am#^j)iPcqv=)t>pXR{5QC=Za>vgG`uS z8waKbR**_X@uqaq;t$T9Z$-cwsdpULoVi}3T;kkEBwp9*9cv_Be{$|3Q6$(UgI#_F zDkqsF)h}~yxomY+`Iz@-CM1FuoV!fGuP7fsM3ZzA0@L-g=;+8PTLiFx-AZnisMNUA zjFrIVdjunuuQ*!6xlhV|ua)8MgHhZ!Hlt{&<=i#a#}!@~quzxmpH%vjc_=^Sc1*-^ za=jJC#Vw+Bbh6p@giuIA-4IHa3$~72@Pf4(cbwp)3{DEF)c=H`>nB>^-qs>Ij4Z#| zlKIZQ4nj9|fo{Sn;YL%M;GfHJ+JqL8poN4xWQ*tsKB?H#H9{X*f$0r%=;*cXYJVjX ze#S_FuA6bF!cRY8Db&!CudFb0Z4up?>kJaQ zT0qq&R0v_;StVWDaGhBe)ySgNINR<%Zxym(bX%^oqKF!h#ubr-)o?Btt3IPaES@^2 zZ1TKpvR>^>n}4wyM3eSh=aoepfY6g~!Y;OD)c;!8?_{@ZHZZz;V5Ni}4<0#=GU;@AQOF*hTc1 z0>3Ry52|j;=*0EgjX020j4-yRoZHn9&KqvLRqWQ$shoEJgItjmZ331?r&gQVGb9e2 z4NLOPU1ZJ9R9IkiamgfmiNZ_I;+{y z^#+iG!J08B#7NnzQ=GXPq!=mZfFP%zX6X6I7Nh0=UKqm5G1^UO`A~0EjJ6knk2|e> zsNptE7Jarz?Z`)V7(Wuh2%<^xLSVopFxJg=x4u$zi1t!KPNw@>Z9>JF42g?R-1`dL zj#=b$7Ug!(bP+m)7ypt)d?Y1RZzS1hW2w0SfR~ll$EA35bjYeGGhg894yIM1BQ zxW#)*;&TJ%O%Tnc2>Q~?HaHr$K-`PmpN(m(ewf(+&U}}qL)HD(RL<;df%D9b)SN1= ztJ5vmCI?dGB%_xK9yWIn*LTm!E(amrwCGYwpu66%br)R@ri3&NBJZe{V8;aPn!}n-a?rRN3Cth0g6Xx4f#>gnS8>=#+FF z$(3f)bT|Yw+q(0dv$jbRdJ9)t1yrrD;6`e_6*p2%4pTTi{i98A@aR!oIX7eabwXzK zF9Gu3N@?$!TXClj>gVY7TPPM951GFcM}p|lT-g)7@}4>&n$CMaFtPt|Wp6Q7UVl?E zS4^|qtAR4MvcCW|eq;&faXP!qAs#)JD+kKvuZiaIZ$e64^_e+S^gNC$hlmE(3!2+w z?O(`&$5Z7n)7{t0nYT;w6maE8+2`A$Pqim*pb7cEdb*cd$S0HSVUl9EbLDNKs9^|L_w-wEb^F&v9^oP{cYB59lMvku@JNzb zIyrTP!mTPWd0HNIUjP1`HhjYYgR*VUkUq zP=A%>MQq+;#2l_;nh++6$@0Al1ND8DeOiP{XdYqi2e%!2<5d$GgepIyNa0q%np?K8@~UZk4Oxj zM~9(UI&;+9so8F82WdgaNsEMB(bOCJ;)#u_41ym$mxe<7%9-U7kGdcp^Y~Jwz~hQ- z%NL3cot4-rEy~()TdEa1Qz>710#$?^O91!zd}*}=>*U>G5lUG4I?TgqBhK zS!;W4oRgUf`dQ3@)#j(M;HI3HXvGqLgi4=>_V^QjW>-}41gkvCm+I_@-+wvT8iH5} zRQiLxJLe6v*WxI1LhUMF*Fq$)Ft+8KkvR7Jo3T__qz!hn(TiB!XK9eScFEr!qrHNg zrkJd!MSO&`o6I;3UpYEeDMBWSrl)Pz35b-SHi(u|&)(>=c2UY{Z(>U@Dz_DkYH2{E zWr(yRB|;Slh-U3YyT_Y*UtxfE$c?3v=G_iitznTllLuAU220>Fns*0)L0gvlBy2en zVgS~twWLzc3D_$@Guu}Sqtx7uM(7aptnUk>)Oq&`S+?5tkZe;0M|NWht-0TP8|4Ho zT2hf!xjwTy7p=E-m#7gHzGSqq_Xqo%;G^FgwGz6L=FJmby%g*Un&d7Tj59%u1C!J{ ztZli|pLXosjgU_n@+tO`rzmZ>5RgyEce=#9M=nbQbM2Vnqb7pjIW*K!r&Qq-$|aTN z%d&Vi&08*{)f9*x(AD;nR;<4pyKzRW<%9ce@5&ZW3t}Sb&{O8z>zHPh z#Jrm3JuNUl7{nYg7kXOs@S=4LVTh!DNtw0Ro50*ILb@g#x3%I9ogH671CnG4kYODa ze{6GeuWpXLfKJW`K-W`3x~4UiKW2>;#xK{(pA7l)fW&eG&0~Sb=^!4@Qt=CBJNvAT z{a|7k3)K(fkdDbS)Q=*@z;&RIX9_j_X7*b~_ELV9?YZct5 z1*rzkJ-p7sKCg@4LmP|+_@T8SA(EBzg3(6&yr-6#xSP_dv7nAA6leUn6S z6BS>!_2A4Q4rqa{$e4Zjq|P2dVlyR#+oko(8K@{AY%+w+^Rj6zRc^2*^T`Y~9mB); z1x%D=NgdUVFd3<6m}%+toV2Z^{1&pPrhJZfs@T7-n7}-NBZS)&BUtpy2y%mgI^G84 zDW}xZjXw23Kx=v;!^UwP>o&8kvs_~*UFfAHyub_esnowa>8aN}q$u+xW_t%<~4w9ehTU_(H zD7wOH2P$xww)IgEeWG%kv4;>eaJ4eztWi(kvl555sqmt)wU9&WF@uwVnZCnC_ka?D z;r7G)8w1dHx#&JYpTD^7?hIQ$jdGBS9+b6(`KYk6kyJRuMRR5CYguj=lz$&6Kg>n* zW%*URQG(s5Ygzn>g^EuoELb8QafBe4-Y&;ryVVyM+Kgt z(vBC;B{v%7oWA9>L|I-CDE|(DTIKTuYqHR>eXn|~l)uE5EzCpnz-@mrPL&@5BD$uE ziV!Z7bnvZ;M)=lr*|I3m(|JzoA)jXio|~w0f;s=%yG=-zHz>`Vrpjuw1J!<@G`r;z zD{tF0TIEMN)f*Ov?GM`EFz^$VwufRUxxoe*@@G!VkRq#CoefpKt@?_;zfy%#e+Hmn)t(qi-e&@7NvV2{j z{0~kWBc9_kP4QIloK>=bONbPd(T0kAkdFRwNeTJ!Hcj-OwD&`71KAy5CCZLpZHF|P z=*yfoS@ykE_8rrL)27Pj9f4 z(`Jk3BWG<;AAV&PYcfX1X(h5z|3D+;b}AFk!3oBMaoPg$oOdiRC^WpknPJZQ*9GJG zH2eNgkxy&UN`%C1nIkyPNqluHE!_p%*?~c#=;(XyL@r4;;+7MdQ23p+h(X?W+#Dk1 zlO4NE%vCM~4@rx>+=z*#!e*&3u!EJv(Y&uLzN^MAxf)^WGE*?m7-(y&)izhfynLJ} zoW@8fKGeHpc|w5ysC~siEqQ8=F#|+`f^$WKZJ+3%)%6k`t_^)d><2_-hEHF%cjo^V ztJpWM+%IjGeZm@csMsQhi$2>>^PL(f7H6rdhV%9X(Qhlx+cx^_Jxl!cO00J@S9Xcz zP~9#)BfTQZ)W>AU?S)wj#MN)qeB53x2tqqh={6(I?(bP}{=aGq!_Z>|NY#-FU&N;X z)h5{Zbq)#-$zGGL@l9ZGnLw-L0E1yLm;j*DHN3_Afc*n4@=6KGh*M_UPiu9lx4SK{ zwe&*Hg80@2bNxQpFzu@37H~fZbk~`8K98-I^L`3egGP3cy)Ck)!_EhiT6Yv(3>KyT z<%JMv-1RC0DbVnPRrZC*EXD9W5dDQ9`~cQgOm}sX>Z1JP##U8b%pshz3RuEw*3wCM zF6iO8KshcUaIdvL41-xAtBKY~gC&V#;GJ&^P9R+kpyk8}2+t6c2|6BmtVTp?JtNzc1>0D;?i&eM5Cqt`?yLahxvha=>|EC@0a-zSgX?}) zab;DMrfJVbAJ~^`VB@O%Xw2v824evfhqs#}spyiL3w3G?<@dL?hPS>MtSTs?-7EC0 zG04DdZNu#w?bu&ET&J^$G4p~$c)2b@0)__xKCX+AfKfpJVzhn^Nk4wHwFyVSYp)eY zN#bO4U2EB(AlRTMol4bou6hhd_#qeV+q6nAu1l6RCI@Tea9tVzlCKCIWu@#Wm+QL9 zYDK|ny}3>=0W*StK3ta}0S^QLeYwse0keaEeq85}fRZ4fKi7Expu939wk>+k93^2xjvwW`bmp3_*HOg?VE0&|}3XV2{>t;$o zeGmYP)mfrT@D&3Uz-EPRvZ|)*Wurtn%q>*9A)^;UoLE-y@i7jgRv86rSJ~OYU}rF0 z^^4B(W?5X^Dan4HY&e?h7RqYJg4O;53s(s^4#4&z$HO6^vve&MpBxN#!k+Zj49A>^ z@yl;0djRRMTM7_AHWT-Zp=13^Tb12UY4d_(TI;w~3>FG6nGR z;7Ov(UnEKI<%A86D23w=IetO}ST6m+IS;~Gxg%bTuoq#SikvT19dvkc1aeBPf6RSs zIVJ{3o$Zu_y)87KC{>Rcym{JwImMVN??uf=bIcM4QX!W+e4lsn%!k&-VvzF;5+i`s>pR_<`Y$NV|Re)$>~2@+&*e1Z$K{O=%V3~w?xM<@v1 z3V0VW3=9~6;31`|hZC;lK?HY71eZ|Fk^sU1qJ|4B%LULXW_`~!FIBwddn7^OJzwtl zP7oN32mQ7Ci=xz&>^vs=s37Hx1BShS;``ct$+z;PT7DK@@ZNKn}i=bN-N{qy~a z=PLSnN>C}43YIu8aoRNJ=NcHi=AU*{2#RzR1Rt&??bK_QL_msI!}-S@NmvpOIln+4 zSm4t2uch#W=(t)^=bEJuf5v%SzUmsP{K5ej(+Jls3K#pG%QQ$c)v4TZUJx-xzR+hl zVSqCMraleM0aUx$8NrjvovRhnE^xLAyn9gnI1+Kb?{J8zbQb>y-{5k`T%~HU(|&b* z?2z4U5V#=!%9W2!ClGv=625obBNlZm&$;NFEo(kUb&DOZ5H^!`SJ%V>F^O3I=yj(a z26EW77@vJB1(vXzE{S)%&iOK>hPrN~yoa4P(RXhF+lDutDvschu6Dd{gR>WMQ{f`f ziOapz1J+~A%9Y*$HjV*S&?FP3Cgt>W8gXuBp=3kKR92;divfe84=Xnz|bh^1q;|E z4zob+u%z;yzp>%^z``!ccJ|md$c0#2d#UEAW68g6B#ezjhN6sl9kDl@Z^(7R@e!^c zMeeD>zRstiDynph6;s?N`b93YRrm%3j(xeLmFwXUwKSJW)tyqa zTsoeS>RKuy+t&4RA(^6~eKalZ8|b~?rlwf zS6lAb!}YaDVBgrw)t2hVBapsXb+u-X$@PvF@ogs$a;>C_k-#Nmr7Mazs;-e*+P;STIDc@xLudEx+9nu?tA;XlDTV^>r?0zsmAAm zWSQfF%!VXWFU3QZ)7J%2<2?ilLleXfSf#oUNp`U70j+Y35=P>LfKKnC4wp4sv0<|7gceG| z_*{^vz~?gajZ#A&LWiT<%_!l-dR=;Eqy9fWF-l%*~x z$R2k=LG~qmQ|5x*=t>vtM!!OcuMn6*t#ZAgjr!VKcwgb2QwwjKI+0Re1RYE2Gz<8J zY8P_dd_#NJxn}aLb*@EPM@2cV_pL5+QT<dQI~z3`_BDBR9H! z)xZh=)CmP46>DAJYn8LKcOUN2+in#PPzvsLJ*M!|S6nkR%6W>}?JDPGyIjR0c~i~1 zP^mO-bJZyP>Rwm-2;GCa7XH2PKSSP)`ZaM;lyo6FyeeOd!`!>BBf9*D=l8Fd*SEX@(t84N6tg<G2z~=-gb24xM%S7 z2+ibuk?vkfnAW1&ZK_JAc6eMOH)$x}5bfR-rs$|{sAmu~%UU=%yzDmf(~0g0!c??6 z(_KK{{@`-(6YbnnL~5qu1)krz*6w}~0V_JW7b&{%)MCA!R&_F_Q@pohGT)f$E({MX zn-|jJ3rcP_odJf zoWwp6iKFF9_j1t)H!?JfkYa5pZNJw=YOb#xVXQ1_EcQ&`A4j?4;R{cd)7%Kx@tAu6 zW9Wt9IxwSMEd{0z5W1MeMS6EXu;)A6=XD}tSqDxq;8jq=K5svoea!tI{^Cw|ss;}6 zM<;t1@{+0U``{2yxu3X^`k=_IhKGb8#+yhVA@RzkBagd>v0;|`MkTz{+8N@4c>h(b zd-1OiBE^ZwC?A``X=hzs75Z$8dkw!e$DO57QsLw7h4XKTds&#$neqo~)8y+WyzZ@Z z{-En|YD(9|a>W`?5spFoJzt5})P?FiUg$3tyHmn-cyYT2i`mU%Afjq@X{gbSEEPSG z-(aO1N`UT^*6ixZc@b`C+>w|go!?yT&W7bA-TjPb8I^pBtU6DZc-zpC2RvgqvBrH~ z6Q#fBo{4wQ7(e6AX%ngPMNez)vd%p&A_PK~3_2xe-W58*SmTTL`$OT<;EvVky5iYH z4WD*wjYzbf2&i$~UvhYz`*)a5lG57UpI5!=EX{B&(CkujeZ*iyLQ~My$%^eDIDV5ymy;t;cZ$ti7Ez<*Azg$ZA)#YMei01(8 z8Tn|ACsRvtzLrMH?Ca@G)lr_?l|H0o!va6=y@XWSd7Eboe;@7HAEt{&W`d%?^m1Bz z-U{!xbGSb$q{S~(vnoweZ7_ov6BQ8%HMT!ju5jNgKZ;C&^4DD6!821qIMn>Nyq(0Z zK9$RpJTtWl@|$(`-bhU&Jd0MS_}qKG>UHw;G|%H27&q2@;Chovx_c*5eNWFCKGfCY z)My4;sCjGK_S~t52kd(gDc{7KucBW-B|Td@8F=?oIOl@|U=t-mc4-?Y2s~*H; zzlqZPVQIXygQpnk6L-6C%Ixi)uiqF2}2!R^7pVJPgBz1=CVtM?$+YdksV zYMFNdm;yHHMU`z36DY(pg_@Ql{_411p7k({JbFh8>{5DoKG%lz8J-F{V#V1T0BO2yk-x8jNz z5BB%UiJq;Yx>)4?LyTUrPVpj<&-e!KOH|v=Gk_Q0?Ll-egx`xDLzb~E5l8iXcNTA| zbGJrZ-C_?E2xDpQJa@0EkHHMrAMlRjgqfZm5hD5bcDXb@A?#ych((Urez65q9O{09 z-zxDSNn#<@O!0osUzT~2p{%2{&YpMR9N_6mU!`@#$q;*CnAi*NRg}rxY<7p?1Dxpzm){P7@Nw*@@f_Abu~${? z?u<~|o-u^|>;};=f(q`63?aOWJla%)QZ>%KfE(6(ehdw18PWoIa<^#%+nNYbdDnW6 zKOv;WJNF69_RG&67GJSbI_Yzh=#6g${7I{IW!#HiisacY&j$r9!6 z!rgaxPKGH(RJY0d5#?NTA4SSv_pO3&jpqrkd!80nho{nY9SC!3N7LW(w&p?mJUD9J zL-k9%$ewV(^HZ1(-f#Q+3OuHY(6+G;RQK{HhP2pA&1pEB;k?vUAUtEO$B*RL`y|5s z#C1*Vbo`t7H-sY;GJoXx05*ug7C9u2dyqroema`wMP87Tp1Lq7FbZLz9C4QQkanKFH}U;MczQ^bd=eBjc^9Rtx2S=6dn( zr=x%2YQnxdb}p5sdI_e}JvURuaL;I7_p4{TMwv%-Ga*A)f9lB(yvFz%cL>#<^$eq; z0-UXK4!MW3^O6TK>Dq{ZiGnXPsM4qrNt2GNQFV;Fomd}tUB9jeb3se*&QM+3kQVQzi`Q^cE{9Td{wP=4y*3R! zIoCZD29ds&Jl)~x4r8QM?nuGlFm8&sm9l{A&F(~nZYp&@gnV82_SE_ATj-qX9zg|l zS)s6q@r3i?C0>(4-*oYIP!>|!qn@EF&Ux%qvd#^_|a{S9LR#;ZgROQ|vy2k7&!c*Y6*+tQ!?-MydJ|T~3I1iKx3!{M zUGpD*M@qQgdrr3!Q&~fnDmU`LJ>YHBLcfRIH=9bH_7rf57mdQiYGhFk7Ydn~=AInFL z^-ja7_EqmT>>W5?75F?J+PTgvl$^2axOts74~*rocMhfO!Zhk{^>pKyW!@V@b+D{B zY!*{kK%EYy#$noE8of4oU&CSitm`%U%;*lI%};srbT#PeS+Z;a)4?+o*ZbW%?y%bX zjRs*fE!(}RSS&xdM)B+#@1q*1_4ZEl;#OvzcV)OP1rpvQG05#=w|a4Xh`$rI*XVy4 zwqDU~K$VxS9wd?8?w@Gg$IouT>JfhLPxi#Ye5>~}kt3=y#tW9Z!wZ(Wnd%DMt?BT3 zZ!s_0<((A<^<3_Q?o1x~Y48*t$9L@auF^mYcl0fHCjEKBa}R?nE)Ub;!`SQ;xQ%IL z!Hba*&|`P3&GV;WFHV=!ptz&nKXhB4D)6Ur{CnQ>imnz|Z==E+yiZ-fd!D5nr}vYq zA~JUA7O~qo_kw2+3}C7~@6nfYym6Gb*zG3uPxpbq>#N`486_$*lGpctpU-)SRWRaK`datBjogg1u=f9>TkWf$eojcrXm^J8c6vL^4`FeG0{ zxafUc3E%zrKz|p+PZ!ZRd-8X`dJ{Feofu^gWmlxW&TszVMSh`r&VD@gVGTd3@y!-V zqe{AWgRUjShr`U}wUff6Z)oZnf`i-LEuUaKi)7XSaPPh9CB6MAm8)>09 z1!VJ^**=d(_a?e;;KCuEr|_PFg7_Ft?(IWxyrX3KNIOhy?{b$+R`}t5z7h@6L-oq? z{)fj8@GXeYbwk|KXk3qax!W+`2?Y488t&^WvbdH%2&kJ&hx^Wjz=5skR^O_y5NM|1 zqdldSn+6p6G9!*Jp6!P(+Y_@<)x})s8=%pB2#m1Jzm`-*skg+O+#VT@JIM8OvUMIT7vC`00T$g*ZPi&Kg4n zgJY(XaY@!oRn18-E56@%Hbkh+K5CUsH+m{&T<7!qnkIQ>nQ~N#vAczSCOGg;8*~ zg=QkSQ=mZ zs^DWy39le2Ox%2=o2bYP&p^_R$VnV=*0(l1#3E!qNXky0W6gpHoBDRRUhK)2p0oLp z3$qC~CqGQMdnAfv&B#+f(AugLj#SQ7LsHZPn&p9Dk5b`xVx;IL2^hyim z+K8;1HHwy%(#}&Ms>Eb1ht7m7H-+blkv~S1iZ)pYBpAvyhaKzoeDO{P0x!V3%byu4C3(LAKGH z++9bqXTa5ih)xk7cU(Nz?@wE41xFv2D;BEaFRaKHOqoq^W|xs_~!mu_D~%> zch5>32{?w+Q`3=%@wdTQI;{?`IWCcvnDn7g!n~f6Dl!p2$uYySknFHE^2~L1UY!A= z`HUTrwLzn_;pPrmamw}hUh{$lnT08OTw5e8&MKlTZPvc4xGxqLL2>PcN2qQHU)IV(&Zrcnv~@Y4S^r!8S#1BxOQq5GA(wh9O}ox<{5F? zcTnHEv(`o=JvzaUNIOe~iHd{LG3oG*uY~&H+x`RPUqFh%E5w+fwAx;jg>5RCDvPza z5}A>OZ!CpVC;Iv-JYY`NPE8ct*4@}P(!8wZ%Mj|o}KCQ z@vg;Lk3{LbNU}T$_*&?Jm06DP?u$qI;gtS_U_v(jdSzC3O+>ea6a4Ow7R$x7tTgbc ztZhpA_EiG?9mW9X9`J4LYfxvUq%by?UK7l?F}7VcP= zm8=OjRl58>k-iyD|pRa5#5Yzsp>x8~X$q?)Csb?L&Z zI?l{Ttr3}6I%A^WuhM0wv*Fb&u(eDoyuT;ng73#Aa3mf3 z-@YvDe^$11=m8CH!vpCaj(R5xS$}ObZ-u9ZX1C2Qqk^wOqv-83nl|)wfzq1CAIe%6 zsyJxxmaG}z>*EzXHZN$DH8?s(QPt>U7>HX^U}$t)b*-CWo~HKI1d+gO21d($o*Lh)YIAKz`A9nHPI&)O$ccQso> z#;>T?g!5fZSt~_2$fuWfFQlfMVne9maF&nP{ggF3LdoW|KeIknL?V>99Ja(q_f#mw z67iovCU|va_j~1%&c)8x{g3UsY^`uWzM9gAo5HhOiGYvA$Gg9yRK3={n(vOyUZH6E z+F^v2yP4d=UBXZI0G#m!0CdD>{ChKL*Ba@s6b z93pdehjwK(HG6X;^oNax>?yi|m0kV4Xs9u}R(Atx4x+k@1Z;;*3D}HH+1QK+bBLOa zTa=qP#4i30!8Kww)>a<9csVYQUWkfs$&+%j>%w)pxQG#(i^_v<%zjy;6PX=uroH{L zk%z2Y6Gr3xnlR2BoDF?yA6SQp(~HV)56^yI(T!Mkv%fdjjLaUV=tOdek@VQ$?ECn^ zQQ7xKD}DLggzN;Za?AF~**AxZOqNw*$*a8J?(F-a2HReg4I{VyAuS%G{Gyf?zOgtP zSzZQk?5ykpC4AgtI{yu5BKVqts~^lBujoXMit$wmt&j7`+1a>uEuhNhvvF5mlKn*( z6j*y|vT*~tAR9NJx6`R-vQy{|UqT$6c{#fynb&3SVBM1JKQ&NWz4>f5MA3?Dh@umz z_|1qksxHrtqxj^u$(;Xqc6o$yBR8(jPLY{~;)HFPpx~YK;mg@DNnDq`B0@J1%IPux zUt8xN6~%SMab{+R-IX;AWd)_M3%D+dKO!1XtMg_sdeq|21Y?iKUx)@OT0zlx8WX{= zw*Hn7xssA2o0ynrh!`WUZ6tyxMq7V?zvB;#oM=lL8$nGy8jStU;*ZS==kU+W&YREN zdGEe?_uh9eo+g3cs8kg(I16a=ld&2>EOg5zsW)Fxq3W=0|6uRTH|wCM%dn3eFs^sggMDakVfhqK8cK=}Fq$MZY30Xi)B&QLgLZRys@r4{xUEjTBf`E^ z_kGMzns`k8b4hL2i9GV4dL4-isjkY|z)v4m_ji`ZLkZ=hB^T{Gs}8p=S-r$F4hrWF zj}ArCsB`LH<;7@wDS7u>yHI_0rz~FZwR%59n807ZsuoJ}D>(Z{yqTB*SRlvt7H5-O*JMeg8H9` zE#Pmrsj2X8#l2K*V^EewrKe)rs>4{8#i*gRBieX4jy4vG@*BARPvq6i@!TEFR$Anj zOhMZWX~*(oE*36W*Ogg4lX2q+;~3!>+~%))vq7z+PV|E9r_+f$xnHlq;M)?hT> zji>8urzCGcGsTpe+e4$isl95qOCy?T$Zx7yEd zA#aXrCN)I55@^FRc7e~yU|U6@j5j6r!u+B9Z25vW&1LZ-%q)44*0nV2pYB1lLb0_x z)|lNmZhl@ivIO>at?h_sx`3>KlS5_iOs@YAr!k{ON&KHx)kLJHYA2=&OGOU}e+1*MKKCDKO~QcmeqM>*Fj z*_Wd5F*jAQ1=iqcrl7mCk}0fGA5K@6MW#{Z+YxQ0pR)&sRU~v5&nO#g#01qa!~|7Q z-jLrQ9bPTNk{8yELu@}4cpNWN?mrkN!h;OUg-^+QF*=H_+#NL5I6R#nI?O7~oe(M0 zI4}7D{wVL7HHq(PU=trEq}FOV!@{uxk}`gfviC03iY4>xlp**L zr;LqJQ28shUA*BE8;ERtB_}4JhHRpYMVP_I-DRJO@@Hshe@UTz z2A|!=ys%Ip8)qyEb{$va0fn!QI$pQKslj2?$ZC9({DG3x?(WAl)rPU6)$4qf@I0fb ze2ZSjXI*3O85Rpaq=Qc(edUkTRl8{ko{2B2EyH--7;UI%odd2f@Q(-}Hwv6T7FlN> z12W_NlJW?F@;q-`CbX%Je2KcTMOGHRFY8>2lh%7+1V zYEPlP2kBG#8cKlC)^Ez-Z_Tj%J95!d{J25-t4#Fc8t32$-(pEcRI?b$%Q zb5G&<1o*W--g$pK-#fkFkC&}2!OY89uC`3*bm?e}CnHdpC&AoENom@VJq0KL)(&18 z5xd#YLYFD&j-#ITS30xs*1z(}_#346RMV(H(h5jlsnr{uaklTw6;g8a7V4YmbQvA8 z*>|vs;=W~Zlt}tn>TlDCDjd$a{EkPni3s=!d*1#mzVs#7?$g3tgnoLAs?sH-bXaYO zu`?$Z6+#D>`lUTN>t_e|6Y1{vFse zngjdqcY)&nPsif_-e3IJAB*3@e_N%&4gFt#@qcFYj?9O>;~E6o(ji=HAl%JBxLbj6 zw>yUW(I4)AkKrDT_dCCY8x$hn0Mxw>p?(U4Y72zA9|-kx$50O*LbYj7LP1ZU_D;|- zoCJR+V>t28uW2Jh`7X+&nK~j<+}ST zPv~i|h4{0X^pucb1>1%c?A!Cx*F$vZ>(D@bttkHbTGyek!#dR0%CFk>^=KXXI^3Vf z{)DGpR9*aW()AWgaF#Kg^>*DDGG`DLMhc4lDae=B9COe&mnhhI;+9|m4N|(1a9mBr zK#muA*2{XiAfOm>cc-)Tq;oudvsoWv3}r$zTCt8yR#lx$jH+_<7o}e#8N;L_miYEr zh8nCeAM4*a;lv11qNy;!35UN;OXn+c^&(_W!)6ezkzXv=oruc`QXG8JI&Fc#bG-US zLnA28_NP!z$q77dxjx(~Wb+LkJx`P)aW$|PNB~|u9qm8)# E1I_?k9smFU diff --git a/test/gcfault.test b/test/gcfault.test index d54b78faf..2d77f9ef2 100644 --- a/test/gcfault.test +++ b/test/gcfault.test @@ -10,7 +10,7 @@ #*********************************************************************** # This file implements regression tests for SQLite library. The # focus of this file is testing OOM error handling within the built-in -# group_concat() function. +# group_concat() and string_agg() functions. # set testdir [file dirname $argv0] @@ -40,7 +40,7 @@ foreach {enc} { } do_faultsim_test 1.$enc.2 -faults oom-t* -body { - execsql { SELECT group_concat(e, (SELECT s FROM s WHERE i=2)) FROM e } + execsql { SELECT string_agg(e, (SELECT s FROM s WHERE i=2)) FROM e } } do_faultsim_test 1.$enc.3 -faults oom-t* -prep { diff --git a/test/gencol1.test b/test/gencol1.test index f3fbb0dfb..ed7ea567d 100644 --- a/test/gencol1.test +++ b/test/gencol1.test @@ -662,11 +662,8 @@ do_execsql_test gencol1-23.4 { # 2023-03-07 https://sqlite.org/forum/forumpost/b312e075b5 # -do_execsql_test gencol1-23.5 { +do_catchsql_test gencol1-23.5 { CREATE TABLE v0(c1 INT, c2 AS (RAISE(IGNORE))); -} -do_catchsql_test gencol1-23.6 { - SELECT * FROM v0; } {1 {RAISE() may only be used within a trigger-program}} finish_test diff --git a/test/indexA.test b/test/indexA.test new file mode 100644 index 000000000..518d7e18a --- /dev/null +++ b/test/indexA.test @@ -0,0 +1,350 @@ +# 2023 September 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 +set testprefix indexA + +do_execsql_test 1.0 { + CREATE TABLE t1(a TEXT, b, c); + CREATE INDEX i1 ON t1(b, c) WHERE a='abc'; + INSERT INTO t1 VALUES('abc', 1, 2); +} + +do_execsql_test 1.1 { + SELECT * FROM t1 WHERE a='abc' +} {abc 1 2} + +do_eqp_test 1.2 { + SELECT * FROM t1 WHERE a='abc' +} {USING COVERING INDEX i1} + +do_execsql_test 1.3 { + CREATE INDEX i2 ON t1(b, c) WHERE a=5; + INSERT INTO t1 VALUES(5, 4, 3); + + SELECT a, typeof(a), b, c FROM t1 WHERE a=5; +} {5 text 4 3} + +do_execsql_test 1.4 { + CREATE TABLE t2(x); + INSERT INTO t2 VALUES('v'); +} + +do_execsql_test 1.5 { + SELECT x, a, b, c FROM t2 LEFT JOIN t1 ON (a=5 AND b=x) +} {v {} {} {}} + +do_execsql_test 1.6 { + SELECT x, a, b, c FROM t2 RIGHT JOIN t1 ON (t1.a=5 AND t1.b=t2.x) +} {{} abc 1 2 {} 5 4 3} + +do_eqp_test 1.7 { + SELECT x, a, b, c FROM t2 RIGHT JOIN t1 ON (t1.a=5 AND t1.b=t2.x) +} {USING INDEX i2} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE TABLE x1(a TEXT, b, c); + INSERT INTO x1 VALUES('2', 'two', 'ii'); + INSERT INTO x1 VALUES('2.0', 'twopointoh', 'ii.0'); + + CREATE TABLE x2(a NUMERIC, b, c); + INSERT INTO x2 VALUES('2', 'two', 'ii'); + INSERT INTO x2 VALUES('2.0', 'twopointoh', 'ii.0'); + + CREATE TABLE x3(a REAL, b, c); + INSERT INTO x3 VALUES('2', 'two', 'ii'); + INSERT INTO x3 VALUES('2.0', 'twopointoh', 'ii.0'); +} + +foreach {tn idx} { + 0 { + } + 1 { + CREATE INDEX i1 ON x1(b, c) WHERE a=2; + CREATE INDEX i2 ON x2(b, c) WHERE a=2; + CREATE INDEX i3 ON x3(b, c) WHERE a=2; + } + 2 { + CREATE INDEX i1 ON x1(b, c) WHERE a=2.0; + CREATE INDEX i2 ON x2(b, c) WHERE a=2.0; + CREATE INDEX i3 ON x3(b, c) WHERE a=2.0; + } + 3 { + CREATE INDEX i1 ON x1(b, c) WHERE a='2.0'; + CREATE INDEX i2 ON x2(b, c) WHERE a='2.0'; + CREATE INDEX i3 ON x3(b, c) WHERE a='2.0'; + } + 4 { + CREATE INDEX i1 ON x1(b, c) WHERE a='2'; + CREATE INDEX i2 ON x2(b, c) WHERE a='2'; + CREATE INDEX i3 ON x3(b, c) WHERE a='2'; + } +} { + execsql { DROP INDEX IF EXISTS i1 } + execsql { DROP INDEX IF EXISTS i2 } + execsql { DROP INDEX IF EXISTS i3 } + + execsql $idx + do_execsql_test 2.1.$tn.1 { + SELECT *, typeof(a) FROM x1 WHERE a=2 + } {2 two ii text} + do_execsql_test 2.1.$tn.2 { + SELECT *, typeof(a) FROM x1 WHERE a=2.0 + } {2.0 twopointoh ii.0 text} + do_execsql_test 2.1.$tn.3 { + SELECT *, typeof(a) FROM x1 WHERE a='2' + } {2 two ii text} + do_execsql_test 2.1.$tn.4 { + SELECT *, typeof(a) FROM x1 WHERE a='2.0' + } {2.0 twopointoh ii.0 text} + + do_execsql_test 2.1.$tn.5 { + SELECT *, typeof(a) FROM x2 WHERE a=2 + } {2 two ii integer 2 twopointoh ii.0 integer} + do_execsql_test 2.1.$tn.6 { + SELECT *, typeof(a) FROM x2 WHERE a=2.0 + } {2 two ii integer 2 twopointoh ii.0 integer} + do_execsql_test 2.1.$tn.7 { + SELECT *, typeof(a) FROM x2 WHERE a='2' + } {2 two ii integer 2 twopointoh ii.0 integer} + do_execsql_test 2.1.$tn.8 { + SELECT *, typeof(a) FROM x2 WHERE a='2.0' + } {2 two ii integer 2 twopointoh ii.0 integer} + + do_execsql_test 2.1.$tn.9 { + SELECT *, typeof(a) FROM x3 WHERE a=2 + } {2.0 two ii real 2.0 twopointoh ii.0 real} + do_execsql_test 2.1.$tn.10 { + SELECT *, typeof(a) FROM x3 WHERE a=2.0 + } {2.0 two ii real 2.0 twopointoh ii.0 real} + do_execsql_test 2.1.$tn.11 { + SELECT *, typeof(a) FROM x3 WHERE a='2' + } {2.0 two ii real 2.0 twopointoh ii.0 real} + do_execsql_test 2.1.$tn.12 { + SELECT *, typeof(a) FROM x3 WHERE a='2.0' + } {2.0 two ii real 2.0 twopointoh ii.0 real} + +} + +reset_db +do_execsql_test 3.0 { + CREATE TABLE x1(a TEXT, d PRIMARY KEY, b, c) WITHOUT ROWID; + INSERT INTO x1 VALUES('2', 1, 'two', 'ii'); + INSERT INTO x1 VALUES('2.0', 2, 'twopointoh', 'ii.0'); + + CREATE TABLE x2(a NUMERIC, b, c, d PRIMARY KEY) WITHOUT ROWID; + INSERT INTO x2 VALUES('2', 'two', 'ii', 1); + INSERT INTO x2 VALUES('2.0', 'twopointoh', 'ii.0', 2); + + CREATE TABLE x3(d PRIMARY KEY, a REAL, b, c) WITHOUT ROWID; + INSERT INTO x3 VALUES(34, '2', 'two', 'ii'); + INSERT INTO x3 VALUES(35, '2.0', 'twopointoh', 'ii.0'); +} + +foreach {tn idx} { + 0 { + } + 1 { + CREATE INDEX i1 ON x1(b, c) WHERE a=2; + CREATE INDEX i2 ON x2(b, c) WHERE a=2; + CREATE INDEX i3 ON x3(b, c) WHERE a=2; + } + 2 { + CREATE INDEX i1 ON x1(b, c) WHERE a=2.0; + CREATE INDEX i2 ON x2(b, c) WHERE a=2.0; + CREATE INDEX i3 ON x3(b, c) WHERE a=2.0; + } + 3 { + CREATE INDEX i1 ON x1(b, c) WHERE a='2.0'; + CREATE INDEX i2 ON x2(b, c) WHERE a='2.0'; + CREATE INDEX i3 ON x3(b, c) WHERE a='2.0'; + } + 4 { + CREATE INDEX i1 ON x1(b, c) WHERE a='2'; + CREATE INDEX i2 ON x2(b, c) WHERE a='2'; + CREATE INDEX i3 ON x3(b, c) WHERE a='2'; + } +} { + execsql { DROP INDEX IF EXISTS i1 } + execsql { DROP INDEX IF EXISTS i2 } + execsql { DROP INDEX IF EXISTS i3 } + + execsql $idx + do_execsql_test 3.1.$tn.1 { + SELECT a, b, c, typeof(a) FROM x1 WHERE a=2 + } {2 two ii text} + do_execsql_test 3.1.$tn.2 { + SELECT a, b, c, typeof(a) FROM x1 WHERE a=2.0 + } {2.0 twopointoh ii.0 text} + do_execsql_test 3.1.$tn.3 { + SELECT a, b, c, typeof(a) FROM x1 WHERE a='2' + } {2 two ii text} + do_execsql_test 3.1.$tn.4 { + SELECT a, b, c, typeof(a) FROM x1 WHERE a='2.0' + } {2.0 twopointoh ii.0 text} + + do_execsql_test 3.1.$tn.5 { + SELECT a, b, c, typeof(a) FROM x2 WHERE a=2 + } {2 two ii integer 2 twopointoh ii.0 integer} + do_execsql_test 3.1.$tn.6 { + SELECT a, b, c, typeof(a) FROM x2 WHERE a=2.0 + } {2 two ii integer 2 twopointoh ii.0 integer} + do_execsql_test 3.1.$tn.7 { + SELECT a, b, c, typeof(a) FROM x2 WHERE a='2' + } {2 two ii integer 2 twopointoh ii.0 integer} + do_execsql_test 3.1.$tn.8 { + SELECT a, b, c, typeof(a) FROM x2 WHERE a='2.0' + } {2 two ii integer 2 twopointoh ii.0 integer} + + do_execsql_test 3.1.$tn.9 { + SELECT a, b, c, typeof(a) FROM x3 WHERE a=2 + } {2.0 two ii real 2.0 twopointoh ii.0 real} + do_execsql_test 3.1.$tn.10 { + SELECT a, b, c, typeof(a) FROM x3 WHERE a=2.0 + } {2.0 two ii real 2.0 twopointoh ii.0 real} + do_execsql_test 3.1.$tn.11 { + SELECT a, b, c, typeof(a) FROM x3 WHERE a='2' + } {2.0 two ii real 2.0 twopointoh ii.0 real} + do_execsql_test 3.1.$tn.12 { + SELECT a, b, c, typeof(a) FROM x3 WHERE a='2.0' + } {2.0 two ii real 2.0 twopointoh ii.0 real} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE TABLE t2(a INTEGER, b TEXT); + INSERT INTO t2 VALUES(1, 'two'); + INSERT INTO t2 VALUES(2, 'two'); + INSERT INTO t2 VALUES(3, 'two'); + INSERT INTO t2 VALUES(1, 'three'); + INSERT INTO t2 VALUES(2, 'three'); + INSERT INTO t2 VALUES(3, 'three'); + + CREATE INDEX t2a_two ON t2(a) WHERE b='two'; +} + +# explain_i { SELECT sum(a), b FROM t2 WHERE b='two' } +do_execsql_test 4.1.1 { + SELECT sum(a), b FROM t2 WHERE b='two' +} {6 two} +do_eqp_test 4.1.2 { + SELECT sum(a), b FROM t2 WHERE b='two' +} {USING COVERING INDEX t2a_two} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(a INTEGER PRIMQRY KEY, b, c); +} +do_catchsql_test 5.1 { + CREATE INDEX ex1 ON t1(c) WHERE b IS 'abc' COLLATE g; +} {1 {no such collation sequence: g}} + +proc xyz {lhs rhs} { + return [string compare $lhs $rhs] +} +db collate xyz xyz +do_execsql_test 5.2 { + CREATE INDEX ex1 ON t1(c) WHERE b IS 'abc' COLLATE xyz; +} +db close +sqlite3 db test.db +do_execsql_test 5.3 { + SELECT * FROM t1 +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t2(x INTEGER PRIMARY KEY, y INTEGER, z INTEGER); + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 1, 2); + INSERT INTO t2 VALUES(1, 5, 1); + INSERT INTO t2 VALUES(2, 5, 2); + + CREATE INDEX t2z ON t2(z) WHERE y=5; +} + +do_execsql_test 6.1 { + ANALYZE; + UPDATE sqlite_stat1 SET stat = '50 1' WHERE idx='t2z'; + UPDATE sqlite_stat1 SET stat = '50' WHERE tbl='t2' AND idx IS NULL; + UPDATE sqlite_stat1 SET stat = '5000' WHERE tbl='t1' AND idx IS NULL; + ANALYZE sqlite_schema; +} + +do_execsql_test 6.2 { + SELECT * FROM t1, t2 WHERE b=1 AND z=c AND y=5; +} { + 1 1 1 1 5 1 + 2 1 2 2 5 2 +} + +do_eqp_test 6.3 { + SELECT * FROM t1, t2 WHERE b=1 AND z=c AND y=5; +} {BLOOM FILTER ON t2} + +do_execsql_test 6.4 { + SELECT * FROM t1 LEFT JOIN t2 ON (y=5) WHERE b=1 AND z IS c; +} { + 1 1 1 1 5 1 + 2 1 2 2 5 2 +} + +do_eqp_test 6.5 { + SELECT * FROM t1 LEFT JOIN t2 ON (y=5) WHERE b=1 AND z IS c; +} {BLOOM FILTER ON t2} + +do_execsql_test 6.6 { + CREATE INDEX t2yz ON t2(y, z) WHERE y=5; +} + +do_execsql_test 6.7 { + SELECT * FROM t1 LEFT JOIN t2 ON (y=5) WHERE b=1 AND z IS c; +} { + 1 1 1 1 5 1 + 2 1 2 2 5 2 +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 7.0 { + CREATE TABLE t1(i INTEGER PRIMARY KEY, b TEXT, c TEXT); + CREATE INDEX i1 ON t1(c) WHERE b='abc' AND i=5; + INSERT INTO t1 VALUES(5, 'abc', 'xyz'); + SELECT * FROM t1 INDEXED BY i1 WHERE b='abc' AND i=5 ORDER BY c; +} {5 abc xyz} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 8.0 { + CREATE TABLE t1(a, b, c); + CREATE INDEX ex2 ON t1(a, 4); + CREATE INDEX ex1 ON t1(a) WHERE 4=b; + INSERT INTO t1 VALUES(1, 4, 1); + INSERT INTO t1 VALUES(1, 5, 1); + INSERT INTO t1 VALUES(2, 4, 2); +} +do_execsql_test 8.1 { + SELECT * FROM t1 WHERE b=4; +} { + 1 4 1 2 4 2 +} + +finish_test diff --git a/test/joinH.test b/test/joinH.test index edba26de2..0fed7f2aa 100644 --- a/test/joinH.test +++ b/test/joinH.test @@ -132,4 +132,123 @@ do_execsql_test 6.0 { do_execsql_test 6.1 { SELECT * FROM t1 LEFT JOIN t2 ON true WHERE CASE WHEN t2.b THEN 0 ELSE 1 END; } {3 NULL} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 7.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c); + CREATE TABLE t3(d); + + INSERT INTO t1 VALUES ('a', 'a'); + INSERT INTO t2 VALUES ('ddd'); + INSERT INTO t3 VALUES(1234); +} + +do_execsql_test 7.1 { + SELECT t2.rowid FROM t1 JOIN (t2 JOIN t3); +} {1} + +do_execsql_test 7.1 { + UPDATE t1 SET b = t2.rowid FROM t2, t3; +} + +do_execsql_test 7.2 { + SELECT * FROM t1 +} {a 1} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 8.0 { + CREATE TABLE x1(a INTEGER PRIMARY KEY, b); + CREATE TABLE x2(c, d); + CREATE TABLE x3(rowid, _rowid_); + + CREATE TABLE x4(rowid, _rowid_, oid); + + INSERT INTO x1 VALUES(1000, 'thousand'); + INSERT INTO x2 VALUES('c', 'd'); + INSERT INTO x3(oid, rowid, _rowid_) VALUES(43, 'hello', 'world'); + INSERT INTO x4(oid, rowid, _rowid_) VALUES('forty three', 'hello', 'world'); +} + +do_execsql_test 8.1 { + SELECT x3.oid FROM x1 JOIN (x2 JOIN x3 ON c='c') +} 43 + +breakpoint +do_execsql_test 8.2 { + SELECT x3.rowid FROM x1 JOIN (x2 JOIN x3 ON c='c') +} {hello} + +do_execsql_test 8.3 { + SELECT x4.oid FROM x1 JOIN (x2 JOIN x4 ON c='c') +} {{forty three}} + + +#--------------------------------------------------------------------- +# +reset_db +do_execsql_test 9.0 { + CREATE TABLE x1(a); + CREATE TABLE x2(b); + CREATE TABLE x3(c); + + CREATE TABLE wo1(a PRIMARY KEY, b) WITHOUT ROWID; + CREATE TABLE wo2(a PRIMARY KEY, rowid) WITHOUT ROWID; + CREATE TABLE wo3(a PRIMARY KEY, b) WITHOUT ROWID; +} + +do_catchsql_test 9.1 { + SELECT rowid FROM wo1, x1, x2; +} {1 {no such column: rowid}} +do_catchsql_test 9.2 { + SELECT rowid FROM wo1, (x1, x2); +} {1 {no such column: rowid}} +do_catchsql_test 9.3 { + SELECT rowid FROM wo1 JOIN (x1 JOIN x2); +} {1 {no such column: rowid}} +do_catchsql_test 9.4 { + SELECT a FROM wo1, x1, x2; +} {1 {ambiguous column name: a}} + + +# It is not possible to use "rowid" in a USING clause. +# +do_catchsql_test 9.5 { + SELECT * FROM x1 JOIN x2 USING (rowid); +} {1 {cannot join using column rowid - column not present in both tables}} +do_catchsql_test 9.6 { + SELECT * FROM wo2 JOIN x2 USING (rowid); +} {1 {cannot join using column rowid - column not present in both tables}} + +# "rowid" columns are not matched by NATURAL JOIN. If they were, then +# the SELECT below would return zero rows. +do_execsql_test 9.7 { + INSERT INTO x1(rowid, a) VALUES(101, 'A'); + INSERT INTO x2(rowid, b) VALUES(55, 'B'); + SELECT * FROM x1 NATURAL JOIN x2; +} {A B} + +do_execsql_test 9.8 { + INSERT INTO wo1(a, b) VALUES('mya', 'myb'); + INSERT INTO wo2(a, rowid) VALUES('mypk', 'myrowid'); + INSERT INTO wo3(a, b) VALUES('MYA', 'MYB'); + INSERT INTO x3(rowid, c) VALUES(99, 'x3B'); +} + +do_catchsql_test 9.8 { + SELECT rowid FROM x1 JOIN (x2 JOIN wo2); +} {0 myrowid} +do_catchsql_test 9.9 { + SELECT _rowid_ FROM wo1 JOIN (wo3 JOIN x3) +} {0 99} +do_catchsql_test 9.10 { + SELECT oid FROM wo1 JOIN (wo3 JOIN x3) +} {0 99} +do_catchsql_test 9.11 { + SELECT oid FROM wo2 JOIN (wo3 JOIN x3) +} {0 99} + + finish_test diff --git a/test/json101.test b/test/json101.test index 459033074..c62991bbb 100644 --- a/test/json101.test +++ b/test/json101.test @@ -119,6 +119,9 @@ do_execsql_test json101-4.7 { do_execsql_test json101-4.8 { SELECT x FROM j1 WHERE json_insert(x)<>x; } {} +do_execsql_test json101-4.9 { + SELECT json_insert('{"a":1}','$.b',CAST(x'0000' AS text)); +} {{{"a":1,"b":"\u0000\u0000"}}} # json_extract(JSON,'$') will return objects and arrays without change. # @@ -1039,5 +1042,18 @@ do_execsql_test json101-22.2 { ); } {{{"a":2,"b":4,"c":6}}} +# 2023-10-17 https://sqlite.org/forum/forumpost/fc0e3f1e2a +# Incorrect accesss to '$[0]' in parsed + edited JSON. +# +do_execsql_test json101-23.1 { + SELECT j, j->>0, j->>1 + FROM (SELECT json_set(json_set('[]','$[#]',0), '$[#]',1) AS j); +} {{[0,1]} 0 1} +do_execsql_test json101-23.2 { + SELECT j, j->>0, j->>1 + FROM (SELECT json_set('[]','$[#]',0,'$[#]',1) AS j); +} {{[0,1]} 0 1} + + finish_test diff --git a/test/memdb2.test b/test/memdb2.test index 286bfc3f8..7c2144991 100644 --- a/test/memdb2.test +++ b/test/memdb2.test @@ -74,4 +74,3 @@ foreach {tn fname} { } finish_test - diff --git a/test/memjournal2.test b/test/memjournal2.test index ec5ba56da..d08bcb5a6 100644 --- a/test/memjournal2.test +++ b/test/memjournal2.test @@ -59,5 +59,3 @@ for {set jj 200} {$jj <= 300} {incr jj} { finish_test - - diff --git a/test/mutex1.test b/test/mutex1.test index 0fc8368f7..cb189a7a8 100644 --- a/test/mutex1.test +++ b/test/mutex1.test @@ -115,6 +115,14 @@ ifcapable threadsafe1&&shared_cache { } } { + # For journal_mode=memory, the static_prng mutex is not required. This + # is because the header of an in-memory journal does not contain + # any random bytes, and so no call to sqlite3_randomness() is made. + if {[permutation]=="inmemory_journal"} { + set idx [lsearch $mutexes static_prng] + if {$idx>=0} { set mutexes [lreplace $mutexes $idx $idx] } + } + do_test mutex1.2.$mode.1 { catch {db close} sqlite3_shutdown diff --git a/test/pendingrace.test b/test/pendingrace.test index f0e1a18ff..ef42578f2 100644 --- a/test/pendingrace.test +++ b/test/pendingrace.test @@ -121,6 +121,3 @@ tvfs delete tvfs2 delete finish_test - - - diff --git a/test/quickcheck.test b/test/quickcheck.test index 94016e845..18c42a13d 100644 --- a/test/quickcheck.test +++ b/test/quickcheck.test @@ -31,4 +31,3 @@ do_execsql_test 1.1 { } finish_test - diff --git a/test/rowvalue9.test b/test/rowvalue9.test index aee5e7ea4..baa13f4f9 100644 --- a/test/rowvalue9.test +++ b/test/rowvalue9.test @@ -350,5 +350,3 @@ do_eqp_test 9.5e { finish_test - - diff --git a/test/rowvalueA.test b/test/rowvalueA.test index dc5a7a014..8760c2c39 100644 --- a/test/rowvalueA.test +++ b/test/rowvalueA.test @@ -74,4 +74,3 @@ do_catchsql_test 2.3 { } {1 {row value misused}} finish_test - diff --git a/test/scanstatus2.test b/test/scanstatus2.test index cbffee018..e4b510d20 100644 --- a/test/scanstatus2.test +++ b/test/scanstatus2.test @@ -145,8 +145,8 @@ do_graph_test 1.4 { QUERY (nCycle=nnn) --MATERIALIZE v2 (nCycle=nnn) ----SCAN t2 (nCycle=nnn) ---SCAN v2 (nCycle=nnn) --SCAN t1 (nCycle=nnn) +--SCAN v2 (nCycle=nnn) --USE TEMP B-TREE FOR ORDER BY (nCycle=nnn) } @@ -332,5 +332,3 @@ QUERY (nCycle=nnn) #puts_debug_info { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 } finish_test - - diff --git a/test/select3.test b/test/select3.test index 4c9d71b4f..4bbd70cc7 100644 --- a/test/select3.test +++ b/test/select3.test @@ -434,4 +434,3 @@ do_execsql_test 12.8 { } finish_test - diff --git a/test/sessionfuzz.c b/test/sessionfuzz.c index f2e4cd5a6..093c2b043 100644 --- a/test/sessionfuzz.c +++ b/test/sessionfuzz.c @@ -66,14 +66,6 @@ #define SQLITE_ENABLE_DESERIALIZE 1 #include "sqlite3.c" -/* Create a test database. This will be an in-memory database */ -static const char zInitSql[] = - "CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c,d);\n" - "CREATE TABLE t2(e TEXT PRIMARY KEY NOT NULL,f,g);\n" - "CREATE TABLE t3(w REAL PRIMARY KEY NOT NULL,x,y);\n" - "CREATE TABLE t4(z PRIMARY KEY) WITHOUT ROWID;\n" -; - /* Code to populate the database */ static const char zFillSql[] = "INSERT INTO t1(a,b,c,d) VALUES\n" diff --git a/test/shell1.test b/test/shell1.test index d01737910..19848549a 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -1081,10 +1081,10 @@ do_test shell1-5.0 { # set escapes [list \ \t \\t \n \\n \v \\v \f \\f \ - " " "\" \"" \" \\\" ' \"'\" \\ \\\\] + " " "\" \"" \" \\\" \\ \\\\] } set char [string map $escapes $char] - set x [catchcmdex test.db ".print $char\n"] + set x [catchcmdex test.db ".print \"$char\"\n"] set code [lindex $x 0] set res [lindex $x 1] if {$code ne "0"} { @@ -1165,21 +1165,21 @@ do_test shell1-7.1.2 { } {0 {CREATE TABLE Z (x TEXT PRIMARY KEY); CREATE TABLE _ (x TEXT PRIMARY KEY);}} do_test shell1-7.1.3 { - catchcmd "test.db" ".schema \\\\_" + catchcmd "test.db" ".schema \"\\\\_\"" } {0 {CREATE TABLE _ (x TEXT PRIMARY KEY);}} do_test shell1-7.1.4 { catchcmd "test.db" ".schema __" } {0 {CREATE TABLE YY (x TEXT PRIMARY KEY); CREATE TABLE __ (x TEXT PRIMARY KEY);}} do_test shell1-7.1.5 { - catchcmd "test.db" ".schema \\\\_\\\\_" + catchcmd "test.db" ".schema \"\\\\_\\\\_\"" } {0 {CREATE TABLE __ (x TEXT PRIMARY KEY);}} do_test shell1-7.1.6 { catchcmd "test.db" ".schema ___" } {0 {CREATE TABLE WWW (x TEXT PRIMARY KEY); CREATE TABLE ___ (x TEXT PRIMARY KEY);}} do_test shell1-7.1.7 { - catchcmd "test.db" ".schema \\\\_\\\\_\\\\_" + catchcmd "test.db" ".schema \"\\\\_\\\\_\\\\_\"" } {0 {CREATE TABLE ___ (x TEXT PRIMARY KEY);}} } diff --git a/test/statfault.test b/test/statfault.test index b5980d417..19e0a6787 100644 --- a/test/statfault.test +++ b/test/statfault.test @@ -52,4 +52,3 @@ do_faultsim_test 2 -faults * -prep { } finish_test - diff --git a/test/subquery.test b/test/subquery.test index a048f9ed4..c51edba04 100644 --- a/test/subquery.test +++ b/test/subquery.test @@ -11,8 +11,6 @@ # This file implements regression tests for SQLite library. The # focus of this script is testing correlated subqueries # -# $Id: subquery.test,v 1.17 2009/01/09 01:12:28 drh Exp $ -# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -613,4 +611,45 @@ do_execsql_test subquery-9.4 { SELECT (SELECT DISTINCT x FROM t1 ORDER BY +x LIMIT 1 OFFSET 2) FROM t1; } {{} {} {} {}} +# 2023-09-15 +# Query planner performance regression reported by private email +# on 2023-09-14, caused by VIEWSCAN optimization of check-in 609fbb94b8f01d67 +# from 2022-09-01. +# +reset_db +do_execsql_test subquery-10.1 { + CREATE TABLE t1(aa TEXT, bb INT, cc TEXT); + CREATE INDEX x11 on t1(bb); + CREATE INDEX x12 on t1(aa); + CREATE TABLE t2(aa TEXT, xx INT); + ANALYZE sqlite_master; + INSERT INTO sqlite_stat1(tbl, idx, stat) VALUES('t1', 'x11', '156789 28'); + INSERT INTO sqlite_stat1(tbl, idx, stat) VALUES('t1', 'x12', '156789 1'); + ANALYZE sqlite_master; +} +do_eqp_test subquery-10.2 { + WITH v1(aa,cc,bb) AS (SELECT aa, cc, bb FROM t1 WHERE bb=12345), + v2(aa,mx) AS (SELECT aa, max(xx) FROM t2 GROUP BY aa) + SELECT * FROM v1 JOIN v2 ON v1.aa=v2.aa; +} { + QUERY PLAN + |--CO-ROUTINE v2 + | |--SCAN t2 + | `--USE TEMP B-TREE FOR GROUP BY + |--SEARCH t1 USING INDEX x11 (bb=?) + `--SEARCH v2 USING AUTOMATIC COVERING INDEX (aa=?) +} +# ^^^^^^^^^^^^^ +# Prior to the fix the incorrect (slow) plan caused by the +# VIEWSCAN optimization was: +# +# QUERY PLAN +# |--CO-ROUTINE v2 +# | |--SCAN t2 +# | `--USE TEMP B-TREE FOR GROUP BY +# |--SCAN v2 +# `--SEARCH t1 USING INDEX x12 (aa=?) +# + + finish_test diff --git a/test/table.test b/test/table.test index 7be6b3769..b961207f8 100644 --- a/test/table.test +++ b/test/table.test @@ -784,13 +784,13 @@ do_catchsql_test table-16.5 { } {1 {unknown function: count()}} do_catchsql_test table-16.6 { DROP TABLE t16; - CREATE TABLE t16(x DEFAULT(group_concat('x',','))); + CREATE TABLE t16(x DEFAULT(string_agg('x',','))); INSERT INTO t16(rowid) VALUES(123); SELECT rowid, x FROM t16; -} {1 {unknown function: group_concat()}} +} {1 {unknown function: string_agg()}} do_catchsql_test table-16.7 { INSERT INTO t16 DEFAULT VALUES; -} {1 {unknown function: group_concat()}} +} {1 {unknown function: string_agg()}} # Ticket [https://www.sqlite.org/src/info/094d39a4c95ee4abbc417f04214617675ba15c63] # describes a assertion fault that occurs on a CREATE TABLE .. AS SELECT statement. diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 22e3b17bf..1f1862ffd 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -59,7 +59,6 @@ Usage: where SWITCHES are: --jobs NUMBER-OF-JOBS - --fuzztest --zipvfs ZIPVFS-SOURCE-DIR Interesting values for PERMUTATION are: @@ -83,11 +82,6 @@ If a PERMUTATION is specified and is followed by the path to a Tcl script instead of a list of patterns, then that single Tcl test script is run with the specified permutation. -The --fuzztest option is ignored if the PERMUTATION is "release". Otherwise, -if it is present, then "make -C fuzztest" is run as part of the tests, -where is the directory containing the testfixture binary used to -run the script. - The "status" and "njob" commands are designed to be run from the same directory as a running testrunner.tcl script that is running tests. The "status" command prints a report describing the current state and progress @@ -157,19 +151,28 @@ set TRG(zipvfs) "" ;# -zipvfs option, if any switch -nocase -glob -- $tcl_platform(os) { *darwin* { - set TRG(platform) osx - set TRG(make) make.sh - set TRG(makecmd) "bash make.sh" + set TRG(platform) osx + set TRG(make) make.sh + set TRG(makecmd) "bash make.sh" + set TRG(testfixture) testfixture + set TRG(run) run.sh + set TRG(runcmd) "bash run.sh" } *linux* { - set TRG(platform) linux - set TRG(make) make.sh - set TRG(makecmd) "bash make.sh" + set TRG(platform) linux + set TRG(make) make.sh + set TRG(makecmd) "bash make.sh" + set TRG(testfixture) testfixture + set TRG(run) run.sh + set TRG(runcmd) "bash run.sh" } *win* { - set TRG(platform) win - set TRG(make) make.bat - set TRG(makecmd) make.bat + set TRG(platform) win + set TRG(make) make.bat + set TRG(makecmd) make.bat + set TRG(testfixture) testfixture.exe + set TRG(run) run.bat + set TRG(runcmd) "run.bat" } default { error "cannot determine platform!" @@ -181,20 +184,59 @@ switch -nocase -glob -- $tcl_platform(os) { # The database schema used by the testrunner.db database. # set TRG(schema) { - DROP TABLE IF EXISTS script; + DROP TABLE IF EXISTS jobs; DROP TABLE IF EXISTS config; - CREATE TABLE script( - build TEXT DEFAULT '', - config TEXT, - filename TEXT, -- full path to test script - slow BOOLEAN, -- true if script is "slow" + /* + ** This table contains one row for each job that testrunner.tcl must run + ** before the entire test run is finished. + ** + ** jobid: + ** Unique identifier for each job. Must be a +ve non-zero number. + ** + ** displaytype: + ** 3 or 4 letter mnemonic for the class of tests this belongs to e.g. + ** "fuzz", "tcl", "make" etc. + ** + ** displayname: + ** Name/description of job. For display purposes. + ** + ** build: + ** If the job requires a make.bat/make.sh make wrapper (i.e. to build + ** something), the name of the build configuration it uses. See + ** testrunner_data.tcl for a list of build configs. e.g. "Win32-MemDebug". + ** + ** dirname: + ** If the job should use a well-known directory name for its + ** sub-directory instead of an anonymous "testdir[1234...]" sub-dir + ** that is deleted after the job is finished. + ** + ** cmd: + ** Bash or batch script to run the job. + ** + ** depid: + ** The jobid value of a job that this job depends on. This job may not + ** be run before its depid job has finished successfully. + ** + ** priority: + ** Higher values run first. Sometimes. + */ + CREATE TABLE jobs( + /* Fields populated when db is initialized */ + jobid INTEGER PRIMARY KEY, -- id to identify job + displaytype TEXT NOT NULL, -- Type of test (for one line report) + displayname TEXT NOT NULL, -- Human readable job name + build TEXT NOT NULL DEFAULT '', -- make.sh/make.bat file request, if any + dirname TEXT NOT NULL DEFAULT '', -- directory name, if required + cmd TEXT NOT NULL, -- shell command to run + depid INTEGER, -- identifier of dependency (or '') + priority INTEGER NOT NULL, -- higher priority jobs may run earlier + + /* Fields updated as jobs run */ + starttime INTEGER, + endtime INTEGER, state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ), - time INTEGER, -- Time in ms - output TEXT, -- full output of test script - priority INTEGER, - jobtype TEXT CHECK( jobtype IN ('script', 'build', 'make') ), - PRIMARY KEY(build, config, filename) + output TEXT ); CREATE TABLE config( @@ -202,8 +244,8 @@ set TRG(schema) { value ) WITHOUT ROWID; - CREATE INDEX i1 ON script(state, jobtype); - CREATE INDEX i2 ON script(state, priority); + CREATE INDEX i1 ON jobs(state, priority); + CREATE INDEX i2 ON jobs(depid); } #------------------------------------------------------------------------- @@ -303,39 +345,14 @@ if {[llength $argv]==1 && [string compare -nocase status [lindex $argv 0]]==0 } { - proc display_job {build config filename {tm ""}} { - if {$config=="build"} { - set fname "build: $filename" - set config "" - } elseif {$config=="make"} { - set fname "make: $filename" - set config "" - } else { - set fname [file normalize $filename] - if {[string first $::srcdir $fname]==0} { - set fname [string range $fname [string length $::srcdir]+1 end] - } - } - set dfname [format %-33s $fname] + proc display_job {jobdict {tm ""}} { + array set job $jobdict + + set dfname [format %-60s $job(displayname)] - set dbuild "" - set dconfig "" - set dparams "" set dtm "" - if {$build!=""} { set dbuild $build } - if {$config!="" && $config!="full"} { set dconfig $config } - if {$dbuild!="" || $dconfig!=""} { - append dparams "(" - if {$dbuild!=""} {append dparams "build=$dbuild"} - if {$dbuild!="" && $dconfig!=""} {append dparams " "} - if {$dconfig!=""} {append dparams "config=$dconfig"} - append dparams ")" - set dparams [format %-33s $dparams] - } - if {$tm!=""} { - set dtm "\[${tm}ms\]" - } - puts " $dfname $dparams $dtm" + if {$tm!=""} { set dtm "\[[expr {$tm-$job(starttime)}]ms\]" } + puts " $dfname $dtm" } sqlite3 mydb $TRG(dbname) @@ -355,7 +372,7 @@ if {[llength $argv]==1 set total 0 foreach s {"" ready running done failed} { set S($s) 0 } mydb eval { - SELECT state, count(*) AS cnt FROM script GROUP BY 1 + SELECT state, count(*) AS cnt FROM jobs GROUP BY 1 } { incr S($state) $cnt incr total $cnt @@ -375,19 +392,17 @@ if {[llength $argv]==1 if {$S(running)>0} { puts "Running: " mydb eval { - SELECT build, config, filename, time FROM script WHERE state='running' - ORDER BY time - } { - display_job $build $config $filename [expr $now-$time] + SELECT * FROM jobs WHERE state='running' ORDER BY starttime + } job { + display_job [array get job] $now } } if {$S(failed)>0} { puts "Failures: " mydb eval { - SELECT build, config, filename FROM script WHERE state='failed' - ORDER BY 3 - } { - display_job $build $config $filename + SELECT * FROM jobs WHERE state='failed' ORDER BY starttime + } job { + display_job [array get job] } } @@ -408,8 +423,6 @@ for {set ii 0} {$ii < [llength $argv]} {incr ii} { incr ii set TRG(nJob) [lindex $argv $ii] if {$isLast} { usage } - } elseif {($n>2 && [string match "$a*" --fuzztest]) || $a=="-f"} { - set TRG(fuzztest) 1 } elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} { incr ii set TRG(zipvfs) [lindex $argv $ii] @@ -480,100 +493,141 @@ proc create_or_clear_dir {dir} { } } -proc copy_dir {from to} { - foreach f [glob -nocomplain [file join $from *]] { - catch { file copy -force $f $to } - } -} - proc build_to_dirname {bname} { set fold [string tolower [string map {- _} $bname]] return "testrunner_build_$fold" } #------------------------------------------------------------------------- -# Return a list of tests to run. Each element of the list is itself a -# list of two elements - the name of a permuations.test configuration -# followed by the full path to a test script. i.e.: + +proc r_write_db {tcl} { + trdb eval { BEGIN EXCLUSIVE } + uplevel $tcl + trdb eval { COMMIT } +} + +# Obtain a new job to be run by worker $iJob (an integer). A job is +# returned as a three element list: # -# {BUILD CONFIG FILENAME} {BUILD CONFIG FILENAME} ... +# {$build $config $file} # -proc testset_patternlist {patternlist} { - global TRG +proc r_get_next_job {iJob} { + global T - set testset [list] ;# return value + if {($iJob%2)} { + set orderby "ORDER BY priority ASC" + } else { + set orderby "ORDER BY priority DESC" + } - set first [lindex $patternlist 0] + set ret [list] - if {$first=="sdevtest" || $first=="mdevtest"} { - set CONFIGS(sdevtest) {All-Debug All-Sanitize} - set CONFIGS(mdevtest) {All-Debug All-O0} + r_write_db { + set query " + SELECT * FROM jobs AS j WHERE state='ready' $orderby LIMIT 1 + " + trdb eval $query job { + set tm [clock_milliseconds] + set T($iJob) $tm + set jobid $job(jobid) - set patternlist [lrange $patternlist 1 end] + trdb eval { + UPDATE jobs SET starttime=$tm, state='running' WHERE jobid=$jobid + } - foreach b $CONFIGS($first) { - lappend testset [list $b build testfixture] - lappend testset [list $b make fuzztest] - testset_append testset $b veryquick $patternlist + set ret [array get job] } - } elseif {$first=="release"} { - set platform $::TRG(platform) + } - set patternlist [lrange $patternlist 1 end] - foreach b [trd_builds $platform] { - foreach c [trd_configs $platform $b] { - testset_append testset $b $c $patternlist - } + return $ret +} - if {[llength $patternlist]==0 || $b=="User-Auth"} { - set target testfixture - } else { - set target coretestprogs - } - lappend testset [list $b build $target] - } +#rename r_get_next_job r_get_next_job_r +#proc r_get_next_job {iJob} { + #puts [time { set res [r_get_next_job_r $iJob] }] + #set res +#} - if {[llength $patternlist]==0} { - foreach b [trd_builds $platform] { - foreach e [trd_extras $platform $b] { - lappend testset [list $b make $e] - } - } - } +# Usage: +# +# add_job OPTION ARG OPTION ARG... +# +# where available OPTIONS are: +# +# -displaytype +# -displayname +# -build +# -dirname +# -cmd +# -depid +# -priority +# +# Returns the jobid value for the new job. +# +proc add_job {args} { - set TRG(fuzztest) 0 ;# ignore --fuzztest option in this case + set options { + -displaytype -displayname -build -dirname + -cmd -depid -priority + } - } elseif {$first=="all"} { + # Set default values of options. + set A(-dirname) "" + set A(-depid) "" + set A(-priority) 0 + set A(-build) "" - set clist [trd_all_configs] - set patternlist [lrange $patternlist 1 end] - foreach c $clist { - testset_append testset "" $c $patternlist - } + array set A $args - } elseif {[info exists ::testspec($first)]} { - set clist $first - testset_append testset "" $first [lrange $patternlist 1 end] - } elseif { [llength $patternlist]==0 } { - testset_append testset "" veryquick $patternlist - } else { - testset_append testset "" full $patternlist + # Check all required options are present. And that no extras are present. + foreach o $options { + if {[info exists A($o)]==0} { error "missing required option $o" } } - if {$TRG(fuzztest)} { - if {$TRG(platform)=="win"} { error "todo" } - lappend testset [list "" make fuzztest] + foreach o [array names A] { + if {[lsearch -exact $options $o]<0} { error "unrecognized option: $o" } } - set testset + set state "" + if {$A(-depid)==""} { set state ready } + + trdb eval { + INSERT INTO jobs( + displaytype, displayname, build, dirname, cmd, depid, priority, + state + ) VALUES ( + $A(-displaytype), + $A(-displayname), + $A(-build), + $A(-dirname), + $A(-cmd), + $A(-depid), + $A(-priority), + $state + ) + } + + trdb last_insert_rowid } -proc testset_append {listvar build config patternlist} { - upvar $listvar lvar +proc add_tcl_jobs {build config patternlist} { + global TRG - catch { array unset O } - array set O $::testspec($config) + set topdir [file dirname $::testdir] + set testrunner_tcl [file normalize [info script]] + + if {$build==""} { + set testfixture [info nameofexec] + } else { + set testfixture [file join [lindex $build 1] $TRG(testfixture)] + } + if {[lindex $build 2]=="Valgrind"} { + set setvar "export OMIT_MISUSE=1\n" + set testfixture "${setvar}valgrind -v --error-exitcode=1 $testfixture" + } + + # The ::testspec array is populated by permutations.test + foreach f [dict get $::testspec($config) -files] { - foreach f $O(-files) { if {[llength $patternlist]>0} { set bMatch 0 foreach p $patternlist { @@ -585,171 +639,242 @@ proc testset_append {listvar build config patternlist} { if {$bMatch==0} continue } - if {[file pathtype $f]!="absolute"} { - set f [file join $::testdir $f] + if {[file pathtype $f]!="absolute"} { set f [file join $::testdir $f] } + set f [file normalize $f] + + set displayname [string map [list $topdir/ {}] $f] + if {$config=="full" || $config=="veryquick"} { + set cmd "$testfixture $f" + } else { + set cmd "$testfixture $testrunner_tcl $config $f" + set displayname "config=$config $displayname" + } + if {$build!=""} { + set displayname "[lindex $build 2] $displayname" } - lappend lvar [list $build $config $f] + + set lProp [trd_test_script_properties $f] + set priority 0 + if {[lsearch $lProp slow]>=0} { set priority 2 } + if {[lsearch $lProp superslow]>=0} { set priority 4 } + + add_job \ + -displaytype tcl \ + -displayname $displayname \ + -cmd $cmd \ + -depid [lindex $build 0] \ + -priority $priority + } } -#-------------------------------------------------------------------------- +proc add_build_job {buildname target} { + global TRG + set dirname "[string tolower [string map {- _} $buildname]]_$target" + set dirname "testrunner_bld_$dirname" -proc r_write_db {tcl} { - trdb eval { BEGIN EXCLUSIVE } - uplevel $tcl - trdb eval { COMMIT } + set id [add_job \ + -displaytype bld \ + -displayname "Build $buildname ($target)" \ + -dirname $dirname \ + -build $buildname \ + -cmd "$TRG(makecmd) $target" \ + -priority 3 + ] + + list $id [file normalize $dirname] $buildname } -# Obtain a new job to be run by worker $iJob (an integer). A job is -# returned as a three element list: -# -# {$build $config $file} -# -proc r_get_next_job {iJob} { - global T +proc add_make_job {bld target} { + global TRG - if {($iJob%2)} { - set orderby "ORDER BY priority ASC" + if {$TRG(platform)=="win"} { + set path [string map {/ \\} [lindex $bld 1]] + set cmd "xcopy /S $path\\* ." } else { - set orderby "ORDER BY priority DESC" + set cmd "cp -r [lindex $bld 1]/* ." } + append cmd "\n$TRG(makecmd) $target" + + add_job \ + -displaytype make \ + -displayname "[lindex $bld 2] make $target" \ + -cmd $cmd \ + -depid [lindex $bld 0] \ + -priority 1 +} - r_write_db { - set f "" - set c "" - trdb eval " - SELECT build, config, filename - FROM script - WHERE state='ready' - $orderby LIMIT 1 - " { - set b $build - set c $config - set f $filename - } - if {$f!=""} { - set tm [clock_milliseconds] - set T($iJob) $tm - trdb eval { - UPDATE script SET state='running', time=$tm - WHERE (build, config, filename) = ($b, $c, $f) +proc add_fuzztest_jobs {buildname} { + + foreach {interpreter scripts} [trd_fuzztest_data] { + set subcmd [lrange $interpreter 1 end] + set interpreter [lindex $interpreter 0] + + set bld [add_build_job $buildname $interpreter] + foreach {depid dirname displayname} $bld {} + + foreach s $scripts { + + # Fuzz data files fuzzdata1.db and fuzzdata2.db are larger than + # the others. So ensure that these are run as a higher priority. + set tail [file tail $s] + if {$tail=="fuzzdata1.db" || $tail=="fuzzdata2.db"} { + set priority 5 + } else { + set priority 1 } + + add_job \ + -displaytype fuzz \ + -displayname "$buildname $interpreter $tail" \ + -depid $depid \ + -cmd "[file join $dirname $interpreter] $subcmd $s" \ + -priority $priority } } - - if {$f==""} { return "" } - list $b $c $f } -#rename r_get_next_job r_get_next_job_r -#proc r_get_next_job {iJob} { -# puts [time { set res [r_get_next_job_r $iJob] }] -# set res -#} - -proc make_new_testset {} { +proc add_zipvfs_jobs {} { global TRG + source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl] + + set bld [add_build_job Zipvfs $TRG(testfixture)] + foreach s [zipvfs_testrunner_files] { + set cmd "[file join [lindex $bld 1] $TRG(testfixture)] $s" + add_job \ + -displaytype tcl \ + -displayname "Zipvfs [file tail $s]" \ + -cmd $cmd \ + -depid [lindex $bld 0] + } - set tests [testset_patternlist $TRG(patternlist)] + set ::env(SQLITE_TEST_DIR) $::testdir +} + +proc add_jobs_from_cmdline {patternlist} { + global TRG if {$TRG(zipvfs)!=""} { - source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl] - set tests [concat $tests [zipvfs_testrunner_testset]] + add_zipvfs_jobs + if {[llength $patternlist]==0} return } - r_write_db { - - trdb eval $TRG(schema) - set nJob $TRG(nJob) - set cmdline $TRG(cmdline) - set tm [clock_milliseconds] - trdb eval { REPLACE INTO config VALUES('njob', $nJob ); } - trdb eval { REPLACE INTO config VALUES('cmdline', $cmdline ); } - trdb eval { REPLACE INTO config VALUES('start', $tm ); } - - foreach t $tests { - foreach {b c s} $t {} - set slow 0 - - if {$c!="make" && $c!="build"} { - set fd [open $s] - for {set ii 0} {$ii<100 && ![eof $fd]} {incr ii} { - set line [gets $fd] - if {[string match -nocase *testrunner:* $line]} { - regexp -nocase {.*testrunner:(.*)} $line -> properties - foreach p $properties { - if {$p=="slow"} { set slow 1 } - if {$p=="superslow"} { set slow 2 } - } - } - } - close $fd - } + if {[llength $patternlist]==0} { + set patternlist [list veryquick] + } - if {$c=="make" && $b==""} { - # --fuzztest option - set slow 1 + set first [lindex $patternlist 0] + switch -- $first { + all { + set patternlist [lrange $patternlist 1 end] + set clist [trd_all_configs] + foreach c $clist { + add_tcl_jobs "" $c $patternlist } + } - if {$c=="veryquick"} { - set c "" + mdevtest { + foreach b [list All-O0 All-Debug] { + set bld [add_build_job $b $TRG(testfixture)] + add_tcl_jobs $bld veryquick "" + add_fuzztest_jobs $b } + } - set state ready - if {$b!="" && $c!="build"} { - set state "" + sdevtest { + foreach b [list All-Sanitize All-Debug] { + set bld [add_build_job $b $TRG(testfixture)] + add_tcl_jobs $bld veryquick "" + add_fuzztest_jobs $b } + } - set priority [expr {$slow*2}] - if {$c=="make"} { incr priority 3 } - if {$c=="build"} { incr priority 1 } + release { + foreach b [trd_builds $TRG(platform)] { + set bld [add_build_job $b $TRG(testfixture)] + foreach c [trd_configs $TRG(platform) $b] { + add_tcl_jobs $bld $c "" + } - if {$c=="make" || $c=="build"} { - set jobtype $c - } else { - set jobtype "script" + foreach e [trd_extras $TRG(platform) $b] { + if {$e=="fuzztest"} { + add_fuzztest_jobs $b + } else { + add_make_job $bld $e + } + } } + } - trdb eval { - INSERT INTO script - (build, config, filename, slow, state, priority, jobtype) - VALUES ($b, $c, $s, $slow, $state, $priority, $jobtype) + default { + if {[info exists ::testspec($first)]} { + add_tcl_jobs "" $first [lrange $patternlist 1 end] + } else { + add_tcl_jobs "" full $patternlist } } } } -proc script_input_ready {fd iJob b c f} { +proc make_new_testset {} { + global TRG + + r_write_db { + trdb eval $TRG(schema) + set nJob $TRG(nJob) + set cmdline $TRG(cmdline) + set tm [clock_milliseconds] + trdb eval { REPLACE INTO config VALUES('njob', $nJob ); } + trdb eval { REPLACE INTO config VALUES('cmdline', $cmdline ); } + trdb eval { REPLACE INTO config VALUES('start', $tm ); } + + add_jobs_from_cmdline $TRG(patternlist) + } + +} + +proc script_input_ready {fd iJob jobid} { global TRG global O global T if {[eof $fd]} { + trdb eval { SELECT * FROM jobs WHERE jobid=$jobid } job {} + + # If this job specified a directory name, then delete the run.sh/run.bat + # file from it before continuing. This is because the contents of this + # directory might be copied by some other job, and we don't want to copy + # the run.sh file in this case. + if {$job(dirname)!=""} { + file delete -force [file join $job(dirname) $TRG(run)] + } + set ::done 1 fconfigure $fd -blocking 1 set state "done" set rc [catch { close $fd } msg] if {$rc} { - puts "FAILED: $b $c $f" + if {[info exists TRG(reportlength)]} { + puts -nonewline "[string repeat " " $TRG(reportlength)]\r" + } + puts "FAILED: $job(displayname) ($iJob)" set state "failed" } - set tm [expr [clock_milliseconds] - $T($iJob)] + set tm [clock_milliseconds] + set jobtm [expr {$tm - $job(starttime)}] - puts $TRG(log) "### $b ### $c ### $f ${tm}ms ($state)" + puts $TRG(log) "### $job(displayname) ${jobtm}ms ($state)" puts $TRG(log) [string trim $O($iJob)] r_write_db { set output $O($iJob) trdb eval { - UPDATE script SET output = $output, state=$state, time=$tm - WHERE (build, config, filename) = ($b, $c, $f) - } - if {$state=="done" && $c=="build"} { - trdb eval { - UPDATE script SET state = 'ready' WHERE (build, state)==($b, '') - } + UPDATE jobs + SET output=$output, state=$state, endtime=$tm + WHERE jobid=$jobid; + UPDATE jobs SET state='ready' WHERE depid=$jobid; } } @@ -780,79 +905,39 @@ proc launch_another_job {iJob} { set testfixture [info nameofexec] set script $TRG(info_script) - set dir [dirname $iJob] - create_or_clear_dir $dir - set O($iJob) "" - set job [r_get_next_job $iJob] - if {$job==""} { return 0 } - - foreach {b c f} $job {} + set jobdict [r_get_next_job $iJob] + if {$jobdict==""} { return 0 } + array set job $jobdict - if {$c=="build"} { - set testdir [file dirname $TRG(info_script)] - set srcdir [file dirname $testdir] - set builddir [build_to_dirname $b] - create_or_clear_dir $builddir + set dir $job(dirname) + if {$dir==""} { set dir [dirname $iJob] } + create_or_clear_dir $dir - if {$b=="Zipvfs"} { + if {$job(build)!=""} { + set srcdir [file dirname $::testdir] + if {$job(build)=="Zipvfs"} { set script [zipvfs_testrunner_script] } else { - set script [trd_buildscript $b $srcdir [expr {$TRG(platform)=="win"}]] + set bWin [expr {$TRG(platform)=="win"}] + set script [trd_buildscript $job(build) $srcdir $bWin] } - - set fd [open [file join $builddir $TRG(make)] w] + set fd [open [file join $dir $TRG(make)] w] puts $fd $script close $fd - - puts "Launching build \"$b\" in directory $builddir..." - set target coretestprogs - if {$b=="User-Auth"} { set target testfixture } - - set cmd "$TRG(makecmd) $target" - set dir $builddir - - } elseif {$c=="make"} { - if {$b==""} { - if {$f!="fuzztest"} { error "corruption in testrunner.db!" } - # Special case - run [make fuzztest] - set makedir [file dirname $testfixture] - if {$TRG(platform)=="win"} { - error "how?" - } else { - set cmd [list make -C $makedir fuzztest] - } - } else { - set builddir [build_to_dirname $b] - copy_dir $builddir $dir - set cmd "$TRG(makecmd) $f" - } - } else { - if {$b==""} { - set testfixture [info nameofexec] - } else { - set tail testfixture - if {$TRG(platform)=="win"} { set tail testfixture.exe } - set testfixture [file normalize [file join [build_to_dirname $b] $tail]] - } - - if {$c=="valgrind"} { - set testfixture "valgrind -v --error-exitcode=1 $testfixture" - set ::env(OMIT_MISUSE) 1 - } - set cmd [concat $testfixture [list $script $c $f]] } set pwd [pwd] cd $dir - set fd [open "|$cmd 2>@1" r] + set fd [open $TRG(run) w] + puts $fd $job(cmd) + close $fd + set fd [open "|$TRG(runcmd) 2>@1" r] cd $pwd - set pid [pid $fd] fconfigure $fd -blocking false - fileevent $fd readable [list script_input_ready $fd $iJob $b $c $f] - unset -nocomplain ::env(OMIT_MISUSE) + fileevent $fd readable [list script_input_ready $fd $iJob $job(jobid)] return 1 } @@ -863,39 +948,36 @@ proc one_line_report {} { set tm [expr [clock_milliseconds] - $TRG(starttime)] set tm [format "%d" [expr int($tm/1000.0 + 0.5)]] - foreach s {ready running done failed} { - set v($s,build) 0 - set v($s,make) 0 - set v($s,script) 0 - } - r_write_db { - trdb eval { - SELECT state, jobtype, count(*) AS cnt - FROM script - GROUP BY state, jobtype + trdb eval { + SELECT displaytype, state, count(*) AS cnt + FROM jobs + GROUP BY 1, 2 } { - set v($state,$jobtype) $cnt - if {[info exists t($jobtype)]} { - incr t($jobtype) $cnt - } else { - set t($jobtype) $cnt - } + set v($state,$displaytype) $cnt + incr t($displaytype) $cnt } } set text "" - foreach j [array names t] { + foreach j [lsort [array names t]] { + foreach k {done failed running} { incr v($k,$j) 0 } set fin [expr $v(done,$j) + $v(failed,$j)] - lappend text "$j ($fin/$t($j)) f=$v(failed,$j) r=$v(running,$j)" + lappend text "${j}($fin/$t($j))" + if {$v(failed,$j)>0} { + lappend text "f$v(failed,$j)" + } + if {$v(running,$j)>0} { + lappend text "r$v(running,$j)" + } } if {[info exists TRG(reportlength)]} { puts -nonewline "[string repeat " " $TRG(reportlength)]\r" } - set report "${tm}s: [join $text { }]" + set report "${tm} [join $text { }]" set TRG(reportlength) [string length $report] - if {[string length $report]<80} { + if {[string length $report]<100} { puts -nonewline "$report\r" flush stdout } else { @@ -907,9 +989,8 @@ proc one_line_report {} { proc launch_some_jobs {} { global TRG - r_write_db { - set nJob [trdb one { SELECT value FROM config WHERE name='njob' }] - } + set nJob [trdb one { SELECT value FROM config WHERE name='njob' }] + while {[dirs_nHelper]<$nJob} { set iDir [dirs_allocDir] if {0==[launch_another_job $iDir]} { @@ -927,7 +1008,6 @@ proc run_testset {} { set TRG(log) [open $TRG(logname) w] launch_some_jobs - # launch_another_job $ii one_line_report while {[dirs_nHelper]>0} { @@ -940,13 +1020,13 @@ proc run_testset {} { r_write_db { set tm [clock_milliseconds] trdb eval { REPLACE INTO config VALUES('end', $tm ); } - set nErr [trdb one {SELECT count(*) FROM script WHERE state='failed'}] + set nErr [trdb one {SELECT count(*) FROM jobs WHERE state='failed'}] if {$nErr>0} { puts "$nErr failures:" trdb eval { - SELECT build, config, filename FROM script WHERE state='failed' + SELECT displayname FROM jobs WHERE state='failed' } { - puts "FAILED: $build $config $filename" + puts "FAILED: $displayname" } } } @@ -960,7 +1040,7 @@ sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) set tm [lindex [time { make_new_testset }] 0] if {$TRG(nJob)>1} { - puts "splitting work across $TRG(nJob) cores" + puts "splitting work across $TRG(nJob) jobs" } puts "built testset in [expr $tm/1000]ms.." run_testset diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index 2493970ea..9032ced4d 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -106,6 +106,7 @@ namespace eval trd { set build(Sanitize) { CC=clang -fsanitize=address,undefined -DSQLITE_ENABLE_STAT4 + -DSQLITE_OMIT_LOOKASIDE=1 -DCONFIG_SLOWDOWN_FACTOR=5.0 --enable-debug --enable-all @@ -373,15 +374,45 @@ proc trd_configs {platform bld} { proc trd_extras {platform bld} { trd_import + if {[info exists extra($platform.$bld)]==0} { return [list] } + return $extra($platform.$bld) +} + +# Usage: +# +# trd_fuzztest_data +# +# This returns data used by testrunner.tcl to run commands equivalent +# to [make fuzztest]. The returned value is a list, which should be +# interpreted as a sequence of pairs. The first element of each pair +# is an interpreter name. The second element is a list of files. +# testrunner.tcl automatically creates one job to build each interpreter, +# and one to run each of the files with it once it has been built. +# +# In practice, the returned value looks like this: +# +# { +# {fuzzcheck {$testdir/fuzzdata1.db $testdir/fuzzdata2.db ...}} +# {{sessionfuzz run} $testdir/sessionfuzz-data1.db} +# } +# +# where $testdir is replaced by the full-path to the test-directory (the +# directory containing this file). "fuzzcheck" and "sessionfuzz" have .exe +# extensions on windows. +# +proc trd_fuzztest_data {} { + set EXE "" + set lFuzzDb [glob [file join $::testdir fuzzdata*.db]] + set lSessionDb [glob [file join $::testdir sessionfuzz-data*.db]] - set elist [list] - if {[info exists extra($platform.$bld)]} { - set elist $extra($platform.$bld) + if {$::tcl_platform(platform)=="windows"} { + return [list fuzzcheck.exe $lFuzzDb] } - set elist + return [list fuzzcheck $lFuzzDb {sessionfuzz run} $lSessionDb] } + proc trd_all_configs {} { trd_import set all_configs @@ -397,7 +428,7 @@ proc make_sh_script {srcdir opts cflags makeOpts configOpts} { set myopts "" if {[info exists ::env(OPTS)]} { append myopts "# From environment variable:\n" - append myopts "OPTS=$::env(OPTS)\n" + append myopts "OPTS=$::env(OPTS)\n\n" } foreach o [lsort $opts] { append myopts "OPTS=\"\$OPTS $o\"\n" @@ -512,7 +543,6 @@ proc make_script {cfg srcdir bMsvc} { default { error "Cannot translate $param for MSVC" } - } } @@ -520,11 +550,19 @@ proc make_script {cfg srcdir bMsvc} { } if {[string range $param 0 0]=="-"} { - if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { - lappend makeOpts OPTIMIZATIONS=$level - } else { - lappend cflags $param + + if {$bMsvc} { + if {[regexp -- {^-O(\d+)$} $param -> level]} { + lappend makeOpts OPTIMIZATIONS=$level + continue + } + if {$param eq "-fsanitize=address,undefined"} { + lappend makeOpts ASAN=1 + continue + } } + + lappend cflags $param continue } @@ -563,4 +601,36 @@ proc trd_buildscript {config srcdir bMsvc} { return [make_script $build($config) $srcdir $bMsvc] } +# Usage: +# +# trd_test_script_properties PATH +# +# The argument must be a path to a Tcl test script. This function scans the +# first 100 lines of the script for lines that look like: +# +# TESTRUNNER: +# +# where is a list of identifiers, each of which defines a +# property of the test script. Example properties are "slow" or "superslow". +# +proc trd_test_script_properties {path} { + # Use this global array as a cache: + global trd_test_script_properties_cache + + if {![info exists trd_test_script_properties_cache($path)]} { + set fd [open $path] + set ret [list] + for {set line 0} {$line < 100 && ![eof $fd]} {incr line} { + set text [gets $fd] + if {[string match -nocase *testrunner:* $text]} { + regexp -nocase {.*testrunner:(.*)} $text -> properties + lappend ret {*}$properties + } + } + set trd_test_script_properties_cache($path) $ret + close $fd + } + + set trd_test_script_properties_cache($path) +} diff --git a/test/thread3.test b/test/thread3.test index 25699b765..79a75bdbb 100644 --- a/test/thread3.test +++ b/test/thread3.test @@ -75,4 +75,3 @@ do_execsql_test "1.Total BUSY errors: $nTotalBusy .2" { } $nAttempt finish_test - diff --git a/test/tkt-cbd054fa6b.test b/test/tkt-cbd054fa6b.test index 86248ca21..435d80787 100644 --- a/test/tkt-cbd054fa6b.test +++ b/test/tkt-cbd054fa6b.test @@ -65,7 +65,7 @@ do_test tkt-cbd05-1.2 { } {} do_test tkt-cbd05-1.3 { execsql { - SELECT tbl,idx,group_concat(s(sample),' ') + SELECT tbl,idx,string_agg(s(sample),' ') FROM vvv WHERE idx = 't1_x' GROUP BY tbl,idx diff --git a/test/trigger2.test b/test/trigger2.test index 6e007e969..70d59f3a0 100644 --- a/test/trigger2.test +++ b/test/trigger2.test @@ -790,4 +790,3 @@ do_catchsql_test 11.2 { finish_test - diff --git a/test/unhex.test b/test/unhex.test index f41e906a8..af2cfae39 100644 --- a/test/unhex.test +++ b/test/unhex.test @@ -55,6 +55,8 @@ do_catchsql_test 3.1 { #-------------------------------------------------------------------------- # Test the 2-argument version. # +# Zap global x array set in some previous test. +if {[array exists x]} {array unset x} foreach {tn hex} { 1 "FFFF ABCD" 2 "FFFF ABCD" @@ -98,5 +100,3 @@ do_execsql_test 6.4.3 { SELECT typeof(unhex('1234', NULL)) } {null} finish_test - - diff --git a/test/vacuum-into.test b/test/vacuum-into.test index 98692a108..698d65f54 100644 --- a/test/vacuum-into.test +++ b/test/vacuum-into.test @@ -185,6 +185,3 @@ tvfs delete finish_test - - - diff --git a/test/vt02.c b/test/vt02.c index ddad136fb..06fade096 100644 --- a/test/vt02.c +++ b/test/vt02.c @@ -580,8 +580,11 @@ static void sqlite3BestIndexLog( sqlite3_finalize(pStmt); } sqlite3RunSql(db,pVTab, - "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'nConstraint',%d)", - zLogTab, iBI, pInfo->nConstraint + "INSERT INTO temp.\"%w\"(bi,vn,ix) VALUES(%d,'nConstraint',%d)" + "RETURNING iif(bi=%d,'ok',RAISE(ABORT,'wrong trigger'))", + /* The RETURNING clause checks to see that the returning trigger fired + ** for the correct INSERT in the case of nested INSERT RETURNINGs. */ + zLogTab, iBI, pInfo->nConstraint, iBI ); for(i=0; inConstraint; i++){ sqlite3_value *pVal; @@ -983,7 +986,9 @@ const sqlite3_module vt02Module = { /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, - /* xRollbackTo */ 0 + /* xRollbackTo */ 0, + /* xShadowName */ 0, + /* xIntegrity */ 0 }; static void vt02CoreInit(sqlite3 *db){ diff --git a/test/walseh1.test b/test/walseh1.test index c3a655f53..225b69f74 100644 --- a/test/walseh1.test +++ b/test/walseh1.test @@ -146,5 +146,3 @@ do_faultsim_test 6 -faults seh -prep { catch { db close } finish_test - - diff --git a/test/window1.test b/test/window1.test index 37a5183f9..c9bbae3ee 100644 --- a/test/window1.test +++ b/test/window1.test @@ -158,7 +158,7 @@ do_execsql_test 4.9 { do_execsql_test 4.10.1 { SELECT a, count() OVER (ORDER BY a DESC), - group_concat(a, '.') OVER (ORDER BY a DESC) + string_agg(a, '.') OVER (ORDER BY a DESC) FROM t2 ORDER BY a DESC } { 6 1 6 @@ -825,7 +825,7 @@ foreach {tn sql error} { } do_execsql_test 18.3.1 { - SELECT group_concat(c, '.') OVER (PARTITION BY b ORDER BY c) + SELECT string_agg(c, '.') OVER (PARTITION BY b ORDER BY c) FROM t1 } {four four.six four.six.two five five.one five.one.three} @@ -836,7 +836,7 @@ do_execsql_test 18.3.2 { } {four four.six four.six.two five five.one five.one.three} do_execsql_test 18.3.3 { - SELECT group_concat(c, '.') OVER win2 + SELECT string_agg(c, '.') OVER win2 FROM t1 WINDOW win1 AS (PARTITION BY b), win2 AS (win1 ORDER BY c) @@ -850,7 +850,7 @@ do_execsql_test 18.3.4 { } {four four.six four.six.two five five.one five.one.three} do_execsql_test 18.3.5 { - SELECT group_concat(c, '.') OVER win5 + SELECT string_agg(c, '.') OVER win5 FROM t1 WINDOW win1 AS (PARTITION BY b), win2 AS (win1), @@ -1127,7 +1127,7 @@ do_execsql_test 28.1.1 { } do_execsql_test 28.1.2 { - SELECT group_concat(b,'') OVER w1 FROM t1 + SELECT string_agg(b,'') OVER w1 FROM t1 WINDOW w1 AS (ORDER BY a RANGE BETWEEN 3 PRECEDING AND 1 PRECEDING) } { {} {} diff --git a/test/window3.test b/test/window3.test index 4f759abb7..1893b539d 100644 --- a/test/window3.test +++ b/test/window3.test @@ -1181,7 +1181,7 @@ do_execsql_test 1.1.13.6 { {} {} {} {} {} {} {} {} {} {} {}} do_execsql_test 1.1.14.1 { - SELECT group_concat(CAST(b AS TEXT), '.') OVER (ORDER BY a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t2 + SELECT string_agg(CAST(b AS TEXT), '.') OVER (ORDER BY a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t2 } {89 89.81 89.81.96 89.81.96.59 89.81.96.59.38 89.81.96.59.38.68 89.81.96.59.38.68.39 89.81.96.59.38.68.39.62 89.81.96.59.38.68.39.62.91 89.81.96.59.38.68.39.62.91.46 89.81.96.59.38.68.39.62.91.46.6 @@ -1471,7 +1471,7 @@ do_execsql_test 1.1.14.2 { 89.59.39.99.29.59.89.89.29.9.79.49.59.29.59.19.39.9.9.99.69.39} do_execsql_test 1.1.14.3 { - SELECT group_concat(CAST(b AS TEXT), '.') OVER ( ORDER BY b,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2 + SELECT string_agg(CAST(b AS TEXT), '.') OVER ( ORDER BY b,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2 } {1 1.1 1.1.2 1.1.2.2 1.1.2.2.3 1.1.2.2.3.3 1.1.2.2.3.3.4 1.1.2.2.3.3.4.5 1.1.2.2.3.3.4.5.6 1.1.2.2.3.3.4.5.6.7 1.1.2.2.3.3.4.5.6.7.7 1.1.2.2.3.3.4.5.6.7.7.7 1.1.2.2.3.3.4.5.6.7.7.7.8 @@ -1758,7 +1758,7 @@ do_execsql_test 1.1.14.4 { 9.9.9.19.29.29.29.39.39.39.49.59.59.59.59.69.79.89.89.89.99.99} do_execsql_test 1.1.14.5 { - SELECT group_concat(CAST(b AS TEXT), '.') OVER ( ORDER BY b%10,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2 + SELECT string_agg(CAST(b AS TEXT), '.') OVER ( ORDER BY b%10,a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM t2 } {90 90.40 90.40.30 90.40.30.80 90.40.30.80.20 90.40.30.80.20.90 90.40.30.80.20.90.60 90.40.30.80.20.90.60.70 90.40.30.80.20.90.60.70.80 90.40.30.80.20.90.60.70.80.90 90.40.30.80.20.90.60.70.80.90.30 @@ -1960,7 +1960,7 @@ do_execsql_test 1.1.14.6 { 83 27 17 7} do_execsql_test 1.1.14.7 { - SELECT group_concat(CAST(b AS TEXT), '.') OVER (win1 ORDER BY b%10 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) + SELECT string_agg(CAST(b AS TEXT), '.') OVER (win1 ORDER BY b%10 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t2 WINDOW win1 AS (PARTITION BY b%2,a) ORDER BY 1 diff --git a/test/with3.test b/test/with3.test index 650740dcc..9b110debf 100644 --- a/test/with3.test +++ b/test/with3.test @@ -132,8 +132,8 @@ do_eqp_test 3.2.2 { | | `--SCALAR SUBQUERY xxxxxx | | `--SCAN w2 | `--RECURSIVE STEP - | |--SCAN c - | `--SCAN w1 + | |--SCAN w1 + | `--SCAN c |--SCAN c |--SEARCH w2 USING INTEGER PRIMARY KEY (rowid=?) `--SEARCH w1 USING INTEGER PRIMARY KEY (rowid=?) diff --git a/tool/cktclsh.sh b/tool/cktclsh.sh new file mode 100644 index 000000000..1928a4099 --- /dev/null +++ b/tool/cktclsh.sh @@ -0,0 +1,11 @@ +# Fail with an error if the TCLSH named in $2 is not tclsh version $1 or later. +# +echo "set vers $1" >cktclsh$1.tcl +echo 'if {$tcl_version<$vers} {exit 1}' >>cktclsh$1.tcl +if ! $2 cktclsh$1.tcl +then + echo "ERROR: This makefile target requires tclsh $1 or later." + rm cktclsh$1.tcl + exit 1 +fi +rm cktclsh$1.tcl diff --git a/tool/fuzzershell.c b/tool/fuzzershell.c index 9a2710359..7a7aef029 100644 --- a/tool/fuzzershell.c +++ b/tool/fuzzershell.c @@ -680,6 +680,11 @@ static sqlite3_module seriesModule = { 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; /* END the generate_series(START,END,STEP) implementation *********************************************************************************/ diff --git a/tool/mkautoconfamal.sh b/tool/mkautoconfamal.sh index b7483189d..35dbfb41e 100644 --- a/tool/mkautoconfamal.sh +++ b/tool/mkautoconfamal.sh @@ -28,7 +28,7 @@ DATETIME=`grep '^D' $TOP/manifest | sed -e 's/[^0-9]//g' -e 's/\(............\). # Verify that the version number in the TEA autoconf file is correct. # Fail with an error if not. # -if grep $VERSION autoconf/tea/configure.ac +if grep $VERSION $TOP/autoconf/tea/configure.ac then echo "TEA version number ok" else echo "TEA version number mismatch. Should be $VERSION"; exit 1 fi diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl index 317f30a35..098bf16e3 100755 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -239,6 +239,7 @@ set boolean_defnil_options { SQLITE_OMIT_REINDEX SQLITE_OMIT_SCHEMA_PRAGMAS SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS + SQLITE_OMIT_SEH SQLITE_OMIT_SHARED_CACHE SQLITE_OMIT_SHUTDOWN_DIRECTORIES SQLITE_OMIT_SUBQUERY @@ -308,6 +309,7 @@ set value_options { SQLITE_ENABLE_8_3_NAMES SQLITE_ENABLE_CEROD SQLITE_ENABLE_LOCKING_STYLE + SQLITE_EXTRA_AUTOEXT SQLITE_EXTRA_INIT SQLITE_EXTRA_SHUTDOWN SQLITE_FTS3_MAX_EXPR_DEPTH diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 6def8fa44..53fa59ac7 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -116,7 +116,7 @@ if {$tcl_platform(platform)=="windows"} { if {[file executable $vsrcprog] && [file readable $srcroot/manifest]} { set res [string trim [split [exec $vsrcprog -x $srcroot]] \n] puts $out "** The content in this amalgamation comes from Fossil check-in" - puts -nonewline $out "** [string range [lindex $res 0] 1 35]" + puts -nonewline $out "** [string range [lindex $res 0] 0 35]" if {[llength $res]==1} { puts $out "." } else { diff --git a/tool/mktoolzip.tcl b/tool/mktoolzip.tcl new file mode 100644 index 000000000..885bae960 --- /dev/null +++ b/tool/mktoolzip.tcl @@ -0,0 +1,69 @@ +#!/usr/bin/tclsh +# +# Run this script in order to generate a ZIP archive containing various +# command-line tools. +# +# The makefile that invokes this script must first build the following +# binaries: +# +# testfixture -- used to run this script +# sqlite3 -- the SQLite CLI +# sqldiff -- Program to diff two databases +# sqlite3_analyzer -- Space analyzer +# +switch $tcl_platform(os) { + {Windows NT} { + set OS win32 + set EXE .exe + } + Linux { + set OS linux + set EXE {} + } + Darwin { + set OS osx + set EXE {} + } + default { + set OS unknown + set EXE {} + } +} +switch $tcl_platform(machine) { + arm64 { + set ARCH arm64 + } + x86_64 { + set ARCH x64 + } + amd64 - + intel { + if {$tcl_platform(pointerSize)==4} { + set ARCH x86 + } else { + set ARCH x64 + } + } + default { + set ARCH unk + } +} +set in [open [file join [file dirname [file dirname [info script]]] VERSION]] +set vers [read $in] +close $in +scan $vers %d.%d.%d v1 v2 v3 +set v2 [format 3%02d%02d00 $v2 $v3] +set name sqlite-tools-$OS-$ARCH-$v2.zip + +if {$OS=="win32"} { + # The win32 tar.exe supports the -a ("auto-compress") option. This causes + # tar to create an archive type based on the extension of the output file. + # In this case, a zip file. + puts "tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE" + puts [exec tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE] + puts "$name: [file size $name] bytes" +} else { + puts "zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE" + puts [exec zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE] + puts [exec ls -l $name] +} diff --git a/tool/showdb.c b/tool/showdb.c index 0e99331d7..1b80c7f17 100644 --- a/tool/showdb.c +++ b/tool/showdb.c @@ -959,6 +959,10 @@ static void page_usage_freelist(u32 pgno){ a = fileRead((pgno-1)*g.pagesize, g.pagesize); iNext = decodeInt32(a); n = decodeInt32(a+4); + if( n>(g.pagesize - 8)/4 ){ + printf("ERROR: page %d too many freelist entries (%d)\n", pgno, n); + n = (g.pagesize - 8)/4; + } for(i=0; i65536 ){ + printf("\"%s\": invalid page size.\n", argv[1]); + return 1; + } mxFrame = (sbuf.st_size - 32)/(pagesize + 24); printf("Available pages: 1..%d\n", mxFrame); if( argc==2 ){ diff --git a/tool/spaceanal.tcl b/tool/spaceanal.tcl index d0c5e65e3..8fe72b99b 100644 --- a/tool/spaceanal.tcl +++ b/tool/spaceanal.tcl @@ -581,6 +581,7 @@ set inuse_pgcnt [expr wide([mem eval $sql])] set inuse_percent [percent $inuse_pgcnt $file_pgcnt] set free_pgcnt [expr {$file_pgcnt-$inuse_pgcnt-$av_pgcnt}] +if {$file_bytes>1073741824 && $free_pgcnt>0} {incr free_pgcnt -1} set free_percent [percent $free_pgcnt $file_pgcnt] set free_pgcnt2 [db one {PRAGMA freelist_count}] set free_percent2 [percent $free_pgcnt2 $file_pgcnt] diff --git a/tool/sqldiff.c b/tool/sqldiff.c index 0d27ff89e..a61256611 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -596,9 +596,9 @@ static void diff_one_table(const char *zTab, FILE *out){ /* Build the comparison query */ for(n2=n; az2[n2]; n2++){ - char *zTab = safeId(az2[n2]); - fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zTab); - sqlite3_free(zTab); + char *zNTab = safeId(az2[n2]); + fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab); + sqlite3_free(zNTab); } nQ = nPk2+1+2*(n2-nPk2); if( n2>nPk2 ){ From 45a205917fe2325d2062e4c6ffec6c50f68ee34a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 13 Nov 2023 13:10:08 -0500 Subject: [PATCH 113/340] update LICENSE.md with SQLCipher license to avoid ambiguity and remove redundance --- LICENSE | 24 ------------------------ LICENSE.md | 28 +++++++++++++++++++++++----- 2 files changed, 23 insertions(+), 29 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7167addca..000000000 --- a/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2008-2023, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE.md b/LICENSE.md index f68a6c175..7167addca 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,24 @@ -The author disclaims copyright to this source code. In place of -a legal notice, here is a blessing: +Copyright (c) 2008-2023, ZETETIC LLC +All rights reserved. - * 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. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From c6bf239e2b4af7719a7b57b9060b008830490c80 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 15 Nov 2023 10:06:09 -0500 Subject: [PATCH 114/340] cipher_integrity check will report expected page size if invalid --- src/crypto_impl.c | 2 +- test/sqlcipher-integrity.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 318c48b03..6e3ab2c3e 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1391,7 +1391,7 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *col } if(file_sz % ctx->page_sz != 0) { - result = sqlite3_mprintf("page %d has an invalid size of %lld bytes", page, file_sz - ((file_sz / ctx->page_sz) * ctx->page_sz)); + result = sqlite3_mprintf("page %d has an invalid size of %lld bytes (expected %d bytes)", page, file_sz - ((file_sz / ctx->page_sz) * ctx->page_sz), ctx->page_sz); sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, result, P4_DYNAMIC); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); } diff --git a/test/sqlcipher-integrity.test b/test/sqlcipher-integrity.test index 5e13eff98..fa0949892 100644 --- a/test/sqlcipher-integrity.test +++ b/test/sqlcipher-integrity.test @@ -310,7 +310,7 @@ do_test version-4-integrity-check-invalid-last-page { PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; } -} {ok {page 240 has an invalid size of 2 bytes}} +} {ok {page 240 has an invalid size of 2 bytes (expected 4096 bytes)}} db close file delete -force test.db From abe21033397cb6d2868f83904a7ed1c0c46f61ac Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 27 Nov 2023 15:28:06 -0500 Subject: [PATCH 115/340] Snapshot of upstream SQLite 3.44.2 --- Makefile.in | 2 + Makefile.msc | 2 + VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 +- ext/consio/console_io.c | 679 ++++++ ext/consio/console_io.h | 280 +++ ext/fts5/fts5_hash.c | 6 +- ext/fts5/fts5_index.c | 37 +- ext/fts5/test/fts5corrupt5.test | 85 + ext/fts5/test/fts5faultG.test | 26 + ext/fts5/test/fts5prefix2.test | 30 + ext/fts5/test/fts5secure8.test | 51 + ext/wasm/api/sqlite3-wasm.c | 4 +- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js | 13 +- manifest | 88 +- manifest.uuid | 2 +- src/expr.c | 4 +- src/json.c | 96 +- src/main.c | 2 +- src/mutex_w32.c | 2 +- src/pager.c | 5 +- src/select.c | 13 +- src/shell.c.in | 2069 +++++++---------- src/sqlite.h.in | 61 +- src/sqliteInt.h | 12 +- src/vdbe.c | 5 +- src/vdbeapi.c | 12 + src/vdbeaux.c | 3 +- src/vdbemem.c | 2 +- src/where.c | 14 + src/window.c | 2 +- test/alter.test | 29 + test/indexexpr1.test | 14 + test/joinH.test | 58 + test/json501.test | 5 + test/shell1.test | 15 +- test/whereG.test | 14 + tool/mkctimec.tcl | 18 +- tool/mkpragmatab.tcl | 16 +- tool/srctree-check.tcl | 39 +- 41 files changed, 2495 insertions(+), 1342 deletions(-) create mode 100755 ext/consio/console_io.c create mode 100644 ext/consio/console_io.h create mode 100644 ext/fts5/test/fts5secure8.test diff --git a/Makefile.in b/Makefile.in index c521586a1..357c99514 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1137,6 +1137,8 @@ SHELL_SRC = \ $(TOP)/src/shell.c.in \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ + $(TOP)/ext/consio/console_io.c \ + $(TOP)/ext/consio/console_io.h \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/basexx.c \ $(TOP)/ext/misc/base64.c \ diff --git a/Makefile.msc b/Makefile.msc index 4339be891..3a4d46b01 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -2261,6 +2261,8 @@ keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe # Source files that go into making shell.c SHELL_SRC = \ $(TOP)\src\shell.c.in \ + $(TOP)\ext\consio\console_io.c \ + $(TOP)\ext\consio\console_io.h \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\completion.c \ $(TOP)\ext\misc\base64.c \ diff --git a/VERSION b/VERSION index faf0dcbb0..1b9d70c30 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.44.0 +3.44.2 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index e34bda9f2..503494b37 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.44.0]) +AC_INIT([sqlite],[3.44.2]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 7b31f6f7e..1931b8eb3 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.69 for sqlite 3.44.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.44.2. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.44.0' -PACKAGE_STRING='sqlite 3.44.0' +PACKAGE_VERSION='3.44.2' +PACKAGE_STRING='sqlite 3.44.2' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1472,7 +1472,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.44.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.44.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.44.0:";; + short | recursive ) echo "Configuration of sqlite 3.44.2:";; esac cat <<\_ACEOF @@ -1668,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.44.0 +sqlite configure 3.44.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2087,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.44.0, which was +It was created by sqlite $as_me 3.44.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.44.0, which was +This file was extended by sqlite $as_me 3.44.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.44.0 +sqlite config.status 3.44.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c new file mode 100755 index 000000000..6b412feef --- /dev/null +++ b/ext/consio/console_io.c @@ -0,0 +1,679 @@ +/* +** 2023 November 4 +** +** 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 various interfaces used for console and stream I/O +** by the SQLite project command-line tools, as explained in console_io.h . +** Functions prefixed by "SQLITE_INTERNAL_LINKAGE" behave as described there. +*/ + +#ifndef SQLITE_CDECL +# define SQLITE_CDECL +#endif + +#ifndef SHELL_NO_SYSINC +# include +# include +# include +# include +# include +# include "console_io.h" +# include "sqlite3.h" +#endif + +#ifndef SQLITE_CIO_NO_TRANSLATE +# if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT +# ifndef SHELL_NO_SYSINC +# include +# include +# undef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# include +# endif +# define CIO_WIN_WC_XLATE 1 /* Use WCHAR Windows APIs for console I/O */ +# else +# ifndef SHELL_NO_SYSINC +# include +# endif +# define CIO_WIN_WC_XLATE 0 /* Use plain C library stream I/O at console */ +# endif +#else +# define CIO_WIN_WC_XLATE 0 /* Not exposing translation routines at all */ +#endif + +#if CIO_WIN_WC_XLATE +/* Character used to represent a known-incomplete UTF-8 char group (�) */ +static WCHAR cBadGroup = 0xfffd; +#endif + +#if CIO_WIN_WC_XLATE +static HANDLE handleOfFile(FILE *pf){ + int fileDesc = _fileno(pf); + union { intptr_t osfh; HANDLE fh; } fid = { + (fileDesc>=0)? _get_osfhandle(fileDesc) : (intptr_t)INVALID_HANDLE_VALUE + }; + return fid.fh; +} +#endif + +#ifndef SQLITE_CIO_NO_TRANSLATE +typedef struct PerStreamTags { +# if CIO_WIN_WC_XLATE + HANDLE hx; + DWORD consMode; + char acIncomplete[4]; +# else + short reachesConsole; +# endif + FILE *pf; +} PerStreamTags; + +/* Define NULL-like value for things which can validly be 0. */ +# define SHELL_INVALID_FILE_PTR ((FILE *)~0) +# if CIO_WIN_WC_XLATE +# define SHELL_INVALID_CONS_MODE 0xFFFF0000 +# endif + +# if CIO_WIN_WC_XLATE +# define PST_INITIALIZER { INVALID_HANDLE_VALUE, SHELL_INVALID_CONS_MODE, \ + {0,0,0,0}, SHELL_INVALID_FILE_PTR } +# else +# define PST_INITIALIZER { 0, SHELL_INVALID_FILE_PTR } +# endif + +/* Quickly say whether a known output is going to the console. */ +# if CIO_WIN_WC_XLATE +static short pstReachesConsole(PerStreamTags *ppst){ + return (ppst->hx != INVALID_HANDLE_VALUE); +} +# else +# define pstReachesConsole(ppst) 0 +# endif + +# if CIO_WIN_WC_XLATE +static void restoreConsoleArb(PerStreamTags *ppst){ + if( pstReachesConsole(ppst) ) SetConsoleMode(ppst->hx, ppst->consMode); +} +# else +# define restoreConsoleArb(ppst) +# endif + +/* Say whether FILE* appears to be a console, collect associated info. */ +static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){ +# if CIO_WIN_WC_XLATE + short rv = 0; + DWORD dwCM = SHELL_INVALID_CONS_MODE; + HANDLE fh = handleOfFile(pf); + ppst->pf = pf; + if( INVALID_HANDLE_VALUE != fh ){ + rv = (GetFileType(fh) == FILE_TYPE_CHAR && GetConsoleMode(fh,&dwCM)); + } + ppst->hx = (rv)? fh : INVALID_HANDLE_VALUE; + ppst->consMode = dwCM; + return rv; +# else + ppst->pf = pf; + ppst->reachesConsole = ( (short)isatty(fileno(pf)) ); + return ppst->reachesConsole; +# endif +} + +# if CIO_WIN_WC_XLATE +/* Define console modes for use with the Windows Console API. */ +# define SHELL_CONI_MODE \ + (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | 0x80 \ + | ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_PROCESSED_INPUT) +# define SHELL_CONO_MODE (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT \ + | ENABLE_VIRTUAL_TERMINAL_PROCESSING) +# endif + +typedef struct ConsoleInfo { + PerStreamTags pstSetup[3]; + PerStreamTags pstDesignated[3]; + StreamsAreConsole sacSetup; +} ConsoleInfo; + +static short isValidStreamInfo(PerStreamTags *ppst){ + return (ppst->pf != SHELL_INVALID_FILE_PTR); +} + +static ConsoleInfo consoleInfo = { + { /* pstSetup */ PST_INITIALIZER, PST_INITIALIZER, PST_INITIALIZER }, + { /* pstDesignated[] */ PST_INITIALIZER, PST_INITIALIZER, PST_INITIALIZER }, + SAC_NoConsole /* sacSetup */ +}; + +SQLITE_INTERNAL_LINKAGE FILE* invalidFileStream = (FILE *)~0; + +# if CIO_WIN_WC_XLATE +static void maybeSetupAsConsole(PerStreamTags *ppst, short odir){ + if( pstReachesConsole(ppst) ){ + DWORD cm = odir? SHELL_CONO_MODE : SHELL_CONI_MODE; + SetConsoleMode(ppst->hx, cm); + } +} +# else +# define maybeSetupAsConsole(ppst,odir) +# endif + +SQLITE_INTERNAL_LINKAGE void consoleRenewSetup(void){ +# if CIO_WIN_WC_XLATE + int ix = 0; + while( ix < 6 ){ + PerStreamTags *ppst = (ix<3)? + &consoleInfo.pstSetup[ix] : &consoleInfo.pstDesignated[ix-3]; + maybeSetupAsConsole(ppst, (ix % 3)>0); + ++ix; + } +# endif +} + +SQLITE_INTERNAL_LINKAGE StreamsAreConsole +consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ){ + StreamsAreConsole rv = SAC_NoConsole; + FILE* apf[3] = { pfIn, pfOut, pfErr }; + int ix; + for( ix = 2; ix >= 0; --ix ){ + PerStreamTags *ppst = &consoleInfo.pstSetup[ix]; + if( streamOfConsole(apf[ix], ppst) ){ + rv |= (SAC_InConsole< 0 ) fflush(apf[ix]); + } + consoleInfo.sacSetup = rv; + consoleRenewSetup(); + return rv; +} + +SQLITE_INTERNAL_LINKAGE void SQLITE_CDECL consoleRestore( void ){ +# if CIO_WIN_WC_XLATE + static ConsoleInfo *pci = &consoleInfo; + if( pci->sacSetup ){ + int ix; + for( ix=0; ix<3; ++ix ){ + if( pci->sacSetup & (SAC_InConsole<pstSetup[ix]; + SetConsoleMode(ppst->hx, ppst->consMode); + } + } + } +# endif +} +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#ifdef SQLITE_CIO_INPUT_REDIR +/* Say whether given FILE* is among those known, via either +** consoleClassifySetup() or set{Output,Error}Stream, as +** readable, and return an associated PerStreamTags pointer +** if so. Otherwise, return 0. +*/ +static PerStreamTags * isKnownReadable(FILE *pf){ + static PerStreamTags *apst[] = { + &consoleInfo.pstDesignated[0], &consoleInfo.pstSetup[0], 0 + }; + int ix = 0; + do { + if( apst[ix]->pf == pf ) break; + } while( apst[++ix] != 0 ); + return apst[ix]; +} +#endif + +#ifndef SQLITE_CIO_NO_TRANSLATE +/* Say whether given FILE* is among those known, via either +** consoleClassifySetup() or set{Output,Error}Stream, as +** writable, and return an associated PerStreamTags pointer +** if so. Otherwise, return 0. +*/ +static PerStreamTags * isKnownWritable(FILE *pf){ + static PerStreamTags *apst[] = { + &consoleInfo.pstDesignated[1], &consoleInfo.pstDesignated[2], + &consoleInfo.pstSetup[1], &consoleInfo.pstSetup[2], 0 + }; + int ix = 0; + do { + if( apst[ix]->pf == pf ) break; + } while( apst[++ix] != 0 ); + return apst[ix]; +} + +static FILE *designateEmitStream(FILE *pf, unsigned chix){ + FILE *rv = consoleInfo.pstDesignated[chix].pf; + if( pf == invalidFileStream ) return rv; + else{ + /* Setting a possibly new output stream. */ + PerStreamTags *ppst = isKnownWritable(pf); + if( ppst != 0 ){ + PerStreamTags pst = *ppst; + consoleInfo.pstDesignated[chix] = pst; + }else streamOfConsole(pf, &consoleInfo.pstDesignated[chix]); + } + return rv; +} + +SQLITE_INTERNAL_LINKAGE FILE *setOutputStream(FILE *pf){ + return designateEmitStream(pf, 1); +} +# ifdef CONSIO_SET_ERROR_STREAM +SQLITE_INTERNAL_LINKAGE FILE *setErrorStream(FILE *pf){ + return designateEmitStream(pf, 2); +} +# endif +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#ifndef SQLITE_CIO_NO_SETMODE +# if CIO_WIN_WC_XLATE +static void setModeFlushQ(FILE *pf, short bFlush, int mode){ + if( bFlush ) fflush(pf); + _setmode(_fileno(pf), mode); +} +# else +# define setModeFlushQ(f, b, m) if(b) fflush(f) +# endif + +SQLITE_INTERNAL_LINKAGE void setBinaryMode(FILE *pf, short bFlush){ + setModeFlushQ(pf, bFlush, _O_BINARY); +} +SQLITE_INTERNAL_LINKAGE void setTextMode(FILE *pf, short bFlush){ + setModeFlushQ(pf, bFlush, _O_TEXT); +} +# undef setModeFlushQ + +#else /* defined(SQLITE_CIO_NO_SETMODE) */ +# define setBinaryMode(f, bFlush) do{ if((bFlush)) fflush(f); }while(0) +# define setTextMode(f, bFlush) do{ if((bFlush)) fflush(f); }while(0) +#endif /* defined(SQLITE_CIO_NO_SETMODE) */ + +#ifndef SQLITE_CIO_NO_TRANSLATE +# if CIO_WIN_WC_XLATE +/* Write buffer cBuf as output to stream known to reach console, +** limited to ncTake char's. Return ncTake on success, else 0. */ +static int conZstrEmit(PerStreamTags *ppst, const char *z, int ncTake){ + int rv = 0; + if( z!=NULL ){ + int nwc = MultiByteToWideChar(CP_UTF8,0, z,ncTake, 0,0); + if( nwc > 0 ){ + WCHAR *zw = sqlite3_malloc64(nwc*sizeof(WCHAR)); + if( zw!=NULL ){ + nwc = MultiByteToWideChar(CP_UTF8,0, z,ncTake, zw,nwc); + if( nwc > 0 ){ + /* Translation from UTF-8 to UTF-16, then WCHARs out. */ + if( WriteConsoleW(ppst->hx, zw,nwc, 0, NULL) ){ + rv = ncTake; + } + } + sqlite3_free(zw); + } + } + } + return rv; +} + +/* For {f,o,e}PrintfUtf8() when stream is known to reach console. */ +static int conioVmPrintf(PerStreamTags *ppst, const char *zFormat, va_list ap){ + char *z = sqlite3_vmprintf(zFormat, ap); + if( z ){ + int rv = conZstrEmit(ppst, z, (int)strlen(z)); + sqlite3_free(z); + return rv; + }else return 0; +} +# endif /* CIO_WIN_WC_XLATE */ + +# ifdef CONSIO_GET_EMIT_STREAM +static PerStreamTags * getDesignatedEmitStream(FILE *pf, unsigned chix, + PerStreamTags *ppst){ + PerStreamTags *rv = isKnownWritable(pf); + short isValid = (rv!=0)? isValidStreamInfo(rv) : 0; + if( rv != 0 && isValid ) return rv; + streamOfConsole(pf, ppst); + return ppst; +} +# endif + +/* Get stream info, either for designated output or error stream when +** chix equals 1 or 2, or for an arbitrary stream when chix == 0. +** In either case, ppst references a caller-owned PerStreamTags +** struct which may be filled in if none of the known writable +** streams is being held by consoleInfo. The ppf parameter is a +** byref output when chix!=0 and a byref input when chix==0. + */ +static PerStreamTags * +getEmitStreamInfo(unsigned chix, PerStreamTags *ppst, + /* in/out */ FILE **ppf){ + PerStreamTags *ppstTry; + FILE *pfEmit; + if( chix > 0 ){ + ppstTry = &consoleInfo.pstDesignated[chix]; + if( !isValidStreamInfo(ppstTry) ){ + ppstTry = &consoleInfo.pstSetup[chix]; + pfEmit = ppst->pf; + }else pfEmit = ppstTry->pf; + if( !isValidStreamInfo(ppstTry) ){ + pfEmit = (chix > 1)? stderr : stdout; + ppstTry = ppst; + streamOfConsole(pfEmit, ppstTry); + } + *ppf = pfEmit; + }else{ + ppstTry = isKnownWritable(*ppf); + if( ppstTry != 0 ) return ppstTry; + streamOfConsole(*ppf, ppst); + return ppst; + } + return ppstTry; +} + +SQLITE_INTERNAL_LINKAGE int oPrintfUtf8(const char *zFormat, ...){ + va_list ap; + int rv; + FILE *pfOut; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); +# else + getEmitStreamInfo(1, &pst, &pfOut); +# endif + assert(zFormat!=0); + va_start(ap, zFormat); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + rv = conioVmPrintf(ppst, zFormat, ap); + }else{ +# endif + rv = vfprintf(pfOut, zFormat, ap); +# if CIO_WIN_WC_XLATE + } +# endif + va_end(ap); + return rv; +} + +SQLITE_INTERNAL_LINKAGE int ePrintfUtf8(const char *zFormat, ...){ + va_list ap; + int rv; + FILE *pfErr; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); +# else + getEmitStreamInfo(2, &pst, &pfErr); +# endif + assert(zFormat!=0); + va_start(ap, zFormat); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + rv = conioVmPrintf(ppst, zFormat, ap); + }else{ +# endif + rv = vfprintf(pfErr, zFormat, ap); +# if CIO_WIN_WC_XLATE + } +# endif + va_end(ap); + return rv; +} + +SQLITE_INTERNAL_LINKAGE int fPrintfUtf8(FILE *pfO, const char *zFormat, ...){ + va_list ap; + int rv; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); +# else + getEmitStreamInfo(0, &pst, &pfO); +# endif + assert(zFormat!=0); + va_start(ap, zFormat); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + maybeSetupAsConsole(ppst, 1); + rv = conioVmPrintf(ppst, zFormat, ap); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + }else{ +# endif + rv = vfprintf(pfO, zFormat, ap); +# if CIO_WIN_WC_XLATE + } +# endif + va_end(ap); + return rv; +} + +SQLITE_INTERNAL_LINKAGE int fPutsUtf8(const char *z, FILE *pfO){ + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); +# else + getEmitStreamInfo(0, &pst, &pfO); +# endif + assert(z!=0); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + int rv; + maybeSetupAsConsole(ppst, 1); + rv = conZstrEmit(ppst, z, (int)strlen(z)); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + return rv; + }else { +# endif + return (fputs(z, pfO)<0)? 0 : (int)strlen(z); +# if CIO_WIN_WC_XLATE + } +# endif +} + +SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z){ + FILE *pfErr; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); +# else + getEmitStreamInfo(2, &pst, &pfErr); +# endif + assert(z!=0); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ) return conZstrEmit(ppst, z, (int)strlen(z)); + else { +# endif + return (fputs(z, pfErr)<0)? 0 : (int)strlen(z); +# if CIO_WIN_WC_XLATE + } +# endif +} + +SQLITE_INTERNAL_LINKAGE int oPutsUtf8(const char *z){ + FILE *pfOut; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); +# else + getEmitStreamInfo(1, &pst, &pfOut); +# endif + assert(z!=0); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ) return conZstrEmit(ppst, z, (int)strlen(z)); + else { +# endif + return (fputs(z, pfOut)<0)? 0 : (int)strlen(z); +# if CIO_WIN_WC_XLATE + } +# endif +} + +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#if !(defined(SQLITE_CIO_NO_UTF8SCAN) && defined(SQLITE_CIO_NO_TRANSLATE)) +/* Skip over as much z[] input char sequence as is valid UTF-8, +** limited per nAccept char's or whole characters and containing +** no char cn such that ((1<=0 => char count, nAccept<0 => character + */ +SQLITE_INTERNAL_LINKAGE const char* +zSkipValidUtf8(const char *z, int nAccept, long ccm){ + int ng = (nAccept<0)? -nAccept : 0; + const char *pcLimit = (nAccept>=0)? z+nAccept : 0; + assert(z!=0); + while( (pcLimit)? (z= pcLimit ) return z; + else{ + char ct = *zt++; + if( ct==0 || (zt-z)>4 || (ct & 0xC0)!=0x80 ){ + /* Trailing bytes are too few, too many, or invalid. */ + return z; + } + } + } while( ((c <<= 1) & 0x40) == 0x40 ); /* Eat lead byte's count. */ + z = zt; + } + } + return z; +} +#endif /*!(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE))*/ + +#ifndef SQLITE_CIO_NO_TRANSLATE + +#ifdef CONSIO_SPUTB +SQLITE_INTERNAL_LINKAGE int +fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){ + assert(pfO!=0); +# if CIO_WIN_WC_XLATE + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); + if( pstReachesConsole(ppst) ){ + int rv; + maybeSetupAsConsole(ppst, 1); + rv = conZstrEmit(ppst, cBuf, nAccept); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + return rv; + }else { +# endif + return (int)fwrite(cBuf, 1, nAccept, pfO); +# if CIO_WIN_WC_XLATE + } +# endif +} +#endif /* defined(CONSIO_SPUTB) */ + +SQLITE_INTERNAL_LINKAGE int +oPutbUtf8(const char *cBuf, int nAccept){ + FILE *pfOut; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); +# else + getEmitStreamInfo(1, &pst, &pfOut); +# endif +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + return conZstrEmit(ppst, cBuf, nAccept); + }else { +# endif + return (int)fwrite(cBuf, 1, nAccept, pfOut); +# if CIO_WIN_WC_XLATE + } +# endif +} + +# ifdef CONSIO_EPUTB +SQLITE_INTERNAL_LINKAGE int +ePutbUtf8(const char *cBuf, int nAccept){ + FILE *pfErr; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + return conZstrEmit(ppst, cBuf, nAccept); + }else { +# endif + return (int)fwrite(cBuf, 1, nAccept, pfErr); +# if CIO_WIN_WC_XLATE + } +# endif +} +# endif /* defined(CONSIO_EPUTB) */ + +SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ + if( pfIn==0 ) pfIn = stdin; +# if CIO_WIN_WC_XLATE + if( pfIn == consoleInfo.pstSetup[0].pf + && (consoleInfo.sacSetup & SAC_InConsole)!=0 ){ +# if CIO_WIN_WC_XLATE==1 +# define SHELL_GULP 150 /* Count of WCHARS to be gulped at a time */ + WCHAR wcBuf[SHELL_GULP+1]; + int lend = 0, noc = 0; + if( ncMax > 0 ) cBuf[0] = 0; + while( noc < ncMax-8-1 && !lend ){ + /* There is room for at least 2 more characters and a 0-terminator. */ + int na = (ncMax > SHELL_GULP*4+1 + noc)? SHELL_GULP : (ncMax-1 - noc)/4; +# undef SHELL_GULP + DWORD nbr = 0; + BOOL bRC = ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf, na, &nbr, 0); + if( bRC && nbr>0 && (wcBuf[nbr-1]&0xF800)==0xD800 ){ + /* Last WHAR read is first of a UTF-16 surrogate pair. Grab its mate. */ + DWORD nbrx; + bRC &= ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf+nbr, 1, &nbrx, 0); + if( bRC ) nbr += nbrx; + } + if( !bRC || (noc==0 && nbr==0) ) return 0; + if( nbr > 0 ){ + int nmb = WideCharToMultiByte(CP_UTF8, 0, wcBuf,nbr,0,0,0,0); + if( nmb != 0 && noc+nmb <= ncMax ){ + int iseg = noc; + nmb = WideCharToMultiByte(CP_UTF8, 0, wcBuf,nbr,cBuf+noc,nmb,0,0); + noc += nmb; + /* Fixup line-ends as coded by Windows for CR (or "Enter".) + ** This is done without regard for any setMode{Text,Binary}() + ** call that might have been done on the interactive input. + */ + if( noc > 0 ){ + if( cBuf[noc-1]=='\n' ){ + lend = 1; + if( noc > 1 && cBuf[noc-2]=='\r' ) cBuf[--noc-1] = '\n'; + } + } + /* Check for ^Z (anywhere in line) too, to act as EOF. */ + while( iseg < noc ){ + if( cBuf[iseg]=='\x1a' ){ + noc = iseg; /* Chop ^Z and anything following. */ + lend = 1; /* Counts as end of line too. */ + break; + } + ++iseg; + } + }else break; /* Drop apparent garbage in. (Could assert.) */ + }else break; + } + /* If got nothing, (after ^Z chop), must be at end-of-file. */ + if( noc > 0 ){ + cBuf[noc] = 0; + return cBuf; + }else return 0; +# endif + }else{ +# endif + return fgets(cBuf, ncMax, pfIn); +# if CIO_WIN_WC_XLATE + } +# endif +} +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#undef SHELL_INVALID_FILE_PTR diff --git a/ext/consio/console_io.h b/ext/consio/console_io.h new file mode 100644 index 000000000..21697d595 --- /dev/null +++ b/ext/consio/console_io.h @@ -0,0 +1,280 @@ +/* +** 2023 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 exposes various interfaces used for console and other I/O +** by the SQLite project command-line tools. These interfaces are used +** at either source conglomeration time, compilation time, or run time. +** This source provides for either inclusion into conglomerated, +** "single-source" forms or separate compilation then linking. +** +** Platform dependencies are "hidden" here by various stratagems so +** that, provided certain conditions are met, the programs using this +** source or object code compiled from it need no explicit conditional +** compilation in their source for their console and stream I/O. +** +** The symbols and functionality exposed here are not a public API. +** This code may change in tandem with other project code as needed. +** +** When this .h file and its companion .c are directly incorporated into +** a source conglomeration (such as shell.c), the preprocessor symbol +** CIO_WIN_WC_XLATE is defined as 0 or 1, reflecting whether console I/O +** translation for Windows is effected for the build. +*/ + +#ifndef SQLITE_INTERNAL_LINKAGE +# define SQLITE_INTERNAL_LINKAGE extern /* external to translation unit */ +# include +#else +# define SHELL_NO_SYSINC /* Better yet, modify mkshellc.tcl for this. */ +#endif + +#ifndef SQLITE3_H +# include "sqlite3.h" +#endif + +#ifndef SQLITE_CIO_NO_CLASSIFY + +/* Define enum for use with following function. */ +typedef enum StreamsAreConsole { + SAC_NoConsole = 0, + SAC_InConsole = 1, SAC_OutConsole = 2, SAC_ErrConsole = 4, + SAC_AnyConsole = 0x7 +} StreamsAreConsole; + +/* +** Classify the three standard I/O streams according to whether +** they are connected to a console attached to the process. +** +** Returns the bit-wise OR of SAC_{In,Out,Err}Console values, +** or SAC_NoConsole if none of the streams reaches a console. +** +** This function should be called before any I/O is done with +** the given streams. As a side-effect, the given inputs are +** recorded so that later I/O operations on them may be done +** differently than the C library FILE* I/O would be done, +** iff the stream is used for the I/O functions that follow, +** and to support the ones that use an implicit stream. +** +** On some platforms, stream or console mode alteration (aka +** "Setup") may be made which is undone by consoleRestore(). +*/ +SQLITE_INTERNAL_LINKAGE StreamsAreConsole +consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ); +/* A usual call for convenience: */ +#define SQLITE_STD_CONSOLE_INIT() consoleClassifySetup(stdin,stdout,stderr) + +/* +** After an initial call to consoleClassifySetup(...), renew +** the same setup it effected. (A call not after is an error.) +** This will restore state altered by consoleRestore(); +** +** Applications which run an inferior (child) process which +** inherits the same I/O streams may call this function after +** such a process exits to guard against console mode changes. +*/ +SQLITE_INTERNAL_LINKAGE void consoleRenewSetup(void); + +/* +** Undo any side-effects left by consoleClassifySetup(...). +** +** This should be called after consoleClassifySetup() and +** before the process terminates normally. It is suitable +** for use with the atexit() C library procedure. After +** this call, no console I/O should be done until one of +** console{Classify or Renew}Setup(...) is called again. +** +** Applications which run an inferior (child) process that +** inherits the same I/O streams might call this procedure +** before so that said process will have a console setup +** however users have configured it or come to expect. +*/ +SQLITE_INTERNAL_LINKAGE void SQLITE_CDECL consoleRestore( void ); + +#else /* defined(SQLITE_CIO_NO_CLASSIFY) */ +# define consoleClassifySetup(i,o,e) +# define consoleRenewSetup() +# define consoleRestore() +#endif /* defined(SQLITE_CIO_NO_CLASSIFY) */ + +#ifndef SQLITE_CIO_NO_REDIRECT +/* +** Set stream to be used for the functions below which write +** to "the designated X stream", where X is Output or Error. +** Returns the previous value. +** +** Alternatively, pass the special value, invalidFileStream, +** to get the designated stream value without setting it. +** +** Before the designated streams are set, they default to +** those passed to consoleClassifySetup(...), and before +** that is called they default to stdout and stderr. +** +** It is error to close a stream so designated, then, without +** designating another, use the corresponding {o,e}Emit(...). +*/ +SQLITE_INTERNAL_LINKAGE FILE *invalidFileStream; +SQLITE_INTERNAL_LINKAGE FILE *setOutputStream(FILE *pf); +# ifdef CONSIO_SET_ERROR_STREAM +SQLITE_INTERNAL_LINKAGE FILE *setErrorStream(FILE *pf); +# endif +#else +# define setOutputStream(pf) +# define setErrorStream(pf) +#endif /* !defined(SQLITE_CIO_NO_REDIRECT) */ + +#ifndef SQLITE_CIO_NO_TRANSLATE +/* +** Emit output like fprintf(). If the output is going to the +** console and translation from UTF-8 is necessary, perform +** the needed translation. Otherwise, write formatted output +** to the provided stream almost as-is, possibly with newline +** translation as specified by set{Binary,Text}Mode(). +*/ +SQLITE_INTERNAL_LINKAGE int fPrintfUtf8(FILE *pfO, const char *zFormat, ...); +/* Like fPrintfUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int oPrintfUtf8(const char *zFormat, ...); +/* Like fPrintfUtf8 except stream is always the designated error. */ +SQLITE_INTERNAL_LINKAGE int ePrintfUtf8(const char *zFormat, ...); + +/* +** Emit output like fputs(). If the output is going to the +** console and translation from UTF-8 is necessary, perform +** the needed translation. Otherwise, write given text to the +** provided stream almost as-is, possibly with newline +** translation as specified by set{Binary,Text}Mode(). +*/ +SQLITE_INTERNAL_LINKAGE int fPutsUtf8(const char *z, FILE *pfO); +/* Like fPutsUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int oPutsUtf8(const char *z); +/* Like fPutsUtf8 except stream is always the designated error. */ +SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z); + +/* +** Emit output like fPutsUtf8(), except that the length of the +** accepted char or character sequence is limited by nAccept. +** +** Returns the number of accepted char values. +*/ +#ifdef CONSIO_SPUTB +SQLITE_INTERNAL_LINKAGE int +fPutbUtf8(FILE *pfOut, const char *cBuf, int nAccept); +#endif +/* Like fPutbUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int +oPutbUtf8(const char *cBuf, int nAccept); +/* Like fPutbUtf8 except stream is always the designated error. */ +#ifdef CONSIO_EPUTB +SQLITE_INTERNAL_LINKAGE int +ePutbUtf8(const char *cBuf, int nAccept); +#endif + +/* +** Collect input like fgets(...) with special provisions for input +** from the console on platforms that require same. Defers to the +** C library fgets() when input is not from the console. Newline +** translation may be done as set by set{Binary,Text}Mode(). As a +** convenience, pfIn==NULL is treated as stdin. +*/ +SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn); +/* Like fGetsUtf8 except stream is always the designated input. */ +/* SQLITE_INTERNAL_LINKAGE char* iGetsUtf8(char *cBuf, int ncMax); */ + +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#ifndef SQLITE_CIO_NO_SETMODE +/* +** Set given stream for binary mode, where newline translation is +** not done, or for text mode where, for some platforms, newlines +** are translated to the platform's conventional char sequence. +** If bFlush true, flush the stream. +** +** An additional side-effect is that if the stream is one passed +** to consoleClassifySetup() as an output, it is flushed first. +** +** Note that binary/text mode has no effect on console I/O +** translation. On all platforms, newline to the console starts +** a new line and CR,LF chars from the console become a newline. +*/ +SQLITE_INTERNAL_LINKAGE void setBinaryMode(FILE *, short bFlush); +SQLITE_INTERNAL_LINKAGE void setTextMode(FILE *, short bFlush); +#endif + +#ifdef SQLITE_CIO_PROMPTED_IN +typedef struct Prompts { + int numPrompts; + const char **azPrompts; +} Prompts; + +/* +** Macros for use of a line editor. +** +** The following macros define operations involving use of a +** line-editing library or simple console interaction. +** A "T" argument is a text (char *) buffer or filename. +** A "N" argument is an integer. +** +** SHELL_ADD_HISTORY(T) // Record text as line(s) of history. +** SHELL_READ_HISTORY(T) // Read history from file named by T. +** SHELL_WRITE_HISTORY(T) // Write history to file named by T. +** SHELL_STIFLE_HISTORY(N) // Limit history to N entries. +** +** A console program which does interactive console input is +** expected to call: +** SHELL_READ_HISTORY(T) before collecting such input; +** SHELL_ADD_HISTORY(T) as record-worthy input is taken; +** SHELL_STIFLE_HISTORY(N) after console input ceases; then +** SHELL_WRITE_HISTORY(T) before the program exits. +*/ + +/* +** Retrieve a single line of input text from an input stream. +** +** If pfIn is the input stream passed to consoleClassifySetup(), +** and azPrompt is not NULL, then a prompt is issued before the +** line is collected, as selected by the isContinuation flag. +** Array azPrompt[{0,1}] holds the {main,continuation} prompt. +** +** If zBufPrior is not NULL then it is a buffer from a prior +** call to this routine that can be reused, or will be freed. +** +** The result is stored in space obtained from malloc() and +** must either be freed by the caller or else passed back to +** this function as zBufPrior for reuse. +** +** This function may call upon services of a line-editing +** library to interactively collect line edited input. +*/ +SQLITE_INTERNAL_LINKAGE char * +shellGetLine(FILE *pfIn, char *zBufPrior, int nLen, + short isContinuation, Prompts azPrompt); +#endif /* defined(SQLITE_CIO_PROMPTED_IN) */ +/* +** TBD: Define an interface for application(s) to generate +** completion candidates for use by the line-editor. +** +** This may be premature; the CLI is the only application +** that does this. Yet, getting line-editing melded into +** console I/O is desirable because a line-editing library +** may have to establish console operating mode, possibly +** in a way that interferes with the above functionality. +*/ + +#if !(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE)) +/* Skip over as much z[] input char sequence as is valid UTF-8, +** limited per nAccept char's or whole characters and containing +** no char cn such that ((1<=0 => char count, nAccept<0 => character + */ +SQLITE_INTERNAL_LINKAGE const char* +zSkipValidUtf8(const char *z, int nAccept, long ccm); + +#endif diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c index 7e50c3660..391791c7a 100644 --- a/ext/fts5/fts5_hash.c +++ b/ext/fts5/fts5_hash.c @@ -432,10 +432,8 @@ static Fts5HashEntry *fts5HashEntryMerge( } /* -** Extract all tokens from hash table iHash and link them into a list -** in sorted order. The hash table is cleared before returning. It is -** the responsibility of the caller to free the elements of the returned -** list. +** Link all tokens from hash table iHash into a list in sorted order. The +** tokens are not removed from the hash table. */ static int fts5HashEntrySort( Fts5Hash *pHash, diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 4e6afb281..c467addb8 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -2719,6 +2719,14 @@ static void fts5SegIterHashInit( pLeaf->p = (u8*)pList; } } + + /* The call to sqlite3Fts5HashScanInit() causes the hash table to + ** fill the size field of all existing position lists. This means they + ** can no longer be appended to. Since the only scenario in which they + ** can be appended to is if the previous operation on this table was + ** a DELETE, by clearing the Fts5Index.bDelete flag we can avoid this + ** possibility altogether. */ + p->bDelete = 0; }else{ p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data), (const char*)pTerm, nTerm, (void**)&pLeaf, &nList @@ -4396,7 +4404,7 @@ static void fts5WriteAppendPoslistData( const u8 *a = aData; int n = nData; - assert( p->pConfig->pgsz>0 ); + assert( p->pConfig->pgsz>0 || p->rc!=SQLITE_OK ); while( p->rc==SQLITE_OK && (pPage->buf.n + pPage->pgidx.n + n)>=p->pConfig->pgsz ){ @@ -5129,18 +5137,24 @@ static void fts5DoSecureDelete( iOff = iStart; - /* Set variable bLastInDoclist to true if this entry happens to be - ** the last rowid in the doclist for its term. */ + /* If the position-list for the entry being removed flows over past + ** the end of this page, delete the portion of the position-list on the + ** next page and beyond. + ** + ** Set variable bLastInDoclist to true if this entry happens + ** to be the last rowid in the doclist for its term. */ + if( iNextOff>=iPgIdx ){ + int pgno = pSeg->iLeafPgno+1; + fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); + iNextOff = iPgIdx; + } + if( pSeg->bDel==0 ){ - if( iNextOff>=iPgIdx ){ - int pgno = pSeg->iLeafPgno+1; - fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); - iNextOff = iPgIdx; - }else{ + if( iNextOff!=iPgIdx ){ /* Loop through the page-footer. If iNextOff (offset of the ** entry following the one we are removing) is equal to the ** offset of a key on this page, then the entry is the last - ** in its doclist. */ + ** in its doclist. */ int iKeyOff = 0; for(iIdx=0; iIdxrc==SQLITE_OK ); fts5IndexFlush(p); - assert( p->nContentlessDelete==0 ); + assert( p->rc!=SQLITE_OK || p->nContentlessDelete==0 ); pStruct = fts5StructureRead(p); + assert( p->rc!=SQLITE_OK || pStruct!=0 ); fts5StructureInvalidate(p); if( pStruct ){ @@ -6204,7 +6219,7 @@ int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ /* Flush the hash table to disk if required */ if( iRowidiWriteRowid || (iRowid==p->iWriteRowid && p->bDelete==0) - || (p->nPendingData > p->pConfig->nHashSize) + || (p->nPendingData > p->pConfig->nHashSize) ){ fts5IndexFlush(p); } diff --git a/ext/fts5/test/fts5corrupt5.test b/ext/fts5/test/fts5corrupt5.test index efbe2e13e..60910dce7 100644 --- a/ext/fts5/test/fts5corrupt5.test +++ b/ext/fts5/test/fts5corrupt5.test @@ -880,6 +880,91 @@ do_catchsql_test 5.4 { UPDATE t1 SET content=randomblob(500); } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db +do_test 6.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 32768 pagesize 4096 filename crash-42fa37b694d45a.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........ +| 96: 00 00 00 00 0d 00 00 00 07 0d d2 00 0f c4 0f 6d ...............m +| 112: 0f 02 0e ab 0e 4e 0d f6 0d d2 00 00 00 00 00 00 .....N.......... +| 3536: 00 00 22 07 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet +| 3552: 32 74 32 07 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE +| 3568: 20 74 32 28 78 29 56 06 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta +| 3584: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c +| 3600: 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB +| 3616: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k +| 3632: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) +| 3648: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 05 WITHOUT ROWID[. +| 3664: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d +| 3680: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize +| 3696: 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't +| 3712: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN +| 3728: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE +| 3744: 59 2c 20 73 7a 20 42 4c 4f 42 29 55 04 06 17 21 Y, sz BLOB)U...! +| 3760: 21 01 77 74 61 62 6c 65 74 31 5f 63 6f 6e 74 65 !.wtablet1_conte +| 3776: 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 04 43 52 45 ntt1_content.CRE +| 3792: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 6f ATE TABLE 't1_co +| 3808: 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 45 ntent'(id INTEGE +| 3824: 52 20 50 52 49 4d 41 52 49 20 4b 45 59 2c 20 63 R PRIMARI KEY, c +| 3840: 30 29 69 03 07 17 19 19 01 81 2d 74 61 62 6c 65 0)i.......-table +| 3856: 74 31 5f 69 64 78 74 31 5f 69 64 78 03 43 52 45 t1_idxt1_idx.CRE +| 3872: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 69 64 ATE TABLE 't1_id +| 3888: 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 x'(segid, term, +| 3904: 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 pgno, PRIMARY KE +| 3920: 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 29 20 Y(segid, term)) +| 3936: 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 02 07 WITHOUT ROWIDU.. +| 3952: 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 64 61 ......tablet1_da +| 3968: 74 61 74 31 5f 64 61 74 61 02 43 52 45 41 54 45 tat1_data.CREATE +| 3984: 20 54 41 42 4c 45 20 27 74 31 5f 64 61 74 61 27 TABLE 't1_data' +| 4000: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM +| 4016: 41 52 b9 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 AR. KEY, block B +| 4032: 4c 4f 42 29 3a 01 06 17 11 11 08 63 74 61 62 6c LOB):......ctabl +| 4048: 65 74 31 74 31 43 52 45 41 54 45 20 56 49 52 54 et1t1CREATE VIRT +| 4064: 55 41 4c 20 54 41 42 4c 45 20 74 31 20 55 53 49 UAL TABLE t1 USI +| 4080: 4e 47 20 66 74 73 35 28 63 6f 6e 74 65 6e 74 29 NG fts5(content) +| page 2 offset 4096 +| 0: 0d 00 00 00 03 0f bd 00 0f e8 0f ef 0f bd f0 00 ................ +| 16: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 24 84 80 .............$.. +| 4032: 80 80 80 01 03 00 4e 00 10 00 1e 06 30 61 62 61 ......N.....0aba +| 4048: 63 6c 01 02 02 04 02 66 74 02 5f 02 04 04 6e 64 cl.....ft._...nd +| 4064: 6f 6e 02 02 02 04 0a 07 05 01 03 00 10 03 03 0f on.............. +| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 11 ...$............ +| page 3 offset 8192 +| 0: 0a 00 00 00 01 0f 00 01 00 00 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 03 0f e0 00 0f f6 0f ec 0f e0 00 00 ................ +| 4064: 0a 03 03 00 1b 61 62 61 6e 64 6f 6e 08 02 03 00 .....abandon.... +| 4080: 17 61 62 61 66 74 08 01 03 00 17 61 62 61 63 6b .abaft.....aback +| page 5 offset 16384 +| 0: 0d 00 00 00 03 0f ee 00 0f fa 0f 00 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 03 ................ +| 4080: 03 00 0e 01 04 02 03 00 0e 01 04 01 03 00 0e 01 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| page 7 offset 24576 +| 0: 0d 00 00 10 03 0f d6 00 0f 00 00 00 00 00 00 00 ................ +| 4048: 00 00 00 00 00 00 09 03 02 1b 72 65 62 75 69 6c ..........rebuil +| 4064: 64 11 02 02 2b 69 6e 74 65 67 72 69 74 79 2d 63 d...+integrity-c +| 4080: 68 65 63 6b 0a 01 02 1d 6f 70 74 69 6d 00 00 00 heck....optim... +| page 8 offset 28672 +| 0: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| end crash-42fa37b694d45a.db +}]} {} + +do_execsql_test 6.1 { + INSERT INTO t1(t1,rank) VALUES('secure-delete',1); +} +do_catchsql_test 6.2 { + UPDATE t1 SET content=randomblob(500) WHERE t1; +} {1 {constraint failed}} sqlite3_fts5_may_be_corrupt 0 finish_test diff --git a/ext/fts5/test/fts5faultG.test b/ext/fts5/test/fts5faultG.test index bdcc153ad..33a41dcb3 100644 --- a/ext/fts5/test/fts5faultG.test +++ b/ext/fts5/test/fts5faultG.test @@ -46,5 +46,31 @@ do_faultsim_test 1 -faults oom* -prep { faultsim_test_result {0 {}} } +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, content=, contentless_delete=1); + BEGIN; + INSERT INTO t1 VALUES('here''s some text'); + INSERT INTO t1 VALUES('useful stuff, text'); + INSERT INTO t1 VALUES('what would we do without text!'); + COMMIT; +} +faultsim_save_and_close +do_faultsim_test 2 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + BEGIN; + DELETE FROM t1 WHERE rowid=2; + } +} -body { + execsql { + INSERT INTO t1(t1) VALUES('optimize'); + } +} -test { + faultsim_integrity_check + faultsim_test_result {0 {}} +} + + finish_test diff --git a/ext/fts5/test/fts5prefix2.test b/ext/fts5/test/fts5prefix2.test index bf16e81a7..29744c86b 100644 --- a/ext/fts5/test/fts5prefix2.test +++ b/ext/fts5/test/fts5prefix2.test @@ -52,6 +52,36 @@ do_execsql_test 2.1 { SELECT * FROM t2('to*'); } {top to tommy} +#------------------------------------------------------------------------- + +foreach {tn newrowid} { + 1 122 + 2 123 + 3 124 +} { + reset_db + do_execsql_test 3.$tn.0 { + CREATE VIRTUAL TABLE t12 USING fts5(x); + INSERT INTO t12(rowid, x) VALUES(123, 'wwww'); + } + do_execsql_test 3.$tn.1 { + BEGIN; + DELETE FROM t12 WHERE rowid=123; + SELECT * FROM t12('wwww*'); + INSERT INTO t12(rowid, x) VALUES($newrowid, 'wwww'); + SELECT * FROM t12('wwww*'); + END; + } {wwww} + do_execsql_test 3.$tn.2 { + INSERT INTO t12(t12) VALUES('integrity-check'); + } + do_execsql_test 3.$tn.3 { + SELECT rowid FROM t12('wwww*'); + } $newrowid +} + +finish_test + finish_test diff --git a/ext/fts5/test/fts5secure8.test b/ext/fts5/test/fts5secure8.test new file mode 100644 index 000000000..8ceb9630a --- /dev/null +++ b/ext/fts5/test/fts5secure8.test @@ -0,0 +1,51 @@ +# 2023 Nov 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +ifcapable !fts5 { finish_test ; return } +set ::testprefix fts5secure8 + +proc sql_repeat {txt n} { + string repeat $txt $n +} +db func repeat sql_repeat + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(x); + + INSERT INTO ft(ft, rank) VALUES('pgsz', 64); + + INSERT INTO ft(rowid, x) VALUES(100, 'hello world'); + INSERT INTO ft(rowid, x) VALUES(200, 'one day'); + + BEGIN; + INSERT INTO ft(rowid, x) VALUES(45, 'one two three'); + UPDATE ft SET x = repeat('hello world ', 500) WHERE rowid=100; + COMMIT +} + +do_execsql_test 1.1 { + INSERT INTO ft(ft, rank) VALUES('secure-delete', 1); + DELETE FROM ft WHERE rowid=100; +} + +do_execsql_test 1.2 { + PRAGMA integrity_check; +} {ok} + + + + + +finish_test + + diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 18d27bdf0..300307e5e 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -877,7 +877,7 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_STMTSTATUS_FILTER_HIT); DefInt(SQLITE_STMTSTATUS_MEMUSED); } _DefGroup; - + DefGroup(syncFlags) { DefInt(SQLITE_SYNC_NORMAL); DefInt(SQLITE_SYNC_FULL); @@ -901,6 +901,8 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_DETERMINISTIC); DefInt(SQLITE_DIRECTONLY); DefInt(SQLITE_INNOCUOUS); + DefInt(SQLITE_SUBTYPE); + DefInt(SQLITE_RESULT_SUBTYPE); } _DefGroup; DefGroup(version) { diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index bad599673..1f88b713a 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -247,9 +247,8 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi globalThis.sqlite3Worker1Promiser.defaultConfig = { worker: function(){ //#if target=es6-bundler-friendly - return new Worker("sqlite3-worker1-bundler-friendly.mjs",{ - type: 'module' /* Noting that neither Firefox nor Safari suppor this, - as of this writing. */ + return new Worker(new URL("sqlite3-worker1-bundler-friendly.mjs", import.meta.url),{ + type: 'module' }); //#else let theJs = "sqlite3-worker1.js"; @@ -267,8 +266,12 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = { } return new Worker(theJs + globalThis.location.search); //#endif - }.bind({ + } +//#ifnot target=es6-bundler-friendly + .bind({ currentScript: globalThis?.document?.currentScript - }), + }) +//#endif + , onerror: (...args)=>console.error('worker1 promiser error',...args) }; diff --git a/manifest b/manifest index 603321be0..1041b8b17 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.44.0 -D 2023-11-01T11:23:50.173 +C Version\s3.44.2 +D 2023-11-24T11:41:44.921 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 8b59912fc1538f96a08555605c5886cdcc733696ae7f22e374b2a4752196ca20 +F Makefile.in bddd493839ce057414b19cd405293363f7efb7a73a3d8f6d2ed1b59943ddfcb1 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 -F Makefile.msc f0cf219350d9af4fba411b4f6306dce2adc897484e8f446de1fb4f40de674d00 +F Makefile.msc 59bb36dba001f0b38212be0794fb838f25371008b180929bcf08aa799694c168 F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8 -F VERSION 4c09b629c03b8ae32317cb336a32f3aa3252841d6dcd51184cecc4278d08f21e +F VERSION 672a5ddf487d19856d8b1015797e14578fd8f8d59c14d9832ab0fe5938c6ab9b F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -22,7 +22,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac 1ee185f2f8c2e9bed69d0113222724202f962d04eafd57197e65c662d2d9b180 +F autoconf/tea/configure.ac 0c358d924fe9cd5c624b9038e025bca35b572a15501014c69afe38b8ae4d7371 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 1d9cbcb416cb5387b3f750ae6db63cfedb703a2e46bf8ca61daecff31d208252 x +F configure a56c8f24e118eff90e3870411bfc844e86c00577faf092ded131120a637ad2da x F configure.ac de31fea7d975bb7ebafbe0e2190a855cc80d48558bf0c9a6578a1836daf1cd3a F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -50,6 +50,8 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91 F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad1aff3294f94 F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a +F ext/consio/console_io.c e048bd69f3a357a4b6b5da01c504728631d944ad5735ad83c44800c80a91e978 x +F ext/consio/console_io.h d18bcc50f4e4d42b65be8fecba72918364ae77182c86360aca842e44b823384e F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 F ext/expert/expert1.test 0dd5cb096d66bed593e33053a3b364f6ef52ed72064bf5cf298364636dbf3cd6 @@ -93,8 +95,8 @@ F ext/fts5/fts5_aux.c 35c4101613eff86902877a4dedd9400b07922e412cbdd637b45041dce2 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 -F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d -F ext/fts5/fts5_index.c 730c9c32ada18ce1eb7ff847b36507f4b005d88d47af7b47db521e695a8ea4c7 +F ext/fts5/fts5_hash.c 076058f93327051952a752dc765df1acfe783eb11b419b30652aa1fc1f987902 +F ext/fts5/fts5_index.c 458cbed8a3e17617cbf7e80cdfb7612000b9bb3781f286b345fb9655858658cf F ext/fts5/fts5_main.c a07ed863b8bd9e6fefb62db2fd40a3518eb30a5f7dcfda5be915dd2db45efa2f F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca56d6601426d F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae @@ -143,7 +145,7 @@ F ext/fts5/test/fts5corrupt.test b6d4034b682bb3387bc44c510c71b3c67d4349e4df13949 F ext/fts5/test/fts5corrupt2.test 99e7e23a58b4d89eb7167c6de1669cbc595cd3c79ab333e0eb56405473319e77 F ext/fts5/test/fts5corrupt3.test 7da9895dafa404efd20728f66ff4b94399788bdc042c36fe2689801bba2ccd78 F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3 -F ext/fts5/test/fts5corrupt5.test eb6ba5ca28ef7c4c6b01e850d388cdb3dacc8c4c2f383f79d0a98128257742b4 +F ext/fts5/test/fts5corrupt5.test 38a238df26c4de471e1c4b98f8de6c902bc692577a1c08d0ff4f2251f3559644 F ext/fts5/test/fts5corrupt6.test bf8eeae07825b088b9665d9d8e4accbd8dc9bf3cb85b6c64cf6c9e18ccc420a4 F ext/fts5/test/fts5corrupt7.test 80ad7f683a8bda2404731bb77e8c3dbbb620c1f6cc583cca8239f6accd6338c0 F ext/fts5/test/fts5delete.test 619295b20dbc1d840b403ee07c878f52378849c3c02e44f2ee143b3e978a0aa7 @@ -167,7 +169,7 @@ F ext/fts5/test/fts5faultB.test d606bdb8e81aaeb6f41de3fc9fc7ae315733f0903fbff05c F ext/fts5/test/fts5faultD.test e7ed7895abfe6bc98a5e853826f6b74956e7ba7f594f1860bbf9e504b9647996 F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e4710c77eb8ce7075 F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1 -F ext/fts5/test/fts5faultG.test 340e59d2c2c1c7c379224f3968ee8d09b0f64bf56c5194217d1ded887b9d47c4 +F ext/fts5/test/fts5faultG.test d2e5a4d9a34e08dcaadcaeafef74d10cbc2abdd11aa2659a18af0294bf2812d3 F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b996159f6909dc8079 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e @@ -193,7 +195,7 @@ F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd1 F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15 F ext/fts5/test/fts5porter2.test 0d251a673f02fa13ca7f011654873b3add20745f7402f108600a23e52d8c7457 F ext/fts5/test/fts5prefix.test a0fa67b06650f2deaa7bf27745899d94e0fb547ad9ecbd08bfad98c04912c056 -F ext/fts5/test/fts5prefix2.test 3847ce46f70b82d61c6095103a9d7c53f2952c40a4704157bc079c04d9c8b18b +F ext/fts5/test/fts5prefix2.test ad751d4a5b029726ee908a7664e27d27bde7584218b8d7944c2a323afd381432 F ext/fts5/test/fts5query.test ac363b17a442620bb0780e93c24f16a5f963dfe2f23dc85647b869efcfada728 F ext/fts5/test/fts5rank.test 30f29e278cd7fb8831ba4f082feb74d8eb90c463bf07113ae200afc2b467ef32 F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde40a46fc5d785b451e7 @@ -207,6 +209,7 @@ F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b98 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a F ext/fts5/test/fts5secure6.test 74bf04733cc523bccca519bb03d3b4e2ed6f6e3db7c59bf6be82c88a0ac857fd F ext/fts5/test/fts5secure7.test fd03d0868d64340a1db8615b02e5508fea409de13910114e4f19eaefc120777a +F ext/fts5/test/fts5secure8.test eb3579e9d58b0acad97e8082dee1f99b2d393198f03500b453c2b25761c0c298 F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b F ext/fts5/test/fts5simple2.test 258a1b0c590409bfa5271e872c79572b319d2a56554d0585f68f146a0da603f0 @@ -588,8 +591,8 @@ F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 595953994aa3ae2287c889c4da39ab3d6f17b6461ecf4bec334b7a3faafddb02 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 46c4afa6c50d7369252c104f274ad977a97e91ccfafc38b400fe36e90bdda88e -F ext/wasm/api/sqlite3-wasm.c 038de1b6d40b2cc0f41a143a0451db60b2a6f1b5bc06de67da255c54ea1661b7 -F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f +F ext/wasm/api/sqlite3-wasm.c d0e09eb5ed3743c00294e30019e591c3aa150572ae7ffe8a8994568a7377589f +F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 569d4e859968e65f55dec5fac0b879828a23c8b179162cc7812fec19f844dd21 F ext/wasm/api/sqlite3-worker1.c-pp.js a541112aa51e16705f13a99bb943c64efe178aa28c86704a955f8fd9afe4ba37 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e @@ -671,7 +674,7 @@ F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574 F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 -F src/expr.c 433f12e1237524482b0b2681c07da3cd54ddada2a625237cecde419f3e3a2553 +F src/expr.c cacf230b124891ea830d80395ea2119c2d7ee52c134a010f3950de9391ab79d1 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 F src/func.c 472f6dcfa39cf54f89a6aec76c79c225fb880a6c14469c15d361331662b9bf43 @@ -681,10 +684,10 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c d69c6e28ff7b602877bda68cd20583b8487c059759aa4d154dd21b3fd99c6238 +F src/json.c b2045aafc52f0475dc5d723d11cc0aaa9e1d233a28031b08c8174a3e407ed50c F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 -F src/main.c e1bc8864834697503d370d94613be945d05ca1c5ebdda43e7d5c8ee8c48d433c +F src/main.c 1b89f3de98d1b59fec5bac1d66d6ece21f703821b8eaa0d53d9604c35309f6f9 F src/malloc.c f016922435dc7d1f1f5083a03338a3e91f8c67ce2c5bdcfa4cdef62e612f5fcc F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -698,7 +701,7 @@ F src/mutex.c 1b4c7e5e3621b510e0c18397210be27cd54c8084141144fbbafd003fde948e88 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 F src/mutex_unix.c f7ee5a2061a4c11815a2bf4fc0e2bfa6fb8d9dc89390eb613ca0cec32fc9a3d1 -F src/mutex_w32.c 38b56d0bc8d54c17c20cbaaad3719b0c36b92fd07a7e34360d0c6a18d5589912 +F src/mutex_w32.c 28f8d480387db5b2ef5248705dd4e19db0cfc12c3ba426695a7d2c45c48e6885 F src/notify.c 57c2d1a2805d6dee32acd5d250d928ab94e02d76369ae057dee7d445fd64e878 F src/os.c 509452169d5ea739723e213b8e2481cf0e587f0e88579a912d200db5269f5f6d F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 @@ -708,7 +711,7 @@ F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d87210 F src/os_unix.c cb116fde9e3ca3c1bbfdf89d6928f776a2a34da168e2667426523a4db353b271 F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 699aab8dfc88056d796b03b40c0ab979040d58dfc3ae9db207f1be91e4880bbf +F src/pager.c 987ab3a2cd9065d62e9955474470ff733445e2357432a67e3d0f5a8f9313e334 F src/pager.h f4d33fec8052603758792045493423b8871a996da2d0973927b7d36cd6070473 F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 @@ -721,12 +724,12 @@ F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 31229276a8eb5b5de1428cd2d80f6f1cf8ffc5248be25e47cf575df12f1b8f23 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c a19daa26e95f7245106a31f288b2f50c72d1f2cc156703f04c8c91450e111515 -F src/shell.c.in 3e9371ca6a93294931a8ed8b098bc3cb15d57567aa9a1f2ade72db7f5c572795 -F src/sqlite.h.in ef0e41e83ad1ac0dcc9ec9939bf541a44b1c5de821bee2d6c61754c3252f3276 +F src/select.c 85857bedd2913d888aa571755b48c54cd2e6e7fcb0087e19b226ee0368cfda1e +F src/shell.c.in cbe850737d4166347bfe5fc7d695229379347509bf114cf6abeb1e384f2d7a70 +F src/sqlite.h.in 4f841d3d117b830ee5ee45e8d89ceff1195f3ebb72d041ace8d116ba4c103b35 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 567e317f8631883897b7d3da43fce778b7c30dd0dd7f714558c9725fc1c1196c +F src/sqliteInt.h 08efc0ad9ddbbc5ea859111521dff2c4cecc589c0c9a3e4840c927663b31445b F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -791,13 +794,13 @@ F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c b22cc9f203a8c0b9ee5338a67f8860347d14845864c10248bebe84518a781677 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 -F src/vdbe.c 14479441337135eed8e290fb1d4abb7db657d93217a3b1ea8a2f031d3895536a +F src/vdbe.c 1cfb61c58193dcb74e8e0f90dc1405ff7fe39f43a1e9bdd38329e174f2102ed4 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c -F src/vdbeapi.c fe654b1f54e1feebcaed6c2ae3ed035cc65bfeb9a1169bed866abc42bfc63ff6 -F src/vdbeaux.c dffcf79e7e415fcd6e4c8ac1ec7124cae5257018443adf09551c807655b04993 +F src/vdbeapi.c dd812ddd2f7806519e9d492c820456f86628bb6014b559034506e7f9d7f683c5 +F src/vdbeaux.c 16f27624831aa6663fe5927b647a0a54dfda0088da9b170ebc14bce98c5ac414 F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5 -F src/vdbemem.c c936e9002af4993b84c4eb7133d6b1190efe46d391cc86117ecd67ba17b1a04b +F src/vdbemem.c 0012d5f01cc866833847c2f3ae4c318ac53a1cb3d28acad9c35e688039464cf0 F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c 2143db7db0ceed69b21422581f434baffc507a08d831565193a7a02882a1b6a7 @@ -806,11 +809,11 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c 313ce81270d2a414672370e1ee74e65949ad620519193d4cac2986d073cbc8a0 +F src/where.c 45b2239e127beaaae2367e503ca4c82868d8fef707c7f7f4a6c0528e7d5f65ff F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 -F src/window.c ad21e2b73ec75acc79dde2576c573f54a338b0c49e9de847ce984f9b9595b5e2 +F src/window.c 5b1387d59df30d481ed14cceef5f4d1dab1f8752aa106ba72c8b62777bd139d2 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 @@ -820,7 +823,7 @@ F test/aggnested.test 2e738bfe2980df301a782f6e7bbf9459266f64f7e72f58f3b5c843bf89 F test/aggorderby.test e6b98dbbf3ababa96892435d387de2dcf602ef02c2b848d2d817473066f154ba F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 -F test/alter.test 403a7f8842457044a994d0ffb42963d6e84fcfbf5e8f54556063b25d966cd454 +F test/alter.test 3c00eff1e2036b9f93e9cd0f3d3e63750ac87ecb5bc71b9d7bd07cbf2ac4c494 F test/alter2.test a966ccfcddf9ce0a4e0e6ff1aca9e6e7948e0e242cd7e43fc091948521807687 F test/alter3.test ffc4ab29ce78a3517a66afd69b2730667e3471622509c283b2bd4c46f680fba3 F test/alter4.test 716caa071dd8a3c6d57225778d15d3c3cbf5e34b2e84ae44199aeb2bbf50a707 @@ -1266,7 +1269,7 @@ F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a91 F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997 F test/indexA.test 11d84f6995e6e5b9d8315953fb1b6d29772ee7c7803ee9112715e7e4dd3e4974 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 -F test/indexexpr1.test 62558b1cfd7ccbe7bc015849cc6d1a13ef124e80cbd5b3a98dc66c3c9cce0cf4 +F test/indexexpr1.test 833f511213a5e26549186813f0566bd72f978177a7e6e98a2d2dd695de3c670d F test/indexexpr2.test 1c382e81ef996d8ae8b834a74f2a9013dddf59214c32201d7c8a656d739f999a F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 @@ -1306,7 +1309,7 @@ F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f2 F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28 F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 -F test/joinH.test 84198ea42bf78b79fe399c0567218cd6df36c50c6dd27d9c4aab221acaad929e +F test/joinH.test f69e5b53b7d887914e854b6a131efbed4ea9f5ca52bdab81788bfc3e79299f43 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e @@ -1322,7 +1325,7 @@ F test/json102.test 4c69694773a470f1fda34e5f4ba24920b35184fb66050b450fc2ef9ab5ad F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d -F test/json501.test f71710f60fa45b19dc336fbaac9e8362f70f80cf81badefdb845ed3f7c7c2ccc +F test/json501.test c419deb835b70c1a2c8532936927bcc1146730328edd2052276715bfd209724d F test/json502.test 98c38e3c4573841028a1381dfb81d4c3f9b105d39668167da10d055e503f6d0b F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff F test/kvtest.c 6e0228409ea7ca0497dad503fbd109badb5e59545d131014b6aaac68b56f484a @@ -1561,7 +1564,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 2191c892d8256b1b6cccb4fca894cfc2e748450cf422e6ec80a320a0d538b6df +F test/shell1.test c7127a5e780ffc9e14c476773127fdf292c6db226529c44c1676f37b3793123f F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f @@ -1966,7 +1969,7 @@ F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6 F test/whereD.test c1c335e914e28b122e000e9310f02d2be83e1c9dbca2e29f46bd732703944d1b F test/whereE.test 7a727b5d5b6bc8fa4cef5206e90cc0363e55ca7f0566f6fbad0206e43170f59e F test/whereF.test 926b65519608e3f2aa28720822b9154fb5c7b13519dd78194f434a511ab3dac5 -F test/whereG.test b2a479f425f7d0a432df7e842e8484560908ef703fe0fd407888ff85e7097238 +F test/whereG.test 649d5ad02a87a76ec2ac8de9441e2c83a4dd0f29e459a31215c0533788c6bf07 F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test c4bb7e2ca56d49bd8ab5c7bd085b8b83e353922b46904d68aefb3c7468643581 F test/whereJ.test fc05e374cc9f2dc204148d6c06822c380ad388895fe97a6d335b94a26a08aecf @@ -2059,13 +2062,13 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh cbdcf993fa83dccbef7fb77b39cdeb31ef9f77d9d88c9e343b58d35ca3898a6a F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl 372452e24267dfe1b496eec3992d10c6e5e7d7870a152560cdcfe5404bc8cc04 x +F tool/mkctimec.tcl a16682eae5f01f85e5861b2aa215ca0d46b4230658ee25977e02b4508566fb75 x F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef F tool/mkopcodeh.tcl 769d9e6a8b462323150dc13a8539d6064664b72974f7894befe2491cc73e05cd F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa -F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d +F tool/mkpragmatab.tcl 32e359ccb21011958a821955254bd7a5fa7915d01a8c16fed91ffc8b40cb4adf F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971fed0 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 @@ -2106,7 +2109,7 @@ F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaa F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f -F tool/srctree-check.tcl cef630bc4ff21a460d72479c43a42bf1c1ed61897659305c35c8d72e91bcb176 +F tool/srctree-check.tcl 9f1098d439159692ece45cc9e701b35df4956269399fe0c770e41f235d1ce5e6 F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37 F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d @@ -2139,11 +2142,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d1895dd8f5757a339f619f22b29c8a739398ded673bb9c93f1b8eb8a4b38f510 -R 06db090922d30265c1e1381238ea6702 -T +sym-major-release * +P f796da626b5c3b099cc246a8b46d92839922e34f398d673b0b03d7ee33a28a60 +R 1ffc537351df4c25512f7c1fd5cd9511 T +sym-release * -T +sym-version-3.44.0 * +T +sym-version-3.44.2 * U drh -Z 14f36413d8197ea0c35026cb3842c9f7 +Z abd52f0184ebdbba1837e841b542823e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c618d9af7..972590b28 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301 +ebead0e7230cd33bcec9f95d2183069565b9e709bf745c9b5db65cc0cbf92c0f diff --git a/src/expr.c b/src/expr.c index 0a975ae2e..34d78741d 100644 --- a/src/expr.c +++ b/src/expr.c @@ -6043,8 +6043,8 @@ int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){ */ int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){ return sqlite3ExprCompare(0, - sqlite3ExprSkipCollateAndLikely(pA), - sqlite3ExprSkipCollateAndLikely(pB), + sqlite3ExprSkipCollate(pA), + sqlite3ExprSkipCollate(pB), iTab); } diff --git a/src/json.c b/src/json.c index c2129a026..de7143bb1 100644 --- a/src/json.c +++ b/src/json.c @@ -432,13 +432,19 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ zIn++; N -= 2; while( N>0 ){ - for(i=0; i0 ){ jsonAppendRawNZ(p, zIn, i); zIn += i; N -= i; if( N==0 ) break; } + if( zIn[0]=='"' ){ + jsonAppendRawNZ(p, "\\\"", 2); + zIn++; + N--; + continue; + } assert( zIn[0]=='\\' ); switch( (u8)zIn[1] ){ case '\'': @@ -833,7 +839,8 @@ static void jsonReturnJson( JsonParse *pParse, /* The complete JSON */ JsonNode *pNode, /* Node to return */ sqlite3_context *pCtx, /* Return value for this function */ - int bGenerateAlt /* Also store the rendered text in zAlt */ + int bGenerateAlt, /* Also store the rendered text in zAlt */ + int omitSubtype /* Do not call sqlite3_result_subtype() */ ){ JsonString s; if( pParse->oom ){ @@ -848,7 +855,7 @@ static void jsonReturnJson( pParse->nAlt = s.nUsed; } jsonResult(&s); - sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + if( !omitSubtype ) sqlite3_result_subtype(pCtx, JSON_SUBTYPE); } } @@ -889,7 +896,8 @@ static u32 jsonHexToInt4(const char *z){ static void jsonReturn( JsonParse *pParse, /* Complete JSON parse tree */ JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx /* Return value for this function */ + sqlite3_context *pCtx, /* Return value for this function */ + int omitSubtype /* Do not call sqlite3_result_subtype() */ ){ switch( pNode->eType ){ default: { @@ -1035,7 +1043,7 @@ static void jsonReturn( } case JSON_ARRAY: case JSON_OBJECT: { - jsonReturnJson(pParse, pNode, pCtx, 0); + jsonReturnJson(pParse, pNode, pCtx, 0, omitSubtype); break; } } @@ -2387,7 +2395,7 @@ static void jsonParseFunc( printf("iSubst = %u\n", p->iSubst); printf("iHold = %u\n", p->iHold); jsonDebugPrintNodeEntries(p->aNode, p->nNode); - jsonReturnJson(p, p->aNode, ctx, 1); + jsonReturnJson(p, p->aNode, ctx, 1, 0); } /* @@ -2573,15 +2581,14 @@ static void jsonExtractFunc( } if( pNode ){ if( flags & JSON_JSON ){ - jsonReturnJson(p, pNode, ctx, 0); + jsonReturnJson(p, pNode, ctx, 0, 0); }else{ - jsonReturn(p, pNode, ctx); - sqlite3_result_subtype(ctx, 0); + jsonReturn(p, pNode, ctx, 1); } } }else{ pNode = jsonLookup(p, zPath, 0, ctx); - if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx); + if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx, 0); } }else{ /* Two or more PATH arguments results in a JSON array with each @@ -2707,7 +2714,7 @@ static void jsonPatchFunc( if( pResult && pX->oom==0 ){ jsonDebugPrintParse(pX); jsonDebugPrintNode(pResult); - jsonReturnJson(pX, pResult, ctx, 0); + jsonReturnJson(pX, pResult, ctx, 0, 0); }else{ sqlite3_result_error_nomem(ctx); } @@ -2786,7 +2793,7 @@ static void jsonRemoveFunc( } } if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); } remove_done: jsonDebugPrintParse(p); @@ -2915,7 +2922,7 @@ static void jsonReplaceFunc( jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); replace_err: jsonDebugPrintParse(pParse); jsonParseFree(pParse); @@ -2969,7 +2976,7 @@ static void jsonSetFunc( } } jsonDebugPrintParse(pParse); - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); jsonSetDone: jsonParseFree(pParse); } @@ -3484,7 +3491,7 @@ static int jsonEachColumn( case JEACH_KEY: { if( p->i==0 ) break; if( p->eType==JSON_OBJECT ){ - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); }else if( p->eType==JSON_ARRAY ){ u32 iKey; if( p->bRecursive ){ @@ -3500,7 +3507,7 @@ static int jsonEachColumn( } case JEACH_VALUE: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); break; } case JEACH_TYPE: { @@ -3511,7 +3518,7 @@ static int jsonEachColumn( case JEACH_ATOM: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; if( pThis->eType>=JSON_ARRAY ) break; - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); break; } case JEACH_ID: { @@ -3804,34 +3811,43 @@ static sqlite3_module jsonTreeModule = { void sqlite3RegisterJsonFunctions(void){ #ifndef SQLITE_OMIT_JSON static FuncDef aJsonFunc[] = { - JFUNCTION(json, 1, 0, jsonRemoveFunc), - JFUNCTION(json_array, -1, 0, jsonArrayFunc), - JFUNCTION(json_array_length, 1, 0, jsonArrayLengthFunc), - JFUNCTION(json_array_length, 2, 0, jsonArrayLengthFunc), - JFUNCTION(json_error_position,1, 0, jsonErrorFunc), - JFUNCTION(json_extract, -1, 0, jsonExtractFunc), - JFUNCTION(->, 2, JSON_JSON, jsonExtractFunc), - JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc), - JFUNCTION(json_insert, -1, 0, jsonSetFunc), - JFUNCTION(json_object, -1, 0, jsonObjectFunc), - JFUNCTION(json_patch, 2, 0, jsonPatchFunc), - JFUNCTION(json_quote, 1, 0, jsonQuoteFunc), - JFUNCTION(json_remove, -1, 0, jsonRemoveFunc), - JFUNCTION(json_replace, -1, 0, jsonReplaceFunc), - JFUNCTION(json_set, -1, JSON_ISSET, jsonSetFunc), - JFUNCTION(json_type, 1, 0, jsonTypeFunc), - JFUNCTION(json_type, 2, 0, jsonTypeFunc), - JFUNCTION(json_valid, 1, 0, jsonValidFunc), -#if SQLITE_DEBUG - JFUNCTION(json_parse, 1, 0, jsonParseFunc), - JFUNCTION(json_test1, 1, 0, jsonTest1Func), + /* calls sqlite3_result_subtype() */ + /* | */ + /* Uses cache ______ | __ calls sqlite3_value_subtype() */ + /* | | | */ + /* Num args _________ | | | ___ Flags */ + /* | | | | | */ + /* | | | | | */ + JFUNCTION(json, 1, 1, 1, 0, 0, jsonRemoveFunc), + JFUNCTION(json_array, -1, 0, 1, 1, 0, jsonArrayFunc), + JFUNCTION(json_array_length, 1, 1, 0, 0, 0, jsonArrayLengthFunc), + JFUNCTION(json_array_length, 2, 1, 0, 0, 0, jsonArrayLengthFunc), + JFUNCTION(json_error_position,1, 1, 0, 0, 0, jsonErrorFunc), + JFUNCTION(json_extract, -1, 1, 1, 0, 0, jsonExtractFunc), + JFUNCTION(->, 2, 1, 1, 0, JSON_JSON, jsonExtractFunc), + JFUNCTION(->>, 2, 1, 0, 0, JSON_SQL, jsonExtractFunc), + JFUNCTION(json_insert, -1, 1, 1, 1, 0, jsonSetFunc), + JFUNCTION(json_object, -1, 0, 1, 1, 0, jsonObjectFunc), + JFUNCTION(json_patch, 2, 1, 1, 0, 0, jsonPatchFunc), + JFUNCTION(json_quote, 1, 0, 1, 1, 0, jsonQuoteFunc), + JFUNCTION(json_remove, -1, 1, 1, 0, 0, jsonRemoveFunc), + JFUNCTION(json_replace, -1, 1, 1, 1, 0, jsonReplaceFunc), + JFUNCTION(json_set, -1, 1, 1, 1, JSON_ISSET, jsonSetFunc), + JFUNCTION(json_type, 1, 1, 0, 0, 0, jsonTypeFunc), + JFUNCTION(json_type, 2, 1, 0, 0, 0, jsonTypeFunc), + JFUNCTION(json_valid, 1, 1, 0, 0, 0, jsonValidFunc), +#ifdef SQLITE_DEBUG + JFUNCTION(json_parse, 1, 1, 1, 0, 0, jsonParseFunc), + JFUNCTION(json_test1, 1, 1, 0, 1, 0, jsonTest1Func), #endif WAGGREGATE(json_group_array, 1, 0, 0, jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC), WAGGREGATE(json_group_object, 2, 0, 0, jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC) + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC) }; sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); #endif diff --git a/src/main.c b/src/main.c index fbe00f5fa..6acfdc325 100644 --- a/src/main.c +++ b/src/main.c @@ -1914,7 +1914,7 @@ int sqlite3CreateFunc( assert( SQLITE_FUNC_CONSTANT==SQLITE_DETERMINISTIC ); assert( SQLITE_FUNC_DIRECT==SQLITE_DIRECTONLY ); extraFlags = enc & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY| - SQLITE_SUBTYPE|SQLITE_INNOCUOUS); + SQLITE_SUBTYPE|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE); enc &= (SQLITE_FUNC_ENCMASK|SQLITE_ANY); /* The SQLITE_INNOCUOUS flag is the same bit as SQLITE_FUNC_UNSAFE. But diff --git a/src/mutex_w32.c b/src/mutex_w32.c index e0e0dfb06..7eb5b50be 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -87,7 +87,7 @@ void sqlite3MemoryBarrier(void){ SQLITE_MEMORY_BARRIER; #elif defined(__GNUC__) __sync_synchronize(); -#elif MSVC_VERSION>=1300 +#elif MSVC_VERSION>=1400 _ReadWriteBarrier(); #elif defined(MemoryBarrier) MemoryBarrier(); diff --git a/src/pager.c b/src/pager.c index e54750424..4687ab0f1 100644 --- a/src/pager.c +++ b/src/pager.c @@ -5062,10 +5062,13 @@ int sqlite3PagerOpen( */ sqlite3_file *sqlite3_database_file_object(const char *zName){ Pager *pPager; + const char *p; while( zName[-1]!=0 || zName[-2]!=0 || zName[-3]!=0 || zName[-4]!=0 ){ zName--; } - pPager = *(Pager**)(zName - 4 - sizeof(Pager*)); + p = zName - 4 - sizeof(Pager*); + assert( EIGHT_BYTE_ALIGNMENT(p) ); + pPager = *(Pager**)p; return pPager->fd; } diff --git a/src/select.c b/src/select.c index 7a79385e0..e5312c7ee 100644 --- a/src/select.c +++ b/src/select.c @@ -2319,7 +2319,8 @@ void sqlite3SubqueryColumnTypes( NameContext sNC; assert( pSelect!=0 ); - assert( (pSelect->selFlags & SF_Resolved)!=0 ); + testcase( (pSelect->selFlags & SF_Resolved)==0 ); + assert( (pSelect->selFlags & SF_Resolved)!=0 || IN_RENAME_OBJECT ); assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 ); assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); if( db->mallocFailed || IN_RENAME_OBJECT ) return; @@ -6372,10 +6373,11 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ SrcList *pTabList; SrcItem *pFrom; - assert( p->selFlags & SF_Resolved ); if( p->selFlags & SF_HasTypeInfo ) return; p->selFlags |= SF_HasTypeInfo; pParse = pWalker->pParse; + testcase( (p->selFlags & SF_Resolved)==0 ); + assert( (p->selFlags & SF_Resolved) || IN_RENAME_OBJECT ); pTabList = p->pSrc; for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; @@ -7397,6 +7399,7 @@ int sqlite3Select( TREETRACE(0x1000,pParse,p, ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + unsetJoinExpr(p->pWhere, pItem->iCursor, 0); } } if( pItem->fg.jointype & JT_LTORJ ){ @@ -7411,17 +7414,15 @@ int sqlite3Select( TREETRACE(0x1000,pParse,p, ("RIGHT-JOIN simplifies to JOIN on term %d\n",j)); pI2->fg.jointype &= ~(JT_RIGHT|JT_OUTER); + unsetJoinExpr(p->pWhere, pI2->iCursor, 1); } } } - for(j=pTabList->nSrc-1; j>=i; j--){ + for(j=pTabList->nSrc-1; j>=0; j--){ pTabList->a[j].fg.jointype &= ~JT_LTORJ; if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; } } - assert( pItem->iCursor>=0 ); - unsetJoinExpr(p->pWhere, pItem->iCursor, - pTabList->a[0].fg.jointype & JT_LTORJ); } /* No further action if this term of the FROM clause is not a subquery */ diff --git a/src/shell.c.in b/src/shell.c.in index ad16d7204..d52c7c53a 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -234,29 +234,56 @@ typedef unsigned char u8; /* string conversion routines only needed on Win32 */ extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); -extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); -extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); #endif -/* On Windows, we normally run with output mode of TEXT so that \n characters -** are automatically translated into \r\n. However, this behavior needs -** to be disabled in some cases (ex: when generating CSV output and when -** rendering quoted strings that contain \n characters). The following -** routines take care of that. -*/ -#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT -static void setBinaryMode(FILE *file, int isOutput){ - if( isOutput ) fflush(file); - _setmode(_fileno(file), _O_BINARY); -} -static void setTextMode(FILE *file, int isOutput){ - if( isOutput ) fflush(file); - _setmode(_fileno(file), _O_TEXT); -} +/* Use console I/O package as a direct INCLUDE. */ +#define SQLITE_INTERNAL_LINKAGE static + +#ifdef SQLITE_SHELL_FIDDLE +/* Deselect most features from the console I/O package for Fiddle. */ +# define SQLITE_CIO_NO_REDIRECT +# define SQLITE_CIO_NO_CLASSIFY +# define SQLITE_CIO_NO_TRANSLATE +# define SQLITE_CIO_NO_SETMODE +#endif +INCLUDE ../ext/consio/console_io.h +INCLUDE ../ext/consio/console_io.c + +#ifndef SQLITE_SHELL_FIDDLE +/* From here onward, fgets() is redirected to the console_io library. */ +# define fgets(b,n,f) fGetsUtf8(b,n,f) +/* + * Define macros for emitting output text in various ways: + * sputz(s, z) => emit 0-terminated string z to given stream s + * sputf(s, f, ...) => emit varargs per format f to given stream s + * oputz(z) => emit 0-terminated string z to default stream + * oputf(f, ...) => emit varargs per format f to default stream + * eputz(z) => emit 0-terminated string z to error stream + * eputf(f, ...) => emit varargs per format f to error stream + * oputb(b, n) => emit char buffer b[0..n-1] to default stream + * + * Note that the default stream is whatever has been last set via: + * setOutputStream(FILE *pf) + * This is normally the stream that CLI normal output goes to. + * For the stand-alone CLI, it is stdout with no .output redirect. + */ +# define sputz(s,z) fPutsUtf8(z,s) +# define sputf fPrintfUtf8 +# define oputz(z) oPutsUtf8(z) +# define oputf oPrintfUtf8 +# define eputz(z) ePutsUtf8(z) +# define eputf ePrintfUtf8 +# define oputb(buf,na) oPutbUtf8(buf,na) #else -# define setBinaryMode(X,Y) -# define setTextMode(X,Y) +/* For Fiddle, all console handling and emit redirection is omitted. */ +# define sputz(fp,z) fputs(z,fp) +# define sputf(fp,fmt, ...) fprintf(fp,fmt,__VA_ARGS__) +# define oputz(z) fputs(z,stdout) +# define oputf(fmt, ...) printf(fmt,__VA_ARGS__) +# define eputz(z) fputs(z,stderr) +# define eputf(fmt, ...) fprintf(stderr,fmt,__VA_ARGS__) +# define oputb(buf,na) fwrite(buf,1,na,stdout) #endif /* True if the timer is enabled */ @@ -331,10 +358,10 @@ static void endTimer(void){ sqlite3_int64 iEnd = timeOfDay(); struct rusage sEnd; getrusage(RUSAGE_SELF, &sEnd); - printf("Run Time: real %.3f user %f sys %f\n", - (iEnd - iBegin)*0.001, - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + oputf("Run Time: real %.3f user %f sys %f\n", + (iEnd - iBegin)*0.001, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); } } @@ -410,10 +437,10 @@ static void endTimer(void){ FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; sqlite3_int64 ftWallEnd = timeOfDay(); getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); - printf("Run Time: real %.3f user %f sys %f\n", - (ftWallEnd - ftWallBegin)*0.001, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); + oputf("Run Time: real %.3f user %f sys %f\n", + (ftWallEnd - ftWallBegin)*0.001, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); } } @@ -450,28 +477,9 @@ static int bail_on_error = 0; static int stdin_is_interactive = 1; /* -** If build is for non-RT Windows, without 3rd-party line editing, -** console input and output may be done in a UTF-8 compatible way, -** if the OS is capable of it and the --no-utf8 option is not seen. -*/ -#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \ - && !defined(SHELL_OMIT_WIN_UTF8) && !SQLITE_OS_WINRT -# define SHELL_WIN_UTF8_OPT 1 -/* Record whether to do UTF-8 console I/O translation per stream. */ - static int console_utf8_in = 0; - static int console_utf8_out = 0; -/* Record whether can do UTF-8 or --no-utf8 seen in invocation. */ - static int mbcs_opted = 1; /* Assume cannot do until shown otherwise. */ -#else -# define console_utf8_in 0 -# define console_utf8_out 0 -# define SHELL_WIN_UTF8_OPT 0 -#endif - -/* -** On Windows systems we have to know if standard output is a console -** in order to translate UTF-8 into MBCS. The following variable is -** true if translation is required. +** On Windows systems we need to know if standard output is a console +** in order to show that UTF-16 translation is done in the sign-on +** banner. The following variable is true if it is the console. */ static int stdout_is_console = 1; @@ -593,251 +601,17 @@ static char *dynamicContinuePrompt(void){ shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4); dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel); } - shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4); + shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, + PROMPT_LEN_MAX-4); } } return dynPrompt.dynamicPrompt; } #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ -#if SHELL_WIN_UTF8_OPT -/* Following struct is used for UTF-8 console I/O. */ -static struct ConsoleState { - int stdinEof; /* EOF has been seen on console input */ - int infsMode; /* Input file stream mode upon shell start */ - UINT inCodePage; /* Input code page upon shell start */ - UINT outCodePage; /* Output code page upon shell start */ - HANDLE hConsole; /* Console input or output handle */ - DWORD consoleMode; /* Console mode upon shell start */ -} conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; - -#ifndef _O_U16TEXT /* For build environments lacking this constant: */ -# define _O_U16TEXT 0x20000 -#endif - -/* -** If given stream number is a console, return 1 and get some attributes, -** else return 0 and set the output attributes to invalid values. -*/ -static short console_attrs(unsigned stnum, HANDLE *pH, DWORD *pConsMode){ - static int stid[3] = { STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE }; - HANDLE h; - *pH = INVALID_HANDLE_VALUE; - *pConsMode = 0; - if( stnum > 2 ) return 0; - h = GetStdHandle(stid[stnum]); - if( h!=*pH && GetFileType(h)==FILE_TYPE_CHAR && GetConsoleMode(h,pConsMode) ){ - *pH = h; - return 1; - } - return 0; -} - -/* -** Perform a runtime test of Windows console to determine if it can -** do char-stream I/O correctly when the code page is set to CP_UTF8. -** Returns are: 1 => yes it can, 0 => no it cannot -** -** The console's output code page is momentarily set, then restored. -** So this should only be run when the process is given use of the -** console for either input or output. -*/ -static short ConsoleDoesUTF8(void){ - UINT ocp = GetConsoleOutputCP(); - const char TrialUtf8[] = { '\xC8', '\xAB' }; /* "ȫ" or 2 MBCS characters */ - WCHAR aReadBack[1] = { 0 }; /* Read back as 0x022B when decoded as UTF-8. */ - CONSOLE_SCREEN_BUFFER_INFO csbInfo = {0}; - /* Create an inactive screen buffer with which to do the experiment. */ - HANDLE hCSB = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE, 0, 0, - CONSOLE_TEXTMODE_BUFFER, NULL); - if( hCSB!=INVALID_HANDLE_VALUE ){ - COORD cpos = {0,0}; - DWORD rbc; - SetConsoleCursorPosition(hCSB, cpos); - SetConsoleOutputCP(CP_UTF8); - /* Write 2 chars which are a single character in UTF-8 but more in MBCS. */ - WriteConsoleA(hCSB, TrialUtf8, sizeof(TrialUtf8), NULL, NULL); - ReadConsoleOutputCharacterW(hCSB, &aReadBack[0], 1, cpos, &rbc); - GetConsoleScreenBufferInfo(hCSB, &csbInfo); - SetConsoleOutputCP(ocp); - CloseHandle(hCSB); - } - /* Return 1 if cursor advanced by 1 position, else 0. */ - return (short)(csbInfo.dwCursorPosition.X == 1 && aReadBack[0] == 0x022B); -} - -static short in_console = 0; -static short out_console = 0; - -/* -** Determine whether either normal I/O stream is the console, -** and whether it can do UTF-8 translation, setting globals -** in_console, out_console and mbcs_opted accordingly. -*/ -static void probe_console(void){ - HANDLE h; - DWORD cMode; - in_console = console_attrs(0, &h, &cMode); - out_console = console_attrs(1, &h, &cMode); - if( in_console || out_console ) mbcs_opted = !ConsoleDoesUTF8(); -} - -/* -** If console is used for normal I/O, absent a --no-utf8 option, -** prepare console for UTF-8 input (from either typing or suitable -** paste operations) and/or for UTF-8 output rendering. -** -** The console state upon entry is preserved, in conState, so that -** console_restore() can later restore the same console state. -** -** The globals console_utf8_in and console_utf8_out are set, for -** later use in selecting UTF-8 or MBCS console I/O translations. -** This routine depends upon globals set by probe_console(). -*/ -static void console_prepare_utf8(void){ - struct ConsoleState csWork = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; - - console_utf8_in = console_utf8_out = 0; - if( (!in_console && !out_console) || mbcs_opted ) return; - console_attrs((in_console)? 0 : 1, &conState.hConsole, &conState.consoleMode); - conState.inCodePage = GetConsoleCP(); - conState.outCodePage = GetConsoleOutputCP(); - if( in_console ){ - SetConsoleCP(CP_UTF8); - DWORD newConsoleMode = conState.consoleMode - | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; - SetConsoleMode(conState.hConsole, newConsoleMode); - conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); - console_utf8_in = 1; - } - if( out_console ){ - SetConsoleOutputCP(CP_UTF8); - console_utf8_out = 1; - } -} - -/* -** Undo the effects of console_prepare_utf8(), if any. -*/ -static void SQLITE_CDECL console_restore(void){ - if( (console_utf8_in||console_utf8_out) - && conState.hConsole!=INVALID_HANDLE_VALUE ){ - if( console_utf8_in ){ - SetConsoleCP(conState.inCodePage); - _setmode(_fileno(stdin), conState.infsMode); - } - if( console_utf8_out ) SetConsoleOutputCP(conState.outCodePage); - SetConsoleMode(conState.hConsole, conState.consoleMode); - /* Avoid multiple calls. */ - conState.hConsole = INVALID_HANDLE_VALUE; - conState.consoleMode = 0; - console_utf8_in = 0; - console_utf8_out = 0; - } -} - -/* -** Collect input like fgets(...) with special provisions for input -** from the Windows console to get around its strange coding issues. -** Defers to plain fgets() when input is not interactive or when the -** UTF-8 input is unavailable or opted out. -*/ -static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ - if( fin==0 ) fin = stdin; - if( fin==stdin && stdin_is_interactive && console_utf8_in ){ -# define SQLITE_IALIM 150 - wchar_t wbuf[SQLITE_IALIM]; - int lend = 0; - int noc = 0; - if( ncmax==0 || conState.stdinEof ) return 0; - buf[0] = 0; - while( noc SQLITE_IALIM*4+1 + noc) - ? SQLITE_IALIM : (ncmax-1 - noc)/4; -# undef SQLITE_IALIM - DWORD nbr = 0; - BOOL bRC = ReadConsoleW(conState.hConsole, wbuf, na, &nbr, 0); - if( !bRC || (noc==0 && nbr==0) ) return 0; - if( nbr > 0 ){ - int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, - wbuf,nbr,0,0,0,0); - if( nmb !=0 && noc+nmb <= ncmax ){ - int iseg = noc; - nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, - wbuf,nbr,buf+noc,nmb,0,0); - noc += nmb; - /* Fixup line-ends as coded by Windows for CR (or "Enter".)*/ - if( noc > 0 ){ - if( buf[noc-1]=='\n' ){ - lend = 1; - if( noc > 1 && buf[noc-2]=='\r' ){ - buf[noc-2] = '\n'; - --noc; - } - } - } - /* Check for ^Z (anywhere in line) too. */ - while( iseg < noc ){ - if( buf[iseg]==0x1a ){ - conState.stdinEof = 1; - noc = iseg; /* Chop ^Z and anything following. */ - break; - } - ++iseg; - } - }else break; /* Drop apparent garbage in. (Could assert.) */ - }else break; - } - /* If got nothing, (after ^Z chop), must be at end-of-file. */ - if( noc == 0 ) return 0; - buf[noc] = 0; - return buf; - }else{ - return fgets(buf, ncmax, fin); - } -} - -# define fgets(b,n,f) utf8_fgets(b,n,f) -#endif /* SHELL_WIN_UTF8_OPT */ - -/* -** Render output like fprintf(). Except, if the output is going to the -** console and if this is running on a Windows machine, and if UTF-8 -** output unavailable (or available but opted out), translate the -** output from UTF-8 into MBCS for output through 8-bit stdout stream. -** (Without -no-utf8, no translation is needed and must not be done.) -*/ -#if defined(_WIN32) || defined(WIN32) -void utf8_printf(FILE *out, const char *zFormat, ...){ - va_list ap; - va_start(ap, zFormat); - if( stdout_is_console && (out==stdout || out==stderr) && !console_utf8_out ){ - char *z1 = sqlite3_vmprintf(zFormat, ap); - char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); - sqlite3_free(z1); - fputs(z2, out); - sqlite3_free(z2); - }else{ - vfprintf(out, zFormat, ap); - } - va_end(ap); -} -#elif !defined(utf8_printf) -# define utf8_printf fprintf -#endif - -/* -** Render output like fprintf(). This should not be used on anything that -** includes string formatting (e.g. "%s"). -*/ -#if !defined(raw_printf) -# define raw_printf fprintf -#endif - /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ - raw_printf(stderr,"Error: out of memory\n"); + eputz("Error: out of memory\n"); exit(1); } @@ -869,18 +643,18 @@ static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ va_start(ap, zFormat); z = sqlite3_vmprintf(zFormat, ap); va_end(ap); - utf8_printf(iotrace, "%s", z); + sputf(iotrace, "%s", z); sqlite3_free(z); } #endif /* -** Output string zUtf to stream pOut as w characters. If w is negative, +** Output string zUtf to Out stream as w characters. If w is negative, ** then right-justify the text. W is the width in UTF-8 characters, not ** in bytes. This is different from the %*.*s specification in printf ** since with %*.*s the width is measured in bytes, not characters. */ -static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ +static void utf8_width_print(int w, const char *zUtf){ int i; int n; int aw = w<0 ? -w : w; @@ -895,11 +669,11 @@ static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ } } if( n>=aw ){ - utf8_printf(pOut, "%.*s", i, zUtf); + oputf("%.*s", i, zUtf); }else if( w<0 ){ - utf8_printf(pOut, "%*s%s", aw-n, "", zUtf); + oputf("%*s%s", aw-n, "", zUtf); }else{ - utf8_printf(pOut, "%s%*s", zUtf, aw-n, ""); + oputf("%s%*s", zUtf, aw-n, ""); } } @@ -959,7 +733,7 @@ static int strlenChar(const char *z){ ** Otherwise return 0. */ static FILE * openChrSource(const char *zFile){ -#ifdef _WIN32 +#if defined(_WIN32) || defined(WIN32) struct _stat x = {0}; # define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) /* On Windows, open first, then check the stream nature. This order @@ -1022,23 +796,6 @@ static char *local_getline(char *zLine, FILE *in){ break; } } -#if defined(_WIN32) || defined(WIN32) - /* For interactive input on Windows systems, with -no-utf8, - ** translate the multi-byte characterset characters into UTF-8. - ** This is the translation that predates console UTF-8 input. */ - if( stdin_is_interactive && in==stdin && !console_utf8_in ){ - char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); - if( zTrans ){ - i64 nTrans = strlen(zTrans)+1; - if( nTrans>nLine ){ - zLine = realloc(zLine, nTrans); - shell_check_oom(zLine); - } - memcpy(zLine, zTrans, nTrans); - sqlite3_free(zTrans); - } - } -#endif /* defined(_WIN32) || defined(WIN32) */ return zLine; } @@ -1065,7 +822,7 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ }else{ zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; #if SHELL_USE_LOCAL_GETLINE - printf("%s", zPrompt); + sputz(stdout, zPrompt); fflush(stdout); do{ zResult = local_getline(zPrior, stdin); @@ -1312,7 +1069,7 @@ static void shellDtostr( char z[400]; if( n<1 ) n = 1; if( n>350 ) n = 350; - snprintf(z, sizeof(z)-1, "%#+.*e", n, r); + sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); } @@ -1726,7 +1483,7 @@ static const char *modeDescr[] = { static void shellLog(void *pArg, int iErrCode, const char *zMsg){ ShellState *p = (ShellState*)pArg; if( p->pLog==0 ) return; - utf8_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg); + sputf(p->pLog, "(%d) %s\n", iErrCode, zMsg); fflush(p->pLog); } @@ -1741,9 +1498,9 @@ static void shellPutsFunc( int nVal, sqlite3_value **apVal ){ - ShellState *p = (ShellState*)sqlite3_user_data(pCtx); + /* Unused: (ShellState*)sqlite3_user_data(pCtx); */ (void)nVal; - utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0])); + oputf("%s\n", sqlite3_value_text(apVal[0])); sqlite3_result_value(pCtx, apVal[0]); } @@ -1762,8 +1519,7 @@ static void failIfSafeMode( va_start(ap, zErrMsg); zMsg = sqlite3_vmprintf(zErrMsg, ap); va_end(ap); - raw_printf(stderr, "line %d: ", p->lineno); - utf8_printf(stderr, "%s\n", zMsg); + eputf("line %d: %s\n", p->lineno, zMsg); exit(1); } } @@ -1931,7 +1687,7 @@ static void outputModePop(ShellState *p){ /* ** Output the given string as a hex-encoded blob (eg. X'1234' ) */ -static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ +static void output_hex_blob(const void *pBlob, int nBlob){ int i; unsigned char *aBlob = (unsigned char*)pBlob; @@ -1948,7 +1704,7 @@ static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ } zStr[i*2] = '\0'; - raw_printf(out,"X'%s'", zStr); + oputf("X'%s'", zStr); sqlite3_free(zStr); } @@ -1978,25 +1734,28 @@ static const char *unused_string( ** ** See also: output_quoted_escaped_string() */ -static void output_quoted_string(FILE *out, const char *z){ +static void output_quoted_string(const char *z){ int i; char c; - setBinaryMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + FILE *pfO = setOutputStream(invalidFileStream); + setBinaryMode(pfO, 1); +#endif if( z==0 ) return; for(i=0; (c = z[i])!=0 && c!='\''; i++){} if( c==0 ){ - utf8_printf(out,"'%s'",z); + oputf("'%s'",z); }else{ - raw_printf(out, "'"); + oputz("'"); while( *z ){ for(i=0; (c = z[i])!=0 && c!='\''; i++){} if( c=='\'' ) i++; if( i ){ - utf8_printf(out, "%.*s", i, z); + oputf("%.*s", i, z); z += i; } if( c=='\'' ){ - raw_printf(out, "'"); + oputz("'"); continue; } if( c==0 ){ @@ -2004,9 +1763,13 @@ static void output_quoted_string(FILE *out, const char *z){ } z++; } - raw_printf(out, "'"); + oputz("'"); } - setTextMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + setTextMode(pfO, 1); +#else + setTextMode(stdout, 1); +#endif } /* @@ -2018,13 +1781,16 @@ static void output_quoted_string(FILE *out, const char *z){ ** This is like output_quoted_string() but with the addition of the \r\n ** escape mechanism. */ -static void output_quoted_escaped_string(FILE *out, const char *z){ +static void output_quoted_escaped_string(const char *z){ int i; char c; - setBinaryMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + FILE *pfO = setOutputStream(invalidFileStream); + setBinaryMode(pfO, 1); +#endif for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){} if( c==0 ){ - utf8_printf(out,"'%s'",z); + oputf("'%s'",z); }else{ const char *zNL = 0; const char *zCR = 0; @@ -2036,23 +1802,23 @@ static void output_quoted_escaped_string(FILE *out, const char *z){ if( z[i]=='\r' ) nCR++; } if( nNL ){ - raw_printf(out, "replace("); + oputz("replace("); zNL = unused_string(z, "\\n", "\\012", zBuf1); } if( nCR ){ - raw_printf(out, "replace("); + oputz("replace("); zCR = unused_string(z, "\\r", "\\015", zBuf2); } - raw_printf(out, "'"); + oputz("'"); while( *z ){ for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){} if( c=='\'' ) i++; if( i ){ - utf8_printf(out, "%.*s", i, z); + oputf("%.*s", i, z); z += i; } if( c=='\'' ){ - raw_printf(out, "'"); + oputz("'"); continue; } if( c==0 ){ @@ -2060,93 +1826,139 @@ static void output_quoted_escaped_string(FILE *out, const char *z){ } z++; if( c=='\n' ){ - raw_printf(out, "%s", zNL); + oputz(zNL); continue; } - raw_printf(out, "%s", zCR); + oputz(zCR); } - raw_printf(out, "'"); + oputz("'"); if( nCR ){ - raw_printf(out, ",'%s',char(13))", zCR); + oputf(",'%s',char(13))", zCR); } if( nNL ){ - raw_printf(out, ",'%s',char(10))", zNL); + oputf(",'%s',char(10))", zNL); } } - setTextMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + setTextMode(pfO, 1); +#else + setTextMode(stdout, 1); +#endif } +/* +** Find earliest of chars within s specified in zAny. +** With ns == ~0, is like strpbrk(s,zAny) and s must be 0-terminated. +*/ +static const char *anyOfInStr(const char *s, const char *zAny, size_t ns){ + const char *pcFirst = 0; + if( ns == ~(size_t)0 ) ns = strlen(s); + while(*zAny){ + const char *pc = (const char*)memchr(s, *zAny&0xff, ns); + if( pc ){ + pcFirst = pc; + ns = pcFirst - s; + } + ++zAny; + } + return pcFirst; +} /* ** Output the given string as a quoted according to C or TCL quoting rules. */ -static void output_c_string(FILE *out, const char *z){ - unsigned int c; - fputc('"', out); - while( (c = *(z++))!=0 ){ - if( c=='\\' ){ - fputc(c, out); - fputc(c, out); - }else if( c=='"' ){ - fputc('\\', out); - fputc('"', out); - }else if( c=='\t' ){ - fputc('\\', out); - fputc('t', out); - }else if( c=='\n' ){ - fputc('\\', out); - fputc('n', out); - }else if( c=='\r' ){ - fputc('\\', out); - fputc('r', out); +static void output_c_string(const char *z){ + char c; + static const char *zq = "\""; + static long ctrlMask = ~0L; + static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */ + char ace[3] = "\\?"; + char cbsSay; + oputz(zq); + while( *z!=0 ){ + const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0); + const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask); + const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast; + if( pcEnd > z ) oputb(z, (int)(pcEnd-z)); + if( (c = *pcEnd)==0 ) break; + ++pcEnd; + switch( c ){ + case '\\': case '"': + cbsSay = (char)c; + break; + case '\t': cbsSay = 't'; break; + case '\n': cbsSay = 'n'; break; + case '\r': cbsSay = 'r'; break; + case '\f': cbsSay = 'f'; break; + default: cbsSay = 0; break; + } + if( cbsSay ){ + ace[1] = cbsSay; + oputz(ace); }else if( !isprint(c&0xff) ){ - raw_printf(out, "\\%03o", c&0xff); + oputf("\\%03o", c&0xff); }else{ - fputc(c, out); + ace[1] = (char)c; + oputz(ace+1); } + z = pcEnd; } - fputc('"', out); + oputz(zq); } /* ** Output the given string as a quoted according to JSON quoting rules. */ -static void output_json_string(FILE *out, const char *z, i64 n){ - unsigned int c; +static void output_json_string(const char *z, i64 n){ + char c; + static const char *zq = "\""; + static long ctrlMask = ~0L; + static const char *zDQBS = "\"\\"; + const char *pcLimit; + char ace[3] = "\\?"; + char cbsSay; + if( z==0 ) z = ""; - if( n<0 ) n = strlen(z); - fputc('"', out); - while( n-- ){ + pcLimit = z + ((n<0)? strlen(z) : (size_t)n); + oputz(zq); + while( z < pcLimit ){ + const char *pcDQBS = anyOfInStr(z, zDQBS, pcLimit-z); + const char *pcPast = zSkipValidUtf8(z, (int)(pcLimit-z), ctrlMask); + const char *pcEnd = (pcDQBS && pcDQBS < pcPast)? pcDQBS : pcPast; + if( pcEnd > z ){ + oputb(z, (int)(pcEnd-z)); + z = pcEnd; + } + if( z >= pcLimit ) break; c = *(z++); - if( c=='\\' || c=='"' ){ - fputc('\\', out); - fputc(c, out); + switch( c ){ + case '"': case '\\': + cbsSay = (char)c; + break; + case '\b': cbsSay = 'b'; break; + case '\f': cbsSay = 'f'; break; + case '\n': cbsSay = 'n'; break; + case '\r': cbsSay = 'r'; break; + case '\t': cbsSay = 't'; break; + default: cbsSay = 0; break; + } + if( cbsSay ){ + ace[1] = cbsSay; + oputz(ace); }else if( c<=0x1f ){ - fputc('\\', out); - if( c=='\b' ){ - fputc('b', out); - }else if( c=='\f' ){ - fputc('f', out); - }else if( c=='\n' ){ - fputc('n', out); - }else if( c=='\r' ){ - fputc('r', out); - }else if( c=='\t' ){ - fputc('t', out); - }else{ - raw_printf(out, "u%04x",c); - } + oputf("u%04x", c); }else{ - fputc(c, out); + ace[1] = (char)c; + oputz(ace+1); } } - fputc('"', out); + oputz(zq); } /* ** Output the given string with characters that are special to ** HTML escaped. */ -static void output_html_string(FILE *out, const char *z){ +static void output_html_string(const char *z){ int i; if( z==0 ) z = ""; while( *z ){ @@ -2158,18 +1970,18 @@ static void output_html_string(FILE *out, const char *z){ && z[i]!='\''; i++){} if( i>0 ){ - utf8_printf(out,"%.*s",i,z); + oputf("%.*s",i,z); } if( z[i]=='<' ){ - raw_printf(out,"<"); + oputz("<"); }else if( z[i]=='&' ){ - raw_printf(out,"&"); + oputz("&"); }else if( z[i]=='>' ){ - raw_printf(out,">"); + oputz(">"); }else if( z[i]=='\"' ){ - raw_printf(out,"""); + oputz("""); }else if( z[i]=='\'' ){ - raw_printf(out,"'"); + oputz("'"); }else{ break; } @@ -2207,9 +2019,8 @@ static const char needCsvQuote[] = { ** is only issued if bSep is true. */ static void output_csv(ShellState *p, const char *z, int bSep){ - FILE *out = p->out; if( z==0 ){ - utf8_printf(out,"%s",p->nullValue); + oputf("%s",p->nullValue); }else{ unsigned i; for(i=0; z[i]; i++){ @@ -2221,14 +2032,14 @@ static void output_csv(ShellState *p, const char *z, int bSep){ if( i==0 || strstr(z, p->colSeparator)!=0 ){ char *zQuoted = sqlite3_mprintf("\"%w\"", z); shell_check_oom(zQuoted); - utf8_printf(out, "%s", zQuoted); + oputz(zQuoted); sqlite3_free(zQuoted); }else{ - utf8_printf(out, "%s", z); + oputz(z); } } if( bSep ){ - utf8_printf(p->out, "%s", p->colSeparator); + oputz(p->colSeparator); } } @@ -2336,16 +2147,16 @@ static int shellAuth( az[1] = zA2; az[2] = zA3; az[3] = zA4; - utf8_printf(p->out, "authorizer: %s", azAction[op]); + oputf("authorizer: %s", azAction[op]); for(i=0; i<4; i++){ - raw_printf(p->out, " "); + oputz(" "); if( az[i] ){ - output_c_string(p->out, az[i]); + output_c_string(az[i]); }else{ - raw_printf(p->out, "NULL"); + oputz("NULL"); } } - raw_printf(p->out, "\n"); + oputz("\n"); if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4); return SQLITE_OK; } @@ -2361,7 +2172,7 @@ static int shellAuth( ** sqlite3_complete() returns false, try to terminate the comment before ** printing the result. https://sqlite.org/forum/forumpost/d7be961c5c */ -static void printSchemaLine(FILE *out, const char *z, const char *zTail){ +static void printSchemaLine(const char *z, const char *zTail){ char *zToFree = 0; if( z==0 ) return; if( zTail==0 ) return; @@ -2383,16 +2194,16 @@ static void printSchemaLine(FILE *out, const char *z, const char *zTail){ } } if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){ - utf8_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); + oputf("CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); }else{ - utf8_printf(out, "%s%s", z, zTail); + oputf("%s%s", z, zTail); } sqlite3_free(zToFree); } -static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ +static void printSchemaLineN(char *z, int n, const char *zTail){ char c = z[n]; z[n] = 0; - printSchemaLine(out, z, zTail); + printSchemaLine(z, zTail); z[n] = c; } @@ -2420,7 +2231,7 @@ static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){ if( zText==0 ) return; nText = strlen(zText); if( p->autoEQPtest ){ - utf8_printf(p->out, "%d,%d,%s\n", iEqpId, p2, zText); + oputf("%d,%d,%s\n", iEqpId, p2, zText); } pNew = sqlite3_malloc64( sizeof(*pNew) + nText ); shell_check_oom(pNew); @@ -2468,8 +2279,7 @@ static void eqp_render_level(ShellState *p, int iEqpId){ for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){ pNext = eqp_next_row(p, iEqpId, pRow); z = pRow->zText; - utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix, - pNext ? "|--" : "`--", z); + oputf("%s%s%s\n", p->sGraph.zPrefix, pNext ? "|--" : "`--", z); if( n<(i64)sizeof(p->sGraph.zPrefix)-7 ){ memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4); eqp_render_level(p, pRow->iEqpId); @@ -2489,13 +2299,13 @@ static void eqp_render(ShellState *p, i64 nCycle){ eqp_reset(p); return; } - utf8_printf(p->out, "%s\n", pRow->zText+3); + oputf("%s\n", pRow->zText+3); p->sGraph.pRow = pRow->pNext; sqlite3_free(pRow); }else if( nCycle>0 ){ - utf8_printf(p->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle); + oputf("QUERY PLAN (cycles=%lld [100%%])\n", nCycle); }else{ - utf8_printf(p->out, "QUERY PLAN\n"); + oputz("QUERY PLAN\n"); } p->sGraph.zPrefix[0] = 0; eqp_render_level(p, 0); @@ -2511,13 +2321,13 @@ static int progress_handler(void *pClientData) { ShellState *p = (ShellState*)pClientData; p->nProgress++; if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){ - raw_printf(p->out, "Progress limit reached (%u)\n", p->nProgress); + oputf("Progress limit reached (%u)\n", p->nProgress); if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0; return 1; } if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){ - raw_printf(p->out, "Progress %u\n", p->nProgress); + oputf("Progress %u\n", p->nProgress); } return 0; } @@ -2526,14 +2336,14 @@ static int progress_handler(void *pClientData) { /* ** Print N dashes */ -static void print_dashes(FILE *out, int N){ +static void print_dashes(int N){ const char zDash[] = "--------------------------------------------------"; const int nDash = sizeof(zDash) - 1; while( N>nDash ){ - fputs(zDash, out); + oputz(zDash); N -= nDash; } - raw_printf(out, "%.*s", N, zDash); + oputf("%.*s", N, zDash); } /* @@ -2546,15 +2356,15 @@ static void print_row_separator( ){ int i; if( nArg>0 ){ - fputs(zSep, p->out); - print_dashes(p->out, p->actualWidth[0]+2); + oputz(zSep); + print_dashes(p->actualWidth[0]+2); for(i=1; iout); - print_dashes(p->out, p->actualWidth[i]+2); + oputz(zSep); + print_dashes(p->actualWidth[i]+2); } - fputs(zSep, p->out); + oputz(zSep); } - fputs("\n", p->out); + oputz("\n"); } /* @@ -2584,10 +2394,10 @@ static int shell_callback( int len = strlen30(azCol[i] ? azCol[i] : ""); if( len>w ) w = len; } - if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator); + if( p->cnt++>0 ) oputz(p->rowSeparator); for(i=0; iout,"%*s = %s%s", w, azCol[i], - azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); + oputf("%*s = %s%s", w, azCol[i], + azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); } break; } @@ -2614,12 +2424,12 @@ static int shell_callback( /* If this is the first row seen, print out the headers */ if( p->cnt++==0 ){ for(i=0; iout, aWidth[i], azCol[ aMap[i] ]); - fputs(i==nArg-1 ? "\n" : " ", p->out); + utf8_width_print(aWidth[i], azCol[ aMap[i] ]); + oputz(i==nArg-1 ? "\n" : " "); } for(i=0; iout, aWidth[i]); - fputs(i==nArg-1 ? "\n" : " ", p->out); + print_dashes(aWidth[i]); + oputz(i==nArg-1 ? "\n" : " "); } } @@ -2637,17 +2447,17 @@ static int shell_callback( } if( i==iIndent && p->aiIndent && p->pStmt ){ if( p->iIndentnIndent ){ - utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); + oputf("%*.s", p->aiIndent[p->iIndent], ""); } p->iIndent++; } - utf8_width_print(p->out, w, zVal ? zVal : p->nullValue); - fputs(i==nArg-1 ? "\n" : zSep, p->out); + utf8_width_print(w, zVal ? zVal : p->nullValue); + oputz(i==nArg-1 ? "\n" : zSep); } break; } case MODE_Semi: { /* .schema and .fullschema output */ - printSchemaLine(p->out, azArg[0], ";\n"); + printSchemaLine(azArg[0], ";\n"); break; } case MODE_Pretty: { /* .schema and .fullschema with --indent */ @@ -2662,7 +2472,7 @@ static int shell_callback( if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0 || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0 ){ - utf8_printf(p->out, "%s;\n", azArg[0]); + oputf("%s;\n", azArg[0]); break; } z = sqlite3_mprintf("%s", azArg[0]); @@ -2695,7 +2505,7 @@ static int shell_callback( }else if( c==')' ){ nParen--; if( nLine>0 && nParen==0 && j>0 ){ - printSchemaLineN(p->out, z, j, "\n"); + printSchemaLineN(z, j, "\n"); j = 0; } } @@ -2704,7 +2514,7 @@ static int shell_callback( && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) ){ if( c=='\n' ) j--; - printSchemaLineN(p->out, z, j, "\n "); + printSchemaLineN(z, j, "\n "); j = 0; nLine++; while( IsSpace(z[i+1]) ){ i++; } @@ -2712,64 +2522,59 @@ static int shell_callback( } z[j] = 0; } - printSchemaLine(p->out, z, ";\n"); + printSchemaLine(z, ";\n"); sqlite3_free(z); break; } case MODE_List: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,"%s%s",azCol[i], - i==nArg-1 ? p->rowSeparator : p->colSeparator); + oputf("%s%s",azCol[i], i==nArg-1 ? p->rowSeparator : p->colSeparator); } } if( azArg==0 ) break; for(i=0; inullValue; - utf8_printf(p->out, "%s", z); - if( iout, "%s", p->colSeparator); - }else{ - utf8_printf(p->out, "%s", p->rowSeparator); - } + oputz(z); + oputz((icolSeparator : p->rowSeparator); } break; } case MODE_Html: { if( p->cnt++==0 && p->showHeader ){ - raw_printf(p->out,""); + oputz(""); for(i=0; iout,""); - output_html_string(p->out, azCol[i]); - raw_printf(p->out,"\n"); + oputz(""); + output_html_string(azCol[i]); + oputz("\n"); } - raw_printf(p->out,"\n"); + oputz("\n"); } if( azArg==0 ) break; - raw_printf(p->out,""); + oputz(""); for(i=0; iout,""); - output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue); - raw_printf(p->out,"\n"); + oputz(""); + output_html_string(azArg[i] ? azArg[i] : p->nullValue); + oputz("\n"); } - raw_printf(p->out,"\n"); + oputz("\n"); break; } case MODE_Tcl: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,azCol[i] ? azCol[i] : ""); - if(iout, "%s", p->colSeparator); + output_c_string(azCol[i] ? azCol[i] : ""); + if(icolSeparator); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); } if( azArg==0 ) break; for(i=0; iout, azArg[i] ? azArg[i] : p->nullValue); - if(iout, "%s", p->colSeparator); + output_c_string(azArg[i] ? azArg[i] : p->nullValue); + if(icolSeparator); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); break; } case MODE_Csv: { @@ -2778,57 +2583,57 @@ static int shell_callback( for(i=0; iout, "%s", p->rowSeparator); + oputz(p->rowSeparator); } if( nArg>0 ){ for(i=0; iout, "%s", p->rowSeparator); + oputz(p->rowSeparator); } setTextMode(p->out, 1); break; } case MODE_Insert: { if( azArg==0 ) break; - utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); + oputf("INSERT INTO %s",p->zDestTable); if( p->showHeader ){ - raw_printf(p->out,"("); + oputz("("); for(i=0; i0 ) raw_printf(p->out, ","); + if( i>0 ) oputz(","); if( quoteChar(azCol[i]) ){ char *z = sqlite3_mprintf("\"%w\"", azCol[i]); shell_check_oom(z); - utf8_printf(p->out, "%s", z); + oputz(z); sqlite3_free(z); }else{ - raw_printf(p->out, "%s", azCol[i]); + oputf("%s", azCol[i]); } } - raw_printf(p->out,")"); + oputz(")"); } p->cnt++; for(i=0; iout, i>0 ? "," : " VALUES("); + oputz(i>0 ? "," : " VALUES("); if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - utf8_printf(p->out,"NULL"); + oputz("NULL"); }else if( aiType && aiType[i]==SQLITE_TEXT ){ if( ShellHasFlag(p, SHFLG_Newlines) ){ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); }else{ - output_quoted_escaped_string(p->out, azArg[i]); + output_quoted_escaped_string(azArg[i]); } }else if( aiType && aiType[i]==SQLITE_INTEGER ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else if( aiType && aiType[i]==SQLITE_FLOAT ){ char z[50]; double r = sqlite3_column_double(p->pStmt, i); sqlite3_uint64 ur; memcpy(&ur,&r,sizeof(r)); if( ur==0x7ff0000000000000LL ){ - raw_printf(p->out, "9.0e+999"); + oputz("9.0e+999"); }else if( ur==0xfff0000000000000LL ){ - raw_printf(p->out, "-9.0e+999"); + oputz("-9.0e+999"); }else{ sqlite3_int64 ir = (sqlite3_int64)r; if( r==(double)ir ){ @@ -2836,21 +2641,21 @@ static int shell_callback( }else{ sqlite3_snprintf(50,z,"%!.20g", r); } - raw_printf(p->out, "%s", z); + oputz(z); } }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_hex_blob(p->out, pBlob, nBlob); + output_hex_blob(pBlob, nBlob); }else if( isNumber(azArg[i], 0) ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else if( ShellHasFlag(p, SHFLG_Newlines) ){ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); }else{ - output_quoted_escaped_string(p->out, azArg[i]); + output_quoted_escaped_string(azArg[i]); } } - raw_printf(p->out,");\n"); + oputz(");\n"); break; } case MODE_Json: { @@ -2862,37 +2667,37 @@ static int shell_callback( } p->cnt++; for(i=0; iout, azCol[i], -1); - putc(':', p->out); + output_json_string(azCol[i], -1); + oputz(":"); if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - fputs("null",p->out); + oputz("null"); }else if( aiType && aiType[i]==SQLITE_FLOAT ){ char z[50]; double r = sqlite3_column_double(p->pStmt, i); sqlite3_uint64 ur; memcpy(&ur,&r,sizeof(r)); if( ur==0x7ff0000000000000LL ){ - raw_printf(p->out, "9.0e+999"); + oputz("9.0e+999"); }else if( ur==0xfff0000000000000LL ){ - raw_printf(p->out, "-9.0e+999"); + oputz("-9.0e+999"); }else{ sqlite3_snprintf(50,z,"%!.20g", r); - raw_printf(p->out, "%s", z); + oputz(z); } }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_json_string(p->out, pBlob, nBlob); + output_json_string(pBlob, nBlob); }else if( aiType && aiType[i]==SQLITE_TEXT ){ - output_json_string(p->out, azArg[i], -1); + output_json_string(azArg[i], -1); }else{ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); } if( iout); + oputz(","); } } - putc('}', p->out); + oputz("}"); break; } case MODE_Quote: { @@ -2900,7 +2705,7 @@ static int shell_callback( if( p->cnt==0 && p->showHeader ){ for(i=0; i0 ) fputs(p->colSeparator, p->out); - output_quoted_string(p->out, azCol[i]); + output_quoted_string(azCol[i]); } fputs(p->rowSeparator, p->out); } @@ -2908,24 +2713,24 @@ static int shell_callback( for(i=0; i0 ) fputs(p->colSeparator, p->out); if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - utf8_printf(p->out,"NULL"); + oputz("NULL"); }else if( aiType && aiType[i]==SQLITE_TEXT ){ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); }else if( aiType && aiType[i]==SQLITE_INTEGER ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else if( aiType && aiType[i]==SQLITE_FLOAT ){ char z[50]; double r = sqlite3_column_double(p->pStmt, i); sqlite3_snprintf(50,z,"%!.20g", r); - raw_printf(p->out, "%s", z); + oputz(z); }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_hex_blob(p->out, pBlob, nBlob); + output_hex_blob(pBlob, nBlob); }else if( isNumber(azArg[i], 0) ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else{ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); } } fputs(p->rowSeparator, p->out); @@ -2934,17 +2739,17 @@ static int shell_callback( case MODE_Ascii: { if( p->cnt++==0 && p->showHeader ){ for(i=0; i0 ) utf8_printf(p->out, "%s", p->colSeparator); - utf8_printf(p->out,"%s",azCol[i] ? azCol[i] : ""); + if( i>0 ) oputz(p->colSeparator); + oputz(azCol[i] ? azCol[i] : ""); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); } if( azArg==0 ) break; for(i=0; i0 ) utf8_printf(p->out, "%s", p->colSeparator); - utf8_printf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue); + if( i>0 ) oputz(p->colSeparator); + oputz(azArg[i] ? azArg[i] : p->nullValue); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); break; } case MODE_EQP: { @@ -3023,7 +2828,7 @@ static void createSelftestTable(ShellState *p){ "DROP TABLE [_shell$self];" ,0,0,&zErrMsg); if( zErrMsg ){ - utf8_printf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg); + eputf("SELFTEST initialization failure: %s\n", zErrMsg); sqlite3_free(zErrMsg); } sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0); @@ -3126,8 +2931,8 @@ static int run_table_dump_query( rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ char *zContext = shell_error_context(zSelect, p->db); - utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s", rc, - sqlite3_errmsg(p->db), zContext); + oputf("/**** ERROR: (%d) %s *****/\n%s", + rc, sqlite3_errmsg(p->db), zContext); sqlite3_free(zContext); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; return rc; @@ -3136,23 +2941,22 @@ static int run_table_dump_query( nResult = sqlite3_column_count(pSelect); while( rc==SQLITE_ROW ){ z = (const char*)sqlite3_column_text(pSelect, 0); - utf8_printf(p->out, "%s", z); + oputf("%s", z); for(i=1; iout, ",%s", sqlite3_column_text(pSelect, i)); + oputf(",%s", sqlite3_column_text(pSelect, i)); } if( z==0 ) z = ""; while( z[0] && (z[0]!='-' || z[1]!='-') ) z++; if( z[0] ){ - raw_printf(p->out, "\n;\n"); + oputz("\n;\n"); }else{ - raw_printf(p->out, ";\n"); + oputz(";\n"); } rc = sqlite3_step(pSelect); } rc = sqlite3_finalize(pSelect); if( rc!=SQLITE_OK ){ - utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, - sqlite3_errmsg(p->db)); + oputf("/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; } return rc; @@ -3188,7 +2992,7 @@ static char *save_err_msg( /* ** Attempt to display I/O stats on Linux using /proc/PID/io */ -static void displayLinuxIoStats(FILE *out){ +static void displayLinuxIoStats(void){ FILE *in; char z[200]; sqlite3_snprintf(sizeof(z), z, "/proc/%d/io", getpid()); @@ -3211,7 +3015,7 @@ static void displayLinuxIoStats(FILE *out){ for(i=0; iout, "%-36s %s\n", zLabel, zLine); + oputf("%-36s %s\n", zLabel, zLine); } /* @@ -3256,30 +3059,28 @@ static int display_stats( ){ int iCur; int iHiwtr; - FILE *out; if( pArg==0 || pArg->out==0 ) return 0; - out = pArg->out; if( pArg->pStmt && pArg->statsOn==2 ){ int nCol, i, x; sqlite3_stmt *pStmt = pArg->pStmt; char z[100]; nCol = sqlite3_column_count(pStmt); - raw_printf(out, "%-36s %d\n", "Number of output columns:", nCol); + oputf("%-36s %d\n", "Number of output columns:", nCol); for(i=0; istatsOn==3 ){ if( pArg->pStmt ){ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset); - raw_printf(pArg->out, "VM-steps: %d\n", iCur); + oputf("VM-steps: %d\n", iCur); } return 0; } - displayStatLine(pArg, "Memory Used:", + displayStatLine("Memory Used:", "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset); - displayStatLine(pArg, "Number of Outstanding Allocations:", + displayStatLine("Number of Outstanding Allocations:", "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset); if( pArg->shellFlgs & SHFLG_Pagecache ){ - displayStatLine(pArg, "Number of Pcache Pages Used:", + displayStatLine("Number of Pcache Pages Used:", "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset); } - displayStatLine(pArg, "Number of Pcache Overflow Bytes:", + displayStatLine("Number of Pcache Overflow Bytes:", "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset); - displayStatLine(pArg, "Largest Allocation:", + displayStatLine("Largest Allocation:", "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset); - displayStatLine(pArg, "Largest Pcache Allocation:", + displayStatLine("Largest Pcache Allocation:", "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset); #ifdef YYTRACKMAXSTACKDEPTH - displayStatLine(pArg, "Deepest Parser Stack:", + displayStatLine("Deepest Parser Stack:", "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset); #endif @@ -3316,77 +3117,68 @@ static int display_stats( iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, - "Lookaside Slots Used: %d (max %d)\n", - iCur, iHiwtr); + oputf("Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Successful lookaside attempts: %d\n", - iHiwtr); + oputf("Successful lookaside attempts: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Lookaside failures due to size: %d\n", - iHiwtr); + oputf("Lookaside failures due to size: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Lookaside failures due to OOM: %d\n", - iHiwtr); + oputf("Lookaside failures due to OOM: %d\n", iHiwtr); } iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Pager Heap Usage: %d bytes\n", - iCur); + oputf("Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache hits: %d\n", iCur); + oputf("Page cache hits: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache misses: %d\n", iCur); + oputf("Page cache misses: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache writes: %d\n", iCur); + oputf("Page cache writes: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache spills: %d\n", iCur); + oputf("Page cache spills: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Schema Heap Usage: %d bytes\n", - iCur); + oputf("Schema Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n", - iCur); + oputf("Statement Heap/Lookaside Usage: %d bytes\n", iCur); } if( pArg->pStmt ){ int iHit, iMiss; iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); - raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur); + oputf("Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); - raw_printf(pArg->out, "Sort Operations: %d\n", iCur); + oputf("Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); - raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur); + oputf("Autoindex Inserts: %d\n", iCur); iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset); iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset); if( iHit || iMiss ){ - raw_printf(pArg->out, "Bloom filter bypass taken: %d/%d\n", - iHit, iHit+iMiss); + oputf("Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss); } iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); - raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur); + oputf("Virtual Machine Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset); - raw_printf(pArg->out, "Reprepare operations: %d\n", iCur); + oputf("Reprepare operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset); - raw_printf(pArg->out, "Number of times run: %d\n", iCur); + oputf("Number of times run: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset); - raw_printf(pArg->out, "Memory used by prepared stmt: %d\n", iCur); + oputf("Memory used by prepared stmt: %d\n", iCur); } #ifdef __linux__ - displayLinuxIoStats(pArg->out); + displayLinuxIoStats(); #endif /* Do not remove this machine readable comment: extra-stats-output-here */ @@ -3621,7 +3413,7 @@ static void display_scanstats( UNUSED_PARAMETER(pArg); #else if( pArg->scanstatsOn==3 ){ - const char *zSql = + const char *zSql = " SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec," " round(ncycle*100.0 / (sum(ncycle) OVER ()), 2)||'%' AS cycles" " FROM bytecode(?)"; @@ -3767,17 +3559,17 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ /* Draw horizontal line N characters long using unicode box ** characters */ -static void print_box_line(FILE *out, int N){ +static void print_box_line(int N){ const char zDash[] = BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; const int nDash = sizeof(zDash) - 1; N *= 3; while( N>nDash ){ - utf8_printf(out, zDash); + oputz(zDash); N -= nDash; } - utf8_printf(out, "%.*s", N, zDash); + oputf("%.*s", N, zDash); } /* @@ -3792,15 +3584,15 @@ static void print_box_row_separator( ){ int i; if( nArg>0 ){ - utf8_printf(p->out, "%s", zSep1); - print_box_line(p->out, p->actualWidth[0]+2); + oputz(zSep1); + print_box_line(p->actualWidth[0]+2); for(i=1; iout, "%s", zSep2); - print_box_line(p->out, p->actualWidth[i]+2); + oputz(zSep2); + print_box_line(p->actualWidth[i]+2); } - utf8_printf(p->out, "%s", zSep3); + oputz(zSep3); } - fputs("\n", p->out); + oputz("\n"); } /* @@ -4063,11 +3855,11 @@ static void exec_prepared_stmt_columnar( for(i=0; iactualWidth[i]; if( p->colWidth[i]<0 ) w = -w; - utf8_width_print(p->out, w, azData[i]); + utf8_width_print(w, azData[i]); fputs(i==nColumn-1?"\n":" ", p->out); } for(i=0; iout, p->actualWidth[i]); + print_dashes(p->actualWidth[i]); fputs(i==nColumn-1?"\n":" ", p->out); } } @@ -4081,8 +3873,8 @@ static void exec_prepared_stmt_columnar( for(i=0; iactualWidth[i]; n = strlenChar(azData[i]); - utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); - fputs(i==nColumn-1?" |\n":" | ", p->out); + oputf("%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); + oputz(i==nColumn-1?" |\n":" | "); } print_row_separator(p, nColumn, "+"); break; @@ -4094,8 +3886,8 @@ static void exec_prepared_stmt_columnar( for(i=0; iactualWidth[i]; n = strlenChar(azData[i]); - utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); - fputs(i==nColumn-1?" |\n":" | ", p->out); + oputf("%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); + oputz(i==nColumn-1?" |\n":" | "); } print_row_separator(p, nColumn, "|"); break; @@ -4104,13 +3896,13 @@ static void exec_prepared_stmt_columnar( colSep = " " BOX_13 " "; rowSep = " " BOX_13 "\n"; print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34); - utf8_printf(p->out, BOX_13 " "); + oputz(BOX_13 " "); for(i=0; iactualWidth[i]; n = strlenChar(azData[i]); - utf8_printf(p->out, "%*s%s%*s%s", - (w-n)/2, "", azData[i], (w-n+1)/2, "", - i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); + oputf("%*s%s%*s%s", + (w-n)/2, "", azData[i], (w-n+1)/2, "", + i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); } print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); break; @@ -4118,28 +3910,28 @@ static void exec_prepared_stmt_columnar( } for(i=nColumn, j=0; icMode!=MODE_Column ){ - utf8_printf(p->out, "%s", p->cMode==MODE_Box?BOX_13" ":"| "); + oputz(p->cMode==MODE_Box?BOX_13" ":"| "); } z = azData[i]; if( z==0 ) z = p->nullValue; w = p->actualWidth[j]; if( p->colWidth[j]<0 ) w = -w; - utf8_width_print(p->out, w, z); + utf8_width_print(w, z); if( j==nColumn-1 ){ - utf8_printf(p->out, "%s", rowSep); + oputz(rowSep); if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1cMode==MODE_Table ){ print_row_separator(p, nColumn, "+"); }else if( p->cMode==MODE_Box ){ print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); }else if( p->cMode==MODE_Column ){ - raw_printf(p->out, "\n"); + oputz("\n"); } } j = -1; if( seenInterrupt ) goto columnar_end; }else{ - utf8_printf(p->out, "%s", colSep); + oputz(colSep); } } if( p->cMode==MODE_Table ){ @@ -4149,7 +3941,7 @@ static void exec_prepared_stmt_columnar( } columnar_end: if( seenInterrupt ){ - utf8_printf(p->out, "Interrupt\n"); + oputz("Interrupt\n"); } nData = (nRow+1)*nColumn; for(i=0; iout; int bVerbose = pState->expert.bVerbose; rc = sqlite3_expert_analyze(p, pzErr); @@ -4298,8 +4089,8 @@ static int expertFinish( if( bVerbose ){ const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES); - raw_printf(out, "-- Candidates -----------------------------\n"); - raw_printf(out, "%s\n", zCand); + oputz("-- Candidates -----------------------------\n"); + oputf("%s\n", zCand); } for(i=0; i=2 && 0==cli_strncmp(z, "-sample", n) ){ if( i==(nArg-1) ){ - raw_printf(stderr, "option requires an argument: %s\n", z); + eputf("option requires an argument: %s\n", z); rc = SQLITE_ERROR; }else{ iSample = (int)integerValue(azArg[++i]); if( iSample<0 || iSample>100 ){ - raw_printf(stderr, "value out of range: %s\n", azArg[i]); + eputf("value out of range: %s\n", azArg[i]); rc = SQLITE_ERROR; } } } else{ - raw_printf(stderr, "unknown option: %s\n", z); + eputf("unknown option: %s\n", z); rc = SQLITE_ERROR; } } @@ -4365,8 +4156,7 @@ static int expertDotCommand( if( rc==SQLITE_OK ){ pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); if( pState->expert.pExpert==0 ){ - raw_printf(stderr, "sqlite3_expert_new: %s\n", - zErr ? zErr : "out of memory"); + eputf("sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory"); rc = SQLITE_ERROR; }else{ sqlite3_expert_config( @@ -4693,9 +4483,9 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ noSys = (p->shellFlgs & SHFLG_DumpNoSys)!=0; if( cli_strcmp(zTable, "sqlite_sequence")==0 && !noSys ){ - if( !dataOnly ) raw_printf(p->out, "DELETE FROM sqlite_sequence;\n"); + if( !dataOnly ) oputz("DELETE FROM sqlite_sequence;\n"); }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){ - if( !dataOnly ) raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + if( !dataOnly ) oputz("ANALYZE sqlite_schema;\n"); }else if( cli_strncmp(zTable, "sqlite_", 7)==0 ){ return 0; }else if( dataOnly ){ @@ -4703,7 +4493,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ }else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ char *zIns; if( !p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=ON;\n"); + oputz("PRAGMA writable_schema=ON;\n"); p->writableSchema = 1; } zIns = sqlite3_mprintf( @@ -4711,11 +4501,11 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ "VALUES('table','%q','%q',0,'%q');", zTable, zTable, zSql); shell_check_oom(zIns); - utf8_printf(p->out, "%s\n", zIns); + oputf("%s\n", zIns); sqlite3_free(zIns); return 0; }else{ - printSchemaLine(p->out, zSql, ";\n"); + printSchemaLine(zSql, ";\n"); } if( cli_strcmp(zType, "table")==0 ){ @@ -4773,7 +4563,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ p->mode = p->cMode = MODE_Insert; rc = shell_exec(p, sSelect.z, 0); if( (rc&0xff)==SQLITE_CORRUPT ){ - raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); + oputz("/****** CORRUPTION ERROR *******/\n"); toggleSelectOrder(p->db); shell_exec(p, sSelect.z, 0); toggleSelectOrder(p->db); @@ -4804,9 +4594,9 @@ static int run_schema_dump_query( if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); - raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); + oputz("/****** CORRUPTION ERROR *******/\n"); if( zErr ){ - utf8_printf(p->out, "/****** %s ******/\n", zErr); + oputf("/****** %s ******/\n", zErr); sqlite3_free(zErr); zErr = 0; } @@ -4815,7 +4605,7 @@ static int run_schema_dump_query( sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery); rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); if( rc ){ - utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr); + oputf("/****** ERROR: %s ******/\n", zErr); }else{ rc = SQLITE_CORRUPT; } @@ -5171,10 +4961,10 @@ static int showHelp(FILE *out, const char *zPattern){ } if( ((hw^hh)&HH_Undoc)==0 ){ if( (hh&HH_Summary)!=0 ){ - utf8_printf(out, ".%s\n", azHelp[i]+1); + sputf(out, ".%s\n", azHelp[i]+1); ++n; }else if( (hw&HW_SummaryOnly)==0 ){ - utf8_printf(out, "%s\n", azHelp[i]); + sputf(out, "%s\n", azHelp[i]); } } } @@ -5184,7 +4974,7 @@ static int showHelp(FILE *out, const char *zPattern){ shell_check_oom(zPat); for(i=0; i65536 || (pgsz & (pgsz-1))!=0 ){ - utf8_printf(stderr, "invalid pagesize\n"); + eputz("invalid pagesize\n"); goto readHexDb_error; } for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){ @@ -5453,7 +5243,7 @@ readHexDb_error: p->lineno = nLine; } sqlite3_free(a); - utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine); + eputf("Error on line %d of --hexdb input\n", nLine); return 0; } #endif /* SQLITE_OMIT_DESERIALIZE */ @@ -5529,22 +5319,19 @@ static void open_db(ShellState *p, int openFlags){ } globalDb = p->db; if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", - zDbFilename, sqlite3_errmsg(p->db)); + eputf("Error: unable to open database \"%s\": %s\n", + zDbFilename, sqlite3_errmsg(p->db)); if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ exit(1); } sqlite3_close(p->db); sqlite3_open(":memory:", &p->db); if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - utf8_printf(stderr, - "Also: unable to open substitute in-memory database.\n" - ); + eputz("Also: unable to open substitute in-memory database.\n"); exit(1); }else{ - utf8_printf(stderr, - "Notice: using substitute in-memory database instead of \"%s\"\n", - zDbFilename); + eputf("Notice: using substitute in-memory database instead of \"%s\"\n", + zDbFilename); } } sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0); @@ -5651,7 +5438,7 @@ static void open_db(ShellState *p, int openFlags){ SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE); if( rc ){ - utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc); + eputf("Error: sqlite3_deserialize() returns %d\n", rc); } if( p->szMax>0 ){ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax); @@ -5675,8 +5462,7 @@ static void open_db(ShellState *p, int openFlags){ void close_db(sqlite3 *db){ int rc = sqlite3_close(db); if( rc ){ - utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", - rc, sqlite3_errmsg(db)); + eputf("Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); } } @@ -5837,8 +5623,7 @@ static int booleanValue(const char *zArg){ if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ return 0; } - utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", - zArg); + eputf("ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg); return 0; } @@ -5876,7 +5661,7 @@ static FILE *output_file_open(const char *zFile, int bTextMode){ }else{ f = fopen(zFile, bTextMode ? "w" : "wb"); if( f==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); + eputf("Error: cannot open \"%s\"\n", zFile); } } return f; @@ -5898,7 +5683,7 @@ static int sql_trace_callback( i64 nSql; if( p->traceOut==0 ) return 0; if( mType==SQLITE_TRACE_CLOSE ){ - utf8_printf(p->traceOut, "-- closing database connection\n"); + sputz(p->traceOut, "-- closing database connection\n"); return 0; } if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ @@ -5929,12 +5714,12 @@ static int sql_trace_callback( switch( mType ){ case SQLITE_TRACE_ROW: case SQLITE_TRACE_STMT: { - utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql); + sputf(p->traceOut, "%.*s;\n", (int)nSql, zSql); break; } case SQLITE_TRACE_PROFILE: { sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; - utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); + sputf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); break; } } @@ -6041,12 +5826,11 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ break; } if( pc==cQuote && c!='\r' ){ - utf8_printf(stderr, "%s:%d: unescaped %c character\n", - p->zFile, p->nLine, cQuote); + eputf("%s:%d: unescaped %c character\n", p->zFile, p->nLine, cQuote); } if( c==EOF ){ - utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n", - p->zFile, startLine, cQuote); + eputf("%s:%d: unterminated %c-quoted field\n", + p->zFile, startLine, cQuote); p->cTerm = c; break; } @@ -6144,9 +5928,8 @@ static void tryToCloneData( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Error %d: %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); + eputf("Error %d: %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_data_xfer; } n = sqlite3_column_count(pQuery); @@ -6162,9 +5945,8 @@ static void tryToCloneData( memcpy(zInsert+i, ");", 3); rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0); if( rc ){ - utf8_printf(stderr, "Error %d: %s on [%s]\n", - sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), - zInsert); + eputf("Error %d: %s on [%s]\n", + sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), zInsert); goto end_data_xfer; } for(k=0; k<2; k++){ @@ -6199,8 +5981,8 @@ static void tryToCloneData( } /* End for */ rc = sqlite3_step(pInsert); if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){ - utf8_printf(stderr, "Error %d: %s\n", sqlite3_extended_errcode(newDb), - sqlite3_errmsg(newDb)); + eputf("Error %d: %s\n", + sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb)); } sqlite3_reset(pInsert); cnt++; @@ -6217,7 +5999,7 @@ static void tryToCloneData( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Warning: cannot step \"%s\" backwards", zTable); + eputf("Warning: cannot step \"%s\" backwards", zTable); break; } } /* End for(k=0...) */ @@ -6254,9 +6036,8 @@ static void tryToCloneSchema( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Error: (%d) %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); + eputf("Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db), + sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; } while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ @@ -6264,10 +6045,10 @@ static void tryToCloneSchema( zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){ - printf("%s... ", zName); fflush(stdout); + sputf(stdout, "%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ - utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + eputf("Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } @@ -6275,7 +6056,7 @@ static void tryToCloneSchema( if( xForEach ){ xForEach(p, newDb, (const char*)zName); } - printf("done\n"); + sputz(stdout, "done\n"); } if( rc!=SQLITE_DONE ){ sqlite3_finalize(pQuery); @@ -6285,9 +6066,8 @@ static void tryToCloneSchema( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Error: (%d) %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); + eputf("Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; } while( sqlite3_step(pQuery)==SQLITE_ROW ){ @@ -6295,17 +6075,17 @@ static void tryToCloneSchema( zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue; - printf("%s... ", zName); fflush(stdout); + sputf(stdout, "%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ - utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + eputf("Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } if( xForEach ){ xForEach(p, newDb, (const char*)zName); } - printf("done\n"); + sputz(stdout, "done\n"); } } end_schema_xfer: @@ -6322,13 +6102,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){ int rc; sqlite3 *newDb = 0; if( access(zNewDb,0)==0 ){ - utf8_printf(stderr, "File \"%s\" already exists.\n", zNewDb); + eputf("File \"%s\" already exists.\n", zNewDb); return; } rc = sqlite3_open(zNewDb, &newDb); if( rc ){ - utf8_printf(stderr, "Cannot create output database: %s\n", - sqlite3_errmsg(newDb)); + eputf("Cannot create output database: %s\n", sqlite3_errmsg(newDb)); }else{ sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0); sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0); @@ -6340,6 +6119,18 @@ static void tryToClone(ShellState *p, const char *zNewDb){ close_db(newDb); } +#ifndef SQLITE_SHELL_FIDDLE +/* +** Change the output stream (file or pipe or console) to something else. +*/ +static void output_redir(ShellState *p, FILE *pfNew){ + if( p->out != stdout ) eputz("Output already redirected.\n"); + else{ + p->out = pfNew; + setOutputStream(pfNew); + } +} + /* ** Change the output file back to stdout. ** @@ -6367,7 +6158,7 @@ static void output_reset(ShellState *p){ char *zCmd; zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); if( system(zCmd) ){ - utf8_printf(stderr, "Failed: [%s]\n", zCmd); + eputf("Failed: [%s]\n", zCmd); }else{ /* Give the start/open/xdg-open command some time to get ** going before we continue, and potential delete the @@ -6382,7 +6173,12 @@ static void output_reset(ShellState *p){ } p->outfile[0] = 0; p->out = stdout; + setOutputStream(stdout); } +#else +# define output_redir(SS,pfO) +# define output_reset(SS) +#endif /* ** Run an SQL command and return the single integer result. @@ -6453,7 +6249,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "error: %s\n", sqlite3_errmsg(p->db)); + eputf("error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); return 1; } @@ -6466,28 +6262,28 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ memcpy(aHdr, pb, 100); sqlite3_finalize(pStmt); }else{ - raw_printf(stderr, "unable to read database header\n"); + eputz("unable to read database header\n"); sqlite3_finalize(pStmt); return 1; } i = get2byteInt(aHdr+16); if( i==1 ) i = 65536; - utf8_printf(p->out, "%-20s %d\n", "database page size:", i); - utf8_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]); - utf8_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]); - utf8_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + oputf("%-20s %d\n", "database page size:", i); + oputf("%-20s %d\n", "write format:", aHdr[18]); + oputf("%-20s %d\n", "read format:", aHdr[19]); + oputf("%-20s %d\n", "reserved bytes:", aHdr[20]); for(i=0; iout, "%-20s %u", aField[i].zName, val); + oputf("%-20s %u", aField[i].zName, val); switch( ofst ){ case 56: { - if( val==1 ) raw_printf(p->out, " (utf8)"); - if( val==2 ) raw_printf(p->out, " (utf16le)"); - if( val==3 ) raw_printf(p->out, " (utf16be)"); + if( val==1 ) oputz(" (utf8)"); + if( val==2 ) oputz(" (utf16le)"); + if( val==3 ) oputz(" (utf16be)"); } } - raw_printf(p->out, "\n"); + oputz("\n"); } if( zDb==0 ){ zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); @@ -6500,11 +6296,11 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab); int val = db_int(p->db, zSql); sqlite3_free(zSql); - utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val); + oputf("%-20s %d\n", aQuery[i].zName, val); } sqlite3_free(zSchemaTab); sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); - utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion); + oputf("%-20s %u\n", "data version", iDataVersion); return 0; } #endif /* SQLITE_SHELL_HAVE_RECOVER */ @@ -6514,7 +6310,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ */ static int shellDatabaseError(sqlite3 *db){ const char *zErr = sqlite3_errmsg(db); - utf8_printf(stderr, "Error: %s\n", zErr); + eputf("Error: %s\n", zErr); return 1; } @@ -6749,7 +6545,6 @@ static int lintFkeyIndexes( int nArg /* Number of entries in azArg[] */ ){ sqlite3 *db = pState->db; /* Database handle to query "main" db of */ - FILE *out = pState->out; /* Stream to write non-error output to */ int bVerbose = 0; /* If -verbose is present */ int bGroupByParent = 0; /* If -groupbyparent is present */ int i; /* To iterate through azArg[] */ @@ -6831,9 +6626,7 @@ static int lintFkeyIndexes( zIndent = " "; } else{ - raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", - azArg[0], azArg[1] - ); + eputf("Usage: %s %s ?-verbose? ?-groupbyparent?\n", azArg[0], azArg[1]); return SQLITE_ERROR; } } @@ -6877,23 +6670,23 @@ static int lintFkeyIndexes( if( rc!=SQLITE_OK ) break; if( res<0 ){ - raw_printf(stderr, "Error: internal error"); + eputz("Error: internal error"); break; }else{ if( bGroupByParent && (bVerbose || res==0) && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) ){ - raw_printf(out, "-- Parent table %s\n", zParent); + oputf("-- Parent table %s\n", zParent); sqlite3_free(zPrev); zPrev = sqlite3_mprintf("%s", zParent); } if( res==0 ){ - raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + oputf("%s%s --> %s\n", zIndent, zCI, zTarget); }else if( bVerbose ){ - raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", - zIndent, zFrom, zTarget + oputf("%s/* no extra indexes required for %s -> %s */\n", + zIndent, zFrom, zTarget ); } } @@ -6901,16 +6694,16 @@ static int lintFkeyIndexes( sqlite3_free(zPrev); if( rc!=SQLITE_OK ){ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + eputf("%s\n", sqlite3_errmsg(db)); } rc2 = sqlite3_finalize(pSql); if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ rc = rc2; - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + eputf("%s\n", sqlite3_errmsg(db)); } }else{ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + eputf("%s\n", sqlite3_errmsg(db)); } return rc; @@ -6930,9 +6723,9 @@ static int lintDotCommand( return lintFkeyIndexes(pState, azArg, nArg); usage: - raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]); - raw_printf(stderr, "Where sub-commands are:\n"); - raw_printf(stderr, " fkey-indexes\n"); + eputf("Usage %s sub-command ?switches...?\n", azArg[0]); + eputz("Where sub-commands are:\n"); + eputz(" fkey-indexes\n"); return SQLITE_ERROR; } @@ -6947,9 +6740,7 @@ static void shellPrepare( if( *pRc==SQLITE_OK ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ - raw_printf(stderr, "sql error: %s (%d)\n", - sqlite3_errmsg(db), sqlite3_errcode(db) - ); + eputf("sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db)); *pRc = rc; } } @@ -7000,7 +6791,7 @@ void shellFinalize( int rc = sqlite3_finalize(pStmt); if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ - raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + eputf("SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -7021,7 +6812,7 @@ void shellReset( if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ sqlite3 *db = sqlite3_db_handle(pStmt); - raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + eputf("SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -7071,11 +6862,11 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ va_start(ap, zFmt); z = sqlite3_vmprintf(zFmt, ap); va_end(ap); - utf8_printf(stderr, "Error: %s\n", z); + eputf("Error: %s\n", z); if( pAr->fromCmdLine ){ - utf8_printf(stderr, "Use \"-A\" for more help\n"); + eputz("Use \"-A\" for more help\n"); }else{ - utf8_printf(stderr, "Use \".archive --help\" for more help\n"); + eputz("Use \".archive --help\" for more help\n"); } sqlite3_free(z); return SQLITE_ERROR; @@ -7175,7 +6966,7 @@ static int arParseCommand( struct ArSwitch *pEnd = &aSwitch[nSwitch]; if( nArg<=1 ){ - utf8_printf(stderr, "Wrong number of arguments. Usage:\n"); + eputz("Wrong number of arguments. Usage:\n"); return arUsage(stderr); }else{ char *z = azArg[1]; @@ -7281,7 +7072,7 @@ static int arParseCommand( } } if( pAr->eCmd==0 ){ - utf8_printf(stderr, "Required argument missing. Usage:\n"); + eputz("Required argument missing. Usage:\n"); return arUsage(stderr); } return SQLITE_OK; @@ -7324,7 +7115,7 @@ static int arCheckEntries(ArCommand *pAr){ } shellReset(&rc, pTest); if( rc==SQLITE_OK && bOk==0 ){ - utf8_printf(stderr, "not found in archive: %s\n", z); + eputf("not found in archive: %s\n", z); rc = SQLITE_ERROR; } } @@ -7391,18 +7182,15 @@ static int arListCommand(ArCommand *pAr){ shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + oputf("%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( pAr->bVerbose ){ - utf8_printf(pAr->p->out, "%s % 10d %s %s\n", - sqlite3_column_text(pSql, 0), - sqlite3_column_int(pSql, 1), - sqlite3_column_text(pSql, 2), - sqlite3_column_text(pSql, 3) - ); + oputf("%s % 10d %s %s\n", + sqlite3_column_text(pSql, 0), sqlite3_column_int(pSql, 1), + sqlite3_column_text(pSql, 2),sqlite3_column_text(pSql, 3)); }else{ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + oputf("%s\n", sqlite3_column_text(pSql, 0)); } } } @@ -7411,7 +7199,6 @@ static int arListCommand(ArCommand *pAr){ return rc; } - /* ** Implementation of .ar "Remove" command. */ @@ -7430,7 +7217,7 @@ static int arRemoveCommand(ArCommand *pAr){ zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); + oputf("%s\n", zSql); }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); @@ -7443,7 +7230,7 @@ static int arRemoveCommand(ArCommand *pAr){ } } if( zErr ){ - utf8_printf(stdout, "ERROR: %s\n", zErr); + sputf(stdout, "ERROR: %s\n", zErr); /* stdout? */ sqlite3_free(zErr); } } @@ -7507,11 +7294,11 @@ static int arExtractCommand(ArCommand *pAr){ j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); sqlite3_bind_int(pSql, j, i); if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + oputf("%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( i==0 && pAr->bVerbose ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + oputf("%s\n", sqlite3_column_text(pSql, 0)); } } } @@ -7531,13 +7318,13 @@ static int arExtractCommand(ArCommand *pAr){ static int arExecSql(ArCommand *pAr, const char *zSql){ int rc; if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); + oputf("%s\n", zSql); rc = SQLITE_OK; }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); if( zErr ){ - utf8_printf(stdout, "ERROR: %s\n", zErr); + sputf(stdout, "ERROR: %s\n", zErr); sqlite3_free(zErr); } } @@ -7712,15 +7499,13 @@ static int arDotCommand( } cmd.db = 0; if( cmd.bDryRun ){ - utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, - eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); + oputf("-- open database '%s'%s\n", cmd.zFile, + eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "cannot open file: %s (%s)\n", - cmd.zFile, sqlite3_errmsg(cmd.db) - ); + eputf("cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db)); goto end_ar_command; } sqlite3_fileio_init(cmd.db, 0, 0); @@ -7733,7 +7518,7 @@ static int arDotCommand( if( cmd.eCmd!=AR_CMD_CREATE && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) ){ - utf8_printf(stderr, "database does not contain an 'sqlar' table\n"); + eputz("database does not contain an 'sqlar' table\n"); rc = SQLITE_ERROR; goto end_ar_command; } @@ -7791,7 +7576,7 @@ end_ar_command: */ static int recoverSqlCb(void *pCtx, const char *zSql){ ShellState *pState = (ShellState*)pCtx; - utf8_printf(pState->out, "%s;\n", zSql); + sputf(pState->out, "%s;\n", zSql); return SQLITE_OK; } @@ -7834,7 +7619,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ bRowids = 0; } else{ - utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); + eputf("unexpected option: %s\n", azArg[i]); showHelp(pState->out, azArg[0]); return 1; } @@ -7853,7 +7638,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ const char *zErr = sqlite3_recover_errmsg(p); int errCode = sqlite3_recover_errcode(p); - raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode); + eputf("sql error: %s (%d)\n", zErr, errCode); } rc = sqlite3_recover_finish(p); return rc; @@ -7878,7 +7663,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ #define rc_err_oom_die(rc) \ if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ - fprintf(stderr,"E:%d\n",rc), assert(0) + eputf("E:%d\n",rc), assert(0) #else static void rc_err_oom_die(int rc){ if( rc==SQLITE_NOMEM ) shell_check_oom(0); @@ -8018,6 +7803,7 @@ FROM (\ sqlite3_exec(*pDb,"drop table if exists ColNames;" "drop view if exists RepeatedNames;",0,0,0); #endif +#undef SHELL_COLFIX_DB rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); rc_err_oom_die(rc); } @@ -8131,7 +7917,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_OMIT_AUTHORIZATION if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){ if( nArg!=2 ){ - raw_printf(stderr, "Usage: .auth ON|OFF\n"); + eputz("Usage: .auth ON|OFF\n"); rc = 1; goto meta_command_exit; } @@ -8178,7 +7964,7 @@ static int do_meta_command(char *zLine, ShellState *p){ bAsync = 1; }else { - utf8_printf(stderr, "unknown option: %s\n", azArg[j]); + eputf("unknown option: %s\n", azArg[j]); return 1; } }else if( zDestFile==0 ){ @@ -8187,19 +7973,19 @@ static int do_meta_command(char *zLine, ShellState *p){ zDb = zDestFile; zDestFile = azArg[j]; }else{ - raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); + eputz("Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); return 1; } } if( zDestFile==0 ){ - raw_printf(stderr, "missing FILENAME argument on .backup\n"); + eputz("missing FILENAME argument on .backup\n"); return 1; } if( zDb==0 ) zDb = "main"; rc = sqlite3_open_v2(zDestFile, &pDest, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); + eputf("Error: cannot open \"%s\"\n", zDestFile); close_db(pDest); return 1; } @@ -8210,7 +7996,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb); if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + eputf("Error: %s\n", sqlite3_errmsg(pDest)); close_db(pDest); return 1; } @@ -8219,7 +8005,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( rc==SQLITE_DONE ){ rc = 0; }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + eputf("Error: %s\n", sqlite3_errmsg(pDest)); rc = 1; } close_db(pDest); @@ -8230,7 +8016,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ bail_on_error = booleanValue(azArg[1]); }else{ - raw_printf(stderr, "Usage: .bail on|off\n"); + eputz("Usage: .bail on|off\n"); rc = 1; } }else @@ -8244,9 +8030,8 @@ static int do_meta_command(char *zLine, ShellState *p){ setTextMode(p->out, 1); } }else{ - raw_printf(stderr, "The \".binary\" command is deprecated." - " Use \".crnl\" instead.\n"); - raw_printf(stderr, "Usage: .binary on|off\n"); + eputz("The \".binary\" command is deprecated. Use \".crnl\" instead.\n" + "Usage: .binary on|off\n"); rc = 1; } }else @@ -8270,11 +8055,11 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = chdir(azArg[1]); #endif if( rc ){ - utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]); + eputf("Cannot change to directory \"%s\"\n", azArg[1]); rc = 1; } }else{ - raw_printf(stderr, "Usage: .cd DIRECTORY\n"); + eputz("Usage: .cd DIRECTORY\n"); rc = 1; } }else @@ -8284,7 +8069,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); + eputz("Usage: .changes on|off\n"); rc = 1; } }else @@ -8298,17 +8083,16 @@ static int do_meta_command(char *zLine, ShellState *p){ char *zRes = 0; output_reset(p); if( nArg!=2 ){ - raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); + eputz("Usage: .check GLOB-PATTERN\n"); rc = 2; }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ rc = 2; }else if( testcase_glob(azArg[1],zRes)==0 ){ - utf8_printf(stderr, - "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", - p->zTestcase, azArg[1], zRes); + eputf("testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + p->zTestcase, azArg[1], zRes); rc = 1; }else{ - utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); + oputf("testcase-%s ok\n", p->zTestcase); p->nCheck++; } sqlite3_free(zRes); @@ -8321,7 +8105,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ tryToClone(p, azArg[1]); }else{ - raw_printf(stderr, "Usage: .clone FILENAME\n"); + eputz("Usage: .clone FILENAME\n"); rc = 1; } }else @@ -8341,9 +8125,9 @@ static int do_meta_command(char *zLine, ShellState *p){ zFile = "(temporary-file)"; } if( p->pAuxDb == &p->aAuxDb[i] ){ - utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile); + sputf(stdout, "ACTIVE %d: %s\n", i, zFile); }else if( p->aAuxDb[i].db!=0 ){ - utf8_printf(stdout, " %d: %s\n", i, zFile); + sputf(stdout, " %d: %s\n", i, zFile); } } }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ @@ -8360,7 +8144,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( i<0 || i>=ArraySize(p->aAuxDb) ){ /* No-op */ }else if( p->pAuxDb == &p->aAuxDb[i] ){ - raw_printf(stderr, "cannot close the active database connection\n"); + eputz("cannot close the active database connection\n"); rc = 1; }else if( p->aAuxDb[i].db ){ session_close_all(p, i); @@ -8368,7 +8152,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->aAuxDb[i].db = 0; } }else{ - raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); + eputz("Usage: .connection [close] [CONNECTION-NUMBER]\n"); rc = 1; } }else @@ -8382,9 +8166,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else{ #if !defined(_WIN32) && !defined(WIN32) - raw_printf(stderr, "The \".crnl\" is a no-op on non-Windows machines.\n"); + eputz("The \".crnl\" is a no-op on non-Windows machines.\n"); #endif - raw_printf(stderr, "Usage: .crnl on|off\n"); + eputz("Usage: .crnl on|off\n"); rc = 1; } }else @@ -8397,7 +8181,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); rc = 1; }else{ while( sqlite3_step(pStmt)==SQLITE_ROW ){ @@ -8416,11 +8200,9 @@ static int do_meta_command(char *zLine, ShellState *p){ int eTxn = sqlite3_txn_state(p->db, azName[i*2]); int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); const char *z = azName[i*2+1]; - utf8_printf(p->out, "%s: %s %s%s\n", - azName[i*2], - z && z[0] ? z : "\"\"", - bRdonly ? "r/o" : "r/w", - eTxn==SQLITE_TXN_NONE ? "" : + oputf("%s: %s %s%s\n", + azName[i*2], z && z[0] ? z : "\"\"", bRdonly ? "r/o" : "r/w", + eTxn==SQLITE_TXN_NONE ? "" : eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); free(azName[i*2]); free(azName[i*2+1]); @@ -8460,12 +8242,12 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); } sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); + oputf("%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ - utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); - utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); + eputf("Error: unknown dbconfig \"%s\"\n", azArg[1]); + eputz("Enter \".dbconfig\" with no arguments for a list\n"); } }else @@ -8495,8 +8277,8 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ) z++; if( cli_strcmp(z,"preserve-rowids")==0 ){ #ifdef SQLITE_OMIT_VIRTUALTABLE - raw_printf(stderr, "The --preserve-rowids option is not compatible" - " with SQLITE_OMIT_VIRTUALTABLE\n"); + eputz("The --preserve-rowids option is not compatible" + " with SQLITE_OMIT_VIRTUALTABLE\n"); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -8514,7 +8296,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ShellSetFlag(p, SHFLG_DumpNoSys); }else { - raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]); + eputf("Unknown option \"%s\" on \".dump\"\n", azArg[i]); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -8548,8 +8330,8 @@ static int do_meta_command(char *zLine, ShellState *p){ /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ - raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); - raw_printf(p->out, "BEGIN TRANSACTION;\n"); + oputz("PRAGMA foreign_keys=OFF;\n"); + oputz("BEGIN TRANSACTION;\n"); } p->writableSchema = 0; p->showHeader = 0; @@ -8580,13 +8362,13 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_free(zLike); if( p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); + oputz("PRAGMA writable_schema=OFF;\n"); p->writableSchema = 0; } sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); + oputz(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); } p->showHeader = savedShowHeader; p->shellFlgs = savedShellFlags; @@ -8596,7 +8378,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ setOrClearFlag(p, SHFLG_Echo, azArg[1]); }else{ - raw_printf(stderr, "Usage: .echo on|off\n"); + eputz("Usage: .echo on|off\n"); rc = 1; } }else @@ -8627,7 +8409,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->autoEQP = (u8)booleanValue(azArg[1]); } }else{ - raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); + eputz("Usage: .eqp off|on|trace|trigger|full\n"); rc = 1; } }else @@ -8666,9 +8448,8 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_OMIT_VIRTUALTABLE if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ - raw_printf(stderr, - "Cannot run experimental commands such as \"%s\" in safe mode\n", - azArg[0]); + eputf("Cannot run experimental commands such as \"%s\" in safe mode\n", + azArg[0]); rc = 1; }else{ open_db(p, 0); @@ -8724,10 +8505,9 @@ static int do_meta_command(char *zLine, ShellState *p){ /* --help lists all file-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available file-controls:\n"); + oputz("Available file-controls:\n"); for(i=0; iout, " .filectrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); + oputf(" .filectrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; goto meta_command_exit; @@ -8742,16 +8522,16 @@ static int do_meta_command(char *zLine, ShellState *p){ filectrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n" - "Use \".filectrl --help\" for help\n", zCmd); + eputf("Error: ambiguous file-control: \"%s\"\n" + "Use \".filectrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; } } } if( filectrl<0 ){ - utf8_printf(stderr,"Error: unknown file-control: %s\n" - "Use \".filectrl --help\" for help\n", zCmd); + eputf("Error: unknown file-control: %s\n" + "Use \".filectrl --help\" for help\n", zCmd); }else{ switch(filectrl){ case SQLITE_FCNTL_SIZE_LIMIT: { @@ -8794,7 +8574,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg!=2 ) break; sqlite3_file_control(p->db, zSchema, filectrl, &z); if( z ){ - utf8_printf(p->out, "%s\n", z); + oputf("%s\n", z); sqlite3_free(z); } isOk = 2; @@ -8808,19 +8588,19 @@ static int do_meta_command(char *zLine, ShellState *p){ } x = -1; sqlite3_file_control(p->db, zSchema, filectrl, &x); - utf8_printf(p->out,"%d\n", x); + oputf("%d\n", x); isOk = 2; break; } } } if( isOk==0 && iCtrl>=0 ){ - utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + oputf("Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ char zBuf[100]; sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); - raw_printf(p->out, "%s\n", zBuf); + oputf("%s\n", zBuf); } }else @@ -8835,7 +8615,7 @@ static int do_meta_command(char *zLine, ShellState *p){ nArg = 1; } if( nArg!=1 ){ - raw_printf(stderr, "Usage: .fullschema ?--indent?\n"); + eputz("Usage: .fullschema ?--indent?\n"); rc = 1; goto meta_command_exit; } @@ -8861,15 +8641,15 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( doStats==0 ){ - raw_printf(p->out, "/* No STAT tables available */\n"); + oputz("/* No STAT tables available */\n"); }else{ - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + oputz("ANALYZE sqlite_schema;\n"); data.cMode = data.mode = MODE_Insert; data.zDestTable = "sqlite_stat1"; shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); data.zDestTable = "sqlite_stat4"; shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + oputz("ANALYZE sqlite_schema;\n"); } }else @@ -8878,7 +8658,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->showHeader = booleanValue(azArg[1]); p->shellFlgs |= SHFLG_HeaderSet; }else{ - raw_printf(stderr, "Usage: .headers on|off\n"); + eputz("Usage: .headers on|off\n"); rc = 1; } }else @@ -8887,7 +8667,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg>=2 ){ n = showHelp(p->out, azArg[1]); if( n==0 ){ - utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); + oputf("Nothing matches '%s'\n", azArg[1]); } }else{ showHelp(p->out, 0); @@ -8931,7 +8711,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( zTable==0 ){ zTable = z; }else{ - utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z); + oputf("ERROR: extra argument: \"%s\". Usage:\n", z); showHelp(p->out, "import"); goto meta_command_exit; } @@ -8952,14 +8732,14 @@ static int do_meta_command(char *zLine, ShellState *p){ xRead = csv_read_one_field; useOutputMode = 0; }else{ - utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z); + oputf("ERROR: unknown option: \"%s\". Usage:\n", z); showHelp(p->out, "import"); goto meta_command_exit; } } if( zTable==0 ){ - utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n", - zFile==0 ? "FILE" : "TABLE"); + oputf("ERROR: missing %s argument. Usage:\n", + zFile==0 ? "FILE" : "TABLE"); showHelp(p->out, "import"); goto meta_command_exit; } @@ -8970,20 +8750,17 @@ static int do_meta_command(char *zLine, ShellState *p){ ** the column and row separator characters from the output mode. */ nSep = strlen30(p->colSeparator); if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null column separator required for import\n"); + eputz("Error: non-null column separator required for import\n"); goto meta_command_exit; } if( nSep>1 ){ - raw_printf(stderr, - "Error: multi-character column separators not allowed" + eputz("Error: multi-character column separators not allowed" " for import\n"); goto meta_command_exit; } nSep = strlen30(p->rowSeparator); if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null row separator required for import\n"); + eputz("Error: non-null row separator required for import\n"); goto meta_command_exit; } if( nSep==2 && p->mode==MODE_Csv @@ -8997,8 +8774,8 @@ static int do_meta_command(char *zLine, ShellState *p){ nSep = strlen30(p->rowSeparator); } if( nSep>1 ){ - raw_printf(stderr, "Error: multi-character row separators not allowed" - " for import\n"); + eputz("Error: multi-character row separators not allowed" + " for import\n"); goto meta_command_exit; } sCtx.cColSep = (u8)p->colSeparator[0]; @@ -9008,7 +8785,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sCtx.nLine = 1; if( sCtx.zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + eputz("Error: pipes are not supported in this OS\n"); goto meta_command_exit; #else sCtx.in = popen(sCtx.zFile+1, "r"); @@ -9020,19 +8797,19 @@ static int do_meta_command(char *zLine, ShellState *p){ sCtx.xCloser = fclose; } if( sCtx.in==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); + eputf("Error: cannot open \"%s\"\n", zFile); goto meta_command_exit; } if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ char zSep[2]; zSep[1] = 0; zSep[0] = sCtx.cColSep; - utf8_printf(p->out, "Column separator "); - output_c_string(p->out, zSep); - utf8_printf(p->out, ", row separator "); + oputz("Column separator "); + output_c_string(zSep); + oputz(", row separator "); zSep[0] = sCtx.cRowSep; - output_c_string(p->out, zSep); - utf8_printf(p->out, "\n"); + output_c_string(zSep); + oputz("\n"); } sCtx.z = sqlite3_malloc64(120); if( sCtx.z==0 ){ @@ -9067,14 +8844,14 @@ static int do_meta_command(char *zLine, ShellState *p){ } zColDefs = zAutoColumn(0, &dbCols, &zRenames); if( zRenames!=0 ){ - utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, - "Columns renamed during .import %s due to duplicates:\n" - "%s\n", sCtx.zFile, zRenames); + sputf((stdin_is_interactive && p->in==stdin)? p->out : stderr, + "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); sqlite3_free(zRenames); } assert(dbCols==0); if( zColDefs==0 ){ - utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); + eputf("%s: empty file\n", sCtx.zFile); import_fail: sqlite3_free(zCreate); sqlite3_free(zSql); @@ -9085,11 +8862,11 @@ static int do_meta_command(char *zLine, ShellState *p){ } zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); if( eVerbose>=1 ){ - utf8_printf(p->out, "%s\n", zCreate); + oputf("%s\n", zCreate); } rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); if( rc ){ - utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); + eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); goto import_fail; } sqlite3_free(zCreate); @@ -9098,7 +8875,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } if( rc ){ if (pStmt) sqlite3_finalize(pStmt); - utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); goto import_fail; } sqlite3_free(zSql); @@ -9120,11 +8897,11 @@ static int do_meta_command(char *zLine, ShellState *p){ zSql[j++] = ')'; zSql[j] = 0; if( eVerbose>=2 ){ - utf8_printf(p->out, "Insert using: %s\n", zSql); + oputf("Insert using: %s\n", zSql); } rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); if (pStmt) sqlite3_finalize(pStmt); goto import_fail; } @@ -9157,9 +8934,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); if( i=nCol ){ sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, - startLine, sqlite3_errmsg(p->db)); + eputf("%s:%d: INSERT failed: %s\n", + sCtx.zFile, startLine, sqlite3_errmsg(p->db)); sCtx.nErr++; }else{ sCtx.nRow++; @@ -9190,9 +8966,8 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); if( eVerbose>0 ){ - utf8_printf(p->out, - "Added %d rows with %d errors using %d lines of input\n", - sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + oputf("Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1); } }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ @@ -9207,14 +8982,14 @@ static int do_meta_command(char *zLine, ShellState *p){ int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ int i; if( !ShellHasFlag(p,SHFLG_TestingMode) ){ - utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", - "imposter"); + eputf(".%s unavailable without --unsafe-testing\n", + "imposter"); rc = 1; goto meta_command_exit; } if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ - utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n" - " .imposter off\n"); + eputz("Usage: .imposter INDEX IMPOSTER\n" + " .imposter off\n"); /* Also allowed, but not documented: ** ** .imposter TABLE IMPOSTER @@ -9273,7 +9048,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_finalize(pStmt); if( i==0 || tnum==0 ){ - utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]); + eputf("no such index: \"%s\"\n", azArg[1]); rc = 1; sqlite3_free(zCollist); goto meta_command_exit; @@ -9288,16 +9063,14 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); if( rc ){ - utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); + eputf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); }else{ - utf8_printf(stdout, "%s;\n", zSql); - raw_printf(stdout, - "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n", - azArg[1], isWO ? "table" : "index" - ); + sputf(stdout, "%s;\n", zSql); + sputf(stdout, "WARNING: writing to an imposter table will corrupt" + " the \"%s\" %s!\n", azArg[1], isWO ? "table" : "index"); } }else{ - raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + eputf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); rc = 1; } sqlite3_free(zSql); @@ -9317,7 +9090,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ iotrace = fopen(azArg[1], "w"); if( iotrace==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); + eputf("Error: cannot open \"%s\"\n", azArg[1]); sqlite3IoTrace = 0; rc = 1; }else{ @@ -9349,11 +9122,11 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); if( nArg==1 ){ for(i=0; idb, aLimit[i].limitCode, -1)); + sputf(stdout, "%20s %d\n", aLimit[i].zLimitName, + sqlite3_limit(p->db, aLimit[i].limitCode, -1)); } }else if( nArg>3 ){ - raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n"); + eputz("Usage: .limit NAME ?NEW-VALUE?\n"); rc = 1; goto meta_command_exit; }else{ @@ -9364,16 +9137,16 @@ static int do_meta_command(char *zLine, ShellState *p){ if( iLimit<0 ){ iLimit = i; }else{ - utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]); + eputf("ambiguous limit: \"%s\"\n", azArg[1]); rc = 1; goto meta_command_exit; } } } if( iLimit<0 ){ - utf8_printf(stderr, "unknown limit: \"%s\"\n" - "enter \".limits\" with no arguments for a list.\n", - azArg[1]); + eputf("unknown limit: \"%s\"\n" + "enter \".limits\" with no arguments for a list.\n", + azArg[1]); rc = 1; goto meta_command_exit; } @@ -9381,8 +9154,8 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_limit(p->db, aLimit[iLimit].limitCode, (int)integerValue(azArg[2])); } - printf("%20s %d\n", aLimit[iLimit].zLimitName, - sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); + sputf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } }else @@ -9398,7 +9171,7 @@ static int do_meta_command(char *zLine, ShellState *p){ failIfSafeMode(p, "cannot run .load in safe mode"); if( nArg<2 || azArg[1][0]==0 ){ /* Must have a non-empty FILE. (Will not load self.) */ - raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); + eputz("Usage: .load FILE ?ENTRYPOINT?\n"); rc = 1; goto meta_command_exit; } @@ -9407,7 +9180,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; } @@ -9416,7 +9189,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){ if( nArg!=2 ){ - raw_printf(stderr, "Usage: .log FILENAME\n"); + eputz("Usage: .log FILENAME\n"); rc = 1; }else{ const char *zFile = azArg[1]; @@ -9424,8 +9197,8 @@ static int do_meta_command(char *zLine, ShellState *p){ && cli_strcmp(zFile,"on")!=0 && cli_strcmp(zFile,"off")!=0 ){ - raw_printf(stdout, "cannot set .log to anything other " - "than \"on\" or \"off\"\n"); + sputz(stdout, "cannot set .log to anything other" + " than \"on\" or \"off\"\n"); zFile = "off"; } output_file_close(p->pLog); @@ -9464,17 +9237,17 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( zTabname==0 ){ zTabname = z; }else if( z[0]=='-' ){ - utf8_printf(stderr, "unknown option: %s\n", z); - utf8_printf(stderr, "options:\n" - " --noquote\n" - " --quote\n" - " --wordwrap on/off\n" - " --wrap N\n" - " --ww\n"); + eputf("unknown option: %s\n", z); + eputz("options:\n" + " --noquote\n" + " --quote\n" + " --wordwrap on/off\n" + " --wrap N\n" + " --ww\n"); rc = 1; goto meta_command_exit; }else{ - utf8_printf(stderr, "extra argument: \"%s\"\n", z); + eputf("extra argument: \"%s\"\n", z); rc = 1; goto meta_command_exit; } @@ -9483,14 +9256,12 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->mode==MODE_Column || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){ - raw_printf - (p->out, - "current output mode: %s --wrap %d --wordwrap %s --%squote\n", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); + oputf("current output mode: %s --wrap %d --wordwrap %s --%squote\n", + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); }else{ - raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]); + oputf("current output mode: %s\n", modeDescr[p->mode]); } zMode = modeDescr[p->mode]; } @@ -9549,9 +9320,9 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( cli_strncmp(zMode,"json",n2)==0 ){ p->mode = MODE_Json; }else{ - raw_printf(stderr, "Error: mode should be one of: " - "ascii box column csv html insert json line list markdown " - "qbox quote table tabs tcl\n"); + eputz("Error: mode should be one of: " + "ascii box column csv html insert json line list markdown " + "qbox quote table tabs tcl\n"); rc = 1; } p->cMode = p->mode; @@ -9560,11 +9331,11 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_SHELL_FIDDLE if( c=='n' && cli_strcmp(azArg[0], "nonce")==0 ){ if( nArg!=2 ){ - raw_printf(stderr, "Usage: .nonce NONCE\n"); + eputz("Usage: .nonce NONCE\n"); rc = 1; }else if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){ - raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", - p->lineno, azArg[1]); + eputf("line %d: incorrect nonce: \"%s\"\n", + p->lineno, azArg[1]); exit(1); }else{ p->bSafeMode = 0; @@ -9579,7 +9350,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); }else{ - raw_printf(stderr, "Usage: .nullvalue STRING\n"); + eputz("Usage: .nullvalue STRING\n"); rc = 1; } }else @@ -9618,11 +9389,11 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif /* !SQLITE_SHELL_FIDDLE */ if( z[0]=='-' ){ - utf8_printf(stderr, "unknown option: %s\n", z); + eputf("unknown option: %s\n", z); rc = 1; goto meta_command_exit; }else if( zFN ){ - utf8_printf(stderr, "extra argument: \"%s\"\n", z); + eputf("extra argument: \"%s\"\n", z); rc = 1; goto meta_command_exit; }else{ @@ -9664,7 +9435,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->pAuxDb->zDbFilename = zNewFilename; open_db(p, OPEN_DB_KEEPALIVE); if( p->db==0 ){ - utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); + eputf("Error: cannot open '%s'\n", zNewFilename); sqlite3_free(zNewFilename); }else{ p->pAuxDb->zFreeOnClose = zNewFilename; @@ -9688,9 +9459,9 @@ static int do_meta_command(char *zLine, ShellState *p){ int i; int eMode = 0; int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ - unsigned char zBOM[4]; /* Byte-order mark to using if --bom is present */ + static const char *zBomUtf8 = "\xef\xbb\xbf"; + const char *zBom = 0; - zBOM[0] = 0; failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); if( c=='e' ){ eMode = 'x'; @@ -9703,17 +9474,13 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ){ if( z[1]=='-' ) z++; if( cli_strcmp(z,"-bom")==0 ){ - zBOM[0] = 0xef; - zBOM[1] = 0xbb; - zBOM[2] = 0xbf; - zBOM[3] = 0; + zBom = zBomUtf8; }else if( c!='e' && cli_strcmp(z,"-x")==0 ){ eMode = 'x'; /* spreadsheet */ }else if( c!='e' && cli_strcmp(z,"-e")==0 ){ eMode = 'e'; /* text editor */ }else{ - utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", - azArg[i]); + oputf("ERROR: unknown option: \"%s\". Usage:\n", azArg[i]); showHelp(p->out, azArg[0]); rc = 1; goto meta_command_exit; @@ -9725,8 +9492,7 @@ static int do_meta_command(char *zLine, ShellState *p){ break; } }else{ - utf8_printf(p->out,"ERROR: extra parameter: \"%s\". Usage:\n", - azArg[i]); + oputf("ERROR: extra parameter: \"%s\". Usage:\n", azArg[i]); showHelp(p->out, azArg[0]); rc = 1; sqlite3_free(zFile); @@ -9765,30 +9531,30 @@ static int do_meta_command(char *zLine, ShellState *p){ shell_check_oom(zFile); if( zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + eputz("Error: pipes are not supported in this OS\n"); rc = 1; - p->out = stdout; + output_redir(p, stdout); #else - p->out = popen(zFile + 1, "w"); - if( p->out==0 ){ - utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); - p->out = stdout; + FILE *pfPipe = popen(zFile + 1, "w"); + if( pfPipe==0 ){ + eputf("Error: cannot open pipe \"%s\"\n", zFile + 1); rc = 1; }else{ - if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); + output_redir(p, pfPipe); + if( zBom ) oputz(zBom); sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } #endif }else{ - p->out = output_file_open(zFile, bTxtMode); - if( p->out==0 ){ + FILE *pfFile = output_file_open(zFile, bTxtMode); + if( pfFile==0 ){ if( cli_strcmp(zFile,"off")!=0 ){ - utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); + eputf("Error: cannot write to \"%s\"\n", zFile); } - p->out = stdout; rc = 1; } else { - if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); + output_redir(p, pfFile); + if( zBom ) oputz(zBom); sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } } @@ -9829,8 +9595,8 @@ static int do_meta_command(char *zLine, ShellState *p){ "SELECT key, quote(value) " "FROM temp.sqlite_parameters;", -1, &pStmt, 0); while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), - sqlite3_column_text(pStmt,1)); + oputf("%-*s %s\n", len, sqlite3_column_text(pStmt,0), + sqlite3_column_text(pStmt,1)); } sqlite3_finalize(pStmt); } @@ -9874,7 +9640,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rx!=SQLITE_OK ){ - utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); + oputf("Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); pStmt = 0; rc = 1; @@ -9903,10 +9669,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){ int i; for(i=1; i1 ) raw_printf(p->out, " "); - utf8_printf(p->out, "%s", azArg[i]); + if( i>1 ) oputz(" "); + oputz(azArg[i]); } - raw_printf(p->out, "\n"); + oputz("\n"); }else #ifndef SQLITE_OMIT_PROGRESS_CALLBACK @@ -9935,7 +9701,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } if( cli_strcmp(z,"limit")==0 ){ if( i+1>=nArg ){ - utf8_printf(stderr, "Error: missing argument on --limit\n"); + eputz("Error: missing argument on --limit\n"); rc = 1; goto meta_command_exit; }else{ @@ -9943,7 +9709,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } continue; } - utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]); + eputf("Error: unknown option: \"%s\"\n", azArg[i]); rc = 1; goto meta_command_exit; }else{ @@ -9976,19 +9742,19 @@ static int do_meta_command(char *zLine, ShellState *p){ int savedLineno = p->lineno; failIfSafeMode(p, "cannot run .read in safe mode"); if( nArg!=2 ){ - raw_printf(stderr, "Usage: .read FILE\n"); + eputz("Usage: .read FILE\n"); rc = 1; goto meta_command_exit; } if( azArg[1][0]=='|' ){ #ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + eputz("Error: pipes are not supported in this OS\n"); rc = 1; p->out = stdout; #else p->in = popen(azArg[1]+1, "r"); if( p->in==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); + eputf("Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p); @@ -9996,7 +9762,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } #endif }else if( (p->in = openChrSource(azArg[1]))==0 ){ - utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + eputf("Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p); @@ -10023,20 +9789,20 @@ static int do_meta_command(char *zLine, ShellState *p){ zSrcFile = azArg[2]; zDb = azArg[1]; }else{ - raw_printf(stderr, "Usage: .restore ?DB? FILE\n"); + eputz("Usage: .restore ?DB? FILE\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_open(zSrcFile, &pSrc); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile); + eputf("Error: cannot open \"%s\"\n", zSrcFile); close_db(pSrc); return 1; } open_db(p, 0); pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main"); if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); close_db(pSrc); return 1; } @@ -10051,10 +9817,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( rc==SQLITE_DONE ){ rc = 0; }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ - raw_printf(stderr, "Error: source database is busy\n"); + eputz("Error: source database is busy\n"); rc = 1; }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); rc = 1; } close_db(pSrc); @@ -10075,11 +9841,15 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_db_config( p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 ); -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS - raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); +#if !defined(SQLITE_ENABLE_STMT_SCANSTATUS) + eputz("Warning: .scanstats not available in this build.\n"); +#elif !defined(SQLITE_ENABLE_BYTECODE_VTAB) + if( p->scanstatsOn==3 ){ + eputz("Warning: \".scanstats vm\" not available in this build.\n"); + } #endif }else{ - raw_printf(stderr, "Usage: .scanstats on|off|est\n"); + eputz("Usage: .scanstats on|off|est\n"); rc = 1; } }else @@ -10108,14 +9878,13 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( optionMatch(azArg[ii],"nosys") ){ bNoSystemTabs = 1; }else if( azArg[ii][0]=='-' ){ - utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]); + eputf("Unknown option: \"%s\"\n", azArg[ii]); rc = 1; goto meta_command_exit; }else if( zName==0 ){ zName = azArg[ii]; }else{ - raw_printf(stderr, - "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); + eputz("Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } @@ -10148,7 +9917,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list", -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); rc = 1; goto meta_command_exit; @@ -10210,18 +9979,18 @@ static int do_meta_command(char *zLine, ShellState *p){ appendText(&sSelect, "sql IS NOT NULL" " ORDER BY snum, rowid", 0); if( bDebug ){ - utf8_printf(p->out, "SQL: %s;\n", sSelect.z); + oputf("SQL: %s;\n", sSelect.z); }else{ rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); } freeText(&sSelect); } if( zErrMsg ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; }else if( rc != SQLITE_OK ){ - raw_printf(stderr,"Error: querying schema information\n"); + eputz("Error: querying schema information\n"); rc = 1; }else{ rc = 0; @@ -10267,11 +10036,11 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nCmd!=2 ) goto session_syntax_error; if( pSession->p==0 ){ session_not_open: - raw_printf(stderr, "ERROR: No sessions are open\n"); + eputz("ERROR: No sessions are open\n"); }else{ rc = sqlite3session_attach(pSession->p, azCmd[1]); if( rc ){ - raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc); + eputf("ERROR: sqlite3session_attach() returns %d\n",rc); rc = 0; } } @@ -10290,8 +10059,8 @@ static int do_meta_command(char *zLine, ShellState *p){ if( pSession->p==0 ) goto session_not_open; out = fopen(azCmd[1], "wb"); if( out==0 ){ - utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n", - azCmd[1]); + eputf("ERROR: cannot open \"%s\" for writing\n", + azCmd[1]); }else{ int szChng; void *pChng; @@ -10301,13 +10070,12 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); } if( rc ){ - printf("Error: error code %d\n", rc); + sputf(stdout, "Error: error code %d\n", rc); rc = 0; } if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){ - raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n", - szChng); + eputf("ERROR: Failed to write entire %d-byte output\n", szChng); } sqlite3_free(pChng); fclose(out); @@ -10334,8 +10102,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_enable(pSession->p, ii); - utf8_printf(p->out, "session %s enable flag = %d\n", - pSession->zName, ii); + oputf("session %s enable flag = %d\n", pSession->zName, ii); } }else @@ -10352,10 +10119,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(pSession->azFilter); nByte = sizeof(pSession->azFilter[0])*(nCmd-1); pSession->azFilter = sqlite3_malloc( nByte ); - if( pSession->azFilter==0 ){ - raw_printf(stderr, "Error: out or memory\n"); - exit(1); - } + shell_check_oom( pSession->azFilter ); for(ii=1; iiazFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); shell_check_oom(x); @@ -10373,8 +10137,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_indirect(pSession->p, ii); - utf8_printf(p->out, "session %s indirect flag = %d\n", - pSession->zName, ii); + oputf("session %s indirect flag = %d\n", pSession->zName, ii); } }else @@ -10386,8 +10149,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nCmd!=1 ) goto session_syntax_error; if( pAuxDb->nSession ){ ii = sqlite3session_isempty(pSession->p); - utf8_printf(p->out, "session %s isempty flag = %d\n", - pSession->zName, ii); + oputf("session %s isempty flag = %d\n", pSession->zName, ii); } }else @@ -10396,7 +10158,7 @@ static int do_meta_command(char *zLine, ShellState *p){ */ if( cli_strcmp(azCmd[0],"list")==0 ){ for(i=0; inSession; i++){ - utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); + oputf("%d %s\n", i, pAuxDb->aSession[i].zName); } }else @@ -10411,19 +10173,18 @@ static int do_meta_command(char *zLine, ShellState *p){ if( zName[0]==0 ) goto session_syntax_error; for(i=0; inSession; i++){ if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ - utf8_printf(stderr, "Session \"%s\" already exists\n", zName); + eputf("Session \"%s\" already exists\n", zName); goto meta_command_exit; } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ - raw_printf(stderr, - "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); + eputf("Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); goto meta_command_exit; } pSession = &pAuxDb->aSession[pAuxDb->nSession]; rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); if( rc ){ - raw_printf(stderr, "Cannot open session: error code=%d\n", rc); + eputf("Cannot open session: error code=%d\n", rc); rc = 0; goto meta_command_exit; } @@ -10447,7 +10208,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int i, v; for(i=1; iout, "%s: %d 0x%x\n", azArg[i], v, v); + oputf("%s: %d 0x%x\n", azArg[i], v, v); } } if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){ @@ -10456,7 +10217,7 @@ static int do_meta_command(char *zLine, ShellState *p){ char zBuf[200]; v = integerValue(azArg[i]); sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v); - utf8_printf(p->out, "%s", zBuf); + oputz(zBuf); } } }else @@ -10483,9 +10244,8 @@ static int do_meta_command(char *zLine, ShellState *p){ bVerbose++; }else { - utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", - azArg[i], azArg[0]); - raw_printf(stderr, "Should be one of: --init -v\n"); + eputf("Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); + eputz("Should be one of: --init -v\n"); rc = 1; goto meta_command_exit; } @@ -10514,7 +10274,7 @@ static int do_meta_command(char *zLine, ShellState *p){ -1, &pStmt, 0); } if( rc ){ - raw_printf(stderr, "Error querying the selftest table\n"); + eputz("Error querying the selftest table\n"); rc = 1; sqlite3_finalize(pStmt); goto meta_command_exit; @@ -10530,10 +10290,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( zAns==0 ) continue; k = 0; if( bVerbose>0 ){ - printf("%d: %s %s\n", tno, zOp, zSql); + sputf(stdout, "%d: %s %s\n", tno, zOp, zSql); } if( cli_strcmp(zOp,"memo")==0 ){ - utf8_printf(p->out, "%s\n", zSql); + oputf("%s\n", zSql); }else if( cli_strcmp(zOp,"run")==0 ){ char *zErrMsg = 0; @@ -10542,23 +10302,22 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); nTest++; if( bVerbose ){ - utf8_printf(p->out, "Result: %s\n", str.z); + oputf("Result: %s\n", str.z); } if( rc || zErrMsg ){ nErr++; rc = 1; - utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg); + oputf("%d: error-code-%d: %s\n", tno, rc, zErrMsg); sqlite3_free(zErrMsg); }else if( cli_strcmp(zAns,str.z)!=0 ){ nErr++; rc = 1; - utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); - utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z); + oputf("%d: Expected: [%s]\n", tno, zAns); + oputf("%d: Got: [%s]\n", tno, str.z); } - }else - { - utf8_printf(stderr, - "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); + } + else{ + eputf("Unknown operation \"%s\" on selftest line %d\n", zOp, tno); rc = 1; break; } @@ -10566,12 +10325,12 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); } /* End loop over k */ freeText(&str); - utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); + oputf("%d errors out of %d tests\n", nErr, nTest); }else if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){ if( nArg<2 || nArg>3 ){ - raw_printf(stderr, "Usage: .separator COL ?ROW?\n"); + eputz("Usage: .separator COL ?ROW?\n"); rc = 1; } if( nArg>=2 ){ @@ -10614,14 +10373,13 @@ static int do_meta_command(char *zLine, ShellState *p){ bDebug = 1; }else { - utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", - azArg[i], azArg[0]); + eputf("Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); showHelp(p->out, azArg[0]); rc = 1; goto meta_command_exit; } }else if( zLike ){ - raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n"); + eputz("Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; }else{ @@ -10693,7 +10451,7 @@ static int do_meta_command(char *zLine, ShellState *p){ freeText(&sQuery); freeText(&sSql); if( bDebug ){ - utf8_printf(p->out, "%s\n", zSql); + oputf("%s\n", zSql); }else{ shell_exec(p, zSql, 0); } @@ -10723,7 +10481,7 @@ static int do_meta_command(char *zLine, ShellState *p){ "' OR ') as query, tname from tabcols group by tname)" , zRevText); shell_check_oom(zRevText); - if( bDebug ) utf8_printf(p->out, "%s\n", zRevText); + if( bDebug ) oputf("%s\n", zRevText); lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); if( lrc!=SQLITE_OK ){ /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the @@ -10736,7 +10494,7 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); sqlite3_stmt *pCheckStmt; lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); - if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery); + if( bDebug ) oputf("%s\n", zGenQuery); if( lrc!=SQLITE_OK ){ rc = 1; }else{ @@ -10744,9 +10502,8 @@ static int do_meta_command(char *zLine, ShellState *p){ double countIrreversible = sqlite3_column_double(pCheckStmt, 0); if( countIrreversible>0 ){ int sz = (int)(countIrreversible + 0.5); - utf8_printf(stderr, - "Digest includes %d invalidly encoded text field%s.\n", - sz, (sz>1)? "s": ""); + eputf("Digest includes %d invalidly encoded text field%s.\n", + sz, (sz>1)? "s": ""); } } sqlite3_finalize(pCheckStmt); @@ -10754,7 +10511,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); } } - if( rc ) utf8_printf(stderr, ".sha3sum failed.\n"); + if( rc ) eputz(".sha3sum failed.\n"); sqlite3_free(zRevText); } #endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */ @@ -10770,7 +10527,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int i, x; failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); if( nArg<2 ){ - raw_printf(stderr, "Usage: .system COMMAND\n"); + eputz("Usage: .system COMMAND\n"); rc = 1; goto meta_command_exit; } @@ -10779,9 +10536,11 @@ static int do_meta_command(char *zLine, ShellState *p){ zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"", zCmd, azArg[i]); } + consoleRestore(); x = zCmd!=0 ? system(zCmd) : 1; + consoleRenewSetup(); sqlite3_free(zCmd); - if( x ) raw_printf(stderr, "System command returns %d\n", x); + if( x ) eputf("System command returns %d\n", x); }else #endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */ @@ -10790,52 +10549,51 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *zOut; int i; if( nArg!=1 ){ - raw_printf(stderr, "Usage: .show\n"); + eputz("Usage: .show\n"); rc = 1; goto meta_command_exit; } - utf8_printf(p->out, "%12.12s: %s\n","echo", - azBool[ShellHasFlag(p, SHFLG_Echo)]); - utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); - utf8_printf(p->out, "%12.12s: %s\n","explain", - p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); - utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]); + oputf("%12.12s: %s\n","echo", + azBool[ShellHasFlag(p, SHFLG_Echo)]); + oputf("%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); + oputf("%12.12s: %s\n","explain", + p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); + oputf("%12.12s: %s\n","headers", azBool[p->showHeader!=0]); if( p->mode==MODE_Column || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){ - utf8_printf - (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); + oputf("%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); }else{ - utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]); - } - utf8_printf(p->out, "%12.12s: ", "nullvalue"); - output_c_string(p->out, p->nullValue); - raw_printf(p->out, "\n"); - utf8_printf(p->out,"%12.12s: %s\n","output", - strlen30(p->outfile) ? p->outfile : "stdout"); - utf8_printf(p->out,"%12.12s: ", "colseparator"); - output_c_string(p->out, p->colSeparator); - raw_printf(p->out, "\n"); - utf8_printf(p->out,"%12.12s: ", "rowseparator"); - output_c_string(p->out, p->rowSeparator); - raw_printf(p->out, "\n"); + oputf("%12.12s: %s\n","mode", modeDescr[p->mode]); + } + oputf("%12.12s: ", "nullvalue"); + output_c_string(p->nullValue); + oputz("\n"); + oputf("%12.12s: %s\n","output", + strlen30(p->outfile) ? p->outfile : "stdout"); + oputf("%12.12s: ", "colseparator"); + output_c_string(p->colSeparator); + oputz("\n"); + oputf("%12.12s: ", "rowseparator"); + output_c_string(p->rowSeparator); + oputz("\n"); switch( p->statsOn ){ case 0: zOut = "off"; break; default: zOut = "on"; break; case 2: zOut = "stmt"; break; case 3: zOut = "vmstep"; break; } - utf8_printf(p->out, "%12.12s: %s\n","stats", zOut); - utf8_printf(p->out, "%12.12s: ", "width"); + oputf("%12.12s: %s\n","stats", zOut); + oputf("%12.12s: ", "width"); for (i=0;inWidth;i++) { - raw_printf(p->out, "%d ", p->colWidth[i]); + oputf("%d ", p->colWidth[i]); } - raw_printf(p->out, "\n"); - utf8_printf(p->out, "%12.12s: %s\n", "filename", - p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); + oputz("\n"); + oputf("%12.12s: %s\n", "filename", + p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); }else if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){ @@ -10850,7 +10608,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( nArg==1 ){ display_stats(p->db, p, 0); }else{ - raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n"); + eputz("Usage: .stats ?on|off|stmt|vmstep?\n"); rc = 1; } }else @@ -10876,7 +10634,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* It is an historical accident that the .indexes command shows an error ** when called with the wrong number of arguments whereas the .tables ** command does not. */ - raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); + eputz("Usage: .indexes ?LIKE-PATTERN?\n"); rc = 1; sqlite3_finalize(pStmt); goto meta_command_exit; @@ -10952,10 +10710,9 @@ static int do_meta_command(char *zLine, ShellState *p){ for(i=0; iout, "%s%-*s", zSp, maxlen, - azResult[j] ? azResult[j]:""); + oputf("%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:""); } - raw_printf(p->out, "\n"); + oputz("\n"); } } @@ -10969,7 +10726,7 @@ static int do_meta_command(char *zLine, ShellState *p){ output_reset(p); p->out = output_file_open("testcase-out.txt", 0); if( p->out==0 ){ - raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n"); + eputz("Error: cannot open 'testcase-out.txt'\n"); } if( nArg>=2 ){ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); @@ -11010,7 +10767,7 @@ static int do_meta_command(char *zLine, ShellState *p){ {"seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, {"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, {"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, - {"uselongdouble", SQLITE_TESTCTRL_USELONGDOUBLE,0,"?BOOLEAN|\"default\"?"}, + {"uselongdouble", SQLITE_TESTCTRL_USELONGDOUBLE,0,"?BOOLEAN|\"default\"?"}, }; int testctrl = -1; int iCtrl = -1; @@ -11030,11 +10787,11 @@ static int do_meta_command(char *zLine, ShellState *p){ /* --help lists all test-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available test-controls:\n"); + oputz("Available test-controls:\n"); for(i=0; iout, " .testctrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); + oputf(" .testctrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; goto meta_command_exit; @@ -11050,16 +10807,16 @@ static int do_meta_command(char *zLine, ShellState *p){ testctrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n" - "Use \".testctrl --help\" for help\n", zCmd); + eputf("Error: ambiguous test-control: \"%s\"\n" + "Use \".testctrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; } } } if( testctrl<0 ){ - utf8_printf(stderr,"Error: unknown test-control: %s\n" - "Use \".testctrl --help\" for help\n", zCmd); + eputf("Error: unknown test-control: %s\n" + "Use \".testctrl --help\" for help\n", zCmd); }else{ switch(testctrl){ @@ -11099,7 +10856,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3 *db; if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){ sqlite3_randomness(sizeof(ii),&ii); - printf("-- random seed: %d\n", ii); + sputf(stdout, "-- random seed: %d\n", ii); } if( nArg==3 ){ db = 0; @@ -11167,7 +10924,7 @@ static int do_meta_command(char *zLine, ShellState *p){ case SQLITE_TESTCTRL_SEEK_COUNT: { u64 x = 0; rc2 = sqlite3_test_control(testctrl, p->db, &x); - utf8_printf(p->out, "%llu\n", x); + oputf("%llu\n", x); isOk = 3; break; } @@ -11198,11 +10955,11 @@ static int do_meta_command(char *zLine, ShellState *p){ int val = 0; rc2 = sqlite3_test_control(testctrl, -id, &val); if( rc2!=SQLITE_OK ) break; - if( id>1 ) utf8_printf(p->out, " "); - utf8_printf(p->out, "%d: %d", id, val); + if( id>1 ) oputz(" "); + oputf("%d: %d", id, val); id++; } - if( id>1 ) utf8_printf(p->out, "\n"); + if( id>1 ) oputz("\n"); isOk = 3; } break; @@ -11218,12 +10975,12 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( isOk==0 && iCtrl>=0 ){ - utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + oputf("Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ - raw_printf(p->out, "%d\n", rc2); + oputf("%d\n", rc2); }else if( isOk==2 ){ - raw_printf(p->out, "0x%08x\n", rc2); + oputf("0x%08x\n", rc2); } }else #endif /* !defined(SQLITE_UNTESTABLE) */ @@ -11237,11 +10994,11 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ enableTimer = booleanValue(azArg[1]); if( enableTimer && !HAS_TIMER ){ - raw_printf(stderr, "Error: timer not available on this system.\n"); + eputz("Error: timer not available on this system.\n"); enableTimer = 0; } }else{ - raw_printf(stderr, "Usage: .timer on|off\n"); + eputz("Usage: .timer on|off\n"); rc = 1; } }else @@ -11278,7 +11035,7 @@ static int do_meta_command(char *zLine, ShellState *p){ mType |= SQLITE_TRACE_CLOSE; } else { - raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z); + eputf("Unknown option \"%s\" on \".trace\"\n", z); rc = 1; goto meta_command_exit; } @@ -11302,7 +11059,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int lenOpt; char *zOpt; if( nArg<2 ){ - raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n"); + eputz("Usage: .unmodule [--allexcept] NAME ...\n"); rc = 1; goto meta_command_exit; } @@ -11324,60 +11081,60 @@ static int do_meta_command(char *zLine, ShellState *p){ #if SQLITE_USER_AUTHENTICATION if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){ if( nArg<2 ){ - raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n"); + eputz("Usage: .user SUBCOMMAND ...\n"); rc = 1; goto meta_command_exit; } open_db(p, 0); if( cli_strcmp(azArg[1],"login")==0 ){ if( nArg!=4 ){ - raw_printf(stderr, "Usage: .user login USER PASSWORD\n"); + eputz("Usage: .user login USER PASSWORD\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], strlen30(azArg[3])); if( rc ){ - utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]); + eputf("Authentication failed for user %s\n", azArg[2]); rc = 1; } }else if( cli_strcmp(azArg[1],"add")==0 ){ if( nArg!=5 ){ - raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n"); + eputz("Usage: .user add USER PASSWORD ISADMIN\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), booleanValue(azArg[4])); if( rc ){ - raw_printf(stderr, "User-Add failed: %d\n", rc); + eputf("User-Add failed: %d\n", rc); rc = 1; } }else if( cli_strcmp(azArg[1],"edit")==0 ){ if( nArg!=5 ){ - raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n"); + eputz("Usage: .user edit USER PASSWORD ISADMIN\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), booleanValue(azArg[4])); if( rc ){ - raw_printf(stderr, "User-Edit failed: %d\n", rc); + eputf("User-Edit failed: %d\n", rc); rc = 1; } }else if( cli_strcmp(azArg[1],"delete")==0 ){ if( nArg!=3 ){ - raw_printf(stderr, "Usage: .user delete USER\n"); + eputz("Usage: .user delete USER\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_delete(p->db, azArg[2]); if( rc ){ - raw_printf(stderr, "User-Delete failed: %d\n", rc); + eputf("User-Delete failed: %d\n", rc); rc = 1; } }else{ - raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n"); + eputz("Usage: .user login|add|edit|delete ...\n"); rc = 1; goto meta_command_exit; } @@ -11386,21 +11143,21 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; - utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, - sqlite3_libversion(), sqlite3_sourceid()); + oputf("SQLite %s %s\n" /*extra-version-info*/, + sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB - utf8_printf(p->out, "zlib version %s\n", zlibVersion()); + oputf("zlib version %s\n", zlibVersion()); #endif #define CTIMEOPT_VAL_(opt) #opt #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) #if defined(__clang__) && defined(__clang_major__) - utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." - CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); + oputf("clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); #elif defined(_MSC_VER) - utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); + oputf("msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); #elif defined(__GNUC__) && defined(__VERSION__) - utf8_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); + oputf("gcc-" __VERSION__ " (%s)\n", zPtrSz); #endif }else @@ -11410,10 +11167,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); if( pVfs ){ - utf8_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); - raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + oputf("vfs.zName = \"%s\"\n", pVfs->zName); + oputf("vfs.iVersion = %d\n", pVfs->iVersion); + oputf("vfs.szOsFile = %d\n", pVfs->szOsFile); + oputf("vfs.mxPathname = %d\n", pVfs->mxPathname); } } }else @@ -11425,13 +11182,13 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); } for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ - utf8_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, - pVfs==pCurrent ? " <--- CURRENT" : ""); - raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + oputf("vfs.zName = \"%s\"%s\n", pVfs->zName, + pVfs==pCurrent ? " <--- CURRENT" : ""); + oputf("vfs.iVersion = %d\n", pVfs->iVersion); + oputf("vfs.szOsFile = %d\n", pVfs->szOsFile); + oputf("vfs.mxPathname = %d\n", pVfs->mxPathname); if( pVfs->pNext ){ - raw_printf(p->out, "-----------------------------------\n"); + oputz("-----------------------------------\n"); } } }else @@ -11442,7 +11199,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); if( zVfsName ){ - utf8_printf(p->out, "%s\n", zVfsName); + oputf("%s\n", zVfsName); sqlite3_free(zVfsName); } } @@ -11466,8 +11223,8 @@ static int do_meta_command(char *zLine, ShellState *p){ }else { - utf8_printf(stderr, "Error: unknown command or invalid arguments: " - " \"%s\". Enter \".help\" for help\n", azArg[0]); + eputf("Error: unknown command or invalid arguments: " + " \"%s\". Enter \".help\" for help\n", azArg[0]); rc = 1; } @@ -11657,7 +11414,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ }else{ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); } - utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail); + eputf("%s %s\n", zPrefix, zErrorTail); sqlite3_free(zErrMsg); zErrMsg = 0; return 1; @@ -11666,13 +11423,13 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, "changes: %lld total_changes: %lld", sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); - raw_printf(p->out, "%s\n", zLineBuf); + oputf("%s\n", zLineBuf); } return 0; } static void echo_group_input(ShellState *p, const char *zDo){ - if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); + if( ShellHasFlag(p, SHFLG_Echo) ) oputf("%s\n", zDo); } #ifdef SQLITE_SHELL_FIDDLE @@ -11730,8 +11487,8 @@ static int process_input(ShellState *p){ if( p->inputNesting==MAX_INPUT_NESTING ){ /* This will be more informative in a later version. */ - utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." - " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); + eputf("Input nesting limit (%d) reached at line %d." + " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); return 1; } ++p->inputNesting; @@ -11742,7 +11499,7 @@ static int process_input(ShellState *p){ zLine = one_input_line(p->in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ - if( p->in==0 && stdin_is_interactive ) printf("\n"); + if( p->in==0 && stdin_is_interactive ) oputz("\n"); break; } if( seenInterrupt ){ @@ -11952,8 +11709,8 @@ static void process_sqliterc( if( sqliterc == NULL ){ home_dir = find_home_dir(0); if( home_dir==0 ){ - raw_printf(stderr, "-- warning: cannot find home directory;" - " cannot read ~/.sqliterc\n"); + eputz("-- warning: cannot find home directory;" + " cannot read ~/.sqliterc\n"); return; } zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); @@ -11963,12 +11720,12 @@ static void process_sqliterc( p->in = fopen(sqliterc,"rb"); if( p->in ){ if( stdin_is_interactive ){ - utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc); + eputf("-- Loading resources from %s\n", sqliterc); } if( process_input(p) && bail_on_error ) exit(1); fclose(p->in); }else if( sqliterc_override!=0 ){ - utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc); + eputf("cannot open: \"%s\"\n", sqliterc); if( bail_on_error ) exit(1); } p->in = inSaved; @@ -12018,9 +11775,6 @@ static const char zOptions[] = " -multiplex enable the multiplexor VFS\n" #endif " -newline SEP set output row separator. Default: '\\n'\n" -#if SHELL_WIN_UTF8_OPT - " -no-utf8 do not try to set up UTF-8 output (for legacy)\n" -#endif " -nofollow refuse to open symbolic links to database files\n" " -nonce STRING set the safe-mode escape nonce\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" @@ -12037,9 +11791,6 @@ static const char zOptions[] = " -table set output mode to 'table'\n" " -tabs set output mode to 'tabs'\n" " -unsafe-testing allow unsafe commands and modes for testing\n" -#if SHELL_WIN_UTF8_OPT && 0 /* Option is accepted, but is now the default. */ - " -utf8 setup interactive console code page for UTF-8\n" -#endif " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE @@ -12050,14 +11801,13 @@ static const char zOptions[] = #endif ; static void usage(int showDetail){ - utf8_printf(stderr, - "Usage: %s [OPTIONS] [FILENAME [SQL]]\n" - "FILENAME is the name of an SQLite database. A new database is created\n" - "if the file does not previously exist. Defaults to :memory:.\n", Argv0); + eputf("Usage: %s [OPTIONS] [FILENAME [SQL]]\n" + "FILENAME is the name of an SQLite database. A new database is created\n" + "if the file does not previously exist. Defaults to :memory:.\n", Argv0); if( showDetail ){ - utf8_printf(stderr, "OPTIONS include:\n%s", zOptions); + eputf("OPTIONS include:\n%s", zOptions); }else{ - raw_printf(stderr, "Use the -help option for additional information\n"); + eputz("Use the -help option for additional information\n"); } exit(1); } @@ -12068,8 +11818,8 @@ static void usage(int showDetail){ */ static void verify_uninitialized(void){ if( sqlite3_config(-1)==SQLITE_MISUSE ){ - utf8_printf(stdout, "WARNING: attempt to configure SQLite after" - " initialization.\n"); + sputz(stdout, "WARNING: attempt to configure SQLite after" + " initialization.\n"); } } @@ -12098,7 +11848,7 @@ static void main_init(ShellState *data) { /* ** Output text to the console in a font that attracts extra attention. */ -#ifdef _WIN32 +#if defined(_WIN32) || defined(WIN32) static void printBold(const char *zText){ #if !SQLITE_OS_WINRT HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); @@ -12108,14 +11858,14 @@ static void printBold(const char *zText){ FOREGROUND_RED|FOREGROUND_INTENSITY ); #endif - printf("%s", zText); + oputz(zText); #if !SQLITE_OS_WINRT SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); #endif } #else static void printBold(const char *zText){ - printf("\033[1m%s\033[0m", zText); + oputf("\033[1m%s\033[0m", zText); } #endif @@ -12125,15 +11875,14 @@ static void printBold(const char *zText){ */ static char *cmdline_option_value(int argc, char **argv, int i){ if( i==argc ){ - utf8_printf(stderr, "%s: Error: missing argument to %s\n", - argv[0], argv[argc-1]); + eputf("%s: Error: missing argument to %s\n", argv[0], argv[argc-1]); exit(1); } return argv[i]; } static void sayAbnormalExit(void){ - if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); + if( seenInterrupt ) eputz("Program interrupted.\n"); } #ifndef SQLITE_SHELL_IS_UTF8 @@ -12163,6 +11912,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ # define data shellState #else ShellState data; + StreamsAreConsole consStreams = SAC_NoConsole; #endif const char *zInitFile = 0; int i; @@ -12184,12 +11934,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdout_is_console = 1; data.wasm.zDefaultDbName = "/fiddle.sqlite3"; #else - stdin_is_interactive = isatty(0); - stdout_is_console = isatty(1); -#endif -#if SHELL_WIN_UTF8_OPT - probe_console(); /* Check for console I/O and UTF-8 capability. */ - if( !mbcs_opted ) atexit(console_restore); + consStreams = consoleClassifySetup(stdin, stdout, stderr); + stdin_is_interactive = (consStreams & SAC_InConsole)!=0; + stdout_is_console = (consStreams & SAC_OutConsole)!=0; + atexit(consoleRestore); #endif atexit(sayAbnormalExit); #ifdef SQLITE_DEBUG @@ -12198,9 +11946,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #if !defined(_WIN32_WCE) if( getenv("SQLITE_DEBUG_BREAK") ){ if( isatty(0) && isatty(2) ){ - fprintf(stderr, - "attach debugger to process %d and press any key to continue.\n", - GETPID()); + eputf("attach debugger to process %d and press any key to continue.\n", + GETPID()); fgetc(stdin); }else{ #if defined(_WIN32) || defined(WIN32) @@ -12220,14 +11967,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ signal(SIGINT, interrupt_handler); #elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){ - fprintf(stderr, "No ^C handler.\n"); + eputz("No ^C handler.\n"); } #endif #if USE_SYSTEM_SQLITE+0!=1 if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ - utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", - sqlite3_sourceid(), SQLITE_SOURCE_ID); + eputf("SQLite header and source version mismatch\n%s\n%s\n", + sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); } #endif @@ -12323,14 +12070,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ */ stdin_is_interactive = 0; }else if( cli_strcmp(z,"-utf8")==0 ){ -#if SHELL_WIN_UTF8_OPT - /* Option accepted, but is ignored except for this diagnostic. */ - if( mbcs_opted ) fprintf(stderr, "Cannot do UTF-8 at this console.\n"); -#endif /* SHELL_WIN_UTF8_OPT */ }else if( cli_strcmp(z,"-no-utf8")==0 ){ -#if SHELL_WIN_UTF8_OPT - mbcs_opted = 1; -#endif /* SHELL_WIN_UTF8_OPT */ }else if( cli_strcmp(z,"-heap")==0 ){ #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) const char *zSize; @@ -12465,26 +12205,17 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( pVfs ){ sqlite3_vfs_register(pVfs, 1); }else{ - utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs); + eputf("no such VFS: \"%s\"\n", zVfs); exit(1); } } -#if SHELL_WIN_UTF8_OPT - /* Get indicated Windows console setup done before running invocation commands. */ - if( in_console || out_console ){ - console_prepare_utf8(); - } - if( !in_console ){ - setBinaryMode(stdin, 0); - } -#endif if( data.pAuxDb->zDbFilename==0 ){ #ifndef SQLITE_OMIT_MEMORYDB data.pAuxDb->zDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else - utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0); + eputf("%s: Error: no database filename specified\n", Argv0); return 1; #endif } @@ -12601,8 +12332,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - printf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), - 8*(int)sizeof(char*)); + oputf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ /* already handled */ @@ -12658,18 +12389,18 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ open_db(&data, 0); rc = shell_exec(&data, z, &zErrMsg); if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); if( bail_on_error ) return rc!=0 ? rc : 1; }else if( rc!=0 ){ - utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); + eputf("Error: unable to process SQL \"%s\"\n", z); if( bail_on_error ) return rc; } } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) }else if( cli_strncmp(z, "-A", 2)==0 ){ if( nCmd>0 ){ - utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" - " with \"%s\"\n", z); + eputf("Error: cannot mix regular SQL or dot-commands" + " with \"%s\"\n", z); return 1; } open_db(&data, OPEN_DB_ZIPFILE); @@ -12687,8 +12418,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ /* Acted upon in first pass. */ }else{ - utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); - raw_printf(stderr,"Use -help for a list of options.\n"); + eputf("%s: Error: unknown option: %s\n", Argv0, z); + eputz("Use -help for a list of options.\n"); return 1; } data.cMode = data.mode; @@ -12712,9 +12443,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ rc = shell_exec(&data, azCmd[i], &zErrMsg); if( zErrMsg || rc ){ if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); }else{ - utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]); + eputf("Error: unable to process SQL: %s\n", azCmd[i]); } sqlite3_free(zErrMsg); free(azCmd); @@ -12728,26 +12459,20 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( stdin_is_interactive ){ char *zHome; char *zHistory; - const char *zCharset = ""; int nHistory; -#if SHELL_WIN_UTF8_OPT - switch( console_utf8_in+2*console_utf8_out ){ - default: case 0: break; - case 1: zCharset = " (utf8 in)"; break; - case 2: zCharset = " (utf8 out)"; break; - case 3: zCharset = " (utf8 I/O)"; break; - } -#endif - printf( - "SQLite version %s %.19s%s\n" /*extra-version-info*/ - "Enter \".help\" for usage hints.\n", - sqlite3_libversion(), sqlite3_sourceid(), zCharset - ); +#if CIO_WIN_WC_XLATE +# define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "") +#else +# define SHELL_CIO_CHAR_SET "" +#endif + oputf("SQLite version %s %.19s%s\n" /*extra-version-info*/ + "Enter \".help\" for usage hints.\n", + sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET); if( warnInmemoryDb ){ - printf("Connected to a "); + oputz("Connected to a "); printBold("transient in-memory database"); - printf(".\nUse \".open FILENAME\" to reopen on a " - "persistent database.\n"); + oputz(".\nUse \".open FILENAME\" to reopen on a" + " persistent database.\n"); } zHistory = getenv("SQLITE_HISTORY"); if( zHistory ){ @@ -12807,8 +12532,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ memset(&data, 0, sizeof(data)); #ifdef SQLITE_DEBUG if( sqlite3_memory_used()>mem_main_enter ){ - utf8_printf(stderr, "Memory leaked: %u bytes\n", - (unsigned int)(sqlite3_memory_used()-mem_main_enter)); + eputf("Memory leaked: %u bytes\n", + (unsigned int)(sqlite3_memory_used()-mem_main_enter)); } #endif #endif /* !SQLITE_SHELL_FIDDLE */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 01a7f4d4e..53c037c3b 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -5573,13 +5573,27 @@ int sqlite3_create_window_function( **
      ** ** [[SQLITE_SUBTYPE]]
      SQLITE_SUBTYPE
      -** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call +** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call ** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. -** Specifying this flag makes no difference for scalar or aggregate user -** functions. However, if it is not specified for a user-defined window -** function, then any sub-types belonging to arguments passed to the window -** function may be discarded before the window function is called (i.e. -** sqlite3_value_subtype() will always return 0). +** This flag instructs SQLite to omit some corner-case optimizations that +** might disrupt the operation of the [sqlite3_value_subtype()] function, +** causing it to return zero rather than the correct subtype(). +** SQL functions that invokes [sqlite3_value_subtype()] should have this +** property. If the SQLITE_SUBTYPE property is omitted, then the return +** value from [sqlite3_value_subtype()] might sometimes be zero even though +** a non-zero subtype was specified by the function argument expression. +** +** [[SQLITE_RESULT_SUBTYPE]]
      SQLITE_RESULT_SUBTYPE
      +** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_result_subtype()] to cause a sub-type to be associated with its +** result. +** Every function that invokes [sqlite3_result_subtype()] should have this +** property. If it does not, then the call to [sqlite3_result_subtype()] +** might become a no-op if the function is used as term in an +** [expression index]. On the other hand, SQL functions that never invoke +** [sqlite3_result_subtype()] should avoid setting this property, as the +** purpose of this property is to disable certain optimizations that are +** incompatible with subtypes. **
      **
  • */ @@ -5587,6 +5601,7 @@ int sqlite3_create_window_function( #define SQLITE_DIRECTONLY 0x000080000 #define SQLITE_SUBTYPE 0x000100000 #define SQLITE_INNOCUOUS 0x000200000 +#define SQLITE_RESULT_SUBTYPE 0x001000000 /* ** CAPI3REF: Deprecated Functions @@ -5783,6 +5798,12 @@ int sqlite3_value_encoding(sqlite3_value*); ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. +** +** Every [application-defined SQL function] that invoke this interface +** should include the [SQLITE_SUBTYPE] property in the text +** encoding argument when the function is [sqlite3_create_function|registered]. +** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype() +** might return zero instead of the upstream subtype in some corner cases. */ unsigned int sqlite3_value_subtype(sqlite3_value*); @@ -5913,14 +5934,22 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*); **
  • ^(when sqlite3_set_auxdata() is invoked again on the same ** parameter)^, or **
  • ^(during the original sqlite3_set_auxdata() call when a memory -** allocation error occurs.)^ +** allocation error occurs.)^ +**
  • ^(during the original sqlite3_set_auxdata() call if the function +** is evaluated during query planning instead of during query execution, +** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ ** -** Note the last bullet in particular. The destructor X in +** Note the last two bullets in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the ** function implementation should not make any use of P after -** sqlite3_set_auxdata() has been called. +** sqlite3_set_auxdata() has been called. Furthermore, a call to +** sqlite3_get_auxdata() that occurs immediately after a corresponding call +** to sqlite3_set_auxdata() might still return NULL if an out-of-memory +** condition occurred during the sqlite3_set_auxdata() call or if the +** function is being evaluated during query planning rather than during +** query execution. ** ** ^(In practice, auxiliary data is preserved between function calls for ** function parameters that are compile-time constants, including literal @@ -6194,6 +6223,20 @@ int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase ** in future releases of SQLite. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_RESULT_SUBTYPE] property in its +** text encoding argument when the SQL function is +** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE] +** property is omitted from the function that invokes sqlite3_result_subtype(), +** then in some cases the sqlite3_result_subtype() might fail to set +** the result subtype. +** +** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any +** SQL function that invokes the sqlite3_result_subtype() interface +** and that does not have the SQLITE_RESULT_SUBTYPE property will raise +** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1 +** by default. */ void sqlite3_result_subtype(sqlite3_context*,unsigned int); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index dbf01dd13..6d88c7ab4 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2014,14 +2014,15 @@ struct FuncDestructor { #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ #define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */ -/* 0x8000 -- available for reuse */ +#define SQLITE_FUNC_RUNONLY 0x8000 /* Cannot be used by valueFromFunction */ #define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */ #define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */ #define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */ -#define SQLITE_FUNC_SUBTYPE 0x00100000 /* Result likely to have sub-type */ +/* SQLITE_SUBTYPE 0x00100000 // Consumer of subtypes */ #define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */ #define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */ #define SQLITE_FUNC_BUILTIN 0x00800000 /* This is a built-in function */ +/* SQLITE_RESULT_SUBTYPE 0x01000000 // Generator of subtypes */ #define SQLITE_FUNC_ANYORDER 0x08000000 /* count/min/max aggregate */ /* Identifier numbers for each in-line function */ @@ -2113,9 +2114,10 @@ struct FuncDestructor { #define MFUNCTION(zName, nArg, xPtr, xFunc) \ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } -#define JFUNCTION(zName, nArg, iArg, xFunc) \ - {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|\ - SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ +#define JFUNCTION(zName, nArg, bUseCache, bWS, bRS, iArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_FUNC_CONSTANT|\ + SQLITE_UTF8|((bUseCache)*SQLITE_FUNC_RUNONLY)|\ + ((bRS)*SQLITE_SUBTYPE)|((bWS)*SQLITE_RESULT_SUBTYPE), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ {nArg, SQLITE_FUNC_BUILTIN|\ diff --git a/src/vdbe.c b/src/vdbe.c index 221e8847d..f8b53de54 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -132,11 +132,12 @@ int sqlite3_found_count = 0; ** sqlite3CantopenError(lineno) */ static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){ - static int n = 0; + static u64 n = 0; (void)pc; (void)pOp; (void)v; n++; + if( n==LARGEST_UINT64 ) abort(); /* So that n is used, preventing a warning */ } #endif @@ -8183,7 +8184,7 @@ case OP_VCheck: { /* out2 */ pTab = pOp->p4.pTab; assert( pTab!=0 ); assert( IsVirtual(pTab) ); - assert( pTab->u.vtab.p!=0 ); + if( pTab->u.vtab.p==0 ) break; pVtab = pTab->u.vtab.p->pVtab; assert( pVtab!=0 ); pModule = pVtab->pModule; diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 31ebbc697..3d4b9f76d 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -531,6 +531,18 @@ void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ #ifdef SQLITE_ENABLE_API_ARMOR if( pCtx==0 ) return; #endif +#if defined(SQLITE_STRICT_SUBTYPE) && SQLITE_STRICT_SUBTYPE+0!=0 + if( pCtx->pFunc!=0 + && (pCtx->pFunc->funcFlags & SQLITE_RESULT_SUBTYPE)==0 + ){ + char zErr[200]; + sqlite3_snprintf(sizeof(zErr), zErr, + "misuse of sqlite3_result_subtype() by %s()", + pCtx->pFunc->zName); + sqlite3_result_error(pCtx, zErr, -1); + return; + } +#endif /* SQLITE_STRICT_SUBTYPE */ pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); pOut->eSubtype = eSubtype & 0xff; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 54317b816..1dbd1dc0a 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -211,10 +211,11 @@ static int growOpArray(Vdbe *v, int nOp){ ** sqlite3CantopenError(lineno) */ static void test_addop_breakpoint(int pc, Op *pOp){ - static int n = 0; + static u64 n = 0; (void)pc; (void)pOp; n++; + if( n==LARGEST_UINT64 ) abort(); /* so that n is used, preventing a warning */ } #endif diff --git a/src/vdbemem.c b/src/vdbemem.c index eee828143..d52716468 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -1515,7 +1515,7 @@ static int valueFromFunction( #endif assert( pFunc ); if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 - || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + || (pFunc->funcFlags & (SQLITE_FUNC_NEEDCOLL|SQLITE_FUNC_RUNONLY))!=0 ){ return SQLITE_OK; } diff --git a/src/where.c b/src/where.c index 05ae24f7b..cfd5d5e1a 100644 --- a/src/where.c +++ b/src/where.c @@ -5810,6 +5810,20 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( continue; } if( sqlite3ExprIsConstant(pExpr) ) continue; + if( pExpr->op==TK_FUNCTION ){ + /* Functions that might set a subtype should not be replaced by the + ** value taken from an expression index since the index omits the + ** subtype. https://sqlite.org/forum/forumpost/68d284c86b082c3e */ + int n; + FuncDef *pDef; + sqlite3 *db = pParse->db; + assert( ExprUseXList(pExpr) ); + n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + continue; + } + } p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; p->pIENext = pParse->pIdxEpr; diff --git a/src/window.c b/src/window.c index 2c449592d..62df349fb 100644 --- a/src/window.c +++ b/src/window.c @@ -1038,7 +1038,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ assert( ExprUseXList(pWin->pOwner) ); assert( pWin->pWFunc!=0 ); pArgs = pWin->pOwner->x.pList; - if( pWin->pWFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ + if( pWin->pWFunc->funcFlags & SQLITE_SUBTYPE ){ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); pWin->bExprArgs = 1; diff --git a/test/alter.test b/test/alter.test index ee8e6c0b9..9201f40ad 100644 --- a/test/alter.test +++ b/test/alter.test @@ -954,4 +954,33 @@ do_catchsql_test alter-20.3 { INSERT INTO t1(a) VALUES(45); } {1 {cannot store BLOB value in TEXT column t1.b}} +# 2023-11-17 dbsqlfuzz e0900262dadd5c78c2226ad6a435c7f0255be2cd +# Assertion fault associated with ALTER TABLE and an +# aggregate ORDER BY within an unknown aggregate function. +# +reset_db +do_execsql_test alter-21.1 { + CREATE TABLE t1(a,b,c,d); + CREATE TABLE t2(a,b,c,d,x); + CREATE TRIGGER r1 AFTER INSERT ON t2 BEGIN + SELECT unknown_function(a ORDER BY (SELECT group_concat(DISTINCT a ORDER BY a) FROM t1)) FROM t1; + END; + ALTER TABLE t2 RENAME TO e; +} {} +do_execsql_test alter-21.2 { + SELECT name, type FROM sqlite_schema ORDER BY name; +} {e table r1 trigger t1 table} +do_execsql_test alter-21.3 { + DROP TRIGGER r1; + CREATE TRIGGER r2 AFTER INSERT ON e BEGIN + SELECT unknown_function(a ORDER BY (SELECT group_concat(a ORDER BY a) FROM (SELECT b FROM t1))) FROM t1; + END; + ALTER TABLE e RENAME TO t99; +} +do_execsql_test alter-21.4 { + SELECT name, type FROM sqlite_schema ORDER BY name; +} {r2 trigger t1 table t99 table} + + + finish_test diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 51ef73bbf..0316ee9d4 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -616,4 +616,18 @@ do_execsql_test indexexpr1-2200 { ) v ON v.type = 0 AND v.tag = u.tag; } {7 100 8 101} +# 2023-11-08 Forum post https://sqlite.org/forum/forumpost/68d284c86b082c3e +# +# Functions that return subtypes and that are indexed cannot be used to +# cover function calls from the main table, since the indexed value does +# not know the subtype. +# +reset_db +do_execsql_test indexexpr1-2300 { + CREATE TABLE t1(x INT, y TEXT); + INSERT INTO t1(x,y) VALUES(1,'{b:5}'); + CREATE INDEX t1j ON t1(json(y)); + SELECT json_insert('{}', '$.a', json(y)) FROM t1; +} {{{"a":{"b":5}}}} + finish_test diff --git a/test/joinH.test b/test/joinH.test index 0fed7f2aa..370226680 100644 --- a/test/joinH.test +++ b/test/joinH.test @@ -250,5 +250,63 @@ do_catchsql_test 9.11 { SELECT oid FROM wo2 JOIN (wo3 JOIN x3) } {0 99} +reset_db +do_execsql_test 10.0 { + CREATE TABLE rt0 (c0 INTEGER, c1 INTEGER, c2 INTEGER, c3 INTEGER, c4 INTEGER); + CREATE TABLE rt3 (c3 INTEGER); + + INSERT INTO rt0(c3, c1) VALUES (x'', '1'); + INSERT INTO rt0(c3, c1) VALUES ('-1', -1e500); + INSERT INTO rt0(c3, c1) VALUES (1, x''); + + CREATE VIEW v6(c0, c1, c2) AS SELECT 0, 0, 0; +} + +do_execsql_test 10.1 { + SELECT COUNT(*) FROM rt0 LEFT JOIN rt3 JOIN v6 ON ((CASE v6.c0 WHEN rt0.c4 THEN rt3.c3 END) NOT BETWEEN (rt0.c4) AND (NULL)) WHERE (rt0.c1); -- 2 +} {0} + +do_execsql_test 10.2 { + SELECT COUNT(*) FROM rt0 LEFT JOIN rt3 RIGHT OUTER JOIN v6 ON ((CASE v6.c0 WHEN rt0.c4 THEN rt3.c3 END) NOT BETWEEN (rt0.c4) AND (NULL)) WHERE (rt0.c1); -- 2 +} {0} + +#------------------------------------------------------------------------- + +do_execsql_test 11.1 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, d); + CREATE TABLE t3(e, f); + + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t2 VALUES(2, 2); + INSERT INTO t3 VALUES(3, 3); +} + +do_execsql_test 11.2 { + SELECT * FROM t1 LEFT JOIN t2 RIGHT JOIN t3 ON (t2.c=10) +} {{} {} {} {} 3 3} + +do_execsql_test 11.3 { + SELECT * FROM t1 LEFT JOIN t2 RIGHT JOIN t3 ON (t2.c=10) WHERE t1.a=1 +} {} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 12.1 { + CREATE TABLE t1(a1 INT, b1 TEXT); + INSERT INTO t1 VALUES(88,''); + CREATE TABLE t2(c2 INT, d2 TEXT); + INSERT INTO t2 VALUES(88,''); + CREATE TABLE t3(e3 TEXT PRIMARY KEY); + INSERT INTO t3 VALUES(''); +} + +do_execsql_test 12.2 { + SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1; +} +do_execsql_test 12.3 { + SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1; +} finish_test diff --git a/test/json501.test b/test/json501.test index a37326973..3318eea7f 100644 --- a/test/json501.test +++ b/test/json501.test @@ -300,5 +300,10 @@ do_execsql_test 12.4 { || ' "xyz"}')->>'a'; } xyz +# 2023-11-08 forum/forumpost/ddcad3e884 +# +do_execsql_test 13.1 { + SELECT json('{x:''a "b" c''}'); +} {{{"x":"a \"b\" c"}}} finish_test diff --git a/test/shell1.test b/test/shell1.test index 19848549a..5d4243f47 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -496,11 +496,16 @@ do_test shell1-3.14.3 { # .output FILENAME Send output to FILENAME do_test shell1-3.15.1 { - catchcmd "test.db" ".output" -} {0 {}} + catchcmd "test.db" ".output +.print x" +} {0 x} do_test shell1-3.15.2 { - catchcmd "test.db" ".output FOO" -} {0 {}} + catchcmd "test.db" ".output FOO +.print x +.output +SELECT readfile('FOO');" +} {0 {x +}} do_test shell1-3.15.3 { # too many arguments catchcmd "test.db" ".output FOO BAD" @@ -1067,7 +1072,7 @@ do_test shell1-5.0 { # set escapes [list \ \a \\a \b \\b \t \\t \n \\n \v \\v \f \\f \r \\r \ - " " "\" \"" \" \\\" ' \"'\" \\ \\\\] + " " "\" \"" \" \\\" \\ \\\\] } else { # # NOTE: On Unix, we need to escape most of the whitespace characters diff --git a/test/whereG.test b/test/whereG.test index 6ca363ed8..c15405823 100644 --- a/test/whereG.test +++ b/test/whereG.test @@ -311,6 +311,20 @@ do_execsql_test 8.9 { do_execsql_test 8.10 { SELECT * FROM t0 WHERE likelihood(t0.rowid <= '0', 0.5); } {} +# Forum https://sqlite.org/forum/forumpost/45ec3d9788 +reset_db +do_execsql_test 8.11 { + CREATE TABLE t1(c0 INT); + INSERT INTO t1(c0) VALUES (NULL); + CREATE INDEX i46 ON t1(CAST( (c0 IS TRUE) AS TEXT)); + CREATE VIEW v0(c2) AS SELECT CAST( (c0 IS TRUE) AS TEXT ) FROM t1; +} +do_execsql_test 8.12 { + SELECT quote(c0), quote(c2) FROM t1, v0 WHERE (0 < LIKELY(v0.c2)); +} {NULL '0'} +do_execsql_test 8.13 { + SELECT quote(c0), quote(c2) FROM t1, v0 WHERE (0 < LIKELY(v0.c2)) IS TRUE; +} {NULL '0'} # 2019-12-31: assertion fault discovered by Yongheng's fuzzer. # Harmless memIsValid() due to the code generators failure to diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl index 098bf16e3..9e425c0fd 100755 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -7,6 +7,13 @@ # definition used in src/ctime.c, run this script from # the checkout root. It generates src/ctime.c . # +# Results are normally written into src/ctime.c. But if an argument is +# provided, results are written there instead. Examples: +# +# tclsh tool/mkctimec.tcl ;# <-- results to src/ctime.c +# +# tclsh tool/mkctimec.tcl /dev/tty ;# <-- results to the terminal +# set ::headWarning {/* DO NOT EDIT! @@ -429,10 +436,15 @@ foreach v $value2_options { }] } -set ctime_c "src/ctime.c" +if {$argc>0} { + set destfile [lindex $argv 0] +} else { + set destfile "[file dir [file dir [file normal $argv0]]]/src/ctime.c" + puts "Overwriting $destfile..." +} -if {[catch {set cfd [open $ctime_c w]}]!=0} { - puts stderr "File '$ctime_c' unwritable." +if {[catch {set cfd [open $destfile w]}]!=0} { + puts stderr "File '$destfile' unwritable." exit 1; } diff --git a/tool/mkpragmatab.tcl b/tool/mkpragmatab.tcl index 5c36df049..2b5b6fdcd 100644 --- a/tool/mkpragmatab.tcl +++ b/tool/mkpragmatab.tcl @@ -9,6 +9,14 @@ # Then add the extra "case PragTyp_XXXXX:" and subsequent code for the # new pragma in ../src/pragma.c. # +# The results are normally written into the ../src/pragma.h file. However, +# if an alternative output file name is provided as an argument, then +# results are written into the alternative. For example: +# +# tclsh tool/mkpragmatab.tcl ;# <--- Results to src/pragma.h +# +# tclsh tool/mkpragmatab.tcl /dev/tty ;# <-- results to terminal +# # Flag meanings: set flagMeaning(NeedSchema) {Force schema load before running} @@ -402,8 +410,12 @@ set pragma_def { # Open the output file # -set destfile "[file dir [file dir [file normal $argv0]]]/src/pragma.h" -puts "Overwriting $destfile with new pragma table..." +if {$argc>0} { + set destfile [lindex $argv 0] +} else { + set destfile "[file dir [file dir [file normal $argv0]]]/src/pragma.h" + puts "Overwriting $destfile with new pragma table..." +} set fd [open $destfile wb] puts $fd {/* DO NOT EDIT! ** This file is automatically generated by the script at diff --git a/tool/srctree-check.tcl b/tool/srctree-check.tcl index 234e70fae..f44e3ceec 100644 --- a/tool/srctree-check.tcl +++ b/tool/srctree-check.tcl @@ -27,12 +27,15 @@ proc readfile {filename} { # Find the root of the tree. # set ROOT [file dir [file dir [file normalize $argv0]]] -cd $ROOT # Name of the TCL interpreter # set TCLSH [info nameofexe] +# Number of errors seen. +# +set NERR 0 + ######################### autoconf/tea/configure.ac ########################### set confac [readfile $ROOT/autoconf/tea/configure.ac] @@ -42,38 +45,46 @@ append pattern [string trim $vers] append pattern {])} if {[string first $pattern $confac]<=0} { puts "ERROR: ./autoconf/tea/configure.ac does not agree with ./VERSION" - exit 1 + puts "...... Fix: manually edit ./autoconf/tea/configure.ac to" + incr NERR } ######################### autoconf/Makefile.msc ############################### set f1 [readfile $ROOT/autoconf/Makefile.msc] -exec mv $ROOT/autoconf/Makefile.msc $ROOT/autoconf/Makefile.msc.tmp -exec $TCLSH $ROOT/tool/mkmsvcmin.tcl -set f2 [readfile $ROOT/autoconf/Makefile.msc] -exec mv $ROOT/autoconf/Makefile.msc.tmp $ROOT/autoconf/Makefile.msc +exec $TCLSH $ROOT/tool/mkmsvcmin.tcl $ROOT/Makefile.msc tmp1.txt +set f2 [readfile tmp1.txt] +file delete tmp1.txt if {$f1 != $f2} { puts "ERROR: ./autoconf/Makefile.msc does not agree with ./Makefile.msc" + puts "...... Fix: tclsh tool/mkmsvcmin.tcl" + incr NERR } ######################### src/pragma.h ######################################## set f1 [readfile $ROOT/src/pragma.h] -exec mv $ROOT/src/pragma.h $ROOT/src/pragma.h.tmp -exec $TCLSH $ROOT/tool/mkpragmatab.tcl -set f2 [readfile $ROOT/src/pragma.h] -exec mv $ROOT/src/pragma.h.tmp $ROOT/src/pragma.h +exec $TCLSH $ROOT/tool/mkpragmatab.tcl tmp2.txt +set f2 [readfile tmp2.txt] +file delete tmp2.txt if {$f1 != $f2} { puts "ERROR: ./src/pragma.h does not agree with ./tool/mkpragmatab.tcl" + puts "...... Fix: tclsh tool/mkpragmatab.tcl" + incr NERR } ######################### src/ctime.c ######################################## set f1 [readfile $ROOT/src/ctime.c] -exec mv $ROOT/src/ctime.c $ROOT/src/ctime.c.tmp -exec $TCLSH $ROOT/tool/mkctimec.tcl -set f2 [readfile $ROOT/src/ctime.c] -exec mv $ROOT/src/ctime.c.tmp $ROOT/src/ctime.c +exec $TCLSH $ROOT/tool/mkctimec.tcl tmp3.txt +set f2 [readfile tmp3.txt] +file delete tmp3.txt if {$f1 != $f2} { puts "ERROR: ./src/ctime.c does not agree with ./tool/mkctimec.tcl" + puts "..... Fix: tclsh tool/mkctimec.tcl" + incr NERR } + +# If any errors are seen, exit 1 so that the build will fail. +# +if {$NERR>0} {exit 1} From befb0709091fa29fa0f3b7eb3944de7b753d1a44 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 5 Jan 2024 16:38:39 -0500 Subject: [PATCH 116/340] update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e49104676..2c1aa0cff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. -## [4.5.6] - (TBD - [4.5.6 changes]) +## [4.5.6] - (January 2024 - [4.5.6 changes]) +- Updates baseline to upstream SQLite 3.44.2 +- Improve PRAGMA cipher_integrity check to report expected page size if invalid +- Implement PRAGMA page_size compatibility with PRAGMA cipher_page_size so both will operate properly on encrypted databases +- Updates LICENSE.md with SQLCipher license to avoid ambiguity and remove redundance ## [4.5.5] - (August 2023 - [4.5.5 changes]) - Updates baseline to upstream SQLite 3.42.0 From ddc7aa91d2324d55505c0d3234e4153f9dc05000 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 18 Jan 2024 15:29:41 -0500 Subject: [PATCH 117/340] bump version --- CHANGELOG.md | 4 ++++ SQLCipher.podspec.json | 4 ++-- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c1aa0cff..7a170879e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. +## [4.5.7] - (TBD - [4.5.7 changes]) + ## [4.5.6] - (January 2024 - [4.5.6 changes]) - Updates baseline to upstream SQLite 3.44.2 - Improve PRAGMA cipher_integrity check to report expected page size if invalid @@ -232,6 +234,8 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 +[4.5.7]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.7 +[4.5.7 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.6...v4.5.7 [4.5.6]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.6 [4.5.6 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.5...v4.5.6 [4.5.5]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.5 diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 3157bd1b3..2ec926437 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.6" + "tag": "v4.5.7" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.6", + "version": "4.5.7", "subspecs": [ { "compiler_flags": [ diff --git a/src/crypto.h b/src/crypto.h index c95833086..a06c20dbc 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -84,7 +84,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.6 +#define CIPHER_VERSION_NUMBER 4.5.7 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index c7087bb9e..cc70e2ab1 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.6 community}} +} {{4.5.7 community}} db close file delete -force test.db From a4df8d60a1cbad6112630a0fe56ae887bb6217d4 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 18 Jan 2024 15:38:50 -0500 Subject: [PATCH 118/340] Snapshot of upstream SQLite 3.45.0 --- Makefile.in | 48 +- Makefile.msc | 13 +- README.md | 20 +- VERSION | 2 +- autoconf/Makefile.msc | 1 + autoconf/tea/configure.ac | 2 +- configure | 18 +- configure.ac | 2 +- doc/compile-for-windows.md | 14 +- doc/jsonb.md | 290 + doc/testrunner.md | 78 +- ext/consio/console_io.c | 17 +- ext/consio/console_io.h | 4 +- ext/fts5/extract_api_docs.tcl | 4 +- ext/fts5/fts5.h | 68 +- ext/fts5/fts5Int.h | 33 +- ext/fts5/fts5_aux.c | 14 +- ext/fts5/fts5_buffer.c | 3 + ext/fts5/fts5_config.c | 10 + ext/fts5/fts5_expr.c | 208 +- ext/fts5/fts5_hash.c | 49 +- ext/fts5/fts5_index.c | 859 ++- ext/fts5/fts5_main.c | 135 +- ext/fts5/fts5_storage.c | 2 +- ext/fts5/fts5_tcl.c | 208 +- ext/fts5/fts5_tokenize.c | 97 +- ext/fts5/fts5_vocab.c | 2 +- ext/fts5/test/fts5_common.tcl | 34 + ext/fts5/test/fts5aa.test | 68 +- ext/fts5/test/fts5aux.test | 43 + ext/fts5/test/fts5content.test | 35 + ext/fts5/test/fts5corrupt5.test | 485 ++ ext/fts5/test/fts5faultH.test | 141 + ext/fts5/test/fts5misc.test | 36 +- ext/fts5/test/fts5origintext.test | 297 + ext/fts5/test/fts5origintext2.test | 146 + ext/fts5/test/fts5origintext3.test | 101 + ext/fts5/test/fts5origintext4.test | 80 + ext/fts5/test/fts5origintext5.test | 273 + ext/fts5/test/fts5secure3.test | 123 +- ext/fts5/test/fts5simple2.test | 4 +- ext/fts5/test/fts5synonym2.test | 2 +- ext/fts5/test/fts5tokenizer2.test | 89 + ext/fts5/test/fts5trigram2.test | 109 + ext/fts5/test/fts5vocab2.test | 24 + ext/jni/GNUmakefile | 15 +- ext/jni/README.md | 41 +- ext/jni/src/c/sqlite3-jni.c | 726 ++- ext/jni/src/c/sqlite3-jni.h | 134 +- .../sqlite/jni/annotation/Experimental.java | 30 + .../org/sqlite/jni/annotation/NotNull.java | 40 +- .../org/sqlite/jni/annotation/Nullable.java | 9 +- .../sqlite/jni/capi/AggregateFunction.java | 70 +- .../sqlite/jni/capi/AuthorizerCallback.java | 3 +- ext/jni/src/org/sqlite/jni/capi/CApi.java | 738 ++- .../org/sqlite/jni/capi/CallbackProxy.java | 5 +- .../jni/capi/CollationNeededCallback.java | 7 +- .../sqlite/jni/capi/CommitHookCallback.java | 3 +- ...allback.java => ConfigSqlLogCallback.java} | 4 +- .../org/sqlite/jni/capi/OutputPointer.java | 22 + .../sqlite/jni/capi/PrepareMultiCallback.java | 7 +- .../jni/capi/PreupdateHookCallback.java | 3 +- .../sqlite/jni/capi/RollbackHookCallback.java | 5 +- .../src/org/sqlite/jni/capi/SQLFunction.java | 67 - ext/jni/src/org/sqlite/jni/capi/Tester1.java | 342 +- .../sqlite/jni/capi/UpdateHookCallback.java | 3 +- .../src/org/sqlite/jni/capi/ValueHolder.java | 6 +- ext/jni/src/org/sqlite/jni/capi/sqlite3.java | 2 +- .../src/org/sqlite/jni/capi/sqlite3_blob.java | 3 +- .../src/org/sqlite/jni/capi/sqlite3_stmt.java | 2 +- .../src/org/sqlite/jni/fts5/TesterFts5.java | 18 +- .../jni/wrapper1/AggregateFunction.java | 74 +- .../org/sqlite/jni/wrapper1/SqlFunction.java | 271 +- .../src/org/sqlite/jni/wrapper1/Sqlite.java | 1941 +++++- .../sqlite/jni/wrapper1/SqliteException.java | 33 +- .../src/org/sqlite/jni/wrapper1/Tester2.java | 879 ++- .../org/sqlite/jni/wrapper1/ValueHolder.java | 4 +- .../sqlite/jni/wrapper1/WindowFunction.java | 42 + ext/misc/randomjson.c | 80 +- ext/misc/totype.c | 18 +- ext/recover/dbdata.c | 1 + ext/rtree/rtree.c | 21 +- ext/session/sqlite3session.c | 4 +- ext/wasm/GNUmakefile | 175 +- ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api | 1 + ext/wasm/api/sqlite3-api-glue.js | 1 + ext/wasm/api/sqlite3-api-oo1.js | 5 +- ext/wasm/api/sqlite3-api-worker1.js | 37 +- ext/wasm/api/sqlite3-wasm.c | 8 +- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js | 7 +- ext/wasm/api/sqlite3-worker1.c-pp.js | 4 + ext/wasm/batch-runner-sahpool.html | 86 + ext/wasm/batch-runner-sahpool.js | 341 ++ ext/wasm/batch-runner.js | 24 +- ext/wasm/demo-123.js | 15 +- ext/wasm/index-dist.html | 9 +- ext/wasm/index.html | 9 +- ext/wasm/jaccwabyt/jaccwabyt.md | 5 +- ext/wasm/speedtest1-worker.html | 2 +- ext/wasm/speedtest1-worker.js | 23 +- ext/wasm/test-opfs-vfs.js | 2 +- ext/wasm/tester1.c-pp.js | 30 +- main.mk | 6 + manifest | 331 +- manifest.uuid | 2 +- src/analyze.c | 26 +- src/btree.c | 1 - src/btreeInt.h | 2 +- src/build.c | 20 +- src/date.c | 6 + src/expr.c | 28 +- src/global.c | 3 + src/json.c | 5435 +++++++++++------ src/main.c | 22 + src/os_unix.c | 324 +- src/pager.c | 17 +- src/pager.h | 2 +- src/pragma.c | 3 +- src/prepare.c | 1 + src/printf.c | 2 +- src/resolve.c | 11 +- src/select.c | 69 +- src/shell.c.in | 173 +- src/sqlite.h.in | 17 +- src/sqliteInt.h | 32 +- src/sqliteLimit.h | 2 +- src/status.c | 4 +- src/test1.c | 2 + src/test_func.c | 3 +- src/treeview.c | 2 +- src/utf.c | 33 +- src/util.c | 2 +- src/vdbe.c | 47 +- src/vdbe.h | 1 + src/vdbeapi.c | 13 +- src/vdbeaux.c | 6 +- src/vtab.c | 1 - src/wal.c | 136 +- src/where.c | 134 +- src/whereInt.h | 2 +- test/aggnested.test | 124 +- test/aggorderby.test | 40 + test/date.test | 5 + test/fts3integrity.test | 42 + test/func4.test | 12 +- test/fuzzcheck.c | 8 +- test/json/README.md | 57 +- test/json/json-speed-check.sh | 23 +- test/json/jsonb-q1.txt | 24 + test/json101.test | 130 +- test/json102.test | 352 +- test/json105.test | 12 +- test/json106.test | 73 + test/json501.test | 6 +- test/json502.test | 27 + test/jsonb01.test | 49 + test/releasetest_data.tcl | 845 --- test/shell4.test | 2 +- test/shell9.test | 148 + test/snapshot_up.test | 2 + test/tester.tcl | 11 +- test/testrunner.tcl | 114 +- test/testrunner_data.tcl | 10 +- test/trace3.test | 27 +- test/unionall.test | 7 + test/wapp.tcl | 987 --- test/wapptest.tcl | 909 --- test/where3.test | 22 + test/window1.test | 2 +- tool/sqldiff.c | 440 +- tool/sqlite3_analyzer.c.in | 47 + tool/srctree-check.tcl | 15 +- 172 files changed, 15300 insertions(+), 6941 deletions(-) create mode 100644 doc/jsonb.md create mode 100644 ext/fts5/test/fts5faultH.test create mode 100644 ext/fts5/test/fts5origintext.test create mode 100644 ext/fts5/test/fts5origintext2.test create mode 100644 ext/fts5/test/fts5origintext3.test create mode 100644 ext/fts5/test/fts5origintext4.test create mode 100644 ext/fts5/test/fts5origintext5.test create mode 100644 ext/fts5/test/fts5tokenizer2.test create mode 100644 ext/fts5/test/fts5trigram2.test create mode 100644 ext/jni/src/org/sqlite/jni/annotation/Experimental.java rename ext/jni/src/org/sqlite/jni/capi/{ConfigSqllogCallback.java => ConfigSqlLogCallback.java} (86%) create mode 100644 ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java create mode 100644 ext/wasm/batch-runner-sahpool.html create mode 100644 ext/wasm/batch-runner-sahpool.js mode change 100755 => 100644 src/btree.c create mode 100644 test/fts3integrity.test create mode 100644 test/json/jsonb-q1.txt create mode 100644 test/json106.test create mode 100644 test/jsonb01.test delete mode 100644 test/releasetest_data.tcl create mode 100644 test/shell9.test delete mode 100644 test/wapp.tcl delete mode 100755 test/wapptest.tcl diff --git a/Makefile.in b/Makefile.in index 357c99514..cb894666d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -447,6 +447,7 @@ TESTSRC += \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/prefixes.c \ $(TOP)/ext/misc/qpvtab.c \ + $(TOP)/ext/misc/randomjson.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ @@ -600,6 +601,7 @@ SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC +SHELL_OPT += -DSQLITE_STRICT_SUBTYPE=1 FUZZERSHELL_OPT = FUZZCHECK_OPT += -I$(TOP)/test FUZZCHECK_OPT += -I$(TOP)/ext/recover @@ -630,7 +632,9 @@ FUZZCHECK_OPT += \ -DSQLITE_MAX_MMAP_SIZE=0 \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_PRINTF_PRECISION_LIMIT=1000 \ - -DSQLITE_PRIVATE="" + -DSQLITE_PRIVATE="" \ + -DSQLITE_STRICT_SUBTYPE=1 \ + -DSQLITE_STATIC_RANDOMJSON FUZZCHECK_SRC += $(TOP)/test/fuzzcheck.c FUZZCHECK_SRC += $(TOP)/test/ossfuzz.c @@ -638,6 +642,7 @@ FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c FUZZCHECK_SRC += $(TOP)/ext/recover/dbdata.c FUZZCHECK_SRC += $(TOP)/ext/recover/sqlite3recover.c FUZZCHECK_SRC += $(TOP)/test/vt02.c +FUZZCHECK_SRC += $(TOP)/ext/misc/randomjson.c DBFUZZ_OPT = ST_OPT = -DSQLITE_OS_KV_OPTIONAL @@ -710,6 +715,21 @@ fuzzcheck-asan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) fuzzcheck-ubsan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP) $(LTLINK) -o $@ -fsanitize=undefined $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) +# Usage: FUZZDB=filename make run-fuzzcheck +# +# Where filename is a fuzzcheck database, this target builds and runs +# fuzzcheck, fuzzcheck-asan, and fuzzcheck-ubsan on that database. +# +# FUZZDB can be a glob pattern of two or more databases. Example: +# +# FUZZDB=test/fuzzdata*.db make run-fuzzcheck +# +run-fuzzcheck: fuzzcheck$(TEXE) fuzzcheck-asan$(TEXE) fuzzcheck-ubsan$(TEXE) + @if test "$(FUZZDB)" = ""; then echo 'ERROR: No FUZZDB specified. Rerun with FUZZDB=filename'; exit 1; fi + ./fuzzcheck$(TEXE) --spinner $(FUZZDB) + ./fuzzcheck-asan$(TEXE) --spinner $(FUZZDB) + ./fuzzcheck-ubsan$(TEXE) --spinner $(FUZZDB) + ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \ $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) @@ -1135,21 +1155,21 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c # Source files that go into making shell.c SHELL_SRC = \ $(TOP)/src/shell.c.in \ - $(TOP)/ext/misc/appendvfs.c \ + $(TOP)/ext/consio/console_io.c \ + $(TOP)/ext/consio/console_io.h \ + $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ - $(TOP)/ext/consio/console_io.c \ - $(TOP)/ext/consio/console_io.h \ - $(TOP)/ext/misc/decimal.c \ - $(TOP)/ext/misc/basexx.c \ - $(TOP)/ext/misc/base64.c \ - $(TOP)/ext/misc/base85.c \ + $(TOP)/ext/misc/decimal.c \ + $(TOP)/ext/misc/basexx.c \ + $(TOP)/ext/misc/base64.c \ + $(TOP)/ext/misc/base85.c \ $(TOP)/ext/misc/fileio.c \ - $(TOP)/ext/misc/ieee754.c \ - $(TOP)/ext/misc/regexp.c \ - $(TOP)/ext/misc/series.c \ + $(TOP)/ext/misc/ieee754.c \ + $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ - $(TOP)/ext/misc/uint.c \ + $(TOP)/ext/misc/uint.c \ $(TOP)/ext/expert/sqlite3expert.c \ $(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/misc/zipfile.c \ @@ -1158,7 +1178,7 @@ SHELL_SRC = \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/sqlite3recover.h \ - $(TOP)/src/test_windirent.c + $(TOP)/src/test_windirent.c shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c @@ -1276,6 +1296,8 @@ TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_STMTVTAB TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_DBPAGE_VTAB TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_BYTECODE_VTAB TESTFIXTURE_FLAGS += -DSQLITE_CKSUMVFS_STATIC +TESTFIXTURE_FLAGS += -DSQLITE_STATIC_RANDOMJSON +TESTFIXTURE_FLAGS += -DSQLITE_STRICT_SUBTYPE=1 TESTFIXTURE_SRC0 = $(TESTSRC2) libsqlite3.la TESTFIXTURE_SRC1 = sqlite3.c diff --git a/Makefile.msc b/Makefile.msc index 3a4d46b01..19bfe2f38 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1584,6 +1584,7 @@ TESTEXT = \ $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\prefixes.c \ $(TOP)\ext\misc\qpvtab.c \ + $(TOP)\ext\misc\randomjson.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\remember.c \ $(TOP)\ext\misc\series.c \ @@ -1692,6 +1693,7 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1 !ENDIF # <> @@ -1728,6 +1730,8 @@ FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MMAP_SIZE=0 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRIVATE="" +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_STRICT_SUBTYPE=1 +FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_STATIC_RANDOMJSON FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000 @@ -1744,6 +1748,7 @@ FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\fuzzinvariants.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\vt02.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\dbdata.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\sqlite3recover.c +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\randomjson.c OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION @@ -1823,8 +1828,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT /link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) # <> -sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) - $(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) +sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) + $(LTLINK) $(NO_WARN) -I$(TOP)\ext\consio $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) @@ -2433,6 +2438,8 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_CKSUMVFS_STATIC=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS) +TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STATIC_RANDOMJSON +TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STRICT_SUBTYPE=1 TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2) TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C) @@ -2543,7 +2550,7 @@ smoketest: $(TESTPROGS) shelltest: $(TESTPROGS) .\testfixture.exe $(TOP)\test\permutations.test shell -sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(SQLITE_TCL_DEP) +sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE_TCL_DEP) $(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in > $@ sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS) diff --git a/README.md b/README.md index ebb917e21..0975a32d9 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ extension and only later escaped to the wild as an independent library.) Test scripts and programs are found in the **test/** subdirectory. Additional test code is found in other source repositories. -See [How SQLite Is Tested](http://www.sqlite.org/testing.html) for +See [How SQLite Is Tested](https://www.sqlite.org/testing.html) for additional information. The **ext/** subdirectory contains code for extensions. The @@ -183,7 +183,7 @@ manually-edited files and automatically-generated files. The SQLite interface is defined by the **sqlite3.h** header file, which is generated from src/sqlite.h.in, ./manifest.uuid, and ./VERSION. The -[Tcl script](http://www.tcl.tk) at tool/mksqlite3h.tcl does the conversion. +[Tcl script](https://www.tcl.tk) at tool/mksqlite3h.tcl does the conversion. The manifest.uuid file contains the SHA3 hash of the particular check-in and is used to generate the SQLITE\_SOURCE\_ID macro. The VERSION file contains the current SQLite version number. The sqlite3.h header is really @@ -250,14 +250,14 @@ individual source file exceeds 32K lines in length. ## How It All Fits Together SQLite is modular in design. -See the [architectural description](http://www.sqlite.org/arch.html) +See the [architectural description](https://www.sqlite.org/arch.html) for details. Other documents that are useful in (helping to understand how SQLite works include the -[file format](http://www.sqlite.org/fileformat2.html) description, -the [virtual machine](http://www.sqlite.org/opcode.html) that runs +[file format](https://www.sqlite.org/fileformat2.html) description, +the [virtual machine](https://www.sqlite.org/opcode.html) that runs prepared statements, the description of -[how transactions work](http://www.sqlite.org/atomiccommit.html), and -the [overview of the query planner](http://www.sqlite.org/optoverview.html). +[how transactions work](https://www.sqlite.org/atomiccommit.html), and +the [overview of the query planner](https://www.sqlite.org/optoverview.html). Years of effort have gone into optimizing SQLite, both for small size and high performance. And optimizations tend to result in @@ -353,7 +353,7 @@ hidden by also modifying the makefiles. ## Contacts -The main SQLite website is [http:/sqlite.org/](http://sqlite.org/) +The main SQLite website is [https://sqlite.org/](https://sqlite.org/) with geographically distributed backups at -[http://www2.sqlite.org/](http://www2.sqlite.org) and -[http://www3.sqlite.org/](http://www3.sqlite.org). +[https://www2.sqlite.org/](https://www2.sqlite.org) and +[https://www3.sqlite.org/](https://www3.sqlite.org). diff --git a/VERSION b/VERSION index 1b9d70c30..ff3ff28f6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.44.2 +3.45.0 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 280bb95de..45a07a9f3 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -990,6 +990,7 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1 !ENDIF diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index 503494b37..4df57344b 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.44.2]) +AC_INIT([sqlite],[3.45.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 1931b8eb3..6a47c4dcc 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.69 for sqlite 3.44.2. +# Generated by GNU Autoconf 2.69 for sqlite 3.45.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.44.2' -PACKAGE_STRING='sqlite 3.44.2' +PACKAGE_VERSION='3.45.0' +PACKAGE_STRING='sqlite 3.45.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1472,7 +1472,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.44.2 to adapt to many kinds of systems. +\`configure' configures sqlite 3.45.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.44.2:";; + short | recursive ) echo "Configuration of sqlite 3.45.0:";; esac cat <<\_ACEOF @@ -1668,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.44.2 +sqlite configure 3.45.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2087,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.44.2, which was +It was created by sqlite $as_me 3.45.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.44.2, which was +This file was extended by sqlite $as_me 3.45.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.44.2 +sqlite config.status 3.45.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 837d2fb01..54f985fd7 100644 --- a/configure.ac +++ b/configure.ac @@ -74,7 +74,7 @@ # you don't need (for example BLT) by erasing or commenting out # the corresponding code. # -AC_INIT([sqlite],[m4_esyscmd(cat VERSION | tr -d '\n')]) +AC_INIT([sqlite],m4_esyscmd(cat VERSION | tr -d '\n')) dnl Make sure the local VERSION file matches this configure script sqlite_version_sanity_check=`cat $srcdir/VERSION | tr -d '\n'` diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md index 20f79afa9..b8a50afb3 100644 --- a/doc/compile-for-windows.md +++ b/doc/compile-for-windows.md @@ -1,7 +1,7 @@ # Notes On Compiling SQLite On Windows 11 Here are step-by-step instructions on how to build SQLite from -canonical source on a new Windows 11 PC, as of 2023-08-16: +canonical source on a new Windows 11 PC, as of 2023-11-01: 1. Install Microsoft Visual Studio. The free "community edition" will work fine. Do a standard install for C++ development. @@ -84,6 +84,18 @@ following minor changes:
  • `set PATH=c:\tcl32\bin;%PATH%` +## Building a DLL + +The command the developers use for building the deliverable DLL on the +[download page](https://sqlite.org/download.html) is as follows: + +> ~~~~ +nmake /f Makefile.msc sqlite3.dll USE_NATIVE_LIBPATHS=1 "OPTS=-DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS4=1 -DSQLITE_ENABLE_FTS5=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_ENABLE_JSON1=1 -DSQLITE_ENABLE_GEOPOLY=1 -DSQLITE_ENABLE_SESSION=1 -DSQLITE_ENABLE_PREUPDATE_HOOK=1 -DSQLITE_ENABLE_SERIALIZE=1 -DSQLITE_ENABLE_MATH_FUNCTIONS=1" +~~~~ + +That command generates both the sqlite3.dll and sqlite3.def files. The same +command works for both 32-bit and 64-bit builds. + ## Statically Linking The TCL Library Some utility programs associated with SQLite need to be linked diff --git a/doc/jsonb.md b/doc/jsonb.md new file mode 100644 index 000000000..5beed1631 --- /dev/null +++ b/doc/jsonb.md @@ -0,0 +1,290 @@ +# The JSONB Format + +This document describes SQLite's JSONB binary encoding of +JSON. + +## 1.0 What Is JSONB? + +Beginning with version 3.45.0 (circa 2024-01-01), SQLite supports an +alternative binary encoding of JSON which we call "JSONB". JSONB is +a binary format that stored as a BLOB. + +The advantage of JSONB over ordinary text RFC 8259 JSON is that JSONB +is both slightly smaller (by between 5% and 10% in most cases) and +can be processed in less than half the number of CPU cycles. The built-in +[JSON SQL functions] of SQLite can accept either ordinary text JSON +or the binary JSONB encoding for any of their JSON inputs. + +The "JSONB" name is inspired by [PostgreSQL](https://postgresql.org), but the +on-disk format for SQLite's JSONB is not the same as PostgreSQL's. +The two formats have the same name, but they have wildly different internal +representations and are not in any way binary compatible. + +The central idea behind this JSONB specification is that each element +begins with a header that includes the size and type of that element. +The header takes the place of punctuation such as double-quotes, +curly-brackes, square-brackets, commas, and colons. Since the size +and type of each element is contained in its header, the element can +be read faster since it is no longer necessary to carefully scan forward +looking for the closing delimiter. The payload of JSONB is the same +as for corresponding text JSON. The same payload bytes occur in the +same order. The only real difference between JSONB and ordinary text +JSON is that JSONB includes a binary header on +each element and omits delimiter and separator punctuation. + +### 1.1 Internal Use Only + +The details of the JSONB are not intended to be visible to application +developers. Application developers should look at JSONB as an opaque BLOB +used internally by SQLite. Nevertheless, we want the format to be backwards +compatible across all future versions of SQLite. To that end, the format +is documented by this file in the source tree. But this file should be +used only by SQLite core developers, not by developers of applications +that only use SQLite. + +## 2.0 The Purpose Of This Document + +JSONB is not intended as an external format to be used by +applications. JSONB is designed for internal use by SQLite only. +Programmers do not need to understand the JSONB format in order to +use it effectively. +Applications should access JSONB only through the [JSON SQL functions], +not by looking at individual bytes of the BLOB. + +However, JSONB is intended to be portable and backwards compatible +for all future versions of SQLite. In other words, you should not have +to export and reimport your SQLite database files when you upgrade to +a newer SQLite version. For that reason, the JSONB format needs to +be well-defined. + +This document is therefore similar in purpose to the +[SQLite database file format] document that describes the on-disk +format of an SQLite database file. Applications are not expected +to directly read and write the bits and bytes of SQLite database files. +The SQLite database file format is carefully documented so that it +can be stable and enduring. In the same way, the JSONB representation +of JSON is documented here so that it too can be stable and enduring, +not so that applications can read or writes individual bytes. + +## 3.0 Encoding + +JSONB is a direct translation of the underlying text JSON. The difference +is that JSONB uses a binary encoding that is faster to parse compared to +the detailed syntax of text JSON. + +Each JSON element is encoded as a header and a payload. The header +determines type of element (string, numeric, boolean, null, object, or +array) and the size of the payload. The header can be between 1 and +9 bytes in size. The payload can be any size from zero bytes up to the +maximum allowed BLOB size. + +### 3.1 Payload Size + +The upper four bits of the first byte of the header determine size of the +header and possibly also the size of the payload. +If the upper four bits have a value between 0 and 11, then the header is +exactly one byte in size and the payload size is determined by those +upper four bits. If the upper four bits have a value between 12 and 15, +that means that the total header size is 2, 3, 5, or 9 bytes and the +payload size is unsigned big-endian integer that is contained in the +subsequent bytes. The size integer is the one byte that following the +initial header byte if the upper four bits +are 12, two bytes if the upper bits are 13, four bytes if the upper bits +are 14, and eight bytes if the upper bits are 15. The current design +of SQLite does not support BLOB values larger than 2GiB, so the eight-byte +variant of the payload size integer will never be used by the current code. +The eight-byte payload size integer is included in the specification +to allow for future expansion. + +The header for an element does *not* need to be in its simplest +form. For example, consider the JSON numeric value "`1`". +That element can be encode in five different ways: + + * `0x13 0x31` + * `0xc3 0x01 0x31` + * `0xd3 0x00 0x01 0x31` + * `0xe3 0x00 0x00 0x00 0x01 0x31` + * `0xf3 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x31` + +The shortest encoding is preferred, of course, and usually happens with +primitive elements such as numbers. However the total size of an array +or object might not be known exactly when the header of the element is +first generated. It is convenient to reserve space for the largest +possible header and then go back and fill in the correct payload size +at the end. This technique can result in array or object headers that +are larger than absolutely necessary. + +### 3.2 Element Type + +The least-significant four bits of the first byte of the header (the first +byte masked against 0x0f) determine element type. The following codes are +used: + +
      +
    1. NULL → +The element is a JSON "null". The payload size for a true JSON NULL must +must be zero. Future versions of SQLite might extend the JSONB format +with elements that have a zero element type but a non-zero size. In that +way, legacy versions of SQLite will interpret the element as a NULL +for backwards compatibility while newer versions will interpret the +element in some other way. + +

    2. TRUE → +The element is a JSON "true". The payload size must be zero for a actual +"true" value. Elements with type 1 and a non-zero payload size are +reserved for future expansion. Legacy implementations that see an element +type of 1 with a non-zero payload size should continue to interpret that +element as "true" for compatibility. + +

    3. FALSE → +The element is a JSON "false". The payload size must be zero for a actual +"false" value. Elements with type 2 and a non-zero payload size are +reserved for future expansion. Legacy implementations that see an element +type of 2 with a non-zero payload size should continue to interpret that +element as "false" for compatibility. + +

    4. INT → +The element is a JSON integer value in the canonical +RFC 8259 format, without extensions. The payload is the ASCII +text representation of that numeric value. + +

    5. INT5 → +The element is a JSON integer value that is not in the +canonical format. The payload is the ASCII +text representation of that numeric value. Because the payload is in a +non-standard format, it will need to be translated when the JSONB is +converted into RFC 8259 text JSON. + +

    6. FLOAT → +The element is a JSON floating-point value in the canonical +RFC 8259 format, without extensions. The payload is the ASCII +text representation of that numeric value. + +

    7. FLOAT5 → +The element is a JSON floating-point value that is not in the +canonical format. The payload is the ASCII +text representation of that numeric value. Because the payload is in a +non-standard format, it will need to be translated when the JSONB is +converted into RFC 8259 text JSON. + +

    8. TEXT → +The element is a JSON string value that does not contain +any escapes nor any characters that need to be escaped for either SQL or +JSON. The payload is the UTF8 text representation of the string value. +The payload does not include string delimiters. + +

    9. TEXTJ → +The element is a JSON string value that contains +RFC 8259 character escapes (such as "\n" or "\u0020"). +Those escapes will need to be translated into actual UTF8 if this element +is [json_extract|extracted] into SQL. +The payload is the UTF8 text representation of the escaped string value. +The payload does not include string delimiters. + +

    10. TEXT5 → +The element is a JSON string value that contains +character escapes, including some character escapes that part of JSON5 +and which are not found in the canonical RFC 8259 spec. +Those escapes will need to be translated into standard JSON prior to +rendering the JSON as text, or into their actual UTF8 characters if this +element is [json_extract|extracted] into SQL. +The payload is the UTF8 text representation of the escaped string value. +The payload does not include string delimiters. + +

    11. TEXTRAW → +The element is a JSON string value that contains +UTF8 characters that need to be escaped if this string is rendered into +standard JSON text. +The payload does not include string delimiters. + +

    12. ARRAY → +The element is a JSON array. The payload contains +JSONB elements that comprise values contained within the array. + +

    13. OBJECT → +The element is a JSON object. The payload contains +pairs of JSONB elements that comprise entries for the JSON object. +The first element in each pair must be a string (types 7 through 10). +The second element of each pair may be any types, including nested +arrays or objects. + +

    14. RESERVED-13 → +Reserved for future expansion. Legacy implements that encounter this +element type should raise an error. + +

    15. RESERVED-14 → +Reserved for future expansion. Legacy implements that encounter this +element type should raise an error. + +

    16. RESERVED-15 → +Reserved for future expansion. Legacy implements that encounter this +element type should raise an error. +

    + +Element types outside the range of 0 to 12 are reserved for future +expansion. The current implement raises an error if see an element type +other than those listed above. However, future versions of SQLite might +use of the three remaining element types to implement indexing or similar +optimizations, to speed up lookup against large JSON arrays and/or objects. + +### 3.3 Design Rationale For Element Types + +A key goal of JSONB is that it should be quick to translate +to and from text JSON and/or be constructed from SQL values. +When converting from text into JSONB, we do not want the +converter subroutine to burn CPU cycles converting elements +values into some standard format which might never be used. +Format conversion is "lazy" - it is deferred until actually +needed. This has implications for the JSONB format design: + + 1. Numeric values are stored as text, not a numbers. The values are + a direct copy of the text JSON values from which they are derived. + + 2. There are multiple element types depending on the details of value + formats. For example, INT is used for pure RFC-8259 integer + literals and INT5 exists for JSON5 extensions such as hexadecimal + notation. FLOAT is used for pure RFC-8259 floating point literals + and FLOAT5 is used for JSON5 extensions. There are four different + representations of strings, depending on where the string came from + and how special characters within the string are escaped. + +A second goal of JSONB is that it should be capable of serving as the +"parse tree" for JSON when a JSON value is being processed by the +various [JSON SQL functions] built into SQLite. Before JSONB was +developed, operations such [json_replace()] and [json_patch()] +and similar worked in three stages: + + + 1. Translate the text JSON into a internal format that is + easier to scan and edit. + 2. Perform the requested operation on the JSON. + 3. Translate the internal format back into text. + +JSONB seeks to serve as the internal format directly - bypassing +the first and third stages of that process. Since most of the CPU +cycles are spent on the first and third stages, that suggests that +JSONB processing will be much faster than text JSON processing. + +So when processing JSONB, only the second stage of the three-stage +process is required. But when processing text JSON, it is still necessary +to do stages one and three. If JSONB is to be used as the internal +binary representation, this is yet another reason to store numeric +values as text. Storing numbers as text minimizes the amount of +conversion work needed for stages one and three. This is also why +there are four different representations of text in JSONB. Different +text representations are used for text coming from different sources +(RFC-8259 JSON, JSON5, or SQL string values) and conversions only +happen if and when they are actually needed. + +### 3.4 Valid JSONB BLOBs + +A valid JSONB BLOB consists of a single JSON element. The element must +exactly fill the BLOB. This one element is often a JSON object or array +and those usually contain additional elements as its payload, but the +element can be a primite value such a string, number, boolean, or null. + +When the built-in JSON functions are attempting to determine if a BLOB +argument is a JSONB or just a random BLOB, they look at the header of +the outer element to see that it is well-formed and that the element +completely fills the BLOB. If these conditions are met, then the BLOB +is accepted as a JSONB value. diff --git a/doc/testrunner.md b/doc/testrunner.md index d828fd76d..d420076c4 100644 --- a/doc/testrunner.md +++ b/doc/testrunner.md @@ -2,6 +2,26 @@ # The testrunner.tcl Script + + + # 1. Overview testrunner.tcl is a Tcl script used to run multiple SQLite tests using @@ -44,6 +64,7 @@ Sometimes testrunner.tcl uses the [testfixture] binary that it is run with to run tests (see "Binary Tests" below). Sometimes it builds testfixture and other binaries in specific configurations to test (see "Source Tests"). + # 2. Binary Tests The commands described in this section all run various combinations of the Tcl @@ -61,6 +82,7 @@ these tests is therefore: The following sub-sections describe the various options that can be passed to testrunner.tcl to test binary testfixture builds. + ## 2.1. Organization of Tcl Tests Tcl tests are stored in files that match the pattern *\*.test*. They are @@ -91,6 +113,7 @@ Running **all** tests is to run all tests in the full test set, plus a dozen or so permutations. The specific permutations that are run as part of "all" are defined in file *testrunner_data.tcl*. + ## 2.2. Commands to Run Tests To run the "veryquick" test set, use either of the following: @@ -114,6 +137,12 @@ a specified pattern (e.g. all tests that start with "fts5"), either of: ./testfixture $TESTDIR/testrunner.tcl 'fts5*' ``` +Strictly speaking, for a test to be run the pattern must match the script +filename, not including the directory, using the rules of Tcl's +\[string match\] command. Except that before the matching is done, any "%" +characters specified as part of the pattern are transformed to "\*". + + To run "all" tests (full + permutations): ``` @@ -141,6 +170,7 @@ Or, if the failure occured as part of a permutation: TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT? + # 3. Source Code Tests The commands described in this section invoke the C compiler to build @@ -159,7 +189,8 @@ shell that supports SQLite 3.31.1 or newer via "package require sqlite3". TODO: ./configure + Makefile.msc build systems. -## Commands to Run SQLite Tests + +## 3.1. Commands to Run SQLite Tests The **mdevtest** command is equivalent to running the veryquick tests and the [make fuzztest] target once for each of two --enable-all builds - one @@ -201,7 +232,18 @@ of the specific tests run. tclsh $TESTDIR/testrunner.tcl release ``` -## Running ZipVFS Tests +As with source code tests, one or more patterns +may be appended to any of the above commands (mdevtest, sdevtest or release). +In that case only Tcl tests (no fuzz or other tests) that match the specified +pattern are run. For example, to run the just the Tcl rtree tests in all +builds and configurations supported by "release": + +``` + tclsh $TESTDIR/testrunner.tcl release rtree% +``` + + +## 3.2. Running ZipVFS Tests testrunner.tcl can build a zipvfs-enabled testfixture and use it to run tests from the Zipvfs project with the following command: @@ -217,7 +259,8 @@ test both SQLite and Zipvfs with a single command: tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest ``` -## Investigating Source Code Test Failures + +## 3.3. Investigating Source Code Test Failures Investigating a test failure that occurs during source code testing is a two step process: @@ -244,9 +287,31 @@ target to build. This may be used either to run a [make] command test directly, or else to build a testfixture (or testfixture.exe) binary with which to run a Tcl test script, as described above. + +# 4. Extra testrunner.tcl Options + +The testrunner.tcl script options in this section may be used with both source +code and binary tests. + +The **--buildonly** option instructs testrunner.tcl just to build the binaries +required by a test, not to run any actual tests. For example: +``` + # Build binaries required by release test. + tclsh $TESTDIR/testrunner.tcl --buildonly release" +``` -# 4. Controlling CPU Core Utilization +The **--dryrun** option prevents testrunner.tcl from building any binaries +or running any tests. Instead, it just writes the shell commands that it +would normally execute into the testrunner.log file. Example: + +``` + # Log the shell commmands that make up the mdevtest test. + tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest" +``` + + +# 5. Controlling CPU Core Utilization When running either binary or source code tests, testrunner.tcl reports the number of jobs it intends to use to stdout. e.g. @@ -277,8 +342,3 @@ testrunner.log and testrunner.db files: - - - - - diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c index 6b412feef..3acb0daa2 100755 --- a/ext/consio/console_io.c +++ b/ext/consio/console_io.c @@ -24,9 +24,11 @@ # include # include # include -# include "console_io.h" # include "sqlite3.h" #endif +#ifndef HAVE_CONSOLE_IO_H +# include "console_io.h" +#endif #ifndef SQLITE_CIO_NO_TRANSLATE # if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT @@ -549,12 +551,11 @@ zSkipValidUtf8(const char *z, int nAccept, long ccm){ #endif /*!(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE))*/ #ifndef SQLITE_CIO_NO_TRANSLATE - -#ifdef CONSIO_SPUTB +# ifdef CONSIO_SPUTB SQLITE_INTERNAL_LINKAGE int fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){ assert(pfO!=0); -# if CIO_WIN_WC_XLATE +# if CIO_WIN_WC_XLATE PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); if( pstReachesConsole(ppst) ){ @@ -564,13 +565,13 @@ fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){ if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); return rv; }else { -# endif +# endif return (int)fwrite(cBuf, 1, nAccept, pfO); -# if CIO_WIN_WC_XLATE +# if CIO_WIN_WC_XLATE } -# endif +# endif } -#endif /* defined(CONSIO_SPUTB) */ +# endif SQLITE_INTERNAL_LINKAGE int oPutbUtf8(const char *cBuf, int nAccept){ diff --git a/ext/consio/console_io.h b/ext/consio/console_io.h index 21697d595..26fd7dd94 100644 --- a/ext/consio/console_io.h +++ b/ext/consio/console_io.h @@ -28,7 +28,7 @@ ** CIO_WIN_WC_XLATE is defined as 0 or 1, reflecting whether console I/O ** translation for Windows is effected for the build. */ - +#define HAVE_CONSOLE_IO_H 1 #ifndef SQLITE_INTERNAL_LINKAGE # define SQLITE_INTERNAL_LINKAGE extern /* external to translation unit */ # include @@ -166,8 +166,8 @@ SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z); #ifdef CONSIO_SPUTB SQLITE_INTERNAL_LINKAGE int fPutbUtf8(FILE *pfOut, const char *cBuf, int nAccept); -#endif /* Like fPutbUtf8 except stream is always the designated output. */ +#endif SQLITE_INTERNAL_LINKAGE int oPutbUtf8(const char *cBuf, int nAccept); /* Like fPutbUtf8 except stream is always the designated error. */ diff --git a/ext/fts5/extract_api_docs.tcl b/ext/fts5/extract_api_docs.tcl index 2320d70b7..6762a036d 100644 --- a/ext/fts5/extract_api_docs.tcl +++ b/ext/fts5/extract_api_docs.tcl @@ -223,10 +223,12 @@ proc main {data} { Fts5ExtensionApi { set struct [get_fts5_struct $data "^struct Fts5ExtensionApi" "^.;"] set map [list] + set lKey [list] foreach {k v} [get_struct_members $data] { if {[string match x* $k]==0} continue - lappend map $k "$k" + lappend lKey $k } + foreach k [lsort -decr $lKey] { lappend map $k "$k" } output [string map $map $struct] } diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h index 323d73a28..250d2ee7e 100644 --- a/ext/fts5/fts5.h +++ b/ext/fts5/fts5.h @@ -88,8 +88,11 @@ struct Fts5PhraseIter { ** created with the "columnsize=0" option. ** ** xColumnText: -** This function attempts to retrieve the text of column iCol of the -** current document. If successful, (*pz) is set to point to a buffer +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the text of column iCol of +** the current document. If successful, (*pz) is set to point to a buffer ** containing the text in utf-8 encoding, (*pn) is set to the size in bytes ** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, ** if an error occurs, an SQLite error code is returned and the final values @@ -99,8 +102,10 @@ struct Fts5PhraseIter { ** Returns the number of phrases in the current query expression. ** ** xPhraseSize: -** Returns the number of tokens in phrase iPhrase of the query. Phrases -** are numbered starting from zero. +** If parameter iCol is less than zero, or greater than or equal to the +** number of phrases in the current query, as returned by xPhraseCount, +** 0 is returned. Otherwise, this function returns the number of tokens in +** phrase iPhrase of the query. Phrases are numbered starting from zero. ** ** xInstCount: ** Set *pnInst to the total number of occurrences of all phrases within @@ -116,12 +121,13 @@ struct Fts5PhraseIter { ** Query for the details of phrase match iIdx within the current row. ** Phrase matches are numbered starting from zero, so the iIdx argument ** should be greater than or equal to zero and smaller than the value -** output by xInstCount(). +** output by xInstCount(). If iIdx is less than zero or greater than +** or equal to the value returned by xInstCount(), SQLITE_RANGE is returned. ** -** Usually, output parameter *piPhrase is set to the phrase number, *piCol +** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol ** to the column in which it occurs and *piOff the token offset of the -** first token of the phrase. Returns SQLITE_OK if successful, or an error -** code (i.e. SQLITE_NOMEM) if an error occurs. +** first token of the phrase. SQLITE_OK is returned if successful, or an +** error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the ** "detail=none" or "detail=column" option. @@ -147,6 +153,10 @@ struct Fts5PhraseIter { ** Invoking Api.xUserData() returns a copy of the pointer passed as ** the third argument to pUserData. ** +** If parameter iPhrase is less than zero, or greater than or equal to +** the number of phrases in the query, as returned by xPhraseCount(), +** this function returns SQLITE_RANGE. +** ** If the callback function returns any value other than SQLITE_OK, the ** query is abandoned and the xQueryPhrase function returns immediately. ** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. @@ -261,9 +271,42 @@ struct Fts5PhraseIter { ** ** xPhraseNextColumn() ** See xPhraseFirstColumn above. +** +** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase iPhrase of the current +** query. Before returning, output parameter *ppToken is set to point +** to a buffer containing the requested token, and *pnToken to the +** size of this buffer in bytes. +** +** If iPhrase or iToken are less than zero, or if iPhrase is greater than +** or equal to the number of phrases in the query as reported by +** xPhraseCount(), or if iToken is equal to or greater than the number of +** tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken + are both zeroed. +** +** The output text is not a copy of the query text that specified the +** token. It is the output of the tokenizer module. For tokendata=1 +** tables, this includes any embedded 0x00 and trailing data. +** +** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase hit iIdx within the +** current row. If iIdx is less than zero or greater than or equal to the +** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, +** output variable (*ppToken) is set to point to a buffer containing the +** matching document token, and (*pnToken) to the size of that buffer in +** bytes. This API is not available if the specified token matches a +** prefix query term. In that case both output variables are always set +** to 0. +** +** The output text is not a copy of the document text that was tokenized. +** It is the output of the tokenizer module. For tokendata=1 tables, this +** includes any embedded 0x00 and trailing data. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 2 */ + int iVersion; /* Currently always set to 3 */ void *(*xUserData)(Fts5Context*); @@ -298,6 +341,13 @@ struct Fts5ExtensionApi { int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); + + /* Below this point are iVersion>=3 only */ + int (*xQueryToken)(Fts5Context*, + int iPhrase, int iToken, + const char **ppToken, int *pnToken + ); + int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*); }; /* diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 8bbafbaaf..9beb26e05 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -196,6 +196,7 @@ struct Fts5Config { char *zContent; /* content table */ char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ + int bTokendata; /* "tokendata=" option value (dflt==0) */ int eDetail; /* FTS5_DETAIL_XXX value */ char *zContentExprlist; Fts5Tokenizer *pTok; @@ -384,17 +385,19 @@ struct Fts5IndexIter { /* ** Values used as part of the flags argument passed to IndexQuery(). */ -#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */ -#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */ -#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ -#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ +#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */ +#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */ +#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ +#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ /* The following are used internally by the fts5_index.c module. They are ** defined here only to make it easier to avoid clashes with the flags ** above. */ -#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 -#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 -#define FTS5INDEX_QUERY_SKIPHASH 0x0040 +#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 +#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 +#define FTS5INDEX_QUERY_SKIPHASH 0x0040 +#define FTS5INDEX_QUERY_NOTOKENDATA 0x0080 +#define FTS5INDEX_QUERY_SCANONETERM 0x0100 /* ** Create/destroy an Fts5Index object. @@ -463,6 +466,10 @@ void *sqlite3Fts5StructureRef(Fts5Index*); void sqlite3Fts5StructureRelease(void*); int sqlite3Fts5StructureTest(Fts5Index*, void*); +/* +** Used by xInstToken(): +*/ +int sqlite3Fts5IterToken(Fts5IndexIter*, i64, int, int, const char**, int*); /* ** Insert or remove data to or from the index. Each time a document is @@ -540,6 +547,13 @@ int sqlite3Fts5IndexLoadConfig(Fts5Index *p); int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin); int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid); +void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter*); + +/* Used to populate hash tables for xInstToken in detail=none/column mode. */ +int sqlite3Fts5IndexIterWriteTokendata( + Fts5IndexIter*, const char*, int, i64 iRowid, int iCol, int iOff +); + /* ** End of interface to code in fts5_index.c. **************************************************************************/ @@ -645,6 +659,7 @@ void sqlite3Fts5HashScanNext(Fts5Hash*); int sqlite3Fts5HashScanEof(Fts5Hash*); void sqlite3Fts5HashScanEntry(Fts5Hash *, const char **pzTerm, /* OUT: term (nul-terminated) */ + int *pnTerm, /* OUT: Size of term in bytes */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ); @@ -771,6 +786,10 @@ int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**); int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *); +int sqlite3Fts5ExprQueryToken(Fts5Expr*, int, int, const char**, int*); +int sqlite3Fts5ExprInstToken(Fts5Expr*, i64, int, int, int, int, const char**, int*); +void sqlite3Fts5ExprClearTokens(Fts5Expr*); + /******************************************* ** The fts5_expr.c API above this point is used by the other hand-written ** C code in this module. The interfaces below this point are called by diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c index fa58b9aac..30101fbe2 100644 --- a/ext/fts5/fts5_aux.c +++ b/ext/fts5/fts5_aux.c @@ -211,6 +211,14 @@ static int fts5HighlightCb( } if( iPos==p->iRangeEnd ){ + if( p->bOpen ){ + if( p->iter.iStart>=0 && iPos>=p->iter.iStart ){ + fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); + p->iOff = iEndOff; + } + fts5HighlightAppend(&rc, p, p->zClose, -1); + p->bOpen = 0; + } fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); p->iOff = iEndOff; } @@ -244,8 +252,10 @@ static void fts5HighlightFunction( ctx.zClose = (const char*)sqlite3_value_text(apVal[2]); ctx.iRangeEnd = -1; rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn); - - if( ctx.zIn ){ + if( rc==SQLITE_RANGE ){ + sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); + rc = SQLITE_OK; + }else if( ctx.zIn ){ if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter); } diff --git a/ext/fts5/fts5_buffer.c b/ext/fts5/fts5_buffer.c index b9614e129..891ef0203 100644 --- a/ext/fts5/fts5_buffer.c +++ b/ext/fts5/fts5_buffer.c @@ -68,6 +68,7 @@ void sqlite3Fts5BufferAppendBlob( ){ if( nData ){ if( fts5BufferGrow(pRc, pBuf, nData) ) return; + assert( pBuf->p!=0 ); memcpy(&pBuf->p[pBuf->n], pData, nData); pBuf->n += nData; } @@ -169,6 +170,7 @@ int sqlite3Fts5PoslistNext64( i64 *piOff /* IN/OUT: Current offset */ ){ int i = *pi; + assert( a!=0 || i==0 ); if( i>=n ){ /* EOF */ *piOff = -1; @@ -176,6 +178,7 @@ int sqlite3Fts5PoslistNext64( }else{ i64 iOff = *piOff; u32 iVal; + assert( a!=0 ); fts5FastGetVarint32(a, i, iVal); if( iVal<=1 ){ if( iVal==0 ){ diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c index 5d0770502..d2e8309cd 100644 --- a/ext/fts5/fts5_config.c +++ b/ext/fts5/fts5_config.c @@ -398,6 +398,16 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("tokendata", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed tokendata=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bTokendata = (zArg[0]=='1'); + } + return rc; + } + *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd); return SQLITE_ERROR; } diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index f5101ba06..05c1b59c1 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -100,7 +100,9 @@ struct Fts5ExprNode { struct Fts5ExprTerm { u8 bPrefix; /* True for a prefix term */ u8 bFirst; /* True if token must be first in column */ - char *zTerm; /* nul-terminated term */ + char *pTerm; /* Term data */ + int nQueryTerm; /* Effective size of term in bytes */ + int nFullTerm; /* Size of term in bytes incl. tokendata */ Fts5IndexIter *pIter; /* Iterator for this term */ Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */ }; @@ -967,7 +969,7 @@ static int fts5ExprNearInitAll( p->pIter = 0; } rc = sqlite3Fts5IndexQuery( - pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), + pExpr->pIndex, p->pTerm, p->nQueryTerm, (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), pNear->pColset, @@ -1604,7 +1606,7 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){ Fts5ExprTerm *pSyn; Fts5ExprTerm *pNext; Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; - sqlite3_free(pTerm->zTerm); + sqlite3_free(pTerm->pTerm); sqlite3Fts5IterClose(pTerm->pIter); for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){ pNext = pSyn->pSynonym; @@ -1702,6 +1704,7 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset( typedef struct TokenCtx TokenCtx; struct TokenCtx { Fts5ExprPhrase *pPhrase; + Fts5Config *pConfig; int rc; }; @@ -1735,8 +1738,12 @@ static int fts5ParseTokenize( rc = SQLITE_NOMEM; }else{ memset(pSyn, 0, (size_t)nByte); - pSyn->zTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); - memcpy(pSyn->zTerm, pToken, nToken); + pSyn->pTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); + pSyn->nFullTerm = pSyn->nQueryTerm = nToken; + if( pCtx->pConfig->bTokendata ){ + pSyn->nQueryTerm = (int)strlen(pSyn->pTerm); + } + memcpy(pSyn->pTerm, pToken, nToken); pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym; pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn; } @@ -1761,7 +1768,11 @@ static int fts5ParseTokenize( if( rc==SQLITE_OK ){ pTerm = &pPhrase->aTerm[pPhrase->nTerm++]; memset(pTerm, 0, sizeof(Fts5ExprTerm)); - pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken); + pTerm->pTerm = sqlite3Fts5Strndup(&rc, pToken, nToken); + pTerm->nFullTerm = pTerm->nQueryTerm = nToken; + if( pCtx->pConfig->bTokendata && rc==SQLITE_OK ){ + pTerm->nQueryTerm = (int)strlen(pTerm->pTerm); + } } } @@ -1828,6 +1839,7 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm( memset(&sCtx, 0, sizeof(TokenCtx)); sCtx.pPhrase = pAppend; + sCtx.pConfig = pConfig; rc = fts5ParseStringFromToken(pToken, &z); if( rc==SQLITE_OK ){ @@ -1875,12 +1887,15 @@ int sqlite3Fts5ExprClonePhrase( Fts5Expr **ppNew ){ int rc = SQLITE_OK; /* Return code */ - Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */ + Fts5ExprPhrase *pOrig = 0; /* The phrase extracted from pExpr */ Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ - TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */ - - pOrig = pExpr->apExprPhrase[iPhrase]; - pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); + TokenCtx sCtx = {0,0,0}; /* Context object for fts5ParseTokenize */ + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + rc = SQLITE_RANGE; + }else{ + pOrig = pExpr->apExprPhrase[iPhrase]; + pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); + } if( rc==SQLITE_OK ){ pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase*)); @@ -1893,7 +1908,7 @@ int sqlite3Fts5ExprClonePhrase( pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)); } - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(pOrig!=0) ){ Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset; if( pColsetOrig ){ sqlite3_int64 nByte; @@ -1907,26 +1922,27 @@ int sqlite3Fts5ExprClonePhrase( } } - if( pOrig->nTerm ){ - int i; /* Used to iterate through phrase terms */ - for(i=0; rc==SQLITE_OK && inTerm; i++){ - int tflags = 0; - Fts5ExprTerm *p; - for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ - const char *zTerm = p->zTerm; - rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), - 0, 0); - tflags = FTS5_TOKEN_COLOCATED; - } - if( rc==SQLITE_OK ){ - sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; - sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; + if( rc==SQLITE_OK ){ + if( pOrig->nTerm ){ + int i; /* Used to iterate through phrase terms */ + sCtx.pConfig = pExpr->pConfig; + for(i=0; rc==SQLITE_OK && inTerm; i++){ + int tflags = 0; + Fts5ExprTerm *p; + for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ + rc = fts5ParseTokenize((void*)&sCtx,tflags,p->pTerm,p->nFullTerm,0,0); + tflags = FTS5_TOKEN_COLOCATED; + } + if( rc==SQLITE_OK ){ + sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; + } } + }else{ + /* This happens when parsing a token or quoted phrase that contains + ** no token characters at all. (e.g ... MATCH '""'). */ + sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } - }else{ - /* This happens when parsing a token or quoted phrase that contains - ** no token characters at all. (e.g ... MATCH '""'). */ - sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } if( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){ @@ -2296,11 +2312,13 @@ static Fts5ExprNode *fts5ParsePhraseToAnd( if( parseGrowPhraseArray(pParse) ){ fts5ExprPhraseFree(pPhrase); }else{ + Fts5ExprTerm *p = &pNear->apPhrase[0]->aTerm[ii]; + Fts5ExprTerm *pTo = &pPhrase->aTerm[0]; pParse->apPhrase[pParse->nPhrase++] = pPhrase; pPhrase->nTerm = 1; - pPhrase->aTerm[0].zTerm = sqlite3Fts5Strndup( - &pParse->rc, pNear->apPhrase[0]->aTerm[ii].zTerm, -1 - ); + pTo->pTerm = sqlite3Fts5Strndup(&pParse->rc, p->pTerm, p->nFullTerm); + pTo->nQueryTerm = p->nQueryTerm; + pTo->nFullTerm = p->nFullTerm; pRet->apChild[ii] = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, sqlite3Fts5ParseNearset(pParse, 0, pPhrase) ); @@ -2485,16 +2503,17 @@ static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ /* Determine the maximum amount of space required. */ for(p=pTerm; p; p=p->pSynonym){ - nByte += (int)strlen(pTerm->zTerm) * 2 + 3 + 2; + nByte += pTerm->nQueryTerm * 2 + 3 + 2; } zQuoted = sqlite3_malloc64(nByte); if( zQuoted ){ int i = 0; for(p=pTerm; p; p=p->pSynonym){ - char *zIn = p->zTerm; + char *zIn = p->pTerm; + char *zEnd = &zIn[p->nQueryTerm]; zQuoted[i++] = '"'; - while( *zIn ){ + while( zInnTerm; iTerm++){ - char *zTerm = pPhrase->aTerm[iTerm].zTerm; - zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm); + Fts5ExprTerm *p = &pPhrase->aTerm[iTerm]; + zRet = fts5PrintfAppend(zRet, "%s%.*s", iTerm==0?"":" ", + p->nQueryTerm, p->pTerm + ); if( pPhrase->aTerm[iTerm].bPrefix ){ zRet = fts5PrintfAppend(zRet, "*"); } @@ -2974,6 +2995,17 @@ static int fts5ExprColsetTest(Fts5Colset *pColset, int iCol){ return 0; } +/* +** pToken is a buffer nToken bytes in size that may or may not contain +** an embedded 0x00 byte. If it does, return the number of bytes in +** the buffer before the 0x00. If it does not, return nToken. +*/ +static int fts5QueryTerm(const char *pToken, int nToken){ + int ii; + for(ii=0; iipExpr; int i; + int nQuery = nToken; + i64 iRowid = pExpr->pRoot->iRowid; UNUSED_PARAM2(iUnused1, iUnused2); - if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + if( nQuery>FTS5_MAX_TOKEN_SIZE ) nQuery = FTS5_MAX_TOKEN_SIZE; + if( pExpr->pConfig->bTokendata ){ + nQuery = fts5QueryTerm(pToken, nQuery); + } if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++; for(i=0; inPhrase; i++){ - Fts5ExprTerm *pTerm; + Fts5ExprTerm *pT; if( p->aPopulator[i].bOk==0 ) continue; - for(pTerm=&pExpr->apExprPhrase[i]->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){ - int nTerm = (int)strlen(pTerm->zTerm); - if( (nTerm==nToken || (nTermbPrefix)) - && memcmp(pTerm->zTerm, pToken, nTerm)==0 + for(pT=&pExpr->apExprPhrase[i]->aTerm[0]; pT; pT=pT->pSynonym){ + if( (pT->nQueryTerm==nQuery || (pT->nQueryTermbPrefix)) + && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0 ){ int rc = sqlite3Fts5PoslistWriterAppend( &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff ); + if( rc==SQLITE_OK && pExpr->pConfig->bTokendata && !pT->bPrefix ){ + int iCol = p->iOff>>32; + int iTokOff = p->iOff & 0x7FFFFFFF; + rc = sqlite3Fts5IndexIterWriteTokendata( + pT->pIter, pToken, nToken, iRowid, iCol, iTokOff + ); + } if( rc ) return rc; break; } @@ -3135,3 +3178,80 @@ int sqlite3Fts5ExprPhraseCollist( return rc; } + +/* +** Does the work of the fts5_api.xQueryToken() API method. +*/ +int sqlite3Fts5ExprQueryToken( + Fts5Expr *pExpr, + int iPhrase, + int iToken, + const char **ppOut, + int *pnOut +){ + Fts5ExprPhrase *pPhrase = 0; + + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + return SQLITE_RANGE; + } + pPhrase = pExpr->apExprPhrase[iPhrase]; + if( iToken<0 || iToken>=pPhrase->nTerm ){ + return SQLITE_RANGE; + } + + *ppOut = pPhrase->aTerm[iToken].pTerm; + *pnOut = pPhrase->aTerm[iToken].nFullTerm; + return SQLITE_OK; +} + +/* +** Does the work of the fts5_api.xInstToken() API method. +*/ +int sqlite3Fts5ExprInstToken( + Fts5Expr *pExpr, + i64 iRowid, + int iPhrase, + int iCol, + int iOff, + int iToken, + const char **ppOut, + int *pnOut +){ + Fts5ExprPhrase *pPhrase = 0; + Fts5ExprTerm *pTerm = 0; + int rc = SQLITE_OK; + + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + return SQLITE_RANGE; + } + pPhrase = pExpr->apExprPhrase[iPhrase]; + if( iToken<0 || iToken>=pPhrase->nTerm ){ + return SQLITE_RANGE; + } + pTerm = &pPhrase->aTerm[iToken]; + if( pTerm->bPrefix==0 ){ + if( pExpr->pConfig->bTokendata ){ + rc = sqlite3Fts5IterToken( + pTerm->pIter, iRowid, iCol, iOff+iToken, ppOut, pnOut + ); + }else{ + *ppOut = pTerm->pTerm; + *pnOut = pTerm->nFullTerm; + } + } + return rc; +} + +/* +** Clear the token mappings for all Fts5IndexIter objects mannaged by +** the expression passed as the only argument. +*/ +void sqlite3Fts5ExprClearTokens(Fts5Expr *pExpr){ + int ii; + for(ii=0; iinPhrase; ii++){ + Fts5ExprTerm *pT; + for(pT=&pExpr->apExprPhrase[ii]->aTerm[0]; pT; pT=pT->pSynonym){ + sqlite3Fts5IndexIterClearTokendata(pT->pIter); + } + } +} diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c index 391791c7a..5e0959aa8 100644 --- a/ext/fts5/fts5_hash.c +++ b/ext/fts5/fts5_hash.c @@ -36,10 +36,15 @@ struct Fts5Hash { /* ** Each entry in the hash table is represented by an object of the -** following type. Each object, its key (a nul-terminated string) and -** its current data are stored in a single memory allocation. The -** key immediately follows the object in memory. The position list -** data immediately follows the key data in memory. +** following type. Each object, its key, and its current data are stored +** in a single memory allocation. The key immediately follows the object +** in memory. The position list data immediately follows the key data +** in memory. +** +** The key is Fts5HashEntry.nKey bytes in size. It consists of a single +** byte identifying the index (either the main term index or a prefix-index), +** followed by the term data. For example: "0token". There is no +** nul-terminator - in this case nKey=6. ** ** The data that follows the key is in a similar, but not identical format ** to the doclist data stored in the database. It is: @@ -174,8 +179,7 @@ static int fts5HashResize(Fts5Hash *pHash){ unsigned int iHash; Fts5HashEntry *p = apOld[i]; apOld[i] = p->pHashNext; - iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), - (int)strlen(fts5EntryKey(p))); + iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), p->nKey); p->pHashNext = apNew[iHash]; apNew[iHash] = p; } @@ -259,7 +263,7 @@ int sqlite3Fts5HashWrite( for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ char *zKey = fts5EntryKey(p); if( zKey[0]==bByte - && p->nKey==nToken + && p->nKey==nToken+1 && memcmp(&zKey[1], pToken, nToken)==0 ){ break; @@ -289,9 +293,9 @@ int sqlite3Fts5HashWrite( zKey[0] = bByte; memcpy(&zKey[1], pToken, nToken); assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1) ); - p->nKey = nToken; + p->nKey = nToken+1; zKey[nToken+1] = '\0'; - p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry); + p->nData = nToken+1 + sizeof(Fts5HashEntry); p->pHashNext = pHash->aSlot[iHash]; pHash->aSlot[iHash] = p; pHash->nEntry++; @@ -408,12 +412,17 @@ static Fts5HashEntry *fts5HashEntryMerge( *ppOut = p1; p1 = 0; }else{ - int i = 0; char *zKey1 = fts5EntryKey(p1); char *zKey2 = fts5EntryKey(p2); - while( zKey1[i]==zKey2[i] ) i++; + int nMin = MIN(p1->nKey, p2->nKey); + + int cmp = memcmp(zKey1, zKey2, nMin); + if( cmp==0 ){ + cmp = p1->nKey - p2->nKey; + } + assert( cmp!=0 ); - if( ((u8)zKey1[i])>((u8)zKey2[i]) ){ + if( cmp>0 ){ /* p2 is smaller */ *ppOut = p2; ppOut = &p2->pScanNext; @@ -455,7 +464,7 @@ static int fts5HashEntrySort( Fts5HashEntry *pIter; for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){ if( pTerm==0 - || (pIter->nKey+1>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm)) + || (pIter->nKey>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm)) ){ Fts5HashEntry *pEntry = pIter; pEntry->pScanNext = 0; @@ -494,12 +503,11 @@ int sqlite3Fts5HashQuery( for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ zKey = fts5EntryKey(p); - assert( p->nKey+1==(int)strlen(zKey) ); - if( nTerm==p->nKey+1 && memcmp(zKey, pTerm, nTerm)==0 ) break; + if( nTerm==p->nKey && memcmp(zKey, pTerm, nTerm)==0 ) break; } if( p ){ - int nHashPre = sizeof(Fts5HashEntry) + nTerm + 1; + int nHashPre = sizeof(Fts5HashEntry) + nTerm; int nList = p->nData - nHashPre; u8 *pRet = (u8*)(*ppOut = sqlite3_malloc64(nPre + nList + 10)); if( pRet ){ @@ -560,19 +568,22 @@ int sqlite3Fts5HashScanEof(Fts5Hash *p){ void sqlite3Fts5HashScanEntry( Fts5Hash *pHash, const char **pzTerm, /* OUT: term (nul-terminated) */ + int *pnTerm, /* OUT: Size of term in bytes */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ){ Fts5HashEntry *p; if( (p = pHash->pScan) ){ char *zKey = fts5EntryKey(p); - int nTerm = (int)strlen(zKey); + int nTerm = p->nKey; fts5HashAddPoslistSize(pHash, p, 0); *pzTerm = zKey; - *ppDoclist = (const u8*)&zKey[nTerm+1]; - *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1); + *pnTerm = nTerm; + *ppDoclist = (const u8*)&zKey[nTerm]; + *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm); }else{ *pzTerm = 0; + *pnTerm = 0; *ppDoclist = 0; *pnDoclist = 0; } diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index c467addb8..8eb8f328f 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -323,6 +323,9 @@ typedef struct Fts5SegWriter Fts5SegWriter; typedef struct Fts5Structure Fts5Structure; typedef struct Fts5StructureLevel Fts5StructureLevel; typedef struct Fts5StructureSegment Fts5StructureSegment; +typedef struct Fts5TokenDataIter Fts5TokenDataIter; +typedef struct Fts5TokenDataMap Fts5TokenDataMap; +typedef struct Fts5TombstoneArray Fts5TombstoneArray; struct Fts5Data { u8 *p; /* Pointer to buffer containing record */ @@ -357,6 +360,7 @@ struct Fts5Index { /* Error state. */ int rc; /* Current error code */ + int flushRc; /* State used by the fts5DataXXX() functions. */ sqlite3_blob *pReader; /* RO incr-blob open on %_data table */ @@ -365,6 +369,7 @@ struct Fts5Index { sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */ sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */ sqlite3_stmt *pIdxSelect; + sqlite3_stmt *pIdxNextSelect; int nRead; /* Total number of blocks read */ sqlite3_stmt *pDeleteFromIdx; @@ -518,8 +523,7 @@ struct Fts5SegIter { Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ i64 iLeafOffset; /* Byte offset within current leaf */ - Fts5Data **apTombstone; /* Array of tombstone pages */ - int nTombstone; + Fts5TombstoneArray *pTombArray; /* Array of tombstone pages */ /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); @@ -546,6 +550,15 @@ struct Fts5SegIter { u8 bDel; /* True if the delete flag is set */ }; +/* +** Array of tombstone pages. Reference counted. +*/ +struct Fts5TombstoneArray { + int nRef; /* Number of pointers to this object */ + int nTombstone; + Fts5Data *apTombstone[1]; /* Array of tombstone pages */ +}; + /* ** Argument is a pointer to an Fts5Data structure that contains a ** leaf page. @@ -590,9 +603,16 @@ struct Fts5SegIter { ** poslist: ** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered. ** There is no way to tell if this is populated or not. +** +** pColset: +** If not NULL, points to an object containing a set of column indices. +** Only matches that occur in one of these columns will be returned. +** The Fts5Iter does not own the Fts5Colset object, and so it is not +** freed when the iterator is closed - it is owned by the upper layer. */ struct Fts5Iter { Fts5IndexIter base; /* Base class containing output vars */ + Fts5TokenDataIter *pTokenDataIter; Fts5Index *pIndex; /* Index that owns this iterator */ Fts5Buffer poslist; /* Buffer containing current poslist */ @@ -610,7 +630,6 @@ struct Fts5Iter { Fts5SegIter aSeg[1]; /* Array of segment iterators */ }; - /* ** An instance of the following type is used to iterate through the contents ** of a doclist-index record. @@ -1528,9 +1547,9 @@ static int fts5DlidxLvlNext(Fts5DlidxLvl *pLvl){ } if( iOffnn ){ - i64 iVal; + u64 iVal; pLvl->iLeafPgno += (iOff - pLvl->iOff) + 1; - iOff += fts5GetVarint(&pData->p[iOff], (u64*)&iVal); + iOff += fts5GetVarint(&pData->p[iOff], &iVal); pLvl->iRowid += iVal; pLvl->iOff = iOff; }else{ @@ -1909,18 +1928,20 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ } /* -** Allocate a tombstone hash page array (pIter->apTombstone) for the -** iterator passed as the second argument. If an OOM error occurs, leave -** an error in the Fts5Index object. +** Allocate a tombstone hash page array object (pIter->pTombArray) for +** the iterator passed as the second argument. If an OOM error occurs, +** leave an error in the Fts5Index object. */ static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){ const int nTomb = pIter->pSeg->nPgTombstone; if( nTomb>0 ){ - Fts5Data **apTomb = 0; - apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb); - if( apTomb ){ - pIter->apTombstone = apTomb; - pIter->nTombstone = nTomb; + int nByte = nTomb * sizeof(Fts5Data*) + sizeof(Fts5TombstoneArray); + Fts5TombstoneArray *pNew; + pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte); + if( pNew ){ + pNew->nTombstone = nTomb; + pNew->nRef = 1; + pIter->pTombArray = pNew; } } } @@ -2177,15 +2198,16 @@ static void fts5SegIterNext_None( }else{ const u8 *pList = 0; const char *zTerm = 0; + int nTerm = 0; int nList; sqlite3Fts5HashScanNext(p->pHash); - sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList); if( pList==0 ) goto next_none_eof; pIter->pLeaf->p = (u8*)pList; pIter->pLeaf->nn = nList; pIter->pLeaf->szLeaf = nList; pIter->iEndofDoclist = nList; - sqlite3Fts5BufferSet(&p->rc,&pIter->term, (int)strlen(zTerm), (u8*)zTerm); + sqlite3Fts5BufferSet(&p->rc,&pIter->term, nTerm, (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); } @@ -2251,11 +2273,12 @@ static void fts5SegIterNext( }else if( pIter->pSeg==0 ){ const u8 *pList = 0; const char *zTerm = 0; + int nTerm = 0; int nList = 0; assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm ); if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){ sqlite3Fts5HashScanNext(p->pHash); - sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList); } if( pList==0 ){ fts5DataRelease(pIter->pLeaf); @@ -2265,8 +2288,7 @@ static void fts5SegIterNext( pIter->pLeaf->nn = nList; pIter->pLeaf->szLeaf = nList; pIter->iEndofDoclist = nList+1; - sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm), - (u8*)zTerm); + sqlite3Fts5BufferSet(&p->rc, &pIter->term, nTerm, (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); *pbNewTerm = 1; } @@ -2652,7 +2674,7 @@ static void fts5SegIterSeekInit( fts5LeafSeek(p, bGe, pIter, pTerm, nTerm); } - if( p->rc==SQLITE_OK && bGe==0 ){ + if( p->rc==SQLITE_OK && (bGe==0 || (flags & FTS5INDEX_QUERY_SCANONETERM)) ){ pIter->flags |= FTS5_SEGITER_ONETERM; if( pIter->pLeaf ){ if( flags & FTS5INDEX_QUERY_DESC ){ @@ -2668,7 +2690,9 @@ static void fts5SegIterSeekInit( } fts5SegIterSetNext(p, pIter); - fts5SegIterAllocTombstone(p, pIter); + if( 0==(flags & FTS5INDEX_QUERY_SCANONETERM) ){ + fts5SegIterAllocTombstone(p, pIter); + } /* Either: ** @@ -2685,6 +2709,79 @@ static void fts5SegIterSeekInit( ); } + +/* +** SQL used by fts5SegIterNextInit() to find the page to open. +*/ +static sqlite3_stmt *fts5IdxNextStmt(Fts5Index *p){ + if( p->pIdxNextSelect==0 ){ + Fts5Config *pConfig = p->pConfig; + fts5IndexPrepareStmt(p, &p->pIdxNextSelect, sqlite3_mprintf( + "SELECT pgno FROM '%q'.'%q_idx' WHERE " + "segid=? AND term>? ORDER BY term ASC LIMIT 1", + pConfig->zDb, pConfig->zName + )); + + } + return p->pIdxNextSelect; +} + +/* +** This is similar to fts5SegIterSeekInit(), except that it initializes +** the segment iterator to point to the first term following the page +** with pToken/nToken on it. +*/ +static void fts5SegIterNextInit( + Fts5Index *p, + const char *pTerm, int nTerm, + Fts5StructureSegment *pSeg, /* Description of segment */ + Fts5SegIter *pIter /* Object to populate */ +){ + int iPg = -1; /* Page of segment to open */ + int bDlidx = 0; + sqlite3_stmt *pSel = 0; /* SELECT to find iPg */ + + pSel = fts5IdxNextStmt(p); + if( pSel ){ + assert( p->rc==SQLITE_OK ); + sqlite3_bind_int(pSel, 1, pSeg->iSegid); + sqlite3_bind_blob(pSel, 2, pTerm, nTerm, SQLITE_STATIC); + + if( sqlite3_step(pSel)==SQLITE_ROW ){ + i64 val = sqlite3_column_int64(pSel, 0); + iPg = (int)(val>>1); + bDlidx = (val & 0x0001); + } + p->rc = sqlite3_reset(pSel); + sqlite3_bind_null(pSel, 2); + if( p->rc ) return; + } + + memset(pIter, 0, sizeof(*pIter)); + pIter->pSeg = pSeg; + pIter->flags |= FTS5_SEGITER_ONETERM; + if( iPg>=0 ){ + pIter->iLeafPgno = iPg - 1; + fts5SegIterNextPage(p, pIter); + fts5SegIterSetNext(p, pIter); + } + if( pIter->pLeaf ){ + const u8 *a = pIter->pLeaf->p; + int iTermOff = 0; + + pIter->iPgidxOff = pIter->pLeaf->szLeaf; + pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], iTermOff); + pIter->iLeafOffset = iTermOff; + fts5SegIterLoadTerm(p, pIter, 0); + fts5SegIterLoadNPos(p, pIter); + if( bDlidx ) fts5SegIterLoadDlidx(p, pIter); + + assert( p->rc!=SQLITE_OK || + fts5BufferCompareBlob(&pIter->term, (const u8*)pTerm, nTerm)>0 + ); + } +} + /* ** Initialize the object pIter to point to term pTerm/nTerm within the ** in-memory hash table. If there is no such term in the hash-table, the @@ -2711,8 +2808,7 @@ static void fts5SegIterHashInit( const u8 *pList = 0; p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm); - sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList); - n = (z ? (int)strlen((const char*)z) : 0); + sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &n, &pList, &nList); if( pList ){ pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data)); if( pLeaf ){ @@ -2771,6 +2867,23 @@ static void fts5IndexFreeArray(Fts5Data **ap, int n){ } } +/* +** Decrement the ref-count of the object passed as the only argument. If it +** reaches 0, free it and its contents. +*/ +static void fts5TombstoneArrayDelete(Fts5TombstoneArray *p){ + if( p ){ + p->nRef--; + if( p->nRef<=0 ){ + int ii; + for(ii=0; iinTombstone; ii++){ + fts5DataRelease(p->apTombstone[ii]); + } + sqlite3_free(p); + } + } +} + /* ** Zero the iterator passed as the only argument. */ @@ -2778,7 +2891,7 @@ static void fts5SegIterClear(Fts5SegIter *pIter){ fts5BufferFree(&pIter->term); fts5DataRelease(pIter->pLeaf); fts5DataRelease(pIter->pNextLeaf); - fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone); + fts5TombstoneArrayDelete(pIter->pTombArray); fts5DlidxIterFree(pIter->pDlidx); sqlite3_free(pIter->aRowidOffset); memset(pIter, 0, sizeof(Fts5SegIter)); @@ -3023,7 +3136,6 @@ static void fts5SegIterNextFrom( }while( p->rc==SQLITE_OK ); } - /* ** Free the iterator object passed as the second argument. */ @@ -3168,24 +3280,25 @@ static int fts5IndexTombstoneQuery( static int fts5MultiIterIsDeleted(Fts5Iter *pIter){ int iFirst = pIter->aFirst[1].iFirst; Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + Fts5TombstoneArray *pArray = pSeg->pTombArray; - if( pSeg->pLeaf && pSeg->nTombstone ){ + if( pSeg->pLeaf && pArray ){ /* Figure out which page the rowid might be present on. */ - int iPg = ((u64)pSeg->iRowid) % pSeg->nTombstone; + int iPg = ((u64)pSeg->iRowid) % pArray->nTombstone; assert( iPg>=0 ); /* If tombstone hash page iPg has not yet been loaded from the ** database, load it now. */ - if( pSeg->apTombstone[iPg]==0 ){ - pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex, + if( pArray->apTombstone[iPg]==0 ){ + pArray->apTombstone[iPg] = fts5DataRead(pIter->pIndex, FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg) ); - if( pSeg->apTombstone[iPg]==0 ) return 0; + if( pArray->apTombstone[iPg]==0 ) return 0; } return fts5IndexTombstoneQuery( - pSeg->apTombstone[iPg], - pSeg->nTombstone, + pArray->apTombstone[iPg], + pArray->nTombstone, pSeg->iRowid ); } @@ -3724,6 +3837,32 @@ static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ } } +/* +** All the component segment-iterators of pIter have been set up. This +** functions finishes setup for iterator pIter itself. +*/ +static void fts5MultiIterFinishSetup(Fts5Index *p, Fts5Iter *pIter){ + int iIter; + for(iIter=pIter->nSeg-1; iIter>0; iIter--){ + int iEq; + if( (iEq = fts5MultiIterDoCompare(pIter, iIter)) ){ + Fts5SegIter *pSeg = &pIter->aSeg[iEq]; + if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); + fts5MultiIterAdvanced(p, pIter, iEq, iIter); + } + } + fts5MultiIterSetEof(pIter); + fts5AssertMultiIterSetup(p, pIter); + + if( (pIter->bSkipEmpty && fts5MultiIterIsEmpty(p, pIter)) + || fts5MultiIterIsDeleted(pIter) + ){ + fts5MultiIterNext(p, pIter, 0, 0); + }else if( pIter->base.bEof==0 ){ + Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + pIter->xSetOutputs(pIter, pSeg); + } +} /* ** Allocate a new Fts5Iter object. @@ -3805,31 +3944,12 @@ static void fts5MultiIterNew( assert( iIter==nSeg ); } - /* If the above was successful, each component iterators now points + /* If the above was successful, each component iterator now points ** to the first entry in its segment. In this case initialize the ** aFirst[] array. Or, if an error has occurred, free the iterator ** object and set the output variable to NULL. */ if( p->rc==SQLITE_OK ){ - for(iIter=pNew->nSeg-1; iIter>0; iIter--){ - int iEq; - if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){ - Fts5SegIter *pSeg = &pNew->aSeg[iEq]; - if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); - fts5MultiIterAdvanced(p, pNew, iEq, iIter); - } - } - fts5MultiIterSetEof(pNew); - fts5AssertMultiIterSetup(p, pNew); - - if( (pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew)) - || fts5MultiIterIsDeleted(pNew) - ){ - fts5MultiIterNext(p, pNew, 0, 0); - }else if( pNew->base.bEof==0 ){ - Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; - pNew->xSetOutputs(pNew, pSeg); - } - + fts5MultiIterFinishSetup(p, pNew); }else{ fts5MultiIterFree(pNew); *ppOut = 0; @@ -3854,7 +3974,6 @@ static void fts5MultiIterNew2( pNew = fts5MultiIterAlloc(p, 2); if( pNew ){ Fts5SegIter *pIter = &pNew->aSeg[1]; - pIter->flags = FTS5_SEGITER_ONETERM; if( pData->szLeaf>0 ){ pIter->pLeaf = pData; @@ -4002,6 +4121,7 @@ static void fts5IndexDiscardData(Fts5Index *p){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; p->nPendingRow = 0; + p->flushRc = SQLITE_OK; } p->nContentlessDelete = 0; } @@ -4217,7 +4337,7 @@ static void fts5WriteDlidxAppend( } if( pDlidx->bPrevValid ){ - iVal = iRowid - pDlidx->iPrev; + iVal = (u64)iRowid - (u64)pDlidx->iPrev; }else{ i64 iPgno = (i==0 ? pWriter->writer.pgno : pDlidx[-1].pgno); assert( pDlidx->buf.n==0 ); @@ -5340,10 +5460,10 @@ static void fts5FlushSecureDelete( Fts5Index *p, Fts5Structure *pStruct, const char *zTerm, + int nTerm, i64 iRowid ){ const int f = FTS5INDEX_QUERY_SKIPHASH; - int nTerm = (int)strlen(zTerm); Fts5Iter *pIter = 0; /* Used to find term instance */ fts5MultiIterNew(p, pStruct, f, 0, (const u8*)zTerm, nTerm, -1, 0, &pIter); @@ -5417,8 +5537,7 @@ static void fts5FlushOneHash(Fts5Index *p){ int nDoclist; /* Size of doclist in bytes */ /* Get the term and doclist for this entry. */ - sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); - nTerm = (int)strlen(zTerm); + sqlite3Fts5HashScanEntry(pHash, &zTerm, &nTerm, &pDoclist, &nDoclist); if( bSecureDelete==0 ){ fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); if( p->rc!=SQLITE_OK ) break; @@ -5448,7 +5567,7 @@ static void fts5FlushOneHash(Fts5Index *p){ if( bSecureDelete ){ if( eDetail==FTS5_DETAIL_NONE ){ if( iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ iOff++; continue; @@ -5584,6 +5703,10 @@ static void fts5FlushOneHash(Fts5Index *p){ */ static void fts5IndexFlush(Fts5Index *p){ /* Unless it is empty, flush the hash table to disk */ + if( p->flushRc ){ + p->rc = p->flushRc; + return; + } if( p->nPendingData || p->nContentlessDelete ){ assert( p->pHash ); fts5FlushOneHash(p); @@ -5592,6 +5715,8 @@ static void fts5IndexFlush(Fts5Index *p){ p->nPendingData = 0; p->nPendingRow = 0; p->nContentlessDelete = 0; + }else if( p->nPendingData || p->nContentlessDelete ){ + p->flushRc = p->rc; } } } @@ -6078,7 +6203,7 @@ static void fts5SetupPrefixIter( u8 *pToken, /* Buffer containing prefix to match */ int nToken, /* Size of buffer pToken in bytes */ Fts5Colset *pColset, /* Restrict matches to these columns */ - Fts5Iter **ppIter /* OUT: New iterator */ + Fts5Iter **ppIter /* OUT: New iterator */ ){ Fts5Structure *pStruct; Fts5Buffer *aBuf; @@ -6099,8 +6224,9 @@ static void fts5SetupPrefixIter( aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); pStruct = fts5StructureRead(p); + assert( p->rc!=SQLITE_OK || (aBuf && pStruct) ); - if( aBuf && pStruct ){ + if( p->rc==SQLITE_OK ){ const int flags = FTS5INDEX_QUERY_SCAN | FTS5INDEX_QUERY_SKIPEMPTY | FTS5INDEX_QUERY_NOOUTPUT; @@ -6112,6 +6238,12 @@ static void fts5SetupPrefixIter( int bNewTerm = 1; memset(&doclist, 0, sizeof(doclist)); + + /* If iIdx is non-zero, then it is the number of a prefix-index for + ** prefixes 1 character longer than the prefix being queried for. That + ** index contains all the doclists required, except for the one + ** corresponding to the prefix itself. That one is extracted from the + ** main term index here. */ if( iIdx!=0 ){ int dummy = 0; const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT; @@ -6135,6 +6267,7 @@ static void fts5SetupPrefixIter( pToken[0] = FTS5_MAIN_PREFIX + iIdx; fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); fts5IterSetOutputCb(&p->rc, p1); + for( /* no-op */ ; fts5MultiIterEof(p, p1)==0; fts5MultiIterNext2(p, p1, &bNewTerm) @@ -6150,7 +6283,6 @@ static void fts5SetupPrefixIter( } if( p1->base.nData==0 ) continue; - if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ int i1 = i*nMerge; @@ -6189,7 +6321,7 @@ static void fts5SetupPrefixIter( } fts5MultiIterFree(p1); - pData = fts5IdxMalloc(p, sizeof(Fts5Data)+doclist.n+FTS5_DATA_ZERO_PADDING); + pData = fts5IdxMalloc(p, sizeof(*pData)+doclist.n+FTS5_DATA_ZERO_PADDING); if( pData ){ pData->p = (u8*)&pData[1]; pData->nn = pData->szLeaf = doclist.n; @@ -6332,6 +6464,7 @@ int sqlite3Fts5IndexClose(Fts5Index *p){ sqlite3_finalize(p->pIdxWriter); sqlite3_finalize(p->pIdxDeleter); sqlite3_finalize(p->pIdxSelect); + sqlite3_finalize(p->pIdxNextSelect); sqlite3_finalize(p->pDataVersion); sqlite3_finalize(p->pDeleteFromIdx); sqlite3Fts5HashFree(p->pHash); @@ -6427,6 +6560,454 @@ int sqlite3Fts5IndexWrite( return rc; } +/* +** pToken points to a buffer of size nToken bytes containing a search +** term, including the index number at the start, used on a tokendata=1 +** table. This function returns true if the term in buffer pBuf matches +** token pToken/nToken. +*/ +static int fts5IsTokendataPrefix( + Fts5Buffer *pBuf, + const u8 *pToken, + int nToken +){ + return ( + pBuf->n>=nToken + && 0==memcmp(pBuf->p, pToken, nToken) + && (pBuf->n==nToken || pBuf->p[nToken]==0x00) + ); +} + +/* +** Ensure the segment-iterator passed as the only argument points to EOF. +*/ +static void fts5SegIterSetEOF(Fts5SegIter *pSeg){ + fts5DataRelease(pSeg->pLeaf); + pSeg->pLeaf = 0; +} + +/* +** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an +** array of these for each row it visits. Or, for an iterator used by an +** "ORDER BY rank" query, it accumulates an array of these for the entire +** query. +** +** Each instance in the array indicates the iterator (and therefore term) +** associated with position iPos of rowid iRowid. This is used by the +** xInstToken() API. +*/ +struct Fts5TokenDataMap { + i64 iRowid; /* Row this token is located in */ + i64 iPos; /* Position of token */ + int iIter; /* Iterator token was read from */ +}; + +/* +** An object used to supplement Fts5Iter for tokendata=1 iterators. +*/ +struct Fts5TokenDataIter { + int nIter; + int nIterAlloc; + + int nMap; + int nMapAlloc; + Fts5TokenDataMap *aMap; + + Fts5PoslistReader *aPoslistReader; + int *aPoslistToIter; + Fts5Iter *apIter[1]; +}; + +/* +** This function appends iterator pAppend to Fts5TokenDataIter pIn and +** returns the result. +*/ +static Fts5TokenDataIter *fts5AppendTokendataIter( + Fts5Index *p, /* Index object (for error code) */ + Fts5TokenDataIter *pIn, /* Current Fts5TokenDataIter struct */ + Fts5Iter *pAppend /* Append this iterator */ +){ + Fts5TokenDataIter *pRet = pIn; + + if( p->rc==SQLITE_OK ){ + if( pIn==0 || pIn->nIter==pIn->nIterAlloc ){ + int nAlloc = pIn ? pIn->nIterAlloc*2 : 16; + int nByte = nAlloc * sizeof(Fts5Iter*) + sizeof(Fts5TokenDataIter); + Fts5TokenDataIter *pNew = (Fts5TokenDataIter*)sqlite3_realloc(pIn, nByte); + + if( pNew==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + if( pIn==0 ) memset(pNew, 0, nByte); + pRet = pNew; + pNew->nIterAlloc = nAlloc; + } + } + } + if( p->rc ){ + sqlite3Fts5IterClose((Fts5IndexIter*)pAppend); + }else{ + pRet->apIter[pRet->nIter++] = pAppend; + } + assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc ); + + return pRet; +} + +/* +** Delete an Fts5TokenDataIter structure and its contents. +*/ +static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){ + if( pSet ){ + int ii; + for(ii=0; iinIter; ii++){ + fts5MultiIterFree(pSet->apIter[ii]); + } + sqlite3_free(pSet->aPoslistReader); + sqlite3_free(pSet->aMap); + sqlite3_free(pSet); + } +} + +/* +** Append a mapping to the token-map belonging to object pT. +*/ +static void fts5TokendataIterAppendMap( + Fts5Index *p, + Fts5TokenDataIter *pT, + int iIter, + i64 iRowid, + i64 iPos +){ + if( p->rc==SQLITE_OK ){ + if( pT->nMap==pT->nMapAlloc ){ + int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; + int nByte = nNew * sizeof(Fts5TokenDataMap); + Fts5TokenDataMap *aNew; + + aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nByte); + if( aNew==0 ){ + p->rc = SQLITE_NOMEM; + return; + } + + pT->aMap = aNew; + pT->nMapAlloc = nNew; + } + + pT->aMap[pT->nMap].iRowid = iRowid; + pT->aMap[pT->nMap].iPos = iPos; + pT->aMap[pT->nMap].iIter = iIter; + pT->nMap++; + } +} + +/* +** The iterator passed as the only argument must be a tokendata=1 iterator +** (pIter->pTokenDataIter!=0). This function sets the iterator output +** variables (pIter->base.*) according to the contents of the current +** row. +*/ +static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){ + int ii; + int nHit = 0; + i64 iRowid = SMALLEST_INT64; + int iMin = 0; + + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + + pIter->base.nData = 0; + pIter->base.pData = 0; + + for(ii=0; iinIter; ii++){ + Fts5Iter *p = pT->apIter[ii]; + if( p->base.bEof==0 ){ + if( nHit==0 || p->base.iRowidbase.iRowid; + nHit = 1; + pIter->base.pData = p->base.pData; + pIter->base.nData = p->base.nData; + iMin = ii; + }else if( p->base.iRowid==iRowid ){ + nHit++; + } + } + } + + if( nHit==0 ){ + pIter->base.bEof = 1; + }else{ + int eDetail = pIter->pIndex->pConfig->eDetail; + pIter->base.bEof = 0; + pIter->base.iRowid = iRowid; + + if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){ + fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, iRowid, -1); + }else + if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){ + int nReader = 0; + int nByte = 0; + i64 iPrev = 0; + + /* Allocate array of iterators if they are not already allocated. */ + if( pT->aPoslistReader==0 ){ + pT->aPoslistReader = (Fts5PoslistReader*)sqlite3Fts5MallocZero( + &pIter->pIndex->rc, + pT->nIter * (sizeof(Fts5PoslistReader) + sizeof(int)) + ); + if( pT->aPoslistReader==0 ) return; + pT->aPoslistToIter = (int*)&pT->aPoslistReader[pT->nIter]; + } + + /* Populate an iterator for each poslist that will be merged */ + for(ii=0; iinIter; ii++){ + Fts5Iter *p = pT->apIter[ii]; + if( iRowid==p->base.iRowid ){ + pT->aPoslistToIter[nReader] = ii; + sqlite3Fts5PoslistReaderInit( + p->base.pData, p->base.nData, &pT->aPoslistReader[nReader++] + ); + nByte += p->base.nData; + } + } + + /* Ensure the output buffer is large enough */ + if( fts5BufferGrow(&pIter->pIndex->rc, &pIter->poslist, nByte+nHit*10) ){ + return; + } + + /* Ensure the token-mapping is large enough */ + if( eDetail==FTS5_DETAIL_FULL && pT->nMapAlloc<(pT->nMap + nByte) ){ + int nNew = (pT->nMapAlloc + nByte) * 2; + Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc( + pT->aMap, nNew*sizeof(Fts5TokenDataMap) + ); + if( aNew==0 ){ + pIter->pIndex->rc = SQLITE_NOMEM; + return; + } + pT->aMap = aNew; + pT->nMapAlloc = nNew; + } + + pIter->poslist.n = 0; + + while( 1 ){ + i64 iMinPos = LARGEST_INT64; + + /* Find smallest position */ + iMin = 0; + for(ii=0; iiaPoslistReader[ii]; + if( pReader->bEof==0 ){ + if( pReader->iPosiPos; + iMin = ii; + } + } + } + + /* If all readers were at EOF, break out of the loop. */ + if( iMinPos==LARGEST_INT64 ) break; + + sqlite3Fts5PoslistSafeAppend(&pIter->poslist, &iPrev, iMinPos); + sqlite3Fts5PoslistReaderNext(&pT->aPoslistReader[iMin]); + + if( eDetail==FTS5_DETAIL_FULL ){ + pT->aMap[pT->nMap].iPos = iMinPos; + pT->aMap[pT->nMap].iIter = pT->aPoslistToIter[iMin]; + pT->aMap[pT->nMap].iRowid = iRowid; + pT->nMap++; + } + } + + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + } + } +} + +/* +** The iterator passed as the only argument must be a tokendata=1 iterator +** (pIter->pTokenDataIter!=0). This function advances the iterator. If +** argument bFrom is false, then the iterator is advanced to the next +** entry. Or, if bFrom is true, it is advanced to the first entry with +** a rowid of iFrom or greater. +*/ +static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){ + int ii; + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + + for(ii=0; iinIter; ii++){ + Fts5Iter *p = pT->apIter[ii]; + if( p->base.bEof==0 + && (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowidpIndex, p, bFrom, iFrom); + while( bFrom && p->base.bEof==0 + && p->base.iRowidpIndex->rc==SQLITE_OK + ){ + fts5MultiIterNext(p->pIndex, p, 0, 0); + } + } + } + + fts5IterSetOutputsTokendata(pIter); +} + +/* +** If the segment-iterator passed as the first argument is at EOF, then +** set pIter->term to a copy of buffer pTerm. +*/ +static void fts5TokendataSetTermIfEof(Fts5Iter *pIter, Fts5Buffer *pTerm){ + if( pIter && pIter->aSeg[0].pLeaf==0 ){ + fts5BufferSet(&pIter->pIndex->rc, &pIter->aSeg[0].term, pTerm->n, pTerm->p); + } +} + +/* +** This function sets up an iterator to use for a non-prefix query on a +** tokendata=1 table. +*/ +static Fts5Iter *fts5SetupTokendataIter( + Fts5Index *p, /* FTS index to query */ + const u8 *pToken, /* Buffer containing query term */ + int nToken, /* Size of buffer pToken in bytes */ + Fts5Colset *pColset /* Colset to filter on */ +){ + Fts5Iter *pRet = 0; + Fts5TokenDataIter *pSet = 0; + Fts5Structure *pStruct = 0; + const int flags = FTS5INDEX_QUERY_SCANONETERM | FTS5INDEX_QUERY_SCAN; + + Fts5Buffer bSeek = {0, 0, 0}; + Fts5Buffer *pSmall = 0; + + fts5IndexFlush(p); + pStruct = fts5StructureRead(p); + + while( p->rc==SQLITE_OK ){ + Fts5Iter *pPrev = pSet ? pSet->apIter[pSet->nIter-1] : 0; + Fts5Iter *pNew = 0; + Fts5SegIter *pNewIter = 0; + Fts5SegIter *pPrevIter = 0; + + int iLvl, iSeg, ii; + + pNew = fts5MultiIterAlloc(p, pStruct->nSegment); + if( pSmall ){ + fts5BufferSet(&p->rc, &bSeek, pSmall->n, pSmall->p); + fts5BufferAppendBlob(&p->rc, &bSeek, 1, (const u8*)"\0"); + }else{ + fts5BufferSet(&p->rc, &bSeek, nToken, pToken); + } + if( p->rc ){ + sqlite3Fts5IterClose((Fts5IndexIter*)pNew); + break; + } + + pNewIter = &pNew->aSeg[0]; + pPrevIter = (pPrev ? &pPrev->aSeg[0] : 0); + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + int bDone = 0; + + if( pPrevIter ){ + if( fts5BufferCompare(pSmall, &pPrevIter->term) ){ + memcpy(pNewIter, pPrevIter, sizeof(Fts5SegIter)); + memset(pPrevIter, 0, sizeof(Fts5SegIter)); + bDone = 1; + }else if( pPrevIter->iEndofDoclist>pPrevIter->pLeaf->szLeaf ){ + fts5SegIterNextInit(p,(const char*)bSeek.p,bSeek.n-1,pSeg,pNewIter); + bDone = 1; + } + } + + if( bDone==0 ){ + fts5SegIterSeekInit(p, bSeek.p, bSeek.n, flags, pSeg, pNewIter); + } + + if( pPrevIter ){ + if( pPrevIter->pTombArray ){ + pNewIter->pTombArray = pPrevIter->pTombArray; + pNewIter->pTombArray->nRef++; + } + }else{ + fts5SegIterAllocTombstone(p, pNewIter); + } + + pNewIter++; + if( pPrevIter ) pPrevIter++; + if( p->rc ) break; + } + } + fts5TokendataSetTermIfEof(pPrev, pSmall); + + pNew->bSkipEmpty = 1; + pNew->pColset = pColset; + fts5IterSetOutputCb(&p->rc, pNew); + + /* Loop through all segments in the new iterator. Find the smallest + ** term that any segment-iterator points to. Iterator pNew will be + ** used for this term. Also, set any iterator that points to a term that + ** does not match pToken/nToken to point to EOF */ + pSmall = 0; + for(ii=0; iinSeg; ii++){ + Fts5SegIter *pII = &pNew->aSeg[ii]; + if( 0==fts5IsTokendataPrefix(&pII->term, pToken, nToken) ){ + fts5SegIterSetEOF(pII); + } + if( pII->pLeaf && (!pSmall || fts5BufferCompare(pSmall, &pII->term)>0) ){ + pSmall = &pII->term; + } + } + + /* If pSmall is still NULL at this point, then the new iterator does + ** not point to any terms that match the query. So delete it and break + ** out of the loop - all required iterators have been collected. */ + if( pSmall==0 ){ + sqlite3Fts5IterClose((Fts5IndexIter*)pNew); + break; + } + + /* Append this iterator to the set and continue. */ + pSet = fts5AppendTokendataIter(p, pSet, pNew); + } + + if( p->rc==SQLITE_OK && pSet ){ + int ii; + for(ii=0; iinIter; ii++){ + Fts5Iter *pIter = pSet->apIter[ii]; + int iSeg; + for(iSeg=0; iSegnSeg; iSeg++){ + pIter->aSeg[iSeg].flags |= FTS5_SEGITER_ONETERM; + } + fts5MultiIterFinishSetup(p, pIter); + } + } + + if( p->rc==SQLITE_OK ){ + pRet = fts5MultiIterAlloc(p, 0); + } + if( pRet ){ + pRet->pTokenDataIter = pSet; + if( pSet ){ + fts5IterSetOutputsTokendata(pRet); + }else{ + pRet->base.bEof = 1; + } + }else{ + fts5TokendataIterDelete(pSet); + } + + fts5StructureRelease(pStruct); + fts5BufferFree(&bSeek); + return pRet; +} + + /* ** Open a new iterator to iterate though all rowid that match the ** specified token or token prefix. @@ -6448,8 +7029,13 @@ int sqlite3Fts5IndexQuery( if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ int iIdx = 0; /* Index to search */ int iPrefixIdx = 0; /* +1 prefix index */ + int bTokendata = pConfig->bTokendata; if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); + if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){ + bTokendata = 0; + } + /* Figure out which index to search and set iIdx accordingly. If this ** is a prefix query for which there is no prefix index, set iIdx to ** greater than pConfig->nPrefix to indicate that the query will be @@ -6475,7 +7061,10 @@ int sqlite3Fts5IndexQuery( } } - if( iIdx<=pConfig->nPrefix ){ + if( bTokendata && iIdx==0 ){ + buf.p[0] = '0'; + pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset); + }else if( iIdx<=pConfig->nPrefix ){ /* Straight index lookup */ Fts5Structure *pStruct = fts5StructureRead(p); buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); @@ -6522,7 +7111,11 @@ int sqlite3Fts5IndexQuery( int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; assert( pIter->pIndex->rc==SQLITE_OK ); - fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); + if( pIter->pTokenDataIter ){ + fts5TokendataIterNext(pIter, 0, 0); + }else{ + fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); + } return fts5IndexReturn(pIter->pIndex); } @@ -6555,7 +7148,11 @@ int sqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){ */ int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); + if( pIter->pTokenDataIter ){ + fts5TokendataIterNext(pIter, 1, iMatch); + }else{ + fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); + } return fts5IndexReturn(pIter->pIndex); } @@ -6570,6 +7167,99 @@ const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){ return (z ? &z[1] : 0); } +/* +** This is used by xInstToken() to access the token at offset iOff, column +** iCol of row iRowid. The token is returned via output variables *ppOut +** and *pnOut. The iterator passed as the first argument must be a tokendata=1 +** iterator (pIter->pTokenDataIter!=0). +*/ +int sqlite3Fts5IterToken( + Fts5IndexIter *pIndexIter, + i64 iRowid, + int iCol, + int iOff, + const char **ppOut, int *pnOut +){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + Fts5TokenDataMap *aMap = pT->aMap; + i64 iPos = (((i64)iCol)<<32) + iOff; + + int i1 = 0; + int i2 = pT->nMap; + int iTest = 0; + + while( i2>i1 ){ + iTest = (i1 + i2) / 2; + + if( aMap[iTest].iRowidiRowid ){ + i2 = iTest; + }else{ + if( aMap[iTest].iPosiPos ){ + i2 = iTest; + }else{ + break; + } + } + } + + if( i2>i1 ){ + Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter]; + *ppOut = (const char*)pMap->aSeg[0].term.p+1; + *pnOut = pMap->aSeg[0].term.n-1; + } + + return SQLITE_OK; +} + +/* +** Clear any existing entries from the token-map associated with the +** iterator passed as the only argument. +*/ +void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + if( pIter && pIter->pTokenDataIter ){ + pIter->pTokenDataIter->nMap = 0; + } +} + +/* +** Set a token-mapping for the iterator passed as the first argument. This +** is used in detail=column or detail=none mode when a token is requested +** using the xInstToken() API. In this case the caller tokenizers the +** current row and configures the token-mapping via multiple calls to this +** function. +*/ +int sqlite3Fts5IndexIterWriteTokendata( + Fts5IndexIter *pIndexIter, + const char *pToken, int nToken, + i64 iRowid, int iCol, int iOff +){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + Fts5Index *p = pIter->pIndex; + int ii; + + assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL ); + assert( pIter->pTokenDataIter ); + + for(ii=0; iinIter; ii++){ + Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term; + if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break; + } + if( iinIter ){ + fts5TokendataIterAppendMap(p, pT, ii, iRowid, (((i64)iCol)<<32) + iOff); + } + return fts5IndexReturn(p); +} + /* ** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). */ @@ -6577,6 +7267,7 @@ void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){ if( pIndexIter ){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; Fts5Index *pIndex = pIter->pIndex; + fts5TokendataIterDelete(pIter->pTokenDataIter); fts5MultiIterFree(pIter); sqlite3Fts5IndexCloseReader(pIndex); } @@ -7084,7 +7775,9 @@ static int fts5QueryCksum( int eDetail = p->pConfig->eDetail; u64 cksum = *pCksum; Fts5IndexIter *pIter = 0; - int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); + int rc = sqlite3Fts5IndexQuery( + p, z, n, (flags | FTS5INDEX_QUERY_NOTOKENDATA), 0, &pIter + ); while( rc==SQLITE_OK && ALWAYS(pIter!=0) && 0==sqlite3Fts5IterEof(pIter) ){ i64 rowid = pIter->iRowid; @@ -7251,7 +7944,7 @@ static void fts5IndexIntegrityCheckEmpty( } static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ - int iTermOff = 0; + i64 iTermOff = 0; int ii; Fts5Buffer buf1 = {0,0,0}; @@ -7260,7 +7953,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ ii = pLeaf->szLeaf; while( iinn && p->rc==SQLITE_OK ){ int res; - int iOff; + i64 iOff; int nIncr; ii += fts5GetVarint32(&pLeaf->p[ii], nIncr); @@ -7782,6 +8475,24 @@ static void fts5DecodeRowidList( } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +static void fts5BufferAppendTerm(int *pRc, Fts5Buffer *pBuf, Fts5Buffer *pTerm){ + int ii; + fts5BufferGrow(pRc, pBuf, pTerm->n*2 + 1); + if( *pRc==SQLITE_OK ){ + for(ii=0; iin; ii++){ + if( pTerm->p[ii]==0x00 ){ + pBuf->p[pBuf->n++] = '\\'; + pBuf->p[pBuf->n++] = '0'; + }else{ + pBuf->p[pBuf->n++] = pTerm->p[ii]; + } + } + pBuf->p[pBuf->n] = 0x00; + } +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The implementation of user-defined scalar function fts5_decode(). @@ -7889,9 +8600,8 @@ static void fts5DecodeFunction( iOff += fts5GetVarint32(&a[iOff], nAppend); term.n = nKeep; fts5BufferAppendBlob(&rc, &term, nAppend, &a[iOff]); - sqlite3Fts5BufferAppendPrintf( - &rc, &s, " term=%.*s", term.n, (const char*)term.p - ); + sqlite3Fts5BufferAppendPrintf(&rc, &s, " term="); + fts5BufferAppendTerm(&rc, &s, &term); iOff += nAppend; /* Figure out where the doclist for this term ends */ @@ -7999,9 +8709,8 @@ static void fts5DecodeFunction( fts5BufferAppendBlob(&rc, &term, nByte, &a[iOff]); iOff += nByte; - sqlite3Fts5BufferAppendPrintf( - &rc, &s, " term=%.*s", term.n, (const char*)term.p - ); + sqlite3Fts5BufferAppendPrintf(&rc, &s, " term="); + fts5BufferAppendTerm(&rc, &s, &term); iOff += fts5DecodeDoclist(&rc, &s, &a[iOff], iEnd-iOff); } diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 6e86ca595..eb7ee7408 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -118,7 +118,7 @@ struct Fts5FullTable { Fts5Global *pGlobal; /* Global (connection wide) data */ Fts5Cursor *pSortCsr; /* Sort data from this cursor */ int iSavepoint; /* Successful xSavepoint()+1 */ - int bInSavepoint; + #ifdef SQLITE_DEBUG struct Fts5TransactionState ts; #endif @@ -656,12 +656,15 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ } idxStr[iIdxStr] = '\0'; - /* Set idxFlags flags for the ORDER BY clause */ + /* Set idxFlags flags for the ORDER BY clause + ** + ** Note that tokendata=1 tables cannot currently handle "ORDER BY rowid DESC". + */ if( pInfo->nOrderBy==1 ){ int iSort = pInfo->aOrderBy[0].iColumn; if( iSort==(pConfig->nCol+1) && bSeenMatch ){ idxFlags |= FTS5_BI_ORDER_RANK; - }else if( iSort==-1 ){ + }else if( iSort==-1 && (!pInfo->aOrderBy[0].desc || !pConfig->bTokendata) ){ idxFlags |= FTS5_BI_ORDER_ROWID; } if( BitFlagTest(idxFlags, FTS5_BI_ORDER_RANK|FTS5_BI_ORDER_ROWID) ){ @@ -913,6 +916,16 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ ); assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) ); + /* If this cursor uses FTS5_PLAN_MATCH and this is a tokendata=1 table, + ** clear any token mappings accumulated at the fts5_index.c level. In + ** other cases, specifically FTS5_PLAN_SOURCE and FTS5_PLAN_SORTED_MATCH, + ** we need to retain the mappings for the entire query. */ + if( pCsr->ePlan==FTS5_PLAN_MATCH + && ((Fts5Table*)pCursor->pVtab)->pConfig->bTokendata + ){ + sqlite3Fts5ExprClearTokens(pCsr->pExpr); + } + if( pCsr->ePlan<3 ){ int bSkip = 0; if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc; @@ -1573,7 +1586,10 @@ static int fts5SpecialInsert( }else if( 0==sqlite3_stricmp("flush", zCmd) ){ rc = sqlite3Fts5FlushToDisk(&pTab->p); }else{ - rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + rc = sqlite3Fts5FlushToDisk(&pTab->p); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + } if( rc==SQLITE_OK ){ rc = sqlite3Fts5ConfigSetValue(pTab->p.pConfig, zCmd, pVal, &bError); } @@ -1898,7 +1914,10 @@ static int fts5ApiColumnText( ){ int rc = SQLITE_OK; Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; - if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); + if( iCol<0 || iCol>=pTab->pConfig->nCol ){ + rc = SQLITE_RANGE; + }else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) || pCsr->ePlan==FTS5_PLAN_SPECIAL ){ *pz = 0; @@ -1923,8 +1942,9 @@ static int fts5CsrPoslist( int rc = SQLITE_OK; int bLive = (pCsr->pSorter==0); - if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ - + if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){ + rc = SQLITE_RANGE; + }else if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ Fts5PoslistPopulator *aPopulator; int i; @@ -1948,15 +1968,21 @@ static int fts5CsrPoslist( CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST); } - if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ - Fts5Sorter *pSorter = pCsr->pSorter; - int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); - *pn = pSorter->aIdx[iPhrase] - i1; - *pa = &pSorter->aPoslist[i1]; + if( rc==SQLITE_OK ){ + if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ + Fts5Sorter *pSorter = pCsr->pSorter; + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + *pn = pSorter->aIdx[iPhrase] - i1; + *pa = &pSorter->aPoslist[i1]; + }else{ + *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); + } }else{ - *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); + *pa = 0; + *pn = 0; } + return rc; } @@ -2063,12 +2089,6 @@ static int fts5ApiInst( ){ if( iIdx<0 || iIdx>=pCsr->nInstCount ){ rc = SQLITE_RANGE; -#if 0 - }else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){ - *piPhrase = pCsr->aInst[iIdx*3]; - *piCol = pCsr->aInst[iIdx*3 + 2]; - *piOff = -1; -#endif }else{ *piPhrase = pCsr->aInst[iIdx*3]; *piCol = pCsr->aInst[iIdx*3 + 1]; @@ -2323,13 +2343,56 @@ static int fts5ApiPhraseFirstColumn( return rc; } +/* +** xQueryToken() API implemenetation. +*/ +static int fts5ApiQueryToken( + Fts5Context* pCtx, + int iPhrase, + int iToken, + const char **ppOut, + int *pnOut +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return sqlite3Fts5ExprQueryToken(pCsr->pExpr, iPhrase, iToken, ppOut, pnOut); +} + +/* +** xInstToken() API implemenetation. +*/ +static int fts5ApiInstToken( + Fts5Context *pCtx, + int iIdx, + int iToken, + const char **ppOut, int *pnOut +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + int rc = SQLITE_OK; + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 + || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) + ){ + if( iIdx<0 || iIdx>=pCsr->nInstCount ){ + rc = SQLITE_RANGE; + }else{ + int iPhrase = pCsr->aInst[iIdx*3]; + int iCol = pCsr->aInst[iIdx*3 + 1]; + int iOff = pCsr->aInst[iIdx*3 + 2]; + i64 iRowid = fts5CursorRowid(pCsr); + rc = sqlite3Fts5ExprInstToken( + pCsr->pExpr, iRowid, iPhrase, iCol, iOff, iToken, ppOut, pnOut + ); + } + } + return rc; +} + static int fts5ApiQueryPhrase(Fts5Context*, int, void*, int(*)(const Fts5ExtensionApi*, Fts5Context*, void*) ); static const Fts5ExtensionApi sFts5Api = { - 2, /* iVersion */ + 3, /* iVersion */ fts5ApiUserData, fts5ApiColumnCount, fts5ApiRowCount, @@ -2349,6 +2412,8 @@ static const Fts5ExtensionApi sFts5Api = { fts5ApiPhraseNext, fts5ApiPhraseFirstColumn, fts5ApiPhraseNextColumn, + fts5ApiQueryToken, + fts5ApiInstToken }; /* @@ -2615,9 +2680,7 @@ static int fts5RenameMethod( ){ int rc; Fts5FullTable *pTab = (Fts5FullTable*)pVtab; - pTab->bInSavepoint = 1; rc = sqlite3Fts5StorageRename(pTab->pStorage, zName); - pTab->bInSavepoint = 0; return rc; } @@ -2634,26 +2697,12 @@ int sqlite3Fts5FlushToDisk(Fts5Table *pTab){ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; int rc = SQLITE_OK; - char *zSql = 0; - fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint); - if( pTab->bInSavepoint==0 ){ - zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')", - pTab->p.pConfig->zDb, pTab->p.pConfig->zName, pTab->p.pConfig->zName - ); - if( zSql ){ - pTab->bInSavepoint = 1; - rc = sqlite3_exec(pTab->p.pConfig->db, zSql, 0, 0, 0); - pTab->bInSavepoint = 0; - sqlite3_free(zSql); - }else{ - rc = SQLITE_NOMEM; - } - if( rc==SQLITE_OK ){ - pTab->iSavepoint = iSavepoint+1; - } + fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint); + rc = sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); + if( rc==SQLITE_OK ){ + pTab->iSavepoint = iSavepoint+1; } - return rc; } @@ -2685,8 +2734,8 @@ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ int rc = SQLITE_OK; fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint); fts5TripCursors(pTab); - pTab->p.pConfig->pgsz = 0; if( (iSavepoint+1)<=pTab->iSavepoint ){ + pTab->p.pConfig->pgsz = 0; rc = sqlite3Fts5StorageRollback(pTab->pStorage); } return rc; @@ -2914,7 +2963,7 @@ static int fts5ShadowName(const char *zName){ ** if anything is found amiss. Return a NULL pointer if everything is ** OK. */ -static int fts5Integrity( +static int fts5IntegrityMethod( sqlite3_vtab *pVtab, /* the FTS5 virtual table to check */ const char *zSchema, /* Name of schema in which this table lives */ const char *zTabname, /* Name of the table itself */ @@ -2972,7 +3021,7 @@ static int fts5Init(sqlite3 *db){ /* xRelease */ fts5ReleaseMethod, /* xRollbackTo */ fts5RollbackToMethod, /* xShadowName */ fts5ShadowName, - /* xIntegrity */ fts5Integrity + /* xIntegrity */ fts5IntegrityMethod }; int rc; diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index 9480da7c5..a04b152fb 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -673,7 +673,7 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){ } if( rc==SQLITE_OK ){ - rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); + rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, pConfig->pzErrmsg); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){ diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c index 80c600dbb..853a41865 100644 --- a/ext/fts5/fts5_tcl.c +++ b/ext/fts5/fts5_tcl.c @@ -244,6 +244,9 @@ static int SQLITE_TCLAPI xF5tApi( { "xGetAuxdataInt", 1, "CLEAR" }, /* 15 */ { "xPhraseForeach", 4, "IPHRASE COLVAR OFFVAR SCRIPT" }, /* 16 */ { "xPhraseColumnForeach", 3, "IPHRASE COLVAR SCRIPT" }, /* 17 */ + + { "xQueryToken", 2, "IPHRASE ITERM" }, /* 18 */ + { "xInstToken", 2, "IDX ITERM" }, /* 19 */ { 0, 0, 0} }; @@ -500,6 +503,38 @@ static int SQLITE_TCLAPI xF5tApi( break; } + CASE(18, "xQueryToken") { + const char *pTerm = 0; + int nTerm = 0; + int iPhrase = 0; + int iTerm = 0; + + if( Tcl_GetIntFromObj(interp, objv[2], &iPhrase) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[3], &iTerm) ) return TCL_ERROR; + rc = p->pApi->xQueryToken(p->pFts, iPhrase, iTerm, &pTerm, &nTerm); + if( rc==SQLITE_OK ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(pTerm, nTerm)); + } + + break; + } + + CASE(19, "xInstToken") { + const char *pTerm = 0; + int nTerm = 0; + int iIdx = 0; + int iTerm = 0; + + if( Tcl_GetIntFromObj(interp, objv[2], &iIdx) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[3], &iTerm) ) return TCL_ERROR; + rc = p->pApi->xInstToken(p->pFts, iIdx, iTerm, &pTerm, &nTerm); + if( rc==SQLITE_OK ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(pTerm, nTerm)); + } + + break; + } + default: assert( 0 ); break; @@ -1117,6 +1152,176 @@ static int SQLITE_TCLAPI f5tRegisterTok( return TCL_OK; } +typedef struct OriginTextCtx OriginTextCtx; +struct OriginTextCtx { + sqlite3 *db; + fts5_api *pApi; +}; + +typedef struct OriginTextTokenizer OriginTextTokenizer; +struct OriginTextTokenizer { + Fts5Tokenizer *pTok; /* Underlying tokenizer object */ + fts5_tokenizer tokapi; /* API implementation for pTok */ +}; + +/* +** Delete the OriginTextCtx object indicated by the only argument. +*/ +static void f5tOrigintextTokenizerDelete(void *pCtx){ + OriginTextCtx *p = (OriginTextCtx*)pCtx; + ckfree(p); +} + +static int f5tOrigintextCreate( + void *pCtx, + const char **azArg, + int nArg, + Fts5Tokenizer **ppOut +){ + OriginTextCtx *p = (OriginTextCtx*)pCtx; + OriginTextTokenizer *pTok = 0; + void *pTokCtx = 0; + int rc = SQLITE_OK; + + pTok = (OriginTextTokenizer*)sqlite3_malloc(sizeof(OriginTextTokenizer)); + if( pTok==0 ){ + rc = SQLITE_NOMEM; + }else if( nArg<1 ){ + rc = SQLITE_ERROR; + }else{ + /* Locate the underlying tokenizer */ + rc = p->pApi->xFindTokenizer(p->pApi, azArg[0], &pTokCtx, &pTok->tokapi); + } + + /* Create the new tokenizer instance */ + if( rc==SQLITE_OK ){ + rc = pTok->tokapi.xCreate(pTokCtx, &azArg[1], nArg-1, &pTok->pTok); + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(pTok); + pTok = 0; + } + *ppOut = (Fts5Tokenizer*)pTok; + return rc; +} + +static void f5tOrigintextDelete(Fts5Tokenizer *pTokenizer){ + OriginTextTokenizer *p = (OriginTextTokenizer*)pTokenizer; + if( p->pTok ){ + p->tokapi.xDelete(p->pTok); + } + sqlite3_free(p); +} + +typedef struct OriginTextCb OriginTextCb; +struct OriginTextCb { + void *pCtx; + const char *pText; + int nText; + int (*xToken)(void *, int, const char *, int, int, int); + + char *aBuf; /* Buffer to use */ + int nBuf; /* Allocated size of aBuf[] */ +}; + +static int xOriginToken( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input */ +){ + OriginTextCb *p = (OriginTextCb*)pCtx; + int ret = 0; + + if( nToken==(iEnd-iStart) && 0==memcmp(pToken, &p->pText[iStart], nToken) ){ + /* Token exactly matches document text. Pass it through as is. */ + ret = p->xToken(p->pCtx, tflags, pToken, nToken, iStart, iEnd); + }else{ + int nReq = nToken + 1 + (iEnd-iStart); + if( nReq>p->nBuf ){ + sqlite3_free(p->aBuf); + p->aBuf = sqlite3_malloc(nReq*2); + if( p->aBuf==0 ) return SQLITE_NOMEM; + p->nBuf = nReq*2; + } + + memcpy(p->aBuf, pToken, nToken); + p->aBuf[nToken] = '\0'; + memcpy(&p->aBuf[nToken+1], &p->pText[iStart], iEnd-iStart); + ret = p->xToken(p->pCtx, tflags, p->aBuf, nReq, iStart, iEnd); + } + + return ret; +} + + +static int f5tOrigintextTokenize( + Fts5Tokenizer *pTokenizer, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + int (*xToken)(void *, int, const char *, int, int, int) +){ + OriginTextTokenizer *p = (OriginTextTokenizer*)pTokenizer; + OriginTextCb cb; + int ret; + + memset(&cb, 0, sizeof(cb)); + cb.pCtx = pCtx; + cb.pText = pText; + cb.nText = nText; + cb.xToken = xToken; + + ret = p->tokapi.xTokenize(p->pTok,(void*)&cb,flags,pText,nText,xOriginToken); + sqlite3_free(cb.aBuf); + return ret; +} + +/* +** sqlite3_fts5_register_origintext DB +** +** Description... +*/ +static int SQLITE_TCLAPI f5tRegisterOriginText( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3 *db = 0; + fts5_api *pApi = 0; + int rc; + fts5_tokenizer tok = {0, 0, 0}; + OriginTextCtx *pCtx = 0; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( f5tDbAndApi(interp, objv[1], &db, &pApi) ) return TCL_ERROR; + + pCtx = (OriginTextCtx*)ckalloc(sizeof(OriginTextCtx)); + pCtx->db = db; + pCtx->pApi = pApi; + + tok.xCreate = f5tOrigintextCreate; + tok.xDelete = f5tOrigintextDelete; + tok.xTokenize = f5tOrigintextTokenize; + rc = pApi->xCreateTokenizer( + pApi, "origintext", (void*)pCtx, &tok, f5tOrigintextTokenizerDelete + ); + + Tcl_ResetResult(interp); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); + return TCL_ERROR; + } + return TCL_OK; +} + /* ** Entry point. */ @@ -1133,7 +1338,8 @@ int Fts5tcl_Init(Tcl_Interp *interp){ { "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt, 0 }, { "sqlite3_fts5_token_hash", f5tTokenHash, 0 }, { "sqlite3_fts5_register_matchinfo", f5tRegisterMatchinfo, 0 }, - { "sqlite3_fts5_register_fts5tokenize", f5tRegisterTok, 0 } + { "sqlite3_fts5_register_fts5tokenize", f5tRegisterTok, 0 }, + { "sqlite3_fts5_register_origintext",f5tRegisterOriginText, 0 } }; int i; F5tTokenizerContext *pContext; diff --git a/ext/fts5/fts5_tokenize.c b/ext/fts5/fts5_tokenize.c index e61d6b1ed..f12056170 100644 --- a/ext/fts5/fts5_tokenize.c +++ b/ext/fts5/fts5_tokenize.c @@ -229,6 +229,12 @@ static const unsigned char sqlite3Utf8Trans1[] = { #endif /* ifndef SQLITE_AMALGAMATION */ +#define FTS5_SKIP_UTF8(zIn) { \ + if( ((unsigned char)(*(zIn++)))>=0xc0 ){ \ + while( (((unsigned char)*zIn) & 0xc0)==0x80 ){ zIn++; } \ + } \ +} + typedef struct Unicode61Tokenizer Unicode61Tokenizer; struct Unicode61Tokenizer { unsigned char aTokenChar[128]; /* ASCII range token characters */ @@ -1264,6 +1270,7 @@ static int fts5PorterTokenize( typedef struct TrigramTokenizer TrigramTokenizer; struct TrigramTokenizer { int bFold; /* True to fold to lower-case */ + int iFoldParam; /* Parameter to pass to Fts5UnicodeFold() */ }; /* @@ -1290,6 +1297,7 @@ static int fts5TriCreate( }else{ int i; pNew->bFold = 1; + pNew->iFoldParam = 0; for(i=0; rc==SQLITE_OK && ibFold = (zArg[0]=='0'); } + }else if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){ + if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){ + rc = SQLITE_ERROR; + }else{ + pNew->iFoldParam = (zArg[0]!='0') ? 2 : 0; + } }else{ rc = SQLITE_ERROR; } } + + if( pNew->iFoldParam!=0 && pNew->bFold==0 ){ + rc = SQLITE_ERROR; + } + if( rc!=SQLITE_OK ){ fts5TriDelete((Fts5Tokenizer*)pNew); pNew = 0; @@ -1324,40 +1343,62 @@ static int fts5TriTokenize( TrigramTokenizer *p = (TrigramTokenizer*)pTok; int rc = SQLITE_OK; char aBuf[32]; + char *zOut = aBuf; + int ii; const unsigned char *zIn = (const unsigned char*)pText; const unsigned char *zEof = &zIn[nText]; u32 iCode; + int aStart[3]; /* Input offset of each character in aBuf[] */ UNUSED_PARAM(unusedFlags); - while( 1 ){ - char *zOut = aBuf; - int iStart = zIn - (const unsigned char*)pText; - const unsigned char *zNext; - - READ_UTF8(zIn, zEof, iCode); - if( iCode==0 ) break; - zNext = zIn; - if( zInbFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); - WRITE_UTF8(zOut, iCode); + + /* Populate aBuf[] with the characters for the first trigram. */ + for(ii=0; ii<3; ii++){ + do { + aStart[ii] = zIn - (const unsigned char*)pText; READ_UTF8(zIn, zEof, iCode); - if( iCode==0 ) break; - }else{ - break; - } - if( zInbFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); - WRITE_UTF8(zOut, iCode); + if( iCode==0 ) return SQLITE_OK; + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); + }while( iCode==0 ); + WRITE_UTF8(zOut, iCode); + } + + /* At the start of each iteration of this loop: + ** + ** aBuf: Contains 3 characters. The 3 characters of the next trigram. + ** zOut: Points to the byte following the last character in aBuf. + ** aStart[3]: Contains the byte offset in the input text corresponding + ** to the start of each of the three characters in the buffer. + */ + assert( zIn<=zEof ); + while( 1 ){ + int iNext; /* Start of character following current tri */ + const char *z1; + + /* Read characters from the input up until the first non-diacritic */ + do { + iNext = zIn - (const unsigned char*)pText; READ_UTF8(zIn, zEof, iCode); if( iCode==0 ) break; - if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); - WRITE_UTF8(zOut, iCode); - }else{ - break; - } - rc = xToken(pCtx, 0, aBuf, zOut-aBuf, iStart, iStart + zOut-aBuf); - if( rc!=SQLITE_OK ) break; - zIn = zNext; + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); + }while( iCode==0 ); + + /* Pass the current trigram back to fts5 */ + rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext); + if( iCode==0 || rc!=SQLITE_OK ) break; + + /* Remove the first character from buffer aBuf[]. Append the character + ** with codepoint iCode. */ + z1 = aBuf; + FTS5_SKIP_UTF8(z1); + memmove(aBuf, z1, zOut - z1); + zOut -= (z1 - aBuf); + WRITE_UTF8(zOut, iCode); + + /* Update the aStart[] array */ + aStart[0] = aStart[1]; + aStart[1] = aStart[2]; + aStart[2] = iNext; } return rc; @@ -1380,7 +1421,9 @@ int sqlite3Fts5TokenizerPattern( ){ if( xCreate==fts5TriCreate ){ TrigramTokenizer *p = (TrigramTokenizer*)pTok; - return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB; + if( p->iFoldParam==0 ){ + return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB; + } } return FTS5_PATTERN_NONE; } diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c index d738ada31..4782d0fb9 100644 --- a/ext/fts5/fts5_vocab.c +++ b/ext/fts5/fts5_vocab.c @@ -629,7 +629,7 @@ static int fts5VocabFilterMethod( if( pEq ){ zTerm = (const char *)sqlite3_value_text(pEq); nTerm = sqlite3_value_bytes(pEq); - f = 0; + f = FTS5INDEX_QUERY_NOTOKENDATA; }else{ if( pGe ){ zTerm = (const char *)sqlite3_value_text(pGe); diff --git a/ext/fts5/test/fts5_common.tcl b/ext/fts5/test/fts5_common.tcl index 9c012932d..fda388a6f 100644 --- a/ext/fts5/test/fts5_common.tcl +++ b/ext/fts5/test/fts5_common.tcl @@ -61,6 +61,12 @@ proc fts5_test_collist {cmd} { set res } +proc fts5_collist {cmd iPhrase} { + set res [list] + $cmd xPhraseColumnForeach $iPhrase c { lappend res $c } + set res +} + proc fts5_test_columnsize {cmd} { set res [list] for {set i 0} {$i < [$cmd xColumnCount]} {incr i} { @@ -69,6 +75,10 @@ proc fts5_test_columnsize {cmd} { set res } +proc fts5_columntext {cmd iCol} { + $cmd xColumnText $iCol +} + proc fts5_test_columntext {cmd} { set res [list] for {set i 0} {$i < [$cmd xColumnCount]} {incr i} { @@ -125,6 +135,13 @@ proc fts5_test_queryphrase {cmd} { set res } +proc fts5_queryphrase {cmd iPhrase} { + set cnt [list] + for {set j 0} {$j < [$cmd xColumnCount]} {incr j} { lappend cnt 0 } + $cmd xQueryPhrase $iPhrase [list test_queryphrase_cb cnt] + set cnt +} + proc fts5_test_phrasecount {cmd} { $cmd xPhraseCount } @@ -154,6 +171,9 @@ proc fts5_aux_test_functions {db} { fts5_test_queryphrase fts5_test_phrasecount + fts5_columntext + fts5_queryphrase + fts5_collist } { sqlite3_fts5_create_function $db $f $f } @@ -438,6 +458,20 @@ proc detail_is_none {} { detail_check ; expr {$::detail == "none"} } proc detail_is_col {} { detail_check ; expr {$::detail == "col" } } proc detail_is_full {} { detail_check ; expr {$::detail == "full"} } +proc foreach_tokenizer_mode {prefix script} { + set saved $::testprefix + foreach {d mapping} { + "" {} + "-origintext" {, tokenize="origintext unicode61", tokendata=1} + } { + set s [string map [list %TOKENIZER% $mapping] $script] + set ::testprefix "$prefix$d" + reset_db + sqlite3_fts5_register_origintext db + uplevel $s + } + set ::testprefix $saved +} #------------------------------------------------------------------------- # Convert a poslist of the type returned by fts5_test_poslist() to a diff --git a/ext/fts5/test/fts5aa.test b/ext/fts5/test/fts5aa.test index e1551fc51..a80a307a4 100644 --- a/ext/fts5/test/fts5aa.test +++ b/ext/fts5/test/fts5aa.test @@ -22,6 +22,7 @@ ifcapable !fts5 { } foreach_detail_mode $::testprefix { +foreach_tokenizer_mode $::testprefix { do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts5(a, b, c); @@ -44,7 +45,7 @@ do_execsql_test 1.1 { # do_execsql_test 2.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%); } do_execsql_test 2.1 { INSERT INTO t1 VALUES('a b c', 'd e f'); @@ -73,8 +74,9 @@ do_execsql_test 2.4 { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 3.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); } foreach {i x y} { 1 {g f d b f} {h h e i a} @@ -97,8 +99,9 @@ foreach {i x y} { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 4.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } foreach {i x y} { @@ -121,8 +124,9 @@ foreach {i x y} { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 5.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } foreach {i x y} { @@ -145,8 +149,9 @@ foreach {i x y} { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 6.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } @@ -181,6 +186,7 @@ do_execsql_test 6.6 { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db expr srand(0) do_execsql_test 7.0 { CREATE VIRTUAL TABLE t1 USING fts5(x,y,z); @@ -222,6 +228,7 @@ for {set i 1} {$i <= 10} {incr i} { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 8.0 { CREATE VIRTUAL TABLE t1 USING fts5(x, prefix="1,2,3"); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); @@ -236,6 +243,7 @@ do_execsql_test 8.1 { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db expr srand(0) @@ -280,8 +288,9 @@ for {set i 1} {$i <= 10} {incr i} { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 10.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); } set d10 { 1 {g f d b f} {h h e i a} @@ -314,19 +323,19 @@ do_execsql_test 10.4.2 { INSERT INTO t1(t1) VALUES('integrity-check') } #------------------------------------------------------------------------- # do_catchsql_test 11.1 { - CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank, detail=%DETAIL%); + CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank, detail=%DETAIL% %TOKENIZER%); } {1 {reserved fts5 column name: rank}} do_catchsql_test 11.2 { - CREATE VIRTUAL TABLE rank USING fts5(a, b, c, detail=%DETAIL%); + CREATE VIRTUAL TABLE rank USING fts5(a, b, c, detail=%DETAIL% %TOKENIZER%); } {1 {reserved fts5 table name: rank}} do_catchsql_test 11.3 { - CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rowid, detail=%DETAIL%); + CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rowid, detail=%DETAIL% %TOKENIZER%); } {1 {reserved fts5 column name: rowid}} #------------------------------------------------------------------------- # do_execsql_test 12.1 { - CREATE VIRTUAL TABLE t2 USING fts5(x,y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t2 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); } {} do_catchsql_test 12.2 { @@ -341,8 +350,9 @@ do_test 12.3 { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 13.1 { - CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL% %TOKENIZER%); INSERT INTO t1(rowid, x) VALUES(1, 'o n e'), (2, 't w o'); } {} @@ -365,8 +375,9 @@ do_execsql_test 13.6 { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 14.1 { - CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); WITH d(x,y) AS ( SELECT NULL, 'xyz xyz xyz xyz xyz xyz' @@ -449,8 +460,9 @@ do_catchsql_test 16.2 { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 17.1 { - CREATE VIRTUAL TABLE b2 USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE b2 USING fts5(x, detail=%DETAIL% %TOKENIZER%); INSERT INTO b2 VALUES('a'); INSERT INTO b2 VALUES('b'); INSERT INTO b2 VALUES('c'); @@ -466,8 +478,9 @@ do_test 17.2 { if {[string match n* %DETAIL%]==0} { reset_db + sqlite3_fts5_register_origintext db do_execsql_test 17.3 { - CREATE VIRTUAL TABLE c2 USING fts5(x, y, detail=%DETAIL%); + CREATE VIRTUAL TABLE c2 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%); INSERT INTO c2 VALUES('x x x', 'x x x'); SELECT rowid FROM c2 WHERE c2 MATCH 'y:x'; } {1} @@ -476,8 +489,9 @@ if {[string match n* %DETAIL%]==0} { #------------------------------------------------------------------------- # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 17.1 { - CREATE VIRTUAL TABLE uio USING fts5(ttt, detail=%DETAIL%); + CREATE VIRTUAL TABLE uio USING fts5(ttt, detail=%DETAIL% %TOKENIZER%); INSERT INTO uio VALUES(NULL); INSERT INTO uio SELECT NULL FROM uio; INSERT INTO uio SELECT NULL FROM uio; @@ -524,8 +538,8 @@ do_execsql_test 17.9 { #-------------------------------------------------------------------- # do_execsql_test 18.1 { - CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL%); - CREATE VIRTUAL TABLE t2 USING fts5(c, d, detail=%DETAIL%); + CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t2 USING fts5(c, d, detail=%DETAIL% %TOKENIZER%); INSERT INTO t1 VALUES('abc*', NULL); INSERT INTO t2 VALUES(1, 'abcdefg'); } @@ -540,8 +554,9 @@ do_execsql_test 18.3 { # fts5 table in the temp schema. # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 19.0 { - CREATE VIRTUAL TABLE temp.t1 USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE temp.t1 USING fts5(x, detail=%DETAIL% %TOKENIZER%); INSERT INTO t1 VALUES('x y z'); INSERT INTO t1 VALUES('w x 1'); SELECT rowid FROM t1 WHERE t1 MATCH 'x'; @@ -551,8 +566,9 @@ do_execsql_test 19.0 { # Test that 6 and 7 byte varints can be read. # reset_db +sqlite3_fts5_register_origintext db do_execsql_test 20.0 { - CREATE VIRTUAL TABLE temp.tmp USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE temp.tmp USING fts5(x, detail=%DETAIL% %TOKENIZER%); } set ::ids [list \ 0 [expr 1<<36] [expr 2<<36] [expr 1<<43] [expr 2<<43] @@ -570,7 +586,7 @@ do_test 20.1 { # do_execsql_test 21.0 { CREATE TEMP TABLE t8(a, b); - CREATE VIRTUAL TABLE ft USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE ft USING fts5(x, detail=%DETAIL% %TOKENIZER%); } do_execsql_test 21.1 { @@ -581,7 +597,7 @@ do_execsql_test 21.1 { } do_execsql_test 22.0 { - CREATE VIRTUAL TABLE t9 USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE t9 USING fts5(x, detail=%DETAIL% %TOKENIZER%); INSERT INTO t9(rowid, x) VALUES(2, 'bbb'); BEGIN; INSERT INTO t9(rowid, x) VALUES(1, 'aaa'); @@ -596,7 +612,7 @@ do_execsql_test 22.1 { #------------------------------------------------------------------------- do_execsql_test 23.0 { - CREATE VIRTUAL TABLE t10 USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE t10 USING fts5(x, detail=%DETAIL% %TOKENIZER%); CREATE TABLE t11(x); } do_execsql_test 23.1 { @@ -608,7 +624,7 @@ do_execsql_test 23.2 { #------------------------------------------------------------------------- do_execsql_test 24.0 { - CREATE VIRTUAL TABLE t12 USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE t12 USING fts5(x, detail=%DETAIL% %TOKENIZER%); INSERT INTO t12 VALUES('aaaa'); } do_execsql_test 24.1 { @@ -618,6 +634,9 @@ do_execsql_test 24.1 { INSERT INTO t12 VALUES('aaaa'); END; } +execsql_pp { + SELECT rowid, hex(block) FROM t12_data +} do_execsql_test 24.2 { INSERT INTO t12(t12) VALUES('integrity-check'); } @@ -627,7 +646,7 @@ do_execsql_test 24.3 { #------------------------------------------------------------------------- do_execsql_test 25.0 { - CREATE VIRTUAL TABLE t13 USING fts5(x, detail=%DETAIL%); + CREATE VIRTUAL TABLE t13 USING fts5(x, detail=%DETAIL% %TOKENIZER%); } do_execsql_test 25.1 { BEGIN; @@ -638,6 +657,7 @@ SELECT * FROM t13('BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB } +} } expand_all_sql db diff --git a/ext/fts5/test/fts5aux.test b/ext/fts5/test/fts5aux.test index b5a13aea1..5569f48cf 100644 --- a/ext/fts5/test/fts5aux.test +++ b/ext/fts5/test/fts5aux.test @@ -334,4 +334,47 @@ do_execsql_test 11.2 { SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1; } {5} +#------------------------------------------------------------------------- +# Test that xColumnText returns SQLITE_RANGE when it should. +# +reset_db +fts5_aux_test_functions db +do_execsql_test 12.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, b, c); + INSERT INTO t1 VALUES('one', 'two', 'three'); + INSERT INTO t1 VALUES('one', 'one', 'one'); + INSERT INTO t1 VALUES('two', 'two', 'two'); + INSERT INTO t1 VALUES('three', 'three', 'three'); +} + +do_catchsql_test 12.1.1 { + SELECT fts5_columntext(t1, -1) FROM t1('two'); +} {1 SQLITE_RANGE} +do_catchsql_test 12.1.2 { + SELECT fts5_columntext(t1, 3) FROM t1('two'); +} {1 SQLITE_RANGE} +do_catchsql_test 12.1.2 { + SELECT fts5_columntext(t1, 1) FROM t1('one AND two'); +} {0 two} + +do_catchsql_test 12.2.1 { + SELECT fts5_queryphrase(t1, -1) FROM t1('one AND two'); +} {1 SQLITE_RANGE} +do_catchsql_test 12.2.2 { + SELECT fts5_queryphrase(t1, 2) FROM t1('one AND two'); +} {1 SQLITE_RANGE} +do_catchsql_test 12.2.3 { + SELECT fts5_queryphrase(t1, 1) FROM t1('one AND two'); +} {0 {{1 2 1}}} + +do_catchsql_test 12.3.1 { + SELECT fts5_collist(t1, -1) FROM t1('one AND two'); +} {1 SQLITE_RANGE} +do_catchsql_test 12.3.2 { + SELECT fts5_collist(t1, 2) FROM t1('one AND two'); +} {1 SQLITE_RANGE} +do_catchsql_test 12.3.3 { + SELECT fts5_collist(t1, 1) FROM t1('one AND two'); +} {0 1} + finish_test diff --git a/ext/fts5/test/fts5content.test b/ext/fts5/test/fts5content.test index ca2726a90..986d7f331 100644 --- a/ext/fts5/test/fts5content.test +++ b/ext/fts5/test/fts5content.test @@ -293,4 +293,39 @@ do_catchsql_test 7.2.5 { SELECT * FROM t1('abc') ORDER BY rank; } {1 {recursively defined fts5 content table}} +#--------------------------------------------------------------------------- +# Check that if the content table is a view, and that view contains an +# error, a reasonable error message is returned if the user tries to +# read from the view via the fts5 table. +# +reset_db +do_execsql_test 8.1 { + CREATE VIEW a1 AS + SELECT 1 AS r, text_value(1) AS t + UNION ALL + SELECT 2 AS r, text_value(2) AS t; + + CREATE VIRTUAL TABLE t1 USING fts5(t, content='a1', content_rowid='r'); +} + +foreach {tn sql} { + 1 "SELECT * FROM t1" + 2 "INSERT INTO t1(t1) VALUES('rebuild')" + 3 "SELECT * FROM t1 WHERE rowid=1" +} { + do_catchsql_test 8.2.$tn $sql {1 {no such function: text_value}} +} + +proc text_value {i} { + if {$i==1} { return "one" } + if {$i==2} { return "two" } + return "many" +} +db func text_value text_value + +do_execsql_test 8.3.1 { SELECT * FROM t1 } {one two} +do_execsql_test 8.3.2 { INSERT INTO t1(t1) VALUES('rebuild') } +do_execsql_test 8.3.3 { SELECT * FROM t1 WHERE rowid=1 } {one} +do_execsql_test 8.3.4 { SELECT rowid FROM t1('two') } {2} + finish_test diff --git a/ext/fts5/test/fts5corrupt5.test b/ext/fts5/test/fts5corrupt5.test index 60910dce7..1588d8d69 100644 --- a/ext/fts5/test/fts5corrupt5.test +++ b/ext/fts5/test/fts5corrupt5.test @@ -966,6 +966,491 @@ do_catchsql_test 6.2 { UPDATE t1 SET content=randomblob(500) WHERE t1; } {1 {constraint failed}} +#------------------------------------------------------------------------- +reset_db +do_test 7.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 40960 pagesize 4096 filename crash-d8b4a99207c10b.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 0a .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 0d 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 96: 00 00 00 00 0d 00 00 00 0d 0b 62 00 0f 97 0f 40 ..........b....@ +| 112: 0e d5 0e 75 0e 18 0d c0 0d 66 0d 0f 0c a4 0c 44 ...u.....f.....D +| 128: 0b ec 0b a7 0b 62 00 00 00 00 00 00 00 00 00 00 .....b.......... +| 2912: 00 00 43 0d 06 17 11 11 08 75 74 61 62 6c 65 74 ..C......utablet +| 2928: 34 74 34 43 52 45 41 54 45 20 56 49 52 54 55 41 4t4CREATE VIRTUA +| 2944: 4c 20 54 41 42 4c 45 20 74 34 20 55 53 49 4e 47 L TABLE t4 USING +| 2960: 20 66 74 73 35 76 6f 63 61 62 28 27 74 32 27 2c fts5vocab('t2', +| 2976: 20 27 72 6f 77 27 29 43 0c 06 17 11 11 08 75 74 'row')C......ut +| 2992: 61 62 6c 65 74 33 74 33 43 52 45 41 54 45 20 56 ablet3t3CREATE V +| 3008: 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 33 20 IRTUAL TABLE t3 +| 3024: 55 53 49 4e 47 20 66 74 73 35 76 6f 63 61 62 28 USING fts5vocab( +| 3040: 27 74 31 27 2c 20 27 72 6f 77 27 29 56 0b 06 17 't1', 'row')V... +| 3056: 1f 1f 01 7d 74 61 62 6c 65 74 32 5f 63 6f 6e 66 ....tablet2_conf +| 3072: 69 67 74 32 5f 63 6f 6e 66 69 67 0a 43 52 45 41 igt2_config.CREA +| 3088: 54 45 20 54 41 42 4c 45 20 27 74 32 5f 63 6f 6e TE TABLE 't2_con +| 3104: 66 69 67 27 28 6b 20 50 52 49 4d 41 52 59 20 4b fig'(k PRIMARY K +| 3120: 45 59 2c 20 76 29 20 57 49 54 48 4f 55 54 20 52 EY, v) WITHOUT R +| 3136: 4f 57 49 44 5e 0a 07 17 21 21 01 81 07 74 61 62 OWID^...!!...tab +| 3152: 6c 65 74 32 5f 63 6f 6e 74 65 6e 74 74 32 5f 63 let2_contentt2_c +| 3168: 6f 6e 74 65 6e 74 09 43 52 45 41 54 45 20 54 41 ontent.CREATE TA +| 3184: 42 4c 45 20 27 74 32 5f 63 6f 6e 74 65 6e 74 27 BLE 't2_content' +| 3200: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM +| 3216: 41 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 2c ARY KEY, c0, c1, +| 3232: 20 63 32 29 69 09 07 17 19 19 01 81 2d 74 61 62 c2)i.......-tab +| 3248: 6c 65 74 32 5f 69 64 78 74 32 5f 69 64 78 08 43 let2_idxt2_idx.C +| 3264: 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 32 5f REATE TABLE 't2_ +| 3280: 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d idx'(segid, term +| 3296: 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 , pgno, PRIMARY +| 3312: 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 KEY(segid, term) +| 3328: 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 ) WITHOUT ROWIDU +| 3344: 08 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 32 5f ........tablet2_ +| 3360: 64 61 74 61 74 32 5f 64 61 74 61 07 43 52 45 41 datat2_data.CREA +| 3376: 54 45 20 54 41 42 4c 45 20 27 74 32 5f 64 61 74 TE TABLE 't2_dat +| 3392: 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 a'(id INTEGER PR +| 3408: 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b IMARY KEY, block +| 3424: 20 42 4c 4f 42 29 58 07 07 17 11 11 08 81 1d 74 BLOB)X........t +| 3440: 61 62 6c 65 74 32 74 32 43 52 45 41 54 45 20 56 ablet2t2CREATE V +| 3456: 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 32 20 IRTUAL TABLE t2 +| 3472: 55 53 49 4e 47 20 66 74 73 35 28 27 61 27 2c 5b USING fts5('a',[ +| 3488: 62 5d 2c 22 63 22 2c 64 65 74 61 69 6c 3d 6e 6f b],.c.,detail=no +| 3504: 6e 65 2c 63 6f 6c 75 6d 6e 73 69 7a 65 3d 30 29 ne,columnsize=0) +| 3520: 56 06 06 17 1f 1f 01 7d 74 61 62 6c 65 74 31 5f V.......tablet1_ +| 3536: 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 69 67 06 configt1_config. +| 3552: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 3568: 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 49 4d 41 _config'(k PRIMA +| 3584: 52 59 20 4b 45 59 2c 20 76 29 20 57 49 54 48 4f RY KEY, v) WITHO +| 3600: 55 54 20 52 4f 57 49 44 5b 05 07 17 21 21 01 81 UT ROWID[...!!.. +| 3616: 01 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 .tablet1_docsize +| 3632: 74 31 5f 64 6f 63 73 69 7a 65 05 43 52 45 41 54 t1_docsize.CREAT +| 3648: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs +| 3664: 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 52 20 ize'(id INTEGER +| 3680: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 7a 20 PRIMARY KEY, sz +| 3696: 42 4c 4f 42 29 5e 04 07 17 21 21 01 81 07 74 61 BLOB)^...!!...ta +| 3712: 62 6c 65 74 31 5f 63 6f 6e 74 65 6e 74 74 31 5f blet1_contentt1_ +| 3728: 63 6f 6e 74 65 6e 74 04 43 52 45 41 54 45 20 54 content.CREATE T +| 3744: 41 42 4c 45 20 27 74 31 5f 63 6f 6e 74 65 6e 74 ABLE 't1_content +| 3760: 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 '(id INTEGER PRI +| 3776: 4d 41 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 MARY KEY, c0, c1 +| 3792: 2c 20 63 32 29 69 03 07 17 19 19 01 81 2d 74 61 , c2)i.......-ta +| 3808: 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 64 78 03 blet1_idxt1_idx. +| 3824: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 3840: 5f 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 _idx'(segid, ter +| 3856: 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 m, pgno, PRIMARY +| 3872: 20 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d KEY(segid, term +| 3888: 29 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 )) WITHOUT ROWID +| 3904: 55 02 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 U........tablet1 +| 3920: 5f 64 61 74 61 74 31 5f 64 61 74 61 02 43 52 45 _datat1_data.CRE +| 3936: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 61 ATE TABLE 't1_da +| 3952: 74 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 ta'(id INTEGER P +| 3968: 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 RIMARY KEY, bloc +| 3984: 6b 20 42 4c 4f 42 29 67 01 07 17 11 11 08 81 3b k BLOB)g.......; +| 4000: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE +| 4016: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1 +| 4032: 20 55 53 49 4e 47 20 66 74 73 35 28 61 2c 62 20 USING fts5(a,b +| 4048: 75 6e 69 6e 64 65 78 65 64 2c 63 2c 74 6f 6b 65 unindexed,c,toke +| 4064: 6e 69 7a 65 3d 22 70 6f 72 74 65 72 20 61 73 63 nize=.porter asc +| 4080: 69 69 22 2c 74 6f 6b 65 6e 64 61 74 61 3d 31 29 ii.,tokendata=1) +| page 2 offset 4096 +| 0: 0d 0f 68 00 05 0f 13 00 0f e6 0f 13 0f a8 0f 7c ..h............| +| 16: 0f 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .*.............. +| 3856: 00 00 00 15 0a 03 00 30 00 00 00 00 01 03 03 00 .......0........ +| 3872: 03 01 01 01 02 01 01 03 01 01 37 8c 80 80 80 80 ..........7..... +| 3888: 01 03 00 74 00 00 00 2e 02 30 61 03 02 02 01 01 ...t.....0a..... +| 3904: 62 03 02 03 01 01 63 03 02 04 01 01 67 03 06 01 b.....c.....g... +| 3920: 02 02 01 01 68 03 06 01 02 03 01 01 69 03 06 01 ....h.......i... +| 3936: 02 04 04 06 06 06 08 08 0f ef 00 14 2a 00 00 00 ............*... +| 3952: 00 01 02 02 00 02 01 01 01 02 01 01 25 88 80 80 ............%... +| 3968: 80 80 01 03 00 50 00 00 00 1f 02 30 67 02 08 02 .....P.....0g... +| 3984: 01 02 02 01 01 68 02 08 03 01 02 03 01 01 69 02 .....h........i. +| 4000: 08 04 01 02 04 04 09 09 37 84 80 80 80 7f f1 03 ........7....... +| 4016: 00 74 00 00 00 2e 02 30 61 01 02 02 01 01 62 01 .t.....0a.....b. +| 4032: 02 03 01 01 63 01 02 04 01 01 67 01 06 01 02 02 ....c.....g..... +| 4048: 01 01 68 01 06 01 02 03 01 01 69 01 06 01 02 04 ..h.......i..... +| 4064: 04 06 06 06 08 08 07 01 03 00 14 03 09 00 09 00 ................ +| 4080: 00 00 11 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ +| page 3 offset 8192 +| 0: 0a 00 00 00 03 0f ec 00 0f fa 0f f3 0f ec 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 06 04 01 0c ................ +| 4080: 01 03 02 06 04 01 0c 01 02 02 05 04 09 0c 01 02 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 03 0f be 00 0f ea 0f d4 0f be 00 00 ................ +| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 03 ................ +| 4032: 05 00 17 17 17 61 20 62 20 63 67 20 68 20 69 67 .....a b cg h ig +| 4048: 20 68 20 69 14 02 05 00 17 17 17 67 20 68 20 69 h i.......g h i +| 4064: 61 20 62 20 63 67 20 68 20 69 14 01 05 00 17 17 a b cg h i...... +| 4080: 17 61 20 62 20 63 64 20 65 20 66 67 20 68 20 69 .a b cd e fg h i +| page 5 offset 16384 +| 0: 0d 00 00 00 03 0f e8 00 0f f8 0f f0 0f e8 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 06 03 03 00 12 03 00 03 ................ +| 4080: 06 02 03 00 12 03 00 03 06 01 03 00 12 03 00 03 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| page 7 offset 24576 +| 0: 0d 00 00 00 03 0f 9e 00 0f e6 0f ef 0f 9e 00 00 ................ +| 3984: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 84 ..............A. +| 4000: 80 80 80 80 01 04 00 81 06 00 00 00 34 02 30 61 ............4.0a +| 4016: 01 01 01 01 01 62 01 01 01 01 01 63 01 01 01 01 .....b.....c.... +| 4032: 01 64 01 01 01 65 01 01 01 66 01 01 01 67 01 01 .d...e...f...g.. +| 4048: 01 01 01 68 01 01 01 01 01 69 01 01 01 04 06 06 ...h.....i...... +| 4064: 06 04 04 04 06 06 07 01 03 00 14 03 09 09 09 0f ................ +| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ +| page 8 offset 28672 +| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 9 offset 32768 +| 0: 0d 00 00 00 03 0f be 00 0f ea 0f d4 0f be 00 00 ................ +| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 03 ................ +| 4032: 05 00 17 17 17 61 20 62 20 63 67 20 68 20 69 67 .....a b cg h ig +| 4048: 20 68 20 69 14 02 05 00 17 17 17 67 20 68 20 69 h i.......g h i +| 4064: 61 20 62 20 63 67 20 68 20 69 14 01 05 00 17 17 a b cg h i...... +| 4080: 17 61 20 62 20 63 64 20 65 20 66 67 20 68 20 69 .a b cd e fg h i +| page 10 offset 36864 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| end crash-d8b4a99207c10b.db +}]} {} + +do_catchsql_test 7.1 { + SELECT snippet(t1, -1, '.', '..', '[', ']'), + highlight(t1, 2, '[', ']') + FROM t1('g + h') + WHERE rank MATCH 'bm25(1.0, 1.0)' ORDER BY rank; +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +reset_db +do_test 8.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 20480 pagesize 4096 filename crash-d57c01958e48ab.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 05 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 05 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 96: 00 00 00 00 0d 00 00 00 05 0e 10 00 0f 97 0f 40 ...............@ +| 112: 0e d5 0e 68 0e 10 01 00 00 00 00 00 00 00 00 00 ...h............ +| 3600: 56 05 06 17 1f 1f 01 7d 74 61 62 6c 65 74 31 5f V.......tablet1_ +| 3616: 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 69 67 05 configt1_config. +| 3632: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 3648: 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 49 4d 41 _config'(k PRIMA +| 3664: 52 59 20 4b 45 59 2c 20 76 29 20 57 49 54 48 4f RY KEY, v) WITHO +| 3680: 55 54 20 52 4f 57 49 44 6b 04 07 17 21 21 01 81 UT ROWIDk...!!.. +| 3696: 21 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 !tablet1_docsize +| 3712: 74 31 5f 64 6f 63 73 69 7a 65 04 43 52 45 41 54 t1_docsize.CREAT +| 3728: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs +| 3744: 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 52 20 ize'(id INTEGER +| 3760: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 7a 20 PRIMARY KEY, sz +| 3776: 42 4c 4f 42 2c 20 6f 72 69 67 69 6e 20 49 4e 54 BLOB, origin INT +| 3792: 45 47 45 52 29 69 03 07 17 19 19 01 81 2d 74 61 EGER)i.......-ta +| 3808: 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 64 78 03 blet1_idxt1_idx. +| 3824: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 +| 3840: 5f 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 _idx'(segid, ter +| 3856: 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 m, pgno, PRIMARY +| 3872: 20 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d KEY(segid, term +| 3888: 29 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 )) WITHOUT ROWID +| 3904: 55 02 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 U........tablet1 +| 3920: 5f 64 61 74 61 74 31 5f 64 61 74 61 02 43 52 45 _datat1_data.CRE +| 3936: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 61 ATE TABLE 't1_da +| 3952: 74 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 ta'(id INTEGER P +| 3968: 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 RIMARY KEY, bloc +| 3984: 6b 20 42 4c 4f 42 29 67 01 07 17 11 11 08 81 3b k BLOB)g.......; +| 4000: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE +| 4016: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1 +| 4032: 20 55 53 49 4e 47 20 66 74 73 35 28 61 2c 20 62 USING fts5(a, b +| 4048: 2c 20 63 6f 6e 74 65 6e 74 3d 27 27 2c 20 63 6f , content='', co +| 4064: 6e 74 65 6e 74 6c 65 73 73 5f 64 65 6c 65 74 65 ntentless_delete +| 4080: 3d 31 2c 20 74 6f 6b 65 6e 64 61 74 61 3d 31 29 =1, tokendata=1) +| page 2 offset 4096 +| 0: 0d 0f eb 00 03 0e 17 00 0f e2 0e 17 0e 31 00 00 .............1.. +| 16: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3600: 00 00 00 00 00 00 00 18 0a 03 00 36 00 00 00 00 ...........6.... +| 3616: ff 00 00 01 01 01 01 00 01 01 01 01 01 01 00 00 ................ +| 3632: 07 83 29 84 80 80 80 80 01 04 00 86 56 00 00 01 ..).........V... +| 3648: 96 04 30 61 61 61 01 02 02 01 04 02 04 01 08 02 ..0aaa.......... +| 3664: 04 04 04 01 10 02 04 04 04 04 04 04 04 01 20 02 .............. . +| 3680: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 01 ................ +| 3696: 40 02 04 04 04 04 04 04 04 04 04 04 04 04 04 04 @............... +| 3712: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ +| 3728: 04 01 81 00 02 04 04 04 04 04 04 04 04 04 04 04 ................ +| 3744: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ +| 3760: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ +| 3776: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ +| 3792: 04 04 04 04 02 02 62 63 01 06 01 01 02 01 03 62 ......bc.......b +| 3808: 62 62 02 02 03 01 04 03 06 01 08 03 06 06 06 01 bb.............. +| 3824: 10 03 06 06 06 06 06 06 06 01 20 03 06 06 06 06 .......... ..... +| 3840: 06 06 06 06 06 06 06 06 06 06 06 01 40 03 06 06 ............@... +| 3856: 06 06 06 06 06 06 06 06 06 06 06 06 06 06 06 06 ................ +| 3872: 06 06 06 06 06 06 06 06 06 06 16 06 06 02 02 63 ...............c +| 3888: 64 02 06 01 01 02 01 03 63 63 63 03 02 05 01 04 d.......ccc..... +| 3904: 05 0a 01 08 05 0a 0a 0a 01 10 05 0a 0a 0a 0a 0a ................ +| 3920: 0a 0a 01 20 05 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a ... ............ +| 3936: 0a 0a 0a 0a 02 02 64 65 03 06 01 01 02 01 03 64 ......de.......d +| 3952: 64 64 04 02 09 01 04 09 12 01 08 09 12 12 12 01 dd.............. +| 3968: 10 09 12 12 12 12 12 12 12 02 02 65 66 04 06 01 ...........ef... +| 3984: 01 02 01 03 65 65 65 05 02 11 01 04 11 22 01 08 ....eee......... +| 4000: 11 22 22 22 02 02 66 67 05 06 01 01 02 01 03 66 ......fg.......f +| 4016: 56 66 06 02 21 01 04 21 42 02 02 67 68 06 06 01 Vf..!..!B..gh... +| 4032: 01 02 cb 03 67 67 67 07 02 41 02 02 68 69 07 06 ....ggg..A..hi.. +| 4048: 01 01 02 04 81 13 09 50 09 2e 09 1c 09 12 09 0c .......P........ +| 4064: 09 08 07 01 03 00 14 07 81 77 07 00 00 00 15 22 .........w...... +| 4080: 00 00 00 00 ff 00 00 01 00 00 00 00 00 00 05 0c ................ +| page 3 offset 8192 +| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 07 0f c8 00 0f f8 0f f0 0f e8 0f e0 ................ +| 16: 0f d8 0f d0 0f c8 00 00 00 00 00 00 00 00 00 00 ................ +| 4032: 00 00 00 00 00 00 00 00 06 07 04 00 10 09 7f 01 ................ +| 4048: 06 06 04 00 10 09 3f 01 06 05 04 00 10 09 1f 01 ......?......... +| 4064: 06 04 04 00 10 09 0f 01 06 03 04 00 10 09 07 01 ................ +| 4080: 06 02 04 00 10 09 03 01 06 01 04 00 10 09 01 01 ................ +| page 5 offset 16384 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| end crash-d57c01958e48ab.db +}]} {} + +do_catchsql_test 8.1 { + SELECT rowid FROM t1('a* NOT ý‘') ; +} {0 {1 2 3 4 5 6 7}} + +#------------------------------------------------------------------------- +reset_db +do_test 9.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 32768 pagesize 4096 filename crash-c76a16c24c8ba6.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 08 .....@ ........ +| 32: 00 00 00 02 00 00 00 01 00 00 00 09 00 00 00 04 ................ +| 96: 00 00 00 00 0d 0f c7 00 07 0d 92 00 0f 8d 0f 36 ...............6 +| 112: 0e cb 0e 6b 0e 0e 0d b6 0d 92 0d 92 00 00 00 00 ...k............ +| 3472: 00 00 22 08 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet +| 3488: 32 74 32 08 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE +| 3504: 20 74 32 28 78 29 56 07 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta +| 3520: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c +| 3536: 6f 6e 66 69 67 07 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB +| 3552: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k +| 3568: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) +| 3584: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 06 WITHOUT ROWID[. +| 3600: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d +| 3616: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize +| 3632: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't +| 3648: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN +| 3664: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE +| 3680: 59 2c 20 73 7a 20 42 4c 4f 42 29 5e 05 07 17 21 Y, sz BLOB)^...! +| 3696: 21 01 81 07 74 61 62 6c 65 74 31 5f 63 6f 6e 74 !...tablet1_cont +| 3712: 65 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 05 43 52 entt1_content.CR +| 3728: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 EATE TABLE 't1_c +| 3744: 6f 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 ontent'(id INTEG +| 3760: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, +| 3776: 63 30 2c 20 63 31 2c 20 63 32 29 69 04 07 17 19 c0, c1, c2)i.... +| 3792: 19 01 81 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 ...-tablet1_idxt +| 3808: 31 5f 69 64 78 04 43 52 45 41 54 45 20 54 41 42 1_idx.CREATE TAB +| 3824: 4c 45 20 27 74 31 5f 69 64 78 27 28 73 65 67 69 LE 't1_idx'(segi +| 3840: 64 2c 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 d, term, pgno, P +| 3856: 52 49 4d 41 52 59 20 4b 45 59 28 73 65 67 69 64 RIMARY KEY(segid +| 3872: 2c 20 74 65 72 6d 29 29 20 57 49 54 48 4f 55 54 , term)) WITHOUT +| 3888: 20 52 4f 57 49 44 55 03 07 17 1b 1b 01 81 01 74 ROWIDU........t +| 3904: 61 62 6c 65 74 31 5f 64 61 74 61 74 31 5f 64 61 ablet1_datat1_da +| 3920: 74 61 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 ta.CREATE TABLE +| 3936: 27 74 31 5f 64 61 74 61 27 28 69 64 20 49 4e 54 't1_data'(id INT +| 3952: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY +| 3968: 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 38 02 06 , block BLOB)8.. +| 3984: 17 11 11 08 5f 74 61 62 6c 65 74 31 74 31 43 52 ...._tablet1t1CR +| 4000: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB +| 4016: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 LE t1 USING fts5 +| 4032: 28 61 2c 62 2c 63 29 00 00 00 00 00 00 00 00 00 (a,b,c)......... +| page 3 offset 8192 +| 0: 0d 00 00 00 03 0c 94 00 0f e6 0f ef 0c 94 00 00 ................ +| 3216: 00 00 00 00 86 4a 84 80 80 80 80 01 04 00 8d 18 .....J.......... +| 3232: 00 00 03 2b 02 30 30 01 02 06 01 02 06 01 02 06 ...+.00......... +| 3248: 1f 02 03 01 02 03 01 02 03 01 08 32 30 31 36 30 ...........20160 +| 3264: 36 30 39 01 02 07 01 02 07 01 02 07 01 01 34 01 609...........4. +| 3280: 02 05 01 02 05 01 02 05 01 01 35 01 02 04 01 02 ..........5..... +| 3296: 04 01 02 04 02 07 30 30 30 30 30 30 30 1c 02 04 ......0000000... +| 3312: 01 02 04 01 02 04 01 06 62 69 6e 61 72 79 03 06 ........binary.. +| 3328: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3344: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3360: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3376: 03 06 01 02 02 03 06 01 02 02 01 08 63 6f 6d 70 ............comp +| 3392: 69 6c 65 72 01 02 02 01 02 02 01 02 02 01 06 64 iler...........d +| 3408: 62 73 74 61 74 07 02 03 01 02 03 01 02 03 02 04 bstat........... +| 3424: 65 62 75 67 04 02 02 01 02 02 01 02 02 01 06 65 ebug...........e +| 3440: 6e 61 62 6c 65 07 02 02 01 02 02 01 02 02 01 02 nable........... +| 3456: 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 ................ +| 3472: 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 ................ +| 3488: 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 ................ +| 3504: 02 01 02 02 02 08 78 74 65 6e 73 69 6f 6e 1f 02 ......xtension.. +| 3520: 04 01 02 04 01 02 04 01 04 66 74 73 34 0a 02 03 .........fts4... +| 3536: 01 02 03 01 02 03 04 01 35 0d 02 03 01 02 03 01 ........5....... +| 3552: 02 03 01 03 67 63 63 01 02 03 01 02 03 01 02 03 ....gcc......... +| 3568: 02 06 65 6f 70 6f 6c 79 10 02 03 01 02 03 01 02 ..eopoly........ +| 3584: 03 01 05 6a 73 6f 6e 31 13 02 03 01 02 03 01 02 ...json1........ +| 3600: 03 01 04 6c 6f 61 64 1f 02 03 01 02 03 01 02 03 ...load......... +| 3616: 01 03 6d 61 78 1c 02 02 01 02 02 01 02 02 02 05 ..max........... +| 3632: 65 6d 6f 72 79 1c 02 03 01 02 03 01 02 03 04 04 emory........... +| 3648: 73 79 73 35 16 02 03 01 02 03 01 02 03 01 06 6e sys5...........n +| 3664: 6f 63 61 73 65 02 06 01 02 02 03 06 01 02 02 03 ocase........... +| 3680: 06 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 ................ +| 3696: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3712: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3728: 02 01 04 6f 6d 69 74 1f 02 02 01 02 02 01 02 02 ...omit......... +| 3744: 01 05 72 74 72 65 65 19 02 03 01 02 03 01 02 03 ..rtree......... +| 3760: 04 02 69 6d 01 06 01 02 02 03 06 01 02 02 03 06 ..im............ +| 3776: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ +| 3792: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ +| 3808: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ +| 3824: 01 0a 74 68 72 65 61 64 73 61 66 65 03 57 34 56 ..threadsafe.W4V +| 3840: 94 64 91 46 85 84 04 76 74 61 62 07 02 04 01 02 .d.F...vtab..... +| 3856: 04 01 02 04 01 01 78 01 06 01 01 02 01 06 01 01 ......x......... +| 3872: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 10 02 ................ +| 3888: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3904: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 3920: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 3936: 01 02 01 06 01 01 10 01 06 01 01 02 01 06 01 01 ................ +| 3952: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 ................ +| 3968: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ +| 3984: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ +| 4000: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ +| 4016: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ +| 4032: 02 01 06 01 01 02 01 06 01 01 02 04 15 13 0c 0c ................ +| 4048: 12 44 13 11 0f 47 13 0f 0c 0e 11 10 0f 0e 10 0f .D...G.......... +| 4064: 44 0f 10 40 15 0f 07 01 03 00 14 24 5a 24 24 0f D..@.......$Z$$. +| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ +| page 4 offset 12288 +| 0: 0a 00 00 00 01 0f fa 00 00 00 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 5 offset 16384 +| 0: 0d 00 00 00 24 0c 0a 00 0f d8 0f af 0f 86 0f 74 ....$..........t +| 16: 0f 61 0f 4e 0f 2f 0f 0f 0e ef 0e d7 0e be 0e a5 .a.N./.......... +| 32: 0e 8d 0e 74 0e 5b 0e 40 0e 24 0e 08 0d ef 0d d5 ...t.[.@.$...... +| 48: 0d bb 0d a0 0d 84 0d 68 0d 4f 0d 35 0d 1b 0c fb .......h.O.5.... +| 64: 0c da 0c b9 0c 99 0c 78 0c 57 0c 3e 0c 24 0c 0a .......x.W.>.$.. +| 3072: 00 00 00 00 00 00 00 00 00 00 18 24 05 00 25 0f ...........$..%. +| 3088: 19 54 48 52 45 41 44 53 41 46 45 3d 30 58 42 49 .THREADSAFE=0XBI +| 3104: 4e 41 52 59 18 23 05 00 25 0f 19 54 48 52 45 41 NARY.#..%..THREA +| 3120: 44 53 41 46 45 3d 30 58 4e 4f 43 41 53 45 17 22 DSAFE=0XNOCASE.. +| 3136: 05 00 25 0f 17 54 48 52 45 41 44 53 31 46 45 3d ..%..THREADS1FE= +| 3152: 30 58 52 64 52 49 4d 1f 21 05 00 33 0f 19 4f 4d 0XRdRIM.!..3..OM +| 3168: 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 4f IT LOAD EXTENSIO +| 3184: 4e 58 42 49 4e 41 52 59 1f 20 05 00 33 0f 19 4f NXBINARY. ..3..O +| 3200: 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 MIT LOAD EXTENSI +| 3216: 4f 4e 58 4e 4f 43 41 53 45 1e 1f 05 00 33 0f 17 ONXNOCASE....3.. +| 3232: 4f 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 OMIT LOAD EXTENS +| 3248: 49 4f 4e 58 52 54 52 49 4d 1f 1e 05 00 33 0f 19 IONXRTRIM....3.. +| 3264: 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 30 MAX MEMORY=50000 +| 3280: 30 30 30 58 42 49 4e 41 52 59 1f 1d 05 00 33 0f 000XBINARY....3. +| 3296: 19 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 .MAX MEMORY=5000 +| 3312: 30 30 30 30 58 4e 4f 43 41 53 45 1e 1c 05 00 33 0000XNOCASE....3 +| 3328: 0f 17 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 ..MAX MEMORY=500 +| 3344: 30 30 30 30 30 58 52 54 52 49 4d 18 1b 05 00 25 00000XRTRIM....% +| 3360: 0f 19 45 4e 41 42 4c 45 20 52 54 52 45 45 58 42 ..ENABLE RTREEXB +| 3376: 49 4e 41 52 59 18 1a 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3392: 4c 45 20 52 54 52 45 45 58 4e 4f 43 41 53 45 17 LE RTREEXNOCASE. +| 3408: 19 05 00 25 0f 17 45 4e 41 42 4c 45 20 52 54 52 ...%..ENABLE RTR +| 3424: 45 45 58 52 54 52 49 4d 1a 18 05 00 29 0f 19 45 EEXRTRIM....)..E +| 3440: 4e 41 42 4b 45 20 4d 45 4d 53 59 53 35 58 42 49 NABKE MEMSYS5XBI +| 3456: 4e 41 52 59 1a 17 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL +| 3472: 42 60 2d 45 4d 53 59 53 35 58 4e 4f 43 41 53 45 B`-EMSYS5XNOCASE +| 3488: 19 16 05 00 29 0f 17 45 4e 41 42 4c 45 20 4d 45 ....)..ENABLE ME +| 3504: 4d 53 59 53 35 58 52 54 52 49 4d 18 15 05 00 25 MSYS5XRTRIM....% +| 3520: 0f 19 45 4e 41 42 4c 45 20 4a 53 4f 4e 31 58 42 ..ENABLE JSON1XB +| 3536: 49 4e 41 52 59 18 14 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB +| 3552: 4c 45 20 4a 53 4f 4e 31 58 4e 4f 43 41 53 45 17 LE JSON1XNOCASE. +| 3568: 13 05 00 25 0f 17 45 4e 41 42 4c 45 20 4a 53 4f ...%..ENABLE JSO +| 3584: 4e 31 58 52 54 52 49 4d 1a 12 05 00 29 0f 19 45 N1XRTRIM....)..E +| 3600: 4e 41 42 4c 45 20 47 45 4f 50 4f 4c 59 58 42 49 NABLE GEOPOLYXBI +| 3616: 4e 41 52 59 1a 11 05 00 39 0f 19 45 4e 41 42 4c NARY....9..ENABL +| 3632: 45 20 47 45 4f 50 4f 4c 59 58 4e 4f 43 41 53 45 E GEOPOLYXNOCASE +| 3648: 19 10 05 00 29 0f 17 45 4e 41 42 4c 45 20 47 45 ....)..ENABLE GE +| 3664: 4f 50 4f 4c 59 58 52 54 52 49 4d 17 0f 05 00 23 OPOLYXRTRIM....# +| 3680: 0f 19 45 4e 41 42 4c 45 20 46 54 53 35 58 42 49 ..ENABLE FTS5XBI +| 3696: 4e 41 52 59 17 0e 05 00 23 0f 19 45 4e 41 42 4c NARY....#..ENABL +| 3712: 45 20 46 54 53 35 58 4e 4f 43 41 53 45 16 0d 05 E FTS5XNOCASE... +| 3728: 00 23 0f 17 45 4e 41 42 4c 45 20 46 54 53 35 58 .#..ENABLE FTS5X +| 3744: 52 54 52 49 4d 17 0c 05 00 23 0f 19 45 4e 41 42 RTRIM....#..ENAB +| 3760: 4c 45 20 46 54 53 34 58 42 49 4e 41 52 59 17 0b LE FTS4XBINARY.. +| 3776: 05 00 23 0f 19 45 4e 41 42 4c 45 20 46 54 53 34 ..#..ENABLE FTS4 +| 3792: 58 4e 4f 43 41 53 45 16 0a 05 00 23 0f 17 45 4e XNOCASE....#..EN +| 3808: 41 42 4c 45 20 46 54 53 34 58 52 54 52 49 4d 1e ABLE FTS4XRTRIM. +| 3824: 09 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3840: 54 41 54 20 56 54 41 42 58 42 49 4e 41 52 59 1e TAT VTABXBINARY. +| 3856: 08 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3872: 54 41 54 20 56 54 24 15 48 4e 4f 43 41 53 45 1d TAT VT$.HNOCASE. +| 3888: 07 05 00 31 0f 17 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS +| 3904: 54 41 54 20 56 54 41 42 58 52 54 52 49 4d 11 06 TAT VTABXRTRIM.. +| 3920: 05 00 17 0f 19 44 45 42 55 47 58 42 49 4e 41 52 .....DEBUGXBINAR +| 3936: 59 11 05 05 00 17 0f 19 44 45 42 55 47 58 4e 4f Y.......DEBUGXNO +| 3952: 43 41 53 45 10 04 05 00 17 0f 17 44 45 42 55 47 CASE.......DEBUG +| 3968: 58 52 54 52 49 4d 27 03 05 00 43 0f 19 43 4f 4d XRTRIM'...C..COM +| 3984: 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e 30 20 PILER=gcc-5.4.0 +| 4000: 32 30 31 36 30 36 30 39 58 42 49 4e 41 52 59 27 20160609XBINARY' +| 4016: 02 05 00 43 0f 19 43 4f 4d 50 49 4c 45 52 3c 67 ...C..COMPILER100; } {101} + +do_execsql_test 1.9 { + DELETE FROM ft; + INSERT INTO ft(ft) VALUES('optimize'); + SELECT count(*) FROM ft_data; +} {2} +do_execsql_test 1.10 { + BEGIN; + INSERT INTO ft VALUES('Hello'); + INSERT INTO ft VALUES('hello'); + INSERT INTO ft VALUES('HELLO'); + INSERT INTO ft VALUES('today'); + INSERT INTO ft VALUES('today'); + INSERT INTO ft VALUES('today'); + INSERT INTO ft VALUES('World'); + INSERT INTO ft VALUES('world'); + INSERT INTO ft VALUES('WORLD'); +} + +do_execsql_test 1.11 { SELECT rowid FROM ft('hello'); } {1 2 3} +do_execsql_test 1.12 { SELECT rowid FROM ft('today'); } {4 5 6} +do_execsql_test 1.13 { SELECT rowid FROM ft('world'); } {7 8 9} +do_execsql_test 1.14 { SELECT rowid FROM ft('hello') ORDER BY rank; } {1 2 3} + +#------------------------------------------------------------------------ +reset_db +sqlite3_fts5_register_origintext db +proc tokens {cmd} { + set ret [list] + for {set iTok 0} {$iTok < [$cmd xInstCount]} {incr iTok} { + set txt [$cmd xInstToken $iTok 0] + set txt [string map [list "\0" "."] $txt] + lappend ret $txt + } + set ret +} +sqlite3_fts5_create_function db tokens tokens + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE x1 USING fts5( + v, tokenize="origintext unicode61", tokendata=1, detail=none + ); + + INSERT INTO x1 VALUES('xxx Xxx XXX yyy YYY yyy'); + INSERT INTO x1 VALUES('xxx yyy xxx yyy yyy yyy'); +} + +do_execsql_test 2.1 { + SELECT tokens(x1) FROM x1('xxx'); +} { + {xxx xxx.Xxx xxx.XXX} {xxx xxx} +} + +do_execsql_test 2.2 { + UPDATE x1_content SET c0 = 'xxx xxX xxx yyy yyy yyy' WHERE id=1; +} + +do_execsql_test 2.3 { + SELECT tokens(x1) FROM x1('xxx'); +} { + {xxx {} xxx} {xxx xxx} +} + +finish_test + diff --git a/ext/fts5/test/fts5origintext3.test b/ext/fts5/test/fts5origintext3.test new file mode 100644 index 000000000..834844595 --- /dev/null +++ b/ext/fts5/test/fts5origintext3.test @@ -0,0 +1,101 @@ +# 2023 November 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. +# +#*********************************************************************** +# +# Tests focused on phrase queries. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5origintext3 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +foreach_detail_mode $testprefix { + reset_db + + sqlite3_fts5_register_origintext db + fts5_aux_test_functions db + proc insttoken {cmd iIdx iToken} { + set txt [$cmd xInstToken $iIdx $iToken] + string map [list "\0" "."] $txt + } + sqlite3_fts5_create_function db insttoken insttoken + + do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5( + x, tokenize="origintext unicode61", tokendata=1, detail=%DETAIL% + ); + } + + do_execsql_test 1.1 { + INSERT INTO ft VALUES('Hello world HELLO WORLD hello'); + } + + do_execsql_test 1.2 { + SELECT fts5_test_poslist(ft) FROM ft('hello'); + } {{0.0.0 0.0.2 0.0.4}} + + do_execsql_test 1.3 { + SELECT + insttoken(ft, 0, 0), + insttoken(ft, 1, 0), + insttoken(ft, 2, 0) + FROM ft('hello'); + } {hello.Hello hello.HELLO hello} + + do_execsql_test 1.4 { + SELECT + insttoken(ft, 0, 0), + insttoken(ft, 1, 0), + insttoken(ft, 2, 0) + FROM ft('hello') ORDER BY rank; + } {hello.Hello hello.HELLO hello} + + do_execsql_test 1.5 { + CREATE VIRTUAL TABLE ft2 USING fts5( + x, tokenize="origintext unicode61", tokendata=1, detail=%DETAIL% + ); + INSERT INTO ft2(rowid, x) VALUES(1, 'ONE one two three ONE'); + INSERT INTO ft2(rowid, x) VALUES(2, 'TWO one two three TWO'); + INSERT INTO ft2(rowid, x) VALUES(3, 'THREE one two three THREE'); + } + + do_execsql_test 1.6 { + SELECT insttoken(ft2, 0, 0), rowid FROM ft2('three') ORDER BY rank; + } {three.THREE 3 three 1 three 2} + + do_execsql_test 1.7 { + INSERT INTO ft2(rowid, x) VALUES(10, 'aaa bbb BBB'); + INSERT INTO ft2(rowid, x) VALUES(12, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(13, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(14, 'bbb BBB bbb'); + INSERT INTO ft2(rowid, x) VALUES(15, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(16, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(17, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(18, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(19, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(20, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(21, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(22, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(23, 'bbb bbb bbb'); + INSERT INTO ft2(rowid, x) VALUES(24, 'aaa bbb BBB'); + } + + do_execsql_test 1.8 { SELECT rowid FROM ft2('aaa AND bbb'); } {10 24} + do_execsql_test 1.9 { SELECT rowid FROM ft2('bbb AND aaa'); } {10 24} + +} + +finish_test + diff --git a/ext/fts5/test/fts5origintext4.test b/ext/fts5/test/fts5origintext4.test new file mode 100644 index 000000000..c4ae35011 --- /dev/null +++ b/ext/fts5/test/fts5origintext4.test @@ -0,0 +1,80 @@ +# 2023 November 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. +# +#*********************************************************************** +# +# Tests focused on phrase queries. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5origintext4 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +# The tests below verify that a doclist-index is used to limit the number +# of pages loaded into the cache. It does this by querying sqlite3_db_status() +# for the amount of memory used by the pager cache. +# +# memsubsys1 effectively limits the page-cache to 24 pages. Which masks +# the effect tested by the tests in this file. And "mmap" prevents the +# cache from being used, also preventing these tests from working. +# +if {[permutation]=="memsubsys1" || [permutation]=="mmap"} { + finish_test + return +} + +sqlite3_fts5_register_origintext db +do_execsql_test 1.0 { + PRAGMA page_size = 4096; + CREATE VIRTUAL TABLE ft USING fts5( + x, tokenize="origintext unicode61", tokendata=1 + ); +} + +do_execsql_test 1.1 { + BEGIN; + INSERT INTO ft SELECT 'the first thing'; + + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<90000 + ) + INSERT INTO ft SELECT 'The second thing' FROM s; + + INSERT INTO ft SELECT 'the first thing'; + COMMIT; + INSERT INTO ft(ft) VALUES('optimize'); +} + +foreach {tn sql expr} { + 1 { SELECT rowid FROM ft('the') } {$mem > 250000} + 2 { SELECT rowid FROM ft('first') } {$mem < 50000} + 3 { SELECT rowid FROM ft('the first') } {$mem < 50000} +} { + db close + sqlite3 db test.db + sqlite3_fts5_register_origintext db + + execsql $sql + do_test 1.2.$tn { + set mem [lindex [sqlite3_db_status db CACHE_USED 0] 1] + expr $expr + } 1 +} + +proc b {x} { string map [list "\0" "."] $x } +db func b b +# execsql_pp { SELECT segid, b(term), pgno from ft_idx } + +finish_test + diff --git a/ext/fts5/test/fts5origintext5.test b/ext/fts5/test/fts5origintext5.test new file mode 100644 index 000000000..03d5bee21 --- /dev/null +++ b/ext/fts5/test/fts5origintext5.test @@ -0,0 +1,273 @@ +# 2023 Dec 04 +# +# 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. +# +#*********************************************************************** +# +# Tests for tables that use both tokendata=1 and contentless_delete=1. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5origintext + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +# Return a random integer between 0 and n-1. +# +proc random {n} { expr {abs(int(rand()*$n))} } + +# Select an element of the list passed as the only argument at random and +# return it. +# +proc select_one {list} { + set n [llength $list] + lindex $list [random $n] +} + +# Given a term that consists entirely of alphabet characters, return all +# permutations of the term using upper and lower case characters. e.g. +# +# "abc" -> {CBA cBA CbA cbA CBa cBa Cba cba} +# +proc casify {term {lRet {{}}}} { + if {$term==""} { return $lRet } + set t [string range $term 1 end] + set f1 [string toupper [string range $term 0 0]] + set f2 [string tolower [string range $term 0 0]] + set ret [list] + foreach x $lRet { + lappend ret "$x$f1" + lappend ret "$x$f2" + } + return [casify $t $ret] +} + +proc vocab {} { + list abc def ghi jkl mno pqr stu vwx yza +} + +# Return a random 3 letter term. +# +proc term {} { + if {[info exists ::expanded_vocab]==0} { + foreach v [vocab] { lappend ::expanded_vocab {*}[casify $v] } + } + + select_one $::expanded_vocab +} + +# Return a document - between 3 and 10 terms. +# +proc document {} { + set nTerm [expr [random 3] + 7] + set doc "" + for {set ii 0} {$ii < $nTerm} {incr ii} { + lappend doc [term] + } + set doc +} +db func document document + +#------------------------------------------------------------------------- + +expr srand(6) + +set NDOC 200 +set NLOOP 50 + +sqlite3_fts5_register_origintext db + +proc tokens {cmd} { + set ret [list] + for {set iTok 0} {$iTok < [$cmd xInstCount]} {incr iTok} { + set txt [$cmd xInstToken $iTok 0] + set txt [string map [list "\0" "."] $txt] + lappend ret $txt + } + set ret +} +sqlite3_fts5_create_function db tokens tokens + +proc rankfunc {cmd} { + $cmd xRowid +} +sqlite3_fts5_create_function db rankfunc rankfunc + +proc ctrl_tokens {term args} { + set ret [list] + set term [string tolower $term] + foreach doc $args { + foreach a $doc { + if {[string tolower $a]==$term} { + if {$a==$term} { + lappend ret $a + } else { + lappend ret [string tolower $a].$a + } + } + } + } + set ret +} +db func ctrl_tokens ctrl_tokens + +proc do_all_vocab_test {tn} { + foreach ::v [concat [vocab] nnn] { + set answer [execsql { + SELECT id, ctrl_tokens($::v, x) FROM ctrl WHERE x LIKE '%' || $::v || '%' + }] + do_execsql_test $tn.$::v.1 { + SELECT rowid, tokens(ft) FROM ft($::v) + } $answer + do_execsql_test $tn.$::v.2 { + SELECT rowid, tokens(ft) FROM ft($::v) ORDER BY rank + } $answer + } +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5( + x, tokenize="origintext unicode61", content=, contentless_delete=1, + tokendata=1 + ); + + CREATE TABLE ctrl(id INTEGER PRIMARY KEY, x TEXT); + INSERT INTO ft(ft, rank) VALUES('pgsz', 64); + INSERT INTO ft(ft, rank) VALUES('rank', 'rankfunc()'); +} +do_test 1.1 { + for {set ii 0} {$ii < $NDOC} {incr ii} { + set doc [document] + execsql { + INSERT INTO ft(rowid, x) VALUES($ii, $doc); + INSERT INTO ctrl(id, x) VALUES($ii, $doc); + } + } +} {} + +#execsql_pp { SELECT * FROM ctrl } +#execsql_pp { SELECT * FROM ft } +#fts5_aux_test_functions db +#execsql_pp { SELECT rowid, tokens(ft), fts5_test_poslist(ft) FROM ft('ghi'); } + +do_all_vocab_test 1.2 + +for {set ii 0} {$ii < $NLOOP} {incr ii} { + set lRowid [execsql { SELECT id FROM ctrl WHERE random() % 2 }] + foreach r $lRowid { + execsql { DELETE FROM ft WHERE rowid = $r } + execsql { DELETE FROM ctrl WHERE rowid = $r } + + set doc [document] + execsql { INSERT INTO ft(rowid, x) VALUES($r, $doc) } + execsql { INSERT INTO ctrl(id, x) VALUES($r, $doc) } + } + do_all_vocab_test 1.3.$ii +} + +#------------------------------------------------------------------------- + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft2 USING fts5( + x, y, tokenize="origintext unicode61", content=, contentless_delete=1, + tokendata=1 + ); + + CREATE TABLE ctrl2(id INTEGER PRIMARY KEY, x TEXT, y TEXT); + INSERT INTO ft2(ft2, rank) VALUES('pgsz', 64); + INSERT INTO ft2(ft2, rank) VALUES('rank', 'rankfunc()'); +} +do_test 2.1 { + for {set ii 0} {$ii < $NDOC} {incr ii} { + set doc1 [document] + set doc2 [document] + execsql { + INSERT INTO ft2(rowid, x, y) VALUES($ii, $doc, $doc2); + INSERT INTO ctrl2(id, x, y) VALUES($ii, $doc, $doc2); + } + } +} {} + +proc do_all_vocab_test2 {tn} { + foreach ::v [vocab] { + set answer [execsql { + SELECT id, ctrl_tokens($::v, x, y) FROM ctrl2 + WHERE x LIKE '%' || $::v || '%' OR y LIKE '%' || $::v || '%'; + }] + do_execsql_test $tn.$::v.1 { + SELECT rowid, tokens(ft2) FROM ft2($::v) + } $answer + do_execsql_test $tn.$::v.2 { + SELECT rowid, tokens(ft2) FROM ft2($::v) ORDER BY rank + } $answer + } +} + +do_all_vocab_test2 2.2 + +for {set ii 0} {$ii < $NLOOP} {incr ii} { + set lRowid [execsql { SELECT id FROM ctrl2 WHERE random() % 2 }] + foreach r $lRowid { + execsql { DELETE FROM ft2 WHERE rowid = $r } + execsql { DELETE FROM ctrl2 WHERE rowid = $r } + + set doc1 [document] + set doc2 [document] + execsql { INSERT INTO ft2(rowid, x, y) VALUES($r, $doc, $doc1) } + execsql { INSERT INTO ctrl2(id, x, y) VALUES($r, $doc, $doc2) } + } + do_all_vocab_test 2.3.$ii +} + +#------------------------------------------------------------------------- + +unset -nocomplain ::expanded_vocab +proc vocab {} { + list abcde fghij klmno +} + +proc do_all_vocab_test3 {tn} { + foreach ::v [concat [vocab] nnn] { + set answer [execsql { + SELECT rowid, ctrl_tokens($::v, w) FROM ctrl3 WHERE w LIKE '%' || $::v || '%' + }] + do_execsql_test $tn.$::v.1 { + SELECT rowid, tokens(ft3) FROM ft3($::v) + } $answer + do_execsql_test $tn.$::v.2 { + SELECT rowid, tokens(ft3) FROM ft3($::v) ORDER BY rank + } $answer + } +} + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE ft3 USING fts5( + w, tokenize="origintext unicode61", content=, contentless_delete=1, + tokendata=1 + ); + INSERT INTO ft3(ft3, rank) VALUES('rank', 'rankfunc()'); + CREATE TABLE ctrl3(w); +} + +do_execsql_test 3.1 { + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<2 + ) + INSERT INTO ctrl3 SELECT document() FROM s; + INSERT INTO ft3(rowid, w) SELECT rowid, w FROM ctrl3; +} + +do_all_vocab_test3 3.2 + + +finish_test + diff --git a/ext/fts5/test/fts5secure3.test b/ext/fts5/test/fts5secure3.test index bc56e0820..49347144f 100644 --- a/ext/fts5/test/fts5secure3.test +++ b/ext/fts5/test/fts5secure3.test @@ -86,71 +86,76 @@ do_execsql_test 2.8 { # Tests with large/small rowid values. # -reset_db - -expr srand(0) - -set vocab { - Popper Poppins Popsicle Porfirio Porrima Porsche - Porter Portia Portland Portsmouth Portugal Portuguese - Poseidon Post PostgreSQL Potemkin Potomac Potsdam - Pottawatomie Potter Potts Pound Poussin Powell - PowerPC PowerPoint Powers Powhatan Poznan Prada - Prado Praetorian Prague Praia Prakrit Pratchett - Pratt Pravda Praxiteles Preakness Precambrian Preminger - Premyslid Prensa Prentice Pres Presbyterian Presbyterianism -} -proc newdoc {} { - for {set i 0} {$i<8} {incr i} { - lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]] +foreach {tn cfg} { + 1 "" + 2 "INSERT INTO fff(fff, rank) VALUES('secure-delete', 1)" +} { + reset_db + + expr srand(0) + + set vocab { + Popper Poppins Popsicle Porfirio Porrima Porsche + Porter Portia Portland Portsmouth Portugal Portuguese + Poseidon Post PostgreSQL Potemkin Potomac Potsdam + Pottawatomie Potter Potts Pound Poussin Powell + PowerPC PowerPoint Powers Powhatan Poznan Prada + Prado Praetorian Prague Praia Prakrit Pratchett + Pratt Pravda Praxiteles Preakness Precambrian Preminger + Premyslid Prensa Prentice Pres Presbyterian Presbyterianism } - set ret -} -db func newdoc newdoc - -do_execsql_test 3.0 { - CREATE VIRTUAL TABLE fff USING fts5(y); - INSERT INTO fff(fff, rank) VALUES('pgsz', 64); - - WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) - INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; - - WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) - INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; - - WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) - INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; - - INSERT INTO fff(fff, rank) VALUES('secure-delete', 1); -} - -proc lshuffle {in} { - set out [list] - while {[llength $in]>0} { - set idx [expr int(abs(rand()) * [llength $in])] - lappend out [lindex $in $idx] - set in [lreplace $in $idx $idx] + proc newdoc {} { + for {set i 0} {$i<8} {incr i} { + lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]] + } + set ret + } + db func newdoc newdoc + + do_execsql_test 3.$tn.0 { + CREATE VIRTUAL TABLE fff USING fts5(y); + INSERT INTO fff(fff, rank) VALUES('pgsz', 64); + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; } - set out -} - -#dump fff -set iTest 1 -foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] { - #if {$iTest==1} { dump fff } - #if {$iTest==1} { breakpoint } - do_execsql_test 3.1.$iTest.$ii { - DELETE FROM fff WHERE rowid=$ii; + execsql $cfg + + proc lshuffle {in} { + set out [list] + while {[llength $in]>0} { + set idx [expr int(abs(rand()) * [llength $in])] + lappend out [lindex $in $idx] + set in [lreplace $in $idx $idx] + } + set out } - #if {$iTest==1} { dump fff } - if {($iTest % 20)==0} { - do_execsql_test 3.1.$iTest.$ii.ic { - INSERT INTO fff(fff) VALUES('integrity-check'); + + #dump fff + + set iTest 1 + foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] { + #if {$iTest==1} { dump fff } + #if {$iTest==1} { breakpoint } + do_execsql_test 3.$tn.1.$iTest.$ii { + DELETE FROM fff WHERE rowid=$ii; + } + #if {$iTest==1} { dump fff } + if {($iTest % 20)==0} { + do_execsql_test 3.$tn.1.$iTest.$ii.ic { + INSERT INTO fff(fff) VALUES('integrity-check'); + } } + #if {$iTest==1} { break } + incr iTest } - #if {$iTest==1} { break } - incr iTest } #execsql_pp { SELECT rowid FROM fff('post') ORDER BY rowid ASC } diff --git a/ext/fts5/test/fts5simple2.test b/ext/fts5/test/fts5simple2.test index e57cea70f..6c0e0e166 100644 --- a/ext/fts5/test/fts5simple2.test +++ b/ext/fts5/test/fts5simple2.test @@ -343,7 +343,9 @@ do_execsql_test 17.0 { INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); COMMIT; } -do_execsql_test 17.1 { SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 } +do_execsql_test 17.1 { + SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 +} do_execsql_test 17.2 { BEGIN; INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); diff --git a/ext/fts5/test/fts5synonym2.test b/ext/fts5/test/fts5synonym2.test index 2c2705e34..9fdf75769 100644 --- a/ext/fts5/test/fts5synonym2.test +++ b/ext/fts5/test/fts5synonym2.test @@ -42,7 +42,7 @@ proc fts5_test_bothlist {cmd} { } sqlite3_fts5_create_function db fts5_test_bothlist fts5_test_bothlist -proc fts5_rowid {cmd} { expr [$cmd xColumnText -1] } +proc fts5_rowid {cmd} { expr [$cmd xRowid] } sqlite3_fts5_create_function db fts5_rowid fts5_rowid do_execsql_test 1.$tok.0.1 " diff --git a/ext/fts5/test/fts5tokenizer2.test b/ext/fts5/test/fts5tokenizer2.test new file mode 100644 index 000000000..bdabd5312 --- /dev/null +++ b/ext/fts5/test/fts5tokenizer2.test @@ -0,0 +1,89 @@ +# 2023 Nov 03 +# +# 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. +# +#*********************************************************************** +# +# Tests focusing on the built-in fts5 tokenizers. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5tokenizer2 + +# If SQLITE_ENABLE_FTS5 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +sqlite3_fts5_create_tokenizer db tst get_tst_tokenizer +proc get_tst_tokenizer {args} { + return "tst_tokenizer" +} +proc tst_tokenizer {flags txt} { + set token "" + set lTok [list] + + foreach c [split $txt {}] { + if {$token==""} { + append token $c + } else { + set t1 [string is upper $token] + set t2 [string is upper $c] + + if {$t1!=$t2} { + lappend lTok $token + set token "" + } + append token $c + } + } + if {$token!=""} { lappend lTok $token } + + set iOff 0 + foreach t $lTok { + set n [string length $t] + sqlite3_fts5_token $t $iOff [expr $iOff+$n] + incr iOff $n + } +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(t, tokenize=tst); +} + +do_execsql_test 1.1 { + INSERT INTO t1 VALUES('AAdontBBmess'); +} + +do_execsql_test 1.2 { + SELECT snippet(t1, 0, '>', '<', '...', 4) FROM t1('BB'); +} {AAdont>BB', '<') FROM t1('BB'); +} {AAdont>BB', '<') FROM t1('AA'); +} {>AA', '<') FROM t1('dont'); +} {AA>dont', '<') FROM t1('mess'); +} {AAdontBB>mess<} + +do_execsql_test 1.7 { + SELECT highlight(t1, 0, '>', '<') FROM t1('BB mess'); +} {AAdont>BBmess<} + + +finish_test diff --git a/ext/fts5/test/fts5trigram2.test b/ext/fts5/test/fts5trigram2.test new file mode 100644 index 000000000..f5beae5b2 --- /dev/null +++ b/ext/fts5/test/fts5trigram2.test @@ -0,0 +1,109 @@ +# 2023 October 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. +# +#************************************************************************* +# +# Tests for the fts5 "trigram" tokenizer. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +ifcapable !fts5 { finish_test ; return } +set ::testprefix fts5trigram2 + +do_execsql_test 1.0 " + CREATE VIRTUAL TABLE t1 USING fts5(y, tokenize='trigram remove_diacritics 1'); + INSERT INTO t1 VALUES('abc\u0303defghijklm'); + INSERT INTO t1 VALUES('a\u0303b\u0303c\u0303defghijklm'); +" + +do_execsql_test 1.1 { + SELECT highlight(t1, 0, '(', ')') FROM t1('abc'); +} [list \ + "(abc\u0303)defghijklm" \ + "(a\u0303b\u0303c\u0303)defghijklm" \ +] + +do_execsql_test 1.2 { + SELECT highlight(t1, 0, '(', ')') FROM t1('bcde'); +} [list \ + "a(bc\u0303de)fghijklm" \ + "a\u0303(b\u0303c\u0303de)fghijklm" \ +] + +do_execsql_test 1.3 { + SELECT highlight(t1, 0, '(', ')') FROM t1('cdef'); +} [list \ + "ab(c\u0303def)ghijklm" \ + "a\u0303b\u0303(c\u0303def)ghijklm" \ +] + +do_execsql_test 1.4 { + SELECT highlight(t1, 0, '(', ')') FROM t1('def'); +} [list \ + "abc\u0303(def)ghijklm" \ + "a\u0303b\u0303c\u0303(def)ghijklm" \ +] + + +#------------------------------------------------------------------------- +do_catchsql_test 2.0 { + CREATE VIRTUAL TABLE t2 USING fts5( + z, tokenize='trigram case_sensitive 1 remove_diacritics 1' + ); +} {1 {error in tokenizer constructor}} + +do_execsql_test 2.1 { + CREATE VIRTUAL TABLE t2 USING fts5( + z, tokenize='trigram case_sensitive 0 remove_diacritics 1' + ); +} +do_execsql_test 2.2 " + INSERT INTO t2 VALUES('\u00E3bcdef'); + INSERT INTO t2 VALUES('b\u00E3cdef'); + INSERT INTO t2 VALUES('bc\u00E3def'); + INSERT INTO t2 VALUES('bcd\u00E3ef'); +" + +do_execsql_test 2.3 { + SELECT highlight(t2, 0, '(', ')') FROM t2('abc'); +} "(\u00E3bc)def" +do_execsql_test 2.4 { + SELECT highlight(t2, 0, '(', ')') FROM t2('bac'); +} "(b\u00E3c)def" +do_execsql_test 2.5 { + SELECT highlight(t2, 0, '(', ')') FROM t2('bca'); +} "(bc\u00E3)def" +do_execsql_test 2.6 " + SELECT highlight(t2, 0, '(', ')') FROM t2('\u00E3bc'); +" "(\u00E3bc)def" + +#------------------------------------------------------------------------- +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE t3 USING fts5( + z, tokenize='trigram remove_diacritics 1' + ); +} {} +do_execsql_test 3.1 " + INSERT INTO t3 VALUES ('\u0303abc\u0303'); +" +do_execsql_test 3.2 { + SELECT highlight(t3, 0, '(', ')') FROM t3('abc'); +} "\u0303(abc\u0303)" + +#------------------------------------------------------------------------- +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t4 USING fts5(z, tokenize=trigram); +} {} + +breakpoint +do_execsql_test 4.1 { + INSERT INTO t4 VALUES('ABCD'); +} {} + +finish_test diff --git a/ext/fts5/test/fts5vocab2.test b/ext/fts5/test/fts5vocab2.test index 6f7aad329..ecacc50da 100644 --- a/ext/fts5/test/fts5vocab2.test +++ b/ext/fts5/test/fts5vocab2.test @@ -280,6 +280,30 @@ do_catchsql_test 5.2 { INSERT INTO t1 SELECT randomblob(3000) FROM v1 } {1 {query aborted}} +#------------------------------------------------------------------------- +reset_db +sqlite3_fts5_may_be_corrupt 1 + +do_execsql_test 6.0 { + BEGIN TRANSACTION; + CREATE VIRTUAL TABLE t1 USING fts5(a,b unindexed,c,tokenize="porter ascii",tokendata=1); + REPLACE INTO t1_data VALUES(1,X'03090009'); + REPLACE INTO t1_data VALUES(10,X'000000000103030003010101020101030101'); + REPLACE INTO t1_data VALUES(137438953473,X'0000002e023061010202010162010203010163010204010167010601020201016801060102030101690106010204040606060808'); + REPLACE INTO t1_data VALUES(274877906945,X'0000001f013067020802010202010168020803010203010169020804010204040909'); + REPLACE INTO t1_data VALUES(412316860417,X'0000002e023061030202010162030203010163030204010167030601020201016803060102030101690306010204040606060808'); + COMMIT; +} + +do_execsql_test 6.1 { + CREATE VIRTUAL TABLE t3 USING fts5vocab('t1', 'row'); +} + +do_catchsql_test 6.2 { + SELECT * FROM t3; +} {1 {database disk image is malformed}} + +sqlite3_fts5_may_be_corrupt 0 finish_test diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 19a508047..26a38dad9 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -35,6 +35,8 @@ $(dir.bld.c): javac.flags ?= -Xlint:unchecked -Xlint:deprecation java.flags ?= +javac.flags += -encoding utf8 +# -------------^^^^^^^^^^^^^^ required for Windows builds jnicheck ?= 1 ifeq (1,$(jnicheck)) java.flags += -Xcheck:jni @@ -79,6 +81,7 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile # Be explicit about which Java files to compile so that we can work on # in-progress files without requiring them to be in a compilable statae. JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\ + Experimental.java \ NotNull.java \ Nullable.java \ ) $(patsubst %,$(dir.src.capi)/%,\ @@ -91,7 +94,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\ CollationNeededCallback.java \ CommitHookCallback.java \ ConfigLogCallback.java \ - ConfigSqllogCallback.java \ + ConfigSqlLogCallback.java \ NativePointerHolder.java \ OutputPointer.java \ PrepareMultiCallback.java \ @@ -110,6 +113,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\ WindowFunction.java \ XDestroyCallback.java \ sqlite3.java \ + sqlite3_blob.java \ sqlite3_context.java \ sqlite3_stmt.java \ sqlite3_value.java \ @@ -120,6 +124,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\ Sqlite.java \ SqliteException.java \ ValueHolder.java \ + WindowFunction.java \ ) JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\ @@ -159,12 +164,13 @@ endif CLASS_FILES := define CLASSFILE_DEPS all: $(1).class +$(1).class: $(1).java CLASS_FILES += $(1).class endef $(foreach B,$(basename \ $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\ $(eval $(call CLASSFILE_DEPS,$(B)))) -$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE) +$(CLASS_FILES): $(MAKEFILE) $(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES) #.PHONY: classfiles @@ -226,7 +232,8 @@ SQLITE_OPT += -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ -DSQLITE_ENABLE_PREUPDATE_HOOK \ -DSQLITE_ENABLE_NORMALIZE \ - -DSQLITE_ENABLE_SQLLOG + -DSQLITE_ENABLE_SQLLOG \ + -DSQLITE_ENABLE_COLUMN_METADATA endif ifeq (1,$(opt.debug)) @@ -316,7 +323,7 @@ test-one: $(test.deps) $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags) test-sqllog: $(test.deps) @echo "Testing with -sqllog..." - $(bin.java) $(test.flags.jvm) -sqllog + $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags) -sqllog test-mt: $(test.deps) @echo "Testing in multi-threaded mode:"; $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \ diff --git a/ext/jni/README.md b/ext/jni/README.md index f2811fddb..fc7b5f761 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -16,9 +16,8 @@ Technical support is available in the forum: > **FOREWARNING:** this subproject is very much in development and subject to any number of changes. Please do not rely on any information about its API until this disclaimer is removed. The JNI - bindings released with version 3.43 are a "tech preview" and 3.44 - will be "final," at which point strong backward compatibility - guarantees will apply. + bindings released with version 3.43 are a "tech preview." Once + finalized, strong backward compatibility guarantees will apply. Project goals/requirements: @@ -43,11 +42,13 @@ Non-goals: - Creation of high-level OO wrapper APIs. Clients are free to create them off of the C-style API. +- Virtual tables are unlikely to be supported due to the amount of + glue code needed to fit them into Java. + - Support for mixed-mode operation, where client code accesses SQLite both via the Java-side API and the C API via their own native - code. In such cases, proxy functionalities (primarily callback - handler wrappers of all sorts) may fail because the C-side use of - the SQLite APIs will bypass those proxies. + code. Such cases would be a minefield of potential mis-interactions + between this project's JNI bindings and mixed-mode client code. Hello World @@ -123,15 +124,13 @@ sensible default argument values. In all such cases they are thin proxies around the corresponding C APIs and do not introduce new semantics. -In some very few cases, Java-specific capabilities have been added in +In a few cases, Java-specific capabilities have been added in new APIs, all of which have "_java" somewhere in their names. Examples include: - `sqlite3_result_java_object()` - `sqlite3_column_java_object()` -- `sqlite3_column_java_casted()` - `sqlite3_value_java_object()` -- `sqlite3_value_java_casted()` which, as one might surmise, collectively enable the passing of arbitrary Java objects from user-defined SQL functions through to the @@ -150,6 +149,9 @@ pending statements have been closed. Be aware that Java garbage collection _cannot_ close a database or finalize a prepared statement. Those things require explicit API calls. +Classes for which it is sensible support Java's `AutoCloseable` +interface so can be used with try-with-resources constructs. + Golden Rule #2: _Never_ Throw from Callbacks (Unless...) ------------------------------------------------------------------------ @@ -159,14 +161,15 @@ retain C-like semantics. For example, they are not permitted to throw or propagate exceptions and must return error information (if any) via result codes or `null`. The only cases where the C-style APIs may throw is through client-side misuse, e.g. passing in a null where it -shouldn't be used. The APIs clearly mark function parameters which -should not be null, but does not actively defend itself against such -misuse. Some C-style APIs explicitly accept `null` as a no-op for -usability's sake, and some of the JNI APIs deliberately return an -error code, instead of segfaulting, when passed a `null`. +may cause a `NullPointerException`. The APIs clearly mark function +parameters which should not be null, but does not generally actively +defend itself against such misuse. Some C-style APIs explicitly accept +`null` as a no-op for usability's sake, and some of the JNI APIs +deliberately return an error code, instead of segfaulting, when passed +a `null`. Client-defined callbacks _must never throw exceptions_ unless _very -explicitly documented_ as being throw-safe. Exceptions are generally +explitly documented_ as being throw-safe. Exceptions are generally reserved for higher-level bindings which are constructed to specifically deal with them and ensure that they do not leak C-level resources. In some cases, callback handlers are permitted to throw, in @@ -292,14 +295,14 @@ int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, `SQLFunction` is not used directly, but is instead instantiated via one of its three subclasses: -- `SQLFunction.Scalar` implements simple scalar functions using but a +- `ScalarFunction` implements simple scalar functions using but a single callback. -- `SQLFunction.Aggregate` implements aggregate functions using two +- `AggregateFunction` implements aggregate functions using two callbacks. -- `SQLFunction.Window` implements window functions using four +- `WindowFunction` implements window functions using four callbacks. -Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for +Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/capi/Tester1.java) for `SQLFunction` for how it's used. Reminder: see the disclaimer at the top of this document regarding the diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 245ce4f9e..5deff19ef 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -15,13 +15,14 @@ /* ** If you found this comment by searching the code for -** CallStaticObjectMethod then you're the victim of an OpenJDK bug: +** CallStaticObjectMethod because it appears in console output then +** you're probably the victim of an OpenJDK bug: ** ** https://bugs.openjdk.org/browse/JDK-8130659 ** -** It's known to happen with OpenJDK v8 but not with v19. -** -** This code does not use JNI's CallStaticObjectMethod(). +** It's known to happen with OpenJDK v8 but not with v19. It was +** triggered by this code long before it made any use of +** CallStaticObjectMethod(). */ /* @@ -90,12 +91,6 @@ #endif #endif -/**********************************************************************/ -/* SQLITE_M... */ -#ifndef SQLITE_MAX_ALLOCATION_SIZE -# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff -#endif - /**********************************************************************/ /* SQLITE_O... */ #ifndef SQLITE_OMIT_DEPRECATED @@ -112,6 +107,12 @@ # undef SQLITE_OMIT_UTF16 1 #endif +/**********************************************************************/ +/* SQLITE_S... */ +#ifndef SQLITE_STRICT_SUBTYPE +# define SQLITE_STRICT_SUBTYPE 1 +#endif + /**********************************************************************/ /* SQLITE_T... */ #ifndef SQLITE_TEMP_STORE @@ -191,6 +192,8 @@ ** ** This use of intptr_t is the _only_ reason we require ** which, in turn, requires building with -std=c99 (or later). +** +** See also: the notes for LongPtrGet_T. */ #define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr)) #define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR)) @@ -207,8 +210,8 @@ ** ** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers */ -#define JniArgsEnvObj JNIEnv * const env, jobject jSelf -#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz +#define JniArgsEnvObj JNIEnv * env, jobject jSelf +#define JniArgsEnvClass JNIEnv * env, jclass jKlazz /* ** Helpers to account for -Xcheck:jni warnings about not having ** checked for exceptions. @@ -657,6 +660,21 @@ struct S3JniGlobalType { jmethodID ctorLong1 /* the Long(long) constructor */; jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; jmethodID stringGetBytes /* the String.getBytes(Charset) method */; + + /* + ByteBuffer may or may not be supported via JNI on any given + platform: + + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support + + We only store a ref to byteBuffer.klazz if JNI support for + ByteBuffer is available (which we determine during static init). + */ + struct { + jclass klazz /* global ref to java.nio.ByteBuffer */; + jmethodID midAlloc /* ByteBuffer.allocateDirect() */; + jmethodID midLimit /* ByteBuffer.limit() */; + } byteBuffer; } g; /* ** The list of Java-side auto-extensions @@ -863,6 +881,58 @@ static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsiz #define s3jni_jbyteArray_commit(jByteArray,jBytes) \ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT) +/* +** If jbb is-a java.nio.Buffer object and the JNI environment supports +** it, *pBuf is set to the buffer's memory and *pN is set to its +** limit() (as opposed to its capacity()). If jbb is NULL, not a +** Buffer, or the JNI environment does not support that operation, +** *pBuf is set to 0 and *pN is set to 0. +** +** Note that the length of the buffer can be larger than SQLITE_LIMIT +** but this function does not know what byte range of the buffer is +** required so cannot check for that violation. The caller is required +** to ensure that any to-be-bind()ed range fits within SQLITE_LIMIT. +** +** Sidebar: it is unfortunate that we cannot get ByteBuffer.limit() +** via a JNI method like we can for ByteBuffer.capacity(). We instead +** have to call back into Java to get the limit(). Depending on how +** the ByteBuffer is used, the limit and capacity might be the same, +** but when reusing a buffer, the limit may well change whereas the +** capacity is fixed. The problem with, e.g., read()ing blob data to a +** ByteBuffer's memory based on its capacity is that Java-level code +** is restricted to accessing the range specified in +** ByteBuffer.limit(). If we were to honor only the capacity, we +** could end up writing to, or reading from, parts of a ByteBuffer +** which client code itself cannot access without explicitly modifying +** the limit. The penalty we pay for this correctness is that we must +** call into Java to get the limit() of every ByteBuffer we work with. +** +** An alternative to having to call into ByteBuffer.limit() from here +** would be to add private native impls of all ByteBuffer-using +** methods, each of which adds a jint parameter which _must_ be set to +** theBuffer.limit() by public Java APIs which use those private impls +** to do the real work. +*/ +static void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){ + *pBuf = 0; + *pN = 0; + if( jbb ){ + *pBuf = (*env)->GetDirectBufferAddress(env, jbb); + if( *pBuf ){ + /* + ** Maintenance reminder: do not use + ** (*env)->GetDirectBufferCapacity(env,jbb), even though it + ** would be much faster, for reasons explained in this + ** function's comments. + */ + *pN = (*env)->CallIntMethod(env, jbb, SJG.g.byteBuffer.midLimit); + S3JniExceptionIsFatal("Error calling ByteBuffer.limit() method."); + } + } +} +#define s3jni_get_nio_buffer(JOBJ,vpOut,jpOut) \ + s3jni__get_nio_buffer(env,(JOBJ),(vpOut),(jpOut)) + /* ** Returns the current JNIEnv object. Fails fatally if it cannot find ** the object. @@ -1061,6 +1131,47 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, return rv; } +/* +** Creates a new ByteBuffer instance with a capacity of n. assert()s +** that SJG.g.byteBuffer.klazz is not 0 and n>0. +*/ +static jobject s3jni__new_ByteBuffer(JNIEnv * const env, int n){ + jobject rv = 0; + assert( SJG.g.byteBuffer.klazz ); + assert( SJG.g.byteBuffer.midAlloc ); + assert( n > 0 ); + rv = (*env)->CallStaticObjectMethod(env, SJG.g.byteBuffer.klazz, + SJG.g.byteBuffer.midAlloc, (jint)n); + S3JniIfThrew { + S3JniExceptionReport; + S3JniExceptionClear; + } + s3jni_oom_check( rv ); + return rv; +} + +/* +** If n>0 and sqlite3_jni_supports_nio() is true then this creates a +** new ByteBuffer object and copies n bytes from p to it. Returns NULL +** if n is 0, sqlite3_jni_supports_nio() is false, or on allocation +** error (unless fatal alloc failures are enabled). +*/ +static jobject s3jni__blob_to_ByteBuffer(JNIEnv * const env, + const void * p, int n){ + jobject rv = NULL; + assert( n >= 0 ); + if( 0==n || !SJG.g.byteBuffer.klazz ){ + return NULL; + } + rv = s3jni__new_ByteBuffer(env, n); + if( rv ){ + void * tgt = (*env)->GetDirectBufferAddress(env, rv); + memcpy(tgt, p, (size_t)n); + } + return rv; +} + + /* ** Requires jx to be a Throwable. Calls its toString() method and ** returns its value converted to a UTF-8 string. The caller owns the @@ -1472,15 +1583,15 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph, ** argument is a Java sqlite3 object, as this operation only has void ** pointers to work with. */ -#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T)) -#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ) -#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ) -#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ) -#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ) -#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ) -#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ) +#define PtrGet_T(T,JOBJ) (T*)NativePointerHolder_get((JOBJ), S3JniNph(T)) +#define PtrGet_sqlite3(JOBJ) PtrGet_T(sqlite3, (JOBJ)) +#define PtrGet_sqlite3_backup(JOBJ) PtrGet_T(sqlite3_backup, (JOBJ)) +#define PtrGet_sqlite3_blob(JOBJ) PtrGet_T(sqlite3_blob, (JOBJ)) +#define PtrGet_sqlite3_context(JOBJ) PtrGet_T(sqlite3_context, (JOBJ)) +#define PtrGet_sqlite3_stmt(JOBJ) PtrGet_T(sqlite3_stmt, (JOBJ)) +#define PtrGet_sqlite3_value(JOBJ) PtrGet_T(sqlite3_value, (JOBJ)) /* -** S3JniLongPtr_T(X,Y) expects X to be an unqualified sqlite3 struct +** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct ** type name and Y to be a native pointer to such an object in the ** form of a jlong value. The jlong is simply cast to (X*). This ** approach is, as of 2023-09-27, supplanting the former approach. We @@ -1488,13 +1599,22 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph, ** the C side, because it's reportedly significantly faster. The ** intptr_t part here is necessary for compatibility with (at least) ** ARM32. +** +** 2023-11-09: testing has not revealed any measurable performance +** difference between the approach of passing type T to C compared to +** passing pointer-to-T to C, and adding support for the latter +** everywhere requires sigificantly more code. As of this writing, the +** older/simpler approach is being applied except for (A) where the +** newer approach has already been applied and (B) hot-spot APIs where +** a difference of microseconds (i.e. below our testing measurement +** threshold) might add up. */ -#define S3JniLongPtr_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr)) -#define S3JniLongPtr_sqlite3(JLongAsPtr) S3JniLongPtr_T(sqlite3,JLongAsPtr) -#define S3JniLongPtr_sqlite3_backup(JLongAsPtr) S3JniLongPtr_T(sqlite3_backup,JLongAsPtr) -#define S3JniLongPtr_sqlite3_blob(JLongAsPtr) S3JniLongPtr_T(sqlite3_blob,JLongAsPtr) -#define S3JniLongPtr_sqlite3_stmt(JLongAsPtr) S3JniLongPtr_T(sqlite3_stmt,JLongAsPtr) -#define S3JniLongPtr_sqlite3_value(JLongAsPtr) S3JniLongPtr_T(sqlite3_value,JLongAsPtr) +#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)((JLongAsPtr))) +#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,(JLongAsPtr)) +#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,(JLongAsPtr)) +#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,(JLongAsPtr)) +#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,(JLongAsPtr)) +#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,(JLongAsPtr)) /* ** Extracts the new S3JniDb instance from the free-list, or allocates ** one if needed, associates it with pDb, and returns. Returns NULL @@ -1553,7 +1673,7 @@ static void S3JniDb_xDestroy(void *p){ #define S3JniDb_from_c(sqlite3Ptr) \ ((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0) #define S3JniDb_from_jlong(sqlite3PtrAsLong) \ - S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong)) + S3JniDb_from_c(LongPtrGet_T(sqlite3,sqlite3PtrAsLong)) /* ** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out @@ -1670,8 +1790,9 @@ static int encodingTypeIsValid(int eTextRep){ } } -/* For use with sqlite3_result/value_pointer() */ -static const char * const ResultJavaValuePtrStr = "org.sqlite.jni.capi.ResultJavaVal"; +/* For use with sqlite3_result_pointer(), sqlite3_value_pointer(), + sqlite3_bind_java_object(), and sqlite3_column_java_object(). */ +static const char * const s3jni__value_jref_key = "org.sqlite.jni.capi.ResultJavaVal"; /* ** If v is not NULL, it must be a jobject global reference. Its @@ -1880,13 +2001,16 @@ static int udf_args(JNIEnv *env, /* ** Requires that jCx and jArgv are sqlite3_context -** resp. array-of-sqlite3_value values initialized by udf_args(). This +** resp. array-of-sqlite3_value values initialized by udf_args(). The +** latter will be 0-and-NULL for UDF types with no arguments. This ** function zeroes out the nativePointer member of jCx and each entry ** in jArgv. This is a safety-net precaution to avoid undefined -** behavior if a Java-side UDF holds a reference to one of its -** arguments. This MUST be called from any function which successfully -** calls udf_args(), after calling the corresponding UDF and checking -** its exception status. It MUST NOT be called in any other case. +** behavior if a Java-side UDF holds a reference to its context or one +** of its arguments. This MUST be called from any function which +** successfully calls udf_args(), after calling the corresponding UDF +** and checking its exception status, or which Java-wraps a +** sqlite3_context for use with a UDF(ish) call. It MUST NOT be called +** in any other case. */ static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){ int i = 0; @@ -1894,8 +2018,29 @@ static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){ NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0); for( ; i < argc; ++i ){ jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i); - assert(jsv); - NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0); + /* + ** There is a potential Java-triggerable case of Undefined + ** Behavior here, but it would require intentional misuse of the + ** API: + ** + ** If a Java UDF grabs an sqlite3_value from its argv and then + ** assigns that element to null, it becomes unreachable to us so + ** we cannot clear out its pointer. That Java-side object's + ** getNativePointer() will then refer to a stale value, so passing + ** it into (e.g.) sqlite3_value_SOMETHING() would invoke UB. + ** + ** High-level wrappers can avoid that possibility if they do not + ** expose sqlite3_value directly to clients (as is the case in + ** org.sqlite.jni.wrapper1.SqlFunction). + ** + ** One potential (but expensive) workaround for this would be to + ** privately store a duplicate argv array in each sqlite3_context + ** wrapper object, and clear the native pointers from that copy. + */ + assert(jsv && "Someone illegally modified a UDF argument array."); + if( jsv ){ + NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0); + } } } @@ -1984,6 +2129,7 @@ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, rc = udf_report_exception(env, isFinal, cx, s->zFuncName, zFuncType); } + udf_unargs(env, jcx, 0, 0); S3JniUnrefLocal(jcx); }else{ if( isFinal ) sqlite3_result_error_nomem(cx); @@ -2057,12 +2203,12 @@ static void udf_xInverse(sqlite3_context* cx, int argc, /** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ #define WRAP_INT_STMT(JniNameSuffix,CName) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \ - return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt)); \ + return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt)); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ #define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \ - return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)n); \ + return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)n); \ } /** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */ #define WRAP_BOOL_STMT(JniNameSuffix,CName) \ @@ -2073,41 +2219,41 @@ static void udf_xInverse(sqlite3_context* cx, int argc, #define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \ return s3jni_utf8_to_jstring( \ - CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx), \ + CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx), \ -1); \ } /** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */ #define WRAP_BOOL_DB(JniNameSuffix,CName) \ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ - return CName(S3JniLongPtr_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \ + return CName(LongPtrGet_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \ } /** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ #define WRAP_INT_DB(JniNameSuffix,CName) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ - return (jint)CName(S3JniLongPtr_sqlite3(jpDb)); \ + return (jint)CName(LongPtrGet_sqlite3(jpDb)); \ } /** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ #define WRAP_INT64_DB(JniNameSuffix,CName) \ JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ - return (jlong)CName(S3JniLongPtr_sqlite3(jpDb)); \ + return (jlong)CName(LongPtrGet_sqlite3(jpDb)); \ } /** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */ #define WRAP_STR_DB_INT(JniNameSuffix,CName) \ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \ return s3jni_utf8_to_jstring( \ - CName(S3JniLongPtr_sqlite3(jpDb), (int)ndx), \ + CName(LongPtrGet_sqlite3(jpDb), (int)ndx), \ -1); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ #define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \ + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \ return (jint)(sv ? CName(sv): DfltOnNull); \ } /** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */ #define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull) \ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \ + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \ return (jint)(sv ? CName(sv) : DfltOnNull) \ ? JNI_TRUE : JNI_FALSE; \ } @@ -2120,9 +2266,11 @@ WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16) WRAP_INT_STMT(1column_1count, sqlite3_column_count) WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype) WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name) +#ifdef SQLITE_ENABLE_COLUMN_METADATA WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name) WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name) WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name) +#endif WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) WRAP_INT_STMT(1data_1count, sqlite3_data_count) WRAP_STR_DB_INT(1db_1name, sqlite3_db_name) @@ -2181,7 +2329,9 @@ S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)( return S3JniCast_P2L(p); } -/* Central auto-extension handler. */ +/* +** Central auto-extension runner for auto-extensions created in Java. +*/ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ int rc = 0; @@ -2318,7 +2468,7 @@ S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)( ){ int rc = 0; if( jpBack!=0 ){ - rc = sqlite3_backup_finish( S3JniLongPtr_sqlite3_backup(jpBack) ); + rc = sqlite3_backup_finish( LongPtrGet_sqlite3_backup(jpBack) ); } return rc; } @@ -2327,8 +2477,8 @@ S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)( JniArgsEnvClass, jlong jpDbDest, jstring jTDest, jlong jpDbSrc, jstring jTSrc ){ - sqlite3 * const pDest = S3JniLongPtr_sqlite3(jpDbDest); - sqlite3 * const pSrc = S3JniLongPtr_sqlite3(jpDbSrc); + sqlite3 * const pDest = LongPtrGet_sqlite3(jpDbDest); + sqlite3 * const pSrc = LongPtrGet_sqlite3(jpDbSrc); char * const zDest = s3jni_jstring_to_utf8(jTDest, 0); char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0); jobject rv = 0; @@ -2351,19 +2501,19 @@ S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)( S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)( JniArgsEnvClass, jlong jpBack ){ - return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack)); + return sqlite3_backup_pagecount(LongPtrGet_sqlite3_backup(jpBack)); } S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)( JniArgsEnvClass, jlong jpBack ){ - return sqlite3_backup_remaining(S3JniLongPtr_sqlite3_backup(jpBack)); + return sqlite3_backup_remaining(LongPtrGet_sqlite3_backup(jpBack)); } S3JniApi(sqlite3_backup_step(),jint,1backup_1step)( JniArgsEnvClass, jlong jpBack, jint nPage ){ - return sqlite3_backup_step(S3JniLongPtr_sqlite3_backup(jpBack), (int)nPage); + return sqlite3_backup_step(LongPtrGet_sqlite3_backup(jpBack), (int)nPage); } S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( @@ -2376,34 +2526,140 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( if( nMax>nBA ){ nMax = nBA; } - rc = sqlite3_bind_blob(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + rc = sqlite3_bind_blob(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, SQLITE_TRANSIENT); s3jni_jbyteArray_release(baData, pBuf); }else{ rc = baData ? SQLITE_NOMEM - : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx ); + : sqlite3_bind_null( LongPtrGet_sqlite3_stmt(jpStmt), ndx ); } return (jint)rc; } +/** + Helper for use with s3jni_setup_nio_args(). +*/ +struct S3JniNioArgs { + jobject jBuf; /* input - ByteBuffer */ + jint iOffset; /* input - byte offset */ + jint iHowMany; /* input - byte count to bind/read/write */ + jint nBuf; /* output - jBuf's buffer size */ + void * p; /* output - jBuf's buffer memory */ + void * pStart; /* output - offset of p to bind/read/write */ + int nOut; /* output - number of bytes from pStart to bind/read/write */ +}; +typedef struct S3JniNioArgs S3JniNioArgs; +static const S3JniNioArgs S3JniNioArgs_empty = { + 0,0,0,0,0,0,0 +}; + +/* +** Internal helper for sqlite3_bind_nio_buffer(), +** sqlite3_result_nio_buffer(), and similar methods which take a +** ByteBuffer object as either input or output. Populates pArgs and +** returns 0 on success, non-0 if the operation should fail. The +** caller is required to check for SJG.g.byteBuffer.klazz!=0 before calling +** this and reporting it in a way appropriate for that routine. This +** function may assert() that SJG.g.byteBuffer.klazz is not 0. +** +** The (jBuffer, iOffset, iHowMany) arguments are the (ByteBuffer, offset, +** length) arguments to the bind/result method. +** +** If iHowMany is negative then it's treated as "until the end" and +** the calculated slice is trimmed to fit if needed. If iHowMany is +** positive and extends past the end of jBuffer then SQLITE_ERROR is +** returned. +** +** Returns 0 if everything looks to be in order, else some SQLITE_... +** result code +*/ +static int s3jni_setup_nio_args( + JNIEnv *env, S3JniNioArgs * pArgs, + jobject jBuffer, jint iOffset, jint iHowMany +){ + jlong iEnd = 0; + const int bAllowTruncate = iHowMany<0; + *pArgs = S3JniNioArgs_empty; + pArgs->jBuf = jBuffer; + pArgs->iOffset = iOffset; + pArgs->iHowMany = iHowMany; + assert( SJG.g.byteBuffer.klazz ); + if( pArgs->iOffset<0 ){ + return SQLITE_ERROR + /* SQLITE_MISUSE or SQLITE_RANGE would fit better but we use + SQLITE_ERROR for consistency with the code documented for a + negative target blob offset in sqlite3_blob_read/write(). */; + } + s3jni_get_nio_buffer(pArgs->jBuf, &pArgs->p, &pArgs->nBuf); + if( !pArgs->p ){ + return SQLITE_MISUSE; + }else if( pArgs->iOffset>=pArgs->nBuf ){ + pArgs->pStart = 0; + pArgs->nOut = 0; + return 0; + } + assert( pArgs->nBuf > 0 ); + assert( pArgs->iOffset < pArgs->nBuf ); + iEnd = pArgs->iHowMany<0 + ? pArgs->nBuf - pArgs->iOffset + : pArgs->iOffset + pArgs->iHowMany; + if( iEnd>(jlong)pArgs->nBuf ){ + if( bAllowTruncate ){ + iEnd = pArgs->nBuf - pArgs->iOffset; + }else{ + return SQLITE_ERROR + /* again: for consistency with blob_read/write(), though + SQLITE_MISUSE or SQLITE_RANGE would be a better fit. */; + } + } + if( iEnd - pArgs->iOffset > (jlong)SQLITE_MAX_LENGTH ){ + return SQLITE_TOOBIG; + } + assert( pArgs->iOffset >= 0 ); + assert( iEnd > pArgs->iOffset ); + pArgs->pStart = pArgs->p + pArgs->iOffset; + pArgs->nOut = (int)(iEnd - pArgs->iOffset); + assert( pArgs->nOut > 0 ); + assert( (pArgs->pStart + pArgs->nOut) <= (pArgs->p + pArgs->nBuf) ); + return 0; +} + +S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jobject jBuffer, + jint iOffset, jint iN +){ + sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt); + S3JniNioArgs args; + int rc; + if( !pStmt || !SJG.g.byteBuffer.klazz ) return SQLITE_MISUSE; + rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN); + if(rc){ + return rc; + }else if( !args.pStart || !args.nOut ){ + return sqlite3_bind_null(pStmt, ndx); + } + return sqlite3_bind_blob( pStmt, (int)ndx, args.pStart, + args.nOut, SQLITE_TRANSIENT ); +} + S3JniApi(sqlite3_bind_double(),jint,1bind_1double)( JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val ){ - return (jint)sqlite3_bind_double(S3JniLongPtr_sqlite3_stmt(jpStmt), + return (jint)sqlite3_bind_double(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); } S3JniApi(sqlite3_bind_int(),jint,1bind_1int)( JniArgsEnvClass, jlong jpStmt, jint ndx, jint val ){ - return (jint)sqlite3_bind_int(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (int)val); + return (jint)sqlite3_bind_int(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)( JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val ){ - return (jint)sqlite3_bind_int64(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); + return (jint)sqlite3_bind_int64(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); } /* @@ -2412,13 +2668,13 @@ S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)( S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)( JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val ){ - sqlite3_stmt * const pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt); + sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt); int rc = SQLITE_MISUSE; if(pStmt){ jobject const rv = S3JniRefGlobal(val); if( rv ){ - rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr, + rc = sqlite3_bind_pointer(pStmt, ndx, rv, s3jni__value_jref_key, S3Jni_jobject_finalizer); }else if(val){ rc = SQLITE_NOMEM; @@ -2432,13 +2688,13 @@ S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)( S3JniApi(sqlite3_bind_null(),jint,1bind_1null)( JniArgsEnvClass, jlong jpStmt, jint ndx ){ - return (jint)sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx); + return (jint)sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx); } S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)( JniArgsEnvClass, jlong jpStmt ){ - return (jint)sqlite3_bind_parameter_count(S3JniLongPtr_sqlite3_stmt(jpStmt)); + return (jint)sqlite3_bind_parameter_count(LongPtrGet_sqlite3_stmt(jpStmt)); } S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)( @@ -2447,7 +2703,7 @@ S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)( int rc = 0; jbyte * const pBuf = s3jni_jbyteArray_bytes(jName); if( pBuf ){ - rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt), + rc = sqlite3_bind_parameter_index(LongPtrGet_sqlite3_stmt(jpStmt), (const char *)pBuf); s3jni_jbyteArray_release(jName, pBuf); } @@ -2458,7 +2714,7 @@ S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)( JniArgsEnvClass, jlong jpStmt, jint ndx ){ const char *z = - sqlite3_bind_parameter_name(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx); + sqlite3_bind_parameter_name(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx); return z ? s3jni_utf8_to_jstring(z, -1) : 0; } @@ -2480,14 +2736,14 @@ static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx, such cases, we do not expose the byte-limit arguments in the public API. */ rc = is16 - ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + ? sqlite3_bind_text16(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, SQLITE_TRANSIENT) - : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + : sqlite3_bind_text(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, (int)nMax, SQLITE_TRANSIENT); }else{ rc = baData - ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx) + ? sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx) : SQLITE_NOMEM; } s3jni_jbyteArray_release(baData, pBuf); @@ -2511,9 +2767,9 @@ S3JniApi(sqlite3_bind_value(),jint,1bind_1value)( JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue ){ int rc = 0; - sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt); + sqlite3_stmt * pStmt = LongPtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - sqlite3_value *v = S3JniLongPtr_sqlite3_value(jpValue); + sqlite3_value *v = LongPtrGet_sqlite3_value(jpValue); if( v ){ rc = sqlite3_bind_value(pStmt, (int)ndx, v); }else{ @@ -2528,27 +2784,27 @@ S3JniApi(sqlite3_bind_value(),jint,1bind_1value)( S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)( JniArgsEnvClass, jlong jpStmt, jint ndx, jint n ){ - return (jint)sqlite3_bind_zeroblob(S3JniLongPtr_sqlite3_stmt(jpStmt), + return (jint)sqlite3_bind_zeroblob(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); } S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)( JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n ){ - return (jint)sqlite3_bind_zeroblob64(S3JniLongPtr_sqlite3_stmt(jpStmt), + return (jint)sqlite3_bind_zeroblob64(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); } S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)( JniArgsEnvClass, jlong jpBlob ){ - return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob)); + return sqlite3_blob_bytes(LongPtrGet_sqlite3_blob(jpBlob)); } S3JniApi(sqlite3_blob_close(),jint,1blob_1close)( JniArgsEnvClass, jlong jpBlob ){ - sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob); + sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE; } @@ -2556,7 +2812,7 @@ S3JniApi(sqlite3_blob_open(),jint,1blob_1open)( JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol, jlong jRowId, jint flags, jobject jOut ){ - sqlite3 * const db = S3JniLongPtr_sqlite3(jpDb); + sqlite3 * const db = LongPtrGet_sqlite3(jpDb); sqlite3_blob * pBlob = 0; char * zDbName = 0, * zTableName = 0, * zColumnName = 0; int rc; @@ -2590,7 +2846,7 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)( int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE; if( pBa ){ jsize const nTgt = (*env)->GetArrayLength(env, jTgt); - rc = sqlite3_blob_read(S3JniLongPtr_sqlite3_blob(jpBlob), pBa, + rc = sqlite3_blob_read(LongPtrGet_sqlite3_blob(jpBlob), pBa, (int)nTgt, (int)iOffset); if( 0==rc ){ s3jni_jbyteArray_commit(jTgt, pBa); @@ -2601,17 +2857,41 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)( return rc; } +S3JniApi(sqlite3_blob_read_nio_buffer(),jint,1blob_1read_1nio_1buffer)( + JniArgsEnvClass, jlong jpBlob, jint iSrcOff, jobject jBB, jint iTgtOff, jint iHowMany +){ + sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); + S3JniNioArgs args; + int rc; + if( !b || !SJG.g.byteBuffer.klazz || iHowMany<0 ){ + return SQLITE_MISUSE; + }else if( iTgtOff<0 || iSrcOff<0 ){ + return SQLITE_ERROR + /* for consistency with underlying sqlite3_blob_read() */; + }else if( 0==iHowMany ){ + return 0; + } + rc = s3jni_setup_nio_args(env, &args, jBB, iTgtOff, iHowMany); + if(rc){ + return rc; + }else if( !args.pStart || !args.nOut ){ + return 0; + } + assert( args.iHowMany>0 ); + return sqlite3_blob_read( b, args.pStart, (int)args.nOut, (int)iSrcOff ); +} + S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)( JniArgsEnvClass, jlong jpBlob, jlong iNewRowId ){ - return (jint)sqlite3_blob_reopen(S3JniLongPtr_sqlite3_blob(jpBlob), + return (jint)sqlite3_blob_reopen(LongPtrGet_sqlite3_blob(jpBlob), (sqlite3_int64)iNewRowId); } S3JniApi(sqlite3_blob_write(),jint,1blob_1write)( JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset ){ - sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob); + sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0; const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0; int rc = SQLITE_MISUSE; @@ -2622,6 +2902,29 @@ S3JniApi(sqlite3_blob_write(),jint,1blob_1write)( return (jint)rc; } +S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)( + JniArgsEnvClass, jlong jpBlob, jint iTgtOff, jobject jBB, jint iSrcOff, jint iHowMany +){ + sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); + S3JniNioArgs args; + int rc; + if( !b || !SJG.g.byteBuffer.klazz ){ + return SQLITE_MISUSE; + }else if( iTgtOff<0 || iSrcOff<0 ){ + return SQLITE_ERROR + /* for consistency with underlying sqlite3_blob_write() */; + }else if( 0==iHowMany ){ + return 0; + } + rc = s3jni_setup_nio_args(env, &args, jBB, iSrcOff, iHowMany); + if(rc){ + return rc; + }else if( !args.pStart || !args.nOut ){ + return 0; + } + return sqlite3_blob_write( b, args.pStart, (int)args.nOut, (int)iTgtOff ); +} + /* Central C-to-Java busy handler proxy. */ static int s3jni_busy_handler(void* pState, int n){ S3JniDb * const ps = (S3JniDb *)pState; @@ -2820,7 +3123,7 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)( }else{ jclass const klazz = (*env)->GetObjectClass(env, jHook); jmethodID const xCallback = (*env)->GetMethodID( - env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I" + env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)V" ); S3JniUnrefLocal(klazz); S3JniIfThrew { @@ -2870,6 +3173,41 @@ S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)( return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } +S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)( + JniArgsEnvClass, jlong jpStmt, jint ndx +){ + sqlite3_stmt * const stmt = LongPtrGet_sqlite3_stmt(jpStmt); + jobject rv = 0; + if( stmt ){ + sqlite3 * const db = sqlite3_db_handle(stmt); + sqlite3_value * sv; + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + sv = sqlite3_column_value(stmt, (int)ndx); + if( sv ){ + rv = S3JniRefLocal( + sqlite3_value_pointer(sv, s3jni__value_jref_key) + ); + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + } + return rv; +} + +S3JniApi(sqlite3_column_nio_buffer(),jobject,1column_1nio_1buffer)( + JniArgsEnvClass, jobject jStmt, jint ndx +){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jStmt); + jobject rv = 0; + if( stmt ){ + const void * const p = sqlite3_column_blob(stmt, (int)ndx); + if( p ){ + const int n = sqlite3_column_bytes(stmt, (int)ndx); + rv = s3jni__blob_to_ByteBuffer(env, p, n); + } + } + return rv; +} + S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)( JniArgsEnvClass, jobject jpStmt, jint ndx ){ @@ -2925,7 +3263,10 @@ static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback) : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0); S3JniIfThrew{ - rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw"); + rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, + isCommit + ? "Commit hook callback threw" + : "Rollback hook callback threw"); } S3JniHook_localundup(hook); } @@ -3028,7 +3369,7 @@ S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)( return rc; } -S3JniApi(sqlite3_complete(),int,1complete)( +S3JniApi(sqlite3_complete(),jint,1complete)( JniArgsEnvClass, jbyteArray jSql ){ jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql); @@ -3044,8 +3385,9 @@ S3JniApi(sqlite3_complete(),int,1complete)( return rc; } -S3JniApi(sqlite3_config() /*for a small subset of options.*/, - jint,1config__I)(JniArgsEnvClass, jint n){ +S3JniApi(sqlite3_config() /*for a small subset of options.*/ + sqlite3_config__enable()/* internal name to avoid name-mangling issues*/, + jint,1config_1_1enable)(JniArgsEnvClass, jint n){ switch( n ){ case SQLITE_CONFIG_SINGLETHREAD: case SQLITE_CONFIG_MULTITHREAD: @@ -3075,8 +3417,9 @@ static void s3jni_config_log(void *ignored, int errCode, const char *z){ } } -S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */, - jint, 1config__Lorg_sqlite_jni_ConfigLogCallback_2 +S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */ + sqlite3_config__config_log() /* internal name */, + jint, 1config_1_1CONFIG_1LOG )(JniArgsEnvClass, jobject jLog){ S3JniHook * const pHook = &SJG.hook.configlog; int rc = 0; @@ -3150,9 +3493,10 @@ void sqlite3_init_sqllog(void){ } #endif -S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */, - jint, 1config__Lorg_sqlite_jni_ConfigSqllogCallback_2)( - JniArgsEnvClass, jobject jLog){ +S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */ + sqlite3_config__SQLLOG() /*internal name*/, + jint, 1config_1_1SQLLOG +)(JniArgsEnvClass, jobject jLog){ #ifndef SQLITE_ENABLE_SQLLOG return SQLITE_MISUSE; #else @@ -3412,7 +3756,6 @@ S3JniApi( } break; } - case 0: default: rc = SQLITE_MISUSE; } @@ -3475,7 +3818,7 @@ S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)( return (jint)rc; } -S3JniApi(sqlite3_db_release_memory(),int,1db_1release_1memory)( +S3JniApi(sqlite3_db_release_memory(),jint,1db_1release_1memory)( JniArgsEnvClass, jobject jDb ){ sqlite3 * const pDb = PtrGet_sqlite3(jDb); @@ -3516,9 +3859,16 @@ S3JniApi(sqlite3_errmsg(),jstring,1errmsg)( S3JniApi(sqlite3_errstr(),jstring,1errstr)( JniArgsEnvClass, jint rcCode ){ - jstring const rv = (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) - /* We know these values to be plain ASCII, so pose no MUTF-8 - ** incompatibility */; + jstring rv; + const char * z = sqlite3_errstr((int)rcCode); + if( !z ){ + /* This hypothetically cannot happen, but we'll behave like the + low-level library would in such a case... */ + z = "unknown error"; + } + rv = (*env)->NewStringUTF(env, z) + /* We know these values to be plain ASCII, so pose no MUTF-8 + ** incompatibility */; s3jni_oom_check( rv ); return rv; } @@ -3570,19 +3920,21 @@ S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)( #endif } -S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)( +S3JniApi(sqlite3_extended_result_codes(),jint,1extended_1result_1codes)( JniArgsEnvClass, jobject jpDb, jboolean onoff ){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); - int const rc = pDb ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) : 0; - return rc ? JNI_TRUE : JNI_FALSE; + int const rc = pDb + ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) + : SQLITE_MISUSE; + return rc; } S3JniApi(sqlite3_finalize(),jint,1finalize)( JniArgsEnvClass, jlong jpStmt ){ return jpStmt - ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt)) + ? sqlite3_finalize(LongPtrGet_sqlite3_stmt(jpStmt)) : 0; } @@ -3623,7 +3975,9 @@ S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)( ** any resources owned by that cache entry and making that slot ** available for re-use. */ -JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){ +S3JniApi(sqlite3_java_uncache_thread(), jboolean, 1java_1uncache_1thread)( + JniArgsEnvClass +){ int rc; S3JniEnv_mutex_enter; rc = S3JniEnv_uncache(env); @@ -3631,6 +3985,29 @@ JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){ return rc ? JNI_TRUE : JNI_FALSE; } +S3JniApi(sqlite3_jni_db_error(), jint, 1jni_1db_1error)( + JniArgsEnvClass, jobject jDb, jint jRc, jstring jStr +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + int rc = SQLITE_MISUSE; + if( ps ){ + char *zStr; + zStr = jStr + ? s3jni_jstring_to_utf8( jStr, 0) + : NULL; + rc = s3jni_db_error( ps->pDb, (int)jRc, zStr ); + sqlite3_free(zStr); + } + return rc; +} + +S3JniApi(sqlite3_jni_supports_nio(), jboolean,1jni_1supports_1nio)( + JniArgsEnvClass +){ + return SJG.g.byteBuffer.klazz ? JNI_TRUE : JNI_FALSE; +} + + S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)( JniArgsEnvClass, jstring jWord ){ @@ -3804,14 +4181,15 @@ S3JniApi(sqlite3_open_v2(),jint,1open_1v2)( } /* Proxy for the sqlite3_prepare[_v2/3]() family. */ -jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self, - jlong jpDb, jbyteArray baSql, - jint nMax, jint prepFlags, - jobject jOutStmt, jobject outTail){ +static jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, + jclass self, + jlong jpDb, jbyteArray baSql, + jint nMax, jint prepFlags, + jobject jOutStmt, jobject outTail){ sqlite3_stmt * pStmt = 0; jobject jStmt = 0; const char * zTail = 0; - sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb); + sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb); jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql) : 0; int rc = SQLITE_ERROR; @@ -3966,11 +4344,11 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, #if !defined(SQLITE_ENABLE_PREUPDATE_HOOK) /* We need no-op impls for preupdate_{count,depth,blobwrite}() */ -S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)( +S3JniApi(sqlite3_preupdate_blobwrite(),jint,1preupdate_1blobwrite)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } -S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)( +S3JniApi(sqlite3_preupdate_count(),jint,1preupdate_1count)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } -S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)( +S3JniApi(sqlite3_preupdate_depth(),jint,1preupdate_1depth)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } #endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -4065,7 +4443,7 @@ S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)( static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb, jint iCol, jobject jOut){ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK - sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb); + sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb); int rc = SQLITE_MISUSE; if( pDb ){ sqlite3_value * pOut = 0; @@ -4286,7 +4664,7 @@ S3JniApi(sqlite3_result_double(),void,1result_1double)( } S3JniApi(sqlite3_result_error(),void,1result_1error)( - JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep + JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, jint eTextRep ){ const char * zUnspecified = "Unspecified error."; jsize const baLen = (*env)->GetArrayLength(env, baMsg); @@ -4351,7 +4729,7 @@ S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)( jobject const rjv = S3JniRefGlobal(v); if( rjv ){ sqlite3_result_pointer(pCx, rjv, - ResultJavaValuePtrStr, S3Jni_jobject_finalizer); + s3jni__value_jref_key, S3Jni_jobject_finalizer); }else{ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } @@ -4360,12 +4738,53 @@ S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)( } } +S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)( + JniArgsEnvClass, jobject jpCtx, jobject jBuffer, + jint iOffset, jint iN +){ + sqlite3_context * pCx = PtrGet_sqlite3_context(jpCtx); + int rc; + S3JniNioArgs args; + if( !pCx ){ + return; + }else if( !SJG.g.byteBuffer.klazz ){ + sqlite3_result_error( + pCx, "This JVM does not support JNI access to ByteBuffers.", -1 + ); + return; + } + rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN); + if(rc){ + if( iOffset<0 ){ + sqlite3_result_error(pCx, "Start index may not be negative.", -1); + }else if( SQLITE_TOOBIG==rc ){ + sqlite3_result_error_toobig(pCx); + }else{ + sqlite3_result_error( + pCx, "Invalid arguments to sqlite3_result_nio_buffer().", -1 + ); + } + }else if( !args.pStart || !args.nOut ){ + sqlite3_result_null(pCx); + }else{ + sqlite3_result_blob(pCx, args.pStart, args.nOut, SQLITE_TRANSIENT); + } +} + + S3JniApi(sqlite3_result_null(),void,1result_1null)( JniArgsEnvClass, jobject jpCx ){ sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); } +S3JniApi(sqlite3_result_subtype(),void,1result_1subtype)( + JniArgsEnvClass, jobject jpCx, jint v +){ + sqlite3_result_subtype(PtrGet_sqlite3_context(jpCx), (unsigned int)v); +} + + S3JniApi(sqlite3_result_text(),void,1result_1text)( JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax ){ @@ -4539,20 +4958,8 @@ S3JniApi(sqlite3_shutdown(),jint,1shutdown)( S3JniEnv_uncache( SJG.envCache.aHead->env ); } } S3JniEnv_mutex_leave; -#if 0 - /* - ** Is automatically closing any still-open dbs a good idea? We will - ** get rid of the perDb list once sqlite3 gets a per-db client - ** state, at which point we won't have a central list of databases - ** to close. - */ - S3JniDb_mutex_enter; - while( SJG.perDb.pHead ){ - s3jni_close_db(env, SJG.perDb.pHead->jDb, 2); - } - S3JniDb_mutex_leave; -#endif - /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */ + /* Do not clear S3JniGlobal.jvm or S3JniGlobal.g: it's legal to + ** restart the lib. */ return sqlite3_shutdown(); } @@ -4594,7 +5001,7 @@ static int s3jni_strlike_glob(int isLike, JNIEnv *const env, jbyteArray baG, jbyteArray baT, jint escLike){ int rc = 0; jbyte * const pG = s3jni_jbyteArray_bytes(baG); - jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0; + jbyte * const pT = s3jni_jbyteArray_bytes(baT); /* Note that we're relying on the byte arrays having been NUL-terminated on the Java side. */ @@ -4633,13 +5040,13 @@ S3JniApi(sqlite3_sql(),jstring,1sql)( } S3JniApi(sqlite3_step(),jint,1step)( - JniArgsEnvClass,jobject jStmt + JniArgsEnvClass, jlong jpStmt ){ - sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); + sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt); return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE; } -S3JniApi(sqlite3_table_column_metadata(),int,1table_1column_1metadata)( +S3JniApi(sqlite3_table_column_metadata(),jint,1table_1column_1metadata)( JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName, jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull, jobject jPrimaryKey, jobject jAutoinc @@ -4815,7 +5222,7 @@ S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)( S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0; int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0; @@ -4825,17 +5232,17 @@ S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)( : NULL; } -S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)( +S3JniApi(sqlite3_value_bytes(),jint,1value_1bytes)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return sv ? sqlite3_value_bytes(sv) : 0; } -S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)( +S3JniApi(sqlite3_value_bytes16(),jint,1value_1bytes16)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return sv ? sqlite3_value_bytes16(sv) : 0; } @@ -4843,7 +5250,7 @@ S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)( S3JniApi(sqlite3_value_double(),jdouble,1value_1double)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0); } @@ -4851,7 +5258,7 @@ S3JniApi(sqlite3_value_double(),jdouble,1value_1double)( S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0; jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0; if( sd && !rv ) { @@ -4864,7 +5271,7 @@ S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)( S3JniApi(sqlite3_value_free(),void,1value_1free)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); if( sv ){ sqlite3_value_free(sv); } @@ -4873,30 +5280,45 @@ S3JniApi(sqlite3_value_free(),void,1value_1free)( S3JniApi(sqlite3_value_int(),jint,1value_1int)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return (jint) (sv ? sqlite3_value_int(sv) : 0); } S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL); } S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return sv - ? sqlite3_value_pointer(sv, ResultJavaValuePtrStr) + ? sqlite3_value_pointer(sv, s3jni__value_jref_key) : 0; } +S3JniApi(sqlite3_value_nio_buffer(),jobject,1value_1nio_1buffer)( + JniArgsEnvClass, jobject jVal +){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jVal); + jobject rv = 0; + if( sv ){ + const void * const p = sqlite3_value_blob(sv); + if( p ){ + const int n = sqlite3_value_bytes(sv); + rv = s3jni__blob_to_ByteBuffer(env, p, n); + } + } + return rv; +} + S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0; int const n = p ? sqlite3_value_bytes(sv) : 0; return p ? s3jni_new_jbyteArray(p, n) : 0; @@ -4907,7 +5329,7 @@ S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)( S3JniApi(sqlite3_value_text(),jstring,1value_1text)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0; int const n = p ? sqlite3_value_bytes(sv) : 0; return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0; @@ -4917,7 +5339,7 @@ S3JniApi(sqlite3_value_text(),jstring,1value_1text)( S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const int n = sv ? sqlite3_value_bytes16(sv) : 0; const void * const p = sv ? sqlite3_value_text16(sv) : 0; return p ? s3jni_text16_to_jstring(env, p, n) : 0; @@ -5499,7 +5921,7 @@ JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){ return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx)); } -JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ +JniDeclFtsXA(jint,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; S3JniFts5AuxData * pAux; @@ -5893,6 +6315,28 @@ Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){ s3jni_oom_fatal( SJG.metrics.mutex ); #endif + { + /* Test whether this JVM supports direct memory access via + ByteBuffer. */ + unsigned char buf[16] = {0}; + jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16); + if( bb ){ + SJG.g.byteBuffer.klazz = S3JniRefGlobal((*env)->GetObjectClass(env, bb)); + SJG.g.byteBuffer.midAlloc = (*env)->GetStaticMethodID( + env, SJG.g.byteBuffer.klazz, "allocateDirect", "(I)Ljava/nio/ByteBuffer;" + ); + S3JniExceptionIsFatal("Error getting ByteBuffer.allocateDirect() method."); + SJG.g.byteBuffer.midLimit = (*env)->GetMethodID( + env, SJG.g.byteBuffer.klazz, "limit", "()I" + ); + S3JniExceptionIsFatal("Error getting ByteBuffer.limit() method."); + S3JniUnrefLocal(bb); + }else{ + SJG.g.byteBuffer.klazz = 0; + SJG.g.byteBuffer.midAlloc = 0; + } + } + sqlite3_shutdown() /* So that it becomes legal for Java-level code to call ** sqlite3_config(). */; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index bf6df7ac9..082a20212 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -427,8 +427,6 @@ extern "C" { #define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L #undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT #define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L -#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE -#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE 2L #undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB #define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L #undef org_sqlite_jni_capi_CApi_SQLITE_OK @@ -707,8 +705,12 @@ extern "C" { #define org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC 2048L #undef org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY #define org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY 524288L +#undef org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE +#define org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE 1048576L #undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS #define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L +#undef org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE +#define org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE 16777216L #undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE #define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L #undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ @@ -775,6 +777,22 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread (JNIEnv *, jclass); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_jni_supports_nio + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_jni_db_error + * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1db_1error + (JNIEnv *, jclass, jobject, jint, jstring); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_aggregate_context @@ -871,6 +889,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64 JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object (JNIEnv *, jclass, jlong, jint, jobject); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_bind_nio_buffer + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;ILjava/nio/ByteBuffer;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1nio_1buffer + (JNIEnv *, jclass, jobject, jint, jobject, jint, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_null @@ -975,6 +1001,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read (JNIEnv *, jclass, jlong, jbyteArray, jint); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_read_nio_buffer + * Signature: (JILjava/nio/ByteBuffer;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read_1nio_1buffer + (JNIEnv *, jclass, jlong, jint, jobject, jint, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_blob_reopen @@ -991,6 +1025,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write (JNIEnv *, jclass, jlong, jbyteArray, jint); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_write_nio_buffer + * Signature: (JILjava/nio/ByteBuffer;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write_1nio_1buffer + (JNIEnv *, jclass, jlong, jint, jobject, jint, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_busy_handler @@ -1087,6 +1129,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16 JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count (JNIEnv *, jclass, jlong); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_column_database_name + * Signature: (JI)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name + (JNIEnv *, jclass, jlong, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_decltype @@ -1119,6 +1169,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int64 (JNIEnv *, jclass, jobject, jint); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_column_java_object + * Signature: (JI)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1java_1object + (JNIEnv *, jclass, jlong, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_name @@ -1129,11 +1187,11 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name /* * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_column_database_name - * Signature: (JI)Ljava/lang/String; + * Method: sqlite3_column_nio_buffer + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/nio/ByteBuffer; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name - (JNIEnv *, jclass, jlong, jint); +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1nio_1buffer + (JNIEnv *, jclass, jobject, jint); /* * Class: org_sqlite_jni_capi_CApi @@ -1225,26 +1283,26 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete /* * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_config + * Method: sqlite3_config__enable * Signature: (I)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1enable (JNIEnv *, jclass, jint); /* * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_config - * Signature: (Lorg/sqlite/jni/capi/ConfigSqllogCallback;)I + * Method: sqlite3_config__CONFIG_LOG + * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigSqllogCallback_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1CONFIG_1LOG (JNIEnv *, jclass, jobject); /* * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_config - * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I + * Method: sqlite3_config__SQLLOG + * Signature: (Lorg/sqlite/jni/capi/ConfigSqlLogCallback;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1SQLLOG (JNIEnv *, jclass, jobject); /* @@ -1394,9 +1452,9 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_extended_result_codes - * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)I */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes (JNIEnv *, jclass, jobject, jboolean); /* @@ -1679,14 +1737,6 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1nom JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1code (JNIEnv *, jclass, jobject, jint); -/* - * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_result_null - * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V - */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null - (JNIEnv *, jclass, jobject); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_int @@ -1711,6 +1761,30 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64 JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object (JNIEnv *, jclass, jobject, jobject); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_result_nio_buffer + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/nio/ByteBuffer;II)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1nio_1buffer + (JNIEnv *, jclass, jobject, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_result_null + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_result_subtype + * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1subtype + (JNIEnv *, jclass, jobject, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_value @@ -1850,10 +1924,10 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64 /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_step - * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I + * Signature: (J)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step - (JNIEnv *, jclass, jobject); + (JNIEnv *, jclass, jlong); /* * Class: org_sqlite_jni_capi_CApi @@ -2063,6 +2137,14 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64 JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object (JNIEnv *, jclass, jlong); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_value_nio_buffer + * Signature: (Lorg/sqlite/jni/capi/sqlite3_value;)Ljava/nio/ByteBuffer; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nio_1buffer + (JNIEnv *, jclass, jobject); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_nochange diff --git a/ext/jni/src/org/sqlite/jni/annotation/Experimental.java b/ext/jni/src/org/sqlite/jni/annotation/Experimental.java new file mode 100644 index 000000000..190435c85 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/Experimental.java @@ -0,0 +1,30 @@ +/* +** 2023-09-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 houses the Experimental annotation for the sqlite3 C API. +*/ +package org.sqlite.jni.annotation; +import java.lang.annotation.*; + +/** + This annotation is for flagging methods, constructors, and types + which are expressly experimental and subject to any amount of + change or outright removal. Client code should not rely on such + features. +*/ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target({ + ElementType.METHOD, + ElementType.CONSTRUCTOR, + ElementType.TYPE +}) +public @interface Experimental{} diff --git a/ext/jni/src/org/sqlite/jni/annotation/NotNull.java b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java index 3b4c1c7af..0c31782f2 100644 --- a/ext/jni/src/org/sqlite/jni/annotation/NotNull.java +++ b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java @@ -9,21 +9,22 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file houses the NotNull annotaion for the sqlite3 C API. +** This file houses the NotNull annotation for the sqlite3 C API. */ package org.sqlite.jni.annotation; +import java.lang.annotation.*; /** This annotation is for flagging parameters which may not legally be null or point to closed/finalized C-side resources.

    In the case of Java types which map directly to C struct types - (e.g. {@link org.sqlite.jni.sqlite3}, {@link - org.sqlite.jni.sqlite3_stmt}, and {@link - org.sqlite.jni.sqlite3_context}), a closed/finalized resource is - also considered to be null for purposes this annotation because the - C-side effect of passing such a handle is the same as if null is - passed.

    + (e.g. {@link org.sqlite.jni.capi.sqlite3}, {@link + org.sqlite.jni.capi.sqlite3_stmt}, and {@link + org.sqlite.jni.capi.sqlite3_context}), a closed/finalized resource + is also considered to be null for purposes this annotation because + the C-side effect of passing such a handle is the same as if null + is passed.

    When used in the context of Java interfaces which are called from the C APIs, this annotation communicates that the C API will @@ -31,12 +32,23 @@

    Passing a null, for this annotation's definition of null, for any parameter marked with this annoation specifically invokes - undefined behavior.

    + undefined behavior (see below).

    Passing 0 (i.e. C NULL) or a negative value for any long-type parameter marked with this annoation specifically invokes undefined - behavior. Such values are treated as C pointers in the JNI - layer.

    + behavior (see below). Such values are treated as C pointers in the + JNI layer.

    + +

    Undefined behaviour: the JNI build uses the {@code + SQLITE_ENABLE_API_ARMOR} build flag, meaning that the C code + invoked with invalid NULL pointers and the like will not invoke + undefined behavior in the conventional C sense, but may, for + example, return result codes which are not documented for the + affected APIs or may otherwise behave unpredictably. In no known + cases will such arguments result in C-level code dereferencing a + NULL pointer or accessing out-of-bounds (or otherwise invalid) + memory. In other words, they may cause unexpected behavior but + should never cause an outright crash or security issue.

    Note that the C-style API does not throw any exceptions on its own because it has a no-throw policy in order to retain its C-style @@ -48,12 +60,12 @@ semantics, but it may trigger NullPointerExceptions (or similar) if code.

    This annotation is solely for the use by the classes in the - org.sqlite package and subpackages, but is made public so that + org.sqlite.jni package and subpackages, but is made public so that javadoc will link to it from the annotated functions. It is not part of the public API and client-level code must not rely on it.

    */ -@java.lang.annotation.Documented -@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) -@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PARAMETER) public @interface NotNull{} diff --git a/ext/jni/src/org/sqlite/jni/annotation/Nullable.java b/ext/jni/src/org/sqlite/jni/annotation/Nullable.java index ddc8502d6..e3fa30efc 100644 --- a/ext/jni/src/org/sqlite/jni/annotation/Nullable.java +++ b/ext/jni/src/org/sqlite/jni/annotation/Nullable.java @@ -9,9 +9,10 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file houses the Nullable annotaion for the sqlite3 C API. +** This file houses the Nullable annotation for the sqlite3 C API. */ package org.sqlite.jni.annotation; +import java.lang.annotation.*; /** This annotation is for flagging parameters which may legally be @@ -26,7 +27,7 @@ annotated functions. It is not part of the public API and client-level code must not rely on it. */ -@java.lang.annotation.Documented -@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) -@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PARAMETER) public @interface Nullable{} diff --git a/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java index 89c4f2742..1fa6c6b80 100644 --- a/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java +++ b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java @@ -42,9 +42,75 @@ If this function throws, it is translated into an sqlite3_result_error(). */ public void xDestroy() {} + /** + PerContextState assists aggregate and window functions in + managing their accumulator state across calls to the UDF's + callbacks. + +

    T must be of a type which can be legally stored as a value in + java.util.HashMap. + +

    If a given aggregate or window function is called multiple times + in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., + then the clients need some way of knowing which call is which so + that they can map their state between their various UDF callbacks + and reset it via xFinal(). This class takes care of such + mappings. + +

    This class works by mapping + sqlite3_context.getAggregateContext() to a single piece of + state, of a client-defined type (the T part of this class), which + persists across a "matching set" of the UDF's callbacks. + +

    This class is a helper providing commonly-needed functionality + - it is not required for use with aggregate or window functions. + Client UDFs are free to perform such mappings using custom + approaches. The provided {@link AggregateFunction} and {@link + WindowFunction} classes use this. + */ + public static final class PerContextState { + private final java.util.Map> map + = new java.util.HashMap<>(); + + /** + Should be called from a UDF's xStep(), xValue(), and xInverse() + methods, passing it that method's first argument and an initial + value for the persistent state. If there is currently no + mapping for the given context within the map, one is created + using the given initial value, else the existing one is used + and the 2nd argument is ignored. It returns a ValueHolder + which can be used to modify that state directly without + requiring that the client update the underlying map's entry. + +

    The caller is obligated to eventually call + takeAggregateState() to clear the mapping. + */ + public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + final Long key = cx.getAggregateContext(true); + ValueHolder rc = null==key ? null : map.get(key); + if( null==rc ){ + map.put(key, rc = new ValueHolder<>(initialValue)); + } + return rc; + } + + /** + Should be called from a UDF's xFinal() method and passed that + method's first argument. This function removes the value + associated with cx.getAggregateContext() from the map and + returns it, returning null if no other UDF method has been + called to set up such a mapping. The latter condition will be + the case if a UDF is used in a statement which has no result + rows. + */ + public T takeAggregateState(sqlite3_context cx){ + final ValueHolder h = map.remove(cx.getAggregateContext(false)); + return null==h ? null : h.value; + } + } + /** Per-invocation state for the UDF. */ - private final SQLFunction.PerContextState map = - new SQLFunction.PerContextState<>(); + private final PerContextState map = new PerContextState<>(); /** To be called from the implementation's xStep() method, as well diff --git a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java index ce7c6fca6..298e3a590 100644 --- a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java @@ -20,7 +20,8 @@ public interface AuthorizerCallback extends CallbackProxy { /** Must function as described for the C-level - sqlite3_set_authorizer() callback. + sqlite3_set_authorizer() callback. If it throws, the error is + converted to a db-level error and the exception is suppressed. */ int call(int opId, @Nullable String s1, @Nullable String s2, @Nullable String s3, @Nullable String s4); diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java index 302cdb760..b5d08306e 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CApi.java +++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file declares JNI bindings for the sqlite3 C API. +** This file declares the main JNI bindings for the sqlite3 C API. */ package org.sqlite.jni.capi; import java.nio.charset.StandardCharsets; @@ -32,15 +32,6 @@

    The C-side part can be found in sqlite3-jni.c. -

    This class is package-private in order to keep Java clients from - having direct access to the low-level C-style APIs, a design - decision made by Java developers based on the C-style API being - riddled with opportunities for Java developers to proverbially shoot - themselves in the foot with. Third-party copies of this code may - eliminate that guard by simply changing this class from - package-private to public. Its methods which are intended to be - exposed that way are all public. -

    Only functions which materially differ from their C counterparts are documented here, and only those material differences are documented. The C documentation is otherwise applicable for these @@ -78,7 +69,7 @@ eliminate that guard by simply changing this class from NUL-terminated, and conversion to a Java byte array must sometimes be careful to add one. Functions which take a length do not require this so long as the length is provided. Search the CApi class - for "\0" for many examples. + for "\0" for examples. @@ -128,10 +119,38 @@ private static byte[] nulTerminateUtf8(String s){

    This routine returns false without side effects if the current JNIEnv is not cached, else returns true, but this information is primarily for testing of the JNI bindings and is not information - which client-level code can use to make any informed decisions. + which client-level code can use to make any informed + decisions. Its return type and semantics are not considered + stable and may change at any time. */ public static native boolean sqlite3_java_uncache_thread(); + /** + Returns true if this JVM has JNI-level support for C-level direct + memory access using java.nio.ByteBuffer, else returns false. + */ + @Experimental + public static native boolean sqlite3_jni_supports_nio(); + + /** + For internal use only. Sets the given db's error code and + (optionally) string. If rc is 0, it defaults to SQLITE_ERROR. + + On success it returns rc. On error it may return a more serious + code, such as SQLITE_NOMEM. Returns SQLITE_MISUSE if db is null. + */ + static native int sqlite3_jni_db_error(@NotNull sqlite3 db, + int rc, @Nullable String msg); + + /** + Convenience overload which uses e.toString() as the error + message. + */ + static int sqlite3_jni_db_error(@NotNull sqlite3 db, + int rc, @NotNull Exception e){ + return sqlite3_jni_db_error(db, rc, e.toString()); + } + ////////////////////////////////////////////////////////////////////// // Maintenance reminder: please keep the sqlite3_.... functions // alphabetized. The SQLITE_... values. on the other hand, are @@ -173,13 +192,13 @@ auto-extension callback interface is unnecessary here. */ public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback); - static native int sqlite3_backup_finish(@NotNull long ptrToBackup); + private static native int sqlite3_backup_finish(@NotNull long ptrToBackup); public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){ - return sqlite3_backup_finish(b.clearNativePointer()); + return null==b ? 0 : sqlite3_backup_finish(b.clearNativePointer()); } - static native sqlite3_backup sqlite3_backup_init( + private static native sqlite3_backup sqlite3_backup_init( @NotNull long ptrToDbDest, @NotNull String destTableName, @NotNull long ptrToDbSrc, @NotNull String srcTableName ); @@ -192,25 +211,25 @@ public static sqlite3_backup sqlite3_backup_init( dbSrc.getNativePointer(), srcTableName ); } - static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup); + private static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup); public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){ return sqlite3_backup_pagecount(b.getNativePointer()); } - static native int sqlite3_backup_remaining(@NotNull long ptrToBackup); + private static native int sqlite3_backup_remaining(@NotNull long ptrToBackup); public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){ return sqlite3_backup_remaining(b.getNativePointer()); } - static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage); + private static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage); public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){ return sqlite3_backup_step(b.getNativePointer(), nPage); } - static native int sqlite3_bind_blob( + private static native int sqlite3_bind_blob( @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n ); @@ -218,7 +237,7 @@ static native int sqlite3_bind_blob( If n is negative, SQLITE_MISUSE is returned. If n>data.length then n is silently truncated to data.length. */ - static int sqlite3_bind_blob( + public static int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n ){ return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n); @@ -232,7 +251,31 @@ public static int sqlite3_bind_blob( : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length); } - static native int sqlite3_bind_double( + /** + Convenience overload which is a simple proxy for + sqlite3_bind_nio_buffer(). + */ + @Experimental + /*public*/ static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data, + int begin, int n + ){ + return sqlite3_bind_nio_buffer(stmt, ndx, data, begin, n); + } + + /** + Convenience overload which is equivalant to passing its arguments + to sqlite3_bind_nio_buffer() with the values 0 and -1 for the + final two arguments. + */ + @Experimental + /*public*/ static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data + ){ + return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1); + } + + private static native int sqlite3_bind_double( @NotNull long ptrToStmt, int ndx, double v ); @@ -242,7 +285,7 @@ public static int sqlite3_bind_double( return sqlite3_bind_double(stmt.getNativePointer(), ndx, v); } - static native int sqlite3_bind_int( + private static native int sqlite3_bind_int( @NotNull long ptrToStmt, int ndx, int v ); @@ -252,7 +295,7 @@ public static int sqlite3_bind_int( return sqlite3_bind_int(stmt.getNativePointer(), ndx, v); } - static native int sqlite3_bind_int64( + private static native int sqlite3_bind_int64( @NotNull long ptrToStmt, int ndx, long v ); @@ -260,10 +303,65 @@ public static int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, int ndx, long v return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v ); } - static native int sqlite3_bind_java_object( + private static native int sqlite3_bind_java_object( @NotNull long ptrToStmt, int ndx, @Nullable Object o ); + /** + Binds the contents of the given buffer object as a blob. + + The byte range of the buffer may be restricted by providing a + start index and a number of bytes. beginPos may not be negative. + Negative howMany is interpretated as the remainder of the buffer + past the given start position, up to the buffer's limit() (as + opposed its capacity()). + + If beginPos+howMany would extend past the limit() of the buffer + then SQLITE_ERROR is returned. + + If any of the following are true, this function behaves like + sqlite3_bind_null(): the buffer is null, beginPos is at or past + the end of the buffer, howMany is 0, or the calculated slice of + the blob has a length of 0. + + If ndx is out of range, it returns SQLITE_RANGE, as documented + for sqlite3_bind_blob(). If beginPos is negative or if + sqlite3_jni_supports_nio() returns false then SQLITE_MISUSE is + returned. Note that this function is bound (as it were) by the + SQLITE_LIMIT_LENGTH constraint and SQLITE_TOOBIG is returned if + the resulting slice of the buffer exceeds that limit. + + This function does not modify the buffer's streaming-related + cursors. + + If the buffer is modified in a separate thread while this + operation is running, results are undefined and will likely + result in corruption of the bound data or a segmentation fault. + + Design note: this function should arguably take a java.nio.Buffer + instead of ByteBuffer, but it can only operate on "direct" + buffers and the only such class offered by Java is (apparently) + ByteBuffer. + + @see https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html + */ + @Experimental + /*public*/ static native int sqlite3_bind_nio_buffer( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data, + int beginPos, int howMany + ); + + /** + Convenience overload which binds the given buffer's entire + contents, up to its limit() (as opposed to its capacity()). + */ + @Experimental + /*public*/ static int sqlite3_bind_nio_buffer( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data + ){ + return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1); + } + /** Binds the given object at the given index. If o is null then this behaves like sqlite3_bind_null(). @@ -276,13 +374,13 @@ public static int sqlite3_bind_java_object( return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o); } - static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx); + private static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx); public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_bind_null(stmt.getNativePointer(), ndx); } - static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt); + private static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt); public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){ return sqlite3_bind_parameter_count(stmt.getNativePointer()); @@ -309,7 +407,7 @@ public static int sqlite3_bind_parameter_index( return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8); } - static native String sqlite3_bind_parameter_name( + private static native String sqlite3_bind_parameter_name( @NotNull long ptrToStmt, int index ); @@ -317,7 +415,7 @@ public static String sqlite3_bind_parameter_name(@NotNull sqlite3_stmt stmt, int return sqlite3_bind_parameter_name(stmt.getNativePointer(), index); } - static native int sqlite3_bind_text( + private static native int sqlite3_bind_text( @NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes ); @@ -361,7 +459,7 @@ public static int sqlite3_bind_text( : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length); } - static native int sqlite3_bind_text16( + private static native int sqlite3_bind_text16( @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes ); @@ -402,7 +500,7 @@ public static int sqlite3_bind_text16( : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length); } - static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue); + private static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue); /** Functions like the C-level sqlite3_bind_value(), or @@ -413,13 +511,13 @@ public static int sqlite3_bind_value(@NotNull sqlite3_stmt stmt, int ndx, sqlite null==val ? 0L : val.getNativePointer()); } - static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n); + private static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n); public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){ return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n); } - static native int sqlite3_bind_zeroblob64( + private static native int sqlite3_bind_zeroblob64( @NotNull long ptrToStmt, int ndx, long n ); @@ -427,19 +525,19 @@ public static int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, l return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n); } - static native int sqlite3_blob_bytes(@NotNull long ptrToBlob); + private static native int sqlite3_blob_bytes(@NotNull long ptrToBlob); public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){ return sqlite3_blob_bytes(blob.getNativePointer()); } - static native int sqlite3_blob_close(@Nullable long ptrToBlob); + private static native int sqlite3_blob_close(@Nullable long ptrToBlob); public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){ - return sqlite3_blob_close(blob.clearNativePointer()); + return null==blob ? 0 : sqlite3_blob_close(blob.clearNativePointer()); } - static native int sqlite3_blob_open( + private static native int sqlite3_blob_open( @NotNull long ptrToDb, @NotNull String dbName, @NotNull String tableName, @NotNull String columnName, long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out @@ -467,17 +565,125 @@ public static sqlite3_blob sqlite3_blob_open( return out.take(); }; - static native int sqlite3_blob_read( - @NotNull long ptrToBlob, @NotNull byte[] target, int iOffset + private static native int sqlite3_blob_read( + @NotNull long ptrToBlob, @NotNull byte[] target, int srcOffset ); + /** + As per C's sqlite3_blob_read(), but writes its output to the + given byte array. Note that the final argument is the offset of + the source buffer, not the target array. + */ public static int sqlite3_blob_read( - @NotNull sqlite3_blob b, @NotNull byte[] target, int iOffset + @NotNull sqlite3_blob src, @NotNull byte[] target, int srcOffset ){ - return sqlite3_blob_read(b.getNativePointer(), target, iOffset); + return sqlite3_blob_read(src.getNativePointer(), target, srcOffset); } - static native int sqlite3_blob_reopen( + /** + An internal level of indirection. + */ + @Experimental + private static native int sqlite3_blob_read_nio_buffer( + @NotNull long ptrToBlob, int srcOffset, + @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany + ); + + /** + Reads howMany bytes from offset srcOffset of src into position + tgtOffset of tgt. + + Returns SQLITE_MISUSE if src is null, tgt is null, or + sqlite3_jni_supports_nio() returns false. Returns SQLITE_ERROR if + howMany or either offset are negative. If argument validation + succeeds, it returns the result of the underlying call to + sqlite3_blob_read() (0 on success). + */ + @Experimental + /*public*/ static int sqlite3_blob_read_nio_buffer( + @NotNull sqlite3_blob src, int srcOffset, + @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany + ){ + return (JNI_SUPPORTS_NIO && src!=null && tgt!=null) + ? sqlite3_blob_read_nio_buffer( + src.getNativePointer(), srcOffset, tgt, tgtOffset, howMany + ) + : SQLITE_MISUSE; + } + + /** + Convenience overload which reads howMany bytes from position + srcOffset of src and returns the result as a new ByteBuffer. + + srcOffset may not be negative. If howMany is negative, it is + treated as all bytes following srcOffset. + + Returns null if sqlite3_jni_supports_nio(), any arguments are + invalid, if the number of bytes to read is 0 or is larger than + the src blob, or the underlying call to sqlite3_blob_read() fails + for any reason. + */ + @Experimental + /*public*/ static java.nio.ByteBuffer sqlite3_blob_read_nio_buffer( + @NotNull sqlite3_blob src, int srcOffset, int howMany + ){ + if( !JNI_SUPPORTS_NIO || src==null ) return null; + else if( srcOffset<0 ) return null; + final int nB = sqlite3_blob_bytes(src); + if( srcOffset>=nB ) return null; + else if( howMany<0 ) howMany = nB - srcOffset; + if( srcOffset + howMany > nB ) return null; + final java.nio.ByteBuffer tgt = + java.nio.ByteBuffer.allocateDirect(howMany); + final int rc = sqlite3_blob_read_nio_buffer( + src.getNativePointer(), srcOffset, tgt, 0, howMany + ); + return 0==rc ? tgt : null; + } + + /** + Overload alias for sqlite3_blob_read_nio_buffer(). + */ + @Experimental + /*public*/ static int sqlite3_blob_read( + @NotNull sqlite3_blob src, int srcOffset, + @NotNull java.nio.ByteBuffer tgt, + int tgtOffset, int howMany + ){ + return sqlite3_blob_read_nio_buffer( + src, srcOffset, tgt, tgtOffset, howMany + ); + } + + /** + Convenience overload which uses 0 for both src and tgt offsets + and reads a number of bytes equal to the smaller of + sqlite3_blob_bytes(src) and tgt.limit(). + + On success it sets tgt.limit() to the number of bytes read. On + error, tgt.limit() is not modified. + + Returns 0 on success. Returns SQLITE_MISUSE is either argument is + null or sqlite3_jni_supports_nio() returns false. Else it returns + the result of the underlying call to sqlite3_blob_read(). + */ + @Experimental + /*public*/ static int sqlite3_blob_read( + @NotNull sqlite3_blob src, + @NotNull java.nio.ByteBuffer tgt + ){ + if(!JNI_SUPPORTS_NIO || src==null || tgt==null) return SQLITE_MISUSE; + final int nSrc = sqlite3_blob_bytes(src); + final int nTgt = tgt.limit(); + final int nRead = nTgt T sqlite3_column_java_object( + @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class type + ){ + final Object o = sqlite3_column_java_object(stmt, ndx); + return type.isInstance(o) ? (T)o : null; } - static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx); + private static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx); - public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){ - return sqlite3_column_database_name(stmt.getNativePointer(), ndx); + public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){ + return sqlite3_column_name(stmt.getNativePointer(), ndx); } - static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx); + /** + A variant of sqlite3_column_blob() which returns the blob as a + ByteBuffer object. Returns null if its argument is null, if + sqlite3_jni_supports_nio() is false, or if sqlite3_column_blob() + would return null for the same inputs. + */ + @Experimental + /*public*/ static native java.nio.ByteBuffer sqlite3_column_nio_buffer( + @NotNull sqlite3_stmt stmt, int ndx + ); + private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx); + + /** + Only available if built with SQLITE_ENABLE_COLUMN_METADATA. + */ public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_origin_name(stmt.getNativePointer(), ndx); } - static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx); + private static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx); + /** + Only available if built with SQLITE_ENABLE_COLUMN_METADATA. + */ public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_table_name(stmt.getNativePointer(), ndx); } @@ -672,7 +1001,7 @@ public static native String sqlite3_column_text16( // return rv; // } - static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx); + private static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx); public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_type(stmt.getNativePointer(), ndx); @@ -682,7 +1011,7 @@ public static native sqlite3_value sqlite3_column_value( @NotNull sqlite3_stmt stmt, int ndx ); - static native int sqlite3_collation_needed( + private static native int sqlite3_collation_needed( @NotNull long ptrToDb, @Nullable CollationNeededCallback callback ); @@ -696,7 +1025,7 @@ public static int sqlite3_collation_needed( return sqlite3_collation_needed(db.getNativePointer(), callback); } - static native CommitHookCallback sqlite3_commit_hook( + private static native CommitHookCallback sqlite3_commit_hook( @NotNull long ptrToDb, @Nullable CommitHookCallback hook ); @@ -726,6 +1055,24 @@ public static int sqlite3_complete(@NotNull String sql){ return sqlite3_complete( nulTerminateUtf8(sql) ); } + /** + Internal level of indirection for sqlite3_config(int). + */ + private static native int sqlite3_config__enable(int op); + + /** + Internal level of indirection for sqlite3_config(ConfigLogCallback). + */ + private static native int sqlite3_config__CONFIG_LOG( + @Nullable ConfigLogCallback logger + ); + + /** + Internal level of indirection for sqlite3_config(ConfigSqlLogCallback). + */ + private static native int sqlite3_config__SQLLOG( + @Nullable ConfigSqlLogCallback logger + ); /**

    Works like in the C API with the exception that it only supports @@ -742,12 +1089,14 @@

    Note that sqlite3_config() is not threadsafe with regards to the rest of the library. This must not be called when any other library APIs are being called. */ - public static native int sqlite3_config(int op); + public static int sqlite3_config(int op){ + return sqlite3_config__enable(op); + } /** If the native library was built with SQLITE_ENABLE_SQLLOG defined then this acts as a proxy for C's - sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the + sqlite3_config(SQLITE_CONFIG_SQLLOG,...). This sets or clears the logger. If installation of a logger fails, any previous logger is retained. @@ -758,13 +1107,17 @@

    Note that sqlite3_config() is not threadsafe with regards to the rest of the library. This must not be called when any other library APIs are being called. */ - public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger ); + public static int sqlite3_config( @Nullable ConfigSqlLogCallback logger ){ + return sqlite3_config__SQLLOG(logger); + } /** The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG option. */ - public static native int sqlite3_config( @Nullable ConfigLogCallback logger ); + public static int sqlite3_config( @Nullable ConfigLogCallback logger ){ + return sqlite3_config__CONFIG_LOG(logger); + } /** Unlike the C API, this returns null if its argument is @@ -795,7 +1148,7 @@ public static native int sqlite3_create_function( int nArg, int eTextRep, @NotNull SQLFunction func ); - static native int sqlite3_data_count(@NotNull long ptrToStmt); + private static native int sqlite3_data_count(@NotNull long ptrToStmt); public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){ return sqlite3_data_count(stmt.getNativePointer()); @@ -807,7 +1160,7 @@ Overload for sqlite3_db_config() calls which take (int,int*) SQLITE_DBCONFIG_... options which uses this call form.

    Unlike the C API, this returns SQLITE_MISUSE if its db argument - are null (as opposed to invoking UB). + is null (as opposed to invoking UB). */ public static native int sqlite3_db_config( @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out @@ -830,7 +1183,6 @@ public static String sqlite3_db_name(@NotNull sqlite3 db, int ndx){ return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx); } - public static native String sqlite3_db_filename( @NotNull sqlite3 db, @NotNull String dbName ); @@ -850,7 +1202,7 @@ public static native int sqlite3_db_status( public static native String sqlite3_errmsg(@NotNull sqlite3 db); - static native int sqlite3_error_offset(@NotNull long ptrToDb); + private static native int sqlite3_error_offset(@NotNull long ptrToDb); /** Note that the returned byte offset values assume UTF-8-encoded @@ -864,17 +1216,17 @@ public static int sqlite3_error_offset(@NotNull sqlite3 db){ public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); - static native int sqlite3_extended_errcode(@NotNull long ptrToDb); + private static native int sqlite3_extended_errcode(@NotNull long ptrToDb); public static int sqlite3_extended_errcode(@NotNull sqlite3 db){ return sqlite3_extended_errcode(db.getNativePointer()); } - public static native boolean sqlite3_extended_result_codes( - @NotNull sqlite3 db, boolean onoff + public static native int sqlite3_extended_result_codes( + @NotNull sqlite3 db, boolean on ); - static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb); + private static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb); public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){ return sqlite3_get_autocommit(db.getNativePointer()); @@ -884,7 +1236,7 @@ public static native Object sqlite3_get_auxdata( @NotNull sqlite3_context cx, int n ); - static native int sqlite3_finalize(long ptrToStmt); + private static native int sqlite3_finalize(long ptrToStmt); public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){ return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer()); @@ -1166,9 +1518,13 @@ A convenience wrapper around sqlite3_prepare_v3() which accepts array. It loops over the input bytes looking for statements. Each one it finds is passed to p.call(), passing ownership of it to that function. If p.call() returns 0, looping - continues, else the loop stops. + continues, else the loop stops and p.call()'s result code is + returned. If preparation of any given segment fails, looping + stops and that result code is returned. -

    If p.call() throws, the exception is propagated. +

    If p.call() throws, the exception is converted to a db-level + error and a non-0 code is returned, in order to retain the + C-style error semantics of the API.

    How each statement is handled, including whether it is finalized or not, is up to the callback object. e.g. the callback might @@ -1178,29 +1534,33 @@ A convenience wrapper around sqlite3_prepare_v3() which accepts */ public static int sqlite3_prepare_multi( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - int preFlags, + int prepFlags, @NotNull PrepareMultiCallback p){ final OutputPointer.Int32 oTail = new OutputPointer.Int32(); int pos = 0, n = 1; byte[] sqlChunk = sqlUtf8; int rc = 0; final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); - while(0==rc && pos 0){ + if( pos>0 ){ sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); } if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail); + rc = sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail); if( 0!=rc ) break; pos = oTail.value; stmt = outStmt.take(); - if( null == stmt ){ - // empty statement was parsed. + if( null==stmt ){ + // empty statement (whitespace/comments) continue; } - rc = p.call(stmt); + try{ + rc = p.call(stmt); + }catch(Exception e){ + rc = sqlite3_jni_db_error( db, SQLITE_ERROR, e ); + } } return rc; } @@ -1256,7 +1616,7 @@ public static int sqlite3_prepare_multi( return sqlite3_prepare_multi(db, sql, 0, p); } - static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb); + private static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this @@ -1267,7 +1627,7 @@ public static int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db){ return sqlite3_preupdate_blobwrite(db.getNativePointer()); } - static native int sqlite3_preupdate_count(@NotNull long ptrToDb); + private static native int sqlite3_preupdate_count(@NotNull long ptrToDb); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this @@ -1278,7 +1638,7 @@ public static int sqlite3_preupdate_count(@NotNull sqlite3 db){ return sqlite3_preupdate_count(db.getNativePointer()); } - static native int sqlite3_preupdate_depth(@NotNull long ptrToDb); + private static native int sqlite3_preupdate_depth(@NotNull long ptrToDb); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this @@ -1289,7 +1649,7 @@ public static int sqlite3_preupdate_depth(@NotNull sqlite3 db){ return sqlite3_preupdate_depth(db.getNativePointer()); } - static native PreupdateHookCallback sqlite3_preupdate_hook( + private static native PreupdateHookCallback sqlite3_preupdate_hook( @NotNull long ptrToDb, @Nullable PreupdateHookCallback hook ); @@ -1304,13 +1664,22 @@ public static PreupdateHookCallback sqlite3_preupdate_hook( return sqlite3_preupdate_hook(db.getNativePointer(), hook); } - static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col, + private static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col, @NotNull OutputPointer.sqlite3_value out); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this acts as a proxy for C's sqlite3_preupdate_new(), else it returns SQLITE_MISUSE with no side effects. + + WARNING: client code _must not_ hold a reference to the returned + sqlite3_value object beyond the scope of the preupdate hook in + which this function is called. Doing so will leave the client + holding a stale pointer, the address of which could point to + anything at all after the pre-update hook is complete. This API + has no way to record such objects and clear/invalidate them at + the end of a pre-update hook. We "could" add infrastructure to do + so, but would require significant levels of bookkeeping. */ public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col, @NotNull OutputPointer.sqlite3_value out){ @@ -1327,13 +1696,16 @@ public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){ return out.take(); } - static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col, + private static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col, @NotNull OutputPointer.sqlite3_value out); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this acts as a proxy for C's sqlite3_preupdate_old(), else it returns SQLITE_MISUSE with no side effects. + + WARNING: see warning in sqlite3_preupdate_new() regarding the + potential for stale sqlite3_value handles. */ public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col, @NotNull OutputPointer.sqlite3_value out){ @@ -1378,7 +1750,7 @@ The main sqlite3_result_error() impl of which all others are results in the C-level sqlite3_result_error() being called with a complaint about the invalid argument. */ - static native void sqlite3_result_error( + private static native void sqlite3_result_error( @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep ); @@ -1432,10 +1804,6 @@ public static native void sqlite3_result_error_code( @NotNull sqlite3_context cx, int c ); - public static native void sqlite3_result_null( - @NotNull sqlite3_context cx - ); - public static native void sqlite3_result_int( @NotNull sqlite3_context cx, int v ); @@ -1454,9 +1822,6 @@ but that function is not exposed to JNI because (A) cross-language semantic mismatch and (B) Java doesn't need that argument for its intended purpose (type safety). -

    Note that there is no sqlite3_column_java_object(), as the - C-level API has no sqlite3_column_pointer() to proxy. - @see #sqlite3_value_java_object @see #sqlite3_bind_java_object */ @@ -1464,6 +1829,45 @@ public static native void sqlite3_result_java_object( @NotNull sqlite3_context cx, @NotNull Object o ); + /** + Similar to sqlite3_bind_nio_buffer(), this works like + sqlite3_result_blob() but accepts a java.nio.ByteBuffer as its + input source. See sqlite3_bind_nio_buffer() for the semantics of + the second and subsequent arguments. + + If cx is null then this function will silently fail. If + sqlite3_jni_supports_nio() returns false or iBegin is negative, + an error result is set. If (begin+n) extends beyond the end of + the buffer, it is silently truncated to fit. + + If any of the following apply, this function behaves like + sqlite3_result_null(): the blob is null, the resulting slice of + the blob is empty. + + If the resulting slice of the buffer exceeds SQLITE_LIMIT_LENGTH + then this function behaves like sqlite3_result_error_toobig(). + */ + @Experimental + /*public*/ static native void sqlite3_result_nio_buffer( + @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob, + int begin, int n + ); + + /** + Convenience overload which uses the whole input object + as the result blob content. + */ + @Experimental + /*public*/ static void sqlite3_result_nio_buffer( + @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob + ){ + sqlite3_result_nio_buffer(cx, blob, 0, -1); + } + + public static native void sqlite3_result_null( + @NotNull sqlite3_context cx + ); + public static void sqlite3_result_set( @NotNull sqlite3_context cx, @NotNull Boolean v ){ @@ -1524,6 +1928,10 @@ public static void sqlite3_result_set( else sqlite3_result_blob(cx, blob, blob.length); } + public static native void sqlite3_result_subtype( + @NotNull sqlite3_context cx, int val + ); + public static native void sqlite3_result_value( @NotNull sqlite3_context cx, @NotNull sqlite3_value v ); @@ -1550,6 +1958,29 @@ public static void sqlite3_result_blob( sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); } + /** + Convenience overload which behaves like + sqlite3_result_nio_buffer(). + */ + @Experimental + /*public*/ static void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob, + int begin, int n + ){ + sqlite3_result_nio_buffer(cx, blob, begin, n); + } + + /** + Convenience overload which behaves like the two-argument overload of + sqlite3_result_nio_buffer(). + */ + @Experimental + /*public*/ static void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob + ){ + sqlite3_result_nio_buffer(cx, blob); + } + /** Binds the given text using C's sqlite3_result_blob64() unless: @@ -1607,7 +2038,8 @@ public static void sqlite3_result_text(

      -
    • text is null: translates to a call to sqlite3_result_null()
    • +
    • text is null: translates to a call to {@link + #sqlite3_result_null}
    • text is too large: translates to a call to {@link #sqlite3_result_error_toobig}
    • @@ -1651,7 +2083,7 @@ public static void sqlite3_result_text16( } } - static native RollbackHookCallback sqlite3_rollback_hook( + private static native RollbackHookCallback sqlite3_rollback_hook( @NotNull long ptrToDb, @Nullable RollbackHookCallback hook ); @@ -1703,20 +2135,24 @@ public static native int sqlite3_status64( @NotNull OutputPointer.Int64 pHighwater, boolean reset ); - public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); + private static native int sqlite3_step(@NotNull long ptrToStmt); + + public static int sqlite3_step(@NotNull sqlite3_stmt stmt){ + return null==stmt ? SQLITE_MISUSE : sqlite3_step(stmt.getNativePointer()); + } public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt); - static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op); + private static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op); public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){ - return sqlite3_stmt_explain(stmt.getNativePointer(), op); + return null==stmt ? SQLITE_MISUSE : sqlite3_stmt_explain(stmt.getNativePointer(), op); } - static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt); + private static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt); public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){ - return sqlite3_stmt_isexplain(stmt.getNativePointer()); + return null==stmt ? 0 : sqlite3_stmt_isexplain(stmt.getNativePointer()); } public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt); @@ -1737,7 +2173,7 @@ Internal impl of the public sqlite3_strglob() method. Neither signature is the public-facing one. */ private static native int sqlite3_strglob( - @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8 + @NotNull byte[] glob, @NotNull byte[] nulTerminatedUtf8 ); public static int sqlite3_strglob( @@ -1751,7 +2187,7 @@ public static int sqlite3_strglob( The LIKE counterpart of the private sqlite3_strglob() method. */ private static native int sqlite3_strlike( - @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8, + @NotNull byte[] glob, @NotNull byte[] nulTerminatedUtf8, int escChar ); @@ -1763,7 +2199,7 @@ public static int sqlite3_strlike( (int)escChar); } - static native int sqlite3_system_errno(@NotNull long ptrToDb); + private static native int sqlite3_system_errno(@NotNull long ptrToDb); public static int sqlite3_system_errno(@NotNull sqlite3 db){ return sqlite3_system_errno(db.getNativePointer()); @@ -1809,13 +2245,13 @@ public static TableColumnMetadata sqlite3_table_column_metadata( public static native int sqlite3_threadsafe(); - static native int sqlite3_total_changes(@NotNull long ptrToDb); + private static native int sqlite3_total_changes(@NotNull long ptrToDb); public static int sqlite3_total_changes(@NotNull sqlite3 db){ return sqlite3_total_changes(db.getNativePointer()); } - static native long sqlite3_total_changes64(@NotNull long ptrToDb); + private static native long sqlite3_total_changes64(@NotNull long ptrToDb); public static long sqlite3_total_changes64(@NotNull sqlite3 db){ return sqlite3_total_changes64(db.getNativePointer()); @@ -1838,7 +2274,7 @@ public static native int sqlite3_txn_state( @NotNull sqlite3 db, @Nullable String zSchema ); - static native UpdateHookCallback sqlite3_update_hook( + private static native UpdateHookCallback sqlite3_update_hook( @NotNull long ptrToDb, @Nullable UpdateHookCallback hook ); @@ -1858,67 +2294,67 @@ public static UpdateHookCallback sqlite3_update_hook( sqlite3_create_function(). */ - static native byte[] sqlite3_value_blob(@NotNull long ptrToValue); + private static native byte[] sqlite3_value_blob(@NotNull long ptrToValue); public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){ return sqlite3_value_blob(v.getNativePointer()); } - static native int sqlite3_value_bytes(@NotNull long ptrToValue); + private static native int sqlite3_value_bytes(@NotNull long ptrToValue); public static int sqlite3_value_bytes(@NotNull sqlite3_value v){ return sqlite3_value_bytes(v.getNativePointer()); } - static native int sqlite3_value_bytes16(@NotNull long ptrToValue); + private static native int sqlite3_value_bytes16(@NotNull long ptrToValue); public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){ return sqlite3_value_bytes16(v.getNativePointer()); } - static native double sqlite3_value_double(@NotNull long ptrToValue); + private static native double sqlite3_value_double(@NotNull long ptrToValue); public static double sqlite3_value_double(@NotNull sqlite3_value v){ return sqlite3_value_double(v.getNativePointer()); } - static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue); + private static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue); public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){ return sqlite3_value_dup(v.getNativePointer()); } - static native int sqlite3_value_encoding(@NotNull long ptrToValue); + private static native int sqlite3_value_encoding(@NotNull long ptrToValue); public static int sqlite3_value_encoding(@NotNull sqlite3_value v){ return sqlite3_value_encoding(v.getNativePointer()); } - static native void sqlite3_value_free(@Nullable long ptrToValue); + private static native void sqlite3_value_free(@Nullable long ptrToValue); public static void sqlite3_value_free(@Nullable sqlite3_value v){ - sqlite3_value_free(v.getNativePointer()); + if( null!=v ) sqlite3_value_free(v.clearNativePointer()); } - static native boolean sqlite3_value_frombind(@NotNull long ptrToValue); + private static native boolean sqlite3_value_frombind(@NotNull long ptrToValue); public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){ return sqlite3_value_frombind(v.getNativePointer()); } - static native int sqlite3_value_int(@NotNull long ptrToValue); + private static native int sqlite3_value_int(@NotNull long ptrToValue); public static int sqlite3_value_int(@NotNull sqlite3_value v){ return sqlite3_value_int(v.getNativePointer()); } - static native long sqlite3_value_int64(@NotNull long ptrToValue); + private static native long sqlite3_value_int64(@NotNull long ptrToValue); public static long sqlite3_value_int64(@NotNull sqlite3_value v){ return sqlite3_value_int64(v.getNativePointer()); } - static native Object sqlite3_value_java_object(@NotNull long ptrToValue); + private static native Object sqlite3_value_java_object(@NotNull long ptrToValue); /** If the given value was set using {@link @@ -1938,31 +2374,42 @@ A variant of sqlite3_value_java_object() which returns the given Class, else it returns null. */ @SuppressWarnings("unchecked") - public static T sqlite3_value_java_casted(@NotNull sqlite3_value v, + public static T sqlite3_value_java_object(@NotNull sqlite3_value v, @NotNull Class type){ final Object o = sqlite3_value_java_object(v); return type.isInstance(o) ? (T)o : null; } - static native int sqlite3_value_nochange(@NotNull long ptrToValue); + /** + A variant of sqlite3_column_blob() which returns the blob as a + ByteBuffer object. Returns null if its argument is null, if + sqlite3_jni_supports_nio() is false, or if sqlite3_value_blob() + would return null for the same input. + */ + @Experimental + /*public*/ static native java.nio.ByteBuffer sqlite3_value_nio_buffer( + @NotNull sqlite3_value v + ); + + private static native int sqlite3_value_nochange(@NotNull long ptrToValue); public static int sqlite3_value_nochange(@NotNull sqlite3_value v){ return sqlite3_value_nochange(v.getNativePointer()); } - static native int sqlite3_value_numeric_type(@NotNull long ptrToValue); + private static native int sqlite3_value_numeric_type(@NotNull long ptrToValue); public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){ return sqlite3_value_numeric_type(v.getNativePointer()); } - static native int sqlite3_value_subtype(@NotNull long ptrToValue); + private static native int sqlite3_value_subtype(@NotNull long ptrToValue); public static int sqlite3_value_subtype(@NotNull sqlite3_value v){ return sqlite3_value_subtype(v.getNativePointer()); } - static native byte[] sqlite3_value_text(@NotNull long ptrToValue); + private static native byte[] sqlite3_value_text(@NotNull long ptrToValue); /** Functions identially to the C API, and this note is just to @@ -1974,13 +2421,13 @@ public static byte[] sqlite3_value_text(@NotNull sqlite3_value v){ return sqlite3_value_text(v.getNativePointer()); } - static native String sqlite3_value_text16(@NotNull long ptrToValue); + private static native String sqlite3_value_text16(@NotNull long ptrToValue); public static String sqlite3_value_text16(@NotNull sqlite3_value v){ return sqlite3_value_text16(v.getNativePointer()); } - static native int sqlite3_value_type(@NotNull long ptrToValue); + private static native int sqlite3_value_type(@NotNull long ptrToValue); public static int sqlite3_value_type(@NotNull sqlite3_value v){ return sqlite3_value_type(v.getNativePointer()); @@ -2255,7 +2702,6 @@ public static int sqlite3_value_type(@NotNull sqlite3_value v){ // prepare flags public static final int SQLITE_PREPARE_PERSISTENT = 1; - public static final int SQLITE_PREPARE_NORMALIZE = 2; public static final int SQLITE_PREPARE_NO_VTAB = 4; // result codes @@ -2411,9 +2857,11 @@ public static int sqlite3_value_type(@NotNull sqlite3_value v){ public static final int SQLITE_TXN_WRITE = 2; // udf flags - public static final int SQLITE_DETERMINISTIC = 0x000000800; - public static final int SQLITE_DIRECTONLY = 0x000080000; - public static final int SQLITE_INNOCUOUS = 0x000200000; + public static final int SQLITE_DETERMINISTIC = 0x000000800; + public static final int SQLITE_DIRECTONLY = 0x000080000; + public static final int SQLITE_SUBTYPE = 0x000100000; + public static final int SQLITE_INNOCUOUS = 0x000200000; + public static final int SQLITE_RESULT_SUBTYPE = 0x001000000; // virtual tables public static final int SQLITE_INDEX_SCAN_UNIQUE = 1; @@ -2442,8 +2890,8 @@ public static int sqlite3_value_type(@NotNull sqlite3_value v){ public static final int SQLITE_FAIL = 3; public static final int SQLITE_REPLACE = 5; static { - // This MUST come after the SQLITE_MAX_... values or else - // attempting to modify them silently fails. init(); } + /* Must come after static init(). */ + private static final boolean JNI_SUPPORTS_NIO = sqlite3_jni_supports_nio(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java index 049570256..7df748e8d 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java +++ b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java @@ -24,8 +24,9 @@ callback interface on behalf of the C library. propagated. For callback interfaces which support returning error info to the core, the JNI binding will convert any exceptions to C-level error information. For callback interfaces which do not - support, all exceptions will necessarily be suppressed in order to - retain the C-style no-throw semantics. + support returning error information, all exceptions will + necessarily be suppressed in order to retain the C-style no-throw + semantics and avoid invoking undefined behavior in the C layer.

      Callbacks of this style follow a common naming convention: diff --git a/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java index fe61fe506..ffd7fa94a 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java @@ -21,8 +21,9 @@ public interface CollationNeededCallback extends CallbackProxy { Has the same semantics as the C-level sqlite3_create_collation() callback. -

      If it throws, the exception message is passed on to the db and - the exception is suppressed. +

      Because the C API has no mechanism for reporting errors + from this callbacks, any exceptions thrown by this callback + are suppressed. */ - int call(sqlite3 db, int eTextRep, String collationName); + void call(sqlite3 db, int eTextRep, String collationName); } diff --git a/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java index 24373bdf2..e1e55c78d 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java @@ -19,7 +19,8 @@ public interface CommitHookCallback extends CallbackProxy { /** Works as documented for the C-level sqlite3_commit_hook() - callback. Must not throw. + callback. If it throws, the exception is translated into + a db-level error. */ int call(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java b/ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java similarity index 86% rename from ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java rename to ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java index df753e651..a5530b49a 100644 --- a/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java @@ -16,10 +16,10 @@ /** A callback for use with sqlite3_config(). */ -public interface ConfigSqllogCallback { +public interface ConfigSqlLogCallback { /** Must function as described for a C-level callback for - {@link CApi#sqlite3_config(ConfigSqllogCallback)}, with the slight signature change. + {@link CApi#sqlite3_config(ConfigSqlLogCallback)}, with the slight signature change. */ void call(sqlite3 db, String msg, int msgType ); } diff --git a/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java b/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java index 60b902538..7bf7529da 100644 --- a/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java @@ -228,4 +228,26 @@ public static final class ByteArray { /** Sets the current value. */ public final void set(byte[] v){value = v;} } + + /** + Output pointer for use with native routines which return + blobs via java.nio.ByteBuffer. + + See {@link org.sqlite.jni.capi.CApi#sqlite3_jni_supports_nio} + */ + public static final class ByteBuffer { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public java.nio.ByteBuffer value; + /** Initializes with the value null. */ + public ByteBuffer(){this(null);} + /** Initializes with the value v. */ + public ByteBuffer(java.nio.ByteBuffer v){value = v;} + /** Returns the current value. */ + public final java.nio.ByteBuffer get(){return value;} + /** Sets the current value. */ + public final void set(java.nio.ByteBuffer v){value = v;} + } } diff --git a/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java b/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java index 1c805a9b1..9f6dd478c 100644 --- a/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java @@ -25,7 +25,10 @@ public interface PrepareMultiCallback extends CallbackProxy { sqlite3_prepare_multi() will _not_ finalize st - it is up to the call() implementation how st is handled. - Must return 0 on success or an SQLITE_... code on error. + Must return 0 on success or an SQLITE_... code on error. If it + throws, sqlite3_prepare_multi() will transform the exception into + a db-level error in order to retain the C-style error semantics + of the API. See the {@link Finalize} class for a wrapper which finalizes the statement after calling a proxy PrepareMultiCallback. @@ -37,7 +40,7 @@ to the call() implementation how st is handled. any sqlite3_stmt passed to its callback. */ public static final class Finalize implements PrepareMultiCallback { - private PrepareMultiCallback p; + private final PrepareMultiCallback p; /** p is the proxy to call() when this.call() is called. */ diff --git a/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java index 99d3fb035..38f7c5613 100644 --- a/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java @@ -19,7 +19,8 @@ public interface PreupdateHookCallback extends CallbackProxy { /** Must function as described for the C-level sqlite3_preupdate_hook() - callback. + callback. If it throws, the exception is translated to a + db-level error and the exception is suppressed. */ void call(sqlite3 db, int op, String dbName, String dbTable, long iKey1, long iKey2 ); diff --git a/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java index 5ce17e718..cf9c4b6e7 100644 --- a/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java @@ -18,8 +18,9 @@ */ public interface RollbackHookCallback extends CallbackProxy { /** - Works as documented for the C-level sqlite3_rollback_hook() - callback. + Must function as documented for the C-level sqlite3_rollback_hook() + callback. If it throws, the exception is translated into + a db-level error. */ void call(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java index 4806e2fc0..7ad1381a7 100644 --- a/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java @@ -33,71 +33,4 @@ */ public interface SQLFunction { - /** - PerContextState assists aggregate and window functions in - managing their accumulator state across calls to the UDF's - callbacks. - -

      T must be of a type which can be legally stored as a value in - java.util.HashMap. - -

      If a given aggregate or window function is called multiple times - in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., - then the clients need some way of knowing which call is which so - that they can map their state between their various UDF callbacks - and reset it via xFinal(). This class takes care of such - mappings. - -

      This class works by mapping - sqlite3_context.getAggregateContext() to a single piece of - state, of a client-defined type (the T part of this class), which - persists across a "matching set" of the UDF's callbacks. - -

      This class is a helper providing commonly-needed functionality - - it is not required for use with aggregate or window functions. - Client UDFs are free to perform such mappings using custom - approaches. The provided {@link AggregateFunction} and {@link - WindowFunction} classes use this. - */ - public static final class PerContextState { - private final java.util.Map> map - = new java.util.HashMap<>(); - - /** - Should be called from a UDF's xStep(), xValue(), and xInverse() - methods, passing it that method's first argument and an initial - value for the persistent state. If there is currently no - mapping for the given context within the map, one is created - using the given initial value, else the existing one is used - and the 2nd argument is ignored. It returns a ValueHolder - which can be used to modify that state directly without - requiring that the client update the underlying map's entry. - -

      The caller is obligated to eventually call - takeAggregateState() to clear the mapping. - */ - public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ - final Long key = cx.getAggregateContext(true); - ValueHolder rc = null==key ? null : map.get(key); - if( null==rc ){ - map.put(key, rc = new ValueHolder<>(initialValue)); - } - return rc; - } - - /** - Should be called from a UDF's xFinal() method and passed that - method's first argument. This function removes the value - associated with cx.getAggregateContext() from the map and - returns it, returning null if no other UDF method has been - called to set up such a mapping. The latter condition will be - the case if a UDF is used in a statement which has no result - rows. - */ - public T takeAggregateState(sqlite3_context cx){ - final ValueHolder h = map.remove(cx.getAggregateContext(false)); - return null==h ? null : h.value; - } - } - } diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java index 6fb28e65b..05b1cfeae 100644 --- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java @@ -38,6 +38,14 @@ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @interface SingleThreadOnly{} +/** + Annotation for Tester1 tests which must only be run if + sqlite3_jni_supports_nio() is true. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface RequiresJniNio{} + public class Tester1 implements Runnable { //! True when running in multi-threaded mode. private static boolean mtMode = false; @@ -46,7 +54,7 @@ public class Tester1 implements Runnable { //! True to shuffle the order of the tests. private static boolean shuffle = false; //! True to dump the list of to-run tests to stdout. - private static boolean listRunTests = false; + private static int listRunTests = 0; //! True to squelch all out() and outln() output. private static boolean quietMode = false; //! Total number of runTests() calls. @@ -327,7 +335,7 @@ private void testPrepare123(){ rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", - SQLITE_PREPARE_NORMALIZE, outStmt); + 0, outStmt); affirm(0 == rc); stmt = outStmt.get(); affirm(0 != stmt.getNativePointer()); @@ -382,6 +390,15 @@ private void testBindFetchInt(){ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); affirm( sqlite3_stmt_readonly(stmt) ); affirm( !sqlite3_stmt_busy(stmt) ); + if( sqlite3_compileoption_used("ENABLE_COLUMN_METADATA") ){ + /* Unlike in native C code, JNI won't trigger an + UnsatisfiedLinkError until these are called (on Linux, at + least). */ + affirm("t".equals(sqlite3_column_table_name(stmt,0))); + affirm("main".equals(sqlite3_column_database_name(stmt,0))); + affirm("a".equals(sqlite3_column_origin_name(stmt,0))); + } + int total2 = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ affirm( sqlite3_stmt_busy(stmt) ); @@ -477,6 +494,7 @@ private void testBindFetchText(){ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); StringBuilder sbuf = new StringBuilder(); n = 0; + final boolean tryNio = sqlite3_jni_supports_nio(); while( SQLITE_ROW == sqlite3_step(stmt) ){ final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0)); final String txt = sqlite3_column_text16(stmt, 0); @@ -491,6 +509,15 @@ private void testBindFetchText(){ StandardCharsets.UTF_8)) ); affirm( txt.length() == sqlite3_value_bytes16(sv)/2 ); affirm( txt.equals(sqlite3_value_text16(sv)) ); + if( tryNio ){ + java.nio.ByteBuffer bu = sqlite3_value_nio_buffer(sv); + byte ba[] = sqlite3_value_blob(sv); + affirm( ba.length == bu.capacity() ); + int i = 0; + for( byte b : ba ){ + affirm( b == bu.get(i++) ); + } + } sqlite3_value_free(sv); ++n; } @@ -548,6 +575,79 @@ private void testBindFetchBlob(){ sqlite3_close_v2(db); } + @RequiresJniNio + private void testBindByteBuffer(){ + /* TODO: these tests need to be much more extensive to check the + begin/end range handling. */ + + java.nio.ByteBuffer zeroCheck = + java.nio.ByteBuffer.allocateDirect(0); + affirm( null != zeroCheck ); + zeroCheck = null; + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + + final java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocateDirect(10); + buf.put((byte)0x31)/*note that we'll skip this one*/ + .put((byte)0x32) + .put((byte)0x33) + .put((byte)0x34) + .put((byte)0x35)/*we'll skip this one too*/; + + final int expectTotal = buf.get(1) + buf.get(2) + buf.get(3); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + affirm( SQLITE_ERROR == sqlite3_bind_blob(stmt, 1, buf, -1, 0), + "Buffer offset may not be negative." ); + affirm( 0 == sqlite3_bind_blob(stmt, 1, buf, 1, 3) ); + affirm( SQLITE_DONE == sqlite3_step(stmt) ); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t;"); + int total = 0; + affirm( SQLITE_ROW == sqlite3_step(stmt) ); + byte blob[] = sqlite3_column_blob(stmt, 0); + java.nio.ByteBuffer nioBlob = + sqlite3_column_nio_buffer(stmt, 0); + affirm(3 == blob.length); + affirm(blob.length == nioBlob.capacity()); + affirm(blob.length == nioBlob.limit()); + int i = 0; + for(byte b : blob){ + affirm( i<=3 ); + affirm(b == buf.get(1 + i)); + affirm(b == nioBlob.get(i)); + ++i; + total += b; + } + affirm( SQLITE_DONE == sqlite3_step(stmt) ); + sqlite3_finalize(stmt); + affirm(total == expectTotal); + + SQLFunction func = + new ScalarFunction(){ + public void xFunc(sqlite3_context cx, sqlite3_value[] args){ + sqlite3_result_blob(cx, buf, 1, 3); + } + }; + + affirm( 0 == sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func) ); + stmt = prepare(db, "SELECT myfunc()"); + affirm( SQLITE_ROW == sqlite3_step(stmt) ); + blob = sqlite3_column_blob(stmt, 0); + affirm(3 == blob.length); + i = 0; + total = 0; + for(byte b : blob){ + affirm( i<=3 ); + affirm(b == buf.get(1 + i++)); + total += b; + } + affirm( SQLITE_DONE == sqlite3_step(stmt) ); + sqlite3_finalize(stmt); + affirm(total == expectTotal); + + sqlite3_close_v2(db); + } + private void testSql(){ sqlite3 db = createNewDb(); sqlite3_stmt stmt = prepare(db, "SELECT 1"); @@ -593,9 +693,9 @@ public void xDestroy() { }; final CollationNeededCallback collLoader = new CollationNeededCallback(){ @Override - public int call(sqlite3 dbArg, int eTextRep, String collationName){ + public void call(sqlite3 dbArg, int eTextRep, String collationName){ affirm(dbArg == db/* as opposed to a temporary object*/); - return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); + sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); } }; int rc = sqlite3_collation_needed(db, collLoader); @@ -803,13 +903,17 @@ public void xFunc(sqlite3_context cx, sqlite3_value args[]){ affirm( 0==rc ); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ + affirm( testResult.value == sqlite3_column_java_object(stmt, 0) ); + affirm( testResult.value == sqlite3_column_java_object(stmt, 0, sqlite3.class) ); + affirm( null == sqlite3_column_java_object(stmt, 0, sqlite3_stmt.class) ); + affirm( null == sqlite3_column_java_object(stmt,1) ); final sqlite3_value v = sqlite3_column_value(stmt, 0); affirm( testResult.value == sqlite3_value_java_object(v) ); - affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) ); + affirm( testResult.value == sqlite3_value_java_object(v, sqlite3.class) ); affirm( testResult.value == - sqlite3_value_java_casted(v, testResult.value.getClass()) ); - affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) ); - affirm( null == sqlite3_value_java_casted(v, String.class) ); + sqlite3_value_java_object(v, testResult.value.getClass()) ); + affirm( testResult.value == sqlite3_value_java_object(v, Object.class) ); + affirm( null == sqlite3_value_java_object(v, String.class) ); ++n; } sqlite3_finalize(stmt); @@ -824,15 +928,28 @@ private void testUdfAggregate(){ // To confirm that xFinal() is called with no aggregate state // when the corresponding result set is empty. new ValueHolder<>(false); + final ValueHolder neverEverDoThisInClientCode = new ValueHolder<>(null); + final ValueHolder neverEverDoThisInClientCode2 = new ValueHolder<>(null); SQLFunction func = new AggregateFunction(){ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ + if( null==neverEverDoThisInClientCode.value ){ + /* !!!NEVER!!! hold a reference to an sqlite3_value or + sqlite3_context object like this in client code! They + are ONLY legal for the duration of their single + call. We do it here ONLY to test that the defenses + against clients doing this are working. */ + neverEverDoThisInClientCode.value = args; + } final ValueHolder agg = this.getAggregateState(cx, 0); agg.value += sqlite3_value_int(args[0]); affirm( agg == this.getAggregateState(cx, 0) ); } @Override public void xFinal(sqlite3_context cx){ + if( null==neverEverDoThisInClientCode2.value ){ + neverEverDoThisInClientCode2.value = cx; + } final Integer v = this.takeAggregateState(cx); if(null == v){ xFinalNull.value = true; @@ -857,6 +974,10 @@ public void xFinal(sqlite3_context cx){ } affirm( 1==n ); affirm(!xFinalNull.value); + affirm( null!=neverEverDoThisInClientCode.value ); + affirm( null!=neverEverDoThisInClientCode2.value ); + affirm( 0 xBusyCalled = new ValueHolder<>(0); - BusyHandlerCallback handler = new BusyHandlerCallback(){ - @Override public int call(int n){ - //outln("busy handler #"+n); - return n > 2 ? 0 : ++xBusyCalled.value; - } - }; - rc = sqlite3_busy_handler(db2, handler); - affirm(0 == rc); + int rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + final sqlite3 db1 = outDb.get(); + execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); + rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + affirm( outDb.get() != db1 ); + final sqlite3 db2 = outDb.get(); + + affirm( "main".equals( sqlite3_db_name(db1, 0) ) ); + rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); + affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); + affirm( "foo".equals( sqlite3_db_name(db1, 0) ) ); + affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) ); + + final ValueHolder xBusyCalled = new ValueHolder<>(0); + BusyHandlerCallback handler = new BusyHandlerCallback(){ + @Override public int call(int n){ + //outln("busy handler #"+n); + return n > 2 ? 0 : ++xBusyCalled.value; + } + }; + rc = sqlite3_busy_handler(db2, handler); + affirm(0 == rc); - // Force a locked condition... - execSql(db1, "BEGIN EXCLUSIVE"); - rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); - affirm( SQLITE_BUSY == rc); - affirm( null == outStmt.get() ); - affirm( 3 == xBusyCalled.value ); - sqlite3_close_v2(db1); - sqlite3_close_v2(db2); - try{ - final java.io.File f = new java.io.File(dbName); - f.delete(); - }catch(Exception e){ - /* ignore */ + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); + affirm( SQLITE_BUSY == rc); + affirm( null == outStmt.get() ); + affirm( 3 == xBusyCalled.value ); + sqlite3_close_v2(db1); + sqlite3_close_v2(db2); + }finally{ + try{(new java.io.File(dbName)).delete();} + catch(Exception e){/* ignore */} } } @@ -1093,6 +1213,7 @@ private void testProgress(){ private void testCommitHook(){ final sqlite3 db = createNewDb(); + sqlite3_extended_result_codes(db, true); final ValueHolder counter = new ValueHolder<>(0); final ValueHolder hookResult = new ValueHolder<>(0); final CommitHookCallback theHook = new CommitHookCallback(){ @@ -1135,7 +1256,7 @@ private void testCommitHook(){ affirm( 5 == counter.value ); hookResult.value = SQLITE_ERROR; int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); - affirm( SQLITE_CONSTRAINT == rc ); + affirm( SQLITE_CONSTRAINT_COMMITHOOK == rc ); affirm( 6 == counter.value ); sqlite3_close_v2(db); } @@ -1354,6 +1475,9 @@ public int call(int op, String s0, String s1, String s2, String s3){ authRc.value = SQLITE_DENY; int rc = execSql(db, false, "UPDATE t SET a=2"); affirm( SQLITE_AUTH==rc ); + sqlite3_set_authorizer(db, null); + rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( 0==rc ); // TODO: expand these tests considerably sqlite3_close(db); } @@ -1415,7 +1539,7 @@ private synchronized void testAutoExtension(){ val.value = 0; final AutoExtensionCallback ax2 = new AutoExtensionCallback(){ - @Override public synchronized int call(sqlite3 db){ + @Override public int call(sqlite3 db){ ++val.value; return 0; } @@ -1626,7 +1750,7 @@ private void testBlobOpen(){ sqlite3_finalize(stmt); b = sqlite3_blob_open(db, "main", "t", "a", - sqlite3_last_insert_rowid(db), 1); + sqlite3_last_insert_rowid(db), 0); affirm( null!=b ); rc = sqlite3_blob_reopen(b, 2); affirm( 0==rc ); @@ -1636,6 +1760,86 @@ private void testBlobOpen(){ affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" ); rc = sqlite3_blob_close(b); affirm( 0==rc ); + + if( !sqlite3_jni_supports_nio() ){ + outln("WARNING: skipping tests for ByteBuffer-using sqlite3_blob APIs ", + "because this platform lacks that support."); + sqlite3_close_v2(db); + return; + } + /* Sanity checks for the java.nio.ByteBuffer-taking overloads of + sqlite3_blob_read/write(). */ + execSql(db, "UPDATE t SET a=zeroblob(10)"); + b = sqlite3_blob_open(db, "main", "t", "a", 1, 1); + affirm( null!=b ); + java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocateDirect(10); + for( byte i = 0; i < 10; ++i ){ + bb.put((int)i, (byte)(48+i & 0xff)); + } + rc = sqlite3_blob_write(b, 1, bb, 1, 10); + affirm( rc==SQLITE_ERROR, "b length < (srcOffset + bb length)" ); + rc = sqlite3_blob_write(b, -1, bb); + affirm( rc==SQLITE_ERROR, "Target offset may not be negative" ); + rc = sqlite3_blob_write(b, 0, bb, -1, -1); + affirm( rc==SQLITE_ERROR, "Source offset may not be negative" ); + rc = sqlite3_blob_write(b, 1, bb, 1, 8); + affirm( rc==0 ); + // b's contents: 0 49 50 51 52 53 54 55 56 0 + // ascii: 0 '1' '2' '3' '4' '5' '6' '7' '8' 0 + byte br[] = new byte[10]; + java.nio.ByteBuffer bbr = + java.nio.ByteBuffer.allocateDirect(bb.limit()); + rc = sqlite3_blob_read( b, br, 0 ); + affirm( rc==0 ); + rc = sqlite3_blob_read( b, bbr ); + affirm( rc==0 ); + java.nio.ByteBuffer bbr2 = sqlite3_blob_read_nio_buffer(b, 0, 12); + affirm( null==bbr2, "Read size is too big"); + bbr2 = sqlite3_blob_read_nio_buffer(b, -1, 3); + affirm( null==bbr2, "Source offset is negative"); + bbr2 = sqlite3_blob_read_nio_buffer(b, 5, 6); + affirm( null==bbr2, "Read pos+size is too big"); + bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 7); + affirm( null==bbr2, "Read pos+size is too big"); + bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 6); + affirm( null!=bbr2 ); + java.nio.ByteBuffer bbr3 = + java.nio.ByteBuffer.allocateDirect(2 * bb.limit()); + java.nio.ByteBuffer bbr4 = + java.nio.ByteBuffer.allocateDirect(5); + rc = sqlite3_blob_read( b, bbr3 ); + affirm( rc==0 ); + rc = sqlite3_blob_read( b, bbr4 ); + affirm( rc==0 ); + affirm( sqlite3_blob_bytes(b)==bbr3.limit() ); + affirm( 5==bbr4.limit() ); + sqlite3_blob_close(b); + affirm( 0==br[0] ); + affirm( 0==br[9] ); + affirm( 0==bbr.get(0) ); + affirm( 0==bbr.get(9) ); + affirm( bbr2.limit() == 6 ); + affirm( 0==bbr3.get(0) ); + { + Exception ex = null; + try{ bbr3.get(11); } + catch(Exception e){ex = e;} + affirm( ex instanceof IndexOutOfBoundsException, + "bbr3.limit() was reset by read()" ); + ex = null; + } + affirm( 0==bbr4.get(0) ); + for( int i = 1; i < 9; ++i ){ + affirm( br[i] == 48 + i ); + affirm( br[i] == bbr.get(i) ); + affirm( br[i] == bbr3.get(i) ); + if( i>3 ){ + affirm( br[i] == bbr2.get(i-4) ); + } + if( i < bbr4.limit() ){ + affirm( br[i] == bbr4.get(i) ); + } + } sqlite3_close_v2(db); } @@ -1648,9 +1852,13 @@ private void testPrepareMulti(){ }; final List liStmt = new ArrayList(); final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll(); + final ValueHolder toss = new ValueHolder<>(null); PrepareMultiCallback m = new PrepareMultiCallback() { @Override public int call(sqlite3_stmt st){ liStmt.add(st); + if( null!=toss.value ){ + throw new RuntimeException(toss.value); + } return proxy.call(st); } }; @@ -1660,6 +1868,10 @@ private void testPrepareMulti(){ for( sqlite3_stmt st : liStmt ){ sqlite3_finalize(st); } + toss.value = "This is an exception."; + rc = sqlite3_prepare_multi(db, "SELECT 1", m); + affirm( SQLITE_ERROR==rc ); + affirm( sqlite3_errmsg(db).indexOf(toss.value)>0 ); sqlite3_close_v2(db); } @@ -1698,7 +1910,7 @@ private void runTests(boolean fromThread) throws Exception { mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); java.util.Collections.shuffle(mlist); } - if( listRunTests ){ + if( (!fromThread && listRunTests>0) || listRunTests>1 ){ synchronized(this.getClass()){ if( !fromThread ){ out("Initial test"," list: "); @@ -1760,8 +1972,11 @@ public void run() { -naps: sleep small random intervals between tests in order to add some chaos for cross-thread contention. + -list-tests: outputs the list of tests being run, minus some - which are hard-coded. This is noisy in multi-threaded mode. + which are hard-coded. In multi-threaded mode, use this twice to + to emit the list run by each thread (which may differ from the initial + list, in particular if -shuffle is used). -fail: forces an exception to be thrown during the test run. Use with -shuffle to make its appearance unpredictable. @@ -1790,7 +2005,7 @@ public static void main(String[] args) throws Exception { }else if(arg.equals("shuffle")){ shuffle = true; }else if(arg.equals("list-tests")){ - listRunTests = true; + ++listRunTests; }else if(arg.equals("fail")){ forceFail = true; }else if(arg.equals("sqllog")){ @@ -1809,7 +2024,7 @@ public static void main(String[] args) throws Exception { if( sqlLog ){ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){ - final ConfigSqllogCallback log = new ConfigSqllogCallback() { + final ConfigSqlLogCallback log = new ConfigSqlLogCallback() { @Override public void call(sqlite3 db, String msg, int op){ switch(op){ case 0: outln("Opening db: ",db); break; @@ -1820,7 +2035,7 @@ public static void main(String[] args) throws Exception { }; int rc = sqlite3_config( log ); affirm( 0==rc ); - rc = sqlite3_config( (ConfigSqllogCallback)null ); + rc = sqlite3_config( (ConfigSqlLogCallback)null ); affirm( 0==rc ); rc = sqlite3_config( log ); affirm( 0==rc ); @@ -1858,18 +2073,20 @@ public static void main(String[] args) throws Exception { if( forceFail ){ testMethods.add(m); } + }else if( m.isAnnotationPresent( RequiresJniNio.class ) + && !sqlite3_jni_supports_nio() ){ + outln("Skipping test for lack of JNI java.nio.ByteBuffer support: ", + name,"()\n"); + ++nSkipped; }else if( !m.isAnnotationPresent( ManualTest.class ) ){ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){ - if( 0==nSkipped++ ){ - out("Skipping tests in multi-thread mode:"); - } - out(" "+name+"()"); + out("Skipping test in multi-thread mode: ",name,"()\n"); + ++nSkipped; }else if( name.startsWith("test") ){ testMethods.add(m); } } } - if( nSkipped>0 ) out("\n"); } final long timeStart = System.currentTimeMillis(); @@ -1901,6 +2118,7 @@ public static void main(String[] args) throws Exception { sqlite3_libversion_number(),"\n", sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n", "SQLITE_THREADSAFE=",sqlite3_threadsafe()); + outln("JVM NIO support? ",sqlite3_jni_supports_nio() ? "YES" : "NO"); final boolean showLoopCount = (nRepeat>1 && nThread>1); if( showLoopCount ){ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); diff --git a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java index 33d72a5dd..e3d491f67 100644 --- a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java @@ -19,7 +19,8 @@ public interface UpdateHookCallback extends CallbackProxy { /** Must function as described for the C-level sqlite3_update_hook() - callback. + callback. If it throws, the exception is translated into + a db-level error. */ void call(int opId, String dbName, String tableName, long rowId); } diff --git a/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java index b3f03ac86..0a469fea9 100644 --- a/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java +++ b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java @@ -9,14 +9,16 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains a set of tests for the sqlite3 JNI bindings. +** This file contains the ValueHolder utility class for the sqlite3 +** JNI bindings. */ package org.sqlite.jni.capi; /** A helper class which simply holds a single value. Its primary use is for communicating values out of anonymous classes, as doing so - requires a "final" reference. + requires a "final" reference, as well as communicating aggregate + SQL function state across calls to such functions. */ public class ValueHolder { public T value; diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java index 901317f0e..cc6f2e6e8 100644 --- a/ext/jni/src/org/sqlite/jni/capi/sqlite3.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java @@ -38,6 +38,6 @@ public String toString(){ } @Override public void close(){ - CApi.sqlite3_close_v2(this.clearNativePointer()); + CApi.sqlite3_close_v2(this); } } diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java index 1b96c18b0..bdc0200af 100644 --- a/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java @@ -25,7 +25,6 @@ public final class sqlite3_blob extends NativePointerHolder private sqlite3_blob(){} @Override public void close(){ - CApi.sqlite3_blob_close(this.clearNativePointer()); + CApi.sqlite3_blob_close(this); } - } diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java index 3b8b71f8a..564891c72 100644 --- a/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java @@ -25,6 +25,6 @@ public final class sqlite3_stmt extends NativePointerHolder private sqlite3_stmt(){} @Override public void close(){ - CApi.sqlite3_finalize(this.clearNativePointer()); + CApi.sqlite3_finalize(this); } } diff --git a/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java b/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java index c4264c541..095e649ca 100644 --- a/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java @@ -625,10 +625,20 @@ private static void test2(){ "SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid", "[x, x, x y z, x z, x y z, x]" ); - do_execsql_test(db, - "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid", - "[null, null, null, null, null, null]" - ); + boolean threw = false; + try{ + /* columntext() used to return NULLs when given an out-of bounds column + but now results in a range error. */ + do_execsql_test(db, + "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid", + "[null, null, null, null, null, null]" + ); + }catch(Exception e){ + threw = true; + affirm( e.getMessage().matches(".*column index out of range") ); + } + affirm( threw ); + threw = false; /* Test fts5_columntotalsize() */ do_execsql_test(db, diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java index 173d775e6..fc63b5354 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java @@ -12,10 +12,6 @@ ** This file is part of the wrapper1 interface for sqlite3. */ package org.sqlite.jni.wrapper1; -import org.sqlite.jni.capi.CApi; -import org.sqlite.jni.annotation.*; -import org.sqlite.jni.capi.sqlite3_context; -import org.sqlite.jni.capi.sqlite3_value; /** EXPERIMENTAL/INCOMPLETE/UNTESTED @@ -51,9 +47,75 @@ arguments for xFinal() but will contain the context object needed */ public void xDestroy() {} + /** + PerContextState assists aggregate and window functions in + managing their accumulator state across calls to the UDF's + callbacks. + +

      T must be of a type which can be legally stored as a value in + java.util.HashMap. + +

      If a given aggregate or window function is called multiple times + in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., + then the clients need some way of knowing which call is which so + that they can map their state between their various UDF callbacks + and reset it via xFinal(). This class takes care of such + mappings. + +

      This class works by mapping + sqlite3_context.getAggregateContext() to a single piece of + state, of a client-defined type (the T part of this class), which + persists across a "matching set" of the UDF's callbacks. + +

      This class is a helper providing commonly-needed functionality + - it is not required for use with aggregate or window functions. + Client UDFs are free to perform such mappings using custom + approaches. The provided {@link AggregateFunction} and {@link + WindowFunction} classes use this. + */ + public static final class PerContextState { + private final java.util.Map> map + = new java.util.HashMap<>(); + + /** + Should be called from a UDF's xStep(), xValue(), and xInverse() + methods, passing it that method's first argument and an initial + value for the persistent state. If there is currently no + mapping for the given context within the map, one is created + using the given initial value, else the existing one is used + and the 2nd argument is ignored. It returns a ValueHolder + which can be used to modify that state directly without + requiring that the client update the underlying map's entry. + +

      The caller is obligated to eventually call + takeAggregateState() to clear the mapping. + */ + public ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){ + final Long key = args.getContext().getAggregateContext(true); + ValueHolder rc = null==key ? null : map.get(key); + if( null==rc ){ + map.put(key, rc = new ValueHolder<>(initialValue)); + } + return rc; + } + + /** + Should be called from a UDF's xFinal() method and passed that + method's first argument. This function removes the value + associated with with the arguments' aggregate context from the + map and returns it, returning null if no other UDF method has + been called to set up such a mapping. The latter condition will + be the case if a UDF is used in a statement which has no result + rows. + */ + public T takeAggregateState(SqlFunction.Arguments args){ + final ValueHolder h = map.remove(args.getContext().getAggregateContext(false)); + return null==h ? null : h.value; + } + } + /** Per-invocation state for the UDF. */ - private final SqlFunction.PerContextState map = - new SqlFunction.PerContextState<>(); + private final PerContextState map = new PerContextState<>(); /** To be called from the implementation's xStep() method, as well diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java index d6acda5aa..dcfc2ebeb 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java @@ -22,6 +22,14 @@ */ public interface SqlFunction { + public static final int DETERMINISTIC = CApi.SQLITE_DETERMINISTIC; + public static final int INNOCUOUS = CApi.SQLITE_INNOCUOUS; + public static final int DIRECTONLY = CApi.SQLITE_DIRECTONLY; + public static final int SUBTYPE = CApi.SQLITE_SUBTYPE; + public static final int RESULT_SUBTYPE = CApi.SQLITE_RESULT_SUBTYPE; + public static final int UTF8 = CApi.SQLITE_UTF8; + public static final int UTF16 = CApi.SQLITE_UTF16; + /** The Arguments type is an abstraction on top of the lower-level UDF function argument types. It provides _most_ of the functionality @@ -45,48 +53,10 @@ public final static class Arguments implements Iterable T getObjectCasted(Class type){ return a.getObjectCasted(ndx, type); } - public int getType(){return a.getType(ndx);} - public Object getAuxData(){return a.getAuxData(ndx);} - public void setAuxData(Object o){a.setAuxData(ndx, o);} - } - - @Override - public java.util.Iterator iterator(){ - final Arg[] proxies = new Arg[args.length]; - for( int i = 0; i < args.length; ++i ){ - proxies[i] = new Arg(this, i); - } - return java.util.Arrays.stream(proxies).iterator(); - } - /** Returns the sqlite3_value at the given argument index or throws an IllegalArgumentException exception if ndx is out of range. @@ -100,29 +70,40 @@ private sqlite3_value valueAt(int ndx){ return args[ndx]; } + //! Returns the underlying sqlite3_context for these arguments. sqlite3_context getContext(){return cx;} + /** + Returns the Sqlite (db) object associated with this UDF call, + or null if the UDF is somehow called without such an object or + the db has been closed in an untimely manner (e.g. closed by a + UDF call). + */ + public Sqlite getDb(){ + return Sqlite.fromNative( CApi.sqlite3_context_db_handle(cx) ); + } + public int getArgCount(){ return args.length; } - public int getInt(int arg){return CApi.sqlite3_value_int(valueAt(arg));} - public long getInt64(int arg){return CApi.sqlite3_value_int64(valueAt(arg));} - public double getDouble(int arg){return CApi.sqlite3_value_double(valueAt(arg));} - public byte[] getBlob(int arg){return CApi.sqlite3_value_blob(valueAt(arg));} - public byte[] getText(int arg){return CApi.sqlite3_value_text(valueAt(arg));} - public String getText16(int arg){return CApi.sqlite3_value_text16(valueAt(arg));} - public int getBytes(int arg){return CApi.sqlite3_value_bytes(valueAt(arg));} - public int getBytes16(int arg){return CApi.sqlite3_value_bytes16(valueAt(arg));} - public Object getObject(int arg){return CApi.sqlite3_value_java_object(valueAt(arg));} - public T getObjectCasted(int arg, Class type){ - return CApi.sqlite3_value_java_casted(valueAt(arg), type); + public int getInt(int argNdx){return CApi.sqlite3_value_int(valueAt(argNdx));} + public long getInt64(int argNdx){return CApi.sqlite3_value_int64(valueAt(argNdx));} + public double getDouble(int argNdx){return CApi.sqlite3_value_double(valueAt(argNdx));} + public byte[] getBlob(int argNdx){return CApi.sqlite3_value_blob(valueAt(argNdx));} + public byte[] getText(int argNdx){return CApi.sqlite3_value_text(valueAt(argNdx));} + public String getText16(int argNdx){return CApi.sqlite3_value_text16(valueAt(argNdx));} + public int getBytes(int argNdx){return CApi.sqlite3_value_bytes(valueAt(argNdx));} + public int getBytes16(int argNdx){return CApi.sqlite3_value_bytes16(valueAt(argNdx));} + public Object getObject(int argNdx){return CApi.sqlite3_value_java_object(valueAt(argNdx));} + public T getObject(int argNdx, Class type){ + return CApi.sqlite3_value_java_object(valueAt(argNdx), type); } - public int getType(int arg){return CApi.sqlite3_value_type(valueAt(arg));} - public int getSubtype(int arg){return CApi.sqlite3_value_subtype(valueAt(arg));} - public int getNumericType(int arg){return CApi.sqlite3_value_numeric_type(valueAt(arg));} - public int getNoChange(int arg){return CApi.sqlite3_value_nochange(valueAt(arg));} - public boolean getFromBind(int arg){return CApi.sqlite3_value_frombind(valueAt(arg));} - public int getEncoding(int arg){return CApi.sqlite3_value_encoding(valueAt(arg));} + public int getType(int argNdx){return CApi.sqlite3_value_type(valueAt(argNdx));} + public int getSubtype(int argNdx){return CApi.sqlite3_value_subtype(valueAt(argNdx));} + public int getNumericType(int argNdx){return CApi.sqlite3_value_numeric_type(valueAt(argNdx));} + public int getNoChange(int argNdx){return CApi.sqlite3_value_nochange(valueAt(argNdx));} + public boolean getFromBind(int argNdx){return CApi.sqlite3_value_frombind(valueAt(argNdx));} + public int getEncoding(int argNdx){return CApi.sqlite3_value_encoding(valueAt(argNdx));} public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); } public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); } @@ -133,7 +114,12 @@ public T getObjectCasted(int arg, Class type){ public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);} public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);} public void resultNull(){CApi.sqlite3_result_null(cx);} + /** + Analog to sqlite3_result_value(), using the Value object at the + given argument index. + */ public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));} + public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);} public void resultZeroBlob(long n){ // Throw on error? If n is too big, // sqlite3_result_error_toobig() is automatically called. @@ -146,88 +132,75 @@ public void resultZeroBlob(long n){ public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);} public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);} - public void setAuxData(int arg, Object o){ + /** + Callbacks should invoke this on OOM errors, instead of throwing + OutOfMemoryError, because the latter cannot be propagated + through the C API. + */ + public void resultNoMem(){CApi.sqlite3_result_error_nomem(cx);} + + /** + Analog to sqlite3_set_auxdata() but throws if argNdx is out of + range. + */ + public void setAuxData(int argNdx, Object o){ /* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html The value of the N parameter to these interfaces should be non-negative. Future enhancements may make use of negative N values to define new kinds of function caching behavior. */ - valueAt(arg); - CApi.sqlite3_set_auxdata(cx, arg, o); + valueAt(argNdx); + CApi.sqlite3_set_auxdata(cx, argNdx, o); } - public Object getAuxData(int arg){ - valueAt(arg); - return CApi.sqlite3_get_auxdata(cx, arg); + /** + Analog to sqlite3_get_auxdata() but throws if argNdx is out of + range. + */ + public Object getAuxData(int argNdx){ + valueAt(argNdx); + return CApi.sqlite3_get_auxdata(cx, argNdx); } - } - - /** - PerContextState assists aggregate and window functions in - managing their accumulator state across calls to the UDF's - callbacks. - -

      T must be of a type which can be legally stored as a value in - java.util.HashMap. - -

      If a given aggregate or window function is called multiple times - in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., - then the clients need some way of knowing which call is which so - that they can map their state between their various UDF callbacks - and reset it via xFinal(). This class takes care of such - mappings. - -

      This class works by mapping - sqlite3_context.getAggregateContext() to a single piece of - state, of a client-defined type (the T part of this class), which - persists across a "matching set" of the UDF's callbacks. - -

      This class is a helper providing commonly-needed functionality - - it is not required for use with aggregate or window functions. - Client UDFs are free to perform such mappings using custom - approaches. The provided {@link AggregateFunction} and {@link - WindowFunction} classes use this. - */ - public static final class PerContextState { - private final java.util.Map> map - = new java.util.HashMap<>(); /** - Should be called from a UDF's xStep(), xValue(), and xInverse() - methods, passing it that method's first argument and an initial - value for the persistent state. If there is currently no - mapping for the given context within the map, one is created - using the given initial value, else the existing one is used - and the 2nd argument is ignored. It returns a ValueHolder - which can be used to modify that state directly without - requiring that the client update the underlying map's entry. - -

      The caller is obligated to eventually call - takeAggregateState() to clear the mapping. + Represents a single SqlFunction argument. Primarily intended + for use with the Arguments class's Iterable interface. */ - public ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){ - final Long key = args.getContext().getAggregateContext(true); - ValueHolder rc = null==key ? null : map.get(key); - if( null==rc ){ - map.put(key, rc = new ValueHolder<>(initialValue)); + public final static class Arg { + private final Arguments a; + private final int ndx; + /* Only for use by the Arguments class. */ + private Arg(Arguments a, int ndx){ + this.a = a; + this.ndx = ndx; } - return rc; + /** Returns this argument's index in its parent argument list. */ + public int getIndex(){return ndx;} + public int getInt(){return a.getInt(ndx);} + public long getInt64(){return a.getInt64(ndx);} + public double getDouble(){return a.getDouble(ndx);} + public byte[] getBlob(){return a.getBlob(ndx);} + public byte[] getText(){return a.getText(ndx);} + public String getText16(){return a.getText16(ndx);} + public int getBytes(){return a.getBytes(ndx);} + public int getBytes16(){return a.getBytes16(ndx);} + public Object getObject(){return a.getObject(ndx);} + public T getObject(Class type){ return a.getObject(ndx, type); } + public int getType(){return a.getType(ndx);} + public Object getAuxData(){return a.getAuxData(ndx);} + public void setAuxData(Object o){a.setAuxData(ndx, o);} } - /** - Should be called from a UDF's xFinal() method and passed that - method's first argument. This function removes the value - associated with with the arguments' aggregate context from the - map and returns it, returning null if no other UDF method has - been called to set up such a mapping. The latter condition will - be the case if a UDF is used in a statement which has no result - rows. - */ - public T takeAggregateState(SqlFunction.Arguments args){ - final ValueHolder h = map.remove(args.getContext().getAggregateContext(false)); - return null==h ? null : h.value; + @Override + public java.util.Iterator iterator(){ + final Arg[] proxies = new Arg[args.length]; + for( int i = 0; i < args.length; ++i ){ + proxies[i] = new Arg(this, i); + } + return java.util.Arrays.stream(proxies).iterator(); } + } /** @@ -235,7 +208,7 @@ public T takeAggregateState(SqlFunction.Arguments args){ for use with the org.sqlite.jni.capi.ScalarFunction interface. */ static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction { - final ScalarFunction impl; + private final ScalarFunction impl; ScalarAdapter(ScalarFunction impl){ this.impl = impl; } @@ -261,8 +234,9 @@ public void xDestroy(){ Internal-use adapter for wrapping this package's AggregateFunction for use with the org.sqlite.jni.capi.AggregateFunction interface. */ - static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction { - final AggregateFunction impl; + static /*cannot be final without duplicating the whole body in WindowAdapter*/ + class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction { + private final AggregateFunction impl; AggregateAdapter(AggregateFunction impl){ this.impl = impl; } @@ -282,8 +256,9 @@ public void xStep(sqlite3_context cx, sqlite3_value[] args){ } /** - As for the xFinal() argument of the C API's sqlite3_create_function(). - If the proxied function throws, it is translated into a sqlite3_result_error(). + As for the xFinal() argument of the C API's + sqlite3_create_function(). If the proxied function throws, it + is translated into a sqlite3_result_error(). */ public void xFinal(sqlite3_context cx){ try{ @@ -298,4 +273,46 @@ public void xDestroy(){ } } + /** + Internal-use adapter for wrapping this package's WindowFunction + for use with the org.sqlite.jni.capi.WindowFunction interface. + */ + static final class WindowAdapter extends AggregateAdapter { + private final WindowFunction impl; + WindowAdapter(WindowFunction impl){ + super(impl); + this.impl = impl; + } + + /** + Proxies this.impl.xInverse(), adapting the call arguments to that + function's signature. If the proxied function throws, it is + translated to sqlite_result_error() with the exception's + message. + */ + public void xInverse(sqlite3_context cx, sqlite3_value[] args){ + try{ + impl.xInverse( new SqlFunction.Arguments(cx, args) ); + }catch(Exception e){ + CApi.sqlite3_result_error(cx, e); + } + } + + /** + As for the xValue() argument of the C API's sqlite3_create_window_function(). + If the proxied function throws, it is translated into a sqlite3_result_error(). + */ + public void xValue(sqlite3_context cx){ + try{ + impl.xValue( new SqlFunction.Arguments(cx, null) ); + }catch(Exception e){ + CApi.sqlite3_result_error(cx, e); + } + } + + public void xDestroy(){ + impl.xDestroy(); + } + } + } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java index bcf97b239..de131e854 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java @@ -13,11 +13,13 @@ */ package org.sqlite.jni.wrapper1; import java.nio.charset.StandardCharsets; -import static org.sqlite.jni.capi.CApi.*; import org.sqlite.jni.capi.CApi; import org.sqlite.jni.capi.sqlite3; import org.sqlite.jni.capi.sqlite3_stmt; +import org.sqlite.jni.capi.sqlite3_backup; +import org.sqlite.jni.capi.sqlite3_blob; import org.sqlite.jni.capi.OutputPointer; +import java.nio.ByteBuffer; /** This class represents a database connection, analog to the C-side @@ -28,12 +30,296 @@ */ public final class Sqlite implements AutoCloseable { private sqlite3 db; + private static final boolean JNI_SUPPORTS_NIO = + CApi.sqlite3_jni_supports_nio(); + + // Result codes + public static final int OK = CApi.SQLITE_OK; + public static final int ERROR = CApi.SQLITE_ERROR; + public static final int INTERNAL = CApi.SQLITE_INTERNAL; + public static final int PERM = CApi.SQLITE_PERM; + public static final int ABORT = CApi.SQLITE_ABORT; + public static final int BUSY = CApi.SQLITE_BUSY; + public static final int LOCKED = CApi.SQLITE_LOCKED; + public static final int NOMEM = CApi.SQLITE_NOMEM; + public static final int READONLY = CApi.SQLITE_READONLY; + public static final int INTERRUPT = CApi.SQLITE_INTERRUPT; + public static final int IOERR = CApi.SQLITE_IOERR; + public static final int CORRUPT = CApi.SQLITE_CORRUPT; + public static final int NOTFOUND = CApi.SQLITE_NOTFOUND; + public static final int FULL = CApi.SQLITE_FULL; + public static final int CANTOPEN = CApi.SQLITE_CANTOPEN; + public static final int PROTOCOL = CApi.SQLITE_PROTOCOL; + public static final int EMPTY = CApi.SQLITE_EMPTY; + public static final int SCHEMA = CApi.SQLITE_SCHEMA; + public static final int TOOBIG = CApi.SQLITE_TOOBIG; + public static final int CONSTRAINT = CApi. SQLITE_CONSTRAINT; + public static final int MISMATCH = CApi.SQLITE_MISMATCH; + public static final int MISUSE = CApi.SQLITE_MISUSE; + public static final int NOLFS = CApi.SQLITE_NOLFS; + public static final int AUTH = CApi.SQLITE_AUTH; + public static final int FORMAT = CApi.SQLITE_FORMAT; + public static final int RANGE = CApi.SQLITE_RANGE; + public static final int NOTADB = CApi.SQLITE_NOTADB; + public static final int NOTICE = CApi.SQLITE_NOTICE; + public static final int WARNING = CApi.SQLITE_WARNING; + public static final int ROW = CApi.SQLITE_ROW; + public static final int DONE = CApi.SQLITE_DONE; + public static final int ERROR_MISSING_COLLSEQ = CApi.SQLITE_ERROR_MISSING_COLLSEQ; + public static final int ERROR_RETRY = CApi.SQLITE_ERROR_RETRY; + public static final int ERROR_SNAPSHOT = CApi.SQLITE_ERROR_SNAPSHOT; + public static final int IOERR_READ = CApi.SQLITE_IOERR_READ; + public static final int IOERR_SHORT_READ = CApi.SQLITE_IOERR_SHORT_READ; + public static final int IOERR_WRITE = CApi.SQLITE_IOERR_WRITE; + public static final int IOERR_FSYNC = CApi.SQLITE_IOERR_FSYNC; + public static final int IOERR_DIR_FSYNC = CApi.SQLITE_IOERR_DIR_FSYNC; + public static final int IOERR_TRUNCATE = CApi.SQLITE_IOERR_TRUNCATE; + public static final int IOERR_FSTAT = CApi.SQLITE_IOERR_FSTAT; + public static final int IOERR_UNLOCK = CApi.SQLITE_IOERR_UNLOCK; + public static final int IOERR_RDLOCK = CApi.SQLITE_IOERR_RDLOCK; + public static final int IOERR_DELETE = CApi.SQLITE_IOERR_DELETE; + public static final int IOERR_BLOCKED = CApi.SQLITE_IOERR_BLOCKED; + public static final int IOERR_NOMEM = CApi.SQLITE_IOERR_NOMEM; + public static final int IOERR_ACCESS = CApi.SQLITE_IOERR_ACCESS; + public static final int IOERR_CHECKRESERVEDLOCK = CApi.SQLITE_IOERR_CHECKRESERVEDLOCK; + public static final int IOERR_LOCK = CApi.SQLITE_IOERR_LOCK; + public static final int IOERR_CLOSE = CApi.SQLITE_IOERR_CLOSE; + public static final int IOERR_DIR_CLOSE = CApi.SQLITE_IOERR_DIR_CLOSE; + public static final int IOERR_SHMOPEN = CApi.SQLITE_IOERR_SHMOPEN; + public static final int IOERR_SHMSIZE = CApi.SQLITE_IOERR_SHMSIZE; + public static final int IOERR_SHMLOCK = CApi.SQLITE_IOERR_SHMLOCK; + public static final int IOERR_SHMMAP = CApi.SQLITE_IOERR_SHMMAP; + public static final int IOERR_SEEK = CApi.SQLITE_IOERR_SEEK; + public static final int IOERR_DELETE_NOENT = CApi.SQLITE_IOERR_DELETE_NOENT; + public static final int IOERR_MMAP = CApi.SQLITE_IOERR_MMAP; + public static final int IOERR_GETTEMPPATH = CApi.SQLITE_IOERR_GETTEMPPATH; + public static final int IOERR_CONVPATH = CApi.SQLITE_IOERR_CONVPATH; + public static final int IOERR_VNODE = CApi.SQLITE_IOERR_VNODE; + public static final int IOERR_AUTH = CApi.SQLITE_IOERR_AUTH; + public static final int IOERR_BEGIN_ATOMIC = CApi.SQLITE_IOERR_BEGIN_ATOMIC; + public static final int IOERR_COMMIT_ATOMIC = CApi.SQLITE_IOERR_COMMIT_ATOMIC; + public static final int IOERR_ROLLBACK_ATOMIC = CApi.SQLITE_IOERR_ROLLBACK_ATOMIC; + public static final int IOERR_DATA = CApi.SQLITE_IOERR_DATA; + public static final int IOERR_CORRUPTFS = CApi.SQLITE_IOERR_CORRUPTFS; + public static final int LOCKED_SHAREDCACHE = CApi.SQLITE_LOCKED_SHAREDCACHE; + public static final int LOCKED_VTAB = CApi.SQLITE_LOCKED_VTAB; + public static final int BUSY_RECOVERY = CApi.SQLITE_BUSY_RECOVERY; + public static final int BUSY_SNAPSHOT = CApi.SQLITE_BUSY_SNAPSHOT; + public static final int BUSY_TIMEOUT = CApi.SQLITE_BUSY_TIMEOUT; + public static final int CANTOPEN_NOTEMPDIR = CApi.SQLITE_CANTOPEN_NOTEMPDIR; + public static final int CANTOPEN_ISDIR = CApi.SQLITE_CANTOPEN_ISDIR; + public static final int CANTOPEN_FULLPATH = CApi.SQLITE_CANTOPEN_FULLPATH; + public static final int CANTOPEN_CONVPATH = CApi.SQLITE_CANTOPEN_CONVPATH; + public static final int CANTOPEN_SYMLINK = CApi.SQLITE_CANTOPEN_SYMLINK; + public static final int CORRUPT_VTAB = CApi.SQLITE_CORRUPT_VTAB; + public static final int CORRUPT_SEQUENCE = CApi.SQLITE_CORRUPT_SEQUENCE; + public static final int CORRUPT_INDEX = CApi.SQLITE_CORRUPT_INDEX; + public static final int READONLY_RECOVERY = CApi.SQLITE_READONLY_RECOVERY; + public static final int READONLY_CANTLOCK = CApi.SQLITE_READONLY_CANTLOCK; + public static final int READONLY_ROLLBACK = CApi.SQLITE_READONLY_ROLLBACK; + public static final int READONLY_DBMOVED = CApi.SQLITE_READONLY_DBMOVED; + public static final int READONLY_CANTINIT = CApi.SQLITE_READONLY_CANTINIT; + public static final int READONLY_DIRECTORY = CApi.SQLITE_READONLY_DIRECTORY; + public static final int ABORT_ROLLBACK = CApi.SQLITE_ABORT_ROLLBACK; + public static final int CONSTRAINT_CHECK = CApi.SQLITE_CONSTRAINT_CHECK; + public static final int CONSTRAINT_COMMITHOOK = CApi.SQLITE_CONSTRAINT_COMMITHOOK; + public static final int CONSTRAINT_FOREIGNKEY = CApi.SQLITE_CONSTRAINT_FOREIGNKEY; + public static final int CONSTRAINT_FUNCTION = CApi.SQLITE_CONSTRAINT_FUNCTION; + public static final int CONSTRAINT_NOTNULL = CApi.SQLITE_CONSTRAINT_NOTNULL; + public static final int CONSTRAINT_PRIMARYKEY = CApi.SQLITE_CONSTRAINT_PRIMARYKEY; + public static final int CONSTRAINT_TRIGGER = CApi.SQLITE_CONSTRAINT_TRIGGER; + public static final int CONSTRAINT_UNIQUE = CApi.SQLITE_CONSTRAINT_UNIQUE; + public static final int CONSTRAINT_VTAB = CApi.SQLITE_CONSTRAINT_VTAB; + public static final int CONSTRAINT_ROWID = CApi.SQLITE_CONSTRAINT_ROWID; + public static final int CONSTRAINT_PINNED = CApi.SQLITE_CONSTRAINT_PINNED; + public static final int CONSTRAINT_DATATYPE = CApi.SQLITE_CONSTRAINT_DATATYPE; + public static final int NOTICE_RECOVER_WAL = CApi.SQLITE_NOTICE_RECOVER_WAL; + public static final int NOTICE_RECOVER_ROLLBACK = CApi.SQLITE_NOTICE_RECOVER_ROLLBACK; + public static final int WARNING_AUTOINDEX = CApi.SQLITE_WARNING_AUTOINDEX; + public static final int AUTH_USER = CApi.SQLITE_AUTH_USER; + public static final int OK_LOAD_PERMANENTLY = CApi.SQLITE_OK_LOAD_PERMANENTLY; + + // sqlite3_open() flags + public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE; + public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE; + public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE; + + // transaction state + public static final int TXN_NONE = CApi.SQLITE_TXN_NONE; + public static final int TXN_READ = CApi.SQLITE_TXN_READ; + public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE; + + // sqlite3_status() ops + public static final int STATUS_MEMORY_USED = CApi.SQLITE_STATUS_MEMORY_USED; + public static final int STATUS_PAGECACHE_USED = CApi.SQLITE_STATUS_PAGECACHE_USED; + public static final int STATUS_PAGECACHE_OVERFLOW = CApi.SQLITE_STATUS_PAGECACHE_OVERFLOW; + public static final int STATUS_MALLOC_SIZE = CApi.SQLITE_STATUS_MALLOC_SIZE; + public static final int STATUS_PARSER_STACK = CApi.SQLITE_STATUS_PARSER_STACK; + public static final int STATUS_PAGECACHE_SIZE = CApi.SQLITE_STATUS_PAGECACHE_SIZE; + public static final int STATUS_MALLOC_COUNT = CApi.SQLITE_STATUS_MALLOC_COUNT; + + // sqlite3_db_status() ops + public static final int DBSTATUS_LOOKASIDE_USED = CApi.SQLITE_DBSTATUS_LOOKASIDE_USED; + public static final int DBSTATUS_CACHE_USED = CApi.SQLITE_DBSTATUS_CACHE_USED; + public static final int DBSTATUS_SCHEMA_USED = CApi.SQLITE_DBSTATUS_SCHEMA_USED; + public static final int DBSTATUS_STMT_USED = CApi.SQLITE_DBSTATUS_STMT_USED; + public static final int DBSTATUS_LOOKASIDE_HIT = CApi.SQLITE_DBSTATUS_LOOKASIDE_HIT; + public static final int DBSTATUS_LOOKASIDE_MISS_SIZE = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE; + public static final int DBSTATUS_LOOKASIDE_MISS_FULL = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL; + public static final int DBSTATUS_CACHE_HIT = CApi.SQLITE_DBSTATUS_CACHE_HIT; + public static final int DBSTATUS_CACHE_MISS = CApi.SQLITE_DBSTATUS_CACHE_MISS; + public static final int DBSTATUS_CACHE_WRITE = CApi.SQLITE_DBSTATUS_CACHE_WRITE; + public static final int DBSTATUS_DEFERRED_FKS = CApi.SQLITE_DBSTATUS_DEFERRED_FKS; + public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED; + public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL; + + // Limits + public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH; + public static final int LIMIT_SQL_LENGTH = CApi.SQLITE_LIMIT_SQL_LENGTH; + public static final int LIMIT_COLUMN = CApi.SQLITE_LIMIT_COLUMN; + public static final int LIMIT_EXPR_DEPTH = CApi.SQLITE_LIMIT_EXPR_DEPTH; + public static final int LIMIT_COMPOUND_SELECT = CApi.SQLITE_LIMIT_COMPOUND_SELECT; + public static final int LIMIT_VDBE_OP = CApi.SQLITE_LIMIT_VDBE_OP; + public static final int LIMIT_FUNCTION_ARG = CApi.SQLITE_LIMIT_FUNCTION_ARG; + public static final int LIMIT_ATTACHED = CApi.SQLITE_LIMIT_ATTACHED; + public static final int LIMIT_LIKE_PATTERN_LENGTH = CApi.SQLITE_LIMIT_LIKE_PATTERN_LENGTH; + public static final int LIMIT_VARIABLE_NUMBER = CApi.SQLITE_LIMIT_VARIABLE_NUMBER; + public static final int LIMIT_TRIGGER_DEPTH = CApi.SQLITE_LIMIT_TRIGGER_DEPTH; + public static final int LIMIT_WORKER_THREADS = CApi.SQLITE_LIMIT_WORKER_THREADS; + + // sqlite3_prepare_v3() flags + public static final int PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT; + public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB; + + // sqlite3_trace_v2() flags + public static final int TRACE_STMT = CApi.SQLITE_TRACE_STMT; + public static final int TRACE_PROFILE = CApi.SQLITE_TRACE_PROFILE; + public static final int TRACE_ROW = CApi.SQLITE_TRACE_ROW; + public static final int TRACE_CLOSE = CApi.SQLITE_TRACE_CLOSE; + public static final int TRACE_ALL = TRACE_STMT | TRACE_PROFILE | TRACE_ROW | TRACE_CLOSE; + + // sqlite3_db_config() ops + public static final int DBCONFIG_ENABLE_FKEY = CApi.SQLITE_DBCONFIG_ENABLE_FKEY; + public static final int DBCONFIG_ENABLE_TRIGGER = CApi.SQLITE_DBCONFIG_ENABLE_TRIGGER; + public static final int DBCONFIG_ENABLE_FTS3_TOKENIZER = CApi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER; + public static final int DBCONFIG_ENABLE_LOAD_EXTENSION = CApi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION; + public static final int DBCONFIG_NO_CKPT_ON_CLOSE = CApi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE; + public static final int DBCONFIG_ENABLE_QPSG = CApi.SQLITE_DBCONFIG_ENABLE_QPSG; + public static final int DBCONFIG_TRIGGER_EQP = CApi.SQLITE_DBCONFIG_TRIGGER_EQP; + public static final int DBCONFIG_RESET_DATABASE = CApi.SQLITE_DBCONFIG_RESET_DATABASE; + public static final int DBCONFIG_DEFENSIVE = CApi.SQLITE_DBCONFIG_DEFENSIVE; + public static final int DBCONFIG_WRITABLE_SCHEMA = CApi.SQLITE_DBCONFIG_WRITABLE_SCHEMA; + public static final int DBCONFIG_LEGACY_ALTER_TABLE = CApi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE; + public static final int DBCONFIG_DQS_DML = CApi.SQLITE_DBCONFIG_DQS_DML; + public static final int DBCONFIG_DQS_DDL = CApi.SQLITE_DBCONFIG_DQS_DDL; + public static final int DBCONFIG_ENABLE_VIEW = CApi.SQLITE_DBCONFIG_ENABLE_VIEW; + public static final int DBCONFIG_LEGACY_FILE_FORMAT = CApi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT; + public static final int DBCONFIG_TRUSTED_SCHEMA = CApi.SQLITE_DBCONFIG_TRUSTED_SCHEMA; + public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS; + public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER; + + // sqlite3_config() ops + public static final int CONFIG_SINGLETHREAD = CApi.SQLITE_CONFIG_SINGLETHREAD; + public static final int CONFIG_MULTITHREAD = CApi.SQLITE_CONFIG_MULTITHREAD; + public static final int CONFIG_SERIALIZED = CApi.SQLITE_CONFIG_SERIALIZED; + + // Encodings + public static final int UTF8 = CApi.SQLITE_UTF8; + public static final int UTF16 = CApi.SQLITE_UTF16; + public static final int UTF16LE = CApi.SQLITE_UTF16LE; + public static final int UTF16BE = CApi.SQLITE_UTF16BE; + /* We elide the UTF16_ALIGNED from this interface because it + is irrelevant for the Java interface. */ + + // SQL data type IDs + public static final int INTEGER = CApi.SQLITE_INTEGER; + public static final int FLOAT = CApi.SQLITE_FLOAT; + public static final int TEXT = CApi.SQLITE_TEXT; + public static final int BLOB = CApi.SQLITE_BLOB; + public static final int NULL = CApi.SQLITE_NULL; + + // Authorizer codes. + public static final int DENY = CApi.SQLITE_DENY; + public static final int IGNORE = CApi.SQLITE_IGNORE; + public static final int CREATE_INDEX = CApi.SQLITE_CREATE_INDEX; + public static final int CREATE_TABLE = CApi.SQLITE_CREATE_TABLE; + public static final int CREATE_TEMP_INDEX = CApi.SQLITE_CREATE_TEMP_INDEX; + public static final int CREATE_TEMP_TABLE = CApi.SQLITE_CREATE_TEMP_TABLE; + public static final int CREATE_TEMP_TRIGGER = CApi.SQLITE_CREATE_TEMP_TRIGGER; + public static final int CREATE_TEMP_VIEW = CApi.SQLITE_CREATE_TEMP_VIEW; + public static final int CREATE_TRIGGER = CApi.SQLITE_CREATE_TRIGGER; + public static final int CREATE_VIEW = CApi.SQLITE_CREATE_VIEW; + public static final int DELETE = CApi.SQLITE_DELETE; + public static final int DROP_INDEX = CApi.SQLITE_DROP_INDEX; + public static final int DROP_TABLE = CApi.SQLITE_DROP_TABLE; + public static final int DROP_TEMP_INDEX = CApi.SQLITE_DROP_TEMP_INDEX; + public static final int DROP_TEMP_TABLE = CApi.SQLITE_DROP_TEMP_TABLE; + public static final int DROP_TEMP_TRIGGER = CApi.SQLITE_DROP_TEMP_TRIGGER; + public static final int DROP_TEMP_VIEW = CApi.SQLITE_DROP_TEMP_VIEW; + public static final int DROP_TRIGGER = CApi.SQLITE_DROP_TRIGGER; + public static final int DROP_VIEW = CApi.SQLITE_DROP_VIEW; + public static final int INSERT = CApi.SQLITE_INSERT; + public static final int PRAGMA = CApi.SQLITE_PRAGMA; + public static final int READ = CApi.SQLITE_READ; + public static final int SELECT = CApi.SQLITE_SELECT; + public static final int TRANSACTION = CApi.SQLITE_TRANSACTION; + public static final int UPDATE = CApi.SQLITE_UPDATE; + public static final int ATTACH = CApi.SQLITE_ATTACH; + public static final int DETACH = CApi.SQLITE_DETACH; + public static final int ALTER_TABLE = CApi.SQLITE_ALTER_TABLE; + public static final int REINDEX = CApi.SQLITE_REINDEX; + public static final int ANALYZE = CApi.SQLITE_ANALYZE; + public static final int CREATE_VTABLE = CApi.SQLITE_CREATE_VTABLE; + public static final int DROP_VTABLE = CApi.SQLITE_DROP_VTABLE; + public static final int FUNCTION = CApi.SQLITE_FUNCTION; + public static final int SAVEPOINT = CApi.SQLITE_SAVEPOINT; + public static final int RECURSIVE = CApi.SQLITE_RECURSIVE; //! Used only by the open() factory functions. private Sqlite(sqlite3 db){ this.db = db; } + /** Maps org.sqlite.jni.capi.sqlite3 to Sqlite instances. */ + private static final java.util.Map nativeToWrapper + = new java.util.HashMap<>(); + + + /** + When any given thread is done using the SQLite library, calling + this will free up any native-side resources which may be + associated specifically with that thread. This is not strictly + necessary, in particular in applications which only use SQLite + from a single thread, but may help free some otherwise errant + resources. + + Calling into SQLite from a given thread after this has been + called in that thread is harmless. The library will simply start + to re-cache certain state for that thread. + + Contrariwise, failing to call this will effectively leak a small + amount of cached state for the thread, which may add up to + significant amounts if the application uses SQLite from many + threads. + + This must never be called while actively using SQLite from this + thread, e.g. from within a query loop or a callback which is + operating on behalf of the library. + */ + static void uncacheThread(){ + CApi.sqlite3_java_uncache_thread(); + } + + /** + Returns the Sqlite object associated with the given sqlite3 + object, or null if there is no such mapping. + */ + static Sqlite fromNative(sqlite3 low){ + synchronized(nativeToWrapper){ + return nativeToWrapper.get(low); + } + } + /** Returns a newly-opened db connection or throws SqliteException if opening fails. All arguments are as documented for @@ -44,7 +330,7 @@ private Sqlite(sqlite3 db){ */ public static Sqlite open(String filename, int flags, String vfsName){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); - final int rc = sqlite3_open_v2(filename, out, flags, vfsName); + final int rc = CApi.sqlite3_open_v2(filename, out, flags, vfsName); final sqlite3 n = out.take(); if( 0!=rc ){ if( null==n ) throw new SqliteException(rc); @@ -52,7 +338,12 @@ public static Sqlite open(String filename, int flags, String vfsName){ n.close(); throw ex; } - return new Sqlite(n); + final Sqlite rv = new Sqlite(n); + synchronized(nativeToWrapper){ + nativeToWrapper.put(n, rv); + } + runAutoExtensions(rv); + return rv; } public static Sqlite open(String filename, int flags){ @@ -60,11 +351,147 @@ public static Sqlite open(String filename, int flags){ } public static Sqlite open(String filename){ - return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null); + return open(filename, OPEN_READWRITE|OPEN_CREATE, null); + } + + public static String libVersion(){ + return CApi.sqlite3_libversion(); + } + + public static int libVersionNumber(){ + return CApi.sqlite3_libversion_number(); + } + + public static String libSourceId(){ + return CApi.sqlite3_sourceid(); + } + + /** + Returns the value of the native library's build-time value of the + SQLITE_THREADSAFE build option. + */ + public static int libThreadsafe(){ + return CApi.sqlite3_threadsafe(); + } + + /** + Analog to sqlite3_compileoption_get(). + */ + public static String compileOptionGet(int n){ + return CApi.sqlite3_compileoption_get(n); + } + + /** + Analog to sqlite3_compileoption_used(). + */ + public static boolean compileOptionUsed(String optName){ + return CApi.sqlite3_compileoption_used(optName); + } + + private static boolean hasNormalizeSql = + compileOptionUsed("ENABLE_NORMALIZE"); + + private static boolean hasSqlLog = + compileOptionUsed("ENABLE_SQLLOG"); + + /** + Throws UnsupportedOperationException if check is false. + flag is expected to be the name of an SQLITE_ENABLE_... + build flag. + */ + private static void checkSupported(boolean check, String flag){ + if( !check ){ + throw new UnsupportedOperationException( + "Library was built without "+flag + ); + } + } + + /** + Analog to sqlite3_complete(). + */ + public static boolean isCompleteStatement(String sql){ + switch(CApi.sqlite3_complete(sql)){ + case 0: return false; + case CApi.SQLITE_MISUSE: + throw new IllegalArgumentException("Input may not be null."); + case CApi.SQLITE_NOMEM: + throw new OutOfMemoryError(); + default: + return true; + } + } + + public static int keywordCount(){ + return CApi.sqlite3_keyword_count(); + } + + public static boolean keywordCheck(String word){ + return CApi.sqlite3_keyword_check(word); + } + + public static String keywordName(int index){ + return CApi.sqlite3_keyword_name(index); + } + + public static boolean strglob(String glob, String txt){ + return 0==CApi.sqlite3_strglob(glob, txt); + } + + public static boolean strlike(String glob, String txt, char escChar){ + return 0==CApi.sqlite3_strlike(glob, txt, escChar); + } + + /** + Output object for use with status() and libStatus(). + */ + public static final class Status { + /** The current value for the requested status() or libStatus() metric. */ + long current; + /** The peak value for the requested status() or libStatus() metric. */ + long peak; + }; + + /** + As per sqlite3_status64(), but returns its current and high-water + results as a Status object. Throws if the first argument is + not one of the STATUS_... constants. + */ + public static Status libStatus(int op, boolean resetStats){ + org.sqlite.jni.capi.OutputPointer.Int64 pCurrent = + new org.sqlite.jni.capi.OutputPointer.Int64(); + org.sqlite.jni.capi.OutputPointer.Int64 pHighwater = + new org.sqlite.jni.capi.OutputPointer.Int64(); + checkRcStatic( CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats) ); + final Status s = new Status(); + s.current = pCurrent.value; + s.peak = pHighwater.value; + return s; + } + + /** + As per sqlite3_db_status(), but returns its current and + high-water results as a Status object. Throws if the first + argument is not one of the DBSTATUS_... constants or on any other + misuse. + */ + public Status status(int op, boolean resetStats){ + org.sqlite.jni.capi.OutputPointer.Int32 pCurrent = + new org.sqlite.jni.capi.OutputPointer.Int32(); + org.sqlite.jni.capi.OutputPointer.Int32 pHighwater = + new org.sqlite.jni.capi.OutputPointer.Int32(); + checkRc( CApi.sqlite3_db_status(thisDb(), op, pCurrent, pHighwater, resetStats) ); + final Status s = new Status(); + s.current = pCurrent.value; + s.peak = pHighwater.value; + return s; } @Override public void close(){ if(null!=this.db){ + synchronized(nativeToWrapper){ + nativeToWrapper.remove(this.db); + } this.db.close(); this.db = null; } @@ -77,7 +504,7 @@ public static Sqlite open(String filename){ */ sqlite3 nativeHandle(){ return this.db; } - private sqlite3 affirmOpen(){ + private sqlite3 thisDb(){ if( null==db || 0==db.getNativePointer() ){ throw new IllegalArgumentException("This database instance is closed."); } @@ -88,115 +515,207 @@ private sqlite3 affirmOpen(){ // return s==null ? null : s.getBytes(StandardCharsets.UTF_8); // } - private void affirmRcOk(int rc){ + /** + If rc!=0, throws an SqliteException. If this db is currently + opened and has non-0 sqlite3_errcode(), the error state is + extracted from it, else only the string form of rc is used. It is + the caller's responsibility to filter out non-error codes such as + SQLITE_ROW and SQLITE_DONE before calling this. + + As a special case, if rc is SQLITE_NOMEM, an OutOfMemoryError is + thrown. + */ + private void checkRc(int rc){ if( 0!=rc ){ - throw new SqliteException(db); + if( CApi.SQLITE_NOMEM==rc ){ + throw new OutOfMemoryError(); + }else if( null==db || 0==CApi.sqlite3_errcode(db) ){ + throw new SqliteException(rc); + }else{ + throw new SqliteException(db); + } } } /** - Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to - create new instances. + Like checkRc() but behaves as if that function were + called with a null db object. */ - public final class Stmt implements AutoCloseable { - private Sqlite _db = null; - private sqlite3_stmt stmt = null; - /** Only called by the prepare() factory functions. */ - Stmt(Sqlite db, sqlite3_stmt stmt){ - this._db = db; - this.stmt = stmt; + private static void checkRcStatic(int rc){ + if( 0!=rc ){ + if( CApi.SQLITE_NOMEM==rc ){ + throw new OutOfMemoryError(); + }else{ + throw new SqliteException(rc); + } } + } - sqlite3_stmt nativeHandle(){ - return stmt; - } + /** + Toggles the use of extended result codes on or off. By default + they are turned off, but they can be enabled by default by + including the OPEN_EXRESCODE flag when opening a database. - private sqlite3_stmt affirmOpen(){ - if( null==stmt || 0==stmt.getNativePointer() ){ - throw new IllegalArgumentException("This Stmt has been finalized."); - } - return stmt; - } + Because this API reports db-side errors using exceptions, + enabling this may change the values returned by + SqliteException.errcode(). + */ + public void useExtendedResultCodes(boolean on){ + checkRc( CApi.sqlite3_extended_result_codes(thisDb(), on) ); + } - /** - Corresponds to sqlite3_finalize(), but we cannot override the - name finalize() here because this one requires a different - signature. It does not throw on error here because "destructors - do not throw." If it returns non-0, the object is still - finalized. - */ - public int finalizeStmt(){ - int rc = 0; - if( null!=stmt ){ - sqlite3_finalize(stmt); - stmt = null; - } - return rc; - } + /** + Analog to sqlite3_prepare_v3(), this prepares the first SQL + statement from the given input string and returns it as a + Stmt. It throws an SqliteException if preparation fails or an + IllegalArgumentException if the input is empty (e.g. contains + only comments or whitespace). - @Override public void close(){ - finalizeStmt(); + The first argument must be SQL input in UTF-8 encoding. + + prepFlags must be 0 or a bitmask of the PREPARE_... constants. + + For processing multiple statements from a single input, use + prepareMulti(). + + Design note: though the C-level API succeeds with a null + statement object for empty inputs, that approach is cumbersome to + use in higher-level APIs because every prepared statement has to + be checked for null before using it. + */ + public Stmt prepare(byte utf8Sql[], int prepFlags){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + final int rc = CApi.sqlite3_prepare_v3(thisDb(), utf8Sql, prepFlags, out); + checkRc(rc); + final sqlite3_stmt q = out.take(); + if( null==q ){ + /* The C-level API treats input which is devoid of SQL + statements (e.g. all comments or an empty string) as success + but returns a NULL sqlite3_stmt object. In higher-level APIs, + wrapping a "successful NULL" object that way is tedious to + use because it forces clients and/or wrapper-level code to + check for that unusual case. In practice, higher-level + bindings are generally better-served by treating empty SQL + input as an error. */ + throw new IllegalArgumentException("Input contains no SQL statements."); } + return new Stmt(this, q); + } + /** + Equivalent to prepare(X, prepFlags), where X is + sql.getBytes(StandardCharsets.UTF_8). + */ + public Stmt prepare(String sql, int prepFlags){ + return prepare( sql.getBytes(StandardCharsets.UTF_8), prepFlags ); + } + + /** + Equivalent to prepare(sql, 0). + */ + public Stmt prepare(String sql){ + return prepare(sql, 0); + } + + + /** + Callback type for use with prepareMulti(). + */ + public interface PrepareMulti { /** - Throws if rc is any value other than 0, SQLITE_ROW, or - SQLITE_DONE, else returns rc. + Gets passed a Stmt which it may handle in arbitrary ways. + Ownership of st is passed to this function. It must throw on + error. */ - private int checkRc(int rc){ - switch(rc){ - case 0: - case SQLITE_ROW: - case SQLITE_DONE: return rc; - default: - throw new SqliteException(this); - } - } + void call(Sqlite.Stmt st); + } + /** + A PrepareMulti implementation which calls another PrepareMulti + object and then finalizes its statement. + */ + public static class PrepareMultiFinalize implements PrepareMulti { + private final PrepareMulti pm; /** - Works like sqlite3_step() but throws SqliteException for any - result other than 0, SQLITE_ROW, or SQLITE_DONE. + Proxies the given PrepareMulti via this object's call() method. */ - public int step(){ - return checkRc(sqlite3_step(affirmOpen())); + public PrepareMultiFinalize(PrepareMulti proxy){ + this.pm = proxy; } - - public Sqlite db(){ return this._db; } - /** - Works like sqlite3_reset() but throws on error. + Passes st to the call() method of the object this one proxies, + then finalizes st, propagating any exceptions from call() after + finalizing st. */ - public void reset(){ - checkRc(sqlite3_reset(affirmOpen())); + @Override public void call(Stmt st){ + try{ pm.call(st); } + finally{ st.finalizeStmt(); } } + } - public void clearBindings(){ - sqlite3_clear_bindings( affirmOpen() ); - } + /** + Equivalent to prepareMulti(sql,0,visitor). + */ + public void prepareMulti(String sql, PrepareMulti visitor){ + prepareMulti( sql, 0, visitor ); } + /** + Equivallent to prepareMulti(X,prepFlags,visitor), where X is + sql.getBytes(StandardCharsets.UTF_8). + */ + public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){ + prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor); + } /** - prepare() TODOs include: + A variant of prepare() which can handle multiple SQL statements + in a single input string. For each statement in the given string, + the statement is passed to visitor.call() a single time, passing + ownership of the statement to that function. This function does + not step() or close() statements - those operations are left to + caller or the visitor function. - - overloads taking byte[] and ByteBuffer. + Unlike prepare(), this function does not fail if the input + contains only whitespace or SQL comments. In that case it is up + to the caller to arrange for that to be an error (if desired). - - multi-statement processing, like CApi.sqlite3_prepare_multi() - but using a callback specific to the higher-level Stmt class - rather than the sqlite3_stmt class. - */ - public Stmt prepare(String sql, int prepFlags){ - final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); - final int rc = sqlite3_prepare_v3(affirmOpen(), sql, prepFlags, out); - affirmRcOk(rc); - return new Stmt(this, out.take()); - } + PrepareMultiFinalize offers a proxy which finalizes each + statement after it is passed to another client-defined visitor. - public Stmt prepare(String sql){ - return prepare(sql, 0); + Be aware that certain legal SQL constructs may fail in the + preparation phase, before the corresponding statement can be + stepped. Most notably, authorizer checks which disallow access to + something in a statement behave that way. + */ + public void prepareMulti(byte sqlUtf8[], int prepFlags, PrepareMulti visitor){ + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + final org.sqlite.jni.capi.OutputPointer.sqlite3_stmt outStmt = + new org.sqlite.jni.capi.OutputPointer.sqlite3_stmt(); + final org.sqlite.jni.capi.OutputPointer.Int32 oTail = + new org.sqlite.jni.capi.OutputPointer.Int32(); + while( pos < sqlChunk.length ){ + sqlite3_stmt stmt = null; + if( pos>0 ){ + sqlChunk = java.util.Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + checkRc( + CApi.sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail) + ); + pos = oTail.value; + stmt = outStmt.take(); + if( null==stmt ){ + /* empty statement, e.g. only comments or whitespace, was parsed. */ + continue; + } + visitor.call(new Stmt(this, stmt)); + } } - public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f ){ - int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep, + public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){ + int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep, new SqlFunction.ScalarAdapter(f)); if( 0!=rc ) throw new SqliteException(db); } @@ -205,8 +724,8 @@ public void createFunction(String name, int nArg, ScalarFunction f){ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f); } - public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f ){ - int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep, + public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f){ + int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep, new SqlFunction.AggregateAdapter(f)); if( 0!=rc ) throw new SqliteException(db); } @@ -215,4 +734,1258 @@ public void createFunction(String name, int nArg, AggregateFunction f){ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f); } + public void createFunction(String name, int nArg, int eTextRep, WindowFunction f){ + int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep, + new SqlFunction.WindowAdapter(f)); + if( 0!=rc ) throw new SqliteException(db); + } + + public void createFunction(String name, int nArg, WindowFunction f){ + this.createFunction(name, nArg, CApi.SQLITE_UTF8, f); + } + + public long changes(){ + return CApi.sqlite3_changes64(thisDb()); + } + + public long totalChanges(){ + return CApi.sqlite3_total_changes64(thisDb()); + } + + public long lastInsertRowId(){ + return CApi.sqlite3_last_insert_rowid(thisDb()); + } + + public void setLastInsertRowId(long rowId){ + CApi.sqlite3_set_last_insert_rowid(thisDb(), rowId); + } + + public void interrupt(){ + CApi.sqlite3_interrupt(thisDb()); + } + + public boolean isInterrupted(){ + return CApi.sqlite3_is_interrupted(thisDb()); + } + + public boolean isAutoCommit(){ + return CApi.sqlite3_get_autocommit(thisDb()); + } + + /** + Analog to sqlite3_txn_state(). Returns one of TXN_NONE, TXN_READ, + or TXN_WRITE to denote this database's current transaction state + for the given schema name (or the most restrictive state of any + schema if zSchema is null). + */ + public int transactionState(String zSchema){ + return CApi.sqlite3_txn_state(thisDb(), zSchema); + } + + /** + Analog to sqlite3_db_name(). Returns null if passed an unknown + index. + */ + public String dbName(int dbNdx){ + return CApi.sqlite3_db_name(thisDb(), dbNdx); + } + + /** + Analog to sqlite3_db_filename(). Returns null if passed an + unknown db name. + */ + public String dbFileName(String dbName){ + return CApi.sqlite3_db_filename(thisDb(), dbName); + } + + /** + Analog to sqlite3_db_config() for the call forms which take one + of the boolean-type db configuration flags (namely the + DBCONFIG_... constants defined in this class). On success it + returns the result of that underlying call. Throws on error. + */ + public boolean dbConfig(int op, boolean on){ + org.sqlite.jni.capi.OutputPointer.Int32 pOut = + new org.sqlite.jni.capi.OutputPointer.Int32(); + checkRc( CApi.sqlite3_db_config(thisDb(), op, on ? 1 : 0, pOut) ); + return pOut.get()!=0; + } + + /** + Analog to the variant of sqlite3_db_config() for configuring the + SQLITE_DBCONFIG_MAINDBNAME option. Throws on error. + */ + public void setMainDbName(String name){ + checkRc( + CApi.sqlite3_db_config(thisDb(), CApi.SQLITE_DBCONFIG_MAINDBNAME, + name) + ); + } + + /** + Analog to sqlite3_db_readonly() but throws an SqliteException + with result code SQLITE_NOTFOUND if given an unknown database + name. + */ + public boolean readOnly(String dbName){ + final int rc = CApi.sqlite3_db_readonly(thisDb(), dbName); + if( 0==rc ) return false; + else if( rc>0 ) return true; + throw new SqliteException(CApi.SQLITE_NOTFOUND); + } + + /** + Analog to sqlite3_db_release_memory(). + */ + public void releaseMemory(){ + CApi.sqlite3_db_release_memory(thisDb()); + } + + /** + Analog to sqlite3_release_memory(). + */ + public static int libReleaseMemory(int n){ + return CApi.sqlite3_release_memory(n); + } + + /** + Analog to sqlite3_limit(). limitId must be one of the + LIMIT_... constants. + + Returns the old limit for the given option. If newLimit is + negative, it returns the old limit without modifying the limit. + + If sqlite3_limit() returns a negative value, this function throws + an SqliteException with the SQLITE_RANGE result code but no + further error info (because that case does not qualify as a + db-level error). Such errors may indicate an invalid argument + value or an invalid range for newLimit (the underlying function + does not differentiate between those). + */ + public int limit(int limitId, int newLimit){ + final int rc = CApi.sqlite3_limit(thisDb(), limitId, newLimit); + if( rc<0 ){ + throw new SqliteException(CApi.SQLITE_RANGE); + } + return rc; + } + + /** + Analog to sqlite3_errstr(). + */ + static String errstr(int resultCode){ + return CApi.sqlite3_errstr(resultCode); + } + + /** + A wrapper object for use with tableColumnMetadata(). They are + created and populated only via that interface. + */ + public final class TableColumnMetadata { + Boolean pNotNull = null; + Boolean pPrimaryKey = null; + Boolean pAutoinc = null; + String pzCollSeq = null; + String pzDataType = null; + + private TableColumnMetadata(){} + + public String getDataType(){ return pzDataType; } + public String getCollation(){ return pzCollSeq; } + public boolean isNotNull(){ return pNotNull; } + public boolean isPrimaryKey(){ return pPrimaryKey; } + public boolean isAutoincrement(){ return pAutoinc; } + } + + /** + Returns data about a database, table, and (optionally) column + (which may be null), as per sqlite3_table_column_metadata(). + Throws if passed invalid arguments, else returns the result as a + new TableColumnMetadata object. + */ + TableColumnMetadata tableColumnMetadata( + String zDbName, String zTableName, String zColumnName + ){ + org.sqlite.jni.capi.OutputPointer.String pzDataType + = new org.sqlite.jni.capi.OutputPointer.String(); + org.sqlite.jni.capi.OutputPointer.String pzCollSeq + = new org.sqlite.jni.capi.OutputPointer.String(); + org.sqlite.jni.capi.OutputPointer.Bool pNotNull + = new org.sqlite.jni.capi.OutputPointer.Bool(); + org.sqlite.jni.capi.OutputPointer.Bool pPrimaryKey + = new org.sqlite.jni.capi.OutputPointer.Bool(); + org.sqlite.jni.capi.OutputPointer.Bool pAutoinc + = new org.sqlite.jni.capi.OutputPointer.Bool(); + final int rc = CApi.sqlite3_table_column_metadata( + thisDb(), zDbName, zTableName, zColumnName, + pzDataType, pzCollSeq, pNotNull, pPrimaryKey, pAutoinc + ); + checkRc(rc); + TableColumnMetadata rv = new TableColumnMetadata(); + rv.pzDataType = pzDataType.value; + rv.pzCollSeq = pzCollSeq.value; + rv.pNotNull = pNotNull.value; + rv.pPrimaryKey = pPrimaryKey.value; + rv.pAutoinc = pAutoinc.value; + return rv; + } + + public interface TraceCallback { + /** + Called by sqlite3 for various tracing operations, as per + sqlite3_trace_v2(). Note that this interface elides the 2nd + argument to the native trace callback, as that role is better + filled by instance-local state. + +

      These callbacks may throw, in which case their exceptions are + converted to C-level error information. + +

      The 2nd argument to this function, if non-null, will be a an + Sqlite or Sqlite.Stmt object, depending on the first argument + (see below). + +

      The final argument to this function is the "X" argument + documented for sqlite3_trace() and sqlite3_trace_v2(). Its type + depends on value of the first argument: + +

      - SQLITE_TRACE_STMT: pNative is a Sqlite.Stmt. pX is a String + containing the prepared SQL. + +

      - SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long + holding an approximate number of nanoseconds the statement took + to run. + +

      - SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null. + +

      - SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. + */ + void call(int traceFlag, Object pNative, Object pX); + } + + /** + Analog to sqlite3_trace_v2(). traceMask must be a mask of the + TRACE_... constants. Pass a null callback to remove tracing. + + Throws on error. + */ + public void trace(int traceMask, TraceCallback callback){ + final Sqlite self = this; + final org.sqlite.jni.capi.TraceV2Callback tc = + (null==callback) ? null : new org.sqlite.jni.capi.TraceV2Callback(){ + @SuppressWarnings("unchecked") + @Override public int call(int flag, Object pNative, Object pX){ + switch(flag){ + case TRACE_ROW: + case TRACE_PROFILE: + case TRACE_STMT: + callback.call(flag, Sqlite.Stmt.fromNative((sqlite3_stmt)pNative), pX); + break; + case TRACE_CLOSE: + callback.call(flag, self, pX); + break; + } + return 0; + } + }; + checkRc( CApi.sqlite3_trace_v2(thisDb(), traceMask, tc) ); + }; + + /** + Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to + create new instances. + */ + public static final class Stmt implements AutoCloseable { + private Sqlite _db = null; + private sqlite3_stmt stmt = null; + + /** Only called by the prepare() factory functions. */ + Stmt(Sqlite db, sqlite3_stmt stmt){ + this._db = db; + this.stmt = stmt; + synchronized(nativeToWrapper){ + nativeToWrapper.put(this.stmt, this); + } + } + + sqlite3_stmt nativeHandle(){ + return stmt; + } + + /** Maps org.sqlite.jni.capi.sqlite3_stmt to Stmt instances. */ + private static final java.util.Map nativeToWrapper + = new java.util.HashMap<>(); + + /** + Returns the Stmt object associated with the given sqlite3_stmt + object, or null if there is no such mapping. + */ + static Stmt fromNative(sqlite3_stmt low){ + synchronized(nativeToWrapper){ + return nativeToWrapper.get(low); + } + } + + /** + If this statement is still opened, its low-level handle is + returned, else an IllegalArgumentException is thrown. + */ + private sqlite3_stmt thisStmt(){ + if( null==stmt || 0==stmt.getNativePointer() ){ + throw new IllegalArgumentException("This Stmt has been finalized."); + } + return stmt; + } + + /** Throws if n is out of range of this statement's result column + count. Intended to be used by the columnXyz() methods. */ + private sqlite3_stmt checkColIndex(int n){ + if(n<0 || n>=columnCount()){ + throw new IllegalArgumentException("Column index "+n+" is out of range."); + } + return thisStmt(); + } + + /** + Corresponds to sqlite3_finalize(), but we cannot override the + name finalize() here because this one requires a different + signature. It does not throw on error here because "destructors + do not throw." If it returns non-0, the object is still + finalized, but the result code is an indication that something + went wrong in a prior call into the statement's API, as + documented for sqlite3_finalize(). + */ + public int finalizeStmt(){ + int rc = 0; + if( null!=stmt ){ + synchronized(nativeToWrapper){ + nativeToWrapper.remove(this.stmt); + } + CApi.sqlite3_finalize(stmt); + stmt = null; + _db = null; + } + return rc; + } + + @Override public void close(){ + finalizeStmt(); + } + + /** + Throws if rc is any value other than 0, SQLITE_ROW, or + SQLITE_DONE, else returns rc. Error state for the exception is + extracted from this statement object (if it's opened) or the + string form of rc. + */ + private int checkRc(int rc){ + switch(rc){ + case 0: + case CApi.SQLITE_ROW: + case CApi.SQLITE_DONE: return rc; + default: + if( null==stmt ) throw new SqliteException(rc); + else throw new SqliteException(this); + } + } + + /** + Works like sqlite3_step() but returns true for SQLITE_ROW, + false for SQLITE_DONE, and throws SqliteException for any other + result. + */ + public boolean step(){ + switch(checkRc(CApi.sqlite3_step(thisStmt()))){ + case CApi.SQLITE_ROW: return true; + case CApi.SQLITE_DONE: return false; + default: + throw new IllegalStateException( + "This \"cannot happen\": all possible result codes were checked already." + ); + } + } + + /** + Works like sqlite3_step(), returning the same result codes as + that function unless throwOnError is true, in which case it + will throw an SqliteException for any result codes other than + Sqlite.ROW or Sqlite.DONE. + + The utility of this overload over the no-argument one is the + ability to handle BUSY and LOCKED errors more easily. + */ + public int step(boolean throwOnError){ + final int rc = (null==stmt) + ? Sqlite.MISUSE + : CApi.sqlite3_step(stmt); + return throwOnError ? checkRc(rc) : rc; + } + + /** + Returns the Sqlite which prepared this statement, or null if + this statement has been finalized. + */ + public Sqlite getDb(){ return this._db; } + + /** + Works like sqlite3_reset() but throws on error. + */ + public void reset(){ + checkRc(CApi.sqlite3_reset(thisStmt())); + } + + public boolean isBusy(){ + return CApi.sqlite3_stmt_busy(thisStmt()); + } + + public boolean isReadOnly(){ + return CApi.sqlite3_stmt_readonly(thisStmt()); + } + + public String sql(){ + return CApi.sqlite3_sql(thisStmt()); + } + + public String expandedSql(){ + return CApi.sqlite3_expanded_sql(thisStmt()); + } + + /** + Analog to sqlite3_stmt_explain() but throws if op is invalid. + */ + public void explain(int op){ + checkRc(CApi.sqlite3_stmt_explain(thisStmt(), op)); + } + + /** + Analog to sqlite3_stmt_isexplain(). + */ + public int isExplain(){ + return CApi.sqlite3_stmt_isexplain(thisStmt()); + } + + /** + Analog to sqlite3_normalized_sql(), but throws + UnsupportedOperationException if the library was built without + the SQLITE_ENABLE_NORMALIZE flag. + */ + public String normalizedSql(){ + Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_NORMALIZE"); + return CApi.sqlite3_normalized_sql(thisStmt()); + } + + public void clearBindings(){ + CApi.sqlite3_clear_bindings( thisStmt() ); + } + public void bindInt(int ndx, int val){ + checkRc(CApi.sqlite3_bind_int(thisStmt(), ndx, val)); + } + public void bindInt64(int ndx, long val){ + checkRc(CApi.sqlite3_bind_int64(thisStmt(), ndx, val)); + } + public void bindDouble(int ndx, double val){ + checkRc(CApi.sqlite3_bind_double(thisStmt(), ndx, val)); + } + public void bindObject(int ndx, Object o){ + checkRc(CApi.sqlite3_bind_java_object(thisStmt(), ndx, o)); + } + public void bindNull(int ndx){ + checkRc(CApi.sqlite3_bind_null(thisStmt(), ndx)); + } + public int bindParameterCount(){ + return CApi.sqlite3_bind_parameter_count(thisStmt()); + } + public int bindParameterIndex(String paramName){ + return CApi.sqlite3_bind_parameter_index(thisStmt(), paramName); + } + public String bindParameterName(int ndx){ + return CApi.sqlite3_bind_parameter_name(thisStmt(), ndx); + } + public void bindText(int ndx, byte[] utf8){ + checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, utf8)); + } + public void bindText(int ndx, String asUtf8){ + checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, asUtf8)); + } + public void bindText16(int ndx, byte[] utf16){ + checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, utf16)); + } + public void bindText16(int ndx, String asUtf16){ + checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, asUtf16)); + } + public void bindZeroBlob(int ndx, int n){ + checkRc(CApi.sqlite3_bind_zeroblob(thisStmt(), ndx, n)); + } + public void bindBlob(int ndx, byte[] bytes){ + checkRc(CApi.sqlite3_bind_blob(thisStmt(), ndx, bytes)); + } + + public byte[] columnBlob(int ndx){ + return CApi.sqlite3_column_blob( checkColIndex(ndx), ndx ); + } + public byte[] columnText(int ndx){ + return CApi.sqlite3_column_text( checkColIndex(ndx), ndx ); + } + public String columnText16(int ndx){ + return CApi.sqlite3_column_text16( checkColIndex(ndx), ndx ); + } + public int columnBytes(int ndx){ + return CApi.sqlite3_column_bytes( checkColIndex(ndx), ndx ); + } + public int columnBytes16(int ndx){ + return CApi.sqlite3_column_bytes16( checkColIndex(ndx), ndx ); + } + public int columnInt(int ndx){ + return CApi.sqlite3_column_int( checkColIndex(ndx), ndx ); + } + public long columnInt64(int ndx){ + return CApi.sqlite3_column_int64( checkColIndex(ndx), ndx ); + } + public double columnDouble(int ndx){ + return CApi.sqlite3_column_double( checkColIndex(ndx), ndx ); + } + public int columnType(int ndx){ + return CApi.sqlite3_column_type( checkColIndex(ndx), ndx ); + } + public String columnDeclType(int ndx){ + return CApi.sqlite3_column_decltype( checkColIndex(ndx), ndx ); + } + /** + Analog to sqlite3_column_count() but throws if this statement + has been finalized. + */ + public int columnCount(){ + /* We cannot reliably cache the column count in a class + member because an ALTER TABLE from a separate statement + can invalidate that count and we have no way, short of + installing a COMMIT handler or the like, of knowing when + to re-read it. We cannot install such a handler without + interfering with a client's ability to do so. */ + return CApi.sqlite3_column_count(thisStmt()); + } + public int columnDataCount(){ + return CApi.sqlite3_data_count( thisStmt() ); + } + public Object columnObject(int ndx){ + return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx ); + } + public T columnObject(int ndx, Class type){ + return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx, type ); + } + public String columnName(int ndx){ + return CApi.sqlite3_column_name( checkColIndex(ndx), ndx ); + } + public String columnDatabaseName(int ndx){ + return CApi.sqlite3_column_database_name( checkColIndex(ndx), ndx ); + } + public String columnOriginName(int ndx){ + return CApi.sqlite3_column_origin_name( checkColIndex(ndx), ndx ); + } + public String columnTableName(int ndx){ + return CApi.sqlite3_column_table_name( checkColIndex(ndx), ndx ); + } + } /* Stmt class */ + + /** + Interface for auto-extensions, as per the + sqlite3_auto_extension() API. + + Design note: the chicken/egg timing of auto-extension execution + requires that this feature be entirely re-implemented in Java + because the C-level API has no access to the Sqlite type so + cannot pass on an object of that type while the database is being + opened. One side effect of this reimplementation is that this + class's list of auto-extensions is 100% independent of the + C-level list so, e.g., clearAutoExtensions() will have no effect + on auto-extensions added via the C-level API and databases opened + from that level of API will not be passed to this level's + AutoExtension instances. + */ + public interface AutoExtension { + public void call(Sqlite db); + } + + private static final java.util.Set autoExtensions = + new java.util.LinkedHashSet<>(); + + /** + Passes db to all auto-extensions. If any one of them throws, + db.close() is called before the exception is propagated. + */ + private static void runAutoExtensions(Sqlite db){ + AutoExtension list[]; + synchronized(autoExtensions){ + /* Avoid that modifications to the AutoExtension list from within + auto-extensions affect this execution of this list. */ + list = autoExtensions.toArray(new AutoExtension[0]); + } + try { + for( AutoExtension ax : list ) ax.call(db); + }catch(Exception e){ + db.close(); + throw e; + } + } + + /** + Analog to sqlite3_auto_extension(), adds the given object to the + list of auto-extensions if it is not already in that list. The + given object will be run as part of Sqlite.open(), and passed the + being-opened database. If the extension throws then open() will + fail. + + This API does not guaranty whether or not manipulations made to + the auto-extension list from within auto-extension callbacks will + affect the current traversal of the auto-extension list. Whether + or not they do is unspecified and subject to change between + versions. e.g. if an AutoExtension calls addAutoExtension(), + whether or not the new extension will be run on the being-opened + database is undefined. + + Note that calling Sqlite.open() from an auto-extension will + necessarily result in recursion loop and (eventually) a stack + overflow. + */ + public static void addAutoExtension( AutoExtension e ){ + if( null==e ){ + throw new IllegalArgumentException("AutoExtension may not be null."); + } + synchronized(autoExtensions){ + autoExtensions.add(e); + } + } + + /** + Removes the given object from the auto-extension list if it is in + that list, otherwise this has no side-effects beyond briefly + locking that list. + */ + public static void removeAutoExtension( AutoExtension e ){ + synchronized(autoExtensions){ + autoExtensions.remove(e); + } + } + + /** + Removes all auto-extensions which were added via addAutoExtension(). + */ + public static void clearAutoExtensions(){ + synchronized(autoExtensions){ + autoExtensions.clear(); + } + } + + /** + Encapsulates state related to the sqlite3 backup API. Use + Sqlite.initBackup() to create new instances. + */ + public static final class Backup implements AutoCloseable { + private sqlite3_backup b = null; + private Sqlite dbTo = null; + private Sqlite dbFrom = null; + + Backup(Sqlite dbDest, String schemaDest,Sqlite dbSrc, String schemaSrc){ + this.dbTo = dbDest; + this.dbFrom = dbSrc; + b = CApi.sqlite3_backup_init(dbDest.nativeHandle(), schemaDest, + dbSrc.nativeHandle(), schemaSrc); + if(null==b) toss(); + } + + private void toss(){ + int rc = CApi.sqlite3_errcode(dbTo.nativeHandle()); + if(0!=rc) throw new SqliteException(dbTo); + rc = CApi.sqlite3_errcode(dbFrom.nativeHandle()); + if(0!=rc) throw new SqliteException(dbFrom); + throw new SqliteException(CApi.SQLITE_ERROR); + } + + private sqlite3_backup getNative(){ + if( null==b ) throw new IllegalStateException("This Backup is already closed."); + return b; + } + /** + If this backup is still active, this completes the backup and + frees its native resources, otherwise it this is a no-op. + */ + public void finish(){ + if( null!=b ){ + CApi.sqlite3_backup_finish(b); + b = null; + dbTo = null; + dbFrom = null; + } + } + + /** Equivalent to finish(). */ + @Override public void close(){ + this.finish(); + } + + /** + Analog to sqlite3_backup_step(). Returns 0 if stepping succeeds + or, Sqlite.DONE if the end is reached, Sqlite.BUSY if one of + the databases is busy, Sqlite.LOCKED if one of the databases is + locked, and throws for any other result code or if this object + has been closed. Note that BUSY and LOCKED are not necessarily + permanent errors, so do not trigger an exception. + */ + public int step(int pageCount){ + final int rc = CApi.sqlite3_backup_step(getNative(), pageCount); + switch(rc){ + case 0: + case Sqlite.DONE: + case Sqlite.BUSY: + case Sqlite.LOCKED: + return rc; + default: + toss(); + return CApi.SQLITE_ERROR/*not reached*/; + } + } + + /** + Analog to sqlite3_backup_pagecount(). + */ + public int pageCount(){ + return CApi.sqlite3_backup_pagecount(getNative()); + } + + /** + Analog to sqlite3_backup_remaining(). + */ + public int remaining(){ + return CApi.sqlite3_backup_remaining(getNative()); + } + } + + /** + Analog to sqlite3_backup_init(). If schemaSrc is null, "main" is + assumed. Throws if either this db or dbSrc (the source db) are + not opened, if either of schemaDest or schemaSrc are null, or if + the underlying call to sqlite3_backup_init() fails. + + The returned object must eventually be cleaned up by either + arranging for it to be auto-closed (e.g. using + try-with-resources) or by calling its finish() method. + */ + public Backup initBackup(String schemaDest, Sqlite dbSrc, String schemaSrc){ + thisDb(); + dbSrc.thisDb(); + if( null==schemaSrc || null==schemaDest ){ + throw new IllegalArgumentException( + "Neither the source nor destination schema name may be null." + ); + } + return new Backup(this, schemaDest, dbSrc, schemaSrc); + } + + + /** + Callback type for use with createCollation(). + */ + public interface Collation { + /** + Called by the SQLite core to compare inputs. Implementations + must compare its two arguments using memcmp(3) semantics. + + Warning: the SQLite core has no mechanism for reporting errors + from custom collations and its workflow does not accommodate + propagation of exceptions from callbacks. Any exceptions thrown + from collations will be silently supressed and sorting results + will be unpredictable. + */ + int call(byte[] lhs, byte[] rhs); + } + + /** + Analog to sqlite3_create_collation(). + + Throws if name is null or empty, c is null, or the encoding flag + is invalid. The encoding must be one of the UTF8, UTF16, UTF16LE, + or UTF16BE constants. + */ + public void createCollation(String name, int encoding, Collation c){ + thisDb(); + if( null==name || 0==name.length()){ + throw new IllegalArgumentException("Collation name may not be null or empty."); + } + if( null==c ){ + throw new IllegalArgumentException("Collation may not be null."); + } + switch(encoding){ + case UTF8: + case UTF16: + case UTF16LE: + case UTF16BE: + break; + default: + throw new IllegalArgumentException("Invalid Collation encoding."); + } + checkRc( + CApi.sqlite3_create_collation( + thisDb(), name, encoding, new org.sqlite.jni.capi.CollationCallback(){ + @Override public int call(byte[] lhs, byte[] rhs){ + try{return c.call(lhs, rhs);} + catch(Exception e){return 0;} + } + @Override public void xDestroy(){} + } + ) + ); + } + + /** + Callback for use with onCollationNeeded(). + */ + public interface CollationNeeded { + /** + Must behave as documented for the callback for + sqlite3_collation_needed(). + + Warning: the C API has no mechanism for reporting or + propagating errors from this callback, so any exceptions it + throws are suppressed. + */ + void call(Sqlite db, int encoding, String collationName); + } + + /** + Sets up the given object to be called by the SQLite core when it + encounters a collation name which it does not know. Pass a null + object to disconnect the object from the core. This replaces any + existing collation-needed loader, or is a no-op if the given + object is already registered. Throws if registering the loader + fails. + */ + public void onCollationNeeded( CollationNeeded cn ){ + org.sqlite.jni.capi.CollationNeededCallback cnc = null; + if( null!=cn ){ + cnc = new org.sqlite.jni.capi.CollationNeededCallback(){ + @Override public void call(sqlite3 db, int encoding, String collationName){ + final Sqlite xdb = Sqlite.fromNative(db); + if(null!=xdb) cn.call(xdb, encoding, collationName); + } + }; + } + checkRc( CApi.sqlite3_collation_needed(thisDb(), cnc) ); + } + + /** + Callback for use with busyHandler(). + */ + public interface BusyHandler { + /** + Must function as documented for the C-level + sqlite3_busy_handler() callback argument, minus the (void*) + argument the C-level function requires. + + If this function throws, it is translated to a database-level + error. + */ + int call(int n); + } + + /** + Analog to sqlite3_busy_timeout(). + */ + public void setBusyTimeout(int ms){ + checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms)); + } + + /** + Analog to sqlite3_busy_handler(). If b is null then any + current handler is cleared. + */ + public void setBusyHandler( BusyHandler b ){ + org.sqlite.jni.capi.BusyHandlerCallback bhc = null; + if( null!=b ){ + bhc = new org.sqlite.jni.capi.BusyHandlerCallback(){ + @Override public int call(int n){ + return b.call(n); + } + }; + } + checkRc( CApi.sqlite3_busy_handler(thisDb(), bhc) ); + } + + public interface CommitHook { + /** + Must behave as documented for the C-level sqlite3_commit_hook() + callback. If it throws, the exception is translated into + a db-level error. + */ + int call(); + } + + /** + A level of indirection to permit setCommitHook() to have similar + semantics as the C API, returning the previous hook. The caveat + is that if the low-level API is used to install a hook, it will + have a different hook type than Sqlite.CommitHook so + setCommitHook() will return null instead of that object. + */ + private static class CommitHookProxy + implements org.sqlite.jni.capi.CommitHookCallback { + final CommitHook commitHook; + CommitHookProxy(CommitHook ch){ + this.commitHook = ch; + } + @Override public int call(){ + return commitHook.call(); + } + } + + /** + Analog to sqlite3_commit_hook(). Returns the previous hook, if + any (else null). Throws if this db is closed. + + Minor caveat: if a commit hook is set on this object's underlying + db handle using the lower-level SQLite API, this function may + return null when replacing it, despite there being a hook, + because it will have a different callback type. So long as the + handle is only manipulated via the high-level API, this caveat + does not apply. + */ + public CommitHook setCommitHook( CommitHook c ){ + CommitHookProxy chp = null; + if( null!=c ){ + chp = new CommitHookProxy(c); + } + final org.sqlite.jni.capi.CommitHookCallback rv = + CApi.sqlite3_commit_hook(thisDb(), chp); + return (rv instanceof CommitHookProxy) + ? ((CommitHookProxy)rv).commitHook + : null; + } + + + public interface RollbackHook { + /** + Must behave as documented for the C-level sqlite3_rollback_hook() + callback. If it throws, the exception is translated into + a db-level error. + */ + void call(); + } + + /** + A level of indirection to permit setRollbackHook() to have similar + semantics as the C API, returning the previous hook. The caveat + is that if the low-level API is used to install a hook, it will + have a different hook type than Sqlite.RollbackHook so + setRollbackHook() will return null instead of that object. + */ + private static class RollbackHookProxy + implements org.sqlite.jni.capi.RollbackHookCallback { + final RollbackHook rollbackHook; + RollbackHookProxy(RollbackHook ch){ + this.rollbackHook = ch; + } + @Override public void call(){rollbackHook.call();} + } + + /** + Analog to sqlite3_rollback_hook(). Returns the previous hook, if + any (else null). Throws if this db is closed. + + Minor caveat: if a rollback hook is set on this object's underlying + db handle using the lower-level SQLite API, this function may + return null when replacing it, despite there being a hook, + because it will have a different callback type. So long as the + handle is only manipulated via the high-level API, this caveat + does not apply. + */ + public RollbackHook setRollbackHook( RollbackHook c ){ + RollbackHookProxy chp = null; + if( null!=c ){ + chp = new RollbackHookProxy(c); + } + final org.sqlite.jni.capi.RollbackHookCallback rv = + CApi.sqlite3_rollback_hook(thisDb(), chp); + return (rv instanceof RollbackHookProxy) + ? ((RollbackHookProxy)rv).rollbackHook + : null; + } + + public interface UpdateHook { + /** + Must function as described for the C-level sqlite3_update_hook() + callback. + */ + void call(int opId, String dbName, String tableName, long rowId); + } + + /** + A level of indirection to permit setUpdateHook() to have similar + semantics as the C API, returning the previous hook. The caveat + is that if the low-level API is used to install a hook, it will + have a different hook type than Sqlite.UpdateHook so + setUpdateHook() will return null instead of that object. + */ + private static class UpdateHookProxy + implements org.sqlite.jni.capi.UpdateHookCallback { + final UpdateHook updateHook; + UpdateHookProxy(UpdateHook ch){ + this.updateHook = ch; + } + @Override public void call(int opId, String dbName, String tableName, long rowId){ + updateHook.call(opId, dbName, tableName, rowId); + } + } + + /** + Analog to sqlite3_update_hook(). Returns the previous hook, if + any (else null). Throws if this db is closed. + + Minor caveat: if a update hook is set on this object's underlying + db handle using the lower-level SQLite API, this function may + return null when replacing it, despite there being a hook, + because it will have a different callback type. So long as the + handle is only manipulated via the high-level API, this caveat + does not apply. + */ + public UpdateHook setUpdateHook( UpdateHook c ){ + UpdateHookProxy chp = null; + if( null!=c ){ + chp = new UpdateHookProxy(c); + } + final org.sqlite.jni.capi.UpdateHookCallback rv = + CApi.sqlite3_update_hook(thisDb(), chp); + return (rv instanceof UpdateHookProxy) + ? ((UpdateHookProxy)rv).updateHook + : null; + } + + + /** + Callback interface for use with setProgressHandler(). + */ + public interface ProgressHandler { + /** + Must behave as documented for the C-level sqlite3_progress_handler() + callback. If it throws, the exception is translated into + a db-level error. + */ + int call(); + } + + /** + Analog to sqlite3_progress_handler(), sets the current progress + handler or clears it if p is null. + + Note that this API, in contrast to setUpdateHook(), + setRollbackHook(), and setCommitHook(), cannot return the + previous handler. That inconsistency is part of the lower-level C + API. + */ + public void setProgressHandler( int n, ProgressHandler p ){ + org.sqlite.jni.capi.ProgressHandlerCallback phc = null; + if( null!=p ){ + phc = new org.sqlite.jni.capi.ProgressHandlerCallback(){ + @Override public int call(){ return p.call(); } + }; + } + CApi.sqlite3_progress_handler( thisDb(), n, phc ); + } + + + /** + Callback for use with setAuthorizer(). + */ + public interface Authorizer { + /** + Must function as described for the C-level + sqlite3_set_authorizer() callback. If it throws, the error is + converted to a db-level error and the exception is suppressed. + */ + int call(int opId, String s1, String s2, String s3, String s4); + } + + /** + Analog to sqlite3_set_authorizer(), this sets the current + authorizer callback, or clears if it passed null. + */ + public void setAuthorizer( Authorizer a ) { + org.sqlite.jni.capi.AuthorizerCallback ac = null; + if( null!=a ){ + ac = new org.sqlite.jni.capi.AuthorizerCallback(){ + @Override public int call(int opId, String s1, String s2, String s3, String s4){ + return a.call(opId, s1, s2, s3, s4); + } + }; + } + checkRc( CApi.sqlite3_set_authorizer( thisDb(), ac ) ); + } + + /** + Object type for use with blobOpen() + */ + public final class Blob implements AutoCloseable { + private Sqlite db; + private sqlite3_blob b; + Blob(Sqlite db, sqlite3_blob b){ + this.db = db; + this.b = b; + } + + /** + If this blob is still opened, its low-level handle is + returned, else an IllegalArgumentException is thrown. + */ + private sqlite3_blob thisBlob(){ + if( null==b || 0==b.getNativePointer() ){ + throw new IllegalArgumentException("This Blob has been finalized."); + } + return b; + } + + /** + Analog to sqlite3_blob_close(). + */ + @Override public void close(){ + if( null!=b ){ + CApi.sqlite3_blob_close(b); + b = null; + db = null; + } + } + + /** + Throws if the JVM does not have JNI-level support for + ByteBuffer. + */ + private void checkNio(){ + if( !Sqlite.JNI_SUPPORTS_NIO ){ + throw new UnsupportedOperationException( + "This JVM does not support JNI access to ByteBuffer." + ); + } + } + /** + Analog to sqlite3_blob_reopen() but throws on error. + */ + public void reopen(long newRowId){ + db.checkRc( CApi.sqlite3_blob_reopen(thisBlob(), newRowId) ); + } + + /** + Analog to sqlite3_blob_write() but throws on error. + */ + public void write( byte[] bytes, int atOffset ){ + db.checkRc( CApi.sqlite3_blob_write(thisBlob(), bytes, atOffset) ); + } + + /** + Analog to sqlite3_blob_read() but throws on error. + */ + public void read( byte[] dest, int atOffset ){ + db.checkRc( CApi.sqlite3_blob_read(thisBlob(), dest, atOffset) ); + } + + /** + Analog to sqlite3_blob_bytes(). + */ + public int bytes(){ + return CApi.sqlite3_blob_bytes(thisBlob()); + } + } + + /** + Analog to sqlite3_blob_open(). Returns a Blob object for the + given database, table, column, and rowid. The blob is opened for + read-write mode if writeable is true, else it is read-only. + + The returned object must eventually be freed, before this + database is closed, by either arranging for it to be auto-closed + or calling its close() method. + + Throws on error. + */ + public Blob blobOpen(String dbName, String tableName, String columnName, + long iRow, boolean writeable){ + final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob(); + checkRc( + CApi.sqlite3_blob_open(thisDb(), dbName, tableName, columnName, + iRow, writeable ? 1 : 0, out) + ); + return new Blob(this, out.take()); + } + + /** + Callback for use with libConfigLog(). + */ + public interface ConfigLog { + /** + Must function as described for a C-level callback for + sqlite3_config()'s SQLITE_CONFIG_LOG callback, with the slight + signature change. Any exceptions thrown from this callback are + necessarily suppressed. + */ + void call(int errCode, String msg); + } + + /** + Analog to sqlite3_config() with the SQLITE_CONFIG_LOG option, + this sets or (if log is null) clears the current logger. + */ + public static void libConfigLog(ConfigLog log){ + final org.sqlite.jni.capi.ConfigLogCallback l = + null==log + ? null + : new org.sqlite.jni.capi.ConfigLogCallback() { + @Override public void call(int errCode, String msg){ + log.call(errCode, msg); + } + }; + checkRcStatic(CApi.sqlite3_config(l)); + } + + /** + Callback for use with libConfigSqlLog(). + */ + public interface ConfigSqlLog { + /** + Must function as described for a C-level callback for + sqlite3_config()'s SQLITE_CONFIG_SQLLOG callback, with the + slight signature change. Any exceptions thrown from this + callback are necessarily suppressed. + */ + void call(Sqlite db, String msg, int msgType); + } + + /** + Analog to sqlite3_config() with the SQLITE_CONFIG_SQLLOG option, + this sets or (if log is null) clears the current logger. + + If SQLite is built without SQLITE_ENABLE_SQLLOG defined then this + will throw an UnsupportedOperationException. + */ + public static void libConfigSqlLog(ConfigSqlLog log){ + Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_SQLLOG"); + final org.sqlite.jni.capi.ConfigSqlLogCallback l = + null==log + ? null + : new org.sqlite.jni.capi.ConfigSqlLogCallback() { + @Override public void call(sqlite3 db, String msg, int msgType){ + try{ + log.call(fromNative(db), msg, msgType); + }catch(Exception e){ + /* Suppressed */ + } + } + }; + checkRcStatic(CApi.sqlite3_config(l)); + } + + /** + Analog to the C-level sqlite3_config() with one of the + SQLITE_CONFIG_... constants defined as CONFIG_... in this + class. Throws on error, including passing of an unknown option or + if a specified option is not supported by the underlying build of + the SQLite library. + */ + public static void libConfigOp( int op ){ + checkRcStatic(CApi.sqlite3_config(op)); + } + } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java index 111f004db..9b4440f19 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java @@ -12,7 +12,7 @@ ** This file is part of the wrapper1 interface for sqlite3. */ package org.sqlite.jni.wrapper1; -import static org.sqlite.jni.capi.CApi.*; +import org.sqlite.jni.capi.CApi; import org.sqlite.jni.capi.sqlite3; /** @@ -22,10 +22,10 @@ and C via JNI. */ public final class SqliteException extends java.lang.RuntimeException { - int errCode = SQLITE_ERROR; - int xerrCode = SQLITE_ERROR; - int errOffset = -1; - int sysErrno = 0; + private int errCode = CApi.SQLITE_ERROR; + private int xerrCode = CApi.SQLITE_ERROR; + private int errOffset = -1; + private int sysErrno = 0; /** Records the given error string and uses SQLITE_ERROR for both the @@ -38,28 +38,31 @@ public SqliteException(String msg){ /** Uses sqlite3_errstr(sqlite3ResultCode) for the error string and sets both the error code and extended error code to the given - value. + value. This approach includes no database-level information and + systemErrno() will be 0, so is intended only for use with sqlite3 + APIs for which a result code is not an error but which the + higher-level wrapper should treat as one. */ public SqliteException(int sqlite3ResultCode){ - super(sqlite3_errstr(sqlite3ResultCode)); + super(CApi.sqlite3_errstr(sqlite3ResultCode)); errCode = xerrCode = sqlite3ResultCode; } /** Records the current error state of db (which must not be null and - must refer to an opened db object). Note that this does NOT close + must refer to an opened db object). Note that this does not close the db. - Design note: closing the db on error is likely only useful during + Design note: closing the db on error is really only useful during a failed db-open operation, and the place(s) where that can happen are inside this library, not client-level code. */ SqliteException(sqlite3 db){ - super(sqlite3_errmsg(db)); - errCode = sqlite3_errcode(db); - xerrCode = sqlite3_extended_errcode(db); - errOffset = sqlite3_error_offset(db); - sysErrno = sqlite3_system_errno(db); + super(CApi.sqlite3_errmsg(db)); + errCode = CApi.sqlite3_errcode(db); + xerrCode = CApi.sqlite3_extended_errcode(db); + errOffset = CApi.sqlite3_error_offset(db); + sysErrno = CApi.sqlite3_system_errno(db); } /** @@ -71,7 +74,7 @@ public SqliteException(Sqlite db){ } public SqliteException(Sqlite.Stmt stmt){ - this( stmt.db() ); + this(stmt.getDb()); } public int errcode(){ return errCode; } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java index f5fd5f84e..5ac41323c 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java @@ -12,14 +12,13 @@ ** This file contains a set of tests for the sqlite3 JNI bindings. */ package org.sqlite.jni.wrapper1; -//import static org.sqlite.jni.capi.CApi.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.sqlite.jni.capi.*; +import org.sqlite.jni.capi.CApi; /** An annotation for Tester2 tests which we do not want to run in @@ -46,7 +45,7 @@ public class Tester2 implements Runnable { //! True to shuffle the order of the tests. private static boolean shuffle = false; //! True to dump the list of to-run tests to stdout. - private static boolean listRunTests = false; + private static int listRunTests = 0; //! True to squelch all out() and outln() output. private static boolean quietMode = false; //! Total number of runTests() calls. @@ -125,49 +124,37 @@ public static void affirm(Boolean v){ } - public static void execSql(Sqlite db, String[] sql){ + public static void execSql(Sqlite db, String sql[]){ execSql(db, String.join("", sql)); } + /** + Executes all SQL statements in the given string. If throwOnError + is true then it will throw for any prepare/step errors, else it + will return the corresponding non-0 result code. + */ public static int execSql(Sqlite dbw, boolean throwOnError, String sql){ - final sqlite3 db = dbw.nativeHandle(); - OutputPointer.Int32 oTail = new OutputPointer.Int32(); - final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); - int pos = 0, n = 1; - byte[] sqlChunk = sqlUtf8; - int rc = 0; - sqlite3_stmt stmt = null; - final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); - while(pos < sqlChunk.length){ - if(pos > 0){ - sqlChunk = Arrays.copyOfRange(sqlChunk, pos, - sqlChunk.length); - } - if( 0==sqlChunk.length ) break; - rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); - if(throwOnError) affirm(0 == rc); - else if( 0!=rc ) break; - pos = oTail.value; - stmt = outStmt.take(); - if( null == stmt ){ - // empty statement was parsed. - continue; - } - affirm(0 != stmt.getNativePointer()); - while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(stmt)) ){ - } - CApi.sqlite3_finalize(stmt); - affirm(0 == stmt.getNativePointer()); - if(0!=rc && CApi.SQLITE_ROW!=rc && CApi.SQLITE_DONE!=rc){ - break; + final ValueHolder rv = new ValueHolder<>(0); + final Sqlite.PrepareMulti pm = new Sqlite.PrepareMulti(){ + @Override public void call(Sqlite.Stmt stmt){ + try{ + while( Sqlite.ROW == (rv.value = stmt.step(throwOnError)) ){} + } + finally{ stmt.finalizeStmt(); } + } + }; + try { + dbw.prepareMulti(sql, pm); + }catch(SqliteException se){ + if( throwOnError ){ + throw se; + }else{ + /* This error (likely) happened in the prepare() phase and we + need to preempt it. */ + rv.value = se.errcode(); } } - CApi.sqlite3_finalize(stmt); - if(CApi.SQLITE_ROW==rc || CApi.SQLITE_DONE==rc) rc = 0; - if( 0!=rc && throwOnError){ - throw new SqliteException(db); - } - return rc; + return (rv.value==Sqlite.DONE) ? 0 : rv.value; } static void execSql(Sqlite db, String sql){ @@ -176,15 +163,7 @@ static void execSql(Sqlite db, String sql){ @SingleThreadOnly /* because it's thread-agnostic */ private void test1(){ - affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER); - } - - /* Copy/paste/rename this to add new tests. */ - private void _testTemplate(){ - //final sqlite3 db = createNewDb(); - //sqlite3_stmt stmt = prepare(db,"SELECT 1"); - //sqlite3_finalize(stmt); - //sqlite3_close_v2(db); + affirm(Sqlite.libVersionNumber() == CApi.SQLITE_VERSION_NUMBER); } private void nap() throws InterruptedException { @@ -194,9 +173,9 @@ private void nap() throws InterruptedException { } Sqlite openDb(String name){ - final Sqlite db = Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE| - CApi.SQLITE_OPEN_CREATE| - CApi.SQLITE_OPEN_EXRESCODE); + final Sqlite db = Sqlite.open(name, Sqlite.OPEN_READWRITE| + Sqlite.OPEN_CREATE| + Sqlite.OPEN_EXRESCODE); ++metrics.dbOpen; return db; } @@ -206,15 +185,24 @@ Sqlite openDb(String name){ void testOpenDb1(){ Sqlite db = openDb(); affirm( 0!=db.nativeHandle().getNativePointer() ); + affirm( "main".equals( db.dbName(0) ) ); + db.setMainDbName("foo"); + affirm( "foo".equals( db.dbName(0) ) ); + affirm( db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, true) + /* The underlying function has different mangled names in jdk8 + vs jdk19, and this call is here to ensure that the build + fails if it cannot find both names. */ ); + affirm( !db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, false) ); + SqliteException ex = null; + try{ db.dbConfig(0, false); } + catch(SqliteException e){ ex = e; } + affirm( null!=ex ); + ex = null; db.close(); affirm( null==db.nativeHandle() ); - SqliteException ex = null; - try { - db = openDb("/no/such/dir/.../probably"); - }catch(SqliteException e){ - ex = e; - } + try{ db = openDb("/no/such/dir/.../probably"); } + catch(SqliteException e){ ex = e; } affirm( ex!=null ); affirm( ex.errcode() != 0 ); affirm( ex.extendedErrcode() != 0 ); @@ -224,19 +212,51 @@ void testOpenDb1(){ void testPrepare1(){ try (Sqlite db = openDb()) { - Sqlite.Stmt stmt = db.prepare("SELECT 1"); + Sqlite.Stmt stmt = db.prepare("SELECT ?1"); + Exception e = null; affirm( null!=stmt.nativeHandle() ); - affirm( CApi.SQLITE_ROW == stmt.step() ); - affirm( CApi.SQLITE_DONE == stmt.step() ); + affirm( db == stmt.getDb() ); + affirm( 1==stmt.bindParameterCount() ); + affirm( "?1".equals(stmt.bindParameterName(1)) ); + affirm( null==stmt.bindParameterName(2) ); + stmt.bindInt64(1, 1); + stmt.bindDouble(1, 1.1); + stmt.bindObject(1, db); + stmt.bindNull(1); + stmt.bindText(1, new byte[] {32,32,32}); + stmt.bindText(1, "123"); + stmt.bindText16(1, "123".getBytes(StandardCharsets.UTF_16)); + stmt.bindText16(1, "123"); + stmt.bindZeroBlob(1, 8); + stmt.bindBlob(1, new byte[] {1,2,3,4}); + stmt.bindInt(1, 17); + try{ stmt.bindInt(2,1); } + catch(Exception ex){ e = ex; } + affirm( null!=e ); + e = null; + affirm( stmt.step() ); + try{ stmt.columnInt(1); } + catch(Exception ex){ e = ex; } + affirm( null!=e ); + e = null; + affirm( 17 == stmt.columnInt(0) ); + affirm( 17L == stmt.columnInt64(0) ); + affirm( 17.0 == stmt.columnDouble(0) ); + affirm( "17".equals(stmt.columnText16(0)) ); + affirm( !stmt.step() ); stmt.reset(); - affirm( CApi.SQLITE_ROW == stmt.step() ); - affirm( CApi.SQLITE_DONE == stmt.step() ); + affirm( Sqlite.ROW==stmt.step(false) ); + affirm( !stmt.step() ); affirm( 0 == stmt.finalizeStmt() ); affirm( null==stmt.nativeHandle() ); - stmt = db.prepare("SELECT 1"); - affirm( CApi.SQLITE_ROW == stmt.step() ); - affirm( 0 == stmt.finalizeStmt() ) + stmt = db.prepare("SELECT ?"); + stmt.bindObject(1, db); + affirm( Sqlite.ROW == stmt.step(false) ); + affirm( db==stmt.columnObject(0) ); + affirm( db==stmt.columnObject(0, Sqlite.class ) ); + affirm( null==stmt.columnObject(0, Sqlite.Stmt.class ) ); + affirm( 0==stmt.finalizeStmt() ) /* getting a non-0 out of sqlite3_finalize() is tricky */; affirm( null==stmt.nativeHandle() ); } @@ -249,28 +269,37 @@ void testUdfScalar(){ final ValueHolder vh = new ValueHolder<>(0); final ScalarFunction f = new ScalarFunction(){ public void xFunc(SqlFunction.Arguments args){ + affirm( db == args.getDb() ); for( SqlFunction.Arguments.Arg arg : args ){ vh.value += arg.getInt(); } + args.resultInt(vh.value); } public void xDestroy(){ ++xDestroyCalled.value; } }; db.createFunction("myfunc", -1, f); - execSql(db, "select myfunc(1,2,3)"); + Sqlite.Stmt q = db.prepare("select myfunc(1,2,3)"); + affirm( q.step() ); affirm( 6 == vh.value ); + affirm( 6 == q.columnInt(0) ); + q.finalizeStmt(); + affirm( 0 == xDestroyCalled.value ); vh.value = 0; - execSql(db, "select myfunc(-1,-2,-3)"); + q = db.prepare("select myfunc(-1,-2,-3)"); + affirm( q.step() ); affirm( -6 == vh.value ); + affirm( -6 == q.columnInt(0) ); affirm( 0 == xDestroyCalled.value ); + q.finalizeStmt(); } affirm( 1 == xDestroyCalled.value ); } void testUdfAggregate(){ final ValueHolder xDestroyCalled = new ValueHolder<>(0); - final ValueHolder vh = new ValueHolder<>(0); + Sqlite.Stmt q = null; try (Sqlite db = openDb()) { execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)"); final AggregateFunction f = new AggregateFunction(){ @@ -284,18 +313,650 @@ public void xFinal(SqlFunction.Arguments args){ final Integer v = this.takeAggregateState(args); if( null==v ) args.resultNull(); else args.resultInt(v); - vh.value = v; } public void xDestroy(){ ++xDestroyCalled.value; } }; - db.createFunction("myagg", -1, f); - execSql(db, "select myagg(a) from t"); - affirm( 6 == vh.value ); + db.createFunction("summer", 1, f); + q = db.prepare( + "with cte(v) as ("+ + "select 3 union all select 5 union all select 7"+ + ") select summer(v), summer(v+1) from cte" + /* ------------------^^^^^^^^^^^ ensures that we're handling + sqlite3_aggregate_context() properly. */ + ); + affirm( q.step() ); + affirm( 15==q.columnInt(0) ); + q.finalizeStmt(); + q = null; affirm( 0 == xDestroyCalled.value ); + db.createFunction("summerN", -1, f); + + q = db.prepare("select summerN(1,8,9), summerN(2,3,4)"); + affirm( q.step() ); + affirm( 18==q.columnInt(0) ); + affirm( 9==q.columnInt(1) ); + q.finalizeStmt(); + q = null; + + }/*db*/ + finally{ + if( null!=q ) q.finalizeStmt(); + } + affirm( 2 == xDestroyCalled.value + /* because we've bound the same instance twice */ ); + } + + private void testUdfWindow(){ + final Sqlite db = openDb(); + /* Example window function, table, and results taken from: + https://sqlite.org/windowfunctions.html#udfwinfunc */ + final WindowFunction func = new WindowFunction(){ + //! Impl of xStep() and xInverse() + private void xStepInverse(SqlFunction.Arguments args, int v){ + this.getAggregateState(args,0).value += v; + } + @Override public void xStep(SqlFunction.Arguments args){ + this.xStepInverse(args, args.getInt(0)); + } + @Override public void xInverse(SqlFunction.Arguments args){ + this.xStepInverse(args, -args.getInt(0)); + } + //! Impl of xFinal() and xValue() + private void xFinalValue(SqlFunction.Arguments args, Integer v){ + if(null == v) args.resultNull(); + else args.resultInt(v); + } + @Override public void xFinal(SqlFunction.Arguments args){ + xFinalValue(args, this.takeAggregateState(args)); + affirm( null == this.getAggregateState(args,null).value ); + } + @Override public void xValue(SqlFunction.Arguments args){ + xFinalValue(args, this.getAggregateState(args,null).value); + } + }; + db.createFunction("winsumint", 1, func); + execSql(db, new String[] { + "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", + "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" + }); + final Sqlite.Stmt stmt = db.prepare( + "SELECT x, winsumint(y) OVER ("+ + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+ + ") AS sum_y "+ + "FROM twin ORDER BY x;" + ); + int n = 0; + while( stmt.step() ){ + final String s = stmt.columnText16(0); + final int i = stmt.columnInt(1); + switch(++n){ + case 1: affirm( "a".equals(s) && 9==i ); break; + case 2: affirm( "b".equals(s) && 12==i ); break; + case 3: affirm( "c".equals(s) && 16==i ); break; + case 4: affirm( "d".equals(s) && 12==i ); break; + case 5: affirm( "e".equals(s) && 9==i ); break; + default: affirm( false /* cannot happen */ ); + } + } + stmt.close(); + affirm( 5 == n ); + db.close(); + } + + private void testKeyword(){ + final int n = Sqlite.keywordCount(); + affirm( n>0 ); + affirm( !Sqlite.keywordCheck("_nope_") ); + affirm( Sqlite.keywordCheck("seLect") ); + affirm( null!=Sqlite.keywordName(0) ); + affirm( null!=Sqlite.keywordName(n-1) ); + affirm( null==Sqlite.keywordName(n) ); + } + + + private void testExplain(){ + final Sqlite db = openDb(); + Sqlite.Stmt q = db.prepare("SELECT 1"); + affirm( 0 == q.isExplain() ); + q.explain(0); + affirm( 0 == q.isExplain() ); + q.explain(1); + affirm( 1 == q.isExplain() ); + q.explain(2); + affirm( 2 == q.isExplain() ); + Exception ex = null; + try{ + q.explain(-1); + }catch(Exception e){ + ex = e; + } + affirm( ex instanceof SqliteException ); + q.finalizeStmt(); + db.close(); + } + + + private void testTrace(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + /* Ensure that characters outside of the UTF BMP survive the trip + from Java to sqlite3 and back to Java. (At no small efficiency + penalty.) */ + final String nonBmpChar = "😃"; + db.trace( + Sqlite.TRACE_ALL, + new Sqlite.TraceCallback(){ + @Override public void call(int traceFlag, Object pNative, Object x){ + ++counter.value; + //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); + switch(traceFlag){ + case Sqlite.TRACE_STMT: + affirm(pNative instanceof Sqlite.Stmt); + //outln("TRACE_STMT sql = "+x); + affirm(x instanceof String); + affirm( ((String)x).indexOf(nonBmpChar) > 0 ); + break; + case Sqlite.TRACE_PROFILE: + affirm(pNative instanceof Sqlite.Stmt); + affirm(x instanceof Long); + //outln("TRACE_PROFILE time = "+x); + break; + case Sqlite.TRACE_ROW: + affirm(pNative instanceof Sqlite.Stmt); + affirm(null == x); + //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); + break; + case Sqlite.TRACE_CLOSE: + affirm(pNative instanceof Sqlite); + affirm(null == x); + break; + default: + affirm(false /*cannot happen*/); + break; + } + } + }); + execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ + "SELECT 'w"+nonBmpChar+"orld'"); + affirm( 6 == counter.value ); + db.close(); + affirm( 7 == counter.value ); + } + + private void testStatus(){ + final Sqlite db = openDb(); + execSql(db, "create table t(a); insert into t values(1),(2),(3)"); + + Sqlite.Status s = Sqlite.libStatus(Sqlite.STATUS_MEMORY_USED, false); + affirm( s.current > 0 ); + affirm( s.peak >= s.current ); + + s = db.status(Sqlite.DBSTATUS_SCHEMA_USED, false); + affirm( s.current > 0 ); + affirm( s.peak == 0 /* always 0 for SCHEMA_USED */ ); + + db.close(); + } + + @SingleThreadOnly /* because multiple threads legitimately make these + results unpredictable */ + private synchronized void testAutoExtension(){ + final ValueHolder val = new ValueHolder<>(0); + final ValueHolder toss = new ValueHolder<>(null); + final Sqlite.AutoExtension ax = new Sqlite.AutoExtension(){ + @Override public void call(Sqlite db){ + ++val.value; + if( null!=toss.value ){ + throw new RuntimeException(toss.value); + } + } + }; + Sqlite.addAutoExtension(ax); + openDb().close(); + affirm( 1==val.value ); + openDb().close(); + affirm( 2==val.value ); + Sqlite.clearAutoExtensions(); + openDb().close(); + affirm( 2==val.value ); + + Sqlite.addAutoExtension( ax ); + Sqlite.addAutoExtension( ax ); // Must not add a second entry + Sqlite.addAutoExtension( ax ); // or a third one + openDb().close(); + affirm( 3==val.value ); + + Sqlite db = openDb(); + affirm( 4==val.value ); + execSql(db, "ATTACH ':memory:' as foo"); + affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." ); + db.close(); + db = null; + + Sqlite.removeAutoExtension(ax); + openDb().close(); + affirm( 4==val.value ); + Sqlite.addAutoExtension(ax); + Exception err = null; + toss.value = "Throwing from auto_extension."; + try{ + openDb(); + }catch(Exception e){ + err = e; + } + affirm( err!=null ); + affirm( err.getMessage().indexOf(toss.value)>=0 ); + toss.value = null; + + val.value = 0; + final Sqlite.AutoExtension ax2 = new Sqlite.AutoExtension(){ + @Override public void call(Sqlite db){ + ++val.value; + } + }; + Sqlite.addAutoExtension(ax2); + openDb().close(); + affirm( 2 == val.value ); + Sqlite.removeAutoExtension(ax); + openDb().close(); + affirm( 3 == val.value ); + Sqlite.addAutoExtension(ax); + openDb().close(); + affirm( 5 == val.value ); + Sqlite.removeAutoExtension(ax2); + openDb().close(); + affirm( 6 == val.value ); + Sqlite.addAutoExtension(ax2); + openDb().close(); + affirm( 8 == val.value ); + + Sqlite.clearAutoExtensions(); + openDb().close(); + affirm( 8 == val.value ); + } + + private void testBackup(){ + final Sqlite dbDest = openDb(); + + try (Sqlite dbSrc = openDb()) { + execSql(dbSrc, new String[]{ + "pragma page_size=512; VACUUM;", + "create table t(a);", + "insert into t(a) values(1),(2),(3);" + }); + Exception e = null; + try { + dbSrc.initBackup("main",dbSrc,"main"); + }catch(Exception x){ + e = x; + } + affirm( e instanceof SqliteException ); + e = null; + try (Sqlite.Backup b = dbDest.initBackup("main",dbSrc,"main")) { + affirm( null!=b ); + int rc; + while( Sqlite.DONE!=(rc = b.step(1)) ){ + affirm( 0==rc ); + } + affirm( b.pageCount() > 0 ); + b.finish(); + } + } + + try (Sqlite.Stmt q = dbDest.prepare("SELECT sum(a) from t")) { + q.step(); + affirm( q.columnInt(0) == 6 ); + } + dbDest.close(); + } + + private void testCollation(){ + final Sqlite db = openDb(); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + final Sqlite.Collation myCollation = new Sqlite.Collation() { + private String myState = + "this is local state. There is much like it, but this is mine."; + @Override + // Reverse-sorts its inputs... + public int call(byte[] lhs, byte[] rhs){ + int len = lhs.length > rhs.length ? rhs.length : lhs.length; + int c = 0, i = 0; + for(i = 0; i < len; ++i){ + c = lhs[i] - rhs[i]; + if(0 != c) break; + } + if(0==c){ + if(i < lhs.length) c = 1; + else if(i < rhs.length) c = -1; + } + return -c; + } + }; + final Sqlite.CollationNeeded collLoader = new Sqlite.CollationNeeded(){ + @Override + public void call(Sqlite dbArg, int eTextRep, String collationName){ + affirm(dbArg == db); + db.createCollation("reversi", eTextRep, myCollation); + } + }; + db.onCollationNeeded(collLoader); + Sqlite.Stmt stmt = db.prepare("SELECT a FROM t ORDER BY a COLLATE reversi"); + int counter = 0; + while( stmt.step() ){ + final String val = stmt.columnText16(0); + ++counter; + switch(counter){ + case 1: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 3: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + stmt.finalizeStmt(); + stmt = db.prepare("SELECT a FROM t ORDER BY a"); + counter = 0; + while( stmt.step() ){ + final String val = stmt.columnText16(0); + ++counter; + //outln("Non-REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 3: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 1: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + stmt.finalizeStmt(); + db.onCollationNeeded(null); + db.close(); + } + + @SingleThreadOnly /* because threads inherently break this test */ + private void testBusy(){ + final String dbName = "_busy-handler.db"; + try{ + Sqlite db1 = openDb(dbName); + ++metrics.dbOpen; + execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); + Sqlite db2 = openDb(dbName); + ++metrics.dbOpen; + + final ValueHolder xBusyCalled = new ValueHolder<>(0); + Sqlite.BusyHandler handler = new Sqlite.BusyHandler(){ + @Override public int call(int n){ + return n > 2 ? 0 : ++xBusyCalled.value; + } + }; + db2.setBusyHandler(handler); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + int rc = 0; + SqliteException ex = null; + try{ + db2.prepare("SELECT * from t"); + }catch(SqliteException x){ + ex = x; + } + affirm( null!=ex ); + affirm( Sqlite.BUSY == ex.errcode() ); + affirm( 3 == xBusyCalled.value ); + db1.close(); + db2.close(); + }finally{ + try{(new java.io.File(dbName)).delete();} + catch(Exception e){/* ignore */} + } + } + + private void testCommitHook(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder hookResult = new ValueHolder<>(0); + final Sqlite.CommitHook theHook = new Sqlite.CommitHook(){ + @Override public int call(){ + ++counter.value; + return hookResult.value; + } + }; + Sqlite.CommitHook oldHook = db.setCommitHook(theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 2 == counter.value ); + execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;"); + affirm( 2 == counter.value /* NOT invoked if no changes are made */ ); + execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;"); + affirm( 3 == counter.value ); + oldHook = db.setCommitHook(theHook); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = db.setCommitHook(null); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = db.setCommitHook(null); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); + affirm( 4 == counter.value ); + + final Sqlite.CommitHook newHook = new Sqlite.CommitHook(){ + @Override public int call(){return 0;} + }; + oldHook = db.setCommitHook(newHook); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = db.setCommitHook(theHook); + affirm( newHook == oldHook ); + execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); + affirm( 5 == counter.value ); + hookResult.value = Sqlite.ERROR; + int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); + affirm( Sqlite.CONSTRAINT_COMMITHOOK == rc ); + affirm( 6 == counter.value ); + db.close(); + } + + private void testRollbackHook(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final Sqlite.RollbackHook theHook = new Sqlite.RollbackHook(){ + @Override public void call(){ + ++counter.value; + } + }; + Sqlite.RollbackHook oldHook = db.setRollbackHook(theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 0 == counter.value ); + execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;"); + affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ ); + + final Sqlite.RollbackHook newHook = new Sqlite.RollbackHook(){ + @Override public void call(){} + }; + oldHook = db.setRollbackHook(newHook); + affirm( theHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 1 == counter.value ); + oldHook = db.setRollbackHook(theHook); + affirm( newHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 2 == counter.value ); + int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 0 == rc ); + affirm( 3 == counter.value ); + db.close(); + } + + private void testUpdateHook(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final Sqlite.UpdateHook theHook = new Sqlite.UpdateHook(){ + @Override + public void call(int opId, String dbName, String tableName, long rowId){ + ++counter.value; + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + Sqlite.UpdateHook oldHook = db.setUpdateHook(theHook); + affirm( null == oldHook ); + expectedOp.value = Sqlite.INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = Sqlite.UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = db.setUpdateHook(theHook); + affirm( theHook == oldHook ); + expectedOp.value = Sqlite.DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = db.setUpdateHook(null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = db.setUpdateHook(null); + affirm( null == oldHook ); + + final Sqlite.UpdateHook newHook = new Sqlite.UpdateHook(){ + @Override public void call(int opId, String dbName, String tableName, long rowId){ + } + }; + oldHook = db.setUpdateHook(newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = db.setUpdateHook(theHook); + affirm( newHook == oldHook ); + expectedOp.value = Sqlite.UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + db.close(); + } + + private void testProgress(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + db.setProgressHandler(1, new Sqlite.ProgressHandler(){ + @Override public int call(){ + ++counter.value; + return 0; + } + }); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( counter.value > 0 ); + int nOld = counter.value; + db.setProgressHandler(0, null); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( nOld == counter.value ); + db.close(); + } + + private void testAuthorizer(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder authRc = new ValueHolder<>(0); + final Sqlite.Authorizer auth = new Sqlite.Authorizer(){ + public int call(int op, String s0, String s1, String s2, String s3){ + ++counter.value; + //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3); + return authRc.value; + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + db.setAuthorizer(auth); + execSql(db, "UPDATE t SET a=1"); + affirm( 1 == counter.value ); + authRc.value = Sqlite.DENY; + int rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( Sqlite.AUTH==rc ); + db.setAuthorizer(null); + rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( 0==rc ); + db.close(); + } + + private void testBlobOpen(){ + final Sqlite db = openDb(); + + execSql(db, "CREATE TABLE T(a BLOB);" + +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');" + ); + Sqlite.Blob b = db.blobOpen("main", "t", "a", + db.lastInsertRowId(), true); + affirm( 3==b.bytes() ); + b.write(new byte[] {100, 101, 102 /*"DEF"*/}, 0); + b.close(); + Sqlite.Stmt stmt = db.prepare("SELECT length(a), a FROM t ORDER BY a"); + affirm( stmt.step() ); + affirm( 3 == stmt.columnInt(0) ); + affirm( "def".equals(stmt.columnText16(1)) ); + stmt.finalizeStmt(); + + b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), false); + final byte[] tgt = new byte[3]; + b.read( tgt, 0 ); + affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" ); + execSql(db,"UPDATE t SET a=zeroblob(10) WHERE rowid=2"); + b.close(); + b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), true); + byte[] bw = new byte[]{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + }; + b.write(bw, 0); + byte[] br = new byte[10]; + b.read(br, 0); + for( int i = 0; i < br.length; ++i ){ + affirm(bw[i] == br[i]); + } + b.close(); + db.close(); + } + + void testPrepareMulti(){ + final ValueHolder fCount = new ValueHolder<>(0); + final ValueHolder mCount = new ValueHolder<>(0); + try (Sqlite db = openDb()) { + execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)"); + db.createFunction("counter", -1, new ScalarFunction(){ + @Override public void xFunc(SqlFunction.Arguments args){ + ++fCount.value; + args.resultNull(); + } + } + ); + final Sqlite.PrepareMulti pm = new Sqlite.PrepareMultiFinalize( + new Sqlite.PrepareMulti() { + @Override public void call(Sqlite.Stmt q){ + ++mCount.value; + while(q.step()){} + } + } + ); + final String sql = "select counter(*) from t;"+ + "select counter(*) from t; /* comment */"+ + "select counter(*) from t; -- comment\n" + ; + db.prepareMulti(sql, pm); + } + affirm( 3 == mCount.value ); + affirm( 9 == fCount.value ); + } + + + /* Copy/paste/rename this to add new tests. */ + private void _testTemplate(){ + try (Sqlite db = openDb()) { + Sqlite.Stmt stmt = db.prepare("SELECT 1"); + stmt.finalizeStmt(); } - affirm( 1 == xDestroyCalled.value ); } private void runTests(boolean fromThread) throws Exception { @@ -305,7 +966,7 @@ private void runTests(boolean fromThread) throws Exception { mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); java.util.Collections.shuffle(mlist); } - if( listRunTests ){ + if( (!fromThread && listRunTests>0) || listRunTests>1 ){ synchronized(this.getClass()){ if( !fromThread ){ out("Initial test"," list: "); @@ -344,8 +1005,7 @@ public void run() { listErrors.add(e); } }finally{ - affirm( CApi.sqlite3_java_uncache_thread() ); - affirm( !CApi.sqlite3_java_uncache_thread() ); + Sqlite.uncacheThread(); } } @@ -368,7 +1028,9 @@ public void run() { some chaos for cross-thread contention. -list-tests: outputs the list of tests being run, minus some - which are hard-coded. This is noisy in multi-threaded mode. + which are hard-coded. In multi-threaded mode, use this twice to + to emit the list run by each thread (which may differ from the initial + list, in particular if -shuffle is used). -fail: forces an exception to be thrown during the test run. Use with -shuffle to make its appearance unpredictable. @@ -397,7 +1059,7 @@ public static void main(String[] args) throws Exception { }else if(arg.equals("shuffle")){ shuffle = true; }else if(arg.equals("list-tests")){ - listRunTests = true; + ++listRunTests; }else if(arg.equals("fail")){ forceFail = true; }else if(arg.equals("sqllog")){ @@ -415,39 +1077,29 @@ public static void main(String[] args) throws Exception { } if( sqlLog ){ - if( CApi.sqlite3_compileoption_used("ENABLE_SQLLOG") ){ - final ConfigSqllogCallback log = new ConfigSqllogCallback() { - @Override public void call(sqlite3 db, String msg, int op){ + if( Sqlite.compileOptionUsed("ENABLE_SQLLOG") ){ + Sqlite.libConfigSqlLog( new Sqlite.ConfigSqlLog() { + @Override public void call(Sqlite db, String msg, int op){ switch(op){ case 0: outln("Opening db: ",db); break; case 1: outln("SQL ",db,": ",msg); break; case 2: outln("Closing db: ",db); break; } } - }; - int rc = CApi.sqlite3_config( log ); - affirm( 0==rc ); - rc = CApi.sqlite3_config( (ConfigSqllogCallback)null ); - affirm( 0==rc ); - rc = CApi.sqlite3_config( log ); - affirm( 0==rc ); + } + ); }else{ outln("WARNING: -sqllog is not active because library was built ", "without SQLITE_ENABLE_SQLLOG."); } } if( configLog ){ - final ConfigLogCallback log = new ConfigLogCallback() { + Sqlite.libConfigLog( new Sqlite.ConfigLog() { @Override public void call(int code, String msg){ - outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg); + outln("ConfigLog: ",Sqlite.errstr(code),": ", msg); }; - }; - int rc = CApi.sqlite3_config( log ); - affirm( 0==rc ); - rc = CApi.sqlite3_config( (ConfigLogCallback)null ); - affirm( 0==rc ); - rc = CApi.sqlite3_config( log ); - affirm( 0==rc ); + } + ); } quietMode = squelchTestOutput; @@ -480,39 +1132,16 @@ public static void main(String[] args) throws Exception { } final long timeStart = System.currentTimeMillis(); - int nLoop = 0; - switch( CApi.sqlite3_threadsafe() ){ /* Sanity checking */ - case 0: - affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ), - "Could not switch to single-thread mode." ); - affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ), - "Could switch to multithread mode." ); - affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ), - "Could not switch to serialized threading mode." ); - outln("This is a single-threaded build. Not using threads."); - nThread = 1; - break; - case 1: - case 2: - affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ), - "Could not switch to single-thread mode." ); - affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ), - "Could not switch to multithread mode." ); - affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ), - "Could not switch to serialized threading mode." ); - break; - default: - affirm( false, "Unhandled SQLITE_THREADSAFE value." ); - } outln("libversion_number: ", - CApi.sqlite3_libversion_number(),"\n", - CApi.sqlite3_libversion(),"\n",CApi.SQLITE_SOURCE_ID,"\n", + Sqlite.libVersionNumber(),"\n", + Sqlite.libVersion(),"\n",Sqlite.libSourceId(),"\n", "SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe()); final boolean showLoopCount = (nRepeat>1 && nThread>1); if( showLoopCount ){ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); } if( takeNaps ) outln("Napping between tests is enabled."); + int nLoop = 0; for( int n = 0; n < nRepeat; ++n ){ ++nLoop; if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop); @@ -554,7 +1183,7 @@ public static void main(String[] args) throws Exception { if( doSomethingForDev ){ CApi.sqlite3_jni_internal_details(); } - affirm( 0==CApi.sqlite3_release_memory(1) ); + affirm( 0==Sqlite.libReleaseMemory(1) ); CApi.sqlite3_shutdown(); int nMethods = 0; int nNatives = 0; diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java b/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java index 009936a43..7549bb97b 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java @@ -9,13 +9,13 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains a set of tests for the sqlite3 JNI bindings. +** This file contains the ValueHolder utility class. */ package org.sqlite.jni.wrapper1; /** A helper class which simply holds a single value. Its primary use - is for communicating values out of anonymous classes, as doing so + is for communicating values out of anonymous callbacks, as doing so requires a "final" reference. */ public class ValueHolder { diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java new file mode 100644 index 000000000..a3905567d --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java @@ -0,0 +1,42 @@ +/* +** 2023-10-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 is part of the wrapper1 interface for sqlite3. +*/ +package org.sqlite.jni.wrapper1; + +/** + A SqlFunction implementation for window functions. The T type + represents the type of data accumulated by this function while it + works. e.g. a SUM()-like UDF might use Integer or Long and a + CONCAT()-like UDF might use a StringBuilder or a List. +*/ +public abstract class WindowFunction extends AggregateFunction { + + /** + As for the xInverse() argument of the C API's + sqlite3_create_window_function(). If this function throws, the + exception is reported via sqlite3_result_error(). + */ + public abstract void xInverse(SqlFunction.Arguments args); + + /** + As for the xValue() argument of the C API's + sqlite3_create_window_function(). If this function throws, it is + translated into sqlite3_result_error(). + + Note that the passed-in object will not actually contain any + arguments for xValue() but will contain the context object needed + for setting the call's result or error state. + */ + public abstract void xValue(SqlFunction.Arguments args); + +} diff --git a/ext/misc/randomjson.c b/ext/misc/randomjson.c index 3a6f545fe..cacc4feb6 100644 --- a/ext/misc/randomjson.c +++ b/ext/misc/randomjson.c @@ -26,9 +26,14 @@ ** ** .load ./randomjson ** SELECT random_json(1); +** SELECT random_json5(1); */ -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT1 +#ifdef SQLITE_STATIC_RANDOMJSON +# include "sqlite3.h" +#else +# include "sqlite3ext.h" + SQLITE_EXTENSION_INIT1 +#endif #include #include #include @@ -51,17 +56,18 @@ static unsigned int prngInt(Prng *p){ return p->x ^ p->y; } -static const char *azJsonAtoms[] = { - /* JSON /* JSON-5 */ +static char *azJsonAtoms[] = { + /* JSON JSON-5 */ "0", "0", "1", "1", "-1", "-1", "2", "+2", - "3", "3", - "2.5", "2.5", + "3DDDD", "3DDDD", + "2.5DD", "2.5DD", "0.75", ".75", "-4.0e2", "-4.e2", "5.0e-3", "+5e-3", + "6.DDe+0DD", "6.DDe+0DD", "0", "0x0", "512", "0x200", "256", "+0x100", @@ -73,12 +79,14 @@ static const char *azJsonAtoms[] = { "-9.0e999", "-Infinity", "9.0e999", "+Infinity", "null", "NaN", - "-0.0005123", "-0.0005123", + "-0.0005DD", "-0.0005DD", "4.35e-3", "+4.35e-3", "\"gem\\\"hay\"", "\"gem\\\"hay\"", "\"icy'joy\"", "'icy\\'joy\'", "\"keylog\"", "\"key\\\nlog\"", "\"mix\\\\\\tnet\"", "\"mix\\\\\\tnet\"", + "\"oat\\r\\n\"", "\"oat\\r\\n\"", + "\"\\fpan\\b\"", "\"\\fpan\\b\"", "{}", "{}", "[]", "[]", "[]", "[/*empty*/]", @@ -89,19 +97,20 @@ static const char *azJsonAtoms[] = { "\"day\"", "\"day\"", "\"end\"", "'end'", "\"fly\"", "\"fly\"", + "\"\\u00XX\\u00XX\"", "\"\\xXX\\xXX\"", + "\"y\\uXXXXz\"", "\"y\\uXXXXz\"", "\"\"", "\"\"", }; -static const char *azJsonTemplate[] = { +static char *azJsonTemplate[] = { /* JSON JSON-5 */ - "{\"a\":%,\"b\":%,\"c\":%}", "{a:%,b:%,c:%}", + "{\"a\":%,\"b\":%,\"cDD\":%}", "{a:%,b:%,cDD:%}", "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"e\":%}", "{a:%,b:%,c:%,d:%,e:%}", - "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,\"\":%}", + "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,'':%}", "{\"d\":%}", "{d:%}", "{\"eeee\":%, \"ffff\":%}", "{eeee:% /*and*/, ffff:%}", - "{\"$g\":%,\"_h_\":%}", "{$g:%,_h_:%,}", + "{\"$g\":%,\"_h_\":%,\"a b c d\":%}", "{$g:%,_h_:%,\"a b c d\":%}", "{\"x\":%,\n \"y\":%}", "{\"x\":%,\n \"y\":%}", - "{\"a b c d\":%,\"e\":%,\"f\":%,\"x\":%,\"y\":%}", - "{\"a b c d\":%,e:%,f:%,x:%,y:%}", + "{\"\\u00XX\":%,\"\\uXXXX\":%}", "{\"\\xXX\":%,\"\\uXXXX\":%}", "{\"Z\":%}", "{Z:%,}", "[%]", "[%,]", "[%,%]", "[%,%]", @@ -122,15 +131,13 @@ static void jsonExpand( unsigned int r /* Growth probability 0..1000. 0 means no growth */ ){ unsigned int i, j, k; - const char *z; + char *z; + char *zX; size_t n; + char zBuf[200]; j = 0; - if( zSrc==0 ){ - k = prngInt(p)%(count(azJsonTemplate)/2); - k = k*2 + eType; - zSrc = azJsonTemplate[k]; - } + if( zSrc==0 ) zSrc = "%"; if( strlen(zSrc)>=STRSZ/10 ) r = 0; for(i=0; zSrc[i]; i++){ if( zSrc[i]!='%' ){ @@ -149,9 +156,36 @@ static void jsonExpand( z = azJsonTemplate[k]; } n = strlen(z); + if( (zX = strstr(z,"XX"))!=0 ){ + unsigned int y = prngInt(p); + if( (y&0xff)==((y>>8)&0xff) ) y += 0x100; + while( (y&0xff)==((y>>16)&0xff) || ((y>>8)&0xff)==((y>>16)&0xff) ){ + y += 0x10000; + } + memcpy(zBuf, z, n+1); + z = zBuf; + zX = strstr(z,"XX"); + while( zX!=0 ){ + zX[0] = "0123456789abcdef"[y%16]; y /= 16; + zX[1] = "0123456789abcdef"[y%16]; y /= 16; + zX = strstr(zX, "XX"); + } + }else if( (zX = strstr(z,"DD"))!=0 ){ + unsigned int y = prngInt(p); + memcpy(zBuf, z, n+1); + z = zBuf; + zX = strstr(z,"DD"); + while( zX!=0 ){ + zX[0] = "0123456789"[y%10]; y /= 10; + zX[1] = "0123456789"[y%10]; y /= 10; + zX = strstr(zX, "DD"); + } + } + assert( strstr(z, "XX")==0 ); + assert( strstr(z, "DD")==0 ); if( j+n=zEnd && nDigits>0 && eValid && nonNum==0; } +/* +** Convert a floating point value to an integer. Or, if this cannot be +** done in a way that avoids 'outside the range of representable values' +** warnings from UBSAN, return 0. +** +** This function is a modified copy of internal SQLite function +** sqlite3RealToI64(). +*/ +static sqlite3_int64 totypeDoubleToInt(double r){ + if( r<-9223372036854774784.0 ) return 0; + if( r>+9223372036854774784.0 ) return 0; + return (sqlite3_int64)r; +} + /* ** tointeger(X): If X is any value (integer, double, blob, or string) that ** can be losslessly converted into an integer, then make the conversion and @@ -365,7 +379,7 @@ static void tointegerFunc( switch( sqlite3_value_type(argv[0]) ){ case SQLITE_FLOAT: { double rVal = sqlite3_value_double(argv[0]); - sqlite3_int64 iVal = (sqlite3_int64)rVal; + sqlite3_int64 iVal = totypeDoubleToInt(rVal); if( rVal==(double)iVal ){ sqlite3_result_int64(context, iVal); } @@ -440,7 +454,7 @@ static void torealFunc( case SQLITE_INTEGER: { sqlite3_int64 iVal = sqlite3_value_int64(argv[0]); double rVal = (double)iVal; - if( iVal==(sqlite3_int64)rVal ){ + if( iVal==totypeDoubleToInt(rVal) ){ sqlite3_result_double(context, rVal); } break; diff --git a/ext/recover/dbdata.c b/ext/recover/dbdata.c index 25a6e9fd6..b6cb26ecd 100644 --- a/ext/recover/dbdata.c +++ b/ext/recover/dbdata.c @@ -582,6 +582,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ bNextPage = 1; }else{ iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); + if( nPayload>0x7fffff00 ) nPayload &= 0x3fff; } /* If this is a leaf intkey cell, load the rowid */ diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 1705abd51..013bb0b5b 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -717,7 +717,7 @@ static int nodeAcquire( ** increase its reference count and return it. */ if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){ - if( pParent && pParent!=pNode->pParent ){ + if( pParent && ALWAYS(pParent!=pNode->pParent) ){ RTREE_IS_CORRUPT(pRtree); return SQLITE_CORRUPT_VTAB; } @@ -3452,7 +3452,7 @@ static int rtreeSqlInit( } sqlite3_free(zSql); } - if( pRtree->nAux ){ + if( pRtree->nAux && rc!=SQLITE_NOMEM ){ pRtree->zReadAuxSql = sqlite3_mprintf( "SELECT * FROM \"%w\".\"%w_rowid\" WHERE rowid=?1", zDb, zPrefix); @@ -4141,15 +4141,13 @@ static int rtreeCheckTable( check.zTab = zTab; /* Find the number of auxiliary columns */ - if( check.rc==SQLITE_OK ){ - pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); - if( pStmt ){ - nAux = sqlite3_column_count(pStmt) - 2; - sqlite3_finalize(pStmt); - }else - if( check.rc!=SQLITE_NOMEM ){ - check.rc = SQLITE_OK; - } + pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); + if( pStmt ){ + nAux = sqlite3_column_count(pStmt) - 2; + sqlite3_finalize(pStmt); + }else + if( check.rc!=SQLITE_NOMEM ){ + check.rc = SQLITE_OK; } /* Find number of dimensions in the rtree table. */ @@ -4204,6 +4202,7 @@ static int rtreeIntegrity( if( rc==SQLITE_OK && *pzErr ){ *pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z", pRtree->zDb, pRtree->zName, *pzErr); + if( (*pzErr)==0 ) rc = SQLITE_NOMEM; } return rc; } diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 4f682a13d..acb945194 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -2348,9 +2348,7 @@ void sqlite3session_delete(sqlite3_session *pSession){ ** associated hash-tables. */ sessionDeleteTable(pSession, pSession->pTable); - /* Assert that all allocations have been freed and then free the - ** session object itself. */ - // assert( pSession->nMalloc==0 ); + /* Free the session object. */ sqlite3_free(pSession); } diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index c0cab212d..f0cff463a 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -42,7 +42,9 @@ # 1) Consolidate the code generation for sqlite3*.*js into a script # which generates the makefile code, rather than using $(call) and # $(eval), or at least centralize the setup of the numerous vars -# related to each build variant $(JS_BUILD_MODES). +# related to each build variant $(JS_BUILD_MODES). (Update: an +# external script was attempted but it's even less legible than the +# $(eval) indirection going on in this file. # default: all #default: quick @@ -51,9 +53,39 @@ MAKEFILE := $(lastword $(MAKEFILE_LIST)) CLEAN_FILES := DISTCLEAN_FILES := ./--dummy-- release: oz -# JS_BUILD_MODES exists solely to reduce repetition in documentation -# below. + +######################################################################## +# JS_BUILD_NAMES exists for documentation purposes only. It enumerates +# the core build styles: +# +# - sqlite3 = canonical library build +# +# - sqlite3-wasmfs = WASMFS-capable library build +# +JS_BUILD_NAMES := sqlite3 sqlite3-wasmfs + +######################################################################## +# JS_BUILD_MODES exists for documentation purposes only. It enumerates +# the various "flavors" of build, each of which requires slight +# customization of the output: +# +# - vanilla = plain-vanilla JS for use in browsers. This is the +# canonical build mode. +# +# - esm = ES6 module, a.k.a. ESM, for use in browsers. +# +# - bundler-friendly = esm slightly tweaked for "bundler" +# tools. Bundlers are invariably based on node.js, so these builds +# are intended to be read at build-time by node.js but with a final +# target of browsers. +# +# - node = for use by node.js for node.js, as opposed to by node.js on +# behalf o browser-side code (use bundler-friendly for that). Note +# that persistent storage (OPFS) is not available in these builds. +# JS_BUILD_MODES := vanilla esm bunder-friendly node + +######################################################################## # Emscripten SDK home dir and related binaries... EMSDK_HOME ?= $(word 1,$(wildcard $(HOME)/emsdk $(HOME)/src/emsdk)) emcc.bin ?= $(word 1,$(wildcard $(EMSDK_HOME)/upstream/emscripten/emcc) $(shell which emcc)) @@ -93,11 +125,14 @@ else maybe-wasm-strip = $(wasm-strip) endif +######################################################################## +# dir.top = the top dir of the canonical build tree, where +# sqlite3.[ch] live. dir.top := ../.. -# Reminder: some Emscripten flags require absolute paths but we want -# relative paths for most stuff simply to reduce noise. The -# $(abspath...) GNU make function can transform relative paths to -# absolute. +# Maintenance reminder: some Emscripten flags require absolute paths +# but we want relative paths for most stuff simply to reduce +# noise. The $(abspath...) GNU make function can transform relative +# paths to absolute. dir.wasm := $(patsubst %/,%,$(dir $(MAKEFILE))) dir.api := api dir.jacc := jaccwabyt @@ -143,12 +178,14 @@ endif # Set up sqlite3.c and sqlite3.h... # # To build with SEE (https://sqlite.org/see), either put sqlite3-see.c -# in the top of this build tree or pass -# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only -# encryption modules with no 3rd-party dependencies will currently -# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not -# coincidentally, those 3 modules are included in the sqlite3-see.c -# bundle. +# in $(dir.top) or pass sqlite3.c=PATH_TO_sqlite3-see.c to the $(MAKE) +# invocation. Note that only encryption modules with no 3rd-party +# dependencies will currently work here: AES256-OFB, AES128-OFB, and +# AES128-CCM. Not coincidentally, those 3 modules are included in the +# sqlite3-see.c bundle. Note, however, that distributing an SEE build +# of the WASM on a public site is in violation of the SEE license +# because it effectively provides a usable copy of the SEE build to +# all visitors. # # A custom sqlite3.c must not have any spaces in its name. # $(sqlite3.canonical.c) must point to the sqlite3.c in @@ -193,6 +230,10 @@ SQLITE_OPT = \ # can be used to find errant uses of sqlite3_js_vfs_create_file() # in client code. +########################################################################@ +# It's important that sqlite3.h be built to completion before any +# other parts of the build run, thus we use .NOTPARALLEL to disable +# parallel build of that file and its dependants. .NOTPARALLEL: $(sqlite3.h) $(sqlite3.h): $(MAKE) -C $(dir.top) sqlite3.c @@ -242,6 +283,7 @@ ifneq (,$(sqlite3_wasm_extra_init.c)) cflags.wasm_extra_init := -DSQLITE_WASM_EXTRA_INIT endif +######################################################################### # bin.version-info = binary to output various sqlite3 version info for # embedding in the JS files and in building the distribution zip file. # It must NOT be in $(dir.tmp) because we need it to survive the @@ -251,11 +293,12 @@ bin.version-info := $(dir.top)/version-info $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile $(MAKE) -C $(dir.top) version-info +######################################################################### # bin.stripcomments is used for stripping C/C++-style comments from JS # files. The JS files contain large chunks of documentation which we # don't need for all builds. That app's -k flag is of particular # importance here, as it allows us to retain the opening comment -# blocks, which contain the license header and version info. +# block(s), which contain the license header and version info. bin.stripccomments := $(dir.tool)/stripccomments $(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE) $(CC) -o $@ $< @@ -287,6 +330,9 @@ DISTCLEAN_FILES += $(bin.stripccomments) # c-pp.c was written specifically for the sqlite project's JavaScript # builds but is maintained as a standalone project: # https://fossil.wanderinghorse.net/r/c-pp +# +# Note that the SQLITE_... build flags used here have NO EFFECT on the +# JS/WASM build. They are solely for use with $(bin.c-pp) itself. bin.c-pp := ./c-pp $(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE) $(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \ @@ -347,6 +393,7 @@ emcc_opt_full := $(emcc_opt) -g3 # -Oz when small deliverable size is a priority. ######################################################################## +######################################################################## # EXPORTED_FUNCTIONS.* = files for use with Emscripten's # -sEXPORTED_FUNCTION flag. EXPORTED_FUNCTIONS.api.main := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) @@ -358,6 +405,7 @@ EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api $(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(sqlite3.c) $(MAKEFILE) cat $(EXPORTED_FUNCTIONS.api.in) > $@ +######################################################################## # sqlite3-license-version.js = generated JS file with the license # header and version info. sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js @@ -370,20 +418,35 @@ sqlite3-api-build-version.js := $(dir.tmp)/sqlite3-api-build-version.js # sqlite3-api.jses = the list of JS files which make up # $(sqlite3-api.js.in), in the order they need to be assembled. sqlite3-api.jses := $(sqlite3-license-version.js) +# sqlite3-api-prologue.js: initial boostrapping bits: sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js +# whwhasm.js and jaccwabyt.js: Low-level utils, mostly replacing +# Emscripten glue: sqlite3-api.jses += $(dir.common)/whwasmutil.js sqlite3-api.jses += $(dir.jacc)/jaccwabyt.js +# sqlite3-api-glue.js Glues the previous part together: sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js +# $(sqlite3-api-build-version.js) = library version info sqlite3-api.jses += $(sqlite3-api-build-version.js) +# sqlite3-api-oo1.js = the oo1 API: sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js +# sqlite3-api-worker.js = the Worker1 API: sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js +# sqlite3-v-helper = helper APIs for VFSes and VTABLEs: sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js +# sqlite3-vfs-opfs.c-pp.js = the first OPFS VFS: sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js +# sqlite3-vfs-opfs-sahpool.c-pp.js = the second OPFS VFS: sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js +# sqlite3-api-cleanup.js = "finalizes" the build and cleans up +# any extraneous global symbols which are needed temporarily +# by the previous files. sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js +######################################################################## # SOAP.js is an external API file which is part of our distribution -# but not part of the sqlite3-api.js amalgamation. +# but not part of the sqlite3-api.js amalgamation. It's a component of +# the first OPFS VFS and necessarily an external file. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js)) sqlite3-api.ext.jses += $(SOAP.js.bld) @@ -438,7 +501,9 @@ endif # emcc flags for .c/.o. emcc.cflags := emcc.cflags += -std=c99 -fPIC -# -------------^^^^^^^^ we need c99 for $(sqlite3-wasm.c). +# -------------^^^^^^^^ we need c99 for $(sqlite3-wasm.c), primarily +# for variadic macros and snprintf() to implement +# sqlite3_wasm_enum_json(). emcc.cflags += -I. -I$(dir.top) ######################################################################## # emcc flags specific to building .js/.wasm files... @@ -459,14 +524,16 @@ emcc.jsflags += -sIMPORTED_MEMORY emcc.jsflags += -sSTRICT_JS=0 # STRICT_JS disabled due to: # https://github.com/emscripten-core/emscripten/issues/18610 -# TL;DR: does not work with MODULARIZE or EXPORT_ES6 as of version 3.1.31. +# TL;DR: does not work with MODULARIZE or EXPORT_ES6 as of version +# 3.1.31. The fix for that in newer emcc's is to throw a built-time +# error if STRICT_JS is used together with those options. # -sENVIRONMENT values for the various build modes: emcc.environment.vanilla := web,worker emcc.environment.bundler-friendly := $(emcc.environment.vanilla) emcc.environment.esm := $(emcc.environment.vanilla) emcc.environment.node := node -# Note that adding "node" to the list for the other builds causes +# Note that adding ",node" to the list for the other builds causes # Emscripten to generate code which confuses node: it cannot reliably # determine whether the build is for a browser or for node. @@ -518,13 +585,14 @@ emcc.jsflags += -sSTACK_SIZE=512KB # extern-post-js.js. However... using a temporary symbol name here # and then adding sqlite3InitModule() ourselves results in 2 global # symbols: we cannot "delete" the Emscripten-defined -# $(sqlite3.js.init-func) because it's declared with "var". +# $(sqlite3.js.init-func) from vanilla builds (as opposed to ESM +# builds) because it's declared with "var". sqlite3.js.init-func := sqlite3InitModule emcc.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. #emcc.jsflags += -sSTRICT # fails due to missing __syscall_...() #emcc.jsflags += -sALLOW_UNIMPLEMENTED_SYSCALLS -#emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API +#emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. fiddle needs the FS API #emcc.jsflags += -sABORTING_MALLOC # only for experimentation emcc.jsflags += -sALLOW_TABLE_GROWTH # ^^^^ -sALLOW_TABLE_GROWTH is required for installing new SQL UDFs @@ -568,15 +636,22 @@ emcc.jsflags += -sLLD_REPORT_UNDEFINED # -g3 debugging info, _huge_. ######################################################################## +######################################################################## +# $(sqlite3-api-build-version.js) injects the build version info into +# the bundle in JSON form. $(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) @echo "Making $@..." @{ \ - echo 'globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ - echo -n ' sqlite3.version = '; \ - $(bin.version-info) --json; \ - echo ';'; \ - echo '});'; \ + echo 'globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ + echo -n ' sqlite3.version = '; \ + $(bin.version-info) --json; \ + echo ';'; \ + echo '});'; \ } > $@ + +######################################################################## +# $(sqlite3-license-version.js) contains the license header and +# in-comment build version info. $(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) \ $(MAKEFILE) @echo "Making $@..."; { \ @@ -594,7 +669,11 @@ $(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) ######################################################################## # --post-js and --pre-js are emcc flags we use to append/prepend JS to # the generated emscripten module file. These rules set up the core -# pre/post files for use by the various builds. +# pre/post files for use by the various builds. --pre-js is used to +# inject code which needs to run as part of the pre-WASM-load phase. +# --post-js injects code which runs after the WASM module is loaded +# and includes the entirety of the library plus some +# Emscripten-specific post-bootstrapping code. pre-js.js.in := $(dir.api)/pre-js.c-pp.js post-js.js.in := $(dir.tmp)/post-js.c-pp.js post-jses.js := \ @@ -612,18 +691,26 @@ $(post-js.js.in): $(post-jses.js) $(MAKEFILE) ######################################################################## # call-make-pre-post is a $(call)able which creates rules for -# pre-js-$(1)-$(2).js. $1 = the base name of the JS file on whose -# behalf this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is +# pre-js.$(1)-$(2).js. $1 = the base name of the JS file on whose +# behalf this pre-js is for (one of: $(JS_BUILD_NAMES)). $2 is # the build mode: one of $(JS_BUILD_MODES). This sets up # --[extern-][pre/post]-js flags in $(pre-post-$(1)-$(2).flags) and # dependencies in $(pre-post-$(1)-$(2).deps). The resulting files get # filtered using $(C-PP.FILTER). Any flags necessary for such # filtering need to be set in $(c-pp.D.$(1)-$(2)) before $(call)ing # this. +# +# Maintenance note: a shell script was written to generate these rules +# with the hope that it would make them more legible and maintainable, +# but embedding makefile code in another language makes it even less +# legible than having the level of $(eval) indirection which we have +# here. define call-make-pre-post pre-post-$(1)-$(2).flags ?= -pre-js.js.$(1)-$(2) := $$(dir.tmp)/pre-js.$(1)-$(2).intermediary.js -$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) +pre-js.js.$(1)-$(2).intermediary := $$(dir.tmp)/pre-js.$(1)-$(2).intermediary.js +pre-js.js.$(1)-$(2) := $$(dir.tmp)/pre-js.$(1)-$(2).js +#$$(error $$(pre-js.js.$(1)-$(2).intermediary) $$(pre-js.js.$(1)-$(2))) +$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)-$(2).intermediary),$$(c-pp.D.$(1)-$(2)))) post-js.js.$(1)-$(2) := $$(dir.tmp)/post-js.$(1)-$(2).js $$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) extern-post-js.js.$(1)-$(2) := $$(dir.tmp)/extern-post-js.$(1)-$(2).js @@ -634,8 +721,8 @@ pre-post-common.flags.$(1)-$(2) := \ --extern-post-js=$$(extern-post-js.js.$(1)-$(2)) pre-post-jses.$(1)-$(2).deps := $$(pre-post-jses.deps.common) \ $$(post-js.js.$(1)-$(2)) $$(extern-post-js.js.$(1)-$(2)) -$$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(1)-$(2)) $$(MAKEFILE) - cp $$(pre-js.js.$(1)-$(2)) $$@ +$$(pre-js.js.$(1)-$(2)): $$(pre-js.js.$(1)-$(2).intermediary) $$(MAKEFILE) + cp $$(pre-js.js.$(1)-$(2).intermediary) $$@ @if [ sqlite3-wasmfs = $(1) ]; then \ echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \ elif [ sqlite3 != $(1) ]; then \ @@ -643,10 +730,10 @@ $$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(1)-$(2)) $$(MAKEFILE) fi >> $$@ pre-post-$(1)-$(2).deps := \ $$(pre-post-jses.$(1)-$(2).deps) \ - $$(dir.tmp)/pre-js-$(1)-$(2).js + $$(dir.tmp)/pre-js.$(1)-$(2).js pre-post-$(1)-$(2).flags += \ $$(pre-post-common.flags.$(1)-$(2)) \ - --pre-js=$$(dir.tmp)/pre-js-$(1)-$(2).js + --pre-js=$$(dir.tmp)/pre-js.$(1)-$(2).js endef # /post-js and pre-js ######################################################################## @@ -683,8 +770,8 @@ sqlite3-wasmfs.cfiles := $(sqlite3-wasm.cfiles) # Upstream RFE: # https://github.com/emscripten-core/emscripten/issues/18237 # -# Maintenance reminder: Mac sed works differently than GNU sed, so -# don't use sed for this. +# Maintenance reminder: Mac sed works differently than GNU sed, so we +# use awk instead of sed for this. define SQLITE3.xJS.ESM-EXPORT-DEFAULT if [ x1 = x$(1) ]; then \ echo "Fragile workaround for emscripten/issues/18237. See SQLITE3.xJS.RECIPE."; \ @@ -700,6 +787,7 @@ if [ x1 = x$(1) ]; then \ fi endef +######################################################################## # extern-post-js* and extern-pre-js* are files for use with # Emscripten's --extern-pre-js and --extern-post-js flags. extern-pre-js.js := $(dir.api)/extern-pre-js.js @@ -711,11 +799,12 @@ pre-post-common.flags := \ # pre-post-jses.deps.* = a list of dependencies for the # --[extern-][pre/post]-js files. pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) + ######################################################################## # SETUP_LIB_BUILD_MODE is a $(call)'able which sets up numerous pieces # for one of the build modes. # -# $1 = one of: sqlite3, sqlite3-wasmfs +# $1 = one of: $(JS_BUILD_NAMES) # $2 = build mode name: one of $(JS_BUILD_MODES) # $3 = 1 for ESM build mode, else 0 # $4 = resulting sqlite-api JS/MJS file @@ -726,7 +815,8 @@ pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) # Maintenance reminder: be careful not to introduce spaces around args # ($1, $2), otherwise string concatenation will malfunction. # -# emcc.environment.$(2) must be set to a value for the -sENVIRONMENT flag. +# emcc.environment.$(2) must be set to a value for emcc's +# -sENVIRONMENT flag. # # $(cflags.$(1)) and $(cflags.$(1).$(2)) may be defined to append # CFLAGS to a given build mode. @@ -781,8 +871,7 @@ sqlite3-node.mjs := $(dir.dout)/sqlite3-node.mjs $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,vanilla,0,\ $(sqlite3-api.js), $(sqlite3.js))) $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,esm,1,\ - $(sqlite3-api.mjs), $(sqlite3.mjs), \ - -Dtarget=es6-module, -sEXPORT_ES6 -sUSE_ES6_IMPORT_META)) + $(sqlite3-api.mjs), $(sqlite3.mjs), -Dtarget=es6-module)) $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,bundler-friendly,1,\ $(sqlite3-api-bundler-friendly.mjs),$(sqlite3-bundler-friendly.mjs),\ $(c-pp.D.sqlite3-esm) -Dtarget=es6-bundler-friendly)) @@ -798,7 +887,7 @@ $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,node,1,\ # -Dtarget=es6-module -Dtarget=es6-bundler-friendly: intended for # "bundler-friendly" ESM module build. These have some restrictions # on how URL() objects are constructed in some contexts: URLs which -# refer to files which are part of this project must be references +# refer to files which are part of this project must be referenced # as string literals so that bundlers' static-analysis tools can # find those files and include them in their bundles. # @@ -854,7 +943,7 @@ dir.sql := sql speedtest1 := ../../speedtest1 speedtest1.c := ../../test/speedtest1.c speedtest1.sql := $(dir.sql)/speedtest1.sql -speedtest1.cliflags := --size 25 --big-transactions +speedtest1.cliflags := --size 10 --big-transactions $(speedtest1): $(MAKE) -C ../.. speedtest1 $(speedtest1.sql): $(speedtest1) $(MAKEFILE) @@ -1087,4 +1176,4 @@ endif # Run local web server for the test/demo pages. httpd: - althttpd -max-age 1 -enable-sab -page index.html + althttpd -max-age 1 -enable-sab 1 -page index.html diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api index ad2872d83..57cd61eb9 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -63,6 +63,7 @@ _sqlite3_file_control _sqlite3_finalize _sqlite3_free _sqlite3_get_auxdata +_sqlite3_get_autocommit _sqlite3_initialize _sqlite3_keyword_count _sqlite3_keyword_name diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index f23a02366..29efb3e07 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -188,6 +188,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], ["sqlite3_finalize", "int", "sqlite3_stmt*"], ["sqlite3_free", undefined,"*"], + ["sqlite3_get_autocommit", "int", "sqlite3*"], ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"], ["sqlite3_initialize", undefined], /*["sqlite3_interrupt", undefined, "sqlite3*" diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index 4677b8976..160d59db5 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -1,3 +1,4 @@ +//#ifnot omit-oo1 /* 2022-07-22 @@ -1940,4 +1941,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*main-window-only bits*/ }); - +//#else +/* Built with the omit-oo1 flag. */ +//#endif ifnot omit-oo1 diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index 29f7d2be6..3099c19ec 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -1,3 +1,4 @@ +//#ifnot omit-oo1 /** 2022-07-22 @@ -62,7 +63,7 @@ ``` { - type: string, // one of: 'open', 'close', 'exec', 'config-get' + type: string, // one of: 'open', 'close', 'exec', 'export', 'config-get' messageId: OPTIONAL arbitrary value. The worker will copy it as-is into response messages to assist in client-side dispatching. @@ -325,6 +326,37 @@ passed only a string), noting that options.resultRows and options.columnNames may be populated by the call to db.exec(). + + ==================================================================== + "export" the current db + + To export the underlying database as a byte array... + + Message format: + + ``` + { + type: "export", + messageId: ...as above..., + dbId: ...as above... + } + ``` + + Response: + + ``` + { + type: "export", + messageId: ...as above..., + dbId: ...as above... + result: { + byteArray: Uint8Array (as per sqlite3_js_db_export()), + filename: the db filename, + mimetype: "application/x-sqlite3" + } + } + ``` + */ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ sqlite3.initWorker1API = function(){ @@ -658,3 +690,6 @@ sqlite3.initWorker1API = function(){ globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'}); }.bind({sqlite3}); }); +//#else +/* Built with the omit-oo1 flag. */ +//#endif ifnot omit-oo1 diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 300307e5e..618d0f085 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -147,6 +147,12 @@ # define SQLITE_OS_KV_OPTIONAL 1 #endif +/**********************************************************************/ +/* SQLITE_S... */ +#ifndef SQLITE_STRICT_SUBTYPE +# define SQLITE_STRICT_SUBTYPE 1 +#endif + /**********************************************************************/ /* SQLITE_T... */ #ifndef SQLITE_TEMP_STORE @@ -1097,7 +1103,7 @@ const char * sqlite3_wasm_enum_json(void){ M(xShadowName, "i(s)"); } _StructBinder; #undef CurrentStruct - + /** ** Workaround: in order to map the various inner structs from ** sqlite3_index_info, we have to uplift those into constructs we diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index 1f88b713a..cd78ed4bc 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -1,3 +1,4 @@ +//#ifnot omit-oo1 /* 2022-08-24 @@ -199,10 +200,11 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi msg = Object.create(null); msg.type = arguments[0]; msg.args = arguments[1]; + msg.dbId = msg.args.dbId; }else{ toss("Invalid arugments for sqlite3Worker1Promiser()-created factory."); } - if(!msg.dbId) msg.dbId = dbId; + if(!msg.dbId && msg.type!=='open') msg.dbId = dbId; msg.messageId = genMsgId(msg); msg.departureTime = performance.now(); const proxy = Object.create(null); @@ -275,3 +277,6 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = { , onerror: (...args)=>console.error('worker1 promiser error',...args) }; +//#else +/* Built with the omit-oo1 flag. */ +//#endif ifnot omit-oo1 diff --git a/ext/wasm/api/sqlite3-worker1.c-pp.js b/ext/wasm/api/sqlite3-worker1.c-pp.js index 220722ffe..74de9ec7e 100644 --- a/ext/wasm/api/sqlite3-worker1.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1.c-pp.js @@ -1,3 +1,4 @@ +//#ifnot omit-oo1 /* 2022-05-23 @@ -48,3 +49,6 @@ import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs'; } //#endif sqlite3InitModule().then(sqlite3 => sqlite3.initWorker1API()); +//#else +/* Built with the omit-oo1 flag. */ +//#endif ifnot omit-oo1 diff --git a/ext/wasm/batch-runner-sahpool.html b/ext/wasm/batch-runner-sahpool.html new file mode 100644 index 000000000..ad7e7b540 --- /dev/null +++ b/ext/wasm/batch-runner-sahpool.html @@ -0,0 +1,86 @@ + + + + + + + + sqlite3-api batch SQL runner for the SAHPool VFS + + +

      sqlite3-api batch SQL runner for the SAHPool VFS
      +
      + + + + +
      +
      + + + + diff --git a/ext/wasm/batch-runner-sahpool.js b/ext/wasm/batch-runner-sahpool.js new file mode 100644 index 000000000..dfa5044a9 --- /dev/null +++ b/ext/wasm/batch-runner-sahpool.js @@ -0,0 +1,341 @@ +/* + 2023-11-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. + + *********************************************************************** + + A basic batch SQL runner for the SAHPool VFS. This file must be run in + a worker thread. This is not a full-featured app, just a way to get some + measurements for batch execution of SQL for the OPFS SAH Pool VFS. +*/ +'use strict'; + +const wMsg = function(msgType,...args){ + postMessage({ + type: msgType, + data: args + }); +}; +const toss = function(...args){throw new Error(args.join(' '))}; +const warn = (...args)=>{ wMsg('warn',...args); }; +const error = (...args)=>{ wMsg('error',...args); }; +const log = (...args)=>{ wMsg('stdout',...args); } +let sqlite3; +const urlParams = new URL(globalThis.location.href).searchParams; +const cacheSize = (()=>{ + if(urlParams.has('cachesize')) return +urlParams.get('cachesize'); + return 200; +})(); + + +/** Throws if the given sqlite3 result code is not 0. */ +const checkSqliteRc = (dbh,rc)=>{ + if(rc) toss("Prepare failed:",sqlite3.capi.sqlite3_errmsg(dbh)); +}; + +const sqlToDrop = [ + "SELECT type,name FROM sqlite_schema ", + "WHERE name NOT LIKE 'sqlite\\_%' escape '\\' ", + "AND name NOT LIKE '\\_%' escape '\\'" +].join(''); + +const clearDbSqlite = function(db){ + // This would be SO much easier with the oo1 API, but we specifically want to + // inject metrics we can't get via that API, and we cannot reliably (OPFS) + // open the same DB twice to clear it using that API, so... + const rc = sqlite3.wasm.exports.sqlite3_wasm_db_reset(db.handle); + log("reset db rc =",rc,db.id, db.filename); +}; + +const App = { + db: undefined, + cache:Object.create(null), + log: log, + warn: warn, + error: error, + metrics: { + fileCount: 0, + runTimeMs: 0, + prepareTimeMs: 0, + stepTimeMs: 0, + stmtCount: 0, + strcpyMs: 0, + sqlBytes: 0 + }, + fileList: undefined, + execSql: async function(name,sql){ + const db = this.db; + const banner = "========================================"; + this.log(banner, + "Running",name,'('+sql.length,'bytes)'); + const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; + let pStmt = 0, pSqlBegin; + const metrics = db.metrics = Object.create(null); + metrics.prepTotal = metrics.stepTotal = 0; + metrics.stmtCount = 0; + metrics.malloc = 0; + metrics.strcpy = 0; + if(this.gotErr){ + this.error("Cannot run SQL: error cleanup is pending."); + return; + } + // Run this async so that the UI can be updated for the above header... + const endRun = ()=>{ + metrics.evalSqlEnd = performance.now(); + metrics.evalTimeTotal = (metrics.evalSqlEnd - metrics.evalSqlStart); + this.log("metrics:",JSON.stringify(metrics, undefined, ' ')); + this.log("prepare() count:",metrics.stmtCount); + this.log("Time in prepare_v2():",metrics.prepTotal,"ms", + "("+(metrics.prepTotal / metrics.stmtCount),"ms per prepare())"); + this.log("Time in step():",metrics.stepTotal,"ms", + "("+(metrics.stepTotal / metrics.stmtCount),"ms per step())"); + this.log("Total runtime:",metrics.evalTimeTotal,"ms"); + this.log("Overhead (time - prep - step):", + (metrics.evalTimeTotal - metrics.prepTotal - metrics.stepTotal)+"ms"); + this.log(banner,"End of",name); + this.metrics.prepareTimeMs += metrics.prepTotal; + this.metrics.stepTimeMs += metrics.stepTotal; + this.metrics.stmtCount += metrics.stmtCount; + this.metrics.strcpyMs += metrics.strcpy; + this.metrics.sqlBytes += sql.length; + }; + + const runner = function(resolve, reject){ + ++this.metrics.fileCount; + metrics.evalSqlStart = performance.now(); + const stack = wasm.scopedAllocPush(); + try { + let t, rc; + let sqlByteLen = sql.byteLength; + const [ppStmt, pzTail] = wasm.scopedAllocPtr(2); + t = performance.now(); + pSqlBegin = wasm.scopedAlloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed"); + metrics.malloc = performance.now() - t; + metrics.byteLength = sqlByteLen; + let pSql = pSqlBegin; + const pSqlEnd = pSqlBegin + sqlByteLen; + t = performance.now(); + wasm.heap8().set(sql, pSql); + wasm.poke(pSql + sqlByteLen, 0); + //log("SQL:",wasm.cstrToJs(pSql)); + metrics.strcpy = performance.now() - t; + let breaker = 0; + while(pSql && wasm.peek8(pSql)){ + wasm.pokePtr(ppStmt, 0); + wasm.pokePtr(pzTail, 0); + t = performance.now(); + rc = capi.sqlite3_prepare_v2( + db.handle, pSql, sqlByteLen, ppStmt, pzTail + ); + metrics.prepTotal += performance.now() - t; + checkSqliteRc(db.handle, rc); + pStmt = wasm.peekPtr(ppStmt); + pSql = wasm.peekPtr(pzTail); + sqlByteLen = pSqlEnd - pSql; + if(!pStmt) continue/*empty statement*/; + ++metrics.stmtCount; + t = performance.now(); + rc = capi.sqlite3_step(pStmt); + capi.sqlite3_finalize(pStmt); + pStmt = 0; + metrics.stepTotal += performance.now() - t; + switch(rc){ + case capi.SQLITE_ROW: + case capi.SQLITE_DONE: break; + default: checkSqliteRc(db.handle, rc); toss("Not reached."); + } + } + resolve(this); + }catch(e){ + if(pStmt) capi.sqlite3_finalize(pStmt); + this.gotErr = e; + reject(e); + }finally{ + capi.sqlite3_exec(db.handle,"rollback;",0,0,0); + wasm.scopedAllocPop(stack); + } + }.bind(this); + const p = new Promise(runner); + return p.catch( + (e)=>this.error("Error via execSql("+name+",...):",e.message) + ).finally(()=>{ + endRun(); + }); + }, + + /** + Loads batch-runner.list and populates the selection list from + it. Returns a promise which resolves to nothing in particular + when it completes. Only intended to be run once at the start + of the app. + */ + loadSqlList: async function(){ + const infile = 'batch-runner.list'; + this.log("Loading list of SQL files:", infile); + let txt; + try{ + const r = await fetch(infile); + if(404 === r.status){ + toss("Missing file '"+infile+"'."); + } + if(!r.ok) toss("Loading",infile,"failed:",r.statusText); + txt = await r.text(); + }catch(e){ + this.error(e.message); + throw e; + } + App.fileList = txt.split(/\n+/).filter(x=>!!x); + this.log("Loaded",infile); + }, + + /** Fetch ./fn and return its contents as a Uint8Array. */ + fetchFile: async function(fn, cacheIt=false){ + if(cacheIt && this.cache[fn]) return this.cache[fn]; + this.log("Fetching",fn,"..."); + let sql; + try { + const r = await fetch(fn); + if(!r.ok) toss("Fetch failed:",r.statusText); + sql = new Uint8Array(await r.arrayBuffer()); + }catch(e){ + this.error(e.message); + throw e; + } + this.log("Fetched",sql.length,"bytes from",fn); + if(cacheIt) this.cache[fn] = sql; + return sql; + }/*fetchFile()*/, + + /** + Converts this.metrics() to a form which is suitable for easy conversion to + CSV. It returns an array of arrays. The first sub-array is the column names. + The 2nd and subsequent are the values, one per test file (only the most recent + metrics are kept for any given file). + */ + metricsToArrays: function(){ + const rc = []; + Object.keys(this.dbs).sort().forEach((k)=>{ + const d = this.dbs[k]; + const m = d.metrics; + delete m.evalSqlStart; + delete m.evalSqlEnd; + const mk = Object.keys(m).sort(); + if(!rc.length){ + rc.push(['db', ...mk]); + } + const row = [k.split('/').pop()/*remove dir prefix from filename*/]; + rc.push(row); + row.push(...mk.map((kk)=>m[kk])); + }); + return rc; + }, + + metricsToBlob: function(colSeparator='\t'){ + const ar = [], ma = this.metricsToArrays(); + if(!ma.length){ + this.error("Metrics are empty. Run something."); + return; + } + ma.forEach(function(row){ + ar.push(row.join(colSeparator),'\n'); + }); + return new Blob(ar); + }, + + /** + Fetch file fn and eval it as an SQL blob. This is an async + operation and returns a Promise which resolves to this + object on success. + */ + evalFile: async function(fn){ + const sql = await this.fetchFile(fn); + return this.execSql(fn,sql); + }/*evalFile()*/, + + /** + Fetches the handle of the db associated with + this.e.selImpl.value, opening it if needed. + */ + initDb: function(){ + const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; + const stack = wasm.scopedAllocPush(); + let pDb = 0; + const d = Object.create(null); + d.filename = "/batch.db"; + try{ + const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; + const ppDb = wasm.scopedAllocPtr(); + const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, this.PoolUtil.vfsName); + pDb = wasm.peekPtr(ppDb) + if(rc) toss("sqlite3_open_v2() failed with code",rc); + capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0); + this.log("cache_size =",cacheSize); + }catch(e){ + if(pDb) capi.sqlite3_close_v2(pDb); + throw e; + }finally{ + wasm.scopedAllocPop(stack); + } + d.handle = pDb; + this.log("Opened db:",d.filename,'@',d.handle); + return d; + }, + + closeDb: function(){ + if(this.db.handle){ + this.sqlite3.capi.sqlite3_close_v2(this.db.handle); + this.db.handle = undefined; + } + }, + + run: async function(sqlite3){ + delete this.run; + this.sqlite3 = sqlite3; + const capi = sqlite3.capi, wasm = sqlite3.wasm; + this.log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); + this.log("WASM heap size =",wasm.heap8().length); + let timeStart; + sqlite3.installOpfsSAHPoolVfs({ + clearOnInit: true, initialCapacity: 4, + name: 'batch-sahpool', + verbosity: 2 + }).then(PoolUtil=>{ + App.PoolUtil = PoolUtil; + App.db = App.initDb(); + }) + .then(async ()=>this.loadSqlList()) + .then(async ()=>{ + timeStart = performance.now(); + for(let i = 0; i < App.fileList.length; ++i){ + const fn = App.fileList[i]; + await App.evalFile(fn); + if(App.gotErr) throw App.gotErr; + } + }) + .then(()=>{ + App.metrics.runTimeMs = performance.now() - timeStart; + App.log("total metrics:",JSON.stringify(App.metrics, undefined, ' ')); + App.log("Reload the page to run this again."); + App.closeDb(); + App.PoolUtil.removeVfs(); + }) + .catch(e=>this.error("ERROR:",e)); + }/*run()*/ +}/*App*/; + +let sqlite3Js = 'sqlite3.js'; +if(urlParams.has('sqlite3.dir')){ + sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; +} +importScripts(sqlite3Js); +globalThis.sqlite3InitModule().then(async function(sqlite3_){ + log("Done initializing. Running batch runner..."); + sqlite3 = sqlite3_; + App.run(sqlite3_); +}); diff --git a/ext/wasm/batch-runner.js b/ext/wasm/batch-runner.js index ff287a66e..e7a322b7f 100644 --- a/ext/wasm/batch-runner.js +++ b/ext/wasm/batch-runner.js @@ -72,7 +72,6 @@ App.logHtml("reset db rc =",rc,db.id, db.filename); }; - const E = (s)=>document.querySelector(s); const App = { e: { @@ -91,6 +90,15 @@ db: Object.create(null), dbs: Object.create(null), cache:{}, + metrics: { + fileCount: 0, + runTimeMs: 0, + prepareTimeMs: 0, + stepTimeMs: 0, + stmtCount: 0, + strcpyMs: 0, + sqlBytes: 0 + }, log: console.log.bind(console), warn: console.warn.bind(console), cls: function(){this.e.output.innerHTML = ''}, @@ -117,7 +125,6 @@ "Running",name,'('+sql.length,'bytes) using',db.id); const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; let pStmt = 0, pSqlBegin; - const stack = wasm.scopedAllocPush(); const metrics = db.metrics = Object.create(null); metrics.prepTotal = metrics.stepTotal = 0; metrics.stmtCount = 0; @@ -142,6 +149,11 @@ this.logHtml("Overhead (time - prep - step):", (metrics.evalTimeTotal - metrics.prepTotal - metrics.stepTotal)+"ms"); this.logHtml(banner,"End of",name); + this.metrics.prepareTimeMs += metrics.prepTotal; + this.metrics.stepTimeMs += metrics.stepTotal; + this.metrics.stmtCount += metrics.stmtCount; + this.metrics.strcpyMs += metrics.strcpy; + this.metrics.sqlBytes += sql.length; }; let runner; @@ -214,7 +226,9 @@ }.bind(this); }else{/*sqlite3 db...*/ runner = function(resolve, reject){ + ++this.metrics.fileCount; metrics.evalSqlStart = performance.now(); + const stack = wasm.scopedAllocPush(); try { let t; let sqlByteLen = sql.byteLength; @@ -269,7 +283,7 @@ let p; if(1){ p = new Promise(function(res,rej){ - setTimeout(()=>runner(res, rej), 50)/*give UI a chance to output the "running" banner*/; + setTimeout(()=>runner(res, rej), 0)/*give UI a chance to output the "running" banner*/; }); }else{ p = new Promise(runner); @@ -401,7 +415,7 @@ }); return new Blob(ar); }, - + downloadMetrics: function(){ const b = this.metricsToBlob(); if(!b) return; @@ -576,6 +590,8 @@ const timeTotal = performance.now() - timeStart; who.logHtml("Run-remaining time:",timeTotal,"ms ("+(timeTotal/1000/60)+" minute(s))"); who.clearStorage(); + App.metrics.runTimeMs = timeTotal; + who.logHtml("Total metrics:",JSON.stringify(App.metrics,undefined,' ')); }, false); }/*run()*/ }/*App*/; diff --git a/ext/wasm/demo-123.js b/ext/wasm/demo-123.js index 8e03ee80f..9f90ca756 100644 --- a/ext/wasm/demo-123.js +++ b/ext/wasm/demo-123.js @@ -20,7 +20,7 @@ the main (UI) thread. */ let logHtml; - if(self.window === self /* UI thread */){ + if(globalThis.window === globalThis /* UI thread */){ console.log("Running demo from main UI thread."); logHtml = function(cssClass,...args){ const ln = document.createElement('div'); @@ -250,7 +250,7 @@ }/*demo1()*/; log("Loading and initializing sqlite3 module..."); - if(self.window!==self) /*worker thread*/{ + if(globalThis.window!==globalThis) /*worker thread*/{ /* If sqlite3.js is in a directory other than this script, in order to get sqlite3.js to resolve sqlite3.wasm properly, we have to @@ -262,19 +262,20 @@ that's not needed. URL arguments passed as part of the filename via importScripts() - are simply lost, and such scripts see the self.location of + are simply lost, and such scripts see the globalThis.location of _this_ script. */ let sqlite3Js = 'sqlite3.js'; - const urlParams = new URL(self.location.href).searchParams; + const urlParams = new URL(globalThis.location.href).searchParams; if(urlParams.has('sqlite3.dir')){ sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; } importScripts(sqlite3Js); } - self.sqlite3InitModule({ - // We can redirect any stdout/stderr from the module - // like so... + globalThis.sqlite3InitModule({ + /* We can redirect any stdout/stderr from the module like so, but + note that doing so makes use of Emscripten-isms, not + well-defined sqlite APIs. */ print: log, printErr: error }).then(function(sqlite3){ diff --git a/ext/wasm/index-dist.html b/ext/wasm/index-dist.html index 36e21117e..f5bcdc1cb 100644 --- a/ext/wasm/index-dist.html +++ b/ext/wasm/index-dist.html @@ -46,6 +46,9 @@ file:// URLs.
    • Any OPFS-related pages or tests require:
        +
      • An OPFS-capable browser released after February + 2023. Some tests will work with Chromium-based browsers + going back to around v102.
      • That the web server emit the so-called COOP and @@ -53,12 +56,6 @@ headers. althttpd requires the -enable-sab flag for that.
      • -
      • A very recent version of a Chromium-based browser - (v102 at least, possibly newer). OPFS support in the - other major browsers is pending. Development and testing - is currently done against a dev-channel release of - Chrome (v111 as of 2023-02-10). -
    diff --git a/ext/wasm/index.html b/ext/wasm/index.html index 70ce0441e..ebbfd6763 100644 --- a/ext/wasm/index.html +++ b/ext/wasm/index.html @@ -31,6 +31,9 @@ file:// URLs.
  • Any OPFS-related pages or tests require:
      +
    • An OPFS-capable browser released after February + 2023. Some tests will work with Chromium-based browsers + going back to around v102.
    • That the web server emit the so-called COOP and @@ -38,12 +41,6 @@ headers. althttpd requires the -enable-sab flag for that.
    • -
    • A very recent version of a - Chromium-based browser (v102 at least, possibly newer). OPFS - support in the other major browsers is pending. Development - and testing is currently done against a dev-channel release - of Chrome (v111 as of 2023-02-10). -
  • diff --git a/ext/wasm/jaccwabyt/jaccwabyt.md b/ext/wasm/jaccwabyt/jaccwabyt.md index dd80ed1c6..431741edc 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.md +++ b/ext/wasm/jaccwabyt/jaccwabyt.md @@ -206,9 +206,8 @@ const MyBinder = StructBinderFactory({ It also offers a number of other settings, but all are optional except for the ones shown above. Those three config options abstract away details which are specific to a given WASM environment. They provide -the WASM "heap" memory (a byte array), the memory allocator, and the -deallocator. In a conventional Emscripten setup, that config might -simply look like: +the WASM "heap" memory, the memory allocator, and the deallocator. In +a conventional Emscripten setup, that config might simply look like: > ```javascript diff --git a/ext/wasm/speedtest1-worker.html b/ext/wasm/speedtest1-worker.html index 3adf8f2ee..8c9a77dc5 100644 --- a/ext/wasm/speedtest1-worker.html +++ b/ext/wasm/speedtest1-worker.html @@ -350,7 +350,7 @@ eControls.classList.remove('hidden'); break; case 'stdout': log(msg.data); break; - case 'stdout': logErr(msg.data); break; + case 'stderr': logErr(msg.data); break; case 'run-start': eControls.disabled = true; log("Running speedtest1 with argv =",msg.data.join(' ')); diff --git a/ext/wasm/speedtest1-worker.js b/ext/wasm/speedtest1-worker.js index dedc32029..3abc589b5 100644 --- a/ext/wasm/speedtest1-worker.js +++ b/ext/wasm/speedtest1-worker.js @@ -7,9 +7,9 @@ } importScripts(speedtestJs); /** - If this environment contains OPFS, this function initializes it and - returns the name of the dir on which OPFS is mounted, else it returns - an empty string. + If this build includes WASMFS, this function initializes it and + returns the name of the dir on which OPFS is mounted, else it + returns an empty string. */ const wasmfsDir = function f(wasmUtil){ if(undefined !== f._) return f._; @@ -47,6 +47,7 @@ }; const log = (...args)=>logMsg('stdout',args); const logErr = (...args)=>logMsg('stderr',args); + const realSahName = 'opfs-sahpool-speedtest1'; const runSpeedtest = async function(cliFlagsArray){ const scope = App.wasm.scopedAllocPush(); @@ -57,7 +58,6 @@ ]; App.logBuffer.length = 0; const ndxSahPool = argv.indexOf('opfs-sahpool'); - const realSahName = 'opfs-sahpool-speedtest1'; if(ndxSahPool>0){ argv[ndxSahPool] = realSahName; log("Updated argv for opfs-sahpool: --vfs",realSahName); @@ -73,7 +73,7 @@ clearOnInit: true, verbosity: 2 }).then(PoolUtil=>{ - log("opfs-sahpool successfully installed as",realSahName); + log("opfs-sahpool successfully installed as",PoolUtil.vfsName); App.sqlite3.$SAHPoolUtil = PoolUtil; //console.log("sqlite3.oo1.OpfsSAHPoolDb =", App.sqlite3.oo1.OpfsSAHPoolDb); }); @@ -102,19 +102,6 @@ } }; - const sahpSanityChecks = function(sqlite3){ - log("Attempting OpfsSAHPoolDb sanity checks..."); - const db = new sqlite3.oo1.OpfsSAHPoolDb('opfs-sahpoool.db'); - const fn = db.filename; - db.exec([ - 'create table t(a);', - 'insert into t(a) values(1),(2),(3);' - ]); - db.close(); - sqlite3.wasm.sqlite3_wasm_vfs_unlink(sqlite3_vfs_find("opfs-sahpool"), fn); - log("SAH sanity checks done."); - }; - const EmscriptenModule = { print: log, printErr: logErr, diff --git a/ext/wasm/test-opfs-vfs.js b/ext/wasm/test-opfs-vfs.js index 292d77af1..96d0eacfc 100644 --- a/ext/wasm/test-opfs-vfs.js +++ b/ext/wasm/test-opfs-vfs.js @@ -22,7 +22,7 @@ const tryOpfsVfs = async function(sqlite3){ const opfs = sqlite3.opfs; log("tryOpfsVfs()"); if(!sqlite3.opfs){ - const e = toss("OPFS is not available."); + const e = new Error("OPFS is not available."); error(e); throw e; } diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index 92d763f1b..36ca4c976 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -1688,7 +1688,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; wasm.sqlite3_wasm_vfs_unlink(0, filename); } } - }/*sqlite3_js_vfs_create_file()*/) + }/*sqlite3_js_posix_create_file()*/) //////////////////////////////////////////////////////////////////// .t({ @@ -2605,28 +2605,6 @@ globalThis.sqlite3InitModule = sqlite3InitModule; } } }/*kvvfs sanity checks*/) - .t({ - name: 'kvvfs sqlite3_js_vfs_create_file()', - predicate: ()=>"kvvfs does not currently support this", - test: function(sqlite3){ - let db; - try { - db = new this.JDb(this.kvvfsDbFile); - const exp = capi.sqlite3_js_db_export(db); - db.close(); - this.kvvfsUnlink(); - capi.sqlite3_js_vfs_create_file("kvvfs", this.kvvfsDbFile, exp); - db = new this.JDb(filename); - T.assert(6 === db.selectValue('select count(*) from kvvfs')); - }finally{ - db.close(); - this.kvvfsUnlink(); - } - delete this.kvvfsDbFile; - delete this.kvvfsUnlink; - delete this.JDb; - } - }/*kvvfs sqlite3_js_vfs_create_file()*/) ;/* end kvvfs tests */ //////////////////////////////////////////////////////////////////////// @@ -2644,13 +2622,17 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.assert( 0 === rc /*void pointer*/ ); // Commit hook... + T.assert( 0!=capi.sqlite3_get_autocommit(db) ); db.exec("BEGIN; SELECT 1; COMMIT"); T.assert(0 === countCommit, "No-op transactions (mostly) do not trigger commit hook."); db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT"); T.assert(1 === countCommit, "But EXCLUSIVE transactions do."); - db.transaction((d)=>{d.exec("create table t(a)");}); + db.transaction((d)=>{ + T.assert( 0==capi.sqlite3_get_autocommit(db) ); + d.exec("create table t(a)"); + }); T.assert(2 === countCommit); // Rollback hook: diff --git a/main.mk b/main.mk index fdfdb7529..081e0cd3b 100644 --- a/main.mk +++ b/main.mk @@ -360,6 +360,7 @@ TESTSRC += \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/prefixes.c \ $(TOP)/ext/misc/qpvtab.c \ + $(TOP)/ext/misc/randomjson.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ @@ -526,12 +527,15 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB +FUZZCHECK_OPT += -DSQLITE_STRICT_SUBTYPE=1 +FUZZCHECK_OPT += -DSQLITE_STATIC_RANDOMJSON FUZZSRC += $(TOP)/test/fuzzcheck.c FUZZSRC += $(TOP)/test/ossfuzz.c FUZZSRC += $(TOP)/test/vt02.c FUZZSRC += $(TOP)/test/fuzzinvariants.c FUZZSRC += $(TOP)/ext/recover/dbdata.c FUZZSRC += $(TOP)/ext/recover/sqlite3recover.c +FUZZSRC += $(TOP)/ext/misc/randomjson.c DBFUZZ_OPT = KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ ST_OPT = -DSQLITE_THREADSAFE=0 @@ -898,6 +902,8 @@ TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_DBPAGE_VTAB TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_BYTECODE_VTAB TESTFIXTURE_FLAGS += -DTCLSH_INIT_PROC=sqlite3TestInit TESTFIXTURE_FLAGS += -DSQLITE_CKSUMVFS_STATIC +TESTFIXTURE_FLAGS += -DSQLITE_STATIC_RANDOMJSON +TESTFIXTURE_FLAGS += -DSQLITE_STRICT_SUBTYPE=1 testfixture$(EXE): $(TESTSRC2) libsqlite3.a $(TESTSRC) $(TOP)/src/tclsqlite.c $(TCCX) $(TCL_FLAGS) $(TESTFIXTURE_FLAGS) \ diff --git a/manifest b/manifest index 1041b8b17..c1d98bbfb 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,13 @@ -C Version\s3.44.2 -D 2023-11-24T11:41:44.921 +C Version\s3.45.0 +D 2024-01-15T17:01:13.164 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in bddd493839ce057414b19cd405293363f7efb7a73a3d8f6d2ed1b59943ddfcb1 +F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 -F Makefile.msc 59bb36dba001f0b38212be0794fb838f25371008b180929bcf08aa799694c168 -F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8 -F VERSION 672a5ddf487d19856d8b1015797e14578fd8f8d59c14d9832ab0fe5938c6ab9b +F Makefile.msc df56c06ef05add5ebcdcc9d4823fb2ec66ac16b06acb6971a7420859025886fa +F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3 +F VERSION 73573d4545343f001bf5dc5461173a7c78c203dd046cabcf99153878cf25d3a6 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -15,14 +15,14 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc 34f8c222846db1b354720c8a78a4fa7dec9176ea447c0fffdf442b7f74e8b1df +F autoconf/Makefile.msc ac338c36a338f6b49475da71930f45145a181d6b551578d5a7a64f113ef27b2c F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac 0c358d924fe9cd5c624b9038e025bca35b572a15501014c69afe38b8ae4d7371 +F autoconf/tea/configure.ac 4c32b08691a5b296206b38422b53b92b65be3d3f6b3dd6552a50981a61f5acda F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,15 +33,16 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure a56c8f24e118eff90e3870411bfc844e86c00577faf092ded131120a637ad2da x -F configure.ac de31fea7d975bb7ebafbe0e2190a855cc80d48558bf0c9a6578a1836daf1cd3a +F configure bcb1042e92775424a1021d2f4c89c78a699a6225df01fa8c593df7df0be6ad10 x +F configure.ac f25bd7843120f2c2b8bc9db5a92b0502bbdd28e68907415c3d42fc8e57c657b9 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd -F doc/compile-for-windows.md 922ba580d210a1f1bd3ef9d0413121556f9b5714fb5c01e664d980f85fa4ac8c +F doc/compile-for-windows.md 50b27d77be96195c66031a3181cb8684ed822327ea834e07f9c014213e5e3bcf F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f +F doc/jsonb.md 5fab4b8613aa9153fbeb6259297bd4697988af8b3d23900deba588fa7841456b F doc/lemon.html 44a53a1d2b42d7751f7b2f478efb23c978e258d794bfd172442307a755b9fa44 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 -F doc/testrunner.md 2434864be2219d4f0b6ffc99d0a2172d531c4ca4345340776f67ad4edd90dc90 +F doc/testrunner.md 8d36ec692cf4994bb66d84a4645b9afa1ce9d47dc12cbf8d437c5a5fb6ddeedb F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a @@ -50,8 +51,8 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91 F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad1aff3294f94 F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a -F ext/consio/console_io.c e048bd69f3a357a4b6b5da01c504728631d944ad5735ad83c44800c80a91e978 x -F ext/consio/console_io.h d18bcc50f4e4d42b65be8fecba72918364ae77182c86360aca842e44b823384e +F ext/consio/console_io.c e1be639e79e54264b3ae97ca291728987a9aa82e6a4526458e6400f5e083e524 x +F ext/consio/console_io.h 0548b83d7c4b7270ad544a67f2bb90cebc519637fa39b1838df4744cf0d87646 F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 F ext/expert/expert1.test 0dd5cb096d66bed593e33053a3b364f6ef52ed72064bf5cf298364636dbf3cd6 @@ -88,28 +89,28 @@ F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7 F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6dbd6348ef0cfc324a7 F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb -F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 -F ext/fts5/fts5.h 05501612cc655504c5dce8ba765ab621d50fc478490089beaa0d75e00b23e520 -F ext/fts5/fts5Int.h 78a63cc0795186cde5384816a9403a68c65774b35d952e05b81a1b4b158e07c8 -F ext/fts5/fts5_aux.c 35c4101613eff86902877a4dedd9400b07922e412cbdd637b45041dce2fd5388 -F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 -F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 -F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 -F ext/fts5/fts5_hash.c 076058f93327051952a752dc765df1acfe783eb11b419b30652aa1fc1f987902 -F ext/fts5/fts5_index.c 458cbed8a3e17617cbf7e80cdfb7612000b9bb3781f286b345fb9655858658cf -F ext/fts5/fts5_main.c a07ed863b8bd9e6fefb62db2fd40a3518eb30a5f7dcfda5be915dd2db45efa2f -F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca56d6601426d -F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae +F ext/fts5/extract_api_docs.tcl bc3a0ca78be7d3df08e7602c00ca48021ebae40682d75eb001bfdf6e54ffb44e +F ext/fts5/fts5.h ecba24fed7b359b3a53016bb07e411b3b4c9cdf163aa141006536423a63b611e +F ext/fts5/fts5Int.h defa43c0932265138ee910ca416e6baccf8b774e0f3d610e74be1ab2880e9834 +F ext/fts5/fts5_aux.c 4584e88878e54828bf7d4d0d83deedd232ec60628b7731be02bad6adb62304b1 +F ext/fts5/fts5_buffer.c 0eec58bff585f1a44ea9147eae5da2447292080ea435957f7488c70673cb6f09 +F ext/fts5/fts5_config.c 8072a207034b51ae9b7694121d1b5715c794e94b275e088f70ae532378ca5cdf +F ext/fts5/fts5_expr.c e91156ebdcc08d837f4f324168f69f3c0d7fdef0e521fd561efb48ef3297b696 +F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1 +F ext/fts5/fts5_index.c bb1965c3965f6fe5f64160bf1c0694a9684a790a783f293a76da1d38d319b258 +F ext/fts5/fts5_main.c 94a03dd431022d706290bb81b7f2180a0bb7c98f1397b5fbc90e18d3ed8d366c +F ext/fts5/fts5_storage.c f9e31b0d155e9b2c92d5d3a09ad7a56b937fbf1c7f962e10f4ca6281349f3934 +F ext/fts5/fts5_tcl.c cf0fd0dbe64ec272491b749e0d594f563cda03336aeb60900129e6d18b0aefb8 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b -F ext/fts5/fts5_tokenize.c 5e251efb0f1af99a25ed50010ba6b1ad1250aca5921af1988fdcabe5ebc3cb43 +F ext/fts5/fts5_tokenize.c 83cfcede3898001cab84432a36ce1503e3080cf9b1c682b022ec82e267ea4c13 F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80 -F ext/fts5/fts5_vocab.c aed56169ae5c1aa9b8189c779ffeef04ed516d3c712c06914e6d91a6759f4e4a +F ext/fts5/fts5_vocab.c 209e0c151e108d5f3621fa24b91e9b02f3750ee6c3f9ccec312df39481b68a09 F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba -F ext/fts5/test/fts5_common.tcl a9de9c2209cc4e7ae3c753e783504e67206c6c1467d08f209cd0c5923d3e8d8b -F ext/fts5/test/fts5aa.test ba5158eba7d61359becdfca895ef471072c7bf7b20e5e60dcb4d024c8419c926 +F ext/fts5/test/fts5_common.tcl 3378732aae2a7d9a4b9b5c40bde678d4259ca16bd490883325aecc4747bcb384 +F ext/fts5/test/fts5aa.test 4db81519863244a3cab35795fe65ab6b592e7970c7409eba098b23ebbfc08d95 F ext/fts5/test/fts5ab.test bd932720c748383277456b81f91bc00453de2174f9762cd05f95d0495dc50390 F ext/fts5/test/fts5ac.test a7aa7e1fefc6e1918aa4d3111d5c44a09177168e962c5fd2cca9620de8a7ed6d F ext/fts5/test/fts5ad.test e8cf959dfcd57c8e46d6f5f25665686f3b6627130a9a981371dafdf6482790de @@ -123,7 +124,7 @@ F ext/fts5/test/fts5ak.test f459a64c9d38698af72a7c657ab6349bca96150241dd69fcce75 F ext/fts5/test/fts5al.test 00c4c1c6a1366b73aa48ce2068c634520867c3cf7f5d1676ebbb775ee1f35734 F ext/fts5/test/fts5alter.test 5565f7e4605512b69171ac18ca84398603f9f6456dbe377beeca97e83cc242cd F ext/fts5/test/fts5auto.test 78989e6527ce69c9eddbef7392fea5c10b0010cd2b2ae68eec7bc869c471e691 -F ext/fts5/test/fts5aux.test 3f194345fcd581f49f7fbb2e5495400efcc7d2835b77816328d8283c942f41b8 +F ext/fts5/test/fts5aux.test ed3596469f85a6cff5f6060e0cd9e3f9602051d8db2b497f5d12c85d39f20a62 F ext/fts5/test/fts5auxdata.test eacc97ff04892f1a5f3d4df5a73f8bcbc3955ea1d12c9f24137eb1fc079e7611 F ext/fts5/test/fts5bigid.test 2860854c2561a57594192b00c33a29f91cb85e25f3d6c03b5c2b8f62708f39dd F ext/fts5/test/fts5bigpl.test 6466c89b38439f0aba26ac09e232a6b963f29b1cbe1304f6a664fe1e7a8f5fd3 @@ -135,7 +136,7 @@ F ext/fts5/test/fts5columnsize.test 45459ce4dd9fd853b6044cdc9674921bff89e3d840f3 F ext/fts5/test/fts5config.test 60094712debc59286c59aef0e6cf511c37d866802776a825ce437d26afe0817f F ext/fts5/test/fts5conflict.test bf6030a77dbb1bedfcc42e589ed7980846c995765d77460551e448b56d741244 F ext/fts5/test/fts5connect.test 08030168fc96fc278fa81f28654fb7e90566f33aff269c073e19b3ae9126b2f4 -F ext/fts5/test/fts5content.test 219a4e49386b9b197b9b7cadca97ea10ddff858ecd8b763a1cb8bb07575afc2a +F ext/fts5/test/fts5content.test 282b373c58c8e798568ec2ced18b23f29bffa8d61317a0e51a035000ad6cd731 F ext/fts5/test/fts5contentless.test 1cd1237894eeff11feb1ff8180044eac0b17dde22c181f7a722f2dcbfdb3377c F ext/fts5/test/fts5contentless2.test 14c83bdacf8230f5f7ca74ecf2926b87d8a7cb788a69ce9937020428ac4fe192 F ext/fts5/test/fts5contentless3.test 353d871c5ea08992aed3e2ebda0b1bdc35116cd24fe330fe7cf05be1e2b49fd7 @@ -145,7 +146,7 @@ F ext/fts5/test/fts5corrupt.test b6d4034b682bb3387bc44c510c71b3c67d4349e4df13949 F ext/fts5/test/fts5corrupt2.test 99e7e23a58b4d89eb7167c6de1669cbc595cd3c79ab333e0eb56405473319e77 F ext/fts5/test/fts5corrupt3.test 7da9895dafa404efd20728f66ff4b94399788bdc042c36fe2689801bba2ccd78 F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3 -F ext/fts5/test/fts5corrupt5.test 38a238df26c4de471e1c4b98f8de6c902bc692577a1c08d0ff4f2251f3559644 +F ext/fts5/test/fts5corrupt5.test 0a33d1028837aaf37e55a0538060a8a0cc2e47fee112d1e09b52d50bde03c37d F ext/fts5/test/fts5corrupt6.test bf8eeae07825b088b9665d9d8e4accbd8dc9bf3cb85b6c64cf6c9e18ccc420a4 F ext/fts5/test/fts5corrupt7.test 80ad7f683a8bda2404731bb77e8c3dbbb620c1f6cc583cca8239f6accd6338c0 F ext/fts5/test/fts5delete.test 619295b20dbc1d840b403ee07c878f52378849c3c02e44f2ee143b3e978a0aa7 @@ -170,6 +171,7 @@ F ext/fts5/test/fts5faultD.test e7ed7895abfe6bc98a5e853826f6b74956e7ba7f594f1860 F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e4710c77eb8ce7075 F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1 F ext/fts5/test/fts5faultG.test d2e5a4d9a34e08dcaadcaeafef74d10cbc2abdd11aa2659a18af0294bf2812d3 +F ext/fts5/test/fts5faultH.test 57f53c87ffd59be0265840f2b54a16811f9cb9012db86aad9b41d0d14d85dfe3 F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b996159f6909dc8079 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e @@ -182,7 +184,7 @@ F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fc F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2 -F ext/fts5/test/fts5misc.test 5ca82f2a5ee016b0842043155d1382f98a34d0d86b2791165a44d7807f6e0f54 +F ext/fts5/test/fts5misc.test 89dc46e37951b7f6653809f4abf6b1ca2f1fa62259efaf719339288f76fb6be9 F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd @@ -190,6 +192,11 @@ F ext/fts5/test/fts5onepass.test f9b7d9b2c334900c6542a869760290e2ab5382af8fbd618 F ext/fts5/test/fts5optimize.test 36a752d24c818792032e4ff502936fc9cc5ef938721696396fdc79214b2717f1 F ext/fts5/test/fts5optimize2.test 93e742c36b487d8874621360af5b1ce4d39b04fb9e71ce9bc34015c5fc811785 F ext/fts5/test/fts5optimize3.test bf9c91bb927d0fb2b9a06318a217a0419183ac5913842e062c7e0b98ea5d0fca +F ext/fts5/test/fts5origintext.test d2796fa08ee7aecfabdc0c45bb8a2fb16a00ea8757e63fbc153b718dbe430a39 +F ext/fts5/test/fts5origintext2.test f3b9436de540828d01f0672df855b09ebc0863e126d5b56234701d71dfa73634 +F ext/fts5/test/fts5origintext3.test 0d25933506600452a5ab3873cbb418ed5f2de2446c3672b9997b1ea104b0e7f0 +F ext/fts5/test/fts5origintext4.test 0c4e4514b68d9ddb15e5a538d9d234da85747a3fd62432265dbdba5c8708e457 +F ext/fts5/test/fts5origintext5.test a037bdf7235a22033c4663837bdb12d9738245464a3ac2f60c71fc40d07ede7d F ext/fts5/test/fts5phrase.test 13e5d8e9083077b3d9c74315b3c92ec723cc6eb37c8155e0bfe1bba00559f07b F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd116514f79c49c5a F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15 @@ -204,7 +211,7 @@ F ext/fts5/test/fts5rowid.test b8790ec170a8dc1942a15aef3db926a5f3061b1ff17101300 F ext/fts5/test/fts5savepoint.test 050796b24929325cdbbb2fbfe2794816ae95d298e940ae15032200c2f4a73725 F ext/fts5/test/fts5secure.test a02f771742fb2b1b9bdcb4bf523bcf2d0aa1ff597831d40fe3e72aaa6d0ec40f F ext/fts5/test/fts5secure2.test 2e961d7eef939f294c56b5d895cac7f1c3a60b934ee2cfd5e5e620bdf1ba6bbc -F ext/fts5/test/fts5secure3.test c7e1080a6912f2a3ac68f2e05b88b72a99de38543509b2bbf427cac5c9c1c610 +F ext/fts5/test/fts5secure3.test 6d066828d225b0dbe5db818d4d6165df7bb70210e68a577e858e8762400d5a23 F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b9846cf7fc8337b67c97 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a F ext/fts5/test/fts5secure6.test 74bf04733cc523bccca519bb03d3b4e2ed6f6e3db7c59bf6be82c88a0ac857fd @@ -212,14 +219,16 @@ F ext/fts5/test/fts5secure7.test fd03d0868d64340a1db8615b02e5508fea409de13910114 F ext/fts5/test/fts5secure8.test eb3579e9d58b0acad97e8082dee1f99b2d393198f03500b453c2b25761c0c298 F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b -F ext/fts5/test/fts5simple2.test 258a1b0c590409bfa5271e872c79572b319d2a56554d0585f68f146a0da603f0 +F ext/fts5/test/fts5simple2.test 8dd2389ee75e21a1429fe87e5f8c7d9a97ad1470304a8a2d3ba4b8c3c345fecd F ext/fts5/test/fts5simple3.test d5c74a9d3ca71bd5dd5cacb7c55b86ea12cdddfc8b1910e3de2995206898380f F ext/fts5/test/fts5synonym.test 1651815b8008de170e8e600dcacc17521d765482ea8f074ae82cfa870d8bb7fb -F ext/fts5/test/fts5synonym2.test 8f891fc49cc1e8daed727051e77e1f42849c784a6a54bef82564761b2cb3e016 +F ext/fts5/test/fts5synonym2.test e2f6ff68c4fbe12a866a3a87510f553d9dac99bcb74c10b56487c4c0a562fcf5 F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 +F ext/fts5/test/fts5tokenizer2.test cb5428c7cfb3b6a74b7adfcde65506e329112003e8dffa7501d01c2d18d02569 F ext/fts5/test/fts5trigram.test 6c4e37864f3e7d90673db5563d9736d7e40080ab94d10ebdffa94c1b77941da0 +F ext/fts5/test/fts5trigram2.test 9fe4207f8a4241747aff1005258b564958588d21bfd240d6cd4c2e955d31c156 F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602 F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9 @@ -230,7 +239,7 @@ F ext/fts5/test/fts5unindexed.test 9021af86a0fb9fc616f7a69a996db0116e7936d0db638 F ext/fts5/test/fts5update.test b8affd796e45c94a4d19ad5c26606ea06065a0f162a9562d9f005b5a80ccf0bc F ext/fts5/test/fts5version.test d6e5a5897550afeccc2f8531d87404dc1c289ee89385dd4318dbdd75e71d7a67 F ext/fts5/test/fts5vocab.test 7ed80d9af1ddaaa1637da05e406327b5aac250848bc604c1c1cc667908b87760 -F ext/fts5/test/fts5vocab2.test 681980e92e031c9f3fe8d9c149189e876c108da2fb0fb3a25bd8a9b94bff8f68 +F ext/fts5/test/fts5vocab2.test 1b1f0059f762ffb404213d35dac013e010621f08128589b6ec7bec59d9a710f3 F ext/fts5/tool/fts5speed.tcl b0056f91a55b2d1a3684ec05729de92b042e2f85 F ext/fts5/tool/fts5txt2db.tcl c0d43c8590656f8240e622b00957b3a0facc49482411a9fdc2870b45c0c82f9f F ext/fts5/tool/loadfts5.tcl 95b03429ee6b138645703c6ca192c3ac96eaf093 @@ -239,68 +248,70 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 5c3ac326bf3853486ebe0d70819abc790cc65c412182ce4ebd5012b008d9b059 -F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4 +F ext/jni/GNUmakefile 59eb05f2a363bdfac8d15d66bed624bfe1ff289229184f3861b95f98a19cf4b2 +F ext/jni/README.md d899789a9082a07b99bf30b1bbb6204ae57c060efcaa634536fa669323918f42 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa -F ext/jni/src/c/sqlite3-jni.c 6f6df9657989e9ca2cfdcc2fe9a71c279de56d5c941adfd09a0f24256de35c8f -F ext/jni/src/c/sqlite3-jni.h b4c413a0d0c734683da1049cfcf89e35ae2719759d0656ec0f8c57188f18cab8 -F ext/jni/src/org/sqlite/jni/annotation/NotNull.java a99341e88154e70447596b1af6a27c586317df41a7e0f246fd41370cd7b723b2 -F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba +F ext/jni/src/c/sqlite3-jni.c c1292e690a20c7787a63e8d8ac6e2dfed49c97282ed056a7cfda5da461f0b7d8 +F ext/jni/src/c/sqlite3-jni.h 913ab8e8fee432ae40f0e387c8231118d17053714703f5ded18202912a8a3fbf +F ext/jni/src/org/sqlite/jni/annotation/Experimental.java 8603498634e41d0f7c70f661f64e05df64376562ea8f126829fd1e0cdd47e82b +F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 38e7e58a69b26dc100e458b31dfa3b2a7d67bc36d051325526ef1987d5bc8a24 +F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 56e3dee1f3f703a545dfdeddc1c3d64d1581172b1ad01ffcae95c18547fafd90 F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca F ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1afa90d3f236f79cc7fcd2497e111992644f7596fbc8e8bcf7f1908ae00acd6c -F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java bc29e986c866c2ddbbb9f935f5b7264c1c1026864e50a4a735192864f75e37c0 -F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013 +F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63418129656daa9a9f30e7e7be982bd5ab394b1dbd0 +F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca -F ext/jni/src/org/sqlite/jni/capi/CApi.java bccb442ca81cd4decb1adae99006a60b7a9f54e5153842e738c01104e97d1de0 -F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 0bfd6e56e8265c2f05c9207665707285534d78f8466ef0e0430c65677f00943d +F ext/jni/src/org/sqlite/jni/capi/CApi.java 27bbd944ea8c147afd25b93f17dc397f3627611ebe2878944a32ffeffc98e99e +F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a -F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95 -F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 29c002f3c638cc80f7db1594564a262d1beb32637824c3dca2d60a224d1f71d7 +F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab +F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 482f53dfec9e3ac2a9070d3fceebd56250932aaaf7c4f5bc8de29fc011416e0c F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4 -F ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 701f2e4d8bdeb27cfbeeb56315d15b13d8752b0fdbca705f31bd4366c58d8a33 +F ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java e5723900b6458bc6288f52187090a78ebe0a20f403ac7c887ec9061dfe51aba7 F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61 -F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 68f60aec7aeb5cd4e5fb83449037f668c63cb99f682ee1036cc226d0cbd909b9 -F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java aca8f9fa72e3b6602bc9a7dd3ae9f5b2808103fbbee9b2749dc96c19cdc261a1 -F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 819d938e26208adde17ca4b7ddde1d8cd6915b6ab7b708249a9787beca6bd6b6 +F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 246b0e66c4603f41c567105a21189d138aaf8c58203ecd4928802333da553e7c +F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java 97352091abd7556167f4799076396279a51749fdae2b72a6ba61cd39b3df0359 +F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java efcf57545c5e282d1dd332fa63329b3b218d98f356ef107a9dbe3979be82213a F ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 01bc0c238eed2d5f93c73522cb7849a445cc9098c2ed1e78248fa20ed1cfde5b F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc2537a25ad1628f3638398d8a60cacefa7f -F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 105e324d09c207100485e7667ad172e64322c62426bb49b547e9b0dc9c33f5f0 -F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java fef556adbc3624292423083a648bdf97fa8a4f6b3b6577c9660dd7bd6a6d3c4a +F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java e172210a2080e851ebb694c70e9f0bf89284237795e38710a7f5f1b61e3f6787 +F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385155fa3b8011a5cca0bb3c28468c7131c1a5 F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1 F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615 F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f -F ext/jni/src/org/sqlite/jni/capi/Tester1.java ca195521b6bda3e0cd00e76bb71ec8060d1fab76a2f13b1af9feea40789f44bb +F ext/jni/src/org/sqlite/jni/capi/Tester1.java e5fa17301b7266c1cbe4bcce67788e08e45871c7c72c153d515abb37e501de0a F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723 -F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4 -F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 9f9e151f1da017b706c0ee5f40f4c86b54e773d6ae4339723e0cc85a456251ab +F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56 +F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 2ce069f3e007fdbbe1f4e507a5a407fc9679da31a0aa40985e6317ed4d5ec7b5 F ext/jni/src/org/sqlite/jni/capi/WindowFunction.java caf4396f91b2567904cf94bc538a069fd62260d975bd037d15a02a890ed1ef9e F ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java f3abb8dd7381f53ebba909437090caf68200f06717b8a7d6aa96fa3e8133117d F ext/jni/src/org/sqlite/jni/capi/package-info.java 08ff986a65d2be9162442c82d28a65ce431d826f188520717c2ecb1484d0a50e -F ext/jni/src/org/sqlite/jni/capi/sqlite3.java 4010bbebc5bf44e2044e610786088cdee7dc155da2b333c0551492ff1cedf33b +F ext/jni/src/org/sqlite/jni/capi/sqlite3.java c6a5c555d163d76663534f2b2cce7cab15325b9852d0f58c6688a85e73ae52f0 F ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 6742b431cd4d77e8000c1f92ec66265a58414c86bf3b0b5fbcb1164e08477227 -F ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java f204ab6ab1263e119fe43730141a00662d80972129a5351dfb11aae5d282df36 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java 59e26ca5254cd4771f467237bcfe2d8deed30a77152fabcd4574fd406c301d63 F ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java f0ef982009c335c4393ffcb68051809ca1711e4f47bcb8d1d46952f22c01bc22 -F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java ff579621e9bd5ffbc6b2ef9f996c12db4df6e0c8cc5697c91273e5fca279fcf8 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java 293b5fa7d5b5724c87de544654aca1103d76f3092bc2c8f4360102a65ba25dff F ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java e1d62a257c13504b46d39d5c21c49cf157ad73fda00cc5f34c931aa008c37049 F ext/jni/src/org/sqlite/jni/fts5/Fts5.java e94681023785f1eff5399f0ddc82f46b035977d350f14838db659236ebdf6b41 F ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 338637e6e5a2cc385d962b220f3c1f475cc371d12ae43d18ef27327b6e6225f7 F ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java 7da0fbb5728f7c056a43e6407f13dd0c7c9c445221267786a109b987f5fc8a9d F ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java 28045042d593a1f1b9b80d54ec77cbf1d8a1bc95e442eceefa9a3a6f56600b0e F ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java 3c8f677ffb85b8782f865d6fcbc16200b3375d0e3c29ed541a494fde3011bf49 -F ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java eaee4d641229a098eb704b96a45c9a23c6514dc39009d3611e265bab33834deb +F ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java 51e16bf9050af7cb246d17d6a19c001cfc916bf20f425c96625aaccaf74688e8 F ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java 1efd1220ea328a32f2d2a1b16c735864159e929480f71daad4de9d5944839167 F ext/jni/src/org/sqlite/jni/fts5/fts5_api.java a8e88c3783d21cec51b0748568a96653fead88f8f4953376178d9c7385b197ea F ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 9e2b954d210d572552b28aca523b272fae14bd41e318921b22f65b728d5bf978 F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ade23d329843f809cd0d0f4f1a2856da6e6b4d90 F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e -F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 5ad99bd74c85f56bbef324d9ec29b4048f4620547c9a80093d8586c3557f9f9a +F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483 F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03 -F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 004394eeb944baa56e36cd7ae69ba6d4a52b52db3c49439db16e98270b861421 -F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java a9ddc6a9e8c113168cc67592ae24c0e56d30dd06226eeab012f2761a0889d7bb -F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1386f7b753134fc12253ce2fbbc448ba8c970567fac01a3356cb672e14408d73 -F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java c24b510ebe801c30533cc62efdf69a4a5e2da9ec4b49f8d403f2060693f060a0 -F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af +F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 27b141f5914c7cb0e40e90a301d5e05b77f3bd42236834a68031b7086381fafd +F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java ada39f18e4e3e9d4868dadbc3f7bfe1c6c7fde74fb1fb2954c3f0f70120b805c +F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 982538ddb4c0719ef87dfa664cd137b09890b546029a7477810bd64d4c47ee35 +F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java ce45f2ec85facbb73690096547ed166e7be82299e3d92eaa206f82b60a6ec969 +F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java a84e90c43724a69c2ecebd601bc8e5139f869b7d08cb705c77ef757dacdd0593 +F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/jni/src/tests/900-001-fts.test bf0ce17a8d082773450e91f2388f5bbb2dfa316d0b676c313c637a91198090f0 @@ -384,7 +395,7 @@ F ext/misc/pcachetrace.c f4227ce03fb16aa8d6f321b72dd051097419d7a028a9853af048bee F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 F ext/misc/prefixes.c 82645f79229877afab08c8b08ca1e7fa31921280906b90a61c294e4f540cd2a6 F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47cc06c -F ext/misc/randomjson.c 7dd13664155319d47b9facc0d8dbf45e13062966a47168e54e3f26d48240d7ea +F ext/misc/randomjson.c ef835fc64289e76ac4873b85fe12f9463a036168d7683cf2b773e36e6262c4ed F ext/misc/regexp.c 4bdd0045912f81c84908bd535ec5ad3b1c8540b4287c70ab84070963624047db F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c @@ -397,7 +408,7 @@ F ext/misc/spellfix.c c0aa7b80d6df45f7da59d912b38752bcac1af53a5766966160e6c5cdd3 F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7 F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321 F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b -F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b +F ext/misc/totype.c 75ed9827d19cc3b434fc2aeb60725d4d46e1534373615612a4d1cfdcc3d60922 F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b F ext/misc/unionvtab.c 716d385256d5fb4beea31b0efede640807e423e85c9784d21d22f0cce010a785 F ext/misc/urifuncs.c f71360d14fa9e7626b563f1f781c6148109462741c5235ac63ae0f8917b9c751 @@ -456,7 +467,7 @@ F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69 F ext/rbu/sqlite3rbu.c d4ddf8f0e93772556e452a6c2814063cf47efb760a0834391a9d0cd9859fa4b9 F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 -F ext/recover/dbdata.c fc7147a68422cbbbaa481ee92ae1752cc25f5a24302bece1c70dcb76345bd736 +F ext/recover/dbdata.c b7746c2ec801b453840831311be4b31f8c8f9dd97791060a69bbf12392c78949 F ext/recover/recover1.test c484d01502239f11b61f23c1cee9f5dd19fa17617f8974e42e74d64639c524cf F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a F ext/recover/recoverbuild.test c74170e0f7b02456af41838afeb5353fdb985a48cc2331d661bbabbca7c6b8e3 @@ -484,7 +495,7 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c 0dd4775e896cee6067979d67aff7c998e75c2c9d9cd8d62a1a790c09cde7adca -F ext/rtree/rtree.c 2e1452a9338fe4db057fa677277bed86b65c667ed48b9b59144adae99f85a7cb +F ext/rtree/rtree.c d0134bb75bc92b18a1dc011ec10419642f055c67af8ff44fc4a07c5fa9f189cb F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test 2b5b8c719c6a4abe377f57766f428a49af36a93061cb146cccfdc3b30000c0a4 F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d @@ -557,14 +568,14 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c 7b0f9bf6479aebd49e5f34cdaeb1479cde843cf5189bca6f32db4f36c130b414 +F ext/session/sqlite3session.c 829d468f0f3d2710aace56b0116a7ca3f414683ce78e3125ae5e21547a895078 F ext/session/sqlite3session.h 4cf19a51975746d7cff2fdd74db8b769c570958e1c3639ac150d824ac1553b3e F ext/session/test_session.c 7b94ad945cd4afe6c73ee935aeb3d44b4446186e1729362af616c7695a5283d9 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 0e362f3fc04eab6628cbe4f1e35f4ab4a200881f6b5f753b27fb45eabeddd9d2 +F ext/wasm/GNUmakefile 99aad6d6a28c43573f80825e986427c1a024a3298aaf0c69c56a0c6b336f12c8 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff @@ -572,7 +583,7 @@ F ext/wasm/SQLTester/SQLTester.mjs ec2f6ba63a0f2f0562941a0fb8e46b7dc55589711513f F ext/wasm/SQLTester/SQLTester.run.mjs c72b7fe2072d05992f7a3d8c6a1d34e95712513ceabe40849784e24e41c84638 F ext/wasm/SQLTester/index.html 3f8a016df0776be76605abf20e815ecaafbe055abac0e1fe5ea080e7846b760d F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536 -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api f442460ed9a109e637dd3ea1caa4489553ad9414e8988118b208bb7a4bbece6b F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 F ext/wasm/api/README.md 5eb44fa02e9c693a1884a3692428647894b0380b24bca120866b7a24c8786134 @@ -582,20 +593,22 @@ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219 F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e -F ext/wasm/api/sqlite3-api-glue.js 26aedfb27915f4f316f6eac84078443e4f0d2dfe5f012310014923ed4b77b2b6 -F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8 +F ext/wasm/api/sqlite3-api-glue.js 119b91c8a7ce6648679eb66fcdd1ed07ef7fd892eb501d658fbfefcc962012d9 +F ext/wasm/api/sqlite3-api-oo1.js 7f3bcf0549ac44cde4b9da0b642d771916738d3f6781fb8a1757c50a91e506c0 F ext/wasm/api/sqlite3-api-prologue.js 9aeba7b45cf41b3a26d34d7fb2525633cd1adfc544888c1ea8dbb077496f4ce9 -F ext/wasm/api/sqlite3-api-worker1.js f941382f21006b4a817754184e2661b0a63ce650201f3419cd60f4758b6fd60e +F ext/wasm/api/sqlite3-api-worker1.js fd46628ef147dd5856c88f63a9a279a40f744f1fdfddd55251ad8fbc3d8200ae F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 595953994aa3ae2287c889c4da39ab3d6f17b6461ecf4bec334b7a3faafddb02 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 46c4afa6c50d7369252c104f274ad977a97e91ccfafc38b400fe36e90bdda88e -F ext/wasm/api/sqlite3-wasm.c d0e09eb5ed3743c00294e30019e591c3aa150572ae7ffe8a8994568a7377589f -F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 569d4e859968e65f55dec5fac0b879828a23c8b179162cc7812fec19f844dd21 -F ext/wasm/api/sqlite3-worker1.c-pp.js a541112aa51e16705f13a99bb943c64efe178aa28c86704a955f8fd9afe4ba37 +F ext/wasm/api/sqlite3-wasm.c dfd1f1a225b267e8fd641dcd6c7d579fbe2b731aeaa123324135efac830a2bcf +F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js f234191fe6bf41a5a1e59c9f43ed816e74a522b3d60d3f556f66c3085c448503 +F ext/wasm/api/sqlite3-worker1.c-pp.js 5e8706c2c4af2a57fbcdc02f4e7ef79869971bc21bb8ede777687786ce1c92d5 +F ext/wasm/batch-runner-sahpool.html e9a38fdeb36a13eac7b50241dfe7ae066fe3f51f5c0b0151e7baee5fce0d07a7 +F ext/wasm/batch-runner-sahpool.js 54a3ac228e6c4703fe72fb65c897e19156263a51fe9b7e21d2834a45e876aabd F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 -F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e +F ext/wasm/batch-runner.js 05ec254f5dbfe605146d9640b3db17d6ef8c3fbef6aa8396051ca72bb5884e3f F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4 F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 @@ -603,7 +616,7 @@ F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a7 F ext/wasm/common/whwasmutil.js 4c64594eecc7af4ae64259e95a71ba2a7edf118881aaff0bba86d0c7164e78e4 F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 -F ext/wasm/demo-123.js 38aa8faec4d0ace1c973bc8a7a1533584463ebeecd4c420daa7d9687beeb9cb5 +F ext/wasm/demo-123.js c7b3cca50c55841c381a9ca4f9396e5bbdc6114273d0b10a43e378e32e7be5bf F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8 F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f @@ -617,26 +630,26 @@ F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d695 F ext/wasm/fiddle/fiddle-worker.js e0153f9af6500805c6f09c0b3cfdb7d857e9d6863dbee9d50d1628fccf5f4b4d F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 -F ext/wasm/index-dist.html 22379774f0ad4edcaaa8cf9c674c82e794cc557719a8addabed74eb8069d412e -F ext/wasm/index.html 4e7847b909f4ae0da8c829b150b79454050e53b3658431f138636257729cd42b +F ext/wasm/index-dist.html e91d76e4581185238fd3d42ed86ec600f7023ed3e3a944c5c356f25304bf1263 +F ext/wasm/index.html b31ce41c0da476d5ffcef23069b9d3415b419d65af5779096ebcfbcbade453a9 F ext/wasm/jaccwabyt/jaccwabyt.js 1264710db3cfbcb6887d95665b7aeba60c1126eaef789ca4cf1a4a17d5bc7f54 -F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb +F ext/wasm/jaccwabyt/jaccwabyt.md 59a20df389abcc3606eb4eaea7fb7ba14504beb3e345dbea9b99a0618ba3bec8 F ext/wasm/module-symbols.html dc476b403369b26a1a23773e13b80f41b9a49f0825e81435fe3600a7cfbbe337 F ext/wasm/scratchpad-wasmfs.html a3d7388f3c4b263676b58b526846e9d02dfcb4014ff29d3a5040935286af5b96 F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd842231505895eff00dbd57c63 F ext/wasm/speedtest1-wasmfs.html 0e9d335a9b5b5fafe6e1bc8dc0f0ca7e22e6eb916682a2d7c36218bb7d67379d F ext/wasm/speedtest1-wasmfs.mjs ac5cadbf4ffe69e9eaac8b45e8523f030521e02bb67d654c6eb5236d9c456cbe -F ext/wasm/speedtest1-worker.html e33e2064bda572c0c3ebaec7306c35aa758d9d27e245d67e807f8cc4a9351cc5 -F ext/wasm/speedtest1-worker.js 315d26198c46be7c85e26fda15d80ef882424276abde25ffd8b026fb02a35d8c +F ext/wasm/speedtest1-worker.html 864b65ed78ce24847a348c180e7f267621a02ca027068a1863ec1c90187c1852 +F ext/wasm/speedtest1-worker.js 4d2ea70a3c24e05bdca78025202841f33d298c4fa9541a0070c3228661f89ecd F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f -F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b +F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 -F ext/wasm/tester1.c-pp.js fb20d9e1c308ea34a29d8afdda1a6c5edc406241b5560fa23c19c14747ee41bc +F ext/wasm/tester1.c-pp.js a92dc256738dbd1b50f142d1fd0c835294ba09b7bb6526650360e942f88cb63f F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -644,7 +657,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk 51fd5fc8565007266e6bfd9e299199c752b171707a667b8914a204b38258e1b1 +F main.mk ef8d6b0e76b27d2b32d7b71ea30a2b2626b668f998a4f32f6171c9623a310a53 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -657,37 +670,37 @@ F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 F src/alter.c 30c2333b8bb3af71e4eb9adeadee8aa20edb15917ed44b8422e5cd15f3dfcddc -F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f +F src/analyze.c 0f15753308c3bca7674f31fa7e0807ffcb8b120c36eef7d00b62b33079ddc854 F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39 F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 -F src/btree.c f3b09c5414de3a11db73e11e1d66f4c5e53c9e89876ff3b531a887ab656ca303 x +F src/btree.c dee25e097b749275333b55d64a5ffc079249576f8e88a2ee476468cf67510f4b F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 -F src/btreeInt.h ef12a72b708677e48d6bc8dcd66fed25434740568b89e2cfa368093cfc5b9d15 -F src/build.c 189e4517d67f09f0a3e0d8e1faa6e2ef0c2e95f6ac82e33c912cb7efa2a359cc +F src/btreeInt.h 3e2589726c4f105e653461814f65857465da68be1fac688de340c43b873f4062 +F src/build.c e7d9044592eeeea8e78d8ae53ca8d31fd6e92ca0d4f53e2f2e8ccf7352e0b04b F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b -F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574 +F src/date.c 3b8d02977d160e128469de38493b4085f7c5cf4073193459909a6af3cf6d7c91 F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 -F src/expr.c cacf230b124891ea830d80395ea2119c2d7ee52c134a010f3950de9391ab79d1 +F src/expr.c 3381ee4c9aa7ccde22a2a7f35ce343925a7a25d96bdc943649131f9decdebad2 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 F src/func.c 472f6dcfa39cf54f89a6aec76c79c225fb880a6c14469c15d361331662b9bf43 -F src/global.c 29f56a330ed9d1b5cd9b79ac0ca36f97ac3afc730ff8bfa987b0db9e559d684d +F src/global.c 765a0656d6cbf043cb272ff0ae38f39cc46713539ffe6793258ed3eb4b188b52 F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c b2045aafc52f0475dc5d723d11cc0aaa9e1d233a28031b08c8174a3e407ed50c +F src/json.c 4913fd22c4f0fa30643afb93a4d78d289cd490620e782b31016c3d4b2049b1cc F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 -F src/main.c 1b89f3de98d1b59fec5bac1d66d6ece21f703821b8eaa0d53d9604c35309f6f9 +F src/main.c 438b95162acfa17b7d218f586f5bde11d6ae82bcf030c9611fc537556870ad6b F src/malloc.c f016922435dc7d1f1f5083a03338a3e91f8c67ce2c5bdcfa4cdef62e612f5fcc F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -708,33 +721,33 @@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c cb116fde9e3ca3c1bbfdf89d6928f776a2a34da168e2667426523a4db353b271 +F src/os_unix.c 5dc41030cd5dfd99b907976b1725a4ed695566405d33744e4824c3d6aff245a3 F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 987ab3a2cd9065d62e9955474470ff733445e2357432a67e3d0f5a8f9313e334 -F src/pager.h f4d33fec8052603758792045493423b8871a996da2d0973927b7d36cd6070473 +F src/pager.c ff60e98138d2499082ac6230f01ac508aba545315debccfca2fd6042f5f10fcd +F src/pager.h 4b1140d691860de0be1347474c51fee07d5420bd7f802d38cbab8ea4ab9f538a F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 -F src/pragma.c b3b4ad9c0298d63098a067acca613c21a5f56b4d176d5842922bcd0b07b7164e +F src/pragma.c b5b4cff830575e6188cd56a295a57448d2b9dbc53f0dae58e22b97354cda3781 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c bde74add20fc0e8ce0c4e937a1f70a36d17413afe4f71d3e103f5cb74b17c8d9 -F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750 +F src/prepare.c 371f6115cb69286ebc12c6f2d7511279c2e47d9f54f475d46a554d687a3b312c +F src/printf.c 18fbdf028345c8fbe6044f5f5bfda5a10d48d6287afef088cc21b0ca57985640 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 31229276a8eb5b5de1428cd2d80f6f1cf8ffc5248be25e47cf575df12f1b8f23 +F src/resolve.c e25f51a473a5f30a0d978e4df2aaa98aeec84eac29ecae1ad4708a6c3e669345 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 85857bedd2913d888aa571755b48c54cd2e6e7fcb0087e19b226ee0368cfda1e -F src/shell.c.in cbe850737d4166347bfe5fc7d695229379347509bf114cf6abeb1e384f2d7a70 -F src/sqlite.h.in 4f841d3d117b830ee5ee45e8d89ceff1195f3ebb72d041ace8d116ba4c103b35 +F src/select.c f1a81ff4f8e9e76c224e2ab3a4baa799add0db22158c7fcede65d8cc4a6fa2da +F src/shell.c.in d1ed426aae2d547932971e8019939cacb4dfda8258e45b8924b250e488e2d53d +F src/sqlite.h.in 61a60b4ea04db8ead15e1579b20b64cb56e9f55d52c5f9f9694de630110593a3 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 08efc0ad9ddbbc5ea859111521dff2c4cecc589c0c9a3e4840c927663b31445b -F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 -F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 +F src/sqliteInt.h 73800d73e21180e6b3df8d0fe7d11758dc24367fd2b0b0075b48fc116de406bb +F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728 +F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd -F src/test1.c f9620e8f0d0fa4edb239201a732c4dd1562f0cdd9741955c89332d49e14a5edd +F src/test1.c ac6542cddd1f405e332d869946b977b2ce8b4dc28b9f7cc61df38abe1fe49bc3 F src/test2.c 54520d0565ef2b9bf0f8f1dcac43dc4d06baf4ffe13d10905f8d8c3ad3e4b9ab F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 @@ -753,7 +766,7 @@ F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf F src/test_demovfs.c 38a459d1c78fd9afa770445b224c485e079018d6ac07332ff9bd07b54d2b8ce9 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 F src/test_fs.c 56cc17e4fdc57efa61695026e2ba96e910b17060d7ee01d775ec048791522e2f -F src/test_func.c 24df3a346c012b1fc9e1001d346db6054deb426db0a7437e92490630e71c9b0a +F src/test_func.c 4d2dc7e3e0946e55091784ddaf0302294f2ee300614f6f3e19a4b38df77d5167 F src/test_hexio.c 9478e56a0f08e07841a014a93b20e4ba2709ab56d039d1ca8020e26846aa19bd F src/test_init.c f2cc4774b7c9140f76e45ecbb2ae219f68e3acbbe248c0179db666a70eae9f08 F src/test_intarray.c 26ffba666beb658d73cd925d9b4fb56913a3ca9aaeac122b3691436abb192b92 @@ -787,30 +800,30 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 -F src/treeview.c 62fafcd31eea60b718f8daf448116b7b19f90134ebc6c20777ddbb07f56a3d28 +F src/treeview.c c6fc972683fd00f975d8b32a81c1f25d2fb7d4035366bf45c9f5622d3ccd70ee F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 -F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c b22cc9f203a8c0b9ee5338a67f8860347d14845864c10248bebe84518a781677 +F src/utf.c f23165685a67b4caf8ec08fb274cb3f319103decfb2a980b7cfd55d18dfa855e +F src/util.c 078f040366d5bd5f47658d045f901c768c1c636c6eaea121f3a1cbd63c3edb5b F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 -F src/vdbe.c 1cfb61c58193dcb74e8e0f90dc1405ff7fe39f43a1e9bdd38329e174f2102ed4 -F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 +F src/vdbe.c 96ac876e57f480bd35ec8d74ed992bca6ae9deebe8b527a3a718e7b4714d6c2e +F src/vdbe.h 88e19a982df9027ec1c177c793d1a5d34dc23d8f06e3b2d997f43688b05ee0eb F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c -F src/vdbeapi.c dd812ddd2f7806519e9d492c820456f86628bb6014b559034506e7f9d7f683c5 -F src/vdbeaux.c 16f27624831aa6663fe5927b647a0a54dfda0088da9b170ebc14bce98c5ac414 +F src/vdbeapi.c 8f57d60c89da0b60e6d4e272358c511f6bae4e24330bdb11f8b42f986d1bf21b +F src/vdbeaux.c c5a471b34e9c4cfc0295a3e10734fd197670ffaebcb742f284c8e17e8026ceea F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5 F src/vdbemem.c 0012d5f01cc866833847c2f3ae4c318ac53a1cb3d28acad9c35e688039464cf0 F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c 2143db7db0ceed69b21422581f434baffc507a08d831565193a7a02882a1b6a7 -F src/vtab.c 154725ebecd3bc02f7fbd7ad3974334f73fff76e02a964e828e48a7c5fb7efff +F src/vtab.c 11948e105f56e84099ca17f1f434b1944539ea84de26d0d767eadfbc670ce1ea F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9 +F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c 45b2239e127beaaae2367e503ca4c82868d8fef707c7f7f4a6c0528e7d5f65ff -F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1 +F src/where.c 217fe82a26c0fb6a3c7fd01865d821e752f9c01fb72f114af3f0b77ce234d1fb +F src/whereInt.h 82a13766f13d1a53b05387c2e60726289ef26404bc7b9b1f7770204d97357fb8 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 F src/window.c 5b1387d59df30d481ed14cceef5f4d1dab1f8752aa106ba72c8b62777bd139d2 @@ -819,8 +832,8 @@ F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867d F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggfault.test 777f269d0da5b0c2524c7ff6d99ae9a93db4f1b1839a914dd2a12e3035c29829 -F test/aggnested.test 2e738bfe2980df301a782f6e7bbf9459266f64f7e72f58f3b5c843bf897c568c -F test/aggorderby.test e6b98dbbf3ababa96892435d387de2dcf602ef02c2b848d2d817473066f154ba +F test/aggnested.test 610b0ce2c3e8f3daee25f9752800ee8d785db10da4aa1fbeea0ea1aabaf1d704 +F test/aggorderby.test cc3abf5de64d46ff66395ca8c2346b66c2576d5aedb7bffc5b0742508856e3bf F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 F test/alter.test 3c00eff1e2036b9f93e9cd0f3d3e63750ac87ecb5bc71b9d7bd07cbf2ac4c494 @@ -1016,7 +1029,7 @@ F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c47 F test/cursorhint.test 05cf0febe5c5f8a31f199401fd1c9322249e753950d55f26f9d5aca61408a270 F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8 -F test/date.test c0d17cdfd89395bc78087b131e3538d96f864b5029c335318011accc7c0d0934 +F test/date.test ff2341a1ef71b9a27979494d299222f9a293aa22cb9ff6e9c38d88a895317ebf F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1 F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5 F test/date4.test 8aeb3de5b5e9fda968baa9357e4c0fae573724b7904943410195a19e96e31b6a @@ -1157,6 +1170,7 @@ F test/fts3fault2.test 7b2741e5095367238380b0fcdb837f36c24484c7a5f353659b387df63 F test/fts3fault3.test 4a39a1618546776255dc1de306213b600aef87eca589ca8428a70c00fd11961b F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641 F test/fts3fuzz001.test c78afcd8ad712ea0b8d2ed50851a8aab3bc9dc52c64a536291e07112f519357c +F test/fts3integrity.test 0c6fe7353d7b24d78862f4272ee9df4da2f32b3ff30fa3396945cda8119580a8 F test/fts3join.test 1a4d786539b2b79a41c28ef2ac22cacd92a8ee830249b68a7dee4a020848e3bb F test/fts3malloc.test b0e4c133b8d61d4f6d112d8110f8320e9e453ef6 F test/fts3matchinfo.test aa66cc50615578b30f6df9984819ae5b702511cf8a94251ec7c594096a703a4a @@ -1204,7 +1218,7 @@ F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d F test/func.test 3a29323b640c0552f6e9f1577407ced3a68e7d8c0bc04b61dd6040fa593a3a02 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a -F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 +F test/func4.test e8ef9b2bd6a192a213cbd5cf31a3b35e25cd6ff2fdaeea0b58d63be31b03d220 F test/func5.test 863e6d1bd0013d09c17236f8a13ea34008dd857d87d85a13a673960e4c25d82a F test/func6.test 9cc9b1f43b435af34fe1416eb1e318c8920448ea7a6962f2121972f5215cb9b0 F test/func7.test adbfc910385a6ffd15dc47be3c619ef070c542fcb7488964badb17b2d9a4d080 @@ -1217,7 +1231,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 69b8549e112fb815931a8c14c7955a0c407ae91a79356eecb82458384f2cb989 +F test/fuzzcheck.c 7e1bcc242dc4b42e43e4708c8140fe268db83a6901fbc681d150c77aa185e328 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1316,17 +1330,20 @@ F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ff F test/jrnlmode.test 9b5bc01dac22223cb60ec2d5f97acf568d73820794386de5634dcadbea9e1946 F test/jrnlmode2.test 8759a1d4657c064637f8b079592651530db738419e1d649c6df7048cd724363d F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa -F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35e30ee2 +F test/json/README.md de59d5ba0bd2796d797115688630a6405bbf43a2891bad445ac6b9f38b83f236 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 -F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x -F test/json101.test bc05d2476fd6f7ead31ec05b43d1b24b2b193ae112fd8f0d2ed56d9a904f9fa5 -F test/json102.test 4c69694773a470f1fda34e5f4ba24920b35184fb66050b450fc2ef9ab5ad310b +F test/json/json-speed-check.sh 912ee03e700a65c827ee0c7b4752c21ec5ef69cf7679d2f482ca817042bead52 x +F test/json/jsonb-q1.txt 1e180fe6491efab307e318b22879e3a736ac9a96539bbde7911a13ee5b33abc7 +F test/json101.test 70587d7d35ef9e2126364ba70f0c951f70827cfbd28649d779ff3df7e8f87547 +F test/json102.test 557a46e16df1aa9bdbc4076a71a45814ea0e7503d6621d87d42a8c04cbc2b0ef F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 -F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d -F test/json501.test c419deb835b70c1a2c8532936927bcc1146730328edd2052276715bfd209724d -F test/json502.test 98c38e3c4573841028a1381dfb81d4c3f9b105d39668167da10d055e503f6d0b +F test/json105.test 043838b56e68f3252a0dcf5be1689016f6f3f05056f8dcfcdc9d074f4d932988 +F test/json106.test 1d46a9294e2ced35c7f87cebbcb9626d01abab04f1969d7ded7b6f6a1d9be0f2 +F test/json501.test ab168a12eb6eb14d479f8c1cdae3ac062fd5a4679f17f976e96f1af518408330 +F test/json502.test 84634d3dbb521d2814e43624025b760c6198456c8197bbec6c977c0236648f5b +F test/jsonb01.test cace70765b36a36aec9a85a41ea65667d3bbf647d4400ddc3ac76f8fe7d94f90 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff F test/kvtest.c 6e0228409ea7ca0497dad503fbd109badb5e59545d131014b6aaac68b56f484a F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 @@ -1487,7 +1504,6 @@ F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0 F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest_data.tcl 80ef3941bf7ad136f4dc3d8960786f10a4f488797238171bdd757cc6eb4c3efa F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb F test/returning1.test db532cde29d6aebbc48c6ddc3149b30476f8e69ca7a2c4b53986c7635e6fd8ec @@ -1567,11 +1583,12 @@ F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 F test/shell1.test c7127a5e780ffc9e14c476773127fdf292c6db226529c44c1676f37b3793123f F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a -F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f +F test/shell4.test 947029e5a9efae9054d424b309fc0311439c0c3a0866ebfa3b8a771120708220 F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915 +F test/shell9.test f457a96c088344908e0518dbabffd02eda8ac2a8733f278679e5f47c103efbab F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@ -1587,7 +1604,7 @@ F test/snapshot2.test 8d6ff5dd9cc503f6e12d408a30409c3f9c653507b24408d9cd7195931c F test/snapshot3.test 8744313270c55f6e18574283553d3c5c5fe4c5970585663613a0e75c151e599b F test/snapshot4.test d4e9347ef2fcabc491fc893506c7bbaf334da3be111d6eb4f3a97cc623b78322 F test/snapshot_fault.test 129234ceb9b26a0e1000e8563a16e790f5c1412354e70749cbd78c3d5d07d60a -F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a393cdd41c +F test/snapshot_up.test 77dc7853bfb2b4fa249f76e1714cfa1e596826165d9ef22c06ac3a0b7b778d9a F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test f86751134159abb5e5fd4381a0d7038c91013638cd1e3fa1d7850901f6df6196 @@ -1649,9 +1666,9 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede -F test/testrunner.tcl 14c8b8ece841b1dd17516a0dc9c7ad9b5f4d4db7987974d3fdf66ae56b2a71fa -F test/testrunner_data.tcl 7f73f93634d32dafc857ed491b840f371113d09fde6a8bfb9e47b938d47b8c85 +F test/tester.tcl fe617b88c7eb08bdf983d2aaa31c20fbf439eee7b8e0d61ca636fcd0c305bbbf +F test/testrunner.tcl 8e2a5c7550b78d3283eee6103104ae2bcf56aa1df892dbd1608f27b93ebf4de8 +F test/testrunner_data.tcl 7ffd951527bbc614e723fd8d123b6834321878530696adecfdf6035100bac64e F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1816,7 +1833,7 @@ F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7 F test/tpch01.test 4479008f85f6f8f25f7ab2cb305d665752b4727fa28a8df3d8e0ad46520c62ff F test/trace.test a659a9862957f4789e37a92b3bf6d2caf5c86b02cdeefc41e850ae53acf6992a F test/trace2.test f5cb67ad3bc09e0c58e8cca78dfd0b5639259983 -F test/trace3.test ae2004df24b585fed9046cc0bae4601762bc6fc4aa321d475f1350bba5047f31 +F test/trace3.test 4f418ed30d15d9d17dcf13a17f0bd99a92e3038e038798e35db7525f82f4c281 F test/trans.test 45f6f9ab6f66a7b5744f1caac06b558f95da62501916906cf55586a896f9f439 F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76 F test/trans3.test 91a100e5412b488e22a655fe423a14c26403ab94 @@ -1849,7 +1866,7 @@ F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a F test/unhex.test b7f1b806207cb77fa31c3e434fe92fba524464e3e9356809bfcc28f15af1a8b7 -F test/unionall.test eb9afa030897af75fd2f0dd28354ef63c8a5897b6c76aa1f15acae61a12eabcf +F test/unionall.test 5b1c4186a661e4bf762875caf4c61d8fda3dd04a6fa9005187f6ba8900c2913f F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1 F test/unionallfault.test 652bfbb630e6c43135965dc1e8f0a9a791da83aec885d626a632fe1909c56f73 F test/unionvtab.test e1704ab1b4c1bb3ffc9da4681f8e85a0b909fd80b937984fc94b27415ac8e5a4 @@ -1952,11 +1969,9 @@ F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747 F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1 -F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec -F test/wapptest.tcl 1bea58a6a8e68a73f542ee4fca28b771b84ed803bd0c9e385087070b3d747b3c x F test/where.test 59abb854eee24f166b5f7ba9d17eb250abc59ce0a66c48912ffb10763648196d F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6 -F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753 +F test/where3.test 4ccb156ae33de86414a52775a6f590a9d60ba2cbc7a93a24fa331b7bcf5b6030 F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8 F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b @@ -1985,7 +2000,7 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2 F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test ccfeaf116afc6a8f748a8122a4f1ee6b69e6bbc5acee61197d3c17167338b100 +F test/window1.test 5e8abe56a7d667eeddbba6de180086dcf69ed528d046447a25464f945ece101f F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -2103,13 +2118,13 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x F tool/split-sqlite3c.tcl 5aa60643afca558bc732b1444ae81a522326f91e1dc5665b369c54f09e20de60 -F tool/sqldiff.c fcccbc07da942b4534d0c769e9fcc21c67cbd7086ddc1c8f13372c40a83d4634 -F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c +F tool/sqldiff.c 985452ffc8554baee0f945d078859d393a22abc0bbf553a9f12acae1b6e1fdd0 +F tool/sqlite3_analyzer.c.in 8da2b08f56eeac331a715036cf707cc20f879f231362be0c22efd682e2b89b4f F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f -F tool/srctree-check.tcl 9f1098d439159692ece45cc9e701b35df4956269399fe0c770e41f235d1ce5e6 +F tool/srctree-check.tcl c15f860a3c97d5f7b4c14b60392d9466af29dd006c4ef18127f502641e2977a8 F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37 F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d @@ -2142,10 +2157,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f796da626b5c3b099cc246a8b46d92839922e34f398d673b0b03d7ee33a28a60 -R 1ffc537351df4c25512f7c1fd5cd9511 +P f47a5f4e0ce078e6cc1183e6cbb3c4013af379b496efae94863a42e5c39928ed +R ab5a4296dc153e787688d4ab18626d94 T +sym-release * -T +sym-version-3.44.2 * +T +sym-version-3.45.0 * U drh -Z abd52f0184ebdbba1837e841b542823e +Z fb1ecde834264212c972e5b30790a005 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 972590b28..8f36bdb72 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ebead0e7230cd33bcec9f95d2183069565b9e709bf745c9b5db65cc0cbf92c0f +1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d diff --git a/src/analyze.c b/src/analyze.c index a7a8b6d66..59e3d9837 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -264,9 +264,9 @@ static void openStatTable( typedef struct StatAccum StatAccum; typedef struct StatSample StatSample; struct StatSample { - tRowcnt *anEq; /* sqlite_stat4.nEq */ tRowcnt *anDLt; /* sqlite_stat4.nDLt */ #ifdef SQLITE_ENABLE_STAT4 + tRowcnt *anEq; /* sqlite_stat4.nEq */ tRowcnt *anLt; /* sqlite_stat4.nLt */ union { i64 iRowid; /* Rowid in main table of the key */ @@ -424,9 +424,9 @@ static void statInit( /* Allocate the space required for the StatAccum object */ n = sizeof(*p) - + sizeof(tRowcnt)*nColUp /* StatAccum.anEq */ - + sizeof(tRowcnt)*nColUp; /* StatAccum.anDLt */ + + sizeof(tRowcnt)*nColUp; /* StatAccum.anDLt */ #ifdef SQLITE_ENABLE_STAT4 + n += sizeof(tRowcnt)*nColUp; /* StatAccum.anEq */ if( mxSample ){ n += sizeof(tRowcnt)*nColUp /* StatAccum.anLt */ + sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */ @@ -447,9 +447,9 @@ static void statInit( p->nKeyCol = nKeyCol; p->nSkipAhead = 0; p->current.anDLt = (tRowcnt*)&p[1]; - p->current.anEq = &p->current.anDLt[nColUp]; #ifdef SQLITE_ENABLE_STAT4 + p->current.anEq = &p->current.anDLt[nColUp]; p->mxSample = p->nLimit==0 ? mxSample : 0; if( mxSample ){ u8 *pSpace; /* Allocated space not yet assigned */ @@ -716,7 +716,9 @@ static void statPush( if( p->nRow==0 ){ /* This is the first call to this function. Do initialization. */ +#ifdef SQLITE_ENABLE_STAT4 for(i=0; inCol; i++) p->current.anEq[i] = 1; +#endif }else{ /* Second and subsequent calls get processed here */ #ifdef SQLITE_ENABLE_STAT4 @@ -725,15 +727,17 @@ static void statPush( /* Update anDLt[], anLt[] and anEq[] to reflect the values that apply ** to the current row of the index. */ +#ifdef SQLITE_ENABLE_STAT4 for(i=0; icurrent.anEq[i]++; } +#endif for(i=iChng; inCol; i++){ p->current.anDLt[i]++; #ifdef SQLITE_ENABLE_STAT4 if( p->mxSample ) p->current.anLt[i] += p->current.anEq[i]; -#endif p->current.anEq[i] = 1; +#endif } } @@ -867,7 +871,9 @@ static void statGet( u64 iVal = (p->nRow + nDistinct - 1) / nDistinct; if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1; sqlite3_str_appendf(&sStat, " %llu", iVal); +#ifdef SQLITE_ENABLE_STAT4 assert( p->current.anEq[i] ); +#endif } sqlite3ResultStrAccum(context, &sStat); } @@ -1556,6 +1562,16 @@ static void decodeIntArray( while( z[0]!=0 && z[0]!=' ' ) z++; while( z[0]==' ' ) z++; } + + /* Set the bLowQual flag if the peak number of rows obtained + ** from a full equality match is so large that a full table scan + ** seems likely to be faster than using the index. + */ + if( aLog[0] > 66 /* Index has more than 100 rows */ + && aLog[0] <= aLog[nOut-1] /* And only a single value seen */ + ){ + pIndex->bLowQual = 1; + } } } diff --git a/src/btree.c b/src/btree.c old mode 100755 new mode 100644 index 5be30d562..907e37f1e --- a/src/btree.c +++ b/src/btree.c @@ -5161,7 +5161,6 @@ static int accessPayload( assert( aWrite>=pBufStart ); /* due to (6) */ memcpy(aSave, aWrite, 4); rc = sqlite3OsRead(fd, aWrite, a+4, (i64)pBt->pageSize*(nextPage-1)); - if( rc && nextPage>pBt->nPage ) rc = SQLITE_CORRUPT_BKPT; nextPage = get4byte(aWrite); memcpy(aWrite, aSave, 4); }else diff --git a/src/btreeInt.h b/src/btreeInt.h index 563e15f8a..67a7db25c 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -64,7 +64,7 @@ ** 22 1 Min embedded payload fraction (must be 32) ** 23 1 Min leaf payload fraction (must be 32) ** 24 4 File change counter -** 28 4 Reserved for future use +** 28 4 The size of the database in pages ** 32 4 First freelist page ** 36 4 Number of freelist pages in the file ** 40 60 15 4-byte meta values passed to higher layers diff --git a/src/build.c b/src/build.c index 3d0679235..a2553da9f 100644 --- a/src/build.c +++ b/src/build.c @@ -720,7 +720,7 @@ void sqlite3ColumnSetExpr( */ Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){ if( pCol->iDflt==0 ) return 0; - if( NEVER(!IsOrdinaryTable(pTab)) ) return 0; + if( !IsOrdinaryTable(pTab) ) return 0; if( NEVER(pTab->u.tab.pDfltList==0) ) return 0; if( NEVER(pTab->u.tab.pDfltList->nExpriDflt) ) return 0; return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; @@ -873,6 +873,9 @@ void sqlite3DeleteTable(sqlite3 *db, Table *pTable){ if( db->pnBytesFreed==0 && (--pTable->nTabRef)>0 ) return; deleteTable(db, pTable); } +void sqlite3DeleteTableGeneric(sqlite3 *db, void *pTable){ + sqlite3DeleteTable(db, (Table*)pTable); +} /* @@ -1410,7 +1413,8 @@ void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ /* ** Clean up the data structures associated with the RETURNING clause. */ -static void sqlite3DeleteReturning(sqlite3 *db, Returning *pRet){ +static void sqlite3DeleteReturning(sqlite3 *db, void *pArg){ + Returning *pRet = (Returning*)pArg; Hash *pHash; pHash = &(db->aDb[1].pSchema->trigHash); sqlite3HashInsert(pHash, pRet->zName, 0); @@ -1452,8 +1456,7 @@ void sqlite3AddReturning(Parse *pParse, ExprList *pList){ pParse->u1.pReturning = pRet; pRet->pParse = pParse; pRet->pReturnEL = pList; - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3DeleteReturning, pRet); + sqlite3ParserAddCleanup(pParse, sqlite3DeleteReturning, pRet); testcase( pParse->earlyCleanup ); if( db->mallocFailed ) return; sqlite3_snprintf(sizeof(pRet->zName), pRet->zName, @@ -1652,7 +1655,8 @@ char sqlite3AffinityType(const char *zIn, Column *pCol){ assert( zIn!=0 ); while( zIn[0] ){ - h = (h<<8) + sqlite3UpperToLower[(*zIn)&0xff]; + u8 x = *(u8*)zIn; + h = (h<<8) + sqlite3UpperToLower[x]; zIn++; if( h==(('c'<<24)+('h'<<16)+('a'<<8)+'r') ){ /* CHAR */ aff = SQLITE_AFF_TEXT; @@ -5517,7 +5521,7 @@ void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ if( iDb<0 ) return; z = sqlite3NameFromToken(db, pObjName); if( z==0 ) return; - zDb = db->aDb[iDb].zDbSName; + zDb = pName2->n ? db->aDb[iDb].zDbSName : 0; pTab = sqlite3FindTable(db, z, zDb); if( pTab ){ reindexTable(pParse, pTab, 0); @@ -5527,6 +5531,7 @@ void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ pIndex = sqlite3FindIndex(db, z, zDb); sqlite3DbFree(db, z); if( pIndex ){ + iDb = sqlite3SchemaToIndex(db, pIndex->pTable->pSchema); sqlite3BeginWriteOperation(pParse, 0, iDb); sqlite3RefillIndex(pParse, pIndex, -1); return; @@ -5692,4 +5697,7 @@ void sqlite3WithDelete(sqlite3 *db, With *pWith){ sqlite3DbFree(db, pWith); } } +void sqlite3WithDeleteGeneric(sqlite3 *db, void *pWith){ + sqlite3WithDelete(db, (With*)pWith); +} #endif /* !defined(SQLITE_OMIT_CTE) */ diff --git a/src/date.c b/src/date.c index f16308544..e493542db 100644 --- a/src/date.c +++ b/src/date.c @@ -1043,6 +1043,12 @@ static int isDate( } computeJD(p); if( p->isError || !validJulianDay(p->iJD) ) return 1; + if( argc==1 && p->validYMD && p->D>28 ){ + /* Make sure a YYYY-MM-DD is normalized. + ** Example: 2023-02-31 -> 2023-03-03 */ + assert( p->validJD ); + p->validYMD = 0; + } return 0; } diff --git a/src/expr.c b/src/expr.c index 34d78741d..f9b280bbc 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1223,9 +1223,7 @@ void sqlite3ExprAddFunctionOrderBy( assert( ExprUseXList(pExpr) ); if( pExpr->x.pList==0 || NEVER(pExpr->x.pList->nExpr==0) ){ /* Ignore ORDER BY on zero-argument aggregates */ - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3ExprListDelete, - pOrderBy); + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, pOrderBy); return; } if( IsWindowFunc(pExpr) ){ @@ -1406,6 +1404,9 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ void sqlite3ExprDelete(sqlite3 *db, Expr *p){ if( p ) sqlite3ExprDeleteNN(db, p); } +void sqlite3ExprDeleteGeneric(sqlite3 *db, void *p){ + if( ALWAYS(p) ) sqlite3ExprDeleteNN(db, (Expr*)p); +} /* ** Clear both elements of an OnOrUsing object @@ -1431,9 +1432,7 @@ void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ ** pExpr to the pParse->pConstExpr list with a register number of 0. */ void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3ExprDelete, - pExpr); + sqlite3ParserAddCleanup(pParse, sqlite3ExprDeleteGeneric, pExpr); } /* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the @@ -2239,6 +2238,9 @@ static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){ void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ if( pList ) exprListDeleteNN(db, pList); } +void sqlite3ExprListDeleteGeneric(sqlite3 *db, void *pList){ + if( ALWAYS(pList) ) exprListDeleteNN(db, (ExprList*)pList); +} /* ** Return the bitwise-OR of all Expr.flags fields in the given @@ -2738,9 +2740,10 @@ int sqlite3ExprCanBeNull(const Expr *p){ case TK_COLUMN: assert( ExprUseYTab(p) ); return ExprHasProperty(p, EP_CanBeNull) || - p->y.pTab==0 || /* Reference to column of index on expression */ + NEVER(p->y.pTab==0) || /* Reference to column of index on expr */ (p->iColumn>=0 && p->y.pTab->aCol!=0 /* Possible due to prior error */ + && ALWAYS(p->iColumny.pTab->nCol) && p->y.pTab->aCol[p->iColumn].notNull==0); default: return 1; @@ -5322,8 +5325,10 @@ void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); if( inReg!=target ){ u8 op; - if( ALWAYS(pExpr) - && (ExprHasProperty(pExpr,EP_Subquery) || pExpr->op==TK_REGISTER) + Expr *pX = sqlite3ExprSkipCollateAndLikely(pExpr); + testcase( pX!=pExpr ); + if( ALWAYS(pX) + && (ExprHasProperty(pX,EP_Subquery) || pX->op==TK_REGISTER) ){ op = OP_Copy; }else{ @@ -6769,13 +6774,14 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ case TK_AGG_FUNCTION: { if( (pNC->ncFlags & NC_InAggFunc)==0 && pWalker->walkerDepth==pExpr->op2 + && pExpr->pAggInfo==0 ){ /* Check to see if pExpr is a duplicate of another aggregate ** function that is already in the pAggInfo structure */ struct AggInfo_func *pItem = pAggInfo->aFunc; for(i=0; inFunc; i++, pItem++){ - if( pItem->pFExpr==pExpr ) break; + if( NEVER(pItem->pFExpr==pExpr) ) break; if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){ break; } @@ -6818,6 +6824,8 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ }else{ pItem->bOBPayload = 1; } + pItem->bUseSubtype = + (pItem->pFunc->funcFlags & SQLITE_SUBTYPE)!=0; }else{ pItem->iOBTab = -1; } diff --git a/src/global.c b/src/global.c index 60cd13e2a..7f27d91d1 100644 --- a/src/global.c +++ b/src/global.c @@ -244,6 +244,9 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* bSmallMalloc */ 1, /* bExtraSchemaChecks */ sizeof(LONGDOUBLE_TYPE)>8, /* bUseLongDouble */ +#ifdef SQLITE_DEBUG + 0, /* bJsonSelfcheck */ +#endif 0x7ffffffe, /* mxStrlen */ 0, /* neverCorrupt */ SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ diff --git a/src/json.c b/src/json.c index de7143bb1..682f30597 100644 --- a/src/json.c +++ b/src/json.c @@ -10,24 +10,145 @@ ** ****************************************************************************** ** -** This SQLite JSON functions. +** SQLite JSON functions. ** ** This file began as an extension in ext/misc/json1.c in 2015. That ** extension proved so useful that it has now been moved into the core. ** -** For the time being, all JSON is stored as pure text. (We might add -** a JSONB type in the future which stores a binary encoding of JSON in -** a BLOB, but there is no support for JSONB in the current implementation. -** This implementation parses JSON text at 250 MB/s, so it is hard to see -** how JSONB might improve on that.) +** The original design stored all JSON as pure text, canonical RFC-8259. +** Support for JSON-5 extensions was added with version 3.42.0 (2023-05-16). +** All generated JSON text still conforms strictly to RFC-8259, but text +** with JSON-5 extensions is accepted as input. +** +** Beginning with version 3.45.0 (circa 2024-01-01), these routines also +** accept BLOB values that have JSON encoded using a binary representation +** called "JSONB". The name JSONB comes from PostgreSQL, however the on-disk +** format SQLite JSONB is completely different and incompatible with +** PostgreSQL JSONB. +** +** Decoding and interpreting JSONB is still O(N) where N is the size of +** the input, the same as text JSON. However, the constant of proportionality +** for JSONB is much smaller due to faster parsing. The size of each +** element in JSONB is encoded in its header, so there is no need to search +** for delimiters using persnickety syntax rules. JSONB seems to be about +** 3x faster than text JSON as a result. JSONB is also tends to be slightly +** smaller than text JSON, by 5% or 10%, but there are corner cases where +** JSONB can be slightly larger. So you are not far mistaken to say that +** a JSONB blob is the same size as the equivalent RFC-8259 text. +** +** +** THE JSONB ENCODING: +** +** Every JSON element is encoded in JSONB as a header and a payload. +** The header is between 1 and 9 bytes in size. The payload is zero +** or more bytes. +** +** The lower 4 bits of the first byte of the header determines the +** element type: +** +** 0: NULL +** 1: TRUE +** 2: FALSE +** 3: INT -- RFC-8259 integer literal +** 4: INT5 -- JSON5 integer literal +** 5: FLOAT -- RFC-8259 floating point literal +** 6: FLOAT5 -- JSON5 floating point literal +** 7: TEXT -- Text literal acceptable to both SQL and JSON +** 8: TEXTJ -- Text containing RFC-8259 escapes +** 9: TEXT5 -- Text containing JSON5 and/or RFC-8259 escapes +** 10: TEXTRAW -- Text containing unescaped syntax characters +** 11: ARRAY +** 12: OBJECT +** +** The other three possible values (13-15) are reserved for future +** enhancements. +** +** The upper 4 bits of the first byte determine the size of the header +** and sometimes also the size of the payload. If X is the first byte +** of the element and if X>>4 is between 0 and 11, then the payload +** will be that many bytes in size and the header is exactly one byte +** in size. Other four values for X>>4 (12-15) indicate that the header +** is more than one byte in size and that the payload size is determined +** by the remainder of the header, interpreted as a unsigned big-endian +** integer. +** +** Value of X>>4 Size integer Total header size +** ------------- -------------------- ----------------- +** 12 1 byte (0-255) 2 +** 13 2 byte (0-65535) 3 +** 14 4 byte (0-4294967295) 5 +** 15 8 byte (0-1.8e19) 9 +** +** The payload size need not be expressed in its minimal form. For example, +** if the payload size is 10, the size can be expressed in any of 5 different +** ways: (1) (X>>4)==10, (2) (X>>4)==12 following by on 0x0a byte, +** (3) (X>>4)==13 followed by 0x00 and 0x0a, (4) (X>>4)==14 followed by +** 0x00 0x00 0x00 0x0a, or (5) (X>>4)==15 followed by 7 bytes of 0x00 and +** a single byte of 0x0a. The shorter forms are preferred, of course, but +** sometimes when generating JSONB, the payload size is not known in advance +** and it is convenient to reserve sufficient header space to cover the +** largest possible payload size and then come back later and patch up +** the size when it becomes known, resulting in a non-minimal encoding. +** +** The value (X>>4)==15 is not actually used in the current implementation +** (as SQLite is currently unable handle BLOBs larger than about 2GB) +** but is included in the design to allow for future enhancements. +** +** The payload follows the header. NULL, TRUE, and FALSE have no payload and +** their payload size must always be zero. The payload for INT, INT5, +** FLOAT, FLOAT5, TEXT, TEXTJ, TEXT5, and TEXTROW is text. Note that the +** "..." or '...' delimiters are omitted from the various text encodings. +** The payload for ARRAY and OBJECT is a list of additional elements that +** are the content for the array or object. The payload for an OBJECT +** must be an even number of elements. The first element of each pair is +** the label and must be of type TEXT, TEXTJ, TEXT5, or TEXTRAW. +** +** A valid JSONB blob consists of a single element, as described above. +** Usually this will be an ARRAY or OBJECT element which has many more +** elements as its content. But the overall blob is just a single element. +** +** Input validation for JSONB blobs simply checks that the element type +** code is between 0 and 12 and that the total size of the element +** (header plus payload) is the same as the size of the BLOB. If those +** checks are true, the BLOB is assumed to be JSONB and processing continues. +** Errors are only raised if some other miscoding is discovered during +** processing. +** +** Additional information can be found in the doc/jsonb.md file of the +** canonical SQLite source tree. */ #ifndef SQLITE_OMIT_JSON #include "sqliteInt.h" +/* JSONB element types +*/ +#define JSONB_NULL 0 /* "null" */ +#define JSONB_TRUE 1 /* "true" */ +#define JSONB_FALSE 2 /* "false" */ +#define JSONB_INT 3 /* integer acceptable to JSON and SQL */ +#define JSONB_INT5 4 /* integer in 0x000 notation */ +#define JSONB_FLOAT 5 /* float acceptable to JSON and SQL */ +#define JSONB_FLOAT5 6 /* float with JSON5 extensions */ +#define JSONB_TEXT 7 /* Text compatible with both JSON and SQL */ +#define JSONB_TEXTJ 8 /* Text with JSON escapes */ +#define JSONB_TEXT5 9 /* Text with JSON-5 escape */ +#define JSONB_TEXTRAW 10 /* SQL text that needs escaping for JSON */ +#define JSONB_ARRAY 11 /* An array */ +#define JSONB_OBJECT 12 /* An object */ + +/* Human-readable names for the JSONB values. The index for each +** string must correspond to the JSONB_* integer above. +*/ +static const char * const jsonbType[] = { + "null", "true", "false", "integer", "integer", + "real", "real", "text", "text", "text", + "text", "array", "object", "", "", "", "" +}; + /* ** Growing our own isspace() routine this way is twice as fast as ** the library isspace() function, resulting in a 7% overall performance -** increase for the parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). +** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). */ static const char jsonIsSpace[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, @@ -48,11 +169,19 @@ static const char jsonIsSpace[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -#define fast_isspace(x) (jsonIsSpace[(unsigned char)x]) +#define jsonIsspace(x) (jsonIsSpace[(unsigned char)x]) /* -** Characters that are special to JSON. Control charaters, -** '"' and '\\'. +** The set of all space characters recognized by jsonIsspace(). +** Useful as the second argument to strspn(). +*/ +static const char jsonSpaces[] = "\011\012\015\040"; + +/* +** Characters that are special to JSON. Control characters, +** '"' and '\\' and '\''. Actually, '\'' is not special to +** canonical JSON, but it is special in JSON-5, so we include +** it in the set of special characters. */ static const char jsonIsOk[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -74,22 +203,49 @@ static const char jsonIsOk[256] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - -#if !defined(SQLITE_DEBUG) && !defined(SQLITE_COVERAGE_TEST) -# define VVA(X) -#else -# define VVA(X) X -#endif - /* Objects */ +typedef struct JsonCache JsonCache; typedef struct JsonString JsonString; -typedef struct JsonNode JsonNode; typedef struct JsonParse JsonParse; -typedef struct JsonCleanup JsonCleanup; + +/* +** Magic number used for the JSON parse cache in sqlite3_get_auxdata() +*/ +#define JSON_CACHE_ID (-429938) /* Cache entry */ +#define JSON_CACHE_SIZE 4 /* Max number of cache entries */ + +/* +** jsonUnescapeOneChar() returns this invalid code point if it encounters +** a syntax error. +*/ +#define JSON_INVALID_CHAR 0x99999 + +/* A cache mapping JSON text into JSONB blobs. +** +** Each cache entry is a JsonParse object with the following restrictions: +** +** * The bReadOnly flag must be set +** +** * The aBlob[] array must be owned by the JsonParse object. In other +** words, nBlobAlloc must be non-zero. +** +** * eEdit and delta must be zero. +** +** * zJson must be an RCStr. In other words bJsonIsRCStr must be true. +*/ +struct JsonCache { + sqlite3 *db; /* Database connection */ + int nUsed; /* Number of active entries in the cache */ + JsonParse *a[JSON_CACHE_SIZE]; /* One line for each cache entry */ +}; /* An instance of this object represents a JSON string ** under construction. Really, this is a generic string accumulator ** that can be and is used to create strings other than JSON. +** +** If the generated string is longer than will fit into the zSpace[] buffer, +** then it will be an RCStr string. This aids with caching of large +** JSON strings. */ struct JsonString { sqlite3_context *pCtx; /* Function context - put error messages here */ @@ -97,121 +253,75 @@ struct JsonString { u64 nAlloc; /* Bytes of storage available in zBuf[] */ u64 nUsed; /* Bytes of zBuf[] currently used */ u8 bStatic; /* True if zBuf is static space */ - u8 bErr; /* True if an error has been encountered */ + u8 eErr; /* True if an error has been encountered */ char zSpace[100]; /* Initial static space */ }; -/* A deferred cleanup task. A list of JsonCleanup objects might be -** run when the JsonParse object is destroyed. -*/ -struct JsonCleanup { - JsonCleanup *pJCNext; /* Next in a list */ - void (*xOp)(void*); /* Routine to run */ - void *pArg; /* Argument to xOp() */ -}; +/* Allowed values for JsonString.eErr */ +#define JSTRING_OOM 0x01 /* Out of memory */ +#define JSTRING_MALFORMED 0x02 /* Malformed JSONB */ +#define JSTRING_ERR 0x04 /* Error already sent to sqlite3_result */ -/* JSON type values +/* The "subtype" set for text JSON values passed through using +** sqlite3_result_subtype() and sqlite3_value_subtype(). */ -#define JSON_SUBST 0 /* Special edit node. Uses u.iPrev */ -#define JSON_NULL 1 -#define JSON_TRUE 2 -#define JSON_FALSE 3 -#define JSON_INT 4 -#define JSON_REAL 5 -#define JSON_STRING 6 -#define JSON_ARRAY 7 -#define JSON_OBJECT 8 - -/* The "subtype" set for JSON values */ #define JSON_SUBTYPE 74 /* Ascii for "J" */ /* -** Names of the various JSON types: +** Bit values for the flags passed into various SQL function implementations +** via the sqlite3_user_data() value. */ -static const char * const jsonType[] = { - "subst", - "null", "true", "false", "integer", "real", "text", "array", "object" -}; - -/* Bit values for the JsonNode.jnFlag field -*/ -#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ -#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ -#define JNODE_REMOVE 0x04 /* Do not output */ -#define JNODE_REPLACE 0x08 /* Target of a JSON_SUBST node */ -#define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */ -#define JNODE_LABEL 0x20 /* Is a label of an object */ -#define JNODE_JSON5 0x40 /* Node contains JSON5 enhancements */ - - -/* A single node of parsed JSON. An array of these nodes describes -** a parse of JSON + edits. -** -** Use the json_parse() SQL function (available when compiled with -** -DSQLITE_DEBUG) to see a dump of complete JsonParse objects, including -** a complete listing and decoding of the array of JsonNodes. -*/ -struct JsonNode { - u8 eType; /* One of the JSON_ type values */ - u8 jnFlags; /* JNODE flags */ - u8 eU; /* Which union element to use */ - u32 n; /* Bytes of content for INT, REAL or STRING - ** Number of sub-nodes for ARRAY and OBJECT - ** Node that SUBST applies to */ - union { - const char *zJContent; /* 1: Content for INT, REAL, and STRING */ - u32 iAppend; /* 2: More terms for ARRAY and OBJECT */ - u32 iKey; /* 3: Key for ARRAY objects in json_tree() */ - u32 iPrev; /* 4: Previous SUBST node, or 0 */ - } u; -}; +#define JSON_JSON 0x01 /* Result is always JSON */ +#define JSON_SQL 0x02 /* Result is always SQL */ +#define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ +#define JSON_ISSET 0x04 /* json_set(), not json_insert() */ +#define JSON_BLOB 0x08 /* Use the BLOB output format */ -/* A parsed and possibly edited JSON string. Lifecycle: -** -** 1. JSON comes in and is parsed into an array aNode[]. The original -** JSON text is stored in zJson. -** -** 2. Zero or more changes are made (via json_remove() or json_replace() -** or similar) to the aNode[] array. -** -** 3. A new, edited and mimified JSON string is generated from aNode -** and stored in zAlt. The JsonParse object always owns zAlt. +/* A parsed JSON value. Lifecycle: ** -** Step 1 always happens. Step 2 and 3 may or may not happen, depending -** on the operation. +** 1. JSON comes in and is parsed into a JSONB value in aBlob. The +** original text is stored in zJson. This step is skipped if the +** input is JSONB instead of text JSON. ** -** aNode[].u.zJContent entries typically point into zJson. Hence zJson -** must remain valid for the lifespan of the parse. For edits, -** aNode[].u.zJContent might point to malloced space other than zJson. -** Entries in pClup are responsible for freeing that extra malloced space. +** 2. The aBlob[] array is searched using the JSON path notation, if needed. +** +** 3. Zero or more changes are made to aBlob[] (via json_remove() or +** json_replace() or json_patch() or similar). ** -** When walking the parse tree in aNode[], edits are ignored if useMod is -** false. +** 4. New JSON text is generated from the aBlob[] for output. This step +** is skipped if the function is one of the jsonb_* functions that +** returns JSONB instead of text JSON. */ struct JsonParse { - u32 nNode; /* Number of slots of aNode[] used */ - u32 nAlloc; /* Number of slots of aNode[] allocated */ - JsonNode *aNode; /* Array of nodes containing the parse */ - char *zJson; /* Original JSON string (before edits) */ - char *zAlt; /* Revised and/or mimified JSON */ - u32 *aUp; /* Index of parent of each node */ - JsonCleanup *pClup;/* Cleanup operations prior to freeing this object */ + u8 *aBlob; /* JSONB representation of JSON value */ + u32 nBlob; /* Bytes of aBlob[] actually used */ + u32 nBlobAlloc; /* Bytes allocated to aBlob[]. 0 if aBlob is external */ + char *zJson; /* Json text used for parsing */ + sqlite3 *db; /* The database connection to which this object belongs */ + int nJson; /* Length of the zJson string in bytes */ + u32 nJPRef; /* Number of references to this object */ + u32 iErr; /* Error location in zJson[] */ u16 iDepth; /* Nesting depth */ u8 nErr; /* Number of errors seen */ u8 oom; /* Set to true if out of memory */ u8 bJsonIsRCStr; /* True if zJson is an RCStr */ u8 hasNonstd; /* True if input uses non-standard features like JSON5 */ - u8 useMod; /* Actually use the edits contain inside aNode */ - u8 hasMod; /* aNode contains edits from the original zJson */ - u32 nJPRef; /* Number of references to this object */ - int nJson; /* Length of the zJson string in bytes */ - int nAlt; /* Length of alternative JSON string zAlt, in bytes */ - u32 iErr; /* Error location in zJson[] */ - u32 iSubst; /* Last JSON_SUBST entry in aNode[] */ - u32 iHold; /* Age of this entry in the cache for LRU replacement */ + u8 bReadOnly; /* Do not modify. */ + /* Search and edit information. See jsonLookupStep() */ + u8 eEdit; /* Edit operation to apply */ + int delta; /* Size change due to the edit */ + u32 nIns; /* Number of bytes to insert */ + u32 iLabel; /* Location of label if search landed on an object value */ + u8 *aIns; /* Content to be inserted */ }; +/* Allowed values for JsonParse.eEdit */ +#define JEDIT_DEL 1 /* Delete if exists */ +#define JEDIT_REPL 2 /* Overwrite if exists */ +#define JEDIT_INS 3 /* Insert if not exists */ +#define JEDIT_SET 4 /* Insert or overwrite */ + /* ** Maximum nesting depth of JSON for this implementation. ** @@ -219,15 +329,151 @@ struct JsonParse { ** descent parser. A depth of 1000 is far deeper than any sane JSON ** should go. Historical note: This limit was 2000 prior to version 3.42.0 */ -#define JSON_MAX_DEPTH 1000 +#ifndef SQLITE_JSON_MAX_DEPTH +# define JSON_MAX_DEPTH 1000 +#else +# define JSON_MAX_DEPTH SQLITE_JSON_MAX_DEPTH +#endif + +/* +** Allowed values for the flgs argument to jsonParseFuncArg(); +*/ +#define JSON_EDITABLE 0x01 /* Generate a writable JsonParse object */ +#define JSON_KEEPERROR 0x02 /* Return non-NULL even if there is an error */ + +/************************************************************************** +** Forward references +**************************************************************************/ +static void jsonReturnStringAsBlob(JsonString*); +static int jsonFuncArgMightBeBinary(sqlite3_value *pJson); +static u32 jsonTranslateBlobToText(const JsonParse*,u32,JsonString*); +static void jsonReturnParse(sqlite3_context*,JsonParse*); +static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32); +static void jsonParseFree(JsonParse*); +static u32 jsonbPayloadSize(const JsonParse*, u32, u32*); +static u32 jsonUnescapeOneChar(const char*, u32, u32*); + +/************************************************************************** +** Utility routines for dealing with JsonCache objects +**************************************************************************/ + +/* +** Free a JsonCache object. +*/ +static void jsonCacheDelete(JsonCache *p){ + int i; + for(i=0; inUsed; i++){ + jsonParseFree(p->a[i]); + } + sqlite3DbFree(p->db, p); +} +static void jsonCacheDeleteGeneric(void *p){ + jsonCacheDelete((JsonCache*)p); +} + +/* +** Insert a new entry into the cache. If the cache is full, expel +** the least recently used entry. Return SQLITE_OK on success or a +** result code otherwise. +** +** Cache entries are stored in age order, oldest first. +*/ +static int jsonCacheInsert( + sqlite3_context *ctx, /* The SQL statement context holding the cache */ + JsonParse *pParse /* The parse object to be added to the cache */ +){ + JsonCache *p; + + assert( pParse->zJson!=0 ); + assert( pParse->bJsonIsRCStr ); + assert( pParse->delta==0 ); + p = sqlite3_get_auxdata(ctx, JSON_CACHE_ID); + if( p==0 ){ + sqlite3 *db = sqlite3_context_db_handle(ctx); + p = sqlite3DbMallocZero(db, sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM; + p->db = db; + sqlite3_set_auxdata(ctx, JSON_CACHE_ID, p, jsonCacheDeleteGeneric); + p = sqlite3_get_auxdata(ctx, JSON_CACHE_ID); + if( p==0 ) return SQLITE_NOMEM; + } + if( p->nUsed >= JSON_CACHE_SIZE ){ + jsonParseFree(p->a[0]); + memmove(p->a, &p->a[1], (JSON_CACHE_SIZE-1)*sizeof(p->a[0])); + p->nUsed = JSON_CACHE_SIZE-1; + } + assert( pParse->nBlobAlloc>0 ); + pParse->eEdit = 0; + pParse->nJPRef++; + pParse->bReadOnly = 1; + p->a[p->nUsed] = pParse; + p->nUsed++; + return SQLITE_OK; +} + +/* +** Search for a cached translation the json text supplied by pArg. Return +** the JsonParse object if found. Return NULL if not found. +** +** When a match if found, the matching entry is moved to become the +** most-recently used entry if it isn't so already. +** +** The JsonParse object returned still belongs to the Cache and might +** be deleted at any moment. If the caller whants the JsonParse to +** linger, it needs to increment the nPJRef reference counter. +*/ +static JsonParse *jsonCacheSearch( + sqlite3_context *ctx, /* The SQL statement context holding the cache */ + sqlite3_value *pArg /* Function argument containing SQL text */ +){ + JsonCache *p; + int i; + const char *zJson; + int nJson; + + if( sqlite3_value_type(pArg)!=SQLITE_TEXT ){ + return 0; + } + zJson = (const char*)sqlite3_value_text(pArg); + if( zJson==0 ) return 0; + nJson = sqlite3_value_bytes(pArg); + + p = sqlite3_get_auxdata(ctx, JSON_CACHE_ID); + if( p==0 ){ + return 0; + } + for(i=0; inUsed; i++){ + if( p->a[i]->zJson==zJson ) break; + } + if( i>=p->nUsed ){ + for(i=0; inUsed; i++){ + if( p->a[i]->nJson!=nJson ) continue; + if( memcmp(p->a[i]->zJson, zJson, nJson)==0 ) break; + } + } + if( inUsed ){ + if( inUsed-1 ){ + /* Make the matching entry the most recently used entry */ + JsonParse *tmp = p->a[i]; + memmove(&p->a[i], &p->a[i+1], (p->nUsed-i-1)*sizeof(tmp)); + p->a[p->nUsed-1] = tmp; + i = p->nUsed - 1; + } + assert( p->a[i]->delta==0 ); + return p->a[i]; + }else{ + return 0; + } +} /************************************************************************** ** Utility routines for dealing with JsonString objects **************************************************************************/ -/* Set the JsonString object to an empty string +/* Turn uninitialized bulk memory into a valid JsonString object +** holding a zero-length string. */ -static void jsonZero(JsonString *p){ +static void jsonStringZero(JsonString *p){ p->zBuf = p->zSpace; p->nAlloc = sizeof(p->zSpace); p->nUsed = 0; @@ -236,39 +482,39 @@ static void jsonZero(JsonString *p){ /* Initialize the JsonString object */ -static void jsonInit(JsonString *p, sqlite3_context *pCtx){ +static void jsonStringInit(JsonString *p, sqlite3_context *pCtx){ p->pCtx = pCtx; - p->bErr = 0; - jsonZero(p); + p->eErr = 0; + jsonStringZero(p); } /* Free all allocated memory and reset the JsonString object back to its ** initial state. */ -static void jsonReset(JsonString *p){ +static void jsonStringReset(JsonString *p){ if( !p->bStatic ) sqlite3RCStrUnref(p->zBuf); - jsonZero(p); + jsonStringZero(p); } /* Report an out-of-memory (OOM) condition */ -static void jsonOom(JsonString *p){ - p->bErr = 1; - sqlite3_result_error_nomem(p->pCtx); - jsonReset(p); +static void jsonStringOom(JsonString *p){ + p->eErr |= JSTRING_OOM; + if( p->pCtx ) sqlite3_result_error_nomem(p->pCtx); + jsonStringReset(p); } /* Enlarge pJson->zBuf so that it can hold at least N more bytes. ** Return zero on success. Return non-zero on an OOM error */ -static int jsonGrow(JsonString *p, u32 N){ +static int jsonStringGrow(JsonString *p, u32 N){ u64 nTotal = NnAlloc ? p->nAlloc*2 : p->nAlloc+N+10; char *zNew; if( p->bStatic ){ - if( p->bErr ) return 1; + if( p->eErr ) return 1; zNew = sqlite3RCStrNew(nTotal); if( zNew==0 ){ - jsonOom(p); + jsonStringOom(p); return SQLITE_NOMEM; } memcpy(zNew, p->zBuf, (size_t)p->nUsed); @@ -277,8 +523,8 @@ static int jsonGrow(JsonString *p, u32 N){ }else{ p->zBuf = sqlite3RCStrResize(p->zBuf, nTotal); if( p->zBuf==0 ){ - p->bErr = 1; - jsonZero(p); + p->eErr |= JSTRING_OOM; + jsonStringZero(p); return SQLITE_NOMEM; } } @@ -288,20 +534,20 @@ static int jsonGrow(JsonString *p, u32 N){ /* Append N bytes from zIn onto the end of the JsonString string. */ -static SQLITE_NOINLINE void jsonAppendExpand( +static SQLITE_NOINLINE void jsonStringExpandAndAppend( JsonString *p, const char *zIn, u32 N ){ assert( N>0 ); - if( jsonGrow(p,N) ) return; + if( jsonStringGrow(p,N) ) return; memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; } static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ if( N==0 ) return; if( N+p->nUsed >= p->nAlloc ){ - jsonAppendExpand(p,zIn,N); + jsonStringExpandAndAppend(p,zIn,N); }else{ memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; @@ -310,7 +556,7 @@ static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ assert( N>0 ); if( N+p->nUsed >= p->nAlloc ){ - jsonAppendExpand(p,zIn,N); + jsonStringExpandAndAppend(p,zIn,N); }else{ memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; @@ -322,7 +568,7 @@ static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ */ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ va_list ap; - if( (p->nUsed + N >= p->nAlloc) && jsonGrow(p, N) ) return; + if( (p->nUsed + N >= p->nAlloc) && jsonStringGrow(p, N) ) return; va_start(ap, zFormat); sqlite3_vsnprintf(N, p->zBuf+p->nUsed, zFormat, ap); va_end(ap); @@ -332,7 +578,7 @@ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ /* Append a single character */ static SQLITE_NOINLINE void jsonAppendCharExpand(JsonString *p, char c){ - if( jsonGrow(p,1) ) return; + if( jsonStringGrow(p,1) ) return; p->zBuf[p->nUsed++] = c; } static void jsonAppendChar(JsonString *p, char c){ @@ -343,24 +589,17 @@ static void jsonAppendChar(JsonString *p, char c){ } } -/* Try to force the string to be a zero-terminated RCStr string. +/* Make sure there is a zero terminator on p->zBuf[] ** ** Return true on success. Return false if an OOM prevents this ** from happening. */ -static int jsonForceRCStr(JsonString *p){ +static int jsonStringTerminate(JsonString *p){ jsonAppendChar(p, 0); - if( p->bErr ) return 0; - p->nUsed--; - if( p->bStatic==0 ) return 1; - p->nAlloc = 0; - p->nUsed++; - jsonGrow(p, p->nUsed); p->nUsed--; - return p->bStatic==0; + return p->eErr==0; } - /* Append a comma separator to the output buffer, if the previous ** character is not '[' or '{'. */ @@ -373,21 +612,66 @@ static void jsonAppendSeparator(JsonString *p){ } /* Append the N-byte string in zIn to the end of the JsonString string -** under construction. Enclose the string in "..." and escape -** any double-quotes or backslash characters contained within the +** under construction. Enclose the string in double-quotes ("...") and +** escape any double-quotes or backslash characters contained within the ** string. +** +** This routine is a high-runner. There is a measurable performance +** increase associated with unwinding the jsonIsOk[] loop. */ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ - u32 i; - if( zIn==0 || ((N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0) ) return; + u32 k; + u8 c; + const u8 *z = (const u8*)zIn; + if( z==0 ) return; + if( (N+p->nUsed+2 >= p->nAlloc) && jsonStringGrow(p,N+2)!=0 ) return; p->zBuf[p->nUsed++] = '"'; - for(i=0; izBuf[p->nUsed++] = c; - }else if( c=='"' || c=='\\' ){ + while( 1 /*exit-by-break*/ ){ + k = 0; + /* The following while() is the 4-way unwound equivalent of + ** + ** while( k=N ){ + while( k=N ){ + if( k>0 ){ + memcpy(&p->zBuf[p->nUsed], z, k); + p->nUsed += k; + } + break; + } + if( k>0 ){ + memcpy(&p->zBuf[p->nUsed], z, k); + p->nUsed += k; + z += k; + N -= k; + } + c = z[0]; + if( c=='"' || c=='\\' ){ json_simple_escape: - if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return; + if( (p->nUsed+N+3 > p->nAlloc) && jsonStringGrow(p,N+3)!=0 ) return; p->zBuf[p->nUsed++] = '\\'; p->zBuf[p->nUsed++] = c; }else if( c=='\'' ){ @@ -408,7 +692,7 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ c = aSpecial[c]; goto json_simple_escape; } - if( (p->nUsed+N+7+i > p->nAlloc) && jsonGrow(p,N+7-i)!=0 ) return; + if( (p->nUsed+N+7 > p->nAlloc) && jsonStringGrow(p,N+7)!=0 ) return; p->zBuf[p->nUsed++] = '\\'; p->zBuf[p->nUsed++] = 'u'; p->zBuf[p->nUsed++] = '0'; @@ -416,146 +700,18 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ p->zBuf[p->nUsed++] = "0123456789abcdef"[c>>4]; p->zBuf[p->nUsed++] = "0123456789abcdef"[c&0xf]; } + z++; + N--; } p->zBuf[p->nUsed++] = '"'; assert( p->nUsednAlloc ); } /* -** The zIn[0..N] string is a JSON5 string literal. Append to p a translation -** of the string literal that standard JSON and that omits all JSON5 -** features. -*/ -static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ - u32 i; - jsonAppendChar(p, '"'); - zIn++; - N -= 2; - while( N>0 ){ - for(i=0; i0 ){ - jsonAppendRawNZ(p, zIn, i); - zIn += i; - N -= i; - if( N==0 ) break; - } - if( zIn[0]=='"' ){ - jsonAppendRawNZ(p, "\\\"", 2); - zIn++; - N--; - continue; - } - assert( zIn[0]=='\\' ); - switch( (u8)zIn[1] ){ - case '\'': - jsonAppendChar(p, '\''); - break; - case 'v': - jsonAppendRawNZ(p, "\\u0009", 6); - break; - case 'x': - jsonAppendRawNZ(p, "\\u00", 4); - jsonAppendRawNZ(p, &zIn[2], 2); - zIn += 2; - N -= 2; - break; - case '0': - jsonAppendRawNZ(p, "\\u0000", 6); - break; - case '\r': - if( zIn[2]=='\n' ){ - zIn++; - N--; - } - break; - case '\n': - break; - case 0xe2: - assert( N>=4 ); - assert( 0x80==(u8)zIn[2] ); - assert( 0xa8==(u8)zIn[3] || 0xa9==(u8)zIn[3] ); - zIn += 2; - N -= 2; - break; - default: - jsonAppendRawNZ(p, zIn, 2); - break; - } - zIn += 2; - N -= 2; - } - jsonAppendChar(p, '"'); -} - -/* -** The zIn[0..N] string is a JSON5 integer literal. Append to p a translation -** of the string literal that standard JSON and that omits all JSON5 -** features. -*/ -static void jsonAppendNormalizedInt(JsonString *p, const char *zIn, u32 N){ - if( zIn[0]=='+' ){ - zIn++; - N--; - }else if( zIn[0]=='-' ){ - jsonAppendChar(p, '-'); - zIn++; - N--; - } - if( zIn[0]=='0' && (zIn[1]=='x' || zIn[1]=='X') ){ - sqlite3_int64 i = 0; - int rc = sqlite3DecOrHexToI64(zIn, &i); - if( rc<=1 ){ - jsonPrintf(100,p,"%lld",i); - }else{ - assert( rc==2 ); - jsonAppendRawNZ(p, "9.0e999", 7); - } - return; - } - assert( N>0 ); - jsonAppendRawNZ(p, zIn, N); -} - -/* -** The zIn[0..N] string is a JSON5 real literal. Append to p a translation -** of the string literal that standard JSON and that omits all JSON5 -** features. -*/ -static void jsonAppendNormalizedReal(JsonString *p, const char *zIn, u32 N){ - u32 i; - if( zIn[0]=='+' ){ - zIn++; - N--; - }else if( zIn[0]=='-' ){ - jsonAppendChar(p, '-'); - zIn++; - N--; - } - if( zIn[0]=='.' ){ - jsonAppendChar(p, '0'); - } - for(i=0; i0 ){ - jsonAppendRawNZ(p, zIn, N); - } -} - - - -/* -** Append a function parameter value to the JSON string under -** construction. +** Append an sqlite3_value (such as a function parameter) to the JSON +** string under construction in p. */ -static void jsonAppendValue( +static void jsonAppendSqlValue( JsonString *p, /* Append to this JSON string */ sqlite3_value *pValue /* Value to append */ ){ @@ -585,304 +741,136 @@ static void jsonAppendValue( break; } default: { - if( p->bErr==0 ){ + if( jsonFuncArgMightBeBinary(pValue) ){ + JsonParse px; + memset(&px, 0, sizeof(px)); + px.aBlob = (u8*)sqlite3_value_blob(pValue); + px.nBlob = sqlite3_value_bytes(pValue); + jsonTranslateBlobToText(&px, 0, p); + }else if( p->eErr==0 ){ sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); - p->bErr = 2; - jsonReset(p); + p->eErr = JSTRING_ERR; + jsonStringReset(p); } break; } } } - -/* Make the JSON in p the result of the SQL function. +/* Make the text in p (which is probably a generated JSON text string) +** the result of the SQL function. ** -** The JSON string is reset. +** The JsonString is reset. +** +** If pParse and ctx are both non-NULL, then the SQL string in p is +** loaded into the zJson field of the pParse object as a RCStr and the +** pParse is added to the cache. */ -static void jsonResult(JsonString *p){ - if( p->bErr==0 ){ - if( p->bStatic ){ +static void jsonReturnString( + JsonString *p, /* String to return */ + JsonParse *pParse, /* JSONB source or NULL */ + sqlite3_context *ctx /* Where to cache */ +){ + assert( (pParse!=0)==(ctx!=0) ); + assert( ctx==0 || ctx==p->pCtx ); + if( p->eErr==0 ){ + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(p->pCtx)); + if( flags & JSON_BLOB ){ + jsonReturnStringAsBlob(p); + }else if( p->bStatic ){ sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, SQLITE_TRANSIENT, SQLITE_UTF8); - }else if( jsonForceRCStr(p) ){ - sqlite3RCStrRef(p->zBuf); - sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + }else if( jsonStringTerminate(p) ){ + if( pParse && pParse->bJsonIsRCStr==0 && pParse->nBlobAlloc>0 ){ + int rc; + pParse->zJson = sqlite3RCStrRef(p->zBuf); + pParse->nJson = p->nUsed; + pParse->bJsonIsRCStr = 1; + rc = jsonCacheInsert(ctx, pParse); + if( rc==SQLITE_NOMEM ){ + sqlite3_result_error_nomem(ctx); + jsonStringReset(p); + return; + } + } + sqlite3_result_text64(p->pCtx, sqlite3RCStrRef(p->zBuf), p->nUsed, sqlite3RCStrUnref, SQLITE_UTF8); + }else{ + sqlite3_result_error_nomem(p->pCtx); } - } - if( p->bErr==1 ){ + }else if( p->eErr & JSTRING_OOM ){ sqlite3_result_error_nomem(p->pCtx); + }else if( p->eErr & JSTRING_MALFORMED ){ + sqlite3_result_error(p->pCtx, "malformed JSON", -1); } - jsonReset(p); + jsonStringReset(p); } /************************************************************************** -** Utility routines for dealing with JsonNode and JsonParse objects +** Utility routines for dealing with JsonParse objects **************************************************************************/ -/* -** Return the number of consecutive JsonNode slots need to represent -** the parsed JSON at pNode. The minimum answer is 1. For ARRAY and -** OBJECT types, the number might be larger. -** -** Appended elements are not counted. The value returned is the number -** by which the JsonNode counter should increment in order to go to the -** next peer value. -*/ -static u32 jsonNodeSize(JsonNode *pNode){ - return pNode->eType>=JSON_ARRAY ? pNode->n+1 : 1; -} - /* ** Reclaim all memory allocated by a JsonParse object. But do not ** delete the JsonParse object itself. */ static void jsonParseReset(JsonParse *pParse){ - while( pParse->pClup ){ - JsonCleanup *pTask = pParse->pClup; - pParse->pClup = pTask->pJCNext; - pTask->xOp(pTask->pArg); - sqlite3_free(pTask); - } assert( pParse->nJPRef<=1 ); - if( pParse->aNode ){ - sqlite3_free(pParse->aNode); - pParse->aNode = 0; - } - pParse->nNode = 0; - pParse->nAlloc = 0; - if( pParse->aUp ){ - sqlite3_free(pParse->aUp); - pParse->aUp = 0; - } if( pParse->bJsonIsRCStr ){ sqlite3RCStrUnref(pParse->zJson); pParse->zJson = 0; + pParse->nJson = 0; pParse->bJsonIsRCStr = 0; } - if( pParse->zAlt ){ - sqlite3RCStrUnref(pParse->zAlt); - pParse->zAlt = 0; + if( pParse->nBlobAlloc ){ + sqlite3DbFree(pParse->db, pParse->aBlob); + pParse->aBlob = 0; + pParse->nBlob = 0; + pParse->nBlobAlloc = 0; } } /* -** Free a JsonParse object that was obtained from sqlite3_malloc(). -** -** Note that destroying JsonParse might call sqlite3RCStrUnref() to -** destroy the zJson value. The RCStr object might recursively invoke -** JsonParse to destroy this pParse object again. Take care to ensure -** that this recursive destructor sequence terminates harmlessly. +** Decrement the reference count on the JsonParse object. When the +** count reaches zero, free the object. */ static void jsonParseFree(JsonParse *pParse){ - if( pParse->nJPRef>1 ){ - pParse->nJPRef--; - }else{ - jsonParseReset(pParse); - sqlite3_free(pParse); + if( pParse ){ + if( pParse->nJPRef>1 ){ + pParse->nJPRef--; + }else{ + jsonParseReset(pParse); + sqlite3DbFree(pParse->db, pParse); + } } } +/************************************************************************** +** Utility routines for the JSON text parser +**************************************************************************/ + /* -** Add a cleanup task to the JsonParse object. -** -** If an OOM occurs, the cleanup operation happens immediately -** and this function returns SQLITE_NOMEM. +** Translate a single byte of Hex into an integer. +** This routine only gives a correct answer if h really is a valid hexadecimal +** character: 0..9a..fA..F. But unlike sqlite3HexToInt(), it does not +** assert() if the digit is not hex. */ -static int jsonParseAddCleanup( - JsonParse *pParse, /* Add the cleanup task to this parser */ - void(*xOp)(void*), /* The cleanup task */ - void *pArg /* Argument to the cleanup */ -){ - JsonCleanup *pTask = sqlite3_malloc64( sizeof(*pTask) ); - if( pTask==0 ){ - pParse->oom = 1; - xOp(pArg); - return SQLITE_ERROR; - } - pTask->pJCNext = pParse->pClup; - pParse->pClup = pTask; - pTask->xOp = xOp; - pTask->pArg = pArg; - return SQLITE_OK; +static u8 jsonHexToInt(int h){ +#ifdef SQLITE_ASCII + h += 9*(1&(h>>6)); +#endif +#ifdef SQLITE_EBCDIC + h += 9*(1&~(h>>4)); +#endif + return (u8)(h & 0xf); } /* -** Convert the JsonNode pNode into a pure JSON string and -** append to pOut. Subsubstructure is also included. Return -** the number of JsonNode objects that are encoded. -*/ -static void jsonRenderNode( - JsonParse *pParse, /* the complete parse of the JSON */ - JsonNode *pNode, /* The node to render */ - JsonString *pOut /* Write JSON here */ -){ - assert( pNode!=0 ); - while( (pNode->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ - u32 idx = (u32)(pNode - pParse->aNode); - u32 i = pParse->iSubst; - while( 1 /*exit-by-break*/ ){ - assert( inNode ); - assert( pParse->aNode[i].eType==JSON_SUBST ); - assert( pParse->aNode[i].eU==4 ); - assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ - pNode = &pParse->aNode[i+1]; - break; - } - i = pParse->aNode[i].u.iPrev; - } - } - switch( pNode->eType ){ - default: { - assert( pNode->eType==JSON_NULL ); - jsonAppendRawNZ(pOut, "null", 4); - break; - } - case JSON_TRUE: { - jsonAppendRawNZ(pOut, "true", 4); - break; - } - case JSON_FALSE: { - jsonAppendRawNZ(pOut, "false", 5); - break; - } - case JSON_STRING: { - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_RAW ){ - if( pNode->jnFlags & JNODE_LABEL ){ - jsonAppendChar(pOut, '"'); - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); - jsonAppendChar(pOut, '"'); - }else{ - jsonAppendString(pOut, pNode->u.zJContent, pNode->n); - } - }else if( pNode->jnFlags & JNODE_JSON5 ){ - jsonAppendNormalizedString(pOut, pNode->u.zJContent, pNode->n); - }else{ - assert( pNode->n>0 ); - jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); - } - break; - } - case JSON_REAL: { - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_JSON5 ){ - jsonAppendNormalizedReal(pOut, pNode->u.zJContent, pNode->n); - }else{ - assert( pNode->n>0 ); - jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); - } - break; - } - case JSON_INT: { - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_JSON5 ){ - jsonAppendNormalizedInt(pOut, pNode->u.zJContent, pNode->n); - }else{ - assert( pNode->n>0 ); - jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); - } - break; - } - case JSON_ARRAY: { - u32 j = 1; - jsonAppendChar(pOut, '['); - for(;;){ - while( j<=pNode->n ){ - if( (pNode[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ - jsonAppendSeparator(pOut); - jsonRenderNode(pParse, &pNode[j], pOut); - } - j += jsonNodeSize(&pNode[j]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pNode->eU==2 ); - pNode = &pParse->aNode[pNode->u.iAppend]; - j = 1; - } - jsonAppendChar(pOut, ']'); - break; - } - case JSON_OBJECT: { - u32 j = 1; - jsonAppendChar(pOut, '{'); - for(;;){ - while( j<=pNode->n ){ - if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ - jsonAppendSeparator(pOut); - jsonRenderNode(pParse, &pNode[j], pOut); - jsonAppendChar(pOut, ':'); - jsonRenderNode(pParse, &pNode[j+1], pOut); - } - j += 1 + jsonNodeSize(&pNode[j+1]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pNode->eU==2 ); - pNode = &pParse->aNode[pNode->u.iAppend]; - j = 1; - } - jsonAppendChar(pOut, '}'); - break; - } - } -} - -/* -** Return a JsonNode and all its descendants as a JSON string. -*/ -static void jsonReturnJson( - JsonParse *pParse, /* The complete JSON */ - JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx, /* Return value for this function */ - int bGenerateAlt, /* Also store the rendered text in zAlt */ - int omitSubtype /* Do not call sqlite3_result_subtype() */ -){ - JsonString s; - if( pParse->oom ){ - sqlite3_result_error_nomem(pCtx); - return; - } - if( pParse->nErr==0 ){ - jsonInit(&s, pCtx); - jsonRenderNode(pParse, pNode, &s); - if( bGenerateAlt && pParse->zAlt==0 && jsonForceRCStr(&s) ){ - pParse->zAlt = sqlite3RCStrRef(s.zBuf); - pParse->nAlt = s.nUsed; - } - jsonResult(&s); - if( !omitSubtype ) sqlite3_result_subtype(pCtx, JSON_SUBTYPE); - } -} - -/* -** Translate a single byte of Hex into an integer. -** This routine only works if h really is a valid hexadecimal -** character: 0..9a..fA..F -*/ -static u8 jsonHexToInt(int h){ - assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') ); -#ifdef SQLITE_EBCDIC - h += 9*(1&~(h>>4)); -#else - h += 9*(1&(h>>6)); -#endif - return (u8)(h & 0xf); -} - -/* -** Convert a 4-byte hex string into an integer +** Convert a 4-byte hex string into an integer */ static u32 jsonHexToInt4(const char *z){ u32 v; - assert( sqlite3Isxdigit(z[0]) ); - assert( sqlite3Isxdigit(z[1]) ); - assert( sqlite3Isxdigit(z[2]) ); - assert( sqlite3Isxdigit(z[3]) ); v = (jsonHexToInt(z[0])<<12) + (jsonHexToInt(z[1])<<8) + (jsonHexToInt(z[2])<<4) @@ -890,282 +878,6 @@ static u32 jsonHexToInt4(const char *z){ return v; } -/* -** Make the JsonNode the return value of the function. -*/ -static void jsonReturn( - JsonParse *pParse, /* Complete JSON parse tree */ - JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx, /* Return value for this function */ - int omitSubtype /* Do not call sqlite3_result_subtype() */ -){ - switch( pNode->eType ){ - default: { - assert( pNode->eType==JSON_NULL ); - sqlite3_result_null(pCtx); - break; - } - case JSON_TRUE: { - sqlite3_result_int(pCtx, 1); - break; - } - case JSON_FALSE: { - sqlite3_result_int(pCtx, 0); - break; - } - case JSON_INT: { - sqlite3_int64 i = 0; - int rc; - int bNeg = 0; - const char *z; - - assert( pNode->eU==1 ); - z = pNode->u.zJContent; - if( z[0]=='-' ){ z++; bNeg = 1; } - else if( z[0]=='+' ){ z++; } - rc = sqlite3DecOrHexToI64(z, &i); - if( rc<=1 ){ - sqlite3_result_int64(pCtx, bNeg ? -i : i); - }else if( rc==3 && bNeg ){ - sqlite3_result_int64(pCtx, SMALLEST_INT64); - }else{ - goto to_double; - } - break; - } - case JSON_REAL: { - double r; - const char *z; - assert( pNode->eU==1 ); - to_double: - z = pNode->u.zJContent; - sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); - sqlite3_result_double(pCtx, r); - break; - } - case JSON_STRING: { - if( pNode->jnFlags & JNODE_RAW ){ - assert( pNode->eU==1 ); - sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n, - SQLITE_TRANSIENT); - }else if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ - /* JSON formatted without any backslash-escapes */ - assert( pNode->eU==1 ); - sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2, - SQLITE_TRANSIENT); - }else{ - /* Translate JSON formatted string into raw text */ - u32 i; - u32 n = pNode->n; - const char *z; - char *zOut; - u32 j; - u32 nOut = n; - assert( pNode->eU==1 ); - z = pNode->u.zJContent; - zOut = sqlite3_malloc( nOut+1 ); - if( zOut==0 ){ - sqlite3_result_error_nomem(pCtx); - break; - } - for(i=1, j=0; i>6)); - zOut[j++] = 0x80 | (v&0x3f); - }else{ - u32 vlo; - if( (v&0xfc00)==0xd800 - && i>18); - zOut[j++] = 0x80 | ((v>>12)&0x3f); - zOut[j++] = 0x80 | ((v>>6)&0x3f); - zOut[j++] = 0x80 | (v&0x3f); - }else{ - zOut[j++] = 0xe0 | (v>>12); - zOut[j++] = 0x80 | ((v>>6)&0x3f); - zOut[j++] = 0x80 | (v&0x3f); - } - } - continue; - }else if( c=='b' ){ - c = '\b'; - }else if( c=='f' ){ - c = '\f'; - }else if( c=='n' ){ - c = '\n'; - }else if( c=='r' ){ - c = '\r'; - }else if( c=='t' ){ - c = '\t'; - }else if( c=='v' ){ - c = '\v'; - }else if( c=='\'' || c=='"' || c=='/' || c=='\\' ){ - /* pass through unchanged */ - }else if( c=='0' ){ - c = 0; - }else if( c=='x' ){ - c = (jsonHexToInt(z[i+1])<<4) | jsonHexToInt(z[i+2]); - i += 2; - }else if( c=='\r' && z[i+1]=='\n' ){ - i++; - continue; - }else if( 0xe2==(u8)c ){ - assert( 0x80==(u8)z[i+1] ); - assert( 0xa8==(u8)z[i+2] || 0xa9==(u8)z[i+2] ); - i += 2; - continue; - }else{ - continue; - } - } /* end if( c=='\\' ) */ - zOut[j++] = c; - } /* end for() */ - zOut[j] = 0; - sqlite3_result_text(pCtx, zOut, j, sqlite3_free); - } - break; - } - case JSON_ARRAY: - case JSON_OBJECT: { - jsonReturnJson(pParse, pNode, pCtx, 0, omitSubtype); - break; - } - } -} - -/* Forward reference */ -static int jsonParseAddNode(JsonParse*,u32,u32,const char*); - -/* -** A macro to hint to the compiler that a function should not be -** inlined. -*/ -#if defined(__GNUC__) -# define JSON_NOINLINE __attribute__((noinline)) -#elif defined(_MSC_VER) && _MSC_VER>=1310 -# define JSON_NOINLINE __declspec(noinline) -#else -# define JSON_NOINLINE -#endif - - -/* -** Add a single node to pParse->aNode after first expanding the -** size of the aNode array. Return the index of the new node. -** -** If an OOM error occurs, set pParse->oom and return -1. -*/ -static JSON_NOINLINE int jsonParseAddNodeExpand( - JsonParse *pParse, /* Append the node to this object */ - u32 eType, /* Node type */ - u32 n, /* Content size or sub-node count */ - const char *zContent /* Content */ -){ - u32 nNew; - JsonNode *pNew; - assert( pParse->nNode>=pParse->nAlloc ); - if( pParse->oom ) return -1; - nNew = pParse->nAlloc*2 + 10; - pNew = sqlite3_realloc64(pParse->aNode, sizeof(JsonNode)*nNew); - if( pNew==0 ){ - pParse->oom = 1; - return -1; - } - pParse->nAlloc = sqlite3_msize(pNew)/sizeof(JsonNode); - pParse->aNode = pNew; - assert( pParse->nNodenAlloc ); - return jsonParseAddNode(pParse, eType, n, zContent); -} - -/* -** Create a new JsonNode instance based on the arguments and append that -** instance to the JsonParse. Return the index in pParse->aNode[] of the -** new node, or -1 if a memory allocation fails. -*/ -static int jsonParseAddNode( - JsonParse *pParse, /* Append the node to this object */ - u32 eType, /* Node type */ - u32 n, /* Content size or sub-node count */ - const char *zContent /* Content */ -){ - JsonNode *p; - assert( pParse->aNode!=0 || pParse->nNode>=pParse->nAlloc ); - if( pParse->nNode>=pParse->nAlloc ){ - return jsonParseAddNodeExpand(pParse, eType, n, zContent); - } - assert( pParse->aNode!=0 ); - p = &pParse->aNode[pParse->nNode]; - assert( p!=0 ); - p->eType = (u8)(eType & 0xff); - p->jnFlags = (u8)(eType >> 8); - VVA( p->eU = zContent ? 1 : 0 ); - p->n = n; - p->u.zJContent = zContent; - return pParse->nNode++; -} - -/* -** Add an array of new nodes to the current pParse->aNode array. -** Return the index of the first node added. -** -** If an OOM error occurs, set pParse->oom. -*/ -static void jsonParseAddNodeArray( - JsonParse *pParse, /* Append the node to this object */ - JsonNode *aNode, /* Array of nodes to add */ - u32 nNode /* Number of elements in aNew */ -){ - assert( aNode!=0 ); - assert( nNode>=1 ); - if( pParse->nNode + nNode > pParse->nAlloc ){ - u32 nNew = pParse->nNode + nNode; - JsonNode *aNew = sqlite3_realloc64(pParse->aNode, nNew*sizeof(JsonNode)); - if( aNew==0 ){ - pParse->oom = 1; - return; - } - pParse->nAlloc = sqlite3_msize(aNew)/sizeof(JsonNode); - pParse->aNode = aNew; - } - memcpy(&pParse->aNode[pParse->nNode], aNode, nNode*sizeof(JsonNode)); - pParse->nNode += nNode; -} - -/* -** Add a new JSON_SUBST node. The node immediately following -** this new node will be the substitute content for iNode. -*/ -static int jsonParseAddSubstNode( - JsonParse *pParse, /* Add the JSON_SUBST here */ - u32 iNode /* References this node */ -){ - int idx = jsonParseAddNode(pParse, JSON_SUBST, iNode, 0); - if( pParse->oom ) return -1; - pParse->aNode[iNode].jnFlags |= JNODE_REPLACE; - pParse->aNode[idx].eU = 4; - pParse->aNode[idx].u.iPrev = pParse->iSubst; - pParse->iSubst = idx; - pParse->hasMod = 1; - pParse->useMod = 1; - return idx; -} - /* ** Return true if z[] begins with 2 (or more) hexadecimal digits */ @@ -1319,97 +1031,534 @@ static const struct NanInfName { char *zMatch; char *zRepl; } aNanInfName[] = { - { 'i', 'I', 3, JSON_REAL, 7, "inf", "9.0e999" }, - { 'i', 'I', 8, JSON_REAL, 7, "infinity", "9.0e999" }, - { 'n', 'N', 3, JSON_NULL, 4, "NaN", "null" }, - { 'q', 'Q', 4, JSON_NULL, 4, "QNaN", "null" }, - { 's', 'S', 4, JSON_NULL, 4, "SNaN", "null" }, + { 'i', 'I', 3, JSONB_FLOAT, 7, "inf", "9.0e999" }, + { 'i', 'I', 8, JSONB_FLOAT, 7, "infinity", "9.0e999" }, + { 'n', 'N', 3, JSONB_NULL, 4, "NaN", "null" }, + { 'q', 'Q', 4, JSONB_NULL, 4, "QNaN", "null" }, + { 's', 'S', 4, JSONB_NULL, 4, "SNaN", "null" }, }; + +/* +** Report the wrong number of arguments for json_insert(), json_replace() +** or json_set(). +*/ +static void jsonWrongNumArgs( + sqlite3_context *pCtx, + const char *zFuncName +){ + char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", + zFuncName); + sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_free(zMsg); +} + +/**************************************************************************** +** Utility routines for dealing with the binary BLOB representation of JSON +****************************************************************************/ + /* -** Parse a single JSON value which begins at pParse->zJson[i]. Return the -** index of the first character past the end of the value parsed. +** Expand pParse->aBlob so that it holds at least N bytes. ** -** Special return values: +** Return the number of errors. +*/ +static int jsonBlobExpand(JsonParse *pParse, u32 N){ + u8 *aNew; + u32 t; + assert( N>pParse->nBlobAlloc ); + if( pParse->nBlobAlloc==0 ){ + t = 100; + }else{ + t = pParse->nBlobAlloc*2; + } + if( tdb, pParse->aBlob, t); + if( aNew==0 ){ pParse->oom = 1; return 1; } + pParse->aBlob = aNew; + pParse->nBlobAlloc = t; + return 0; +} + +/* +** If pParse->aBlob is not previously editable (because it is taken +** from sqlite3_value_blob(), as indicated by the fact that +** pParse->nBlobAlloc==0 and pParse->nBlob>0) then make it editable +** by making a copy into space obtained from malloc. ** -** 0 End of input -** -1 Syntax error -** -2 '}' seen -** -3 ']' seen -** -4 ',' seen -** -5 ':' seen +** Return true on success. Return false on OOM. */ -static int jsonParseValue(JsonParse *pParse, u32 i){ - char c; - u32 j; - int iThis; - int x; - JsonNode *pNode; - const char *z = pParse->zJson; -json_parse_restart: - switch( (u8)z[i] ){ - case '{': { - /* Parse object */ - iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); - if( iThis<0 ) return -1; - if( ++pParse->iDepth > JSON_MAX_DEPTH ){ - pParse->iErr = i; - return -1; +static int jsonBlobMakeEditable(JsonParse *pParse, u32 nExtra){ + u8 *aOld; + u32 nSize; + assert( !pParse->bReadOnly ); + if( pParse->oom ) return 0; + if( pParse->nBlobAlloc>0 ) return 1; + aOld = pParse->aBlob; + nSize = pParse->nBlob + nExtra; + pParse->aBlob = 0; + if( jsonBlobExpand(pParse, nSize) ){ + return 0; + } + assert( pParse->nBlobAlloc >= pParse->nBlob + nExtra ); + memcpy(pParse->aBlob, aOld, pParse->nBlob); + return 1; +} + +/* Expand pParse->aBlob and append one bytes. +*/ +static SQLITE_NOINLINE void jsonBlobExpandAndAppendOneByte( + JsonParse *pParse, + u8 c +){ + jsonBlobExpand(pParse, pParse->nBlob+1); + if( pParse->oom==0 ){ + assert( pParse->nBlob+1<=pParse->nBlobAlloc ); + pParse->aBlob[pParse->nBlob++] = c; + } +} + +/* Append a single character. +*/ +static void jsonBlobAppendOneByte(JsonParse *pParse, u8 c){ + if( pParse->nBlob >= pParse->nBlobAlloc ){ + jsonBlobExpandAndAppendOneByte(pParse, c); + }else{ + pParse->aBlob[pParse->nBlob++] = c; + } +} + +/* Slow version of jsonBlobAppendNode() that first resizes the +** pParse->aBlob structure. +*/ +static void jsonBlobAppendNode(JsonParse*,u8,u32,const void*); +static SQLITE_NOINLINE void jsonBlobExpandAndAppendNode( + JsonParse *pParse, + u8 eType, + u32 szPayload, + const void *aPayload +){ + if( jsonBlobExpand(pParse, pParse->nBlob+szPayload+9) ) return; + jsonBlobAppendNode(pParse, eType, szPayload, aPayload); +} + + +/* Append an node type byte together with the payload size and +** possibly also the payload. +** +** If aPayload is not NULL, then it is a pointer to the payload which +** is also appended. If aPayload is NULL, the pParse->aBlob[] array +** is resized (if necessary) so that it is big enough to hold the +** payload, but the payload is not appended and pParse->nBlob is left +** pointing to where the first byte of payload will eventually be. +*/ +static void jsonBlobAppendNode( + JsonParse *pParse, /* The JsonParse object under construction */ + u8 eType, /* Node type. One of JSONB_* */ + u32 szPayload, /* Number of bytes of payload */ + const void *aPayload /* The payload. Might be NULL */ +){ + u8 *a; + if( pParse->nBlob+szPayload+9 > pParse->nBlobAlloc ){ + jsonBlobExpandAndAppendNode(pParse,eType,szPayload,aPayload); + return; + } + assert( pParse->aBlob!=0 ); + a = &pParse->aBlob[pParse->nBlob]; + if( szPayload<=11 ){ + a[0] = eType | (szPayload<<4); + pParse->nBlob += 1; + }else if( szPayload<=0xff ){ + a[0] = eType | 0xc0; + a[1] = szPayload & 0xff; + pParse->nBlob += 2; + }else if( szPayload<=0xffff ){ + a[0] = eType | 0xd0; + a[1] = (szPayload >> 8) & 0xff; + a[2] = szPayload & 0xff; + pParse->nBlob += 3; + }else{ + a[0] = eType | 0xe0; + a[1] = (szPayload >> 24) & 0xff; + a[2] = (szPayload >> 16) & 0xff; + a[3] = (szPayload >> 8) & 0xff; + a[4] = szPayload & 0xff; + pParse->nBlob += 5; + } + if( aPayload ){ + pParse->nBlob += szPayload; + memcpy(&pParse->aBlob[pParse->nBlob-szPayload], aPayload, szPayload); + } +} + +/* Change the payload size for the node at index i to be szPayload. +*/ +static int jsonBlobChangePayloadSize( + JsonParse *pParse, + u32 i, + u32 szPayload +){ + u8 *a; + u8 szType; + u8 nExtra; + u8 nNeeded; + int delta; + if( pParse->oom ) return 0; + a = &pParse->aBlob[i]; + szType = a[0]>>4; + if( szType<=11 ){ + nExtra = 0; + }else if( szType==12 ){ + nExtra = 1; + }else if( szType==13 ){ + nExtra = 2; + }else{ + nExtra = 4; + } + if( szPayload<=11 ){ + nNeeded = 0; + }else if( szPayload<=0xff ){ + nNeeded = 1; + }else if( szPayload<=0xffff ){ + nNeeded = 2; + }else{ + nNeeded = 4; + } + delta = nNeeded - nExtra; + if( delta ){ + u32 newSize = pParse->nBlob + delta; + if( delta>0 ){ + if( newSize>pParse->nBlobAlloc && jsonBlobExpand(pParse, newSize) ){ + return 0; /* OOM error. Error state recorded in pParse->oom. */ + } + a = &pParse->aBlob[i]; + memmove(&a[1+delta], &a[1], pParse->nBlob - (i+1)); + }else{ + memmove(&a[1], &a[1-delta], pParse->nBlob - (i+1-delta)); } - for(j=i+1;;j++){ - u32 nNode = pParse->nNode; - x = jsonParseValue(pParse, j); - if( x<=0 ){ - if( x==(-2) ){ - j = pParse->iErr; - if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1; - break; + pParse->nBlob = newSize; + } + if( nNeeded==0 ){ + a[0] = (a[0] & 0x0f) | (szPayload<<4); + }else if( nNeeded==1 ){ + a[0] = (a[0] & 0x0f) | 0xc0; + a[1] = szPayload & 0xff; + }else if( nNeeded==2 ){ + a[0] = (a[0] & 0x0f) | 0xd0; + a[1] = (szPayload >> 8) & 0xff; + a[2] = szPayload & 0xff; + }else{ + a[0] = (a[0] & 0x0f) | 0xe0; + a[1] = (szPayload >> 24) & 0xff; + a[2] = (szPayload >> 16) & 0xff; + a[3] = (szPayload >> 8) & 0xff; + a[4] = szPayload & 0xff; + } + return delta; +} + +/* +** If z[0] is 'u' and is followed by exactly 4 hexadecimal character, +** then set *pOp to JSONB_TEXTJ and return true. If not, do not make +** any changes to *pOp and return false. +*/ +static int jsonIs4HexB(const char *z, int *pOp){ + if( z[0]!='u' ) return 0; + if( !jsonIs4Hex(&z[1]) ) return 0; + *pOp = JSONB_TEXTJ; + return 1; +} + +/* +** Check a single element of the JSONB in pParse for validity. +** +** The element to be checked starts at offset i and must end at on the +** last byte before iEnd. +** +** Return 0 if everything is correct. Return the 1-based byte offset of the +** error if a problem is detected. (In other words, if the error is at offset +** 0, return 1). +*/ +static u32 jsonbValidityCheck( + const JsonParse *pParse, /* Input JSONB. Only aBlob and nBlob are used */ + u32 i, /* Start of element as pParse->aBlob[i] */ + u32 iEnd, /* One more than the last byte of the element */ + u32 iDepth /* Current nesting depth */ +){ + u32 n, sz, j, k; + const u8 *z; + u8 x; + if( iDepth>JSON_MAX_DEPTH ) return i+1; + sz = 0; + n = jsonbPayloadSize(pParse, i, &sz); + if( NEVER(n==0) ) return i+1; /* Checked by caller */ + if( NEVER(i+n+sz!=iEnd) ) return i+1; /* Checked by caller */ + z = pParse->aBlob; + x = z[i] & 0x0f; + switch( x ){ + case JSONB_NULL: + case JSONB_TRUE: + case JSONB_FALSE: { + return n+sz==1 ? 0 : i+1; + } + case JSONB_INT: { + if( sz<1 ) return i+1; + j = i+n; + if( z[j]=='-' ){ + j++; + if( sz<2 ) return i+1; + } + k = i+n+sz; + while( jhasNonstd = 1; - x = k; + } + return 0; + } + case JSONB_INT5: { + if( sz<3 ) return i+1; + j = i+n; + if( z[j]=='-' ){ + if( sz<4 ) return i+1; + j++; + } + if( z[j]!='0' ) return i+1; + if( z[j+1]!='x' && z[j+1]!='X' ) return j+2; + j += 2; + k = i+n+sz; + while( jiErr = j; - return -1; + return j+1; } } - if( pParse->oom ) return -1; - pNode = &pParse->aNode[nNode]; - if( pNode->eType!=JSON_STRING ){ - pParse->iErr = j; - return -1; + return 0; + } + case JSONB_FLOAT: + case JSONB_FLOAT5: { + u8 seen = 0; /* 0: initial. 1: '.' seen 2: 'e' seen */ + if( sz<2 ) return i+1; + j = i+n; + k = j+sz; + if( z[j]=='-' ){ + j++; + if( sz<3 ) return i+1; } - pNode->jnFlags |= JNODE_LABEL; - j = x; - if( z[j]==':' ){ + if( z[j]=='.' ){ + if( x==JSONB_FLOAT ) return j+1; + if( !sqlite3Isdigit(z[j+1]) ) return j+1; + j += 2; + seen = 1; + }else if( z[j]=='0' && x==JSONB_FLOAT ){ + if( j+3>k ) return j+1; + if( z[j+1]!='.' && z[j+1]!='e' && z[j+1]!='E' ) return j+1; j++; - }else{ - if( fast_isspace(z[j]) ){ - do{ j++; }while( fast_isspace(z[j]) ); - if( z[j]==':' ){ - j++; - goto parse_object_value; + } + for(; j0 ) return j+1; + if( x==JSONB_FLOAT && (j==k-1 || !sqlite3Isdigit(z[j+1])) ){ + return j+1; } + seen = 1; + continue; } - x = jsonParseValue(pParse, j); - if( x!=(-5) ){ + if( z[j]=='e' || z[j]=='E' ){ + if( seen==2 ) return j+1; + if( j==k-1 ) return j+1; + if( z[j+1]=='+' || z[j+1]=='-' ){ + j++; + if( j==k-1 ) return j+1; + } + seen = 2; + continue; + } + return j+1; + } + if( seen==0 ) return i+1; + return 0; + } + case JSONB_TEXT: { + j = i+n; + k = j+sz; + while( j=k ){ + return j+1; + }else if( strchr("\"\\/bfnrt",z[j+1])!=0 ){ + j++; + }else if( z[j+1]=='u' ){ + if( j+5>=k ) return j+1; + if( !jsonIs4Hex((const char*)&z[j+2]) ) return j+1; + j++; + }else if( x!=JSONB_TEXT5 ){ + return j+1; + }else{ + u32 c = 0; + u32 szC = jsonUnescapeOneChar((const char*)&z[j], k-j, &c); + if( c==JSON_INVALID_CHAR ) return j+1; + j += szC - 1; + } + } + j++; + } + return 0; + } + case JSONB_TEXTRAW: { + return 0; + } + case JSONB_ARRAY: { + u32 sub; + j = i+n; + k = j+sz; + while( jk ) return j+1; + sub = jsonbValidityCheck(pParse, j, j+n+sz, iDepth+1); + if( sub ) return sub; + j += n + sz; + } + assert( j==k ); + return 0; + } + case JSONB_OBJECT: { + u32 cnt = 0; + u32 sub; + j = i+n; + k = j+sz; + while( jk ) return j+1; + if( (cnt & 1)==0 ){ + x = z[j] & 0x0f; + if( xJSONB_TEXTRAW ) return j+1; + } + sub = jsonbValidityCheck(pParse, j, j+n+sz, iDepth+1); + if( sub ) return sub; + cnt++; + j += n + sz; + } + assert( j==k ); + if( (cnt & 1)!=0 ) return j+1; + return 0; + } + default: { + return i+1; + } + } +} + +/* +** Translate a single element of JSON text at pParse->zJson[i] into +** its equivalent binary JSONB representation. Append the translation into +** pParse->aBlob[] beginning at pParse->nBlob. The size of +** pParse->aBlob[] is increased as necessary. +** +** Return the index of the first character past the end of the element parsed, +** or one of the following special result codes: +** +** 0 End of input +** -1 Syntax error or OOM +** -2 '}' seen \ +** -3 ']' seen \___ For these returns, pParse->iErr is set to +** -4 ',' seen / the index in zJson[] of the seen character +** -5 ':' seen / +*/ +static int jsonTranslateTextToBlob(JsonParse *pParse, u32 i){ + char c; + u32 j; + u32 iThis, iStart; + int x; + u8 t; + const char *z = pParse->zJson; +json_parse_restart: + switch( (u8)z[i] ){ + case '{': { + /* Parse object */ + iThis = pParse->nBlob; + jsonBlobAppendNode(pParse, JSONB_OBJECT, pParse->nJson-i, 0); + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + pParse->iErr = i; + return -1; + } + iStart = pParse->nBlob; + for(j=i+1;;j++){ + u32 iBlob = pParse->nBlob; + x = jsonTranslateTextToBlob(pParse, j); + if( x<=0 ){ + int op; + if( x==(-2) ){ + j = pParse->iErr; + if( pParse->nBlob!=(u32)iStart ) pParse->hasNonstd = 1; + break; + } + j += json5Whitespace(&z[j]); + op = JSONB_TEXT; + if( sqlite3JsonId1(z[j]) + || (z[j]=='\\' && jsonIs4HexB(&z[j+1], &op)) + ){ + int k = j+1; + while( (sqlite3JsonId2(z[k]) && json5Whitespace(&z[k])==0) + || (z[k]=='\\' && jsonIs4HexB(&z[k+1], &op)) + ){ + k++; + } + assert( iBlob==pParse->nBlob ); + jsonBlobAppendNode(pParse, op, k-j, &z[j]); + pParse->hasNonstd = 1; + x = k; + }else{ + if( x!=-1 ) pParse->iErr = j; + return -1; + } + } + if( pParse->oom ) return -1; + t = pParse->aBlob[iBlob] & 0x0f; + if( tJSONB_TEXTRAW ){ + pParse->iErr = j; + return -1; + } + j = x; + if( z[j]==':' ){ + j++; + }else{ + if( jsonIsspace(z[j]) ){ + /* strspn() is not helpful here */ + do{ j++; }while( jsonIsspace(z[j]) ); + if( z[j]==':' ){ + j++; + goto parse_object_value; + } + } + x = jsonTranslateTextToBlob(pParse, j); + if( x!=(-5) ){ if( x!=(-1) ) pParse->iErr = j; return -1; } j = pParse->iErr+1; } parse_object_value: - x = jsonParseValue(pParse, j); + x = jsonTranslateTextToBlob(pParse, j); if( x<=0 ){ if( x!=(-1) ) pParse->iErr = j; return -1; @@ -1420,15 +1569,15 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ }else if( z[j]=='}' ){ break; }else{ - if( fast_isspace(z[j]) ){ - do{ j++; }while( fast_isspace(z[j]) ); + if( jsonIsspace(z[j]) ){ + j += 1 + (u32)strspn(&z[j+1], jsonSpaces); if( z[j]==',' ){ continue; }else if( z[j]=='}' ){ break; } } - x = jsonParseValue(pParse, j); + x = jsonTranslateTextToBlob(pParse, j); if( x==(-4) ){ j = pParse->iErr; continue; @@ -1441,25 +1590,26 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ pParse->iErr = j; return -1; } - pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + jsonBlobChangePayloadSize(pParse, iThis, pParse->nBlob - iStart); pParse->iDepth--; return j+1; } case '[': { /* Parse array */ - iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); - if( iThis<0 ) return -1; + iThis = pParse->nBlob; + jsonBlobAppendNode(pParse, JSONB_ARRAY, pParse->nJson - i, 0); + iStart = pParse->nBlob; + if( pParse->oom ) return -1; if( ++pParse->iDepth > JSON_MAX_DEPTH ){ pParse->iErr = i; return -1; } - memset(&pParse->aNode[iThis].u, 0, sizeof(pParse->aNode[iThis].u)); for(j=i+1;;j++){ - x = jsonParseValue(pParse, j); + x = jsonTranslateTextToBlob(pParse, j); if( x<=0 ){ if( x==(-3) ){ j = pParse->iErr; - if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1; + if( pParse->nBlob!=iStart ) pParse->hasNonstd = 1; break; } if( x!=(-1) ) pParse->iErr = j; @@ -1471,15 +1621,15 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ }else if( z[j]==']' ){ break; }else{ - if( fast_isspace(z[j]) ){ - do{ j++; }while( fast_isspace(z[j]) ); + if( jsonIsspace(z[j]) ){ + j += 1 + (u32)strspn(&z[j+1], jsonSpaces); if( z[j]==',' ){ continue; }else if( z[j]==']' ){ break; } } - x = jsonParseValue(pParse, j); + x = jsonTranslateTextToBlob(pParse, j); if( x==(-4) ){ j = pParse->iErr; continue; @@ -1492,23 +1642,33 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ pParse->iErr = j; return -1; } - pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + jsonBlobChangePayloadSize(pParse, iThis, pParse->nBlob - iStart); pParse->iDepth--; return j+1; } case '\'': { - u8 jnFlags; + u8 opcode; char cDelim; pParse->hasNonstd = 1; - jnFlags = JNODE_JSON5; + opcode = JSONB_TEXT; goto parse_string; case '"': /* Parse string */ - jnFlags = 0; + opcode = JSONB_TEXT; parse_string: cDelim = z[i]; - for(j=i+1; 1; j++){ - if( jsonIsOk[(unsigned char)z[j]] ) continue; + j = i+1; + while( 1 /*exit-by-break*/ ){ + if( jsonIsOk[(u8)z[j]] ){ + if( !jsonIsOk[(u8)z[j+1]] ){ + j += 1; + }else if( !jsonIsOk[(u8)z[j+2]] ){ + j += 2; + }else{ + j += 3; + continue; + } + } c = z[j]; if( c==cDelim ){ break; @@ -1517,16 +1677,16 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' || c=='n' || c=='r' || c=='t' || (c=='u' && jsonIs4Hex(&z[j+1])) ){ - jnFlags |= JNODE_ESCAPE; + if( opcode==JSONB_TEXT ) opcode = JSONB_TEXTJ; }else if( c=='\'' || c=='0' || c=='v' || c=='\n' || (0xe2==(u8)c && 0x80==(u8)z[j+1] && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2])) || (c=='x' && jsonIs2Hex(&z[j+1])) ){ - jnFlags |= (JNODE_ESCAPE|JNODE_JSON5); + opcode = JSONB_TEXT5; pParse->hasNonstd = 1; }else if( c=='\r' ){ if( z[j+1]=='\n' ) j++; - jnFlags |= (JNODE_ESCAPE|JNODE_JSON5); + opcode = JSONB_TEXT5; pParse->hasNonstd = 1; }else{ pParse->iErr = j; @@ -1536,14 +1696,17 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ /* Control characters are not allowed in strings */ pParse->iErr = j; return -1; + }else if( c=='"' ){ + opcode = JSONB_TEXT5; } + j++; } - jsonParseAddNode(pParse, JSON_STRING | (jnFlags<<8), j+1-i, &z[i]); + jsonBlobAppendNode(pParse, opcode, j-1-i, &z[i+1]); return j+1; } case 't': { if( strncmp(z+i,"true",4)==0 && !sqlite3Isalnum(z[i+4]) ){ - jsonParseAddNode(pParse, JSON_TRUE, 0, 0); + jsonBlobAppendOneByte(pParse, JSONB_TRUE); return i+4; } pParse->iErr = i; @@ -1551,23 +1714,22 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ } case 'f': { if( strncmp(z+i,"false",5)==0 && !sqlite3Isalnum(z[i+5]) ){ - jsonParseAddNode(pParse, JSON_FALSE, 0, 0); + jsonBlobAppendOneByte(pParse, JSONB_FALSE); return i+5; } pParse->iErr = i; return -1; } case '+': { - u8 seenDP, seenE, jnFlags; + u8 seenE; pParse->hasNonstd = 1; - jnFlags = JNODE_JSON5; + t = 0x00; /* Bit 0x01: JSON5. Bit 0x02: FLOAT */ goto parse_number; case '.': if( sqlite3Isdigit(z[i+1]) ){ pParse->hasNonstd = 1; - jnFlags = JNODE_JSON5; + t = 0x03; /* Bit 0x01: JSON5. Bit 0x02: FLOAT */ seenE = 0; - seenDP = JSON_REAL; goto parse_number_2; } pParse->iErr = i; @@ -1584,9 +1746,8 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ case '8': case '9': /* Parse number */ - jnFlags = 0; + t = 0x00; /* Bit 0x01: JSON5. Bit 0x02: FLOAT */ parse_number: - seenDP = JSON_INT; seenE = 0; assert( '-' < '0' ); assert( '+' < '0' ); @@ -1596,9 +1757,9 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( c<='0' ){ if( c=='0' ){ if( (z[i+1]=='x' || z[i+1]=='X') && sqlite3Isxdigit(z[i+2]) ){ - assert( seenDP==JSON_INT ); + assert( t==0x00 ); pParse->hasNonstd = 1; - jnFlags |= JNODE_JSON5; + t = 0x01; for(j=i+3; sqlite3Isxdigit(z[j]); j++){} goto parse_number_finish; }else if( sqlite3Isdigit(z[i+1]) ){ @@ -1615,15 +1776,15 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ ){ pParse->hasNonstd = 1; if( z[i]=='-' ){ - jsonParseAddNode(pParse, JSON_REAL, 8, "-9.0e999"); + jsonBlobAppendNode(pParse, JSONB_FLOAT, 6, "-9e999"); }else{ - jsonParseAddNode(pParse, JSON_REAL, 7, "9.0e999"); + jsonBlobAppendNode(pParse, JSONB_FLOAT, 5, "9e999"); } return i + (sqlite3StrNICmp(&z[i+4],"inity",5)==0 ? 9 : 4); } if( z[i+1]=='.' ){ pParse->hasNonstd = 1; - jnFlags |= JNODE_JSON5; + t |= 0x01; goto parse_number_2; } pParse->iErr = i; @@ -1635,30 +1796,31 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ return -1; }else if( (z[i+2]=='x' || z[i+2]=='X') && sqlite3Isxdigit(z[i+3]) ){ pParse->hasNonstd = 1; - jnFlags |= JNODE_JSON5; + t |= 0x01; for(j=i+4; sqlite3Isxdigit(z[j]); j++){} goto parse_number_finish; } } } } + parse_number_2: for(j=i+1;; j++){ c = z[j]; if( sqlite3Isdigit(c) ) continue; if( c=='.' ){ - if( seenDP==JSON_REAL ){ + if( (t & 0x02)!=0 ){ pParse->iErr = j; return -1; } - seenDP = JSON_REAL; + t |= 0x02; continue; } if( c=='e' || c=='E' ){ if( z[j-1]<'0' ){ if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ pParse->hasNonstd = 1; - jnFlags |= JNODE_JSON5; + t |= 0x01; }else{ pParse->iErr = j; return -1; @@ -1668,7 +1830,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ pParse->iErr = j; return -1; } - seenDP = JSON_REAL; + t |= 0x02; seenE = 1; c = z[j+1]; if( c=='+' || c=='-' ){ @@ -1686,14 +1848,18 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( z[j-1]<'0' ){ if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ pParse->hasNonstd = 1; - jnFlags |= JNODE_JSON5; + t |= 0x01; }else{ pParse->iErr = j; return -1; } } parse_number_finish: - jsonParseAddNode(pParse, seenDP | (jnFlags<<8), j - i, &z[i]); + assert( JSONB_INT+0x01==JSONB_INT5 ); + assert( JSONB_FLOAT+0x01==JSONB_FLOAT5 ); + assert( JSONB_INT+0x02==JSONB_FLOAT ); + if( z[i]=='+' ) i++; + jsonBlobAppendNode(pParse, JSONB_INT+t, j-i, &z[i]); return j; } case '}': { @@ -1719,9 +1885,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ case 0x0a: case 0x0d: case 0x20: { - do{ - i++; - }while( fast_isspace(z[i]) ); + i += 1 + (u32)strspn(&z[i+1], jsonSpaces); goto json_parse_restart; } case 0x0b: @@ -1743,7 +1907,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ } case 'n': { if( strncmp(z+i,"null",4)==0 && !sqlite3Isalnum(z[i+4]) ){ - jsonParseAddNode(pParse, JSON_NULL, 0, 0); + jsonBlobAppendOneByte(pParse, JSONB_NULL); return i+4; } /* fall-through into the default case that checks for NaN */ @@ -1759,8 +1923,11 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ continue; } if( sqlite3Isalnum(z[i+nn]) ) continue; - jsonParseAddNode(pParse, aNanInfName[k].eType, - aNanInfName[k].nRepl, aNanInfName[k].zRepl); + if( aNanInfName[k].eType==JSONB_FLOAT ){ + jsonBlobAppendNode(pParse, JSONB_FLOAT, 5, "9e999"); + }else{ + jsonBlobAppendOneByte(pParse, JSONB_NULL); + } pParse->hasNonstd = 1; return i + nn; } @@ -1770,6 +1937,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ } /* End switch(z[i]) */ } + /* ** Parse a complete JSON string. Return 0 on success or non-zero if there ** are any errors. If an error occurs, free all memory held by pParse, @@ -1778,20 +1946,26 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ ** pParse must be initialized to an empty parse object prior to calling ** this routine. */ -static int jsonParse( +static int jsonConvertTextToBlob( JsonParse *pParse, /* Initialize and fill this JsonParse object */ sqlite3_context *pCtx /* Report errors here */ ){ int i; const char *zJson = pParse->zJson; - i = jsonParseValue(pParse, 0); + i = jsonTranslateTextToBlob(pParse, 0); if( pParse->oom ) i = -1; if( i>0 ){ +#ifdef SQLITE_DEBUG assert( pParse->iDepth==0 ); - while( fast_isspace(zJson[i]) ) i++; + if( sqlite3Config.bJsonSelfcheck ){ + assert( jsonbValidityCheck(pParse, 0, pParse->nBlob, 0)==0 ); + } +#endif + while( jsonIsspace(zJson[i]) ) i++; if( zJson[i] ){ i += json5Whitespace(&zJson[i]); if( zJson[i] ){ + if( pCtx ) sqlite3_result_error(pCtx, "malformed JSON", -1); jsonParseReset(pParse); return 1; } @@ -1812,248 +1986,710 @@ static int jsonParse( return 0; } +/* +** The input string pStr is a well-formed JSON text string. Convert +** this into the JSONB format and make it the return value of the +** SQL function. +*/ +static void jsonReturnStringAsBlob(JsonString *pStr){ + JsonParse px; + memset(&px, 0, sizeof(px)); + jsonStringTerminate(pStr); + px.zJson = pStr->zBuf; + px.nJson = pStr->nUsed; + px.db = sqlite3_context_db_handle(pStr->pCtx); + (void)jsonTranslateTextToBlob(&px, 0); + if( px.oom ){ + sqlite3DbFree(px.db, px.aBlob); + sqlite3_result_error_nomem(pStr->pCtx); + }else{ + assert( px.nBlobAlloc>0 ); + assert( !px.bReadOnly ); + sqlite3_result_blob(pStr->pCtx, px.aBlob, px.nBlob, SQLITE_DYNAMIC); + } +} -/* Mark node i of pParse as being a child of iParent. Call recursively -** to fill in all the descendants of node i. +/* The byte at index i is a node type-code. This routine +** determines the payload size for that node and writes that +** payload size in to *pSz. It returns the offset from i to the +** beginning of the payload. Return 0 on error. */ -static void jsonParseFillInParentage(JsonParse *pParse, u32 i, u32 iParent){ - JsonNode *pNode = &pParse->aNode[i]; - u32 j; - pParse->aUp[i] = iParent; - switch( pNode->eType ){ - case JSON_ARRAY: { - for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j)){ - jsonParseFillInParentage(pParse, i+j, i); +static u32 jsonbPayloadSize(const JsonParse *pParse, u32 i, u32 *pSz){ + u8 x; + u32 sz; + u32 n; + if( NEVER(i>pParse->nBlob) ){ + *pSz = 0; + return 0; + } + x = pParse->aBlob[i]>>4; + if( x<=11 ){ + sz = x; + n = 1; + }else if( x==12 ){ + if( i+1>=pParse->nBlob ){ + *pSz = 0; + return 0; + } + sz = pParse->aBlob[i+1]; + n = 2; + }else if( x==13 ){ + if( i+2>=pParse->nBlob ){ + *pSz = 0; + return 0; + } + sz = (pParse->aBlob[i+1]<<8) + pParse->aBlob[i+2]; + n = 3; + }else if( x==14 ){ + if( i+4>=pParse->nBlob ){ + *pSz = 0; + return 0; + } + sz = ((u32)pParse->aBlob[i+1]<<24) + (pParse->aBlob[i+2]<<16) + + (pParse->aBlob[i+3]<<8) + pParse->aBlob[i+4]; + n = 5; + }else{ + if( i+8>=pParse->nBlob + || pParse->aBlob[i+1]!=0 + || pParse->aBlob[i+2]!=0 + || pParse->aBlob[i+3]!=0 + || pParse->aBlob[i+4]!=0 + ){ + *pSz = 0; + return 0; + } + sz = (pParse->aBlob[i+5]<<24) + (pParse->aBlob[i+6]<<16) + + (pParse->aBlob[i+7]<<8) + pParse->aBlob[i+8]; + n = 9; + } + if( i+sz+n > pParse->nBlob + && i+sz+n > pParse->nBlob-pParse->delta + ){ + sz = 0; + n = 0; + } + *pSz = sz; + return n; +} + + +/* +** Translate the binary JSONB representation of JSON beginning at +** pParse->aBlob[i] into a JSON text string. Append the JSON +** text onto the end of pOut. Return the index in pParse->aBlob[] +** of the first byte past the end of the element that is translated. +** +** If an error is detected in the BLOB input, the pOut->eErr flag +** might get set to JSTRING_MALFORMED. But not all BLOB input errors +** are detected. So a malformed JSONB input might either result +** in an error, or in incorrect JSON. +** +** The pOut->eErr JSTRING_OOM flag is set on a OOM. +*/ +static u32 jsonTranslateBlobToText( + const JsonParse *pParse, /* the complete parse of the JSON */ + u32 i, /* Start rendering at this index */ + JsonString *pOut /* Write JSON here */ +){ + u32 sz, n, j, iEnd; + + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ){ + pOut->eErr |= JSTRING_MALFORMED; + return pParse->nBlob+1; + } + switch( pParse->aBlob[i] & 0x0f ){ + case JSONB_NULL: { + jsonAppendRawNZ(pOut, "null", 4); + return i+1; + } + case JSONB_TRUE: { + jsonAppendRawNZ(pOut, "true", 4); + return i+1; + } + case JSONB_FALSE: { + jsonAppendRawNZ(pOut, "false", 5); + return i+1; + } + case JSONB_INT: + case JSONB_FLOAT: { + jsonAppendRaw(pOut, (const char*)&pParse->aBlob[i+n], sz); + break; + } + case JSONB_INT5: { /* Integer literal in hexadecimal notation */ + u32 k = 2; + sqlite3_uint64 u = 0; + const char *zIn = (const char*)&pParse->aBlob[i+n]; + int bOverflow = 0; + if( zIn[0]=='-' ){ + jsonAppendChar(pOut, '-'); + k++; + }else if( zIn[0]=='+' ){ + k++; + } + for(; keErr |= JSTRING_MALFORMED; + break; + }else if( (u>>60)!=0 ){ + bOverflow = 1; + }else{ + u = u*16 + sqlite3HexToInt(zIn[k]); + } + } + jsonPrintf(100,pOut,bOverflow?"9.0e999":"%llu", u); + break; + } + case JSONB_FLOAT5: { /* Float literal missing digits beside "." */ + u32 k = 0; + const char *zIn = (const char*)&pParse->aBlob[i+n]; + if( zIn[0]=='-' ){ + jsonAppendChar(pOut, '-'); + k++; + } + if( zIn[k]=='.' ){ + jsonAppendChar(pOut, '0'); + } + for(; kaBlob[i+n], sz); + jsonAppendChar(pOut, '"'); + break; + } + case JSONB_TEXT5: { + const char *zIn; + u32 k; + u32 sz2 = sz; + zIn = (const char*)&pParse->aBlob[i+n]; + jsonAppendChar(pOut, '"'); + while( sz2>0 ){ + for(k=0; k0 ){ + jsonAppendRawNZ(pOut, zIn, k); + if( k>=sz2 ){ + break; + } + zIn += k; + sz2 -= k; + } + if( zIn[0]=='"' ){ + jsonAppendRawNZ(pOut, "\\\"", 2); + zIn++; + sz2--; + continue; + } + assert( zIn[0]=='\\' ); + assert( sz2>=1 ); + if( sz2<2 ){ + pOut->eErr |= JSTRING_MALFORMED; + break; + } + switch( (u8)zIn[1] ){ + case '\'': + jsonAppendChar(pOut, '\''); + break; + case 'v': + jsonAppendRawNZ(pOut, "\\u0009", 6); + break; + case 'x': + if( sz2<4 ){ + pOut->eErr |= JSTRING_MALFORMED; + sz2 = 2; + break; + } + jsonAppendRawNZ(pOut, "\\u00", 4); + jsonAppendRawNZ(pOut, &zIn[2], 2); + zIn += 2; + sz2 -= 2; + break; + case '0': + jsonAppendRawNZ(pOut, "\\u0000", 6); + break; + case '\r': + if( sz2>2 && zIn[2]=='\n' ){ + zIn++; + sz2--; + } + break; + case '\n': + break; + case 0xe2: + /* '\' followed by either U+2028 or U+2029 is ignored as + ** whitespace. Not that in UTF8, U+2028 is 0xe2 0x80 0x29. + ** U+2029 is the same except for the last byte */ + if( sz2<4 + || 0x80!=(u8)zIn[2] + || (0xa8!=(u8)zIn[3] && 0xa9!=(u8)zIn[3]) + ){ + pOut->eErr |= JSTRING_MALFORMED; + sz2 = 2; + break; + } + zIn += 2; + sz2 -= 2; + break; + default: + jsonAppendRawNZ(pOut, zIn, 2); + break; + } + assert( sz2>=2 ); + zIn += 2; + sz2 -= 2; + } + jsonAppendChar(pOut, '"'); + break; + } + case JSONB_TEXTRAW: { + jsonAppendString(pOut, (const char*)&pParse->aBlob[i+n], sz); + break; + } + case JSONB_ARRAY: { + jsonAppendChar(pOut, '['); + j = i+n; + iEnd = j+sz; + while( j0 ) pOut->nUsed--; + jsonAppendChar(pOut, ']'); break; } - case JSON_OBJECT: { - for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j+1)+1){ - pParse->aUp[i+j] = i; - jsonParseFillInParentage(pParse, i+j+1, i); + case JSONB_OBJECT: { + int x = 0; + jsonAppendChar(pOut, '{'); + j = i+n; + iEnd = j+sz; + while( jeErr |= JSTRING_MALFORMED; + if( sz>0 ) pOut->nUsed--; + jsonAppendChar(pOut, '}'); break; } + default: { + pOut->eErr |= JSTRING_MALFORMED; break; } } + return i+n+sz; +} + +/* Return true if the input pJson +** +** For performance reasons, this routine does not do a detailed check of the +** input BLOB to ensure that it is well-formed. Hence, false positives are +** possible. False negatives should never occur, however. +*/ +static int jsonFuncArgMightBeBinary(sqlite3_value *pJson){ + u32 sz, n; + const u8 *aBlob; + int nBlob; + JsonParse s; + if( sqlite3_value_type(pJson)!=SQLITE_BLOB ) return 0; + aBlob = sqlite3_value_blob(pJson); + nBlob = sqlite3_value_bytes(pJson); + if( nBlob<1 ) return 0; + if( NEVER(aBlob==0) || (aBlob[0] & 0x0f)>JSONB_OBJECT ) return 0; + memset(&s, 0, sizeof(s)); + s.aBlob = (u8*)aBlob; + s.nBlob = nBlob; + n = jsonbPayloadSize(&s, 0, &sz); + if( n==0 ) return 0; + if( sz+n!=(u32)nBlob ) return 0; + if( (aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0 ) return 0; + return sz+n==(u32)nBlob; } /* -** Compute the parentage of all nodes in a completed parse. +** Given that a JSONB_ARRAY object starts at offset i, return +** the number of entries in that array. */ -static int jsonParseFindParents(JsonParse *pParse){ - u32 *aUp; - assert( pParse->aUp==0 ); - aUp = pParse->aUp = sqlite3_malloc64( sizeof(u32)*pParse->nNode ); - if( aUp==0 ){ - pParse->oom = 1; - return SQLITE_NOMEM; +static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){ + u32 n, sz, i, iEnd; + u32 k = 0; + n = jsonbPayloadSize(pParse, iRoot, &sz); + iEnd = iRoot+n+sz; + for(i=iRoot+n; n>0 && idelta. */ -#define JSON_CACHE_ID (-429938) /* First cache entry */ -#define JSON_CACHE_SZ 4 /* Max number of cache entries */ +static void jsonAfterEditSizeAdjust(JsonParse *pParse, u32 iRoot){ + u32 sz = 0; + u32 nBlob; + assert( pParse->delta!=0 ); + assert( pParse->nBlobAlloc >= pParse->nBlob ); + nBlob = pParse->nBlob; + pParse->nBlob = pParse->nBlobAlloc; + (void)jsonbPayloadSize(pParse, iRoot, &sz); + pParse->nBlob = nBlob; + sz += pParse->delta; + pParse->delta += jsonBlobChangePayloadSize(pParse, iRoot, sz); +} /* -** Obtain a complete parse of the JSON found in the pJson argument +** Modify the JSONB blob at pParse->aBlob by removing nDel bytes of +** content beginning at iDel, and replacing them with nIns bytes of +** content given by aIns. ** -** Use the sqlite3_get_auxdata() cache to find a preexisting parse -** if it is available. If the cache is not available or if it -** is no longer valid, parse the JSON again and return the new parse. -** Also register the new parse so that it will be available for -** future sqlite3_get_auxdata() calls. +** nDel may be zero, in which case no bytes are removed. But iDel is +** still important as new bytes will be insert beginning at iDel. ** -** If an error occurs and pErrCtx!=0 then report the error on pErrCtx -** and return NULL. +** aIns may be zero, in which case space is created to hold nIns bytes +** beginning at iDel, but that space is uninitialized. ** -** The returned pointer (if it is not NULL) is owned by the cache in -** most cases, not the caller. The caller does NOT need to invoke -** jsonParseFree(), in most cases. -** -** Except, if an error occurs and pErrCtx==0 then return the JsonParse -** object with JsonParse.nErr non-zero and the caller will own the JsonParse -** object. In that case, it will be the responsibility of the caller to -** invoke jsonParseFree(). To summarize: -** -** pErrCtx!=0 || p->nErr==0 ==> Return value p is owned by the -** cache. Call does not need to -** free it. -** -** pErrCtx==0 && p->nErr!=0 ==> Return value is owned by the caller -** and so the caller must free it. +** Set pParse->oom if an OOM occurs. */ -static JsonParse *jsonParseCached( - sqlite3_context *pCtx, /* Context to use for cache search */ - sqlite3_value *pJson, /* Function param containing JSON text */ - sqlite3_context *pErrCtx, /* Write parse errors here if not NULL */ - int bUnedited /* No prior edits allowed */ +static void jsonBlobEdit( + JsonParse *pParse, /* The JSONB to be modified is in pParse->aBlob */ + u32 iDel, /* First byte to be removed */ + u32 nDel, /* Number of bytes to remove */ + const u8 *aIns, /* Content to insert */ + u32 nIns /* Bytes of content to insert */ ){ - char *zJson = (char*)sqlite3_value_text(pJson); - int nJson = sqlite3_value_bytes(pJson); - JsonParse *p; - JsonParse *pMatch = 0; - int iKey; - int iMinKey = 0; - u32 iMinHold = 0xffffffff; - u32 iMaxHold = 0; - int bJsonRCStr; + i64 d = (i64)nIns - (i64)nDel; + if( d!=0 ){ + if( pParse->nBlob + d > pParse->nBlobAlloc ){ + jsonBlobExpand(pParse, pParse->nBlob+d); + if( pParse->oom ) return; + } + memmove(&pParse->aBlob[iDel+nIns], + &pParse->aBlob[iDel+nDel], + pParse->nBlob - (iDel+nDel)); + pParse->nBlob += d; + pParse->delta += d; + } + if( nIns && aIns ) memcpy(&pParse->aBlob[iDel], aIns, nIns); +} - if( zJson==0 ) return 0; - for(iKey=0; iKeynJson==nJson - && (p->hasMod==0 || bUnedited==0) - && (p->zJson==zJson || memcmp(p->zJson,zJson,nJson)==0) - ){ - p->nErr = 0; - p->useMod = 0; - pMatch = p; - }else - if( pMatch==0 - && p->zAlt!=0 - && bUnedited==0 - && p->nAlt==nJson - && memcmp(p->zAlt, zJson, nJson)==0 + if( z[i+1]=='\r' ){ + if( i+2nErr = 0; - p->useMod = 1; - pMatch = p; - }else if( p->iHoldiHold; - iMinKey = iKey; - } - if( p->iHold>iMaxHold ){ - iMaxHold = p->iHold; - } - } - if( pMatch ){ - /* The input JSON text was found in the cache. Use the preexisting - ** parse of this JSON */ - pMatch->nErr = 0; - pMatch->iHold = iMaxHold+1; - assert( pMatch->nJPRef>0 ); /* pMatch is owned by the cache */ - return pMatch; - } - - /* The input JSON was not found anywhere in the cache. We will need - ** to parse it ourselves and generate a new JsonParse object. - */ - bJsonRCStr = sqlite3ValueIsOfClass(pJson,sqlite3RCStrUnref); - p = sqlite3_malloc64( sizeof(*p) + (bJsonRCStr ? 0 : nJson+1) ); - if( p==0 ){ - sqlite3_result_error_nomem(pCtx); - return 0; - } - memset(p, 0, sizeof(*p)); - if( bJsonRCStr ){ - p->zJson = sqlite3RCStrRef(zJson); - p->bJsonIsRCStr = 1; - }else{ - p->zJson = (char*)&p[1]; - memcpy(p->zJson, zJson, nJson+1); - } - p->nJPRef = 1; - if( jsonParse(p, pErrCtx) ){ - if( pErrCtx==0 ){ - p->nErr = 1; - assert( p->nJPRef==1 ); /* Caller will own the new JsonParse object p */ - return p; + i += 4; + continue; } - jsonParseFree(p); - return 0; + break; } - p->nJson = nJson; - p->iHold = iMaxHold+1; - /* Transfer ownership of the new JsonParse to the cache */ - sqlite3_set_auxdata(pCtx, JSON_CACHE_ID+iMinKey, p, - (void(*)(void*))jsonParseFree); - return (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iMinKey); + return i; } /* -** Compare the OBJECT label at pNode against zKey,nKey. Return true on -** a match. +** Input z[0..n] defines JSON escape sequence including the leading '\\'. +** Decode that escape sequence into a single character. Write that +** character into *piOut. Return the number of bytes in the escape sequence. +** +** If there is a syntax error of some kind (for example too few characters +** after the '\\' to complete the encoding) then *piOut is set to +** JSON_INVALID_CHAR. */ -static int jsonLabelCompare(const JsonNode *pNode, const char *zKey, u32 nKey){ - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_RAW ){ - if( pNode->n!=nKey ) return 0; - return strncmp(pNode->u.zJContent, zKey, nKey)==0; - }else{ - if( pNode->n!=nKey+2 ) return 0; - return strncmp(pNode->u.zJContent+1, zKey, nKey)==0; +static u32 jsonUnescapeOneChar(const char *z, u32 n, u32 *piOut){ + assert( n>0 ); + assert( z[0]=='\\' ); + if( n<2 ){ + *piOut = JSON_INVALID_CHAR; + return n; } -} -static int jsonSameLabel(const JsonNode *p1, const JsonNode *p2){ - if( p1->jnFlags & JNODE_RAW ){ - return jsonLabelCompare(p2, p1->u.zJContent, p1->n); - }else if( p2->jnFlags & JNODE_RAW ){ - return jsonLabelCompare(p1, p2->u.zJContent, p2->n); - }else{ - return p1->n==p2->n && strncmp(p1->u.zJContent,p2->u.zJContent,p1->n)==0; + switch( (u8)z[1] ){ + case 'u': { + u32 v, vlo; + if( n<6 ){ + *piOut = JSON_INVALID_CHAR; + return n; + } + v = jsonHexToInt4(&z[2]); + if( (v & 0xfc00)==0xd800 + && n>=12 + && z[6]=='\\' + && z[7]=='u' + && ((vlo = jsonHexToInt4(&z[8]))&0xfc00)==0xdc00 + ){ + *piOut = ((v&0x3ff)<<10) + (vlo&0x3ff) + 0x10000; + return 12; + }else{ + *piOut = v; + return 6; + } + } + case 'b': { *piOut = '\b'; return 2; } + case 'f': { *piOut = '\f'; return 2; } + case 'n': { *piOut = '\n'; return 2; } + case 'r': { *piOut = '\r'; return 2; } + case 't': { *piOut = '\t'; return 2; } + case 'v': { *piOut = '\v'; return 2; } + case '0': { *piOut = 0; return 2; } + case '\'': + case '"': + case '/': + case '\\':{ *piOut = z[1]; return 2; } + case 'x': { + if( n<4 ){ + *piOut = JSON_INVALID_CHAR; + return n; + } + *piOut = (jsonHexToInt(z[2])<<4) | jsonHexToInt(z[3]); + return 4; + } + case 0xe2: + case '\r': + case '\n': { + u32 nSkip = jsonBytesToBypass(z, n); + if( nSkip==0 ){ + *piOut = JSON_INVALID_CHAR; + return n; + }else if( nSkip==n ){ + *piOut = 0; + return n; + }else if( z[nSkip]=='\\' ){ + return nSkip + jsonUnescapeOneChar(&z[nSkip], n-nSkip, piOut); + }else{ + int sz = sqlite3Utf8ReadLimited((u8*)&z[nSkip], n-nSkip, piOut); + return nSkip + sz; + } + } + default: { + *piOut = JSON_INVALID_CHAR; + return 2; + } } } -/* forward declaration */ -static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**); /* -** Search along zPath to find the node specified. Return a pointer -** to that node, or NULL if zPath is malformed or if there is no such -** node. +** Compare two object labels. Return 1 if they are equal and +** 0 if they differ. ** -** If pApnd!=0, then try to append new nodes to complete zPath if it is -** possible to do so and if no existing node corresponds to zPath. If -** new nodes are appended *pApnd is set to 1. +** In this version, we know that one or the other or both of the +** two comparands contains an escape sequence. */ -static JsonNode *jsonLookupStep( - JsonParse *pParse, /* The JSON to search */ - u32 iRoot, /* Begin the search at this node */ - const char *zPath, /* The path to search */ - int *pApnd, /* Append nodes to complete path if not NULL */ - const char **pzErr /* Make *pzErr point to any syntax error in zPath */ +static SQLITE_NOINLINE int jsonLabelCompareEscaped( + const char *zLeft, /* The left label */ + u32 nLeft, /* Size of the left label in bytes */ + int rawLeft, /* True if zLeft contains no escapes */ + const char *zRight, /* The right label */ + u32 nRight, /* Size of the right label in bytes */ + int rawRight /* True if zRight is escape-free */ ){ - u32 i, j, nKey; - const char *zKey; - JsonNode *pRoot; - if( pParse->oom ) return 0; - pRoot = &pParse->aNode[iRoot]; - if( pRoot->jnFlags & (JNODE_REPLACE|JNODE_REMOVE) && pParse->useMod ){ - while( (pRoot->jnFlags & JNODE_REPLACE)!=0 ){ - u32 idx = (u32)(pRoot - pParse->aNode); - i = pParse->iSubst; - while( 1 /*exit-by-break*/ ){ - assert( inNode ); - assert( pParse->aNode[i].eType==JSON_SUBST ); - assert( pParse->aNode[i].eU==4 ); - assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ - pRoot = &pParse->aNode[i+1]; - iRoot = i+1; - break; - } - i = pParse->aNode[i].u.iPrev; + u32 cLeft, cRight; + assert( rawLeft==0 || rawRight==0 ); + while( 1 /*exit-by-return*/ ){ + if( nLeft==0 ){ + cLeft = 0; + }else if( rawLeft || zLeft[0]!='\\' ){ + cLeft = ((u8*)zLeft)[0]; + if( cLeft>=0xc0 ){ + int sz = sqlite3Utf8ReadLimited((u8*)zLeft, nLeft, &cLeft); + zLeft += sz; + nLeft -= sz; + }else{ + zLeft++; + nLeft--; } + }else{ + u32 n = jsonUnescapeOneChar(zLeft, nLeft, &cLeft); + zLeft += n; + assert( n<=nLeft ); + nLeft -= n; } - if( pRoot->jnFlags & JNODE_REMOVE ){ - return 0; + if( nRight==0 ){ + cRight = 0; + }else if( rawRight || zRight[0]!='\\' ){ + cRight = ((u8*)zRight)[0]; + if( cRight>=0xc0 ){ + int sz = sqlite3Utf8ReadLimited((u8*)zRight, nRight, &cRight); + zRight += sz; + nRight -= sz; + }else{ + zRight++; + nRight--; + } + }else{ + u32 n = jsonUnescapeOneChar(zRight, nRight, &cRight); + zRight += n; + assert( n<=nRight ); + nRight -= n; } + if( cLeft!=cRight ) return 0; + if( cLeft==0 ) return 1; } - if( zPath[0]==0 ) return pRoot; - if( zPath[0]=='.' ){ - if( pRoot->eType!=JSON_OBJECT ) return 0; +} + +/* +** Compare two object labels. Return 1 if they are equal and +** 0 if they differ. Return -1 if an OOM occurs. +*/ +static int jsonLabelCompare( + const char *zLeft, /* The left label */ + u32 nLeft, /* Size of the left label in bytes */ + int rawLeft, /* True if zLeft contains no escapes */ + const char *zRight, /* The right label */ + u32 nRight, /* Size of the right label in bytes */ + int rawRight /* True if zRight is escape-free */ +){ + if( rawLeft && rawRight ){ + /* Simpliest case: Neither label contains escapes. A simple + ** memcmp() is sufficient. */ + if( nLeft!=nRight ) return 0; + return memcmp(zLeft, zRight, nLeft)==0; + }else{ + return jsonLabelCompareEscaped(zLeft, nLeft, rawLeft, + zRight, nRight, rawRight); + } +} + +/* +** Error returns from jsonLookupStep() +*/ +#define JSON_LOOKUP_ERROR 0xffffffff +#define JSON_LOOKUP_NOTFOUND 0xfffffffe +#define JSON_LOOKUP_PATHERROR 0xfffffffd +#define JSON_LOOKUP_ISERROR(x) ((x)>=JSON_LOOKUP_PATHERROR) + +/* Forward declaration */ +static u32 jsonLookupStep(JsonParse*,u32,const char*,u32); + + +/* This helper routine for jsonLookupStep() populates pIns with +** binary data that is to be inserted into pParse. +** +** In the common case, pIns just points to pParse->aIns and pParse->nIns. +** But if the zPath of the original edit operation includes path elements +** that go deeper, additional substructure must be created. +** +** For example: +** +** json_insert('{}', '$.a.b.c', 123); +** +** The search stops at '$.a' But additional substructure must be +** created for the ".b.c" part of the patch so that the final result +** is: {"a":{"b":{"c"::123}}}. This routine populates pIns with +** the binary equivalent of {"b":{"c":123}} so that it can be inserted. +** +** The caller is responsible for resetting pIns when it has finished +** using the substructure. +*/ +static u32 jsonCreateEditSubstructure( + JsonParse *pParse, /* The original JSONB that is being edited */ + JsonParse *pIns, /* Populate this with the blob data to insert */ + const char *zTail /* Tail of the path that determins substructure */ +){ + static const u8 emptyObject[] = { JSONB_ARRAY, JSONB_OBJECT }; + int rc; + memset(pIns, 0, sizeof(*pIns)); + pIns->db = pParse->db; + if( zTail[0]==0 ){ + /* No substructure. Just insert what is given in pParse. */ + pIns->aBlob = pParse->aIns; + pIns->nBlob = pParse->nIns; + rc = 0; + }else{ + /* Construct the binary substructure */ + pIns->nBlob = 1; + pIns->aBlob = (u8*)&emptyObject[zTail[0]=='.']; + pIns->eEdit = pParse->eEdit; + pIns->nIns = pParse->nIns; + pIns->aIns = pParse->aIns; + rc = jsonLookupStep(pIns, 0, zTail, 0); + pParse->oom |= pIns->oom; + } + return rc; /* Error code only */ +} + +/* +** Search along zPath to find the Json element specified. Return an +** index into pParse->aBlob[] for the start of that element's value. +** +** If the value found by this routine is the value half of label/value pair +** within an object, then set pPath->iLabel to the start of the corresponding +** label, before returning. +** +** Return one of the JSON_LOOKUP error codes if problems are seen. +** +** This routine will also modify the blob. If pParse->eEdit is one of +** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, or JEDIT_SET, then changes might be +** made to the selected value. If an edit is performed, then the return +** value does not necessarily point to the select element. If an edit +** is performed, the return value is only useful for detecting error +** conditions. +*/ +static u32 jsonLookupStep( + JsonParse *pParse, /* The JSON to search */ + u32 iRoot, /* Begin the search at this element of aBlob[] */ + const char *zPath, /* The path to search */ + u32 iLabel /* Label if iRoot is a value of in an object */ +){ + u32 i, j, k, nKey, sz, n, iEnd, rc; + const char *zKey; + u8 x; + + if( zPath[0]==0 ){ + if( pParse->eEdit && jsonBlobMakeEditable(pParse, pParse->nIns) ){ + n = jsonbPayloadSize(pParse, iRoot, &sz); + sz += n; + if( pParse->eEdit==JEDIT_DEL ){ + if( iLabel>0 ){ + sz += iRoot - iLabel; + iRoot = iLabel; + } + jsonBlobEdit(pParse, iRoot, sz, 0, 0); + }else if( pParse->eEdit==JEDIT_INS ){ + /* Already exists, so json_insert() is a no-op */ + }else{ + /* json_set() or json_replace() */ + jsonBlobEdit(pParse, iRoot, sz, pParse->aIns, pParse->nIns); + } + } + pParse->iLabel = iLabel; + return iRoot; + } + if( zPath[0]=='.' ){ + int rawKey = 1; + x = pParse->aBlob[iRoot]; zPath++; if( zPath[0]=='"' ){ zKey = zPath + 1; @@ -2062,315 +2698,802 @@ static JsonNode *jsonLookupStep( if( zPath[i] ){ i++; }else{ - *pzErr = zPath; - return 0; + return JSON_LOOKUP_PATHERROR; } testcase( nKey==0 ); + rawKey = memchr(zKey, '\\', nKey)==0; }else{ zKey = zPath; for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){} nKey = i; if( nKey==0 ){ - *pzErr = zPath; - return 0; + return JSON_LOOKUP_PATHERROR; + } + } + if( (x & 0x0f)!=JSONB_OBJECT ) return JSON_LOOKUP_NOTFOUND; + n = jsonbPayloadSize(pParse, iRoot, &sz); + j = iRoot + n; /* j is the index of a label */ + iEnd = j+sz; + while( jaBlob[j] & 0x0f; + if( xJSONB_TEXTRAW ) return JSON_LOOKUP_ERROR; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return JSON_LOOKUP_ERROR; + k = j+n; /* k is the index of the label text */ + if( k+sz>=iEnd ) return JSON_LOOKUP_ERROR; + zLabel = (const char*)&pParse->aBlob[k]; + rawLabel = x==JSONB_TEXT || x==JSONB_TEXTRAW; + if( jsonLabelCompare(zKey, nKey, rawKey, zLabel, sz, rawLabel) ){ + u32 v = k+sz; /* v is the index of the value */ + if( ((pParse->aBlob[v])&0x0f)>JSONB_OBJECT ) return JSON_LOOKUP_ERROR; + n = jsonbPayloadSize(pParse, v, &sz); + if( n==0 || v+n+sz>iEnd ) return JSON_LOOKUP_ERROR; + assert( j>0 ); + rc = jsonLookupStep(pParse, v, &zPath[i], j); + if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); + return rc; } + j = k+sz; + if( ((pParse->aBlob[j])&0x0f)>JSONB_OBJECT ) return JSON_LOOKUP_ERROR; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return JSON_LOOKUP_ERROR; + j += n+sz; } - j = 1; - for(;;){ - while( j<=pRoot->n ){ - if( jsonLabelCompare(pRoot+j, zKey, nKey) ){ - return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr); + if( j>iEnd ) return JSON_LOOKUP_ERROR; + if( pParse->eEdit>=JEDIT_INS ){ + u32 nIns; /* Total bytes to insert (label+value) */ + JsonParse v; /* BLOB encoding of the value to be inserted */ + JsonParse ix; /* Header of the label to be inserted */ + testcase( pParse->eEdit==JEDIT_INS ); + testcase( pParse->eEdit==JEDIT_SET ); + memset(&ix, 0, sizeof(ix)); + ix.db = pParse->db; + jsonBlobAppendNode(&ix, rawKey?JSONB_TEXTRAW:JSONB_TEXT5, nKey, 0); + pParse->oom |= ix.oom; + rc = jsonCreateEditSubstructure(pParse, &v, &zPath[i]); + if( !JSON_LOOKUP_ISERROR(rc) + && jsonBlobMakeEditable(pParse, ix.nBlob+nKey+v.nBlob) + ){ + assert( !pParse->oom ); + nIns = ix.nBlob + nKey + v.nBlob; + jsonBlobEdit(pParse, j, 0, 0, nIns); + if( !pParse->oom ){ + assert( pParse->aBlob!=0 ); /* Because pParse->oom!=0 */ + assert( ix.aBlob!=0 ); /* Because pPasre->oom!=0 */ + memcpy(&pParse->aBlob[j], ix.aBlob, ix.nBlob); + k = j + ix.nBlob; + memcpy(&pParse->aBlob[k], zKey, nKey); + k += nKey; + memcpy(&pParse->aBlob[k], v.aBlob, v.nBlob); + if( ALWAYS(pParse->delta) ) jsonAfterEditSizeAdjust(pParse, iRoot); } - j++; - j += jsonNodeSize(&pRoot[j]); - } - if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pRoot->eU==2 ); - iRoot = pRoot->u.iAppend; - pRoot = &pParse->aNode[iRoot]; - j = 1; - } - if( pApnd ){ - u32 iStart, iLabel; - JsonNode *pNode; - assert( pParse->useMod ); - iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); - iLabel = jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); - zPath += i; - pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); - if( pParse->oom ) return 0; - if( pNode ){ - pRoot = &pParse->aNode[iRoot]; - assert( pRoot->eU==0 ); - pRoot->u.iAppend = iStart; - pRoot->jnFlags |= JNODE_APPEND; - VVA( pRoot->eU = 2 ); - pParse->aNode[iLabel].jnFlags |= JNODE_RAW; - } - return pNode; + } + jsonParseReset(&v); + jsonParseReset(&ix); + return rc; } }else if( zPath[0]=='[' ){ - i = 0; - j = 1; - while( sqlite3Isdigit(zPath[j]) ){ - i = i*10 + zPath[j] - '0'; - j++; + x = pParse->aBlob[iRoot] & 0x0f; + if( x!=JSONB_ARRAY ) return JSON_LOOKUP_NOTFOUND; + n = jsonbPayloadSize(pParse, iRoot, &sz); + k = 0; + i = 1; + while( sqlite3Isdigit(zPath[i]) ){ + k = k*10 + zPath[i] - '0'; + i++; } - if( j<2 || zPath[j]!=']' ){ + if( i<2 || zPath[i]!=']' ){ if( zPath[1]=='#' ){ - JsonNode *pBase = pRoot; - int iBase = iRoot; - if( pRoot->eType!=JSON_ARRAY ) return 0; - for(;;){ - while( j<=pBase->n ){ - if( (pBase[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i++; - j += jsonNodeSize(&pBase[j]); - } - if( (pBase->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pBase->eU==2 ); - iBase = pBase->u.iAppend; - pBase = &pParse->aNode[iBase]; - j = 1; - } - j = 2; + k = jsonbArrayCount(pParse, iRoot); + i = 2; if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ - unsigned int x = 0; - j = 3; + unsigned int nn = 0; + i = 3; do{ - x = x*10 + zPath[j] - '0'; - j++; - }while( sqlite3Isdigit(zPath[j]) ); - if( x>i ) return 0; - i -= x; + nn = nn*10 + zPath[i] - '0'; + i++; + }while( sqlite3Isdigit(zPath[i]) ); + if( nn>k ) return JSON_LOOKUP_NOTFOUND; + k -= nn; } - if( zPath[j]!=']' ){ - *pzErr = zPath; - return 0; + if( zPath[i]!=']' ){ + return JSON_LOOKUP_PATHERROR; } }else{ - *pzErr = zPath; - return 0; + return JSON_LOOKUP_PATHERROR; + } + } + j = iRoot+n; + iEnd = j+sz; + while( jdelta ) jsonAfterEditSizeAdjust(pParse, iRoot); + return rc; } + k--; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return JSON_LOOKUP_ERROR; + j += n+sz; } - if( pRoot->eType!=JSON_ARRAY ) return 0; - zPath += j + 1; - j = 1; - for(;;){ - while( j<=pRoot->n - && (i>0 || ((pRoot[j].jnFlags & JNODE_REMOVE)!=0 && pParse->useMod)) + if( j>iEnd ) return JSON_LOOKUP_ERROR; + if( k>0 ) return JSON_LOOKUP_NOTFOUND; + if( pParse->eEdit>=JEDIT_INS ){ + JsonParse v; + testcase( pParse->eEdit==JEDIT_INS ); + testcase( pParse->eEdit==JEDIT_SET ); + rc = jsonCreateEditSubstructure(pParse, &v, &zPath[i+1]); + if( !JSON_LOOKUP_ISERROR(rc) + && jsonBlobMakeEditable(pParse, v.nBlob) ){ - if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i--; - j += jsonNodeSize(&pRoot[j]); - } - if( i==0 && j<=pRoot->n ) break; - if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pRoot->eU==2 ); - iRoot = pRoot->u.iAppend; - pRoot = &pParse->aNode[iRoot]; - j = 1; - } - if( j<=pRoot->n ){ - return jsonLookupStep(pParse, iRoot+j, zPath, pApnd, pzErr); - } - if( i==0 && pApnd ){ - u32 iStart; - JsonNode *pNode; - assert( pParse->useMod ); - iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0); - pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); - if( pParse->oom ) return 0; - if( pNode ){ - pRoot = &pParse->aNode[iRoot]; - assert( pRoot->eU==0 ); - pRoot->u.iAppend = iStart; - pRoot->jnFlags |= JNODE_APPEND; - VVA( pRoot->eU = 2 ); - } - return pNode; + assert( !pParse->oom ); + jsonBlobEdit(pParse, j, 0, v.aBlob, v.nBlob); + } + jsonParseReset(&v); + if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); + return rc; } }else{ - *pzErr = zPath; + return JSON_LOOKUP_PATHERROR; } - return 0; + return JSON_LOOKUP_NOTFOUND; } /* -** Append content to pParse that will complete zPath. Return a pointer -** to the inserted node, or return NULL if the append fails. +** Convert a JSON BLOB into text and make that text the return value +** of an SQL function. */ -static JsonNode *jsonLookupAppend( - JsonParse *pParse, /* Append content to the JSON parse */ - const char *zPath, /* Description of content to append */ - int *pApnd, /* Set this flag to 1 */ - const char **pzErr /* Make this point to any syntax error */ +static void jsonReturnTextJsonFromBlob( + sqlite3_context *ctx, + const u8 *aBlob, + u32 nBlob ){ - *pApnd = 1; - if( zPath[0]==0 ){ - jsonParseAddNode(pParse, JSON_NULL, 0, 0); - return pParse->oom ? 0 : &pParse->aNode[pParse->nNode-1]; + JsonParse x; + JsonString s; + + if( NEVER(aBlob==0) ) return; + memset(&x, 0, sizeof(x)); + x.aBlob = (u8*)aBlob; + x.nBlob = nBlob; + jsonStringInit(&s, ctx); + jsonTranslateBlobToText(&x, 0, &s); + jsonReturnString(&s, 0, 0); +} + + +/* +** Return the value of the BLOB node at index i. +** +** If the value is a primitive, return it as an SQL value. +** If the value is an array or object, return it as either +** JSON text or the BLOB encoding, depending on the JSON_B flag +** on the userdata. +*/ +static void jsonReturnFromBlob( + JsonParse *pParse, /* Complete JSON parse tree */ + u32 i, /* Index of the node */ + sqlite3_context *pCtx, /* Return value for this function */ + int textOnly /* return text JSON. Disregard user-data */ +){ + u32 n, sz; + int rc; + sqlite3 *db = sqlite3_context_db_handle(pCtx); + + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ){ + sqlite3_result_error(pCtx, "malformed JSON", -1); + return; } - if( zPath[0]=='.' ){ - jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); - }else if( strncmp(zPath,"[0]",3)==0 ){ - jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); - }else{ - return 0; + switch( pParse->aBlob[i] & 0x0f ){ + case JSONB_NULL: { + if( sz ) goto returnfromblob_malformed; + sqlite3_result_null(pCtx); + break; + } + case JSONB_TRUE: { + if( sz ) goto returnfromblob_malformed; + sqlite3_result_int(pCtx, 1); + break; + } + case JSONB_FALSE: { + if( sz ) goto returnfromblob_malformed; + sqlite3_result_int(pCtx, 0); + break; + } + case JSONB_INT5: + case JSONB_INT: { + sqlite3_int64 iRes = 0; + char *z; + int bNeg = 0; + char x; + if( sz==0 ) goto returnfromblob_malformed; + x = (char)pParse->aBlob[i+n]; + if( x=='-' ){ + if( sz<2 ) goto returnfromblob_malformed; + n++; + sz--; + bNeg = 1; + } + z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); + if( z==0 ) goto returnfromblob_oom; + rc = sqlite3DecOrHexToI64(z, &iRes); + sqlite3DbFree(db, z); + if( rc==0 ){ + sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); + }else if( rc==3 && bNeg ){ + sqlite3_result_int64(pCtx, SMALLEST_INT64); + }else if( rc==1 ){ + goto returnfromblob_malformed; + }else{ + if( bNeg ){ n--; sz++; } + goto to_double; + } + break; + } + case JSONB_FLOAT5: + case JSONB_FLOAT: { + double r; + char *z; + if( sz==0 ) goto returnfromblob_malformed; + to_double: + z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); + if( z==0 ) goto returnfromblob_oom; + rc = sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); + sqlite3DbFree(db, z); + if( rc<=0 ) goto returnfromblob_malformed; + sqlite3_result_double(pCtx, r); + break; + } + case JSONB_TEXTRAW: + case JSONB_TEXT: { + sqlite3_result_text(pCtx, (char*)&pParse->aBlob[i+n], sz, + SQLITE_TRANSIENT); + break; + } + case JSONB_TEXT5: + case JSONB_TEXTJ: { + /* Translate JSON formatted string into raw text */ + u32 iIn, iOut; + const char *z; + char *zOut; + u32 nOut = sz; + z = (const char*)&pParse->aBlob[i+n]; + zOut = sqlite3DbMallocRaw(db, nOut+1); + if( zOut==0 ) goto returnfromblob_oom; + for(iIn=iOut=0; iIn=2 ); + zOut[iOut++] = (char)(0xc0 | (v>>6)); + zOut[iOut++] = 0x80 | (v&0x3f); + }else if( v<0x10000 ){ + assert( szEscape>=3 ); + zOut[iOut++] = 0xe0 | (v>>12); + zOut[iOut++] = 0x80 | ((v>>6)&0x3f); + zOut[iOut++] = 0x80 | (v&0x3f); + }else if( v==JSON_INVALID_CHAR ){ + /* Silently ignore illegal unicode */ + }else{ + assert( szEscape>=4 ); + zOut[iOut++] = 0xf0 | (v>>18); + zOut[iOut++] = 0x80 | ((v>>12)&0x3f); + zOut[iOut++] = 0x80 | ((v>>6)&0x3f); + zOut[iOut++] = 0x80 | (v&0x3f); + } + iIn += szEscape - 1; + }else{ + zOut[iOut++] = c; + } + } /* end for() */ + assert( iOut<=nOut ); + zOut[iOut] = 0; + sqlite3_result_text(pCtx, zOut, iOut, SQLITE_DYNAMIC); + break; + } + case JSONB_ARRAY: + case JSONB_OBJECT: { + int flags = textOnly ? 0 : SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)); + if( flags & JSON_BLOB ){ + sqlite3_result_blob(pCtx, &pParse->aBlob[i], sz+n, SQLITE_TRANSIENT); + }else{ + jsonReturnTextJsonFromBlob(pCtx, &pParse->aBlob[i], sz+n); + } + break; + } + default: { + goto returnfromblob_malformed; + } } - if( pParse->oom ) return 0; - return jsonLookupStep(pParse, pParse->nNode-1, zPath, pApnd, pzErr); + return; + +returnfromblob_oom: + sqlite3_result_error_nomem(pCtx); + return; + +returnfromblob_malformed: + sqlite3_result_error(pCtx, "malformed JSON", -1); + return; } /* -** Return the text of a syntax error message on a JSON path. Space is -** obtained from sqlite3_malloc(). +** pArg is a function argument that might be an SQL value or a JSON +** value. Figure out what it is and encode it as a JSONB blob. +** Return the results in pParse. +** +** pParse is uninitialized upon entry. This routine will handle the +** initialization of pParse. The result will be contained in +** pParse->aBlob and pParse->nBlob. pParse->aBlob might be dynamically +** allocated (if pParse->nBlobAlloc is greater than zero) in which case +** the caller is responsible for freeing the space allocated to pParse->aBlob +** when it has finished with it. Or pParse->aBlob might be a static string +** or a value obtained from sqlite3_value_blob(pArg). +** +** If the argument is a BLOB that is clearly not a JSONB, then this +** function might set an error message in ctx and return non-zero. +** It might also set an error message and return non-zero on an OOM error. */ -static char *jsonPathSyntaxError(const char *zErr){ - return sqlite3_mprintf("JSON path error near '%q'", zErr); +static int jsonFunctionArgToBlob( + sqlite3_context *ctx, + sqlite3_value *pArg, + JsonParse *pParse +){ + int eType = sqlite3_value_type(pArg); + static u8 aNull[] = { 0x00 }; + memset(pParse, 0, sizeof(pParse[0])); + pParse->db = sqlite3_context_db_handle(ctx); + switch( eType ){ + default: { + pParse->aBlob = aNull; + pParse->nBlob = 1; + return 0; + } + case SQLITE_BLOB: { + if( jsonFuncArgMightBeBinary(pArg) ){ + pParse->aBlob = (u8*)sqlite3_value_blob(pArg); + pParse->nBlob = sqlite3_value_bytes(pArg); + }else{ + sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1); + return 1; + } + break; + } + case SQLITE_TEXT: { + const char *zJson = (const char*)sqlite3_value_text(pArg); + int nJson = sqlite3_value_bytes(pArg); + if( zJson==0 ) return 1; + if( sqlite3_value_subtype(pArg)==JSON_SUBTYPE ){ + pParse->zJson = (char*)zJson; + pParse->nJson = nJson; + if( jsonConvertTextToBlob(pParse, ctx) ){ + sqlite3_result_error(ctx, "malformed JSON", -1); + sqlite3DbFree(pParse->db, pParse->aBlob); + memset(pParse, 0, sizeof(pParse[0])); + return 1; + } + }else{ + jsonBlobAppendNode(pParse, JSONB_TEXTRAW, nJson, zJson); + } + break; + } + case SQLITE_FLOAT: { + double r = sqlite3_value_double(pArg); + if( NEVER(sqlite3IsNaN(r)) ){ + jsonBlobAppendNode(pParse, JSONB_NULL, 0, 0); + }else{ + int n = sqlite3_value_bytes(pArg); + const char *z = (const char*)sqlite3_value_text(pArg); + if( z==0 ) return 1; + if( z[0]=='I' ){ + jsonBlobAppendNode(pParse, JSONB_FLOAT, 5, "9e999"); + }else if( z[0]=='-' && z[1]=='I' ){ + jsonBlobAppendNode(pParse, JSONB_FLOAT, 6, "-9e999"); + }else{ + jsonBlobAppendNode(pParse, JSONB_FLOAT, n, z); + } + } + break; + } + case SQLITE_INTEGER: { + int n = sqlite3_value_bytes(pArg); + const char *z = (const char*)sqlite3_value_text(pArg); + if( z==0 ) return 1; + jsonBlobAppendNode(pParse, JSONB_INT, n, z); + break; + } + } + if( pParse->oom ){ + sqlite3_result_error_nomem(ctx); + return 1; + }else{ + return 0; + } } /* -** Do a node lookup using zPath. Return a pointer to the node on success. -** Return NULL if not found or if there is an error. -** -** On an error, write an error message into pCtx and increment the -** pParse->nErr counter. +** Generate a bad path error. ** -** If pApnd!=NULL then try to append missing nodes and set *pApnd = 1 if -** nodes are appended. +** If ctx is not NULL then push the error message into ctx and return NULL. +** If ctx is NULL, then return the text of the error message. */ -static JsonNode *jsonLookup( - JsonParse *pParse, /* The JSON to search */ - const char *zPath, /* The path to search */ - int *pApnd, /* Append nodes to complete path if not NULL */ - sqlite3_context *pCtx /* Report errors here, if not NULL */ +static char *jsonBadPathError( + sqlite3_context *ctx, /* The function call containing the error */ + const char *zPath /* The path with the problem */ ){ - const char *zErr = 0; - JsonNode *pNode = 0; - char *zMsg; - - if( zPath==0 ) return 0; - if( zPath[0]!='$' ){ - zErr = zPath; - goto lookup_err; - } - zPath++; - pNode = jsonLookupStep(pParse, 0, zPath, pApnd, &zErr); - if( zErr==0 ) return pNode; - -lookup_err: - pParse->nErr++; - assert( zErr!=0 && pCtx!=0 ); - zMsg = jsonPathSyntaxError(zErr); + char *zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); + if( ctx==0 ) return zMsg; if( zMsg ){ - sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_result_error(ctx, zMsg, -1); sqlite3_free(zMsg); }else{ - sqlite3_result_error_nomem(pCtx); + sqlite3_result_error_nomem(ctx); } return 0; } +/* argv[0] is a BLOB that seems likely to be a JSONB. Subsequent +** arguments come in parse where each pair contains a JSON path and +** content to insert or set at that patch. Do the updates +** and return the result. +** +** The specific operation is determined by eEdit, which can be one +** of JEDIT_INS, JEDIT_REPL, or JEDIT_SET. +*/ +static void jsonInsertIntoBlob( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv, + int eEdit /* JEDIT_INS, JEDIT_REPL, or JEDIT_SET */ +){ + int i; + u32 rc = 0; + const char *zPath = 0; + int flgs; + JsonParse *p; + JsonParse ax; + + assert( (argc&1)==1 ); + flgs = argc==1 ? 0 : JSON_EDITABLE; + p = jsonParseFuncArg(ctx, argv[0], flgs); + if( p==0 ) return; + for(i=1; inBlob, ax.aBlob, ax.nBlob); + } + rc = 0; + }else{ + p->eEdit = eEdit; + p->nIns = ax.nBlob; + p->aIns = ax.aBlob; + p->delta = 0; + rc = jsonLookupStep(p, 0, zPath+1, 0); + } + jsonParseReset(&ax); + if( rc==JSON_LOOKUP_NOTFOUND ) continue; + if( JSON_LOOKUP_ISERROR(rc) ) goto jsonInsertIntoBlob_patherror; + } + jsonReturnParse(ctx, p); + jsonParseFree(p); + return; + +jsonInsertIntoBlob_patherror: + jsonParseFree(p); + if( rc==JSON_LOOKUP_ERROR ){ + sqlite3_result_error(ctx, "malformed JSON", -1); + }else{ + jsonBadPathError(ctx, zPath); + } + return; +} /* -** Report the wrong number of arguments for json_insert(), json_replace() -** or json_set(). +** Generate a JsonParse object, containing valid JSONB in aBlob and nBlob, +** from the SQL function argument pArg. Return a pointer to the new +** JsonParse object. +** +** Ownership of the new JsonParse object is passed to the caller. The +** caller should invoke jsonParseFree() on the return value when it +** has finished using it. +** +** If any errors are detected, an appropriate error messages is set +** using sqlite3_result_error() or the equivalent and this routine +** returns NULL. This routine also returns NULL if the pArg argument +** is an SQL NULL value, but no error message is set in that case. This +** is so that SQL functions that are given NULL arguments will return +** a NULL value. */ -static void jsonWrongNumArgs( - sqlite3_context *pCtx, - const char *zFuncName +static JsonParse *jsonParseFuncArg( + sqlite3_context *ctx, + sqlite3_value *pArg, + u32 flgs ){ - char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", - zFuncName); - sqlite3_result_error(pCtx, zMsg, -1); - sqlite3_free(zMsg); + int eType; /* Datatype of pArg */ + JsonParse *p = 0; /* Value to be returned */ + JsonParse *pFromCache = 0; /* Value taken from cache */ + sqlite3 *db; /* The database connection */ + + assert( ctx!=0 ); + eType = sqlite3_value_type(pArg); + if( eType==SQLITE_NULL ){ + return 0; + } + pFromCache = jsonCacheSearch(ctx, pArg); + if( pFromCache ){ + pFromCache->nJPRef++; + if( (flgs & JSON_EDITABLE)==0 ){ + return pFromCache; + } + } + db = sqlite3_context_db_handle(ctx); +rebuild_from_cache: + p = sqlite3DbMallocZero(db, sizeof(*p)); + if( p==0 ) goto json_pfa_oom; + memset(p, 0, sizeof(*p)); + p->db = db; + p->nJPRef = 1; + if( pFromCache!=0 ){ + u32 nBlob = pFromCache->nBlob; + p->aBlob = sqlite3DbMallocRaw(db, nBlob); + if( p->aBlob==0 ) goto json_pfa_oom; + memcpy(p->aBlob, pFromCache->aBlob, nBlob); + p->nBlobAlloc = p->nBlob = nBlob; + p->hasNonstd = pFromCache->hasNonstd; + jsonParseFree(pFromCache); + return p; + } + if( eType==SQLITE_BLOB ){ + u32 n, sz = 0; + p->aBlob = (u8*)sqlite3_value_blob(pArg); + p->nBlob = (u32)sqlite3_value_bytes(pArg); + if( p->nBlob==0 ){ + goto json_pfa_malformed; + } + if( NEVER(p->aBlob==0) ){ + goto json_pfa_oom; + } + if( (p->aBlob[0] & 0x0f)>JSONB_OBJECT ){ + goto json_pfa_malformed; + } + n = jsonbPayloadSize(p, 0, &sz); + if( n==0 + || sz+n!=p->nBlob + || ((p->aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0) + ){ + goto json_pfa_malformed; + } + if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){ + goto json_pfa_oom; + } + return p; + } + p->zJson = (char*)sqlite3_value_text(pArg); + p->nJson = sqlite3_value_bytes(pArg); + if( p->nJson==0 ) goto json_pfa_malformed; + if( NEVER(p->zJson==0) ) goto json_pfa_oom; + if( jsonConvertTextToBlob(p, (flgs & JSON_KEEPERROR) ? 0 : ctx) ){ + if( flgs & JSON_KEEPERROR ){ + p->nErr = 1; + return p; + }else{ + jsonParseFree(p); + return 0; + } + }else{ + int isRCStr = sqlite3ValueIsOfClass(pArg, sqlite3RCStrUnref); + int rc; + if( !isRCStr ){ + char *zNew = sqlite3RCStrNew( p->nJson ); + if( zNew==0 ) goto json_pfa_oom; + memcpy(zNew, p->zJson, p->nJson); + p->zJson = zNew; + p->zJson[p->nJson] = 0; + }else{ + sqlite3RCStrRef(p->zJson); + } + p->bJsonIsRCStr = 1; + rc = jsonCacheInsert(ctx, p); + if( rc==SQLITE_NOMEM ) goto json_pfa_oom; + if( flgs & JSON_EDITABLE ){ + pFromCache = p; + p = 0; + goto rebuild_from_cache; + } + } + return p; + +json_pfa_malformed: + if( flgs & JSON_KEEPERROR ){ + p->nErr = 1; + return p; + }else{ + jsonParseFree(p); + sqlite3_result_error(ctx, "malformed JSON", -1); + return 0; + } + +json_pfa_oom: + jsonParseFree(pFromCache); + jsonParseFree(p); + sqlite3_result_error_nomem(ctx); + return 0; } /* -** Mark all NULL entries in the Object passed in as JNODE_REMOVE. +** Make the return value of a JSON function either the raw JSONB blob +** or make it JSON text, depending on whether the JSON_BLOB flag is +** set on the function. */ -static void jsonRemoveAllNulls(JsonNode *pNode){ - int i, n; - assert( pNode->eType==JSON_OBJECT ); - n = pNode->n; - for(i=2; i<=n; i += jsonNodeSize(&pNode[i])+1){ - switch( pNode[i].eType ){ - case JSON_NULL: - pNode[i].jnFlags |= JNODE_REMOVE; - break; - case JSON_OBJECT: - jsonRemoveAllNulls(&pNode[i]); - break; +static void jsonReturnParse( + sqlite3_context *ctx, + JsonParse *p +){ + int flgs; + if( p->oom ){ + sqlite3_result_error_nomem(ctx); + return; + } + flgs = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + if( flgs & JSON_BLOB ){ + if( p->nBlobAlloc>0 && !p->bReadOnly ){ + sqlite3_result_blob(ctx, p->aBlob, p->nBlob, SQLITE_DYNAMIC); + p->nBlobAlloc = 0; + }else{ + sqlite3_result_blob(ctx, p->aBlob, p->nBlob, SQLITE_TRANSIENT); } + }else{ + JsonString s; + jsonStringInit(&s, ctx); + p->delta = 0; + jsonTranslateBlobToText(p, 0, &s); + jsonReturnString(&s, p, ctx); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); } } - /**************************************************************************** ** SQL functions used for testing and debugging ****************************************************************************/ #if SQLITE_DEBUG /* -** Print N node entries. +** Decode JSONB bytes in aBlob[] starting at iStart through but not +** including iEnd. Indent the +** content by nIndent spaces. */ -static void jsonDebugPrintNodeEntries( - JsonNode *aNode, /* First node entry to print */ - int N /* Number of node entries to print */ +static void jsonDebugPrintBlob( + JsonParse *pParse, /* JSON content */ + u32 iStart, /* Start rendering here */ + u32 iEnd, /* Do not render this byte or any byte after this one */ + int nIndent, /* Indent by this many spaces */ + sqlite3_str *pOut /* Generate output into this sqlite3_str object */ ){ - int i; - for(i=0; iaBlob[iStart] & 0x0f; + u32 savedNBlob = pParse->nBlob; + sqlite3_str_appendf(pOut, "%5d:%*s", iStart, nIndent, ""); + if( pParse->nBlobAlloc>pParse->nBlob ){ + pParse->nBlob = pParse->nBlobAlloc; } - printf("node %4u: %-7s n=%-5d", i, zType, aNode[i].n); - if( (aNode[i].jnFlags & ~JNODE_LABEL)!=0 ){ - u8 f = aNode[i].jnFlags; - if( f & JNODE_RAW ) printf(" RAW"); - if( f & JNODE_ESCAPE ) printf(" ESCAPE"); - if( f & JNODE_REMOVE ) printf(" REMOVE"); - if( f & JNODE_REPLACE ) printf(" REPLACE"); - if( f & JNODE_APPEND ) printf(" APPEND"); - if( f & JNODE_JSON5 ) printf(" JSON5"); + nn = n = jsonbPayloadSize(pParse, iStart, &sz); + if( nn==0 ) nn = 1; + if( sz>0 && xaBlob[iStart+i]); } + if( n==0 ){ + sqlite3_str_appendf(pOut, " ERROR invalid node size\n"); + iStart = n==0 ? iStart+1 : iEnd; + continue; + } + pParse->nBlob = savedNBlob; + if( iStart+n+sz>iEnd ){ + iEnd = iStart+n+sz; + if( iEnd>pParse->nBlob ){ + if( pParse->nBlobAlloc>0 && iEnd>pParse->nBlobAlloc ){ + iEnd = pParse->nBlobAlloc; + }else{ + iEnd = pParse->nBlob; + } + } + } + sqlite3_str_appendall(pOut," <-- "); + switch( x ){ + case JSONB_NULL: sqlite3_str_appendall(pOut,"null"); break; + case JSONB_TRUE: sqlite3_str_appendall(pOut,"true"); break; + case JSONB_FALSE: sqlite3_str_appendall(pOut,"false"); break; + case JSONB_INT: sqlite3_str_appendall(pOut,"int"); break; + case JSONB_INT5: sqlite3_str_appendall(pOut,"int5"); break; + case JSONB_FLOAT: sqlite3_str_appendall(pOut,"float"); break; + case JSONB_FLOAT5: sqlite3_str_appendall(pOut,"float5"); break; + case JSONB_TEXT: sqlite3_str_appendall(pOut,"text"); break; + case JSONB_TEXTJ: sqlite3_str_appendall(pOut,"textj"); break; + case JSONB_TEXT5: sqlite3_str_appendall(pOut,"text5"); break; + case JSONB_TEXTRAW: sqlite3_str_appendall(pOut,"textraw"); break; + case JSONB_ARRAY: { + sqlite3_str_appendf(pOut,"array, %u bytes\n", sz); + jsonDebugPrintBlob(pParse, iStart+n, iStart+n+sz, nIndent+2, pOut); + showContent = 0; + break; + } + case JSONB_OBJECT: { + sqlite3_str_appendf(pOut, "object, %u bytes\n", sz); + jsonDebugPrintBlob(pParse, iStart+n, iStart+n+sz, nIndent+2, pOut); + showContent = 0; + break; + } + default: { + sqlite3_str_appendall(pOut, "ERROR: unknown node type\n"); + showContent = 0; + break; + } + } + if( showContent ){ + if( sz==0 && x<=JSONB_FALSE ){ + sqlite3_str_append(pOut, "\n", 1); + }else{ + u32 i; + sqlite3_str_appendall(pOut, ": \""); + for(i=iStart+n; iaBlob[i]; + if( c<0x20 || c>=0x7f ) c = '.'; + sqlite3_str_append(pOut, (char*)&c, 1); + } + sqlite3_str_append(pOut, "\"\n", 2); + } + } + iStart += n + sz; } } -#endif /* SQLITE_DEBUG */ - - -#if 0 /* 1 for debugging. 0 normally. Requires -DSQLITE_DEBUG too */ -static void jsonDebugPrintParse(JsonParse *p){ - jsonDebugPrintNodeEntries(p->aNode, p->nNode); -} -static void jsonDebugPrintNode(JsonNode *pNode){ - jsonDebugPrintNodeEntries(pNode, jsonNodeSize(pNode)); +static void jsonShowParse(JsonParse *pParse){ + sqlite3_str out; + char zBuf[1000]; + if( pParse==0 ){ + printf("NULL pointer\n"); + return; + }else{ + printf("nBlobAlloc = %u\n", pParse->nBlobAlloc); + printf("nBlob = %u\n", pParse->nBlob); + printf("delta = %d\n", pParse->delta); + if( pParse->nBlob==0 ) return; + printf("content (bytes 0..%u):\n", pParse->nBlob-1); + } + sqlite3StrAccumInit(&out, 0, zBuf, sizeof(zBuf), 1000000); + jsonDebugPrintBlob(pParse, 0, pParse->nBlob, 0, &out); + printf("%s", sqlite3_str_value(&out)); + sqlite3_str_reset(&out); } -#else - /* The usual case */ -# define jsonDebugPrintNode(X) -# define jsonDebugPrintParse(X) -#endif +#endif /* SQLITE_DEBUG */ #ifdef SQLITE_DEBUG /* ** SQL function: json_parse(JSON) ** -** Parse JSON using jsonParseCached(). Then print a dump of that -** parse on standard output. Return the mimified JSON result, just -** like the json() function. +** Parse JSON using jsonParseFuncArg(). Return text that is a +** human-readable dump of the binary JSONB for the input parameter. */ static void jsonParseFunc( sqlite3_context *ctx, @@ -2378,38 +3501,19 @@ static void jsonParseFunc( sqlite3_value **argv ){ JsonParse *p; /* The parse */ + sqlite3_str out; - assert( argc==1 ); - p = jsonParseCached(ctx, argv[0], ctx, 0); + assert( argc>=1 ); + sqlite3StrAccumInit(&out, 0, 0, 0, 1000000); + p = jsonParseFuncArg(ctx, argv[0], 0); if( p==0 ) return; - printf("nNode = %u\n", p->nNode); - printf("nAlloc = %u\n", p->nAlloc); - printf("nJson = %d\n", p->nJson); - printf("nAlt = %d\n", p->nAlt); - printf("nErr = %u\n", p->nErr); - printf("oom = %u\n", p->oom); - printf("hasNonstd = %u\n", p->hasNonstd); - printf("useMod = %u\n", p->useMod); - printf("hasMod = %u\n", p->hasMod); - printf("nJPRef = %u\n", p->nJPRef); - printf("iSubst = %u\n", p->iSubst); - printf("iHold = %u\n", p->iHold); - jsonDebugPrintNodeEntries(p->aNode, p->nNode); - jsonReturnJson(p, p->aNode, ctx, 1, 0); -} - -/* -** The json_test1(JSON) function return true (1) if the input is JSON -** text generated by another json function. It returns (0) if the input -** is not known to be JSON. -*/ -static void jsonTest1Func( - sqlite3_context *ctx, - int argc, - sqlite3_value **argv -){ - UNUSED_PARAMETER(argc); - sqlite3_result_int(ctx, sqlite3_value_subtype(argv[0])==JSON_SUBTYPE); + if( argc==1 ){ + jsonDebugPrintBlob(p, 0, p->nBlob, 0, &out); + sqlite3_result_text64(ctx, out.zText, out.nChar, SQLITE_DYNAMIC, SQLITE_UTF8); + }else{ + jsonShowParse(p); + } + jsonParseFree(p); } #endif /* SQLITE_DEBUG */ @@ -2418,7 +3522,7 @@ static void jsonTest1Func( ****************************************************************************/ /* -** Implementation of the json_QUOTE(VALUE) function. Return a JSON value +** Implementation of the json_quote(VALUE) function. Return a JSON value ** corresponding to the SQL value input. Mostly this means putting ** double-quotes around strings and returning the unquoted string "null" ** when given a NULL input. @@ -2431,9 +3535,9 @@ static void jsonQuoteFunc( JsonString jx; UNUSED_PARAMETER(argc); - jsonInit(&jx, ctx); - jsonAppendValue(&jx, argv[0]); - jsonResult(&jx); + jsonStringInit(&jx, ctx); + jsonAppendSqlValue(&jx, argv[0]); + jsonReturnString(&jx, 0, 0); sqlite3_result_subtype(ctx, JSON_SUBTYPE); } @@ -2450,18 +3554,17 @@ static void jsonArrayFunc( int i; JsonString jx; - jsonInit(&jx, ctx); + jsonStringInit(&jx, ctx); jsonAppendChar(&jx, '['); for(i=0; inNode ); if( argc==2 ){ const char *zPath = (const char*)sqlite3_value_text(argv[1]); - pNode = jsonLookup(p, zPath, 0, ctx); - }else{ - pNode = p->aNode; - } - if( pNode==0 ){ - return; - } - if( pNode->eType==JSON_ARRAY ){ - while( 1 /*exit-by-break*/ ){ - i = 1; - while( i<=pNode->n ){ - if( (pNode[i].jnFlags & JNODE_REMOVE)==0 ) n++; - i += jsonNodeSize(&pNode[i]); + if( zPath==0 ){ + jsonParseFree(p); + return; + } + i = jsonLookupStep(p, 0, zPath[0]=='$' ? zPath+1 : "@", 0); + if( JSON_LOOKUP_ISERROR(i) ){ + if( i==JSON_LOOKUP_NOTFOUND ){ + /* no-op */ + }else if( i==JSON_LOOKUP_PATHERROR ){ + jsonBadPathError(ctx, zPath); + }else{ + sqlite3_result_error(ctx, "malformed JSON", -1); } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - if( p->useMod==0 ) break; - assert( pNode->eU==2 ); - pNode = &p->aNode[pNode->u.iAppend]; + eErr = 1; + i = 0; } + }else{ + i = 0; } - sqlite3_result_int64(ctx, n); + if( (p->aBlob[i] & 0x0f)==JSONB_ARRAY ){ + cnt = jsonbArrayCount(p, i); + } + if( !eErr ) sqlite3_result_int64(ctx, cnt); + jsonParseFree(p); } -/* -** Bit values for the flags passed into jsonExtractFunc() or -** jsonSetFunc() via the user-data value. -*/ -#define JSON_JSON 0x01 /* Result is always JSON */ -#define JSON_SQL 0x02 /* Result is always SQL */ -#define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ -#define JSON_ISSET 0x04 /* json_set(), not json_insert() */ +/* True if the string is all digits */ +static int jsonAllDigits(const char *z, int n){ + int i; + for(i=0; i2 ){ + jsonAppendChar(&jx, '['); + } + for(i=1; i and ->> operators accept abbreviated PATH arguments. This - ** is mostly for compatibility with PostgreSQL, but also for - ** convenience. - ** - ** NUMBER ==> $[NUMBER] // PG compatible - ** LABEL ==> $.LABEL // PG compatible - ** [NUMBER] ==> $[NUMBER] // Not PG. Purely for convenience - */ - jsonInit(&jx, ctx); - if( sqlite3Isdigit(zPath[0]) ){ - jsonAppendRawNZ(&jx, "$[", 2); - jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); - jsonAppendRawNZ(&jx, "]", 2); - }else{ - jsonAppendRawNZ(&jx, "$.", 1 + (zPath[0]!='[')); - jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); - jsonAppendChar(&jx, 0); - } - pNode = jx.bErr ? 0 : jsonLookup(p, jx.zBuf, 0, ctx); - jsonReset(&jx); + const char *zPath = (const char*)sqlite3_value_text(argv[i]); + int nPath; + u32 j; + if( zPath==0 ) goto json_extract_error; + nPath = sqlite3Strlen30(zPath); + if( zPath[0]=='$' ){ + j = jsonLookupStep(p, 0, zPath+1, 0); + }else if( (flags & JSON_ABPATH) ){ + /* The -> and ->> operators accept abbreviated PATH arguments. This + ** is mostly for compatibility with PostgreSQL, but also for + ** convenience. + ** + ** NUMBER ==> $[NUMBER] // PG compatible + ** LABEL ==> $.LABEL // PG compatible + ** [NUMBER] ==> $[NUMBER] // Not PG. Purely for convenience + */ + jsonStringInit(&jx, ctx); + if( jsonAllDigits(zPath, nPath) ){ + jsonAppendRawNZ(&jx, "[", 1); + jsonAppendRaw(&jx, zPath, nPath); + jsonAppendRawNZ(&jx, "]", 2); + }else if( jsonAllAlphanum(zPath, nPath) ){ + jsonAppendRawNZ(&jx, ".", 1); + jsonAppendRaw(&jx, zPath, nPath); + }else if( zPath[0]=='[' && nPath>=3 && zPath[nPath-1]==']' ){ + jsonAppendRaw(&jx, zPath, nPath); }else{ - pNode = jsonLookup(p, zPath, 0, ctx); + jsonAppendRawNZ(&jx, ".\"", 2); + jsonAppendRaw(&jx, zPath, nPath); + jsonAppendRawNZ(&jx, "\"", 1); } - if( pNode ){ + jsonStringTerminate(&jx); + j = jsonLookupStep(p, 0, jx.zBuf, 0); + jsonStringReset(&jx); + }else{ + jsonBadPathError(ctx, zPath); + goto json_extract_error; + } + if( jnBlob ){ + if( argc==2 ){ if( flags & JSON_JSON ){ - jsonReturnJson(p, pNode, ctx, 0, 0); + jsonStringInit(&jx, ctx); + jsonTranslateBlobToText(p, j, &jx); + jsonReturnString(&jx, 0, 0); + jsonStringReset(&jx); + assert( (flags & JSON_BLOB)==0 ); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); }else{ - jsonReturn(p, pNode, ctx, 1); + jsonReturnFromBlob(p, j, ctx, 0); + if( (flags & (JSON_SQL|JSON_BLOB))==0 + && (p->aBlob[j]&0x0f)>=JSONB_ARRAY + ){ + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } } + }else{ + jsonAppendSeparator(&jx); + jsonTranslateBlobToText(p, j, &jx); } - }else{ - pNode = jsonLookup(p, zPath, 0, ctx); - if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx, 0); - } - }else{ - /* Two or more PATH arguments results in a JSON array with each - ** element of the array being the value selected by one of the PATHs */ - int i; - jsonInit(&jx, ctx); - jsonAppendChar(&jx, '['); - for(i=1; inErr ) break; - jsonAppendSeparator(&jx); - if( pNode ){ - jsonRenderNode(p, pNode, &jx); + }else if( j==JSON_LOOKUP_NOTFOUND ){ + if( argc==2 ){ + goto json_extract_error; /* Return NULL if not found */ }else{ + jsonAppendSeparator(&jx); jsonAppendRawNZ(&jx, "null", 4); } + }else if( j==JSON_LOOKUP_ERROR ){ + sqlite3_result_error(ctx, "malformed JSON", -1); + goto json_extract_error; + }else{ + jsonBadPathError(ctx, zPath); + goto json_extract_error; } - if( i==argc ){ - jsonAppendChar(&jx, ']'); - jsonResult(&jx); + } + if( argc>2 ){ + jsonAppendChar(&jx, ']'); + jsonReturnString(&jx, 0, 0); + if( (flags & JSON_BLOB)==0 ){ sqlite3_result_subtype(ctx, JSON_SUBTYPE); } - jsonReset(&jx); } +json_extract_error: + jsonStringReset(&jx); + jsonParseFree(p); + return; } -/* This is the RFC 7396 MergePatch algorithm. +/* +** Return codes for jsonMergePatch() +*/ +#define JSON_MERGE_OK 0 /* Success */ +#define JSON_MERGE_BADTARGET 1 /* Malformed TARGET blob */ +#define JSON_MERGE_BADPATCH 2 /* Malformed PATCH blob */ +#define JSON_MERGE_OOM 3 /* Out-of-memory condition */ + +/* +** RFC-7396 MergePatch for two JSONB blobs. +** +** pTarget is the target. pPatch is the patch. The target is updated +** in place. The patch is read-only. +** +** The original RFC-7396 algorithm is this: +** +** define MergePatch(Target, Patch): +** if Patch is an Object: +** if Target is not an Object: +** Target = {} # Ignore the contents and set it to an empty Object +** for each Name/Value pair in Patch: +** if Value is null: +** if Name exists in Target: +** remove the Name/Value pair from Target +** else: +** Target[Name] = MergePatch(Target[Name], Value) +** return Target +** else: +** return Patch +** +** Here is an equivalent algorithm restructured to show the actual +** implementation: +** +** 01 define MergePatch(Target, Patch): +** 02 if Patch is not an Object: +** 03 return Patch +** 04 else: // if Patch is an Object +** 05 if Target is not an Object: +** 06 Target = {} +** 07 for each Name/Value pair in Patch: +** 08 if Name exists in Target: +** 09 if Value is null: +** 10 remove the Name/Value pair from Target +** 11 else +** 12 Target[name] = MergePatch(Target[Name], Value) +** 13 else if Value is not NULL: +** 14 if Value is not an Object: +** 15 Target[name] = Value +** 16 else: +** 17 Target[name] = MergePatch('{}',value) +** 18 return Target +** | +** ^---- Line numbers referenced in comments in the implementation */ -static JsonNode *jsonMergePatch( - JsonParse *pParse, /* The JSON parser that contains the TARGET */ - u32 iTarget, /* Node of the TARGET in pParse */ - JsonNode *pPatch /* The PATCH */ +static int jsonMergePatch( + JsonParse *pTarget, /* The JSON parser that contains the TARGET */ + u32 iTarget, /* Index of TARGET in pTarget->aBlob[] */ + const JsonParse *pPatch, /* The PATCH */ + u32 iPatch /* Index of PATCH in pPatch->aBlob[] */ ){ - u32 i, j; - u32 iRoot; - JsonNode *pTarget; - if( pPatch->eType!=JSON_OBJECT ){ - return pPatch; - } - assert( iTargetnNode ); - pTarget = &pParse->aNode[iTarget]; - assert( (pPatch->jnFlags & JNODE_APPEND)==0 ); - if( pTarget->eType!=JSON_OBJECT ){ - jsonRemoveAllNulls(pPatch); - return pPatch; - } - iRoot = iTarget; - for(i=1; in; i += jsonNodeSize(&pPatch[i+1])+1){ - u32 nKey; - const char *zKey; - assert( pPatch[i].eType==JSON_STRING ); - assert( pPatch[i].jnFlags & JNODE_LABEL ); - assert( pPatch[i].eU==1 ); - nKey = pPatch[i].n; - zKey = pPatch[i].u.zJContent; - for(j=1; jn; j += jsonNodeSize(&pTarget[j+1])+1 ){ - assert( pTarget[j].eType==JSON_STRING ); - assert( pTarget[j].jnFlags & JNODE_LABEL ); - if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){ - if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ) break; - if( pPatch[i+1].eType==JSON_NULL ){ - pTarget[j+1].jnFlags |= JNODE_REMOVE; - }else{ - JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]); - if( pNew==0 ) return 0; - if( pNew!=&pParse->aNode[iTarget+j+1] ){ - jsonParseAddSubstNode(pParse, iTarget+j+1); - jsonParseAddNodeArray(pParse, pNew, jsonNodeSize(pNew)); - } - pTarget = &pParse->aNode[iTarget]; - } - break; + u8 x; /* Type of a single node */ + u32 n, sz=0; /* Return values from jsonbPayloadSize() */ + u32 iTCursor; /* Cursor position while scanning the target object */ + u32 iTStart; /* First label in the target object */ + u32 iTEndBE; /* Original first byte past end of target, before edit */ + u32 iTEnd; /* Current first byte past end of target */ + u8 eTLabel; /* Node type of the target label */ + u32 iTLabel = 0; /* Index of the label */ + u32 nTLabel = 0; /* Header size in bytes for the target label */ + u32 szTLabel = 0; /* Size of the target label payload */ + u32 iTValue = 0; /* Index of the target value */ + u32 nTValue = 0; /* Header size of the target value */ + u32 szTValue = 0; /* Payload size for the target value */ + + u32 iPCursor; /* Cursor position while scanning the patch */ + u32 iPEnd; /* First byte past the end of the patch */ + u8 ePLabel; /* Node type of the patch label */ + u32 iPLabel; /* Start of patch label */ + u32 nPLabel; /* Size of header on the patch label */ + u32 szPLabel; /* Payload size of the patch label */ + u32 iPValue; /* Start of patch value */ + u32 nPValue; /* Header size for the patch value */ + u32 szPValue; /* Payload size of the patch value */ + + assert( iTarget>=0 && iTargetnBlob ); + assert( iPatch>=0 && iPatchnBlob ); + x = pPatch->aBlob[iPatch] & 0x0f; + if( x!=JSONB_OBJECT ){ /* Algorithm line 02 */ + u32 szPatch; /* Total size of the patch, header+payload */ + u32 szTarget; /* Total size of the target, header+payload */ + n = jsonbPayloadSize(pPatch, iPatch, &sz); + szPatch = n+sz; + sz = 0; + n = jsonbPayloadSize(pTarget, iTarget, &sz); + szTarget = n+sz; + jsonBlobEdit(pTarget, iTarget, szTarget, pPatch->aBlob+iPatch, szPatch); + return pTarget->oom ? JSON_MERGE_OOM : JSON_MERGE_OK; /* Line 03 */ + } + x = pTarget->aBlob[iTarget] & 0x0f; + if( x!=JSONB_OBJECT ){ /* Algorithm line 05 */ + n = jsonbPayloadSize(pTarget, iTarget, &sz); + jsonBlobEdit(pTarget, iTarget+n, sz, 0, 0); + x = pTarget->aBlob[iTarget]; + pTarget->aBlob[iTarget] = (x & 0xf0) | JSONB_OBJECT; + } + n = jsonbPayloadSize(pPatch, iPatch, &sz); + if( NEVER(n==0) ) return JSON_MERGE_BADPATCH; + iPCursor = iPatch+n; + iPEnd = iPCursor+sz; + n = jsonbPayloadSize(pTarget, iTarget, &sz); + if( NEVER(n==0) ) return JSON_MERGE_BADTARGET; + iTStart = iTarget+n; + iTEndBE = iTStart+sz; + + while( iPCursoraBlob[iPCursor] & 0x0f; + if( ePLabelJSONB_TEXTRAW ){ + return JSON_MERGE_BADPATCH; + } + nPLabel = jsonbPayloadSize(pPatch, iPCursor, &szPLabel); + if( nPLabel==0 ) return JSON_MERGE_BADPATCH; + iPValue = iPCursor + nPLabel + szPLabel; + if( iPValue>=iPEnd ) return JSON_MERGE_BADPATCH; + nPValue = jsonbPayloadSize(pPatch, iPValue, &szPValue); + if( nPValue==0 ) return JSON_MERGE_BADPATCH; + iPCursor = iPValue + nPValue + szPValue; + if( iPCursor>iPEnd ) return JSON_MERGE_BADPATCH; + + iTCursor = iTStart; + iTEnd = iTEndBE + pTarget->delta; + while( iTCursoraBlob[iTCursor] & 0x0f; + if( eTLabelJSONB_TEXTRAW ){ + return JSON_MERGE_BADTARGET; } + nTLabel = jsonbPayloadSize(pTarget, iTCursor, &szTLabel); + if( nTLabel==0 ) return JSON_MERGE_BADTARGET; + iTValue = iTLabel + nTLabel + szTLabel; + if( iTValue>=iTEnd ) return JSON_MERGE_BADTARGET; + nTValue = jsonbPayloadSize(pTarget, iTValue, &szTValue); + if( nTValue==0 ) return JSON_MERGE_BADTARGET; + if( iTValue + nTValue + szTValue > iTEnd ) return JSON_MERGE_BADTARGET; + isEqual = jsonLabelCompare( + (const char*)&pPatch->aBlob[iPLabel+nPLabel], + szPLabel, + (ePLabel==JSONB_TEXT || ePLabel==JSONB_TEXTRAW), + (const char*)&pTarget->aBlob[iTLabel+nTLabel], + szTLabel, + (eTLabel==JSONB_TEXT || eTLabel==JSONB_TEXTRAW)); + if( isEqual ) break; + iTCursor = iTValue + nTValue + szTValue; } - if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){ - int iStart; - JsonNode *pApnd; - u32 nApnd; - iStart = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); - jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); - pApnd = &pPatch[i+1]; - if( pApnd->eType==JSON_OBJECT ) jsonRemoveAllNulls(pApnd); - nApnd = jsonNodeSize(pApnd); - jsonParseAddNodeArray(pParse, pApnd, jsonNodeSize(pApnd)); - if( pParse->oom ) return 0; - pParse->aNode[iStart].n = 1+nApnd; - pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; - pParse->aNode[iRoot].u.iAppend = iStart; - VVA( pParse->aNode[iRoot].eU = 2 ); - iRoot = iStart; - pTarget = &pParse->aNode[iTarget]; + x = pPatch->aBlob[iPValue] & 0x0f; + if( iTCursoroom) ) return JSON_MERGE_OOM; + }else{ + /* Algorithm line 12 */ + int rc, savedDelta = pTarget->delta; + pTarget->delta = 0; + rc = jsonMergePatch(pTarget, iTValue, pPatch, iPValue); + if( rc ) return rc; + pTarget->delta += savedDelta; + } + }else if( x>0 ){ /* Algorithm line 13 */ + /* No match and patch value is not NULL */ + u32 szNew = szPLabel+nPLabel; + if( (pPatch->aBlob[iPValue] & 0x0f)!=JSONB_OBJECT ){ /* Line 14 */ + jsonBlobEdit(pTarget, iTEnd, 0, 0, szPValue+nPValue+szNew); + if( pTarget->oom ) return JSON_MERGE_OOM; + memcpy(&pTarget->aBlob[iTEnd], &pPatch->aBlob[iPLabel], szNew); + memcpy(&pTarget->aBlob[iTEnd+szNew], + &pPatch->aBlob[iPValue], szPValue+nPValue); + }else{ + int rc, savedDelta; + jsonBlobEdit(pTarget, iTEnd, 0, 0, szNew+1); + if( pTarget->oom ) return JSON_MERGE_OOM; + memcpy(&pTarget->aBlob[iTEnd], &pPatch->aBlob[iPLabel], szNew); + pTarget->aBlob[iTEnd+szNew] = 0x00; + savedDelta = pTarget->delta; + pTarget->delta = 0; + rc = jsonMergePatch(pTarget, iTEnd+szNew,pPatch,iPValue); + if( rc ) return rc; + pTarget->delta += savedDelta; + } } } - return pTarget; + if( pTarget->delta ) jsonAfterEditSizeAdjust(pTarget, iTarget); + return pTarget->oom ? JSON_MERGE_OOM : JSON_MERGE_OK; } + /* ** Implementation of the json_mergepatch(JSON1,JSON2) function. Return a JSON ** object that is the result of running the RFC 7396 MergePatch() algorithm @@ -2696,28 +3962,27 @@ static void jsonPatchFunc( int argc, sqlite3_value **argv ){ - JsonParse *pX; /* The JSON that is being patched */ - JsonParse *pY; /* The patch */ - JsonNode *pResult; /* The result of the merge */ + JsonParse *pTarget; /* The TARGET */ + JsonParse *pPatch; /* The PATCH */ + int rc; /* Result code */ UNUSED_PARAMETER(argc); - pX = jsonParseCached(ctx, argv[0], ctx, 1); - if( pX==0 ) return; - assert( pX->hasMod==0 ); - pX->hasMod = 1; - pY = jsonParseCached(ctx, argv[1], ctx, 1); - if( pY==0 ) return; - pX->useMod = 1; - pY->useMod = 1; - pResult = jsonMergePatch(pX, 0, pY->aNode); - assert( pResult!=0 || pX->oom ); - if( pResult && pX->oom==0 ){ - jsonDebugPrintParse(pX); - jsonDebugPrintNode(pResult); - jsonReturnJson(pX, pResult, ctx, 0, 0); - }else{ - sqlite3_result_error_nomem(ctx); + assert( argc==2 ); + pTarget = jsonParseFuncArg(ctx, argv[0], JSON_EDITABLE); + if( pTarget==0 ) return; + pPatch = jsonParseFuncArg(ctx, argv[1], 0); + if( pPatch ){ + rc = jsonMergePatch(pTarget, 0, pPatch, 0); + if( rc==JSON_MERGE_OK ){ + jsonReturnParse(ctx, pTarget); + }else if( rc==JSON_MERGE_OOM ){ + sqlite3_result_error_nomem(ctx); + }else{ + sqlite3_result_error(ctx, "malformed JSON", -1); + } + jsonParseFree(pPatch); } + jsonParseFree(pTarget); } @@ -2741,23 +4006,23 @@ static void jsonObjectFunc( "of arguments", -1); return; } - jsonInit(&jx, ctx); + jsonStringInit(&jx, ctx); jsonAppendChar(&jx, '{'); for(i=0; i1); - if( pParse==0 ) return; - for(i=1; i<(u32)argc; i++){ + p = jsonParseFuncArg(ctx, argv[0], argc>1 ? JSON_EDITABLE : 0); + if( p==0 ) return; + for(i=1; inErr ) goto remove_done; - if( pNode ){ - pNode->jnFlags |= JNODE_REMOVE; - pParse->hasMod = 1; - pParse->useMod = 1; - } - } - if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); - } -remove_done: - jsonDebugPrintParse(p); -} - -/* -** Substitute the value at iNode with the pValue parameter. -*/ -static void jsonReplaceNode( - sqlite3_context *pCtx, - JsonParse *p, - int iNode, - sqlite3_value *pValue -){ - int idx = jsonParseAddSubstNode(p, iNode); - if( idx<=0 ){ - assert( p->oom ); - return; - } - switch( sqlite3_value_type(pValue) ){ - case SQLITE_NULL: { - jsonParseAddNode(p, JSON_NULL, 0, 0); - break; + if( zPath==0 ){ + goto json_remove_done; } - case SQLITE_FLOAT: { - char *z = sqlite3_mprintf("%!0.15g", sqlite3_value_double(pValue)); - int n; - if( z==0 ){ - p->oom = 1; - break; - } - n = sqlite3Strlen30(z); - jsonParseAddNode(p, JSON_REAL, n, z); - jsonParseAddCleanup(p, sqlite3_free, z); - break; + if( zPath[0]!='$' ){ + goto json_remove_patherror; } - case SQLITE_INTEGER: { - char *z = sqlite3_mprintf("%lld", sqlite3_value_int64(pValue)); - int n; - if( z==0 ){ - p->oom = 1; - break; - } - n = sqlite3Strlen30(z); - jsonParseAddNode(p, JSON_INT, n, z); - jsonParseAddCleanup(p, sqlite3_free, z); - - break; + if( zPath[1]==0 ){ + /* json_remove(j,'$') returns NULL */ + goto json_remove_done; } - case SQLITE_TEXT: { - const char *z = (const char*)sqlite3_value_text(pValue); - u32 n = (u32)sqlite3_value_bytes(pValue); - if( z==0 ){ - p->oom = 1; - break; - } - if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){ - char *zCopy = sqlite3_malloc64( n+1 ); - int k; - if( zCopy ){ - memcpy(zCopy, z, n); - zCopy[n] = 0; - jsonParseAddCleanup(p, sqlite3_free, zCopy); - }else{ - p->oom = 1; - sqlite3_result_error_nomem(pCtx); - } - k = jsonParseAddNode(p, JSON_STRING, n, zCopy); - assert( k>0 || p->oom ); - if( p->oom==0 ) p->aNode[k].jnFlags |= JNODE_RAW; + p->eEdit = JEDIT_DEL; + p->delta = 0; + rc = jsonLookupStep(p, 0, zPath+1, 0); + if( JSON_LOOKUP_ISERROR(rc) ){ + if( rc==JSON_LOOKUP_NOTFOUND ){ + continue; /* No-op */ + }else if( rc==JSON_LOOKUP_PATHERROR ){ + jsonBadPathError(ctx, zPath); }else{ - JsonParse *pPatch = jsonParseCached(pCtx, pValue, pCtx, 1); - if( pPatch==0 ){ - p->oom = 1; - break; - } - jsonParseAddNodeArray(p, pPatch->aNode, pPatch->nNode); - /* The nodes copied out of pPatch and into p likely contain - ** u.zJContent pointers into pPatch->zJson. So preserve the - ** content of pPatch until p is destroyed. */ - assert( pPatch->nJPRef>=1 ); - pPatch->nJPRef++; - jsonParseAddCleanup(p, (void(*)(void*))jsonParseFree, pPatch); + sqlite3_result_error(ctx, "malformed JSON", -1); } - break; - } - default: { - jsonParseAddNode(p, JSON_NULL, 0, 0); - sqlite3_result_error(pCtx, "JSON cannot hold BLOB values", -1); - p->nErr++; - break; + goto json_remove_done; } } + jsonReturnParse(ctx, p); + jsonParseFree(p); + return; + +json_remove_patherror: + jsonBadPathError(ctx, zPath); + +json_remove_done: + jsonParseFree(p); + return; } /* @@ -2900,32 +4095,12 @@ static void jsonReplaceFunc( int argc, sqlite3_value **argv ){ - JsonParse *pParse; /* The parse */ - JsonNode *pNode; - const char *zPath; - u32 i; - if( argc<1 ) return; if( (argc&1)==0 ) { jsonWrongNumArgs(ctx, "replace"); return; } - pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); - if( pParse==0 ) return; - pParse->nJPRef++; - for(i=1; i<(u32)argc; i+=2){ - zPath = (const char*)sqlite3_value_text(argv[i]); - pParse->useMod = 1; - pNode = jsonLookup(pParse, zPath, 0, ctx); - if( pParse->nErr ) goto replace_err; - if( pNode ){ - jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); - } - } - jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); -replace_err: - jsonDebugPrintParse(pParse); - jsonParseFree(pParse); + jsonInsertIntoBlob(ctx, argc, argv, JEDIT_REPL); } @@ -2946,39 +4121,16 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ - JsonParse *pParse; /* The parse */ - JsonNode *pNode; - const char *zPath; - u32 i; - int bApnd; - int bIsSet = sqlite3_user_data(ctx)!=0; + + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + int bIsSet = (flags&JSON_ISSET)!=0; if( argc<1 ) return; if( (argc&1)==0 ) { jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); return; } - pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); - if( pParse==0 ) return; - pParse->nJPRef++; - for(i=1; i<(u32)argc; i+=2){ - zPath = (const char*)sqlite3_value_text(argv[i]); - bApnd = 0; - pParse->useMod = 1; - pNode = jsonLookup(pParse, zPath, &bApnd, ctx); - if( pParse->oom ){ - sqlite3_result_error_nomem(ctx); - goto jsonSetDone; - }else if( pParse->nErr ){ - goto jsonSetDone; - }else if( pNode && (bApnd || bIsSet) ){ - jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); - } - } - jsonDebugPrintParse(pParse); - jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); -jsonSetDone: - jsonParseFree(pParse); + jsonInsertIntoBlob(ctx, argc, argv, bIsSet ? JEDIT_SET : JEDIT_INS); } /* @@ -2994,27 +4146,93 @@ static void jsonTypeFunc( sqlite3_value **argv ){ JsonParse *p; /* The parse */ - const char *zPath; - JsonNode *pNode; + const char *zPath = 0; + u32 i; - p = jsonParseCached(ctx, argv[0], ctx, 0); + p = jsonParseFuncArg(ctx, argv[0], 0); if( p==0 ) return; if( argc==2 ){ zPath = (const char*)sqlite3_value_text(argv[1]); - pNode = jsonLookup(p, zPath, 0, ctx); + if( zPath==0 ) goto json_type_done; + if( zPath[0]!='$' ){ + jsonBadPathError(ctx, zPath); + goto json_type_done; + } + i = jsonLookupStep(p, 0, zPath+1, 0); + if( JSON_LOOKUP_ISERROR(i) ){ + if( i==JSON_LOOKUP_NOTFOUND ){ + /* no-op */ + }else if( i==JSON_LOOKUP_PATHERROR ){ + jsonBadPathError(ctx, zPath); + }else{ + sqlite3_result_error(ctx, "malformed JSON", -1); + } + goto json_type_done; + } }else{ - pNode = p->aNode; - } - if( pNode ){ - sqlite3_result_text(ctx, jsonType[pNode->eType], -1, SQLITE_STATIC); + i = 0; } + sqlite3_result_text(ctx, jsonbType[p->aBlob[i]&0x0f], -1, SQLITE_STATIC); +json_type_done: + jsonParseFree(p); } /* ** json_valid(JSON) +** json_valid(JSON, FLAGS) +** +** Check the JSON argument to see if it is well-formed. The FLAGS argument +** encodes the various constraints on what is meant by "well-formed": +** +** 0x01 Canonical RFC-8259 JSON text +** 0x02 JSON text with optional JSON-5 extensions +** 0x04 Superficially appears to be JSONB +** 0x08 Strictly well-formed JSONB +** +** If the FLAGS argument is omitted, it defaults to 1. Useful values for +** FLAGS include: +** +** 1 Strict canonical JSON text +** 2 JSON text perhaps with JSON-5 extensions +** 4 Superficially appears to be JSONB +** 5 Canonical JSON text or superficial JSONB +** 6 JSON-5 text or superficial JSONB +** 8 Strict JSONB +** 9 Canonical JSON text or strict JSONB +** 10 JSON-5 text or strict JSONB +** +** Other flag combinations are redundant. For example, every canonical +** JSON text is also well-formed JSON-5 text, so FLAG values 2 and 3 +** are the same. Similarly, any input that passes a strict JSONB validation +** will also pass the superficial validation so 12 through 15 are the same +** as 8 through 11 respectively. +** +** This routine runs in linear time to validate text and when doing strict +** JSONB validation. Superficial JSONB validation is constant time, +** assuming the BLOB is already in memory. The performance advantage +** of superficial JSONB validation is why that option is provided. +** Application developers can choose to do fast superficial validation or +** slower strict validation, according to their specific needs. ** -** Return 1 if JSON is a well-formed canonical JSON string according -** to RFC-7159. Return 0 otherwise. +** Only the lower four bits of the FLAGS argument are currently used. +** Higher bits are reserved for future expansion. To facilitate +** compatibility, the current implementation raises an error if any bit +** in FLAGS is set other than the lower four bits. +** +** The original circa 2015 implementation of the JSON routines in +** SQLite only supported canonical RFC-8259 JSON text and the json_valid() +** function only accepted one argument. That is why the default value +** for the FLAGS argument is 1, since FLAGS=1 causes this routine to only +** recognize canonical RFC-8259 JSON text as valid. The extra FLAGS +** argument was added when the JSON routines were extended to support +** JSON5-like extensions and binary JSONB stored in BLOBs. +** +** Return Values: +** +** * Raise an error if FLAGS is outside the range of 1 to 15. +** * Return NULL if the input is NULL +** * Return 1 if the input is well-formed. +** * Return 0 if the input is not well-formed. */ static void jsonValidFunc( sqlite3_context *ctx, @@ -3022,79 +4240,125 @@ static void jsonValidFunc( sqlite3_value **argv ){ JsonParse *p; /* The parse */ - UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ + u8 flags = 1; + u8 res = 0; + if( argc==2 ){ + i64 f = sqlite3_value_int64(argv[1]); + if( f<1 || f>15 ){ + sqlite3_result_error(ctx, "FLAGS parameter to json_valid() must be" + " between 1 and 15", -1); + return; + } + flags = f & 0x0f; + } + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_NULL: { #ifdef SQLITE_LEGACY_JSON_VALID - /* Incorrect legacy behavior was to return FALSE for a NULL input */ - sqlite3_result_int(ctx, 0); + /* Incorrect legacy behavior was to return FALSE for a NULL input */ + sqlite3_result_int(ctx, 0); #endif - return; - } - p = jsonParseCached(ctx, argv[0], 0, 0); - if( p==0 || p->oom ){ - sqlite3_result_error_nomem(ctx); - sqlite3_free(p); - }else{ - sqlite3_result_int(ctx, p->nErr==0 && (p->hasNonstd==0 || p->useMod)); - if( p->nErr ) jsonParseFree(p); + return; + } + case SQLITE_BLOB: { + if( (flags & 0x0c)!=0 && jsonFuncArgMightBeBinary(argv[0]) ){ + if( flags & 0x04 ){ + /* Superficial checking only - accomplished by the + ** jsonFuncArgMightBeBinary() call above. */ + res = 1; + }else{ + /* Strict checking. Check by translating BLOB->TEXT->BLOB. If + ** no errors occur, call that a "strict check". */ + JsonParse px; + u32 iErr; + memset(&px, 0, sizeof(px)); + px.aBlob = (u8*)sqlite3_value_blob(argv[0]); + px.nBlob = sqlite3_value_bytes(argv[0]); + iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1); + res = iErr==0; + } + } + break; + } + default: { + JsonParse px; + if( (flags & 0x3)==0 ) break; + memset(&px, 0, sizeof(px)); + + p = jsonParseFuncArg(ctx, argv[0], JSON_KEEPERROR); + if( p ){ + if( p->oom ){ + sqlite3_result_error_nomem(ctx); + }else if( p->nErr ){ + /* no-op */ + }else if( (flags & 0x02)!=0 || p->hasNonstd==0 ){ + res = 1; + } + jsonParseFree(p); + }else{ + sqlite3_result_error_nomem(ctx); + } + break; + } } + sqlite3_result_int(ctx, res); } /* ** json_error_position(JSON) ** -** If the argument is not an interpretable JSON string, then return the 1-based -** character position at which the parser first recognized that the input -** was in error. The left-most character is 1. If the string is valid -** JSON, then return 0. -** -** Note that json_valid() is only true for strictly conforming canonical JSON. -** But this routine returns zero if the input contains extension. Thus: -** -** (1) If the input X is strictly conforming canonical JSON: +** If the argument is NULL, return NULL ** -** json_valid(X) returns true -** json_error_position(X) returns 0 +** If the argument is BLOB, do a full validity check and return non-zero +** if the check fails. The return value is the approximate 1-based offset +** to the byte of the element that contains the first error. ** -** (2) If the input X is JSON but it includes extension (such as JSON5) that -** are not part of RFC-8259: -** -** json_valid(X) returns false -** json_error_position(X) return 0 -** -** (3) If the input X cannot be interpreted as JSON even taking extensions -** into account: -** -** json_valid(X) return false -** json_error_position(X) returns 1 or more +** Otherwise interpret the argument is TEXT (even if it is numeric) and +** return the 1-based character position for where the parser first recognized +** that the input was not valid JSON, or return 0 if the input text looks +** ok. JSON-5 extensions are accepted. */ static void jsonErrorFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ - JsonParse *p; /* The parse */ + i64 iErrPos = 0; /* Error position to be returned */ + JsonParse s; + + assert( argc==1 ); UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - p = jsonParseCached(ctx, argv[0], 0, 0); - if( p==0 || p->oom ){ - sqlite3_result_error_nomem(ctx); - sqlite3_free(p); - }else if( p->nErr==0 ){ - sqlite3_result_int(ctx, 0); + memset(&s, 0, sizeof(s)); + s.db = sqlite3_context_db_handle(ctx); + if( jsonFuncArgMightBeBinary(argv[0]) ){ + s.aBlob = (u8*)sqlite3_value_blob(argv[0]); + s.nBlob = sqlite3_value_bytes(argv[0]); + iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1); }else{ - int n = 1; - u32 i; - const char *z = (const char*)sqlite3_value_text(argv[0]); - for(i=0; iiErr && ALWAYS(z[i]); i++){ - if( (z[i]&0xc0)!=0x80 ) n++; + s.zJson = (char*)sqlite3_value_text(argv[0]); + if( s.zJson==0 ) return; /* NULL input or OOM */ + s.nJson = sqlite3_value_bytes(argv[0]); + if( jsonConvertTextToBlob(&s,0) ){ + if( s.oom ){ + iErrPos = -1; + }else{ + /* Convert byte-offset s.iErr into a character offset */ + u32 k; + assert( s.zJson!=0 ); /* Because s.oom is false */ + for(k=0; kzBuf==0 ){ - jsonInit(pStr, ctx); + jsonStringInit(pStr, ctx); jsonAppendChar(pStr, '['); }else if( pStr->nUsed>1 ){ jsonAppendChar(pStr, ','); } pStr->pCtx = ctx; - jsonAppendValue(pStr, argv[0]); + jsonAppendSqlValue(pStr, argv[0]); } } static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ + int flags; pStr->pCtx = ctx; jsonAppendChar(pStr, ']'); - if( pStr->bErr ){ - if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); - assert( pStr->bStatic ); + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + if( pStr->eErr ){ + jsonReturnString(pStr, 0, 0); + return; + }else if( flags & JSON_BLOB ){ + jsonReturnStringAsBlob(pStr); + if( isFinal ){ + if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf); + }else{ + pStr->nUsed--; + } + return; }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, pStr->bStatic ? SQLITE_TRANSIENT : @@ -3219,27 +4493,38 @@ static void jsonObjectStep( pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr)); if( pStr ){ if( pStr->zBuf==0 ){ - jsonInit(pStr, ctx); + jsonStringInit(pStr, ctx); jsonAppendChar(pStr, '{'); }else if( pStr->nUsed>1 ){ jsonAppendChar(pStr, ','); } pStr->pCtx = ctx; z = (const char*)sqlite3_value_text(argv[0]); - n = (u32)sqlite3_value_bytes(argv[0]); + n = sqlite3Strlen30(z); jsonAppendString(pStr, z, n); jsonAppendChar(pStr, ':'); - jsonAppendValue(pStr, argv[1]); + jsonAppendSqlValue(pStr, argv[1]); } } static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ + int flags; jsonAppendChar(pStr, '}'); - if( pStr->bErr ){ - if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); - assert( pStr->bStatic ); + pStr->pCtx = ctx; + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + if( pStr->eErr ){ + jsonReturnString(pStr, 0, 0); + return; + }else if( flags & JSON_BLOB ){ + jsonReturnStringAsBlob(pStr); + if( isFinal ){ + if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf); + }else{ + pStr->nUsed--; + } + return; }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, pStr->bStatic ? SQLITE_TRANSIENT : @@ -3267,19 +4552,37 @@ static void jsonObjectFinal(sqlite3_context *ctx){ /**************************************************************************** ** The json_each virtual table ****************************************************************************/ +typedef struct JsonParent JsonParent; +struct JsonParent { + u32 iHead; /* Start of object or array */ + u32 iValue; /* Start of the value */ + u32 iEnd; /* First byte past the end */ + u32 nPath; /* Length of path */ + i64 iKey; /* Key for JSONB_ARRAY */ +}; + typedef struct JsonEachCursor JsonEachCursor; struct JsonEachCursor { sqlite3_vtab_cursor base; /* Base class - must be first */ u32 iRowid; /* The rowid */ - u32 iBegin; /* The first node of the scan */ - u32 i; /* Index in sParse.aNode[] of current row */ + u32 i; /* Index in sParse.aBlob[] of current row */ u32 iEnd; /* EOF when i equals or exceeds this value */ - u8 eType; /* Type of top-level element */ + u32 nRoot; /* Size of the root path in bytes */ + u8 eType; /* Type of the container for element i */ u8 bRecursive; /* True for json_tree(). False for json_each() */ - char *zJson; /* Input JSON */ - char *zRoot; /* Path by which to filter zJson */ + u32 nParent; /* Current nesting depth */ + u32 nParentAlloc; /* Space allocated for aParent[] */ + JsonParent *aParent; /* Parent elements of i */ + sqlite3 *db; /* Database connection */ + JsonString path; /* Current path */ JsonParse sParse; /* Parse of the input JSON */ }; +typedef struct JsonEachConnection JsonEachConnection; +struct JsonEachConnection { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection */ +}; + /* Constructor for the json_each virtual table */ static int jsonEachConnect( @@ -3289,7 +4592,7 @@ static int jsonEachConnect( sqlite3_vtab **ppVtab, char **pzErr ){ - sqlite3_vtab *pNew; + JsonEachConnection *pNew; int rc; /* Column numbers */ @@ -3315,28 +4618,32 @@ static int jsonEachConnect( "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path," "json HIDDEN,root HIDDEN)"); if( rc==SQLITE_OK ){ - pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + pNew = (JsonEachConnection*)sqlite3DbMallocZero(db, sizeof(*pNew)); + *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; - memset(pNew, 0, sizeof(*pNew)); sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + pNew->db = db; } return rc; } /* destructor for json_each virtual table */ static int jsonEachDisconnect(sqlite3_vtab *pVtab){ - sqlite3_free(pVtab); + JsonEachConnection *p = (JsonEachConnection*)pVtab; + sqlite3DbFree(p->db, pVtab); return SQLITE_OK; } /* constructor for a JsonEachCursor object for json_each(). */ static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + JsonEachConnection *pVtab = (JsonEachConnection*)p; JsonEachCursor *pCur; UNUSED_PARAMETER(p); - pCur = sqlite3_malloc( sizeof(*pCur) ); + pCur = sqlite3DbMallocZero(pVtab->db, sizeof(*pCur)); if( pCur==0 ) return SQLITE_NOMEM; - memset(pCur, 0, sizeof(*pCur)); + pCur->db = pVtab->db; + jsonStringZero(&pCur->path); *ppCursor = &pCur->base; return SQLITE_OK; } @@ -3354,21 +4661,24 @@ static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ /* Reset a JsonEachCursor back to its original state. Free any memory ** held. */ static void jsonEachCursorReset(JsonEachCursor *p){ - sqlite3_free(p->zRoot); jsonParseReset(&p->sParse); + jsonStringReset(&p->path); + sqlite3DbFree(p->db, p->aParent); p->iRowid = 0; p->i = 0; + p->aParent = 0; + p->nParent = 0; + p->nParentAlloc = 0; p->iEnd = 0; p->eType = 0; - p->zJson = 0; - p->zRoot = 0; } /* Destructor for a jsonEachCursor object */ static int jsonEachClose(sqlite3_vtab_cursor *cur){ JsonEachCursor *p = (JsonEachCursor*)cur; jsonEachCursorReset(p); - sqlite3_free(cur); + + sqlite3DbFree(p->db, cur); return SQLITE_OK; } @@ -3379,200 +4689,230 @@ static int jsonEachEof(sqlite3_vtab_cursor *cur){ return p->i >= p->iEnd; } +/* +** If the cursor is currently pointing at the label of a object entry, +** then return the index of the value. For all other cases, return the +** current pointer position, which is the value. +*/ +static int jsonSkipLabel(JsonEachCursor *p){ + if( p->eType==JSONB_OBJECT ){ + u32 sz = 0; + u32 n = jsonbPayloadSize(&p->sParse, p->i, &sz); + return p->i + n + sz; + }else{ + return p->i; + } +} + +/* +** Append the path name for the current element. +*/ +static void jsonAppendPathName(JsonEachCursor *p){ + assert( p->nParent>0 ); + assert( p->eType==JSONB_ARRAY || p->eType==JSONB_OBJECT ); + if( p->eType==JSONB_ARRAY ){ + jsonPrintf(30, &p->path, "[%lld]", p->aParent[p->nParent-1].iKey); + }else{ + u32 n, sz = 0, k, i; + const char *z; + int needQuote = 0; + n = jsonbPayloadSize(&p->sParse, p->i, &sz); + k = p->i + n; + z = (const char*)&p->sParse.aBlob[k]; + if( sz==0 || !sqlite3Isalpha(z[0]) ){ + needQuote = 1; + }else{ + for(i=0; ipath,".\"%.*s\"", sz, z); + }else{ + jsonPrintf(sz+2,&p->path,".%.*s", sz, z); + } + } +} + /* Advance the cursor to the next element for json_tree() */ static int jsonEachNext(sqlite3_vtab_cursor *cur){ JsonEachCursor *p = (JsonEachCursor*)cur; + int rc = SQLITE_OK; if( p->bRecursive ){ - if( p->sParse.aNode[p->i].jnFlags & JNODE_LABEL ) p->i++; - p->i++; - p->iRowid++; - if( p->iiEnd ){ - u32 iUp = p->sParse.aUp[p->i]; - JsonNode *pUp = &p->sParse.aNode[iUp]; - p->eType = pUp->eType; - if( pUp->eType==JSON_ARRAY ){ - assert( pUp->eU==0 || pUp->eU==3 ); - testcase( pUp->eU==3 ); - VVA( pUp->eU = 3 ); - if( iUp==p->i-1 ){ - pUp->u.iKey = 0; - }else{ - pUp->u.iKey++; - } - } - } - }else{ - switch( p->eType ){ - case JSON_ARRAY: { - p->i += jsonNodeSize(&p->sParse.aNode[p->i]); - p->iRowid++; - break; + u8 x; + u8 levelChange = 0; + u32 n, sz = 0; + u32 i = jsonSkipLabel(p); + x = p->sParse.aBlob[i] & 0x0f; + n = jsonbPayloadSize(&p->sParse, i, &sz); + if( x==JSONB_OBJECT || x==JSONB_ARRAY ){ + JsonParent *pParent; + if( p->nParent>=p->nParentAlloc ){ + JsonParent *pNew; + u64 nNew; + nNew = p->nParentAlloc*2 + 3; + pNew = sqlite3DbRealloc(p->db, p->aParent, sizeof(JsonParent)*nNew); + if( pNew==0 ) return SQLITE_NOMEM; + p->nParentAlloc = (u32)nNew; + p->aParent = pNew; } - case JSON_OBJECT: { - p->i += 1 + jsonNodeSize(&p->sParse.aNode[p->i+1]); - p->iRowid++; - break; + levelChange = 1; + pParent = &p->aParent[p->nParent]; + pParent->iHead = p->i; + pParent->iValue = i; + pParent->iEnd = i + n + sz; + pParent->iKey = -1; + pParent->nPath = (u32)p->path.nUsed; + if( p->eType && p->nParent ){ + jsonAppendPathName(p); + if( p->path.eErr ) rc = SQLITE_NOMEM; } - default: { - p->i = p->iEnd; - break; + p->nParent++; + p->i = i + n; + }else{ + p->i = i + n + sz; + } + while( p->nParent>0 && p->i >= p->aParent[p->nParent-1].iEnd ){ + p->nParent--; + p->path.nUsed = p->aParent[p->nParent].nPath; + levelChange = 1; + } + if( levelChange ){ + if( p->nParent>0 ){ + JsonParent *pParent = &p->aParent[p->nParent-1]; + u32 iVal = pParent->iValue; + p->eType = p->sParse.aBlob[iVal] & 0x0f; + }else{ + p->eType = 0; } } + }else{ + u32 n, sz = 0; + u32 i = jsonSkipLabel(p); + n = jsonbPayloadSize(&p->sParse, i, &sz); + p->i = i + n + sz; } - return SQLITE_OK; + if( p->eType==JSONB_ARRAY && p->nParent ){ + p->aParent[p->nParent-1].iKey++; + } + p->iRowid++; + return rc; } -/* Append an object label to the JSON Path being constructed -** in pStr. +/* Length of the path for rowid==0 in bRecursive mode. */ -static void jsonAppendObjectPathElement( - JsonString *pStr, - JsonNode *pNode -){ - int jj, nn; - const char *z; - assert( pNode->eType==JSON_STRING ); - assert( pNode->jnFlags & JNODE_LABEL ); - assert( pNode->eU==1 ); - z = pNode->u.zJContent; - nn = pNode->n; - if( (pNode->jnFlags & JNODE_RAW)==0 ){ - assert( nn>=2 ); - assert( z[0]=='"' || z[0]=='\'' ); - assert( z[nn-1]=='"' || z[0]=='\'' ); - if( nn>2 && sqlite3Isalpha(z[1]) ){ - for(jj=2; jjpath.nUsed; + char *z = p->path.zBuf; + if( p->iRowid==0 && p->bRecursive && n>=2 ){ + while( n>1 ){ + n--; + if( z[n]=='[' || z[n]=='.' ){ + u32 x, sz = 0; + char cSaved = z[n]; + z[n] = 0; + assert( p->sParse.eEdit==0 ); + x = jsonLookupStep(&p->sParse, 0, z+1, 0); + z[n] = cSaved; + if( JSON_LOOKUP_ISERROR(x) ) continue; + if( x + jsonbPayloadSize(&p->sParse, x, &sz) == p->i ) break; } } } - jsonPrintf(nn+2, pStr, ".%.*s", nn, z); -} - -/* Append the name of the path for element i to pStr -*/ -static void jsonEachComputePath( - JsonEachCursor *p, /* The cursor */ - JsonString *pStr, /* Write the path here */ - u32 i /* Path to this element */ -){ - JsonNode *pNode, *pUp; - u32 iUp; - if( i==0 ){ - jsonAppendChar(pStr, '$'); - return; - } - iUp = p->sParse.aUp[i]; - jsonEachComputePath(p, pStr, iUp); - pNode = &p->sParse.aNode[i]; - pUp = &p->sParse.aNode[iUp]; - if( pUp->eType==JSON_ARRAY ){ - assert( pUp->eU==3 || (pUp->eU==0 && pUp->u.iKey==0) ); - testcase( pUp->eU==0 ); - jsonPrintf(30, pStr, "[%d]", pUp->u.iKey); - }else{ - assert( pUp->eType==JSON_OBJECT ); - if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--; - jsonAppendObjectPathElement(pStr, pNode); - } + return n; } /* Return the value of a column */ static int jsonEachColumn( sqlite3_vtab_cursor *cur, /* The cursor */ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ - int i /* Which column to return */ + int iColumn /* Which column to return */ ){ JsonEachCursor *p = (JsonEachCursor*)cur; - JsonNode *pThis = &p->sParse.aNode[p->i]; - switch( i ){ + switch( iColumn ){ case JEACH_KEY: { - if( p->i==0 ) break; - if( p->eType==JSON_OBJECT ){ - jsonReturn(&p->sParse, pThis, ctx, 0); - }else if( p->eType==JSON_ARRAY ){ - u32 iKey; - if( p->bRecursive ){ - if( p->iRowid==0 ) break; - assert( p->sParse.aNode[p->sParse.aUp[p->i]].eU==3 ); - iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey; + if( p->nParent==0 ){ + u32 n, j; + if( p->nRoot==1 ) break; + j = jsonEachPathLength(p); + n = p->nRoot - j; + if( n==0 ){ + break; + }else if( p->path.zBuf[j]=='[' ){ + i64 x; + sqlite3Atoi64(&p->path.zBuf[j+1], &x, n-1, SQLITE_UTF8); + sqlite3_result_int64(ctx, x); + }else if( p->path.zBuf[j+1]=='"' ){ + sqlite3_result_text(ctx, &p->path.zBuf[j+2], n-3, SQLITE_TRANSIENT); }else{ - iKey = p->iRowid; + sqlite3_result_text(ctx, &p->path.zBuf[j+1], n-1, SQLITE_TRANSIENT); } - sqlite3_result_int64(ctx, (sqlite3_int64)iKey); + break; + } + if( p->eType==JSONB_OBJECT ){ + jsonReturnFromBlob(&p->sParse, p->i, ctx, 1); + }else{ + assert( p->eType==JSONB_ARRAY ); + sqlite3_result_int64(ctx, p->aParent[p->nParent-1].iKey); } break; } case JEACH_VALUE: { - if( pThis->jnFlags & JNODE_LABEL ) pThis++; - jsonReturn(&p->sParse, pThis, ctx, 0); + u32 i = jsonSkipLabel(p); + jsonReturnFromBlob(&p->sParse, i, ctx, 1); break; } case JEACH_TYPE: { - if( pThis->jnFlags & JNODE_LABEL ) pThis++; - sqlite3_result_text(ctx, jsonType[pThis->eType], -1, SQLITE_STATIC); + u32 i = jsonSkipLabel(p); + u8 eType = p->sParse.aBlob[i] & 0x0f; + sqlite3_result_text(ctx, jsonbType[eType], -1, SQLITE_STATIC); break; } case JEACH_ATOM: { - if( pThis->jnFlags & JNODE_LABEL ) pThis++; - if( pThis->eType>=JSON_ARRAY ) break; - jsonReturn(&p->sParse, pThis, ctx, 0); + u32 i = jsonSkipLabel(p); + if( (p->sParse.aBlob[i] & 0x0f)sParse, i, ctx, 1); + } break; } case JEACH_ID: { - sqlite3_result_int64(ctx, - (sqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0)); + sqlite3_result_int64(ctx, (sqlite3_int64)p->i); break; } case JEACH_PARENT: { - if( p->i>p->iBegin && p->bRecursive ){ - sqlite3_result_int64(ctx, (sqlite3_int64)p->sParse.aUp[p->i]); + if( p->nParent>0 && p->bRecursive ){ + sqlite3_result_int64(ctx, p->aParent[p->nParent-1].iHead); } break; } case JEACH_FULLKEY: { - JsonString x; - jsonInit(&x, ctx); - if( p->bRecursive ){ - jsonEachComputePath(p, &x, p->i); - }else{ - if( p->zRoot ){ - jsonAppendRaw(&x, p->zRoot, (int)strlen(p->zRoot)); - }else{ - jsonAppendChar(&x, '$'); - } - if( p->eType==JSON_ARRAY ){ - jsonPrintf(30, &x, "[%d]", p->iRowid); - }else if( p->eType==JSON_OBJECT ){ - jsonAppendObjectPathElement(&x, pThis); - } - } - jsonResult(&x); + u64 nBase = p->path.nUsed; + if( p->nParent ) jsonAppendPathName(p); + sqlite3_result_text64(ctx, p->path.zBuf, p->path.nUsed, + SQLITE_TRANSIENT, SQLITE_UTF8); + p->path.nUsed = nBase; break; } case JEACH_PATH: { - if( p->bRecursive ){ - JsonString x; - jsonInit(&x, ctx); - jsonEachComputePath(p, &x, p->sParse.aUp[p->i]); - jsonResult(&x); - break; - } - /* For json_each() path and root are the same so fall through - ** into the root case */ - /* no break */ deliberate_fall_through + u32 n = jsonEachPathLength(p); + sqlite3_result_text64(ctx, p->path.zBuf, n, + SQLITE_TRANSIENT, SQLITE_UTF8); + break; } default: { - const char *zRoot = p->zRoot; - if( zRoot==0 ) zRoot = "$"; - sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC); + sqlite3_result_text(ctx, p->path.zBuf, p->nRoot, SQLITE_STATIC); break; } case JEACH_JSON: { - assert( i==JEACH_JSON ); - sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC); + if( p->sParse.zJson==0 ){ + sqlite3_result_blob(ctx, p->sParse.aBlob, p->sParse.nBlob, + SQLITE_STATIC); + }else{ + sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC); + } break; } } @@ -3663,86 +5003,101 @@ static int jsonEachFilter( int argc, sqlite3_value **argv ){ JsonEachCursor *p = (JsonEachCursor*)cur; - const char *z; const char *zRoot = 0; - sqlite3_int64 n; + u32 i, n, sz; UNUSED_PARAMETER(idxStr); UNUSED_PARAMETER(argc); jsonEachCursorReset(p); if( idxNum==0 ) return SQLITE_OK; - z = (const char*)sqlite3_value_text(argv[0]); - if( z==0 ) return SQLITE_OK; memset(&p->sParse, 0, sizeof(p->sParse)); p->sParse.nJPRef = 1; - if( sqlite3ValueIsOfClass(argv[0], sqlite3RCStrUnref) ){ - p->sParse.zJson = sqlite3RCStrRef((char*)z); - }else{ - n = sqlite3_value_bytes(argv[0]); - p->sParse.zJson = sqlite3RCStrNew( n+1 ); - if( p->sParse.zJson==0 ) return SQLITE_NOMEM; - memcpy(p->sParse.zJson, z, (size_t)n+1); - } - p->sParse.bJsonIsRCStr = 1; - p->zJson = p->sParse.zJson; - if( jsonParse(&p->sParse, 0) ){ - int rc = SQLITE_NOMEM; - if( p->sParse.oom==0 ){ - sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON"); - if( cur->pVtab->zErrMsg ) rc = SQLITE_ERROR; - } - jsonEachCursorReset(p); - return rc; - }else if( p->bRecursive && jsonParseFindParents(&p->sParse) ){ - jsonEachCursorReset(p); - return SQLITE_NOMEM; + p->sParse.db = p->db; + if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){ + if( jsonFuncArgMightBeBinary(argv[0]) ){ + p->sParse.nBlob = sqlite3_value_bytes(argv[0]); + p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]); + }else{ + goto json_each_malformed_input; + } }else{ - JsonNode *pNode = 0; - if( idxNum==3 ){ - const char *zErr = 0; - zRoot = (const char*)sqlite3_value_text(argv[1]); - if( zRoot==0 ) return SQLITE_OK; - n = sqlite3_value_bytes(argv[1]); - p->zRoot = sqlite3_malloc64( n+1 ); - if( p->zRoot==0 ) return SQLITE_NOMEM; - memcpy(p->zRoot, zRoot, (size_t)n+1); - if( zRoot[0]!='$' ){ - zErr = zRoot; - }else{ - pNode = jsonLookupStep(&p->sParse, 0, p->zRoot+1, 0, &zErr); + p->sParse.zJson = (char*)sqlite3_value_text(argv[0]); + p->sParse.nJson = sqlite3_value_bytes(argv[0]); + if( p->sParse.zJson==0 ){ + p->i = p->iEnd = 0; + return SQLITE_OK; + } + if( jsonConvertTextToBlob(&p->sParse, 0) ){ + if( p->sParse.oom ){ + return SQLITE_NOMEM; } - if( zErr ){ + goto json_each_malformed_input; + } + } + if( idxNum==3 ){ + zRoot = (const char*)sqlite3_value_text(argv[1]); + if( zRoot==0 ) return SQLITE_OK; + if( zRoot[0]!='$' ){ + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); + jsonEachCursorReset(p); + return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; + } + p->nRoot = sqlite3Strlen30(zRoot); + if( zRoot[1]==0 ){ + i = p->i = 0; + p->eType = 0; + }else{ + i = jsonLookupStep(&p->sParse, 0, zRoot+1, 0); + if( JSON_LOOKUP_ISERROR(i) ){ + if( i==JSON_LOOKUP_NOTFOUND ){ + p->i = 0; + p->eType = 0; + p->iEnd = 0; + return SQLITE_OK; + } sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonPathSyntaxError(zErr); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; - }else if( pNode==0 ){ - return SQLITE_OK; } - }else{ - pNode = p->sParse.aNode; - } - p->iBegin = p->i = (int)(pNode - p->sParse.aNode); - p->eType = pNode->eType; - if( p->eType>=JSON_ARRAY ){ - assert( pNode->eU==0 ); - VVA( pNode->eU = 3 ); - pNode->u.iKey = 0; - p->iEnd = p->i + pNode->n + 1; - if( p->bRecursive ){ - p->eType = p->sParse.aNode[p->sParse.aUp[p->i]].eType; - if( p->i>0 && (p->sParse.aNode[p->i-1].jnFlags & JNODE_LABEL)!=0 ){ - p->i--; - } + if( p->sParse.iLabel ){ + p->i = p->sParse.iLabel; + p->eType = JSONB_OBJECT; }else{ - p->i++; + p->i = i; + p->eType = JSONB_ARRAY; } - }else{ - p->iEnd = p->i+1; } + jsonAppendRaw(&p->path, zRoot, p->nRoot); + }else{ + i = p->i = 0; + p->eType = 0; + p->nRoot = 1; + jsonAppendRaw(&p->path, "$", 1); + } + p->nParent = 0; + n = jsonbPayloadSize(&p->sParse, i, &sz); + p->iEnd = i+n+sz; + if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY && !p->bRecursive ){ + p->i = i + n; + p->eType = p->sParse.aBlob[i] & 0x0f; + p->aParent = sqlite3DbMallocZero(p->db, sizeof(JsonParent)); + if( p->aParent==0 ) return SQLITE_NOMEM; + p->nParent = 1; + p->nParentAlloc = 1; + p->aParent[0].iKey = 0; + p->aParent[0].iEnd = p->iEnd; + p->aParent[0].iHead = p->i; + p->aParent[0].iValue = i; } return SQLITE_OK; + +json_each_malformed_input: + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON"); + jsonEachCursorReset(p); + return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } /* The methods of the json_each virtual table */ @@ -3811,40 +5166,54 @@ static sqlite3_module jsonTreeModule = { void sqlite3RegisterJsonFunctions(void){ #ifndef SQLITE_OMIT_JSON static FuncDef aJsonFunc[] = { - /* calls sqlite3_result_subtype() */ - /* | */ - /* Uses cache ______ | __ calls sqlite3_value_subtype() */ - /* | | | */ - /* Num args _________ | | | ___ Flags */ - /* | | | | | */ - /* | | | | | */ - JFUNCTION(json, 1, 1, 1, 0, 0, jsonRemoveFunc), - JFUNCTION(json_array, -1, 0, 1, 1, 0, jsonArrayFunc), - JFUNCTION(json_array_length, 1, 1, 0, 0, 0, jsonArrayLengthFunc), - JFUNCTION(json_array_length, 2, 1, 0, 0, 0, jsonArrayLengthFunc), - JFUNCTION(json_error_position,1, 1, 0, 0, 0, jsonErrorFunc), - JFUNCTION(json_extract, -1, 1, 1, 0, 0, jsonExtractFunc), - JFUNCTION(->, 2, 1, 1, 0, JSON_JSON, jsonExtractFunc), - JFUNCTION(->>, 2, 1, 0, 0, JSON_SQL, jsonExtractFunc), - JFUNCTION(json_insert, -1, 1, 1, 1, 0, jsonSetFunc), - JFUNCTION(json_object, -1, 0, 1, 1, 0, jsonObjectFunc), - JFUNCTION(json_patch, 2, 1, 1, 0, 0, jsonPatchFunc), - JFUNCTION(json_quote, 1, 0, 1, 1, 0, jsonQuoteFunc), - JFUNCTION(json_remove, -1, 1, 1, 0, 0, jsonRemoveFunc), - JFUNCTION(json_replace, -1, 1, 1, 1, 0, jsonReplaceFunc), - JFUNCTION(json_set, -1, 1, 1, 1, JSON_ISSET, jsonSetFunc), - JFUNCTION(json_type, 1, 1, 0, 0, 0, jsonTypeFunc), - JFUNCTION(json_type, 2, 1, 0, 0, 0, jsonTypeFunc), - JFUNCTION(json_valid, 1, 1, 0, 0, 0, jsonValidFunc), -#ifdef SQLITE_DEBUG - JFUNCTION(json_parse, 1, 1, 1, 0, 0, jsonParseFunc), - JFUNCTION(json_test1, 1, 1, 0, 1, 0, jsonTest1Func), + /* sqlite3_result_subtype() ----, ,--- sqlite3_value_subtype() */ + /* | | */ + /* Uses cache ------, | | ,---- Returns JSONB */ + /* | | | | */ + /* Number of arguments ---, | | | | ,--- Flags */ + /* | | | | | | */ + JFUNCTION(json, 1,1,1, 0,0,0, jsonRemoveFunc), + JFUNCTION(jsonb, 1,1,0, 0,1,0, jsonRemoveFunc), + JFUNCTION(json_array, -1,0,1, 1,0,0, jsonArrayFunc), + JFUNCTION(jsonb_array, -1,0,1, 1,1,0, jsonArrayFunc), + JFUNCTION(json_array_length, 1,1,0, 0,0,0, jsonArrayLengthFunc), + JFUNCTION(json_array_length, 2,1,0, 0,0,0, jsonArrayLengthFunc), + JFUNCTION(json_error_position,1,1,0, 0,0,0, jsonErrorFunc), + JFUNCTION(json_extract, -1,1,1, 0,0,0, jsonExtractFunc), + JFUNCTION(jsonb_extract, -1,1,0, 0,1,0, jsonExtractFunc), + JFUNCTION(->, 2,1,1, 0,0,JSON_JSON, jsonExtractFunc), + JFUNCTION(->>, 2,1,0, 0,0,JSON_SQL, jsonExtractFunc), + JFUNCTION(json_insert, -1,1,1, 1,0,0, jsonSetFunc), + JFUNCTION(jsonb_insert, -1,1,0, 1,1,0, jsonSetFunc), + JFUNCTION(json_object, -1,0,1, 1,0,0, jsonObjectFunc), + JFUNCTION(jsonb_object, -1,0,1, 1,1,0, jsonObjectFunc), + JFUNCTION(json_patch, 2,1,1, 0,0,0, jsonPatchFunc), + JFUNCTION(jsonb_patch, 2,1,0, 0,1,0, jsonPatchFunc), + JFUNCTION(json_quote, 1,0,1, 1,0,0, jsonQuoteFunc), + JFUNCTION(json_remove, -1,1,1, 0,0,0, jsonRemoveFunc), + JFUNCTION(jsonb_remove, -1,1,0, 0,1,0, jsonRemoveFunc), + JFUNCTION(json_replace, -1,1,1, 1,0,0, jsonReplaceFunc), + JFUNCTION(jsonb_replace, -1,1,0, 1,1,0, jsonReplaceFunc), + JFUNCTION(json_set, -1,1,1, 1,0,JSON_ISSET, jsonSetFunc), + JFUNCTION(jsonb_set, -1,1,0, 1,1,JSON_ISSET, jsonSetFunc), + JFUNCTION(json_type, 1,1,0, 0,0,0, jsonTypeFunc), + JFUNCTION(json_type, 2,1,0, 0,0,0, jsonTypeFunc), + JFUNCTION(json_valid, 1,1,0, 0,0,0, jsonValidFunc), + JFUNCTION(json_valid, 2,1,0, 0,0,0, jsonValidFunc), +#if SQLITE_DEBUG + JFUNCTION(json_parse, 1,1,0, 0,0,0, jsonParseFunc), #endif WAGGREGATE(json_group_array, 1, 0, 0, jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| SQLITE_DETERMINISTIC), + WAGGREGATE(jsonb_group_array, 1, JSON_BLOB, 0, + jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), WAGGREGATE(json_group_object, 2, 0, 0, + jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), + WAGGREGATE(jsonb_group_object,2, JSON_BLOB, 0, jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| SQLITE_DETERMINISTIC) diff --git a/src/main.c b/src/main.c index 6acfdc325..03429983d 100644 --- a/src/main.c +++ b/src/main.c @@ -4659,6 +4659,28 @@ int sqlite3_test_control(int op, ...){ break; } #endif + + /* sqlite3_test_control(SQLITE_TESTCTRL_JSON_SELFCHECK, &onOff); + ** + ** Activate or deactivate validation of JSONB that is generated from + ** text. Off by default, as the validation is slow. Validation is + ** only available if compiled using SQLITE_DEBUG. + ** + ** If onOff is initially 1, then turn it on. If onOff is initially + ** off, turn it off. If onOff is initially -1, then change onOff + ** to be the current setting. + */ + case SQLITE_TESTCTRL_JSON_SELFCHECK: { +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) + int *pOnOff = va_arg(ap, int*); + if( *pOnOff<0 ){ + *pOnOff = sqlite3Config.bJsonSelfcheck; + }else{ + sqlite3Config.bJsonSelfcheck = (u8)((*pOnOff)&0xff); + } +#endif + break; + } } va_end(ap); #endif /* SQLITE_UNTESTABLE */ diff --git a/src/os_unix.c b/src/os_unix.c index a33e6f4df..80e6f6ad9 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -4054,7 +4054,13 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ #ifdef SQLITE_ENABLE_SETLK_TIMEOUT case SQLITE_FCNTL_LOCK_TIMEOUT: { int iOld = pFile->iBusyTimeout; +#if SQLITE_ENABLE_SETLK_TIMEOUT==1 pFile->iBusyTimeout = *(int*)pArg; +#elif SQLITE_ENABLE_SETLK_TIMEOUT==2 + pFile->iBusyTimeout = !!(*(int*)pArg); +#else +# error "SQLITE_ENABLE_SETLK_TIMEOUT must be set to 1 or 2" +#endif *(int*)pArg = iOld; return SQLITE_OK; } @@ -4307,6 +4313,25 @@ static int unixGetpagesize(void){ ** Either unixShmNode.pShmMutex must be held or unixShmNode.nRef==0 and ** unixMutexHeld() is true when reading or writing any other field ** in this structure. +** +** aLock[SQLITE_SHM_NLOCK]: +** This array records the various locks held by clients on each of the +** SQLITE_SHM_NLOCK slots. If the aLock[] entry is set to 0, then no +** locks are held by the process on this slot. If it is set to -1, then +** some client holds an EXCLUSIVE lock on the locking slot. If the aLock[] +** value is set to a positive value, then it is the number of shared +** locks currently held on the slot. +** +** aMutex[SQLITE_SHM_NLOCK]: +** Normally, when SQLITE_ENABLE_SETLK_TIMEOUT is not defined, mutex +** pShmMutex is used to protect the aLock[] array and the right to +** call fcntl() on unixShmNode.hShm to obtain or release locks. +** +** If SQLITE_ENABLE_SETLK_TIMEOUT is defined though, we use an array +** of mutexes - one for each locking slot. To read or write locking +** slot aLock[iSlot], the caller must hold the corresponding mutex +** aMutex[iSlot]. Similarly, to call fcntl() to obtain or release a +** lock corresponding to slot iSlot, mutex aMutex[iSlot] must be held. */ struct unixShmNode { unixInodeInfo *pInode; /* unixInodeInfo that owns this SHM node */ @@ -4320,10 +4345,11 @@ struct unixShmNode { char **apRegion; /* Array of mapped shared-memory regions */ int nRef; /* Number of unixShm objects pointing to this */ unixShm *pFirst; /* All unixShm objects pointing to this */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + sqlite3_mutex *aMutex[SQLITE_SHM_NLOCK]; +#endif int aLock[SQLITE_SHM_NLOCK]; /* # shared locks on slot, -1==excl lock */ #ifdef SQLITE_DEBUG - u8 exclMask; /* Mask of exclusive locks held */ - u8 sharedMask; /* Mask of shared locks held */ u8 nextShmId; /* Next available unixShm.id value */ #endif }; @@ -4406,16 +4432,35 @@ static int unixShmSystemLock( struct flock f; /* The posix advisory locking structure */ int rc = SQLITE_OK; /* Result code form fcntl() */ - /* Access to the unixShmNode object is serialized by the caller */ pShmNode = pFile->pInode->pShmNode; - assert( pShmNode->nRef==0 || sqlite3_mutex_held(pShmNode->pShmMutex) ); - assert( pShmNode->nRef>0 || unixMutexHeld() ); + + /* Assert that the parameters are within expected range and that the + ** correct mutex or mutexes are held. */ + assert( pShmNode->nRef>=0 ); + assert( (ofst==UNIX_SHM_DMS && n==1) + || (ofst>=UNIX_SHM_BASE && ofst+n<=(UNIX_SHM_BASE+SQLITE_SHM_NLOCK)) + ); + if( ofst==UNIX_SHM_DMS ){ + assert( pShmNode->nRef>0 || unixMutexHeld() ); + assert( pShmNode->nRef==0 || sqlite3_mutex_held(pShmNode->pShmMutex) ); + }else{ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int ii; + for(ii=ofst-UNIX_SHM_BASE; iiaMutex[ii]) ); + } +#else + assert( sqlite3_mutex_held(pShmNode->pShmMutex) ); + assert( pShmNode->nRef>0 ); +#endif + } /* Shared locks never span more than one byte */ assert( n==1 || lockType!=F_RDLCK ); /* Locks are within range */ assert( n>=1 && n<=SQLITE_SHM_NLOCK ); + assert( ofst>=UNIX_SHM_BASE && ofst<=(UNIX_SHM_DMS+SQLITE_SHM_NLOCK) ); if( pShmNode->hShm>=0 ){ int res; @@ -4426,7 +4471,7 @@ static int unixShmSystemLock( f.l_len = n; res = osSetPosixAdvisoryLock(pShmNode->hShm, &f, pFile); if( res==-1 ){ -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) && SQLITE_ENABLE_SETLK_TIMEOUT==1 rc = (pFile->iBusyTimeout ? SQLITE_BUSY_TIMEOUT : SQLITE_BUSY); #else rc = SQLITE_BUSY; @@ -4434,39 +4479,28 @@ static int unixShmSystemLock( } } - /* Update the global lock state and do debug tracing */ + /* Do debug tracing */ #ifdef SQLITE_DEBUG - { u16 mask; OSTRACE(("SHM-LOCK ")); - mask = ofst>31 ? 0xffff : (1<<(ofst+n)) - (1<exclMask &= ~mask; - pShmNode->sharedMask &= ~mask; + OSTRACE(("unlock %d..%d ok\n", ofst, ofst+n-1)); }else if( lockType==F_RDLCK ){ - OSTRACE(("read-lock %d ok", ofst)); - pShmNode->exclMask &= ~mask; - pShmNode->sharedMask |= mask; + OSTRACE(("read-lock %d..%d ok\n", ofst, ofst+n-1)); }else{ assert( lockType==F_WRLCK ); - OSTRACE(("write-lock %d ok", ofst)); - pShmNode->exclMask |= mask; - pShmNode->sharedMask &= ~mask; + OSTRACE(("write-lock %d..%d ok\n", ofst, ofst+n-1)); } }else{ if( lockType==F_UNLCK ){ - OSTRACE(("unlock %d failed", ofst)); + OSTRACE(("unlock %d..%d failed\n", ofst, ofst+n-1)); }else if( lockType==F_RDLCK ){ - OSTRACE(("read-lock failed")); + OSTRACE(("read-lock %d..%d failed\n", ofst, ofst+n-1)); }else{ assert( lockType==F_WRLCK ); - OSTRACE(("write-lock %d failed", ofst)); + OSTRACE(("write-lock %d..%d failed\n", ofst, ofst+n-1)); } } - OSTRACE((" - afterwards %03x,%03x\n", - pShmNode->sharedMask, pShmNode->exclMask)); - } #endif return rc; @@ -4503,6 +4537,11 @@ static void unixShmPurge(unixFile *pFd){ int i; assert( p->pInode==pFd->pInode ); sqlite3_mutex_free(p->pShmMutex); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + for(i=0; iaMutex[i]); + } +#endif for(i=0; inRegion; i+=nShmPerMap){ if( p->hShm>=0 ){ osMunmap(p->apRegion[i], p->szRegion); @@ -4562,7 +4601,20 @@ static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ pShmNode->isUnlocked = 1; rc = SQLITE_READONLY_CANTINIT; }else{ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* Do not use a blocking lock here. If the lock cannot be obtained + ** immediately, it means some other connection is truncating the + ** *-shm file. And after it has done so, it will not release its + ** lock, but only downgrade it to a shared lock. So no point in + ** blocking here. The call below to obtain the shared DMS lock may + ** use a blocking lock. */ + int iSaveTimeout = pDbFd->iBusyTimeout; + pDbFd->iBusyTimeout = 0; +#endif rc = unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + pDbFd->iBusyTimeout = iSaveTimeout; +#endif /* The first connection to attach must truncate the -shm file. We ** truncate to 3 bytes (an arbitrary small number, less than the ** -shm header size) rather than 0 as a system debugging aid, to @@ -4683,6 +4735,18 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ rc = SQLITE_NOMEM_BKPT; goto shm_open_err; } +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + { + int ii; + for(ii=0; iiaMutex[ii] = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( pShmNode->aMutex[ii]==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto shm_open_err; + } + } + } +#endif } if( pInode->bProcessLock==0 ){ @@ -4904,9 +4968,11 @@ static int unixShmMap( */ #ifdef SQLITE_DEBUG static int assertLockingArrayOk(unixShmNode *pShmNode){ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + return 1; +#else unixShm *pX; int aLock[SQLITE_SHM_NLOCK]; - assert( sqlite3_mutex_held(pShmNode->pShmMutex) ); memset(aLock, 0, sizeof(aLock)); for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ @@ -4924,13 +4990,14 @@ static int assertLockingArrayOk(unixShmNode *pShmNode){ assert( 0==memcmp(pShmNode->aLock, aLock, sizeof(aLock)) ); return (memcmp(pShmNode->aLock, aLock, sizeof(aLock))==0); +#endif } #endif /* ** Change the lock state for a shared-memory segment. ** -** Note that the relationship between SHAREd and EXCLUSIVE locks is a little +** Note that the relationship between SHARED and EXCLUSIVE locks is a little ** different here than in posix. In xShmLock(), one can go from unlocked ** to shared and back or from unlocked to exclusive and back. But one may ** not go from shared to exclusive or from exclusive to shared. @@ -4945,7 +5012,7 @@ static int unixShmLock( unixShm *p; /* The shared memory being locked */ unixShmNode *pShmNode; /* The underlying file iNode */ int rc = SQLITE_OK; /* Result code */ - u16 mask; /* Mask of locks to take or release */ + u16 mask = (1<<(ofst+n)) - (1<pShm; @@ -4980,88 +5047,151 @@ static int unixShmLock( ** It is not permitted to block on the RECOVER lock. */ #ifdef SQLITE_ENABLE_SETLK_TIMEOUT - assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( - (ofst!=2) /* not RECOVER */ - && (ofst!=1 || (p->exclMask|p->sharedMask)==0) - && (ofst!=0 || (p->exclMask|p->sharedMask)<3) - && (ofst<3 || (p->exclMask|p->sharedMask)<(1<exclMask|p->sharedMask); + assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( + (ofst!=2) /* not RECOVER */ + && (ofst!=1 || lockMask==0 || lockMask==2) + && (ofst!=0 || lockMask<3) + && (ofst<3 || lockMask<(1<1 || mask==(1<pShmMutex); - assert( assertLockingArrayOk(pShmNode) ); - if( flags & SQLITE_SHM_UNLOCK ){ - if( (p->exclMask|p->sharedMask) & mask ){ - int ii; - int bUnlock = 1; - - for(ii=ofst; ii((p->sharedMask & (1<sharedMask & (1<1 ); - aLock[ofst]--; - } - - /* Undo the local locks */ - if( rc==SQLITE_OK ){ - p->exclMask &= ~mask; - p->sharedMask &= ~mask; - } - } - }else if( flags & SQLITE_SHM_SHARED ){ - assert( n==1 ); - assert( (p->exclMask & (1<sharedMask & mask)==0 ){ - if( aLock[ofst]<0 ){ - rc = SQLITE_BUSY; - }else if( aLock[ofst]==0 ){ - rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n); - } + /* Check if there is any work to do. There are three cases: + ** + ** a) An unlock operation where there are locks to unlock, + ** b) An shared lock where the requested lock is not already held + ** c) An exclusive lock where the requested lock is not already held + ** + ** The SQLite core never requests an exclusive lock that it already holds. + ** This is assert()ed below. + */ + assert( flags!=(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK) + || 0==(p->exclMask & mask) + ); + if( ((flags & SQLITE_SHM_UNLOCK) && ((p->exclMask|p->sharedMask) & mask)) + || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask)) + || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK)) + ){ - /* Get the local shared locks */ - if( rc==SQLITE_OK ){ - p->sharedMask |= mask; - aLock[ofst]++; - } - } - }else{ - /* Make sure no sibling connections hold locks that will block this - ** lock. If any do, return SQLITE_BUSY right away. */ - int ii; - for(ii=ofst; iisharedMask & mask)==0 ); - if( ALWAYS((p->exclMask & (1<aMutex[iMutex]); + if( rc!=SQLITE_OK ) goto leave_shmnode_mutexes; + }else{ + sqlite3_mutex_enter(pShmNode->aMutex[iMutex]); } } +#else + sqlite3_mutex_enter(pShmNode->pShmMutex); +#endif - /* Get the exclusive locks at the system level. Then if successful - ** also update the in-memory values. */ - if( rc==SQLITE_OK ){ - rc = unixShmSystemLock(pDbFd, F_WRLCK, ofst+UNIX_SHM_BASE, n); - if( rc==SQLITE_OK ){ + if( ALWAYS(rc==SQLITE_OK) ){ + if( flags & SQLITE_SHM_UNLOCK ){ + /* Case (a) - unlock. */ + int bUnlock = 1; + assert( (p->exclMask & p->sharedMask)==0 ); + assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask ); + assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask ); + + /* If this is a SHARED lock being unlocked, it is possible that other + ** clients within this process are holding the same SHARED lock. In + ** this case, set bUnlock to 0 so that the posix lock is not removed + ** from the file-descriptor below. */ + if( flags & SQLITE_SHM_SHARED ){ + assert( n==1 ); + assert( aLock[ofst]>=1 ); + if( aLock[ofst]>1 ){ + bUnlock = 0; + aLock[ofst]--; + p->sharedMask &= ~mask; + } + } + + if( bUnlock ){ + rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n); + if( rc==SQLITE_OK ){ + memset(&aLock[ofst], 0, sizeof(int)*n); + p->sharedMask &= ~mask; + p->exclMask &= ~mask; + } + } + }else if( flags & SQLITE_SHM_SHARED ){ + /* Case (b) - a shared lock. */ + + if( aLock[ofst]<0 ){ + /* An exclusive lock is held by some other connection. BUSY. */ + rc = SQLITE_BUSY; + }else if( aLock[ofst]==0 ){ + rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n); + } + + /* Get the local shared locks */ + if( rc==SQLITE_OK ){ + p->sharedMask |= mask; + aLock[ofst]++; + } + }else{ + /* Case (c) - an exclusive lock. */ + int ii; + + assert( flags==(SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE) ); assert( (p->sharedMask & mask)==0 ); - p->exclMask |= mask; + assert( (p->exclMask & mask)==0 ); + + /* Make sure no sibling connections hold locks that will block this + ** lock. If any do, return SQLITE_BUSY right away. */ for(ii=ofst; iiexclMask |= mask; + for(ii=ofst; ii=ofst; iMutex--){ + sqlite3_mutex_leave(pShmNode->aMutex[iMutex]); } +#else + sqlite3_mutex_leave(pShmNode->pShmMutex); +#endif } - assert( assertLockingArrayOk(pShmNode) ); - sqlite3_mutex_leave(pShmNode->pShmMutex); + OSTRACE(("SHM-LOCK shmid-%d, pid-%d got %03x,%03x\n", p->id, osGetpid(0), p->sharedMask, p->exclMask)); return rc; diff --git a/src/pager.c b/src/pager.c index 4687ab0f1..37588f0b2 100644 --- a/src/pager.c +++ b/src/pager.c @@ -688,7 +688,7 @@ struct Pager { char *zJournal; /* Name of the journal file */ int (*xBusyHandler)(void*); /* Function to call when busy */ void *pBusyHandlerArg; /* Context argument for xBusyHandler */ - int aStat[4]; /* Total cache hits, misses, writes, spills */ + u32 aStat[4]; /* Total cache hits, misses, writes, spills */ #ifdef SQLITE_TEST int nRead; /* Database pages read */ #endif @@ -818,9 +818,8 @@ int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ #ifndef SQLITE_OMIT_WAL if( pPager->pWal ){ u32 iRead = 0; - int rc; - rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); - return (rc==SQLITE_OK && iRead==0); + (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); + return iRead==0; } #endif return 1; @@ -6832,11 +6831,11 @@ int *sqlite3PagerStats(Pager *pPager){ a[3] = pPager->eState==PAGER_OPEN ? -1 : (int) pPager->dbSize; a[4] = pPager->eState; a[5] = pPager->errCode; - a[6] = pPager->aStat[PAGER_STAT_HIT]; - a[7] = pPager->aStat[PAGER_STAT_MISS]; + a[6] = (int)pPager->aStat[PAGER_STAT_HIT] & 0x7fffffff; + a[7] = (int)pPager->aStat[PAGER_STAT_MISS] & 0x7fffffff; a[8] = 0; /* Used to be pPager->nOvfl */ a[9] = pPager->nRead; - a[10] = pPager->aStat[PAGER_STAT_WRITE]; + a[10] = (int)pPager->aStat[PAGER_STAT_WRITE] & 0x7fffffff; return a; } #endif @@ -6852,7 +6851,7 @@ int *sqlite3PagerStats(Pager *pPager){ ** reset parameter is non-zero, the cache hit or miss count is zeroed before ** returning. */ -void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){ +void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, u64 *pnVal){ assert( eStat==SQLITE_DBSTATUS_CACHE_HIT || eStat==SQLITE_DBSTATUS_CACHE_MISS @@ -7792,7 +7791,7 @@ int sqlite3PagerWalFramesize(Pager *pPager){ } #endif -#ifdef SQLITE_USE_SEH +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) int sqlite3PagerWalSystemErrno(Pager *pPager){ return sqlite3WalSystemErrno(pPager->pWal); } diff --git a/src/pager.h b/src/pager.h index 044c2573e..7ef9a237a 100644 --- a/src/pager.h +++ b/src/pager.h @@ -216,7 +216,7 @@ sqlite3_file *sqlite3PagerJrnlFile(Pager*); const char *sqlite3PagerJournalname(Pager*); void *sqlite3PagerTempSpace(Pager*); int sqlite3PagerIsMemdb(Pager*); -void sqlite3PagerCacheStat(Pager *, int, int, int *); +void sqlite3PagerCacheStat(Pager *, int, int, u64*); void sqlite3PagerClearCache(Pager*); int sqlite3SectorSize(sqlite3_file *); diff --git a/src/pragma.c b/src/pragma.c index 90076b0c2..4c9057418 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1779,7 +1779,8 @@ void sqlite3Pragma( if( pVTab->pModule->iVersion<4 ) continue; if( pVTab->pModule->xIntegrity==0 ) continue; sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick); - sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + pTab->nTabRef++; + sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF); a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); integrityCheckResultRow(v); sqlite3VdbeJumpHere(v, a1); diff --git a/src/prepare.c b/src/prepare.c index d3e134e76..87569ee91 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -868,6 +868,7 @@ static int sqlite3LockAndPrepare( assert( (rc&db->errMask)==rc ); db->busyHandler.nBusy = 0; sqlite3_mutex_leave(db->mutex); + assert( rc==SQLITE_OK || (*ppStmt)==0 ); return rc; } diff --git a/src/printf.c b/src/printf.c index 3c0b182d3..c6b3803ca 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1369,7 +1369,7 @@ void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ /***************************************************************************** -** Reference counted string storage +** Reference counted string/blob storage *****************************************************************************/ /* diff --git a/src/resolve.c b/src/resolve.c index 0072f6b6a..b4f03fe7e 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -182,6 +182,7 @@ Bitmask sqlite3ExprColUsed(Expr *pExpr){ assert( ExprUseYTab(pExpr) ); pExTab = pExpr->y.pTab; assert( pExTab!=0 ); + assert( n < pExTab->nCol ); if( (pExTab->tabFlags & TF_HasGenerated)!=0 && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0 ){ @@ -758,6 +759,7 @@ static int lookupName( sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); pParse->checkSchema = 1; pTopNC->nNcErr++; + eNewExprOp = TK_NULL; } assert( pFJMatch==0 ); @@ -784,7 +786,7 @@ static int lookupName( ** If a generated column is referenced, set bits for every column ** of the table. */ - if( pExpr->iColumn>=0 && pMatch!=0 ){ + if( pExpr->iColumn>=0 && cnt==1 && pMatch!=0 ){ pMatch->colUsed |= sqlite3ExprColUsed(pExpr); } @@ -1249,11 +1251,12 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ while( pNC2 && sqlite3ReferencesSrcList(pParse, pExpr, pNC2->pSrcList)==0 ){ - pExpr->op2++; + pExpr->op2 += (1 + pNC2->nNestedSelect); pNC2 = pNC2->pNext; } assert( pDef!=0 || IN_RENAME_OBJECT ); if( pNC2 && pDef ){ + pExpr->op2 += pNC2->nNestedSelect; assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg ); assert( SQLITE_FUNC_ANYORDER==NC_OrderAgg ); testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 ); @@ -1812,6 +1815,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ /* Recursively resolve names in all subqueries in the FROM clause */ + if( pOuterNC ) pOuterNC->nNestedSelect++; for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){ @@ -1836,6 +1840,9 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } } + if( pOuterNC && ALWAYS(pOuterNC->nNestedSelect>0) ){ + pOuterNC->nNestedSelect--; + } /* Set up the local name-context to pass to sqlite3ResolveExprNames() to ** resolve the result-set expression list. diff --git a/src/select.c b/src/select.c index e5312c7ee..121572779 100644 --- a/src/select.c +++ b/src/select.c @@ -184,6 +184,9 @@ Select *sqlite3SelectNew( void sqlite3SelectDelete(sqlite3 *db, Select *p){ if( OK_IF_ALWAYS_TRUE(p) ) clearSelect(db, p, 1); } +void sqlite3SelectDeleteGeneric(sqlite3 *db, void *p){ + if( ALWAYS(p) ) clearSelect(db, (Select*)p, 1); +} /* ** Return a pointer to the right-most SELECT statement in a compound. @@ -3204,9 +3207,7 @@ static int multiSelect( pDest->iSdst = dest.iSdst; pDest->nSdst = dest.nSdst; if( pDelete ){ - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3SelectDelete, - pDelete); + sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete); } return rc; } @@ -3757,8 +3758,7 @@ static int multiSelectOrderBy( /* Make arrangements to free the 2nd and subsequent arms of the compound ** after the parse has finished */ if( pSplit->pPrior ){ - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior); + sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSplit->pPrior); } pSplit->pPrior = pPrior; pPrior->pNext = pSplit; @@ -4579,9 +4579,7 @@ static int flattenSubquery( Table *pTabToDel = pSubitem->pTab; if( pTabToDel->nTabRef==1 ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - sqlite3ParserAddCleanup(pToplevel, - (void(*)(sqlite3*,void*))sqlite3DeleteTable, - pTabToDel); + sqlite3ParserAddCleanup(pToplevel, sqlite3DeleteTableGeneric, pTabToDel); testcase( pToplevel->earlyCleanup ); }else{ pTabToDel->nTabRef--; @@ -5628,8 +5626,7 @@ static struct Cte *searchWith( With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ if( pWith ){ if( bFree ){ - pWith = (With*)sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3WithDelete, + pWith = (With*)sqlite3ParserAddCleanup(pParse, sqlite3WithDeleteGeneric, pWith); if( pWith==0 ) return 0; } @@ -6662,6 +6659,7 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ assert( pFunc->pFExpr->pLeft!=0 ); assert( pFunc->pFExpr->pLeft->op==TK_ORDER ); assert( ExprUseXList(pFunc->pFExpr->pLeft) ); + assert( pFunc->pFunc!=0 ); pOBList = pFunc->pFExpr->pLeft->x.pList; if( !pFunc->bOBUnique ){ nExtra++; /* One extra column for the OP_Sequence */ @@ -6671,6 +6669,9 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ assert( ExprUseXList(pFunc->pFExpr) ); nExtra += pFunc->pFExpr->x.pList->nExpr; } + if( pFunc->bUseSubtype ){ + nExtra += pFunc->pFExpr->x.pList->nExpr; + } pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOBList, 0, nExtra); if( !pFunc->bOBUnique && pParse->nErr==0 ){ pKeyInfo->nKeyField++; @@ -6697,16 +6698,17 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ assert( ExprUseXList(pF->pFExpr) ); pList = pF->pFExpr->x.pList; if( pF->iOBTab>=0 ){ - /* For an ORDER BY aggregate, calls to OP_AggStep where deferred and - ** all content was stored in emphermal table pF->iOBTab. Extract that - ** content now (in ORDER BY order) and make all calls to OP_AggStep + /* For an ORDER BY aggregate, calls to OP_AggStep were deferred. Inputs + ** were stored in emphermal table pF->iOBTab. Here, we extract those + ** inputs (in ORDER BY order) and make all calls to OP_AggStep ** before doing the OP_AggFinal call. */ int iTop; /* Start of loop for extracting columns */ int nArg; /* Number of columns to extract */ int nKey; /* Key columns to be skipped */ int regAgg; /* Extract into this array */ int j; /* Loop counter */ - + + assert( pF->pFunc!=0 ); nArg = pList->nExpr; regAgg = sqlite3GetTempRange(pParse, nArg); @@ -6723,6 +6725,15 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ for(j=nArg-1; j>=0; j--){ sqlite3VdbeAddOp3(v, OP_Column, pF->iOBTab, nKey+j, regAgg+j); } + if( pF->bUseSubtype ){ + int regSubtype = sqlite3GetTempReg(pParse); + int iBaseCol = nKey + nArg + (pF->bOBPayload==0 && pF->bOBUnique==0); + for(j=nArg-1; j>=0; j--){ + sqlite3VdbeAddOp3(v, OP_Column, pF->iOBTab, iBaseCol+j, regSubtype); + sqlite3VdbeAddOp2(v, OP_SetSubtype, regSubtype, regAgg+j); + } + sqlite3ReleaseTempReg(pParse, regSubtype); + } sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nArg); @@ -6777,6 +6788,7 @@ static void updateAccumulator( ExprList *pList; assert( ExprUseXList(pF->pFExpr) ); assert( !IsWindowFunc(pF->pFExpr) ); + assert( pF->pFunc!=0 ); pList = pF->pFExpr->x.pList; if( ExprHasProperty(pF->pFExpr, EP_WinFunc) ){ Expr *pFilter = pF->pFExpr->y.pWin->pFilter; @@ -6821,6 +6833,9 @@ static void updateAccumulator( if( pF->bOBPayload ){ regAggSz += nArg; } + if( pF->bUseSubtype ){ + regAggSz += nArg; + } regAggSz++; /* One extra register to hold result of MakeRecord */ regAgg = sqlite3GetTempRange(pParse, regAggSz); regDistinct = regAgg; @@ -6833,6 +6848,14 @@ static void updateAccumulator( if( pF->bOBPayload ){ regDistinct = regAgg+jj; sqlite3ExprCodeExprList(pParse, pList, regDistinct, 0, SQLITE_ECEL_DUP); + jj += nArg; + } + if( pF->bUseSubtype ){ + int kk; + int regBase = pF->bOBPayload ? regDistinct : regAgg; + for(kk=0; kknExpr; @@ -7037,7 +7060,8 @@ static SrcItem *isSelfJoinView( /* ** Deallocate a single AggInfo object */ -static void agginfoFree(sqlite3 *db, AggInfo *p){ +static void agginfoFree(sqlite3 *db, void *pArg){ + AggInfo *p = (AggInfo*)pArg; sqlite3DbFree(db, p->aCol); sqlite3DbFree(db, p->aFunc); sqlite3DbFreeNN(db, p); @@ -7111,7 +7135,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ pSub->selFlags |= SF_Aggregate; pSub->selFlags &= ~SF_Compound; pSub->nSelectRow = 0; - sqlite3ExprListDelete(db, pSub->pEList); + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, pSub->pEList); pTerm = pPrior ? sqlite3ExprDup(db, pCount, 0) : pCount; pSub->pEList = sqlite3ExprListAppend(pParse, 0, pTerm); pTerm = sqlite3PExpr(pParse, TK_SELECT, 0, 0); @@ -7291,9 +7315,8 @@ int sqlite3Select( sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); } #endif - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3ExprListDelete, - p->pOrderBy); + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, + p->pOrderBy); testcase( pParse->earlyCleanup ); p->pOrderBy = 0; } @@ -7485,9 +7508,8 @@ int sqlite3Select( ){ TREETRACE(0x800,pParse,p, ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3ExprListDelete, - pSub->pOrderBy); + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, + pSub->pOrderBy); pSub->pOrderBy = 0; } @@ -8016,8 +8038,7 @@ int sqlite3Select( */ pAggInfo = sqlite3DbMallocZero(db, sizeof(*pAggInfo) ); if( pAggInfo ){ - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))agginfoFree, pAggInfo); + sqlite3ParserAddCleanup(pParse, agginfoFree, pAggInfo); testcase( pParse->earlyCleanup ); } if( db->mallocFailed ){ diff --git a/src/shell.c.in b/src/shell.c.in index d52c7c53a..da3b9f870 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -251,6 +251,7 @@ INCLUDE ../ext/consio/console_io.h INCLUDE ../ext/consio/console_io.c #ifndef SQLITE_SHELL_FIDDLE + /* From here onward, fgets() is redirected to the console_io library. */ # define fgets(b,n,f) fGetsUtf8(b,n,f) /* @@ -275,6 +276,7 @@ INCLUDE ../ext/consio/console_io.c # define eputz(z) ePutsUtf8(z) # define eputf ePrintfUtf8 # define oputb(buf,na) oPutbUtf8(buf,na) + #else /* For Fiddle, all console handling and emit redirection is omitted. */ # define sputz(fp,z) fputs(z,fp) @@ -358,7 +360,7 @@ static void endTimer(void){ sqlite3_int64 iEnd = timeOfDay(); struct rusage sEnd; getrusage(RUSAGE_SELF, &sEnd); - oputf("Run Time: real %.3f user %f sys %f\n", + sputf(stdout, "Run Time: real %.3f user %f sys %f\n", (iEnd - iBegin)*0.001, timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); @@ -437,7 +439,7 @@ static void endTimer(void){ FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; sqlite3_int64 ftWallEnd = timeOfDay(); getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); - oputf("Run Time: real %.3f user %f sys %f\n", + sputf(stdout, "Run Time: real %.3f user %f sys %f\n", (ftWallEnd - ftWallBegin)*0.001, timeDiff(&ftUserBegin, &ftUserEnd), timeDiff(&ftKernelBegin, &ftKernelEnd)); @@ -734,14 +736,14 @@ static int strlenChar(const char *z){ */ static FILE * openChrSource(const char *zFile){ #if defined(_WIN32) || defined(WIN32) - struct _stat x = {0}; + struct __stat64 x = {0}; # define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) /* On Windows, open first, then check the stream nature. This order ** is necessary because _stat() and sibs, when checking a named pipe, ** effectively break the pipe as its supplier sees it. */ FILE *rv = fopen(zFile, "rb"); if( rv==0 ) return 0; - if( _fstat(_fileno(rv), &x) != 0 + if( _fstat64(_fileno(rv), &x) != 0 || !STAT_CHR_SRC(x.st_mode)){ fclose(rv); rv = 0; @@ -1290,6 +1292,7 @@ struct ShellState { u8 eTraceType; /* SHELL_TRACE_* value for type of trace */ u8 bSafeMode; /* True to prohibit unsafe operations */ u8 bSafeModePersist; /* The long-term value of bSafeMode */ + u8 eRestoreState; /* See comments above doAutoDetectRestore() */ ColModeOpts cmOpts; /* Option values affecting columnar mode output */ unsigned statsOn; /* True to display memory stats before each finalize */ unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */ @@ -5317,7 +5320,6 @@ static void open_db(ShellState *p, int openFlags){ break; } } - globalDb = p->db; if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ eputf("Error: unable to open database \"%s\": %s\n", zDbFilename, sqlite3_errmsg(p->db)); @@ -5334,6 +5336,7 @@ static void open_db(ShellState *p, int openFlags){ zDbFilename); } } + globalDb = p->db; sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0); /* Reflect the use or absence of --unsafe-testing invocation. */ @@ -6729,7 +6732,6 @@ static int lintDotCommand( return SQLITE_ERROR; } -#if !defined SQLITE_OMIT_VIRTUALTABLE static void shellPrepare( sqlite3 *db, int *pRc, @@ -6748,12 +6750,8 @@ static void shellPrepare( /* ** Create a prepared statement using printf-style arguments for the SQL. -** -** This routine is could be marked "static". But it is not always used, -** depending on compile-time options. By omitting the "static", we avoid -** nuisance compiler warnings about "defined but not used". */ -void shellPreparePrintf( +static void shellPreparePrintf( sqlite3 *db, int *pRc, sqlite3_stmt **ppStmt, @@ -6776,13 +6774,10 @@ void shellPreparePrintf( } } -/* Finalize the prepared statement created using shellPreparePrintf(). -** -** This routine is could be marked "static". But it is not always used, -** depending on compile-time options. By omitting the "static", we avoid -** nuisance compiler warnings about "defined but not used". +/* +** Finalize the prepared statement created using shellPreparePrintf(). */ -void shellFinalize( +static void shellFinalize( int *pRc, sqlite3_stmt *pStmt ){ @@ -6798,6 +6793,7 @@ void shellFinalize( } } +#if !defined SQLITE_OMIT_VIRTUALTABLE /* Reset the prepared statement created using shellPreparePrintf(). ** ** This routine is could be marked "static". But it is not always used, @@ -7864,6 +7860,30 @@ FROM (\ } } +/* +** Check if the sqlite_schema table contains one or more virtual tables. If +** parameter zLike is not NULL, then it is an SQL expression that the +** sqlite_schema row must also match. If one or more such rows are found, +** print the following warning to the output: +** +** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled +*/ +static int outputDumpWarning(ShellState *p, const char *zLike){ + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + shellPreparePrintf(p->db, &rc, &pStmt, + "SELECT 1 FROM sqlite_schema o WHERE " + "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true" + ); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + oputz("/* WARNING: " + "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n" + ); + } + shellFinalize(&rc, pStmt); + return rc; +} + /* ** If an input line begins with "." then invoke this routine to ** process that line. @@ -8326,6 +8346,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); + outputDumpWarning(p, zLike); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. @@ -10754,6 +10775,7 @@ static int do_meta_command(char *zLine, ShellState *p){ {"fk_no_action", SQLITE_TESTCTRL_FK_NO_ACTION, 0, "BOOLEAN" }, {"imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, + {"json_selfcheck", SQLITE_TESTCTRL_JSON_SELFCHECK ,0,"BOOLEAN" }, {"localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, {"never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, {"optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, @@ -10972,6 +10994,16 @@ static int do_meta_command(char *zLine, ShellState *p){ isOk = 3; } break; + case SQLITE_TESTCTRL_JSON_SELFCHECK: + if( nArg==2 ){ + rc2 = -1; + isOk = 1; + }else{ + rc2 = booleanValue(azArg[2]); + isOk = 3; + } + sqlite3_test_control(testctrl, &rc2); + break; } } if( isOk==0 && iCtrl>=0 ){ @@ -11378,6 +11410,88 @@ static int line_is_complete(char *zSql, int nSql){ return rc; } +/* +** This function is called after processing each line of SQL in the +** runOneSqlLine() function. Its purpose is to detect scenarios where +** defensive mode should be automatically turned off. Specifically, when +** +** 1. The first line of input is "PRAGMA foreign_keys=OFF;", +** 2. The second line of input is "BEGIN TRANSACTION;", +** 3. The database is empty, and +** 4. The shell is not running in --safe mode. +** +** The implementation uses the ShellState.eRestoreState to maintain state: +** +** 0: Have not seen any SQL. +** 1: Have seen "PRAGMA foreign_keys=OFF;". +** 2-6: Currently running .dump transaction. If the "2" bit is set, +** disable DEFENSIVE when done. If "4" is set, disable DQS_DDL. +** 7: Nothing left to do. This function becomes a no-op. +*/ +static int doAutoDetectRestore(ShellState *p, const char *zSql){ + int rc = SQLITE_OK; + + if( p->eRestoreState<7 ){ + switch( p->eRestoreState ){ + case 0: { + const char *zExpect = "PRAGMA foreign_keys=OFF;"; + assert( strlen(zExpect)==24 ); + if( p->bSafeMode==0 && memcmp(zSql, zExpect, 25)==0 ){ + p->eRestoreState = 1; + }else{ + p->eRestoreState = 7; + } + break; + }; + + case 1: { + int bIsDump = 0; + const char *zExpect = "BEGIN TRANSACTION;"; + assert( strlen(zExpect)==18 ); + if( memcmp(zSql, zExpect, 19)==0 ){ + /* Now check if the database is empty. */ + const char *zQuery = "SELECT 1 FROM sqlite_schema LIMIT 1"; + sqlite3_stmt *pStmt = 0; + + bIsDump = 1; + shellPrepare(p->db, &rc, zQuery, &pStmt); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + bIsDump = 0; + } + shellFinalize(&rc, pStmt); + } + if( bIsDump && rc==SQLITE_OK ){ + int bDefense = 0; + int bDqsDdl = 0; + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &bDefense); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DQS_DDL, -1, &bDqsDdl); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DQS_DDL, 1, 0); + p->eRestoreState = (bDefense ? 2 : 0) + (bDqsDdl ? 4 : 0); + }else{ + p->eRestoreState = 7; + } + break; + } + + default: { + if( sqlite3_get_autocommit(p->db) ){ + if( (p->eRestoreState & 2) ){ + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 1, 0); + } + if( (p->eRestoreState & 4) ){ + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DQS_DDL, 0, 0); + } + p->eRestoreState = 7; + } + break; + } + } + } + + return rc; +} + /* ** Run a single line of SQL. Return the number of errors. */ @@ -11425,6 +11539,8 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); oputf("%s\n", zLineBuf); } + + if( doAutoDetectRestore(p, zSql) ) return 1; return 0; } @@ -11858,14 +11974,14 @@ static void printBold(const char *zText){ FOREGROUND_RED|FOREGROUND_INTENSITY ); #endif - oputz(zText); + sputz(stdout, zText); #if !SQLITE_OS_WINRT SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); #endif } #else static void printBold(const char *zText){ - oputf("\033[1m%s\033[0m", zText); + sputf(stdout, "\033[1m%s\033[0m", zText); } #endif @@ -12059,10 +12175,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-init")==0 ){ zInitFile = cmdline_option_value(argc, argv, ++i); }else if( cli_strcmp(z,"-interactive")==0 ){ - /* Need to check for interactive override here to so that it can - ** affect console setup (for Windows only) and testing thereof. - */ - stdin_is_interactive = 1; }else if( cli_strcmp(z,"-batch")==0 ){ /* Need to check for batch mode here to so we can avoid printing ** informational messages (like from process_sqliterc) before @@ -12332,11 +12444,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - oputf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), - 8*(int)sizeof(char*)); + sputf(stdout, "%s %s (%d-bit)\n", + sqlite3_libversion(), sqlite3_sourceid(), 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ - /* already handled */ + /* Need to check for interactive override here to so that it can + ** affect console setup (for Windows only) and testing thereof. + */ + stdin_is_interactive = 1; }else if( cli_strcmp(z,"-batch")==0 ){ /* already handled */ }else if( cli_strcmp(z,"-utf8")==0 ){ @@ -12465,13 +12580,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #else # define SHELL_CIO_CHAR_SET "" #endif - oputf("SQLite version %s %.19s%s\n" /*extra-version-info*/ + sputf(stdout, "SQLite version %s %.19s%s\n" /*extra-version-info*/ "Enter \".help\" for usage hints.\n", sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET); if( warnInmemoryDb ){ - oputz("Connected to a "); + sputz(stdout, "Connected to a "); printBold("transient in-memory database"); - oputz(".\nUse \".open FILENAME\" to reopen on a" + sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a" " persistent database.\n"); } zHistory = getenv("SQLITE_HISTORY"); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 53c037c3b..4a19fe918 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -3954,15 +3954,17 @@ void sqlite3_free_filename(sqlite3_filename); ** ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language -** text that describes the error, as either UTF-8 or UTF-16 respectively. +** text that describes the error, as either UTF-8 or UTF-16 respectively, +** or NULL if no error message is available. ** (See how SQLite handles [invalid UTF] for exceptions to this rule.) ** ^(Memory to hold the error message string is managed internally. ** The application does not need to worry about freeing the result. ** However, the error string might be overwritten or deallocated by ** subsequent calls to other SQLite interface functions.)^ ** -** ^The sqlite3_errstr() interface returns the English-language text -** that describes the [result code], as UTF-8. +** ^The sqlite3_errstr(E) interface returns the English-language text +** that describes the [result code] E, as UTF-8, or NULL if E is not an +** result code for which a text error message is available. ** ^(Memory to hold the error message string is managed internally ** and must not be freed by the application)^. ** @@ -8037,9 +8039,11 @@ int sqlite3_vfs_unregister(sqlite3_vfs*); ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() -** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable -** behavior.)^ +** will always return SQLITE_BUSY. In most cases the SQLite core only uses +** sqlite3_mutex_try() as an optimization, so this is acceptable +** behavior. The exceptions are unix builds that set the +** SQLITE_ENABLE_SETLK_TIMEOUT build option. In that case a working +** sqlite3_mutex_try() is required.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was ** previously entered by the same thread. The behavior @@ -8298,6 +8302,7 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 #define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ +#define SQLITE_TESTCTRL_JSON_SELFCHECK 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 6d88c7ab4..79a36e060 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -328,6 +328,19 @@ # undef SQLITE_USE_SEH #endif +/* +** Enable SQLITE_DIRECT_OVERFLOW_READ, unless the build explicitly +** disables it using -DSQLITE_DIRECT_OVERFLOW_READ=0 +*/ +#if defined(SQLITE_DIRECT_OVERFLOW_READ) && SQLITE_DIRECT_OVERFLOW_READ+1==1 + /* Disable if -DSQLITE_DIRECT_OVERFLOW_READ=0 */ +# undef SQLITE_DIRECT_OVERFLOW_READ +#else + /* In all other cases, enable */ +# define SQLITE_DIRECT_OVERFLOW_READ 1 +#endif + + /* ** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2. ** 0 means mutexes are permanently disable and the library is never @@ -2114,11 +2127,11 @@ struct FuncDestructor { #define MFUNCTION(zName, nArg, xPtr, xFunc) \ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } -#define JFUNCTION(zName, nArg, bUseCache, bWS, bRS, iArg, xFunc) \ +#define JFUNCTION(zName, nArg, bUseCache, bWS, bRS, bJsonB, iArg, xFunc) \ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_FUNC_CONSTANT|\ SQLITE_UTF8|((bUseCache)*SQLITE_FUNC_RUNONLY)|\ ((bRS)*SQLITE_SUBTYPE)|((bWS)*SQLITE_RESULT_SUBTYPE), \ - SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } + SQLITE_INT_TO_PTR(iArg|((bJsonB)*JSON_BLOB)),0,xFunc,0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ {nArg, SQLITE_FUNC_BUILTIN|\ SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ @@ -2753,6 +2766,7 @@ struct Index { unsigned isCovering:1; /* True if this is a covering index */ unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ + unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */ unsigned bNoQuery:1; /* Do not use this index to optimize queries */ unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */ unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */ @@ -2866,6 +2880,7 @@ struct AggInfo { int iOBTab; /* Ephemeral table to implement ORDER BY */ u8 bOBPayload; /* iOBTab has payload columns separate from key */ u8 bOBUnique; /* Enforce uniqueness on iOBTab keys */ + u8 bUseSubtype; /* Transfer subtype info through sorter */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ u32 selId; /* Select to which this AggInfo belongs */ @@ -3399,6 +3414,7 @@ struct NameContext { int nRef; /* Number of names resolved by this context */ int nNcErr; /* Number of errors encountered while resolving names */ int ncFlags; /* Zero or more NC_* flags defined below */ + u32 nNestedSelect; /* Number of nested selects using this NC */ Select *pWinSelect; /* SELECT statement for any window functions */ }; @@ -4115,6 +4131,9 @@ struct sqlite3_str { ** ** 3. Make a (read-only) copy of a read-only RCStr string using ** sqlite3RCStrRef(). +** +** "String" is in the name, but an RCStr object can also be used to hold +** binary data. */ struct RCStr { u64 nRCRef; /* Number of references */ @@ -4173,6 +4192,9 @@ struct Sqlite3Config { u8 bSmallMalloc; /* Avoid large memory allocations if true */ u8 bExtraSchemaChecks; /* Verify type,name,tbl_name in schema */ u8 bUseLongDouble; /* Make use of long double */ +#ifdef SQLITE_DEBUG + u8 bJsonSelfcheck; /* Double-check JSON parsing */ +#endif int mxStrlen; /* Maximum string length */ int neverCorrupt; /* Database is always well-formed */ int szLookaside; /* Default lookaside buffer size */ @@ -4799,6 +4821,7 @@ void sqlite3ExprOrderByAggregateError(Parse*,Expr*); void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); void sqlite3ExprDelete(sqlite3*, Expr*); +void sqlite3ExprDeleteGeneric(sqlite3*,void*); void sqlite3ExprDeferredDelete(Parse*, Expr*); void sqlite3ExprUnmapAndDelete(Parse*, Expr*); ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); @@ -4808,6 +4831,7 @@ void sqlite3ExprListSetSortOrder(ExprList*,int,int); void sqlite3ExprListSetName(Parse*,ExprList*,const Token*,int); void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*); void sqlite3ExprListDelete(sqlite3*, ExprList*); +void sqlite3ExprListDeleteGeneric(sqlite3*,void*); u32 sqlite3ExprListFlags(const ExprList*); int sqlite3IndexHasDuplicateRootPage(Index*); int sqlite3Init(sqlite3*, char**); @@ -4898,6 +4922,7 @@ void sqlite3CreateView(Parse*,Token*,Token*,Token*,ExprList*,Select*,int,int); void sqlite3DropTable(Parse*, SrcList*, int, int); void sqlite3CodeDropTable(Parse*, Table*, int, int); void sqlite3DeleteTable(sqlite3*, Table*); +void sqlite3DeleteTableGeneric(sqlite3*, void*); void sqlite3FreeIndex(sqlite3*, Index*); #ifndef SQLITE_OMIT_AUTOINCREMENT void sqlite3AutoincrementBegin(Parse *pParse); @@ -4934,6 +4959,7 @@ int sqlite3Select(Parse*, Select*, SelectDest*); Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, Expr*,ExprList*,u32,Expr*); void sqlite3SelectDelete(sqlite3*, Select*); +void sqlite3SelectDeleteGeneric(sqlite3*,void*); Table *sqlite3SrcListLookup(Parse*, SrcList*); int sqlite3IsReadOnly(Parse*, Table*, Trigger*); void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); @@ -5160,6 +5186,7 @@ int sqlite3Utf16ByteLen(const void *pData, int nChar); #endif int sqlite3Utf8CharLen(const char *pData, int nByte); u32 sqlite3Utf8Read(const u8**); +int sqlite3Utf8ReadLimited(const u8*, int, u32*); LogEst sqlite3LogEst(u64); LogEst sqlite3LogEstAdd(LogEst,LogEst); LogEst sqlite3LogEstFromDouble(double); @@ -5506,6 +5533,7 @@ const char *sqlite3JournalModename(int); void sqlite3CteDelete(sqlite3*,Cte*); With *sqlite3WithAdd(Parse*,With*,Cte*); void sqlite3WithDelete(sqlite3*,With*); + void sqlite3WithDeleteGeneric(sqlite3*,void*); With *sqlite3WithPush(Parse*, With*, u8); #else # define sqlite3CteNew(P,T,E,S) ((void*)0) diff --git a/src/sqliteLimit.h b/src/sqliteLimit.h index bfb596da2..abf59e1b3 100644 --- a/src/sqliteLimit.h +++ b/src/sqliteLimit.h @@ -187,7 +187,7 @@ ** max_page_count macro. */ #ifndef SQLITE_MAX_PAGE_COUNT -# define SQLITE_MAX_PAGE_COUNT 1073741823 +# define SQLITE_MAX_PAGE_COUNT 0xfffffffe /* 4294967294 */ #endif /* diff --git a/src/status.c b/src/status.c index 784016700..a462c9429 100644 --- a/src/status.c +++ b/src/status.c @@ -362,7 +362,7 @@ int sqlite3_db_status( case SQLITE_DBSTATUS_CACHE_MISS: case SQLITE_DBSTATUS_CACHE_WRITE:{ int i; - int nRet = 0; + u64 nRet = 0; assert( SQLITE_DBSTATUS_CACHE_MISS==SQLITE_DBSTATUS_CACHE_HIT+1 ); assert( SQLITE_DBSTATUS_CACHE_WRITE==SQLITE_DBSTATUS_CACHE_HIT+2 ); @@ -375,7 +375,7 @@ int sqlite3_db_status( *pHighwater = 0; /* IMP: R-42420-56072 */ /* IMP: R-54100-20147 */ /* IMP: R-29431-39229 */ - *pCurrent = nRet; + *pCurrent = (int)nRet & 0x7fffffff; break; } diff --git a/src/test1.c b/src/test1.c index 55be0596b..9c28259b4 100644 --- a/src/test1.c +++ b/src/test1.c @@ -8120,6 +8120,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( extern int sqlite3_prefixes_init(sqlite3*,char**,const sqlite3_api_routines*); #endif extern int sqlite3_qpvtab_init(sqlite3*,char**,const sqlite3_api_routines*); + extern int sqlite3_randomjson_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_regexp_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_remember_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_series_init(sqlite3*,char**,const sqlite3_api_routines*); @@ -8152,6 +8153,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( { "prefixes", sqlite3_prefixes_init }, #endif { "qpvtab", sqlite3_qpvtab_init }, + { "randomjson", sqlite3_randomjson_init }, { "regexp", sqlite3_regexp_init }, { "remember", sqlite3_remember_init }, { "series", sqlite3_series_init }, diff --git a/src/test_func.c b/src/test_func.c index fa5243826..80df48828 100644 --- a/src/test_func.c +++ b/src/test_func.c @@ -694,7 +694,8 @@ static int registerTestFunctions( { "test_extract", 2, SQLITE_UTF8, test_extract}, { "test_zeroblob", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, test_zeroblob}, { "test_getsubtype", 1, SQLITE_UTF8, test_getsubtype}, - { "test_setsubtype", 2, SQLITE_UTF8, test_setsubtype}, + { "test_setsubtype", 2, SQLITE_UTF8|SQLITE_RESULT_SUBTYPE, + test_setsubtype}, { "test_frombind", -1, SQLITE_UTF8, test_frombind}, }; int i; diff --git a/src/treeview.c b/src/treeview.c index 1fad8673d..2576532b6 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -781,7 +781,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ assert( pExpr->x.pList->nExpr==2 ); pY = pExpr->x.pList->a[0].pExpr; pZ = pExpr->x.pList->a[1].pExpr; - sqlite3TreeViewLine(pView, "BETWEEN"); + sqlite3TreeViewLine(pView, "BETWEEN%s", zFlgs); sqlite3TreeViewExpr(pView, pX, 1); sqlite3TreeViewExpr(pView, pY, 1); sqlite3TreeViewExpr(pView, pZ, 0); diff --git a/src/utf.c b/src/utf.c index 5f27babdf..216864f5c 100644 --- a/src/utf.c +++ b/src/utf.c @@ -164,7 +164,38 @@ u32 sqlite3Utf8Read( return c; } - +/* +** Read a single UTF8 character out of buffer z[], but reading no +** more than n characters from the buffer. z[] is not zero-terminated. +** +** Return the number of bytes used to construct the character. +** +** Invalid UTF8 might generate a strange result. No effort is made +** to detect invalid UTF8. +** +** At most 4 bytes will be read out of z[]. The return value will always +** be between 1 and 4. +*/ +int sqlite3Utf8ReadLimited( + const u8 *z, + int n, + u32 *piOut +){ + u32 c; + int i = 1; + assert( n>0 ); + c = z[0]; + if( c>=0xc0 ){ + c = sqlite3Utf8Trans1[c-0xc0]; + if( n>4 ) n = 4; + while( ip1]; memAboutToChange(p, pIn1); sqlite3VdbeMemIntegerify(pIn1); - pIn1->u.i += pOp->p2; + *(u64*)&pIn1->u.i += (u64)pOp->p2; break; } @@ -8180,9 +8180,10 @@ case OP_VCheck: { /* out2 */ pOut = &aMem[pOp->p2]; sqlite3VdbeMemSetNull(pOut); /* Innocent until proven guilty */ - assert( pOp->p4type==P4_TABLE ); + assert( pOp->p4type==P4_TABLEREF ); pTab = pOp->p4.pTab; assert( pTab!=0 ); + assert( pTab->nTabRef>0 ); assert( IsVirtual(pTab) ); if( pTab->u.vtab.p==0 ) break; pVtab = pTab->u.vtab.p->pVtab; @@ -8191,13 +8192,11 @@ case OP_VCheck: { /* out2 */ assert( pModule!=0 ); assert( pModule->iVersion>=4 ); assert( pModule->xIntegrity!=0 ); - pTab->nTabRef++; sqlite3VtabLock(pTab->u.vtab.p); assert( pOp->p1>=0 && pOp->p1nDb ); rc = pModule->xIntegrity(pVtab, db->aDb[pOp->p1].zDbSName, pTab->zName, pOp->p3, &zErr); sqlite3VtabUnlock(pTab->u.vtab.p); - sqlite3DeleteTable(db, pTab); if( rc ){ sqlite3_free(zErr); goto abort_due_to_error; @@ -8322,6 +8321,7 @@ case OP_VColumn: { /* ncycle */ const sqlite3_module *pModule; Mem *pDest; sqlite3_context sContext; + FuncDef nullFunc; VdbeCursor *pCur = p->apCsr[pOp->p1]; assert( pCur!=0 ); @@ -8339,6 +8339,9 @@ case OP_VColumn: { /* ncycle */ memset(&sContext, 0, sizeof(sContext)); sContext.pOut = pDest; sContext.enc = encoding; + nullFunc.pUserData = 0; + nullFunc.funcFlags = SQLITE_RESULT_SUBTYPE; + sContext.pFunc = &nullFunc; assert( pOp->p5==OPFLAG_NOCHNG || pOp->p5==0 ); if( pOp->p5 & OPFLAG_NOCHNG ){ sqlite3VdbeMemSetNull(pDest); @@ -8671,6 +8674,42 @@ case OP_ClrSubtype: { /* in1 */ break; } +/* Opcode: GetSubtype P1 P2 * * * +** Synopsis: r[P2] = r[P1].subtype +** +** Extract the subtype value from register P1 and write that subtype +** into register P2. If P1 has no subtype, then P1 gets a NULL. +*/ +case OP_GetSubtype: { /* in1 out2 */ + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + if( pIn1->flags & MEM_Subtype ){ + sqlite3VdbeMemSetInt64(pOut, pIn1->eSubtype); + }else{ + sqlite3VdbeMemSetNull(pOut); + } + break; +} + +/* Opcode: SetSubtype P1 P2 * * * +** Synopsis: r[P2].subtype = r[P1] +** +** Set the subtype value of register P2 to the integer from register P1. +** If P1 is NULL, clear the subtype from p2. +*/ +case OP_SetSubtype: { /* in1 out2 */ + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + if( pIn1->flags & MEM_Null ){ + pOut->flags &= ~MEM_Subtype; + }else{ + assert( pIn1->flags & MEM_Int ); + pOut->flags |= MEM_Subtype; + pOut->eSubtype = (u8)(pIn1->u.i & 0xff); + } + break; +} + /* Opcode: FilterAdd P1 * P3 P4 * ** Synopsis: filter(P1) += key(P3@P4) ** diff --git a/src/vdbe.h b/src/vdbe.h index f44f24f93..25bda6be7 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -126,6 +126,7 @@ typedef struct VdbeOpList VdbeOpList; #define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ #define P4_INTARRAY (-14) /* P4 is a vector of 32-bit integers */ #define P4_FUNCCTX (-15) /* P4 is a pointer to an sqlite3_context object */ +#define P4_TABLEREF (-16) /* Like P4_TABLE, but reference counted */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 3d4b9f76d..14c6091e0 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -152,7 +152,15 @@ int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ int rc = SQLITE_OK; Vdbe *p = (Vdbe*)pStmt; #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex; + sqlite3_mutex *mutex; +#endif +#ifdef SQLITE_ENABLE_API_ARMOR + if( pStmt==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif +#if SQLITE_THREADSAFE + mutex = p->db->mutex; #endif sqlite3_mutex_enter(mutex); for(i=0; inVar; i++){ @@ -942,9 +950,8 @@ int sqlite3_step(sqlite3_stmt *pStmt){ void *sqlite3_user_data(sqlite3_context *p){ #ifdef SQLITE_ENABLE_API_ARMOR if( p==0 ) return 0; -#else - assert( p && p->pFunc ); #endif + assert( p && p->pFunc ); return p->pFunc->pUserData; } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 1dbd1dc0a..420365e93 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -1400,6 +1400,10 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ if( db->pnBytesFreed==0 ) sqlite3VtabUnlock((VTable *)p4); break; } + case P4_TABLEREF: { + if( db->pnBytesFreed==0 ) sqlite3DeleteTable(db, (Table*)p4); + break; + } } } @@ -1527,7 +1531,7 @@ static void SQLITE_NOINLINE vdbeChangeP4Full( int n ){ if( pOp->p4type ){ - freeP4(p->db, pOp->p4type, pOp->p4.p); + assert( pOp->p4type > P4_FREE_IF_LE ); pOp->p4type = 0; pOp->p4.p = 0; } diff --git a/src/vtab.c b/src/vtab.c index a9cfcb9d7..f83921678 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -315,7 +315,6 @@ void sqlite3VtabUnlockList(sqlite3 *db){ if( p ){ db->pDisconnect = 0; - sqlite3ExpirePreparedStatements(db, 0); do { VTable *pNext = p->pNext; sqlite3VtabUnlock(p); diff --git a/src/wal.c b/src/wal.c index d63c13ffc..fd2eabfd9 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2004,6 +2004,19 @@ static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ } #ifdef SQLITE_ENABLE_SETLK_TIMEOUT + + +/* +** Attempt to enable blocking locks that block for nMs ms. Return 1 if +** blocking locks are successfully enabled, or 0 otherwise. +*/ +static int walEnableBlockingMs(Wal *pWal, int nMs){ + int rc = sqlite3OsFileControl( + pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&nMs + ); + return (rc==SQLITE_OK); +} + /* ** Attempt to enable blocking locks. Blocking locks are enabled only if (a) ** they are supported by the VFS, and (b) the database handle is configured @@ -2015,11 +2028,7 @@ static int walEnableBlocking(Wal *pWal){ if( pWal->db ){ int tmout = pWal->db->busyTimeout; if( tmout ){ - int rc; - rc = sqlite3OsFileControl( - pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout - ); - res = (rc==SQLITE_OK); + res = walEnableBlockingMs(pWal, tmout); } } return res; @@ -2068,20 +2077,10 @@ void sqlite3WalDb(Wal *pWal, sqlite3 *db){ pWal->db = db; } -/* -** Take an exclusive WRITE lock. Blocking if so configured. -*/ -static int walLockWriter(Wal *pWal){ - int rc; - walEnableBlocking(pWal); - rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); - walDisableBlocking(pWal); - return rc; -} #else # define walEnableBlocking(x) 0 # define walDisableBlocking(x) -# define walLockWriter(pWal) walLockExclusive((pWal), WAL_WRITE_LOCK, 1) +# define walEnableBlockingMs(pWal, ms) 0 # define sqlite3WalDb(pWal, db) #endif /* ifdef SQLITE_ENABLE_SETLK_TIMEOUT */ @@ -2682,7 +2681,9 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ } }else{ int bWriteLock = pWal->writeLock; - if( bWriteLock || SQLITE_OK==(rc = walLockWriter(pWal)) ){ + if( bWriteLock + || SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) + ){ pWal->writeLock = 1; if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ badHdr = walIndexTryHdr(pWal, pChanged); @@ -2690,7 +2691,8 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ /* If the wal-index header is still malformed even while holding ** a WRITE lock, it can only mean that the header is corrupted and ** needs to be reconstructed. So run recovery to do exactly that. - */ + ** Disable blocking locks first. */ + walDisableBlocking(pWal); rc = walIndexRecover(pWal); *pChanged = 1; } @@ -2900,6 +2902,37 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ return rc; } +/* +** The final argument passed to walTryBeginRead() is of type (int*). The +** caller should invoke walTryBeginRead as follows: +** +** int cnt = 0; +** do { +** rc = walTryBeginRead(..., &cnt); +** }while( rc==WAL_RETRY ); +** +** The final value of "cnt" is of no use to the caller. It is used by +** the implementation of walTryBeginRead() as follows: +** +** + Each time walTryBeginRead() is called, it is incremented. Once +** it reaches WAL_RETRY_PROTOCOL_LIMIT - indicating that walTryBeginRead() +** has many times been invoked and failed with WAL_RETRY - walTryBeginRead() +** returns SQLITE_PROTOCOL. +** +** + If SQLITE_ENABLE_SETLK_TIMEOUT is defined and walTryBeginRead() failed +** because a blocking lock timed out (SQLITE_BUSY_TIMEOUT from the OS +** layer), the WAL_RETRY_BLOCKED_MASK bit is set in "cnt". In this case +** the next invocation of walTryBeginRead() may omit an expected call to +** sqlite3OsSleep(). There has already been a delay when the previous call +** waited on a lock. +*/ +#define WAL_RETRY_PROTOCOL_LIMIT 100 +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +# define WAL_RETRY_BLOCKED_MASK 0x10000000 +#else +# define WAL_RETRY_BLOCKED_MASK 0 +#endif + /* ** Attempt to start a read transaction. This might fail due to a race or ** other transient condition. When that happens, it returns WAL_RETRY to @@ -2950,13 +2983,16 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** so it takes care to hold an exclusive lock on the corresponding ** WAL_READ_LOCK() while changing values. */ -static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ +static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */ u32 mxReadMark; /* Largest aReadMark[] value */ int mxI; /* Index of largest aReadMark[] value */ int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ u32 mxFrame; /* Wal frame to lock to */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int nBlockTmout = 0; +#endif assert( pWal->readLock<0 ); /* Not currently locked */ @@ -2980,14 +3016,34 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** so that on the 100th (and last) RETRY we delay for 323 milliseconds. ** The total delay time before giving up is less than 10 seconds. */ - if( cnt>5 ){ + (*pCnt)++; + if( *pCnt>5 ){ int nDelay = 1; /* Pause time in microseconds */ - if( cnt>100 ){ + int cnt = (*pCnt & ~WAL_RETRY_BLOCKED_MASK); + if( cnt>WAL_RETRY_PROTOCOL_LIMIT ){ VVA_ONLY( pWal->lockError = 1; ) return SQLITE_PROTOCOL; } - if( cnt>=10 ) nDelay = (cnt-9)*(cnt-9)*39; + if( *pCnt>=10 ) nDelay = (cnt-9)*(cnt-9)*39; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* In SQLITE_ENABLE_SETLK_TIMEOUT builds, configure the file-descriptor + ** to block for locks for approximately nDelay us. This affects three + ** locks: (a) the shared lock taken on the DMS slot in os_unix.c (if + ** using os_unix.c), (b) the WRITER lock taken in walIndexReadHdr() if the + ** first attempted read fails, and (c) the shared lock taken on the + ** read-mark. + ** + ** If the previous call failed due to an SQLITE_BUSY_TIMEOUT error, + ** then sleep for the minimum of 1us. The previous call already provided + ** an extra delay while it was blocking on the lock. + */ + nBlockTmout = (nDelay+998) / 1000; + if( !useWal && walEnableBlockingMs(pWal, nBlockTmout) ){ + if( *pCnt & WAL_RETRY_BLOCKED_MASK ) nDelay = 1; + } +#endif sqlite3OsSleep(pWal->pVfs, nDelay); + *pCnt &= ~WAL_RETRY_BLOCKED_MASK; } if( !useWal ){ @@ -2995,6 +3051,13 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ if( pWal->bShmUnreliable==0 ){ rc = walIndexReadHdr(pWal, pChanged); } +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + walDisableBlocking(pWal); + if( rc==SQLITE_BUSY_TIMEOUT ){ + rc = SQLITE_BUSY; + *pCnt |= WAL_RETRY_BLOCKED_MASK; + } +#endif if( rc==SQLITE_BUSY ){ /* If there is not a recovery running in another thread or process ** then convert BUSY errors to WAL_RETRY. If recovery is known to @@ -3109,9 +3172,19 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; } + (void)walEnableBlockingMs(pWal, nBlockTmout); rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); + walDisableBlocking(pWal); if( rc ){ - return rc==SQLITE_BUSY ? WAL_RETRY : rc; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ){ + *pCnt |= WAL_RETRY_BLOCKED_MASK; + } +#else + assert( rc!=SQLITE_BUSY_TIMEOUT ); +#endif + assert( (rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT ); + return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc; } /* Now that the read-lock has been obtained, check that neither the ** value in the aReadMark[] array or the contents of the wal-index @@ -3299,7 +3372,7 @@ static int walBeginReadTransaction(Wal *pWal, int *pChanged){ #endif do{ - rc = walTryBeginRead(pWal, pChanged, 0, ++cnt); + rc = walTryBeginRead(pWal, pChanged, 0, &cnt); }while( rc==WAL_RETRY ); testcase( (rc&0xff)==SQLITE_BUSY ); testcase( (rc&0xff)==SQLITE_IOERR ); @@ -3480,6 +3553,7 @@ static int walFindFrame( iRead = iFrame; } if( (nCollide--)==0 ){ + *piRead = 0; return SQLITE_CORRUPT_BKPT; } iKey = walNextHash(iKey); @@ -3783,7 +3857,7 @@ static int walRestartLog(Wal *pWal){ cnt = 0; do{ int notUsed; - rc = walTryBeginRead(pWal, ¬Used, 1, ++cnt); + rc = walTryBeginRead(pWal, ¬Used, 1, &cnt); }while( rc==WAL_RETRY ); assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */ testcase( (rc&0xff)==SQLITE_IOERR ); @@ -4204,10 +4278,9 @@ int sqlite3WalCheckpoint( if( pWal->readOnly ) return SQLITE_READONLY; WALTRACE(("WAL%p: checkpoint begins\n", pWal)); - /* Enable blocking locks, if possible. If blocking locks are successfully - ** enabled, set xBusy2=0 so that the busy-handler is never invoked. */ + /* Enable blocking locks, if possible. */ sqlite3WalDb(pWal, db); - (void)walEnableBlocking(pWal); + if( xBusy2 ) (void)walEnableBlocking(pWal); /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive ** "checkpoint" lock on the database file. @@ -4248,9 +4321,14 @@ int sqlite3WalCheckpoint( /* Read the wal-index header. */ SEH_TRY { if( rc==SQLITE_OK ){ + /* For a passive checkpoint, do not re-enable blocking locks after + ** reading the wal-index header. A passive checkpoint should not block + ** or invoke the busy handler. The only lock such a checkpoint may + ** attempt to obtain is a lock on a read-slot, and it should give up + ** immediately and do a partial checkpoint if it cannot obtain it. */ walDisableBlocking(pWal); rc = walIndexReadHdr(pWal, &isChanged); - (void)walEnableBlocking(pWal); + if( eMode2!=SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ sqlite3OsUnfetch(pWal->pDbFd, 0, 0); } diff --git a/src/where.c b/src/where.c index cfd5d5e1a..4ff2815ba 100644 --- a/src/where.c +++ b/src/where.c @@ -678,12 +678,22 @@ static void translateColumnToCopy( for(; iStartp1!=iTabCur ) continue; if( pOp->opcode==OP_Column ){ +#ifdef SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("TRANSLATE OP_Column to OP_Copy at %d\n", iStart); + } +#endif pOp->opcode = OP_Copy; pOp->p1 = pOp->p2 + iRegister; pOp->p2 = pOp->p3; pOp->p3 = 0; pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */ }else if( pOp->opcode==OP_Rowid ){ +#ifdef SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("TRANSLATE OP_Rowid to OP_Sequence at %d\n", iStart); + } +#endif pOp->opcode = OP_Sequence; pOp->p1 = iAutoidxCur; #ifdef SQLITE_ALLOW_ROWID_IN_VIEW @@ -2010,7 +2020,8 @@ static int whereRangeScanEst( ** sample, then assume they are 4x more selective. This brings ** the estimated selectivity more in line with what it would be ** if estimated without the use of STAT4 tables. */ - if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) ); + if( iLwrIdx==iUprIdx ){ nNew -= 20; } + assert( 20==sqlite3LogEst(4) ); }else{ nNew = 10; assert( 10==sqlite3LogEst(2) ); } @@ -2234,17 +2245,34 @@ void sqlite3WhereClausePrint(WhereClause *pWC){ #ifdef WHERETRACE_ENABLED /* ** Print a WhereLoop object for debugging purposes +** +** Format example: +** +** .--- Position in WHERE clause rSetup, rRun, nOut ---. +** | | +** | .--- selfMask nTerm ------. | +** | | | | +** | | .-- prereq Idx wsFlags----. | | +** | | | Name | | | +** | | | __|__ nEq ---. ___|__ | __|__ +** | / \ / \ / \ | / \ / \ / \ +** 1.002.001 t2.t2xy 2 f 010241 N 2 cost 0,56,31 */ -void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){ - WhereInfo *pWInfo = pWC->pWInfo; - int nb = 1+(pWInfo->pTabList->nSrc+3)/4; - SrcItem *pItem = pWInfo->pTabList->a + p->iTab; - Table *pTab = pItem->pTab; - Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1; - sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId, - p->iTab, nb, p->maskSelf, nb, p->prereq & mAll); - sqlite3DebugPrintf(" %12s", - pItem->zAlias ? pItem->zAlias : pTab->zName); +void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC){ + if( pWC ){ + WhereInfo *pWInfo = pWC->pWInfo; + int nb = 1+(pWInfo->pTabList->nSrc+3)/4; + SrcItem *pItem = pWInfo->pTabList->a + p->iTab; + Table *pTab = pItem->pTab; + Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1; + sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId, + p->iTab, nb, p->maskSelf, nb, p->prereq & mAll); + sqlite3DebugPrintf(" %12s", + pItem->zAlias ? pItem->zAlias : pTab->zName); + }else{ + sqlite3DebugPrintf("%c%2d.%03llx.%03llx %c%d", + p->cId, p->iTab, p->maskSelf, p->prereq & 0xfff, p->cId, p->iTab); + } if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ const char *zName; if( p->u.btree.pIndex && (zName = p->u.btree.pIndex->zName)!=0 ){ @@ -2281,6 +2309,15 @@ void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){ } } } +void sqlite3ShowWhereLoop(const WhereLoop *p){ + if( p ) sqlite3WhereLoopPrint(p, 0); +} +void sqlite3ShowWhereLoopList(const WhereLoop *p){ + while( p ){ + sqlite3ShowWhereLoop(p); + p = p->pNextLoop; + } +} #endif /* @@ -2393,46 +2430,60 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ } /* -** Return TRUE if all of the following are true: -** -** (1) X has the same or lower cost, or returns the same or fewer rows, -** than Y. -** (2) X uses fewer WHERE clause terms than Y -** (3) Every WHERE clause term used by X is also used by Y -** (4) X skips at least as many columns as Y -** (5) If X is a covering index, than Y is too -** -** Conditions (2) and (3) mean that X is a "proper subset" of Y. -** If X is a proper subset of Y then Y is a better choice and ought -** to have a lower cost. This routine returns TRUE when that cost -** relationship is inverted and needs to be adjusted. Constraint (4) -** was added because if X uses skip-scan less than Y it still might -** deserve a lower cost even if it is a proper subset of Y. Constraint (5) -** was added because a covering index probably deserves to have a lower cost -** than a non-covering index even if it is a proper subset. +** Return TRUE if X is a proper subset of Y but is of equal or less cost. +** In other words, return true if all constraints of X are also part of Y +** and Y has additional constraints that might speed the search that X lacks +** but the cost of running X is not more than the cost of running Y. +** +** In other words, return true if the cost relationwship between X and Y +** is inverted and needs to be adjusted. +** +** Case 1: +** +** (1a) X and Y use the same index. +** (1b) X has fewer == terms than Y +** (1c) Neither X nor Y use skip-scan +** (1d) X does not have a a greater cost than Y +** +** Case 2: +** +** (2a) X has the same or lower cost, or returns the same or fewer rows, +** than Y. +** (2b) X uses fewer WHERE clause terms than Y +** (2c) Every WHERE clause term used by X is also used by Y +** (2d) X skips at least as many columns as Y +** (2e) If X is a covering index, than Y is too */ static int whereLoopCheaperProperSubset( const WhereLoop *pX, /* First WhereLoop to compare */ const WhereLoop *pY /* Compare against this WhereLoop */ ){ int i, j; + if( pX->rRun>pY->rRun && pX->nOut>pY->nOut ) return 0; /* (1d) and (2a) */ + assert( (pX->wsFlags & WHERE_VIRTUALTABLE)==0 ); + assert( (pY->wsFlags & WHERE_VIRTUALTABLE)==0 ); + if( pX->u.btree.nEq < pY->u.btree.nEq /* (1b) */ + && pX->u.btree.pIndex==pY->u.btree.pIndex /* (1a) */ + && pX->nSkip==0 && pY->nSkip==0 /* (1c) */ + ){ + return 1; /* Case 1 is true */ + } if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ - return 0; /* X is not a subset of Y */ + return 0; /* (2b) */ } - if( pX->rRun>pY->rRun && pX->nOut>pY->nOut ) return 0; - if( pY->nSkip > pX->nSkip ) return 0; + if( pY->nSkip > pX->nSkip ) return 0; /* (2d) */ for(i=pX->nLTerm-1; i>=0; i--){ if( pX->aLTerm[i]==0 ) continue; for(j=pY->nLTerm-1; j>=0; j--){ if( pY->aLTerm[j]==pX->aLTerm[i] ) break; } - if( j<0 ) return 0; /* X not a subset of Y since term X[i] not used by Y */ + if( j<0 ) return 0; /* (2c) */ } if( (pX->wsFlags&WHERE_IDX_ONLY)!=0 && (pY->wsFlags&WHERE_IDX_ONLY)==0 ){ - return 0; /* Constraint (5) */ + return 0; /* (2e) */ } - return 1; /* All conditions meet */ + return 1; /* Case 2 is true */ } /* @@ -2922,7 +2973,10 @@ static int whereLoopAddBtreeIndex( assert( pNew->u.btree.nBtm==0 ); opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS; } - if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); + if( pProbe->bUnordered || pProbe->bLowQual ){ + if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); + if( pProbe->bLowQual ) opMask &= ~(WO_EQ|WO_IN|WO_IS); + } assert( pNew->u.btree.nEqnColumn ); assert( pNew->u.btree.nEqnKeyCol @@ -6027,7 +6081,10 @@ WhereInfo *sqlite3WhereBegin( ** field (type Bitmask) it must be aligned on an 8-byte boundary on ** some architectures. Hence the ROUND8() below. */ - nByteWInfo = ROUND8P(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); + nByteWInfo = ROUND8P(sizeof(WhereInfo)); + if( nTabList>1 ){ + nByteWInfo = ROUND8P(nByteWInfo + (nTabList-1)*sizeof(WhereLevel)); + } pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop)); if( db->mallocFailed ){ sqlite3DbFree(db, pWInfo); @@ -6589,6 +6646,11 @@ WhereInfo *sqlite3WhereBegin( pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); } +#ifdef WHERETRACE_ENABLED + /* Prevent harmless compiler warnings about debugging routines + ** being declared but never used */ + sqlite3ShowWhereLoopList(0); +#endif /* WHERETRACE_ENABLED */ return 0; } diff --git a/src/whereInt.h b/src/whereInt.h index 343aca9f3..f3cc5776a 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -502,7 +502,7 @@ Bitmask sqlite3WhereGetMask(WhereMaskSet*,int); #ifdef WHERETRACE_ENABLED void sqlite3WhereClausePrint(WhereClause *pWC); void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm); -void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC); +void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC); #endif WhereTerm *sqlite3WhereFindTerm( WhereClause *pWC, /* The WHERE clause to be searched */ diff --git a/test/aggnested.test b/test/aggnested.test index 5f033a246..f3539076b 100644 --- a/test/aggnested.test +++ b/test/aggnested.test @@ -358,8 +358,130 @@ do_execsql_test 6.2.2 { FROM t2 GROUP BY 'constant_string'; } {{}} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 7.0 { + CREATE TABLE invoice ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + amount DOUBLE PRECISION DEFAULT NULL, + name VARCHAR(100) DEFAULT NULL + ); + + INSERT INTO invoice (amount, name) VALUES + (4.0, 'Michael'), (15.0, 'Bara'), (4.0, 'Michael'), (6.0, 'John'); +} + +do_execsql_test 7.1 { + SELECT sum(amount), name + from invoice + group by name + having (select v > 6 from (select sum(amount) v) t) +} { + 15.0 Bara + 8.0 Michael +} + +do_execsql_test 7.2 { + SELECT (select 1 from (select sum(amount))) FROM invoice +} {1} + +do_execsql_test 8.0 { + CREATE TABLE t1(x INT); + INSERT INTO t1 VALUES(100); + INSERT INTO t1 VALUES(20); + INSERT INTO t1 VALUES(3); + SELECT (SELECT y FROM (SELECT sum(x) AS y) AS t2 ) FROM t1; +} {123} + +do_execsql_test 8.1 { + SELECT ( + SELECT y FROM ( + SELECT z AS y FROM (SELECT sum(x) AS z) AS t2 + ) + ) FROM t1; +} {123} + +do_execsql_test 8.2 { + SELECT ( + SELECT a FROM ( + SELECT y AS a FROM ( + SELECT z AS y FROM (SELECT sum(x) AS z) AS t2 + ) + ) + ) FROM t1; +} {123} + +#------------------------------------------------------------------------- +# dbsqlfuzz 04408efc51ae46897c4c122b407412045ed221b4 +# +reset_db +do_execsql_test 9.1 { + WITH out(i, j, k) AS ( + VALUES(1234, 5678, 9012) + ) + SELECT ( + SELECT ( + SELECT min(abc) = ( SELECT ( SELECT 1234 fROM (SELECT abc) ) ) + FROM ( + SELECT sum( out.i ) + ( SELECT sum( out.i ) ) AS abc FROM (SELECT out.j) + ) + ) + ) FROM out; +} {0} - +do_execsql_test 9.2 { + CREATE TABLE t1(a); + CREATE TABLE t2(b); + INSERT INTO t1 VALUES(1), (2), (3); + INSERT INTO t2 VALUES(4), (5), (6); + + SELECT ( + SELECT min(y) + (SELECT x) FROM ( + SELECT sum(a) AS x, b AS y FROM t2 + ) + ) + FROM t1; +} {10} + +do_execsql_test 9.3 { + SELECT ( + SELECT min(y) + (SELECT (SELECT x)) FROM ( + SELECT sum(a) AS x, b AS y FROM t2 + ) + ) + FROM t1; +} {10} + +do_execsql_test 9.4 { + SELECT ( + SELECT (SELECT x) FROM ( + SELECT sum(a) AS x, b AS y FROM t2 + ) GROUP BY y + ) + FROM t1; +} {6} + +do_execsql_test 9.5 { + SELECT ( + SELECT (SELECT (SELECT x)) FROM ( + SELECT sum(a) AS x, b AS y FROM t2 + ) GROUP BY y + ) + FROM t1; +} {6} + +# 2023-12-16 +# New test case for check-in [4470f657d2069972] from 2023-11-02 +# https://bugs.chromium.org/p/chromium/issues/detail?id=1511689 +# +do_execsql_test 10.1 { + DROP TABLE IF EXISTS t0; + DROP TABLE IF EXISTS t1; + CREATE TABLE t0(c1, c2); INSERT INTO t0 VALUES(1,2); + CREATE TABLE t1(c3, c4); INSERT INTO t1 VALUES(3,4); + SELECT * FROM t0 WHERE EXISTS (SELECT 1 FROM t1 GROUP BY c3 HAVING ( SELECT count(*) FROM (SELECT 1 UNION ALL SELECT sum(DISTINCT c1) ) ) ) BETWEEN 1 AND 1; +} {1 2} finish_test diff --git a/test/aggorderby.test b/test/aggorderby.test index f94c5ec89..eed1f83a7 100644 --- a/test/aggorderby.test +++ b/test/aggorderby.test @@ -118,5 +118,45 @@ do_execsql_test aggorderby-8.2 { SELECT sum(DISTINCT x ORDER BY y) FROM c; } 6 +# Subtype information is transfered through the sorter for aggregates +# that make use of subtype info. +# +do_execsql_test aggorderby-9.0 { + WITH c(x,y) AS (VALUES + ('{a:3}', 3), + ('[1,1]', 1), + ('[4,4]', 4), + ('{x:2}', 2)) + SELECT json_group_array(json(x) ORDER BY y) FROM c; +} {{[[1,1],{"x":2},{"a":3},[4,4]]}} +do_execsql_test aggorderby-9.1 { + WITH c(x,y) AS (VALUES + ('[4,4]', 4), + ('{a:3}', 3), + ('[4,4]', 4), + ('[1,1]', 1), + ('[4,4]', 4), + ('{x:2}', 2)) + SELECT json_group_array(DISTINCT json(x) ORDER BY y) FROM c; +} {{[[1,1],{"x":2},{"a":3},[4,4]]}} +do_execsql_test aggorderby-9.2 { + WITH c(x,y) AS (VALUES + ('{a:3}', 3), + ('[1,1]', 1), + ('[4,4]', 4), + ('{x:2}', 2)) + SELECT json_group_array(json(x) ORDER BY json(x)) FROM c; +} {{[[1,1],[4,4],{"a":3},{"x":2}]}} +do_execsql_test aggorderby-9.3 { + WITH c(x,y) AS (VALUES + ('[4,4]', 4), + ('{a:3}', 3), + ('[4,4]', 4), + ('[1,1]', 1), + ('[4,4]', 4), + ('{x:2}', 2)) + SELECT json_group_array(DISTINCT json(x) ORDER BY json(x)) FROM c; +} {{[[1,1],[4,4],{"a":3},{"x":2}]}} + finish_test diff --git a/test/date.test b/test/date.test index fb76dac8a..19cecc2db 100644 --- a/test/date.test +++ b/test/date.test @@ -146,6 +146,8 @@ datetest 2.49 {datetime('2003-10-22 12:24','0000 second')} {2003-10-22 12:24:00} datetest 2.50 {datetime('2003-10-22 12:24','0001 second')} {2003-10-22 12:24:01} datetest 2.51 {datetime('2003-10-22 12:24','nonsense')} NULL +datetest 2.60 {datetime('2023-02-31')} {2023-03-03 00:00:00} + datetest 3.1 {strftime('%d','2003-10-31 12:34:56.432')} 31 datetest 3.2.1 {strftime('pre%fpost','2003-10-31 12:34:56.432')} pre56.432post datetest 3.2.2 {strftime('%f','2003-10-31 12:34:59.9999999')} 59.999 @@ -452,6 +454,9 @@ datetest 13.31 {date('2001-01-01','+1.5 years')} {2002-07-02} datetest 13.32 {date('2002-01-01','+1.5 years')} {2003-07-02} datetest 13.33 {date('2002-01-01','-1.5 years')} {2000-07-02} datetest 13.34 {date('2001-01-01','-1.5 years')} {1999-07-02} +datetest 13.35 {date('2023-02-28')} {2023-02-28} +datetest 13.36 {date('2023-02-29')} {2023-03-01} +datetest 13.37 {date('2023-04-31')} {2023-05-01} # Test for issues reported by BareFeet (list.sql at tandb.com.au) # on mailing list on 2008-06-12. diff --git a/test/fts3integrity.test b/test/fts3integrity.test new file mode 100644 index 000000000..bcbc49dc3 --- /dev/null +++ b/test/fts3integrity.test @@ -0,0 +1,42 @@ +# 2023 December 16 +# +# 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 runs all tests. +# +# $Id: fts3.test,v 1.2 2008/07/23 18:17:32 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix fts3integrity + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts3 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts3(x); + INSERT INTO t1 VALUES('first row'); + INSERT INTO t1 VALUES('second row'); + + CREATE TABLE t2(x PRIMARY KEY); + INSERT INTO t2 VALUES('first row'); + INSERT INTO t2 VALUES('second row'); +} + +sqlite3 db2 test.db + +do_execsql_test -db db2 1.1 { + CREATE TABLE t3(x, y); +} + +do_execsql_test 1.2 { + PRAGMA integrity_check; +} {ok} + +finish_test diff --git a/test/func4.test b/test/func4.test index 924c1fa53..64c7a11ed 100644 --- a/test/func4.test +++ b/test/func4.test @@ -92,7 +92,7 @@ do_execsql_test func4-1.22 { } {{}} do_execsql_test func4-1.23 { SELECT tointeger(-9223372036854775808 - 1); -} {-9223372036854775808} +} {{}} do_execsql_test func4-1.24 { SELECT tointeger(-9223372036854775808); } {-9223372036854775808} @@ -269,7 +269,7 @@ ifcapable floatingpoint { } {-9.223372036854776e+18} do_execsql_test func4-2.24 { SELECT toreal(-9223372036854775808); - } {-9.223372036854776e+18} + } {{}} if {$highPrecision(2)} { do_execsql_test func4-2.25 { SELECT toreal(-9223372036854775808 + 1); @@ -277,7 +277,7 @@ ifcapable floatingpoint { } do_execsql_test func4-2.26 { SELECT toreal(-9223372036854775807 - 1); - } {-9.223372036854776e+18} + } {{}} if {$highPrecision(2)} { do_execsql_test func4-2.27 { SELECT toreal(-9223372036854775807); @@ -461,7 +461,7 @@ ifcapable check { catchsql { INSERT INTO t1 (x) VALUES ('-9223372036854775809'); } - } {0 {}} + } {1 {CHECK constraint failed: tointeger(x) IS NOT NULL}} if {$highPrecision(1)} { do_test func4-3.19 { catchsql { @@ -573,10 +573,10 @@ ifcapable floatingpoint { } {1} do_execsql_test func4-5.6 { SELECT tointeger(toreal(-9223372036854775808 - 1)); - } {-9223372036854775808} + } {{}} do_execsql_test func4-5.7 { SELECT tointeger(toreal(-9223372036854775808)); - } {-9223372036854775808} + } {{}} if {$highPrecision(2)} { do_execsql_test func4-5.8 { SELECT tointeger(toreal(-9223372036854775808 + 1)); diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index 23200a5f0..dd4912011 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -159,9 +159,10 @@ static struct GlobalVars { } g; /* -** Include the external vt02.c module. +** Include the external vt02.c and randomjson.c modules. */ extern int sqlite3_vt02_init(sqlite3*,char***,void*); +extern int sqlite3_randomjson_init(sqlite3*,char***,void*); /* @@ -1267,6 +1268,8 @@ int runCombinedDbSqlInput( } sqlite3_limit(cx.db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH, 100); sqlite3_hard_heap_limit64(heapLimit); + rc = 1; + sqlite3_test_control(SQLITE_TESTCTRL_JSON_SELFCHECK, &rc); if( nDb>=20 && aDb[18]==2 && aDb[19]==2 ){ aDb[18] = aDb[19] = 1; @@ -1295,6 +1298,9 @@ int runCombinedDbSqlInput( /* Add the vt02 virtual table */ sqlite3_vt02_init(cx.db, 0, 0); + /* Add the random_json() and random_json5() functions */ + sqlite3_randomjson_init(cx.db, 0, 0); + /* Add support for sqlite_dbdata and sqlite_dbptr virtual tables used ** by the recovery API */ sqlite3_dbdata_init(cx.db, 0, 0); diff --git a/test/json/README.md b/test/json/README.md index 55044ffca..4ebbda6d3 100644 --- a/test/json/README.md +++ b/test/json/README.md @@ -4,40 +4,63 @@ JSON inputs. # 1.0 Prerequisites - 1. Valgrind + * Standard SQLite build environment (SQLite source tree, compiler, make, etc.) - 2. Fossil + * Valgrind - 3. tclsh + * Fossil (only the "fossil xdiff" command is used by this procedure) + + * tclsh # 2.0 Setup - 1. Run: "`tclsh json-generator.tcl | sqlite3 json100mb.db`" to create + * Run: "`tclsh json-generator.tcl | sqlite3 json100mb.db`" to create the 100 megabyte test database. Do this so that the "json100mb.db" file lands in the directory from which you will run tests, not in the test/json subdirectory of the source tree. - 2. Build the baseline sqlite3.c file with sqlite3.h and shell.c. - ("`CFLAGS='-Os -g' make -e clean sqlite3.c`") + * Make a copy of "json100mb.db" into "jsonb100mb.db" - change the prefix + from "json" to "jsonb". + + * Bring up jsonb100mb.db in the sqlite3 command-line shell. + Convert all of the content into JSONB using a commands like this: + +> UPDATE data1 SET x=jsonb(x); +> VACUUM; + + * Build the baseline sqlite3.c file with sqlite3.h and shell.c. + +> make clean sqlite3.c - 3. Run "`sh json-speed-check.sh trunk`". This creates the baseline - profile in "jout-trunk.txt". + * Run "`sh json-speed-check.sh trunk`". This creates the baseline + profile in "jout-trunk.txt" for the preformance test using text JSON. + + * Run "`sh json-speed-check.sh trunk --jsonb`". This creates the + baseline profile in "joutb-trunk.txt" for the performance test + for processing JSONB + + * (Optional) Verify that the json100mb.db database really does contain + approximately 100MB of JSON content by running: + +> SELECT sum(length(x)) FROM data1; +> SELECT * FROM data1 WHERE NOT json_valid(x); # 3.0 Testing - 1. Build the sqlite3.c (with sqlite3.h and shell.c) to be tested. + * Build the sqlite3.c (with sqlite3.h and shell.c) to be tested. - 2. Run "`sh json-speed-check.sh x1`". The profile output will appear + * Run "`sh json-speed-check.sh x1`". The profile output will appear in jout-x1.txt. Substitute any label you want in place of "x1". - 3. Run the script shown below in the CLI. + * Run "`sh json-speed-check.sh x1 --jsonb`". The profile output will appear + in joutb-x1.txt. Substitute any label you want in place of "x1". + + * Run the script shown below in the CLI. Divide 2500 by the real elapse time from this test to get an estimate for number of MB/s that the JSON parser is able to process. -> ~~~~ -.open json100mb.db -.timer on -WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<25) -SELECT sum(json_valid(x)) FROM c, data1; -~~~~ +> .open json100mb.db +> .timer on +> WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<25) +> SELECT sum(json_valid(x)) FROM c, data1; diff --git a/test/json/json-speed-check.sh b/test/json/json-speed-check.sh index f7e7f1be5..682a7aeed 100755 --- a/test/json/json-speed-check.sh +++ b/test/json/json-speed-check.sh @@ -35,11 +35,13 @@ LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_PROGRESS_CALLBACK" LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_SHARED_CACHE" LEAN_OPTS="$LEAN_OPTS -DSQLITE_USE_ALLOCA" BASELINE="trunk" +TYPE="json" doExplain=0 doCachegrind=1 doVdbeProfile=0 doWal=1 doDiff=1 +doJsonB=0 while test "$1" != ""; do case $1 in --nodiff) @@ -54,6 +56,10 @@ while test "$1" != ""; do --gcc7) CC=gcc-7 ;; + --jsonb) + doJsonB=1 + TYPE="jsonb" + ;; -*) CC_OPTS="$CC_OPTS $1" ;; @@ -69,12 +75,13 @@ rm -f cachegrind.out.* jsonshell $CC -g -Os -Wall -I. $CC_OPTS ./shell.c ./sqlite3.c -o jsonshell -ldl -lpthread ls -l jsonshell | tee -a summary-$NAME.txt home=`echo $0 | sed -e 's,/[^/]*$,,'` -echo ./jsonshell json100mb.db "<$home/json-q1.txt" -valgrind --tool=cachegrind ./jsonshell json100mb.db <$home/json-q1.txt \ - 2>&1 | tee -a summary-$NAME.txt -cg_anno.tcl cachegrind.out.* >jout-$NAME.txt -echo '*****************************************************' >>jout-$NAME.txt -sed 's/^[0-9=-]\{9\}/==00000==/' summary-$NAME.txt >>jout-$NAME.txt -if test "$NAME" != "$BASELINE"; then - fossil xdiff --tk -c 20 jout-$BASELINE.txt jout-$NAME.txt +DB=$TYPE''100mb.db +echo ./jsonshell $DB "<$home/$TYPE-q1.txt" +valgrind --tool=cachegrind ./jsonshell json100mb_b.db <$home/$TYPE-q1.txt \ + 2>&1 | tee -a summary-$NAME.txt +cg_anno.tcl cachegrind.out.* >$TYPE-$NAME.txt +echo '*****************************************************' >>$TYPE-$NAME.txt +sed 's/^[0-9=-]\{9\}/==00000==/' summary-$NAME.txt >>$TYPE-$NAME.txt +if test "$NAME" != "$BASELINE" -a $doDiff -ne 0; then + fossil xdiff --tk -c 20 $TYPE-$BASELINE.txt $TYPE-$NAME.txt fi diff --git a/test/json/jsonb-q1.txt b/test/json/jsonb-q1.txt new file mode 100644 index 000000000..e78c63670 --- /dev/null +++ b/test/json/jsonb-q1.txt @@ -0,0 +1,24 @@ +.mode qbox +.timer on +.param set $label 'q87' +SELECT rowid, x->>$label FROM data1 WHERE x->>$label IS NOT NULL; + +CREATE TEMP TABLE t2(x JSON TEXT); +WITH RECURSIVE + c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<25000), + array1(y) AS ( + SELECT json_group_array( + json_object('x',x,'y',random(),'z',hex(randomblob(50))) + ) + FROM c + ), + c2(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c2 WHERE n<5) +INSERT INTO t2(x) + SELECT jsonb_object('a',n,'b',n*2,'c',y,'d',3,'e',5,'f',6) FROM array1, c2; +CREATE INDEX t2x1 ON t2(x->>'a'); +CREATE INDEX t2x2 ON t2(x->>'b'); +CREATE INDEX t2x3 ON t2(x->>'e'); +CREATE INDEX t2x4 ON t2(x->>'f'); +UPDATE t2 SET x=jsonb_replace(x,'$.f',(x->>'f')+1); +UPDATE t2 SET x=jsonb_set(x,'$.e',(x->>'f')-1); +UPDATE t2 SET x=jsonb_remove(x,'$.d'); diff --git a/test/json101.test b/test/json101.test index c62991bbb..bae68e734 100644 --- a/test/json101.test +++ b/test/json101.test @@ -36,6 +36,9 @@ do_execsql_test json101-1.2 { do_catchsql_test json101-1.3 { SELECT json_array(1,printf('%.1000c','x'),x'abcd',3); } {1 {JSON cannot hold BLOB values}} +do_catchsql_test json101-1.3b { + SELECT jsonb_array(1,printf('%.1000c','x'),x'abcd',3); +} {1 {JSON cannot hold BLOB values}} do_execsql_test json101-1.4 { SELECT json_array(-9223372036854775808,9223372036854775807,0,1,-1, 0.0, 1.0, -1.0, -1e99, +2e100, @@ -47,36 +50,83 @@ do_execsql_test json101-1.4 { 'abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ', 99); } {[-9223372036854775808,9223372036854775807,0,1,-1,0.0,1.0,-1.0,-1.0e+99,2.0e+100,"one","two","three",4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,null,21,22,23,24,25,26,27,28,29,30,31,"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ",99]} +do_execsql_test json101-1.4b { + SELECT json(jsonb_array(-9223372036854775808,9223372036854775807,0,1,-1, + 0.0, 1.0, -1.0, -1e99, +2e100, + 'one','two','three', + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, NULL, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 'abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ', + 99)); +} {[-9223372036854775808,9223372036854775807,0,1,-1,0.0,1.0,-1.0,-1.0e+99,2.0e+100,"one","two","three",4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,null,21,22,23,24,25,26,27,28,29,30,31,"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ",99]} do_execsql_test json101-2.1 { SELECT json_object('a',1,'b',2.5,'c',null,'d','String Test'); } {{{"a":1,"b":2.5,"c":null,"d":"String Test"}}} +do_execsql_test json101-2.1b { + SELECT json(jsonb_object('a',1,'b',2.5,'c',null,'d','String Test')); +} {{{"a":1,"b":2.5,"c":null,"d":"String Test"}}} do_catchsql_test json101-2.2 { SELECT json_object('a',printf('%.1000c','x'),2,2.5); } {1 {json_object() labels must be TEXT}} +do_catchsql_test json101-2.2b { + SELECT jsonb_object('a',printf('%.1000c','x'),2,2.5); +} {1 {json_object() labels must be TEXT}} +do_execsql_test json101-2.2.2 { + SELECT json_object('a',json_array('xyx',77,4.5),'x',2.5); +} {{{"a":["xyx",77,4.5],"x":2.5}}} +do_execsql_test json101-2.2.2b { + SELECT json(jsonb_object('a',json_array('xyx',77,4.5),'x',2.5)); +} {{{"a":["xyx",77,4.5],"x":2.5}}} +do_execsql_test json101-2.2.3 { + SELECT json_object('a',jsonb_array('xyx',77,4.5),'x',2.5); +} {{{"a":["xyx",77,4.5],"x":2.5}}} +do_execsql_test json101-2.2.3b { + SELECT json(jsonb_object('a',jsonb_array('xyx',77,4.5),'x',2.5)); +} {{{"a":["xyx",77,4.5],"x":2.5}}} do_catchsql_test json101-2.3 { SELECT json_object('a',1,'b'); } {1 {json_object() requires an even number of arguments}} do_catchsql_test json101-2.4 { SELECT json_object('a',printf('%.1000c','x'),'b',x'abcd'); } {1 {JSON cannot hold BLOB values}} +do_execsql_test json101-2.5 { + SELECT json_object('a',printf('%.10c','x'),'b',jsonb_array(1,2,3)); +} {{{"a":"xxxxxxxxxx","b":[1,2,3]}}} do_execsql_test json101-3.1 { SELECT json_replace('{"a":1,"b":2}','$.a','[3,4,5]'); } {{{"a":"[3,4,5]","b":2}}} +do_execsql_test json101-3.1b { + SELECT json(jsonb_replace('{"a":1,"b":2}','$.a','[3,4,5]')); +} {{{"a":"[3,4,5]","b":2}}} do_execsql_test json101-3.2 { SELECT json_replace('{"a":1,"b":2}','$.a',json('[3,4,5]')); } {{{"a":[3,4,5],"b":2}}} +do_execsql_test json101-3.2b { + SELECT json_replace('{"a":1,"b":2}','$.a',jsonb('[3,4,5]')); +} {{{"a":[3,4,5],"b":2}}} do_execsql_test json101-3.3 { SELECT json_type(json_set('{"a":1,"b":2}','$.b','{"x":3,"y":4}'),'$.b'); } {text} +do_execsql_test json101-3.3b { + SELECT json_type(jsonb_set('{"a":1,"b":2}','$.b','{"x":3,"y":4}'),'$.b'); +} {text} do_execsql_test json101-3.4 { SELECT json_type(json_set('{"a":1,"b":2}','$.b',json('{"x":3,"y":4}')),'$.b'); } {object} +do_execsql_test json101-3.4b { + SELECT json_type(jsonb_set('{"a":1,"b":2}','$.b',jsonb('{"x":3,"y":4}')),'$.b'); +} {object} ifcapable vtab { -do_execsql_test json101-3.5 { - SELECT fullkey, atom, '|' FROM json_tree(json_set('{}','$.x',123,'$.x',456)); -} {{$} {} | {$.x} 456 |} + do_execsql_test json101-3.5 { + SELECT fullkey, atom, '|' FROM json_tree(json_set('{}','$.x',123,'$.x',456)); + } {{$} {} | {$.x} 456 |} + do_execsql_test json101-3.5b { + SELECT fullkey, atom, '|' FROM json_tree(jsonb_set('{}','$.x',123,'$.x',456)); + } {{$} {} | {$.x} 456 |} } # Per rfc7159, any JSON value is allowed at the top level, and whitespace @@ -131,6 +181,13 @@ do_execsql_test json101-4.10 { WHERE json_extract(x,'$')<>x AND json_type(x) IN ('object','array'); } {4} +do_execsql_test json101-4.10b { + CREATE TABLE j1b AS SELECT jsonb(x) AS "x" FROM j1; + SELECT count(*) FROM j1b WHERE json_type(x) IN ('object','array'); + SELECT json(x) FROM j1b + WHERE json_extract(x,'$')<>json(x) + AND json_type(x) IN ('object','array'); +} {4} do_execsql_test json101-5.1 { CREATE TABLE j2(id INTEGER PRIMARY KEY, json, src); @@ -258,11 +315,17 @@ do_execsql_test json101-5.1 { } ]','https://adobe.github.io/Spry/samples/data_region/JSONDataSetSample.html'); SELECT count(*) FROM j2; -} {3} + CREATE TABLE j2b(id INTEGER PRIMARY KEY, json, src); + INSERT INTO J2b(id,json,src) SELECT id, jsonb(json), src FROM j2; + SELECT count(*) FROM j2b; +} {3 3} do_execsql_test json101-5.2 { SELECT id, json_valid(json), json_type(json), '|' FROM j2 ORDER BY id; } {1 1 object | 2 1 object | 3 1 array |} +do_execsql_test json101-5.2b { + SELECT id, json_valid(json,5), json_type(json), '|' FROM j2b ORDER BY id; +} {1 1 object | 2 1 object | 3 1 array |} ifcapable !vtab { finish_test @@ -277,6 +340,12 @@ do_execsql_test json101-5.3 { WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' ELSE '.'||key END); } {} +do_execsql_test json101-5.3b { + SELECT j2b.rowid, jx.rowid, fullkey, path, key + FROM j2b, json_tree(j2b.json) AS jx + WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' + ELSE '.'||key END); +} {} do_execsql_test json101-5.4 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx @@ -371,6 +440,13 @@ do_execsql_test json101-8.1 { UPDATE t8 SET b=json_array(a); SELECT b FROM t8; } {{["abc\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#xyz"]}} +do_execsql_test json101-8.1b { + DROP TABLE IF EXISTS t8; + CREATE TABLE t8(a,b); + INSERT INTO t8(a) VALUES('abc' || char(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) || 'xyz'); + UPDATE t8 SET b=jsonb_array(a); + SELECT json(b) FROM t8; +} {{["abc\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#xyz"]}} do_execsql_test json101-8.2 { SELECT a=json_extract(b,'$[0]') FROM t8; } {1} @@ -401,7 +477,7 @@ do_execsql_test json101-9.4 { SELECT json_quote(null); } {"null"} do_catchsql_test json101-9.5 { - SELECT json_quote(x'30313233'); + SELECT json_quote(x'3031323334'); } {1 {JSON cannot hold BLOB values}} do_catchsql_test json101-9.6 { SELECT json_quote(123,456) @@ -769,14 +845,23 @@ do_execsql_test json101-12.100 { } }'); } {} + do_execsql_test json101-12.110 { SELECT json_remove(x, '$.settings.layer2."dis.legomenon".forceDisplay') FROM t12; } {{{"settings":{"layer2":{"hapax.legomenon":{"forceDisplay":true,"transliterate":true,"add.footnote":true,"summary.report":true},"dis.legomenon":{"transliterate":false,"add.footnote":false,"summary.report":true},"tris.legomenon":{"forceDisplay":true,"transliterate":false,"add.footnote":false,"summary.report":false}}}}}} +do_execsql_test json101-12.110b { + SELECT json_remove(jsonb(x), '$.settings.layer2."dis.legomenon".forceDisplay') + FROM t12; +} {{{"settings":{"layer2":{"hapax.legomenon":{"forceDisplay":true,"transliterate":true,"add.footnote":true,"summary.report":true},"dis.legomenon":{"transliterate":false,"add.footnote":false,"summary.report":true},"tris.legomenon":{"forceDisplay":true,"transliterate":false,"add.footnote":false,"summary.report":false}}}}}} do_execsql_test json101-12.120 { SELECT json_extract(x, '$.settings.layer2."tris.legomenon"."summary.report"') FROM t12; } {0} +do_execsql_test json101-12.120b { + SELECT json_extract(jsonb(x), '$.settings.layer2."tris.legomenon"."summary.report"') + FROM t12; +} {0} # 2018-01-26 # ticket https://www.sqlite.org/src/tktview/80177f0c226ff54f6ddd41 @@ -840,16 +925,16 @@ do_execsql_test json101-14.170 { # do_execsql_test json101-15.100 { SELECT * FROM JSON_EACH('{"a":1, "b":2}'); -} {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} +} {a 1 integer 1 1 {} {$.a} {$} b 2 integer 2 5 {} {$.b} {$}} do_execsql_test json101-15.110 { SELECT xyz.* FROM JSON_EACH('{"a":1, "b":2}') AS xyz; -} {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} +} {a 1 integer 1 1 {} {$.a} {$} b 2 integer 2 5 {} {$.b} {$}} do_execsql_test json101-15.120 { SELECT * FROM (JSON_EACH('{"a":1, "b":2}')); -} {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} +} {a 1 integer 1 1 {} {$.a} {$} b 2 integer 2 5 {} {$.b} {$}} do_execsql_test json101-15.130 { SELECT xyz.* FROM (JSON_EACH('{"a":1, "b":2}')) AS xyz; -} {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} +} {a 1 integer 1 1 {} {$.a} {$} b 2 integer 2 5 {} {$.b} {$}} # 2019-11-10 # Mailing list bug report on the handling of surrogate pairs @@ -889,7 +974,7 @@ do_execsql_test json101-18.4 { } {6} do_catchsql_test json101-18.5 { SELECT json_extract('{"":8}', '$.'); -} {1 {JSON path error near ''}} +} {1 {bad JSON path: '$.'}} # 2022-08-29 https://sqlite.org/forum/forumpost/9b9e4716c0d7bbd1 # This is not a problem specifically with JSON functions. It is @@ -1054,6 +1139,29 @@ do_execsql_test json101-23.2 { FROM (SELECT json_set('[]','$[#]',0,'$[#]',1) AS j); } {{[0,1]} 0 1} - +# Insert/Set/Replace where the path specifies substructure that +# does not yet exist +# +proc tx x {return [string map [list ( \173 ) \175 ' \042 < \133 > \135] $x]} +foreach {id start path ins set repl} { + 1 {{}} {$.a.b.c} ('a':('b':('c':9))) ('a':('b':('c':9))) () + 2 {{a:4}} {$.a.b.c} ('a':4) ('a':4) ('a':4) + 3 {{a:{}}} {$.a.b.c} ('a':('b':('c':9))) ('a':('b':('c':9))) ('a':()) + 4 {[0,1,2]} {$[3].a[0].b} <0,1,2,('a':<('b':9)>)> <0,1,2,('a':<('b':9)>)> <0,1,2> + 5 {[0,1,2]} {$[1].a[0].b} <0,1,2> <0,1,2> <0,1,2> + 6 {[0,{},2]} {$[1].a[0].b} <0,('a':<('b':9)>),2> <0,('a':<('b':9)>),2> <0,(),2> + 7 {[0,1,2]} {$[3][0].b} <0,1,2,<('b':9)>> <0,1,2,<('b':9)>> <0,1,2> + 8 {[0,1,2]} {$[1][0].b} <0,1,2> <0,1,2> <0,1,2> +} { + do_execsql_test json101-24.$id.insert { + SELECT json_insert($start,$path,9); + } [list [tx $ins]] + do_execsql_test json101-24.$id.set { + SELECT json_set($start,$path,9); + } [list [tx $set]] + do_execsql_test json101-24.$id.replace { + SELECT json_replace($start,$path,9); + } [list [tx $repl]] +} finish_test diff --git a/test/json102.test b/test/json102.test index ce901efeb..15a54b47c 100644 --- a/test/json102.test +++ b/test/json102.test @@ -21,159 +21,492 @@ source $testdir/tester.tcl do_execsql_test json102-100 { SELECT json_object('ex','[52,3.14159]'); } {{{"ex":"[52,3.14159]"}}} +do_execsql_test json102-100b { + SELECT json(jsonb_object('ex','[52,3.14159]')); +} {{{"ex":"[52,3.14159]"}}} do_execsql_test json102-110 { SELECT json_object('ex',json('[52,3.14159]')); } {{{"ex":[52,3.14159]}}} +do_execsql_test json102-110-2 { + SELECT json(jsonb_object('ex',json('[52,3.14159]'))); +} {{{"ex":[52,3.14159]}}} +do_execsql_test json102-110-3 { + SELECT json_object('ex',jsonb('[52,3.14159]')); +} {{{"ex":[52,3.14159]}}} +do_execsql_test json102-110-3 { + SELECT json(jsonb_object('ex',jsonb('[52,3.14159]'))); +} {{{"ex":[52,3.14159]}}} do_execsql_test json102-120 { SELECT json_object('ex',json_array(52,3.14159)); } {{{"ex":[52,3.14159]}}} +do_execsql_test json102-120-2 { + SELECT json(jsonb_object('ex',json_array(52,3.14159))); +} {{{"ex":[52,3.14159]}}} +do_execsql_test json102-120-3 { + SELECT json_object('ex',jsonb_array(52,3.14159)); +} {{{"ex":[52,3.14159]}}} +do_execsql_test json102-120-4 { + SELECT json(jsonb_object('ex',jsonb_array(52,3.14159))); +} {{{"ex":[52,3.14159]}}} do_execsql_test json102-130 { SELECT json(' { "this" : "is", "a": [ "test" ] } '); } {{{"this":"is","a":["test"]}}} +do_execsql_test json102-130b { + SELECT json(jsonb(' { "this" : "is", "a": [ "test" ] } ')); +} {{{"this":"is","a":["test"]}}} do_execsql_test json102-140 { SELECT json_array(1,2,'3',4); } {{[1,2,"3",4]}} +do_execsql_test json102-140b { + SELECT json(jsonb_array(1,2,'3',4)); +} {{[1,2,"3",4]}} do_execsql_test json102-150 { SELECT json_array('[1,2]'); } {{["[1,2]"]}} +do_execsql_test json102-150b { + SELECT json(jsonb_array('[1,2]')); +} {{["[1,2]"]}} do_execsql_test json102-160 { SELECT json_array(json_array(1,2)); } {{[[1,2]]}} +do_execsql_test json102-160-2 { + SELECT json_array(jsonb_array(1,2)); +} {{[[1,2]]}} +do_execsql_test json102-160-3 { + SELECT json(jsonb_array(json_array(1,2))); +} {{[[1,2]]}} +do_execsql_test json102-160-4 { + SELECT json(jsonb_array(jsonb_array(1,2))); +} {{[[1,2]]}} do_execsql_test json102-170 { SELECT json_array(1,null,'3','[4,5]','{"six":7.7}'); } {{[1,null,"3","[4,5]","{\"six\":7.7}"]}} +do_execsql_test json102-170b { + SELECT json(jsonb_array(1,null,'3','[4,5]','{"six":7.7}')); +} {{[1,null,"3","[4,5]","{\"six\":7.7}"]}} do_execsql_test json102-180 { SELECT json_array(1,null,'3',json('[4,5]'),json('{"six":7.7}')); } {{[1,null,"3",[4,5],{"six":7.7}]}} +do_execsql_test json102-180-2 { + SELECT json_array(1,null,'3',jsonb('[4,5]'),json('{"six":7.7}')); +} {{[1,null,"3",[4,5],{"six":7.7}]}} +do_execsql_test json102-180-3 { + SELECT json(jsonb_array(1,null,'3',json('[4,5]'),json('{"six":7.7}'))); +} {{[1,null,"3",[4,5],{"six":7.7}]}} +do_execsql_test json102-180-4 { + SELECT json(jsonb_array(1,null,'3',jsonb('[4,5]'),jsonb('{"six":7.7}'))); +} {{[1,null,"3",[4,5],{"six":7.7}]}} do_execsql_test json102-190 { SELECT json_array_length('[1,2,3,4]'); } {{4}} +do_execsql_test json102-190b { + SELECT json_array_length(jsonb('[1,2,3,4]')); +} {{4}} do_execsql_test json102-191 { SELECT json_array_length( json_remove('[1,2,3,4]','$[2]') ); } {{3}} +do_execsql_test json102-191b { + SELECT json_array_length( jsonb_remove('[1,2,3,4]','$[2]') ); +} {{3}} do_execsql_test json102-200 { SELECT json_array_length('[1,2,3,4]', '$'); } {{4}} +do_execsql_test json102-200b { + SELECT json_array_length(jsonb('[1,2,3,4]'), '$'); +} {{4}} do_execsql_test json102-210 { SELECT json_array_length('[1,2,3,4]', '$[2]'); } {{0}} +do_execsql_test json102-210b { + SELECT json_array_length(jsonb('[1,2,3,4]'), '$[2]'); +} {{0}} +do_execsql_test json102-220 { + SELECT json_array_length('{"one":[1,2,3]}'); +} {{0}} do_execsql_test json102-220 { SELECT json_array_length('{"one":[1,2,3]}'); } {{0}} -do_execsql_test json102-230 { - SELECT json_array_length('{"one":[1,2,3]}', '$.one'); +do_execsql_test json102-230b { + SELECT json_array_length(jsonb('{"one":[1,2,3]}'), '$.one'); } {{3}} do_execsql_test json102-240 { SELECT json_array_length('{"one":[1,2,3]}', '$.two'); } {{}} +do_execsql_test json102-240b { + SELECT json_array_length(jsonb('{"one":[1,2,3]}'), '$.two'); +} {{}} do_execsql_test json102-250 { SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$'); } {{{"a":2,"c":[4,5,{"f":7}]}}} +do_execsql_test json102-250-2 { + SELECT json_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$'); +} {{{"a":2,"c":[4,5,{"f":7}]}}} +do_execsql_test json102-250-3 { + SELECT json(jsonb_extract('{"a":2,"c":[4,5,{"f":7}]}', '$')); +} {{{"a":2,"c":[4,5,{"f":7}]}}} +do_execsql_test json102-250-4 { + SELECT json(jsonb_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$')); +} {{{"a":2,"c":[4,5,{"f":7}]}}} do_execsql_test json102-260 { SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c'); } {{[4,5,{"f":7}]}} +do_execsql_test json102-260-2 { + SELECT json_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$.c'); +} {{[4,5,{"f":7}]}} +do_execsql_test json102-260-3 { + SELECT json(jsonb_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c')); +} {{[4,5,{"f":7}]}} +do_execsql_test json102-260-4 { + SELECT json(jsonb_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$.c')); +} {{[4,5,{"f":7}]}} do_execsql_test json102-270 { SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2]'); } {{{"f":7}}} +do_execsql_test json102-270-2 { + SELECT json_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$.c[2]'); +} {{{"f":7}}} +do_execsql_test json102-270-3 { + SELECT json(jsonb_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$.c[2]')); +} {{{"f":7}}} +do_execsql_test json102-270-4 { + SELECT json(jsonb_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2]')); +} {{{"f":7}}} do_execsql_test json102-280 { SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2].f'); } {{7}} +do_execsql_test json102-280b { + SELECT jsonb_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2].f'); +} {{7}} do_execsql_test json102-290 { SELECT json_extract('{"a":2,"c":[4,5],"f":7}','$.c','$.a'); } {{[[4,5],2]}} +do_execsql_test json102-290-2 { + SELECT json_extract(jsonb('{"a":2,"c":[4,5],"f":7}'),'$.c','$.a'); +} {{[[4,5],2]}} +do_execsql_test json102-290-3 { + SELECT json(jsonb_extract('{"a":2,"c":[4,5],"f":7}','$.c','$.a')); +} {{[[4,5],2]}} +do_execsql_test json102-290-4 { + SELECT json(jsonb_extract(jsonb('{"a":2,"c":[4,5],"f":7}'),'$.c','$.a')); +} {{[[4,5],2]}} do_execsql_test json102-300 { SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x'); } {{}} +do_execsql_test json102-300b { + SELECT jsonb_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x'); +} {{}} do_execsql_test json102-310 { SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x', '$.a'); } {{[null,2]}} +do_execsql_test json102-310-2 { + SELECT json_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$.x', '$.a'); +} {{[null,2]}} +do_execsql_test json102-310-3 { + SELECT json(jsonb_extract(jsonb('{"a":2,"c":[4,5,{"f":7}]}'), '$.x', '$.a')); +} {{[null,2]}} +do_execsql_test json102-310-43 { + SELECT json(jsonb_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x', '$.a')); +} {{[null,2]}} do_execsql_test json102-320 { SELECT json_insert('{"a":2,"c":4}', '$.a', 99); } {{{"a":2,"c":4}}} +do_execsql_test json102-320-2 { + SELECT json_insert(jsonb('{"a":2,"c":4}'), '$.a', 99); +} {{{"a":2,"c":4}}} +do_execsql_test json102-320-3 { + SELECT json(jsonb_insert('{"a":2,"c":4}', '$.a', 99)); +} {{{"a":2,"c":4}}} +do_execsql_test json102-320-4 { + SELECT json(jsonb_insert(jsonb('{"a":2,"c":4}'), '$.a', 99)); +} {{{"a":2,"c":4}}} do_execsql_test json102-330 { SELECT json_insert('{"a":2,"c":4}', '$.e', 99); } {{{"a":2,"c":4,"e":99}}} +do_execsql_test json102-330-2 { + SELECT json_insert(jsonb('{"a":2,"c":4}'), '$.e', 99); +} {{{"a":2,"c":4,"e":99}}} +do_execsql_test json102-330-3 { + SELECT json(jsonb_insert('{"a":2,"c":4}', '$.e', 99)); +} {{{"a":2,"c":4,"e":99}}} +do_execsql_test json102-330-4 { + SELECT json(jsonb_insert(jsonb('{"a":2,"c":4}'), '$.e', 99)); +} {{{"a":2,"c":4,"e":99}}} do_execsql_test json102-340 { SELECT json_replace('{"a":2,"c":4}', '$.a', 99); } {{{"a":99,"c":4}}} +do_execsql_test json102-340-2 { + SELECT json_replace(jsonb('{"a":2,"c":4}'), '$.a', 99); +} {{{"a":99,"c":4}}} +do_execsql_test json102-340-3 { + SELECT json(jsonb_replace('{"a":2,"c":4}', '$.a', 99)); +} {{{"a":99,"c":4}}} +do_execsql_test json102-340-4 { + SELECT json(jsonb_replace(jsonb('{"a":2,"c":4}'), '$.a', 99)); +} {{{"a":99,"c":4}}} do_execsql_test json102-350 { SELECT json_replace('{"a":2,"c":4}', '$.e', 99); } {{{"a":2,"c":4}}} +do_execsql_test json102-350-2 { + SELECT json_replace(jsonb('{"a":2,"c":4}'), '$.e', 99); +} {{{"a":2,"c":4}}} +do_execsql_test json102-350-3 { + SELECT json(jsonb_replace('{"a":2,"c":4}', '$.e', 99)); +} {{{"a":2,"c":4}}} +do_execsql_test json102-350-4 { + SELECT json(jsonb_replace(jsonb('{"a":2,"c":4}'), '$.e', 99)); +} {{{"a":2,"c":4}}} do_execsql_test json102-360 { SELECT json_set('{"a":2,"c":4}', '$.a', 99); } {{{"a":99,"c":4}}} +do_execsql_test json102-360-2 { + SELECT json_set(jsonb('{"a":2,"c":4}'), '$.a', 99); +} {{{"a":99,"c":4}}} +do_execsql_test json102-360-3 { + SELECT json(jsonb_set('{"a":2,"c":4}', '$.a', 99)); +} {{{"a":99,"c":4}}} +do_execsql_test json102-360-4 { + SELECT json(jsonb_set(jsonb('{"a":2,"c":4}'), '$.a', 99)); +} {{{"a":99,"c":4}}} do_execsql_test json102-370 { SELECT json_set('{"a":2,"c":4}', '$.e', 99); } {{{"a":2,"c":4,"e":99}}} +do_execsql_test json102-370-2 { + SELECT json_set(jsonb('{"a":2,"c":4}'), '$.e', 99); +} {{{"a":2,"c":4,"e":99}}} +do_execsql_test json102-370-3 { + SELECT json(jsonb_set('{"a":2,"c":4}', '$.e', 99)); +} {{{"a":2,"c":4,"e":99}}} +do_execsql_test json102-370-4 { + SELECT json(jsonb_set(jsonb('{"a":2,"c":4}'), '$.e', 99)); +} {{{"a":2,"c":4,"e":99}}} do_execsql_test json102-380 { SELECT json_set('{"a":2,"c":4}', '$.c', '[97,96]'); } {{{"a":2,"c":"[97,96]"}}} +do_execsql_test json102-380-2 { + SELECT json_set(jsonb('{"a":2,"c":4}'), '$.c', '[97,96]'); +} {{{"a":2,"c":"[97,96]"}}} +do_execsql_test json102-380-3 { + SELECT json(jsonb_set('{"a":2,"c":4}', '$.c', '[97,96]')); +} {{{"a":2,"c":"[97,96]"}}} +do_execsql_test json102-380-4 { + SELECT json(jsonb_set(jsonb('{"a":2,"c":4}'), '$.c', '[97,96]')); +} {{{"a":2,"c":"[97,96]"}}} do_execsql_test json102-390 { SELECT json_set('{"a":2,"c":4}', '$.c', json('[97,96]')); } {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-390-2 { + SELECT json_set(jsonb('{"a":2,"c":4}'), '$.c', json('[97,96]')); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-390-3 { + SELECT json(jsonb_set('{"a":2,"c":4}', '$.c', json('[97,96]'))); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-390-4 { + SELECT json(jsonb_set(jsonb('{"a":2,"c":4}'), '$.c', json('[97,96]'))); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-390-5 { + SELECT json_set('{"a":2,"c":4}', '$.c', jsonb('[97,96]')); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-390-6 { + SELECT json_set(jsonb('{"a":2,"c":4}'), '$.c', jsonb('[97,96]')); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-390-7 { + SELECT json(jsonb_set('{"a":2,"c":4}', '$.c', jsonb('[97,96]'))); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-390-8 { + SELECT json(jsonb_set(jsonb('{"a":2,"c":4}'), '$.c', jsonb('[97,96]'))); +} {{{"a":2,"c":[97,96]}}} do_execsql_test json102-400 { SELECT json_set('{"a":2,"c":4}', '$.c', json_array(97,96)); } {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-400-2 { + SELECT json_set(jsonb('{"a":2,"c":4}'), '$.c', json_array(97,96)); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-400-3 { + SELECT json(jsonb_set('{"a":2,"c":4}', '$.c', json_array(97,96))); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-400-4 { + SELECT json(jsonb_set(jsonb('{"a":2,"c":4}'), '$.c', json_array(97,96))); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-400-5 { + SELECT json_set('{"a":2,"c":4}', '$.c', jsonb_array(97,96)); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-400-6 { + SELECT json_set(jsonb('{"a":2,"c":4}'), '$.c', jsonb_array(97,96)); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-400-7 { + SELECT json(jsonb_set('{"a":2,"c":4}', '$.c', jsonb_array(97,96))); +} {{{"a":2,"c":[97,96]}}} +do_execsql_test json102-400-8 { + SELECT json(jsonb_set(jsonb('{"a":2,"c":4}'), '$.c', jsonb_array(97,96))); +} {{{"a":2,"c":[97,96]}}} do_execsql_test json102-410 { SELECT json_object('a',2,'c',4); } {{{"a":2,"c":4}}} +do_execsql_test json102-410b { + SELECT json(jsonb_object('a',2,'c',4)); +} {{{"a":2,"c":4}}} do_execsql_test json102-420 { SELECT json_object('a',2,'c','{e:5}'); } {{{"a":2,"c":"{e:5}"}}} +do_execsql_test json102-420b { + SELECT json(jsonb_object('a',2,'c','{e:5}')); +} {{{"a":2,"c":"{e:5}"}}} do_execsql_test json102-430 { SELECT json_object('a',2,'c',json_object('e',5)); } {{{"a":2,"c":{"e":5}}}} +do_execsql_test json102-430-2 { + SELECT json(jsonb_object('a',2,'c',json_object('e',5))); +} {{{"a":2,"c":{"e":5}}}} +do_execsql_test json102-430-3 { + SELECT json_object('a',2,'c',jsonb_object('e',5)); +} {{{"a":2,"c":{"e":5}}}} +do_execsql_test json102-430-4 { + SELECT json(jsonb_object('a',2,'c',jsonb_object('e',5))); +} {{{"a":2,"c":{"e":5}}}} do_execsql_test json102-440 { SELECT json_remove('[0,1,2,3,4]','$[2]'); } {{[0,1,3,4]}} +do_execsql_test json102-440-2 { + SELECT json_remove(jsonb('[0,1,2,3,4]'),'$[2]'); +} {{[0,1,3,4]}} +do_execsql_test json102-440-3 { + SELECT json(jsonb_remove('[0,1,2,3,4]','$[2]')); +} {{[0,1,3,4]}} +do_execsql_test json102-440-4 { + SELECT json(jsonb_remove(jsonb('[0,1,2,3,4]'),'$[2]')); +} {{[0,1,3,4]}} do_execsql_test json102-450 { SELECT json_remove('[0,1,2,3,4]','$[2]','$[0]'); } {{[1,3,4]}} +do_execsql_test json102-450-2 { + SELECT json_remove(jsonb('[0,1,2,3,4]'),'$[2]','$[0]'); +} {{[1,3,4]}} +do_execsql_test json102-450-3 { + SELECT json(jsonb_remove('[0,1,2,3,4]','$[2]','$[0]')); +} {{[1,3,4]}} +do_execsql_test json102-450-4 { + SELECT json(jsonb_remove(jsonb('[0,1,2,3,4]'),'$[2]','$[0]')); +} {{[1,3,4]}} do_execsql_test json102-460 { SELECT json_remove('[0,1,2,3,4]','$[0]','$[2]'); } {{[1,2,4]}} +do_execsql_test json102-460-2 { + SELECT json_remove(jsonb('[0,1,2,3,4]'),'$[0]','$[2]'); +} {{[1,2,4]}} +do_execsql_test json102-460-3 { + SELECT json(jsonb_remove('[0,1,2,3,4]','$[0]','$[2]')); +} {{[1,2,4]}} +do_execsql_test json102-460-4 { + SELECT json(jsonb_remove(jsonb('[0,1,2,3,4]'),'$[0]','$[2]')); +} {{[1,2,4]}} do_execsql_test json102-470 { SELECT json_remove('{"x":25,"y":42}'); } {{{"x":25,"y":42}}} +do_execsql_test json102-470-2 { + SELECT json_remove(jsonb('{"x":25,"y":42}')); +} {{{"x":25,"y":42}}} +do_execsql_test json102-470-3 { + SELECT json(jsonb_remove('{"x":25,"y":42}')); +} {{{"x":25,"y":42}}} +do_execsql_test json102-470-4 { + SELECT json(jsonb_remove(jsonb('{"x":25,"y":42}'))); +} {{{"x":25,"y":42}}} do_execsql_test json102-480 { SELECT json_remove('{"x":25,"y":42}','$.z'); } {{{"x":25,"y":42}}} +do_execsql_test json102-480-2 { + SELECT json_remove(jsonb('{"x":25,"y":42}'),'$.z'); +} {{{"x":25,"y":42}}} +do_execsql_test json102-480-3 { + SELECT json(jsonb_remove('{"x":25,"y":42}','$.z')); +} {{{"x":25,"y":42}}} +do_execsql_test json102-480-4 { + SELECT json(jsonb_remove(jsonb('{"x":25,"y":42}'),'$.z')); +} {{{"x":25,"y":42}}} do_execsql_test json102-490 { SELECT json_remove('{"x":25,"y":42}','$.y'); } {{{"x":25}}} +do_execsql_test json102-490-2 { + SELECT json_remove(jsonb('{"x":25,"y":42}'),'$.y'); +} {{{"x":25}}} +do_execsql_test json102-490-3 { + SELECT json(jsonb_remove('{"x":25,"y":42}','$.y')); +} {{{"x":25}}} +do_execsql_test json102-490-4 { + SELECT json(jsonb_remove(jsonb('{"x":25,"y":42}'),'$.y')); +} {{{"x":25}}} do_execsql_test json102-500 { SELECT json_remove('{"x":25,"y":42}','$'); } {{}} +do_execsql_test json102-500-2 { + SELECT json_remove(jsonb('{"x":25,"y":42}'),'$'); +} {{}} +do_execsql_test json102-500-3 { + SELECT json(jsonb_remove('{"x":25,"y":42}','$')); +} {{}} +do_execsql_test json102-500-4 { + SELECT json(jsonb_remove(jsonb('{"x":25,"y":42}'),'$')); +} {{}} do_execsql_test json102-510 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}'); } {{object}} +do_execsql_test json102-510b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778'); +} {{object}} do_execsql_test json102-520 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$'); } {{object}} +do_execsql_test json102-520b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$'); +} {{object}} do_execsql_test json102-530 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a'); } {{array}} +do_execsql_test json102-530b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a'); +} {{array}} do_execsql_test json102-540 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[0]'); } {{integer}} +do_execsql_test json102-540b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a[0]'); +} {{integer}} do_execsql_test json102-550 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[1]'); } {{real}} +do_execsql_test json102-550b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a[1]'); +} {{real}} do_execsql_test json102-560 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[2]'); } {{true}} +do_execsql_test json102-560b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a[2]'); +} {{true}} do_execsql_test json102-570 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[3]'); } {{false}} +do_execsql_test json102-570b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a[3]'); +} {{false}} do_execsql_test json102-580 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[4]'); } {{null}} +do_execsql_test json102-580b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a[4]'); +} {{null}} do_execsql_test json102-590 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[5]'); } {{text}} +do_execsql_test json102-590b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a[5]'); +} {{text}} do_execsql_test json102-600 { SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[6]'); } {{}} +do_execsql_test json102-600b { + SELECT json_type(x'cc0f1761cb0b133235332e350102001778','$.a[6]'); +} {{}} do_execsql_test json102-610 { SELECT json_valid(char(123)||'"x":35'||char(125)); } {{1}} @@ -183,17 +516,24 @@ do_execsql_test json102-620 { ifcapable vtab { do_execsql_test json102-1000 { - CREATE TABLE user(name,phone); + CREATE TABLE user(name,phone,phoneb); INSERT INTO user(name,phone) VALUES ('Alice','["919-555-2345","804-555-3621"]'), ('Bob','["201-555-8872"]'), ('Cindy','["704-555-9983"]'), ('Dave','["336-555-8421","704-555-4321","803-911-4421"]'); + UPDATE user SET phoneb=jsonb(phone); SELECT DISTINCT user.name FROM user, json_each(user.phone) WHERE json_each.value LIKE '704-%' ORDER BY 1; } {Cindy Dave} +do_execsql_test json102-1000b { + SELECT DISTINCT user.name + FROM user, json_each(user.phoneb) + WHERE json_each.value LIKE '704-%' + ORDER BY 1; +} {Cindy Dave} do_execsql_test json102-1010 { UPDATE user @@ -253,6 +593,12 @@ do_execsql_test json102-1110 { WHERE json_tree.type NOT IN ('object','array') ORDER BY +big.rowid, +json_tree.id } $correct_answer +do_execsql_test json102-1110b { + SELECT big.rowid, fullkey, value + FROM big, json_tree(jsonb(big.json)) + WHERE json_tree.type NOT IN ('object','array') + ORDER BY +big.rowid, +json_tree.id +} $correct_answer do_execsql_test json102-1120 { SELECT big.rowid, fullkey, atom FROM big, json_tree(big.json) diff --git a/test/json105.test b/test/json105.test index 83face188..509db94e1 100644 --- a/test/json105.test +++ b/test/json105.test @@ -13,7 +13,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -set testprefix json104 +set testprefix json105 # This is the example from pages 2 and 3 of RFC-7396 db eval { @@ -96,18 +96,18 @@ json_replace_test 80 {'$.b[#-1]','AAA','$.b[#-1]','BBB'} \ do_catchsql_test json105-6.10 { SELECT json_extract(j, '$.b[#-]') FROM t1; -} {1 {JSON path error near '[#-]'}} +} {1 {bad JSON path: '$.b[#-]'}} do_catchsql_test json105-6.20 { SELECT json_extract(j, '$.b[#9]') FROM t1; -} {1 {JSON path error near '[#9]'}} +} {1 {bad JSON path: '$.b[#9]'}} do_catchsql_test json105-6.30 { SELECT json_extract(j, '$.b[#+2]') FROM t1; -} {1 {JSON path error near '[#+2]'}} +} {1 {bad JSON path: '$.b[#+2]'}} do_catchsql_test json105-6.40 { SELECT json_extract(j, '$.b[#-1') FROM t1; -} {1 {JSON path error near '[#-1'}} +} {1 {bad JSON path: '$.b[#-1'}} do_catchsql_test json105-6.50 { SELECT json_extract(j, '$.b[#-1x]') FROM t1; -} {1 {JSON path error near '[#-1x]'}} +} {1 {bad JSON path: '$.b[#-1x]'}} finish_test diff --git a/test/json106.test b/test/json106.test new file mode 100644 index 000000000..23fa02843 --- /dev/null +++ b/test/json106.test @@ -0,0 +1,73 @@ +# 2023-12-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. +# +#*********************************************************************** +# Invariant tests for JSON built around the randomjson extension +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix json106 + +# These tests require virtual table "json_tree" to run. +ifcapable !vtab { finish_test ; return } + +load_static_extension db randomjson +db eval { + CREATE TEMP TABLE t1(j0,j5,p); + CREATE TEMP TABLE kv(n,key,val); +} +unset -nocomplain ii +for {set ii 1} {$ii<=5000} {incr ii} { + do_execsql_test $ii.1 { + DELETE FROM t1; + INSERT INTO t1(j0,j5) VALUES(random_json($ii),random_json5($ii)); + SELECT json_valid(j0), json_valid(j5,2) FROM t1; + } {1 1} + do_execsql_test $ii.2 { + SELECT count(*) + FROM t1, json_tree(j0) AS rt + WHERE rt.type NOT IN ('object','array') + AND rt.atom IS NOT (j0 ->> rt.fullkey); + } 0 + do_execsql_test $ii.3 { + SELECT count(*) + FROM t1, json_tree(j5) AS rt + WHERE rt.type NOT IN ('object','array') + AND rt.atom IS NOT (j0 ->> rt.fullkey); + } 0 + do_execsql_test $ii.4 { + DELETE FROM kv; + INSERT INTO kv + SELECT rt.rowid, rt.fullkey, rt.atom + FROM t1, json_tree(j0) AS rt + WHERE rt.type NOT IN ('object','array'); + } + do_execsql_test $ii.5 { + SELECT count(*) + FROM t1, kv + WHERE key NOT LIKE '%]' + AND json_remove(j5,key)->>key IS NOT NULL + } 0 + do_execsql_test $ii.6 { + SELECT count(*) + FROM t1, kv + WHERE key NOT LIKE '%]' + AND json_insert(json_remove(j5,key),key,val)->>key IS NOT val + } 0 + do_execsql_test $ii.7 { + UPDATE t1 SET p=json_patch(j0,j5); + SELECT count(*) + FROM t1, kv + WHERE p->>key IS NOT val + } 0 +} + + +finish_test diff --git a/test/json501.test b/test/json501.test index 3318eea7f..40b3b563d 100644 --- a/test/json501.test +++ b/test/json501.test @@ -252,13 +252,13 @@ do_execsql_test 8.11 { do_execsql_test 9.1 { WITH c(x) AS (VALUES('{x: +Infinity}')) SELECT x->>'x', json(x) FROM c; -} {Inf {{"x":9.0e999}}} +} {Inf {{"x":9e999}}} do_execsql_test 9.2 { WITH c(x) AS (VALUES('{x: -Infinity}')) SELECT x->>'x', json(x) FROM c; -} {-Inf {{"x":-9.0e999}}} +} {-Inf {{"x":-9e999}}} do_execsql_test 9.3 { WITH c(x) AS (VALUES('{x: Infinity}')) SELECT x->>'x', json(x) FROM c; -} {Inf {{"x":9.0e999}}} +} {Inf {{"x":9e999}}} do_execsql_test 9.4 { WITH c(x) AS (VALUES('{x: NaN}')) SELECT x->>'x', json(x) FROM c; } {{} {{"x":null}}} diff --git a/test/json502.test b/test/json502.test index 595bf6331..cc14b8cb7 100644 --- a/test/json502.test +++ b/test/json502.test @@ -36,5 +36,32 @@ do_catchsql_test 2.3 { SELECT '{a:null,{"h":[1,[1,2,3]],"j":"abc"}:true}'->'$h[#-1]'; } {1 {malformed JSON}} +# Verify that escaped label names are compared correctly. +# +do_execsql_test 3.1 { + SELECT '{"a\x62c":123}' ->> 'abc'; +} 123 +do_execsql_test 3.2 { + SELECT '{"abc":123}' ->> 'a\x62c'; +} 123 + +db null null +do_execsql_test 3.3 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(json_insert('{}','$.a\',111,'$."b\\"',222)); + INSERT INTO t1 VALUES(jsonb_insert('{}','$.a\',111,'$."b\\"',222)); + SELECT x->'$.a\', x->'$.a\\', x->'$."a\\"', x->'$."b\\"' FROM t1; +} {111 null 111 222 111 null 111 222} + +do_execsql_test 3.4 { + SELECT json_patch('{"a\x62c":123}','{"ab\x63":456}') ->> 'abc'; +} 456 + +ifcapable vtab { + do_execsql_test 4.1 { + SELECT * FROM json_tree('{"\u0017":1}','$."\x17"'); + } {{\x17} 1 integer 1 1 null {$."\x17"} {$}} +} finish_test diff --git a/test/jsonb01.test b/test/jsonb01.test new file mode 100644 index 000000000..d1b53ae6c --- /dev/null +++ b/test/jsonb01.test @@ -0,0 +1,49 @@ +# 2023-11-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. +# +#*********************************************************************** +# Test cases for JSONB +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test jsonb01-1.1 { + CREATE TABLE t1(x JSON BLOB); + INSERT INTO t1 VALUES(jsonb('{a:5,b:{x:10,y:11},c:[1,2,3,4]}')); +} +foreach {id path res} { + 1 {$.a} {{{"b":{"x":10,"y":11},"c":[1,2,3,4]}}} + 2 {$.b} {{{"a":5,"c":[1,2,3,4]}}} + 3 {$.c} {{{"a":5,"b":{"x":10,"y":11}}}} + 4 {$.d} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,3,4]}}} + 5 {$.b.x} {{{"a":5,"b":{"y":11},"c":[1,2,3,4]}}} + 6 {$.b.y} {{{"a":5,"b":{"x":10},"c":[1,2,3,4]}}} + 7 {$.c[0]} {{{"a":5,"b":{"x":10,"y":11},"c":[2,3,4]}}} + 8 {$.c[1]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,3,4]}}} + 9 {$.c[2]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,4]}}} + 10 {$.c[3]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,3]}}} + 11 {$.c[4]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,3,4]}}} + 12 {$.c[#]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,3,4]}}} + 13 {$.c[#-1]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,3]}}} + 14 {$.c[#-2]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,4]}}} + 15 {$.c[#-3]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,3,4]}}} + 16 {$.c[#-4]} {{{"a":5,"b":{"x":10,"y":11},"c":[2,3,4]}}} + 17 {$.c[#-5]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,3,4]}}} + 18 {$.c[#-6]} {{{"a":5,"b":{"x":10,"y":11},"c":[1,2,3,4]}}} +} { + do_execsql_test jsonb01-1.2.$id.1 { + SELECT json(jsonb_remove(x,$path)) FROM t1; + } $res + do_execsql_test jsonb01-1.2.$id.2 { + SELECT json_remove(x,$path) FROM t1; + } $res +} + +finish_test diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl deleted file mode 100644 index 4ed57a9c8..000000000 --- a/test/releasetest_data.tcl +++ /dev/null @@ -1,845 +0,0 @@ -# 2019 August 01 -# -# 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 a program that produces scripts (either shell scripts -# or batch files) to implement a particular test that is part of the SQLite -# release testing procedure. For example, to run veryquick.test with a -# specified set of -D compiler switches. -# -# A "configuration" is a set of options passed to [./configure] and [make] -# to build the SQLite library in a particular fashion. A "platform" is a -# list of tests; most platforms are named after the hardware/OS platform -# that the tests will be run on as part of the release procedure. Each -# "test" is a combination of a configuration and a makefile target (e.g. -# "fulltest"). The program may be invoked as follows: -# -set USAGE { -$argv0 script ?-msvc? CONFIGURATION TARGET - Given a configuration and make target, return a bash (or, if -msvc - is specified, batch) script to execute the test. The first argument - passed to the script must be a directory containing SQLite source code. - -$argv0 configurations - List available configurations. - -$argv0 platforms - List available platforms. - -$argv0 tests ?-nodebug? PLATFORM - List tests in a specified platform. If the -nodebug switch is - specified, synthetic debug/ndebug configurations are omitted. Each - test is a combination of a configuration and a makefile target. -} - -# Omit comments (text between # and \n) in a long multi-line string. -# -proc strip_comments {in} { - regsub -all {#[^\n]*\n} $in {} out - return $out -} - -array set ::Configs [strip_comments { - "Default" { - -O2 - --disable-amalgamation --disable-shared - --enable-session - -DSQLITE_ENABLE_RBU - } - "All-Debug" { - --enable-debug --enable-all - } - "All-O0" { - -O0 --enable-all - } - "Sanitize" { - CC=clang -fsanitize=address,undefined - -DSQLITE_ENABLE_STAT4 - -DCONFIG_SLOWDOWN_FACTOR=5.0 - --enable-debug - --enable-all - } - "Stdcall" { - -DUSE_STDCALL=1 - -O2 - } - "Have-Not" { - # The "Have-Not" configuration sets all possible -UHAVE_feature options - # in order to verify that the code works even on platforms that lack - # these support services. - -DHAVE_FDATASYNC=0 - -DHAVE_GMTIME_R=0 - -DHAVE_ISNAN=0 - -DHAVE_LOCALTIME_R=0 - -DHAVE_LOCALTIME_S=0 - -DHAVE_MALLOC_USABLE_SIZE=0 - -DHAVE_STRCHRNUL=0 - -DHAVE_USLEEP=0 - -DHAVE_UTIME=0 - } - "Unlock-Notify" { - -O2 - -DSQLITE_ENABLE_UNLOCK_NOTIFY - -DSQLITE_THREADSAFE - -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 - } - "User-Auth" { - -O2 - -DSQLITE_USER_AUTHENTICATION=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 - -DSQLITE_ENABLE_STMT_SCANSTATUS - -DSQLITE_LIKE_DOESNT_MATCH_BLOBS - -DSQLITE_ENABLE_CURSOR_HINTS - } - "Check-Symbols" { - -DSQLITE_MEMDEBUG=1 - -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_MEMSYS5=1 - -DSQLITE_ENABLE_MEMSYS3=1 - -DSQLITE_ENABLE_COLUMN_METADATA=1 - -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 - -DSQLITE_SECURE_DELETE=1 - -DSQLITE_SOUNDEX=1 - -DSQLITE_ENABLE_ATOMIC_WRITE=1 - -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 - -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_STMT_SCANSTATUS - --enable-fts5 --enable-session - } - "Debug-One" { - --disable-shared - -O2 -funsigned-char - -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_COLUMN_METADATA=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_HIDDEN_COLUMNS - -DSQLITE_MAX_ATTACHED=125 - -DSQLITE_MUTATION_TEST - --enable-fts5 - } - "Debug-Two" { - -DSQLITE_DEFAULT_MEMSTATUS=0 - -DSQLITE_MAX_EXPR_DEPTH=0 - --enable-debug - } - "Fast-One" { - -O6 - -DSQLITE_ENABLE_FTS4=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_RBU - -DSQLITE_MAX_ATTACHED=125 - -DSQLITE_MAX_MMAP_SIZE=12884901888 - -DSQLITE_ENABLE_SORTER_MMAP=1 - -DLONGDOUBLE_TYPE=double - --enable-session - } - "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_ENABLE_HIDDEN_COLUMNS - -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 - --enable-fts5 --enable-session - } - "Locking-Style" { - -O2 - -DSQLITE_ENABLE_LOCKING_STYLE=1 - } - "Apple" { - -Os - -DHAVE_GMTIME_R=1 - -DHAVE_ISNAN=1 - -DHAVE_LOCALTIME_R=1 - -DHAVE_PREAD=1 - -DHAVE_PWRITE=1 - -DHAVE_UTIME=1 - -DSQLITE_DEFAULT_CACHE_SIZE=1000 - -DSQLITE_DEFAULT_CKPTFULLFSYNC=1 - -DSQLITE_DEFAULT_MEMSTATUS=1 - -DSQLITE_DEFAULT_PAGE_SIZE=1024 - -DSQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS=1 - -DSQLITE_ENABLE_API_ARMOR=1 - -DSQLITE_ENABLE_AUTO_PROFILE=1 - -DSQLITE_ENABLE_FLOCKTIMEOUT=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 - -DSQLITE_ENABLE_FTS3_TOKENIZER=1 - -DSQLITE_ENABLE_PERSIST_WAL=1 - -DSQLITE_ENABLE_PURGEABLE_PCACHE=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_SNAPSHOT=1 - # -DSQLITE_ENABLE_SQLLOG=1 - -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 - -DSQLITE_MAX_LENGTH=2147483645 - -DSQLITE_MAX_VARIABLE_NUMBER=500000 - # -DSQLITE_MEMDEBUG=1 - -DSQLITE_NO_SYNC=1 - -DSQLITE_OMIT_AUTORESET=1 - -DSQLITE_OMIT_LOAD_EXTENSION=1 - -DSQLITE_PREFER_PROXY_LOCKING=1 - -DSQLITE_SERIES_CONSTRAINT_VERIFY=1 - -DSQLITE_THREADSAFE=2 - -DSQLITE_USE_URI=1 - -DSQLITE_WRITE_WALFRAME_PREBUFFERED=1 - -DUSE_GUARDED_FD=1 - -DUSE_PREAD=1 - --enable-fts5 - } - "Extra-Robustness" { - -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 - -DSQLITE_MAX_ATTACHED=62 - } - "Devkit" { - -DSQLITE_DEFAULT_FILE_FORMAT=4 - -DSQLITE_MAX_ATTACHED=30 - -DSQLITE_ENABLE_COLUMN_METADATA - -DSQLITE_ENABLE_FTS4 - -DSQLITE_ENABLE_FTS5 - -DSQLITE_ENABLE_FTS4_PARENTHESIS - -DSQLITE_DISABLE_FTS4_DEFERRED - -DSQLITE_ENABLE_RTREE - --enable-fts5 - } - "No-lookaside" { - -DSQLITE_TEST_REALLOC_STRESS=1 - -DSQLITE_OMIT_LOOKASIDE=1 - } - "Valgrind" { - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_FTS4 - -DSQLITE_ENABLE_RTREE - -DSQLITE_ENABLE_HIDDEN_COLUMNS - -DLONGDOUBLE_TYPE=double - -DCONFIG_SLOWDOWN_FACTOR=8.0 - } - - "Windows-Memdebug" { - MEMDEBUG=1 - DEBUG=3 - } - "Windows-Win32Heap" { - WIN32HEAP=1 - DEBUG=4 - } - - # The next group of configurations are used only by the - # Failure-Detection platform. They are all the same, but we need - # different names for them all so that they results appear in separate - # subdirectories. - # - Fail0 {-O0} - Fail2 {-O0} - Fail3 {-O0} - Fail4 {-O0} - FuzzFail1 {-O0} - FuzzFail2 {-O0} -}] -if {$tcl_platform(os)=="Darwin"} { - lappend Configs(Apple) -DSQLITE_ENABLE_LOCKING_STYLE=1 -} - -array set ::Platforms [strip_comments { - Linux-x86_64 { - "Check-Symbols*" "" checksymbols - "Fast-One" QUICKTEST_INCLUDE=rbu.test "fuzztest test" - "Debug-One" "" "mptest test" - "Debug-Two" "" test - "Have-Not" "" test - "Secure-Delete" "" test - "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test - "User-Auth" "" tcltest - "Update-Delete-Limit" "" test - "Extra-Robustness" "" test - "Device-Two" "" "threadtest test" - "No-lookaside" "" test - "Devkit" "" test - "Apple" "" test - "Sanitize*" "" test - "Device-One" "" alltest - "Default" "" "threadtest fuzztest alltest" - "Valgrind*" "" valgrindtest - } - Linux-i686 { - "Devkit" "" test - "Have-Not" "" test - "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test - "Device-One" "" test - "Device-Two" "" test - "Default" "" "threadtest fuzztest alltest" - } - Darwin-i386 { - "Locking-Style" "" "mptest test" - "Have-Not" "" test - "Apple" "" "threadtest fuzztest alltest" - } - Darwin-x86_64 { - "Locking-Style" "" "mptest test" - "Have-Not" "" test - "Apple" "" "threadtest fuzztest alltest" - } - Darwin-arm64 { - "Locking-Style" "" "mptest test" - "Have-Not" "" test - "Apple" "" "threadtest fuzztest alltest" - } - "Windows NT-intel" { - "Stdcall" "" test - "Have-Not" "" test - "Windows-Memdebug*" "" test - "Windows-Win32Heap*" "" test - "Default" "" "mptest fulltestonly" - } - "Windows NT-amd64" { - "Stdcall" "" test - "Have-Not" "" test - "Windows-Memdebug*" "" test - "Windows-Win32Heap*" "" test - "Default" "" "mptest fulltestonly" - } - - # The Failure-Detection platform runs various tests that deliberately - # fail. This is used as a test of this script to verify that this script - # correctly identifies failures. - # - Failure-Detection { - Fail0* "TEST_FAILURE=0" test - Sanitize* "TEST_FAILURE=1" test - Fail2* "TEST_FAILURE=2" valgrindtest - Fail3* "TEST_FAILURE=3" valgrindtest - Fail4* "TEST_FAILURE=4" test - FuzzFail1* "TEST_FAILURE=5" test - FuzzFail2* "TEST_FAILURE=5" valgrindtest - } -}] - -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- -# End of configuration section. -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- - -# Configuration verification: Check that each entry in the list of configs -# specified for each platforms exists. -# -foreach {key value} [array get ::Platforms] { - foreach {v vars t} $value { - if {[string range $v end end]=="*"} { - set v [string range $v 0 end-1] - } - if {0==[info exists ::Configs($v)]} { - puts stderr "No such configuration: \"$v\"" - exit -1 - } - } -} - -proc usage {} { - global argv0 - puts stderr [subst $::USAGE] - exit 1 -} - -proc is_prefix {p str min} { - set n [string length $p] - if {$n<$min} { return 0 } - if {[string range $str 0 [expr $n-1]]!=$p} { return 0 } - return 1 -} - -proc main_configurations {} { - foreach k [lsort [array names ::Configs]] { - puts $k - } -} - -proc main_platforms {} { - foreach k [lsort [array names ::Platforms]] { - puts "\"$k\"" - } -} - -proc main_script {args} { - set bMsvc 0 - set nArg [llength $args] - if {$nArg==3} { - if {![is_prefix [lindex $args 0] -msvc 2]} usage - set bMsvc 1 - } elseif {$nArg<2 || $nArg>3} { - usage - } - set config [lindex $args end-1] - set target [lindex $args end] - - set opts [list] ;# OPTS value - set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value - set makeOpts [list] ;# Extra args for [make] - set configOpts [list] ;# Extra args for [configure] - - if {$::tcl_platform(platform)=="windows" || $bMsvc} { - lappend opts -DSQLITE_OS_WIN=1 - } else { - lappend opts -DSQLITE_OS_UNIX=1 - } - - # Figure out if this is a synthetic ndebug or debug configuration. - # - set bRemoveDebug 0 - if {[string match *-ndebug $config]} { - set bRemoveDebug 1 - set config [string range $config 0 end-7] - } - if {[string match *-debug $config]} { - lappend opts -DSQLITE_DEBUG - lappend opts -DSQLITE_EXTRA_IFNULLROW - set config [string range $config 0 end-6] - } - regexp {^(.*)-[0-9]+} $config -> config - - # Ensure that the named configuration exists. - # - if {![info exists ::Configs($config)]} { - puts stderr "No such config: $config" - exit 1 - } - - # Loop through the parameters of the nominated configuration, updating - # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as - # follows: - # - # 1. If the parameter begins with a "*", discard it. - # - # 2. If $bRemoveDebug is set and the parameter is -DSQLITE_DEBUG or - # -DSQLITE_DEBUG=1, discard it - # - # 3. If the parameter begins with "-D", add it to $opts. - # - # 4. If the parameter begins with "--" add it to $configOpts. Unless - # this command is preparing a script for MSVC - then add an - # equivalent to $makeOpts or $opts. - # - # 5. If the parameter begins with "-" add it to $cflags. If in MSVC - # mode and the parameter is an -O option, instead add - # an OPTIMIZATIONS= switch to $makeOpts. - # - # 6. If none of the above apply, add the parameter to $makeOpts - # - foreach param $::Configs($config) { - if {[string range $param 0 0]=="*"} continue - - if {$bRemoveDebug} { - if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1" - || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1" - || $param=="--enable-debug" - } { - continue - } - } - - if {[string range $param 0 1]=="-D"} { - lappend opts $param - continue - } - - if {[string range $param 0 1]=="--"} { - if {$bMsvc} { - switch -- $param { - --disable-amalgamation { - lappend makeOpts USE_AMALGAMATION=0 - } - --disable-shared { - lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 - } - --enable-fts5 { - lappend opts -DSQLITE_ENABLE_FTS5 - } - --enable-shared { - lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 - } - --enable-session { - lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK - lappend opts -DSQLITE_ENABLE_SESSION - } - default { - error "Cannot translate $param for MSVC" - } - } - } else { - lappend configOpts $param - } - - continue - } - - if {[string range $param 0 0]=="-"} { - if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { - lappend makeOpts OPTIMIZATIONS=$level - } else { - lappend cflags $param - } - continue - } - - lappend makeOpts $param - } - - # Some configurations specify -DHAVE_USLEEP=0. For all others, add - # -DHAVE_USLEEP=1. - # - if {[lsearch $opts "-DHAVE_USLEEP=0"]<0} { - lappend opts -DHAVE_USLEEP=1 - } - - if {$bMsvc==0} { - puts {set -e} - puts {} - puts {if [ "$#" -ne 1 ] ; then} - puts { echo "Usage: $0 " } - puts { exit -1 } - puts {fi } - puts {SRCDIR=$1} - puts {} - puts "TCL=\"[::tcl::pkgconfig get libdir,install]\"" - - puts "\$SRCDIR/configure --with-tcl=\$TCL $configOpts" - puts {} - puts {OPTS=" -DSQLITE_NO_SYNC=1"} - foreach o $opts { - puts "OPTS=\"\$OPTS $o\"" - } - puts {} - puts "CFLAGS=\"$cflags\"" - puts {} - puts "make $target \"CFLAGS=\$CFLAGS\" \"OPTS=\$OPTS\" $makeOpts" - } else { - - puts {set SRCDIR=%1} - set makecmd "nmake /f %SRCDIR%\\Makefile.msc TOP=%SRCDIR% $target " - append makecmd "\"CFLAGS=$cflags\" \"OPTS=$opts\" $makeOpts" - - puts "set TMP=%CD%" - puts $makecmd - } -} - -proc main_trscript {args} { - set bMsvc 0 - set nArg [llength $args] - if {$nArg==3} { - if {![is_prefix [lindex $args 0] -msvc 2]} usage - set bMsvc 1 - } elseif {$nArg<2 || $nArg>3} { - usage - } - set config [lindex $args end-1] - set srcdir [lindex $args end] - - set opts [list] ;# OPTS value - set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value - set makeOpts [list] ;# Extra args for [make] - set configOpts [list] ;# Extra args for [configure] - - if {$::tcl_platform(platform)=="windows" || $bMsvc} { - lappend opts -DSQLITE_OS_WIN=1 - } else { - lappend opts -DSQLITE_OS_UNIX=1 - } - - # Figure out if this is a synthetic ndebug or debug configuration. - # - set bRemoveDebug 0 - if {[string match *-ndebug $config]} { - set bRemoveDebug 1 - set config [string range $config 0 end-7] - } - if {[string match *-debug $config]} { - lappend opts -DSQLITE_DEBUG - lappend opts -DSQLITE_EXTRA_IFNULLROW - set config [string range $config 0 end-6] - } - regexp {^(.*)-[0-9]+} $config -> config - - # Ensure that the named configuration exists. - # - if {![info exists ::Configs($config)]} { - puts stderr "No such config: $config" - exit 1 - } - - # Loop through the parameters of the nominated configuration, updating - # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as - # follows: - # - # 1. If the parameter begins with a "*", discard it. - # - # 2. If $bRemoveDebug is set and the parameter is -DSQLITE_DEBUG or - # -DSQLITE_DEBUG=1, discard it - # - # 3. If the parameter begins with "-D", add it to $opts. - # - # 4. If the parameter begins with "--" add it to $configOpts. Unless - # this command is preparing a script for MSVC - then add an - # equivalent to $makeOpts or $opts. - # - # 5. If the parameter begins with "-" add it to $cflags. If in MSVC - # mode and the parameter is an -O option, instead add - # an OPTIMIZATIONS= switch to $makeOpts. - # - # 6. If none of the above apply, add the parameter to $makeOpts - # - foreach param $::Configs($config) { - if {[string range $param 0 0]=="*"} continue - - if {$bRemoveDebug} { - if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1" - || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1" - || $param=="--enable-debug" - } { - continue - } - } - - if {[string range $param 0 1]=="-D"} { - lappend opts $param - continue - } - - if {[string range $param 0 1]=="--"} { - if {$bMsvc} { - switch -- $param { - --disable-amalgamation { - lappend makeOpts USE_AMALGAMATION=0 - } - --disable-shared { - lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 - } - --enable-fts5 { - lappend opts -DSQLITE_ENABLE_FTS5 - } - --enable-shared { - lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 - } - --enable-session { - lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK - lappend opts -DSQLITE_ENABLE_SESSION - } - --enable-all { - } - --enable-debug { - # lappend makeOpts OPTIMIZATIONS=0 - lappend opts -DSQLITE_DEBUG - } - default { - error "Cannot translate $param for MSVC" - } - } - } else { - lappend configOpts $param - } - - continue - } - - if {[string range $param 0 0]=="-"} { - if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { - lappend makeOpts OPTIMIZATIONS=$level - } else { - lappend cflags $param - } - continue - } - - lappend makeOpts $param - } - - # Some configurations specify -DHAVE_USLEEP=0. For all others, add - # -DHAVE_USLEEP=1. - # - if {[lsearch $opts "-DHAVE_USLEEP=0"]<0} { - lappend opts -DHAVE_USLEEP=1 - } - - if {$bMsvc==0} { - puts {set -e} - puts {} - puts {if [ "$#" -ne 1 ] ; then} - puts { echo "Usage: $0 " } - puts { exit -1 } - puts {fi } - puts "SRCDIR=\"$srcdir\"" - puts {} - puts "TCL=\"[::tcl::pkgconfig get libdir,install]\"" - - puts {if [ ! -f Makefile ] ; then} - puts " \$SRCDIR/configure --with-tcl=\$TCL $configOpts" - puts {fi} - puts {} - if {[info exists ::env(OPTS)]} { - puts "# From environment variable:" - puts "OPTS=$::env(OPTS)" - puts "" - } - puts {OPTS="$OPTS -DSQLITE_NO_SYNC=1"} - foreach o $opts { - puts "OPTS=\"\$OPTS $o\"" - } - puts {} - puts "CFLAGS=\"$cflags\"" - puts {} - puts "make \$1 \"CFLAGS=\$CFLAGS\" \"OPTS=\$OPTS\" $makeOpts" - } else { - - set srcdir [file nativename [file normalize $srcdir]] - # set srcdir [string map [list "\\" "\\\\"] $srcdir] - - puts {set TARGET=%1} - set makecmd "nmake /f $srcdir\\Makefile.msc TOP=\"$srcdir\" %TARGET% " - append makecmd "\"CFLAGS=$cflags\" \"OPTS=$opts\" $makeOpts" - - puts "set TMP=%CD%" - puts $makecmd - } -} - -proc main_tests {args} { - set bNodebug 0 - set nArg [llength $args] - if {$nArg==2} { - if {[is_prefix [lindex $args 0] -nodebug 2]} { - set bNodebug 1 - } elseif {[is_prefix [lindex $args 0] -debug 2]} { - set bNodebug 0 - } else usage - } elseif {$nArg==0 || $nArg>2} { - usage - } - set p [lindex $args end] - if {![info exists ::Platforms($p)]} { - puts stderr "No such platform: $p" - exit 1 - } - - set lTest [list] - - foreach {config vars target} $::Platforms($p) { - if {[string range $config end end]=="*"} { - set config [string range $config 0 end-1] - } elseif {$bNodebug==0} { - set dtarget test - if {[lsearch $target fuzztest]<0 && [lsearch $target test]<0} { - set dtarget tcltest - } - if {$vars!=""} { set dtarget "$vars $dtarget" } - - if {[string first SQLITE_DEBUG $::Configs($config)]>=0 - || [string first --enable-debug $::Configs($config)]>=0 - } { - lappend lTest "$config-ndebug \"$dtarget\"" - } else { - lappend lTest "$config-debug \"$dtarget\"" - } - } - - if {[llength $target]==1 && ([string match "*TEST_FAILURE*" $vars] || ( - [lsearch $target "valgrindtest"]<0 - && [lsearch $target "alltest"]<0 - && [lsearch $target "fulltestonly"]<0 - && ![string match Sanitize* $config] - ))} { - if {$vars!=""} { set target "$vars $target" } - lappend lTest "$config \"$target\"" - } else { - set idir -1 - foreach t $target { - if {$t=="valgrindtest" || $t=="alltest" || $t=="fulltestonly" - || [string match Sanitize* $config] - } { - if {$vars!=""} { set t "$vars $t" } - for {set ii 1} {$ii<=4} {incr ii} { - lappend lTest "$config-[incr idir] \"TCLTEST_PART=$ii/4 $t\"" - } - } else { - if {$vars!=""} { set t "$vars $t" } - lappend lTest "$config-[incr idir] \"$t\"" - } - } - } - } - - foreach l $lTest { - puts $l - } - -} - -if {[llength $argv]==0} { usage } -set cmd [lindex $argv 0] -set n [expr [llength $argv]-1] -if {[string match ${cmd}* configurations] && $n==0} { - main_configurations -} elseif {[string match ${cmd}* script]} { - main_script {*}[lrange $argv 1 end] -} elseif {[string match ${cmd}* trscript]} { - main_trscript {*}[lrange $argv 1 end] -} elseif {[string match ${cmd}* platforms] && $n==0} { - main_platforms -} elseif {[string match ${cmd}* tests]} { - main_tests {*}[lrange $argv 1 end] -} else { - usage -} diff --git a/test/shell4.test b/test/shell4.test index 193dc8b69..eee59b02b 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -117,7 +117,7 @@ do_test shell4-2.2 { } {0 {}} do_test shell4-2.3 { catchcmd ":memory:" ".trace stdout\n.dump\n.trace off\n" -} {/^0 {PRAGMA.*}$/} +} {/^0 {SELECT.*}$/} do_test shell4-2.4 { catchcmd ":memory:" ".trace stdout\nCREATE TABLE t1(x);SELECT * FROM t1;" } {0 {CREATE TABLE t1(x); diff --git a/test/shell9.test b/test/shell9.test new file mode 100644 index 000000000..34c9d8c5d --- /dev/null +++ b/test/shell9.test @@ -0,0 +1,148 @@ +# 2024 Jan 8 +# +# 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 focus of this file is testing the CLI shell tool. Specifically, +# testing that it is possible to run a ".dump" script that creates +# virtual tables without explicitly disabling defensive mode. +# +# And, that it can process a ".dump" script that contains strings +# delimited using double-quotes in the schema (DQS_DDL setting). +# + +# Test plan: +# +# shell1-1.*: Basic command line option handling. +# shell1-2.*: Basic "dot" command token parsing. +# shell1-3.*: Basic test that "dot" command can be called. +# shell1-{4-8}.*: Test various "dot" commands's functionality. +# shell1-9.*: Basic test that "dot" commands and SQL intermix ok. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set CLI [test_cli_invocation] + +set ::testprefix shell9 + +ifcapable !fts5 { + finish_test + return +} + +#---------------------------------------------------------------------------- +# Test cases shell9-1.* verify that scripts output by .dump may be parsed +# by the shell tool without explicitly disabling DEFENSIVE mode, unless +# the shell is in safe mode. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, b, c); + INSERT INTO t1 VALUES('one', 'two', 'three'); +} +db close + +# Create .dump file in "testdump.txt". +# +set out [open testdump.txt w] +puts $out [lindex [catchcmd test.db .dump] 1] +close $out + +# Check testdump.txt can be processed if the initial db is empty. +# +do_test 1.1.1 { + forcedelete test.db + catchcmd test.db ".read testdump.txt" +} {0 {}} +sqlite3 db test.db +do_execsql_test 1.1.2 { + SELECT * FROM t1; +} {one two three} + +# Check testdump.txt cannot be processed if the initial db is not empty. +# +reset_db +do_execsql_test 1.2.1 { + CREATE TABLE t4(hello); +} +db close +do_test 1.2.2 { + catchcmd test.db ".read testdump.txt" +} {1 {Parse error near line 5: table sqlite_master may not be modified}} + +# Check testdump.txt cannot be processed if the db is in safe mode +# +do_test 1.3.1 { + forcedelete test.db + catchsafecmd test.db ".read testdump.txt" +} {1 {line 1: cannot run .read in safe mode}} +do_test 1.3.2 { + set fd [open testdump.txt] + set script [read $fd] + close $fd + forcedelete test.db + catchsafecmd test.db $script +} {1 {Parse error near line 5: table sqlite_master may not be modified}} +do_test 1.3.3 { + # Quick check that the above would have worked but for safe mode. + forcedelete test.db + catchcmd test.db $script +} {0 {}} + +#---------------------------------------------------------------------------- +# Test cases shell9-2.* verify that a warning is printed at the top of +# .dump scripts that contain virtual tables. +# +proc contains_warning {text} { + return [string match "*WARNING: Script requires that*" $text] +} + +reset_db +do_execsql_test 2.0.1 { + CREATE TABLE t1(x); + CREATE TABLE t2(y); + INSERT INTO t1 VALUES('one'); + INSERT INTO t2 VALUES('two'); +} +do_test 2.0.2 { + contains_warning [catchcmd test.db .dump] +} 0 + +do_execsql_test 2.1.1 { + CREATE virtual TABLE r1 USING fts5(x); +} +do_test 2.1.2 { + contains_warning [catchcmd test.db .dump] +} 1 + +do_test 2.2.1 { + contains_warning [catchcmd test.db ".dump t1"] +} 0 +do_test 2.2.2 { + contains_warning [catchcmd test.db ".dump r1"] +} 1 + +#------------------------------------------------------------------------- +reset_db +sqlite3_db_config db DQS_DDL 1 +do_execsql_test 3.1.0 { + CREATE TABLE t4(hello, check( hello IS NOT "xyz") ); +} +db close + +# Create .dump file in "testdump.txt". +# +set out [open testdump.txt w] +puts $out [lindex [catchcmd test.db .dump] 1] +close $out +do_test 3.1.1 { + forcedelete test.db + catchcmd test.db ".read testdump.txt" +} {0 {}} + +finish_test diff --git a/test/snapshot_up.test b/test/snapshot_up.test index de8e5afab..5f389e7be 100644 --- a/test/snapshot_up.test +++ b/test/snapshot_up.test @@ -27,6 +27,8 @@ if {[permutation]=="inmemory_journal"} { return } +db timeout 1000 + do_execsql_test 1.0 { CREATE TABLE t1(a, b, c); PRAGMA journal_mode = wal; diff --git a/test/tester.tcl b/test/tester.tcl index 021830aa9..b96bc505d 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -554,7 +554,7 @@ if {[info exists cmdlinearg]==0} { } unset -nocomplain a set testdir [file normalize $testdir] - set cmdlinearg(TESTFIXTURE_HOME) [pwd] + set cmdlinearg(TESTFIXTURE_HOME) [file dirname [info nameofexec]] set cmdlinearg(INFO_SCRIPT) [file normalize [info script]] set argv0 [file normalize $argv0] if {$cmdlinearg(testdir)!=""} { @@ -884,6 +884,15 @@ proc catchcmd {db {cmd ""}} { set rc [catch { eval $line } msg] list $rc $msg } +proc catchsafecmd {db {cmd ""}} { + global CLI + set out [open cmds.txt w] + puts $out $cmd + close $out + set line "exec $CLI -safe $db < cmds.txt" + set rc [catch { eval $line } msg] + list $rc $msg +} proc catchcmdex {db {cmd ""}} { global CLI diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 1f1862ffd..0c704daf2 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -58,6 +58,8 @@ Usage: $a0 status where SWITCHES are: + --buildonly + --dryrun --jobs NUMBER-OF-JOBS --zipvfs ZIPVFS-SOURCE-DIR @@ -148,6 +150,8 @@ set TRG(cmdline) $argv set TRG(reporttime) 2000 set TRG(fuzztest) 0 ;# is the fuzztest option present. set TRG(zipvfs) "" ;# -zipvfs option, if any +set TRG(buildonly) 0 ;# True if --buildonly option +set TRG(dryrun) 0 ;# True if --dryrun option switch -nocase -glob -- $tcl_platform(os) { *darwin* { @@ -425,8 +429,12 @@ for {set ii 0} {$ii < [llength $argv]} {incr ii} { if {$isLast} { usage } } elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} { incr ii - set TRG(zipvfs) [lindex $argv $ii] + set TRG(zipvfs) [file normalize [lindex $argv $ii]] if {$isLast} { usage } + } elseif {($n>2 && [string match "$a*" --buildonly]) || $a=="-b"} { + set TRG(buildonly) 1 + } elseif {($n>2 && [string match "$a*" --dryrun]) || $a=="-d"} { + set TRG(dryrun) 1 } else { usage } @@ -752,6 +760,20 @@ proc add_zipvfs_jobs {} { set ::env(SQLITE_TEST_DIR) $::testdir } +# Used to add jobs for "mdevtest" and "sdevtest". +# +proc add_devtest_jobs {lBld patternlist} { + global TRG + + foreach b $lBld { + set bld [add_build_job $b $TRG(testfixture)] + add_tcl_jobs $bld veryquick $patternlist + if {$patternlist==""} { + add_fuzztest_jobs $b + } + } +} + proc add_jobs_from_cmdline {patternlist} { global TRG @@ -775,33 +797,28 @@ proc add_jobs_from_cmdline {patternlist} { } mdevtest { - foreach b [list All-O0 All-Debug] { - set bld [add_build_job $b $TRG(testfixture)] - add_tcl_jobs $bld veryquick "" - add_fuzztest_jobs $b - } + add_devtest_jobs {All-O0 All-Debug} [lrange $patternlist 1 end] } sdevtest { - foreach b [list All-Sanitize All-Debug] { - set bld [add_build_job $b $TRG(testfixture)] - add_tcl_jobs $bld veryquick "" - add_fuzztest_jobs $b - } + add_devtest_jobs {All-Sanitize All-Debug} [lrange $patternlist 1 end] } release { + set patternlist [lrange $patternlist 1 end] foreach b [trd_builds $TRG(platform)] { set bld [add_build_job $b $TRG(testfixture)] foreach c [trd_configs $TRG(platform) $b] { - add_tcl_jobs $bld $c "" + add_tcl_jobs $bld $c $patternlist } - foreach e [trd_extras $TRG(platform) $b] { - if {$e=="fuzztest"} { - add_fuzztest_jobs $b - } else { - add_make_job $bld $e + if {$patternlist==""} { + foreach e [trd_extras $TRG(platform) $b] { + if {$e=="fuzztest"} { + add_fuzztest_jobs $b + } else { + add_make_job $bld $e + } } } } @@ -834,6 +851,17 @@ proc make_new_testset {} { } +proc mark_job_as_finished {jobid output state endtm} { + r_write_db { + trdb eval { + UPDATE jobs + SET output=$output, state=$state, endtime=$endtm + WHERE jobid=$jobid; + UPDATE jobs SET state='ready' WHERE depid=$jobid; + } + } +} + proc script_input_ready {fd iJob jobid} { global TRG global O @@ -868,15 +896,7 @@ proc script_input_ready {fd iJob jobid} { puts $TRG(log) "### $job(displayname) ${jobtm}ms ($state)" puts $TRG(log) [string trim $O($iJob)] - r_write_db { - set output $O($iJob) - trdb eval { - UPDATE jobs - SET output=$output, state=$state, endtime=$tm - WHERE jobid=$jobid; - UPDATE jobs SET state='ready' WHERE depid=$jobid; - } - } + mark_job_as_finished $jobid $O($iJob) $state $tm dirs_freeDir $iJob launch_some_jobs @@ -928,16 +948,28 @@ proc launch_another_job {iJob} { close $fd } - set pwd [pwd] - cd $dir - set fd [open $TRG(run) w] - puts $fd $job(cmd) - close $fd - set fd [open "|$TRG(runcmd) 2>@1" r] - cd $pwd + if { $TRG(dryrun) } { + + mark_job_as_finished $job(jobid) "" done 0 + dirs_freeDir $iJob + if {$job(build)!=""} { + puts $TRG(log) "(cd $dir ; $job(cmd) )" + } else { + puts $TRG(log) "$job(cmd)" + } + + } else { + set pwd [pwd] + cd $dir + set fd [open $TRG(run) w] + puts $fd $job(cmd) + close $fd + set fd [open "|$TRG(runcmd) 2>@1" r] + cd $pwd - fconfigure $fd -blocking false - fileevent $fd readable [list script_input_ready $fd $iJob $job(jobid)] + fconfigure $fd -blocking false + fileevent $fd readable [list script_input_ready $fd $iJob $job(jobid)] + } return 1 } @@ -1035,6 +1067,16 @@ proc run_testset {} { puts "Test log is $TRG(logname)" } +# Handle the --buildonly option, if it was specified. +# +proc handle_buildonly {} { + global TRG + if {$TRG(buildonly)} { + r_write_db { + trdb eval { DELETE FROM jobs WHERE displaytype!='bld' } + } + } +} sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) @@ -1043,6 +1085,8 @@ if {$TRG(nJob)>1} { puts "splitting work across $TRG(nJob) jobs" } puts "built testset in [expr $tm/1000]ms.." + +handle_buildonly run_testset trdb close #puts [pwd] diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index 9032ced4d..984c6d827 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -30,7 +30,7 @@ namespace eval trd { set tcltest(osx.Locking-Style) veryquick set tcltest(osx.Have-Not) veryquick - set tcltest(osx.Apple) all + set tcltest(osx.Apple) all_less_no_mutex_try set tcltest(win.Stdcall) veryquick set tcltest(win.Have-Not) veryquick @@ -100,11 +100,11 @@ namespace eval trd { } set build(All-Sanitize) { -DSQLITE_OMIT_LOOKASIDE=1 - --enable-all -fsanitize=address,undefined + --enable-all -fsanitize=address,undefined -fno-sanitize-recover=undefined } set build(Sanitize) { - CC=clang -fsanitize=address,undefined + CC=clang -fsanitize=address,undefined -fno-sanitize-recover=undefined -DSQLITE_ENABLE_STAT4 -DSQLITE_OMIT_LOOKASIDE=1 -DCONFIG_SLOWDOWN_FACTOR=5.0 @@ -267,6 +267,7 @@ namespace eval trd { -DSQLITE_ENABLE_PERSIST_WAL=1 -DSQLITE_ENABLE_PURGEABLE_PCACHE=1 -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_SETLK_TIMEOUT=2 -DSQLITE_ENABLE_SNAPSHOT=1 -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 -DSQLITE_MAX_LENGTH=2147483645 @@ -366,6 +367,9 @@ proc trd_configs {platform bld} { set clist $all_configs } elseif {$clist=="all_plus_autovacuum_crash"} { set clist [concat $all_configs autovacuum_crash] + } elseif {$clist=="all_less_no_mutex_try"} { + set idx [lsearch $all_configs no_mutex_try] + set clist [lreplace $all_configs $idx $idx] } } diff --git a/test/trace3.test b/test/trace3.test index e9935acfb..496cc2360 100644 --- a/test/trace3.test +++ b/test/trace3.test @@ -132,14 +132,27 @@ do_test trace3-4.3 { list $stmt [expr {$ns >= 0 && $ns <= 9999999}]; # less than 0.010 seconds } {/^-?\d+ 1$/} do_test trace3-4.4 { - set ::stmtlist(record) {} - db trace_v2 trace_v2_record 2 - execsql { - SELECT a, b FROM t1 ORDER BY a; + set cnt 0 + while {1} { + set ::stmtlist(record) {} + db trace_v2 trace_v2_record 2 + execsql { + SELECT a, b FROM t1 ORDER BY a; + } + set stmt [lindex [lindex $::stmtlist(record) 0] 0] + set ns [lindex [lindex $::stmtlist(record) 0] 1] + if {$ns<0 || $ns>9999999} { #less than 0.010 seconds + incr cnt + if {$cnt>3} { + set res "time out of bounds. Expected less than 99999999. Got $ns" + break + } + } else { + set res 1 + break + } } - set stmt [lindex [lindex $::stmtlist(record) 0] 0] - set ns [lindex [lindex $::stmtlist(record) 0] 1] - list $stmt [expr {$ns >= 0 && $ns <= 9999999}]; # less than 0.010 seconds + list $stmt $res } {/^-?\d+ 1$/} do_test trace3-5.1 { diff --git a/test/unionall.test b/test/unionall.test index 32fc76543..99cb48a25 100644 --- a/test/unionall.test +++ b/test/unionall.test @@ -346,6 +346,13 @@ do_execsql_test 5.10 { do_execsql_test 5.20 { SELECT *, '+' FROM t1 LEFT JOIN t3 ON (a NOT IN(SELECT v FROM t1 LEFT JOIN t2 ON (a=k))=k); } {0 {} {} {} + 1 one {} {} + 2 two {} {} + 5 five {} {} + 3 three {} {} + 6 six {} {} +} +ifcapable vtab { +do_catchsql_test 5.30 { + SELECT * FROM (t1 NATURAL JOIN pragma_table_xinfo('t1_a') NATURAL JOIN t3) t1 + NATURAL JOIN t2 NATURAL JOIN t3 + WHERE rowid ISNULL>0 AND 0%y; +} {1 {no such column: rowid}} +} reset_db do_execsql_test 6.0 { diff --git a/test/wapp.tcl b/test/wapp.tcl deleted file mode 100644 index 53c21e892..000000000 --- a/test/wapp.tcl +++ /dev/null @@ -1,987 +0,0 @@ -# Copyright (c) 2017 D. Richard Hipp -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the Simplified BSD License (also -# known as the "2-Clause License" or "FreeBSD License".) -# -# This program is distributed in the hope that it will be useful, -# but without any warranty; without even the implied warranty of -# merchantability or fitness for a particular purpose. -# -#--------------------------------------------------------------------------- -# -# Design rules: -# -# (1) All identifiers in the global namespace begin with "wapp" -# -# (2) Indentifiers intended for internal use only begin with "wappInt" -# -package require Tcl 8.6 - -# Add text to the end of the HTTP reply. No interpretation or transformation -# of the text is performs. The argument should be enclosed within {...} -# -proc wapp {txt} { - global wapp - dict append wapp .reply $txt -} - -# Add text to the page under construction. Do no escaping on the text. -# -# Though "unsafe" in general, there are uses for this kind of thing. -# For example, if you want to return the complete, unmodified content of -# a file: -# -# set fd [open content.html rb] -# wapp-unsafe [read $fd] -# close $fd -# -# You could do the same thing using ordinary "wapp" instead of "wapp-unsafe". -# The difference is that wapp-safety-check will complain about the misuse -# of "wapp", but it assumes that the person who write "wapp-unsafe" understands -# the risks. -# -# Though occasionally necessary, the use of this interface should be minimized. -# -proc wapp-unsafe {txt} { - global wapp - dict append wapp .reply $txt -} - -# Add text to the end of the reply under construction. The following -# substitutions are made: -# -# %html(...) Escape text for inclusion in HTML -# %url(...) Escape text for use as a URL -# %qp(...) Escape text for use as a URI query parameter -# %string(...) Escape text for use within a JSON string -# %unsafe(...) No transformations of the text -# -# The substitutions above terminate at the first ")" character. If the -# text of the TCL string in ... contains ")" characters itself, use instead: -# -# %html%(...)% -# %url%(...)% -# %qp%(...)% -# %string%(...)% -# %unsafe%(...)% -# -# In other words, use "%(...)%" instead of "(...)" to include the TCL string -# to substitute. -# -# The %unsafe substitution should be avoided whenever possible, obviously. -# In addition to the substitutions above, the text also does backslash -# escapes. -# -# The wapp-trim proc works the same as wapp-subst except that it also removes -# whitespace from the left margin, so that the generated HTML/CSS/Javascript -# does not appear to be indented when delivered to the client web browser. -# -if {$tcl_version>=8.7} { - proc wapp-subst {txt} { - global wapp - regsub -all -command \ - {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt wappInt-enc txt - dict append wapp .reply [subst -novariables -nocommand $txt] - } - proc wapp-trim {txt} { - global wapp - regsub -all {\n\s+} [string trim $txt] \n txt - regsub -all -command \ - {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt wappInt-enc txt - dict append wapp .reply [subst -novariables -nocommand $txt] - } - proc wappInt-enc {all mode nu1 txt} { - return [uplevel 2 "wappInt-enc-$mode \"$txt\""] - } -} else { - proc wapp-subst {txt} { - global wapp - regsub -all {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt \ - {[wappInt-enc-\1 "\3"]} txt - dict append wapp .reply [uplevel 1 [list subst -novariables $txt]] - } - proc wapp-trim {txt} { - global wapp - regsub -all {\n\s+} [string trim $txt] \n txt - regsub -all {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt \ - {[wappInt-enc-\1 "\3"]} txt - dict append wapp .reply [uplevel 1 [list subst -novariables $txt]] - } -} - -# There must be a wappInt-enc-NAME routine for each possible substitution -# in wapp-subst. Thus there are routines for "html", "url", "qp", and "unsafe". -# -# wappInt-enc-html Escape text so that it is safe to use in the -# body of an HTML document. -# -# wappInt-enc-url Escape text so that it is safe to pass as an -# argument to href= and src= attributes in HTML. -# -# wappInt-enc-qp Escape text so that it is safe to use as the -# value of a query parameter in a URL or in -# post data or in a cookie. -# -# wappInt-enc-string Escape ", ', \, and < for using inside of a -# javascript string literal. The < character -# is escaped to prevent "" from causing -# problems in embedded javascript. -# -# wappInt-enc-unsafe Perform no encoding at all. Unsafe. -# -proc wappInt-enc-html {txt} { - return [string map {& & < < > > \" " \\ \} $txt] -} -proc wappInt-enc-unsafe {txt} { - return $txt -} -proc wappInt-enc-url {s} { - if {[regsub -all {[^-{}@~?=#_.:/a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { - set s [subst -novar -noback $s] - } - if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} { - set s [subst -novar -noback $s] - } - return $s -} -proc wappInt-enc-qp {s} { - if {[regsub -all {[^-{}_.a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { - set s [subst -novar -noback $s] - } - if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} { - set s [subst -novar -noback $s] - } - return $s -} -proc wappInt-enc-string {s} { - return [string map {\\ \\\\ \" \\\" ' \\' < \\u003c} $s] -} - -# This is a helper routine for wappInt-enc-url and wappInt-enc-qp. It returns -# an appropriate %HH encoding for the single character c. If c is a unicode -# character, then this routine might return multiple bytes: %HH%HH%HH -# -proc wappInt-%HHchar {c} { - if {$c==" "} {return +} - return [regsub -all .. [binary encode hex [encoding convertto utf-8 $c]] {%&}] -} - - -# Undo the www-url-encoded format. -# -# HT: This code stolen from ncgi.tcl -# -proc wappInt-decode-url {str} { - set str [string map [list + { } "\\" "\\\\" \[ \\\[ \] \\\]] $str] - regsub -all -- \ - {%([Ee][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])} \ - $str {[encoding convertfrom utf-8 [binary decode hex \1\2\3]]} str - regsub -all -- \ - {%([CDcd][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])} \ - $str {[encoding convertfrom utf-8 [binary decode hex \1\2]]} str - regsub -all -- {%([0-7][A-Fa-f0-9])} $str {\\u00\1} str - return [subst -novar $str] -} - -# Reset the document back to an empty string. -# -proc wapp-reset {} { - global wapp - dict set wapp .reply {} -} - -# Change the mime-type of the result document. -# -proc wapp-mimetype {x} { - global wapp - dict set wapp .mimetype $x -} - -# Change the reply code. -# -proc wapp-reply-code {x} { - global wapp - dict set wapp .reply-code $x -} - -# Set a cookie -# -proc wapp-set-cookie {name value} { - global wapp - dict lappend wapp .new-cookies $name $value -} - -# Unset a cookie -# -proc wapp-clear-cookie {name} { - wapp-set-cookie $name {} -} - -# Add extra entries to the reply header -# -proc wapp-reply-extra {name value} { - global wapp - dict lappend wapp .reply-extra $name $value -} - -# Specifies how the web-page under construction should be cached. -# The argument should be one of: -# -# no-cache -# max-age=N (for some integer number of seconds, N) -# private,max-age=N -# -proc wapp-cache-control {x} { - wapp-reply-extra Cache-Control $x -} - -# Redirect to a different web page -# -proc wapp-redirect {uri} { - wapp-reply-code {307 Redirect} - wapp-reply-extra Location $uri -} - -# Return the value of a wapp parameter -# -proc wapp-param {name {dflt {}}} { - global wapp - if {![dict exists $wapp $name]} {return $dflt} - return [dict get $wapp $name] -} - -# Return true if a and only if the wapp parameter $name exists -# -proc wapp-param-exists {name} { - global wapp - return [dict exists $wapp $name] -} - -# Set the value of a wapp parameter -# -proc wapp-set-param {name value} { - global wapp - dict set wapp $name $value -} - -# Return all parameter names that match the GLOB pattern, or all -# names if the GLOB pattern is omitted. -# -proc wapp-param-list {{glob {*}}} { - global wapp - return [dict keys $wapp $glob] -} - -# By default, Wapp does not decode query parameters and POST parameters -# for cross-origin requests. This is a security restriction, designed to -# help prevent cross-site request forgery (CSRF) attacks. -# -# As a consequence of this restriction, URLs for sites generated by Wapp -# that contain query parameters will not work as URLs found in other -# websites. You cannot create a link from a second website into a Wapp -# website if the link contains query planner, by default. -# -# Of course, it is sometimes desirable to allow query parameters on external -# links. For URLs for which this is safe, the application should invoke -# wapp-allow-xorigin-params. This procedure tells Wapp that it is safe to -# go ahead and decode the query parameters even for cross-site requests. -# -# In other words, for Wapp security is the default setting. Individual pages -# need to actively disable the cross-site request security if those pages -# are safe for cross-site access. -# -proc wapp-allow-xorigin-params {} { - global wapp - if {![dict exists $wapp .qp] && ![dict get $wapp SAME_ORIGIN]} { - wappInt-decode-query-params - } -} - -# Set the content-security-policy. -# -# The default content-security-policy is very strict: "default-src 'self'" -# The default policy prohibits the use of in-line javascript or CSS. -# -# Provide an alternative CSP as the argument. Or use "off" to disable -# the CSP completely. -# -proc wapp-content-security-policy {val} { - global wapp - if {$val=="off"} { - dict unset wapp .csp - } else { - dict set wapp .csp $val - } -} - -# Examine the bodys of all procedures in this program looking for -# unsafe calls to various Wapp interfaces. Return a text string -# containing warnings. Return an empty string if all is ok. -# -# This routine is advisory only. It misses some constructs that are -# dangerous and flags others that are safe. -# -proc wapp-safety-check {} { - set res {} - foreach p [info procs] { - set ln 0 - foreach x [split [info body $p] \n] { - incr ln - if {[regexp {^[ \t]*wapp[ \t]+([^\n]+)} $x all tail] - && [string index $tail 0]!="\173" - && [regexp {[[$]} $tail] - } { - append res "$p:$ln: unsafe \"wapp\" call: \"[string trim $x]\"\n" - } - if {[regexp {^[ \t]*wapp-(subst|trim)[ \t]+[^\173]} $x all cx]} { - append res "$p:$ln: unsafe \"wapp-$cx\" call: \"[string trim $x]\"\n" - } - } - } - return $res -} - -# Return a string that descripts the current environment. Applications -# might find this useful for debugging. -# -proc wapp-debug-env {} { - global wapp - set out {} - foreach var [lsort [dict keys $wapp]] { - if {[string index $var 0]=="."} continue - append out "$var = [list [dict get $wapp $var]]\n" - } - append out "\[pwd\] = [list [pwd]]\n" - return $out -} - -# Tracing function for each HTTP request. This is overridden by wapp-start -# if tracing is enabled. -# -proc wappInt-trace {} {} - -# Start up a listening socket. Arrange to invoke wappInt-new-connection -# for each inbound HTTP connection. -# -# port Listen on this TCP port. 0 means to select a port -# that is not currently in use -# -# wappmode One of "scgi", "remote-scgi", "server", or "local". -# -# fromip If not {}, then reject all requests from IP addresses -# other than $fromip -# -proc wappInt-start-listener {port wappmode fromip} { - if {[string match *scgi $wappmode]} { - set type SCGI - set server [list wappInt-new-connection \ - wappInt-scgi-readable $wappmode $fromip] - } else { - set type HTTP - set server [list wappInt-new-connection \ - wappInt-http-readable $wappmode $fromip] - } - if {$wappmode=="local" || $wappmode=="scgi"} { - set x [socket -server $server -myaddr 127.0.0.1 $port] - } else { - set x [socket -server $server $port] - } - set coninfo [chan configure $x -sockname] - set port [lindex $coninfo 2] - if {$wappmode=="local"} { - wappInt-start-browser http://127.0.0.1:$port/ - } elseif {$fromip!=""} { - puts "Listening for $type requests on TCP port $port from IP $fromip" - } else { - puts "Listening for $type requests on TCP port $port" - } -} - -# Start a web-browser and point it at $URL -# -proc wappInt-start-browser {url} { - global tcl_platform - if {$tcl_platform(platform)=="windows"} { - exec cmd /c start $url & - } elseif {$tcl_platform(os)=="Darwin"} { - exec open $url & - } elseif {[catch {exec xdg-open $url}]} { - exec firefox $url & - } -} - -# This routine is a "socket -server" callback. The $chan, $ip, and $port -# arguments are added by the socket command. -# -# Arrange to invoke $callback when content is available on the new socket. -# The $callback will process inbound HTTP or SCGI content. Reject the -# request if $fromip is not an empty string and does not match $ip. -# -proc wappInt-new-connection {callback wappmode fromip chan ip port} { - upvar #0 wappInt-$chan W - if {$fromip!="" && ![string match $fromip $ip]} { - close $chan - return - } - set W [dict create REMOTE_ADDR $ip REMOTE_PORT $port WAPP_MODE $wappmode \ - .header {}] - fconfigure $chan -blocking 0 -translation binary - fileevent $chan readable [list $callback $chan] -} - -# Close an input channel -# -proc wappInt-close-channel {chan} { - if {$chan=="stdout"} { - # This happens after completing a CGI request - exit 0 - } else { - unset ::wappInt-$chan - close $chan - } -} - -# Process new text received on an inbound HTTP request -# -proc wappInt-http-readable {chan} { - if {[catch [list wappInt-http-readable-unsafe $chan] msg]} { - puts stderr "$msg\n$::errorInfo" - wappInt-close-channel $chan - } -} -proc wappInt-http-readable-unsafe {chan} { - upvar #0 wappInt-$chan W wapp wapp - if {![dict exists $W .toread]} { - # If the .toread key is not set, that means we are still reading - # the header - set line [string trimright [gets $chan]] - set n [string length $line] - if {$n>0} { - if {[dict get $W .header]=="" || [regexp {^\s+} $line]} { - dict append W .header $line - } else { - dict append W .header \n$line - } - if {[string length [dict get $W .header]]>100000} { - error "HTTP request header too big - possible DOS attack" - } - } elseif {$n==0} { - # We have reached the blank line that terminates the header. - global argv0 - set a0 [file normalize $argv0] - dict set W SCRIPT_FILENAME $a0 - dict set W DOCUMENT_ROOT [file dir $a0] - if {[wappInt-parse-header $chan]} { - catch {close $chan} - return - } - set len 0 - if {[dict exists $W CONTENT_LENGTH]} { - set len [dict get $W CONTENT_LENGTH] - } - if {$len>0} { - # Still need to read the query content - dict set W .toread $len - } else { - # There is no query content, so handle the request immediately - set wapp $W - wappInt-handle-request $chan 0 - } - } - } else { - # If .toread is set, that means we are reading the query content. - # Continue reading until .toread reaches zero. - set got [read $chan [dict get $W .toread]] - dict append W CONTENT $got - dict set W .toread [expr {[dict get $W .toread]-[string length $got]}] - if {[dict get $W .toread]<=0} { - # Handle the request as soon as all the query content is received - set wapp $W - wappInt-handle-request $chan 0 - } - } -} - -# Decode the HTTP request header. -# -# This routine is always running inside of a [catch], so if -# any problems arise, simply raise an error. -# -proc wappInt-parse-header {chan} { - upvar #0 wappInt-$chan W - set hdr [split [dict get $W .header] \n] - if {$hdr==""} {return 1} - set req [lindex $hdr 0] - dict set W REQUEST_METHOD [set method [lindex $req 0]] - if {[lsearch {GET HEAD POST} $method]<0} { - error "unsupported request method: \"[dict get $W REQUEST_METHOD]\"" - } - set uri [lindex $req 1] - set split_uri [split $uri ?] - set uri0 [lindex $split_uri 0] - if {![regexp {^/[-.a-z0-9_/]*$} $uri0]} { - error "invalid request uri: \"$uri0\"" - } - dict set W REQUEST_URI $uri0 - dict set W PATH_INFO $uri0 - set uri1 [lindex $split_uri 1] - dict set W QUERY_STRING $uri1 - set n [llength $hdr] - for {set i 1} {$i<$n} {incr i} { - set x [lindex $hdr $i] - if {![regexp {^(.+): +(.*)$} $x all name value]} { - error "invalid header line: \"$x\"" - } - set name [string toupper $name] - switch -- $name { - REFERER {set name HTTP_REFERER} - USER-AGENT {set name HTTP_USER_AGENT} - CONTENT-LENGTH {set name CONTENT_LENGTH} - CONTENT-TYPE {set name CONTENT_TYPE} - HOST {set name HTTP_HOST} - COOKIE {set name HTTP_COOKIE} - ACCEPT-ENCODING {set name HTTP_ACCEPT_ENCODING} - default {set name .hdr:$name} - } - dict set W $name $value - } - return 0 -} - -# Decode the QUERY_STRING parameters from a GET request or the -# application/x-www-form-urlencoded CONTENT from a POST request. -# -# This routine sets the ".qp" element of the ::wapp dict as a signal -# that query parameters have already been decoded. -# -proc wappInt-decode-query-params {} { - global wapp - dict set wapp .qp 1 - if {[dict exists $wapp QUERY_STRING]} { - foreach qterm [split [dict get $wapp QUERY_STRING] &] { - set qsplit [split $qterm =] - set nm [lindex $qsplit 0] - if {[regexp {^[a-z][a-z0-9]*$} $nm]} { - dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]] - } - } - } - if {[dict exists $wapp CONTENT_TYPE] && [dict exists $wapp CONTENT]} { - set ctype [dict get $wapp CONTENT_TYPE] - if {$ctype=="application/x-www-form-urlencoded"} { - foreach qterm [split [string trim [dict get $wapp CONTENT]] &] { - set qsplit [split $qterm =] - set nm [lindex $qsplit 0] - if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} { - dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]] - } - } - } elseif {[string match multipart/form-data* $ctype]} { - regexp {^(.*?)\r\n(.*)$} [dict get $wapp CONTENT] all divider body - set ndiv [string length $divider] - while {[string length $body]} { - set idx [string first $divider $body] - set unit [string range $body 0 [expr {$idx-3}]] - set body [string range $body [expr {$idx+$ndiv+2}] end] - if {[regexp {^Content-Disposition: form-data; (.*?)\r\n\r\n(.*)$} \ - $unit unit hdr content]} { - if {[regexp {name="(.*)"; filename="(.*)"\r\nContent-Type: (.*?)$}\ - $hdr hr name filename mimetype]} { - dict set wapp $name.filename \ - [string map [list \\\" \" \\\\ \\] $filename] - dict set wapp $name.mimetype $mimetype - dict set wapp $name.content $content - } elseif {[regexp {name="(.*)"} $hdr hr name]} { - dict set wapp $name $content - } - } - } - } - } -} - -# Invoke application-supplied methods to generate a reply to -# a single HTTP request. -# -# This routine always runs within [catch], so handle exceptions by -# invoking [error]. -# -proc wappInt-handle-request {chan useCgi} { - global wapp - dict set wapp .reply {} - dict set wapp .mimetype {text/html; charset=utf-8} - dict set wapp .reply-code {200 Ok} - dict set wapp .csp {default-src 'self'} - - # Set up additional CGI environment values - # - if {![dict exists $wapp HTTP_HOST]} { - dict set wapp BASE_URL {} - } elseif {[dict exists $wapp HTTPS]} { - dict set wapp BASE_URL https://[dict get $wapp HTTP_HOST] - } else { - dict set wapp BASE_URL http://[dict get $wapp HTTP_HOST] - } - if {![dict exists $wapp REQUEST_URI]} { - dict set wapp REQUEST_URI / - } elseif {[regsub {\?.*} [dict get $wapp REQUEST_URI] {} newR]} { - # Some servers (ex: nginx) append the query parameters to REQUEST_URI. - # These need to be stripped off - dict set wapp REQUEST_URI $newR - } - if {[dict exists $wapp SCRIPT_NAME]} { - dict append wapp BASE_URL [dict get $wapp SCRIPT_NAME] - } else { - dict set wapp SCRIPT_NAME {} - } - if {![dict exists $wapp PATH_INFO]} { - # If PATH_INFO is missing (ex: nginx) then construct it - set URI [dict get $wapp REQUEST_URI] - set skip [string length [dict get $wapp SCRIPT_NAME]] - dict set wapp PATH_INFO [string range $URI $skip end] - } - if {[regexp {^/([^/]+)(.*)$} [dict get $wapp PATH_INFO] all head tail]} { - dict set wapp PATH_HEAD $head - dict set wapp PATH_TAIL [string trimleft $tail /] - } else { - dict set wapp PATH_INFO {} - dict set wapp PATH_HEAD {} - dict set wapp PATH_TAIL {} - } - dict set wapp SELF_URL [dict get $wapp BASE_URL]/[dict get $wapp PATH_HEAD] - - # Parse query parameters from the query string, the cookies, and - # POST data - # - if {[dict exists $wapp HTTP_COOKIE]} { - foreach qterm [split [dict get $wapp HTTP_COOKIE] {;}] { - set qsplit [split [string trim $qterm] =] - set nm [lindex $qsplit 0] - if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} { - dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]] - } - } - } - set same_origin 0 - if {[dict exists $wapp HTTP_REFERER]} { - set referer [dict get $wapp HTTP_REFERER] - set base [dict get $wapp BASE_URL] - if {$referer==$base || [string match $base/* $referer]} { - set same_origin 1 - } - } - dict set wapp SAME_ORIGIN $same_origin - if {$same_origin} { - wappInt-decode-query-params - } - - # Invoke the application-defined handler procedure for this page - # request. If an error occurs while running that procedure, generate - # an HTTP reply that contains the error message. - # - wapp-before-dispatch-hook - wappInt-trace - set mname [dict get $wapp PATH_HEAD] - if {[catch { - if {$mname!="" && [llength [info proc wapp-page-$mname]]>0} { - wapp-page-$mname - } else { - wapp-default - } - } msg]} { - if {[wapp-param WAPP_MODE]=="local" || [wapp-param WAPP_MODE]=="server"} { - puts "ERROR: $::errorInfo" - } - wapp-reset - wapp-reply-code "500 Internal Server Error" - wapp-mimetype text/html - wapp-trim { -

    Wapp Application Error

    -
    %html($::errorInfo)
    - } - dict unset wapp .new-cookies - } - - # Transmit the HTTP reply - # - if {$chan=="stdout"} { - puts $chan "Status: [dict get $wapp .reply-code]\r" - } else { - puts $chan "HTTP/1.1 [dict get $wapp .reply-code]\r" - puts $chan "Server: wapp\r" - puts $chan "Connection: close\r" - } - if {[dict exists $wapp .reply-extra]} { - foreach {name value} [dict get $wapp .reply-extra] { - puts $chan "$name: $value\r" - } - } - if {[dict exists $wapp .csp]} { - puts $chan "Content-Security-Policy: [dict get $wapp .csp]\r" - } - set mimetype [dict get $wapp .mimetype] - puts $chan "Content-Type: $mimetype\r" - if {[dict exists $wapp .new-cookies]} { - foreach {nm val} [dict get $wapp .new-cookies] { - if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} { - if {$val==""} { - puts $chan "Set-Cookie: $nm=; HttpOnly; Path=/; Max-Age=1\r" - } else { - set val [wappInt-enc-url $val] - puts $chan "Set-Cookie: $nm=$val; HttpOnly; Path=/\r" - } - } - } - } - if {[string match text/* $mimetype]} { - set reply [encoding convertto utf-8 [dict get $wapp .reply]] - if {[regexp {\ygzip\y} [wapp-param HTTP_ACCEPT_ENCODING]]} { - catch { - set x [zlib gzip $reply] - set reply $x - puts $chan "Content-Encoding: gzip\r" - } - } - } else { - set reply [dict get $wapp .reply] - } - puts $chan "Content-Length: [string length $reply]\r" - puts $chan \r - puts -nonewline $chan $reply - flush $chan - wappInt-close-channel $chan -} - -# This routine runs just prior to request-handler dispatch. The -# default implementation is a no-op, but applications can override -# to do additional transformations or checks. -# -proc wapp-before-dispatch-hook {} {return} - -# Process a single CGI request -# -proc wappInt-handle-cgi-request {} { - global wapp env - foreach key { - CONTENT_LENGTH - CONTENT_TYPE - DOCUMENT_ROOT - HTTP_ACCEPT_ENCODING - HTTP_COOKIE - HTTP_HOST - HTTP_REFERER - HTTP_USER_AGENT - HTTPS - PATH_INFO - QUERY_STRING - REMOTE_ADDR - REQUEST_METHOD - REQUEST_URI - REMOTE_USER - SCRIPT_FILENAME - SCRIPT_NAME - SERVER_NAME - SERVER_PORT - SERVER_PROTOCOL - } { - if {[info exists env($key)]} { - dict set wapp $key $env($key) - } - } - set len 0 - if {[dict exists $wapp CONTENT_LENGTH]} { - set len [dict get $wapp CONTENT_LENGTH] - } - if {$len>0} { - fconfigure stdin -translation binary - dict set wapp CONTENT [read stdin $len] - } - dict set wapp WAPP_MODE cgi - fconfigure stdout -translation binary - wappInt-handle-request stdout 1 -} - -# Process new text received on an inbound SCGI request -# -proc wappInt-scgi-readable {chan} { - if {[catch [list wappInt-scgi-readable-unsafe $chan] msg]} { - puts stderr "$msg\n$::errorInfo" - wappInt-close-channel $chan - } -} -proc wappInt-scgi-readable-unsafe {chan} { - upvar #0 wappInt-$chan W wapp wapp - if {![dict exists $W .toread]} { - # If the .toread key is not set, that means we are still reading - # the header. - # - # An SGI header is short. This implementation assumes the entire - # header is available all at once. - # - dict set W .remove_addr [dict get $W REMOTE_ADDR] - set req [read $chan 15] - set n [string length $req] - scan $req %d:%s len hdr - incr len [string length "$len:,"] - append hdr [read $chan [expr {$len-15}]] - foreach {nm val} [split $hdr \000] { - if {$nm==","} break - dict set W $nm $val - } - set len 0 - if {[dict exists $W CONTENT_LENGTH]} { - set len [dict get $W CONTENT_LENGTH] - } - if {$len>0} { - # Still need to read the query content - dict set W .toread $len - } else { - # There is no query content, so handle the request immediately - dict set W SERVER_ADDR [dict get $W .remove_addr] - set wapp $W - wappInt-handle-request $chan 0 - } - } else { - # If .toread is set, that means we are reading the query content. - # Continue reading until .toread reaches zero. - set got [read $chan [dict get $W .toread]] - dict append W CONTENT $got - dict set W .toread [expr {[dict get $W .toread]-[string length $got]}] - if {[dict get $W .toread]<=0} { - # Handle the request as soon as all the query content is received - dict set W SERVER_ADDR [dict get $W .remove_addr] - set wapp $W - wappInt-handle-request $chan 0 - } - } -} - -# Start up the wapp framework. Parameters are a list passed as the -# single argument. -# -# -server $PORT Listen for HTTP requests on this TCP port $PORT -# -# -local $PORT Listen for HTTP requests on 127.0.0.1:$PORT -# -# -scgi $PORT Listen for SCGI requests on 127.0.0.1:$PORT -# -# -remote-scgi $PORT Listen for SCGI requests on TCP port $PORT -# -# -cgi Handle a single CGI request -# -# With no arguments, the behavior is called "auto". In "auto" mode, -# if the GATEWAY_INTERFACE environment variable indicates CGI, then run -# as CGI. Otherwise, start an HTTP server bound to the loopback address -# only, on an arbitrary TCP port, and automatically launch a web browser -# on that TCP port. -# -# Additional options: -# -# -fromip GLOB Reject any incoming request where the remote -# IP address does not match the GLOB pattern. This -# value defaults to '127.0.0.1' for -local and -scgi. -# -# -nowait Do not wait in the event loop. Return immediately -# after all event handlers are established. -# -# -trace "puts" each request URL as it is handled, for -# debugging -# -# -lint Run wapp-safety-check on the application instead -# of running the application itself -# -# -Dvar=value Set TCL global variable "var" to "value" -# -# -proc wapp-start {arglist} { - global env - set mode auto - set port 0 - set nowait 0 - set fromip {} - set n [llength $arglist] - for {set i 0} {$i<$n} {incr i} { - set term [lindex $arglist $i] - if {[string match --* $term]} {set term [string range $term 1 end]} - switch -glob -- $term { - -server { - incr i; - set mode "server" - set port [lindex $arglist $i] - } - -local { - incr i; - set mode "local" - set fromip 127.0.0.1 - set port [lindex $arglist $i] - } - -scgi { - incr i; - set mode "scgi" - set fromip 127.0.0.1 - set port [lindex $arglist $i] - } - -remote-scgi { - incr i; - set mode "remote-scgi" - set port [lindex $arglist $i] - } - -cgi { - set mode "cgi" - } - -fromip { - incr i - set fromip [lindex $arglist $i] - } - -nowait { - set nowait 1 - } - -trace { - proc wappInt-trace {} { - set q [wapp-param QUERY_STRING] - set uri [wapp-param BASE_URL][wapp-param PATH_INFO] - if {$q!=""} {append uri ?$q} - puts $uri - } - } - -lint { - set res [wapp-safety-check] - if {$res!=""} { - puts "Potential problems in this code:" - puts $res - exit 1 - } else { - exit - } - } - -D*=* { - if {[regexp {^.D([^=]+)=(.*)$} $term all var val]} { - set ::$var $val - } - } - default { - error "unknown option: $term" - } - } - } - if {$mode=="auto"} { - if {[info exists env(GATEWAY_INTERFACE)] - && [string match CGI/1.* $env(GATEWAY_INTERFACE)]} { - set mode cgi - } else { - set mode local - } - } - if {$mode=="cgi"} { - wappInt-handle-cgi-request - } else { - wappInt-start-listener $port $mode $fromip - if {!$nowait} { - vwait ::forever - } - } -} - -# Call this version 1.0 -package provide wapp 1.0 diff --git a/test/wapptest.tcl b/test/wapptest.tcl deleted file mode 100755 index d37b2e48c..000000000 --- a/test/wapptest.tcl +++ /dev/null @@ -1,909 +0,0 @@ -#!/bin/sh -# \ -exec wapptclsh "$0" ${1+"$@"} - -# package required wapp -source [file join [file dirname [info script]] wapp.tcl] - -# Variables set by the "control" form: -# -# G(platform) - User selected platform. -# G(cfgglob) - Glob pattern that all configurations must match -# G(test) - Set to "Normal", "Veryquick", "Smoketest" or "Build-Only". -# G(keep) - Boolean. True to delete no files after each test. -# G(msvc) - Boolean. True to use MSVC as the compiler. -# G(tcl) - Use Tcl from this directory for builds. -# G(jobs) - How many sub-processes to run simultaneously. -# -set G(platform) $::tcl_platform(os)-$::tcl_platform(machine) -set G(cfgglob) * -set G(test) Normal -set G(keep) 1 -set G(msvc) 0 -set G(tcl) [::tcl::pkgconfig get libdir,install] -set G(jobs) 3 -set G(debug) 0 - -set G(noui) 0 -set G(stdout) 0 - - -proc wapptest_init {} { - global G - - set lSave [list platform test keep msvc tcl jobs debug noui stdout cfgglob] - foreach k $lSave { set A($k) $G($k) } - array unset G - foreach k $lSave { set G($k) $A($k) } - - # The root of the SQLite source tree. - set G(srcdir) [file dirname [file dirname [info script]]] - - set G(sqlite_version) "unknown" - - # Either "config", "running" or "stopped": - set G(state) "config" - - set G(hostname) "(unknown host)" - catch { set G(hostname) [exec hostname] } - set G(host) $G(hostname) - append G(host) " $::tcl_platform(os) $::tcl_platform(osVersion)" - append G(host) " $::tcl_platform(machine) $::tcl_platform(byteOrder)" -} - -proc wapptest_run {} { - global G - set_test_array - set G(state) "running" - - wapptest_openlog - - wapptest_output "Running the following for $G(platform). $G(jobs) jobs." - foreach t $G(test_array) { - set config [dict get $t config] - set target [dict get $t target] - wapptest_output [format " %-25s%s" $config $target] - } - wapptest_output [string repeat * 70] -} - -proc releasetest_data {args} { - global G - set rtd [file join $G(srcdir) test releasetest_data.tcl] - set fd [open "|[info nameofexecutable] $rtd $args" r+] - set ret [read $fd] - close $fd - return $ret -} - -# Generate the text for the box at the top of the UI. The current SQLite -# version, according to fossil, along with a warning if there are -# uncommitted changes in the checkout. -# -proc generate_fossil_info {} { - global G - set pwd [pwd] - cd $G(srcdir) - set rc [catch { - set r1 [exec fossil info] - set r2 [exec fossil changes] - }] - cd $pwd - if {$rc} return - - foreach line [split $r1 "\n"] { - if {[regexp {^checkout: *(.*)$} $line -> co]} { - wapp-trim {
    %html($co) } - } - } - - if {[string trim $r2]!=""} { - wapp-trim { -
    - WARNING: Uncommitted changes in checkout - - } - } -} - -# If the application is in "config" state, set the contents of the -# ::G(test_array) global to reflect the tests that will be run. If the -# app is in some other state ("running" or "stopped"), this command -# is a no-op. -# -proc set_test_array {} { - global G - if { $G(state)=="config" } { - set G(test_array) [list] - set debug "-debug" - if {$G(debug)==0} { set debug "-nodebug"} - foreach {config target} [releasetest_data tests $debug $G(platform)] { - - # All configuration names must match $g(cfgglob), which defaults to * - # - if {![string match -nocase $G(cfgglob) $config]} continue - - # If using MSVC, do not run sanitize or valgrind tests. Or the - # checksymbols test. - if {$G(msvc) && ( - "Sanitize" == $config - || "checksymbols" in $target - || "valgrindtest" in $target - )} { - continue - } - - # If the test mode is not "Normal", override the target. - # - if {$target!="checksymbols" && $G(platform)!="Failure-Detection"} { - switch -- $G(test) { - Veryquick { set target quicktest } - Smoketest { set target smoketest } - Build-Only { - set target testfixture - if {$::tcl_platform(platform)=="windows"} { - set target testfixture.exe - } - } - } - } - - lappend G(test_array) [dict create config $config target $target] - } - } -} - -proc count_tests_and_errors {name logfile} { - global G - - set fd [open $logfile rb] - set seen 0 - while {![eof $fd]} { - set line [gets $fd] - if {[regexp {(\d+) errors out of (\d+) tests} $line all nerr ntest]} { - incr G(test.$name.nError) $nerr - incr G(test.$name.nTest) $ntest - set seen 1 - if {$nerr>0} { - set G(test.$name.errmsg) $line - } - } - if {[regexp {runtime error: +(.*)} $line all msg]} { - # skip over "value is outside range" errors - if {[regexp {.* is outside the range of representable} $line]} { - # noop - } else { - incr G(test.$name.nError) - if {$G(test.$name.errmsg)==""} { - set G(test.$name.errmsg) $msg - } - } - } - if {[regexp {fatal error +(.*)} $line all msg]} { - incr G(test.$name.nError) - if {$G(test.$name.errmsg)==""} { - set G(test.$name.errmsg) $msg - } - } - if {[regexp {ERROR SUMMARY: (\d+) errors.*} $line all cnt] && $cnt>0} { - incr G(test.$name.nError) - if {$G(test.$name.errmsg)==""} { - set G(test.$name.errmsg) $all - } - } - if {[regexp {^VERSION: 3\.\d+.\d+} $line]} { - set v [string range $line 9 end] - if {$G(sqlite_version) eq "unknown"} { - set G(sqlite_version) $v - } elseif {$G(sqlite_version) ne $v} { - set G(test.$name.errmsg) "version conflict: {$G(sqlite_version)} vs. {$v}" - } - } - } - close $fd - if {$G(test) == "Build-Only"} { - incr G(test.$name.nTest) - if {$G(test.$name.nError)>0} { - set errmsg "Build failed" - } - } elseif {!$seen} { - set G(test.$name.errmsg) "Test did not complete" - if {[file readable core]} { - append G(test.$name.errmsg) " - core file exists" - } - } -} - -proc wapptest_output {str} { - global G - if {$G(stdout)} { puts $str } - if {[info exists G(log)]} { - puts $G(log) $str - flush $G(log) - } -} -proc wapptest_openlog {} { - global G - set G(log) [open wapptest-out.txt w+] -} -proc wapptest_closelog {} { - global G - close $G(log) - unset G(log) -} - -proc format_seconds {seconds} { - set min [format %.2d [expr ($seconds / 60) % 60]] - set hr [format %.2d [expr $seconds / 3600]] - set sec [format %.2d [expr $seconds % 60]] - return "$hr:$min:$sec" -} - -# This command is invoked once a slave process has finished running its -# tests, successfully or otherwise. Parameter $name is the name of the -# test, $rc the exit code returned by the slave process. -# -proc slave_test_done {name rc} { - global G - set G(test.$name.done) [clock seconds] - set G(test.$name.nError) 0 - set G(test.$name.nTest) 0 - set G(test.$name.errmsg) "" - if {$rc} { - incr G(test.$name.nError) - } - if {[file exists $G(test.$name.log)]} { - count_tests_and_errors $name $G(test.$name.log) - } - - # If the "keep files" checkbox is clear, delete all files except for - # the executables and test logs. And any core file that is present. - if {$G(keep)==0} { - set keeplist { - testfixture testfixture.exe - sqlite3 sqlite3.exe - test.log test-out.txt - core - wapptest_make.sh - wapptest_configure.sh - wapptest_run.tcl - } - foreach f [glob -nocomplain [file join $G(test.$name.dir) *]] { - set t [file tail $f] - if {[lsearch $keeplist $t]<0} { - catch { file delete -force $f } - } - } - } - - # Format a message regarding the success or failure of hte test. - set t [format_seconds [expr $G(test.$name.done) - $G(test.$name.start)]] - set res "OK" - if {$G(test.$name.nError)} { set res "FAILED" } - set dots [string repeat . [expr 60 - [string length $name]]] - set msg "$name $dots $res ($t)" - - wapptest_output $msg - if {[info exists G(test.$name.errmsg)] && $G(test.$name.errmsg)!=""} { - wapptest_output " $G(test.$name.errmsg)" - } -} - -# This is a fileevent callback invoked each time a file-descriptor that -# connects this process to a slave process is readable. -# -proc slave_fileevent {name} { - global G - set fd $G(test.$name.channel) - - if {[eof $fd]} { - fconfigure $fd -blocking 1 - set rc [catch { close $fd }] - unset G(test.$name.channel) - slave_test_done $name $rc - } else { - set line [gets $fd] - if {[string trim $line] != ""} { puts "Trace : $name - \"$line\"" } - } - - do_some_stuff -} - -# Return the contents of the "slave script" - the script run by slave -# processes to actually perform the test. All it does is execute the -# test script already written to disk (wapptest_cmd.sh or wapptest_cmd.bat). -# -proc wapptest_slave_script {} { - global G - if {$G(msvc)==0} { - set dir [file join .. $G(srcdir)] - set res [subst -nocommands { - set rc [catch "exec sh wapptest_cmd.sh {$dir} >>& test.log" ] - exit [set rc] - }] - } else { - set dir [file nativename [file normalize $G(srcdir)]] - set dir [string map [list "\\" "\\\\"] $dir] - set res [subst -nocommands { - set rc [catch "exec wapptest_cmd.bat {$dir} >>& test.log" ] - exit [set rc] - }] - } - - set res -} - - -# Launch a slave process to run a test. -# -proc slave_launch {name target dir} { - global G - - catch { file mkdir $dir } msg - foreach f [glob -nocomplain [file join $dir *]] { - catch { file delete -force $f } - } - set G(test.$name.dir) $dir - - # Write the test command to wapptest_cmd.sh|bat. - # - set ext sh - if {$G(msvc)} { set ext bat } - set fd1 [open [file join $dir wapptest_cmd.$ext] w] - if {$G(msvc)} { - puts $fd1 [releasetest_data script -msvc $name $target] - } else { - puts $fd1 [releasetest_data script $name $target] - } - close $fd1 - - # Write the wapptest_run.tcl script to the test directory. To run the - # commands in the other two files. - # - set fd3 [open [file join $dir wapptest_run.tcl] w] - puts $fd3 [wapptest_slave_script] - close $fd3 - - set pwd [pwd] - cd $dir - set fd [open "|[info nameofexecutable] wapptest_run.tcl" r+] - cd $pwd - - set G(test.$name.channel) $fd - fconfigure $fd -blocking 0 - fileevent $fd readable [list slave_fileevent $name] -} - -proc do_some_stuff {} { - global G - - # Count the number of running jobs. A running job has an entry named - # "channel" in its dictionary. - set nRunning 0 - set bFinished 1 - foreach j $G(test_array) { - set name [dict get $j config] - if { [info exists G(test.$name.channel)]} { incr nRunning } - if {![info exists G(test.$name.done)]} { set bFinished 0 } - } - - if {$bFinished} { - set nError 0 - set nTest 0 - set nConfig 0 - foreach j $G(test_array) { - set name [dict get $j config] - incr nError $G(test.$name.nError) - incr nTest $G(test.$name.nTest) - incr nConfig - } - set G(result) "$nError errors from $nTest tests in $nConfig configurations." - wapptest_output [string repeat * 70] - wapptest_output $G(result) - catch { - append G(result) " SQLite version $G(sqlite_version)" - wapptest_output " SQLite version $G(sqlite_version)" - } - set G(state) "stopped" - wapptest_closelog - if {$G(noui)} { exit 0 } - } else { - set nLaunch [expr $G(jobs) - $nRunning] - foreach j $G(test_array) { - if {$nLaunch<=0} break - set name [dict get $j config] - if { ![info exists G(test.$name.channel)] - && ![info exists G(test.$name.done)] - } { - - set target [dict get $j target] - set dir [string tolower [string map {" " _ "-" _} $name]] - set G(test.$name.start) [clock seconds] - set G(test.$name.log) [file join $dir test.log] - - slave_launch $name $target $dir - - incr nLaunch -1 - } - } - } -} - -proc generate_select_widget {label id lOpt opt} { - wapp-trim { - - } -} - -proc generate_main_page {{extra {}}} { - global G - set_test_array - - set hostname $G(hostname) - wapp-trim { - - - %html($hostname): wapptest.tcl - - - - } - - set host $G(host) - wapp-trim { -
    %string($host) - } - generate_fossil_info - wapp-trim { -
    -
    -
    - } - - # Build the "platform" select widget. - set lOpt [releasetest_data platforms] - generate_select_widget Platform control_platform $lOpt $G(platform) - - # Build the "test" select widget. - set lOpt [list Normal Veryquick Smoketest Build-Only] - generate_select_widget Test control_test $lOpt $G(test) - - # Build the "jobs" select widget. Options are 1 to 8. - generate_select_widget Jobs control_jobs {1 2 3 4 5 6 7 8 12 16} $G(jobs) - - switch $G(state) { - config { - set txt "Run Tests!" - set id control_run - } - running { - set txt "STOP Tests!" - set id control_stop - } - stopped { - set txt "Reset!" - set id control_reset - } - } - wapp-trim { -
    - - -
    - } - - wapp-trim { -

    - - - - - - - - - - - } - wapp-trim { -
    - } - wapp-trim { -
    -
    - } - wapp-page-tests - - set script "script/$G(state).js" - wapp-trim { -
    - - - - } -} - -proc wapp-default {} { - generate_main_page -} - -proc wapp-page-tests {} { - global G - wapp-trim { } - foreach t $G(test_array) { - set config [dict get $t config] - set target [dict get $t target] - - set class "testwait" - set seconds "" - - if {[info exists G(test.$config.log)]} { - if {[info exists G(test.$config.channel)]} { - set class "testrunning" - set seconds [expr [clock seconds] - $G(test.$config.start)] - } elseif {[info exists G(test.$config.done)]} { - if {$G(test.$config.nError)>0} { - set class "testfail" - } else { - set class "testdone" - } - set seconds [expr $G(test.$config.done) - $G(test.$config.start)] - } - set seconds [format_seconds $seconds] - } - - wapp-trim { - - -
    %html($config) - %html($target) - %html($seconds) - - } - if {[info exists G(test.$config.log)]} { - set log $G(test.$config.log) - set uri "log/$log" - wapp-trim { - %html($log) - } - } - if {[info exists G(test.$config.errmsg)] && $G(test.$config.errmsg)!=""} { - set errmsg $G(test.$config.errmsg) - wapp-trim { -
    %html($errmsg) - } - } - } - - wapp-trim {
    } - - if {[info exists G(result)]} { - set res $G(result) - wapp-trim { -
    %string($res)
    - } - } -} - -# URI: /control -# -# Whenever the form at the top of the application page is submitted, it -# is submitted here. -# -proc wapp-page-control {} { - global G - if {$::G(state)=="config"} { - set lControls [list platform test tcl jobs keep msvc debug] - set G(msvc) 0 - set G(keep) 0 - set G(debug) 0 - } else { - set lControls [list jobs] - } - foreach v $lControls { - if {[wapp-param-exists control_$v]} { - set G($v) [wapp-param control_$v] - } - } - - if {[wapp-param-exists control_run]} { - # This is a "run test" command. - wapptest_run - } - - if {[wapp-param-exists control_stop]} { - # A "STOP tests" command. - set G(state) "stopped" - set G(result) "Test halted by user" - foreach j $G(test_array) { - set name [dict get $j config] - if { [info exists G(test.$name.channel)] } { - close $G(test.$name.channel) - unset G(test.$name.channel) - slave_test_done $name 1 - } - } - wapptest_closelog - } - - if {[wapp-param-exists control_reset]} { - # A "reset app" command. - set G(state) "config" - wapptest_init - } - - if {$::G(state) == "running"} { - do_some_stuff - } - wapp-redirect / -} - -# URI: /style.css -# -# Return the stylesheet for the application main page. -# -proc wapp-page-style.css {} { - wapp-subst { - - /* The boxes with black borders use this class */ - .border { - border: 3px groove #444444; - padding: 1em; - margin-top: 1em; - margin-bottom: 1em; - } - - /* Float to the right (used for the Run/Stop/Reset button) */ - .right { float: right; } - - /* Style for the large red warning at the top of the page */ - .warning { - color: red; - font-weight: bold; - } - - /* Styles used by cells in the test table */ - .padleft { padding-left: 5ex; } - .nowrap { white-space: nowrap; } - - /* Styles for individual tests, depending on the outcome */ - .testwait { } - .testrunning { color: blue } - .testdone { color: green } - .testfail { color: red } - } -} - -# URI: /script/${state}.js -# -# The last part of this URI is always "config.js", "running.js" or -# "stopped.js", depending on the state of the application. It returns -# the javascript part of the front-end for the requested state to the -# browser. -# -proc wapp-page-script {} { - regexp {[^/]*$} [wapp-param REQUEST_URI] script - - set tcl $::G(tcl) - set keep $::G(keep) - set msvc $::G(msvc) - set debug $::G(debug) - - wapp-subst { - var lElem = \["control_platform", "control_test", "control_msvc", - "control_jobs", "control_debug" - \]; - lElem.forEach(function(e) { - var elem = document.getElementById(e); - elem.addEventListener("change", function() { control.submit() } ); - }) - - elem = document.getElementById("control_tcl"); - elem.value = "%string($tcl)" - - elem = document.getElementById("control_keep"); - elem.checked = %string($keep); - - elem = document.getElementById("control_msvc"); - elem.checked = %string($msvc); - - elem = document.getElementById("control_debug"); - elem.checked = %string($debug); - } - - if {$script != "config.js"} { - wapp-subst { - var lElem = \["control_platform", "control_test", - "control_tcl", "control_keep", "control_msvc", - "control_debug" - \]; - lElem.forEach(function(e) { - var elem = document.getElementById(e); - elem.disabled = true; - }) - } - } - - if {$script == "running.js"} { - wapp-subst { - function reload_tests() { - fetch('tests') - .then( data => data.text() ) - .then( data => { - document.getElementById("tests").innerHTML = data; - }) - .then( data => { - if( document.getElementById("result") ){ - document.location = document.location; - } else { - setTimeout(reload_tests, 1000) - } - }); - } - - setTimeout(reload_tests, 1000) - } - } -} - -# URI: /env -# -# This is for debugging only. Serves no other purpose. -# -proc wapp-page-env {} { - wapp-allow-xorigin-params - wapp-trim { -

    Wapp Environment

    \n
    -    
    %html([wapp-debug-env])
    - } -} - -# URI: /log/dirname/test.log -# -# This URI reads file "dirname/test.log" from disk, wraps it in a
    -# block, and returns it to the browser. Use for viewing log files.
    -#
    -proc wapp-page-log {} {
    -  set log [string range [wapp-param REQUEST_URI] 5 end]
    -  set fd [open $log]
    -  set data [read $fd]
    -  close $fd
    -  wapp-trim {
    -    
    -    %html($data)
    -    
    - } -} - -# Print out a usage message. Then do [exit 1]. -# -proc wapptest_usage {} { - puts stderr { -This Tcl script is used to test various configurations of SQLite. By -default it uses "wapp" to provide an interactive interface. Supported -command line options (all optional) are: - - --platform PLATFORM (which tests to run) - --config GLOB (only run configurations matching GLOB) - --smoketest (run "make smoketest" only) - --veryquick (run veryquick.test only) - --buildonly (build executables, do not run tests) - --jobs N (number of concurrent jobs) - --tcl DIR (where to find tclConfig.sh) - --deletefiles (delete extra files after each test) - --msvc (Use MS Visual C) - --debug (Also run [n]debugging versions of tests) - --noui (do not use wapp) - } - exit 1 -} - -# Sort command line arguments into two groups: those that belong to wapp, -# and those that belong to the application. -set WAPPARG(-server) 1 -set WAPPARG(-local) 1 -set WAPPARG(-scgi) 1 -set WAPPARG(-remote-scgi) 1 -set WAPPARG(-fromip) 1 -set WAPPARG(-nowait) 0 -set WAPPARG(-cgi) 0 -set lWappArg [list] -set lTestArg [list] -for {set i 0} {$i < [llength $argv]} {incr i} { - set arg [lindex $argv $i] - if {[string range $arg 0 1]=="--"} { - set arg [string range $arg 1 end] - } - if {[info exists WAPPARG($arg)]} { - lappend lWappArg $arg - if {$WAPPARG($arg)} { - incr i - lappend lWappArg [lindex $argv $i] - } - } else { - lappend lTestArg $arg - } -} - -wapptest_init -for {set i 0} {$i < [llength $lTestArg]} {incr i} { - set opt [lindex $lTestArg $i] - if {[string range $opt 0 1]=="--"} { - set opt [string range $opt 1 end] - } - switch -- $opt { - -platform { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set arg [lindex $lTestArg $i] - set lPlatform [releasetest_data platforms] - if {[lsearch $lPlatform $arg]<0} { - puts stderr "No such platform: $arg. Platforms are: $lPlatform" - exit -1 - } - set G(platform) $arg - } - - -smoketest { set G(test) Smoketest } - -veryquick { set G(test) Veryquick } - -buildonly { set G(test) Build-Only } - -jobs { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set G(jobs) [lindex $lTestArg $i] - } - - -tcl { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set G(tcl) [lindex $lTestArg $i] - } - - -deletefiles { - set G(keep) 0 - } - - -msvc { - set G(msvc) 1 - } - - -debug { - set G(debug) 1 - } - - -noui { - set G(noui) 1 - set G(stdout) 1 - } - - -config { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set G(cfgglob) [lindex $lTestArg $i] - } - - -stdout { - set G(stdout) 1 - } - - default { - puts stderr "Unrecognized option: [lindex $lTestArg $i]" - wapptest_usage - } - } -} - -if {$G(noui)==0} { - wapp-start $lWappArg -} else { - wapptest_run - do_some_stuff - vwait forever -} diff --git a/test/where3.test b/test/where3.test index 20af995cf..f574c0d55 100644 --- a/test/where3.test +++ b/test/where3.test @@ -492,5 +492,27 @@ foreach disabled_opt {none omit-noop-join all} { } {123} } +# 2023-12-23 +# https://sqlite.org/forum/forumpost/2568d1f6e6 +# +# Index usage should be "x=? and y=?" - equality on both values. +# Not: "x=? AND y>?" - inequality on "y" +# +reset_db +do_execsql_test where3-8.1 { + CREATE TABLE t1(a,b,c,d); INSERT INTO t1 VALUES(1,2,3,4); + CREATE TABLE t2(x,y); INSERT INTO t2 VALUES(3,4); + CREATE INDEX t2xy ON t2(x,y); + SELECT 1 FROM t1 JOIN t2 ON x=c AND y=d WHERE d>0; +} 1 +do_eqp_test where3-8.2 { + SELECT 1 FROM t1 JOIN t2 ON x=c AND y=d WHERE d>0; +} { + QUERY PLAN + |--SCAN t1 + `--SEARCH t2 USING COVERING INDEX t2xy (x=? AND y=?) +} + + finish_test diff --git a/test/window1.test b/test/window1.test index c9bbae3ee..d8348a898 100644 --- a/test/window1.test +++ b/test/window1.test @@ -1881,7 +1881,7 @@ do_catchsql_test 57.3 { SELECT max(y) OVER( ORDER BY (SELECT x FROM (SELECT sum(y) AS x FROM t1))) ) FROM t3; -} {1 {misuse of aggregate: sum()}} +} {0 5} # 2020-06-06 ticket 1f6f353b684fc708 reset_db diff --git a/tool/sqldiff.c b/tool/sqldiff.c index a61256611..cbdfc35cd 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -13,7 +13,8 @@ ** This is a utility program that computes the differences in content ** between two SQLite databases. ** -** To compile, simply link against SQLite. +** To compile, simply link against SQLite. (Windows builds must also link +** against ext/consio/console_io.c.) ** ** See the showHelp() routine below for a brief description of how to ** run the utility. @@ -26,6 +27,19 @@ #include #include "sqlite3.h" +/* Output function substitutions that cause UTF8 characters to be rendered +** correctly on Windows: +** +** fprintf() -> Wfprintf() +** +*/ +#if defined(_WIN32) +# include "console_io.h" +# define Wfprintf fPrintfUtf8 +#else +# define Wfprintf fprintf +#endif + /* ** All global variables are gathered into the "g" singleton. */ @@ -46,22 +60,10 @@ struct GlobalVars { #define DEBUG_DIFF_SQL 0x000002 /* -** Dynamic string object +** Clear and free an sqlite3_str object */ -typedef struct Str Str; -struct Str { - char *z; /* Text of the string */ - int nAlloc; /* Bytes allocated in z[] */ - int nUsed; /* Bytes actually used in z[] */ -}; - -/* -** Initialize a Str object -*/ -static void strInit(Str *p){ - p->z = 0; - p->nAlloc = 0; - p->nUsed = 0; +static void strFree(sqlite3_str *pStr){ + sqlite3_free(sqlite3_str_finish(pStr)); } /* @@ -69,12 +71,14 @@ static void strInit(Str *p){ ** abort the program. */ static void cmdlineError(const char *zFormat, ...){ + sqlite3_str *pOut = sqlite3_str_new(0); va_list ap; - fprintf(stderr, "%s: ", g.zArgv0); va_start(ap, zFormat); - vfprintf(stderr, zFormat, ap); + sqlite3_str_vappendf(pOut, zFormat, ap); va_end(ap); - fprintf(stderr, "\n\"%s --help\" for more help\n", g.zArgv0); + Wfprintf(stderr, "%s: %s\n", g.zArgv0, sqlite3_str_value(pOut)); + strFree(pOut); + Wfprintf(stderr, "\"%s --help\" for more help\n", g.zArgv0); exit(1); } @@ -83,49 +87,16 @@ static void cmdlineError(const char *zFormat, ...){ ** abort the program. */ static void runtimeError(const char *zFormat, ...){ + sqlite3_str *pOut = sqlite3_str_new(0); va_list ap; - fprintf(stderr, "%s: ", g.zArgv0); va_start(ap, zFormat); - vfprintf(stderr, zFormat, ap); + sqlite3_str_vappendf(pOut, zFormat, ap); va_end(ap); - fprintf(stderr, "\n"); + Wfprintf(stderr, "%s: %s\n", g.zArgv0, sqlite3_str_value(pOut)); + strFree(pOut); exit(1); } -/* -** Free all memory held by a Str object -*/ -static void strFree(Str *p){ - sqlite3_free(p->z); - strInit(p); -} - -/* -** Add formatted text to the end of a Str object -*/ -static void strPrintf(Str *p, const char *zFormat, ...){ - int nNew; - for(;;){ - if( p->z ){ - va_list ap; - va_start(ap, zFormat); - sqlite3_vsnprintf(p->nAlloc-p->nUsed, p->z+p->nUsed, zFormat, ap); - va_end(ap); - nNew = (int)strlen(p->z + p->nUsed); - }else{ - nNew = p->nAlloc; - } - if( p->nUsed+nNew < p->nAlloc-1 ){ - p->nUsed += nNew; - break; - } - p->nAlloc = p->nAlloc*2 + 1000; - p->z = sqlite3_realloc(p->z, p->nAlloc); - if( p->z==0 ) runtimeError("out of memory"); - } -} - - /* Safely quote an SQL identifier. Use the minimum amount of transformation ** necessary to allow the string to be used with %s. @@ -453,7 +424,7 @@ static void dump_table(const char *zTab, FILE *out){ int i; /* Loop counter */ sqlite3_stmt *pStmt; /* SQL statement */ const char *zSep; /* Separator string */ - Str ins; /* Beginning of the INSERT statement */ + sqlite3_str *pIns; /* Beginning of the INSERT statement */ pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema WHERE name=%Q", zTab); if( SQLITE_ROW==sqlite3_step(pStmt) ){ @@ -462,54 +433,53 @@ static void dump_table(const char *zTab, FILE *out){ sqlite3_finalize(pStmt); if( !g.bSchemaOnly ){ az = columnNames("aux", zTab, &nPk, 0); - strInit(&ins); + pIns = sqlite3_str_new(0); if( az==0 ){ pStmt = db_prepare("SELECT * FROM aux.%s", zId); - strPrintf(&ins,"INSERT INTO %s VALUES", zId); + sqlite3_str_appendf(pIns,"INSERT INTO %s VALUES", zId); }else{ - Str sql; - strInit(&sql); + sqlite3_str *pSql = sqlite3_str_new(0); zSep = "SELECT"; for(i=0; az[i]; i++){ - strPrintf(&sql, "%s %s", zSep, az[i]); + sqlite3_str_appendf(pSql, "%s %s", zSep, az[i]); zSep = ","; } - strPrintf(&sql," FROM aux.%s", zId); + sqlite3_str_appendf(pSql," FROM aux.%s", zId); zSep = " ORDER BY"; for(i=1; i<=nPk; i++){ - strPrintf(&sql, "%s %d", zSep, i); + sqlite3_str_appendf(pSql, "%s %d", zSep, i); zSep = ","; } - pStmt = db_prepare("%s", sql.z); - strFree(&sql); - strPrintf(&ins, "INSERT INTO %s", zId); + pStmt = db_prepare("%s", sqlite3_str_value(pSql)); + strFree(pSql); + sqlite3_str_appendf(pIns, "INSERT INTO %s", zId); zSep = "("; for(i=0; az[i]; i++){ - strPrintf(&ins, "%s%s", zSep, az[i]); + sqlite3_str_appendf(pIns, "%s%s", zSep, az[i]); zSep = ","; } - strPrintf(&ins,") VALUES"); + sqlite3_str_appendf(pIns,") VALUES"); namelistFree(az); } nCol = sqlite3_column_count(pStmt); while( SQLITE_ROW==sqlite3_step(pStmt) ){ - fprintf(out, "%s",ins.z); + Wfprintf(out, "%s",sqlite3_str_value(pIns)); zSep = "("; for(i=0; inPk2 ){ zSep = "SELECT "; for(i=0; i1)?", ":""), i); + sqlite3_str_appendf(pSql, "\nORDER BY "); + for(i=1; i<=nPK; i++) sqlite3_str_appendf(pSql, "%s%d", ((i>1)?", ":""), i); } static void rbudiff_one_table(const char *zTab, FILE *out){ @@ -1264,14 +1236,17 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ char **azCol; /* NULL terminated array of col names */ int i; int nCol; - Str ct = {0, 0, 0}; /* The "CREATE TABLE data_xxx" statement */ - Str sql = {0, 0, 0}; /* Query to find differences */ - Str insert = {0, 0, 0}; /* First part of output INSERT statement */ + sqlite3_str *pCt; /* The "CREATE TABLE data_xxx" statement */ + sqlite3_str *pSql; /* Query to find differences */ + sqlite3_str *pInsert; /* First part of output INSERT statement */ sqlite3_stmt *pStmt = 0; int nRow = 0; /* Total rows in data_xxx table */ /* --rbu mode must use real primary keys. */ g.bSchemaPK = 1; + pCt = sqlite3_str_new(0); + pSql = sqlite3_str_new(0); + pInsert = sqlite3_str_new(0); /* Check that the schemas of the two tables match. Exit early otherwise. */ checkSchemasMatch(zTab); @@ -1285,35 +1260,35 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ for(nCol=0; azCol[nCol]; nCol++); /* Build and output the CREATE TABLE statement for the data_xxx table */ - strPrintf(&ct, "CREATE TABLE IF NOT EXISTS 'data_%q'(", zTab); - if( bOtaRowid ) strPrintf(&ct, "rbu_rowid, "); - strPrintfArray(&ct, ", ", "%s", &azCol[bOtaRowid], -1); - strPrintf(&ct, ", rbu_control);"); + sqlite3_str_appendf(pCt, "CREATE TABLE IF NOT EXISTS 'data_%q'(", zTab); + if( bOtaRowid ) sqlite3_str_appendf(pCt, "rbu_rowid, "); + strPrintfArray(pCt, ", ", "%s", &azCol[bOtaRowid], -1); + sqlite3_str_appendf(pCt, ", rbu_control);"); /* Get the SQL for the query to retrieve data from the two databases */ - getRbudiffQuery(zTab, azCol, nPK, bOtaRowid, &sql); + getRbudiffQuery(zTab, azCol, nPK, bOtaRowid, pSql); /* Build the first part of the INSERT statement output for each row ** in the data_xxx table. */ - strPrintf(&insert, "INSERT INTO 'data_%q' (", zTab); - if( bOtaRowid ) strPrintf(&insert, "rbu_rowid, "); - strPrintfArray(&insert, ", ", "%s", &azCol[bOtaRowid], -1); - strPrintf(&insert, ", rbu_control) VALUES("); + sqlite3_str_appendf(pInsert, "INSERT INTO 'data_%q' (", zTab); + if( bOtaRowid ) sqlite3_str_appendf(pInsert, "rbu_rowid, "); + strPrintfArray(pInsert, ", ", "%s", &azCol[bOtaRowid], -1); + sqlite3_str_appendf(pInsert, ", rbu_control) VALUES("); - pStmt = db_prepare("%s", sql.z); + pStmt = db_prepare("%s", sqlite3_str_value(pSql)); while( sqlite3_step(pStmt)==SQLITE_ROW ){ /* If this is the first row output, print out the CREATE TABLE - ** statement first. And then set ct.z to NULL so that it is not + ** statement first. And reset pCt so that it will not be ** printed again. */ - if( ct.z ){ - fprintf(out, "%s\n", ct.z); - strFree(&ct); + if( sqlite3_str_length(pCt) ){ + fprintf(out, "%s\n", sqlite3_str_value(pCt)); + sqlite3_str_reset(pCt); } /* Output the first part of the INSERT statement */ - fprintf(out, "%s", insert.z); + fprintf(out, "%s", sqlite3_str_value(pInsert)); nRow++; if( sqlite3_column_type(pStmt, nCol)==SQLITE_INTEGER ){ @@ -1369,15 +1344,16 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ sqlite3_finalize(pStmt); if( nRow>0 ){ - Str cnt = {0, 0, 0}; - strPrintf(&cnt, "INSERT INTO rbu_count VALUES('data_%q', %d);", zTab, nRow); - fprintf(out, "%s\n", cnt.z); - strFree(&cnt); + sqlite3_str *pCnt = sqlite3_str_new(0); + sqlite3_str_appendf(pCnt, + "INSERT INTO rbu_count VALUES('data_%q', %d);", zTab, nRow); + fprintf(out, "%s\n", sqlite3_str_value(pCnt)); + strFree(pCnt); } - strFree(&ct); - strFree(&sql); - strFree(&insert); + strFree(pCt); + strFree(pSql); + strFree(pInsert); } /* @@ -1399,25 +1375,25 @@ static void summarize_one_table(const char *zTab, FILE *out){ int n2; /* Number of columns in aux */ int i; /* Loop counter */ const char *zSep; /* Separator string */ - Str sql; /* Comparison query */ + sqlite3_str *pSql; /* Comparison query */ sqlite3_stmt *pStmt; /* Query statement to do the diff */ sqlite3_int64 nUpdate; /* Number of updated rows */ sqlite3_int64 nUnchanged; /* Number of unmodified rows */ sqlite3_int64 nDelete; /* Number of deleted rows */ sqlite3_int64 nInsert; /* Number of inserted rows */ - strInit(&sql); + pSql = sqlite3_str_new(0); if( sqlite3_table_column_metadata(g.db,"aux",zTab,0,0,0,0,0,0) ){ if( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){ /* Table missing from second database. */ - fprintf(out, "%s: missing from second database\n", zTab); + Wfprintf(out, "%s: missing from second database\n", zTab); } goto end_summarize_one_table; } if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){ /* Table missing from source */ - fprintf(out, "%s: missing from first database\n", zTab); + Wfprintf(out, "%s: missing from first database\n", zTab); goto end_summarize_one_table; } @@ -1434,57 +1410,57 @@ static void summarize_one_table(const char *zTab, FILE *out){ || az[n] ){ /* Schema mismatch */ - fprintf(out, "%s: incompatible schema\n", zTab); + Wfprintf(out, "%s: incompatible schema\n", zTab); goto end_summarize_one_table; } /* Build the comparison query */ for(n2=n; az[n2]; n2++){} - strPrintf(&sql, "SELECT 1, count(*)"); + sqlite3_str_appendf(pSql, "SELECT 1, count(*)"); if( n2==nPk2 ){ - strPrintf(&sql, ", 0\n"); + sqlite3_str_appendf(pSql, ", 0\n"); }else{ zSep = ", sum("; for(i=nPk; az[i]; i++){ - strPrintf(&sql, "%sA.%s IS NOT B.%s", zSep, az[i], az[i]); + sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s", zSep, az[i], az[i]); zSep = " OR "; } - strPrintf(&sql, ")\n"); + sqlite3_str_appendf(pSql, ")\n"); } - strPrintf(&sql, " FROM main.%s A, aux.%s B\n", zId, zId); + sqlite3_str_appendf(pSql, " FROM main.%s A, aux.%s B\n", zId, zId); zSep = " WHERE"; for(i=0; inPk ){ - strPrintf(&sql, "SELECT %d", SQLITE_UPDATE); + sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_UPDATE); for(i=0; i Date: Thu, 1 Feb 2024 10:31:17 -0500 Subject: [PATCH 119/340] Snapshot of upstream SQLite 3.45.1 --- VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 ++--- ext/fts3/fts3.c | 31 +++----- ext/fts3/fts3Int.h | 2 + ext/fts3/fts3_write.c | 6 +- ext/fts5/fts5_main.c | 16 ++-- ext/fts5/test/fts5integrity.test | 26 +++++++ ext/wasm/GNUmakefile | 7 +- manifest | 48 ++++++------ manifest.uuid | 2 +- src/btree.c | 5 +- src/json.c | 129 ++++++++++++++++++++----------- src/os_unix.c | 7 +- src/os_win.c | 7 +- src/where.c | 5 +- test/fts4intck1.test | 17 ++++ test/json107.test | 86 +++++++++++++++++++++ test/jsonb01.test | 4 + test/mmap1.test | 16 ++-- test/mmapcorrupt.test | 51 ++++++++++++ 21 files changed, 360 insertions(+), 127 deletions(-) create mode 100644 test/json107.test create mode 100644 test/mmapcorrupt.test diff --git a/VERSION b/VERSION index ff3ff28f6..08d7ea82b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.45.0 +3.45.1 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index 4df57344b..740bd9749 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.45.0]) +AC_INIT([sqlite],[3.45.1]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 6a47c4dcc..b7a3bfa42 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.69 for sqlite 3.45.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.45.1. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.45.0' -PACKAGE_STRING='sqlite 3.45.0' +PACKAGE_VERSION='3.45.1' +PACKAGE_STRING='sqlite 3.45.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1472,7 +1472,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.45.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.45.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.45.0:";; + short | recursive ) echo "Configuration of sqlite 3.45.1:";; esac cat <<\_ACEOF @@ -1668,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.45.0 +sqlite configure 3.45.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2087,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.45.0, which was +It was created by sqlite $as_me 3.45.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.45.0, which was +This file was extended by sqlite $as_me 3.45.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.45.0 +sqlite config.status 3.45.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 65852804a..379590188 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -4006,7 +4006,7 @@ static int fts3ShadowName(const char *zName){ ** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual ** table. */ -static int fts3Integrity( +static int fts3IntegrityMethod( sqlite3_vtab *pVtab, /* The virtual table to be checked */ const char *zSchema, /* Name of schema in which pVtab lives */ const char *zTabname, /* Name of the pVTab table */ @@ -4014,30 +4014,21 @@ static int fts3Integrity( char **pzErr /* Write error message here */ ){ Fts3Table *p = (Fts3Table*)pVtab; - char *zSql; int rc; - char *zErr = 0; + int bOk = 0; - assert( pzErr!=0 ); - assert( *pzErr==0 ); UNUSED_PARAMETER(isQuick); - zSql = sqlite3_mprintf( - "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", - zSchema, zTabname, zTabname); - if( zSql==0 ){ - return SQLITE_NOMEM; - } - rc = sqlite3_exec(p->db, zSql, 0, 0, &zErr); - sqlite3_free(zSql); - if( (rc&0xff)==SQLITE_CORRUPT ){ - *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s", - p->bFts4 ? 4 : 3, zSchema, zTabname); - }else if( rc!=SQLITE_OK ){ + rc = sqlite3Fts3IntegrityCheck(p, &bOk); + assert( rc!=SQLITE_CORRUPT_VTAB || bOk==0 ); + if( rc!=SQLITE_OK && rc!=SQLITE_CORRUPT_VTAB ){ *pzErr = sqlite3_mprintf("unable to validate the inverted index for" " FTS%d table %s.%s: %s", - p->bFts4 ? 4 : 3, zSchema, zTabname, zErr); + p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc)); + }else if( bOk==0 ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s", + p->bFts4 ? 4 : 3, zSchema, zTabname); } - sqlite3_free(zErr); + sqlite3Fts3SegmentsClose(p); return SQLITE_OK; } @@ -4068,7 +4059,7 @@ static const sqlite3_module fts3Module = { /* xRelease */ fts3ReleaseMethod, /* xRollbackTo */ fts3RollbackToMethod, /* xShadowName */ fts3ShadowName, - /* xIntegrity */ fts3Integrity, + /* xIntegrity */ fts3IntegrityMethod, }; /* diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index 5a5b123b4..3b236faf4 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -653,5 +653,7 @@ int sqlite3FtsUnicodeIsdiacritic(int); int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*); +int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk); + #endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */ #endif /* _FTSINT_H */ diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 0f894943d..2516a3908 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -5294,7 +5294,7 @@ static u64 fts3ChecksumIndex( ** If an error occurs (e.g. an OOM or IO error), return an SQLite error ** code. The final value of *pbOk is undefined in this case. */ -static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ +int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){ int rc = SQLITE_OK; /* Return code */ u64 cksum1 = 0; /* Checksum based on FTS index contents */ u64 cksum2 = 0; /* Checksum based on %_content contents */ @@ -5372,7 +5372,7 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ sqlite3_finalize(pStmt); } - *pbOk = (cksum1==cksum2); + *pbOk = (rc==SQLITE_OK && cksum1==cksum2); return rc; } @@ -5412,7 +5412,7 @@ static int fts3DoIntegrityCheck( ){ int rc; int bOk = 0; - rc = fts3IntegrityCheck(p, &bOk); + rc = sqlite3Fts3IntegrityCheck(p, &bOk); if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB; return rc; } diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index eb7ee7408..7c818ce74 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -2971,27 +2971,21 @@ static int fts5IntegrityMethod( char **pzErr /* Write error message here */ ){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; - Fts5Config *pConfig = pTab->p.pConfig; - char *zSql; - char *zErr = 0; int rc; + assert( pzErr!=0 && *pzErr==0 ); UNUSED_PARAM(isQuick); - zSql = sqlite3_mprintf( - "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", - zSchema, zTabname, pConfig->zName); - if( zSql==0 ) return SQLITE_NOMEM; - rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr); - sqlite3_free(zSql); + rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0); if( (rc&0xff)==SQLITE_CORRUPT ){ *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", zSchema, zTabname); }else if( rc!=SQLITE_OK ){ *pzErr = sqlite3_mprintf("unable to validate the inverted index for" " FTS5 table %s.%s: %s", - zSchema, zTabname, zErr); + zSchema, zTabname, sqlite3_errstr(rc)); } - sqlite3_free(zErr); + sqlite3Fts5IndexCloseReader(pTab->p.pIndex); + return SQLITE_OK; } diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test index f9851dc15..1bb367538 100644 --- a/ext/fts5/test/fts5integrity.test +++ b/ext/fts5/test/fts5integrity.test @@ -355,4 +355,30 @@ do_execsql_test 11.4 { PRAGMA integrity_check(t2); } {ok} +#------------------------------------------------------------------- +reset_db + +do_execsql_test 12.1 { + CREATE VIRTUAL TABLE x1 USING fts5(a, b); + INSERT INTO x1 VALUES('one', 'two'); + INSERT INTO x1 VALUES('three', 'four'); + INSERT INTO x1 VALUES('five', 'six'); +} + +do_execsql_test 12.2 { + PRAGMA integrity_check +} {ok} + +db close +sqlite3 db test.db -readonly 1 + +explain_i { + PRAGMA integrity_check + } +do_execsql_test 12.3 { + PRAGMA integrity_check +} {ok} + + + finish_test diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index f0cff463a..8f733b668 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -652,6 +652,9 @@ $(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) ######################################################################## # $(sqlite3-license-version.js) contains the license header and # in-comment build version info. +# +# Maintenance reminder: there are awk binaries out there which do not +# support -e SCRIPT. $(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) \ $(MAKEFILE) @echo "Making $@..."; { \ @@ -659,8 +662,8 @@ $(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) echo '/*'; \ echo '** This code was built from sqlite3 version...'; \ echo "**"; \ - awk -e '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' \ - -e '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ + awk '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' $(sqlite3.h); \ + awk '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ echo "**"; \ echo "** Using the Emscripten SDK version $(emcc.version)."; \ echo '*/'; \ diff --git a/manifest b/manifest index c1d98bbfb..993b2d44c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.45.0 -D 2024-01-15T17:01:13.164 +C Version\s3.45.1 +D 2024-01-30T16:01:20.753 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 F Makefile.msc df56c06ef05add5ebcdcc9d4823fb2ec66ac16b06acb6971a7420859025886fa F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3 -F VERSION 73573d4545343f001bf5dc5461173a7c78c203dd046cabcf99153878cf25d3a6 +F VERSION 3053efa694656bdb7936806c93ba21037a14b66bd098c655957dda84f3c092dd F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -22,7 +22,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac 4c32b08691a5b296206b38422b53b92b65be3d3f6b3dd6552a50981a61f5acda +F autoconf/tea/configure.ac 1d2a7197e018375443c9a0b57d4b637d1a149e573e9937d838abce65e94082e7 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure bcb1042e92775424a1021d2f4c89c78a699a6225df01fa8c593df7df0be6ad10 x +F configure 1e822b6dbb79c7f6cb10a0a8c1798dfd2334471b8f03b94e46a884646b0495f4 x F configure.ac f25bd7843120f2c2b8bc9db5a92b0502bbdd28e68907415c3d42fc8e57c657b9 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -63,9 +63,9 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c d01dfb641fc04efeeadcb94d7a8342eb07d71c1a3a3852ec8ab5e64c1fcdfff9 +F ext/fts3/fts3.c fd64a588471ce00b19da08acb0d6f904277a21ac1d15141d5913c83591afa027 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h be688580701d41340de73384e3acc8c55be12a438583207444bd5e20f9ef426c +F ext/fts3/fts3Int.h 968f7d7cae541a6926146e9fd3fb2b2ccbd3845b7890a8ed03de0c06ac776682 F ext/fts3/fts3_aux.c 7eab82a9cf0830f6551ba3abfdbe73ed39e322a4d3940ee82fbf723674ecd9f3 F ext/fts3/fts3_expr.c 903bfb9433109fffb10e910d7066c49cbf8eeae316adc93f0499c4da7dfc932a F ext/fts3/fts3_hash.c 8b6e31bfb0844c27dc6092c2620bdb1fca17ed613072db057d96952c6bdb48b7 @@ -81,7 +81,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c 5bb4721330ca589f906e72bb824dd4080b313c6d4c4231fa541e9db32dc67982 +F ext/fts3/fts3_write.c c2d7a8dfb6e7a00c6c88ce626785cf4c50ed18eba34b5fbd53cacd60af96d0f2 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -98,7 +98,7 @@ F ext/fts5/fts5_config.c 8072a207034b51ae9b7694121d1b5715c794e94b275e088f70ae532 F ext/fts5/fts5_expr.c e91156ebdcc08d837f4f324168f69f3c0d7fdef0e521fd561efb48ef3297b696 F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1 F ext/fts5/fts5_index.c bb1965c3965f6fe5f64160bf1c0694a9684a790a783f293a76da1d38d319b258 -F ext/fts5/fts5_main.c 94a03dd431022d706290bb81b7f2180a0bb7c98f1397b5fbc90e18d3ed8d366c +F ext/fts5/fts5_main.c cd56ed9619e9bc55ae603ecafd5965c3684bb4c1de7dd00893c307ddf98afe88 F ext/fts5/fts5_storage.c f9e31b0d155e9b2c92d5d3a09ad7a56b937fbf1c7f962e10f4ca6281349f3934 F ext/fts5/fts5_tcl.c cf0fd0dbe64ec272491b749e0d594f563cda03336aeb60900129e6d18b0aefb8 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee @@ -176,7 +176,7 @@ F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b99 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e F ext/fts5/test/fts5hash.test dc7bc7e0cdeb42cfce31294ad2f8fcf43192bfd0145bb7f3ecc5465d8c72696f -F ext/fts5/test/fts5integrity.test 0d249d351163e17e2227aa9850e68193f88a7813d16cc7d51287e554bf915b3d +F ext/fts5/test/fts5integrity.test f1723fe9fb9381b26c946ab4d7505041434df2c449d1cd53f45c7bf8c098dfa2 F ext/fts5/test/fts5interrupt.test 09613247b273a99889808ef852898177e671406fe71fdde7ea00e78ea283d227 F ext/fts5/test/fts5lastrowid.test be98fe3e03235296585b72daad7aed5717ba0062bae5e5c18dd6e04e194c6b28 F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc2782680740513c4d1fc114b43d4ad @@ -575,7 +575,7 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 99aad6d6a28c43573f80825e986427c1a024a3298aaf0c69c56a0c6b336f12c8 +F ext/wasm/GNUmakefile 8a4d5ca21d7f21c4751ef732f2bb837c915641ee8ad25753929998fa44847e61 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff @@ -676,7 +676,7 @@ F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 -F src/btree.c dee25e097b749275333b55d64a5ffc079249576f8e88a2ee476468cf67510f4b +F src/btree.c c64df2b1623501e397128261de58d3ab44c301e4eb993a4055aa971444420200 F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 F src/btreeInt.h 3e2589726c4f105e653461814f65857465da68be1fac688de340c43b873f4062 F src/build.c e7d9044592eeeea8e78d8ae53ca8d31fd6e92ca0d4f53e2f2e8ccf7352e0b04b @@ -697,7 +697,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c 4913fd22c4f0fa30643afb93a4d78d289cd490620e782b31016c3d4b2049b1cc +F src/json.c 31eb3e138661284bc561dd8d23b948126716847571d5b6e86044a284fce81cde F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 F src/main.c 438b95162acfa17b7d218f586f5bde11d6ae82bcf030c9611fc537556870ad6b @@ -721,8 +721,8 @@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c 5dc41030cd5dfd99b907976b1725a4ed695566405d33744e4824c3d6aff245a3 -F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 +F src/os_unix.c fa9b81b642e60e77ffaf98bd1a2e5fde16c1c2317614ec178bf3bd5864772356 +F src/os_win.c 6ff43bac175bd9ed79e7c0f96840b139f2f51d01689a638fd05128becf94908a F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c ff60e98138d2499082ac6230f01ac508aba545315debccfca2fd6042f5f10fcd F src/pager.h 4b1140d691860de0be1347474c51fee07d5420bd7f802d38cbab8ea4ab9f538a @@ -822,7 +822,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c 217fe82a26c0fb6a3c7fd01865d821e752f9c01fb72f114af3f0b77ce234d1fb +F src/where.c 56277e7110e6c81918434908bb7d597b917adfa9a176f5d95eb954b93dbc57b8 F src/whereInt.h 82a13766f13d1a53b05387c2e60726289ef26404bc7b9b1f7770204d97357fb8 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 @@ -1197,7 +1197,7 @@ F test/fts4docid.test e33c383cfbdff0284685604d256f347a18fdbf01 F test/fts4growth.test 289833c34ad45a5e6e6133b53b6a71647231fb89d36ddcb8d9c87211b6721d7f F test/fts4growth2.test 13ad4e76451af6e6906c95cdc725d01b00044269 F test/fts4incr.test 4e353a0bd886ea984e56fce9e77724fc923b8d0d -F test/fts4intck1.test 43774c641fdf6607c6ee90c3db8af065a37434d55d6eaf13bafe515e8b0c5729 +F test/fts4intck1.test 54e7f28e34b72fb0c614d414bb1f568154d463c5a00b20944e893df858372ed4 F test/fts4langid.test 4be912f42454998e239a2e877600263e0394afbaba03e06cedcc5a08693a345a F test/fts4lastrowid.test 185835895948d5325c7710649824042373b2203149abe8024a9319d25234dfd7 F test/fts4merge.test 57d093660a5093ae6e9fbd2d17592a88b45bbd66db2703c4b640b28828dbe38b @@ -1341,9 +1341,10 @@ F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a888011 F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 043838b56e68f3252a0dcf5be1689016f6f3f05056f8dcfcdc9d074f4d932988 F test/json106.test 1d46a9294e2ced35c7f87cebbcb9626d01abab04f1969d7ded7b6f6a1d9be0f2 +F test/json107.test 59054e815c8f6b67d634d44ace421cf975828fb5651c4460aa66015c8e19d562 F test/json501.test ab168a12eb6eb14d479f8c1cdae3ac062fd5a4679f17f976e96f1af518408330 F test/json502.test 84634d3dbb521d2814e43624025b760c6198456c8197bbec6c977c0236648f5b -F test/jsonb01.test cace70765b36a36aec9a85a41ea65667d3bbf647d4400ddc3ac76f8fe7d94f90 +F test/jsonb01.test f4cdfb4cf5a0c940091b17675ed9583f45add0c938f07d65b0de0e19d3a9a101 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff F test/kvtest.c 6e0228409ea7ca0497dad503fbd109badb5e59545d131014b6aaac68b56f484a F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 @@ -1416,10 +1417,11 @@ F test/misc7.test d912f3d45c2989191b797504a220ca225d6be80b21acad22ba0d35f4a9ee45 F test/misc8.test 4db9f8be59834cea08c87e9658014080efa02678ef54a088f84fa5647e81fee0 F test/misuse.test 9e7f78402005e833af71dcab32d048003869eca5abcaccc985d4f8dc1d86bcc7 F test/mjournal.test 28a08d5cb5fb5b5702a46e19176e45e964e0800d1f894677169e79f34030e152 -F test/mmap1.test 5c1f768828094b0dd94e55ae7f10489a1ded74772682be2c4c78679d0acaf7ef +F test/mmap1.test 18de3fd7b70a777af6004ca2feecfcdd3d0be17fa04058e808baf530c94b1a1d F test/mmap2.test 9d6dd9ddb4ad2379f29cc78f38ce1e63ed418022 F test/mmap3.test b3c297e78e6a8520aafcc1a8f140535594c9086e F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 +F test/mmapcorrupt.test 0d89724591f22a376019f3df60d075b838dd2ba6dae6effb0be465c49cf86d4a F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 @@ -2157,10 +2159,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f47a5f4e0ce078e6cc1183e6cbb3c4013af379b496efae94863a42e5c39928ed -R ab5a4296dc153e787688d4ab18626d94 +P ab40e282465c989bf249453d7c6f60072a38b691f579411cdf9aad234b20f0f7 +R 733155be1814ed819345d16353d7d6cc T +sym-release * -T +sym-version-3.45.0 * +T +sym-vesion-3.45.1 * U drh -Z fb1ecde834264212c972e5b30790a005 +Z 27c9e9ada796013bdfd50fea314c088d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8f36bdb72..9f213aa72 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d +e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a diff --git a/src/btree.c b/src/btree.c index 907e37f1e..c41fb811a 100644 --- a/src/btree.c +++ b/src/btree.c @@ -6280,7 +6280,10 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur){ } pPage = pCur->pPage; - assert( pPage->isInit ); + if( sqlite3FaultSim(412) ) pPage->isInit = 0; + if( !pPage->isInit ){ + return SQLITE_CORRUPT_BKPT; + } if( !pPage->leaf ){ int idx = pCur->ix; rc = moveToChild(pCur, get4byte(findCell(pPage, idx))); diff --git a/src/json.c b/src/json.c index 682f30597..70cc4b7bc 100644 --- a/src/json.c +++ b/src/json.c @@ -589,6 +589,16 @@ static void jsonAppendChar(JsonString *p, char c){ } } +/* Remove a single character from the end of the string +*/ +static void jsonStringTrimOneChar(JsonString *p){ + if( p->eErr==0 ){ + assert( p->nUsed>0 ); + p->nUsed--; + } +} + + /* Make sure there is a zero terminator on p->zBuf[] ** ** Return true on success. Return false if an OOM prevents this @@ -596,7 +606,7 @@ static void jsonAppendChar(JsonString *p, char c){ */ static int jsonStringTerminate(JsonString *p){ jsonAppendChar(p, 0); - p->nUsed--; + jsonStringTrimOneChar(p); return p->eErr==0; } @@ -2062,8 +2072,8 @@ static u32 jsonbPayloadSize(const JsonParse *pParse, u32 i, u32 *pSz){ (pParse->aBlob[i+7]<<8) + pParse->aBlob[i+8]; n = 9; } - if( i+sz+n > pParse->nBlob - && i+sz+n > pParse->nBlob-pParse->delta + if( (i64)i+sz+n > pParse->nBlob + && (i64)i+sz+n > pParse->nBlob-pParse->delta ){ sz = 0; n = 0; @@ -2113,6 +2123,7 @@ static u32 jsonTranslateBlobToText( } case JSONB_INT: case JSONB_FLOAT: { + if( sz==0 ) goto malformed_jsonb; jsonAppendRaw(pOut, (const char*)&pParse->aBlob[i+n], sz); break; } @@ -2121,6 +2132,7 @@ static u32 jsonTranslateBlobToText( sqlite3_uint64 u = 0; const char *zIn = (const char*)&pParse->aBlob[i+n]; int bOverflow = 0; + if( sz==0 ) goto malformed_jsonb; if( zIn[0]=='-' ){ jsonAppendChar(pOut, '-'); k++; @@ -2143,6 +2155,7 @@ static u32 jsonTranslateBlobToText( case JSONB_FLOAT5: { /* Float literal missing digits beside "." */ u32 k = 0; const char *zIn = (const char*)&pParse->aBlob[i+n]; + if( sz==0 ) goto malformed_jsonb; if( zIn[0]=='-' ){ jsonAppendChar(pOut, '-'); k++; @@ -2256,11 +2269,12 @@ static u32 jsonTranslateBlobToText( jsonAppendChar(pOut, '['); j = i+n; iEnd = j+sz; - while( jeErr==0 ){ j = jsonTranslateBlobToText(pParse, j, pOut); jsonAppendChar(pOut, ','); } - if( sz>0 ) pOut->nUsed--; + if( j>iEnd ) pOut->eErr |= JSTRING_MALFORMED; + if( sz>0 ) jsonStringTrimOneChar(pOut); jsonAppendChar(pOut, ']'); break; } @@ -2269,17 +2283,18 @@ static u32 jsonTranslateBlobToText( jsonAppendChar(pOut, '{'); j = i+n; iEnd = j+sz; - while( jeErr==0 ){ j = jsonTranslateBlobToText(pParse, j, pOut); jsonAppendChar(pOut, (x++ & 1) ? ',' : ':'); } - if( x & 1 ) pOut->eErr |= JSTRING_MALFORMED; - if( sz>0 ) pOut->nUsed--; + if( (x & 1)!=0 || j>iEnd ) pOut->eErr |= JSTRING_MALFORMED; + if( sz>0 ) jsonStringTrimOneChar(pOut); jsonAppendChar(pOut, '}'); break; } default: { + malformed_jsonb: pOut->eErr |= JSTRING_MALFORMED; break; } @@ -3206,6 +3221,38 @@ static void jsonInsertIntoBlob( return; } +/* +** If pArg is a blob that seems like a JSONB blob, then initialize +** p to point to that JSONB and return TRUE. If pArg does not seem like +** a JSONB blob, then return FALSE; +** +** This routine is only called if it is already known that pArg is a +** blob. The only open question is whether or not the blob appears +** to be a JSONB blob. +*/ +static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){ + u32 n, sz = 0; + p->aBlob = (u8*)sqlite3_value_blob(pArg); + p->nBlob = (u32)sqlite3_value_bytes(pArg); + if( p->nBlob==0 ){ + p->aBlob = 0; + return 0; + } + if( NEVER(p->aBlob==0) ){ + return 0; + } + if( (p->aBlob[0] & 0x0f)<=JSONB_OBJECT + && (n = jsonbPayloadSize(p, 0, &sz))>0 + && sz+n==p->nBlob + && ((p->aBlob[0] & 0x0f)>JSONB_FALSE || sz==0) + ){ + return 1; + } + p->aBlob = 0; + p->nBlob = 0; + return 0; +} + /* ** Generate a JsonParse object, containing valid JSONB in aBlob and nBlob, ** from the SQL function argument pArg. Return a pointer to the new @@ -3262,29 +3309,24 @@ static JsonParse *jsonParseFuncArg( return p; } if( eType==SQLITE_BLOB ){ - u32 n, sz = 0; - p->aBlob = (u8*)sqlite3_value_blob(pArg); - p->nBlob = (u32)sqlite3_value_bytes(pArg); - if( p->nBlob==0 ){ - goto json_pfa_malformed; - } - if( NEVER(p->aBlob==0) ){ - goto json_pfa_oom; - } - if( (p->aBlob[0] & 0x0f)>JSONB_OBJECT ){ - goto json_pfa_malformed; - } - n = jsonbPayloadSize(p, 0, &sz); - if( n==0 - || sz+n!=p->nBlob - || ((p->aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0) - ){ - goto json_pfa_malformed; - } - if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){ - goto json_pfa_oom; + if( jsonArgIsJsonb(pArg,p) ){ + if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){ + goto json_pfa_oom; + } + return p; } - return p; + /* If the blob is not valid JSONB, fall through into trying to cast + ** the blob into text which is then interpreted as JSON. (tag-20240123-a) + ** + ** This goes against all historical documentation about how the SQLite + ** JSON functions were suppose to work. From the beginning, blob was + ** reserved for expansion and a blob value should have raised an error. + ** But it did not, due to a bug. And many applications came to depend + ** upon this buggy behavior, espeically when using the CLI and reading + ** JSON text using readfile(), which returns a blob. For this reason + ** we will continue to support the bug moving forward. + ** See for example https://sqlite.org/forum/forumpost/012136abd5292b8d + */ } p->zJson = (char*)sqlite3_value_text(pArg); p->nJson = sqlite3_value_bytes(pArg); @@ -4260,12 +4302,12 @@ static void jsonValidFunc( return; } case SQLITE_BLOB: { - if( (flags & 0x0c)!=0 && jsonFuncArgMightBeBinary(argv[0]) ){ + if( jsonFuncArgMightBeBinary(argv[0]) ){ if( flags & 0x04 ){ /* Superficial checking only - accomplished by the ** jsonFuncArgMightBeBinary() call above. */ res = 1; - }else{ + }else if( flags & 0x08 ){ /* Strict checking. Check by translating BLOB->TEXT->BLOB. If ** no errors occur, call that a "strict check". */ JsonParse px; @@ -4276,8 +4318,11 @@ static void jsonValidFunc( iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1); res = iErr==0; } + break; } - break; + /* Fall through into interpreting the input as text. See note + ** above at tag-20240123-a. */ + /* no break */ deliberate_fall_through } default: { JsonParse px; @@ -4402,7 +4447,7 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ if( isFinal ){ if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf); }else{ - pStr->nUsed--; + jsonStringTrimOneChar(pStr); } return; }else if( isFinal ){ @@ -4412,7 +4457,7 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); - pStr->nUsed--; + jsonStringTrimOneChar(pStr); } }else{ sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC); @@ -4522,7 +4567,7 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ if( isFinal ){ if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf); }else{ - pStr->nUsed--; + jsonStringTrimOneChar(pStr); } return; }else if( isFinal ){ @@ -4532,7 +4577,7 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); - pStr->nUsed--; + jsonStringTrimOneChar(pStr); } }else{ sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC); @@ -5013,13 +5058,9 @@ static int jsonEachFilter( memset(&p->sParse, 0, sizeof(p->sParse)); p->sParse.nJPRef = 1; p->sParse.db = p->db; - if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){ - if( jsonFuncArgMightBeBinary(argv[0]) ){ - p->sParse.nBlob = sqlite3_value_bytes(argv[0]); - p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]); - }else{ - goto json_each_malformed_input; - } + if( jsonFuncArgMightBeBinary(argv[0]) ){ + p->sParse.nBlob = sqlite3_value_bytes(argv[0]); + p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]); }else{ p->sParse.zJson = (char*)sqlite3_value_text(argv[0]); p->sParse.nJson = sqlite3_value_bytes(argv[0]); diff --git a/src/os_unix.c b/src/os_unix.c index 80e6f6ad9..4b3d63c2c 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5441,11 +5441,16 @@ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ #if SQLITE_MAX_MMAP_SIZE>0 if( pFd->mmapSizeMax>0 ){ + /* Ensure that there is always at least a 256 byte buffer of addressable + ** memory following the returned page. If the database is corrupt, + ** SQLite may overread the page slightly (in practice only a few bytes, + ** but 256 is safe, round, number). */ + const int nEofBuffer = 256; if( pFd->pMapRegion==0 ){ int rc = unixMapfile(pFd, -1); if( rc!=SQLITE_OK ) return rc; } - if( pFd->mmapSize >= iOff+nAmt ){ + if( pFd->mmapSize >= (iOff+nAmt+nEofBuffer) ){ *pp = &((u8 *)pFd->pMapRegion)[iOff]; pFd->nFetchOut++; } diff --git a/src/os_win.c b/src/os_win.c index dc16c08b5..442c108e9 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -4521,6 +4521,11 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ #if SQLITE_MAX_MMAP_SIZE>0 if( pFd->mmapSizeMax>0 ){ + /* Ensure that there is always at least a 256 byte buffer of addressable + ** memory following the returned page. If the database is corrupt, + ** SQLite may overread the page slightly (in practice only a few bytes, + ** but 256 is safe, round, number). */ + const int nEofBuffer = 256; if( pFd->pMapRegion==0 ){ int rc = winMapfile(pFd, -1); if( rc!=SQLITE_OK ){ @@ -4529,7 +4534,7 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ return rc; } } - if( pFd->mmapSize >= iOff+nAmt ){ + if( pFd->mmapSize >= (iOff+nAmt+nEofBuffer) ){ assert( pFd->pMapRegion!=0 ); *pp = &((u8 *)pFd->pMapRegion)[iOff]; pFd->nFetchOut++; diff --git a/src/where.c b/src/where.c index 4ff2815ba..77813666e 100644 --- a/src/where.c +++ b/src/where.c @@ -6056,7 +6056,10 @@ WhereInfo *sqlite3WhereBegin( /* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */ testcase( pOrderBy && pOrderBy->nExpr==BMS-1 ); - if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0; + if( pOrderBy && pOrderBy->nExpr>=BMS ){ + pOrderBy = 0; + wctrlFlags &= ~WHERE_WANT_DISTINCT; + } /* The number of tables in the FROM clause is limited by the number of ** bits in a Bitmask diff --git a/test/fts4intck1.test b/test/fts4intck1.test index abdc46bf5..6596b2f99 100644 --- a/test/fts4intck1.test +++ b/test/fts4intck1.test @@ -54,5 +54,22 @@ do_execsql_test 2.3 { PRAGMA integrity_check(t2); } {{malformed inverted index for FTS4 table main.t2}} +#------------------------------------------------------------------------- +# Test that integrity-check works on a read-only database. +# +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE x1 USING fts4(a, b); + INSERT INTO x1 VALUES('one', 'two'); + INSERT INTO x1 VALUES('three', 'four'); +} +db close +sqlite3 db test.db -readonly 1 + +do_execsql_test 3.1 { + PRAGMA integrity_check; +} {ok} + + finish_test diff --git a/test/json107.test b/test/json107.test new file mode 100644 index 000000000..779b557fb --- /dev/null +++ b/test/json107.test @@ -0,0 +1,86 @@ +# 2024-01-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. +# +#*********************************************************************** +# +# Legacy JSON bug: If the input is a BLOB that when cast into TEXT looks +# like valid JSON, then treat it as valid JSON. +# +# The original intent of the JSON functions was to raise an error on any +# BLOB input. That intent was clearly documented, but the code failed to +# to implement it. Subsequently, many applications began to depend on the +# incorrect behavior, especially apps that used readfile() to read JSON +# content, since readfile() returns a BLOB. So we need to support the +# bug moving forward. +# +# The tests in this fail verify that the original buggy behavior is +# preserved. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix json107 + +if {[db one {PRAGMA encoding}]!="UTF-8"} { + # These tests only work for a UTF-8 encoding. + finish_test + return +} + +do_execsql_test 1.1 { + SELECT json_valid( CAST('{"a":1}' AS BLOB) ); +} 1 +do_execsql_test 1.1.1 { + SELECT json_valid( CAST('{"a":1}' AS BLOB), 1); +} 1 +do_execsql_test 1.1.2 { + SELECT json_valid( CAST('{"a":1}' AS BLOB), 2); +} 1 +do_execsql_test 1.1.4 { + SELECT json_valid( CAST('{"a":1}' AS BLOB), 4); +} 0 +do_execsql_test 1.1.8 { + SELECT json_valid( CAST('{"a":1}' AS BLOB), 8); +} 0 + +do_execsql_test 1.2.1 { + SELECT CAST('{"a":123}' AS blob) -> 'a'; +} 123 +do_execsql_test 1.2.2 { + SELECT CAST('{"a":123}' AS blob) ->> 'a'; +} 123 +do_execsql_test 1.2.3 { + SELECT json_extract(CAST('{"a":123}' AS blob), '$.a'); +} 123 +do_execsql_test 1.3 { + SELECT json_insert(CAST('{"a":123}' AS blob),'$.b',456); +} {{{"a":123,"b":456}}} +do_execsql_test 1.4 { + SELECT json_remove(CAST('{"a":123,"b":456}' AS blob),'$.a'); +} {{{"b":456}}} +do_execsql_test 1.5 { + SELECT json_set(CAST('{"a":123,"b":456}' AS blob),'$.a',789); +} {{{"a":789,"b":456}}} +do_execsql_test 1.6 { + SELECT json_replace(CAST('{"a":123,"b":456}' AS blob),'$.a',789); +} {{{"a":789,"b":456}}} +do_execsql_test 1.7 { + SELECT json_type(CAST('{"a":123,"b":456}' AS blob)); +} object +do_execsql_test 1.8 { + SELECT json(CAST('{"a":123,"b":456}' AS blob)); +} {{{"a":123,"b":456}}} + +ifcapable vtab { + do_execsql_test 2.1 { + SELECT key, value FROM json_tree( CAST('{"a":123,"b":456}' AS blob) ) + WHERE atom; + } {a 123 b 456} +} +finish_test diff --git a/test/jsonb01.test b/test/jsonb01.test index d1b53ae6c..8f16428dc 100644 --- a/test/jsonb01.test +++ b/test/jsonb01.test @@ -46,4 +46,8 @@ foreach {id path res} { } $res } +do_catchsql_test jsonb01-2.0 { + SELECT x'8ce6ffffffff171333' -> '$'; +} {1 {malformed JSON}} + finish_test diff --git a/test/mmap1.test b/test/mmap1.test index 3362f7187..6a9625427 100644 --- a/test/mmap1.test +++ b/test/mmap1.test @@ -45,18 +45,18 @@ proc register_rblob_code {dbname seed} { } -# For cases 1.1 and 1.4, the number of pages read using xRead() is 4 on -# unix and 9 on windows. The difference is that windows only ever maps +# For cases 1.1 and 1.4, the number of pages read using xRead() is 8 on +# unix and 12 on windows. The difference is that windows only ever maps # an integer number of OS pages (i.e. creates mappings that are a multiple # of 4KB in size). Whereas on unix any sized mapping may be created. # foreach {t mmap_size nRead c2init} { - 1.1 { PRAGMA mmap_size = 67108864 } /[49]/ {PRAGMA mmap_size = 0} - 1.2 { PRAGMA mmap_size = 53248 } 150 {PRAGMA mmap_size = 0} - 1.3 { PRAGMA mmap_size = 0 } 344 {PRAGMA mmap_size = 0} - 1.4 { PRAGMA mmap_size = 67108864 } /[49]/ {PRAGMA mmap_size = 67108864 } - 1.5 { PRAGMA mmap_size = 53248 } 150 {PRAGMA mmap_size = 67108864 } - 1.6 { PRAGMA mmap_size = 0 } 344 {PRAGMA mmap_size = 67108864 } + 1.1 { PRAGMA mmap_size = 67108864 } /8|12/ {PRAGMA mmap_size = 0} + 1.2 { PRAGMA mmap_size = 53248 } /15[34]/ {PRAGMA mmap_size = 0} + 1.3 { PRAGMA mmap_size = 0 } 344 {PRAGMA mmap_size = 0} + 1.4 { PRAGMA mmap_size = 67108864 } /12|8/ {PRAGMA mmap_size = 67108864 } + 1.5 { PRAGMA mmap_size = 53248 } /15[34]/ {PRAGMA mmap_size = 67108864 } + 1.6 { PRAGMA mmap_size = 0 } 344 {PRAGMA mmap_size = 67108864 } } { do_multiclient_test tn { diff --git a/test/mmapcorrupt.test b/test/mmapcorrupt.test new file mode 100644 index 000000000..70dbe8464 --- /dev/null +++ b/test/mmapcorrupt.test @@ -0,0 +1,51 @@ +# 2024 January 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. +# +#*********************************************************************** +# +# Test special cases of corrupt database handling in mmap-mode. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix mmapcorrupt + +database_may_be_corrupt + +db close +sqlite3_shutdown +sqlite3_config_lookaside 0 0 +sqlite3_initialize + +reset_db +do_execsql_test 1.0 { + PRAGMA page_size = 16384; + CREATE TABLE tn1(a PRIMARY KEY) WITHOUT ROWID; + CREATE TABLE t0(a PRIMARY KEY) WITHOUT ROWID; + CREATE TABLE t1(a PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t1 VALUES('B'); +} +db close + +set sz [file size test.db] +hexio_write test.db [expr $sz-3] 800380 + +sqlite3 db test.db +do_execsql_test 2.1 { + PRAGMA mmap_size = 1000000; + SELECT sql FROM sqlite_schema LIMIT 1; + SELECT * FROM t0; +} {1000000 {CREATE TABLE tn1(a PRIMARY KEY) WITHOUT ROWID}} + +do_execsql_test 2.2 { + INSERT INTO t0 SELECT * FROM t1; +} + +finish_test + From ae808b5cf74f46c97237abbfc5bdc152b9dc1b18 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 22 Feb 2024 15:37:29 -0500 Subject: [PATCH 120/340] fix compilation with SQLCIPHER_OMIT_LOG reported in PR #504 --- src/crypto.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/crypto.h b/src/crypto.h index a06c20dbc..8f967a4e3 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -326,7 +326,6 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); int sqlcipher_set_log(const char *destination); void sqlcipher_set_log_level(unsigned int level); -void sqlcipher_log(unsigned int tag, const char *message, ...); #define SQLCIPHER_LOG_NONE 0x00 #define SQLCIPHER_LOG_ERROR 0x01 @@ -336,6 +335,12 @@ void sqlcipher_log(unsigned int tag, const char *message, ...); #define SQLCIPHER_LOG_TRACE 0x10 #define SQLCIPHER_LOG_ALL 0xffffffff +#ifdef SQLCIPHER_OMIT_LOG +#define sqlcipher_log(tag, message, ...) +#else +void sqlcipher_log(unsigned int tag, const char *message, ...); +#endif + void sqlcipher_vdbe_return_string(Parse*, const char*, const char*, int); #ifdef CODEC_DEBUG_PAGEDATA From 228b244eb3f4ace7e6add3608bcd4b8e3d438ffd Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 23 Feb 2024 12:27:01 -0500 Subject: [PATCH 121/340] add privacy manifest for cocoapods --- SQLCipher.podspec.json | 1 + sqlcipher-resources/PrivacyInfo.xcprivacy | 29 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 sqlcipher-resources/PrivacyInfo.xcprivacy diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 2ec926437..a24eb58f9 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -49,6 +49,7 @@ ], "name": "common", "source_files": "sqlite3.{h,c}", + "resource_bundles": {"SQLCipher": ["sqlcipher-resources/PrivacyInfo.xcprivacy"]}, "xcconfig": { "HEADER_SEARCH_PATHS": "$(PODS_ROOT)/SQLCipher", "GCC_PREPROCESSOR_DEFINITIONS": "$(inherited) SQLITE_HAS_CODEC=1", diff --git a/sqlcipher-resources/PrivacyInfo.xcprivacy b/sqlcipher-resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..9ac078de7 --- /dev/null +++ b/sqlcipher-resources/PrivacyInfo.xcprivacy @@ -0,0 +1,29 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + + + + + NSPrivacyAccessedAPITypes + + + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + From 55abcfd3878e9ad7e811fa04ceb60662d337304a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 23 Feb 2024 13:15:11 -0500 Subject: [PATCH 122/340] relocate reference databases under sqlcipher-resources --- .../sqlcipher-1.1.8-testkey.db | Bin .../sqlcipher-2.0-be-testkey.db | Bin .../sqlcipher-2.0-beta-testkey.db | Bin .../sqlcipher-2.0-le-testkey.db | Bin .../sqlcipher-3.0-testkey.db | Bin .../sqlcipher-4.0-testkey.db | Bin test/sqlcipher.tcl | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename sqlcipher-1.1.8-testkey.db => sqlcipher-resources/sqlcipher-1.1.8-testkey.db (100%) rename sqlcipher-2.0-be-testkey.db => sqlcipher-resources/sqlcipher-2.0-be-testkey.db (100%) rename sqlcipher-2.0-beta-testkey.db => sqlcipher-resources/sqlcipher-2.0-beta-testkey.db (100%) rename sqlcipher-2.0-le-testkey.db => sqlcipher-resources/sqlcipher-2.0-le-testkey.db (100%) rename sqlcipher-3.0-testkey.db => sqlcipher-resources/sqlcipher-3.0-testkey.db (100%) rename sqlcipher-4.0-testkey.db => sqlcipher-resources/sqlcipher-4.0-testkey.db (100%) diff --git a/sqlcipher-1.1.8-testkey.db b/sqlcipher-resources/sqlcipher-1.1.8-testkey.db similarity index 100% rename from sqlcipher-1.1.8-testkey.db rename to sqlcipher-resources/sqlcipher-1.1.8-testkey.db diff --git a/sqlcipher-2.0-be-testkey.db b/sqlcipher-resources/sqlcipher-2.0-be-testkey.db similarity index 100% rename from sqlcipher-2.0-be-testkey.db rename to sqlcipher-resources/sqlcipher-2.0-be-testkey.db diff --git a/sqlcipher-2.0-beta-testkey.db b/sqlcipher-resources/sqlcipher-2.0-beta-testkey.db similarity index 100% rename from sqlcipher-2.0-beta-testkey.db rename to sqlcipher-resources/sqlcipher-2.0-beta-testkey.db diff --git a/sqlcipher-2.0-le-testkey.db b/sqlcipher-resources/sqlcipher-2.0-le-testkey.db similarity index 100% rename from sqlcipher-2.0-le-testkey.db rename to sqlcipher-resources/sqlcipher-2.0-le-testkey.db diff --git a/sqlcipher-3.0-testkey.db b/sqlcipher-resources/sqlcipher-3.0-testkey.db similarity index 100% rename from sqlcipher-3.0-testkey.db rename to sqlcipher-resources/sqlcipher-3.0-testkey.db diff --git a/sqlcipher-4.0-testkey.db b/sqlcipher-resources/sqlcipher-4.0-testkey.db similarity index 100% rename from sqlcipher-4.0-testkey.db rename to sqlcipher-resources/sqlcipher-4.0-testkey.db diff --git a/test/sqlcipher.tcl b/test/sqlcipher.tcl index 4129dca2f..d058d366b 100644 --- a/test/sqlcipher.tcl +++ b/test/sqlcipher.tcl @@ -40,7 +40,7 @@ file delete -force test3.db file delete -force test4.db set testdir [file dirname $argv0] -set sampleDir [file normalize [file dirname [file dirname $argv0]]] +set sampleDir [file normalize [file dirname [file dirname $argv0]]]/sqlcipher-resources # If the library is not compiled with has_codec support then # skip all tests in this file. From 9b5158e053907175d64b92b164431836666746d4 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 7 Mar 2024 10:49:56 -0500 Subject: [PATCH 123/340] add "device" logging and profile target using os_log for apple (and logcat on android) --- src/crypto.h | 5 +++++ src/crypto_impl.c | 48 +++++++++++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 8f967a4e3..21522cc51 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -44,6 +44,11 @@ #include #endif +#ifdef __APPLE__ +#include +#include +#endif + #include #if defined(_WIN32) || defined(SQLITE_OS_WINRT) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 6e3ab2c3e..a27c3da67 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -79,7 +79,7 @@ static sqlcipher_provider *default_provider = NULL; static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; static FILE* sqlcipher_log_file = NULL; -static volatile int sqlcipher_log_logcat = 0; +static volatile int sqlcipher_log_device = 0; static volatile unsigned int sqlcipher_log_level = SQLCIPHER_LOG_NONE; sqlite3_mutex* sqlcipher_mutex(int mutex) { @@ -1630,16 +1630,22 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz } #if !defined(SQLITE_OMIT_TRACE) + +#define SQLCIPHER_PROFILE_FMT "Elapsed time:%.3f ms - %s\n" +#define SQLCIPHER_PROFILE_FMT_OSLOG "Elapsed time:%{public}.3f ms - %{public}s\n" + static int sqlcipher_profile_callback(unsigned int trace, void *file, void *stmt, void *run_time){ FILE *f = (FILE*) file; - char *fmt = "Elapsed time:%.3f ms - %s\n"; double elapsed = (*((sqlite3_uint64*)run_time))/1000000.0; -#ifdef __ANDROID__ if(f == NULL) { - __android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", fmt, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); - } +#if defined(__ANDROID__) + __android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", SQLCIPHER_PROFILE_FMT, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); +#elif defined(__APPLE__) + os_log(OS_LOG_DEFAULT, SQLCIPHER_PROFILE_FMT_OSLOG, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); #endif - if(f) fprintf(f, fmt, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); + } else { + fprintf(f, SQLCIPHER_PROFILE_FMT, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); + } return SQLITE_OK; } #endif @@ -1656,8 +1662,8 @@ int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ f = stdout; }else if(sqlite3_stricmp(destination, "stderr") == 0){ f = stderr; - }else if(sqlite3_stricmp(destination, "logcat") == 0){ - f = NULL; /* file pointer will be NULL indicating logcat on android */ + }else if(sqlite3_stricmp(destination, "logcat") == 0 || sqlite3_stricmp(destination, "device") == 0){ + f = NULL; /* file pointer will be NULL indicating the device target (i.e. logcat or oslog). We will accept logcat for backwards compatibility */ }else{ #if !defined(SQLCIPHER_PROFILE_USE_FOPEN) && (defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT)) if(fopen_s(&f, destination, "a") != 0) return SQLITE_ERROR; @@ -1686,17 +1692,22 @@ const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) { void sqlcipher_log(unsigned int level, const char *message, ...) { va_list params; va_start(params, message); + char *formatted = NULL; #ifdef CODEC_DEBUG -#ifdef __ANDROID__ +#if defined(__ANDROID__) __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); +#elif define(__APPLE__) + formatted = sqlite3_mprintf(message, params); + os_log(OS_LOG_DEFAULT, "%s", formatted); + sqlite3_free(formatted); #else vfprintf(stderr, message, params); fprintf(stderr, "\n"); #endif #endif - if(level > sqlcipher_log_level || (sqlcipher_log_logcat == 0 && sqlcipher_log_file == NULL)) { + if(level > sqlcipher_log_level || (sqlcipher_log_device == 0 && sqlcipher_log_file == NULL)) { /* no log target or tag not in included filters */ goto end; } @@ -1726,11 +1737,15 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { fprintf((FILE*)sqlcipher_log_file, "\n"); } } -#ifdef __ANDROID__ - if(sqlcipher_log_logcat) { + if(sqlcipher_log_device) { +#if defined(__ANDROID__) __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); - } +#elif defined(__APPLE__) + formatted = sqlite3_mprintf(message, params); + os_log(OS_LOG_DEFAULT, "%{public}s", formatted); + sqlite3_free(formatted); #endif + } end: va_end(params); } @@ -1750,10 +1765,11 @@ int sqlcipher_set_log(const char *destination){ fclose((FILE*)sqlcipher_log_file); } sqlcipher_log_file = NULL; - sqlcipher_log_logcat = 0; + sqlcipher_log_device = 0; - if(sqlite3_stricmp(destination, "logcat") == 0){ - sqlcipher_log_logcat = 1; + if(sqlite3_stricmp(destination, "logcat") == 0 || sqlite3_stricmp(destination, "device") == 0){ + /* use the appropriate device log. accept logcat for backwards compatibility */ + sqlcipher_log_device = 1; } else if(sqlite3_stricmp(destination, "stdout") == 0){ sqlcipher_log_file = stdout; }else if(sqlite3_stricmp(destination, "stderr") == 0){ From 62e677759b99cf311e29059a2a7da5eefc54bf11 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 7 Mar 2024 20:02:03 -0500 Subject: [PATCH 124/340] correct log formatting --- src/crypto_impl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index a27c3da67..ca6c610aa 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1698,7 +1698,7 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { #if defined(__ANDROID__) __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); #elif define(__APPLE__) - formatted = sqlite3_mprintf(message, params); + formatted = sqlite3_vmprintf(message, params); os_log(OS_LOG_DEFAULT, "%s", formatted); sqlite3_free(formatted); #else @@ -1741,7 +1741,7 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { #if defined(__ANDROID__) __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); #elif defined(__APPLE__) - formatted = sqlite3_mprintf(message, params); + formatted = sqlite3_vmprintf(message, params); os_log(OS_LOG_DEFAULT, "%{public}s", formatted); sqlite3_free(formatted); #endif From 8a098cbe78b9926a35704736107496606e1cf18a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Sat, 9 Mar 2024 14:30:08 -0500 Subject: [PATCH 125/340] instruct amalgamator not to cache openssl includes --- src/crypto_openssl.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index 8ab1ad3ab..ce0c11690 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -34,12 +34,12 @@ #include "sqliteInt.h" #include "crypto.h" #include "sqlcipher.h" -#include -#include -#include -#include -#include -#include +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ static unsigned int openssl_init_count = 0; From 70c31b9b889d9db7070f63b97bbd74276ec77d6b Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 11 Mar 2024 12:22:14 -0400 Subject: [PATCH 126/340] allow exclusion of device-specific log functionality via SQLCIPHER_OMIT_LOG_DEVICE --- src/crypto.h | 8 ++++---- src/crypto_impl.c | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 21522cc51..cdd973caa 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -40,14 +40,14 @@ #include "pager.h" #include "vdbeInt.h" -#ifdef __ANDROID__ +#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) +#if defined(__ANDROID__) #include -#endif - -#ifdef __APPLE__ +#elif defined(__APPLE__) #include #include #endif +#endif #include diff --git a/src/crypto_impl.c b/src/crypto_impl.c index ca6c610aa..108bc6438 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1638,10 +1638,12 @@ static int sqlcipher_profile_callback(unsigned int trace, void *file, void *stmt FILE *f = (FILE*) file; double elapsed = (*((sqlite3_uint64*)run_time))/1000000.0; if(f == NULL) { +#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) #if defined(__ANDROID__) __android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", SQLCIPHER_PROFILE_FMT, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); #elif defined(__APPLE__) os_log(OS_LOG_DEFAULT, SQLCIPHER_PROFILE_FMT_OSLOG, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); +#endif #endif } else { fprintf(f, SQLCIPHER_PROFILE_FMT, elapsed, sqlite3_sql((sqlite3_stmt*)stmt)); @@ -1695,16 +1697,17 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { char *formatted = NULL; #ifdef CODEC_DEBUG +#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) #if defined(__ANDROID__) __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); -#elif define(__APPLE__) +#elif defined(__APPLE__) formatted = sqlite3_vmprintf(message, params); os_log(OS_LOG_DEFAULT, "%s", formatted); sqlite3_free(formatted); -#else +#endif +#endif vfprintf(stderr, message, params); fprintf(stderr, "\n"); -#endif #endif if(level > sqlcipher_log_level || (sqlcipher_log_device == 0 && sqlcipher_log_file == NULL)) { @@ -1737,6 +1740,7 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { fprintf((FILE*)sqlcipher_log_file, "\n"); } } +#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) if(sqlcipher_log_device) { #if defined(__ANDROID__) __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); @@ -1746,6 +1750,8 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { sqlite3_free(formatted); #endif } +#endif + end: va_end(params); } From 53f02caee06668764ac2a472aae06d8dd2bddb58 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 15 Mar 2024 11:59:59 -0400 Subject: [PATCH 127/340] ensure a single log output target per invocation of sqlcipher_log() and avoid reuse of params --- src/crypto_impl.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 108bc6438..9dffb5f9b 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1697,23 +1697,46 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { char *formatted = NULL; #ifdef CODEC_DEBUG -#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) +#if defined(SQLCIPHER_OMIT_LOG_DEVICE) + vfprintf(stderr, message, params); + fprintf(stderr, "\n"); + goto end; +#else #if defined(__ANDROID__) __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); + goto end; #elif defined(__APPLE__) formatted = sqlite3_vmprintf(message, params); os_log(OS_LOG_DEFAULT, "%s", formatted); sqlite3_free(formatted); -#endif -#endif + goto end; +#else vfprintf(stderr, message, params); fprintf(stderr, "\n"); + goto end; +#endif +#endif #endif if(level > sqlcipher_log_level || (sqlcipher_log_device == 0 && sqlcipher_log_file == NULL)) { /* no log target or tag not in included filters */ goto end; } + +#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) + if(sqlcipher_log_device) { +#if defined(__ANDROID__) + __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); + goto end; +#elif defined(__APPLE__) + formatted = sqlite3_vmprintf(message, params); + os_log(OS_LOG_DEFAULT, "%{public}s", formatted); + sqlite3_free(formatted); + goto end; +#endif + } +#endif + if(sqlcipher_log_file != NULL){ char buffer[24]; struct tm tt; @@ -1738,19 +1761,9 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { fprintf((FILE*)sqlcipher_log_file, "%s.%03d: ", buffer, ms); vfprintf((FILE*)sqlcipher_log_file, message, params); fprintf((FILE*)sqlcipher_log_file, "\n"); + goto end; } } -#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) - if(sqlcipher_log_device) { -#if defined(__ANDROID__) - __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); -#elif defined(__APPLE__) - formatted = sqlite3_vmprintf(message, params); - os_log(OS_LOG_DEFAULT, "%{public}s", formatted); - sqlite3_free(formatted); -#endif - } -#endif end: va_end(params); From 0eb60296adec11e91b02ef67cd09bb46f463e650 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 18 Mar 2024 13:11:44 -0400 Subject: [PATCH 128/340] Snapshot of upstream SQLite 3.45.2 --- VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 +-- ext/consio/console_io.c | 11 ++ ext/fts5/fts5_index.c | 11 +- ext/fts5/fts5_tcl.c | 2 +- ext/fts5/test/fts5faultH.test | 11 +- ext/misc/noop.c | 22 +++ ext/recover/test_recover.c | 2 +- ext/rtree/rtree.c | 30 ++-- ext/rtree/rtreeJ.test | 273 ++++++++++++++++++++++++++++++++++ manifest | 85 +++++------ manifest.uuid | 2 +- src/btree.c | 10 +- src/func.c | 6 +- src/insert.c | 2 +- src/json.c | 21 ++- src/memdb.c | 8 + src/pragma.c | 59 ++++---- src/printf.c | 1 + src/resolve.c | 54 +++++-- src/shell.c.in | 80 +++++----- src/sqlite.h.in | 2 + src/sqliteInt.h | 5 +- src/test1.c | 49 ++++++ src/upsert.c | 21 ++- src/util.c | 5 +- src/vdbe.c | 8 +- src/vdbeaux.c | 36 ++++- test/func.test | 8 + test/fuzzcheck.c | 4 +- test/json101.test | 14 ++ test/memdb1.test | 15 +- test/notnull2.test | 16 +- test/pragma.test | 15 ++ test/printf.test | 17 +++ test/shell5.test | 14 ++ test/types3.test | 30 +++- test/upsert5.test | 42 ++++++ 39 files changed, 839 insertions(+), 174 deletions(-) create mode 100644 ext/rtree/rtreeJ.test diff --git a/VERSION b/VERSION index 08d7ea82b..caa72fa19 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.45.1 +3.45.2 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index 740bd9749..ef6e9e680 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.45.1]) +AC_INIT([sqlite],[3.45.2]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index b7a3bfa42..5253b4f18 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.69 for sqlite 3.45.1. +# Generated by GNU Autoconf 2.69 for sqlite 3.45.2. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.45.1' -PACKAGE_STRING='sqlite 3.45.1' +PACKAGE_VERSION='3.45.2' +PACKAGE_STRING='sqlite 3.45.2' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1472,7 +1472,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.45.1 to adapt to many kinds of systems. +\`configure' configures sqlite 3.45.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.45.1:";; + short | recursive ) echo "Configuration of sqlite 3.45.2:";; esac cat <<\_ACEOF @@ -1668,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.45.1 +sqlite configure 3.45.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2087,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.45.1, which was +It was created by sqlite $as_me 3.45.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.45.1, which was +This file was extended by sqlite $as_me 3.45.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.45.1 +sqlite config.status 3.45.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c index 3acb0daa2..3e2f556f5 100755 --- a/ext/consio/console_io.c +++ b/ext/consio/console_io.c @@ -29,6 +29,9 @@ #ifndef HAVE_CONSOLE_IO_H # include "console_io.h" #endif +#if defined(_MSC_VER) +# pragma warning(disable : 4204) +#endif #ifndef SQLITE_CIO_NO_TRANSLATE # if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT @@ -127,6 +130,10 @@ static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){ # endif } +# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +# define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4) +# endif + # if CIO_WIN_WC_XLATE /* Define console modes for use with the Windows Console API. */ # define SHELL_CONI_MODE \ @@ -677,4 +684,8 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ } #endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ +#if defined(_MSC_VER) +# pragma warning(default : 4204) +#endif + #undef SHELL_INVALID_FILE_PTR diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 8eb8f328f..333fefa2d 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -6837,23 +6837,26 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){ static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){ int ii; Fts5TokenDataIter *pT = pIter->pTokenDataIter; + Fts5Index *pIndex = pIter->pIndex; for(ii=0; iinIter; ii++){ Fts5Iter *p = pT->apIter[ii]; if( p->base.bEof==0 && (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowidpIndex, p, bFrom, iFrom); + fts5MultiIterNext(pIndex, p, bFrom, iFrom); while( bFrom && p->base.bEof==0 && p->base.iRowidpIndex->rc==SQLITE_OK + && pIndex->rc==SQLITE_OK ){ - fts5MultiIterNext(p->pIndex, p, 0, 0); + fts5MultiIterNext(pIndex, p, 0, 0); } } } - fts5IterSetOutputsTokendata(pIter); + if( pIndex->rc==SQLITE_OK ){ + fts5IterSetOutputsTokendata(pIter); + } } /* diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c index 853a41865..c5b5f41f8 100644 --- a/ext/fts5/fts5_tcl.c +++ b/ext/fts5/fts5_tcl.c @@ -1169,7 +1169,7 @@ struct OriginTextTokenizer { */ static void f5tOrigintextTokenizerDelete(void *pCtx){ OriginTextCtx *p = (OriginTextCtx*)pCtx; - ckfree(p); + ckfree((char*)p); } static int f5tOrigintextCreate( diff --git a/ext/fts5/test/fts5faultH.test b/ext/fts5/test/fts5faultH.test index 540b889f3..df430f20f 100644 --- a/ext/fts5/test/fts5faultH.test +++ b/ext/fts5/test/fts5faultH.test @@ -127,7 +127,7 @@ do_execsql_test 3.0 { COMMIT; } -do_faultsim_test 3 -faults oom* -prep { +do_faultsim_test 3.1 -faults oom* -prep { } -body { execsql { SELECT rowid FROM t1('BBB AND AAA'); @@ -136,6 +136,15 @@ do_faultsim_test 3 -faults oom* -prep { faultsim_integrity_check faultsim_test_result {0 {10 35}} } +do_faultsim_test 3.2 -faults oom* -prep { +} -body { + execsql { + SELECT count(*) FROM t1('BBB'); + } +} -test { + faultsim_integrity_check + faultsim_test_result {0 27} +} finish_test diff --git a/ext/misc/noop.c b/ext/misc/noop.c index d3a58670c..18c25e10f 100644 --- a/ext/misc/noop.c +++ b/ext/misc/noop.c @@ -38,6 +38,24 @@ static void noopfunc( sqlite3_result_value(context, argv[0]); } +/* +** Implementation of the multitype_text() function. +** +** The function returns its argument. The result will always have a +** TEXT value. But if the original input is numeric, it will also +** have that numeric value. +*/ +static void multitypeTextFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + (void)argc; + (void)sqlite3_value_text(argv[0]); + sqlite3_result_value(context, argv[0]); +} + #ifdef _WIN32 __declspec(dllexport) #endif @@ -64,5 +82,9 @@ int sqlite3_noop_init( rc = sqlite3_create_function(db, "noop_nd", 1, SQLITE_UTF8, 0, noopfunc, 0, 0); + if( rc ) return rc; + rc = sqlite3_create_function(db, "multitype_text", 1, + SQLITE_UTF8, + 0, multitypeTextFunc, 0, 0); return rc; } diff --git a/ext/recover/test_recover.c b/ext/recover/test_recover.c index 1c333df8e..ba8ef6da1 100644 --- a/ext/recover/test_recover.c +++ b/ext/recover/test_recover.c @@ -236,7 +236,7 @@ static int test_sqlite3_recover_init( zDb = Tcl_GetString(objv[2]); if( zDb[0]=='\0' ) zDb = 0; - pNew = ckalloc(sizeof(TestRecover)); + pNew = (TestRecover*)ckalloc(sizeof(TestRecover)); if( bSql==0 ){ zUri = Tcl_GetString(objv[3]); pNew->p = sqlite3_recover_init(db, zDb, zUri); diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 013bb0b5b..02127fa11 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -694,11 +694,9 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){ ** Clear the Rtree.pNodeBlob object */ static void nodeBlobReset(Rtree *pRtree){ - if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){ - sqlite3_blob *pBlob = pRtree->pNodeBlob; - pRtree->pNodeBlob = 0; - sqlite3_blob_close(pBlob); - } + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + sqlite3_blob_close(pBlob); } /* @@ -742,7 +740,6 @@ static int nodeAcquire( &pRtree->pNodeBlob); } if( rc ){ - nodeBlobReset(pRtree); *ppNode = 0; /* If unable to open an sqlite3_blob on the desired row, that can only ** be because the shadow tables hold erroneous data. */ @@ -802,6 +799,7 @@ static int nodeAcquire( } *ppNode = pNode; }else{ + nodeBlobReset(pRtree); if( pNode ){ pRtree->nNodeRef--; sqlite3_free(pNode); @@ -946,6 +944,7 @@ static void nodeGetCoord( int iCoord, /* Which coordinate to extract */ RtreeCoord *pCoord /* OUT: Space to write result to */ ){ + assert( iCellzData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord); } @@ -1135,7 +1134,9 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){ sqlite3_finalize(pCsr->pReadAux); sqlite3_free(pCsr); pRtree->nCursor--; - nodeBlobReset(pRtree); + if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){ + nodeBlobReset(pRtree); + } return SQLITE_OK; } @@ -1720,7 +1721,11 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ int rc = SQLITE_OK; RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); if( rc==SQLITE_OK && ALWAYS(p) ){ - *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + if( p->iCell>=NCELL(pNode) ){ + rc = SQLITE_ABORT; + }else{ + *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + } } return rc; } @@ -1738,6 +1743,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ if( rc ) return rc; if( NEVER(p==0) ) return SQLITE_OK; + if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT; if( i==0 ){ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else if( i<=pRtree->nDim2 ){ @@ -3219,8 +3225,7 @@ static int rtreeUpdate( */ static int rtreeBeginTransaction(sqlite3_vtab *pVtab){ Rtree *pRtree = (Rtree *)pVtab; - assert( pRtree->inWrTrans==0 ); - pRtree->inWrTrans++; + pRtree->inWrTrans = 1; return SQLITE_OK; } @@ -3234,6 +3239,9 @@ static int rtreeEndTransaction(sqlite3_vtab *pVtab){ nodeBlobReset(pRtree); return SQLITE_OK; } +static int rtreeRollback(sqlite3_vtab *pVtab){ + return rtreeEndTransaction(pVtab); +} /* ** The xRename method for rtree module virtual tables. @@ -3352,7 +3360,7 @@ static sqlite3_module rtreeModule = { rtreeBeginTransaction, /* xBegin - begin transaction */ rtreeEndTransaction, /* xSync - sync transaction */ rtreeEndTransaction, /* xCommit - commit transaction */ - rtreeEndTransaction, /* xRollback - rollback transaction */ + rtreeRollback, /* xRollback - rollback transaction */ 0, /* xFindFunction - function overloading */ rtreeRename, /* xRename - rename the table */ rtreeSavepoint, /* xSavepoint */ diff --git a/ext/rtree/rtreeJ.test b/ext/rtree/rtreeJ.test new file mode 100644 index 000000000..b091d2c68 --- /dev/null +++ b/ext/rtree/rtreeJ.test @@ -0,0 +1,273 @@ +# 2024-02-03 +# +# 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. +# +#*********************************************************************** +# +# ROLLBACK in the middle of an RTREE query +# +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source $testdir/tester.tcl +set testprefix rtreeJ +ifcapable !rtree { finish_test ; return } + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2); + INSERT INTO t1 VALUES(1, 1, 1), (2, 2, 2); +} {} + +do_execsql_test 1.1 { + SELECT * FROM t1 +} {1 1.0 1.0 2 2.0 2.0} + +# If a ROLLBACK occurs that backs out changes to the RTREE, then +# all pending queries to the RTREE are aborted. +# +do_test 1.2 { + db eval { + BEGIN; + INSERT INTO t1 VALUES(3, 3, 3); + INSERT INTO t1 VALUES(4, 4, 4); + } + set rc [catch { + db eval { SELECT * FROM t1 } { + if {$id==1} { + db eval { ROLLBACK } + } + lappend res $id $x1 $x2 + } + } msg] + list $rc $msg +} {1 {query aborted}} + +do_execsql_test 1.3 { + SELECT * FROM t1; +} {1 1.0 1.0 2 2.0 2.0} + +# A COMMIT of changes to the RTREE does not affect pending queries +# +do_test 1.4 { + set res {} + db eval { + BEGIN; + INSERT INTO t1 VALUES(5, 5, 5); + INSERT INTO t1 VALUES(6, 6, 6); + } + db eval { SELECT * FROM t1 } { + if {$id==1} { + db eval { COMMIT } + } + lappend res $id $x1 $x2 + } + set res +} {1 1.0 1.0 2 2.0 2.0 5 5.0 5.0 6 6.0 6.0} + +do_execsql_test 1.5 { + SELECT * FROM t1; +} {1 1.0 1.0 2 2.0 2.0 5 5.0 5.0 6 6.0 6.0} + +do_execsql_test 1.6 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,1,1),(2,2,2),(3,3,3),(4,4,4); + CREATE TABLE t2(x); + SELECT * FROM t1; +} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0 4 4.0 4.0} + +# A rollback that does not affect the rtree table because +# the rtree table has not been written to does not cause +# a query abort. +# +do_test 1.7 { + set res {} + db eval { + BEGIN; + INSERT INTO t2(x) VALUES(12345); + } + db eval { SELECT * FROM t1 } { + if {$id==1} { + db eval { ROLLBACK } + } + lappend res $id $x1 $x2 + } + set res +} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0 4 4.0 4.0} + +# ROLLBACK TO that affects the RTREE does cause a query abort. +# +do_test 1.8 { + db eval { + DELETE FROM t1 WHERE rowid>1; + BEGIN; + DELETE FROM t2; + INSERT INTO t2(x) VALUES(23456); + SAVEPOINT 'one'; + INSERT INTO t1 VALUES(2,2,2),(3,3,3); + } + set rc [catch { + db eval { SELECT * FROM t1 } { + if {$id==1} { + db eval { ROLLBACK TO 'one'; } + } + lappend res $id $x1 $x2 + } + } msg] + list $rc $msg +} {1 {query aborted}} + +do_execsql_test 1.9 { + COMMIT; + SELECT * FROM t1; +} {1 1.0 1.0} + +# ROLLBACK TO that does not affect the RTREE does not cause a query abort. +# +do_execsql_test 1.10 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,1,1),(2,2,2),(3,3,3); + BEGIN; + DELETE FROM t2; + INSERT INTO t2(x) VALUES(34567); + SAVEPOINT 'one'; + INSERT INTO t2(x) VALUES('a string'); + SELECT * FROM t1; +} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0} +do_test 1.11 { + set rc [catch { + set res {} + db eval { SELECT * FROM t1 } { + if {$id==2} { + # db eval { ROLLBACK TO 'one'; } + } + lappend res $id $x1 $x2 + } + set res + } msg] + list $rc $msg +} {0 {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0}} + +do_execsql_test 1.12 { + COMMIT; + SELECT * FROM t1; +} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0} + +#---------------------------------------------------------------------- + +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2); + INSERT INTO t1 VALUES(1, 1, 1), (2, 2, 2); + CREATE TABLE t2(x); +} {} + +do_test 2.1 { + db eval { + BEGIN; + INSERT INTO t1 VALUES(3, 3, 3); + PRAGMA writable_schema = RESET; + } + + set rc [catch { + db eval { SELECT x1, x2 FROM t1 } { + if {$x1==1} { + db eval { ROLLBACK } + } + lappend res $x1 $x2 + } + } msg] + list $rc $msg +} {1 {query aborted}} + +do_execsql_test 2.1 { + CREATE TABLE bak_node(nodeno, data); + CREATE TABLE bak_parent(nodeno, parentnode); + CREATE TABLE bak_rowid(rowid, nodeno); +} +proc save_t1 {} { + db eval { + DELETE FROM bak_node; + DELETE FROM bak_parent; + DELETE FROM bak_rowid; + INSERT INTO bak_node SELECT * FROM t1_node; + INSERT INTO bak_parent SELECT * FROM t1_parent; + INSERT INTO bak_rowid SELECT * FROM t1_rowid; + } +} +proc restore_t1 {} { + db eval { + DELETE FROM t1_node; + DELETE FROM t1_parent; + DELETE FROM t1_rowid; + INSERT INTO t1_node SELECT * FROM bak_node; + INSERT INTO t1_parent SELECT * FROM bak_parent; + INSERT INTO t1_rowid SELECT * FROM bak_rowid; + } +} + +do_test 2.3 { + save_t1 + db eval { + INSERT INTO t1 VALUES(3, 3, 3); + } + set rc [catch { + db eval { SELECT rowid, x1, x2 FROM t1 } { + if {$x1==1} { + restore_t1 + } + lappend res $x1 $x2 + } + } msg] + list $rc $msg +} {1 {query aborted}} +do_execsql_test 2.4 { + SELECT * FROM t1 +} {1 1.0 1.0 2 2.0 2.0} + +do_test 2.5 { + save_t1 + db eval { + INSERT INTO t1 VALUES(3, 3, 3); + } + set rc [catch { + db eval { SELECT x1 FROM t1 } { + if {$x1==1} { + restore_t1 + } + lappend res $x1 $x2 + } + } msg] + list $rc $msg +} {1 {query aborted}} +do_execsql_test 2.6 { + SELECT * FROM t1 +} {1 1.0 1.0 2 2.0 2.0} + +do_test 2.7 { + save_t1 + db eval { + INSERT INTO t1 VALUES(3, 3, 3); + } + set ::res [list] + set rc [catch { + db eval { SELECT 'abc' FROM t1 } { + if {$::res==[list]} { + restore_t1 + set ::bDone 1 + } + lappend res abc + } + } msg] + set res +} {abc abc abc} +do_execsql_test 2.6 { + SELECT * FROM t1 +} {1 1.0 1.0 2 2.0 2.0} + + +finish_test diff --git a/manifest b/manifest index 993b2d44c..e139b78e6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.45.1 -D 2024-01-30T16:01:20.753 +C Version\s3.45.2 +D 2024-03-12T11:06:23.127 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 F Makefile.msc df56c06ef05add5ebcdcc9d4823fb2ec66ac16b06acb6971a7420859025886fa F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3 -F VERSION 3053efa694656bdb7936806c93ba21037a14b66bd098c655957dda84f3c092dd +F VERSION 0aec26ad81430ff6a34a47daacf854d2fe0d0aae2b822230f949082f0adfffe7 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -22,7 +22,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac 1d2a7197e018375443c9a0b57d4b637d1a149e573e9937d838abce65e94082e7 +F autoconf/tea/configure.ac a86f4955f600af734b106207db9cc03322877864356585e83df84fd4e5c7a586 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 1e822b6dbb79c7f6cb10a0a8c1798dfd2334471b8f03b94e46a884646b0495f4 x +F configure 8dc5fe9688a74fbe82d21598a69d575a12faa8fcf471e95cc7dea514da63b411 x F configure.ac f25bd7843120f2c2b8bc9db5a92b0502bbdd28e68907415c3d42fc8e57c657b9 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -51,7 +51,7 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91 F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad1aff3294f94 F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a -F ext/consio/console_io.c e1be639e79e54264b3ae97ca291728987a9aa82e6a4526458e6400f5e083e524 x +F ext/consio/console_io.c f32b757c9ee7fdf68e7586bee306f8368759e7cd12febb2a6839199b1c1af395 x F ext/consio/console_io.h 0548b83d7c4b7270ad544a67f2bb90cebc519637fa39b1838df4744cf0d87646 F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 @@ -97,10 +97,10 @@ F ext/fts5/fts5_buffer.c 0eec58bff585f1a44ea9147eae5da2447292080ea435957f7488c70 F ext/fts5/fts5_config.c 8072a207034b51ae9b7694121d1b5715c794e94b275e088f70ae532378ca5cdf F ext/fts5/fts5_expr.c e91156ebdcc08d837f4f324168f69f3c0d7fdef0e521fd561efb48ef3297b696 F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1 -F ext/fts5/fts5_index.c bb1965c3965f6fe5f64160bf1c0694a9684a790a783f293a76da1d38d319b258 +F ext/fts5/fts5_index.c ee0f4d50bc0c58a7c5ef7d645e7e38e1e59315b8ea9d722ae00c5f949ee65379 F ext/fts5/fts5_main.c cd56ed9619e9bc55ae603ecafd5965c3684bb4c1de7dd00893c307ddf98afe88 F ext/fts5/fts5_storage.c f9e31b0d155e9b2c92d5d3a09ad7a56b937fbf1c7f962e10f4ca6281349f3934 -F ext/fts5/fts5_tcl.c cf0fd0dbe64ec272491b749e0d594f563cda03336aeb60900129e6d18b0aefb8 +F ext/fts5/fts5_tcl.c fdf7e2bb9a9186cfcaf2d2ce11d338309342b7a7593c2812bc54455db53da5d2 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b F ext/fts5/fts5_tokenize.c 83cfcede3898001cab84432a36ce1503e3080cf9b1c682b022ec82e267ea4c13 @@ -171,7 +171,7 @@ F ext/fts5/test/fts5faultD.test e7ed7895abfe6bc98a5e853826f6b74956e7ba7f594f1860 F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e4710c77eb8ce7075 F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1 F ext/fts5/test/fts5faultG.test d2e5a4d9a34e08dcaadcaeafef74d10cbc2abdd11aa2659a18af0294bf2812d3 -F ext/fts5/test/fts5faultH.test 57f53c87ffd59be0265840f2b54a16811f9cb9012db86aad9b41d0d14d85dfe3 +F ext/fts5/test/fts5faultH.test b5c3b62642b7d321504a0a4f424eb80b4f6927969173334c8ca20df388557622 F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b996159f6909dc8079 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e @@ -389,7 +389,7 @@ F ext/misc/memtrace.c 7c0d115d2ef716ad0ba632c91e05bd119cb16c1aedf3bec9f06196ead2 F ext/misc/memvfs.c 7dffa8cc89c7f2d73da4bd4ccea1bcbd2bd283e3bb4cea398df7c372a197291b F ext/misc/mmapwarm.c a81af4aaec00f24f308e2f4c19bf1d88f3ac3ce848c36daa7a4cd38145c4080d F ext/misc/nextchar.c 7877914c2a80c2f181dd04c3dbef550dfb54c93495dc03da2403b5dd58f34edd -F ext/misc/noop.c 81efe4cad9ec740e64388b14281cb983e6e2c223fed43eb77ab3e34946e0c1ab +F ext/misc/noop.c f1a21cc9b7a4e667e5c8458d80ba680b8bd4315a003f256006046879f679c5a0 F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d09ea61f F ext/misc/pcachetrace.c f4227ce03fb16aa8d6f321b72dd051097419d7a028a9853af048bee7645cb405 F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691 @@ -483,7 +483,7 @@ F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486 F ext/recover/sqlite3recover.c e6eb20c469bcdb96f297f2241860bccabf9f036bfa7f3d47bcc6ca1191b108dc F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 -F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade +F ext/recover/test_recover.c fd871a40f2238022bedcbdf3cb493b91225edaa94d6ae8892af97a10e7ccc4ba F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 F ext/repair/checkindex.c af5c66463f51462d8a6f796b2c44ef8cfa1116bbdc35a15da07c67a705388bfd @@ -495,7 +495,7 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c 0dd4775e896cee6067979d67aff7c998e75c2c9d9cd8d62a1a790c09cde7adca -F ext/rtree/rtree.c d0134bb75bc92b18a1dc011ec10419642f055c67af8ff44fc4a07c5fa9f189cb +F ext/rtree/rtree.c bdf97fac91b37b3a6041ab4a0dd06e81c7ba1541601733b40f97fb51e76e79ce F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test 2b5b8c719c6a4abe377f57766f428a49af36a93061cb146cccfdc3b30000c0a4 F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d @@ -515,6 +515,7 @@ F ext/rtree/rtreeF.test 81ffa7ef51c4e4618d497a57328c265bf576990c7070633b623b23cd F ext/rtree/rtreeG.test 1b9ca6e3effb48f4161edaa463ddeaa8fca4b2526d084f9cbf5dbe4e0184939c F ext/rtree/rtreeH.test 0885151ee8429242625600ae47142cca935332c70a06737f35af53a7bd7aaf90 F ext/rtree/rtreeI.test 608e77f7fde9be5a12eae316baef640fffaafcfa90a3d67443e78123e19c4ca4 +F ext/rtree/rtreeJ.test 93227ccd4d6c328f5ac46a902b8880041509dd2d68f6ce71560f0d8ab5bb507a F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195 F ext/rtree/rtree_util.tcl 202ca70df1f0645ef9d5a2170e62d378a28098d9407f0569e85c9c1cf1bd020a F ext/rtree/rtreecheck.test 934546ad9b563e090ee0c5cbdc69ad014189ad76e5df7320526797a9a345661f @@ -676,7 +677,7 @@ F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 -F src/btree.c c64df2b1623501e397128261de58d3ab44c301e4eb993a4055aa971444420200 +F src/btree.c 5410768087e524f331816c751c4b1f714693fa864a448337e443e49cc397aeaf F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 F src/btreeInt.h 3e2589726c4f105e653461814f65857465da68be1fac688de340c43b873f4062 F src/build.c e7d9044592eeeea8e78d8ae53ca8d31fd6e92ca0d4f53e2f2e8ccf7352e0b04b @@ -690,14 +691,14 @@ F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 F src/expr.c 3381ee4c9aa7ccde22a2a7f35ce343925a7a25d96bdc943649131f9decdebad2 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 -F src/func.c 472f6dcfa39cf54f89a6aec76c79c225fb880a6c14469c15d361331662b9bf43 +F src/func.c 4204c56196847faefef57fa14e43b8e4d65eb8d7e65318abe463472e3fd148cb F src/global.c 765a0656d6cbf043cb272ff0ae38f39cc46713539ffe6793258ed3eb4b188b52 F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c 31eb3e138661284bc561dd8d23b948126716847571d5b6e86044a284fce81cde +F src/insert.c eb33ea46dcab93e90f112fced343aaf41f59cbd2e951d5066f1f9302be1c2f34 +F src/json.c 29a42bc92c2384653b8b5e5ad26bdee4e2334544c7cfb78ceb4a3ca81d674686 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 F src/main.c 438b95162acfa17b7d218f586f5bde11d6ae82bcf030c9611fc537556870ad6b @@ -707,7 +708,7 @@ F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff -F src/memdb.c 559c42e61eb70cd6d4bc692b042497133c6d96c09a3d514d92f3dac72268e223 +F src/memdb.c 16679def118b5fd75292a253166d3feba3ec9c6189205bf209643ecdb2174ecc F src/memjournal.c c283c6c95d940eb9dc70f1863eef3ee40382dbd35e5a1108026e7817c206e8a0 F src/msvc.h 80b35f95d93bf996ccb3e498535255f2ef1118c78764719a7cd15ab4106ccac9 F src/mutex.c 1b4c7e5e3621b510e0c18397210be27cd54c8084141144fbbafd003fde948e88 @@ -730,24 +731,24 @@ F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 -F src/pragma.c b5b4cff830575e6188cd56a295a57448d2b9dbc53f0dae58e22b97354cda3781 +F src/pragma.c b61a1a1801befe6763e787b96988deb156a56e82a2b4dea004bc78e4551c9d5c F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c 371f6115cb69286ebc12c6f2d7511279c2e47d9f54f475d46a554d687a3b312c -F src/printf.c 18fbdf028345c8fbe6044f5f5bfda5a10d48d6287afef088cc21b0ca57985640 +F src/printf.c d3392b2a20ee314ddeef34fb43c904bf4619eb20ff9a9e07e3950a7e4dcd6912 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c e25f51a473a5f30a0d978e4df2aaa98aeec84eac29ecae1ad4708a6c3e669345 +F src/resolve.c 446bcb8ebf0ea7066c2ca99e5336f0dbc9230ac76f80fafd1bfa82fe7871af2d F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c f1a81ff4f8e9e76c224e2ab3a4baa799add0db22158c7fcede65d8cc4a6fa2da -F src/shell.c.in d1ed426aae2d547932971e8019939cacb4dfda8258e45b8924b250e488e2d53d -F src/sqlite.h.in 61a60b4ea04db8ead15e1579b20b64cb56e9f55d52c5f9f9694de630110593a3 +F src/shell.c.in f7cc8711aee604bc078a93d777ad7246980485c57fa3047408b0f842ba03c14d +F src/sqlite.h.in 020d7b7307dda51420dc48b47e5334eaface77baba6bd9818375d392eb3ab5b5 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 73800d73e21180e6b3df8d0fe7d11758dc24367fd2b0b0075b48fc116de406bb +F src/sqliteInt.h 59e83fc2b01e7c7f550a9ab77fd5876a49f37acbf4cc01c031fc7947f95959d6 F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd -F src/test1.c ac6542cddd1f405e332d869946b977b2ce8b4dc28b9f7cc61df38abe1fe49bc3 +F src/test1.c 310f43eb17a9252a7790726ca652e4ea3197da17c19eec93b8578863a49dc7b4 F src/test2.c 54520d0565ef2b9bf0f8f1dcac43dc4d06baf4ffe13d10905f8d8c3ad3e4b9ab F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 @@ -803,15 +804,15 @@ F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd F src/treeview.c c6fc972683fd00f975d8b32a81c1f25d2fb7d4035366bf45c9f5622d3ccd70ee F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 -F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 +F src/upsert.c 2e60567a0e9e8520c18671b30712a88dc73534474304af94f32bb5f3ef65ac65 F src/utf.c f23165685a67b4caf8ec08fb274cb3f319103decfb2a980b7cfd55d18dfa855e -F src/util.c 078f040366d5bd5f47658d045f901c768c1c636c6eaea121f3a1cbd63c3edb5b +F src/util.c c346e2a811285f6bad8a87c862070207855159a783993d03ca20dea2c74ace04 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 -F src/vdbe.c 96ac876e57f480bd35ec8d74ed992bca6ae9deebe8b527a3a718e7b4714d6c2e +F src/vdbe.c b2a45392265cb83f60251406039bf5255462d4a6d8deb05b2eaccab5abb2e20b F src/vdbe.h 88e19a982df9027ec1c177c793d1a5d34dc23d8f06e3b2d997f43688b05ee0eb F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c F src/vdbeapi.c 8f57d60c89da0b60e6d4e272358c511f6bae4e24330bdb11f8b42f986d1bf21b -F src/vdbeaux.c c5a471b34e9c4cfc0295a3e10734fd197670ffaebcb742f284c8e17e8026ceea +F src/vdbeaux.c 56900c9a41f23260c8346f212bd6005eb9171f9a2f70d0cfb1441a078a0e4b84 F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5 F src/vdbemem.c 0012d5f01cc866833847c2f3ae4c318ac53a1cb3d28acad9c35e688039464cf0 F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547 @@ -1215,7 +1216,7 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test 3a29323b640c0552f6e9f1577407ced3a68e7d8c0bc04b61dd6040fa593a3a02 +F test/func.test 504d202650c7940b5aa98364dd68f242df87f39f829e51074a55d79fc7bc7414 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a F test/func4.test e8ef9b2bd6a192a213cbd5cf31a3b35e25cd6ff2fdaeea0b58d63be31b03d220 @@ -1231,7 +1232,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 7e1bcc242dc4b42e43e4708c8140fe268db83a6901fbc681d150c77aa185e328 +F test/fuzzcheck.c e6a40f53ac5624aa5b7c4f31c385f09ba088d524cecc4512fd3057caeed8f530 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1335,7 +1336,7 @@ F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd286 F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 912ee03e700a65c827ee0c7b4752c21ec5ef69cf7679d2f482ca817042bead52 x F test/json/jsonb-q1.txt 1e180fe6491efab307e318b22879e3a736ac9a96539bbde7911a13ee5b33abc7 -F test/json101.test 70587d7d35ef9e2126364ba70f0c951f70827cfbd28649d779ff3df7e8f87547 +F test/json101.test 30db5b055b103ccabc53a29cfe6cda3345d07e171aeb25403dafa04f19e98b19 F test/json102.test 557a46e16df1aa9bdbc4076a71a45814ea0e7503d6621d87d42a8c04cbc2b0ef F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 @@ -1395,7 +1396,7 @@ F test/malloctraceviewer.tcl 3e3ddf11e30d2b20f53aa16aa6615082fb24a100bea61cca721 F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7 -F test/memdb1.test 2c4e9cc10d21c6bf4e217d72b7f6b8ba9b2605971bb2c5e6df76018e189f98f5 +F test/memdb1.test 2fb27d5dadd4e7784d2229e570f6368b059fc0b7fe88ca25e46e17150ba8ad4c F test/memdb2.test 4ba1fc09e2f51df80d148a540e4a3fa66d0462e91167b27497084de4d1f6b5b4 F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c F test/memjournal2.test dbc2c5cb5f7b38950f4f6dc3e73fcecf0fcbed3fc32c7ce913bba164d288da1e @@ -1438,7 +1439,7 @@ F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 F test/notify3.test 796c7b7157f55c93b4e672b724e9c923a6fc6aa72ac419379a623e2350472e22 F test/notnull.test a37b663d5bb728d66fc182016613fb8e4a0a4bbf3d75b8876a7527f7d4ed3f18 -F test/notnull2.test 1ee4acbd614d3cf5f1c4a52f5af7fc771b82352f1a51a86afeaa02c9df1d82ef +F test/notnull2.test 2ac7b4e04917148c7a1a9ed36df20150175ce942f07f5714375b29acbaca7106 F test/notnullfault.test fc4bb7845582a2b3db376001ef49118393b1b11abe0d24adb03db057ee2b73d5 F test/null.test b7ff206a1c60fe01aa2abd33ef9ea83c93727d993ca8a613de86e925c9f2bc6f F test/nulls1.test 7a5e4346ee4285034100b4cd20e6784f16a9d6c927e44ecdf10034086bbee9c9 @@ -1479,14 +1480,14 @@ F test/pendingrace.test 6aa33756b950c4529f79c4f3817a9a1e4025bd0d9961571a05c0279b F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff F test/permutations.test f7caf8dd5c7b1da74842a48df116f7f193399c656d4ffc805cd0d9658568c675 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f -F test/pragma.test 57a36226218c03cfb381019fe43234b2cefbd8a1f12825514f906a17ccf7991e +F test/pragma.test cddd4b534d7fb5cf113d1308dea4231f3548e8a7f3a65d7d1cf4810c87090b5a F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31 F test/pragma4.test ca5e4dfc46adfe490f75d73734f70349d95a199e6510973899e502eef2c8b1f8 F test/pragma5.test 7b33fc43e2e41abf17f35fb73f71b49671a380ea92a6c94b6ce530a25f8d9102 F test/pragmafault.test 275edaf3161771d37de60e5c2b412627ac94cef11739236bec12ed1258b240f8 F test/prefixes.test b524a1c44bffec225b9aec98bd728480352aa8532ac4c15771fb85e8beef65d9 -F test/printf.test 512152dca7f2f578f045a5a732e7bee08e4f47a8a212f83ce46791b518eba70f +F test/printf.test 685fec5a0c5af2490ab0632775a301554361d674211d690f5bee0a97b05333de F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224cce60 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc @@ -1586,7 +1587,7 @@ F test/shell1.test c7127a5e780ffc9e14c476773127fdf292c6db226529c44c1676f37b37931 F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a F test/shell4.test 947029e5a9efae9054d424b309fc0311439c0c3a0866ebfa3b8a771120708220 -F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b +F test/shell5.test 263bfd6a49049295277e3f5bdc221390dc5e72f39954b23d43204ed81993304f F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915 @@ -1866,7 +1867,7 @@ F test/tt3_stress.c f9a769ca8b026ecc76ee93ca8c9700a5619f8e51c581107c4053ba6ac97f F test/tt3_vacuum.c 71b254cde1fc49d6c8c44efd54f4668f3e57d7b3a8f4601ade069f75a999ba39 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac -F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a +F test/types3.test c9db8f9e80309edfa4252585cf16bcab7ed31f39eeb904d21e831199a3613fb0 F test/unhex.test b7f1b806207cb77fa31c3e434fe92fba524464e3e9356809bfcc28f15af1a8b7 F test/unionall.test 5b1c4186a661e4bf762875caf4c61d8fda3dd04a6fa9005187f6ba8900c2913f F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1 @@ -1889,7 +1890,7 @@ F test/upsert1.test a512e2f884d3a36159fce2e45108c236f78ae38e35bda55f4050db580ceb F test/upsert2.test 720e94d09f7362a282bc69b3c6b83d51daeaaf0440eb4920a08b86518b8c7496 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 -F test/upsert5.test fff0dcfce73c649204543088d8e5bde01172676063ec9b8f8fc7f195abc386fe +F test/upsert5.test 9953b180d02d1369cdbb6c73c900834e5fef8cb78e98e07511c8762ec21cc176 F test/upsertfault.test f21ca47740841fdb4d61acfa7b17646d773e67724fe8c185b71c018db8a94b35 F test/uri.test c1abaaaa28e9422d61e5f3f9cbc8ef993ec49fe802f581520731708561d49384 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 @@ -2159,10 +2160,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ab40e282465c989bf249453d7c6f60072a38b691f579411cdf9aad234b20f0f7 -R 733155be1814ed819345d16353d7d6cc +P b26f24441f84a30deb9a562ab6c6de7543fbc3b3b93c34277964c9c21d734153 +R 155ffa116058b8f4537c03201d4943ec T +sym-release * -T +sym-vesion-3.45.1 * +T +sym-version-3.45.2 * U drh -Z 27c9e9ada796013bdfd50fea314c088d +Z ed4a58f9c6cbbc2b817c3b07c0b5f709 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 9f213aa72..c69bc8e96 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a +d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77 diff --git a/src/btree.c b/src/btree.c index c41fb811a..0fdc680f4 100644 --- a/src/btree.c +++ b/src/btree.c @@ -6956,7 +6956,10 @@ static int fillInCell( n = nHeader + nPayload; testcase( n==3 ); testcase( n==4 ); - if( n<4 ) n = 4; + if( n<4 ){ + n = 4; + pPayload[nPayload] = 0; + } *pnSize = n; assert( nSrc<=nPayload ); testcase( nSrcpBt->nPreformatSize; - if( szNew<4 ) szNew = 4; + if( szNew<4 ){ + szNew = 4; + newCell[3] = 0; + } if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){ CellInfo info; pPage->xParseCell(pPage, newCell, &info); diff --git a/src/func.c b/src/func.c index 58ef4fef9..9fbd1e9e1 100644 --- a/src/func.c +++ b/src/func.c @@ -1101,13 +1101,13 @@ void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){ double r1, r2; const char *zVal; r1 = sqlite3_value_double(pValue); - sqlite3_str_appendf(pStr, "%!.15g", r1); + sqlite3_str_appendf(pStr, "%!0.15g", r1); zVal = sqlite3_str_value(pStr); if( zVal ){ sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8); if( r1!=r2 ){ sqlite3_str_reset(pStr); - sqlite3_str_appendf(pStr, "%!.20e", r1); + sqlite3_str_appendf(pStr, "%!0.20e", r1); } } break; @@ -1409,7 +1409,7 @@ static void replaceFunc( } if( zPattern[0]==0 ){ assert( sqlite3_value_type(argv[1])!=SQLITE_NULL ); - sqlite3_result_value(context, argv[0]); + sqlite3_result_text(context, (const char*)zStr, nStr, SQLITE_TRANSIENT); return; } nPattern = sqlite3_value_bytes(argv[1]); diff --git a/src/insert.c b/src/insert.c index 1c31ca233..095298b90 100644 --- a/src/insert.c +++ b/src/insert.c @@ -1086,7 +1086,7 @@ void sqlite3Insert( pNx->iDataCur = iDataCur; pNx->iIdxCur = iIdxCur; if( pNx->pUpsertTarget ){ - if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){ + if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx, pUpsert) ){ goto insert_cleanup; } } diff --git a/src/json.c b/src/json.c index 70cc4b7bc..5bfa869f6 100644 --- a/src/json.c +++ b/src/json.c @@ -1607,6 +1607,7 @@ static int jsonTranslateTextToBlob(JsonParse *pParse, u32 i){ case '[': { /* Parse array */ iThis = pParse->nBlob; + assert( i<=(u32)pParse->nJson ); jsonBlobAppendNode(pParse, JSONB_ARRAY, pParse->nJson - i, 0); iStart = pParse->nBlob; if( pParse->oom ) return -1; @@ -2005,6 +2006,10 @@ static void jsonReturnStringAsBlob(JsonString *pStr){ JsonParse px; memset(&px, 0, sizeof(px)); jsonStringTerminate(pStr); + if( pStr->eErr ){ + sqlite3_result_error_nomem(pStr->pCtx); + return; + } px.zJson = pStr->zBuf; px.nJson = pStr->nUsed; px.db = sqlite3_context_db_handle(pStr->pCtx); @@ -3330,8 +3335,9 @@ static JsonParse *jsonParseFuncArg( } p->zJson = (char*)sqlite3_value_text(pArg); p->nJson = sqlite3_value_bytes(pArg); + if( db->mallocFailed ) goto json_pfa_oom; if( p->nJson==0 ) goto json_pfa_malformed; - if( NEVER(p->zJson==0) ) goto json_pfa_oom; + assert( p->zJson!=0 ); if( jsonConvertTextToBlob(p, (flgs & JSON_KEEPERROR) ? 0 : ctx) ){ if( flgs & JSON_KEEPERROR ){ p->nErr = 1; @@ -3497,10 +3503,10 @@ static void jsonDebugPrintBlob( if( sz==0 && x<=JSONB_FALSE ){ sqlite3_str_append(pOut, "\n", 1); }else{ - u32 i; + u32 j; sqlite3_str_appendall(pOut, ": \""); - for(i=iStart+n; iaBlob[i]; + for(j=iStart+n; jaBlob[j]; if( c<0x20 || c>=0x7f ) c = '.'; sqlite3_str_append(pOut, (char*)&c, 1); } @@ -4908,6 +4914,9 @@ static int jsonEachColumn( case JEACH_VALUE: { u32 i = jsonSkipLabel(p); jsonReturnFromBlob(&p->sParse, i, ctx, 1); + if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){ + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } break; } case JEACH_TYPE: { @@ -4954,9 +4963,9 @@ static int jsonEachColumn( case JEACH_JSON: { if( p->sParse.zJson==0 ){ sqlite3_result_blob(ctx, p->sParse.aBlob, p->sParse.nBlob, - SQLITE_STATIC); + SQLITE_TRANSIENT); }else{ - sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC); + sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_TRANSIENT); } break; } diff --git a/src/memdb.c b/src/memdb.c index 657cb9ca6..d83a51d54 100644 --- a/src/memdb.c +++ b/src/memdb.c @@ -799,6 +799,14 @@ unsigned char *sqlite3_serialize( pOut = 0; }else{ sz = sqlite3_column_int64(pStmt, 0)*szPage; + if( sz==0 ){ + sqlite3_reset(pStmt); + sqlite3_exec(db, "BEGIN IMMEDIATE; COMMIT;", 0, 0, 0); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + sz = sqlite3_column_int64(pStmt, 0)*szPage; + } + } if( piSize ) *piSize = sz; if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ pOut = 0; diff --git a/src/pragma.c b/src/pragma.c index 4c9057418..a1243acc1 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1762,31 +1762,7 @@ void sqlite3Pragma( int mxCol; /* Maximum non-virtual column number */ if( pObjTab && pObjTab!=pTab ) continue; - if( !IsOrdinaryTable(pTab) ){ -#ifndef SQLITE_OMIT_VIRTUALTABLE - sqlite3_vtab *pVTab; - int a1; - if( !IsVirtual(pTab) ) continue; - if( pTab->nCol<=0 ){ - const char *zMod = pTab->u.vtab.azArg[0]; - if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; - } - sqlite3ViewGetColumnNames(pParse, pTab); - if( pTab->u.vtab.p==0 ) continue; - pVTab = pTab->u.vtab.p->pVtab; - if( NEVER(pVTab==0) ) continue; - if( NEVER(pVTab->pModule==0) ) continue; - if( pVTab->pModule->iVersion<4 ) continue; - if( pVTab->pModule->xIntegrity==0 ) continue; - sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick); - pTab->nTabRef++; - sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF); - a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); - integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, a1); -#endif - continue; - } + if( !IsOrdinaryTable(pTab) ) continue; if( isQuick || HasRowid(pTab) ){ pPk = 0; r2 = 0; @@ -1921,6 +1897,7 @@ void sqlite3Pragma( ** is REAL, we have to load the actual data using OP_Column ** to reliably determine if the value is a NULL. */ sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3); + sqlite3ColumnDefault(v, pTab, j, 3); jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk); VdbeCoverage(v); } @@ -2111,6 +2088,38 @@ void sqlite3Pragma( } } } + +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Second pass to invoke the xIntegrity method on all virtual + ** tables. + */ + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + sqlite3_vtab *pVTab; + int a1; + if( pObjTab && pObjTab!=pTab ) continue; + if( IsOrdinaryTable(pTab) ) continue; + if( !IsVirtual(pTab) ) continue; + if( pTab->nCol<=0 ){ + const char *zMod = pTab->u.vtab.azArg[0]; + if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; + } + sqlite3ViewGetColumnNames(pParse, pTab); + if( pTab->u.vtab.p==0 ) continue; + pVTab = pTab->u.vtab.p->pVtab; + if( NEVER(pVTab==0) ) continue; + if( NEVER(pVTab->pModule==0) ) continue; + if( pVTab->pModule->iVersion<4 ) continue; + if( pVTab->pModule->xIntegrity==0 ) continue; + sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick); + pTab->nTabRef++; + sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF); + a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); + continue; + } +#endif } { static const int iLn = VDBE_OFFSET_LINENO(2); diff --git a/src/printf.c b/src/printf.c index c6b3803ca..2e09431bf 100644 --- a/src/printf.c +++ b/src/printf.c @@ -498,6 +498,7 @@ void sqlite3_str_vappendf( if( xtype==etFLOAT ){ iRound = -precision; }else if( xtype==etGENERIC ){ + if( precision==0 ) precision = 1; iRound = precision; }else{ iRound = precision+1; diff --git a/src/resolve.c b/src/resolve.c index b4f03fe7e..24e47789b 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -79,6 +79,8 @@ static void resolveAlias( assert( iCol>=0 && iColnExpr ); pOrig = pEList->a[iCol].pExpr; assert( pOrig!=0 ); + assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ); + if( pExpr->pAggInfo ) return; db = pParse->db; pDup = sqlite3ExprDup(db, pOrig, 0); if( db->mallocFailed ){ @@ -964,6 +966,19 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ** resolved. This prevents "column" from being counted as having been ** referenced, which might prevent a SELECT from being erroneously ** marked as correlated. + ** + ** 2024-03-28: Beware of aggregates. A bare column of aggregated table + ** can still evaluate to NULL even though it is marked as NOT NULL. + ** Example: + ** + ** CREATE TABLE t1(a INT NOT NULL); + ** SELECT a, a IS NULL, a IS NOT NULL, count(*) FROM t1; + ** + ** The "a IS NULL" and "a IS NOT NULL" expressions cannot be optimized + ** here because at the time this case is hit, we do not yet know whether + ** or not t1 is being aggregated. We have to assume the worst and omit + ** the optimization. The only time it is safe to apply this optimization + ** is within the WHERE clause. */ case TK_NOTNULL: case TK_ISNULL: { @@ -974,19 +989,36 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ anRef[i] = p->nRef; } sqlite3WalkExpr(pWalker, pExpr->pLeft); - if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ - testcase( ExprHasProperty(pExpr, EP_OuterON) ); - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - pExpr->u.iValue = (pExpr->op==TK_NOTNULL); - pExpr->flags |= EP_IntValue; - pExpr->op = TK_INTEGER; + if( IN_RENAME_OBJECT ) return WRC_Prune; + if( sqlite3ExprCanBeNull(pExpr->pLeft) ){ + /* The expression can be NULL. So the optimization does not apply */ + return WRC_Prune; + } - for(i=0, p=pNC; p && ipNext, i++){ - p->nRef = anRef[i]; + for(i=0, p=pNC; p; p=p->pNext, i++){ + if( (p->ncFlags & NC_Where)==0 ){ + return WRC_Prune; /* Not in a WHERE clause. Unsafe to optimize. */ } - sqlite3ExprDelete(pParse->db, pExpr->pLeft); - pExpr->pLeft = 0; } + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x80000 ){ + sqlite3DebugPrintf( + "NOT NULL strength reduction converts the following to %d:\n", + pExpr->op==TK_NOTNULL + ); + sqlite3ShowExpr(pExpr); + } +#endif /* TREETRACE_ENABLED */ + pExpr->u.iValue = (pExpr->op==TK_NOTNULL); + pExpr->flags |= EP_IntValue; + pExpr->op = TK_INTEGER; + for(i=0, p=pNC; p && ipNext, i++){ + p->nRef = anRef[i]; + } + sqlite3ExprDelete(pParse->db, pExpr->pLeft); + pExpr->pLeft = 0; return WRC_Prune; } @@ -1886,7 +1918,9 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; } + sNC.ncFlags |= NC_Where; if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; + sNC.ncFlags &= ~NC_Where; /* Resolve names in table-valued-function arguments */ for(i=0; ipSrc->nSrc; i++){ diff --git a/src/shell.c.in b/src/shell.c.in index da3b9f870..8ee0dd2e7 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -3764,6 +3764,7 @@ static void exec_prepared_stmt_columnar( rc = sqlite3_step(pStmt); if( rc!=SQLITE_ROW ) return; nColumn = sqlite3_column_count(pStmt); + if( nColumn==0 ) goto columnar_end; nAlloc = nColumn*4; if( nAlloc<=0 ) nAlloc = 1; azData = sqlite3_malloc64( nAlloc*sizeof(char*) ); @@ -3849,7 +3850,6 @@ static void exec_prepared_stmt_columnar( if( n>p->actualWidth[j] ) p->actualWidth[j] = n; } if( seenInterrupt ) goto columnar_end; - if( nColumn==0 ) goto columnar_end; switch( p->cMode ){ case MODE_Column: { colSep = " "; @@ -8698,16 +8698,15 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_SHELL_FIDDLE if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ char *zTable = 0; /* Insert data into this table */ - char *zSchema = 0; /* within this schema (may default to "main") */ + char *zSchema = 0; /* Schema of zTable */ char *zFile = 0; /* Name of file to extra content from */ sqlite3_stmt *pStmt = NULL; /* A statement */ int nCol; /* Number of columns in the table */ - int nByte; /* Number of bytes in an SQL string */ + i64 nByte; /* Number of bytes in an SQL string */ int i, j; /* Loop counters */ int needCommit; /* True to COMMIT or ROLLBACK at end */ int nSep; /* Number of bytes in p->colSeparator[] */ - char *zSql; /* An SQL statement */ - char *zFullTabName; /* Table name with schema if applicable */ + char *zSql = 0; /* An SQL statement */ ImportCtx sCtx; /* Reader context */ char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ int eVerbose = 0; /* Larger for more console output */ @@ -8841,24 +8840,14 @@ static int do_meta_command(char *zLine, ShellState *p){ while( (nSkip--)>0 ){ while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} } - if( zSchema!=0 ){ - zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable); - }else{ - zFullTabName = sqlite3_mprintf("\"%w\"", zTable); - } - zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName); - if( zSql==0 || zFullTabName==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - nByte = strlen30(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ - if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ + if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){ + /* Table does not exist. Create it. */ sqlite3 *dbCols = 0; char *zRenames = 0; char *zColDefs; - zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName); + zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", + zSchema ? zSchema : "main", zTable); while( xRead(&sCtx) ){ zAutoColumn(sCtx.z, &dbCols, 0); if( sCtx.cTerm!=sCtx.cColSep ) break; @@ -8873,34 +8862,50 @@ static int do_meta_command(char *zLine, ShellState *p){ assert(dbCols==0); if( zColDefs==0 ){ eputf("%s: empty file\n", sCtx.zFile); - import_fail: - sqlite3_free(zCreate); - sqlite3_free(zSql); - sqlite3_free(zFullTabName); import_cleanup(&sCtx); rc = 1; goto meta_command_exit; } zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); + if( zCreate==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } if( eVerbose>=1 ){ oputf("%s\n", zCreate); } rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); + sqlite3_free(zCreate); + zCreate = 0; if( rc ){ eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); - goto import_fail; + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; } - sqlite3_free(zCreate); - zCreate = 0; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); } + zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", + zTable, zSchema); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + nByte = strlen(zSql); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; if( rc ){ if (pStmt) sqlite3_finalize(pStmt); eputf("Error: %s\n", sqlite3_errmsg(p->db)); - goto import_fail; + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; + } + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + nCol = sqlite3_column_int(pStmt, 0); + }else{ + nCol = 0; } - sqlite3_free(zSql); - nCol = sqlite3_column_count(pStmt); sqlite3_finalize(pStmt); pStmt = 0; if( nCol==0 ) return 0; /* no columns, no error */ @@ -8909,7 +8914,12 @@ static int do_meta_command(char *zLine, ShellState *p){ import_cleanup(&sCtx); shell_out_of_memory(); } - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName); + if( zSchema ){ + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", + zSchema, zTable); + }else{ + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); + } j = strlen30(zSql); for(i=1; idb, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; if( rc ){ eputf("Error: %s\n", sqlite3_errmsg(p->db)); if (pStmt) sqlite3_finalize(pStmt); - goto import_fail; + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; } - sqlite3_free(zSql); - sqlite3_free(zFullTabName); needCommit = sqlite3_get_autocommit(p->db); if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); do{ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 4a19fe918..f21cffd51 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -420,6 +420,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. **
  • The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. +**
  • The application must not dereference the arrays or string pointers +** passed as the 3rd and 4th callback parameters after it returns. ** */ int sqlite3_exec( diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 79a36e060..49cff8fc1 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1123,6 +1123,7 @@ extern u32 sqlite3TreeTrace; ** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing ** 0x00020000 Transform DISTINCT into GROUP BY ** 0x00040000 SELECT tree dump after all code has been generated +** 0x00080000 NOT NULL strength reduction */ /* @@ -3448,6 +3449,7 @@ struct NameContext { #define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */ #define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */ #define NC_NoSelect 0x080000 /* Do not descend into sub-selects */ +#define NC_Where 0x100000 /* Processing WHERE clause of a SELECT */ #define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */ /* @@ -3471,6 +3473,7 @@ struct Upsert { Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */ Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */ u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */ + u8 isDup; /* True if 2nd or later with same pUpsertIdx */ /* Above this point is the parse tree for the ON CONFLICT clauses. ** The next group of fields stores intermediate data. */ void *pToFree; /* Free memory when deleting the Upsert object */ @@ -5546,7 +5549,7 @@ const char *sqlite3JournalModename(int); Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); void sqlite3UpsertDelete(sqlite3*,Upsert*); Upsert *sqlite3UpsertDup(sqlite3*,Upsert*); - int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*); + int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*,Upsert*); void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int); Upsert *sqlite3UpsertOfIndex(Upsert*,Index*); int sqlite3UpsertNextIsIPK(Upsert*); diff --git a/src/test1.c b/src/test1.c index 9c28259b4..8faf5a397 100644 --- a/src/test1.c +++ b/src/test1.c @@ -991,6 +991,39 @@ static void intrealFunction( sqlite3_test_control(SQLITE_TESTCTRL_RESULT_INTREAL, context); } +/* +** These SQL functions attempt to return a value (their first argument) +** that has been modified to have multiple datatypes. For example both +** TEXT and INTEGER. +*/ +static void addTextTypeFunction( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + (void)sqlite3_value_text(argv[0]); + (void)argc; + sqlite3_result_value(context, argv[0]); +} +static void addIntTypeFunction( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + (void)sqlite3_value_int64(argv[0]); + (void)argc; + sqlite3_result_value(context, argv[0]); +} +static void addRealTypeFunction( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + (void)sqlite3_value_double(argv[0]); + (void)argc; + sqlite3_result_value(context, argv[0]); +} + /* ** SQL function: strtod(X) ** @@ -1103,6 +1136,22 @@ static int SQLITE_TCLAPI test_create_function( 0, intrealFunction, 0, 0); } + /* The add_text_type(), add_int_type(), and add_real_type() functions + ** attempt to return a value that has multiple datatypes. + */ + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "add_text_type", 1, SQLITE_UTF8, + 0, addTextTypeFunction, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "add_int_type", 1, SQLITE_UTF8, + 0, addIntTypeFunction, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "add_real_type", 1, SQLITE_UTF8, + 0, addRealTypeFunction, 0, 0); + } + /* Functions strtod() and dtostr() work as in the shell. These routines ** use the standard C library to convert between floating point and ** text. This is used to compare SQLite's internal conversion routines diff --git a/src/upsert.c b/src/upsert.c index be0d0550d..f74d4fabf 100644 --- a/src/upsert.c +++ b/src/upsert.c @@ -90,7 +90,8 @@ Upsert *sqlite3UpsertNew( int sqlite3UpsertAnalyzeTarget( Parse *pParse, /* The parsing context */ SrcList *pTabList, /* Table into which we are inserting */ - Upsert *pUpsert /* The ON CONFLICT clauses */ + Upsert *pUpsert, /* The ON CONFLICT clauses */ + Upsert *pAll /* Complete list of all ON CONFLICT clauses */ ){ Table *pTab; /* That table into which we are inserting */ int rc; /* Result code */ @@ -193,6 +194,14 @@ int sqlite3UpsertAnalyzeTarget( continue; } pUpsert->pUpsertIdx = pIdx; + if( sqlite3UpsertOfIndex(pAll,pIdx)!=pUpsert ){ + /* Really this should be an error. The isDup ON CONFLICT clause will + ** never fire. But this problem was not discovered until three years + ** after multi-CONFLICT upsert was added, and so we silently ignore + ** the problem to prevent breaking applications that might actually + ** have redundant ON CONFLICT clauses. */ + pUpsert->isDup = 1; + } break; } if( pUpsert->pUpsertIdx==0 ){ @@ -219,9 +228,13 @@ int sqlite3UpsertNextIsIPK(Upsert *pUpsert){ Upsert *pNext; if( NEVER(pUpsert==0) ) return 0; pNext = pUpsert->pNextUpsert; - if( pNext==0 ) return 1; - if( pNext->pUpsertTarget==0 ) return 1; - if( pNext->pUpsertIdx==0 ) return 1; + while( 1 /*exit-by-return*/ ){ + if( pNext==0 ) return 1; + if( pNext->pUpsertTarget==0 ) return 1; + if( pNext->pUpsertIdx==0 ) return 1; + if( !pNext->isDup ) return 0; + pNext = pNext->pNextUpsert; + } return 0; } diff --git a/src/util.c b/src/util.c index 207b901ba..dd4990264 100644 --- a/src/util.c +++ b/src/util.c @@ -627,6 +627,9 @@ int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ u64 s2; rr[0] = (double)s; s2 = (u64)rr[0]; +#if defined(_MSC_VER) && _MSC_VER<1700 + if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); } +#endif rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); if( e>0 ){ while( e>=100 ){ @@ -1069,7 +1072,7 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ assert( p->n>0 ); assert( p->nzBuf) ); p->iDP = p->n + exp; - if( iRound<0 ){ + if( iRound<=0 ){ iRound = p->iDP - iRound; if( iRound==0 && p->zBuf[i+1]>='5' ){ iRound = 1; diff --git a/src/vdbe.c b/src/vdbe.c index 6d45bbbbb..23f848944 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -2301,7 +2301,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ } } }else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){ - if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ + if( (flags1 & MEM_Str)!=0 ){ + pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); + }else if( (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn1->flags & MEM_Int ); testcase( pIn1->flags & MEM_Real ); testcase( pIn1->flags & MEM_IntReal ); @@ -2310,7 +2312,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; } - if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ + if( (flags3 & MEM_Str)!=0 ){ + pIn3->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); + }else if( (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn3->flags & MEM_Int ); testcase( pIn3->flags & MEM_Real ); testcase( pIn3->flags & MEM_IntReal ); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 420365e93..fe0dbd6b0 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -4060,6 +4060,23 @@ static void serialGet( pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real; } } +static int serialGet7( + const unsigned char *buf, /* Buffer to deserialize from */ + Mem *pMem /* Memory cell to write value into */ +){ + u64 x = FOUR_BYTE_UINT(buf); + u32 y = FOUR_BYTE_UINT(buf+4); + x = (x<<32) + y; + assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 ); + swapMixedEndianFloat(x); + memcpy(&pMem->u.r, &x, sizeof(x)); + if( IsNaN(x) ){ + pMem->flags = MEM_Null; + return 1; + } + pMem->flags = MEM_Real; + return 0; +} void sqlite3VdbeSerialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ @@ -4739,7 +4756,7 @@ int sqlite3VdbeRecordCompareWithSkip( }else if( serial_type==0 ){ rc = -1; }else if( serial_type==7 ){ - sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); + serialGet7(&aKey1[d1], &mem1); rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r); }else{ i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]); @@ -4764,14 +4781,18 @@ int sqlite3VdbeRecordCompareWithSkip( }else if( serial_type==0 ){ rc = -1; }else{ - sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); if( serial_type==7 ){ - if( mem1.u.ru.r ){ + if( serialGet7(&aKey1[d1], &mem1) ){ + rc = -1; /* mem1 is a NaN */ + }else if( mem1.u.ru.r ){ rc = -1; }else if( mem1.u.r>pRhs->u.r ){ rc = +1; + }else{ + assert( rc==0 ); } }else{ + sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r); } } @@ -4841,7 +4862,14 @@ int sqlite3VdbeRecordCompareWithSkip( /* RHS is null */ else{ serial_type = aKey1[idx1]; - rc = (serial_type!=0 && serial_type!=10); + if( serial_type==0 + || serial_type==10 + || (serial_type==7 && serialGet7(&aKey1[d1], &mem1)!=0) + ){ + assert( rc==0 ); + }else{ + rc = 1; + } } if( rc!=0 ){ diff --git a/test/func.test b/test/func.test index 883950a0c..c7b8f7235 100644 --- a/test/func.test +++ b/test/func.test @@ -786,6 +786,11 @@ do_test func-16.1 { } } {X'616263' NULL} +# Test the quote function for +Inf and -Inf +do_execsql_test func-16.2 { + SELECT quote(4.2e+859), quote(-7.8e+904); +} {9.0e+999 -9.0e+999} + # Correctly handle function error messages that include %. Ticket #1354 # do_test func-17.1 { @@ -1042,6 +1047,9 @@ do_test func-21.8 { SELECT replace('aaaaaaa', 'a', '0123456789'); } } {0123456789012345678901234567890123456789012345678901234567890123456789} +do_execsql_test func-21.9 { + SELECT typeof(replace(1,'',0)); +} {text} ifcapable tclvar { do_test func-21.9 { diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index dd4912011..e4ad1c113 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -161,8 +161,8 @@ static struct GlobalVars { /* ** Include the external vt02.c and randomjson.c modules. */ -extern int sqlite3_vt02_init(sqlite3*,char***,void*); -extern int sqlite3_randomjson_init(sqlite3*,char***,void*); +extern int sqlite3_vt02_init(sqlite3*,char**,const sqlite3_api_routines*); +extern int sqlite3_randomjson_init(sqlite3*,char**,const sqlite3_api_routines*); /* diff --git a/test/json101.test b/test/json101.test index bae68e734..3963ffbb6 100644 --- a/test/json101.test +++ b/test/json101.test @@ -378,6 +378,20 @@ do_execsql_test json101-5.8 { WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); } {} +# 2024-02-16 https://sqlite.org/forum/forumpost/ecb94cd210 +# Regression in json_tree()/json_each(). The value column +# should have the "J" subtype if the value is an array or +# object. +# +do_execsql_test json101-5.10 { + SELECT json_insert('{}','$.a',value) FROM json_tree('[1,2,3]') WHERE atom IS NULL; +} {{{"a":[1,2,3]}}} +# ^^^^^^^--- In double-quotes, a string literal, prior to bug fix + +do_execsql_test json101-5.11 { + SELECT json_insert('{}','$.a',value) FROM json_tree('"[1,2,3]"'); +} {{{"a":"[1,2,3]"}}} + do_execsql_test json101-6.1 { SELECT json_valid('{"a":55,"b":72,}'); } {0} diff --git a/test/memdb1.test b/test/memdb1.test index 5e219a4c0..3a31c8e28 100644 --- a/test/memdb1.test +++ b/test/memdb1.test @@ -84,7 +84,6 @@ do_test 152 { catchsql {INSERT INTO t1 VALUES(3,4);} } {1 {attempt to write a readonly database}} -breakpoint do_test 160 { db deserialize -maxsize 32768 $db1 db eval {SELECT * FROM t1} @@ -248,6 +247,7 @@ if {[wal_is_capable]} { set fd [open test.db] fconfigure $fd -translation binary -encoding binary set data [read $fd [expr 20*1024]] + close $fd sqlite3 db "" db deserialize $data @@ -267,4 +267,17 @@ if {[wal_is_capable]} { } {1 {database disk image is malformed}} } +# 2024-01-20 +# https://sqlite.org/forum/forumpost/498777780e16880a +# +# Make sure a database is initialized before serializing it. +# +reset_db +sqlite3 dbempty :memory: +do_test 900 { + set len [string length [dbempty serialize]] + expr {$len>0} +} 1 +dbempty close + finish_test diff --git a/test/notnull2.test b/test/notnull2.test index 7f6808681..09161efbd 100644 --- a/test/notnull2.test +++ b/test/notnull2.test @@ -59,14 +59,14 @@ do_vmstep_test 1.4.2 { do_vmstep_test 1.5.1 { SELECT count(*) FROM t2 WHERE EXISTS( - SELECT t2.d IS NULL FROM t1 WHERE t1.a=450 + SELECT 1 FROM t1 WHERE t1.a=450 AND t2.d IS NULL ) -} 10000 {1000} +} 7000 {0} do_vmstep_test 1.5.2 { SELECT count(*) FROM t2 WHERE EXISTS( - SELECT t2.c IS NULL FROM t1 WHERE t1.a=450 + SELECT 1 FROM t1 WHERE t1.a=450 AND t2.c IS NULL ) -} +100000 {1000} +} +8000 {0} #------------------------------------------------------------------------- reset_db @@ -111,4 +111,12 @@ do_execsql_test 4.1 { SELECT * FROM (SELECT 3 AS c FROM t1) AS t3 LEFT JOIN t2 ON c IS NULL; } {3 {}} +# 2024-03-08 https://sqlite.org/forum/forumpost/440f2a2f17 +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(a INT NOT NULL); + SELECT a IS NULL, a IS NOT NULL, count(*) FROM t1; +} {1 0 0} + finish_test diff --git a/test/pragma.test b/test/pragma.test index 5b45a7440..8f78a7e02 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -556,6 +556,21 @@ ifcapable altertable { do_execsql_test pragma-3.23 { PRAGMA integrity_check(1); } {{non-unique entry in index t1a}} + + # forum post https://sqlite.org/forum/forumpost/ee4f6fa5ab + do_execsql_test pragma-3.24 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a); + INSERT INTO t1 VALUES (1); + ALTER TABLE t1 ADD COLUMN b NOT NULL DEFAULT 0.25; + SELECT * FROM t1; + PRAGMA integrity_check(t1); + } {1 0.25 ok} + do_execsql_test pragma-3.25 { + ALTER TABLE t1 ADD COLUMN c CHECK (1); + SELECT * FROM t1; + PRAGMA integrity_check(t1); + } {1 0.25 {} ok} } # PRAGMA integrity check (or more specifically the sqlite3BtreeCount() diff --git a/test/printf.test b/test/printf.test index 6d4ad71d2..cc439e617 100644 --- a/test/printf.test +++ b/test/printf.test @@ -3833,4 +3833,21 @@ do_execsql_test printf-18.1 { SELECT length( format('%,.249f', -5.0e-300) ); } {252} +# 2024-02-16 +# https://sqlite.org/forum/info/393708f4a8 +# +# The problem introduced by on 2023-07-03 by +# https://sqlite.org/src/info/32befb224b254639 +# +do_execsql_test printf-19.1 { + SELECT format('%0.0f %0.0g %0.0g', 0.9, 0.09, 1.9); +} {{1 0.09 2}} +do_execsql_test printf-19.2 { + SELECT format('%0.0f %#0.0f',0.0, 0.0); +} {{0 0.}} +do_execsql_test printf-19.3 { + SELECT format('%,.0f %,.0f',12345e+10, 12345e+11); +} {{123,450,000,000,000 1,234,500,000,000,000}} + + finish_test diff --git a/test/shell5.test b/test/shell5.test index 39018a0ce..20f2ba219 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -570,4 +570,18 @@ SELECT * FROM t1;} } {0 { 1 = あい 2 = うえお}} +# 2024-03-11 https://sqlite.org/forum/forumpost/ca014d7358 +# Import into a table that contains computed columns. +# +do_test shell5-7.1 { + set out [open shell5.csv w] + fconfigure $out -translation lf + puts $out {aaa|bbb} + close $out + forcedelete test.db + catchcmd :memory: {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b)); +.import shell5.csv t1 +SELECT * FROM t1;} +} {0 aaa|bbb|aaabbb} + finish_test diff --git a/test/types3.test b/test/types3.test index 807ae84f9..0ff346ce2 100644 --- a/test/types3.test +++ b/test/types3.test @@ -12,8 +12,6 @@ # of this file is testing the interaction of SQLite manifest types # with Tcl dual-representations. # -# $Id: types3.test,v 1.8 2008/04/28 13:02:58 drh Exp $ -# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -96,4 +94,32 @@ do_test types3-2.6 { tcl_variable_type V } {} +# See https://sqlite.org/forum/forumpost/3776b48e71 +# +# On a text-affinity comparison of two values where one of +# the values has both MEM_Str and a numeric type like MEM_Int, +# make sure that only the MEM_Str representation is used. +# +sqlite3_create_function db +do_execsql_test types3-3.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x TEXT PRIMARY KEY); + INSERT INTO t1 VALUES('1'); + SELECT * FROM t1 WHERE NOT x=upper(1); +} {} +do_execsql_test types3-3.2 { + SELECT * FROM t1 WHERE NOT x=add_text_type(1); +} {} +do_execsql_test types3-3.3 { + SELECT * FROM t1 WHERE NOT x=add_int_type('1'); +} {} +do_execsql_test types3-3.4 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1.25); + SELECT * FROM t1 WHERE NOT x=add_real_type('1.25'); +} {} +do_execsql_test types3-3.5 { + SELECT * FROM t1 WHERE NOT x=add_text_type(1.25); +} {} + finish_test diff --git a/test/upsert5.test b/test/upsert5.test index 3161abf15..e56e71d4b 100644 --- a/test/upsert5.test +++ b/test/upsert5.test @@ -408,4 +408,46 @@ do_catchsql_test 2.1 { } {1 {no such table: nosuchtable}} +# 2024-03-08 https://sqlite.org/forum/forumpost/919c6579c8 +# A redundant ON CONFLICT clause in an upsert can lead to +# index corruption. +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(aa INTEGER PRIMARY KEY, bb INT); + INSERT INTO t1 VALUES(11,22); + CREATE UNIQUE INDEX t1bb ON t1(bb); + REPLACE INTO t1 VALUES(11,33) + ON CONFLICT(bb) DO UPDATE SET aa = 44 + ON CONFLICT(bb) DO UPDATE SET aa = 44; + PRAGMA integrity_check; +} {ok} +do_execsql_test 3.1 { + SELECT * FROM t1 NOT INDEXED; +} {11 33} +do_execsql_test 3.2 { + SELECT * FROM t1 INDEXED BY t1bb; +} {11 33} +do_execsql_test 3.3 { + DROP TABLE t1; + CREATE TABLE t1(aa INTEGER PRIMARY KEY, bb INT, cc INT); + INSERT INTO t1 VALUES(10,21,32),(11,22,33),(12,23,34); + CREATE UNIQUE INDEX t1bb ON t1(bb); + CREATE UNIQUE INDEX t1cc ON t1(cc); + REPLACE INTO t1 VALUES(11,44,55) + ON CONFLICT(bb) DO UPDATE SET aa = 99 + ON CONFLICT(cc) DO UPDATE SET aa = 99 + ON CONFLICT(bb) DO UPDATE SET aa = 99; + PRAGMA integrity_check; +} {ok} +do_execsql_test 3.4 { + SELECT * FROM t1 NOT INDEXED ORDER BY +aa; +} {10 21 32 11 44 55 12 23 34} +do_execsql_test 3.5 { + SELECT * FROM t1 INDEXED BY t1bb ORDER BY +aa; +} {10 21 32 11 44 55 12 23 34} +do_execsql_test 3.6 { + SELECT * FROM t1 INDEXED BY t1cc ORDER BY +aa; +} {10 21 32 11 44 55 12 23 34} + finish_test From 86ae0bde241eeb838f8444020ef02eb654916368 Mon Sep 17 00:00:00 2001 From: Micah Moore Date: Mon, 18 Mar 2024 16:11:00 -0400 Subject: [PATCH 129/340] Adds _SQLITE3_H_=1 _FTS5_H=1 _SQLITE3RTREE_H_=1 to podspec GCC_PREPROCESSOR_DEFINITIONS to avoid redefinition conflicts when consuming project uses Swift and import SQLite3 module. --- SQLCipher.podspec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 3157bd1b3..fde4895c6 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -51,7 +51,7 @@ "source_files": "sqlite3.{h,c}", "xcconfig": { "HEADER_SEARCH_PATHS": "$(PODS_ROOT)/SQLCipher", - "GCC_PREPROCESSOR_DEFINITIONS": "$(inherited) SQLITE_HAS_CODEC=1", + "GCC_PREPROCESSOR_DEFINITIONS": "$(inherited) SQLITE_HAS_CODEC=1 _SQLITE3_H_=1 _FTS5_H=1 _SQLITE3RTREE_H_=1", "OTHER_CFLAGS": "$(inherited) -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLITE_SOUNDEX -DSQLITE_THREADSAFE -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_STAT3 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_LOAD_EXTENSION -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS4_UNICODE61 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLCIPHER_CRYPTO_CC -DHAVE_USLEEP=1 -DSQLITE_MAX_VARIABLE_NUMBER=99999" } }, From 4218fd5a38d21af1417147cbe2de44b99caad26b Mon Sep 17 00:00:00 2001 From: Micah Moore Date: Mon, 18 Mar 2024 16:13:39 -0400 Subject: [PATCH 130/340] Increases iOS and tvOS version in podspec to 12.0 which is the minimum of Xcode 15.3 --- SQLCipher.podspec.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index fde4895c6..1fcc38ca0 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -6,9 +6,9 @@ "license": "BSD", "name": "SQLCipher", "platforms": { - "ios": "11.0", + "ios": "12.0", "osx": "10.13", - "tvos": "11.0", + "tvos": "12.0", "watchos": "7.0" }, "prepare_command": "./configure --enable-tempstore=yes --with-crypto-lib=commoncrypto CFLAGS=\"-DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLITE_SOUNDEX -DSQLITE_THREADSAFE -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_STAT3 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_LOAD_EXTENSION -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS4_UNICODE61 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DHAVE_USLEEP=1 -DSQLITE_MAX_VARIABLE_NUMBER=99999\"; make sqlite3.c", From 46306ecf549043fc315582877a8670e619b0429e Mon Sep 17 00:00:00 2001 From: Micah Moore Date: Tue, 9 Apr 2024 12:23:04 -0400 Subject: [PATCH 131/340] Adds Privacy Accessed API Types to Privacy manifest for file timestamp + disk space usages. --- sqlcipher-resources/PrivacyInfo.xcprivacy | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/sqlcipher-resources/PrivacyInfo.xcprivacy b/sqlcipher-resources/PrivacyInfo.xcprivacy index 9ac078de7..ce463c727 100644 --- a/sqlcipher-resources/PrivacyInfo.xcprivacy +++ b/sqlcipher-resources/PrivacyInfo.xcprivacy @@ -19,7 +19,23 @@ NSPrivacyAccessedAPITypes - + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + 3B52.1 + + NSPrivacyTrackingDomains From e94562c6988c973df829b0d222dc9222d3625b02 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 11 Apr 2024 16:07:58 -0400 Subject: [PATCH 132/340] fix malformed man caused by old merge conflict --- sqlcipher.1 | 96 ++++------------------------------------------------- 1 file changed, 7 insertions(+), 89 deletions(-) diff --git a/sqlcipher.1 b/sqlcipher.1 index b1d15e019..c9a310057 100644 --- a/sqlcipher.1 +++ b/sqlcipher.1 @@ -2,7 +2,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH SQLCIPHER 1 "Thu Aug 31 12:00:00 EDT 2023" +.TH SQLCIPHER 1 "Fri Aug 11 23:50:12 CET 2023" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -49,7 +49,7 @@ a table named "memos" and insert a couple of records into that table: $ .B sqlcipher mydata.db .br -SQLite version 3.43.0 2023-08-11 17:45:23 +SQLCipher version 3.43.0 2023-08-11 17:45:23 .br Enter ".help" for usage hints. .br @@ -111,87 +111,10 @@ sqlite> ... .sp .fi -<<<<<<< HEAD:sqlcipher.1 -.SH OPTIONS -.B sqlcipher -has the following options: -.TP -.B \-bail -Stop after hitting an error. -.TP -.B \-batch -Force batch I/O. -.TP -.B \-column -Query results will be displayed in a table like form, using -whitespace characters to separate the columns and align the -output. -.TP -.BI \-cmd\ command -run -.I command -before reading stdin -.TP -.B \-csv -Set output mode to CSV (comma separated values). -.TP -.B \-echo -Print commands before execution. -.TP -.BI \-init\ file -Read and execute commands from -.I file -, which can contain a mix of SQL statements and meta-commands. -.TP -.B \-[no]header -Turn headers on or off. -.TP -.B \-help -Show help on options and exit. -.TP -.B \-html -Query results will be output as simple HTML tables. -.TP -.B \-interactive -Force interactive I/O. -.TP -.B \-line -Query results will be displayed with one value per line, rows -separated by a blank line. Designed to be easily parsed by -scripts or other programs -.TP -.B \-list -Query results will be displayed with the separator (|, by default) -character between each field value. The default. -.TP -.BI \-mmap\ N -Set default mmap size to -.I N -\. -.TP -.BI \-nullvalue\ string -Set string used to represent NULL values. Default is '' -(empty string). -.TP -.BI \-separator\ separator -Set output field separator. Default is '|'. -.TP -.B \-stats -Print memory stats before each finalize. -.TP -.B \-version -Show SQLite version. -.TP -.BI \-vfs\ name -Use -.I name -as the default VFS. -======= The available commands differ by version and build options, so they are not listed here. Please refer to your local copy for all available options. ->>>>>>> sqlite-release:sqlite3.1 .SH INIT FILE @@ -215,7 +138,7 @@ continue prompt = " ...> " .fi o If the file -.B ${XDG_CONFIG_HOME}/sqlite3/sqliterc +.B ${XDG_CONFIG_HOME}/sqlcipher/sqliterc or .B ~/.sqliterc exists, the first of those to be found is processed during startup. @@ -226,17 +149,12 @@ o If the -init option is present, the specified file is processed. o All other command line options are processed. .SH SEE ALSO -<<<<<<< HEAD:sqlcipher.1 -https://www.zetetic.net/sqlcipher -======= -https://sqlite.org/cli.html -.br -https://sqlite.org/fiddle (a WebAssembly build of the CLI app) ->>>>>>> sqlite-release:sqlite3.1 +https://zetetic.net/sqlcipher + .br -The sqlite3-doc package. +The sqlcipher-doc package. .SH AUTHOR This manual page was originally written by Andreas Rottmann , for the Debian GNU/Linux system (but may be used by others). It was subsequently revised by Bill Bumgarner , -Laszlo Boszormenyi , and the sqlite3 developers. +Laszlo Boszormenyi , and the sqlcipher developers. From 7b69f52ebc151e15ff850d29df98c7f64d9108a7 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 15 Apr 2024 17:24:34 -0400 Subject: [PATCH 133/340] Snapshot of upstream SQLite 3.45.3 --- VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 ++-- ext/recover/dbdata.c | 24 ++++- ext/recover/recovercorrupt2.test | 28 ++++++ ext/recover/sqlite3recover.c | 2 +- ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js | 2 +- manifest | 94 +++++++++---------- manifest.uuid | 2 +- src/btree.c | 4 +- src/build.c | 9 +- src/ctime.c | 3 + src/expr.c | 14 ++- src/func.c | 6 +- src/global.c | 3 + src/insert.c | 5 +- src/main.c | 12 +++ src/pager.c | 2 +- src/pragma.c | 6 +- src/resolve.c | 33 ++++++- src/select.c | 34 +++++-- src/shell.c.in | 7 ++ src/sqlite.h.in | 17 ++++ src/sqliteInt.h | 21 ++++- src/test_config.c | 8 ++ src/update.c | 3 + src/util.c | 13 +++ src/where.c | 8 +- test/default.test | 4 + test/func.test | 21 +++++ test/fuzzinvariants.c | 8 ++ test/join5.test | 8 +- test/joinH.test | 38 +++++++- test/misc2.test | 29 ++++-- test/misc8.test | 7 +- test/pragma4.test | 18 +++- test/returning1.test | 43 ++++++--- test/rowid.test | 37 +++++--- test/scanstatus2.test | 2 +- test/trigger9.test | 27 ++++-- test/unionall.test | 2 +- test/vacuum-into.test | 35 ++++++- tool/mkctimec.tcl | 1 + 43 files changed, 514 insertions(+), 148 deletions(-) diff --git a/VERSION b/VERSION index caa72fa19..77c28ab7c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.45.2 +3.45.3 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index ef6e9e680..ba4690a93 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.45.2]) +AC_INIT([sqlite],[3.45.3]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 5253b4f18..08afa13a1 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.69 for sqlite 3.45.2. +# Generated by GNU Autoconf 2.69 for sqlite 3.45.3. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.45.2' -PACKAGE_STRING='sqlite 3.45.2' +PACKAGE_VERSION='3.45.3' +PACKAGE_STRING='sqlite 3.45.3' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1472,7 +1472,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.45.2 to adapt to many kinds of systems. +\`configure' configures sqlite 3.45.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.45.2:";; + short | recursive ) echo "Configuration of sqlite 3.45.3:";; esac cat <<\_ACEOF @@ -1668,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.45.2 +sqlite configure 3.45.3 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2087,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.45.2, which was +It was created by sqlite $as_me 3.45.3, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.45.2, which was +This file was extended by sqlite $as_me 3.45.3, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.45.2 +sqlite config.status 3.45.3 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/recover/dbdata.c b/ext/recover/dbdata.c index b6cb26ecd..ca6371026 100644 --- a/ext/recover/dbdata.c +++ b/ext/recover/dbdata.c @@ -494,6 +494,15 @@ static void dbdataValue( } } +/* This macro is a copy of the MX_CELL() macro in the SQLite core. Given +** a page-size, it returns the maximum number of cells that may be present +** on the page. */ +#define DBDATA_MX_CELL(pgsz) ((pgsz-8)/6) + +/* Maximum number of fields that may appear in a single record. This is +** the "hard-limit", according to comments in sqliteLimit.h. */ +#define DBDATA_MX_FIELD 32676 + /* ** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry. */ @@ -522,6 +531,9 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ assert( iOff+3+2<=pCsr->nPage ); pCsr->iCell = pTab->bPtr ? -2 : 0; pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]); + if( pCsr->nCell>DBDATA_MX_CELL(pCsr->nPage) ){ + pCsr->nCell = DBDATA_MX_CELL(pCsr->nPage); + } } if( pTab->bPtr ){ @@ -566,19 +578,19 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ if( pCsr->iCell>=pCsr->nCell ){ bNextPage = 1; }else{ + int iCellPtr = iOff + 8 + nPointer + pCsr->iCell*2; - iOff += 8 + nPointer + pCsr->iCell*2; - if( iOff>pCsr->nPage ){ + if( iCellPtr>pCsr->nPage ){ bNextPage = 1; }else{ - iOff = get_uint16(&pCsr->aPage[iOff]); + iOff = get_uint16(&pCsr->aPage[iCellPtr]); } /* For an interior node cell, skip past the child-page number */ iOff += nPointer; /* Load the "byte of payload including overflow" field */ - if( bNextPage || iOff>pCsr->nPage ){ + if( bNextPage || iOff>pCsr->nPage || iOff<=iCellPtr ){ bNextPage = 1; }else{ iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); @@ -661,7 +673,9 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ pCsr->iField++; if( pCsr->iField>0 ){ sqlite3_int64 iType; - if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){ + if( pCsr->pHdrPtr>=&pCsr->pRec[pCsr->nRec] + || pCsr->iField>=DBDATA_MX_FIELD + ){ bNextPage = 1; }else{ int szField = 0; diff --git a/ext/recover/recovercorrupt2.test b/ext/recover/recovercorrupt2.test index 29acc27a3..6c216308f 100644 --- a/ext/recover/recovercorrupt2.test +++ b/ext/recover/recovercorrupt2.test @@ -524,5 +524,33 @@ do_test 7.1 { list [catch { $R finish } msg] $msg } {1 {file is not a database}} +reset_db +breakpoint +do_test 8.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 8192 pagesize 4096 filename db.sqlite +| page 1 offset 0 +| 0: ac ae b3 76 74 65 20 66 6f 72 6d 61 74 20 33 00 ...vte format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 01 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ +| 96: 00 2e 76 8a 0d ff ff ff 1e 0f cb 00 0f cb 00 00 ..v............. +| 4032: 00 00 00 00 00 00 00 00 00 00 00 33 01 06 17 19 ...........3.... +| 4048: 19 01 43 74 61 62 6c 65 54 61 62 6c 65 30 54 61 ..CtableTable0Ta +| 4064: 62 6c 65 30 02 43 52 45 41 54 45 20 54 41 42 4c ble0.CREATE TABL +| 4080: 45 20 54 61 62 6c 65 30 20 28 43 6f 6c 30 20 29 E Table0 (Col0 ) +| page 2 offset 4096 +| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................ +| end db.sqlite +}]} {} + +do_test 8.1 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {0 {}} + finish_test diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index c445c5179..1d858c0ab 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -1189,7 +1189,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){ if( bTable && !bVirtual ){ if( SQLITE_ROW==sqlite3_step(pTblname) ){ const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0); - recoverAddTable(p, zTbl, iRoot); + if( zTbl ) recoverAddTable(p, zTbl, iRoot); } recoverReset(p, pTblname); } diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js index 870073cc0..2089eceb4 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -1271,7 +1271,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return poolUtil; }).catch(async (e)=>{ await thePool.removeVfs().catch(()=>{}); - return e; + throw e; }); }).catch((err)=>{ //error("rejecting promise:",err); diff --git a/manifest b/manifest index e139b78e6..e068a441a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.45.2 -D 2024-03-12T11:06:23.127 +C Version\s3.45.3 +D 2024-04-15T13:34:05.554 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 F Makefile.msc df56c06ef05add5ebcdcc9d4823fb2ec66ac16b06acb6971a7420859025886fa F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3 -F VERSION 0aec26ad81430ff6a34a47daacf854d2fe0d0aae2b822230f949082f0adfffe7 +F VERSION 72f46aae916c0bad94b6e64aef327e60bad69bb591a93b95f079ccedcb32b7c7 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@ -22,7 +22,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac a86f4955f600af734b106207db9cc03322877864356585e83df84fd4e5c7a586 +F autoconf/tea/configure.ac 13ba7cab3eedc3af6650fa0037deac0de3c21f909165c2e76e0995fd546b958c F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,7 +33,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 8dc5fe9688a74fbe82d21598a69d575a12faa8fcf471e95cc7dea514da63b411 x +F configure 7398a16e7a444c105d0300a6a0f5e7248fb0bef006385c07b53dfaf0d3c7bbd8 x F configure.ac f25bd7843120f2c2b8bc9db5a92b0502bbdd28e68907415c3d42fc8e57c657b9 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -467,13 +467,13 @@ F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69 F ext/rbu/sqlite3rbu.c d4ddf8f0e93772556e452a6c2814063cf47efb760a0834391a9d0cd9859fa4b9 F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 -F ext/recover/dbdata.c b7746c2ec801b453840831311be4b31f8c8f9dd97791060a69bbf12392c78949 +F ext/recover/dbdata.c d2e00d3cac74319c9c6def2e56ab2146b4f4ba5d820ab275e8da24e9766c247e F ext/recover/recover1.test c484d01502239f11b61f23c1cee9f5dd19fa17617f8974e42e74d64639c524cf F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a F ext/recover/recoverbuild.test c74170e0f7b02456af41838afeb5353fdb985a48cc2331d661bbabbca7c6b8e3 F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e F ext/recover/recovercorrupt.test 64c081ad1200ae77b447da99eb724785d6bf71715f394543dc7689642e92bf49 -F ext/recover/recovercorrupt2.test b9b974f006340a1300b5a7e687bd6097c5238498181c6f92d87406a77ece430b +F ext/recover/recovercorrupt2.test 1418f1710debc24ff38276cedfcea234beb37a34205708e7e3e6d76cc4a979db F ext/recover/recoverfault.test 9d9f88eeb222615a25e7514f234c950d46bee20d24cd8db49d8fff8d650dcfe1 F ext/recover/recoverfault2.test 730e7371bcda769554d15460cb23126abba1be8eca9539ccabf63623e7bb7e09 F ext/recover/recoverold.test 68db3d6f85dd2b98e785b6c4da4f5eea4bbe52ccf6674d9a94c7506dc92596aa @@ -481,7 +481,7 @@ F ext/recover/recoverpgsz.test 3658ab8e68475b1bb87d6af88baa04551c84b73280a566a1b F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a3a21030057bfd81411 F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486 -F ext/recover/sqlite3recover.c e6eb20c469bcdb96f297f2241860bccabf9f036bfa7f3d47bcc6ca1191b108dc +F ext/recover/sqlite3recover.c 65ef0f56301a16c0536c9839fb7e23540c9c4f75da0afe3b7b4d163c8f624404 F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 F ext/recover/test_recover.c fd871a40f2238022bedcbdf3cb493b91225edaa94d6ae8892af97a10e7ccc4ba F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 @@ -601,7 +601,7 @@ F ext/wasm/api/sqlite3-api-worker1.js fd46628ef147dd5856c88f63a9a279a40f744f1fdf F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 -F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 595953994aa3ae2287c889c4da39ab3d6f17b6461ecf4bec334b7a3faafddb02 +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 90d93d3365567d52c6959ed5d6f80adccd173799772d73639ff647ada58193d4 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 46c4afa6c50d7369252c104f274ad977a97e91ccfafc38b400fe36e90bdda88e F ext/wasm/api/sqlite3-wasm.c dfd1f1a225b267e8fd641dcd6c7d579fbe2b731aeaa123324135efac830a2bcf F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js f234191fe6bf41a5a1e59c9f43ed816e74a522b3d60d3f556f66c3085c448503 @@ -677,31 +677,31 @@ F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 -F src/btree.c 5410768087e524f331816c751c4b1f714693fa864a448337e443e49cc397aeaf +F src/btree.c 0d264fb19a783325fb48be7e74bb19999b17914896e36bdee73c5d31d06d616d F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 F src/btreeInt.h 3e2589726c4f105e653461814f65857465da68be1fac688de340c43b873f4062 -F src/build.c e7d9044592eeeea8e78d8ae53ca8d31fd6e92ca0d4f53e2f2e8ccf7352e0b04b +F src/build.c ec93d135f9c02b2a5d174384b6f9c222c16b1c8e473c482284759f3f0a35e336 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b +F src/ctime.c 64e4b1227b4ed123146f0aa2989131d1fbd9b927b11e80c9d58c6a68f9cd5ce3 F src/date.c 3b8d02977d160e128469de38493b4085f7c5cf4073193459909a6af3cf6d7c91 F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 -F src/expr.c 3381ee4c9aa7ccde22a2a7f35ce343925a7a25d96bdc943649131f9decdebad2 +F src/expr.c 1fece60622b8b74d8b3bf59faba4c2509a177ea65d9642dfbd22ceb7ec6eeea5 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 -F src/func.c 4204c56196847faefef57fa14e43b8e4d65eb8d7e65318abe463472e3fd148cb -F src/global.c 765a0656d6cbf043cb272ff0ae38f39cc46713539ffe6793258ed3eb4b188b52 +F src/func.c 283d4f3b2751a1d9339fd93a8a013d1948fd5f4474a3cab0955eb4fafd445d0f +F src/global.c 61a419dd9e993b9be0f91de4c4ccf322b053eb829868e089f0321dd669be3b90 F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c eb33ea46dcab93e90f112fced343aaf41f59cbd2e951d5066f1f9302be1c2f34 +F src/insert.c 7d318f638431af858b5ebd5d2c17d6dacd0adf114dbbfbe23f3033c3c043c7f0 F src/json.c 29a42bc92c2384653b8b5e5ad26bdee4e2334544c7cfb78ceb4a3ca81d674686 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 -F src/main.c 438b95162acfa17b7d218f586f5bde11d6ae82bcf030c9611fc537556870ad6b +F src/main.c 8a59d297ec77e6b78550433bfccb95a1b26f2fb69aaaf233206e21579a1cfcc1 F src/malloc.c f016922435dc7d1f1f5083a03338a3e91f8c67ce2c5bdcfa4cdef62e612f5fcc F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -725,25 +725,25 @@ F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d87210 F src/os_unix.c fa9b81b642e60e77ffaf98bd1a2e5fde16c1c2317614ec178bf3bd5864772356 F src/os_win.c 6ff43bac175bd9ed79e7c0f96840b139f2f51d01689a638fd05128becf94908a F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c ff60e98138d2499082ac6230f01ac508aba545315debccfca2fd6042f5f10fcd +F src/pager.c 9beb80f6e330dd63c5d8ba0f7a7f3a55fff22067a68d424949c389bfc6fa0c56 F src/pager.h 4b1140d691860de0be1347474c51fee07d5420bd7f802d38cbab8ea4ab9f538a F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 -F src/pragma.c b61a1a1801befe6763e787b96988deb156a56e82a2b4dea004bc78e4551c9d5c +F src/pragma.c a13593a09da0d27b44a57bf6b2b9f6d163ad78aef4ad1d64c6214897dbe0c9ba F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c 371f6115cb69286ebc12c6f2d7511279c2e47d9f54f475d46a554d687a3b312c F src/printf.c d3392b2a20ee314ddeef34fb43c904bf4619eb20ff9a9e07e3950a7e4dcd6912 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 446bcb8ebf0ea7066c2ca99e5336f0dbc9230ac76f80fafd1bfa82fe7871af2d +F src/resolve.c b81374797e47a2e98c2a10b7152ad70280dfeb228f26ac45470760d02e360318 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c f1a81ff4f8e9e76c224e2ab3a4baa799add0db22158c7fcede65d8cc4a6fa2da -F src/shell.c.in f7cc8711aee604bc078a93d777ad7246980485c57fa3047408b0f842ba03c14d -F src/sqlite.h.in 020d7b7307dda51420dc48b47e5334eaface77baba6bd9818375d392eb3ab5b5 +F src/select.c 8b9b78a58781955d5bc676efde95e3cbc82174bf7c1b3968c855faff134c24d2 +F src/shell.c.in e4815eb8a7b3110994a1d7e9f42e6b3e9e3576d25ec63f75259b797938cada98 +F src/sqlite.h.in 4f7840e1abb041b80da0af48f870e2ae3552e2a97c902eebe7455fe5a89562e3 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 59e83fc2b01e7c7f550a9ab77fd5876a49f37acbf4cc01c031fc7947f95959d6 +F src/sqliteInt.h 867a6691a5f06ceb6a17a828337f6ace1ad35c44f324bf50d4a2169c3db8571f F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -762,7 +762,7 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 F src/test_bestindex.c f6af1e41cb7901edafb065a8198e4a0192dd42432b642d038965be5e628dec12 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 -F src/test_config.c f0cc1f517deaa96dd384822ae2bb91534fa56aa458528b439830d709941d3932 +F src/test_config.c 5fa77ee6064ba546e144c4fea870c5ede2c54314616f81485c6a9c4192100c75 F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f F src/test_demovfs.c 38a459d1c78fd9afa770445b224c485e079018d6ac07332ff9bd07b54d2b8ce9 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 @@ -803,10 +803,10 @@ F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 F src/treeview.c c6fc972683fd00f975d8b32a81c1f25d2fb7d4035366bf45c9f5622d3ccd70ee F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81 -F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 +F src/update.c 732404a04d1737ef14bb6ec6b84f74edf28b3c102a92ae46b4855438a710efe7 F src/upsert.c 2e60567a0e9e8520c18671b30712a88dc73534474304af94f32bb5f3ef65ac65 F src/utf.c f23165685a67b4caf8ec08fb274cb3f319103decfb2a980b7cfd55d18dfa855e -F src/util.c c346e2a811285f6bad8a87c862070207855159a783993d03ca20dea2c74ace04 +F src/util.c a94fdaa08e3bbd35e425a1c7aed3065646cffe2a1cdf870885e8c6c61ce22bfc F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 F src/vdbe.c b2a45392265cb83f60251406039bf5255462d4a6d8deb05b2eaccab5abb2e20b F src/vdbe.h 88e19a982df9027ec1c177c793d1a5d34dc23d8f06e3b2d997f43688b05ee0eb @@ -823,7 +823,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c 56277e7110e6c81918434908bb7d597b917adfa9a176f5d95eb954b93dbc57b8 +F src/where.c e3cb2a01bc265547b936fcbbbce8d55fdf8b017e7d55803be31b56cfc16d3fae F src/whereInt.h 82a13766f13d1a53b05387c2e60726289ef26404bc7b9b1f7770204d97357fb8 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 @@ -1044,7 +1044,7 @@ F test/dbpagefault.test 35f06cfb2ef100a9b19d25754e8141b9cba9b7daabd4c60fa5af93fc F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test ef731887b43ee32ef86e1c8fddb61a40789f988332c029c601dcf2c319277e9e -F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50 +F test/default.test 830fad7180cdf0e6a06e93acc0403bf73762314a639363314db5674c631b6127 F test/delete.test 2686e1c98d552ef37d79ad55b17b93fe96fad9737786917ce3839767f734c48f F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab @@ -1216,7 +1216,7 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test 504d202650c7940b5aa98364dd68f242df87f39f829e51074a55d79fc7bc7414 +F test/func.test b56905748ce0567c01d60005f3e6ad1af19453d224ba4730ee687d048fd09ef9 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a F test/func4.test e8ef9b2bd6a192a213cbd5cf31a3b35e25cd6ff2fdaeea0b58d63be31b03d220 @@ -1244,7 +1244,7 @@ F test/fuzzdata8.db 4a53b6d077c6a5c23b609d8d3ac66996fa55ba3f8d02f9b6efdd0214a767 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc -F test/fuzzinvariants.c b34530e8431f2cf3591eff588fc7684d6fdef466916fb46141c8c5374a3d8099 +F test/fuzzinvariants.c 4355043e98cd8555c62462fcbba91c17c6492b0b017bbbe68656d5f2208f6444 F test/gcfault.test 4ea410ac161e685f17b19e1f606f58514a2850e806c65b846d05f60d436c5b0d F test/gencol1.test e169bdfa11c7ed5e9f322a98a7db3afe9e66235750b68c923efee8e1876b46ec F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 @@ -1313,7 +1313,7 @@ F test/join.test f7abfef3faeaf2800308872e33a57e5b6e4a2b44fb8c6b90c6068412e71a6cf F test/join2.test 8561fe82ce434ac96de91544072e578dc2cadddf2d9bc9cd802f866a9b92502e F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 -F test/join5.test 7cc3f3595bb41e60f3f96d5cb6dd8cfcbc31212f0136bba6fc081c082994a94a +F test/join5.test 380d12a9350f99f0cc681a4f1fea999886f18b3fe0d71a9b3065bcaead1e007f F test/join6.test f809c025fa253f9e150c0e9afd4cef8813257bceeb6f46e04041228c9403cc2c F test/join7.test 2268dcbb54b724391dda3748ea95c60d960607ffeed67885675998e7117697f6 F test/join8.test d384d63985e3991c404afccadaf3efd1cdf9cd72680167f80e3cb80b95c18c68 @@ -1324,7 +1324,7 @@ F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f2 F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28 F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 -F test/joinH.test f69e5b53b7d887914e854b6a131efbed4ea9f5ca52bdab81788bfc3e79299f43 +F test/joinH.test 55f69e64da74d4eca2235237f3acb657aef181e22e45daa228e35bba865e0255 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e @@ -1409,13 +1409,13 @@ F test/minmax2.test cf9311babb6f0518d04e42fd6a42c619531c4309a9dd790a2c4e9b3bc595 F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 F test/minmax4.test 272ca395257f05937dc96441c9dde4bc9fbf116a8d4fa02baeb0d13d50e36c87 F test/misc1.test 8d138a4926ab90617c1aa29ce26e7785ae2b83a4d3a195d543b7374e05589dd1 -F test/misc2.test 71e746af479119386ac2ed7ab7d81d99970e75b49ffd3e8efffee100b4b5f350 +F test/misc2.test a1a3573cc02662becd967766021d6f16c54684d56df5f227481c7ef0d9df0bd0 F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d F test/misc4.test 10cd6addb2fa9093df4751a1b92b50440175dd5468a6ec84d0386e78f087db0e F test/misc5.test c4aeaa0fa28faa08f2485309c38db4719e6cd1364215d5687a5b96d340a3fa58 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 F test/misc7.test d912f3d45c2989191b797504a220ca225d6be80b21acad22ba0d35f4a9ee4579 -F test/misc8.test 4db9f8be59834cea08c87e9658014080efa02678ef54a088f84fa5647e81fee0 +F test/misc8.test 08d2380bc435486b12161521f225043ac2be26f02471c2c1ea4cac0b1548edbd F test/misuse.test 9e7f78402005e833af71dcab32d048003869eca5abcaccc985d4f8dc1d86bcc7 F test/mjournal.test 28a08d5cb5fb5b5702a46e19176e45e964e0800d1f894677169e79f34030e152 F test/mmap1.test 18de3fd7b70a777af6004ca2feecfcdd3d0be17fa04058e808baf530c94b1a1d @@ -1483,7 +1483,7 @@ F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b76 F test/pragma.test cddd4b534d7fb5cf113d1308dea4231f3548e8a7f3a65d7d1cf4810c87090b5a F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31 -F test/pragma4.test ca5e4dfc46adfe490f75d73734f70349d95a199e6510973899e502eef2c8b1f8 +F test/pragma4.test c7539e5e63cdfd60b3c8114360ec2cf838e2cd6e3ebfd648152319dd8d6f6be0 F test/pragma5.test 7b33fc43e2e41abf17f35fb73f71b49671a380ea92a6c94b6ce530a25f8d9102 F test/pragmafault.test 275edaf3161771d37de60e5c2b412627ac94cef11739236bec12ed1258b240f8 F test/prefixes.test b524a1c44bffec225b9aec98bd728480352aa8532ac4c15771fb85e8beef65d9 @@ -1509,7 +1509,7 @@ F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed13 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test db532cde29d6aebbc48c6ddc3149b30476f8e69ca7a2c4b53986c7635e6fd8ec +F test/returning1.test 3ead782eddf51f573cdd43bcbb10d1b485ac095a19a76d16c43fd159ea9b7466 F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f @@ -1517,7 +1517,7 @@ F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a F test/round1.test 29c3c9039936ed024d672f003c4d35ee11c14c0acb75c5f7d6188ff16190cfd4 F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 -F test/rowid.test e29025be95baf6b32f0d5edef59a7633028325896a98f1caa8019559ca910350 +F test/rowid.test d27191b5ce794c05bf61081e8b2c546a1844c1641321dcaf7fb785234256cc8e F test/rowvalue.test baf4fa3ec1a8c1c920c3faa5fd25959cb454bbd99ac8960397c34549d9fc4abe F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b F test/rowvalue3.test 1347e25ca11c547c5a6ff0cc5626f95aa9740e9275bfaec096029f57cb2130ce @@ -1540,7 +1540,7 @@ F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7 F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test b249328caf4d317e71058006872b8012598a5fa045b30bf24a81eeff650ab49e -F test/scanstatus2.test 317670daf7f3eef48a9598cb7800ba8eccab51949cf52bca3f7da3b83a0c1c8c +F test/scanstatus2.test 2cb4d67ebbf578711dbf0377489a44759d0e21cdcce2fd55f0e044364961abec F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce @@ -1849,7 +1849,7 @@ F test/trigger5.test 619391a3e9fc194081d22cefd830d811e7badf83 F test/trigger6.test 0e411654f122552da6590f0b4e6f781048a4a9b9 F test/trigger7.test e7ce54bfda67a88d778aea42544e151c465547a7e617127b6914c2221a6d53c1 F test/trigger8.test 30cb0530bd7c4728055420e3f739aa00412eafa4 -F test/trigger9.test fd49aff8b724ae1a6238989ecf84320d88c0e8f8d9142a891b93f826dfe37b59 +F test/trigger9.test 1724b595661da3dd3c8d79f0ebae818132a39e65c241bad2049f66952b1dc29d F test/triggerA.test 837be862d8721f903dba3f3ceff05b32e0bee5214cf6ea3da5fadf12d3650e9d F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe F test/triggerC.test 29f5a28d0fe39e6e2c01f6e1f53f08c0955170ae10a63ad023e33cb0a1682a51 @@ -1869,7 +1869,7 @@ F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test c9db8f9e80309edfa4252585cf16bcab7ed31f39eeb904d21e831199a3613fb0 F test/unhex.test b7f1b806207cb77fa31c3e434fe92fba524464e3e9356809bfcc28f15af1a8b7 -F test/unionall.test 5b1c4186a661e4bf762875caf4c61d8fda3dd04a6fa9005187f6ba8900c2913f +F test/unionall.test 04d30726c5056f84f92b3a12bf8d8a1dbbe807d1ddc8af95def09e6ef2dd91e3 F test/unionall2.test 71e8fa08d5699d50dc9f9dc0c9799c2e7a6bb7931a330d369307a4df7f157fa1 F test/unionallfault.test 652bfbb630e6c43135965dc1e8f0a9a791da83aec885d626a632fe1909c56f73 F test/unionvtab.test e1704ab1b4c1bb3ffc9da4681f8e85a0b909fd80b937984fc94b27415ac8e5a4 @@ -1896,7 +1896,7 @@ F test/uri.test c1abaaaa28e9422d61e5f3f9cbc8ef993ec49fe802f581520731708561d49384 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 F test/utf16align.test 9fde0bb5d3a821594aa68c6829ab9c5453a084384137ebb9f6153e2d678039da -F test/vacuum-into.test 35dc6f79b563f91c61822f61797363e97fed1bf28f1f722688b98d43f1980d76 +F test/vacuum-into.test 77845cee98770c416dae9b0da6bb3229753861f2da65c11b4f9715d081712d8a F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d F test/vacuum2.test 9fd45ce6ce29f5614c249e03938d3567c06a9e772d4f155949f8eafe2d8af520 F test/vacuum3.test d9d9a04ee58c485b94694fd4f68cffaba49c32234fdefe1ac1a622c5e17d4ce3 @@ -2080,7 +2080,7 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh cbdcf993fa83dccbef7fb77b39cdeb31ef9f77d9d88c9e343b58d35ca3898a6a F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl a16682eae5f01f85e5861b2aa215ca0d46b4230658ee25977e02b4508566fb75 x +F tool/mkctimec.tcl 060e9785e9503bf51f8b1b11b542bdeef90fd0ceb0738154f6762acec0c61e5f x F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef @@ -2160,10 +2160,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b26f24441f84a30deb9a562ab6c6de7543fbc3b3b93c34277964c9c21d734153 -R 155ffa116058b8f4537c03201d4943ec +P 08dd2b927bf8fbda76b4ea07ca1827efa9bb0b68348683abfd19328c117dc405 +R c4d37ec4d4b175414d039c166b6f8ab8 T +sym-release * -T +sym-version-3.45.2 * +T +sym-version-3.45.3 * U drh -Z ed4a58f9c6cbbc2b817c3b07c0b5f709 +Z 024d96198ee99cc296e773820295fe65 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c69bc8e96..7b96963b9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77 +8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355 diff --git a/src/btree.c b/src/btree.c index 0fdc680f4..84a70cc57 100644 --- a/src/btree.c +++ b/src/btree.c @@ -9470,7 +9470,7 @@ int sqlite3BtreeInsert( }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); idx = ++pCur->ix; - pCur->curFlags &= ~BTCF_ValidNKey; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); }else{ assert( pPage->leaf ); } @@ -9500,7 +9500,7 @@ int sqlite3BtreeInsert( */ if( pPage->nOverflow ){ assert( rc==SQLITE_OK ); - pCur->curFlags &= ~(BTCF_ValidNKey); + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); rc = balance(pCur); /* Must make sure nOverflow is reset to zero even if the balance() diff --git a/src/build.c b/src/build.c index a2553da9f..4e46ea0b5 100644 --- a/src/build.c +++ b/src/build.c @@ -3006,9 +3006,12 @@ void sqlite3CreateView( ** on a view, even though views do not have rowids. The following flag ** setting fixes this problem. But the fix can be disabled by compiling ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that - ** depend upon the old buggy behavior. */ -#ifndef SQLITE_ALLOW_ROWID_IN_VIEW - p->tabFlags |= TF_NoVisibleRowid; + ** depend upon the old buggy behavior. The ability can also be toggled + ** using sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW,...) */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + p->tabFlags |= sqlite3Config.mNoVisibleRowid; /* Optional. Allow by default */ +#else + p->tabFlags |= TF_NoVisibleRowid; /* Never allow rowid in view */ #endif sqlite3TwoPartName(pParse, pName1, pName2, &pName); diff --git a/src/ctime.c b/src/ctime.c index cf761299f..0ffe2a5bd 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -65,6 +65,9 @@ static const char * const sqlite3azCompileOpt[] = { "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), # endif #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + "ALLOW_ROWID_IN_VIEW", +#endif #ifdef SQLITE_ALLOW_URI_AUTHORITY "ALLOW_URI_AUTHORITY", #endif diff --git a/src/expr.c b/src/expr.c index f9b280bbc..e508c7a8a 100644 --- a/src/expr.c +++ b/src/expr.c @@ -218,9 +218,10 @@ Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){ assert( pExpr->x.pList->nExpr>0 ); assert( pExpr->op==TK_FUNCTION ); pExpr = pExpr->x.pList->a[0].pExpr; - }else{ - assert( pExpr->op==TK_COLLATE ); + }else if( pExpr->op==TK_COLLATE ){ pExpr = pExpr->pLeft; + }else{ + break; } } return pExpr; @@ -2739,9 +2740,12 @@ int sqlite3ExprCanBeNull(const Expr *p){ return 0; case TK_COLUMN: assert( ExprUseYTab(p) ); - return ExprHasProperty(p, EP_CanBeNull) || - NEVER(p->y.pTab==0) || /* Reference to column of index on expr */ - (p->iColumn>=0 + return ExprHasProperty(p, EP_CanBeNull) + || NEVER(p->y.pTab==0) /* Reference to column of index on expr */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + || (p->iColumn==XN_ROWID && IsView(p->y.pTab)) +#endif + || (p->iColumn>=0 && p->y.pTab->aCol!=0 /* Possible due to prior error */ && ALWAYS(p->iColumny.pTab->nCol) && p->y.pTab->aCol[p->iColumn].notNull==0); diff --git a/src/func.c b/src/func.c index 9fbd1e9e1..18004984d 100644 --- a/src/func.c +++ b/src/func.c @@ -1892,7 +1892,7 @@ static void sumFinalize(sqlite3_context *context){ if( p->approx ){ if( p->ovrfl ){ sqlite3_result_error(context,"integer overflow",-1); - }else if( !sqlite3IsNaN(p->rErr) ){ + }else if( !sqlite3IsOverflow(p->rErr) ){ sqlite3_result_double(context, p->rSum+p->rErr); }else{ sqlite3_result_double(context, p->rSum); @@ -1909,7 +1909,7 @@ static void avgFinalize(sqlite3_context *context){ double r; if( p->approx ){ r = p->rSum; - if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } @@ -1923,7 +1923,7 @@ static void totalFinalize(sqlite3_context *context){ if( p ){ if( p->approx ){ r = p->rSum; - if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } diff --git a/src/global.c b/src/global.c index 7f27d91d1..121b3f6d6 100644 --- a/src/global.c +++ b/src/global.c @@ -288,6 +288,9 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { #endif #ifndef SQLITE_UNTESTABLE 0, /* xTestCallback */ +#endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + 0, /* mNoVisibleRowid. 0 == allow rowid-in-view */ #endif 0, /* bLocaltimeFault */ 0, /* xAltLocaltime */ diff --git a/src/insert.c b/src/insert.c index 095298b90..c2c2f7cc0 100644 --- a/src/insert.c +++ b/src/insert.c @@ -2978,7 +2978,10 @@ static int xferOptimization( } } #ifndef SQLITE_OMIT_CHECK - if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){ + if( pDest->pCheck + && (db->mDbFlags & DBFLAG_Vacuum)==0 + && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) + ){ return 0; /* Tables have different CHECK constraints. Ticket #2252 */ } #endif diff --git a/src/main.c b/src/main.c index 03429983d..bff801a87 100644 --- a/src/main.c +++ b/src/main.c @@ -765,6 +765,18 @@ int sqlite3_config(int op, ...){ } #endif /* SQLITE_OMIT_DESERIALIZE */ + case SQLITE_CONFIG_ROWID_IN_VIEW: { + int *pVal = va_arg(ap,int*); +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( 0==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = TF_NoVisibleRowid; + if( 1==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = 0; + *pVal = (sqlite3GlobalConfig.mNoVisibleRowid==0); +#else + *pVal = 0; +#endif + break; + } + default: { rc = SQLITE_ERROR; break; diff --git a/src/pager.c b/src/pager.c index 37588f0b2..268dc7db3 100644 --- a/src/pager.c +++ b/src/pager.c @@ -7087,7 +7087,7 @@ sqlite3_file *sqlite3PagerFile(Pager *pPager){ ** This will be either the rollback journal or the WAL file. */ sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){ -#if SQLITE_OMIT_WAL +#ifdef SQLITE_OMIT_WAL return pPager->jfd; #else return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd; diff --git a/src/pragma.c b/src/pragma.c index a1243acc1..0a6873201 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -2756,7 +2756,11 @@ static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ j = seen[0]-1; pIdxInfo->aConstraintUsage[j].argvIndex = 1; pIdxInfo->aConstraintUsage[j].omit = 1; - if( seen[1]==0 ) return SQLITE_OK; + if( seen[1]==0 ){ + pIdxInfo->estimatedCost = (double)1000; + pIdxInfo->estimatedRows = 1000; + return SQLITE_OK; + } pIdxInfo->estimatedCost = (double)20; pIdxInfo->estimatedRows = 20; j = seen[1]-1; diff --git a/src/resolve.c b/src/resolve.c index 24e47789b..fdf260d8c 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -468,8 +468,37 @@ static int lookupName( } } if( 0==cnt && VisibleRowid(pTab) ){ + /* pTab is a potential ROWID match. Keep track of it and match + ** the ROWID later if that seems appropriate. (Search for "cntTab" + ** to find related code.) Only allow a ROWID match if there is + ** a single ROWID match candidate. + */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + /* In SQLITE_ALLOW_ROWID_IN_VIEW mode, allow a ROWID match + ** if there is a single VIEW candidate or if there is a single + ** non-VIEW candidate plus multiple VIEW candidates. In other + ** words non-VIEW candidate terms take precedence over VIEWs. + */ + if( cntTab==0 + || (cntTab==1 + && ALWAYS(pMatch!=0) + && ALWAYS(pMatch->pTab!=0) + && (pMatch->pTab->tabFlags & TF_Ephemeral)!=0 + && (pTab->tabFlags & TF_Ephemeral)==0) + ){ + cntTab = 1; + pMatch = pItem; + }else{ + cntTab++; + } +#else + /* The (much more common) non-SQLITE_ALLOW_ROWID_IN_VIEW case is + ** simpler since we require exactly one candidate, which will + ** always be a non-VIEW + */ cntTab++; pMatch = pItem; +#endif } } if( pMatch ){ @@ -595,13 +624,13 @@ static int lookupName( ** Perhaps the name is a reference to the ROWID */ if( cnt==0 - && cntTab==1 + && cntTab>=1 && pMatch && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && sqlite3IsRowid(zCol) && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom) ){ - cnt = 1; + cnt = cntTab; if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1; pExpr->affExpr = SQLITE_AFF_INTEGER; } diff --git a/src/select.c b/src/select.c index 121572779..fdc7f5e67 100644 --- a/src/select.c +++ b/src/select.c @@ -1953,11 +1953,7 @@ static const char *columnTypeImpl( ** data for the result-set column of the sub-select. */ if( iColpEList->nExpr -#ifdef SQLITE_ALLOW_ROWID_IN_VIEW - && iCol>=0 -#else - && ALWAYS(iCol>=0) -#endif + && (!ViewCanHaveRowid || iCol>=0) ){ /* If iCol is less than zero, then the expression requests the ** rowid of the sub-select or view. This expression is legal (see @@ -5132,6 +5128,10 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** ** (11) The subquery is not a VALUES clause ** +** (12) The WHERE clause is not "rowid ISNULL" or the equivalent. This +** case only comes up if SQLite is compiled using +** SQLITE_ALLOW_ROWID_IN_VIEW. +** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. */ @@ -5242,6 +5242,18 @@ static int pushDownWhereTerms( } #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( ViewCanHaveRowid && (pWhere->op==TK_ISNULL || pWhere->op==TK_NOTNULL) ){ + Expr *pLeft = pWhere->pLeft; + if( ALWAYS(pLeft) + && pLeft->op==TK_COLUMN + && pLeft->iColumn < 0 + ){ + return 0; /* Restriction (12) */ + } + } +#endif + if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){ nChng++; pSubq->selFlags |= SF_PushDown; @@ -5869,12 +5881,14 @@ int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); pTab->iPKey = -1; + pTab->eTabType = TABTYP_VIEW; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); #ifndef SQLITE_ALLOW_ROWID_IN_VIEW /* The usual case - do not allow ROWID on a subquery */ pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; #else - pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */ + /* Legacy compatibility mode */ + pTab->tabFlags |= TF_Ephemeral | sqlite3Config.mNoVisibleRowid; #endif return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; } @@ -6142,7 +6156,7 @@ static int selectExpander(Walker *pWalker, Select *p){ pNestedFrom = pFrom->pSelect->pEList; assert( pNestedFrom!=0 ); assert( pNestedFrom->nExpr==pTab->nCol ); - assert( VisibleRowid(pTab)==0 ); + assert( VisibleRowid(pTab)==0 || ViewCanHaveRowid ); }else{ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; @@ -6174,7 +6188,8 @@ static int selectExpander(Walker *pWalker, Select *p){ pUsing = 0; } - nAdd = pTab->nCol + (VisibleRowid(pTab) && (selFlags&SF_NestedFrom)); + nAdd = pTab->nCol; + if( VisibleRowid(pTab) && (selFlags & SF_NestedFrom)!=0 ) nAdd++; for(j=0; ja[pNew->nExpr-1]; assert( pX->zEName==0 ); if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ - if( pNestedFrom ){ + if( pNestedFrom && (!ViewCanHaveRowid || jnExpr) ){ + assert( jnExpr ); pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); testcase( pX->zEName==0 ); }else{ diff --git a/src/shell.c.in b/src/shell.c.in index 8ee0dd2e7..1220b421d 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -11905,6 +11905,7 @@ static const char zOptions[] = " -newline SEP set output row separator. Default: '\\n'\n" " -nofollow refuse to open symbolic links to database files\n" " -nonce STRING set the safe-mode escape nonce\n" + " -no-rowid-in-view Disable rowid-in-view using sqlite3_config()\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" " -pcachetrace trace all page cache operations\n" @@ -12195,6 +12196,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdin_is_interactive = 0; }else if( cli_strcmp(z,"-utf8")==0 ){ }else if( cli_strcmp(z,"-no-utf8")==0 ){ + }else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){ + int val = 0; + sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW, &val); + assert( val==0 ); }else if( cli_strcmp(z,"-heap")==0 ){ #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) const char *zSize; @@ -12470,6 +12475,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ /* already handled */ }else if( cli_strcmp(z,"-no-utf8")==0 ){ /* already handled */ + }else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){ + /* already handled */ }else if( cli_strcmp(z,"-heap")==0 ){ i++; }else if( cli_strcmp(z,"-pagecache")==0 ){ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index f21cffd51..7acdde872 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -2143,6 +2143,22 @@ struct sqlite3_mem_methods { ** configuration setting is never used, then the default maximum is determined ** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that ** compile-time option is not set, then the default maximum is 1073741824. +** +** [[SQLITE_CONFIG_ROWID_IN_VIEW]] +**
    SQLITE_CONFIG_ROWID_IN_VIEW +**
    The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability +** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is +** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability +** defaults to on. This configuration option queries the current setting or +** changes the setting to off or on. The argument is a pointer to an integer. +** If that integer initially holds a value of 1, then the ability for VIEWs to +** have ROWIDs is activated. If the integer initially holds zero, then the +** ability is deactivated. Any other initial value for the integer leaves the +** setting unchanged. After changes, if any, the integer is written with +** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite +** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and +** recommended case) then the integer is always filled with zero, regardless +** if its initial value. **
  • */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ @@ -2174,6 +2190,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ +#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ /* ** CAPI3REF: Database Connection Configuration Options diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 49cff8fc1..933286bfe 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2530,6 +2530,15 @@ struct Table { #define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) #define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0) +/* Macro is true if the SQLITE_ALLOW_ROWID_IN_VIEW (mis-)feature is +** available. By default, this macro is false +*/ +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW +# define ViewCanHaveRowid 0 +#else +# define ViewCanHaveRowid (sqlite3Config.mNoVisibleRowid==0) +#endif + /* ** Each foreign key constraint is an instance of the following structure. ** @@ -4244,6 +4253,11 @@ struct Sqlite3Config { #endif #ifndef SQLITE_UNTESTABLE int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ +#endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + u32 mNoVisibleRowid; /* TF_NoVisibleRowid if the ROWID_IN_VIEW + ** feature is disabled. 0 if rowids can + ** occur in views. */ #endif int bLocaltimeFault; /* True to fail localtime() calls */ int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ @@ -4700,10 +4714,13 @@ void sqlite3MutexWarnOnContention(sqlite3_mutex*); # define EXP754 (((u64)0x7ff)<<52) # define MAN754 ((((u64)1)<<52)-1) # define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0) +# define IsOvfl(X) (((X)&EXP754)==EXP754) int sqlite3IsNaN(double); + int sqlite3IsOverflow(double); #else -# define IsNaN(X) 0 -# define sqlite3IsNaN(X) 0 +# define IsNaN(X) 0 +# define sqlite3IsNaN(X) 0 +# define sqlite3IsOVerflow(X) 0 #endif /* diff --git a/src/test_config.c b/src/test_config.c index ee766a26d..76904e5bf 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -59,6 +59,14 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "rowid32", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + Tcl_SetVar2( + interp, "sqlite_options", "allow_rowid_in_view", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2( + interp, "sqlite_options", "allow_rowid_in_view", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_CASE_SENSITIVE_LIKE Tcl_SetVar2(interp, "sqlite_options","casesensitivelike","1",TCL_GLOBAL_ONLY); #else diff --git a/src/update.c b/src/update.c index cd7d73f3f..b6068caa7 100644 --- a/src/update.c +++ b/src/update.c @@ -921,6 +921,9 @@ void sqlite3Update( } } if( chngRowid==0 && pPk==0 ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( isView ) sqlite3VdbeAddOp2(v, OP_Null, 0, regOldRowid); +#endif sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); } } diff --git a/src/util.c b/src/util.c index dd4990264..ca886a1c2 100644 --- a/src/util.c +++ b/src/util.c @@ -68,6 +68,19 @@ int sqlite3IsNaN(double x){ } #endif /* SQLITE_OMIT_FLOATING_POINT */ +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** Return true if the floating point value is NaN or +Inf or -Inf. +*/ +int sqlite3IsOverflow(double x){ + int rc; /* The value return */ + u64 y; + memcpy(&y,&x,sizeof(y)); + rc = IsOvfl(y); + return rc; +} +#endif /* SQLITE_OMIT_FLOATING_POINT */ + /* ** Compute a string length that is limited to what can be stored in ** lower 30 bits of a 32-bit signed integer. diff --git a/src/where.c b/src/where.c index 77813666e..8e7b112ac 100644 --- a/src/where.c +++ b/src/where.c @@ -5850,16 +5850,10 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( for(i=0; inColumn; i++){ Expr *pExpr; int j = pIdx->aiColumn[i]; - int bMaybeNullRow; if( j==XN_EXPR ){ pExpr = pIdx->aColExpr->a[i].pExpr; - testcase( pTabItem->fg.jointype & JT_LEFT ); - testcase( pTabItem->fg.jointype & JT_RIGHT ); - testcase( pTabItem->fg.jointype & JT_LTORJ ); - bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){ pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); - bMaybeNullRow = 0; }else{ continue; } @@ -5891,7 +5885,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( p->iDataCur = pTabItem->iCursor; p->iIdxCur = iIdxCur; p->iIdxCol = i; - p->bMaybeNullRow = bMaybeNullRow; + p->bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){ p->aff = pIdx->zColAff[i]; } diff --git a/test/default.test b/test/default.test index 06a180c1d..de67f643b 100644 --- a/test/default.test +++ b/test/default.test @@ -136,5 +136,9 @@ do_catchsql_test default-5.1 { CREATE TABLE t1 (a,b DEFAULT(random() NOTNULL IN (RAISE(IGNORE),2,3))); INSERT INTO t1(a) VALUES(1); } {1 {RAISE() may only be used within a trigger-program}} +do_catchsql_test default-5.2 { + CREATE TABLE Table0 (Col0 DEFAULT (RAISE(IGNORE) ) ) ; + INSERT INTO Table0 DEFAULT VALUES ; +} {1 {RAISE() may only be used within a trigger-program}} finish_test diff --git a/test/func.test b/test/func.test index c7b8f7235..a3ecd4e30 100644 --- a/test/func.test +++ b/test/func.test @@ -1561,4 +1561,25 @@ do_execsql_test func-38.100 { WITH t1(x) AS (VALUES(-9e+999)) SELECT sum(x), avg(x), total(x) FROM t1; } {Inf Inf Inf -Inf -Inf -Inf} +# 2024-03-21 https://sqlite.org/forum/forumpost/23b8688ef4 +# Another problem with Kahan-Babushka-Neumaier summation and +# infinities. +# +do_execsql_test func-39.101 { + WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<1) + SELECT sum(1.7976931348623157e308), + avg(1.7976931348623157e308), + total(1.7976931348623157e308) + FROM c; +} {1.79769313486232e+308 1.79769313486232e+308 1.79769313486232e+308} +for {set i 2} {$i<10} {incr i} { + do_execsql_test func-39.[expr {10*$i+100}] { + WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<$i) + SELECT sum(1.7976931348623157e308), + avg(1.7976931348623157e308), + total(1.7976931348623157e308) + FROM c; + } {Inf Inf Inf} +} + finish_test diff --git a/test/fuzzinvariants.c b/test/fuzzinvariants.c index 5d473f19f..00b2c1174 100644 --- a/test/fuzzinvariants.c +++ b/test/fuzzinvariants.c @@ -296,6 +296,14 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ ** WHERE clause. */ continue; } +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( sqlite3_strlike("%rowid%",zColName,0)==0 + || sqlite3_strlike("%oid%",zColName,0)==0 + ){ + /* ROWID values are unreliable if SQLITE_ALLOW_ROWID_IN_VIEW is used */ + continue; + } +#endif for(j=0; j0 AND 0%y; -} {1 {no such column: rowid}} +} {1 {ambiguous column name: rowid}} } reset_db diff --git a/test/vacuum-into.test b/test/vacuum-into.test index 698d65f54..d559b7fb3 100644 --- a/test/vacuum-into.test +++ b/test/vacuum-into.test @@ -26,13 +26,36 @@ ifcapable {!vacuum} { forcedelete out.db do_execsql_test vacuum-into-100 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + CREATE TABLE t1( + a INTEGER PRIMARY KEY, + b ANY, + c INT AS (b+1), --- See "2024-04-09" block + CHECK( typeof(b)!='integer' OR b>a-5 ) --- comment below + ); WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) INSERT INTO t1(a,b) SELECT x, randomblob(600) FROM c; CREATE INDEX t1b ON t1(b); DELETE FROM t1 WHERE a%2; SELECT count(*), sum(a), sum(length(b)) FROM t1; } {50 2550 30000} + +# Update 2024-04-09 for forum post eec177d68fe7fa2c. +# +# VACUUM INTO is sensitive to tables holding both generated columns +# and CHECK constraints. +# +# CHECK constraints are ignored for read-only databases in order to save +# memory (see check-in 34ddf02d3d21151b on 2014-05-21). But the xfer +# optimization normally only works if CHECK constraints match between the +# source and destination tables. So the xfer optimization was not +# working for VACUUM INTO when the source was a read-only database and the +# table held CHECK constraints. But if the table has generated columns, +# then the xfer optimization is required or else VACUUM will raise an +# error. +# +# Fix this by ignoring CHECK constraints when determining whether or not +# the xfer optimization can run while doing VACUUM. + do_execsql_test vacuum-into-110 { VACUUM main INTO 'out.db'; } {} @@ -88,11 +111,21 @@ do_catchsql_test vacuum-into-420 { # The ability to VACUUM INTO a read-only database db close +if {$tcl_platform(platform)=="windows"} { + file attributes test.db -readonly 1 +} else { + file attributes test.db -permissions 292 ;# 292 == 0444 +} sqlite3 db test.db -readonly 1 forcedelete test.db2 do_execsql_test vacuum-into-500 { VACUUM INTO 'test.db2'; } +if {$tcl_platform(platform)=="windows"} { + file attributes test.db -readonly 0 +} else { + file attributes test.db -permissions 420 ;# 420 = 0644 +} sqlite3 db2 test.db2 do_test vacuum-into-510 { db2 eval {SELECT name FROM sqlite_master ORDER BY 1} diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl index 9e425c0fd..135164e3d 100755 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -105,6 +105,7 @@ set boolean_defnnz_options { set boolean_defnil_options { SQLITE_32BIT_ROWID SQLITE_4_BYTE_ALIGNED_MALLOC + SQLITE_ALLOW_ROWID_IN_VIEW SQLITE_ALLOW_URI_AUTHORITY SQLITE_BUG_COMPATIBLE_20160819 SQLITE_CASE_SENSITIVE_LIKE From 2d6941f26daa3d270bd7daad36dfba5bd6f3d12f Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 16 Apr 2024 13:33:08 -0400 Subject: [PATCH 134/340] update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a170879e..01cdf06f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. -## [4.5.7] - (TBD - [4.5.7 changes]) +## [4.5.7] - (April 2024 - [4.5.7 changes]) +- Updates baseline to upstream SQLite 3.45.3 +- Adds "device" logging and profile target using os_log for Apple (and logcat on Android) +- Fixes issues compiling with SQLITE_OMIT_LOG +- fixes malformed man page caused by old merge conflict +- Updates podspec for current Xcode versions, improved Swift support, and Privacy Manifest ## [4.5.6] - (January 2024 - [4.5.6 changes]) - Updates baseline to upstream SQLite 3.44.2 From 832e65896006e87719c1f6498b8f431df2d602b1 Mon Sep 17 00:00:00 2001 From: Micah Moore Date: Wed, 24 Apr 2024 11:08:56 -0400 Subject: [PATCH 135/340] Moves _SQLITE3_H_=1 _FTS5_H=1 _SQLITE3RTREE_H_=1 for podspec to user_target_xcconfig as we only want that applied to the consuming project. --- SQLCipher.podspec.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index e3ffb5cef..c30b8708d 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -52,8 +52,11 @@ "resource_bundles": {"SQLCipher": ["sqlcipher-resources/PrivacyInfo.xcprivacy"]}, "xcconfig": { "HEADER_SEARCH_PATHS": "$(PODS_ROOT)/SQLCipher", - "GCC_PREPROCESSOR_DEFINITIONS": "$(inherited) SQLITE_HAS_CODEC=1 _SQLITE3_H_=1 _FTS5_H=1 _SQLITE3RTREE_H_=1", + "GCC_PREPROCESSOR_DEFINITIONS": "SQLITE_HAS_CODEC=1", "OTHER_CFLAGS": "$(inherited) -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLITE_SOUNDEX -DSQLITE_THREADSAFE -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_STAT3 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_LOAD_EXTENSION -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS4_UNICODE61 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLCIPHER_CRYPTO_CC -DHAVE_USLEEP=1 -DSQLITE_MAX_VARIABLE_NUMBER=99999" + }, + "user_target_xcconfig": { + "GCC_PREPROCESSOR_DEFINITIONS": "_SQLITE3_H_=1 _FTS5_H=1 _SQLITE3RTREE_H_=1" } }, { From 840c42ff68df0f43bbef8b9380dd8533dd248ce3 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 30 Apr 2024 10:38:05 -0400 Subject: [PATCH 136/340] bump version to 4.5.8 --- CHANGELOG.md | 4 ++++ SQLCipher.podspec.json | 4 ++-- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01cdf06f7..d01b384ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # SQLCipher Change Log All notable changes to this project will be documented in this file. +## [unreleased] - (? 2024 - [unreleased changes]) + ## [4.5.7] - (April 2024 - [4.5.7 changes]) - Updates baseline to upstream SQLite 3.45.3 - Adds "device" logging and profile target using os_log for Apple (and logcat on Android) @@ -239,6 +241,8 @@ All notable changes to this project will be documented in this file. ### Security - Change KDF iteration length from 4,000 to 64,000 +[unreleased]: https://github.com/sqlcipher/sqlcipher/tree/prerelease +[unreleased changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.7...prerelease [4.5.7]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.7 [4.5.7 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.6...v4.5.7 [4.5.6]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.6 diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index e3ffb5cef..3ee4a4f1a 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.7" + "tag": "v4.5.8" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.7", + "version": "4.5.8", "subspecs": [ { "compiler_flags": [ diff --git a/src/crypto.h b/src/crypto.h index cdd973caa..54cd01bfd 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -89,7 +89,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.7 +#define CIPHER_VERSION_NUMBER 4.5.8 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index cc70e2ab1..dcf45ad43 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.7 community}} +} {{4.5.8 community}} db close file delete -force test.db From 1205c8595dce53a8d91ee8113a66ca37e8e28890 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 1 May 2024 15:44:49 -0400 Subject: [PATCH 137/340] bump version to 4.5.9 --- SQLCipher.podspec.json | 4 ++-- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 3ee4a4f1a..4f67842cf 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.8" + "tag": "v4.5.9" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.8", + "version": "4.5.9", "subspecs": [ { "compiler_flags": [ diff --git a/src/crypto.h b/src/crypto.h index 54cd01bfd..122e7d70a 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -89,7 +89,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.8 +#define CIPHER_VERSION_NUMBER 4.5.9 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index dcf45ad43..a2f920d65 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.8 community}} +} {{4.5.9 community}} db close file delete -force test.db From 35b074f5b887871428b0e4740779427eb779d849 Mon Sep 17 00:00:00 2001 From: Micah Moore Date: Thu, 2 May 2024 18:47:07 -0400 Subject: [PATCH 138/340] Removes empty NSPrivacyCollectedDataType from PrivacyInfo.xcprivacy. --- sqlcipher-resources/PrivacyInfo.xcprivacy | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/sqlcipher-resources/PrivacyInfo.xcprivacy b/sqlcipher-resources/PrivacyInfo.xcprivacy index ce463c727..60b27a68c 100644 --- a/sqlcipher-resources/PrivacyInfo.xcprivacy +++ b/sqlcipher-resources/PrivacyInfo.xcprivacy @@ -3,20 +3,7 @@ NSPrivacyCollectedDataTypes - - - NSPrivacyCollectedDataType - - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - - - - + NSPrivacyAccessedAPITypes From e991e64acf884e2c926c84f5843fad49c348ad7e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 6 May 2024 14:29:43 -0400 Subject: [PATCH 139/340] reduce level for autovacuum short read logging --- src/crypto_impl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 9dffb5f9b..6d057cd91 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1108,7 +1108,7 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int resulted from a short read (i.e. sqlite attempted to pull a page after the end of the file. these short read failures must be ignored for autovaccum mode to work so wipe the output buffer and return SQLITE_OK to skip the decryption step. */ - sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlcipher_page_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK", pgno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_page_cipher: zeroed page (short read) for pgno %d with autovacuum enabled, returning SQLITE_OK", pgno); sqlcipher_memset(out, 0, page_sz); return SQLITE_OK; } else { From 5601211fe9f1fe19b848fe8d03d0e2fc8193b126 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 6 May 2024 15:00:28 -0400 Subject: [PATCH 140/340] establish default sqlcipher log level and target upon first activation --- src/crypto_impl.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 6d057cd91..e23aca860 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -81,6 +81,7 @@ static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; static FILE* sqlcipher_log_file = NULL; static volatile int sqlcipher_log_device = 0; static volatile unsigned int sqlcipher_log_level = SQLCIPHER_LOG_NONE; +static volatile int sqlcipher_log_set = 0; sqlite3_mutex* sqlcipher_mutex(int mutex) { if(mutex < 0 || mutex >= SQLCIPHER_MUTEX_COUNT) return NULL; @@ -203,6 +204,19 @@ void sqlcipher_activate() { for(i = 0; i < SQLCIPHER_MUTEX_COUNT; i++) { sqlcipher_static_mutex[i] = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); } +#ifndef SQLCIPHER_OMIT_DEFAULT_LOGGING + /* when sqlcipher is first activated, set a default log target and level of WARN. Use the "device log" + for android (logcat) or apple (console). Use stderr on all other platforms. */ + if(!sqlcipher_log_set) { + sqlcipher_log_level = SQLCIPHER_LOG_WARN; +#if defined(__ANDROID__) || defined(__APPLE_) + sqlcipher_log_device = 1; +#else + sqlcipher_log_file = stderr; +#endif + sqlcipher_log_set = 1; + } +#endif } /* check to see if there is a provider registered at this point From e7852e3c64c1aff1b3206932fd0332a2352a6b9a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 8 May 2024 11:45:25 -0400 Subject: [PATCH 141/340] log santization and cleanup --- src/crypto.c | 16 ++++++------ src/crypto_impl.c | 64 ++++++++++++++++++++++++--------------------- test/sqlcipher.test | 6 +++++ 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index fcb1d9d6c..4a2611725 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -768,7 +768,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_ERROR, "simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlite3Codec: simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { @@ -802,7 +802,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_ERROR, "simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlite3Codec: simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { @@ -847,7 +847,7 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { if(ctx != NULL && SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) { /* there is already a codec attached to this database, so we should not proceed */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipherCodecAttach: no codec attached to db, exiting"); + sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlcipherCodecAttach: no codec attached to db"); return SQLITE_OK; } @@ -874,7 +874,7 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { if(rc != SQLITE_OK) { /* initialization failed, do not attach potentially corrupted context */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipherCodecAttach: context initialization failed forcing error state with rc=%d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipherCodecAttach: context initialization failed, forcing error state with rc=%d", rc); /* force an error at the pager level, such that even the upstream caller ignores the return code the pager will be in an error state and will process no further operations */ sqlite3pager_error(pPager, rc); @@ -941,7 +941,7 @@ int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { int db_index = sqlcipher_find_db_index(db, zDb); return sqlcipherCodecAttach(db, db_index, pKey, nKey); } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_key_v2: no key provided"); + sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlite3_key_v2: no key provided"); return SQLITE_ERROR; } @@ -977,7 +977,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(ctx == NULL) { /* there was no codec attached to this database, so this should do nothing! */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no codec attached to db, exiting"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no codec attached to db %s: rekey can't be used on an unencrypted database", zDb); return SQLITE_MISUSE; } @@ -1006,7 +1006,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); } } else { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: error %d occurred getting page %d", rc, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: error %d occurred reading page %d", rc, pgno); } } } @@ -1027,7 +1027,7 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { } return SQLITE_OK; } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no key provided"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no key provided for db %s: rekey can't be used to decrypt an encrypted database", zDb); return SQLITE_ERROR; } diff --git a/src/crypto_impl.c b/src/crypto_impl.c index e23aca860..c8fdc5566 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -351,7 +351,8 @@ void sqlcipher_mlock(void *ptr, sqlite_uint64 sz) { sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_lock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); rc = mlock(ptr - offset, sz + offset); if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: mlock() returned %d errno=%d", rc, errno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) @@ -359,7 +360,8 @@ void sqlcipher_mlock(void *ptr, sqlite_uint64 sz) { sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_lock: calling VirtualLock(%p,%d)", ptr, sz); rc = VirtualLock(ptr, sz); if(rc==0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: VirtualLock() returned %d LastError=%d", rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -378,7 +380,8 @@ void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_unlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); rc = munlock(ptr - offset, sz + offset); if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: munlock() returned %d errno=%d", rc, errno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) @@ -386,7 +389,8 @@ void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_lock: calling VirtualUnlock(%p,%d)", ptr, sz); rc = VirtualUnlock(ptr, sz); if(!rc) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: VirtualUnlock() returned %d LastError=%d", rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -776,7 +780,7 @@ int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx) { } void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_error: ctx=%p, error=%d", ctx, error); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_error %d", error); sqlite3pager_error(ctx->pBt->pBt->pPager, error); ctx->pBt->pBt->db->errCode = error; } @@ -1122,21 +1126,21 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int resulted from a short read (i.e. sqlite attempted to pull a page after the end of the file. these short read failures must be ignored for autovaccum mode to work so wipe the output buffer and return SQLITE_OK to skip the decryption step. */ - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_page_cipher: zeroed page (short read) for pgno %d with autovacuum enabled, returning SQLITE_OK", pgno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_page_cipher: zeroed page (short read) for pgno %d with autovacuum enabled", pgno); sqlcipher_memset(out, 0, page_sz); return SQLITE_OK; } else { /* if the page memory is not all zeros, it means the there was data and a hmac on the page. since the check failed, the page was either tampered with or corrupted. wipe the output buffer, and return SQLITE_ERROR to the caller */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac check failed for pgno=%d", pgno); goto error; } } } if(ctx->provider->cipher(ctx->provider_ctx, mode, c_ctx->key, ctx->key_sz, iv_out, in, size, out) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR", mode, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: cipher operation mode=%d failed for pgno=%d", mode, pgno); goto error; }; @@ -1188,19 +1192,19 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if (c_ctx->pass_sz == ((ctx->key_sz * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, ctx->key_sz * 2)) { int n = c_ctx->pass_sz - 3; /* adjust for leading x' and tailing ' */ const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: using raw key from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, n, c_ctx->key); } else if (c_ctx->pass_sz == (((ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, (ctx->key_sz + ctx->kdf_salt_sz) * 2)) { const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: using raw key from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, (ctx->key_sz * 2), c_ctx->key); cipher_hex2bin(z + (ctx->key_sz * 2), (ctx->kdf_salt_sz * 2), ctx->kdf_salt); } else { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, ctx->key_sz, c_ctx->key) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_ctx_key_derive: error occurred from provider kdf generating encryption key"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error occurred from provider kdf generating encryption key"); return SQLITE_ERROR; } } @@ -1234,7 +1238,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->key, ctx->key_sz, ctx->hmac_kdf_salt, ctx->kdf_salt_sz, ctx->fast_kdf_iter, ctx->key_sz, c_ctx->hmac_key) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_ctx_key_derive: error occurred from provider kdf generating HMAC key"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error occurred from provider kdf generating HMAC key"); return SQLITE_ERROR; } } @@ -1242,7 +1246,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { c_ctx->derive_key = 0; return SQLITE_OK; } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_ctx_key_derive: key material is not present on the context for key derivation"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: key material is not present on the context for key derivation"); return SQLITE_ERROR; } @@ -1441,7 +1445,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* Version 4 - current, no upgrade required, so exit immediately */ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version, &journal_mode); if(rc == SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "No upgrade required - exiting"); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_codec_ctx_migrate: no upgrade required - exiting"); goto cleanup; } @@ -1449,7 +1453,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { pragma_compat = sqlite3_mprintf("PRAGMA cipher_compatibility = %d;", i); rc = sqlcipher_check_connection(db_filename, pass, pass_sz, pragma_compat, &user_version, &journal_mode); if(rc == SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "Version %d format found", i); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_migrate: version %d format found", i); goto migrate; } if(pragma_compat) sqlcipher_free(pragma_compat, sqlite3Strlen30(pragma_compat)); @@ -1457,7 +1461,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { } /* if we exit the loop normally we failed to determine the version, this is an error */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "Upgrade format not determined"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: unable to determine format version for upgrade: this may indicate custom settings were used "); goto handle_error; migrate: @@ -1474,55 +1478,55 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { rc = sqlite3_exec(db, pragma_compat, NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "set compatibility mode failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: set compatibility mode failed, error code %d", rc); goto handle_error; } /* force journal mode to DELETE, we will set it back later if different */ rc = sqlite3_exec(db, "PRAGMA journal_mode = delete;", NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "force journal mode DELETE failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: force journal mode DELETE failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, attach_command, NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "attach failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: attach failed, error code %d", rc); goto handle_error; } rc = sqlite3_key_v2(db, "migrate", pass, pass_sz); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "keying attached database failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: keying attached database failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, "SELECT sqlcipher_export('migrate');", NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_export failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: sqlcipher_export failed, error code %d", rc); goto handle_error; } #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_MIGRATE) > 0) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_ERROR, "simulated migrate failure, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlcipher_codec_ctx_migrate: simulated migrate failure, error code %d", rc); goto handle_error; } #endif rc = sqlite3_exec(db, set_user_version, NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "set user version failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: set user version failed, error code %d", rc); goto handle_error; } if( !db->autoCommit ){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "cannot migrate from within a transaction"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: cannot migrate from within a transaction"); goto handle_error; } if( db->nVdbeActive>1 ){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "cannot migrate - SQL statements in progress"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: cannot migrate - SQL statements in progress"); goto handle_error; } @@ -1560,13 +1564,13 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { if(!MoveFileExW(w_migrated_db_filename, w_db_filename, MOVEFILE_REPLACE_EXISTING)) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_ERROR, "error occurred while renaming %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %d", rc); goto handle_error; } #else sqlcipher_log(SQLCIPHER_LOG_DEBUG, "performing POSIX rename"); if ((rc = rename(migrated_db_filename, db_filename)) != 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "error occurred while renaming %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %d", rc); goto handle_error; } #endif @@ -1598,12 +1602,12 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { goto cleanup; handle_error: - sqlcipher_log(SQLCIPHER_LOG_ERROR, "An error occurred attempting to migrate the database - last error %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: an error occurred attempting to migrate the database - last error %d", rc); cleanup: if(migrated_db_filename) { int del_rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "deleted migration database: %d", del_rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_migrate: deleted migration database: %d", del_rc); } if(pass) sqlcipher_free(pass, pass_sz); diff --git a/test/sqlcipher.test b/test/sqlcipher.test index 845bc23f2..c30d30c8b 100644 --- a/test/sqlcipher.test +++ b/test/sqlcipher.test @@ -41,6 +41,12 @@ source $testdir/permutations.test set pretests "" +sqlite_orig db :memory: +execsql { + PRAGMA cipher_log_level = NONE; +} +db close + test_suite "sqlcipher" -prefix "" -description { Runs SQLCipher tests } -files [ From dfecf0a41eda3b7bb882f191d24b08f4388ee318 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 9 May 2024 12:16:53 -0400 Subject: [PATCH 142/340] bump version to 4.6.0 --- SQLCipher.podspec.json | 4 ++-- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 4f67842cf..68fc8a6a2 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.5.9" + "tag": "v4.6.0" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.5.9", + "version": "4.6.0", "subspecs": [ { "compiler_flags": [ diff --git a/src/crypto.h b/src/crypto.h index 122e7d70a..0c3eea8ca 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -89,7 +89,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.5.9 +#define CIPHER_VERSION_NUMBER 4.6.0 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index a2f920d65..f9e053db8 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.5.9 community}} +} {{4.6.0 community}} db close file delete -force test.db From 4d9f4980c5e79b4cc5d47877aa1115aed560879c Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 15 May 2024 10:49:09 -0400 Subject: [PATCH 143/340] update changelog --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01b384ea..2e7c7dc9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. ## [unreleased] - (? 2024 - [unreleased changes]) +## [4.6.0] - (May 2024 - [4.6.0 changes]) +- Sets default log level to WARN +- Sends default log output to: logcat for Android; Console for iOS and macOS; and stderr for all other platforms +- General improvements to log level assignments, output, and sanitization +- Fixes Apple Privacy Manifest by removing empty NSPrivacyCollectedDataType from PrivacyInfo.xcprivacy +- Moves Swift support defines for podspec user_target_xcconfig so they only apply to the consuming project + ## [4.5.7] - (April 2024 - [4.5.7 changes]) - Updates baseline to upstream SQLite 3.45.3 - Adds "device" logging and profile target using os_log for Apple (and logcat on Android) @@ -242,7 +249,9 @@ All notable changes to this project will be documented in this file. - Change KDF iteration length from 4,000 to 64,000 [unreleased]: https://github.com/sqlcipher/sqlcipher/tree/prerelease -[unreleased changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.7...prerelease +[unreleased changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.6.0...prerelease +[4.6.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.6.0 +[4.6.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.7...v4.6.0 [4.5.7]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.7 [4.5.7 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.6...v4.5.7 [4.5.6]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.6 From 148c2bfd665ad3453b4c941fa43feafd79462bfd Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 May 2024 13:32:48 -0400 Subject: [PATCH 144/340] skip uneccesary sqlcipher_free calls --- src/crypto_impl.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index c8fdc5566..fe4089b72 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -468,10 +468,10 @@ static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) { static void sqlcipher_cipher_ctx_free(codec_ctx* ctx, cipher_ctx **iCtx) { cipher_ctx *c_ctx = *iCtx; sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_free: iCtx=%p", iCtx); - sqlcipher_free(c_ctx->key, ctx->key_sz); - sqlcipher_free(c_ctx->hmac_key, ctx->key_sz); - sqlcipher_free(c_ctx->pass, c_ctx->pass_sz); - sqlcipher_free(c_ctx->keyspec, ctx->keyspec_sz); + if(c_ctx->key) sqlcipher_free(c_ctx->key, ctx->key_sz); + if(c_ctx->hmac_key) sqlcipher_free(c_ctx->hmac_key, ctx->key_sz); + if(c_ctx->pass) sqlcipher_free(c_ctx->pass, c_ctx->pass_sz); + if(c_ctx->keyspec) sqlcipher_free(c_ctx->keyspec, ctx->keyspec_sz); sqlcipher_free(c_ctx, sizeof(cipher_ctx)); } @@ -542,8 +542,8 @@ static int sqlcipher_cipher_ctx_copy(codec_ctx *ctx, cipher_ctx *target, cipher_ void *hmac_key = target->hmac_key; sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_copy: target=%p, source=%p", target, source); - sqlcipher_free(target->pass, target->pass_sz); - sqlcipher_free(target->keyspec, ctx->keyspec_sz); + if(target->pass) sqlcipher_free(target->pass, target->pass_sz); + if(target->keyspec) sqlcipher_free(target->keyspec, ctx->keyspec_sz); memcpy(target, source, sizeof(cipher_ctx)); target->key = key; /* restore pointer to previously allocated key data */ @@ -573,7 +573,7 @@ static int sqlcipher_cipher_ctx_copy(codec_ctx *ctx, cipher_ctx *target, cipher_ */ static int sqlcipher_cipher_ctx_set_keyspec(codec_ctx *ctx, cipher_ctx *c_ctx, const unsigned char *key) { /* free, zero existing pointers and size */ - sqlcipher_free(c_ctx->keyspec, ctx->keyspec_sz); + if(c_ctx->keyspec) sqlcipher_free(c_ctx->keyspec, ctx->keyspec_sz); c_ctx->keyspec = NULL; c_ctx->keyspec = sqlcipher_malloc(ctx->keyspec_sz); @@ -613,7 +613,7 @@ static void sqlcipher_set_derive_key(codec_ctx *ctx, int derive) { */ static int sqlcipher_cipher_ctx_set_pass(cipher_ctx *ctx, const void *zKey, int nKey) { /* free, zero existing pointers and size */ - sqlcipher_free(ctx->pass, ctx->pass_sz); + if(ctx->pass) sqlcipher_free(ctx->pass, ctx->pass_sz); ctx->pass = NULL; ctx->pass_sz = 0; @@ -846,7 +846,7 @@ int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { return SQLITE_ERROR; } /* attempt to free the existing page buffer */ - sqlcipher_free(ctx->buffer,ctx->page_sz); + if(ctx->buffer) sqlcipher_free(ctx->buffer,ctx->page_sz); ctx->page_sz = size; /* pre-allocate a page buffer of PageSize bytes. This will @@ -1025,12 +1025,14 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { codec_ctx *ctx = *iCtx; sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_ctx_free: iCtx=%p", iCtx); - sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz); - sqlcipher_free(ctx->hmac_kdf_salt, ctx->kdf_salt_sz); - sqlcipher_free(ctx->buffer, ctx->page_sz); + if(ctx->kdf_salt) sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz); + if(ctx->hmac_kdf_salt) sqlcipher_free(ctx->hmac_kdf_salt, ctx->kdf_salt_sz); + if(ctx->buffer) sqlcipher_free(ctx->buffer, ctx->page_sz); - ctx->provider->ctx_free(&ctx->provider_ctx); - sqlcipher_free(ctx->provider, sizeof(sqlcipher_provider)); + if(ctx->provider) { + ctx->provider->ctx_free(&ctx->provider_ctx); + sqlcipher_free(ctx->provider, sizeof(sqlcipher_provider)); + } sqlcipher_cipher_ctx_free(ctx, &ctx->read_ctx); sqlcipher_cipher_ctx_free(ctx, &ctx->write_ctx); From 033cc64f7a7df3ef72a10cfe54737cc6ccf34f68 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 May 2024 13:36:18 -0400 Subject: [PATCH 145/340] correct function name in sqlcipher_mlock log output --- src/crypto_impl.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index fe4089b72..1c25ed3d6 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -348,20 +348,20 @@ void sqlcipher_mlock(void *ptr, sqlite_uint64 sz) { if(ptr == NULL || sz == 0) return; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_lock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mlock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); rc = mlock(ptr - offset, sz + offset); if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: mlock() returned %d errno=%d", rc, errno); - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mlock: mlock() returned %d errno=%d", rc, errno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mlock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) int rc; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_lock: calling VirtualLock(%p,%d)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mlock: calling VirtualLock(%p,%d)", ptr, sz); rc = VirtualLock(ptr, sz); if(rc==0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_lock: VirtualLock() returned %d LastError=%d", rc, GetLastError()); - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mlock: VirtualLock() returned %d LastError=%d", rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mlock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif From 569876dd07d402cb9367987229fcf9c49c815ed8 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 17 May 2024 13:44:07 -0400 Subject: [PATCH 146/340] downgrade memory unlock failures to info level and fix function name in log output --- src/crypto_impl.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 1c25ed3d6..d1485eeff 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -377,20 +377,25 @@ void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { if(ptr == NULL || sz == 0) return; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_unlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_munlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); rc = munlock(ptr - offset, sz + offset); if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: munlock() returned %d errno=%d", rc, errno); - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_munlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) int rc; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_lock: calling VirtualUnlock(%p,%d)", ptr, sz); + + if(ptr == NULL || sz == 0) return; + + sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_munlock: calling VirtualUnlock(%p,%d)", ptr, sz); rc = VirtualUnlock(ptr, sz); + + /* because memory allocations may be made from the same individual page, it is possible for VirtualUnlock to be called + * multiple times for the same page. Subsequent calls will return an error, but this can be safely ignored (i.e. because + * the previous call for that page unlocked the memory already). Log an info level event only in that case. */ if(!rc) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mem_unlock: VirtualUnlock() returned %d LastError=%d", rc, GetLastError()); - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_munlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif From 91520d41740c598bb9c8a7515cf057d75a804ada Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 23 May 2024 13:34:01 -0400 Subject: [PATCH 147/340] version bump to 4.6.1 --- SQLCipher.podspec.json | 4 ++-- src/crypto.h | 2 +- test/sqlcipher-pragmas.test | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 1247c8293..fe924726a 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -15,10 +15,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.6.0" + "tag": "v4.6.1" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.6.0", + "version": "4.6.1", "subspecs": [ { "compiler_flags": [ diff --git a/src/crypto.h b/src/crypto.h index 0c3eea8ca..d33c0f6cc 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -89,7 +89,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.6.0 +#define CIPHER_VERSION_NUMBER 4.6.1 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index f9e053db8..bf3e1a1af 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.6.0 community}} +} {{4.6.1 community}} db close file delete -force test.db From 58e90d6b29853cdd553b3e41432d3fb8400eee94 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 23 May 2024 13:52:30 -0400 Subject: [PATCH 148/340] Snapshot of upstream SQLite 3.46.0 --- Makefile.in | 70 +- Makefile.msc | 76 +- VERSION | 2 +- art/icon-243x273.gif | Bin 0 -> 24689 bytes art/icon-80x90.gif | Bin 0 -> 3392 bytes autoconf/Makefile.msc | 7 + autoconf/tea/configure.ac | 2 +- configure | 18 +- doc/lemon.html | 18 + doc/testrunner.md | 38 +- ext/expert/expert1.test | 1 + ext/expert/sqlite3expert.c | 2 +- ext/fts3/fts3.c | 12 +- ext/fts3/fts3_snippet.c | 2 +- ext/fts3/fts3_write.c | 7 +- ext/fts5/fts5.h | 4 +- ext/fts5/fts5_main.c | 3 +- ext/fts5/test/fts5fault8.test | 14 +- ext/icu/icu.c | 37 +- ext/intck/intck1.test | 332 +++++++ ext/intck/intck2.test | 177 ++++ ext/intck/intck_common.tcl | 66 ++ ext/intck/intckbusy.test | 49 + ext/intck/intckcorrupt.test | 236 +++++ ext/intck/intckfault.test | 42 + ext/intck/sqlite3intck.c | 940 ++++++++++++++++++ ext/intck/sqlite3intck.h | 171 ++++ ext/intck/test_intck.c | 238 +++++ ext/misc/cksumvfs.c | 6 +- ext/misc/fileio.c | 24 +- ext/misc/series.c | 120 ++- ext/misc/sqlar.c | 7 +- ext/misc/vtablog.c | 194 +++- ext/rbu/sqlite3rbu.c | 4 +- ext/recover/dbdata.c | 200 ++-- ext/recover/recover1.test | 15 +- ext/recover/recovercorrupt3.test | 549 ++++++++++ ext/recover/recovercorrupt4.test | 64 ++ ext/rtree/rtree.c | 6 +- ext/rtree/rtree1.test | 18 + ext/session/sessionchange.test | 101 ++ ext/session/sessionconflict.test | 76 ++ ext/session/sessionstat1.test | 4 +- ext/session/sqlite3session.c | 254 +++-- ext/session/sqlite3session.h | 24 + ext/session/test_session.c | 256 +++-- ext/userauth/user-auth.txt | 12 + ext/wasm/GNUmakefile | 87 +- ext/wasm/SQLTester/SQLTester.mjs | 12 +- ext/wasm/api/README.md | 10 +- ext/wasm/api/post-js-header.js | 6 +- ext/wasm/api/sqlite3-api-glue.js | 291 +++++- ext/wasm/api/sqlite3-api-oo1.js | 274 ++++- ext/wasm/api/sqlite3-api-prologue.js | 84 +- ext/wasm/api/sqlite3-api-worker1.js | 40 +- ext/wasm/api/sqlite3-opfs-async-proxy.js | 12 +- ext/wasm/api/sqlite3-vfs-helper.c-pp.js | 103 ++ ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js | 5 +- ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 69 +- ...-helper.js => sqlite3-vtab-helper.c-pp.js} | 307 +----- ext/wasm/api/sqlite3-wasm.c | 162 ++- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js | 82 +- ext/wasm/common/whwasmutil.js | 52 +- ...r.html => demo-worker1-promiser.c-pp.html} | 10 +- ...miser.js => demo-worker1-promiser.c-pp.js} | 48 +- ext/wasm/dist.make | 23 +- ext/wasm/fiddle.make | 10 +- ext/wasm/fiddle/fiddle-worker.js | 9 +- ext/wasm/fiddle/fiddle.js | 17 +- ext/wasm/index-dist.html | 2 + ext/wasm/index.html | 2 + ext/wasm/speedtest1-worker.js | 4 - ext/wasm/tester1.c-pp.js | 135 ++- main.mk | 69 +- manifest | 376 +++---- manifest.uuid | 2 +- src/alter.c | 5 + src/analyze.c | 75 +- src/btree.c | 172 +++- src/btree.h | 1 + src/btreeInt.h | 1 + src/build.c | 19 +- src/date.c | 299 +++++- src/expr.c | 202 +++- src/insert.c | 238 ++++- src/json.c | 238 ++++- src/malloc.c | 20 + src/os_unix.c | 15 +- src/parse.y | 70 +- src/pragma.c | 261 +++-- src/prepare.c | 8 +- src/printf.c | 9 +- src/resolve.c | 36 +- src/select.c | 79 +- src/shell.c.in | 207 +++- src/sqlite.h.in | 63 +- src/sqliteInt.h | 45 +- src/test_bestindex.c | 5 +- src/test_tclsh.c | 2 + src/tokenize.c | 74 +- src/treeview.c | 10 +- src/trigger.c | 68 ++ src/util.c | 38 + src/vdbe.c | 115 ++- src/vdbe.h | 2 + src/vdbeapi.c | 3 +- src/vdbeaux.c | 23 +- src/vdbemem.c | 63 +- src/vdbevtab.c | 4 +- src/vtab.c | 32 +- src/where.c | 326 +++++- src/wherecode.c | 42 +- src/whereexpr.c | 11 +- src/window.c | 2 +- test/alter2.test | 8 +- test/altertab2.test | 22 + test/altertab3.test | 50 + test/avfs.test | 1 + test/bestindex8.test | 2 +- test/bestindexC.test | 213 ++++ test/busy.test | 2 +- test/capi3.test | 2 +- test/cksumvfs.test | 33 + test/corruptC.test | 2 +- test/corruptD.test | 2 +- test/corruptL.test | 85 ++ test/cost.test | 2 +- test/date.test | 85 +- test/date4.test | 6 +- test/default.test | 4 +- test/distinctagg.test | 9 +- test/e_reindex.test | 6 +- test/eqp.test | 6 +- test/eqp2.test | 49 + test/exprfault2.test | 35 + test/fts3fault3.test | 28 + test/fts3snippet2.test | 11 + test/func2.test | 23 + test/func4.test | 35 +- test/fuzzcheck.c | 16 +- test/fuzzinvariants.c | 39 +- test/icu.test | 18 +- test/in4.test | 4 +- test/in5.test | 22 + test/in7.test | 141 +++ test/indexexpr1.test | 52 + test/json102.test | 31 + test/json106.test | 6 + test/json108.test | 45 + test/json501.test | 27 + test/lemon-test01.y | 7 +- test/literal.test | 103 ++ test/literal2.tcl | 40 + test/literal2.test | 84 ++ test/misc1.test | 2 +- test/misc5.test | 24 +- test/mmapcorrupt.test | 4 + test/orderby1.test | 2 +- test/permutations.test | 1 + test/pragma.test | 35 +- test/pragma4.test | 32 +- test/pragma6.test | 74 ++ test/pushdown.test | 113 ++- test/quote.test | 10 +- test/readonly.test | 55 + test/recover.test | 1 + test/returning1.test | 81 +- test/scanstatus2.test | 13 +- test/selectA.test | 4 +- test/sessionfuzz.c | 1 + test/shell1.test | 1 + test/shell2.test | 1 + test/shell3.test | 1 + test/shell4.test | 1 + test/shell5.test | 1 + test/shell6.test | 1 + test/shell7.test | 1 + test/shell8.test | 28 +- test/shell9.test | 1 + test/speedtest1.c | 46 + test/sqllimits1.test | 4 +- test/subquery.test | 59 ++ test/tabfunc01.test | 26 + test/testrunner.tcl | 239 ++++- test/testrunner_data.tcl | 45 +- test/tkt-8454a207b9.test | 2 +- test/tkt-bd484a090c.test | 2 +- test/upsert1.test | 24 + test/values.test | 715 +++++++++++++ test/valuesfault.test | 37 + test/view.test | 23 + test/vtabL.test | 75 ++ test/whereL.test | 45 + test/whereN.test | 103 ++ tool/lemon.c | 262 +++-- tool/lempar.c | 132 +-- tool/mkopcodeh.tcl | 9 +- tool/mksqlite3c.tcl | 27 +- tool/speed-check.sh | 3 + 199 files changed, 11223 insertions(+), 2183 deletions(-) create mode 100644 art/icon-243x273.gif create mode 100644 art/icon-80x90.gif create mode 100644 ext/intck/intck1.test create mode 100644 ext/intck/intck2.test create mode 100644 ext/intck/intck_common.tcl create mode 100644 ext/intck/intckbusy.test create mode 100644 ext/intck/intckcorrupt.test create mode 100644 ext/intck/intckfault.test create mode 100644 ext/intck/sqlite3intck.c create mode 100644 ext/intck/sqlite3intck.h create mode 100644 ext/intck/test_intck.c create mode 100644 ext/recover/recovercorrupt3.test create mode 100644 ext/recover/recovercorrupt4.test create mode 100644 ext/session/sessionchange.test create mode 100644 ext/session/sessionconflict.test create mode 100644 ext/wasm/api/sqlite3-vfs-helper.c-pp.js rename ext/wasm/api/{sqlite3-v-helper.js => sqlite3-vtab-helper.c-pp.js} (57%) rename ext/wasm/{demo-worker1-promiser.html => demo-worker1-promiser.c-pp.html} (86%) rename ext/wasm/{demo-worker1-promiser.js => demo-worker1-promiser.c-pp.js} (86%) create mode 100644 test/bestindexC.test create mode 100644 test/cksumvfs.test create mode 100644 test/eqp2.test create mode 100644 test/exprfault2.test create mode 100644 test/in7.test create mode 100644 test/json108.test create mode 100644 test/literal.test create mode 100644 test/literal2.tcl create mode 100644 test/literal2.test create mode 100644 test/pragma6.test create mode 100644 test/readonly.test create mode 100644 test/values.test create mode 100644 test/valuesfault.test create mode 100644 test/vtabL.test create mode 100644 test/whereN.test diff --git a/Makefile.in b/Makefile.in index cb894666d..c16e1c127 100644 --- a/Makefile.in +++ b/Makefile.in @@ -418,6 +418,8 @@ TESTSRC = \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/test_recover.c \ + $(TOP)/ext/intck/test_intck.c \ + $(TOP)/ext/intck/sqlite3intck.c \ $(TOP)/ext/rbu/test_rbu.c # Statically linked extensions @@ -815,7 +817,7 @@ has_tclsh85: touch .target_source sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84 - $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . @@ -826,7 +828,7 @@ sqlite3r.c: sqlite3.c sqlite3r.h has_tclsh84 cp $(TOP)/ext/recover/sqlite3recover.c tsrc/ cp $(TOP)/ext/recover/sqlite3recover.h tsrc/ cp $(TOP)/ext/recover/dbdata.c tsrc/ - $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) sqlite3ext.h: .target_source cp tsrc/sqlite3ext.h . @@ -1152,35 +1154,37 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c $(BCC) -o mkkeywordhash$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) $(TOP)/tool/mkkeywordhash.c ./mkkeywordhash$(BEXE) >keywordhash.h -# Source files that go into making shell.c -SHELL_SRC = \ - $(TOP)/src/shell.c.in \ - $(TOP)/ext/consio/console_io.c \ - $(TOP)/ext/consio/console_io.h \ - $(TOP)/ext/misc/appendvfs.c \ - $(TOP)/ext/misc/completion.c \ - $(TOP)/ext/misc/decimal.c \ - $(TOP)/ext/misc/basexx.c \ - $(TOP)/ext/misc/base64.c \ - $(TOP)/ext/misc/base85.c \ - $(TOP)/ext/misc/fileio.c \ - $(TOP)/ext/misc/ieee754.c \ - $(TOP)/ext/misc/regexp.c \ - $(TOP)/ext/misc/series.c \ - $(TOP)/ext/misc/shathree.c \ - $(TOP)/ext/misc/sqlar.c \ - $(TOP)/ext/misc/uint.c \ - $(TOP)/ext/expert/sqlite3expert.c \ - $(TOP)/ext/expert/sqlite3expert.h \ - $(TOP)/ext/misc/zipfile.c \ - $(TOP)/ext/misc/memtrace.c \ - $(TOP)/ext/misc/pcachetrace.c \ - $(TOP)/ext/recover/dbdata.c \ - $(TOP)/ext/recover/sqlite3recover.c \ - $(TOP)/ext/recover/sqlite3recover.h \ - $(TOP)/src/test_windirent.c - -shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl has_tclsh84 +# Source and header files that shell.c depends on +SHELL_DEP = \ + $(TOP)/src/shell.c.in \ + $(TOP)/ext/consio/console_io.c \ + $(TOP)/ext/consio/console_io.h \ + $(TOP)/ext/expert/sqlite3expert.c \ + $(TOP)/ext/expert/sqlite3expert.h \ + $(TOP)/ext/intck/sqlite3intck.c \ + $(TOP)/ext/intck/sqlite3intck.h \ + $(TOP)/ext/misc/appendvfs.c \ + $(TOP)/ext/misc/base64.c \ + $(TOP)/ext/misc/base85.c \ + $(TOP)/ext/misc/completion.c \ + $(TOP)/ext/misc/decimal.c \ + $(TOP)/ext/misc/fileio.c \ + $(TOP)/ext/misc/ieee754.c \ + $(TOP)/ext/misc/memtrace.c \ + $(TOP)/ext/misc/pcachetrace.c \ + $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/series.c \ + $(TOP)/ext/misc/shathree.c \ + $(TOP)/ext/misc/sqlar.c \ + $(TOP)/ext/misc/uint.c \ + $(TOP)/ext/misc/zipfile.c \ + $(TOP)/ext/recover/dbdata.c \ + $(TOP)/ext/recover/sqlite3recover.c \ + $(TOP)/ext/recover/sqlite3recover.h \ + $(TOP)/src/test_windirent.c \ + $(TOP)/src/test_windirent.h + +shell.c: $(SHELL_DEP) $(TOP)/tool/mkshellc.tcl has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c @@ -1308,9 +1312,9 @@ testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC) $(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \ -o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS) -coretestprogs: $(TESTPROGS) +coretestprogs: testfixture$(BEXE) sqlite3$(BEXE) -testprogs: coretestprogs srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE) +testprogs: $(TESTPROGS) srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE) # A very detailed test running most or all test cases fulltest: alltest fuzztest diff --git a/Makefile.msc b/Makefile.msc index 19bfe2f38..5257cee98 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -18,6 +18,13 @@ USE_AMALGAMATION = 1 !ENDIF # <> +# Optionally set EXTRA_SRC to a list of C files to append to +# the generated sqlite3.c. +# +!IFNDEF EXTRA_SRC +EXTRA_SRC = +!ENDIF + # Set this non-0 to enable full warnings (-W4, etc) when compiling. # !IFNDEF USE_FULLWARN @@ -1595,6 +1602,8 @@ TESTEXT = \ $(TOP)\ext\rtree\test_rtreedoc.c \ $(TOP)\ext\recover\sqlite3recover.c \ $(TOP)\ext\recover\test_recover.c \ + $(TOP)\ext\intck\test_intck.c \ + $(TOP)\ext\intck\sqlite3intck.c \ $(TOP)\ext\recover\dbdata.c # If use of zlib is enabled, add the "zipfile.c" source file. @@ -1913,7 +1922,7 @@ mptest: mptester.exe echo > .target_source sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-verify.exe - $(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) + $(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) $(EXTRA_SRC) sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl $(TCLSH_CMD) $(TOP)\tool\split-sqlite3c.tcl @@ -2263,39 +2272,44 @@ mkkeywordhash.exe: $(TOP)\tool\mkkeywordhash.c keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe .\mkkeywordhash.exe > keywordhash.h -# Source files that go into making shell.c -SHELL_SRC = \ - $(TOP)\src\shell.c.in \ - $(TOP)\ext\consio\console_io.c \ - $(TOP)\ext\consio\console_io.h \ - $(TOP)\ext\misc\appendvfs.c \ - $(TOP)\ext\misc\completion.c \ - $(TOP)\ext\misc\base64.c \ - $(TOP)\ext\misc\base85.c \ - $(TOP)\ext\misc\decimal.c \ - $(TOP)\ext\misc\fileio.c \ - $(TOP)\ext\misc\ieee754.c \ - $(TOP)\ext\misc\regexp.c \ - $(TOP)\ext\misc\series.c \ - $(TOP)\ext\misc\shathree.c \ - $(TOP)\ext\misc\uint.c \ - $(TOP)\ext\expert\sqlite3expert.c \ - $(TOP)\ext\expert\sqlite3expert.h \ - $(TOP)\ext\misc\memtrace.c \ - $(TOP)\ext\misc\pcachetrace.c \ - $(TOP)\ext\recover\dbdata.c \ - $(TOP)\ext\recover\sqlite3recover.c \ - $(TOP)\ext\recover\sqlite3recover.h \ - $(TOP)\src\test_windirent.c +# Source and header files that shell.c depends on +SHELL_DEP = \ + $(TOP)\src\shell.c.in \ + $(TOP)\ext\consio\console_io.c \ + $(TOP)\ext\consio\console_io.h \ + $(TOP)\ext\expert\sqlite3expert.c \ + $(TOP)\ext\expert\sqlite3expert.h \ + $(TOP)\ext\intck\sqlite3intck.c \ + $(TOP)\ext\intck\sqlite3intck.h \ + $(TOP)\ext\misc\appendvfs.c \ + $(TOP)\ext\misc\base64.c \ + $(TOP)\ext\misc\base85.c \ + $(TOP)\ext\misc\completion.c \ + $(TOP)\ext\misc\decimal.c \ + $(TOP)\ext\misc\fileio.c \ + $(TOP)\ext\misc\ieee754.c \ + $(TOP)\ext\misc\memtrace.c \ + $(TOP)\ext\misc\pcachetrace.c \ + $(TOP)\ext\misc\regexp.c \ + $(TOP)\ext\misc\series.c \ + $(TOP)\ext\misc\shathree.c \ + $(TOP)\ext\misc\sqlar.c \ + $(TOP)\ext\misc\uint.c \ + $(TOP)\ext\misc\zipfile.c \ + $(TOP)\ext\recover\dbdata.c \ + $(TOP)\ext\recover\sqlite3recover.c \ + $(TOP)\ext\recover\sqlite3recover.h \ + $(TOP)\src\test_windirent.c \ + $(TOP)\src\test_windirent.h # If use of zlib is enabled, add the "zipfile.c" source file. # !IF $(USE_ZLIB)!=0 -SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\sqlar.c -SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\zipfile.c +SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\sqlar.c +SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\zipfile.c !ENDIF -shell.c: $(SHELL_SRC) $(TOP)\tool\mkshellc.tcl +shell.c: $(SHELL_DEP) $(TOP)\tool\mkshellc.tcl $(TCLSH_CMD) $(TOP)\tool\mkshellc.tcl > shell.c zlib: @@ -2482,9 +2496,9 @@ extensiontest: testfixture.exe testloadext.dll tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe $(TOP)\tool\mktoolzip.tcl .\testfixture.exe $(TOP)\tool\mktoolzip.tcl -coretestprogs: $(TESTPROGS) +coretestprogs: testfixture.exe sqlite3.exe -testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe +testprogs: $(TESTPROGS) srcck1.exe fuzzcheck.exe sessionfuzz.exe fulltest: alltest fuzztest @@ -2539,7 +2553,7 @@ mdevtest: # Testing for a release # -releasetest: testfixture.exe fuzztest +releasetest: testfixture.exe testfixture.exe $(TOP)\test\testrunner.tcl release diff --git a/VERSION b/VERSION index 77c28ab7c..850ac8f28 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.45.3 +3.46.0 diff --git a/art/icon-243x273.gif b/art/icon-243x273.gif new file mode 100644 index 0000000000000000000000000000000000000000..e1cdfd0b5142092b56108852d86776a7da5366ee GIT binary patch literal 24689 zcmZ@;^;gu-*MD!pvP&%8uyo792XP5?=|;LyT2fIEDP z5CjBK;qm(iJagv9nKLuz&YgM1)zDJM%GncV8z>2K;XDX4>P zngF+~5Q>I~>stIb)j_xH$yM~2Rdm5Brj#lsU^N?rnl)JKE~T~&xt1xtstKE_37wi1 zy^alst~HC6Da6Q{&B#VT$3n=^T3pKjtnPqNa|CHQAatD|YVIH%cci`>Le-s4&w)kH z32Nv>Y2*ww^gtSW&{%mfnz#abkD*#mz(ziRNf6xPDWlaB5px$A>-$2cZnDmgSsk9L zyFb-`=CYuI9}f@VZUVQC$DYdI`?D(W!$Q6%xdMUtB7p_hpXKWMhnWS0>x3jZ zKaT)C?*cs^pm^2^JRgG*S`ndb@X%U@&}P=KdWNtHf#`@YIAhbg9)ENKLhG(!LS6Wg0l z9BD(K@NrP=IC<<8<*U!IS6>*C$5~UyIo?h%zn!Ivo#apG;ZNunebp=Wx}QH~fG=ZG zEOSaIV^%tQS|MjjCTB)IdmfuUs6d>)`EK?)aZd8xvRv+(bk3@JR=-N_v`X%*PQjdh z@q*6#NyEYg)8ZxTvL(}^NwcEQP9J96s^_0H&byZmY80-Tm9E&7uNW4t*i>)ass8Ht zVZ*6%&9ipHy>aJJ{l=5VuK_Jvfvr2fO}qZ>hru0N&pUVg`%i<%{|3!n`AuGV_MA9% zokW(@zNu@CEvZU;SCH1&^P+7kvVA6|zAv}6KfH4`yyHuH?|5PFP|@I2-PBywr!R!w zZ{Y(6QG?$ihmT@Mjzas6Vm}{;PyUIX_#HWU_G_++WuMabufL{UMDL6DMh#fEB^rANX z74NztQGD8&nw7adu`K_&FZ5UD^}R%^#BgX;-Ry7!j;g=|9pU6)sH=thi})J6(m7JDCJELoy!)z$LIKBEDV;M}&@R zF@s%s_h29-+2k1DO~2wR9M`+Bd<_Guo%d;?QekwjMWqU< z#XQlwWT-TQ&S+l$bDoE^*fTx~wC~l=bqaOaLOZe<%ME?GVoIeR1{Z_XFnZlIo}S8n z!P32|J|iVq)o4n`UZuXS$A0aMwxLAzeb=A}dMC|F)mnY=cl#AxDXXgWQ?DuynkGX_ z%bUJyAMH0Uzj`Fu@O}5&L4{M;d^f9}Op-cEdV!3M0ut!55=iyiV(l*aL%sDwc!kPm zxiXoKZZ5k89%;_IimNbAA3Ugcz!#q+Jt&exNyd3+*sz)z`@*n#Krm1In8RWCbJY;0 zUXgt4zt?*|hLz|gWH=?n;;TRVMuBRFZu}0eVRnGp_D$P`aQcqn+Jj|gm3(f>PHWWL z*3El$1=r1o%_v54Ig5V|L*e?bh_m;pA=Ka%s?tL6>aB!^X($5&3kd9~+pSBdY5-DW zxT8w=#bPia)w`7wbRf5#|MX@reU8ebi(P}LO8K37Mi~SoHetP{I(E`tppPe z)#ZW83AahBqQY-X5FS~f!Vq-@tMks zQi;q$T*-$Yxaw`M`IvH0`o2^fiUsu`z&scnY_|(#2JN7c- zTy1%xbEz#Sa(2+qkv-)!!ddFubaT^Gb*ljc*=b^W3Yz?{B=DX%h4xs;MG>@88UzR9 z@U>U`svK&hP}W>;@}+`P=J~@2@AAs{H3T#J5-H+QEA7IXym{lN_zynu-)uI`p0bbC z9{3gpS?(;8)9T`@GsQ1lC^xEUNB%-xdFyP~clKhb3y7hf74tbX6GZ;X&@I{R$S3 z!UQd-DT7>lFuPXE7hr7bWw;c6tNGq(-AwQCoA1H%8B|}_{F^TqCF3J0*S${8T49?X zUpZ%P{9a?`z+94HUy0wlWWx^Lxm)$BG>OW);T;+J+$fD(5VW01tC~abPPM_{nZs7D zG*I3I3UR(r+TcxZ0bW9q2|%Fvj^_D_QKom%jL?PWvCkw*V{I1h2{S+aEcyH>@2$i{c0H=&}si87gKH;z4Qo*2y z=ob|bqzTZ)94LmgWBJ84MXGP5Xgl_IoZHeNlYcJR>T_DhzWv?wfE2K>PEh!}4cwJ) zdQW|7$Zo-?jm4$ei>*!|l2wYcW-2|rLxFyKDnTqBvb)E_m?j7HrU6~tdO-lStfE&~ zz$|1(;Yxc-EK7$yxgi~Qb-Mg_Oj6~2sY2XK>N9q~VE;^f-9HbT585gCpeQT*(zaWH zvvF%Nc%8m2;9);YV-AgXAm5d5@p?LGP7?z!$zr9&5@DSY6&{B36_81sBv0ZCj=Q8( zi`m9CV)X9}C5$NO5I1iI*s*kyDD1se7MIRl9xFWz_^Q5i2)Xm%YP_;zX*4qDD0`Ib z+rXW`J%{OW?S~;7ACoRV&0k&WeQ(*$x_tf{d4BmjGUV)jAYg&ls}G3!;u{r#pRuEw zBT}tzlHEp$eUidfoSGcb`vobP7BPamBcWe`U_Jxzh}oktS`ZHkB)H~BJqB=jqaNGA zCT_s(ag<#vRLkB}9;e6w=Ky4sZ9(e2?0Tv%IQ->P{4Mq8<>rAf*XKI*c+G|YHF!9x zKO`nHif|ALj8$rqC|{B2 z(F+%54PL7gW;i2=3?Sda1!Fszi>J7}g}nZlFtHH<@~cNM6FX`D2}3H2z%OI?FQ@<% zoY3G$MFS^jVjpB^gzTEfKr}-Lg)ep^sHRttU_bY_Y0T>HZw91mq2HlaX&5K<$~t?o9DPb3?NY!J;bw%NQ^#i#o7_I(y3m*^!YFZ78r0yU%0h zR-)$sYVrWQeUwLke@y#KSXr7)D?Bhm*lxKmFyB0;>O90S$|LnWq|G9Re%5h}B^GKK zcRn4PFC5F=7@}AB{E|6BEA?*p4ZEpCn9vmf%Bxg^b`q#nr07U4-Q=J)br1tS!3LK= zeyF`)q{c(1&POzWqNrhysMT>W*=llEfU-k4aGLZA7Vrw}5?6qYGqQ;3p9z@ni>Opo(S zMRes;F!>>?qO|9I1}O%WWCR_z@ED`#3ia>`Kw}4F9hsP#vLDQFV2xh>QbBEDpue%? zPGj(P@96kwpSS(duz`#~x5PBfkU4xtOIoxMOB}srU|U6|ys#bRuZ-4#*e6N73fwf|Ue9}3Veh8^IkIq)FR7?|~OI-!e|PM!Bi*!tAf8e&haZbue?A$MGXx33@@ z2D0w`N~LU!&i2n9)(ly+jIP|KvZcU3Xv`dD%w5>Y+?C5q*Ua0TjmwV8^ZOHp3lC^G@IE9X%F`%MMMOqe`DhvcgRmWRim%5GtuqZ$z5wdkiEr$V7 zuRPLW6+_i=RsP^%+UB9gfvCnF)4Ri}e?j9(Z+@C5R$_B$u(?|US(9@4s@ky~X)*HK zDdkvi`{LB+(Ya|?McMw^`Fp<#L+0W(8w)eB54MV*W6$2iYv&~eqz++=r|hVfSMrdQ;8G)i7j|a;i{z@6zDewP^B(p3R zjx9X8a?4L{syY$P8kfrz3QByPUbrV;qT`-7OGM7pBIfb&Lf+sY9+q@RdFdBe%RIRbeKK083uOGj@d_$CoVnDTDY@4m5!@A!ft-kKm$zvX&1}0S-gf&>*X|djrVYeb zR?0QyE$EGv50lGD!($H&3X5hqP)c?+BSopyHP{0ZhX5O?1#C}kf$o%b%$Or0mH%{k z*>q1~x<*!7i&-c~3>swrbPf^gy%+kPw={_DAxFHs%6@lw%_5iZP@$^|R{9C6BR(C_ z^_Kzwnt=$SNQ6K@

    %cs;vAR!eVq7mii7mJ|csO7HSO-yQb2~*7WX&R(D%P-vqKh z;alG{ic)hpQ(CNlX+B1^rSoA~SNL#!`(EFK*if%xYtvyD#~^~;^*-vjpS7>YUI@aq z1u&98FmK2#85Pukp~^If{&(r&dXMNBhkPi&wnFXsthC({LlYzwCF_Q=6{;6uXY1kpZz|Q~1&9#QG*o|InY#|H?W`SH{GKN2f5z?4iD& z9DQ6_!#e3mFD*z7xc3`Rf=!u~seqTz*rOE?(7}V^N(8rD^(JftPkdkRX_psZB!bA4 zsCoXt2nL-kL@Kp%BI8v2~P7q7C+Js%;E;Jq{Vvf<$I@$(2!2 z-E3_%=wDM9)@-3{BXzNSY8u+39J2XbivJuqKgr73CmYx8Uyf8A9&JT^rr96;Gdw!9 zLq-nNj>Q#R`4+t0;XnZpK@>==s`6}%i~V`fA&#|?{{770f@ofvUMs5C0j9((7sbK!y~#zY;q4XkLM?O1ztMV}Id}Bt zW9G-GY!|n_H8vbpK|js&{cg-bu6_+_d%EAicrAzFT5Hp%g`q#w!+)!Wh@W%BRs)+U zB@Y@~Znhm+uE&b4H4v$8w)HVSgFdmiUorSv0M*P>#m+|r=N&OiQ~aCpSt)LI{&8_pnRO^!2qu%;$GD~Azmy=W@QzO^f7XFS7HSXsK z&!=45!!PVv?jwVrtvcE+e$H-dNThn(hL8t9t24}z)we>Dz7imx>K|dr55W}PW#CF` zP7FX#V*cP>dA`*1G5gWIyDz0~uIPA^-!tfJA|gvZA)6|YG6#({dh=C#N00UQeux|} zF0D82QT|u95E9?*`KQswYscZr_Q$IS(+*zKQrB`CDz+Byera7f*33libM3!AIND-A z2}+n8qh7t9{dv3e^UaZ|h6;pb#cMX%_lj>t5p%ny-&pS(^?+$Mt||s%KAU0w-rOF^!+S& z@x1+D{gd~RzT|PWL~D)SnqhnIcEZ6>@KNK+S%%~)o8G}I>Qx7cU(BPwq^|ABwD;Li ztxt`uGwxG9#z8Z%tK2z} zM$L6NV`-Y1B=EFN(akA#`h)Ggr>0%+CEj*~a?_5?omb4Mha>4nl;?*IN=#cx`W&tX z*0W)Ah>^f%+BNHN>Qq{N-LpMhXe0lf6mjn#%XOagWR#xy3T6PWtA}*+4m=1+gyeVq09ERv9 z-DQaE86Zj;usthh@|&(o2>HAl7~62NR{M~fj;Rcp`r&TC{y~oB>~jqOIau^=gzbkX zPaR4*R+@}`72?G}*!`MkN373%&Oc zUVom*k{J;+45E%zf-*+T;hUJVN=NGGGII~QX)APG6tadZm1E@gqH@LVBG#O%etWwK z;(lL#d)(H}YxDiq&y+{oE$$EAd8Q6Di5IcnK=Qu6IS&5MRsSb8<7gt6ApuhL5<*rB@&|EK-j{o*Z9Uw z@tf}QDY(Q_O$wYh8VpX`7=a77r8#O_|GYpjhi>e-ww|sj(~!i!PllJ*dpCxUBEKu9 z=-gmE_LOoWcPtzI{im9S_wiT96s7)tus7>18WScyxFtm{6cVbsTW2L^YgoW^s}+xT zC`l&+F&JRcH+IcF`Sviny?)Z9s}r>n%I@15$-zfVeymvGS9zyOPQ}m9MtMlSZs@h# zI=$g_A0D%+)(=z_mU7qAE81S~TNm9_sSJ~=9vP$8OtpTG6o+4ziPwDQi<$R&vFSuGQr@Ca)Ui8)YBkqt5iR>Bu_;x6&d!HDj22~K zWFGhayfZ=Q$F&RBytk+05Br)W^&UeCC47B8b8A@sZudIinzLjQ(6J3=nDSw*Na+br zO^qkB^m$pgSm!kGBnpWv>Hx5>EH`3B#fyo#Ej-G`8a}fcgohS;I>2vS49`JHt zyyB;b>j&`_0Se*~QbM{f#p-~u)21_J7H|~bp;o_ygTG2&M`;k7*6Y6<8fmWkoe_&Z zJk{qiMe&*mmf6<5G)&&1FdU%yL7uk2s;ZcKF#%*kn=oVKAe-W9aHH6W_}g^3~(RHkvj{B4rR^-szBOk)J=r91`4G?jlc z^bnp*CXf03wgg%F=TfVhcjI%y8ig08QS!cw<^tQe0mK(;z=XNpphq;ACo)-F4W{;% zx_j43tVKBvB<~-MBqI@MbR8AsqJW#>gG%eBUh+m8NeB}FOo>{#@~Ce?+}_7Uh8%#2 z9q!wfl~x9$x!2#BQ|-Hcb~g)4VXC2EbdWGXj$2vC)J+L@g51I2J|?x-G^HMU$!82? z4LC44ssqA?kK5ZFO3<+ann;?;A6Gq_4<{Do6`=ep(EhY~4JM9;>0T364$N3|UTw!LlxN^qx*}%ox$(LMa zt2g@rNUPKI zf}BjZ{aXnx9!b;e%PL}WV-VBuSlBbyi(7%8^94@w97`R`5o~Qowl&XLz1&n^)=yMA zmnIljB4bNJ85=>`td0odR2Nob`Un7mAVutVL&%AtwlNC}O0w%y0@vKjp0L~L-?&RC z*0+Vm+9JnvUd)Y0q{!wg>FvAUx1OKFL2J0@&1J~#y?U5kDNxGt82a7u9{xk2uuMIveiD3Yg$-uP={woH z$?eB!e3Ow+4@Tt#5GPF-`ZNV$C_9E!3d0-@!D^78mR{ZIT&kA3M<4)d(pBI<2V1>Bm zHjY#)iD$NppR`O>cU(YjL%TaChm_S+yTvD!r>s?PAGcT+bx*M?Yl^E@yy%4LiYW&x ztG6gY`Bkrilue_;lO97*-XKUC#xn4_eUON4@*AWk9zK_FEea38fK0T|qI1k*>6{Pg z2rvwRJ`GP^l-AcJARZN}cn-$6)^Y2SaMr3<$G64z+BG+Bt3vzqpc>va-D=6I8kylH z=sumA4)x4#EzfRk-P-|_eJUsIy1E+T`K{dkw-y`N;K3q(vCYAaBw#F*Za&%#jc35( z+2Hu?#{xP8&Z`;AXckxXKxCSmOr#hdeCRK)v!M~Bd-KGn{fUXzW6$p3nvRIq8nw;x zL19X!-dZe9v2T*K{;iZbsbz}m4$4%0Vxo~btZeEHp^u`rPQJbrlxFebhW$`8B9TmR z77WILP-yiXJm;oDSzZ*OJQ(0`?SH`+iQ|Au`Zwui-ewiouA5N}P}KGhQ=0bCN~i1# zU>JOQqNUN@$9kgUE3To_JrG!Tbw3^$ru5yethUt>_KCNtgvRwI;$#3{{@6+;z zL5{CRCUnFkN`E&$d^^mL@fpklI@0Z%+>;4IT!&el7@GQrTNyxTz46<2TuY_tQtmOA zVHh%uJh?vs>i?;PC6XGQ)?gpa{i}XknjKTZxPxOKQ3XL~6lV6Q$xp%RM1w2D6Ag=C zL)KbDixz#Z+Oe-3`YR`$6r9E87+7C0jQx5zy4Dc+Nx#2E-y&G;_P#z>JPciKz|pG3 zeL7*T3FEHSFVcZ23YaYX89`8uo9l)%?~n9!>#3fOt*VBzVG$dy1Rne9%S>uUIG$<- z50C&3(rDHSXe|o3&Yn#rjo6{1vwBPViUVFM8jBM{F%1y-8<<>{fw6a=d4;|WW*WBUI5qQczI|?t<_7O49tuo8E&D>dZ!Bpt zt^LfHyWGSpYmDlB_$_CXZ)IO7Zo)JqMrG3C-_V1YP+$D3;!%lju66=rC^Yd-i_zXF zJmSz)Y)!YqWV}0&Jr%Fcjv4CfVwr7uQ5b55coMD+oAjNU^wl#@ubs`PHPLCce6&CD zebNNCKankAMclW{DWCSuf@PY{WxSuw`ega&boN{KY#)bJmZ_C`ILyOz#u#p4lE$2Z zp-p2ikyngCvh`cj;@P9f1y>2=uGBfA+2a4~bbF%rV6x6T0EmLjEKmp~=`lJ&&7)xE z!r=(mT)yo>zVlqZnpJQ7Lb|EVK-L#yEUd45q5nyEdb~{$r`4dE)rTyYg)pp6IQ)kU z%mig}Lzuv15KW6h^onM8mljeh^CgRc5dL^5mQ0X>{0bo9HxqMFRn&`f`7k=;H$|Lt z&Ui9|**CS|*~w~+M3}KR%repPQ@o9Zh0WmNQbty|QPjc_?B4O;kt2zFqp-Q}J(j^= ztt|Y*jkcG3)nJC#!Nx`zFUojRQF&6za&R07JOipHa&y@I3tU zSF4FyLqmf_lfy84cX^%Ve_c4%e||SHb_hiXziO!DZV@uy)CQO&Jaw)3^ z60nI@d*Z1hmdtj7aoJsOnf=!Ke+LtCDV9twPPnh@CRj)FG}v1;n1&k6#Nfqr{B`d^ z=5Tv)vlzVe6yx7uypZXeh8K5eq+4d>VkmR*ROlt0M|eah@cy^8jdp`4<+{wlim&Oi zhW^}jeMh$JFF({a%+nkdn9N7?HZ#83+-ke)%w@$SxM+DkH_m8(E5U{JuG8B^mm|Fm zZ!W7Ny}AD`ziM0y*F121XlCn0Wi4%FB$8k~S0MuKcgS)jV@KgB@IXm%R)a{{$K>}C zPokr!5abv@#MHrN$>Cu9Ka4ueaK-WJ?$YKjiA67c$M}TJqaN2=OsidQ?m3OD%PhGV zt!!SuweU^C&3I{TT*EB(xG1e`qVB_KQaARC}zT$iI&&HCs zz?E$@T=O4#kLE1G|6?RDt`grLgPYCPn|5XCq4CUVcpwdM>vql}u^b>?>?XmYQ^KP! ztOzqXufoAxCS%A?ig2ikP=*ydOcKry0$ER)6{oyK3FnG@A6?; zcId|56wQ>>QA4)@cRM=wG5kwY0dQ7=ZwWP-GP*`CkG6`MRYa(2iK0gLosvHo!vvd+ zfPH9PIOuU@+jKO-+T7aQzw(mZJ3n<UGAsoug}XKtZ^L>3%ruZ zCePn2zpLHOKG)zZq^ta$>_|+)ybaIvUNKCsDHDkxLY^{s3hZ-guy& zPbzO>`1{gOGaLobuzZ_(u&)4zN@5^}4K{|v{h=oh8n`@{4!-^R>#CaV^y_qUJK}(H z>tM+*d@FmoHT!V$=OHEcgES_$IQ^s81dr{_{p{yQo)O`OM3{*tgf5Y=36YicK%CrV z%pBa44JH8o_)GrYnm6xrjS&CIqf<2J-v;4Bv;ZeQ4+f)mH&NjR+a40P!`lkHByPJ| zoIjQlf=S+XlQw^FMR8+i@you{9UshnA8Br%w#y$&`p3UuK8%I?(vm(Bkv>uywzsY= zj^A|i*KZs*Y0KB-N(sUvqP_=3MatR%5d0_{t&Y;X!GDFO?qXiB%Vtpmkf=k@L-2G$ zs2F#+IL-0U*B`ReKa_Hg&1jA#X^s_M9-EQ-{Fmcn(f;J_mWR@`SC^U3-9n%L&bEyY z!;FPNtOg?Ys+qmz2^+L)wa`>N+%E5g*a&}Fj4S{}sM$uin8`AP?;6vMYlHYJ~ zbW$@f{2zw<(}WMHJt<5LtbZ9;{QZ2aJ#a$u?}X&DG4sC_cb-kXd{%xHIQ22G^65o2 z&AAck?^P3xkIB6Y89XStv}d+;SVt)-*Mb1S0UGU*4lMYSOM7UbfQ3(VmU-bU@jaq~ zFi37AhK3AJ2O9z~L)UPF{@-7?VFuW%h0(ydvvUJsSfj?X+0^|nsega>g?t&kIwp@Y z0F%>+W3|yDgj{6_&mwPoVS@^?1ZiieDg`adOcMoCR@I>8QhUX}Lkf?gU?vM|-umIJ z$PVHS=4X}WA9%^5q=^TN`1L6m_)!uRt37xJ$y8~tx#R^#A)h@(Dafw77$L8YDR}11 zDybC8^IX6mGbwBUo}x1;{fX|r<2+Bz@FcMu*NCVc+1Gi*Aj&2nU4q9bJAHZ;6LRHl zlhpBI!T}<9zL$0`xITnS`&4_hXjNg7f=#HxX zIMU1c`9o1X*^`j*j39}*jfeMXCau{6Eau$8L*}0S!7i;dsW`*bv=7YgVC*PeXn~^p z@kF&`Z`2wHd;O1L(=TeFBd2afd1qLy9Rs1P}WG6;hEzvo#k82nEFM? zTFZPPjA&<(8NqV5XZ699?RD+&A%X>Gw1Bn+r$3$k^lB83(adU?xLKM*42KIWTd2r_ zM@5~tXa(miS02ft&Ihe{Vf>l$P+9NS(S3S@%Tx1Y4IUdD;6@hsmW{!$?RTbPXD+g8;-iyQX?`tU*Zja5=t2=D`TmI zKvj(GI4B*->^h(tR&e&3&LpQ&vh@E{S$!szJOooLIw?L-^M3Pul;#O9ugu{u;}Pgp zE-i?lxPct(8fA;I&!7?dGtf0C`i&=|gNfWLviDkWkuXh()J!cW=i95-hU6`1l9ayJ z5ec-}S@yXIjho9!RB2jlCz;HTTzQzx(-co_}w=g;I*k}}bW_OdagxgRIQgnZ=oV~n^x25< zJ77Og^yREUNS#s6Rle&vOW>ep4VXUlP?gPs6#6H4GvoIvYv50M(4>R_D+R6t#6g4r z-abgN*%3S762lG+I8!ueIp;N zMB{7VE|7UK`RqaM2h*=&W{tajEuBA0?G{DdAC;Qk4y~aNpD_O#B;OU(SK zhxz+8pX`kOEe#J;zIO^kjOrs2;$J;1hi-z%KtF&8lpe``e4u^pH9$vYT|z~Pe@}Eq z7-o5!Oo*D+W+bYb@=4h==&yZzep+tsr8U(qT1%>ngAzJ+rvUPqdh&zsAwkWPtrw;Z zI;Mx7mGU-!tt?1?I6vIGzG`CHWKT9B5Pen5UqQGe#eymQL10m0a)IYNFbxr>r*Frd znLBj*%8)RGu5d`i0{^Oa$AYq6vE>C4+Td;9jZf#7m$ZL%QXhCdrwX<+@l>1P5NUDI_ow&f8HH`eHWNJ``84?t1K#;|TQ$hU!!}C4b z95r{798eZkfJ7iGa~#*FjbtQV*+ssOx2I2P_{i_z_09MxE8X7nv*J&GA83%+muf%U zXgecGqg7PUSD~( z^c=yok+CBrX&Prl2I1hhS_C(qsr327AMc&9=eM*@Ab z;`s8k4n(etTm9^W+)QWynyUnwzH{~Jqe2JuJraa7ec5ALEa!aa z>NY?|1gO!V%jE@IFe-CMpFqmX?+%gq437gKia5xmu5m>N1U2A-*Wa}3eeS6rsFtb8 z5CLt#L!meX5vS9?Y=pP|TfW3ey30FE3&F^lG1vD=>|?!ULC9_RX3O}Ttzhoa`$ z|4T$6-qBpx(Q#ezpxAh5*H-4fqInfN`LLnUUK3jOBRpQ}d3Fg93=V?fFfLPp*a0vD z(w3M0jknW~Io10pJ#aS&8JNY`g1(dj^O5dSfjjy3&~#ipjMThn-c>Xi5APokJuesy z=jAct;g#p*q=Rrf}01-$Mkp3DQit-7hsIcLuhro^(c%+tR@slUb(>Oh^aCM>x-PqkzQN{j-F^Lr6(8D(m!GIaPZ^ODj??gt8TID+?LR$8<_HE%EsCVy*bp zUGmkH*JW<4^SC++JkG=NhH|@ZXaw@f8LbM-^2&=QsAEZ5JC<nqvl;+0LsF1Y zlX1lt;7D|{8x-5D>|AtBd*uJkhCxYAFa{iruoJQ*Vwb9uLpYSLA)A-KlLynOZnYts zy@8I%y^*k?nY&?rmv7&mPtecN(ji|nRDdzT@xPJ{z6&0<%nmynYljv`E&H%hG>mhc z1%e@w;Q$Jbf&}~dAv+2q<;>ATRzm|4loC(w%Wl%tJ*`C8oh1~AD4UI-Z6dA&gc4kA zA_}y}xon>2*{H8uQRiaY3atI|EX~%1ukmVFt?>~FV{ZV2E`D@@CU66K2W$3JLQTPy zWM>5AQaHo$s*}$U!`+U#?~C*|||yg(;+Fn_?SEsKP&{wZ4? zodtpELIDN$gHpBv+U^HceGN=kG3pIPtvx(hk8F zTUsNo!Bt-^>V$$Xm7ez&#kV=fhdRfR74vX}@_0IiG;LEd?eHaE@e#)Hc2PK}E-o~p zdo2^kxWfPgaR15lC~S}*nmhlnxuIu~y@0br8lEDI1Qy4oJB%2Sksx+>U;%h#f8WoD zu=}ZlUdEA^p_3AF@}K@q6yso_j4i&{hx0n>Wc>4`qw>bxH|KwMQeE71E`C+u^u&d#gZbv z6=qteHv=WUmNg1XZ?!KKoZBP=!Xf9st2u8!SbTl$)f^<{6X?sA)q+t^r422U{hA6^4Oxn<~ulW7xz&tFS)uG}^g2lCLqWv*@ z16Cdb>EgK&o%nqv8TXj@@}<|>xcG|t zgXIem^a&y1lJIVX$oq%Dm`$Jrt~yXe5F1>H0_zn+AW?7#!c|`-O5sYR!(|i)01h$( zQR54YdY*1-j3JJNcY$n$#ICCH=!2KZ+79!f66c23zfDwuKkx`EuZ4=QXN#Yxe>}bQ zaK-B3>0QY)7s-CJ?|teIc~3f;FFTe3%Xs!W_|gcdei9|1M4wOCO2KvIPDjZKK@;s* z0y&Hqbm1)OdYQ-|#m`tCiXx``@J$>CCEHO-@3fv0moBUdti1V?z`e2m)9)eKQ^|fh zH=efhjpV^qR=X=dohcNn`KEY64Ae1kYKvO{?P@i0UhJC9Fcja>pQY}RzbHOEpM9QdeAK(7{2*-Y` z!tJBPEYr_bh3xtv^}U085`$7bvg;=?n&?Dd%tksSZ0GYpEcSKERQ?H90`c^6Fg&CN zx2^{uC<~Odg%uUl6bw{oYE%@>tyIWDIr%@T|1L%|`~JH=DqD|vWKNW>j+Mk@ROnA| z3Qr^@WiT|p#t)yUIG(Cr`Iw|i@hty`{07>R1yLQc=IlG7FkaEOK0hzR!?6IHAI_=& zj_z?B5>G-H3_@FQKv6YGK3twfg3N4pHChW9YsCbHZ%7&FNeGkRYapDS7A zpfcl^r^a_`TweaXfBV!F>C5w{nmhTC$7@NR?GCQE13N=NBdms&MQMogEj`~NT=x8LD?cvB#I1qp%_r-%LA3{=JxQoYUx5;vERC8}DaXHnI zXZWdk)bgHyxMgb0@?=%&pIS8Euo$ZE>-2lpSLx9%+y9ebl|i6!3WG2dz%hd~MID6Q zw>iK3SiZRFh)L;g=0Ycd1{p5E8=o)HgC&93eQ{)HA+QaAXaPXMKxy~!%@%EyIVCaU zI_G=d+%@%PuOD(v-tatD3LLG%oq@PEK!NS&fgf+gcggvG&GafxW@uHlhZdLkMUeK&fiUv?RZxEC?Ke5G0}KEv$N5W#KnZM4 zXF$LKxIr~I!y24`0iZxYj=WGrW&m(R0Jwm}Tl&f9!c zTXw#ivChMLzUTYA=lqYy^3YedzPEe7zdO-0I?fM#83V!@RG$otKc(?Nb zxZAvXH+{VmzTpqP;eYp$FTKv!eBwVo(wn<$o58E|Fi5bYWgM*fjQ;=y>;QJ(!wp=( zl`alQlt2sAKoC$|5}5#^jlKB+1$Id$15|*Sv%EPp0X+};9K`(0XFBBne&R2F)8hf~ z7ysiAzPXP9AS}YvJM9O?B;#?$I7uMhH@6bowN4?%2&@2<{N@21KnR$C1XLVQG#(1X z>5;MgpR2}HsCXXafgp7IAPmCe6Tk68ejVJuW!u000|Xy|eCiAon5U1QF=Q5*5h82KYBimGPP(p$T2@*<-s0aZUB$kjE zf&qpGZ$=h1dK76=rAtMcGcx6i9XoUM@DWs~&>*XP@Zb^H)&C$@u3p7{9azwy*MeTj z5)`<0AltWY4Z`i(Hf`CjZPA7uNC;2DGGwZlL9+yiPl*y-j@Zapr3erX9^62BgM)#I ziWg(t8RF)F5j9q3yeILxl6;247bW-j534(LI^a>Xaa`*I2$1Z5Xk!}f()2B>i>-g99TgjnBwDUB?m6j$RZOY z&<{1qDoTNYV+@>fN^z`k0*^27z^$r61Q7(eyTbepLowZA&O)$?BXch?)pCxwGRN90 zPP^3V>pDU7ND+t`d{|R5pj^Gg zV^1l3m|+GW&_Dw`7BEuDM*S8AfwiN;>3{(h;uFEW$R>gbQ;tCBh)4%on81J=+94I* z*T})*)GzX=BM&|Bz)Fw_hm{l9Ib#+0I$D1XR{vM&1S+^hw!l=*#JCFj601P;_-+Om z{A3~xBMR-Wq7lZkw4w*%;L%6-T8J;D8z&NZr;J=;%mD~q2tWg4;@w#(a;%VIkS_wM zW7RLe#KSi*$6UJCFym6QE2U$Fnrbtx{f(iyS>IMcVP=V3|c)Caev{JZ#mRd#rm6y=jBHAD{g4w>LVE zGR|m$vHoK4;fgC*XCYAt+Rg|y2}CvO7y>BRASER@KnVC&z>1(oG!mG=05~wW=poKj zL9@dh^ng7<1Og9txC;iO#<>n2?Q$IS;IE_x!V!wFD;NZm3NI%nrJ=Bc2V0?->|m=w zL}(CrAj1%3AcB*WfCR5QgAe)>xZ1f)0y{Da4+t=Um6RX^Q5+t)#5FF9pypf@@IWyh z7(H>&t9nGMLm;k#LF|Rlg)qb*3TNo13%0O?E*v4|#Kgi8#t;uIoFN@ID2N*lB7H(& z!3Yd6DH6zl21-bqMjT_iy-^?}MgOTs0V;4K5~KhDmm=Ooj0b__QGfwF=wkF}Lj|GD zVGeY#U?3)FhptHFm3VOFEO8}ESIUxxxXh*IwpKpvaqpG~3+6AyfITmzFENx^2@47p zgds?P1=kAF1SpV!qa=d>Iiq>Xx6hJnCcnJTmCbb_N-|?uNP2V z5)_y=)!a?pL`zuGGnm&C!~enraR)OHQGxJ5jS6U>0~&k_i#D3T19WDT4j^D98(2(E zGD3pA1!4qPo(83*PzAbIT-wu@ZkhuwYx+yW=CYr= zv?VUbFp!0o!3QMx9eJe1OdZ%~GZtYR1|m5UY9t^9E19SWA`k(dkU*0`f&d9<^nd}Z z0j|(1xT}Xm?1eD+?RXSo#y6V!B@Wg6W&g4fmcrt>sa5nbhIJa}Pd%!MQ9Y zE!5NitHzr^0BoSUqO<`47Z{HS#A;dsUdlb;lFtYzAb=aNBerpcDP8UQrNR2Oxyk)& z4e`m`-~Q65_B5z(@c%nLsmv0mH{Iu#>M#W%Om`yI^+^lFRZ))!0Je1N0F%PC5+?CY z1(xav1PtJTXv9~(P+_Mk$#}t@7PY@Py$f%Ty1iNMGq12buy69f%HR4|m_N?%D-nF- z+kPn!cc8-(h`1TlC}ssB!xViUU;!nLL>&akfCGqEnzLRCCL$|j3b=^k!_j66bDc_E zt@EV`?w6i3w(*Sr`&Swob)LU9ZXOV9OC8g)xBN|TbLXtR^W}>UXGjcZ-X;PAxWG~+ zfItf(@Bj@sP$bDn00x|R){!k{A}WZc0v|xmjGB38^Ewq6?vP3iwmFu@z3EAXACUI&uL5h_Wd0utB(Y!jfyJBq9>18QvU#0S0W` z9qm3d7O1DCIk5SzHB9VsrQ%b24^`QEMw%=cbYP$7hLx`LG0%(b^MKw`g9kP?9&$L9 z!6KKSWGKTDK4qhc--t*k2mmsS5)A@ifCA;(n`kYff)+$X1p&~302ok!uBv=zR&v4@ zT>jh(9{-%qyq$+z)3eFt|LSc?<+A^PN*2 z+gaf?XYQ?yUC%jq;l6~o!yt;BK|b$%_=K0`f)eiM0|Qc)Kvk;4eINr6m;fd=8t+65 zaDfKAt0+6Lo3~C&q;g}#0R}kN1Q^f&0GN~ZJo};);B7rRcy0mKiTH*i@0RKi} z9se`{117~H%*vZY4u4AE1wtSID&PjzKpygsXXd5lTCJF*A^?Z$0D-UZa?Aj8%#|Le z<`OFpw1W9&=??13_!cYars6hw?s|SNua>RV7Gx0a;MZF14tNih9;YD4z~NX&1Sr5G zMkXcfgEG*gD2$s*nzhZTBLtx$x`=kB;Af zPwAX)3nOoPbW0Pzpbp3om8vk=tgpxfPq%PoL2TvKGN_dbun)w549tLaG@>+a#86n^ z0V<%(NWy<8;84cKtcC{yazg7O01n&%9tyGce&+V-VBVyn5ns(2?H~~3%^7XU*#D-& z&UEd*7|-W)&H7Nu0S7A&PVrvw<@sJI*cOE8S`8ePO)8>LdoT!=2+H5Kgb>8w45$uM zEafy*!UY(B0o1{(5Fi3xzyVyM1nB1gN+1S8-~ucz0eB$vkP(9DB@m+G3l>rLCMfw1 zOdi)L=zgyw@oez6L6m`Fy$BjhXFLe+wOrKI)DH!00kZ@B1(Wh7Qh4$fB zrO_E#Y4@bDBQH=be-GH2@Th!a3?C;Aoo_Cy5c!g?3cI4!reX|rOAD3m8~-tJE@esH zcE}CI1+^q?13)PUjDi`oj6Faua7y3=qG$jJpbI)nD-XwKr~uyTAS3^8_wWELLsA-p zE+m6a*MRK#9EFEIb|<$8)4XEP6y4iqi%4w&yHd(P)j3FrjxE>TGg9B~dDE-@|z z1W=2-Kq0w&<0Fk&VQAOJi72bclnWwST=lkT2i4vJF@?otcK;0%OdTE0ml*ed{_ z!6-bS0;baeq-6nUAOsvB1qfgV#=$!uXnN@G4!)8OPIG8{lY>mN8~+tAC$X{FI?~@B z5F261K0h&o=3qhU3MOqOKAn#4d^7Um6P3Jx?iR!e|FburfDDM>VKH)Pbfz&sb?>=?(_=wa- zI}t~XbSjKAK5J7*k?#vM$cE}bK;<$>3H1z^;4m@8Gbm%-iXs^(0PHG)1#Y4S3?Kut z5=Hb<)sqqc(LEQ125*wFgIWGz)u^ z6HRhRzu*Z3R6y_YBI_Uu%0O9SLKBP8GfDns<_hO!_41^))X23TrSALty6z?9$( z44$Ak=YXBU@B^7K5WJvD-G*5BV=@yYx#g&zwAPS^F2u#2y7E~oZV)lx{1}K12YQh0PuIayV0E z4vw`7V&uz2V0juK7w@4REWjX#QR{L5TR%2>$l)ovat^>1l~(LOzX17c_W17Q4!q!O zQML+ml}E!CSA|py!2n5d_CI~JH+Ay(j1*Onk0$vO`Ts~1V9`?Qm@Hz4Cju@20VYx; znjruTasY5305aePt`=*d$7gJ#3hQe3Mze46%?m&?Hx|STJ{3p#l}ODrE)CW4Hv_ zX#X`(eYS(u6o2jXN5wV`HAsvD6*PsGdy$lldsIlmN>UqXY*8y!Fg^&_;jM-3?hW~VncXLu~WBMgSSSNG(Kk4?CY&utwv`__GqL+Dw z^HrMn^=6k~21p>Jeun`nRwRz$0{~zh>^gdoIVxHWEXh)b3-uD;xPyH<4Qm=4A=*`C zct_b-ux;D5)pv%UfXUj~MmB%};2`|~z;Hy{q^;L0pP-*9@C$Ufc5Rw_aW-E^IkxYX zjqw&&S9@Pc7@NJijmudK3RShcRtgSy0e+hSG*u)Nz%q>cuA|z7OH>-gmJVFq?AT-hd^Tx&neTFTiL;cgdA}jL#*eQ!z4*%OfDD+7 z1gNt9iUJunyvTp>$P;my^Jq`$fch5ure(>>uY5>#d$DuXKLyWE4VzAV+OQ9sIN4AJ zPCx`qjsuFK2YT?#4YB1QSwQUo-kLkWd(`-#oH&`WrvH3yKUaPCG(B@RNuS`h6}pH0R6DVU_mliEqJ$6vqLcjoDBL zP5_h`;O*vJ|7bnl=s*n4aK*G*9W5Q$b-b);8qWh<@U$CWMLoAOJ*ew;q4SmDE1azm zS>kyH&1fqlJ(xeib!=}GUICl#(3yKn8(qm*)crJE<+ZG*!a(nwb?-8!J9x5*6SiM2 zovT`_#o55?z!imH1`dFEFfHcc#T>8zDryT1hA@v({ICC$40WJ+{U90mT+w zzuLNAa?gnt@3WlpdvhTK$$lCD@OkE>X-mt)wQ&mAPH`1pU#+3*^b~7z98>-Y-d5oA zwr|_@XaAS!#d+v!{LvFW%5N0KZN3iZV4YE7%u7FBPQP(7GF(|Hh+&%8pTJdNTJS7? zuy)q*pPtlnb9%Kmdp+t-fzlfPQ z=Viz`1phOFgjtZKL^@7{$Pn>h$GxFMiyA$OG^x_1Oq)7=TC|*zGG7jQf%3#lRXbp6 zHarQFWYvRp@L0t7! z!M+4-1diZ098D*>oq~9QZkY?({1i_Pzs!5HN^PK^)EQF{dv0a{HC<#$kpj8!@jB}9pA$onTHdjU%1=<-{ zW(DV!tM;k6Wu*ntF$EDMgit{=p)MPwJ8m!m#utj@I3XTP!emukEV>pYSf{0A2mg8| z+HsLg!scX0W_L&?tFOX-L{hl4`c)7Z#0o(|2?$WbY`+S!0<9NdL?I!CgoYq?zQ~oCCtgeDaQ<^zpx&#R(R1l88COdZx7MR`vhFhtMpY96WCb8UPf*%JvP2vG9LRx9RC5L1NlMId1W z65>FBN=x=2p}7W^M`;aLF@`g`&CzK%G1_)Ta@$SOp_*xR2YOXvHe5#)X-7EAQGg-D z0t*lz&DD-0<^~l}gh9D>U7bn?%VBKx1;mchw(e#EPh@Y3DQ0`AYY!$|+5d14p7!LE zUqmLFk$-Y0Qf;-*0okxKvMjLa+ocG`g-;F(1{H~)(7`m1KmXJ>R&2+$evm=N^1xkX zk5KF}A&KM@NVW`_y<&_1gq{hl&;I&i*jrirV#MEm{qrYT|MddR583?*fiVX7r7wI0 ze8P5$^#ccZARf?bAf4Qh1u5(y5KMZM!b%sd;Rvrbmgx$T{_+Hx?B)*h8&2<@G!PUT zt%N76106)jJKh<|g~Wl3lVm8v*o7}EeM`vvI-)=9A;g1S5g4YbumA^Wz=2Fm3OJI$ z1Sim8Nr4zd<%Gth3O!0_OWIAtP1P_8> z4I3Y(lDeR=Dp07x3=U9%CJNFhtT=)Yp74b*SaA?u>l_&M*f}PBtwPtTAlT4YK`>gY zGnVWkCo9-FH0lwQRKy|{DaghyqSBP0q@XKZ$x1JQVF^9}fdB@H%X_fH4M%W-7pmwr zEpDVjG_yc+j1R-)!N8&&Kbjx`xx}S3 z+|UF%P_B_+D2o{KV4E+@CzR$n$k_1mg^hp+TXR{FGo>W459u*AUqXrLUSgK+)a52Y z+b5O?6&n`{AqFkDWjP0ljw=*NE0#;2gd`*eGUNyf<@2BzPXCJ1l#*e75)z0&o*>dB zjR7(sP3fryl2S2zbblv>f=}G zQI0eSp$vO~!W7{5(I`0eItJ`Y6s~uww5HXCNg}G)+{)6WvJ`VXb&^usiqw!Ib)`xD zXdQG~Q?W@%d>}x}F zy3>{h^`Bo2>sbGJ7reNI4l2+9W#xI-yav@8G|&J%_j=gGM$j7?5bRrZiZ{K_0R$apf3c&Ax;}RJSD8PONj_?LX1KGk(xWlbh?gm8r;S@g^02+|(iff!% z9&q=@KMpD$G~fduFL|8a-~k1geB}x<#s?IDW0uQ&IqFb=0z5VIoGa$#=+?Q<4YLCU z)cfZ|ZwfL1@B!i$ed$N3xdL>}^r#Od9yvEqfO4dc^&TkT-B^!$G!*awd2~JM^7sI@ k#~yX9XB_QCrv@^_&i1&=eeQIxyWQ`O_q+#b9svOWJ3OQjr2qf` literal 0 HcmV?d00001 diff --git a/art/icon-80x90.gif b/art/icon-80x90.gif new file mode 100644 index 0000000000000000000000000000000000000000..ebb2390005ce48bf41e6db5b428a18ad7a9bdc5e GIT binary patch literal 3392 zcmV-G4Zre7Nk%w1VNd{C0QUd@3^F_eJ4_5bOa(SZ6Fp28J4OLLQ~*9&3OrE?HAy5r zMgcxy2SZI4LrelgRSZK^0!3U3MOy+%UJOcH2T4^EL{u6?R1!s66H8niOj{U9RRdC6 z8d6*(OIRmSTP#UX0!CsAMPmU;VhT)T14U{9OKJ;FX$nMY5=~+lO=JX7WDHVe1W{`X zQ)>@eZ532#08MfPNp%8Ja{yF(2v~O&T6HN`W;9!8Csk=SWpWu}cSdS=PH%WP7P6KJ-5q9uByDSDl!vTKD34Ou< ze$WMhy$XW92!gc~g1Z@nw-ki1C5gB-iLg6?o>qjCW{H<9GJ@ih0+0w(g%yx z43g0WlGYTI(ixZ19GTM>nb{JH)Fh6@B#y``lEy2O$Rw7@Bb(GKoYg0r*)pEjJD=4w zpV&H{+c=uaJD}G+q1iQ{+ApEsIilh+pW#NG$U~ynL!#MBrrJ)a+)Jg@QK#Hhsoq2bH@ zb-Cqvyytqo>2$j4c)##`!RLO$>3GQXeA4}U+W&Uc|7^wdWW)4_sinqt2y~u^a*@eN@n#R(n#>=S6)wb2zvdz|k#_NU2?ug3khs^Pd&hdiB z@r%*(h1C3s)BA_i_mb52mCow9+2pO?`L5XJt>pWy=>DiG5T_CX>@2HM@dak03rDV0RRB^04x9i002+`S^xkD{{Zg^ z97wRB!Gj1BDqP60p~Hs|6~5`R<;D#jZz5{k7?B#A3lc<#6ait!F^(ux7JOp^q(})c zVcKY;vgSrM6GX%e38H6-5;vM1T;+)jm7_?LDqYI7sZ*s=q%<*7BjiY!BYVQ!Qczi% zCu0^hT12+2*t2HSsx8~7ty;Ha$Ht9|#z%>fc(Y=?fQ(?8Igy5O8%(&c;lqd%D@N=s z%U!E^^Ns+)W*{pmn7n1nwvF?)&zwDf9!;9G=+2u>gZ3;sbT&62bV9~EqQyuOBRmKk zH|5T+&A@{TA5Ofu@#5R$jEJ0d1Q**dLx`Y2Qw|tQ*tTKYzC9Z@@7=+3{~u4jy!rFl zv8`gUJTF!WaJ0OX5S#ivWn)flw?>{nHvjvX$De-#7T6zp*!U8}YBqh8$}6UxpcWDB_4D7N~}F?nNj88*k(!VMq~laUUI{Xk$$^8djs@jyv9% zBaS!n2&9le21%rmtVqb*4sU2cpI#K+H%E=t_(J5BRbr{-mO}PYgGjOEH9-z>C2pS)=6hCa?V*zFLmx&XD@j2X{VKQ@~MVwk&U1MMIelzWS9}; za32_-6l$rTfchfnopxrrX{VspnIv-~wULYm6XKQTbjD0NiY~hJ|B{O?zT^TZptAxB zYplJ%noBOV);jC1y8MbOtg;r{tENhjDgp>xZ6Uz~IjI_w4FW0S1tq)UQtYqbDhqD7 z#P&MtxVC=#t+Bu2l8YS9iXiD5AyJDaOp@NW?Jn((>u$K{;v(>`xBTlbxCSGfi@U4n zz+gxcWROt~BgAyC2$kteiNFO5?D5AShb*$k3x`~?z;1-_um}^#2;m4WMg~(xQc_}z zEw|*X^Ugf??DNk*tMcV+(~59|KsPuG@zL4NVKdIP>>RbuQ|DYY)mCGDOD$1%y|vD6 zOc>@%E#K1ygOQcpG$mZO?e^Pn$1V5VwFv#}*e~sHk1NZ z`RubPoCKlY@)9ICEtFSo`Q?~ret9jLQ?5!7sfO7TO>d;&cL*WK82B8A#M1feuxmcc zER`!qTTD0z)CJmCL0pXFth;W?E5sLX{PD;qZ~XAY&;E)jrXUennipjBMg~~P{1R8d z|3N(T;D;~%_~eWKJ@l`*^1LT3Oc;}+`SOrc_VZa)eOIiDQhq6}sBelZ(BmHf2gtwS zsqcUQgkAyxn2Id8r&?TT1NL-+H+$)XY26b=DM(R@4th|4{d0u}`}e^PhOmSv?BFSA zfTlF*?H+81z|qa8s($2)$aj-lw|9`y*wPFNqFae@;a=&}zfOk8YfFcu;|H?dQLK2Y(s6YjZ&Vd?qpanfBK_`I) zY_jYE$+*WcD1ZXIy<{XDI0--LkOxUf0uqpj1V~3JP)Hzxq#q@zNI|+%mIgGWkr0AI zC-A~8R4YZ>TZp#ukO-5G)Tbu}=}8em)SnWys7FnzQ6Dh_p77*;#>l`$X~;4R@^cP( zAnGBK%GIuR^$=V2DpwaF1E)SKUtNd*>Q)vpnt2neiAV%kw>np_YPGI)MZ{e1n%6%d zLavVx!kR_`0oCmw0uX~t2-1WBH+s_xdmySI3}M$pOjfIuwXA08I$69@wzGaM#3P!} zgc3{u1k#y69PR;!2|%#~%fVM0e%BBS|K(w_gqW;ucdOgn^7gmD{ljF@FoP@5fIceQ z=MEqPF$(NpwGh$P9&+mk;QE%ggvhQTe9K+zR@VAMFYOrQiosF(yU@Vhp+HbOLIkruR&0~@@zwsW{ccIQiB z`VyiK7oM<&F-+kLci6-8)k9N70E71IbCe;`0UY3vg9$`n0w{1RgzZ3xLh#|nIL@(- zb4-XG`R1>0*v77Qw()@NW8Oo_HK9f)jKyVZm4cD&`?ZEo)Y z4`UGQduY;yYGGvpx>2_m<}h!18~or1KXx6OkY@-0uHTEL4-Sqo4V7q%;MnFiw-L_m z2p=2c2xm38N0I?*+`|KE|5}skYI5;v9dewB8!x(-r5=FM*0uk~6 zZEezWJAho?G>7@km%eJF-`oy4x5F5QtP@4rR0JlmN#F)PXr?>8<{$q$)5p&AFU)}n zkI6_EF0g~4YrW`X2RqpZ9(ShG;R|1Yf(%6907=~-2NBi!)|38qvFE(*OsD(J>Av&n z#Gwrx(5T>>His{W9r1`CxZ)3gb1(oQ1RS^rGFCo$DG}W4m{&aJ5l;jXD3bK0*A}%e zUw6*0e(ZEu0tt@Rm(p*Y>rYQT*=>*eRa2n^!t{O9=s^48Xa44#=lsB>zyeDv|30W+ zdh>B!ch%2*4o+af|30s;FE4ms_^T)Ws>A=t>!&j6aliceS3Uo+N1^)U7YH-PVEg*V z9rgalbWp$ol6QU100oMNcejUrWCsN$AQbs`51wFl(l-ZmV17AgeqI0u8fbwQXnq{H zfnGod8K{Bg7X>8{4H0+`%s>T!5C$)Ia7SlwGx!8Du!8#M1z$jdbN~i(FoHZtf*crx zJZOPJNQ6N+f;NbQ0^tm9Fa;MFVcEBRAGdnhH-IY0gySFwtRM$+;0d1Kg#@{&;fObgKuDcc}RZA*N4~FWPs>N%N5CA)90y>lc literal 0 HcmV?d00001 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 45a07a9f3..a4270fb2a 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -18,6 +18,13 @@ TOP = . +# Optionally set EXTRA_SRC to a list of C files to append to +# the generated sqlite3.c. +# +!IFNDEF EXTRA_SRC +EXTRA_SRC = +!ENDIF + # Set this non-0 to enable full warnings (-W4, etc) when compiling. # !IFNDEF USE_FULLWARN diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index ba4690a93..f188f2203 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.45.3]) +AC_INIT([sqlite],[3.46.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index 08afa13a1..f6717f96f 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.69 for sqlite 3.45.3. +# Generated by GNU Autoconf 2.69 for sqlite 3.46.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.45.3' -PACKAGE_STRING='sqlite 3.45.3' +PACKAGE_VERSION='3.46.0' +PACKAGE_STRING='sqlite 3.46.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1472,7 +1472,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.45.3 to adapt to many kinds of systems. +\`configure' configures sqlite 3.46.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.45.3:";; + short | recursive ) echo "Configuration of sqlite 3.46.0:";; esac cat <<\_ACEOF @@ -1668,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.45.3 +sqlite configure 3.46.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2087,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.45.3, which was +It was created by sqlite $as_me 3.46.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.45.3, which was +This file was extended by sqlite $as_me 3.46.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.45.3 +sqlite config.status 3.46.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/doc/lemon.html b/doc/lemon.html index 66665f46f..4147d9b31 100644 --- a/doc/lemon.html +++ b/doc/lemon.html @@ -683,6 +683,7 @@

    4.4 Special Directives

  • %endif
  • %extra_argument
  • %fallback +
  • %free
  • %if
  • %ifdef
  • %ifndef @@ -693,6 +694,7 @@

    4.4 Special Directives

  • %parse_accept
  • %parse_failure
  • %right +
  • %realloc
  • %stack_overflow
  • %stack_size
  • %start_symbol @@ -1200,6 +1202,21 @@

    4.4.25 The %wildcard directive

    the wildcard token and some other token, the other token is always used. The wildcard token is only matched if there are no alternatives.

    + +

    4.4.26 The %realloc and %free directives

    + +

    The %realloc and %free directives defines function +that allocate and free heap memory. The signatures of these functions +should be the same as the realloc() and free() functions from the standard +C library. + +

    If both of these functions are defined +then these functions are used to allocate and free +memory for supplemental parser stack space, if the initial +parse stack space is exceeded. The initial parser stack size +is specified by either %stack_size or the +-DYYSTACKDEPTH compile-time flag. +

    5.0 Error Processing

    @@ -1224,6 +1241,7 @@

    5.0 Error Processing

    first syntax error, of course, if there are no instances of the "error" non-terminal in your grammar.

    +

    6.0 History of Lemon

    diff --git a/doc/testrunner.md b/doc/testrunner.md index d420076c4..d0248573e 100644 --- a/doc/testrunner.md +++ b/doc/testrunner.md @@ -17,7 +17,6 @@
  • 3.3. Investigating Source Code Test Failures
  • 4. Extra testrunner.tcl Options -# 4. Extra testrunner.tcl Options
  • 5. Controlling CPU Core Utilization @@ -29,12 +28,18 @@ multiple jobs. It supports the following types of tests: * Tcl test scripts. - * Tests run with [make] commands. Specifically, at time of writing, - [make fuzztest], [make mptest], [make sourcetest] and [make threadtest]. + * Tests run with `make` commands. Examples: + - `make mdevtest` + - `make releasetest` + - `make sdevtest` + - `make testrunner` testrunner.tcl pipes the output of all tests and builds run into log file -**testrunner.log**, created in the cwd directory. Searching this file for -"failed" is a good way to find the output of a failed test. +**testrunner.log**, created in the current working directory. Search this +file to find details of errors. Suggested search commands: + + * `grep "^!" testrunner.log` + * `grep failed testrunner.log` testrunner.tcl also populates SQLite database **testrunner.db**. This database contains details of all tests run, running and to be run. A useful query @@ -60,7 +65,7 @@ Running: in another terminal is a good way to keep an eye on a long running test. -Sometimes testrunner.tcl uses the [testfixture] binary that it is run with +Sometimes testrunner.tcl uses the `testfixture` binary that it is run with to run tests (see "Binary Tests" below). Sometimes it builds testfixture and other binaries in specific configurations to test (see "Source Tests"). @@ -68,9 +73,9 @@ other binaries in specific configurations to test (see "Source Tests"). # 2. Binary Tests The commands described in this section all run various combinations of the Tcl -test scripts using the [testfixture] binary used to run the testrunner.tcl +test scripts using the `testfixture` binary used to run the testrunner.tcl script (i.e. they do not invoke the compiler to build new binaries, or the -[make] command to run tests that are not Tcl scripts). The procedure to run +`make` command to run tests that are not Tcl scripts). The procedure to run these tests is therefore: 1. Build the "testfixture" (or "testfixture.exe" for windows) binary using @@ -193,7 +198,7 @@ TODO: ./configure + Makefile.msc build systems. ## 3.1. Commands to Run SQLite Tests The **mdevtest** command is equivalent to running the veryquick tests and -the [make fuzztest] target once for each of two --enable-all builds - one +the `make fuzztest` target once for each of two --enable-all builds - one with debugging enabled and one without: ``` @@ -283,7 +288,7 @@ a dos \*.bat file on windows. For example: ``` The generated bash or \*.bat file script accepts a single argument - a makefile -target to build. This may be used either to run a [make] command test directly, +target to build. This may be used either to run a `make` command test directly, or else to build a testfixture (or testfixture.exe) binary with which to run a Tcl test script, as described above. @@ -310,6 +315,16 @@ would normally execute into the testrunner.log file. Example: tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest" ``` +The **--explain** option is similar to --dryrun in that it prevents testrunner.tcl +from building any binaries or running any tests. The difference is that --explain +prints on standard output a human-readable summary of all the builds and tests that +would have been run. + +``` + # Show what builds and tests would have been run + tclsh $TESTDIR/testrunner.tcl --explain mdevtest +``` + # 5. Controlling CPU Core Utilization @@ -339,6 +354,3 @@ testrunner.log and testrunner.db files: ``` $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS ``` - - - diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index 453334234..c456c30c5 100644 --- a/ext/expert/expert1.test +++ b/ext/expert/expert1.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. Specifically, # the ".recommend" command. diff --git a/ext/expert/sqlite3expert.c b/ext/expert/sqlite3expert.c index 33d62226f..276c2cc9f 100644 --- a/ext/expert/sqlite3expert.c +++ b/ext/expert/sqlite3expert.c @@ -1948,7 +1948,7 @@ sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){ sqlite3_stmt *pSql = 0; rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg, "SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'" - " AND sql NOT LIKE 'CREATE VIRTUAL %%'" + " AND sql NOT LIKE 'CREATE VIRTUAL %%' ORDER BY rowid" ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ const char *zSql = (const char*)sqlite3_column_text(pSql, 0); diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 379590188..f977aabfb 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -4014,22 +4014,24 @@ static int fts3IntegrityMethod( char **pzErr /* Write error message here */ ){ Fts3Table *p = (Fts3Table*)pVtab; - int rc; + int rc = SQLITE_OK; int bOk = 0; UNUSED_PARAMETER(isQuick); rc = sqlite3Fts3IntegrityCheck(p, &bOk); - assert( rc!=SQLITE_CORRUPT_VTAB || bOk==0 ); - if( rc!=SQLITE_OK && rc!=SQLITE_CORRUPT_VTAB ){ + assert( rc!=SQLITE_CORRUPT_VTAB ); + if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){ *pzErr = sqlite3_mprintf("unable to validate the inverted index for" " FTS%d table %s.%s: %s", p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc)); - }else if( bOk==0 ){ + if( *pzErr ) rc = SQLITE_OK; + }else if( rc==SQLITE_OK && bOk==0 ){ *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s", p->bFts4 ? 4 : 3, zSchema, zTabname); + if( *pzErr==0 ) rc = SQLITE_NOMEM; } sqlite3Fts3SegmentsClose(p); - return SQLITE_OK; + return rc; } diff --git a/ext/fts3/fts3_snippet.c b/ext/fts3/fts3_snippet.c index 227c5f00f..f6caabf4c 100644 --- a/ext/fts3/fts3_snippet.c +++ b/ext/fts3/fts3_snippet.c @@ -446,7 +446,7 @@ static void fts3SnippetDetails( } mCover |= mPhrase; - for(j=0; jnToken; j++){ + for(j=0; jnToken && jnSnippet; j++){ mHighlight |= (mPos>>j); } diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 2516a3908..5a449dec1 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -5372,7 +5372,12 @@ int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){ sqlite3_finalize(pStmt); } - *pbOk = (rc==SQLITE_OK && cksum1==cksum2); + if( rc==SQLITE_CORRUPT_VTAB ){ + rc = SQLITE_OK; + *pbOk = 0; + }else{ + *pbOk = (rc==SQLITE_OK && cksum1==cksum2); + } return rc; } diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h index 250d2ee7e..551618e71 100644 --- a/ext/fts5/fts5.h +++ b/ext/fts5/fts5.h @@ -55,8 +55,8 @@ struct Fts5PhraseIter { ** EXTENSION API FUNCTIONS ** ** xUserData(pFts): -** Return a copy of the context pointer the extension function was -** registered with. +** Return a copy of the pUserData pointer passed to the xCreateFunction() +** API when the extension function was registered. ** ** xColumnTotalSize(pFts, iCol, pnToken): ** If parameter iCol is less than zero, set output variable *pnToken diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 7c818ce74..f609f7f34 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -2979,6 +2979,7 @@ static int fts5IntegrityMethod( if( (rc&0xff)==SQLITE_CORRUPT ){ *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", zSchema, zTabname); + rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM; }else if( rc!=SQLITE_OK ){ *pzErr = sqlite3_mprintf("unable to validate the inverted index for" " FTS5 table %s.%s: %s", @@ -2986,7 +2987,7 @@ static int fts5IntegrityMethod( } sqlite3Fts5IndexCloseReader(pTab->p.pIndex); - return SQLITE_OK; + return rc; } static int fts5Init(sqlite3 *db){ diff --git a/ext/fts5/test/fts5fault8.test b/ext/fts5/test/fts5fault8.test index 5afab7754..dc060a159 100644 --- a/ext/fts5/test/fts5fault8.test +++ b/ext/fts5/test/fts5fault8.test @@ -57,7 +57,6 @@ foreach_detail_mode $testprefix { } ;# foreach_detail_mode... - do_execsql_test 4.0 { CREATE VIRTUAL TABLE x2 USING fts5(a); INSERT INTO x2(x2, rank) VALUES('crisismerge', 2); @@ -80,5 +79,18 @@ do_faultsim_test 4 -faults oom-* -prep { faultsim_test_result {0 {}} {1 SQLITE_NOMEM} } +set TMPDBERROR {1 {unable to open a temporary database file for storing temporary tables}} + +do_faultsim_test 5 -faults oom-t* -prep { + faultsim_restore_and_reopen + execsql { PRAGMA temp_store = memory } +} -body { + execsql { PRAGMA integrity_check } +} -test { + if {[string match {*error code=7*} $testresult]==0} { + faultsim_test_result {0 ok} {1 SQLITE_NOMEM} $::TMPDBERROR + } +} + finish_test diff --git a/ext/icu/icu.c b/ext/icu/icu.c index e745ab025..69867bfa8 100644 --- a/ext/icu/icu.c +++ b/ext/icu/icu.c @@ -471,7 +471,7 @@ static void icuLoadCollation( UCollator *pUCollator; /* ICU library collation object */ int rc; /* Return code from sqlite3_create_collation_x() */ - assert(nArg==2); + assert(nArg==2 || nArg==3); (void)nArg; /* Unused parameter */ zLocale = (const char *)sqlite3_value_text(apArg[0]); zName = (const char *)sqlite3_value_text(apArg[1]); @@ -486,7 +486,39 @@ static void icuLoadCollation( return; } assert(p); - + if(nArg==3){ + const char *zOption = (const char*)sqlite3_value_text(apArg[2]); + static const struct { + const char *zName; + UColAttributeValue val; + } aStrength[] = { + { "PRIMARY", UCOL_PRIMARY }, + { "SECONDARY", UCOL_SECONDARY }, + { "TERTIARY", UCOL_TERTIARY }, + { "DEFAULT", UCOL_DEFAULT_STRENGTH }, + { "QUARTERNARY", UCOL_QUATERNARY }, + { "IDENTICAL", UCOL_IDENTICAL }, + }; + unsigned int i; + for(i=0; i=sizeof(aStrength)/sizeof(aStrength[0]) ){ + sqlite3_str *pStr = sqlite3_str_new(sqlite3_context_db_handle(p)); + sqlite3_str_appendf(pStr, + "unknown collation strength \"%s\" - should be one of:", + zOption); + for(i=0; i +#include + +#include +#include + +/* +** nKeyVal: +** The number of values that make up the 'key' for the current pCheck +** statement. +** +** rc: +** Error code returned by most recent sqlite3_intck_step() or +** sqlite3_intck_unlock() call. This is set to SQLITE_DONE when +** the integrity-check operation is finished. +** +** zErr: +** If the object has entered the error state, this is the error message. +** Is freed using sqlite3_free() when the object is deleted. +** +** zTestSql: +** The value returned by the most recent call to sqlite3_intck_testsql(). +** Each call to testsql() frees the previous zTestSql value (using +** sqlite3_free()) and replaces it with the new value it will return. +*/ +struct sqlite3_intck { + sqlite3 *db; + const char *zDb; /* Copy of zDb parameter to _open() */ + char *zObj; /* Current object. Or NULL. */ + + sqlite3_stmt *pCheck; /* Current check statement */ + char *zKey; + int nKeyVal; + + char *zMessage; + int bCorruptSchema; + + int rc; /* Error code */ + char *zErr; /* Error message */ + char *zTestSql; /* Returned by sqlite3_intck_test_sql() */ +}; + + +/* +** Some error has occurred while using database p->db. Save the error message +** and error code currently held by the database handle in p->rc and p->zErr. +*/ +static void intckSaveErrmsg(sqlite3_intck *p){ + p->rc = sqlite3_errcode(p->db); + sqlite3_free(p->zErr); + p->zErr = sqlite3_mprintf("%s", sqlite3_errmsg(p->db)); +} + +/* +** If the handle passed as the first argument is already in the error state, +** then this function is a no-op (returns NULL immediately). Otherwise, if an +** error occurs within this function, it leaves an error in said handle. +** +** Otherwise, this function attempts to prepare SQL statement zSql and +** return the resulting statement handle to the user. +*/ +static sqlite3_stmt *intckPrepare(sqlite3_intck *p, const char *zSql){ + sqlite3_stmt *pRet = 0; + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0); + if( p->rc!=SQLITE_OK ){ + intckSaveErrmsg(p); + assert( pRet==0 ); + } + } + return pRet; +} + +/* +** If the handle passed as the first argument is already in the error state, +** then this function is a no-op (returns NULL immediately). Otherwise, if an +** error occurs within this function, it leaves an error in said handle. +** +** Otherwise, this function treats argument zFmt as a printf() style format +** string. It formats it according to the trailing arguments and then +** attempts to prepare the results and return the resulting prepared +** statement. +*/ +static sqlite3_stmt *intckPrepareFmt(sqlite3_intck *p, const char *zFmt, ...){ + sqlite3_stmt *pRet = 0; + va_list ap; + char *zSql = 0; + va_start(ap, zFmt); + zSql = sqlite3_vmprintf(zFmt, ap); + if( p->rc==SQLITE_OK && zSql==0 ){ + p->rc = SQLITE_NOMEM; + } + pRet = intckPrepare(p, zSql); + sqlite3_free(zSql); + va_end(ap); + return pRet; +} + +/* +** Finalize SQL statement pStmt. If an error occurs and the handle passed +** as the first argument does not already contain an error, store the +** error in the handle. +*/ +static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){ + int rc = sqlite3_finalize(pStmt); + if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){ + intckSaveErrmsg(p); + } +} + +/* +** If there is already an error in handle p, return it. Otherwise, call +** sqlite3_step() on the statement handle and return that value. +*/ +static int intckStep(sqlite3_intck *p, sqlite3_stmt *pStmt){ + if( p->rc ) return p->rc; + return sqlite3_step(pStmt); +} + +/* +** Execute SQL statement zSql. There is no way to obtain any results +** returned by the statement. This function uses the sqlite3_intck error +** code convention. +*/ +static void intckExec(sqlite3_intck *p, const char *zSql){ + sqlite3_stmt *pStmt = 0; + pStmt = intckPrepare(p, zSql); + intckStep(p, pStmt); + intckFinalize(p, pStmt); +} + +/* +** A wrapper around sqlite3_mprintf() that uses the sqlite3_intck error +** code convention. +*/ +static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){ + va_list ap; + char *zRet = 0; + va_start(ap, zFmt); + zRet = sqlite3_vmprintf(zFmt, ap); + if( p->rc==SQLITE_OK ){ + if( zRet==0 ){ + p->rc = SQLITE_NOMEM; + } + }else{ + sqlite3_free(zRet); + zRet = 0; + } + return zRet; +} + +/* +** This is used by sqlite3_intck_unlock() to save the vector key value +** required to restart the current pCheck query as a nul-terminated string +** in p->zKey. +*/ +static void intckSaveKey(sqlite3_intck *p){ + int ii; + char *zSql = 0; + sqlite3_stmt *pStmt = 0; + sqlite3_stmt *pXinfo = 0; + const char *zDir = 0; + + assert( p->pCheck ); + assert( p->zKey==0 ); + + pXinfo = intckPrepareFmt(p, + "SELECT group_concat(desc, '') FROM %Q.sqlite_schema s, " + "pragma_index_xinfo(%Q, %Q) " + "WHERE s.type='index' AND s.name=%Q", + p->zDb, p->zObj, p->zDb, p->zObj + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXinfo) ){ + zDir = (const char*)sqlite3_column_text(pXinfo, 0); + } + + if( zDir==0 ){ + /* Object is a table, not an index. This is the easy case,as there are + ** no DESC columns or NULL values in a primary key. */ + const char *zSep = "SELECT '(' || "; + for(ii=0; iinKeyVal; ii++){ + zSql = intckMprintf(p, "%z%squote(?)", zSql, zSep); + zSep = " || ', ' || "; + } + zSql = intckMprintf(p, "%z || ')'", zSql); + }else{ + + /* Object is an index. */ + assert( p->nKeyVal>1 ); + for(ii=p->nKeyVal; ii>0; ii--){ + int bLastIsDesc = zDir[ii-1]=='1'; + int bLastIsNull = sqlite3_column_type(p->pCheck, ii)==SQLITE_NULL; + const char *zLast = sqlite3_column_name(p->pCheck, ii); + char *zLhs = 0; + char *zRhs = 0; + char *zWhere = 0; + + if( bLastIsNull ){ + if( bLastIsDesc ) continue; + zWhere = intckMprintf(p, "'%s IS NOT NULL'", zLast); + }else{ + const char *zOp = bLastIsDesc ? "<" : ">"; + zWhere = intckMprintf(p, "'%s %s ' || quote(?%d)", zLast, zOp, ii); + } + + if( ii>1 ){ + const char *zLhsSep = ""; + const char *zRhsSep = ""; + int jj; + for(jj=0; jjpCheck,jj+1); + zLhs = intckMprintf(p, "%z%s%s", zLhs, zLhsSep, zAlias); + zRhs = intckMprintf(p, "%z%squote(?%d)", zRhs, zRhsSep, jj+1); + zLhsSep = ","; + zRhsSep = " || ',' || "; + } + + zWhere = intckMprintf(p, + "'(%z) IS (' || %z || ') AND ' || %z", + zLhs, zRhs, zWhere); + } + zWhere = intckMprintf(p, "'WHERE ' || %z", zWhere); + + zSql = intckMprintf(p, "%z%s(quote( %z ) )", + zSql, + (zSql==0 ? "VALUES" : ",\n "), + zWhere + ); + } + zSql = intckMprintf(p, + "WITH wc(q) AS (\n%z\n)" + "SELECT 'VALUES' || group_concat('(' || q || ')', ',\n ') FROM wc" + , zSql + ); + } + + pStmt = intckPrepare(p, zSql); + if( p->rc==SQLITE_OK ){ + for(ii=0; iinKeyVal; ii++){ + sqlite3_bind_value(pStmt, ii+1, sqlite3_column_value(p->pCheck, ii+1)); + } + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + p->zKey = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0)); + } + intckFinalize(p, pStmt); + } + + sqlite3_free(zSql); + intckFinalize(p, pXinfo); +} + +/* +** Find the next database object (table or index) to check. If successful, +** set sqlite3_intck.zObj to point to a nul-terminated buffer containing +** the object's name before returning. +*/ +static void intckFindObject(sqlite3_intck *p){ + sqlite3_stmt *pStmt = 0; + char *zPrev = p->zObj; + p->zObj = 0; + + assert( p->rc==SQLITE_OK ); + assert( p->pCheck==0 ); + + pStmt = intckPrepareFmt(p, + "WITH tables(table_name) AS (" + " SELECT name" + " FROM %Q.sqlite_schema WHERE (type='table' OR type='index') AND rootpage" + " UNION ALL " + " SELECT 'sqlite_schema'" + ")" + "SELECT table_name FROM tables " + "WHERE ?1 IS NULL OR table_name%s?1 " + "ORDER BY 1" + , p->zDb, (p->zKey ? ">=" : ">") + ); + + if( p->rc==SQLITE_OK ){ + sqlite3_bind_text(pStmt, 1, zPrev, -1, SQLITE_TRANSIENT); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + p->zObj = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0)); + } + } + intckFinalize(p, pStmt); + + /* If this is a new object, ensure the previous key value is cleared. */ + if( sqlite3_stricmp(p->zObj, zPrev) ){ + sqlite3_free(p->zKey); + p->zKey = 0; + } + + sqlite3_free(zPrev); +} + +/* +** Return the size in bytes of the first token in nul-terminated buffer z. +** For the purposes of this call, a token is either: +** +** * a quoted SQL string, +* * a contiguous series of ascii alphabet characters, or +* * any other single byte. +*/ +static int intckGetToken(const char *z){ + char c = z[0]; + int iRet = 1; + if( c=='\'' || c=='"' || c=='`' ){ + while( 1 ){ + if( z[iRet]==c ){ + iRet++; + if( z[iRet]!=c ) break; + } + iRet++; + } + } + else if( c=='[' ){ + while( z[iRet++]!=']' && z[iRet] ); + } + else if( (c>='A' && c<='Z') || (c>='a' && c<='z') ){ + while( (z[iRet]>='A' && z[iRet]<='Z') || (z[iRet]>='a' && z[iRet]<='z') ){ + iRet++; + } + } + + return iRet; +} + +/* +** Return true if argument c is an ascii whitespace character. +*/ +static int intckIsSpace(char c){ + return (c==' ' || c=='\t' || c=='\n' || c=='\r'); +} + +/* +** Argument z points to the text of a CREATE INDEX statement. This function +** identifies the part of the text that contains either the index WHERE +** clause (if iCol<0) or the iCol'th column of the index. +** +** If (iCol<0), the identified fragment does not include the "WHERE" keyword, +** only the expression that follows it. If (iCol>=0) then the identified +** fragment does not include any trailing sort-order keywords - "ASC" or +** "DESC". +** +** If the CREATE INDEX statement does not contain the requested field or +** clause, NULL is returned and (*pnByte) is set to 0. Otherwise, a pointer to +** the identified fragment is returned and output parameter (*pnByte) set +** to its size in bytes. +*/ +static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){ + int iOff = 0; + int iThisCol = 0; + int iStart = 0; + int nOpen = 0; + + const char *zRet = 0; + int nRet = 0; + + int iEndOfCol = 0; + + /* Skip forward until the first "(" token */ + while( z[iOff]!='(' ){ + iOff += intckGetToken(&z[iOff]); + if( z[iOff]=='\0' ) return 0; + } + assert( z[iOff]=='(' ); + + nOpen = 1; + iOff++; + iStart = iOff; + while( z[iOff] ){ + const char *zToken = &z[iOff]; + int nToken = 0; + + /* Check if this is the end of the current column - either a "," or ")" + ** when nOpen==1. */ + if( nOpen==1 ){ + if( z[iOff]==',' || z[iOff]==')' ){ + if( iCol==iThisCol ){ + int iEnd = iEndOfCol ? iEndOfCol : iOff; + nRet = (iEnd - iStart); + zRet = &z[iStart]; + break; + } + iStart = iOff+1; + while( intckIsSpace(z[iStart]) ) iStart++; + iThisCol++; + } + if( z[iOff]==')' ) break; + } + if( z[iOff]=='(' ) nOpen++; + if( z[iOff]==')' ) nOpen--; + nToken = intckGetToken(zToken); + + if( (nToken==3 && 0==sqlite3_strnicmp(zToken, "ASC", nToken)) + || (nToken==4 && 0==sqlite3_strnicmp(zToken, "DESC", nToken)) + ){ + iEndOfCol = iOff; + }else if( 0==intckIsSpace(zToken[0]) ){ + iEndOfCol = 0; + } + + iOff += nToken; + } + + /* iStart is now the byte offset of 1 byte passed the final ')' in the + ** CREATE INDEX statement. Try to find a WHERE clause to return. */ + while( zRet==0 && z[iOff] ){ + int n = intckGetToken(&z[iOff]); + if( n==5 && 0==sqlite3_strnicmp(&z[iOff], "where", 5) ){ + zRet = &z[iOff+5]; + nRet = (int)strlen(zRet); + } + iOff += n; + } + + /* Trim any whitespace from the start and end of the returned string. */ + if( zRet ){ + while( intckIsSpace(zRet[0]) ){ + nRet--; + zRet++; + } + while( nRet>0 && intckIsSpace(zRet[nRet-1]) ) nRet--; + } + + *pnByte = nRet; + return zRet; +} + +/* +** User-defined SQL function wrapper for intckParseCreateIndex(): +** +** SELECT parse_create_index(, ); +*/ +static void intckParseCreateIndexFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + const char *zSql = (const char*)sqlite3_value_text(apVal[0]); + int idx = sqlite3_value_int(apVal[1]); + const char *zRes = 0; + int nRes = 0; + + assert( nVal==2 ); + if( zSql ){ + zRes = intckParseCreateIndex(zSql, idx, &nRes); + } + sqlite3_result_text(pCtx, zRes, nRes, SQLITE_TRANSIENT); +} + +/* +** Return true if sqlite3_intck.db has automatic indexes enabled, false +** otherwise. +*/ +static int intckGetAutoIndex(sqlite3_intck *p){ + int bRet = 0; + sqlite3_stmt *pStmt = 0; + pStmt = intckPrepare(p, "PRAGMA automatic_index"); + if( SQLITE_ROW==intckStep(p, pStmt) ){ + bRet = sqlite3_column_int(pStmt, 0); + } + intckFinalize(p, pStmt); + return bRet; +} + +/* +** Return true if zObj is an index, or false otherwise. +*/ +static int intckIsIndex(sqlite3_intck *p, const char *zObj){ + int bRet = 0; + sqlite3_stmt *pStmt = 0; + pStmt = intckPrepareFmt(p, + "SELECT 1 FROM %Q.sqlite_schema WHERE name=%Q AND type='index'", + p->zDb, zObj + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + bRet = 1; + } + intckFinalize(p, pStmt); + return bRet; +} + +/* +** Return a pointer to a nul-terminated buffer containing the SQL statement +** used to check database object zObj (a table or index) for corruption. +** If parameter zPrev is not NULL, then it must be a string containing the +** vector key required to restart the check where it left off last time. +** If pnKeyVal is not NULL, then (*pnKeyVal) is set to the number of +** columns in the vector key value for the specified object. +** +** This function uses the sqlite3_intck error code convention. +*/ +static char *intckCheckObjectSql( + sqlite3_intck *p, /* Integrity check object */ + const char *zObj, /* Object (table or index) to scan */ + const char *zPrev, /* Restart key vector, if any */ + int *pnKeyVal /* OUT: Number of key-values for this scan */ +){ + char *zRet = 0; + sqlite3_stmt *pStmt = 0; + int bAutoIndex = 0; + int bIsIndex = 0; + + const char *zCommon = + /* Relation without_rowid also contains just one row. Column "b" is + ** set to true if the table being examined is a WITHOUT ROWID table, + ** or false otherwise. */ + ", without_rowid(b) AS (" + " SELECT EXISTS (" + " SELECT 1 FROM tabname, pragma_index_list(tab, db) AS l" + " WHERE origin='pk' " + " AND NOT EXISTS (SELECT 1 FROM sqlite_schema WHERE name=l.name)" + " )" + ")" + "" + /* Table idx_cols contains 1 row for each column in each index on the + ** table being checked. Columns are: + ** + ** idx_name: Name of the index. + ** idx_ispk: True if this index is the PK of a WITHOUT ROWID table. + ** col_name: Name of indexed column, or NULL for index on expression. + ** col_expr: Indexed expression, including COLLATE clause. + ** col_alias: Alias used for column in 'intck_wrapper' table. + */ + ", idx_cols(idx_name, idx_ispk, col_name, col_expr, col_alias) AS (" + " SELECT l.name, (l.origin=='pk' AND w.b), i.name, COALESCE((" + " SELECT parse_create_index(sql, i.seqno) FROM " + " sqlite_schema WHERE name = l.name" + " ), format('\"%w\"', i.name) || ' COLLATE ' || quote(i.coll))," + " 'c' || row_number() OVER ()" + " FROM " + " tabname t," + " without_rowid w," + " pragma_index_list(t.tab, t.db) l," + " pragma_index_xinfo(l.name) i" + " WHERE i.key" + " UNION ALL" + " SELECT '', 1, '_rowid_', '_rowid_', 'r1' FROM without_rowid WHERE b=0" + ")" + "" + "" + /* + ** For a PK declared as "PRIMARY KEY(a, b) ... WITHOUT ROWID", where + ** the intck_wrapper aliases of "a" and "b" are "c1" and "c2": + ** + ** o_pk: "o.c1, o.c2" + ** i_pk: "i.'a', i.'b'" + ** ... + ** n_pk: 2 + */ + ", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk, n_pk) AS (" + " WITH pkfields(f, a) AS (" + " SELECT i.col_name, i.col_alias FROM idx_cols i WHERE i.idx_ispk" + " )" + " SELECT t.db, t.tab, t.idx, " + " group_concat(a, ', '), " + " group_concat('i.'||quote(f), ', '), " + " group_concat('quote(o.'||a||')', ' || '','' || '), " + " format('(%s)==(%s)'," + " group_concat('o.'||a, ', '), " + " group_concat(format('\"%w\"', f), ', ')" + " )," + " group_concat('%s', ',')," + " group_concat('quote('||a||')', ', '), " + " count(*)" + " FROM tabname t, pkfields" + ")" + "" + ", idx(name, match_expr, partial, partial_alias, idx_ps, idx_idx) AS (" + " SELECT idx_name," + " format('(%s,%s) IS (%s,%s)', " + " group_concat(i.col_expr, ', '), i_pk," + " group_concat('o.'||i.col_alias, ', '), o_pk" + " ), " + " parse_create_index(" + " (SELECT sql FROM sqlite_schema WHERE name=idx_name), -1" + " )," + " 'cond' || row_number() OVER ()" + " , group_concat('%s', ',')" + " , group_concat('quote('||i.col_alias||')', ', ')" + " FROM tabpk t, " + " without_rowid w," + " idx_cols i" + " WHERE i.idx_ispk==0 " + " GROUP BY idx_name" + ")" + "" + ", wrapper_with(s) AS (" + " SELECT 'intck_wrapper AS (\n SELECT\n ' || (" + " WITH f(a, b) AS (" + " SELECT col_expr, col_alias FROM idx_cols" + " UNION ALL " + " SELECT partial, partial_alias FROM idx WHERE partial IS NOT NULL" + " )" + " SELECT group_concat(format('%s AS %s', a, b), ',\n ') FROM f" + " )" + " || format('\n FROM %Q.%Q ', t.db, t.tab)" + /* If the object being checked is a table, append "NOT INDEXED". + ** Otherwise, append "INDEXED BY ", and then, if the index + ** is a partial index " WHERE ". */ + " || CASE WHEN t.idx IS NULL THEN " + " 'NOT INDEXED'" + " ELSE" + " format('INDEXED BY %Q%s', t.idx, ' WHERE '||i.partial)" + " END" + " || '\n)'" + " FROM tabname t LEFT JOIN idx i ON (i.name=t.idx)" + ")" + "" + ; + + bAutoIndex = intckGetAutoIndex(p); + if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 0"); + + bIsIndex = intckIsIndex(p, zObj); + if( bIsIndex ){ + pStmt = intckPrepareFmt(p, + /* Table idxname contains a single row. The first column, "db", contains + ** the name of the db containing the table (e.g. "main") and the second, + ** "tab", the name of the table itself. */ + "WITH tabname(db, tab, idx) AS (" + " SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), %Q " + ")" + "" + ", whereclause(w_c) AS (%s)" + "" + "%s" /* zCommon */ + "" + ", case_statement(c) AS (" + " SELECT " + " 'CASE WHEN (' || group_concat(col_alias, ', ') || ', 1) IS (\n' " + " || ' SELECT ' || group_concat(col_expr, ', ') || ', 1 FROM '" + " || format('%%Q.%%Q NOT INDEXED WHERE %%s\n', t.db, t.tab, p.eq_pk)" + " || ' )\n THEN NULL\n '" + " || 'ELSE format(''surplus entry ('" + " || group_concat('%%s', ',') || ',' || p.ps_pk" + " || ') in index ' || t.idx || ''', ' " + " || group_concat('quote('||i.col_alias||')', ', ') || ', ' || p.pk_pk" + " || ')'" + " || '\n END AS error_message'" + " FROM tabname t, tabpk p, idx_cols i WHERE i.idx_name=t.idx" + ")" + "" + ", thiskey(k, n) AS (" + " SELECT group_concat(i.col_alias, ', ') || ', ' || p.o_pk, " + " count(*) + p.n_pk " + " FROM tabpk p, idx_cols i WHERE i.idx_name=p.idx" + ")" + "" + ", main_select(m, n) AS (" + " SELECT format(" + " 'WITH %%s\n' ||" + " ', idx_checker AS (\n' ||" + " ' SELECT %%s,\n' ||" + " ' %%s\n' || " + " ' FROM intck_wrapper AS o\n' ||" + " ')\n'," + " ww.s, c, t.k" + " ), t.n" + " FROM case_statement, wrapper_with ww, thiskey t" + ")" + + "SELECT m || " + " group_concat('SELECT * FROM idx_checker ' || w_c, ' UNION ALL '), n" + " FROM " + "main_select, whereclause " + , p->zDb, p->zDb, zObj, zObj + , zPrev ? zPrev : "VALUES('')", zCommon + ); + }else{ + pStmt = intckPrepareFmt(p, + /* Table tabname contains a single row. The first column, "db", contains + ** the name of the db containing the table (e.g. "main") and the second, + ** "tab", the name of the table itself. */ + "WITH tabname(db, tab, idx, prev) AS (SELECT %Q, %Q, NULL, %Q)" + "" + "%s" /* zCommon */ + + /* expr(e) contains one row for each index on table zObj. Value e + ** is set to an expression that evaluates to NULL if the required + ** entry is present in the index, or an error message otherwise. */ + ", expr(e, p) AS (" + " SELECT format('CASE WHEN EXISTS \n" + " (SELECT 1 FROM %%Q.%%Q AS i INDEXED BY %%Q WHERE %%s%%s)\n" + " THEN NULL\n" + " ELSE format(''entry (%%s,%%s) missing from index %%s'', %%s, %%s)\n" + " END\n'" + " , t.db, t.tab, i.name, i.match_expr, ' AND (' || partial || ')'," + " i.idx_ps, t.ps_pk, i.name, i.idx_idx, t.pk_pk)," + " CASE WHEN partial IS NULL THEN NULL ELSE i.partial_alias END" + " FROM tabpk t, idx i" + ")" + + ", numbered(ii, cond, e) AS (" + " SELECT 0, 'n.ii=0', 'NULL'" + " UNION ALL " + " SELECT row_number() OVER ()," + " '(n.ii='||row_number() OVER ()||COALESCE(' AND '||p||')', ')'), e" + " FROM expr" + ")" + + ", counter_with(w) AS (" + " SELECT 'WITH intck_counter(ii) AS (\n ' || " + " group_concat('SELECT '||ii, ' UNION ALL\n ') " + " || '\n)' FROM numbered" + ")" + "" + ", case_statement(c) AS (" + " SELECT 'CASE ' || " + " group_concat(format('\n WHEN %%s THEN (%%s)', cond, e), '') ||" + " '\nEND AS error_message'" + " FROM numbered" + ")" + "" + + /* This table contains a single row consisting of a single value - + ** the text of an SQL expression that may be used by the main SQL + ** statement to output an SQL literal that can be used to resume + ** the scan if it is suspended. e.g. for a rowid table, an expression + ** like: + ** + ** format('(%d,%d)', _rowid_, n.ii) + */ + ", thiskey(k, n) AS (" + " SELECT o_pk || ', ii', n_pk+1 FROM tabpk" + ")" + "" + ", whereclause(w_c) AS (" + " SELECT CASE WHEN prev!='' THEN " + " '\nWHERE (' || o_pk ||', n.ii) > ' || prev" + " ELSE ''" + " END" + " FROM tabpk, tabname" + ")" + "" + ", main_select(m, n) AS (" + " SELECT format(" + " '%%s, %%s\nSELECT %%s,\n%%s\nFROM intck_wrapper AS o" + ", intck_counter AS n%%s\nORDER BY %%s', " + " w, ww.s, c, thiskey.k, whereclause.w_c, t.o_pk" + " ), thiskey.n" + " FROM case_statement, tabpk t, counter_with, " + " wrapper_with ww, thiskey, whereclause" + ")" + + "SELECT m, n FROM main_select", + p->zDb, zObj, zPrev, zCommon + ); + } + + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + zRet = intckMprintf(p, "%s", (const char*)sqlite3_column_text(pStmt, 0)); + if( pnKeyVal ){ + *pnKeyVal = sqlite3_column_int(pStmt, 1); + } + } + intckFinalize(p, pStmt); + + if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 1"); + return zRet; +} + +/* +** Open a new integrity-check object. +*/ +int sqlite3_intck_open( + sqlite3 *db, /* Database handle to operate on */ + const char *zDbArg, /* "main", "temp" etc. */ + sqlite3_intck **ppOut /* OUT: New integrity-check handle */ +){ + sqlite3_intck *pNew = 0; + int rc = SQLITE_OK; + const char *zDb = zDbArg ? zDbArg : "main"; + int nDb = (int)strlen(zDb); + + pNew = (sqlite3_intck*)sqlite3_malloc(sizeof(*pNew) + nDb + 1); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + pNew->zDb = (const char*)&pNew[1]; + memcpy(&pNew[1], zDb, nDb+1); + rc = sqlite3_create_function(db, "parse_create_index", + 2, SQLITE_UTF8, 0, intckParseCreateIndexFunc, 0, 0 + ); + if( rc!=SQLITE_OK ){ + sqlite3_intck_close(pNew); + pNew = 0; + } + } + + *ppOut = pNew; + return rc; +} + +/* +** Free the integrity-check object. +*/ +void sqlite3_intck_close(sqlite3_intck *p){ + if( p ){ + sqlite3_finalize(p->pCheck); + sqlite3_create_function( + p->db, "parse_create_index", 1, SQLITE_UTF8, 0, 0, 0, 0 + ); + sqlite3_free(p->zObj); + sqlite3_free(p->zKey); + sqlite3_free(p->zTestSql); + sqlite3_free(p->zErr); + sqlite3_free(p->zMessage); + sqlite3_free(p); + } +} + +/* +** Step the integrity-check object. +*/ +int sqlite3_intck_step(sqlite3_intck *p){ + if( p->rc==SQLITE_OK ){ + + if( p->zMessage ){ + sqlite3_free(p->zMessage); + p->zMessage = 0; + } + + if( p->bCorruptSchema ){ + p->rc = SQLITE_DONE; + }else + if( p->pCheck==0 ){ + intckFindObject(p); + if( p->rc==SQLITE_OK ){ + if( p->zObj ){ + char *zSql = 0; + zSql = intckCheckObjectSql(p, p->zObj, p->zKey, &p->nKeyVal); + p->pCheck = intckPrepare(p, zSql); + sqlite3_free(zSql); + sqlite3_free(p->zKey); + p->zKey = 0; + }else{ + p->rc = SQLITE_DONE; + } + }else if( p->rc==SQLITE_CORRUPT ){ + p->rc = SQLITE_OK; + p->zMessage = intckMprintf(p, "%s", + "corruption found while reading database schema" + ); + p->bCorruptSchema = 1; + } + } + + if( p->pCheck ){ + assert( p->rc==SQLITE_OK ); + if( sqlite3_step(p->pCheck)==SQLITE_ROW ){ + /* Normal case, do nothing. */ + }else{ + intckFinalize(p, p->pCheck); + p->pCheck = 0; + p->nKeyVal = 0; + if( p->rc==SQLITE_CORRUPT ){ + p->rc = SQLITE_OK; + p->zMessage = intckMprintf(p, + "corruption found while scanning database object %s", p->zObj + ); + } + } + } + } + + return p->rc; +} + +/* +** Return a message describing the corruption encountered by the most recent +** call to sqlite3_intck_step(), or NULL if no corruption was encountered. +*/ +const char *sqlite3_intck_message(sqlite3_intck *p){ + assert( p->pCheck==0 || p->zMessage==0 ); + if( p->zMessage ){ + return p->zMessage; + } + if( p->pCheck ){ + return (const char*)sqlite3_column_text(p->pCheck, 0); + } + return 0; +} + +/* +** Return the error code and message. +*/ +int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){ + if( pzErr ) *pzErr = p->zErr; + return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc); +} + +/* +** Close any read transaction the integrity-check object is holding open +** on the database. +*/ +int sqlite3_intck_unlock(sqlite3_intck *p){ + if( p->rc==SQLITE_OK && p->pCheck ){ + assert( p->zKey==0 && p->nKeyVal>0 ); + intckSaveKey(p); + intckFinalize(p, p->pCheck); + p->pCheck = 0; + } + return p->rc; +} + +/* +** Return the SQL statement used to check object zObj. Or, if zObj is +** NULL, the current SQL statement. +*/ +const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){ + sqlite3_free(p->zTestSql); + if( zObj ){ + p->zTestSql = intckCheckObjectSql(p, zObj, 0, 0); + }else{ + if( p->zObj ){ + p->zTestSql = intckCheckObjectSql(p, p->zObj, p->zKey, 0); + }else{ + sqlite3_free(p->zTestSql); + p->zTestSql = 0; + } + } + return p->zTestSql; +} diff --git a/ext/intck/sqlite3intck.h b/ext/intck/sqlite3intck.h new file mode 100644 index 000000000..e08a86f28 --- /dev/null +++ b/ext/intck/sqlite3intck.h @@ -0,0 +1,171 @@ +/* +** 2024-02-08 +** +** 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. +** +************************************************************************* +*/ + +/* +** Incremental Integrity-Check Extension +** ------------------------------------- +** +** This module contains code to check whether or not an SQLite database +** is well-formed or corrupt. This is the same task as performed by SQLite's +** built-in "PRAGMA integrity_check" command. This module differs from +** "PRAGMA integrity_check" in that: +** +** + It is less thorough - this module does not detect certain types +** of corruption that are detected by the PRAGMA command. However, +** it does detect all kinds of corruption that are likely to cause +** errors in SQLite applications. +** +** + It is slower. Sometimes up to three times slower. +** +** + It allows integrity-check operations to be split into multiple +** transactions, so that the database does not need to be read-locked +** for the duration of the integrity-check. +** +** One way to use the API to run integrity-check on the "main" database +** of handle db is: +** +** int rc = SQLITE_OK; +** sqlite3_intck *p = 0; +** +** sqlite3_intck_open(db, "main", &p); +** while( SQLITE_OK==sqlite3_intck_step(p) ){ +** const char *zMsg = sqlite3_intck_message(p); +** if( zMsg ) printf("corruption: %s\n", zMsg); +** } +** rc = sqlite3_intck_error(p, &zErr); +** if( rc!=SQLITE_OK ){ +** printf("error occured (rc=%d), (errmsg=%s)\n", rc, zErr); +** } +** sqlite3_intck_close(p); +** +** Usually, the sqlite3_intck object opens a read transaction within the +** first call to sqlite3_intck_step() and holds it open until the +** integrity-check is complete. However, if sqlite3_intck_unlock() is +** called, the read transaction is ended and a new read transaction opened +** by the subsequent call to sqlite3_intck_step(). +*/ + +#ifndef _SQLITE_INTCK_H +#define _SQLITE_INTCK_H + +#include "sqlite3.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** An ongoing incremental integrity-check operation is represented by an +** opaque pointer of the following type. +*/ +typedef struct sqlite3_intck sqlite3_intck; + +/* +** Open a new incremental integrity-check object. If successful, populate +** output variable (*ppOut) with the new object handle and return SQLITE_OK. +** Or, if an error occurs, set (*ppOut) to NULL and return an SQLite error +** code (e.g. SQLITE_NOMEM). +** +** The integrity-check will be conducted on database zDb (which must be "main", +** "temp", or the name of an attached database) of database handle db. Once +** this function has been called successfully, the caller should not use +** database handle db until the integrity-check object has been destroyed +** using sqlite3_intck_close(). +*/ +int sqlite3_intck_open( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Database name ("main", "temp" etc.) */ + sqlite3_intck **ppOut /* OUT: New sqlite3_intck handle */ +); + +/* +** Close and release all resources associated with a handle opened by an +** earlier call to sqlite3_intck_open(). The results of using an +** integrity-check handle after it has been passed to this function are +** undefined. +*/ +void sqlite3_intck_close(sqlite3_intck *pCk); + +/* +** Do the next step of the integrity-check operation specified by the handle +** passed as the only argument. This function returns SQLITE_DONE if the +** integrity-check operation is finished, or an SQLite error code if +** an error occurs, or SQLITE_OK if no error occurs but the integrity-check +** is not finished. It is not considered an error if database corruption +** is encountered. +** +** Following a successful call to sqlite3_intck_step() (one that returns +** SQLITE_OK), sqlite3_intck_message() returns a non-NULL value if +** corruption was detected in the db. +** +** If an error occurs and a value other than SQLITE_OK or SQLITE_DONE is +** returned, then the integrity-check handle is placed in an error state. +** In this state all subsequent calls to sqlite3_intck_step() or +** sqlite3_intck_unlock() will immediately return the same error. The +** sqlite3_intck_error() method may be used to obtain an English language +** error message in this case. +*/ +int sqlite3_intck_step(sqlite3_intck *pCk); + +/* +** If the previous call to sqlite3_intck_step() encountered corruption +** within the database, then this function returns a pointer to a buffer +** containing a nul-terminated string describing the corruption in +** English. If the previous call to sqlite3_intck_step() did not encounter +** corruption, or if there was no previous call, this function returns +** NULL. +*/ +const char *sqlite3_intck_message(sqlite3_intck *pCk); + +/* +** Close any read-transaction opened by an earlier call to +** sqlite3_intck_step(). Any subsequent call to sqlite3_intck_step() will +** open a new transaction. Return SQLITE_OK if successful, or an SQLite error +** code otherwise. +** +** If an error occurs, then the integrity-check handle is placed in an error +** state. In this state all subsequent calls to sqlite3_intck_step() or +** sqlite3_intck_unlock() will immediately return the same error. The +** sqlite3_intck_error() method may be used to obtain an English language +** error message in this case. +*/ +int sqlite3_intck_unlock(sqlite3_intck *pCk); + +/* +** If an error has occurred in an earlier call to sqlite3_intck_step() +** or sqlite3_intck_unlock(), then this method returns the associated +** SQLite error code. Additionally, if pzErr is not NULL, then (*pzErr) +** may be set to point to a nul-terminated string containing an English +** language error message. Or, if no error message is available, to +** NULL. +** +** If no error has occurred within sqlite3_intck_step() or +** sqlite_intck_unlock() calls on the handle passed as the first argument, +** then SQLITE_OK is returned and (*pzErr) set to NULL. +*/ +int sqlite3_intck_error(sqlite3_intck *pCk, const char **pzErr); + +/* +** This API is used for testing only. It returns the full-text of an SQL +** statement used to test object zObj, which may be a table or index. +** The returned buffer is valid until the next call to either this function +** or sqlite3_intck_close() on the same sqlite3_intck handle. +*/ +const char *sqlite3_intck_test_sql(sqlite3_intck *pCk, const char *zObj); + + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* ifndef _SQLITE_INTCK_H */ diff --git a/ext/intck/test_intck.c b/ext/intck/test_intck.c new file mode 100644 index 000000000..84008fb07 --- /dev/null +++ b/ext/intck/test_intck.c @@ -0,0 +1,238 @@ +/* +** 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. +** +************************************************************************* +** Code for testing all sorts of SQLite interfaces. This code +** is not included in the SQLite library. +*/ + +#include "sqlite3.h" +#include "sqlite3intck.h" + +#if defined(INCLUDE_SQLITE_TCL_H) +# include "sqlite_tcl.h" +#else +# include "tcl.h" +#endif + +#include +#include + +/* In test1.c */ +int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); +const char *sqlite3ErrName(int); + +typedef struct TestIntck TestIntck; +struct TestIntck { + sqlite3_intck *intck; +}; + +static int testIntckCmd( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + struct Subcmd { + const char *zName; + int nArg; + const char *zExpect; + } aCmd[] = { + {"close", 0, ""}, /* 0 */ + {"step", 0, ""}, /* 1 */ + {"message", 0, ""}, /* 2 */ + {"error", 0, ""}, /* 3 */ + {"unlock", 0, ""}, /* 4 */ + {"test_sql", 1, ""}, /* 5 */ + {0 , 0} + }; + int rc = TCL_OK; + int iIdx = -1; + TestIntck *p = (TestIntck*)clientData; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ..."); + return TCL_ERROR; + } + + rc = Tcl_GetIndexFromObjStruct( + interp, objv[1], aCmd, sizeof(aCmd[0]), "SUB-COMMAND", 0, &iIdx + ); + if( rc ) return rc; + + if( objc!=2+aCmd[iIdx].nArg ){ + Tcl_WrongNumArgs(interp, 2, objv, aCmd[iIdx].zExpect); + return TCL_ERROR; + } + + switch( iIdx ){ + case 0: assert( 0==strcmp("close", aCmd[iIdx].zName) ); { + Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0)); + break; + } + + case 1: assert( 0==strcmp("step", aCmd[iIdx].zName) ); { + rc = sqlite3_intck_step(p->intck); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + break; + } + + case 2: assert( 0==strcmp("message", aCmd[iIdx].zName) ); { + const char *z = sqlite3_intck_message(p->intck); + Tcl_SetObjResult(interp, Tcl_NewStringObj(z ? z : "", -1)); + break; + } + + case 3: assert( 0==strcmp("error", aCmd[iIdx].zName) ); { + const char *zErr = 0; + rc = sqlite3_intck_error(p->intck, 0); + Tcl_Obj *pRes = Tcl_NewObj(); + Tcl_ListObjAppendElement( + interp, pRes, Tcl_NewStringObj(sqlite3ErrName(rc), -1) + ); + sqlite3_intck_error(p->intck, &zErr); + Tcl_ListObjAppendElement( + interp, pRes, Tcl_NewStringObj(zErr ? zErr : 0, -1) + ); + Tcl_SetObjResult(interp, pRes); + break; + } + + case 4: assert( 0==strcmp("unlock", aCmd[iIdx].zName) ); { + rc = sqlite3_intck_unlock(p->intck); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + break; + } + + case 5: assert( 0==strcmp("test_sql", aCmd[iIdx].zName) ); { + const char *zObj = Tcl_GetString(objv[2]); + const char *zSql = sqlite3_intck_test_sql(p->intck, zObj[0] ? zObj : 0); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zSql, -1)); + break; + } + } + + return TCL_OK; +} + +/* +** Destructor for commands created by test_sqlite3_intck(). +*/ +static void testIntckFree(void *clientData){ + TestIntck *p = (TestIntck*)clientData; + sqlite3_intck_close(p->intck); + ckfree(p); +} + +/* +** tclcmd: sqlite3_intck DB DBNAME +*/ +static int test_sqlite3_intck( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + char zName[64]; + int iName = 0; + Tcl_CmdInfo info; + TestIntck *p = 0; + sqlite3 *db = 0; + const char *zDb = 0; + int rc = SQLITE_OK; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME"); + return TCL_ERROR; + } + + p = (TestIntck*)ckalloc(sizeof(TestIntck)); + memset(p, 0, sizeof(TestIntck)); + + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ + return TCL_ERROR; + } + zDb = Tcl_GetString(objv[2]); + if( zDb[0]=='\0' ) zDb = 0; + + rc = sqlite3_intck_open(db, zDb, &p->intck); + if( rc!=SQLITE_OK ){ + ckfree(p); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errstr(rc), -1)); + return TCL_ERROR; + } + + do { + sprintf(zName, "intck%d", iName++); + }while( Tcl_GetCommandInfo(interp, zName, &info)!=0 ); + Tcl_CreateObjCommand(interp, zName, testIntckCmd, (void*)p, testIntckFree); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zName, -1)); + + return TCL_OK; +} + +/* +** tclcmd: test_do_intck DB DBNAME +*/ +static int test_do_intck( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3 *db = 0; + const char *zDb = 0; + int rc = SQLITE_OK; + sqlite3_intck *pCk = 0; + Tcl_Obj *pRet = 0; + const char *zErr = 0; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ + return TCL_ERROR; + } + zDb = Tcl_GetString(objv[2]); + + pRet = Tcl_NewObj(); + Tcl_IncrRefCount(pRet); + + rc = sqlite3_intck_open(db, zDb, &pCk); + if( rc==SQLITE_OK ){ + while( sqlite3_intck_step(pCk)==SQLITE_OK ){ + const char *zMsg = sqlite3_intck_message(pCk); + if( zMsg ){ + Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(zMsg, -1)); + } + } + rc = sqlite3_intck_error(pCk, &zErr); + } + if( rc!=SQLITE_OK ){ + if( zErr ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1)); + }else{ + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + } + }else{ + Tcl_SetObjResult(interp, pRet); + } + Tcl_DecrRefCount(pRet); + sqlite3_intck_close(pCk); + sqlite3_intck_close(0); + return rc ? TCL_ERROR : TCL_OK; +} + +int Sqlitetestintck_Init(Tcl_Interp *interp){ + Tcl_CreateObjCommand(interp, "sqlite3_intck", test_sqlite3_intck, 0, 0); + Tcl_CreateObjCommand(interp, "test_do_intck", test_do_intck, 0, 0); + return TCL_OK; +} diff --git a/ext/misc/cksumvfs.c b/ext/misc/cksumvfs.c index e7c2c9d5c..2d7f6584e 100644 --- a/ext/misc/cksumvfs.c +++ b/ext/misc/cksumvfs.c @@ -446,9 +446,9 @@ static int cksmRead( ** (2) checksum verification is enabled ** (3) we are not in the middle of checkpoint */ - if( iAmt>=512 /* (1) */ - && p->verifyCksm /* (2) */ - && !p->inCkpt /* (3) */ + if( iAmt>=512 && (iAmt & (iAmt-1))==0 /* (1) */ + && p->verifyCksm /* (2) */ + && !p->inCkpt /* (3) */ ){ u8 cksum[8]; cksmCompute((u8*)zBuf, iAmt-8, cksum); diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c index 70546adfc..ca8090ed2 100644 --- a/ext/misc/fileio.c +++ b/ext/misc/fileio.c @@ -372,7 +372,9 @@ static int writeFile( #if !defined(_WIN32) && !defined(WIN32) if( S_ISLNK(mode) ){ const char *zTo = (const char*)sqlite3_value_text(pData); - if( zTo==0 || symlink(zTo, zFile)<0 ) return 1; + if( zTo==0 ) return 1; + unlink(zFile); + if( symlink(zTo, zFile)<0 ) return 1; }else #endif { @@ -458,13 +460,19 @@ static int writeFile( return 1; } #else - /* Legacy unix */ - struct timeval times[2]; - times[0].tv_usec = times[1].tv_usec = 0; - times[0].tv_sec = time(0); - times[1].tv_sec = mtime; - if( utimes(zFile, times) ){ - return 1; + /* Legacy unix. + ** + ** Do not use utimes() on a symbolic link - it sees through the link and + ** modifies the timestamps on the target. Or fails if the target does + ** not exist. */ + if( 0==S_ISLNK(mode) ){ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; + } } #endif } diff --git a/ext/misc/series.c b/ext/misc/series.c index abd6af7ad..0dfed181f 100644 --- a/ext/misc/series.c +++ b/ext/misc/series.c @@ -103,16 +103,20 @@ SQLITE_EXTENSION_INIT1 ** index is ix. The 0th member is given by smBase. The sequence members ** progress per ix increment by smStep. */ -static sqlite3_int64 genSeqMember(sqlite3_int64 smBase, - sqlite3_int64 smStep, - sqlite3_uint64 ix){ - if( ix>=(sqlite3_uint64)LLONG_MAX ){ +static sqlite3_int64 genSeqMember( + sqlite3_int64 smBase, + sqlite3_int64 smStep, + sqlite3_uint64 ix +){ + static const sqlite3_uint64 mxI64 = + ((sqlite3_uint64)0x7fffffff)<<32 | 0xffffffff; + if( ix>=mxI64 ){ /* Get ix into signed i64 range. */ - ix -= (sqlite3_uint64)LLONG_MAX; + ix -= mxI64; /* With 2's complement ALU, this next can be 1 step, but is split into * 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */ - smBase += (LLONG_MAX/2) * smStep; - smBase += (LLONG_MAX - LLONG_MAX/2) * smStep; + smBase += (mxI64/2) * smStep; + smBase += (mxI64 - mxI64/2) * smStep; } /* Under UBSAN (or on 1's complement machines), must do this last term * in steps to avoid the dreaded (and harmless) signed multiply overlow. */ @@ -372,13 +376,13 @@ static int seriesEof(sqlite3_vtab_cursor *cur){ ** parameter. (idxStr is not used in this implementation.) idxNum ** is a bitmask showing which constraints are available: ** -** 1: start=VALUE -** 2: stop=VALUE -** 4: step=VALUE -** -** Also, if bit 8 is set, that means that the series should be output -** in descending order rather than in ascending order. If bit 16 is -** set, then output must appear in ascending order. +** 0x01: start=VALUE +** 0x02: stop=VALUE +** 0x04: step=VALUE +** 0x08: descending order +** 0x10: ascending order +** 0x20: LIMIT VALUE +** 0x40: OFFSET VALUE ** ** This routine should initialize the cursor and position it so that it ** is pointing at the first row, or pointing off the end of the table @@ -392,26 +396,44 @@ static int seriesFilter( series_cursor *pCur = (series_cursor *)pVtabCursor; int i = 0; (void)idxStrUnused; - if( idxNum & 1 ){ + if( idxNum & 0x01 ){ pCur->ss.iBase = sqlite3_value_int64(argv[i++]); }else{ pCur->ss.iBase = 0; } - if( idxNum & 2 ){ + if( idxNum & 0x02 ){ pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); }else{ pCur->ss.iTerm = 0xffffffff; } - if( idxNum & 4 ){ + if( idxNum & 0x04 ){ pCur->ss.iStep = sqlite3_value_int64(argv[i++]); if( pCur->ss.iStep==0 ){ pCur->ss.iStep = 1; }else if( pCur->ss.iStep<0 ){ - if( (idxNum & 16)==0 ) idxNum |= 8; + if( (idxNum & 0x10)==0 ) idxNum |= 0x08; } }else{ pCur->ss.iStep = 1; } + if( idxNum & 0x20 ){ + sqlite3_int64 iLimit = sqlite3_value_int64(argv[i++]); + sqlite3_int64 iTerm; + if( idxNum & 0x40 ){ + sqlite3_int64 iOffset = sqlite3_value_int64(argv[i++]); + if( iOffset>0 ){ + pCur->ss.iBase += pCur->ss.iStep*iOffset; + } + } + if( iLimit>=0 ){ + iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep; + if( pCur->ss.iStep<0 ){ + if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm; + }else{ + if( iTermss.iTerm ) pCur->ss.iTerm = iTerm; + } + } + } for(i=0; iss.isReversing = pCur->ss.iStep > 0; }else{ pCur->ss.isReversing = pCur->ss.iStep < 0; @@ -442,10 +464,13 @@ static int seriesFilter( ** ** The query plan is represented by bits in idxNum: ** -** (1) start = $value -- constraint exists -** (2) stop = $value -- constraint exists -** (4) step = $value -- constraint exists -** (8) output in descending order +** 0x01 start = $value -- constraint exists +** 0x02 stop = $value -- constraint exists +** 0x04 step = $value -- constraint exists +** 0x08 output is in descending order +** 0x10 output is in ascending order +** 0x20 LIMIT $value -- constraint exists +** 0x40 OFFSET $value -- constraint exists */ static int seriesBestIndex( sqlite3_vtab *pVTab, @@ -453,10 +478,12 @@ static int seriesBestIndex( ){ int i, j; /* Loop over constraints */ int idxNum = 0; /* The query plan bitmask */ +#ifndef ZERO_ARGUMENT_GENERATE_SERIES int bStartSeen = 0; /* EQ constraint seen on the START column */ +#endif int unusableMask = 0; /* Mask of unusable constraints */ int nArg = 0; /* Number of arguments that seriesFilter() expects */ - int aIdx[3]; /* Constraints on start, stop, and step */ + int aIdx[5]; /* Constraints on start, stop, step, LIMIT, OFFSET */ const struct sqlite3_index_constraint *pConstraint; /* This implementation assumes that the start, stop, and step columns @@ -464,28 +491,54 @@ static int seriesBestIndex( assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); - aIdx[0] = aIdx[1] = aIdx[2] = -1; + aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = -1; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ int iCol; /* 0 for start, 1 for stop, 2 for step */ int iMask; /* bitmask for those column */ + int op = pConstraint->op; + if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT + && op<=SQLITE_INDEX_CONSTRAINT_OFFSET + ){ + if( pConstraint->usable==0 ){ + /* do nothing */ + }else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){ + aIdx[3] = i; + idxNum |= 0x20; + }else{ + assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET ); + aIdx[4] = i; + idxNum |= 0x40; + } + continue; + } if( pConstraint->iColumniColumn - SERIES_COLUMN_START; assert( iCol>=0 && iCol<=2 ); iMask = 1 << iCol; - if( iCol==0 ) bStartSeen = 1; +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){ + bStartSeen = 1; + } +#endif if( pConstraint->usable==0 ){ unusableMask |= iMask; continue; - }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + }else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){ idxNum |= iMask; aIdx[iCol] = i; } } - for(i=0; i<3; i++){ + if( aIdx[3]==0 ){ + /* Ignore OFFSET if LIMIT is omitted */ + idxNum &= ~0x60; + aIdx[4] = 0; + } + for(i=0; i<5; i++){ if( (j = aIdx[i])>=0 ){ pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; - pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; + pIdxInfo->aConstraintUsage[j].omit = + !SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3; } } /* The current generate_column() implementation requires at least one @@ -506,19 +559,22 @@ static int seriesBestIndex( ** this plan is unusable */ return SQLITE_CONSTRAINT; } - if( (idxNum & 3)==3 ){ + if( (idxNum & 0x03)==0x03 ){ /* Both start= and stop= boundaries are available. This is the ** the preferred case */ pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); pIdxInfo->estimatedRows = 1000; if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){ if( pIdxInfo->aOrderBy[0].desc ){ - idxNum |= 8; + idxNum |= 0x08; }else{ - idxNum |= 16; + idxNum |= 0x10; } pIdxInfo->orderByConsumed = 1; } + }else if( (idxNum & 0x21)==0x21 ){ + /* We have start= and LIMIT */ + pIdxInfo->estimatedRows = 2500; }else{ /* If either boundary is missing, we have to generate a huge span ** of numbers. Make this case very expensive so that the query diff --git a/ext/misc/sqlar.c b/ext/misc/sqlar.c index be079a546..9f726f0b8 100644 --- a/ext/misc/sqlar.c +++ b/ext/misc/sqlar.c @@ -81,7 +81,7 @@ static void sqlarUncompressFunc( sqlite3_value **argv ){ uLong nData; - uLongf sz; + sqlite3_int64 sz; assert( argc==2 ); sz = sqlite3_value_int(argv[1]); @@ -89,14 +89,15 @@ static void sqlarUncompressFunc( if( sz<=0 || sz==(nData = sqlite3_value_bytes(argv[0])) ){ sqlite3_result_value(context, argv[0]); }else{ + uLongf szf = sz; const Bytef *pData= sqlite3_value_blob(argv[0]); Bytef *pOut = sqlite3_malloc(sz); if( pOut==0 ){ sqlite3_result_error_nomem(context); - }else if( Z_OK!=uncompress(pOut, &sz, pData, nData) ){ + }else if( Z_OK!=uncompress(pOut, &szf, pData, nData) ){ sqlite3_result_error(context, "error in uncompress()", -1); }else{ - sqlite3_result_blob(context, pOut, sz, SQLITE_TRANSIENT); + sqlite3_result_blob(context, pOut, szf, SQLITE_TRANSIENT); } sqlite3_free(pOut); } diff --git a/ext/misc/vtablog.c b/ext/misc/vtablog.c index e414e7afa..2cc29c285 100644 --- a/ext/misc/vtablog.c +++ b/ext/misc/vtablog.c @@ -38,8 +38,9 @@ SQLITE_EXTENSION_INIT1 typedef struct vtablog_vtab vtablog_vtab; struct vtablog_vtab { sqlite3_vtab base; /* Base class - must be first */ + char *zDb; /* Schema name. argv[1] of xConnect/xCreate */ + char *zName; /* Table name. argv[2] of xConnect/xCreate */ int nRow; /* Number of rows in the table */ - int iInst; /* Instance number for this vtablog table */ int nCursor; /* Number of cursors created */ }; @@ -167,15 +168,14 @@ static int vtablogConnectCreate( char **pzErr, int isCreate ){ - static int nInst = 0; vtablog_vtab *pNew; int i; int rc; - int iInst = ++nInst; char *zSchema = 0; char *zNRow = 0; - printf("vtablog%s(tab=%d):\n", isCreate ? "Create" : "Connect", iInst); + printf("%s.%s.%s():\n", argv[1], argv[2], + isCreate ? "xCreate" : "xConnect"); printf(" argc=%d\n", argc); for(i=0; inRow = 10; if( zNRow ) pNew->nRow = atoi(zNRow); - pNew->iInst = iInst; + printf(" nrow = %d\n", pNew->nRow); + pNew->zDb = sqlite3_mprintf("%s", argv[1]); + pNew->zName = sqlite3_mprintf("%s", argv[2]); } + +vtablog_end_connect: + sqlite3_free(zSchema); + sqlite3_free(zNRow); return rc; } static int vtablogCreate( @@ -237,7 +244,9 @@ static int vtablogConnect( */ static int vtablogDisconnect(sqlite3_vtab *pVtab){ vtablog_vtab *pTab = (vtablog_vtab*)pVtab; - printf("vtablogDisconnect(%d)\n", pTab->iInst); + printf("%s.%s.xDisconnect()\n", pTab->zDb, pTab->zName); + sqlite3_free(pTab->zDb); + sqlite3_free(pTab->zName); sqlite3_free(pVtab); return SQLITE_OK; } @@ -247,7 +256,9 @@ static int vtablogDisconnect(sqlite3_vtab *pVtab){ */ static int vtablogDestroy(sqlite3_vtab *pVtab){ vtablog_vtab *pTab = (vtablog_vtab*)pVtab; - printf("vtablogDestroy(%d)\n", pTab->iInst); + printf("%s.%s.xDestroy()\n", pTab->zDb, pTab->zName); + sqlite3_free(pTab->zDb); + sqlite3_free(pTab->zName); sqlite3_free(pVtab); return SQLITE_OK; } @@ -258,7 +269,8 @@ static int vtablogDestroy(sqlite3_vtab *pVtab){ static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ vtablog_vtab *pTab = (vtablog_vtab*)p; vtablog_cursor *pCur; - printf("vtablogOpen(tab=%d, cursor=%d)\n", pTab->iInst, ++pTab->nCursor); + printf("%s.%s.xOpen(cursor=%d)\n", pTab->zDb, pTab->zName, + ++pTab->nCursor); pCur = sqlite3_malloc( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); @@ -273,7 +285,7 @@ static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ static int vtablogClose(sqlite3_vtab_cursor *cur){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogClose(tab=%d, cursor=%d)\n", pTab->iInst, pCur->iCursor); + printf("%s.%s.xClose(cursor=%d)\n", pTab->zDb, pTab->zName, pCur->iCursor); sqlite3_free(cur); return SQLITE_OK; } @@ -285,8 +297,9 @@ static int vtablogClose(sqlite3_vtab_cursor *cur){ static int vtablogNext(sqlite3_vtab_cursor *cur){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogNext(tab=%d, cursor=%d) rowid %d -> %d\n", - pTab->iInst, pCur->iCursor, (int)pCur->iRowid, (int)pCur->iRowid+1); + printf("%s.%s.xNext(cursor=%d) rowid %d -> %d\n", + pTab->zDb, pTab->zName, pCur->iCursor, + (int)pCur->iRowid, (int)pCur->iRowid+1); pCur->iRowid++; return SQLITE_OK; } @@ -310,8 +323,8 @@ static int vtablogColumn( }else{ sqlite3_snprintf(sizeof(zVal),zVal,"{%d}%d", i, pCur->iRowid); } - printf("vtablogColumn(tab=%d, cursor=%d, i=%d): [%s]\n", - pTab->iInst, pCur->iCursor, i, zVal); + printf("%s.%s.xColumn(cursor=%d, i=%d): [%s]\n", + pTab->zDb, pTab->zName, pCur->iCursor, i, zVal); sqlite3_result_text(ctx, zVal, -1, SQLITE_TRANSIENT); return SQLITE_OK; } @@ -323,8 +336,8 @@ static int vtablogColumn( static int vtablogRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogRowid(tab=%d, cursor=%d): %d\n", - pTab->iInst, pCur->iCursor, (int)pCur->iRowid); + printf("%s.%s.xRowid(cursor=%d): %d\n", + pTab->zDb, pTab->zName, pCur->iCursor, (int)pCur->iRowid); *pRowid = pCur->iRowid; return SQLITE_OK; } @@ -337,8 +350,8 @@ static int vtablogEof(sqlite3_vtab_cursor *cur){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; int rc = pCur->iRowid >= pTab->nRow; - printf("vtablogEof(tab=%d, cursor=%d): %d\n", - pTab->iInst, pCur->iCursor, rc); + printf("%s.%s.xEof(cursor=%d): %d\n", + pTab->zDb, pTab->zName, pCur->iCursor, rc); return rc; } @@ -417,7 +430,7 @@ static int vtablogFilter( ){ vtablog_cursor *pCur = (vtablog_cursor *)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogFilter(tab=%d, cursor=%d):\n", pTab->iInst, pCur->iCursor); + printf("%s.%s.xFilter(cursor=%d):\n", pTab->zDb, pTab->zName, pCur->iCursor); pCur->iRowid = 0; return SQLITE_OK; } @@ -430,12 +443,37 @@ static int vtablogFilter( */ static int vtablogBestIndex( sqlite3_vtab *tab, - sqlite3_index_info *pIdxInfo + sqlite3_index_info *p ){ vtablog_vtab *pTab = (vtablog_vtab*)tab; - printf("vtablogBestIndex(tab=%d):\n", pTab->iInst); - pIdxInfo->estimatedCost = (double)500; - pIdxInfo->estimatedRows = 500; + int i; + printf("%s.%s.xBestIndex():\n", pTab->zDb, pTab->zName); + printf(" colUsed: 0x%016llx\n", p->colUsed); + printf(" nConstraint: %d\n", p->nConstraint); + for(i=0; inConstraint; i++){ + printf( + " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", + i, + p->aConstraint[i].iColumn, + p->aConstraint[i].iTermOffset, + p->aConstraint[i].op, + p->aConstraint[i].usable, + sqlite3_vtab_collation(p,i)); + } + printf(" nOrderBy: %d\n", p->nOrderBy); + for(i=0; inOrderBy; i++){ + printf(" orderby[%d]: col=%d desc=%d\n", + i, + p->aOrderBy[i].iColumn, + p->aOrderBy[i].desc); + } + p->estimatedCost = (double)500; + p->estimatedRows = 500; + printf(" idxNum=%d\n", p->idxNum); + printf(" idxStr=NULL\n"); + printf(" orderByConsumed=%d\n", p->orderByConsumed); + printf(" estimatedCost=%g\n", p->estimatedCost); + printf(" estimatedRows=%lld\n", p->estimatedRows); return SQLITE_OK; } @@ -454,7 +492,7 @@ static int vtablogUpdate( ){ vtablog_vtab *pTab = (vtablog_vtab*)tab; int i; - printf("vtablogUpdate(tab=%d):\n", pTab->iInst); + printf("%s.%s.xUpdate():\n", pTab->zDb, pTab->zName); printf(" argc=%d\n", argc); for(i=0; izDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogSync(sqlite3_vtab *tab){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xSync()\n", pTab->zDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogCommit(sqlite3_vtab *tab){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xCommit()\n", pTab->zDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogRollback(sqlite3_vtab *tab){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRollback()\n", pTab->zDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogSavepoint(sqlite3_vtab *tab, int N){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xSavepoint(%d)\n", pTab->zDb, pTab->zName, N); + return SQLITE_OK; +} +static int vtablogRelease(sqlite3_vtab *tab, int N){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRelease(%d)\n", pTab->zDb, pTab->zName, N); + return SQLITE_OK; +} +static int vtablogRollbackTo(sqlite3_vtab *tab, int N){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRollbackTo(%d)\n", pTab->zDb, pTab->zName, N); + return SQLITE_OK; +} + +static int vtablogFindMethod( + sqlite3_vtab *tab, + int nArg, + const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg +){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xFindMethod(nArg=%d, zName=%s)\n", + pTab->zDb, pTab->zName, nArg, zName); + return SQLITE_OK; +} +static int vtablogRename(sqlite3_vtab *tab, const char *zNew){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRename('%s')\n", pTab->zDb, pTab->zName, zNew); + sqlite3_free(pTab->zName); + pTab->zName = sqlite3_mprintf("%s", zNew); + return SQLITE_OK; +} + +/* Any table name that contains the text "shadow" is seen as a +** shadow table. Nothing else is. +*/ +static int vtablogShadowName(const char *zName){ + printf("vtablog.xShadowName('%s')\n", zName); + return sqlite3_strglob("*shadow*", zName)==0; +} + +static int vtablogIntegrity( + sqlite3_vtab *tab, + const char *zSchema, + const char *zTabName, + int mFlags, + char **pzErr +){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xIntegrity(mFlags=0x%x)\n", pTab->zDb, pTab->zName, mFlags); + return 0; +} + /* ** This following structure defines all the methods for the ** vtablog virtual table. */ static sqlite3_module vtablogModule = { - 0, /* iVersion */ + 4, /* iVersion */ vtablogCreate, /* xCreate */ vtablogConnect, /* xConnect */ vtablogBestIndex, /* xBestIndex */ @@ -483,17 +597,17 @@ static sqlite3_module vtablogModule = { vtablogColumn, /* xColumn - read data */ vtablogRowid, /* xRowid - read data */ vtablogUpdate, /* xUpdate */ - 0, /* xBegin */ - 0, /* xSync */ - 0, /* xCommit */ - 0, /* xRollback */ - 0, /* xFindMethod */ - 0, /* xRename */ - 0, /* xSavepoint */ - 0, /* xRelease */ - 0, /* xRollbackTo */ - 0, /* xShadowName */ - 0 /* xIntegrity */ + vtablogBegin, /* xBegin */ + vtablogSync, /* xSync */ + vtablogCommit, /* xCommit */ + vtablogRollback, /* xRollback */ + vtablogFindMethod, /* xFindMethod */ + vtablogRename, /* xRename */ + vtablogSavepoint, /* xSavepoint */ + vtablogRelease, /* xRelease */ + vtablogRollbackTo, /* xRollbackTo */ + vtablogShadowName, /* xShadowName */ + vtablogIntegrity /* xIntegrity */ }; #ifdef _WIN32 diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c index 15b05cede..feb7695d6 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -199,6 +199,7 @@ typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; #endif /* @@ -885,6 +886,7 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ if( rc!=SQLITE_ROW ){ rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg); pIter->zTbl = 0; + pIter->zDataTbl = 0; }else{ pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0); pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1); @@ -2979,7 +2981,7 @@ static i64 rbuShmChecksum(sqlite3rbu *p){ u32 volatile *ptr; p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr); if( p->rc==SQLITE_OK ){ - iRet = ((i64)ptr[10] << 32) + ptr[11]; + iRet = (i64)(((u64)ptr[10] << 32) + ptr[11]); } } return iRet; diff --git a/ext/recover/dbdata.c b/ext/recover/dbdata.c index ca6371026..109aeef2d 100644 --- a/ext/recover/dbdata.c +++ b/ext/recover/dbdata.c @@ -88,6 +88,15 @@ typedef unsigned int u32; typedef struct DbdataTable DbdataTable; typedef struct DbdataCursor DbdataCursor; +typedef struct DbdataBuffer DbdataBuffer; + +/* +** Buffer type. +*/ +struct DbdataBuffer { + u8 *aBuf; + sqlite3_int64 nBuf; +}; /* Cursor object */ struct DbdataCursor { @@ -104,7 +113,7 @@ struct DbdataCursor { sqlite3_int64 iRowid; /* Only for the sqlite_dbdata table */ - u8 *pRec; /* Buffer containing current record */ + DbdataBuffer rec; sqlite3_int64 nRec; /* Size of pRec[] in bytes */ sqlite3_int64 nHdr; /* Size of header in bytes */ int iField; /* Current field number */ @@ -149,6 +158,31 @@ struct DbdataTable { " schema TEXT HIDDEN" \ ")" +/* +** Ensure the buffer passed as the first argument is at least nMin bytes +** in size. If an error occurs while attempting to resize the buffer, +** SQLITE_NOMEM is returned. Otherwise, SQLITE_OK. +*/ +static int dbdataBufferSize(DbdataBuffer *pBuf, sqlite3_int64 nMin){ + if( nMin>pBuf->nBuf ){ + sqlite3_int64 nNew = nMin+16384; + u8 *aNew = (u8*)sqlite3_realloc64(pBuf->aBuf, nNew); + + if( aNew==0 ) return SQLITE_NOMEM; + pBuf->aBuf = aNew; + pBuf->nBuf = nNew; + } + return SQLITE_OK; +} + +/* +** Release the allocation managed by buffer pBuf. +*/ +static void dbdataBufferFree(DbdataBuffer *pBuf){ + sqlite3_free(pBuf->aBuf); + memset(pBuf, 0, sizeof(*pBuf)); +} + /* ** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual ** table. @@ -289,9 +323,9 @@ static void dbdataResetCursor(DbdataCursor *pCsr){ pCsr->iField = 0; pCsr->bOnePage = 0; sqlite3_free(pCsr->aPage); - sqlite3_free(pCsr->pRec); - pCsr->pRec = 0; + dbdataBufferFree(&pCsr->rec); pCsr->aPage = 0; + pCsr->nRec = 0; } /* @@ -433,63 +467,75 @@ static void dbdataValue( u8 *pData, sqlite3_int64 nData ){ - if( eType>=0 && dbdataValueBytes(eType)<=nData ){ - switch( eType ){ - case 0: - case 10: - case 11: - sqlite3_result_null(pCtx); - break; - - case 8: - sqlite3_result_int(pCtx, 0); - break; - case 9: - sqlite3_result_int(pCtx, 1); - break; - - case 1: case 2: case 3: case 4: case 5: case 6: case 7: { - sqlite3_uint64 v = (signed char)pData[0]; - pData++; - switch( eType ){ - case 7: - 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++; - } - - if( eType==7 ){ - double r; - memcpy(&r, &v, sizeof(r)); - sqlite3_result_double(pCtx, r); - }else{ - sqlite3_result_int64(pCtx, (sqlite3_int64)v); + if( eType>=0 ){ + if( dbdataValueBytes(eType)<=nData ){ + switch( eType ){ + case 0: + case 10: + case 11: + sqlite3_result_null(pCtx); + break; + + case 8: + sqlite3_result_int(pCtx, 0); + break; + case 9: + sqlite3_result_int(pCtx, 1); + break; + + case 1: case 2: case 3: case 4: case 5: case 6: case 7: { + sqlite3_uint64 v = (signed char)pData[0]; + pData++; + switch( eType ){ + case 7: + 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++; + } + + if( eType==7 ){ + double r; + memcpy(&r, &v, sizeof(r)); + sqlite3_result_double(pCtx, r); + }else{ + sqlite3_result_int64(pCtx, (sqlite3_int64)v); + } + break; } - break; - } - - default: { - int n = ((eType-12) / 2); - if( eType % 2 ){ - switch( enc ){ -#ifndef SQLITE_OMIT_UTF16 - case SQLITE_UTF16BE: - sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT); - break; - case SQLITE_UTF16LE: - sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT); - break; -#endif - default: - sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT); - break; + + default: { + int n = ((eType-12) / 2); + if( eType % 2 ){ + switch( enc ){ + #ifndef SQLITE_OMIT_UTF16 + case SQLITE_UTF16BE: + sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; + case SQLITE_UTF16LE: + sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; + #endif + default: + sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT); + break; + } + }else{ + sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); } - }else{ - sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); } } + }else{ + if( eType==7 ){ + sqlite3_result_double(pCtx, 0.0); + }else if( eType<7 ){ + sqlite3_result_int(pCtx, 0); + }else if( eType%2 ){ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + }else{ + sqlite3_result_blob(pCtx, "", 0, SQLITE_STATIC); + } } } } @@ -551,7 +597,8 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ } }else{ /* If there is no record loaded, load it now. */ - if( pCsr->pRec==0 ){ + assert( pCsr->rec.aBuf!=0 || pCsr->nRec==0 ); + if( pCsr->nRec==0 ){ int bHasRowid = 0; int nPointer = 0; sqlite3_int64 nPayload = 0; @@ -595,6 +642,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ }else{ iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); if( nPayload>0x7fffff00 ) nPayload &= 0x3fff; + if( nPayload==0 ) nPayload = 1; } /* If this is a leaf intkey cell, load the rowid */ @@ -629,13 +677,12 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ /* Allocate space for payload. And a bit more to catch small buffer ** overruns caused by attempting to read a varint or similar from ** near the end of a corrupt record. */ - pCsr->pRec = (u8*)sqlite3_malloc64(nPayload+DBDATA_PADDING_BYTES); - if( pCsr->pRec==0 ) return SQLITE_NOMEM; - memset(pCsr->pRec, 0, nPayload+DBDATA_PADDING_BYTES); - pCsr->nRec = nPayload; + rc = dbdataBufferSize(&pCsr->rec, nPayload+DBDATA_PADDING_BYTES); + if( rc!=SQLITE_OK ) return rc; + assert( nPayload!=0 ); /* Load the nLocal bytes of payload */ - memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal); + memcpy(pCsr->rec.aBuf, &pCsr->aPage[iOff], nLocal); iOff += nLocal; /* Load content from overflow pages */ @@ -653,19 +700,22 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ nCopy = U-4; if( nCopy>nRem ) nCopy = nRem; - memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy); + memcpy(&pCsr->rec.aBuf[nPayload-nRem], &aOvfl[4], nCopy); nRem -= nCopy; pgnoOvfl = get_uint32(aOvfl); sqlite3_free(aOvfl); } + nPayload -= nRem; } + memset(&pCsr->rec.aBuf[nPayload], 0, DBDATA_PADDING_BYTES); + pCsr->nRec = nPayload; - iHdr = dbdataGetVarintU32(pCsr->pRec, &nHdr); + iHdr = dbdataGetVarintU32(pCsr->rec.aBuf, &nHdr); if( nHdr>nPayload ) nHdr = 0; pCsr->nHdr = nHdr; - pCsr->pHdrPtr = &pCsr->pRec[iHdr]; - pCsr->pPtr = &pCsr->pRec[pCsr->nHdr]; + pCsr->pHdrPtr = &pCsr->rec.aBuf[iHdr]; + pCsr->pPtr = &pCsr->rec.aBuf[pCsr->nHdr]; pCsr->iField = (bHasRowid ? -1 : 0); } } @@ -673,7 +723,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ pCsr->iField++; if( pCsr->iField>0 ){ sqlite3_int64 iType; - if( pCsr->pHdrPtr>=&pCsr->pRec[pCsr->nRec] + if( pCsr->pHdrPtr>=&pCsr->rec.aBuf[pCsr->nRec] || pCsr->iField>=DBDATA_MX_FIELD ){ bNextPage = 1; @@ -681,8 +731,8 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ int szField = 0; pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType); szField = dbdataValueBytes(iType); - if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))pPtr = &pCsr->pRec[pCsr->nRec]; + if( (pCsr->nRec - (pCsr->pPtr - pCsr->rec.aBuf))pPtr = &pCsr->rec.aBuf[pCsr->nRec]; }else{ pCsr->pPtr += szField; } @@ -692,20 +742,18 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ if( bNextPage ){ sqlite3_free(pCsr->aPage); - sqlite3_free(pCsr->pRec); pCsr->aPage = 0; - pCsr->pRec = 0; + pCsr->nRec = 0; if( pCsr->bOnePage ) return SQLITE_OK; pCsr->iPgno++; }else{ - if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->pRec[pCsr->nHdr] ){ + if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->rec.aBuf[pCsr->nHdr] ){ return SQLITE_OK; } /* Advance to the next cell. The next iteration of the loop will load ** the record and so on. */ - sqlite3_free(pCsr->pRec); - pCsr->pRec = 0; + pCsr->nRec = 0; pCsr->iCell++; } } @@ -895,12 +943,12 @@ static int dbdataColumn( case DBDATA_COLUMN_VALUE: { if( pCsr->iField<0 ){ sqlite3_result_int64(ctx, pCsr->iIntkey); - }else if( &pCsr->pRec[pCsr->nRec] >= pCsr->pPtr ){ + }else if( &pCsr->rec.aBuf[pCsr->nRec] >= pCsr->pPtr ){ sqlite3_int64 iType; dbdataGetVarintU32(pCsr->pHdrPtr, &iType); dbdataValue( ctx, pCsr->enc, iType, pCsr->pPtr, - &pCsr->pRec[pCsr->nRec] - pCsr->pPtr + &pCsr->rec.aBuf[pCsr->nRec] - pCsr->pPtr ); } break; diff --git a/ext/recover/recover1.test b/ext/recover/recover1.test index 070dd03d6..11a43780d 100644 --- a/ext/recover/recover1.test +++ b/ext/recover/recover1.test @@ -342,7 +342,7 @@ foreach enc {utf8 utf16 utf16le utf16be} { DELETE FROM sqlite_schema WHERE name='t1'; } - proc my_sql_hook {sql} { + proc my_sql_hook2 {sql} { if {[string match "INSERT INTO lostandfound*" $sql]} { lappend ::script $sql } @@ -350,7 +350,7 @@ foreach enc {utf8 utf16 utf16le utf16be} { } do_test 18.$enc.2 { set ::script [list] - set R [sqlite3_recover_init_sql db main my_sql_hook] + set R [sqlite3_recover_init_sql db main my_sql_hook2] $R config lostandfound lostandfound $R run $R finish @@ -358,7 +358,18 @@ foreach enc {utf8 utf16 utf16le utf16be} { } {{INSERT INTO lostandfound VALUES(2, 2, 2, 1, 'abc', 'def')}} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 19.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + + ALTER TABLE t1 ADD COLUMN c NOT NULL DEFAULT 13; + INSERT INTO t1 VALUES(3, 'three', 'hello world'); +} +do_recover_test 19.1 diff --git a/ext/recover/recovercorrupt3.test b/ext/recover/recovercorrupt3.test new file mode 100644 index 000000000..9a7c2d094 --- /dev/null +++ b/ext/recover/recovercorrupt3.test @@ -0,0 +1,549 @@ +# 2024 May 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recovercorrupt3 + +#| 0: d5 d5 9b d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 ................ +#| 16: 04 00 00 00 1d 00 00 00 00 00 00 00 5f 5f 5f 5f ............____ +#| 32: 5f 5f 5f 5f 5f 5f 5f 5f 71 5f 5f 5f 02 02 02 02 ________q___.... +#| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +#| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +#| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +#| 96: 02 02 02 02 + +#------------------------------------------------------------------------- +reset_db +do_test 1.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 3821 pagesize 1024 filename clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 01 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ +| 96: 00 2e 7a 70 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 a0 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 29 29 ..............)) +| 464: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 480: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 496: 29 29 29 dd dd dd dd dd dd dd dd dd dd dd dd dd )))............. +| 512: dd dd dd dd dd dd dd dd dd 6e 69 d2 e9 e9 e9 d2 .........ni..... +| 528: d2 d2 d2 d2 dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 544: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 560: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 576: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 592: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 608: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 624: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 640: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 656: dd dd dd dd dd dd dd da dd dd dd dd dd dd dd dd ................ +| 672: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 688: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 704: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 720: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 736: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 752: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 768: dd dd dd dd dd dd dd dd dd dd dd dd dd 29 29 29 .............))) +| 784: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 800: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 816: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 832: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 848: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 864: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 880: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 896: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 912: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 928: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 944: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 960: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 976: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 992: 29 29 29 29 29 29 29 29 29 29 dd dd dd dd dd dd ))))))))))...... +| 1008: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| page 2 offset 1024 +| 0: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 16: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 32: dd dd 6e 69 d2 e9 e9 e9 d2 d2 d2 d2 d2 dd dd dd ..ni............ +| 48: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 64: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 80: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 96: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 112: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 128: dd dd dd dd dd dd 29 29 29 29 29 29 29 29 29 29 ......)))))))))) +| 144: 29 29 29 29 29 29 29 29 29 ad a5 29 29 29 29 00 )))))))))..)))). +| 160: 75 9c 11 00 5b e5 64 28 7c ca 09 69 28 2d 69 00 u...[.d(|..i(-i. +| 176: 85 88 6c 81 48 83 a0 93 c0 c0 82 8b 81 84 85 f9 ..l.H........... +| 192: 88 7a 00 7f 00 96 40 7b 12 4b 84 75 a0 00 99 a0 .z....@..K.u.... +| 208: df a0 7e 81 c6 90 8f 7f 84 85 cc 84 82 90 88 60 ..~............` +| 224: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 240: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 256: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 272: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 288: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 848: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 864: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 880: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 896: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 912: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 928: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 944: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 960: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 3 offset 2048 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 848: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 864: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 880: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 896: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 912: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 928: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 944: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 960: 80 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 4 offset 3072 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 5f 5f 5f 5f 5f 5f 5f 00 d5 fe fe fe 08 00 00 00 _______......... +| end clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +}]} {} + +sqlite3_dbdata_init db +do_execsql_test 1.1 { + PRAGMA writable_schema = 1; +} + +do_test 1.2 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +#------------------------------------------------------------------------- +reset_db +do_test 2.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 3821 pagesize 1024 filename clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 04 00 01 01 00 40 20 20 00 00 00 01 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ +| 96: 00 2e 7a 70 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 a0 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 29 29 ..............)) +| 464: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 480: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 496: 29 29 29 dd dd dd dd dd dd dd dd dd dd dd dd dd )))............. +| 512: dd dd dd dd dd dd dd dd dd 6e 69 d2 e9 e9 e9 d2 .........ni..... +| 528: d2 d2 d2 d2 dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 544: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 560: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 576: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 592: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 608: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 624: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 640: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 656: dd dd dd dd dd dd dd da dd dd dd dd dd dd dd dd ................ +| 672: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 688: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 704: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 720: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 736: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 752: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 768: dd dd dd dd dd dd dd dd dd dd dd dd dd 29 29 29 .............))) +| 784: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 800: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 816: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 832: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 848: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 864: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 880: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 896: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 912: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 928: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 944: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 960: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 976: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 992: 29 29 29 29 29 29 29 29 29 29 dd dd dd dd dd dd ))))))))))...... +| 1008: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| page 2 offset 1024 +| 0: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 16: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 32: dd dd 6e 69 d2 e9 e9 e9 d2 d2 d2 d2 d2 dd dd dd ..ni............ +| 48: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 64: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 80: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 96: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 112: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 128: dd dd dd dd dd dd 29 29 29 29 29 29 29 29 29 29 ......)))))))))) +| 144: 29 29 29 29 29 29 29 29 29 ad a5 29 29 29 29 00 )))))))))..)))). +| 160: 75 9c 11 00 5b e5 64 28 7c ca 09 69 28 2d 69 00 u...[.d(|..i(-i. +| 176: 85 88 6c 81 48 83 a0 93 c0 c0 82 8b 81 84 85 f9 ..l.H........... +| 192: 88 7a 00 7f 00 96 40 7b 12 4b 84 75 a0 00 99 a0 .z....@..K.u.... +| 208: df a0 7e 81 c6 90 8f 7f 84 85 cc 84 82 90 88 60 ..~............` +| 224: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 240: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 256: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 272: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 288: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 848: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 864: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 880: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 896: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 912: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 928: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 944: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 960: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 3 offset 2048 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 848: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 864: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 880: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 896: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 912: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 928: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 944: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 960: 80 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 4 offset 3072 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 5f 5f 5f 5f 5f 5f 5f 00 d5 fe fe fe 08 00 00 00 _______......... +| end clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +}]} {} + +sqlite3_dbdata_init db +do_execsql_test 2.1 { + PRAGMA writable_schema = 1; +} + +do_test 2.2 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +finish_test + diff --git a/ext/recover/recovercorrupt4.test b/ext/recover/recovercorrupt4.test new file mode 100644 index 000000000..0ff0b502d --- /dev/null +++ b/ext/recover/recovercorrupt4.test @@ -0,0 +1,64 @@ +# 2024 May 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recovercorrupt4 + +database_may_be_corrupt + +if {[permutation]!="inmemory_journal"} { + # This test cannot be run with the inmemory_journal permutation, as it + # must open a truncated, corrupt, database file. With the inmemory_journal + # permutation, this fails (SQLITE_CORRUPT error) when the [sqlite3] wrapper + # executes "PRAGMA journal_mode = memory". + do_execsql_test 1.0 { + CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL, filler BLOB NOT NULL DEFAULT 13); + -- CREATE UNIQUE INDEX rows_index ON rows(indexed); + INSERT INTO rows(indexed, unindexed, filler) VALUES(1, 1, x'31'); + INSERT INTO rows(indexed, unindexed, filler) VALUES(2, 2, x'32'); + INSERT INTO rows(indexed, unindexed, filler) VALUES(4, 4, x'34'); + INSERT INTO rows(indexed, unindexed, filler) VALUES(8, 8, randomblob(2048)); + } + + db close + + do_test 1.1 { + set sz [expr [file size test.db] - 1024] + set fd [open test.db] + fconfigure $fd -encoding binary -translation binary + + set data [read $fd $sz] + set fd2 [open test.db2 w] + fconfigure $fd2 -encoding binary -translation binary + puts -nonewline $fd2 $data + close $fd2 + set {} {} + } {} + + do_test 1.2 { + forcedelete test.db3 + sqlite3 db test.db2 + set R [sqlite3_recover_init db main test.db3] + $R run + $R finish + } {} + + do_test 1.3 { + sqlite3 db test.db3 + execsql { + SELECT indexed, unindexed FROM rows + } + } {1 1 2 2 4 4 8 8} +} + +finish_test + diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 02127fa11..299b5b54b 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -1841,6 +1841,8 @@ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ return SQLITE_OK; } +int sqlite3IntFloatCompare(i64,double); + /* ** Rtree virtual table module xFilter method. */ @@ -1870,7 +1872,8 @@ static int rtreeFilter( i64 iNode = 0; int eType = sqlite3_value_numeric_type(argv[0]); if( eType==SQLITE_INTEGER - || (eType==SQLITE_FLOAT && sqlite3_value_double(argv[0])==iRowid) + || (eType==SQLITE_FLOAT + && 0==sqlite3IntFloatCompare(iRowid,sqlite3_value_double(argv[0]))) ){ rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); }else{ @@ -3225,6 +3228,7 @@ static int rtreeUpdate( */ static int rtreeBeginTransaction(sqlite3_vtab *pVtab){ Rtree *pRtree = (Rtree *)pVtab; + assert( pRtree->inWrTrans==0 ); pRtree->inWrTrans = 1; return SQLITE_OK; } diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index 61664e152..e596df71d 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -797,4 +797,22 @@ do_test 23.0 { db eval {PRAGMA integrity_check;} } {ok} +reset_db +do_execsql_test 24.0 { + CREATE VIRTUAL TABLE rt1 USING rtree_i32(rid, c1, c2); + INSERT INTO rt1(rid, c1, c2) VALUES (9223372036854775807, 10, 18); +} + +do_execsql_test 24.1 { + SELECT (rid = (CAST (9223372036854775807 AS REAL))) + FROM rt1 WHERE + (rid = (CAST (9223372036854775807 AS REAL))); +} + +do_execsql_test 24.2 { + DELETE FROM rt1; + INSERT INTO rt1(rid, c1, c2) VALUES(1,2,3); + SELECT * FROM rt1 WHERE rid=1.005; +} {} + finish_test diff --git a/ext/session/sessionchange.test b/ext/session/sessionchange.test new file mode 100644 index 000000000..c1c28622e --- /dev/null +++ b/ext/session/sessionchange.test @@ -0,0 +1,101 @@ +# 2011 March 07 +# +# 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. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionchange + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); +} + +do_test 1.1 { + set C [changeset_from_sql { + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(10, 20, 30); + }] + + set res [list] + set iter [sqlite3changeset_start $C] + while {[$iter next]=="SQLITE_ROW"} { + lappend res [$iter data] + } + $iter finalize + + set res +} [list \ + {INSERT t1 0 X.. {} {i 10 i 20 i 30}} \ + {INSERT t1 0 X.. {} {i 1 i 2 i 3}} \ +] + + +do_test 1.2 { + sqlite3changegroup grp + set iter [sqlite3changeset_start $C] + while {[$iter next]=="SQLITE_ROW"} { + grp add_change $iter + } + $iter finalize + + set res [list] + grp output + sqlite3session_foreach c [grp output] { lappend res $c } + grp delete + set res +} [list \ + {INSERT t1 0 X.. {} {i 10 i 20 i 30}} \ + {INSERT t1 0 X.. {} {i 1 i 2 i 3}} \ +] + +do_test 1.3.1 { + set iter [sqlite3changeset_start $C] + sqlite3changegroup grp + list [catch { grp add_change $iter } msg] $msg +} {1 SQLITE_ERROR} +do_test 1.3.2 { + while {[$iter next]=="SQLITE_ROW"} { } + list [catch { grp add_change $iter } msg] $msg +} {1 SQLITE_ERROR} +grp delete +$iter finalize + +do_test 1.4 { + set res [list] + set iter [sqlite3changeset_start -invert $C] + while {[$iter next]=="SQLITE_ROW"} { + lappend res [$iter data] + } + $iter finalize + set res +} [list \ + {DELETE t1 0 X.. {i 10 i 20 i 30} {}} \ + {DELETE t1 0 X.. {i 1 i 2 i 3} {}} \ +] + +do_test 1.5 { + sqlite3changegroup grp + set iter [sqlite3changeset_start -invert $C] + $iter next + list [catch { grp add_change $iter } msg] $msg +} {1 SQLITE_ERROR} +$iter finalize +grp delete + + + +finish_test diff --git a/ext/session/sessionconflict.test b/ext/session/sessionconflict.test new file mode 100644 index 000000000..25b260299 --- /dev/null +++ b/ext/session/sessionconflict.test @@ -0,0 +1,76 @@ +# 2011 March 07 +# +# 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. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionconflict + +db close +sqlite3_shutdown +test_sqlite3_log log +proc log {code msg} { puts "LOG $code $msg" } +sqlite3 db test.db + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 1.0 { + do_common_sql { + CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE); + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); + } +} {} + +do_execsql_test -db db2 1.1 { + INSERT INTO t1 VALUES(6, 6, 6); +} + +proc xConflict {args} { + return "ABORT" +} + + +do_test 1.2 { + set chng [changeset_from_sql { + UPDATE t1 SET b=10, c=10 WHERE a=1; + UPDATE t1 SET b=444 WHERE a=2; + INSERT INTO t1 VALUES(4, 4, 4); + INSERT INTO t1 VALUES(5, 5, 5); + INSERT INTO t1 VALUES(6, 6, 6); + }] + + execsql BEGIN db2 + set res [list [catch { sqlite3changeset_apply db2 $chng xConflict } msg] $msg] + execsql ROLLBACK db2 + set res +} {1 SQLITE_ABORT} + +do_execsql_test -db db2 1.3 { + SELECT * FROM t1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 6 6 6 +} + + + +finish_test diff --git a/ext/session/sessionstat1.test b/ext/session/sessionstat1.test index 2757d6044..16dc4e272 100644 --- a/ext/session/sessionstat1.test +++ b/ext/session/sessionstat1.test @@ -92,7 +92,7 @@ do_test 2.1 { } {} do_execsql_test -db db2 2.2 { - SELECT * FROM sqlite_stat1 + SELECT * FROM sqlite_stat1 ORDER BY tbl, idx } { t1 sqlite_autoindex_t1_1 {32 1} t1 t1b {32 4} @@ -104,7 +104,7 @@ do_test 2.3 { } {} do_execsql_test -db db2 2.4 { - SELECT * FROM sqlite_stat1 + SELECT * FROM sqlite_stat1 ORDER BY tbl, idx; } { t1 sqlite_autoindex_t1_1 {32 1} t1 t1b {32 4} diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index acb945194..7a8132bfa 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -3685,14 +3685,14 @@ static int sessionChangesetNextOne( p->rc = sessionInputBuffer(&p->in, 2); if( p->rc!=SQLITE_OK ) return p->rc; + sessionDiscardData(&p->in); + p->in.iCurrent = p->in.iNext; + /* If the iterator is already at the end of the changeset, return DONE. */ if( p->in.iNext>=p->in.nData ){ return SQLITE_DONE; } - sessionDiscardData(&p->in); - p->in.iCurrent = p->in.iNext; - op = p->in.aData[p->in.iNext++]; while( op=='T' || op=='P' ){ if( pbNew ) *pbNew = 1; @@ -5427,6 +5427,7 @@ struct sqlite3_changegroup { int rc; /* Error code */ int bPatch; /* True to accumulate patchsets */ SessionTable *pList; /* List of tables in current patch */ + SessionBuffer rec; sqlite3 *db; /* Configured by changegroup_schema() */ char *zDb; /* Configured by changegroup_schema() */ @@ -5725,108 +5726,128 @@ static int sessionChangesetExtendRecord( } /* -** Add all changes in the changeset traversed by the iterator passed as -** the first argument to the changegroup hash tables. +** Locate or create a SessionTable object that may be used to add the +** change currently pointed to by iterator pIter to changegroup pGrp. +** If successful, set output variable (*ppTab) to point to the table +** object and return SQLITE_OK. Otherwise, if some error occurs, return +** an SQLite error code and leave (*ppTab) set to NULL. */ -static int sessionChangesetToHash( - sqlite3_changeset_iter *pIter, /* Iterator to read from */ - sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ - int bRebase /* True if hash table is for rebasing */ +static int sessionChangesetFindTable( + sqlite3_changegroup *pGrp, + const char *zTab, + sqlite3_changeset_iter *pIter, + SessionTable **ppTab ){ - u8 *aRec; - int nRec; int rc = SQLITE_OK; SessionTable *pTab = 0; - SessionBuffer rec = {0, 0, 0}; + int nTab = (int)strlen(zTab); + u8 *abPK = 0; + int nCol = 0; - while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ - const char *zNew; - int nCol; - int op; - int iHash; - int bIndirect; - SessionChange *pChange; - SessionChange *pExist = 0; - SessionChange **pp; - - /* Ensure that only changesets, or only patchsets, but not a mixture - ** of both, are being combined. It is an error to try to combine a - ** changeset and a patchset. */ - if( pGrp->pList==0 ){ - pGrp->bPatch = pIter->bPatchset; - }else if( pIter->bPatchset!=pGrp->bPatch ){ - rc = SQLITE_ERROR; - break; - } + *ppTab = 0; + sqlite3changeset_pk(pIter, &abPK, &nCol); - sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect); - if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){ - /* Search the list for a matching table */ - int nNew = (int)strlen(zNew); - u8 *abPK; + /* Search the list for an existing table */ + for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break; + } - sqlite3changeset_pk(pIter, &abPK, 0); - for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ - if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break; + /* If one was not found above, create a new table now */ + if( !pTab ){ + SessionTable **ppNew; + + pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nTab+1); + if( !pTab ){ + return SQLITE_NOMEM; + } + memset(pTab, 0, sizeof(SessionTable)); + pTab->nCol = nCol; + pTab->abPK = (u8*)&pTab[1]; + memcpy(pTab->abPK, abPK, nCol); + pTab->zName = (char*)&pTab->abPK[nCol]; + memcpy(pTab->zName, zTab, nTab+1); + + if( pGrp->db ){ + pTab->nCol = 0; + rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); + if( rc ){ + assert( pTab->azCol==0 ); + sqlite3_free(pTab); + return rc; } - if( !pTab ){ - SessionTable **ppTab; + } - pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1); - if( !pTab ){ - rc = SQLITE_NOMEM; - break; - } - memset(pTab, 0, sizeof(SessionTable)); - pTab->nCol = nCol; - pTab->abPK = (u8*)&pTab[1]; - memcpy(pTab->abPK, abPK, nCol); - pTab->zName = (char*)&pTab->abPK[nCol]; - memcpy(pTab->zName, zNew, nNew+1); - - if( pGrp->db ){ - pTab->nCol = 0; - rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); - if( rc ){ - assert( pTab->azCol==0 ); - sqlite3_free(pTab); - break; - } - } + /* The new object must be linked on to the end of the list, not + ** simply added to the start of it. This is to ensure that the + ** tables within the output of sqlite3changegroup_output() are in + ** the right order. */ + for(ppNew=&pGrp->pList; *ppNew; ppNew=&(*ppNew)->pNext); + *ppNew = pTab; + } - /* The new object must be linked on to the end of the list, not - ** simply added to the start of it. This is to ensure that the - ** tables within the output of sqlite3changegroup_output() are in - ** the right order. */ - for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext); - *ppTab = pTab; - } + /* Check that the table is compatible. */ + if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ + rc = SQLITE_SCHEMA; + } - if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ - rc = SQLITE_SCHEMA; - break; - } - } + *ppTab = pTab; + return rc; +} - if( nColnCol ){ - assert( pGrp->db ); - rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec); - if( rc ) break; - aRec = rec.aBuf; - nRec = rec.nBuf; - } +/* +** Add the change currently indicated by iterator pIter to the hash table +** belonging to changegroup pGrp. +*/ +static int sessionOneChangeToHash( + sqlite3_changegroup *pGrp, + sqlite3_changeset_iter *pIter, + int bRebase +){ + int rc = SQLITE_OK; + int nCol = 0; + int op = 0; + int iHash = 0; + int bIndirect = 0; + SessionChange *pChange = 0; + SessionChange *pExist = 0; + SessionChange **pp = 0; + SessionTable *pTab = 0; + u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2]; + int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2; - if( sessionGrowHash(0, pIter->bPatchset, pTab) ){ - rc = SQLITE_NOMEM; - break; - } + /* Ensure that only changesets, or only patchsets, but not a mixture + ** of both, are being combined. It is an error to try to combine a + ** changeset and a patchset. */ + if( pGrp->pList==0 ){ + pGrp->bPatch = pIter->bPatchset; + }else if( pIter->bPatchset!=pGrp->bPatch ){ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_OK ){ + const char *zTab = 0; + sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); + rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab); + } + + if( rc==SQLITE_OK && nColnCol ){ + SessionBuffer *pBuf = &pGrp->rec; + rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf); + aRec = pBuf->aBuf; + nRec = pBuf->nBuf; + assert( pGrp->db ); + } + + if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){ + rc = SQLITE_NOMEM; + } + + if( rc==SQLITE_OK ){ + /* Search for existing entry. If found, remove it from the hash table. + ** Code below may link it back in. */ iHash = sessionChangeHash( pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange ); - - /* Search for existing entry. If found, remove it from the hash table. - ** Code below may link it back in. - */ for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){ int bPkOnly1 = 0; int bPkOnly2 = 0; @@ -5841,19 +5862,41 @@ static int sessionChangesetToHash( break; } } + } + if( rc==SQLITE_OK ){ rc = sessionChangeMerge(pTab, bRebase, pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange ); - if( rc ) break; - if( pChange ){ - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; - pTab->nEntry++; - } + } + if( rc==SQLITE_OK && pChange ){ + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + pTab->nEntry++; + } + + if( rc==SQLITE_OK ) rc = pIter->rc; + return rc; +} + +/* +** Add all changes in the changeset traversed by the iterator passed as +** the first argument to the changegroup hash tables. +*/ +static int sessionChangesetToHash( + sqlite3_changeset_iter *pIter, /* Iterator to read from */ + sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ + int bRebase /* True if hash table is for rebasing */ +){ + u8 *aRec; + int nRec; + int rc = SQLITE_OK; + + while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){ + rc = sessionOneChangeToHash(pGrp, pIter, bRebase); + if( rc!=SQLITE_OK ) break; } - sqlite3_free(rec.aBuf); if( rc==SQLITE_OK ) rc = pIter->rc; return rc; } @@ -5981,6 +6024,23 @@ int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){ return rc; } +/* +** Add a single change to a changeset-group. +*/ +int sqlite3changegroup_add_change( + sqlite3_changegroup *pGrp, + sqlite3_changeset_iter *pIter +){ + if( pIter->in.iCurrent==pIter->in.iNext + || pIter->rc!=SQLITE_OK + || pIter->bInvert + ){ + /* Iterator does not point to any valid entry or is an INVERT iterator. */ + return SQLITE_ERROR; + } + return sessionOneChangeToHash(pGrp, pIter, 0); +} + /* ** Obtain a buffer containing a changeset representing the concatenation ** of all changesets added to the group so far. @@ -6030,6 +6090,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ if( pGrp ){ sqlite3_free(pGrp->zDb); sessionDeleteTable(0, pGrp->pList); + sqlite3_free(pGrp->rec.aBuf); sqlite3_free(pGrp); } } @@ -6431,6 +6492,7 @@ int sqlite3rebaser_rebase_strm( void sqlite3rebaser_delete(sqlite3_rebaser *p){ if( p ){ sessionDeleteTable(0, p->grp.pList); + sqlite3_free(p->grp.rec.aBuf); sqlite3_free(p); } } diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 160ea8786..f950d419b 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1057,6 +1057,30 @@ int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb); */ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); +/* +** CAPI3REF: Add A Single Change To A Changegroup +** METHOD: sqlite3_changegroup +** +** This function adds the single change currently indicated by the iterator +** passed as the second argument to the changegroup object. The rules for +** adding the change are just as described for [sqlite3changegroup_add()]. +** +** If the change is successfully added to the changegroup, SQLITE_OK is +** returned. Otherwise, an SQLite error code is returned. +** +** The iterator must point to a valid entry when this function is called. +** If it does not, SQLITE_ERROR is returned and no change is added to the +** changegroup. Additionally, the iterator must not have been opened with +** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also +** returned. +*/ +int sqlite3changegroup_add_change( + sqlite3_changegroup*, + sqlite3_changeset_iter* +); + + + /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup ** METHOD: sqlite3_changegroup diff --git a/ext/session/test_session.c b/ext/session/test_session.c index af42351ba..00c3c2506 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -1038,6 +1038,64 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat( return rc; } +static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){ + Tcl_Obj *pVar = 0; + int nCol; /* Number of columns in table */ + int nCol2; /* Number of columns in table */ + int op; /* SQLITE_INSERT, UPDATE or DELETE */ + const char *zTab; /* Name of table change applies to */ + Tcl_Obj *pOld; /* Vector of old.* values */ + Tcl_Obj *pNew; /* Vector of new.* values */ + int bIndirect; + + char *zPK; + unsigned char *abPK; + int i; + + sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); + pVar = Tcl_NewObj(); + + Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( + op==SQLITE_INSERT ? "INSERT" : + op==SQLITE_UPDATE ? "UPDATE" : + "DELETE", -1 + )); + + Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); + Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); + + zPK = ckalloc(nCol+1); + memset(zPK, 0, nCol+1); + sqlite3changeset_pk(pIter, &abPK, &nCol2); + assert( nCol==nCol2 ); + for(i=0; ipGrp, pIter->pIter); + if( rc!=SQLITE_OK ){ + rc = test_session_error(interp, rc, 0); + } + break; + }; + default: { /* delete */ assert( iSub==3 ); Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); @@ -1585,6 +1609,115 @@ static int SQLITE_TCLAPI test_sqlite3changegroup( return TCL_OK; } +extern const char *sqlite3ErrName(int); + +/* +** Destructor for Tcl iterator command object. +*/ +static void test_iter_del(void *clientData){ + TestChangeIter *p = (TestChangeIter*)clientData; + sqlite3changeset_finalize(p->pIter); + ckfree(p); +} + +static int SQLITE_TCLAPI test_iter_cmd( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + static const char *aSub[] = { + "next", /* 0 */ + "data", /* 1 */ + "finalize", /* 2 */ + 0 + }; + int iSub = 0; + + TestChangeIter *p = (TestChangeIter*)clientData; + int rc = SQLITE_OK; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "CMD"); + return TCL_ERROR; + } + + if( Tcl_GetIndexFromObj(interp, objv[1], aSub, "sub-command", 0, &iSub) ){ + return TCL_ERROR; + } + switch( iSub ){ + case 0: + rc = sqlite3changeset_next(p->pIter); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + break; + case 1: + Tcl_SetObjResult(interp, testIterData(p->pIter)); + break; + case 2: + rc = sqlite3changeset_finalize(p->pIter); + p->pIter = 0; + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + break; + default: + assert( 0 ); + break; + } + + return TCL_OK; +} + +/* +** Tclcmd: sqlite3changeset_start ?-invert? CHANGESET +*/ +static int SQLITE_TCLAPI test_sqlite3changeset_start( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int isInvert = 0; + void *pChangeset = 0; /* Buffer containing changeset */ + int nChangeset = 0; /* Size of buffer aChangeset in bytes */ + TestChangeIter *pNew = 0; + sqlite3_changeset_iter *pIter = 0; + int flags = 0; + int rc = SQLITE_OK; + + static int iCmd = 1; + char zCmd[64]; + + if( objc==3 ){ + int n = 0; + const char *z = Tcl_GetStringFromObj(objv[1], &n); + isInvert = (n>=2 && sqlite3_strnicmp(z, "-invert", n)==0); + } + + if( objc!=2 && (objc!=3 || !isInvert) ){ + Tcl_WrongNumArgs(interp, 1, objv, "?-invert? CHANGESET"); + return TCL_ERROR; + } + + flags = isInvert ? SQLITE_CHANGESETSTART_INVERT : 0; + pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[objc-1], &nChangeset); + rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, flags); + if( rc!=SQLITE_OK ){ + char *zErr = sqlite3_mprintf( + "error in sqlite3changeset_start_v2() - %d", rc + ); + Tcl_AppendResult(interp, zErr, (char*)0); + return TCL_ERROR; + } + + pNew = (TestChangeIter*)ckalloc(sizeof(TestChangeIter)); + pNew->pIter = pIter; + + sprintf(zCmd, "csiter%d", iCmd++); + Tcl_CreateObjCommand(interp, zCmd, test_iter_cmd, (void*)pNew, test_iter_del); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1)); + return TCL_OK; +} + int TestSession_Init(Tcl_Interp *interp){ struct Cmd { const char *zCmd; @@ -1592,6 +1725,7 @@ int TestSession_Init(Tcl_Interp *interp){ } aCmd[] = { { "sqlite3session", test_sqlite3session }, { "sqlite3changegroup", test_sqlite3changegroup }, + { "sqlite3changeset_start", test_sqlite3changeset_start }, { "sqlite3session_foreach", test_sqlite3session_foreach }, { "sqlite3changeset_invert", test_sqlite3changeset_invert }, { "sqlite3changeset_concat", test_sqlite3changeset_concat }, diff --git a/ext/userauth/user-auth.txt b/ext/userauth/user-auth.txt index ba4eabc13..9d6ba2333 100644 --- a/ext/userauth/user-auth.txt +++ b/ext/userauth/user-auth.txt @@ -1,3 +1,15 @@ +*********************************** NOTICE ************************************ +* This extension is deprecated. The SQLite developers do not maintain this * +* extension. At some point in the future, it might disappear from the source * +* tree. * +* * +* If you are using this extension and think it should be supported moving * +* forward, visit the SQLite Forum (https://sqlite.org/forum) and argue your * +* case there. * +* * +* This deprecation notice was added on 2024-01-22. * +******************************************************************************* + Activate the user authentication logic by including the ext/userauth/userauth.c source code file in the build and adding the -DSQLITE_USER_AUTHENTICATION compile-time option. diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 8f733b668..115374e62 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -43,8 +43,9 @@ # which generates the makefile code, rather than using $(call) and # $(eval), or at least centralize the setup of the numerous vars # related to each build variant $(JS_BUILD_MODES). (Update: an -# external script was attempted but it's even less legible than the -# $(eval) indirection going on in this file. +# external script was attempted but generating properly-escaped +# makefile code from within a shell script is even less legible +# than the $(eval) indirection going on in this file.) # default: all #default: quick @@ -288,6 +289,12 @@ endif # embedding in the JS files and in building the distribution zip file. # It must NOT be in $(dir.tmp) because we need it to survive the # cleanup process for the dist build to work properly. +# +# Slight caveat: this uses the version info from the in-tree +# sqlite3.c/h, which may diff from a user-provided $(sqlite3.c). The +# end result is that the generated JS files may have static version +# info from $(bin.version-info) which differ from their runtime-emited +# version info (e.g. from sqlite3_libversion()). bin.version-info := $(dir.top)/version-info .NOTPARALLEL: $(bin.version-info) $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile @@ -306,8 +313,9 @@ DISTCLEAN_FILES += $(bin.stripccomments) ######################################################################## -# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via ./c-pp -f -# $(1) ... +# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via: +# +# ./c-pp -f $(1) -o $(2) $(3) # # Historical notes: # @@ -333,19 +341,26 @@ DISTCLEAN_FILES += $(bin.stripccomments) # # Note that the SQLITE_... build flags used here have NO EFFECT on the # JS/WASM build. They are solely for use with $(bin.c-pp) itself. +# +# -D... flags which should be included in all invocations should be +# appended to $(C-PP.FILTER.global). bin.c-pp := ./c-pp $(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE) $(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \ -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \ -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \ -DSQLITE_TEMP_STORE=3 +C-PP.FILTER.global ?= +ifeq (1,$(SQLITE_C_IS_SEE)) + C-PP.FILTER.global += -Denable-see +endif define C-PP.FILTER # Create $2 from $1 using $(bin.c-pp) # $1 = Input file: c-pp -f $(1).js # $2 = Output file: c-pp -o $(2).js # $3 = optional c-pp -D... flags $(2): $(1) $$(MAKEFILE) $$(bin.c-pp) - $$(bin.c-pp) -f $(1) -o $$@ $(3) + $$(bin.c-pp) -f $(1) -o $$@ $(3) $(C-PP.FILTER.global) CLEAN_FILES += $(2) endef # /end C-PP.FILTER @@ -432,8 +447,10 @@ sqlite3-api.jses += $(sqlite3-api-build-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js # sqlite3-api-worker.js = the Worker1 API: sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js -# sqlite3-v-helper = helper APIs for VFSes and VTABLEs: -sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js +# sqlite3-vfs-helper = helper APIs for VFSes: +sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js +# sqlite3-vtab-helper = helper APIs for VTABLEs: +sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js # sqlite3-vfs-opfs.c-pp.js = the first OPFS VFS: sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js # sqlite3-vfs-opfs-sahpool.c-pp.js = the second OPFS VFS: @@ -449,13 +466,14 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js # the first OPFS VFS and necessarily an external file. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js)) -sqlite3-api.ext.jses += $(SOAP.js.bld) +# +# $(sqlite3-api.ext.jses) = API-related files which are standalone files, +# not part of the amalgamation. +# +sqlite3-api.ext.jses := $(SOAP.js.bld) $(SOAP.js.bld): $(SOAP.js) cp $< $@ -all quick: $(sqlite3-api.ext.jses) -q: quick - ######################################################################## # $(sqlite3-api*.*js) contain the core library code but not the # Emscripten-related glue which deals with loading sqlite3.wasm. In @@ -528,6 +546,10 @@ emcc.jsflags += -sSTRICT_JS=0 # 3.1.31. The fix for that in newer emcc's is to throw a built-time # error if STRICT_JS is used together with those options. +# emcc.jsflags += -sSTRICT=1 +# -sSTRICT=1 Causes failures about unknown symbols which the build +# tools should be installing, e.g. __syscall_geteuid32 + # -sENVIRONMENT values for the various build modes: emcc.environment.vanilla := web,worker emcc.environment.bundler-friendly := $(emcc.environment.vanilla) @@ -548,6 +570,11 @@ emcc.environment.node := node # time with 16mb+ memory and 3X time when starting with 8MB. However, # such test results are inconsistent due to browser internals which # are opaque to us. +# +# 2024-03-04: emsdk 3.1.55 replaces INITIAL_MEMORY with INITIAL_HEAP, +# but also says (in its changelog): "Note that it is currently not +# supported in all configurations (#21071)." +# https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md emcc.jsflags += -sALLOW_MEMORY_GROWTH emcc.INITIAL_MEMORY.128 := 134217728 emcc.INITIAL_MEMORY.96 := 100663296 @@ -813,13 +840,13 @@ pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) # $4 = resulting sqlite-api JS/MJS file # $5 = resulting JS/MJS file # $6 = -D... flags for $(bin.c-pp) -# $7 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out) +# $7 = optional extra flags for emcc # # Maintenance reminder: be careful not to introduce spaces around args # ($1, $2), otherwise string concatenation will malfunction. # -# emcc.environment.$(2) must be set to a value for emcc's -# -sENVIRONMENT flag. +# Before calling this, emcc.environment.$(2) must be set to a value +# for emcc's -sENVIRONMENT flag. # # $(cflags.$(1)) and $(cflags.$(1).$(2)) may be defined to append # CFLAGS to a given build mode. @@ -926,18 +953,39 @@ sqlite3-worker1.js.in := $(dir.api)/sqlite3-worker1.c-pp.js sqlite3-worker1-promiser.js.in := $(dir.api)/sqlite3-worker1-promiser.c-pp.js sqlite3-worker1.js := $(dir.dout)/sqlite3-worker1.js sqlite3-worker1-promiser.js := $(dir.dout)/sqlite3-worker1-promiser.js -sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs +sqlite3-worker1-promiser.mjs := $(dir.dout)/sqlite3-worker1-promiser.mjs +sqlite3-worker1-bundler-friendly.mjs := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js))) -$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\ +$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.mjs),\ $(c-pp.D.sqlite3-bundler-friendly))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\ $(sqlite3-worker1-promiser-bundler-friendly.js),\ $(c-pp.D.sqlite3-bundler-friendly))) -$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \ +$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.mjs),\ + -Dtarget=es6-module -Dtarget=es6-bundler-friendly)) +$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.mjs) \ $(sqlite3-worker1-promiser-bundler-friendly.js) -$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) +$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.js)) +$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.mjs,\ + -Dtarget=es6-module)) +$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser.html)) +$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser-esm.html,\ + -Dtarget=es6-module)) +all: $(sqlite3-worker1.js) \ + $(sqlite3-worker1-promiser.js) $(sqlite3-worker1-promiser.mjs) + +demo-worker1-promiser.html: $(sqlite3-worker1-promiser.js) demo-worker1-promiser.js +demo-worker1-promiser-esm.html: $(sqlite3-worker1-promiser.mjs) demo-worker1-promiser.mjs +all: demo-worker1-promiser.html demo-worker1-promiser-esm.html + +sqlite3-api.ext.jses += \ + $(sqlite3-worker1-promiser.mjs) \ + $(sqlite3-worker1-bundler-friendly.mjs) \ + $(sqlite3-worker1.js) +all quick: $(sqlite3-api.ext.jses) +q: quick ######################################################################## # batch-runner.js is part of one of the test apps which reads in SQL @@ -978,6 +1026,9 @@ emcc.speedtest1.common += -sABORTING_MALLOC emcc.speedtest1.common += -sSTRICT_JS=0 emcc.speedtest1.common += -sMODULARIZE emcc.speedtest1.common += -Wno-limited-postlink-optimizations +emcc.speedtest1.common += -Wno-unused-main +# ^^^^ -Wno-unused-main is for emcc 3.1.52+. speedtest1 has a wasm_main() which is +# exported and called by the JS code. EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) emcc.speedtest1.common += -sSTACK_SIZE=512KB emcc.speedtest1.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) diff --git a/ext/wasm/SQLTester/SQLTester.mjs b/ext/wasm/SQLTester/SQLTester.mjs index a72399aef..71c5d4c53 100644 --- a/ext/wasm/SQLTester/SQLTester.mjs +++ b/ext/wasm/SQLTester/SQLTester.mjs @@ -174,11 +174,17 @@ const Rx = newObj({ squiggly: /[{}]/ }); + + const Util = newObj({ toss, - unlink: function(fn){ - return 0==sqlite3.wasm.sqlite3_wasm_vfs_unlink(0,fn); + unlink: function f(fn){ + if(!f.unlink){ + f.unlink = sqlite3.wasm.xWrap('sqlite3__wasm_vfs_unlink','int', + ['*','string']); + } + return 0==f.unlink(0,fn); }, argvToString: (list)=>{ @@ -197,7 +203,7 @@ const Util = newObj({ utf8Encode: (str)=>__utf8Encoder.encode(str), - strglob: sqlite3.wasm.xWrap('sqlite3_wasm_SQLTester_strglob','int', + strglob: sqlite3.wasm.xWrap('sqlite3__wasm_SQLTester_strglob','int', ['string','string']) })/*Util*/; diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md index eb0f073cf..ebd4aaacb 100644 --- a/ext/wasm/api/README.md +++ b/ext/wasm/api/README.md @@ -78,10 +78,12 @@ browser client: a Promise-based interface into the Worker #1 API. This is a far user-friendlier way to interface with databases running in a Worker thread. -- **`sqlite3-v-helper.js`**\ - Installs `sqlite3.vfs` and `sqlite3.vtab`, namespaces which contain - helpers for use by downstream code which creates `sqlite3_vfs` - and `sqlite3_module` implementations. +- **`sqlite3-vfs-helper.js`**\ + Installs the `sqlite3.vfs` namespace, which contain helpers for use + by downstream code which creates `sqlite3_vfs` implementations. +- **`sqlite3-vtab-helper.js`**\ + Installs the `sqlite3.vtab` namespace, which contain helpers for use + by downstream code which creates `sqlite3_module` implementations. - **`sqlite3-vfs-opfs.c-pp.js`**\ is an sqlite3 VFS implementation which supports the Origin-Private FileSystem (OPFS) as a storage layer to provide persistent storage diff --git a/ext/wasm/api/post-js-header.js b/ext/wasm/api/post-js-header.js index 0e27e1fd9..7fd82a7d6 100644 --- a/ext/wasm/api/post-js-header.js +++ b/ext/wasm/api/post-js-header.js @@ -19,8 +19,10 @@ Module.postRun.push(function(Module/*the Emscripten-style module object*/){ - sqlite3-api-glue.js => glues previous parts together - sqlite3-api-oo.js => SQLite3 OO API #1 - sqlite3-api-worker1.js => Worker-based API - - sqlite3-vfs-helper.js => Internal-use utilities for... - - sqlite3-vfs-opfs.js => OPFS VFS + - sqlite3-vfs-helper.c-pp.js => Utilities for VFS impls + - sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls + - sqlite3-vfs-opfs.c-pp.js => OPFS VFS + - sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS - sqlite3-api-cleanup.js => final API cleanup - post-js-footer.js => closes this postRun() function */ diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 29efb3e07..83b2ee172 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -14,7 +14,8 @@ previous steps of the sqlite3-api.js bootstrapping process: sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It initializes the main API pieces so that the downstream components - (e.g. sqlite3-api-oo1.js) have all that they need. + (e.g. sqlite3-api-oo1.js) have all of the infrastructure that they + need. */ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 'use strict'; @@ -328,7 +329,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); } - if(wasm.exports.sqlite3_activate_see instanceof Function){ +//#if enable-see + if(wasm.exports.sqlite3_key_v2 instanceof Function){ + /** + This code is capable of using an SEE build but note that an SEE + WASM build is generally incompatible with SEE's license + conditions. It is permitted for use internally in organizations + which have licensed SEE, but not for public sites because + exposing an SEE build of sqlite3.wasm effectively provides all + clients with a working copy of the commercial SEE code. + */ wasm.bindingSignatures.push( ["sqlite3_key", "int", "sqlite3*", "string", "int"], ["sqlite3_key_v2","int","sqlite3*","string","*","int"], @@ -337,10 +347,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_activate_see", undefined, "string"] ); } +//#endif enable-see + /** Functions which require BigInt (int64) support are separated from the others because we need to conditionally bind them or apply dummy impls, depending on the capabilities of the environment. + (That said: we never actually build without BigInt support, + and such builds are untested.) Note that not all of these functions directly require int64 but are only for use with APIs which require int64. For example, @@ -359,7 +373,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /* Careful! Short version: de/serialize() are problematic because they might use a different allocator than the user for managing the deserialized block. de/serialize() are ONLY safe to use with - sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, + sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. Because + of this, the canonical builds of sqlite3.wasm/js guarantee that + sqlite3.wasm.alloc() and friends use those allocators. Custom builds + may not guarantee that, however. */, ["sqlite3_drop_modules", "int", ["sqlite3*", "**"]], ["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]], ["sqlite3_malloc64", "*","i64"], @@ -422,8 +439,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ // Add session/changeset APIs... if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){ - /* ACHTUNG: 2022-12-23: the session/changeset API bindings are - COMPLETELY UNTESTED. */ /** FuncPtrAdapter options for session-related callbacks with the native signature "i(ps)". This proxy converts the 2nd argument @@ -601,16 +616,25 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Functions which are intended solely for API-internal use by the WASM components, not client code. These get installed into - sqlite3.wasm. Some of them get exposed to clients via variants - named sqlite3_js_...(). + sqlite3.util. Some of them get exposed to clients via variants + in sqlite3_js_...(). + + 2024-01-11: these were renamed, with two underscores in the + prefix, to ensure that clients do not accidentally depend on + them. They have always been documented as internal-use-only, so + no clients "should" be depending on the old names. */ - wasm.bindingSignatures.wasm = [ - ["sqlite3_wasm_db_reset", "int", "sqlite3*"], - ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], - ["sqlite3_wasm_vfs_create_file", "int", - "sqlite3_vfs*","string","*", "int"], - ["sqlite3_wasm_posix_create_file", "int", "string","*", "int"], - ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] + wasm.bindingSignatures.wasmInternal = [ + ["sqlite3__wasm_db_reset", "int", "sqlite3*"], + ["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], + [/* DO NOT USE. This is deprecated since 2023-08-11 because it can + trigger assert() in debug builds when used with file sizes + which are not sizes to a multiple of a valid db page size. */ + "sqlite3__wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int" + ], + ["sqlite3__wasm_posix_create_file", "int", "string","*", "int"], + ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"], + ["sqlite3__wasm_qfmt_token","string:dealloc", "string","int"] ]; /** @@ -652,7 +676,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Use case: sqlite3_bind_pointer() and sqlite3_result_pointer() call for "a static string and preferably a string - literal". This converter is used to ensure that the string + literal." This converter is used to ensure that the string value seen by those functions is long-lived and behaves as they need it to. */ @@ -674,14 +698,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. */ const __xArgPtr = wasm.xWrap.argAdapter('*'); - const nilType = function(){}/*a class no value can ever be an instance of*/; + const nilType = function(){ + /*a class which no value can ever be an instance of*/ + }; wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr) ('sqlite3_context*', __xArgPtr) ('sqlite3_value*', __xArgPtr) ('void*', __xArgPtr) ('sqlite3_changegroup*', __xArgPtr) ('sqlite3_changeset_iter*', __xArgPtr) - //('sqlite3_rebaser*', __xArgPtr) ('sqlite3_session*', __xArgPtr) ('sqlite3_stmt*', (v)=> __xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) @@ -742,8 +767,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ for(const e of wasm.bindingSignatures){ capi[e[0]] = wasm.xWrap.apply(null, e); } - for(const e of wasm.bindingSignatures.wasm){ - wasm[e[0]] = wasm.xWrap.apply(null, e); + for(const e of wasm.bindingSignatures.wasmInternal){ + util[e[0]] = wasm.xWrap.apply(null, e); } /* For C API functions which cannot work properly unless @@ -765,9 +790,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ implicitly making it part of the public interface. */ delete wasm.bindingSignatures; - if(wasm.exports.sqlite3_wasm_db_error){ + if(wasm.exports.sqlite3__wasm_db_error){ const __db_err = wasm.xWrap( - 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' + 'sqlite3__wasm_db_error', 'int', 'sqlite3*', 'int', 'string' ); /** Sets the given db's error state. Accepts: @@ -785,7 +810,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Returns the resulting code. Pass (pDb,0,0) to clear the error state. */ - util.sqlite3_wasm_db_error = function(pDb, resultCode, message){ + util.sqlite3__wasm_db_error = function(pDb, resultCode, message){ if(resultCode instanceof sqlite3.WasmAllocError){ resultCode = capi.SQLITE_NOMEM; message = 0 /*avoid allocating message string*/; @@ -796,17 +821,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return pDb ? __db_err(pDb, resultCode, message) : resultCode; }; }else{ - util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ - console.warn("sqlite3_wasm_db_error() is not exported.",arguments); + util.sqlite3__wasm_db_error = function(pDb,errCode,msg){ + console.warn("sqlite3__wasm_db_error() is not exported.",arguments); return errCode; }; } }/*xWrap() bindings*/ {/* Import C-level constants and structs... */ - const cJson = wasm.xCall('sqlite3_wasm_enum_json'); + const cJson = wasm.xCall('sqlite3__wasm_enum_json'); if(!cJson){ - toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", + toss("Maintenance required: increase sqlite3__wasm_enum_json()'s", "static buffer size!"); } //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); @@ -877,7 +902,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ delete capi[k]; } capi.sqlite3_vtab_config = wasm.xWrap( - 'sqlite3_wasm_vtab_config','int',[ + 'sqlite3__wasm_vtab_config','int',[ 'sqlite3*', 'int', 'int'] ); }/* end vtab-related setup */ @@ -889,7 +914,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ consistency with non-special-case wrappings. */ const __dbArgcMismatch = (pDb,f,n)=>{ - return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, + return util.sqlite3__wasm_db_error(pDb, capi.SQLITE_MISUSE, f+"() requires "+n+" argument"+ (1===n?"":'s')+"."); }; @@ -898,7 +923,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ argument and require SQLITE_UTF8. Sets the db error code to SQLITE_FORMAT and returns that code. */ const __errEncoding = (pDb)=>{ - return util.sqlite3_wasm_db_error( + return util.sqlite3__wasm_db_error( pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding." ); }; @@ -1128,7 +1153,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } return rc; }catch(e){ - return util.sqlite3_wasm_db_error(pDb, e); + return util.sqlite3__wasm_db_error(pDb, e); } }; @@ -1254,7 +1279,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return rc; }catch(e){ console.error("sqlite3_create_function_v2() setup threw:",e); - return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); + return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } }; @@ -1299,7 +1324,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return rc; }catch(e){ console.error("sqlite3_create_window_function() setup threw:",e); - return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); + return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } }; /** @@ -1394,7 +1419,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); default: - return util.sqlite3_wasm_db_error( + return util.sqlite3__wasm_db_error( pDb, capi.SQLITE_MISUSE, "Invalid SQL argument type for sqlite3_prepare_v2/v3()." ); @@ -1438,7 +1463,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if('string'===typeof text){ [p, n] = wasm.allocCString(text); }else{ - return util.sqlite3_wasm_db_error( + return util.sqlite3__wasm_db_error( capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, "Invalid 3rd argument type for sqlite3_bind_text()." ); @@ -1446,7 +1471,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); }catch(e){ wasm.dealloc(p); - return util.sqlite3_wasm_db_error( + return util.sqlite3__wasm_db_error( capi.sqlite3_db_handle(pStmt), e ); } @@ -1472,7 +1497,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if('string'===typeof pMem){ [p, n] = wasm.allocCString(pMem); }else{ - return util.sqlite3_wasm_db_error( + return util.sqlite3__wasm_db_error( capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, "Invalid 3rd argument type for sqlite3_bind_blob()." ); @@ -1480,7 +1505,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); }catch(e){ wasm.dealloc(p); - return util.sqlite3_wasm_db_error( + return util.sqlite3__wasm_db_error( capi.sqlite3_db_handle(pStmt), e ); } @@ -1504,11 +1529,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */ case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */ case capi.SQLITE_CONFIG_URI:// 17 /* int */ - return wasm.exports.sqlite3_wasm_config_i(op, args[0]); + return wasm.exports.sqlite3__wasm_config_i(op, args[0]); case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */ - return wasm.exports.sqlite3_wasm_config_ii(op, args[0], args[1]); + return wasm.exports.sqlite3__wasm_config_ii(op, args[0], args[1]); case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */ - return wasm.exports.sqlite3_wasm_config_j(op, args[0]); + return wasm.exports.sqlite3__wasm_config_j(op, args[0]); case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */ case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */ case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */ @@ -1529,6 +1554,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */ case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */ default: + /* maintenance note: we specifically do not include + SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that + it's only for legacy support and no apps written with + this API require that. */ return capi.SQLITE_NOTFOUND; } }; @@ -1574,11 +1603,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if( pKvvfs ){/* kvvfs-specific glue */ if(util.isUIThread()){ const kvvfsMethods = new capi.sqlite3_kvvfs_methods( - wasm.exports.sqlite3_wasm_kvvfs_methods() + wasm.exports.sqlite3__wasm_kvvfs_methods() ); delete capi.sqlite3_kvvfs_methods; - const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, + const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack, pstack = wasm.pstack; const kvvfsStorage = (zClass)=> @@ -1587,7 +1616,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Implementations for members of the object referred to by - sqlite3_wasm_kvvfs_methods(). We swap out the native + sqlite3__wasm_kvvfs_methods(). We swap out the native implementations with these, which use localStorage or sessionStorage for their backing store. */ @@ -1667,5 +1696,181 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }/*pKvvfs*/ + /* Warn if client-level code makes use of FuncPtrAdapter. */ wasm.xWrap.FuncPtrAdapter.warnOnUse = true; + + const StructBinder = sqlite3.StructBinder + /* we require a local alias b/c StructBinder is removed from the sqlite3 + object during the final steps of the API cleanup. */; + /** + Installs a StructBinder-bound function pointer member of the + given name and function in the given StructBinder.StructType + target object. + + It creates a WASM proxy for the given function and arranges for + that proxy to be cleaned up when tgt.dispose() is called. Throws + on the slightest hint of error, e.g. tgt is-not-a StructType, + name does not map to a struct-bound member, etc. + + As a special case, if the given function is a pointer, then + `wasm.functionEntry()` is used to validate that it is a known + function. If so, it is used as-is with no extra level of proxying + or cleanup, else an exception is thrown. It is legal to pass a + value of 0, indicating a NULL pointer, with the caveat that 0 + _is_ a legal function pointer in WASM but it will not be accepted + as such _here_. (Justification: the function at address zero must + be one which initially came from the WASM module, not a method we + want to bind to a virtual table or VFS.) + + This function returns a proxy for itself which is bound to tgt + and takes 2 args (name,func). That function returns the same + thing as this one, permitting calls to be chained. + + If called with only 1 arg, it has no side effects but returns a + func with the same signature as described above. + + ACHTUNG: because we cannot generically know how to transform JS + exceptions into result codes, the installed functions do no + automatic catching of exceptions. It is critical, to avoid + undefined behavior in the C layer, that methods mapped via + this function do not throw. The exception, as it were, to that + rule is... + + If applyArgcCheck is true then each JS function (as opposed to + function pointers) gets wrapped in a proxy which asserts that it + is passed the expected number of arguments, throwing if the + argument count does not match expectations. That is only intended + for dev-time usage for sanity checking, and may leave the C + environment in an undefined state. + */ + const installMethod = function callee( + tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck + ){ + if(!(tgt instanceof StructBinder.StructType)){ + toss("Usage error: target object is-not-a StructType."); + }else if(!(func instanceof Function) && !wasm.isPtr(func)){ + toss("Usage errror: expecting a Function or WASM pointer to one."); + } + if(1===arguments.length){ + return (n,f)=>callee(tgt, n, f, applyArgcCheck); + } + if(!callee.argcProxy){ + callee.argcProxy = function(tgt, funcName, func,sig){ + return function(...args){ + if(func.length!==arguments.length){ + toss("Argument mismatch for", + tgt.structInfo.name+"::"+funcName + +": Native signature is:",sig); + } + return func.apply(this, args); + } + }; + /* An ondispose() callback for use with + StructBinder-created types. */ + callee.removeFuncList = function(){ + if(this.ondispose.__removeFuncList){ + this.ondispose.__removeFuncList.forEach( + (v,ndx)=>{ + if('number'===typeof v){ + try{wasm.uninstallFunction(v)} + catch(e){/*ignore*/} + } + /* else it's a descriptive label for the next number in + the list. */ + } + ); + delete this.ondispose.__removeFuncList; + } + }; + }/*static init*/ + const sigN = tgt.memberSignature(name); + if(sigN.length<2){ + toss("Member",name,"does not have a function pointer signature:",sigN); + } + const memKey = tgt.memberKey(name); + const fProxy = (applyArgcCheck && !wasm.isPtr(func)) + /** This middle-man proxy is only for use during development, to + confirm that we always pass the proper number of + arguments. We know that the C-level code will always use the + correct argument count. */ + ? callee.argcProxy(tgt, memKey, func, sigN) + : func; + if(wasm.isPtr(fProxy)){ + if(fProxy && !wasm.functionEntry(fProxy)){ + toss("Pointer",fProxy,"is not a WASM function table entry."); + } + tgt[memKey] = fProxy; + }else{ + const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); + tgt[memKey] = pFunc; + if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){ + tgt.addOnDispose('ondispose.__removeFuncList handler', + callee.removeFuncList); + tgt.ondispose.__removeFuncList = []; + } + tgt.ondispose.__removeFuncList.push(memKey, pFunc); + } + return (n,f)=>callee(tgt, n, f, applyArgcCheck); + }/*installMethod*/; + installMethod.installMethodArgcCheck = false; + + /** + Installs methods into the given StructBinder.StructType-type + instance. Each entry in the given methods object must map to a + known member of the given StructType, else an exception will be + triggered. See installMethod() for more details, including the + semantics of the 3rd argument. + + As an exception to the above, if any two or more methods in the + 2nd argument are the exact same function, installMethod() is + _not_ called for the 2nd and subsequent instances, and instead + those instances get assigned the same method pointer which is + created for the first instance. This optimization is primarily to + accommodate special handling of sqlite3_module::xConnect and + xCreate methods. + + On success, returns its first argument. Throws on error. + */ + const installMethods = function( + structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + const seen = new Map /* map of */; + for(const k of Object.keys(methods)){ + const m = methods[k]; + const prior = seen.get(m); + if(prior){ + const mkey = structInstance.memberKey(k); + structInstance[mkey] = structInstance[structInstance.memberKey(prior)]; + }else{ + installMethod(structInstance, k, m, applyArgcCheck); + seen.set(m, k); + } + } + return structInstance; + }; + + /** + Equivalent to calling installMethod(this,...arguments) with a + first argument of this object. If called with 1 or 2 arguments + and the first is an object, it's instead equivalent to calling + installMethods(this,...arguments). + */ + StructBinder.StructType.prototype.installMethod = function callee( + name, func, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + return (arguments.length < 3 && name && 'object'===typeof name) + ? installMethods(this, ...arguments) + : installMethod(this, ...arguments); + }; + + /** + Equivalent to calling installMethods() with a first argument + of this object. + */ + StructBinder.StructType.prototype.installMethods = function( + methods, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + return installMethods(this, methods, applyArgcCheck); + }; + }); diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index 160d59db5..e557cbd57 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -87,6 +87,104 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ const __vfsPostOpenSql = Object.create(null); +//#if enable-see + /** + Converts ArrayBuffer or Uint8Array ba into a string of hex + digits. + */ + const byteArrayToHex = function(ba){ + if( ba instanceof ArrayBuffer ){ + ba = new Uint8Array(ba); + } + const li = []; + const digits = "0123456789abcdef"; + for( const d of ba ){ + li.push( digits[(d & 0xf0) >> 4], digits[d & 0x0f] ); + } + return li.join(''); + }; + + /** + Internal helper to apply an SEE key to a just-opened + database. Requires that db be-a DB object which has just been + opened, opt be the options object processed by its ctor, and opt + must have either the key, hexkey, or textkey properties, either + as a string, an ArrayBuffer, or a Uint8Array. + + This is a no-op in non-SEE builds. It throws on error and returns + without side effects if none of the key/textkey/hexkey options + are set. It throws if more than one is set or if any are set to + values of an invalid type. + + Returns true if it applies the key, else an unspecified falsy value. + */ + const dbCtorApplySEEKey = function(db,opt){ + if( !capi.sqlite3_key_v2 ) return; + let keytype; + let key; + const check = (opt.key ? 1 : 0) + (opt.hexkey ? 1 : 0) + (opt.textkey ? 1 : 0); + if( !check ) return; + else if( check>1 ){ + toss3(capi.SQLITE_MISUSE, + "Only ONE of (key, hexkey, textkey) may be provided."); + } + if( opt.key ){ + /* It is not legal to bind an argument to PRAGMA key=?, so we + convert it to a hexkey... */ + keytype = 'key'; + key = opt.key; + if('string'===typeof key){ + key = new TextEncoder('utf-8').encode(key); + } + if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){ + key = byteArrayToHex(key); + keytype = 'hexkey'; + }else{ + toss3(capi.SQLITE_MISUSE, + "Invalid value for the 'key' option. Expecting a string,", + "ArrayBuffer, or Uint8Array."); + return; + } + }else if( opt.textkey ){ + /* For textkey we need it to be in string form, so convert it to + a string if it's a byte array... */ + keytype = 'textkey'; + key = opt.textkey; + if(key instanceof ArrayBuffer){ + key = new Uint8Array(key); + } + if(key instanceof Uint8Array){ + key = new TextDecoder('utf-8').decode(key); + }else if('string'!==typeof key){ + toss3(capi.SQLITE_MISUSE, + "Invalid value for the 'textkey' option. Expecting a string,", + "ArrayBuffer, or Uint8Array."); + } + }else if( opt.hexkey ){ + keytype = 'hexkey'; + key = opt.hexkey; + if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){ + key = byteArrayToHex(key); + }else if('string'!==typeof key){ + toss3(capi.SQLITE_MISUSE, + "Invalid value for the 'hexkey' option. Expecting a string,", + "ArrayBuffer, or Uint8Array."); + } + /* else assume it's valid hex codes */ + }else{ + return; + } + let stmt; + try{ + stmt = db.prepare("PRAGMA "+keytype+"="+util.sqlite3__wasm_qfmt_token(key, 1)); + stmt.step(); + }finally{ + if(stmt) stmt.finalize(); + } + return true; + }; +//#endif enable-see + /** A proxy for DB class constructors. It must be called with the being-construct DB object as its "this". See the DB constructor @@ -175,16 +273,28 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ __ptrMap.set(this, pDb); __stmtMap.set(this, Object.create(null)); try{ +//#if enable-see + dbCtorApplySEEKey(this,opt); +//#endif // Check for per-VFS post-open SQL/callback... - const pVfs = capi.sqlite3_js_db_vfs(pDb); - if(!pVfs) toss3("Internal error: cannot get VFS for new db handle."); + const pVfs = capi.sqlite3_js_db_vfs(pDb) + || toss3("Internal error: cannot get VFS for new db handle."); const postInitSql = __vfsPostOpenSql[pVfs]; - if(postInitSql instanceof Function){ - postInitSql(this, sqlite3); - }else if(postInitSql){ - checkSqlite3Rc( - pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) - ); + if(postInitSql){ + /** + Reminder: if this db is encrypted and the client did _not_ pass + in the key, any init code will fail, causing the ctor to throw. + We don't actually know whether the db is encrypted, so we cannot + sensibly apply any heuristics which skip the init code only for + encrypted databases for which no key has yet been supplied. + */ + if(postInitSql instanceof Function){ + postInitSql(this, sqlite3); + }else{ + checkSqlite3Rc( + pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) + ); + } } }catch(e){ this.close(); @@ -280,6 +390,36 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - `flags`: open-mode flags - `vfs`: the VFS fname +//#if enable-see + + SEE-capable builds optionally support ONE of the following + additional options: + + - `key`, `hexkey`, or `textkey`: encryption key as a string, + ArrayBuffer, or Uint8Array. These flags function as documented + for the SEE pragmas of the same names. Using a byte array for + `hexkey` is equivalent to the same series of hex codes in + string form, so `'666f6f'` is equivalent to + `Uint8Array([0x66,0x6f,0x6f])`. A `textkey` byte array is + assumed to be UTF-8. A `key` string is transformed into a UTF-8 + byte array, and a `key` byte array is transformed into a + `hexkey` with the same bytes. + + In non-SEE builds, these options are ignored. In SEE builds, + `PRAGMA key/textkey/hexkey=X` is executed immediately after + opening the db. If more than one of the options is provided, + or any option has an invalid argument type, an exception is + thrown. + + Note that some DB subclasses may run post-initialization SQL + code, e.g. to set a busy-handler timeout or tweak the page cache + size. Such code is run _after_ the SEE key is applied. If no key + is supplied and the database is encrypted, execution of the + post-initialization SQL will fail, causing the constructor to + throw. + +//#endif enable-see + The `filename` and `vfs` arguments may be either JS strings or C-strings allocated via WASM. `flags` is required to be a JS string (because it's specific to this API, which is specific @@ -288,7 +428,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ For purposes of passing a DB instance to C-style sqlite3 functions, the DB object's read-only `pointer` property holds its `sqlite3*` pointer value. That property can also be used to check - whether this DB instance is still open. + whether this DB instance is still open: it will evaluate to + `undefined` after the DB object's close() method is called. In the main window thread, the filenames `":localStorage:"` and `":sessionStorage:"` are special: they cause the db to use either @@ -433,40 +574,56 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ out.returnVal = ()=>opt.resultRows; } if(opt.callback || opt.resultRows){ - switch((undefined===opt.rowMode) - ? 'array' : opt.rowMode) { - case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break; - case 'array': out.cbArg = (stmt)=>stmt.get([]); break; - case 'stmt': - if(Array.isArray(opt.resultRows)){ - toss3("exec(): invalid rowMode for a resultRows array: must", - "be one of 'array', 'object',", - "a result column number, or column name reference."); - } - out.cbArg = (stmt)=>stmt; + switch((undefined===opt.rowMode) ? 'array' : opt.rowMode) { + case 'object': + out.cbArg = (stmt,cache)=>{ + if( !cache.columnNames ) cache.columnNames = stmt.getColumnNames([]); + /* https://sqlite.org/forum/forumpost/3632183d2470617d: + conversion of rows to objects (key/val pairs) is + somewhat expensive for large data sets because of the + native-to-JS conversion of the column names. If we + instead cache the names and build objects from that + list of strings, it can run twice as fast. The + difference is not noticeable for small data sets but + becomes human-perceivable when enough rows are + involved. */ + const row = stmt.get([]); + const rv = Object.create(null); + for( const i in cache.columnNames ) rv[cache.columnNames[i]] = row[i]; + return rv; + }; + break; + case 'array': out.cbArg = (stmt)=>stmt.get([]); break; + case 'stmt': + if(Array.isArray(opt.resultRows)){ + toss3("exec(): invalid rowMode for a resultRows array: must", + "be one of 'array', 'object',", + "a result column number, or column name reference."); + } + out.cbArg = (stmt)=>stmt; + break; + default: + if(util.isInt32(opt.rowMode)){ + out.cbArg = (stmt)=>stmt.get(opt.rowMode); break; - default: - if(util.isInt32(opt.rowMode)){ - out.cbArg = (stmt)=>stmt.get(opt.rowMode); - break; - }else if('string'===typeof opt.rowMode - && opt.rowMode.length>1 - && '$'===opt.rowMode[0]){ - /* "$X": fetch column named "X" (case-sensitive!). Prior - to 2022-12-14 ":X" and "@X" were also permitted, but - having so many options is unnecessary and likely to - cause confusion. */ - const $colName = opt.rowMode.substr(1); - out.cbArg = (stmt)=>{ - const rc = stmt.get(Object.create(null))[$colName]; - return (undefined===rc) - ? toss3(capi.SQLITE_NOTFOUND, - "exec(): unknown result column:",$colName) - : rc; - }; - break; - } - toss3("Invalid rowMode:",opt.rowMode); + }else if('string'===typeof opt.rowMode + && opt.rowMode.length>1 + && '$'===opt.rowMode[0]){ + /* "$X": fetch column named "X" (case-sensitive!). Prior + to 2022-12-14 ":X" and "@X" were also permitted, but + having so many options is unnecessary and likely to + cause confusion. */ + const $colName = opt.rowMode.substr(1); + out.cbArg = (stmt)=>{ + const rc = stmt.get(Object.create(null))[$colName]; + return (undefined===rc) + ? toss3(capi.SQLITE_NOTFOUND, + "exec(): unknown result column:",$colName) + : rc; + }; + break; + } + toss3("Invalid rowMode:",opt.rowMode); } } return out; @@ -884,10 +1041,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ and names. */) ? 0 : 1; evalFirstResult = false; if(arg.cbArg || resultRows){ + const cbArgCache = Object.create(null) + /* 2nd arg for arg.cbArg, used by (at least) row-to-object + converter */; for(; stmt.step(); stmt._lockedByExec = false){ - if(0===gotColNames++) stmt.getColumnNames(opt.columnNames); + if(0===gotColNames++){ + stmt.getColumnNames(cbArgCache.columnNames = (opt.columnNames || [])); + } stmt._lockedByExec = true; - const row = arg.cbArg(stmt); + const row = arg.cbArg(stmt,cbArgCache); if(resultRows) resultRows.push(row); if(callback && false === callback.call(opt, row, stmt)){ break; @@ -1522,7 +1684,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ they are larger than 32 bits, else double or int32, depending on whether they have a fractional part. Booleans are bound as integer 0 or 1. It is not expected the distinction of binding - doubles which have no fractional parts is integers is + doubles which have no fractional parts and integers is significant for the majority of clients due to sqlite3's data typing model. If [BigInt] support is enabled then this routine will bind BigInt values as 64-bit integers if they'll @@ -1706,7 +1868,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ an exception is thrown. By default it will determine the data type of the result - automatically. If passed a second arugment, it must be one + automatically. If passed a second argument, it must be one of the enumeration values for sqlite3 types, which are defined as members of the sqlite3 module: SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value, @@ -1906,16 +2068,26 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Functionally equivalent to DB(storageName,'c','kvvfs') except that it throws if the given storage name is not one of 'local' or 'session'. + + As of version 3.46, the argument may optionally be an options + object in the form: + + { + filename: 'session'|'local', + ... etc. (all options supported by the DB ctor) + } + + noting that the 'vfs' option supported by main DB + constructor is ignored here: the vfs is always 'kvvfs'. */ sqlite3.oo1.JsStorageDb = function(storageName='session'){ + const opt = dbCtorHelper.normalizeArgs(...arguments); + storageName = opt.filename; if('session'!==storageName && 'local'!==storageName){ toss3("JsStorageDb db name must be one of 'session' or 'local'."); } - dbCtorHelper.call(this, { - filename: storageName, - flags: 'c', - vfs: "kvvfs" - }); + opt.vfs = 'kvvfs'; + dbCtorHelper.call(this, opt); }; const jdb = sqlite3.oo1.JsStorageDb; jdb.prototype = Object.create(DB.prototype); diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index ef1154f6b..689c79ae3 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -37,7 +37,7 @@ This function expects a configuration object, intended to abstract away details specific to any given WASM environment, primarily so - that it can be used without any _direct_ dependency on + that it can be used without any direct dependency on Emscripten. (Note the default values for the config object!) The config object is only honored the first time this is called. Subsequent calls ignore the argument and return the same @@ -98,14 +98,37 @@ The returned object is the top-level sqlite3 namespace object. + + Client code may optionally assign sqlite3ApiBootstrap.defaultConfig + an object-type value before calling sqlite3ApiBootstrap() (without + arguments) in order to tell that call to use this object as its + default config value. The intention of this is to provide + downstream clients with a reasonably flexible approach for plugging + in an environment-suitable configuration without having to define a + new global-scope symbol. + + However, because clients who access this library via an + Emscripten-hosted module will not have an opportunity to call + sqlite3ApiBootstrap() themselves, nor to access it before it is + called, an alternative option for setting the configuration is to + define globalThis.sqlite3ApiConfig to an object. If it is set, it + is used instead of sqlite3ApiBootstrap.defaultConfig if + sqlite3ApiBootstrap() is called without arguments. + + Both sqlite3ApiBootstrap.defaultConfig and + globalThis.sqlite3ApiConfig get deleted by sqlite3ApiBootstrap() + because any changes to them made after that point would have no + useful effect. */ 'use strict'; globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig) ){ if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */ - console.warn("sqlite3ApiBootstrap() called multiple times.", - "Config and external initializers are ignored on calls after the first."); + (sqlite3ApiBootstrap.sqlite3.config || console).warn( + "sqlite3ApiBootstrap() called multiple times.", + "Config and external initializers are ignored on calls after the first." + ); return sqlite3ApiBootstrap.sqlite3; } const config = Object.assign(Object.create(null),{ @@ -114,8 +137,16 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( bigIntEnabled: (()=>{ if('undefined'!==typeof Module){ /* Emscripten module will contain HEAPU64 when built with - -sWASM_BIGINT=1, else it will not. */ - return !!Module.HEAPU64; + -sWASM_BIGINT=1, else it will not. + + As of emsdk 3.1.55, when building in strict mode, HEAPxyz + are only available if _explicitly_ included in the exports, + else they are not. We do not (as of 2024-03-04) use -sSTRICT + for the canonical builds. + */ + if( !!Module.HEAPU64 ) return true; + /* Else fall through and hope for the best. Nobody _really_ + builds this without BigInt support, do they? */ } return !!globalThis.BigInt64Array; })(), @@ -149,6 +180,15 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( config[k] = config[k](); } }); + + /** + Eliminate any confusion about whether these config objects may + be used after library initialization by eliminating the outward-facing + objects... + */ + delete globalThis.sqlite3ApiConfig; + delete sqlite3ApiBootstrap.defaultConfig; + /** The main sqlite3 binding API gets installed into this object, mimicking the C API as closely as we can. The numerous members @@ -205,7 +245,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( The exception's message is created by concatenating its arguments with a space between each, except for the - two-args-with-an-objec form and that the first argument will + two-args-with-an-object form and that the first argument will get coerced to a string, as described above, if it's an integer. @@ -1061,7 +1101,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( are undefined if the passed-in value did not come from this.pointer. */ - restore: wasm.exports.sqlite3_wasm_pstack_restore, + restore: wasm.exports.sqlite3__wasm_pstack_restore, /** Attempts to allocate the given number of bytes from the pstack. On success, it zeroes out a block of memory of the @@ -1083,7 +1123,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( if('string'===typeof n && !(n = wasm.sizeofIR(n))){ WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")"); } - return wasm.exports.sqlite3_wasm_pstack_alloc(n) + return wasm.exports.sqlite3__wasm_pstack_alloc(n) || WasmAllocError.toss("Could not allocate",n, "bytes from the pstack."); }, @@ -1163,10 +1203,10 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( */ pointer: { configurable: false, iterable: true, writeable: false, - get: wasm.exports.sqlite3_wasm_pstack_ptr + get: wasm.exports.sqlite3__wasm_pstack_ptr //Whether or not a setter as an alternative to restore() is //clearer or would just lead to confusion is unclear. - //set: wasm.exports.sqlite3_wasm_pstack_restore + //set: wasm.exports.sqlite3__wasm_pstack_restore }, /** sqlite3.wasm.pstack.quota to the total number of bytes @@ -1175,7 +1215,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( */ quota: { configurable: false, iterable: true, writeable: false, - get: wasm.exports.sqlite3_wasm_pstack_quota + get: wasm.exports.sqlite3__wasm_pstack_quota }, /** sqlite3.wasm.pstack.remaining resolves to the amount of space @@ -1183,7 +1223,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( */ remaining: { configurable: false, iterable: true, writeable: false, - get: wasm.exports.sqlite3_wasm_pstack_remaining + get: wasm.exports.sqlite3__wasm_pstack_remaining } })/*wasm.pstack properties*/; @@ -1256,14 +1296,14 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( } try{ if(pdir && 0===wasm.xCallWrapped( - 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir + 'sqlite3__wasm_init_wasmfs', 'i32', ['string'], pdir )){ return __wasmfsOpfsDir = pdir; }else{ return __wasmfsOpfsDir = ""; } }catch(e){ - // sqlite3_wasm_init_wasmfs() is not available + // sqlite3__wasm_init_wasmfs() is not available return __wasmfsOpfsDir = ""; } }; @@ -1365,7 +1405,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const zSchema = schema ? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema)) : 0; - let rc = wasm.exports.sqlite3_wasm_db_serialize( + let rc = wasm.exports.sqlite3__wasm_db_serialize( pDb, zSchema, ppOut, pSize, 0 ); if(rc){ @@ -1391,7 +1431,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( or not provided, then "main" is assumed. */ capi.sqlite3_js_db_vfs = - (dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName); + (dbPointer, dbName=0)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName); /** A thin wrapper around capi.sqlite3_aggregate_context() which @@ -1449,7 +1489,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( if(!util.isInt32(dataLen) || dataLen<0){ SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file()."); } - const rc = wasm.sqlite3_wasm_posix_create_file(filename, pData, dataLen); + const rc = util.sqlite3__wasm_posix_create_file(filename, pData, dataLen); if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", capi.sqlite3_js_rc_str(rc)); }finally{ @@ -1551,7 +1591,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file()."); } try{ - const rc = wasm.sqlite3_wasm_vfs_create_file(vfs, filename, pData, dataLen); + const rc = util.sqlite3__wasm_vfs_create_file(vfs, filename, pData, dataLen); if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", capi.sqlite3_js_rc_str(rc)); }finally{ @@ -1672,12 +1712,12 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( */ capi.sqlite3_db_config = function(pDb, op, ...args){ if(!this.s){ - this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int', + this.s = wasm.xWrap('sqlite3__wasm_db_config_s','int', ['sqlite3*', 'int', 'string:static'] /* MAINDBNAME requires a static string */); - this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int', + this.pii = wasm.xWrap('sqlite3__wasm_db_config_pii', 'int', ['sqlite3*', 'int', '*','int', 'int']); - this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int', + this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int', ['sqlite3*', 'int', 'int','*']); } switch(op){ @@ -1798,7 +1838,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** Calls either sqlite3_result_error_nomem(), if e is-a WasmAllocError, or sqlite3_result_error(). In the latter case, - the second arugment is coerced to a string to create the error + the second argument is coerced to a string to create the error message. The first argument is a (sqlite3_context*). Returns void. diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index 3099c19ec..2e597613e 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -359,6 +359,7 @@ */ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ +const util = sqlite3.util; sqlite3.initWorker1API = function(){ 'use strict'; const toss = (...args)=>{throw new Error(args.join(' '))}; @@ -409,12 +410,12 @@ sqlite3.initWorker1API = function(){ if(db){ delete this.dbs[getDbId(db)]; const filename = db.filename; - const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0); + const pVfs = util.sqlite3__wasm_db_vfs(db.pointer, 0); db.close(); const ddNdx = this.dbList.indexOf(db); if(ddNdx>=0) this.dbList.splice(ddNdx, 1); if(alsoUnlink && filename && pVfs){ - sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); + util.sqlite3__wasm_vfs_unlink(pVfs, filename); } } }, @@ -458,11 +459,6 @@ sqlite3.initWorker1API = function(){ return wState.dbList[0] && getDbId(wState.dbList[0]); }; - const guessVfs = function(filename){ - const m = /^file:.+(vfs=(\w+))/.exec(filename); - return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0); - }; - const isSpecialDbFilename = (n)=>{ return ""===n || ':'===n[0]; }; @@ -484,36 +480,8 @@ sqlite3.initWorker1API = function(){ toss("Throwing because of simulateError flag."); } const rc = Object.create(null); - let byteArray, pVfs; oargs.vfs = args.vfs; - if(isSpecialDbFilename(args.filename)){ - oargs.filename = args.filename || ""; - }else{ - oargs.filename = args.filename; - byteArray = args.byteArray; - if(byteArray) pVfs = guessVfs(args.filename); - } - if(pVfs){ - /* 2022-11-02: this feature is as-yet untested except that - sqlite3_wasm_vfs_create_file() has been tested from the - browser dev console. */ - let pMem; - try{ - pMem = sqlite3.wasm.allocFromTypedArray(byteArray); - const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file( - pVfs, oargs.filename, pMem, byteArray.byteLength - ); - if(rc) sqlite3.SQLite3Error.toss(rc); - }catch(e){ - throw new sqlite3.SQLite3Error( - e.name+' creating '+args.filename+": "+e.message, { - cause: e - } - ); - }finally{ - if(pMem) sqlite3.wasm.dealloc(pMem); - } - } + oargs.filename = args.filename || ""; const db = wState.open(oargs); rc.filename = db.filename; rc.persistent = !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js index cafd296c6..e671094f0 100644 --- a/ext/wasm/api/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -51,7 +51,7 @@ */ "use strict"; const wPost = (type,...args)=>postMessage({type, payload:args}); -const installAsyncProxy = function(self){ +const installAsyncProxy = function(){ const toss = function(...args){throw new Error(args.join(' '))}; if(globalThis.window === globalThis){ toss("This code cannot run from the main thread.", @@ -562,6 +562,14 @@ const installAsyncProxy = function(self){ wTimeEnd(); return; } + if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){ + try{ + await hDir.removeEntry(filenamePart); + }catch(e){ + /* ignoring */ + //warn("Ignoring failed Unlink of",filename,":",e); + } + } const hFile = await hDir.getFileHandle(filenamePart, {create}); wTimeEnd(); const fh = Object.assign(Object.create(null),{ @@ -911,5 +919,5 @@ if(!globalThis.SharedArrayBuffer){ !navigator?.storage?.getDirectory){ wPost('opfs-unavailable',"Missing required OPFS APIs."); }else{ - installAsyncProxy(self); + installAsyncProxy(); } diff --git a/ext/wasm/api/sqlite3-vfs-helper.c-pp.js b/ext/wasm/api/sqlite3-vfs-helper.c-pp.js new file mode 100644 index 000000000..4d29c7b91 --- /dev/null +++ b/ext/wasm/api/sqlite3-vfs-helper.c-pp.js @@ -0,0 +1,103 @@ +/* +** 2022-11-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 installs sqlite3.vfs, a namespace of helpers for use in + the creation of JavaScript implementations of sqlite3_vfs. +*/ +'use strict'; +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; + const vfs = Object.create(null); + sqlite3.vfs = vfs; + + /** + Uses sqlite3_vfs_register() to register this + sqlite3.capi.sqlite3_vfs instance. This object must have already + been filled out properly. If the first argument is truthy, the + VFS is registered as the default VFS, else it is not. + + On success, returns this object. Throws on error. + */ + capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){ + if(!(this instanceof sqlite3.capi.sqlite3_vfs)){ + toss("Expecting a sqlite3_vfs-type argument."); + } + const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0); + if(rc){ + toss("sqlite3_vfs_register(",this,") failed with rc",rc); + } + if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){ + toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", + this); + } + return this; + }; + + /** + A wrapper for + sqlite3.StructBinder.StructType.prototype.installMethods() or + registerVfs() to reduce installation of a VFS and/or its I/O + methods to a single call. + + Accepts an object which contains the properties "io" and/or + "vfs", each of which is itself an object with following properties: + + - `struct`: an sqlite3.StructBinder.StructType-type struct. This + must be a populated (except for the methods) object of type + sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the + "vfs" entry). + + - `methods`: an object mapping sqlite3_io_methods method names + (e.g. 'xClose') to JS implementations of those methods. The JS + implementations must be call-compatible with their native + counterparts. + + For each of those object, this function passes its (`struct`, + `methods`, (optional) `applyArgcCheck`) properties to + installMethods(). + + If the `vfs` entry is set then: + + - Its `struct` property's registerVfs() is called. The + `vfs` entry may optionally have an `asDefault` property, which + gets passed as the argument to registerVfs(). + + - If `struct.$zName` is falsy and the entry has a string-type + `name` property, `struct.$zName` is set to the C-string form of + that `name` value before registerVfs() is called. That string + gets added to the on-dispose state of the struct. + + On success returns this object. Throws on error. + */ + vfs.installVfs = function(opt){ + let count = 0; + const propList = ['io','vfs']; + for(const key of propList){ + const o = opt[key]; + if(o){ + ++count; + o.struct.installMethods(o.methods, !!o.applyArgcCheck); + if('vfs'===key){ + if(!o.struct.$zName && 'string'===typeof o.name){ + o.struct.addOnDispose( + o.struct.$zName = wasm.allocCString(o.name) + ); + } + o.struct.registerVfs(!!o.asDefault); + } + } + } + if(!count) toss("Misuse: installVfs() options object requires at least", + "one of:", propList); + return this; + }; +}/*sqlite3ApiBootstrap.initializers.push()*/); diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js index 2089eceb4..3f4182dac 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -1137,8 +1137,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ existing content. Throws if the pool has no available file slots, on I/O error, or if the input does not appear to be a database. In the latter case, only a cursory examination is made. - Note that this routine is _only_ for importing database files, - not arbitrary files, the reason being that this VFS will + Results are undefined if the given db name refers to an opened + db. Note that this routine is _only_ for importing database + files, not arbitrary files, the reason being that this VFS will automatically clean up any non-database files so importing them is pointless. diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index af89f216f..4c654c335 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -245,7 +245,8 @@ const installOpfsVfs = function callee(options){ opfsIoMethods.$iVersion = 1; opfsVfs.$iVersion = 2/*yes, two*/; opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof; - opfsVfs.$mxPathname = 1024/*sure, why not?*/; + opfsVfs.$mxPathname = 1024/* sure, why not? The OPFS name length limit + is undocumented/unspecified. */; opfsVfs.$zName = wasm.allocCString("opfs"); // All C-side memory of opfsVfs is zeroed out, but just to be explicit: opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null; @@ -422,10 +423,25 @@ const installOpfsVfs = function callee(options){ }); state.opfsFlags = Object.assign(Object.create(null),{ /** - Flag for use with xOpen(). "opfs-unlock-asap=1" enables - this. See defaultUnlockAsap, below. + Flag for use with xOpen(). URI flag "opfs-unlock-asap=1" + enables this. See defaultUnlockAsap, below. */ OPFS_UNLOCK_ASAP: 0x01, + /** + Flag for use with xOpen(). URI flag "delete-before-open=1" + tells the VFS to delete the db file before attempting to open + it. This can be used, e.g., to replace a db which has been + corrupted (without forcing us to expose a delete/unlink() + function in the public API). + + Failure to unlink the file is ignored but may lead to + downstream errors. An unlink can fail if, e.g., another tab + has the handle open. + + It goes without saying that deleting a file out from under another + instance results in Undefined Behavior. + */ + OPFS_UNLINK_BEFORE_OPEN: 0x02, /** If true, any async routine which implicitly acquires a sync access handle (i.e. an OPFS lock) will release that locks at @@ -874,13 +890,17 @@ const installOpfsVfs = function callee(options){ let opfsFlags = 0; if(0===zName){ zName = randomFilename(); - }else if('number'===typeof zName){ + }else if(wasm.isPtr(zName)){ if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){ /* -----------------------^^^^^ MUST pass the untranslated C-string here. */ opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP; } + if(capi.sqlite3_uri_boolean(zName, "delete-before-open", 0)){ + opfsFlags |= state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN; + } zName = wasm.cstrToJs(zName); + //warn("xOpen zName =",zName, "opfsFlags =",opfsFlags); } const fh = Object.create(null); fh.fid = pFile; @@ -993,27 +1013,6 @@ const installOpfsVfs = function callee(options){ */ opfsUtil.randomFilename = randomFilename; - /** - Re-registers the OPFS VFS. This is intended only for odd use - cases which have to call sqlite3_shutdown() as part of their - initialization process, which will unregister the VFS - registered by installOpfsVfs(). If passed a truthy value, the - OPFS VFS is registered as the default VFS, else it is not made - the default. Returns the result of the the - sqlite3_vfs_register() call. - - Design note: the problem of having to re-register things after - a shutdown/initialize pair is more general. How to best plug - that in to the library is unclear. In particular, we cannot - hook in to any C-side calls to sqlite3_initialize(), so we - cannot add an after-initialize callback mechanism. - */ - opfsUtil.registerVfs = (asDefault=false)=>{ - return wasm.exports.sqlite3_vfs_register( - opfsVfs.pointer, asDefault ? 1 : 0 - ); - }; - /** Returns a promise which resolves to an object which represents all files and directories in the OPFS tree. The top-most object @@ -1213,16 +1212,18 @@ const installOpfsVfs = function callee(options){ Asynchronously imports the given bytes (a byte array or ArrayBuffer) into the given database file. + Results are undefined if the given db name refers to an opened + db. + If passed a function for its second argument, its behaviour - changes to async and it imports its data in chunks fed to it by - the given callback function. It calls the callback (which may - be async) repeatedly, expecting either a Uint8Array or - ArrayBuffer (to denote new input) or undefined (to denote - EOF). For so long as the callback continues to return - non-undefined, it will append incoming data to the given - VFS-hosted database file. When called this way, the resolved - value of the returned Promise is the number of bytes written to - the target file. + changes: imports its data in chunks fed to it by the given + callback function. It calls the callback (which may be async) + repeatedly, expecting either a Uint8Array or ArrayBuffer (to + denote new input) or undefined (to denote EOF). For so long as + the callback continues to return non-undefined, it will append + incoming data to the given VFS-hosted database file. When + called this way, the resolved value of the returned Promise is + the number of bytes written to the target file. It very specifically requires the input to be an SQLite3 database and throws if that's not the case. It does so in diff --git a/ext/wasm/api/sqlite3-v-helper.js b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js similarity index 57% rename from ext/wasm/api/sqlite3-v-helper.js rename to ext/wasm/api/sqlite3-vtab-helper.c-pp.js index e63da8afc..7359ea39a 100644 --- a/ext/wasm/api/sqlite3-v-helper.js +++ b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js @@ -10,19 +10,13 @@ */ /** - This file installs sqlite3.vfs, and object which exists to assist - in the creation of JavaScript implementations of sqlite3_vfs, along - with its virtual table counterpart, sqlite3.vtab. + This file installs sqlite3.vtab, a namespace of helpers for use in + the creation of JavaScript implementations virtual tables. */ 'use strict'; globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; - const vfs = Object.create(null), vtab = Object.create(null); - - const StructBinder = sqlite3.StructBinder - /* we require a local alias b/c StructBinder is removed from the sqlite3 - object during the final steps of the API cleanup. */; - sqlite3.vfs = vfs; + const vtab = Object.create(null); sqlite3.vtab = vtab; const sii = capi.sqlite3_index_info; @@ -72,257 +66,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr); }; - /** - Installs a StructBinder-bound function pointer member of the - given name and function in the given StructType target object. - - It creates a WASM proxy for the given function and arranges for - that proxy to be cleaned up when tgt.dispose() is called. Throws - on the slightest hint of error, e.g. tgt is-not-a StructType, - name does not map to a struct-bound member, etc. - - As a special case, if the given function is a pointer, then - `wasm.functionEntry()` is used to validate that it is a known - function. If so, it is used as-is with no extra level of proxying - or cleanup, else an exception is thrown. It is legal to pass a - value of 0, indicating a NULL pointer, with the caveat that 0 - _is_ a legal function pointer in WASM but it will not be accepted - as such _here_. (Justification: the function at address zero must - be one which initially came from the WASM module, not a method we - want to bind to a virtual table or VFS.) - - This function returns a proxy for itself which is bound to tgt - and takes 2 args (name,func). That function returns the same - thing as this one, permitting calls to be chained. - - If called with only 1 arg, it has no side effects but returns a - func with the same signature as described above. - - ACHTUNG: because we cannot generically know how to transform JS - exceptions into result codes, the installed functions do no - automatic catching of exceptions. It is critical, to avoid - undefined behavior in the C layer, that methods mapped via - this function do not throw. The exception, as it were, to that - rule is... - - If applyArgcCheck is true then each JS function (as opposed to - function pointers) gets wrapped in a proxy which asserts that it - is passed the expected number of arguments, throwing if the - argument count does not match expectations. That is only intended - for dev-time usage for sanity checking, and will leave the C - environment in an undefined state. - */ - const installMethod = function callee( - tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck - ){ - if(!(tgt instanceof StructBinder.StructType)){ - toss("Usage error: target object is-not-a StructType."); - }else if(!(func instanceof Function) && !wasm.isPtr(func)){ - toss("Usage errror: expecting a Function or WASM pointer to one."); - } - if(1===arguments.length){ - return (n,f)=>callee(tgt, n, f, applyArgcCheck); - } - if(!callee.argcProxy){ - callee.argcProxy = function(tgt, funcName, func,sig){ - return function(...args){ - if(func.length!==arguments.length){ - toss("Argument mismatch for", - tgt.structInfo.name+"::"+funcName - +": Native signature is:",sig); - } - return func.apply(this, args); - } - }; - /* An ondispose() callback for use with - StructBinder-created types. */ - callee.removeFuncList = function(){ - if(this.ondispose.__removeFuncList){ - this.ondispose.__removeFuncList.forEach( - (v,ndx)=>{ - if('number'===typeof v){ - try{wasm.uninstallFunction(v)} - catch(e){/*ignore*/} - } - /* else it's a descriptive label for the next number in - the list. */ - } - ); - delete this.ondispose.__removeFuncList; - } - }; - }/*static init*/ - const sigN = tgt.memberSignature(name); - if(sigN.length<2){ - toss("Member",name,"does not have a function pointer signature:",sigN); - } - const memKey = tgt.memberKey(name); - const fProxy = (applyArgcCheck && !wasm.isPtr(func)) - /** This middle-man proxy is only for use during development, to - confirm that we always pass the proper number of - arguments. We know that the C-level code will always use the - correct argument count. */ - ? callee.argcProxy(tgt, memKey, func, sigN) - : func; - if(wasm.isPtr(fProxy)){ - if(fProxy && !wasm.functionEntry(fProxy)){ - toss("Pointer",fProxy,"is not a WASM function table entry."); - } - tgt[memKey] = fProxy; - }else{ - const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); - tgt[memKey] = pFunc; - if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){ - tgt.addOnDispose('ondispose.__removeFuncList handler', - callee.removeFuncList); - tgt.ondispose.__removeFuncList = []; - } - tgt.ondispose.__removeFuncList.push(memKey, pFunc); - } - return (n,f)=>callee(tgt, n, f, applyArgcCheck); - }/*installMethod*/; - installMethod.installMethodArgcCheck = false; - - /** - Installs methods into the given StructType-type instance. Each - entry in the given methods object must map to a known member of - the given StructType, else an exception will be triggered. See - installMethod() for more details, including the semantics of the - 3rd argument. - - As an exception to the above, if any two or more methods in the - 2nd argument are the exact same function, installMethod() is - _not_ called for the 2nd and subsequent instances, and instead - those instances get assigned the same method pointer which is - created for the first instance. This optimization is primarily to - accommodate special handling of sqlite3_module::xConnect and - xCreate methods. - - On success, returns its first argument. Throws on error. - */ - const installMethods = function( - structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck - ){ - const seen = new Map /* map of */; - for(const k of Object.keys(methods)){ - const m = methods[k]; - const prior = seen.get(m); - if(prior){ - const mkey = structInstance.memberKey(k); - structInstance[mkey] = structInstance[structInstance.memberKey(prior)]; - }else{ - installMethod(structInstance, k, m, applyArgcCheck); - seen.set(m, k); - } - } - return structInstance; - }; - - /** - Equivalent to calling installMethod(this,...arguments) with a - first argument of this object. If called with 1 or 2 arguments - and the first is an object, it's instead equivalent to calling - installMethods(this,...arguments). - */ - StructBinder.StructType.prototype.installMethod = function callee( - name, func, applyArgcCheck = installMethod.installMethodArgcCheck - ){ - return (arguments.length < 3 && name && 'object'===typeof name) - ? installMethods(this, ...arguments) - : installMethod(this, ...arguments); - }; - - /** - Equivalent to calling installMethods() with a first argument - of this object. - */ - StructBinder.StructType.prototype.installMethods = function( - methods, applyArgcCheck = installMethod.installMethodArgcCheck - ){ - return installMethods(this, methods, applyArgcCheck); - }; - - /** - Uses sqlite3_vfs_register() to register this - sqlite3.capi.sqlite3_vfs. This object must have already been - filled out properly. If the first argument is truthy, the VFS is - registered as the default VFS, else it is not. - - On success, returns this object. Throws on error. - */ - capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){ - if(!(this instanceof sqlite3.capi.sqlite3_vfs)){ - toss("Expecting a sqlite3_vfs-type argument."); - } - const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0); - if(rc){ - toss("sqlite3_vfs_register(",this,") failed with rc",rc); - } - if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){ - toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", - this); - } - return this; - }; - - /** - A wrapper for installMethods() or registerVfs() to reduce - installation of a VFS and/or its I/O methods to a single - call. - - Accepts an object which contains the properties "io" and/or - "vfs", each of which is itself an object with following properties: - - - `struct`: an sqlite3.StructType-type struct. This must be a - populated (except for the methods) object of type - sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the - "vfs" entry). - - - `methods`: an object mapping sqlite3_io_methods method names - (e.g. 'xClose') to JS implementations of those methods. The JS - implementations must be call-compatible with their native - counterparts. - - For each of those object, this function passes its (`struct`, - `methods`, (optional) `applyArgcCheck`) properties to - installMethods(). - - If the `vfs` entry is set then: - - - Its `struct` property's registerVfs() is called. The - `vfs` entry may optionally have an `asDefault` property, which - gets passed as the argument to registerVfs(). - - - If `struct.$zName` is falsy and the entry has a string-type - `name` property, `struct.$zName` is set to the C-string form of - that `name` value before registerVfs() is called. That string - gets added to the on-dispose state of the struct. - - On success returns this object. Throws on error. - */ - vfs.installVfs = function(opt){ - let count = 0; - const propList = ['io','vfs']; - for(const key of propList){ - const o = opt[key]; - if(o){ - ++count; - installMethods(o.struct, o.methods, !!o.applyArgcCheck); - if('vfs'===key){ - if(!o.struct.$zName && 'string'===typeof o.name){ - o.struct.addOnDispose( - o.struct.$zName = wasm.allocCString(o.name) - ); - } - o.struct.registerVfs(!!o.asDefault); - } - } - } - if(!count) toss("Misuse: installVfs() options object requires at least", - "one of:", propList); - return this; - }; - /** Internal factory function for xVtab and xCursor impls. */ @@ -456,30 +199,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo); - /** - Given an error object, this function returns - sqlite3.capi.SQLITE_NOMEM if (e instanceof - sqlite3.WasmAllocError), else it returns its - second argument. Its intended usage is in the methods - of a sqlite3_vfs or sqlite3_module: - - ``` - try{ - let rc = ... - return rc; - }catch(e){ - return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ); - // where SQLITE_XYZ is some call-appropriate result code. - } - ``` - */ - /**vfs.exceptionToRc = vtab.exceptionToRc = - (e, defaultRc=capi.SQLITE_ERROR)=>( - (e instanceof sqlite3.WasmAllocError) - ? capi.SQLITE_NOMEM - : defaultRc - );*/ - /** Given an sqlite3_module method name and error object, this function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof @@ -525,20 +244,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; vtab.xError.errorReporter = 1 ? console.error.bind(console) : false; - /** - "The problem" with this is that it introduces an outer function with - a different arity than the passed-in method callback. That means we - cannot do argc validation on these. Additionally, some methods (namely - xConnect) may have call-specific error handling. It would be a shame to - hard-coded that per-method support in this function. - */ - /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){ - return function(...args){ - try { method(...args); } - }catch(e){ return vtab.xError(methodName, e, defaultRc) } - }; - */ - /** A helper for sqlite3_vtab::xRowid() and xUpdate() implementations. It must be passed the final argument to one of @@ -685,12 +390,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ remethods[k] = fwrap(k, m); } } - installMethods(mod, remethods, false); + mod.installMethods(remethods, false); }else{ // No automatic exception handling. Trust the client // to not throw. - installMethods( - mod, methods, !!opt.applyArgcCheck/*undocumented option*/ + mod.installMethods( + methods, !!opt.applyArgcCheck/*undocumented option*/ ); } if(0===mod.$iVersion){ diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 618d0f085..48ae2297a 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -238,28 +238,28 @@ ** Another option is to malloc() a chunk of our own and call that our ** "stack". */ -SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_end(void){ +SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_end(void){ extern void __heap_base /* see https://stackoverflow.com/questions/10038964 */; return &__heap_base; } -SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_begin(void){ +SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_begin(void){ extern void __data_end; return &__data_end; } static void * pWasmStackPtr = 0; -SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_ptr(void){ - if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end(); +SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_ptr(void){ + if(!pWasmStackPtr) pWasmStackPtr = sqlite3__wasm_stack_end(); return pWasmStackPtr; } -SQLITE_WASM_EXPORT void sqlite3_wasm_stack_restore(void * p){ +SQLITE_WASM_EXPORT void sqlite3__wasm_stack_restore(void * p){ pWasmStackPtr = p; } -SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){ +SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_alloc(int n){ if(n<=0) return 0; n = (n + 7) & ~7 /* align to 8-byte boundary */; - unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr(); - unsigned const char * const b = (unsigned const char *)sqlite3_wasm_stack_begin(); + unsigned char * const p = (unsigned char *)sqlite3__wasm_stack_ptr(); + unsigned const char * const b = (unsigned const char *)sqlite3__wasm_stack_begin(); if(b + n >= p || b + n < b/*overflow*/) return 0; return pWasmStackPtr = p - n; } @@ -267,7 +267,7 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){ /* ** State for the "pseudo-stack" allocator implemented in -** sqlite3_wasm_pstack_xyz(). In order to avoid colliding with +** sqlite3__wasm_pstack_xyz(). In order to avoid colliding with ** Emscripten-controled stack space, it carves out a bit of stack ** memory to use for that purpose. This memory ends up in the ** WASM-managed memory, such that routines which manipulate the wasm @@ -291,14 +291,14 @@ static struct { /* ** Returns the current pstack position. */ -SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){ +SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_ptr(void){ return PStack.pPos; } /* ** Sets the pstack position poitner to p. Results are undefined if the -** given value did not come from sqlite3_wasm_pstack_ptr(). +** given value did not come from sqlite3__wasm_pstack_ptr(). */ -SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){ +SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){ assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos); assert(0==((unsigned long long)p & 0x7)); if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){ @@ -313,7 +313,7 @@ SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){ ** JS code from having to do so, and most uses of the pstack will ** call for doing so). */ -SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){ +SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){ if( n<=0 ) return 0; //if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */; n = (n + 7) & ~7 /* align to 8-byte boundary */; @@ -324,9 +324,9 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){ } /* ** Return the number of bytes left which can be -** sqlite3_wasm_pstack_alloc()'d. +** sqlite3__wasm_pstack_alloc()'d. */ -SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){ +SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){ assert(PStack.pPos >= PStack.pBegin); assert(PStack.pPos <= PStack.pEnd); return (int)(PStack.pPos - PStack.pBegin); @@ -337,7 +337,7 @@ SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){ ** any space which is currently allocated. This value is a ** compile-time constant. */ -SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){ +SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_quota(void){ return (int)(PStack.pEnd - PStack.pBegin); } @@ -356,7 +356,7 @@ SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){ ** Returns err_code. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ +int sqlite3__wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ if( db!=0 ){ if( 0!=zMsg ){ const int nMsg = sqlite3Strlen30(zMsg); @@ -380,7 +380,7 @@ struct WasmTestStruct { }; typedef struct WasmTestStruct WasmTestStruct; SQLITE_WASM_EXPORT -void sqlite3_wasm_test_struct(WasmTestStruct * s){ +void sqlite3__wasm_test_struct(WasmTestStruct * s){ if(s){ s->v4 *= 2; s->v8 = s->v4 * 2; @@ -408,7 +408,7 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){ ** increased. In debug builds that will trigger an assert(). */ SQLITE_WASM_EXPORT -const char * sqlite3_wasm_enum_json(void){ +const char * sqlite3__wasm_enum_json(void){ static char aBuffer[1024 * 20] = {0} /* where the JSON goes */; int n = 0, nChildren = 0, nStruct = 0 /* output counters for figuring out where commas go */; @@ -425,7 +425,7 @@ const char * sqlite3_wasm_enum_json(void){ /* Core output macros... */ #define lenCheck assert(zPos < zEnd - 128 \ - && "sqlite3_wasm_enum_json() buffer is too small."); \ + && "sqlite3__wasm_enum_json() buffer is too small."); \ if( zPos >= zEnd - 128 ) return 0 #define outf(format,...) \ zPos += snprintf(zPos, ((size_t)(zEnd - zPos)), format, __VA_ARGS__); \ @@ -545,6 +545,10 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_CONFIG_SMALL_MALLOC); DefInt(SQLITE_CONFIG_SORTERREF_SIZE); DefInt(SQLITE_CONFIG_MEMDB_MAXSIZE); + /* maintenance note: we specifically do not include + SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that + it's only for legacy support and no apps written with + this API require that. */ } _DefGroup; DefGroup(dataTypes) { @@ -1220,7 +1224,7 @@ const char * sqlite3_wasm_enum_json(void){ ** call is returned. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ +int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ int rc = SQLITE_MISUSE /* ??? */; if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0); if( zName && pVfs && pVfs->xDelete ){ @@ -1238,7 +1242,7 @@ int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ ** given name is open. */ SQLITE_WASM_EXPORT -sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ +sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ sqlite3_vfs * pVfs = 0; sqlite3_file_control(pDb, zDbName ? zDbName : "main", SQLITE_FCNTL_VFS_POINTER, &pVfs); @@ -1261,7 +1265,7 @@ sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ ** SQLITE_MISUSE if pDb is NULL. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_db_reset(sqlite3 *pDb){ +int sqlite3__wasm_db_reset(sqlite3 *pDb){ int rc = SQLITE_MISUSE; if( pDb ){ sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0); @@ -1288,11 +1292,11 @@ int sqlite3_wasm_db_reset(sqlite3 *pDb){ ** takes no measures to ensure that is the case. ** ** This implementation appears to work fine, but -** sqlite3_wasm_db_serialize() is arguably the better way to achieve +** sqlite3__wasm_db_serialize() is arguably the better way to achieve ** this. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_db_export_chunked( sqlite3* pDb, +int sqlite3__wasm_db_export_chunked( sqlite3* pDb, int (*xCallback)(unsigned const char *zOut, int n) ){ sqlite3_int64 nSize = 0; sqlite3_int64 nPos = 0; @@ -1343,7 +1347,7 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb, ** sqlite3_free() to free it. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, +int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema, unsigned char **pOut, sqlite3_int64 *nOut, unsigned int mFlags ){ unsigned char * z; @@ -1366,7 +1370,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, ** this function's out-of-scope use of the sqlite3_vfs/file/io_methods ** APIs leads to triggering of assertions in the core library. Its use ** is now deprecated and VFS-specific APIs for importing files need to -** be found to replace it. sqlite3_wasm_posix_create_file() is +** be found to replace it. sqlite3__wasm_posix_create_file() is ** suitable for the "unix" family of VFSes. ** ** Creates a new file using the I/O API of the given VFS, containing @@ -1407,7 +1411,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, ** support is disabled or unavailable. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, +int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs, const char *zFilename, const unsigned char * pData, int nData ){ @@ -1497,7 +1501,7 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, ** SQLITE_IOERR on error. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_posix_create_file( const char *zFilename, +int sqlite3__wasm_posix_create_file( const char *zFilename, const unsigned char * pData, int nData ){ int rc; @@ -1520,17 +1524,17 @@ int sqlite3_wasm_posix_create_file( const char *zFilename, ** for use by the sqlite project's own JS/WASM bindings. ** ** Allocates sqlite3KvvfsMethods.nKeySize bytes from -** sqlite3_wasm_pstack_alloc() and returns 0 if that allocation fails, +** sqlite3__wasm_pstack_alloc() and returns 0 if that allocation fails, ** else it passes that string to kvstorageMakeKey() and returns a ** NUL-terminated pointer to that string. It is up to the caller to -** use sqlite3_wasm_pstack_restore() to free the returned pointer. +** use sqlite3__wasm_pstack_restore() to free the returned pointer. */ SQLITE_WASM_EXPORT -char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass, +char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass, const char *zKeyIn){ assert(sqlite3KvvfsMethods.nKeySize>24); char *zKeyOut = - (char *)sqlite3_wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize); + (char *)sqlite3__wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize); if(zKeyOut){ kvstorageMakeKey(zClass, zKeyIn, zKeyOut); } @@ -1545,7 +1549,7 @@ char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass, ** I/O methods and associated state. */ SQLITE_WASM_EXPORT -sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ +sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } @@ -1560,7 +1564,7 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ ** valid value. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ +int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){ switch(op){ case SQLITE_VTAB_DIRECTONLY: case SQLITE_VTAB_INNOCUOUS: @@ -1580,7 +1584,7 @@ int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ ** (int,int*) variadic args. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ +int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ switch(op){ case SQLITE_DBCONFIG_ENABLE_FKEY: case SQLITE_DBCONFIG_ENABLE_TRIGGER: @@ -1613,7 +1617,7 @@ int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ ** (void*,int,int) variadic args. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){ +int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){ switch(op){ case SQLITE_DBCONFIG_LOOKASIDE: return sqlite3_db_config(pDb, op, pArg1, arg2, arg3); @@ -1629,7 +1633,7 @@ int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int ** (const char *) variadic args. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ +int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ switch(op){ case SQLITE_DBCONFIG_MAINDBNAME: return sqlite3_db_config(pDb, op, zArg); @@ -1646,7 +1650,7 @@ int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ ** a single integer argument. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_config_i(int op, int arg){ +int sqlite3__wasm_config_i(int op, int arg){ return sqlite3_config(op, arg); } @@ -1658,7 +1662,7 @@ int sqlite3_wasm_config_i(int op, int arg){ ** two int arguments. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_config_ii(int op, int arg1, int arg2){ +int sqlite3__wasm_config_ii(int op, int arg1, int arg2){ return sqlite3_config(op, arg1, arg2); } @@ -1670,39 +1674,28 @@ int sqlite3_wasm_config_ii(int op, int arg1, int arg2){ ** a single i64 argument. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){ +int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){ return sqlite3_config(op, arg); } -#if 0 -// Pending removal after verification of a workaround discussed in the -// forum post linked to below. /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** -** Returns a pointer to sqlite3_free(). In compliant browsers the -** return value, when passed to sqlite3.wasm.exports.functionEntry(), -** must resolve to the same function as -** sqlite3.wasm.exports.sqlite3_free. i.e. from a dev console where -** sqlite3 is exported globally, the following must be true: -** -** ``` -** sqlite3.wasm.functionEntry( -** sqlite3.wasm.exports.sqlite3_wasm_ptr_to_sqlite3_free() -** ) === sqlite3.wasm.exports.sqlite3_free -** ``` -** -** Using a function to return this pointer, as opposed to exporting it -** via sqlite3_wasm_enum_json(), is an attempt to work around a -** Safari-specific quirk covered at -** https://sqlite.org/forum/info/e5b20e1feb37a19a. -**/ +** If z is not NULL, returns the result of passing z to +** sqlite3_mprintf()'s %Q modifier (if addQuotes is true) or %q (if +** addQuotes is 0). Returns NULL if z is NULL or on OOM. +*/ SQLITE_WASM_EXPORT -void * sqlite3_wasm_ptr_to_sqlite3_free(void){ - return (void*)sqlite3_free; +char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){ + char * rc = 0; + if( z ){ + rc = addQuotes + ? sqlite3_mprintf("%Q", z) + : sqlite3_mprintf("%q", z); + } + return rc; } -#endif #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #include @@ -1729,7 +1722,7 @@ void * sqlite3_wasm_ptr_to_sqlite3_free(void){ ** defined, SQLITE_NOTFOUND is returned without side effects. */ SQLITE_WASM_EXPORT -int sqlite3_wasm_init_wasmfs(const char *zMountPoint){ +int sqlite3__wasm_init_wasmfs(const char *zMountPoint){ static backend_t pOpfs = 0; if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs"; if( !pOpfs ){ @@ -1749,7 +1742,7 @@ int sqlite3_wasm_init_wasmfs(const char *zMountPoint){ } #else SQLITE_WASM_EXPORT -int sqlite3_wasm_init_wasmfs(const char *zUnused){ +int sqlite3__wasm_init_wasmfs(const char *zUnused){ //emscripten_console_warn("WASMFS OPFS is not compiled in."); if(zUnused){/*unused*/} return SQLITE_NOTFOUND; @@ -1759,51 +1752,51 @@ int sqlite3_wasm_init_wasmfs(const char *zUnused){ #if SQLITE_WASM_TESTS SQLITE_WASM_EXPORT -int sqlite3_wasm_test_intptr(int * p){ +int sqlite3__wasm_test_intptr(int * p){ return *p = *p * 2; } SQLITE_WASM_EXPORT -void * sqlite3_wasm_test_voidptr(void * p){ +void * sqlite3__wasm_test_voidptr(void * p){ return p; } SQLITE_WASM_EXPORT -int64_t sqlite3_wasm_test_int64_max(void){ +int64_t sqlite3__wasm_test_int64_max(void){ return (int64_t)0x7fffffffffffffff; } SQLITE_WASM_EXPORT -int64_t sqlite3_wasm_test_int64_min(void){ - return ~sqlite3_wasm_test_int64_max(); +int64_t sqlite3__wasm_test_int64_min(void){ + return ~sqlite3__wasm_test_int64_max(); } SQLITE_WASM_EXPORT -int64_t sqlite3_wasm_test_int64_times2(int64_t x){ +int64_t sqlite3__wasm_test_int64_times2(int64_t x){ return x * 2; } SQLITE_WASM_EXPORT -void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){ - *max = sqlite3_wasm_test_int64_max(); - *min = sqlite3_wasm_test_int64_min(); +void sqlite3__wasm_test_int64_minmax(int64_t * min, int64_t *max){ + *max = sqlite3__wasm_test_int64_max(); + *min = sqlite3__wasm_test_int64_min(); /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/ } SQLITE_WASM_EXPORT -int64_t sqlite3_wasm_test_int64ptr(int64_t * p){ - /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/ +int64_t sqlite3__wasm_test_int64ptr(int64_t * p){ + /*printf("sqlite3__wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/ return *p = *p * 2; } SQLITE_WASM_EXPORT -void sqlite3_wasm_test_stack_overflow(int recurse){ - if(recurse) sqlite3_wasm_test_stack_overflow(recurse); +void sqlite3__wasm_test_stack_overflow(int recurse){ + if(recurse) sqlite3__wasm_test_stack_overflow(recurse); } /* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */ SQLITE_WASM_EXPORT -char * sqlite3_wasm_test_str_hello(int fail){ +char * sqlite3__wasm_test_str_hello(int fail){ char * s = fail ? 0 : (char *)sqlite3_malloc(6); if(s){ memcpy(s, "hello", 5); @@ -1838,12 +1831,12 @@ char * sqlite3_wasm_test_str_hello(int fail){ ** optional + or - sign in front, or a hexadecimal ** literal of the form 0x... */ -static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){ +static int sqlite3__wasm_SQLTester_strnotglob(const char *zGlob, const char *z){ int c, c2; int invert; int seen; typedef int (*recurse_f)(const char *,const char *); - static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob; + static const recurse_f recurse = sqlite3__wasm_SQLTester_strnotglob; while( (c = (*(zGlob++)))!=0 ){ if( c=='*' ){ @@ -1918,11 +1911,10 @@ static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){ } SQLITE_WASM_EXPORT -int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){ - return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z); +int sqlite3__wasm_SQLTester_strglob(const char *zGlob, const char *z){ + return !sqlite3__wasm_SQLTester_strnotglob(zGlob, z); } - #endif /* SQLITE_WASM_TESTS */ #undef SQLITE_WASM_EXPORT diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index cd78ed4bc..55e497ead 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -42,9 +42,13 @@ - `onready` (optional, but...): this callback is called with no arguments when the worker fires its initial 'sqlite3-api'/'worker1-ready' message, which it does when - sqlite3.initWorker1API() completes its initialization. This is - the simplest way to tell the worker to kick off work at the - earliest opportunity. + sqlite3.initWorker1API() completes its initialization. This is the + simplest way to tell the worker to kick off work at the earliest + opportunity, and the only way to know when the worker module has + completed loading. The irony of using a callback for this, instead + of returning a promise from sqlite3Worker1Promiser() is not lost on + the developers: see sqlite3Worker1Promiser.v2() which uses a + Promise instead. - `onunhandled` (optional): a callback which gets passed the message event object for any worker.onmessage() events which @@ -156,6 +160,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi if(!config.worker) config.worker = callee.defaultConfig.worker; if('function'===typeof config.worker) config.worker = config.worker(); let dbId; + let promiserFunc; config.worker.onmessage = function(ev){ ev = ev.data; debug('worker1.onmessage',ev); @@ -163,7 +168,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi if(!msgHandler){ if(ev && 'sqlite3-api'===ev.type && 'worker1-ready'===ev.result) { /*fired one time when the Worker1 API initializes*/ - if(config.onready) config.onready(); + if(config.onready) config.onready(promiserFunc); return; } msgHandler = handlerMap[ev.type] /* check for exec per-row callback */; @@ -192,7 +197,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi try {msgHandler.resolve(ev)} catch(e){msgHandler.reject(e)} }/*worker.onmessage()*/; - return function(/*(msgType, msgArgs) || (msgEnvelope)*/){ + return promiserFunc = function(/*(msgType, msgArgs) || (msgEnvelope)*/){ let msg; if(1===arguments.length){ msg = arguments[0]; @@ -202,7 +207,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi msg.args = arguments[1]; msg.dbId = msg.args.dbId; }else{ - toss("Invalid arugments for sqlite3Worker1Promiser()-created factory."); + toss("Invalid arguments for sqlite3Worker1Promiser()-created factory."); } if(!msg.dbId && msg.type!=='open') msg.dbId = dbId; msg.messageId = genMsgId(msg); @@ -246,9 +251,10 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi return p; }; }/*sqlite3Worker1Promiser()*/; + globalThis.sqlite3Worker1Promiser.defaultConfig = { worker: function(){ -//#if target=es6-bundler-friendly +//#if target=es6-module return new Worker(new URL("sqlite3-worker1-bundler-friendly.mjs", import.meta.url),{ type: 'module' }); @@ -269,14 +275,72 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = { return new Worker(theJs + globalThis.location.search); //#endif } -//#ifnot target=es6-bundler-friendly +//#ifnot target=es6-module .bind({ currentScript: globalThis?.document?.currentScript }) //#endif , onerror: (...args)=>console.error('worker1 promiser error',...args) -}; +}/*defaultConfig*/; + +/** + sqlite3Worker1Promiser.v2(), added in 3.46, works identically to + sqlite3Worker1Promiser() except that it returns a Promise instead + of relying an an onready callback in the config object. The Promise + resolves to the same factory function which + sqlite3Worker1Promiser() returns. + + If config is-a function or is an object which contains an onready + function, that function is replaced by a proxy which will resolve + after calling the original function and will reject if that + function throws. +*/ +sqlite3Worker1Promiser.v2 = function(config){ + let oldFunc; + if( 'function' == typeof config ){ + oldFunc = config; + config = {}; + }else if('function'===typeof config?.onready){ + oldFunc = config.onready; + delete config.onready; + } + const promiseProxy = Object.create(null); + config = Object.assign((config || Object.create(null)),{ + onready: async function(func){ + try { + if( oldFunc ) await oldFunc(func); + promiseProxy.resolve(func); + } + catch(e){promiseProxy.reject(e)} + } + }); + const p = new Promise(function(resolve,reject){ + promiseProxy.resolve = resolve; + promiseProxy.reject = reject; + }); + try{ + this.original(config); + }catch(e){ + promiseProxy.reject(e); + } + return p; +}.bind({ + /* We do this because clients are + recommended to delete globalThis.sqlite3Worker1Promiser. */ + original: sqlite3Worker1Promiser +}); + +//#if target=es6-module +/** + When built as a module, we export sqlite3Worker1Promiser.v2() + instead of sqlite3Worker1Promise() because (A) its interface is more + conventional for ESM usage and (B) the ESM option export option for + this API did not exist until v2 was created, so there's no backwards + incompatibility. +*/ +export default sqlite3Worker1Promiser.v2; +//#endif /* target=es6-module */ //#else /* Built with the omit-oo1 flag. */ //#endif ifnot omit-oo1 diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index 0437ef35d..b8a2a8774 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -1382,15 +1382,19 @@ globalThis.WhWasmUtilInstaller = function(target){ conversion of argument or return types, but see xWrap() and xCallWrapped() for variants which do. + If the first argument is a function is is assumed to be + a WASM-bound function and is used as-is instead of looking up + the function via xGet(). + As a special case, if passed only 1 argument after the name and that argument in an Array, that array's entries become the function arguments. (This is not an ambiguous case because it's not legal to pass an Array object to a WASM function.) */ target.xCall = function(fname, ...args){ - const f = target.xGet(fname); + const f = (fname instanceof Function) ? fname : target.xGet(fname); if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function."); - if(f.length!==args.length) __argcMismatch(fname,f.length) + if(f.length!==args.length) __argcMismatch(((f===fname) ? f.name : fname),f.length) /* This is arguably over-pedantic but we want to help clients keep from shooting themselves in the foot when calling C APIs. */; return (2===arguments.length && Array.isArray(arguments[1])) @@ -1537,7 +1541,7 @@ globalThis.WhWasmUtilInstaller = function(target){ jsFuncToWasm(). - bindScope (string): one of ('transient', 'context', - 'singleton'). Bind scopes are: + 'singleton', 'permanent'). Bind scopes are: - 'transient': it will convert JS functions to WASM only for the duration of the xWrap()'d function call, using @@ -1787,11 +1791,29 @@ globalThis.WhWasmUtilInstaller = function(target){ const __xResultAdapterCheck = (t)=>xResult.get(t) || toss("Result adapter not found:",t); + /** + Fetches the xWrap() argument adapter mapped to t, calls it, + passing in all remaining arguments, and returns the result. + Throws if t is not mapped to an argument converter. + */ cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args); + /** + Identical to convertArg() except that it does not perform + an is-defined check on the mapping to t before invoking it. + */ cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args); + /** + Fetches the xWrap() result adapter mapped to t, calls it, passing + it v, and returns the result. Throws if t is not mapped to an + argument converter. + */ cache.xWrap.convertResult = (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); + /** + Identical to convertResult() except that it does not perform an + is-defined check on the mapping to t before invoking it. + */ cache.xWrap.convertResultNoCheck = (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined)); @@ -1903,15 +1925,15 @@ globalThis.WhWasmUtilInstaller = function(target){ const C-string, encoded as UTF-8, copies it to a JS string, and returns that JS string. - - `string:dealloc` or `utf8:dealloc) (results): treats the result value - as a non-const UTF-8 C-string, ownership of which has just been - transfered to the caller. It copies the C-string to a JS - string, frees the C-string, and returns the JS string. If such - a result value is NULL, the JS result is `null`. Achtung: when - using an API which returns results from a specific allocator, - e.g. `my_malloc()`, this conversion _is not legal_. Instead, an - equivalent conversion which uses the appropriate deallocator is - required. For example: + - `string:dealloc` or `utf8:dealloc` (results): treats the result + value as a non-const UTF-8 C-string, ownership of which has + just been transfered to the caller. It copies the C-string to a + JS string, frees the C-string, and returns the JS string. If + such a result value is NULL, the JS result is `null`. Achtung: + when using an API which returns results from a specific + allocator, e.g. `my_malloc()`, this conversion _is not + legal_. Instead, an equivalent conversion which uses the + appropriate deallocator is required. For example: ```js target.xWrap.resultAdapter('string:my_free',(i)=>{ @@ -2012,8 +2034,12 @@ globalThis.WhWasmUtilInstaller = function(target){ arguments may be passed in after that one, and what those arguments are, is _not_ part of the public interface and is _not_ stable. + + Maintenance reminder: the Ember framework modifies the core + Array type, breaking for-in loops. */ - for(const i in args) args[i] = cxw.convertArgNoCheck( + let i = 0; + for(; i < args.length; ++i) args[i] = cxw.convertArgNoCheck( argTypes[i], args[i], args, i ); return cxw.convertResultNoCheck(resultType, xf.apply(null,args)); diff --git a/ext/wasm/demo-worker1-promiser.html b/ext/wasm/demo-worker1-promiser.c-pp.html similarity index 86% rename from ext/wasm/demo-worker1-promiser.html rename to ext/wasm/demo-worker1-promiser.c-pp.html index e99131e6c..e0b487bdf 100644 --- a/ext/wasm/demo-worker1-promiser.html +++ b/ext/wasm/demo-worker1-promiser.c-pp.html @@ -6,7 +6,11 @@ +//#if target=es6-module + worker-promise (via ESM) tests +//#else worker-promise tests +//#endif
    worker-promise tests
    @@ -22,13 +26,17 @@
    Downloading...
    - +
    Most stuff on this page happens in the dev console.

    +//#if target=es6-module + +//#else +//#endif diff --git a/ext/wasm/demo-worker1-promiser.js b/ext/wasm/demo-worker1-promiser.c-pp.js similarity index 86% rename from ext/wasm/demo-worker1-promiser.js rename to ext/wasm/demo-worker1-promiser.c-pp.js index c2d24623a..f6fc9568a 100644 --- a/ext/wasm/demo-worker1-promiser.js +++ b/ext/wasm/demo-worker1-promiser.c-pp.js @@ -13,9 +13,15 @@ Demonstration of the sqlite3 Worker API #1 Promiser: a Promise-based proxy for for the sqlite3 Worker #1 API. */ -'use strict'; -(function(){ - const T = self.SqliteTestUtil; +//#if target=es6-module +import {default as promiserFactory} from "./jswasm/sqlite3-worker1-promiser.mjs"; +//#else +"use strict"; +const promiserFactory = globalThis.sqlite3Worker1Promiser.v2; +delete globalThis.sqlite3Worker1Promiser; +//#endif +(async function(){ + const T = globalThis.SqliteTestUtil; const eOutput = document.querySelector('#test-output'); const warn = console.warn.bind(console); const error = console.error.bind(console); @@ -33,31 +39,35 @@ logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms"); }; - //why is this triggered even when we catch() a Promise? - //window.addEventListener('unhandledrejection', function(event) { - // warn('unhandledrejection',event); - //}); - const promiserConfig = { - worker: ()=>{ - const w = new Worker("jswasm/sqlite3-worker1.js"); - w.onerror = (event)=>error("worker.onerror",event); - return w; +//#ifnot target=es6-module + /** + The v1 interfaces uses an onready function. The v2 interface optionally + accepts one but does not require it. If provided, it is called _before_ + the promise is resolved, and the promise is rejected if onready() throws. + */ + onready: function(f){ + /* f === the function returned by promiserFactory(). + Ostensibly (f === workerPromise) but this function is + called before the promiserFactory() Promise resolves, so + before workerPromise is set. */ + console.warn("This is the v2 interface - you don't need an onready() function."); }, +//#endif debug: 1 ? undefined : (...args)=>console.debug('worker debug',...args), onunhandled: function(ev){ error("Unhandled worker message:",ev.data); }, - onready: function(){ - self.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/; - runTests(); - }, onerror: function(ev){ error("worker1 error:",ev); } }; - const workerPromise = self.sqlite3Worker1Promiser(promiserConfig); - delete self.sqlite3Worker1Promiser; + const workerPromise = await promiserFactory(promiserConfig) + .then((func)=>{ + console.log("Init complete. Starting tests momentarily."); + globalThis.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/; + return func; + }); const wtest = async function(msgType, msgArgs, callback){ if(2===arguments.length && 'function'===typeof msgArgs){ @@ -271,5 +281,5 @@ }).finally(()=>logHtml('',"That's all, folks!")); }/*runTests2()*/; - log("Init complete, but async init bits may still be running."); + runTests(); })(); diff --git a/ext/wasm/dist.make b/ext/wasm/dist.make index 5d610e37b..e820e066d 100644 --- a/ext/wasm/dist.make +++ b/ext/wasm/dist.make @@ -19,10 +19,15 @@ MAKEFILE.dist := $(lastword $(MAKEFILE_LIST)) # built, and won't be built until we expand the dependencies. Thus we # have to use a temporary name for the archive until we can get # that binary built. +ifeq (1,$(SQLITE_C_IS_SEE)) +dist-name-extra := -see +else +dist-name-extra := +endif ifeq (,$(filter snapshot,$(MAKECMDGOALS))) -dist-name-prefix := sqlite-wasm +dist-name-prefix := sqlite-wasm$(dist-name-extra) else -dist-name-prefix := sqlite-wasm-snapshot-$(shell /usr/bin/date +%Y%m%d) +dist-name-prefix := sqlite-wasm$(dist-name-extra)-snapshot-$(shell /usr/bin/date +%Y%m%d) endif dist-name := $(dist-name-prefix)-TEMP @@ -49,12 +54,18 @@ dist.top.extras := \ tester1.js tester1.mjs \ demo-jsstorage.html demo-jsstorage.js \ demo-worker1.html demo-worker1.js \ - demo-worker1-promiser.html demo-worker1-promiser.js -dist.jswasm.extras := $(sqlite3-api.ext.jses) $(sqlite3.wasm) + demo-worker1-promiser.html demo-worker1-promiser.js \ + demo-worker1-promiser-esm.html demo-worker1-promiser.mjs +dist.jswasm.extras := $(sqlite3.wasm) \ + $(sqlite3-api.ext.jses) dist.common.extras := \ $(wildcard $(dir.common)/*.css) \ $(dir.common)/SqliteTestUtil.js +#$(info sqlite3-worker1-promiser.mjs = $(sqlite3-worker1-promiser.mjs)) +#$(info sqlite3-worker1.js = $(sqlite3-worker1.js)) +#$(info sqlite3-api.ext.jses = $(sqlite3-api.ext.jses)) +#$(info dist.jswasm.extras = $(dist.jswasm.extras)) .PHONY: dist snapshot # DIST_STRIP_COMMENTS $(call)able to be used in stripping C-style # from the dist copies of certain files. @@ -67,7 +78,8 @@ endef # STRIP_K1.js = list of JS files which need to be passed through # $(bin.stripcomments) with a single -k flag. STRIP_K1.js := $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) \ - $(sqlite3-worker1-bundler-friendly.js) $(sqlite3-worker1-promiser-bundler-friendly.js) + $(sqlite3-worker1-bundler-friendly.js) \ + $(sqlite3-api.ext.jses) # STRIP_K2.js = list of JS files which need to be passed through # $(bin.stripcomments) with two -k flags. STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \ @@ -88,6 +100,7 @@ STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \ dist: \ $(bin.stripccomments) $(bin.version-info) \ $(dist.build) $(STRIP_K1.js) $(STRIP_K2.js) \ + $(dist.jswasm.extras) $(dist.common.extras) \ $(MAKEFILE) $(MAKEFILE.dist) @echo "Making end-user deliverables..." @rm -fr $(dist-dir.top) diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make index 57141d7e2..496e518de 100644 --- a/ext/wasm/fiddle.make +++ b/ext/wasm/fiddle.make @@ -9,16 +9,18 @@ MAKEFILE.fiddle := $(lastword $(MAKEFILE_LIST)) # shell.c and its build flags... make-np-0 := make -C $(dir.top) -n -p make-np-1 := sed -e 's/(TOP)/(dir.top)/g' +# Extract SHELL_OPT and SHELL_DEP from the top-most makefile and import +# them as vars here... $(eval $(shell $(make-np-0) | grep -e '^SHELL_OPT ' | $(make-np-1))) -$(eval $(shell $(make-np-0) | grep -e '^SHELL_SRC ' | $(make-np-1))) +$(eval $(shell $(make-np-0) | grep -e '^SHELL_DEP ' | $(make-np-1))) # ^^^ can't do that in 1 invocation b/c newlines get stripped ifeq (,$(SHELL_OPT)) $(error Could not parse SHELL_OPT from $(dir.top)/Makefile.) endif -ifeq (,$(SHELL_SRC)) -$(error Could not parse SHELL_SRC from $(dir.top)/Makefile.) +ifeq (,$(SHELL_DEP)) +$(error Could not parse SHELL_DEP from $(dir.top)/Makefile.) endif -$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c) +$(dir.top)/shell.c: $(SHELL_DEP) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c) $(MAKE) -C $(dir.top) shell.c # /shell.c ######################################################################## diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js index 67f61008f..27d915eb2 100644 --- a/ext/wasm/fiddle/fiddle-worker.js +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -166,11 +166,10 @@ stdout("SQLite version", capi.sqlite3_libversion(), capi.sqlite3_sourceid().substr(0,19)); stdout('Welcome to the "fiddle" shell.'); - if(sqlite3.opfs){ + if(capi.sqlite3_vfs_find("opfs")){ stdout("\nOPFS is available. To open a persistent db, use:\n\n", " .open file:name?vfs=opfs\n\nbut note that some", "features (e.g. upload) do not yet work with OPFS."); - sqlite3.opfs.registerVfs(); } stdout('\nEnter ".help" for usage hints.'); this.exec([ // initialization commands... @@ -317,7 +316,7 @@ }; console.warn("Unknown fiddle-worker message type:",ev); }; - + /** emscripten module for use with build mode -sMODULARIZE. */ @@ -374,9 +373,7 @@ "for use in the dev console.", sqlite3); globalThis.sqlite3 = sqlite3; const dbVfs = sqlite3.wasm.xWrap('fiddle_db_vfs', "*", ['string']); - fiddleModule.fsUnlink = (fn)=>{ - return sqlite3.wasm.sqlite3_wasm_vfs_unlink(dbVfs(0), fn); - }; + fiddleModule.fsUnlink = (fn)=>fiddleModule.FS.unlink(fn); wMsg('fiddle-ready'); }).catch(e=>{ console.error("Fiddle worker init failed:",e); diff --git a/ext/wasm/fiddle/fiddle.js b/ext/wasm/fiddle/fiddle.js index 2a3d1746f..d28589835 100644 --- a/ext/wasm/fiddle/fiddle.js +++ b/ext/wasm/fiddle/fiddle.js @@ -403,8 +403,10 @@ E('#btn-reset').addEventListener('click',()=>SF.resetDb()); const taInput = E('#input'); const btnClearIn = E('#btn-clear'); + const selectExamples = E('#select-examples'); btnClearIn.addEventListener('click',function(){ taInput.value = ''; + selectExamples.selectedIndex = 0; },false); // Ctrl-enter and shift-enter both run the current SQL. taInput.addEventListener('keydown',function(ev){ @@ -733,16 +735,15 @@ ]}, //{name: "Timer on", sql: ".timer on"}, // ^^^ re-enable if emscripten re-enables getrusage() + {name: "Box Mode", sql: ".mode box"}, {name: "Setup table T", sql:[ ".nullvalue NULL\n", "CREATE TABLE t(a,b);\n", "INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);\n", "SELECT * FROM t;\n" ]}, - {name: "Table list", sql: ".tables"}, - {name: "Box Mode", sql: ".mode box"}, - {name: "JSON Mode", sql: ".mode json"}, - {name: "Mandlebrot", sql:[ + {name: "sqlite_schema", sql: "select * from sqlite_schema"}, + {name: "Mandelbrot", sql:[ "WITH RECURSIVE", " xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),\n", " yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),\n", @@ -760,7 +761,13 @@ " FROM m2 GROUP BY cy\n", " )\n", "SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n", - ]} + ]}, + {name: "JSON pretty-print", + sql: "select json_pretty(json_object('ex',json('[52,3.14159]')))" + }, + {name: "JSON pretty-print (with tabs)", + sql: "select json_pretty(json_object('ex',json('[52,3.14159]')),char(0x09))" + } ]; const newOpt = function(lbl,val){ const o = document.createElement('option'); diff --git a/ext/wasm/index-dist.html b/ext/wasm/index-dist.html index f5bcdc1cb..7b778b020 100644 --- a/ext/wasm/index-dist.html +++ b/ext/wasm/index-dist.html @@ -97,6 +97,8 @@ wrapper is significantly easier to use, however.
  • demo-worker1-promiser: a demo of the Promise-based wrapper of the Worker1 API.
  • +
  • demo-worker1-promiser-esm: + same as the previous demo except loads the promiser from an ESM module.
  • diff --git a/ext/wasm/index.html b/ext/wasm/index.html index ebbfd6763..d12a3aa03 100644 --- a/ext/wasm/index.html +++ b/ext/wasm/index.html @@ -84,6 +84,8 @@ wrapper is significantly easier to use, however.
  • demo-worker1-promiser: a demo of the Promise-based wrapper of the Worker1 API.
  • +
  • demo-worker1-promiser-esm: + same as the previous demo except loads the promiser from an ESM module.
  • speedtest1 ports (sqlite3's primary benchmarking tool)... diff --git a/ext/wasm/speedtest1-worker.js b/ext/wasm/speedtest1-worker.js index 3abc589b5..5261c8393 100644 --- a/ext/wasm/speedtest1-worker.js +++ b/ext/wasm/speedtest1-worker.js @@ -111,10 +111,6 @@ self.sqlite3InitModule(EmscriptenModule).then(async (sqlite3)=>{ const S = globalThis.S = App.sqlite3 = sqlite3; log("Loaded speedtest1 module. Setting up..."); - App.vfsUnlink = function(pDb, fname){ - const pVfs = S.wasm.sqlite3_wasm_db_vfs(pDb, 0); - if(pVfs) S.wasm.sqlite3_wasm_vfs_unlink(pVfs, fname||0); - }; App.pDir = wasmfsDir(S.wasm); App.wasm = S.wasm; //if(App.pDir) log("Persistent storage:",pDir); diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index 36ca4c976..fe5bdc837 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -63,7 +63,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; /* Predicate for tests/groups. */ const testIsTodo = ()=>false; const haveWasmCTests = ()=>{ - return !!wasm.exports.sqlite3_wasm_test_intptr; + return !!wasm.exports.sqlite3__wasm_test_intptr; }; const hasOpfs = ()=>{ return globalThis.FileSystemHandle @@ -722,7 +722,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; //log("xCall()..."); { - const pJson = w.xCall('sqlite3_wasm_enum_json'); + const pJson = w.xCall('sqlite3__wasm_enum_json'); T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300); } @@ -736,9 +736,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.mustThrowMatching(()=>fw(1), /requires 0 arg/); let rc = fw(); T.assert('string'===typeof rc).assert(rc.length>5); - rc = w.xCallWrapped('sqlite3_wasm_enum_json','*'); + rc = w.xCallWrapped('sqlite3__wasm_enum_json','*'); T.assert(rc>0 && Number.isFinite(rc)); - rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8'); + rc = w.xCallWrapped('sqlite3__wasm_enum_json','utf8'); T.assert('string'===typeof rc).assert(rc.length>300); @@ -821,7 +821,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; if(haveWasmCTests()){ if(!sqlite3.config.useStdAlloc){ - fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']); + fw = w.xWrap('sqlite3__wasm_test_str_hello', 'utf8:dealloc',['i32']); rc = fw(0); T.assert('hello'===rc); rc = fw(1); @@ -831,14 +831,14 @@ globalThis.sqlite3InitModule = sqlite3InitModule; if(w.bigIntEnabled){ w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v)); w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v)); - fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice'); + fw = w.xWrap('sqlite3__wasm_test_int64_times2','thrice','twice'); rc = fw(1); T.assert(12n===rc); w.scopedAllocCall(function(){ const pI1 = w.scopedAlloc(8), pI2 = pI1+4; w.pokePtr([pI1, pI2], 0); - const f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']); + const f = w.xWrap('sqlite3__wasm_test_int64_minmax',undefined,['i64*','i64*']); const [r1, r2] = w.peek64([pI1, pI2]); T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2)); }); @@ -942,7 +942,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). assert(0===wts.$ppV).assert(0===wts.$xFunc); const testFunc = - W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/); + W.xGet('sqlite3__wasm_test_struct'/*name gets mangled in -O3 builds!*/); let counter = 0; //log("wts.pointer =",wts.pointer); const wtsFunc = function(arg){ @@ -1128,7 +1128,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.g('sqlite3.oo1') .t('Create db', function(sqlite3){ const dbFile = '/tester1.db'; - wasm.sqlite3_wasm_vfs_unlink(0, dbFile); + sqlite3.util.sqlite3__wasm_vfs_unlink(0, dbFile); const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c'); db.onclose = { disposeAfter: [], @@ -1459,7 +1459,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; rv = db.exec("SELECT 1 WHERE 0",{rowMode: 0}); T.assert(Array.isArray(rv)).assert(0===rv.length); if(wasm.bigIntEnabled && haveWasmCTests()){ - const mI = wasm.xCall('sqlite3_wasm_test_int64_max'); + const mI = wasm.xCall('sqlite3__wasm_test_int64_max'); const b = BigInt(Number.MAX_SAFE_INTEGER * 2); T.assert(b === db.selectValue("SELECT "+b)). assert(b === db.selectValue("SELECT ?", b)). @@ -1482,7 +1482,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; /*step() skipped intentionally*/.reset(true); } finally { T.assert(0===st.finalize()) - .assert(undefined===st.finalize()); + .assert(undefined===st.finalize()); } try { @@ -1685,7 +1685,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.assert(n>0 && db2.selectValue(sql) === n); }finally{ db2.close(); - wasm.sqlite3_wasm_vfs_unlink(0, filename); + sqlite3.util.sqlite3__wasm_vfs_unlink(0, filename); } } }/*sqlite3_js_posix_create_file()*/) @@ -2075,7 +2075,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; try{ ptrInt = w.scopedAlloc(4); w.poke32(ptrInt,origValue); - const cf = w.xGet('sqlite3_wasm_test_intptr'); + const cf = w.xGet('sqlite3__wasm_test_intptr'); const oldPtrInt = ptrInt; T.assert(origValue === w.peek32(ptrInt)); const rc = cf(ptrInt); @@ -2090,13 +2090,13 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const v64 = ()=>w.peek64(pi64) T.assert(v64() == o64); //T.assert(o64 === w.peek64(pi64)); - const cf64w = w.xGet('sqlite3_wasm_test_int64ptr'); + const cf64w = w.xGet('sqlite3__wasm_test_int64ptr'); cf64w(pi64); T.assert(v64() == BigInt(2 * o64)); cf64w(pi64); T.assert(v64() == BigInt(4 * o64)); - const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2'); + const biTimes2 = w.xGet('sqlite3__wasm_test_int64_times2'); T.assert(BigInt(2 * o64) === biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError in the call :/ */)); @@ -2106,13 +2106,13 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const g64 = (p)=>w.peek64(p); w.poke64([pMin, pMax], 0); const minMaxI64 = [ - w.xCall('sqlite3_wasm_test_int64_min'), - w.xCall('sqlite3_wasm_test_int64_max') + w.xCall('sqlite3__wasm_test_int64_min'), + w.xCall('sqlite3__wasm_test_int64_max') ]; T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)). assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER)); //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]); - w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax); + w.xCall('sqlite3__wasm_test_int64_minmax', pMin, pMax); T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch"). assert(g64(pMax) === minMaxI64[1], "int64 mismatch"); //log("pMin",g64(pMin), "pMax",g64(pMax)); @@ -2560,7 +2560,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; //////////////////////////////////////////////////////////////////////// .t('Close db', function(){ T.assert(this.db).assert(wasm.isPtr(this.db.pointer)); - //wasm.sqlite3_wasm_db_reset(this.db); // will leak virtual tables! + //wasm.sqlite3__wasm_db_reset(this.db); // will leak virtual tables! this.db.close(); T.assert(!this.db.pointer); }) @@ -2587,7 +2587,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const pVfs = capi.sqlite3_vfs_find('kvvfs'); T.assert(pVfs); const JDb = this.JDb = sqlite3.oo1.JsStorageDb; - const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)}; + const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile); unlink(); let db = new JDb(filename); try { @@ -2605,6 +2605,60 @@ globalThis.sqlite3InitModule = sqlite3InitModule; } } }/*kvvfs sanity checks*/) +//#if enable-see + .t({ + name: 'kvvfs with SEE encryption', + predicate: ()=>(isUIThread() + || "Only available in main thread."), + test: function(sqlite3){ + this.kvvfsUnlink(); + let db; + const encOpt1 = 1 + ? {textkey: 'foo'} + : {key: 'foo'}; + const encOpt2 = encOpt1.textkey + ? encOpt1 + : {hexkey: new Uint8Array([0x66,0x6f,0x6f]/*==>"foo"*/)} + try{ + db = new this.JDb({ + filename: this.kvvfsDbFile, + ...encOpt1 + }); + db.exec([ + "create table t(a,b);", + "insert into t(a,b) values(1,2),(3,4)" + ]); + db.close(); + let err; + try{ + db = new this.JDb({ + filename: this.kvvfsDbFile, + flags: 'ct' + }); + T.assert(db) /* opening is fine, but... */; + db.exec("select 1 from sqlite_schema"); + console.warn("sessionStorage =",sessionStorage); + }catch(e){ + err = e; + }finally{ + db.close(); + } + T.assert(err,"Expecting an exception") + .assert(sqlite3.capi.SQLITE_NOTADB==err.resultCode, + "Expecting NOTADB"); + db = new sqlite3.oo1.DB({ + filename: this.kvvfsDbFile, + vfs: 'kvvfs', + ...encOpt2 + }); + T.assert( 4===db.selectValue('select sum(a) from t') ); + }finally{ + if( db ) db.close(); + this.kvvfsUnlink(); + } + } + })/*kvvfs with SEE*/ +//#endif enable-see ;/* end kvvfs tests */ //////////////////////////////////////////////////////////////////////// @@ -2888,18 +2942,17 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .t({ name: 'OPFS db sanity checks', test: async function(sqlite3){ + T.assert(capi.sqlite3_vfs_find('opfs')); + const opfs = sqlite3.opfs; const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db'; - const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs'); - T.assert(pVfs); - const unlink = this.opfsUnlink = - (fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)}; - unlink(); - let db = new sqlite3.oo1.OpfsDb(filename); + const fileUri = 'file://'+filename+'?delete-before-open=1'; + const initSql = [ + 'create table p(a);', + 'insert into p(a) values(1),(2),(3)' + ]; + let db = new sqlite3.oo1.OpfsDb(fileUri); try { - db.exec([ - 'create table p(a);', - 'insert into p(a) values(1),(2),(3)' - ]); + db.exec(initSql); T.assert(3 === db.selectValue('select count(*) from p')); db.close(); db = new sqlite3.oo1.OpfsDb(filename); @@ -2911,7 +2964,14 @@ globalThis.sqlite3InitModule = sqlite3InitModule; && 0===this.opfsDbExport.byteLength % 512); }finally{ db.close(); - unlink(); + } + T.assert(await opfs.entryExists(filename)); + try { + db = new sqlite3.oo1.OpfsDb(fileUri); + db.exec(initSql) /* will throw if delete-before-open did not work */; + T.assert(3 === db.selectValue('select count(*) from p')); + }finally{ + if(db) db.close(); } } }/*OPFS db sanity checks*/) @@ -2919,15 +2979,17 @@ globalThis.sqlite3InitModule = sqlite3InitModule; name: 'OPFS import', test: async function(sqlite3){ let db; + const filename = this.opfsDbFile; try { const exp = this.opfsDbExport; - const filename = this.opfsDbFile; delete this.opfsDbExport; this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp); db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); T.assert(6 === db.selectValue('select count(*) from p')). assert( this.opfsImportSize == exp.byteLength ); db.close(); + const unlink = this.opfsUnlink = + (fn=filename)=>sqlite3.util.sqlite3__wasm_vfs_unlink("opfs",fn); this.opfsUnlink(filename); T.assert(!(await sqlite3.opfs.entryExists(filename))); // Try again with a function as an input source: @@ -2954,11 +3016,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; name: '(Internal-use) OPFS utility APIs', test: async function(sqlite3){ const filename = this.opfsDbFile; - const pVfs = this.opfsVfs; const unlink = this.opfsUnlink; - T.assert(filename && pVfs && !!unlink); + T.assert(filename && !!unlink); delete this.opfsDbFile; - delete this.opfsVfs; delete this.opfsUnlink; /************************************************************** ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended @@ -3209,6 +3269,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; print: log, printErr: error }).then(async function(sqlite3){ + TestUtil.assert(!!sqlite3.util); log("Done initializing WASM/JS bits. Running tests..."); sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes."); globalThis.S = sqlite3; @@ -3227,9 +3288,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; logClass('warning',"BigInt/int64 support is disabled."); } if(haveWasmCTests()){ - log("sqlite3_wasm_test_...() APIs are available."); + log("sqlite3__wasm_test_...() APIs are available."); }else{ - logClass('warning',"sqlite3_wasm_test_...() APIs unavailable."); + logClass('warning',"sqlite3__wasm_test_...() APIs unavailable."); } log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', ')); TestUtil.runTests(sqlite3); diff --git a/main.mk b/main.mk index 081e0cd3b..139e182eb 100644 --- a/main.mk +++ b/main.mk @@ -230,7 +230,6 @@ SRC += \ SRC += \ $(TOP)/ext/misc/stmt.c - # FTS5 things # FTS5_HDR = \ @@ -375,7 +374,9 @@ TESTSRC += \ $(TOP)/ext/rtree/test_rtreedoc.c \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/dbdata.c \ - $(TOP)/ext/recover/test_recover.c + $(TOP)/ext/recover/test_recover.c \ + $(TOP)/ext/intck/test_intck.c \ + $(TOP)/ext/intck/sqlite3intck.c #TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c @@ -659,7 +660,7 @@ target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl fts5.c touch target_source sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl src-verify - tclsh $(TOP)/tool/mksqlite3c.tcl + tclsh $(TOP)/tool/mksqlite3c.tcl $(EXTRA_SRC) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c @@ -671,7 +672,7 @@ sqlite3ext.h: target_source cp tsrc/sqlite3ext.h . sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl src-verify - tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros=1 + tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros=1 $(EXTRA_SRC) echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c @@ -717,8 +718,7 @@ opcodes.c: opcodes.h $(TOP)/tool/mkopcodec.tcl tclsh $(TOP)/tool/mkopcodec.tcl opcodes.h >opcodes.c opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl - cat parse.h $(TOP)/src/vdbe.c | \ - tclsh $(TOP)/tool/mkopcodeh.tcl >opcodes.h + cat parse.h $(TOP)/src/vdbe.c | tclsh $(TOP)/tool/mkopcodeh.tcl >opcodes.h # Rules to build parse.c and parse.h - the outputs of lemon. # @@ -741,32 +741,37 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c $(BCC) -o mkkeywordhash $(OPTS) $(TOP)/tool/mkkeywordhash.c ./mkkeywordhash >keywordhash.h -# Source files that go into making shell.c -SHELL_SRC = \ - $(TOP)/src/shell.c.in \ - $(TOP)/ext/misc/appendvfs.c \ - $(TOP)/ext/misc/completion.c \ - $(TOP)/ext/misc/base64.c \ - $(TOP)/ext/misc/base85.c \ - $(TOP)/ext/misc/decimal.c \ - $(TOP)/ext/misc/fileio.c \ - $(TOP)/ext/misc/ieee754.c \ - $(TOP)/ext/misc/regexp.c \ - $(TOP)/ext/misc/series.c \ - $(TOP)/ext/misc/shathree.c \ - $(TOP)/ext/misc/sqlar.c \ - $(TOP)/ext/misc/uint.c \ - $(TOP)/ext/expert/sqlite3expert.c \ - $(TOP)/ext/expert/sqlite3expert.h \ - $(TOP)/ext/misc/zipfile.c \ - $(TOP)/ext/misc/memtrace.c \ - $(TOP)/ext/misc/pcachetrace.c \ - $(TOP)/ext/recover/dbdata.c \ - $(TOP)/ext/recover/sqlite3recover.c \ - $(TOP)/ext/recover/sqlite3recover.h \ - $(TOP)/src/test_windirent.c - -shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl +# Source and header files that shell.c depends on +SHELL_DEP = \ + $(TOP)/src/shell.c.in \ + $(TOP)/ext/consio/console_io.c \ + $(TOP)/ext/consio/console_io.h \ + $(TOP)/ext/expert/sqlite3expert.c \ + $(TOP)/ext/expert/sqlite3expert.h \ + $(TOP)/ext/intck/sqlite3intck.c \ + $(TOP)/ext/intck/sqlite3intck.h \ + $(TOP)/ext/misc/appendvfs.c \ + $(TOP)/ext/misc/base64.c \ + $(TOP)/ext/misc/base85.c \ + $(TOP)/ext/misc/completion.c \ + $(TOP)/ext/misc/decimal.c \ + $(TOP)/ext/misc/fileio.c \ + $(TOP)/ext/misc/ieee754.c \ + $(TOP)/ext/misc/memtrace.c \ + $(TOP)/ext/misc/pcachetrace.c \ + $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/series.c \ + $(TOP)/ext/misc/shathree.c \ + $(TOP)/ext/misc/sqlar.c \ + $(TOP)/ext/misc/uint.c \ + $(TOP)/ext/misc/zipfile.c \ + $(TOP)/ext/recover/dbdata.c \ + $(TOP)/ext/recover/sqlite3recover.c \ + $(TOP)/ext/recover/sqlite3recover.h \ + $(TOP)/src/test_windirent.c \ + $(TOP)/src/test_windirent.h + +shell.c: $(SHELL_DEP) $(TOP)/tool/mkshellc.tcl tclsh $(TOP)/tool/mkshellc.tcl >shell.c diff --git a/manifest b/manifest index e068a441a..430e21933 100644 --- a/manifest +++ b/manifest @@ -1,28 +1,30 @@ -C Version\s3.45.3 -D 2024-04-15T13:34:05.554 +C Version\s3.46.0 +D 2024-05-23T13:25:27.566 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79 +F Makefile.in 993a7874e3d3721df61846f03dda4a9ef7490da11953ae36ba1bb0c0346eaf4a F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 -F Makefile.msc df56c06ef05add5ebcdcc9d4823fb2ec66ac16b06acb6971a7420859025886fa +F Makefile.msc e64a52619310d3067f6c38f56eedd15918a82dade70954197d6da486ad99d7f4 F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3 -F VERSION 72f46aae916c0bad94b6e64aef327e60bad69bb591a93b95f079ccedcb32b7c7 +F VERSION c84541c6a9e8426462176fbb1f9ecb5cfd7d1bb56228053ff7eeba8841673eb6 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 +F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5 +F art/icon-80x90.gif 65509ce3e5f86a9cd64fe7fca2d23954199f31fe44c1e09e208c80fb83d87031 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc ac338c36a338f6b49475da71930f45145a181d6b551578d5a7a64f113ef27b2c +F autoconf/Makefile.msc 7ac6c331fc3b8aa57b6782db995b8c0e49230352decd4e2662fd07c06a9ed623 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac 13ba7cab3eedc3af6650fa0037deac0de3c21f909165c2e76e0995fd546b958c +F autoconf/tea/configure.ac 9e74135563a901d9b1a019bad1c9d73a6659fa32325f6a565bef72bfb0ec7297 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -33,16 +35,16 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 7398a16e7a444c105d0300a6a0f5e7248fb0bef006385c07b53dfaf0d3c7bbd8 x +F configure 40f7af9ed5ca0d44a4b9bc7ad34f1ee4867bb4eeb19e75036be6bed66193a498 x F configure.ac f25bd7843120f2c2b8bc9db5a92b0502bbdd28e68907415c3d42fc8e57c657b9 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/compile-for-windows.md 50b27d77be96195c66031a3181cb8684ed822327ea834e07f9c014213e5e3bcf F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f F doc/jsonb.md 5fab4b8613aa9153fbeb6259297bd4697988af8b3d23900deba588fa7841456b -F doc/lemon.html 44a53a1d2b42d7751f7b2f478efb23c978e258d794bfd172442307a755b9fa44 +F doc/lemon.html 8b266ff711d2ec7f867c3dca37634963f48a630329908cc282beebfa8c708706 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 -F doc/testrunner.md 8d36ec692cf4994bb66d84a4645b9afa1ce9d47dc12cbf8d437c5a5fb6ddeedb +F doc/testrunner.md 15583cf8c7d8a1c3378fd5d4319ca769a14c4d950a5df9b015d01d5be290dc69 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a @@ -55,15 +57,15 @@ F ext/consio/console_io.c f32b757c9ee7fdf68e7586bee306f8368759e7cd12febb2a683919 F ext/consio/console_io.h 0548b83d7c4b7270ad544a67f2bb90cebc519637fa39b1838df4744cf0d87646 F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test 0dd5cb096d66bed593e33053a3b364f6ef52ed72064bf5cf298364636dbf3cd6 -F ext/expert/sqlite3expert.c 90446bb1183429308c68ceb23e00cb8252f43b8886cf5efa3fc7e833b5fa24c8 +F ext/expert/expert1.test 53a749de08939e3bc14f804e97410927d46fa772cbce0247d7e8fa6fc2523b0c +F ext/expert/sqlite3expert.c c8cea5ff15fbe792cccc4992a9b40b706411c41d32611f617897fecac6ff06a4 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c7cc3bf59ee F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c fd64a588471ce00b19da08acb0d6f904277a21ac1d15141d5913c83591afa027 +F ext/fts3/fts3.c c922380b62bd15bce953dae3350337acbd0fff07c10cdc805819409791eb480a F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe F ext/fts3/fts3Int.h 968f7d7cae541a6926146e9fd3fb2b2ccbd3845b7890a8ed03de0c06ac776682 F ext/fts3/fts3_aux.c 7eab82a9cf0830f6551ba3abfdbe73ed39e322a4d3940ee82fbf723674ecd9f3 @@ -72,7 +74,7 @@ F ext/fts3/fts3_hash.c 8b6e31bfb0844c27dc6092c2620bdb1fca17ed613072db057d96952c6 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf F ext/fts3/fts3_icu.c 305ce7fb6036484085b5556a9c8e62acdc7763f0f4cdf5fd538212a9f3720116 F ext/fts3/fts3_porter.c e19807ce0ae31c1c6e9898e89ecc93183d7ec224ea101af039722a4f49e5f2b8 -F ext/fts3/fts3_snippet.c 4d6523e3eddeb7b46e7a82b3476a0a86a0c04821e0e2b8dd40f45ee28057cb13 +F ext/fts3/fts3_snippet.c 610328fe128c047c6b0eba77768982ccf3933daae095d497949a75c9dfd47409 F ext/fts3/fts3_term.c 845f0e2456b1be42f7f1bec1da1dfc05bc347531eff90775ffc6698902c281de F ext/fts3/fts3_test.c d8d7b2734f894e8a489987447658e374cdd3a3bc8575c401decf1911cb7c6454 F ext/fts3/fts3_tokenize_vtab.c 7fd9ef364f257b97218b9c331f2378e307375c592f70fd541f714e747d944962 @@ -81,7 +83,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c c1de4ae28356ad98ccb8b2e3388a7fdcce7607b5523738c9afb6275dab765154 F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226 F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f -F ext/fts3/fts3_write.c c2d7a8dfb6e7a00c6c88ce626785cf4c50ed18eba34b5fbd53cacd60af96d0f2 +F ext/fts3/fts3_write.c 81cd8f7e8003e427a1801e04842776b731af26dd93af206e4e66ea5ae319cad1 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674 @@ -90,7 +92,7 @@ F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7 F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6dbd6348ef0cfc324a7 F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb F ext/fts5/extract_api_docs.tcl bc3a0ca78be7d3df08e7602c00ca48021ebae40682d75eb001bfdf6e54ffb44e -F ext/fts5/fts5.h ecba24fed7b359b3a53016bb07e411b3b4c9cdf163aa141006536423a63b611e +F ext/fts5/fts5.h 8856e11a5f0269cd346754cea0765efe8089635b80cad3222e8bfdb08cd5348a F ext/fts5/fts5Int.h defa43c0932265138ee910ca416e6baccf8b774e0f3d610e74be1ab2880e9834 F ext/fts5/fts5_aux.c 4584e88878e54828bf7d4d0d83deedd232ec60628b7731be02bad6adb62304b1 F ext/fts5/fts5_buffer.c 0eec58bff585f1a44ea9147eae5da2447292080ea435957f7488c70673cb6f09 @@ -98,7 +100,7 @@ F ext/fts5/fts5_config.c 8072a207034b51ae9b7694121d1b5715c794e94b275e088f70ae532 F ext/fts5/fts5_expr.c e91156ebdcc08d837f4f324168f69f3c0d7fdef0e521fd561efb48ef3297b696 F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1 F ext/fts5/fts5_index.c ee0f4d50bc0c58a7c5ef7d645e7e38e1e59315b8ea9d722ae00c5f949ee65379 -F ext/fts5/fts5_main.c cd56ed9619e9bc55ae603ecafd5965c3684bb4c1de7dd00893c307ddf98afe88 +F ext/fts5/fts5_main.c d68bd9533d5a638b7f6fae61c3cb0a15257dcdcccedaf3d0b3c9f55940c85048 F ext/fts5/fts5_storage.c f9e31b0d155e9b2c92d5d3a09ad7a56b937fbf1c7f962e10f4ca6281349f3934 F ext/fts5/fts5_tcl.c fdf7e2bb9a9186cfcaf2d2ce11d338309342b7a7593c2812bc54455db53da5d2 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee @@ -163,7 +165,7 @@ F ext/fts5/test/fts5fault4.test 87a10d0caee57da587c7588b0c8d25d2930197399b4812ad F ext/fts5/test/fts5fault5.test a336e4e11847de24c9497f80cce18e00bb3fab7fb11f97d04eb9af898900a762 F ext/fts5/test/fts5fault6.test a0fc0a8f99e4b16500c31dfc7e38e1defe0f1693ac47650517ac7b723b1956f8 F ext/fts5/test/fts5fault7.test 0acbec416edb24b8881f154e99c31e9ccf73f539cfcd164090be139e9e97ed4c -F ext/fts5/test/fts5fault8.test 318238659d35f82ad215ecb57ca4c87486ea85d45dbeedaee42f148ff5105ee2 +F ext/fts5/test/fts5fault8.test 9353fe6a2a993c3231e09c49b0f4a12c8d306319555ff2ca6672b5b86fe9b0dd F ext/fts5/test/fts5fault9.test 098e6b894bbdf9b2192f994a30f4043673fb3f338b6b8ab1624c704422f39119 F ext/fts5/test/fts5faultA.test be4487576bff8c22cee6597d1893b312f306504a8c6ccd3c53ca85af12290c8c F ext/fts5/test/fts5faultB.test d606bdb8e81aaeb6f41de3fc9fc7ae315733f0903fbff05cf54f5b045b729ab5 @@ -246,8 +248,17 @@ F ext/fts5/tool/loadfts5.tcl 95b03429ee6b138645703c6ca192c3ac96eaf093 F ext/fts5/tool/mkfts5c.tcl 3eba8e9bee4221ed165f3304b51b2a74a705f4ec5df3d044573a2be539534af8 F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 -F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 +F ext/icu/icu.c 3add8197e0a86c1761771a39500ebae749438bcf1836160b407a56b4eaa8721c F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 +F ext/intck/intck1.test f3a3cba14b6aeff145ffa5515546dd22f7510dad91512e519f43b92b56514012 +F ext/intck/intck2.test d2457c7e5e5b688046d15ebe08a1e1427cc5e7a6dc8d6af215f42e8bcaf67304 +F ext/intck/intck_common.tcl a61fd2697ae55b0a3d89847ca0b590c6e0d8ff64bebb70920d93724799894159 +F ext/intck/intckbusy.test d5ed4ef85a4b1dc1dee2484bd14a4bb68529659cca743327df0c775f005fa387 +F ext/intck/intckcorrupt.test f6c302792326fb3db9dcfc70b554c55369bc4b52882eaaf039cfe0b74c821029 +F ext/intck/intckfault.test cff3f75dff74abb3edfcb13f6aa53f6436746ab64b09fe5e2028f051e985efab +F ext/intck/sqlite3intck.c 0d10df36e2b7b438aa80ecd3f5e584d41b747586b038258fe6b407f66b81e7c5 +F ext/intck/sqlite3intck.h 2b40c38e7063ab822c974c0bd4aed97dabb579ccfe2e180a4639bb3bbef0f1c9 +F ext/intck/test_intck.c 34243458378a12d1356c79219a03f244800533b3ab65b4a02861f0403364df12 F ext/jni/GNUmakefile 59eb05f2a363bdfac8d15d66bed624bfe1ff289229184f3861b95f98a19cf4b2 F ext/jni/README.md d899789a9082a07b99bf30b1bbb6204ae57c060efcaa634536fa669323918f42 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa @@ -371,7 +382,7 @@ F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0e F ext/misc/btreeinfo.c cb952620eedf5c0b7625b678f0f08e54d2ec0011d4e50efda5ebdc97f3df7d04 F ext/misc/carray.c 34fac63770971611c5285de0a9f0ac67d504eaf66be891f637add9290f1c76a5 F ext/misc/carray.h 503209952ccf2431c7fd899ebb92bf46bf7635b38aace42ec8aa1b8d7b6e98a5 -F ext/misc/cksumvfs.c 9224e33cc0cb6aa61ff1d7d7b8fd6fe56beca9f9c47954fa4ae0a69bef608f69 +F ext/misc/cksumvfs.c 3a7931dd30667be6348af919f3f9e6188dfd7646b42af8e399a499b327f5bd63 F ext/misc/closure.c 0e04f52d93e678dd6f950f195f365992edf3c380df246f3d80425cba4c13891e F ext/misc/completion.c ef78835483b43ac18c96be312b90b615d8368189909be03513ab7a9338131298 F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 @@ -380,7 +391,7 @@ F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f82 F ext/misc/decimal.c 172cf81a8634e6a0f0bedaf71a8372fee63348cf5a3c4e1b78bb233c35889fdc F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 606100185fb90d6a1eade1ed0414d53503c86820d8956a06e3b0a56291894f2b -F ext/misc/fileio.c d88e60f63557d76d4e38acffda5556b2ab42e98f5d830897f22aba65930d975c +F ext/misc/fileio.c 916638042f318701460485032e33981056747d0f92e6757aa9499f2363ea7047 F ext/misc/fossildelta.c 8c026e086e406e2b69947f1856fa3b848fff5379962276430d10085b8756b05a F ext/misc/fuzzer.c 8b28acf1a7e95d50e332bdd47e792ff27054ad99d3f9bc2e91273814d4b31a5a F ext/misc/ieee754.c 62a90978204d2c956d5036eb89e548e736ca5fac0e965912867ddd7bb833256d @@ -400,12 +411,12 @@ F ext/misc/regexp.c 4bdd0045912f81c84908bd535ec5ad3b1c8540b4287c70ab840709636240 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 -F ext/misc/series.c 80692f675de989629ee20796f75010ce87ca826cb383131040e84f7e1bea5d7a +F ext/misc/series.c d96e5aac21658c6b5d54f918ac140460ec7197734c1a4fba806950831a7b1e7a F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d F ext/misc/shathree.c 543af7ce71d391cd3a9ab6924a6a1124efc63211fd0f2e240dc4b56077ba88ac F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c c0aa7b80d6df45f7da59d912b38752bcac1af53a5766966160e6c5cdd397dbea -F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7 +F ext/misc/sqlar.c a6175790482328171da47095f87608b48a476d4fac78d8a9ff18b03a2454f634 F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321 F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b F ext/misc/totype.c 75ed9827d19cc3b434fc2aeb60725d4d46e1534373615612a4d1cfdcc3d60922 @@ -415,7 +426,7 @@ F ext/misc/urifuncs.c f71360d14fa9e7626b563f1f781c6148109462741c5235ac63ae0f8917 F ext/misc/uuid.c 5bb2264c1b64d163efa46509544fd7500cb8769cb7c16dd52052da8d961505cf F ext/misc/vfslog.c 3932ab932eeb2601dbc4447cb14d445aaa9fbe43b863ef5f014401c3420afd20 F ext/misc/vfsstat.c a85df08654743922a19410d7b1e3111de41bb7cd07d20dd16eda4e2b808d269d -F ext/misc/vtablog.c f2c9d41afe00b51b2c8307b79f508eb0c2dcd57bae074807944e73376fcf15b7 +F ext/misc/vtablog.c 1100250ce8782db37c833e3a9a5c9a3ecf1af5e15b8325572b82e6e0a138ffb5 F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f65b4fc0668 F ext/misc/zipfile.c 64cb3d98b6316586e6056d182051aa9d28fdedfbf4b908e6b7a7d70209b1db11 @@ -464,16 +475,18 @@ F ext/rbu/rbuvacuum.test 542561741ff2b262e3694bc6012b44694ee62c545845319a06f3237 F ext/rbu/rbuvacuum2.test ae097d04feb041446a74fac94b24bffeb3fdd60e32b848c5611e507ab702b81b F ext/rbu/rbuvacuum3.test 3ce42695fdf21aaa3499e857d7d4253bc499ad759bcd6c9362042c13cd37d8de F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69eefaebb205 -F ext/rbu/sqlite3rbu.c d4ddf8f0e93772556e452a6c2814063cf47efb760a0834391a9d0cd9859fa4b9 +F ext/rbu/sqlite3rbu.c 4a3376c0fb9a844a799ac529fb81260523f6b13c9f629bc270c632dbae5fc1f8 F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 -F ext/recover/dbdata.c d2e00d3cac74319c9c6def2e56ab2146b4f4ba5d820ab275e8da24e9766c247e -F ext/recover/recover1.test c484d01502239f11b61f23c1cee9f5dd19fa17617f8974e42e74d64639c524cf +F ext/recover/dbdata.c a22ecd689f00ff2ad33b5633c4ef84c8f088c65faeac18d4eb73c128395c7aec +F ext/recover/recover1.test e16d78e94183562abff569967b18b7c77451d7044365516cd0fe14713a284851 F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a F ext/recover/recoverbuild.test c74170e0f7b02456af41838afeb5353fdb985a48cc2331d661bbabbca7c6b8e3 F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e F ext/recover/recovercorrupt.test 64c081ad1200ae77b447da99eb724785d6bf71715f394543dc7689642e92bf49 F ext/recover/recovercorrupt2.test 1418f1710debc24ff38276cedfcea234beb37a34205708e7e3e6d76cc4a979db +F ext/recover/recovercorrupt3.test 2e7b9a1b528ca23ed382cec6f64e3fcbbd0f8e852add7562397fd8df83f335d5 +F ext/recover/recovercorrupt4.test cc4a56086c50fba6a5b20db122a3f220195b3d4f11a86e0858c7f5f5d8ba58d1 F ext/recover/recoverfault.test 9d9f88eeb222615a25e7514f234c950d46bee20d24cd8db49d8fff8d650dcfe1 F ext/recover/recoverfault2.test 730e7371bcda769554d15460cb23126abba1be8eca9539ccabf63623e7bb7e09 F ext/recover/recoverold.test 68db3d6f85dd2b98e785b6c4da4f5eea4bbe52ccf6674d9a94c7506dc92596aa @@ -495,9 +508,9 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c 0dd4775e896cee6067979d67aff7c998e75c2c9d9cd8d62a1a790c09cde7adca -F ext/rtree/rtree.c bdf97fac91b37b3a6041ab4a0dd06e81c7ba1541601733b40f97fb51e76e79ce +F ext/rtree/rtree.c b6133dba5ae331fa6c1fc34df6aa623eba951b05ac35116f954a0bf7ab550436 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 -F ext/rtree/rtree1.test 2b5b8c719c6a4abe377f57766f428a49af36a93061cb146cccfdc3b30000c0a4 +F ext/rtree/rtree1.test e0608db762b2aadca0ecb6f97396cf66244490adc3ba88f2a292b27be3e1da3e F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d F ext/rtree/rtree3.test 272594f88c344e973864008bbe4c71fd3a41a264c097d568593ee7886d83d409 F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b @@ -555,6 +568,8 @@ F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8 F ext/session/sessionalter.test 460bdac2832a550519f6bc32e5db2c0cee94f335870aaf25a3a403a81ab20e17 F ext/session/sessionat.test 00c8badb35e43a2f12a716d2734a44d614ff62361979b6b85419035bc04b45ee F ext/session/sessionbig.test 47c381e7acfabeef17d98519a3080d69151723354d220afa2053852182ca7adf +F ext/session/sessionchange.test 77c4702050f24270b58070e2cf01c95c3d232a3ef164b70f31974b386ce69903 +F ext/session/sessionconflict.test 8b8cbd98548e2e636ddc17d0986276f60e833fb865617dd4f88ea5bbe3a16b96 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test 573bf027fb870d57bd4e7cf50822a3e4b17b2b923407438747aaa918dec57a09 F ext/session/sessionfault2.test b0d6a7c1d7398a7e800d84657404909c7d385965ea8576dc79ed344c46fbf41c @@ -567,44 +582,45 @@ F ext/session/sessionnoop2.test de4672dce88464396ec9f30ed08c6c01643a69c53ae540fa F ext/session/sessionrebase.test 702378bdcb5062f1106e74457beca8797d09c113a81768734a58b197b5b334e2 F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a80600a44396f7363 F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 -F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c +F ext/session/sessionstat1.test 5e718d5888c0c49bbb33a7a4f816366db85f59f6a4f97544a806421b85dc2dec F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c 829d468f0f3d2710aace56b0116a7ca3f414683ce78e3125ae5e21547a895078 -F ext/session/sqlite3session.h 4cf19a51975746d7cff2fdd74db8b769c570958e1c3639ac150d824ac1553b3e -F ext/session/test_session.c 7b94ad945cd4afe6c73ee935aeb3d44b4446186e1729362af616c7695a5283d9 +F ext/session/sqlite3session.c c7473aafbd88f796391a8c25aa90975a8f3729ab7f4f8cf74ab9d3b014e10abe +F ext/session/sqlite3session.h 683ccbf16e2c2521661fc4c1cf918ce57002039efbcabcd8097fa4bca569104b +F ext/session/test_session.c 8bcc857125372e640f75ab63b4188080f9bbab92b65f86dfd160721c574b2044 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 -F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 +F ext/userauth/user-auth.txt ca7e9ee82ca4e1c1744295f8184dd70edfae1992865d26c64303f539eb6c084c F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 8a4d5ca21d7f21c4751ef732f2bb837c915641ee8ad25753929998fa44847e61 +F ext/wasm/GNUmakefile 21f015f342e4ed9b7ff632c10750512259d26c836a34f4f535673fae9a7c9fcc F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff -F ext/wasm/SQLTester/SQLTester.mjs ec2f6ba63a0f2f0562941a0fb8e46b7dc55589711513f1952349785966edfe50 +F ext/wasm/SQLTester/SQLTester.mjs ce765c0ad7d57f93553d12ef4dca574deb00300134a26d472daacab49031e1fb F ext/wasm/SQLTester/SQLTester.run.mjs c72b7fe2072d05992f7a3d8c6a1d34e95712513ceabe40849784e24e41c84638 F ext/wasm/SQLTester/index.html 3f8a016df0776be76605abf20e815ecaafbe055abac0e1fe5ea080e7846b760d F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api f442460ed9a109e637dd3ea1caa4489553ad9414e8988118b208bb7a4bbece6b F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 -F ext/wasm/api/README.md 5eb44fa02e9c693a1884a3692428647894b0380b24bca120866b7a24c8786134 +F ext/wasm/api/README.md 34fe11466f9c1d81b10a0469e1114e5f1c5a6365c73d80a1a6ca639a1a358b73 F ext/wasm/api/extern-post-js.c-pp.js c4154a7f90c2d7e51fd6738273908152036c3457fdc0b6523f1be3ef51105aac F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1 -F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 +F ext/wasm/api/post-js-header.js 04dc12c3edd666b64a1b4ef3b6690c88dcc653f26451fd4734472d8e29c1c122 F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219 F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e -F ext/wasm/api/sqlite3-api-glue.js 119b91c8a7ce6648679eb66fcdd1ed07ef7fd892eb501d658fbfefcc962012d9 -F ext/wasm/api/sqlite3-api-oo1.js 7f3bcf0549ac44cde4b9da0b642d771916738d3f6781fb8a1757c50a91e506c0 -F ext/wasm/api/sqlite3-api-prologue.js 9aeba7b45cf41b3a26d34d7fb2525633cd1adfc544888c1ea8dbb077496f4ce9 -F ext/wasm/api/sqlite3-api-worker1.js fd46628ef147dd5856c88f63a9a279a40f744f1fdfddd55251ad8fbc3d8200ae +F ext/wasm/api/sqlite3-api-glue.js 114085f4dceb28e06d20d3fb597b2501a4aa69f4b6cd29234f7cc1cf81d5b92d +F ext/wasm/api/sqlite3-api-oo1.js c373cc04625a96bd3f01ce8ebeac93a5d38dbda6215818c925570df5a945565e +F ext/wasm/api/sqlite3-api-prologue.js b347a0c5350247f90174a0ad9b9e72a99a5f837f31f78f60fcdb829b2ca30b63 +F ext/wasm/api/sqlite3-api-worker1.js 9704b77b5eb9d0d498ceeaf3e7a837021b14c52ac15d6556c7f97e278ec725c3 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 -F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 -F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 -F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 90d93d3365567d52c6959ed5d6f80adccd173799772d73639ff647ada58193d4 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 46c4afa6c50d7369252c104f274ad977a97e91ccfafc38b400fe36e90bdda88e -F ext/wasm/api/sqlite3-wasm.c dfd1f1a225b267e8fd641dcd6c7d579fbe2b731aeaa123324135efac830a2bcf -F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js f234191fe6bf41a5a1e59c9f43ed816e74a522b3d60d3f556f66c3085c448503 +F ext/wasm/api/sqlite3-opfs-async-proxy.js 196ad83d36ca794e564044788c9d21b964679d63cad865f604da37c4afc9a285 +F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 8433ee332d5f5e39fb19427fccb7bad7f44aa99b5504daad3343fc128c311e78 +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 3c72f1a0e6a7343c8c882d29d01bb440f10be12c844651605b486e76f3d6cc8c +F ext/wasm/api/sqlite3-vtab-helper.c-pp.js a2fcbc3fecdd0eea229283584ebc122f29d98194083675dbe5cb2cf3a17fe309 +F ext/wasm/api/sqlite3-wasm.c 9267174b9b0591b4f71193542ab57adf95bb9415f7d3453acf4a8ca8052f5e6c +F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 46f303ba8ddd1b2f0a391798837beddfa72e8c897038c8047eda49ce7d5ed46b F ext/wasm/api/sqlite3-worker1.c-pp.js 5e8706c2c4af2a57fbcdc02f4e7ef79869971bc21bb8ede777687786ce1c92d5 F ext/wasm/batch-runner-sahpool.html e9a38fdeb36a13eac7b50241dfe7ae066fe3f51f5c0b0151e7baee5fce0d07a7 F ext/wasm/batch-runner-sahpool.js 54a3ac228e6c4703fe72fb65c897e19156263a51fe9b7e21d2834a45e876aabd @@ -614,25 +630,25 @@ F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25 F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f -F ext/wasm/common/whwasmutil.js 4c64594eecc7af4ae64259e95a71ba2a7edf118881aaff0bba86d0c7164e78e4 +F ext/wasm/common/whwasmutil.js f7b3461028899b923fb554029a5c980396b02800b742aabb44346dd8704fd11b F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.js c7b3cca50c55841c381a9ca4f9396e5bbdc6114273d0b10a43e378e32e7be5bf F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8 -F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f -F ext/wasm/demo-worker1-promiser.js 5e5c7d7c91cd7aae9cc733afd02569ba9c6928292db413b550e8b842f4b75e87 +F ext/wasm/demo-worker1-promiser.c-pp.html 635cf90685805e21772a5f7a35d1ace80f98a9ef7c42ff04d7a125ddca7e5db8 +F ext/wasm/demo-worker1-promiser.c-pp.js fcc628cb42fcfaf07d250477801de1e6deb1e319d003976612a0db8d76b9fccc F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d F ext/wasm/demo-worker1.js 836bece8615b17b1b572584f7b15912236a5947fe8c68b98d2737d7e287447ef -F ext/wasm/dist.make 3a851858aad72e246a5d9c5aaf6b6a144305f1bf898ac1846760ea7bab95c9a3 +F ext/wasm/dist.make 653e212c1e84aa3be168d62a10616ccea45ee9585b0192745d2706707a5248ce F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f -F ext/wasm/fiddle.make fa2ba6e90457ba2a71cb745f200d409caf773076df75ae5b177cc225d7627a11 +F ext/wasm/fiddle.make 3c2eace29255d6ddd219f5d8cc2728cb28b9fe717ea80b6062c2a6178947a16b F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f -F ext/wasm/fiddle/fiddle-worker.js e0153f9af6500805c6f09c0b3cfdb7d857e9d6863dbee9d50d1628fccf5f4b4d -F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715 +F ext/wasm/fiddle/fiddle-worker.js 850e66fce39b89d59e161d1abac43a181a4caa89ddeea162765d660277cd84ce +F ext/wasm/fiddle/fiddle.js b444a5646a9aac9f3fc06c53d78af5e1912eb235d69a8e6010723e4eb0e9d4a1 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 -F ext/wasm/index-dist.html e91d76e4581185238fd3d42ed86ec600f7023ed3e3a944c5c356f25304bf1263 -F ext/wasm/index.html b31ce41c0da476d5ffcef23069b9d3415b419d65af5779096ebcfbcbade453a9 +F ext/wasm/index-dist.html 564b5ec5669676482c5a25dea9e721d8eafed426ecb155f93d29aeff8507511f +F ext/wasm/index.html 4337f495416756802669f69f9f9f3df9f87ee4c1918e6718719b4b5718e4713a F ext/wasm/jaccwabyt/jaccwabyt.js 1264710db3cfbcb6887d95665b7aeba60c1126eaef789ca4cf1a4a17d5bc7f54 F ext/wasm/jaccwabyt/jaccwabyt.md 59a20df389abcc3606eb4eaea7fb7ba14504beb3e345dbea9b99a0618ba3bec8 F ext/wasm/module-symbols.html dc476b403369b26a1a23773e13b80f41b9a49f0825e81435fe3600a7cfbbe337 @@ -641,7 +657,7 @@ F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd84223150 F ext/wasm/speedtest1-wasmfs.html 0e9d335a9b5b5fafe6e1bc8dc0f0ca7e22e6eb916682a2d7c36218bb7d67379d F ext/wasm/speedtest1-wasmfs.mjs ac5cadbf4ffe69e9eaac8b45e8523f030521e02bb67d654c6eb5236d9c456cbe F ext/wasm/speedtest1-worker.html 864b65ed78ce24847a348c180e7f267621a02ca027068a1863ec1c90187c1852 -F ext/wasm/speedtest1-worker.js 4d2ea70a3c24e05bdca78025202841f33d298c4fa9541a0070c3228661f89ecd +F ext/wasm/speedtest1-worker.js 95e549e13a4d35863a9a7fc66122b5f546c0130d3be7b06dfcc556eb66d24bde F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 @@ -650,7 +666,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 -F ext/wasm/tester1.c-pp.js a92dc256738dbd1b50f142d1fd0c835294ba09b7bb6526650360e942f88cb63f +F ext/wasm/tester1.c-pp.js 6d0a9aa44a97b4aadd582e0999ce45a2671b854a12ea3205d1c908da6bd4bdef F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -658,7 +674,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk ef8d6b0e76b27d2b32d7b71ea30a2b2626b668f998a4f32f6171c9623a310a53 +F main.mk e5da4b6c13f2b15bba7f1efb0a47089dfdde0973e85a024785485655d59fd758 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -670,25 +686,25 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 -F src/alter.c 30c2333b8bb3af71e4eb9adeadee8aa20edb15917ed44b8422e5cd15f3dfcddc -F src/analyze.c 0f15753308c3bca7674f31fa7e0807ffcb8b120c36eef7d00b62b33079ddc854 +F src/alter.c e1b6782b85dd758f89e5c588e4e3eb82638c2dafc0c857b79a43bb8ec1746fca +F src/analyze.c a3df28274e2565ba5656577d7e3fd262169a213e6eb0bd47890e0f0729a4031c F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39 F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 -F src/btree.c 0d264fb19a783325fb48be7e74bb19999b17914896e36bdee73c5d31d06d616d -F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240 -F src/btreeInt.h 3e2589726c4f105e653461814f65857465da68be1fac688de340c43b873f4062 -F src/build.c ec93d135f9c02b2a5d174384b6f9c222c16b1c8e473c482284759f3f0a35e336 +F src/btree.c 71b80e77b255144db47180fda8138740608e382a44231942464029b1a45fc036 +F src/btree.h 55066f513eb095db935169dab1dc2f7c7a747ef223c533f5d4ad4dfed346cbd0 +F src/btreeInt.h 98aadb6dcb77b012cab2574d6a728fad56b337fc946839b9898c4b4c969e30b6 +F src/build.c 11ec7014a3c468e7b3ccc8dda8d9111cd5a29a358df18818788601e0600aaabd F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 64e4b1227b4ed123146f0aa2989131d1fbd9b927b11e80c9d58c6a68f9cd5ce3 -F src/date.c 3b8d02977d160e128469de38493b4085f7c5cf4073193459909a6af3cf6d7c91 +F src/date.c 126ba2ab10aeb2e7ba6e089b5f07b747c0625b8287f78b60da346eda8d23c875 F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 -F src/expr.c 1fece60622b8b74d8b3bf59faba4c2509a177ea65d9642dfbd22ceb7ec6eeea5 +F src/expr.c f7bad20d2f74005f1f876e7fbb627222ea28250e44b296b047403720c5c21818 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 F src/func.c 283d4f3b2751a1d9339fd93a8a013d1948fd5f4474a3cab0955eb4fafd445d0f @@ -697,12 +713,12 @@ F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 7d318f638431af858b5ebd5d2c17d6dacd0adf114dbbfbe23f3033c3c043c7f0 -F src/json.c 29a42bc92c2384653b8b5e5ad26bdee4e2334544c7cfb78ceb4a3ca81d674686 +F src/insert.c 4bd7c7e54a1062dcd0214b7a6296f7194eb10fb14d3ddca1ed20b01c2a86a18c +F src/json.c bf1b51e32158b3d01d96a878d3dba8d2e633a7e5bf2534d4617f89de8a6b9a91 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 F src/main.c 8a59d297ec77e6b78550433bfccb95a1b26f2fb69aaaf233206e21579a1cfcc1 -F src/malloc.c f016922435dc7d1f1f5083a03338a3e91f8c67ce2c5bdcfa4cdef62e612f5fcc +F src/malloc.c 410e570b30c26cc36e3372577df50f7a96ee3eed5b2b161c6b6b48773c650c5e F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 @@ -722,28 +738,28 @@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c fa9b81b642e60e77ffaf98bd1a2e5fde16c1c2317614ec178bf3bd5864772356 +F src/os_unix.c 6227cbc4ac93046f121436886cf3712da6f4e2082af6314f976eeae1d86b794a F src/os_win.c 6ff43bac175bd9ed79e7c0f96840b139f2f51d01689a638fd05128becf94908a F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 9beb80f6e330dd63c5d8ba0f7a7f3a55fff22067a68d424949c389bfc6fa0c56 F src/pager.h 4b1140d691860de0be1347474c51fee07d5420bd7f802d38cbab8ea4ab9f538a -F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec +F src/parse.y 50516253433303673ff6b009983bb246d1527415e5a9af22acc51b0eedb9a10d F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 -F src/pragma.c a13593a09da0d27b44a57bf6b2b9f6d163ad78aef4ad1d64c6214897dbe0c9ba +F src/pragma.c 52bfbf6dfd668b69b5eb9bd1186e3a67367c8453807150d6e75239229924f684 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c 371f6115cb69286ebc12c6f2d7511279c2e47d9f54f475d46a554d687a3b312c -F src/printf.c d3392b2a20ee314ddeef34fb43c904bf4619eb20ff9a9e07e3950a7e4dcd6912 +F src/prepare.c d99931f45416652895e502328ca49fe782cfc4e1ebdcda13b3736d991ebf42ce +F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c b81374797e47a2e98c2a10b7152ad70280dfeb228f26ac45470760d02e360318 +F src/resolve.c 22f1fa3423b377c02ae78d451cfeb1c2d96dcf0389c0642cbdcd19d3bfd7ae01 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 8b9b78a58781955d5bc676efde95e3cbc82174bf7c1b3968c855faff134c24d2 -F src/shell.c.in e4815eb8a7b3110994a1d7e9f42e6b3e9e3576d25ec63f75259b797938cada98 -F src/sqlite.h.in 4f7840e1abb041b80da0af48f870e2ae3552e2a97c902eebe7455fe5a89562e3 +F src/select.c 1a841c38974d45cf15a7611398479182b61ad4c187423c380741d8b1688fe607 +F src/shell.c.in 885dafabb3f16d68bdb4576683afb0e39a1939f50985b162255bf656c470babf +F src/sqlite.h.in c71d9ef76a6d32dc7ff2d373f2e57ce09056af26c1457bcadae5358b7628c7c3 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 867a6691a5f06ceb6a17a828337f6ace1ad35c44f324bf50d4a2169c3db8571f +F src/sqliteInt.h 6a9fa3902c9faca2b57060e822f2afadfbf96d64c4ede81e201f0e0c42d7e4aa F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -759,7 +775,7 @@ F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 -F src/test_bestindex.c f6af1e41cb7901edafb065a8198e4a0192dd42432b642d038965be5e628dec12 +F src/test_bestindex.c 770429c434221afe6216ec81fe4c00ad3bbdad1d5e64576aa613ffb7c5a984f0 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c 5fa77ee6064ba546e144c4fea870c5ede2c54314616f81485c6a9c4192100c75 @@ -789,7 +805,7 @@ F src/test_schema.c cbfd7a9a9b6b40d4377d0c76a6c5b2a58387385977f26edab4e77eb5f90a F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 9fdb13b1df05e639808d44fcb8f6064aaded32b6565c00b215cfd05a060d1aca -F src/test_tclsh.c 3ff5d188a72f00807425954ea3b493dfd3a4b890ecc6700ea83bad2fd1332ecf +F src/test_tclsh.c aaf0d1de4a518a8db5ad38e5262be3e48b4a74ad1909f2dba753cecb30979d5d F src/test_tclvar.c 3273f9d59395b336e381b53cfc68ec6ebdaada4e93106a2e976ffb0550504e1c F src/test_thread.c 7ddcf0c8b79fa3c1d172f82f322302c963d923cdb503c6171f3c8081586d0b01 F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43 @@ -800,34 +816,34 @@ F src/test_windirent.h da2e5b73c32d09905fbdd00f27cd802212a32a58ead882736fe4f5eb7 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 -F src/treeview.c c6fc972683fd00f975d8b32a81c1f25d2fb7d4035366bf45c9f5622d3ccd70ee -F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81 +F src/tokenize.c 3f703cacdab728d7741e5a6ac242006d74fe1c2754d4f03ed889d7253259bd68 +F src/treeview.c 38eefdc85d2793c4059ae651a611b30eb034389fb428f69e572bbea565da6c78 +F src/trigger.c 0858f75818ed1580332db274f1032bcc5effe567cb132df5c5be8b1d800ca97f F src/update.c 732404a04d1737ef14bb6ec6b84f74edf28b3c102a92ae46b4855438a710efe7 F src/upsert.c 2e60567a0e9e8520c18671b30712a88dc73534474304af94f32bb5f3ef65ac65 F src/utf.c f23165685a67b4caf8ec08fb274cb3f319103decfb2a980b7cfd55d18dfa855e -F src/util.c a94fdaa08e3bbd35e425a1c7aed3065646cffe2a1cdf870885e8c6c61ce22bfc +F src/util.c 4d6d7ebfe6772a1b950c97bbb1d1a72ad4874617ec498ab8aa73b7f5a43e44bb F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 -F src/vdbe.c b2a45392265cb83f60251406039bf5255462d4a6d8deb05b2eaccab5abb2e20b -F src/vdbe.h 88e19a982df9027ec1c177c793d1a5d34dc23d8f06e3b2d997f43688b05ee0eb +F src/vdbe.c 3b1793c5d2235ae89b01ef051a33d7d2ad3704c71799653b112686735ad401ff +F src/vdbe.h c2d78d15112c3fc5ab87f5e8e0b75d2db1c624409de2e858c3d1aafb1650bb4f F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c -F src/vdbeapi.c 8f57d60c89da0b60e6d4e272358c511f6bae4e24330bdb11f8b42f986d1bf21b -F src/vdbeaux.c 56900c9a41f23260c8346f212bd6005eb9171f9a2f70d0cfb1441a078a0e4b84 +F src/vdbeapi.c 80235ac380e9467fec1cb0883354d841f2a771976e766995f7e0c77f845406df +F src/vdbeaux.c 3bcf13776c39bf660a52b4b97f6389a421c2756f9ffbf4c0d94f73e1935d8d9c F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5 -F src/vdbemem.c 0012d5f01cc866833847c2f3ae4c318ac53a1cb3d28acad9c35e688039464cf0 +F src/vdbemem.c 831a244831eaa45335f9ae276b50a7a82ee10d8c46c2c72492d4eb8c98d94d89 F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 -F src/vdbevtab.c 2143db7db0ceed69b21422581f434baffc507a08d831565193a7a02882a1b6a7 -F src/vtab.c 11948e105f56e84099ca17f1f434b1944539ea84de26d0d767eadfbc670ce1ea +F src/vdbevtab.c fc46b9cbd759dc013f0b3724549cc0d71379183c667df3a5988f7e2f1bd485f3 +F src/vtab.c 5fb499d20494b7eecaadb7584634af9afcb374cb0524912b475fcb1712458a1b F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c e3cb2a01bc265547b936fcbbbce8d55fdf8b017e7d55803be31b56cfc16d3fae +F src/where.c 6f02c3936d1f9a637d8d7b5ad7362371af3e4434b0ec1eb950189a83de560d59 F src/whereInt.h 82a13766f13d1a53b05387c2e60726289ef26404bc7b9b1f7770204d97357fb8 -F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 -F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 -F src/window.c 5b1387d59df30d481ed14cceef5f4d1dab1f8752aa106ba72c8b62777bd139d2 +F src/wherecode.c f5255f49d1f42b6e7e6b0362ff3522fa88cbcaa7213e52f9374744027ecdebca +F src/whereexpr.c 67d15caf88a1a9528283d68ff578e024cf9fe810b517bb0343e5aaf695ad97dd +F src/window.c 5d95122dd330bfaebd732358c8ef067c5a9394a53ac249470d611d0ce2c52be2 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 @@ -838,7 +854,7 @@ F test/aggorderby.test cc3abf5de64d46ff66395ca8c2346b66c2576d5aedb7bffc5b0742508 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 F test/alter.test 3c00eff1e2036b9f93e9cd0f3d3e63750ac87ecb5bc71b9d7bd07cbf2ac4c494 -F test/alter2.test a966ccfcddf9ce0a4e0e6ff1aca9e6e7948e0e242cd7e43fc091948521807687 +F test/alter2.test 7e3d26ab409df52df887b366a63902c3429b935c41cb962fd58ffc25784f2f19 F test/alter3.test ffc4ab29ce78a3517a66afd69b2730667e3471622509c283b2bd4c46f680fba3 F test/alter4.test 716caa071dd8a3c6d57225778d15d3c3cbf5e34b2e84ae44199aeb2bbf50a707 F test/alterauth.test 63442ba61ceb0c1eeb63aac1f4f5cebfa509d352276059d27106ae256bafc959 @@ -854,8 +870,8 @@ F test/altermalloc2.test 17fb3724c4b004c469c27dc4ef181608aa644555fbd3f3236767584 F test/altermalloc3.test 8040e486368403f2fdd6fc3998258b499bd4cc2f3ddbb5f8f874cd436f076e81 F test/alterqf.test 8ec03d776de9c391daa0078ea8f838903bdcfb11dfae4ba3576b48436834ccba F test/altertab.test 8a2712f9076da5012a002d0b5cc0a421398a5bf61c25bab41b77c427586a7a27 -F test/altertab2.test 62597b6fd08feaba1b6bfe7d31dac6117c67e06dc9ce9c478a3abe75b5926de0 -F test/altertab3.test 6c432fbb9963e0bd6549bf1422f6861d744ee5a80cb3298564e81e556481df16 +F test/altertab2.test fff90e3f01e8eb0e09282f538b8ec7cfeb035dbedbe570fe1983440f4613ad0e +F test/altertab3.test b331ae34e69594e19605e3297805202d6156fcc8f75379dfd972a2e51cae8721 F test/altertrig.test aacc980b657354fe2d3d4d3a004f07d04ccc1a93e5ef82d68a79088c274ddc6b F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 2fb21d7d64748636384e6cb8998dbf83968caf644c07fcb4f76c18f2e7ede94b @@ -899,7 +915,7 @@ F test/autoindex5.test 2ee94f033b87ca0160e08d81034c507aff8e230df2627f0304fa309b2 F test/autovacuum.test 00671369bbf96c6a49989a9425f5b78b94075d6a4b031e5e00000c2c32f365df F test/autovacuum2.test 76f7eb4fe6a6bf6d33a196a7141dba98886d2fb53a268d7feca285d5da4759d7 F test/autovacuum_ioerr2.test 8a367b224183ad801e0e24dcb7d1501f45f244b4 -F test/avfs.test 0c3a38e03cccb0fc3127838462dc05dc3f4c1480d770c084b388304c25de3652 +F test/avfs.test 76f59743dc1f5fa533840d1818b420fe1ee45e21c0fd6bbac7942ba677903128 F test/avtrans.test b7dc25459ecbd86c6fa9c606ee3068f59d81e225118617dcf2bbb6ded2ade89e F test/backcompat.test 3e64cedda754c778ef6bbe417b6e7a295e662a4d F test/backup.test 3b08fd4af69f0fa786931103a31f4542b184aba16e239e5f22b18c3c2476697f @@ -919,10 +935,11 @@ F test/bestindex4.test 3039894f2dad50f3a68443dffad1b44c9b067ac03870102df1ce3d9a4 F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb75dc98d F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4 F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e -F test/bestindex8.test 333ad8c6a554b885a49b68c019166eda92b05f493a92b36b0acdf7f766d04dad +F test/bestindex8.test b63a4f171a2c83d481bb14c431a8b72e85d27b2ffdaa0435a95d58ca941678f9 F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0 F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce +F test/bestindexC.test 9e6f184be080fd9c4605a7e5c7097eed1a259372f9af78151c37b072a9086f86 F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -946,13 +963,13 @@ F test/boundary4.test 89e02fa66397b8a325d5eb102b5806f961f8ec4b F test/btree01.test fef17d9e999ac4f04095948e3438fbe674f4e07bb2c63bb1cad41d87baee077f F test/btree02.test 7555a5440453d900410160a52554fe6478af4faf53098f7235f1f443d5a1d6cc F test/btreefault.test a82a23b0578bc587afbf9a622c8f54a54f63762f062ba8a35613cfee38ab42f9 -F test/busy.test 510dc6daaad18bcbbc085bcc6217d6dc418def5e73f72ce1475eea0cb7834727 +F test/busy.test caff7164c16ce06a53af51f9e4c2753d4cc64250e00790a5e48b9c4f4be37597 F test/busy2.test 20823a5d7c42fb257d9f108c66312d90b1bb4ec3d80ba6b4e371073727560f98 F test/cache.test 13bc046b26210471ca6f2889aceb1ea52dc717de F test/cacheflush.test af25bb1509df04c1da10e38d8f322d66eceedf61 F test/cachespill.test 895997f84a25b323b166aecb69baab2d6380ea98f9e0bcc688c4493c535cfab9 F test/capi2.test 4ee545824adc3eb33bf57ef89f77440b28188ec3da72e5425ff0fcdba32e8d5a -F test/capi3.test 3910a73c38ac76d69778dd9eb481ab7cd6ed59117fc047b4f6056a5c72529de1 +F test/capi3.test 4892b5e53d2a6941edc9d204a0ab174dd66e8689282d9a15e4384561c3965945 F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4 F test/capi3c.test 31d3a6778f2d06f2d9222bd7660c41a516d1518a059b069e96ebbeadb5a490f7 F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde @@ -965,6 +982,7 @@ F test/changes2.test 07949edcc732af28cb54276bfb7d99723bccc1e905a423648bf57ac5cb0 F test/check.test 56e4ed457e9f8683b9fc56f5b964f461f6e8a8dd5a13f3d495408215d66419ed F test/checkfault.test da6cb3d50247169efcb20bdf57863a3ccfa1d27d9e55cd324f0680096970f014 F test/chunksize.test 427d87791743486cbf0c3b8c625002f3255cb3a89c6eba655a98923b1387b760 +F test/cksumvfs.test 6f05dc95847c06a3dc10eee6b5ab1351d78314a52d0db15717c9388f4cb96646 F test/close.test eccbad8ecd611d974cbf47278c3d4e5874faf02d811338d5d348af42d56d647c F test/closure01.test 9905883f1b171a4638f98fc764879f154e214a306d3d8daf412a15e7f3a9b1e0 F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91 @@ -997,8 +1015,8 @@ F test/corrupt8.test 2399dfe40d2c0c63af86706e30f3e6302a8d0516 F test/corrupt9.test 730a3db08d4ab9aa43392ea30d9c2b4879cbff85 F test/corruptA.test 112f4b2ae0b95ebf3ea63718642fb969a93acea557ace3a307234d19c245989b F test/corruptB.test 73a8d6c0b9833697ecf16b63e3c5c05c945b5dec -F test/corruptC.test 9cf32275dae3ca33f645afe5d1d3f5ba5ac2af2b0833dfb5282f9dccb6fb81bb -F test/corruptD.test a828c788535946a372a56a750b242cd96287cd823657abe5a73c5e51b91bdd28 +F test/corruptC.test 7d6d9e907334ea3ccb7111a0656cafa30a28f8a5f2aaf1c45ad712236302856a +F test/corruptD.test 614320aa519f6bf6c7dd2f581f9513ff7b6826954180cca1a606d0e25ea084a3 F test/corruptE.test 4143791f2dfb443aec5b7fabfa5821e6063eccc3b49b06f212c2f014715fd476 F test/corruptF.test be9fde98e4c93648f1ba52b74e5318edc8f59fe4 F test/corruptG.test adf79b669cbfd19e28c8191a610d083ae53a6d51 @@ -1006,10 +1024,10 @@ F test/corruptH.test 79801d97ec5c2f9f3c87739aa1ec2eb786f96454 F test/corruptI.test 9d8cbf6214e492abe9e822e759b9751ae336cec0a6fe3ff3b37bfbd8ff9c22ca F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4 F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af -F test/corruptL.test 504d90502d9993440226edc355d2275524b89064ea3df5ee5c27f7028ec59d07 +F test/corruptL.test 652fc8ac0763a6fd3eb28b951d481924167b2d9936083bcc68253b2274a0c8fe F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067 F test/corruptN.test 7c099d153a554001b4fb829c799b01f2ea6276cbc32479131e0db0da4efd9cc4 -F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576 +F test/cost.test cc434a026b1e9d0d98137a147e24e5daf1b1ad09e9ff7da63b34c83ddd136d92 F test/count.test cd4bd531066e8d77ef8fe1e3fc8253d042072e117ccab214b290cf83f1602249 F test/countofview.test 4088e461a10ee33e69803c177a69aa1d7bba81a9ffc2df66d76465a22ca7fdfc F test/coveridxscan.test f35c7208dedc4f98e471c569df64c0f95a49f6e072d8dc7c8f99bdee2697de1b @@ -1030,10 +1048,10 @@ F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c47 F test/cursorhint.test 05cf0febe5c5f8a31f199401fd1c9322249e753950d55f26f9d5aca61408a270 F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8 -F test/date.test ff2341a1ef71b9a27979494d299222f9a293aa22cb9ff6e9c38d88a895317ebf +F test/date.test c8ff835023f2107b57ce7a45c92265d51c98a23fc93231e998f12d850831aad6 F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1 F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5 -F test/date4.test 8aeb3de5b5e9fda968baa9357e4c0fae573724b7904943410195a19e96e31b6a +F test/date4.test 75dc8401e8c0639a228cd26a6eaa4ff5ea8ccda912b9853d1c9462c476670e17 F test/dbdata.test 042f49acff3438f940eeba5868d3af080ae64ddf26ae78f80c92bec3ca7d8603 F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e F test/dbfuzz001.test 6c9a4622029d69dc38926f115864b055cb2f39badd25ec22cbfb130c8ba8e9c3 @@ -1044,7 +1062,7 @@ F test/dbpagefault.test 35f06cfb2ef100a9b19d25754e8141b9cba9b7daabd4c60fa5af93fc F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef F test/decimal.test ef731887b43ee32ef86e1c8fddb61a40789f988332c029c601dcf2c319277e9e -F test/default.test 830fad7180cdf0e6a06e93acc0403bf73762314a639363314db5674c631b6127 +F test/default.test c7124864cded213a3f118bc7e2e26f34b7c36dfa26cf6945cc8b7f5db1191277 F test/delete.test 2686e1c98d552ef37d79ad55b17b93fe96fad9737786917ce3839767f734c48f F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab @@ -1056,7 +1074,7 @@ F test/descidx3.test 953c831df7ea219c73826dfbf2f6ee02d95040725aa88ccb4fa43d1a199 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e F test/distinct.test 691c9e850b0d0b56b66e7e235453198cb4cf0760e324b7403d3c5abbeab0a014 F test/distinct2.test bb71cc7b5e58e895787f9910a788c254f679928d324732d063fe9bc202ecbe71 -F test/distinctagg.test ad2b4cf1483cd4cf24867dfafbfa0abb61184d92085fcc9784cea0592b278d64 +F test/distinctagg.test 40d7169ae5846caaf62c6e307d2ca3c333daf9b6f7cde888956a339a97afe85f F test/e_blobbytes.test 4c01dfe4f12087b92b20705a3fdfded45dc4ed16d5a211fed4e1d2786ba68a52 F test/e_blobclose.test 692fc02a058476c2222a63d97e3f3b2b809c1842e5525ded7f854d540ac2e075 F test/e_blobopen.test 29f6055ee453b8e679fe9570c4d3acfedbef821622c5dad16875148c5952ef50 @@ -1070,7 +1088,7 @@ F test/e_expr.test b950818a48269506d75a41c819003bd77a0893bc4a4f2fdee191bc74109c1 F test/e_fkey.test feeba6238aeff9d809fb6236b351da8df4ae9bda89e088e54526b31a0cbfeec5 F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e -F test/e_reindex.test 2b0e29344497d9a8a999453a003cb476b6b1d2eef2d6c120f83c2d3a429f3164 +F test/e_reindex.test 027bb13d2c7e9e865886eed6349f126a273f8037899b636bf5fb53c7fc815921 F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 F test/e_select.test 327a15f14068bbd6f647cedc67210f8680fcb2f05e481a0a855fccd2abfa1292 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f @@ -1087,7 +1105,8 @@ F test/enc.test 9a7be5479da985381d740b15f432800f65e2c87029ee57a318f42cb2eb43763a F test/enc2.test 848bf05f15b011719f478dddb7b5e9aea35e39e457493cba4c4eef75d849a5ec F test/enc3.test 55ef64416d72975c66167310a51dc9fc544ba3ae4858b8d5ab22f4cb6500b087 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test f3f7548d2f3df03e2f23ecaf35c7c2cc7b89848bd7c3606d94a010521b7ea4f6 +F test/eqp.test 3302598f611220a6c61e29d9b7bb62fd4a43504509b978dbabdb0b3e56ae3bc0 +F test/eqp2.test 6e8996148de88f0e7670491e92e712a2920a369b4406f21a27c3c9b6a46b68dd F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/eval.test 73969a2d43a511bf44080c44485a8c4d796b6a4f038d19e491867081155692c0 F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650bf0747 @@ -1097,6 +1116,7 @@ F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 F test/exprfault.test da33606d799718e2f8e34efd0e5858884a1ad87f608774c552a7f5517cc27181 +F test/exprfault2.test c49e84273898969af5dbc4fe6a3f4335f14639799f343590336c9ddf84425965 F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9 F test/external_reader.test c7d34694f1b25c32d866f56ac80c1e29edddc42b4ef90cad589263ffac2cde0c F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 @@ -1168,7 +1188,7 @@ F test/fts3expr5.test a5b9a053becbdb8e973fbf4d6d3abaabeb42d511d1848bd57931f3e0a1 F test/fts3f.test 8c438d5e1cab526b0021988fb1dc70cf3597b006a33ffd6c955ee89929077fe3 F test/fts3fault.test f4e1342acfe6d216a001490e8cd52afac1f9ffe4a11bbcdcb296129a45c5df45 F test/fts3fault2.test 7b2741e5095367238380b0fcdb837f36c24484c7a5f353659b387df63cf039ec -F test/fts3fault3.test 4a39a1618546776255dc1de306213b600aef87eca589ca8428a70c00fd11961b +F test/fts3fault3.test ccdd2292dd2d4e21e30fc5f4c8e064f79e516087eec5ff57ab6bc4f6a7714097 F test/fts3first.test dbdedd20914c8d539aa3206c9b34a23775644641 F test/fts3fuzz001.test c78afcd8ad712ea0b8d2ed50851a8aab3bc9dc52c64a536291e07112f519357c F test/fts3integrity.test 0c6fe7353d7b24d78862f4272ee9df4da2f32b3ff30fa3396945cda8119580a8 @@ -1186,7 +1206,7 @@ F test/fts3rank.test cd99bc83a3c923c8d52afd90d86979cf05fc41849f892faeac3988055ef F test/fts3rnd.test 1320d8826a845e38a96e769562bf83d7a92a15d0 F test/fts3shared.test 57e26a801f21027b7530da77db54286a6fe4997e F test/fts3snippet.test 0887196d67cffbe365edde535b95ecc642a532ce8551ccd9a73aab5999c3ffae -F test/fts3snippet2.test e79afeb1f673713f96d7fc5655726081975399d11e659d15553207be43301dc4 +F test/fts3snippet2.test 03f6738ab3897bea2ba6be424a0613872e167acbf37a66200d655d737b470f65 F test/fts3sort.test ed34c716a11cc2009a35210e84ad5f9c102362ca F test/fts3tok1.test a663f4cac22a9505400bc22aacb818d7055240409c28729669ea7d4cc2120d15 F test/fts3tok_err.test 52273cd193b9036282f7bacb43da78c6be87418d @@ -1217,9 +1237,9 @@ F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401b F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d F test/func.test b56905748ce0567c01d60005f3e6ad1af19453d224ba4730ee687d048fd09ef9 -F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f +F test/func2.test 69f6ae3751b4ec765bdc3b803c0a255aa0f693f28f44805bef03e6b4a3fd242f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a -F test/func4.test e8ef9b2bd6a192a213cbd5cf31a3b35e25cd6ff2fdaeea0b58d63be31b03d220 +F test/func4.test a3f9062487dbd826776f54f4e0e9517fe8c3cf689af92735308965774d51fac5 F test/func5.test 863e6d1bd0013d09c17236f8a13ea34008dd857d87d85a13a673960e4c25d82a F test/func6.test 9cc9b1f43b435af34fe1416eb1e318c8920448ea7a6962f2121972f5215cb9b0 F test/func7.test adbfc910385a6ffd15dc47be3c619ef070c542fcb7488964badb17b2d9a4d080 @@ -1232,7 +1252,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c e6a40f53ac5624aa5b7c4f31c385f09ba088d524cecc4512fd3057caeed8f530 +F test/fuzzcheck.c dc159967609d00b0cfe619e735cbbf8482570aca85711397034b0662b6c18fc7 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1244,7 +1264,7 @@ F test/fuzzdata8.db 4a53b6d077c6a5c23b609d8d3ac66996fa55ba3f8d02f9b6efdd0214a767 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc -F test/fuzzinvariants.c 4355043e98cd8555c62462fcbba91c17c6492b0b017bbbe68656d5f2208f6444 +F test/fuzzinvariants.c 0729b9d8ed77ad0f8c5c7601168a707d5803087d2da030ede9057c51c809cc6c F test/gcfault.test 4ea410ac161e685f17b19e1f606f58514a2850e806c65b846d05f60d436c5b0d F test/gencol1.test e169bdfa11c7ed5e9f322a98a7db3afe9e66235750b68c923efee8e1876b46ec F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 @@ -1253,15 +1273,16 @@ F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711 F test/hook.test 18cae9140fa7f9a6f346e892a3fe3e31b2ca0be1494cd01b918adb74281016a6 F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 -F test/icu.test 716a6b89fbabe5cc63e0cd4c260befb08fd7b9d761f04d43669233292f0753b1 +F test/icu.test 8da7d52cd9722c82f33b0466ed915460cb03c23a38f18a9a2d3ff97da9a4a8c0 F test/ieee754.test b0945d12be7d255f3dfa18e2511b17ca37e0edd2b803231c52d05b86c04ab26e F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 F test/in.test d1cad4ededd425568b2e39fb0c31fa9a3772311dd595801ff13ba3912b69bba6 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 -F test/in4.test fdd1d8134da8376985c2edba6035a2de1f6c731524d2ffa651419e8fe2cd1c5a -F test/in5.test b32ce7f4a93f44c5dee94af16886d922cc16ebe33c8e1765c73d4049d0f4b40f +F test/in4.test bb767ec1cfd1730256f0a83219f0acda36bc251b63f8b8bb7d8c7cff17875a4f +F test/in5.test 4fd79c70dfa0681313e8cdca07f5ff0400bdc0e20f808a5c59eaef1e4b48082a F test/in6.test f5f40d6816a8bb7c784424b58a10ac38efb76ab29127a2c17399e0cbeeda0e4b +F test/in7.test 742b18c284cd9a9cd1347d3a8affeee44b8de11e875e91a1d40498c18ba16441 F test/incrblob.test c9b96afc292aeff43d6687bcb09b0280aa599822 F test/incrblob2.test a494c9e848560039a23974b9119cfc2cf3ad3bd15cc2694ee6367ae537ef8f1f F test/incrblob3.test 67621a04b3084113bf38ce03797d70eca012d9d8f948193b8f655df577b0da6f @@ -1284,7 +1305,7 @@ F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a91 F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997 F test/indexA.test 11d84f6995e6e5b9d8315953fb1b6d29772ee7c7803ee9112715e7e4dd3e4974 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 -F test/indexexpr1.test 833f511213a5e26549186813f0566bd72f978177a7e6e98a2d2dd695de3c670d +F test/indexexpr1.test 24fa85a12da384dd1d56f7b24e593c51a8a54b4c5e2e8bbb9e5fdf1099427faf F test/indexexpr2.test 1c382e81ef996d8ae8b834a74f2a9013dddf59214c32201d7c8a656d739f999a F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 @@ -1337,25 +1358,29 @@ F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb9 F test/json/json-speed-check.sh 912ee03e700a65c827ee0c7b4752c21ec5ef69cf7679d2f482ca817042bead52 x F test/json/jsonb-q1.txt 1e180fe6491efab307e318b22879e3a736ac9a96539bbde7911a13ee5b33abc7 F test/json101.test 30db5b055b103ccabc53a29cfe6cda3345d07e171aeb25403dafa04f19e98b19 -F test/json102.test 557a46e16df1aa9bdbc4076a71a45814ea0e7503d6621d87d42a8c04cbc2b0ef +F test/json102.test 4b3a0f94535f033239b67c13dbee8b47d2b5ee467e0f2fdab5eadf370bbe5fd3 F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 043838b56e68f3252a0dcf5be1689016f6f3f05056f8dcfcdc9d074f4d932988 -F test/json106.test 1d46a9294e2ced35c7f87cebbcb9626d01abab04f1969d7ded7b6f6a1d9be0f2 +F test/json106.test 4aed3afd16549045d198a8d9cea00deea96e1f2ecf55864dce96cac558b8abef F test/json107.test 59054e815c8f6b67d634d44ace421cf975828fb5651c4460aa66015c8e19d562 -F test/json501.test ab168a12eb6eb14d479f8c1cdae3ac062fd5a4679f17f976e96f1af518408330 +F test/json108.test 0a5f1e2d4b35a1bc33052563d2a5ede03052e2099e58cb424547656c898e0f49 +F test/json501.test b95e2d14988b682a5cadf079dd6162f0f85fb74cd59c6b1f1624110104a974eb F test/json502.test 84634d3dbb521d2814e43624025b760c6198456c8197bbec6c977c0236648f5b F test/jsonb01.test f4cdfb4cf5a0c940091b17675ed9583f45add0c938f07d65b0de0e19d3a9a101 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff F test/kvtest.c 6e0228409ea7ca0497dad503fbd109badb5e59545d131014b6aaac68b56f484a F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 -F test/lemon-test01.y 58b764610fd934e189ffbb0bbfa33d171b9cb06019b55bdc04d090d6767e11d7 +F test/lemon-test01.y 70110eff607ab137ccc851edb2bc7e14a6d4f246b5d2d25f82a60b69d87a9ff2 F test/like.test 242ee7f5d08a031144c0daf63bbd7e7710c847ccf387a83347e0b61b3aa69526 F test/like2.test d3be15fefee3e02fc88942a9b98f26c5339bbdef7783c90023c092c4955fe3d3 F test/like3.test a76e5938fadbe6d32807284c796bafd869974a961057bc5fc5a28e06de98745c F test/limit.test 350f5d03c29e7dff9a2cde016f84f8d368d40bcd02fa2b2a52fa10c4bf3cbfaf F test/limit2.test 9409b033284642a859fafc95f29a5a6a557bd57c1f0d7c3f554bd64ed69df77e +F test/literal.test a65dca9fef86e51b8e45544268e37abbd4bb94ba35fd65f6fdcab2f288cd8f79 +F test/literal2.tcl 1499037beaf661aeecdbe48801220a181d805372a64c6128d5f26bb6a4a8f0ce +F test/literal2.test b149e16b5fc9ee6249069a8858ed41052f222014fe0ba7ad43c2fb989c2dada2 F test/loadext.test faa4f6eed07a5aac35d57fdd7bc07f8fc82464cfd327567c10cf0ba3c86cde04 F test/loadext2.test 0408380b57adca04004247179837a18e866a74f7 F test/lock.test be4fe08118fb988fed741f429b7dd5d65e1c90db @@ -1408,11 +1433,11 @@ F test/minmax.test fe638b55d77d2375531a8f549b338eafcd9adfbd2f72df37ed77d9b26ca0a F test/minmax2.test cf9311babb6f0518d04e42fd6a42c619531c4309a9dd790a2c4e9b3bc595e0de F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 F test/minmax4.test 272ca395257f05937dc96441c9dde4bc9fbf116a8d4fa02baeb0d13d50e36c87 -F test/misc1.test 8d138a4926ab90617c1aa29ce26e7785ae2b83a4d3a195d543b7374e05589dd1 +F test/misc1.test e3e36262aff1bd9b8b9bf1eeb3af04adb3fc1e23f0a92dbff708bba9e939ace1 F test/misc2.test a1a3573cc02662becd967766021d6f16c54684d56df5f227481c7ef0d9df0bd0 F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d F test/misc4.test 10cd6addb2fa9093df4751a1b92b50440175dd5468a6ec84d0386e78f087db0e -F test/misc5.test c4aeaa0fa28faa08f2485309c38db4719e6cd1364215d5687a5b96d340a3fa58 +F test/misc5.test 027cf0ac10314ea534173f335a33bb4059907ddabbac2c16786766d6f26c8923 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 F test/misc7.test d912f3d45c2989191b797504a220ca225d6be80b21acad22ba0d35f4a9ee4579 F test/misc8.test 08d2380bc435486b12161521f225043ac2be26f02471c2c1ea4cac0b1548edbd @@ -1422,7 +1447,7 @@ F test/mmap1.test 18de3fd7b70a777af6004ca2feecfcdd3d0be17fa04058e808baf530c94b1a F test/mmap2.test 9d6dd9ddb4ad2379f29cc78f38ce1e63ed418022 F test/mmap3.test b3c297e78e6a8520aafcc1a8f140535594c9086e F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 -F test/mmapcorrupt.test 0d89724591f22a376019f3df60d075b838dd2ba6dae6effb0be465c49cf86d4a +F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 @@ -1450,7 +1475,7 @@ F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394 F test/optfuzz-db01.c 9f2fa80b8f84ebbf1f2e8b13421a4e0477fe300f6686fbd76cac1d2db66e0fdc F test/optfuzz-db01.txt 21f6bdeadc701cf11528276e2a55c70bfcb846ba42df327f979bd9e7b6ce7041 F test/optfuzz.c 690430a0bf0ad047d5a168bf52b05b2ee97aedaad8c14337e9eb5050faa64994 -F test/orderby1.test 02cfd870127a7342170b829175c5c53e9e7405744451ac1aeb2f7e2b0c18ca76 +F test/orderby1.test 7d0e4ee692a3e808c1026b3c483594ad1e468b68b50dcefa0d678a8c05274ceb F test/orderby2.test bc11009f7cd99d96b1b11e57b199b00633eb5b04 F test/orderby3.test 8619d06a3debdcd80a27c0fdea5c40b468854b99 F test/orderby4.test 4d39bfbaaa3ae64d026ca2ff166353d2edca4ba4 @@ -1478,38 +1503,40 @@ F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/pendingrace.test 6aa33756b950c4529f79c4f3817a9a1e4025bd0d9961571a05c0279bd183d9c6 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test f7caf8dd5c7b1da74842a48df116f7f193399c656d4ffc805cd0d9658568c675 +F test/permutations.test 405542f1d659942994a6b38a9e024cf5cfd23eaa68c806aeb24a72d7c9186e80 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f -F test/pragma.test cddd4b534d7fb5cf113d1308dea4231f3548e8a7f3a65d7d1cf4810c87090b5a +F test/pragma.test 11cb9310c42f921918f7f563e3c0b6e70f9f9c3a6a1cf12af8fccb6c574f3882 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31 -F test/pragma4.test c7539e5e63cdfd60b3c8114360ec2cf838e2cd6e3ebfd648152319dd8d6f6be0 +F test/pragma4.test f93f317693e90ece4ed0f5505d9035cae03f9fc684b718278433f2e48703f693 F test/pragma5.test 7b33fc43e2e41abf17f35fb73f71b49671a380ea92a6c94b6ce530a25f8d9102 +F test/pragma6.test c5ec577ba087954b4dfa619a3cbe97b155b60a0af487527abe89b10fc17e6512 F test/pragmafault.test 275edaf3161771d37de60e5c2b412627ac94cef11739236bec12ed1258b240f8 F test/prefixes.test b524a1c44bffec225b9aec98bd728480352aa8532ac4c15771fb85e8beef65d9 F test/printf.test 685fec5a0c5af2490ab0632775a301554361d674211d690f5bee0a97b05333de F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224cce60 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc -F test/pushdown.test 1495a09837a1cedfc0adf07ba42dc6b83be05a2c15de331b67c39a0e22078238 +F test/pushdown.test 3330746a897ea271b1021bc1e7e57c6f8d49bcb8ca7d58a823d01aa64a303cc7 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a F test/quota-glob.test 32901e9eed6705d68ca3faee2a06b73b57cb3c26 F test/quota.test bfb269ce81ea52f593f9648316cd5013d766dd2a F test/quota2.test 7dc12e08b11cbc4c16c9ba2aa2e040ea8d8ab4b8 -F test/quote.test ffb40f0eb7a25c1d8cfe11ee2fe67f8e85fbf3fed348810834114be1fdada142 +F test/quote.test 7b01b2a261bc26d9821aea9f4941ce1e08191d62fc55ba8862440fb3a59197a4 F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459 F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 -F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0471e +F test/readonly.test c1985f0b4ab55041f2ba629dadc6578a3ff0f0e5b0ec7912e85c51f49c3e82fe +F test/recover.test 6463509a7404e0c35431dd9b4a1c3b4a29d7a6af8a08462b31670c8a5a616d3a F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test 3ead782eddf51f573cdd43bcbb10d1b485ac095a19a76d16c43fd159ea9b7466 +F test/returning1.test 38eee9d07ac1dd4fbd4ce7373497f3783db86b9a76f13ea6a9f9afaf934f888b F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f @@ -1540,7 +1567,7 @@ F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7 F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test b249328caf4d317e71058006872b8012598a5fa045b30bf24a81eeff650ab49e -F test/scanstatus2.test 2cb4d67ebbf578711dbf0377489a44759d0e21cdcce2fd55f0e044364961abec +F test/scanstatus2.test 688adc0c3ab1ffadead218cbce6446b10aa892004a8ea5e3640d59257fb836f2 F test/schema.test 5dd11c96ba64744de955315d2e4f8992e447533690153b93377dffb2a5ef5431 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5c9ce @@ -1560,7 +1587,7 @@ F test/select6.test 9b2fb4ffedf52e1b5703cfcae1212e7a4a063f014c0458d78d29aca3db76 F test/select7.test f659f231489349e8c5734e610803d7654207318f F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d F test/select9.test f7586b207ce2304ab80dc93d3146469a28fd4403621dd3a82d06644563d3c812 -F test/selectA.test 6aef8b2136a4ac7a3e2e4161d2b8ca7bc6ebe2779de084f9bb66ca9e2323a937 +F test/selectA.test 1da8ce3884c326e11d2855baffb76436b0d7e044404af8a2a70d1399a4ff7e29 F test/selectB.test 954e4e49cf1f896d61794e440669e03a27ceea25 F test/selectC.test 38c530b0cc5728b793c3c11f52b52c70290d39822224acd39011c89c1853bd31 F test/selectD.test 6d1909b49970bf92f45ce657505befcef5fc7cbc13544e18103a316d32189bfb @@ -1570,7 +1597,7 @@ F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae F test/selectH.test 0b54599f1917d99568c9b929df22ec6261ed7b6d2f02a46b5945ef81b7871aac F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb -F test/sessionfuzz.c 666b47e177c7b25f01ba645d41fb9131d2d54ae673f0d81c08f5af2b3e6ecbda +F test/sessionfuzz.c f693b8827034a3bed7616d89c65fb4fe8b7ff3c0f000c6ea6beda69b7f1aced3 F test/shared.test f022874d9d299fe913529dc10f52ad5a386e4e7ff709270b9b1111b3a0f3420a F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879 F test/shared3.test f8cd07c1a2b7cdb315c01671a0b2f8e3830b11ef31da6baa9a9cd8da88965403 @@ -1583,15 +1610,15 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test c7127a5e780ffc9e14c476773127fdf292c6db226529c44c1676f37b3793123f -F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb -F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a -F test/shell4.test 947029e5a9efae9054d424b309fc0311439c0c3a0866ebfa3b8a771120708220 -F test/shell5.test 263bfd6a49049295277e3f5bdc221390dc5e72f39954b23d43204ed81993304f -F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 -F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f -F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915 -F test/shell9.test f457a96c088344908e0518dbabffd02eda8ac2a8733f278679e5f47c103efbab +F test/shell1.test 17a5ca9c6f24f807b2f505b4b38fcbce143d96cd8664c06c34bbbe0672bf7c30 +F test/shell2.test 56da24128304c9ab67da2964cc80beff7b35761c446ec6e6e98bff2775b15026 +F test/shell3.test 5ad4b2813717956414f2c0c8a2027895cd98ccf7dd54dbacbde4d4f5591ce5a1 +F test/shell4.test 522fdc628c55eff697b061504fb0a9e4e6dfc5d9087a633ab0f3dd11bcc4f807 +F test/shell5.test 5b2ab1c0540217773f939927c24163a56257446da3f564d4724042620bfea762 +F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8 +F test/shell7.test 753c6ece5361df50025a50cadf378ea36db9cc05fb23d7a96cff7fa130626ef9 +F test/shell8.test aea51ecbcd4494c746b096aeff51d841d04d5f0dc4b62eb42427f16109b87acd +F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209 F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@ -1625,13 +1652,13 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest1.c f9505ad6f9c2c3c488a370a2d193e9603a030e51126ef3ecfeb056d21f0e7ad5 +F test/speedtest1.c 19c9b60908d25502d2831f97efee8b81006c356ab8c08327e25d24a4144f2131 F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb F test/sqldiff1.test 1b7ab4f312442c5cc6b3a5f299fa8ca051416d1dd173cb1126fd51bf64f2c3fb -F test/sqllimits1.test b28e5cc8d337aaf290614d96a47e8fbfb720bb7ad35620c9d5432996fd413ac4 +F test/sqllimits1.test dee96a51b83ef866d06ec3c687d4c951d97b02549facc5be88c9dfcb215b98bf F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a F test/startup.c 1beb5ca66fcc0fce95c3444db9d1674f90fc605499a574ae2434dcfc10d22805 F test/stat.test 123212a20ceb496893d5254a5f6c76442ce549fdc08d1702d8288a2bbaac8408 @@ -1641,7 +1668,7 @@ F test/stmtvtab1.test 6873dfb24f8e79cbb5b799b95c2e4349060eb7a3b811982749a84b3594 F test/strict1.test 4d2b492152b984fd7e8196d23eb88e2ccb0ef9e46ca2f96c2ce7147ceef9d168 F test/strict2.test b22c7a98b5000aef937f1990776497f0e979b1a23bc4f63e2d53b00e59b20070 F test/subjournal.test 8d4e2572c0ee9a15549f0d8e40863161295107e52f07a3e8012a2e1fdd093c49 -F test/subquery.test 312c5d26304b0e93a56ba2cb9d4480b8a1c8217e3b2b8f8be2bfb0b2458ac3a7 +F test/subquery.test 903abf41049f8404256f7be24b3151328304a5b25162e17ab0079460237382fc F test/subquery2.test 90cf944b9de8204569cf656028391e4af1ccc8c0cc02d4ef38ee3be8de1ffb12 F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303 F test/substr.test a673e3763e247e9b5e497a6cacbaf3da2bd8ec8921c0677145c109f2e633f36b @@ -1657,7 +1684,7 @@ F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d433309 F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039 F test/syscall.test a39d9a36f852ae6e4800f861bc2f2e83f68bbc2112d9399931ecfadeabd2d69d F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 -F test/tabfunc01.test 54f27eacd054aa528a8b6e3331192c484104f30aaee351ad035f2b39a00f87c4 +F test/tabfunc01.test f150d206294471d20f50029e6b46b76b87a7a010b16dc57eb44245c76dd02802 F test/table.test 7862a00b58b5541511a26757ea9c5c7c3f8298766e98aa099deec703d9c0a8e0 F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 @@ -1670,8 +1697,8 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc F test/tester.tcl fe617b88c7eb08bdf983d2aaa31c20fbf439eee7b8e0d61ca636fcd0c305bbbf -F test/testrunner.tcl 8e2a5c7550b78d3283eee6103104ae2bcf56aa1df892dbd1608f27b93ebf4de8 -F test/testrunner_data.tcl 7ffd951527bbc614e723fd8d123b6834321878530696adecfdf6035100bac64e +F test/testrunner.tcl 1386667c04207d0a540ce1a9bc5ee0b734f7a3ba856c14a03943fb4f32de55bb +F test/testrunner_data.tcl 3d36660cfd55ea5e20e661e8f94c0520feebcb437848f9b98b33c483cc479c0c F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1716,7 +1743,7 @@ F test/tkt-7a31705a7e6.test 9e9c057b6a9497c8f7ba7b16871029414ccf6550e7345d9085d6 F test/tkt-7bbfb7d442.test e87b59e620700b5a52ecd92f05d56686c1cad9e1aa17456eada55e0bb821b698 F test/tkt-80ba201079.test 105a721e6aad0ae3c5946d7615d1e4d03f6145b8 F test/tkt-80e031a00f.test 7c93af53f43527f50020983a4bcc39b077e77c7362d7af8e04a5fd155fe5e829 -F test/tkt-8454a207b9.test aff2e76143cfa443ddce6f7d85968a2e9b57a3deb0b881b730120740555f9e2f +F test/tkt-8454a207b9.test ead80b7a01438ca1436cee029694a96c821346cf1e24f06de12f8172e139ddbb F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed F test/tkt-91e2e8ba6f.test 08c4f94ae07696b05c9b822da0b4e5337a2f54c5 @@ -1733,7 +1760,7 @@ F test/tkt-b351d95f9.test d14a503c414c5c58fdde3e80f9a3cfef986498c0 F test/tkt-b72787b1.test a95e8cdad0b98af1853ac7f0afd4ab27b77bf5f3 F test/tkt-b75a9ca6b0.test dc6a853c242f7d0326564ae32e9e5eb462b5e8d2bc5b01ea3b18fd24f8e5894b F test/tkt-ba7cbfaedc.test b4c0deccc12aeb55cfdb57935b16b5d67c5a9877 -F test/tkt-bd484a090c.test 60460bf946f79a79712b71f202eda501ca99b898 +F test/tkt-bd484a090c.test e6af3e3a4242cd8f1c91c736364f09075d8e33e3b86f6492a1ee36278ea71b61 F test/tkt-bdc6bbbb38.test fc38bb09bdd440e3513a1f5f98fc60a075182d7d F test/tkt-c48d99d690.test ba61977d62ab612fc515b3c488a6fbd6464a2447 F test/tkt-c694113d5.test 82c461924ada5c14866c47e85535b0b0923ba16a2e907e370061a5ca77f65d77 @@ -1886,7 +1913,7 @@ F test/upfrom2.test 66f3ebf721b3cebd922faee5c386bf244f816d416b57c000753ff51af623 F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0 F test/upfrom4.test 78f742a6577c91a7a55c64edb8811004e7c6aa99b8d57b2320f70a918c357807 F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6 -F test/upsert1.test a512e2f884d3a36159fce2e45108c236f78ae38e35bda55f4050db580ceb25d3 +F test/upsert1.test beba4316fbd4b7b9d76784313f6129a548cfe7abea04d46db33e2efce1ab0ac2 F test/upsert2.test 720e94d09f7362a282bc69b3c6b83d51daeaaf0440eb4920a08b86518b8c7496 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 @@ -1904,9 +1931,11 @@ F test/vacuum4.test 7ea76b769fffeb41f925303b04cbcf5a5bbeabe55e4c60ae754ff24eeeb7 F test/vacuum5.test 263b144d537e92ad8e9ca8a73cc6e1583f41cfd0dda9432b87f7806174a2f48c F test/vacuum6.test b137b04bf3392d3f5c3b8fda0ce85a6775a70ca112f6559f74ff52dc9ce042fd F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb +F test/values.test 52102ad9e5068b449429e40a976486a52246041f7cd79d086a2b170e77dec925 +F test/valuesfault.test 2ef23ed965e3bd08e268cdc38a0d11653390ddbbe1e8e2e98d16f55edd30f6e8 F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 -F test/view.test d4c4281e1679245829db35597817282f60dc513fc39cc5439078f009bd118487 +F test/view.test 3c23d7a068e9e4a0c4e6907498042772adea725f0630c3d9638ffd4e5a08b92b F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 F test/vt02.c 5b44ac67b1a283fedecf2d6e2ceda61e7a157f01d44dcb4490dcb1e87d057060 @@ -1929,6 +1958,7 @@ F test/vtabH.test 2efb5a24b0bb50796b21eca23032cfb77abfa4b0c03938e38ce5897abac404 F test/vtabI.test 751b07636700dbdea328e4265b6077ccd6811a3f F test/vtabJ.test a6aef49d558af90fae10565b29501f82a95781cb4f797f2d13e2d19f9b6bc77b F test/vtabK.test 13293177528fada1235c0112db0d187d754af1355c5a39371abd365104e3afbf +F test/vtabL.test c7b7f537978005d063fa2f53a3cd5a46ecf651ecd19970cb9ed4203698398deb F test/vtab_alter.test 736e66fb5ec7b4fee58229aa3ada2f27ec58bc58c00edae4836890c3784c6783 F test/vtab_err.test dcc8b7b9cb67522b3fe7a272c73856829dae4ab7fdb30399aea1b6981bda2b65 F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad @@ -1992,8 +2022,9 @@ F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test c4bb7e2ca56d49bd8ab5c7bd085b8b83e353922b46904d68aefb3c7468643581 F test/whereJ.test fc05e374cc9f2dc204148d6c06822c380ad388895fe97a6d335b94a26a08aecf F test/whereK.test 0270ab7f04ba5436fb9156d31d642a1c82727f4c4bfe5ba90d435c78cf44684a -F test/whereL.test 9d7c8a9f4e5e82d6859e61cf8758c3856c7e0a7fd8be11c92cac8c3ec39228fd +F test/whereL.test 438a397fa883b77bb6361c08a8befa41b52e9cfbe15a2a43715d122f8cfa8649 F test/whereM.test 0dbc9998783458ddcf3cc078ca7c2951d8b2677d472ecf0028f449ed327c0250 +F test/whereN.test 63a3584b71acfb6963416de82f26c6b1644abc5ca6080c76546b9246734c8803 F test/wherefault.test 6cf2a9c5712952d463d3f45ebee7f6caf400984df51a195d884cfb7eb0e837a7 F test/wherelfault.test 9012e4ef5259058b771606616bd007af5d154e64cc25fa9fd4170f6411db44e3 F test/wherelimit.test afb46397c6d7e964e6e294ba3569864a0c570fe3807afc634236c2b752372f31 @@ -2071,8 +2102,8 @@ F tool/genfkey.test b6afd7b825d797a1e1274f519ab5695373552ecad5cd373530c63533638a F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/index_usage.c f62a0c701b2c7ff2f3e21d206f093c123f222dbf07136a10ffd1ca15a5c706c5 F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f -F tool/lemon.c 19e368bc8e97ff4071115119a7911ca3b0c56eba7926d8ada8b4a86fcc69a176 -F tool/lempar.c 57478ea48420da05faa873c6d1616321caa5464644588c97fbe8e0ea04450748 +F tool/lemon.c 2eaee61479f9b97056950741c8f671a13281c819b94246698264a322360319a9 +F tool/lempar.c e6b649778e5c027c8365ff01d7ef39297cd7285fa1f881cce31792689541e79f F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c c34e5944318415de513d29a6098df247a9618c96d83c38d4abd88641fe46e669 @@ -2084,14 +2115,14 @@ F tool/mkctimec.tcl 060e9785e9503bf51f8b1b11b542bdeef90fd0ceb0738154f6762acec0c6 F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef -F tool/mkopcodeh.tcl 769d9e6a8b462323150dc13a8539d6064664b72974f7894befe2491cc73e05cd +F tool/mkopcodeh.tcl 2b4e6967a670ef21bf53a164964c35c6163277d002a4c6f56fa231d68c88d023 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl 32e359ccb21011958a821955254bd7a5fa7915d01a8c16fed91ffc8b40cb4adf F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971fed0 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f -F tool/mksqlite3c.tcl 2c760ab786cb509b47f00c96fea82994866cb99f5e046df81c768288f57897b4 +F tool/mksqlite3c.tcl c6acfdf4e4ef93478ff3ce3cd593e17abb03f446036ce710c3156bcfa18665e0 F tool/mksqlite3h.tcl d391cff7cad0a372ee1406faee9ccc7dad9cb80a0c95cae0f73d10dd26e06762 F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b F tool/mktoolzip.tcl c7a9b685f5131d755e7d941cec50cee7f34178b9e34c9a89811eeb08617f8423 @@ -2113,7 +2144,7 @@ F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c F tool/showwal.c 11eca547980a066b081f512636151233350ac679f29ecf4ebfce7f4530230b3d F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe F tool/spaceanal.tcl 70c87c04cfd2e77b3e6f21c33ca768296aa8f67d4ab4874786ac8fbb28433477 -F tool/speed-check.sh 72dc85b2c0484af971ee3e7d10775f72b4e771e27e162c2099b3bf25517c25fb +F tool/speed-check.sh e8d20cc2eb9c85ec1ba562226de144435456dcdff4ee618de49603c6958f6116 F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff @@ -2160,10 +2191,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 08dd2b927bf8fbda76b4ea07ca1827efa9bb0b68348683abfd19328c117dc405 -R c4d37ec4d4b175414d039c166b6f8ab8 +P 3210e1ca4d0efedf9710c97abd050ba10d3af98cb1f029c26daa84daf42fbc7e +R 03f38bbe86d08cc2accd8330faccbd25 +T +sym-major-release * T +sym-release * -T +sym-version-3.45.3 * +T +sym-version-3.46.0 * U drh -Z 024d96198ee99cc296e773820295fe65 +Z 82f35d0822d1dfbb574e5dd2459b4a36 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7b96963b9..7dd5f56d1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355 +96c92aba00c8375bc32fafcdf12429c58bd8aabfcadab6683e35bbb9cdebf19e diff --git a/src/alter.c b/src/alter.c index ec45e1433..c1e0a295a 100644 --- a/src/alter.c +++ b/src/alter.c @@ -2262,7 +2262,12 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ if( i==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regOut); }else{ + char aff = pTab->aCol[i].affinity; + if( aff==SQLITE_AFF_REAL ){ + pTab->aCol[i].affinity = SQLITE_AFF_NUMERIC; + } sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); + pTab->aCol[i].affinity = aff; } nField++; } diff --git a/src/analyze.c b/src/analyze.c index 59e3d9837..8c48a8ff2 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -872,7 +872,7 @@ static void statGet( if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1; sqlite3_str_appendf(&sStat, " %llu", iVal); #ifdef SQLITE_ENABLE_STAT4 - assert( p->current.anEq[i] ); + assert( p->current.anEq[i] || p->nRow==0 ); #endif } sqlite3ResultStrAccum(context, &sStat); @@ -1057,7 +1057,7 @@ static void analyzeOneTable( for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int nCol; /* Number of columns in pIdx. "N" */ - int addrRewind; /* Address of "OP_Rewind iIdxCur" */ + int addrGotoEnd; /* Address of "OP_Rewind iIdxCur" */ int addrNextRow; /* Address of "next_row:" */ const char *zIdxName; /* Name of the index */ int nColTest; /* Number of columns to test for changes */ @@ -1081,9 +1081,14 @@ static void analyzeOneTable( /* ** Pseudo-code for loop that calls stat_push(): ** - ** Rewind csr - ** if eof(csr) goto end_of_scan; ** regChng = 0 + ** Rewind csr + ** if eof(csr){ + ** stat_init() with count = 0; + ** goto end_of_scan; + ** } + ** count() + ** stat_init() ** goto chng_addr_0; ** ** next_row: @@ -1122,41 +1127,36 @@ static void analyzeOneTable( sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); - /* Invoke the stat_init() function. The arguments are: - ** + /* Implementation of the following: + ** + ** regChng = 0 + ** Rewind csr + ** if eof(csr){ + ** stat_init() with count = 0; + ** goto end_of_scan; + ** } + ** count() + ** stat_init() + ** goto chng_addr_0; + */ + assert( regTemp2==regStat+4 ); + sqlite3VdbeAddOp2(v, OP_Integer, db->nAnalysisLimit, regTemp2); + + /* Arguments to stat_init(): ** (1) the number of columns in the index including the rowid ** (or for a WITHOUT ROWID table, the number of PK columns), ** (2) the number of columns in the key without the rowid/pk - ** (3) estimated number of rows in the index, - */ + ** (3) estimated number of rows in the index. */ sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat+1); assert( regRowid==regStat+2 ); sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regRowid); -#ifdef SQLITE_ENABLE_STAT4 - if( OptimizationEnabled(db, SQLITE_Stat4) ){ - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regTemp); - addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); - VdbeCoverage(v); - }else -#endif - { - addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); - VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_Count, iIdxCur, regTemp, 1); - } - assert( regTemp2==regStat+4 ); - sqlite3VdbeAddOp2(v, OP_Integer, db->nAnalysisLimit, regTemp2); + sqlite3VdbeAddOp3(v, OP_Count, iIdxCur, regTemp, + OptimizationDisabled(db, SQLITE_Stat4)); sqlite3VdbeAddFunctionCall(pParse, 0, regStat+1, regStat, 4, &statInitFuncdef, 0); + addrGotoEnd = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); + VdbeCoverage(v); - /* Implementation of the following: - ** - ** Rewind csr - ** if eof(csr) goto end_of_scan; - ** regChng = 0 - ** goto next_push_0; - ** - */ sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng); addrNextRow = sqlite3VdbeCurrentAddr(v); @@ -1263,6 +1263,12 @@ static void analyzeOneTable( } /* Add the entry to the stat1 table. */ + if( pIdx->pPartIdxWhere ){ + /* Partial indexes might get a zero-entry in sqlite_stat1. But + ** an empty table is omitted from sqlite_stat1. */ + sqlite3VdbeJumpHere(v, addrGotoEnd); + addrGotoEnd = 0; + } callStatGet(pParse, regStat, STAT_GET_STAT1, regStat1); assert( "BBB"[0]==SQLITE_AFF_TEXT ); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); @@ -1286,6 +1292,13 @@ static void analyzeOneTable( int addrIsNull; u8 seekOp = HasRowid(pTab) ? OP_NotExists : OP_NotFound; + /* No STAT4 data is generated if the number of rows is zero */ + if( addrGotoEnd==0 ){ + sqlite3VdbeAddOp2(v, OP_Cast, regStat1, SQLITE_AFF_INTEGER); + addrGotoEnd = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); + VdbeCoverage(v); + } + if( doOnce ){ int mxCol = nCol; Index *pX; @@ -1338,7 +1351,7 @@ static void analyzeOneTable( #endif /* SQLITE_ENABLE_STAT4 */ /* End of analysis */ - sqlite3VdbeJumpHere(v, addrRewind); + if( addrGotoEnd ) sqlite3VdbeJumpHere(v, addrGotoEnd); } diff --git a/src/btree.c b/src/btree.c index 84a70cc57..62b898979 100644 --- a/src/btree.c +++ b/src/btree.c @@ -151,8 +151,47 @@ int corruptPageError(int lineno, MemPage *p){ # define SQLITE_CORRUPT_PAGE(pMemPage) SQLITE_CORRUPT_PGNO(pMemPage->pgno) #endif +/* Default value for SHARED_LOCK_TRACE macro if shared-cache is disabled +** or if the lock tracking is disabled. This is always the value for +** release builds. +*/ +#define SHARED_LOCK_TRACE(X,MSG,TAB,TYPE) /*no-op*/ + #ifndef SQLITE_OMIT_SHARED_CACHE +#if 0 +/* ^---- Change to 1 and recompile to enable shared-lock tracing +** for debugging purposes. +** +** Print all shared-cache locks on a BtShared. Debugging use only. +*/ +static void sharedLockTrace( + BtShared *pBt, + const char *zMsg, + int iRoot, + int eLockType +){ + BtLock *pLock; + if( iRoot>0 ){ + printf("%s-%p %u%s:", zMsg, pBt, iRoot, eLockType==READ_LOCK?"R":"W"); + }else{ + printf("%s-%p:", zMsg, pBt); + } + for(pLock=pBt->pLock; pLock; pLock=pLock->pNext){ + printf(" %p/%u%s", pLock->pBtree, pLock->iTable, + pLock->eLock==READ_LOCK ? "R" : "W"); + while( pLock->pNext && pLock->pBtree==pLock->pNext->pBtree ){ + pLock = pLock->pNext; + printf(",%u%s", pLock->iTable, pLock->eLock==READ_LOCK ? "R" : "W"); + } + } + printf("\n"); + fflush(stdout); +} +#undef SHARED_LOCK_TRACE +#define SHARED_LOCK_TRACE(X,MSG,TAB,TYPE) sharedLockTrace(X,MSG,TAB,TYPE) +#endif /* Shared-lock tracing */ + #ifdef SQLITE_DEBUG /* **** This function is only used as part of an assert() statement. *** @@ -229,6 +268,8 @@ static int hasSharedCacheTableLock( iTab = iRoot; } + SHARED_LOCK_TRACE(pBtree->pBt,"hasLock",iRoot,eLockType); + /* Search for the required lock. Either a write-lock on root-page iTab, a ** write-lock on the schema table, or (if the client is reading) a ** read-lock on iTab will suffice. Return 1 if any of these are found. */ @@ -362,6 +403,8 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ BtLock *pLock = 0; BtLock *pIter; + SHARED_LOCK_TRACE(pBt,"setLock", iTable, eLock); + assert( sqlite3BtreeHoldsMutex(p) ); assert( eLock==READ_LOCK || eLock==WRITE_LOCK ); assert( p->db!=0 ); @@ -429,6 +472,8 @@ static void clearAllSharedCacheTableLocks(Btree *p){ assert( p->sharable || 0==*ppIter ); assert( p->inTrans>0 ); + SHARED_LOCK_TRACE(pBt, "clearAllLocks", 0, 0); + while( *ppIter ){ BtLock *pLock = *ppIter; assert( (pBt->btsFlags & BTS_EXCLUSIVE)==0 || pBt->pWriter==pLock->pBtree ); @@ -467,6 +512,9 @@ static void clearAllSharedCacheTableLocks(Btree *p){ */ static void downgradeAllSharedCacheTableLocks(Btree *p){ BtShared *pBt = p->pBt; + + SHARED_LOCK_TRACE(pBt, "downgradeLocks", 0, 0); + if( pBt->pWriter==p ){ BtLock *pLock; pBt->pWriter = 0; @@ -5080,9 +5128,12 @@ static int accessPayload( if( pCur->aOverflow==0 || nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow) ){ - Pgno *aNew = (Pgno*)sqlite3Realloc( - pCur->aOverflow, nOvfl*2*sizeof(Pgno) - ); + Pgno *aNew; + if( sqlite3FaultSim(413) ){ + aNew = 0; + }else{ + aNew = (Pgno*)sqlite3Realloc(pCur->aOverflow, nOvfl*2*sizeof(Pgno)); + } if( aNew==0 ){ return SQLITE_NOMEM_BKPT; }else{ @@ -5092,6 +5143,12 @@ static int accessPayload( memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno)); pCur->curFlags |= BTCF_ValidOvfl; }else{ + /* Sanity check the validity of the overflow page cache */ + assert( pCur->aOverflow[0]==nextPage + || pCur->aOverflow[0]==0 + || CORRUPT_DB ); + assert( pCur->aOverflow[0]!=0 || pCur->aOverflow[offset/ovflSize]==0 ); + /* If the overflow page-list cache has been allocated and the ** entry for the first required overflow page is valid, skip ** directly to it. @@ -5573,6 +5630,23 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ return rc; } +#ifdef SQLITE_DEBUG +/* The cursors is CURSOR_VALID and has BTCF_AtLast set. Verify that +** this flags are true for a consistent database. +** +** This routine is is called from within assert() statements only. +** It is an internal verification routine and does not appear in production +** builds. +*/ +static int cursorIsAtLastEntry(BtCursor *pCur){ + int ii; + for(ii=0; iiiPage; ii++){ + if( pCur->aiIdx[ii]!=pCur->apPage[ii]->nCell ) return 0; + } + return pCur->ix==pCur->pPage->nCell-1 && pCur->pPage->leaf!=0; +} +#endif + /* Move the cursor to the last entry in the table. Return SQLITE_OK ** on success. Set *pRes to 0 if the cursor actually points to something ** or set *pRes to 1 if the table is empty. @@ -5601,18 +5675,7 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ /* If the cursor already points to the last entry, this is a no-op. */ if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ -#ifdef SQLITE_DEBUG - /* This block serves to assert() that the cursor really does point - ** to the last entry in the b-tree. */ - int ii; - for(ii=0; iiiPage; ii++){ - assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); - } - assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB ); - testcase( pCur->ix!=pCur->pPage->nCell-1 ); - /* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */ - assert( pCur->pPage->leaf ); -#endif + assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB ); *pRes = 0; return SQLITE_OK; } @@ -5665,6 +5728,7 @@ int sqlite3BtreeTableMoveto( } if( pCur->info.nKeycurFlags & BTCF_AtLast)!=0 ){ + assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB ); *pRes = -1; return SQLITE_OK; } @@ -6131,10 +6195,10 @@ i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - /* Currently this interface is only called by the OP_IfSmaller - ** opcode, and it that case the cursor will always be valid and - ** will always point to a leaf node. */ - if( NEVER(pCur->eState!=CURSOR_VALID) ) return -1; + /* Currently this interface is only called by the OP_IfSizeBetween + ** opcode and the OP_Count opcode with P3=1. In either case, + ** the cursor will always be valid unless the btree is empty. */ + if( pCur->eState!=CURSOR_VALID ) return 0; if( NEVER(pCur->pPage->leaf==0) ) return -1; n = pCur->pPage->nCell; @@ -8265,7 +8329,7 @@ static int balance_nonroot( ** table-interior, index-leaf, or index-interior). */ if( pOld->aData[0]!=apOld[0]->aData[0] ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PAGE(pOld); goto balance_cleanup; } @@ -8289,7 +8353,7 @@ static int balance_nonroot( memset(&b.szCell[b.nCell], 0, sizeof(b.szCell[0])*(limit+pOld->nOverflow)); if( pOld->nOverflow>0 ){ if( NEVER(limitaiOvfl[0]) ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PAGE(pOld); goto balance_cleanup; } limit = pOld->aiOvfl[0]; @@ -8932,7 +8996,7 @@ static int anotherValidCursor(BtCursor *pCur){ && pOther->eState==CURSOR_VALID && pOther->pPage==pCur->pPage ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pCur->pPage); } } return SQLITE_OK; @@ -8992,7 +9056,7 @@ static int balance(BtCursor *pCur){ /* The page being written is not a root page, and there is currently ** more than one reference to it. This only happens if the page is one ** of its own ancestor pages. Corruption. */ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PAGE(pPage); }else{ MemPage * const pParent = pCur->apPage[iPage-1]; int const iIdx = pCur->aiIdx[iPage-1]; @@ -9156,7 +9220,7 @@ static SQLITE_NOINLINE int btreeOverwriteOverflowCell( rc = btreeGetPage(pBt, ovflPgno, &pPage, 0); if( rc ) return rc; if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PAGE(pPage); }else{ if( iOffset+ovflPageSize<(u32)nTotal ){ ovflPgno = get4byte(pPage->aData); @@ -9184,7 +9248,7 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd || pCur->info.pPayload < pPage->aData + pPage->cellOffset ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pPage); } if( pCur->info.nLocal==nTotal ){ /* The entire cell is local */ @@ -9265,7 +9329,7 @@ int sqlite3BtreeInsert( ** Which can only happen if the SQLITE_NoSchemaError flag was set when ** the schema was loaded. This cannot be asserted though, as a user might ** set the flag, load the schema, and then unset the flag. */ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pCur->pgnoRoot); } } @@ -9388,7 +9452,7 @@ int sqlite3BtreeInsert( if( pPage->nFree<0 ){ if( NEVER(pCur->eState>CURSOR_INVALID) ){ /* ^^^^^--- due to the moveToRoot() call above */ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PAGE(pPage); }else{ rc = btreeComputeFreeSpace(pPage); } @@ -9430,7 +9494,7 @@ int sqlite3BtreeInsert( CellInfo info; assert( idx>=0 ); if( idx>=pPage->nCell ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pPage); } rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ){ @@ -9457,10 +9521,10 @@ int sqlite3BtreeInsert( ** necessary to add the PTRMAP_OVERFLOW1 pointer-map entry. */ assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */ if( oldCell < pPage->aData+pPage->hdrOffset+10 ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pPage); } if( oldCell+szNew > pPage->aDataEnd ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pPage); } memcpy(oldCell, newCell, szNew); return SQLITE_OK; @@ -9562,7 +9626,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ nIn = pSrc->info.nLocal; aIn = pSrc->info.pPayload; if( aIn+nIn>pSrc->pPage->aDataEnd ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pSrc->pPage); } nRem = pSrc->info.nPayload; if( nIn==nRem && nInpPage->maxLocal ){ @@ -9587,7 +9651,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ if( nRem>nIn ){ if( aIn+nIn+4>pSrc->pPage->aDataEnd ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pSrc->pPage); } ovflIn = get4byte(&pSrc->info.pPayload[nIn]); } @@ -9683,7 +9747,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID ); if( rc || pCur->eState!=CURSOR_VALID ) return rc; }else{ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pCur->pgnoRoot); } } assert( pCur->eState==CURSOR_VALID ); @@ -9692,14 +9756,14 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ iCellIdx = pCur->ix; pPage = pCur->pPage; if( pPage->nCell<=iCellIdx ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pPage); } pCell = findCell(pPage, iCellIdx); if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pPage); } if( pCell<&pPage->aCellIdx[pPage->nCell] ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PAGE(pPage); } /* If the BTREE_SAVEPOSITION bit is on, then the cursor position must @@ -9790,7 +9854,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ n = pCur->pPage->pgno; } pCell = findCell(pLeaf, pLeaf->nCell-1); - if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT; + if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_PAGE(pLeaf); nCell = pLeaf->xCellSize(pLeaf, pCell); assert( MX_CELL_SIZE(pBt) >= nCell ); pTmp = pBt->pTmpSpace; @@ -9906,7 +9970,7 @@ static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){ */ sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &pgnoRoot); if( pgnoRoot>btreePagecount(pBt) ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pgnoRoot); } pgnoRoot++; @@ -9954,7 +10018,7 @@ static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){ } rc = ptrmapGet(pBt, pgnoRoot, &eType, &iPtrPage); if( eType==PTRMAP_ROOTPAGE || eType==PTRMAP_FREEPAGE ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(pgnoRoot); } if( rc!=SQLITE_OK ){ releasePage(pRoot); @@ -10044,14 +10108,14 @@ static int clearDatabasePage( assert( sqlite3_mutex_held(pBt->mutex) ); if( pgno>btreePagecount(pBt) ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pgno); } rc = getAndInitPage(pBt, pgno, &pPage, 0); if( rc ) return rc; if( (pBt->openFlags & BTREE_SINGLE)==0 && sqlite3PagerPageRefcount(pPage->pDbPage) != (1 + (pgno==1)) ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PAGE(pPage); goto cleardatabasepage_out; } hdr = pPage->hdrOffset; @@ -10155,7 +10219,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ assert( p->inTrans==TRANS_WRITE ); assert( iTable>=2 ); if( iTable>btreePagecount(pBt) ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(iTable); } rc = sqlite3BtreeClearTable(p, iTable, 0); @@ -10749,6 +10813,9 @@ static int checkTreePage( ** number of cells on the page. */ nCell = get2byte(&data[hdr+3]); assert( pPage->nCell==nCell ); + if( pPage->leaf || pPage->intKey==0 ){ + pCheck->nRow += nCell; + } /* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page ** immediately follows the b-tree page header. */ @@ -10860,6 +10927,7 @@ static int checkTreePage( btreeHeapInsert(heap, (pc<<16)|(pc+size-1)); } } + assert( heap!=0 ); /* Add the freeblocks to the min-heap ** ** EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header @@ -10959,6 +11027,7 @@ int sqlite3BtreeIntegrityCheck( sqlite3 *db, /* Database connection that is running the check */ Btree *p, /* The btree to be checked */ Pgno *aRoot, /* An array of root pages numbers for individual trees */ + Mem *aCnt, /* Memory cells to write counts for each tree to */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ int *pnErr, /* OUT: Write number of errors seen to this variable */ @@ -10972,7 +11041,9 @@ int sqlite3BtreeIntegrityCheck( int bPartial = 0; /* True if not checking all btrees */ int bCkFreelist = 1; /* True to scan the freelist */ VVA_ONLY( int nRef ); + assert( nRoot>0 ); + assert( aCnt!=0 ); /* aRoot[0]==0 means this is a partial check */ if( aRoot[0]==0 ){ @@ -11045,15 +11116,18 @@ int sqlite3BtreeIntegrityCheck( testcase( pBt->db->flags & SQLITE_CellSizeCk ); pBt->db->flags &= ~(u64)SQLITE_CellSizeCk; for(i=0; (int)iautoVacuum && aRoot[i]>1 && !bPartial ){ - checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); - } + if( pBt->autoVacuum && aRoot[i]>1 && !bPartial ){ + checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); + } #endif - sCheck.v0 = aRoot[i]; - checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64); + sCheck.v0 = aRoot[i]; + checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64); + } + sqlite3MemSetArrayInt64(aCnt, i, sCheck.nRow); } pBt->db->flags = savedDbFlags; diff --git a/src/btree.h b/src/btree.h index b45ace7e1..9731b8f2d 100644 --- a/src/btree.h +++ b/src/btree.h @@ -331,6 +331,7 @@ int sqlite3BtreeIntegrityCheck( sqlite3 *db, /* Database connection that is running the check */ Btree *p, /* The btree to be checked */ Pgno *aRoot, /* An array of root pages numbers for individual trees */ + sqlite3_value *aCnt, /* OUT: entry counts for each btree in aRoot[] */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ int *pnErr, /* OUT: Write number of errors seen to this variable */ diff --git a/src/btreeInt.h b/src/btreeInt.h index 67a7db25c..121329725 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -707,6 +707,7 @@ struct IntegrityCk { StrAccum errMsg; /* Accumulate the error message text here */ u32 *heap; /* Min-heap used for analyzing cell coverage */ sqlite3 *db; /* Database connection running the check */ + i64 nRow; /* Number of rows visited in current tree */ }; /* diff --git a/src/build.c b/src/build.c index 4e46ea0b5..10aa34240 100644 --- a/src/build.c +++ b/src/build.c @@ -189,7 +189,7 @@ void sqlite3FinishCoding(Parse *pParse){ } sqlite3VdbeAddOp0(v, OP_Halt); -#if SQLITE_USER_AUTHENTICATION +#if SQLITE_USER_AUTHENTICATION && !defined(SQLITE_OMIT_SHARED_CACHE) if( pParse->nTableLock>0 && db->init.busy==0 ){ sqlite3UserAuthInit(db); if( db->auth.authLevelrc = SQLITE_ERROR; pParse->nErr++; return; } + iCsr = pParse->nTab++; regYield = ++pParse->nMem; regRec = ++pParse->nMem; regRowid = ++pParse->nMem; - assert(pParse->nTab==1); sqlite3MayAbort(pParse); - sqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iCsr, pParse->regRoot, iDb); sqlite3VdbeChangeP5(v, OPFLAG_P2ISREG); - pParse->nTab = 2; addrTop = sqlite3VdbeCurrentAddr(v) + 1; sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); if( pParse->nErr ) return; @@ -2862,11 +2862,11 @@ void sqlite3EndTable( VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec); sqlite3TableAffinity(v, p, 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, 1, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, 1, regRec, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, iCsr, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iCsr, regRec, regRowid); sqlite3VdbeGoto(v, addrInsLoop); sqlite3VdbeJumpHere(v, addrInsLoop); - sqlite3VdbeAddOp1(v, OP_Close, 1); + sqlite3VdbeAddOp1(v, OP_Close, iCsr); } /* Compute the complete text of the CREATE statement */ @@ -2923,13 +2923,10 @@ void sqlite3EndTable( /* Test for cycles in generated columns and illegal expressions ** in CHECK constraints and in DEFAULT clauses. */ if( p->tabFlags & TF_HasGenerated ){ - sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0, + sqlite3VdbeAddOp4(v, OP_SqlExec, 0x0001, 0, 0, sqlite3MPrintf(db, "SELECT*FROM\"%w\".\"%w\"", db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); } - sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0, - sqlite3MPrintf(db, "PRAGMA \"%w\".integrity_check(%Q)", - db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); } /* Add the table to the in-memory representation of the database. diff --git a/src/date.c b/src/date.c index e493542db..d74cecb1d 100644 --- a/src/date.c +++ b/src/date.c @@ -71,13 +71,14 @@ struct DateTime { int tz; /* Timezone offset in minutes */ double s; /* Seconds */ char validJD; /* True (1) if iJD is valid */ - char rawS; /* Raw numeric value stored in s */ char validYMD; /* True (1) if Y,M,D are valid */ char validHMS; /* True (1) if h,m,s are valid */ - char validTZ; /* True (1) if tz is valid */ - char tzSet; /* Timezone was set explicitly */ - char isError; /* An overflow has occurred */ - char useSubsec; /* Display subsecond precision */ + char nFloor; /* Days to implement "floor" */ + unsigned rawS : 1; /* Raw numeric value stored in s */ + unsigned isError : 1; /* An overflow has occurred */ + unsigned useSubsec : 1; /* Display subsecond precision */ + unsigned isUtc : 1; /* Time is known to be UTC */ + unsigned isLocal : 1; /* Time is known to be localtime */ }; @@ -175,6 +176,8 @@ static int parseTimezone(const char *zDate, DateTime *p){ sgn = +1; }else if( c=='Z' || c=='z' ){ zDate++; + p->isLocal = 0; + p->isUtc = 1; goto zulu_time; }else{ return c!=0; @@ -187,7 +190,6 @@ static int parseTimezone(const char *zDate, DateTime *p){ p->tz = sgn*(nMn + nHr*60); zulu_time: while( sqlite3Isspace(*zDate) ){ zDate++; } - p->tzSet = 1; return *zDate!=0; } @@ -231,7 +233,6 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ p->m = m; p->s = s + ms; if( parseTimezone(zDate, p) ) return 1; - p->validTZ = (p->tz!=0)?1:0; return 0; } @@ -278,15 +279,40 @@ static void computeJD(DateTime *p){ p->validJD = 1; if( p->validHMS ){ p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000 + 0.5); - if( p->validTZ ){ + if( p->tz ){ p->iJD -= p->tz*60000; p->validYMD = 0; p->validHMS = 0; - p->validTZ = 0; + p->tz = 0; + p->isUtc = 1; + p->isLocal = 0; } } } +/* +** Given the YYYY-MM-DD information current in p, determine if there +** is day-of-month overflow and set nFloor to the number of days that +** would need to be subtracted from the date in order to bring the +** date back to the end of the month. +*/ +static void computeFloor(DateTime *p){ + assert( p->validYMD || p->isError ); + assert( p->D>=0 && p->D<=31 ); + assert( p->M>=0 && p->M<=12 ); + if( p->D<=28 ){ + p->nFloor = 0; + }else if( (1<M) & 0x15aa ){ + p->nFloor = 0; + }else if( p->M!=2 ){ + p->nFloor = (p->D==31); + }else if( p->Y%4!=0 || (p->Y%100==0 && p->Y%400!=0) ){ + p->nFloor = p->D - 28; + }else{ + p->nFloor = p->D - 29; + } +} + /* ** Parse dates of the form ** @@ -325,12 +351,16 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){ p->Y = neg ? -Y : Y; p->M = M; p->D = D; - if( p->validTZ ){ + computeFloor(p); + if( p->tz ){ computeJD(p); } return 0; } + +static void clearYMD_HMS_TZ(DateTime *p); /* Forward declaration */ + /* ** Set the time to the current time reported by the VFS. ** @@ -340,6 +370,9 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ p->iJD = sqlite3StmtCurrentTime(context); if( p->iJD>0 ){ p->validJD = 1; + p->isUtc = 1; + p->isLocal = 0; + clearYMD_HMS_TZ(p); return 0; }else{ return 1; @@ -478,7 +511,7 @@ static void computeYMD_HMS(DateTime *p){ static void clearYMD_HMS_TZ(DateTime *p){ p->validYMD = 0; p->validHMS = 0; - p->validTZ = 0; + p->tz = 0; } #ifndef SQLITE_OMIT_LOCALTIME @@ -610,7 +643,7 @@ static int toLocaltime( p->validHMS = 1; p->validJD = 0; p->rawS = 0; - p->validTZ = 0; + p->tz = 0; p->isError = 0; return SQLITE_OK; } @@ -630,12 +663,12 @@ static const struct { float rLimit; /* Maximum NNN value for this transform */ float rXform; /* Constant used for this transform */ } aXformType[] = { - { 6, "second", 4.6427e+14, 1.0 }, - { 6, "minute", 7.7379e+12, 60.0 }, - { 4, "hour", 1.2897e+11, 3600.0 }, - { 3, "day", 5373485.0, 86400.0 }, - { 5, "month", 176546.0, 2592000.0 }, - { 4, "year", 14713.0, 31536000.0 }, + /* 0 */ { 6, "second", 4.6427e+14, 1.0 }, + /* 1 */ { 6, "minute", 7.7379e+12, 60.0 }, + /* 2 */ { 4, "hour", 1.2897e+11, 3600.0 }, + /* 3 */ { 3, "day", 5373485.0, 86400.0 }, + /* 4 */ { 5, "month", 176546.0, 30.0*86400.0 }, + /* 5 */ { 4, "year", 14713.0, 365.0*86400.0 }, }; /* @@ -667,14 +700,20 @@ static void autoAdjustDate(DateTime *p){ ** NNN.NNNN seconds ** NNN months ** NNN years +** +/-YYYY-MM-DD HH:MM:SS.SSS +** ceiling +** floor ** start of month ** start of year ** start of week ** start of day ** weekday N ** unixepoch +** auto ** localtime ** utc +** subsec +** subsecond ** ** Return 0 on success and 1 if there is any kind of error. If the error ** is in a system call (i.e. localtime()), then an error message is written @@ -705,6 +744,37 @@ static int parseModifier( } break; } + case 'c': { + /* + ** ceiling + ** + ** Resolve day-of-month overflow by rolling forward into the next + ** month. As this is the default action, this modifier is really + ** a no-op that is only included for symmetry. See "floor". + */ + if( sqlite3_stricmp(z, "ceiling")==0 ){ + computeJD(p); + clearYMD_HMS_TZ(p); + rc = 0; + p->nFloor = 0; + } + break; + } + case 'f': { + /* + ** floor + ** + ** Resolve day-of-month overflow by rolling back to the end of the + ** previous month. + */ + if( sqlite3_stricmp(z, "floor")==0 ){ + computeJD(p); + p->iJD -= p->nFloor*86400000; + clearYMD_HMS_TZ(p); + rc = 0; + } + break; + } case 'j': { /* ** julianday @@ -731,7 +801,9 @@ static int parseModifier( ** show local time. */ if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){ - rc = toLocaltime(p, pCtx); + rc = p->isLocal ? SQLITE_OK : toLocaltime(p, pCtx); + p->isUtc = 0; + p->isLocal = 1; } break; } @@ -756,7 +828,7 @@ static int parseModifier( } #ifndef SQLITE_OMIT_LOCALTIME else if( sqlite3_stricmp(z, "utc")==0 && sqlite3NotPureFunc(pCtx) ){ - if( p->tzSet==0 ){ + if( p->isUtc==0 ){ i64 iOrigJD; /* Original localtime */ i64 iGuess; /* Guess at the corresponding utc time */ int cnt = 0; /* Safety to prevent infinite loop */ @@ -779,7 +851,8 @@ static int parseModifier( memset(p, 0, sizeof(*p)); p->iJD = iGuess; p->validJD = 1; - p->tzSet = 1; + p->isUtc = 1; + p->isLocal = 0; } rc = SQLITE_OK; } @@ -799,7 +872,7 @@ static int parseModifier( && r>=0.0 && r<7.0 && (n=(int)r)==r ){ sqlite3_int64 Z; computeYMD_HMS(p); - p->validTZ = 0; + p->tz = 0; p->validJD = 0; computeJD(p); Z = ((p->iJD + 129600000)/86400000) % 7; @@ -839,7 +912,7 @@ static int parseModifier( p->h = p->m = 0; p->s = 0.0; p->rawS = 0; - p->validTZ = 0; + p->tz = 0; p->validJD = 0; if( sqlite3_stricmp(z,"month")==0 ){ p->D = 1; @@ -910,6 +983,7 @@ static int parseModifier( x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; p->Y += x; p->M -= x*12; + computeFloor(p); computeJD(p); p->validHMS = 0; p->validYMD = 0; @@ -956,11 +1030,12 @@ static int parseModifier( z += n; while( sqlite3Isspace(*z) ) z++; n = sqlite3Strlen30(z); - if( n>10 || n<3 ) break; + if( n<3 || n>10 ) break; if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--; computeJD(p); assert( rc==1 ); rRounder = r<0 ? -0.5 : +0.5; + p->nFloor = 0; for(i=0; iM += (int)r; x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; p->Y += x; p->M -= x*12; + computeFloor(p); p->validJD = 0; r -= (int)r; break; } case 5: { /* Special processing to add years */ int y = (int)r; - assert( strcmp(aXformType[i].zName,"year")==0 ); + assert( strcmp(aXformType[5].zName,"year")==0 ); computeYMD_HMS(p); + assert( p->M>=0 && p->M<=12 ); p->Y += y; + computeFloor(p); p->validJD = 0; r -= (int)r; break; @@ -1236,22 +1314,83 @@ static void dateFunc( } } +/* +** Compute the number of days after the most recent January 1. +** +** In other words, compute the zero-based day number for the +** current year: +** +** Jan01 = 0, Jan02 = 1, ..., Jan31 = 30, Feb01 = 31, ... +** Dec31 = 364 or 365. +*/ +static int daysAfterJan01(DateTime *pDate){ + DateTime jan01 = *pDate; + assert( jan01.validYMD ); + assert( jan01.validHMS ); + assert( pDate->validJD ); + jan01.validJD = 0; + jan01.M = 1; + jan01.D = 1; + computeJD(&jan01); + return (int)((pDate->iJD-jan01.iJD+43200000)/86400000); +} + +/* +** Return the number of days after the most recent Monday. +** +** In other words, return the day of the week according +** to this code: +** +** 0=Monday, 1=Tuesday, 2=Wednesday, ..., 6=Sunday. +*/ +static int daysAfterMonday(DateTime *pDate){ + assert( pDate->validJD ); + return (int)((pDate->iJD+43200000)/86400000) % 7; +} + +/* +** Return the number of days after the most recent Sunday. +** +** In other words, return the day of the week according +** to this code: +** +** 0=Sunday, 1=Monday, 2=Tues, ..., 6=Saturday +*/ +static int daysAfterSunday(DateTime *pDate){ + assert( pDate->validJD ); + return (int)((pDate->iJD+129600000)/86400000) % 7; +} + /* ** strftime( FORMAT, TIMESTRING, MOD, MOD, ...) ** ** Return a string described by FORMAT. Conversions as follows: ** -** %d day of month +** %d day of month 01-31 +** %e day of month 1-31 ** %f ** fractional seconds SS.SSS +** %F ISO date. YYYY-MM-DD +** %G ISO year corresponding to %V 0000-9999. +** %g 2-digit ISO year corresponding to %V 00-99 ** %H hour 00-24 -** %j day of year 000-366 +** %k hour 0-24 (leading zero converted to space) +** %I hour 01-12 +** %j day of year 001-366 ** %J ** julian day number +** %l hour 1-12 (leading zero converted to space) ** %m month 01-12 ** %M minute 00-59 +** %p "am" or "pm" +** %P "AM" or "PM" +** %R time as HH:MM ** %s seconds since 1970-01-01 ** %S seconds 00-59 -** %w day of week 0-6 Sunday==0 -** %W week of year 00-53 +** %T time as HH:MM:SS +** %u day of week 1-7 Monday==1, Sunday==7 +** %w day of week 0-6 Sunday==0, Monday==1 +** %U week of year 00-53 (First Sunday is start of week 01) +** %V week of year 01-53 (First week containing Thursday is week 01) +** %W week of year 00-53 (First Monday is start of week 01) ** %Y year 0000-9999 ** %% % */ @@ -1288,7 +1427,7 @@ static void strftimeFunc( sqlite3_str_appendf(&sRes, cf=='d' ? "%02d" : "%2d", x.D); break; } - case 'f': { + case 'f': { /* Fractional seconds. (Non-standard) */ double s = x.s; if( s>59.999 ) s = 59.999; sqlite3_str_appendf(&sRes, "%06.3f", s); @@ -1298,6 +1437,21 @@ static void strftimeFunc( sqlite3_str_appendf(&sRes, "%04d-%02d-%02d", x.Y, x.M, x.D); break; } + case 'G': /* Fall thru */ + case 'g': { + DateTime y = x; + assert( y.validJD ); + /* Move y so that it is the Thursday in the same week as x */ + y.iJD += (3 - daysAfterMonday(&x))*86400000; + y.validYMD = 0; + computeYMD(&y); + if( cf=='g' ){ + sqlite3_str_appendf(&sRes, "%02d", y.Y%100); + }else{ + sqlite3_str_appendf(&sRes, "%04d", y.Y); + } + break; + } case 'H': case 'k': { sqlite3_str_appendf(&sRes, cf=='H' ? "%02d" : "%2d", x.h); @@ -1311,25 +1465,11 @@ static void strftimeFunc( sqlite3_str_appendf(&sRes, cf=='I' ? "%02d" : "%2d", h); break; } - case 'W': /* Fall thru */ - case 'j': { - int nDay; /* Number of days since 1st day of year */ - DateTime y = x; - y.validJD = 0; - y.M = 1; - y.D = 1; - computeJD(&y); - nDay = (int)((x.iJD-y.iJD+43200000)/86400000); - if( cf=='W' ){ - int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ - wd = (int)(((x.iJD+43200000)/86400000)%7); - sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7); - }else{ - sqlite3_str_appendf(&sRes,"%03d",nDay+1); - } + case 'j': { /* Day of year. Jan01==1, Jan02==2, and so forth */ + sqlite3_str_appendf(&sRes,"%03d",daysAfterJan01(&x)+1); break; } - case 'J': { + case 'J': { /* Julian day number. (Non-standard) */ sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0); break; } @@ -1372,13 +1512,33 @@ static void strftimeFunc( sqlite3_str_appendf(&sRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s); break; } - case 'u': /* Fall thru */ - case 'w': { - char c = (char)(((x.iJD+129600000)/86400000) % 7) + '0'; + case 'u': /* Day of week. 1 to 7. Monday==1, Sunday==7 */ + case 'w': { /* Day of week. 0 to 6. Sunday==0, Monday==1 */ + char c = (char)daysAfterSunday(&x) + '0'; if( c=='0' && cf=='u' ) c = '7'; sqlite3_str_appendchar(&sRes, 1, c); break; } + case 'U': { /* Week num. 00-53. First Sun of the year is week 01 */ + sqlite3_str_appendf(&sRes,"%02d", + (daysAfterJan01(&x)-daysAfterSunday(&x)+7)/7); + break; + } + case 'V': { /* Week num. 01-53. First week with a Thur is week 01 */ + DateTime y = x; + /* Adjust y so that is the Thursday in the same week as x */ + assert( y.validJD ); + y.iJD += (3 - daysAfterMonday(&x))*86400000; + y.validYMD = 0; + computeYMD(&y); + sqlite3_str_appendf(&sRes,"%02d", daysAfterJan01(&y)/7+1); + break; + } + case 'W': { /* Week num. 00-53. First Mon of the year is week 01 */ + sqlite3_str_appendf(&sRes,"%02d", + (daysAfterJan01(&x)-daysAfterMonday(&x)+7)/7); + break; + } case 'Y': { sqlite3_str_appendf(&sRes,"%04d",x.Y); break; @@ -1525,9 +1685,7 @@ static void timediffFunc( d1.iJD = d2.iJD - d1.iJD; d1.iJD += (u64)1486995408 * (u64)100000; } - d1.validYMD = 0; - d1.validHMS = 0; - d1.validTZ = 0; + clearYMD_HMS_TZ(&d1); computeYMD_HMS(&d1); sqlite3StrAccumInit(&sRes, 0, 0, 0, 100); sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f", @@ -1596,6 +1754,36 @@ static void currentTimeFunc( } #endif +#if !defined(SQLITE_OMIT_DATETIME_FUNCS) && defined(SQLITE_DEBUG) +/* +** datedebug(...) +** +** This routine returns JSON that describes the internal DateTime object. +** Used for debugging and testing only. Subject to change. +*/ +static void datedebugFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + char *zJson; + zJson = sqlite3_mprintf( + "{iJD:%lld,Y:%d,M:%d,D:%d,h:%d,m:%d,tz:%d," + "s:%.3f,validJD:%d,validYMS:%d,validHMS:%d," + "nFloor:%d,rawS:%d,isError:%d,useSubsec:%d," + "isUtc:%d,isLocal:%d}", + x.iJD, x.Y, x.M, x.D, x.h, x.m, x.tz, + x.s, x.validJD, x.validYMD, x.validHMS, + x.nFloor, x.rawS, x.isError, x.useSubsec, + x.isUtc, x.isLocal); + sqlite3_result_text(context, zJson, -1, sqlite3_free); + } +} +#endif /* !SQLITE_OMIT_DATETIME_FUNCS && SQLITE_DEBUG */ + + /* ** This function registered all of the above C functions as SQL ** functions. This should be the only routine in this file with @@ -1611,6 +1799,9 @@ void sqlite3RegisterDateTimeFunctions(void){ PURE_DATE(datetime, -1, 0, 0, datetimeFunc ), PURE_DATE(strftime, -1, 0, 0, strftimeFunc ), PURE_DATE(timediff, 2, 0, 0, timediffFunc ), +#ifdef SQLITE_DEBUG + PURE_DATE(datedebug, -1, 0, 0, datedebugFunc ), +#endif DFUNCTION(current_time, 0, 0, 0, ctimeFunc ), DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc), DFUNCTION(current_date, 0, 0, 0, cdateFunc ), diff --git a/src/expr.c b/src/expr.c index e508c7a8a..27f89d659 100644 --- a/src/expr.c +++ b/src/expr.c @@ -915,11 +915,12 @@ void sqlite3ExprSetErrorOffset(Expr *pExpr, int iOfst){ ** appear to be quoted. If the quotes were of the form "..." (double-quotes) ** then the EP_DblQuoted flag is set on the expression node. ** -** Special case: If op==TK_INTEGER and pToken points to a string that -** can be translated into a 32-bit integer, then the token is not -** stored in u.zToken. Instead, the integer values is written -** into u.iValue and the EP_IntValue flag is set. No extra storage +** Special case (tag-20240227-a): If op==TK_INTEGER and pToken points to +** a string that can be translated into a 32-bit integer, then the token is +** not stored in u.zToken. Instead, the integer values is written +** into u.iValue and the EP_IntValue flag is set. No extra storage ** is allocated to hold the integer text and the dequote flag is ignored. +** See also tag-20240227-b. */ Expr *sqlite3ExprAlloc( sqlite3 *db, /* Handle for sqlite3DbMallocRawNN() */ @@ -935,7 +936,7 @@ Expr *sqlite3ExprAlloc( if( pToken ){ if( op!=TK_INTEGER || pToken->z==0 || sqlite3GetInt32(pToken->z, &iValue)==0 ){ - nExtra = pToken->n+1; + nExtra = pToken->n+1; /* tag-20240227-a */ assert( iValue>=0 ); } } @@ -1367,6 +1368,7 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); assert( db!=0 ); +exprDeleteRestart: assert( !ExprUseUValue(p) || p->u.iValue>=0 ); assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); @@ -1382,7 +1384,6 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ assert( (ExprUseXList(p) && p->x.pList==0) || p->pRight==0 ); - if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); if( p->pRight ){ assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3ExprDeleteNN(db, p->pRight); @@ -1397,6 +1398,19 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ } #endif } + if( p->pLeft && p->op!=TK_SELECT_COLUMN ){ + Expr *pLeft = p->pLeft; + if( !ExprHasProperty(p, EP_Static) + && !ExprHasProperty(pLeft, EP_Static) + ){ + /* Avoid unnecessary recursion on unary operators */ + sqlite3DbNNFreeNN(db, p); + p = pLeft; + goto exprDeleteRestart; + }else{ + sqlite3ExprDeleteNN(db, pLeft); + } + } } if( !ExprHasProperty(p, EP_Static) ){ sqlite3DbNNFreeNN(db, p); @@ -1429,11 +1443,11 @@ void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ ** ** The pExpr might be deleted immediately on an OOM error. ** -** The deferred delete is (currently) implemented by adding the -** pExpr to the pParse->pConstExpr list with a register number of 0. +** Return 0 if the delete was successfully deferred. Return non-zero +** if the delete happened immediately because of an OOM. */ -void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ - sqlite3ParserAddCleanup(pParse, sqlite3ExprDeleteGeneric, pExpr); +int sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ + return 0==sqlite3ParserAddCleanup(pParse, sqlite3ExprDeleteGeneric, pExpr); } /* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the @@ -1869,17 +1883,19 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){ pNewItem->iCursor = pOldItem->iCursor; pNewItem->addrFillSub = pOldItem->addrFillSub; pNewItem->regReturn = pOldItem->regReturn; + pNewItem->regResult = pOldItem->regResult; if( pNewItem->fg.isIndexedBy ){ pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy); + }else if( pNewItem->fg.isTabFunc ){ + pNewItem->u1.pFuncArg = + sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); + }else{ + pNewItem->u1.nRow = pOldItem->u1.nRow; } pNewItem->u2 = pOldItem->u2; if( pNewItem->fg.isCte ){ pNewItem->u2.pCteUse->nUse++; } - if( pNewItem->fg.isTabFunc ){ - pNewItem->u1.pFuncArg = - sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); - } pTab = pNewItem->pTab = pOldItem->pTab; if( pTab ){ pTab->nTabRef++; @@ -2345,6 +2361,54 @@ Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ return pExpr; } +/* +** pExpr is a TK_FUNCTION node. Try to determine whether or not the +** function is a constant function. A function is constant if all of +** the following are true: +** +** (1) It is a scalar function (not an aggregate or window function) +** (2) It has either the SQLITE_FUNC_CONSTANT or SQLITE_FUNC_SLOCHNG +** property. +** (3) All of its arguments are constants +** +** This routine sets pWalker->eCode to 0 if pExpr is not a constant. +** It makes no changes to pWalker->eCode if pExpr is constant. In +** every case, it returns WRC_Abort. +** +** Called as a service subroutine from exprNodeIsConstant(). +*/ +static SQLITE_NOINLINE int exprNodeIsConstantFunction( + Walker *pWalker, + Expr *pExpr +){ + int n; /* Number of arguments */ + ExprList *pList; /* List of arguments */ + FuncDef *pDef; /* The function */ + sqlite3 *db; /* The database */ + + assert( pExpr->op==TK_FUNCTION ); + if( ExprHasProperty(pExpr, EP_TokenOnly) + || (pList = pExpr->x.pList)==0 + ){; + n = 0; + }else{ + n = pList->nExpr; + sqlite3WalkExprList(pWalker, pList); + if( pWalker->eCode==0 ) return WRC_Abort; + } + db = pWalker->pParse->db; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 + || pDef->xFinalize!=0 + || (pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 + || ExprHasProperty(pExpr, EP_WinFunc) + ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Prune; +} + /* ** These routines are Walker callbacks used to check expressions to @@ -2373,6 +2437,7 @@ Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ ** malformed schema error. */ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ + assert( pWalker->eCode>0 ); /* If pWalker->eCode is 2 then any term of the expression that comes from ** the ON or USING clauses of an outer join disqualifies the expression @@ -2392,6 +2457,8 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ ){ if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL); return WRC_Continue; + }else if( pWalker->pParse ){ + return exprNodeIsConstantFunction(pWalker, pExpr); }else{ pWalker->eCode = 0; return WRC_Abort; @@ -2420,9 +2487,11 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ case TK_IF_NULL_ROW: case TK_REGISTER: case TK_DOT: + case TK_RAISE: testcase( pExpr->op==TK_REGISTER ); testcase( pExpr->op==TK_IF_NULL_ROW ); testcase( pExpr->op==TK_DOT ); + testcase( pExpr->op==TK_RAISE ); pWalker->eCode = 0; return WRC_Abort; case TK_VARIABLE: @@ -2444,15 +2513,15 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } } -static int exprIsConst(Expr *p, int initFlag, int iCur){ +static int exprIsConst(Parse *pParse, Expr *p, int initFlag){ Walker w; w.eCode = initFlag; + w.pParse = pParse; w.xExprCallback = exprNodeIsConstant; w.xSelectCallback = sqlite3SelectWalkFail; #ifdef SQLITE_DEBUG w.xSelectCallback2 = sqlite3SelectWalkAssert2; #endif - w.u.iCur = iCur; sqlite3WalkExpr(&w, p); return w.eCode; } @@ -2464,9 +2533,15 @@ static int exprIsConst(Expr *p, int initFlag, int iCur){ ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is ** a constant. +** +** The pParse parameter may be NULL. But if it is NULL, there is no way +** to determine if function calls are constant or not, and hence all +** function calls will be considered to be non-constant. If pParse is +** not NULL, then a function call might be constant, depending on the +** function and on its parameters. */ -int sqlite3ExprIsConstant(Expr *p){ - return exprIsConst(p, 1, 0); +int sqlite3ExprIsConstant(Parse *pParse, Expr *p){ + return exprIsConst(pParse, p, 1); } /* @@ -2482,8 +2557,24 @@ int sqlite3ExprIsConstant(Expr *p){ ** can be added to the pParse->pConstExpr list and evaluated once when ** the prepared statement starts up. See sqlite3ExprCodeRunJustOnce(). */ -int sqlite3ExprIsConstantNotJoin(Expr *p){ - return exprIsConst(p, 2, 0); +static int sqlite3ExprIsConstantNotJoin(Parse *pParse, Expr *p){ + return exprIsConst(pParse, p, 2); +} + +/* +** This routine examines sub-SELECT statements as an expression is being +** walked as part of sqlite3ExprIsTableConstant(). Sub-SELECTs are considered +** constant as long as they are uncorrelated - meaning that they do not +** contain any terms from outer contexts. +*/ +static int exprSelectWalkTableConstant(Walker *pWalker, Select *pSelect){ + assert( pSelect!=0 ); + assert( pWalker->eCode==3 || pWalker->eCode==0 ); + if( (pSelect->selFlags & SF_Correlated)!=0 ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Prune; } /* @@ -2491,9 +2582,26 @@ int sqlite3ExprIsConstantNotJoin(Expr *p){ ** for any single row of the table with cursor iCur. In other words, the ** expression must not refer to any non-deterministic function nor any ** table other than iCur. +** +** Consider uncorrelated subqueries to be constants if the bAllowSubq +** parameter is true. */ -int sqlite3ExprIsTableConstant(Expr *p, int iCur){ - return exprIsConst(p, 3, iCur); +static int sqlite3ExprIsTableConstant(Expr *p, int iCur, int bAllowSubq){ + Walker w; + w.eCode = 3; + w.pParse = 0; + w.xExprCallback = exprNodeIsConstant; + if( bAllowSubq ){ + w.xSelectCallback = exprSelectWalkTableConstant; + }else{ + w.xSelectCallback = sqlite3SelectWalkFail; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif + } + w.u.iCur = iCur; + sqlite3WalkExpr(&w, p); + return w.eCode; } /* @@ -2511,7 +2619,10 @@ int sqlite3ExprIsTableConstant(Expr *p, int iCur){ ** ** (1) pExpr cannot refer to any table other than pSrc->iCursor. ** -** (2) pExpr cannot use subqueries or non-deterministic functions. +** (2a) pExpr cannot use subqueries unless the bAllowSubq parameter is +** true and the subquery is non-correlated +** +** (2b) pExpr cannot use non-deterministic functions. ** ** (3) pSrc cannot be part of the left operand for a RIGHT JOIN. ** (Is there some way to relax this constraint?) @@ -2540,7 +2651,8 @@ int sqlite3ExprIsTableConstant(Expr *p, int iCur){ int sqlite3ExprIsSingleTableConstraint( Expr *pExpr, /* The constraint */ const SrcList *pSrcList, /* Complete FROM clause */ - int iSrc /* Which element of pSrcList to use */ + int iSrc, /* Which element of pSrcList to use */ + int bAllowSubq /* Allow non-correlated subqueries */ ){ const SrcItem *pSrc = &pSrcList->a[iSrc]; if( pSrc->fg.jointype & JT_LTORJ ){ @@ -2565,7 +2677,8 @@ int sqlite3ExprIsSingleTableConstraint( } } } - return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ + /* Rules (1), (2a), and (2b) handled by the following: */ + return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor, bAllowSubq); } @@ -2650,7 +2763,7 @@ int sqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprList *pGroupBy){ */ int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ assert( isInit==0 || isInit==1 ); - return exprIsConst(p, 4+isInit, 0); + return exprIsConst(0, p, 4+isInit); } #ifdef SQLITE_ENABLE_CURSOR_HINTS @@ -2898,13 +3011,13 @@ static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){ ** The argument is an IN operator with a list (not a subquery) on the ** right-hand side. Return TRUE if that list is constant. */ -static int sqlite3InRhsIsConstant(Expr *pIn){ +static int sqlite3InRhsIsConstant(Parse *pParse, Expr *pIn){ Expr *pLHS; int res; assert( !ExprHasProperty(pIn, EP_xIsSelect) ); pLHS = pIn->pLeft; pIn->pLeft = 0; - res = sqlite3ExprIsConstant(pIn); + res = sqlite3ExprIsConstant(pParse, pIn); pIn->pLeft = pLHS; return res; } @@ -3173,7 +3286,7 @@ int sqlite3FindInIndex( if( eType==0 && (inFlags & IN_INDEX_NOOP_OK) && ExprUseXList(pX) - && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) + && (!sqlite3InRhsIsConstant(pParse,pX) || pX->x.pList->nExpr<=2) ){ pParse->nTab--; /* Back out the allocation of the unused cursor */ iTab = -1; /* Cursor is not allocated */ @@ -3456,7 +3569,7 @@ void sqlite3CodeRhsOfIN( ** this code only executes once. Because for a non-constant ** expression we need to rerun this code each time. */ - if( addrOnce && !sqlite3ExprIsConstant(pE2) ){ + if( addrOnce && !sqlite3ExprIsConstant(pParse, pE2) ){ sqlite3VdbeChangeToNoop(v, addrOnce-1); sqlite3VdbeChangeToNoop(v, addrOnce); ExprClearProperty(pExpr, EP_Subrtn); @@ -4620,12 +4733,6 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( pExpr->u.zToken!=0 ); assert( pExpr->u.zToken[0]!=0 ); sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target); - if( pExpr->u.zToken[1]!=0 ){ - const char *z = sqlite3VListNumToName(pParse->pVList, pExpr->iColumn); - assert( pExpr->u.zToken[0]=='?' || (z && !strcmp(pExpr->u.zToken, z)) ); - pParse->pVList[0] = 0; /* Indicate VList may no longer be enlarged */ - sqlite3VdbeAppendP4(v, (char*)z, P4_STATIC); - } return target; } case TK_REGISTER: { @@ -4799,7 +4906,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ } #endif - if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){ + if( ConstFactorOk(pParse) + && sqlite3ExprIsConstantNotJoin(pParse,pExpr) + ){ /* SQL functions can be expensive. So try to avoid running them ** multiple times if we know they always give the same result */ return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); @@ -4830,7 +4939,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ } for(i=0; ia[i].pExpr) ){ + if( i<32 && sqlite3ExprIsConstant(pParse, pFarg->a[i].pExpr) ){ testcase( i==31 ); constMask |= MASKBIT32(i); } @@ -4972,8 +5081,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( !ExprHasProperty(pExpr, EP_Collate) ){ /* A TK_COLLATE Expr node without the EP_Collate tag is a so-called ** "SOFT-COLLATE" that is added to constraints that are pushed down - ** from outer queries into sub-queries by the push-down optimization. - ** Clear subtypes as subtypes may not cross a subquery boundary. + ** from outer queries into sub-queries by the WHERE-clause push-down + ** optimization. Clear subtypes as subtypes may not cross a subquery + ** boundary. */ assert( pExpr->pLeft ); sqlite3ExprCode(pParse, pExpr->pLeft, target); @@ -5297,7 +5407,7 @@ int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ if( ConstFactorOk(pParse) && ALWAYS(pExpr!=0) && pExpr->op!=TK_REGISTER - && sqlite3ExprIsConstantNotJoin(pExpr) + && sqlite3ExprIsConstantNotJoin(pParse, pExpr) ){ *pReg = 0; r2 = sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); @@ -5361,7 +5471,7 @@ void sqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){ ** might choose to code the expression at initialization time. */ void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ - if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){ + if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pParse,pExpr) ){ sqlite3ExprCodeRunJustOnce(pParse, pExpr, target); }else{ sqlite3ExprCodeCopy(pParse, pExpr, target); @@ -5420,7 +5530,7 @@ int sqlite3ExprCodeExprList( sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); } }else if( (flags & SQLITE_ECEL_FACTOR)!=0 - && sqlite3ExprIsConstantNotJoin(pExpr) + && sqlite3ExprIsConstantNotJoin(pParse,pExpr) ){ sqlite3ExprCodeRunJustOnce(pParse, pExpr, target+i); }else{ @@ -6571,9 +6681,8 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ && pAggInfo->aCol[iAgg].pCExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); - if( pExpr ){ + if( pExpr && !sqlite3ExprDeferredDelete(pParse, pExpr) ){ pAggInfo->aCol[iAgg].pCExpr = pExpr; - sqlite3ExprDeferredDelete(pParse, pExpr); } } }else{ @@ -6582,9 +6691,8 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ && pAggInfo->aFunc[iAgg].pFExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); - if( pExpr ){ + if( pExpr && !sqlite3ExprDeferredDelete(pParse, pExpr) ){ pAggInfo->aFunc[iAgg].pFExpr = pExpr; - sqlite3ExprDeferredDelete(pParse, pExpr); } } } diff --git a/src/insert.c b/src/insert.c index c2c2f7cc0..072386e65 100644 --- a/src/insert.c +++ b/src/insert.c @@ -577,6 +577,195 @@ void sqlite3AutoincrementEnd(Parse *pParse){ # define autoIncStep(A,B,C) #endif /* SQLITE_OMIT_AUTOINCREMENT */ +/* +** If argument pVal is a Select object returned by an sqlite3MultiValues() +** that was able to use the co-routine optimization, finish coding the +** co-routine. +*/ +void sqlite3MultiValuesEnd(Parse *pParse, Select *pVal){ + if( ALWAYS(pVal) && pVal->pSrc->nSrc>0 ){ + SrcItem *pItem = &pVal->pSrc->a[0]; + sqlite3VdbeEndCoroutine(pParse->pVdbe, pItem->regReturn); + sqlite3VdbeJumpHere(pParse->pVdbe, pItem->addrFillSub - 1); + } +} + +/* +** Return true if all expressions in the expression-list passed as the +** only argument are constant. +*/ +static int exprListIsConstant(Parse *pParse, ExprList *pRow){ + int ii; + for(ii=0; iinExpr; ii++){ + if( 0==sqlite3ExprIsConstant(pParse, pRow->a[ii].pExpr) ) return 0; + } + return 1; +} + +/* +** Return true if all expressions in the expression-list passed as the +** only argument are both constant and have no affinity. +*/ +static int exprListIsNoAffinity(Parse *pParse, ExprList *pRow){ + int ii; + if( exprListIsConstant(pParse,pRow)==0 ) return 0; + for(ii=0; iinExpr; ii++){ + Expr *pExpr = pRow->a[ii].pExpr; + assert( pExpr->op!=TK_RAISE ); + assert( pExpr->affExpr==0 ); + if( 0!=sqlite3ExprAffinity(pExpr) ) return 0; + } + return 1; + +} + +/* +** This function is called by the parser for the second and subsequent +** rows of a multi-row VALUES clause. Argument pLeft is the part of +** the VALUES clause already parsed, argument pRow is the vector of values +** for the new row. The Select object returned represents the complete +** VALUES clause, including the new row. +** +** There are two ways in which this may be achieved - by incremental +** coding of a co-routine (the "co-routine" method) or by returning a +** Select object equivalent to the following (the "UNION ALL" method): +** +** "pLeft UNION ALL SELECT pRow" +** +** If the VALUES clause contains a lot of rows, this compound Select +** object may consume a lot of memory. +** +** When the co-routine method is used, each row that will be returned +** by the VALUES clause is coded into part of a co-routine as it is +** passed to this function. The returned Select object is equivalent to: +** +** SELECT * FROM ( +** Select object to read co-routine +** ) +** +** The co-routine method is used in most cases. Exceptions are: +** +** a) If the current statement has a WITH clause. This is to avoid +** statements like: +** +** WITH cte AS ( VALUES('x'), ('y') ... ) +** SELECT * FROM cte AS a, cte AS b; +** +** This will not work, as the co-routine uses a hard-coded register +** for its OP_Yield instructions, and so it is not possible for two +** cursors to iterate through it concurrently. +** +** b) The schema is currently being parsed (i.e. the VALUES clause is part +** of a schema item like a VIEW or TRIGGER). In this case there is no VM +** being generated when parsing is taking place, and so generating +** a co-routine is not possible. +** +** c) There are non-constant expressions in the VALUES clause (e.g. +** the VALUES clause is part of a correlated sub-query). +** +** d) One or more of the values in the first row of the VALUES clause +** has an affinity (i.e. is a CAST expression). This causes problems +** because the complex rules SQLite uses (see function +** sqlite3SubqueryColumnTypes() in select.c) to determine the effective +** affinity of such a column for all rows require access to all values in +** the column simultaneously. +*/ +Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){ + + if( pParse->bHasWith /* condition (a) above */ + || pParse->db->init.busy /* condition (b) above */ + || exprListIsConstant(pParse,pRow)==0 /* condition (c) above */ + || (pLeft->pSrc->nSrc==0 && + exprListIsNoAffinity(pParse,pLeft->pEList)==0) /* condition (d) above */ + || IN_SPECIAL_PARSE + ){ + /* The co-routine method cannot be used. Fall back to UNION ALL. */ + Select *pSelect = 0; + int f = SF_Values | SF_MultiValue; + if( pLeft->pSrc->nSrc ){ + sqlite3MultiValuesEnd(pParse, pLeft); + f = SF_Values; + }else if( pLeft->pPrior ){ + /* In this case set the SF_MultiValue flag only if it was set on pLeft */ + f = (f & pLeft->selFlags); + } + pSelect = sqlite3SelectNew(pParse, pRow, 0, 0, 0, 0, 0, f, 0); + pLeft->selFlags &= ~SF_MultiValue; + if( pSelect ){ + pSelect->op = TK_ALL; + pSelect->pPrior = pLeft; + pLeft = pSelect; + } + }else{ + SrcItem *p = 0; /* SrcItem that reads from co-routine */ + + if( pLeft->pSrc->nSrc==0 ){ + /* Co-routine has not yet been started and the special Select object + ** that accesses the co-routine has not yet been created. This block + ** does both those things. */ + Vdbe *v = sqlite3GetVdbe(pParse); + Select *pRet = sqlite3SelectNew(pParse, 0, 0, 0, 0, 0, 0, 0, 0); + + /* Ensure the database schema has been read. This is to ensure we have + ** the correct text encoding. */ + if( (pParse->db->mDbFlags & DBFLAG_SchemaKnownOk)==0 ){ + sqlite3ReadSchema(pParse); + } + + if( pRet ){ + SelectDest dest; + pRet->pSrc->nSrc = 1; + pRet->pPrior = pLeft->pPrior; + pRet->op = pLeft->op; + pLeft->pPrior = 0; + pLeft->op = TK_SELECT; + assert( pLeft->pNext==0 ); + assert( pRet->pNext==0 ); + p = &pRet->pSrc->a[0]; + p->pSelect = pLeft; + p->fg.viaCoroutine = 1; + p->addrFillSub = sqlite3VdbeCurrentAddr(v) + 1; + p->regReturn = ++pParse->nMem; + p->iCursor = -1; + p->u1.nRow = 2; + sqlite3VdbeAddOp3(v,OP_InitCoroutine,p->regReturn,0,p->addrFillSub); + sqlite3SelectDestInit(&dest, SRT_Coroutine, p->regReturn); + + /* Allocate registers for the output of the co-routine. Do so so + ** that there are two unused registers immediately before those + ** used by the co-routine. This allows the code in sqlite3Insert() + ** to use these registers directly, instead of copying the output + ** of the co-routine to a separate array for processing. */ + dest.iSdst = pParse->nMem + 3; + dest.nSdst = pLeft->pEList->nExpr; + pParse->nMem += 2 + dest.nSdst; + + pLeft->selFlags |= SF_MultiValue; + sqlite3Select(pParse, pLeft, &dest); + p->regResult = dest.iSdst; + assert( pParse->nErr || dest.iSdst>0 ); + pLeft = pRet; + } + }else{ + p = &pLeft->pSrc->a[0]; + assert( !p->fg.isTabFunc && !p->fg.isIndexedBy ); + p->u1.nRow++; + } + + if( pParse->nErr==0 ){ + assert( p!=0 ); + if( p->pSelect->pEList->nExpr!=pRow->nExpr ){ + sqlite3SelectWrongNumTermsError(pParse, p->pSelect); + }else{ + sqlite3ExprCodeExprList(pParse, pRow, p->regResult, 0, 0); + sqlite3VdbeAddOp1(pParse->pVdbe, OP_Yield, p->regReturn); + } + } + sqlite3ExprListDelete(pParse->db, pRow); + } + + return pLeft; +} /* Forward declaration */ static int xferOptimization( @@ -913,25 +1102,40 @@ void sqlite3Insert( if( pSelect ){ /* Data is coming from a SELECT or from a multi-row VALUES clause. ** Generate a co-routine to run the SELECT. */ - int regYield; /* Register holding co-routine entry-point */ - int addrTop; /* Top of the co-routine */ int rc; /* Result code */ - regYield = ++pParse->nMem; - addrTop = sqlite3VdbeCurrentAddr(v) + 1; - sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); - sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); - dest.iSdst = bIdListInOrder ? regData : 0; - dest.nSdst = pTab->nCol; - rc = sqlite3Select(pParse, pSelect, &dest); - regFromSelect = dest.iSdst; - assert( db->pParse==pParse ); - if( rc || pParse->nErr ) goto insert_cleanup; - assert( db->mallocFailed==0 ); - sqlite3VdbeEndCoroutine(v, regYield); - sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ - assert( pSelect->pEList ); - nColumn = pSelect->pEList->nExpr; + if( pSelect->pSrc->nSrc==1 + && pSelect->pSrc->a[0].fg.viaCoroutine + && pSelect->pPrior==0 + ){ + SrcItem *pItem = &pSelect->pSrc->a[0]; + dest.iSDParm = pItem->regReturn; + regFromSelect = pItem->regResult; + nColumn = pItem->pSelect->pEList->nExpr; + ExplainQueryPlan((pParse, 0, "SCAN %S", pItem)); + if( bIdListInOrder && nColumn==pTab->nCol ){ + regData = regFromSelect; + regRowid = regData - 1; + regIns = regRowid - (IsVirtual(pTab) ? 1 : 0); + } + }else{ + int addrTop; /* Top of the co-routine */ + int regYield = ++pParse->nMem; + addrTop = sqlite3VdbeCurrentAddr(v) + 1; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); + sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); + dest.iSdst = bIdListInOrder ? regData : 0; + dest.nSdst = pTab->nCol; + rc = sqlite3Select(pParse, pSelect, &dest); + regFromSelect = dest.iSdst; + assert( db->pParse==pParse ); + if( rc || pParse->nErr ) goto insert_cleanup; + assert( db->mallocFailed==0 ); + sqlite3VdbeEndCoroutine(v, regYield); + sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ + assert( pSelect->pEList ); + nColumn = pSelect->pEList->nExpr; + } /* Set useTempTable to TRUE if the result of the SELECT statement ** should be written into a temporary table (template 4). Set to diff --git a/src/json.c b/src/json.c index 5bfa869f6..4db468c92 100644 --- a/src/json.c +++ b/src/json.c @@ -563,7 +563,6 @@ static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ } } - /* Append formatted text (not to exceed N bytes) to the JsonString. */ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ @@ -621,6 +620,40 @@ static void jsonAppendSeparator(JsonString *p){ jsonAppendChar(p, ','); } +/* c is a control character. Append the canonical JSON representation +** of that control character to p. +** +** This routine assumes that the output buffer has already been enlarged +** sufficiently to hold the worst-case encoding plus a nul terminator. +*/ +static void jsonAppendControlChar(JsonString *p, u8 c){ + static const char aSpecial[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + assert( sizeof(aSpecial)==32 ); + assert( aSpecial['\b']=='b' ); + assert( aSpecial['\f']=='f' ); + assert( aSpecial['\n']=='n' ); + assert( aSpecial['\r']=='r' ); + assert( aSpecial['\t']=='t' ); + assert( c>=0 && cnUsed+7 <= p->nAlloc ); + if( aSpecial[c] ){ + p->zBuf[p->nUsed] = '\\'; + p->zBuf[p->nUsed+1] = aSpecial[c]; + p->nUsed += 2; + }else{ + p->zBuf[p->nUsed] = '\\'; + p->zBuf[p->nUsed+1] = 'u'; + p->zBuf[p->nUsed+2] = '0'; + p->zBuf[p->nUsed+3] = '0'; + p->zBuf[p->nUsed+4] = "0123456789abcdef"[c>>4]; + p->zBuf[p->nUsed+5] = "0123456789abcdef"[c&0xf]; + p->nUsed += 6; + } +} + /* Append the N-byte string in zIn to the end of the JsonString string ** under construction. Enclose the string in double-quotes ("...") and ** escape any double-quotes or backslash characters contained within the @@ -680,35 +713,14 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ } c = z[0]; if( c=='"' || c=='\\' ){ - json_simple_escape: if( (p->nUsed+N+3 > p->nAlloc) && jsonStringGrow(p,N+3)!=0 ) return; p->zBuf[p->nUsed++] = '\\'; p->zBuf[p->nUsed++] = c; }else if( c=='\'' ){ p->zBuf[p->nUsed++] = c; }else{ - static const char aSpecial[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - assert( sizeof(aSpecial)==32 ); - assert( aSpecial['\b']=='b' ); - assert( aSpecial['\f']=='f' ); - assert( aSpecial['\n']=='n' ); - assert( aSpecial['\r']=='r' ); - assert( aSpecial['\t']=='t' ); - assert( c>=0 && cnUsed+N+7 > p->nAlloc) && jsonStringGrow(p,N+7)!=0 ) return; - p->zBuf[p->nUsed++] = '\\'; - p->zBuf[p->nUsed++] = 'u'; - p->zBuf[p->nUsed++] = '0'; - p->zBuf[p->nUsed++] = '0'; - p->zBuf[p->nUsed++] = "0123456789abcdef"[c>>4]; - p->zBuf[p->nUsed++] = "0123456789abcdef"[c&0xf]; + jsonAppendControlChar(p, c); } z++; N--; @@ -1409,7 +1421,10 @@ static u32 jsonbValidityCheck( if( !jsonIsOk[z[j]] && z[j]!='\'' ){ if( z[j]=='"' ){ if( x==JSONB_TEXTJ ) return j+1; - }else if( z[j]!='\\' || j+1>=k ){ + }else if( z[j]<=0x1f ){ + /* Control characters in JSON5 string literals are ok */ + if( x==JSONB_TEXTJ ) return j+1; + }else if( NEVER(z[j]!='\\') || j+1>=k ){ return j+1; }else if( strchr("\"\\/bfnrt",z[j+1])!=0 ){ j++; @@ -1704,9 +1719,14 @@ static int jsonTranslateTextToBlob(JsonParse *pParse, u32 i){ return -1; } }else if( c<=0x1f ){ - /* Control characters are not allowed in strings */ - pParse->iErr = j; - return -1; + if( c==0 ){ + pParse->iErr = j; + return -1; + } + /* Control characters are not allowed in canonical JSON string + ** literals, but are allowed in JSON5 string literals. */ + opcode = JSONB_TEXT5; + pParse->hasNonstd = 1; }else if( c=='"' ){ opcode = JSONB_TEXT5; } @@ -1922,6 +1942,7 @@ static int jsonTranslateTextToBlob(JsonParse *pParse, u32 i){ return i+4; } /* fall-through into the default case that checks for NaN */ + /* no break */ deliberate_fall_through } default: { u32 k; @@ -2190,7 +2211,7 @@ static u32 jsonTranslateBlobToText( zIn = (const char*)&pParse->aBlob[i+n]; jsonAppendChar(pOut, '"'); while( sz2>0 ){ - for(k=0; k0 ){ jsonAppendRawNZ(pOut, zIn, k); if( k>=sz2 ){ @@ -2205,6 +2226,13 @@ static u32 jsonTranslateBlobToText( sz2--; continue; } + if( zIn[0]<=0x1f ){ + if( pOut->nUsed+7>pOut->nAlloc && jsonStringGrow(pOut,7) ) break; + jsonAppendControlChar(pOut, zIn[0]); + zIn++; + sz2--; + continue; + } assert( zIn[0]=='\\' ); assert( sz2>=1 ); if( sz2<2 ){ @@ -2307,6 +2335,112 @@ static u32 jsonTranslateBlobToText( return i+n+sz; } +/* Context for recursion of json_pretty() +*/ +typedef struct JsonPretty JsonPretty; +struct JsonPretty { + JsonParse *pParse; /* The BLOB being rendered */ + JsonString *pOut; /* Generate pretty output into this string */ + const char *zIndent; /* Use this text for indentation */ + u32 szIndent; /* Bytes in zIndent[] */ + u32 nIndent; /* Current level of indentation */ +}; + +/* Append indentation to the pretty JSON under construction */ +static void jsonPrettyIndent(JsonPretty *pPretty){ + u32 jj; + for(jj=0; jjnIndent; jj++){ + jsonAppendRaw(pPretty->pOut, pPretty->zIndent, pPretty->szIndent); + } +} + +/* +** Translate the binary JSONB representation of JSON beginning at +** pParse->aBlob[i] into a JSON text string. Append the JSON +** text onto the end of pOut. Return the index in pParse->aBlob[] +** of the first byte past the end of the element that is translated. +** +** This is a variant of jsonTranslateBlobToText() that "pretty-prints" +** the output. Extra whitespace is inserted to make the JSON easier +** for humans to read. +** +** If an error is detected in the BLOB input, the pOut->eErr flag +** might get set to JSTRING_MALFORMED. But not all BLOB input errors +** are detected. So a malformed JSONB input might either result +** in an error, or in incorrect JSON. +** +** The pOut->eErr JSTRING_OOM flag is set on a OOM. +*/ +static u32 jsonTranslateBlobToPrettyText( + JsonPretty *pPretty, /* Pretty-printing context */ + u32 i /* Start rendering at this index */ +){ + u32 sz, n, j, iEnd; + const JsonParse *pParse = pPretty->pParse; + JsonString *pOut = pPretty->pOut; + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ){ + pOut->eErr |= JSTRING_MALFORMED; + return pParse->nBlob+1; + } + switch( pParse->aBlob[i] & 0x0f ){ + case JSONB_ARRAY: { + j = i+n; + iEnd = j+sz; + jsonAppendChar(pOut, '['); + if( jnIndent++; + while( pOut->eErr==0 ){ + jsonPrettyIndent(pPretty); + j = jsonTranslateBlobToPrettyText(pPretty, j); + if( j>=iEnd ) break; + jsonAppendRawNZ(pOut, ",\n", 2); + } + jsonAppendChar(pOut, '\n'); + pPretty->nIndent--; + jsonPrettyIndent(pPretty); + } + jsonAppendChar(pOut, ']'); + i = iEnd; + break; + } + case JSONB_OBJECT: { + j = i+n; + iEnd = j+sz; + jsonAppendChar(pOut, '{'); + if( jnIndent++; + while( pOut->eErr==0 ){ + jsonPrettyIndent(pPretty); + j = jsonTranslateBlobToText(pParse, j, pOut); + if( j>iEnd ){ + pOut->eErr |= JSTRING_MALFORMED; + break; + } + jsonAppendRawNZ(pOut, ": ", 2); + j = jsonTranslateBlobToPrettyText(pPretty, j); + if( j>=iEnd ) break; + jsonAppendRawNZ(pOut, ",\n", 2); + } + jsonAppendChar(pOut, '\n'); + pPretty->nIndent--; + jsonPrettyIndent(pPretty); + } + jsonAppendChar(pOut, '}'); + i = iEnd; + break; + } + default: { + i = jsonTranslateBlobToText(pParse, i, pOut); + break; + } + } + return i; +} + + /* Return true if the input pJson ** ** For performance reasons, this routine does not do a detailed check of the @@ -3557,11 +3691,12 @@ static void jsonParseFunc( if( p==0 ) return; if( argc==1 ){ jsonDebugPrintBlob(p, 0, p->nBlob, 0, &out); - sqlite3_result_text64(ctx, out.zText, out.nChar, SQLITE_DYNAMIC, SQLITE_UTF8); + sqlite3_result_text64(ctx,out.zText,out.nChar,SQLITE_TRANSIENT,SQLITE_UTF8); }else{ jsonShowParse(p); } jsonParseFree(p); + sqlite3_str_reset(&out); } #endif /* SQLITE_DEBUG */ @@ -3660,13 +3795,6 @@ static void jsonArrayLengthFunc( jsonParseFree(p); } -/* True if the string is all digits */ -static int jsonAllDigits(const char *z, int n){ - int i; - for(i=0; i $[NUMBER] // Not PG. Purely for convenience */ jsonStringInit(&jx, ctx); - if( jsonAllDigits(zPath, nPath) ){ + if( sqlite3_value_type(argv[i])==SQLITE_INTEGER ){ jsonAppendRawNZ(&jx, "[", 1); jsonAppendRaw(&jx, zPath, nPath); jsonAppendRawNZ(&jx, "]", 2); @@ -4225,6 +4353,40 @@ static void jsonTypeFunc( jsonParseFree(p); } +/* +** json_pretty(JSON) +** json_pretty(JSON, INDENT) +** +** Return text that is a pretty-printed rendering of the input JSON. +** If the argument is not valid JSON, return NULL. +** +** The INDENT argument is text that is used for indentation. If omitted, +** it defaults to four spaces (the same as PostgreSQL). +*/ +static void jsonPrettyFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonString s; /* The output string */ + JsonPretty x; /* Pretty printing context */ + + memset(&x, 0, sizeof(x)); + x.pParse = jsonParseFuncArg(ctx, argv[0], 0); + if( x.pParse==0 ) return; + x.pOut = &s; + jsonStringInit(&s, ctx); + if( argc==1 || (x.zIndent = (const char*)sqlite3_value_text(argv[1]))==0 ){ + x.zIndent = " "; + x.szIndent = 4; + }else{ + x.szIndent = (u32)strlen(x.zIndent); + } + jsonTranslateBlobToPrettyText(&x, 0); + jsonReturnString(&s, 0, 0); + jsonParseFree(x.pParse); +} + /* ** json_valid(JSON) ** json_valid(JSON, FLAGS) @@ -5239,6 +5401,8 @@ void sqlite3RegisterJsonFunctions(void){ JFUNCTION(jsonb_object, -1,0,1, 1,1,0, jsonObjectFunc), JFUNCTION(json_patch, 2,1,1, 0,0,0, jsonPatchFunc), JFUNCTION(jsonb_patch, 2,1,0, 0,1,0, jsonPatchFunc), + JFUNCTION(json_pretty, 1,1,0, 0,0,0, jsonPrettyFunc), + JFUNCTION(json_pretty, 2,1,0, 0,0,0, jsonPrettyFunc), JFUNCTION(json_quote, 1,0,1, 1,0,0, jsonQuoteFunc), JFUNCTION(json_remove, -1,1,1, 0,0,0, jsonRemoveFunc), JFUNCTION(jsonb_remove, -1,1,0, 0,1,0, jsonRemoveFunc), diff --git a/src/malloc.c b/src/malloc.c index 356750682..9a635e716 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -221,6 +221,24 @@ static void sqlite3MallocAlarm(int nByte){ sqlite3_mutex_enter(mem0.mutex); } +#ifdef SQLITE_DEBUG +/* +** This routine is called whenever an out-of-memory condition is seen, +** It's only purpose to to serve as a breakpoint for gdb or similar +** code debuggers when working on out-of-memory conditions, for example +** caused by PRAGMA hard_heap_limit=N. +*/ +static SQLITE_NOINLINE void test_oom_breakpoint(u64 n){ + static u64 nOomFault = 0; + nOomFault += n; + /* The assert() is never reached in a human lifetime. It is here mostly + ** to prevent code optimizers from optimizing out this function. */ + assert( (nOomFault>>32) < 0xffffffff ); +} +#else +# define test_oom_breakpoint(X) /* No-op for production builds */ +#endif + /* ** Do a memory allocation with statistics and alarms. Assume the ** lock is already held. @@ -247,6 +265,7 @@ static void mallocWithAlarm(int n, void **pp){ if( mem0.hardLimit ){ nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); if( nUsed >= mem0.hardLimit - nFull ){ + test_oom_breakpoint(1); *pp = 0; return; } @@ -535,6 +554,7 @@ void *sqlite3Realloc(void *pOld, u64 nBytes){ sqlite3MallocAlarm(nDiff); if( mem0.hardLimit>0 && nUsed >= mem0.hardLimit - nDiff ){ sqlite3_mutex_leave(mem0.mutex); + test_oom_breakpoint(1); return 0; } } diff --git a/src/os_unix.c b/src/os_unix.c index 4b3d63c2c..9e7ba05d6 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -1295,8 +1295,12 @@ static int unixLogErrorAtLine( ** available, the error message will often be an empty string. Not a ** huge problem. Incorrectly concluding that the GNU version is available ** could lead to a segfault though. + ** + ** Forum post 3f13857fa4062301 reports that the Android SDK may use + ** int-type return, depending on its version. */ -#if defined(STRERROR_R_CHAR_P) || defined(__USE_GNU) +#if (defined(STRERROR_R_CHAR_P) || defined(__USE_GNU)) \ + && !defined(ANDROID) && !defined(__ANDROID__) zErr = # endif strerror_r(iErrno, aErr, sizeof(aErr)-1); @@ -6394,12 +6398,19 @@ static int unixOpen( rc = SQLITE_READONLY_DIRECTORY; }else if( errno!=EISDIR && isReadWrite ){ /* Failed to open the file for read/write access. Try read-only. */ + UnixUnusedFd *pReadonly = 0; flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); openFlags &= ~(O_RDWR|O_CREAT); flags |= SQLITE_OPEN_READONLY; openFlags |= O_RDONLY; isReadonly = 1; - fd = robust_open(zName, openFlags, openMode); + pReadonly = findReusableFd(zName, flags); + if( pReadonly ){ + fd = pReadonly->fd; + sqlite3_free(pReadonly); + }else{ + fd = robust_open(zName, openFlags, openMode); + } } } if( fd<0 ){ diff --git a/src/parse.y b/src/parse.y index 19491192e..071e10abd 100644 --- a/src/parse.y +++ b/src/parse.y @@ -21,6 +21,10 @@ */ } +// Function used to enlarge the parser stack, if needed +%realloc parserStackRealloc +%free sqlite3_free + // All token codes are small integers with #defines that begin with "TK_" %token_prefix TK_ @@ -45,7 +49,7 @@ } } %stack_overflow { - sqlite3ErrorMsg(pParse, "parser stack overflow"); + sqlite3OomFault(pParse->db); } // The name of the generated procedure that implements the parser @@ -547,12 +551,21 @@ cmd ::= select(X). { } return pSelect; } + + /* Memory allocator for parser stack resizing. This is a thin wrapper around + ** sqlite3_realloc() that includes a call to sqlite3FaultSim() to facilitate + ** testing. + */ + static void *parserStackRealloc(void *pOld, sqlite3_uint64 newSize){ + return sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); + } } %ifndef SQLITE_OMIT_CTE select(A) ::= WITH wqlist(W) selectnowith(X). {A = attachWithToSelect(pParse,X,W);} select(A) ::= WITH RECURSIVE wqlist(W) selectnowith(X). {A = attachWithToSelect(pParse,X,W);} + %endif /* SQLITE_OMIT_CTE */ select(A) ::= selectnowith(A). { Select *p = A; @@ -610,24 +623,27 @@ oneselect(A) ::= SELECT distinct(D) selcollist(W) from(X) where_opt(Y) %endif -oneselect(A) ::= values(A). - +// Single row VALUES clause. +// %type values {Select*} +oneselect(A) ::= values(A). %destructor values {sqlite3SelectDelete(pParse->db, $$);} values(A) ::= VALUES LP nexprlist(X) RP. { A = sqlite3SelectNew(pParse,X,0,0,0,0,0,SF_Values,0); } -values(A) ::= values(A) COMMA LP nexprlist(Y) RP. { - Select *pRight, *pLeft = A; - pRight = sqlite3SelectNew(pParse,Y,0,0,0,0,0,SF_Values|SF_MultiValue,0); - if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue; - if( pRight ){ - pRight->op = TK_ALL; - pRight->pPrior = pLeft; - A = pRight; - }else{ - A = pLeft; - } + +// Multiple row VALUES clause. +// +%type mvalues {Select*} +oneselect(A) ::= mvalues(A). { + sqlite3MultiValuesEnd(pParse, A); +} +%destructor mvalues {sqlite3SelectDelete(pParse->db, $$);} +mvalues(A) ::= values(A) COMMA LP nexprlist(Y) RP. { + A = sqlite3MultiValues(pParse, A, Y); +} +mvalues(A) ::= mvalues(A) COMMA LP nexprlist(Y) RP. { + A = sqlite3MultiValues(pParse, A, Y); } // The "distinct" nonterminal is true (1) if the DISTINCT keyword is @@ -1266,8 +1282,17 @@ expr(A) ::= NOT(B) expr(X). expr(A) ::= BITNOT(B) expr(X). {A = sqlite3PExpr(pParse, @B, X, 0);/*A-overwrites-B*/} expr(A) ::= PLUS|MINUS(B) expr(X). [BITNOT] { - A = sqlite3PExpr(pParse, @B==TK_PLUS ? TK_UPLUS : TK_UMINUS, X, 0); - /*A-overwrites-B*/ + Expr *p = X; + u8 op = @B + (TK_UPLUS-TK_PLUS); + assert( TK_UPLUS>TK_PLUS ); + assert( TK_UMINUS == TK_MINUS + (TK_UPLUS - TK_PLUS) ); + if( p && p->op==TK_UPLUS ){ + p->op = op; + A = p; + }else{ + A = sqlite3PExpr(pParse, op, p, 0); + /*A-overwrites-B*/ + } } expr(A) ::= expr(B) PTR(C) expr(D). { @@ -1309,7 +1334,7 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { if( A ) sqlite3ExprIdToTrueFalse(A); }else{ Expr *pRHS = Y->a[0].pExpr; - if( Y->nExpr==1 && sqlite3ExprIsConstant(pRHS) && A->op!=TK_VECTOR ){ + if( Y->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && A->op!=TK_VECTOR ){ Y->a[0].pExpr = 0; sqlite3ExprListDelete(pParse->db, Y); pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); @@ -1749,9 +1774,10 @@ with ::= WITH RECURSIVE wqlist(W). { sqlite3WithPush(pParse, W, 1); } wqas(A) ::= AS. {A = M10d_Any;} wqas(A) ::= AS MATERIALIZED. {A = M10d_Yes;} wqas(A) ::= AS NOT MATERIALIZED. {A = M10d_No;} -wqitem(A) ::= nm(X) eidlist_opt(Y) wqas(M) LP select(Z) RP. { +wqitem(A) ::= withnm(X) eidlist_opt(Y) wqas(M) LP select(Z) RP. { A = sqlite3CteNew(pParse, &X, Y, Z, M); /*A-overwrites-X*/ } +withnm(A) ::= nm(A). {pParse->bHasWith = 1;} wqlist(A) ::= wqitem(X). { A = sqlite3WithAdd(pParse, 0, X); /*A-overwrites-X*/ } @@ -1912,8 +1938,8 @@ filter_clause(A) ::= FILTER LP WHERE expr(X) RP. { A = X; } TRUEFALSE /* True or false keyword */ ISNOT /* Combination of IS and NOT */ FUNCTION /* A function invocation */ - UMINUS /* Unary minus */ UPLUS /* Unary plus */ + UMINUS /* Unary minus */ TRUTH /* IS TRUE or IS FALSE or IS NOT TRUE or IS NOT FALSE */ REGISTER /* Reference to a VDBE register */ VECTOR /* Vector */ @@ -1923,6 +1949,12 @@ filter_clause(A) ::= FILTER LP WHERE expr(X) RP. { A = X; } SPAN /* The span operator */ ERROR /* An expression containing an error */ . + +term(A) ::= QNUMBER(X). { + A=tokenExpr(pParse,@X,X); + sqlite3DequoteNumber(pParse, A); +} + /* There must be no more than 255 tokens defined above. If this grammar ** is extended with new rules and tokens, they must either be so few in ** number that TK_SPAN is no more than 255, or else the new tokens must diff --git a/src/pragma.c b/src/pragma.c index 0a6873201..a8045aab1 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -30,6 +30,34 @@ ** ../tool/mkpragmatab.tcl. */ #include "pragma.h" +/* +** When the 0x10 bit of PRAGMA optimize is set, any ANALYZE commands +** will be run with an analysis_limit set to the lessor of the value of +** the following macro or to the actual analysis_limit if it is non-zero, +** in order to prevent PRAGMA optimize from running for too long. +** +** The value of 2000 is chosen emperically so that the worst-case run-time +** for PRAGMA optimize does not exceed 100 milliseconds against a variety +** of test databases on a RaspberryPI-4 compiled using -Os and without +** -DSQLITE_DEBUG. Of course, your mileage may vary. For the purpose of +** this paragraph, "worst-case" means that ANALYZE ends up being +** run on every table in the database. The worst case typically only +** happens if PRAGMA optimize is run on a database file for which ANALYZE +** has not been previously run and the 0x10000 flag is included so that +** all tables are analyzed. The usual case for PRAGMA optimize is that +** no ANALYZE commands will be run at all, or if any ANALYZE happens it +** will be against a single table, so that expected timing for PRAGMA +** optimize on a PI-4 is more like 1 millisecond or less with the 0x10000 +** flag or less than 100 microseconds without the 0x10000 flag. +** +** An analysis limit of 2000 is almost always sufficient for the query +** planner to fully characterize an index. The additional accuracy from +** a larger analysis is not usually helpful. +*/ +#ifndef SQLITE_DEFAULT_OPTIMIZE_LIMIT +# define SQLITE_DEFAULT_OPTIMIZE_LIMIT 2000 +#endif + /* ** Interpret the given string as a safety level. Return 0 for OFF, ** 1 for ON or NORMAL, 2 for FULL, and 3 for EXTRA. Return 1 for an empty or @@ -1675,7 +1703,7 @@ void sqlite3Pragma( /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; if( zRight ){ - if( sqlite3GetInt32(zRight, &mxErr) ){ + if( sqlite3GetInt32(pValue->z, &mxErr) ){ if( mxErr<=0 ){ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; } @@ -1692,7 +1720,6 @@ void sqlite3Pragma( Hash *pTbls; /* Set of all tables in the schema */ int *aRoot; /* Array of root page numbers of all btrees */ int cnt = 0; /* Number of entries in aRoot[] */ - int mxIdx = 0; /* Maximum number of indexes for any table */ if( OMIT_TEMPDB && i==1 ) continue; if( iDb>=0 && i!=iDb ) continue; @@ -1714,7 +1741,6 @@ void sqlite3Pragma( if( pObjTab && pObjTab!=pTab ) continue; if( HasRowid(pTab) ) cnt++; for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } - if( nIdx>mxIdx ) mxIdx = nIdx; } if( cnt==0 ) continue; if( pObjTab ) cnt++; @@ -1734,11 +1760,11 @@ void sqlite3Pragma( aRoot[0] = cnt; /* Make sure sufficient number of registers have been allocated */ - sqlite3TouchRegister(pParse, 8+mxIdx); + sqlite3TouchRegister(pParse, 8+cnt); sqlite3ClearTempRegCache(pParse); /* Do the b-tree integrity checks */ - sqlite3VdbeAddOp4(v, OP_IntegrityCk, 2, cnt, 1, (char*)aRoot,P4_INTARRAY); + sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY); sqlite3VdbeChangeP5(v, (u8)i); addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, @@ -1748,6 +1774,36 @@ void sqlite3Pragma( integrityCheckResultRow(v); sqlite3VdbeJumpHere(v, addr); + /* Check that the indexes all have the right number of rows */ + cnt = pObjTab ? 1 : 0; + sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + int iTab = 0; + Table *pTab = sqliteHashData(x); + Index *pIdx; + if( pObjTab && pObjTab!=pTab ) continue; + if( HasRowid(pTab) ){ + iTab = cnt++; + }else{ + iTab = cnt; + for(pIdx=pTab->pIndex; ALWAYS(pIdx); pIdx=pIdx->pNext){ + if( IsPrimaryKeyIndex(pIdx) ) break; + iTab++; + } + } + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->pPartIdxWhere==0 ){ + addr = sqlite3VdbeAddOp3(v, OP_Eq, 8+cnt, 0, 8+iTab); + VdbeCoverageNeverNull(v); + sqlite3VdbeLoadString(v, 4, pIdx->zName); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 2, 3); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, addr); + } + cnt++; + } + } + /* Make sure all the indices are constructed correctly. */ for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ @@ -2071,21 +2127,9 @@ void sqlite3Pragma( } sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v); sqlite3VdbeJumpHere(v, loopTop-1); - if( !isQuick ){ - sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - if( pPk==pIdx ) continue; - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3); - addr = sqlite3VdbeAddOp3(v, OP_Eq, 8+j, 0, 3); VdbeCoverage(v); - sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); - sqlite3VdbeLoadString(v, 4, pIdx->zName); - sqlite3VdbeAddOp3(v, OP_Concat, 4, 2, 3); - integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, addr); - } - if( pPk ){ - sqlite3ReleaseTempRange(pParse, r2, pPk->nKeyCol); - } + if( pPk ){ + assert( !isQuick ); + sqlite3ReleaseTempRange(pParse, r2, pPk->nKeyCol); } } @@ -2383,44 +2427,63 @@ void sqlite3Pragma( ** ** The optional argument is a bitmask of optimizations to perform: ** - ** 0x0001 Debugging mode. Do not actually perform any optimizations - ** but instead return one line of text for each optimization - ** that would have been done. Off by default. + ** 0x00001 Debugging mode. Do not actually perform any optimizations + ** but instead return one line of text for each optimization + ** that would have been done. Off by default. ** - ** 0x0002 Run ANALYZE on tables that might benefit. On by default. - ** See below for additional information. + ** 0x00002 Run ANALYZE on tables that might benefit. On by default. + ** See below for additional information. ** - ** 0x0004 (Not yet implemented) Record usage and performance - ** information from the current session in the - ** database file so that it will be available to "optimize" - ** pragmas run by future database connections. + ** 0x00010 Run all ANALYZE operations using an analysis_limit that + ** is the lessor of the current analysis_limit and the + ** SQLITE_DEFAULT_OPTIMIZE_LIMIT compile-time option. + ** The default value of SQLITE_DEFAULT_OPTIMIZE_LIMIT is + ** currently (2024-02-19) set to 2000, which is such that + ** the worst case run-time for PRAGMA optimize on a 100MB + ** database will usually be less than 100 milliseconds on + ** a RaspberryPI-4 class machine. On by default. ** - ** 0x0008 (Not yet implemented) Create indexes that might have - ** been helpful to recent queries + ** 0x10000 Look at tables to see if they need to be reanalyzed + ** due to growth or shrinkage even if they have not been + ** queried during the current connection. Off by default. ** - ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all - ** of the optimizations listed above except Debug Mode, including new - ** optimizations that have not yet been invented. If new optimizations are - ** ever added that should be off by default, those off-by-default - ** optimizations will have bitmasks of 0x10000 or larger. + ** The default MASK is and always shall be 0x0fffe. In the current + ** implementation, the default mask only covers the 0x00002 optimization, + ** though additional optimizations that are covered by 0x0fffe might be + ** added in the future. Optimizations that are off by default and must + ** be explicitly requested have masks of 0x10000 or greater. ** ** DETERMINATION OF WHEN TO RUN ANALYZE ** ** In the current implementation, a table is analyzed if only if all of ** the following are true: ** - ** (1) MASK bit 0x02 is set. + ** (1) MASK bit 0x00002 is set. + ** + ** (2) The table is an ordinary table, not a virtual table or view. ** - ** (2) The query planner used sqlite_stat1-style statistics for one or - ** more indexes of the table at some point during the lifetime of - ** the current connection. + ** (3) The table name does not begin with "sqlite_". ** - ** (3) One or more indexes of the table are currently unanalyzed OR - ** the number of rows in the table has increased by 25 times or more - ** since the last time ANALYZE was run. + ** (4) One or more of the following is true: + ** (4a) The 0x10000 MASK bit is set. + ** (4b) One or more indexes on the table lacks an entry + ** in the sqlite_stat1 table. + ** (4c) The query planner used sqlite_stat1-style statistics for one + ** or more indexes of the table at some point during the lifetime + ** of the current connection. + ** + ** (5) One or more of the following is true: + ** (5a) One or more indexes on the table lacks an entry + ** in the sqlite_stat1 table. (Same as 4a) + ** (5b) The number of rows in the table has increased or decreased by + ** 10-fold. In other words, the current size of the table is + ** 10 times larger than the size in sqlite_stat1 or else the + ** current size is less than 1/10th the size in sqlite_stat1. ** ** The rules for when tables are analyzed are likely to change in - ** future releases. + ** future releases. Future versions of SQLite might accept a string + ** literal argument to this pragma that contains a mnemonic description + ** of the options rather than a bitmap. */ case PragTyp_OPTIMIZE: { int iDbLast; /* Loop termination point for the schema loop */ @@ -2432,6 +2495,10 @@ void sqlite3Pragma( LogEst szThreshold; /* Size threshold above which reanalysis needed */ char *zSubSql; /* SQL statement for the OP_SqlExec opcode */ u32 opMask; /* Mask of operations to perform */ + int nLimit; /* Analysis limit to use */ + int nCheck = 0; /* Number of tables to be optimized */ + int nBtree = 0; /* Number of btrees to scan */ + int nIndex; /* Number of indexes on the current table */ if( zRight ){ opMask = (u32)sqlite3Atoi(zRight); @@ -2439,6 +2506,14 @@ void sqlite3Pragma( }else{ opMask = 0xfffe; } + if( (opMask & 0x10)==0 ){ + nLimit = 0; + }else if( db->nAnalysisLimit>0 + && db->nAnalysisLimitnTab++; for(iDbLast = zDb?iDb:db->nDb-1; iDb<=iDbLast; iDb++){ if( iDb==1 ) continue; @@ -2447,23 +2522,61 @@ void sqlite3Pragma( for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ pTab = (Table*)sqliteHashData(k); - /* If table pTab has not been used in a way that would benefit from - ** having analysis statistics during the current session, then skip it. - ** This also has the effect of skipping virtual tables and views */ - if( (pTab->tabFlags & TF_StatsUsed)==0 ) continue; + /* This only works for ordinary tables */ + if( !IsOrdinaryTable(pTab) ) continue; + + /* Do not scan system tables */ + if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) ) continue; - /* Reanalyze if the table is 25 times larger than the last analysis */ - szThreshold = pTab->nRowLogEst + 46; assert( sqlite3LogEst(25)==46 ); + /* Find the size of the table as last recorded in sqlite_stat1. + ** If any index is unanalyzed, then the threshold is -1 to + ** indicate a new, unanalyzed index + */ + szThreshold = pTab->nRowLogEst; + nIndex = 0; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + nIndex++; if( !pIdx->hasStat1 ){ - szThreshold = 0; /* Always analyze if any index lacks statistics */ - break; + szThreshold = -1; /* Always analyze if any index lacks statistics */ } } - if( szThreshold ){ - sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); - sqlite3VdbeAddOp3(v, OP_IfSmaller, iTabCur, - sqlite3VdbeCurrentAddr(v)+2+(opMask&1), szThreshold); + + /* If table pTab has not been used in a way that would benefit from + ** having analysis statistics during the current session, then skip it, + ** unless the 0x10000 MASK bit is set. */ + if( (pTab->tabFlags & TF_MaybeReanalyze)!=0 ){ + /* Check for size change if stat1 has been used for a query */ + }else if( opMask & 0x10000 ){ + /* Check for size change if 0x10000 is set */ + }else if( pTab->pIndex!=0 && szThreshold<0 ){ + /* Do analysis if unanalyzed indexes exists */ + }else{ + /* Otherwise, we can skip this table */ + continue; + } + + nCheck++; + if( nCheck==2 ){ + /* If ANALYZE might be invoked two or more times, hold a write + ** transaction for efficiency */ + sqlite3BeginWriteOperation(pParse, 0, iDb); + } + nBtree += nIndex+1; + + /* Reanalyze if the table is 10 times larger or smaller than + ** the last analysis. Unconditional reanalysis if there are + ** unanalyzed indexes. */ + sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); + if( szThreshold>=0 ){ + const LogEst iRange = 33; /* 10x size change */ + sqlite3VdbeAddOp4Int(v, OP_IfSizeBetween, iTabCur, + sqlite3VdbeCurrentAddr(v)+2+(opMask&1), + szThreshold>=iRange ? szThreshold-iRange : -1, + szThreshold+iRange); + VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp2(v, OP_Rewind, iTabCur, + sqlite3VdbeCurrentAddr(v)+2+(opMask&1)); VdbeCoverage(v); } zSubSql = sqlite3MPrintf(db, "ANALYZE \"%w\".\"%w\"", @@ -2473,11 +2586,27 @@ void sqlite3Pragma( sqlite3VdbeAddOp4(v, OP_String8, 0, r1, 0, zSubSql, P4_DYNAMIC); sqlite3VdbeAddOp2(v, OP_ResultRow, r1, 1); }else{ - sqlite3VdbeAddOp4(v, OP_SqlExec, 0, 0, 0, zSubSql, P4_DYNAMIC); + sqlite3VdbeAddOp4(v, OP_SqlExec, nLimit ? 0x02 : 00, nLimit, 0, + zSubSql, P4_DYNAMIC); } } } sqlite3VdbeAddOp0(v, OP_Expire); + + /* In a schema with a large number of tables and indexes, scale back + ** the analysis_limit to avoid excess run-time in the worst case. + */ + if( !db->mallocFailed && nLimit>0 && nBtree>100 ){ + int iAddr, iEnd; + VdbeOp *aOp; + nLimit = 100*nLimit/nBtree; + if( nLimit<100 ) nLimit = 100; + aOp = sqlite3VdbeGetOp(v, 0); + iEnd = sqlite3VdbeCurrentAddr(v); + for(iAddr=0; iAddrnConstraint; i++, pConstraint++){ - if( pConstraint->usable==0 ) continue; - if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; if( pConstraint->iColumn < pTab->iHidden ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->usable==0 ) return SQLITE_CONSTRAINT; j = pConstraint->iColumn - pTab->iHidden; assert( j < 2 ); seen[j] = i+1; @@ -2756,16 +2885,13 @@ static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ j = seen[0]-1; pIdxInfo->aConstraintUsage[j].argvIndex = 1; pIdxInfo->aConstraintUsage[j].omit = 1; - if( seen[1]==0 ){ - pIdxInfo->estimatedCost = (double)1000; - pIdxInfo->estimatedRows = 1000; - return SQLITE_OK; - } pIdxInfo->estimatedCost = (double)20; pIdxInfo->estimatedRows = 20; - j = seen[1]-1; - pIdxInfo->aConstraintUsage[j].argvIndex = 2; - pIdxInfo->aConstraintUsage[j].omit = 1; + if( seen[1] ){ + j = seen[1]-1; + pIdxInfo->aConstraintUsage[j].argvIndex = 2; + pIdxInfo->aConstraintUsage[j].omit = 1; + } return SQLITE_OK; } @@ -2785,6 +2911,7 @@ static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){ int i; sqlite3_finalize(pCsr->pPragma); pCsr->pPragma = 0; + pCsr->iRowid = 0; for(i=0; iazArg); i++){ sqlite3_free(pCsr->azArg[i]); pCsr->azArg[i] = 0; diff --git a/src/prepare.c b/src/prepare.c index 87569ee91..df9c98f74 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -633,7 +633,13 @@ void *sqlite3ParserAddCleanup( void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */ void *pPtr /* Pointer to object to be cleaned up */ ){ - ParseCleanup *pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup)); + ParseCleanup *pCleanup; + if( sqlite3FaultSim(300) ){ + pCleanup = 0; + sqlite3OomFault(pParse->db); + }else{ + pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup)); + } if( pCleanup ){ pCleanup->pNext = pParse->pCleanup; pParse->pCleanup = pCleanup; diff --git a/src/printf.c b/src/printf.c index 2e09431bf..c0dcc5d0f 100644 --- a/src/printf.c +++ b/src/printf.c @@ -534,13 +534,14 @@ void sqlite3_str_vappendf( } exp = s.iDP-1; - if( xtype==etGENERIC && precision>0 ) precision--; /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ if( xtype==etGENERIC ){ + assert( precision>0 ); + precision--; flag_rtz = !flag_alternateform; if( exp<-4 || exp>precision ){ xtype = etEXP; @@ -856,9 +857,13 @@ void sqlite3_str_vappendf( sqlite3_str_appendall(pAccum, pItem->zAlias); }else{ Select *pSel = pItem->pSelect; - assert( pSel!=0 ); + assert( pSel!=0 ); /* Because of tag-20240424-1 */ if( pSel->selFlags & SF_NestedFrom ){ sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId); + }else if( pSel->selFlags & SF_MultiValue ){ + assert( !pItem->fg.isTabFunc && !pItem->fg.isIndexedBy ); + sqlite3_str_appendf(pAccum, "%u-ROW VALUES CLAUSE", + pItem->u1.nRow); }else{ sqlite3_str_appendf(pAccum, "(subquery-%u)", pSel->selId); } diff --git a/src/resolve.c b/src/resolve.c index fdf260d8c..bf8326aa6 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -279,7 +279,7 @@ static int lookupName( Parse *pParse, /* The parsing context */ const char *zDb, /* Name of the database containing table, or NULL */ const char *zTab, /* Name of table containing column, or NULL */ - const char *zCol, /* Name of the column. */ + const Expr *pRight, /* Name of the column. */ NameContext *pNC, /* The name context used to resolve the name */ Expr *pExpr /* Make this EXPR node point to the selected column */ ){ @@ -296,6 +296,7 @@ static int lookupName( Table *pTab = 0; /* Table holding the row */ Column *pCol; /* A column of pTab */ ExprList *pFJMatch = 0; /* Matches for FULL JOIN .. USING */ + const char *zCol = pRight->u.zToken; assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ @@ -527,7 +528,8 @@ static int lookupName( if( pParse->bReturning ){ if( (pNC->ncFlags & NC_UBaseReg)!=0 && ALWAYS(zTab==0 - || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0) + || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0 + || isValidSchemaTableName(zTab, pParse->pTriggerTab, 0)) ){ pExpr->iTable = op!=TK_DELETE; pTab = pParse->pTriggerTab; @@ -631,6 +633,11 @@ static int lookupName( && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom) ){ cnt = cntTab; +#if SQLITE_ALLOW_ROWID_IN_VIEW+0==2 + if( pMatch->pTab!=0 && IsView(pMatch->pTab) ){ + eNewExprOp = TK_NULL; + } +#endif if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1; pExpr->affExpr = SQLITE_AFF_INTEGER; } @@ -784,6 +791,10 @@ static int lookupName( sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol); }else if( zTab ){ sqlite3ErrorMsg(pParse, "%s: %s.%s", zErr, zTab, zCol); + }else if( cnt==0 && ExprHasProperty(pRight,EP_DblQuoted) ){ + sqlite3ErrorMsg(pParse, "%s: \"%s\" - should this be a" + " string literal in single-quotes?", + zErr, zCol); }else{ sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol); } @@ -817,8 +828,12 @@ static int lookupName( ** If a generated column is referenced, set bits for every column ** of the table. */ - if( pExpr->iColumn>=0 && cnt==1 && pMatch!=0 ){ - pMatch->colUsed |= sqlite3ExprColUsed(pExpr); + if( pMatch ){ + if( pExpr->iColumn>=0 ){ + pMatch->colUsed |= sqlite3ExprColUsed(pExpr); + }else{ + pMatch->fg.rowidUsed = 1; + } } pExpr->op = eNewExprOp; @@ -1061,7 +1076,6 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ */ case TK_ID: case TK_DOT: { - const char *zColumn; const char *zTable; const char *zDb; Expr *pRight; @@ -1070,7 +1084,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ zDb = 0; zTable = 0; assert( !ExprHasProperty(pExpr, EP_IntValue) ); - zColumn = pExpr->u.zToken; + pRight = pExpr; }else{ Expr *pLeft = pExpr->pLeft; testcase( pNC->ncFlags & NC_IdxExpr ); @@ -1089,14 +1103,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } assert( ExprUseUToken(pLeft) && ExprUseUToken(pRight) ); zTable = pLeft->u.zToken; - zColumn = pRight->u.zToken; assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, (void*)pExpr, (void*)pRight); sqlite3RenameTokenRemap(pParse, (void*)&pExpr->y.pTab, (void*)pLeft); } } - return lookupName(pParse, zDb, zTable, zColumn, pNC, pExpr); + return lookupName(pParse, zDb, zTable, pRight, pNC, pExpr); } /* Resolve function names @@ -1272,11 +1285,9 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #endif } } -#ifndef SQLITE_OMIT_WINDOWFUNC - else if( ExprHasProperty(pExpr, EP_WinFunc) ){ + else if( ExprHasProperty(pExpr, EP_WinFunc) || pExpr->pLeft ){ is_agg = 1; } -#endif sqlite3WalkExprList(pWalker, pList); if( is_agg ){ if( pExpr->pLeft ){ @@ -1346,6 +1357,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ testcase( pNC->ncFlags & NC_PartIdx ); testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); + assert( pExpr->x.pSelect ); if( pNC->ncFlags & NC_SelfRef ){ notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr); }else{ @@ -1354,6 +1366,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pNC->nRef>=nRef ); if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); + pExpr->x.pSelect->selFlags |= SF_Correlated; } pNC->ncFlags |= NC_Subquery; } @@ -1879,6 +1892,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ if( pOuterNC ) pOuterNC->nNestedSelect++; for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; + assert( pItem->zName!=0 || pItem->pSelect!=0 );/* Test of tag-20240424-1*/ if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){ int nRef = pOuterNC ? pOuterNC->nRef : 0; const char *zSavedContext = pParse->zAuthContext; diff --git a/src/select.c b/src/select.c index fdc7f5e67..9278ea18b 100644 --- a/src/select.c +++ b/src/select.c @@ -1673,9 +1673,16 @@ static void generateSortTail( int addrExplain; /* Address of OP_Explain instruction */ #endif - ExplainQueryPlan2(addrExplain, (pParse, 0, - "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat>0?"RIGHT PART OF ":"") - ); + nKey = pOrderBy->nExpr - pSort->nOBSat; + if( pSort->nOBSat==0 || nKey==1 ){ + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat?"LAST TERM OF ":"" + )); + }else{ + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR LAST %d TERMS OF ORDER BY", nKey + )); + } sqlite3VdbeScanStatusRange(v, addrExplain,pSort->addrPush,pSort->addrPushEnd); sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, pSort->addrPush); @@ -1713,7 +1720,6 @@ static void generateSortTail( regRow = sqlite3GetTempRange(pParse, nColumn); } } - nKey = pOrderBy->nExpr - pSort->nOBSat; if( pSort->sortFlags & SORTFLAG_UseSorter ){ int regSortOut = ++pParse->nMem; iSortTab = pParse->nTab++; @@ -2318,8 +2324,7 @@ void sqlite3SubqueryColumnTypes( NameContext sNC; assert( pSelect!=0 ); - testcase( (pSelect->selFlags & SF_Resolved)==0 ); - assert( (pSelect->selFlags & SF_Resolved)!=0 || IN_RENAME_OBJECT ); + assert( (pSelect->selFlags & SF_Resolved)!=0 ); assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 ); assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); if( db->mallocFailed || IN_RENAME_OBJECT ) return; @@ -2330,17 +2335,22 @@ void sqlite3SubqueryColumnTypes( for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; i64 n; + int m = 0; + Select *pS2 = pSelect; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); + while( pCol->affinity<=SQLITE_AFF_NONE && pS2->pNext!=0 ){ + m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); + pS2 = pS2->pNext; + pCol->affinity = sqlite3ExprAffinity(pS2->pEList->a[i].pExpr); + } if( pCol->affinity<=SQLITE_AFF_NONE ){ pCol->affinity = aff; } - if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ - int m = 0; - Select *pS2; - for(m=0, pS2=pSelect->pNext; pS2; pS2=pS2->pNext){ + if( pCol->affinity>=SQLITE_AFF_TEXT && (pS2->pNext || pS2!=pSelect) ){ + for(pS2=pS2->pNext; pS2; pS2=pS2->pNext){ m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); } if( pCol->affinity==SQLITE_AFF_TEXT && (m&0x01)!=0 ){ @@ -2370,12 +2380,12 @@ void sqlite3SubqueryColumnTypes( } } if( zType ){ - i64 m = sqlite3Strlen30(zType); + const i64 k = sqlite3Strlen30(zType); n = sqlite3Strlen30(pCol->zCnName); - pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+k+2); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); if( pCol->zCnName ){ - memcpy(&pCol->zCnName[n+1], zType, m+1); + memcpy(&pCol->zCnName[n+1], zType, k+1); pCol->colFlags |= COLFLAG_HASTYPE; } } @@ -4772,7 +4782,7 @@ static void constInsert( ){ int i; assert( pColumn->op==TK_COLUMN ); - assert( sqlite3ExprIsConstant(pValue) ); + assert( sqlite3ExprIsConstant(pConst->pParse, pValue) ); if( ExprHasProperty(pColumn, EP_FixedCol) ) return; if( sqlite3ExprAffinity(pValue)!=0 ) return; @@ -4830,10 +4840,10 @@ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ pLeft = pExpr->pLeft; assert( pRight!=0 ); assert( pLeft!=0 ); - if( pRight->op==TK_COLUMN && sqlite3ExprIsConstant(pLeft) ){ + if( pRight->op==TK_COLUMN && sqlite3ExprIsConstant(pConst->pParse, pLeft) ){ constInsert(pConst,pRight,pLeft,pExpr); } - if( pLeft->op==TK_COLUMN && sqlite3ExprIsConstant(pRight) ){ + if( pLeft->op==TK_COLUMN && sqlite3ExprIsConstant(pConst->pParse, pRight) ){ constInsert(pConst,pLeft,pRight,pExpr); } } @@ -5054,6 +5064,18 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** The hope is that the terms added to the inner query will make it more ** efficient. ** +** NAME AMBIGUITY +** +** This optimization is called the "WHERE-clause push-down optimization". +** +** Do not confuse this optimization with another unrelated optimization +** with a similar name: The "MySQL push-down optimization" causes WHERE +** clause terms that can be evaluated using only the index and without +** reference to the table are run first, so that if they are false, +** unnecessary table seeks are avoided. +** +** RULES +** ** Do not attempt this optimization if: ** ** (1) (** This restriction was removed on 2017-09-29. We used to @@ -5119,10 +5141,10 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** (9c) There is a RIGHT JOIN (or FULL JOIN) in between the ON/USING ** clause and the subquery. ** -** Without this restriction, the push-down optimization might move -** the ON/USING filter expression from the left side of a RIGHT JOIN -** over to the right side, which leads to incorrect answers. See -** also restriction (6) in sqlite3ExprIsSingleTableConstraint(). +** Without this restriction, the WHERE-clause push-down optimization +** might move the ON/USING filter expression from the left side of a +** RIGHT JOIN over to the right side, which leads to incorrect answers. +** See also restriction (6) in sqlite3ExprIsSingleTableConstraint(). ** ** (10) The inner query is not the right-hand table of a RIGHT JOIN. ** @@ -5254,7 +5276,7 @@ static int pushDownWhereTerms( } #endif - if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){ + if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc, 1) ){ nChng++; pSubq->selFlags |= SF_PushDown; while( pSubq ){ @@ -6389,8 +6411,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ if( p->selFlags & SF_HasTypeInfo ) return; p->selFlags |= SF_HasTypeInfo; pParse = pWalker->pParse; - testcase( (p->selFlags & SF_Resolved)==0 ); - assert( (p->selFlags & SF_Resolved) || IN_RENAME_OBJECT ); + assert( (p->selFlags & SF_Resolved) ); pTabList = p->pSrc; for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; @@ -6460,6 +6481,8 @@ void sqlite3SelectPrep( */ static void printAggInfo(AggInfo *pAggInfo){ int ii; + sqlite3DebugPrintf("AggInfo %d/%p:\n", + pAggInfo->selId, pAggInfo); for(ii=0; iinColumn; ii++){ struct AggInfo_col *pCol = &pAggInfo->aCol[ii]; sqlite3DebugPrintf( @@ -7650,7 +7673,7 @@ int sqlite3Select( /* Generate code for all sub-queries in the FROM clause */ pSub = pItem->pSelect; - if( pSub==0 ) continue; + if( pSub==0 || pItem->addrFillSub!=0 ) continue; /* The code for a subquery should only be generated once. */ assert( pItem->addrFillSub==0 ); @@ -7681,7 +7704,7 @@ int sqlite3Select( #endif assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 ); }else{ - TREETRACE(0x4000,pParse,p,("Push-down not possible\n")); + TREETRACE(0x4000,pParse,p,("WHERE-lcause push-down not possible\n")); } /* Convert unused result columns of the subquery into simple NULL @@ -8562,6 +8585,12 @@ int sqlite3Select( sqlite3ExprListDelete(db, pMinMaxOrderBy); #ifdef SQLITE_DEBUG if( pAggInfo && !db->mallocFailed ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20,pParse,p,("Finished with AggInfo\n")); + printAggInfo(pAggInfo); + } +#endif for(i=0; inColumn; i++){ Expr *pExpr = pAggInfo->aCol[i].pCExpr; if( pExpr==0 ) continue; diff --git a/src/shell.c.in b/src/shell.c.in index 1220b421d..0029682f3 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -268,6 +268,9 @@ INCLUDE ../ext/consio/console_io.c * setOutputStream(FILE *pf) * This is normally the stream that CLI normal output goes to. * For the stand-alone CLI, it is stdout with no .output redirect. + * + * The ?putz(z) forms are required for the Fiddle builds for string literal + * output, in aid of enforcing format string to argument correspondence. */ # define sputz(s,z) fPutsUtf8(z,s) # define sputf fPrintfUtf8 @@ -279,12 +282,18 @@ INCLUDE ../ext/consio/console_io.c #else /* For Fiddle, all console handling and emit redirection is omitted. */ -# define sputz(fp,z) fputs(z,fp) +/* These next 3 macros are for emitting formatted output. When complaints + * from the WASM build are issued for non-formatted output, (when a mere + * string literal is to be emitted, the ?putz(z) forms should be used. + * (This permits compile-time checking of format string / argument mismatch.) + */ +# define oputf(fmt, ...) printf(fmt,__VA_ARGS__) +# define eputf(fmt, ...) fprintf(stderr,fmt,__VA_ARGS__) # define sputf(fp,fmt, ...) fprintf(fp,fmt,__VA_ARGS__) +/* These next 3 macros are for emitting simple string literals. */ # define oputz(z) fputs(z,stdout) -# define oputf(fmt, ...) printf(fmt,__VA_ARGS__) # define eputz(z) fputs(z,stderr) -# define eputf(fmt, ...) fprintf(stderr,fmt,__VA_ARGS__) +# define sputz(fp,z) fputs(z,fp) # define oputb(buf,na) fwrite(buf,1,na,stdout) #endif @@ -1213,6 +1222,9 @@ INCLUDE ../ext/misc/sqlar.c INCLUDE ../ext/expert/sqlite3expert.h INCLUDE ../ext/expert/sqlite3expert.c +INCLUDE ../ext/intck/sqlite3intck.h +INCLUDE ../ext/intck/sqlite3intck.c + #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) #define SQLITE_SHELL_HAVE_RECOVER 1 #else @@ -4732,6 +4744,7 @@ static const char *(azHelp[]) = { ".indexes ?TABLE? Show names of indexes", " If TABLE is specified, only show indexes for", " tables matching TABLE using the LIKE operator.", + ".intck ?STEPS_PER_UNLOCK? Run an incremental integrity check on the db", #ifdef SQLITE_ENABLE_IOTRACE ",iotrace FILE Enable I/O diagnostic logging to FILE", #endif @@ -7641,6 +7654,40 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ } #endif /* SQLITE_SHELL_HAVE_RECOVER */ +/* +** Implementation of ".intck STEPS_PER_UNLOCK" command. +*/ +static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){ + sqlite3_intck *p = 0; + int rc = SQLITE_OK; + + rc = sqlite3_intck_open(pState->db, "main", &p); + if( rc==SQLITE_OK ){ + i64 nStep = 0; + i64 nError = 0; + const char *zErr = 0; + while( SQLITE_OK==sqlite3_intck_step(p) ){ + const char *zMsg = sqlite3_intck_message(p); + if( zMsg ){ + oputf("%s\n", zMsg); + nError++; + } + nStep++; + if( nStepPerUnlock && (nStep % nStepPerUnlock)==0 ){ + sqlite3_intck_unlock(p); + } + } + rc = sqlite3_intck_error(p, &zErr); + if( zErr ){ + eputf("%s\n", zErr); + } + sqlite3_intck_close(p); + + oputf("%lld steps, %lld errors\n", nStep, nError); + } + + return rc; +} /* * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it. @@ -7884,6 +7931,45 @@ static int outputDumpWarning(ShellState *p, const char *zLike){ return rc; } +/* +** Fault-Simulator state and logic. +*/ +static struct { + int iId; /* ID that triggers a simulated fault. -1 means "any" */ + int iErr; /* The error code to return on a fault */ + int iCnt; /* Trigger the fault only if iCnt is already zero */ + int iInterval; /* Reset iCnt to this value after each fault */ + int eVerbose; /* When to print output */ + int nHit; /* Number of hits seen so far */ + int nRepeat; /* Turn off after this many hits. 0 for never */ + int nSkip; /* Skip this many before first fault */ +} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0}; + +/* +** This is the fault-sim callback +*/ +static int faultsim_callback(int iArg){ + if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){ + return SQLITE_OK; + } + if( faultsim_state.iCnt ){ + if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--; + if( faultsim_state.eVerbose>=2 ){ + oputf("FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt); + } + return SQLITE_OK; + } + if( faultsim_state.eVerbose>=1 ){ + oputf("FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr); + } + faultsim_state.iCnt = faultsim_state.iInterval; + faultsim_state.nHit++; + if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){ + faultsim_state.iCnt = -1; + } + return faultsim_state.iErr; +} + /* ** If an input line begins with "." then invoke this routine to ** process that line. @@ -8375,7 +8461,8 @@ static int do_meta_command(char *zLine, ShellState *p){ zSql = sqlite3_mprintf( "SELECT sql FROM sqlite_schema AS o " "WHERE (%s) AND sql NOT NULL" - " AND type IN ('index','trigger','view')", + " AND type IN ('index','trigger','view') " + "ORDER BY type COLLATE NOCASE DESC", zLike ); run_table_dump_query(p, zSql); @@ -9110,6 +9197,21 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ + if( c=='i' && cli_strncmp(azArg[0], "intck", n)==0 ){ + i64 iArg = 0; + if( nArg==2 ){ + iArg = integerValue(azArg[1]); + if( iArg==0 ) iArg = -1; + } + if( (nArg!=1 && nArg!=2) || iArg<0 ){ + eputf("%s","Usage: .intck STEPS_PER_UNLOCK\n"); + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + rc = intckDatabaseCmd(p, iArg); + }else + #ifdef SQLITE_ENABLE_IOTRACE if( c=='i' && cli_strncmp(azArg[0], "iotrace", n)==0 ){ SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); @@ -10783,7 +10885,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /*{"bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ {"byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, {"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, - /*{"fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ + {"fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"args..." }, {"fk_no_action", SQLITE_TESTCTRL_FK_NO_ACTION, 0, "BOOLEAN" }, {"imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, @@ -11016,6 +11118,76 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_test_control(testctrl, &rc2); break; + case SQLITE_TESTCTRL_FAULT_INSTALL: { + int kk; + int bShowHelp = nArg<=2; + isOk = 3; + for(kk=2; kk0 ) faultsim_state.eVerbose--; + }else if( cli_strcmp(z,"-id")==0 && kk+1=0 ){ @@ -11938,7 +12110,7 @@ static void usage(int showDetail){ }else{ eputz("Use the -help option for additional information\n"); } - exit(1); + exit(0); } /* @@ -12638,6 +12810,11 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #ifndef SQLITE_SHELL_FIDDLE /* In WASM mode we have to leave the db state in place so that ** client code can "push" SQL into it after this call returns. */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( data.expert.pExpert ){ + expertFinish(&data, 1, 0); + } +#endif free(azCmd); set_table_name(&data, 0); if( data.db ){ @@ -12704,7 +12881,7 @@ sqlite3_vfs * fiddle_db_vfs(const char *zDbName){ /* Only for emcc experimentation purposes. */ sqlite3 * fiddle_db_arg(sqlite3 *arg){ - printf("fiddle_db_arg(%p)\n", (const void*)arg); + oputf("fiddle_db_arg(%p)\n", (const void*)arg); return arg; } @@ -12730,12 +12907,22 @@ const char * fiddle_db_filename(const char * zDbName){ /* ** Completely wipes out the contents of the currently-opened database -** but leaves its storage intact for reuse. +** but leaves its storage intact for reuse. If any transactions are +** active, they are forcibly rolled back. */ void fiddle_reset_db(void){ if( globalDb ){ - int rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); - if( 0==rc ) rc = sqlite3_exec(globalDb, "VACUUM", 0, 0, 0); + int rc; + while( sqlite3_txn_state(globalDb,0)>0 ){ + /* + ** Resolve problem reported in + ** https://sqlite.org/forum/forumpost/0b41a25d65 + */ + oputz("Rolling back in-progress transaction.\n"); + sqlite3_exec(globalDb,"ROLLBACK", 0, 0, 0); + } + rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); + if( 0==rc ) sqlite3_exec(globalDb, "VACUUM", 0, 0, 0); sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); } } diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 7acdde872..549b52a15 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -764,11 +764,11 @@ struct sqlite3_file { ** ** xLock() upgrades the database file lock. In other words, xLock() moves the ** database file lock in the direction NONE toward EXCLUSIVE. The argument to -** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never +** xLock() is always one of SHARED, RESERVED, PENDING, or EXCLUSIVE, never ** SQLITE_LOCK_NONE. If the database file lock is already at or above the ** requested lock, then the call to xLock() is a no-op. ** xUnlock() downgrades the database file lock to either SHARED or NONE. -* If the lock is already at or below the requested lock state, then the call +** If the lock is already at or below the requested lock state, then the call ** to xUnlock() is a no-op. ** The xCheckReservedLock() method checks whether any database connection, ** either in this process or in some other process, is holding a RESERVED, @@ -3305,8 +3305,8 @@ int sqlite3_set_authorizer( #define SQLITE_RECURSIVE 33 /* NULL NULL */ /* -** CAPI3REF: Tracing And Profiling Functions -** METHOD: sqlite3 +** CAPI3REF: Deprecated Tracing And Profiling Functions +** DEPRECATED ** ** These routines are deprecated. Use the [sqlite3_trace_v2()] interface ** instead of the routines described here. @@ -6887,6 +6887,12 @@ int sqlite3_autovacuum_pages( ** The exceptions defined in this paragraph might change in a future ** release of SQLite. ** +** Whether the update hook is invoked before or after the +** corresponding change is currently unspecified and may differ +** depending on the type of change. Do not rely on the order of the +** hook call with regards to the final result of the operation which +** triggers the hook. +** ** The update hook implementation must not do anything that will modify ** the database connection that invoked the update hook. Any actions ** to modify the database connection must be deferred until after the @@ -8357,7 +8363,7 @@ int sqlite3_test_control(int op, ...); ** The sqlite3_keyword_count() interface returns the number of distinct ** keywords understood by SQLite. ** -** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and +** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and ** makes *Z point to that keyword expressed as UTF8 and writes the number ** of bytes in the keyword into *L. The string that *Z points to is not ** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns @@ -9936,24 +9942,45 @@ const char *sqlite3_vtab_collation(sqlite3_index_info*,int); **
  • ** ^(If the sqlite3_vtab_distinct() interface returns 2, that means ** that the query planner does not need the rows returned in any particular -** order, as long as rows with the same values in all "aOrderBy" columns -** are adjacent.)^ ^(Furthermore, only a single row for each particular -** combination of values in the columns identified by the "aOrderBy" field -** needs to be returned.)^ ^It is always ok for two or more rows with the same -** values in all "aOrderBy" columns to be returned, as long as all such rows -** are adjacent. ^The virtual table may, if it chooses, omit extra rows -** that have the same value for all columns identified by "aOrderBy". -** ^However omitting the extra rows is optional. +** order, as long as rows with the same values in all columns identified +** by "aOrderBy" are adjacent.)^ ^(Furthermore, when two or more rows +** contain the same values for all columns identified by "colUsed", all but +** one such row may optionally be omitted from the result.)^ +** The virtual table is not required to omit rows that are duplicates +** over the "colUsed" columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. ** This mode is used for a DISTINCT query. **

  • -** ^(If the sqlite3_vtab_distinct() interface returns 3, that means -** that the query planner needs only distinct rows but it does need the -** rows to be sorted.)^ ^The virtual table implementation is free to omit -** rows that are identical in all aOrderBy columns, if it wants to, but -** it is not required to omit any rows. This mode is used for queries +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means the +** virtual table must return rows in the order defined by "aOrderBy" as +** if the sqlite3_vtab_distinct() interface had returned 0. However if +** two or more rows in the result have the same values for all columns +** identified by "colUsed", then all but one such row may optionally be +** omitted.)^ Like when the return value is 2, the virtual table +** is not required to omit rows that are duplicates over the "colUsed" +** columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. +** This mode is used for queries ** that have both DISTINCT and ORDER BY clauses. ** ** +**

    The following table summarizes the conditions under which the +** virtual table is allowed to set the "orderByConsumed" flag based on +** the value returned by sqlite3_vtab_distinct(). This table is a +** restatement of the previous four paragraphs: +** +** +** +**
    sqlite3_vtab_distinct() return value +** Rows are returned in aOrderBy order +** Rows with the same value in all aOrderBy columns are adjacent +** Duplicates over all colUsed columns may be omitted +**
    0yesyesno +**
    1noyesno +**
    2noyesyes +**
    3yesyesyes +**
    +** ** ^For the purposes of comparing virtual table output values to see if the ** values are same value for sorting purposes, two NULL values are considered ** to be the same. In other words, the comparison operator is "IS" diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 933286bfe..d98a4f7f0 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -609,6 +609,8 @@ # define SQLITE_OMIT_ALTERTABLE #endif +#define SQLITE_DIGIT_SEPARATOR '_' + /* ** Return true (non-zero) if the input is an integer that is too large ** to fit in 32-bits. This macro is used inside of various testcase() @@ -886,7 +888,7 @@ typedef INT16_TYPE LogEst; # define SQLITE_PTRSIZE __SIZEOF_POINTER__ # elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ defined(_M_ARM) || defined(__arm__) || defined(__x86) || \ - (defined(__APPLE__) && defined(__POWERPC__)) || \ + (defined(__APPLE__) && defined(__ppc__)) || \ (defined(__TOS_AIX__) && !defined(__64BIT__)) # define SQLITE_PTRSIZE 4 # else @@ -1154,7 +1156,7 @@ extern u32 sqlite3WhereTrace; ** 0x00000010 Display sqlite3_index_info xBestIndex calls ** 0x00000020 Range an equality scan metrics ** 0x00000040 IN operator decisions -** 0x00000080 WhereLoop cost adjustements +** 0x00000080 WhereLoop cost adjustments ** 0x00000100 ** 0x00000200 Covering index decisions ** 0x00000400 OR optimization @@ -1597,6 +1599,10 @@ struct FuncDefHash { }; #define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ) +#if defined(SQLITE_USER_AUTHENTICATION) +# warning "The SQLITE_USER_AUTHENTICATION extension is deprecated. \ + See ext/userauth/user-auth.txt for details." +#endif #ifdef SQLITE_USER_AUTHENTICATION /* ** Information held in the "sqlite3" database connection object and used @@ -1900,7 +1906,7 @@ struct sqlite3 { #define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */ #define SQLITE_Stat4 0x00000800 /* Use STAT4 data */ /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */ -#define SQLITE_PushDown 0x00001000 /* The push-down optimization */ +#define SQLITE_PushDown 0x00001000 /* WHERE-clause push-down opt */ #define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */ #define SQLITE_SkipScan 0x00004000 /* Skip-scans */ #define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */ @@ -2473,8 +2479,7 @@ struct Table { #define TF_HasStored 0x00000040 /* Has one or more STORED columns */ #define TF_HasGenerated 0x00000060 /* Combo: HasVirtual + HasStored */ #define TF_WithoutRowid 0x00000080 /* No rowid. PRIMARY KEY is the key */ -#define TF_StatsUsed 0x00000100 /* Query planner decisions affected by - ** Index.aiRowLogEst[] values */ +#define TF_MaybeReanalyze 0x00000100 /* Maybe run ANALYZE on this table */ #define TF_NoVisibleRowid 0x00000200 /* No user-visible "rowid" column */ #define TF_OOOHidden 0x00000400 /* Out-of-Order hidden columns */ #define TF_HasNotNull 0x00000800 /* Contains NOT NULL constraints */ @@ -3274,10 +3279,12 @@ struct IdList { ** ** Union member validity: ** -** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc -** u1.pFuncArg fg.isTabFunc && !fg.isIndexedBy -** u2.pIBIndex fg.isIndexedBy && !fg.isCte -** u2.pCteUse fg.isCte && !fg.isIndexedBy +** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc +** u1.pFuncArg fg.isTabFunc && !fg.isIndexedBy +** u1.nRow !fg.isTabFunc && !fg.isIndexedBy +** +** u2.pIBIndex fg.isIndexedBy && !fg.isCte +** u2.pCteUse fg.isCte && !fg.isIndexedBy */ struct SrcItem { Schema *pSchema; /* Schema to which this item is fixed */ @@ -3305,6 +3312,7 @@ struct SrcItem { unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ unsigned isSynthUsing :1; /* u3.pUsing is synthesized from NATURAL */ unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ + unsigned rowidUsed :1; /* The ROWID of this table is referenced */ } fg; int iCursor; /* The VDBE cursor number used to access this table */ union { @@ -3315,6 +3323,7 @@ struct SrcItem { union { char *zIndexedBy; /* Identifier from "INDEXED BY " clause */ ExprList *pFuncArg; /* Arguments to table-valued-function */ + u32 nRow; /* Number of rows in a VALUES clause */ } u1; union { Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ @@ -3572,11 +3581,12 @@ struct Select { #define SF_View 0x0200000 /* SELECT statement is a view */ #define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ #define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ -#define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ +#define SF_PushDown 0x1000000 /* Modified by WHERE-clause push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ #define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ +#define SF_Correlated 0x20000000 /* True if references the outer context */ /* True if S exists and has SF_NestedFrom */ #define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) @@ -3816,6 +3826,7 @@ struct Parse { u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ + u8 bHasWith; /* True if statement contains WITH */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif @@ -4495,6 +4506,9 @@ struct Window { ** due to the SQLITE_SUBTYPE flag */ }; +Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow); +void sqlite3MultiValuesEnd(Parse *pParse, Select *pVal); + #ifndef SQLITE_OMIT_WINDOWFUNC void sqlite3WindowDelete(sqlite3*, Window*); void sqlite3WindowUnlinkFromSelect(Window*); @@ -4812,6 +4826,7 @@ int sqlite3ErrorToParser(sqlite3*,int); void sqlite3Dequote(char*); void sqlite3DequoteExpr(Expr*); void sqlite3DequoteToken(Token*); +void sqlite3DequoteNumber(Parse*, Expr*); void sqlite3TokenInit(Token*,char*); int sqlite3KeywordCode(const unsigned char*, int); int sqlite3RunParser(Parse*, const char*); @@ -4842,7 +4857,7 @@ void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); void sqlite3ExprDelete(sqlite3*, Expr*); void sqlite3ExprDeleteGeneric(sqlite3*,void*); -void sqlite3ExprDeferredDelete(Parse*, Expr*); +int sqlite3ExprDeferredDelete(Parse*, Expr*); void sqlite3ExprUnmapAndDelete(Parse*, Expr*); ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); @@ -5065,12 +5080,10 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3*); u32 sqlite3IsTrueOrFalse(const char*); int sqlite3ExprIdToTrueFalse(Expr*); int sqlite3ExprTruthValue(const Expr*); -int sqlite3ExprIsConstant(Expr*); -int sqlite3ExprIsConstantNotJoin(Expr*); +int sqlite3ExprIsConstant(Parse*,Expr*); int sqlite3ExprIsConstantOrFunction(Expr*, u8); int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); -int sqlite3ExprIsTableConstant(Expr*,int); -int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int); +int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int); #ifdef SQLITE_ENABLE_CURSOR_HINTS int sqlite3ExprContainsSubquery(Expr*); #endif @@ -5255,7 +5268,9 @@ void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); void sqlite3Error(sqlite3*,int); void sqlite3ErrorClear(sqlite3*); void sqlite3SystemError(sqlite3*,int); +#if !defined(SQLITE_OMIT_BLOB_LITERAL) void *sqlite3HexToBlob(sqlite3*, const char *z, int n); +#endif u8 sqlite3HexToInt(int h); int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); diff --git a/src/test_bestindex.c b/src/test_bestindex.c index 8128530b4..0e1e86a81 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -215,6 +215,9 @@ static int tclConnect( rc = SQLITE_ERROR; }else{ rc = sqlite3_declare_vtab(db, Tcl_GetStringResult(interp)); + if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("declare_vtab: %s", sqlite3_errmsg(db)); + } } if( rc!=SQLITE_OK ){ @@ -226,7 +229,7 @@ static int tclConnect( } sqlite3_free(zCmd); - *ppVtab = &pTab->base; + *ppVtab = pTab ? &pTab->base : 0; return rc; } diff --git a/src/test_tclsh.c b/src/test_tclsh.c index 32aee4267..4697c3b85 100644 --- a/src/test_tclsh.c +++ b/src/test_tclsh.c @@ -108,6 +108,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ extern int Sqlitetest_window_Init(Tcl_Interp *); extern int Sqlitetestvdbecov_Init(Tcl_Interp *); extern int TestRecover_Init(Tcl_Interp*); + extern int Sqlitetestintck_Init(Tcl_Interp*); Tcl_CmdInfo cmdInfo; @@ -175,6 +176,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ Sqlitetest_window_Init(interp); Sqlitetestvdbecov_Init(interp); TestRecover_Init(interp); + Sqlitetestintck_Init(interp); Tcl_CreateObjCommand( interp, "load_testfixture_extensions", load_testfixture_extensions,0,0 diff --git a/src/tokenize.c b/src/tokenize.c index f4d013dee..65d1fbf35 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -437,27 +437,58 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ *tokenType = TK_INTEGER; #ifndef SQLITE_OMIT_HEX_INTEGER if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ - for(i=3; sqlite3Isxdigit(z[i]); i++){} - return i; - } + for(i=3; 1; i++){ + if( sqlite3Isxdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } + }else #endif - for(i=0; sqlite3Isdigit(z[i]); i++){} + { + for(i=0; 1; i++){ + if( sqlite3Isdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } #ifndef SQLITE_OMIT_FLOATING_POINT - if( z[i]=='.' ){ - i++; - while( sqlite3Isdigit(z[i]) ){ i++; } - *tokenType = TK_FLOAT; - } - if( (z[i]=='e' || z[i]=='E') && - ( sqlite3Isdigit(z[i+1]) - || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2])) - ) - ){ - i += 2; - while( sqlite3Isdigit(z[i]) ){ i++; } - *tokenType = TK_FLOAT; - } + if( z[i]=='.' ){ + if( *tokenType==TK_INTEGER ) *tokenType = TK_FLOAT; + for(i++; 1; i++){ + if( sqlite3Isdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } + } + if( (z[i]=='e' || z[i]=='E') && + ( sqlite3Isdigit(z[i+1]) + || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2])) + ) + ){ + if( *tokenType==TK_INTEGER ) *tokenType = TK_FLOAT; + for(i+=2; 1; i++){ + if( sqlite3Isdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } + } #endif + } while( IdChar(z[i]) ){ *tokenType = TK_ILLEGAL; i++; @@ -622,10 +653,13 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){ if( tokenType>=TK_WINDOW ){ assert( tokenType==TK_SPACE || tokenType==TK_OVER || tokenType==TK_FILTER || tokenType==TK_ILLEGAL || tokenType==TK_WINDOW + || tokenType==TK_QNUMBER ); #else if( tokenType>=TK_SPACE ){ - assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); + assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL + || tokenType==TK_QNUMBER + ); #endif /* SQLITE_OMIT_WINDOWFUNC */ if( AtomicLoad(&db->u1.isInterrupted) ){ pParse->rc = SQLITE_INTERRUPT; @@ -658,7 +692,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){ assert( n==6 ); tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed); #endif /* SQLITE_OMIT_WINDOWFUNC */ - }else{ + }else if( tokenType!=TK_QNUMBER ){ Token x; x.z = zSql; x.n = n; diff --git a/src/treeview.c b/src/treeview.c index 2576532b6..fa9eac614 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -194,8 +194,10 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ x.printfFlags |= SQLITE_PRINTF_INTERNAL; sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem); if( pItem->pTab ){ - sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx", - pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed); + sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx%s", + pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, + pItem->colUsed, + pItem->fg.rowidUsed ? "+rowid" : ""); } if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==(JT_LEFT|JT_RIGHT) ){ sqlite3_str_appendf(&x, " FULL-OUTER-JOIN"); @@ -235,12 +237,14 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); } if( pItem->pSelect ){ + sqlite3TreeViewPush(&pView, i+1nSrc); if( pItem->pTab ){ Table *pTab = pItem->pTab; sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1); } assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0); + sqlite3TreeViewPop(&pView); } if( pItem->fg.isTabFunc ){ sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:"); @@ -344,7 +348,7 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ sqlite3TreeViewItem(pView, "LIMIT", (n--)>0); sqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0); if( p->pLimit->pRight ){ - sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); + sqlite3TreeViewItem(pView, "OFFSET", 0); sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); sqlite3TreeViewPop(&pView); } diff --git a/src/trigger.c b/src/trigger.c index 97ca249be..98e8da58c 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -951,6 +951,72 @@ static ExprList *sqlite3ExpandReturning( return pNew; } +/* If the Expr node is a subquery or an EXISTS operator or an IN operator that +** uses a subquery, and if the subquery is SF_Correlated, then mark the +** expression as EP_VarSelect. +*/ +static int sqlite3ReturningSubqueryVarSelect(Walker *NotUsed, Expr *pExpr){ + UNUSED_PARAMETER(NotUsed); + if( ExprUseXSelect(pExpr) + && (pExpr->x.pSelect->selFlags & SF_Correlated)!=0 + ){ + testcase( ExprHasProperty(pExpr, EP_VarSelect) ); + ExprSetProperty(pExpr, EP_VarSelect); + } + return WRC_Continue; +} + + +/* +** If the SELECT references the table pWalker->u.pTab, then do two things: +** +** (1) Mark the SELECT as as SF_Correlated. +** (2) Set pWalker->eCode to non-zero so that the caller will know +** that (1) has happened. +*/ +static int sqlite3ReturningSubqueryCorrelated(Walker *pWalker, Select *pSelect){ + int i; + SrcList *pSrc; + assert( pSelect!=0 ); + pSrc = pSelect->pSrc; + assert( pSrc!=0 ); + for(i=0; inSrc; i++){ + if( pSrc->a[i].pTab==pWalker->u.pTab ){ + testcase( pSelect->selFlags & SF_Correlated ); + pSelect->selFlags |= SF_Correlated; + pWalker->eCode = 1; + break; + } + } + return WRC_Continue; +} + +/* +** Scan the expression list that is the argument to RETURNING looking +** for subqueries that depend on the table which is being modified in the +** statement that is hosting the RETURNING clause (pTab). Mark all such +** subqueries as SF_Correlated. If the subqueries are part of an +** expression, mark the expression as EP_VarSelect. +** +** https://sqlite.org/forum/forumpost/2c83569ce8945d39 +*/ +static void sqlite3ProcessReturningSubqueries( + ExprList *pEList, + Table *pTab +){ + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3ExprWalkNoop; + w.xSelectCallback = sqlite3ReturningSubqueryCorrelated; + w.u.pTab = pTab; + sqlite3WalkExprList(&w, pEList); + if( w.eCode ){ + w.xExprCallback = sqlite3ReturningSubqueryVarSelect; + w.xSelectCallback = sqlite3SelectWalkNoop; + sqlite3WalkExprList(&w, pEList); + } +} + /* ** Generate code for the RETURNING trigger. Unlike other triggers ** that invoke a subprogram in the bytecode, the code for RETURNING @@ -987,6 +1053,7 @@ static void codeReturningTrigger( sSelect.pSrc = &sFrom; sFrom.nSrc = 1; sFrom.a[0].pTab = pTab; + sFrom.a[0].zName = pTab->zName; /* tag-20240424-1 */ sFrom.a[0].iCursor = -1; sqlite3SelectPrep(pParse, &sSelect, 0); if( pParse->nErr==0 ){ @@ -1013,6 +1080,7 @@ static void codeReturningTrigger( int i; int nCol = pNew->nExpr; int reg = pParse->nMem+1; + sqlite3ProcessReturningSubqueries(pNew, pTab); pParse->nMem += nCol+2; pReturning->iRetReg = reg; for(i=0; iu.zToken); } +/* +** Expression p is a QNUMBER (quoted number). Dequote the value in p->u.zToken +** and set the type to INTEGER or FLOAT. "Quoted" integers or floats are those +** that contain '_' characters that must be removed before further processing. +*/ +void sqlite3DequoteNumber(Parse *pParse, Expr *p){ + assert( p!=0 || pParse->db->mallocFailed ); + if( p ){ + const char *pIn = p->u.zToken; + char *pOut = p->u.zToken; + int bHex = (pIn[0]=='0' && (pIn[1]=='x' || pIn[1]=='X')); + int iValue; + assert( p->op==TK_QNUMBER ); + p->op = TK_INTEGER; + do { + if( *pIn!=SQLITE_DIGIT_SEPARATOR ){ + *pOut++ = *pIn; + if( *pIn=='e' || *pIn=='E' || *pIn=='.' ) p->op = TK_FLOAT; + }else{ + if( (bHex==0 && (!sqlite3Isdigit(pIn[-1]) || !sqlite3Isdigit(pIn[1]))) + || (bHex==1 && (!sqlite3Isxdigit(pIn[-1]) || !sqlite3Isxdigit(pIn[1]))) + ){ + sqlite3ErrorMsg(pParse, "unrecognized token: \"%s\"", p->u.zToken); + } + } + }while( *pIn++ ); + if( bHex ) p->op = TK_INTEGER; + + /* tag-20240227-a: If after dequoting, the number is an integer that + ** fits in 32 bits, then it must be converted into EP_IntValue. Other + ** parts of the code expect this. See also tag-20240227-b. */ + if( p->op==TK_INTEGER && sqlite3GetInt32(p->u.zToken, &iValue) ){ + p->u.iValue = iValue; + p->flags |= EP_IntValue; + } + } +} + /* ** If the input token p is quoted, try to adjust the token to remove ** the quotes. This is not always possible: diff --git a/src/vdbe.c b/src/vdbe.c index 23f848944..d8b471de2 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -1129,7 +1129,7 @@ case OP_Return: { /* in1 */ ** ** See also: EndCoroutine */ -case OP_InitCoroutine: { /* jump */ +case OP_InitCoroutine: { /* jump0 */ assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); assert( pOp->p2>=0 && pOp->p2nOp ); assert( pOp->p3>=0 && pOp->p3nOp ); @@ -1152,7 +1152,9 @@ case OP_InitCoroutine: { /* jump */ ** ** The instruction at the address in register P1 is a Yield. ** Jump to the P2 parameter of that Yield. -** After the jump, register P1 becomes undefined. +** After the jump, the value register P1 is left with a value +** such that subsequent OP_Yields go back to the this same +** OP_EndCoroutine instruction. ** ** See also: InitCoroutine */ @@ -1164,8 +1166,8 @@ case OP_EndCoroutine: { /* in1 */ pCaller = &aOp[pIn1->u.i]; assert( pCaller->opcode==OP_Yield ); assert( pCaller->p2>=0 && pCaller->p2nOp ); + pIn1->u.i = (int)(pOp - p->aOp) - 1; pOp = &aOp[pCaller->p2 - 1]; - pIn1->flags = MEM_Undefined; break; } @@ -1182,7 +1184,7 @@ case OP_EndCoroutine: { /* in1 */ ** ** See also: InitCoroutine */ -case OP_Yield: { /* in1, jump */ +case OP_Yield: { /* in1, jump0 */ int pcDest; pIn1 = &aMem[pOp->p1]; assert( VdbeMemDynamic(pIn1)==0 ); @@ -1512,19 +1514,15 @@ case OP_Blob: { /* out2 */ break; } -/* Opcode: Variable P1 P2 * P4 * -** Synopsis: r[P2]=parameter(P1,P4) +/* Opcode: Variable P1 P2 * * * +** Synopsis: r[P2]=parameter(P1) ** ** Transfer the values of bound parameter P1 into register P2 -** -** If the parameter is named, then its name appears in P4. -** The P4 value is used by sqlite3_bind_parameter_name(). */ case OP_Variable: { /* out2 */ Mem *pVar; /* Value being transferred */ assert( pOp->p1>0 && pOp->p1<=p->nVar ); - assert( pOp->p4.z==0 || pOp->p4.z==sqlite3VListNumToName(p->pVList,pOp->p1) ); pVar = &p->aVar[pOp->p1 - 1]; if( sqlite3VdbeMemTooBig(pVar) ){ goto too_big; @@ -2045,7 +2043,7 @@ case OP_AddImm: { /* in1 */ ** without data loss, then jump immediately to P2, or if P2==0 ** raise an SQLITE_MISMATCH exception. */ -case OP_MustBeInt: { /* jump, in1 */ +case OP_MustBeInt: { /* jump0, in1 */ pIn1 = &aMem[pOp->p1]; if( (pIn1->flags & MEM_Int)==0 ){ applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding); @@ -2086,7 +2084,7 @@ case OP_RealAffinity: { /* in1 */ } #endif -#ifndef SQLITE_OMIT_CAST +#if !defined(SQLITE_OMIT_CAST) && !defined(SQLITE_OMIT_ANALYZE) /* Opcode: Cast P1 P2 * * * ** Synopsis: affinity(r[P1]) ** @@ -3658,11 +3656,16 @@ case OP_MakeRecord: { switch( len ){ default: zPayload[7] = (u8)(v&0xff); v >>= 8; zPayload[6] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through case 6: zPayload[5] = (u8)(v&0xff); v >>= 8; zPayload[4] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through case 4: zPayload[3] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through case 3: zPayload[2] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through case 2: zPayload[1] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through case 1: zPayload[0] = (u8)(v&0xff); } zPayload += len; @@ -4581,7 +4584,8 @@ case OP_SequenceTest: { ** is the only cursor opcode that works with a pseudo-table. ** ** P3 is the number of fields in the records that will be stored by -** the pseudo-table. +** the pseudo-table. If P2 is 0 or negative then the pseudo-cursor +** will return NULL for every column. */ case OP_OpenPseudo: { VdbeCursor *pCx; @@ -4724,10 +4728,10 @@ case OP_ColumnsUsed: { ** ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ -case OP_SeekLT: /* jump, in3, group, ncycle */ -case OP_SeekLE: /* jump, in3, group, ncycle */ -case OP_SeekGE: /* jump, in3, group, ncycle */ -case OP_SeekGT: { /* jump, in3, group, ncycle */ +case OP_SeekLT: /* jump0, in3, group, ncycle */ +case OP_SeekLE: /* jump0, in3, group, ncycle */ +case OP_SeekGE: /* jump0, in3, group, ncycle */ +case OP_SeekGT: { /* jump0, in3, group, ncycle */ int res; /* Comparison result */ int oc; /* Opcode */ VdbeCursor *pC; /* The cursor to seek */ @@ -5394,7 +5398,7 @@ case OP_Found: { /* jump, in3, ncycle */ ** ** See also: Found, NotFound, NoConflict, SeekRowid */ -case OP_SeekRowid: { /* jump, in3, ncycle */ +case OP_SeekRowid: { /* jump0, in3, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; @@ -6153,7 +6157,7 @@ case OP_NullRow: { ** configured to use Prev, not Next. */ case OP_SeekEnd: /* ncycle */ -case OP_Last: { /* jump, ncycle */ +case OP_Last: { /* jump0, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; @@ -6187,28 +6191,38 @@ case OP_Last: { /* jump, ncycle */ break; } -/* Opcode: IfSmaller P1 P2 P3 * * +/* Opcode: IfSizeBetween P1 P2 P3 P4 * +** +** Let N be the approximate number of rows in the table or index +** with cursor P1 and let X be 10*log2(N) if N is positive or -1 +** if N is zero. ** -** Estimate the number of rows in the table P1. Jump to P2 if that -** estimate is less than approximately 2**(0.1*P3). +** Jump to P2 if X is in between P3 and P4, inclusive. */ -case OP_IfSmaller: { /* jump */ +case OP_IfSizeBetween: { /* jump */ VdbeCursor *pC; BtCursor *pCrsr; int res; i64 sz; assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p4type==P4_INT32 ); + assert( pOp->p3>=-1 && pOp->p3<=640*2 ); + assert( pOp->p4.i>=-1 && pOp->p4.i<=640*2 ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); pCrsr = pC->uc.pCursor; assert( pCrsr ); rc = sqlite3BtreeFirst(pCrsr, &res); if( rc ) goto abort_due_to_error; - if( res==0 ){ + if( res!=0 ){ + sz = -1; /* -Infinity encoding */ + }else{ sz = sqlite3BtreeRowCountEst(pCrsr); - if( ALWAYS(sz>=0) && sqlite3LogEst((u64)sz)p3 ) res = 1; + assert( sz>0 ); + sz = sqlite3LogEst((u64)sz); } + res = sz>=pOp->p3 && sz<=pOp->p4.i; VdbeBranchTaken(res!=0,2); if( res ) goto jump_to_p2; break; @@ -6261,7 +6275,7 @@ case OP_Sort: { /* jump ncycle */ ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. */ -case OP_Rewind: { /* jump, ncycle */ +case OP_Rewind: { /* jump0, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; @@ -6908,11 +6922,18 @@ case OP_CreateBtree: { /* out2 */ break; } -/* Opcode: SqlExec * * * P4 * +/* Opcode: SqlExec P1 P2 * P4 * ** ** Run the SQL statement or statements specified in the P4 string. -** Disable Auth and Trace callbacks while those statements are running if -** P1 is true. +** +** The P1 parameter is a bitmask of options: +** +** 0x0001 Disable Auth and Trace callbacks while the statements +** in P4 are running. +** +** 0x0002 Set db->nAnalysisLimit to P2 while the statements in +** P4 are running. +** */ case OP_SqlExec: { char *zErr; @@ -6920,6 +6941,7 @@ case OP_SqlExec: { sqlite3_xauth xAuth; #endif u8 mTrace; + int savedAnalysisLimit; sqlite3VdbeIncrWriteCounter(p, 0); db->nSqlExec++; @@ -6928,18 +6950,23 @@ case OP_SqlExec: { xAuth = db->xAuth; #endif mTrace = db->mTrace; - if( pOp->p1 ){ + savedAnalysisLimit = db->nAnalysisLimit; + if( pOp->p1 & 0x0001 ){ #ifndef SQLITE_OMIT_AUTHORIZATION db->xAuth = 0; #endif db->mTrace = 0; } + if( pOp->p1 & 0x0002 ){ + db->nAnalysisLimit = pOp->p2; + } rc = sqlite3_exec(db, pOp->p4.z, 0, 0, &zErr); db->nSqlExec--; #ifndef SQLITE_OMIT_AUTHORIZATION db->xAuth = xAuth; #endif db->mTrace = mTrace; + db->nAnalysisLimit = savedAnalysisLimit; if( zErr || rc ){ sqlite3VdbeError(p, "%s", zErr); sqlite3_free(zErr); @@ -7091,11 +7118,11 @@ case OP_DropTrigger: { /* Opcode: IntegrityCk P1 P2 P3 P4 P5 ** ** Do an analysis of the currently open database. Store in -** register P1 the text of an error message describing any problems. -** If no problems are found, store a NULL in register P1. +** register (P1+1) the text of an error message describing any problems. +** If no problems are found, store a NULL in register (P1+1). ** -** The register P3 contains one less than the maximum number of allowed errors. -** At most reg(P3) errors will be reported. +** The register (P1) contains one less than the maximum number of allowed +** errors. At most reg(P1) errors will be reported. ** In other words, the analysis stops as soon as reg(P1) errors are ** seen. Reg(P1) is updated with the number of errors remaining. ** @@ -7115,19 +7142,21 @@ case OP_IntegrityCk: { Mem *pnErr; /* Register keeping track of errors remaining */ assert( p->bIsReader ); + assert( pOp->p4type==P4_INTARRAY ); nRoot = pOp->p2; aRoot = pOp->p4.ai; assert( nRoot>0 ); + assert( aRoot!=0 ); assert( aRoot[0]==(Pgno)nRoot ); - assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - pnErr = &aMem[pOp->p3]; + assert( pOp->p1>0 && (pOp->p1+1)<=(p->nMem+1 - p->nCursor) ); + pnErr = &aMem[pOp->p1]; assert( (pnErr->flags & MEM_Int)!=0 ); assert( (pnErr->flags & (MEM_Str|MEM_Blob))==0 ); - pIn1 = &aMem[pOp->p1]; + pIn1 = &aMem[pOp->p1+1]; assert( pOp->p5nDb ); assert( DbMaskTest(p->btreeMask, pOp->p5) ); - rc = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], nRoot, - (int)pnErr->u.i+1, &nErr, &z); + rc = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], + &aMem[pOp->p3], nRoot, (int)pnErr->u.i+1, &nErr, &z); sqlite3VdbeMemSetNull(pIn1); if( nErr==0 ){ assert( z==0 ); @@ -7254,7 +7283,9 @@ case OP_RowSetTest: { /* jump, in1, in3 */ ** P1 contains the address of the memory cell that contains the first memory ** cell in an array of values used as arguments to the sub-program. P2 ** contains the address to jump to if the sub-program throws an IGNORE -** exception using the RAISE() function. Register P3 contains the address +** exception using the RAISE() function. P2 might be zero, if there is +** no possibility that an IGNORE exception will be raised. +** Register P3 contains the address ** of a memory cell in this (the parent) VM that is used to allocate the ** memory required by the sub-vdbe at runtime. ** @@ -7262,7 +7293,7 @@ case OP_RowSetTest: { /* jump, in1, in3 */ ** ** If P5 is non-zero, then recursive program invocation is enabled. */ -case OP_Program: { /* jump */ +case OP_Program: { /* jump0 */ int nMem; /* Number of memory registers for sub-program */ int nByte; /* Bytes of runtime space required for sub-program */ Mem *pRt; /* Register to allocate runtime space */ @@ -8811,7 +8842,7 @@ case OP_Filter: { /* jump */ ** error is encountered. */ case OP_Trace: -case OP_Init: { /* jump */ +case OP_Init: { /* jump0 */ int i; #ifndef SQLITE_OMIT_TRACE char *zTrace; diff --git a/src/vdbe.h b/src/vdbe.h index 25bda6be7..9001ace2e 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -296,6 +296,8 @@ RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*); void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); int sqlite3VdbeHasSubProgram(Vdbe*); +void sqlite3MemSetArrayInt64(sqlite3_value *aMem, int iIdx, i64 val); + int sqlite3NotPureFunc(sqlite3_context*); #ifdef SQLITE_ENABLE_BYTECODE_VTAB int sqlite3VdbeBytecodeVtabInit(sqlite3*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 14c6091e0..3182e4070 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -2404,7 +2404,6 @@ int sqlite3_stmt_scanstatus_v2( } if( flags & SQLITE_SCANSTAT_COMPLEX ){ idx = iScan; - pScan = &p->aScan[idx]; }else{ /* If the COMPLEX flag is clear, then this function must ignore any ** ScanStatus structures with ScanStatus.addrLoop set to 0. */ @@ -2417,6 +2416,8 @@ int sqlite3_stmt_scanstatus_v2( } } if( idx>=p->nScan ) return 1; + assert( pScan==0 || pScan==&p->aScan[idx] ); + pScan = &p->aScan[idx]; switch( iScanStatusOp ){ case SQLITE_SCANSTAT_NLOOP: { diff --git a/src/vdbeaux.c b/src/vdbeaux.c index fe0dbd6b0..e4c174e3f 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -939,6 +939,15 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ assert( aLabel!=0 ); /* True because of tag-20230419-1 */ pOp->p2 = aLabel[ADDR(pOp->p2)]; } + + /* OPFLG_JUMP opcodes never have P2==0, though OPFLG_JUMP0 opcodes + ** might */ + assert( pOp->p2>0 + || (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP0)!=0 ); + + /* Jumps never go off the end of the bytecode array */ + assert( pOp->p2nOp + || (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)==0 ); break; } } @@ -3346,9 +3355,9 @@ int sqlite3VdbeHalt(Vdbe *p){ /* Check for immediate foreign key violations. */ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ - sqlite3VdbeCheckFk(p, 0); + (void)sqlite3VdbeCheckFk(p, 0); } - + /* If the auto-commit flag is set and this is the only active writer ** VM, then we do either a commit or rollback of the current transaction. ** @@ -4516,17 +4525,15 @@ int sqlite3IntFloatCompare(i64 i, double r){ return (xr); }else{ i64 y; - double s; if( r<-9223372036854775808.0 ) return +1; if( r>=9223372036854775808.0 ) return -1; y = (i64)r; if( iy ) return +1; - s = (double)i; - testcase( doubleLt(s,r) ); - testcase( doubleLt(r,s) ); - testcase( doubleEq(r,s) ); - return (sr); + testcase( doubleLt(((double)i),r) ); + testcase( doubleLt(r,((double)i)) ); + testcase( doubleEq(r,((double)i)) ); + return (((double)i)r); } } diff --git a/src/vdbemem.c b/src/vdbemem.c index d52716468..8e2aa4a6c 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -943,6 +943,13 @@ void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){ } } +/* +** Set the iIdx'th entry of array aMem[] to contain integer value val. +*/ +void sqlite3MemSetArrayInt64(sqlite3_value *aMem, int iIdx, i64 val){ + sqlite3VdbeMemSetInt64(&aMem[iIdx], val); +} + /* A no-op destructor */ void sqlite3NoopDestructor(void *p){ UNUSED_PARAMETER(p); } @@ -1631,14 +1638,20 @@ static int valueFromExpr( } /* 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 = "-"; + ** case when the value is -9223372036854775808. Except - do not do this + ** for hexadecimal literals. */ + if( op==TK_UMINUS ){ + Expr *pLeft = pExpr->pLeft; + if( (pLeft->op==TK_INTEGER || pLeft->op==TK_FLOAT) ){ + if( ExprHasProperty(pLeft, EP_IntValue) + || pLeft->u.zToken[0]!='0' || (pLeft->u.zToken[1] & ~0x20)!='X' + ){ + pExpr = pLeft; + op = pExpr->op; + negInt = -1; + zNeg = "-"; + } + } } if( op==TK_STRING || op==TK_FLOAT || op==TK_INTEGER ){ @@ -1647,12 +1660,26 @@ static int valueFromExpr( if( ExprHasProperty(pExpr, EP_IntValue) ){ sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt); }else{ - zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken); - if( zVal==0 ) goto no_mem; - sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC); + i64 iVal; + if( op==TK_INTEGER && 0==sqlite3DecOrHexToI64(pExpr->u.zToken, &iVal) ){ + sqlite3VdbeMemSetInt64(pVal, iVal*negInt); + }else{ + zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken); + if( zVal==0 ) goto no_mem; + sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC); + } } - if( (op==TK_INTEGER || op==TK_FLOAT ) && affinity==SQLITE_AFF_BLOB ){ - sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8); + if( affinity==SQLITE_AFF_BLOB ){ + if( op==TK_FLOAT ){ + assert( pVal && pVal->z && pVal->flags==(MEM_Str|MEM_Term) ); + sqlite3AtoF(pVal->z, &pVal->u.r, pVal->n, SQLITE_UTF8); + pVal->flags = MEM_Real; + }else if( op==TK_INTEGER ){ + /* This case is required by -9223372036854775808 and other strings + ** that look like integers but cannot be handled by the + ** sqlite3DecOrHexToI64() call above. */ + sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8); + } }else{ sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8); } @@ -1922,17 +1949,17 @@ int sqlite3Stat4Column( sqlite3_value **ppVal /* OUT: Extracted value */ ){ u32 t = 0; /* a column type code */ - int nHdr; /* Size of the header in the record */ - int iHdr; /* Next unread header byte */ - int iField; /* Next unread data byte */ - int szField = 0; /* Size of the current data field */ + u32 nHdr; /* Size of the header in the record */ + u32 iHdr; /* Next unread header byte */ + i64 iField; /* Next unread data byte */ + u32 szField = 0; /* Size of the current data field */ int i; /* Column index */ u8 *a = (u8*)pRec; /* Typecast byte array */ Mem *pMem = *ppVal; /* Write result into this Mem object */ assert( iCol>0 ); iHdr = getVarint32(a, nHdr); - if( nHdr>nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; + if( nHdr>(u32)nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; iField = nHdr; for(i=0; i<=iCol; i++){ iHdr += getVarint32(&a[iHdr], t); diff --git a/src/vdbevtab.c b/src/vdbevtab.c index b295dff7b..1c9909a1a 100644 --- a/src/vdbevtab.c +++ b/src/vdbevtab.c @@ -286,10 +286,10 @@ static int bytecodevtabColumn( #ifdef SQLITE_ENABLE_STMT_SCANSTATUS case 9: /* nexec */ - sqlite3_result_int(ctx, pOp->nExec); + sqlite3_result_int64(ctx, pOp->nExec); break; case 10: /* ncycle */ - sqlite3_result_int(ctx, pOp->nCycle); + sqlite3_result_int64(ctx, pOp->nCycle); break; #else case 9: /* nexec */ diff --git a/src/vtab.c b/src/vtab.c index f83921678..1036eed44 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -611,6 +611,8 @@ static int vtabCallConstructor( db->pVtabCtx = &sCtx; pTab->nTabRef++; rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr); + assert( pTab!=0 ); + assert( pTab->nTabRef>1 || rc!=SQLITE_OK ); sqlite3DeleteTable(db, pTab); db->pVtabCtx = sCtx.pPrior; if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); @@ -633,7 +635,7 @@ static int vtabCallConstructor( pVTable->nRef = 1; if( sCtx.bDeclared==0 ){ const char *zFormat = "vtable constructor did not declare schema: %s"; - *pzErr = sqlite3MPrintf(db, zFormat, pTab->zName); + *pzErr = sqlite3MPrintf(db, zFormat, zModuleName); sqlite3VtabUnlock(pVTable); rc = SQLITE_ERROR; }else{ @@ -811,12 +813,30 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ Table *pTab; Parse sParse; int initBusy; + int i; + const unsigned char *z; + static const u8 aKeyword[] = { TK_CREATE, TK_TABLE, 0 }; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){ return SQLITE_MISUSE_BKPT; } #endif + + /* Verify that the first two keywords in the CREATE TABLE statement + ** really are "CREATE" and "TABLE". If this is not the case, then + ** sqlite3_declare_vtab() is being misused. + */ + z = (const unsigned char*)zCreateTable; + for(i=0; aKeyword[i]; i++){ + int tokenType = 0; + do{ z += sqlite3GetToken(z, &tokenType); }while( tokenType==TK_SPACE ); + if( tokenType!=aKeyword[i] ){ + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "syntax error"); + return SQLITE_ERROR; + } + } + sqlite3_mutex_enter(db->mutex); pCtx = db->pVtabCtx; if( !pCtx || pCtx->bDeclared ){ @@ -824,6 +844,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ sqlite3_mutex_leave(db->mutex); return SQLITE_MISUSE_BKPT; } + pTab = pCtx->pTab; assert( IsVirtual(pTab) ); @@ -837,11 +858,10 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ initBusy = db->init.busy; db->init.busy = 0; sParse.nQueryLoop = 1; - if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) - && ALWAYS(sParse.pNewTable!=0) - && ALWAYS(!db->mallocFailed) - && IsOrdinaryTable(sParse.pNewTable) - ){ + if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) ){ + assert( sParse.pNewTable!=0 ); + assert( !db->mallocFailed ); + assert( IsOrdinaryTable(sParse.pNewTable) ); assert( sParse.zErrMsg==0 ); if( !pTab->aCol ){ Table *pNew = sParse.pNewTable; diff --git a/src/where.c b/src/where.c index 8e7b112ac..fac0f6c5e 100644 --- a/src/where.c +++ b/src/where.c @@ -302,6 +302,42 @@ static Expr *whereRightSubexprIsColumn(Expr *p){ return 0; } +/* +** Term pTerm is guaranteed to be a WO_IN term. It may be a component term +** of a vector IN expression of the form "(x, y, ...) IN (SELECT ...)". +** This function checks to see if the term is compatible with an index +** column with affinity idxaff (one of the SQLITE_AFF_XYZ values). If so, +** it returns a pointer to the name of the collation sequence (e.g. "BINARY" +** or "NOCASE") used by the comparison in pTerm. If it is not compatible +** with affinity idxaff, NULL is returned. +*/ +static SQLITE_NOINLINE const char *indexInAffinityOk( + Parse *pParse, + WhereTerm *pTerm, + u8 idxaff +){ + Expr *pX = pTerm->pExpr; + Expr inexpr; + + assert( pTerm->eOperator & WO_IN ); + + if( sqlite3ExprIsVector(pX->pLeft) ){ + int iField = pTerm->u.x.iField - 1; + inexpr.flags = 0; + inexpr.op = TK_EQ; + inexpr.pLeft = pX->pLeft->x.pList->a[iField].pExpr; + assert( ExprUseXSelect(pX) ); + inexpr.pRight = pX->x.pSelect->pEList->a[iField].pExpr; + pX = &inexpr; + } + + if( sqlite3IndexAffinityOk(pX, idxaff) ){ + CollSeq *pRet = sqlite3ExprCompareCollSeq(pParse, pX); + return pRet ? pRet->zName : sqlite3StrBINARY; + } + return 0; +} + /* ** Advance to the next WhereTerm that matches according to the criteria ** established when the pScan object was initialized by whereScanInit(). @@ -352,16 +388,24 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ if( (pTerm->eOperator & pScan->opMask)!=0 ){ /* Verify the affinity and collating sequence match */ if( pScan->zCollName && (pTerm->eOperator & WO_ISNULL)==0 ){ - CollSeq *pColl; + const char *zCollName; Parse *pParse = pWC->pWInfo->pParse; pX = pTerm->pExpr; - if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ - continue; + + if( (pTerm->eOperator & WO_IN) ){ + zCollName = indexInAffinityOk(pParse, pTerm, pScan->idxaff); + if( !zCollName ) continue; + }else{ + CollSeq *pColl; + if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ + continue; + } + assert(pX->pLeft); + pColl = sqlite3ExprCompareCollSeq(pParse, pX); + zCollName = pColl ? pColl->zName : sqlite3StrBINARY; } - assert(pX->pLeft); - pColl = sqlite3ExprCompareCollSeq(pParse, pX); - if( pColl==0 ) pColl = pParse->db->pDfltColl; - if( sqlite3StrICmp(pColl->zName, pScan->zCollName) ){ + + if( sqlite3StrICmp(zCollName, pScan->zCollName) ){ continue; } } @@ -713,9 +757,13 @@ static void translateColumnToCopy( ** are no-ops. */ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED) -static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ +static void whereTraceIndexInfoInputs( + sqlite3_index_info *p, /* The IndexInfo object */ + Table *pTab /* The TABLE that is the virtual table */ +){ int i; if( (sqlite3WhereTrace & 0x10)==0 ) return; + sqlite3DebugPrintf("sqlite3_index_info inputs for %s:\n", pTab->zName); for(i=0; inConstraint; i++){ sqlite3DebugPrintf( " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", @@ -733,9 +781,13 @@ static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ p->aOrderBy[i].desc); } } -static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ +static void whereTraceIndexInfoOutputs( + sqlite3_index_info *p, /* The IndexInfo object */ + Table *pTab /* The TABLE that is the virtual table */ +){ int i; if( (sqlite3WhereTrace & 0x10)==0 ) return; + sqlite3DebugPrintf("sqlite3_index_info outputs for %s:\n", pTab->zName); for(i=0; inConstraint; i++){ sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n", i, @@ -749,8 +801,8 @@ static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ sqlite3DebugPrintf(" estimatedRows=%lld\n", p->estimatedRows); } #else -#define whereTraceIndexInfoInputs(A) -#define whereTraceIndexInfoOutputs(A) +#define whereTraceIndexInfoInputs(A,B) +#define whereTraceIndexInfoOutputs(A,B) #endif /* @@ -934,7 +986,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( ** WHERE clause (or the ON clause of a LEFT join) that constrain which ** rows of the target table (pSrc) that can be used. */ if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, pLevel->iFrom) + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, pLevel->iFrom, 0) ){ pPartial = sqlite3ExprAnd(pParse, pPartial, sqlite3ExprDup(pParse->db, pExpr, 0)); @@ -976,7 +1028,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex( ** if they go out of sync. */ if( IsView(pTable) ){ - extraCols = ALLBITS; + extraCols = ALLBITS & ~idxCols; }else{ extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); } @@ -1203,7 +1255,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( for(pTerm=pWInfo->sWC.a; pTermpExpr; if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, iSrc) + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, iSrc, 0) ){ sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); } @@ -1329,7 +1381,7 @@ static sqlite3_index_info *allocateIndexInfo( Expr *pE2; /* Skip over constant terms in the ORDER BY clause */ - if( sqlite3ExprIsConstant(pExpr) ){ + if( sqlite3ExprIsConstant(0, pExpr) ){ continue; } @@ -1364,7 +1416,7 @@ static sqlite3_index_info *allocateIndexInfo( } if( i==n ){ nOrderBy = n; - if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) ){ + if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) && !pSrc->fg.rowidUsed ){ eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ eDistinct = 1; @@ -1441,7 +1493,7 @@ static sqlite3_index_info *allocateIndexInfo( pIdxInfo->nConstraint = j; for(i=j=0; ia[i].pExpr; - if( sqlite3ExprIsConstant(pExpr) ) continue; + if( sqlite3ExprIsConstant(0, pExpr) ) continue; assert( pExpr->op==TK_COLUMN || (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN && pExpr->iColumn==pExpr->pLeft->iColumn) ); @@ -1493,11 +1545,11 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab; int rc; - whereTraceIndexInfoInputs(p); + whereTraceIndexInfoInputs(p, pTab); pParse->db->nSchemaLock++; rc = pVtab->pModule->xBestIndex(pVtab, p); pParse->db->nSchemaLock--; - whereTraceIndexInfoOutputs(p); + whereTraceIndexInfoOutputs(p, pTab); if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){ if( rc==SQLITE_NOMEM ){ @@ -2975,7 +3027,9 @@ static int whereLoopAddBtreeIndex( } if( pProbe->bUnordered || pProbe->bLowQual ){ if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); - if( pProbe->bLowQual ) opMask &= ~(WO_EQ|WO_IN|WO_IS); + if( pProbe->bLowQual && pSrc->fg.isIndexedBy==0 ){ + opMask &= ~(WO_EQ|WO_IN|WO_IS); + } } assert( pNew->u.btree.nEqnColumn ); @@ -3242,10 +3296,13 @@ static int whereLoopAddBtreeIndex( } } - /* Set rCostIdx to the cost of visiting selected rows in index. Add - ** it to pNew->rRun, which is currently set to the cost of the index - ** seek only. Then, if this is a non-covering index, add the cost of - ** visiting the rows in the main table. */ + /* Set rCostIdx to the estimated cost of visiting selected rows in the + ** index. The estimate is the sum of two values: + ** 1. The cost of doing one search-by-key to find the first matching + ** entry + ** 2. Stepping forward in the index pNew->nOut times to find all + ** additional matching entries. + */ assert( pSrc->pTab->szTabRow>0 ); if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ /* The pProbe->szIdxRow is low for an IPK table since the interior @@ -3256,7 +3313,15 @@ static int whereLoopAddBtreeIndex( }else{ rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; } - pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx); + rCostIdx = sqlite3LogEstAdd(rLogSize, rCostIdx); + + /* Estimate the cost of running the loop. If all data is coming + ** from the index, then this is just the cost of doing the index + ** lookup and scan. But if some data is coming out of the main table, + ** we also have to add in the cost of doing pNew->nOut searches to + ** locate the row in the main table that corresponds to the index entry. + */ + pNew->rRun = rCostIdx; if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK|WHERE_EXPRIDX))==0 ){ pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16); } @@ -3362,7 +3427,9 @@ static int indexMightHelpWithOrderBy( for(ii=0; iinExpr; ii++){ Expr *pExpr = sqlite3ExprSkipCollateAndLikely(pOB->a[ii].pExpr); if( NEVER(pExpr==0) ) continue; - if( pExpr->op==TK_COLUMN && pExpr->iTable==iCursor ){ + if( (pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN) + && pExpr->iTable==iCursor + ){ if( pExpr->iColumn<0 ) return 1; for(jj=0; jjnKeyCol; jj++){ if( pExpr->iColumn==pIndex->aiColumn[jj] ) return 1; @@ -3619,7 +3686,7 @@ static void wherePartIdxExpr( u8 aff; if( pLeft->op!=TK_COLUMN ) return; - if( !sqlite3ExprIsConstant(pRight) ) return; + if( !sqlite3ExprIsConstant(0, pRight) ) return; if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pParse, pPart)) ) return; if( pLeft->iColumn<0 ) return; aff = pIdx->pTable->aCol[pLeft->iColumn].affinity; @@ -3968,7 +4035,7 @@ static int whereLoopAddBtree( ** unique index is used (making the index functionally non-unique) ** then the sqlite_stat1 data becomes important for scoring the ** plan */ - pTab->tabFlags |= TF_StatsUsed; + pTab->tabFlags |= TF_MaybeReanalyze; } #ifdef SQLITE_ENABLE_STAT4 sqlite3Stat4ProbeFree(pBuilder->pRec); @@ -3990,6 +4057,21 @@ static int isLimitTerm(WhereTerm *pTerm){ && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; } +/* +** Return true if the first nCons constraints in the pUsage array are +** marked as in-use (have argvIndex>0). False otherwise. +*/ +static int allConstraintsUsed( + struct sqlite3_index_constraint_usage *aUsage, + int nCons +){ + int ii; + for(ii=0; iipNew->iTab. This @@ -4130,13 +4212,20 @@ static int whereLoopAddVirtualOne( *pbIn = 1; assert( (mExclude & WO_IN)==0 ); } + /* Unless pbRetryLimit is non-NULL, there should be no LIMIT/OFFSET + ** terms. And if there are any, they should follow all other terms. */ assert( pbRetryLimit || !isLimitTerm(pTerm) ); - if( isLimitTerm(pTerm) && *pbIn ){ + assert( !isLimitTerm(pTerm) || i>=nConstraint-2 ); + assert( !isLimitTerm(pTerm) || i==nConstraint-1 || isLimitTerm(pTerm+1) ); + + if( isLimitTerm(pTerm) && (*pbIn || !allConstraintsUsed(pUsage, i)) ){ /* If there is an IN(...) term handled as an == (separate call to ** xFilter for each value on the RHS of the IN) and a LIMIT or - ** OFFSET term handled as well, the plan is unusable. Set output - ** variable *pbRetryLimit to true to tell the caller to retry with - ** LIMIT and OFFSET disabled. */ + ** OFFSET term handled as well, the plan is unusable. Similarly, + ** if there is a LIMIT/OFFSET and there are other unused terms, + ** the plan cannot be used. In these cases set variable *pbRetryLimit + ** to true to tell the caller to retry with LIMIT and OFFSET + ** disabled. */ if( pIdxInfo->needToFreeIdxStr ){ sqlite3_free(pIdxInfo->idxStr); pIdxInfo->idxStr = 0; @@ -4993,7 +5082,7 @@ static i8 wherePathSatisfiesOrderBy( if( MASKBIT(i) & obSat ) continue; p = pOrderBy->a[i].pExpr; mTerm = sqlite3WhereExprUsage(&pWInfo->sMaskSet,p); - if( mTerm==0 && !sqlite3ExprIsConstant(p) ) continue; + if( mTerm==0 && !sqlite3ExprIsConstant(0,p) ) continue; if( (mTerm&~orderDistinctMask)==0 ){ obSat |= MASKBIT(i); } @@ -5462,10 +5551,9 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){ pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; } - if( pWInfo->pSelect->pOrderBy - && pWInfo->nOBSat > pWInfo->pSelect->pOrderBy->nExpr ){ - pWInfo->nOBSat = pWInfo->pSelect->pOrderBy->nExpr; - } + /* vvv--- See check-in [12ad822d9b827777] on 2023-03-16 ---vvv */ + assert( pWInfo->pSelect->pOrderBy==0 + || pWInfo->nOBSat <= pWInfo->pSelect->pOrderBy->nExpr ); }else{ pWInfo->revMask = pFrom->revLoop; if( pWInfo->nOBSat<=0 ){ @@ -5508,7 +5596,6 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ } } - pWInfo->nRowOut = pFrom->nRow; /* Free temporary memory and return success */ @@ -5516,6 +5603,83 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ return SQLITE_OK; } +/* +** This routine implements a heuristic designed to improve query planning. +** This routine is called in between the first and second call to +** wherePathSolver(). Hence the name "Interstage" "Heuristic". +** +** The first call to wherePathSolver() (hereafter just "solver()") computes +** the best path without regard to the order of the outputs. The second call +** to the solver() builds upon the first call to try to find an alternative +** path that satisfies the ORDER BY clause. +** +** This routine looks at the results of the first solver() run, and for +** every FROM clause term in the resulting query plan that uses an equality +** constraint against an index, disable other WhereLoops for that same +** FROM clause term that would try to do a full-table scan. This prevents +** an index search from being converted into a full-table scan in order to +** satisfy an ORDER BY clause, since even though we might get slightly better +** performance using the full-scan without sorting if the output size +** estimates are very precise, we might also get severe performance +** degradation using the full-scan if the output size estimate is too large. +** It is better to err on the side of caution. +** +** Except, if the first solver() call generated a full-table scan in an outer +** loop then stop this analysis at the first full-scan, since the second +** solver() run might try to swap that full-scan for another in order to +** get the output into the correct order. In other words, we allow a +** rewrite like this: +** +** First Solver() Second Solver() +** |-- SCAN t1 |-- SCAN t2 +** |-- SEARCH t2 `-- SEARCH t1 +** `-- SORT USING B-TREE +** +** The purpose of this routine is to disallow rewrites such as: +** +** First Solver() Second Solver() +** |-- SEARCH t1 |-- SCAN t2 <--- bad! +** |-- SEARCH t2 `-- SEARCH t1 +** `-- SORT USING B-TREE +** +** See test cases in test/whereN.test for the real-world query that +** originally provoked this heuristic. +*/ +static SQLITE_NOINLINE void whereInterstageHeuristic(WhereInfo *pWInfo){ + int i; +#ifdef WHERETRACE_ENABLED + int once = 0; +#endif + for(i=0; inLevel; i++){ + WhereLoop *p = pWInfo->a[i].pWLoop; + if( p==0 ) break; + if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ) continue; + if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){ + u8 iTab = p->iTab; + WhereLoop *pLoop; + for(pLoop=pWInfo->pLoops; pLoop; pLoop=pLoop->pNextLoop){ + if( pLoop->iTab!=iTab ) continue; + if( (pLoop->wsFlags & (WHERE_CONSTRAINT|WHERE_AUTO_INDEX))!=0 ){ + /* Auto-index and index-constrained loops allowed to remain */ + continue; + } +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x80 ){ + if( once==0 ){ + sqlite3DebugPrintf("Loops disabled by interstage heuristic:\n"); + once = 1; + } + sqlite3WhereLoopPrint(pLoop, &pWInfo->sWC); + } +#endif /* WHERETRACE_ENABLED */ + pLoop->prereq = ALLBITS; /* Prevent 2nd solver() from using this one */ + } + }else{ + break; + } + } +} + /* ** Most queries use only a single table (they are not joins) and have ** simple == constraints against indexed fields. This routine attempts @@ -5804,7 +5968,7 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab]; Table *pTab = pItem->pTab; if( (pTab->tabFlags & TF_HasStat1)==0 ) break; - pTab->tabFlags |= TF_StatsUsed; + pTab->tabFlags |= TF_MaybeReanalyze; if( i>=1 && (pLoop->wsFlags & reqFlags)==reqFlags /* vvvvvv--- Always the case if WHERE_COLUMN_EQ is defined */ @@ -5825,6 +5989,58 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( } } +/* +** Expression Node callback for sqlite3ExprCanReturnSubtype(). +** +** Only a function call is able to return a subtype. So if the node +** is not a function call, return WRC_Prune immediately. +** +** A function call is able to return a subtype if it has the +** SQLITE_RESULT_SUBTYPE property. +** +** Assume that every function is able to pass-through a subtype from +** one of its argument (using sqlite3_result_value()). Most functions +** are not this way, but we don't have a mechanism to distinguish those +** that are from those that are not, so assume they all work this way. +** That means that if one of its arguments is another function and that +** other function is able to return a subtype, then this function is +** able to return a subtype. +*/ +static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){ + int n; + FuncDef *pDef; + sqlite3 *db; + if( pExpr->op!=TK_FUNCTION ){ + return WRC_Prune; + } + assert( ExprUseXList(pExpr) ); + db = pWalker->pParse->db; + n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + pWalker->eCode = 1; + return WRC_Prune; + } + return WRC_Continue; +} + +/* +** Return TRUE if expression pExpr is able to return a subtype. +** +** A TRUE return does not guarantee that a subtype will be returned. +** It only indicates that a subtype return is possible. False positives +** are acceptable as they only disable an optimization. False negatives, +** on the other hand, can lead to incorrect answers. +*/ +static int sqlite3ExprCanReturnSubtype(Parse *pParse, Expr *pExpr){ + Walker w; + memset(&w, 0, sizeof(w)); + w.pParse = pParse; + w.xExprCallback = exprNodeCanReturnSubtype; + sqlite3WalkExpr(&w, pExpr); + return w.eCode; +} + /* ** The index pIdx is used by a query and contains one or more expressions. ** In other words pIdx is an index on an expression. iIdxCur is the cursor @@ -5857,20 +6073,12 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( }else{ continue; } - if( sqlite3ExprIsConstant(pExpr) ) continue; - if( pExpr->op==TK_FUNCTION ){ + if( sqlite3ExprIsConstant(0,pExpr) ) continue; + if( pExpr->op==TK_FUNCTION && sqlite3ExprCanReturnSubtype(pParse,pExpr) ){ /* Functions that might set a subtype should not be replaced by the ** value taken from an expression index since the index omits the ** subtype. https://sqlite.org/forum/forumpost/68d284c86b082c3e */ - int n; - FuncDef *pDef; - sqlite3 *db = pParse->db; - assert( ExprUseXList(pExpr) ); - n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; - pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); - if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ - continue; - } + continue; } p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; @@ -6135,7 +6343,11 @@ WhereInfo *sqlite3WhereBegin( ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } - ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); + if( ALWAYS(pWInfo->pSelect) + && (pWInfo->pSelect->selFlags & SF_MultiValue)==0 + ){ + ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); + } }else{ /* Assign a bit from the bitmask to every term in the FROM clause. ** @@ -6288,6 +6500,7 @@ WhereInfo *sqlite3WhereBegin( wherePathSolver(pWInfo, 0); if( db->mallocFailed ) goto whereBeginError; if( pWInfo->pOrderBy ){ + whereInterstageHeuristic(pWInfo); wherePathSolver(pWInfo, pWInfo->nRowOut+1); if( db->mallocFailed ) goto whereBeginError; } @@ -6837,7 +7050,15 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 ); if( (ws & WHERE_IDX_ONLY)==0 ){ - assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor ); + SrcItem *pSrc = &pTabList->a[pLevel->iFrom]; + assert( pLevel->iTabCur==pSrc->iCursor ); + if( pSrc->fg.viaCoroutine ){ + int m, n; + n = pSrc->regResult; + assert( pSrc->pTab!=0 ); + m = pSrc->pTab->nCol; + sqlite3VdbeAddOp3(v, OP_Null, 0, n, n+m-1); + } sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } if( (ws & WHERE_INDEXED) @@ -6887,6 +7108,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ */ if( pTabItem->fg.viaCoroutine ){ testcase( pParse->db->mallocFailed ); + assert( pTabItem->regResult>=0 ); translateColumnToCopy(pParse, pLevel->addrBody, pLevel->iTabCur, pTabItem->regResult, 0); continue; diff --git a/src/wherecode.c b/src/wherecode.c index 47ce36ce3..a620a5ac0 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -1337,6 +1337,27 @@ static SQLITE_NOINLINE void filterPullDown( } } +/* +** Loop pLoop is a WHERE_INDEXED level that uses at least one IN(...) +** operator. Return true if level pLoop is guaranteed to visit only one +** row for each key generated for the index. +*/ +static int whereLoopIsOneRow(WhereLoop *pLoop){ + if( pLoop->u.btree.pIndex->onError + && pLoop->nSkip==0 + && pLoop->u.btree.nEq==pLoop->u.btree.pIndex->nKeyCol + ){ + int ii; + for(ii=0; iiu.btree.nEq; ii++){ + if( pLoop->aLTerm[ii]->eOperator & (WO_IS|WO_ISNULL) ){ + return 0; + } + } + return 1; + } + return 0; +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -1415,7 +1436,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ pLevel->iLeftJoin = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin); - VdbeComment((v, "init LEFT JOIN no-match flag")); + VdbeComment((v, "init LEFT JOIN match flag")); } /* Compute a safe address to jump to if we discover that the table for @@ -2084,7 +2105,9 @@ Bitmask sqlite3WhereCodeOneLoopStart( } /* Record the instruction used to terminate the loop. */ - if( pLoop->wsFlags & WHERE_ONEROW ){ + if( (pLoop->wsFlags & WHERE_ONEROW) + || (pLevel->u.in.nIn && regBignull==0 && whereLoopIsOneRow(pLoop)) + ){ pLevel->op = OP_Noop; }else if( bRev ){ pLevel->op = OP_Prev; @@ -2474,6 +2497,12 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** iLoop==3: Code all remaining expressions. ** ** An effort is made to skip unnecessary iterations of the loop. + ** + ** This optimization of causing simple query restrictions to occur before + ** more complex one is call the "push-down" optimization in MySQL. Here + ** in SQLite, the name is "MySQL push-down", since there is also another + ** totally unrelated optimization called "WHERE-clause push-down". + ** Sometimes the qualifier is omitted, resulting in an ambiguity, so beware. */ iLoop = (pIdx ? 1 : 2); do{ @@ -2724,7 +2753,16 @@ SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( pRJ->regReturn); for(k=0; ka[k].pWLoop->iTab == pWInfo->a[k].iFrom ); + pRight = &pWInfo->pTabList->a[pWInfo->a[k].iFrom]; mAll |= pWInfo->a[k].pWLoop->maskSelf; + if( pRight->fg.viaCoroutine ){ + sqlite3VdbeAddOp3( + v, OP_Null, 0, pRight->regResult, + pRight->regResult + pRight->pSelect->pEList->nExpr-1 + ); + } sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); iIdxCur = pWInfo->a[k].iIdxCur; if( iIdxCur ){ diff --git a/src/whereexpr.c b/src/whereexpr.c index daf3d5d95..5465dc953 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -989,7 +989,7 @@ static SQLITE_NOINLINE int exprMightBeIndexed2( if( pIdx->aiColumn[i]!=XN_EXPR ) continue; assert( pIdx->bHasExpr ); if( sqlite3ExprCompareSkip(pExpr,pIdx->aColExpr->a[i].pExpr,iCur)==0 - && pExpr->op!=TK_STRING + && !sqlite3ExprIsConstant(0,pIdx->aColExpr->a[i].pExpr) ){ aiCurCol[0] = iCur; aiCurCol[1] = XN_EXPR; @@ -1638,6 +1638,7 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ continue; } if( pWC->a[ii].leftCursor!=iCsr ) return; + if( pWC->a[ii].prereqRight!=0 ) return; } /* Check condition (5). Return early if it is not met. */ @@ -1652,12 +1653,14 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ /* All conditions are met. Add the terms to the where-clause object. */ assert( p->pLimit->op==TK_LIMIT ); - whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, - iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); - if( p->iOffset>0 ){ + if( p->iOffset!=0 && (p->selFlags & SF_Compound)==0 ){ whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight, iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET); } + if( p->iOffset==0 || (p->selFlags & SF_Compound)==0 ){ + whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, + iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); + } } } diff --git a/src/window.c b/src/window.c index 62df349fb..bcee65d92 100644 --- a/src/window.c +++ b/src/window.c @@ -1164,7 +1164,7 @@ void sqlite3WindowListDelete(sqlite3 *db, Window *p){ ** variable values in the expression tree. */ static Expr *sqlite3WindowOffsetExpr(Parse *pParse, Expr *pExpr){ - if( 0==sqlite3ExprIsConstant(pExpr) ){ + if( 0==sqlite3ExprIsConstant(0,pExpr) ){ if( IN_RENAME_OBJECT ) sqlite3RenameExprUnmap(pParse, pExpr); sqlite3ExprDelete(pParse->db, pExpr); pExpr = sqlite3ExprAlloc(pParse->db, TK_NULL, 0, 0); diff --git a/test/alter2.test b/test/alter2.test index aae0061ad..20b75b59e 100644 --- a/test/alter2.test +++ b/test/alter2.test @@ -371,7 +371,7 @@ do_test alter2-7.5 { execsql { SELECT a, typeof(a), b, typeof(b), c, typeof(c) FROM t1 LIMIT 1; } -} {1 integer -123 integer 5 text} +} {1 integer -123.0 real 5 text} #----------------------------------------------------------------------- # Test that UPDATE trigger tables work with default values, and that when @@ -397,11 +397,11 @@ do_test alter2-8.2 { UPDATE t1 SET c = 10 WHERE a = 1; SELECT a, typeof(a), b, typeof(b), c, typeof(c) FROM t1 LIMIT 1; } -} {1 integer -123 integer 10 text} +} {1 integer -123.0 real 10 text} ifcapable trigger { do_test alter2-8.3 { set ::val - } {-123 integer 5 text -123 integer 10 text} + } {-123.0 real 5 text -123.0 real 10 text} } #----------------------------------------------------------------------- @@ -425,7 +425,7 @@ ifcapable trigger { DELETE FROM t1 WHERE a = 2; } set ::val - } {-123 integer 5 text} + } {-123.0 real 5 text} } #----------------------------------------------------------------------- diff --git a/test/altertab2.test b/test/altertab2.test index def9e5668..576dc4967 100644 --- a/test/altertab2.test +++ b/test/altertab2.test @@ -360,4 +360,26 @@ do_catchsql_test 8.6 { SELECT sql FROM sqlite_master WHERE name='i0'; } {1 {error in index i0: second argument to likelihood() must be a constant between 0.0 and 1.0}} + +reset_db + +do_execsql_test 9.0 { + CREATE TABLE t1(a,b,c,d); + CREATE TABLE t2(a,b,c,d,x); + + CREATE TRIGGER AFTER INSERT ON t2 BEGIN + + SELECT group_conct( + 123 ORDER BY ( + SELECT 1 FROM ( VALUES(a, 'b'), ('c') ) + )) + FROM t1; + + END; +} + +do_catchsql_test 9.1 { + ALTER TABLE t2 RENAME TO newname; +} {1 {error in trigger AFTER: all VALUES must have the same number of terms}} + finish_test diff --git a/test/altertab3.test b/test/altertab3.test index 5fd17f3a2..5f5c11b0b 100644 --- a/test/altertab3.test +++ b/test/altertab3.test @@ -736,4 +736,54 @@ do_execsql_test 29.7 { END} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 30.0 { + CREATE TABLE t1(a, b); + CREATE VIEW v1 AS + SELECT ( VALUES(a), (b) ) FROM ( + SELECT a, b FROM t1 + ) + ; +} + +do_execsql_test 30.1 { + SELECT * FROM v1 +} + +do_execsql_test 30.1 { + ALTER TABLE t1 RENAME TO t2; +} +do_execsql_test 30.2 { + SELECT sql FROM sqlite_schema WHERE type='view' +} { + {CREATE VIEW v1 AS + SELECT ( VALUES(a), (b) ) FROM ( + SELECT a, b FROM "t2" + )} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 31.0 { + CREATE TABLE t1(ii INTEGER PRIMARY KEY, tt INTEGER, rr REAL); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50000 + ) + INSERT INTO t1 SELECT NULL, i, 5.0 FROM s; +} + +do_test 31.1 { + set pg [db one {PRAGMA page_count}] + execsql { + ALTER TABLE t1 DROP COLUMN tt; + } + set pg2 [db one {PRAGMA page_count}] + expr $pg==$pg2 +} {1} + +do_execsql_test 31.2 { + SELECT rr FROM t1 LIMIT 1 +} {5.0} + finish_test diff --git a/test/avfs.test b/test/avfs.test index 2ebd608ba..ffd6b309f 100644 --- a/test/avfs.test +++ b/test/avfs.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # This file implements tests for the appendvfs extension. # diff --git a/test/bestindex8.test b/test/bestindex8.test index e95c3c6dc..3ed7f6703 100644 --- a/test/bestindex8.test +++ b/test/bestindex8.test @@ -158,7 +158,7 @@ do_test 2.2 { set ::lFilterArgs [list] execsql { SELECT * FROM vt1 LIMIT 5 OFFSET 50 } set ::lFilterArgs -} {{5 50}} +} {{50 5}} do_test 2.3 { set ::lFilterArgs [list] diff --git a/test/bestindexC.test b/test/bestindexC.test new file mode 100644 index 000000000..c6ddf3061 --- /dev/null +++ b/test/bestindexC.test @@ -0,0 +1,213 @@ +# 2024-04-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 bestindexC + +ifcapable !vtab { + finish_test + return +} + +register_tcl_module db + +proc vtab_command {lVal method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(a)" + } + + xBestIndex { + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + + set idxstr [list] + set res [list] + + set idx 0 + foreach c $clist { + array set a $c + if {$a(usable)==0} continue + if {$a(op)=="limit" && ![info exists ::do_not_use_limit]} { + lappend idxstr limit + lappend res omit $idx + } + if {$a(op)=="offset" && ![info exists ::do_not_use_offset]} { + lappend idxstr offset + lappend res omit $idx + } + incr idx + } + + return "cost 1000000 rows 1000000 idxnum 0 idxstr {$idxstr} $res" + } + + xFilter { + set idxstr [lindex $args 1] + set LIMIT "" + foreach a $idxstr b [lindex $args 2] { + set x($a) $b + } + + if {![info exists x(limit)]} { set x(limit) -1 } + if {![info exists x(offset)]} { set x(offset) -1 } + set LIMIT " LIMIT $x(limit) OFFSET $x(offset)" + + set idx 1 + foreach v $lVal { + lappend lRow "($idx, '$v')" + incr idx + } + + return [list sql " + SELECT * FROM ( VALUES [join $lRow ,]) $LIMIT + "] + } + } + + return {} +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f"); + CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "A B C D E F a b"); +} {} + +do_execsql_test 1.1 { + CREATE TEMP TABLE t_unionall AS + SELECT * FROM x1 UNION ALL SELECT * FROM x2; + + CREATE TEMP TABLE t_intersect AS + SELECT * FROM x1 INTERSECT SELECT * FROM x2; + + CREATE TEMP TABLE t_union AS + SELECT * FROM x1 UNION SELECT * FROM x2; + + CREATE TEMP TABLE t_except AS + SELECT * FROM x1 EXCEPT SELECT * FROM x2; +} + +foreach {tn limit} { + 1 "LIMIT 8" + 2 "LIMIT 4" + 3 "LIMIT 4 OFFSET 2" + 4 "LIMIT 8 OFFSET 4" +} { + + foreach {op tbl} { + "UNION ALL" t_unionall + "UNION" t_union + "INTERSECT" t_intersect + "EXCEPT" t_except + } { + + set expect [execsql "SELECT * FROM $tbl $limit"] + do_execsql_test 1.2.$tbl.$tn "SELECT * FROM ( + SELECT * FROM x1 $op SELECT * FROM x2 + ) $limit" $expect + + } + +} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f"); + CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "a b e f"); +} {} + +do_execsql_test 2.1 { + SELECT * FROM x1 + EXCEPT + SELECT * FROM x2 + LIMIT 3 +} {c d} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "1 2 3 4 5 6 7 8 9 10"); +} {} + +do_execsql_test 3.1 { + SELECT * FROM y1 WHERE a = COALESCE('8', a) LIMIT 3 +} {8} + +do_execsql_test 3.2 { + SELECT * FROM y1 WHERE a = '2' LIMIT 3 +} {2} + +load_static_extension db series +do_execsql_test 3.3 { + SELECT * FROM generate_series(1, 5) WHERE value = (value & 14) LIMIT 3 +} {2 4} + +do_execsql_test 3.4 { + SELECT value FROM generate_series(1,10) WHERE value>2 LIMIT 4 OFFSET 1; +} {4 5 6 7} + +set ::do_not_use_limit 1 +do_execsql_test 3.5 { + SELECT * FROM y1 LIMIT 5 OFFSET 3 +} {4 5 6 7 8} +unset ::do_not_use_limit +set ::do_not_use_offset 1 +do_execsql_test 3.6 { + SELECT * FROM y1 LIMIT 5 OFFSET 3 +} {4 5 6 7 8} +unset ::do_not_use_offset + +#------------------------------------------------------------------------- +reset_db +proc vtab_command {lVal method args} { + switch -- $method { + xConnect { error "not happy!" } + } + + return {} +} + +register_tcl_module db +do_catchsql_test 4.0 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command 1); +} {1 {not happy!}} +do_test 4.1 { + sqlite3_errcode db +} SQLITE_ERROR + +proc vtab_command {lVal method args} { + switch -- $method { + xConnect { + return $lVal + } + } + return {} +} + +do_catchsql_test 4.2 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "PRAGMA page_size=1024"); +} {1 {declare_vtab: syntax error}} +do_catchsql_test 4.3 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1("); +} {1 {declare_vtab: incomplete input}} +do_catchsql_test 4.4 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1(insert)"); +} {1 {declare_vtab: near "insert": syntax error}} + +finish_test diff --git a/test/busy.test b/test/busy.test index be0515b01..896c7fa65 100644 --- a/test/busy.test +++ b/test/busy.test @@ -106,7 +106,7 @@ do_test 3.4 { proc busy_handler {n} { return 1 } do_test 3.5 { catchsql { PRAGMA optimize } -} {0 {}} +} {1 {database is locked}} do_test 3.6 { execsql { COMMIT } db2 diff --git a/test/capi3.test b/test/capi3.test index 40d87ac8e..e65f90e3a 100644 --- a/test/capi3.test +++ b/test/capi3.test @@ -181,7 +181,7 @@ do_test capi3-3.4 { do_test capi3-3.5 { list [sqlite3_system_errno $db2] [sqlite3_close $db2] } [list $::capi3_errno SQLITE_OK] -if {[clang_sanitize_address]==0} { +if {[clang_sanitize_address]==0 && 0} { do_test capi3-3.6.1-misuse { sqlite3_close $db2 } {SQLITE_MISUSE} diff --git a/test/cksumvfs.test b/test/cksumvfs.test new file mode 100644 index 000000000..8c7bcf551 --- /dev/null +++ b/test/cksumvfs.test @@ -0,0 +1,33 @@ +# 2024 March 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 +set testprefix cksumvfs + +sqlite3_register_cksumvfs +db close +sqlite3 db test.db +file_control_reservebytes db 8 + +set text [db one "SELECT hex(randomblob(5000))"] + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, $text); +} + +do_execsql_test 1.1 { + SELECT * FROM t1; +} [list 1 $text] + +finish_test diff --git a/test/corruptC.test b/test/corruptC.test index f5733a818..bf324c120 100644 --- a/test/corruptC.test +++ b/test/corruptC.test @@ -98,7 +98,7 @@ do_test corruptC-2.1 { sqlite3 db test.db catchsql {PRAGMA integrity_check} } {0 {{*** in database main *** -Tree 3 page 3: free space corruption}}} +Tree 3 page 3: free space corruption} {wrong # of entries in index t1i1}}} # test that a corrupt content offset size is handled (seed 5649) # diff --git a/test/corruptD.test b/test/corruptD.test index c35388adf..381e52c80 100644 --- a/test/corruptD.test +++ b/test/corruptD.test @@ -113,7 +113,7 @@ do_test corruptD-1.1.1 { hexio_write test.db [expr 1024+1] FFFF catchsql { PRAGMA quick_check } } {0 {{*** in database main *** -Tree 2 page 2: free space corruption}}} +Tree 2 page 2: free space corruption} {wrong # of entries in index i1}}} do_test corruptD-1.1.2 { incr_change_counter hexio_write test.db [expr 1024+1] [hexio_render_int32 1021] diff --git a/test/corruptL.test b/test/corruptL.test index cf3876441..52adf6fd7 100644 --- a/test/corruptL.test +++ b/test/corruptL.test @@ -1505,4 +1505,89 @@ do_catchsql_test 19.4 { PRAGMA integrity_check; } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db +do_test 18.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 20480 pagesize 4096 filename crash-a4150b729051e4.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 05 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 64: 00 00 00 00 00 00 00 00 00 00 ff f0 00 00 00 00 ................ +| 96: 00 00 00 00 0d 00 00 00 04 0e e5 00 0f c2 0f 75 ...............u +| 112: 0f 19 0e e5 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 3808: 00 00 00 00 00 32 04 06 17 17 11 01 4b 69 6e 64 .....2......Kind +| 3824: 65 78 74 31 61 62 63 74 31 05 43 52 45 41 54 45 ext1abct1.CREATE +| 3840: 20 49 4e 44 45 58 20 74 31 61 62 63 20 4f 4e 20 INDEX t1abc ON +| 3856: 74 31 28 61 2c 62 2c 63 29 5a 03 06 17 25 25 01 t1(a,b,c)Z...%%. +| 3872: 79 74 61 62 6c 65 73 71 6c 69 74 65 5f 73 74 61 ytablesqlite_sta +| 3888: 74 34 73 71 6c 69 74 65 5f 73 74 61 74 34 04 43 t4sqlite_stat4.C +| 3904: 52 45 41 54 45 20 54 41 42 4c 45 20 73 71 6c 69 REATE TABLE sqli +| 3920: 74 65 5f 73 74 61 74 34 28 74 62 6c 2c 69 64 78 te_stat4(tbl,idx +| 3936: 2c 6e 65 71 2c 6e 6c 74 2c 6e 64 6c 74 2c 73 61 ,neq,nlt,ndlt,sa +| 3952: 6d 70 6c 65 29 4b 02 06 17 25 25 01 5b 74 61 62 mple)K...%%.[tab +| 3968: 6c 65 73 71 6c 69 74 65 5f 73 74 61 74 31 73 71 lesqlite_stat1sq +| 3984: 6c 69 74 65 5f 73 74 61 74 31 03 43 52 45 41 54 lite_stat1.CREAT +| 4000: 45 20 54 41 42 4c 45 20 73 71 6c 69 74 65 5f 73 E TABLE sqlite_s +| 4016: 74 61 74 31 28 74 62 6c 2c 69 64 78 2c 73 74 61 tat1(tbl,idx,sta +| 4032: 74 29 3c 01 06 17 11 11 01 65 74 61 62 6c 65 74 t)<......etablet +| 4048: 31 74 31 02 43 52 45 41 54 45 20 54 41 42 4c 45 1t1.CREATE TABLE +| 4064: 20 74 31 28 61 20 54 45 58 54 2c 20 62 20 49 4e t1(a TEXT, b IN +| 4080: 54 2c 20 63 20 49 4e 54 2c 20 64 20 49 4e 54 29 T, c INT, d INT) +| page 2 offset 4096 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4000: 0b 07 05 13 01 01 01 62 63 64 64 06 0b 0c 06 05 .......bcdd..... +| 4016: 13 02 01 01 64 65 66 01 59 09 0a 0c 05 05 13 03 ....def.Y....... +| 4032: 01 01 64 65 66 02 6f 08 09 0c 04 05 13 02 01 01 ..def.o......... +| 4048: 61 62 63 01 59 07 08 0c 03 05 13 02 01 01 87 62 abc.Y..........b +| 4064: 63 00 ea 06 07 0c 02 05 13 02 01 01 61 62 63 00 c...........abc. +| 4080: ea 06 06 0b 01 05 13 01 01 01 61 62 63 7b 04 04 ..........abc... +| page 3 offset 8192 +| 0: 0d 00 00 00 01 0f e0 00 0f e1 00 00 00 00 00 00 ................ +| 4064: 00 1d 01 04 11 17 31 74 31 74 31 61 62 63 31 30 ......1t1t1abc10 +| 4080: 30 30 30 20 35 30 30 30 20 32 30 30 30 20 31 30 000 5000 2000 10 +| page 4 offset 12288 +| 0: 0d 00 00 00 07 0e ac 00 0f d1 0f a0 0f 6f 0f 3e .............o.> +| 16: 0f 0e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3744: 00 00 00 00 00 00 00 00 00 00 00 00 2f 07 07 11 ............/... +| 3760: 17 1b 1b 1b 24 74 31 74 31 61 62 63 32 20 31 20 ....$t1t1abc2 1 +| 3776: 31 20 31 35 20 36 20 36 20 36 32 20 35 20 36 20 1 15 6 6 62 5 6 +| 3792: 36 05 13 02 01 01 64 65 66 02 37 08 05 2f 06 07 6.....def.7../.. +| 3808: 11 17 1b 1b 1b 24 74 41 74 31 61 62 63 32 20 31 .....$tAt1abc2 1 +| 3824: 20 31 20 31 35 20 35 20 55 20 35 32 20 34 20 35 1 15 5 U 52 4 5 +| 3840: 20 35 05 13 02 01 01 64 65 66 01 59 09 06 2e 05 5.....def.Y.... +| 3856: 07 11 17 1b 1b 1b 22 74 31 74 31 61 62 63 31 20 .......t1t1abc1 +| 3872: 31 20 31 20 31 34 20 34 20 34 20 34 31 20 33 20 1 1 14 4 4 41 3 +| 3888: 34 20 34 08 b3 cd f0 f1 62 63 64 64 06 07 2f 05 4 4.....bcdd../. +| 3904: 07 11 17 1b 1b 1b 24 74 37 74 31 61 62 63 34 20 ......$t7t1abc4 +| 3920: 31 20 31 20 31 30 20 33 20 33 20 33 30 20 32 20 1 1 10 3 3 30 2 +| 3936: 33 20 33 05 13 02 01 01 61 62 63 01 59 07 04 2f 3 3.....abc.Y../ +| 3952: 03 07 11 17 1b 1b 1b 24 74 31 74 31 61 62 63 34 .......$t1t1abc4 +| 3968: 20 32 20 31 20 31 30 20 31 20 32 20 32 30 20 31 2 1 10 1 2 20 1 +| 3984: 20 32 20 32 05 13 02 01 01 61 62 63 00 ea 06 03 2 2.....abc.... +| 4000: 2f 02 07 11 17 1b 1b 1b 24 74 31 74 31 61 62 63 /.......$t1t1abc +| 4016: 34 20 32 20 31 20 31 30 20 31 20 31 20 31 30 20 4 2 1 10 1 1 10 +| 4032: 31 20 31 20 31 05 13 02 01 01 61 62 63 00 ea 05 1 1 1.....abc... +| 4048: 02 2d 01 07 11 17 1b 1b 1b 20 74 31 74 31 61 62 .-....... t1t1ab +| 4064: 63 34 20 31 20 31 20 31 30 20 30 20 30 1f 30 30 c4 1 1 10 0 0.00 +| 4080: 20 30 20 30 20 30 05 13 01 01 09 61 62 63 7b 04 0 0 0.....abc.. +| page 5 offset 16384 +| 0: 0a 00 00 00 07 0f a8 00 0f f5 00 00 00 00 00 00 ................ +| 4000: 00 00 00 00 00 00 00 00 0c 05 13 02 01 01 64 65 ..............de +| 4016: 66 02 37 08 05 0c 05 13 02 01 01 64 65 66 01 59 f.7........def.Y +| 4032: 09 06 0b 05 12 01 01 01 62 63 64 64 06 07 0c 05 ........bcdd.... +| 4048: 13 02 01 01 61 62 63 01 59 07 01 2c 05 13 02 01 ....abc.Y..,.... +| 4064: 01 61 62 63 00 ea 06 03 0c 05 13 02 01 01 61 62 .abc..........ab +| 4080: 63 00 ea 05 00 00 00 00 00 00 00 00 00 00 00 00 c............... +| end crash-a4150b729051e4.db +}]} {} + +do_catchsql_test 18.1 { + SELECT a FROM t1 WHERE b GLOB b AND b GLOB '0^x]␅6␚xz]'; +} {1 {database disk image is malformed}} + finish_test diff --git a/test/cost.test b/test/cost.test index 5684177a1..6106caba8 100644 --- a/test/cost.test +++ b/test/cost.test @@ -103,7 +103,7 @@ do_eqp_test 5.2 { } { QUERY PLAN |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY } do_eqp_test 5.3 { diff --git a/test/date.test b/test/date.test index 19cecc2db..d22b652b4 100644 --- a/test/date.test +++ b/test/date.test @@ -209,8 +209,8 @@ datetest 3.16 "strftime('[repeat 200 %Y]','2003-10-31')" [repeat 200 2003] datetest 3.17 "strftime('[repeat 200 abc%m123]','2003-10-31')" \ [repeat 200 abc10123] -foreach c {a b c g h i n o q r t v x y z - A B C D E G K L N O Q V Z +foreach c {a b c h i n o q r t v x y z + A B C D E K L N O Q Z 0 1 2 3 4 5 6 6 7 9 _} { datetest 3.18.$c "strftime('%$c','2003-10-31')" NULL } @@ -262,7 +262,7 @@ datetest 5.15 {datetime('1994-04-16 14:00:00 +05:00 Z')} NULL # localtime->utc and utc->localtime conversions. # # Use SQLITE_TESTCTRL_LOCALTIME_FAULT=2 to set an alternative localtime_r() -# implementation that is not locale-dependent. This testing localtime_r() +# implementation that is not locale-dependent. The testing localtime_r() # operates as follows: # # (1) Localtime is 30 minutes earlier than (west of) UTC on @@ -321,6 +321,38 @@ utc_to_local 6.22 {1800-10-29 12:30:00} {1800-10-29 12:00:00} local_to_utc 6.23 {3000-10-30 12:00:00} {3000-10-30 11:30:00} utc_to_local 6.24 {3000-10-30 11:30:00} {3000-10-30 12:00:00} +# If the time is specified to be ZULU, or if it has an explicit +# timezone extension, then the time will already be UTC and subsequent +# 'utc' modifiers are no-ops. +# +do_execsql_test date-6.25 { + SELECT datetime('2000-10-29 12:00Z','utc','utc'); +} {{2000-10-29 12:00:00}} +do_execsql_test date-6.26 { + SELECT datetime('2000-10-29 12:00:00+05:00'); +} {{2000-10-29 07:00:00}} +do_execsql_test date-6.27 { + SELECT datetime('2000-10-29 12:00:00+05:00', 'utc'); +} {{2000-10-29 07:00:00}} + +# Multiple back-and-forth UTC to LOCAL to UTC... +do_execsql_test date-6.28 { + SELECT datetime('2000-10-29 12:00:00Z', 'localtime'); +} {{2000-10-29 12:30:00}} +do_execsql_test date-6.29 { + SELECT datetime('2000-10-29 12:00:00Z', 'utc', 'localtime'); +} {{2000-10-29 12:30:00}} +do_execsql_test date-6.30 { + SELECT datetime('2000-10-29 12:00:00Z', 'utc', 'localtime', 'utc'); +} {{2000-10-29 12:00:00}} +do_execsql_test date-6.31 { + SELECT datetime('2000-10-29 12:00:00Z', 'utc','localtime','utc','localtime'); +} {{2000-10-29 12:30:00}} +do_execsql_test date-6.32 { + SELECT datetime('2000-10-29 12:00:00Z', 'localtime','localtime'); +} {{2000-10-29 12:30:00}} + + # Restore the use of the OS localtime_r() before going on... sqlite3_test_control SQLITE_TESTCTRL_LOCALTIME_FAULT 0 @@ -573,4 +605,51 @@ datetest 18.2 {unixepoch('1970-01-01T00:00:00.1', 'subsec')} {0.1} datetest 18.3 {unixepoch('1970-01-01T00:00:00.2', 'subsecond')} {0.2} datetest 18.4 {julianday('-4713-11-24 13:40:48.864', 'subsec')} {0.07001} datetest 18.5 {typeof(unixepoch('now', 'subsecond'))} {real} + +# 2024-03-03 the 'ceiling' and 'floor' operators. +# +datetest 19.1 {date('2000-01-31','floor')} {2000-01-31} +datetest 19.2a {date('2000-02-31','floor')} {2000-02-29} +datetest 19.2b {date('1999-02-31','floor')} {1999-02-28} +datetest 19.2c {date('1900-02-31','floor')} {1900-02-28} +datetest 19.3 {date('2000-03-31','floor')} {2000-03-31} +datetest 19.4 {date('2000-04-31','floor')} {2000-04-30} +datetest 19.5 {date('2000-05-31','floor')} {2000-05-31} +datetest 19.6 {date('2000-06-31','floor')} {2000-06-30} +datetest 19.7 {date('2000-07-31','floor')} {2000-07-31} +datetest 19.8 {date('2000-08-31','floor')} {2000-08-31} +datetest 19.9 {date('2000-09-31','floor')} {2000-09-30} +datetest 19.10 {date('2000-10-31','floor')} {2000-10-31} +datetest 19.11 {date('2000-11-31','floor')} {2000-11-30} +datetest 19.12 {date('2000-12-31','floor')} {2000-12-31} +datetest 19.21 {date('2000-01-31','ceiling')} {2000-01-31} +datetest 19.22a {date('2000-02-31','ceiling')} {2000-03-02} +datetest 19.22b {date('1999-02-31','ceiling')} {1999-03-03} +datetest 19.22c {date('1900-02-31','ceiling')} {1900-03-03} +datetest 19.23 {date('2000-03-31','ceiling')} {2000-03-31} +datetest 19.24 {date('2000-04-31','ceiling')} {2000-05-01} +datetest 19.25 {date('2000-05-31','ceiling')} {2000-05-31} +datetest 19.26 {date('2000-06-31','ceiling')} {2000-07-01} +datetest 19.27 {date('2000-07-31','ceiling')} {2000-07-31} +datetest 19.28 {date('2000-08-31','ceiling')} {2000-08-31} +datetest 19.29 {date('2000-09-31','ceiling')} {2000-10-01} +datetest 19.30 {date('2000-10-31','ceiling')} {2000-10-31} +datetest 19.31 {date('2000-11-31','ceiling')} {2000-12-01} +datetest 19.32 {date('2000-12-31','ceiling')} {2000-12-31} +datetest 19.40 {date('2024-01-31','+1 month','ceiling')} {2024-03-02} +datetest 19.41 {date('2024-01-31','+1 month','floor')} {2024-02-29} +datetest 19.42 {date('2023-01-31','+1 month','ceiling')} {2023-03-03} +datetest 19.43 {date('2023-01-31','+1 month','floor')} {2023-02-28} +datetest 19.44 {date('2024-02-29','+1 year','ceiling')} {2025-03-01} +datetest 19.45 {date('2024-02-29','+1 year','floor')} {2025-02-28} +datetest 19.46 {date('2024-02-29','-110 years','ceiling')} {1914-03-01} +datetest 19.47 {date('2024-02-29','-110 years','floor')} {1914-02-28} +datetest 19.48 {date('2024-02-29','-0110-00-00','floor')} {1914-02-28} +datetest 19.49 {date('2024-02-29','-0110-00-00','ceiling')} {1914-03-01} +datetest 19.50 {date('2000-08-31','+0023-06-00','floor')} {2024-02-29} +datetest 19.51 {date('2000-08-31','+0022-06-00','floor')} {2023-02-28} +datetest 19.52 {date('2000-08-31','+0023-06-00','ceiling')} {2024-03-02} +datetest 19.53 {date('2000-08-31','+0022-06-00','ceiling')} {2023-03-03} + + finish_test diff --git a/test/date4.test b/test/date4.test index 0d820a0a4..56a9090b1 100644 --- a/test/date4.test +++ b/test/date4.test @@ -24,12 +24,12 @@ ifcapable {!datetime} { } if {$tcl_platform(os)=="Linux"} { - set FMT {%d,%e,%F,%H,%k,%I,%l,%j,%m,%M,%u,%w,%W,%Y,%%,%P,%p} + set FMT {%d,%e,%F,%H,%k,%I,%l,%j,%m,%M,%u,%w,%W,%Y,%%,%P,%p,%U,%V,%G,%g} } else { set FMT {%d,%e,%F,%H,%I,%j,%p,%R,%u,%w,%W,%%} } -for {set i 0} {$i<=24854} {incr i} { - set TS [expr {$i*86401}] +for {set i 0} {$i<=24858} {incr i} { + set TS [expr {$i*86390}] do_execsql_test date4-$i { SELECT strftime($::FMT,$::TS,'unixepoch'); } [list [strftime $FMT $TS]] diff --git a/test/default.test b/test/default.test index de67f643b..192b3d2ff 100644 --- a/test/default.test +++ b/test/default.test @@ -135,10 +135,10 @@ reset_db do_catchsql_test default-5.1 { CREATE TABLE t1 (a,b DEFAULT(random() NOTNULL IN (RAISE(IGNORE),2,3))); INSERT INTO t1(a) VALUES(1); -} {1 {RAISE() may only be used within a trigger-program}} +} {1 {default value of column [b] is not constant}} do_catchsql_test default-5.2 { CREATE TABLE Table0 (Col0 DEFAULT (RAISE(IGNORE) ) ) ; INSERT INTO Table0 DEFAULT VALUES ; -} {1 {RAISE() may only be used within a trigger-program}} +} {1 {default value of column [Col0] is not constant}} finish_test diff --git a/test/distinctagg.test b/test/distinctagg.test index 199ca0666..9eedd35bd 100644 --- a/test/distinctagg.test +++ b/test/distinctagg.test @@ -95,7 +95,7 @@ foreach {tn use_eph sql res} { 7 0 "SELECT count(DISTINCT a) FROM t2, t1" 5 8 1 "SELECT count(DISTINCT a+b) FROM t1, t2, t2, t2" 6 9 0 "SELECT count(DISTINCT c) FROM t1 WHERE c=2" 1 - 10 1 "SELECT count(DISTINCT t1.rowid) FROM t1, t2" 10 + 10 0 "SELECT count(DISTINCT t1.rowid) FROM t1, t2" 10 } { do_test 3.$tn.1 { set prg [db eval "EXPLAIN $sql"] @@ -148,6 +148,10 @@ do_execsql_test 3.0 { CREATE TABLE t3(x, y, z); INSERT INTO t3 VALUES(1,1,1); INSERT INTO t3 VALUES(2,2,2); + + CREATE TABLE t4(a); + CREATE INDEX t4a ON t4(a); + INSERT INTO t4 VALUES(1), (2), (2), (3), (1); } foreach {tn use_eph sql res} { @@ -158,6 +162,9 @@ foreach {tn use_eph sql res} { 4 0 "SELECT count(DISTINCT f) FROM t2 GROUP BY d, e" {1 2 2 3} 5 1 "SELECT count(DISTINCT f) FROM t2 GROUP BY d" {2 3} 6 0 "SELECT count(DISTINCT f) FROM t2 WHERE d IS 1 GROUP BY e" {1 2 2} + + 7 0 "SELECT count(DISTINCT a) FROM t1" {4} + 8 0 "SELECT count(DISTINCT a) FROM t4" {3} } { do_test 4.$tn.1 { set prg [db eval "EXPLAIN $sql"] diff --git a/test/e_reindex.test b/test/e_reindex.test index 00291b76a..50d2e7d51 100644 --- a/test/e_reindex.test +++ b/test/e_reindex.test @@ -73,12 +73,12 @@ sqlite3 db test.db do_execsql_test e_reindex-1.3 { PRAGMA integrity_check; } [list \ + {wrong # of entries in index i2} \ + {wrong # of entries in index i1} \ {row 3 missing from index i2} \ {row 3 missing from index i1} \ {row 4 missing from index i2} \ - {row 4 missing from index i1} \ - {wrong # of entries in index i2} \ - {wrong # of entries in index i1} + {row 4 missing from index i1} ] do_execsql_test e_reindex-1.4 { diff --git a/test/eqp.test b/test/eqp.test index 61fd617d8..cd441c271 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -413,7 +413,7 @@ do_eqp_test 4.2.3 { | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY } do_eqp_test 4.2.4 { SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 1 @@ -425,7 +425,7 @@ do_eqp_test 4.2.4 { | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY } do_eqp_test 4.2.5 { SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 1 @@ -437,7 +437,7 @@ do_eqp_test 4.2.5 { | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY } do_eqp_test 4.3.1 { diff --git a/test/eqp2.test b/test/eqp2.test new file mode 100644 index 000000000..3c634fc28 --- /dev/null +++ b/test/eqp2.test @@ -0,0 +1,49 @@ +# 2024 March 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 + +set testprefix eqp2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c, d); + CREATE INDEX i1 ON t1(a, b, c); +} + +do_eqp_test 1.1 { + SELECT * FROM t1 ORDER BY a, b, c +} { + QUERY PLAN + `--SCAN t1 USING INDEX i1 +} + + +do_eqp_test 1.2 { + SELECT * FROM t1 ORDER BY a, b, +c +} { + QUERY PLAN + |--SCAN t1 USING INDEX i1 + `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY +} + +do_eqp_test 1.3 { + SELECT * FROM t1 ORDER BY a, +b, +c +} { + QUERY PLAN + |--SCAN t1 USING INDEX i1 + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY +} + +finish_test + + diff --git a/test/exprfault2.test b/test/exprfault2.test new file mode 100644 index 000000000..acbead59f --- /dev/null +++ b/test/exprfault2.test @@ -0,0 +1,35 @@ +# 2024-05-11 +# +# 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 exprfault2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a,b,c,d,f,PRIMARY KEY(b,b)); + CREATE TABLE t2(x INT PRIMARY KEY, y, z); + CREATE TABLE t3(a,b,c,d,e,PRIMARY KEY(a,b))WITHOUT ROWID; +} +faultsim_save_and_close + + +do_faultsim_test 1 -faults oom-t* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + UPDATE t3 SET (d,d,d,d, a )=(SELECT EXISTS(SELECT 1 NOT IN(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT (SELECT max( 1 IN(SELECT x NOT IN(SELECT 1 NOT IN(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT (SELECT max( (SELECT x NOT IN(SELECT 1 NOT IN(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT (SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT (SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) a)| (SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(ORDER BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) z)|9 AS blob) IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))EXCEPT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) z) ORDER BY 1) IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT (SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 x ORDER BY 1)))EXCEPT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) a)| (SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) z)|9 AS blob) IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))EXCEPT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) z) ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 5 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(ORDER BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) z)| 1 AS blob) IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) z)| (SELECT 1 IN(SELECT max( 1 IN(SELECT c ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT (SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) e)|9 AS blob) FROM t2 WHERE aone
    } +#------------------------------------------------------------------------- + +do_execsql_test 3.0 { +CREATE VIRTUAL TABLE f USING fts3(a,b); +INSERT INTO f VALUES (101,x'056522650565056505650d051e056505650565286505650565056505056505650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef65056505844c746e65650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056f05650565056505650565056505650565050565056505640565056505650565056505651e05650565056505650565056505650505656565056505650565056505651e0565056505650565056505650565052265056505650569056505650565056505650565056505650565056505650500406505650565056505650565056505000101e5c501014b010101c501c5c501010101f5010201010101014101017373737373737373737373737373737373737373737373737373737330737373737373737373737373737365056505650d051e05650565056528056505650d05650505656505650565650565056505650565056505e505650565056505656505650565056505650d05650505656505650565ef65056505844c746e65650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565286505c705650565050565059494949494949494949494949494949494949494949494949494949494949494949494650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef650565058405056505650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056505650565056505650505650565056505650565056505650565056500000000000000000000000000000000000000000000000000000000000000000565056505656505650565056505650d056500000000000000000000000000000000000000000000000000000000000000000100000000000000ed0000000000ffffffffffffffffffffff0007ffffff0001c5c50001c5c50001c5c50001c5c50001c5c50001c5c50001e5c50001c5c50001c5c50001c5c50001c5c50001c5c5000100000014720000000000000016dac5c50001c5c50001c5c50001c5c50001c5c50001c5c50d0505656505650565ef650565058405056505650565056505650565056505650565056505650565058405800465056505650565056505651e650565056505650565056505650d05650505656505650565050565650584058005650565056505650565056505650565056522650565056505650d051e056505650561286505c70565056505056505650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef650565058405056505650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056505650565056505650565226505737373737373737373737373737373737373737373737373737373737373737373737373737373737373737373737373733a73737373737373737373737373737373737373737373737373737373737373737373737373737373737c7373737365056505650d051e05650565056528650565056505650505650565056505650565056505650565056505e505650565056505656505650565056505650d05650505656505650565ef65056505'); +} + +do_execsql_test 3.1 { + SELECT length(snippet(f)) FROM f WHERE b MATCH x'0565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056505650565056505650565056505650565050565056505640565056505650565056505651e05650565056522650565056505650d051e056505650565286505650565056505056505650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef65056505844c746e65650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056f05650565056505650565056505650565050565056505640565056505650565056505651e05650565056505650565056505650505656565056505650565056505651e0565056505650565056505650565052265056505650569056505650565056505650565056505650565056505650500406505650565056505650565056505000101e5c501014b010101c501c5c501010101f50102010101010141010141010001017bf15905000000000017'; +} {192} + set sqlite_fts3_enable_parentheses 0 finish_test diff --git a/test/func2.test b/test/func2.test index 08ad85750..a7c7ec3fd 100644 --- a/test/func2.test +++ b/test/func2.test @@ -508,4 +508,27 @@ do_test func2-3.9.2 { bin_to_hex [lindex $blob 0] } "12" +#------------------------------------------------------------------------- +# At one point this was extremely slow to compile. +# +do_test func2-3.10 { + set tm [time { + execsql { + SELECT '' IN (zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(1) + ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + } + }] + + set tm [lindex $tm 0] + expr $tm<2000000 +} {1} + finish_test diff --git a/test/func4.test b/test/func4.test index 64c7a11ed..56cc9063a 100644 --- a/test/func4.test +++ b/test/func4.test @@ -1,4 +1,4 @@ -# 2013 March 10 +# 2023-03-10 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: @@ -9,7 +9,10 @@ # #*********************************************************************** # This file implements regression tests for SQLite library. The focus of -# this file is testing the tointeger() and toreal() functions. +# this file is testing the tointeger() and toreal() functions that are +# part of the "totype.c" extension. This file does not test the core +# SQLite library. Failures of tests in this file are related to the +# ext/misc/totype.c extension. # # Several of the toreal() tests are disabled on platforms where floating # point precision is not high enough to represent their constant integer @@ -23,6 +26,20 @@ load_static_extension db totype set highPrecision(1) [expr \ {[db eval {SELECT tointeger(9223372036854775807 + 1);}] eq {{}}}] +set highPrecision(2) [expr \ + {[db eval {SELECT toreal(-9223372036854775808 + 1);}] eq {{}}}] + +# highPrecision(3) is only known to be false on i586 with gcc-13 and -O2. +# It is true on the exact same platform with -O0. Both results seem +# reasonable, so we'll just very the expectation accordingly. +# +set highPrecision(3) [expr \ + {[db eval {SELECT toreal(9007199254740992 + 1);}] eq {{}}}] + +if {!$highPrecision(1) || !$highPrecision(2) || !$highPrecision(3)} { + puts "NOTICE: use_long_double: [use_long_double] \ + highPrecision: $highPrecision(1) $highPrecision(2) $highPrecision(3)" +} do_execsql_test func4-1.1 { SELECT tointeger(NULL); @@ -195,8 +212,6 @@ do_execsql_test func4-1.55 { } {{}} ifcapable floatingpoint { - set highPrecision(2) [expr \ - {[db eval {SELECT toreal(-9223372036854775808 + 1);}] eq {{}}}] do_execsql_test func4-2.1 { SELECT toreal(NULL); @@ -341,10 +356,14 @@ ifcapable floatingpoint { do_execsql_test func4-2.45 { SELECT toreal(9007199254740992); } {9007199254740992.0} - if {$highPrecision(2)} { + if {$highPrecision(3)} { do_execsql_test func4-2.46 { SELECT toreal(9007199254740992 + 1); } {{}} + } else { + do_execsql_test func4-2.46 { + SELECT toreal(9007199254740992 + 1); + } {9007199254740992.0} } do_execsql_test func4-2.47 { SELECT toreal(9007199254740992 + 2); @@ -626,10 +645,14 @@ ifcapable floatingpoint { do_execsql_test func4-5.22 { SELECT tointeger(toreal(9007199254740992)); } {9007199254740992} - if {$highPrecision(2)} { + if {$highPrecision(3)} { do_execsql_test func4-5.23 { SELECT tointeger(toreal(9007199254740992 + 1)); } {{}} + } else { + do_execsql_test func4-5.23 { + SELECT tointeger(toreal(9007199254740992 + 1)); + } {9007199254740992} } do_execsql_test func4-5.24 { SELECT tointeger(toreal(9007199254740992 + 2)); diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index e4ad1c113..6cae348bd 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -979,7 +979,8 @@ extern int fuzz_invariant( int iRow, /* The row number for pStmt */ int nRow, /* Total number of output rows */ int *pbCorrupt, /* IN/OUT: Flag indicating a corrupt database file */ - int eVerbosity /* How much debugging output */ + int eVerbosity, /* How much debugging output */ + unsigned int dbOpt /* Default optimization flags */ ); /* Implementation of sqlite_dbdata and sqlite_dbptr */ @@ -1031,7 +1032,12 @@ static int recoverDatabase(sqlite3 *db){ /* ** Run the SQL text */ -static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){ +static int runDbSql( + sqlite3 *db, /* Run SQL on this database connection */ + const char *zSql, /* The SQL to be run */ + unsigned int *pBtsFlags, + unsigned int dbOpt /* Default optimization flags */ +){ int rc; sqlite3_stmt *pStmt; int bCorrupt = 0; @@ -1107,7 +1113,7 @@ static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){ iRow++; for(iCnt=0; iCnt<99999; iCnt++){ rc = fuzz_invariant(db, pStmt, iCnt, iRow, nRow, - &bCorrupt, eVerbosity); + &bCorrupt, eVerbosity, dbOpt); if( rc==SQLITE_DONE ) break; if( rc!=SQLITE_ERROR ) g.nInvariant++; if( eVerbosity>0 ){ @@ -1330,7 +1336,7 @@ int runCombinedDbSqlInput( char cSaved = zSql[i+1]; zSql[i+1] = 0; if( sqlite3_complete(zSql+j) ){ - rc = runDbSql(cx.db, zSql+j, &btsFlags); + rc = runDbSql(cx.db, zSql+j, &btsFlags, dbOpt); j = i+1; } zSql[i+1] = cSaved; @@ -1340,7 +1346,7 @@ int runCombinedDbSqlInput( } } if( j100 ) return SQLITE_DONE; zTest = fuzz_invariant_sql(pStmt, iCnt); if( zTest==0 ) return SQLITE_DONE; + if( noOpt ){ + sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, db, ~dbOpt); + } rc = sqlite3_prepare_v2(db, zTest, -1, &pTestStmt, 0); + if( noOpt ){ + sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, db, dbOpt); + } if( rc ){ if( eVerbosity ){ printf("invariant compile failed: %s\n%s\n", @@ -212,7 +226,7 @@ int fuzz_invariant( } sqlite3_finalize(pCk); if( rc==SQLITE_DONE ){ - reportInvariantFailed(pStmt, pTestStmt, iRow); + reportInvariantFailed(pStmt, pTestStmt, iRow, dbOpt, noOpt); return SQLITE_INTERNAL; }else if( eVerbosity>0 ){ printf("invariant-error ignored due to the use of virtual tables\n"); @@ -223,7 +237,6 @@ int fuzz_invariant( return SQLITE_OK; } - /* ** Generate SQL used to test a statement invariant. ** @@ -296,14 +309,6 @@ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ ** WHERE clause. */ continue; } -#ifdef SQLITE_ALLOW_ROWID_IN_VIEW - if( sqlite3_strlike("%rowid%",zColName,0)==0 - || sqlite3_strlike("%oid%",zColName,0)==0 - ){ - /* ROWID values are unreliable if SQLITE_ALLOW_ROWID_IN_VIEW is used */ - continue; - } -#endif for(j=0; j>'$.a.b' FROM t1; +} {5} +db null NULL +do_execsql_test indexexpr1-2211 { + CREATE INDEX t1j ON t1(coalesce(null,json(y))); + SELECT json_insert('{}', '$.a', coalesce(null,json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2220 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', iif(1,json(y),123))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2221 { + CREATE INDEX t1j ON t1(iif(1,json(y),123)); + SELECT json_insert('{}', '$.a', iif(1,json(y),123))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2230 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', ifnull(NULL,json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2231 { + CREATE INDEX t1j ON t1(ifnull(NULL,json(y))); + SELECT json_insert('{}', '$.a', ifnull(NULL,json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2240 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', nullif(json(y),8))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2241 { + CREATE INDEX t1j ON t1(nullif(json(y),8)); + SELECT json_insert('{}', '$.a', nullif(json(y),8))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2250 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', min('~',json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2251 { + CREATE INDEX t1j ON t1(min('~',json(y))); + SELECT json_insert('{}', '$.a', min('~',json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2260 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', max('...',json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2261 { + CREATE INDEX t1j ON t1(max('...',json(y))); + SELECT json_insert('{}', '$.a', max('...',json(y)))->>'$.a.b' FROM t1; +} {5} + # 2023-11-08 Forum post https://sqlite.org/forum/forumpost/68d284c86b082c3e # diff --git a/test/json102.test b/test/json102.test index 15a54b47c..1a00cb67a 100644 --- a/test/json102.test +++ b/test/json102.test @@ -764,4 +764,35 @@ do_execsql_test json102-1720 { SELECT * FROM t1; } {ok 2023-08-03 876 5 {{"x":77,"y":6}}} +# 2024-05-21 https://sqlite.org/forum/forumpost/9e52cdfe15c3926e +# What if the RHS of the -> or ->> operator is a string that looks +# like a number? PostgreSQL treats it as a string. +# +do_execsql_test json102-1800 { + SELECT '{"1":"one","2":"two","3":"three"}'->>'2'; +} two +db null NULL +do_execsql_test json102-1801 { + SELECT '{"1":"one","2":"two","3":"three"}'->>2; +} NULL +do_execsql_test json102-1810 { + SELECT '["zero","one","two"]'->>'1'; +} NULL +do_execsql_test json102-1811 { + SELECT '["zero","one","two"]'->>1; +} one +do_execsql_test json102-1820 { + SELECT '{"1":"one","2":"two","3":"three"}'->'2'; +} {{"two"}} +do_execsql_test json102-1821 { + SELECT '{"1":"one","2":"two","3":"three"}'->2; +} {NULL} +do_execsql_test json102-1830 { + SELECT '["zero","one","two"]'->'1'; +} {NULL} +do_execsql_test json102-1831 { + SELECT '["zero","one","two"]'->1; +} {{"one"}} + + finish_test diff --git a/test/json106.test b/test/json106.test index 23fa02843..06859a10b 100644 --- a/test/json106.test +++ b/test/json106.test @@ -67,6 +67,12 @@ for {set ii 1} {$ii<=5000} {incr ii} { FROM t1, kv WHERE p->>key IS NOT val } 0 + do_execsql_test $ii.8 { + SELECT j0 FROM t1 WHERE json(j0)!=json(json_pretty(j0)); + } {} + do_execsql_test $ii.9 { + SELECT j5 FROM t1 WHERE json(j5)!=json(json_pretty(j5)); + } {} } diff --git a/test/json108.test b/test/json108.test new file mode 100644 index 000000000..71f3814dc --- /dev/null +++ b/test/json108.test @@ -0,0 +1,45 @@ +# 2024-03-06 +# +# 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. +# +#*********************************************************************** +# Invariant tests for JSON built around the randomjson extension +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix json108 + +# These tests require virtual table "json_tree" to run. +ifcapable !vtab { finish_test ; return } + +load_static_extension db randomjson +db eval { + CREATE TEMP TABLE t1(j0,j5); + WITH RECURSIVE c(n) AS (VALUES(0) UNION ALL SELECT n+1 FROM c WHERE n<9) + INSERT INTO t1 SELECT random_json(n), random_json5(n) FROM c; +} + +do_execsql_test 1.1 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,NULL)); +} 10 +do_execsql_test 1.2 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,NULL)); +} 10 +do_execsql_test 1.3 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,'')); +} 10 +do_execsql_test 1.4 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,char(9))); +} 10 +do_execsql_test 1.5 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,'/*hello*/')); +} 10 + + +finish_test diff --git a/test/json501.test b/test/json501.test index 40b3b563d..bfd21055a 100644 --- a/test/json501.test +++ b/test/json501.test @@ -306,4 +306,31 @@ do_execsql_test 13.1 { SELECT json('{x:''a "b" c''}'); } {{{"x":"a \"b\" c"}}} +# 2024-01-31 +# Allow control characters within JSON5 string literals. +# +for {set c 1} {$c<=0x1f} {incr c} { + do_execsql_test 14.$c.1 { + SELECT json_valid('"abc' || char($c) || 'xyz"'); + } {0} + do_execsql_test 14.$c.2 { + SELECT json_valid('"abc' || char($c) || 'xyz"', 2); + } {1} + switch $c { + 8 {set e "\\b"} + 9 {set e "\\t"} + 10 {set e "\\n"} + 12 {set e "\\f"} + 13 {set e "\\r"} + default {set e [format "\\u00%02x" $c]} + } + do_execsql_test 14.$c.3 { + SELECT json('{label:"abc' || char($c) || 'xyz"}'); + } "{{\"label\":\"abc${e}xyz\"}}" + do_execsql_test 14.$c.4 { + SELECT jsonb('{label:"abc' || char($c) || 'xyz"}') -> '$'; + } "{{\"label\":\"abc${e}xyz\"}}" +} + + finish_test diff --git a/test/lemon-test01.y b/test/lemon-test01.y index 0fd514ff3..67890c637 100644 --- a/test/lemon-test01.y +++ b/test/lemon-test01.y @@ -2,6 +2,11 @@ // // lemon lemon-test01.y && gcc -g lemon-test01.c && ./a.out // +// This testcase was made obsolete by check-in 7cca80808cef192f on +// 2021-08-17 (associated with Forum Thread +// https://sqlite.org/forum/forumpost/bd91fd965c9803c4) and no longer +// works. It is retained for historical reference only. +// %token_prefix TK_ %token_type int %default_type int @@ -28,7 +33,7 @@ all ::= error B. #include "lemon-test01.h" static int nTest = 0; static int nErr = 0; - static int testCase(int testId, int shouldBe, int actual){ + static void testCase(int testId, int shouldBe, int actual){ nTest++; if( shouldBe==actual ){ printf("test %d: ok\n", testId); diff --git a/test/literal.test b/test/literal.test new file mode 100644 index 000000000..5aa331e39 --- /dev/null +++ b/test/literal.test @@ -0,0 +1,103 @@ +# 2024-01-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. +# +#*********************************************************************** +# +# This file implements tests for SQL literals + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix literal + +proc test_literal {tn lit type val} { + do_execsql_test $tn.1 "SELECT typeof( $lit ), $lit" [list $type $val] + + ifcapable altertable { + do_execsql_test $tn.2 " + DROP TABLE IF EXISTS x1; + CREATE TABLE x1(a); + INSERT INTO x1 VALUES(123); + ALTER TABLE x1 ADD COLUMN b DEFAULT $lit ; + SELECT typeof(b), b FROM x1; + " [list $type $val] + } + + do_execsql_test $tn.3 " + DROP TABLE IF EXISTS x1; + CREATE TABLE x1(a DEFAULT $lit); + INSERT INTO x1 DEFAULT VALUES; + SELECT typeof(a), a FROM x1; + " [list $type $val] +} + +proc test_literal_error {tn lit unrec} { + do_catchsql_test $tn "SELECT $lit" "1 {unrecognized token: \"$unrec\"}" +} + + +test_literal 1.0 45 integer 45 +test_literal 1.1 0xFF integer 255 +test_literal 1.2 0xFFFFFFFF integer [expr 0xFFFFFFFF] +test_literal 1.3 0x123FFFFFFFF integer [expr 0x123FFFFFFFF] +test_literal 1.4 -0x123FFFFFFFF integer [expr -1 * 0x123FFFFFFFF] +test_literal 1.5 0xFFFFFFFFFFFFFFFF integer -1 +test_literal 1.7 0x7FFFFFFFFFFFFFFF integer [expr 0x7FFFFFFFFFFFFFFF] +test_literal 1.8 -0x7FFFFFFFFFFFFFFF integer [expr -0x7FFFFFFFFFFFFFFF] +test_literal 1.9 +0x7FFFFFFFFFFFFFFF integer [expr +0x7FFFFFFFFFFFFFFF] +test_literal 1.10 -45 integer -45 +test_literal 1.11 '0xFF' text 0xFF +test_literal 1.12 '-0xFF' text -0xFF +test_literal 1.13 -'0xFF' integer 0 +test_literal 1.14 -9223372036854775808 integer -9223372036854775808 + +test_literal 2.1 1e12 real 1000000000000.0 +test_literal 2.2 1.0 real 1.0 +test_literal 2.3 1e1000 real Inf +test_literal 2.4 -1e1000 real -Inf + +test_literal 3.1 1_000 integer 1000 +test_literal 3.2 1.1_1 real 1.11 +test_literal 3.3 1_0.1_1 real 10.11 +test_literal 3.4 1e1_000 real Inf +test_literal 3.5 12_3_456.7_8_9 real 123456.789 +test_literal 3.6 9_223_372_036_854_775_807 integer 9223372036854775807 +test_literal 3.7 9_223_372_036_854_775_808 real 9.22337203685478e+18 +test_literal 3.8 -9_223_372_036_854_775_808 integer -9223372036854775808 + +foreach {tn lit unrec} { + 0 123a456 123a456 + 1 1_ 1_ + 2 1_.4 1_.4 + 3 1e_4 1e_4 + 4 1_e4 1_e4 + 5 1.4_e4 1.4_e4 + 6 1.4e+_4 1.4e + 7 1.4e-_4 1.4e + 8 1.4e4_ 1.4e4_ + 9 1.4_e4 1.4_e4 + 10 1.4e_4 1.4e_4 + 11 12__34 12__34 + 12 1234_ 1234_ + 13 12._34 12._34 + 14 12_.34 12_.34 + 15 12.34_ 12.34_ + 16 1.0e1_______2 1.0e1_______2 +} { + test_literal_error 4.$tn $lit $unrec +} + +# dbsqlfuzz e3186a9e7826e9cd7f4085aa4452f8696485f9e1 +# See tag-20240224-a and -b +# +do_catchsql_test 5.1 { + SELECT 1 ORDER BY 2_3; +} {1 {1st ORDER BY term out of range - should be between 1 and 1}} + +finish_test diff --git a/test/literal2.tcl b/test/literal2.tcl new file mode 100644 index 000000000..e14a03587 --- /dev/null +++ b/test/literal2.tcl @@ -0,0 +1,40 @@ +# 2018 May 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. +# +#*********************************************************************** +# + +source [file join [file dirname $argv0] pg_common.tcl] + +#========================================================================= + + +start_test literal2 "2024 Jan 23" + +execsql_test 1.0 { SELECT 123_456 } +errorsql_test 1.1 { SELECT 123__456 } + +execsql_float_test 2.1 { SELECT 1.0e1_2 } + + +execsql_test 3.0.0 { SELECT 0xFF_FF } +execsql_test 3.0.1 { SELECT 0xFF_EF } +errorsql_test 3.0.2 { SELECT 0xFF__EF } +# errorsql_test 3.0.3 { SELECT 0x_FFEF } +errorsql_test 3.0.4 { SELECT 0xFFEF_ } + +execsql_test 3.1.0 { SELECT 0XFF_FF } +execsql_test 3.1.1 { SELECT 0XFF_EF } +errorsql_test 3.1.2 { SELECT 0XFF__EF } +# errorsql_test 3.1.3 { SELECT 0X_FFEF } +errorsql_test 3.1.4 { SELECT 0XFFEF_ } + +finish_test + + diff --git a/test/literal2.test b/test/literal2.test new file mode 100644 index 000000000..ed177ca26 --- /dev/null +++ b/test/literal2.test @@ -0,0 +1,84 @@ +# 2024 Jan 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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +#################################################### +# DO NOT EDIT! THIS FILE IS AUTOMATICALLY GENERATED! +#################################################### + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix literal2 + +do_execsql_test 1.0 { + SELECT 123_456 +} {123456} + +# PG says ERROR: trailing junk after numeric literal at or near "123_" +do_test 1.1 { catch { execsql { + SELECT 123__456 +} } } 1 + + +do_test 2.1 { + set myres {} + foreach r [db eval {SELECT 1.0e1_2}] { + lappend myres [format %.4f [set r]] + } + set res2 {1000000000000.0000} + set i 0 + foreach r [set myres] r2 [set res2] { + if {[set r]<([set r2]-0.0001) || [set r]>([set r2]+0.0001)} { + error "list element [set i] does not match: got=[set r] expected=[set r2]" + } + incr i + } + set {} {} +} {} + +do_execsql_test 3.0.0 { + SELECT 0xFF_FF +} {65535} + +do_execsql_test 3.0.1 { + SELECT 0xFF_EF +} {65519} + +# PG says ERROR: trailing junk after numeric literal at or near "0xFF_" +do_test 3.0.2 { catch { execsql { + SELECT 0xFF__EF +} } } 1 + +# PG says ERROR: trailing junk after numeric literal at or near "0xFFEF_" +do_test 3.0.4 { catch { execsql { + SELECT 0xFFEF_ +} } } 1 + +do_execsql_test 3.1.0 { + SELECT 0XFF_FF +} {65535} + +do_execsql_test 3.1.1 { + SELECT 0XFF_EF +} {65519} + +# PG says ERROR: trailing junk after numeric literal at or near "0XFF_" +do_test 3.1.2 { catch { execsql { + SELECT 0XFF__EF +} } } 1 + +# PG says ERROR: trailing junk after numeric literal at or near "0XFFEF_" +do_test 3.1.4 { catch { execsql { + SELECT 0XFFEF_ +} } } 1 + +finish_test diff --git a/test/misc1.test b/test/misc1.test index 83acc752a..8110d3867 100644 --- a/test/misc1.test +++ b/test/misc1.test @@ -654,7 +654,7 @@ do_catchsql_test misc1-21.1 { } {1 {near "#0": syntax error}} do_catchsql_test misc1-21.2 { VALUES(0,0x0MATCH#0; -} {1 {near ";": syntax error}} +} {1 {unrecognized token: "0x0MATCH"}} # 2015-04-15 do_execsql_test misc1-22.1 { diff --git a/test/misc5.test b/test/misc5.test index f7c6048d9..84aa9586d 100644 --- a/test/misc5.test +++ b/test/misc5.test @@ -569,11 +569,11 @@ ifcapable subquery&&compound { } # Overflow the lemon parser stack by providing an overly complex -# expression. Make sure that the overflow is detected and reported. +# expression. Make sure that the overflow is detected and the +# stack is grown automatically such that the application calling +# SQLite never notices. # -# This test fails when building with -DYYSTACKDEPTH=0 -# -do_test misc5-7.1 { +do_test misc5-7.1.1 { execsql {CREATE TABLE t1(x)} set sql "INSERT INTO t1 VALUES(" set tail "" @@ -581,9 +581,21 @@ do_test misc5-7.1 { append sql "(1+" append tail ")" } - append sql 2$tail + append sql "0$tail); SELECT * FROM t1;" + catchsql $sql +} {0 200} +do_test misc5-7.1.2 { + execsql {DELETE FROM t1} + set sql "INSERT INTO t1 VALUES(" + set tail "" + for {set i 0} {$i<900} {incr i} { + append sql "(1+" + append tail ")" + } + append sql "0$tail); SELECT * FROM t1;" catchsql $sql -} {1 {parser stack overflow}} +} {0 900} + # Parser stack overflow is silently ignored when it occurs while parsing the # schema and PRAGMA writable_schema is turned on. diff --git a/test/mmapcorrupt.test b/test/mmapcorrupt.test index 70dbe8464..d434ec183 100644 --- a/test/mmapcorrupt.test +++ b/test/mmapcorrupt.test @@ -16,6 +16,10 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix mmapcorrupt +ifcapable !mmap { + finish_test + return +} database_may_be_corrupt db close diff --git a/test/orderby1.test b/test/orderby1.test index 7432b5473..41444a44c 100644 --- a/test/orderby1.test +++ b/test/orderby1.test @@ -520,7 +520,7 @@ do_eqp_test 8.1 { } { QUERY PLAN |--SCAN t1 USING INDEX i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY } do_execsql_test 8.2 { diff --git a/test/permutations.test b/test/permutations.test index 25aa7de01..c26d6ead1 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -95,6 +95,7 @@ foreach f [glob -nocomplain \ $testdir/../ext/lsm1/test/*.test \ $testdir/../ext/recover/*.test \ $testdir/../ext/rbu/*.test \ + $testdir/../ext/intck/*.test \ ] { lappend alltests $f } diff --git a/test/pragma.test b/test/pragma.test index 8f78a7e02..e823a6763 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -372,27 +372,30 @@ ifcapable attach { db close sqlite3 db test.db execsql {PRAGMA integrity_check} - } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + } {{wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2}} do_test pragma-3.3 { execsql {PRAGMA integrity_check=1} - } {{row 1 missing from index i2}} + } {{wrong # of entries in index i2}} do_test pragma-3.4 { execsql { ATTACH DATABASE 'test.db' AS t2; PRAGMA integrity_check } - } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + } {{wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2}} do_test pragma-3.5 { execsql { PRAGMA integrity_check=4 } - } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2}} + } {{wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + do_catchsql_test pragma-3.5.2 { + PRAGMA integrity_check='4' + } {1 {no such table: 4}} do_catchsql_test pragma-3.6 { PRAGMA integrity_check=xyz } {1 {no such table: xyz}} do_catchsql_test pragma-3.6b { PRAGMA integrity_check=t2 - } {0 {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}}} + } {0 {{wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2}}} do_catchsql_test pragma-3.6c { PRAGMA integrity_check=sqlite_schema } {0 ok} @@ -400,7 +403,7 @@ ifcapable attach { execsql { PRAGMA integrity_check=0 } - } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + } {{wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2}} # Add additional corruption by appending unused pages to the end of # the database file testerr.db @@ -435,10 +438,10 @@ ifcapable attach { } {{*** in database t2 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} +Page 6: never used} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2}} do_execsql_test pragma-3.9b { PRAGMA t2.integrity_check=t2; - } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + } {{wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2}} do_execsql_test pragma-3.9c { PRAGMA t2.integrity_check=sqlite_schema; } {ok} @@ -455,7 +458,7 @@ Page 4: never used}} } {{*** in database t2 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2}} +Page 6: never used} {wrong # of entries in index i2} {row 1 missing from index i2}} do_test pragma-3.12 { execsql { PRAGMA integrity_check=4 @@ -463,7 +466,7 @@ Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2}} } {{*** in database t2 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2}} +Page 6: never used} {wrong # of entries in index i2}} do_test pragma-3.13 { execsql { PRAGMA integrity_check=3 @@ -487,10 +490,10 @@ Page 5: never used}} } {{*** in database t2 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 6: never used} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {*** in database t3 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} +Page 6: never used} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2}} do_test pragma-3.16 { execsql { PRAGMA integrity_check(10) @@ -498,10 +501,10 @@ Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} } {{*** in database t2 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 6: never used} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {*** in database t3 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2}} +Page 6: never used} {wrong # of entries in index i2}} do_test pragma-3.17 { execsql { PRAGMA integrity_check=8 @@ -509,7 +512,7 @@ Page 6: never used} {row 1 missing from index i2}} } {{*** in database t2 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 6: never used} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {*** in database t3 *** Page 4: never used Page 5: never used}} do_test pragma-3.18 { @@ -519,7 +522,7 @@ Page 5: never used}} } {{*** in database t2 *** Page 4: never used Page 5: never used -Page 6: never used} {row 1 missing from index i2}} +Page 6: never used} {wrong # of entries in index i2}} } do_test pragma-3.19 { catch {db close} diff --git a/test/pragma4.test b/test/pragma4.test index 97c62261b..5360fbac4 100644 --- a/test/pragma4.test +++ b/test/pragma4.test @@ -97,7 +97,7 @@ do_test pragma4-2.100 { } string map {\[ x \] x \173 {} \175 {}} \ [db eval {EXPLAIN PRAGMA integrity_check}] -} {/ IntegrityCk 2 2 1 x[0-9]+,1x /} +} {/ IntegrityCk 1 2 8 x[0-9]+,1x /} #-------------------------------------------------------------------------- @@ -271,6 +271,8 @@ catch {db3 close} ifcapable vtab { reset_db do_execsql_test 6.0 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; CREATE TABLE t1(a INT PRIMARY KEY, b INT); CREATE TABLE t2(c INT PRIMARY KEY, d INT REFERENCES t1); SELECT t.name, f."table", f."from", i.name, i.pk @@ -281,4 +283,32 @@ ifcapable vtab { } {t2 t1 d a 1} } +# 2024-05-08 https://sqlite.org/forum/forumpost/cf29a33e94 +# +ifcapable vtab { + do_execsql_test 7.0 { + CREATE TABLE t3 ("a" TEXT, "b" TEXT); + CREATE TABLE t4 ("a" TEXT, "b" TEXT, "c" TEXT); + } + + do_execsql_test 7.1 { + CREATE TABLE pragma_t3 AS SELECT * FROM pragma_table_info('t3'); + CREATE TABLE pragma_t4 AS SELECT * FROM pragma_table_info('t4'); + } + + do_execsql_test 7.2 { + SELECT pragma_t4.name, pragma_t3.name + FROM pragma_t4 RIGHT JOIN pragma_t3 ON (pragma_t4.name=pragma_t3.name); + } {a a b b} + + do_execsql_test 7.3 { + SELECT t4.name, t3.name + FROM pragma_table_info('t4') t4 + RIGHT JOIN pragma_table_info('t3') t3 ON (t4.name=t3.name); + } {a a b b} +} + + + + finish_test diff --git a/test/pragma6.test b/test/pragma6.test new file mode 100644 index 000000000..fc5566af1 --- /dev/null +++ b/test/pragma6.test @@ -0,0 +1,74 @@ +# 2024 February 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 tests for PRAGMAs quick_check and integrity_check. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix pragma6 + +database_may_be_corrupt + +#------------------------------------------------------------------------- +# +do_test 1.0 { + sqlite3 db {} + db deserialize [decode_hexdb { + .open --hexdb + | size 12288 pagesize 4096 filename crash-540f4c1eb1e7ac.db + | page 1 offset 0 + | 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. + | 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 03 .....@ ........ + | 32: 00 bb 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + | 96: 00 00 00 00 0d 00 00 00 02 0f 7f 00 0f c3 0f 7f ................ + | 3952: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 42 ...............B + | 3968: 02 06 17 11 11 01 71 74 61 62 6c 65 74 32 74 32 ......qtablet2t2 + | 3984: 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 74 32 .CREATE TABLE t2 + | 4000: 28 61 20 49 4e 54 2c 20 62 20 41 53 20 28 61 2a (a INT, b AS (a* + | 4016: 32 29 20 53 54 4f 52 45 44 20 4e 4f 54 20 4e 55 2) STORED NOT NU + | 4032: 4c 4c 29 3b 01 06 17 11 11 01 63 74 61 62 6c 65 LL);......ctable + | 4048: 74 31 74 31 02 43 52 45 41 54 45 20 54 41 42 4c t1t1.CREATE TABL + | 4064: 45 20 74 31 28 61 20 49 4e 54 2c 20 62 20 41 53 E t1(a INT, b AS + | 4080: 20 28 61 2a 32 29 20 4e 4f 54 20 4e 55 4c 4c 29 (a*2) NOT NULL) + | page 2 offset 4096 + | 0: 0d 00 00 00 05 0f e7 00 00 00 00 00 00 00 00 00 ................ + | 4064: 00 00 00 00 00 00 00 00 03 05 02 01 05 03 04 02 ................ + | 4080: 01 04 03 03 02 01 03 03 02 02 01 02 02 01 02 09 ................ + | page 3 offset 8192 + | 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + | 4048: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 05 ................ + | 4064: 03 01 01 05 0a 05 04 03 01 01 04 08 05 03 03 01 ................ + | 4080: 01 03 06 05 02 03 00 00 00 00 00 00 00 00 00 00 ................ + | end crash-540f4c1eb1e7ac.db + }] +} {} + +do_test 1.1 { + execsql { + CREATE TEMP TABLE t2( + a t1 PRIMARY KEY default 27, + b default(current_timestamp), + d TEXT UNIQUE DEFAULT 'ch`arlie', + c TEXT UNIQUE DEFAULT 084, + UNIQUE(c,b,b,a,b) + ) WITHOUT ROWID; + } + catchsql { INSERT INTO t1(a) VALUES(zeroblob(40000)) } + set {} {} +} {} + +do_test 1.2 { + execsql { PRAGMA integrity_check; } + execsql { PRAGMA quick_check; } + set {} {} +} {} + +finish_test diff --git a/test/pushdown.test b/test/pushdown.test index 1fbe6f34c..5c3e8182d 100644 --- a/test/pushdown.test +++ b/test/pushdown.test @@ -1,4 +1,4 @@ -# 2017 April 29 +# 2017-04-29 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: @@ -8,6 +8,26 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# +# Test cases for the push-down optimizations. +# +# +# There are two different meanings for "push-down optimization". +# +# (1) "MySQL push-down" means that WHERE clause terms that can be +# evaluated using only the index and without reference to the +# table are run first, so that if they are false, unnecessary table +# seeks are avoided. See https://sqlite.org/src/info/d7bb79ed3a40419d +# from 2017-04-29. +# +# (2) "WHERE-clause pushdown" means to push WHERE clause terms in +# outer queries down into subqueries. See +# https://sqlite.org/src/info/6df18e949d367629 from 2015-06-02. +# +# This module started out as tests for MySQL push-down only. But because +# of naming ambiguity, it has picked up test cases for WHERE-clause push-down +# over the years. +# set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -87,8 +107,8 @@ do_test 2.2 { } {three} # 2022-11-25 dbsqlfuzz crash-3a548de406a50e896c1bf7142692d35d339d697f -# Disable the push-down optimization for compound subqueries if any -# arm of the compound has an incompatible affinity. +# Disable the WHERE-clause push-down optimization for compound subqueries +# if any arm of the compound has an incompatible affinity. # reset_db do_execsql_test 3.1 { @@ -185,7 +205,7 @@ do_eqp_test 3.8 { # SELECT (SELECT count(*) FROM t1)+(SELECT count(*) FROM t2) # 2023-05-09 https://sqlite.org/forum/forumpost/a7d4be7fb6 -# Restriction (9) on the push-down optimization. +# Restriction (9) on the WHERE-clause push-down optimization. # reset_db db null - @@ -227,4 +247,89 @@ do_execsql_test 5.0 { WHERE e>0; } {- - 3 4 5} + +# 2024-04-05 +# Allow push-down of operators of the form "expr IN table". +# +reset_db +do_execsql_test 6.0 { + CREATE TABLE t01(w,x,y,z); + CREATE TABLE t02(w,x,y,z); + CREATE VIEW t0(w,x,y,z) AS + SELECT w,x,y,z FROM t01 UNION ALL SELECT w,x,y,z FROM t02; + CREATE INDEX t01x ON t01(w,x,y); + CREATE INDEX t02x ON t02(w,x,y); + CREATE VIEW v1(k) AS VALUES(77),(88),(99); + CREATE TABLE k1(k); + INSERT INTO k1 SELECT * FROM v1; +} +do_eqp_test 6.1 { + WITH k(n) AS (VALUES(77),(88),(99)) + SELECT max(z) FROM t0 WHERE w=123 AND x IN k AND y BETWEEN 44 AND 55; +} { + QUERY PLAN + |--CO-ROUTINE t0 + | `--COMPOUND QUERY + | |--LEFT-MOST SUBQUERY + | | |--SEARCH t01 USING INDEX t01x (w=? AND x=? AND y>? AND y? AND y? AND y? AND y? AND y? AND y3 + RETURNING a, + (SELECT min(a) FROM t1), + (SELECT max(a) FROM t1), + (SELECT round(avg(a),2) FROM t1); + ROLLBACK; +} { + 1 2 8 4.6 + 2 3 8 5.25 + 4 3 8 5.67 + 6 3 8 5.5 + 8 3 3 3.0 +} +do_execsql_test 20.2 { + BEGIN; + DELETE FROM t1 + RETURNING a, + (SELECT min(a) FROM t1), + (SELECT max(a) FROM t1), + (SELECT round(avg(a),2) FROM t1); + ROLLBACK; +} { + 1 2 8 4.6 + 2 3 8 5.25 + 3 4 8 6.0 + 4 6 8 7.0 + 6 8 8 8.0 + 8 N N N +} +do_execsql_test 20.3 { + BEGIN; + DELETE FROM t1 + RETURNING a, + (SELECT min(t2.a)+t1.a*100 FROM t1 AS t2), + (SELECT max(t2.a)+t1.a*100 FROM t1 AS t2), + (SELECT round(avg(t2.a),2)+t1.a*100 FROM t1 AS t2); + ROLLBACK; +} { + 1 102 108 104.6 + 2 203 208 205.25 + 3 304 308 306.0 + 4 406 408 407.0 + 6 608 608 608.0 + 8 N N N +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 21.0 { + PRAGMA writable_schema=ON; + INSERT INTO sqlite_schema DEFAULT VALUES RETURNING sqlite_schema.name; +} {{}} + +do_execsql_test 21.1 { + INSERT INTO sqlite_temp_schema DEFAULT VALUES RETURNING sqlite_temp_schema.name; +} {{}} + finish_test diff --git a/test/scanstatus2.test b/test/scanstatus2.test index ca3a42ffe..7f107cd2e 100644 --- a/test/scanstatus2.test +++ b/test/scanstatus2.test @@ -247,7 +247,7 @@ QUERY (nCycle=nnn) ----SCAN rt2 (nCycle=nnn) ----USE TEMP B-TREE FOR GROUP BY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) ---CREATE AUTOMATIC INDEX ON v1(x1, cnt, x1) (nCycle=nnn) +--CREATE AUTOMATIC INDEX ON v1(x1, cnt) (nCycle=nnn) --BLOOM FILTER ON v1 (x1=?) --SEARCH v1 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) } @@ -328,6 +328,17 @@ QUERY (nCycle=nnn) --SCAN xy2 (nCycle=nnn) } +#------------------------------------------------------------------------- +reset_db + +# Check that an OOB parameter (45) does not cause asan or valgrind errors. +# +do_test 7.0 { + db eval {SELECT * FROM sqlite_schema} + set stmt [db version -last-stmt-ptr] + sqlite3_stmt_scanstatus -flags complex $stmt 1000000 +} {} + #explain_i { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 } #puts_debug_info { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 } diff --git a/test/selectA.test b/test/selectA.test index 7d72bb3fa..91b154848 100644 --- a/test/selectA.test +++ b/test/selectA.test @@ -1340,10 +1340,10 @@ do_eqp_test 4.1.2 { `--MERGE (UNION ALL) |--LEFT | |--SCAN t5 USING INDEX i2 - | `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + | `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY `--RIGHT |--SCAN t4 USING INDEX i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY } do_execsql_test 4.1.3 { diff --git a/test/sessionfuzz.c b/test/sessionfuzz.c index 093c2b043..c389a5e99 100644 --- a/test/sessionfuzz.c +++ b/test/sessionfuzz.c @@ -60,6 +60,7 @@ #define SQLITE_DEBUG 1 #define SQLITE_THREADSAFE 0 +#undef SQLITE_OMIT_LOAD_EXTENSION #define SQLITE_OMIT_LOAD_EXTENSION 0 #define SQLITE_ENABLE_SESSION 1 #define SQLITE_ENABLE_PREUPDATE_HOOK 1 diff --git a/test/shell1.test b/test/shell1.test index 5d4243f47..206fb0a4f 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -11,6 +11,7 @@ # # The focus of this file is testing the CLI shell tool. # +# TESTRUNNER: shell # # Test plan: diff --git a/test/shell2.test b/test/shell2.test index 16ed33c44..c6c27d216 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # diff --git a/test/shell3.test b/test/shell3.test index f8d69946e..c1ea9f6a7 100644 --- a/test/shell3.test +++ b/test/shell3.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # diff --git a/test/shell4.test b/test/shell4.test index eee59b02b..4b7e9b8ee 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # These tests are specific to the .stats command. diff --git a/test/shell5.test b/test/shell5.test index 20f2ba219..877676d72 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # These tests are specific to the .import command. diff --git a/test/shell6.test b/test/shell6.test index 49b4cc334..4841d6c01 100644 --- a/test/shell6.test +++ b/test/shell6.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # Test the shell tool ".lint fkey-indexes" command. # diff --git a/test/shell7.test b/test/shell7.test index 898018d77..dfd9e47c2 100644 --- a/test/shell7.test +++ b/test/shell7.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # Test the readfile() function built into the shell tool. Specifically, # that it does not truncate the blob read at the first embedded 0x00 diff --git a/test/shell8.test b/test/shell8.test index bee603923..ca37598e9 100644 --- a/test/shell8.test +++ b/test/shell8.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # Test the shell tool ".ar" command. # @@ -164,7 +165,7 @@ foreach {tn tcl} { # # Because it is slow, only do this for $tn==1. if {$tn==1} { - do_test 1.$tn.1 { + do_test 1.$tn.4 { catchcmd test_ar.db $c1 file delete -force ar1 after 2000 @@ -193,4 +194,29 @@ do_test 2.1.1 { regsub -all {ar4} [dir_content ar4] ar2 } {ar2/file1 ar2/file2 ar2/junk1} +# Test symbolic links. +# +if {$tcl_platform(platform)=="unix"} { + populate_dir ar2 { + file1 "1234" + file2 "3456" + } + file link ar2/link1 file1 + + forcedelete shell8.db + forcedelete link1 + + do_test 3.1 { + catchcmd shell8.db {.ar -C ar2 -c file2 link1 } + } {0 {}} + + do_test 3.2 { + catchcmd shell8.db {.ar -x} + } {0 {}} + + do_test 3.3 { + catchcmd shell8.db {.ar -x} + } {0 {}} +} + finish_test diff --git a/test/shell9.test b/test/shell9.test index 34c9d8c5d..869cb075d 100644 --- a/test/shell9.test +++ b/test/shell9.test @@ -8,6 +8,7 @@ # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. Specifically, # testing that it is possible to run a ".dump" script that creates diff --git a/test/speedtest1.c b/test/speedtest1.c index 4f3258098..570942381 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -2150,6 +2150,50 @@ void testset_debug1(void){ } } +/* +** This testset focuses on the speed of parsing numeric literals (integers +** and real numbers). This was added to test the impact of allowing "_" +** characters to appear in numeric SQL literals to make them easier to read. +** For example, "SELECT 1_000_000;" instead of "SELECT 1000000;". +*/ +void testset_parsenumber(void){ + const char *zSql1 = "SELECT 1, 12, 123, 1234, 12345, 123456"; + const char *zSql2 = "SELECT 8227256643844975616, 7932208612563860480, " + "2010730661871032832, 9138463067404021760, " + "2557616153664746496, 2557616153664746496"; + const char *zSql3 = "SELECT 1.0, 1.2, 1.23, 123.4, 1.2345, 1.23456"; + const char *zSql4 = "SELECT 8.227256643844975616, 7.932208612563860480, " + "2.010730661871032832, 9.138463067404021760, " + "2.557616153664746496, 2.557616153664746496"; + + const int NROW = 100*g.szTest; + int ii; + + speedtest1_begin_test(100, "parsing small integers"); + for(ii=0; ii #include @@ -2557,6 +2601,8 @@ int main(int argc, char **argv){ testset_fp(); }else if( strcmp(zThisTest,"trigger")==0 ){ testset_trigger(); + }else if( strcmp(zThisTest,"parsenumber")==0 ){ + testset_parsenumber(); }else if( strcmp(zThisTest,"rtree")==0 ){ #ifdef SQLITE_ENABLE_RTREE testset_rtree(6, 147); diff --git a/test/sqllimits1.test b/test/sqllimits1.test index f16208f23..14d39e691 100644 --- a/test/sqllimits1.test +++ b/test/sqllimits1.test @@ -707,6 +707,7 @@ if {$SQLITE_MAX_EXPR_DEPTH==0} { }] } "1 {Expression tree is too large (maximum depth $::SQLITE_MAX_EXPR_DEPTH)}" +if 0 { # Attempting to beat the expression depth limit using nested SELECT # queries causes a parser stack overflow. do_test sqllimits1-9.2 { @@ -718,7 +719,6 @@ if {$SQLITE_MAX_EXPR_DEPTH==0} { catchsql [subst { $expr }] } "1 {parser stack overflow}" -if 0 { do_test sqllimits1-9.3 { execsql { PRAGMA max_page_count = 1000000; -- 1 GB @@ -922,7 +922,7 @@ do_catchsql_test sqllimits1-18.1 { do_catchsql_test sqllimits1-18.2 { INSERT INTO b1 VALUES(1), (2), (3), (4), (5), (6), (7), (8), (9), (10) UNION VALUES(11); -} {1 {too many terms in compound SELECT}} +} {0 {}} #------------------------------------------------------------------------- # diff --git a/test/subquery.test b/test/subquery.test index c51edba04..17061d4b6 100644 --- a/test/subquery.test +++ b/test/subquery.test @@ -651,5 +651,64 @@ do_eqp_test subquery-10.2 { # `--SEARCH t1 USING INDEX x12 (aa=?) # +#----------------------------------------------------------------------------- +# 2024-04-25 Column affinities for columns of compound subqueries +# +reset_db +sqlite3_test_control SQLITE_TESTCTRL_INTERNAL_FUNCTIONS db +do_execsql_test subquery-11.1 { + CREATE TABLE t1(ix INT, rx REAL, bx BLOB, tx TEXT, ax); + INSERT INTO t1 VALUES(1,1.0,x'31','x',NULL); + WITH c(a) AS (SELECT 'y' UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT 'y') SELECT affinity(a) FROM c; +} {text text text text} +do_execsql_test subquery-11.2 { + WITH c(a) AS (SELECT 2 UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT 2) SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.3 { + WITH c(a) AS (SELECT 2.0 UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT 2.0) SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.4 { + WITH c(a) AS (SELECT null UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT null) SELECT affinity(a) FROM c; +} {text text text text} +do_execsql_test subquery-11.5 { + WITH c(a) AS (SELECT x'32' UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT x'32') SELECT affinity(a) FROM c; +} {text text text text} +do_execsql_test subquery-11.6 { + WITH c(a) AS (SELECT 3 UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT 3) SELECT affinity(a) FROM c; +} {integer integer integer integer} +do_execsql_test subquery-11.7 { + WITH c(a) AS (SELECT 3.0 UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT 3.0) SELECT affinity(a) FROM c; +} {integer integer integer integer} +do_execsql_test subquery-11.8 { + WITH c(a) AS (SELECT '3' UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT '3') SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.10 { + WITH c(a) AS (SELECT x'32' UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT x'32') SELECT affinity(a) FROM c; +} {integer integer integer integer} +do_execsql_test subquery-11.11 { + WITH c(a) AS (SELECT 4 UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT 4) SELECT affinity(a) FROM c; +} {real real real real} +do_execsql_test subquery-11.12 { + WITH c(a) AS (SELECT '4' UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT '4') SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.13 { + WITH c(a) AS (SELECT null UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT null) SELECT affinity(a) FROM c; +} {real real real real} +do_execsql_test subquery-11.14 { + WITH c(a) AS (SELECT x'b4' UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT x'b4') SELECT affinity(a) FROM c; +} {real real real real} finish_test diff --git a/test/tabfunc01.test b/test/tabfunc01.test index 8e90c549f..3a62b81f9 100644 --- a/test/tabfunc01.test +++ b/test/tabfunc01.test @@ -322,6 +322,32 @@ ifcapable altertable { } {1 {table pragma_compile_options may not be altered}} } +#----------------------------------------------------------------------------- +# 2024-04-26 LIMIT and OFFSET passed into virtual tables +# https://sqlite.org/forum/forumpost/c243b8f856 +# +do_execsql_test tabfunc01-900 { + SELECT * FROM ( + SELECT * FROM generate_series(1,10) + UNION ALL + SELECT * FROM generate_series(101,104) + ) LIMIT 10 OFFSET 5; +} {6 7 8 9 10 101 102 103 104} +do_execsql_test tabfunc01-910 { + SELECT * FROM ( + SELECT * FROM generate_series(1,10) + UNION ALL + SELECT * FROM generate_series(101,104) + ) LIMIT -1 OFFSET 5; +} {6 7 8 9 10 101 102 103 104} +do_execsql_test tabfunc01-920 { + SELECT * FROM ( + SELECT * FROM generate_series(1,10) + UNION ALL + SELECT * FROM generate_series(101,104) + ) LIMIT -1 OFFSET 0; +} {1 2 3 4 5 6 7 8 9 10 101 102 103 104} + # Free up memory allocations intarray_addr diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 0c704daf2..fd8271676 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -54,22 +54,35 @@ proc usage {} { Usage: $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS? $a0 PERMUTATION FILE + $a0 help $a0 njob ?NJOB? + $a0 script ?-msvc? CONFIG $a0 status where SWITCHES are: - --buildonly - --dryrun - --jobs NUMBER-OF-JOBS - --zipvfs ZIPVFS-SOURCE-DIR + --buildonly Build test exes but do not run tests + --config CONFIGS Only use configs on comma-separate list CONFIGS + --dryrun Write what would have happened to testrunner.log + --explain Write summary to stdout + --jobs NUM Run tests using NUM separate processes + --omit CONFIGS Omit configs on comma-separated list CONFIGS + --stop-on-coredump Stop running if any test segfaults + --stop-on-error Stop running after any reported error + --zipvfs ZIPVFSDIR ZIPVFS source directory + +Special values for PERMUTATION that work with plain tclsh: + + list - show all allowed PERMUTATION arguments. + mdevtest - tests recommended prior to normal development check-ins. + release - full release test with various builds. + sdevtest - like mdevtest but using ASAN and UBSAN. -Interesting values for PERMUTATION are: +Other PERMUTATION arguments must be run using testfixture, not tclsh: - veryquick - a fast subset of the tcl test scripts. This is the default. - full - all tcl test scripts. all - all tcl test scripts, plus a subset of test scripts rerun with various permutations. - release - full release test with various builds. + full - all tcl test scripts. + veryquick - a fast subset of the tcl test scripts. This is the default. If no PATTERN arguments are present, all tests specified by the PERMUTATION are run. Otherwise, each pattern is interpreted as a glob pattern. Only @@ -89,6 +102,12 @@ directory as a running testrunner.tcl script that is running tests. The "status" command prints a report describing the current state and progress of the tests. The "njob" command may be used to query or modify the number of sub-processes the test script uses to run tests. + +The "script" command outputs the script used to build a configuration. +Add the "-msvc" option for a Windows-compatible script. For a list of +available configurations enter "$a0 script help". + +Full documentation here: https://sqlite.org/src/doc/trunk/doc/testrunner.md }]] exit 1 @@ -126,6 +145,10 @@ proc guess_number_of_cores {} { } proc default_njob {} { + global env + if {[info exists env(NJOB)] && $env(NJOB)>=1} { + return $env(NJOB) + } set nCore [guess_number_of_cores] if {$nCore<=2} { set nHelper 1 @@ -151,7 +174,12 @@ set TRG(reporttime) 2000 set TRG(fuzztest) 0 ;# is the fuzztest option present. set TRG(zipvfs) "" ;# -zipvfs option, if any set TRG(buildonly) 0 ;# True if --buildonly option +set TRG(config) {} ;# Only build the named configurations +set TRG(omitconfig) {} ;# Do not build these configurations set TRG(dryrun) 0 ;# True if --dryrun option +set TRG(explain) 0 ;# True for the --explain option +set TRG(stopOnError) 0 ;# Stop running at first failure +set TRG(stopOnCore) 0 ;# Stop on a core-dump switch -nocase -glob -- $tcl_platform(os) { *darwin* { @@ -159,6 +187,7 @@ switch -nocase -glob -- $tcl_platform(os) { set TRG(make) make.sh set TRG(makecmd) "bash make.sh" set TRG(testfixture) testfixture + set TRG(shell) sqlite3 set TRG(run) run.sh set TRG(runcmd) "bash run.sh" } @@ -167,14 +196,16 @@ switch -nocase -glob -- $tcl_platform(os) { set TRG(make) make.sh set TRG(makecmd) "bash make.sh" set TRG(testfixture) testfixture + set TRG(shell) sqlite3 set TRG(run) run.sh set TRG(runcmd) "bash run.sh" } *win* { set TRG(platform) win set TRG(make) make.bat - set TRG(makecmd) make.bat + set TRG(makecmd) "call make.bat" set TRG(testfixture) testfixture.exe + set TRG(shell) sqlite3.exe set TRG(run) run.bat set TRG(runcmd) "run.bat" } @@ -239,7 +270,7 @@ set TRG(schema) { /* Fields updated as jobs run */ starttime INTEGER, endtime INTEGER, - state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ), + state TEXT CHECK( state IN ('','ready','running','done','failed','omit') ), output TEXT ); @@ -326,6 +357,14 @@ if {([llength $argv]==2 || [llength $argv]==1) } #-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- +# Check if this is the "help" command: +# +if {[string compare -nocase help [lindex $argv 0]]==0} { + usage +} +#-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Check if this is the "script" command: # @@ -408,6 +447,10 @@ if {[llength $argv]==1 } job { display_job [array get job] } + set nOmit [db one {SELECT count(*) FROM jobs WHERE state='omit'}] + if {$nOmit} { + puts "$nOmit jobs omitted due to failures" + } } mydb close @@ -433,8 +476,20 @@ for {set ii 0} {$ii < [llength $argv]} {incr ii} { if {$isLast} { usage } } elseif {($n>2 && [string match "$a*" --buildonly]) || $a=="-b"} { set TRG(buildonly) 1 + } elseif {($n>2 && [string match "$a*" --config]) || $a=="-c"} { + incr ii + set TRG(config) [lindex $argv $ii] } elseif {($n>2 && [string match "$a*" --dryrun]) || $a=="-d"} { set TRG(dryrun) 1 + } elseif {($n>2 && [string match "$a*" --explain]) || $a=="-e"} { + set TRG(explain) 1 + } elseif {($n>2 && [string match "$a*" --omit]) || $a=="-c"} { + incr ii + set TRG(omitconfig) [lindex $argv $ii] + } elseif {[string match "$a*" --stop-on-error]} { + set TRG(stopOnError) 1 + } elseif {[string match "$a*" --stop-on-coredump]} { + set TRG(stopOnCore) 1 } else { usage } @@ -444,8 +499,6 @@ for {set ii 0} {$ii < [llength $argv]} {incr ii} { } set argv [list] - - # This script runs individual tests - tcl scripts or [make xyz] commands - # in directories named "testdir$N", where $N is an integer. This variable # contains a list of integers indicating the directories in use. @@ -617,7 +670,16 @@ proc add_job {args} { trdb last_insert_rowid } -proc add_tcl_jobs {build config patternlist} { +# Argument $build is either an empty string, or else a list of length 3 +# describing the job to build testfixture. In the usual form: +# +# {ID DIRNAME DISPLAYNAME} +# +# e.g +# +# {1 /home/user/sqlite/test/testrunner_bld_xyz All-Debug} +# +proc add_tcl_jobs {build config patternlist {shelldepid ""}} { global TRG set topdir [file dirname $::testdir] @@ -666,34 +728,59 @@ proc add_tcl_jobs {build config patternlist} { if {[lsearch $lProp slow]>=0} { set priority 2 } if {[lsearch $lProp superslow]>=0} { set priority 4 } + set depid [lindex $build 0] + if {$shelldepid!="" && [lsearch $lProp shell]>=0} { set depid $shelldepid } + add_job \ -displaytype tcl \ -displayname $displayname \ -cmd $cmd \ - -depid [lindex $build 0] \ + -depid $depid \ -priority $priority - } } -proc add_build_job {buildname target} { +proc add_build_job {buildname target {postcmd ""} {depid ""}} { global TRG set dirname "[string tolower [string map {- _} $buildname]]_$target" set dirname "testrunner_bld_$dirname" + set cmd "$TRG(makecmd) $target" + if {$postcmd!=""} { + append cmd "\n" + append cmd $postcmd + } + set id [add_job \ -displaytype bld \ -displayname "Build $buildname ($target)" \ -dirname $dirname \ -build $buildname \ - -cmd "$TRG(makecmd) $target" \ + -cmd $cmd \ + -depid $depid \ -priority 3 ] list $id [file normalize $dirname] $buildname } +proc add_shell_build_job {buildname dirname depid} { + global TRG + + if {$TRG(platform)=="win"} { + set path [string map {/ \\} "$dirname/"] + set copycmd "xcopy $TRG(shell) $path" + } else { + set copycmd "cp $TRG(shell) $dirname/" + } + + return [ + add_build_job $buildname $TRG(shell) $copycmd $depid + ] +} + + proc add_make_job {bld target} { global TRG @@ -767,10 +854,30 @@ proc add_devtest_jobs {lBld patternlist} { foreach b $lBld { set bld [add_build_job $b $TRG(testfixture)] - add_tcl_jobs $bld veryquick $patternlist + add_tcl_jobs $bld veryquick $patternlist SHELL if {$patternlist==""} { add_fuzztest_jobs $b } + + if {[trdb one "SELECT EXISTS (SELECT 1 FROM jobs WHERE depid='SHELL')"]} { + set sbld [add_shell_build_job $b [lindex $bld 1] [lindex $bld 0]] + set sbldid [lindex $sbld 0] + trdb eval { + UPDATE jobs SET depid=$sbldid WHERE depid='SHELL' + } + } + + } +} + +# Check to ensure that the interpreter is a full-blown "testfixture" +# build and not just a "tclsh". If this is not the case, issue an +# error message and exit. +# +proc must_be_testfixture {} { + if {[lsearch [info commands] sqlite3_soft_heap_limit]<0} { + puts "Use testfixture, not tclsh, for these arguments." + exit 1 } } @@ -789,6 +896,7 @@ proc add_jobs_from_cmdline {patternlist} { set first [lindex $patternlist 0] switch -- $first { all { + must_be_testfixture set patternlist [lrange $patternlist 1 end] set clist [trd_all_configs] foreach c $clist { @@ -797,16 +905,26 @@ proc add_jobs_from_cmdline {patternlist} { } mdevtest { - add_devtest_jobs {All-O0 All-Debug} [lrange $patternlist 1 end] + set config_set { + All-O0 + All-Debug + } + add_devtest_jobs $config_set [lrange $patternlist 1 end] } sdevtest { - add_devtest_jobs {All-Sanitize All-Debug} [lrange $patternlist 1 end] + set config_set { + All-Sanitize + All-Debug + } + add_devtest_jobs $config_set [lrange $patternlist 1 end] } release { set patternlist [lrange $patternlist 1 end] foreach b [trd_builds $TRG(platform)] { + if {$TRG(config)!="" && ![regexp "\\y$b\\y" $TRG(config)]} continue + if {[regexp "\\y$b\\y" $TRG(omitconfig)]} continue set bld [add_build_job $b $TRG(testfixture)] foreach c [trd_configs $TRG(platform) $b] { add_tcl_jobs $bld $c $patternlist @@ -824,7 +942,15 @@ proc add_jobs_from_cmdline {patternlist} { } } + list { + set allperm [array names ::testspec] + lappend allperm all mdevtest sdevtest release list + puts "Allowed values for the PERMUTATION argument: [lsort $allperm]" + exit 0 + } + default { + must_be_testfixture if {[info exists ::testspec($first)]} { add_tcl_jobs "" $first [lrange $patternlist 1 end] } else { @@ -853,11 +979,16 @@ proc make_new_testset {} { proc mark_job_as_finished {jobid output state endtm} { r_write_db { + if {$state=="failed"} { + set childstate omit + } else { + set childstate ready + } trdb eval { UPDATE jobs SET output=$output, state=$state, endtime=$endtm WHERE jobid=$jobid; - UPDATE jobs SET state='ready' WHERE depid=$jobid; + UPDATE jobs SET state=$childstate WHERE depid=$jobid; } } } @@ -888,6 +1019,14 @@ proc script_input_ready {fd iJob jobid} { } puts "FAILED: $job(displayname) ($iJob)" set state "failed" + if {$TRG(stopOnError)} { + puts "OUTPUT: $O($iJob)" + exit 1 + } + if {$TRG(stopOnCore) && [string first {core dumped} $O($iJob)]>0} { + puts "OUTPUT: $O($iJob)" + exit 1 + } } set tm [clock_milliseconds] @@ -948,6 +1087,16 @@ proc launch_another_job {iJob} { close $fd } + # Add a batch/shell file command to set the directory used for temp + # files to the test's working directory. Otherwise, tests that use + # large numbers of temp files (e.g. zipvfs), might generate temp + # filename collisions. + if {$TRG(platform)=="win"} { + set set_tmp_dir "SET SQLITE_TMPDIR=[file normalize $dir]" + } else { + set set_tmp_dir "export SQLITE_TMPDIR=\"[file normalize $dir]\"" + } + if { $TRG(dryrun) } { mark_job_as_finished $job(jobid) "" done 0 @@ -962,7 +1111,8 @@ proc launch_another_job {iJob} { set pwd [pwd] cd $dir set fd [open $TRG(run) w] - puts $fd $job(cmd) + puts $fd $set_tmp_dir + puts $fd $job(cmd) close $fd set fd [open "|$TRG(runcmd) 2>@1" r] cd $pwd @@ -1061,6 +1211,10 @@ proc run_testset {} { puts "FAILED: $displayname" } } + set nOmit [trdb one {SELECT count(*) FROM jobs WHERE state='omit'}] + if {$nOmit>0} { + puts "$nOmit jobs skipped due to prior failures" + } } puts "\nTest database is $TRG(dbname)" @@ -1078,15 +1232,42 @@ proc handle_buildonly {} { } } +# Handle the --explain option. Provide a human-readable +# explanation of all the tests that are in the trdb database jobs +# table. +# +proc explain_layer {indent depid} { + global TRG + if {$TRG(buildonly)} { + set showtests 0 + } else { + set showtests 1 + } + trdb eval {SELECT jobid, displayname, displaytype, dirname + FROM jobs WHERE depid=$depid ORDER BY displayname} { + if {$displaytype=="bld"} { + puts "${indent}$displayname in $dirname" + explain_layer "${indent} " $jobid + } elseif {$showtests} { + puts "${indent}[lindex $displayname end]" + } + } +} +proc explain_tests {} { + explain_layer "" "" +} + sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) set tm [lindex [time { make_new_testset }] 0] -if {$TRG(nJob)>1} { - puts "splitting work across $TRG(nJob) jobs" +if {$TRG(explain)} { + explain_tests +} else { + if {$TRG(nJob)>1} { + puts "splitting work across $TRG(nJob) jobs" + } + puts "built testset in [expr $tm/1000]ms.." + handle_buildonly + run_testset } -puts "built testset in [expr $tm/1000]ms.." - -handle_buildonly -run_testset trdb close -#puts [pwd] diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index 984c6d827..f8f12d91a 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -23,6 +23,7 @@ namespace eval trd { set tcltest(linux.No-lookaside) veryquick set tcltest(linux.Devkit) veryquick set tcltest(linux.Apple) veryquick + set tcltest(linux.Android) veryquick set tcltest(linux.Sanitize) veryquick set tcltest(linux.Device-One) all set tcltest(linux.Default) all_plus_autovacuum_crash @@ -53,6 +54,7 @@ namespace eval trd { set extra(linux.Device-Two) {fuzztest sourcetest threadtest} set extra(linux.No-lookaside) {fuzztest sourcetest} set extra(linux.Devkit) {fuzztest sourcetest} + set extra(linux.Android) {fuzztest sourcetest} set extra(linux.Apple) {fuzztest sourcetest} set extra(linux.Sanitize) {fuzztest sourcetest} set extra(linux.Default) {fuzztest sourcetest threadtest} @@ -108,6 +110,7 @@ namespace eval trd { -DSQLITE_ENABLE_STAT4 -DSQLITE_OMIT_LOOKASIDE=1 -DCONFIG_SLOWDOWN_FACTOR=5.0 + -DSQLITE_ENABLE_RBU --enable-debug --enable-all } @@ -245,6 +248,40 @@ namespace eval trd { -O2 -DSQLITE_ENABLE_LOCKING_STYLE=1 } + set build(Android) { + -Os + -DHAVE_USLEEP=1 + -DSQLITE_HAVE_ISNAN + -DSQLITE_POWERSAFE_OVERWRITE=1 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_DEFAULT_AUTOVACUUM=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_ENABLE_FTS3 + -DSQLITE_ENABLE_FTS3_BACKWARDS + -DSQLITE_ENABLE_FTS4 + -DSQLITE_SECURE_DELETE + -DSQLITE_ENABLE_BATCH_ATOMIC_WRITE + -DBIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD + -DSQLITE_ALLOW_ROWID_IN_VIEW + -DSQLITE_ENABLE_BYTECODE_VTAB + -Wno-unused-parameter + -Werror + -DUSE_PREAD64 + -Dfdatasync=fdatasync + -DHAVE_MALLOC_H=1 + -DHAVE_MALLOC_USABLE_SIZE + -DSQLITE_ENABLE_DBSTAT_VTAB + } + # Compile-options used by Android but omitted from these + # tests: + # -DNDEBUG=1 + # -DSQLITE_DEFAULT_LEGACY_ALTER_TABLE + # -DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576 + # -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 + # -DSQLITE_OMIT_BUILTIN_TEST + # -DSQLITE_OMIT_LOAD_EXTENSION + # -DSQLITE_OMIT_COMPILEOPTION_DIAGS + # set build(Apple) { -Os -DHAVE_GMTIME_R=1 @@ -598,7 +635,12 @@ proc trd_buildscript {config srcdir bMsvc} { # Ensure that the named configuration exists. if {![info exists build($config)]} { - error "No such build config: $config" + if {$config!="help"} { + puts "No such build config: $config" + } + puts "Available configurations: [lsort [array names build]]" + flush stdout + exit 1 } # Generate and return the script. @@ -637,4 +679,3 @@ proc trd_test_script_properties {path} { set trd_test_script_properties_cache($path) } - diff --git a/test/tkt-8454a207b9.test b/test/tkt-8454a207b9.test index 20e142057..42b95c8f5 100644 --- a/test/tkt-8454a207b9.test +++ b/test/tkt-8454a207b9.test @@ -49,7 +49,7 @@ do_test tkt-8454a207b9.4 { ALTER TABLE t1 ADD COLUMN e DEFAULT -123.0; SELECT e, typeof(e) FROM t1; } -} {-123 integer} +} {-123.0 real} do_test tkt-8454a207b9.5 { db eval { ALTER TABLE t1 ADD COLUMN f DEFAULT -123.5; diff --git a/test/tkt-bd484a090c.test b/test/tkt-bd484a090c.test index 3d2b59995..7867c8dc9 100644 --- a/test/tkt-bd484a090c.test +++ b/test/tkt-bd484a090c.test @@ -30,7 +30,7 @@ do_test 2.1 { catchsql { SELECT datetime('now', 'localtime') } } {1 {local time unavailable}} do_test 2.2 { - catchsql { SELECT datetime('now', 'utc') } + catchsql { SELECT datetime('2000-01-01', 'utc') } } {1 {local time unavailable}} sqlite3_test_control SQLITE_TESTCTRL_LOCALTIME_FAULT 0 diff --git a/test/upsert1.test b/test/upsert1.test index 781831133..8af273a89 100644 --- a/test/upsert1.test +++ b/test/upsert1.test @@ -268,4 +268,28 @@ do_catchsql_test upsert1-1210 { INSERT INTO t1(a,b) VALUES(1,2) ON CONFLICT(b+?1) DO NOTHING; } {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}} +# 2024-04-11 https://sqlite.org/forum/forumpost/284955a3cd454a15 +# Incorrect value passed into a trigger that fires as the result of +# an upsert. +# +reset_db +do_execsql_test upsert1-1300 { + CREATE TABLE t1(x INT, y TEXT); + INSERT INTO t1 VALUES + (11, printf('%.9000c','a')), + (11, printf('%.9000c','a')), + (33, printf('%.9000c','b')), + (33, printf('%.9000c','b')); + CREATE TABLE t2(x INT UNIQUE, y TEXT); + CREATE TRIGGER r1 BEFORE UPDATE ON t2 BEGIN + SELECT raise(ABORT,'Incorrect old.y value passed to trigger!') + WHERE old.y != new.y; + /* ^^^ This trigger will fire and cause the ABORT if the problem has + ** not been fixed, or if there is a regression. */ + END; + INSERT INTO t2(x, y) SELECT x, y FROM t1 + WHERE true + ON CONFLICT (x) DO UPDATE SET y = excluded.y; +} {} + finish_test diff --git a/test/values.test b/test/values.test new file mode 100644 index 000000000..fed2c5c6c --- /dev/null +++ b/test/values.test @@ -0,0 +1,715 @@ +# 2024 March 3 +# +# 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. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix values + + +do_execsql_test 1.0 { + CREATE TABLE x1(a, b, c); +} + + +explain_i { + INSERT INTO x1(a, b, c) VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); +} +do_execsql_test 1.1.1 { + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); +} +do_execsql_test 1.1.2 { + SELECT * FROM x1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 4 4 4 +} + +do_execsql_test 1.2.0 { + DELETE FROM x1 +} +do_execsql_test 1.2.1 { + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3) UNION ALL SELECT 4, 4, 4; + SELECT * FROM x1; +} {1 1 1 2 2 2 3 3 3 4 4 4} + +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 4 + +do_execsql_test 1.2.2 { + DELETE FROM x1; + INSERT INTO x1 + VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5) + UNION ALL SELECT 6, 6, 6; + SELECT * FROM x1; +} {1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6} + +do_execsql_test 1.2.3 { + DELETE FROM x1; + INSERT INTO x1 + VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4) + UNION ALL SELECT 6, 6, 6; + SELECT * FROM x1; +} {1 1 1 2 2 2 3 3 3 4 4 4 6 6 6} + +do_execsql_test 1.2.4 { + DELETE FROM x1; + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3) UNION ALL SELECT 6, 6, 6; + SELECT * FROM x1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 6 6 6 +} + +set a 4 +set b 5 +set c 6 +do_execsql_test 1.2.5 { + DELETE FROM x1; + INSERT INTO x1 + VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), + (4, 4, $a), (5, 5, $b), (6, 6, $c) +} + +do_execsql_test 1.2.6 { + SELECT * FROM x1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 4 4 4 + 5 5 5 + 6 6 6 +} + +#------------------------------------------------------------------------- +# SQLITE_LIMIT_COMPOUND_SELECT set to 0. +# +reset_db + +do_execsql_test 2.0 { + CREATE TABLE x1(a, b, c); +} + +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 3 + +do_catchsql_test 2.1.1 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10, 10) +} {1 {all VALUES must have the same number of terms}} + +do_catchsql_test 2.1.2 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10) +} {1 {all VALUES must have the same number of terms}} + +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 0 + +do_execsql_test 2.2 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10) +} {} +do_execsql_test 2.3 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10) + UNION ALL + SELECT 5, 12, 12 + ORDER BY 1 +} {} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 3.0 { + CREATE TABLE y1(x, y); +} + +do_execsql_test 3.1.1 { + DELETE FROM y1; + INSERT INTO y1 VALUES(1, 2), (3, 4), (row_number() OVER (), 5); +} +do_execsql_test 3.1.2 { + SELECT * FROM y1; +} {1 2 3 4 1 5} +do_execsql_test 3.2.1 { + DELETE FROM y1; + INSERT INTO y1 VALUES(1, 2), (3, 4), (row_number() OVER (), 6) + , (row_number() OVER (), 7) +} +do_execsql_test 3.1.2 { + SELECT * FROM y1; +} {1 2 3 4 1 6 1 7} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 4.0 { + CREATE TABLE x1(a PRIMARY KEY, b) WITHOUT ROWID; +} + +foreach {tn iLimit} {1 0 2 3} { + sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT $iLimit + + do_execsql_test 4.1.1 { + DELETE FROM x1; + INSERT INTO x1 VALUES + (1, 1), + (2, (SELECT * FROM (VALUES('a'), ('b'), ('c'), ('d')) )) + } + do_execsql_test 4.1.2 { + SELECT * FROM x1 + } {1 1 2 a} + + do_execsql_test 4.2.1 { + DELETE FROM x1; + INSERT INTO x1 VALUES + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, (SELECT * FROM (VALUES('a'), ('b'), ('c'), ('d')) )) + } + do_execsql_test 4.2.2 { + SELECT * FROM x1 + } {1 1 2 2 3 3 4 4 5 a} + + do_execsql_test 4.3.1 { + DELETE FROM x1; + INSERT INTO x1 VALUES + (1, (SELECT * FROM (VALUES('a'), ('b'), ('c'), ('d'), ('e')) )) + } + do_execsql_test 4.3.2 { + SELECT * FROM x1 + } {1 a} +} + +#------------------------------------------------------------------------ +reset_db + +do_execsql_test 5.0 { + CREATE VIEW v1 AS VALUES(1, 2, 3), (4, 5, 6), (7, 8, 9); +} +do_execsql_test 5.1 { + SELECT * FROM v1 +} {1 2 3 4 5 6 7 8 9} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1), (2); +} + +do_execsql_test 6.1 { + SELECT ( VALUES( x ), ( x ) ) FROM t1; +} {1 2} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('x'), ('y'); +} + +do_execsql_test 6.1 { + SELECT * FROM t1, (VALUES(1), (2)) +} {x 1 x 2 y 1 y 2} + +do_execsql_test 6.2 { + VALUES(CAST(44 AS REAL)),(55); +} {44.0 55} + +#------------------------------------------------------------------------ +do_execsql_test 7.1 { + WITH x1(a, b) AS ( + VALUES(1, 2), ('a', 'b') + ) + SELECT * FROM x1 one, x1 two +} { + 1 2 1 2 + 1 2 a b + a b 1 2 + a b a b +} + +#------------------------------------------------------------------------- +reset_db + +set VVV { + ( VALUES('a', 'b'), ('c', 'd'), (123, NULL) ) +} +set VVV2 { + ( + SELECT 'a' AS column1, 'b' AS column2 + UNION ALL SELECT 'c', 'd' UNION ALL SELECT 123, NULL + ) +} + +do_execsql_test 8.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('d'), (NULL), (123) +} +foreach {tn q res} { + 1 "SELECT * FROM t1 LEFT JOIN VVV" { + d a b d c d d 123 {} + {} a b {} c d {} 123 {} + 123 a b 123 c d 123 123 {} + } + + 2 "SELECT * FROM t1 LEFT JOIN VVV ON (column1=x)" { + d {} {} + {} {} {} + 123 123 {} + } + + 3 "SELECT * FROM t1 RIGHT JOIN VVV" { + d a b d c d d 123 {} + {} a b {} c d {} 123 {} + 123 a b 123 c d 123 123 {} + } + + 4 "SELECT * FROM t1 RIGHT JOIN VVV ON (column1=x)" { + 123 123 {} + {} a b + {} c d + } + + 5 "SELECT * FROM t1 FULL OUTER JOIN VVV ON (column1=x)" { + d {} {} + {} {} {} + 123 123 {} + {} a b + {} c d + } + + 6 "SELECT count(*) FROM VVV" { 3 } + + 7 "SELECT (SELECT column1 FROM VVV)" { a } + + 8 "SELECT * FROM VVV UNION ALL SELECT * FROM VVV" { + a b c d 123 {} + a b c d 123 {} + } + + 9 "SELECT * FROM VVV INTERSECT SELECT * FROM VVV" { + 123 {} a b c d + } + + 10 "SELECT * FROM VVV eXCEPT SELECT * FROM VVV" { } + + 11 "SELECT * FROM VVV eXCEPT SELECT 'a', 'b'" { 123 {} c d } + +} { + set q1 [string map [list VVV $VVV] $q] + set q2 [string map [list VVV $VVV2] $q] + set q3 "WITH VVV AS $VVV $q" + + do_execsql_test 8.1.$tn.1 $q1 $res + do_execsql_test 8.1.$tn.2 $q2 $res + do_execsql_test 8.1.$tn.3 $q3 $res +} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 9.1 { + VALUES(456), (123), (NULL) UNION ALL SELECT 122 ORDER BY 1 +} { {} 122 123 456 } + +do_execsql_test 9.2 { + VALUES (1, 2), (3, 4), ( + ( SELECT column1 FROM ( VALUES (5, 6), (7, 8) ) ), + ( SELECT max(column2) FROM ( VALUES (5, 1), (7, 6) ) ) + ) +} { 1 2 3 4 5 6 } + +do_execsql_test 10.1 { + CREATE TABLE a2(a, b, c DEFAULT 'xyz'); +} +do_execsql_test 10.2 { + INSERT INTO a2(a) VALUES(3),(4); +} + +#------------------------------------------------------------------------- +reset_db +ifcapable fts5 { + do_execsql_test 11.0 { + CREATE VIRTUAL TABLE ft USING fts3(x); + } + do_execsql_test 11.1 { + INSERT INTO ft VALUES('one'), ('two'); + } +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 12.0 { + CREATE TABLE t1(a, b); +} +do_execsql_test 12.1 { + INSERT INTO t1 SELECT 1, 2 UNION ALL VALUES(3, 4), (5, 6); +} +do_execsql_test 12.2 { + SELECT * FROM t1 +} {1 2 3 4 5 6} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 13.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('xyz'); + + SELECT ( + VALUES( (max(substr('abc', 1, 1), x)) ), + (123), + (456) + ) + FROM t1; +} {xyz} + +do_catchsql_test 13.1 { + VALUES(300), (zeroblob(300) OVER win); +} {1 {zeroblob() may not be used as a window function}} + +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 14.1 { + PRAGMA encoding = utf16; + CREATE TABLE t1(a, b); +} {} + +db close +sqlite3 db test.db + +do_execsql_test 14.2 { + INSERT INTO t1 VALUES + (17, 'craft'), + (16, 'urtlek' IN(1,2,3)); +} + +#-------------------------------------------------------------------------- +# +reset_db +do_eqp_test 15.1 { + VALUES(1),(2),(3),(4),(5); +} { + QUERY PLAN + `--SCAN 5-ROW VALUES CLAUSE +} +do_execsql_test 15.2 { + CREATE TABLE t1(a,b); +} +do_eqp_test 15.3 { + INSERT INTO t1 VALUES + (1,2),(3,4),(7,8); +} { + QUERY PLAN + `--SCAN 3-ROW VALUES CLAUSE +} +do_eqp_test 15.4 { + INSERT INTO t1 VALUES + (1,2),(3,4),(7,8), + (5,row_number()OVER()); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN 3-ROW VALUES CLAUSE + `--UNION ALL + |--CO-ROUTINE (subquery-xxxxxx) + | `--SCAN CONSTANT ROW + `--SCAN (subquery-xxxxxx) +} +do_eqp_test 15.5 { + SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6)), (VALUES('a'),('b'),('c')); +} { + QUERY PLAN + |--SCAN 6-ROW VALUES CLAUSE + `--SCAN 3-ROW VALUES CLAUSE +} +do_execsql_test 15.6 { + CREATE TABLE t2(x,y); +} +do_eqp_test 15.7 { + SELECT * FROM t2 UNION ALL VALUES(1,2),(3,4),(5,6),(7,8); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN t2 + `--UNION ALL + `--SCAN 4-ROW VALUES CLAUSE +} + +#-------------------------------------------------------------------------- +# The VALUES-as-coroutine optimization can be applied to later rows of +# a VALUES clause even if earlier rows do not qualify. +# +reset_db +do_execsql_test 16.1 { + CREATE TABLE t1(a,b); +} +do_execsql_test 16.2 { + BEGIN; + INSERT INTO t1 VALUES(1,2),(3,4),(5,6), + (7,row_number()OVER()), + (9,10), (11,12), (13,14), (15,16); + SELECT * FROM t1 ORDER BY a, b; + ROLLBACK; +} {1 2 3 4 5 6 7 1 9 10 11 12 13 14 15 16} +do_eqp_test 16.3 { + INSERT INTO t1 VALUES(1,2),(3,4),(5,6), + (7,row_number()OVER()), + (9,10), (11,12), (13,14), (15,16); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN 3-ROW VALUES CLAUSE + |--UNION ALL + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + `--UNION ALL + `--SCAN 4-ROW VALUES CLAUSE +} +do_execsql_test 16.4 { + BEGIN; + INSERT INTO t1 VALUES + (1,row_number()OVER()), + (2,3), (4,5), (6,7); + SELECT * FROM t1 ORDER BY a, b; + ROLLBACK; +} {1 1 2 3 4 5 6 7} +do_eqp_test 16.5 { + INSERT INTO t1 VALUES + (1,row_number()OVER()), + (2,3), (4,5), (6,7); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + `--UNION ALL + `--SCAN 3-ROW VALUES CLAUSE +} +do_execsql_test 16.6 { + BEGIN; + INSERT INTO t1 VALUES + (1,2),(3,4), + (5,row_number()OVER()), + (7,8),(9,10),(11,12), + (13,row_number()OVER()), + (15,16),(17,18),(19,20),(21,22); + SELECT * FROM t1 ORDER BY a, b; + ROLLBACK; +} { 1 2 3 4 5 1 7 8 9 10 11 12 13 1 15 16 17 18 19 20 21 22} +do_eqp_test 16.7 { + INSERT INTO t1 VALUES + (1,2),(3,4), + (5,row_number()OVER()), + (7,8),(9,10),(11,12), + (13,row_number()OVER()), + (15,16),(17,18),(19,20),(21,22); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN 2-ROW VALUES CLAUSE + |--UNION ALL + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + |--UNION ALL + | `--SCAN 3-ROW VALUES CLAUSE + |--UNION ALL + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + `--UNION ALL + `--SCAN 4-ROW VALUES CLAUSE +} + +#-------------------------------------------------------------------------- +# 2024-03-23 dbsqlfuzz crash-c2c5e7e08b7e489d270a26d895077a03f678c33b +# +do_execsql_test 17.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1 AS SELECT * FROM (VALUES(1,2), (3,4 IN (1,2,3))); +} + +do_execsql_test 17.2 { + SELECT * FROM t1 +} {1 2 3 0} + +# 2024-03-25 dbsqlfuzz crash-74cf7c9904360322a6c917e4934b127543d1cd51 +# +do_catchsql_test 18.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY); + INSERT INTO t1 VALUES(RAISE(IGNORE)),(0); +} {1 {RAISE() may only be used within a trigger-program}} +do_catchsql_test 18.2 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(RAISE(IGNORE)),(0); + END; + INSERT INTO t1 VALUES(1,2,3); + SELECT * FROM t1; +} {0 {1 2 3}} +do_catchsql_test 18.3.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(RAISE(ABORT,'error 18.3')),(0); + END; + INSERT INTO t1 VALUES(1,2,3); +} {1 {error 18.3}} +do_execsql_test 18.3.2 { + SELECT * FROM t1; +} {} +do_catchsql_test 18.4.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(1),(RAISE(ABORT,'error 18.4')),(0); + END; + INSERT INTO t1 VALUES(1,2,3); +} {1 {error 18.4}} +do_execsql_test 18.4.2 { + SELECT * FROM t1; +} {} +do_catchsql_test 18.5.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(1), + (CASE WHEN new.z>7 THEN RAISE(ABORT,'error 18.5') ELSE 2 END); + END; + INSERT INTO t1 VALUES(1,2,3); + SELECT * FROM t1; +} {0 {1 2 3 2 1 {} 3 2 {}}} +do_catchsql_test 18.5.2 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,2,13); +} {1 {error 18.5}} +do_catchsql_test 18.5.3 { + SELECT * FROM t1; +} {0 {}} + +# 2024-04-18 dbsqlfuzz crash-bde3bf80aedf25afa56e2997a0545a314765d3f8 +# Verify that the VALUES expressions used as an argument to an outer +# join work correctly. +# +reset_db +db null NULL +do_execsql_test 19.1 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(11,22); + SELECT * FROM t1 LEFT JOIN (VALUES(33,44),(55,66)) AS t2 ON a=b; +} {11 22 NULL NULL} +do_execsql_test 19.2 { + SELECT * FROM (VALUES(33,44),(55,66)) AS t2 RIGHT JOIN t1 ON a=b; +} {NULL NULL 11 22} +do_execsql_test 19.3 { + SELECT *, '|' FROM t1 FULL JOIN (VALUES(33,44),(55,66)) AS t2 ON a=b + ORDER BY +column1 +} {11 22 NULL NULL | NULL NULL 33 44 | NULL NULL 55 66 |} +do_execsql_test 19.4 { + SELECT *, '|' FROM (VALUES(33,44),(55,66)) AS t2 FULL JOIN t1 ON a=b + ORDER BY +column1 +} {NULL NULL 11 22 | 33 44 NULL NULL | 55 66 NULL NULL |} + +# 2024-04-21 dbsqlfuzz 6fd1ff3a64bef4a6c092e8d757548e95698b0df5 +# A continuation of the 2024-04-18 problem above. We have to create +# Pseudo-cursor that is always NULL on the viaCoroutine loop in case +# there are OP_Columns generated against it by the sub-WHERE clause. +# +db null N +do_execsql_test 19.5 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + DROP TABLE IF EXISTS t3; + CREATE TABLE t1(a,b); INSERT INTO t1 VALUES(1,2); + CREATE TABLE t2(column1,column2); INSERT INTO t2 VALUES(11,22),(33,44); + CREATE TABLE t3(d,e); INSERT INTO t3 VALUES(3,4); +} +do_execsql_test 19.6 { + -- output verify using PG 14.2 + SELECT * + FROM t1 CROSS JOIN t2 FULL JOIN t3 ON a=d + ORDER BY +d, +column1; +} {1 2 11 22 N N + 1 2 33 44 N N + N N N N 3 4} +do_execsql_test 19.7 { + SELECT * + FROM t1 CROSS JOIN (VALUES(11,22),(33,44)) FULL JOIN t3 ON a=d + ORDER BY +d, +column1; +} {1 2 11 22 N N + 1 2 33 44 N N + N N N N 3 4} +do_execsql_test 19.8 { + -- output verified using PG 14.2 + SELECT * + FROM t1 CROSS JOIN t2 FULL JOIN t3 ON a=d + WHERE column1 IS NULL; +} {N N N N 3 4} +do_execsql_test 19.9 { + SELECT * + FROM t1 CROSS JOIN (VALUES(11,22),(33,44)) FULL JOIN t3 ON a=d + WHERE column1 IS NULL; +} {N N N N 3 4} + +finish_test diff --git a/test/valuesfault.test b/test/valuesfault.test new file mode 100644 index 000000000..bc5dddfb0 --- /dev/null +++ b/test/valuesfault.test @@ -0,0 +1,37 @@ +# 2024 March 3 +# +# 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. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix valuesfault +source $testdir/malloc_common.tcl + + +do_execsql_test 1.0 { + CREATE TABLE x1(a, b, c); +} +faultsim_save_and_close + +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen + sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 2 +} -body { + execsql { + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); + } +} -test { + faultsim_test_result {0 {}} +} + + +finish_test diff --git a/test/view.test b/test/view.test index 90c9c9909..241742025 100644 --- a/test/view.test +++ b/test/view.test @@ -801,4 +801,27 @@ ifcapable schema_pragmas { } { 0 a INT 0 {} 0 1 b BLOB 0 {} 0 } } +#----------------------------------------------------------------------- +# 2024-04-25 Trying to make type information on compound subqueries +# more predictable and rational. +# +reset_db +do_execsql_test view-31.1 { + CREATE TABLE x2(b TEXT); + CREATE TABLE x1(a TEXT); + INSERT INTO x1 VALUES('123'); + -- Two queries get the same result even though the order of terms + -- in the CTE is reversed + WITH c(x) AS ( SELECT b FROM x2 UNION SELECT 123 ) + SELECT count(*) FROM x1 WHERE a IN c; + WITH c(x) AS ( SELECT 123 UNION SELECT b FROM x2 ) + SELECT count(*) FROM x1 WHERE a IN c; +} {0 0} +do_execsql_test view-31.2 { + CREATE TABLE t3(a INTEGER, b TEXT); + INSERT INTO t3 VALUES(123, 123); + WITH s AS ( VALUES(123), (456) ) SELECT * FROM t3 WHERE b IN s; +} {123 123} + + finish_test diff --git a/test/vtabL.test b/test/vtabL.test new file mode 100644 index 000000000..45528edcb --- /dev/null +++ b/test/vtabL.test @@ -0,0 +1,75 @@ +# 2024-03-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 vtabL + +ifcapable !vtab { + finish_test + return +} + +register_tcl_module db + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return $::create_table_sql + } + } + + return {} +} + +foreach {tn cts} { + 1 {SELECT 123} + 2 {SELECT 123, 456} + 3 {INSERT INTO t1 VALUES(5, 6)} + 4 {CREATE INDEX i1 ON t1(a)} + 5 {CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END;} + 6 {DROP TABLE nosuchtable} + 7 {DROP TABLE x1} + 8 {DROP TABLE t1} +} { + set ::create_table_sql $cts + do_catchsql_test 1.$tn { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); + } {1 {declare_vtab: syntax error}} +} + +foreach {tn cts} { + 9 {CREATE TABLE xyz AS SELECT * FROM sqlite_schema} + 10 {CREATE TABLE xyz AS SELECT 1 AS 'col'} +} { + set ::create_table_sql $cts + do_catchsql_test 1.$tn { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); + } {1 {declare_vtab: SQL logic error}} +} + +foreach {tn cts} { + 1 {CREATE TABLE IF NOT EXISTS t1(a, b)} + 2 {CREATE TABLE ""(a, b PRIMARY KEY) WITHOUT ROWID} +} { + set ::create_table_sql $cts + execsql { DROP TABLE IF EXISTS x1 } + do_execsql_test 2.$tn.1 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); + } + do_execsql_test 2.$tn.2 { + SELECT a, b FROM x1 + } +} + +finish_test + diff --git a/test/whereL.test b/test/whereL.test index c3bdcb8f3..2e9ae219e 100644 --- a/test/whereL.test +++ b/test/whereL.test @@ -49,6 +49,33 @@ do_eqp_test 120 { |--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) `--SCAN t3 } +do_eqp_test 121 { + SELECT * FROM t1, t2, t3 + WHERE t1.a=t2.a AND t2.a=t3.j AND t3.j=abs(5) + ORDER BY t1.a; +} { + QUERY PLAN + |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + |--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) + `--SCAN t3 +} + +# The sqlite3ExprIsConstant() routine does not believe that +# the expression "coalesce(5,random())" is constant. So the +# optimization does not apply in this case. +# +sqlite3_create_function db +do_eqp_test 122 { + SELECT * FROM t1, t2, t3 + WHERE t1.a=t2.a AND t2.a=t3.j AND t3.j=coalesce(5,random()) + ORDER BY t1.a; +} { + QUERY PLAN + |--SCAN t3 + |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + |--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) + `--USE TEMP B-TREE FOR ORDER BY +} # Constant propagation in the face of collating sequences: # @@ -209,4 +236,22 @@ do_eqp_test 710 { `--SEARCH t1 USING INDEX idx (=?) } +# 2024-03-07 https://sqlite.org/forum/forumpost/ecdfc02339 +# A refinement is needed to the enhancements tested by the prior test case +# to avoid another problem with indexes on constant expressions. +# +reset_db +db null NULL +do_execsql_test 800 { + CREATE TABLE t0(c0, c1); + CREATE TABLE t1(c2); + CREATE INDEX i0 ON t1(NULL); + INSERT INTO t1(c2) VALUES (0.2); + CREATE VIEW v0(c3) AS SELECT DISTINCT c2 FROM t1; + SELECT * FROM v0 LEFT JOIN t0 ON c3pNext = memChunkList; + p->sz = nByte; + memChunkList = p; + return (void*)&p[1]; +} +static void *lemon_calloc(size_t nElem, size_t sz){ + void *p = lemon_malloc(nElem*sz); + memset(p, 0, nElem*sz); + return p; +} +static void lemon_free(void *pOld){ + if( pOld ){ + MemChunk *p = (MemChunk*)pOld; + p--; + memset(pOld, 0, p->sz); + } +} +static void *lemon_realloc(void *pOld, size_t nNew){ + void *pNew; + MemChunk *p; + if( pOld==0 ) return lemon_malloc(nNew); + p = (MemChunk*)pOld; + p--; + if( p->sz>=nNew ) return pOld; + pNew = lemon_malloc( nNew ); + memcpy(pNew, pOld, p->sz); + return pNew; +} + +/* Free all outstanding memory allocations. +** Do this right before exiting. +*/ +static void lemon_free_all(void){ + while( memChunkList ){ + MemChunk *pNext = memChunkList->pNext; + free( memChunkList ); + memChunkList = pNext; + } +} + /* ** Compilers are starting to complain about the use of sprintf() and strcpy(), ** saying they are unsafe. So we define our own versions of those routines too. @@ -418,6 +494,8 @@ struct lemon { char *filename; /* Name of the input file */ char *outname; /* Name of the current output file */ char *tokenprefix; /* A prefix added to token names in the .h file */ + char *reallocFunc; /* Function to use to allocate stack space */ + char *freeFunc; /* Function to use to free stack space */ int nconflict; /* Number of parsing conflicts */ int nactiontab; /* Number of entries in the yy_action[] table */ int nlookaheadtab; /* Number of entries in yy_lookahead[] */ @@ -495,7 +573,7 @@ static struct action *Action_new(void){ if( actionfreelist==0 ){ int i; int amt = 100; - actionfreelist = (struct action *)calloc(amt, sizeof(struct action)); + actionfreelist = (struct action *)lemon_calloc(amt, sizeof(struct action)); if( actionfreelist==0 ){ fprintf(stderr,"Unable to allocate memory for a new parser action."); exit(1); @@ -614,14 +692,14 @@ struct acttab { /* Free all memory associated with the given acttab */ void acttab_free(acttab *p){ - free( p->aAction ); - free( p->aLookahead ); - free( p ); + lemon_free( p->aAction ); + lemon_free( p->aLookahead ); + lemon_free( p ); } /* Allocate a new acttab structure */ acttab *acttab_alloc(int nsymbol, int nterminal){ - acttab *p = (acttab *) calloc( 1, sizeof(*p) ); + acttab *p = (acttab *) lemon_calloc( 1, sizeof(*p) ); if( p==0 ){ fprintf(stderr,"Unable to allocate memory for a new acttab."); exit(1); @@ -640,7 +718,7 @@ acttab *acttab_alloc(int nsymbol, int nterminal){ void acttab_action(acttab *p, int lookahead, int action){ if( p->nLookahead>=p->nLookaheadAlloc ){ p->nLookaheadAlloc += 25; - p->aLookahead = (struct lookahead_action *) realloc( p->aLookahead, + p->aLookahead = (struct lookahead_action *) lemon_realloc( p->aLookahead, sizeof(p->aLookahead[0])*p->nLookaheadAlloc ); if( p->aLookahead==0 ){ fprintf(stderr,"malloc failed\n"); @@ -690,7 +768,7 @@ int acttab_insert(acttab *p, int makeItSafe){ if( p->nAction + n >= p->nActionAlloc ){ int oldAlloc = p->nActionAlloc; p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; - p->aAction = (struct lookahead_action *) realloc( p->aAction, + p->aAction = (struct lookahead_action *) lemon_realloc( p->aAction, sizeof(p->aAction[0])*p->nActionAlloc); if( p->aAction==0 ){ fprintf(stderr,"malloc failed\n"); @@ -1312,7 +1390,7 @@ static struct config **basisend = 0; /* End of list of basis configs */ /* Return a pointer to a new configuration */ PRIVATE struct config *newconfig(void){ - return (struct config*)calloc(1, sizeof(struct config)); + return (struct config*)lemon_calloc(1, sizeof(struct config)); } /* The configuration "old" is no longer used */ @@ -1528,19 +1606,19 @@ static char *bDefineUsed = 0; /* True for every -D macro actually used */ static void handle_D_option(char *z){ char **paz; nDefine++; - azDefine = (char **) realloc(azDefine, sizeof(azDefine[0])*nDefine); + azDefine = (char **) lemon_realloc(azDefine, sizeof(azDefine[0])*nDefine); if( azDefine==0 ){ fprintf(stderr,"out of memory\n"); exit(1); } - bDefineUsed = (char*)realloc(bDefineUsed, nDefine); + bDefineUsed = (char*)lemon_realloc(bDefineUsed, nDefine); if( bDefineUsed==0 ){ fprintf(stderr,"out of memory\n"); exit(1); } bDefineUsed[nDefine-1] = 0; paz = &azDefine[nDefine-1]; - *paz = (char *) malloc( lemonStrlen(z)+1 ); + *paz = (char *) lemon_malloc( lemonStrlen(z)+1 ); if( *paz==0 ){ fprintf(stderr,"out of memory\n"); exit(1); @@ -1554,7 +1632,7 @@ static void handle_D_option(char *z){ */ static char *outputDir = NULL; static void handle_d_option(char *z){ - outputDir = (char *) malloc( lemonStrlen(z)+1 ); + outputDir = (char *) lemon_malloc( lemonStrlen(z)+1 ); if( outputDir==0 ){ fprintf(stderr,"out of memory\n"); exit(1); @@ -1564,7 +1642,7 @@ static void handle_d_option(char *z){ static char *user_templatename = NULL; static void handle_T_option(char *z){ - user_templatename = (char *) malloc( lemonStrlen(z)+1 ); + user_templatename = (char *) lemon_malloc( lemonStrlen(z)+1 ); if( user_templatename==0 ){ memory_error(); } @@ -1801,6 +1879,7 @@ int main(int argc, char **argv){ /* return 0 on success, 1 on failure. */ exitcode = ((lem.errorcnt > 0) || (lem.nconflict > 0)) ? 1 : 0; + lemon_free_all(); exit(exitcode); return (exitcode); } @@ -2389,7 +2468,7 @@ static void parseonetoken(struct pstate *psp) case IN_RHS: if( x[0]=='.' ){ struct rule *rp; - rp = (struct rule *)calloc( sizeof(struct rule) + + rp = (struct rule *)lemon_calloc( sizeof(struct rule) + sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1); if( rp==0 ){ ErrorMsg(psp->filename,psp->tokenlineno, @@ -2441,17 +2520,17 @@ static void parseonetoken(struct pstate *psp) struct symbol *msp = psp->rhs[psp->nrhs-1]; if( msp->type!=MULTITERMINAL ){ struct symbol *origsp = msp; - msp = (struct symbol *) calloc(1,sizeof(*msp)); + msp = (struct symbol *) lemon_calloc(1,sizeof(*msp)); memset(msp, 0, sizeof(*msp)); msp->type = MULTITERMINAL; msp->nsubsym = 1; - msp->subsym = (struct symbol **) calloc(1,sizeof(struct symbol*)); + msp->subsym = (struct symbol**)lemon_calloc(1,sizeof(struct symbol*)); msp->subsym[0] = origsp; msp->name = origsp->name; psp->rhs[psp->nrhs-1] = msp; } msp->nsubsym++; - msp->subsym = (struct symbol **) realloc(msp->subsym, + msp->subsym = (struct symbol **) lemon_realloc(msp->subsym, sizeof(struct symbol*)*msp->nsubsym); msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]); if( ISLOWER(x[1]) || ISLOWER(msp->subsym[0]->name[0]) ){ @@ -2531,6 +2610,12 @@ static void parseonetoken(struct pstate *psp) }else if( strcmp(x,"default_type")==0 ){ psp->declargslot = &(psp->gp->vartype); psp->insertLineMacro = 0; + }else if( strcmp(x,"realloc")==0 ){ + psp->declargslot = &(psp->gp->reallocFunc); + psp->insertLineMacro = 0; + }else if( strcmp(x,"free")==0 ){ + psp->declargslot = &(psp->gp->freeFunc); + psp->insertLineMacro = 0; }else if( strcmp(x,"stack_size")==0 ){ psp->declargslot = &(psp->gp->stacksize); psp->insertLineMacro = 0; @@ -2661,7 +2746,7 @@ static void parseonetoken(struct pstate *psp) nLine = lemonStrlen(zLine); n += nLine + lemonStrlen(psp->filename) + nBack; } - *psp->declargslot = (char *) realloc(*psp->declargslot, n); + *psp->declargslot = (char *) lemon_realloc(*psp->declargslot, n); zBuf = *psp->declargslot + nOld; if( addLineMacro ){ if( nOld && zBuf[-1]!='\n' ){ @@ -2775,7 +2860,7 @@ static void parseonetoken(struct pstate *psp) }else if( ISUPPER(x[0]) || ((x[0]=='|' || x[0]=='/') && ISUPPER(x[1])) ){ struct symbol *msp = psp->tkclass; msp->nsubsym++; - msp->subsym = (struct symbol **) realloc(msp->subsym, + msp->subsym = (struct symbol **) lemon_realloc(msp->subsym, sizeof(struct symbol*)*msp->nsubsym); if( !ISUPPER(x[0]) ) x++; msp->subsym[msp->nsubsym-1] = Symbol_new(x); @@ -2990,10 +3075,10 @@ void Parse(struct lemon *gp) fseek(fp,0,2); filesize = ftell(fp); rewind(fp); - filebuf = (char *)malloc( filesize+1 ); + filebuf = (char *)lemon_malloc( filesize+1 ); if( filesize>100000000 || filebuf==0 ){ ErrorMsg(ps.filename,0,"Input file too large."); - free(filebuf); + lemon_free(filebuf); gp->errorcnt++; fclose(fp); return; @@ -3001,7 +3086,7 @@ void Parse(struct lemon *gp) if( fread(filebuf,1,filesize,fp)!=filesize ){ ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.", filesize); - free(filebuf); + lemon_free(filebuf); gp->errorcnt++; fclose(fp); return; @@ -3113,7 +3198,7 @@ void Parse(struct lemon *gp) *cp = (char)c; /* Restore the buffer */ cp = nextcp; } - free(filebuf); /* Release the buffer after parsing */ + lemon_free(filebuf); /* Release the buffer after parsing */ gp->rule = ps.firstrule; gp->errorcnt = ps.errorcnt; } @@ -3131,7 +3216,7 @@ struct plink *Plink_new(void){ if( plink_freelist==0 ){ int i; int amt = 100; - plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) ); + plink_freelist = (struct plink *)lemon_calloc( amt, sizeof(struct plink) ); if( plink_freelist==0 ){ fprintf(stderr, "Unable to allocate memory for a new follow-set propagation link.\n"); @@ -3184,9 +3269,7 @@ void Plink_delete(struct plink *plp) ** Procedures for generating reports and tables in the LEMON parser generator. */ -/* Generate a filename with the given suffix. Space to hold the -** name comes from malloc() and must be freed by the calling -** function. +/* Generate a filename with the given suffix. */ PRIVATE char *file_makename(struct lemon *lemp, const char *suffix) { @@ -3203,7 +3286,7 @@ PRIVATE char *file_makename(struct lemon *lemp, const char *suffix) sz += lemonStrlen(suffix); if( outputDir ) sz += lemonStrlen(outputDir) + 1; sz += 5; - name = (char*)malloc( sz ); + name = (char*)lemon_malloc( sz ); if( name==0 ){ fprintf(stderr,"Can't allocate space for a filename.\n"); exit(1); @@ -3230,7 +3313,7 @@ PRIVATE FILE *file_open( ){ FILE *fp; - if( lemp->outname ) free(lemp->outname); + if( lemp->outname ) lemon_free(lemp->outname); lemp->outname = file_makename(lemp, suffix); fp = fopen(lemp->outname,mode); if( fp==0 && *mode=='w' ){ @@ -3546,14 +3629,14 @@ PRIVATE char *pathsearch(char *argv0, char *name, int modemask) if( cp ){ c = *cp; *cp = 0; - path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 ); + path = (char *)lemon_malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 ); if( path ) lemon_sprintf(path,"%s/%s",argv0,name); *cp = c; }else{ pathlist = getenv("PATH"); if( pathlist==0 ) pathlist = ".:/bin:/usr/bin"; - pathbuf = (char *) malloc( lemonStrlen(pathlist) + 1 ); - path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 ); + pathbuf = (char *) lemon_malloc( lemonStrlen(pathlist) + 1 ); + path = (char *)lemon_malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 ); if( (pathbuf != 0) && (path!=0) ){ pathbufptr = pathbuf; lemon_strcpy(pathbuf, pathlist); @@ -3569,7 +3652,7 @@ PRIVATE char *pathsearch(char *argv0, char *name, int modemask) if( access(path,modemask)==0 ) break; } } - free(pathbufptr); + lemon_free(pathbufptr); } return path; } @@ -3700,7 +3783,7 @@ PRIVATE FILE *tplt_open(struct lemon *lemp) fprintf(stderr,"Can't open the template file \"%s\".\n",tpltname); lemp->errorcnt++; } - free(toFree); + lemon_free(toFree); return in; } @@ -3829,7 +3912,7 @@ PRIVATE char *append_str(const char *zText, int n, int p1, int p2){ } if( (int) (n+sizeof(zInt)*2+used) >= alloced ){ alloced = n + sizeof(zInt)*2 + used + 200; - z = (char *) realloc(z, alloced); + z = (char *) lemon_realloc(z, alloced); } if( z==0 ) return empty; while( n-- > 0 ){ @@ -4119,7 +4202,7 @@ void print_stack_union( /* Allocate and initialize types[] and allocate stddt[] */ arraysize = lemp->nsymbol * 2; - types = (char**)calloc( arraysize, sizeof(char*) ); + types = (char**)lemon_calloc( arraysize, sizeof(char*) ); if( types==0 ){ fprintf(stderr,"Out of memory.\n"); exit(1); @@ -4136,7 +4219,7 @@ void print_stack_union( len = lemonStrlen(sp->datatype); if( len>maxdtlength ) maxdtlength = len; } - stddt = (char*)malloc( maxdtlength*2 + 1 ); + stddt = (char*)lemon_malloc( maxdtlength*2 + 1 ); if( stddt==0 ){ fprintf(stderr,"Out of memory.\n"); exit(1); @@ -4185,7 +4268,7 @@ void print_stack_union( } if( types[hash]==0 ){ sp->dtnum = hash + 1; - types[hash] = (char*)malloc( lemonStrlen(stddt)+1 ); + types[hash] = (char*)lemon_malloc( lemonStrlen(stddt)+1 ); if( types[hash]==0 ){ fprintf(stderr,"Out of memory.\n"); exit(1); @@ -4207,13 +4290,13 @@ void print_stack_union( for(i=0; ierrsym && lemp->errsym->useCnt ){ fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++; } - free(stddt); - free(types); + lemon_free(stddt); + lemon_free(types); fprintf(out,"} YYMINORTYPE;\n"); lineno++; *plineno = lineno; } @@ -4309,7 +4392,7 @@ void ReportTable( struct action *ap; struct rule *rp; struct acttab *pActtab; - int i, j, n, sz; + int i, j, n, sz, mn, mx; int nLookAhead; int szActionType; /* sizeof(YYACTIONTYPE) */ int szCodeType; /* sizeof(YYCODETYPE) */ @@ -4442,7 +4525,7 @@ void ReportTable( if( mhflag ){ char *incName = file_makename(lemp, ".h"); fprintf(out,"#include \"%s\"\n", incName); lineno++; - free(incName); + lemon_free(incName); } tplt_xfer(lemp->name,in,out,&lineno); @@ -4501,6 +4584,21 @@ void ReportTable( fprintf(out,"#define %sARG_FETCH\n",name); lineno++; fprintf(out,"#define %sARG_STORE\n",name); lineno++; } + if( lemp->reallocFunc ){ + fprintf(out,"#define YYREALLOC %s\n", lemp->reallocFunc); lineno++; + }else{ + fprintf(out,"#define YYREALLOC realloc\n"); lineno++; + } + if( lemp->freeFunc ){ + fprintf(out,"#define YYFREE %s\n", lemp->freeFunc); lineno++; + }else{ + fprintf(out,"#define YYFREE free\n"); lineno++; + } + if( lemp->reallocFunc && lemp->freeFunc ){ + fprintf(out,"#define YYDYNSTACK 1\n"); lineno++; + }else{ + fprintf(out,"#define YYDYNSTACK 0\n"); lineno++; + } if( lemp->ctx && lemp->ctx[0] ){ i = lemonStrlen(lemp->ctx); while( i>=1 && ISSPACE(lemp->ctx[i-1]) ) i--; @@ -4534,7 +4632,7 @@ void ReportTable( ** table must be computed before generating the YYNSTATE macro because ** we need to know how many states can be eliminated. */ - ax = (struct axset *) calloc(lemp->nxstate*2, sizeof(ax[0])); + ax = (struct axset *) lemon_calloc(lemp->nxstate*2, sizeof(ax[0])); if( ax==0 ){ fprintf(stderr,"malloc failed\n"); exit(1); @@ -4592,7 +4690,7 @@ void ReportTable( } #endif } - free(ax); + lemon_free(ax); /* Mark rules that are actually used for reduce actions after all ** optimizations have been applied @@ -4624,6 +4722,22 @@ void ReportTable( fprintf(out,"#define YY_MIN_REDUCE %d\n", lemp->minReduce); lineno++; i = lemp->minReduce + lemp->nrule; fprintf(out,"#define YY_MAX_REDUCE %d\n", i-1); lineno++; + + /* Minimum and maximum token values that have a destructor */ + mn = mx = 0; + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + + if( sp && sp->type!=TERMINAL && sp->destructor ){ + if( mn==0 || sp->indexindex; + if( sp->index>mx ) mx = sp->index; + } + } + if( lemp->tokendest ) mn = 0; + if( lemp->vardest ) mx = lemp->nsymbol-1; + fprintf(out,"#define YY_MIN_DSTRCTR %d\n", mn); lineno++; + fprintf(out,"#define YY_MAX_DSTRCTR %d\n", mx); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); /* Now output the action table and its associates: @@ -4767,7 +4881,7 @@ void ReportTable( /* Generate the table of fallback tokens. */ if( lemp->has_fallback ){ - int mx = lemp->nterminal - 1; + mx = lemp->nterminal - 1; /* 2019-08-28: Generate fallback entries for every token to avoid ** having to do a range check on the index */ /* while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } */ @@ -5202,7 +5316,7 @@ void SetSize(int n) /* Allocate a new set */ char *SetNew(void){ char *s; - s = (char*)calloc( size, 1); + s = (char*)lemon_calloc( size, 1); if( s==0 ){ memory_error(); } @@ -5212,7 +5326,7 @@ char *SetNew(void){ /* Deallocate a set */ void SetFree(char *s) { - free(s); + lemon_free(s); } /* Add a new element to the set. Return TRUE if the element was added @@ -5271,7 +5385,7 @@ const char *Strsafe(const char *y) if( y==0 ) return 0; z = Strsafe_find(y); - if( z==0 && (cpy=(char *)malloc( lemonStrlen(y)+1 ))!=0 ){ + if( z==0 && (cpy=(char *)lemon_malloc( lemonStrlen(y)+1 ))!=0 ){ lemon_strcpy(cpy,y); z = cpy; Strsafe_insert(z); @@ -5307,13 +5421,13 @@ static struct s_x1 *x1a; /* Allocate a new associative array */ void Strsafe_init(void){ if( x1a ) return; - x1a = (struct s_x1*)malloc( sizeof(struct s_x1) ); + x1a = (struct s_x1*)lemon_malloc( sizeof(struct s_x1) ); if( x1a ){ x1a->size = 1024; x1a->count = 0; - x1a->tbl = (x1node*)calloc(1024, sizeof(x1node) + sizeof(x1node*)); + x1a->tbl = (x1node*)lemon_calloc(1024, sizeof(x1node) + sizeof(x1node*)); if( x1a->tbl==0 ){ - free(x1a); + lemon_free(x1a); x1a = 0; }else{ int i; @@ -5348,7 +5462,7 @@ int Strsafe_insert(const char *data) struct s_x1 array; array.size = arrSize = x1a->size*2; array.count = x1a->count; - array.tbl = (x1node*)calloc(arrSize, sizeof(x1node) + sizeof(x1node*)); + array.tbl = (x1node*)lemon_calloc(arrSize, sizeof(x1node)+sizeof(x1node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x1node**)&(array.tbl[arrSize]); for(i=0; ifrom = &(array.ht[h]); array.ht[h] = newnp; } - /* free(x1a->tbl); // This program was originally for 16-bit machines. + /* lemon_free(x1a->tbl); // This program was originally for 16-bit machines. ** Don't worry about freeing memory on modern platforms. */ *x1a = array; } @@ -5404,7 +5518,7 @@ struct symbol *Symbol_new(const char *x) sp = Symbol_find(x); if( sp==0 ){ - sp = (struct symbol *)calloc(1, sizeof(struct symbol) ); + sp = (struct symbol *)lemon_calloc(1, sizeof(struct symbol) ); MemoryCheck(sp); sp->name = Strsafe(x); sp->type = ISUPPER(*x) ? TERMINAL : NONTERMINAL; @@ -5475,13 +5589,13 @@ static struct s_x2 *x2a; /* Allocate a new associative array */ void Symbol_init(void){ if( x2a ) return; - x2a = (struct s_x2*)malloc( sizeof(struct s_x2) ); + x2a = (struct s_x2*)lemon_malloc( sizeof(struct s_x2) ); if( x2a ){ x2a->size = 128; x2a->count = 0; - x2a->tbl = (x2node*)calloc(128, sizeof(x2node) + sizeof(x2node*)); + x2a->tbl = (x2node*)lemon_calloc(128, sizeof(x2node) + sizeof(x2node*)); if( x2a->tbl==0 ){ - free(x2a); + lemon_free(x2a); x2a = 0; }else{ int i; @@ -5516,7 +5630,7 @@ int Symbol_insert(struct symbol *data, const char *key) struct s_x2 array; array.size = arrSize = x2a->size*2; array.count = x2a->count; - array.tbl = (x2node*)calloc(arrSize, sizeof(x2node) + sizeof(x2node*)); + array.tbl = (x2node*)lemon_calloc(arrSize, sizeof(x2node)+sizeof(x2node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x2node**)&(array.tbl[arrSize]); for(i=0; ifrom = &(array.ht[h]); array.ht[h] = newnp; } - /* free(x2a->tbl); // This program was originally written for 16-bit + /* lemon_free(x2a->tbl); // This program was originally written for 16-bit ** machines. Don't worry about freeing this trivial amount of memory ** on modern platforms. Just leak it. */ *x2a = array; @@ -5593,7 +5707,7 @@ struct symbol **Symbol_arrayof() int i,arrSize; if( x2a==0 ) return 0; arrSize = x2a->count; - array = (struct symbol **)calloc(arrSize, sizeof(struct symbol *)); + array = (struct symbol **)lemon_calloc(arrSize, sizeof(struct symbol *)); if( array ){ for(i=0; itbl[i].data; } @@ -5641,7 +5755,7 @@ PRIVATE unsigned statehash(struct config *a) struct state *State_new() { struct state *newstate; - newstate = (struct state *)calloc(1, sizeof(struct state) ); + newstate = (struct state *)lemon_calloc(1, sizeof(struct state) ); MemoryCheck(newstate); return newstate; } @@ -5674,13 +5788,13 @@ static struct s_x3 *x3a; /* Allocate a new associative array */ void State_init(void){ if( x3a ) return; - x3a = (struct s_x3*)malloc( sizeof(struct s_x3) ); + x3a = (struct s_x3*)lemon_malloc( sizeof(struct s_x3) ); if( x3a ){ x3a->size = 128; x3a->count = 0; - x3a->tbl = (x3node*)calloc(128, sizeof(x3node) + sizeof(x3node*)); + x3a->tbl = (x3node*)lemon_calloc(128, sizeof(x3node) + sizeof(x3node*)); if( x3a->tbl==0 ){ - free(x3a); + lemon_free(x3a); x3a = 0; }else{ int i; @@ -5715,7 +5829,7 @@ int State_insert(struct state *data, struct config *key) struct s_x3 array; array.size = arrSize = x3a->size*2; array.count = x3a->count; - array.tbl = (x3node*)calloc(arrSize, sizeof(x3node) + sizeof(x3node*)); + array.tbl = (x3node*)lemon_calloc(arrSize, sizeof(x3node)+sizeof(x3node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x3node**)&(array.tbl[arrSize]); for(i=0; ifrom = &(array.ht[h]); array.ht[h] = newnp; } - free(x3a->tbl); + lemon_free(x3a->tbl); *x3a = array; } /* Insert the new data */ @@ -5772,7 +5886,7 @@ struct state **State_arrayof(void) int i,arrSize; if( x3a==0 ) return 0; arrSize = x3a->count; - array = (struct state **)calloc(arrSize, sizeof(struct state *)); + array = (struct state **)lemon_calloc(arrSize, sizeof(struct state *)); if( array ){ for(i=0; itbl[i].data; } @@ -5814,13 +5928,13 @@ static struct s_x4 *x4a; /* Allocate a new associative array */ void Configtable_init(void){ if( x4a ) return; - x4a = (struct s_x4*)malloc( sizeof(struct s_x4) ); + x4a = (struct s_x4*)lemon_malloc( sizeof(struct s_x4) ); if( x4a ){ x4a->size = 64; x4a->count = 0; - x4a->tbl = (x4node*)calloc(64, sizeof(x4node) + sizeof(x4node*)); + x4a->tbl = (x4node*)lemon_calloc(64, sizeof(x4node) + sizeof(x4node*)); if( x4a->tbl==0 ){ - free(x4a); + lemon_free(x4a); x4a = 0; }else{ int i; @@ -5855,7 +5969,8 @@ int Configtable_insert(struct config *data) struct s_x4 array; array.size = arrSize = x4a->size*2; array.count = x4a->count; - array.tbl = (x4node*)calloc(arrSize, sizeof(x4node) + sizeof(x4node*)); + array.tbl = (x4node*)lemon_calloc(arrSize, + sizeof(x4node) + sizeof(x4node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x4node**)&(array.tbl[arrSize]); for(i=0; ifrom = &(array.ht[h]); array.ht[h] = newnp; } - /* free(x4a->tbl); // This code was originall written for 16-bit machines. - ** on modern machines, don't worry about freeing this trival amount of - ** memory. */ *x4a = array; } /* Insert the new data */ diff --git a/tool/lempar.c b/tool/lempar.c index 8cc57897d..851a0e2e5 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -67,6 +67,9 @@ ** ParseARG_STORE Code to store %extra_argument into yypParser ** ParseARG_FETCH Code to extract %extra_argument from yypParser ** ParseCTX_* As ParseARG_ except for %extra_context +** YYREALLOC Name of the realloc() function to use +** YYFREE Name of the free() function to use +** YYDYNSTACK True if stack space should be extended on heap ** YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. ** YYNSTATE the combined number of states. @@ -80,6 +83,8 @@ ** YY_NO_ACTION The yy_action[] code for no-op ** YY_MIN_REDUCE Minimum value for reduce actions ** YY_MAX_REDUCE Maximum value for reduce actions +** YY_MIN_DSTRCTR Minimum symbol value that has a destructor +** YY_MAX_DSTRCTR Maximum symbol value that has a destructor */ #ifndef INTERFACE # define INTERFACE 1 @@ -101,6 +106,22 @@ # define yytestcase(X) #endif +/* Macro to determine if stack space has the ability to grow using +** heap memory. +*/ +#if YYSTACKDEPTH<=0 || YYDYNSTACK +# define YYGROWABLESTACK 1 +#else +# define YYGROWABLESTACK 0 +#endif + +/* Guarantee a minimum number of initial stack slots. +*/ +#if YYSTACKDEPTH<=0 +# undef YYSTACKDEPTH +# define YYSTACKDEPTH 2 /* Need a minimum stack size */ +#endif + /* Next are the tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement @@ -212,14 +233,9 @@ struct yyParser { #endif ParseARG_SDECL /* A place to hold %extra_argument */ ParseCTX_SDECL /* A place to hold %extra_context */ -#if YYSTACKDEPTH<=0 - int yystksz; /* Current side of the stack */ - yyStackEntry *yystack; /* The parser's stack */ - yyStackEntry yystk0; /* First stack entry */ -#else - yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ - yyStackEntry *yystackEnd; /* Last entry in the stack */ -#endif + yyStackEntry *yystackEnd; /* Last entry in the stack */ + yyStackEntry *yystack; /* The parser stack */ + yyStackEntry yystk0[YYSTACKDEPTH]; /* Initial stack space */ }; typedef struct yyParser yyParser; @@ -273,37 +289,45 @@ static const char *const yyRuleName[] = { #endif /* NDEBUG */ -#if YYSTACKDEPTH<=0 +#if YYGROWABLESTACK /* ** Try to increase the size of the parser stack. Return the number ** of errors. Return 0 on success. */ static int yyGrowStack(yyParser *p){ + int oldSize = 1 + (int)(p->yystackEnd - p->yystack); int newSize; int idx; yyStackEntry *pNew; - newSize = p->yystksz*2 + 100; - idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; - if( p->yystack==&p->yystk0 ){ - pNew = malloc(newSize*sizeof(pNew[0])); - if( pNew ) pNew[0] = p->yystk0; + newSize = oldSize*2 + 100; + idx = (int)(p->yytos - p->yystack); + if( p->yystack==p->yystk0 ){ + pNew = YYREALLOC(0, newSize*sizeof(pNew[0])); + if( pNew==0 ) return 1; + memcpy(pNew, p->yystack, oldSize*sizeof(pNew[0])); }else{ - pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0])); + if( pNew==0 ) return 1; } - if( pNew ){ - p->yystack = pNew; - p->yytos = &p->yystack[idx]; + p->yystack = pNew; + p->yytos = &p->yystack[idx]; #ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", - yyTracePrompt, p->yystksz, newSize); - } -#endif - p->yystksz = newSize; + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", + yyTracePrompt, oldSize, newSize); } - return pNew==0; +#endif + p->yystackEnd = &p->yystack[newSize-1]; + return 0; } +#endif /* YYGROWABLESTACK */ + +#if !YYGROWABLESTACK +/* For builds that do no have a growable stack, yyGrowStack always +** returns an error. +*/ +# define yyGrowStack(X) 1 #endif /* Datatype of the argument to the memory allocated passed as the @@ -323,24 +347,14 @@ void ParseInit(void *yypRawParser ParseCTX_PDECL){ #ifdef YYTRACKMAXSTACKDEPTH yypParser->yyhwm = 0; #endif -#if YYSTACKDEPTH<=0 - yypParser->yytos = NULL; - yypParser->yystack = NULL; - yypParser->yystksz = 0; - if( yyGrowStack(yypParser) ){ - yypParser->yystack = &yypParser->yystk0; - yypParser->yystksz = 1; - } -#endif + yypParser->yystack = yypParser->yystk0; + yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; #ifndef YYNOERRORRECOVERY yypParser->yyerrcnt = -1; #endif yypParser->yytos = yypParser->yystack; yypParser->yystack[0].stateno = 0; yypParser->yystack[0].major = 0; -#if YYSTACKDEPTH>0 - yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; -#endif } #ifndef Parse_ENGINEALWAYSONSTACK @@ -426,9 +440,26 @@ static void yy_pop_parser_stack(yyParser *pParser){ */ void ParseFinalize(void *p){ yyParser *pParser = (yyParser*)p; - while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); -#if YYSTACKDEPTH<=0 - if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); + + /* In-lined version of calling yy_pop_parser_stack() for each + ** element left in the stack */ + yyStackEntry *yytos = pParser->yytos; + while( yytos>pParser->yystack ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + if( yytos->major>=YY_MIN_DSTRCTR ){ + yy_destructor(pParser, yytos->major, &yytos->minor); + } + yytos--; + } + +#if YYGROWABLESTACK + if( pParser->yystack!=pParser->yystk0 ) YYFREE(pParser->yystack); #endif } @@ -654,25 +685,19 @@ static void yy_shift( assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); } #endif -#if YYSTACKDEPTH>0 - if( yypParser->yytos>yypParser->yystackEnd ){ - yypParser->yytos--; - yyStackOverflow(yypParser); - return; - } -#else - if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ + yytos = yypParser->yytos; + if( yytos>yypParser->yystackEnd ){ if( yyGrowStack(yypParser) ){ yypParser->yytos--; yyStackOverflow(yypParser); return; } + yytos = yypParser->yytos; + assert( yytos <= yypParser->yystackEnd ); } -#endif if( yyNewState > YY_MAX_SHIFT ){ yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; } - yytos = yypParser->yytos; yytos->stateno = yyNewState; yytos->major = yyMajor; yytos->minor.yy0 = yyMinor; @@ -911,19 +936,12 @@ void Parse( (int)(yypParser->yytos - yypParser->yystack)); } #endif -#if YYSTACKDEPTH>0 if( yypParser->yytos>=yypParser->yystackEnd ){ - yyStackOverflow(yypParser); - break; - } -#else - if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ if( yyGrowStack(yypParser) ){ yyStackOverflow(yypParser); break; } } -#endif } yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor ParseCTX_PARAM); }else if( yyact <= YY_MAX_SHIFTREDUCE ){ diff --git a/tool/mkopcodeh.tcl b/tool/mkopcodeh.tcl index 6fb3b7594..18fe1a265 100644 --- a/tool/mkopcodeh.tcl +++ b/tool/mkopcodeh.tcl @@ -81,6 +81,7 @@ while {![eof $in]} { set op($name) -1 set group($name) 0 set jump($name) 0 + set jump0($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 @@ -109,6 +110,7 @@ while {![eof $in]} { out2 {set out2($name) 1} out3 {set out3($name) 1} ncycle {set ncycle($name) 1} + jump0 {set jump($name) 1; set jump0($name) 1;} } } if {$group($name)} { @@ -137,6 +139,7 @@ puts "/* Automatically generated. Do not edit */" puts "/* See the tool/mkopcodeh.tcl script for details */" foreach name {OP_Noop OP_Explain OP_Abortable} { set jump($name) 0 + set jump0($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 @@ -256,7 +259,9 @@ for {set i 0} {$i<=$max} {incr i} { set name $def($i) puts -nonewline [format {#define %-16s %3d} $name $i] set com {} - if {[info exists jump($name)] && $jump($name)} { + if {[info exists jump0($name)] && $jump0($name)} { + lappend com "jump0" + } elseif {[info exists jump($name)] && $jump($name)} { lappend com "jump" } if {[info exists sameas($i)]} { @@ -289,6 +294,7 @@ for {set i 0} {$i<=$max} {incr i} { if {$out2($name)} {incr x 16} if {$out3($name)} {incr x 32} if {$ncycle($name)} {incr x 64} + if {$jump0($name)} {incr x 128} } set bv($i) $x } @@ -304,6 +310,7 @@ puts "#define OPFLG_IN3 0x08 /* in3: P3 is an input */" puts "#define OPFLG_OUT2 0x10 /* out2: P2 is an output */" puts "#define OPFLG_OUT3 0x20 /* out3: P3 is an output */" puts "#define OPFLG_NCYCLE 0x40 /* ncycle:Cycles count against P1 */" +puts "#define OPFLG_JUMP0 0x80 /* jump0: P2 might be zero */" puts "#define OPFLG_INITIALIZER \173\\" for {set i 0} {$i<=$max} {incr i} { if {$i%8==0} { diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 53fa59ac7..ef8353df4 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -17,7 +17,7 @@ # After the "tsrc" directory has been created and populated, run # this script: # -# tclsh mksqlite3c.tcl +# tclsh mksqlite3c.tcl [flags] [extra source files] # # The amalgamated SQLite code will be written into sqlite3.c # @@ -42,6 +42,7 @@ set linemacros 0 set useapicall 0 set enable_recover 0 set srcdir tsrc +set extrasrc [list] for {set i 0} {$i<[llength $argv]} {incr i} { set x [lindex $argv $i] @@ -63,8 +64,10 @@ for {set i 0} {$i<[llength $argv]} {incr i} { } elseif {[regexp {^-?-((help)|\?)$} $x]} { puts $help exit 0 - } else { + } elseif {[regexp {^-?-} $x]} { error "unknown command-line option: $x" + } else { + lappend extrasrc $x } } set in [open $srcdir/sqlite3.h] @@ -349,6 +352,21 @@ proc copy_file {filename} { section_comment "End of $tail" } +# Read the source file named $filename and write it into the +# sqlite3.c output file. The only transformation is the trimming +# of EOL whitespace. +# +proc copy_file_verbatim {filename} { + global out + set in [open $filename r] + set tail [file tail $filename] + section_comment "Begin EXTRA_SRC file $tail" + while {![eof $in]} { + set line [string trimright [gets $in]] + puts $out $line + } + section_comment "End of EXTRA_SRC $tail" +} # Process the source files. Process files containing commonly # used subroutines first in order to help the compiler find @@ -470,13 +488,16 @@ set flist { sqlite3session.c fts5.c stmt.c -} +} if {$enable_recover} { lappend flist sqlite3recover.c dbdata.c } foreach file $flist { copy_file $srcdir/$file } +foreach file $extrasrc { + copy_file_verbatim $file +} puts $out \ "/* Return the source-id for this library */ diff --git a/tool/speed-check.sh b/tool/speed-check.sh index 4cc25794a..5d425c3b3 100644 --- a/tool/speed-check.sh +++ b/tool/speed-check.sh @@ -161,6 +161,9 @@ while test "$1" != ""; do --fp) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset fp" ;; + --parsenumber) + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset parsenumber" + ;; --stmtscanstatus) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --stmtscanstatus" ;; From c50f84f591e11cbed4069a9bb0209a69a46d330c Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 3 Jul 2024 15:18:44 -0400 Subject: [PATCH 149/340] Avoids overwriting log level and target if they've already been set This commit corrects an issue what could occur if cipher_log_level and/or cipher_log are set prior to activating sqlcipher, i.e. if those pragmas are called before PRAGMA key or sqlite3_key. In that case the specific requested log settings would be ovewritten by different defaults. --- src/crypto_impl.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index d1485eeff..0179aff46 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -205,15 +205,24 @@ void sqlcipher_activate() { sqlcipher_static_mutex[i] = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); } #ifndef SQLCIPHER_OMIT_DEFAULT_LOGGING - /* when sqlcipher is first activated, set a default log target and level of WARN. Use the "device log" - for android (logcat) or apple (console). Use stderr on all other platforms. */ + /* when sqlcipher is first activated, set a default log target and level of WARN if the + logging settings have not yet been initialized. Use the "device log" for + android (logcat) or apple (console). Use stderr on all other platforms. */ if(!sqlcipher_log_set) { - sqlcipher_log_level = SQLCIPHER_LOG_WARN; + + /* set log level if it is different than the uninitalized default value of NONE */ + if(sqlcipher_log_level == SQLCIPHER_LOG_NONE) { + sqlcipher_log_level = SQLCIPHER_LOG_WARN; + } + + /* set the default file or device if neither is already set */ + if(sqlcipher_log_device == 0 && sqlcipher_log_file == NULL) { #if defined(__ANDROID__) || defined(__APPLE_) - sqlcipher_log_device = 1; + sqlcipher_log_device = 1; #else - sqlcipher_log_file = stderr; + sqlcipher_log_file = stderr; #endif + } sqlcipher_log_set = 1; } #endif From 44fb4679324a6e0e8b0d01bebe8c61a7d652d600 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 8 Jul 2024 12:58:28 -0400 Subject: [PATCH 150/340] Adds PRAGMA cipher_log_subsystem to restrict output of log messages This change categorizes all sqlcipher_log calls with a subsystem of either CORE, MEMORY, MUTEX, or PROVIDER. The pragma allows an application to restrict logging of messages to a specific subsystem to reduce the verbosity of log messages. This is most useful in the context of TRACE and DEBUG level logging where the volume of log messages is very high. --- src/crypto.c | 119 ++++++++++-------- src/crypto.h | 10 +- src/crypto_impl.c | 264 ++++++++++++++++++++------------------- src/crypto_libtomcrypt.c | 32 ++--- src/crypto_nss.c | 16 +-- src/crypto_openssl.c | 92 +++++++------- 6 files changed, 279 insertions(+), 254 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 4a2611725..fa9a9785c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -53,11 +53,11 @@ static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ct page_sz = sqlcipher_codec_ctx_get_pagesize(ctx); reserve_sz = sqlcipher_codec_ctx_get_reservesize(ctx); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d", page_sz, reserve_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d", page_sz, reserve_sz); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: entering database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: entered database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: entered database mutex %p", db->mutex); db->nextPagesize = page_sz; /* before forcing the page size we need to unset the BTS_PAGESIZE_FIXED flag, else @@ -65,29 +65,29 @@ static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ct pDb->pBt->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; rc = sqlite3BtreeSetPageSize(pDb->pBt, page_sz, reserve_sz, 0); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d", rc); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: leaving database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "codec_set_btree_to_codec_pagesize: left database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: left database mutex %p", db->mutex); return rc; } static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) { struct Db *pDb = &db->aDb[nDb]; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_set_pass_key: db=%p nDb=%d for_ctx=%d", db, nDb, for_ctx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_pass_key: db=%p nDb=%d for_ctx=%d", db, nDb, for_ctx); if(pDb->pBt) { codec_ctx *ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); if(ctx) { return sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, for_ctx); } else { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_set_pass_key: error ocurred fetching codec from pager on db %d", nDb); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "codec_set_pass_key: error ocurred fetching codec from pager on db %d", nDb); return SQLITE_ERROR; } } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "codec_set_pass_key: no btree present on db %d", nDb); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "codec_set_pass_key: no btree present on db %d", nDb); return SQLITE_ERROR; } @@ -101,12 +101,12 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } if(sqlite3_stricmp(zLeft, "key") !=0 && sqlite3_stricmp(zLeft, "rekey") != 0) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_pragma: db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); } #ifdef SQLCIPHER_EXT if(sqlcipher_ext_pragma(db, iDb, pParse, zLeft, zRight)) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_pragma: PRAGMA handled by sqlcipher_ext_pragma"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: PRAGMA handled by sqlcipher_ext_pragma"); } else #endif #ifdef SQLCIPHER_TEST @@ -190,7 +190,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef char *migrate_status = sqlite3_mprintf("%d", status); sqlcipher_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); if(status != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_pragma: error occurred during cipher_migrate: %d", status); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: error occurred during cipher_migrate: %d", status); sqlcipher_codec_ctx_set_error(ctx, status); } } @@ -698,6 +698,17 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlcipher_set_log_level(level); sqlcipher_vdbe_return_string(pParse, "cipher_log_level", sqlite3_mprintf("%u", level), P4_DYNAMIC); } else + if( sqlite3_stricmp(zLeft, "cipher_log_subsystem")==0 && zRight){ + unsigned int subsys = SQLCIPHER_LOG_ALL; + if(sqlite3_stricmp(zRight, "NONE" )==0) subsys = SQLCIPHER_LOG_NONE; + else if(sqlite3_stricmp(zRight, "ALL" )==0) subsys = SQLCIPHER_LOG_ALL; + else if(sqlite3_stricmp(zRight, "CORE" )==0) subsys = SQLCIPHER_LOG_CORE; + else if(sqlite3_stricmp(zRight, "MEMORY" )==0) subsys = SQLCIPHER_LOG_MEMORY; + else if(sqlite3_stricmp(zRight, "MUTEX" )==0) subsys = SQLCIPHER_LOG_MUTEX; + else if(sqlite3_stricmp(zRight, "PROVIDER")==0) subsys = SQLCIPHER_LOG_PROVIDER; + sqlcipher_set_log_subsystem(subsys); + sqlcipher_vdbe_return_string(pParse, "cipher_log_subsystem", sqlite3_mprintf("%u", subsys), P4_DYNAMIC); + } else if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); sqlcipher_vdbe_return_string(pParse, "cipher_log", status, P4_DYNAMIC); @@ -732,7 +743,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { int plaintext_header_sz = sqlcipher_codec_ctx_get_plaintext_header_size(ctx); int cctx = CIPHER_READ_CTX; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3Codec: pgno=%d, mode=%d, page_sz=%d", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3Codec: pgno=%d, mode=%d, page_sz=%d", pgno, mode, page_sz); #ifdef SQLCIPHER_EXT if(sqlcipher_license_check(ctx) != SQLITE_OK) return NULL; @@ -740,7 +751,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { /* call to derive keys if not present yet */ if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error occurred during key derivation: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error occurred during key derivation: %d", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -749,7 +760,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { PRAGMA. We can't set the error state on the pager at that point because the pager may not be open yet. However, this is a fatal error state, so abort the codec */ if(plaintext_header_sz < 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error invalid plaintext_header_sz: %d", plaintext_header_sz); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error invalid plaintext_header_sz: %d", plaintext_header_sz); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); return NULL; } @@ -758,7 +769,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { offset = plaintext_header_sz ? plaintext_header_sz : FILE_HEADER_SZ; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3Codec: switch mode=%d offset=%d", mode, offset); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3Codec: switch mode=%d offset=%d", mode, offset); switch(mode) { case CODEC_READ_OP: /* decrypt */ if(pgno == 1) /* copy initial part of file header or SQLite magic to buffer */ @@ -768,13 +779,13 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlite3Codec: simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3Codec: simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { /* failure to decrypt a page is considered a permanent error and will render the pager unusable in order to prevent inconsistent data being loaded into page cache */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error decrypting page %d data: %d", pgno, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error decrypting page %d data: %d", pgno, rc); sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); } else { @@ -792,7 +803,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { void *kdf_salt = NULL; /* retrieve the kdf salt */ if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &kdf_salt)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error retrieving salt: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error retrieving salt: %d", rc); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; } @@ -802,13 +813,13 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlite3Codec: simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3Codec: simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); } #endif if(rc != SQLITE_OK) { /* failure to encrypt a page is considered a permanent error and will render the pager unusable in order to prevent corrupted pages from being written to the main databased when using WAL */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error encrypting page %d data: %d", pgno, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error encrypting page %d data: %d", pgno, rc); sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); sqlcipher_codec_ctx_set_error(ctx, rc); return NULL; @@ -818,7 +829,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { break; default: - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3Codec: error unsupported codec mode %d", mode); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error unsupported codec mode %d", mode); sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); /* unsupported mode, set error */ return pData; break; @@ -835,7 +846,7 @@ static void sqlite3FreeCodecArg(void *pCodecArg) { int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { struct Db *pDb = &db->aDb[nDb]; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecAttach: db=%p, nDb=%d", db, nDb); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: db=%p, nDb=%d", db, nDb); if(nKey && zKey && pDb->pBt) { int rc; @@ -847,19 +858,19 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { if(ctx != NULL && SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) { /* there is already a codec attached to this database, so we should not proceed */ - sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlcipherCodecAttach: no codec attached to db"); + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: no codec attached to db"); return SQLITE_OK; } /* check if the sqlite3_file is open, and if not force handle to NULL */ if((fd = sqlite3PagerFile(pPager))->pMethods == 0) fd = NULL; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecAttach: calling sqlcipher_activate()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipher_activate()"); sqlcipher_activate(); /* perform internal initialization for sqlcipher */ - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipherCodecAttach: entering database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipherCodecAttach: entered database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: entered database mutex %p", db->mutex); #ifdef SQLCIPHER_EXT if((rc = sqlite3_set_authorizer(db, sqlcipher_license_authorizer, db)) != SQLITE_OK) { @@ -869,44 +880,44 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { #endif /* point the internal codec argument against the contet to be prepared */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecAttach: calling sqlcipher_codec_ctx_init()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipher_codec_ctx_init()"); rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, zKey, nKey); if(rc != SQLITE_OK) { /* initialization failed, do not attach potentially corrupted context */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipherCodecAttach: context initialization failed, forcing error state with rc=%d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: context initialization failed, forcing error state with rc=%d", rc); /* force an error at the pager level, such that even the upstream caller ignores the return code the pager will be in an error state and will process no further operations */ sqlite3pager_error(pPager, rc); pDb->pBt->pBt->db->errCode = rc; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipherCodecAttach: leaving database mutex %p (early return on rc=%d)", db->mutex, rc); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: leaving database mutex %p (early return on rc=%d)", db->mutex, rc); sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipherCodecAttach: left database mutex %p (early return on rc=%d)", db->mutex, rc); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: left database mutex %p (early return on rc=%d)", db->mutex, rc); return rc; } - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecAttach: calling sqlcipherPagerSetCodec()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipherPagerSetCodec()"); sqlcipherPagerSetCodec(sqlite3BtreePager(pDb->pBt), sqlite3Codec, NULL, sqlite3FreeCodecArg, (void *) ctx); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecAttach: calling codec_set_btree_to_codec_pagesize()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling codec_set_btree_to_codec_pagesize()"); codec_set_btree_to_codec_pagesize(db, pDb, ctx); /* force secure delete. This has the benefit of wiping internal data when deleted and also ensures that all pages are written to disk (i.e. not skipped by sqlite3PagerDontWrite optimizations) */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecAttach: calling sqlite3BtreeSecureDelete()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlite3BtreeSecureDelete()"); sqlite3BtreeSecureDelete(pDb->pBt, 1); /* if fd is null, then this is an in-memory database and we dont' want to overwrite the AutoVacuum settings if not null, then set to the default */ if(fd != NULL) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecAttach: calling sqlite3BtreeSetAutoVacuum()"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlite3BtreeSetAutoVacuum()"); sqlite3BtreeSetAutoVacuum(pDb->pBt, SQLITE_DEFAULT_AUTOVACUUM); } - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipherCodecAttach: leaving database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipherCodecAttach: left database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: left database mutex %p", db->mutex); } return SQLITE_OK; } @@ -930,23 +941,23 @@ void sqlite3_activate_see(const char* in) { } int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_key: db=%p", db); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_key: db=%p", db); return sqlite3_key_v2(db, "main", pKey, nKey); } int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_key_v2: db=%p zDb=%s", db, zDb); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_key_v2: db=%p zDb=%s", db, zDb); /* attach key if db and pKey are not null and nKey is > 0 */ if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); return sqlcipherCodecAttach(db, db_index, pKey, nKey); } - sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlite3_key_v2: no key provided"); + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3_key_v2: no key provided"); return SQLITE_ERROR; } int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey: db=%p", db); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey: db=%p", db); return sqlite3_rekey_v2(db, "main", pKey, nKey); } @@ -961,11 +972,11 @@ int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { ** 3. If there is a key present, re-encrypt the database with the new key */ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: db=%p zDb=%s", db, zDb); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: db=%p zDb=%s", db, zDb); if(db && pKey && nKey) { int db_index = sqlcipher_find_db_index(db, zDb); struct Db *pDb = &db->aDb[db_index]; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: database zDb=%p db_index:%d", zDb, db_index); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: database zDb=%p db_index:%d", zDb, db_index); if(pDb->pBt) { codec_ctx *ctx; int rc, page_count; @@ -977,13 +988,13 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(ctx == NULL) { /* there was no codec attached to this database, so this should do nothing! */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no codec attached to db %s: rekey can't be used on an unencrypted database", zDb); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: no codec attached to db %s: rekey can't be used on an unencrypted database", zDb); return SQLITE_MISUSE; } - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: entering database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: entering database mutex %p", db->mutex); sqlite3_mutex_enter(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: entered database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: entered database mutex %p", db->mutex); codec_set_pass_key(db, db_index, pKey, nKey, CIPHER_WRITE_CTX); @@ -1003,37 +1014,37 @@ int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { if(rc == SQLITE_OK) { sqlite3PagerUnref(page); } else { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); } } else { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: error %d occurred reading page %d", rc, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: error %d occurred reading page %d", rc, pgno); } } } /* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */ if(rc == SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: committing"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: committing"); rc = sqlite3BtreeCommit(pDb->pBt); sqlcipher_codec_key_copy(ctx, CIPHER_WRITE_CTX); } else { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlite3_rekey_v2: rollback"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: rollback"); sqlite3BtreeRollback(pDb->pBt, SQLITE_ABORT_ROLLBACK, 0); } - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: leaving database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: leaving database mutex %p", db->mutex); sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlite3_rekey_v2: left database mutex %p", db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: left database mutex %p", db->mutex); } return SQLITE_OK; } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlite3_rekey_v2: no key provided for db %s: rekey can't be used to decrypt an encrypted database", zDb); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: no key provided for db %s: rekey can't be used to decrypt an encrypted database", zDb); return SQLITE_ERROR; } void sqlcipherCodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) { struct Db *pDb = &db->aDb[nDb]; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipherCodecGetKey:db=%p, nDb=%d", db, nDb); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecGetKey:db=%p, nDb=%d", db, nDb); if( pDb->pBt ) { codec_ctx *ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); diff --git a/src/crypto.h b/src/crypto.h index d33c0f6cc..363fc5804 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -331,6 +331,7 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); int sqlcipher_set_log(const char *destination); void sqlcipher_set_log_level(unsigned int level); +void sqlcipher_set_log_subsystem(unsigned int subsys); #define SQLCIPHER_LOG_NONE 0x00 #define SQLCIPHER_LOG_ERROR 0x01 @@ -340,10 +341,15 @@ void sqlcipher_set_log_level(unsigned int level); #define SQLCIPHER_LOG_TRACE 0x10 #define SQLCIPHER_LOG_ALL 0xffffffff +#define SQLCIPHER_LOG_CORE 0x01 +#define SQLCIPHER_LOG_MEMORY 0x02 +#define SQLCIPHER_LOG_MUTEX 0x04 +#define SQLCIPHER_LOG_PROVIDER 0x08 + #ifdef SQLCIPHER_OMIT_LOG -#define sqlcipher_log(tag, message, ...) +#define sqlcipher_log(level, subsys, message, ...) #else -void sqlcipher_log(unsigned int tag, const char *message, ...); +void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, ...); #endif void sqlcipher_vdbe_return_string(Parse*, const char*, const char*, int); diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 0179aff46..4bf055a38 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -81,6 +81,7 @@ static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; static FILE* sqlcipher_log_file = NULL; static volatile int sqlcipher_log_device = 0; static volatile unsigned int sqlcipher_log_level = SQLCIPHER_LOG_NONE; +static volatile unsigned int sqlcipher_log_subsys = SQLCIPHER_LOG_ALL; static volatile int sqlcipher_log_set = 0; sqlite3_mutex* sqlcipher_mutex(int mutex) { @@ -98,7 +99,7 @@ static void *sqlcipher_mem_malloc(int n) { void *ptr = default_mem_methods.xMalloc(n); if(!sqlcipher_mem_executed) sqlcipher_mem_executed = 1; if(sqlcipher_mem_security_on) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_malloc: calling sqlcipher_mlock(%p,%d)", ptr, n); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mem_malloc: calling sqlcipher_mlock(%p,%d)", ptr, n); sqlcipher_mlock(ptr, n); } return ptr; @@ -111,7 +112,7 @@ static void sqlcipher_mem_free(void *p) { if(!sqlcipher_mem_executed) sqlcipher_mem_executed = 1; if(sqlcipher_mem_security_on) { sz = sqlcipher_mem_size(p); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mem_free: calling sqlcipher_memset(%p,0,%d) and sqlcipher_munlock(%p, %d)", p, sz, p, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mem_free: calling sqlcipher_memset(%p,0,%d) and sqlcipher_munlock(%p, %d)", p, sz, p, sz); sqlcipher_memset(p, 0, sz); sqlcipher_munlock(p, sz); } @@ -168,9 +169,9 @@ void sqlcipher_init_memmethods() { } int sqlcipher_register_provider(sqlcipher_provider *p) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: entering SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_register_provider: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: entered SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_register_provider: entered SQLCIPHER_MUTEX_PROVIDER"); if(default_provider != NULL && default_provider != p) { /* only free the current registerd provider if it has been initialized @@ -179,9 +180,9 @@ int sqlcipher_register_provider(sqlcipher_provider *p) { sqlcipher_free(default_provider, sizeof(sqlcipher_provider)); } default_provider = p; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: leaving SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_register_provider: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_register_provider: left SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_register_provider: left SQLCIPHER_MUTEX_PROVIDER"); return SQLITE_OK; } @@ -194,9 +195,9 @@ sqlcipher_provider* sqlcipher_get_provider() { } void sqlcipher_activate() { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: entering static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_activate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: entered static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_activate: entered static master mutex"); /* allocate new mutexes */ if(sqlcipher_activate_count == 0) { @@ -248,41 +249,41 @@ void sqlcipher_activate() { #else #error "NO DEFAULT SQLCIPHER CRYPTO PROVIDER DEFINED" #endif - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_activate: calling sqlcipher_register_provider(%p)", p); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_activate: calling sqlcipher_register_provider(%p)", p); #ifdef SQLCIPHER_EXT sqlcipher_ext_provider_setup(p); #endif sqlcipher_register_provider(p); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_activate: called sqlcipher_register_provider(%p)",p); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_activate: called sqlcipher_register_provider(%p)",p); } sqlcipher_activate_count++; /* increment activation count */ - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: leaving static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_activate: leaving static master mutex"); sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_activate: left static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_activate: left static master mutex"); } void sqlcipher_deactivate() { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entering static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entered static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: entered static master mutex"); sqlcipher_activate_count--; /* if no connections are using sqlcipher, cleanup globals */ if(sqlcipher_activate_count < 1) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entering SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: entered SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: entered SQLCIPHER_MUTEX_PROVIDER"); if(default_provider != NULL) { sqlcipher_free(default_provider, sizeof(sqlcipher_provider)); default_provider = NULL; } - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER"); #ifdef SQLCIPHER_EXT sqlcipher_ext_provider_destroy(); @@ -298,9 +299,9 @@ void sqlcipher_deactivate() { sqlcipher_activate_count = 0; /* reset activation count */ } - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: leaving static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: leaving static master mutex"); sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_deactivate: left static master mutex"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: left static master mutex"); } /* constant time memset using volitile to avoid having the memset @@ -313,7 +314,7 @@ void* sqlcipher_memset(void *v, unsigned char value, sqlite_uint64 len) { if (v == NULL) return v; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_memset: setting %p[0-%llu]=%d)", a, len, value); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_memset: setting %p[0-%llu]=%d)", a, len, value); for(i = 0; i < len; i++) { a[i] = value; } @@ -357,20 +358,20 @@ void sqlcipher_mlock(void *ptr, sqlite_uint64 sz) { if(ptr == NULL || sz == 0) return; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mlock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); rc = mlock(ptr - offset, sz + offset); if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mlock: mlock() returned %d errno=%d", rc, errno); - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mlock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: mlock() returned %d errno=%d", rc, errno); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) int rc; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_mlock: calling VirtualLock(%p,%d)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: calling VirtualLock(%p,%d)", ptr, sz); rc = VirtualLock(ptr, sz); if(rc==0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_mlock: VirtualLock() returned %d LastError=%d", rc, GetLastError()); - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_mlock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: VirtualLock() returned %d LastError=%d", rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -386,10 +387,10 @@ void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { if(ptr == NULL || sz == 0) return; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_munlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); rc = munlock(ptr - offset, sz + offset); if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_munlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) #if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) @@ -397,14 +398,14 @@ void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { if(ptr == NULL || sz == 0) return; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_munlock: calling VirtualUnlock(%p,%d)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: calling VirtualUnlock(%p,%d)", ptr, sz); rc = VirtualUnlock(ptr, sz); /* because memory allocations may be made from the same individual page, it is possible for VirtualUnlock to be called * multiple times for the same page. Subsequent calls will return an error, but this can be safely ignored (i.e. because * the previous call for that page unlocked the memory already). Log an info level event only in that case. */ if(!rc) { - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_munlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); } #endif #endif @@ -420,7 +421,7 @@ void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { * memory segment so it can be paged */ void sqlcipher_free(void *ptr, sqlite_uint64 sz) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_free: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_free: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); sqlcipher_memset(ptr, 0, sz); sqlcipher_munlock(ptr, sz); sqlite3_free(ptr); @@ -433,9 +434,9 @@ void sqlcipher_free(void *ptr, sqlite_uint64 sz) { */ void* sqlcipher_malloc(sqlite_uint64 sz) { void *ptr; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_malloc: calling sqlite3Malloc(%llu)", sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_malloc: calling sqlite3Malloc(%llu)", sz); ptr = sqlite3Malloc(sz); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_malloc: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_malloc: calling sqlcipher_memset(%p,0,%llu)", ptr, sz); sqlcipher_memset(ptr, 0, sz); sqlcipher_mlock(ptr, sz); return ptr; @@ -459,15 +460,15 @@ char* sqlcipher_version() { */ static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) { cipher_ctx *c_ctx; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_init: allocating context"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_cipher_ctx_init: allocating context"); *iCtx = (cipher_ctx *) sqlcipher_malloc(sizeof(cipher_ctx)); c_ctx = *iCtx; if(c_ctx == NULL) return SQLITE_NOMEM; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_init: allocating key"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_cipher_ctx_init: allocating key"); c_ctx->key = (unsigned char *) sqlcipher_malloc(ctx->key_sz); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_init: allocating hmac_key"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_cipher_ctx_init: allocating hmac_key"); c_ctx->hmac_key = (unsigned char *) sqlcipher_malloc(ctx->key_sz); if(c_ctx->key == NULL) return SQLITE_NOMEM; @@ -481,7 +482,7 @@ static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) { */ static void sqlcipher_cipher_ctx_free(codec_ctx* ctx, cipher_ctx **iCtx) { cipher_ctx *c_ctx = *iCtx; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_free: iCtx=%p", iCtx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "cipher_ctx_free: iCtx=%p", iCtx); if(c_ctx->key) sqlcipher_free(c_ctx->key, ctx->key_sz); if(c_ctx->hmac_key) sqlcipher_free(c_ctx->hmac_key, ctx->key_sz); if(c_ctx->pass) sqlcipher_free(c_ctx->pass, c_ctx->pass_sz); @@ -504,7 +505,7 @@ static int sqlcipher_codec_ctx_reserve_setup(codec_ctx *ctx) { ((reserve / ctx->block_sz) + 1) * ctx->block_sz; } - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d", + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d", base_reserve, ctx->block_sz, ctx->hmac_sz, reserve); ctx->reserve_sz = reserve; @@ -528,7 +529,7 @@ static int sqlcipher_cipher_ctx_cmp(cipher_ctx *c1, cipher_ctx *c2) { c1->pass_sz) )); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_cmp: c1=%p c2=%p sqlcipher_memcmp(c1->pass, c2_pass)=%d are_equal=%d", + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_cmp: c1=%p c2=%p sqlcipher_memcmp(c1->pass, c2_pass)=%d are_equal=%d", c1, c2, (c1->pass == NULL || c2->pass == NULL) ? -1 : @@ -555,7 +556,7 @@ static int sqlcipher_cipher_ctx_copy(codec_ctx *ctx, cipher_ctx *target, cipher_ void *key = target->key; void *hmac_key = target->hmac_key; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_copy: target=%p, source=%p", target, source); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_copy: target=%p, source=%p", target, source); if(target->pass) sqlcipher_free(target->pass, target->pass_sz); if(target->keyspec) sqlcipher_free(target->keyspec, ctx->keyspec_sz); memcpy(target, source, sizeof(cipher_ctx)); @@ -645,7 +646,7 @@ int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nKey, int int rc; if((rc = sqlcipher_cipher_ctx_set_pass(c_ctx, zKey, nKey)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_pass: error %d from sqlcipher_cipher_ctx_set_pass", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_set_pass: error %d from sqlcipher_cipher_ctx_set_pass", rc); return rc; } @@ -653,7 +654,7 @@ int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nKey, int if(for_ctx == 2) { if((rc = sqlcipher_cipher_ctx_copy(ctx, for_ctx ? ctx->read_ctx : ctx->write_ctx, c_ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_pass: error %d from sqlcipher_cipher_ctx_copy", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_set_pass: error %d from sqlcipher_cipher_ctx_copy", rc); return rc; } } @@ -743,7 +744,7 @@ int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size) { return SQLITE_OK; } ctx->plaintext_header_sz = -1; - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_plaintext_header_size: attempt to set invalid plantext_header_size %d", size); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_set_plaintext_header_size: attempt to set invalid plantext_header_size %d", size); return SQLITE_ERROR; } @@ -794,7 +795,7 @@ int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx) { } void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_error %d", error); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_set_error %d", error); sqlite3pager_error(ctx->pBt->pBt->pPager, error); ctx->pBt->pBt->db->errCode = error; } @@ -815,11 +816,11 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { } /* read salt from header, if present, otherwise generate a new random salt */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init_kdf_salt: obtaining salt"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init_kdf_salt: obtaining salt"); if(fd == NULL || fd->pMethods == 0 || sqlite3OsRead(fd, ctx->kdf_salt, ctx->kdf_salt_sz, 0) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random"); if(ctx->provider->random(ctx->provider_ctx, ctx->kdf_salt, ctx->kdf_salt_sz) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init_kdf_salt: error retrieving random bytes from provider"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init_kdf_salt: error retrieving random bytes from provider"); return SQLITE_ERROR; } } @@ -833,7 +834,7 @@ int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int si SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT); return SQLITE_OK; } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_set_kdf_salt: attempt to set salt of incorrect size %d", size); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_set_kdf_salt: attempt to set salt of incorrect size %d", size); return SQLITE_ERROR; } @@ -841,7 +842,7 @@ int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) { int rc = SQLITE_OK; if(!SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT)) { if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_get_kdf_salt: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_get_kdf_salt: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); } } *salt = ctx->kdf_salt; @@ -856,7 +857,7 @@ void sqlcipher_codec_get_keyspec(codec_ctx *ctx, void **zKey, int *nKey) { int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { if(!((size != 0) && ((size & (size - 1)) == 0)) || size < 512 || size > 65536) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "cipher_page_size not a power of 2 and between 512 and 65536 inclusive"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "cipher_page_size not a power of 2 and between 512 and 65536 inclusive"); return SQLITE_ERROR; } /* attempt to free the existing page buffer */ @@ -887,7 +888,7 @@ int sqlcipher_get_default_pagesize() { void sqlcipher_set_mem_security(int on) { /* memory security can only be enabled, not disabled */ if(on) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_set_mem_security: on"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_set_mem_security: on"); sqlcipher_mem_security_on = on; } } @@ -895,7 +896,7 @@ void sqlcipher_set_mem_security(int on) { int sqlcipher_get_mem_security() { /* only report that memory security is enabled if pragma cipher_memory_security is ON and SQLCipher's allocator/deallocator was run at least one timecurrently used */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_get_mem_security: sqlcipher_mem_security_on = %d, sqlcipher_mem_executed = %d", sqlcipher_mem_security_on, sqlcipher_mem_executed); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_get_mem_security: sqlcipher_mem_security_on = %d, sqlcipher_mem_executed = %d", sqlcipher_mem_security_on, sqlcipher_mem_executed); return sqlcipher_mem_security_on && sqlcipher_mem_executed; } @@ -904,7 +905,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi int rc; codec_ctx *ctx; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating context"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_codec_ctx_init: allocating context"); *iCtx = sqlcipher_malloc(sizeof(codec_ctx)); ctx = *iCtx; @@ -917,7 +918,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi directly off the database file. This is the salt for the key derivation function. If we get a short read allocate a new random salt value */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating kdf_salt"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_codec_ctx_init: allocating kdf_salt"); ctx->kdf_salt_sz = FILE_HEADER_SZ; ctx->kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz); if(ctx->kdf_salt == NULL) return SQLITE_NOMEM; @@ -925,7 +926,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi /* allocate space for separate hmac salt data. We want the HMAC derivation salt to be different than the encryption key derivation salt */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating hmac_kdf_salt"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_codec_ctx_init: allocating hmac_kdf_salt"); ctx->hmac_kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz); if(ctx->hmac_kdf_salt == NULL) return SQLITE_NOMEM; @@ -933,23 +934,23 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi ctx->flags = default_flags; /* setup the crypto provider */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_init: allocating provider"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_codec_ctx_init: allocating provider"); ctx->provider = (sqlcipher_provider *) sqlcipher_malloc(sizeof(sqlcipher_provider)); if(ctx->provider == NULL) return SQLITE_NOMEM; /* make a copy of the provider to be used for the duration of the context */ - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: entering SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_codec_ctx_init: entering SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: entered SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_codec_ctx_init: entered SQLCIPHER_MUTEX_PROVIDER"); memcpy(ctx->provider, default_provider, sizeof(sqlcipher_provider)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: leaving SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_codec_ctx_init: leaving SQLCIPHER_MUTEX_PROVIDER"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_codec_ctx_init: left SQLCIPHER_MUTEX_PROVIDER"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_codec_ctx_init: left SQLCIPHER_MUTEX_PROVIDER"); if((rc = ctx->provider->ctx_init(&ctx->provider_ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d returned from ctx_init", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d returned from ctx_init", rc); return rc; } @@ -968,64 +969,64 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi cases where bytes 16 & 17 of the page header are a power of 2 as reported by John Lehman */ if((rc = sqlcipher_codec_ctx_set_pagesize(ctx, default_page_size)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d returned from sqlcipher_codec_ctx_set_pagesize with %d", rc, default_page_size); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d returned from sqlcipher_codec_ctx_set_pagesize with %d", rc, default_page_size); return rc; } /* establish settings for the KDF iterations and fast (HMAC) KDF iterations */ if((rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, default_kdf_iter)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting default_kdf_iter %d", rc, default_kdf_iter); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting default_kdf_iter %d", rc, default_kdf_iter); return rc; } if((rc = sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, FAST_PBKDF2_ITER)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting fast_kdf_iter to %d", rc, FAST_PBKDF2_ITER); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting fast_kdf_iter to %d", rc, FAST_PBKDF2_ITER); return rc; } /* set the default HMAC and KDF algorithms which will determine the reserve size */ if((rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, default_hmac_algorithm)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_hmac_algorithm with %d", rc, default_hmac_algorithm); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_hmac_algorithm with %d", rc, default_hmac_algorithm); return rc; } /* Note that use_hmac is a special case that requires recalculation of page size so we call set_use_hmac to perform setup */ if((rc = sqlcipher_codec_ctx_set_use_hmac(ctx, SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC))) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting use_hmac %d", rc, SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC)); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting use_hmac %d", rc, SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC)); return rc; } if((rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, default_kdf_algorithm)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_kdf_algorithm with %d", rc, default_kdf_algorithm); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_kdf_algorithm with %d", rc, default_kdf_algorithm); return rc; } /* setup the default plaintext header size */ if((rc = sqlcipher_codec_ctx_set_plaintext_header_size(ctx, default_plaintext_header_sz)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_plaintext_header_size with %d", rc, default_plaintext_header_sz); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_plaintext_header_size with %d", rc, default_plaintext_header_sz); return rc; } /* initialize the read and write sub-contexts. this must happen after key_sz is established */ if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->read_ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d initializing read_ctx", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d initializing read_ctx", rc); return rc; } if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->write_ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d initializing write_ctx", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d initializing write_ctx", rc); return rc; } /* set the key material on one of the sub cipher contexts and sync them up */ if((rc = sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, 0)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d setting pass key", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting pass key", rc); return rc; } if((rc = sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_init: error %d copying write_ctx to read_ctx", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d copying write_ctx to read_ctx", rc); return rc; } @@ -1038,7 +1039,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi */ void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { codec_ctx *ctx = *iCtx; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "codec_ctx_free: iCtx=%p", iCtx); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "codec_ctx_free: iCtx=%p", iCtx); if(ctx->kdf_salt) sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz); if(ctx->hmac_kdf_salt) sqlcipher_free(ctx->hmac_kdf_salt, ctx->kdf_salt_sz); if(ctx->buffer) sqlcipher_free(ctx->buffer, ctx->page_sz); @@ -1113,12 +1114,12 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int hmac_out = out + size + ctx->iv_sz; out_start = out; /* note the original position of the output buffer pointer, as out will be rewritten during encryption */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_page_cipher: pgno=%d, mode=%d, size=%d", pgno, mode, size); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: pgno=%d, mode=%d, size=%d", pgno, mode, size); CODEC_HEXDUMP("sqlcipher_page_cipher: input page data", in, page_sz); /* the key size should never be zero. If it is, error out. */ if(ctx->key_sz == 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: error possible context corruption, key_sz is zero for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: error possible context corruption, key_sz is zero for pgno=%d", pgno); goto error; } @@ -1131,38 +1132,38 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, in, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac operation on decrypt failed for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: hmac operation on decrypt failed for pgno=%d", pgno); goto error; } - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_page_cipher: comparing hmac on in=%p out=%p hmac_sz=%d", hmac_in, hmac_out, ctx->hmac_sz); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: comparing hmac on in=%p out=%p hmac_sz=%d", hmac_in, hmac_out, ctx->hmac_sz); if(sqlcipher_memcmp(hmac_in, hmac_out, ctx->hmac_sz) != 0) { /* the hmac check failed */ if(sqlite3BtreeGetAutoVacuum(ctx->pBt) != BTREE_AUTOVACUUM_NONE && sqlcipher_ismemset(in, 0, page_sz) == 0) { /* first check if the entire contents of the page is zeros. If so, this page resulted from a short read (i.e. sqlite attempted to pull a page after the end of the file. these short read failures must be ignored for autovaccum mode to work so wipe the output buffer and return SQLITE_OK to skip the decryption step. */ - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_page_cipher: zeroed page (short read) for pgno %d with autovacuum enabled", pgno); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: zeroed page (short read) for pgno %d with autovacuum enabled", pgno); sqlcipher_memset(out, 0, page_sz); return SQLITE_OK; } else { /* if the page memory is not all zeros, it means the there was data and a hmac on the page. since the check failed, the page was either tampered with or corrupted. wipe the output buffer, and return SQLITE_ERROR to the caller */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac check failed for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: hmac check failed for pgno=%d", pgno); goto error; } } } if(ctx->provider->cipher(ctx->provider_ctx, mode, c_ctx->key, ctx->key_sz, iv_out, in, size, out) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: cipher operation mode=%d failed for pgno=%d", mode, pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: cipher operation mode=%d failed for pgno=%d", mode, pgno); goto error; }; if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) { if(sqlcipher_page_hmac(ctx, c_ctx, pgno, out_start, size + ctx->iv_sz, hmac_out) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_page_cipher: hmac operation on encrypt failed for pgno=%d", pgno); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_page_cipher: hmac operation on encrypt failed for pgno=%d", pgno); goto error; }; } @@ -1192,7 +1193,7 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int */ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { int rc; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_key_derive: ctx->kdf_salt_sz=%d ctx->kdf_iter=%d ctx->fast_kdf_iter=%d ctx->key_sz=%d", + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: ctx->kdf_salt_sz=%d ctx->kdf_iter=%d ctx->fast_kdf_iter=%d ctx->key_sz=%d", ctx->kdf_salt_sz, ctx->kdf_iter, ctx->fast_kdf_iter, ctx->key_sz); if(c_ctx->pass && c_ctx->pass_sz) { /* if key material is present on the context for derivation */ @@ -1200,7 +1201,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { /* if necessary, initialize the salt from the header or random source */ if(!SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT)) { if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: error %d from sqlcipher_codec_ctx_init_kdf_salt", rc); return rc; } } @@ -1208,26 +1209,26 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if (c_ctx->pass_sz == ((ctx->key_sz * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, ctx->key_sz * 2)) { int n = c_ctx->pass_sz - 3; /* adjust for leading x' and tailing ' */ const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_key_derive: using raw key from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, n, c_ctx->key); } else if (c_ctx->pass_sz == (((ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, (ctx->key_sz + ctx->kdf_salt_sz) * 2)) { const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_key_derive: using raw key from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: using raw key from hex"); cipher_hex2bin(z, (ctx->key_sz * 2), c_ctx->key); cipher_hex2bin(z + (ctx->key_sz * 2), (ctx->kdf_salt_sz * 2), ctx->kdf_salt); } else { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations", ctx->kdf_iter); if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter, ctx->key_sz, c_ctx->key) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error occurred from provider kdf generating encryption key"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: error occurred from provider kdf generating encryption key"); return SQLITE_ERROR; } } /* set the context "keyspec" containing the hex-formatted key and salt to be used when attaching databases */ if((rc = sqlcipher_cipher_ctx_set_keyspec(ctx, c_ctx, c_ctx->key)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error %d from sqlcipher_cipher_ctx_set_keyspec", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: error %d from sqlcipher_cipher_ctx_set_keyspec", rc); return rc; } @@ -1247,14 +1248,14 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { ctx->hmac_kdf_salt[i] ^= hmac_salt_mask; } - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations", + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations", ctx->fast_kdf_iter); if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->key, ctx->key_sz, ctx->hmac_kdf_salt, ctx->kdf_salt_sz, ctx->fast_kdf_iter, ctx->key_sz, c_ctx->hmac_key) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: error occurred from provider kdf generating HMAC key"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: error occurred from provider kdf generating HMAC key"); return SQLITE_ERROR; } } @@ -1262,7 +1263,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { c_ctx->derive_key = 0; return SQLITE_OK; } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_cipher_ctx_key_derive: key material is not present on the context for key derivation"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_cipher_ctx_key_derive: key material is not present on the context for key derivation"); return SQLITE_ERROR; } @@ -1270,7 +1271,7 @@ int sqlcipher_codec_key_derive(codec_ctx *ctx) { /* derive key on first use if necessary */ if(ctx->read_ctx->derive_key) { if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->read_ctx) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_key_derive: error occurred deriving read_ctx key"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_key_derive: error occurred deriving read_ctx key"); return SQLITE_ERROR; } } @@ -1279,12 +1280,12 @@ int sqlcipher_codec_key_derive(codec_ctx *ctx) { if(sqlcipher_cipher_ctx_cmp(ctx->write_ctx, ctx->read_ctx) == 0) { /* the relevant parameters are the same, just copy read key */ if(sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_key_derive: error occurred copying read_ctx to write_ctx"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_key_derive: error occurred copying read_ctx to write_ctx"); return SQLITE_ERROR; } } else { if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->write_ctx) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_key_derive: error occurred deriving write_ctx key"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_key_derive: error occurred deriving write_ctx key"); return SQLITE_ERROR; } } @@ -1461,7 +1462,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* Version 4 - current, no upgrade required, so exit immediately */ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version, &journal_mode); if(rc == SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_codec_ctx_migrate: no upgrade required - exiting"); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: no upgrade required - exiting"); goto cleanup; } @@ -1469,7 +1470,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { pragma_compat = sqlite3_mprintf("PRAGMA cipher_compatibility = %d;", i); rc = sqlcipher_check_connection(db_filename, pass, pass_sz, pragma_compat, &user_version, &journal_mode); if(rc == SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_migrate: version %d format found", i); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: version %d format found", i); goto migrate; } if(pragma_compat) sqlcipher_free(pragma_compat, sqlite3Strlen30(pragma_compat)); @@ -1477,7 +1478,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { } /* if we exit the loop normally we failed to determine the version, this is an error */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: unable to determine format version for upgrade: this may indicate custom settings were used "); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: unable to determine format version for upgrade: this may indicate custom settings were used "); goto handle_error; migrate: @@ -1494,55 +1495,55 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { rc = sqlite3_exec(db, pragma_compat, NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: set compatibility mode failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: set compatibility mode failed, error code %d", rc); goto handle_error; } /* force journal mode to DELETE, we will set it back later if different */ rc = sqlite3_exec(db, "PRAGMA journal_mode = delete;", NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: force journal mode DELETE failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: force journal mode DELETE failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, attach_command, NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: attach failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: attach failed, error code %d", rc); goto handle_error; } rc = sqlite3_key_v2(db, "migrate", pass, pass_sz); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: keying attached database failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: keying attached database failed, error code %d", rc); goto handle_error; } rc = sqlite3_exec(db, "SELECT sqlcipher_export('migrate');", NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: sqlcipher_export failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: sqlcipher_export failed, error code %d", rc); goto handle_error; } #ifdef SQLCIPHER_TEST if((sqlcipher_get_test_flags() & TEST_FAIL_MIGRATE) > 0) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_WARN, "sqlcipher_codec_ctx_migrate: simulated migrate failure, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: simulated migrate failure, error code %d", rc); goto handle_error; } #endif rc = sqlite3_exec(db, set_user_version, NULL, NULL, NULL); if(rc != SQLITE_OK){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: set user version failed, error code %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: set user version failed, error code %d", rc); goto handle_error; } if( !db->autoCommit ){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: cannot migrate from within a transaction"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: cannot migrate from within a transaction"); goto handle_error; } if( db->nVdbeActive>1 ){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: cannot migrate - SQL statements in progress"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: cannot migrate - SQL statements in progress"); goto handle_error; } @@ -1554,7 +1555,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* unset the BTS_PAGESIZE_FIXED flag to avoid SQLITE_READONLY */ pDest->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; rc = sqlite3BtreeSetPageSize(pDest, default_page_size, nRes, 0); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "set btree page size to %d res %d rc %d", default_page_size, nRes, rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: set btree page size to %d res %d rc %d", default_page_size, nRes, rc); if( rc!=SQLITE_OK ) goto handle_error; sqlcipherCodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz); @@ -1568,7 +1569,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { sqlite3OsClose(destfile); #if defined(_WIN32) || defined(SQLITE_OS_WINRT) - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "performing windows MoveFileExA"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: performing windows MoveFileExA"); w_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) db_filename, -1, NULL, 0); w_db_filename = sqlcipher_malloc(w_db_filename_sz * sizeof(wchar_t)); @@ -1580,50 +1581,50 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { if(!MoveFileExW(w_migrated_db_filename, w_db_filename, MOVEFILE_REPLACE_EXISTING)) { rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %d", rc); goto handle_error; } #else - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "performing POSIX rename"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: performing POSIX rename"); if ((rc = rename(migrated_db_filename, db_filename)) != 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %d", rc); goto handle_error; } #endif - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "renamed migration database %s to main database %s: %d", migrated_db_filename, db_filename, rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: renamed migration database %s to main database %s: %d", migrated_db_filename, db_filename, rc); rc = sqlite3OsOpen(db->pVfs, migrated_db_filename, srcfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reopened migration database: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reopened migration database: %d", rc); if( rc!=SQLITE_OK ) goto handle_error; rc = sqlite3OsOpen(db->pVfs, db_filename, destfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reopened main database: %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reopened main database: %d", rc); if( rc!=SQLITE_OK ) goto handle_error; sqlite3pager_reset(pDest->pBt->pPager); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reset pager"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reset pager"); rc = sqlite3_exec(db, "DETACH DATABASE migrate;", NULL, NULL, NULL); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "DETACH DATABASE called %d", rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: DETACH DATABASE called %d", rc); if(rc != SQLITE_OK) goto cleanup; sqlite3ResetAllSchemasOfConnection(db); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "reset all schemas"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reset all schemas"); set_journal_mode = sqlite3_mprintf("PRAGMA journal_mode = %s;", journal_mode); rc = sqlite3_exec(db, set_journal_mode, NULL, NULL, NULL); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "%s: %d", set_journal_mode, rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: executed %s: %d", set_journal_mode, rc); if( rc!=SQLITE_OK ) goto handle_error; goto cleanup; handle_error: - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_ctx_migrate: an error occurred attempting to migrate the database - last error %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: an error occurred attempting to migrate the database - last error %d", rc); cleanup: if(migrated_db_filename) { int del_rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_ctx_migrate: deleted migration database: %d", del_rc); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: deleted migration database: %d", del_rc); } if(pass) sqlcipher_free(pass, pass_sz); @@ -1651,7 +1652,7 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz int buffer_sz = n / 2; unsigned char *random; const unsigned char *z = (const unsigned char *)zRight + 2; /* adjust lead offset of x' */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, "sqlcipher_codec_add_random: using raw random blob from hex"); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_add_random: using raw random blob from hex"); random = sqlcipher_malloc(buffer_sz); memset(random, 0, buffer_sz); cipher_hex2bin(z, n, random); @@ -1659,7 +1660,7 @@ int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz sqlcipher_free(random, buffer_sz); return rc; } - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_codec_add_random: attemt to add random with invalid format"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_add_random: attemt to add random with invalid format"); return SQLITE_ERROR; } @@ -1725,7 +1726,7 @@ const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) { /* constants from https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-crt/misc/gettimeofday.c */ #define FILETIME_1970 116444736000000000ull /* seconds between 1/1/1601 and 1/1/1970 */ #define HECTONANOSEC_PER_SEC 10000000ull -void sqlcipher_log(unsigned int level, const char *message, ...) { +void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, ...) { va_list params; va_start(params, message); char *formatted = NULL; @@ -1751,9 +1752,12 @@ void sqlcipher_log(unsigned int level, const char *message, ...) { #endif #endif #endif - - if(level > sqlcipher_log_level || (sqlcipher_log_device == 0 && sqlcipher_log_file == NULL)) { - /* no log target or tag not in included filters */ + if( + level > sqlcipher_log_level /* log level is higher, e.g. level filter is at ERROR but this message is DEBUG */ + || (sqlcipher_log_subsys & subsys) == 0 /* subsystem filter doesn't match this message subsys */ + || (sqlcipher_log_device == 0 && sqlcipher_log_file == NULL) /* no configured log target */ + ) { + /* skip logging this message */ goto end; } @@ -1808,6 +1812,10 @@ void sqlcipher_set_log_level(unsigned int level) { sqlcipher_log_level = level; } +void sqlcipher_set_log_subsystem(unsigned int subsys) { + sqlcipher_log_subsys = subsys; +} + int sqlcipher_set_log(const char *destination){ #ifdef SQLCIPHER_OMIT_LOG return SQLITE_ERROR; @@ -1834,7 +1842,7 @@ int sqlcipher_set_log(const char *destination){ if((sqlcipher_log_file = fopen(destination, "a")) == 0) return SQLITE_ERROR; #endif } - sqlcipher_log(SQLCIPHER_LOG_INFO, "sqlcipher_set_log: set log to %s", destination); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_CORE, "sqlcipher_set_log: set log to %s", destination); return SQLITE_OK; #endif } diff --git a/src/crypto_libtomcrypt.c b/src/crypto_libtomcrypt.c index 03a068979..ddf086961 100644 --- a/src/crypto_libtomcrypt.c +++ b/src/crypto_libtomcrypt.c @@ -48,9 +48,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { int block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ; const unsigned char * data = (const unsigned char *)buffer; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); while(data_to_read > 0){ rc = fortuna_add_entropy(data, block_sz, &prng); @@ -64,9 +64,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { } fortuna_ready(&prng); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); return rc; } @@ -74,9 +74,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) { static int sqlcipher_ltc_activate(void *ctx) { unsigned char random_buffer[FORTUNA_MAX_SZ]; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ); if(ltc_init == 0) { @@ -102,17 +102,17 @@ static int sqlcipher_ltc_activate(void *ctx) { } sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } static int sqlcipher_ltc_deactivate(void *ctx) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); ltc_ref_count--; if(ltc_ref_count == 0){ @@ -120,9 +120,9 @@ static int sqlcipher_ltc_deactivate(void *ctx) { sqlcipher_memset((void *)&prng, 0, sizeof(prng)); } - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -136,15 +136,15 @@ static const char* sqlcipher_ltc_get_provider_version(void *ctx) { } static int sqlcipher_ltc_random(void *ctx, void *buffer, int length) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); fortuna_read(buffer, length, &prng); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); return SQLITE_OK; } diff --git a/src/crypto_nss.c b/src/crypto_nss.c index 5de302ca7..3c8bcd3f6 100644 --- a/src/crypto_nss.c +++ b/src/crypto_nss.c @@ -44,25 +44,25 @@ int sqlcipher_nss_setup(sqlcipher_provider *p); static int sqlcipher_nss_activate(void *ctx) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); if (nss_init_context == NULL) { nss_init_context = NSS_InitContext("", "", "", "", NULL, NSS_INIT_READONLY | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT); } nss_init_count++; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } static int sqlcipher_nss_deactivate(void *ctx) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); nss_init_count--; if (nss_init_count == 0 && nss_init_context != NULL) { @@ -70,9 +70,9 @@ static int sqlcipher_nss_deactivate(void *ctx) { nss_init_context = NULL; } - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index ce0c11690..fc57221f4 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -46,7 +46,7 @@ static unsigned int openssl_init_count = 0; static void sqlcipher_openssl_log_errors() { unsigned long err = 0; while((err = ERR_get_error()) != 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_log_errors: ERR_get_error() returned %lx: %s", err, ERR_error_string(err, NULL)); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_log_errors: ERR_get_error() returned %lx: %s", err, ERR_error_string(err, NULL)); } } @@ -75,15 +75,15 @@ static void HMAC_CTX_free(HMAC_CTX *ctx) static int sqlcipher_openssl_add_random(void *ctx, void *buffer, int length) { #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif RAND_add(buffer, length, 0); #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif return SQLITE_OK; } @@ -102,9 +102,9 @@ static int sqlcipher_openssl_activate(void *ctx) { e.g. on startup */ int rc = 0; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); #if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) ERR_load_crypto_strings(); @@ -113,16 +113,16 @@ static int sqlcipher_openssl_activate(void *ctx) { #ifdef SQLCIPHER_FIPS if(!FIPS_mode()){ if(!(rc = FIPS_mode_set(1))){ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_activate: FIPS_mode_set() returned %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_activate: FIPS_mode_set() returned %d", rc); sqlcipher_openssl_log_errors(); } } #endif openssl_init_count++; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -130,15 +130,15 @@ static int sqlcipher_openssl_activate(void *ctx) { freeing the EVP structures on the final deactivation to ensure that OpenSSL memory is cleaned up */ static int sqlcipher_openssl_deactivate(void *ctx) { - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); openssl_init_count--; - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE"); return SQLITE_OK; } @@ -164,18 +164,18 @@ static int sqlcipher_openssl_random (void *ctx, void *buffer, int length) { but a more proper solution is that applications setup platform-appropriate thread saftey in openssl externally */ #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif rc = RAND_bytes((unsigned char *)buffer, length); #ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND"); sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND)); - sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND"); #endif if(!rc) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_random: RAND_bytes() returned %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_random: RAND_bytes() returned %d", rc); sqlcipher_openssl_log_errors(); return SQLITE_ERROR; } @@ -193,7 +193,7 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ hctx = HMAC_CTX_new(); if(hctx == NULL) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_CTX_new() failed"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: HMAC_CTX_new() failed"); sqlcipher_openssl_log_errors(); goto error; } @@ -201,46 +201,46 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ switch(algorithm) { case SQLCIPHER_HMAC_SHA1: if(!(rc = HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha1(), NULL))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha1() returned %d", key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha1() returned %d", key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; case SQLCIPHER_HMAC_SHA256: if(!(rc = HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha256(), NULL))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha256() returned %d", key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha256() returned %d", key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; case SQLCIPHER_HMAC_SHA512: if(!(rc = HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha512(), NULL))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha512() returned %d", key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: HMAC_Init_ex() with key size %d and EVP_sha512() returned %d", key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; default: - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: invalid algorithm %d", algorithm); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: invalid algorithm %d", algorithm); goto error; } if(!(rc = HMAC_Update(hctx, in, in_sz))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Update() on 1st input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: HMAC_Update() on 1st input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); sqlcipher_openssl_log_errors(); goto error; } if(in2 != NULL) { if(!(rc = HMAC_Update(hctx, in2, in2_sz))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Update() on 2nd input buffer of %d bytes using algorithm %d returned %d", in2_sz, algorithm, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: HMAC_Update() on 2nd input buffer of %d bytes using algorithm %d returned %d", in2_sz, algorithm, rc); sqlcipher_openssl_log_errors(); goto error; } } if(!(rc = HMAC_Final(hctx, out, &outlen))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: HMAC_Final() using algorithm %d returned %d", algorithm, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: HMAC_Final() using algorithm %d returned %d", algorithm, rc); sqlcipher_openssl_log_errors(); goto error; } @@ -266,14 +266,14 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ mac = EVP_MAC_fetch(NULL, "HMAC", NULL); if(mac == NULL) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_fetch for HMAC failed"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: EVP_MAC_fetch for HMAC failed"); sqlcipher_openssl_log_errors(); goto error; } hctx = EVP_MAC_CTX_new(mac); if(hctx == NULL) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_CTX_new() failed"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: EVP_MAC_CTX_new() failed"); sqlcipher_openssl_log_errors(); goto error; } @@ -281,52 +281,52 @@ static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_ switch(algorithm) { case SQLCIPHER_HMAC_SHA1: if(!(rc = EVP_MAC_init(hctx, hmac_key, key_sz, sha1))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha1 returned %d", key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha1 returned %d", key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; case SQLCIPHER_HMAC_SHA256: if(!(rc = EVP_MAC_init(hctx, hmac_key, key_sz, sha256))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha256 returned %d", key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha256 returned %d", key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; case SQLCIPHER_HMAC_SHA512: if(!(rc = EVP_MAC_init(hctx, hmac_key, key_sz, sha512))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha512 returned %d", key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: EVP_MAC_init() with key size %d and sha512 returned %d", key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; default: - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: invalid algorithm %d", algorithm); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: invalid algorithm %d", algorithm); goto error; } if(!(rc = EVP_MAC_update(hctx, in, in_sz))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_update() on 1st input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: EVP_MAC_update() on 1st input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); sqlcipher_openssl_log_errors(); goto error; } if(in2 != NULL) { if(!(rc = EVP_MAC_update(hctx, in2, in2_sz))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: EVP_MAC_update() on 2nd input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: EVP_MAC_update() on 2nd input buffer of %d bytes using algorithm %d returned %d", in_sz, algorithm, rc); sqlcipher_openssl_log_errors(); goto error; } } if(!(rc = EVP_MAC_final(hctx, NULL, &outlen, 0))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: 1st EVP_MAC_final() for output length calculation using algorithm %d returned %d", algorithm, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: 1st EVP_MAC_final() for output length calculation using algorithm %d returned %d", algorithm, rc); sqlcipher_openssl_log_errors(); goto error; } if(!(rc = EVP_MAC_final(hctx, out, &outlen, outlen))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_hmac: 2nd EVP_MAC_final() using algorithm %d returned %d", algorithm, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_hmac: 2nd EVP_MAC_final() using algorithm %d returned %d", algorithm, rc); sqlcipher_openssl_log_errors(); goto error; } @@ -352,21 +352,21 @@ static int sqlcipher_openssl_kdf(void *ctx, int algorithm, const unsigned char * switch(algorithm) { case SQLCIPHER_HMAC_SHA1: if(!(rc = PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha1(), key_sz, key))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha1() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha1() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; case SQLCIPHER_HMAC_SHA256: if(!(rc = PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha256(), key_sz, key))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha256() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha256() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } break; case SQLCIPHER_HMAC_SHA512: if(!(rc = PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha512(), key_sz, key))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha512() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_kdf: PKCS5_PBKDF2_HMAC() for EVP_sha512() workfactor %d and key size %d returned %d", workfactor, key_sz, rc); sqlcipher_openssl_log_errors(); goto error; } @@ -387,31 +387,31 @@ static int sqlcipher_openssl_cipher(void *ctx, int mode, unsigned char *key, int int tmp_csz, csz, rc = 0; EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new(); if(ectx == NULL) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CIPHER_CTX_new failed"); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_cipher: EVP_CIPHER_CTX_new failed"); sqlcipher_openssl_log_errors(); goto error; } if(!(rc = EVP_CipherInit_ex(ectx, OPENSSL_CIPHER, NULL, NULL, NULL, mode))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherInit_ex for mode %d returned %d", mode, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_cipher: EVP_CipherInit_ex for mode %d returned %d", mode, rc); sqlcipher_openssl_log_errors(); goto error; } if(!(rc = EVP_CIPHER_CTX_set_padding(ectx, 0))) { /* no padding */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CIPHER_CTX_set_padding 0 returned %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_cipher: EVP_CIPHER_CTX_set_padding 0 returned %d", rc); sqlcipher_openssl_log_errors(); goto error; } if(!(rc = EVP_CipherInit_ex(ectx, NULL, NULL, key, iv, mode))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherInit_ex for mode %d returned %d", mode, rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_cipher: EVP_CipherInit_ex for mode %d returned %d", mode, rc); sqlcipher_openssl_log_errors(); goto error; } if(!(rc = EVP_CipherUpdate(ectx, out, &tmp_csz, in, in_sz))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherUpdate returned %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_cipher: EVP_CipherUpdate returned %d", rc); sqlcipher_openssl_log_errors(); goto error; } @@ -419,7 +419,7 @@ static int sqlcipher_openssl_cipher(void *ctx, int mode, unsigned char *key, int csz = tmp_csz; out += tmp_csz; if(!(rc = EVP_CipherFinal_ex(ectx, out, &tmp_csz))) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, "sqlcipher_openssl_cipher: EVP_CipherFinal_ex returned %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_PROVIDER, "sqlcipher_openssl_cipher: EVP_CipherFinal_ex returned %d", rc); sqlcipher_openssl_log_errors(); goto error; } From acbf140b4f41eb470f1857a97aa3c9f5310fc2ef Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 8 Jul 2024 13:29:54 -0400 Subject: [PATCH 151/340] Improves error logging in cipher_migrate Several potential error conditions would only be logged at DEBUG level during database version migration. These have been adjusted to log at ERROR or WARN level instead. --- src/crypto_impl.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 4bf055a38..16e3a3fa8 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1555,8 +1555,10 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { /* unset the BTS_PAGESIZE_FIXED flag to avoid SQLITE_READONLY */ pDest->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; rc = sqlite3BtreeSetPageSize(pDest, default_page_size, nRes, 0); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: set btree page size to %d res %d rc %d", default_page_size, nRes, rc); - if( rc!=SQLITE_OK ) goto handle_error; + if(rc != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: failed to set btree page size to %d res %d rc %d", default_page_size, nRes, rc); + goto handle_error; + } sqlcipherCodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz); SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_KEY_USED); @@ -1587,34 +1589,41 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { #else sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: performing POSIX rename"); if ((rc = rename(migrated_db_filename, db_filename)) != 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %d", rc); + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: error occurred while renaming migration files %s to %s: %d", migrated_db_filename, db_filename, rc); goto handle_error; } #endif sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: renamed migration database %s to main database %s: %d", migrated_db_filename, db_filename, rc); rc = sqlite3OsOpen(db->pVfs, migrated_db_filename, srcfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reopened migration database: %d", rc); - if( rc!=SQLITE_OK ) goto handle_error; + if(rc != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: failed to reopen migration database %s: %d", migrated_db_filename, rc); + goto handle_error; + } rc = sqlite3OsOpen(db->pVfs, db_filename, destfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reopened main database: %d", rc); - if( rc!=SQLITE_OK ) goto handle_error; + if(rc != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: failed to reopen main database %s: %d", db_filename, rc); + goto handle_error; + } sqlite3pager_reset(pDest->pBt->pPager); sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reset pager"); rc = sqlite3_exec(db, "DETACH DATABASE migrate;", NULL, NULL, NULL); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: DETACH DATABASE called %d", rc); - if(rc != SQLITE_OK) goto cleanup; + if(rc != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: DETACH DATABASE migrate failed: %d", rc); + } sqlite3ResetAllSchemasOfConnection(db); sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: reset all schemas"); set_journal_mode = sqlite3_mprintf("PRAGMA journal_mode = %s;", journal_mode); rc = sqlite3_exec(db, set_journal_mode, NULL, NULL, NULL); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: executed %s: %d", set_journal_mode, rc); - if( rc!=SQLITE_OK ) goto handle_error; + if(rc != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: failed to re-set journal mode via %s: %d", set_journal_mode, rc); + goto handle_error; + } goto cleanup; @@ -1624,7 +1633,9 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { cleanup: if(migrated_db_filename) { int del_rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0); - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: deleted migration database: %d", del_rc); + if(del_rc != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: failed to delete migration database %s: %d", migrated_db_filename, del_rc); + } } if(pass) sqlcipher_free(pass, pass_sz); From 58ee07587e25eab8ae7d7161ef96a9b4dd7c4b0d Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 8 Jul 2024 14:02:59 -0400 Subject: [PATCH 152/340] Extends PRAGMAs cipher_log_level and cipher_log_subsystem to print current setting Calling PRAGMA cipher_log_level or cipher_log_subsystem without an assignment will now print a friendly string of the current setting. When providing an assignment the PRAGMAs will echo back the setting applied. --- src/crypto.c | 67 ++++++++++++++++++++++++++++++++++++++++++----- src/crypto.h | 2 ++ src/crypto_impl.c | 8 ++++++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index fa9a9785c..f0c1036c2 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -688,18 +688,50 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check"); } } else - if( sqlite3_stricmp(zLeft, "cipher_log_level")==0 && zRight){ - unsigned int level = SQLCIPHER_LOG_NONE; + if( sqlite3_stricmp(zLeft, "cipher_log_level")==0 ){ + unsigned int level = SQLCIPHER_LOG_NONE; + char *out = NULL; + + if(zRight) { if(sqlite3_stricmp(zRight, "ERROR")==0) level = SQLCIPHER_LOG_ERROR; else if(sqlite3_stricmp(zRight, "WARN" )==0) level = SQLCIPHER_LOG_WARN; else if(sqlite3_stricmp(zRight, "INFO" )==0) level = SQLCIPHER_LOG_INFO; else if(sqlite3_stricmp(zRight, "DEBUG")==0) level = SQLCIPHER_LOG_DEBUG; else if(sqlite3_stricmp(zRight, "TRACE")==0) level = SQLCIPHER_LOG_TRACE; sqlcipher_set_log_level(level); - sqlcipher_vdbe_return_string(pParse, "cipher_log_level", sqlite3_mprintf("%u", level), P4_DYNAMIC); + } else { + level = sqlcipher_get_log_level(); + } + switch(level) { + case SQLCIPHER_LOG_NONE: + out = "NONE"; + break; + case SQLCIPHER_LOG_ERROR: + out = "ERROR"; + break; + case SQLCIPHER_LOG_WARN: + out = "WARN"; + break; + case SQLCIPHER_LOG_INFO: + out = "INFO"; + break; + case SQLCIPHER_LOG_DEBUG: + out = "DEBUG"; + break; + case SQLCIPHER_LOG_TRACE: + out = "TRACE"; + break; + case SQLCIPHER_LOG_ALL: + out = "ALL"; + break; + } + sqlcipher_vdbe_return_string(pParse, "cipher_log_level", out, P4_TRANSIENT); } else - if( sqlite3_stricmp(zLeft, "cipher_log_subsystem")==0 && zRight){ - unsigned int subsys = SQLCIPHER_LOG_ALL; + if( sqlite3_stricmp(zLeft, "cipher_log_subsystem")==0 ){ + unsigned int subsys = SQLCIPHER_LOG_NONE; + char *out = NULL; + + if(zRight) { if(sqlite3_stricmp(zRight, "NONE" )==0) subsys = SQLCIPHER_LOG_NONE; else if(sqlite3_stricmp(zRight, "ALL" )==0) subsys = SQLCIPHER_LOG_ALL; else if(sqlite3_stricmp(zRight, "CORE" )==0) subsys = SQLCIPHER_LOG_CORE; @@ -707,7 +739,30 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef else if(sqlite3_stricmp(zRight, "MUTEX" )==0) subsys = SQLCIPHER_LOG_MUTEX; else if(sqlite3_stricmp(zRight, "PROVIDER")==0) subsys = SQLCIPHER_LOG_PROVIDER; sqlcipher_set_log_subsystem(subsys); - sqlcipher_vdbe_return_string(pParse, "cipher_log_subsystem", sqlite3_mprintf("%u", subsys), P4_DYNAMIC); + } else { + subsys = sqlcipher_get_log_subsystem(); + } + switch(subsys) { + case SQLCIPHER_LOG_NONE: + out = "NONE"; + break; + case SQLCIPHER_LOG_ALL: + out = "ALL"; + break; + case SQLCIPHER_LOG_CORE: + out = "CORE"; + break; + case SQLCIPHER_LOG_MEMORY: + out = "MEMORY"; + break; + case SQLCIPHER_LOG_MUTEX: + out = "MUTEX"; + break; + case SQLCIPHER_LOG_PROVIDER: + out = "PROVIDER"; + break; + } + sqlcipher_vdbe_return_string(pParse, "cipher_log_subsystem", out, P4_TRANSIENT); } else if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); diff --git a/src/crypto.h b/src/crypto.h index 363fc5804..0ed90e52a 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -331,7 +331,9 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); int sqlcipher_set_log(const char *destination); void sqlcipher_set_log_level(unsigned int level); +unsigned int sqlcipher_get_log_level(); void sqlcipher_set_log_subsystem(unsigned int subsys); +unsigned int sqlcipher_get_log_subsystem(); #define SQLCIPHER_LOG_NONE 0x00 #define SQLCIPHER_LOG_ERROR 0x01 diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 16e3a3fa8..0d290c3e4 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1823,10 +1823,18 @@ void sqlcipher_set_log_level(unsigned int level) { sqlcipher_log_level = level; } +unsigned int sqlcipher_get_log_level() { + return sqlcipher_log_level; +} + void sqlcipher_set_log_subsystem(unsigned int subsys) { sqlcipher_log_subsys = subsys; } +unsigned int sqlcipher_get_log_subsystem() { + return sqlcipher_log_subsys; +} + int sqlcipher_set_log(const char *destination){ #ifdef SQLCIPHER_OMIT_LOG return SQLITE_ERROR; From 451db24c6edba71dadff2001b4020fde171fef77 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 15 Jul 2024 13:27:39 -0400 Subject: [PATCH 153/340] Emits level and subsystem in log output --- src/crypto.c | 51 ++------------------------------------------ src/crypto.h | 3 +++ src/crypto_impl.c | 54 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 51 insertions(+), 57 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index f0c1036c2..30edbf429 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -690,8 +690,6 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else if( sqlite3_stricmp(zLeft, "cipher_log_level")==0 ){ unsigned int level = SQLCIPHER_LOG_NONE; - char *out = NULL; - if(zRight) { if(sqlite3_stricmp(zRight, "ERROR")==0) level = SQLCIPHER_LOG_ERROR; else if(sqlite3_stricmp(zRight, "WARN" )==0) level = SQLCIPHER_LOG_WARN; @@ -702,35 +700,10 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else { level = sqlcipher_get_log_level(); } - switch(level) { - case SQLCIPHER_LOG_NONE: - out = "NONE"; - break; - case SQLCIPHER_LOG_ERROR: - out = "ERROR"; - break; - case SQLCIPHER_LOG_WARN: - out = "WARN"; - break; - case SQLCIPHER_LOG_INFO: - out = "INFO"; - break; - case SQLCIPHER_LOG_DEBUG: - out = "DEBUG"; - break; - case SQLCIPHER_LOG_TRACE: - out = "TRACE"; - break; - case SQLCIPHER_LOG_ALL: - out = "ALL"; - break; - } - sqlcipher_vdbe_return_string(pParse, "cipher_log_level", out, P4_TRANSIENT); + sqlcipher_vdbe_return_string(pParse, "cipher_log_level", sqlcipher_get_log_subsystem_str(level), P4_TRANSIENT); } else if( sqlite3_stricmp(zLeft, "cipher_log_subsystem")==0 ){ unsigned int subsys = SQLCIPHER_LOG_NONE; - char *out = NULL; - if(zRight) { if(sqlite3_stricmp(zRight, "NONE" )==0) subsys = SQLCIPHER_LOG_NONE; else if(sqlite3_stricmp(zRight, "ALL" )==0) subsys = SQLCIPHER_LOG_ALL; @@ -742,27 +715,7 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else { subsys = sqlcipher_get_log_subsystem(); } - switch(subsys) { - case SQLCIPHER_LOG_NONE: - out = "NONE"; - break; - case SQLCIPHER_LOG_ALL: - out = "ALL"; - break; - case SQLCIPHER_LOG_CORE: - out = "CORE"; - break; - case SQLCIPHER_LOG_MEMORY: - out = "MEMORY"; - break; - case SQLCIPHER_LOG_MUTEX: - out = "MUTEX"; - break; - case SQLCIPHER_LOG_PROVIDER: - out = "PROVIDER"; - break; - } - sqlcipher_vdbe_return_string(pParse, "cipher_log_subsystem", out, P4_TRANSIENT); + sqlcipher_vdbe_return_string(pParse, "cipher_log_subsystem", sqlcipher_get_log_subsystem_str(subsys), P4_TRANSIENT); } else if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); diff --git a/src/crypto.h b/src/crypto.h index 0ed90e52a..2bbf1e459 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -335,6 +335,9 @@ unsigned int sqlcipher_get_log_level(); void sqlcipher_set_log_subsystem(unsigned int subsys); unsigned int sqlcipher_get_log_subsystem(); +char *sqlcipher_get_log_level_str(unsigned int); +char *sqlcipher_get_log_subsystem_str(unsigned int); + #define SQLCIPHER_LOG_NONE 0x00 #define SQLCIPHER_LOG_ERROR 0x01 #define SQLCIPHER_LOG_WARN 0x02 diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 0d290c3e4..e54cb4eb4 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -1733,14 +1733,52 @@ const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) { return ctx->provider->get_provider_version(ctx->provider_ctx); } +char *sqlcipher_get_log_level_str(unsigned int level) { + switch(level) { + case SQLCIPHER_LOG_ERROR: + return "ERROR"; + case SQLCIPHER_LOG_WARN: + return "WARN"; + case SQLCIPHER_LOG_INFO: + return "INFO"; + case SQLCIPHER_LOG_DEBUG: + return "DEBUG"; + case SQLCIPHER_LOG_TRACE: + return "TRACE"; + case SQLCIPHER_LOG_ALL: + return "ALL"; + } + return "NONE"; +} + +char *sqlcipher_get_log_subsystem_str(unsigned int subsys) { + switch(subsys) { + case SQLCIPHER_LOG_NONE: + return "NONE"; + case SQLCIPHER_LOG_CORE: + return "CORE"; + case SQLCIPHER_LOG_MEMORY: + return "MEMORY"; + case SQLCIPHER_LOG_MUTEX: + return "MUTEX"; + case SQLCIPHER_LOG_PROVIDER: + return "PROVIDER"; + } + return "ALL"; +} + + #ifndef SQLCIPHER_OMIT_LOG /* constants from https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-crt/misc/gettimeofday.c */ #define FILETIME_1970 116444736000000000ull /* seconds between 1/1/1601 and 1/1/1970 */ #define HECTONANOSEC_PER_SEC 10000000ull +#define MAX_LOG_LEN 8192 void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, ...) { va_list params; va_start(params, message); - char *formatted = NULL; + char formatted[MAX_LOG_LEN]; + char *out = NULL; + int len = 0; #ifdef CODEC_DEBUG #if defined(SQLCIPHER_OMIT_LOG_DEVICE) @@ -1772,15 +1810,17 @@ void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, goto end; } + sqlite3_snprintf(MAX_LOG_LEN, formatted, "%s %s |", sqlcipher_get_log_level_str(level), sqlcipher_get_log_subsystem_str(subsys)); + len = strlen(formatted); + sqlite3_vsnprintf(MAX_LOG_LEN - len, formatted + len, message, params); + #if !defined(SQLCIPHER_OMIT_LOG_DEVICE) if(sqlcipher_log_device) { #if defined(__ANDROID__) - __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); + __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", formatted); goto end; -#elif defined(__APPLE__) - formatted = sqlite3_vmprintf(message, params); +#elif defined(__APPLEformattes__) os_log(OS_LOG_DEFAULT, "%{public}s", formatted); - sqlite3_free(formatted); goto end; #endif } @@ -1807,9 +1847,7 @@ void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, localtime_r(&sec, &tt); #endif if(strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tt)) { - fprintf((FILE*)sqlcipher_log_file, "%s.%03d: ", buffer, ms); - vfprintf((FILE*)sqlcipher_log_file, message, params); - fprintf((FILE*)sqlcipher_log_file, "\n"); + fprintf((FILE*)sqlcipher_log_file, "%s.%03d: %s\n", buffer, ms, formatted); goto end; } } From abcdba2f181555469964e90727065c0d36b35327 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 15 Jul 2024 13:40:45 -0400 Subject: [PATCH 154/340] Renames log subsystem to source (e.g. PRAGMA cipher_log_source) --- src/crypto.c | 24 ++++++++++++------------ src/crypto.h | 10 +++++----- src/crypto_impl.c | 20 ++++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/crypto.c b/src/crypto.c index 30edbf429..e64938671 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -700,22 +700,22 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else { level = sqlcipher_get_log_level(); } - sqlcipher_vdbe_return_string(pParse, "cipher_log_level", sqlcipher_get_log_subsystem_str(level), P4_TRANSIENT); + sqlcipher_vdbe_return_string(pParse, "cipher_log_level", sqlcipher_get_log_source_str(level), P4_TRANSIENT); } else - if( sqlite3_stricmp(zLeft, "cipher_log_subsystem")==0 ){ - unsigned int subsys = SQLCIPHER_LOG_NONE; + if( sqlite3_stricmp(zLeft, "cipher_log_source")==0 ){ + unsigned int source = SQLCIPHER_LOG_NONE; if(zRight) { - if(sqlite3_stricmp(zRight, "NONE" )==0) subsys = SQLCIPHER_LOG_NONE; - else if(sqlite3_stricmp(zRight, "ALL" )==0) subsys = SQLCIPHER_LOG_ALL; - else if(sqlite3_stricmp(zRight, "CORE" )==0) subsys = SQLCIPHER_LOG_CORE; - else if(sqlite3_stricmp(zRight, "MEMORY" )==0) subsys = SQLCIPHER_LOG_MEMORY; - else if(sqlite3_stricmp(zRight, "MUTEX" )==0) subsys = SQLCIPHER_LOG_MUTEX; - else if(sqlite3_stricmp(zRight, "PROVIDER")==0) subsys = SQLCIPHER_LOG_PROVIDER; - sqlcipher_set_log_subsystem(subsys); + if(sqlite3_stricmp(zRight, "NONE" )==0) source = SQLCIPHER_LOG_NONE; + else if(sqlite3_stricmp(zRight, "ALL" )==0) source = SQLCIPHER_LOG_ALL; + else if(sqlite3_stricmp(zRight, "CORE" )==0) source = SQLCIPHER_LOG_CORE; + else if(sqlite3_stricmp(zRight, "MEMORY" )==0) source = SQLCIPHER_LOG_MEMORY; + else if(sqlite3_stricmp(zRight, "MUTEX" )==0) source = SQLCIPHER_LOG_MUTEX; + else if(sqlite3_stricmp(zRight, "PROVIDER")==0) source = SQLCIPHER_LOG_PROVIDER; + sqlcipher_set_log_source(source); } else { - subsys = sqlcipher_get_log_subsystem(); + source = sqlcipher_get_log_source(); } - sqlcipher_vdbe_return_string(pParse, "cipher_log_subsystem", sqlcipher_get_log_subsystem_str(subsys), P4_TRANSIENT); + sqlcipher_vdbe_return_string(pParse, "cipher_log_source", sqlcipher_get_log_source_str(source), P4_TRANSIENT); } else if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); diff --git a/src/crypto.h b/src/crypto.h index 2bbf1e459..23003b814 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -332,11 +332,11 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); int sqlcipher_set_log(const char *destination); void sqlcipher_set_log_level(unsigned int level); unsigned int sqlcipher_get_log_level(); -void sqlcipher_set_log_subsystem(unsigned int subsys); -unsigned int sqlcipher_get_log_subsystem(); +void sqlcipher_set_log_source(unsigned int source); +unsigned int sqlcipher_get_log_source(); char *sqlcipher_get_log_level_str(unsigned int); -char *sqlcipher_get_log_subsystem_str(unsigned int); +char *sqlcipher_get_log_source_str(unsigned int); #define SQLCIPHER_LOG_NONE 0x00 #define SQLCIPHER_LOG_ERROR 0x01 @@ -352,9 +352,9 @@ char *sqlcipher_get_log_subsystem_str(unsigned int); #define SQLCIPHER_LOG_PROVIDER 0x08 #ifdef SQLCIPHER_OMIT_LOG -#define sqlcipher_log(level, subsys, message, ...) +#define sqlcipher_log(level, source, message, ...) #else -void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, ...); +void sqlcipher_log(unsigned int level, unsigned int source, const char *message, ...); #endif void sqlcipher_vdbe_return_string(Parse*, const char*, const char*, int); diff --git a/src/crypto_impl.c b/src/crypto_impl.c index e54cb4eb4..6bd190319 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -81,7 +81,7 @@ static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; static FILE* sqlcipher_log_file = NULL; static volatile int sqlcipher_log_device = 0; static volatile unsigned int sqlcipher_log_level = SQLCIPHER_LOG_NONE; -static volatile unsigned int sqlcipher_log_subsys = SQLCIPHER_LOG_ALL; +static volatile unsigned int sqlcipher_log_source = SQLCIPHER_LOG_ALL; static volatile int sqlcipher_log_set = 0; sqlite3_mutex* sqlcipher_mutex(int mutex) { @@ -1751,8 +1751,8 @@ char *sqlcipher_get_log_level_str(unsigned int level) { return "NONE"; } -char *sqlcipher_get_log_subsystem_str(unsigned int subsys) { - switch(subsys) { +char *sqlcipher_get_log_source_str(unsigned int source) { + switch(source) { case SQLCIPHER_LOG_NONE: return "NONE"; case SQLCIPHER_LOG_CORE: @@ -1773,7 +1773,7 @@ char *sqlcipher_get_log_subsystem_str(unsigned int subsys) { #define FILETIME_1970 116444736000000000ull /* seconds between 1/1/1601 and 1/1/1970 */ #define HECTONANOSEC_PER_SEC 10000000ull #define MAX_LOG_LEN 8192 -void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, ...) { +void sqlcipher_log(unsigned int level, unsigned int source, const char *message, ...) { va_list params; va_start(params, message); char formatted[MAX_LOG_LEN]; @@ -1803,14 +1803,14 @@ void sqlcipher_log(unsigned int level, unsigned int subsys, const char *message, #endif if( level > sqlcipher_log_level /* log level is higher, e.g. level filter is at ERROR but this message is DEBUG */ - || (sqlcipher_log_subsys & subsys) == 0 /* subsystem filter doesn't match this message subsys */ + || (sqlcipher_log_source & source) == 0 /* source filter doesn't match this message source */ || (sqlcipher_log_device == 0 && sqlcipher_log_file == NULL) /* no configured log target */ ) { /* skip logging this message */ goto end; } - sqlite3_snprintf(MAX_LOG_LEN, formatted, "%s %s |", sqlcipher_get_log_level_str(level), sqlcipher_get_log_subsystem_str(subsys)); + sqlite3_snprintf(MAX_LOG_LEN, formatted, "%s %s ", sqlcipher_get_log_level_str(level), sqlcipher_get_log_source_str(source)); len = strlen(formatted); sqlite3_vsnprintf(MAX_LOG_LEN - len, formatted + len, message, params); @@ -1865,12 +1865,12 @@ unsigned int sqlcipher_get_log_level() { return sqlcipher_log_level; } -void sqlcipher_set_log_subsystem(unsigned int subsys) { - sqlcipher_log_subsys = subsys; +void sqlcipher_set_log_source(unsigned int source) { + sqlcipher_log_source = source; } -unsigned int sqlcipher_get_log_subsystem() { - return sqlcipher_log_subsys; +unsigned int sqlcipher_get_log_source() { + return sqlcipher_log_source; } int sqlcipher_set_log(const char *destination){ From 4ac23997280765cd4189ff4e2e8bdc80df35c9f1 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Mon, 15 Jul 2024 15:52:35 -0400 Subject: [PATCH 155/340] Merges crypto.h, crypto.c, and crypto_impl.c into sqlcipher.c - Makes internal sqlcipher functions static - Cleans up unecessary getter/setter wrapper functions - Simplifies management of default page size, HMAC algorithm, KDF algorithm, plaintext header size, log settings, memory security --- Makefile.in | 13 +- Makefile.msc | 4 +- src/crypto.c | 1296 -------------------- src/crypto.h | 381 ------ src/crypto_cc.c | 1 - src/crypto_nss.c | 1 - src/crypto_openssl.c | 1 - src/{crypto_impl.c => sqlcipher.c} | 1816 +++++++++++++++++++++++----- src/sqlcipher.h | 46 +- tool/mksqlite3c.tcl | 4 +- 10 files changed, 1591 insertions(+), 1972 deletions(-) delete mode 100644 src/crypto.c delete mode 100644 src/crypto.h rename src/{crypto_impl.c => sqlcipher.c} (52%) diff --git a/Makefile.in b/Makefile.in index d1ed75227..8431c25a6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -145,18 +145,15 @@ LTLINK_EXTRAS += $(GCOV_LDFLAGS$(USE_GCOV)) # BEGIN CRYPTO CRYPTOLIBOBJ = \ - crypto.lo \ - crypto_impl.lo \ + sqlcipher.lo \ crypto_openssl.lo \ crypto_libtomcrypt.lo \ crypto_nss.lo \ crypto_cc.lo CRYPTOSRC = \ - $(TOP)/src/crypto.h \ $(TOP)/src/sqlcipher.h \ - $(TOP)/src/crypto.c \ - $(TOP)/src/crypto_impl.c \ + $(TOP)/src/sqlcipher.c \ $(TOP)/src/crypto_libtomcrypt.c \ $(TOP)/src/crypto_nss.c \ $(TOP)/src/crypto_openssl.c \ @@ -893,10 +890,8 @@ opcodes.lo: opcodes.c $(LTCOMPILE) $(TEMP_STORE) -c opcodes.c # BEGIN CRYPTO -crypto.lo: $(TOP)/src/crypto.c $(HDR) - $(LTCOMPILE) -c $(TOP)/src/crypto.c -crypto_impl.lo: $(TOP)/src/crypto_impl.c $(HDR) - $(LTCOMPILE) -c $(TOP)/src/crypto_impl.c +sqlcipher.lo: $(TOP)/src/sqlcipher.c $(HDR) + $(LTCOMPILE) -c $(TOP)/src/sqlcipher.c crypto_openssl.lo: $(TOP)/src/crypto_openssl.c $(HDR) $(LTCOMPILE) -c $(TOP)/src/crypto_openssl.c crypto_nss.lo: $(TOP)/src/crypto_nss.c $(HDR) diff --git a/Makefile.msc b/Makefile.msc index 1b48c448f..d46cee15f 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1327,13 +1327,11 @@ LIBRESOBJS = # Core source code files, part 1. # SRC00 = \ - $(TOP)\src\crypto.c \ + $(TOP)\src\sqlcipher.c \ $(TOP)\src\crypto_cc.c \ - $(TOP)\src\crypto_impl.c \ $(TOP)\src\crypto_libtomcrypt.c \ $(TOP)\src\crypto_nss.c \ $(TOP)\src\crypto_openssl.c \ - $(TOP)\src\crypto.h \ $(TOP)\src\sqlcipher.h \ $(TOP)\src\alter.c \ $(TOP)\src\analyze.c \ diff --git a/src/crypto.c b/src/crypto.c deleted file mode 100644 index e64938671..000000000 --- a/src/crypto.c +++ /dev/null @@ -1,1296 +0,0 @@ -/* -** SQLCipher -** http://sqlcipher.net -** -** Copyright (c) 2008 - 2013, ZETETIC LLC -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the ZETETIC LLC nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -*/ -/* BEGIN SQLCIPHER */ -#ifdef SQLITE_HAS_CODEC - -#include -#include "sqlcipher.h" -#include "crypto.h" - -#ifdef SQLCIPHER_EXT -#include "sqlcipher_ext.h" -#endif - -void sqlcipher_vdbe_return_string(Parse *pParse, const char *zLabel, const char *value, int value_type){ - Vdbe *v = sqlite3GetVdbe(pParse); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLabel, SQLITE_STATIC); - sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, value, value_type); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); -} - -static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ctx) { - int rc, page_sz, reserve_sz; - - page_sz = sqlcipher_codec_ctx_get_pagesize(ctx); - reserve_sz = sqlcipher_codec_ctx_get_reservesize(ctx); - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d", page_sz, reserve_sz); - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: entering database mutex %p", db->mutex); - sqlite3_mutex_enter(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: entered database mutex %p", db->mutex); - db->nextPagesize = page_sz; - - /* before forcing the page size we need to unset the BTS_PAGESIZE_FIXED flag, else - sqliteBtreeSetPageSize will block the change */ - pDb->pBt->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; - rc = sqlite3BtreeSetPageSize(pDb->pBt, page_sz, reserve_sz, 0); - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d", rc); - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: leaving database mutex %p", db->mutex); - sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: left database mutex %p", db->mutex); - - return rc; -} - -static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) { - struct Db *pDb = &db->aDb[nDb]; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_pass_key: db=%p nDb=%d for_ctx=%d", db, nDb, for_ctx); - if(pDb->pBt) { - codec_ctx *ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); - - if(ctx) { - return sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, for_ctx); - } else { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "codec_set_pass_key: error ocurred fetching codec from pager on db %d", nDb); - return SQLITE_ERROR; - } - } - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "codec_set_pass_key: no btree present on db %d", nDb); - return SQLITE_ERROR; -} - -int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLeft, const char *zRight) { - struct Db *pDb = &db->aDb[iDb]; - codec_ctx *ctx = NULL; - int rc; - - if(pDb->pBt) { - ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); - } - - if(sqlite3_stricmp(zLeft, "key") !=0 && sqlite3_stricmp(zLeft, "rekey") != 0) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); - } - -#ifdef SQLCIPHER_EXT - if(sqlcipher_ext_pragma(db, iDb, pParse, zLeft, zRight)) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: PRAGMA handled by sqlcipher_ext_pragma"); - } else -#endif -#ifdef SQLCIPHER_TEST - if( sqlite3_stricmp(zLeft,"cipher_test_on")==0 ){ - if( zRight ) { - unsigned int flags = sqlcipher_get_test_flags(); - if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { - SQLCIPHER_FLAG_SET(flags,TEST_FAIL_ENCRYPT); - } else - if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { - SQLCIPHER_FLAG_SET(flags,TEST_FAIL_DECRYPT); - } else - if(sqlite3_stricmp(zRight, "fail_migrate")==0) { - SQLCIPHER_FLAG_SET(flags,TEST_FAIL_MIGRATE); - } - sqlcipher_set_test_flags(flags); - } - } else - if( sqlite3_stricmp(zLeft,"cipher_test_off")==0 ){ - if( zRight ) { - unsigned int flags = sqlcipher_get_test_flags(); - if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { - SQLCIPHER_FLAG_UNSET(flags,TEST_FAIL_ENCRYPT); - } else - if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { - SQLCIPHER_FLAG_UNSET(flags,TEST_FAIL_DECRYPT); - } else - if(sqlite3_stricmp(zRight, "fail_migrate")==0) { - SQLCIPHER_FLAG_UNSET(flags,TEST_FAIL_MIGRATE); - } - sqlcipher_set_test_flags(flags); - } - } else - if( sqlite3_stricmp(zLeft,"cipher_test")==0 ){ - char *flags = sqlite3_mprintf("%u", sqlcipher_get_test_flags()); - sqlcipher_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); - }else - if( sqlite3_stricmp(zLeft,"cipher_test_rand")==0 ){ - if( zRight ) { - int rand = atoi(zRight); - sqlcipher_set_test_rand(rand); - } else { - char *rand = sqlite3_mprintf("%d", sqlcipher_get_test_rand()); - sqlcipher_vdbe_return_string(pParse, "cipher_test_rand", rand, P4_DYNAMIC); - } - } else -#endif - if( sqlite3_stricmp(zLeft, "cipher_fips_status")== 0 && !zRight ){ - if(ctx) { - char *fips_mode_status = sqlite3_mprintf("%d", sqlcipher_codec_fips_status(ctx)); - sqlcipher_vdbe_return_string(pParse, "cipher_fips_status", fips_mode_status, P4_DYNAMIC); - } - } else - if( sqlite3_stricmp(zLeft, "cipher_store_pass")==0 && zRight ) { - if(ctx) { - char *deprecation = "PRAGMA cipher_store_pass is deprecated, please remove from use"; - sqlcipher_codec_set_store_pass(ctx, sqlite3GetBoolean(zRight, 1)); - sqlcipher_vdbe_return_string(pParse, "cipher_store_pass", deprecation, P4_TRANSIENT); - sqlite3_log(SQLITE_WARNING, deprecation); - } - } else - if( sqlite3_stricmp(zLeft, "cipher_store_pass")==0 && !zRight ) { - if(ctx){ - char *store_pass_value = sqlite3_mprintf("%d", sqlcipher_codec_get_store_pass(ctx)); - sqlcipher_vdbe_return_string(pParse, "cipher_store_pass", store_pass_value, P4_DYNAMIC); - } - } - if( sqlite3_stricmp(zLeft, "cipher_profile")== 0 && zRight ){ - char *profile_status = sqlite3_mprintf("%d", sqlcipher_cipher_profile(db, zRight)); - sqlcipher_vdbe_return_string(pParse, "cipher_profile", profile_status, P4_DYNAMIC); - } else - if( sqlite3_stricmp(zLeft, "cipher_add_random")==0 && zRight ){ - if(ctx) { - char *add_random_status = sqlite3_mprintf("%d", sqlcipher_codec_add_random(ctx, zRight, sqlite3Strlen30(zRight))); - sqlcipher_vdbe_return_string(pParse, "cipher_add_random", add_random_status, P4_DYNAMIC); - } - } else - if( sqlite3_stricmp(zLeft, "cipher_migrate")==0 && !zRight ){ - if(ctx){ - int status = sqlcipher_codec_ctx_migrate(ctx); - char *migrate_status = sqlite3_mprintf("%d", status); - sqlcipher_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); - if(status != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: error occurred during cipher_migrate: %d", status); - sqlcipher_codec_ctx_set_error(ctx, status); - } - } - } else - if( sqlite3_stricmp(zLeft, "cipher_provider")==0 && !zRight ){ - if(ctx) { sqlcipher_vdbe_return_string(pParse, "cipher_provider", - sqlcipher_codec_get_cipher_provider(ctx), P4_TRANSIENT); - } - } else - if( sqlite3_stricmp(zLeft, "cipher_provider_version")==0 && !zRight){ - if(ctx) { sqlcipher_vdbe_return_string(pParse, "cipher_provider_version", - sqlcipher_codec_get_provider_version(ctx), P4_TRANSIENT); - } - } else - if( sqlite3_stricmp(zLeft, "cipher_version")==0 && !zRight ){ - sqlcipher_vdbe_return_string(pParse, "cipher_version", sqlcipher_version(), P4_DYNAMIC); - }else - if( sqlite3_stricmp(zLeft, "cipher")==0 ){ - if(ctx) { - if( zRight ) { - const char* message = "PRAGMA cipher is no longer supported."; - sqlcipher_vdbe_return_string(pParse, "cipher", message, P4_TRANSIENT); - sqlite3_log(SQLITE_WARNING, message); - }else { - sqlcipher_vdbe_return_string(pParse, "cipher", sqlcipher_codec_ctx_get_cipher(ctx), P4_TRANSIENT); - } - } - }else - if( sqlite3_stricmp(zLeft, "rekey_cipher")==0 && zRight ){ - const char* message = "PRAGMA rekey_cipher is no longer supported."; - sqlcipher_vdbe_return_string(pParse, "rekey_cipher", message, P4_TRANSIENT); - sqlite3_log(SQLITE_WARNING, message); - }else - if( sqlite3_stricmp(zLeft,"cipher_default_kdf_iter")==0 ){ - if( zRight ) { - sqlcipher_set_default_kdf_iter(atoi(zRight)); /* change default KDF iterations */ - } else { - char *kdf_iter = sqlite3_mprintf("%d", sqlcipher_get_default_kdf_iter()); - sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_iter", kdf_iter, P4_DYNAMIC); - } - }else - if( sqlite3_stricmp(zLeft, "kdf_iter")==0 ){ - if(ctx) { - if( zRight ) { - sqlcipher_codec_ctx_set_kdf_iter(ctx, atoi(zRight)); /* change of RW PBKDF2 iteration */ - } else { - char *kdf_iter = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_kdf_iter(ctx)); - sqlcipher_vdbe_return_string(pParse, "kdf_iter", kdf_iter, P4_DYNAMIC); - } - } - }else - if( sqlite3_stricmp(zLeft, "fast_kdf_iter")==0){ - if(ctx) { - if( zRight ) { - char *deprecation = "PRAGMA fast_kdf_iter is deprecated, please remove from use"; - sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, atoi(zRight)); /* change of RW PBKDF2 iteration */ - sqlcipher_vdbe_return_string(pParse, "fast_kdf_iter", deprecation, P4_TRANSIENT); - sqlite3_log(SQLITE_WARNING, deprecation); - } else { - char *fast_kdf_iter = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_fast_kdf_iter(ctx)); - sqlcipher_vdbe_return_string(pParse, "fast_kdf_iter", fast_kdf_iter, P4_DYNAMIC); - } - } - }else - if( sqlite3_stricmp(zLeft, "rekey_kdf_iter")==0 && zRight ){ - const char* message = "PRAGMA rekey_kdf_iter is no longer supported."; - sqlcipher_vdbe_return_string(pParse, "rekey_kdf_iter", message, P4_TRANSIENT); - sqlite3_log(SQLITE_WARNING, message); - }else - if( sqlite3_stricmp(zLeft,"page_size")==0 || sqlite3_stricmp(zLeft,"cipher_page_size")==0 ){ - /* PRAGMA cipher_page_size will alter the size of the database pages while ensuring that the - required reserve space is allocated at the end of each page. This will also override the - standard SQLite PRAGMA page_size behavior if a codec context is attached to the database handle. - If PRAGMA page_size is invoked but a codec context is not attached (i.e. dealing with a standard - unencrypted database) then return early and allow the standard PRAGMA page_size logic to apply. */ - if(ctx) { - if( zRight ) { - int size = atoi(zRight); - rc = sqlcipher_codec_ctx_set_pagesize(ctx, size); - if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); - rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); - if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); - } else { - char * page_size = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_pagesize(ctx)); - sqlcipher_vdbe_return_string(pParse, "cipher_page_size", page_size, P4_DYNAMIC); - } - } else { - return 0; /* return early so that the PragTyp_PAGE_SIZE case logic in pragma.c will take effect */ - } - }else - if( sqlite3_stricmp(zLeft,"cipher_default_page_size")==0 ){ - if( zRight ) { - sqlcipher_set_default_pagesize(atoi(zRight)); - } else { - char *default_page_size = sqlite3_mprintf("%d", sqlcipher_get_default_pagesize()); - sqlcipher_vdbe_return_string(pParse, "cipher_default_page_size", default_page_size, P4_DYNAMIC); - } - }else - if( sqlite3_stricmp(zLeft,"cipher_default_use_hmac")==0 ){ - if( zRight ) { - sqlcipher_set_default_use_hmac(sqlite3GetBoolean(zRight,1)); - } else { - char *default_use_hmac = sqlite3_mprintf("%d", sqlcipher_get_default_use_hmac()); - sqlcipher_vdbe_return_string(pParse, "cipher_default_use_hmac", default_use_hmac, P4_DYNAMIC); - } - }else - if( sqlite3_stricmp(zLeft,"cipher_use_hmac")==0 ){ - if(ctx) { - if( zRight ) { - rc = sqlcipher_codec_ctx_set_use_hmac(ctx, sqlite3GetBoolean(zRight,1)); - if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); - /* since the use of hmac has changed, the page size may also change */ - rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); - if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); - } else { - char *hmac_flag = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_use_hmac(ctx)); - sqlcipher_vdbe_return_string(pParse, "cipher_use_hmac", hmac_flag, P4_DYNAMIC); - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_hmac_pgno")==0 ){ - if(ctx) { - if(zRight) { - char *deprecation = "PRAGMA cipher_hmac_pgno is deprecated, please remove from use"; - /* clear both pgno endian flags */ - if(sqlite3_stricmp(zRight, "le") == 0) { - SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_BE_PGNO); - SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_LE_PGNO); - } else if(sqlite3_stricmp(zRight, "be") == 0) { - SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_LE_PGNO); - SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_BE_PGNO); - } else if(sqlite3_stricmp(zRight, "native") == 0) { - SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_LE_PGNO); - SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_BE_PGNO); - } - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", deprecation, P4_TRANSIENT); - sqlite3_log(SQLITE_WARNING, deprecation); - - } else { - if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_LE_PGNO)) { - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "le", P4_TRANSIENT); - } else if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_BE_PGNO)) { - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "be", P4_TRANSIENT); - } else { - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "native", P4_TRANSIENT); - } - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_hmac_salt_mask")==0 ){ - if(ctx) { - if(zRight) { - char *deprecation = "PRAGMA cipher_hmac_salt_mask is deprecated, please remove from use"; - if (sqlite3StrNICmp(zRight ,"x'", 2) == 0 && sqlite3Strlen30(zRight) == 5) { - unsigned char mask = 0; - const unsigned char *hex = (const unsigned char *)zRight+2; - cipher_hex2bin(hex,2,&mask); - sqlcipher_set_hmac_salt_mask(mask); - } - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_salt_mask", deprecation, P4_TRANSIENT); - sqlite3_log(SQLITE_WARNING, deprecation); - } else { - char *hmac_salt_mask = sqlite3_mprintf("%02x", sqlcipher_get_hmac_salt_mask()); - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_salt_mask", hmac_salt_mask, P4_DYNAMIC); - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_plaintext_header_size")==0 ){ - if(ctx) { - if( zRight ) { - int size = atoi(zRight); - /* deliberately ignore result code, if size is invalid it will be set to -1 - and trip the error later in the codec */ - sqlcipher_codec_ctx_set_plaintext_header_size(ctx, size); - } else { - char *size = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_plaintext_header_size(ctx)); - sqlcipher_vdbe_return_string(pParse, "cipher_plaintext_header_size", size, P4_DYNAMIC); - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_default_plaintext_header_size")==0 ){ - if( zRight ) { - sqlcipher_set_default_plaintext_header_size(atoi(zRight)); - } else { - char *size = sqlite3_mprintf("%d", sqlcipher_get_default_plaintext_header_size()); - sqlcipher_vdbe_return_string(pParse, "cipher_default_plaintext_header_size", size, P4_DYNAMIC); - } - }else - if( sqlite3_stricmp(zLeft,"cipher_salt")==0 ){ - if(ctx) { - if(zRight) { - if (sqlite3StrNICmp(zRight ,"x'", 2) == 0 && sqlite3Strlen30(zRight) == (FILE_HEADER_SZ*2)+3) { - unsigned char *salt = (unsigned char*) sqlite3_malloc(FILE_HEADER_SZ); - const unsigned char *hex = (const unsigned char *)zRight+2; - cipher_hex2bin(hex,FILE_HEADER_SZ*2,salt); - sqlcipher_codec_ctx_set_kdf_salt(ctx, salt, FILE_HEADER_SZ); - sqlite3_free(salt); - } - } else { - void *salt; - char *hexsalt = (char*) sqlite3_malloc((FILE_HEADER_SZ*2)+1); - if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &salt)) == SQLITE_OK) { - cipher_bin2hex(salt, FILE_HEADER_SZ, hexsalt); - sqlcipher_vdbe_return_string(pParse, "cipher_salt", hexsalt, P4_DYNAMIC); - } else { - sqlite3_free(hexsalt); - sqlcipher_codec_ctx_set_error(ctx, rc); - } - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_hmac_algorithm")==0 ){ - if(ctx) { - if(zRight) { - rc = SQLITE_ERROR; - if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { - rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { - rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA256); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { - rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA512); - } - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - } else { - int algorithm = sqlcipher_codec_ctx_get_hmac_algorithm(ctx); - if(algorithm == SQLCIPHER_HMAC_SHA1) { - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA1_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_HMAC_SHA256) { - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA256_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_HMAC_SHA512) { - sqlcipher_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA512_LABEL, P4_TRANSIENT); - } - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_default_hmac_algorithm")==0 ){ - if(zRight) { - rc = SQLITE_ERROR; - if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { - rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { - rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA256); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { - rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA512); - } - } else { - int algorithm = sqlcipher_get_default_hmac_algorithm(); - if(algorithm == SQLCIPHER_HMAC_SHA1) { - sqlcipher_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA1_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_HMAC_SHA256) { - sqlcipher_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA256_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_HMAC_SHA512) { - sqlcipher_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA512_LABEL, P4_TRANSIENT); - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_kdf_algorithm")==0 ){ - if(ctx) { - if(zRight) { - rc = SQLITE_ERROR; - if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { - rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { - rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA256); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { - rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA512); - } - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - } else { - int algorithm = sqlcipher_codec_ctx_get_kdf_algorithm(ctx); - if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { - sqlcipher_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { - sqlcipher_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { - sqlcipher_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL, P4_TRANSIENT); - } - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_default_kdf_algorithm")==0 ){ - if(zRight) { - rc = SQLITE_ERROR; - if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { - rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { - rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA256); - } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { - rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA512); - } - } else { - int algorithm = sqlcipher_get_default_kdf_algorithm(); - if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { - sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { - sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL, P4_TRANSIENT); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { - sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL, P4_TRANSIENT); - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_compatibility")==0 ){ - if(ctx) { - if(zRight) { - int version = atoi(zRight); - - switch(version) { - case 1: - rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 4000); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 0); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - break; - - case 2: - rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 4000); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - break; - - case 3: - rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 64000); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - break; - - default: - rc = sqlcipher_codec_ctx_set_pagesize(ctx, 4096); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA512); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA512); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 256000); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - break; - } - - rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); - if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_default_compatibility")==0 ){ - if(zRight) { - int version = atoi(zRight); - switch(version) { - case 1: - sqlcipher_set_default_pagesize(1024); - sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1); - sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1); - sqlcipher_set_default_kdf_iter(4000); - sqlcipher_set_default_use_hmac(0); - break; - - case 2: - sqlcipher_set_default_pagesize(1024); - sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1); - sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1); - sqlcipher_set_default_kdf_iter(4000); - sqlcipher_set_default_use_hmac(1); - break; - - case 3: - sqlcipher_set_default_pagesize(1024); - sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1); - sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1); - sqlcipher_set_default_kdf_iter(64000); - sqlcipher_set_default_use_hmac(1); - break; - - default: - sqlcipher_set_default_pagesize(4096); - sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA512); - sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA512); - sqlcipher_set_default_kdf_iter(256000); - sqlcipher_set_default_use_hmac(1); - break; - } - } - }else - if( sqlite3_stricmp(zLeft,"cipher_memory_security")==0 ){ - if( zRight ) { - sqlcipher_set_mem_security(sqlite3GetBoolean(zRight,1)); - } else { - char *on = sqlite3_mprintf("%d", sqlcipher_get_mem_security()); - sqlcipher_vdbe_return_string(pParse, "cipher_memory_security", on, P4_DYNAMIC); - } - }else - if( sqlite3_stricmp(zLeft,"cipher_settings")==0 ){ - if(ctx) { - int algorithm; - char *pragma; - - pragma = sqlite3_mprintf("PRAGMA kdf_iter = %d;", sqlcipher_codec_ctx_get_kdf_iter(ctx)); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - pragma = sqlite3_mprintf("PRAGMA cipher_page_size = %d;", sqlcipher_codec_ctx_get_pagesize(ctx)); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - pragma = sqlite3_mprintf("PRAGMA cipher_use_hmac = %d;", sqlcipher_codec_ctx_get_use_hmac(ctx)); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - pragma = sqlite3_mprintf("PRAGMA cipher_plaintext_header_size = %d;", sqlcipher_codec_ctx_get_plaintext_header_size(ctx)); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - algorithm = sqlcipher_codec_ctx_get_hmac_algorithm(ctx); - pragma = NULL; - if(algorithm == SQLCIPHER_HMAC_SHA1) { - pragma = sqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA1_LABEL); - } else if(algorithm == SQLCIPHER_HMAC_SHA256) { - pragma = sqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA256_LABEL); - } else if(algorithm == SQLCIPHER_HMAC_SHA512) { - pragma = sqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA512_LABEL); - } - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - algorithm = sqlcipher_codec_ctx_get_kdf_algorithm(ctx); - pragma = NULL; - if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { - pragma = sqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { - pragma = sqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { - pragma = sqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL); - } - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - } - }else - if( sqlite3_stricmp(zLeft,"cipher_default_settings")==0 ){ - int algorithm; - char *pragma; - - pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_iter = %d;", sqlcipher_get_default_kdf_iter()); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - pragma = sqlite3_mprintf("PRAGMA cipher_default_page_size = %d;", sqlcipher_get_default_pagesize()); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - pragma = sqlite3_mprintf("PRAGMA cipher_default_use_hmac = %d;", sqlcipher_get_default_use_hmac()); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - pragma = sqlite3_mprintf("PRAGMA cipher_default_plaintext_header_size = %d;", sqlcipher_get_default_plaintext_header_size()); - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - algorithm = sqlcipher_get_default_hmac_algorithm(); - pragma = NULL; - if(algorithm == SQLCIPHER_HMAC_SHA1) { - pragma = sqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA1_LABEL); - } else if(algorithm == SQLCIPHER_HMAC_SHA256) { - pragma = sqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA256_LABEL); - } else if(algorithm == SQLCIPHER_HMAC_SHA512) { - pragma = sqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA512_LABEL); - } - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - - algorithm = sqlcipher_get_default_kdf_algorithm(); - pragma = NULL; - if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { - pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { - pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL); - } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { - pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL); - } - sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); - }else - if( sqlite3_stricmp(zLeft,"cipher_integrity_check")==0 ){ - if(ctx) { - sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check"); - } - } else - if( sqlite3_stricmp(zLeft, "cipher_log_level")==0 ){ - unsigned int level = SQLCIPHER_LOG_NONE; - if(zRight) { - if(sqlite3_stricmp(zRight, "ERROR")==0) level = SQLCIPHER_LOG_ERROR; - else if(sqlite3_stricmp(zRight, "WARN" )==0) level = SQLCIPHER_LOG_WARN; - else if(sqlite3_stricmp(zRight, "INFO" )==0) level = SQLCIPHER_LOG_INFO; - else if(sqlite3_stricmp(zRight, "DEBUG")==0) level = SQLCIPHER_LOG_DEBUG; - else if(sqlite3_stricmp(zRight, "TRACE")==0) level = SQLCIPHER_LOG_TRACE; - sqlcipher_set_log_level(level); - } else { - level = sqlcipher_get_log_level(); - } - sqlcipher_vdbe_return_string(pParse, "cipher_log_level", sqlcipher_get_log_source_str(level), P4_TRANSIENT); - } else - if( sqlite3_stricmp(zLeft, "cipher_log_source")==0 ){ - unsigned int source = SQLCIPHER_LOG_NONE; - if(zRight) { - if(sqlite3_stricmp(zRight, "NONE" )==0) source = SQLCIPHER_LOG_NONE; - else if(sqlite3_stricmp(zRight, "ALL" )==0) source = SQLCIPHER_LOG_ALL; - else if(sqlite3_stricmp(zRight, "CORE" )==0) source = SQLCIPHER_LOG_CORE; - else if(sqlite3_stricmp(zRight, "MEMORY" )==0) source = SQLCIPHER_LOG_MEMORY; - else if(sqlite3_stricmp(zRight, "MUTEX" )==0) source = SQLCIPHER_LOG_MUTEX; - else if(sqlite3_stricmp(zRight, "PROVIDER")==0) source = SQLCIPHER_LOG_PROVIDER; - sqlcipher_set_log_source(source); - } else { - source = sqlcipher_get_log_source(); - } - sqlcipher_vdbe_return_string(pParse, "cipher_log_source", sqlcipher_get_log_source_str(source), P4_TRANSIENT); - } else - if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ - char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); - sqlcipher_vdbe_return_string(pParse, "cipher_log", status, P4_DYNAMIC); - }else { - return 0; - } - return 1; -} - -/* these constants are used internally within SQLite's pager.c to differentiate between - operations on the main database or journal pages. This is important in the context - of a rekey operations, where the journal must be written using the original key - material (to allow a transactional rollback), while the new database pages are being - written with the new key material*/ -#define CODEC_READ_OP 3 -#define CODEC_WRITE_OP 6 -#define CODEC_JOURNAL_OP 7 - -/* - * sqlite3Codec can be called in multiple modes. - * encrypt mode - expected to return a pointer to the - * encrypted data without altering pData. - * decrypt mode - expected to return a pointer to pData, with - * the data decrypted in the input buffer - */ -static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { - codec_ctx *ctx = (codec_ctx *) iCtx; - int offset = 0, rc = 0; - int page_sz = sqlcipher_codec_ctx_get_pagesize(ctx); - unsigned char *pData = (unsigned char *) data; - void *buffer = sqlcipher_codec_ctx_get_data(ctx); - int plaintext_header_sz = sqlcipher_codec_ctx_get_plaintext_header_size(ctx); - int cctx = CIPHER_READ_CTX; - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3Codec: pgno=%d, mode=%d, page_sz=%d", pgno, mode, page_sz); - -#ifdef SQLCIPHER_EXT - if(sqlcipher_license_check(ctx) != SQLITE_OK) return NULL; -#endif - - /* call to derive keys if not present yet */ - if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error occurred during key derivation: %d", rc); - sqlcipher_codec_ctx_set_error(ctx, rc); - return NULL; - } - - /* if the plaintext_header_size is negative that means an invalid size was set via - PRAGMA. We can't set the error state on the pager at that point because the pager - may not be open yet. However, this is a fatal error state, so abort the codec */ - if(plaintext_header_sz < 0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error invalid plaintext_header_sz: %d", plaintext_header_sz); - sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); - return NULL; - } - - if(pgno == 1) /* adjust starting pointers in data page for header offset on first page*/ - offset = plaintext_header_sz ? plaintext_header_sz : FILE_HEADER_SZ; - - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3Codec: switch mode=%d offset=%d", mode, offset); - switch(mode) { - case CODEC_READ_OP: /* decrypt */ - if(pgno == 1) /* copy initial part of file header or SQLite magic to buffer */ - memcpy(buffer, plaintext_header_sz ? pData : (void *) SQLITE_FILE_HEADER, offset); - - rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); -#ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { - rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3Codec: simulating decryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); - } -#endif - if(rc != SQLITE_OK) { - /* failure to decrypt a page is considered a permanent error and will render the pager unusable - in order to prevent inconsistent data being loaded into page cache */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error decrypting page %d data: %d", pgno, rc); - sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); - sqlcipher_codec_ctx_set_error(ctx, rc); - } else { - SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_KEY_USED); - } - memcpy(pData, buffer, page_sz); /* copy buffer data back to pData and return */ - return pData; - break; - - case CODEC_WRITE_OP: /* encrypt database page, operate on write context and fall through to case 7, so the write context is used*/ - cctx = CIPHER_WRITE_CTX; - - case CODEC_JOURNAL_OP: /* encrypt journal page, operate on read context use to get the original page data from the database */ - if(pgno == 1) { /* copy initial part of file header or salt to buffer */ - void *kdf_salt = NULL; - /* retrieve the kdf salt */ - if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &kdf_salt)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error retrieving salt: %d", rc); - sqlcipher_codec_ctx_set_error(ctx, rc); - return NULL; - } - memcpy(buffer, plaintext_header_sz ? pData : kdf_salt, offset); - } - rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); -#ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { - rc = SQLITE_ERROR; - sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3Codec: simulating encryption failure for pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz); - } -#endif - if(rc != SQLITE_OK) { - /* failure to encrypt a page is considered a permanent error and will render the pager unusable - in order to prevent corrupted pages from being written to the main databased when using WAL */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error encrypting page %d data: %d", pgno, rc); - sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); - sqlcipher_codec_ctx_set_error(ctx, rc); - return NULL; - } - SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_KEY_USED); - return buffer; /* return persistent buffer data, pData remains intact */ - break; - - default: - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error unsupported codec mode %d", mode); - sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); /* unsupported mode, set error */ - return pData; - break; - } -} - -static void sqlite3FreeCodecArg(void *pCodecArg) { - codec_ctx *ctx = (codec_ctx *) pCodecArg; - if(pCodecArg == NULL) return; - sqlcipher_codec_ctx_free(&ctx); /* wipe and free allocated memory for the context */ - sqlcipher_deactivate(); /* cleanup related structures, OpenSSL etc, when codec is detatched */ -} - -int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { - struct Db *pDb = &db->aDb[nDb]; - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: db=%p, nDb=%d", db, nDb); - - if(nKey && zKey && pDb->pBt) { - int rc; - Pager *pPager = pDb->pBt->pBt->pPager; - sqlite3_file *fd; - codec_ctx *ctx; - - ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); - - if(ctx != NULL && SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) { - /* there is already a codec attached to this database, so we should not proceed */ - sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: no codec attached to db"); - return SQLITE_OK; - } - - /* check if the sqlite3_file is open, and if not force handle to NULL */ - if((fd = sqlite3PagerFile(pPager))->pMethods == 0) fd = NULL; - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipher_activate()"); - sqlcipher_activate(); /* perform internal initialization for sqlcipher */ - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: entering database mutex %p", db->mutex); - sqlite3_mutex_enter(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: entered database mutex %p", db->mutex); - -#ifdef SQLCIPHER_EXT - if((rc = sqlite3_set_authorizer(db, sqlcipher_license_authorizer, db)) != SQLITE_OK) { - sqlite3_mutex_leave(db->mutex); - return rc; - } -#endif - - /* point the internal codec argument against the contet to be prepared */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipher_codec_ctx_init()"); - rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, zKey, nKey); - - if(rc != SQLITE_OK) { - /* initialization failed, do not attach potentially corrupted context */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: context initialization failed, forcing error state with rc=%d", rc); - /* force an error at the pager level, such that even the upstream caller ignores the return code - the pager will be in an error state and will process no further operations */ - sqlite3pager_error(pPager, rc); - pDb->pBt->pBt->db->errCode = rc; - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: leaving database mutex %p (early return on rc=%d)", db->mutex, rc); - sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: left database mutex %p (early return on rc=%d)", db->mutex, rc); - return rc; - } - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipherPagerSetCodec()"); - sqlcipherPagerSetCodec(sqlite3BtreePager(pDb->pBt), sqlite3Codec, NULL, sqlite3FreeCodecArg, (void *) ctx); - - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling codec_set_btree_to_codec_pagesize()"); - codec_set_btree_to_codec_pagesize(db, pDb, ctx); - - /* force secure delete. This has the benefit of wiping internal data when deleted - and also ensures that all pages are written to disk (i.e. not skipped by - sqlite3PagerDontWrite optimizations) */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlite3BtreeSecureDelete()"); - sqlite3BtreeSecureDelete(pDb->pBt, 1); - - /* if fd is null, then this is an in-memory database and - we dont' want to overwrite the AutoVacuum settings - if not null, then set to the default */ - if(fd != NULL) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlite3BtreeSetAutoVacuum()"); - sqlite3BtreeSetAutoVacuum(pDb->pBt, SQLITE_DEFAULT_AUTOVACUUM); - } - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: leaving database mutex %p", db->mutex); - sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: left database mutex %p", db->mutex); - } - return SQLITE_OK; -} - -int sqlcipher_find_db_index(sqlite3 *db, const char *zDb) { - int db_index; - if(zDb == NULL){ - return 0; - } - for(db_index = 0; db_index < db->nDb; db_index++) { - struct Db *pDb = &db->aDb[db_index]; - if(strcmp(pDb->zDbSName, zDb) == 0) { - return db_index; - } - } - return 0; -} - -void sqlite3_activate_see(const char* in) { - /* do nothing, security enhancements are always active */ -} - -int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_key: db=%p", db); - return sqlite3_key_v2(db, "main", pKey, nKey); -} - -int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_key_v2: db=%p zDb=%s", db, zDb); - /* attach key if db and pKey are not null and nKey is > 0 */ - if(db && pKey && nKey) { - int db_index = sqlcipher_find_db_index(db, zDb); - return sqlcipherCodecAttach(db, db_index, pKey, nKey); - } - sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3_key_v2: no key provided"); - return SQLITE_ERROR; -} - -int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey: db=%p", db); - return sqlite3_rekey_v2(db, "main", pKey, nKey); -} - -/* sqlite3_rekey_v2 -** Given a database, this will reencrypt the database using a new key. -** There is only one possible modes of operation - to encrypt a database -** that is already encrpyted. If the database is not already encrypted -** this should do nothing -** The proposed logic for this function follows: -** 1. Determine if the database is already encryptped -** 2. If there is NOT already a key present do nothing -** 3. If there is a key present, re-encrypt the database with the new key -*/ -int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: db=%p zDb=%s", db, zDb); - if(db && pKey && nKey) { - int db_index = sqlcipher_find_db_index(db, zDb); - struct Db *pDb = &db->aDb[db_index]; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: database zDb=%p db_index:%d", zDb, db_index); - if(pDb->pBt) { - codec_ctx *ctx; - int rc, page_count; - Pgno pgno; - PgHdr *page; - Pager *pPager = pDb->pBt->pBt->pPager; - - ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); - - if(ctx == NULL) { - /* there was no codec attached to this database, so this should do nothing! */ - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: no codec attached to db %s: rekey can't be used on an unencrypted database", zDb); - return SQLITE_MISUSE; - } - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: entering database mutex %p", db->mutex); - sqlite3_mutex_enter(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: entered database mutex %p", db->mutex); - - codec_set_pass_key(db, db_index, pKey, nKey, CIPHER_WRITE_CTX); - - /* do stuff here to rewrite the database - ** 1. Create a transaction on the database - ** 2. Iterate through each page, reading it and then writing it. - ** 3. If that goes ok then commit and put ctx->rekey into ctx->key - ** note: don't deallocate rekey since it may be used in a subsequent iteration - */ - rc = sqlite3BtreeBeginTrans(pDb->pBt, 1, 0); /* begin write transaction */ - sqlite3PagerPagecount(pPager, &page_count); - for(pgno = 1; rc == SQLITE_OK && pgno <= (unsigned int)page_count; pgno++) { /* pgno's start at 1 see pager.c:pagerAcquire */ - if(!sqlite3pager_is_sj_pgno(pPager, pgno)) { /* skip this page (see pager.c:pagerAcquire for reasoning) */ - rc = sqlite3PagerGet(pPager, pgno, &page, 0); - if(rc == SQLITE_OK) { /* write page see pager_incr_changecounter for example */ - rc = sqlite3PagerWrite(page); - if(rc == SQLITE_OK) { - sqlite3PagerUnref(page); - } else { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); - } - } else { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: error %d occurred reading page %d", rc, pgno); - } - } - } - - /* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */ - if(rc == SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: committing"); - rc = sqlite3BtreeCommit(pDb->pBt); - sqlcipher_codec_key_copy(ctx, CIPHER_WRITE_CTX); - } else { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: rollback"); - sqlite3BtreeRollback(pDb->pBt, SQLITE_ABORT_ROLLBACK, 0); - } - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: leaving database mutex %p", db->mutex); - sqlite3_mutex_leave(db->mutex); - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: left database mutex %p", db->mutex); - } - return SQLITE_OK; - } - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: no key provided for db %s: rekey can't be used to decrypt an encrypted database", zDb); - return SQLITE_ERROR; -} - -void sqlcipherCodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) { - struct Db *pDb = &db->aDb[nDb]; - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecGetKey:db=%p, nDb=%d", db, nDb); - if( pDb->pBt ) { - codec_ctx *ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); - - if(ctx) { - /* pass back the keyspec from the codec, unless PRAGMA cipher_store_pass - is set or keyspec has not yet been derived, in which case pass - back the password key material */ - sqlcipher_codec_get_keyspec(ctx, zKey, nKey); - if(sqlcipher_codec_get_store_pass(ctx) == 1 || *zKey == NULL) { - sqlcipher_codec_get_pass(ctx, zKey, nKey); - } - } else { - *zKey = NULL; - *nKey = 0; - } - } -} - -/* - * Implementation of an "export" function that allows a caller - * to duplicate the main database to an attached database. This is intended - * as a conveneince for users who need to: - * - * 1. migrate from an non-encrypted database to an encrypted database - * 2. move from an encrypted database to a non-encrypted database - * 3. convert beween the various flavors of encrypted databases. - * - * This implementation is based heavily on the procedure and code used - * in vacuum.c, but is exposed as a function that allows export to any - * named attached database. - */ - -/* -** Finalize a prepared statement. If there was an error, store the -** text of the error message in *pzErrMsg. Return the result code. -** -** Based on vacuumFinalize from vacuum.c -*/ -static int sqlcipher_finalize(sqlite3 *db, sqlite3_stmt *pStmt, char **pzErrMsg){ - int rc; - rc = sqlite3VdbeFinalize((Vdbe*)pStmt); - if( rc ){ - sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); - } - return rc; -} - -/* -** Execute zSql on database db. Return an error code. -** -** Based on execSql from vacuum.c -*/ -static int sqlcipher_execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ - sqlite3_stmt *pStmt; - VVA_ONLY( int rc; ) - if( !zSql ){ - return SQLITE_NOMEM; - } - if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){ - sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); - return sqlite3_errcode(db); - } - VVA_ONLY( rc = ) sqlite3_step(pStmt); - assert( rc!=SQLITE_ROW ); - return sqlcipher_finalize(db, pStmt, pzErrMsg); -} - -/* -** Execute zSql on database db. The statement returns exactly -** one column. Execute this as SQL on the same database. -** -** Based on execExecSql from vacuum.c -*/ -static int sqlcipher_execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ - sqlite3_stmt *pStmt; - int rc; - - rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); - if( rc!=SQLITE_OK ) return rc; - - while( SQLITE_ROW==sqlite3_step(pStmt) ){ - rc = sqlcipher_execSql(db, pzErrMsg, (char*)sqlite3_column_text(pStmt, 0)); - if( rc!=SQLITE_OK ){ - sqlcipher_finalize(db, pStmt, pzErrMsg); - return rc; - } - } - - return sqlcipher_finalize(db, pStmt, pzErrMsg); -} - -/* - * copy database and schema from the main database to an attached database - * - * Based on sqlite3RunVacuum from vacuum.c -*/ -void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **argv) { - sqlite3 *db = sqlite3_context_db_handle(context); - const char* targetDb, *sourceDb; - int targetDb_idx = 0; - u64 saved_flags = db->flags; /* Saved value of the db->flags */ - u32 saved_mDbFlags = db->mDbFlags; /* Saved value of the db->mDbFlags */ - int saved_nChange = db->nChange; /* Saved value of db->nChange */ - int saved_nTotalChange = db->nTotalChange; /* Saved value of db->nTotalChange */ - u8 saved_mTrace = db->mTrace; /* Saved value of db->mTrace */ - int rc = SQLITE_OK; /* Return code from service routines */ - char *zSql = NULL; /* SQL statements */ - char *pzErrMsg = NULL; - - if(argc != 1 && argc != 2) { - rc = SQLITE_ERROR; - pzErrMsg = sqlite3_mprintf("invalid number of arguments (%d) passed to sqlcipher_export", argc); - goto end_of_export; - } - - if(sqlite3_value_type(argv[0]) == SQLITE_NULL) { - rc = SQLITE_ERROR; - pzErrMsg = sqlite3_mprintf("target database can't be NULL"); - goto end_of_export; - } - - targetDb = (const char*) sqlite3_value_text(argv[0]); - sourceDb = "main"; - - if(argc == 2) { - if(sqlite3_value_type(argv[1]) == SQLITE_NULL) { - rc = SQLITE_ERROR; - pzErrMsg = sqlite3_mprintf("target database can't be NULL"); - goto end_of_export; - } - sourceDb = (char *) sqlite3_value_text(argv[1]); - } - - - /* if the name of the target is not main, but the index returned is zero - there is a mismatch and we should not proceed */ - targetDb_idx = sqlcipher_find_db_index(db, targetDb); - if(targetDb_idx == 0 && targetDb != NULL && sqlite3_stricmp("main", targetDb) != 0) { - rc = SQLITE_ERROR; - pzErrMsg = sqlite3_mprintf("unknown database %s", targetDb); - goto end_of_export; - } - db->init.iDb = targetDb_idx; - - db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks; - db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum; - db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_Defensive | SQLITE_CountRows); - db->mTrace = 0; - - /* Query the schema of the main database. Create a mirror schema - ** in the temporary database. - */ - zSql = sqlite3_mprintf( - "SELECT sql " - " FROM %s.sqlite_schema WHERE type='table' AND name!='sqlite_sequence'" - " AND rootpage>0" - , sourceDb); - rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); - if( rc!=SQLITE_OK ) goto end_of_export; - sqlite3_free(zSql); - - zSql = sqlite3_mprintf( - "SELECT sql " - " FROM %s.sqlite_schema WHERE sql LIKE 'CREATE INDEX %%' " - , sourceDb); - rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); - if( rc!=SQLITE_OK ) goto end_of_export; - sqlite3_free(zSql); - - zSql = sqlite3_mprintf( - "SELECT sql " - " FROM %s.sqlite_schema WHERE sql LIKE 'CREATE UNIQUE INDEX %%'" - , sourceDb); - rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); - if( rc!=SQLITE_OK ) goto end_of_export; - sqlite3_free(zSql); - - /* Loop through the tables in the main database. For each, do - ** an "INSERT INTO rekey_db.xxx SELECT * FROM main.xxx;" to copy - ** the contents to the temporary database. - */ - zSql = sqlite3_mprintf( - "SELECT 'INSERT INTO %s.' || quote(name) " - "|| ' SELECT * FROM %s.' || quote(name) || ';'" - "FROM %s.sqlite_schema " - "WHERE type = 'table' AND name!='sqlite_sequence' " - " AND rootpage>0" - , targetDb, sourceDb, sourceDb); - rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); - if( rc!=SQLITE_OK ) goto end_of_export; - sqlite3_free(zSql); - - /* Copy over the contents of the sequence table - */ - zSql = sqlite3_mprintf( - "SELECT 'INSERT INTO %s.' || quote(name) " - "|| ' SELECT * FROM %s.' || quote(name) || ';' " - "FROM %s.sqlite_schema WHERE name=='sqlite_sequence';" - , targetDb, sourceDb, targetDb); - rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); - if( rc!=SQLITE_OK ) goto end_of_export; - sqlite3_free(zSql); - - /* Copy the triggers, views, and virtual tables from the main database - ** over to the temporary database. None of these objects has any - ** associated storage, so all we have to do is copy their entries - ** from the SQLITE_MASTER table. - */ - zSql = sqlite3_mprintf( - "INSERT INTO %s.sqlite_schema " - " SELECT type, name, tbl_name, rootpage, sql" - " FROM %s.sqlite_schema" - " WHERE type='view' OR type='trigger'" - " OR (type='table' AND rootpage=0)" - , targetDb, sourceDb); - rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execSql(db, &pzErrMsg, zSql); - if( rc!=SQLITE_OK ) goto end_of_export; - sqlite3_free(zSql); - - zSql = NULL; -end_of_export: - db->init.iDb = 0; - db->flags = saved_flags; - db->mDbFlags = saved_mDbFlags; - db->nChange = saved_nChange; - db->nTotalChange = saved_nTotalChange; - db->mTrace = saved_mTrace; - - if(zSql) sqlite3_free(zSql); - - if(rc) { - if(pzErrMsg != NULL) { - sqlite3_result_error(context, pzErrMsg, -1); - sqlite3DbFree(db, pzErrMsg); - } else { - sqlite3_result_error(context, sqlite3ErrStr(rc), -1); - } - } -} -#endif -/* END SQLCIPHER */ diff --git a/src/crypto.h b/src/crypto.h deleted file mode 100644 index 23003b814..000000000 --- a/src/crypto.h +++ /dev/null @@ -1,381 +0,0 @@ -/* -** SQLCipher -** crypto.h developed by Stephen Lombardo (Zetetic LLC) -** sjlombardo at zetetic dot net -** http://zetetic.net -** -** Copyright (c) 2008, ZETETIC LLC -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the ZETETIC LLC nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -*/ -/* BEGIN SQLCIPHER */ -#ifdef SQLITE_HAS_CODEC -#ifndef CRYPTO_H -#define CRYPTO_H - -#include "sqliteInt.h" -#include "btreeInt.h" -#include "pager.h" -#include "vdbeInt.h" - -#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) -#if defined(__ANDROID__) -#include -#elif defined(__APPLE__) -#include -#include -#endif -#endif - -#include - -#if defined(_WIN32) || defined(SQLITE_OS_WINRT) -#include /* amalgamator: dontcache */ -#else -#include /* amalgamator: dontcache */ -#endif - -#ifndef OMIT_MEMLOCK -#if defined(__unix__) || defined(__APPLE__) || defined(_AIX) -#include /* amalgamator: dontcache */ -#include /* amalgamator: dontcache */ -#include /* amalgamator: dontcache */ -#include /* amalgamator: dontcache */ -#endif -#endif - -#include "sqlcipher.h" - -/* extensions defined in pager.c */ -void *sqlcipherPagerGetCodec(Pager*); -void sqlcipherPagerSetCodec(Pager*, void *(*)(void*,void*,Pgno,int), void (*)(void*,int,int), void (*)(void*), void *); -int sqlite3pager_is_sj_pgno(Pager*, Pgno); -void sqlite3pager_error(Pager*, int); -void sqlite3pager_reset(Pager *pPager); -/* end extensions defined in pager.c */ - -#if !defined (SQLCIPHER_CRYPTO_CC) \ - && !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \ - && !defined (SQLCIPHER_CRYPTO_NSS) \ - && !defined (SQLCIPHER_CRYPTO_OPENSSL) -#define SQLCIPHER_CRYPTO_OPENSSL -#endif - -#define FILE_HEADER_SZ 16 - -#define CIPHER_XSTR(s) CIPHER_STR(s) -#define CIPHER_STR(s) #s - -#ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.6.1 -#endif - -#ifndef CIPHER_VERSION_BUILD -#define CIPHER_VERSION_BUILD community -#endif - -#define CIPHER_DECRYPT 0 -#define CIPHER_ENCRYPT 1 - -#define CIPHER_READ_CTX 0 -#define CIPHER_WRITE_CTX 1 -#define CIPHER_READWRITE_CTX 2 - -#ifndef PBKDF2_ITER -#define PBKDF2_ITER 256000 -#endif - -#define SQLCIPHER_FLAG_GET(FLAG,BIT) ((FLAG & BIT) != 0) -#define SQLCIPHER_FLAG_SET(FLAG,BIT) FLAG |= BIT -#define SQLCIPHER_FLAG_UNSET(FLAG,BIT) FLAG &= ~BIT - -/* possible flags for codec_ctx->flags */ -#define CIPHER_FLAG_HMAC (1 << 0) -#define CIPHER_FLAG_LE_PGNO (1 << 1) -#define CIPHER_FLAG_BE_PGNO (1 << 2) -#define CIPHER_FLAG_KEY_USED (1 << 3) -#define CIPHER_FLAG_HAS_KDF_SALT (1 << 4) - - -#ifndef DEFAULT_CIPHER_FLAGS -#define DEFAULT_CIPHER_FLAGS CIPHER_FLAG_HMAC | CIPHER_FLAG_LE_PGNO -#endif - - -/* by default, sqlcipher will use a reduced number of iterations to generate - the HMAC key / or transform a raw cipher key - */ -#ifndef FAST_PBKDF2_ITER -#define FAST_PBKDF2_ITER 2 -#endif - -/* this if a fixed random array that will be xor'd with the database salt to ensure that the - salt passed to the HMAC key derivation function is not the same as that used to derive - the encryption key. This can be overridden at compile time but it will make the resulting - binary incompatible with the default builds when using HMAC. A future version of SQLcipher - will likely allow this to be defined at runtime via pragma */ -#ifndef HMAC_SALT_MASK -#define HMAC_SALT_MASK 0x3a -#endif - -#ifndef CIPHER_MAX_IV_SZ -#define CIPHER_MAX_IV_SZ 16 -#endif - -#ifndef CIPHER_MAX_KEY_SZ -#define CIPHER_MAX_KEY_SZ 64 -#endif - - -/* -** Simple shared routines for converting hex char strings to binary data - */ -static int cipher_hex2int(char c) { - return (c>='0' && c<='9') ? (c)-'0' : - (c>='A' && c<='F') ? (c)-'A'+10 : - (c>='a' && c<='f') ? (c)-'a'+10 : 0; -} - -static void cipher_hex2bin(const unsigned char *hex, int sz, unsigned char *out){ - int i; - for(i = 0; i < sz; i += 2){ - out[i/2] = (cipher_hex2int(hex[i])<<4) | cipher_hex2int(hex[i+1]); - } -} - -static void cipher_bin2hex(const unsigned char* in, int sz, char *out) { - int i; - for(i=0; i < sz; i++) { - sqlite3_snprintf(3, out + (i*2), "%02x ", in[i]); - } -} - -static int cipher_isHex(const unsigned char *hex, int sz){ - int i; - for(i = 0; i < sz; i++) { - unsigned char c = hex[i]; - if ((c < '0' || c > '9') && - (c < 'A' || c > 'F') && - (c < 'a' || c > 'f')) { - return 0; - } - } - return 1; -} - -/* possible flags for simulating specific test conditions */ -#ifdef SQLCIPHER_TEST -#define TEST_FAIL_ENCRYPT 0x01 -#define TEST_FAIL_DECRYPT 0x02 -#define TEST_FAIL_MIGRATE 0x04 -unsigned int sqlcipher_get_test_flags(void); -void sqlcipher_set_test_flags(unsigned int); -int sqlcipher_get_test_rand(void); -void sqlcipher_set_test_rand(int); -int sqlcipher_get_test_fail(void); -#endif - -/* extensions defined in crypto_impl.c */ -/* the default implementation of SQLCipher uses a cipher_ctx - to keep track of read / write state separately. The following - struct and associated functions are defined here */ -typedef struct { - int derive_key; - int pass_sz; - unsigned char *key; - unsigned char *hmac_key; - unsigned char *pass; - char *keyspec; -} cipher_ctx; - - -typedef struct { - int store_pass; - int kdf_iter; - int fast_kdf_iter; - int kdf_salt_sz; - int key_sz; - int iv_sz; - int block_sz; - int page_sz; - int keyspec_sz; - int reserve_sz; - int hmac_sz; - int plaintext_header_sz; - int hmac_algorithm; - int kdf_algorithm; - unsigned int flags; - unsigned char *kdf_salt; - unsigned char *hmac_kdf_salt; - unsigned char *buffer; - Btree *pBt; - cipher_ctx *read_ctx; - cipher_ctx *write_ctx; - sqlcipher_provider *provider; - void *provider_ctx; -} codec_ctx ; - -/* crypto.c functions */ -int sqlcipher_codec_pragma(sqlite3*, int, Parse*, const char *, const char*); -int sqlcipherCodecAttach(sqlite3*, int, const void *, int); -void sqlcipherCodecGetKey(sqlite3*, int, void**, int*); -void sqlcipher_exportFunc(sqlite3_context *, int, sqlite3_value **); - -/* crypto_impl.c functions */ - -void sqlcipher_init_memmethods(void); - -/* activation and initialization */ -void sqlcipher_activate(void); -void sqlcipher_deactivate(void); - -int sqlcipher_codec_ctx_init(codec_ctx **, Db *, Pager *, const void *, int); -void sqlcipher_codec_ctx_free(codec_ctx **); -int sqlcipher_codec_key_derive(codec_ctx *); -int sqlcipher_codec_key_copy(codec_ctx *, int); - -/* page cipher implementation */ -int sqlcipher_page_cipher(codec_ctx *, int, Pgno, int, int, unsigned char *, unsigned char *); - -/* context setters & getters */ -void sqlcipher_codec_ctx_set_error(codec_ctx *, int); - -void sqlcipher_codec_get_pass(codec_ctx *, void **, int *); -int sqlcipher_codec_ctx_set_pass(codec_ctx *, const void *, int, int); -void sqlcipher_codec_get_keyspec(codec_ctx *, void **zKey, int *nKey); - -int sqlcipher_codec_ctx_set_pagesize(codec_ctx *, int); -int sqlcipher_codec_ctx_get_pagesize(codec_ctx *); -int sqlcipher_codec_ctx_get_reservesize(codec_ctx *); - -void sqlcipher_set_default_pagesize(int page_size); -int sqlcipher_get_default_pagesize(void); - -void sqlcipher_set_default_kdf_iter(int iter); -int sqlcipher_get_default_kdf_iter(void); -int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *, int); -int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx); - -int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int sz); -int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void **salt); - -int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *, int); -int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *); - -const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx); - -void* sqlcipher_codec_ctx_get_data(codec_ctx *); - -void sqlcipher_set_default_use_hmac(int use); -int sqlcipher_get_default_use_hmac(void); - -void sqlcipher_set_hmac_salt_mask(unsigned char mask); -unsigned char sqlcipher_get_hmac_salt_mask(void); - -int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use); -int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx); - -const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx); -int sqlcipher_codec_ctx_migrate(codec_ctx *ctx); -int sqlcipher_codec_add_random(codec_ctx *ctx, const char *data, int random_sz); -int sqlcipher_cipher_profile(sqlite3 *db, const char *destination); -int sqlcipher_codec_get_store_pass(codec_ctx *ctx); -void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey); -void sqlcipher_codec_set_store_pass(codec_ctx *ctx, int value); -int sqlcipher_codec_fips_status(codec_ctx *ctx); -const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx); - -int sqlcipher_set_default_plaintext_header_size(int size); -int sqlcipher_get_default_plaintext_header_size(void); -int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size); -int sqlcipher_codec_ctx_get_plaintext_header_size(codec_ctx *ctx); - -int sqlcipher_set_default_hmac_algorithm(int algorithm); -int sqlcipher_get_default_hmac_algorithm(void); -int sqlcipher_codec_ctx_set_hmac_algorithm(codec_ctx *ctx, int algorithm); -int sqlcipher_codec_ctx_get_hmac_algorithm(codec_ctx *ctx); - -int sqlcipher_set_default_kdf_algorithm(int algorithm); -int sqlcipher_get_default_kdf_algorithm(void); -int sqlcipher_codec_ctx_set_kdf_algorithm(codec_ctx *ctx, int algorithm); -int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx); - -void sqlcipher_set_mem_security(int); -int sqlcipher_get_mem_security(void); - -int sqlcipher_find_db_index(sqlite3 *db, const char *zDb); - -int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *); - -int sqlcipher_set_log(const char *destination); -void sqlcipher_set_log_level(unsigned int level); -unsigned int sqlcipher_get_log_level(); -void sqlcipher_set_log_source(unsigned int source); -unsigned int sqlcipher_get_log_source(); - -char *sqlcipher_get_log_level_str(unsigned int); -char *sqlcipher_get_log_source_str(unsigned int); - -#define SQLCIPHER_LOG_NONE 0x00 -#define SQLCIPHER_LOG_ERROR 0x01 -#define SQLCIPHER_LOG_WARN 0x02 -#define SQLCIPHER_LOG_INFO 0x04 -#define SQLCIPHER_LOG_DEBUG 0x08 -#define SQLCIPHER_LOG_TRACE 0x10 -#define SQLCIPHER_LOG_ALL 0xffffffff - -#define SQLCIPHER_LOG_CORE 0x01 -#define SQLCIPHER_LOG_MEMORY 0x02 -#define SQLCIPHER_LOG_MUTEX 0x04 -#define SQLCIPHER_LOG_PROVIDER 0x08 - -#ifdef SQLCIPHER_OMIT_LOG -#define sqlcipher_log(level, source, message, ...) -#else -void sqlcipher_log(unsigned int level, unsigned int source, const char *message, ...); -#endif - -void sqlcipher_vdbe_return_string(Parse*, const char*, const char*, int); - -#ifdef CODEC_DEBUG_PAGEDATA -#define CODEC_HEXDUMP(DESC,BUFFER,LEN) \ - { \ - int __pctr; \ - printf(DESC); \ - for(__pctr=0; __pctr < LEN; __pctr++) { \ - if(__pctr % 16 == 0) printf("\n%05x: ",__pctr); \ - printf("%02x ",((unsigned char*) BUFFER)[__pctr]); \ - } \ - printf("\n"); \ - fflush(stdout); \ - } -#else -#define CODEC_HEXDUMP(DESC,BUFFER,LEN) -#endif - -#endif -#endif -/* END SQLCIPHER */ - diff --git a/src/crypto_cc.c b/src/crypto_cc.c index a80083071..f5942c298 100644 --- a/src/crypto_cc.c +++ b/src/crypto_cc.c @@ -31,7 +31,6 @@ /* BEGIN SQLCIPHER */ #ifdef SQLITE_HAS_CODEC #ifdef SQLCIPHER_CRYPTO_CC -#include "crypto.h" #include "sqlcipher.h" #include #include diff --git a/src/crypto_nss.c b/src/crypto_nss.c index 3c8bcd3f6..167ede904 100644 --- a/src/crypto_nss.c +++ b/src/crypto_nss.c @@ -31,7 +31,6 @@ /* BEGIN SQLCIPHER */ #ifdef SQLITE_HAS_CODEC #ifdef SQLCIPHER_CRYPTO_NSS -#include "crypto.h" #include "sqlcipher.h" #include #include diff --git a/src/crypto_openssl.c b/src/crypto_openssl.c index fc57221f4..032830242 100644 --- a/src/crypto_openssl.c +++ b/src/crypto_openssl.c @@ -32,7 +32,6 @@ #ifdef SQLITE_HAS_CODEC #ifdef SQLCIPHER_CRYPTO_OPENSSL #include "sqliteInt.h" -#include "crypto.h" #include "sqlcipher.h" #include /* amalgamator: dontcache */ #include /* amalgamator: dontcache */ diff --git a/src/crypto_impl.c b/src/sqlcipher.c similarity index 52% rename from src/crypto_impl.c rename to src/sqlcipher.c index 6bd190319..b7564b8bf 100644 --- a/src/crypto_impl.c +++ b/src/sqlcipher.c @@ -1,8 +1,8 @@ /* ** SQLCipher -** http://sqlcipher.net +** http://zetetic.net ** -** Copyright (c) 2008 - 2013, ZETETIC LLC +** Copyright (c) 2008-2024, ZETETIC LLC ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -31,26 +31,207 @@ /* BEGIN SQLCIPHER */ #ifdef SQLITE_HAS_CODEC +#include "sqliteInt.h" +#include "btreeInt.h" +#include "pager.h" +#include "vdbeInt.h" + +#if !defined(SQLCIPHER_OMIT_LOG_DEVICE) +#if defined(__ANDROID__) +#include +#elif defined(__APPLE__) +#include +#include +#endif +#endif + +#include + +#if defined(_WIN32) || defined(SQLITE_OS_WINRT) +#include /* amalgamator: dontcache */ +#else +#include /* amalgamator: dontcache */ +#endif + +#ifndef OMIT_MEMLOCK +#if defined(__unix__) || defined(__APPLE__) || defined(_AIX) +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#include /* amalgamator: dontcache */ +#endif +#endif + +#include #include "sqlcipher.h" -#include "crypto.h" -#ifdef SQLCIPHER_TEST -static volatile unsigned int cipher_test_flags = 0; -unsigned int sqlcipher_get_test_flags() { - return cipher_test_flags; +/* extensions defined in pager.c */ +void *sqlcipherPagerGetCodec(Pager*); +void sqlcipherPagerSetCodec(Pager*, void *(*)(void*,void*,Pgno,int), void (*)(void*,int,int), void (*)(void*), void *); +int sqlite3pager_is_sj_pgno(Pager*, Pgno); +void sqlite3pager_error(Pager*, int); +void sqlite3pager_reset(Pager *pPager); +/* end extensions defined in pager.c */ + +#if !defined (SQLCIPHER_CRYPTO_CC) \ + && !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \ + && !defined (SQLCIPHER_CRYPTO_NSS) \ + && !defined (SQLCIPHER_CRYPTO_OPENSSL) +#define SQLCIPHER_CRYPTO_OPENSSL +#endif + +#define FILE_HEADER_SZ 16 + +#define CIPHER_XSTR(s) CIPHER_STR(s) +#define CIPHER_STR(s) #s + +#ifndef CIPHER_VERSION_NUMBER +#define CIPHER_VERSION_NUMBER 4.6.1 +#endif + +#ifndef CIPHER_VERSION_BUILD +#define CIPHER_VERSION_BUILD community +#endif + +#define CIPHER_DECRYPT 0 +#define CIPHER_ENCRYPT 1 + +#define CIPHER_READ_CTX 0 +#define CIPHER_WRITE_CTX 1 +#define CIPHER_READWRITE_CTX 2 + +#ifndef PBKDF2_ITER +#define PBKDF2_ITER 256000 +#endif + +#define SQLCIPHER_FLAG_GET(FLAG,BIT) ((FLAG & BIT) != 0) +#define SQLCIPHER_FLAG_SET(FLAG,BIT) FLAG |= BIT +#define SQLCIPHER_FLAG_UNSET(FLAG,BIT) FLAG &= ~BIT + +/* possible flags for codec_ctx->flags */ +#define CIPHER_FLAG_HMAC (1 << 0) +#define CIPHER_FLAG_LE_PGNO (1 << 1) +#define CIPHER_FLAG_BE_PGNO (1 << 2) +#define CIPHER_FLAG_KEY_USED (1 << 3) +#define CIPHER_FLAG_HAS_KDF_SALT (1 << 4) + + +#ifndef DEFAULT_CIPHER_FLAGS +#define DEFAULT_CIPHER_FLAGS CIPHER_FLAG_HMAC | CIPHER_FLAG_LE_PGNO +#endif + + +/* by default, sqlcipher will use a reduced number of iterations to generate + the HMAC key / or transform a raw cipher key + */ +#ifndef FAST_PBKDF2_ITER +#define FAST_PBKDF2_ITER 2 +#endif + +/* this if a fixed random array that will be xor'd with the database salt to ensure that the + salt passed to the HMAC key derivation function is not the same as that used to derive + the encryption key. This can be overridden at compile time but it will make the resulting + binary incompatible with the default builds when using HMAC. A future version of SQLcipher + will likely allow this to be defined at runtime via pragma */ +#ifndef HMAC_SALT_MASK +#define HMAC_SALT_MASK 0x3a +#endif + +#ifndef CIPHER_MAX_IV_SZ +#define CIPHER_MAX_IV_SZ 16 +#endif + +#ifndef CIPHER_MAX_KEY_SZ +#define CIPHER_MAX_KEY_SZ 64 +#endif + + +/* +** Simple shared routines for converting hex char strings to binary data + */ +static int cipher_hex2int(char c) { + return (c>='0' && c<='9') ? (c)-'0' : + (c>='A' && c<='F') ? (c)-'A'+10 : + (c>='a' && c<='f') ? (c)-'a'+10 : 0; } -void sqlcipher_set_test_flags(unsigned int flags) { - cipher_test_flags = flags; + +static void cipher_hex2bin(const unsigned char *hex, int sz, unsigned char *out){ + int i; + for(i = 0; i < sz; i += 2){ + out[i/2] = (cipher_hex2int(hex[i])<<4) | cipher_hex2int(hex[i+1]); + } } +static void cipher_bin2hex(const unsigned char* in, int sz, char *out) { + int i; + for(i=0; i < sz; i++) { + sqlite3_snprintf(3, out + (i*2), "%02x ", in[i]); + } +} + +static int cipher_isHex(const unsigned char *hex, int sz){ + int i; + for(i = 0; i < sz; i++) { + unsigned char c = hex[i]; + if ((c < '0' || c > '9') && + (c < 'A' || c > 'F') && + (c < 'a' || c > 'f')) { + return 0; + } + } + return 1; +} + +/* the default implementation of SQLCipher uses a cipher_ctx + to keep track of read / write state separately. The following + struct and associated functions are defined here */ +typedef struct { + int derive_key; + int pass_sz; + unsigned char *key; + unsigned char *hmac_key; + unsigned char *pass; + char *keyspec; +} cipher_ctx; + + +typedef struct { + int store_pass; + int kdf_iter; + int fast_kdf_iter; + int kdf_salt_sz; + int key_sz; + int iv_sz; + int block_sz; + int page_sz; + int keyspec_sz; + int reserve_sz; + int hmac_sz; + int plaintext_header_sz; + int hmac_algorithm; + int kdf_algorithm; + unsigned int flags; + unsigned char *kdf_salt; + unsigned char *hmac_kdf_salt; + unsigned char *buffer; + Btree *pBt; + cipher_ctx *read_ctx; + cipher_ctx *write_ctx; + sqlcipher_provider *provider; + void *provider_ctx; +} codec_ctx ; + + +#ifdef SQLCIPHER_TEST +/* possible flags for simulating specific test conditions */ +#define TEST_FAIL_ENCRYPT 0x01 +#define TEST_FAIL_DECRYPT 0x02 +#define TEST_FAIL_MIGRATE 0x04 + +static volatile unsigned int cipher_test_flags = 0; static volatile int cipher_test_rand = 0; -int sqlcipher_get_test_rand() { - return cipher_test_rand; -} -void sqlcipher_set_test_rand(int rand) { - cipher_test_rand = rand; -} -int sqlcipher_get_test_fail() { + +static int sqlcipher_get_test_fail() { int x; /* if cipher_test_rand is not set to a non-zero value always fail (return true) */ @@ -61,13 +242,11 @@ int sqlcipher_get_test_fail() { } #endif -/* Generate code to return a string value */ - static volatile unsigned int default_flags = DEFAULT_CIPHER_FLAGS; static volatile unsigned char hmac_salt_mask = HMAC_SALT_MASK; static volatile int default_kdf_iter = PBKDF2_ITER; static volatile int default_page_size = 4096; -static volatile int default_plaintext_header_sz = 0; +static volatile int default_plaintext_header_size = 0; static volatile int default_hmac_algorithm = SQLCIPHER_HMAC_SHA512; static volatile int default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA512; static volatile int sqlcipher_mem_security_on = 0; @@ -89,6 +268,69 @@ sqlite3_mutex* sqlcipher_mutex(int mutex) { return sqlcipher_static_mutex[mutex]; } +static void sqlcipher_mlock(void *ptr, sqlite_uint64 sz) { +#ifndef OMIT_MEMLOCK +#if defined(__unix__) || defined(__APPLE__) + int rc; + unsigned long pagesize = sysconf(_SC_PAGESIZE); + unsigned long offset = (unsigned long) ptr % pagesize; + + if(ptr == NULL || sz == 0) return; + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); + rc = mlock(ptr - offset, sz + offset); + if(rc!=0) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: mlock() returned %d errno=%d", rc, errno); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + } +#elif defined(_WIN32) +#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) + int rc; + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: calling VirtualLock(%p,%d)", ptr, sz); + rc = VirtualLock(ptr, sz); + if(rc==0) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: VirtualLock() returned %d LastError=%d", rc, GetLastError()); + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + } +#endif +#endif +#endif +} + +static void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { +#ifndef OMIT_MEMLOCK +#if defined(__unix__) || defined(__APPLE__) + int rc; + unsigned long pagesize = sysconf(_SC_PAGESIZE); + unsigned long offset = (unsigned long) ptr % pagesize; + + if(ptr == NULL || sz == 0) return; + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); + rc = munlock(ptr - offset, sz + offset); + if(rc!=0) { + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); + } +#elif defined(_WIN32) +#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) + int rc; + + if(ptr == NULL || sz == 0) return; + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: calling VirtualUnlock(%p,%d)", ptr, sz); + rc = VirtualUnlock(ptr, sz); + + /* because memory allocations may be made from the same individual page, it is possible for VirtualUnlock to be called + * multiple times for the same page. Subsequent calls will return an error, but this can be safely ignored (i.e. because + * the previous call for that page unlocked the memory already). Log an info level event only in that case. */ + if(!rc) { + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); + } +#endif +#endif +#endif +} + static int sqlcipher_mem_init(void *pAppData) { return default_mem_methods.xInit(pAppData); } @@ -194,7 +436,7 @@ sqlcipher_provider* sqlcipher_get_provider() { return default_provider; } -void sqlcipher_activate() { +static void sqlcipher_activate() { sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_activate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_activate: entered static master mutex"); @@ -264,7 +506,7 @@ void sqlcipher_activate() { sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_activate: left static master mutex"); } -void sqlcipher_deactivate() { +static void sqlcipher_deactivate() { sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: entering static master mutex"); sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: entered static master mutex"); @@ -349,69 +591,6 @@ int sqlcipher_memcmp(const void *v0, const void *v1, int len) { return (result != 0); } -void sqlcipher_mlock(void *ptr, sqlite_uint64 sz) { -#ifndef OMIT_MEMLOCK -#if defined(__unix__) || defined(__APPLE__) - int rc; - unsigned long pagesize = sysconf(_SC_PAGESIZE); - unsigned long offset = (unsigned long) ptr % pagesize; - - if(ptr == NULL || sz == 0) return; - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu", ptr - offset, sz + offset, pagesize); - rc = mlock(ptr - offset, sz + offset); - if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: mlock() returned %d errno=%d", rc, errno); - sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); - } -#elif defined(_WIN32) -#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) - int rc; - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: calling VirtualLock(%p,%d)", ptr, sz); - rc = VirtualLock(ptr, sz); - if(rc==0) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: VirtualLock() returned %d LastError=%d", rc, GetLastError()); - sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: VirtualLock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); - } -#endif -#endif -#endif -} - -void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { -#ifndef OMIT_MEMLOCK -#if defined(__unix__) || defined(__APPLE__) - int rc; - unsigned long pagesize = sysconf(_SC_PAGESIZE); - unsigned long offset = (unsigned long) ptr % pagesize; - - if(ptr == NULL || sz == 0) return; - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: calling munlock(%p,%lu)", ptr - offset, sz + offset); - rc = munlock(ptr - offset, sz + offset); - if(rc!=0) { - sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); - } -#elif defined(_WIN32) -#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) - int rc; - - if(ptr == NULL || sz == 0) return; - - sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: calling VirtualUnlock(%p,%d)", ptr, sz); - rc = VirtualUnlock(ptr, sz); - - /* because memory allocations may be made from the same individual page, it is possible for VirtualUnlock to be called - * multiple times for the same page. Subsequent calls will return an error, but this can be safely ignored (i.e. because - * the previous call for that page unlocked the memory already). Log an info level event only in that case. */ - if(!rc) { - sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: VirtualUnlock(%p,%d) returned %d LastError=%d", ptr, sz, rc, GetLastError()); - } -#endif -#endif -#endif -} - /** * Free and wipe memory. Uses SQLites internal sqlite3_free so that memory * can be countend and memory leak detection works in the test suite. @@ -496,7 +675,7 @@ static int sqlcipher_codec_ctx_reserve_setup(codec_ctx *ctx) { ctx->hmac_sz = ctx->provider->get_hmac_sz(ctx->provider_ctx, ctx->hmac_algorithm); - if(sqlcipher_codec_ctx_get_use_hmac(ctx)) + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC)) reserve += ctx->hmac_sz; /* if reserve will include hmac, update that size */ /* calculate the amount of reserve needed in even increments of the cipher block size */ @@ -602,19 +781,6 @@ static int sqlcipher_cipher_ctx_set_keyspec(codec_ctx *ctx, cipher_ctx *c_ctx, c return SQLITE_OK; } -int sqlcipher_codec_get_store_pass(codec_ctx *ctx) { - return ctx->store_pass; -} - -void sqlcipher_codec_set_store_pass(codec_ctx *ctx, int value) { - ctx->store_pass = value; -} - -void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey) { - *zKey = ctx->read_ctx->pass; - *nKey = ctx->read_ctx->pass_sz; -} - static void sqlcipher_set_derive_key(codec_ctx *ctx, int derive) { if(ctx->read_ctx != NULL) ctx->read_ctx->derive_key = derive; if(ctx->write_ctx != NULL) ctx->write_ctx->derive_key = derive; @@ -641,7 +807,7 @@ static int sqlcipher_cipher_ctx_set_pass(cipher_ctx *ctx, const void *zKey, int return SQLITE_OK; } -int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nKey, int for_ctx) { +static int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nKey, int for_ctx) { cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx; int rc; @@ -662,59 +828,26 @@ int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nKey, int return SQLITE_OK; } -const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx) { - return ctx->provider->get_cipher(ctx->provider_ctx); -} - -/* set the global default KDF iteration */ -void sqlcipher_set_default_kdf_iter(int iter) { - default_kdf_iter = iter; -} - -int sqlcipher_get_default_kdf_iter() { - return default_kdf_iter; -} - -int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *ctx, int kdf_iter) { +static int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *ctx, int kdf_iter) { ctx->kdf_iter = kdf_iter; sqlcipher_set_derive_key(ctx, 1); return SQLITE_OK; } -int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx) { - return ctx->kdf_iter; -} - -int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *ctx, int fast_kdf_iter) { +static int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *ctx, int fast_kdf_iter) { ctx->fast_kdf_iter = fast_kdf_iter; sqlcipher_set_derive_key(ctx, 1); return SQLITE_OK; } -int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *ctx) { - return ctx->fast_kdf_iter; -} - /* set the global default flag for HMAC */ -void sqlcipher_set_default_use_hmac(int use) { +static void sqlcipher_set_default_use_hmac(int use) { if(use) SQLCIPHER_FLAG_SET(default_flags, CIPHER_FLAG_HMAC); else SQLCIPHER_FLAG_UNSET(default_flags,CIPHER_FLAG_HMAC); } -int sqlcipher_get_default_use_hmac() { - return SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC); -} - -void sqlcipher_set_hmac_salt_mask(unsigned char mask) { - hmac_salt_mask = mask; -} - -unsigned char sqlcipher_get_hmac_salt_mask() { - return hmac_salt_mask; -} - /* set the codec flag for whether this individual database should be using hmac */ -int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use) { +static int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use) { if(use) { SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HMAC); } else { @@ -724,21 +857,12 @@ int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use) { return sqlcipher_codec_ctx_reserve_setup(ctx); } -int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx) { - return SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC); -} - /* the length of plaintext header size must be: * 1. greater than or equal to zero * 2. a multiple of the cipher block size * 3. less than the usable size of the first database page */ -int sqlcipher_set_default_plaintext_header_size(int size) { - default_plaintext_header_sz = size; - return SQLITE_OK; -} - -int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size) { +static int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size) { if(size >= 0 && ctx->block_sz > 0 && (size % ctx->block_sz) == 0 && size < (ctx->page_sz - ctx->reserve_sz)) { ctx->plaintext_header_sz = size; return SQLITE_OK; @@ -748,66 +872,22 @@ int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size) { return SQLITE_ERROR; } -int sqlcipher_get_default_plaintext_header_size() { - return default_plaintext_header_sz; -} - -int sqlcipher_codec_ctx_get_plaintext_header_size(codec_ctx *ctx) { - return ctx->plaintext_header_sz; -} - -/* manipulate HMAC algorithm */ -int sqlcipher_set_default_hmac_algorithm(int algorithm) { - default_hmac_algorithm = algorithm; - return SQLITE_OK; -} - -int sqlcipher_codec_ctx_set_hmac_algorithm(codec_ctx *ctx, int algorithm) { +static int sqlcipher_codec_ctx_set_hmac_algorithm(codec_ctx *ctx, int algorithm) { ctx->hmac_algorithm = algorithm; return sqlcipher_codec_ctx_reserve_setup(ctx); } -int sqlcipher_get_default_hmac_algorithm() { - return default_hmac_algorithm; -} - -int sqlcipher_codec_ctx_get_hmac_algorithm(codec_ctx *ctx) { - return ctx->hmac_algorithm; -} - -/* manipulate KDF algorithm */ -int sqlcipher_set_default_kdf_algorithm(int algorithm) { - default_kdf_algorithm = algorithm; - return SQLITE_OK; -} - -int sqlcipher_codec_ctx_set_kdf_algorithm(codec_ctx *ctx, int algorithm) { +static int sqlcipher_codec_ctx_set_kdf_algorithm(codec_ctx *ctx, int algorithm) { ctx->kdf_algorithm = algorithm; return SQLITE_OK; } -int sqlcipher_get_default_kdf_algorithm() { - return default_kdf_algorithm; -} - -int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx) { - return ctx->kdf_algorithm; -} - -void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) { +static void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) { sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_set_error %d", error); sqlite3pager_error(ctx->pBt->pBt->pPager, error); ctx->pBt->pBt->db->errCode = error; } -int sqlcipher_codec_ctx_get_reservesize(codec_ctx *ctx) { - return ctx->reserve_sz; -} - -void* sqlcipher_codec_ctx_get_data(codec_ctx *ctx) { - return ctx->buffer; -} - static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { sqlite3_file *fd = sqlite3PagerFile(ctx->pBt->pBt->pPager); @@ -828,7 +908,7 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { return SQLITE_OK; } -int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int size) { +static int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int size) { if(size >= ctx->kdf_salt_sz) { memcpy(ctx->kdf_salt, salt, ctx->kdf_salt_sz); SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT); @@ -838,7 +918,7 @@ int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int si return SQLITE_ERROR; } -int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) { +static int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) { int rc = SQLITE_OK; if(!SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT)) { if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) { @@ -850,12 +930,7 @@ int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) { return rc; } -void sqlcipher_codec_get_keyspec(codec_ctx *ctx, void **zKey, int *nKey) { - *zKey = ctx->read_ctx->keyspec; - *nKey = ctx->keyspec_sz; -} - -int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { +static int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { if(!((size != 0) && ((size & (size - 1)) == 0)) || size < 512 || size > 65536) { sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "cipher_page_size not a power of 2 and between 512 and 65536 inclusive"); return SQLITE_ERROR; @@ -873,35 +948,7 @@ int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { return SQLITE_OK; } -int sqlcipher_codec_ctx_get_pagesize(codec_ctx *ctx) { - return ctx->page_sz; -} - -void sqlcipher_set_default_pagesize(int page_size) { - default_page_size = page_size; -} - -int sqlcipher_get_default_pagesize() { - return default_page_size; -} - -void sqlcipher_set_mem_security(int on) { - /* memory security can only be enabled, not disabled */ - if(on) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_set_mem_security: on"); - sqlcipher_mem_security_on = on; - } -} - -int sqlcipher_get_mem_security() { - /* only report that memory security is enabled if pragma cipher_memory_security is ON and - SQLCipher's allocator/deallocator was run at least one timecurrently used */ - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_get_mem_security: sqlcipher_mem_security_on = %d, sqlcipher_mem_executed = %d", sqlcipher_mem_security_on, sqlcipher_mem_executed); - return sqlcipher_mem_security_on && sqlcipher_mem_executed; -} - - -int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const void *zKey, int nKey) { +static int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const void *zKey, int nKey) { int rc; codec_ctx *ctx; @@ -1003,8 +1050,8 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi } /* setup the default plaintext header size */ - if((rc = sqlcipher_codec_ctx_set_plaintext_header_size(ctx, default_plaintext_header_sz)) != SQLITE_OK) { - sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_plaintext_header_size with %d", rc, default_plaintext_header_sz); + if((rc = sqlcipher_codec_ctx_set_plaintext_header_size(ctx, default_plaintext_header_size)) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_init: error %d setting sqlcipher_codec_ctx_set_plaintext_header_size with %d", rc, default_plaintext_header_size); return rc; } @@ -1037,7 +1084,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const voi * Free and wipe memory associated with a cipher_ctx, including the allocated * read_ctx and write_ctx. */ -void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { +static void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { codec_ctx *ctx = *iCtx; sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "codec_ctx_free: iCtx=%p", iCtx); if(ctx->kdf_salt) sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz); @@ -1098,7 +1145,7 @@ static int sqlcipher_page_hmac(codec_ctx *ctx, cipher_ctx *c_ctx, Pgno pgno, uns * in - pointer to input bytes * out - pouter to output bytes */ -int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int page_sz, unsigned char *in, unsigned char *out) { +static int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int page_sz, unsigned char *in, unsigned char *out) { cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx; unsigned char *iv_in, *iv_out, *hmac_in, *hmac_out, *out_start; int size; @@ -1267,7 +1314,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { return SQLITE_ERROR; } -int sqlcipher_codec_key_derive(codec_ctx *ctx) { +static int sqlcipher_codec_key_derive(codec_ctx *ctx) { /* derive key on first use if necessary */ if(ctx->read_ctx->derive_key) { if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->read_ctx) != SQLITE_OK) { @@ -1291,7 +1338,7 @@ int sqlcipher_codec_key_derive(codec_ctx *ctx) { } } - /* TODO: wipe and free passphrase after key derivation */ + /* wipe and free passphrase after key derivation */ if(ctx->store_pass != 1) { sqlcipher_cipher_ctx_set_pass(ctx->read_ctx, NULL, 0); sqlcipher_cipher_ctx_set_pass(ctx->write_ctx, NULL, 0); @@ -1300,7 +1347,7 @@ int sqlcipher_codec_key_derive(codec_ctx *ctx) { return SQLITE_OK; } -int sqlcipher_codec_key_copy(codec_ctx *ctx, int source) { +static int sqlcipher_codec_key_copy(codec_ctx *ctx, int source) { if(source == CIPHER_READ_CTX) { return sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx); } else { @@ -1308,11 +1355,6 @@ int sqlcipher_codec_key_copy(codec_ctx *ctx, int source) { } } -const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx) { - return ctx->provider->get_provider_name(ctx->provider_ctx); -} - - static int sqlcipher_check_connection(const char *filename, char *key, int key_sz, char *sql, int *user_version, char** journal_mode) { int rc; sqlite3 *db = NULL; @@ -1360,7 +1402,7 @@ static int sqlcipher_check_connection(const char *filename, char *key, int key_s return rc; } -int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *column) { +static int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *column) { Pgno page = 1; int rc = 0; char *result; @@ -1436,7 +1478,7 @@ int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *col return SQLITE_OK; } -int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { +static int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { int i, pass_sz, keyspec_sz, nRes, user_version, rc, oflags; Db *pDb = 0; sqlite3 *db = ctx->pBt->db; @@ -1525,7 +1567,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { } #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_MIGRATE) > 0) { + if((cipher_test_flags & TEST_FAIL_MIGRATE) > 0) { rc = SQLITE_ERROR; sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipher_codec_ctx_migrate: simulated migrate failure, error code %d", rc); goto handle_error; @@ -1652,7 +1694,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { return rc; } -int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz){ +static int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz){ const char *suffix = &zRight[random_sz-1]; int n = random_sz - 3; /* adjust for leading x' and tailing ' */ if (n > 0 && @@ -1698,7 +1740,7 @@ static int sqlcipher_profile_callback(unsigned int trace, void *file, void *stmt } #endif -int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ +static int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ #if defined(SQLITE_OMIT_TRACE) return SQLITE_ERROR; #else @@ -1725,15 +1767,7 @@ int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){ #endif } -int sqlcipher_codec_fips_status(codec_ctx *ctx) { - return ctx->provider->fips_status(ctx->provider_ctx); -} - -const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) { - return ctx->provider->get_provider_version(ctx->provider_ctx); -} - -char *sqlcipher_get_log_level_str(unsigned int level) { +static char *sqlcipher_get_log_level_str(unsigned int level) { switch(level) { case SQLCIPHER_LOG_ERROR: return "ERROR"; @@ -1751,7 +1785,7 @@ char *sqlcipher_get_log_level_str(unsigned int level) { return "NONE"; } -char *sqlcipher_get_log_source_str(unsigned int source) { +static char *sqlcipher_get_log_source_str(unsigned int source) { switch(source) { case SQLCIPHER_LOG_NONE: return "NONE"; @@ -1857,23 +1891,7 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, } #endif -void sqlcipher_set_log_level(unsigned int level) { - sqlcipher_log_level = level; -} - -unsigned int sqlcipher_get_log_level() { - return sqlcipher_log_level; -} - -void sqlcipher_set_log_source(unsigned int source) { - sqlcipher_log_source = source; -} - -unsigned int sqlcipher_get_log_source() { - return sqlcipher_log_source; -} - -int sqlcipher_set_log(const char *destination){ +static int sqlcipher_set_log(const char *destination){ #ifdef SQLCIPHER_OMIT_LOG return SQLITE_ERROR; #else @@ -1904,5 +1922,1255 @@ int sqlcipher_set_log(const char *destination){ #endif } +#ifdef SQLCIPHER_EXT +#include "sqlcipher_ext.h" +#endif + +static void sqlcipher_vdbe_return_string(Parse *pParse, const char *zLabel, const char *value, int value_type){ + Vdbe *v = sqlite3GetVdbe(pParse); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLabel, SQLITE_STATIC); + sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, value, value_type); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); +} + +static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ctx) { + int rc; + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d", ctx->page_sz, ctx->reserve_sz); + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: entering database mutex %p", db->mutex); + sqlite3_mutex_enter(db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: entered database mutex %p", db->mutex); + db->nextPagesize = ctx->page_sz; + + /* before forcing the page size we need to unset the BTS_PAGESIZE_FIXED flag, else + sqliteBtreeSetPageSize will block the change */ + pDb->pBt->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; + rc = sqlite3BtreeSetPageSize(pDb->pBt, ctx->page_sz, ctx->reserve_sz, 0); + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize returned %d", rc); + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: leaving database mutex %p", db->mutex); + sqlite3_mutex_leave(db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "codec_set_btree_to_codec_pagesize: left database mutex %p", db->mutex); + + return rc; +} + +static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) { + struct Db *pDb = &db->aDb[nDb]; + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "codec_set_pass_key: db=%p nDb=%d for_ctx=%d", db, nDb, for_ctx); + if(pDb->pBt) { + codec_ctx *ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); + + if(ctx) { + return sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, for_ctx); + } else { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "codec_set_pass_key: error ocurred fetching codec from pager on db %d", nDb); + return SQLITE_ERROR; + } + } + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "codec_set_pass_key: no btree present on db %d", nDb); + return SQLITE_ERROR; +} + +int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLeft, const char *zRight) { + struct Db *pDb = &db->aDb[iDb]; + codec_ctx *ctx = NULL; + int rc; + + if(pDb->pBt) { + ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); + } + + if(sqlite3_stricmp(zLeft, "key") !=0 && sqlite3_stricmp(zLeft, "rekey") != 0) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); + } + +#ifdef SQLCIPHER_EXT + if(sqlcipher_ext_pragma(db, iDb, pParse, zLeft, zRight)) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: PRAGMA handled by sqlcipher_ext_pragma"); + } else +#endif +#ifdef SQLCIPHER_TEST + if( sqlite3_stricmp(zLeft,"cipher_test_on")==0 ){ + if( zRight ) { + if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { + SQLCIPHER_FLAG_SET(cipher_test_flags,TEST_FAIL_ENCRYPT); + } else + if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { + SQLCIPHER_FLAG_SET(cipher_test_flags,TEST_FAIL_DECRYPT); + } else + if(sqlite3_stricmp(zRight, "fail_migrate")==0) { + SQLCIPHER_FLAG_SET(cipher_test_flags,TEST_FAIL_MIGRATE); + } + } + } else + if( sqlite3_stricmp(zLeft,"cipher_test_off")==0 ){ + if( zRight ) { + if(sqlite3_stricmp(zRight, "fail_encrypt")==0) { + SQLCIPHER_FLAG_UNSET(cipher_test_flags,TEST_FAIL_ENCRYPT); + } else + if(sqlite3_stricmp(zRight, "fail_decrypt")==0) { + SQLCIPHER_FLAG_UNSET(cipher_test_flags,TEST_FAIL_DECRYPT); + } else + if(sqlite3_stricmp(zRight, "fail_migrate")==0) { + SQLCIPHER_FLAG_UNSET(cipher_test_flags,TEST_FAIL_MIGRATE); + } + } + } else + if( sqlite3_stricmp(zLeft,"cipher_test")==0 ){ + char *flags = sqlite3_mprintf("%u", cipher_test_flags); + sqlcipher_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); + }else + if( sqlite3_stricmp(zLeft,"cipher_test_rand")==0 ){ + if( zRight ) { + int rand = atoi(zRight); + cipher_test_rand = rand; + } else { + char *rand = sqlite3_mprintf("%d", cipher_test_rand); + sqlcipher_vdbe_return_string(pParse, "cipher_test_rand", rand, P4_DYNAMIC); + } + } else +#endif + if( sqlite3_stricmp(zLeft, "cipher_fips_status")== 0 && !zRight ){ + if(ctx) { + char *fips_mode_status = sqlite3_mprintf("%d", ctx->provider->fips_status(ctx->provider_ctx)); + sqlcipher_vdbe_return_string(pParse, "cipher_fips_status", fips_mode_status, P4_DYNAMIC); + } + } else + if( sqlite3_stricmp(zLeft, "cipher_store_pass")==0 && zRight ) { + if(ctx) { + char *deprecation = "PRAGMA cipher_store_pass is deprecated, please remove from use"; + ctx->store_pass = sqlite3GetBoolean(zRight, 1); + sqlcipher_vdbe_return_string(pParse, "cipher_store_pass", deprecation, P4_TRANSIENT); + sqlite3_log(SQLITE_WARNING, deprecation); + } + } else + if( sqlite3_stricmp(zLeft, "cipher_store_pass")==0 && !zRight ) { + if(ctx){ + char *store_pass_value = sqlite3_mprintf("%d", ctx->store_pass); + sqlcipher_vdbe_return_string(pParse, "cipher_store_pass", store_pass_value, P4_DYNAMIC); + } + } + if( sqlite3_stricmp(zLeft, "cipher_profile")== 0 && zRight ){ + char *profile_status = sqlite3_mprintf("%d", sqlcipher_cipher_profile(db, zRight)); + sqlcipher_vdbe_return_string(pParse, "cipher_profile", profile_status, P4_DYNAMIC); + } else + if( sqlite3_stricmp(zLeft, "cipher_add_random")==0 && zRight ){ + if(ctx) { + char *add_random_status = sqlite3_mprintf("%d", sqlcipher_codec_add_random(ctx, zRight, sqlite3Strlen30(zRight))); + sqlcipher_vdbe_return_string(pParse, "cipher_add_random", add_random_status, P4_DYNAMIC); + } + } else + if( sqlite3_stricmp(zLeft, "cipher_migrate")==0 && !zRight ){ + if(ctx){ + int status = sqlcipher_codec_ctx_migrate(ctx); + char *migrate_status = sqlite3_mprintf("%d", status); + sqlcipher_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); + if(status != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: error occurred during cipher_migrate: %d", status); + sqlcipher_codec_ctx_set_error(ctx, status); + } + } + } else + if( sqlite3_stricmp(zLeft, "cipher_provider")==0 && !zRight ){ + if(ctx) { + sqlcipher_vdbe_return_string(pParse, "cipher_provider", + ctx->provider->get_provider_name(ctx->provider_ctx), P4_TRANSIENT); + } + } else + if( sqlite3_stricmp(zLeft, "cipher_provider_version")==0 && !zRight){ + if(ctx) { + sqlcipher_vdbe_return_string(pParse, "cipher_provider_version", + ctx->provider->get_provider_version(ctx->provider_ctx), P4_TRANSIENT); + } + } else + if( sqlite3_stricmp(zLeft, "cipher_version")==0 && !zRight ){ + sqlcipher_vdbe_return_string(pParse, "cipher_version", sqlcipher_version(), P4_DYNAMIC); + }else + if( sqlite3_stricmp(zLeft, "cipher")==0 ){ + if(ctx) { + if( zRight ) { + const char* message = "PRAGMA cipher is no longer supported."; + sqlcipher_vdbe_return_string(pParse, "cipher", message, P4_TRANSIENT); + sqlite3_log(SQLITE_WARNING, message); + }else { + sqlcipher_vdbe_return_string(pParse, "cipher", + ctx->provider->get_cipher(ctx->provider_ctx), P4_TRANSIENT); + } + } + }else + if( sqlite3_stricmp(zLeft, "rekey_cipher")==0 && zRight ){ + const char* message = "PRAGMA rekey_cipher is no longer supported."; + sqlcipher_vdbe_return_string(pParse, "rekey_cipher", message, P4_TRANSIENT); + sqlite3_log(SQLITE_WARNING, message); + }else + if( sqlite3_stricmp(zLeft,"cipher_default_kdf_iter")==0 ){ + if( zRight ) { + default_kdf_iter = atoi(zRight); /* change default KDF iterations */ + } else { + char *kdf_iter = sqlite3_mprintf("%d", default_kdf_iter); + sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_iter", kdf_iter, P4_DYNAMIC); + } + }else + if( sqlite3_stricmp(zLeft, "kdf_iter")==0 ){ + if(ctx) { + if( zRight ) { + sqlcipher_codec_ctx_set_kdf_iter(ctx, atoi(zRight)); /* change of RW PBKDF2 iteration */ + } else { + char *kdf_iter = sqlite3_mprintf("%d", ctx->kdf_iter); + sqlcipher_vdbe_return_string(pParse, "kdf_iter", kdf_iter, P4_DYNAMIC); + } + } + }else + if( sqlite3_stricmp(zLeft, "fast_kdf_iter")==0){ + if(ctx) { + if( zRight ) { + char *deprecation = "PRAGMA fast_kdf_iter is deprecated, please remove from use"; + sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, atoi(zRight)); /* change of RW PBKDF2 iteration */ + sqlcipher_vdbe_return_string(pParse, "fast_kdf_iter", deprecation, P4_TRANSIENT); + sqlite3_log(SQLITE_WARNING, deprecation); + } else { + char *fast_kdf_iter = sqlite3_mprintf("%d", ctx->fast_kdf_iter); + sqlcipher_vdbe_return_string(pParse, "fast_kdf_iter", fast_kdf_iter, P4_DYNAMIC); + } + } + }else + if( sqlite3_stricmp(zLeft, "rekey_kdf_iter")==0 && zRight ){ + const char* message = "PRAGMA rekey_kdf_iter is no longer supported."; + sqlcipher_vdbe_return_string(pParse, "rekey_kdf_iter", message, P4_TRANSIENT); + sqlite3_log(SQLITE_WARNING, message); + }else + if( sqlite3_stricmp(zLeft,"page_size")==0 || sqlite3_stricmp(zLeft,"cipher_page_size")==0 ){ + /* PRAGMA cipher_page_size will alter the size of the database pages while ensuring that the + required reserve space is allocated at the end of each page. This will also override the + standard SQLite PRAGMA page_size behavior if a codec context is attached to the database handle. + If PRAGMA page_size is invoked but a codec context is not attached (i.e. dealing with a standard + unencrypted database) then return early and allow the standard PRAGMA page_size logic to apply. */ + if(ctx) { + if( zRight ) { + int size = atoi(zRight); + rc = sqlcipher_codec_ctx_set_pagesize(ctx, size); + if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); + rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); + if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); + } else { + char * page_size = sqlite3_mprintf("%d", ctx->page_sz); + sqlcipher_vdbe_return_string(pParse, "cipher_page_size", page_size, P4_DYNAMIC); + } + } else { + return 0; /* return early so that the PragTyp_PAGE_SIZE case logic in pragma.c will take effect */ + } + }else + if( sqlite3_stricmp(zLeft,"cipher_default_page_size")==0 ){ + if( zRight ) { + default_page_size = atoi(zRight); + } else { + char *page_size = sqlite3_mprintf("%d", default_page_size); + sqlcipher_vdbe_return_string(pParse, "cipher_default_page_size", page_size, P4_DYNAMIC); + } + }else + if( sqlite3_stricmp(zLeft,"cipher_default_use_hmac")==0 ){ + if( zRight ) { + sqlcipher_set_default_use_hmac(sqlite3GetBoolean(zRight,1)); + } else { + char *default_use_hmac = sqlite3_mprintf("%d", SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC)); + sqlcipher_vdbe_return_string(pParse, "cipher_default_use_hmac", default_use_hmac, P4_DYNAMIC); + } + }else + if( sqlite3_stricmp(zLeft,"cipher_use_hmac")==0 ){ + if(ctx) { + if( zRight ) { + rc = sqlcipher_codec_ctx_set_use_hmac(ctx, sqlite3GetBoolean(zRight,1)); + if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); + /* since the use of hmac has changed, the page size may also change */ + rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); + if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); + } else { + char *hmac_flag = sqlite3_mprintf("%d", SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC)); + sqlcipher_vdbe_return_string(pParse, "cipher_use_hmac", hmac_flag, P4_DYNAMIC); + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_hmac_pgno")==0 ){ + if(ctx) { + if(zRight) { + char *deprecation = "PRAGMA cipher_hmac_pgno is deprecated, please remove from use"; + /* clear both pgno endian flags */ + if(sqlite3_stricmp(zRight, "le") == 0) { + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_BE_PGNO); + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_LE_PGNO); + } else if(sqlite3_stricmp(zRight, "be") == 0) { + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_LE_PGNO); + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_BE_PGNO); + } else if(sqlite3_stricmp(zRight, "native") == 0) { + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_LE_PGNO); + SQLCIPHER_FLAG_UNSET(ctx->flags, CIPHER_FLAG_BE_PGNO); + } + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", deprecation, P4_TRANSIENT); + sqlite3_log(SQLITE_WARNING, deprecation); + + } else { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_LE_PGNO)) { + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "le", P4_TRANSIENT); + } else if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_BE_PGNO)) { + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "be", P4_TRANSIENT); + } else { + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_pgno", "native", P4_TRANSIENT); + } + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_hmac_salt_mask")==0 ){ + if(ctx) { + if(zRight) { + char *deprecation = "PRAGMA cipher_hmac_salt_mask is deprecated, please remove from use"; + if (sqlite3StrNICmp(zRight ,"x'", 2) == 0 && sqlite3Strlen30(zRight) == 5) { + unsigned char mask = 0; + const unsigned char *hex = (const unsigned char *)zRight+2; + cipher_hex2bin(hex,2,&mask); + hmac_salt_mask = mask; + } + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_salt_mask", deprecation, P4_TRANSIENT); + sqlite3_log(SQLITE_WARNING, deprecation); + } else { + char *mask = sqlite3_mprintf("%02x", hmac_salt_mask); + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_salt_mask", mask, P4_DYNAMIC); + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_plaintext_header_size")==0 ){ + if(ctx) { + if( zRight ) { + int size = atoi(zRight); + /* deliberately ignore result code, if size is invalid it will be set to -1 + and trip the error later in the codec */ + sqlcipher_codec_ctx_set_plaintext_header_size(ctx, size); + } else { + char *size = sqlite3_mprintf("%d", ctx->plaintext_header_sz); + sqlcipher_vdbe_return_string(pParse, "cipher_plaintext_header_size", size, P4_DYNAMIC); + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_default_plaintext_header_size")==0 ){ + if( zRight ) { + default_plaintext_header_size = atoi(zRight); + } else { + char *size = sqlite3_mprintf("%d", default_plaintext_header_size); + sqlcipher_vdbe_return_string(pParse, "cipher_default_plaintext_header_size", size, P4_DYNAMIC); + } + }else + if( sqlite3_stricmp(zLeft,"cipher_salt")==0 ){ + if(ctx) { + if(zRight) { + if (sqlite3StrNICmp(zRight ,"x'", 2) == 0 && sqlite3Strlen30(zRight) == (FILE_HEADER_SZ*2)+3) { + unsigned char *salt = (unsigned char*) sqlite3_malloc(FILE_HEADER_SZ); + const unsigned char *hex = (const unsigned char *)zRight+2; + cipher_hex2bin(hex,FILE_HEADER_SZ*2,salt); + sqlcipher_codec_ctx_set_kdf_salt(ctx, salt, FILE_HEADER_SZ); + sqlite3_free(salt); + } + } else { + void *salt; + char *hexsalt = (char*) sqlite3_malloc((FILE_HEADER_SZ*2)+1); + if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &salt)) == SQLITE_OK) { + cipher_bin2hex(salt, FILE_HEADER_SZ, hexsalt); + sqlcipher_vdbe_return_string(pParse, "cipher_salt", hexsalt, P4_DYNAMIC); + } else { + sqlite3_free(hexsalt); + sqlcipher_codec_ctx_set_error(ctx, rc); + } + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_hmac_algorithm")==0 ){ + if(ctx) { + if(zRight) { + rc = SQLITE_ERROR; + if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { + rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { + rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA256); + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { + rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA512); + } + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + } else { + int algorithm = ctx->hmac_algorithm; + if(ctx->hmac_algorithm == SQLCIPHER_HMAC_SHA1) { + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA1_LABEL, P4_TRANSIENT); + } else if(algorithm == SQLCIPHER_HMAC_SHA256) { + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA256_LABEL, P4_TRANSIENT); + } else if(algorithm == SQLCIPHER_HMAC_SHA512) { + sqlcipher_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA512_LABEL, P4_TRANSIENT); + } + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_default_hmac_algorithm")==0 ){ + if(zRight) { + rc = SQLITE_OK; + if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) { + default_hmac_algorithm = SQLCIPHER_HMAC_SHA1; + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) { + default_hmac_algorithm = SQLCIPHER_HMAC_SHA256; + } else if(sqlite3_stricmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) { + default_hmac_algorithm = SQLCIPHER_HMAC_SHA512; + } + } else { + if(default_hmac_algorithm == SQLCIPHER_HMAC_SHA1) { + sqlcipher_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA1_LABEL, P4_TRANSIENT); + } else if(default_hmac_algorithm == SQLCIPHER_HMAC_SHA256) { + sqlcipher_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA256_LABEL, P4_TRANSIENT); + } else if(default_hmac_algorithm == SQLCIPHER_HMAC_SHA512) { + sqlcipher_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA512_LABEL, P4_TRANSIENT); + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_kdf_algorithm")==0 ){ + if(ctx) { + if(zRight) { + rc = SQLITE_ERROR; + if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { + rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { + rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA256); + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { + rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA512); + } + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + } else { + if(ctx->kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { + sqlcipher_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL, P4_TRANSIENT); + } else if(ctx->kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { + sqlcipher_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL, P4_TRANSIENT); + } else if(ctx->kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { + sqlcipher_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL, P4_TRANSIENT); + } + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_default_kdf_algorithm")==0 ){ + if(zRight) { + rc = SQLITE_OK; + if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) { + default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA1; + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) { + default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA256; + } else if(sqlite3_stricmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) { + default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA512; + } + } else { + if(default_kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { + sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL, P4_TRANSIENT); + } else if(default_kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { + sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL, P4_TRANSIENT); + } else if(default_kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { + sqlcipher_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL, P4_TRANSIENT); + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_compatibility")==0 ){ + if(ctx) { + if(zRight) { + int version = atoi(zRight); + + switch(version) { + case 1: + rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 4000); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 0); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + break; + + case 2: + rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 4000); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + break; + + case 3: + rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 64000); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + break; + + default: + rc = sqlcipher_codec_ctx_set_pagesize(ctx, 4096); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA512); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA512); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 256000); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + break; + } + + rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); + if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_default_compatibility")==0 ){ + if(zRight) { + int version = atoi(zRight); + switch(version) { + case 1: + default_page_size = 1024; + default_hmac_algorithm = SQLCIPHER_HMAC_SHA1; + default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA1; + default_kdf_iter = 4000; + sqlcipher_set_default_use_hmac(0); + break; + + case 2: + default_page_size = 1024; + default_hmac_algorithm = SQLCIPHER_HMAC_SHA1; + default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA1; + default_kdf_iter = 4000; + sqlcipher_set_default_use_hmac(1); + break; + + case 3: + default_page_size = 1024; + default_hmac_algorithm = SQLCIPHER_HMAC_SHA1; + default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA1; + default_kdf_iter = 64000; + sqlcipher_set_default_use_hmac(1); + break; + + default: + default_page_size = 4096; + default_hmac_algorithm = SQLCIPHER_HMAC_SHA512; + default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA512; + default_kdf_iter = 256000; + sqlcipher_set_default_use_hmac(1); + break; + } + } + }else + if( sqlite3_stricmp(zLeft,"cipher_memory_security")==0 ){ + if( zRight ) { + if(sqlite3GetBoolean(zRight,1)) { + /* memory security can only be enabled, not disabled */ + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_set_mem_security: on"); + sqlcipher_mem_security_on = 1; + } + } else { + /* only report that memory security is enabled if pragma cipher_memory_security is ON and + SQLCipher's allocator/deallocator was run at least one time */ + int state = sqlcipher_mem_security_on && sqlcipher_mem_executed; + char *on = sqlite3_mprintf("%d", state); + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, + "sqlcipher_get_mem_security: sqlcipher_mem_security_on = %d, sqlcipher_mem_executed = %d", + sqlcipher_mem_security_on, sqlcipher_mem_executed); + sqlcipher_vdbe_return_string(pParse, "cipher_memory_security", on, P4_DYNAMIC); + } + }else + if( sqlite3_stricmp(zLeft,"cipher_settings")==0 ){ + if(ctx) { + int algorithm; + char *pragma; + + pragma = sqlite3_mprintf("PRAGMA kdf_iter = %d;", ctx->kdf_iter); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = sqlite3_mprintf("PRAGMA cipher_page_size = %d;", ctx->page_sz); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = sqlite3_mprintf("PRAGMA cipher_use_hmac = %d;", SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_HMAC)); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = sqlite3_mprintf("PRAGMA cipher_plaintext_header_size = %d;", ctx->plaintext_header_sz); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + algorithm = ctx->hmac_algorithm; + pragma = NULL; + if(algorithm == SQLCIPHER_HMAC_SHA1) { + pragma = sqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA1_LABEL); + } else if(algorithm == SQLCIPHER_HMAC_SHA256) { + pragma = sqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA256_LABEL); + } else if(algorithm == SQLCIPHER_HMAC_SHA512) { + pragma = sqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA512_LABEL); + } + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + algorithm = ctx->kdf_algorithm; + pragma = NULL; + if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { + pragma = sqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL); + } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { + pragma = sqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL); + } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { + pragma = sqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL); + } + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + } + }else + if( sqlite3_stricmp(zLeft,"cipher_default_settings")==0 ){ + char *pragma; + + pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_iter = %d;", default_kdf_iter); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = sqlite3_mprintf("PRAGMA cipher_default_page_size = %d;", default_page_size); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = sqlite3_mprintf("PRAGMA cipher_default_use_hmac = %d;", SQLCIPHER_FLAG_GET(default_flags, CIPHER_FLAG_HMAC)); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = sqlite3_mprintf("PRAGMA cipher_default_plaintext_header_size = %d;", default_plaintext_header_size); + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = NULL; + if(default_hmac_algorithm == SQLCIPHER_HMAC_SHA1) { + pragma = sqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA1_LABEL); + } else if(default_hmac_algorithm == SQLCIPHER_HMAC_SHA256) { + pragma = sqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA256_LABEL); + } else if(default_hmac_algorithm == SQLCIPHER_HMAC_SHA512) { + pragma = sqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA512_LABEL); + } + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + + pragma = NULL; + if(default_kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) { + pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL); + } else if(default_kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) { + pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL); + } else if(default_kdf_algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) { + pragma = sqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL); + } + sqlcipher_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC); + }else + if( sqlite3_stricmp(zLeft,"cipher_integrity_check")==0 ){ + if(ctx) { + sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check"); + } + } else + if( sqlite3_stricmp(zLeft, "cipher_log_level")==0 ){ + if(zRight) { + sqlcipher_log_level = SQLCIPHER_LOG_NONE; + if(sqlite3_stricmp(zRight, "ERROR")==0) sqlcipher_log_level = SQLCIPHER_LOG_ERROR; + else if(sqlite3_stricmp(zRight, "WARN" )==0) sqlcipher_log_level = SQLCIPHER_LOG_WARN; + else if(sqlite3_stricmp(zRight, "INFO" )==0) sqlcipher_log_level = SQLCIPHER_LOG_INFO; + else if(sqlite3_stricmp(zRight, "DEBUG")==0) sqlcipher_log_level = SQLCIPHER_LOG_DEBUG; + else if(sqlite3_stricmp(zRight, "TRACE")==0) sqlcipher_log_level = SQLCIPHER_LOG_TRACE; + } + sqlcipher_vdbe_return_string(pParse, "cipher_log_level", sqlcipher_get_log_level_str(sqlcipher_log_level), P4_TRANSIENT); + } else + if( sqlite3_stricmp(zLeft, "cipher_log_source")==0 ){ + if(zRight) { + sqlcipher_log_source = SQLCIPHER_LOG_NONE; + if(sqlite3_stricmp(zRight, "NONE" )==0) sqlcipher_log_source = SQLCIPHER_LOG_NONE; + else if(sqlite3_stricmp(zRight, "ALL" )==0) sqlcipher_log_source = SQLCIPHER_LOG_ALL; + else if(sqlite3_stricmp(zRight, "CORE" )==0) sqlcipher_log_source = SQLCIPHER_LOG_CORE; + else if(sqlite3_stricmp(zRight, "MEMORY" )==0) sqlcipher_log_source = SQLCIPHER_LOG_MEMORY; + else if(sqlite3_stricmp(zRight, "MUTEX" )==0) sqlcipher_log_source = SQLCIPHER_LOG_MUTEX; + else if(sqlite3_stricmp(zRight, "PROVIDER")==0) sqlcipher_log_source = SQLCIPHER_LOG_PROVIDER; + } + sqlcipher_vdbe_return_string(pParse, "cipher_log_source", sqlcipher_get_log_source_str(sqlcipher_log_source), P4_TRANSIENT); + } else + if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ + char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); + sqlcipher_vdbe_return_string(pParse, "cipher_log", status, P4_DYNAMIC); + }else { + return 0; + } + return 1; +} + +/* these constants are used internally within SQLite's pager.c to differentiate between + operations on the main database or journal pages. This is important in the context + of a rekey operations, where the journal must be written using the original key + material (to allow a transactional rollback), while the new database pages are being + written with the new key material*/ +#define CODEC_READ_OP 3 +#define CODEC_WRITE_OP 6 +#define CODEC_JOURNAL_OP 7 + +/* + * sqlite3Codec can be called in multiple modes. + * encrypt mode - expected to return a pointer to the + * encrypted data without altering pData. + * decrypt mode - expected to return a pointer to pData, with + * the data decrypted in the input buffer + */ +static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { + codec_ctx *ctx = (codec_ctx *) iCtx; + int offset = 0, rc = 0; + unsigned char *pData = (unsigned char *) data; + int cctx = CIPHER_READ_CTX; + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3Codec: pgno=%d, mode=%d, ctx->page_sz=%d", pgno, mode, ctx->page_sz); + +#ifdef SQLCIPHER_EXT + if(sqlcipher_license_check(ctx) != SQLITE_OK) return NULL; +#endif + + /* call to derive keys if not present yet */ + if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error occurred during key derivation: %d", rc); + sqlcipher_codec_ctx_set_error(ctx, rc); + return NULL; + } + + /* if the plaintext_header_size is negative that means an invalid size was set via + PRAGMA. We can't set the error state on the pager at that point because the pager + may not be open yet. However, this is a fatal error state, so abort the codec */ + if(ctx->plaintext_header_sz < 0) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error invalid ctx->plaintext_header_sz: %d", ctx->plaintext_header_sz); + sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); + return NULL; + } + + if(pgno == 1) /* adjust starting pointers in data page for header offset on first page*/ + offset = ctx->plaintext_header_sz ? ctx->plaintext_header_sz : FILE_HEADER_SZ; + + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3Codec: switch mode=%d offset=%d", mode, offset); + switch(mode) { + case CODEC_READ_OP: /* decrypt */ + if(pgno == 1) /* copy initial part of file header or SQLite magic to buffer */ + memcpy(ctx->buffer, ctx->plaintext_header_sz ? pData : (void *) SQLITE_FILE_HEADER, offset); + + rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, ctx->page_sz - offset, pData + offset, (unsigned char*)ctx->buffer + offset); +#ifdef SQLCIPHER_TEST + if((cipher_test_flags & TEST_FAIL_DECRYPT) > 0 && sqlcipher_get_test_fail()) { + rc = SQLITE_ERROR; + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3Codec: simulating decryption failure for pgno=%d, mode=%d, ctx->page_sz=%d\n", pgno, mode, ctx->page_sz); + } +#endif + if(rc != SQLITE_OK) { + /* failure to decrypt a page is considered a permanent error and will render the pager unusable + in order to prevent inconsistent data being loaded into page cache */ + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error decrypting page %d data: %d", pgno, rc); + sqlcipher_memset((unsigned char*) ctx->buffer+offset, 0, ctx->page_sz-offset); + sqlcipher_codec_ctx_set_error(ctx, rc); + } else { + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_KEY_USED); + } + memcpy(pData, ctx->buffer, ctx->page_sz); /* copy buffer data back to pData and return */ + return pData; + break; + + case CODEC_WRITE_OP: /* encrypt database page, operate on write context and fall through to case 7, so the write context is used*/ + cctx = CIPHER_WRITE_CTX; + + case CODEC_JOURNAL_OP: /* encrypt journal page, operate on read context use to get the original page data from the database */ + if(pgno == 1) { /* copy initial part of file header or salt to buffer */ + void *kdf_salt = NULL; + /* retrieve the kdf salt */ + if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &kdf_salt)) != SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error retrieving salt: %d", rc); + sqlcipher_codec_ctx_set_error(ctx, rc); + return NULL; + } + memcpy(ctx->buffer, ctx->plaintext_header_sz ? pData : kdf_salt, offset); + } + rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, ctx->page_sz - offset, pData + offset, (unsigned char*)ctx->buffer + offset); +#ifdef SQLCIPHER_TEST + if((cipher_test_flags & TEST_FAIL_ENCRYPT) > 0 && sqlcipher_get_test_fail()) { + rc = SQLITE_ERROR; + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3Codec: simulating encryption failure for pgno=%d, mode=%d, ctx->page_sz=%d\n", pgno, mode, ctx->page_sz); + } +#endif + if(rc != SQLITE_OK) { + /* failure to encrypt a page is considered a permanent error and will render the pager unusable + in order to prevent corrupted pages from being written to the main databased when using WAL */ + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error encrypting page %d data: %d", pgno, rc); + sqlcipher_memset((unsigned char*)ctx->buffer+offset, 0, ctx->page_sz-offset); + sqlcipher_codec_ctx_set_error(ctx, rc); + return NULL; + } + SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_KEY_USED); + return ctx->buffer; /* return persistent buffer data, pData remains intact */ + break; + + default: + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error unsupported codec mode %d", mode); + sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); /* unsupported mode, set error */ + return pData; + break; + } +} + +static void sqlite3FreeCodecArg(void *pCodecArg) { + codec_ctx *ctx = (codec_ctx *) pCodecArg; + if(pCodecArg == NULL) return; + sqlcipher_codec_ctx_free(&ctx); /* wipe and free allocated memory for the context */ + sqlcipher_deactivate(); /* cleanup related structures, OpenSSL etc, when codec is detatched */ +} + +int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { + struct Db *pDb = &db->aDb[nDb]; + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: db=%p, nDb=%d", db, nDb); + + if(nKey && zKey && pDb->pBt) { + int rc; + Pager *pPager = pDb->pBt->pBt->pPager; + sqlite3_file *fd; + codec_ctx *ctx; + + ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); + + if(ctx != NULL && SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) { + /* there is already a codec attached to this database, so we should not proceed */ + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: no codec attached to db"); + return SQLITE_OK; + } + + /* check if the sqlite3_file is open, and if not force handle to NULL */ + if((fd = sqlite3PagerFile(pPager))->pMethods == 0) fd = NULL; + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipher_activate()"); + sqlcipher_activate(); /* perform internal initialization for sqlcipher */ + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: entering database mutex %p", db->mutex); + sqlite3_mutex_enter(db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: entered database mutex %p", db->mutex); + +#ifdef SQLCIPHER_EXT + if((rc = sqlite3_set_authorizer(db, sqlcipher_license_authorizer, db)) != SQLITE_OK) { + sqlite3_mutex_leave(db->mutex); + return rc; + } +#endif + + /* point the internal codec argument against the contet to be prepared */ + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipher_codec_ctx_init()"); + rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, zKey, nKey); + + if(rc != SQLITE_OK) { + /* initialization failed, do not attach potentially corrupted context */ + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: context initialization failed, forcing error state with rc=%d", rc); + /* force an error at the pager level, such that even the upstream caller ignores the return code + the pager will be in an error state and will process no further operations */ + sqlite3pager_error(pPager, rc); + pDb->pBt->pBt->db->errCode = rc; + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: leaving database mutex %p (early return on rc=%d)", db->mutex, rc); + sqlite3_mutex_leave(db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: left database mutex %p (early return on rc=%d)", db->mutex, rc); + return rc; + } + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipherPagerSetCodec()"); + sqlcipherPagerSetCodec(sqlite3BtreePager(pDb->pBt), sqlite3Codec, NULL, sqlite3FreeCodecArg, (void *) ctx); + + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling codec_set_btree_to_codec_pagesize()"); + codec_set_btree_to_codec_pagesize(db, pDb, ctx); + + /* force secure delete. This has the benefit of wiping internal data when deleted + and also ensures that all pages are written to disk (i.e. not skipped by + sqlite3PagerDontWrite optimizations) */ + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlite3BtreeSecureDelete()"); + sqlite3BtreeSecureDelete(pDb->pBt, 1); + + /* if fd is null, then this is an in-memory database and + we dont' want to overwrite the AutoVacuum settings + if not null, then set to the default */ + if(fd != NULL) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlite3BtreeSetAutoVacuum()"); + sqlite3BtreeSetAutoVacuum(pDb->pBt, SQLITE_DEFAULT_AUTOVACUUM); + } + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: leaving database mutex %p", db->mutex); + sqlite3_mutex_leave(db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: left database mutex %p", db->mutex); + } + return SQLITE_OK; +} + +int sqlcipher_find_db_index(sqlite3 *db, const char *zDb) { + int db_index; + if(zDb == NULL){ + return 0; + } + for(db_index = 0; db_index < db->nDb; db_index++) { + struct Db *pDb = &db->aDb[db_index]; + if(strcmp(pDb->zDbSName, zDb) == 0) { + return db_index; + } + } + return 0; +} + +void sqlite3_activate_see(const char* in) { + /* do nothing, security enhancements are always active */ +} + +int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_key: db=%p", db); + return sqlite3_key_v2(db, "main", pKey, nKey); +} + +int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_key_v2: db=%p zDb=%s", db, zDb); + /* attach key if db and pKey are not null and nKey is > 0 */ + if(db && pKey && nKey) { + int db_index = sqlcipher_find_db_index(db, zDb); + return sqlcipherCodecAttach(db, db_index, pKey, nKey); + } + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlite3_key_v2: no key provided"); + return SQLITE_ERROR; +} + +int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey: db=%p", db); + return sqlite3_rekey_v2(db, "main", pKey, nKey); +} + +/* sqlite3_rekey_v2 +** Given a database, this will reencrypt the database using a new key. +** There is only one possible modes of operation - to encrypt a database +** that is already encrpyted. If the database is not already encrypted +** this should do nothing +** The proposed logic for this function follows: +** 1. Determine if the database is already encryptped +** 2. If there is NOT already a key present do nothing +** 3. If there is a key present, re-encrypt the database with the new key +*/ +int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: db=%p zDb=%s", db, zDb); + if(db && pKey && nKey) { + int db_index = sqlcipher_find_db_index(db, zDb); + struct Db *pDb = &db->aDb[db_index]; + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: database zDb=%p db_index:%d", zDb, db_index); + if(pDb->pBt) { + codec_ctx *ctx; + int rc, page_count; + Pgno pgno; + PgHdr *page; + Pager *pPager = pDb->pBt->pBt->pPager; + + ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); + + if(ctx == NULL) { + /* there was no codec attached to this database, so this should do nothing! */ + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: no codec attached to db %s: rekey can't be used on an unencrypted database", zDb); + return SQLITE_MISUSE; + } + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: entering database mutex %p", db->mutex); + sqlite3_mutex_enter(db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: entered database mutex %p", db->mutex); + + codec_set_pass_key(db, db_index, pKey, nKey, CIPHER_WRITE_CTX); + + /* do stuff here to rewrite the database + ** 1. Create a transaction on the database + ** 2. Iterate through each page, reading it and then writing it. + ** 3. If that goes ok then commit and put ctx->rekey into ctx->key + ** note: don't deallocate rekey since it may be used in a subsequent iteration + */ + rc = sqlite3BtreeBeginTrans(pDb->pBt, 1, 0); /* begin write transaction */ + sqlite3PagerPagecount(pPager, &page_count); + for(pgno = 1; rc == SQLITE_OK && pgno <= (unsigned int)page_count; pgno++) { /* pgno's start at 1 see pager.c:pagerAcquire */ + if(!sqlite3pager_is_sj_pgno(pPager, pgno)) { /* skip this page (see pager.c:pagerAcquire for reasoning) */ + rc = sqlite3PagerGet(pPager, pgno, &page, 0); + if(rc == SQLITE_OK) { /* write page see pager_incr_changecounter for example */ + rc = sqlite3PagerWrite(page); + if(rc == SQLITE_OK) { + sqlite3PagerUnref(page); + } else { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: error %d occurred writing page %d", rc, pgno); + } + } else { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: error %d occurred reading page %d", rc, pgno); + } + } + } + + /* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */ + if(rc == SQLITE_OK) { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: committing"); + rc = sqlite3BtreeCommit(pDb->pBt); + sqlcipher_codec_key_copy(ctx, CIPHER_WRITE_CTX); + } else { + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: rollback"); + sqlite3BtreeRollback(pDb->pBt, SQLITE_ABORT_ROLLBACK, 0); + } + + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: leaving database mutex %p", db->mutex); + sqlite3_mutex_leave(db->mutex); + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlite3_rekey_v2: left database mutex %p", db->mutex); + } + return SQLITE_OK; + } + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3_rekey_v2: no key provided for db %s: rekey can't be used to decrypt an encrypted database", zDb); + return SQLITE_ERROR; +} + +void sqlcipherCodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) { + struct Db *pDb = &db->aDb[nDb]; + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecGetKey:db=%p, nDb=%d", db, nDb); + if( pDb->pBt ) { + codec_ctx *ctx = (codec_ctx*) sqlcipherPagerGetCodec(pDb->pBt->pBt->pPager); + + if(ctx) { + /* pass back the keyspec from the codec, unless PRAGMA cipher_store_pass + is set or keyspec has not yet been derived, in which case pass + back the password key material */ + *zKey = ctx->read_ctx->keyspec; + *nKey = ctx->keyspec_sz; + if(ctx->store_pass == 1 || *zKey == NULL) { + *zKey = ctx->read_ctx->pass; + *nKey = ctx->read_ctx->pass_sz; + } + } else { + *zKey = NULL; + *nKey = 0; + } + } +} + +/* + * Implementation of an "export" function that allows a caller + * to duplicate the main database to an attached database. This is intended + * as a conveneince for users who need to: + * + * 1. migrate from an non-encrypted database to an encrypted database + * 2. move from an encrypted database to a non-encrypted database + * 3. convert beween the various flavors of encrypted databases. + * + * This implementation is based heavily on the procedure and code used + * in vacuum.c, but is exposed as a function that allows export to any + * named attached database. + */ + +/* +** Finalize a prepared statement. If there was an error, store the +** text of the error message in *pzErrMsg. Return the result code. +** +** Based on vacuumFinalize from vacuum.c +*/ +static int sqlcipher_finalize(sqlite3 *db, sqlite3_stmt *pStmt, char **pzErrMsg){ + int rc; + rc = sqlite3VdbeFinalize((Vdbe*)pStmt); + if( rc ){ + sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); + } + return rc; +} + +/* +** Execute zSql on database db. Return an error code. +** +** Based on execSql from vacuum.c +*/ +static int sqlcipher_execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ + sqlite3_stmt *pStmt; + VVA_ONLY( int rc; ) + if( !zSql ){ + return SQLITE_NOMEM; + } + if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){ + sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); + return sqlite3_errcode(db); + } + VVA_ONLY( rc = ) sqlite3_step(pStmt); + assert( rc!=SQLITE_ROW ); + return sqlcipher_finalize(db, pStmt, pzErrMsg); +} + +/* +** Execute zSql on database db. The statement returns exactly +** one column. Execute this as SQL on the same database. +** +** Based on execExecSql from vacuum.c +*/ +static int sqlcipher_execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ + sqlite3_stmt *pStmt; + int rc; + + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + rc = sqlcipher_execSql(db, pzErrMsg, (char*)sqlite3_column_text(pStmt, 0)); + if( rc!=SQLITE_OK ){ + sqlcipher_finalize(db, pStmt, pzErrMsg); + return rc; + } + } + + return sqlcipher_finalize(db, pStmt, pzErrMsg); +} + +/* + * copy database and schema from the main database to an attached database + * + * Based on sqlite3RunVacuum from vacuum.c +*/ +void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **argv) { + sqlite3 *db = sqlite3_context_db_handle(context); + const char* targetDb, *sourceDb; + int targetDb_idx = 0; + u64 saved_flags = db->flags; /* Saved value of the db->flags */ + u32 saved_mDbFlags = db->mDbFlags; /* Saved value of the db->mDbFlags */ + int saved_nChange = db->nChange; /* Saved value of db->nChange */ + int saved_nTotalChange = db->nTotalChange; /* Saved value of db->nTotalChange */ + u8 saved_mTrace = db->mTrace; /* Saved value of db->mTrace */ + int rc = SQLITE_OK; /* Return code from service routines */ + char *zSql = NULL; /* SQL statements */ + char *pzErrMsg = NULL; + + if(argc != 1 && argc != 2) { + rc = SQLITE_ERROR; + pzErrMsg = sqlite3_mprintf("invalid number of arguments (%d) passed to sqlcipher_export", argc); + goto end_of_export; + } + + if(sqlite3_value_type(argv[0]) == SQLITE_NULL) { + rc = SQLITE_ERROR; + pzErrMsg = sqlite3_mprintf("target database can't be NULL"); + goto end_of_export; + } + + targetDb = (const char*) sqlite3_value_text(argv[0]); + sourceDb = "main"; + + if(argc == 2) { + if(sqlite3_value_type(argv[1]) == SQLITE_NULL) { + rc = SQLITE_ERROR; + pzErrMsg = sqlite3_mprintf("target database can't be NULL"); + goto end_of_export; + } + sourceDb = (char *) sqlite3_value_text(argv[1]); + } + + + /* if the name of the target is not main, but the index returned is zero + there is a mismatch and we should not proceed */ + targetDb_idx = sqlcipher_find_db_index(db, targetDb); + if(targetDb_idx == 0 && targetDb != NULL && sqlite3_stricmp("main", targetDb) != 0) { + rc = SQLITE_ERROR; + pzErrMsg = sqlite3_mprintf("unknown database %s", targetDb); + goto end_of_export; + } + db->init.iDb = targetDb_idx; + + db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks; + db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum; + db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_Defensive | SQLITE_CountRows); + db->mTrace = 0; + + /* Query the schema of the main database. Create a mirror schema + ** in the temporary database. + */ + zSql = sqlite3_mprintf( + "SELECT sql " + " FROM %s.sqlite_schema WHERE type='table' AND name!='sqlite_sequence'" + " AND rootpage>0" + , sourceDb); + rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); + if( rc!=SQLITE_OK ) goto end_of_export; + sqlite3_free(zSql); + + zSql = sqlite3_mprintf( + "SELECT sql " + " FROM %s.sqlite_schema WHERE sql LIKE 'CREATE INDEX %%' " + , sourceDb); + rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); + if( rc!=SQLITE_OK ) goto end_of_export; + sqlite3_free(zSql); + + zSql = sqlite3_mprintf( + "SELECT sql " + " FROM %s.sqlite_schema WHERE sql LIKE 'CREATE UNIQUE INDEX %%'" + , sourceDb); + rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); + if( rc!=SQLITE_OK ) goto end_of_export; + sqlite3_free(zSql); + + /* Loop through the tables in the main database. For each, do + ** an "INSERT INTO rekey_db.xxx SELECT * FROM main.xxx;" to copy + ** the contents to the temporary database. + */ + zSql = sqlite3_mprintf( + "SELECT 'INSERT INTO %s.' || quote(name) " + "|| ' SELECT * FROM %s.' || quote(name) || ';'" + "FROM %s.sqlite_schema " + "WHERE type = 'table' AND name!='sqlite_sequence' " + " AND rootpage>0" + , targetDb, sourceDb, sourceDb); + rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); + if( rc!=SQLITE_OK ) goto end_of_export; + sqlite3_free(zSql); + + /* Copy over the contents of the sequence table + */ + zSql = sqlite3_mprintf( + "SELECT 'INSERT INTO %s.' || quote(name) " + "|| ' SELECT * FROM %s.' || quote(name) || ';' " + "FROM %s.sqlite_schema WHERE name=='sqlite_sequence';" + , targetDb, sourceDb, targetDb); + rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); + if( rc!=SQLITE_OK ) goto end_of_export; + sqlite3_free(zSql); + + /* Copy the triggers, views, and virtual tables from the main database + ** over to the temporary database. None of these objects has any + ** associated storage, so all we have to do is copy their entries + ** from the SQLITE_MASTER table. + */ + zSql = sqlite3_mprintf( + "INSERT INTO %s.sqlite_schema " + " SELECT type, name, tbl_name, rootpage, sql" + " FROM %s.sqlite_schema" + " WHERE type='view' OR type='trigger'" + " OR (type='table' AND rootpage=0)" + , targetDb, sourceDb); + rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execSql(db, &pzErrMsg, zSql); + if( rc!=SQLITE_OK ) goto end_of_export; + sqlite3_free(zSql); + + zSql = NULL; +end_of_export: + db->init.iDb = 0; + db->flags = saved_flags; + db->mDbFlags = saved_mDbFlags; + db->nChange = saved_nChange; + db->nTotalChange = saved_nTotalChange; + db->mTrace = saved_mTrace; + + if(zSql) sqlite3_free(zSql); + + if(rc) { + if(pzErrMsg != NULL) { + sqlite3_result_error(context, pzErrMsg, -1); + sqlite3DbFree(db, pzErrMsg); + } else { + sqlite3_result_error(context, sqlite3ErrStr(rc), -1); + } + } +} #endif /* END SQLCIPHER */ diff --git a/src/sqlcipher.h b/src/sqlcipher.h index 545a05d7f..55934a24b 100644 --- a/src/sqlcipher.h +++ b/src/sqlcipher.h @@ -52,7 +52,6 @@ #define SQLCIPHER_PBKDF2_HMAC_SHA512 2 #define SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL "PBKDF2_HMAC_SHA512" - typedef struct { int (*activate)(void *ctx); int (*deactivate)(void *ctx); @@ -73,10 +72,16 @@ typedef struct { const char* (*get_provider_version)(void *ctx); } sqlcipher_provider; +/* public interfaces called externally */ +void sqlcipher_init_memmethods(void); +int sqlcipher_codec_pragma(sqlite3*, int, Parse*, const char *, const char*); +int sqlcipherCodecAttach(sqlite3*, int, const void *, int); +void sqlcipherCodecGetKey(sqlite3*, int, void**, int*); +void sqlcipher_exportFunc(sqlite3_context *, int, sqlite3_value **); +int sqlcipher_find_db_index(sqlite3 *, const char *); + /* utility functions */ void* sqlcipher_malloc(sqlite_uint64); -void sqlcipher_mlock(void *, sqlite_uint64); -void sqlcipher_munlock(void *, sqlite_uint64); void* sqlcipher_memset(void *, unsigned char, sqlite_uint64); int sqlcipher_ismemset(const void *, unsigned char, sqlite_uint64); int sqlcipher_memcmp(const void *, const void *, int); @@ -97,6 +102,41 @@ sqlcipher_provider* sqlcipher_get_provider(void); sqlite3_mutex* sqlcipher_mutex(int); +#define SQLCIPHER_LOG_NONE 0x00 +#define SQLCIPHER_LOG_ERROR 0x01 +#define SQLCIPHER_LOG_WARN 0x02 +#define SQLCIPHER_LOG_INFO 0x04 +#define SQLCIPHER_LOG_DEBUG 0x08 +#define SQLCIPHER_LOG_TRACE 0x10 +#define SQLCIPHER_LOG_ALL 0xffffffff + +#define SQLCIPHER_LOG_CORE 0x01 +#define SQLCIPHER_LOG_MEMORY 0x02 +#define SQLCIPHER_LOG_MUTEX 0x04 +#define SQLCIPHER_LOG_PROVIDER 0x08 + +#ifdef SQLCIPHER_OMIT_LOG +#define sqlcipher_log(level, source, message, ...) +#else +void sqlcipher_log(unsigned int level, unsigned int source, const char *message, ...); +#endif + +#ifdef CODEC_DEBUG_PAGEDATA +#define CODEC_HEXDUMP(DESC,BUFFER,LEN) \ + { \ + int __pctr; \ + printf(DESC); \ + for(__pctr=0; __pctr < LEN; __pctr++) { \ + if(__pctr % 16 == 0) printf("\n%05x: ",__pctr); \ + printf("%02x ",((unsigned char*) BUFFER)[__pctr]); \ + } \ + printf("\n"); \ + fflush(stdout); \ + } +#else +#define CODEC_HEXDUMP(DESC,BUFFER,LEN) +#endif + #endif #endif /* END SQLCIPHER */ diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 1b25bb690..5cfcd6f08 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -159,7 +159,6 @@ close $in # text of the file in-line. The file only needs to be included once. # foreach hdr { - crypto.h sqlcipher.h btree.h btreeInt.h @@ -430,8 +429,7 @@ set flist { vdbevtab.c memjournal.c - crypto.c - crypto_impl.c + sqlcipher.c crypto_libtomcrypt.c crypto_nss.c crypto_openssl.c From 64f5c4e54a475f5aaa73fa1962f9b59697196433 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 17 Jul 2024 14:56:32 -0400 Subject: [PATCH 156/340] Relocates and defines extension hooks --- src/sqlcipher.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index b7564b8bf..295c05550 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -242,6 +242,11 @@ static int sqlcipher_get_test_fail() { } #endif +#ifdef SQLCIPHER_EXT +int sqlcipher_ext_provider_setup(sqlcipher_provider *); +void sqlcipher_ext_provider_destroy(); +#endif + static volatile unsigned int default_flags = DEFAULT_CIPHER_FLAGS; static volatile unsigned char hmac_salt_mask = HMAC_SALT_MASK; static volatile int default_kdf_iter = PBKDF2_ITER; @@ -488,6 +493,9 @@ static void sqlcipher_activate() { #elif defined (SQLCIPHER_CRYPTO_OPENSSL) extern int sqlcipher_openssl_setup(sqlcipher_provider *p); sqlcipher_openssl_setup(p); +#elif defined (SQLCIPHER_CRYPTO_OSSL3) + extern int sqlcipher_ossl3_setup(sqlcipher_provider *p); + sqlcipher_ossl3_setup(p); #else #error "NO DEFAULT SQLCIPHER CRYPTO PROVIDER DEFINED" #endif @@ -1922,10 +1930,6 @@ static int sqlcipher_set_log(const char *destination){ #endif } -#ifdef SQLCIPHER_EXT -#include "sqlcipher_ext.h" -#endif - static void sqlcipher_vdbe_return_string(Parse *pParse, const char *zLabel, const char *value, int value_type){ Vdbe *v = sqlite3GetVdbe(pParse); sqlite3VdbeSetNumCols(v, 1); @@ -1934,6 +1938,10 @@ static void sqlcipher_vdbe_return_string(Parse *pParse, const char *zLabel, cons sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); } +#ifdef SQLCIPHER_EXT +#include "sqlcipher_ext.h" +#endif + static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ctx) { int rc; From aef55108fbf33fe82627bd3e7fc0161fb552a797 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 19 Jul 2024 12:07:32 -0400 Subject: [PATCH 157/340] Removes unnecessary non-community extension points --- src/func.c | 3 --- src/main.c | 7 ------- src/sqlcipher.c | 32 -------------------------------- 3 files changed, 42 deletions(-) diff --git a/src/func.c b/src/func.c index d1411923e..a1ab9ba39 100644 --- a/src/func.c +++ b/src/func.c @@ -2233,9 +2233,6 @@ void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ extern void sqlcipher_exportFunc(sqlite3_context *, int, sqlite3_value **); sqlite3CreateFunc(db, "sqlcipher_export", -1, SQLITE_TEXT, 0, sqlcipher_exportFunc, 0, 0, 0, 0, 0); } -#ifdef SQLCIPHER_EXT -#include "sqlcipher_funcs_init.h" -#endif #endif /* END SQLCIPHER */ } diff --git a/src/main.c b/src/main.c index 662d9f8b2..20c8c0e06 100644 --- a/src/main.c +++ b/src/main.c @@ -3589,13 +3589,6 @@ static int openDatabase( } } -#ifdef SQLCIPHER_EXT - if( !db->mallocFailed && rc==SQLITE_OK ){ - extern int sqlcipherVtabInit(sqlite3 *); - rc = sqlcipherVtabInit(db); - } -#endif - #ifdef SQLITE_ENABLE_INTERNAL_FUNCTIONS /* Testing use only!!! The -DSQLITE_ENABLE_INTERNAL_FUNCTIONS=1 compile-time ** option gives access to internal functions by default. diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 295c05550..2f88b3e36 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -242,11 +242,6 @@ static int sqlcipher_get_test_fail() { } #endif -#ifdef SQLCIPHER_EXT -int sqlcipher_ext_provider_setup(sqlcipher_provider *); -void sqlcipher_ext_provider_destroy(); -#endif - static volatile unsigned int default_flags = DEFAULT_CIPHER_FLAGS; static volatile unsigned char hmac_salt_mask = HMAC_SALT_MASK; static volatile int default_kdf_iter = PBKDF2_ITER; @@ -500,9 +495,6 @@ static void sqlcipher_activate() { #error "NO DEFAULT SQLCIPHER CRYPTO PROVIDER DEFINED" #endif sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_activate: calling sqlcipher_register_provider(%p)", p); -#ifdef SQLCIPHER_EXT - sqlcipher_ext_provider_setup(p); -#endif sqlcipher_register_provider(p); sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_activate: called sqlcipher_register_provider(%p)",p); } @@ -535,10 +527,6 @@ static void sqlcipher_deactivate() { sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER)); sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER"); -#ifdef SQLCIPHER_EXT - sqlcipher_ext_provider_destroy(); -#endif - /* last connection closed, free mutexes */ if(sqlcipher_activate_count == 0) { int i; @@ -1938,10 +1926,6 @@ static void sqlcipher_vdbe_return_string(Parse *pParse, const char *zLabel, cons sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); } -#ifdef SQLCIPHER_EXT -#include "sqlcipher_ext.h" -#endif - static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ctx) { int rc; @@ -1996,11 +1980,6 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p", db, iDb, pParse, zLeft, zRight, ctx); } -#ifdef SQLCIPHER_EXT - if(sqlcipher_ext_pragma(db, iDb, pParse, zLeft, zRight)) { - sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipher_codec_pragma: PRAGMA handled by sqlcipher_ext_pragma"); - } else -#endif #ifdef SQLCIPHER_TEST if( sqlite3_stricmp(zLeft,"cipher_test_on")==0 ){ if( zRight ) { @@ -2639,10 +2618,6 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlite3Codec: pgno=%d, mode=%d, ctx->page_sz=%d", pgno, mode, ctx->page_sz); -#ifdef SQLCIPHER_EXT - if(sqlcipher_license_check(ctx) != SQLITE_OK) return NULL; -#endif - /* call to derive keys if not present yet */ if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) { sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "sqlite3Codec: error occurred during key derivation: %d", rc); @@ -2766,13 +2741,6 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { sqlite3_mutex_enter(db->mutex); sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipherCodecAttach: entered database mutex %p", db->mutex); -#ifdef SQLCIPHER_EXT - if((rc = sqlite3_set_authorizer(db, sqlcipher_license_authorizer, db)) != SQLITE_OK) { - sqlite3_mutex_leave(db->mutex); - return rc; - } -#endif - /* point the internal codec argument against the contet to be prepared */ sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: calling sqlcipher_codec_ctx_init()"); rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, zKey, nKey); From b12328cd21b1cecdea1fc7e44e6ce36fcb16b724 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 19 Jul 2024 13:26:57 -0400 Subject: [PATCH 158/340] Corrects device logging for android and apple using preformatted messages --- src/sqlcipher.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 2f88b3e36..51e9e94e6 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -1807,11 +1807,10 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, va_list params; va_start(params, message); char formatted[MAX_LOG_LEN]; - char *out = NULL; int len = 0; #ifdef CODEC_DEBUG -#if defined(SQLCIPHER_OMIT_LOG_DEVICE) +#if defined(SQLCIPHER_OMIT_LOG_DEVICE) || (!defined(__ANDROID__) && !defined(__APPLE__)) vfprintf(stderr, message, params); fprintf(stderr, "\n"); goto end; @@ -1820,13 +1819,8 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", message, params); goto end; #elif defined(__APPLE__) - formatted = sqlite3_vmprintf(message, params); - os_log(OS_LOG_DEFAULT, "%s", formatted); - sqlite3_free(formatted); - goto end; -#else - vfprintf(stderr, message, params); - fprintf(stderr, "\n"); + sqlite3_vsnprintf(MAX_LOG_LEN, formatted, message, params); + os_log(OS_LOG_DEFAULT, "%{public}s", formatted); goto end; #endif #endif @@ -1847,9 +1841,9 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, #if !defined(SQLCIPHER_OMIT_LOG_DEVICE) if(sqlcipher_log_device) { #if defined(__ANDROID__) - __android_log_vprint(ANDROID_LOG_DEBUG, "sqlcipher", formatted); + __android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", formatted); goto end; -#elif defined(__APPLEformattes__) +#elif defined(__APPLE__) os_log(OS_LOG_DEFAULT, "%{public}s", formatted); goto end; #endif From 3ac7bde48515ec8b1505982686dac4d09f4f8f1a Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 19 Jul 2024 14:48:25 -0400 Subject: [PATCH 159/340] Allows multiple cipher_log_source values to be selected Calling cipher_log_source = NONE will reset the log source list, after which, calling cipher_log_source for individual sources (e.g. CORE, PROVIDER, MEMORY, MUTEX) will enable each respectively. This allows for the fine-grained selection of log sources (e.g. CORE and PROVIDER, but not MEMORY OR MUTEX). Note that the default log source is now ANY intead of ALL. When calling cipher_log_source with or without arguments, the PRAGMA returns a result set of the enabled levels separated by spaces. Tests for the cipher_log_source and cipher_log_level pragmas are also included. --- src/sqlcipher.c | 46 ++++++++++++++++++++++++++++--------- src/sqlcipher.h | 25 ++++++++++---------- test/sqlcipher-pragmas.test | 26 +++++++++++++++++++++ 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 51e9e94e6..4d9c81f9a 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -260,7 +260,7 @@ static sqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT]; static FILE* sqlcipher_log_file = NULL; static volatile int sqlcipher_log_device = 0; static volatile unsigned int sqlcipher_log_level = SQLCIPHER_LOG_NONE; -static volatile unsigned int sqlcipher_log_source = SQLCIPHER_LOG_ALL; +static volatile unsigned int sqlcipher_log_source = SQLCIPHER_LOG_ANY; static volatile int sqlcipher_log_set = 0; sqlite3_mutex* sqlcipher_mutex(int mutex) { @@ -1775,8 +1775,8 @@ static char *sqlcipher_get_log_level_str(unsigned int level) { return "DEBUG"; case SQLCIPHER_LOG_TRACE: return "TRACE"; - case SQLCIPHER_LOG_ALL: - return "ALL"; + case SQLCIPHER_LOG_ANY: + return "ANY"; } return "NONE"; } @@ -1797,6 +1797,29 @@ static char *sqlcipher_get_log_source_str(unsigned int source) { return "ALL"; } +static char *sqlcipher_get_log_sources_str(unsigned int source) { + if(source == SQLCIPHER_LOG_NONE) { + return sqlite3_mprintf("%s", "NONE"); + } else if (source == SQLCIPHER_LOG_ANY) { + return sqlite3_mprintf("%s", "ANY"); + } else { + char *sources = NULL; + unsigned int flag; + for(flag = SQLCIPHER_LOG_CORE; flag != 0; flag = flag << 1) { + if(SQLCIPHER_FLAG_GET(source, flag)) { + char *src = sqlcipher_get_log_source_str(flag); + if(sources) { + char *tmp = sqlite3_mprintf("%s %s", sources, src); + sqlite3_free(sources); + sources = tmp; + } else { + sources = sqlite3_mprintf("%s", src); + } + } + } + return sources; + } +} #ifndef SQLCIPHER_OMIT_LOG /* constants from https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-crt/misc/gettimeofday.c */ @@ -1827,7 +1850,7 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, #endif if( level > sqlcipher_log_level /* log level is higher, e.g. level filter is at ERROR but this message is DEBUG */ - || (sqlcipher_log_source & source) == 0 /* source filter doesn't match this message source */ + || !SQLCIPHER_FLAG_GET(sqlcipher_log_source, source) /* source filter doesn't match this message source */ || (sqlcipher_log_device == 0 && sqlcipher_log_file == NULL) /* no configured log target */ ) { /* skip logging this message */ @@ -2569,15 +2592,16 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else if( sqlite3_stricmp(zLeft, "cipher_log_source")==0 ){ if(zRight) { - sqlcipher_log_source = SQLCIPHER_LOG_NONE; if(sqlite3_stricmp(zRight, "NONE" )==0) sqlcipher_log_source = SQLCIPHER_LOG_NONE; - else if(sqlite3_stricmp(zRight, "ALL" )==0) sqlcipher_log_source = SQLCIPHER_LOG_ALL; - else if(sqlite3_stricmp(zRight, "CORE" )==0) sqlcipher_log_source = SQLCIPHER_LOG_CORE; - else if(sqlite3_stricmp(zRight, "MEMORY" )==0) sqlcipher_log_source = SQLCIPHER_LOG_MEMORY; - else if(sqlite3_stricmp(zRight, "MUTEX" )==0) sqlcipher_log_source = SQLCIPHER_LOG_MUTEX; - else if(sqlite3_stricmp(zRight, "PROVIDER")==0) sqlcipher_log_source = SQLCIPHER_LOG_PROVIDER; + else if(sqlite3_stricmp(zRight, "ANY" )==0) sqlcipher_log_source = SQLCIPHER_LOG_ANY; + else { + if(sqlite3_stricmp(zRight, "CORE" )==0) SQLCIPHER_FLAG_SET(sqlcipher_log_source, SQLCIPHER_LOG_CORE); + else if(sqlite3_stricmp(zRight, "MEMORY" )==0) SQLCIPHER_FLAG_SET(sqlcipher_log_source, SQLCIPHER_LOG_MEMORY); + else if(sqlite3_stricmp(zRight, "MUTEX" )==0) SQLCIPHER_FLAG_SET(sqlcipher_log_source, SQLCIPHER_LOG_MUTEX); + else if(sqlite3_stricmp(zRight, "PROVIDER")==0) SQLCIPHER_FLAG_SET(sqlcipher_log_source, SQLCIPHER_LOG_PROVIDER); + } } - sqlcipher_vdbe_return_string(pParse, "cipher_log_source", sqlcipher_get_log_source_str(sqlcipher_log_source), P4_TRANSIENT); + sqlcipher_vdbe_return_string(pParse, "cipher_log_source", sqlcipher_get_log_sources_str(sqlcipher_log_source), P4_DYNAMIC); } else if( sqlite3_stricmp(zLeft, "cipher_log")== 0 && zRight ){ char *status = sqlite3_mprintf("%d", sqlcipher_set_log(zRight)); diff --git a/src/sqlcipher.h b/src/sqlcipher.h index 55934a24b..e12c044ef 100644 --- a/src/sqlcipher.h +++ b/src/sqlcipher.h @@ -102,18 +102,19 @@ sqlcipher_provider* sqlcipher_get_provider(void); sqlite3_mutex* sqlcipher_mutex(int); -#define SQLCIPHER_LOG_NONE 0x00 -#define SQLCIPHER_LOG_ERROR 0x01 -#define SQLCIPHER_LOG_WARN 0x02 -#define SQLCIPHER_LOG_INFO 0x04 -#define SQLCIPHER_LOG_DEBUG 0x08 -#define SQLCIPHER_LOG_TRACE 0x10 -#define SQLCIPHER_LOG_ALL 0xffffffff - -#define SQLCIPHER_LOG_CORE 0x01 -#define SQLCIPHER_LOG_MEMORY 0x02 -#define SQLCIPHER_LOG_MUTEX 0x04 -#define SQLCIPHER_LOG_PROVIDER 0x08 +#define SQLCIPHER_LOG_NONE 0 +#define SQLCIPHER_LOG_ANY 0xffffffff + +#define SQLCIPHER_LOG_ERROR (1<<0) +#define SQLCIPHER_LOG_WARN (1<<1) +#define SQLCIPHER_LOG_INFO (1<<2) +#define SQLCIPHER_LOG_DEBUG (1<<3) +#define SQLCIPHER_LOG_TRACE (1<<4) + +#define SQLCIPHER_LOG_CORE (1<<0) +#define SQLCIPHER_LOG_MEMORY (1<<1) +#define SQLCIPHER_LOG_MUTEX (1<<2) +#define SQLCIPHER_LOG_PROVIDER (1<<3) #ifdef SQLCIPHER_OMIT_LOG #define sqlcipher_log(level, source, message, ...) diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index bf3e1a1af..8c39ca019 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -468,6 +468,32 @@ do_test verify-cipher_default_settings_default { db close file delete -force test.db +do_test verify-cipher_log_source { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_log_source; -- default should be ANY + PRAGMA cipher_log_source = NONE; --reset to NONE + PRAGMA cipher_log_source = PROVIDER; -- add PROVIDER to log source + PRAGMA cipher_log_source = CORE; -- add CORE to log source + PRAGMA cipher_log_source = MEMORY; -- add MEMORY to log source + PRAGMA cipher_log_source = NOTASOURCE; -- stay the same + PRAGMA cipher_log_source = ANY; -- reset to ANY + } +} {ANY NONE PROVIDER {CORE PROVIDER} {CORE MEMORY PROVIDER} {CORE MEMORY PROVIDER} ANY} +db close +do_test verify-cipher_log_level { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_log_level; -- default should be error + PRAGMA cipher_log_level = TRACE; + PRAGMA cipher_log_level = DEBUG; + PRAGMA cipher_log_level = INFO; + PRAGMA cipher_log_level = WARN; + PRAGMA cipher_log_level = ERROR; + PRAGMA cipher_log_level = NOTALEVEL; -- an unknown level should set back to none + } +} {WARN TRACE DEBUG INFO WARN ERROR NONE} +db close finish_test From 29497ee55960fa460b2f2d88d03752e0783a8e67 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 19 Jul 2024 15:25:35 -0400 Subject: [PATCH 160/340] Resolve a remaining reference to log source ALL --- src/sqlcipher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 4d9c81f9a..78deddf3b 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -1794,7 +1794,7 @@ static char *sqlcipher_get_log_source_str(unsigned int source) { case SQLCIPHER_LOG_PROVIDER: return "PROVIDER"; } - return "ALL"; + return "ANY"; } static char *sqlcipher_get_log_sources_str(unsigned int source) { From 2e01826a45312bd0db722722e337bd03a882f778 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 19 Jul 2024 16:18:28 -0400 Subject: [PATCH 161/340] Converts to __android_log_write for device log operations --- src/sqlcipher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 78deddf3b..6cc4661f8 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -1864,7 +1864,7 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, #if !defined(SQLCIPHER_OMIT_LOG_DEVICE) if(sqlcipher_log_device) { #if defined(__ANDROID__) - __android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", formatted); + __android_log_write(ANDROID_LOG_DEBUG, "sqlcipher", formatted); goto end; #elif defined(__APPLE__) os_log(OS_LOG_DEFAULT, "%{public}s", formatted); From d170d899434b6b99f53169e45cdb503af9afca8d Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 23 Jul 2024 15:30:30 -0400 Subject: [PATCH 162/340] Restores default log level and corrects comment in test --- test/sqlcipher-pragmas.test | 2 +- test/sqlcipher.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index 8c39ca019..a6f69da3f 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -485,7 +485,7 @@ db close do_test verify-cipher_log_level { sqlite_orig db :memory: execsql { - PRAGMA cipher_log_level; -- default should be error + PRAGMA cipher_log_level; -- default should be WARN PRAGMA cipher_log_level = TRACE; PRAGMA cipher_log_level = DEBUG; PRAGMA cipher_log_level = INFO; diff --git a/test/sqlcipher.test b/test/sqlcipher.test index c30d30c8b..88ab1b5c4 100644 --- a/test/sqlcipher.test +++ b/test/sqlcipher.test @@ -43,7 +43,7 @@ set pretests "" sqlite_orig db :memory: execsql { - PRAGMA cipher_log_level = NONE; + PRAGMA cipher_log = 'sqlcipher-test.log'; } db close From 7dfc063b834df99cd58a888e10891d03585288fb Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 24 Jul 2024 11:47:53 -0400 Subject: [PATCH 163/340] Specifies BSD-3-Clause license in Podspec (Issue #522) --- SQLCipher.podspec.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index fe924726a..a4b193ed2 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -3,7 +3,10 @@ "default_subspecs": "standard", "description": "SQLCipher is an open source extension to SQLite that provides transparent 256-bit AES encryption of database files.", "homepage": "https://www.zetetic.net/sqlcipher/", - "license": "BSD", + "license": { + "type": "BSD-3-Clause", + "file": "LICENSE.md" + } "name": "SQLCipher", "platforms": { "ios": "12.0", From 8e3f8079d13978e185b2f635ef1a398186337440 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 24 Jul 2024 12:05:53 -0400 Subject: [PATCH 164/340] Corrects Podspec license element format --- SQLCipher.podspec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index a4b193ed2..90c5eb3af 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -6,7 +6,7 @@ "license": { "type": "BSD-3-Clause", "file": "LICENSE.md" - } + }, "name": "SQLCipher", "platforms": { "ios": "12.0", From bfa3141e2e478f56385743835f209154b8a3b526 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 1 Aug 2024 10:23:27 -0400 Subject: [PATCH 165/340] Fixes minor compiler warnings related to size_t conversions --- src/sqlcipher.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 6cc4661f8..f0cc7ab2b 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -1830,7 +1830,7 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, va_list params; va_start(params, message); char formatted[MAX_LOG_LEN]; - int len = 0; + size_t len = 0; #ifdef CODEC_DEBUG #if defined(SQLCIPHER_OMIT_LOG_DEVICE) || (!defined(__ANDROID__) && !defined(__APPLE__)) @@ -1859,7 +1859,7 @@ void sqlcipher_log(unsigned int level, unsigned int source, const char *message, sqlite3_snprintf(MAX_LOG_LEN, formatted, "%s %s ", sqlcipher_get_log_level_str(level), sqlcipher_get_log_source_str(source)); len = strlen(formatted); - sqlite3_vsnprintf(MAX_LOG_LEN - len, formatted + len, message, params); + sqlite3_vsnprintf(MAX_LOG_LEN - (int) len, formatted + (int) len, message, params); #if !defined(SQLCIPHER_OMIT_LOG_DEVICE) if(sqlcipher_log_device) { From 5c90435c8e3b5ac7a48ea5cb77d0d7a5073f08ef Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 1 Aug 2024 10:39:04 -0400 Subject: [PATCH 166/340] Replaces use of deprecated WINAPI_FAMILY_APP macro with WINAPI_FAMILY_PC_APP --- src/sqlcipher.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index f0cc7ab2b..fd43dd7b3 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -284,7 +284,7 @@ static void sqlcipher_mlock(void *ptr, sqlite_uint64 sz) { sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: mlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) -#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) +#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_PC_APP)) int rc; sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_mlock: calling VirtualLock(%p,%d)", ptr, sz); rc = VirtualLock(ptr, sz); @@ -312,7 +312,7 @@ static void sqlcipher_munlock(void *ptr, sqlite_uint64 sz) { sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_munlock: munlock(%p,%lu) returned %d errno=%d", ptr - offset, sz + offset, rc, errno); } #elif defined(_WIN32) -#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP)) +#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_PC_APP)) int rc; if(ptr == NULL || sz == 0) return; From 61bf74fdf66433a73b86abb65b0c460ee9c35cfe Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Thu, 1 Aug 2024 10:53:16 -0400 Subject: [PATCH 167/340] Updates minimum working set size on windows to increase lockable pages Under certain conditions the number of VirtualLock-able pages could be exceeded on windows due to the low default working set size. SQLCipher will now check whether the process is using default working set sizes, and if so, it will adjust the minimum working set size to allow additional locked pages. The maximum working set size will not be changed. --- src/sqlcipher.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index fd43dd7b3..b690deb0c 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -406,6 +406,55 @@ void sqlcipher_init_memmethods() { sqlite3_config(SQLITE_CONFIG_MALLOC, &sqlcipher_mem_methods) != SQLITE_OK) { sqlcipher_mem_security_on = sqlcipher_mem_executed = sqlcipher_mem_initialized = 0; } else { +#if !defined(OMIT_MEMLOCK) && defined(_WIN32) && !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_PC_APP)) + HANDLE process = NULL; + SYSTEM_INFO info; + SIZE_T dflt_min_size, dflt_max_size, min_size, max_size; + DWORD pid = GetCurrentProcessId(); + + if(pid == 0) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: error calling GetCurrentProcessId: %d", GetLastError()); + goto cleanup; + } + + if((process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_SET_QUOTA, FALSE, pid)) == NULL) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: error calling OpenProcess for pid=%d: %d", pid, GetLastError()); + goto cleanup; + } + + /* lookup native memory page size for caclulating default working set sizes */ + GetNativeSystemInfo(&info); + + /* https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-setprocessworkingsetsize#parameters + Default Min Set Size is 50 pages. + Default Max Set Size is 345 pages */ + dflt_min_size = info.dwPageSize * 50; + dflt_max_size = info.dwPageSize * 345; + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: calculated dflt_min_size=%zu dflt_max_size=%zu for memory page size %d", dflt_min_size, dflt_max_size, info.dwPageSize); + + /* retrieve current min and max set sizes for comparison */ + if(!GetProcessWorkingSetSize(process, &min_size, &max_size)) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: error calling GetProcessWorkingSetSize %d", GetLastError()); + goto cleanup; + } + sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: GetProcessWorkingSetSize returned min=%zu max=%zu", min_size, max_size); + + if(min_size == dflt_min_size && max_size == dflt_max_size) { + /* application has not set any special non-default working set sizes. Caclulate the new min working set size to be + 5 times default to allow greater number of pages to be VirtualLocked, max size will be left unchanged */ + min_size *= 5; + if(!SetProcessWorkingSetSize(process, min_size, max_size)) { + sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: error calling SetProcessWorkingSetSize with min=%zu max=%zu: %d", min_size, max_size, GetLastError()); + goto cleanup; + } + sqlcipher_log(SQLCIPHER_LOG_DEBUG, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: called SetProcessWorkingSetSize for min=%zu max=%zu", min_size, max_size); + } else { + sqlcipher_log(SQLCIPHER_LOG_INFO, SQLCIPHER_LOG_MEMORY, "sqlcipher_init_memmethods: application has custom working set sizes min=%zu max=%zu - skipped alteration of working set sizes", min_size, max_size); + } + +cleanup: + if (process) CloseHandle(process); +#endif sqlcipher_mem_initialized = 1; } } From 7b5ca787a57e1679b39173815c6c4389108a32f9 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 7 Aug 2024 11:19:57 -0400 Subject: [PATCH 168/340] Updates changelog --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e7c7dc9e..271d0a79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. ## [unreleased] - (? 2024 - [unreleased changes]) +## [4.6.1] - (August 2024 - [4.6.1 changes]) +- Updates baseline to upstream SQLite 3.46.0 +- Significant refactor to merge `crypto.h`, `crypto.c`, and `crypto_impl.c` into a single `sqlcipher.c` source file for simplicity. +- Updates minimum working set size on windows to increase lockable pages +- Adds new `PRAGMA cipher_log_source` for filtering log output on higher verbosity levels +- Improves log output by including the log level and source prior to message +- Improves error logging in `PRAGMA cipher_migrate` +- Fixes issue where log level and target would be overwritten if set prior to initialization +- Corrects Podspec license element to use specific BSD 3 Clause + ## [4.6.0] - (May 2024 - [4.6.0 changes]) - Sets default log level to WARN - Sends default log output to: logcat for Android; Console for iOS and macOS; and stderr for all other platforms @@ -249,7 +259,9 @@ All notable changes to this project will be documented in this file. - Change KDF iteration length from 4,000 to 64,000 [unreleased]: https://github.com/sqlcipher/sqlcipher/tree/prerelease -[unreleased changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.6.0...prerelease +[unreleased changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.6.1...prerelease +[4.6.1]: https://github.com/sqlcipher/sqlcipher/tree/v4.6.1 +[4.6.1 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.6.0...v4.6.1 [4.6.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.6.0 [4.6.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.7...v4.6.0 [4.5.7]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.7 From 686e3b83401318ca4b101022c076dd818886283e Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 13 Aug 2024 16:24:48 -0400 Subject: [PATCH 169/340] Snapshot of upstream SQLite 3.46.1 --- VERSION | 2 +- autoconf/tea/configure.ac | 2 +- configure | 18 +-- ext/consio/console_io.c | 5 - ext/expert/expert1.test | 7 + ext/expert/sqlite3expert.c | 20 ++- ext/fts5/fts5_expr.c | 7 +- ext/fts5/fts5_main.c | 29 ++-- ext/fts5/fts5_tokenize.c | 12 +- ext/fts5/test/fts5aux.test | 24 +++ ext/fts5/test/fts5integrity.test | 27 ++++ ext/fts5/test/fts5secure8.test | 16 ++ ext/fts5/test/fts5tokenizer2.test | 20 +++ ext/fts5/test/fts5trigram.test | 5 +- ext/fts5/test/fts5trigram2.test | 3 + ext/recover/sqlite3recover.c | 2 +- ext/wasm/api/sqlite3-opfs-async-proxy.js | 187 ++++------------------- ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 36 ++++- manifest | 77 +++++----- manifest.uuid | 2 +- src/alter.c | 2 +- src/build.c | 7 +- src/func.c | 2 + src/insert.c | 1 + src/parse.y | 6 +- src/resolve.c | 17 ++- src/shell.c.in | 13 +- src/sqliteInt.h | 2 +- src/vdbeaux.c | 6 +- src/where.c | 51 ++----- test/returning1.test | 13 ++ test/select7.test | 32 ++++ test/shell5.test | 12 ++ test/window1.test | 18 +++ 34 files changed, 378 insertions(+), 305 deletions(-) diff --git a/VERSION b/VERSION index 850ac8f28..421931ea1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.46.0 +3.46.1 diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index f188f2203..ea7eeda70 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.46.0]) +AC_INIT([sqlite],[3.46.1]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/configure b/configure index f6717f96f..5ad0ccad3 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.69 for sqlite 3.46.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.46.1. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.46.0' -PACKAGE_STRING='sqlite 3.46.0' +PACKAGE_VERSION='3.46.1' +PACKAGE_STRING='sqlite 3.46.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1472,7 +1472,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.46.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.46.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1537,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.46.0:";; + short | recursive ) echo "Configuration of sqlite 3.46.1:";; esac cat <<\_ACEOF @@ -1668,7 +1668,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.46.0 +sqlite configure 3.46.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2087,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.46.0, which was +It was created by sqlite $as_me 3.46.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.46.0, which was +This file was extended by sqlite $as_me 3.46.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12547,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.46.0 +sqlite config.status 3.46.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c index 3e2f556f5..3fa613ba9 100755 --- a/ext/consio/console_io.c +++ b/ext/consio/console_io.c @@ -53,11 +53,6 @@ # define CIO_WIN_WC_XLATE 0 /* Not exposing translation routines at all */ #endif -#if CIO_WIN_WC_XLATE -/* Character used to represent a known-incomplete UTF-8 char group (�) */ -static WCHAR cBadGroup = 0xfffd; -#endif - #if CIO_WIN_WC_XLATE static HANDLE handleOfFile(FILE *pf){ int fileDesc = _fileno(pf); diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index c456c30c5..72c4fd72c 100644 --- a/ext/expert/expert1.test +++ b/ext/expert/expert1.test @@ -465,6 +465,13 @@ do_execsql_test 5.3 { t2 t2_idx_0001295b {100 20 5} } +do_catchsql_test 5.4 { + SELECT sqlite_expert_rem(123, 123); +} {1 {no such function: sqlite_expert_rem}} +do_catchsql_test 5.5 { + SELECT sqlite_expert_sample(); +} {1 {no such function: sqlite_expert_sample}} + if 0 { do_test expert1-6.0 { catchcmd :memory: { diff --git a/ext/expert/sqlite3expert.c b/ext/expert/sqlite3expert.c index 276c2cc9f..b59a59728 100644 --- a/ext/expert/sqlite3expert.c +++ b/ext/expert/sqlite3expert.c @@ -626,7 +626,7 @@ static int expertFilter( pCsr->pData = 0; if( rc==SQLITE_OK ){ rc = idxPrintfPrepareStmt(pExpert->db, &pCsr->pData, &pVtab->base.zErrMsg, - "SELECT * FROM main.%Q WHERE sample()", pVtab->pTab->zName + "SELECT * FROM main.%Q WHERE sqlite_expert_sample()", pVtab->pTab->zName ); } @@ -1500,7 +1500,7 @@ struct IdxRemCtx { }; /* -** Implementation of scalar function rem(). +** Implementation of scalar function sqlite_expert_rem(). */ static void idxRemFunc( sqlite3_context *pCtx, @@ -1513,7 +1513,7 @@ static void idxRemFunc( assert( argc==2 ); iSlot = sqlite3_value_int(argv[0]); - assert( iSlot<=p->nSlot ); + assert( iSlotnSlot ); pSlot = &p->aSlot[iSlot]; switch( pSlot->eType ){ @@ -1624,7 +1624,8 @@ static int idxPopulateOneStat1( const char *zName = (const char*)sqlite3_column_text(pIndexXInfo, 0); const char *zColl = (const char*)sqlite3_column_text(pIndexXInfo, 1); zCols = idxAppendText(&rc, zCols, - "%sx.%Q IS rem(%d, x.%Q) COLLATE %s", zComma, zName, nCol, zName, zColl + "%sx.%Q IS sqlite_expert_rem(%d, x.%Q) COLLATE %s", + zComma, zName, nCol, zName, zColl ); zOrder = idxAppendText(&rc, zOrder, "%s%d", zComma, ++nCol); } @@ -1757,13 +1758,13 @@ static int idxPopulateStat1(sqlite3expert *p, char **pzErr){ if( rc==SQLITE_OK ){ sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv); - rc = sqlite3_create_function( - dbrem, "rem", 2, SQLITE_UTF8, (void*)pCtx, idxRemFunc, 0, 0 + rc = sqlite3_create_function(dbrem, "sqlite_expert_rem", + 2, SQLITE_UTF8, (void*)pCtx, idxRemFunc, 0, 0 ); } if( rc==SQLITE_OK ){ - rc = sqlite3_create_function( - p->db, "sample", 0, SQLITE_UTF8, (void*)&samplectx, idxSampleFunc, 0, 0 + rc = sqlite3_create_function(p->db, "sqlite_expert_sample", + 0, SQLITE_UTF8, (void*)&samplectx, idxSampleFunc, 0, 0 ); } @@ -1815,6 +1816,9 @@ static int idxPopulateStat1(sqlite3expert *p, char **pzErr){ rc = sqlite3_exec(p->dbm, "ANALYZE sqlite_schema", 0, 0, 0); } + sqlite3_create_function(p->db, "sqlite_expert_rem", 2, SQLITE_UTF8, 0,0,0,0); + sqlite3_create_function(p->db, "sqlite_expert_sample", 0,SQLITE_UTF8,0,0,0,0); + sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0); return rc; } diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 05c1b59c1..960a4d468 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -324,7 +324,11 @@ int sqlite3Fts5ExprNew( } sqlite3_free(sParse.apPhrase); - *pzErr = sParse.zErr; + if( 0==*pzErr ){ + *pzErr = sParse.zErr; + }else{ + sqlite3_free(sParse.zErr); + } return sParse.rc; } @@ -2452,6 +2456,7 @@ Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( assert( pRight->eType==FTS5_STRING || pRight->eType==FTS5_TERM || pRight->eType==FTS5_EOF + || (pRight->eType==FTS5_AND && pParse->bPhraseToAnd) ); if( pLeft->eType==FTS5_AND ){ diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index f609f7f34..837ea4015 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -1700,6 +1700,7 @@ static int fts5UpdateMethod( rc = SQLITE_ERROR; }else{ rc = fts5SpecialDelete(pTab, apVal); + bUpdateOrDelete = 1; } }else{ rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); @@ -2874,14 +2875,16 @@ int sqlite3Fts5GetTokenizer( if( pMod==0 ){ assert( nArg>0 ); rc = SQLITE_ERROR; - *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]); + if( pzErr ) *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]); }else{ rc = pMod->x.xCreate( pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->pTok ); pConfig->pTokApi = &pMod->x; if( rc!=SQLITE_OK ){ - if( pzErr ) *pzErr = sqlite3_mprintf("error in tokenizer constructor"); + if( pzErr && rc!=SQLITE_NOMEM ){ + *pzErr = sqlite3_mprintf("error in tokenizer constructor"); + } }else{ pConfig->ePattern = sqlite3Fts5TokenizerPattern( pMod->x.xCreate, pConfig->pTok @@ -2975,17 +2978,23 @@ static int fts5IntegrityMethod( assert( pzErr!=0 && *pzErr==0 ); UNUSED_PARAM(isQuick); + assert( pTab->p.pConfig->pzErrmsg==0 ); + pTab->p.pConfig->pzErrmsg = pzErr; rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0); - if( (rc&0xff)==SQLITE_CORRUPT ){ - *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", - zSchema, zTabname); - rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM; - }else if( rc!=SQLITE_OK ){ - *pzErr = sqlite3_mprintf("unable to validate the inverted index for" - " FTS5 table %s.%s: %s", - zSchema, zTabname, sqlite3_errstr(rc)); + if( *pzErr==0 && rc!=SQLITE_OK ){ + if( (rc&0xff)==SQLITE_CORRUPT ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", + zSchema, zTabname); + rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM; + }else{ + *pzErr = sqlite3_mprintf("unable to validate the inverted index for" + " FTS5 table %s.%s: %s", + zSchema, zTabname, sqlite3_errstr(rc)); + } } + sqlite3Fts5IndexCloseReader(pTab->p.pIndex); + pTab->p.pConfig->pzErrmsg = 0; return rc; } diff --git a/ext/fts5/fts5_tokenize.c b/ext/fts5/fts5_tokenize.c index f12056170..2200e7837 100644 --- a/ext/fts5/fts5_tokenize.c +++ b/ext/fts5/fts5_tokenize.c @@ -79,7 +79,7 @@ static int fts5AsciiCreate( int i; memset(p, 0, sizeof(AsciiTokenizer)); memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar)); - for(i=0; rc==SQLITE_OK && ibFold = 1; pNew->iFoldParam = 0; - for(i=0; rc==SQLITE_OK && iiFoldParam!=0 && pNew->bFold==0 ){ rc = SQLITE_ERROR; diff --git a/ext/fts5/test/fts5aux.test b/ext/fts5/test/fts5aux.test index 5569f48cf..796300637 100644 --- a/ext/fts5/test/fts5aux.test +++ b/ext/fts5/test/fts5aux.test @@ -377,4 +377,28 @@ do_catchsql_test 12.3.3 { SELECT fts5_collist(t1, 1) FROM t1('one AND two'); } {0 1} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 13.1 { + CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=ascii); + INSERT INTO t1 VALUES('a b c'), ('d e f'); + PRAGMA integrity_check; +} {ok} + +do_catchsql_test 13.2 { + SELECT highlight(t1, 0, '[', ']') FROM t1 +} {0 {{a b c} {d e f}}} + +do_execsql_test 13.3 { + PRAGMA writable_schema = 1; + UPDATE sqlite_schema SET sql = 'CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=blah)' + WHERE name = 't1'; +} + +db close +sqlite3 db test.db +do_catchsql_test 13.4 { + SELECT highlight(t1, 0, '[', ']') FROM t1 +} {1 {no such tokenizer: blah}} + finish_test diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test index 1bb367538..5ed659b1f 100644 --- a/ext/fts5/test/fts5integrity.test +++ b/ext/fts5/test/fts5integrity.test @@ -380,5 +380,32 @@ do_execsql_test 12.3 { } {ok} +#------------------------------------------------------------------- +reset_db +do_execsql_test 13.1 { + CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=ascii); + INSERT INTO t1 VALUES('a b c'), ('d e f'); + PRAGMA integrity_check; +} {ok} + +db close +sqlite3 db test.db +do_catchsql_test 13.2 { + PRAGMA integrity_check; +} {0 ok} + +do_execsql_test 13.3 { + PRAGMA writable_schema = 1; + UPDATE sqlite_schema SET sql = 'CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=blah)' + WHERE name = 't1'; +} + +db close +sqlite3 db test.db +breakpoint +do_catchsql_test 13.4 { + PRAGMA integrity_check; +} {1 {no such tokenizer: blah}} + finish_test diff --git a/ext/fts5/test/fts5secure8.test b/ext/fts5/test/fts5secure8.test index 8ceb9630a..0216bb6ea 100644 --- a/ext/fts5/test/fts5secure8.test +++ b/ext/fts5/test/fts5secure8.test @@ -42,6 +42,22 @@ do_execsql_test 1.2 { PRAGMA integrity_check; } {ok} +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE xyz USING fts5 ( + name, + content='' + ); + + INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 1); + INSERT INTO xyz (rowid, name) VALUES(1, 'A'); + INSERT INTO xyz (rowid, name) VALUES(2, 'A'); + INSERT INTO xyz(xyz, rowid, name) VALUES('delete', 2, 'A'); +} + +do_execsql_test 2.1 { + pragma quick_check; +} {ok} + diff --git a/ext/fts5/test/fts5tokenizer2.test b/ext/fts5/test/fts5tokenizer2.test index bdabd5312..52b30326a 100644 --- a/ext/fts5/test/fts5tokenizer2.test +++ b/ext/fts5/test/fts5tokenizer2.test @@ -85,5 +85,25 @@ do_execsql_test 1.7 { SELECT highlight(t1, 0, '>', '<') FROM t1('BB mess'); } {AAdont>BBmess<} +# 2024-08-06 https://sqlite.org/forum/forumpost/171bcc2bcd +# Error handling of tokenize= arguments. +# +foreach {n tkz} { + 1 {ascii none} + 2 {unicode61 none} + 3 {porter none} + 4 {trigram none} + 5 {ascii none 0} + 6 {unicode61 none 0} + 7 {porter none 0} + 8 {trigram none 0} +} { + db eval {DROP TABLE IF EXISTS t2;} + do_catchsql_test 2.$n " + DROP TABLE IF EXISTS t2; + CREATE VIRTUAL TABLE t2 USING fts5(a,b,c,tokenize='$tkz'); + " {1 {error in tokenizer constructor}} +} + finish_test diff --git a/ext/fts5/test/fts5trigram.test b/ext/fts5/test/fts5trigram.test index 351c059bf..752686620 100644 --- a/ext/fts5/test/fts5trigram.test +++ b/ext/fts5/test/fts5trigram.test @@ -69,6 +69,9 @@ do_execsql_test 2.0 { INSERT INTO t1 VALUES('abcdefghijklm'); INSERT INTO t1 VALUES('กรุงเทพมหานคร'); } +do_catchsql_test 2.0.1 { + CREATE VIRTUAL TABLE t2 USING fts5(z, tokenize='trigram case_sensitive'); +} {1 {error in tokenizer constructor}} foreach {tn s res} { 1 abc "(abc)defghijklm" @@ -206,7 +209,7 @@ do_execsql_test 7.0 { (20, "жираф.png"), (30, "cat.png"), (40, "кот.png"), - (50, "misic-🎵-.mp3"); + (50, "misic-🎵-.mp3"); } do_execsql_test 7.1 { SELECT rowid FROM f WHERE +filename GLOB '*ир*'; diff --git a/ext/fts5/test/fts5trigram2.test b/ext/fts5/test/fts5trigram2.test index f5beae5b2..395d8994b 100644 --- a/ext/fts5/test/fts5trigram2.test +++ b/ext/fts5/test/fts5trigram2.test @@ -21,6 +21,9 @@ do_execsql_test 1.0 " INSERT INTO t1 VALUES('abc\u0303defghijklm'); INSERT INTO t1 VALUES('a\u0303b\u0303c\u0303defghijklm'); " +do_catchsql_test 1.0.1 { + CREATE VIRTUAL TABLE t2 USING fts5(z, tokenize='trigram remove_diacritics'); +} {1 {error in tokenizer constructor}} do_execsql_test 1.1 { SELECT highlight(t1, 0, '(', ')') FROM t1('abc'); diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index 1d858c0ab..afa1ae861 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -363,8 +363,8 @@ static int recoverError( va_start(ap, zFmt); if( zFmt ){ z = sqlite3_vmprintf(zFmt, ap); - va_end(ap); } + va_end(ap); sqlite3_free(p->zErrMsg); p->zErrMsg = z; p->errCode = errCode; diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js index e671094f0..3e2b20ffb 100644 --- a/ext/wasm/api/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -87,35 +87,6 @@ const installAsyncProxy = function(){ const log = (...args)=>logImpl(2, ...args); const warn = (...args)=>logImpl(1, ...args); const error = (...args)=>logImpl(0, ...args); - const metrics = Object.create(null); - metrics.reset = ()=>{ - let k; - const r = (m)=>(m.count = m.time = m.wait = 0); - for(k in state.opIds){ - r(metrics[k] = Object.create(null)); - } - let s = metrics.s11n = Object.create(null); - s = s.serialize = Object.create(null); - s.count = s.time = 0; - s = metrics.s11n.deserialize = Object.create(null); - s.count = s.time = 0; - }; - metrics.dump = ()=>{ - let k, n = 0, t = 0, w = 0; - for(k in state.opIds){ - const m = metrics[k]; - n += m.count; - t += m.time; - w += m.wait; - m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; - } - console.log(globalThis?.location?.href, - "metrics for",globalThis?.location?.href,":\n", - metrics, - "\nTotal of",n,"op(s) for",t,"ms", - "approx",w,"ms spent waiting on OPFS APIs."); - console.log("Serialization metrics:",metrics.s11n); - }; /** __openFiles is a map of sqlite3_file pointers (integers) to @@ -265,23 +236,34 @@ const installAsyncProxy = function(){ this.name = 'GetSyncHandleError'; } }; + + /** + Attempts to find a suitable SQLITE_xyz result code for Error + object e. Returns either such a translation or rc if if it does + not know how to translate the exception. + */ GetSyncHandleError.convertRc = (e,rc)=>{ - if(1){ - return ( - e instanceof GetSyncHandleError - && ((e.cause.name==='NoModificationAllowedError') - /* Inconsistent exception.name from Chrome/ium with the - same exception.message text: */ - || (e.cause.name==='DOMException' - && 0===e.cause.message.indexOf('Access Handles cannot'))) - ) ? ( - /*console.warn("SQLITE_BUSY",e),*/ - state.sq3Codes.SQLITE_BUSY - ) : rc; - }else{ - return rc; + if( e instanceof GetSyncHandleError ){ + if( e.cause.name==='NoModificationAllowedError' + /* Inconsistent exception.name from Chrome/ium with the + same exception.message text: */ + || (e.cause.name==='DOMException' + && 0===e.cause.message.indexOf('Access Handles cannot')) ){ + return state.sq3Codes.SQLITE_BUSY; + }else if( 'NotFoundError'===e.cause.name ){ + /** + Maintenance reminder: SQLITE_NOTFOUND, though it looks like + a good match, has different semantics than NotFoundError + and is not suitable here. + */ + return state.sq3Codes.SQLITE_CANTOPEN; + } + }else if( 'NotFoundError'===e?.name ){ + return state.sq3Codes.SQLITE_CANTOPEN; } - } + return rc; + }; + /** Returns the sync access handle associated with the given file handle object (which must be a valid handle object, as created by @@ -347,37 +329,6 @@ const installAsyncProxy = function(){ if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); }; - /** - We track 2 different timers: the "metrics" timer records how much - time we spend performing work. The "wait" timer records how much - time we spend waiting on the underlying OPFS timer. See the calls - to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd() - throughout this file to see how they're used. - */ - const __mTimer = Object.create(null); - __mTimer.op = undefined; - __mTimer.start = undefined; - const mTimeStart = (op)=>{ - __mTimer.start = performance.now(); - __mTimer.op = op; - //metrics[op] || toss("Maintenance required: missing metrics for",op); - ++metrics[op].count; - }; - const mTimeEnd = ()=>( - metrics[__mTimer.op].time += performance.now() - __mTimer.start - ); - const __wTimer = Object.create(null); - __wTimer.op = undefined; - __wTimer.start = undefined; - const wTimeStart = (op)=>{ - __wTimer.start = performance.now(); - __wTimer.op = op; - //metrics[op] || toss("Maintenance required: missing metrics for",op); - }; - const wTimeEnd = ()=>( - metrics[__wTimer.op].wait += performance.now() - __wTimer.start - ); - /** Gets set to true by the 'opfs-async-shutdown' command to quit the wait loop. This is only intended for debugging purposes: we cannot @@ -388,37 +339,24 @@ const installAsyncProxy = function(){ /** Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods - methods, as well as helpers like mkdir(). Maintenance reminder: - members are in alphabetical order to simplify finding them. + methods, as well as helpers like mkdir(). */ const vfsAsyncImpls = { - 'opfs-async-metrics': async ()=>{ - mTimeStart('opfs-async-metrics'); - metrics.dump(); - storeAndNotify('opfs-async-metrics', 0); - mTimeEnd(); - }, 'opfs-async-shutdown': async ()=>{ flagAsyncShutdown = true; storeAndNotify('opfs-async-shutdown', 0); }, mkdir: async (dirname)=>{ - mTimeStart('mkdir'); let rc = 0; - wTimeStart('mkdir'); try { await getDirForFilename(dirname+"/filepart", true); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR; - }finally{ - wTimeEnd(); } storeAndNotify('mkdir', rc); - mTimeEnd(); }, xAccess: async (filename)=>{ - mTimeStart('xAccess'); /* OPFS cannot support the full range of xAccess() queries sqlite3 calls for. We can essentially just tell if the file is accessible, but if it is then it's automatically writable @@ -431,26 +369,20 @@ const installAsyncProxy = function(){ accessible, non-0 means not accessible. */ let rc = 0; - wTimeStart('xAccess'); try{ const [dh, fn] = await getDirForFilename(filename); await dh.getFileHandle(fn); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR; - }finally{ - wTimeEnd(); } storeAndNotify('xAccess', rc); - mTimeEnd(); }, xClose: async function(fid/*sqlite3_file pointer*/){ const opName = 'xClose'; - mTimeStart(opName); __implicitLocks.delete(fid); const fh = __openFiles[fid]; let rc = 0; - wTimeStart(opName); if(fh){ delete __openFiles[fid]; await closeSyncHandle(fh); @@ -462,15 +394,11 @@ const installAsyncProxy = function(){ state.s11n.serialize(); rc = state.sq3Codes.SQLITE_NOTFOUND; } - wTimeEnd(); storeAndNotify(opName, rc); - mTimeEnd(); }, xDelete: async function(...args){ - mTimeStart('xDelete'); const rc = await vfsAsyncImpls.xDeleteNoWait(...args); storeAndNotify('xDelete', rc); - mTimeEnd(); }, xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ /* The syncDir flag is, for purposes of the VFS API's semantics, @@ -486,7 +414,6 @@ const installAsyncProxy = function(){ is false. */ let rc = 0; - wTimeStart('xDelete'); try { while(filename){ const [hDir, filenamePart] = await getDirForFilename(filename, false); @@ -502,14 +429,11 @@ const installAsyncProxy = function(){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR_DELETE; } - wTimeEnd(); return rc; }, xFileSize: async function(fid/*sqlite3_file pointer*/){ - mTimeStart('xFileSize'); const fh = __openFiles[fid]; let rc = 0; - wTimeStart('xFileSize'); try{ const sz = await (await getSyncHandle(fh,'xFileSize')).getSize(); state.s11n.serialize(Number(sz)); @@ -518,19 +442,15 @@ const installAsyncProxy = function(){ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR); } await releaseImplicitLock(fh); - wTimeEnd(); storeAndNotify('xFileSize', rc); - mTimeEnd(); }, xLock: async function(fid/*sqlite3_file pointer*/, lockType/*SQLITE_LOCK_...*/){ - mTimeStart('xLock'); const fh = __openFiles[fid]; let rc = 0; const oldLockType = fh.xLock; fh.xLock = lockType; if( !fh.syncHandle ){ - wTimeStart('xLock'); try { await getSyncHandle(fh,'xLock'); __implicitLocks.delete(fid); @@ -539,18 +459,14 @@ const installAsyncProxy = function(){ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK); fh.xLock = oldLockType; } - wTimeEnd(); } storeAndNotify('xLock',rc); - mTimeEnd(); }, xOpen: async function(fid/*sqlite3_file pointer*/, filename, flags/*SQLITE_OPEN_...*/, opfsFlags/*OPFS_...*/){ const opName = 'xOpen'; - mTimeStart(opName); const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags); - wTimeStart('xOpen'); try{ let hDir, filenamePart; try { @@ -558,8 +474,6 @@ const installAsyncProxy = function(){ }catch(e){ state.s11n.storeException(1,e); storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); - mTimeEnd(); - wTimeEnd(); return; } if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){ @@ -571,7 +485,6 @@ const installAsyncProxy = function(){ } } const hFile = await hDir.getFileHandle(filenamePart, {create}); - wTimeEnd(); const fh = Object.assign(Object.create(null),{ fid: fid, filenameAbs: filename, @@ -586,76 +499,50 @@ const installAsyncProxy = function(){ fh.releaseImplicitLocks = (opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP) || state.opfsFlags.defaultUnlockAsap; - if(0 /* this block is modelled after something wa-sqlite - does but it leads to immediate contention on journal files. - Update: this approach reportedly only works for DELETE journal - mode. */ - && (0===(flags & state.sq3Codes.SQLITE_OPEN_MAIN_DB))){ - /* sqlite does not lock these files, so go ahead and grab an OPFS - lock. */ - fh.xLock = "xOpen"/* Truthy value to keep entry from getting - flagged as auto-locked. String value so - that we can easily distinguish is later - if needed. */; - await getSyncHandle(fh,'xOpen'); - } __openFiles[fid] = fh; storeAndNotify(opName, 0); }catch(e){ - wTimeEnd(); error(opName,e); state.s11n.storeException(1,e); storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); } - mTimeEnd(); }, xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){ - mTimeStart('xRead'); let rc = 0, nRead; const fh = __openFiles[fid]; try{ - wTimeStart('xRead'); nRead = (await getSyncHandle(fh,'xRead')).read( fh.sabView.subarray(0, n), {at: Number(offset64)} ); - wTimeEnd(); if(nRead < n){/* Zero-fill remaining bytes */ fh.sabView.fill(0, nRead, n); rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; } }catch(e){ - if(undefined===nRead) wTimeEnd(); error("xRead() failed",e,fh); state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ); } await releaseImplicitLock(fh); storeAndNotify('xRead',rc); - mTimeEnd(); }, xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){ - mTimeStart('xSync'); const fh = __openFiles[fid]; let rc = 0; if(!fh.readOnly && fh.syncHandle){ try { - wTimeStart('xSync'); await fh.syncHandle.flush(); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR_FSYNC; } - wTimeEnd(); } storeAndNotify('xSync',rc); - mTimeEnd(); }, xTruncate: async function(fid/*sqlite3_file pointer*/,size){ - mTimeStart('xTruncate'); let rc = 0; const fh = __openFiles[fid]; - wTimeStart('xTruncate'); try{ affirmNotRO('xTruncate', fh); await (await getSyncHandle(fh,'xTruncate')).truncate(size); @@ -665,33 +552,25 @@ const installAsyncProxy = function(){ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE); } await releaseImplicitLock(fh); - wTimeEnd(); storeAndNotify('xTruncate',rc); - mTimeEnd(); }, xUnlock: async function(fid/*sqlite3_file pointer*/, lockType/*SQLITE_LOCK_...*/){ - mTimeStart('xUnlock'); let rc = 0; const fh = __openFiles[fid]; if( state.sq3Codes.SQLITE_LOCK_NONE===lockType && fh.syncHandle ){ - wTimeStart('xUnlock'); try { await closeSyncHandle(fh) } catch(e){ state.s11n.storeException(1,e); rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; } - wTimeEnd(); } storeAndNotify('xUnlock',rc); - mTimeEnd(); }, xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){ - mTimeStart('xWrite'); let rc; const fh = __openFiles[fid]; - wTimeStart('xWrite'); try{ affirmNotRO('xWrite', fh); rc = ( @@ -705,9 +584,7 @@ const installAsyncProxy = function(){ rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE); } await releaseImplicitLock(fh); - wTimeEnd(); storeAndNotify('xWrite',rc); - mTimeEnd(); } }/*vfsAsyncImpls*/; @@ -741,8 +618,6 @@ const installAsyncProxy = function(){ } }; state.s11n.deserialize = function(clear=false){ - ++metrics.s11n.deserialize.count; - const t = performance.now(); const argc = viewU8[0]; const rc = argc ? [] : null; if(argc){ @@ -767,12 +642,9 @@ const installAsyncProxy = function(){ } if(clear) viewU8[0] = 0; //log("deserialize:",argc, rc); - metrics.s11n.deserialize.time += performance.now() - t; return rc; }; state.s11n.serialize = function(...args){ - const t = performance.now(); - ++metrics.s11n.serialize.count; if(args.length){ //log("serialize():",args); const typeIds = []; @@ -803,7 +675,6 @@ const installAsyncProxy = function(){ }else{ viewU8[0] = 0; } - metrics.s11n.serialize.time += performance.now() - t; }; state.s11n.storeException = state.asyncS11nExceptions @@ -885,7 +756,6 @@ const installAsyncProxy = function(){ } }); initS11n(); - metrics.reset(); log("init state",state); wPost('opfs-async-inited'); waitLoop(); @@ -898,9 +768,6 @@ const installAsyncProxy = function(){ waitLoop(); } break; - case 'opfs-async-metrics': - metrics.dump(); - break; } }; wPost('opfs-async-loaded'); diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index 4c654c335..5b74e7863 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -392,6 +392,7 @@ const installOpfsVfs = function callee(options){ 'SQLITE_ACCESS_EXISTS', 'SQLITE_ACCESS_READWRITE', 'SQLITE_BUSY', + 'SQLITE_CANTOPEN', 'SQLITE_ERROR', 'SQLITE_IOERR', 'SQLITE_IOERR_ACCESS', @@ -444,7 +445,7 @@ const installOpfsVfs = function callee(options){ OPFS_UNLINK_BEFORE_OPEN: 0x02, /** If true, any async routine which implicitly acquires a sync - access handle (i.e. an OPFS lock) will release that locks at + access handle (i.e. an OPFS lock) will release that lock at the end of the call which acquires it. If false, such "autolocks" are not released until the VFS is idle for some brief amount of time. @@ -471,9 +472,22 @@ const installOpfsVfs = function callee(options){ Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */; const t = performance.now(); - Atomics.wait(state.sabOPView, state.opIds.rc, -1) - /* When this wait() call returns, the async half will have - completed the operation and reported its results. */; + while('not-equal'!==Atomics.wait(state.sabOPView, state.opIds.rc, -1)){ + /* + The reason for this loop is buried in the details of a long + discussion at: + + https://github.com/sqlite/sqlite-wasm/issues/12 + + Summary: in at least one browser flavor, under high loads, + the wait()/notify() pairings can get out of sync. Calling + wait() here until it returns 'not-equal' gets them back in + sync. + */ + } + /* When the above wait() call returns 'not-equal', the async + half will have completed the operation and reported its results + in the state.opIds.rc slot of the SAB. */ const rc = Atomics.load(state.sabOPView, state.opIds.rc); metrics[op].wait += performance.now() - t; if(rc && state.asyncS11nExceptions){ @@ -720,9 +734,18 @@ const installOpfsVfs = function callee(options){ involve an inherent race condition. For the time being, pending a better solution, we simply report whether the given pFile is open. + + Update 2024-06-12: based on forum discussions, this + function now always sets pOut to 0 (false): + + https://sqlite.org/forum/forumpost/a2f573b00cda1372 */ - const f = __openFiles[pFile]; - wasm.poke(pOut, f.lockType ? 1 : 0, 'i32'); + if(1){ + wasm.poke(pOut, 0, 'i32'); + }else{ + const f = __openFiles[pFile]; + wasm.poke(pOut, f.lockType ? 1 : 0, 'i32'); + } return 0; }, xClose: function(pFile){ @@ -738,7 +761,6 @@ const installOpfsVfs = function callee(options){ return rc; }, xDeviceCharacteristics: function(pFile){ - //debug("xDeviceCharacteristics(",pFile,")"); return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN; }, xFileControl: function(pFile, opId, pArg){ diff --git a/manifest b/manifest index 430e21933..a83c5c071 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.46.0 -D 2024-05-23T13:25:27.566 +C Version\s3.46.1 +D 2024-08-13T09:16:08.704 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -7,7 +7,7 @@ F Makefile.in 993a7874e3d3721df61846f03dda4a9ef7490da11953ae36ba1bb0c0346eaf4a F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 F Makefile.msc e64a52619310d3067f6c38f56eedd15918a82dade70954197d6da486ad99d7f4 F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3 -F VERSION c84541c6a9e8426462176fbb1f9ecb5cfd7d1bb56228053ff7eeba8841673eb6 +F VERSION 01b0b94ee03b29c27cb72ef03c460cd9476dee97b0cf8cf74b17a201e199820a F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5 F art/icon-80x90.gif 65509ce3e5f86a9cd64fe7fca2d23954199f31fe44c1e09e208c80fb83d87031 @@ -24,7 +24,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac 9e74135563a901d9b1a019bad1c9d73a6659fa32325f6a565bef72bfb0ec7297 +F autoconf/tea/configure.ac 629148599fb2c29003b34e72dd161473b5fe23b6f26a2281200e9581daaede95 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -35,7 +35,7 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 40f7af9ed5ca0d44a4b9bc7ad34f1ee4867bb4eeb19e75036be6bed66193a498 x +F configure 1728380292e4153caf04870b0734c771b9cdc73be655181618f7b8253f2d9d5c x F configure.ac f25bd7843120f2c2b8bc9db5a92b0502bbdd28e68907415c3d42fc8e57c657b9 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd @@ -53,12 +53,12 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91 F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad1aff3294f94 F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a -F ext/consio/console_io.c f32b757c9ee7fdf68e7586bee306f8368759e7cd12febb2a6839199b1c1af395 x +F ext/consio/console_io.c b4885dfea71ed583315de8f0792a29d5fc7c7460b4a26c0aebe5cda5da8b38f8 x F ext/consio/console_io.h 0548b83d7c4b7270ad544a67f2bb90cebc519637fa39b1838df4744cf0d87646 F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 -F ext/expert/expert1.test 53a749de08939e3bc14f804e97410927d46fa772cbce0247d7e8fa6fc2523b0c -F ext/expert/sqlite3expert.c c8cea5ff15fbe792cccc4992a9b40b706411c41d32611f617897fecac6ff06a4 +F ext/expert/expert1.test 661f873fd451127edf822ef0d520088faa319135f6a15bd10be6801ac284ac9b +F ext/expert/sqlite3expert.c 8b09aeb2b95a9fca8b6628b522bf4d69aa746ff64c38eb1e99a9b5fad8cf03b9 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c7cc3bf59ee @@ -97,15 +97,15 @@ F ext/fts5/fts5Int.h defa43c0932265138ee910ca416e6baccf8b774e0f3d610e74be1ab2880 F ext/fts5/fts5_aux.c 4584e88878e54828bf7d4d0d83deedd232ec60628b7731be02bad6adb62304b1 F ext/fts5/fts5_buffer.c 0eec58bff585f1a44ea9147eae5da2447292080ea435957f7488c70673cb6f09 F ext/fts5/fts5_config.c 8072a207034b51ae9b7694121d1b5715c794e94b275e088f70ae532378ca5cdf -F ext/fts5/fts5_expr.c e91156ebdcc08d837f4f324168f69f3c0d7fdef0e521fd561efb48ef3297b696 +F ext/fts5/fts5_expr.c d6a48d81aa96d68090187059f48dba2184e0cb0fd3b50a5a84a649a865cb31a5 F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1 F ext/fts5/fts5_index.c ee0f4d50bc0c58a7c5ef7d645e7e38e1e59315b8ea9d722ae00c5f949ee65379 -F ext/fts5/fts5_main.c d68bd9533d5a638b7f6fae61c3cb0a15257dcdcccedaf3d0b3c9f55940c85048 +F ext/fts5/fts5_main.c 0a8fc885851d76af6ea670090231ed23b9f8f349f44080e79a0f59b1d54511bb F ext/fts5/fts5_storage.c f9e31b0d155e9b2c92d5d3a09ad7a56b937fbf1c7f962e10f4ca6281349f3934 F ext/fts5/fts5_tcl.c fdf7e2bb9a9186cfcaf2d2ce11d338309342b7a7593c2812bc54455db53da5d2 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b -F ext/fts5/fts5_tokenize.c 83cfcede3898001cab84432a36ce1503e3080cf9b1c682b022ec82e267ea4c13 +F ext/fts5/fts5_tokenize.c 2321cbcef0bb99ed326d58f02cbd907e678c86572a72a434d08f6cb78e346dbd F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00 F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80 F ext/fts5/fts5_vocab.c 209e0c151e108d5f3621fa24b91e9b02f3750ee6c3f9ccec312df39481b68a09 @@ -126,7 +126,7 @@ F ext/fts5/test/fts5ak.test f459a64c9d38698af72a7c657ab6349bca96150241dd69fcce75 F ext/fts5/test/fts5al.test 00c4c1c6a1366b73aa48ce2068c634520867c3cf7f5d1676ebbb775ee1f35734 F ext/fts5/test/fts5alter.test 5565f7e4605512b69171ac18ca84398603f9f6456dbe377beeca97e83cc242cd F ext/fts5/test/fts5auto.test 78989e6527ce69c9eddbef7392fea5c10b0010cd2b2ae68eec7bc869c471e691 -F ext/fts5/test/fts5aux.test ed3596469f85a6cff5f6060e0cd9e3f9602051d8db2b497f5d12c85d39f20a62 +F ext/fts5/test/fts5aux.test e0866d924289423164c539576229ea589990e6dd38d05a0b6f8752ad51d70bb4 F ext/fts5/test/fts5auxdata.test eacc97ff04892f1a5f3d4df5a73f8bcbc3955ea1d12c9f24137eb1fc079e7611 F ext/fts5/test/fts5bigid.test 2860854c2561a57594192b00c33a29f91cb85e25f3d6c03b5c2b8f62708f39dd F ext/fts5/test/fts5bigpl.test 6466c89b38439f0aba26ac09e232a6b963f29b1cbe1304f6a664fe1e7a8f5fd3 @@ -178,7 +178,7 @@ F ext/fts5/test/fts5first.test 3fcf2365c00a15fc9704233674789a3b95131d12de18a9b99 F ext/fts5/test/fts5full.test e1701a112354e0ff9a1fdffb0c940c576530c33732ee20ac5e8361777070d717 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e F ext/fts5/test/fts5hash.test dc7bc7e0cdeb42cfce31294ad2f8fcf43192bfd0145bb7f3ecc5465d8c72696f -F ext/fts5/test/fts5integrity.test f1723fe9fb9381b26c946ab4d7505041434df2c449d1cd53f45c7bf8c098dfa2 +F ext/fts5/test/fts5integrity.test f7a9dc2c2b8db0db024eaa546ed56add97f869b67064e258c36dcdd0243f25f2 F ext/fts5/test/fts5interrupt.test 09613247b273a99889808ef852898177e671406fe71fdde7ea00e78ea283d227 F ext/fts5/test/fts5lastrowid.test be98fe3e03235296585b72daad7aed5717ba0062bae5e5c18dd6e04e194c6b28 F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc2782680740513c4d1fc114b43d4ad @@ -218,7 +218,7 @@ F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b98 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a F ext/fts5/test/fts5secure6.test 74bf04733cc523bccca519bb03d3b4e2ed6f6e3db7c59bf6be82c88a0ac857fd F ext/fts5/test/fts5secure7.test fd03d0868d64340a1db8615b02e5508fea409de13910114e4f19eaefc120777a -F ext/fts5/test/fts5secure8.test eb3579e9d58b0acad97e8082dee1f99b2d393198f03500b453c2b25761c0c298 +F ext/fts5/test/fts5secure8.test e68c0ac4447f415ff3e4e82531e99548289286f9f3a29c8cd53036113fe28602 F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b F ext/fts5/test/fts5simple2.test 8dd2389ee75e21a1429fe87e5f8c7d9a97ad1470304a8a2d3ba4b8c3c345fecd @@ -228,9 +228,9 @@ F ext/fts5/test/fts5synonym2.test e2f6ff68c4fbe12a866a3a87510f553d9dac99bcb74c10 F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 -F ext/fts5/test/fts5tokenizer2.test cb5428c7cfb3b6a74b7adfcde65506e329112003e8dffa7501d01c2d18d02569 -F ext/fts5/test/fts5trigram.test 6c4e37864f3e7d90673db5563d9736d7e40080ab94d10ebdffa94c1b77941da0 -F ext/fts5/test/fts5trigram2.test 9fe4207f8a4241747aff1005258b564958588d21bfd240d6cd4c2e955d31c156 +F ext/fts5/test/fts5tokenizer2.test b9d734c1b10bc317a377ffea3ecb5c2937313113a02e364f167d0c7f8c81c282 +F ext/fts5/test/fts5trigram.test be914555deb8504dde682bd5aa343d00c4da37dfad20709a5bac30d5f97f2ef5 +F ext/fts5/test/fts5trigram2.test 4043f8836bbbb0ce37b86dd1e741431c6c595c7e4ba4fc8e26f21dc3b540e228 F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602 F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9 @@ -494,7 +494,7 @@ F ext/recover/recoverpgsz.test 3658ab8e68475b1bb87d6af88baa04551c84b73280a566a1b F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a3a21030057bfd81411 F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486 -F ext/recover/sqlite3recover.c 65ef0f56301a16c0536c9839fb7e23540c9c4f75da0afe3b7b4d163c8f624404 +F ext/recover/sqlite3recover.c 2dcf6b56c5e0e2b43fc4c6115b689ab194c374ced7f7f8380ad9a24d8ef24ac9 F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 F ext/recover/test_recover.c fd871a40f2238022bedcbdf3cb493b91225edaa94d6ae8892af97a10e7ccc4ba F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 @@ -614,10 +614,10 @@ F ext/wasm/api/sqlite3-api-oo1.js c373cc04625a96bd3f01ce8ebeac93a5d38dbda6215818 F ext/wasm/api/sqlite3-api-prologue.js b347a0c5350247f90174a0ad9b9e72a99a5f837f31f78f60fcdb829b2ca30b63 F ext/wasm/api/sqlite3-api-worker1.js 9704b77b5eb9d0d498ceeaf3e7a837021b14c52ac15d6556c7f97e278ec725c3 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 -F ext/wasm/api/sqlite3-opfs-async-proxy.js 196ad83d36ca794e564044788c9d21b964679d63cad865f604da37c4afc9a285 +F ext/wasm/api/sqlite3-opfs-async-proxy.js dd4054e4f673027c330c96a04cf0a03b118156f01b076dc4e9d604dbdd7bfc51 F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 8433ee332d5f5e39fb19427fccb7bad7f44aa99b5504daad3343fc128c311e78 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 3c72f1a0e6a7343c8c882d29d01bb440f10be12c844651605b486e76f3d6cc8c +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 834dcea56e27064ae8429780c85393c82b8f83fc23a1ebebb41849392bfe30bf F ext/wasm/api/sqlite3-vtab-helper.c-pp.js a2fcbc3fecdd0eea229283584ebc122f29d98194083675dbe5cb2cf3a17fe309 F ext/wasm/api/sqlite3-wasm.c 9267174b9b0591b4f71193542ab57adf95bb9415f7d3453acf4a8ca8052f5e6c F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 46f303ba8ddd1b2f0a391798837beddfa72e8c897038c8047eda49ce7d5ed46b @@ -686,7 +686,7 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 -F src/alter.c e1b6782b85dd758f89e5c588e4e3eb82638c2dafc0c857b79a43bb8ec1746fca +F src/alter.c bb663fddf1fe0e2e6d8758b2b7fb6374e7c057a6ca3955f37a48986806029765 F src/analyze.c a3df28274e2565ba5656577d7e3fd262169a213e6eb0bd47890e0f0729a4031c F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39 F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 @@ -696,7 +696,7 @@ F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 F src/btree.c 71b80e77b255144db47180fda8138740608e382a44231942464029b1a45fc036 F src/btree.h 55066f513eb095db935169dab1dc2f7c7a747ef223c533f5d4ad4dfed346cbd0 F src/btreeInt.h 98aadb6dcb77b012cab2574d6a728fad56b337fc946839b9898c4b4c969e30b6 -F src/build.c 11ec7014a3c468e7b3ccc8dda8d9111cd5a29a358df18818788601e0600aaabd +F src/build.c 237ccc0290d131d646be722f418e92ee0a38043aee25e7dfdc75f8ce5b3abe4e F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 64e4b1227b4ed123146f0aa2989131d1fbd9b927b11e80c9d58c6a68f9cd5ce3 @@ -707,13 +707,13 @@ F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 F src/expr.c f7bad20d2f74005f1f876e7fbb627222ea28250e44b296b047403720c5c21818 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 -F src/func.c 283d4f3b2751a1d9339fd93a8a013d1948fd5f4474a3cab0955eb4fafd445d0f +F src/func.c f1f57c6863c1380f31ecf3d61732495bfff847a8e35a832c7e306e310db5a799 F src/global.c 61a419dd9e993b9be0f91de4c4ccf322b053eb829868e089f0321dd669be3b90 F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c 4bd7c7e54a1062dcd0214b7a6296f7194eb10fb14d3ddca1ed20b01c2a86a18c +F src/insert.c 8ff11e9e54c5fc1fe89707b3d41cf44ad2822f712bd3b5da68338ea42518847e F src/json.c bf1b51e32158b3d01d96a878d3dba8d2e633a7e5bf2534d4617f89de8a6b9a91 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 @@ -743,7 +743,7 @@ F src/os_win.c 6ff43bac175bd9ed79e7c0f96840b139f2f51d01689a638fd05128becf94908a F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 9beb80f6e330dd63c5d8ba0f7a7f3a55fff22067a68d424949c389bfc6fa0c56 F src/pager.h 4b1140d691860de0be1347474c51fee07d5420bd7f802d38cbab8ea4ab9f538a -F src/parse.y 50516253433303673ff6b009983bb246d1527415e5a9af22acc51b0eedb9a10d +F src/parse.y 1a526e56da1d8255196bd59d4ca3e26d912d3dc26d18663ade25dd328945062e F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 @@ -752,14 +752,14 @@ F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c d99931f45416652895e502328ca49fe782cfc4e1ebdcda13b3736d991ebf42ce F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 22f1fa3423b377c02ae78d451cfeb1c2d96dcf0389c0642cbdcd19d3bfd7ae01 +F src/resolve.c 4f4c99b8714fa04844a0f1f96ee69eefc8928e300af9247a46cfe80f78f8997c F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 1a841c38974d45cf15a7611398479182b61ad4c187423c380741d8b1688fe607 -F src/shell.c.in 885dafabb3f16d68bdb4576683afb0e39a1939f50985b162255bf656c470babf +F src/shell.c.in ebb698028ec031e0b1595865500097d2005f977be0efd14bd8b0ddf634d5ed8d F src/sqlite.h.in c71d9ef76a6d32dc7ff2d373f2e57ce09056af26c1457bcadae5358b7628c7c3 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 6a9fa3902c9faca2b57060e822f2afadfbf96d64c4ede81e201f0e0c42d7e4aa +F src/sqliteInt.h 9d022fc8dc3fccd69e518c115101ab4c3d4cb45a79549c1a4c26fec492eb2cfb F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -828,7 +828,7 @@ F src/vdbe.c 3b1793c5d2235ae89b01ef051a33d7d2ad3704c71799653b112686735ad401ff F src/vdbe.h c2d78d15112c3fc5ab87f5e8e0b75d2db1c624409de2e858c3d1aafb1650bb4f F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c F src/vdbeapi.c 80235ac380e9467fec1cb0883354d841f2a771976e766995f7e0c77f845406df -F src/vdbeaux.c 3bcf13776c39bf660a52b4b97f6389a421c2756f9ffbf4c0d94f73e1935d8d9c +F src/vdbeaux.c 6e37cb918506c28fe7657454fcbc2e01e66bfba4164f306c2f075fd5c5fef609 F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5 F src/vdbemem.c 831a244831eaa45335f9ae276b50a7a82ee10d8c46c2c72492d4eb8c98d94d89 F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547 @@ -839,7 +839,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c 6f02c3936d1f9a637d8d7b5ad7362371af3e4434b0ec1eb950189a83de560d59 +F src/where.c 839956666ce9f6b5d4951fc1f27e976951aac66920c82f9cdc5036b61b54ef7b F src/whereInt.h 82a13766f13d1a53b05387c2e60726289ef26404bc7b9b1f7770204d97357fb8 F src/wherecode.c f5255f49d1f42b6e7e6b0362ff3522fa88cbcaa7213e52f9374744027ecdebca F src/whereexpr.c 67d15caf88a1a9528283d68ff578e024cf9fe810b517bb0343e5aaf695ad97dd @@ -1536,7 +1536,7 @@ F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed13 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/returning1.test 38eee9d07ac1dd4fbd4ce7373497f3783db86b9a76f13ea6a9f9afaf934f888b +F test/returning1.test 212cd4111bb941a60abf608f20250db666c21eb1bc4d49217e96c87ff3ab9d1a F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f @@ -1584,7 +1584,7 @@ F test/select3.test 180223af31e1ca5537dd395ef9708ae18e651a233777fd366fd0d75469fc F test/select4.test f0684d3da3bccacbe2a1ebadf6fb49d9df6f53acb4c6ebc228a88d0d6054cc7b F test/select5.test 8afc5e5dcdebc2be54472e73ebd9cd1adef1225fd15d37a1c62f969159f390ae F test/select6.test 9b2fb4ffedf52e1b5703cfcae1212e7a4a063f014c0458d78d29aca3db766d1f -F test/select7.test f659f231489349e8c5734e610803d7654207318f +F test/select7.test b825420da8a0b5722fdb77f3369f6396a3d198c46e8787eb26ff9425d4ac9d27 F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d F test/select9.test f7586b207ce2304ab80dc93d3146469a28fd4403621dd3a82d06644563d3c812 F test/selectA.test 1da8ce3884c326e11d2855baffb76436b0d7e044404af8a2a70d1399a4ff7e29 @@ -1614,7 +1614,7 @@ F test/shell1.test 17a5ca9c6f24f807b2f505b4b38fcbce143d96cd8664c06c34bbbe0672bf7 F test/shell2.test 56da24128304c9ab67da2964cc80beff7b35761c446ec6e6e98bff2775b15026 F test/shell3.test 5ad4b2813717956414f2c0c8a2027895cd98ccf7dd54dbacbde4d4f5591ce5a1 F test/shell4.test 522fdc628c55eff697b061504fb0a9e4e6dfc5d9087a633ab0f3dd11bcc4f807 -F test/shell5.test 5b2ab1c0540217773f939927c24163a56257446da3f564d4724042620bfea762 +F test/shell5.test 6a49440bddc33a132f856fb189e71228f8132963655d12a2c8b8a161263b9632 F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8 F test/shell7.test 753c6ece5361df50025a50cadf378ea36db9cc05fb23d7a96cff7fa130626ef9 F test/shell8.test aea51ecbcd4494c746b096aeff51d841d04d5f0dc4b62eb42427f16109b87acd @@ -2034,7 +2034,7 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2 F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976 F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc -F test/window1.test 5e8abe56a7d667eeddbba6de180086dcf69ed528d046447a25464f945ece101f +F test/window1.test 79dc3b9a2226f622d7e104a1fc750d1c4c3c08d6147b59085bdbe05352947ffa F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -2191,11 +2191,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 3210e1ca4d0efedf9710c97abd050ba10d3af98cb1f029c26daa84daf42fbc7e -R 03f38bbe86d08cc2accd8330faccbd25 -T +sym-major-release * +P fc956353d3762d0e655b88f9d0c1a3840b40453a22e97160ccdf60485be56a92 +R 47b9d94ac84047bf00fd35221bcbfde9 T +sym-release * -T +sym-version-3.46.0 * +T +sym-version-3.46.1 * U drh -Z 82f35d0822d1dfbb574e5dd2459b4a36 +Z 519cc14a63561bb4dfa17935ffe00a32 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7dd5f56d1..43d669ec2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -96c92aba00c8375bc32fafcdf12429c58bd8aabfcadab6683e35bbb9cdebf19e +c9c2ab54ba1f5f46360f1b4f35d849cd3f080e6fc2b6c60e91b16c63f69a1e33 diff --git a/src/alter.c b/src/alter.c index c1e0a295a..a8556d115 100644 --- a/src/alter.c +++ b/src/alter.c @@ -1320,7 +1320,7 @@ static int renameResolveTrigger(Parse *pParse){ /* ALWAYS() because if the table of the trigger does not exist, the ** error would have been hit before this point */ if( ALWAYS(pParse->pTriggerTab) ){ - rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab); + rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab)!=0; } /* Resolve symbols in WHEN clause */ diff --git a/src/build.c b/src/build.c index 10aa34240..9747810e8 100644 --- a/src/build.c +++ b/src/build.c @@ -3064,8 +3064,9 @@ void sqlite3CreateView( #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) /* ** The Table structure pTable is really a VIEW. Fill in the names of -** the columns of the view in the pTable structure. Return the number -** of errors. If an error is seen leave an error message in pParse->zErrMsg. +** the columns of the view in the pTable structure. Return non-zero if +** there are errors. If an error is seen an error message is left +** in pParse->zErrMsg. */ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ Table *pSelTab; /* A fake table from which we get the result set */ @@ -3188,7 +3189,7 @@ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ sqlite3DeleteColumnNames(db, pTable); } #endif /* SQLITE_OMIT_VIEW */ - return nErr; + return nErr + pParse->nErr; } int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ assert( pTable!=0 ); diff --git a/src/func.c b/src/func.c index 18004984d..058f71cb6 100644 --- a/src/func.c +++ b/src/func.c @@ -2206,6 +2206,8 @@ static void groupConcatValue(sqlite3_context *context){ sqlite3_result_error_toobig(context); }else if( pAccum->accError==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); + }else if( pGCC->nAccum>0 && pAccum->nChar==0 ){ + sqlite3_result_text(context, "", 1, SQLITE_STATIC); }else{ const char *zText = sqlite3_str_value(pAccum); sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT); diff --git a/src/insert.c b/src/insert.c index 072386e65..a7e94420b 100644 --- a/src/insert.c +++ b/src/insert.c @@ -717,6 +717,7 @@ Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){ pRet->pSrc->nSrc = 1; pRet->pPrior = pLeft->pPrior; pRet->op = pLeft->op; + if( pRet->pPrior ) pRet->selFlags |= SF_Values; pLeft->pPrior = 0; pLeft->op = TK_SELECT; assert( pLeft->pNext==0 ); diff --git a/src/parse.y b/src/parse.y index 071e10abd..2ada5eb24 100644 --- a/src/parse.y +++ b/src/parse.y @@ -530,9 +530,9 @@ cmd ::= select(X). { break; } } - if( (p->selFlags & SF_MultiValue)==0 && - (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 && - cnt>mxSelect + if( (p->selFlags & (SF_MultiValue|SF_Values))==0 + && (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 + && cnt>mxSelect ){ sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); } diff --git a/src/resolve.c b/src/resolve.c index bf8326aa6..590f8ae5e 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -228,7 +228,7 @@ static void extendFJMatch( static SQLITE_NOINLINE int isValidSchemaTableName( const char *zTab, /* Name as it appears in the SQL */ Table *pTab, /* The schema table we are trying to match */ - Schema *pSchema /* non-NULL if a database qualifier is present */ + const char *zDb /* non-NULL if a database qualifier is present */ ){ const char *zLegacy; assert( pTab!=0 ); @@ -239,7 +239,7 @@ static SQLITE_NOINLINE int isValidSchemaTableName( if( sqlite3StrICmp(zTab+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ return 1; } - if( pSchema==0 ) return 0; + if( zDb==0 ) return 0; if( sqlite3StrICmp(zTab+7, &LEGACY_SCHEMA_TABLE[7])==0 ) return 1; if( sqlite3StrICmp(zTab+7, &PREFERRED_SCHEMA_TABLE[7])==0 ) return 1; }else{ @@ -422,7 +422,7 @@ static int lookupName( } }else if( sqlite3StrICmp(zTab, pTab->zName)!=0 ){ if( pTab->tnum!=1 ) continue; - if( !isValidSchemaTableName(zTab, pTab, pSchema) ) continue; + if( !isValidSchemaTableName(zTab, pTab, zDb) ) continue; } assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT && pItem->zAlias ){ @@ -2154,6 +2154,9 @@ int sqlite3ResolveExprNames( ** Resolve all names for all expression in an expression list. This is ** just like sqlite3ResolveExprNames() except that it works for an expression ** list rather than a single expression. +** +** The return value is SQLITE_OK (0) for success or SQLITE_ERROR (1) for a +** failure. */ int sqlite3ResolveExprListNames( NameContext *pNC, /* Namespace to resolve expressions in. */ @@ -2162,7 +2165,7 @@ int sqlite3ResolveExprListNames( int i; int savedHasAgg = 0; Walker w; - if( pList==0 ) return WRC_Continue; + if( pList==0 ) return SQLITE_OK; w.pParse = pNC->pParse; w.xExprCallback = resolveExprStep; w.xSelectCallback = resolveSelectStep; @@ -2176,7 +2179,7 @@ int sqlite3ResolveExprListNames( #if SQLITE_MAX_EXPR_DEPTH>0 w.pParse->nHeight += pExpr->nHeight; if( sqlite3ExprCheckHeight(w.pParse, w.pParse->nHeight) ){ - return WRC_Abort; + return SQLITE_ERROR; } #endif sqlite3WalkExprNN(&w, pExpr); @@ -2193,10 +2196,10 @@ int sqlite3ResolveExprListNames( (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); } - if( w.pParse->nErr>0 ) return WRC_Abort; + if( w.pParse->nErr>0 ) return SQLITE_ERROR; } pNC->ncFlags |= savedHasAgg; - return WRC_Continue; + return SQLITE_OK; } /* diff --git a/src/shell.c.in b/src/shell.c.in index 0029682f3..7960acfab 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -8977,7 +8977,6 @@ static int do_meta_command(char *zLine, ShellState *p){ import_cleanup(&sCtx); shell_out_of_memory(); } - nByte = strlen(zSql); rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); zSql = 0; @@ -8996,16 +8995,21 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); pStmt = 0; if( nCol==0 ) return 0; /* no columns, no error */ - zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); + + nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ + + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ + + strlen(zTable)*2 + 2 /* Quoted table name */ + + nCol*2; /* Space for ",?" for each column */ + zSql = sqlite3_malloc64( nByte ); if( zSql==0 ){ import_cleanup(&sCtx); shell_out_of_memory(); } if( zSchema ){ - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", zSchema, zTable); }else{ - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); } j = strlen30(zSql); for(i=1; i=2 ){ oputf("Insert using: %s\n", zSql); } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index d98a4f7f0..ec126b087 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3388,7 +3388,7 @@ struct SrcList { #define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */ #define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ #define WHERE_RIGHT_JOIN 0x1000 /* Processing a RIGHT JOIN */ - /* 0x2000 not currently used */ +#define WHERE_KEEP_ALL_JOINS 0x2000 /* Do not do the omit-noop-join opt */ #define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */ /* 0x8000 not currently used */ diff --git a/src/vdbeaux.c b/src/vdbeaux.c index e4c174e3f..665f6cd17 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -5336,7 +5336,8 @@ sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff){ assert( iVar>0 ); if( v ){ Mem *pMem = &v->aVar[iVar-1]; - assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); + assert( (v->db->flags & SQLITE_EnableQPSG)==0 + || (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 ); if( 0==(pMem->flags & MEM_Null) ){ sqlite3_value *pRet = sqlite3ValueNew(v->db); if( pRet ){ @@ -5356,7 +5357,8 @@ sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff){ */ void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ assert( iVar>0 ); - assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); + assert( (v->db->flags & SQLITE_EnableQPSG)==0 + || (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 ); if( iVar>=32 ){ v->expmask |= 0x80000000; }else{ diff --git a/src/where.c b/src/where.c index fac0f6c5e..84deb5e9e 100644 --- a/src/where.c +++ b/src/where.c @@ -3959,7 +3959,9 @@ static int whereLoopAddBtree( " according to whereIsCoveringIndex()\n", pProbe->zName)); } } - }else if( m==0 ){ + }else if( m==0 + && (HasRowid(pTab) || pWInfo->pSelect!=0 || sqlite3FaultSim(700)) + ){ WHERETRACE(0x200, ("-> %s a covering index according to bitmasks\n", pProbe->zName, m==0 ? "is" : "is not")); @@ -5848,6 +5850,10 @@ static void showAllWhereLoops(WhereInfo *pWInfo, WhereClause *pWC){ ** the right-most table of a subquery that was flattened into the ** main query and that subquery was the right-hand operand of an ** inner join that held an ON or USING clause. +** 6) The ORDER BY clause has 63 or fewer terms +** 7) The omit-noop-join optimization is enabled. +** +** Items (1), (6), and (7) are checked by the caller. ** ** For example, given: ** @@ -6261,6 +6267,7 @@ WhereInfo *sqlite3WhereBegin( if( pOrderBy && pOrderBy->nExpr>=BMS ){ pOrderBy = 0; wctrlFlags &= ~WHERE_WANT_DISTINCT; + wctrlFlags |= WHERE_KEEP_ALL_JOINS; /* Disable omit-noop-join opt */ } /* The number of tables in the FROM clause is limited by the number of @@ -6561,10 +6568,10 @@ WhereInfo *sqlite3WhereBegin( ** in-line sqlite3WhereCodeOneLoopStart() for performance reasons. */ notReady = ~(Bitmask)0; - if( pWInfo->nLevel>=2 - && pResultSet!=0 /* these two combine to guarantee */ - && 0==(wctrlFlags & WHERE_AGG_DISTINCT) /* condition (1) above */ - && OptimizationEnabled(db, SQLITE_OmitNoopJoin) + if( pWInfo->nLevel>=2 /* Must be a join, or this opt8n is pointless */ + && pResultSet!=0 /* Condition (1) */ + && 0==(wctrlFlags & (WHERE_AGG_DISTINCT|WHERE_KEEP_ALL_JOINS)) /* (1),(6) */ + && OptimizationEnabled(db, SQLITE_OmitNoopJoin) /* (7) */ ){ notReady = whereOmitNoopJoin(pWInfo, notReady); nTabList = pWInfo->nLevel; @@ -6884,26 +6891,6 @@ WhereInfo *sqlite3WhereBegin( } #endif -#ifdef SQLITE_DEBUG -/* -** Return true if cursor iCur is opened by instruction k of the -** bytecode. Used inside of assert() only. -*/ -static int cursorIsOpen(Vdbe *v, int iCur, int k){ - while( k>=0 ){ - VdbeOp *pOp = sqlite3VdbeGetOp(v,k--); - if( pOp->p1!=iCur ) continue; - if( pOp->opcode==OP_Close ) return 0; - if( pOp->opcode==OP_OpenRead ) return 1; - if( pOp->opcode==OP_OpenWrite ) return 1; - if( pOp->opcode==OP_OpenDup ) return 1; - if( pOp->opcode==OP_OpenAutoindex ) return 1; - if( pOp->opcode==OP_OpenEphemeral ) return 1; - } - return 0; -} -#endif /* SQLITE_DEBUG */ - /* ** Generate the end of the WHERE loop. See comments on ** sqlite3WhereBegin() for additional information. @@ -7203,16 +7190,10 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ ** reference. Verify that this is harmless - that the ** table being referenced really is open. */ -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 - || cursorIsOpen(v,pOp->p1,k) - || pOp->opcode==OP_Offset - ); -#else - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 - || cursorIsOpen(v,pOp->p1,k) - ); -#endif + if( pLoop->wsFlags & WHERE_IDX_ONLY ){ + sqlite3ErrorMsg(pParse, "internal query planner error"); + pParse->rc = SQLITE_INTERNAL; + } } }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; diff --git a/test/returning1.test b/test/returning1.test index 05bd445a2..e7be7c65a 100644 --- a/test/returning1.test +++ b/test/returning1.test @@ -531,4 +531,17 @@ do_execsql_test 21.1 { INSERT INTO sqlite_temp_schema DEFAULT VALUES RETURNING sqlite_temp_schema.name; } {{}} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 22.0 { + PRAGMA writable_schema=ON; + CREATE TABLE xyz (a); +} +do_catchsql_test 22.1 { + INSERT INTO sqlite_temp_schema DEFAULT VALUES + RETURNING + (SELECT * FROM xyz AS sqlite_master WHERE a=sqlite_master.name); +} {1 {no such column: sqlite_master.name}} + + finish_test diff --git a/test/select7.test b/test/select7.test index d705ebfaf..0c4051006 100644 --- a/test/select7.test +++ b/test/select7.test @@ -155,6 +155,38 @@ if {[clang_sanitize_address]==0} { } } +# https://issues.chromium.org/issues/358174302 +# Need to support an unlimited number of terms in a VALUES clause, even +# if some of those terms contain double-quoted string literals. +# +do_execsql_test select7-6.5 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a,b,c); +} +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 10 +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 0 +do_catchsql_test select7-6.6 { + INSERT INTO t1 VALUES + (NULL,0,""), (X'',0.0,0.0), (X'',X'',""), (0.0,0.0,""), (NULL,NULL,0.0), + (0,"",0), (0.0,X'',0), ("",X'',0.0), (0.0,X'',NULL), (0,NULL,""), + (0,"",NULL), (0.0,NULL,X''), ("",X'',NULL), (NULL,0,""), + (0,NULL,0), (X'',X'',0.0); +} {1 {no such column: "" - should this be a string literal in single-quotes?}} +do_execsql_test select7-6.7 { + SELECT count(*) FROM t1; +} {0} +sqlite3_db_config db SQLITE_DBCONFIG_DQS_DML 1 +do_catchsql_test select7-6.8 { + INSERT INTO t1 VALUES + (NULL,0,""), (X'',0.0,0.0), (X'',X'',""), (0.0,0.0,""), (NULL,NULL,0.0), + (0,"",0), (0.0,X'',0), ("",X'',0.0), (0.0,X'',NULL), (0,NULL,""), + (0,"",NULL), (0.0,NULL,X''), ("",X'',NULL), (NULL,0,""), + (0,NULL,0), (X'',X'',0.0); +} {0 {}} +do_execsql_test select7-6.9 { + SELECT count(*) FROM t1; +} {16} + # This block of tests verifies that bug aa92c76cd4 is fixed. # do_test select7-7.1 { diff --git a/test/shell5.test b/test/shell5.test index 877676d72..8727edaaf 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -585,4 +585,16 @@ do_test shell5-7.1 { SELECT * FROM t1;} } {0 aaa|bbb|aaabbb} +#------------------------------------------------------------------------- + +do_test shell5-8.1 { + + set out [open shell5.csv w] + fconfigure $out -translation lf + puts $out x + close $out + + catchcmd :memory: {.import --csv shell5.csv '""""""""""""""""""""""""""""""""""""""""""""""'} +} {0 {}} + finish_test diff --git a/test/window1.test b/test/window1.test index d8348a898..457852c14 100644 --- a/test/window1.test +++ b/test/window1.test @@ -2375,5 +2375,23 @@ do_execsql_test 77.2 { SELECT max(~likely(x)) FILTER (WHERE true) FROM t1 INDEXED BY t1x GROUP BY x; } {-2 -3 -5 -9} +# 2024-05-23 https://sqlite.org/forum/forumpost/bf8f43aa522c2299 +# +# A bug in group_concat() when used as a window function, reported +# just hours after the 3.46.0 release, though first appearing +# in 3.28.0. +# +# When used as a window function, a group_concat() was not +# correctly distinguishing between NULL and empty-string for +# its return value. +# +do_execsql_test 78.1 { + SELECT quote(group_concat(x) OVER ()) FROM (SELECT '' AS x); +} '' +do_execsql_test 78.2 { + SELECT quote(group_concat(x) OVER ( + ORDER BY y RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING + )) FROM (SELECT 'abc' AS x, 1 AS y); +} NULL finish_test From c579558bd123b54cf287617a7b395b8bb3643c31 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 13 Aug 2024 16:43:33 -0400 Subject: [PATCH 170/340] Updates changelog to reflect upstream 3.46.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 271d0a79b..04e97db88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. ## [unreleased] - (? 2024 - [unreleased changes]) ## [4.6.1] - (August 2024 - [4.6.1 changes]) -- Updates baseline to upstream SQLite 3.46.0 +- Updates baseline to upstream SQLite 3.46.1 - Significant refactor to merge `crypto.h`, `crypto.c`, and `crypto_impl.c` into a single `sqlcipher.c` source file for simplicity. - Updates minimum working set size on windows to increase lockable pages - Adds new `PRAGMA cipher_log_source` for filtering log output on higher verbosity levels From c5bd336ece77922433aaf6d6fe8cf203b0c299d5 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 13 Aug 2024 17:15:26 -0400 Subject: [PATCH 171/340] Fixes default log output to console for macOS --- CHANGELOG.md | 1 + src/sqlcipher.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04e97db88..33b770a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. - Improves error logging in `PRAGMA cipher_migrate` - Fixes issue where log level and target would be overwritten if set prior to initialization - Corrects Podspec license element to use specific BSD 3 Clause +- Fixes default log output to console for macOS ## [4.6.0] - (May 2024 - [4.6.0 changes]) - Sets default log level to WARN diff --git a/src/sqlcipher.c b/src/sqlcipher.c index b690deb0c..8be4bc923 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -509,7 +509,7 @@ static void sqlcipher_activate() { /* set the default file or device if neither is already set */ if(sqlcipher_log_device == 0 && sqlcipher_log_file == NULL) { -#if defined(__ANDROID__) || defined(__APPLE_) +#if defined(__ANDROID__) || defined(__APPLE__) sqlcipher_log_device = 1; #else sqlcipher_log_file = stderr; From 9d148cdbca5f949beef61b165478fa38c3e279cb Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 23 Aug 2024 13:56:50 -0400 Subject: [PATCH 172/340] Bumps sqlcipher version number to 4.6.2 --- SQLCipher.podspec.json | 4 ++-- src/sqlcipher.c | 2 +- test/sqlcipher-pragmas.test | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLCipher.podspec.json b/SQLCipher.podspec.json index 90c5eb3af..70d0136c1 100644 --- a/SQLCipher.podspec.json +++ b/SQLCipher.podspec.json @@ -18,10 +18,10 @@ "requires_arc": false, "source": { "git": "https://github.com/sqlcipher/sqlcipher.git", - "tag": "v4.6.1" + "tag": "v4.6.2" }, "summary": "Full Database Encryption for SQLite.", - "version": "4.6.1", + "version": "4.6.2", "subspecs": [ { "compiler_flags": [ diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 8be4bc923..945416da4 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -86,7 +86,7 @@ void sqlite3pager_reset(Pager *pPager); #define CIPHER_STR(s) #s #ifndef CIPHER_VERSION_NUMBER -#define CIPHER_VERSION_NUMBER 4.6.1 +#define CIPHER_VERSION_NUMBER 4.6.2 #endif #ifndef CIPHER_VERSION_BUILD diff --git a/test/sqlcipher-pragmas.test b/test/sqlcipher-pragmas.test index a6f69da3f..abe1f4c58 100644 --- a/test/sqlcipher-pragmas.test +++ b/test/sqlcipher-pragmas.test @@ -46,7 +46,7 @@ do_test verify-pragma-cipher-version { execsql { PRAGMA cipher_version; } -} {{4.6.1 community}} +} {{4.6.2 community}} db close file delete -force test.db From c0fc6fc0722de7550f512204f20e3d2a545486a9 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Fri, 20 Sep 2024 09:47:51 -0400 Subject: [PATCH 173/340] Improves warning message when an attempt is made to key an already keyed connection --- src/sqlcipher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index 945416da4..f991f5e35 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -2794,7 +2794,7 @@ int sqlcipherCodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) { if(ctx != NULL && SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) { /* there is already a codec attached to this database, so we should not proceed */ - sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: no codec attached to db"); + sqlcipher_log(SQLCIPHER_LOG_WARN, SQLCIPHER_LOG_CORE, "sqlcipherCodecAttach: disregarding attempt to set key on an previously keyed database connection handle"); return SQLITE_OK; } From 399eb74022d076ac1259abc39429be8eab092a23 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 25 Sep 2024 17:44:40 -0400 Subject: [PATCH 174/340] Prevents changing settings that would affect database access after key is derived and used --- src/sqlcipher.c | 13 +++++++++++++ test/sqlcipher-core.test | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/sqlcipher.c b/src/sqlcipher.c index f991f5e35..cbc84bdbd 100644 --- a/src/sqlcipher.c +++ b/src/sqlcipher.c @@ -874,12 +874,15 @@ static int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nK } static int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *ctx, int kdf_iter) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) return SQLITE_OK; ctx->kdf_iter = kdf_iter; sqlcipher_set_derive_key(ctx, 1); return SQLITE_OK; } static int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *ctx, int fast_kdf_iter) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) return SQLITE_OK; + ctx->fast_kdf_iter = fast_kdf_iter; sqlcipher_set_derive_key(ctx, 1); return SQLITE_OK; @@ -893,6 +896,8 @@ static void sqlcipher_set_default_use_hmac(int use) { /* set the codec flag for whether this individual database should be using hmac */ static int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) return SQLITE_OK; + if(use) { SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HMAC); } else { @@ -918,11 +923,15 @@ static int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int siz } static int sqlcipher_codec_ctx_set_hmac_algorithm(codec_ctx *ctx, int algorithm) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) return SQLITE_OK; + ctx->hmac_algorithm = algorithm; return sqlcipher_codec_ctx_reserve_setup(ctx); } static int sqlcipher_codec_ctx_set_kdf_algorithm(codec_ctx *ctx, int algorithm) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) return SQLITE_OK; + ctx->kdf_algorithm = algorithm; return SQLITE_OK; } @@ -954,6 +963,8 @@ static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) { } static int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int size) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) return SQLITE_OK; + if(size >= ctx->kdf_salt_sz) { memcpy(ctx->kdf_salt, salt, ctx->kdf_salt_sz); SQLCIPHER_FLAG_SET(ctx->flags, CIPHER_FLAG_HAS_KDF_SALT); @@ -976,6 +987,8 @@ static int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) { } static int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { + if(SQLCIPHER_FLAG_GET(ctx->flags, CIPHER_FLAG_KEY_USED)) return SQLITE_OK; + if(!((size != 0) && ((size & (size - 1)) == 0)) || size < 512 || size > 65536) { sqlcipher_log(SQLCIPHER_LOG_ERROR, SQLCIPHER_LOG_CORE, "cipher_page_size not a power of 2 and between 512 and 65536 inclusive"); return SQLITE_ERROR; diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index c4b5e3bf4..ee544a634 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -666,6 +666,28 @@ do_test multiple-key-calls-safe { db close file delete -force test.db +# open a databse and use the valid key. Then +# use pragma cipher_compatability to adjust settings that +# would normally trigger key derivation again. +# the new settings should be ignored + +setup test.db "'testkey'" +do_test setting-changes-after-key-calls-safe { + sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + PRAGMA cache_size = 0; + SELECT name FROM sqlite_schema WHERE type='table'; + INSERT INTO t1(a,b) VALUES (2,zeroblob(8192)); + PRAGMA cipher_compatibility=3; + INSERT INTO t1(a,b) VALUES (3,zeroblob(8192)); + SELECT name FROM sqlite_schema WHERE type='table'; + SELECT count(*) FROM t1; + } +} {ok t1 t1 3} +db close +file delete -force test.db + # 1. create a database with a custom hmac kdf iteration count, # 2. create table and insert operations should work # 3. close database, open it again with the same From 1f09a0ab6a6925ae478d3d62bafd21ec807abe82 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Tue, 22 Oct 2024 16:19:18 -0400 Subject: [PATCH 175/340] Snapshot of upstream SQLite 3.47.0 --- Makefile.in | 225 +- Makefile.msc | 150 +- README.md | 197 +- VERSION | 2 +- autoconf/Makefile.msc | 17 + autoconf/README.txt | 75 +- autoconf/tea/Makefile.in | 16 +- autoconf/tea/README | 36 - autoconf/tea/README.txt | 78 + autoconf/tea/configure.ac | 4 +- autoconf/tea/pkgIndex.tcl.in | 4 +- autoconf/tea/tclconfig/install-sh | 437 ++-- autoconf/tea/tclconfig/tcl.m4 | 396 ++-- autoconf/tea/win/makefile.vc | 467 +--- autoconf/tea/win/rules-ext.vc | 123 + autoconf/tea/win/rules.vc | 1952 +++++++++++++--- autoconf/tea/win/targets.vc | 98 + configure | 421 ++-- configure.ac | 295 +-- doc/compile-for-unix.md | 57 + doc/compile-for-windows.md | 65 +- ext/consio/console_io.c | 91 +- ext/consio/console_io.h | 15 +- ext/expert/expert1.test | 115 + ext/expert/sqlite3expert.c | 99 +- ext/expert/test_expert.c | 10 +- ext/fts3/fts3_snippet.c | 1 + ext/fts3/fts3_term.c | 16 +- ext/fts3/fts3_test.c | 29 +- ext/fts3/fts3_tokenizer.c | 6 +- ext/fts3/unicode/mkunicode.tcl | 3 + ext/fts5/extract_api_docs.tcl | 7 +- ext/fts5/fts5.h | 122 +- ext/fts5/fts5Int.h | 73 +- ext/fts5/fts5_aux.c | 82 +- ext/fts5/fts5_config.c | 166 +- ext/fts5/fts5_expr.c | 41 +- ext/fts5/fts5_index.c | 15 +- ext/fts5/fts5_main.c | 1096 +++++++-- ext/fts5/fts5_storage.c | 486 +++- ext/fts5/fts5_tcl.c | 414 +++- ext/fts5/fts5_tokenize.c | 113 +- ext/fts5/fts5_unicode2.c | 3 + ext/fts5/fts5_vocab.c | 32 +- ext/fts5/test/fts5_common.tcl | 23 + ext/fts5/test/fts5aa.test | 4 +- ext/fts5/test/fts5ab.test | 2 +- ext/fts5/test/fts5ac.test | 2 +- ext/fts5/test/fts5ad.test | 2 +- ext/fts5/test/fts5ae.test | 2 +- ext/fts5/test/fts5af.test | 2 +- ext/fts5/test/fts5ag.test | 2 +- ext/fts5/test/fts5ah.test | 13 +- ext/fts5/test/fts5ai.test | 2 +- ext/fts5/test/fts5aj.test | 2 +- ext/fts5/test/fts5ak.test | 2 +- ext/fts5/test/fts5al.test | 12 +- ext/fts5/test/fts5alter.test | 2 +- ext/fts5/test/fts5auto.test | 2 +- ext/fts5/test/fts5aux.test | 4 +- ext/fts5/test/fts5aux2.test | 71 + ext/fts5/test/fts5auxdata.test | 2 +- ext/fts5/test/fts5bigpl.test | 2 +- ext/fts5/test/fts5blob.test | 166 ++ ext/fts5/test/fts5cat.test | 17 + ext/fts5/test/fts5colset.test | 2 +- ext/fts5/test/fts5columnsize.test | 2 +- ext/fts5/test/fts5config.test | 2 +- ext/fts5/test/fts5content.test | 39 +- ext/fts5/test/fts5contentless.test | 22 +- ext/fts5/test/fts5contentless2.test | 2 +- ext/fts5/test/fts5contentless3.test | 2 +- ext/fts5/test/fts5contentless4.test | 2 +- ext/fts5/test/fts5contentless5.test | 59 +- ext/fts5/test/fts5corrupt.test | 23 +- ext/fts5/test/fts5corrupt2.test | 7 +- ext/fts5/test/fts5corrupt3.test | 185 +- ext/fts5/test/fts5corrupt4.test | 2 +- ext/fts5/test/fts5corrupt5.test | 2 +- ext/fts5/test/fts5corrupt6.test | 2 +- ext/fts5/test/fts5corrupt7.test | 2 +- ext/fts5/test/fts5corrupt8.test | 94 + ext/fts5/test/fts5delete.test | 53 + ext/fts5/test/fts5dlidx.test | 2 +- ext/fts5/test/fts5doclist.test | 2 +- ext/fts5/test/fts5ea.test | 2 +- ext/fts5/test/fts5eb.test | 6 +- ext/fts5/test/fts5expr.test | 52 + ext/fts5/test/fts5fault4.test | 2 +- ext/fts5/test/fts5fault6.test | 2 +- ext/fts5/test/fts5faultG.test | 2 +- ext/fts5/test/fts5faultH.test | 2 +- ext/fts5/test/fts5faultI.test | 294 +++ ext/fts5/test/fts5first.test | 1 + ext/fts5/test/fts5full.test | 2 +- ext/fts5/test/fts5hash.test | 2 +- ext/fts5/test/fts5integrity.test | 5 +- ext/fts5/test/fts5integrity2.test | 56 + ext/fts5/test/fts5interrupt.test | 2 +- ext/fts5/test/fts5lastrowid.test | 2 +- ext/fts5/test/fts5locale.test | 748 ++++++ ext/fts5/test/fts5matchinfo.test | 2 +- ext/fts5/test/fts5merge.test | 2 +- ext/fts5/test/fts5misc.test | 140 +- ext/fts5/test/fts5near.test | 2 +- ext/fts5/test/fts5optimize.test | 2 +- ext/fts5/test/fts5optimize2.test | 2 +- ext/fts5/test/fts5optimize3.test | 2 +- ext/fts5/test/fts5origintext.test | 22 +- ext/fts5/test/fts5origintext2.test | 2 +- ext/fts5/test/fts5origintext3.test | 2 +- ext/fts5/test/fts5origintext4.test | 2 +- ext/fts5/test/fts5origintext5.test | 2 +- ext/fts5/test/fts5phrase.test | 18 +- ext/fts5/test/fts5plan.test | 2 +- ext/fts5/test/fts5porter.test | 2 +- ext/fts5/test/fts5porter2.test | 2 +- ext/fts5/test/fts5prefix.test | 2 +- ext/fts5/test/fts5prefix2.test | 2 +- ext/fts5/test/fts5query.test | 2 +- ext/fts5/test/fts5rank.test | 2 +- ext/fts5/test/fts5rebuild.test | 2 +- ext/fts5/test/fts5restart.test | 3 +- ext/fts5/test/fts5rowid.test | 2 +- ext/fts5/test/fts5savepoint.test | 2 +- ext/fts5/test/fts5secure8.test | 4 + ext/fts5/test/fts5securefault.test | 2 +- ext/fts5/test/fts5simple.test | 33 +- ext/fts5/test/fts5simple2.test | 2 +- ext/fts5/test/fts5simple3.test | 2 +- ext/fts5/test/fts5synonym.test | 2 +- ext/fts5/test/fts5synonym2.test | 2 +- ext/fts5/test/fts5tokenizer.test | 72 +- ext/fts5/test/fts5tokenizer2.test | 2 +- ext/fts5/test/fts5tokenizer3.test | 77 + ext/fts5/test/fts5trigram.test | 88 + ext/fts5/test/fts5trigram2.test | 27 +- ext/fts5/test/fts5ubsan.test | 2 +- ext/fts5/test/fts5unicode.test | 3 +- ext/fts5/test/fts5unicode2.test | 129 +- ext/fts5/test/fts5unicode3.test | 2 +- ext/fts5/test/fts5unicode4.test | 2 +- ext/fts5/test/fts5unindexed.test | 2 +- ext/fts5/test/fts5unindexed2.test | 297 +++ ext/fts5/test/fts5update2.test | 177 ++ ext/fts5/test/fts5version.test | 2 +- ext/fts5/test/fts5vocab.test | 3 +- ext/fts5/test/fts5vocab2.test | 2 +- ext/intck/test_intck.c | 11 +- .../src/org/sqlite/jni/capi/SQLTester.java | 19 +- .../org/sqlite/jni/test-script-interpreter.md | 11 +- ext/misc/completion.c | 2 +- ext/misc/compress.c | 2 +- ext/misc/fileio.c | 13 +- ext/misc/percentile.c | 387 ++- ext/misc/series.c | 256 +- ext/misc/sha1.c | 34 +- ext/misc/shathree.c | 229 +- ext/misc/spellfix.c | 8 +- ext/misc/sqlite3_stdio.c | 275 +++ ext/misc/sqlite3_stdio.h | 55 + ext/misc/stmtrand.c | 97 + src/test_vfstrace.c => ext/misc/vfstrace.c | 207 +- ext/misc/zipfile.c | 16 +- ext/rbu/sqlite3rbu.c | 41 +- ext/rbu/test_rbu.c | 15 +- ext/recover/dbdata.c | 1 + ext/recover/recovercorrupt4.test | 5 +- ext/recover/recoverpgsz.test | 9 +- ext/recover/sqlite3recover.c | 8 +- ext/recover/test_recover.c | 4 +- ext/rtree/test_rtreedoc.c | 10 +- ext/session/changesetfuzz1.test | 3 +- ext/session/session4.test | 7 +- ext/session/sessionalter.test | 1 + ext/session/sessiondiff.test | 2 +- ext/session/sessionfault.test | 1 + ext/session/sessionfault3.test | 24 + ext/session/sessionnoact.test | 1 + ext/session/sqlite3session.c | 49 +- ext/session/test_session.c | 81 +- ext/wasm/GNUmakefile | 591 +++-- ext/wasm/SQLTester/SQLTester.mjs | 28 +- ext/wasm/SQLTester/SQLTester.run.mjs | 15 +- ext/wasm/SQLTester/index.html | 2 - ...e3-api => EXPORTED_FUNCTIONS.sqlite3-core} | 63 +- .../api/EXPORTED_FUNCTIONS.sqlite3-extras | 63 + ext/wasm/api/pre-js.c-pp.js | 15 +- ...3-api-glue.js => sqlite3-api-glue.c-pp.js} | 332 +-- ...te3-api-oo1.js => sqlite3-api-oo1.c-pp.js} | 106 +- ext/wasm/api/sqlite3-api-prologue.js | 5 +- ...worker1.js => sqlite3-api-worker1.c-pp.js} | 7 - ext/wasm/api/sqlite3-opfs-async-proxy.js | 27 +- ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js | 67 +- ext/wasm/api/sqlite3-vfs-opfs.c-pp.js | 69 +- ext/wasm/api/sqlite3-vtab-helper.c-pp.js | 6 +- ext/wasm/api/sqlite3-wasm.c | 164 +- ext/wasm/c-pp.c | 5 +- ext/wasm/common/whwasmutil.js | 26 +- ext/wasm/fiddle.make | 82 +- ext/wasm/fiddle/emscripten.css | 24 - ext/wasm/fiddle/index.html | 26 +- ext/wasm/index.html | 6 +- ext/wasm/mkwasmbuilds.c | 284 +++ ext/wasm/speedtest1-wasmfs.mjs | 12 +- ext/wasm/tester1.c-pp.js | 655 ++++-- ext/wasm/tests/opfs/concurrency/index.html | 3 + ext/wasm/tests/opfs/concurrency/test.js | 25 + ext/wasm/wasmfs.make | 44 +- main.mk | 111 +- manifest | 811 +++---- manifest.uuid | 2 +- src/alter.c | 15 +- src/analyze.c | 20 +- src/attach.c | 15 +- src/auth.c | 2 +- src/btree.c | 35 +- src/btree.h | 3 + src/build.c | 128 +- src/ctime.c | 3 + src/date.c | 14 +- src/dbpage.c | 92 +- src/dbstat.c | 1 + src/delete.c | 18 +- src/expr.c | 272 ++- src/fkey.c | 10 +- src/func.c | 99 +- src/global.c | 1 - src/insert.c | 77 +- src/json.c | 10 +- src/main.c | 71 +- src/mutex.c | 15 +- src/os_unix.c | 101 +- src/pager.c | 1 + src/parse.y | 103 +- src/pcache.c | 1 + src/pcache1.c | 6 +- src/pragma.c | 1 + src/prepare.c | 14 +- src/printf.c | 13 +- src/resolve.c | 88 +- src/select.c | 440 ++-- src/shell.c.in | 2066 ++++++++++------- src/sqlite.h.in | 65 +- src/sqliteInt.h | 125 +- src/tclsqlite.c | 159 +- src/tclsqlite.h | 42 + src/test1.c | 387 ++- src/test2.c | 6 +- src/test3.c | 14 +- src/test4.c | 6 +- src/test5.c | 12 +- src/test6.c | 15 +- src/test8.c | 6 +- src/test9.c | 6 +- src/test_async.c | 11 +- src/test_autoext.c | 9 +- src/test_backup.c | 9 +- src/test_bestindex.c | 38 +- src/test_blob.c | 20 +- src/test_btree.c | 6 +- src/test_config.c | 26 +- src/test_demovfs.c | 9 +- src/test_fs.c | 7 +- src/test_func.c | 6 +- src/test_hexio.c | 44 +- src/test_init.c | 6 +- src/test_intarray.c | 9 +- src/test_malloc.c | 13 +- src/test_md5.c | 9 +- src/test_multiplex.c | 9 +- src/test_mutex.c | 7 +- src/test_osinst.c | 9 +- src/test_pcache.c | 3 +- src/test_quota.c | 11 +- src/test_rtree.c | 12 +- src/test_schema.c | 6 +- src/test_superlock.c | 9 +- src/test_syscall.c | 14 +- src/test_tclsh.c | 9 +- src/test_tclvar.c | 14 +- src/test_thread.c | 14 +- src/test_vdbecov.c | 6 +- src/test_vfs.c | 24 +- src/test_window.c | 2 +- src/treeview.c | 42 +- src/trigger.c | 17 +- src/update.c | 6 +- src/upsert.c | 4 +- src/utf.c | 14 +- src/util.c | 197 +- src/vacuum.c | 23 +- src/vdbe.c | 119 +- src/vdbe.h | 15 + src/vdbeInt.h | 1 + src/vdbeapi.c | 42 +- src/vdbeaux.c | 27 +- src/vdbeblob.c | 5 + src/vdbemem.c | 3 +- src/vdbesort.c | 3 +- src/vtab.c | 1 + src/wal.c | 22 +- src/walker.c | 4 +- src/where.c | 668 ++++-- src/whereInt.h | 8 +- src/wherecode.c | 361 +-- src/whereexpr.c | 63 +- src/window.c | 24 +- test/atof1.test | 2 +- test/autoindex1.test | 3 +- test/autoindex3.test | 35 + test/backcompat.test | 4 +- test/bestindexC.test | 139 ++ test/bestindexD.test | 93 + test/cast.test | 31 +- test/colname.test | 2 +- test/corrupt.test | 2 +- test/corrupt2.test | 18 +- test/corrupt4.test | 2 +- test/corruptK.test | 4 +- test/corruptN.test | 27 + test/crash.test | 2 +- test/date5.test | 86 + test/distinct2.test | 67 + test/eqp.test | 56 +- test/exclusive2.test | 4 +- test/fp-speed-1.c | 10 - test/fts3corrupt4.test | 2 + test/fts3snippet.test | 5 +- test/func.test | 3 - test/func4.test | 2 +- test/func6.test | 2 +- test/func7.test | 6 +- test/fuzz3.test | 2 +- test/fuzzcheck.c | 32 +- test/fuzzinvariants.c | 30 + test/hook.test | 37 +- test/in7.test | 56 + test/incrvacuum3.test | 4 +- test/indexexpr3.test | 83 + test/ioerr.test | 2 +- test/ioerr5.test | 4 +- test/join2.test | 21 + test/json502.test | 13 + test/like.test | 6 +- test/lock5.test | 141 +- test/memdb1.test | 2 +- test/merge1.test | 24 +- test/misc3.test | 4 +- test/nan.test | 9 +- test/orderbyB.test | 94 + test/pendingrace.test | 4 +- test/percentile.test | 473 +++- test/pushdown.test | 31 +- test/readonly.test | 5 +- test/recover.test | 1 - test/rollback.test | 2 +- test/rowvalue4.test | 6 +- test/scanstatus2.test | 2 +- test/shell1.test | 9 +- test/shell2.test | 4 + test/shell3.test | 9 + test/shell5.test | 10 +- test/shell7.test | 1 - test/skipscan1.test | 4 +- test/snapshot3.test | 39 + test/speedtest1.c | 5 - test/starschema1.test | 584 +++++ test/stmtrand.test | 48 + test/superlock.test | 4 +- test/syscall.test | 2 +- test/tabfunc01.test | 12 +- test/tester.tcl | 101 +- test/testrunner.tcl | 559 ++++- test/testrunner_data.tcl | 12 +- test/thread_common.tcl | 3 +- test/tkt-2d1a5c67d.test | 1 + test/tkt3457.test | 2 +- test/trigger1.test | 13 + test/types3.test | 5 +- test/values.test | 2 +- test/vtabH.test | 4 +- test/wal.test | 2 +- test/wal2.test | 6 +- test/wal_common.tcl | 4 - test/walcksum.test | 3 - test/walslow.test | 1 - test/wherelimit3.test | 67 + test/win32longpath.test | 27 +- test/windowE.test | 52 + test/windowpushd.test | 6 +- test/with2.test | 10 + test/with6.test | 44 + test/zipfile.test | 7 +- test/zipfile2.test | 5 +- tool/build-shell.sh | 2 +- tool/buildtclext.tcl | 254 ++ tool/custom.txt | 8 + tool/find_tclconfig.tcl | 24 + tool/lemon.c | 10 + tool/mkctimec.tcl | 2 + tool/mkkeywordhash.c | 6 + tool/mkmsvcmin.tcl | 4 +- tool/mktoolzip.tcl | 18 +- tool/mkvsix.tcl | 4 +- tool/omittest.tcl | 544 ++--- tool/pagesig.c | 2 +- tool/replace.tcl | 4 +- tool/restore_jrnl.tcl | 63 +- tool/showdb.c | 17 +- tool/spaceanal.tcl | 30 +- tool/speed-check.sh | 3 - tool/sqldiff.c | 163 +- tool/sqlite3_analyzer.c.in | 8 +- tool/sqlite3_rsync.c | 1894 +++++++++++++++ tool/src-verify.c | 4 + vsixtest/vsixtest.tcl | 4 +- 417 files changed, 22067 insertions(+), 7870 deletions(-) delete mode 100644 autoconf/tea/README create mode 100644 autoconf/tea/README.txt create mode 100644 autoconf/tea/win/rules-ext.vc create mode 100644 autoconf/tea/win/targets.vc create mode 100644 doc/compile-for-unix.md create mode 100644 ext/fts5/test/fts5aux2.test create mode 100644 ext/fts5/test/fts5blob.test create mode 100644 ext/fts5/test/fts5corrupt8.test create mode 100644 ext/fts5/test/fts5expr.test create mode 100644 ext/fts5/test/fts5faultI.test create mode 100644 ext/fts5/test/fts5integrity2.test create mode 100644 ext/fts5/test/fts5locale.test create mode 100644 ext/fts5/test/fts5tokenizer3.test create mode 100644 ext/fts5/test/fts5unindexed2.test create mode 100644 ext/fts5/test/fts5update2.test create mode 100644 ext/misc/sqlite3_stdio.c create mode 100644 ext/misc/sqlite3_stdio.h create mode 100644 ext/misc/stmtrand.c rename src/test_vfstrace.c => ext/misc/vfstrace.c (81%) rename ext/wasm/api/{EXPORTED_FUNCTIONS.sqlite3-api => EXPORTED_FUNCTIONS.sqlite3-core} (68%) create mode 100644 ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras rename ext/wasm/api/{sqlite3-api-glue.js => sqlite3-api-glue.c-pp.js} (89%) rename ext/wasm/api/{sqlite3-api-oo1.js => sqlite3-api-oo1.c-pp.js} (96%) rename ext/wasm/api/{sqlite3-api-worker1.js => sqlite3-api-worker1.c-pp.js} (98%) delete mode 100644 ext/wasm/fiddle/emscripten.css create mode 100644 ext/wasm/mkwasmbuilds.c create mode 100644 src/tclsqlite.h create mode 100644 test/bestindexD.test create mode 100644 test/date5.test create mode 100644 test/indexexpr3.test create mode 100644 test/orderbyB.test create mode 100644 test/starschema1.test create mode 100644 test/stmtrand.test mode change 100644 => 100755 test/testrunner.tcl create mode 100644 test/wherelimit3.test create mode 100644 tool/buildtclext.tcl create mode 100644 tool/find_tclconfig.tcl create mode 100644 tool/sqlite3_rsync.c diff --git a/Makefile.in b/Makefile.in index c16e1c127..583d81ef7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -83,9 +83,13 @@ TEMP_STORE = -DSQLITE_TEMP_STORE=@TEMP_STORE@ # based on configuration. (-DSQLITE_OMIT*, -DSQLITE_ENABLE*). # The same set of OMIT and ENABLE flags should be passed to the # LEMON parser generator and the mkkeywordhash tool as well. -OPT_FEATURE_FLAGS = @OPT_FEATURE_FLAGS@ +# +# Add OPTIONS=... on the command line to append additional options +# to the OPT_FEATURE_FLAGS. +# +OPT_FEATURE_FLAGS = @OPT_FEATURE_FLAGS@ $(OPTIONS) -TCC += $(OPT_FEATURE_FLAGS) +TCC += $(OPT_FEATURE_FLAGS) # Add in any optional parameters specified on the make commane line # ie. make "OPTS=-DSQLITE_ENABLE_FOO=1 -DSQLITE_OMIT_FOO=1". @@ -117,13 +121,14 @@ HAVE_TCL = @HAVE_TCL@ # TCLSH_CMD = @TCLSH_CMD@ -# Where do we want to install the tcl plugin +# Additional options when running tests using testrunner.tcl +# This is usually either blank, or else --status # -TCLLIBDIR = @TCLLIBDIR@ +TSTRNNR_OPTS = @TSTRNNR_OPTS@ -# The suffix used on shared libraries. Ex: ".dll", ".so", ".dylib" +# Where do we want to install the tcl plugin # -SHLIB_SUFFIX = @TCL_SHLIB_SUFFIX@ +TCLLIBDIR = @TCLLIBDIR@ # If gcov support was enabled by the configure script, add the appropriate # flags here. It's not always as easy as just having the user add the right @@ -454,6 +459,7 @@ TESTSRC += \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ + $(TOP)/ext/misc/stmtrand.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ @@ -644,6 +650,7 @@ FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c FUZZCHECK_SRC += $(TOP)/ext/recover/dbdata.c FUZZCHECK_SRC += $(TOP)/ext/recover/sqlite3recover.c FUZZCHECK_SRC += $(TOP)/test/vt02.c +FUZZCHECK_SRC += $(TOP)/ext/misc/percentile.c FUZZCHECK_SRC += $(TOP)/ext/misc/randomjson.c DBFUZZ_OPT = ST_OPT = -DSQLITE_OS_KV_OPTIONAL @@ -654,11 +661,16 @@ SQLITE3_SHELL_TARGET_ = sqlite3$(TEXE) SQLITE3_SHELL_TARGET_1 = SQLITE3_SHELL_TARGET = $(SQLITE3_SHELL_TARGET_@HAVE_WASI_SDK@) +# Use $(libtclsqlite3.la_$(HAVE_TCL)) to resolve to either +# libtclsqlite3.la or an empty value. +libtclsqlite3.la_0 = +libtclsqlite3.la_1 = libtclsqlite3.la + # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. # all: sqlite3.h libsqlite3.la $(SQLITE3_SHELL_TARGET) \ - $(HAVE_TCL:1=libtclsqlite3.la) + $(libtclsqlite3.la_$(HAVE_TCL)) Makefile: $(TOP)/Makefile.in ./config.status @@ -682,12 +694,26 @@ sqlite3$(TEXE): shell.c sqlite3.c shell.c sqlite3.c \ $(LIBREADLINE) $(TLIBS) -rpath "$(libdir)" -sqldiff$(TEXE): $(TOP)/tool/sqldiff.c sqlite3.lo sqlite3.h - $(LTLINK) -o $@ $(TOP)/tool/sqldiff.c sqlite3.lo $(TLIBS) +sqldiff$(TEXE): $(TOP)/tool/sqldiff.c $(TOP)/ext/misc/sqlite3_stdio.h sqlite3.lo sqlite3.h + $(LTLINK) -I$(TOP)/ext/misc -o $@ $(TOP)/tool/sqldiff.c sqlite3.lo $(TLIBS) dbhash$(TEXE): $(TOP)/tool/dbhash.c sqlite3.lo sqlite3.h $(LTLINK) -o $@ $(TOP)/tool/dbhash.c sqlite3.lo $(TLIBS) +RSYNC_SRC = \ + $(TOP)/tool/sqlite3_rsync.c \ + sqlite3.c + +RSYNC_OPT = \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -USQLITE_THREADSAFE \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_OMIT_DEPRECATED + +sqlite3_rsync$(TEXE): $(RSYNC_SRC) + $(TCC) -o $@ $(RSYNC_OPT) $(RSYNC_SRC) $(TLIBS) + scrub$(TEXE): $(TOP)/ext/misc/scrub.c sqlite3.lo $(LTLINK) -o $@ -I. -DSCRUB_STANDALONE \ $(TOP)/ext/misc/scrub.c sqlite3.lo $(TLIBS) @@ -696,13 +722,13 @@ srcck1$(BEXE): $(TOP)/tool/srcck1.c $(BCC) -o srcck1$(BEXE) $(TOP)/tool/srcck1.c sourcetest: srcck1$(BEXE) sqlite3.c - ./srcck1 sqlite3.c + ./srcck1$(BEXE) sqlite3.c -src-verify: $(TOP)/tool/src-verify.c +src-verify$(BEXE): $(TOP)/tool/src-verify.c $(BCC) -o src-verify$(BEXE) $(TOP)/tool/src-verify.c -verify-source: ./src-verify - ./src-verify $(TOP) +verify-source: ./src-verify$(BEXE) + ./src-verify$(BEXE) $(TOP) fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZERSHELL_OPT) \ @@ -759,20 +785,6 @@ dbfuzz2$(TEXE): $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h mkdir -p dbfuzz2-dir cp $(TOP)/test/dbfuzz2-seed* dbfuzz2-dir -dbfuzz2-asan: $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h - clang-6.0 $(OPT_FEATURE_FLAGS) $(OPTS) -I. -g -O0 \ - -fsanitize=fuzzer,undefined,address -o dbfuzz2-asan \ - $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c $(TLIBS) - mkdir -p dbfuzz2-dir - cp $(TOP)/test/dbfuzz2-seed* dbfuzz2-dir - -dbfuzz2-msan: $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h - clang-6.0 $(OPT_FEATURE_FLAGS) $(OPTS) -I. -g -O0 \ - -fsanitize=fuzzer,undefined,memory -o dbfuzz2-msan \ - $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c $(TLIBS) - mkdir -p dbfuzz2-dir - cp $(TOP)/test/dbfuzz2-seed* dbfuzz2-dir - mptester$(TEXE): sqlite3.lo $(TOP)/mptest/mptest.c $(LTLINK) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.lo \ $(TLIBS) -rpath "$(libdir)" @@ -799,6 +811,10 @@ has_tclsh85: sh $(TOP)/tool/cktclsh.sh 8.5 $(TCLSH_CMD) touch has_tclsh85 +has_tclconfig: + @ if test x"$(HAVE_TCL)" != "x1"; then echo 'ERROR: Requires access to "tclConfig.sh" which "configure" was not able to locate'; exit 1; fi + touch has_tclconfig + # This target creates a directory named "tsrc" and fills it with # copies of all of the C source code and header files needed to @@ -816,7 +832,7 @@ has_tclsh85: cp fts5.c fts5.h tsrc touch .target_source -sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84 +sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify$(BEXE) has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . @@ -1121,7 +1137,7 @@ tclsqlite-shell.lo: $(TOP)/src/tclsqlite.c $(HDR) tclsqlite-stubs.lo: $(TOP)/src/tclsqlite.c $(HDR) $(LTCOMPILE) -DUSE_TCL_STUBS=1 -o $@ -c $(TOP)/src/tclsqlite.c -tclsqlite3$(TEXE): tclsqlite-shell.lo libsqlite3.la +tclsqlite3$(TEXE): has_tclconfig tclsqlite-shell.lo libsqlite3.la $(LTLINK) -o $@ tclsqlite-shell.lo \ libsqlite3.la $(LIBTCL) @@ -1157,8 +1173,6 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c # Source and header files that shell.c depends on SHELL_DEP = \ $(TOP)/src/shell.c.in \ - $(TOP)/ext/consio/console_io.c \ - $(TOP)/ext/consio/console_io.h \ $(TOP)/ext/expert/sqlite3expert.c \ $(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/intck/sqlite3intck.c \ @@ -1172,11 +1186,16 @@ SHELL_DEP = \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/memtrace.c \ $(TOP)/ext/misc/pcachetrace.c \ + $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/series.c \ + $(TOP)/ext/misc/sha1.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ + $(TOP)/ext/misc/sqlite3_stdio.c \ + $(TOP)/ext/misc/sqlite3_stdio.h \ $(TOP)/ext/misc/uint.c \ + $(TOP)/ext/misc/vfstrace.c \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/sqlite3recover.c \ @@ -1308,7 +1327,7 @@ TESTFIXTURE_SRC1 = sqlite3.c TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION)) -testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC) +testfixture$(TEXE): has_tclconfig has_tclsh85 $(TESTFIXTURE_SRC) $(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \ -o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS) @@ -1357,15 +1376,20 @@ tcltest: ./testfixture$(TEXE) testrunner: testfixture$(TEXE) ./testfixture$(TEXE) $(TOP)/test/testrunner.tcl -# Runs both fuzztest and testrunner, consecutively. +# This is the testing target preferred by the core SQLite developers. +# It runs tests under a standard configuration, regardless of how +# ./configure was run. The devs run "make devtest" prior to each +# check-in, at a minimum. Probably other tests too, but at least this +# one. # -devtest: srctree-check testfixture$(TEXE) fuzztest testrunner +devtest: srctree-check sourcetest + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest $(TSTRNNR_OPTS) mdevtest: srctree-check has_tclsh85 - $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest $(TSTRNNR_OPTS) sdevtest: has_tclsh85 - $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest $(TSTRNNR_OPTS) # Validate that various generated files in the source tree # are up-to-date. @@ -1375,16 +1399,19 @@ srctree-check: $(TOP)/tool/srctree-check.tcl # Testing for a release # -releasetest: srctree-check testfixture$(TEXE) - ./testfixture$(TEXE) $(TOP)/test/testrunner.tcl release +releasetest: srctree-check has_tclsh85 verify-source + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl release $(TSTRNNR_OPTS) # Minimal testing that runs in less than 3 minutes # quicktest: ./testfixture$(TEXE) ./testfixture$(TEXE) $(TOP)/test/extraquick.test $(TESTOPTS) -# This is the common case. Run many tests that do not take too long, -# including fuzzcheck, sqlite3_analyzer, and sqldiff tests. +# Try to run tests on whatever options are specified by the +# ./configure. The developers seldom use this target. Instead +# they use "make devtest" which runs tests on a standard set of +# options regardless of how SQLite is configured. This "test" +# target is provided for legacy only. # test: srctree-check fuzztest sourcetest $(TESTPROGS) tcltest @@ -1401,19 +1428,19 @@ valgrindtest: $(TESTPROGS) valgrindfuzz smoketest: $(TESTPROGS) fuzzcheck$(TEXE) ./testfixture$(TEXE) $(TOP)/test/main.test $(TESTOPTS) -shelltest: $(TESTPROGS) - ./testfixture$(TEXT) $(TOP)/test/permutations.test shell +shelltest: + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl release shell sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in has_tclsh85 $(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in >sqlite3_analyzer.c -sqlite3_analyzer$(TEXE): sqlite3_analyzer.c +sqlite3_analyzer$(TEXE): has_tclconfig sqlite3_analyzer.c $(LTLINK) sqlite3_analyzer.c -o $@ $(LIBTCL) $(TLIBS) sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in has_tclsh85 $(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c -sqltclsh$(TEXE): sqltclsh.c +sqltclsh$(TEXE): has_tclconfig sqltclsh.c $(LTLINK) sqltclsh.c -o $@ $(LIBTCL) $(TLIBS) sqlite3_expert$(TEXE): $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqlite3expert.c $(TOP)/ext/expert/expert.c sqlite3.c @@ -1432,7 +1459,7 @@ CHECKER_DEPS =\ sqlite3_checker.c: $(CHECKER_DEPS) has_tclsh85 $(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/ext/repair/sqlite3_checker.c.in >$@ -sqlite3_checker$(TEXE): sqlite3_checker.c +sqlite3_checker$(TEXE): has_tclconfig sqlite3_checker.c $(LTLINK) sqlite3_checker.c -o $@ $(LIBTCL) $(TLIBS) dbdump$(TEXE): $(TOP)/ext/misc/dbdump.c sqlite3.lo @@ -1517,8 +1544,10 @@ snapshot-tarball: sqlite3.c sqlite3rc.h # Build a ZIP archive containing various command-line tools. # -tool-zip: testfixture sqlite3 sqldiff sqlite3_analyzer $(TOP)/tool/mktoolzip.tcl - ./testfixture $(TOP)/tool/mktoolzip.tcl +tool-zip: testfixture$(TEXE) sqlite3$(TEXE) sqldiff$(TEXE) \ + sqlite3_analyzer$(TEXE) sqlite3_rsync$(TEXE) $(TOP)/tool/mktoolzip.tcl + strip sqlite3$(TEXE) sqldiff$(TEXE) sqlite3_analyzer$(TEXE) sqlite3_rsync$(TEXE) + ./testfixture$(TEXE) $(TOP)/tool/mktoolzip.tcl # 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 @@ -1546,7 +1575,12 @@ lib_install: libsqlite3.la $(INSTALL) -d $(DESTDIR)$(libdir) $(LTINSTALL) libsqlite3.la $(DESTDIR)$(libdir) -install: sqlite3$(TEXE) lib_install sqlite3.h sqlite3.pc ${HAVE_TCL:1=tcl_install} +# Use $(tcl_install_$(HAVE_TCL)) to resolve to either tclextension-install or +# an empty value. +tcl_install_0 = +tcl_install_1 = tclextension-install + +install: sqlite3$(TEXE) lib_install sqlite3.h sqlite3.pc $(tcl_install_$(HAVE_TCL)) $(INSTALL) -d $(DESTDIR)$(bindir) $(LTINSTALL) sqlite3$(TEXE) $(DESTDIR)$(bindir) $(INSTALL) -d $(DESTDIR)$(includedir) @@ -1555,51 +1589,64 @@ install: sqlite3$(TEXE) lib_install sqlite3.h sqlite3.pc ${HAVE_TCL:1=tcl_instal $(INSTALL) -d $(DESTDIR)$(pkgconfigdir) $(INSTALL) -m 0644 sqlite3.pc $(DESTDIR)$(pkgconfigdir) -pkgIndex.tcl: - echo 'package ifneeded sqlite3 $(RELEASE) [list load [file join $$dir libtclsqlite3[info sharedlibextension]] sqlite3]' > $@ -tcl_install: lib_install libtclsqlite3.la pkgIndex.tcl - $(INSTALL) -d $(DESTDIR)$(TCLLIBDIR) - $(LTINSTALL) libtclsqlite3.la $(DESTDIR)$(TCLLIBDIR) - rm -f $(DESTDIR)$(TCLLIBDIR)/libtclsqlite3.la $(DESTDIR)$(TCLLIBDIR)/libtclsqlite3.a - $(INSTALL) -m 0644 pkgIndex.tcl $(DESTDIR)$(TCLLIBDIR) - -clean: - rm -f *.lo *.la *.o sqlite3$(TEXE) libsqlite3.la - rm -f sqlite3.h opcodes.* - rm -rf .libs .deps - rm -f lemon$(BEXE) lempar.c parse.* sqlite*.tar.gz - rm -f mkkeywordhash$(BEXE) keywordhash.h - rm -f mksourceid$(BEXE) - rm -f *.da *.bb *.bbg gmon.out - rm -rf tsrc .target_source - rm -f tclsqlite3$(TEXE) - rm -f testfixture$(TEXE) test.db +# Build the SQLite TCL extension in a way that make it compatible +# with whatever version of TCL is running as $TCLSH_CMD, possibly defined +# by --with-tclsh= +# +tclextension: tclsqlite3.c + $(TCLSH_CMD) $(TOP)/tool/buildtclext.tcl --build-only --cc "$(CC)" $(CFLAGS) $(OPT_FEATURE_FLAGS) $(OPTS) + +# Install the SQLite TCL extension in a way that is appropriate for $TCLSH_CMD +# to find it. +# +tclextension-install: tclsqlite3.c + $(TCLSH_CMD) $(TOP)/tool/buildtclext.tcl --cc "$(CC)" $(CFLAGS) $(OPT_FEATURE_FLAGS) $(OPTS) + +# Install the SQLite TCL extension that is used by $TCLSH_CMD +# +tclextension-uninstall: + $(TCLSH_CMD) $(TOP)/tool/buildtclext.tcl --uninstall + +# List all installed the SQLite TCL extension that is are accessible +# by $TCLSH_CMD, included prior versions. +# +tclextension-list: + $(TCLSH_CMD) $(TOP)/tool/buildtclext.tcl --info + + +# Remove build products sufficient so that subsequent makes will recompile +# everything from scratch. Do not remove: +# +# * test results and test logs +# * output from ./configure +# +tidy: + rm -f *.lo *.la *.o *.c *.da *.bb *.bbg gmon.* *.rws sqlite3$(TEXE) + rm -f fts5.h keywordhash.h opcodes.h sqlite3.h sqlite3ext.h sqlite3session.h + rm -rf .libs .deps tsrc .target_source + rm -f lemon$(BEXE) sqlite*.tar.gz + rm -f mkkeywordhash$(BEXE) mksourceid$(BEXE) + rm -f parse.* fts5parse.* + rm -f tclsqlite3$(TEXE) $(TESTPROGS) rm -f LogEst$(TEXE) fts3view$(TEXE) rollback-test$(TEXE) showdb$(TEXE) rm -f showjournal$(TEXE) showstat4$(TEXE) showwal$(TEXE) speedtest1$(TEXE) - rm -f wordcount$(TEXE) changeset$(TEXE) - rm -f version-info$(TEXT) - rm -f sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def - rm -f sqlite3.c - rm -f sqlite3rc.h - rm -f shell.c sqlite3ext.h - rm -f sqlite3_analyzer$(TEXE) sqlite3_analyzer.c - rm -f sqlite-*-output.vsix - rm -f mptester mptester.exe - rm -f rbu rbu.exe - rm -f srcck1 srcck1.exe - rm -f fuzzershell fuzzershell.exe - rm -f fuzzcheck fuzzcheck.exe - rm -f sqldiff sqldiff.exe - rm -f dbhash dbhash.exe - rm -f fts5.* fts5parse.* - rm -f threadtest5 - rm -f src-verify - rm -f custom.rws - rm -f has_tclsh84 has_tclsh85 + rm -f wordcount$(TEXE) changeset$(TEXE) version-info$(TEXE) + rm -f *.dll *.lib *.exp *.def *.pc *.vsix *.so *.dylib pkgIndex.tcl + rm -f sqlite3_analyzer$(TEXE) sqlite3_rsync$(TEXE) + rm -f mptester$(TEXE) rbu$(TEXE) srcck1$(TEXE) + rm -f fuzzershell$(TEXE) fuzzcheck$(TEXE) sqldiff$(TEXE) dbhash$(TEXE) + rm -f threadtest5$(TEXE) + rm -f src-verify$(BEXE) has_tclsh* has_tclconfig + +# Removes build products and test logs. Retains ./configure outputs. +# +clean: tidy + rm -rf omittest* testrunner* testdir* +# Clean up everything. No exceptions. +# distclean: clean - rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \ - $(TESTPROGS) + rm -f sqlite_cfg.h config.log config.status Makefile $(LIBTOOL) # # Windows section diff --git a/Makefile.msc b/Makefile.msc index 5257cee98..1e2084910 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -66,6 +66,14 @@ USE_STDCALL = 0 USE_SEH = 1 !ENDIF +# Use STATICALLY_LINK_TCL=1 to statically link against TCL +# +!IFNDEF STATICALLY_LINK_TCL +STATICALLY_LINK_TCL = 0 +!ELSEIF $(STATICALLY_LINK_TCL)!=0 +CCOPTS = $(CCOPTS) -DSTATIC_BUILD +!ENDIF + # Set this non-0 to have the shell executable link against the core dynamic # link library. # @@ -379,6 +387,7 @@ SQLITE_TCL_DEP = # the Windows platform. # !IFNDEF OPT_FEATURE_FLAGS +OPT_FEATURE_FLAGS = $(OPT_XTRA) !IF $(MINIMAL_AMALGAMATION)==0 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1 @@ -392,6 +401,14 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1 !ENDIF +# Additional feature-options above and beyond what are normally used can be +# be added using OPTIONS=.... on the command-line. These values are +# appended to the OPT_FEATURE_FLAGS variable. +# +!IFDEF OPTIONS +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) $(OPTIONS) +!ENDIF + # Should the session extension be enabled? If so, add compilation options # to enable it. # @@ -920,16 +937,28 @@ TCC = $(TCC) /fsanitize=address # prior to running nmake in order to match the actual installed location and # version on this machine. # -!IFNDEF TCLVERSION -TCLVERSION = 86 +!IF $(STATICALLY_LINK_TCL)!=0 +TCLSUFFIX = s !ENDIF - !IFNDEF TCLSUFFIX TCLSUFFIX = !ENDIF !IFNDEF TCLDIR -TCLDIR = $(TOP)\compat\tcl +TCLDIR = C:\Tcl +!ENDIF + +!IFNDEF TCLVERSION +!IF EXISTS("$(TCLDIR)\lib\tcl90$(TCLSUFFIX).lib") +TCLVERSION = 90 +!ELSEIF EXISTS("$(TCLDIR)\lib\tcl86$(TCLSUFFIX).lib") +TCLVERSION = 86 +!ELSEIF EXISTS("$(TCLDIR)\lib\tcl86t.lib") +TCLSUFFIX = t +TCLVERSION = 86 +!ELSE +TCLVERSION = 90 +!ENDIF !ENDIF !IFNDEF TCLINCDIR @@ -944,9 +973,21 @@ TCLLIBDIR = $(TCLDIR)\lib LIBTCL = tcl$(TCLVERSION)$(TCLSUFFIX).lib !ENDIF +!IFNDEF TCLLIBS +!IF $(STATICALLY_LINK_TCL)!=0 +TCLLIBS = /NODEFAULTLIB:libucrt.lib netapi32.lib user32.lib ucrt.lib +!ELSE +TCLLIBS = +!ENDIF +!ENDIF + !IFNDEF LIBTCLSTUB +!IF EXISTS("$(TCLLIBDIR)\tclstub$(TCLSUFFIX).lib") +LIBTCLSTUB = tclstub$(TCLSUFFIX).lib +!ELSE LIBTCLSTUB = tclstub$(TCLVERSION)$(TCLSUFFIX).lib !ENDIF +!ENDIF !IFNDEF LIBTCLPATH LIBTCLPATH = $(TCLDIR)\bin @@ -1004,10 +1045,18 @@ LIBICU = icuuc.lib icuin.lib # specific Tcl shell to use. # !IFNDEF TCLSH_CMD -!IF $(USE_TCLSH_IN_PATH)!=0 || !EXIST("$(TCLDIR)\bin\tclsh.exe") -TCLSH_CMD = tclsh -!ELSE +!IF EXISTS("$(TCLDIR)\bin\tclsh$(TCLVERSION).exe") +TCLSH_CMD = $(TCLDIR)\bin\tclsh$(TCLVERSION).exe +!ELSEIF EXISTS("$(TCLDIR)\bin\tclsh90.exe") +TCLSH_CMD = $(TCLDIR)\bin\tclsh90.exe +!ELSEIF EXISTS("$(TCLDIR)\bin\tclsh86.exe") +TCLSH_CMD = $(TCLDIR)\bin\tclsh86.exe +!ELSEIF EXISTS("$(TCLDIR)\bin\tclsh86t.exe") +TCLSH_CMD = $(TCLDIR)\bin\tclsh86t.exe +!ELSEIF EXISTS("$(TCLDIR)\bin\tclsh.exe") TCLSH_CMD = $(TCLDIR)\bin\tclsh.exe +!ELSE +TCLSH_CMD = tclsh !ENDIF !ENDIF # <> @@ -1596,6 +1645,7 @@ TESTEXT = \ $(TOP)\ext\misc\remember.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\spellfix.c \ + $(TOP)\ext\misc\stmtrand.c \ $(TOP)\ext\misc\totype.c \ $(TOP)\ext\misc\unionvtab.c \ $(TOP)\ext\misc\wholenumber.c \ @@ -1757,6 +1807,7 @@ FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\fuzzinvariants.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\vt02.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\dbdata.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\sqlite3recover.c +FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\percentile.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\randomjson.c OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c @@ -1813,12 +1864,25 @@ tclsqlite3.def: tclsqlite.lo pkgIndex.tcl: $(TOP)\VERSION for /F %%V in ('type "$(TOP)\VERSION"') do ( \ - echo package ifneeded sqlite3 @version@ [list load [file join $$dir $(SQLITE3TCLDLL)] sqlite3] \ + echo package ifneeded sqlite3 @version@ [list load [file join $$dir $(SQLITE3TCLDLL)] Sqlite3] \ | $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact @version@ %%V > pkgIndex.tcl \ ) $(SQLITE3TCLDLL): libtclsqlite3.lib $(LIBRESOBJS) tclsqlite3.def pkgIndex.tcl $(LD) $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) /DLL /DEF:tclsqlite3.def /OUT:$@ libtclsqlite3.lib $(LIBRESOBJS) $(LTLIBS) $(TLIBS) + +tclextension: $(SQLITE3TCLDLL) + +tclextension-install: $(SQLITE3TCLDLL) + $(TCLSH_CMD) $(TOP)\tool\buildtclext.tcl --install-only + +tclextension-uninstall: + $(TCLSH_CMD) $(TOP)\tool\buildtclext.tcl --uninstall + +tclextension-list: + $(TCLSH_CMD) $(TOP)\tool\buildtclext.tcl --info + + # <> $(SQLITE3DLL): $(LIBOBJ) $(LIBRESOBJS) $(CORE_LINK_DEP) @@ -1837,12 +1901,25 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT /link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) # <> -sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) - $(LTLINK) $(NO_WARN) -I$(TOP)\ext\consio $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) +sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) + $(LTLINK) $(NO_WARN) -I$(TOP)\ext\misc $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) +RSYNC_SRC = \ + $(TOP)\tool\sqlite3_rsync.c \ + $(SQLITE3C) + +RSYNC_OPT = \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_OMIT_DEPRECATED + +sqlite3_rsync.exe: $(RSYNC_SRC) $(LIBRESOBJS) + $(LTLINK) $(RSYNC_OPT) $(NO_WARN) $(RSYNC_SRC) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) + scrub.exe: $(TOP)\ext\misc\scrub.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) -DSCRUB_STANDALONE=1 $(TOP)\ext\misc\scrub.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) @@ -1870,6 +1947,10 @@ fuzzcheck.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H) fuzzcheck-asan.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) /fsanitize=address $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) +run-fuzzcheck: fuzzcheck.exe fuzzcheck-asan.exe + fuzzcheck --spinner $(FUZZDB) + fuzzcheck-asan --spinner $(FUZZDB) + ossshell.exe: $(OSSSHELL_SRC) $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(FUZZCHECK_OPTS) $(OSSSHELL_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) @@ -2275,8 +2356,6 @@ keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe # Source and header files that shell.c depends on SHELL_DEP = \ $(TOP)\src\shell.c.in \ - $(TOP)\ext\consio\console_io.c \ - $(TOP)\ext\consio\console_io.h \ $(TOP)\ext\expert\sqlite3expert.c \ $(TOP)\ext\expert\sqlite3expert.h \ $(TOP)\ext\intck\sqlite3intck.c \ @@ -2290,11 +2369,16 @@ SHELL_DEP = \ $(TOP)\ext\misc\ieee754.c \ $(TOP)\ext\misc\memtrace.c \ $(TOP)\ext\misc\pcachetrace.c \ + $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\series.c \ + $(TOP)\ext\misc\sha1.c \ $(TOP)\ext\misc\shathree.c \ $(TOP)\ext\misc\sqlar.c \ + $(TOP)\ext\misc\sqlite3_stdio.c \ + $(TOP)\ext\misc\sqlite3_stdio.h \ $(TOP)\ext\misc\uint.c \ + $(TOP)\ext\misc\vfstrace.c \ $(TOP)\ext\misc\zipfile.c \ $(TOP)\ext\recover\dbdata.c \ $(TOP)\ext\recover\sqlite3recover.c \ @@ -2493,7 +2577,7 @@ extensiontest: testfixture.exe testloadext.dll @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS) -tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe $(TOP)\tool\mktoolzip.tcl +tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe sqlite3_rsync.exe $(TOP)\tool\mktoolzip.tcl .\testfixture.exe $(TOP)\tool\mktoolzip.tcl coretestprogs: testfixture.exe sqlite3.exe @@ -2521,6 +2605,13 @@ queryplantest: testfixture.exe shell fuzztest: fuzzcheck.exe .\fuzzcheck.exe $(FUZZDATA) +# Legacy testing target for third-party integrators. The SQLite +# developers seldom use this target themselves. Instead +# they use "nmake /f Makefile.msc devtest" which runs tests on +# a standard set of options +# +test: $(TESTPROGS) sourcetest fuzztest tcltest + # Minimal testing that runs in less than 3 minutes (on a fast machine) # quicktest: testfixture.exe sourcetest @@ -2530,7 +2621,6 @@ quicktest: testfixture.exe sourcetest # This is the common case. Run many tests that do not take too long, # including fuzzcheck, sqlite3_analyzer, and sqldiff tests. # -test: $(TESTPROGS) sourcetest fuzztest tcltest # The veryquick.test TCL tests. # @@ -2544,17 +2634,27 @@ tcltest: testfixture.exe testrunner: testfixture.exe .\testfixture.exe $(TOP)\test\testrunner.tcl -# Runs both fuzztest and testrunner, consecutively. +# This is the testing target preferred by the core SQLite developers. +# It runs tests under a standard configuration. The devs run +# "nmake /f Makefile.msc devtest" prior to each check-in, at a minimum. +# Probably other tests too, but at least this one. # -devtest: testfixture.exe fuzztest testrunner +devtest: srctree-check sourcetest + $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest mdevtest: $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest +# Validate that various generated files in the source tree +# are up-to-date. +# +srctree-check: $(TOP)\tool\srctree-check.tcl + $(TCLSH_CMD) $(TOP)\tool\srctree-check.tcl + # Testing for a release # -releasetest: testfixture.exe - testfixture.exe $(TOP)\test\testrunner.tcl release +releasetest: + $(TCLSH_CMD) $(TOP)\test\testrunner.tcl release smoketest: $(TESTPROGS) @@ -2564,7 +2664,7 @@ smoketest: $(TESTPROGS) shelltest: $(TESTPROGS) .\testfixture.exe $(TOP)\test\permutations.test shell -sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE_TCL_DEP) +sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE_TCL_DEP) $(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in > $@ sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS) @@ -2686,6 +2786,16 @@ THREADTEST3_SRC = \ threadtest3.exe: $(THREADTEST3_SRC) $(TOP)\src\test_multiplex.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(TOP)\test\threadtest3.c $(TOP)\src\test_multiplex.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) +# Display key variables that control which version of TCL is to be used. +# +tcl-env: + @echo TCLDIR = $(TCLDIR) + @echo TCLVERSION = $(TCLVERSION) + @echo TCLSUFFIX = $(TCLSUFFIX) + @echo LIBTCL = $(LIBTCL) + @echo LIBTCLSTUB = $(LIBTCLSTUB) + @echo TCLSH_CMD = $(TCLSH_CMD) + LSMDIR=$(TOP)\ext\lsm1 !INCLUDE $(LSMDIR)\Makefile.msc @@ -2719,7 +2829,7 @@ clean: del /Q sqlite3.c sqlite3-*.c sqlite3.h 2>NUL del /Q sqlite3rc.h 2>NUL del /Q shell.c sqlite3ext.h sqlite3session.h 2>NUL - del /Q sqlite3_analyzer.exe sqlite3_analyzer.c 2>NUL + del /Q sqlite3_analyzer.exe sqlite3_analyzer.c sqlite3_rsync.exe 2>NUL del /Q sqlite-*-output.vsix 2>NUL del /Q fuzzershell.exe fuzzcheck.exe sqldiff.exe dbhash.exe 2>NUL del /Q sqltclsh.* 2>NUL diff --git a/README.md b/README.md index 0975a32d9..689b52dab 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,19 @@

    SQLite Source Repository

    This repository contains the complete source code for the -[SQLite database engine](https://sqlite.org/). Some test scripts -are also included. However, many other test scripts +[SQLite database engine](https://sqlite.org/), including +many test scripts. However, other test scripts and most of the documentation are managed separately. +See the [on-line documentation](https://sqlite.org/) for more information +about what SQLite is and how it works from a user's perspective. This +README file is about the source code that goes into building SQLite, +not about how SQLite is used. + ## Version Control SQLite sources are managed using -[Fossil](https://www.fossil-scm.org/), a distributed version control system +[Fossil](https://fossil-scm.org/), a distributed version control system that was specifically designed and written to support SQLite development. The [Fossil repository](https://sqlite.org/src/timeline) contains the urtext. @@ -68,24 +73,26 @@ archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows: then click on the "Tarball" or "ZIP Archive" links on the information page. -If you do want to use Fossil to check out the source tree, +To access sources directly using [Fossil](https://fossil-scm.org/home), first install Fossil version 2.0 or later. -(Source tarballs and precompiled binaries available -[here](https://www.fossil-scm.org/fossil/uv/download.html). Fossil is +Source tarballs and precompiled binaries available at +. Fossil is a stand-alone program. To install, simply download or build the single -executable file and put that file someplace on your $PATH.) +executable file and put that file someplace on your $PATH. Then run commands like this: - mkdir -p ~/sqlite ~/Fossils + mkdir -p ~/sqlite cd ~/sqlite - fossil clone https://www.sqlite.org/src ~/Fossils/sqlite.fossil - fossil open ~/Fossils/sqlite.fossil + fossil open https://sqlite.org/src -After setting up a repository using the steps above, you can always -update to the latest version using: +The "fossil open" command will take two or three minutes. Afterwards, +you can do fast, bandwidth-efficient updates to the whatever versions +of SQLite you like. Some examples: - fossil update trunk ;# latest trunk check-in - fossil update release ;# latest official release + fossil update trunk ;# latest trunk check-in + fossil update release ;# latest official release + fossil update trunk:2024-01-01 ;# First trunk check-in after 2024-01-01 + fossil update version-3.39.0 ;# Version 3.39.0 Or type "fossil ui" to get a web-based user interface. @@ -99,15 +106,42 @@ script found at the root of the source tree. Then run "make". For example: - tar xzf sqlite.tar.gz ;# Unpack the source tree into "sqlite" - mkdir bld ;# Build will occur in a sibling directory - cd bld ;# Change to the build directory - ../sqlite/configure ;# Run the configure script - make ;# Builds the "sqlite3" command-line tool - make sqlite3.c ;# Build the "amalgamation" source file - make devtest ;# Run some tests (requires Tcl) + apt install gcc make tcl-dev ;# Make sure you have all the necessary build tools + tar xzf sqlite.tar.gz ;# Unpack the source tree into "sqlite" + mkdir bld ;# Build will occur in a sibling directory + cd bld ;# Change to the build directory + ../sqlite/configure ;# Run the configure script + make sqlite3 ;# Builds the "sqlite3" command-line tool + make sqlite3.c ;# Build the "amalgamation" source file + make sqldiff ;# Builds the "sqldiff" command-line tool + # Makefile targets below this point require tcl-dev + make tclextension-install ;# Build and install the SQLite TCL extension + make devtest ;# Run development tests + make releasetest ;# Run full release tests + make sqlite3_analyzer ;# Builds the "sqlite3_analyzer" tool + +See the makefile for additional targets. For debugging builds, the +core developers typically run "configure" with options like this: + + ../sqlite/configure --enable-all --enable-debug CFLAGS='-O0 -g' + +For release builds, the core developers usually do: + + ../sqlite/configure --enable-all -See the makefile for additional targets. +Almost all makefile targets require a "tclsh" TCL interpreter version 8.6 or +later. The "tclextension-install" target and the test targets that follow +all require TCL development libraries too. ("apt install tcl-dev"). It is +helpful, but is not required, to install the SQLite TCL extension (the +"tclextension-install" target) prior to running tests. The "releasetest" +target has additional requiremenst, such as "valgrind". + +On "make" command-lines, one can add "OPTIONS=..." to specify additional +compile-time options over and above those set by ./configure. For example, +to compile with the SQLITE_OMIT_DEPRECATED compile-time option, one could say: + + ./configure --enable-all + make OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3 The configure script uses autoconf 2.61 and libtool. If the configure script does not work out for you, there is a generic makefile named @@ -117,7 +151,7 @@ show what changes are needed. ## Compiling for Windows Using MSVC -On Windows, all applicable build products can be compiled with MSVC. +On Windows, everything can be compiled with MSVC. You will also need a working installation of TCL. See the [compile-for-windows.md](doc/compile-for-windows.md) document for additional information about how to install MSVC and TCL and configure your @@ -128,48 +162,72 @@ TCL library, using a command like this: set TCLDIR=c:\Tcl -SQLite uses "tclsh.exe" as part of the build process, and so that utility -program will need to be somewhere on your %PATH%. The finished SQLite library -does not contain any TCL code, but it does use TCL to help with the build process -and to run tests. +SQLite uses "tclsh.exe" as part of the build process, and so that +program will need to be somewhere on your %PATH%. SQLite itself +does not contain any TCL code, but it does use TCL to help with the +build process and to run tests. You may need to install TCL development +libraries in order to successfully complete some makefile targets. +It is helpful, but is not required, to install the SQLite TCL extension +(the "tclextension-install" target) prior to running tests. Build using Makefile.msc. Example: - nmake /f Makefile.msc + nmake /f Makefile.msc sqlite3.exe nmake /f Makefile.msc sqlite3.c + nmake /f Makefile.msc sqldiff.exe + # Makefile targets below this point require TCL development libraries + nmake /f Makefile.msc tclextension-install nmake /f Makefile.msc devtest nmake /f Makefile.msc releasetest + nmake /f Makefile.msc sqlite3_analyzer.exe There are many other makefile targets. See comments in Makefile.msc for details. -## Source Code Tour - -Most of the core source files are in the **src/** subdirectory. The -**src/** folder also contains files used to build the "testfixture" test -harness. The names of the source files used by "testfixture" all begin -with "test". -The **src/** also contains the "shell.c" file -which is the main program for the "sqlite3.exe" -[command-line shell](https://sqlite.org/cli.html) and -the "tclsqlite.c" file which implements the -[Tcl bindings](https://sqlite.org/tclsqlite.html) for SQLite. -(Historical note: SQLite began as a Tcl -extension and only later escaped to the wild as an independent library.) - -Test scripts and programs are found in the **test/** subdirectory. -Additional test code is found in other source repositories. -See [How SQLite Is Tested](https://www.sqlite.org/testing.html) for -additional information. - -The **ext/** subdirectory contains code for extensions. The -Full-text search engine is in **ext/fts3**. The R-Tree engine is in -**ext/rtree**. The **ext/misc** subdirectory contains a number of -smaller, single-file extensions, such as a REGEXP operator. - -The **tool/** subdirectory contains various scripts and programs used -for building generated source code files or for testing or for generating -accessory programs such as "sqlite3_analyzer(.exe)". +As with the unix Makefile, the OPTIONS=... argument can be passed on the nmake +command-line to enable new compile-time options. For example: + + nmake /f Makefile.msc OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3.exe + +## Source Tree Map + + * **src/** - This directory contains the primary source code for the + SQLite core. For historical reasons, C-code used for testing is + also found here. Source files intended for testing begin with "`test`". + The `tclsqlite3.c` and `tclsqlite3.h` files are the TCL interface + for SQLite and are also not part of the core. + + * **test/** - This directory and its subdirectories contains code used + for testing. Files that end in "`.test`" are TCL scripts that run + tests using an augmented TCL interpreter named "testfixture". Use + a command like "`make testfixture`" (unix) or + "`nmake /f Makefile.msc testfixture.exe`" (windows) to build that + augmented TCL interpreter, then run individual tests using commands like + "`testfixture test/main.test`". This test/ subdirectory also contains + additional C code modules and scripts for other kinds of testing. + + * **tool/** - This directory contains programs and scripts used to + build some of the machine-generated code that goes into the SQLite + core, as well as to build and run tests and perform diagnostics. + The source code to [the Lemon parser generator](./doc/lemon.html) is + found here. There are also TCL scripts used to build and/or transform + source code files. For example, the tool/mksqlite3h.tcl script reads + the src/sqlite.h.in file and uses it as a template to construct + the deliverable "sqlite3.h" file that defines the SQLite interface. + + * **ext/** - Various extensions to SQLite are found under this + directory. For example, the FTS5 subsystem is in "ext/fts5/". + Some of these extensions (ex: FTS3/4, FTS5, RTREE) might get built + into the SQLite amalgamation, but not all of them. The + "ext/misc/" subdirectory contains an assortment of one-file extensions, + many of which are omitted from the SQLite core, but which are included + in the [SQLite CLI](https://sqlite.org/cli.html). + + * **doc/** - Some documentation files about SQLite internals are found + here. Note, however, that the primary documentation designed for + application developers and users of SQLite is in a completely separate + repository. Note also that the primary API documentation is derived + from specially constructed comments in the src/sqlite.h.in file. ### Generated Source Code Files @@ -252,31 +310,37 @@ individual source file exceeds 32K lines in length. SQLite is modular in design. See the [architectural description](https://www.sqlite.org/arch.html) for details. Other documents that are useful in -(helping to understand how SQLite works include the +helping to understand how SQLite works include the [file format](https://www.sqlite.org/fileformat2.html) description, the [virtual machine](https://www.sqlite.org/opcode.html) that runs prepared statements, the description of [how transactions work](https://www.sqlite.org/atomiccommit.html), and the [overview of the query planner](https://www.sqlite.org/optoverview.html). -Years of effort have gone into optimizing SQLite, both +Decades of effort have gone into optimizing SQLite, both for small size and high performance. And optimizations tend to result in complex code. So there is a lot of complexity in the current SQLite implementation. It will not be the easiest library in the world to hack. -Key files: +### Key source code files * **sqlite.h.in** - This file defines the public interface to the SQLite library. Readers will need to be familiar with this interface before - trying to understand how the library works internally. + trying to understand how the library works internally. This file is + really a template that is transformed into the "sqlite3.h" deliverable + using a script invoked by the makefile. * **sqliteInt.h** - this header file defines many of the data objects used internally by SQLite. In addition to "sqliteInt.h", some - subsystems have their own header files. + subsystems inside of sQLite have their own header files. These internal + interfaces are not for use by applications. They can and do change + from one release of SQLite to the next. * **parse.y** - This file describes the LALR(1) grammar that SQLite uses to parse SQL statements, and the actions that are taken at each step - in the parsing process. + in the parsing process. The file is processed by the + [Lemon Parser Generator](./doc/lemon.html) to produce the actual C code + used for parsing. * **vdbe.c** - This file implements the virtual machine that runs prepared statements. There are various helper files whose names @@ -319,6 +383,11 @@ Key files: (and some other test programs too) is built and run when you type "make test". + * **VERSION**, **manifest**, and **manifest.uuid** - These files define + the current SQLite version number. The "VERSION" file is human generated, + but the "manifest" and "manifest.uuid" files are automatically generated + by the [Fossil version control system](https://fossil-scm.org/). + There are many other source files. Each has a succinct header comment that describes its purpose and role within the larger system. @@ -336,7 +405,7 @@ The `manifest.uuid` file should contain the SHA3-256 hash of the `manifest` file. If all of the above hash comparisons are correct, then you can be confident that your source tree is authentic and unadulterated. Details on the format for the `manifest` files are available -[on the Fossil website](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest). +[on the Fossil website](https://fossil-scm.org/home/doc/trunk/www/fileformat.wiki#manifest). The process of checking source code authenticity is automated by the makefile: @@ -357,3 +426,7 @@ The main SQLite website is [https://sqlite.org/](https://sqlite.org/) with geographically distributed backups at [https://www2.sqlite.org/](https://www2.sqlite.org) and [https://www3.sqlite.org/](https://www3.sqlite.org). + +Contact the SQLite developers through the +[SQLite Forum](https://sqlite.org/forum/). In an emergency, you +can send private email to the lead developer at drh at sqlite dot org. diff --git a/VERSION b/VERSION index 421931ea1..5e895b243 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.46.1 +3.47.0 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index a4270fb2a..efebc9931 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -66,6 +66,14 @@ USE_STDCALL = 0 USE_SEH = 1 !ENDIF +# Use STATICALLY_LINK_TCL=1 to statically link against TCL +# +!IFNDEF STATICALLY_LINK_TCL +STATICALLY_LINK_TCL = 0 +!ELSEIF $(STATICALLY_LINK_TCL)!=0 +CCOPTS = $(CCOPTS) -DSTATIC_BUILD +!ENDIF + # Set this non-0 to have the shell executable link against the core dynamic # link library. # @@ -301,6 +309,7 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb # the Windows platform. # !IFNDEF OPT_FEATURE_FLAGS +OPT_FEATURE_FLAGS = $(OPT_XTRA) !IF $(MINIMAL_AMALGAMATION)==0 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1 @@ -314,6 +323,14 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1 !ENDIF +# Additional feature-options above and beyond what are normally used can be +# be added using OPTIONS=.... on the command-line. These values are +# appended to the OPT_FEATURE_FLAGS variable. +# +!IFDEF OPTIONS +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) $(OPTIONS) +!ENDIF + # Should the session extension be enabled? If so, add compilation options # to enable it. # diff --git a/autoconf/README.txt b/autoconf/README.txt index ccf5e235a..b3d351074 100644 --- a/autoconf/README.txt +++ b/autoconf/README.txt @@ -9,8 +9,37 @@ This package contains: * a Makefile.msc, sqlite3.rc, and Replace.cs for building with Microsoft Visual C++ on Windows -SUMMARY OF HOW TO BUILD -======================= +WHY USE THIS PACKAGE? +===================== + +The canonical make system for SQLite requires TCL as part of the build +process. Various TCL scripts are used to generate parts of the code and +TCL is used to run tests. But some people would prefer to build SQLite +using only generic tools and without having to install TCL. The purpose +of this package is to provide that capability. + +This package contains a pre-build SQLite amalgamation file "sqlite3.c" +(and its associated header file "sqlite3.h"). Because the amalgamation +has been pre-built, no TCL is required. + +REASONS TO USE THE CANONICAL BUILD SYSTEM RATHER THAN THIS PACKAGE +================================================================== + + * the cononical build system allows you to run tests to verify that + the build worked + * the canonical build system supports more compile-time options + * the canonical build system works for any arbitrary check-in to + the SQLite source tree + +Step-by-step instructions on how to build using the canonical make +system for SQLite can be found at: + + https://sqlite.org/src/doc/trunk/doc/compile-for-unix.md + https://sqlite.org/src/doc/trunk/doc/compile-for-windows.md + + +SUMMARY OF HOW TO BUILD USING THIS PACKAGE +========================================== Unix: ./configure; make Windows: nmake /f Makefile.msc @@ -53,48 +82,6 @@ Using Microsoft Visual C++ 2005 (or later) is recommended. Several Windows platform variants may be built by adding additional macros to the NMAKE command line. -Building for WinRT 8.0 ----------------------- - - FOR_WINRT=1 - -Using Microsoft Visual C++ 2012 (or later) is required. When using the -above, something like the following macro will need to be added to the -NMAKE command line as well: - - "NSDKLIBPATH=%WindowsSdkDir%\..\8.0\lib\win8\um\x86" - -Building for WinRT 8.1 ----------------------- - - FOR_WINRT=1 - -Using Microsoft Visual C++ 2013 (or later) is required. When using the -above, something like the following macro will need to be added to the -NMAKE command line as well: - - "NSDKLIBPATH=%WindowsSdkDir%\..\8.1\lib\winv6.3\um\x86" - -Building for UWP 10.0 ---------------------- - - FOR_WINRT=1 FOR_UWP=1 - -Using Microsoft Visual C++ 2015 (or later) is required. When using the -above, something like the following macros will need to be added to the -NMAKE command line as well: - - "NSDKLIBPATH=%WindowsSdkDir%\..\10\lib\10.0.10586.0\um\x86" - "PSDKLIBPATH=%WindowsSdkDir%\..\10\lib\10.0.10586.0\um\x86" - "NUCRTLIBPATH=%UniversalCRTSdkDir%\..\10\lib\10.0.10586.0\ucrt\x86" - -Building for the Windows 10 SDK -------------------------------- - - FOR_WIN10=1 - -Using Microsoft Visual C++ 2015 (or later) is required. When using the -above, no other macros should be needed on the NMAKE command line. Other preprocessor defines -------------------------- diff --git a/autoconf/tea/Makefile.in b/autoconf/tea/Makefile.in index 5264f89f3..cc98ab182 100644 --- a/autoconf/tea/Makefile.in +++ b/autoconf/tea/Makefile.in @@ -99,7 +99,6 @@ INSTALL_LIBRARY = @INSTALL_LIBRARY@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ CC = @CC@ -CCLD = @CCLD@ CFLAGS_DEFAULT = @CFLAGS_DEFAULT@ CFLAGS_WARNING = @CFLAGS_WARNING@ EXEEXT = @EXEEXT@ @@ -311,19 +310,6 @@ VPATH = $(srcdir):$(srcdir)/generic:$(srcdir)/unix:$(srcdir)/win:$(srcdir)/macos .c.@OBJEXT@: $(COMPILE) -c `@CYGPATH@ $<` -o $@ -tclsample.@OBJEXT@: sampleUuid.h - -$(srcdir)/manifest.uuid: - printf "git-" >$(srcdir)/manifest.uuid - (cd $(srcdir); git rev-parse HEAD >>$(srcdir)/manifest.uuid || \ - (printf "svn-r" >$(srcdir)/manifest.uuid ; \ - svn info --show-item last-changed-revision >>$(srcdir)/manifest.uuid) || \ - printf "unknown" >$(srcdir)/manifest.uuid) - -sampleUuid.h: $(srcdir)/manifest.uuid - echo "#define SAMPLE_VERSION_UUID \\" >$@ - cat $(srcdir)/manifest.uuid >>$@ - echo "" >>$@ #======================================================================== # Distribution creation @@ -451,6 +437,8 @@ install-bin-binaries: binaries fi; \ done +.SUFFIXES: .c .$(OBJEXT) + Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status cd $(top_builddir) \ && CONFIG_FILES=$@ CONFIG_HEADERS= $(SHELL) ./config.status diff --git a/autoconf/tea/README b/autoconf/tea/README deleted file mode 100644 index 99dc8b8f0..000000000 --- a/autoconf/tea/README +++ /dev/null @@ -1,36 +0,0 @@ -This is the SQLite extension for Tcl using the Tcl Extension -Architecture (TEA). For additional information on SQLite see - - http://www.sqlite.org/ - - -UNIX BUILD -========== - -Building under most UNIX systems is easy, just run the configure script -and then run make. For more information about the build process, see -the tcl/unix/README file in the Tcl src dist. The following minimal -example will install the extension in the /opt/tcl directory. - - $ cd sqlite-*-tea - $ ./configure --prefix=/opt/tcl - $ make - $ make install - -WINDOWS BUILD -============= - -The recommended method to build extensions under windows is to use the -Msys + Mingw build process. This provides a Unix-style build while -generating native Windows binaries. Using the Msys + Mingw build tools -means that you can use the same configure script as per the Unix build -to create a Makefile. See the tcl/win/README file for the URL of -the Msys + Mingw download. - -If you have VC++ then you may wish to use the files in the win -subdirectory and build the extension using just VC++. These files have -been designed to be as generic as possible but will require some -additional maintenance by the project developer to synchronise with -the TEA configure.in and Makefile.in files. Instructions for using the -VC++ makefile are written in the first part of the Makefile.vc -file. diff --git a/autoconf/tea/README.txt b/autoconf/tea/README.txt new file mode 100644 index 000000000..b50d4f29a --- /dev/null +++ b/autoconf/tea/README.txt @@ -0,0 +1,78 @@ +This is the SQLite extension for Tcl using the Tcl Extension +Architecture (TEA). + +----------------------- A BETTER WAY --------------------------- + +A better way to build the TCL extension for SQLite is to use the +canonical source code tarball. For Unix: + + ./configure --with-tclsh=$(TCLSH) + make tclextension-install + +For Windows: + + nmake /f Makefile.msc tclextension-install TCLSH_CMD=$(TCLSH) + +In both of the above, replace $(TCLSH) with the full pathname of +of the tclsh that you want the SQLite extension to work with. See +step-by-step instructions at the links below for more information: + + https://sqlite.org/src/doc/trunk/doc/compile-for-unix.md + https://sqlite.org/src/doc/trunk/doc/compile-for-windows.md + +The whole point of the amalgamation-autoconf tarball (in which this +README.txt file is embedded) is to provide a means of compiling +SQLite that does not require first installing TCL and/or "tclsh". +The canonical Makefile in the SQLite source tree provides more +capabilities (such as the the ability to run test cases to ensure +that the build worked) and is better maintained. The only +downside of the canonical Makfile is that it requires a TCL +installation. But if you are wanting to build the TCL extension for +SQLite, then presumably you already have a TCL installation. So why +not just use the more-capable and better-maintained canoncal Makefile? + +This TEA builder is derived from code found at + + http://core.tcl-lang.org/tclconfig + http://core.tcl-lang.org/sampleextension + +The SQLite developers do not understand how it works. It seems to +work for us. It might also work for you. But we cannot promise that. + +If you want to use this TEA builder and it works for you, that's fine. +But if you have trouble, the first thing you should do is go back +to using the canonical Makefile in the SQLite source tree. + +------------------------------------------------------------------ + + +UNIX BUILD +========== + +Building under most UNIX systems is easy, just run the configure script +and then run make. For more information about the build process, see +the tcl/unix/README file in the Tcl src dist. The following minimal +example will install the extension in the /opt/tcl directory. + + $ cd sqlite-*-tea + $ ./configure --prefix=/opt/tcl + $ make + $ make install + +WINDOWS BUILD +============= + +The recommended method to build extensions under windows is to use the +Msys + Mingw build process. This provides a Unix-style build while +generating native Windows binaries. Using the Msys + Mingw build tools +means that you can use the same configure script as per the Unix build +to create a Makefile. See the tcl/win/README file for the URL of +the Msys + Mingw download. + +If you have VC++ then you may wish to use the files in the win +subdirectory and build the extension using just VC++. These files have +been designed to be as generic as possible but will require some +additional maintenance by the project developer to synchronise with +the TEA configure.in and Makefile.in files. Instructions for using the +VC++ makefile are written in the first part of the Makefile.vc +file. diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index ea7eeda70..a995cc2cf 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.46.1]) +AC_INIT([sqlite],[3.47.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. @@ -97,7 +97,7 @@ TEA_ADD_TCL_SOURCES([]) # Patchs from rmax. #-------------------------------------------------------------------- AC_ARG_WITH([system-sqlite], - [AC_HELP_STRING([--with-system-sqlite], + [AS_HELP_STRING([--with-system-sqlite], [use a system-supplied libsqlite3 instead of the bundled one])], [], [with_system_sqlite=no]) if test x$with_system_sqlite != xno; then diff --git a/autoconf/tea/pkgIndex.tcl.in b/autoconf/tea/pkgIndex.tcl.in index f95f7d389..666812dee 100644 --- a/autoconf/tea/pkgIndex.tcl.in +++ b/autoconf/tea/pkgIndex.tcl.in @@ -3,8 +3,8 @@ # if {[package vsatisfies [package provide Tcl] 9.0-]} { package ifneeded sqlite3 @PACKAGE_VERSION@ \ - [list load [file join $dir @PKG_LIB_FILE9@] sqlite3] + [list load [file join $dir @PKG_LIB_FILE9@] Sqlite3] } else { package ifneeded sqlite3 @PACKAGE_VERSION@ \ - [list load [file join $dir @PKG_LIB_FILE8@] sqlite3] + [list load [file join $dir @PKG_LIB_FILE8@] Sqlite3] } diff --git a/autoconf/tea/tclconfig/install-sh b/autoconf/tea/tclconfig/install-sh index 7c34c3f92..ec298b537 100644 --- a/autoconf/tea/tclconfig/install-sh +++ b/autoconf/tea/tclconfig/install-sh @@ -1,7 +1,7 @@ #!/bin/sh # install - install a program, script, or datafile -scriptversion=2011-04-20.01; # UTC +scriptversion=2020-11-14.01; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the @@ -35,25 +35,21 @@ scriptversion=2011-04-20.01; # UTC # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent -# `make' implicit rules from creating a file called install from it +# 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. +tab=' ' nl=' ' -IFS=" "" $nl" +IFS=" $tab$nl" -# set DOITPROG to echo to test this script +# Set DOITPROG to "echo" to test this script. -# Don't use :- since 4.3BSD and earlier shells don't like it. doit=${DOITPROG-} -if test -z "$doit"; then - doit_exec=exec -else - doit_exec=$doit -fi +doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. @@ -68,22 +64,16 @@ mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} -posix_glob='?' -initialize_posix_glob=' - test "$posix_glob" != "?" || { - if (set -f) 2>/dev/null; then - posix_glob= - else - posix_glob=: - fi - } -' - posix_mkdir= # Desired mode of installed file. mode=0755 +# Create dirs (including intermediate dirs) using mode 755. +# This is like GNU 'install' as of coreutils 8.32 (2020). +mkdir_umask=22 + +backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= @@ -97,7 +87,7 @@ dir_arg= dst_arg= copy_on_change=false -no_target_directory= +is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE @@ -114,19 +104,28 @@ Options: --version display version info and exit. -c (ignored) - -C install only if different (preserve the last data modification time) + -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. + -p pass -p to $cpprog. -s $stripprog installed files. - -S $stripprog installed files. + -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG + +By default, rm is invoked with -f; when overridden with RMPROG, +it's up to you to specify -f if you want it. + +If -S is not specified, no backups are attempted. + +Email bug reports to bug-automake@gnu.org. +Automake home page: https://www.gnu.org/software/automake/ " while test $# -ne 0; do @@ -138,45 +137,62 @@ while test $# -ne 0; do -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" - shift;; + shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 - case $mode in - *' '* | *' '* | *' -'* | *'*'* | *'?'* | *'['*) - echo "$0: invalid mode: $mode" >&2 - exit 1;; - esac - shift;; + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; -o) chowncmd="$chownprog $2" - shift;; + shift;; + + -p) cpprog="$cpprog -p";; -s) stripcmd=$stripprog;; - -S) stripcmd="$stripprog $2" - shift;; + -S) backupsuffix="$2" + shift;; - -t) dst_arg=$2 - shift;; + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; - -T) no_target_directory=true;; + -T) is_target_a_directory=never;; --version) echo "$0 $scriptversion"; exit $?;; - --) shift - break;; + --) shift + break;; - -*) echo "$0: invalid option: $1" >&2 - exit 1;; + -*) echo "$0: invalid option: $1" >&2 + exit 1;; *) break;; esac shift done +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. + +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi + if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. @@ -190,6 +206,10 @@ if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then fi shift # arg dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac done fi @@ -198,11 +218,20 @@ if test $# -eq 0; then echo "$0: no input file specified." >&2 exit 1 fi - # It's OK to call `install-sh -d' without argument. + # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi + if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 @@ -219,16 +248,16 @@ if test -z "$dir_arg"; then *[0-7]) if test -z "$stripcmd"; then - u_plus_rw= + u_plus_rw= else - u_plus_rw='% 200' + u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then - u_plus_rw= + u_plus_rw= else - u_plus_rw=,u+rw + u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac @@ -236,9 +265,9 @@ fi for src do - # Protect names starting with `-'. + # Protect names problematic for 'test' and other utilities. case $src in - -*) src=./$src;; + -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then @@ -246,6 +275,10 @@ do dstdir=$dst test -d "$dstdir" dstdir_status=$? + # Don't chown directories that already exist. + if test $dstdir_status = 0; then + chowncmd="" + fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command @@ -260,185 +293,150 @@ do echo "$0: no destination specified." >&2 exit 1 fi - dst=$dst_arg - # Protect names starting with `-'. - case $dst in - -*) dst=./$dst;; - esac - # If destination is a directory, append the input filename; won't work - # if double slashes aren't ignored. + # If destination is a directory, append the input filename. if test -d "$dst"; then - if test -n "$no_target_directory"; then - echo "$0: $dst_arg: Is a directory" >&2 - exit 1 + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 fi dstdir=$dst - dst=$dstdir/`basename "$src"` + dstbase=`basename "$src"` + case $dst in + */) dst=$dst$dstbase;; + *) dst=$dst/$dstbase;; + esac dstdir_status=0 else - # Prefer dirname, but fall back on a substitute if dirname fails. - dstdir=` - (dirname "$dst") 2>/dev/null || - expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$dst" : 'X\(//\)[^/]' \| \ - X"$dst" : 'X\(//\)$' \| \ - X"$dst" : 'X\(/\)' \| . 2>/dev/null || - echo X"$dst" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q' - ` - + dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi + case $dstdir in + */) dstdirslash=$dstdir;; + *) dstdirslash=$dstdir/;; + esac + obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') - # Create intermediate dirs using mode 755 as modified by the umask. - # This is like FreeBSD 'install' as of 1997-10-28. - umask=`umask` - case $stripcmd.$umask in - # Optimize common cases. - *[2367][2367]) mkdir_umask=$umask;; - .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; - - *[0-7]) - mkdir_umask=`expr $umask + 22 \ - - $umask % 100 % 40 + $umask % 20 \ - - $umask % 10 % 4 + $umask % 2 - `;; - *) mkdir_umask=$umask,go-w;; - esac - - # With -d, create the new directory with the user-specified mode. - # Otherwise, rely on $mkdir_umask. - if test -n "$dir_arg"; then - mkdir_mode=-m$mode + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + # The $RANDOM variable is not portable (e.g., dash). Use it + # here however when possible just to lower collision chance. + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + + trap ' + ret=$? + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null + exit $ret + ' 0 + + # Because "mkdir -p" follows existing symlinks and we likely work + # directly in world-writeable /tmp, make sure that the '$tmpdir' + # directory is successfully created first before we actually test + # 'mkdir -p'. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else - mkdir_mode= + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi - - posix_mkdir=false - case $umask in - *[123567][0-7][0-7]) - # POSIX mkdir -p sets u+wx bits regardless of umask, which - # is incompatible with FreeBSD 'install' when (umask & 300) != 0. - ;; - *) - tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ - trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 - - if (umask $mkdir_umask && - exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 - then - if test -z "$dir_arg" || { - # Check for POSIX incompatibilities with -m. - # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or - # other-writeable bit of parent directory when it shouldn't. - # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. - ls_ld_tmpdir=`ls -ld "$tmpdir"` - case $ls_ld_tmpdir in - d????-?r-*) different_mode=700;; - d????-?--*) different_mode=755;; - *) false;; - esac && - $mkdirprog -m$different_mode -p -- "$tmpdir" && { - ls_ld_tmpdir_1=`ls -ld "$tmpdir"` - test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" - } - } - then posix_mkdir=: - fi - rmdir "$tmpdir/d" "$tmpdir" - else - # Remove any dirs left behind by ancient mkdir implementations. - rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null - fi - trap '' 0;; - esac;; + trap '' 0;; esac if $posix_mkdir && ( - umask $mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else - # The umask is ridiculous, or mkdir does not conform to POSIX, + # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in - /*) prefix='/';; - -*) prefix='./';; - *) prefix='';; + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; esac - eval "$initialize_posix_glob" - oIFS=$IFS IFS=/ - $posix_glob set -f + set -f set fnord $dstdir shift - $posix_glob set +f + set +f IFS=$oIFS prefixes= for d do - test -z "$d" && continue - - prefix=$prefix$d - if test -d "$prefix"; then - prefixes= - else - if $posix_mkdir; then - (umask=$mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break - # Don't fail if two instances are running concurrently. - test -d "$prefix" || exit 1 - else - case $prefix in - *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; - *) qprefix=$prefix;; - esac - prefixes="$prefixes '$qprefix'" - fi - fi - prefix=$prefix/ + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ done if test -n "$prefixes"; then - # Don't fail if two instances are running concurrently. - (umask $mkdir_umask && - eval "\$doit_exec \$mkdirprog $prefixes") || - test -d "$dstdir" || exit 1 - obsolete_mkdir_used=true + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true fi fi fi @@ -451,14 +449,25 @@ do else # Make a couple of temp file names in the proper directory. - dsttmp=$dstdir/_inst.$$_ - rmtmp=$dstdir/_rm.$$_ + dsttmp=${dstdirslash}_inst.$$_ + rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. - (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + (umask $cp_umask && + { test -z "$stripcmd" || { + # Create $dsttmp read-write so that cp doesn't create it read-only, + # which would cause strip to fail. + if test -z "$doit"; then + : >"$dsttmp" # No need to fork-exec 'touch'. + else + $doit touch "$dsttmp" + fi + } + } && + $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # @@ -473,20 +482,24 @@ do # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && - old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && - new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && - - eval "$initialize_posix_glob" && - $posix_glob set -f && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && - $posix_glob set +f && - + set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else + # If $backupsuffix is set, and the file being installed + # already exists, attempt a backup. Don't worry if it fails, + # e.g., if mv doesn't support -f. + if test -n "$backupsuffix" && test -f "$dst"; then + $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null + fi + # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || @@ -494,24 +507,24 @@ do # to itself, or perhaps because mv is so ancient that it does not # support -f. { - # Now remove or move aside any old file at destination location. - # We try this two ways since rm can't unlink itself on some - # systems and the destination file might be busy for other - # reasons. In this case, the final cleanup might fail but the new - # file should still install successfully. - { - test ! -f "$dst" || - $doit $rmcmd -f "$dst" 2>/dev/null || - { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && - { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } - } || - { echo "$0: cannot unlink or rename $dst" >&2 - (exit 1); exit 1 - } - } && - - # Now rename the file to the real destination. - $doit $mvcmd "$dsttmp" "$dst" + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 @@ -520,9 +533,9 @@ do done # Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" +# time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: diff --git a/autoconf/tea/tclconfig/tcl.m4 b/autoconf/tea/tclconfig/tcl.m4 index c83d660e1..237d50a7b 100644 --- a/autoconf/tea/tclconfig/tcl.m4 +++ b/autoconf/tea/tclconfig/tcl.m4 @@ -53,6 +53,10 @@ AC_DEFUN([TEA_PATH_TCLCONFIG], [ AS_HELP_STRING([--with-tcl], [directory containing tcl configuration (tclConfig.sh)]), [with_tclconfig="${withval}"]) + AC_ARG_WITH(tcl8, + AS_HELP_STRING([--with-tcl8], + [Compile for Tcl8 in Tcl9 environment]), + [with_tcl8="${withval}"]) AC_MSG_CHECKING([for Tcl configuration]) AC_CACHE_VAL(ac_cv_c_tclconfig,[ @@ -138,10 +142,16 @@ AC_DEFUN([TEA_PATH_TCLCONFIG], [ `ls -d /usr/pkg/lib 2>/dev/null` \ `ls -d /usr/lib 2>/dev/null` \ `ls -d /usr/lib64 2>/dev/null` \ + `ls -d /usr/lib/tcl9.0 2>/dev/null` \ + `ls -d /usr/lib/tcl8.7 2>/dev/null` \ `ls -d /usr/lib/tcl8.6 2>/dev/null` \ `ls -d /usr/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl9.0 2>/dev/null` \ + `ls -d /usr/local/lib/tcl8.7 2>/dev/null` \ `ls -d /usr/local/lib/tcl8.6 2>/dev/null` \ `ls -d /usr/local/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl9.0 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl8.7 2>/dev/null` \ `ls -d /usr/local/lib/tcl/tcl8.6 2>/dev/null` \ `ls -d /usr/local/lib/tcl/tcl8.5 2>/dev/null` \ ; do @@ -282,12 +292,18 @@ AC_DEFUN([TEA_PATH_TKCONFIG], [ `ls -d /usr/local/lib 2>/dev/null` \ `ls -d /usr/contrib/lib 2>/dev/null` \ `ls -d /usr/pkg/lib 2>/dev/null` \ + `ls -d /usr/lib/tk9.0 2>/dev/null` \ + `ls -d /usr/lib/tk8.7 2>/dev/null` \ `ls -d /usr/lib/tk8.6 2>/dev/null` \ `ls -d /usr/lib/tk8.5 2>/dev/null` \ `ls -d /usr/lib 2>/dev/null` \ `ls -d /usr/lib64 2>/dev/null` \ + `ls -d /usr/local/lib/tk9.0 2>/dev/null` \ + `ls -d /usr/local/lib/tk8.7 2>/dev/null` \ `ls -d /usr/local/lib/tk8.6 2>/dev/null` \ `ls -d /usr/local/lib/tk8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tk9.0 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tk8.7 2>/dev/null` \ `ls -d /usr/local/lib/tcl/tk8.6 2>/dev/null` \ `ls -d /usr/local/lib/tcl/tk8.5 2>/dev/null` \ ; do @@ -366,10 +382,10 @@ AC_DEFUN([TEA_LOAD_TCLCONFIG], [ AC_MSG_CHECKING([for existence of ${TCL_BIN_DIR}/tclConfig.sh]) if test -f "${TCL_BIN_DIR}/tclConfig.sh" ; then - AC_MSG_RESULT([loading]) + AC_MSG_RESULT([loading]) . "${TCL_BIN_DIR}/tclConfig.sh" else - AC_MSG_RESULT([could not find ${TCL_BIN_DIR}/tclConfig.sh]) + AC_MSG_RESULT([could not find ${TCL_BIN_DIR}/tclConfig.sh]) fi # If the TCL_BIN_DIR is the build directory (not the install directory), @@ -379,9 +395,9 @@ AC_DEFUN([TEA_LOAD_TCLCONFIG], [ # instead of TCL_BUILD_LIB_SPEC since it will work with both an # installed and uninstalled version of Tcl. if test -f "${TCL_BIN_DIR}/Makefile" ; then - TCL_LIB_SPEC="${TCL_BUILD_LIB_SPEC}" - TCL_STUB_LIB_SPEC="${TCL_BUILD_STUB_LIB_SPEC}" - TCL_STUB_LIB_PATH="${TCL_BUILD_STUB_LIB_PATH}" + TCL_LIB_SPEC="${TCL_BUILD_LIB_SPEC}" + TCL_STUB_LIB_SPEC="${TCL_BUILD_STUB_LIB_SPEC}" + TCL_STUB_LIB_PATH="${TCL_BUILD_STUB_LIB_PATH}" elif test "`uname -s`" = "Darwin"; then # If Tcl was built as a framework, attempt to use the libraries # from the framework at the given location so that linking works @@ -474,10 +490,10 @@ AC_DEFUN([TEA_LOAD_TKCONFIG], [ AC_MSG_CHECKING([for existence of ${TK_BIN_DIR}/tkConfig.sh]) if test -f "${TK_BIN_DIR}/tkConfig.sh" ; then - AC_MSG_RESULT([loading]) + AC_MSG_RESULT([loading]) . "${TK_BIN_DIR}/tkConfig.sh" else - AC_MSG_RESULT([could not find ${TK_BIN_DIR}/tkConfig.sh]) + AC_MSG_RESULT([could not find ${TK_BIN_DIR}/tkConfig.sh]) fi # If the TK_BIN_DIR is the build directory (not the install directory), @@ -487,9 +503,9 @@ AC_DEFUN([TEA_LOAD_TKCONFIG], [ # instead of TK_BUILD_LIB_SPEC since it will work with both an # installed and uninstalled version of Tcl. if test -f "${TK_BIN_DIR}/Makefile" ; then - TK_LIB_SPEC="${TK_BUILD_LIB_SPEC}" - TK_STUB_LIB_SPEC="${TK_BUILD_STUB_LIB_SPEC}" - TK_STUB_LIB_PATH="${TK_BUILD_STUB_LIB_PATH}" + TK_LIB_SPEC="${TK_BUILD_LIB_SPEC}" + TK_STUB_LIB_SPEC="${TK_BUILD_STUB_LIB_SPEC}" + TK_STUB_LIB_PATH="${TK_BUILD_STUB_LIB_PATH}" elif test "`uname -s`" = "Darwin"; then # If Tk was built as a framework, attempt to use the libraries # from the framework at the given location so that linking works @@ -567,37 +583,37 @@ AC_DEFUN([TEA_LOAD_TKCONFIG], [ AC_DEFUN([TEA_PROG_TCLSH], [ AC_MSG_CHECKING([for tclsh]) if test -f "${TCL_BIN_DIR}/Makefile" ; then - # tclConfig.sh is in Tcl build directory - if test "${TEA_PLATFORM}" = "windows"; then - if test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" ; then - TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" - elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" ; then - TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" - elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" ; then - TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" - elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" ; then - TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" - fi - else - TCLSH_PROG="${TCL_BIN_DIR}/tclsh" - fi + # tclConfig.sh is in Tcl build directory + if test "${TEA_PLATFORM}" = "windows"; then + if test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" + fi + else + TCLSH_PROG="${TCL_BIN_DIR}/tclsh" + fi else - # tclConfig.sh is in install location - if test "${TEA_PLATFORM}" = "windows"; then - TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" - else - TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}" - fi - list="`ls -d ${TCL_BIN_DIR}/../bin 2>/dev/null` \ - `ls -d ${TCL_BIN_DIR}/.. 2>/dev/null` \ - `ls -d ${TCL_PREFIX}/bin 2>/dev/null`" - for i in $list ; do - if test -f "$i/${TCLSH_PROG}" ; then - REAL_TCL_BIN_DIR="`cd "$i"; pwd`/" - break - fi - done - TCLSH_PROG="${REAL_TCL_BIN_DIR}${TCLSH_PROG}" + # tclConfig.sh is in install location + if test "${TEA_PLATFORM}" = "windows"; then + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" + else + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}" + fi + list="`ls -d ${TCL_BIN_DIR}/../bin 2>/dev/null` \ + `ls -d ${TCL_BIN_DIR}/.. 2>/dev/null` \ + `ls -d ${TCL_PREFIX}/bin 2>/dev/null`" + for i in $list ; do + if test -f "$i/${TCLSH_PROG}" ; then + REAL_TCL_BIN_DIR="`cd "$i"; pwd`/" + break + fi + done + TCLSH_PROG="${REAL_TCL_BIN_DIR}${TCLSH_PROG}" fi AC_MSG_RESULT([${TCLSH_PROG}]) AC_SUBST(TCLSH_PROG) @@ -625,37 +641,37 @@ AC_DEFUN([TEA_PROG_TCLSH], [ AC_DEFUN([TEA_PROG_WISH], [ AC_MSG_CHECKING([for wish]) if test -f "${TK_BIN_DIR}/Makefile" ; then - # tkConfig.sh is in Tk build directory - if test "${TEA_PLATFORM}" = "windows"; then - if test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" ; then - WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" - elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}s${EXEEXT}" ; then - WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}$s{EXEEXT}" - elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" ; then - WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" - elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" ; then - WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" - fi - else - WISH_PROG="${TK_BIN_DIR}/wish" - fi + # tkConfig.sh is in Tk build directory + if test "${TEA_PLATFORM}" = "windows"; then + if test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}s${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}$s{EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" + fi + else + WISH_PROG="${TK_BIN_DIR}/wish" + fi else - # tkConfig.sh is in install location - if test "${TEA_PLATFORM}" = "windows"; then - WISH_PROG="wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" - else - WISH_PROG="wish${TK_MAJOR_VERSION}.${TK_MINOR_VERSION}" - fi - list="`ls -d ${TK_BIN_DIR}/../bin 2>/dev/null` \ - `ls -d ${TK_BIN_DIR}/.. 2>/dev/null` \ - `ls -d ${TK_PREFIX}/bin 2>/dev/null`" - for i in $list ; do - if test -f "$i/${WISH_PROG}" ; then - REAL_TK_BIN_DIR="`cd "$i"; pwd`/" - break - fi - done - WISH_PROG="${REAL_TK_BIN_DIR}${WISH_PROG}" + # tkConfig.sh is in install location + if test "${TEA_PLATFORM}" = "windows"; then + WISH_PROG="wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" + else + WISH_PROG="wish${TK_MAJOR_VERSION}.${TK_MINOR_VERSION}" + fi + list="`ls -d ${TK_BIN_DIR}/../bin 2>/dev/null` \ + `ls -d ${TK_BIN_DIR}/.. 2>/dev/null` \ + `ls -d ${TK_PREFIX}/bin 2>/dev/null`" + for i in $list ; do + if test -f "$i/${WISH_PROG}" ; then + REAL_TK_BIN_DIR="`cd "$i"; pwd`/" + break + fi + done + WISH_PROG="${REAL_TK_BIN_DIR}${WISH_PROG}" fi AC_MSG_RESULT([${WISH_PROG}]) AC_SUBST(WISH_PROG) @@ -717,22 +733,22 @@ AC_DEFUN([TEA_ENABLE_SHARED], [ if test "$shared_ok" = "yes" ; then AC_MSG_RESULT([shared]) SHARED_BUILD=1 - STUBS_BUILD=1 + STUBS_BUILD=1 else AC_MSG_RESULT([static]) SHARED_BUILD=0 AC_DEFINE(STATIC_BUILD, 1, [This a static build]) - if test "$stubs_ok" = "yes" ; then - STUBS_BUILD=1 - else - STUBS_BUILD=0 - fi + if test "$stubs_ok" = "yes" ; then + STUBS_BUILD=1 + else + STUBS_BUILD=0 + fi fi if test "${STUBS_BUILD}" = "1" ; then AC_DEFINE(USE_TCL_STUBS, 1, [Use Tcl stubs]) AC_DEFINE(USE_TCLOO_STUBS, 1, [Use TclOO stubs]) if test "${TEA_WINDOWINGSYSTEM}" != ""; then - AC_DEFINE(USE_TK_STUBS, 1, [Use Tk stubs]) + AC_DEFINE(USE_TK_STUBS, 1, [Use Tk stubs]) fi fi @@ -1177,21 +1193,21 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ fi if test "$GCC" != "yes" ; then - if test "${SHARED_BUILD}" = "0" ; then + if test "${SHARED_BUILD}" = "0" ; then runtime=-MT - else + else runtime=-MD - fi - case "x`echo \${VisualStudioVersion}`" in - x1[[4-9]]*) - lflags="${lflags} -nodefaultlib:libucrt.lib" - TEA_ADD_LIBS([ucrt.lib]) - ;; - *) - ;; - esac - - if test "$do64bit" != "no" ; then + fi + case "x`echo \${VisualStudioVersion}`" in + x1[[4-9]]*) + lflags="${lflags} -nodefaultlib:libucrt.lib" + TEA_ADD_LIBS([ucrt.lib]) + ;; + *) + ;; + esac + + if test "$do64bit" != "no" ; then CC="cl.exe" RC="rc.exe" lflags="${lflags} -nologo -MACHINE:${MACHINE} " @@ -1490,14 +1506,14 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ # Check to enable 64-bit flags for compiler/linker AS_IF([test "$do64bit" = yes], [ - AS_IF([test "$GCC" = yes], [ - AC_MSG_WARN([64bit mode not supported by gcc]) - ], [ - do64bit_ok=yes - SHLIB_LD="ld -64 -shared -rdata_shared" - CFLAGS="$CFLAGS -64" - LDFLAGS_ARCH="-64" - ]) + AS_IF([test "$GCC" = yes], [ + AC_MSG_WARN([64bit mode not supported by gcc]) + ], [ + do64bit_ok=yes + SHLIB_LD="ld -64 -shared -rdata_shared" + CFLAGS="$CFLAGS -64" + LDFLAGS_ARCH="-64" + ]) ]) ;; Linux*|GNU*|NetBSD-Debian|DragonFly-*|FreeBSD-*) @@ -1519,7 +1535,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LDFLAGS="$LDFLAGS $PTHREAD_LIBS"]) ;; - esac + esac AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) @@ -1724,9 +1740,9 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ # Digital OSF/1 SHLIB_CFLAGS="" AS_IF([test "$SHARED_BUILD" = 1], [ - SHLIB_LD='ld -shared -expect_unresolved "*"' + SHLIB_LD='ld -shared -expect_unresolved "*"' ], [ - SHLIB_LD='ld -non_shared -expect_unresolved "*"' + SHLIB_LD='ld -non_shared -expect_unresolved "*"' ]) SHLIB_SUFFIX=".so" AS_IF([test $doRpath = yes], [ @@ -1896,7 +1912,7 @@ AC_DEFUN([TEA_CONFIG_CFLAGS], [ LDFLAGS="$LDFLAGS -Wl,-Bexport" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], [tcl_cv_ld_Bexport=yes],[tcl_cv_ld_Bexport=no]) - LDFLAGS=$hold_ldflags]) + LDFLAGS=$hold_ldflags]) AS_IF([test $tcl_cv_ld_Bexport = yes], [ LDFLAGS="$LDFLAGS -Wl,-Bexport" ]) @@ -2015,8 +2031,8 @@ dnl # preprocessing tests use only CPPFLAGS. SHORT s; LONG l; ]])], - [tcl_cv_winnt_ignore_void=yes], - [tcl_cv_winnt_ignore_void=no]) + [tcl_cv_winnt_ignore_void=yes], + [tcl_cv_winnt_ignore_void=no]) ) if test "$tcl_cv_winnt_ignore_void" = "yes" ; then AC_DEFINE(HAVE_WINNT_IGNORE_VOID, 1, @@ -2529,20 +2545,19 @@ AC_DEFUN([TEA_TCL_LINK_LIBS], [ # # Might define the following vars: # _ISOC99_SOURCE -# _LARGEFILE64_SOURCE -# _LARGEFILE_SOURCE64 +# _FILE_OFFSET_BITS # #-------------------------------------------------------------------- AC_DEFUN([TEA_TCL_EARLY_FLAG],[ AC_CACHE_VAL([tcl_cv_flag_]translit($1,[A-Z],[a-z]), AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$2]], [[$3]])], - [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no,[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[[#define ]$1[ 1 + [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no,[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[[#define ]$1[ ]m4_default([$4],[1])[ ]$2]], [[$3]])], [tcl_cv_flag_]translit($1,[A-Z],[a-z])=yes, [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no)])) if test ["x${tcl_cv_flag_]translit($1,[A-Z],[a-z])[}" = "xyes"] ; then - AC_DEFINE($1, 1, [Add the ]$1[ flag when building]) + AC_DEFINE($1, m4_default([$4],[1]), [Add the ]$1[ flag when building]) tcl_flags="$tcl_flags $1" fi ]) @@ -2552,10 +2567,10 @@ AC_DEFUN([TEA_TCL_EARLY_FLAGS],[ tcl_flags="" TEA_TCL_EARLY_FLAG(_ISOC99_SOURCE,[#include ], [char *p = (char *)strtoll; char *q = (char *)strtoull;]) - TEA_TCL_EARLY_FLAG(_LARGEFILE64_SOURCE,[#include ], - [struct stat64 buf; int i = stat64("/", &buf);]) - TEA_TCL_EARLY_FLAG(_LARGEFILE_SOURCE64,[#include ], - [char *p = (char *)open64;]) + if test "${TCL_MAJOR_VERSION}" -ne 8 ; then + TEA_TCL_EARLY_FLAG(_FILE_OFFSET_BITS,[#include ], + [switch (0) { case 0: case (sizeof(off_t)==sizeof(long long)): ; }],64) + fi if test "x${tcl_flags}" = "x" ; then AC_MSG_RESULT([none]) else @@ -2579,6 +2594,7 @@ AC_DEFUN([TEA_TCL_EARLY_FLAGS],[ # HAVE_STRUCT_DIRENT64, HAVE_DIR64 # HAVE_STRUCT_STAT64 # HAVE_TYPE_OFF64_T +# _TIME_BITS # #-------------------------------------------------------------------- @@ -2592,9 +2608,9 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ # See if we could use long anyway Note that we substitute in the # type that is our current guess for a 64-bit type inside this check # program, so it should be modified only carefully... - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[switch (0) { - case 1: case (sizeof(${tcl_type_64bit})==sizeof(long)): ; - }]])],[tcl_cv_type_64bit=${tcl_type_64bit}],[])]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[switch (0) { + case 1: case (sizeof(${tcl_type_64bit})==sizeof(long)): ; + }]])],[tcl_cv_type_64bit=${tcl_type_64bit}],[])]) if test "${tcl_cv_type_64bit}" = none ; then AC_DEFINE(TCL_WIDE_INT_IS_LONG, 1, [Do 'long' and 'long long' have the same size (64-bit)?]) AC_MSG_RESULT([yes]) @@ -2609,6 +2625,25 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ AC_MSG_RESULT([${tcl_cv_type_64bit}]) # Now check for auxiliary declarations + if test "${TCL_MAJOR_VERSION}" -ne 8 ; then + AC_CACHE_CHECK([for 64-bit time_t], tcl_cv_time_t_64,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;}]])], + [tcl_cv_time_t_64=yes],[tcl_cv_time_t_64=no])]) + if test "x${tcl_cv_time_t_64}" = "xno" ; then + # Note that _TIME_BITS=64 requires _FILE_OFFSET_BITS=64 + # which SC_TCL_EARLY_FLAGS has defined if necessary. + AC_CACHE_CHECK([if _TIME_BITS=64 enables 64-bit time_t], tcl_cv__time_bits,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#define _TIME_BITS 64 +#include ]], + [[switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;}]])], + [tcl_cv__time_bits=yes],[tcl_cv__time_bits=no])]) + if test "x${tcl_cv__time_bits}" = "xyes" ; then + AC_DEFINE(_TIME_BITS, 64, [_TIME_BITS=64 enables 64-bit time_t.]) + fi + fi + fi + AC_CACHE_CHECK([for struct dirent64], tcl_cv_struct_dirent64,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[struct dirent64 p;]])], @@ -2620,7 +2655,7 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ AC_CACHE_CHECK([for DIR64], tcl_cv_DIR64,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[struct dirent64 *p; DIR64 d = opendir64("."); - p = readdir64(d); rewinddir64(d); closedir64(d);]])], + p = readdir64(d); rewinddir64(d); closedir64(d);]])], [tcl_cv_DIR64=yes], [tcl_cv_DIR64=no])]) if test "x${tcl_cv_DIR64}" = "xyes" ; then AC_DEFINE(HAVE_DIR64, 1, [Is 'DIR64' in ?]) @@ -2643,8 +2678,8 @@ AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ dnl Define HAVE_TYPE_OFF64_T only when the off64_t type and the dnl functions lseek64 and open64 are defined. if test "x${tcl_cv_type_off64_t}" = "xyes" && \ - test "x${ac_cv_func_lseek64}" = "xyes" && \ - test "x${ac_cv_func_open64}" = "xyes" ; then + test "x${ac_cv_func_lseek64}" = "xyes" && \ + test "x${ac_cv_func_open64}" = "xyes" ; then AC_DEFINE(HAVE_TYPE_OFF64_T, 1, [Is off64_t in ?]) AC_MSG_RESULT([yes]) else @@ -2747,8 +2782,6 @@ The PACKAGE_NAME variable must be defined by your TEA configure.ac]) AC_SUBST(PKG_LIB_FILE) AC_SUBST(PKG_LIB_FILE8) AC_SUBST(PKG_LIB_FILE9) - # Substitute STUB_LIB_FILE in case package creates a stub library too. - AC_SUBST(PKG_STUB_LIB_FILE) # We AC_SUBST these here to ensure they are subst'ed, # in case the user doesn't call TEA_ADD_... @@ -3106,11 +3139,15 @@ AC_DEFUN([TEA_SETUP_COMPILER], [ fi fi + if test "${TCL_MAJOR_VERSION}" -lt 9 -a "${TCL_MINOR_VERSION}" -lt 7; then + AC_DEFINE(Tcl_Size, int, [Is 'Tcl_Size' in ?]) + fi + #-------------------------------------------------------------------- # Common compiler flag setup #-------------------------------------------------------------------- - AC_C_BIGENDIAN + AC_C_BIGENDIAN(,,,[#]) ]) #------------------------------------------------------------------------ @@ -3174,10 +3211,11 @@ print("manifest needed") PACKAGE_LIB_PREFIX8="${PACKAGE_LIB_PREFIX}" PACKAGE_LIB_PREFIX9="${PACKAGE_LIB_PREFIX}tcl9" - if test "${TCL_MAJOR_VERSION}" -gt 8 ; then + if test "${TCL_MAJOR_VERSION}" -gt 8 -a x"${with_tcl8}" = x; then PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX9}" else PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX8}" + AC_DEFINE(TCL_MAJOR_VERSION, 8, [Compile for Tcl8?]) fi if test "${TEA_PLATFORM}" = "windows" ; then if test "${SHARED_BUILD}" = "1" ; then @@ -3202,7 +3240,11 @@ print("manifest needed") eval eval "PKG_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" fi # Some packages build their own stubs libraries - eval eval "PKG_STUB_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + if test "${TCL_MAJOR_VERSION}" -gt 8 -a x"${with_tcl8}" = x; then + eval eval "PKG_STUB_LIB_FILE=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub.a" + else + eval eval "PKG_STUB_LIB_FILE=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + fi if test "$GCC" = "yes"; then PKG_STUB_LIB_FILE=lib${PKG_STUB_LIB_FILE} fi @@ -3221,12 +3263,16 @@ print("manifest needed") eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" RANLIB=: else - eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" - eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE8=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" fi # Some packages build their own stubs libraries - eval eval "PKG_STUB_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + if test "${TCL_MAJOR_VERSION}" -gt 8 -a x"${with_tcl8}" = x; then + eval eval "PKG_STUB_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub.a" + else + eval eval "PKG_STUB_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + fi fi # These are escaped so that only CFLAGS is picked up at configure time. @@ -3240,6 +3286,8 @@ print("manifest needed") AC_SUBST(MAKE_SHARED_LIB) AC_SUBST(MAKE_STATIC_LIB) AC_SUBST(MAKE_STUB_LIB) + # Substitute STUB_LIB_FILE in case package creates a stub library too. + AC_SUBST(PKG_STUB_LIB_FILE) AC_SUBST(RANLIB_STUB) AC_SUBST(VC_MANIFEST_EMBED_DLL) AC_SUBST(VC_MANIFEST_EMBED_EXE) @@ -3366,9 +3414,9 @@ AC_DEFUN([TEA_PRIVATE_TCL_HEADERS], [ # any *_NATIVE vars be defined in the Makefile TCL_INCLUDES="-I${TCL_GENERIC_DIR_NATIVE} -I${TCL_PLATFORM_DIR_NATIVE}" if test "`uname -s`" = "Darwin"; then - # If Tcl was built as a framework, attempt to use - # the framework's Headers and PrivateHeaders directories - case ${TCL_DEFS} in + # If Tcl was built as a framework, attempt to use + # the framework's Headers and PrivateHeaders directories + case ${TCL_DEFS} in *TCL_FRAMEWORK*) if test -d "${TCL_BIN_DIR}/Headers" -a \ -d "${TCL_BIN_DIR}/PrivateHeaders"; then @@ -3376,7 +3424,7 @@ AC_DEFUN([TEA_PRIVATE_TCL_HEADERS], [ else TCL_INCLUDES="${TCL_INCLUDES} ${TCL_INCLUDE_SPEC} `echo "${TCL_INCLUDE_SPEC}" | sed -e 's/Headers/PrivateHeaders/'`" fi - ;; + ;; esac result="Using ${TCL_INCLUDES}" else @@ -3817,10 +3865,10 @@ AC_DEFUN([TEA_LOAD_CONFIG], [ AC_MSG_CHECKING([for existence of ${$1_BIN_DIR}/$1Config.sh]) if test -f "${$1_BIN_DIR}/$1Config.sh" ; then - AC_MSG_RESULT([loading]) + AC_MSG_RESULT([loading]) . "${$1_BIN_DIR}/$1Config.sh" else - AC_MSG_RESULT([file not found]) + AC_MSG_RESULT([file not found]) fi # @@ -3834,11 +3882,11 @@ AC_DEFUN([TEA_LOAD_CONFIG], [ if test -f "${$1_BIN_DIR}/Makefile" ; then AC_MSG_WARN([Found Makefile - using build library specs for $1]) - $1_LIB_SPEC=${$1_BUILD_LIB_SPEC} - $1_STUB_LIB_SPEC=${$1_BUILD_STUB_LIB_SPEC} - $1_STUB_LIB_PATH=${$1_BUILD_STUB_LIB_PATH} - $1_INCLUDE_SPEC=${$1_BUILD_INCLUDE_SPEC} - $1_LIBRARY_PATH=${$1_LIBRARY_PATH} + $1_LIB_SPEC=${$1_BUILD_LIB_SPEC} + $1_STUB_LIB_SPEC=${$1_BUILD_STUB_LIB_SPEC} + $1_STUB_LIB_PATH=${$1_BUILD_STUB_LIB_PATH} + $1_INCLUDE_SPEC=${$1_BUILD_INCLUDE_SPEC} + $1_LIBRARY_PATH=${$1_LIBRARY_PATH} fi AC_SUBST($1_VERSION) @@ -3919,6 +3967,10 @@ AC_DEFUN([TEA_EXPORT_CONFIG], [ eval $1_LIB_FLAG="-l$1`echo ${PACKAGE_VERSION} | tr -d .`" eval $1_STUB_LIB_FLAG="-l$1stub`echo ${PACKAGE_VERSION} | tr -d .`" fi + if test "${TCL_MAJOR_VERSION}" -gt 8 -a x"${with_tcl8}" = x; then + eval $1_STUB_LIB_FLAG="-l$1stub" + fi + $1_BUILD_LIB_SPEC="-L`$CYGPATH $(pwd)` ${$1_LIB_FLAG}" $1_LIB_SPEC="-L`$CYGPATH ${pkglibdir}` ${$1_LIB_FLAG}" $1_BUILD_STUB_LIB_SPEC="-L`$CYGPATH $(pwd)` [$]{$1_STUB_LIB_FLAG}" @@ -4008,52 +4060,52 @@ AC_DEFUN([TEA_ZIPFS_SUPPORT], [ AC_CACHE_VAL(ac_cv_path_macher, [ search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do - for j in `ls -r $dir/macher 2> /dev/null` \ - `ls -r $dir/macher 2> /dev/null` ; do - if test x"$ac_cv_path_macher" = x ; then - if test -f "$j" ; then - ac_cv_path_macher=$j - break - fi - fi - done + for j in `ls -r $dir/macher 2> /dev/null` \ + `ls -r $dir/macher 2> /dev/null` ; do + if test x"$ac_cv_path_macher" = x ; then + if test -f "$j" ; then + ac_cv_path_macher=$j + break + fi + fi + done done ]) if test -f "$ac_cv_path_macher" ; then - MACHER_PROG="$ac_cv_path_macher" - AC_MSG_RESULT([$MACHER_PROG]) - AC_MSG_RESULT([Found macher in environment]) + MACHER_PROG="$ac_cv_path_macher" + AC_MSG_RESULT([$MACHER_PROG]) + AC_MSG_RESULT([Found macher in environment]) fi AC_MSG_CHECKING([for zip]) AC_CACHE_VAL(ac_cv_path_zip, [ search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do - for j in `ls -r $dir/zip 2> /dev/null` \ - `ls -r $dir/zip 2> /dev/null` ; do - if test x"$ac_cv_path_zip" = x ; then - if test -f "$j" ; then - ac_cv_path_zip=$j - break - fi - fi - done + for j in `ls -r $dir/zip 2> /dev/null` \ + `ls -r $dir/zip 2> /dev/null` ; do + if test x"$ac_cv_path_zip" = x ; then + if test -f "$j" ; then + ac_cv_path_zip=$j + break + fi + fi + done done ]) if test -f "$ac_cv_path_zip" ; then - ZIP_PROG="$ac_cv_path_zip" - AC_MSG_RESULT([$ZIP_PROG]) - ZIP_PROG_OPTIONS="-rq" - ZIP_PROG_VFSSEARCH="*" - AC_MSG_RESULT([Found INFO Zip in environment]) - # Use standard arguments for zip + ZIP_PROG="$ac_cv_path_zip" + AC_MSG_RESULT([$ZIP_PROG]) + ZIP_PROG_OPTIONS="-rq" + ZIP_PROG_VFSSEARCH="*" + AC_MSG_RESULT([Found INFO Zip in environment]) + # Use standard arguments for zip else - # It is not an error if an installed version of Zip can't be located. - # We can use the locally distributed minizip instead - ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" - ZIP_PROG_OPTIONS="-o -r" - ZIP_PROG_VFSSEARCH="*" - ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" - AC_MSG_RESULT([No zip found on PATH. Building minizip]) + # It is not an error if an installed version of Zip can't be located. + # We can use the locally distributed minizip instead + ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" + ZIP_PROG_OPTIONS="-o -r" + ZIP_PROG_VFSSEARCH="*" + ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" + AC_MSG_RESULT([No zip found on PATH. Building minizip]) fi AC_SUBST(MACHER_PROG) AC_SUBST(ZIP_PROG) @@ -4064,4 +4116,4 @@ AC_DEFUN([TEA_ZIPFS_SUPPORT], [ # Local Variables: # mode: autoconf -# End: \ No newline at end of file +# End: diff --git a/autoconf/tea/win/makefile.vc b/autoconf/tea/win/makefile.vc index da56e811f..bb32f1a75 100644 --- a/autoconf/tea/win/makefile.vc +++ b/autoconf/tea/win/makefile.vc @@ -1,430 +1,61 @@ -# makefile.vc -- -*- Makefile -*- +#------------------------------------------------------------- -*- makefile -*- # -# Microsoft Visual C++ makefile for use with nmake.exe v1.62+ (VC++ 5.0+) +# Sample makefile for building Tcl extensions. # -# This makefile is based upon the Tcl 8.4 Makefile.vc and modified to -# make it suitable as a general package makefile. Look for the word EDIT -# which marks sections that may need modification. As a minumum you will -# need to change the PROJECT, DOTVERSION and DLLOBJS variables to values -# relevant to your package. +# Basic build, test and install +# nmake /s /nologo /f makefile.vc INSTALLDIR=c:\path\to\tcl +# nmake /s /nologo /f makefile.vc INSTALLDIR=c:\path\to\tcl test +# nmake /s /nologo /f makefile.vc INSTALLDIR=c:\path\to\tcl install +# +# For other build options (debug, static etc.) +# See TIP 477 (https://core.tcl.tk/tips/doc/trunk/tip/477.md) for +# detailed documentation. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. -# -# Copyright (c) 1995-1996 Sun Microsystems, Inc. -# Copyright (c) 1998-2000 Ajuba Solutions. -# Copyright (c) 2001 ActiveState Corporation. -# Copyright (c) 2001-2002 David Gravereaux. -# Copyright (c) 2003 Pat Thoyts # -#------------------------------------------------------------------------- -# RCS: @(#)$Id: makefile.vc,v 1.4 2004/07/26 08:22:05 patthoyts Exp $ -#------------------------------------------------------------------------- - -!if !defined(MSDEVDIR) && !defined(MSVCDIR) && !defined(VCINSTALLDIR) && !defined(MSSDK) && !defined(WINDOWSSDKDIR) -MSG = ^ -You will need to run vcvars32.bat from Developer Studio, first, to setup^ -the environment. Jump to this line to read the new instructions. -!error $(MSG) -!endif - #------------------------------------------------------------------------------ -# HOW TO USE this makefile: -# -# 1) It is now necessary to have %MSVCDir% set in the environment. This is -# used as a check to see if vcvars32.bat had been run prior to running -# nmake or during the installation of Microsoft Visual C++, MSVCDir had -# been set globally and the PATH adjusted. Either way is valid. -# -# You'll need to run vcvars32.bat contained in the MsDev's vc(98)/bin -# directory to setup the proper environment, if needed, for your current -# setup. This is a needed bootstrap requirement and allows the swapping of -# different environments to be easier. -# -# 2) To use the Platform SDK (not expressly needed), run setenv.bat after -# vcvars32.bat according to the instructions for it. This can also turn on -# the 64-bit compiler, if your SDK has it. -# -# 3) Targets are: -# all -- Builds everything. -# -- Builds the project (eg: nmake sample) -# test -- Builds and runs the test suite. -# install -- Installs the built binaries and libraries to $(INSTALLDIR) -# in an appropriate subdirectory. -# clean/realclean/distclean -- varying levels of cleaning. -# -# 4) Macros usable on the commandline: -# INSTALLDIR= -# Sets where to install Tcl from the built binaries. -# C:\Progra~1\Tcl is assumed when not specified. -# -# OPTS=static,msvcrt,staticpkg,threads,symbols,profile,loimpact,none -# Sets special options for the core. The default is for none. -# Any combination of the above may be used (comma separated). -# 'none' will over-ride everything to nothing. -# -# static = Builds a static library of the core instead of a -# dll. The shell will be static (and large), as well. -# msvcrt = Effects the static option only to switch it from -# using libcmt(d) as the C runtime [by default] to -# msvcrt(d). This is useful for static embedding -# support. -# staticpkg = Effects the static option only to switch -# tclshXX.exe to have the dde and reg extension linked -# inside it. -# threads = Turns on full multithreading support. -# thrdalloc = Use the thread allocator (shared global free pool). -# symbols = Adds symbols for step debugging. -# profile = Adds profiling hooks. Map file is assumed. -# loimpact = Adds a flag for how NT treats the heap to keep memory -# in use, low. This is said to impact alloc performance. -# -# STATS=memdbg,compdbg,none -# Sets optional memory and bytecode compiler debugging code added -# to the core. The default is for none. Any combination of the -# above may be used (comma separated). 'none' will over-ride -# everything to nothing. -# -# memdbg = Enables the debugging memory allocator. -# compdbg = Enables byte compilation logging. -# -# MACHINE=(IX86|IA64|ALPHA) -# Set the machine type used for the compiler, linker, and -# resource compiler. This hook is needed to tell the tools -# when alternate platforms are requested. IX86 is the default -# when not specified. -# -# TMP_DIR= -# OUT_DIR= -# Hooks to allow the intermediate and output directories to be -# changed. $(OUT_DIR) is assumed to be -# $(BINROOT)\(Release|Debug) based on if symbols are requested. -# $(TMP_DIR) will de $(OUT_DIR)\ by default. -# -# TESTPAT= -# Reads the tests requested to be run from this file. -# -# CFG_ENCODING=encoding -# name of encoding for configuration information. Defaults -# to cp1252 -# -# 5) Examples: -# -# Basic syntax of calling nmake looks like this: -# nmake [-nologo] -f makefile.vc [target|macrodef [target|macrodef] [...]] -# -# Standard (no frills) -# c:\tcl_src\win\>c:\progra~1\micros~1\vc98\bin\vcvars32.bat -# Setting environment for using Microsoft Visual C++ tools. -# c:\tcl_src\win\>nmake -f makefile.vc all -# c:\tcl_src\win\>nmake -f makefile.vc install INSTALLDIR=c:\progra~1\tcl -# -# Building for Win64 -# c:\tcl_src\win\>c:\progra~1\micros~1\vc98\bin\vcvars32.bat -# Setting environment for using Microsoft Visual C++ tools. -# c:\tcl_src\win\>c:\progra~1\platfo~1\setenv.bat /pre64 /RETAIL -# Targeting Windows pre64 RETAIL -# c:\tcl_src\win\>nmake -f makefile.vc MACHINE=IA64 -# -#------------------------------------------------------------------------------ -#============================================================================== -############################################################################### -#------------------------------------------------------------------------------ - -!if !exist("makefile.vc") -MSG = ^ -You must run this makefile only from the directory it is in.^ -Please `cd` to its location first. -!error $(MSG) -!endif - -#------------------------------------------------------------------------- -# Project specific information (EDIT) -# -# You should edit this with the name and version of your project. This -# information is used to generate the name of the package library and -# it's install location. -# -# For example, the sample extension is going to build sample04.dll and -# would install it into $(INSTALLDIR)\lib\sample04 -# -# You need to specify the object files that need to be linked into your -# binary here. -# -#------------------------------------------------------------------------- - -PROJECT = sqlite3 -!include "rules.vc" - -# nmakehelp -V will search the file for tag, skips until a -# number and returns all character until a character not in [0-9.ab] -# is read. - -!if [echo REM = This file is generated from Makefile.vc > versions.vc] -!endif -# get project version from row "AC_INIT([sqlite], [3.x.y])" -!if [echo DOTVERSION = \>> versions.vc] \ - && [nmakehlp -V ..\configure.ac AC_INIT >> versions.vc] -!endif -!include "versions.vc" - -VERSION = $(DOTVERSION:.=) -STUBPREFIX = $(PROJECT)stub - -#------------------------------------------------------------------------- -# Target names and paths ( shouldn't need changing ) -#------------------------------------------------------------------------- - -BINROOT = . -ROOT = .. - -PRJIMPLIB = $(OUT_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib -PRJLIBNAME = $(PROJECT).$(EXT) -PRJLIB = $(OUT_DIR)\$(PRJLIBNAME) - -PRJSTUBLIBNAME = $(STUBPREFIX)$(VERSION).lib -PRJSTUBLIB = $(OUT_DIR)\$(PRJSTUBLIBNAME) -### Make sure we use backslash only. -PRJ_INSTALL_DIR = $(_INSTALLDIR)\$(PROJECT)$(DOTVERSION) -LIB_INSTALL_DIR = $(PRJ_INSTALL_DIR) -BIN_INSTALL_DIR = $(PRJ_INSTALL_DIR) -DOC_INSTALL_DIR = $(PRJ_INSTALL_DIR) -SCRIPT_INSTALL_DIR = $(PRJ_INSTALL_DIR) -INCLUDE_INSTALL_DIR = $(_TCLDIR)\include +# PROJECT is sqlite, not sqlite3 to match TEA AC_INIT definition. +# This makes the generated DLL name also consistent between the two +# except for the "t" suffix which is the convention for nmake builds. +PROJECT = sqlite +PRJ_PACKAGE_TCLNAME = sqlite3 -### The following paths CANNOT have spaces in them. -GENERICDIR = $(ROOT)\generic -WINDIR = $(ROOT)\win -LIBDIR = $(ROOT)\library -DOCDIR = $(ROOT)\doc -TOOLSDIR = $(ROOT)\tools -COMPATDIR = $(ROOT)\compat +!include "rules-ext.vc" -### Figure out where the primary source code file(s) is/are. -!if exist("$(ROOT)\..\..\sqlite3.c") && exist("$(ROOT)\..\..\src\tclsqlite.c") -SQL_INCLUDES = -I"$(ROOT)\..\.." -SQLITE_SRCDIR = $(ROOT)\..\.. -TCLSQLITE_SRCDIR = $(ROOT)\..\..\src -DLLOBJS = $(TMP_DIR)\sqlite3.obj $(TMP_DIR)\tclsqlite.obj -!else -TCLSQLITE_SRCDIR = $(ROOT)\generic -DLLOBJS = $(TMP_DIR)\tclsqlite3.obj -!endif +PRJ_OBJS = $(TMP_DIR)\tclsqlite3.obj -#--------------------------------------------------------------------- -# Compile flags -#--------------------------------------------------------------------- - -!if !$(DEBUG) -!if $(OPTIMIZING) -### This cranks the optimization level to maximize speed -cdebug = -O2 -Op -Gs -!else -cdebug = -!endif -!else if "$(MACHINE)" == "IA64" -### Warnings are too many, can't support warnings into errors. -cdebug = -Z7 -Od -GZ -!else -cdebug = -Z7 -WX -Od -GZ -!endif - -### Declarations common to all compiler options -cflags = -nologo -c -W3 -D_CRT_SECURE_NO_WARNINGS -YX -Fp$(TMP_DIR)^\ - -!if $(MSVCRT) -!if $(DEBUG) -crt = -MDd -!else -crt = -MD -!endif -!else -!if $(DEBUG) -crt = -MTd -!else -crt = -MT -!endif -!endif - -INCLUDES = $(SQL_INCLUDES) $(TCL_INCLUDES) -I"$(WINDIR)" \ - -I"$(GENERICDIR)" -I"$(ROOT)\.." -BASE_CLFAGS = $(cflags) $(cdebug) $(crt) $(INCLUDES) \ +# Preprocessor macros specific to sqlite3. +PRJ_DEFINES = -I"$(ROOT)\.." -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE \ + -DSQLITE_ENABLE_DBPAGE_VTAB=1 -DSQLITE_ENABLE_DBSTAT_VTAB=1 \ + -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 -DSQLITE_ENABLE_FTS4=1 \ + -DSQLITE_ENABLE_FTS5=1 -DSQLITE_ENABLE_COLUMN_METADATA=1 \ + -DSQLITE_ENABLE_JSON1=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1 \ -DSQLITE_3_SUFFIX_ONLY=1 -DSQLITE_ENABLE_RTREE=1 \ - -DSQLITE_ENABLE_FTS3=1 -DSQLITE_OMIT_DEPRECATED=1 \ - -DSQLITE_ENABLE_FTS4=1 \ - -DSQLITE_ENABLE_FTS5=1 \ - -DSQLITE_3_SUFFIX_ONLY=1 \ - -DSQLITE_ENABLE_RTREE=1 \ - -DSQLITE_ENABLE_GEOPOLY=1 \ - -DSQLITE_ENABLE_MATH_FUNCTIONS=1 \ - -DSQLITE_ENABLE_DESERIALIZE=1 \ - -DSQLITE_ENABLE_DBPAGE_VTAB=1 \ - -DSQLITE_ENABLE_BYTECODE_VTAB=1 \ - -DSQLITE_ENABLE_DBSTAT_VTAB=1 - -CON_CFLAGS = $(cflags) $(cdebug) $(crt) -DCONSOLE -DSQLITE_ENABLE_FTS3=1 -TCL_CFLAGS = -DBUILD_sqlite -DUSE_TCL_STUBS \ - -DPACKAGE_VERSION="\"$(DOTVERSION)\"" $(BASE_CLFAGS) \ - $(OPTDEFINES) - -#--------------------------------------------------------------------- -# Link flags -#--------------------------------------------------------------------- - -!if $(DEBUG) -ldebug = -debug:full -debugtype:cv -!else -ldebug = -release -opt:ref -opt:icf,3 -!endif - -### Declarations common to all linker options -lflags = -nologo -machine:$(MACHINE) $(ldebug) - -!if $(PROFILE) -lflags = $(lflags) -profile -!endif - -!if $(ALIGN98_HACK) && !$(STATIC_BUILD) -### Align sections for PE size savings. -lflags = $(lflags) -opt:nowin98 -!else if !$(ALIGN98_HACK) && $(STATIC_BUILD) -### Align sections for speed in loading by choosing the virtual page size. -lflags = $(lflags) -align:4096 -!endif - -!if $(LOIMPACT) -lflags = $(lflags) -ws:aggressive -!endif - -dlllflags = $(lflags) -dll -conlflags = $(lflags) -subsystem:console -guilflags = $(lflags) -subsystem:windows -baselibs = $(TCLSTUBLIB) - -#--------------------------------------------------------------------- -# TclTest flags -#--------------------------------------------------------------------- - -!IF "$(TESTPAT)" != "" -TESTFLAGS = $(TESTFLAGS) -file $(TESTPAT) -!ENDIF - -#--------------------------------------------------------------------- -# Project specific targets (EDIT) -#--------------------------------------------------------------------- - -all: setup $(PROJECT) -$(PROJECT): setup $(PRJLIB) -install: install-binaries install-libraries install-docs - -# Tests need to ensure we load the right dll file we -# have to handle the output differently on Win9x. -# -!if "$(OS)" == "Windows_NT" || "$(MSVCDIR)" == "IDE" -test: setup $(PROJECT) - set TCL_LIBRARY=$(ROOT)/library - $(TCLSH) << -load $(PRJLIB:\=/) -cd "$(ROOT)/tests" -set argv "$(TESTFLAGS)" -source all.tcl -<< -!else -test: setup $(PROJECT) - echo Please wait while the test results are collected - set TCL_LIBRARY=$(ROOT)/library - $(TCLSH) << >tests.log -load $(PRJLIB:\=/) -cd "$(ROOT)/tests" -set argv "$(TESTFLAGS)" -source all.tcl -<< - type tests.log | more -!endif - -setup: - @if not exist $(OUT_DIR)\nul mkdir $(OUT_DIR) - @if not exist $(TMP_DIR)\nul mkdir $(TMP_DIR) - -$(PRJLIB): $(DLLOBJS) - $(link32) $(dlllflags) -out:$@ $(baselibs) @<< -$** -<< - -@del $*.exp - -$(PRJSTUBLIB): $(PRJSTUBOBJS) - $(lib32) -nologo -out:$@ $(PRJSTUBOBJS) - -#--------------------------------------------------------------------- -# Implicit rules -#--------------------------------------------------------------------- - -$(TMP_DIR)\sqlite3.obj: $(SQLITE_SRCDIR)\sqlite3.c - $(cc32) $(TCL_CFLAGS) -DBUILD_$(PROJECT) -Fo$(TMP_DIR)\ \ - -c $(SQLITE_SRCDIR)\sqlite3.c - -$(TMP_DIR)\tclsqlite.obj: $(TCLSQLITE_SRCDIR)\tclsqlite.c - $(cc32) $(TCL_CFLAGS) -DBUILD_$(PROJECT) -Fo$(TMP_DIR)\ \ - -c $(TCLSQLITE_SRCDIR)\tclsqlite.c - -$(TMP_DIR)\tclsqlite3.obj: $(TCLSQLITE_SRCDIR)\tclsqlite3.c - $(cc32) $(TCL_CFLAGS) -DBUILD_$(PROJECT) -Fo$(TMP_DIR)\ \ - -c $(TCLSQLITE_SRCDIR)\tclsqlite3.c - -{$(WINDIR)}.rc{$(TMP_DIR)}.res: - $(rc32) -fo $@ -r -i "$(GENERICDIR)" -D__WIN32__ \ -!if $(DEBUG) - -d DEBUG \ -!endif -!if $(TCL_THREADS) - -d TCL_THREADS \ -!endif -!if $(STATIC_BUILD) - -d STATIC_BUILD \ -!endif - $< - -.SUFFIXES: -.SUFFIXES:.c .rc - -#--------------------------------------------------------------------- -# Installation. (EDIT) -# -# You may need to modify this section to reflect the final distribution -# of your files and possibly to generate documentation. -# -#--------------------------------------------------------------------- - -install-binaries: - @echo Installing binaries to '$(SCRIPT_INSTALL_DIR)' - @if not exist "$(SCRIPT_INSTALL_DIR)" mkdir "$(SCRIPT_INSTALL_DIR)" - @$(CPY) $(PRJLIB) "$(SCRIPT_INSTALL_DIR)" >NUL - -install-libraries: - @echo Installing libraries to '$(SCRIPT_INSTALL_DIR)' - @if exist $(LIBDIR) $(CPY) $(LIBDIR)\*.tcl "$(SCRIPT_INSTALL_DIR)" - @echo Installing package index in '$(SCRIPT_INSTALL_DIR)' - @type << >"$(SCRIPT_INSTALL_DIR)\pkgIndex.tcl" -package ifneeded $(PROJECT) $(DOTVERSION) \ - [list load [file join $$dir $(PRJLIBNAME)] sqlite3] -<< - -install-docs: - @echo Installing documentation files to '$(DOC_INSTALL_DIR)' - @if exist $(DOCDIR) $(CPY) $(DOCDIR)\*.n "$(DOC_INSTALL_DIR)" - -#--------------------------------------------------------------------- -# Clean up -#--------------------------------------------------------------------- - -clean: - @if exist $(TMP_DIR)\nul $(RMDIR) $(TMP_DIR) - @if exist $(WINDIR)\version.vc del $(WINDIR)\version.vc - -realclean: clean - @if exist $(OUT_DIR)\nul $(RMDIR) $(OUT_DIR) - -distclean: realclean - @if exist $(WINDIR)\nmakehlp.exe del $(WINDIR)\nmakehlp.exe - @if exist $(WINDIR)\nmakehlp.obj del $(WINDIR)\nmakehlp.obj + -DSQLITE_UNTESTABLE=1 -DSQLITE_OMIT_LOOKASIDE=1 \ + -DSQLITE_SECURE_DELETE=1 -DSQLITE_SOUNDEX=1 -DSQLITE_ENABLE_GEOPOLY=1 \ + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 \ + -DSQLITE_ENABLE_MATH_FUNCTIONS=1 -DDSQLITE_USE_ALLOCA=1 \ + -DSQLITE_ENABLE_STAT4=1 -DSQLITE_OMIT_DEPRECATED=1 \ + -DSQLITE_WIN32_GETVERSIONEX=0 -DSQLITE_WIN32_NO_ANSI=1 +PRJ_DEFINES = $(PRJ_DEFINES) -I$(TMP_DIR) + +# Standard targets to build, install, test etc. +!include "$(_RULESDIR)\targets.vc" + +# The built-in pkgindex does no suffice for our extension as +# the PROJECT name (sqlite) is not same as init function name (Sqlite3) +pkgindex: + @echo if {[package vsatisfies [package provide Tcl] 9.0-]} { > $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PRJLIBNAME9)] [string totitle $(PRJ_PACKAGE_TCLNAME)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } else { >> $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PRJLIBNAME8)] [string totitle $(PRJ_PACKAGE_TCLNAME)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } >> $(OUT_DIR)\pkgIndex.tcl + +# Install the manpage though on Windows, doubt it does much good +install: default-install-docs-n + +# Explicit dependency rules diff --git a/autoconf/tea/win/rules-ext.vc b/autoconf/tea/win/rules-ext.vc new file mode 100644 index 000000000..479720a4b --- /dev/null +++ b/autoconf/tea/win/rules-ext.vc @@ -0,0 +1,123 @@ +# This file should only be included in makefiles for Tcl extensions, +# NOT in the makefile for Tcl itself. + +!ifndef _RULES_EXT_VC + +# We need to run from the directory the parent makefile is located in. +# nmake does not tell us what makefile was used to invoke it so parent +# makefile has to set the MAKEFILEVC macro or we just make a guess and +# warn if we think that is not the case. +!if "$(MAKEFILEVC)" == "" + +!if exist("$(PROJECT).vc") +MAKEFILEVC = $(PROJECT).vc +!elseif exist("makefile.vc") +MAKEFILEVC = makefile.vc +!endif +!endif # "$(MAKEFILEVC)" == "" + +!if !exist("$(MAKEFILEVC)") +MSG = ^ +You must run nmake from the directory containing the project makefile.^ +If you are doing that and getting this message, set the MAKEFILEVC^ +macro to the name of the project makefile. +!message WARNING: $(MSG) +!endif + +!if "$(PROJECT)" == "tcl" +!error The rules-ext.vc file is not intended for Tcl itself. +!endif + +# We extract version numbers using the nmakehlp program. For now use +# the local copy of nmakehlp. Once we locate Tcl, we will use that +# one if it is newer. +!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "$(NATIVE_ARCH)" +!if [$(CC) -nologo -DNDEBUG "nmakehlp.c" -link -subsystem:console > nul] +!endif +!else +!if [copy x86_64-w64-mingw32-nmakehlp.exe nmakehlp.exe >NUL] +!endif +!endif + +# First locate the Tcl directory that we are working with. +!if "$(TCLDIR)" != "" + +_RULESDIR = $(TCLDIR:/=\) + +!else + +# If an installation path is specified, that is also the Tcl directory. +# Also Tk never builds against an installed Tcl, it needs Tcl sources +!if defined(INSTALLDIR) && "$(PROJECT)" != "tk" +_RULESDIR=$(INSTALLDIR:/=\) +!else +# Locate Tcl sources +!if [echo _RULESDIR = \> nmakehlp.out] \ + || [nmakehlp -L generic\tcl.h >> nmakehlp.out] +_RULESDIR = ..\..\tcl +!else +!include nmakehlp.out +!endif + +!endif # defined(INSTALLDIR).... + +!endif # ifndef TCLDIR + +# Now look for the targets.vc file under the Tcl root. Note we check this +# file and not rules.vc because the latter also exists on older systems. +!if exist("$(_RULESDIR)\lib\nmake\targets.vc") # Building against installed Tcl +_RULESDIR = $(_RULESDIR)\lib\nmake +!elseif exist("$(_RULESDIR)\win\targets.vc") # Building against Tcl sources +_RULESDIR = $(_RULESDIR)\win +!else +# If we have not located Tcl's targets file, most likely we are compiling +# against an older version of Tcl and so must use our own support files. +_RULESDIR = . +!endif + +!if "$(_RULESDIR)" != "." +# Potentially using Tcl's support files. If this extension has its own +# nmake support files, need to compare the versions and pick newer. + +!if exist("rules.vc") # The extension has its own copy + +!if [echo TCL_RULES_MAJOR = \> versions.vc] \ + && [nmakehlp -V "$(_RULESDIR)\rules.vc" RULES_VERSION_MAJOR >> versions.vc] +!endif +!if [echo TCL_RULES_MINOR = \>> versions.vc] \ + && [nmakehlp -V "$(_RULESDIR)\rules.vc" RULES_VERSION_MINOR >> versions.vc] +!endif + +!if [echo OUR_RULES_MAJOR = \>> versions.vc] \ + && [nmakehlp -V "rules.vc" RULES_VERSION_MAJOR >> versions.vc] +!endif +!if [echo OUR_RULES_MINOR = \>> versions.vc] \ + && [nmakehlp -V "rules.vc" RULES_VERSION_MINOR >> versions.vc] +!endif +!include versions.vc +# We have a newer version of the support files, use them +!if ($(TCL_RULES_MAJOR) != $(OUR_RULES_MAJOR)) || ($(TCL_RULES_MINOR) < $(OUR_RULES_MINOR)) +_RULESDIR = . +!endif + +!endif # if exist("rules.vc") + +!endif # if $(_RULESDIR) != "." + +# Let rules.vc know what copy of nmakehlp.c to use. +NMAKEHLPC = $(_RULESDIR)\nmakehlp.c + +# Get rid of our internal defines before calling rules.vc +!undef TCL_RULES_MAJOR +!undef TCL_RULES_MINOR +!undef OUR_RULES_MAJOR +!undef OUR_RULES_MINOR + +!if exist("$(_RULESDIR)\rules.vc") +!message *** Using $(_RULESDIR)\rules.vc +!include "$(_RULESDIR)\rules.vc" +!else +!error *** Could not locate rules.vc in $(_RULESDIR) +!endif + +!endif # _RULES_EXT_VC \ No newline at end of file diff --git a/autoconf/tea/win/rules.vc b/autoconf/tea/win/rules.vc index 99471053c..8ecce0e10 100644 --- a/autoconf/tea/win/rules.vc +++ b/autoconf/tea/win/rules.vc @@ -1,31 +1,139 @@ -#------------------------------------------------------------------------------ +#------------------------------------------------------------- -*- makefile -*- # rules.vc -- # -# Microsoft Visual C++ makefile include for decoding the commandline -# macros. This file does not need editing to build Tcl. +# Part of the nmake based build system for Tcl and its extensions. +# This file does all the hard work in terms of parsing build options, +# compiler switches, defining common targets and macros. The Tcl makefile +# directly includes this. Extensions include it via "rules-ext.vc". +# +# See TIP 477 (https://core.tcl-lang.org/tips/doc/main/tip/477.md) for +# detailed documentation. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # Copyright (c) 2001-2003 David Gravereaux. # Copyright (c) 2003-2008 Patrick Thoyts +# Copyright (c) 2017 Ashok P. Nadkarni #------------------------------------------------------------------------------ !ifndef _RULES_VC _RULES_VC = 1 -cc32 = $(CC) # built-in default. -link32 = link -lib32 = lib -rc32 = $(RC) # built-in default. +# The following macros define the version of the rules.vc nmake build system +# For modifications that are not backward-compatible, you *must* change +# the major version. +RULES_VERSION_MAJOR = 1 +RULES_VERSION_MINOR = 12 -!ifndef INSTALLDIR -### Assume the normal default. -_INSTALLDIR = C:\Program Files\Tcl +# The PROJECT macro must be defined by parent makefile. +!if "$(PROJECT)" == "" +!error *** Error: Macro PROJECT not defined! Please define it before including rules.vc +!endif + +!if "$(PRJ_PACKAGE_TCLNAME)" == "" +PRJ_PACKAGE_TCLNAME = $(PROJECT) +!endif + +# Also special case Tcl and Tk to save some typing later +DOING_TCL = 0 +DOING_TK = 0 +!if "$(PROJECT)" == "tcl" +DOING_TCL = 1 +!elseif "$(PROJECT)" == "tk" +DOING_TK = 1 +!endif + +!ifndef NEED_TK +# Backwards compatibility +!ifdef PROJECT_REQUIRES_TK +NEED_TK = $(PROJECT_REQUIRES_TK) !else -### Fix the path separators. -_INSTALLDIR = $(INSTALLDIR:/=\) +NEED_TK = 0 +!endif +!endif + +!ifndef NEED_TCL_SOURCE +NEED_TCL_SOURCE = 0 +!endif + +!ifdef NEED_TK_SOURCE +!if $(NEED_TK_SOURCE) +NEED_TK = 1 !endif +!else +NEED_TK_SOURCE = 0 +!endif + +################################################################ +# Nmake is a pretty weak environment in syntax and capabilities +# so this file is necessarily verbose. It's broken down into +# the following parts. +# +# 0. Sanity check that compiler environment is set up and initialize +# any built-in settings from the parent makefile +# 1. First define the external tools used for compiling, copying etc. +# as this is independent of everything else. +# 2. Figure out our build structure in terms of the directory, whether +# we are building Tcl or an extension, etc. +# 3. Determine the compiler and linker versions +# 4. Build the nmakehlp helper application +# 5. Determine the supported compiler options and features +# 6. Extract Tcl, Tk, and possibly extensions, version numbers from the +# headers +# 7. Parse the OPTS macro value for user-specified build configuration +# 8. Parse the STATS macro value for statistics instrumentation +# 9. Parse the CHECKS macro for additional compilation checks +# 10. Based on this selected configuration, construct the output +# directory and file paths +# 11. Construct the paths where the package is to be installed +# 12. Set up the actual options passed to compiler and linker based +# on the information gathered above. +# 13. Define some standard build targets and implicit rules. These may +# be optionally disabled by the parent makefile. +# 14. (For extensions only.) Compare the configuration of the target +# Tcl and the extensions and warn against discrepancies. +# +# One final note about the macro names used. They are as they are +# for historical reasons. We would like legacy extensions to +# continue to work with this make include file so be wary of +# changing them for consistency or clarity. + +# 0. Sanity check compiler environment + +# Check to see we are configured to build with MSVC (MSDEVDIR, MSVCDIR or +# VCINSTALLDIR) or with the MS Platform SDK (MSSDK or WindowsSDKDir) + +!if !defined(MSDEVDIR) && !defined(MSVCDIR) && !defined(VCINSTALLDIR) && !defined(MSSDK) && !defined(WINDOWSSDKDIR) +MSG = ^ +Visual C++ compiler environment not initialized. +!error $(MSG) +!endif + +# We need to run from the directory the parent makefile is located in. +# nmake does not tell us what makefile was used to invoke it so parent +# makefile has to set the MAKEFILEVC macro or we just make a guess and +# warn if we think that is not the case. +!if "$(MAKEFILEVC)" == "" + +!if exist("$(PROJECT).vc") +MAKEFILEVC = $(PROJECT).vc +!elseif exist("makefile.vc") +MAKEFILEVC = makefile.vc +!endif +!endif # "$(MAKEFILEVC)" == "" + +!if !exist("$(MAKEFILEVC)") +MSG = ^ +You must run nmake from the directory containing the project makefile.^ +If you are doing that and getting this message, set the MAKEFILEVC^ +macro to the name of the project makefile. +!message WARNING: $(MSG) +!endif + + +################################################################ +# 1. Define external programs being used #---------------------------------------------------------- # Set the proper copy method to avoid overwrite questions @@ -33,28 +141,291 @@ _INSTALLDIR = $(INSTALLDIR:/=\) # "delete all" method. #---------------------------------------------------------- -!if "$(OS)" == "Windows_NT" RMDIR = rmdir /S /Q -ERRNULL = 2>NUL -!if ![ver | find "4.0" > nul] -CPY = echo y | xcopy /i >NUL -COPY = copy >NUL -!else CPY = xcopy /i /y >NUL +CPYDIR = xcopy /e /i /y >NUL COPY = copy /y >NUL +MKDIR = mkdir + +###################################################################### +# 2. Figure out our build environment in terms of what we're building. +# +# (a) Tcl itself +# (b) Tk +# (c) a Tcl extension using libraries/includes from an *installed* Tcl +# (d) a Tcl extension using libraries/includes from Tcl source directory +# +# This last is needed because some extensions still need +# some Tcl interfaces that are not publicly exposed. +# +# The fragment will set the following macros: +# ROOT - root of this module sources +# COMPATDIR - source directory that holds compatibility sources +# DOCDIR - source directory containing documentation files +# GENERICDIR - platform-independent source directory +# WIN_DIR - Windows-specific source directory +# TESTDIR - directory containing test files +# TOOLSDIR - directory containing build tools +# _TCLDIR - root of the Tcl installation OR the Tcl sources. Not set +# when building Tcl itself. +# _INSTALLDIR - native form of the installation path. For Tcl +# this will be the root of the Tcl installation. For extensions +# this will be the lib directory under the root. +# TCLINSTALL - set to 1 if _TCLDIR refers to +# headers and libraries from an installed Tcl, and 0 if built against +# Tcl sources. Not set when building Tcl itself. Yes, not very well +# named. +# _TCL_H - native path to the tcl.h file +# +# If Tk is involved, also sets the following +# _TKDIR - native form Tk installation OR Tk source. Not set if building +# Tk itself. +# TKINSTALL - set 1 if _TKDIR refers to installed Tk and 0 if Tk sources +# _TK_H - native path to the tk.h file + +# Root directory for sources and assumed subdirectories +ROOT = $(MAKEDIR)\.. +# The following paths CANNOT have spaces in them as they appear on the +# left side of implicit rules. +!ifndef COMPATDIR +COMPATDIR = $(ROOT)\compat +!endif +!ifndef DOCDIR +DOCDIR = $(ROOT)\doc +!endif +!ifndef GENERICDIR +GENERICDIR = $(ROOT)\generic +!endif +!ifndef TOOLSDIR +TOOLSDIR = $(ROOT)\tools +!endif +!ifndef TESTDIR +TESTDIR = $(ROOT)\tests +!endif +!ifndef LIBDIR +!if exist("$(ROOT)\library") +LIBDIR = $(ROOT)\library +!else +LIBDIR = $(ROOT)\lib !endif -!else # "$(OS)" != "Windows_NT" -CPY = xcopy /i >_JUNK.OUT # On Win98 NUL does not work here. -COPY = copy >_JUNK.OUT # On Win98 NUL does not work here. -RMDIR = deltree /Y -NULL = \NUL # Used in testing directory existence -ERRNULL = >NUL # Win9x shell cannot redirect stderr !endif -MKDIR = mkdir +!ifndef DEMODIR +!if exist("$(LIBDIR)\demos") +DEMODIR = $(LIBDIR)\demos +!else +DEMODIR = $(ROOT)\demos +!endif +!endif # ifndef DEMODIR +# Do NOT use WINDIR because it is Windows internal environment +# variable to point to c:\windows! +WIN_DIR = $(ROOT)\win -#------------------------------------------------------------------------------ -# Determine the host and target architectures and compiler version. -#------------------------------------------------------------------------------ +!ifndef RCDIR +!if exist("$(WIN_DIR)\rc") +RCDIR = $(WIN_DIR)\rc +!else +RCDIR = $(WIN_DIR) +!endif +!endif +RCDIR = $(RCDIR:/=\) + +# The target directory where the built packages and binaries will be installed. +# INSTALLDIR is the (optional) path specified by the user. +# _INSTALLDIR is INSTALLDIR using the backslash separator syntax +!ifdef INSTALLDIR +### Fix the path separators. +_INSTALLDIR = $(INSTALLDIR:/=\) +!else +### Assume the normal default. +_INSTALLDIR = $(HOMEDRIVE)\Tcl +!endif + +!if $(DOING_TCL) + +# BEGIN Case 2(a) - Building Tcl itself + +# Only need to define _TCL_H +_TCL_H = ..\generic\tcl.h + +# END Case 2(a) - Building Tcl itself + +!elseif $(DOING_TK) + +# BEGIN Case 2(b) - Building Tk + +TCLINSTALL = 0 # Tk always builds against Tcl source, not an installed Tcl +!if "$(TCLDIR)" == "" +!if [echo TCLDIR = \> nmakehlp.out] \ + || [nmakehlp -L generic\tcl.h >> nmakehlp.out] +!error *** Could not locate Tcl source directory. +!endif +!include nmakehlp.out +!endif # TCLDIR == "" + +_TCLDIR = $(TCLDIR:/=\) +_TCL_H = $(_TCLDIR)\generic\tcl.h +!if !exist("$(_TCL_H)") +!error Could not locate tcl.h. Please set the TCLDIR macro to point to the Tcl *source* directory. +!endif + +_TK_H = ..\generic\tk.h + +# END Case 2(b) - Building Tk + +!else + +# BEGIN Case 2(c) or (d) - Building an extension other than Tk + +# If command line has specified Tcl location through TCLDIR, use it +# else default to the INSTALLDIR setting +!if "$(TCLDIR)" != "" + +_TCLDIR = $(TCLDIR:/=\) +!if exist("$(_TCLDIR)\include\tcl.h") # Case 2(c) with TCLDIR defined +TCLINSTALL = 1 +_TCL_H = $(_TCLDIR)\include\tcl.h +!elseif exist("$(_TCLDIR)\generic\tcl.h") # Case 2(d) with TCLDIR defined +TCLINSTALL = 0 +_TCL_H = $(_TCLDIR)\generic\tcl.h +!endif + +!else # # Case 2(c) for extensions with TCLDIR undefined + +# Need to locate Tcl depending on whether it needs Tcl source or not. +# If we don't, check the INSTALLDIR for an installed Tcl first + +!if exist("$(_INSTALLDIR)\include\tcl.h") && !$(NEED_TCL_SOURCE) + +TCLINSTALL = 1 +TCLDIR = $(_INSTALLDIR)\.. +# NOTE: we will be resetting _INSTALLDIR to _INSTALLDIR/lib for extensions +# later so the \.. accounts for the /lib +_TCLDIR = $(_INSTALLDIR)\.. +_TCL_H = $(_TCLDIR)\include\tcl.h + +!else # exist(...) && !$(NEED_TCL_SOURCE) + +!if [echo _TCLDIR = \> nmakehlp.out] \ + || [nmakehlp -L generic\tcl.h >> nmakehlp.out] +!error *** Could not locate Tcl source directory. +!endif +!include nmakehlp.out +TCLINSTALL = 0 +TCLDIR = $(_TCLDIR) +_TCL_H = $(_TCLDIR)\generic\tcl.h + +!endif # exist(...) && !$(NEED_TCL_SOURCE) + +!endif # TCLDIR + +!ifndef _TCL_H +MSG =^ +Failed to find tcl.h. The TCLDIR macro is set incorrectly or is not set and default path does not contain tcl.h. +!error $(MSG) +!endif + +# Now do the same to locate Tk headers and libs if project requires Tk +!if $(NEED_TK) + +!if "$(TKDIR)" != "" + +_TKDIR = $(TKDIR:/=\) +!if exist("$(_TKDIR)\include\tk.h") +TKINSTALL = 1 +_TK_H = $(_TKDIR)\include\tk.h +!elseif exist("$(_TKDIR)\generic\tk.h") +TKINSTALL = 0 +_TK_H = $(_TKDIR)\generic\tk.h +!endif + +!else # TKDIR not defined + +# Need to locate Tcl depending on whether it needs Tcl source or not. +# If we don't, check the INSTALLDIR for an installed Tcl first + +!if exist("$(_INSTALLDIR)\include\tk.h") && !$(NEED_TK_SOURCE) + +TKINSTALL = 1 +# NOTE: we will be resetting _INSTALLDIR to _INSTALLDIR/lib for extensions +# later so the \.. accounts for the /lib +_TKDIR = $(_INSTALLDIR)\.. +_TK_H = $(_TKDIR)\include\tk.h +TKDIR = $(_TKDIR) + +!else # exist("$(_INSTALLDIR)\include\tk.h") && !$(NEED_TK_SOURCE) + +!if [echo _TKDIR = \> nmakehlp.out] \ + || [nmakehlp -L generic\tk.h >> nmakehlp.out] +!error *** Could not locate Tk source directory. +!endif +!include nmakehlp.out +TKINSTALL = 0 +TKDIR = $(_TKDIR) +_TK_H = $(_TKDIR)\generic\tk.h + +!endif # exist("$(_INSTALLDIR)\include\tk.h") && !$(NEED_TK_SOURCE) + +!endif # TKDIR + +!ifndef _TK_H +MSG =^ +Failed to find tk.h. The TKDIR macro is set incorrectly or is not set and default path does not contain tk.h. +!error $(MSG) +!endif + +!endif # NEED_TK + +!if $(NEED_TCL_SOURCE) && $(TCLINSTALL) +MSG = ^ +*** Warning: This extension requires the source distribution of Tcl.^ +*** Please set the TCLDIR macro to point to the Tcl sources. +!error $(MSG) +!endif + +!if $(NEED_TK_SOURCE) +!if $(TKINSTALL) +MSG = ^ +*** Warning: This extension requires the source distribution of Tk.^ +*** Please set the TKDIR macro to point to the Tk sources. +!error $(MSG) +!endif +!endif + + +# If INSTALLDIR set to Tcl installation root dir then reset to the +# lib dir for installing extensions +!if exist("$(_INSTALLDIR)\include\tcl.h") +_INSTALLDIR=$(_INSTALLDIR)\lib +!endif + +# END Case 2(c) or (d) - Building an extension +!endif # if $(DOING_TCL) + +################################################################ +# 3. Determine compiler version and architecture +# In this section, we figure out the compiler version and the +# architecture for which we are building. This sets the +# following macros: +# VCVERSION - the internal compiler version as 1200, 1400, 1910 etc. +# This is also printed by the compiler in dotted form 19.10 etc. +# VCVER - the "marketing version", for example Visual C++ 6 for internal +# compiler version 1200. This is kept only for legacy reasons as it +# does not make sense for recent Microsoft compilers. Only used for +# output directory names. +# ARCH - set to IX86, ARM64 or AMD64 depending on 32- or 64-bit target +# NATIVE_ARCH - set to IX86, ARM64 or AMD64 for the host machine +# MACHINE - same as $(ARCH) - legacy +# _VC_MANIFEST_EMBED_{DLL,EXE} - commands for embedding a manifest if needed + +cc32 = $(CC) # built-in default. +link32 = link +lib32 = lib +rc32 = $(RC) # built-in default. + +#---------------------------------------------------------------- +# Figure out the compiler architecture and version by writing +# the C macros to a file, preprocessing them with the C +# preprocessor and reading back the created file _HASH=^# _VC_MANIFEST_EMBED_EXE= @@ -65,19 +436,70 @@ VCVER=0 && ![echo ARCH=IX86 >> vercl.x] \ && ![echo $(_HASH)elif defined(_M_AMD64) >> vercl.x] \ && ![echo ARCH=AMD64 >> vercl.x] \ + && ![echo $(_HASH)elif defined(_M_ARM64) >> vercl.x] \ + && ![echo ARCH=ARM64 >> vercl.x] \ && ![echo $(_HASH)endif >> vercl.x] \ - && ![cl -nologo -TC -P vercl.x $(ERRNULL)] + && ![$(cc32) -nologo -TC -P vercl.x 2>NUL] !include vercl.i +!if $(VCVERSION) < 1900 !if ![echo VCVER= ^\> vercl.vc] \ && ![set /a $(VCVERSION) / 100 - 6 >> vercl.vc] !include vercl.vc !endif +!else +# The simple calculation above does not apply to new Visual Studio releases +# Keep the compiler version in its native form. +VCVER = $(VCVERSION) +!endif !endif -!if ![del $(ERRNUL) /q/f vercl.x vercl.i vercl.vc] + +!if ![del 2>NUL /q/f vercl.x vercl.i vercl.vc] !endif +#---------------------------------------------------------------- +# The MACHINE macro is used by legacy makefiles so set it as well +!ifdef MACHINE +!if "$(MACHINE)" == "x86" +!undef MACHINE +MACHINE = IX86 +!elseif "$(MACHINE)" == "arm64" +!undef MACHINE +MACHINE = ARM64 +!elseif "$(MACHINE)" == "x64" +!undef MACHINE +MACHINE = AMD64 +!endif +!if "$(MACHINE)" != "$(ARCH)" +!error Specified MACHINE macro $(MACHINE) does not match detected target architecture $(ARCH). +!endif +!else +MACHINE=$(ARCH) +!endif + +#--------------------------------------------------------------- +# The PLATFORM_IDENTIFY macro matches the values returned by +# the Tcl platform::identify command +!if "$(MACHINE)" == "AMD64" +PLATFORM_IDENTIFY = win32-x86_64 +!elseif "$(MACHINE)" == "ARM64" +PLATFORM_IDENTIFY = win32-arm +!else +PLATFORM_IDENTIFY = win32-ix86 +!endif + +# The MULTIPLATFORM macro controls whether binary extensions are installed +# in platform-specific directories. Intended to be set/used by extensions. +!ifndef MULTIPLATFORM_INSTALL +MULTIPLATFORM_INSTALL = 0 +!endif + +#------------------------------------------------------------ +# Figure out the *host* architecture by reading the registry + !if ![reg query HKLM\Hardware\Description\System\CentralProcessor\0 /v Identifier | findstr /i x86] NATIVE_ARCH=IX86 +!elseif ![reg query HKLM\Hardware\Description\System\CentralProcessor\0 /v Identifier | findstr /i ARM | findstr /i 64-bit] +NATIVE_ARCH=ARM64 !else NATIVE_ARCH=AMD64 !endif @@ -88,185 +510,408 @@ _VC_MANIFEST_EMBED_EXE=if exist $@.manifest mt -nologo -manifest $@.manifest -ou _VC_MANIFEST_EMBED_DLL=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2 !endif -!ifndef MACHINE -MACHINE=$(ARCH) -!endif - -!ifndef CFG_ENCODING -CFG_ENCODING = \"cp1252\" -!endif - -!message =============================================================================== +################################################################ +# 4. Build the nmakehlp program +# This is a helper app we need to overcome nmake's limiting +# environment. We will call out to it to get various bits of +# information about supported compiler options etc. +# +# Tcl itself will always use the nmakehlp.c program which is +# in its own source. It will be kept updated there. +# +# Extensions built against an installed Tcl will use the installed +# copy of Tcl's nmakehlp.c if there is one and their own version +# otherwise. In the latter case, they would also be using their own +# rules.vc. Note that older versions of Tcl do not install nmakehlp.c +# or rules.vc. +# +# Extensions built against Tcl sources will use the one from the Tcl source. +# +# When building an extension using a sufficiently new version of Tcl, +# rules-ext.vc will define NMAKEHLPC appropriately to point to the +# copy of nmakehlp.c to be used. -#---------------------------------------------------------- -# build the helper app we need to overcome nmake's limiting -# environment. -#---------------------------------------------------------- +!ifndef NMAKEHLPC +# Default to the one in the current directory (the extension's own nmakehlp.c) +NMAKEHLPC = nmakehlp.c -!if !exist(nmakehlp.exe) -!if [$(cc32) -nologo nmakehlp.c -link -subsystem:console > nul] +!if !$(DOING_TCL) +!if $(TCLINSTALL) +!if exist("$(_TCLDIR)\lib\nmake\nmakehlp.c") +NMAKEHLPC = $(_TCLDIR)\lib\nmake\nmakehlp.c !endif +!else # !$(TCLINSTALL) +!if exist("$(_TCLDIR)\win\nmakehlp.c") +NMAKEHLPC = $(_TCLDIR)\win\nmakehlp.c !endif +!endif # $(TCLINSTALL) +!endif # !$(DOING_TCL) -#---------------------------------------------------------- -# Test for compiler features -#---------------------------------------------------------- +!endif # NMAKEHLPC -### test for optimizations -!if [nmakehlp -c -Ot] -!message *** Compiler has 'Optimizations' -OPTIMIZING = 1 +# We always build nmakehlp even if it exists since we do not know +# what source it was built from. +!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "$(NATIVE_ARCH)" +!if [$(cc32) -nologo "$(NMAKEHLPC)" -link -subsystem:console > nul] +!endif !else -!message *** Compiler does not have 'Optimizations' -OPTIMIZING = 0 +!if [copy $(NMAKEHLPC:nmakehlp.c=x86_64-w64-mingw32-nmakehlp.exe) nmakehlp.exe >NUL] !endif - -OPTIMIZATIONS = - -!if [nmakehlp -c -Ot] -OPTIMIZATIONS = $(OPTIMIZATIONS) -Ot !endif -!if [nmakehlp -c -Oi] -OPTIMIZATIONS = $(OPTIMIZATIONS) -Oi -!endif +################################################################ +# 5. Test for compiler features +# Visual C++ compiler options have changed over the years. Check +# which options are supported by the compiler in use. +# +# The following macros are set: +# OPTIMIZATIONS - the compiler flags to be used for optimized builds +# DEBUGFLAGS - the compiler flags to be used for debug builds +# LINKERFLAGS - Flags passed to the linker +# +# Note that these are the compiler settings *available*, not those +# that will be *used*. The latter depends on the OPTS macro settings +# which we have not yet parsed. +# +# Also note that some of the flags in OPTIMIZATIONS are not really +# related to optimization. They are placed there only for legacy reasons +# as some extensions expect them to be included in that macro. +# -Op improves float consistency. Note only needed for older compilers +# Newer compilers do not need or support this option. !if [nmakehlp -c -Op] -OPTIMIZATIONS = $(OPTIMIZATIONS) -Op +FPOPTS = -Op !endif +# Strict floating point semantics - present in newer compilers in lieu of -Op !if [nmakehlp -c -fp:strict] -OPTIMIZATIONS = $(OPTIMIZATIONS) -fp:strict +FPOPTS = $(FPOPTS) -fp:strict +!endif + +!if "$(MACHINE)" == "IX86" +### test for pentium errata +!if [nmakehlp -c -QI0f] +!message *** Compiler has 'Pentium 0x0f fix' +FPOPTS = $(FPOPTS) -QI0f +!else +!message *** Compiler does not have 'Pentium 0x0f fix' +!endif !endif -!if [nmakehlp -c -Gs] -OPTIMIZATIONS = $(OPTIMIZATIONS) -Gs +### test for optimizations +# /O2 optimization includes /Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy as per +# documentation. Note we do NOT want /Gs as that inserts a _chkstk +# stack probe at *every* function entry, not just those with more than +# a page of stack allocation resulting in a performance hit. However, +# /O2 documentation is misleading as its stack probes are simply the +# default page size locals allocation probes and not what is implied +# by an explicit /Gs option. + +OPTIMIZATIONS = $(FPOPTS) + +!if [nmakehlp -c -O2] +OPTIMIZING = 1 +OPTIMIZATIONS = $(OPTIMIZATIONS) -O2 +!else +# Legacy, really. All modern compilers support this +!message *** Compiler does not have 'Optimizations' +OPTIMIZING = 0 !endif +# Checks for buffer overflows in local arrays !if [nmakehlp -c -GS] OPTIMIZATIONS = $(OPTIMIZATIONS) -GS !endif +# Link time optimization. Note that this option (potentially) makes +# generated libraries only usable by the specific VC++ version that +# created it. Requires /LTCG linker option !if [nmakehlp -c -GL] OPTIMIZATIONS = $(OPTIMIZATIONS) -GL +CC_GL_OPT_ENABLED = 1 +!else +# In newer compilers -GL and -YX are incompatible. +!if [nmakehlp -c -YX] +OPTIMIZATIONS = $(OPTIMIZATIONS) -YX !endif +!endif # [nmakehlp -c -GL] -DEBUGFLAGS = +DEBUGFLAGS = $(FPOPTS) +# Run time error checks. Not available or valid in a release, non-debug build +# RTC is for modern compilers, -GZ is legacy !if [nmakehlp -c -RTC1] DEBUGFLAGS = $(DEBUGFLAGS) -RTC1 !elseif [nmakehlp -c -GZ] DEBUGFLAGS = $(DEBUGFLAGS) -GZ !endif -COMPILERFLAGS =-W3 -DUNICODE -D_UNICODE +#---------------------------------------------------------------- +# Linker flags -# In v13 -GL and -YX are incompatible. -!if [nmakehlp -c -YX] -!if ![nmakehlp -c -GL] -OPTIMIZATIONS = $(OPTIMIZATIONS) -YX -!endif +# LINKER_TESTFLAGS are for internal use when we call nmakehlp to test +# if the linker supports a specific option. Without these flags link will +# return "LNK1561: entry point must be defined" error compiling from VS-IDE: +# They are not passed through to the actual application / extension +# link rules. +!ifndef LINKER_TESTFLAGS +LINKER_TESTFLAGS = /DLL /NOENTRY /OUT:nmakehlp.out !endif -!if "$(MACHINE)" == "IX86" -### test for pentium errata -!if [nmakehlp -c -QI0f] -!message *** Compiler has 'Pentium 0x0f fix' -COMPILERFLAGS = $(COMPILERFLAGS) -QI0f -!else -!message *** Compiler does not have 'Pentium 0x0f fix' +LINKERFLAGS = + +# If compiler has enabled link time optimization, linker must too with -ltcg +!ifdef CC_GL_OPT_ENABLED +!if [nmakehlp -l -ltcg $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS) -ltcg +!endif +!endif + + +################################################################ +# 6. Extract various version numbers from headers +# For Tcl and Tk, version numbers are extracted from tcl.h and tk.h +# respectively. For extensions, versions are extracted from the +# configure.in or configure.ac from the TEA configuration if it +# exists, and unset otherwise. +# Sets the following macros: +# TCL_MAJOR_VERSION +# TCL_MINOR_VERSION +# TCL_RELEASE_SERIAL +# TCL_PATCH_LEVEL +# TCL_PATCH_LETTER +# TCL_VERSION +# TK_MAJOR_VERSION +# TK_MINOR_VERSION +# TK_RELEASE_SERIAL +# TK_PATCH_LEVEL +# TK_PATCH_LETTER +# TK_VERSION +# DOTVERSION - set as (for example) 2.5 +# VERSION - set as (for example 25) +#-------------------------------------------------------------- + +!if [echo REM = This file is generated from rules.vc > versions.vc] !endif +!if [echo TCL_MAJOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" "define TCL_MAJOR_VERSION" >> versions.vc] +!endif +!if [echo TCL_MINOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_MINOR_VERSION >> versions.vc] +!endif +!if [echo TCL_RELEASE_SERIAL = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_RELEASE_SERIAL >> versions.vc] +!endif +!if [echo TCL_PATCH_LEVEL = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_PATCH_LEVEL >> versions.vc] !endif -!if "$(MACHINE)" == "IA64" -### test for Itanium errata -!if [nmakehlp -c -QIA64_Bx] -!message *** Compiler has 'B-stepping errata workarounds' -COMPILERFLAGS = $(COMPILERFLAGS) -QIA64_Bx -!else -!message *** Compiler does not have 'B-stepping errata workarounds' +!if defined(_TK_H) +!if [echo TK_MAJOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) "define TK_MAJOR_VERSION" >> versions.vc] +!endif +!if [echo TK_MINOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) TK_MINOR_VERSION >> versions.vc] !endif +!if [echo TK_RELEASE_SERIAL = \>> versions.vc] \ + && [nmakehlp -V "$(_TK_H)" TK_RELEASE_SERIAL >> versions.vc] !endif +!if [echo TK_PATCH_LEVEL = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) TK_PATCH_LEVEL >> versions.vc] +!endif +!endif # _TK_H -!if "$(MACHINE)" == "IX86" -### test for -align:4096, when align:512 will do. -!if [nmakehlp -l -opt:nowin98] -!message *** Linker has 'Win98 alignment problem' -ALIGN98_HACK = 1 +!include versions.vc + +TCL_VERSION = $(TCL_MAJOR_VERSION)$(TCL_MINOR_VERSION) +TCL_DOTVERSION = $(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION) +!if [nmakehlp -f $(TCL_PATCH_LEVEL) "a"] +TCL_PATCH_LETTER = a +!elseif [nmakehlp -f $(TCL_PATCH_LEVEL) "b"] +TCL_PATCH_LETTER = b !else -!message *** Linker does not have 'Win98 alignment problem' -ALIGN98_HACK = 0 +TCL_PATCH_LETTER = . !endif + +!if defined(_TK_H) + +TK_VERSION = $(TK_MAJOR_VERSION)$(TK_MINOR_VERSION) +TK_DOTVERSION = $(TK_MAJOR_VERSION).$(TK_MINOR_VERSION) +!if [nmakehlp -f $(TK_PATCH_LEVEL) "a"] +TK_PATCH_LETTER = a +!elseif [nmakehlp -f $(TK_PATCH_LEVEL) "b"] +TK_PATCH_LETTER = b !else -ALIGN98_HACK = 0 +TK_PATCH_LETTER = . !endif -LINKERFLAGS = - -!if [nmakehlp -l -ltcg] -LINKERFLAGS =-ltcg !endif -#---------------------------------------------------------- -# Decode the options requested. -#---------------------------------------------------------- +# Set DOTVERSION and VERSION +!if $(DOING_TCL) + +DOTVERSION = $(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION) +VERSION = $(TCL_VERSION) + +!elseif $(DOING_TK) -!if "$(OPTS)" == "" || [nmakehlp -f "$(OPTS)" "none"] +DOTVERSION = $(TK_DOTVERSION) +VERSION = $(TK_VERSION) + +!else # Doing a non-Tk extension + +# If parent makefile has not defined DOTVERSION, try to get it from TEA +# first from a configure.in file, and then from configure.ac +!ifndef DOTVERSION +!if [echo DOTVERSION = \> versions.vc] \ + || [nmakehlp -V $(ROOT)\configure.in ^[$(PROJECT)^] >> versions.vc] +!if [echo DOTVERSION = \> versions.vc] \ + || [nmakehlp -V $(ROOT)\configure.ac ^[$(PROJECT)^] >> versions.vc] +!error *** Could not figure out extension version. Please define DOTVERSION in parent makefile before including rules.vc. +!endif +!endif +!include versions.vc +!endif # DOTVERSION +VERSION = $(DOTVERSION:.=) + +!endif # $(DOING_TCL) ... etc. + +# Windows RC files have 3 version components. Ensure this irrespective +# of how many components the package has specified. Basically, ensure +# minimum 4 components by appending 4 0's and then pick out the first 4. +# Also take care of the fact that DOTVERSION may have "a" or "b" instead +# of "." separating the version components. +DOTSEPARATED=$(DOTVERSION:a=.) +DOTSEPARATED=$(DOTSEPARATED:b=.) +!if [echo RCCOMMAVERSION = \> versions.vc] \ + || [for /f "tokens=1,2,3,4,5* delims=." %a in ("$(DOTSEPARATED).0.0.0.0") do echo %a,%b,%c,%d >> versions.vc] +!error *** Could not generate RCCOMMAVERSION *** +!endif +!include versions.vc + +######################################################################## +# 7. Parse the OPTS macro to work out the requested build configuration. +# Based on this, we will construct the actual switches to be passed to the +# compiler and linker using the macros defined in the previous section. +# The following macros are defined by this section based on OPTS +# STATIC_BUILD - 0 -> Tcl is to be built as a shared library +# 1 -> build as a static library and shell +# TCL_THREADS - legacy but always 1 on Windows since winsock requires it. +# DEBUG - 1 -> debug build, 0 -> release builds +# SYMBOLS - 1 -> generate PDB's, 0 -> no PDB's +# PROFILE - 1 -> generate profiling info, 0 -> no profiling +# PGO - 1 -> profile based optimization, 0 -> no +# MSVCRT - 1 -> link to dynamic C runtime even when building static Tcl build +# 0 -> link to static C runtime for static Tcl build. +# Does not impact shared Tcl builds (STATIC_BUILD == 0) +# Default: 1 for Tcl 8.7 and up, 0 otherwise. +# TCL_USE_STATIC_PACKAGES - 1 -> statically link the registry and dde extensions +# in the Tcl and Wish shell. 0 -> keep them as shared libraries. Does +# not impact shared Tcl builds. Implied by STATIC_BUILD since Tcl 8.7. +# USE_THREAD_ALLOC - 1 -> Use a shared global free pool for allocation. +# 0 -> Use the non-thread allocator. +# UNCHECKED - 1 -> when doing a debug build with symbols, use the release +# C runtime, 0 -> use the debug C runtime. +# USE_STUBS - 1 -> compile to use stubs interfaces, 0 -> direct linking +# CONFIG_CHECK - 1 -> check current build configuration against Tcl +# configuration (ignored for Tcl itself) +# _USE_64BIT_TIME_T - forces a build using 64-bit time_t for 32-bit build +# (CRT library should support this, not needed for Tcl 9.x) +# Further, LINKERFLAGS are modified based on above. + +# Default values for all the above STATIC_BUILD = 0 TCL_THREADS = 1 DEBUG = 0 SYMBOLS = 0 PROFILE = 0 PGO = 0 -MSVCRT = 0 -LOIMPACT = 0 +MSVCRT = 1 TCL_USE_STATIC_PACKAGES = 0 USE_THREAD_ALLOC = 1 UNCHECKED = 0 +CONFIG_CHECK = 1 +!if $(DOING_TCL) +USE_STUBS = 0 !else +USE_STUBS = 1 +!endif + +# If OPTS is not empty AND does not contain "none" which turns off all OPTS +# set the above macros based on OPTS content +!if "$(OPTS)" != "" && ![nmakehlp -f "$(OPTS)" "none"] + +# OPTS are specified, parse them + !if [nmakehlp -f $(OPTS) "static"] !message *** Doing static STATIC_BUILD = 1 -!else -STATIC_BUILD = 0 !endif + +!if [nmakehlp -f $(OPTS) "nostubs"] +!message *** Not using stubs +USE_STUBS = 0 +!endif + +!if [nmakehlp -f $(OPTS) "nomsvcrt"] +!message *** Doing nomsvcrt +MSVCRT = 0 +!else !if [nmakehlp -f $(OPTS) "msvcrt"] !message *** Doing msvcrt -MSVCRT = 1 !else +!if $(TCL_MAJOR_VERSION) == 8 && $(TCL_MINOR_VERSION) < 7 && $(STATIC_BUILD) MSVCRT = 0 !endif -!if [nmakehlp -f $(OPTS) "staticpkg"] +!endif +!endif # [nmakehlp -f $(OPTS) "nomsvcrt"] + +!if [nmakehlp -f $(OPTS) "staticpkg"] && $(STATIC_BUILD) !message *** Doing staticpkg TCL_USE_STATIC_PACKAGES = 1 -!else -TCL_USE_STATIC_PACKAGES = 0 !endif + !if [nmakehlp -f $(OPTS) "nothreads"] !message *** Compile explicitly for non-threaded tcl -TCL_THREADS = 0 -!else -TCL_THREADS = 1 -USE_THREAD_ALLOC= 1 +TCL_THREADS = 0 +USE_THREAD_ALLOC= 0 +!endif + +!if [nmakehlp -f $(OPTS) "tcl8"] +!message *** Build for Tcl8 +TCL_BUILD_FOR = 8 +!endif + +!if $(TCL_MAJOR_VERSION) == 8 +!if [nmakehlp -f $(OPTS) "time64bit"] +!message *** Force 64-bit time_t +_USE_64BIT_TIME_T = 1 +!endif !endif + +# Yes, it's weird that the "symbols" option controls DEBUG and +# the "pdbs" option controls SYMBOLS. That's historical. !if [nmakehlp -f $(OPTS) "symbols"] !message *** Doing symbols DEBUG = 1 !else DEBUG = 0 !endif + !if [nmakehlp -f $(OPTS) "pdbs"] !message *** Doing pdbs SYMBOLS = 1 !else SYMBOLS = 0 !endif + !if [nmakehlp -f $(OPTS) "profile"] !message *** Doing profile PROFILE = 1 !else PROFILE = 0 !endif + !if [nmakehlp -f $(OPTS) "pgi"] !message *** Doing profile guided optimization instrumentation PGO = 1 @@ -276,53 +921,149 @@ PGO = 2 !else PGO = 0 !endif + !if [nmakehlp -f $(OPTS) "loimpact"] -!message *** Doing loimpact -LOIMPACT = 1 -!else -LOIMPACT = 0 +!message *** Warning: ignoring option "loimpact" - deprecated on modern Windows. !endif + +# TBD - should get rid of this option !if [nmakehlp -f $(OPTS) "thrdalloc"] !message *** Doing thrdalloc USE_THREAD_ALLOC = 1 !endif + !if [nmakehlp -f $(OPTS) "tclalloc"] -!message *** Doing tclalloc USE_THREAD_ALLOC = 0 !endif -!if [nmakehlp -f $(OPTS) "unchecked"] -!message *** Doing unchecked -UNCHECKED = 1 -!else -UNCHECKED = 0 + +!if [nmakehlp -f $(OPTS) "unchecked"] +!message *** Doing unchecked +UNCHECKED = 1 +!else +UNCHECKED = 0 +!endif + +!if [nmakehlp -f $(OPTS) "noconfigcheck"] +CONFIG_CHECK = 1 +!else +CONFIG_CHECK = 0 +!endif + +!endif # "$(OPTS)" != "" && ... parsing of OPTS + +# Set linker flags based on above + +!if $(PGO) > 1 +!if [nmakehlp -l -ltcg:pgoptimize $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pgoptimize +!else +MSG=^ +This compiler does not support profile guided optimization. +!error $(MSG) +!endif +!elseif $(PGO) > 0 +!if [nmakehlp -l -ltcg:pginstrument $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pginstrument +!else +MSG=^ +This compiler does not support profile guided optimization. +!error $(MSG) +!endif +!endif + +################################################################ +# 8. Parse the STATS macro to configure code instrumentation +# The following macros are set by this section: +# TCL_MEM_DEBUG - 1 -> enables memory allocation instrumentation +# 0 -> disables +# TCL_COMPILE_DEBUG - 1 -> enables byte compiler logging +# 0 -> disables + +# Default both are off +TCL_MEM_DEBUG = 0 +TCL_COMPILE_DEBUG = 0 + +!if "$(STATS)" != "" && ![nmakehlp -f "$(STATS)" "none"] + +!if [nmakehlp -f $(STATS) "memdbg"] +!message *** Doing memdbg +TCL_MEM_DEBUG = 1 +!else +TCL_MEM_DEBUG = 0 +!endif + +!if [nmakehlp -f $(STATS) "compdbg"] +!message *** Doing compdbg +TCL_COMPILE_DEBUG = 1 +!else +TCL_COMPILE_DEBUG = 0 +!endif + +!endif + +#################################################################### +# 9. Parse the CHECKS macro to configure additional compiler checks +# The following macros are set by this section: +# WARNINGS - compiler switches that control the warnings level +# TCL_NO_DEPRECATED - 1 -> disable support for deprecated functions +# 0 -> enable deprecated functions + +# Defaults - Permit deprecated functions and warning level 3 +TCL_NO_DEPRECATED = 0 +WARNINGS = -W3 + +!if "$(CHECKS)" != "" && ![nmakehlp -f "$(CHECKS)" "none"] + +!if [nmakehlp -f $(CHECKS) "nodep"] +!message *** Doing nodep check +TCL_NO_DEPRECATED = 1 +!endif + +!if [nmakehlp -f $(CHECKS) "fullwarn"] +!message *** Doing full warnings check +WARNINGS = -W4 +!if [nmakehlp -l -warn:3 $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS) -warn:3 !endif !endif +!if [nmakehlp -f $(CHECKS) "64bit"] && [nmakehlp -c -Wp64] +!message *** Doing 64bit portability warnings +WARNINGS = $(WARNINGS) -Wp64 +!endif -!if !$(STATIC_BUILD) -# Make sure we don't build overly fat DLLs. -MSVCRT = 1 -# We shouldn't statically put the extensions inside the shell when dynamic. -TCL_USE_STATIC_PACKAGES = 0 !endif -#---------------------------------------------------------- +################################################################ +# 10. Construct output directory and file paths # Figure-out how to name our intermediate and output directories. -# We wouldn't want different builds to use the same .obj files -# by accident. -#---------------------------------------------------------- +# In order to avoid inadvertent mixing of object files built using +# different compilers, build configurations etc., +# +# Naming convention (suffixes): +# t = full thread support. (Not used for Tcl >= 8.7) +# s = static library (as opposed to an import library) +# g = linked to the debug enabled C run-time. +# x = special static build when it links to the dynamic C run-time. +# +# The following macros are set in this section: +# SUFX - the suffix to use for binaries based on above naming convention +# BUILDDIRTOP - the toplevel default output directory +# is of the form {Release,Debug}[_AMD64][_COMPILERVERSION] +# TMP_DIR - directory where object files are created +# OUT_DIR - directory where output executables are created +# Both TMP_DIR and OUT_DIR are defaulted only if not defined by the +# parent makefile (or command line). The default values are +# based on BUILDDIRTOP. +# STUBPREFIX - name of the stubs library for this project +# PRJIMPLIB - output path of the generated project import library +# PRJLIBNAME - name of generated project library +# PRJLIB - output path of generated project library +# PRJSTUBLIBNAME - name of the generated project stubs library +# PRJSTUBLIB - output path of the generated project stubs library +# RESFILE - output resource file (only if not static build) -#---------------------------------------- -# Naming convention: -# t = full thread support. -# s = static library (as opposed to an -# import library) -# g = linked to the debug enabled C -# run-time. -# x = special static build when it -# links to the dynamic C run-time. -#---------------------------------------- SUFX = tsgx !if $(DEBUG) @@ -338,7 +1079,7 @@ BUILDDIRTOP =$(BUILDDIRTOP)_$(MACHINE) BUILDDIRTOP =$(BUILDDIRTOP)_VC$(VCVER) !endif -!if !$(DEBUG) || $(DEBUG) && $(UNCHECKED) +!if !$(DEBUG) || $(TCL_VERSION) > 86 || $(DEBUG) && $(UNCHECKED) SUFX = $(SUFX:g=) !endif @@ -348,20 +1089,18 @@ TMP_DIRFULL = .\$(BUILDDIRTOP)\$(PROJECT)_ThreadedDynamicStaticX TMP_DIRFULL = $(TMP_DIRFULL:Static=) SUFX = $(SUFX:s=) EXT = dll -!if $(MSVCRT) TMP_DIRFULL = $(TMP_DIRFULL:X=) SUFX = $(SUFX:x=) -!endif !else TMP_DIRFULL = $(TMP_DIRFULL:Dynamic=) EXT = lib -!if !$(MSVCRT) +!if $(MSVCRT) && $(TCL_VERSION) > 86 || !$(MSVCRT) && $(TCL_VERSION) < 87 TMP_DIRFULL = $(TMP_DIRFULL:X=) SUFX = $(SUFX:x=) !endif !endif -!if !$(TCL_THREADS) +!if !$(TCL_THREADS) || $(TCL_VERSION) > 86 TMP_DIRFULL = $(TMP_DIRFULL:Threaded=) SUFX = $(SUFX:t=) !endif @@ -377,335 +1116,798 @@ OUT_DIR = $(TMP_DIR) !endif !endif +# Relative paths -> absolute +!if [echo OUT_DIR = \> nmakehlp.out] \ + || [nmakehlp -Q "$(OUT_DIR)" >> nmakehlp.out] +!error *** Could not fully qualify path OUT_DIR=$(OUT_DIR) +!endif +!if [echo TMP_DIR = \>> nmakehlp.out] \ + || [nmakehlp -Q "$(TMP_DIR)" >> nmakehlp.out] +!error *** Could not fully qualify path TMP_DIR=$(TMP_DIR) +!endif +!include nmakehlp.out -#---------------------------------------------------------- -# Decode the statistics requested. -#---------------------------------------------------------- +# The name of the stubs library for the project being built +STUBPREFIX = $(PROJECT)stub -!if "$(STATS)" == "" || [nmakehlp -f "$(STATS)" "none"] -TCL_MEM_DEBUG = 0 -TCL_COMPILE_DEBUG = 0 -!else -!if [nmakehlp -f $(STATS) "memdbg"] -!message *** Doing memdbg -TCL_MEM_DEBUG = 1 +# +# Set up paths to various Tcl executables and libraries needed by extensions +# + +# TIP 430. Unused for 8.6 but no harm defining it to allow a common rules.vc +TCL_ZIP_FILE = libtcl$(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION)$(TCL_PATCH_LETTER)$(TCL_RELEASE_SERIAL).zip +TK_ZIP_FILE = libtk$(TK_MAJOR_VERSION).$(TK_MINOR_VERSION)$(TK_PATCH_LETTER)$(TK_RELEASE_SERIAL).zip + +!if $(DOING_TCL) +TCLSHNAME = $(PROJECT)sh$(VERSION)$(SUFX).exe +TCLSH = $(OUT_DIR)\$(TCLSHNAME) +TCLIMPLIB = $(OUT_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib +TCLLIBNAME = $(PROJECT)$(VERSION)$(SUFX).$(EXT) +TCLLIB = $(OUT_DIR)\$(TCLLIBNAME) +TCLSCRIPTZIP = $(OUT_DIR)\$(TCL_ZIP_FILE) + +!if $(TCL_MAJOR_VERSION) == 8 +TCLSTUBLIBNAME = $(STUBPREFIX)$(VERSION).lib !else -TCL_MEM_DEBUG = 0 +TCLSTUBLIBNAME = $(STUBPREFIX).lib !endif -!if [nmakehlp -f $(STATS) "compdbg"] -!message *** Doing compdbg -TCL_COMPILE_DEBUG = 1 +TCLSTUBLIB = $(OUT_DIR)\$(TCLSTUBLIBNAME) +TCL_INCLUDES = -I"$(WIN_DIR)" -I"$(GENERICDIR)" + +!else # !$(DOING_TCL) + +!if $(TCLINSTALL) # Building against an installed Tcl + +# When building extensions, we need to locate tclsh. Depending on version +# of Tcl we are building against, this may or may not have a "t" suffix. +# Try various possibilities in turn. +TCLSH = $(_TCLDIR)\bin\tclsh$(TCL_VERSION)$(SUFX:t=).exe +!if !exist("$(TCLSH)") +TCLSH = $(_TCLDIR)\bin\tclsh$(TCL_VERSION)t$(SUFX:t=).exe +!endif + +!if $(TCL_MAJOR_VERSION) == 8 +TCLSTUBLIB = $(_TCLDIR)\lib\tclstub$(TCL_VERSION).lib !else -TCL_COMPILE_DEBUG = 0 +TCLSTUBLIB = $(_TCLDIR)\lib\tclstub.lib !endif +TCLIMPLIB = $(_TCLDIR)\lib\tcl$(TCL_VERSION)$(SUFX:t=).lib +# When building extensions, may be linking against Tcl that does not add +# "t" suffix (e.g. 8.5 or 8.7). If lib not found check for that possibility. +!if !exist("$(TCLIMPLIB)") +TCLIMPLIB = $(_TCLDIR)\lib\tcl$(TCL_VERSION)t$(SUFX:t=).lib !endif +TCL_LIBRARY = $(_TCLDIR)\lib +TCLREGLIB = $(_TCLDIR)\lib\tclreg13$(SUFX:t=).lib +TCLDDELIB = $(_TCLDIR)\lib\tcldde14$(SUFX:t=).lib +TCLSCRIPTZIP = $(_TCLDIR)\lib\$(TCL_ZIP_FILE) +TCLTOOLSDIR = \must\have\tcl\sources\to\build\this\target +TCL_INCLUDES = -I"$(_TCLDIR)\include" +!else # Building against Tcl sources -#---------------------------------------------------------- -# Decode the checks requested. -#---------------------------------------------------------- - -!if "$(CHECKS)" == "" || [nmakehlp -f "$(CHECKS)" "none"] -TCL_NO_DEPRECATED = 0 -WARNINGS = -W3 -!else -!if [nmakehlp -f $(CHECKS) "nodep"] -!message *** Doing nodep check -TCL_NO_DEPRECATED = 1 +TCLSH = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)$(SUFX:t=).exe +!if !exist($(TCLSH)) +TCLSH = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)t$(SUFX:t=).exe +!endif +!if $(TCL_MAJOR_VERSION) == 8 +TCLSTUBLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclstub$(TCL_VERSION).lib !else -TCL_NO_DEPRECATED = 0 +TCLSTUBLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclstub.lib !endif -!if [nmakehlp -f $(CHECKS) "fullwarn"] -!message *** Doing full warnings check -WARNINGS = -W4 -!if [nmakehlp -l -warn:3] -LINKERFLAGS = $(LINKERFLAGS) -warn:3 +TCLIMPLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tcl$(TCL_VERSION)$(SUFX:t=).lib +# When building extensions, may be linking against Tcl that does not add +# "t" suffix (e.g. 8.5 or 8.7). If lib not found check for that possibility. +!if !exist("$(TCLIMPLIB)") +TCLIMPLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tcl$(TCL_VERSION)t$(SUFX:t=).lib !endif +TCL_LIBRARY = $(_TCLDIR)\library +TCLREGLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclreg13$(SUFX:t=).lib +TCLDDELIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tcldde14$(SUFX:t=).lib +TCLSCRIPTZIP = $(_TCLDIR)\win\$(BUILDDIRTOP)\$(TCL_ZIP_FILE) +TCLTOOLSDIR = $(_TCLDIR)\tools +TCL_INCLUDES = -I"$(_TCLDIR)\generic" -I"$(_TCLDIR)\win" + +!endif # TCLINSTALL + +!if !$(STATIC_BUILD) && "$(TCL_BUILD_FOR)" == "8" +tcllibs = "$(TCLSTUBLIB)" !else -WARNINGS = -W3 +tcllibs = "$(TCLSTUBLIB)" "$(TCLIMPLIB)" !endif -!if [nmakehlp -f $(CHECKS) "64bit"] && [nmakehlp -c -Wp64] -!message *** Doing 64bit portability warnings -WARNINGS = $(WARNINGS) -Wp64 + +!endif # $(DOING_TCL) + +# We need a tclsh that will run on the host machine as part of the build. +# IX86 runs on all architectures. +!ifndef TCLSH_NATIVE +!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "$(NATIVE_ARCH)" +TCLSH_NATIVE = $(TCLSH) +!else +!error You must explicitly set TCLSH_NATIVE for cross-compilation !endif !endif -!if $(PGO) > 1 -!if [nmakehlp -l -ltcg:pgoptimize] -LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pgoptimize +# Do the same for Tk and Tk extensions that require the Tk libraries +!if $(DOING_TK) || $(NEED_TK) +WISHNAMEPREFIX = wish +WISHNAME = $(WISHNAMEPREFIX)$(TK_VERSION)$(SUFX).exe +TKLIBNAME8 = tk$(TK_VERSION)$(SUFX).$(EXT) +TKLIBNAME9 = tcl9tk$(TK_VERSION)$(SUFX).$(EXT) +!if $(TCL_MAJOR_VERSION) == 8 || "$(TCL_BUILD_FOR)" == "8" +TKLIBNAME = tk$(TK_VERSION)$(SUFX).$(EXT) +TKIMPLIBNAME = tk$(TK_VERSION)$(SUFX).lib !else -MSG=^ -This compiler does not support profile guided optimization. -!error $(MSG) +TKLIBNAME = tcl9tk$(TK_VERSION)$(SUFX).$(EXT) +TKIMPLIBNAME = tcl9tk$(TK_VERSION)$(SUFX).lib !endif -!elseif $(PGO) > 0 -!if [nmakehlp -l -ltcg:pginstrument] -LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pginstrument +!if $(TK_MAJOR_VERSION) == 8 +TKSTUBLIBNAME = tkstub$(TK_VERSION).lib !else -MSG=^ -This compiler does not support profile guided optimization. -!error $(MSG) +TKSTUBLIBNAME = tkstub.lib !endif + +!if $(DOING_TK) +WISH = $(OUT_DIR)\$(WISHNAME) +TKSTUBLIB = $(OUT_DIR)\$(TKSTUBLIBNAME) +TKIMPLIB = $(OUT_DIR)\$(TKIMPLIBNAME) +TKLIB = $(OUT_DIR)\$(TKLIBNAME) +TK_INCLUDES = -I"$(WIN_DIR)" -I"$(GENERICDIR)" +TKSCRIPTZIP = $(OUT_DIR)\$(TK_ZIP_FILE) + +!else # effectively NEED_TK + +!if $(TKINSTALL) # Building against installed Tk +WISH = $(_TKDIR)\bin\$(WISHNAME) +TKSTUBLIB = $(_TKDIR)\lib\$(TKSTUBLIBNAME) +TKIMPLIB = $(_TKDIR)\lib\$(TKIMPLIBNAME) +# When building extensions, may be linking against Tk that does not add +# "t" suffix (e.g. 8.5 or 8.7). If lib not found check for that possibility. +!if !exist("$(TKIMPLIB)") +TKIMPLIBNAME = tk$(TK_VERSION)$(SUFX:t=).lib +TKIMPLIB = $(_TKDIR)\lib\$(TKIMPLIBNAME) !endif +TK_INCLUDES = -I"$(_TKDIR)\include" +TKSCRIPTZIP = $(_TKDIR)\lib\$(TK_ZIP_FILE) -#---------------------------------------------------------- -# Set our defines now armed with our options. -#---------------------------------------------------------- +!else # Building against Tk sources + +WISH = $(_TKDIR)\win\$(BUILDDIRTOP)\$(WISHNAME) +TKSTUBLIB = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TKSTUBLIBNAME) +TKIMPLIB = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TKIMPLIBNAME) +# When building extensions, may be linking against Tk that does not add +# "t" suffix (e.g. 8.5 or 8.7). If lib not found check for that possibility. +!if !exist("$(TKIMPLIB)") +TKIMPLIBNAME = tk$(TK_VERSION)$(SUFX:t=).lib +TKIMPLIB = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TKIMPLIBNAME) +!endif +TK_INCLUDES = -I"$(_TKDIR)\generic" -I"$(_TKDIR)\win" -I"$(_TKDIR)\xlib" +TKSCRIPTZIP = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TK_ZIP_FILE) + +!endif # TKINSTALL + +tklibs = "$(TKSTUBLIB)" "$(TKIMPLIB)" + +!endif # $(DOING_TK) +!endif # $(DOING_TK) || $(NEED_TK) + +# Various output paths +PRJIMPLIB = $(OUT_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib +PRJLIBNAME8 = $(PROJECT)$(VERSION)$(SUFX).$(EXT) +# Even when building against Tcl 8, PRJLIBNAME9 must not have "t" +PRJLIBNAME9 = tcl9$(PROJECT)$(VERSION)$(SUFX:t=).$(EXT) +!if $(TCL_MAJOR_VERSION) == 8 || "$(TCL_BUILD_FOR)" == "8" +PRJLIBNAME = $(PRJLIBNAME8) +!else +PRJLIBNAME = $(PRJLIBNAME9) +!endif +PRJLIB = $(OUT_DIR)\$(PRJLIBNAME) + +!if $(TCL_MAJOR_VERSION) == 8 +PRJSTUBLIBNAME = $(STUBPREFIX)$(VERSION).lib +!else +PRJSTUBLIBNAME = $(STUBPREFIX).lib +!endif +PRJSTUBLIB = $(OUT_DIR)\$(PRJSTUBLIBNAME) -OPTDEFINES = -DTCL_CFGVAL_ENCODING=$(CFG_ENCODING) -DSTDC_HEADERS +# If extension parent makefile has not defined a resource definition file, +# we will generate one from standard template. +!if !$(DOING_TCL) && !$(DOING_TK) && !$(STATIC_BUILD) +!ifdef RCFILE +RESFILE = $(TMP_DIR)\$(RCFILE:.rc=.res) +!else +RESFILE = $(TMP_DIR)\$(PROJECT).res +!endif +!endif + +################################################################### +# 11. Construct the paths for the installation directories +# The following macros get defined in this section: +# LIB_INSTALL_DIR - where libraries should be installed +# BIN_INSTALL_DIR - where the executables should be installed +# DOC_INSTALL_DIR - where documentation should be installed +# SCRIPT_INSTALL_DIR - where scripts should be installed +# INCLUDE_INSTALL_DIR - where C include files should be installed +# DEMO_INSTALL_DIR - where demos should be installed +# PRJ_INSTALL_DIR - where package will be installed (not set for Tcl and Tk) + +!if $(DOING_TCL) || $(DOING_TK) +LIB_INSTALL_DIR = $(_INSTALLDIR)\lib +BIN_INSTALL_DIR = $(_INSTALLDIR)\bin +DOC_INSTALL_DIR = $(_INSTALLDIR)\doc +!if $(DOING_TCL) +SCRIPT_INSTALL_DIR = $(_INSTALLDIR)\lib\$(PROJECT)$(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION) +MODULE_INSTALL_DIR = $(_INSTALLDIR)\lib\tcl$(TCL_MAJOR_VERSION) +!else # DOING_TK +SCRIPT_INSTALL_DIR = $(_INSTALLDIR)\lib\$(PROJECT)$(TK_MAJOR_VERSION).$(TK_MINOR_VERSION) +!endif +DEMO_INSTALL_DIR = $(SCRIPT_INSTALL_DIR)\demos +INCLUDE_INSTALL_DIR = $(_INSTALLDIR)\include + +!else # extension other than Tk + +PRJ_INSTALL_DIR = $(_INSTALLDIR)\$(PROJECT)$(DOTVERSION) +!if $(MULTIPLATFORM_INSTALL) +LIB_INSTALL_DIR = $(PRJ_INSTALL_DIR)\$(PLATFORM_IDENTIFY) +BIN_INSTALL_DIR = $(PRJ_INSTALL_DIR)\$(PLATFORM_IDENTIFY) +!else +LIB_INSTALL_DIR = $(PRJ_INSTALL_DIR) +BIN_INSTALL_DIR = $(PRJ_INSTALL_DIR) +!endif +DOC_INSTALL_DIR = $(PRJ_INSTALL_DIR) +SCRIPT_INSTALL_DIR = $(PRJ_INSTALL_DIR) +DEMO_INSTALL_DIR = $(PRJ_INSTALL_DIR)\demos +INCLUDE_INSTALL_DIR = $(_INSTALLDIR)\..\include + +!endif + +################################################################### +# 12. Set up actual options to be passed to the compiler and linker +# Now we have all the information we need, set up the actual flags and +# options that we will pass to the compiler and linker. The main +# makefile should use these in combination with whatever other flags +# and switches are specific to it. +# The following macros are defined, names are for historical compatibility: +# OPTDEFINES - /Dxxx C macro flags based on user-specified OPTS +# COMPILERFLAGS - /Dxxx C macro flags independent of any configuration options +# crt - Compiler switch that selects the appropriate C runtime +# cdebug - Compiler switches related to debug AND optimizations +# cwarn - Compiler switches that set warning levels +# cflags - complete compiler switches (subsumes cdebug and cwarn) +# ldebug - Linker switches controlling debug information and optimization +# lflags - complete linker switches (subsumes ldebug) except subsystem type +# dlllflags - complete linker switches to build DLLs (subsumes lflags) +# conlflags - complete linker switches for console program (subsumes lflags) +# guilflags - complete linker switches for GUI program (subsumes lflags) +# baselibs - minimum Windows libraries required. Parent makefile can +# define PRJ_LIBS before including rules.rc if additional libs are needed + +OPTDEFINES = /DSTDC_HEADERS /DUSE_NMAKE=1 +!if $(VCVERSION) > 1600 +OPTDEFINES = $(OPTDEFINES) /DHAVE_STDINT_H=1 +!else +OPTDEFINES = $(OPTDEFINES) /DMP_NO_STDINT=1 +!endif +!if $(VCVERSION) >= 1800 +OPTDEFINES = $(OPTDEFINES) /DHAVE_INTTYPES_H=1 /DHAVE_STDBOOL_H=1 +!endif !if $(TCL_MEM_DEBUG) -OPTDEFINES = $(OPTDEFINES) -DTCL_MEM_DEBUG +OPTDEFINES = $(OPTDEFINES) /DTCL_MEM_DEBUG !endif !if $(TCL_COMPILE_DEBUG) -OPTDEFINES = $(OPTDEFINES) -DTCL_COMPILE_DEBUG -DTCL_COMPILE_STATS +OPTDEFINES = $(OPTDEFINES) /DTCL_COMPILE_DEBUG /DTCL_COMPILE_STATS !endif -!if $(TCL_THREADS) -OPTDEFINES = $(OPTDEFINES) -DTCL_THREADS=1 -!if $(USE_THREAD_ALLOC) -OPTDEFINES = $(OPTDEFINES) -DUSE_THREAD_ALLOC=1 +!if $(TCL_THREADS) && $(TCL_VERSION) < 87 +OPTDEFINES = $(OPTDEFINES) /DTCL_THREADS=1 +!if $(USE_THREAD_ALLOC) && $(TCL_VERSION) < 87 +OPTDEFINES = $(OPTDEFINES) /DUSE_THREAD_ALLOC=1 !endif !endif !if $(STATIC_BUILD) -OPTDEFINES = $(OPTDEFINES) -DSTATIC_BUILD +OPTDEFINES = $(OPTDEFINES) /DSTATIC_BUILD +!elseif $(TCL_VERSION) > 86 +OPTDEFINES = $(OPTDEFINES) /DTCL_WITH_EXTERNAL_TOMMATH +!if "$(MACHINE)" == "AMD64" || "$(MACHINE)" == "ARM64" +OPTDEFINES = $(OPTDEFINES) /DMP_64BIT +!endif !endif !if $(TCL_NO_DEPRECATED) -OPTDEFINES = $(OPTDEFINES) -DTCL_NO_DEPRECATED +OPTDEFINES = $(OPTDEFINES) /DTCL_NO_DEPRECATED +!endif + +!if $(USE_STUBS) +# Note we do not define USE_TCL_STUBS even when building tk since some +# test targets in tk do not use stubs +!if !$(DOING_TCL) +USE_STUBS_DEFS = /DUSE_TCL_STUBS /DUSE_TCLOO_STUBS +!if $(NEED_TK) +USE_STUBS_DEFS = $(USE_STUBS_DEFS) /DUSE_TK_STUBS +!endif !endif +!endif # USE_STUBS !if !$(DEBUG) -OPTDEFINES = $(OPTDEFINES) -DNDEBUG +OPTDEFINES = $(OPTDEFINES) /DNDEBUG !if $(OPTIMIZING) -OPTDEFINES = $(OPTDEFINES) -DTCL_CFG_OPTIMIZED +OPTDEFINES = $(OPTDEFINES) /DTCL_CFG_OPTIMIZED !endif !endif !if $(PROFILE) -OPTDEFINES = $(OPTDEFINES) -DTCL_CFG_PROFILED +OPTDEFINES = $(OPTDEFINES) /DTCL_CFG_PROFILED !endif -!if "$(MACHINE)" == "IA64" || "$(MACHINE)" == "AMD64" -OPTDEFINES = $(OPTDEFINES) -DTCL_CFG_DO64BIT +!if "$(MACHINE)" == "AMD64" || "$(MACHINE)" == "ARM64" +OPTDEFINES = $(OPTDEFINES) /DTCL_CFG_DO64BIT !endif !if $(VCVERSION) < 1300 -OPTDEFINES = $(OPTDEFINES) -DNO_STRTOI64 +OPTDEFINES = $(OPTDEFINES) /DNO_STRTOI64=1 !endif -#---------------------------------------------------------- -# Locate the Tcl headers to build against -#---------------------------------------------------------- - -!if "$(PROJECT)" == "tcl" - -_TCL_H = ..\generic\tcl.h - -!else +!if $(TCL_MAJOR_VERSION) == 8 +!if "$(_USE_64BIT_TIME_T)" == "1" +OPTDEFINES = $(OPTDEFINES) /D_USE_64BIT_TIME_T=1 +!endif +!endif +!if "$(TCL_BUILD_FOR)" == "8" +OPTDEFINES = $(OPTDEFINES) /DTCL_MAJOR_VERSION=8 +!endif -# If INSTALLDIR set to tcl root dir then reset to the lib dir. -!if exist("$(_INSTALLDIR)\include\tcl.h") -_INSTALLDIR=$(_INSTALLDIR)\lib +# Like the TEA system only set this non empty for non-Tk extensions +# Note: some extensions use PACKAGE_NAME and others use PACKAGE_TCLNAME +# so we pass both +!if !$(DOING_TCL) && !$(DOING_TK) +PKGNAMEFLAGS = /DPACKAGE_NAME="\"$(PRJ_PACKAGE_TCLNAME)\"" \ + /DPACKAGE_TCLNAME="\"$(PRJ_PACKAGE_TCLNAME)\"" \ + /DPACKAGE_VERSION="\"$(DOTVERSION)\"" \ + /DMODULE_SCOPE=extern !endif -!if !defined(TCLDIR) -!if exist("$(_INSTALLDIR)\..\include\tcl.h") -TCLINSTALL = 1 -_TCLDIR = $(_INSTALLDIR)\.. -_TCL_H = $(_INSTALLDIR)\..\include\tcl.h -TCLDIR = $(_INSTALLDIR)\.. +# crt picks the C run time based on selected OPTS +!if $(MSVCRT) +!if $(DEBUG) && !$(UNCHECKED) +crt = -MDd !else -MSG=^ -Failed to find tcl.h. Set the TCLDIR macro. -!error $(MSG) +crt = -MD !endif !else -_TCLDIR = $(TCLDIR:/=\) -!if exist("$(_TCLDIR)\include\tcl.h") -TCLINSTALL = 1 -_TCL_H = $(_TCLDIR)\include\tcl.h -!elseif exist("$(_TCLDIR)\generic\tcl.h") -TCLINSTALL = 0 -_TCL_H = $(_TCLDIR)\generic\tcl.h +!if $(DEBUG) && !$(UNCHECKED) +crt = -MTd !else -MSG =^ -Failed to find tcl.h. The TCLDIR macro does not appear correct. -!error $(MSG) -!endif +crt = -MT !endif !endif -#-------------------------------------------------------------- -# Extract various version numbers from tcl headers -# The generated file is then included in the makefile. -#-------------------------------------------------------------- +# cdebug includes compiler options for debugging as well as optimization. +!if $(DEBUG) -!if [echo REM = This file is generated from rules.vc > versions.vc] -!endif -!if [echo TCL_MAJOR_VERSION = \>> versions.vc] \ - && [nmakehlp -V "$(_TCL_H)" TCL_MAJOR_VERSION >> versions.vc] -!endif -!if [echo TCL_MINOR_VERSION = \>> versions.vc] \ - && [nmakehlp -V "$(_TCL_H)" TCL_MINOR_VERSION >> versions.vc] -!endif -!if [echo TCL_PATCH_LEVEL = \>> versions.vc] \ - && [nmakehlp -V "$(_TCL_H)" TCL_PATCH_LEVEL >> versions.vc] -!endif +# In debugging mode, optimizations need to be disabled +cdebug = -Zi -Od $(DEBUGFLAGS) -# If building the tcl core then we need additional package versions -!if "$(PROJECT)" == "tcl" -!if [echo PKG_HTTP_VER = \>> versions.vc] \ - && [nmakehlp -V ..\library\http\pkgIndex.tcl http >> versions.vc] -!endif -!if [echo PKG_TCLTEST_VER = \>> versions.vc] \ - && [nmakehlp -V ..\library\tcltest\pkgIndex.tcl tcltest >> versions.vc] +!else + +cdebug = $(OPTIMIZATIONS) +!if $(SYMBOLS) +cdebug = $(cdebug) -Zi !endif -!if [echo PKG_MSGCAT_VER = \>> versions.vc] \ - && [nmakehlp -V ..\library\msgcat\pkgIndex.tcl msgcat >> versions.vc] + +!endif # $(DEBUG) + +# cwarn includes default warning levels, also C4090 (buggy) and C4146 is useless. +cwarn = $(WARNINGS) -wd4090 -wd4146 + +!if "$(MACHINE)" == "AMD64" || "$(MACHINE)" == "ARM64" +# Disable pointer<->int warnings related to cast between different sizes +# There are a gadzillion of these due to use of ClientData and +# clutter up compiler +# output increasing chance of a real warning getting lost. So disable them. +# Eventually some day, Tcl will be 64-bit clean. +cwarn = $(cwarn) -wd4311 -wd4312 !endif -!if [echo PKG_PLATFORM_VER = \>> versions.vc] \ - && [nmakehlp -V ..\library\platform\pkgIndex.tcl "platform " >> versions.vc] + +### Common compiler options that are architecture specific +!if "$(MACHINE)" == "ARM" +carch = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +!else +carch = !endif -!if [echo PKG_SHELL_VER = \>> versions.vc] \ - && [nmakehlp -V ..\library\platform\pkgIndex.tcl "platform::shell" >> versions.vc] + +# cpuid is only available on intel machines +!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "AMD64" +carch = $(carch) /DHAVE_CPUID=1 !endif -!if [echo PKG_DDE_VER = \>> versions.vc] \ - && [nmakehlp -V ..\library\dde\pkgIndex.tcl "dde " >> versions.vc] + +!if $(DEBUG) +# Turn warnings into errors +cwarn = $(cwarn) -WX !endif -!if [echo PKG_REG_VER =\>> versions.vc] \ - && [nmakehlp -V ..\library\reg\pkgIndex.tcl registry >> versions.vc] + +INCLUDES = $(TCL_INCLUDES) $(TK_INCLUDES) $(PRJ_INCLUDES) +!if !$(DOING_TCL) && !$(DOING_TK) +INCLUDES = $(INCLUDES) -I"$(GENERICDIR)" -I"$(WIN_DIR)" -I"$(COMPATDIR)" !endif + +# These flags are defined roughly in the order of the pre-reform +# rules.vc/makefile.vc to help visually compare that the pre- and +# post-reform build logs + +# cflags contains generic flags used for building practically all object files +cflags = -nologo -c $(COMPILERFLAGS) $(carch) $(cwarn) -Fp$(TMP_DIR)^\ $(cdebug) + +!if $(TCL_MAJOR_VERSION) == 8 && $(TCL_MINOR_VERSION) < 7 +cflags = $(cflags) -DTcl_Size=int !endif -!include versions.vc +# appcflags contains $(cflags) and flags for building the application +# object files (e.g. tclsh, or wish) pkgcflags contains $(cflags) plus +# flags used for building shared object files The two differ in the +# BUILD_$(PROJECT) macro which should be defined only for the shared +# library *implementation* and not for its caller interface -#-------------------------------------------------------------- -# Setup tcl version dependent stuff headers -#-------------------------------------------------------------- +appcflags_nostubs = $(cflags) $(crt) $(INCLUDES) $(TCL_DEFINES) $(PRJ_DEFINES) $(OPTDEFINES) +appcflags = $(appcflags_nostubs) $(USE_STUBS_DEFS) +pkgcflags = $(appcflags) $(PKGNAMEFLAGS) /DBUILD_$(PROJECT) +pkgcflags_nostubs = $(appcflags_nostubs) $(PKGNAMEFLAGS) /DBUILD_$(PROJECT) -!if "$(PROJECT)" != "tcl" +# stubscflags contains $(cflags) plus flags used for building a stubs +# library for the package. Note: /DSTATIC_BUILD is defined in +# $(OPTDEFINES) only if the OPTS configuration indicates a static +# library. However the stubs library is ALWAYS static hence included +# here irrespective of the OPTS setting. +# +# TBD - tclvfs has a comment that stubs libs should not be compiled with -GL +# without stating why. Tcl itself compiled stubs libs with this flag. +# so we do not remove it from cflags. -GL may prevent extensions +# compiled with one VC version to fail to link against stubs library +# compiled with another VC version. Check for this and fix accordingly. +stubscflags = $(cflags) $(PKGNAMEFLAGS) $(PRJ_DEFINES) $(OPTDEFINES) /Zl /GL- /DSTATIC_BUILD $(INCLUDES) $(USE_STUBS_DEFS) -TCL_VERSION = $(TCL_MAJOR_VERSION)$(TCL_MINOR_VERSION) +# Link flags -!if $(TCL_VERSION) < 81 -TCL_DOES_STUBS = 0 +!if $(DEBUG) +ldebug = -debug -debugtype:cv !else -TCL_DOES_STUBS = 1 +ldebug = -release -opt:ref -opt:icf,3 +!if $(SYMBOLS) +ldebug = $(ldebug) -debug -debugtype:cv +!endif !endif -!if $(TCLINSTALL) -TCLSH = "$(_TCLDIR)\bin\tclsh$(TCL_VERSION)$(SUFX).exe" -!if !exist($(TCLSH)) && $(TCL_THREADS) -TCLSH = "$(_TCLDIR)\bin\tclsh$(TCL_VERSION)t$(SUFX).exe" +# Note: Profiling is currently only possible with the Visual Studio Enterprise +!if $(PROFILE) +ldebug= $(ldebug) -profile !endif -TCLSTUBLIB = "$(_TCLDIR)\lib\tclstub$(TCL_VERSION).lib" -TCLIMPLIB = "$(_TCLDIR)\lib\tcl$(TCL_VERSION)$(SUFX).lib" -TCL_LIBRARY = $(_TCLDIR)\lib -TCLREGLIB = "$(_TCLDIR)\lib\tclreg13$(SUFX:t=).lib" -TCLDDELIB = "$(_TCLDIR)\lib\tcldde14$(SUFX:t=).lib" -COFFBASE = \must\have\tcl\sources\to\build\this\target -TCLTOOLSDIR = \must\have\tcl\sources\to\build\this\target -TCL_INCLUDES = -I"$(_TCLDIR)\include" -!else -TCLSH = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)$(SUFX).exe" -!if !exist($(TCLSH)) && $(TCL_THREADS) -TCLSH = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)t$(SUFX).exe" + +### Declarations common to all linker versions +lflags = -nologo -machine:$(MACHINE) $(LINKERFLAGS) $(ldebug) + +!if $(MSVCRT) && !($(DEBUG) && !$(UNCHECKED)) && $(VCVERSION) >= 1900 +lflags = $(lflags) -nodefaultlib:libucrt.lib !endif -TCLSTUBLIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclstub$(TCL_VERSION).lib" -TCLIMPLIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tcl$(TCL_VERSION)$(SUFX).lib" -TCL_LIBRARY = $(_TCLDIR)\library -TCLREGLIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclreg13$(SUFX:t=).lib" -TCLDDELIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tcldde14$(SUFX:t=).lib" -COFFBASE = "$(_TCLDIR)\win\coffbase.txt" -TCLTOOLSDIR = $(_TCLDIR)\tools -TCL_INCLUDES = -I"$(_TCLDIR)\generic" -I"$(_TCLDIR)\win" + +dlllflags = $(lflags) -dll +conlflags = $(lflags) -subsystem:console +guilflags = $(lflags) -subsystem:windows + +# Libraries that are required for every image. +# Extensions should define any additional libraries with $(PRJ_LIBS) +winlibs = kernel32.lib advapi32.lib + +!if $(NEED_TK) +winlibs = $(winlibs) gdi32.lib user32.lib uxtheme.lib !endif +# Avoid 'unresolved external symbol __security_cookie' errors. +# c.f. http://support.microsoft.com/?id=894573 +!if "$(MACHINE)" == "AMD64" +!if $(VCVERSION) > 1399 && $(VCVERSION) < 1500 +winlibs = $(winlibs) bufferoverflowU.lib +!endif !endif -#------------------------------------------------------------------------- -# Locate the Tk headers to build against -#------------------------------------------------------------------------- +baselibs = $(winlibs) $(PRJ_LIBS) -!if "$(PROJECT)" == "tk" -_TK_H = ..\generic\tk.h -_INSTALLDIR = $(_INSTALLDIR)\.. +!if $(MSVCRT) && !($(DEBUG) && !$(UNCHECKED)) && $(VCVERSION) >= 1900 +baselibs = $(baselibs) ucrt.lib !endif -!ifdef PROJECT_REQUIRES_TK -!if !defined(TKDIR) -!if exist("$(_INSTALLDIR)\..\include\tk.h") -TKINSTALL = 1 -_TKDIR = $(_INSTALLDIR)\.. -_TK_H = $(_TKDIR)\include\tk.h -TKDIR = $(_TKDIR) -!elseif exist("$(_TCLDIR)\include\tk.h") -TKINSTALL = 1 -_TKDIR = $(_TCLDIR) -_TK_H = $(_TKDIR)\include\tk.h -TKDIR = $(_TKDIR) +################################################################ +# 13. Define standard commands, common make targets and implicit rules + +CCPKGCMD = $(cc32) $(pkgcflags) -Fo$(TMP_DIR)^\ +CCAPPCMD = $(cc32) $(appcflags) -Fo$(TMP_DIR)^\ +CCSTUBSCMD = $(cc32) $(stubscflags) -Fo$(TMP_DIR)^\ + +LIBCMD = $(lib32) -nologo $(LINKERFLAGS) -out:$@ +DLLCMD = $(link32) $(dlllflags) -out:$@ $(baselibs) $(tcllibs) $(tklibs) + +CONEXECMD = $(link32) $(conlflags) -out:$@ $(baselibs) $(tcllibs) $(tklibs) +GUIEXECMD = $(link32) $(guilflags) -out:$@ $(baselibs) $(tcllibs) $(tklibs) +RESCMD = $(rc32) -fo $@ -r -i "$(GENERICDIR)" -i "$(TMP_DIR)" \ + $(TCL_INCLUDES) /DSTATIC_BUILD=$(STATIC_BUILD) \ + /DDEBUG=$(DEBUG) -d UNCHECKED=$(UNCHECKED) \ + /DCOMMAVERSION=$(RCCOMMAVERSION) \ + /DDOTVERSION=\"$(DOTVERSION)\" \ + /DVERSION=\"$(VERSION)\" \ + /DSUFX=\"$(SUFX)\" \ + /DPROJECT=\"$(PROJECT)\" \ + /DPRJLIBNAME=\"$(PRJLIBNAME)\" + +!ifndef DEFAULT_BUILD_TARGET +DEFAULT_BUILD_TARGET = $(PROJECT) !endif + +default-target: $(DEFAULT_BUILD_TARGET) + +!if $(MULTIPLATFORM_INSTALL) +default-pkgindex: + @echo if {[package vsatisfies [package provide Tcl] 9.0-]} { > $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PLATFORM_IDENTIFY) $(PRJLIBNAME9)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } else { >> $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PLATFORM_IDENTIFY) $(PRJLIBNAME8)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } >> $(OUT_DIR)\pkgIndex.tcl !else -_TKDIR = $(TKDIR:/=\) -!if exist("$(_TKDIR)\include\tk.h") -TKINSTALL = 1 -_TK_H = $(_TKDIR)\include\tk.h -!elseif exist("$(_TKDIR)\generic\tk.h") -TKINSTALL = 0 -_TK_H = $(_TKDIR)\generic\tk.h +default-pkgindex: + @echo if {[package vsatisfies [package provide Tcl] 9.0-]} { > $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PRJLIBNAME9)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } else { >> $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PRJLIBNAME8)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } >> $(OUT_DIR)\pkgIndex.tcl +!endif + +default-pkgindex-tea: + @if exist $(ROOT)\pkgIndex.tcl.in nmakehlp -s << $(ROOT)\pkgIndex.tcl.in > $(OUT_DIR)\pkgIndex.tcl +@PACKAGE_VERSION@ $(DOTVERSION) +@PACKAGE_NAME@ $(PRJ_PACKAGE_TCLNAME) +@PACKAGE_TCLNAME@ $(PRJ_PACKAGE_TCLNAME) +@PKG_LIB_FILE@ $(PRJLIBNAME) +@PKG_LIB_FILE8@ $(PRJLIBNAME8) +@PKG_LIB_FILE9@ $(PRJLIBNAME9) +<< + +default-install: default-install-binaries default-install-libraries +!if $(SYMBOLS) +default-install: default-install-pdbs +!endif + +# Again to deal with historical brokenness, there is some confusion +# in terminlogy. For extensions, the "install-binaries" was used to +# locate target directory for *binary shared libraries* and thus +# the appropriate macro is LIB_INSTALL_DIR since BIN_INSTALL_DIR is +# for executables (exes). On the other hand the "install-libraries" +# target is for *scripts* and should have been called "install-scripts". +default-install-binaries: $(PRJLIB) + @echo Installing binaries to '$(LIB_INSTALL_DIR)' + @if not exist "$(LIB_INSTALL_DIR)" mkdir "$(LIB_INSTALL_DIR)" + @$(CPY) $(PRJLIB) "$(LIB_INSTALL_DIR)" >NUL + +# Alias for default-install-scripts +default-install-libraries: default-install-scripts + +default-install-scripts: $(OUT_DIR)\pkgIndex.tcl + @echo Installing libraries to '$(SCRIPT_INSTALL_DIR)' + @if exist $(LIBDIR) $(CPY) $(LIBDIR)\*.tcl "$(SCRIPT_INSTALL_DIR)" + @echo Installing package index in '$(SCRIPT_INSTALL_DIR)' + @$(CPY) $(OUT_DIR)\pkgIndex.tcl $(SCRIPT_INSTALL_DIR) + +default-install-stubs: + @echo Installing stubs library to '$(SCRIPT_INSTALL_DIR)' + @if not exist "$(SCRIPT_INSTALL_DIR)" mkdir "$(SCRIPT_INSTALL_DIR)" + @$(CPY) $(PRJSTUBLIB) "$(SCRIPT_INSTALL_DIR)" >NUL + +default-install-pdbs: + @echo Installing PDBs to '$(LIB_INSTALL_DIR)' + @if not exist "$(LIB_INSTALL_DIR)" mkdir "$(LIB_INSTALL_DIR)" + @$(CPY) "$(OUT_DIR)\*.pdb" "$(LIB_INSTALL_DIR)\" + +# "emacs font-lock highlighting fix + +default-install-docs-html: + @echo Installing documentation files to '$(DOC_INSTALL_DIR)' + @if not exist "$(DOC_INSTALL_DIR)" mkdir "$(DOC_INSTALL_DIR)" + @if exist $(DOCDIR) for %f in ("$(DOCDIR)\*.html" "$(DOCDIR)\*.css" "$(DOCDIR)\*.png") do @$(COPY) %f "$(DOC_INSTALL_DIR)" + +default-install-docs-n: + @echo Installing documentation files to '$(DOC_INSTALL_DIR)' + @if not exist "$(DOC_INSTALL_DIR)" mkdir "$(DOC_INSTALL_DIR)" + @if exist $(DOCDIR) for %f in ("$(DOCDIR)\*.n") do @$(COPY) %f "$(DOC_INSTALL_DIR)" + +default-install-demos: + @echo Installing demos to '$(DEMO_INSTALL_DIR)' + @if not exist "$(DEMO_INSTALL_DIR)" mkdir "$(DEMO_INSTALL_DIR)" + @if exist $(DEMODIR) $(CPYDIR) "$(DEMODIR)" "$(DEMO_INSTALL_DIR)" + +default-clean: + @echo Cleaning $(TMP_DIR)\* ... + @if exist $(TMP_DIR)\nul $(RMDIR) $(TMP_DIR) + @echo Cleaning $(WIN_DIR)\nmakehlp.obj, nmakehlp.exe ... + @if exist $(WIN_DIR)\nmakehlp.obj del $(WIN_DIR)\nmakehlp.obj + @if exist $(WIN_DIR)\nmakehlp.exe del $(WIN_DIR)\nmakehlp.exe + @if exist $(WIN_DIR)\nmakehlp.out del $(WIN_DIR)\nmakehlp.out + @echo Cleaning $(WIN_DIR)\nmhlp-out.txt ... + @if exist $(WIN_DIR)\nmhlp-out.txt del $(WIN_DIR)\nmhlp-out.txt + @echo Cleaning $(WIN_DIR)\_junk.pch ... + @if exist $(WIN_DIR)\_junk.pch del $(WIN_DIR)\_junk.pch + @echo Cleaning $(WIN_DIR)\vercl.x, vercl.i ... + @if exist $(WIN_DIR)\vercl.x del $(WIN_DIR)\vercl.x + @if exist $(WIN_DIR)\vercl.i del $(WIN_DIR)\vercl.i + @echo Cleaning $(WIN_DIR)\versions.vc, version.vc ... + @if exist $(WIN_DIR)\versions.vc del $(WIN_DIR)\versions.vc + @if exist $(WIN_DIR)\version.vc del $(WIN_DIR)\version.vc + +default-hose: default-clean + @echo Hosing $(OUT_DIR)\* ... + @if exist $(OUT_DIR)\nul $(RMDIR) $(OUT_DIR) + +# Only for backward compatibility +default-distclean: default-hose + +default-setup: + @if not exist $(OUT_DIR)\nul mkdir $(OUT_DIR) + @if not exist $(TMP_DIR)\nul mkdir $(TMP_DIR) + +!if "$(TESTPAT)" != "" +TESTFLAGS = $(TESTFLAGS) -file $(TESTPAT) +!endif + +default-test: default-setup $(PROJECT) + @set TCLLIBPATH=$(OUT_DIR:\=/) + @if exist $(LIBDIR) for %f in ("$(LIBDIR)\*.tcl") do @$(COPY) %f "$(OUT_DIR)" + cd "$(TESTDIR)" && $(DEBUGGER) $(TCLSH) all.tcl $(TESTFLAGS) + +default-shell: default-setup $(PROJECT) + @set TCLLIBPATH=$(OUT_DIR:\=/) + @if exist $(LIBDIR) for %f in ("$(LIBDIR)\*.tcl") do @$(COPY) %f "$(OUT_DIR)" + $(DEBUGGER) $(TCLSH) + +# Generation of Windows version resource +!ifdef RCFILE + +# Note: don't use $** in below rule because there may be other dependencies +# and only the "main" rc must be passed to the resource compiler +$(TMP_DIR)\$(PROJECT).res: $(RCDIR)\$(PROJECT).rc + $(RESCMD) $(RCDIR)\$(PROJECT).rc + !else -MSG =^ -Failed to find tk.h. The TKDIR macro does not appear correct. -!error $(MSG) -!endif -!endif + +# If parent makefile has not defined a resource definition file, +# we will generate one from standard template. +$(TMP_DIR)\$(PROJECT).res: $(TMP_DIR)\$(PROJECT).rc + +$(TMP_DIR)\$(PROJECT).rc: + @$(COPY) << $(TMP_DIR)\$(PROJECT).rc +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION COMMAVERSION + PRODUCTVERSION COMMAVERSION + FILEFLAGSMASK 0x3fL +#ifdef DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Tcl extension " PROJECT + VALUE "OriginalFilename", PRJLIBNAME + VALUE "FileVersion", DOTVERSION + VALUE "ProductName", "Package " PROJECT " for Tcl" + VALUE "ProductVersion", DOTVERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +<< + +!endif # ifdef RCFILE + +!ifndef DISABLE_IMPLICIT_RULES +DISABLE_IMPLICIT_RULES = 0 !endif -#------------------------------------------------------------------------- -# Extract Tk version numbers -#------------------------------------------------------------------------- +!if !$(DISABLE_IMPLICIT_RULES) +# Implicit rule definitions - only for building library objects. For stubs and +# main application, the makefile should define explicit rules. -!if defined(PROJECT_REQUIRES_TK) || "$(PROJECT)" == "tk" +{$(ROOT)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(WIN_DIR)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(GENERICDIR)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(COMPATDIR)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(RCDIR)}.rc{$(TMP_DIR)}.res: + $(RESCMD) $< + +{$(WIN_DIR)}.rc{$(TMP_DIR)}.res: + $(RESCMD) $< + +{$(TMP_DIR)}.rc{$(TMP_DIR)}.res: + $(RESCMD) $< + +.SUFFIXES: +.SUFFIXES:.c .rc -!if [echo TK_MAJOR_VERSION = \>> versions.vc] \ - && [nmakehlp -V $(_TK_H) TK_MAJOR_VERSION >> versions.vc] !endif -!if [echo TK_MINOR_VERSION = \>> versions.vc] \ - && [nmakehlp -V $(_TK_H) TK_MINOR_VERSION >> versions.vc] + +################################################################ +# 14. Sanity check selected options against Tcl build options +# When building an extension, certain configuration options should +# match the ones used when Tcl was built. Here we check and +# warn on a mismatch. +!if !$(DOING_TCL) + +!if $(TCLINSTALL) # Building against an installed Tcl +!if exist("$(_TCLDIR)\lib\nmake\tcl.nmake") +TCLNMAKECONFIG = "$(_TCLDIR)\lib\nmake\tcl.nmake" !endif -!if [echo TK_PATCH_LEVEL = \>> versions.vc] \ - && [nmakehlp -V $(_TK_H) TK_PATCH_LEVEL >> versions.vc] +!else # !$(TCLINSTALL) - building against Tcl source +!if exist("$(_TCLDIR)\win\$(BUILDDIRTOP)\tcl.nmake") +TCLNMAKECONFIG = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tcl.nmake" !endif +!endif # TCLINSTALL -!include versions.vc - -TK_DOTVERSION = $(TK_MAJOR_VERSION).$(TK_MINOR_VERSION) -TK_VERSION = $(TK_MAJOR_VERSION)$(TK_MINOR_VERSION) +!if $(CONFIG_CHECK) +!ifdef TCLNMAKECONFIG +!include $(TCLNMAKECONFIG) -!if "$(PROJECT)" != "tk" -!if $(TKINSTALL) -WISH = "$(_TKDIR)\bin\wish$(TK_VERSION)$(SUFX).exe" -TKSTUBLIB = "$(_TKDIR)\lib\tkstub$(TK_VERSION).lib" -TKIMPLIB = "$(_TKDIR)\lib\tk$(TK_VERSION)$(SUFX).lib" -TK_INCLUDES = -I"$(_TKDIR)\include" -!else -WISH = "$(_TKDIR)\win\$(BUILDDIRTOP)\wish$(TCL_VERSION)$(SUFX).exe" -TKSTUBLIB = "$(_TKDIR)\win\$(BUILDDIRTOP)\tkstub$(TCL_VERSION).lib" -TKIMPLIB = "$(_TKDIR)\win\$(BUILDDIRTOP)\tk$(TCL_VERSION)$(SUFX).lib" -TK_INCLUDES = -I"$(_TKDIR)\generic" -I"$(_TKDIR)\win" -I"$(_TKDIR)\xlib" +!if defined(CORE_MACHINE) && "$(CORE_MACHINE)" != "$(MACHINE)" +!error ERROR: Build target ($(MACHINE)) does not match the Tcl library architecture ($(CORE_MACHINE)). !endif +!if $(TCL_VERSION) < 87 && defined(CORE_USE_THREAD_ALLOC) && $(CORE_USE_THREAD_ALLOC) != $(USE_THREAD_ALLOC) +!message WARNING: Value of USE_THREAD_ALLOC ($(USE_THREAD_ALLOC)) does not match its Tcl core value ($(CORE_USE_THREAD_ALLOC)). !endif - +!if defined(CORE_DEBUG) && $(CORE_DEBUG) != $(DEBUG) +!message WARNING: Value of DEBUG ($(DEBUG)) does not match its Tcl library configuration ($(DEBUG)). !endif +!endif + +!endif # TCLNMAKECONFIG + +!endif # !$(DOING_TCL) + #---------------------------------------------------------- # Display stats being used. #---------------------------------------------------------- +!if !$(DOING_TCL) +!message *** Building against Tcl at '$(_TCLDIR)' +!endif +!if !$(DOING_TK) && $(NEED_TK) +!message *** Building against Tk at '$(_TKDIR)' +!endif !message *** Intermediate directory will be '$(TMP_DIR)' !message *** Output directory will be '$(OUT_DIR)' +!message *** Installation, if selected, will be in '$(_INSTALLDIR)' !message *** Suffix for binaries will be '$(SUFX)' -!message *** Optional defines are '$(OPTDEFINES)' -!message *** Compiler version $(VCVER). Target machine is $(MACHINE) -!message *** Host architecture is $(NATIVE_ARCH) -!message *** Compiler options '$(COMPILERFLAGS) $(OPTIMIZATIONS) $(DEBUGFLAGS) $(WARNINGS)' -!message *** Link options '$(LINKERFLAGS)' - -!endif +!message *** Compiler version $(VCVER). Target $(MACHINE), host $(NATIVE_ARCH). +!endif # ifdef _RULES_VC diff --git a/autoconf/tea/win/targets.vc b/autoconf/tea/win/targets.vc new file mode 100644 index 000000000..49ed7d4a4 --- /dev/null +++ b/autoconf/tea/win/targets.vc @@ -0,0 +1,98 @@ +#------------------------------------------------------------- -*- makefile -*- +# targets.vc -- +# +# Part of the nmake based build system for Tcl and its extensions. +# This file defines some standard targets for the convenience of extensions +# and can be optionally included by the extension makefile. +# See TIP 477 (https://core.tcl-lang.org/tips/doc/main/tip/477.md) for docs. + +$(PROJECT): setup pkgindex $(PRJLIB) + +!ifdef PRJ_STUBOBJS +$(PROJECT): $(PRJSTUBLIB) +$(PRJSTUBLIB): $(PRJ_STUBOBJS) + $(LIBCMD) $** + +$(PRJ_STUBOBJS): + $(CCSTUBSCMD) %s +!endif # PRJ_STUBOBJS + +!ifdef PRJ_MANIFEST +$(PROJECT): $(PRJLIB).manifest +$(PRJLIB).manifest: $(PRJ_MANIFEST) + @nmakehlp -s << $** >$@ +@MACHINE@ $(MACHINE:IX86=X86) +<< +!endif + +!if "$(PROJECT)" != "tcl" && "$(PROJECT)" != "tk" +$(PRJLIB): $(PRJ_OBJS) $(RESFILE) +!if $(STATIC_BUILD) + $(LIBCMD) $** +!else + $(DLLCMD) $** + $(_VC_MANIFEST_EMBED_DLL) +!endif + -@del $*.exp +!endif + +!if "$(PRJ_HEADERS)" != "" && "$(PRJ_OBJS)" != "" +$(PRJ_OBJS): $(PRJ_HEADERS) +!endif + +# If parent makefile has defined stub objects, add their installation +# to the default install +!if "$(PRJ_STUBOBJS)" != "" +default-install: default-install-stubs +!endif + +# Unlike the other default targets, these cannot be in rules.vc because +# the executed command depends on existence of macro PRJ_HEADERS_PUBLIC +# that the parent makefile will not define until after including rules-ext.vc +!if "$(PRJ_HEADERS_PUBLIC)" != "" +default-install: default-install-headers +default-install-headers: + @echo Installing headers to '$(INCLUDE_INSTALL_DIR)' + @for %f in ($(PRJ_HEADERS_PUBLIC)) do @$(COPY) %f "$(INCLUDE_INSTALL_DIR)" +!endif + +!if "$(DISABLE_STANDARD_TARGETS)" == "" +DISABLE_STANDARD_TARGETS = 0 +!endif + +!if "$(DISABLE_TARGET_setup)" == "" +DISABLE_TARGET_setup = 0 +!endif +!if "$(DISABLE_TARGET_install)" == "" +DISABLE_TARGET_install = 0 +!endif +!if "$(DISABLE_TARGET_clean)" == "" +DISABLE_TARGET_clean = 0 +!endif +!if "$(DISABLE_TARGET_test)" == "" +DISABLE_TARGET_test = 0 +!endif +!if "$(DISABLE_TARGET_shell)" == "" +DISABLE_TARGET_shell = 0 +!endif + +!if !$(DISABLE_STANDARD_TARGETS) +!if !$(DISABLE_TARGET_setup) +setup: default-setup +!endif +!if !$(DISABLE_TARGET_install) +install: default-install +!endif +!if !$(DISABLE_TARGET_clean) +clean: default-clean +realclean: hose +hose: default-hose +distclean: realclean default-distclean +!endif +!if !$(DISABLE_TARGET_test) +test: default-test +!endif +!if !$(DISABLE_TARGET_shell) +shell: default-shell +!endif +!endif # DISABLE_STANDARD_TARGETS diff --git a/configure b/configure index 5ad0ccad3..0ddfa67aa 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.69 for sqlite 3.46.1. +# Generated by GNU Autoconf 2.69 for sqlite 3.47.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.46.1' -PACKAGE_STRING='sqlite 3.46.1' +PACKAGE_VERSION='3.47.0' +PACKAGE_STRING='sqlite 3.47.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -781,18 +781,6 @@ TARGET_HAVE_EDITLINE TARGET_HAVE_READLINE TARGET_READLINE_INC TARGET_READLINE_LIBS -HAVE_TCL -TCL_SHLIB_SUFFIX -TCL_STUB_LIB_SPEC -TCL_STUB_LIB_FLAG -TCL_STUB_LIB_FILE -TCL_LIB_SPEC -TCL_LIB_FLAG -TCL_LIB_FILE -TCL_INCLUDE_SPEC -TCL_SRC_DIR -TCL_BIN_DIR -TCL_VERSION TARGET_EXEEXT SQLITE_OS_WIN SQLITE_OS_UNIX @@ -805,7 +793,12 @@ HAVE_WASI_SDK RELEASE VERSION program_prefix +TSTRNNR_OPTS TCLLIBDIR +HAVE_TCL +TCL_STUB_LIB_SPEC +TCL_LIB_SPEC +TCL_INCLUDE_SPEC TCLSH_CMD INSTALL_DATA INSTALL_SCRIPT @@ -894,12 +887,14 @@ enable_fast_install with_gnu_ld enable_libtool_lock enable_largefile +with_tclsh +with_tcl +enable_tcl +enable_test_status with_wasi_sdk enable_threadsafe enable_releasemode enable_tempstore -enable_tcl -with_tcl enable_editline enable_readline with_readline_lib @@ -1472,7 +1467,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.46.1 to adapt to many kinds of systems. +\`configure' configures sqlite 3.47.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1537,7 +1532,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.46.1:";; + short | recursive ) echo "Configuration of sqlite 3.47.0:";; esac cat <<\_ACEOF @@ -1551,11 +1546,13 @@ Optional Features: optimize for fast installation [default=yes] --disable-libtool-lock avoid locking (might break parallel builds) --disable-largefile omit support for large files + --disable-tcl omit building accessory programs that require + TCL-dev + --enable-test-status Full-screen status of tests --disable-threadsafe Disable mutexing --enable-releasemode Support libtool link to release mode --enable-tempstore Use an in-ram database for temporary tables (never,no,yes,always) - --disable-tcl do not build TCL extension --enable-editline enable BSD editline support --disable-readline disable readline support --enable-debug enable debugging & verbose explain @@ -1583,10 +1580,10 @@ Optional Packages: --with-pic try to use only PIC/non-PIC objects [default=use both] --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-tclsh=PATHNAME full pathname of a tclsh to use + --with-tcl=DIR directory containing (tclConfig.sh) --with-wasi-sdk=DIR directory containing the WASI SDK. Triggers cross-compile to WASM. - --with-tcl=DIR directory containing tcl configuration - (tclConfig.sh) --with-readline-lib specify readline library --with-readline-inc specify readline include paths --with-linenoise=DIR source directory for linenoise library @@ -1668,7 +1665,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.46.1 +sqlite configure 3.47.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2087,7 +2084,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.46.1, which was +It was created by sqlite $as_me 3.47.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3945,13 +3942,13 @@ if ${lt_cv_nm_interface+:} false; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3948: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3945: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3951: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3948: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3954: output\"" >&5) + (eval echo "\"\$as_me:3951: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -5157,7 +5154,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5160 "configure"' > conftest.$ac_ext + echo '#line 5157 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6682,11 +6679,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:6685: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6682: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6689: \$? = $ac_status" >&5 + echo "$as_me:6686: \$? = $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. @@ -7021,11 +7018,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:7024: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7021: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7028: \$? = $ac_status" >&5 + echo "$as_me:7025: \$? = $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. @@ -7126,11 +7123,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:7129: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7126: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7133: \$? = $ac_status" >&5 + echo "$as_me:7130: \$? = $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 @@ -7181,11 +7178,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:7184: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7181: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7188: \$? = $ac_status" >&5 + echo "$as_me:7185: \$? = $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 @@ -9561,7 +9558,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9564 "configure" +#line 9561 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -9657,7 +9654,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9660 "configure" +#line 9657 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -10314,11 +10311,31 @@ done USE_AMALGAMATION=1 ######### -# See whether we can run specific tclsh versions known to work well; -# if not, then we fall back to plain tclsh. -# TODO: try other versions before falling back? +# Figure out all the name of a working tclsh and parameters needed to compile against Tcl. +# The --with-tcl= and/or --with-tclsh= configuration arguments might be useful for this. # -for ac_prog in tclsh8.7 tclsh8.6 tclsh8.5 tclsh + +# Check whether --with-tclsh was given. +if test "${with_tclsh+set}" = set; then : + withval=$with_tclsh; +fi + + +# Check whether --with-tcl was given. +if test "${with_tcl+set}" = set; then : + withval=$with_tcl; +fi + +# Check whether --enable-tcl was given. +if test "${enable_tcl+set}" = set; then : + enableval=$enable_tcl; use_tcl=$enableval +else + use_tcl=yes +fi + +original_use_tcl=${use_tcl} +if test x"${with_tclsh}" == x -a x"${with_tcl}" == x; then + for ac_prog in tclsh9.0 tclsh8.6 tclsh do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 @@ -10361,10 +10378,99 @@ fi done test -n "$TCLSH_CMD" || TCLSH_CMD="none" + with_tclsh=${TCLSH_CMD} +fi +if test x"${with_tclsh}" != x -a x"${with_tclsh}" != xnone; then + TCLSH_CMD=${with_tclsh} + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using tclsh at \"$TCLSH_CMD\"" >&5 +$as_echo "using tclsh at \"$TCLSH_CMD\"" >&6; } + if test x"${use_tcl}" = "xyes"; then + with_tcl=`${with_tclsh} <${srcdir}/tool/find_tclconfig.tcl` + if test x"${with_tcl}" != x; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TCLSH_CMD recommends the tclConfig.sh at ${with_tcl}" >&5 +$as_echo "$TCLSH_CMD recommends the tclConfig.sh at ${with_tcl}" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $TCLSH_CMD is unable to recommend a tclConfig.sh" >&5 +$as_echo "$as_me: WARNING: $TCLSH_CMD is unable to recommend a tclConfig.sh" >&2;} + use_tcl=no + fi + fi +fi +if test x"${use_tcl}" = "xyes"; then + if test x"${with_tcl}" != x; then + if test -r ${with_tcl}/tclConfig.sh; then + tclconfig="${with_tcl}/tclConfig.sh" + else + for i in tcl8.6 tcl9.0 lib; do + if test -r ${with_tcl}/$i/tclConfig.sh; then + tclconfig=${with_tcl}/$i/tclConfig.sh + break + fi + done + fi + if test ! -r "${tclconfig}"; then + as_fn_error $? "no tclConfig.sh file found under ${with_tcl}" "$LINENO" 5 + fi + else + # If we have not yet found a tclConfig.sh file, look in $libdir whic is + # set automatically by autoconf or by the --prefix command-line option. + # See https://sqlite.org/forum/forumpost/e04e693439a22457 + libdir=${prefix}/lib + if test -r ${libdir}/tclConfig.sh; then + tclconfig=${libdir}/tclConfig.sh + else + for i in tcl8.6 tcl9.0 lib; do + if test -r ${libdir}/$i/tclConfig.sh; then + tclconfig=${libdir}/$i/tclConfig.sh + break + fi + done + fi + if test ! -r "${tclconfig}"; then + as_fn_error $? "cannot find a usable tclConfig.sh file. + Use --with-tcl=DIR to specify a directory where tclConfig.sh can be found. + SQLite does not use TCL internally, but TCL is required to build SQLite + from canonical sources and TCL is required for testing." "$LINENO" 5 + fi + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: loading TCL configuration from ${tclconfig}" >&5 +$as_echo "loading TCL configuration from ${tclconfig}" >&6; } + . ${tclconfig} + + + + # There are lots of other configuration variables that are provided by the + # tclConfig.sh file and that could be included here. But as of right now, + # TCL_LIB_SPEC is the only what that the Makefile uses. + HAVE_TCL=1 +elif test x"${original_use_tcl}" = "xno"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unable to run tests because of --disable-tcl" >&5 +$as_echo "unable to run tests because of --disable-tcl" >&6; } + HAVE_TCL=0 +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unable to run tests because no tclConfig.sh file could be located" >&5 +$as_echo "unable to run tests because no tclConfig.sh file could be located" >&6; } + HAVE_TCL=0 +fi + +if test x"$TCLSH_CMD" == x; then + TCLSH_CMD=${TCL_EXEC_PREFIX}/bin/tclsh${TCL_VERSION} + if test ! -x ${TCLSH_CMD}; then + TCLSH_CMD_2=${TCL_EXEC_PREFIX}/bin/tclsh + if test ! -x ${TCLSH_CMD_2}; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cannot find a usable tclsh at either ${TCLSH_CMD} or ${TCLSH_CMD_2}" >&5 +$as_echo "$as_me: WARNING: cannot find a usable tclsh at either ${TCLSH_CMD} or ${TCLSH_CMD_2}" >&2;} + TCLSH_CMD=none + else + TCLSH_CMD=${TCLSH_CMD_2} + fi + fi +fi if test "$TCLSH_CMD" = "none"; then # If we can't find a local tclsh, then building the amalgamation will fail. # We act as though --disable-amalgamation has been used. - echo "Warning: can't find tclsh - defaulting to non-amalgamation build." + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Warning: can't find tclsh - defaulting to non-amalgamation build." >&5 +$as_echo "$as_me: WARNING: Warning: can't find tclsh - defaulting to non-amalgamation build." >&2;} USE_AMALGAMATION=0 TCLSH_CMD="tclsh" fi @@ -10372,7 +10478,6 @@ fi if test "x${TCLLIBDIR+set}" != "xset" ; then - TCLLIBDIR='$(libdir)' for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD}` ; do if test -d $i ; then TCLLIBDIR=$i @@ -10382,6 +10487,23 @@ if test "x${TCLLIBDIR+set}" != "xset" ; then TCLLIBDIR="${TCLLIBDIR}/sqlite3" fi +######### +# Set up options for running tests. +# +# Check whether --enable-test-status was given. +if test "${enable_test_status+set}" = set; then : + enableval=$enable_test_status; use_vt100=$enableval +else + use_vt100=no +fi + +if test $use_vt100 != no; then + TSTRNNR_OPTS=--status +else + TSTRNNR_OPTS= +fi + + ######### # Set up an appropriate program prefix @@ -10769,213 +10891,6 @@ fi -########## -# Figure out all the parameters needed to compile against Tcl. -# -# This code is derived from the SC_PATH_TCLCONFIG and SC_LOAD_TCLCONFIG -# macros in the in the tcl.m4 file of the standard TCL distribution. -# Those macros could not be used directly since we have to make some -# minor changes to accomodate systems that do not have TCL installed. -# -# Check whether --enable-tcl was given. -if test "${enable_tcl+set}" = set; then : - enableval=$enable_tcl; use_tcl=$enableval -else - use_tcl=yes -fi - -if test "${use_tcl}" = "yes" ; then - -# Check whether --with-tcl was given. -if test "${with_tcl+set}" = set; then : - withval=$with_tcl; with_tclconfig=${withval} -fi - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Tcl configuration" >&5 -$as_echo_n "checking for Tcl configuration... " >&6; } - if ${ac_cv_c_tclconfig+:} false; then : - $as_echo_n "(cached) " >&6 -else - - # First check to see if --with-tcl was specified. - if test x"${with_tclconfig}" != x ; then - if test -f "${with_tclconfig}/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd ${with_tclconfig}; pwd)` - else - as_fn_error $? "${with_tclconfig} directory doesn't contain tclConfig.sh" "$LINENO" 5 - fi - fi - - # Start autosearch by asking tclsh - if test x"${ac_cv_c_tclconfig}" = x ; then - if test x"$cross_compiling" = xno; then - for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD}` - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig="$i" - break - fi - done - fi - fi - - # On ubuntu 14.10, $auto_path on tclsh is not quite correct. - # So try again after applying corrections. - if test x"${ac_cv_c_tclconfig}" = x ; then - if test x"$cross_compiling" = xno; then - for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD} | sed 's,/tcltk/tcl,/tcl,g'` - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig="$i" - break - fi - done - fi - fi - - # Recent versions of Xcode on Macs hid the tclConfig.sh file - # in a strange place. - if test x"${ac_cv_c_tclconfig}" = x ; then - if test x"$cross_compiling" = xno; then - for i in /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX*.sdk/usr/lib - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig="$i" - break - fi - done - fi - fi - - # then check for a private Tcl installation - if test x"${ac_cv_c_tclconfig}" = x ; then - for i in \ - ../tcl \ - `ls -dr ../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ - `ls -dr ../tcl[8-9].[0-9] 2>/dev/null` \ - `ls -dr ../tcl[8-9].[0-9]* 2>/dev/null` \ - ../../tcl \ - `ls -dr ../../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ - `ls -dr ../../tcl[8-9].[0-9] 2>/dev/null` \ - `ls -dr ../../tcl[8-9].[0-9]* 2>/dev/null` \ - ../../../tcl \ - `ls -dr ../../../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ - `ls -dr ../../../tcl[8-9].[0-9] 2>/dev/null` \ - `ls -dr ../../../tcl[8-9].[0-9]* 2>/dev/null` - do - if test -f "$i/unix/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd $i/unix; pwd)` - break - fi - done - fi - - # check in a few common install locations - if test x"${ac_cv_c_tclconfig}" = x ; then - for i in \ - `ls -d ${libdir} 2>/dev/null` \ - `ls -d /usr/local/lib 2>/dev/null` \ - `ls -d /usr/contrib/lib 2>/dev/null` \ - `ls -d /usr/lib 2>/dev/null` - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd $i; pwd)` - break - fi - done - fi - - # check in a few other private locations - if test x"${ac_cv_c_tclconfig}" = x ; then - for i in \ - ${srcdir}/../tcl \ - `ls -dr ${srcdir}/../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ - `ls -dr ${srcdir}/../tcl[8-9].[0-9] 2>/dev/null` \ - `ls -dr ${srcdir}/../tcl[8-9].[0-9]* 2>/dev/null` - do - if test -f "$i/unix/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd $i/unix; pwd)` - break - fi - done - fi - -fi - - - if test x"${ac_cv_c_tclconfig}" = x ; then - use_tcl=no - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Can't find Tcl configuration definitions" >&5 -$as_echo "$as_me: WARNING: Can't find Tcl configuration definitions" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** Without Tcl the regression tests cannot be executed ***" >&5 -$as_echo "$as_me: WARNING: *** Without Tcl the regression tests cannot be executed ***" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** Consider using --with-tcl=... to define location of Tcl ***" >&5 -$as_echo "$as_me: WARNING: *** Consider using --with-tcl=... to define location of Tcl ***" >&2;} - else - TCL_BIN_DIR=${ac_cv_c_tclconfig} - { $as_echo "$as_me:${as_lineno-$LINENO}: result: found $TCL_BIN_DIR/tclConfig.sh" >&5 -$as_echo "found $TCL_BIN_DIR/tclConfig.sh" >&6; } - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for existence of $TCL_BIN_DIR/tclConfig.sh" >&5 -$as_echo_n "checking for existence of $TCL_BIN_DIR/tclConfig.sh... " >&6; } - if test -f "$TCL_BIN_DIR/tclConfig.sh" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: loading" >&5 -$as_echo "loading" >&6; } - . $TCL_BIN_DIR/tclConfig.sh - else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: file not found" >&5 -$as_echo "file not found" >&6; } - fi - - # - # If the TCL_BIN_DIR is the build directory (not the install directory), - # then set the common variable name to the value of the build variables. - # For example, the variable TCL_LIB_SPEC will be set to the value - # of TCL_BUILD_LIB_SPEC. An extension should make use of TCL_LIB_SPEC - # instead of TCL_BUILD_LIB_SPEC since it will work with both an - # installed and uninstalled version of Tcl. - # - - if test -f $TCL_BIN_DIR/Makefile ; then - TCL_LIB_SPEC=${TCL_BUILD_LIB_SPEC} - TCL_STUB_LIB_SPEC=${TCL_BUILD_STUB_LIB_SPEC} - TCL_STUB_LIB_PATH=${TCL_BUILD_STUB_LIB_PATH} - fi - - # - # eval is required to do the TCL_DBGX substitution - # - - eval "TCL_LIB_FILE=\"${TCL_LIB_FILE}\"" - eval "TCL_LIB_FLAG=\"${TCL_LIB_FLAG}\"" - eval "TCL_LIB_SPEC=\"${TCL_LIB_SPEC}\"" - - eval "TCL_STUB_LIB_FILE=\"${TCL_STUB_LIB_FILE}\"" - eval "TCL_STUB_LIB_FLAG=\"${TCL_STUB_LIB_FLAG}\"" - eval "TCL_STUB_LIB_SPEC=\"${TCL_STUB_LIB_SPEC}\"" - - - - - - - - - - - - - - - fi -fi -if test "${use_tcl}" = "no" ; then - HAVE_TCL="" -else - HAVE_TCL=1 -fi - - ########## # Figure out what C libraries are required to compile programs # that use "readline()" library. @@ -12481,7 +12396,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=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.46.1, which was +This file was extended by sqlite $as_me 3.47.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12547,7 +12462,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.46.1 +sqlite config.status 3.47.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 54f985fd7..f2ec22aae 100644 --- a/configure.ac +++ b/configure.ac @@ -116,15 +116,101 @@ AC_CHECK_FUNCS([fdatasync gmtime_r isnan localtime_r localtime_s malloc_usable_s USE_AMALGAMATION=1 ######### -# See whether we can run specific tclsh versions known to work well; -# if not, then we fall back to plain tclsh. -# TODO: try other versions before falling back? -# -AC_CHECK_PROGS(TCLSH_CMD, [tclsh8.7 tclsh8.6 tclsh8.5 tclsh], none) +# Figure out all the name of a working tclsh and parameters needed to compile against Tcl. +# The --with-tcl= and/or --with-tclsh= configuration arguments might be useful for this. +# +AC_ARG_WITH(tclsh, AS_HELP_STRING([--with-tclsh=PATHNAME],[full pathname of a tclsh to use])) +AC_ARG_WITH(tcl, AS_HELP_STRING([--with-tcl=DIR],[directory containing (tclConfig.sh)])) +AC_ARG_ENABLE(tcl, AS_HELP_STRING([--disable-tcl],[omit building accessory programs that require TCL-dev]), + [use_tcl=$enableval],[use_tcl=yes]) +original_use_tcl=${use_tcl} +if test x"${with_tclsh}" == x -a x"${with_tcl}" == x; then + AC_CHECK_PROGS(TCLSH_CMD, [tclsh9.0 tclsh8.6 tclsh],none) + with_tclsh=${TCLSH_CMD} +fi +if test x"${with_tclsh}" != x -a x"${with_tclsh}" != xnone; then + TCLSH_CMD=${with_tclsh} + AC_MSG_RESULT([using tclsh at "$TCLSH_CMD"]) + if test x"${use_tcl}" = "xyes"; then + with_tcl=`${with_tclsh} <${srcdir}/tool/find_tclconfig.tcl` + if test x"${with_tcl}" != x; then + AC_MSG_RESULT([$TCLSH_CMD recommends the tclConfig.sh at ${with_tcl}]) + else + AC_MSG_WARN([$TCLSH_CMD is unable to recommend a tclConfig.sh]) + use_tcl=no + fi + fi +fi +if test x"${use_tcl}" = "xyes"; then + if test x"${with_tcl}" != x; then + if test -r ${with_tcl}/tclConfig.sh; then + tclconfig="${with_tcl}/tclConfig.sh" + else + for i in tcl8.6 tcl9.0 lib; do + if test -r ${with_tcl}/$i/tclConfig.sh; then + tclconfig=${with_tcl}/$i/tclConfig.sh + break + fi + done + fi + if test ! -r "${tclconfig}"; then + AC_MSG_ERROR([no tclConfig.sh file found under ${with_tcl}]) + fi + else + # If we have not yet found a tclConfig.sh file, look in $libdir whic is + # set automatically by autoconf or by the --prefix command-line option. + # See https://sqlite.org/forum/forumpost/e04e693439a22457 + libdir=${prefix}/lib + if test -r ${libdir}/tclConfig.sh; then + tclconfig=${libdir}/tclConfig.sh + else + for i in tcl8.6 tcl9.0 lib; do + if test -r ${libdir}/$i/tclConfig.sh; then + tclconfig=${libdir}/$i/tclConfig.sh + break + fi + done + fi + if test ! -r "${tclconfig}"; then + AC_MSG_ERROR([cannot find a usable tclConfig.sh file. + Use --with-tcl=DIR to specify a directory where tclConfig.sh can be found. + SQLite does not use TCL internally, but TCL is required to build SQLite + from canonical sources and TCL is required for testing.]) + fi + fi + AC_MSG_RESULT([loading TCL configuration from ${tclconfig}]) + . ${tclconfig} + AC_SUBST(TCL_INCLUDE_SPEC) + AC_SUBST(TCL_LIB_SPEC) + AC_SUBST(TCL_STUB_LIB_SPEC) + # There are lots of other configuration variables that are provided by the + # tclConfig.sh file and that could be included here. But as of right now, + # TCL_LIB_SPEC is the only what that the Makefile uses. + HAVE_TCL=1 +elif test x"${original_use_tcl}" = "xno"; then + AC_MSG_RESULT([unable to run tests because of --disable-tcl]) + HAVE_TCL=0 +else + AC_MSG_RESULT([unable to run tests because no tclConfig.sh file could be located]) + HAVE_TCL=0 +fi +AC_SUBST(HAVE_TCL) +if test x"$TCLSH_CMD" == x; then + TCLSH_CMD=${TCL_EXEC_PREFIX}/bin/tclsh${TCL_VERSION} + if test ! -x ${TCLSH_CMD}; then + TCLSH_CMD_2=${TCL_EXEC_PREFIX}/bin/tclsh + if test ! -x ${TCLSH_CMD_2}; then + AC_MSG_WARN([cannot find a usable tclsh at either ${TCLSH_CMD} or ${TCLSH_CMD_2}]) + TCLSH_CMD=none + else + TCLSH_CMD=${TCLSH_CMD_2} + fi + fi +fi if test "$TCLSH_CMD" = "none"; then # If we can't find a local tclsh, then building the amalgamation will fail. # We act as though --disable-amalgamation has been used. - echo "Warning: can't find tclsh - defaulting to non-amalgamation build." + AC_MSG_WARN([Warning: can't find tclsh - defaulting to non-amalgamation build.]) USE_AMALGAMATION=0 TCLSH_CMD="tclsh" fi @@ -132,7 +218,6 @@ AC_SUBST(TCLSH_CMD) AC_ARG_VAR([TCLLIBDIR], [Where to install tcl plugin]) if test "x${TCLLIBDIR+set}" != "xset" ; then - TCLLIBDIR='$(libdir)' for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD}` ; do if test -d $i ; then TCLLIBDIR=$i @@ -142,6 +227,18 @@ if test "x${TCLLIBDIR+set}" != "xset" ; then TCLLIBDIR="${TCLLIBDIR}/sqlite3" fi +######### +# Set up options for running tests. +# +AC_ARG_ENABLE(test-status, AS_HELP_STRING([--enable-test-status],[Full-screen status of tests]), + [use_vt100=$enableval],[use_vt100=no]) +if test $use_vt100 != no; then + TSTRNNR_OPTS=--status +else + TSTRNNR_OPTS= +fi +AC_SUBST(TSTRNNR_OPTS) + ######### # Set up an appropriate program prefix @@ -339,190 +436,6 @@ AC_SUBST(SQLITE_OS_UNIX) AC_SUBST(SQLITE_OS_WIN) AC_SUBST(TARGET_EXEEXT) -########## -# Figure out all the parameters needed to compile against Tcl. -# -# This code is derived from the SC_PATH_TCLCONFIG and SC_LOAD_TCLCONFIG -# macros in the in the tcl.m4 file of the standard TCL distribution. -# Those macros could not be used directly since we have to make some -# minor changes to accomodate systems that do not have TCL installed. -# -AC_ARG_ENABLE(tcl, AS_HELP_STRING([--disable-tcl],[do not build TCL extension]), - [use_tcl=$enableval],[use_tcl=yes]) -if test "${use_tcl}" = "yes" ; then - AC_ARG_WITH(tcl, AS_HELP_STRING([--with-tcl=DIR],[directory containing tcl configuration (tclConfig.sh)]), with_tclconfig=${withval}) - AC_MSG_CHECKING([for Tcl configuration]) - AC_CACHE_VAL(ac_cv_c_tclconfig,[ - # First check to see if --with-tcl was specified. - if test x"${with_tclconfig}" != x ; then - if test -f "${with_tclconfig}/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd ${with_tclconfig}; pwd)` - else - AC_MSG_ERROR([${with_tclconfig} directory doesn't contain tclConfig.sh]) - fi - fi - - # Start autosearch by asking tclsh - if test x"${ac_cv_c_tclconfig}" = x ; then - if test x"$cross_compiling" = xno; then - for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD}` - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig="$i" - break - fi - done - fi - fi - - # On ubuntu 14.10, $auto_path on tclsh is not quite correct. - # So try again after applying corrections. - if test x"${ac_cv_c_tclconfig}" = x ; then - if test x"$cross_compiling" = xno; then - for i in `echo 'puts stdout $auto_path' | ${TCLSH_CMD} | sed 's,/tcltk/tcl,/tcl,g'` - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig="$i" - break - fi - done - fi - fi - - # Recent versions of Xcode on Macs hid the tclConfig.sh file - # in a strange place. - if test x"${ac_cv_c_tclconfig}" = x ; then - if test x"$cross_compiling" = xno; then - for i in /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX*.sdk/usr/lib - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig="$i" - break - fi - done - fi - fi - - # then check for a private Tcl installation - if test x"${ac_cv_c_tclconfig}" = x ; then - for i in \ - ../tcl \ - `ls -dr ../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ - `ls -dr ../tcl[[8-9]].[[0-9]] 2>/dev/null` \ - `ls -dr ../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ - ../../tcl \ - `ls -dr ../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ - `ls -dr ../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ - `ls -dr ../../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ - ../../../tcl \ - `ls -dr ../../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ - `ls -dr ../../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ - `ls -dr ../../../tcl[[8-9]].[[0-9]]* 2>/dev/null` - do - if test -f "$i/unix/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd $i/unix; pwd)` - break - fi - done - fi - - # check in a few common install locations - if test x"${ac_cv_c_tclconfig}" = x ; then - for i in \ - `ls -d ${libdir} 2>/dev/null` \ - `ls -d /usr/local/lib 2>/dev/null` \ - `ls -d /usr/contrib/lib 2>/dev/null` \ - `ls -d /usr/lib 2>/dev/null` - do - if test -f "$i/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd $i; pwd)` - break - fi - done - fi - - # check in a few other private locations - if test x"${ac_cv_c_tclconfig}" = x ; then - for i in \ - ${srcdir}/../tcl \ - `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ - `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]] 2>/dev/null` \ - `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]]* 2>/dev/null` - do - if test -f "$i/unix/tclConfig.sh" ; then - ac_cv_c_tclconfig=`(cd $i/unix; pwd)` - break - fi - done - fi - ]) - - if test x"${ac_cv_c_tclconfig}" = x ; then - use_tcl=no - AC_MSG_WARN(Can't find Tcl configuration definitions) - AC_MSG_WARN(*** Without Tcl the regression tests cannot be executed ***) - AC_MSG_WARN(*** Consider using --with-tcl=... to define location of Tcl ***) - else - TCL_BIN_DIR=${ac_cv_c_tclconfig} - AC_MSG_RESULT(found $TCL_BIN_DIR/tclConfig.sh) - - AC_MSG_CHECKING([for existence of $TCL_BIN_DIR/tclConfig.sh]) - if test -f "$TCL_BIN_DIR/tclConfig.sh" ; then - AC_MSG_RESULT([loading]) - . $TCL_BIN_DIR/tclConfig.sh - else - AC_MSG_RESULT([file not found]) - fi - - # - # If the TCL_BIN_DIR is the build directory (not the install directory), - # then set the common variable name to the value of the build variables. - # For example, the variable TCL_LIB_SPEC will be set to the value - # of TCL_BUILD_LIB_SPEC. An extension should make use of TCL_LIB_SPEC - # instead of TCL_BUILD_LIB_SPEC since it will work with both an - # installed and uninstalled version of Tcl. - # - - if test -f $TCL_BIN_DIR/Makefile ; then - TCL_LIB_SPEC=${TCL_BUILD_LIB_SPEC} - TCL_STUB_LIB_SPEC=${TCL_BUILD_STUB_LIB_SPEC} - TCL_STUB_LIB_PATH=${TCL_BUILD_STUB_LIB_PATH} - fi - - # - # eval is required to do the TCL_DBGX substitution - # - - eval "TCL_LIB_FILE=\"${TCL_LIB_FILE}\"" - eval "TCL_LIB_FLAG=\"${TCL_LIB_FLAG}\"" - eval "TCL_LIB_SPEC=\"${TCL_LIB_SPEC}\"" - - eval "TCL_STUB_LIB_FILE=\"${TCL_STUB_LIB_FILE}\"" - eval "TCL_STUB_LIB_FLAG=\"${TCL_STUB_LIB_FLAG}\"" - eval "TCL_STUB_LIB_SPEC=\"${TCL_STUB_LIB_SPEC}\"" - - AC_SUBST(TCL_VERSION) - AC_SUBST(TCL_BIN_DIR) - AC_SUBST(TCL_SRC_DIR) - AC_SUBST(TCL_INCLUDE_SPEC) - - AC_SUBST(TCL_LIB_FILE) - AC_SUBST(TCL_LIB_FLAG) - AC_SUBST(TCL_LIB_SPEC) - - AC_SUBST(TCL_STUB_LIB_FILE) - AC_SUBST(TCL_STUB_LIB_FLAG) - AC_SUBST(TCL_STUB_LIB_SPEC) - AC_SUBST(TCL_SHLIB_SUFFIX) - fi -fi -if test "${use_tcl}" = "no" ; then - HAVE_TCL="" -else - HAVE_TCL=1 -fi -AC_SUBST(HAVE_TCL) - ########## # Figure out what C libraries are required to compile programs # that use "readline()" library. diff --git a/doc/compile-for-unix.md b/doc/compile-for-unix.md new file mode 100644 index 000000000..a5caa8b1a --- /dev/null +++ b/doc/compile-for-unix.md @@ -0,0 +1,57 @@ +# Notes On Compiling SQLite On All Kinds Of Unix + +Here are step-by-step instructions on how to build SQLite from +canonical source on any modern machine that isn't Windows. These +notes are tested (on 2024-10-11) on Ubuntu and on MacOS, but they +are general and should work on most any modern unix platform. + + 1. Install a C-compiler. GCC or Clang both work fine. If you are + reading this document, you've probably already done that. + + 2. Install TCL9 development libraries. In this note, we'll do a + private install in the $HOME/local directory, but you can make + adjustments to install TCL9 wherever you like. +

    + This document assumes you are working with TCL version 9.0. +

      +
    1. Get the TCL source archive, perhaps from + + or . +
    2. Untar the source archive. CD into the "unix/" subfolder + of the source tree. +
    3. Run: `mkdir $HOME/local` +
    4. Run: `./configure --prefix=$HOME/local` +
    5. Run: `make install` +
    + + 4. Download the SQLite source tree and unpack it. CD into the + toplevel directory of the source tree. + + 5. Run: `./configure --enable-all --with-tclsh=$HOME/local/bin/tclsh9.0` + + You do not need to use --with-tclsh if the tclsh you want to use is the + first one on your PATH. + + 6. Run the "`Makefile`" makefile with an appropriate target. + Examples: +
      +
    • `make sqlite3.c` +
    • `make sqlite3` +
    • `make sqldiff` +
    • `make sqlite3_rsync` +
    • `make tclextension-install` +
    • `make devtest` +
    • `make releasetest` +
    • `make sqlite3_analyzer` +
    + + It is not required that you run the "tclextension-install" target prior to + running tests. However, the tests will run more smoothly if you do. + The version of SQLite used for the TCL extension does *not* need to + correspond to the version of SQLite under test. So you can install the + SQLite TCL extension once, and then use it to test many different versions + of SQLite. + + + 7. For a debugging build of the CLI, where the ".treetrace" and ".wheretrace" + commands work, add the the --enable-debug argument to configure. diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md index b8a50afb3..05893b56c 100644 --- a/doc/compile-for-windows.md +++ b/doc/compile-for-windows.md @@ -1,7 +1,7 @@ # Notes On Compiling SQLite On Windows 11 Here are step-by-step instructions on how to build SQLite from -canonical source on a new Windows 11 PC, as of 2023-11-01: +canonical source on a new Windows 11 PC, as of 2024-10-09: 1. Install Microsoft Visual Studio. The free "community edition" will work fine. Do a standard install for C++ development. @@ -20,23 +20,23 @@ canonical source on a new Windows 11 PC, as of 2023-11-01: install the TCL development libraries in the "`c:\Tcl`" directory. Make adjustments if you want TCL installed somewhere else. SQLite needs both the - "tclsh.exe" command-line tool as part of the build process, and - the "tcl86.lib" library in order to run tests. You will need - TCL version 8.6 or later. + "tclsh90.exe" command-line tool as part of the build process, and + the "tcl90.lib" and "tclstub.lib" libraries in order to run tests. + This document assumes you are working with TCL version 9.0. + See versions of this document from prior to 2024-10-10 for + instructions on how to build using TCL version 8.6.
    1. Get the TCL source archive, perhaps from - [https://www.tcl.tk/software/tcltk/download.html](https://www.tcl.tk/software/tcltk/download.html). + + or .
    2. Untar or unzip the source archive. CD into the "win/" subfolder of the source tree.
    3. Run: `nmake /f makefile.vc release`
    4. Run: `nmake /f makefile.vc INSTALLDIR=c:\Tcl install` -
    5. CD to `c:\Tcl\lib`. In that subfolder make a copy of the - "`tcl86t.lib`" file to the alternative name "`tcl86.lib`" - (omitting the second 't'). Leave the copy in the same directory - as the original. -
    6. CD to `c:\Tcl\bin`. Make a copy of the "`tclsh86t.exe`" - file into "`tclsh.exe`" (without the "86t") in the same directory. -
    7. Add `c:\Tcl\bin` to your %PATH%. To do this, go to Settings +
    8. Optional: CD to `c:\Tcl\bin` and make a copy of + `tclsh90.exe` over into just `tclsh.exe`. +
    9. Optional: + Add `c:\Tcl\bin` to your %PATH%. To do this, go to Settings and search for "path". Select "edit environment variables for your account" and modify your default PATH accordingly. You will need to close and reopen your command prompts after @@ -52,15 +52,41 @@ canonical source on a new Windows 11 PC, as of 2023-11-01:
    10. `set TCLDIR=c:\Tcl` + If you install TCL in the "`c:\Tcl`" directory (as recommended + in step 3 above), then this step is optional because + "`c:\Tcl`" is the default value for TCLDIR. You can also skip this + step by specifying "`TCLDIR=c:\Tcl`" as an argument to the nmake + commands in step 6 below. + 6. Run the "`Makefile.msc`" makefile with an appropriate target. Examples:
      • `nmake /f makefile.msc`
      • `nmake /f makefile.msc sqlite3.c` +
      • `nmake /f makefile.msc sqlite3.exe` +
      • `nmake /f makefile.msc sqldiff.exe` +
      • `nmake /f makefile.msc sqlite3_rsync.exe` +
      • `nmake /f makefile.msc tclextension-install`
      • `nmake /f makefile.msc devtest`
      • `nmake /f makefile.msc releasetest` +
      • `nmake /f makefile.msc sqlite3_analyzer.exe`
      + It is not required that you run the "tclextension-install" target prior to + running tests. However, the tests will run more smoothly if you do. + The version of SQLite used for the TCL extension does *not* need to + correspond to the version of SQLite under test. So you can install the + SQLite TCL extension once, and then use it to test many different versions + of SQLite. + + + 7. For a debugging build of the CLI, where the ".treetrace" and ".wheretrace" + commands work, add the DEBUG=3 argument to nmake. Like this: +
        +
      • `nmake /f makefile.msc DEBUG=3 clean sqlite3.exe` +
      + + ## 32-bit Builds Doing a 32-bit build is just like doing a 64-bit build with the @@ -108,7 +134,7 @@ nmake /f Makefile.msc sqlite3_analyzer.exe ~~~~ And you will end up with a working executable. However, that executable -will depend on having the "tcl86.dll" library somewhere on your %PATH%. +will depend on having the "tcl98.dll" library somewhere on your %PATH%. Use the following steps to build an executable that has the TCL library statically linked so that it does not depend on separate DLL: @@ -118,27 +144,26 @@ statically linked so that it does not depend on separate DLL: 2. Untar the TCL source tarball into a fresh directory. CD into the "win/" subfolder. - 3. Run: `nmake /f makefile.vc OPTS=nothreads,static shell` - + 3. Run: `nmake /f makefile.vc OPTS=static shell` 4. CD into the "Release*" subfolder that is created (note the wildcard - the full name of the directory might vary). There - you will find the "tcl86s.lib" file. Copy this file into the - same directory that you put the "tcl86.lib" on your initial + you will find the "tcl90s.lib" file. Copy this file into the + same directory that you put the "tcl90.lib" on your initial installation. (In this document, that directory is "`C:\Tcl32\lib`" for 32-bit builds and "`C:\Tcl\lib`" for 64-bit builds.) 5. CD into your SQLite source code directory and build the desired - utility program, but add the following extra arguments to the + utility program, but add the following extra argument to the nmake command line:
      -      CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib"
      +      STATICALLY_LINK_TCL=1
             

      So, for example, to build a statically linked version of sqlite3_analyzer.exe, you might type:

      -      nmake /f Makefile.msc CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib" sqlite3_analyzer.exe
      +      nmake /f Makefile.msc STATICALLY_LINK_TCL=1 sqlite3_analyzer.exe
             
      6. After your executable is built, you can verify that it does not diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c index 3fa613ba9..75324a34f 100755 --- a/ext/consio/console_io.c +++ b/ext/consio/console_io.c @@ -595,6 +595,93 @@ oPutbUtf8(const char *cBuf, int nAccept){ # endif } +/* +** Flush the given output stream. Return non-zero for success, else 0. +*/ +#if !defined(SQLITE_CIO_NO_FLUSH) && !defined(SQLITE_CIO_NO_SETMODE) +SQLITE_INTERNAL_LINKAGE int +fFlushBuffer(FILE *pfOut){ +# if CIO_WIN_WC_XLATE && !defined(SHELL_OMIT_FIO_DUPE) + return FlushFileBuffers(handleOfFile(pfOut))? 1 : 0; +# else + return fflush(pfOut); +# endif +} +#endif + +#if CIO_WIN_WC_XLATE \ + && !defined(SHELL_OMIT_FIO_DUPE) \ + && defined(SQLITE_USE_ONLY_WIN32) +static struct FileAltIds { + int fd; + HANDLE fh; +} altIdsOfFile(FILE *pf){ + struct FileAltIds rv = { _fileno(pf) }; + union { intptr_t osfh; HANDLE fh; } fid = { + (rv.fd>=0)? _get_osfhandle(rv.fd) : (intptr_t)INVALID_HANDLE_VALUE + }; + rv.fh = fid.fh; + return rv; +} + +SQLITE_INTERNAL_LINKAGE size_t +cfWrite(const void *buf, size_t osz, size_t ocnt, FILE *pf){ + size_t rv = 0; + struct FileAltIds fai = altIdsOfFile(pf); + int fmode = _setmode(fai.fd, _O_BINARY); + _setmode(fai.fd, fmode); + while( rv < ocnt ){ + size_t nbo = osz; + while( nbo > 0 ){ + DWORD dwno = (nbo>(1L<<24))? 1L<<24 : (DWORD)nbo; + BOOL wrc = TRUE; + BOOL genCR = (fmode & _O_TEXT)!=0; + if( genCR ){ + const char *pnl = (const char*)memchr(buf, '\n', nbo); + if( pnl ) nbo = pnl - (const char*)buf; + else genCR = 0; + } + if( dwno>0 ) wrc = WriteFile(fai.fh, buf, dwno, 0,0); + if( genCR && wrc ){ + wrc = WriteFile(fai.fh, "\r\n", 2, 0,0); + ++dwno; /* Skip over the LF */ + } + if( !wrc ) return rv; + buf = (const char*)buf + dwno; + nbo += dwno; + } + ++rv; + } + return rv; +} + +/* An fgets() equivalent, using Win32 file API for actual input. +** Input ends when given buffer is filled or a newline is read. +** If the FILE object is in text mode, swallows 0x0D. (ASCII CR) +*/ +SQLITE_INTERNAL_LINKAGE char * +cfGets(char *cBuf, int n, FILE *pf){ + int nci = 0; + struct FileAltIds fai = altIdsOfFile(pf); + int fmode = _setmode(fai.fd, _O_BINARY); + BOOL eatCR = (fmode & _O_TEXT)!=0; + _setmode(fai.fd, fmode); + while( nci < n-1 ){ + DWORD nr; + if( !ReadFile(fai.fh, cBuf+nci, 1, &nr, 0) || nr==0 ) break; + if( nr>0 && (!eatCR || cBuf[nci]!='\r') ){ + nci += nr; + if( cBuf[nci-nr]=='\n' ) break; + } + } + if( nci < n ) cBuf[nci] = 0; + return (nci>0)? cBuf : 0; +} +# else +# define cfWrite(b,os,no,f) fwrite(b,os,no,f) +# define cfGets(b,n,f) fgets(b,n,f) +# endif + # ifdef CONSIO_EPUTB SQLITE_INTERNAL_LINKAGE int ePutbUtf8(const char *cBuf, int nAccept){ @@ -606,7 +693,7 @@ ePutbUtf8(const char *cBuf, int nAccept){ return conZstrEmit(ppst, cBuf, nAccept); }else { # endif - return (int)fwrite(cBuf, 1, nAccept, pfErr); + return (int)cfWrite(cBuf, 1, nAccept, pfErr); # if CIO_WIN_WC_XLATE } # endif @@ -672,7 +759,7 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ # endif }else{ # endif - return fgets(cBuf, ncMax, pfIn); + return cfGets(cBuf, ncMax, pfIn); # if CIO_WIN_WC_XLATE } # endif diff --git a/ext/consio/console_io.h b/ext/consio/console_io.h index 26fd7dd94..1affa15ba 100644 --- a/ext/consio/console_io.h +++ b/ext/consio/console_io.h @@ -176,12 +176,19 @@ SQLITE_INTERNAL_LINKAGE int ePutbUtf8(const char *cBuf, int nAccept); #endif +/* +** Flush the given output stream. Return non-zero for success, else 0. +*/ +#if !defined(SQLITE_CIO_NO_FLUSH) && !defined(SQLITE_CIO_NO_SETMODE) +SQLITE_INTERNAL_LINKAGE int +fFlushBuffer(FILE *pfOut); +#endif + /* ** Collect input like fgets(...) with special provisions for input -** from the console on platforms that require same. Defers to the -** C library fgets() when input is not from the console. Newline -** translation may be done as set by set{Binary,Text}Mode(). As a -** convenience, pfIn==NULL is treated as stdin. +** from the console on such platforms as require same. Newline +** translation may be done as set by set{Binary,Text}Mode(). +** As a convenience, pfIn==NULL is treated as stdin. */ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn); /* Like fGetsUtf8 except stream is always the designated input. */ diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test index 72c4fd72c..0c3b512af 100644 --- a/ext/expert/expert1.test +++ b/ext/expert/expert1.test @@ -402,6 +402,19 @@ do_setup_rec_test $tn.19.0 { SCAN t1 USING COVERING INDEX t1_idx_01a7214e } +ifcapable fts5 { + do_setup_rec_test $tn.20.0 { + CREATE VIRTUAL TABLE ft USING fts5(a); + CREATE TABLE t1(x, y); + } { + SELECT * FROM ft, t1 WHERE a=x + } { + CREATE INDEX t1_idx_00000078 ON t1(x); + SCAN ft VIRTUAL TABLE INDEX 0: + SEARCH t1 USING INDEX t1_idx_00000078 (x=?) + } +} + } proc do_candidates_test {tn sql res} { @@ -428,6 +441,8 @@ do_execsql_test 5.0 { WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100) INSERT INTO t2 SELECT (i-1)/20, (i-1)/5 FROM s; + + CREATE INDEX i1 ON t1( lower(a) ); } do_candidates_test 5.1 { SELECT * FROM t1,t2 WHERE (b=? OR a=?) AND (c=? OR d=?) @@ -457,6 +472,7 @@ do_execsql_test 5.3 { ANALYZE; SELECT * FROM sqlite_stat1 ORDER BY 1, 2; } { + t1 i1 {100 50} t1 t1_idx_00000061 {100 50} t1 t1_idx_00000062 {100 20} t1 t1_idx_000123a7 {100 50 17} @@ -491,4 +507,103 @@ USE TEMP B-TREE FOR ORDER BY }} } +do_execsql_test 6.0 { + CREATE TABLE x1(a, b, c, d); + CREATE INDEX x1ab ON x1(a, lower(b)); + CREATE INDEX x1dcba ON x1(d, b+c, a); +} + +do_candidates_test 6.1 { + SELECT * FROM x1 WHERE b=? ORDER BY a; +} { + CREATE INDEX x1_idx_0001267f ON x1(b, a); + CREATE INDEX x1_idx_00000062 ON x1(b); +} + +#------------------------------------------------------------------------- +ifcapable fts5 { + reset_db + do_execsql_test 7.0 { + CREATE VIRTUAL TABLE ft USING fts5(a); + CREATE TABLE t1(x, y); + } + + do_candidates_test 7.1 { + SELECT * FROM ft, t1 WHERE a=x + } { + CREATE INDEX t1_idx_00000078 ON t1(x); + } + + register_tcl_module db + proc vtab_command {method args} { + global G + + switch -- $method { + xConnect { + return "CREATE TABLE t1(a, b, c);" + } + + xBestIndex { + return [list] + } + + xFilter { + return [list sql "SELECT rowid, * FROM t0"] + } + } + + return {} + } + + do_execsql_test 7.2 { + CREATE TABLE t0(a, b, c); + INSERT INTO t0 VALUES(1, 2, 3), (11, 22, 33); + CREATE VIRTUAL TABLE t2 USING tcl(vtab_command); + } + + do_execsql_test 7.3 { + SELECT * FROM t2 + } { + 1 2 3 + 11 22 33 + } + + do_candidates_test 7.4 { + SELECT * FROM ft, t1 WHERE a=x + } { + CREATE INDEX t1_idx_00000078 ON t1(x); + } + + do_test 7.5 { + set expert [sqlite3_expert_new db] + list [catch { $expert sql "SELECT * FROM ft, t2 WHERE b=1" } msg] $msg + } {1 {no such table: t2}} + $expert destroy + + reset_db + do_execsql_test 7.6 { + BEGIN TRANSACTION; + CREATE TABLE IF NOT EXISTS 'bfts_idx_data'(id INTEGER PRIMARY KEY, block BLOB); + CREATE TABLE IF NOT EXISTS 'fts_idx_data'(id INTEGER PRIMARY KEY, block BLOB); + INSERT INTO fts_idx_data VALUES(1,X''); + INSERT INTO fts_idx_data VALUES(10,X'00000000ff000001000000'); + CREATE TABLE IF NOT EXISTS 'fts_idx_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID; + CREATE TABLE IF NOT EXISTS 'fts_idx_docsize'(id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER); + CREATE TABLE IF NOT EXISTS 'fts_idx_config'(k PRIMARY KEY, v) WITHOUT ROWID; + INSERT INTO fts_idx_config VALUES('version',4); + PRAGMA writable_schema=ON; + INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)VALUES('table','fts_idx','fts_idx',0,'CREATE VIRTUAL TABLE fts_idx USING fts5(Title, Description, Channel, Tags, content='''', contentless_delete=1)'); + + CREATE TABLE f(x BLOB, y); + COMMIT; + PRAGMA writable_schema = RESET; + } + + do_candidates_test 7.4 { + SELECT * FROM fts_idx, f WHERE x = fts_idx.Channel + } { + CREATE INDEX f_idx_00000078 ON f(x); + } +} + finish_test diff --git a/ext/expert/sqlite3expert.c b/ext/expert/sqlite3expert.c index b59a59728..84b4793dd 100644 --- a/ext/expert/sqlite3expert.c +++ b/ext/expert/sqlite3expert.c @@ -1392,6 +1392,66 @@ static int idxProcessTriggers(sqlite3expert *p, char **pzErr){ return rc; } +/* +** This function tests if the schema of the main database of database handle +** db contains an object named zTab. Assuming no error occurs, output parameter +** (*pbContains) is set to true if zTab exists, or false if it does not. +** +** Or, if an error occurs, an SQLite error code is returned. The final value +** of (*pbContains) is undefined in this case. +*/ +static int expertDbContainsObject( + sqlite3 *db, + const char *zTab, + int *pbContains /* OUT: True if object exists */ +){ + const char *zSql = "SELECT 1 FROM sqlite_schema WHERE name = ?"; + sqlite3_stmt *pSql = 0; + int rc = SQLITE_OK; + int ret = 0; + + rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_text(pSql, 1, zTab, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pSql) ){ + ret = 1; + } + rc = sqlite3_finalize(pSql); + } + + *pbContains = ret; + return rc; +} + +/* +** Execute SQL command zSql using database handle db. If no error occurs, +** set (*pzErr) to NULL and return SQLITE_OK. +** +** If an error does occur, return an SQLite error code and set (*pzErr) to +** point to a buffer containing an English language error message. Except, +** if the error message begins with "no such module:", then ignore the +** error and return as if the SQL statement had succeeded. +** +** This is used to copy as much of the database schema as possible while +** ignoring any errors related to missing virtual table modules. +*/ +static int expertSchemaSql(sqlite3 *db, const char *zSql, char **pzErr){ + int rc = SQLITE_OK; + char *zErr = 0; + + rc = sqlite3_exec(db, zSql, 0, 0, &zErr); + if( rc!=SQLITE_OK && zErr ){ + int nErr = STRLEN(zErr); + if( nErr>=15 && memcmp(zErr, "no such module:", 15)==0 ){ + sqlite3_free(zErr); + rc = SQLITE_OK; + zErr = 0; + } + } + + *pzErr = zErr; + return rc; +} static int idxCreateVtabSchema(sqlite3expert *p, char **pzErrmsg){ int rc = idxRegisterVtab(p); @@ -1403,22 +1463,31 @@ static int idxCreateVtabSchema(sqlite3expert *p, char **pzErrmsg){ ** 2) Create the equivalent virtual table in dbv. */ rc = idxPrepareStmt(p->db, &pSchema, pzErrmsg, - "SELECT type, name, sql, 1 FROM sqlite_schema " - "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%%' " + "SELECT type, name, sql, 1, " + " substr(sql,1,14)=='create virtual' COLLATE nocase " + "FROM sqlite_schema " + "WHERE type IN ('table','view') AND " + " substr(name,1,7)!='sqlite_' COLLATE nocase " " UNION ALL " - "SELECT type, name, sql, 2 FROM sqlite_schema " + "SELECT type, name, sql, 2, 0 FROM sqlite_schema " "WHERE type = 'trigger'" " AND tbl_name IN(SELECT name FROM sqlite_schema WHERE type = 'view') " - "ORDER BY 4, 1" + "ORDER BY 4, 5 DESC, 1" ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSchema) ){ const char *zType = (const char*)sqlite3_column_text(pSchema, 0); const char *zName = (const char*)sqlite3_column_text(pSchema, 1); const char *zSql = (const char*)sqlite3_column_text(pSchema, 2); + int bVirtual = sqlite3_column_int(pSchema, 4); + int bExists = 0; if( zType==0 || zName==0 ) continue; - if( zType[0]=='v' || zType[1]=='r' ){ - if( zSql ) rc = sqlite3_exec(p->dbv, zSql, 0, 0, pzErrmsg); + rc = expertDbContainsObject(p->dbv, zName, &bExists); + if( rc || bExists ) continue; + + if( zType[0]=='v' || zType[1]=='r' || bVirtual ){ + /* A view. Or a trigger on a view. */ + if( zSql ) rc = expertSchemaSql(p->dbv, zSql, pzErrmsg); }else{ IdxTable *pTab; rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg); @@ -1623,6 +1692,12 @@ static int idxPopulateOneStat1( const char *zComma = zCols==0 ? "" : ", "; const char *zName = (const char*)sqlite3_column_text(pIndexXInfo, 0); const char *zColl = (const char*)sqlite3_column_text(pIndexXInfo, 1); + if( zName==0 ){ + /* This index contains an expression. Ignore it. */ + sqlite3_free(zCols); + sqlite3_free(zOrder); + return sqlite3_reset(pIndexXInfo); + } zCols = idxAppendText(&rc, zCols, "%sx.%Q IS sqlite_expert_rem(%d, x.%Q) COLLATE %s", zComma, zName, nCol, zName, zColl @@ -1951,12 +2026,18 @@ sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){ if( rc==SQLITE_OK ){ sqlite3_stmt *pSql = 0; rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg, - "SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'" - " AND sql NOT LIKE 'CREATE VIRTUAL %%' ORDER BY rowid" + "SELECT sql, name, substr(sql,1,14)=='create virtual' COLLATE nocase" + " FROM sqlite_schema WHERE substr(name,1,7)!='sqlite_' COLLATE nocase" + " ORDER BY 3 DESC, rowid" ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ const char *zSql = (const char*)sqlite3_column_text(pSql, 0); - if( zSql ) rc = sqlite3_exec(pNew->dbm, zSql, 0, 0, pzErrmsg); + const char *zName = (const char*)sqlite3_column_text(pSql, 1); + int bExists = 0; + rc = expertDbContainsObject(pNew->dbm, zName, &bExists); + if( rc==SQLITE_OK && zSql && bExists==0 ){ + rc = expertSchemaSql(pNew->dbm, zSql, pzErrmsg); + } } idxFinalize(&rc, pSql); } diff --git a/ext/expert/test_expert.c b/ext/expert/test_expert.c index 064c1908a..cae5d0f25 100644 --- a/ext/expert/test_expert.c +++ b/ext/expert/test_expert.c @@ -16,15 +16,7 @@ #include "sqlite3expert.h" #include #include - -#if defined(INCLUDE_SQLITE_TCL_H) -# include "sqlite_tcl.h" -#else -# include "tcl.h" -# ifndef SQLITE_TCLAPI -# define SQLITE_TCLAPI -# endif -#endif +#include "tclsqlite.h" #ifndef SQLITE_OMIT_VIRTUALTABLE diff --git a/ext/fts3/fts3_snippet.c b/ext/fts3/fts3_snippet.c index f6caabf4c..80f62eb3b 100644 --- a/ext/fts3/fts3_snippet.c +++ b/ext/fts3/fts3_snippet.c @@ -398,6 +398,7 @@ static int fts3SnippetNextCandidate(SnippetIter *pIter){ return 1; } + assert( pIter->nSnippet>=0 ); pIter->iCurrent = iStart = iEnd - pIter->nSnippet + 1; for(i=0; inPhrase; i++){ SnippetPhrase *pPhrase = &pIter->aPhrase[i]; diff --git a/ext/fts3/fts3_term.c b/ext/fts3/fts3_term.c index f3a9746a0..655dd9f35 100644 --- a/ext/fts3/fts3_term.c +++ b/ext/fts3/fts3_term.c @@ -78,6 +78,8 @@ static int fts3termConnectMethod( iIndex = atoi(argv[4]); argc--; } + + *ppVtab = 0; /* The user should specify a single argument - the name of an fts3 table. */ if( argc!=4 ){ @@ -95,12 +97,17 @@ static int fts3termConnectMethod( rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA); if( rc!=SQLITE_OK ) return rc; - nByte = sizeof(Fts3termTable) + sizeof(Fts3Table) + nDb + nFts3 + 2; - p = (Fts3termTable *)sqlite3_malloc64(nByte); + nByte = sizeof(Fts3termTable); + p = (Fts3termTable *)sqlite3Fts3MallocZero(nByte); if( !p ) return SQLITE_NOMEM; - memset(p, 0, (size_t)nByte); - p->pFts3Tab = (Fts3Table *)&p[1]; + p->pFts3Tab = (Fts3Table*)sqlite3Fts3MallocZero( + sizeof(Fts3Table) + nDb + nFts3 + 2 + ); + if( p->pFts3Tab==0 ){ + sqlite3_free(p); + return SQLITE_NOMEM; + } p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1]; p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1]; p->pFts3Tab->db = db; @@ -130,6 +137,7 @@ static int fts3termDisconnectMethod(sqlite3_vtab *pVtab){ sqlite3_finalize(pFts3->aStmt[i]); } sqlite3_free(pFts3->zSegmentsTbl); + sqlite3_free(pFts3); sqlite3_free(p); return SQLITE_OK; } diff --git a/ext/fts3/fts3_test.c b/ext/fts3/fts3_test.c index 49a8476bf..3c42a7bf0 100644 --- a/ext/fts3/fts3_test.c +++ b/ext/fts3/fts3_test.c @@ -18,14 +18,7 @@ ** that the sqlite3_tokenizer_module.xLanguage() method is invoked correctly. */ -#if defined(INCLUDE_SQLITE_TCL_H) -# include "sqlite_tcl.h" -#else -# include "tcl.h" -# ifndef SQLITE_TCLAPI -# define SQLITE_TCLAPI -# endif -#endif +#include "tclsqlite.h" #include #include @@ -167,7 +160,8 @@ static int SQLITE_TCLAPI fts3_near_match_cmd( Tcl_Obj *pPhrasecount = 0; Tcl_Obj **apExprToken; - int nExprToken; + Tcl_Size nExprToken; + Tcl_Size nn; UNUSED_PARAMETER(clientData); @@ -201,23 +195,25 @@ static int SQLITE_TCLAPI fts3_near_match_cmd( } } - rc = Tcl_ListObjGetElements(interp, objv[1], &doc.nToken, &apDocToken); + rc = Tcl_ListObjGetElements(interp, objv[1], &nn, &apDocToken); + doc.nToken = (int)nn; if( rc!=TCL_OK ) goto near_match_out; doc.aToken = (NearToken *)ckalloc(doc.nToken*sizeof(NearToken)); for(ii=0; iiz = Tcl_GetStringFromObj(apToken[jj], &pT->n); + pT->z = Tcl_GetStringFromObj(apToken[jj], &nn); + pT->n = (int)nn; } - aPhrase[ii].nToken = nToken; + aPhrase[ii].nToken = (int)nToken; } for(ii=1; ii /* diff --git a/ext/fts3/unicode/mkunicode.tcl b/ext/fts3/unicode/mkunicode.tcl index 58d90c68c..1306629da 100644 --- a/ext/fts3/unicode/mkunicode.tcl +++ b/ext/fts3/unicode/mkunicode.tcl @@ -628,6 +628,9 @@ proc print_categories {lMap} { $caseP $caseS $caseZ + + default: + return 1; } return 0; } diff --git a/ext/fts5/extract_api_docs.tcl b/ext/fts5/extract_api_docs.tcl index 6762a036d..6ee71c262 100644 --- a/ext/fts5/extract_api_docs.tcl +++ b/ext/fts5/extract_api_docs.tcl @@ -82,7 +82,7 @@ proc get_struct_docs {data names} { set current_doc "" } set subject n/a - regexp {^ *([[:alpha:]]*)} $line -> subject + regexp {^ *([[:alnum:]_]*)} $line -> subject if {[lsearch $names $subject]>=0} { set current_header $subject } else { @@ -108,8 +108,11 @@ proc get_tokenizer_docs {data} { append res "
      $line

      \n" continue } + if {[regexp {FTS5_TOKENIZER} $line]} { + set line

      + } if {[regexp {SYNONYM SUPPORT} $line]} { - set line "

      Synonym Support

      " + set line "

      Synonym Support

      " } if {[string trim $line] == ""} { append res "

      \n" diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h index 551618e71..5305a6915 100644 --- a/ext/fts5/fts5.h +++ b/ext/fts5/fts5.h @@ -238,6 +238,10 @@ struct Fts5PhraseIter { ** (i.e. if it is a contentless table), then this API always iterates ** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** +** In all cases, matches are visited in (column ASC, offset ASC) order. +** i.e. all those in column 0, sorted by offset, followed by those in +** column 1, etc. +** ** xPhraseNext() ** See xPhraseFirst above. ** @@ -304,9 +308,32 @@ struct Fts5PhraseIter { ** ** This API can be quite slow if used with an FTS5 table created with the ** "detail=none" or "detail=column" option. +** +** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the locale associated +** with column iCol of the current row. Usually, there is no associated +** locale, and output parameters (*pzLocale) and (*pnLocale) are set +** to NULL and 0, respectively. However, if the fts5_locale() function +** was used to associate a locale with the value when it was inserted +** into the fts5 table, then (*pzLocale) is set to point to a nul-terminated +** buffer containing the name of the locale in utf-8 encoding. (*pnLocale) +** is set to the size in bytes of the buffer, not including the +** nul-terminator. +** +** If successful, SQLITE_OK is returned. Or, if an error occurs, an +** SQLite error code is returned. The final value of the output parameters +** is undefined in this case. +** +** xTokenize_v2: +** Tokenize text using the tokenizer belonging to the FTS5 table. This +** API is the same as the xTokenize() API, except that it allows a tokenizer +** locale to be specified. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 4 */ void *(*xUserData)(Fts5Context*); @@ -348,6 +375,15 @@ struct Fts5ExtensionApi { const char **ppToken, int *pnToken ); int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*); + + /* Below this point are iVersion>=4 only */ + int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xTokenize_v2)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + const char *pLocale, int nLocale, /* Locale to pass to tokenizer */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); }; /* @@ -368,7 +404,7 @@ struct Fts5ExtensionApi { ** A tokenizer instance is required to actually tokenize text. ** ** The first argument passed to this function is a copy of the (void*) -** pointer provided by the application when the fts5_tokenizer object +** pointer provided by the application when the fts5_tokenizer_v2 object ** was registered with FTS5 (the third argument to xCreateTokenizer()). ** The second and third arguments are an array of nul-terminated strings ** containing the tokenizer arguments, if any, specified following the @@ -392,7 +428,7 @@ struct Fts5ExtensionApi { ** argument passed to this function is a pointer to an Fts5Tokenizer object ** returned by an earlier call to xCreate(). ** -** The second argument indicates the reason that FTS5 is requesting +** The third argument indicates the reason that FTS5 is requesting ** tokenization of the supplied text. This is always one of the following ** four values: ** @@ -416,6 +452,13 @@ struct Fts5ExtensionApi { ** on a columnsize=0 database. ** ** +** The sixth and seventh arguments passed to xTokenize() - pLocale and +** nLocale - are a pointer to a buffer containing the locale to use for +** tokenization (e.g. "en_US") and its size in bytes, respectively. The +** pLocale buffer is not nul-terminated. pLocale may be passed NULL (in +** which case nLocale is always 0) to indicate that the tokenizer should +** use its default locale. +** ** For each token in the input string, the supplied callback xToken() must ** be invoked. The first argument to it should be a copy of the pointer ** passed as the second argument to xTokenize(). The third and fourth @@ -439,6 +482,30 @@ struct Fts5ExtensionApi { ** may abandon the tokenization and return any error code other than ** SQLITE_OK or SQLITE_DONE. ** +** If the tokenizer is registered using an fts5_tokenizer_v2 object, +** then the xTokenize() method has two additional arguments - pLocale +** and nLocale. These specify the locale that the tokenizer should use +** for the current request. If pLocale and nLocale are both 0, then the +** tokenizer should use its default locale. Otherwise, pLocale points to +** an nLocale byte buffer containing the name of the locale to use as utf-8 +** text. pLocale is not nul-terminated. +** +** FTS5_TOKENIZER +** +** There is also an fts5_tokenizer object. This is an older, deprecated, +** version of fts5_tokenizer_v2. It is similar except that: +** +**

        +**
      • There is no "iVersion" field, and +**
      • The xTokenize() method does not take a locale argument. +**
      +** +** Legacy fts5_tokenizer tokenizers must be registered using the +** legacy xCreateTokenizer() function, instead of xCreateTokenizer_v2(). +** +** Tokenizer implementations registered using either API may be retrieved +** using both xFindTokenizer() and xFindTokenizer_v2(). +** ** SYNONYM SUPPORT ** ** Custom tokenizers may also support synonyms. Consider a case in which a @@ -547,6 +614,33 @@ struct Fts5ExtensionApi { ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; +typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2; +struct fts5_tokenizer_v2 { + int iVersion; /* Currently always 2 */ + + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + const char *pLocale, int nLocale, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + +/* +** New code should use the fts5_tokenizer_v2 type to define tokenizer +** implementations. The following type is included for legacy applications +** that still use it. +*/ typedef struct fts5_tokenizer fts5_tokenizer; struct fts5_tokenizer { int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); @@ -566,6 +660,7 @@ struct fts5_tokenizer { ); }; + /* Flags that may be passed as the third argument to xTokenize() */ #define FTS5_TOKENIZE_QUERY 0x0001 #define FTS5_TOKENIZE_PREFIX 0x0002 @@ -585,7 +680,7 @@ struct fts5_tokenizer { */ typedef struct fts5_api fts5_api; struct fts5_api { - int iVersion; /* Currently always set to 2 */ + int iVersion; /* Currently always set to 3 */ /* Create a new tokenizer */ int (*xCreateTokenizer)( @@ -612,6 +707,25 @@ struct fts5_api { fts5_extension_function xFunction, void (*xDestroy)(void*) ); + + /* APIs below this point are only available if iVersion>=3 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer_v2 *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer_v2 **ppTokenizer + ); }; /* diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 9beb26e05..51d808177 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -59,6 +59,22 @@ typedef sqlite3_uint64 u64; # define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) # define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) +/* The uptr type is an unsigned integer large enough to hold a pointer +*/ +#if defined(HAVE_STDINT_H) + typedef uintptr_t uptr; +#elif SQLITE_PTRSIZE==4 + typedef u32 uptr; +#else + typedef u64 uptr; +#endif + +#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) +#else +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) +#endif + #endif /* Truncate very long tokens to this many bytes. Hard limit is @@ -142,6 +158,18 @@ struct Fts5Colset { */ typedef struct Fts5Config Fts5Config; +typedef struct Fts5TokenizerConfig Fts5TokenizerConfig; + +struct Fts5TokenizerConfig { + Fts5Tokenizer *pTok; + fts5_tokenizer_v2 *pApi2; + fts5_tokenizer *pApi1; + const char **azArg; + int nArg; + int ePattern; /* FTS_PATTERN_XXX constant */ + const char *pLocale; /* Current locale to use */ + int nLocale; /* Size of pLocale in bytes */ +}; /* ** An instance of the following structure encodes all information that can @@ -181,9 +209,12 @@ typedef struct Fts5Config Fts5Config; ** ** INSERT INTO tbl(tbl, rank) VALUES('prefix-index', $bPrefixIndex); ** +** bLocale: +** Set to true if locale=1 was specified when the table was created. */ struct Fts5Config { sqlite3 *db; /* Database handle */ + Fts5Global *pGlobal; /* Global fts5 object for handle db */ char *zDb; /* Database holding FTS index (e.g. "main") */ char *zName; /* Name of FTS index */ int nCol; /* Number of columns */ @@ -193,16 +224,17 @@ struct Fts5Config { int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */ int eContent; /* An FTS5_CONTENT value */ int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */ + int bContentlessUnindexed; /* "contentless_unindexed=" option (dflt=0) */ char *zContent; /* content table */ char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ int bTokendata; /* "tokendata=" option value (dflt==0) */ + int bLocale; /* "locale=" option value (dflt==0) */ int eDetail; /* FTS5_DETAIL_XXX value */ char *zContentExprlist; - Fts5Tokenizer *pTok; - fts5_tokenizer *pTokApi; + Fts5TokenizerConfig t; int bLock; /* True when table is preparing statement */ - int ePattern; /* FTS_PATTERN_XXX constant */ + /* Values loaded from the %_config table */ int iVersion; /* fts5 file format 'version' */ @@ -231,9 +263,10 @@ struct Fts5Config { #define FTS5_CURRENT_VERSION 4 #define FTS5_CURRENT_VERSION_SECUREDELETE 5 -#define FTS5_CONTENT_NORMAL 0 -#define FTS5_CONTENT_NONE 1 -#define FTS5_CONTENT_EXTERNAL 2 +#define FTS5_CONTENT_NORMAL 0 +#define FTS5_CONTENT_NONE 1 +#define FTS5_CONTENT_EXTERNAL 2 +#define FTS5_CONTENT_UNINDEXED 3 #define FTS5_DETAIL_FULL 0 #define FTS5_DETAIL_NONE 1 @@ -268,6 +301,8 @@ int sqlite3Fts5ConfigSetValue(Fts5Config*, const char*, sqlite3_value*, int*); int sqlite3Fts5ConfigParseRank(const char*, char**, char**); +void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...); + /* ** End of interface to code in fts5_config.c. **************************************************************************/ @@ -312,7 +347,7 @@ char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...); void sqlite3Fts5Put32(u8*, int); int sqlite3Fts5Get32(const u8*); -#define FTS5_POS2COLUMN(iPos) (int)(iPos >> 32) +#define FTS5_POS2COLUMN(iPos) (int)((iPos >> 32) & 0x7FFFFFFF) #define FTS5_POS2OFFSET(iPos) (int)(iPos & 0x7FFFFFFF) typedef struct Fts5PoslistReader Fts5PoslistReader; @@ -597,18 +632,20 @@ struct Fts5Table { Fts5Index *pIndex; /* Full-text index */ }; -int sqlite3Fts5GetTokenizer( - Fts5Global*, - const char **azArg, - int nArg, - Fts5Config*, - char **pzErr -); +int sqlite3Fts5LoadTokenizer(Fts5Config *pConfig); Fts5Table *sqlite3Fts5TableFromCsrid(Fts5Global*, i64); int sqlite3Fts5FlushToDisk(Fts5Table*); +void sqlite3Fts5ClearLocale(Fts5Config *pConfig); +void sqlite3Fts5SetLocale(Fts5Config *pConfig, const char *pLoc, int nLoc); + +int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal); +int sqlite3Fts5DecodeLocaleValue(sqlite3_value *pVal, + const char **ppText, int *pnText, const char **ppLoc, int *pnLoc +); + /* ** End of interface to code in fts5.c. **************************************************************************/ @@ -688,8 +725,8 @@ int sqlite3Fts5StorageRename(Fts5Storage*, const char *zName); int sqlite3Fts5DropAll(Fts5Config*); int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **); -int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**); -int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*); +int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**, int); +int sqlite3Fts5StorageContentInsert(Fts5Storage *p, int, sqlite3_value**, i64*); int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64); int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg); @@ -714,6 +751,9 @@ int sqlite3Fts5StorageOptimize(Fts5Storage *p); int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge); int sqlite3Fts5StorageReset(Fts5Storage *p); +void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage*); +int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel); + /* ** End of interface to code in fts5_storage.c. **************************************************************************/ @@ -866,6 +906,7 @@ int sqlite3Fts5TokenizerPattern( int (*xCreate)(void*, const char**, int, Fts5Tokenizer**), Fts5Tokenizer *pTok ); +int sqlite3Fts5TokenizerPreload(Fts5TokenizerConfig*); /* ** End of interface to code in fts5_tokenizer.c. **************************************************************************/ diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c index 30101fbe2..ad578156d 100644 --- a/ext/fts5/fts5_aux.c +++ b/ext/fts5/fts5_aux.c @@ -226,6 +226,7 @@ static int fts5HighlightCb( return rc; } + /* ** Implementation of highlight() function. */ @@ -256,12 +257,19 @@ static void fts5HighlightFunction( sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); rc = SQLITE_OK; }else if( ctx.zIn ){ + const char *pLoc = 0; /* Locale of column iCol */ + int nLoc = 0; /* Size of pLoc in bytes */ if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter); } if( rc==SQLITE_OK ){ - rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); + rc = pApi->xColumnLocale(pFts, iCol, &pLoc, &nLoc); + } + if( rc==SQLITE_OK ){ + rc = pApi->xTokenize_v2( + pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void*)&ctx, fts5HighlightCb + ); } if( ctx.bOpen ){ fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1); @@ -458,6 +466,8 @@ static void fts5SnippetFunction( memset(&sFinder, 0, sizeof(Fts5SFinder)); for(i=0; ixColumnText(pFts, i, &sFinder.zDoc, &nDoc); if( rc!=SQLITE_OK ) break; - rc = pApi->xTokenize(pFts, - sFinder.zDoc, nDoc, (void*)&sFinder,fts5SentenceFinderCb + rc = pApi->xColumnLocale(pFts, i, &pLoc, &nLoc); + if( rc!=SQLITE_OK ) break; + rc = pApi->xTokenize_v2(pFts, + sFinder.zDoc, nDoc, pLoc, nLoc, (void*)&sFinder, fts5SentenceFinderCb ); if( rc!=SQLITE_OK ) break; rc = pApi->xColumnSize(pFts, i, &nDocsize); @@ -524,6 +536,9 @@ static void fts5SnippetFunction( rc = pApi->xColumnSize(pFts, iBestCol, &nColSize); } if( ctx.zIn ){ + const char *pLoc = 0; /* Locale of column iBestCol */ + int nLoc = 0; /* Bytes in pLoc */ + if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter); } @@ -542,7 +557,12 @@ static void fts5SnippetFunction( } if( rc==SQLITE_OK ){ - rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); + rc = pApi->xColumnLocale(pFts, iBestCol, &pLoc, &nLoc); + } + if( rc==SQLITE_OK ){ + rc = pApi->xTokenize_v2( + pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void*)&ctx,fts5HighlightCb + ); } if( ctx.bOpen ){ fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1); @@ -726,6 +746,53 @@ static void fts5Bm25Function( } } +/* +** Implementation of fts5_get_locale() function. +*/ +static void fts5GetLocaleFunction( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +){ + int iCol = 0; + int eType = 0; + int rc = SQLITE_OK; + const char *zLocale = 0; + int nLocale = 0; + + /* xColumnLocale() must be available */ + assert( pApi->iVersion>=4 ); + + if( nVal!=1 ){ + const char *z = "wrong number of arguments to function fts5_get_locale()"; + sqlite3_result_error(pCtx, z, -1); + return; + } + + eType = sqlite3_value_numeric_type(apVal[0]); + if( eType!=SQLITE_INTEGER ){ + const char *z = "non-integer argument passed to function fts5_get_locale()"; + sqlite3_result_error(pCtx, z, -1); + return; + } + + iCol = sqlite3_value_int(apVal[0]); + if( iCol<0 || iCol>=pApi->xColumnCount(pFts) ){ + sqlite3_result_error_code(pCtx, SQLITE_RANGE); + return; + } + + rc = pApi->xColumnLocale(pFts, iCol, &zLocale, &nLocale); + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(pCtx, rc); + return; + } + + sqlite3_result_text(pCtx, zLocale, nLocale, SQLITE_TRANSIENT); +} + int sqlite3Fts5AuxInit(fts5_api *pApi){ struct Builtin { const char *zFunc; /* Function name (nul-terminated) */ @@ -733,9 +800,10 @@ int sqlite3Fts5AuxInit(fts5_api *pApi){ fts5_extension_function xFunc;/* Callback function */ void (*xDestroy)(void*); /* Destructor function */ } aBuiltin [] = { - { "snippet", 0, fts5SnippetFunction, 0 }, - { "highlight", 0, fts5HighlightFunction, 0 }, - { "bm25", 0, fts5Bm25Function, 0 }, + { "snippet", 0, fts5SnippetFunction, 0 }, + { "highlight", 0, fts5HighlightFunction, 0 }, + { "bm25", 0, fts5Bm25Function, 0 }, + { "fts5_get_locale", 0, fts5GetLocaleFunction, 0 }, }; int rc = SQLITE_OK; /* Return code */ int i; /* To iterate through builtin functions */ diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c index d2e8309cd..a674b44d0 100644 --- a/ext/fts5/fts5_config.c +++ b/ext/fts5/fts5_config.c @@ -234,7 +234,6 @@ static int fts5ConfigSetEnum( ** eventually free any such error message using sqlite3_free(). */ static int fts5ConfigParseSpecial( - Fts5Global *pGlobal, Fts5Config *pConfig, /* Configuration object to update */ const char *zCmd, /* Special command to parse */ const char *zArg, /* Argument to parse */ @@ -242,6 +241,7 @@ static int fts5ConfigParseSpecial( ){ int rc = SQLITE_OK; int nCmd = (int)strlen(zCmd); + if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){ const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES; const char *p; @@ -298,12 +298,11 @@ static int fts5ConfigParseSpecial( if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){ const char *p = (const char*)zArg; sqlite3_int64 nArg = strlen(zArg) + 1; - char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg); - char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2); - char *pSpace = pDel; + char **azArg = sqlite3Fts5MallocZero(&rc, (sizeof(char*) + 2) * nArg); - if( azArg && pSpace ){ - if( pConfig->pTok ){ + if( azArg ){ + char *pSpace = (char*)&azArg[nArg]; + if( pConfig->t.azArg ){ *pzErr = sqlite3_mprintf("multiple tokenize=... directives"); rc = SQLITE_ERROR; }else{ @@ -326,16 +325,14 @@ static int fts5ConfigParseSpecial( *pzErr = sqlite3_mprintf("parse error in tokenize directive"); rc = SQLITE_ERROR; }else{ - rc = sqlite3Fts5GetTokenizer(pGlobal, - (const char**)azArg, (int)nArg, pConfig, - pzErr - ); + pConfig->t.azArg = (const char**)azArg; + pConfig->t.nArg = nArg; + azArg = 0; } } } - sqlite3_free(azArg); - sqlite3_free(pDel); + return rc; } @@ -364,6 +361,16 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("contentless_unindexed", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bContentlessUnindexed = (zArg[0]=='1'); + } + return rc; + } + if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ if( pConfig->zContentRowid ){ *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); @@ -384,6 +391,16 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("locale", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed locale=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bLocale = (zArg[0]=='1'); + } + return rc; + } + if( sqlite3_strnicmp("detail", zCmd, nCmd)==0 ){ const Fts5Enum aDetail[] = { { "none", FTS5_DETAIL_NONE }, @@ -412,16 +429,6 @@ static int fts5ConfigParseSpecial( return SQLITE_ERROR; } -/* -** Allocate an instance of the default tokenizer ("simple") at -** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error -** code if an error occurs. -*/ -static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){ - assert( pConfig->pTok==0 && pConfig->pTokApi==0 ); - return sqlite3Fts5GetTokenizer(pGlobal, 0, 0, pConfig, 0); -} - /* ** Gobble up the first bareword or quoted word from the input buffer zIn. ** Return a pointer to the character immediately following the last in @@ -481,7 +488,8 @@ static int fts5ConfigParseColumn( Fts5Config *p, char *zCol, char *zArg, - char **pzErr + char **pzErr, + int *pbUnindexed ){ int rc = SQLITE_OK; if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME) @@ -492,6 +500,7 @@ static int fts5ConfigParseColumn( }else if( zArg ){ if( 0==sqlite3_stricmp(zArg, "unindexed") ){ p->abUnindexed[p->nCol] = 1; + *pbUnindexed = 1; }else{ *pzErr = sqlite3_mprintf("unrecognized column option: %s", zArg); rc = SQLITE_ERROR; @@ -512,11 +521,26 @@ static int fts5ConfigMakeExprlist(Fts5Config *p){ sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid); if( p->eContent!=FTS5_CONTENT_NONE ){ + assert( p->eContent==FTS5_CONTENT_EXTERNAL + || p->eContent==FTS5_CONTENT_NORMAL + || p->eContent==FTS5_CONTENT_UNINDEXED + ); for(i=0; inCol; i++){ if( p->eContent==FTS5_CONTENT_EXTERNAL ){ sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q", p->azCol[i]); - }else{ + }else if( p->eContent==FTS5_CONTENT_NORMAL || p->abUnindexed[i] ){ sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d", i); + }else{ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", NULL"); + } + } + } + if( p->eContent==FTS5_CONTENT_NORMAL && p->bLocale ){ + for(i=0; inCol; i++){ + if( p->abUnindexed[i]==0 ){ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.l%d", i); + }else{ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", NULL"); } } } @@ -550,10 +574,12 @@ int sqlite3Fts5ConfigParse( Fts5Config *pRet; /* New object to return */ int i; sqlite3_int64 nByte; + int bUnindexed = 0; /* True if there are one or more UNINDEXED */ *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config)); if( pRet==0 ) return SQLITE_NOMEM; memset(pRet, 0, sizeof(Fts5Config)); + pRet->pGlobal = pGlobal; pRet->db = db; pRet->iCookie = -1; @@ -602,13 +628,13 @@ int sqlite3Fts5ConfigParse( rc = SQLITE_ERROR; }else{ if( bOption ){ - rc = fts5ConfigParseSpecial(pGlobal, pRet, + rc = fts5ConfigParseSpecial(pRet, ALWAYS(zOne)?zOne:"", zTwo?zTwo:"", pzErr ); }else{ - rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr); + rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr, &bUnindexed); zOne = 0; } } @@ -640,21 +666,30 @@ int sqlite3Fts5ConfigParse( rc = SQLITE_ERROR; } - /* If a tokenizer= option was successfully parsed, the tokenizer has - ** already been allocated. Otherwise, allocate an instance of the default - ** tokenizer (unicode61) now. */ - if( rc==SQLITE_OK && pRet->pTok==0 ){ - rc = fts5ConfigDefaultTokenizer(pGlobal, pRet); + /* We only allow contentless_unindexed=1 if the table is actually a + ** contentless one. + */ + if( rc==SQLITE_OK + && pRet->bContentlessUnindexed + && pRet->eContent!=FTS5_CONTENT_NONE + ){ + *pzErr = sqlite3_mprintf( + "contentless_unindexed=1 requires a contentless table" + ); + rc = SQLITE_ERROR; } /* If no zContent option was specified, fill in the default values. */ if( rc==SQLITE_OK && pRet->zContent==0 ){ const char *zTail = 0; - assert( pRet->eContent==FTS5_CONTENT_NORMAL - || pRet->eContent==FTS5_CONTENT_NONE + assert( pRet->eContent==FTS5_CONTENT_NORMAL + || pRet->eContent==FTS5_CONTENT_NONE ); if( pRet->eContent==FTS5_CONTENT_NORMAL ){ zTail = "content"; + }else if( bUnindexed && pRet->bContentlessUnindexed ){ + pRet->eContent = FTS5_CONTENT_UNINDEXED; + zTail = "content"; }else if( pRet->bColumnsize ){ zTail = "docsize"; } @@ -688,9 +723,14 @@ int sqlite3Fts5ConfigParse( void sqlite3Fts5ConfigFree(Fts5Config *pConfig){ if( pConfig ){ int i; - if( pConfig->pTok ){ - pConfig->pTokApi->xDelete(pConfig->pTok); + if( pConfig->t.pTok ){ + if( pConfig->t.pApi1 ){ + pConfig->t.pApi1->xDelete(pConfig->t.pTok); + }else{ + pConfig->t.pApi2->xDelete(pConfig->t.pTok); + } } + sqlite3_free((char*)pConfig->t.azArg); sqlite3_free(pConfig->zDb); sqlite3_free(pConfig->zName); for(i=0; inCol; i++){ @@ -765,10 +805,24 @@ int sqlite3Fts5Tokenize( void *pCtx, /* Context passed to xToken() */ int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ ){ - if( pText==0 ) return SQLITE_OK; - return pConfig->pTokApi->xTokenize( - pConfig->pTok, pCtx, flags, pText, nText, xToken - ); + int rc = SQLITE_OK; + if( pText ){ + if( pConfig->t.pTok==0 ){ + rc = sqlite3Fts5LoadTokenizer(pConfig); + } + if( rc==SQLITE_OK ){ + if( pConfig->t.pApi1 ){ + rc = pConfig->t.pApi1->xTokenize( + pConfig->t.pTok, pCtx, flags, pText, nText, xToken + ); + }else{ + rc = pConfig->t.pApi2->xTokenize(pConfig->t.pTok, pCtx, flags, + pText, nText, pConfig->t.pLocale, pConfig->t.nLocale, xToken + ); + } + } + } + return rc; } /* @@ -1022,13 +1076,10 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ && iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE ){ rc = SQLITE_ERROR; - if( pConfig->pzErrmsg ){ - assert( 0==*pConfig->pzErrmsg ); - *pConfig->pzErrmsg = sqlite3_mprintf("invalid fts5 file format " - "(found %d, expected %d or %d) - run 'rebuild'", - iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE - ); - } + sqlite3Fts5ConfigErrmsg(pConfig, "invalid fts5 file format " + "(found %d, expected %d or %d) - run 'rebuild'", + iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE + ); }else{ pConfig->iVersion = iVersion; } @@ -1038,3 +1089,26 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ } return rc; } + +/* +** Set (*pConfig->pzErrmsg) to point to an sqlite3_malloc()ed buffer +** containing the error message created using printf() style formatting +** string zFmt and its trailing arguments. +*/ +void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...){ + va_list ap; /* ... printf arguments */ + char *zMsg = 0; + + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + if( pConfig->pzErrmsg ){ + assert( *pConfig->pzErrmsg==0 ); + *pConfig->pzErrmsg = zMsg; + }else{ + sqlite3_free(zMsg); + } + + va_end(ap); +} + + diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 960a4d468..cd44b96bd 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -54,7 +54,7 @@ struct Fts5Expr { /* ** eType: -** Expression node type. Always one of: +** Expression node type. Usually one of: ** ** FTS5_AND (nChild, apChild valid) ** FTS5_OR (nChild, apChild valid) @@ -62,6 +62,10 @@ struct Fts5Expr { ** FTS5_STRING (pNear valid) ** FTS5_TERM (pNear valid) ** +** An expression node with eType==0 may also exist. It always matches zero +** rows. This is created when a phrase containing no tokens is parsed. +** e.g. "". +** ** iHeight: ** Distance from this node to furthest leaf. This is always 0 for nodes ** of type FTS5_STRING and FTS5_TERM. For all other nodes it is one @@ -282,11 +286,12 @@ int sqlite3Fts5ExprNew( }while( sParse.rc==SQLITE_OK && t!=FTS5_EOF ); sqlite3Fts5ParserFree(pEngine, fts5ParseFree); + assert( sParse.pExpr || sParse.rc!=SQLITE_OK ); assert_expr_depth_ok(sParse.rc, sParse.pExpr); /* If the LHS of the MATCH expression was a user column, apply the ** implicit column-filter. */ - if( iColnCol && sParse.pExpr && sParse.rc==SQLITE_OK ){ + if( sParse.rc==SQLITE_OK && iColnCol ){ int n = sizeof(Fts5Colset); Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&sParse.rc, n); if( pColset ){ @@ -303,15 +308,7 @@ int sqlite3Fts5ExprNew( sParse.rc = SQLITE_NOMEM; sqlite3Fts5ParseNodeFree(sParse.pExpr); }else{ - if( !sParse.pExpr ){ - const int nByte = sizeof(Fts5ExprNode); - pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&sParse.rc, nByte); - if( pNew->pRoot ){ - pNew->pRoot->bEof = 1; - } - }else{ - pNew->pRoot = sParse.pExpr; - } + pNew->pRoot = sParse.pExpr; pNew->pIndex = 0; pNew->pConfig = pConfig; pNew->apExprPhrase = sParse.apPhrase; @@ -1129,7 +1126,7 @@ static int fts5ExprNodeTest_STRING( } }else{ Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; - if( pIter->iRowid==iLast || pIter->bEof ) continue; + if( pIter->iRowid==iLast ) continue; bMatch = 0; if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){ return rc; @@ -1651,9 +1648,6 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset( Fts5ExprNearset *pRet = 0; if( pParse->rc==SQLITE_OK ){ - if( pPhrase==0 ){ - return pNear; - } if( pNear==0 ){ sqlite3_int64 nByte; nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*); @@ -1875,6 +1869,7 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm( }else if( sCtx.pPhrase->nTerm ){ sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = (u8)bPrefix; } + assert( pParse->apPhrase!=0 ); pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase; } @@ -1894,7 +1889,7 @@ int sqlite3Fts5ExprClonePhrase( Fts5ExprPhrase *pOrig = 0; /* The phrase extracted from pExpr */ Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ TokenCtx sCtx = {0,0,0}; /* Context object for fts5ParseTokenize */ - if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + if( !pExpr || iPhrase<0 || iPhrase>=pExpr->nPhrase ){ rc = SQLITE_RANGE; }else{ pOrig = pExpr->apExprPhrase[iPhrase]; @@ -2262,6 +2257,9 @@ static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ } } +/* +** Add pSub as a child of p. +*/ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){ int ii = p->nChild; if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){ @@ -2406,19 +2404,23 @@ Fts5ExprNode *sqlite3Fts5ParseNode( "fts5: %s queries are not supported (detail!=full)", pNear->nPhrase==1 ? "phrase": "NEAR" ); - sqlite3_free(pRet); + sqlite3Fts5ParseNodeFree(pRet); pRet = 0; + pNear = 0; + assert( pLeft==0 && pRight==0 ); } } }else{ + assert( pNear==0 ); fts5ExprAddChildren(pRet, pLeft); fts5ExprAddChildren(pRet, pRight); + pLeft = pRight = 0; if( pRet->iHeight>SQLITE_FTS5_MAX_EXPR_DEPTH ){ sqlite3Fts5ParseError(pParse, "fts5 expression tree is too large (maximum depth %d)", SQLITE_FTS5_MAX_EXPR_DEPTH ); - sqlite3_free(pRet); + sqlite3Fts5ParseNodeFree(pRet); pRet = 0; } } @@ -2470,6 +2472,8 @@ Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( ); if( pRight->eType==FTS5_EOF ){ + assert( pParse->apPhrase!=0 ); + assert( pParse->nPhrase>0 ); assert( pParse->apPhrase[pParse->nPhrase-1]==pRight->pNear->apPhrase[0] ); sqlite3Fts5ParseNodeFree(pRight); pRet = pLeft; @@ -3102,6 +3106,7 @@ static int fts5ExprCheckPoslists(Fts5ExprNode *pNode, i64 iRowid){ pNode->iRowid = iRowid; pNode->bEof = 0; switch( pNode->eType ){ + case 0: case FTS5_TERM: case FTS5_STRING: return (pNode->pNear->apPhrase[0]->poslist.n>0); diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 333fefa2d..a51ae19e7 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -831,11 +831,12 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ if( rc==SQLITE_OK ){ u8 *aOut = 0; /* Read blob data into this buffer */ int nByte = sqlite3_blob_bytes(p->pReader); - sqlite3_int64 nAlloc = sizeof(Fts5Data) + nByte + FTS5_DATA_PADDING; + int szData = (sizeof(Fts5Data) + 7) & ~7; + sqlite3_int64 nAlloc = szData + nByte + FTS5_DATA_PADDING; pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); if( pRet ){ pRet->nn = nByte; - aOut = pRet->p = (u8*)&pRet[1]; + aOut = pRet->p = (u8*)pRet + szData; }else{ rc = SQLITE_NOMEM; } @@ -858,6 +859,7 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ } assert( (pRet==0)==(p->rc!=SQLITE_OK) ); + assert( pRet==0 || EIGHT_BYTE_ALIGNMENT( pRet->p ) ); return pRet; } @@ -2183,7 +2185,7 @@ static void fts5SegIterNext_None( if( iOffiEndofDoclist ){ /* Next entry is on the current page */ - i64 iDelta; + u64 iDelta; iOff += sqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta); pIter->iLeafOffset = iOff; pIter->iRowid += iDelta; @@ -4887,6 +4889,11 @@ static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){ nBest = nPercent; } } + + /* If pLvl is already the input level to an ongoing merge, look no + ** further for a merge candidate. The caller should be allowed to + ** continue merging from pLvl first. */ + if( pLvl->nMerge ) break; } } return iRet; @@ -8811,7 +8818,7 @@ static int fts5structConnectMethod( /* ** We must have a single struct=? constraint that will be passed through -** into the xFilter method. If there is no valid stmt=? constraint, +** into the xFilter method. If there is no valid struct=? constraint, ** then return an SQLITE_CONSTRAINT error. */ static int fts5structBestIndexMethod( diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index 837ea4015..5713fccdd 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -83,8 +83,17 @@ struct Fts5Global { Fts5TokenizerModule *pTok; /* First in list of all tokenizer modules */ Fts5TokenizerModule *pDfltTok; /* Default tokenizer module */ Fts5Cursor *pCsr; /* First in list of all open cursors */ + u32 aLocaleHdr[4]; }; +/* +** Size of header on fts5_locale() values. And macro to access a buffer +** containing a copy of the header from an Fts5Config pointer. +*/ +#define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr )) +#define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr)) + + /* ** Each auxiliary function registered with the FTS5 module is represented ** by an object of the following type. All such objects are stored as part @@ -103,11 +112,28 @@ struct Fts5Auxiliary { ** Each tokenizer module registered with the FTS5 module is represented ** by an object of the following type. All such objects are stored as part ** of the Fts5Global.pTok list. +** +** bV2Native: +** True if the tokenizer was registered using xCreateTokenizer_v2(), false +** for xCreateTokenizer(). If this variable is true, then x2 is populated +** with the routines as supplied by the caller and x1 contains synthesized +** wrapper routines. In this case the user-data pointer passed to +** x1.xCreate should be a pointer to the Fts5TokenizerModule structure, +** not a copy of pUserData. +** +** Of course, if bV2Native is false, then x1 contains the real routines and +** x2 the synthesized ones. In this case a pointer to the Fts5TokenizerModule +** object should be passed to x2.xCreate. +** +** The synthesized wrapper routines are necessary for xFindTokenizer(_v2) +** calls. */ struct Fts5TokenizerModule { char *zName; /* Name of tokenizer */ void *pUserData; /* User pointer passed to xCreate() */ - fts5_tokenizer x; /* Tokenizer functions */ + int bV2Native; /* True if v2 native tokenizer */ + fts5_tokenizer x1; /* Tokenizer functions */ + fts5_tokenizer_v2 x2; /* V2 tokenizer functions */ void (*xDestroy)(void*); /* Destructor function */ Fts5TokenizerModule *pNext; /* Next registered tokenizer module */ }; @@ -118,7 +144,7 @@ struct Fts5FullTable { Fts5Global *pGlobal; /* Global (connection wide) data */ Fts5Cursor *pSortCsr; /* Sort data from this cursor */ int iSavepoint; /* Successful xSavepoint()+1 */ - + #ifdef SQLITE_DEBUG struct Fts5TransactionState ts; #endif @@ -195,7 +221,7 @@ struct Fts5Cursor { Fts5Auxiliary *pAux; /* Currently executing extension function */ Fts5Auxdata *pAuxdata; /* First in linked list of saved aux-data */ - /* Cache used by auxiliary functions xInst() and xInstCount() */ + /* Cache used by auxiliary API functions xInst() and xInstCount() */ Fts5PoslistReader *aInstIter; /* One for each phrase */ int nInstAlloc; /* Size of aInst[] array (entries / 3) */ int nInstCount; /* Number of phrase instances */ @@ -306,10 +332,16 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){ #endif /* -** Return true if pTab is a contentless table. +** Return true if pTab is a contentless table. If parameter bIncludeUnindexed +** is true, this includes contentless tables that store UNINDEXED columns +** only. */ -static int fts5IsContentless(Fts5FullTable *pTab){ - return pTab->p.pConfig->eContent==FTS5_CONTENT_NONE; +static int fts5IsContentless(Fts5FullTable *pTab, int bIncludeUnindexed){ + int eContent = pTab->p.pConfig->eContent; + return ( + eContent==FTS5_CONTENT_NONE + || (bIncludeUnindexed && eContent==FTS5_CONTENT_UNINDEXED) + ); } /* @@ -377,8 +409,12 @@ static int fts5InitVtab( assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 ); } if( rc==SQLITE_OK ){ + pConfig->pzErrmsg = pzErr; pTab->p.pConfig = pConfig; pTab->pGlobal = pGlobal; + if( bCreate || sqlite3Fts5TokenizerPreload(&pConfig->t) ){ + rc = sqlite3Fts5LoadTokenizer(pConfig); + } } /* Open the index sub-system */ @@ -400,11 +436,7 @@ static int fts5InitVtab( /* Load the initial configuration */ if( rc==SQLITE_OK ){ - assert( pConfig->pzErrmsg==0 ); - pConfig->pzErrmsg = pzErr; - rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); - sqlite3Fts5IndexRollback(pTab->p.pIndex); - pConfig->pzErrmsg = 0; + rc = sqlite3Fts5ConfigLoad(pTab->p.pConfig, pTab->p.pConfig->iCookie-1); } if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ @@ -414,6 +446,7 @@ static int fts5InitVtab( rc = sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); } + if( pConfig ) pConfig->pzErrmsg = 0; if( rc!=SQLITE_OK ){ fts5FreeVtab(pTab); pTab = 0; @@ -481,10 +514,10 @@ static int fts5UsePatternMatch( ){ assert( FTS5_PATTERN_GLOB==SQLITE_INDEX_CONSTRAINT_GLOB ); assert( FTS5_PATTERN_LIKE==SQLITE_INDEX_CONSTRAINT_LIKE ); - if( pConfig->ePattern==FTS5_PATTERN_GLOB && p->op==FTS5_PATTERN_GLOB ){ + if( pConfig->t.ePattern==FTS5_PATTERN_GLOB && p->op==FTS5_PATTERN_GLOB ){ return 1; } - if( pConfig->ePattern==FTS5_PATTERN_LIKE + if( pConfig->t.ePattern==FTS5_PATTERN_LIKE && (p->op==FTS5_PATTERN_LIKE || p->op==FTS5_PATTERN_GLOB) ){ return 1; @@ -531,10 +564,10 @@ static int fts5UsePatternMatch( ** This function ensures that there is at most one "r" or "=". And that if ** there exists an "=" then there is no "<" or ">". ** -** Costs are assigned as follows: +** If an unusable MATCH operator is present in the WHERE clause, then +** SQLITE_CONSTRAINT is returned. ** -** a) If an unusable MATCH operator is present in the WHERE clause, the -** cost is unconditionally set to 1e50 (a really big number). +** Costs are assigned as follows: ** ** a) If a MATCH operator is present, the cost depends on the other ** constraints also present. As follows: @@ -567,7 +600,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ int bSeenEq = 0; int bSeenGt = 0; int bSeenLt = 0; - int bSeenMatch = 0; + int nSeenMatch = 0; int bSeenRank = 0; @@ -598,18 +631,15 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ /* A MATCH operator or equivalent */ if( p->usable==0 || iCol<0 ){ /* As there exists an unusable MATCH constraint this is an - ** unusable plan. Set a prohibitively high cost. */ - pInfo->estimatedCost = 1e50; - assert( iIdxStr < pInfo->nConstraint*6 + 1 ); - idxStr[iIdxStr] = 0; - return SQLITE_OK; + ** unusable plan. Return SQLITE_CONSTRAINT. */ + return SQLITE_CONSTRAINT; }else{ if( iCol==nCol+1 ){ if( bSeenRank ) continue; idxStr[iIdxStr++] = 'r'; bSeenRank = 1; - }else if( iCol>=0 ){ - bSeenMatch = 1; + }else{ + nSeenMatch++; idxStr[iIdxStr++] = 'M'; sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); idxStr += strlen(&idxStr[iIdxStr]); @@ -626,6 +656,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ idxStr += strlen(&idxStr[iIdxStr]); pInfo->aConstraintUsage[i].argvIndex = ++iCons; assert( idxStr[iIdxStr]=='\0' ); + nSeenMatch++; }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ idxStr[iIdxStr++] = '='; bSeenEq = 1; @@ -662,7 +693,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ */ if( pInfo->nOrderBy==1 ){ int iSort = pInfo->aOrderBy[0].iColumn; - if( iSort==(pConfig->nCol+1) && bSeenMatch ){ + if( iSort==(pConfig->nCol+1) && nSeenMatch>0 ){ idxFlags |= FTS5_BI_ORDER_RANK; }else if( iSort==-1 && (!pInfo->aOrderBy[0].desc || !pConfig->bTokendata) ){ idxFlags |= FTS5_BI_ORDER_ROWID; @@ -677,14 +708,17 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ /* Calculate the estimated cost based on the flags set in idxFlags. */ if( bSeenEq ){ - pInfo->estimatedCost = bSeenMatch ? 100.0 : 10.0; - if( bSeenMatch==0 ) fts5SetUniqueFlag(pInfo); + pInfo->estimatedCost = nSeenMatch ? 1000.0 : 10.0; + if( nSeenMatch==0 ) fts5SetUniqueFlag(pInfo); }else if( bSeenLt && bSeenGt ){ - pInfo->estimatedCost = bSeenMatch ? 500.0 : 250000.0; + pInfo->estimatedCost = nSeenMatch ? 5000.0 : 250000.0; }else if( bSeenLt || bSeenGt ){ - pInfo->estimatedCost = bSeenMatch ? 750.0 : 750000.0; + pInfo->estimatedCost = nSeenMatch ? 7500.0 : 750000.0; }else{ - pInfo->estimatedCost = bSeenMatch ? 1000.0 : 1000000.0; + pInfo->estimatedCost = nSeenMatch ? 10000.0 : 1000000.0; + } + for(i=1; iestimatedCost *= 0.4; } pInfo->idxNum = idxFlags; @@ -960,6 +994,7 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ } }else{ rc = SQLITE_OK; + CsrFlagSet(pCsr, FTS5CSR_REQUIRE_DOCSIZE); } break; } @@ -989,7 +1024,7 @@ static int fts5PrepareStatement( rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, SQLITE_PREPARE_PERSISTENT, &pRet, 0); if( rc!=SQLITE_OK ){ - *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db)); + sqlite3Fts5ConfigErrmsg(pConfig, "%s", sqlite3_errmsg(pConfig->db)); } sqlite3_free(zSql); } @@ -1213,6 +1248,145 @@ static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){ return iDefault; } +/* +** Set the error message on the virtual table passed as the first argument. +*/ +static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){ + va_list ap; /* ... printf arguments */ + va_start(ap, zFormat); + sqlite3_free(p->p.base.zErrMsg); + p->p.base.zErrMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); +} + +/* +** Arrange for subsequent calls to sqlite3Fts5Tokenize() to use the locale +** specified by pLocale/nLocale. The buffer indicated by pLocale must remain +** valid until after the final call to sqlite3Fts5Tokenize() that will use +** the locale. +*/ +static void sqlite3Fts5SetLocale( + Fts5Config *pConfig, + const char *zLocale, + int nLocale +){ + Fts5TokenizerConfig *pT = &pConfig->t; + pT->pLocale = zLocale; + pT->nLocale = nLocale; +} + +/* +** Clear any locale configured by an earlier call to sqlite3Fts5SetLocale(). +*/ +void sqlite3Fts5ClearLocale(Fts5Config *pConfig){ + sqlite3Fts5SetLocale(pConfig, 0, 0); +} + +/* +** Return true if the value passed as the only argument is an +** fts5_locale() value. +*/ +int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal){ + int ret = 0; + if( sqlite3_value_type(pVal)==SQLITE_BLOB ){ + /* Call sqlite3_value_bytes() after sqlite3_value_blob() in this case. + ** If the blob was created using zeroblob(), then sqlite3_value_blob() + ** may call malloc(). If this malloc() fails, then the values returned + ** by both value_blob() and value_bytes() will be 0. If value_bytes() were + ** called first, then the NULL pointer returned by value_blob() might + ** be dereferenced. */ + const u8 *pBlob = sqlite3_value_blob(pVal); + int nBlob = sqlite3_value_bytes(pVal); + if( nBlob>FTS5_LOCALE_HDR_SIZE + && 0==memcmp(pBlob, FTS5_LOCALE_HDR(pConfig), FTS5_LOCALE_HDR_SIZE) + ){ + ret = 1; + } + } + return ret; +} + +/* +** Value pVal is guaranteed to be an fts5_locale() value, according to +** sqlite3Fts5IsLocaleValue(). This function extracts the text and locale +** from the value and returns them separately. +** +** If successful, SQLITE_OK is returned and (*ppText) and (*ppLoc) set +** to point to buffers containing the text and locale, as utf-8, +** respectively. In this case output parameters (*pnText) and (*pnLoc) are +** set to the sizes in bytes of these two buffers. +** +** Or, if an error occurs, then an SQLite error code is returned. The final +** value of the four output parameters is undefined in this case. +*/ +int sqlite3Fts5DecodeLocaleValue( + sqlite3_value *pVal, + const char **ppText, + int *pnText, + const char **ppLoc, + int *pnLoc +){ + const char *p = sqlite3_value_blob(pVal); + int n = sqlite3_value_bytes(pVal); + int nLoc = 0; + + assert( sqlite3_value_type(pVal)==SQLITE_BLOB ); + assert( n>FTS5_LOCALE_HDR_SIZE ); + + for(nLoc=FTS5_LOCALE_HDR_SIZE; p[nLoc]; nLoc++){ + if( nLoc==(n-1) ){ + return SQLITE_MISMATCH; + } + } + *ppLoc = &p[FTS5_LOCALE_HDR_SIZE]; + *pnLoc = nLoc - FTS5_LOCALE_HDR_SIZE; + + *ppText = &p[nLoc+1]; + *pnText = n - nLoc - 1; + return SQLITE_OK; +} + +/* +** Argument pVal is the text of a full-text search expression. It may or +** may not have been wrapped by fts5_locale(). This function extracts +** the text of the expression, and sets output variable (*pzText) to +** point to a nul-terminated buffer containing the expression. +** +** If pVal was an fts5_locale() value, then sqlite3Fts5SetLocale() is called +** to set the tokenizer to use the specified locale. +** +** If output variable (*pbFreeAndReset) is set to true, then the caller +** is required to (a) call sqlite3Fts5ClearLocale() to reset the tokenizer +** locale, and (b) call sqlite3_free() to free (*pzText). +*/ +static int fts5ExtractExprText( + Fts5Config *pConfig, /* Fts5 configuration */ + sqlite3_value *pVal, /* Value to extract expression text from */ + char **pzText, /* OUT: nul-terminated buffer of text */ + int *pbFreeAndReset /* OUT: Free (*pzText) and clear locale */ +){ + int rc = SQLITE_OK; + + if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + const char *pText = 0; + int nText = 0; + const char *pLoc = 0; + int nLoc = 0; + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + *pzText = sqlite3Fts5Mprintf(&rc, "%.*s", nText, pText); + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + } + *pbFreeAndReset = 1; + }else{ + *pzText = (char*)sqlite3_value_text(pVal); + *pbFreeAndReset = 0; + } + + return rc; +} + + /* ** This is the xFilter interface for the virtual table. See ** the virtual table xFilter method documentation for additional @@ -1247,13 +1421,7 @@ static int fts5FilterMethod( int iIdxStr = 0; Fts5Expr *pExpr = 0; - if( pConfig->bLock ){ - pTab->p.base.zErrMsg = sqlite3_mprintf( - "recursively defined fts5 content table" - ); - return SQLITE_ERROR; - } - + assert( pConfig->bLock==0 ); if( pCsr->ePlan ){ fts5FreeCursorComponents(pCsr); memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr)); @@ -1277,8 +1445,14 @@ static int fts5FilterMethod( pRank = apVal[i]; break; case 'M': { - const char *zText = (const char*)sqlite3_value_text(apVal[i]); + char *zText = 0; + int bFreeAndReset = 0; + int bInternal = 0; + + rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset); + if( rc!=SQLITE_OK ) goto filter_out; if( zText==0 ) zText = ""; + iCol = 0; do{ iCol = iCol*10 + (idxStr[iIdxStr]-'0'); @@ -1290,7 +1464,7 @@ static int fts5FilterMethod( ** indicates that the MATCH expression is not a full text query, ** but a request for an internal parameter. */ rc = fts5SpecialMatch(pTab, pCsr, &zText[1]); - goto filter_out; + bInternal = 1; }else{ char **pzErr = &pTab->p.base.zErrMsg; rc = sqlite3Fts5ExprNew(pConfig, 0, iCol, zText, &pExpr, pzErr); @@ -1298,9 +1472,15 @@ static int fts5FilterMethod( rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr); pExpr = 0; } - if( rc!=SQLITE_OK ) goto filter_out; } + if( bFreeAndReset ){ + sqlite3_free(zText); + sqlite3Fts5ClearLocale(pConfig); + } + + if( bInternal || rc!=SQLITE_OK ) goto filter_out; + break; } case 'L': @@ -1388,9 +1568,7 @@ static int fts5FilterMethod( } } }else if( pConfig->zContent==0 ){ - *pConfig->pzErrmsg = sqlite3_mprintf( - "%s: table does not support scanning", pConfig->zName - ); + fts5SetVtabError(pTab,"%s: table does not support scanning",pConfig->zName); rc = SQLITE_ERROR; }else{ /* This is either a full-table scan (ePlan==FTS5_PLAN_SCAN) or a lookup @@ -1433,9 +1611,13 @@ static i64 fts5CursorRowid(Fts5Cursor *pCsr){ assert( pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE + || pCsr->ePlan==FTS5_PLAN_SCAN + || pCsr->ePlan==FTS5_PLAN_ROWID ); if( pCsr->pSorter ){ return pCsr->pSorter->iRowid; + }else if( pCsr->ePlan>=FTS5_PLAN_SCAN ){ + return sqlite3_column_int64(pCsr->pStmt, 0); }else{ return sqlite3Fts5ExprRowid(pCsr->pExpr); } @@ -1452,25 +1634,16 @@ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ int ePlan = pCsr->ePlan; assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 ); - switch( ePlan ){ - case FTS5_PLAN_SPECIAL: - *pRowid = 0; - break; - - case FTS5_PLAN_SOURCE: - case FTS5_PLAN_MATCH: - case FTS5_PLAN_SORTED_MATCH: - *pRowid = fts5CursorRowid(pCsr); - break; - - default: - *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); - break; + if( ePlan==FTS5_PLAN_SPECIAL ){ + *pRowid = 0; + }else{ + *pRowid = fts5CursorRowid(pCsr); } return SQLITE_OK; } + /* ** If the cursor requires seeking (bSeekRequired flag is set), seek it. ** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise. @@ -1507,8 +1680,13 @@ static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){ rc = sqlite3_reset(pCsr->pStmt); if( rc==SQLITE_OK ){ rc = FTS5_CORRUPT; + fts5SetVtabError((Fts5FullTable*)pTab, + "fts5: missing row %lld from content table %s", + fts5CursorRowid(pCsr), + pTab->pConfig->zContent + ); }else if( pTab->pConfig->pzErrmsg ){ - *pTab->pConfig->pzErrmsg = sqlite3_mprintf( + fts5SetVtabError((Fts5FullTable*)pTab, "%s", sqlite3_errmsg(pTab->pConfig->db) ); } @@ -1517,14 +1695,6 @@ static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){ return rc; } -static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){ - va_list ap; /* ... printf arguments */ - va_start(ap, zFormat); - assert( p->p.base.zErrMsg==0 ); - p->p.base.zErrMsg = sqlite3_vmprintf(zFormat, ap); - va_end(ap); -} - /* ** This function is called to handle an FTS INSERT command. In other words, ** an INSERT statement of the form: @@ -1562,7 +1732,7 @@ static int fts5SpecialInsert( } bLoadConfig = 1; }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){ - if( pConfig->eContent==FTS5_CONTENT_NONE ){ + if( fts5IsContentless(pTab, 1) ){ fts5SetVtabError(pTab, "'rebuild' may not be used with a contentless fts5 table" ); @@ -1618,7 +1788,7 @@ static int fts5SpecialDelete( int eType1 = sqlite3_value_type(apVal[1]); if( eType1==SQLITE_INTEGER ){ sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]); - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2]); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2], 0); } return rc; } @@ -1631,7 +1801,7 @@ static void fts5StorageInsert( ){ int rc = *pRc; if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid); + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, 0, apVal, piRowid); } if( rc==SQLITE_OK ){ rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid); @@ -1639,6 +1809,67 @@ static void fts5StorageInsert( *pRc = rc; } +/* +** +** This function is called when the user attempts an UPDATE on a contentless +** table. Parameter bRowidModified is true if the UPDATE statement modifies +** the rowid value. Parameter apVal[] contains the new values for each user +** defined column of the fts5 table. pConfig is the configuration object of the +** table being updated (guaranteed to be contentless). The contentless_delete=1 +** and contentless_unindexed=1 options may or may not be set. +** +** This function returns SQLITE_OK if the UPDATE can go ahead, or an SQLite +** error code if it cannot. In this case an error message is also loaded into +** pConfig. Output parameter (*pbContent) is set to true if the caller should +** update the %_content table only - not the FTS index or any other shadow +** table. This occurs when an UPDATE modifies only UNINDEXED columns of the +** table. +** +** An UPDATE may proceed if: +** +** * The only columns modified are UNINDEXED columns, or +** +** * The contentless_delete=1 option was specified and all of the indexed +** columns (not a subset) have been modified. +*/ +static int fts5ContentlessUpdate( + Fts5Config *pConfig, + sqlite3_value **apVal, + int bRowidModified, + int *pbContent +){ + int ii; + int bSeenIndex = 0; /* Have seen modified indexed column */ + int bSeenIndexNC = 0; /* Have seen unmodified indexed column */ + int rc = SQLITE_OK; + + for(ii=0; iinCol; ii++){ + if( pConfig->abUnindexed[ii]==0 ){ + if( sqlite3_value_nochange(apVal[ii]) ){ + bSeenIndexNC++; + }else{ + bSeenIndex++; + } + } + } + + if( bSeenIndex==0 && bRowidModified==0 ){ + *pbContent = 1; + }else{ + if( bSeenIndexNC || pConfig->bContentlessDelete==0 ){ + rc = SQLITE_ERROR; + sqlite3Fts5ConfigErrmsg(pConfig, + (pConfig->bContentlessDelete ? + "%s a subset of columns on fts5 contentless-delete table: %s" : + "%s contentless fts5 table: %s") + , "cannot UPDATE", pConfig->zName + ); + } + } + + return rc; +} + /* ** This function is the implementation of the xUpdate callback used by ** FTS3 virtual tables. It is invoked by SQLite each time a row is to be @@ -1725,41 +1956,46 @@ static int fts5UpdateMethod( assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); assert( nArg!=1 || eType0==SQLITE_INTEGER ); - /* Filter out attempts to run UPDATE or DELETE on contentless tables. - ** This is not suported. Except - they are both supported if the CREATE - ** VIRTUAL TABLE statement contained "contentless_delete=1". */ - if( eType0==SQLITE_INTEGER - && pConfig->eContent==FTS5_CONTENT_NONE - && pConfig->bContentlessDelete==0 - ){ - pTab->p.base.zErrMsg = sqlite3_mprintf( - "cannot %s contentless fts5 table: %s", - (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName - ); - rc = SQLITE_ERROR; - } - /* DELETE */ - else if( nArg==1 ){ - i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0); - bUpdateOrDelete = 1; + if( nArg==1 ){ + /* It is only possible to DELETE from a contentless table if the + ** contentless_delete=1 flag is set. */ + if( fts5IsContentless(pTab, 1) && pConfig->bContentlessDelete==0 ){ + fts5SetVtabError(pTab, + "cannot DELETE from contentless fts5 table: %s", pConfig->zName + ); + rc = SQLITE_ERROR; + }else{ + i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0, 0); + bUpdateOrDelete = 1; + } } /* INSERT or UPDATE */ else{ int eType1 = sqlite3_value_numeric_type(apVal[1]); - if( eType1!=SQLITE_INTEGER && eType1!=SQLITE_NULL ){ - rc = SQLITE_MISMATCH; + /* It is an error to write an fts5_locale() value to a table without + ** the locale=1 option. */ + if( pConfig->bLocale==0 ){ + int ii; + for(ii=0; iinCol; ii++){ + sqlite3_value *pVal = apVal[ii+2]; + if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + fts5SetVtabError(pTab, "fts5_locale() requires locale=1"); + rc = SQLITE_MISMATCH; + goto update_out; + } + } } - else if( eType0!=SQLITE_INTEGER ){ + if( eType0!=SQLITE_INTEGER ){ /* An INSERT statement. If the conflict-mode is REPLACE, first remove ** the current entry (if any). */ if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){ i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0, 0); bUpdateOrDelete = 1; } fts5StorageInsert(&rc, pTab, apVal, pRowid); @@ -1767,30 +2003,57 @@ static int fts5UpdateMethod( /* UPDATE */ else{ + Fts5Storage *pStorage = pTab->pStorage; i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */ i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */ - if( eType1==SQLITE_INTEGER && iOld!=iNew ){ + int bContent = 0; /* Content only update */ + + /* If this is a contentless table (including contentless_unindexed=1 + ** tables), check if the UPDATE may proceed. */ + if( fts5IsContentless(pTab, 1) ){ + rc = fts5ContentlessUpdate(pConfig, &apVal[2], iOld!=iNew, &bContent); + if( rc!=SQLITE_OK ) goto update_out; + } + + if( eType1!=SQLITE_INTEGER ){ + rc = SQLITE_MISMATCH; + }else if( iOld!=iNew ){ + assert( bContent==0 ); if( eConflict==SQLITE_REPLACE ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 1); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iNew, 0, 0); } fts5StorageInsert(&rc, pTab, apVal, pRowid); }else{ - rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid); + rc = sqlite3Fts5StorageFindDeleteRow(pStorage, iOld); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageContentInsert(pStorage, 0, apVal, pRowid); + } if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 0); } if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid); + rc = sqlite3Fts5StorageIndexInsert(pStorage, apVal, *pRowid); } } + }else if( bContent ){ + /* This occurs when an UPDATE on a contentless table affects *only* + ** UNINDEXED columns. This is a no-op for contentless_unindexed=0 + ** tables, or a write to the %_content table only for =1 tables. */ + assert( fts5IsContentless(pTab, 1) ); + rc = sqlite3Fts5StorageFindDeleteRow(pStorage, iOld); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageContentInsert(pStorage, 1, apVal, pRowid); + } }else{ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 1); fts5StorageInsert(&rc, pTab, apVal, pRowid); } bUpdateOrDelete = 1; + sqlite3Fts5StorageReleaseDeleteRow(pStorage); } + } } @@ -1807,6 +2070,7 @@ static int fts5UpdateMethod( } } + update_out: pTab->p.pConfig->pzErrmsg = 0; return rc; } @@ -1828,9 +2092,11 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){ ** Implementation of xBegin() method. */ static int fts5BeginMethod(sqlite3_vtab *pVtab){ - fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0); - fts5NewTransaction((Fts5FullTable*)pVtab); - return SQLITE_OK; + int rc = fts5NewTransaction((Fts5FullTable*)pVtab); + if( rc==SQLITE_OK ){ + fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0); + } + return rc; } /* @@ -1884,17 +2150,40 @@ static int fts5ApiRowCount(Fts5Context *pCtx, i64 *pnRow){ return sqlite3Fts5StorageRowCount(pTab->pStorage, pnRow); } -static int fts5ApiTokenize( +/* +** Implementation of xTokenize_v2() API. +*/ +static int fts5ApiTokenize_v2( Fts5Context *pCtx, const char *pText, int nText, + const char *pLoc, int nLoc, void *pUserData, int (*xToken)(void*, int, const char*, int, int, int) ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); - return sqlite3Fts5Tokenize( - pTab->pConfig, FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken + int rc = SQLITE_OK; + + sqlite3Fts5SetLocale(pTab->pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pTab->pConfig, + FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken ); + sqlite3Fts5SetLocale(pTab->pConfig, 0, 0); + + return rc; +} + +/* +** Implementation of xTokenize() API. This is just xTokenize_v2() with NULL/0 +** passed as the locale. +*/ +static int fts5ApiTokenize( + Fts5Context *pCtx, + const char *pText, int nText, + void *pUserData, + int (*xToken)(void*, int, const char*, int, int, int) +){ + return fts5ApiTokenize_v2(pCtx, pText, nText, 0, 0, pUserData, xToken); } static int fts5ApiPhraseCount(Fts5Context *pCtx){ @@ -1907,6 +2196,49 @@ static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){ return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase); } +/* +** Argument pStmt is an SQL statement of the type used by Fts5Cursor. This +** function extracts the text value of column iCol of the current row. +** Additionally, if there is an associated locale, it invokes +** sqlite3Fts5SetLocale() to configure the tokenizer. In all cases the caller +** should invoke sqlite3Fts5ClearLocale() to clear the locale at some point +** after this function returns. +** +** If successful, (*ppText) is set to point to a buffer containing the text +** value as utf-8 and SQLITE_OK returned. (*pnText) is set to the size of that +** buffer in bytes. It is not guaranteed to be nul-terminated. If an error +** occurs, an SQLite error code is returned. The final values of the two +** output parameters are undefined in this case. +*/ +static int fts5TextFromStmt( + Fts5Config *pConfig, + sqlite3_stmt *pStmt, + int iCol, + const char **ppText, + int *pnText +){ + sqlite3_value *pVal = sqlite3_column_value(pStmt, iCol+1); + const char *pLoc = 0; + int nLoc = 0; + int rc = SQLITE_OK; + + if( pConfig->bLocale + && pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, ppText, pnText, &pLoc, &nLoc); + }else{ + *ppText = (const char*)sqlite3_value_text(pVal); + *pnText = sqlite3_value_bytes(pVal); + if( pConfig->bLocale && pConfig->eContent==FTS5_CONTENT_NORMAL ){ + pLoc = (const char*)sqlite3_column_text(pStmt, iCol+1+pConfig->nCol); + nLoc = sqlite3_column_bytes(pStmt, iCol+1+pConfig->nCol); + } + } + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + return rc; +} + static int fts5ApiColumnText( Fts5Context *pCtx, int iCol, @@ -1916,28 +2248,35 @@ static int fts5ApiColumnText( int rc = SQLITE_OK; Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); + + assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL ); if( iCol<0 || iCol>=pTab->pConfig->nCol ){ rc = SQLITE_RANGE; - }else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) - || pCsr->ePlan==FTS5_PLAN_SPECIAL - ){ + }else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab), 0) ){ *pz = 0; *pn = 0; }else{ rc = fts5SeekCursor(pCsr, 0); if( rc==SQLITE_OK ){ - *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol+1); - *pn = sqlite3_column_bytes(pCsr->pStmt, iCol+1); + rc = fts5TextFromStmt(pTab->pConfig, pCsr->pStmt, iCol, pz, pn); + sqlite3Fts5ClearLocale(pTab->pConfig); } } return rc; } +/* +** This is called by various API functions - xInst, xPhraseFirst, +** xPhraseFirstColumn etc. - to obtain the position list for phrase iPhrase +** of the current row. This function works for both detail=full tables (in +** which case the position-list was read from the fts index) or for other +** detail= modes if the row content is available. +*/ static int fts5CsrPoslist( - Fts5Cursor *pCsr, - int iPhrase, - const u8 **pa, - int *pn + Fts5Cursor *pCsr, /* Fts5 cursor object */ + int iPhrase, /* Phrase to find position list for */ + const u8 **pa, /* OUT: Pointer to position list buffer */ + int *pn /* OUT: Size of (*pa) in bytes */ ){ Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; int rc = SQLITE_OK; @@ -1945,20 +2284,32 @@ static int fts5CsrPoslist( if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){ rc = SQLITE_RANGE; + }else if( pConfig->eDetail!=FTS5_DETAIL_FULL + && fts5IsContentless((Fts5FullTable*)pCsr->base.pVtab, 1) + ){ + *pa = 0; + *pn = 0; + return SQLITE_OK; }else if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ Fts5PoslistPopulator *aPopulator; int i; + aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive); if( aPopulator==0 ) rc = SQLITE_NOMEM; + if( rc==SQLITE_OK ){ + rc = fts5SeekCursor(pCsr, 0); + } for(i=0; inCol && rc==SQLITE_OK; i++){ - int n; const char *z; - rc = fts5ApiColumnText((Fts5Context*)pCsr, i, &z, &n); + const char *z = 0; + int n = 0; + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, i, &z, &n); if( rc==SQLITE_OK ){ rc = sqlite3Fts5ExprPopulatePoslists( pConfig, pCsr->pExpr, aPopulator, i, z, n ); } + sqlite3Fts5ClearLocale(pConfig); } sqlite3_free(aPopulator); @@ -1983,7 +2334,6 @@ static int fts5CsrPoslist( *pn = 0; } - return rc; } @@ -2052,7 +2402,8 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ aInst[0] = iBest; aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos); aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos); - if( aInst[1]<0 || aInst[1]>=nCol ){ + assert( aInst[1]>=0 ); + if( aInst[1]>=nCol ){ rc = FTS5_CORRUPT; break; } @@ -2130,7 +2481,7 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){ if( pConfig->bColumnsize ){ i64 iRowid = fts5CursorRowid(pCsr); rc = sqlite3Fts5StorageDocsize(pTab->pStorage, iRowid, pCsr->aColumnSize); - }else if( pConfig->zContent==0 ){ + }else if( !pConfig->zContent || pConfig->eContent==FTS5_CONTENT_UNINDEXED ){ int i; for(i=0; inCol; i++){ if( pConfig->abUnindexed[i]==0 ){ @@ -2139,17 +2490,19 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){ } }else{ int i; + rc = fts5SeekCursor(pCsr, 0); for(i=0; rc==SQLITE_OK && inCol; i++){ if( pConfig->abUnindexed[i]==0 ){ - const char *z; int n; - void *p = (void*)(&pCsr->aColumnSize[i]); + const char *z = 0; + int n = 0; pCsr->aColumnSize[i] = 0; - rc = fts5ApiColumnText(pCtx, i, &z, &n); + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, i, &z, &n); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5Tokenize( - pConfig, FTS5_TOKENIZE_AUX, z, n, p, fts5ColumnSizeCb + rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_AUX, + z, n, (void*)&pCsr->aColumnSize[i], fts5ColumnSizeCb ); } + sqlite3Fts5ClearLocale(pConfig); } } } @@ -2229,11 +2582,10 @@ static void *fts5ApiGetAuxdata(Fts5Context *pCtx, int bClear){ } static void fts5ApiPhraseNext( - Fts5Context *pUnused, + Fts5Context *pCtx, Fts5PhraseIter *pIter, int *piCol, int *piOff ){ - UNUSED_PARAM(pUnused); if( pIter->a>=pIter->b ){ *piCol = -1; *piOff = -1; @@ -2241,8 +2593,12 @@ static void fts5ApiPhraseNext( int iVal; pIter->a += fts5GetVarint32(pIter->a, iVal); if( iVal==1 ){ + /* Avoid returning a (*piCol) value that is too large for the table, + ** even if the position-list is corrupt. The caller might not be + ** expecting it. */ + int nCol = ((Fts5Table*)(((Fts5Cursor*)pCtx)->base.pVtab))->pConfig->nCol; pIter->a += fts5GetVarint32(pIter->a, iVal); - *piCol = iVal; + *piCol = (iVal>=nCol ? nCol-1 : iVal); *piOff = 0; pIter->a += fts5GetVarint32(pIter->a, iVal); } @@ -2392,8 +2748,48 @@ static int fts5ApiQueryPhrase(Fts5Context*, int, void*, int(*)(const Fts5ExtensionApi*, Fts5Context*, void*) ); +/* +** The xColumnLocale() API. +*/ +static int fts5ApiColumnLocale( + Fts5Context *pCtx, + int iCol, + const char **pzLocale, + int *pnLocale +){ + int rc = SQLITE_OK; + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + + *pzLocale = 0; + *pnLocale = 0; + + assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL ); + if( iCol<0 || iCol>=pConfig->nCol ){ + rc = SQLITE_RANGE; + }else if( + pConfig->abUnindexed[iCol]==0 + && 0==fts5IsContentless((Fts5FullTable*)pCsr->base.pVtab, 1) + && pConfig->bLocale + ){ + rc = fts5SeekCursor(pCsr, 0); + if( rc==SQLITE_OK ){ + const char *zDummy = 0; + int nDummy = 0; + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, iCol, &zDummy, &nDummy); + if( rc==SQLITE_OK ){ + *pzLocale = pConfig->t.pLocale; + *pnLocale = pConfig->t.nLocale; + } + sqlite3Fts5ClearLocale(pConfig); + } + } + + return rc; +} + static const Fts5ExtensionApi sFts5Api = { - 3, /* iVersion */ + 4, /* iVersion */ fts5ApiUserData, fts5ApiColumnCount, fts5ApiRowCount, @@ -2414,7 +2810,9 @@ static const Fts5ExtensionApi sFts5Api = { fts5ApiPhraseFirstColumn, fts5ApiPhraseNextColumn, fts5ApiQueryToken, - fts5ApiInstToken + fts5ApiInstToken, + fts5ApiColumnLocale, + fts5ApiTokenize_v2 }; /* @@ -2465,6 +2863,7 @@ static void fts5ApiInvoke( sqlite3_value **argv ){ assert( pCsr->pAux==0 ); + assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL ); pCsr->pAux = pAux; pAux->xFunc(&sFts5Api, (Fts5Context*)pCsr, context, argc, argv); pCsr->pAux = 0; @@ -2478,6 +2877,21 @@ static Fts5Cursor *fts5CursorFromCsrid(Fts5Global *pGlobal, i64 iCsrId){ return pCsr; } +/* +** Parameter zFmt is a printf() style formatting string. This function +** formats it using the trailing arguments and returns the result as +** an error message to the context passed as the first argument. +*/ +static void fts5ResultError(sqlite3_context *pCtx, const char *zFmt, ...){ + char *zErr = 0; + va_list ap; + va_start(ap, zFmt); + zErr = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); + va_end(ap); +} + static void fts5ApiCallback( sqlite3_context *context, int argc, @@ -2493,12 +2907,13 @@ static void fts5ApiCallback( iCsrId = sqlite3_value_int64(argv[0]); pCsr = fts5CursorFromCsrid(pAux->pGlobal, iCsrId); - if( pCsr==0 || pCsr->ePlan==0 ){ - char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId); - sqlite3_result_error(context, zErr, -1); - sqlite3_free(zErr); + if( pCsr==0 || (pCsr->ePlan==0 || pCsr->ePlan==FTS5_PLAN_SPECIAL) ){ + fts5ResultError(context, "no such cursor: %lld", iCsrId); }else{ + sqlite3_vtab *pTab = pCsr->base.pVtab; fts5ApiInvoke(pAux, pCsr, context, argc-1, &argv[1]); + sqlite3_free(pTab->zErrMsg); + pTab->zErrMsg = 0; } } @@ -2616,8 +3031,8 @@ static int fts5ColumnMethod( ** auxiliary function. */ sqlite3_result_int64(pCtx, pCsr->iCsrId); }else if( iCol==pConfig->nCol+1 ){ - /* The value of the "rank" column. */ + if( pCsr->ePlan==FTS5_PLAN_SOURCE ){ fts5PoslistBlob(pCtx, pCsr); }else if( @@ -2628,20 +3043,32 @@ static int fts5ColumnMethod( fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, pCsr->nRankArg, pCsr->apRankArg); } } - }else if( !fts5IsContentless(pTab) ){ - pConfig->pzErrmsg = &pTab->p.base.zErrMsg; - rc = fts5SeekCursor(pCsr, 1); - if( rc==SQLITE_OK ){ - sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); + }else{ + if( !sqlite3_vtab_nochange(pCtx) && pConfig->eContent!=FTS5_CONTENT_NONE ){ + pConfig->pzErrmsg = &pTab->p.base.zErrMsg; + rc = fts5SeekCursor(pCsr, 1); + if( rc==SQLITE_OK ){ + sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1); + if( pConfig->bLocale + && pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + const char *z = 0; + int n = 0; + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, iCol, &z, &n); + if( rc==SQLITE_OK ){ + sqlite3_result_text(pCtx, z, n, SQLITE_TRANSIENT); + } + sqlite3Fts5ClearLocale(pConfig); + }else{ + sqlite3_result_value(pCtx, pVal); + } + } + + pConfig->pzErrmsg = 0; } - pConfig->pzErrmsg = 0; - }else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){ - char *zErr = sqlite3_mprintf("cannot UPDATE a subset of " - "columns on fts5 contentless-delete table: %s", pConfig->zName - ); - sqlite3_result_error(pCtx, zErr, -1); - sqlite3_free(zErr); } + return rc; } @@ -2781,47 +3208,210 @@ static int fts5CreateAux( } /* -** Register a new tokenizer. This is the implementation of the -** fts5_api.xCreateTokenizer() method. +** This function is used by xCreateTokenizer_v2() and xCreateTokenizer(). +** It allocates and partially populates a new Fts5TokenizerModule object. +** The new object is already linked into the Fts5Global context before +** returning. +** +** If successful, SQLITE_OK is returned and a pointer to the new +** Fts5TokenizerModule object returned via output parameter (*ppNew). All +** that is required is for the caller to fill in the methods in +** Fts5TokenizerModule.x1 and x2, and to set Fts5TokenizerModule.bV2Native +** as appropriate. +** +** If an error occurs, an SQLite error code is returned and the final value +** of (*ppNew) undefined. */ -static int fts5CreateTokenizer( - fts5_api *pApi, /* Global context (one per db handle) */ +static int fts5NewTokenizerModule( + Fts5Global *pGlobal, /* Global context (one per db handle) */ const char *zName, /* Name of new function */ void *pUserData, /* User data for aux. function */ - fts5_tokenizer *pTokenizer, /* Tokenizer implementation */ - void(*xDestroy)(void*) /* Destructor for pUserData */ + void(*xDestroy)(void*), /* Destructor for pUserData */ + Fts5TokenizerModule **ppNew ){ - Fts5Global *pGlobal = (Fts5Global*)pApi; - Fts5TokenizerModule *pNew; - sqlite3_int64 nName; /* Size of zName and its \0 terminator */ - sqlite3_int64 nByte; /* Bytes of space to allocate */ int rc = SQLITE_OK; + Fts5TokenizerModule *pNew; + sqlite3_int64 nName; /* Size of zName and its \0 terminator */ + sqlite3_int64 nByte; /* Bytes of space to allocate */ nName = strlen(zName) + 1; nByte = sizeof(Fts5TokenizerModule) + nName; - pNew = (Fts5TokenizerModule*)sqlite3_malloc64(nByte); + *ppNew = pNew = (Fts5TokenizerModule*)sqlite3Fts5MallocZero(&rc, nByte); if( pNew ){ - memset(pNew, 0, (size_t)nByte); pNew->zName = (char*)&pNew[1]; memcpy(pNew->zName, zName, nName); pNew->pUserData = pUserData; - pNew->x = *pTokenizer; pNew->xDestroy = xDestroy; pNew->pNext = pGlobal->pTok; pGlobal->pTok = pNew; if( pNew->pNext==0 ){ pGlobal->pDfltTok = pNew; } + } + + return rc; +} + +/* +** An instance of this type is used as the Fts5Tokenizer object for +** wrapper tokenizers - those that provide access to a v1 tokenizer via +** the fts5_tokenizer_v2 API, and those that provide access to a v2 tokenizer +** via the fts5_tokenizer API. +*/ +typedef struct Fts5VtoVTokenizer Fts5VtoVTokenizer; +struct Fts5VtoVTokenizer { + int bV2Native; /* True if v2 native tokenizer */ + fts5_tokenizer x1; /* Tokenizer functions */ + fts5_tokenizer_v2 x2; /* V2 tokenizer functions */ + Fts5Tokenizer *pReal; +}; + +/* +** Create a wrapper tokenizer. The context argument pCtx points to the +** Fts5TokenizerModule object. +*/ +static int fts5VtoVCreate( + void *pCtx, + const char **azArg, + int nArg, + Fts5Tokenizer **ppOut +){ + Fts5TokenizerModule *pMod = (Fts5TokenizerModule*)pCtx; + Fts5VtoVTokenizer *pNew = 0; + int rc = SQLITE_OK; + + pNew = (Fts5VtoVTokenizer*)sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); + if( rc==SQLITE_OK ){ + pNew->x1 = pMod->x1; + pNew->x2 = pMod->x2; + pNew->bV2Native = pMod->bV2Native; + if( pMod->bV2Native ){ + rc = pMod->x2.xCreate(pMod->pUserData, azArg, nArg, &pNew->pReal); + }else{ + rc = pMod->x1.xCreate(pMod->pUserData, azArg, nArg, &pNew->pReal); + } + if( rc!=SQLITE_OK ){ + sqlite3_free(pNew); + pNew = 0; + } + } + + *ppOut = (Fts5Tokenizer*)pNew; + return rc; +} + +/* +** Delete an Fts5VtoVTokenizer wrapper tokenizer. +*/ +static void fts5VtoVDelete(Fts5Tokenizer *pTok){ + Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok; + if( p ){ + if( p->bV2Native ){ + p->x2.xDelete(p->pReal); + }else{ + p->x1.xDelete(p->pReal); + } + sqlite3_free(p); + } +} + + +/* +** xTokenizer method for a wrapper tokenizer that offers the v1 interface +** (no support for locales). +*/ +static int fts5V1toV2Tokenize( + Fts5Tokenizer *pTok, + void *pCtx, int flags, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int, int, int) +){ + Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok; + assert( p->bV2Native ); + return p->x2.xTokenize(p->pReal, pCtx, flags, pText, nText, 0, 0, xToken); +} + +/* +** xTokenizer method for a wrapper tokenizer that offers the v2 interface +** (with locale support). +*/ +static int fts5V2toV1Tokenize( + Fts5Tokenizer *pTok, + void *pCtx, int flags, + const char *pText, int nText, + const char *pLocale, int nLocale, + int (*xToken)(void*, int, const char*, int, int, int) +){ + Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok; + assert( p->bV2Native==0 ); + UNUSED_PARAM2(pLocale,nLocale); + return p->x1.xTokenize(p->pReal, pCtx, flags, pText, nText, xToken); +} + +/* +** Register a new tokenizer. This is the implementation of the +** fts5_api.xCreateTokenizer_v2() method. +*/ +static int fts5CreateTokenizer_v2( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void *pUserData, /* User data for aux. function */ + fts5_tokenizer_v2 *pTokenizer, /* Tokenizer implementation */ + void(*xDestroy)(void*) /* Destructor for pUserData */ +){ + Fts5Global *pGlobal = (Fts5Global*)pApi; + int rc = SQLITE_OK; + + if( pTokenizer->iVersion>2 ){ + rc = SQLITE_ERROR; }else{ - rc = SQLITE_NOMEM; + Fts5TokenizerModule *pNew = 0; + rc = fts5NewTokenizerModule(pGlobal, zName, pUserData, xDestroy, &pNew); + if( pNew ){ + pNew->x2 = *pTokenizer; + pNew->bV2Native = 1; + pNew->x1.xCreate = fts5VtoVCreate; + pNew->x1.xTokenize = fts5V1toV2Tokenize; + pNew->x1.xDelete = fts5VtoVDelete; + } } return rc; } +/* +** The fts5_api.xCreateTokenizer() method. +*/ +static int fts5CreateTokenizer( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void *pUserData, /* User data for aux. function */ + fts5_tokenizer *pTokenizer, /* Tokenizer implementation */ + void(*xDestroy)(void*) /* Destructor for pUserData */ +){ + Fts5TokenizerModule *pNew = 0; + int rc = SQLITE_OK; + + rc = fts5NewTokenizerModule( + (Fts5Global*)pApi, zName, pUserData, xDestroy, &pNew + ); + if( pNew ){ + pNew->x1 = *pTokenizer; + pNew->x2.xCreate = fts5VtoVCreate; + pNew->x2.xTokenize = fts5V2toV1Tokenize; + pNew->x2.xDelete = fts5VtoVDelete; + } + return rc; +} + +/* +** Search the global context passed as the first argument for a tokenizer +** module named zName. If found, return a pointer to the Fts5TokenizerModule +** object. Otherwise, return NULL. +*/ static Fts5TokenizerModule *fts5LocateTokenizer( - Fts5Global *pGlobal, - const char *zName + Fts5Global *pGlobal, /* Global (one per db handle) object */ + const char *zName /* Name of tokenizer module to find */ ){ Fts5TokenizerModule *pMod = 0; @@ -2836,6 +3426,36 @@ static Fts5TokenizerModule *fts5LocateTokenizer( return pMod; } +/* +** Find a tokenizer. This is the implementation of the +** fts5_api.xFindTokenizer_v2() method. +*/ +static int fts5FindTokenizer_v2( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of tokenizer */ + void **ppUserData, + fts5_tokenizer_v2 **ppTokenizer /* Populate this object */ +){ + int rc = SQLITE_OK; + Fts5TokenizerModule *pMod; + + pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName); + if( pMod ){ + if( pMod->bV2Native ){ + *ppUserData = pMod->pUserData; + }else{ + *ppUserData = (void*)pMod; + } + *ppTokenizer = &pMod->x2; + }else{ + *ppTokenizer = 0; + *ppUserData = 0; + rc = SQLITE_ERROR; + } + + return rc; +} + /* ** Find a tokenizer. This is the implementation of the ** fts5_api.xFindTokenizer() method. @@ -2851,55 +3471,75 @@ static int fts5FindTokenizer( pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName); if( pMod ){ - *pTokenizer = pMod->x; - *ppUserData = pMod->pUserData; + if( pMod->bV2Native==0 ){ + *ppUserData = pMod->pUserData; + }else{ + *ppUserData = (void*)pMod; + } + *pTokenizer = pMod->x1; }else{ - memset(pTokenizer, 0, sizeof(fts5_tokenizer)); + memset(pTokenizer, 0, sizeof(*pTokenizer)); + *ppUserData = 0; rc = SQLITE_ERROR; } return rc; } -int sqlite3Fts5GetTokenizer( - Fts5Global *pGlobal, - const char **azArg, - int nArg, - Fts5Config *pConfig, - char **pzErr -){ - Fts5TokenizerModule *pMod; +/* +** Attempt to instantiate the tokenizer. +*/ +int sqlite3Fts5LoadTokenizer(Fts5Config *pConfig){ + const char **azArg = pConfig->t.azArg; + const int nArg = pConfig->t.nArg; + Fts5TokenizerModule *pMod = 0; int rc = SQLITE_OK; - pMod = fts5LocateTokenizer(pGlobal, nArg==0 ? 0 : azArg[0]); + pMod = fts5LocateTokenizer(pConfig->pGlobal, nArg==0 ? 0 : azArg[0]); if( pMod==0 ){ assert( nArg>0 ); rc = SQLITE_ERROR; - if( pzErr ) *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]); + sqlite3Fts5ConfigErrmsg(pConfig, "no such tokenizer: %s", azArg[0]); }else{ - rc = pMod->x.xCreate( - pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->pTok + int (*xCreate)(void*, const char**, int, Fts5Tokenizer**) = 0; + if( pMod->bV2Native ){ + xCreate = pMod->x2.xCreate; + pConfig->t.pApi2 = &pMod->x2; + }else{ + pConfig->t.pApi1 = &pMod->x1; + xCreate = pMod->x1.xCreate; + } + + rc = xCreate(pMod->pUserData, + (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->t.pTok ); - pConfig->pTokApi = &pMod->x; + if( rc!=SQLITE_OK ){ - if( pzErr && rc!=SQLITE_NOMEM ){ - *pzErr = sqlite3_mprintf("error in tokenizer constructor"); + if( rc!=SQLITE_NOMEM ){ + sqlite3Fts5ConfigErrmsg(pConfig, "error in tokenizer constructor"); } - }else{ - pConfig->ePattern = sqlite3Fts5TokenizerPattern( - pMod->x.xCreate, pConfig->pTok + }else if( pMod->bV2Native==0 ){ + pConfig->t.ePattern = sqlite3Fts5TokenizerPattern( + pMod->x1.xCreate, pConfig->t.pTok ); } } if( rc!=SQLITE_OK ){ - pConfig->pTokApi = 0; - pConfig->pTok = 0; + pConfig->t.pApi1 = 0; + pConfig->t.pApi2 = 0; + pConfig->t.pTok = 0; } return rc; } + +/* +** xDestroy callback passed to sqlite3_create_module(). This is invoked +** when the db handle is being closed. Free memory associated with +** tokenizers and aux functions registered with this db handle. +*/ static void fts5ModuleDestroy(void *pCtx){ Fts5TokenizerModule *pTok, *pNextTok; Fts5Auxiliary *pAux, *pNextAux; @@ -2920,6 +3560,10 @@ static void fts5ModuleDestroy(void *pCtx){ sqlite3_free(pGlobal); } +/* +** Implementation of the fts5() function used by clients to obtain the +** API pointer. +*/ static void fts5Fts5Func( sqlite3_context *pCtx, /* Function call context */ int nArg, /* Number of args */ @@ -2946,6 +3590,67 @@ static void fts5SourceIdFunc( sqlite3_result_text(pCtx, "--FTS5-SOURCE-ID--", -1, SQLITE_TRANSIENT); } +/* +** Implementation of fts5_locale(LOCALE, TEXT) function. +** +** If parameter LOCALE is NULL, or a zero-length string, then a copy of +** TEXT is returned. Otherwise, both LOCALE and TEXT are interpreted as +** text, and the value returned is a blob consisting of: +** +** * The 4 bytes 0x00, 0xE0, 0xB2, 0xEb (FTS5_LOCALE_HEADER). +** * The LOCALE, as utf-8 text, followed by +** * 0x00, followed by +** * The TEXT, as utf-8 text. +** +** There is no final nul-terminator following the TEXT value. +*/ +static void fts5LocaleFunc( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apArg /* Function arguments */ +){ + const char *zLocale = 0; + int nLocale = 0; + const char *zText = 0; + int nText = 0; + + assert( nArg==2 ); + UNUSED_PARAM(nArg); + + zLocale = (const char*)sqlite3_value_text(apArg[0]); + nLocale = sqlite3_value_bytes(apArg[0]); + + zText = (const char*)sqlite3_value_text(apArg[1]); + nText = sqlite3_value_bytes(apArg[1]); + + if( zLocale==0 || zLocale[0]=='\0' ){ + sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT); + }else{ + Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx); + u8 *pBlob = 0; + u8 *pCsr = 0; + int nBlob = 0; + + nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText; + pBlob = (u8*)sqlite3_malloc(nBlob); + if( pBlob==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + + pCsr = pBlob; + memcpy(pCsr, (const u8*)p->aLocaleHdr, FTS5_LOCALE_HDR_SIZE); + pCsr += FTS5_LOCALE_HDR_SIZE; + memcpy(pCsr, zLocale, nLocale); + pCsr += nLocale; + (*pCsr++) = 0x00; + if( zText ) memcpy(pCsr, zText, nText); + assert( &pCsr[nText]==&pBlob[nBlob] ); + + sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free); + } +} + /* ** Return true if zName is the extension on one of the shadow tables used ** by this module. @@ -3038,10 +3743,22 @@ static int fts5Init(sqlite3 *db){ void *p = (void*)pGlobal; memset(pGlobal, 0, sizeof(Fts5Global)); pGlobal->db = db; - pGlobal->api.iVersion = 2; + pGlobal->api.iVersion = 3; pGlobal->api.xCreateFunction = fts5CreateAux; pGlobal->api.xCreateTokenizer = fts5CreateTokenizer; pGlobal->api.xFindTokenizer = fts5FindTokenizer; + pGlobal->api.xCreateTokenizer_v2 = fts5CreateTokenizer_v2; + pGlobal->api.xFindTokenizer_v2 = fts5FindTokenizer_v2; + + /* Initialize pGlobal->aLocaleHdr[] to a 128-bit pseudo-random vector. + ** The constants below were generated randomly. */ + sqlite3_randomness(sizeof(pGlobal->aLocaleHdr), pGlobal->aLocaleHdr); + pGlobal->aLocaleHdr[0] ^= 0xF924976D; + pGlobal->aLocaleHdr[1] ^= 0x16596E13; + pGlobal->aLocaleHdr[2] ^= 0x7C80BEAA; + pGlobal->aLocaleHdr[3] ^= 0x9B03A67F; + assert( sizeof(pGlobal->aLocaleHdr)==16 ); + rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy); if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db); if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db); @@ -3060,6 +3777,13 @@ static int fts5Init(sqlite3 *db){ p, fts5SourceIdFunc, 0, 0 ); } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + db, "fts5_locale", 2, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE, + p, fts5LocaleFunc, 0, 0 + ); + } } /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index a04b152fb..31f5fc5dc 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -16,13 +16,40 @@ #include "fts5Int.h" +/* +** pSavedRow: +** SQL statement FTS5_STMT_LOOKUP2 is a copy of FTS5_STMT_LOOKUP, it +** does a by-rowid lookup to retrieve a single row from the %_content +** table or equivalent external-content table/view. +** +** However, FTS5_STMT_LOOKUP2 is only used when retrieving the original +** values for a row being UPDATEd. In that case, the SQL statement is +** not reset and pSavedRow is set to point at it. This is so that the +** insert operation that follows the delete may access the original +** row values for any new values for which sqlite3_value_nochange() returns +** true. i.e. if the user executes: +** +** CREATE VIRTUAL TABLE ft USING fts5(a, b, c, locale=1); +** ... +** UPDATE fts SET a=?, b=? WHERE rowid=?; +** +** then the value passed to the xUpdate() method of this table as the +** new.c value is an sqlite3_value_nochange() value. So in this case it +** must be read from the saved row stored in Fts5Storage.pSavedRow. +** +** This is necessary - using sqlite3_value_nochange() instead of just having +** SQLite pass the original value back via xUpdate() - so as not to discard +** any locale information associated with such values. +** +*/ struct Fts5Storage { Fts5Config *pConfig; Fts5Index *pIndex; int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */ i64 nTotalRow; /* Total number of rows in FTS table */ i64 *aTotalSize; /* Total sizes of each column */ - sqlite3_stmt *aStmt[11]; + sqlite3_stmt *pSavedRow; + sqlite3_stmt *aStmt[12]; }; @@ -36,14 +63,15 @@ struct Fts5Storage { # error "FTS5_STMT_LOOKUP mismatch" #endif -#define FTS5_STMT_INSERT_CONTENT 3 -#define FTS5_STMT_REPLACE_CONTENT 4 -#define FTS5_STMT_DELETE_CONTENT 5 -#define FTS5_STMT_REPLACE_DOCSIZE 6 -#define FTS5_STMT_DELETE_DOCSIZE 7 -#define FTS5_STMT_LOOKUP_DOCSIZE 8 -#define FTS5_STMT_REPLACE_CONFIG 9 -#define FTS5_STMT_SCAN 10 +#define FTS5_STMT_LOOKUP2 3 +#define FTS5_STMT_INSERT_CONTENT 4 +#define FTS5_STMT_REPLACE_CONTENT 5 +#define FTS5_STMT_DELETE_CONTENT 6 +#define FTS5_STMT_REPLACE_DOCSIZE 7 +#define FTS5_STMT_DELETE_DOCSIZE 8 +#define FTS5_STMT_LOOKUP_DOCSIZE 9 +#define FTS5_STMT_REPLACE_CONFIG 10 +#define FTS5_STMT_SCAN 11 /* ** Prepare the two insert statements - Fts5Storage.pInsertContent and @@ -73,6 +101,7 @@ static int fts5StorageGetStmt( "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC", "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC", "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP */ + "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP2 */ "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ @@ -88,6 +117,8 @@ static int fts5StorageGetStmt( Fts5Config *pC = p->pConfig; char *zSql = 0; + assert( ArraySize(azStmt)==ArraySize(p->aStmt) ); + switch( eStmt ){ case FTS5_STMT_SCAN: zSql = sqlite3_mprintf(azStmt[eStmt], @@ -104,6 +135,7 @@ static int fts5StorageGetStmt( break; case FTS5_STMT_LOOKUP: + case FTS5_STMT_LOOKUP2: zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, pC->zContent, pC->zContentRowid ); @@ -111,20 +143,35 @@ static int fts5StorageGetStmt( case FTS5_STMT_INSERT_CONTENT: case FTS5_STMT_REPLACE_CONTENT: { - int nCol = pC->nCol + 1; - char *zBind; + char *zBind = 0; int i; - zBind = sqlite3_malloc64(1 + nCol*2); - if( zBind ){ - for(i=0; ieContent==FTS5_CONTENT_NORMAL + || pC->eContent==FTS5_CONTENT_UNINDEXED + ); + + /* Add bindings for the "c*" columns - those that store the actual + ** table content. If eContent==NORMAL, then there is one binding + ** for each column. Or, if eContent==UNINDEXED, then there are only + ** bindings for the UNINDEXED columns. */ + for(i=0; rc==SQLITE_OK && i<(pC->nCol+1); i++){ + if( !i || pC->eContent==FTS5_CONTENT_NORMAL || pC->abUnindexed[i-1] ){ + zBind = sqlite3Fts5Mprintf(&rc, "%z%s?%d", zBind, zBind?",":"",i+1); } - zBind[i*2-1] = '\0'; - zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind); - sqlite3_free(zBind); } + + /* Add bindings for any "l*" columns. Only non-UNINDEXED columns + ** require these. */ + if( pC->bLocale && pC->eContent==FTS5_CONTENT_NORMAL ){ + for(i=0; rc==SQLITE_OK && inCol; i++){ + if( pC->abUnindexed[i]==0 ){ + zBind = sqlite3Fts5Mprintf(&rc, "%z,?%d", zBind, pC->nCol+i+2); + } + } + } + + zSql = sqlite3Fts5Mprintf(&rc, azStmt[eStmt], pC->zDb, pC->zName,zBind); + sqlite3_free(zBind); break; } @@ -150,7 +197,7 @@ static int fts5StorageGetStmt( rc = SQLITE_NOMEM; }else{ int f = SQLITE_PREPARE_PERSISTENT; - if( eStmt>FTS5_STMT_LOOKUP ) f |= SQLITE_PREPARE_NO_VTAB; + if( eStmt>FTS5_STMT_LOOKUP2 ) f |= SQLITE_PREPARE_NO_VTAB; p->pConfig->bLock++; rc = sqlite3_prepare_v3(pC->db, zSql, -1, f, &p->aStmt[eStmt], 0); p->pConfig->bLock--; @@ -310,9 +357,11 @@ int sqlite3Fts5StorageOpen( p->pIndex = pIndex; if( bCreate ){ - if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL + || pConfig->eContent==FTS5_CONTENT_UNINDEXED + ){ int nDefn = 32 + pConfig->nCol*10; - char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 10); + char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 20); if( zDefn==0 ){ rc = SQLITE_NOMEM; }else{ @@ -321,8 +370,20 @@ int sqlite3Fts5StorageOpen( sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); iOff = (int)strlen(zDefn); for(i=0; inCol; i++){ - sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); - iOff += (int)strlen(&zDefn[iOff]); + if( pConfig->eContent==FTS5_CONTENT_NORMAL + || pConfig->abUnindexed[i] + ){ + sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); + iOff += (int)strlen(&zDefn[iOff]); + } + } + if( pConfig->bLocale ){ + for(i=0; inCol; i++){ + if( pConfig->abUnindexed[i]==0 ){ + sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", l%d", i); + iOff += (int)strlen(&zDefn[iOff]); + } + } } rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); } @@ -399,15 +460,49 @@ static int fts5StorageInsertCallback( return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken); } +/* +** This function is used as part of an UPDATE statement that modifies the +** rowid of a row. In that case, this function is called first to set +** Fts5Storage.pSavedRow to point to a statement that may be used to +** access the original values of the row being deleted - iDel. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +** It is not considered an error if row iDel does not exist. In this case +** pSavedRow is not set and SQLITE_OK returned. +*/ +int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel){ + int rc = SQLITE_OK; + sqlite3_stmt *pSeek = 0; + + assert( p->pSavedRow==0 ); + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+1, &pSeek, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pSeek, 1, iDel); + if( sqlite3_step(pSeek)!=SQLITE_ROW ){ + rc = sqlite3_reset(pSeek); + }else{ + p->pSavedRow = pSeek; + } + } + + return rc; +} + /* ** If a row with rowid iDel is present in the %_content table, add the ** delete-markers to the FTS index necessary to delete it. Do not actually ** remove the %_content row at this time though. +** +** If parameter bSaveRow is true, then Fts5Storage.pSavedRow is left +** pointing to a statement (FTS5_STMT_LOOKUP2) that may be used to access +** the original values of the row being deleted. This is used by UPDATE +** statements. */ static int fts5StorageDeleteFromIndex( Fts5Storage *p, i64 iDel, - sqlite3_value **apVal + sqlite3_value **apVal, + int bSaveRow /* True to set pSavedRow */ ){ Fts5Config *pConfig = p->pConfig; sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */ @@ -416,12 +511,21 @@ static int fts5StorageDeleteFromIndex( int iCol; Fts5InsertCtx ctx; + assert( bSaveRow==0 || apVal==0 ); + assert( bSaveRow==0 || bSaveRow==1 ); + assert( FTS5_STMT_LOOKUP2==FTS5_STMT_LOOKUP+1 ); + if( apVal==0 ){ - rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0); - if( rc!=SQLITE_OK ) return rc; - sqlite3_bind_int64(pSeek, 1, iDel); - if( sqlite3_step(pSeek)!=SQLITE_ROW ){ - return sqlite3_reset(pSeek); + if( p->pSavedRow && bSaveRow ){ + pSeek = p->pSavedRow; + p->pSavedRow = 0; + }else{ + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+bSaveRow, &pSeek, 0); + if( rc!=SQLITE_OK ) return rc; + sqlite3_bind_int64(pSeek, 1, iDel); + if( sqlite3_step(pSeek)!=SQLITE_ROW ){ + return sqlite3_reset(pSeek); + } } } @@ -429,26 +533,42 @@ static int fts5StorageDeleteFromIndex( ctx.iCol = -1; for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1]==0 ){ - const char *zText; - int nText; + sqlite3_value *pVal = 0; + const char *pText = 0; + int nText = 0; + const char *pLoc = 0; + int nLoc = 0; + assert( pSeek==0 || apVal==0 ); assert( pSeek!=0 || apVal!=0 ); if( pSeek ){ - zText = (const char*)sqlite3_column_text(pSeek, iCol); - nText = sqlite3_column_bytes(pSeek, iCol); - }else if( ALWAYS(apVal) ){ - zText = (const char*)sqlite3_value_text(apVal[iCol-1]); - nText = sqlite3_value_bytes(apVal[iCol-1]); + pVal = sqlite3_column_value(pSeek, iCol); }else{ - continue; + pVal = apVal[iCol-1]; } - ctx.szCol = 0; - rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, - zText, nText, (void*)&ctx, fts5StorageInsertCallback - ); - p->aTotalSize[iCol-1] -= (i64)ctx.szCol; - if( p->aTotalSize[iCol-1]<0 ){ - rc = FTS5_CORRUPT; + + if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + }else{ + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + if( pConfig->bLocale && pSeek ){ + pLoc = (const char*)sqlite3_column_text(pSeek, iCol + pConfig->nCol); + nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol); + } + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + ctx.szCol = 0; + rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, + pText, nText, (void*)&ctx, fts5StorageInsertCallback + ); + p->aTotalSize[iCol-1] -= (i64)ctx.szCol; + if( rc==SQLITE_OK && p->aTotalSize[iCol-1]<0 ){ + rc = FTS5_CORRUPT; + } + sqlite3Fts5ClearLocale(pConfig); } } } @@ -458,11 +578,29 @@ static int fts5StorageDeleteFromIndex( p->nTotalRow--; } - rc2 = sqlite3_reset(pSeek); - if( rc==SQLITE_OK ) rc = rc2; + if( rc==SQLITE_OK && bSaveRow ){ + assert( p->pSavedRow==0 ); + p->pSavedRow = pSeek; + }else{ + rc2 = sqlite3_reset(pSeek); + if( rc==SQLITE_OK ) rc = rc2; + } return rc; } +/* +** Reset any saved statement pSavedRow. Zero pSavedRow as well. This +** should be called by the xUpdate() method of the fts5 table before +** returning from any operation that may have set Fts5Storage.pSavedRow. +*/ +void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){ + assert( pStorage->pSavedRow==0 + || pStorage->pSavedRow==pStorage->aStmt[FTS5_STMT_LOOKUP2] + ); + sqlite3_reset(pStorage->pSavedRow); + pStorage->pSavedRow = 0; +} + /* ** This function is called to process a DELETE on a contentless_delete=1 ** table. It adds the tombstone required to delete the entry with rowid @@ -475,7 +613,9 @@ static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){ int rc = SQLITE_OK; assert( p->pConfig->bContentlessDelete ); - assert( p->pConfig->eContent==FTS5_CONTENT_NONE ); + assert( p->pConfig->eContent==FTS5_CONTENT_NONE + || p->pConfig->eContent==FTS5_CONTENT_UNINDEXED + ); /* Look up the origin of the document in the %_docsize table. Store ** this in stack variable iOrigin. */ @@ -519,12 +659,12 @@ static int fts5StorageInsertDocsize( rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin); sqlite3_bind_int64(pReplace, 3, iOrigin); } - if( rc==SQLITE_OK ){ - sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); - sqlite3_step(pReplace); - rc = sqlite3_reset(pReplace); - sqlite3_bind_null(pReplace, 2); - } + } + if( rc==SQLITE_OK ){ + sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); + sqlite3_step(pReplace); + rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 2); } } return rc; @@ -578,7 +718,12 @@ static int fts5StorageSaveTotals(Fts5Storage *p){ /* ** Remove a row from the FTS table. */ -int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){ +int sqlite3Fts5StorageDelete( + Fts5Storage *p, /* Storage object */ + i64 iDel, /* Rowid to delete from table */ + sqlite3_value **apVal, /* Optional - values to remove from index */ + int bSaveRow /* If true, set pSavedRow for deleted row */ +){ Fts5Config *pConfig = p->pConfig; int rc; sqlite3_stmt *pDel = 0; @@ -594,8 +739,14 @@ int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){ if( rc==SQLITE_OK ){ if( p->pConfig->bContentlessDelete ){ rc = fts5StorageContentlessDelete(p, iDel); + if( rc==SQLITE_OK + && bSaveRow + && p->pConfig->eContent==FTS5_CONTENT_UNINDEXED + ){ + rc = sqlite3Fts5StorageFindDeleteRow(p, iDel); + } }else{ - rc = fts5StorageDeleteFromIndex(p, iDel, apVal); + rc = fts5StorageDeleteFromIndex(p, iDel, apVal, bSaveRow); } } @@ -610,7 +761,9 @@ int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){ } /* Delete the %_content record */ - if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL + || pConfig->eContent==FTS5_CONTENT_UNINDEXED + ){ if( rc==SQLITE_OK ){ rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0); } @@ -642,8 +795,13 @@ int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){ ); if( rc==SQLITE_OK && pConfig->bColumnsize ){ rc = fts5ExecPrintf(pConfig->db, 0, - "DELETE FROM %Q.'%q_docsize';", - pConfig->zDb, pConfig->zName + "DELETE FROM %Q.'%q_docsize';", pConfig->zDb, pConfig->zName + ); + } + + if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_UNINDEXED ){ + rc = fts5ExecPrintf(pConfig->db, 0, + "DELETE FROM %Q.'%q_content';", pConfig->zDb, pConfig->zName ); } @@ -684,14 +842,36 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){ for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){ ctx.szCol = 0; if( pConfig->abUnindexed[ctx.iCol]==0 ){ - const char *zText = (const char*)sqlite3_column_text(pScan, ctx.iCol+1); - int nText = sqlite3_column_bytes(pScan, ctx.iCol+1); - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - zText, nText, - (void*)&ctx, - fts5StorageInsertCallback - ); + int nText = 0; /* Size of pText in bytes */ + const char *pText = 0; /* Pointer to buffer containing text value */ + int nLoc = 0; /* Size of pLoc in bytes */ + const char *pLoc = 0; /* Pointer to buffer containing text value */ + + sqlite3_value *pVal = sqlite3_column_value(pScan, ctx.iCol+1); + if( pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + }else{ + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + if( pConfig->bLocale ){ + int iCol = ctx.iCol + 1 + pConfig->nCol; + pLoc = (const char*)sqlite3_column_text(pScan, iCol); + nLoc = sqlite3_column_bytes(pScan, iCol); + } + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + pText, nText, + (void*)&ctx, + fts5StorageInsertCallback + ); + sqlite3Fts5ClearLocale(pConfig); + } } sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; @@ -757,6 +937,7 @@ static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){ */ int sqlite3Fts5StorageContentInsert( Fts5Storage *p, + int bReplace, /* True to use REPLACE instead of INSERT */ sqlite3_value **apVal, i64 *piRowid ){ @@ -764,7 +945,9 @@ int sqlite3Fts5StorageContentInsert( int rc = SQLITE_OK; /* Insert the new row into the %_content table. */ - if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent!=FTS5_CONTENT_NORMAL + && pConfig->eContent!=FTS5_CONTENT_UNINDEXED + ){ if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ *piRowid = sqlite3_value_int64(apVal[1]); }else{ @@ -773,9 +956,52 @@ int sqlite3Fts5StorageContentInsert( }else{ sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */ int i; /* Counter variable */ - rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0); - for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ - rc = sqlite3_bind_value(pInsert, i, apVal[i]); + + assert( FTS5_STMT_INSERT_CONTENT+1==FTS5_STMT_REPLACE_CONTENT ); + assert( bReplace==0 || bReplace==1 ); + rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT+bReplace, &pInsert, 0); + if( pInsert ) sqlite3_clear_bindings(pInsert); + + /* Bind the rowid value */ + sqlite3_bind_value(pInsert, 1, apVal[1]); + + /* Loop through values for user-defined columns. i=2 is the leftmost + ** user-defined column. As is column 1 of pSavedRow. */ + for(i=2; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ + int bUnindexed = pConfig->abUnindexed[i-2]; + if( pConfig->eContent==FTS5_CONTENT_NORMAL || bUnindexed ){ + sqlite3_value *pVal = apVal[i]; + + if( sqlite3_value_nochange(pVal) && p->pSavedRow ){ + /* This is an UPDATE statement, and user-defined column (i-2) was not + ** modified. Retrieve the value from Fts5Storage.pSavedRow. */ + pVal = sqlite3_column_value(p->pSavedRow, i-1); + if( pConfig->bLocale && bUnindexed==0 ){ + sqlite3_bind_value(pInsert, pConfig->nCol + i, + sqlite3_column_value(p->pSavedRow, pConfig->nCol + i - 1) + ); + } + }else if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + const char *pText = 0; + const char *pLoc = 0; + int nText = 0; + int nLoc = 0; + assert( pConfig->bLocale ); + + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + if( rc==SQLITE_OK ){ + sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT); + if( bUnindexed==0 ){ + int iLoc = pConfig->nCol + i; + sqlite3_bind_text(pInsert, iLoc, pLoc, nLoc, SQLITE_TRANSIENT); + } + } + + continue; + } + + rc = sqlite3_bind_value(pInsert, i, pVal); + } } if( rc==SQLITE_OK ){ sqlite3_step(pInsert); @@ -810,14 +1036,38 @@ int sqlite3Fts5StorageIndexInsert( for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){ ctx.szCol = 0; if( pConfig->abUnindexed[ctx.iCol]==0 ){ - const char *zText = (const char*)sqlite3_value_text(apVal[ctx.iCol+2]); - int nText = sqlite3_value_bytes(apVal[ctx.iCol+2]); - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - zText, nText, - (void*)&ctx, - fts5StorageInsertCallback - ); + int nText = 0; /* Size of pText in bytes */ + const char *pText = 0; /* Pointer to buffer containing text value */ + int nLoc = 0; /* Size of pText in bytes */ + const char *pLoc = 0; /* Pointer to buffer containing text value */ + + sqlite3_value *pVal = apVal[ctx.iCol+2]; + if( p->pSavedRow && sqlite3_value_nochange(pVal) ){ + pVal = sqlite3_column_value(p->pSavedRow, ctx.iCol+1); + if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){ + int iCol = ctx.iCol + 1 + pConfig->nCol; + pLoc = (const char*)sqlite3_column_text(p->pSavedRow, iCol); + nLoc = sqlite3_column_bytes(p->pSavedRow, iCol); + } + }else{ + pVal = apVal[ctx.iCol+2]; + } + + if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + }else{ + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx, + fts5StorageInsertCallback + ); + sqlite3Fts5ClearLocale(pConfig); + } } sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; @@ -981,29 +1231,61 @@ int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){ rc = sqlite3Fts5TermsetNew(&ctx.pTermset); } for(i=0; rc==SQLITE_OK && inCol; i++){ - if( pConfig->abUnindexed[i] ) continue; - ctx.iCol = i; - ctx.szCol = 0; - if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ - rc = sqlite3Fts5TermsetNew(&ctx.pTermset); - } - if( rc==SQLITE_OK ){ - const char *zText = (const char*)sqlite3_column_text(pScan, i+1); - int nText = sqlite3_column_bytes(pScan, i+1); - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - zText, nText, - (void*)&ctx, - fts5StorageIntegrityCallback - ); - } - if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){ - rc = FTS5_CORRUPT; - } - aTotalSize[i] += ctx.szCol; - if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ - sqlite3Fts5TermsetFree(ctx.pTermset); - ctx.pTermset = 0; + if( pConfig->abUnindexed[i]==0 ){ + const char *pText = 0; + int nText = 0; + const char *pLoc = 0; + int nLoc = 0; + sqlite3_value *pVal = sqlite3_column_value(pScan, i+1); + + if( pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + rc = sqlite3Fts5DecodeLocaleValue( + pVal, &pText, &nText, &pLoc, &nLoc + ); + }else{ + if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){ + int iCol = i + 1 + pConfig->nCol; + pLoc = (const char*)sqlite3_column_text(pScan, iCol); + nLoc = sqlite3_column_bytes(pScan, iCol); + } + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + } + + ctx.iCol = i; + ctx.szCol = 0; + + if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + rc = sqlite3Fts5TermsetNew(&ctx.pTermset); + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + pText, nText, + (void*)&ctx, + fts5StorageIntegrityCallback + ); + sqlite3Fts5ClearLocale(pConfig); + } + + /* If this is not a columnsize=0 database, check that the number + ** of tokens in the value matches the aColSize[] value read from + ** the %_docsize table. */ + if( rc==SQLITE_OK + && pConfig->bColumnsize + && ctx.szCol!=aColSize[i] + ){ + rc = FTS5_CORRUPT; + } + aTotalSize[i] += ctx.szCol; + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + sqlite3Fts5TermsetFree(ctx.pTermset); + ctx.pTermset = 0; + } } } sqlite3Fts5TermsetFree(ctx.pTermset); diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c index c5b5f41f8..a8ab44096 100644 --- a/ext/fts5/fts5_tcl.c +++ b/ext/fts5/fts5_tcl.c @@ -14,14 +14,7 @@ #ifdef SQLITE_TEST -#if defined(INCLUDE_SQLITE_TCL_H) -# include "sqlite_tcl.h" -#else -# include "tcl.h" -# ifndef SQLITE_TCLAPI -# define SQLITE_TCLAPI -# endif -#endif +#include "tclsqlite.h" #ifdef SQLITE_ENABLE_FTS5 @@ -103,14 +96,14 @@ static int SQLITE_TCLAPI f5tDbAndApi( rc = sqlite3_prepare_v2(db, "SELECT fts5(?1)", -1, &pStmt, 0); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); + Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (char*)0); return TCL_ERROR; } sqlite3_bind_pointer(pStmt, 1, (void*)&pApi, "fts5_api_ptr", 0); sqlite3_step(pStmt); if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); + Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (char*)0); return TCL_ERROR; } @@ -247,6 +240,7 @@ static int SQLITE_TCLAPI xF5tApi( { "xQueryToken", 2, "IPHRASE ITERM" }, /* 18 */ { "xInstToken", 2, "IDX ITERM" }, /* 19 */ + { "xColumnLocale", 1, "COL" }, /* 20 */ { 0, 0, 0} }; @@ -297,12 +291,12 @@ static int SQLITE_TCLAPI xF5tApi( break; } CASE(3, "xTokenize") { - int nText; + Tcl_Size nText; char *zText = Tcl_GetStringFromObj(objv[2], &nText); F5tFunction ctx; ctx.interp = interp; ctx.pScript = objv[3]; - rc = p->pApi->xTokenize(p->pFts, zText, nText, &ctx, xTokenizeCb); + rc = p->pApi->xTokenize(p->pFts, zText, (int)nText, &ctx, xTokenizeCb); if( rc==SQLITE_OK ){ Tcl_ResetResult(interp); } @@ -398,7 +392,7 @@ static int SQLITE_TCLAPI xF5tApi( CASE(12, "xSetAuxdata") { F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc(sizeof(F5tAuxData)); if( pData==0 ){ - Tcl_AppendResult(interp, "out of memory", 0); + Tcl_AppendResult(interp, "out of memory", (char*)0); return TCL_ERROR; } pData->pObj = objv[2]; @@ -458,7 +452,7 @@ static int SQLITE_TCLAPI xF5tApi( rc = p->pApi->xPhraseFirst(p->pFts, iPhrase, &iter, &iCol, &iOff); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); + Tcl_AppendResult(interp, sqlite3ErrName(rc), (char*)0); return TCL_ERROR; } for( ;iCol>=0; p->pApi->xPhraseNext(p->pFts, &iter, &iCol, &iOff) ){ @@ -535,6 +529,20 @@ static int SQLITE_TCLAPI xF5tApi( break; } + CASE(20, "xColumnLocale") { + const char *z = 0; + int n = 0; + int iCol; + if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ){ + return TCL_ERROR; + } + rc = p->pApi->xColumnLocale(p->pFts, iCol, &z, &n); + if( rc==SQLITE_OK && z ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(z, n)); + } + break; + } + default: assert( 0 ); break; @@ -605,15 +613,16 @@ static void xF5tFunction( sqlite3_result_error(pCtx, Tcl_GetStringResult(p->interp), -1); }else{ Tcl_Obj *pVar = Tcl_GetObjResult(p->interp); - int n; const char *zType = (pVar->typePtr ? pVar->typePtr->name : ""); char c = zType[0]; if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){ /* Only return a BLOB type if the Tcl variable is a bytearray and ** has no string representation. */ - unsigned char *data = Tcl_GetByteArrayFromObj(pVar, &n); - sqlite3_result_blob(pCtx, data, n, SQLITE_TRANSIENT); + Tcl_Size nn; + unsigned char *data = Tcl_GetByteArrayFromObj(pVar, &nn); + sqlite3_result_blob(pCtx, data, (int)nn, SQLITE_TRANSIENT); }else if( c=='b' && strcmp(zType,"boolean")==0 ){ + int n; Tcl_GetIntFromObj(0, pVar, &n); sqlite3_result_int(pCtx, n); }else if( c=='d' && strcmp(zType,"double")==0 ){ @@ -626,8 +635,9 @@ static void xF5tFunction( Tcl_GetWideIntFromObj(0, pVar, &v); sqlite3_result_int64(pCtx, v); }else{ - unsigned char *data = (unsigned char *)Tcl_GetStringFromObj(pVar, &n); - sqlite3_result_text(pCtx, (char *)data, n, SQLITE_TRANSIENT); + Tcl_Size nn; + unsigned char *data = (unsigned char *)Tcl_GetStringFromObj(pVar, &nn); + sqlite3_result_text(pCtx, (char*)data, (int)nn, SQLITE_TRANSIENT); } } } @@ -673,7 +683,7 @@ static int SQLITE_TCLAPI f5tCreateFunction( pApi, zName, (void*)pCtx, xF5tFunction, xF5tDestroy ); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); + Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (char*)0); return TCL_ERROR; } @@ -720,7 +730,7 @@ static int SQLITE_TCLAPI f5tTokenize( Tcl_Obj *CONST objv[] ){ char *zText; - int nText; + Tcl_Size nText; sqlite3 *db = 0; fts5_api *pApi = 0; Fts5Tokenizer *pTok = 0; @@ -729,7 +739,7 @@ static int SQLITE_TCLAPI f5tTokenize( void *pUserdata; int rc; - int nArg; + Tcl_Size nArg; const char **azArg; F5tTokenizeCtx ctx; @@ -740,7 +750,7 @@ static int SQLITE_TCLAPI f5tTokenize( if( objc==5 ){ char *zOpt = Tcl_GetString(objv[1]); if( strcmp("-subst", zOpt) ){ - Tcl_AppendResult(interp, "unrecognized option: ", zOpt, 0); + Tcl_AppendResult(interp, "unrecognized option: ", zOpt, (char*)0); return TCL_ERROR; } } @@ -749,7 +759,7 @@ static int SQLITE_TCLAPI f5tTokenize( return TCL_ERROR; } if( nArg==0 ){ - Tcl_AppendResult(interp, "no such tokenizer: ", 0); + Tcl_AppendResult(interp, "no such tokenizer: ", (char*)0); Tcl_Free((void*)azArg); return TCL_ERROR; } @@ -757,13 +767,13 @@ static int SQLITE_TCLAPI f5tTokenize( rc = pApi->xFindTokenizer(pApi, azArg[0], &pUserdata, &tokenizer); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "no such tokenizer: ", azArg[0], 0); + Tcl_AppendResult(interp, "no such tokenizer: ", azArg[0], (char*)0); return TCL_ERROR; } - rc = tokenizer.xCreate(pUserdata, &azArg[1], nArg-1, &pTok); + rc = tokenizer.xCreate(pUserdata, &azArg[1], (int)(nArg-1), &pTok); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error in tokenizer.xCreate()", 0); + Tcl_AppendResult(interp, "error in tokenizer.xCreate()", (char*)0); return TCL_ERROR; } @@ -773,11 +783,11 @@ static int SQLITE_TCLAPI f5tTokenize( ctx.pRet = pRet; ctx.zInput = zText; rc = tokenizer.xTokenize( - pTok, (void*)&ctx, FTS5_TOKENIZE_DOCUMENT, zText, nText, xTokenizeCb2 + pTok, (void*)&ctx, FTS5_TOKENIZE_DOCUMENT, zText,(int)nText, xTokenizeCb2 ); tokenizer.xDelete(pTok); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error in tokenizer.xTokenize()", 0); + Tcl_AppendResult(interp, "error in tokenizer.xTokenize()", (char*)0); Tcl_DecrRefCount(pRet); return TCL_ERROR; } @@ -801,18 +811,32 @@ typedef struct F5tTokenizerInstance F5tTokenizerInstance; struct F5tTokenizerContext { void *pCtx; int (*xToken)(void*, int, const char*, int, int, int); + F5tTokenizerInstance *pInst; }; struct F5tTokenizerModule { Tcl_Interp *interp; Tcl_Obj *pScript; + void *pParentCtx; + fts5_tokenizer_v2 parent_v2; + fts5_tokenizer parent; F5tTokenizerContext *pContext; }; +/* +** zLocale: +** Within a call to xTokenize_v2(), pLocale/nLocale store the locale +** passed to the call by fts5. This can be retrieved by a Tcl tokenize +** script using [sqlite3_fts5_locale]. +*/ struct F5tTokenizerInstance { Tcl_Interp *interp; Tcl_Obj *pScript; + F5tTokenizerModule *pModule; + Fts5Tokenizer *pParent; F5tTokenizerContext *pContext; + const char *pLocale; + int nLocale; }; static int f5tTokenizerCreate( @@ -821,11 +845,20 @@ static int f5tTokenizerCreate( int nArg, Fts5Tokenizer **ppOut ){ + Fts5Tokenizer *pParent = 0; F5tTokenizerModule *pMod = (F5tTokenizerModule*)pCtx; Tcl_Obj *pEval; int rc = TCL_OK; int i; + assert( pMod->parent_v2.xCreate==0 || pMod->parent.xCreate==0 ); + if( pMod->parent_v2.xCreate ){ + rc = pMod->parent_v2.xCreate(pMod->pParentCtx, 0, 0, &pParent); + } + if( pMod->parent.xCreate ){ + rc = pMod->parent.xCreate(pMod->pParentCtx, 0, 0, &pParent); + } + pEval = Tcl_DuplicateObj(pMod->pScript); Tcl_IncrRefCount(pEval); for(i=0; rc==TCL_OK && iinterp = pMod->interp; pInst->pScript = Tcl_GetObjResult(pMod->interp); pInst->pContext = pMod->pContext; + pInst->pParent = pParent; + pInst->pModule = pMod; Tcl_IncrRefCount(pInst->pScript); *ppOut = (Fts5Tokenizer*)pInst; } @@ -855,11 +890,21 @@ static int f5tTokenizerCreate( static void f5tTokenizerDelete(Fts5Tokenizer *p){ F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p; - Tcl_DecrRefCount(pInst->pScript); - ckfree((char *)pInst); + if( pInst ){ + if( pInst->pParent ){ + if( pInst->pModule->parent_v2.xDelete ){ + pInst->pModule->parent_v2.xDelete(pInst->pParent); + }else{ + pInst->pModule->parent.xDelete(pInst->pParent); + } + } + Tcl_DecrRefCount(pInst->pScript); + ckfree((char *)pInst); + } } -static int f5tTokenizerTokenize( + +static int f5tTokenizerReallyTokenize( Fts5Tokenizer *p, void *pCtx, int flags, @@ -867,6 +912,7 @@ static int f5tTokenizerTokenize( int (*xToken)(void*, int, const char*, int, int, int) ){ F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p; + F5tTokenizerInstance *pOldInst = 0; void *pOldCtx; int (*xOldToken)(void*, int, const char*, int, int, int); Tcl_Obj *pEval; @@ -875,9 +921,11 @@ static int f5tTokenizerTokenize( pOldCtx = pInst->pContext->pCtx; xOldToken = pInst->pContext->xToken; + pOldInst = pInst->pContext->pInst; pInst->pContext->pCtx = pCtx; pInst->pContext->xToken = xToken; + pInst->pContext->pInst = pInst; assert( flags==FTS5_TOKENIZE_DOCUMENT @@ -913,8 +961,104 @@ static int f5tTokenizerTokenize( pInst->pContext->pCtx = pOldCtx; pInst->pContext->xToken = xOldToken; + pInst->pContext->pInst = pOldInst; + return rc; +} + +typedef struct CallbackCtx CallbackCtx; +struct CallbackCtx { + Fts5Tokenizer *p; + void *pCtx; + int flags; + int (*xToken)(void*, int, const char*, int, int, int); +}; + +static int f5tTokenizeCallback( + void *pCtx, + int tflags, + const char *z, int n, + int iStart, int iEnd +){ + CallbackCtx *p = (CallbackCtx*)pCtx; + return f5tTokenizerReallyTokenize(p->p, p->pCtx, p->flags, z, n, p->xToken); +} + +static int f5tTokenizerTokenize_v2( + Fts5Tokenizer *p, + void *pCtx, + int flags, + const char *pText, int nText, + const char *pLoc, int nLoc, + int (*xToken)(void*, int, const char*, int, int, int) +){ + int rc = SQLITE_OK; + F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p; + + pInst->pLocale = pLoc; + pInst->nLocale = nLoc; + + if( pInst->pParent ){ + CallbackCtx ctx; + ctx.p = p; + ctx.pCtx = pCtx; + ctx.flags = flags; + ctx.xToken = xToken; + if( pInst->pModule->parent_v2.xTokenize ){ + rc = pInst->pModule->parent_v2.xTokenize( + pInst->pParent, (void*)&ctx, flags, pText, nText, + pLoc, nLoc, f5tTokenizeCallback + ); + }else{ + rc = pInst->pModule->parent.xTokenize( + pInst->pParent, (void*)&ctx, flags, pText, nText, f5tTokenizeCallback + ); + } + }else{ + rc = f5tTokenizerReallyTokenize(p, pCtx, flags, pText, nText, xToken); + } + + pInst->pLocale = 0; + pInst->nLocale = 0; return rc; } +static int f5tTokenizerTokenize( + Fts5Tokenizer *p, + void *pCtx, + int flags, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int, int, int) +){ + return f5tTokenizerTokenize_v2(p, pCtx, flags, pText, nText, 0, 0, xToken); +} + +/* +** sqlite3_fts5_locale +*/ +static int SQLITE_TCLAPI f5tTokenizerLocale( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + F5tTokenizerContext *p = (F5tTokenizerContext*)clientData; + + if( objc!=1 ){ + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + + if( p->xToken==0 ){ + Tcl_AppendResult(interp, + "sqlite3_fts5_locale may only be used by tokenizer callback", (char*)0 + ); + return TCL_ERROR; + } + + Tcl_SetObjResult(interp, + Tcl_NewStringObj(p->pInst->pLocale, p->pInst->nLocale) + ); + return TCL_OK; +} /* ** sqlite3_fts5_token ?-colocated? TEXT START END @@ -928,13 +1072,13 @@ static int SQLITE_TCLAPI f5tTokenizerReturn( F5tTokenizerContext *p = (F5tTokenizerContext*)clientData; int iStart; int iEnd; - int nToken; + Tcl_Size nToken; int tflags = 0; char *zToken; int rc; if( objc==5 ){ - int nArg; + Tcl_Size nArg; char *zArg = Tcl_GetStringFromObj(objv[1], &nArg); if( nArg<=10 && nArg>=2 && memcmp("-colocated", zArg, nArg)==0 ){ tflags |= FTS5_TOKEN_COLOCATED; @@ -954,12 +1098,12 @@ static int SQLITE_TCLAPI f5tTokenizerReturn( if( p->xToken==0 ){ Tcl_AppendResult(interp, - "sqlite3_fts5_token may only be used by tokenizer callback", 0 + "sqlite3_fts5_token may only be used by tokenizer callback", (char*)0 ); return TCL_ERROR; } - rc = p->xToken(p->pCtx, tflags, zToken, nToken, iStart, iEnd); + rc = p->xToken(p->pCtx, tflags, zToken, (int)nToken, iStart, iEnd); Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE); return rc==SQLITE_OK ? TCL_OK : TCL_ERROR; @@ -1001,32 +1145,112 @@ static int SQLITE_TCLAPI f5tCreateTokenizer( fts5_api *pApi; char *zName; Tcl_Obj *pScript; - fts5_tokenizer t; F5tTokenizerModule *pMod; - int rc; + int rc = SQLITE_OK; + int bV2 = 0; /* True to use _v2 API */ + int iVersion = 2; /* Value for _v2.iVersion */ + const char *zParent = 0; /* Name of parent tokenizer, if any */ + int ii = 0; - if( objc!=4 ){ - Tcl_WrongNumArgs(interp, 1, objv, "DB NAME SCRIPT"); + if( objc<4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "?OPTIONS? DB NAME SCRIPT"); return TCL_ERROR; } - if( f5tDbAndApi(interp, objv[1], &db, &pApi) ){ - return TCL_ERROR; + + /* Parse any options. Set stack variables bV2 and zParent. */ + for(ii=1; iiinterp = interp; pMod->pScript = pScript; - pMod->pContext = pContext; Tcl_IncrRefCount(pScript); - rc = pApi->xCreateTokenizer(pApi, zName, (void*)pMod, &t, f5tDelTokenizer); + pMod->pContext = pContext; + if( zParent ){ + if( bV2 ){ + fts5_tokenizer_v2 *pParent = 0; + rc = pApi->xFindTokenizer_v2(pApi, zParent, &pMod->pParentCtx, &pParent); + if( rc==SQLITE_OK ){ + memcpy(&pMod->parent_v2, pParent, sizeof(fts5_tokenizer_v2)); + pMod->parent_v2.xDelete(0); + } + }else{ + rc = pApi->xFindTokenizer(pApi, zParent, &pMod->pParentCtx,&pMod->parent); + if( rc==SQLITE_OK ){ + pMod->parent.xDelete(0); + } + } + } + + if( rc==SQLITE_OK ){ + void *pModCtx = (void*)pMod; + if( bV2==0 ){ + fts5_tokenizer t; + t.xCreate = f5tTokenizerCreate; + t.xTokenize = f5tTokenizerTokenize; + t.xDelete = f5tTokenizerDelete; + rc = pApi->xCreateTokenizer(pApi, zName, pModCtx, &t, f5tDelTokenizer); + }else{ + fts5_tokenizer_v2 t2; + memset(&t2, 0, sizeof(t2)); + t2.iVersion = iVersion; + t2.xCreate = f5tTokenizerCreate; + t2.xTokenize = f5tTokenizerTokenize_v2; + t2.xDelete = f5tTokenizerDelete; + rc = pApi->xCreateTokenizer_v2(pApi, zName, pModCtx, &t2,f5tDelTokenizer); + } + } + if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error in fts5_api.xCreateTokenizer()", 0); + Tcl_AppendResult(interp, ( + bV2 ? "error in fts5_api.xCreateTokenizer_v2()" + : "error in fts5_api.xCreateTokenizer()" + ), (char*)0); return TCL_ERROR; } @@ -1083,7 +1307,7 @@ static int SQLITE_TCLAPI f5tTokenHash( Tcl_Obj *CONST objv[] ){ char *z; - int n; + Tcl_Size n; unsigned int iVal; int nSlot; @@ -1096,7 +1320,7 @@ static int SQLITE_TCLAPI f5tTokenHash( } z = Tcl_GetStringFromObj(objv[2], &n); - iVal = f5t_fts5HashKey(nSlot, z, n); + iVal = f5t_fts5HashKey(nSlot, z, (int)n); Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal)); return TCL_OK; } @@ -1316,9 +1540,89 @@ static int SQLITE_TCLAPI f5tRegisterOriginText( Tcl_ResetResult(interp); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); + Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (void*)0); + return TCL_ERROR; + } + return TCL_OK; +} + +/* +** This function is used to DROP an fts5 table. It works even if the data +** structures fts5 stores within the database are corrupt, which sometimes +** prevents a straight "DROP TABLE" command from succeeding. +** +** The first parameter is the database handle to use for the DROP TABLE +** operation. The second is the name of the database to drop the fts5 table +** from (i.e. "main", "temp" or the name of an attached database). The +** third parameter is the name of the fts5 table to drop. +** +** SQLITE_OK is returned if the table is successfully dropped. Or, if an +** error occurs, an SQLite error code. +*/ +static int sqlite3_fts5_drop_corrupt_table( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Database name ("main", "temp" etc.) */ + const char *zTab /* Name of fts5 table to drop */ +){ + int rc = SQLITE_OK; + int bDef = 0; + + rc = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &bDef); + if( rc==SQLITE_OK ){ + char *zScript = sqlite3_mprintf( + "DELETE FROM %Q.'%q_data';" + "DELETE FROM %Q.'%q_config';" + "INSERT INTO %Q.'%q_data' VALUES(10, X'0000000000');" + "INSERT INTO %Q.'%q_config' VALUES('version', 4);" + "DROP TABLE %Q.'%q';", + zDb, zTab, zDb, zTab, zDb, zTab, zDb, zTab, zDb, zTab + ); + + if( zScript==0 ){ + rc = SQLITE_NOMEM; + }else{ + if( bDef ) sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0); + rc = sqlite3_exec(db, zScript, 0, 0, 0); + if( bDef ) sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, 0); + sqlite3_free(zScript); + } + } + + return rc; +} + +/* +** sqlite3_fts5_drop_corrupt_table DB DATABASE TABLE +** +** Description... +*/ +static int SQLITE_TCLAPI f5tDropCorruptTable( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3 *db = 0; + const char *zDb = 0; + const char *zTab = 0; + int rc = SQLITE_OK; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DATABASE TABLE"); + return TCL_ERROR; + } + if( f5tDbPointer(interp, objv[1], &db) ){ + return TCL_ERROR; + } + zDb = Tcl_GetString(objv[2]); + zTab = Tcl_GetString(objv[3]); + + rc = sqlite3_fts5_drop_corrupt_table(db, zDb, zTab); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (void*)0); return TCL_ERROR; } + return TCL_OK; } @@ -1333,13 +1637,15 @@ int Fts5tcl_Init(Tcl_Interp *interp){ } aCmd[] = { { "sqlite3_fts5_create_tokenizer", f5tCreateTokenizer, 1 }, { "sqlite3_fts5_token", f5tTokenizerReturn, 1 }, + { "sqlite3_fts5_locale", f5tTokenizerLocale, 1 }, { "sqlite3_fts5_tokenize", f5tTokenize, 0 }, { "sqlite3_fts5_create_function", f5tCreateFunction, 0 }, { "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt, 0 }, { "sqlite3_fts5_token_hash", f5tTokenHash, 0 }, { "sqlite3_fts5_register_matchinfo", f5tRegisterMatchinfo, 0 }, { "sqlite3_fts5_register_fts5tokenize", f5tRegisterTok, 0 }, - { "sqlite3_fts5_register_origintext",f5tRegisterOriginText, 0 } + { "sqlite3_fts5_register_origintext",f5tRegisterOriginText, 0 }, + { "sqlite3_fts5_drop_corrupt_table", f5tDropCorruptTable, 0 } }; int i; F5tTokenizerContext *pContext; diff --git a/ext/fts5/fts5_tokenize.c b/ext/fts5/fts5_tokenize.c index 2200e7837..f9581b080 100644 --- a/ext/fts5/fts5_tokenize.c +++ b/ext/fts5/fts5_tokenize.c @@ -79,7 +79,7 @@ static int fts5AsciiCreate( int i; memset(p, 0, sizeof(AsciiTokenizer)); memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar)); - for(i=0; rc==SQLITE_OK && i=0xc0 ){ \ c = sqlite3Utf8Trans1[c-0xc0]; \ - while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + while( zInpTokenizer ){ - p->tokenizer.xDelete(p->pTokenizer); + p->tokenizer_v2.xDelete(p->pTokenizer); } sqlite3_free(p); } @@ -587,6 +584,7 @@ static int fts5PorterCreate( PorterTokenizer *pRet; void *pUserdata = 0; const char *zBase = "unicode61"; + fts5_tokenizer_v2 *pV2 = 0; if( nArg>0 ){ zBase = azArg[0]; @@ -595,14 +593,15 @@ static int fts5PorterCreate( pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer)); if( pRet ){ memset(pRet, 0, sizeof(PorterTokenizer)); - rc = pApi->xFindTokenizer(pApi, zBase, &pUserdata, &pRet->tokenizer); + rc = pApi->xFindTokenizer_v2(pApi, zBase, &pUserdata, &pV2); }else{ rc = SQLITE_NOMEM; } if( rc==SQLITE_OK ){ int nArg2 = (nArg>0 ? nArg-1 : 0); - const char **azArg2 = (nArg2 ? &azArg[1] : 0); - rc = pRet->tokenizer.xCreate(pUserdata, azArg2, nArg2, &pRet->pTokenizer); + const char **az2 = (nArg2 ? &azArg[1] : 0); + memcpy(&pRet->tokenizer_v2, pV2, sizeof(fts5_tokenizer_v2)); + rc = pRet->tokenizer_v2.xCreate(pUserdata, az2, nArg2, &pRet->pTokenizer); } if( rc!=SQLITE_OK ){ @@ -1253,6 +1252,7 @@ static int fts5PorterTokenize( void *pCtx, int flags, const char *pText, int nText, + const char *pLoc, int nLoc, int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd) ){ PorterTokenizer *p = (PorterTokenizer*)pTokenizer; @@ -1260,8 +1260,8 @@ static int fts5PorterTokenize( sCtx.xToken = xToken; sCtx.pCtx = pCtx; sCtx.aBuf = p->aBuf; - return p->tokenizer.xTokenize( - p->pTokenizer, (void*)&sCtx, flags, pText, nText, fts5PorterCb + return p->tokenizer_v2.xTokenize( + p->pTokenizer, (void*)&sCtx, flags, pText, nText, pLoc, nLoc, fts5PorterCb ); } @@ -1291,41 +1291,46 @@ static int fts5TriCreate( Fts5Tokenizer **ppOut ){ int rc = SQLITE_OK; - TrigramTokenizer *pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew)); + TrigramTokenizer *pNew = 0; UNUSED_PARAM(pUnused); - if( pNew==0 ){ - rc = SQLITE_NOMEM; + if( nArg%2 ){ + rc = SQLITE_ERROR; }else{ int i; - pNew->bFold = 1; - pNew->iFoldParam = 0; - for(i=0; rc==SQLITE_OK && ibFold = 1; + pNew->iFoldParam = 0; + + for(i=0; rc==SQLITE_OK && ibFold = (zArg[0]=='0'); + } + }else if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){ + if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){ + rc = SQLITE_ERROR; + }else{ + pNew->iFoldParam = (zArg[0]!='0') ? 2 : 0; + } }else{ - pNew->bFold = (zArg[0]=='0'); - } - }else if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){ - if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){ rc = SQLITE_ERROR; - }else{ - pNew->iFoldParam = (zArg[0]!='0') ? 2 : 0; } - }else{ + } + + if( pNew->iFoldParam!=0 && pNew->bFold==0 ){ rc = SQLITE_ERROR; } - } - if( iiFoldParam!=0 && pNew->bFold==0 ){ - rc = SQLITE_ERROR; - } - - if( rc!=SQLITE_OK ){ - fts5TriDelete((Fts5Tokenizer*)pNew); - pNew = 0; + + if( rc!=SQLITE_OK ){ + fts5TriDelete((Fts5Tokenizer*)pNew); + pNew = 0; + } } } *ppOut = (Fts5Tokenizer*)pNew; @@ -1430,6 +1435,16 @@ int sqlite3Fts5TokenizerPattern( return FTS5_PATTERN_NONE; } +/* +** Return true if the tokenizer described by p->azArg[] is the trigram +** tokenizer. This tokenizer needs to be loaded before xBestIndex is +** called for the first time in order to correctly handle LIKE/GLOB. +*/ +int sqlite3Fts5TokenizerPreload(Fts5TokenizerConfig *p){ + return (p->nArg>=1 && 0==sqlite3_stricmp(p->azArg[0], "trigram")); +} + + /* ** Register all built-in tokenizers with FTS5. */ @@ -1440,7 +1455,6 @@ int sqlite3Fts5TokenizerInit(fts5_api *pApi){ } aBuiltin[] = { { "unicode61", {fts5UnicodeCreate, fts5UnicodeDelete, fts5UnicodeTokenize}}, { "ascii", {fts5AsciiCreate, fts5AsciiDelete, fts5AsciiTokenize }}, - { "porter", {fts5PorterCreate, fts5PorterDelete, fts5PorterTokenize }}, { "trigram", {fts5TriCreate, fts5TriDelete, fts5TriTokenize}}, }; @@ -1455,6 +1469,19 @@ int sqlite3Fts5TokenizerInit(fts5_api *pApi){ 0 ); } - + if( rc==SQLITE_OK ){ + fts5_tokenizer_v2 sPorter = { + 2, + fts5PorterCreate, + fts5PorterDelete, + fts5PorterTokenize + }; + rc = pApi->xCreateTokenizer_v2(pApi, + "porter", + (void*)pApi, + &sPorter, + 0 + ); + } return rc; } diff --git a/ext/fts5/fts5_unicode2.c b/ext/fts5/fts5_unicode2.c index 3e97264fa..cc164a456 100644 --- a/ext/fts5/fts5_unicode2.c +++ b/ext/fts5/fts5_unicode2.c @@ -364,6 +364,9 @@ int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ default: return 1; } break; + + default: + return 1; } return 0; } diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c index 4782d0fb9..fb280567f 100644 --- a/ext/fts5/fts5_vocab.c +++ b/ext/fts5/fts5_vocab.c @@ -64,6 +64,7 @@ struct Fts5VocabCursor { int nLeTerm; /* Size of zLeTerm in bytes */ char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */ + int colUsed; /* Copy of sqlite3_index_info.colUsed */ /* These are used by 'col' tables only */ int iCol; @@ -90,9 +91,11 @@ struct Fts5VocabCursor { /* ** Bits for the mask used as the idxNum value by xBestIndex/xFilter. */ -#define FTS5_VOCAB_TERM_EQ 0x01 -#define FTS5_VOCAB_TERM_GE 0x02 -#define FTS5_VOCAB_TERM_LE 0x04 +#define FTS5_VOCAB_TERM_EQ 0x0100 +#define FTS5_VOCAB_TERM_GE 0x0200 +#define FTS5_VOCAB_TERM_LE 0x0400 + +#define FTS5_VOCAB_COLUSED_MASK 0xFF /* @@ -269,11 +272,13 @@ static int fts5VocabBestIndexMethod( int iTermEq = -1; int iTermGe = -1; int iTermLe = -1; - int idxNum = 0; + int idxNum = (int)pInfo->colUsed; int nArg = 0; UNUSED_PARAM(pUnused); + assert( (pInfo->colUsed & FTS5_VOCAB_COLUSED_MASK)==pInfo->colUsed ); + for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; if( p->usable==0 ) continue; @@ -365,7 +370,7 @@ static int fts5VocabOpenMethod( if( rc==SQLITE_OK ){ pVTab->zErrMsg = sqlite3_mprintf( "no such fts5 table: %s.%s", pTab->zFts5Db, pTab->zFts5Tbl - ); + ); rc = SQLITE_ERROR; } }else{ @@ -525,9 +530,19 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ switch( pTab->eType ){ case FTS5_VOCAB_ROW: - if( eDetail==FTS5_DETAIL_FULL ){ - while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){ - pCsr->aCnt[0]++; + /* Do not bother counting the number of instances if the "cnt" + ** column is not being read (according to colUsed). */ + if( eDetail==FTS5_DETAIL_FULL && (pCsr->colUsed & 0x04) ){ + while( iPosaCnt[] */ + pCsr->aCnt[0]++; + } } } pCsr->aDoc[0]++; @@ -625,6 +640,7 @@ static int fts5VocabFilterMethod( if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++]; if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++]; if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++]; + pCsr->colUsed = (idxNum & FTS5_VOCAB_COLUSED_MASK); if( pEq ){ zTerm = (const char *)sqlite3_value_text(pEq); diff --git a/ext/fts5/test/fts5_common.tcl b/ext/fts5/test/fts5_common.tcl index fda388a6f..8ea87dbdd 100644 --- a/ext/fts5/test/fts5_common.tcl +++ b/ext/fts5/test/fts5_common.tcl @@ -51,6 +51,10 @@ proc fts5_test_poslist2 {cmd} { sort_poslist $res } +proc fts5_test_insttoken {cmd iInst iToken} { + $cmd xInstToken $iInst $iToken +} + proc fts5_test_collist {cmd} { set res [list] @@ -78,6 +82,9 @@ proc fts5_test_columnsize {cmd} { proc fts5_columntext {cmd iCol} { $cmd xColumnText $iCol } +proc fts5_columnlocale {cmd iCol} { + $cmd xColumnLocale $iCol +} proc fts5_test_columntext {cmd} { set res [list] @@ -87,6 +94,14 @@ proc fts5_test_columntext {cmd} { set res } +proc fts5_test_columnlocale {cmd} { + set res [list] + for {set i 0} {$i < [$cmd xColumnCount]} {incr i} { + lappend res [$cmd xColumnLocale $i] + } + set res +} + proc fts5_test_columntotalsize {cmd} { set res [list] for {set i 0} {$i < [$cmd xColumnCount]} {incr i} { @@ -114,6 +129,10 @@ proc fts5_test_rowcount {cmd} { $cmd xRowCount } +proc fts5_test_rowid {cmd} { + $cmd xRowid +} + proc test_queryphrase_cb {cnt cmd} { upvar $cnt L for {set i 0} {$i < [$cmd xInstCount]} {incr i} { @@ -161,17 +180,21 @@ proc fts5_aux_test_functions {db} { foreach f { fts5_test_columnsize fts5_test_columntext + fts5_test_columnlocale fts5_test_columntotalsize fts5_test_poslist fts5_test_poslist2 fts5_test_collist + fts5_test_insttoken fts5_test_tokenize fts5_test_rowcount + fts5_test_rowid fts5_test_all fts5_test_queryphrase fts5_test_phrasecount fts5_columntext + fts5_columnlocale fts5_queryphrase fts5_collist } { diff --git a/ext/fts5/test/fts5aa.test b/ext/fts5/test/fts5aa.test index a80a307a4..bcad9e724 100644 --- a/ext/fts5/test/fts5aa.test +++ b/ext/fts5/test/fts5aa.test @@ -440,7 +440,7 @@ do_execsql_test 16.1 { proc funk {} { db eval { UPDATE n1_config SET v=50 WHERE k='version' } set fd [db incrblob main n1_data block 10] - fconfigure $fd -encoding binary -translation binary + fconfigure $fd -translation binary # puts -nonewline $fd "\x44\x45" close $fd } @@ -453,7 +453,7 @@ db func funk funk # statement no longer fails. # do_catchsql_test 16.2 { - SELECT funk(), bm25(n1), funk() FROM n1 WHERE n1 MATCH 'a+b+c+d' + SELECT funk(), format('%g',bm25(n1)), funk() FROM n1 WHERE n1 MATCH 'a+b+c+d' } {0 {{} -1e-06 {}}} # {1 {SQL logic error}} diff --git a/ext/fts5/test/fts5ab.test b/ext/fts5/test/fts5ab.test index 5aa745658..7e312286f 100644 --- a/ext/fts5/test/fts5ab.test +++ b/ext/fts5/test/fts5ab.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ab -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5ac.test b/ext/fts5/test/fts5ac.test index f3a914653..4628e909c 100644 --- a/ext/fts5/test/fts5ac.test +++ b/ext/fts5/test/fts5ac.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ac -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5ad.test b/ext/fts5/test/fts5ad.test index 524da6dea..27806a4c0 100644 --- a/ext/fts5/test/fts5ad.test +++ b/ext/fts5/test/fts5ad.test @@ -18,7 +18,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ad -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5ae.test b/ext/fts5/test/fts5ae.test index d9f132ca9..205a59a69 100644 --- a/ext/fts5/test/fts5ae.test +++ b/ext/fts5/test/fts5ae.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ae -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5af.test b/ext/fts5/test/fts5af.test index 3d7929509..9c95ef2da 100644 --- a/ext/fts5/test/fts5af.test +++ b/ext/fts5/test/fts5af.test @@ -18,7 +18,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5af -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5ag.test b/ext/fts5/test/fts5ag.test index 9ead957c9..42cd91378 100644 --- a/ext/fts5/test/fts5ag.test +++ b/ext/fts5/test/fts5ag.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ag -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5ah.test b/ext/fts5/test/fts5ah.test index 000435137..bf9c9e9db 100644 --- a/ext/fts5/test/fts5ah.test +++ b/ext/fts5/test/fts5ah.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ah -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -163,6 +163,17 @@ do_execsql_test 1.8.2 { SELECT count(*) FROM t1 WHERE t1 MATCH 'x' AND rowid < 'text'; } {10000} +do_execsql_test 1.8.3 { + SELECT count(*) FROM t1 WHERE t1 MATCH 'x' AND rowid<5000 AND rowid < 'text'; +} {4999} +do_execsql_test 1.8.4 { + SELECT count(*) FROM t1 WHERE t1 MATCH 'x' AND rowid>5000 AND rowid > 'text'; +} {0} + +do_catchsql_test 1.9 { + SELECT * FROM t1('*xy'); +} {1 {unknown special query: xy}} + } ;# foreach_detail_mode #db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r} diff --git a/ext/fts5/test/fts5ai.test b/ext/fts5/test/fts5ai.test index 20e106939..a6576d3af 100644 --- a/ext/fts5/test/fts5ai.test +++ b/ext/fts5/test/fts5ai.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ai -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5aj.test b/ext/fts5/test/fts5aj.test index 50dae2016..e802306b3 100644 --- a/ext/fts5/test/fts5aj.test +++ b/ext/fts5/test/fts5aj.test @@ -19,7 +19,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5aj -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5ak.test b/ext/fts5/test/fts5ak.test index e248f2a32..253f14fc7 100644 --- a/ext/fts5/test/fts5ak.test +++ b/ext/fts5/test/fts5ak.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ak -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5al.test b/ext/fts5/test/fts5al.test index 842d991a3..7187ad67c 100644 --- a/ext/fts5/test/fts5al.test +++ b/ext/fts5/test/fts5al.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5al -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -293,6 +293,16 @@ do_catchsql_test 4.4.4 { SELECT *, rank FROM t3 WHERE t3 MATCH 'a' AND rank MATCH NULL } {1 {parse error in rank function: }} +# Check that the second and subsequent rank= constraints are ignored. +# +do_catchsql_test 4.3.3 { + SELECT *, rank FROM t3 + WHERE t3 MATCH 'a' AND + rank MATCH 'nosuch()' AND + rank MATCH 'rowidmod(3)' + ORDER BY rank ASC +} {1 {unable to use function MATCH in the requested context}} + } ;# foreach_detail_mode diff --git a/ext/fts5/test/fts5alter.test b/ext/fts5/test/fts5alter.test index 67f948cbb..bb5f78dc8 100644 --- a/ext/fts5/test/fts5alter.test +++ b/ext/fts5/test/fts5alter.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5alter -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5auto.test b/ext/fts5/test/fts5auto.test index 79d432b81..b771af912 100644 --- a/ext/fts5/test/fts5auto.test +++ b/ext/fts5/test/fts5auto.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5auto -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5aux.test b/ext/fts5/test/fts5aux.test index 796300637..960dbc511 100644 --- a/ext/fts5/test/fts5aux.test +++ b/ext/fts5/test/fts5aux.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5aux -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -399,6 +399,6 @@ db close sqlite3 db test.db do_catchsql_test 13.4 { SELECT highlight(t1, 0, '[', ']') FROM t1 -} {1 {no such tokenizer: blah}} +} {1 {SQL logic error}} finish_test diff --git a/ext/fts5/test/fts5aux2.test b/ext/fts5/test/fts5aux2.test new file mode 100644 index 000000000..2352970ec --- /dev/null +++ b/ext/fts5/test/fts5aux2.test @@ -0,0 +1,71 @@ +# 2024 June 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. +# +#*********************************************************************** +# +# Tests focusing on the auxiliary function APIs. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5aux + +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING fts5(a, b); + INSERT INTO x1 VALUES('a b', 'c d'); + INSERT INTO x1 VALUES('d e', 'a b'); + INSERT INTO x1 VALUES('a b', 'e f'); + INSERT INTO x1 VALUES('d e', 'c d'); +} + +fts5_aux_test_functions db +do_execsql_test 1.1 { + SELECT fts5_test_all(x1) FROM x1 WHERE rowid=2 +} [list [list {*}{ + columnsize {2 2} + columntext {{d e} {a b}} + columntotalsize {8 8} + poslist {} + tokenize {{d e} {a b}} + rowcount 4 +}]] + +do_execsql_test 1.2 { + SELECT fts5_test_columntext(x1) FROM x1 +} { + {{a b} {c d}} + {{d e} {a b}} + {{a b} {e f}} + {{d e} {c d}} +} + +do_execsql_test 1.3 { + SELECT fts5_test_rowid(x1) FROM x1 +} { + 1 2 3 4 +} +do_execsql_test 1.4 { + SELECT fts5_test_phrasecount(x1) FROM x1 +} { + 0 0 0 0 +} +do_catchsql_test 1.5 { + SELECT fts5_queryphrase(x1, 0) FROM x1 +} {1 SQLITE_RANGE} +do_execsql_test 1.6 { + SELECT fts5_test_rowcount(x1) FROM x1 +} {4 4 4 4} + + +finish_test diff --git a/ext/fts5/test/fts5auxdata.test b/ext/fts5/test/fts5auxdata.test index a2a41704c..7f99fed31 100644 --- a/ext/fts5/test/fts5auxdata.test +++ b/ext/fts5/test/fts5auxdata.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5auxdata -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5bigpl.test b/ext/fts5/test/fts5bigpl.test index 2c9df11b1..9e3d86c0e 100644 --- a/ext/fts5/test/fts5bigpl.test +++ b/ext/fts5/test/fts5bigpl.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5bigpl -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5blob.test b/ext/fts5/test/fts5blob.test new file mode 100644 index 000000000..934855410 --- /dev/null +++ b/ext/fts5/test/fts5blob.test @@ -0,0 +1,166 @@ +# 2024 July 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 verifies that: +# +# * blob values may be written to locale=0 tables. +# +# * blob values - other than fts5_locale() values - may not be written +# to locale=0 tables. This is an SQLITE_MISMATCH error +# +# * blob values may be returned by queries on the external-content table +# of a locale=0 table. +# +# * blob values not may be returned by queries on the external-content +# table of a locale=1 table, apart from fts5_locale() blobs. This is an +# SQLITE_MISMATCH error. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5blob + +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +# Test that blobs may be stored in normal locale=0 tables. +# +foreach {tn enc} { + 1 utf8 + 2 utf16 +} { + reset_db + fts5_aux_test_functions db + + execsql "PRAGMA encoding = $enc" + + execsql " + CREATE VIRTUAL TABLE t1 USING fts5(x, y); + " + do_execsql_test 1.$tn.0 { + CREATE VIRTUAL TABLE tt USING fts5vocab('t1', 'instance'); + INSERT INTO t1(rowid, x, y) VALUES(1, 555, X'0000000041424320444546'); + INSERT INTO t1(rowid, x, y) VALUES(2, 666, X'41424300444546'); + INSERT INTO t1(rowid, x, y) VALUES(3, 777, 'xyz'); + } + + do_execsql_test 1.$tn.1 { + SELECT rowid, quote(x), quote(y) FROM t1 + } { + 1 555 X'0000000041424320444546' + 2 666 X'41424300444546' + 3 777 'xyz' + } + + do_execsql_test 1.$tn.2 { + DELETE FROM t1 WHERE rowid=2; + DELETE FROM t1 WHERE rowid=1; + } + + do_execsql_test 1.$tn.3 { + PRAGMA integrity_check; + } {ok} +} + +#-------------------------------------------------------------------------- +# Test that a blob may be stored and retrieved in an unindexed column of +# a regular table with locale=1. +# +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x, y UNINDEXED, locale=1); + INSERT INTO t1(rowid, x, y) VALUES(12, 'twelve', X'0000000041424320444546'); +} + +do_execsql_test 2.1 { + select rowid, x, quote(y) FROM t1 +} { + 12 twelve X'0000000041424320444546' +} + +#-------------------------------------------------------------------------- +# Test that blobs may not be written to any type of table with locale=1 +# set. Except, they may be written to UNINDEXED columns. +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(a, b); + + CREATE VIRTUAL TABLE x1 USING fts5(a, b, locale=1); + CREATE VIRTUAL TABLE x2 USING fts5(a, b, locale=1, content=t2); + CREATE VIRTUAL TABLE x3 USING fts5(a, b, locale=1, content=); +} + +do_catchsql_test 3.1 { + INSERT INTO x1(rowid, a, b) VALUES(113, 'hello world', X'123456'); +} {0 {}} +do_catchsql_test 3.2 { + INSERT INTO x2(rowid, a, b) VALUES(113, 'hello world', X'123456'); +} {0 {}} +do_catchsql_test 3.3 { + INSERT INTO x3(rowid, a, b) VALUES(113, 'hello world', X'123456'); +} {0 {}} + + +#-------------------------------------------------------------------------- +# Test that fts5_locale() values may not be written to any type of table +# without locale=1 set. Even to an UNINDEXED column. +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(a, b); + + CREATE VIRTUAL TABLE x1 USING fts5(a, b); + CREATE VIRTUAL TABLE x2 USING fts5(a, b, content=t2); + CREATE VIRTUAL TABLE x3 USING fts5(a, b, content=); + + CREATE VIRTUAL TABLE x4 USING fts5(a, b, c UNINDEXED); +} + +do_catchsql_test 3.1 { + INSERT INTO x1(rowid, a, b) + VALUES(113, 'hello world', fts5_locale('en_AU', 'abc')); +} {1 {fts5_locale() requires locale=1}} +do_catchsql_test 3.2 { + INSERT INTO x2(rowid, a, b) + VALUES(113, 'hello world', fts5_locale('en_AU', 'abc')); +} {1 {fts5_locale() requires locale=1}} +do_catchsql_test 3.3 { + INSERT INTO x3(rowid, a, b) + VALUES(113, 'hello world', fts5_locale('en_AU', 'abc')); +} {1 {fts5_locale() requires locale=1}} +do_catchsql_test 3.4 { + INSERT INTO x4(rowid, a, b, c) + VALUES(113, 'hello world', 'yesno', fts5_locale('en_AU', 'abc')); +} {1 {fts5_locale() requires locale=1}} + + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x); +} + +foreach {tn sql} { + 1 { INSERT INTO x1(rowid, x) VALUES(4.5, 'abcd') } + 2 { INSERT INTO x1(rowid, x) VALUES('xyz', 'abcd') } + 3 { INSERT INTO x1(rowid, x) VALUES(X'001122', 'abcd') } +} { + do_catchsql_test 4.1.$tn $sql {1 {datatype mismatch}} +} + + +finish_test + + diff --git a/ext/fts5/test/fts5cat.test b/ext/fts5/test/fts5cat.test index 483f64bfe..71e2abe3a 100644 --- a/ext/fts5/test/fts5cat.test +++ b/ext/fts5/test/fts5cat.test @@ -55,5 +55,22 @@ do_execsql_test 1.5 { SELECT * FROM t4t } {สนามกีฬา 1 1} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 " + CREATE VIRTUAL TABLE x1 USING fts5(c, + tokenize=\"unicode61 categories ' \t'\"); +" + +do_catchsql_test 2.1 " + CREATE VIRTUAL TABLE x2 USING fts5(c, + tokenize=\"unicode61 categories 'N*\t\tMYZ'\"); +" {1 {error in tokenizer constructor}} + +do_catchsql_test 2.2 " + CREATE VIRTUAL TABLE x2 USING fts5(c, + tokenize=\"unicode61 categories 'N*\t\tXYZ'\"); +" {1 {error in tokenizer constructor}} + finish_test diff --git a/ext/fts5/test/fts5colset.test b/ext/fts5/test/fts5colset.test index 7243743b5..e5429572c 100644 --- a/ext/fts5/test/fts5colset.test +++ b/ext/fts5/test/fts5colset.test @@ -79,7 +79,7 @@ foreach_detail_mode $::testprefix { do_catchsql_test 4.1 { SELECT * FROM t1 WHERE rowid MATCH 'a' - } {1 {unable to use function MATCH in the requested context}} + } {1 {no query solution}} } #------------------------------------------------------------------------- diff --git a/ext/fts5/test/fts5columnsize.test b/ext/fts5/test/fts5columnsize.test index 2b03d575a..7af49184b 100644 --- a/ext/fts5/test/fts5columnsize.test +++ b/ext/fts5/test/fts5columnsize.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5columnsize -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5config.test b/ext/fts5/test/fts5config.test index 35894c6bb..28f3146ea 100644 --- a/ext/fts5/test/fts5config.test +++ b/ext/fts5/test/fts5config.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5config -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5content.test b/ext/fts5/test/fts5content.test index 986d7f331..05b5cc611 100644 --- a/ext/fts5/test/fts5content.test +++ b/ext/fts5/test/fts5content.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5content -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -328,4 +328,41 @@ do_execsql_test 8.3.2 { INSERT INTO t1(t1) VALUES('rebuild') } do_execsql_test 8.3.3 { SELECT * FROM t1 WHERE rowid=1 } {one} do_execsql_test 8.3.4 { SELECT rowid FROM t1('two') } {2} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 9.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, 'one two three'); + INSERT INTO t1 VALUES(2, 'one two three'); + + CREATE VIRTUAL TABLE ft USING fts5(b, content=t1, content_rowid=a); + INSERT INTO ft(ft) VALUES('rebuild'); +} + +do_execsql_test 9.2 { + SELECT rowid, b FROM ft('two'); +} { + 1 {one two three} + 2 {one two three} +} + +do_execsql_test 9.3 { + DELETE FROM t1 WHERE a=2; +} + +do_catchsql_test 9.4 { + SELECT rowid FROM ft('two'); +} {0 {1 2}} + +do_catchsql_test 9.5 { + SELECT * FROM ft('two'); +} {1 {fts5: missing row 2 from content table 'main'.'t1'}} + +fts5_aux_test_functions db + +do_catchsql_test 9.6 { + SELECT rowid, fts5_columntext(ft, 0) FROM ft('two'); +} {1 SQLITE_CORRUPT_VTAB} + finish_test + diff --git a/ext/fts5/test/fts5contentless.test b/ext/fts5/test/fts5contentless.test index 48cfd10ff..991e9888f 100644 --- a/ext/fts5/test/fts5contentless.test +++ b/ext/fts5/test/fts5contentless.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -267,4 +267,24 @@ do_execsql_test 8.2 { SELECT rowid FROM ft('four'); } {} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 9.0 { + CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=0); + INSERT INTO ft VALUES('hello world'); + INSERT INTO ft VALUES('one two three'); +} + +do_catchsql_test 9.1 { + INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, 'hello world'); +} {0 {}} + +do_catchsql_test 9.2 { + CREATE VIRTUAL TABLE ft2 USING fts5(x, content='', contentless_delete=2); +} {1 {malformed contentless_delete=... directive}} + +do_catchsql_test 9.3 { + CREATE VIRTUAL TABLE ft2 USING fts5(x, content='', contentless_delete=11); +} {1 {malformed contentless_delete=... directive}} + finish_test diff --git a/ext/fts5/test/fts5contentless2.test b/ext/fts5/test/fts5contentless2.test index fdd7a60fc..248534bce 100644 --- a/ext/fts5/test/fts5contentless2.test +++ b/ext/fts5/test/fts5contentless2.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5contentless3.test b/ext/fts5/test/fts5contentless3.test index 76119dc59..693840da8 100644 --- a/ext/fts5/test/fts5contentless3.test +++ b/ext/fts5/test/fts5contentless3.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless3 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5contentless4.test b/ext/fts5/test/fts5contentless4.test index 702b33a9d..7fdf8c4b0 100644 --- a/ext/fts5/test/fts5contentless4.test +++ b/ext/fts5/test/fts5contentless4.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless4 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5contentless5.test b/ext/fts5/test/fts5contentless5.test index a20134d1e..86d075328 100644 --- a/ext/fts5/test/fts5contentless5.test +++ b/ext/fts5/test/fts5contentless5.test @@ -15,11 +15,12 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless5 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return } +unset -nocomplain res do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts5(a, b, c, content='', contentless_delete=1); @@ -34,8 +35,8 @@ do_execsql_test 1.01 { } # explain_i "UPDATE t1 SET a='a' WHERE t1.rowid=1" -breakpoint -explain_i "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1 AND b IS NULL" +#breakpoint +#explain_i "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1 AND b IS NULL" #breakpoint #explain_i "UPDATE t1 SET a='a' WHERE b IS NULL AND rowid=?" @@ -55,4 +56,56 @@ foreach {tn up err} { do_catchsql_test 1.$tn $up $res($err) } +#------------------------------------------------------------------------- +reset_db + +proc random {n} { expr {abs(int(rand()*$n))} } +proc select_one {list} { + set n [llength $list] + lindex $list [random $n] +} +proc vocab {} { + list abc def ghi jkl mno pqr stu vwx yza +} +proc term {} { + select_one [vocab] +} +proc document {} { + set nTerm [expr [random 3] + 7] + set doc "" + for {set ii 0} {$ii < $nTerm} {incr ii} { + lappend doc [term] + } + set doc +} +db func document document + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(a, contentless_delete=1, content=''); + INSERT INTO ft(ft, rank) VALUES('pgsz', 64); +} + +do_test 2.1 { + for {set ii 1} {$ii < 12} {incr ii} { + db transaction { + for {set jj 0} {$jj < 10} {incr jj} { + set doc [document] + execsql { INSERT INTO ft VALUES($doc); } + } + } + } +} {} + +do_test 2.2 { + foreach r [db eval {SELECT rowid FROM ft}] { + execsql { DELETE FROM ft WHERE rowid=$r } + } +} {} + +set doc [document] +do_execsql_test 2.3 { + INSERT INTO ft VALUES($doc) +} + + finish_test diff --git a/ext/fts5/test/fts5corrupt.test b/ext/fts5/test/fts5corrupt.test index 9aa84a0ef..0abd8b86d 100644 --- a/ext/fts5/test/fts5corrupt.test +++ b/ext/fts5/test/fts5corrupt.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5corrupt -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -99,6 +99,27 @@ sqlite3_db_config db DEFENSIVE 0 do_catchsql_test 3.1 { DELETE FROM t3_content WHERE rowid = 3; SELECT * FROM t3 WHERE t3 MATCH 'o'; +} {1 {fts5: missing row 3 from content table 'main'.'t3_content'}} + +#-------------------------------------------------------------------- +# +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t2 USING fts5(x); + INSERT INTO t2 VALUES('one two three'); + INSERT INTO t2 VALUES('four five six'); + INSERT INTO t2 VALUES('seven eight nine'); + INSERT INTO t2 VALUES('ten eleven twelve'); +} +do_execsql_test 4.1 { + SELECT hex(block) FROM t2_data WHERE id=1; +} {040C} +do_execsql_test 4.2 { + UPDATE t2_data SET block = X'0402' WHERE id=1 +} +breakpoint +do_catchsql_test 4.3 { + DELETE FROM t2 WHERE rowid=3 } {1 {database disk image is malformed}} finish_test diff --git a/ext/fts5/test/fts5corrupt2.test b/ext/fts5/test/fts5corrupt2.test index 06e2e7425..6b4d6d411 100644 --- a/ext/fts5/test/fts5corrupt2.test +++ b/ext/fts5/test/fts5corrupt2.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5corrupt2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -100,6 +100,7 @@ set lrowid [db one {SELECT max(rowid) FROM t1_data WHERE (rowid & $mask)=0}] set nbyte [db one {SELECT length(block) FROM t1_data WHERE rowid=$lrowid}] set all [db eval {SELECT rowid FROM t1}] sqlite3_db_config db DEFENSIVE 0 +unset -nocomplain res for {set i [expr $nbyte-2]} {$i>=0} {incr i -1} { do_execsql_test 2.$i.1 { BEGIN; @@ -152,7 +153,7 @@ foreach {tn hdr} { execsql BEGIN set fd [db incrblob main x3_data block $rowid] - fconfigure $fd -encoding binary -translation binary + fconfigure $fd -translation binary set existing [read $fd [string length $hdr]] seek $fd 0 puts -nonewline $fd $hdr @@ -238,7 +239,7 @@ foreach {tn hdr} { execsql BEGIN set fd [db incrblob main x5_data block $rowid] - fconfigure $fd -encoding binary -translation binary + fconfigure $fd -translation binary puts -nonewline $fd $hdr close $fd diff --git a/ext/fts5/test/fts5corrupt3.test b/ext/fts5/test/fts5corrupt3.test index f9a95665c..3e8b0377c 100644 --- a/ext/fts5/test/fts5corrupt3.test +++ b/ext/fts5/test/fts5corrupt3.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5corrupt3 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -680,11 +680,11 @@ do_test 12.0 { do_catchsql_test 11.1 { SELECT * FROM t1 WHERE t1 MATCH 'abandon'; -} {1 {vtable constructor failed: t1}} +} {1 {database disk image is malformed}} do_catchsql_test 11.2 { INSERT INTO t1(t1, rank) VALUES('merge', 500); -} {1 {vtable constructor failed: t1}} +} {1 {database disk image is malformed}} #------------------------------------------------------------------------- # @@ -1040,7 +1040,7 @@ do_test 16.0 { do_catchsql_test 16.1 { INSERT INTO t1(t1) VALUES('integrity-check'); -} {1 {vtable constructor failed: t1}} +} {1 {database disk image is malformed}} #-------------------------------------------------------------------------- reset_db @@ -1126,7 +1126,7 @@ do_test 17.0 { do_catchsql_test 17.1 { SELECT * FROM t1 WHERE t1 MATCH 'abandon'; -} {1 {vtable constructor failed: t1}} +} {1 {database disk image is malformed}} #-------------------------------------------------------------------------- reset_db @@ -1630,7 +1630,7 @@ do_test 20.0 { do_catchsql_test 20.1 { SELECT * FROM t1 WHERE t1 MATCH 'abandon'; -} {1 {vtable constructor failed: t1}} +} {1 {database disk image is malformed}} #------------------------------------------------------------------------- reset_db @@ -2100,7 +2100,7 @@ do_test 22.0 { do_catchsql_test 22.1 { INSERT INTO t1(t1) VALUES('optimize'); -} {1 {vtable constructor failed: t1}} +} {1 {database disk image is malformed}} #-------------------------------------------------------------------------- reset_db @@ -3700,7 +3700,7 @@ do_catchsql_test 32.1 { highlight(t1, 2, '[', ']') FROM t1('g + h') WHERE rank MATCH 'bm25(1.0, 1.0)' ORDER BY rank; -} {1 {vtable constructor failed: t1}} +} {1 {database disk image is malformed}} do_catchsql_test 32.2 { SELECT * FROM t3; @@ -4267,7 +4267,7 @@ do_test 35.0 { do_catchsql_test 35.1 { SELECT * FROM t1 WHERE t1 MATCH 'e*'; -} {1 {database disk image is malformed}} +} {1 {fts5: missing row 14 from content table 'main'.'t1_content'}} #------------------------------------------------------------------------- reset_db @@ -8958,7 +8958,6 @@ do_catchsql_test 61.2 { SELECT * FROM t3 ORDER BY rowid; } {/*malformed database schema*/} -breakpoint #------------------------------------------------------------------------- do_test 62.0 { sqlite3 db {} @@ -10768,6 +10767,7 @@ do_catchsql_test 73.1 { reset_db do_test 74.0 { sqlite3 db {} + sqlite3_fts5_register_matchinfo db db deserialize [decode_hexdb { | size 106496 pagesize 4096 filename x.db | page 1 offset 0 @@ -14587,14 +14587,19 @@ do_test 74.0 { | end x.db }]} {} -do_catchsql_test 74.1 { - SELECT rowid, quote(matchinfo(t1,'p�xyb', '
      ') FROM t1('*id'); -} {0 {{}}} +} {1 {no such cursor: 4}} do_catchsql_test 1.2.2 { SELECT a FROM t1 WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*id')); -} {0 {}} +} {1 {no such cursor: 6}} do_catchsql_test 1.3.1 { SELECT highlight(t1, 4, '', '') FROM t1('*reads'); -} {1 {no such cursor: 2}} +} {1 {no such cursor: 0}} do_catchsql_test 1.3.2 { SELECT a FROM t1 WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*reads')); -} {1 {no such cursor: 2}} +} {1 {no such cursor: 0}} db close sqlite3 db test.db @@ -57,7 +57,12 @@ sqlite3 db test.db do_catchsql_test 1.3.3 { SELECT a FROM t1 WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*reads')); -} {1 {no such cursor: 1}} +} {1 {no such cursor: 0}} + +fts5_aux_test_functions db +do_catchsql_test 1.3.4 { + SELECT fts5_columntext(t1) FROM t1('*reads'); +} {1 {no such cursor: 0}} #------------------------------------------------------------------------- reset_db @@ -535,5 +540,130 @@ do_execsql_test 19.0 { COMMIT; } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 20.0 { + CREATE VIRTUAL TABLE x1 USING fts5(a); + INSERT INTO x1(rowid, a) VALUES + (1, 'a b c d'), + (2, 'x b c d'), + (3, 'x y z d'), + (4, 'a y c x'); +} + +do_execsql_test 20.1 { + SELECT rowid FROM x1 WHERE x1 MATCH 'a' AND x1 MATCH 'b'; +} {1} + +do_execsql_test 20.2 { + SELECT rowid FROM x1 WHERE x1 MATCH 'a' AND x1 MATCH 'y'; +} {4} + +do_execsql_test 20.3 { + SELECT rowid FROM x1 WHERE x1 MATCH 'a' OR x1 MATCH 'y'; +} {1 4 3} + +do_execsql_test 20.4 { + SELECT rowid FROM x1 WHERE x1 MATCH 'a' OR (x1 MATCH 'y' AND x1 MATCH 'd'); +} {1 4 3} + +do_execsql_test 20.5 { + SELECT rowid FROM x1 WHERE x1 MATCH 'z' OR (x1 MATCH 'a' AND x1 MATCH 'd'); +} {3 1} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 21.0 { + CREATE TABLE t1(ii INTEGER, x TEXT, y TEXT); + CREATE VIRTUAL TABLE xyz USING fts5(content_rowid=ii, content=t1, x, y); + INSERT INTO t1 VALUES(1, 'one', 'i'); + INSERT INTO t1 VALUES(2, 'two', 'ii'); + INSERT INTO t1 VALUES(3, 'tree', 'iii'); + INSERT INTO xyz(xyz) VALUES('rebuild'); +} + +do_execsql_test 21.1 { + UPDATE xyz SET y='TWO' WHERE rowid=2; + UPDATE t1 SET y='TWO' WHERE ii=2; +} + +do_execsql_test 21.2 { + PRAGMA integrity_check +} {ok} + +breakpoint +sqlite3_db_config db DEFENSIVE 1 +do_execsql_test 21.3 { + CREATE TABLE xyz_notashadow(x, y); + DROP TABLE xyz_notashadow; +} +sqlite3_db_config db DEFENSIVE 0 + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 22.0 { + SELECT fts5(NULL); +} {{}} +do_execsql_test 22.1 { + SELECT count(*) FROM ( + SELECT fts5_source_id() + ) +} {1} +execsql_pp { + SELECT fts5_source_id() +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 23.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x); + INSERT INTO x1 VALUES('one + two + three'); + INSERT INTO x1 VALUES('one + xyz + three'); + INSERT INTO x1 VALUES('xyz + two + xyz'); +} +do_execsql_test 23.1 { + SELECT rowid FROM x1('one + two + three'); +} {1} + +do_execsql_test 23.2 { + SELECT rowid FROM x1('^".." AND one'); +} {} + +do_execsql_test 23.3 { + SELECT rowid FROM x1('abc NEAR ".." NEAR def'); +} {} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 24.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, detail='none'); + INSERT INTO t1(a) VALUES('a'); +} + +do_execsql_test 24.2 { + SELECT rank FROM ( SELECT rank FROM t1('a NOT "" NOT def') ) ORDER BY 1; +} {-1e-06} + +do_execsql_test 24.3 { + SELECT rank FROM ( SELECT rank FROM t1('a NOT � NOT def') ) ORDER BY 1; +} {-1e-06} + +do_execsql_test 24.4 { + SELECT rank FROM ( SELECT rank FROM t1('a NOT "" NOT def') ); +} {-1e-06} + +#------------------------------------------------------------------------- +reset_db +fts5_aux_test_functions db + +do_execsql_test 25.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, detail='none', content=''); + INSERT INTO t1(a) VALUES('a b c'); +} + +do_execsql_test 25.0 { + SELECT fts5_test_poslist(t1) FROM t1('b') ORDER BY rank; +} {{}} + finish_test diff --git a/ext/fts5/test/fts5near.test b/ext/fts5/test/fts5near.test index bbe144a89..318a16948 100644 --- a/ext/fts5/test/fts5near.test +++ b/ext/fts5/test/fts5near.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5near -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5optimize.test b/ext/fts5/test/fts5optimize.test index e0f0fd724..610bf439c 100644 --- a/ext/fts5/test/fts5optimize.test +++ b/ext/fts5/test/fts5optimize.test @@ -14,7 +14,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5optimize -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5optimize2.test b/ext/fts5/test/fts5optimize2.test index b0b28874c..57f4e96b9 100644 --- a/ext/fts5/test/fts5optimize2.test +++ b/ext/fts5/test/fts5optimize2.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5optimize2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5optimize3.test b/ext/fts5/test/fts5optimize3.test index 7b11b9402..79e62f9f2 100644 --- a/ext/fts5/test/fts5optimize3.test +++ b/ext/fts5/test/fts5optimize3.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5optimize2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5origintext.test b/ext/fts5/test/fts5origintext.test index 9752f35d3..8e975fa17 100644 --- a/ext/fts5/test/fts5origintext.test +++ b/ext/fts5/test/fts5origintext.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5origintext -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -261,8 +261,8 @@ do_execsql_test 5.3 { reset_db sqlite3_fts5_register_origintext db do_execsql_test 6.0 { - CREATE VIRTUAL TABLE ft USING fts5( - x, y, tokenize='origintext unicode61', detail=%DETAIL% + CREATE VIRTUAL TABLE ft USING fts5( + x, y, tokenize='origintext unicode61', detail=%DETAIL%, tokendata=0 ); INSERT INTO ft VALUES('One Two', 'Three two'); @@ -291,6 +291,22 @@ do_execsql_test 6.3 { SELECT rowid, tokens(ft) FROM ft('Three*'); } {1 {{}} 2 {{}}} +fts5_aux_test_functions db +do_catchsql_test 6.4 { + SELECT fts5_test_insttoken(ft, -1, 0) FROM ft('one'); +} {1 SQLITE_RANGE} + +do_catchsql_test 6.5 { + SELECT fts5_test_insttoken(ft, 1, 0) FROM ft('one'); +} {1 SQLITE_RANGE} + +do_catchsql_test 6.6 { + CREATE VIRTUAL TABLE ft2 USING fts5(x, tokendata=2); +} {1 {malformed tokendata=... directive}} +do_catchsql_test 6.7 { + CREATE VIRTUAL TABLE ft2 USING fts5(x, content='', tokendata=11); +} {1 {malformed tokendata=... directive}} + } finish_test diff --git a/ext/fts5/test/fts5origintext2.test b/ext/fts5/test/fts5origintext2.test index a8c5d4eb5..a8c717234 100644 --- a/ext/fts5/test/fts5origintext2.test +++ b/ext/fts5/test/fts5origintext2.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5origintext2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5origintext3.test b/ext/fts5/test/fts5origintext3.test index 834844595..9dda2a574 100644 --- a/ext/fts5/test/fts5origintext3.test +++ b/ext/fts5/test/fts5origintext3.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5origintext3 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5origintext4.test b/ext/fts5/test/fts5origintext4.test index c4ae35011..3b907ba2c 100644 --- a/ext/fts5/test/fts5origintext4.test +++ b/ext/fts5/test/fts5origintext4.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5origintext4 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5origintext5.test b/ext/fts5/test/fts5origintext5.test index 03d5bee21..848cc15b5 100644 --- a/ext/fts5/test/fts5origintext5.test +++ b/ext/fts5/test/fts5origintext5.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5origintext -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5phrase.test b/ext/fts5/test/fts5phrase.test index 10598ccf4..708cdfd83 100644 --- a/ext/fts5/test/fts5phrase.test +++ b/ext/fts5/test/fts5phrase.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5phrase -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -93,15 +93,21 @@ foreach {tn cols tokens} { 10 {b} "i e" 11 {a} "i e" } { - set fts "{$cols}:[join $tokens +]" set where [list] foreach c $cols { lappend where "pmatch($c, '$tokens')" } set where [join $where " OR "] - set res [db eval "SELECT rowid FROM t3 WHERE $where"] - do_execsql_test "1.$tn.$fts->([llength $res] rows)" { - SELECT rowid FROM t3($fts) - } $res + foreach fts [list \ + "{$cols}:[join $tokens +]" \ + "{$cols}:NEAR([join $tokens +])" \ + "{$cols}:NEAR([join $tokens +],1)" \ + "{$cols}:NEAR([join $tokens +],111)" \ + ] { + set res [db eval "SELECT rowid FROM t3 WHERE $where"] + do_execsql_test "1.$tn.$fts->([llength $res] rows)" { + SELECT rowid FROM t3($fts) + } $res + } } do_execsql_test 2.0 { diff --git a/ext/fts5/test/fts5plan.test b/ext/fts5/test/fts5plan.test index 6862acf17..57d5254a3 100644 --- a/ext/fts5/test/fts5plan.test +++ b/ext/fts5/test/fts5plan.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5plan -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5porter.test b/ext/fts5/test/fts5porter.test index c7b1ce6f3..de1c3e15a 100644 --- a/ext/fts5/test/fts5porter.test +++ b/ext/fts5/test/fts5porter.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5porter -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5porter2.test b/ext/fts5/test/fts5porter2.test index 6e81b2d31..556060baa 100644 --- a/ext/fts5/test/fts5porter2.test +++ b/ext/fts5/test/fts5porter2.test @@ -18,7 +18,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5porter2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5prefix.test b/ext/fts5/test/fts5prefix.test index 279f312f2..7a29628ea 100644 --- a/ext/fts5/test/fts5prefix.test +++ b/ext/fts5/test/fts5prefix.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5prefix -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5prefix2.test b/ext/fts5/test/fts5prefix2.test index 29744c86b..0860b3cdd 100644 --- a/ext/fts5/test/fts5prefix2.test +++ b/ext/fts5/test/fts5prefix2.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5prefix2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5query.test b/ext/fts5/test/fts5query.test index 5237e8e25..4e8bab8cf 100644 --- a/ext/fts5/test/fts5query.test +++ b/ext/fts5/test/fts5query.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5query -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5rank.test b/ext/fts5/test/fts5rank.test index 8cf223f44..7a700cb97 100644 --- a/ext/fts5/test/fts5rank.test +++ b/ext/fts5/test/fts5rank.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5rank -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5rebuild.test b/ext/fts5/test/fts5rebuild.test index ae881c02f..d74b148fb 100644 --- a/ext/fts5/test/fts5rebuild.test +++ b/ext/fts5/test/fts5rebuild.test @@ -14,7 +14,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5rebuild -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5restart.test b/ext/fts5/test/fts5restart.test index db2c62b67..da58fe3ae 100644 --- a/ext/fts5/test/fts5restart.test +++ b/ext/fts5/test/fts5restart.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5restart -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -29,6 +29,7 @@ do_execsql_test 1.0 { # Run the 'optimize' command. Check that it does not disturb ongoing # full-text queries. # +unset -nocomplain lRowid do_test 1.1 { for {set i 1} {$i < 1000} {incr i} { execsql { INSERT INTO f1 VALUES('a b c d e') } diff --git a/ext/fts5/test/fts5rowid.test b/ext/fts5/test/fts5rowid.test index 8935ecfea..e4e4f6b84 100644 --- a/ext/fts5/test/fts5rowid.test +++ b/ext/fts5/test/fts5rowid.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5rowid -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5savepoint.test b/ext/fts5/test/fts5savepoint.test index 112622275..fdb0a25ba 100644 --- a/ext/fts5/test/fts5savepoint.test +++ b/ext/fts5/test/fts5savepoint.test @@ -13,7 +13,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5savepoint -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5secure8.test b/ext/fts5/test/fts5secure8.test index 0216bb6ea..8b65b7c59 100644 --- a/ext/fts5/test/fts5secure8.test +++ b/ext/fts5/test/fts5secure8.test @@ -58,6 +58,10 @@ do_execsql_test 2.1 { pragma quick_check; } {ok} +do_catchsql_test 2.2 { + INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 'hello world'); +} {1 {SQL logic error}} + diff --git a/ext/fts5/test/fts5securefault.test b/ext/fts5/test/fts5securefault.test index 63874ece5..2959ab65c 100644 --- a/ext/fts5/test/fts5securefault.test +++ b/ext/fts5/test/fts5securefault.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] source $testdir/malloc_common.tcl set testprefix fts5securefault -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. return_if_no_fts5 do_execsql_test 1.0 { diff --git a/ext/fts5/test/fts5simple.test b/ext/fts5/test/fts5simple.test index 936bbb254..ad59bf0d9 100644 --- a/ext/fts5/test/fts5simple.test +++ b/ext/fts5/test/fts5simple.test @@ -13,7 +13,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5simple -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -350,7 +350,7 @@ do_execsql_test 14.3 { do_execsql_test 14.4 { SELECT rowid, x, x1 FROM x1 WHERE x1 MATCH '*reads' -} {0 {} 3} +} {0 {} 2} #------------------------------------------------------------------------- reset_db @@ -480,4 +480,33 @@ do_execsql_test 22.0 { do_catchsql_test 22.1 {SELECT * FROM x1('')} {1 {fts5: syntax error near ""}} do_catchsql_test 22.2 {SELECT * FROM x1(NULL)} {1 {fts5: syntax error near ""}} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 23.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x); + SELECT count(*) FROM x1_data; +} {2} + +do_execsql_test 23.1 { + BEGIN; + INSERT INTO x1 VALUES('a b c d'); + INSERT INTO x1 VALUES('a b c d'); + INSERT INTO x1 VALUES('a b c d'); +} + +do_execsql_test 23.2 { + SELECT count(*) FROM x1_data; +} {2} + +do_execsql_test 23.3 { + INSERT INTO x1(x1) VALUES('flush'); + SELECT count(*) FROM x1_data; +} {3} + +do_execsql_test 23.4 { + ROLLBACK; + SELECT count(*) FROM x1_data; +} {2} + + finish_test diff --git a/ext/fts5/test/fts5simple2.test b/ext/fts5/test/fts5simple2.test index 6c0e0e166..01c590c9f 100644 --- a/ext/fts5/test/fts5simple2.test +++ b/ext/fts5/test/fts5simple2.test @@ -13,7 +13,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5simple2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5simple3.test b/ext/fts5/test/fts5simple3.test index 0d4972b37..680448081 100644 --- a/ext/fts5/test/fts5simple3.test +++ b/ext/fts5/test/fts5simple3.test @@ -13,7 +13,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5simple3 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5synonym.test b/ext/fts5/test/fts5synonym.test index 86610ee9e..55e2f186a 100644 --- a/ext/fts5/test/fts5synonym.test +++ b/ext/fts5/test/fts5synonym.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5synonym -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5synonym2.test b/ext/fts5/test/fts5synonym2.test index 9fdf75769..ec8b750c5 100644 --- a/ext/fts5/test/fts5synonym2.test +++ b/ext/fts5/test/fts5synonym2.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5synonym2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5tokenizer.test b/ext/fts5/test/fts5tokenizer.test index 27370657e..a828e3a22 100644 --- a/ext/fts5/test/fts5tokenizer.test +++ b/ext/fts5/test/fts5tokenizer.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5tokenizer -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -300,5 +300,75 @@ set ::flags [list] do_execsql_test 9.5.1 { SELECT * FROM t1('"abc xyz*"'); } {} do_test 9.5.2 { set ::flags } {query} +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 10.1 { + CREATE VIRTUAL TABLE x1 USING fts5(x, tokenize=unicode61); + PRAGMA writable_schema = 1; + UPDATE sqlite_schema + SET sql = 'CREATE VIRTUAL TABLE x1 USING fts5(x, tokenize="unicode61 error");' + WHERE name = 'x1'; +} + +db close +sqlite3 db test.db + +do_catchsql_test 10.2 { + SELECT * FROM x1('abc'); +} {1 {error in tokenizer constructor}} + +do_catchsql_test 10.3 { + INSERT INTO x1 VALUES('abc'); +} {1 {error in tokenizer constructor}} + +do_execsql_test 10.4 { + PRAGMA writable_schema = 1; + UPDATE sqlite_schema + SET sql = 'CREATE VIRTUAL TABLE x1 USING fts5(x, tokenize="nosuch error");' + WHERE name = 'x1'; +} + +db close +sqlite3 db test.db + +do_catchsql_test 10.5 { + SELECT * FROM x1('abc'); +} {1 {no such tokenizer: nosuch}} +do_catchsql_test 10.6 { + INSERT INTO x1 VALUES('abc'); +} {1 {no such tokenizer: nosuch}} + +do_execsql_test 10.7 { + DROP TABLE x1; + SELECT * FROM sqlite_schema; +} + +reset_db +do_execsql_test 10.8 { + CREATE VIRTUAL TABLE x1 USING fts5(x, tokenize=unicode61); + INSERT INTO x1 VALUES('a b c'), ('d e f'), ('a b c'); + CREATE VIRTUAL TABLE x1v USING fts5vocab(x1, row); + + PRAGMA writable_schema = 1; + UPDATE sqlite_schema + SET sql = 'CREATE VIRTUAL TABLE x1 USING fts5(x, tokenize=simplify);' + WHERE name = 'x1'; +} + +do_execsql_test 10.9 { + SELECT * FROM x1v +} { + a 2 2 b 2 2 c 2 2 d 1 1 e 1 1 f 1 1 +} + +db close +sqlite3 db test.db + +do_execsql_test 10.10 { + SELECT * FROM x1v +} { + a 2 2 b 2 2 c 2 2 d 1 1 e 1 1 f 1 1 +} finish_test diff --git a/ext/fts5/test/fts5tokenizer2.test b/ext/fts5/test/fts5tokenizer2.test index 52b30326a..4fe31d22c 100644 --- a/ext/fts5/test/fts5tokenizer2.test +++ b/ext/fts5/test/fts5tokenizer2.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5tokenizer2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5tokenizer3.test b/ext/fts5/test/fts5tokenizer3.test new file mode 100644 index 000000000..5cdab743c --- /dev/null +++ b/ext/fts5/test/fts5tokenizer3.test @@ -0,0 +1,77 @@ +# 2024 Aug 10 +# +# 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. +# +#*********************************************************************** +# +# Tests focusing on the built-in fts5 tokenizers. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5tokenizer3 + +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + + +proc get_sod {args} { return "split_on_dot" } +proc get_lowercase {args} { return "lowercase" } + +proc lowercase {flags txt} { + set n [string length $txt] + sqlite3_fts5_token [string tolower $txt] 0 $n + return 0 +} + +proc split_on_dot {flags txt} { + set iOff 0 + foreach t [split $txt "."] { + set n [string length $txt] + sqlite3_fts5_token $t $iOff [expr $iOff+$n] + incr iOff [expr {$n+1}] + } + return "" +} + +foreach {tn script} { + 1 { + sqlite3_fts5_create_tokenizer db lowercase get_lowercase + sqlite3_fts5_create_tokenizer -parent lowercase db split_on_dot get_sod + } + 2 { + sqlite3_fts5_create_tokenizer -v2 db lowercase get_lowercase + sqlite3_fts5_create_tokenizer -parent lowercase db split_on_dot get_sod + } + 3 { + sqlite3_fts5_create_tokenizer db lowercase get_lowercase + sqlite3_fts5_create_tokenizer -v2 -parent lowercase db split_on_dot get_sod + } + 4 { + sqlite3_fts5_create_tokenizer -v2 db lowercase get_lowercase + sqlite3_fts5_create_tokenizer -v2 -parent lowercase db split_on_dot get_sod + } +} { + reset_db + eval $script + + do_execsql_test 1.$tn.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize=split_on_dot); + CREATE VIRTUAL TABLE t1vocab USING fts5vocab(t1, instance); + INSERT INTO t1 VALUES('ABC.Def.ghi'); + } + + do_execsql_test 1.$tn.1 { + SELECT term FROM t1vocab ORDER BY 1 + } {abc def ghi} +} + + +finish_test diff --git a/ext/fts5/test/fts5trigram.test b/ext/fts5/test/fts5trigram.test index 752686620..3742c647f 100644 --- a/ext/fts5/test/fts5trigram.test +++ b/ext/fts5/test/fts5trigram.test @@ -56,6 +56,7 @@ foreach {tn like res} { 7 {ABCDEFG%} 1 8 {%รุงเ%} 2 9 {%งเ%} 2 + 10 {%"งเ"%} {} } { do_execsql_test 1.3.$tn { SELECT rowid FROM t1 WHERE y LIKE $like @@ -200,6 +201,12 @@ do_eqp_test 6.3 { do_eqp_test 6.4 { SELECT * FROM ci1 WHERE x GLOB ? } {VIRTUAL TABLE INDEX 0:G0} +do_eqp_test 6.5 { + SELECT * FROM ci1 WHERE x < ? +} {{SCAN ci1 VIRTUAL TABLE INDEX 0:}} +do_eqp_test 6.6 { + SELECT * FROM ci0 WHERE x < ? +} {{SCAN ci0 VIRTUAL TABLE INDEX 0:}} reset_db do_execsql_test 7.0 { @@ -256,4 +263,85 @@ do_execsql_test 8.3 { {[abcde]} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 9.0 { + CREATE VIRTUAL TABLE t1 USING fts5( + a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + tokenize=trigram + ); + + INSERT INTO t1(rowid, a12) VALUES(111, 'thats a tricky case though'); + INSERT INTO t1(rowid, a12) VALUES(222, 'the query planner cannot do'); +} + +do_execsql_test 9.1 { + SELECT rowid FROM t1 WHERE a12 LIKE '%tricky%' +} {111} + +do_execsql_test 9.2 { + SELECT rowid FROM t1 WHERE a12 LIKE '%tricky%' AND a12 LIKE '%case%' +} {111} + +do_execsql_test 9.3 { + SELECT rowid FROM t1 WHERE a12 LIKE NULL +} {} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 10.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=trigram); +} + +do_test 10.1 { + foreach {val} { + "abc \UFFjkl\UFF" + "abc \UFFFjkl\UFFF" + "abc \UFFFFjkl\UFFFF" + "abc \UFFFFFjkl\UFFFFF" + "\UFFjkl\UFF abc" + "\UFFFjkl\UFFF abc" + "\UFFFFjkl\UFFFF abc" + "\UFFFFFjkl\UFFFFF abc" + "\U10001jkl\U10001 abc" + } { + execsql { INSERT INTO t1 VALUES( $val ) } + } +} {} + +do_test 10.2 { + foreach {val} { + X'E18000626320646566' + X'61EDA0806320646566' + X'61EDA0806320646566' + X'61EFBFBE6320646566' + X'76686920E18000626320646566' + X'7668692061EDA0806320646566' + X'7668692061EDA0806320646566' + X'7668692061EFBFBE6320646566' + } { + execsql " INSERT INTO t1 VALUES( $val ) " + } +} {} + +do_test 10.3 { + set a [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0x62}] + set b [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0x62}] + set c [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0xBF 0x62}] + set d [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0x62}] + execsql { + INSERT INTO t1 VALUES($a); + INSERT INTO t1 VALUES($b); + INSERT INTO t1 VALUES($c); + INSERT INTO t1 VALUES($d); + + INSERT INTO t1 VALUES('abcd' || $a); + INSERT INTO t1 VALUES('abcd' || $b); + INSERT INTO t1 VALUES('abcd' || $c); + INSERT INTO t1 VALUES('abcd' || $d); + } +} {} + + + finish_test diff --git a/ext/fts5/test/fts5trigram2.test b/ext/fts5/test/fts5trigram2.test index 395d8994b..c81684a22 100644 --- a/ext/fts5/test/fts5trigram2.test +++ b/ext/fts5/test/fts5trigram2.test @@ -104,9 +104,34 @@ do_execsql_test 4.0 { CREATE VIRTUAL TABLE t4 USING fts5(z, tokenize=trigram); } {} -breakpoint do_execsql_test 4.1 { INSERT INTO t4 VALUES('ABCD'); + INSERT INTO t4 VALUES('DEFG'); } {} +db close +sqlite3 db test.db + +do_eqp_test 4.1 { + SELECT rowid FROM t4 WHERE z LIKE '%abc%' +} {VIRTUAL TABLE INDEX 0:L0} + +do_execsql_test 4.2 { + SELECT rowid FROM t4 WHERE z LIKE '%abc%' +} {1} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE t5 USING fts5( + c1, tokenize='trigram', detail='none' + ); + INSERT INTO t5(rowid, c1) VALUES(1, 'abc_____xyx_yxz'); + INSERT INTO t5(rowid, c1) VALUES(2, 'abc_____xyxz'); + INSERT INTO t5(rowid, c1) VALUES(3, 'ac_____xyxz'); +} {} +do_execsql_test 5.1 { + SELECT rowid FROM t5 WHERE c1 LIKE 'abc%xyxz' +} {2} + finish_test diff --git a/ext/fts5/test/fts5ubsan.test b/ext/fts5/test/fts5ubsan.test index 2dc0aa7bd..76382a1e1 100644 --- a/ext/fts5/test/fts5ubsan.test +++ b/ext/fts5/test/fts5ubsan.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5ubsan -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5unicode.test b/ext/fts5/test/fts5unicode.test index e2d0f6012..f10e0d02d 100644 --- a/ext/fts5/test/fts5unicode.test +++ b/ext/fts5/test/fts5unicode.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5unicode -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -60,6 +60,7 @@ do_execsql_test 2.1 " # require 17 or more bits to store). # +unset -nocomplain A B C D set A [db one {SELECT char(0x1F75E)}] ;# Type So set B [db one {SELECT char(0x1F5FD)}] ;# Type So set C [db one {SELECT char(0x2F802)}] ;# Type Lo diff --git a/ext/fts5/test/fts5unicode2.test b/ext/fts5/test/fts5unicode2.test index 662b9dd87..7a49a1d83 100644 --- a/ext/fts5/test/fts5unicode2.test +++ b/ext/fts5/test/fts5unicode2.test @@ -17,7 +17,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5unicode2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -116,6 +116,7 @@ set docs [list { connected by OR. }] +unset -nocomplain map set map(a) [list "\u00C4" "\u00E4"] ; # LATIN LETTER A WITH DIAERESIS set map(e) [list "\u00CB" "\u00EB"] ; # LATIN LETTER E WITH DIAERESIS set map(i) [list "\u00CF" "\u00EF"] ; # LATIN LETTER I WITH DIAERESIS @@ -470,119 +471,23 @@ do_execsql_test 8.2.3 { } {2 4} #------------------------------------------------------------------------- -# -if 0 { -foreach {tn sql} { - 1 { - CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 [tokenchars= .]); - CREATE VIRTUAL TABLE t6 USING fts4( - tokenize=unicode61 [tokenchars=="] "tokenchars=[]"); - CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 [separators=x\xC4]); - } - 2 { - CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 "tokenchars= ."); - CREATE VIRTUAL TABLE t6 USING fts4(tokenize=unicode61 "tokenchars=[=""]"); - CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 "separators=x\xC4"); - } - 3 { - CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 'tokenchars= .'); - CREATE VIRTUAL TABLE t6 USING fts4(tokenize=unicode61 'tokenchars=="[]'); - CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 'separators=x\xC4'); - } - 4 { - CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 `tokenchars= .`); - CREATE VIRTUAL TABLE t6 USING fts4(tokenize=unicode61 `tokenchars=[="]`); - CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 `separators=x\xC4`); - } -} { - do_execsql_test 9.$tn.0 { - DROP TABLE IF EXISTS t5; - DROP TABLE IF EXISTS t5aux; - DROP TABLE IF EXISTS t6; - DROP TABLE IF EXISTS t6aux; - DROP TABLE IF EXISTS t7; - DROP TABLE IF EXISTS t7aux; - } - do_execsql_test 9.$tn.1 $sql - - do_execsql_test 9.$tn.2 { - CREATE VIRTUAL TABLE t5aux USING fts4aux(t5); - INSERT INTO t5 VALUES('one two three/four.five.six'); - SELECT * FROM t5aux; - } { - four.five.six * 1 1 four.five.six 0 1 1 - {one two three} * 1 1 {one two three} 0 1 1 - } - - do_execsql_test 9.$tn.3 { - CREATE VIRTUAL TABLE t6aux USING fts4aux(t6); - INSERT INTO t6 VALUES('alpha=beta"gamma/delta[epsilon]zeta'); - SELECT * FROM t6aux; - } { - {alpha=beta"gamma} * 1 1 {alpha=beta"gamma} 0 1 1 - {delta[epsilon]zeta} * 1 1 {delta[epsilon]zeta} 0 1 1 - } - - do_execsql_test 9.$tn.4 { - CREATE VIRTUAL TABLE t7aux USING fts4aux(t7); - INSERT INTO t7 VALUES('alephxbeth\xC4gimel'); - SELECT * FROM t7aux; - } { - aleph * 1 1 aleph 0 1 1 - beth * 1 1 beth 0 1 1 - gimel * 1 1 gimel 0 1 1 - } -} - -# Check that multiple options are handled correctly. -# -do_execsql_test 10.1 { - DROP TABLE IF EXISTS t1; - CREATE VIRTUAL TABLE t1 USING fts4(tokenize=unicode61 - "tokenchars=xyz" "tokenchars=.=" "separators=.=" "separators=xy" - "separators=a" "separators=a" "tokenchars=a" "tokenchars=a" - ); - - INSERT INTO t1 VALUES('oneatwoxthreeyfour'); - INSERT INTO t1 VALUES('a.single=word'); - CREATE VIRTUAL TABLE t1aux USING fts4aux(t1); - SELECT * FROM t1aux; -} { - .single=word * 1 1 .single=word 0 1 1 - four * 1 1 four 0 1 1 - one * 1 1 one 0 1 1 - three * 1 1 three 0 1 1 - two * 1 1 two 0 1 1 -} - -# Test that case folding happens after tokenization, not before. -# -do_execsql_test 10.2 { - DROP TABLE IF EXISTS t2; - CREATE VIRTUAL TABLE t2 USING fts4(tokenize=unicode61 "separators=aB"); - INSERT INTO t2 VALUES('oneatwoBthree'); - INSERT INTO t2 VALUES('onebtwoAthree'); - CREATE VIRTUAL TABLE t2aux USING fts4aux(t2); - SELECT * FROM t2aux; -} { - one * 1 1 one 0 1 1 - onebtwoathree * 1 1 onebtwoathree 0 1 1 - three * 1 1 three 0 1 1 - two * 1 1 two 0 1 1 -} -# Test that the tokenchars and separators options work with the -# fts3tokenize table. -# -do_execsql_test 11.1 { - CREATE VIRTUAL TABLE ft1 USING fts3tokenize( - "unicode61", "tokenchars=@.", "separators=1234567890" - ); - SELECT token FROM ft1 WHERE input = 'berlin@street123sydney.road'; +foreach {tn val bErr} { + 1 0 0 + 2 1 0 + 3 2 0 + 4 3 1 + 5 11 1 } { - berlin@street sydney.road -} - + reset_db + set aRes(0) {0 {}} + set aRes(1) {1 {error in tokenizer constructor}} + set res $aRes($bErr) + do_catchsql_test 9.1.$tn " + CREATE VIRTUAL TABLE bl USING fts5( + s, tokenize='trigram remove_diacritics $val' + ); + " $res } finish_test diff --git a/ext/fts5/test/fts5unicode3.test b/ext/fts5/test/fts5unicode3.test index 30eb3c416..ddb61a999 100644 --- a/ext/fts5/test/fts5unicode3.test +++ b/ext/fts5/test/fts5unicode3.test @@ -14,7 +14,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5unicode4.test b/ext/fts5/test/fts5unicode4.test index dfd7f5a25..dc225cb5e 100644 --- a/ext/fts5/test/fts5unicode4.test +++ b/ext/fts5/test/fts5unicode4.test @@ -14,7 +14,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5unicode4 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5unindexed.test b/ext/fts5/test/fts5unindexed.test index 8b72c4c77..5099a8969 100644 --- a/ext/fts5/test/fts5unindexed.test +++ b/ext/fts5/test/fts5unindexed.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5unindexed -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5unindexed2.test b/ext/fts5/test/fts5unindexed2.test new file mode 100644 index 000000000..c0abfc398 --- /dev/null +++ b/ext/fts5/test/fts5unindexed2.test @@ -0,0 +1,297 @@ +# 2024 Sep 13 +# +# 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 tests in this file focus on "unindexed" columns in contentless +# tables. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5unindexed2 + +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + + +do_execsql_test 1.1 { + CREATE VIRTUAL TABLE t1 USING fts5( + a, b UNINDEXED, content=, contentless_unindexed=1 + ); +} {} + +do_execsql_test 1.2 { + INSERT INTO t1 VALUES('abc def', 'ghi jkl'); +} + +do_execsql_test 1.3 { + SELECT rowid, a, b FROM t1 +} {1 {} {ghi jkl}} + +do_execsql_test 1.4 { + INSERT INTO t1(rowid, a, b) VALUES(11, 'hello world', 'one two three'); +} + +do_execsql_test 1.5 { + INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 1, 'abc def', 'ghi jkl'); +} + +do_execsql_test 1.6 { + SELECT rowid, a, b FROM t1 +} { + 11 {} {one two three} +} + +do_execsql_test 1.7 { + PRAGMA integrity_check +} {ok} + +do_execsql_test 1.8 { + INSERT INTO t1(rowid, a, b) VALUES(12, 'abc def', 'ghi jkl'); +} + +do_execsql_test 1.9 { + SELECT rowid, a, b FROM t1('def') +} {12 {} {ghi jkl}} + +do_execsql_test 1.10 { + SELECT rowid, a, b FROM t1('def OR hello') ORDER BY rank +} {11 {} {one two three} 12 {} {ghi jkl}} + +do_execsql_test 1.11 { + SELECT rowid, a, b FROM t1 WHERE rowid=11 +} {11 {} {one two three}} + +do_execsql_test 1.12 { + SELECT rowid, a, b FROM t1 +} {11 {} {one two three} 12 {} {ghi jkl}} + + +fts5_aux_test_functions db +do_execsql_test 1.12.2 { + SELECT rowid, fts5_test_columntext(t1) FROM t1('def OR hello') +} {11 {{} {one two three}} 12 {{} {ghi jkl}}} + +do_execsql_test 1.13 { + INSERT INTO t1(t1) VALUES('delete-all'); +} + +do_execsql_test 1.14 { + SELECT rowid, a, b FROM t1 +} + +do_execsql_test 1.15 { + PRAGMA integrity_check +} {ok} + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t4 USING fts5( + x, y UNINDEXED, z, columnsize=0, content='', contentless_unindexed=1 + ); +} + +do_execsql_test 2.1 { + INSERT INTO t4(rowid, x, y, z) VALUES(1, 'a a', 'b b b', 'c'); +} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE x1 USING fts5( + a UNINDEXED, b, c UNINDEXED, d, content=, contentless_delete=1, + contentless_unindexed=1 + ); +} + +do_execsql_test 3.1 { + INSERT INTO x1(rowid, a, b, c, d) VALUES(131, 'aaa', 'bbb', 'ccc', 'ddd'); +} + +do_execsql_test 3.2 { + SELECT * FROM x1 +} {aaa {} ccc {}} + +do_execsql_test 3.3 { + INSERT INTO x1(rowid, a, b, c, d) VALUES(1000, 'AAA', 'BBB', 'CCC', 'DDD'); +} + +do_execsql_test 3.4 { + SELECT rowid, * FROM x1 +} { + 131 aaa {} ccc {} + 1000 AAA {} CCC {} +} + +do_execsql_test 3.5 { + DELETE FROM x1 WHERE rowid=131; + SELECT rowid, * FROM x1 +} { + 1000 AAA {} CCC {} +} + +do_execsql_test 3.6 { + INSERT INTO x1(rowid, a, b, c, d) VALUES(112, 'aaa', 'bbb', 'ccc', 'ddd'); + SELECT rowid, * FROM x1 +} { + 112 aaa {} ccc {} + 1000 AAA {} CCC {} +} + +do_execsql_test 3.7 { + UPDATE x1 SET b='hello', d='world', rowid=1120 WHERE rowid=112 +} + +do_execsql_test 3.8 { + SELECT rowid, * FROM x1 +} { + 1000 AAA {} CCC {} + 1120 aaa {} ccc {} +} + +do_execsql_test 3.9 { + SELECT rowid, * FROM x1('hello'); +} { + 1120 aaa {} ccc {} +} + +do_execsql_test 3.9 { + SELECT rowid, * FROM x1('bbb'); +} { + 1000 AAA {} CCC {} +} + +fts5_aux_test_functions db +do_execsql_test 3.10 { + SELECT rowid, fts5_test_columntext(x1) FROM x1('b*') +} {1000 {AAA {} CCC {}}} + +#------------------------------------------------------------------------- +# Check that if contentless_unindexed=1 is not specified, the values +# of UNINDEXED columns are not stored in the database. +# +# Also check that contentless_unindexed=1 is not allowed unless the table +# is actually contentless. +# +reset_db +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE ft USING fts5(a, b, c UNINDEXED, content=''); + INSERT INTO ft VALUES('one', 'two', 'three'); + SELECT rowid, * FROM ft; +} {1 {} {} {}} + +do_execsql_test 4.1 { + SELECT name FROM sqlite_schema ORDER BY 1 +} { + ft ft_config ft_data ft_docsize ft_idx +} + +do_catchsql_test 4.2 { + CREATE VIRTUAL TABLE ft2 USING fts5( + a, b, c UNINDEXED, contentless_unindexed=1 + ); +} {1 {contentless_unindexed=1 requires a contentless table}} + +do_catchsql_test 4.3 { + DELETE FROM ft WHERE rowid=1 +} {1 {cannot DELETE from contentless fts5 table: ft}} + +#------------------------------------------------------------------------- +# Check that the usual restrictions on contentless tables apply to +# contentless_unindexed=1 tables. +# +reset_db +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE ft USING fts5( + a, b UNINDEXED, c, content='', contentless_unindexed=1 + ); + INSERT INTO ft VALUES('one', 'two', 'three'); + INSERT INTO ft VALUES('four', 'five', 'six'); + INSERT INTO ft VALUES('seven', 'eight', 'nine'); + SELECT rowid, * FROM ft; +} { + 1 {} two {} + 2 {} five {} + 3 {} eight {} +} + +do_execsql_test 5.1 { + PRAGMA integrity_check +} {ok} + +do_catchsql_test 5.2 { + DELETE FROM ft WHERE rowid=2 +} {1 {cannot DELETE from contentless fts5 table: ft}} + +do_execsql_test 5.3 { + SELECT rowid, * FROM ft('six') +} { + 2 {} five {} +} + +do_catchsql_test 5.4 { + UPDATE ft SET a='x', b='y', c='z' WHERE rowid=3 +} {1 {cannot UPDATE contentless fts5 table: ft}} + +fts5_aux_test_functions db + +do_execsql_test 5.5 { + SELECT fts5_test_columntext(ft) FROM ft WHERE rowid=3 +} { + {{} eight {}} +} +do_execsql_test 5.6 { + SELECT fts5_test_columntext(ft) FROM ft('three'); +} { + {{} two {}} +} + +#------------------------------------------------------------------------- +# Check that it is possible to UPDATE a contentless_unindexed=1 table +# if the only columns being modified are UNINDEXED. +# +# If the contentless_unindexed=1 table is also contentless_delete=1, then +# it is also possible to update indexed columns - but only if *all* indexed +# columns are updated. +# +reset_db +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE ft1 USING fts5(a, b UNINDEXED, c UNINDEXED, d, + contentless_unindexed=1, content='' + ); + + INSERT INTO ft1(rowid, a, b, c, d) VALUES + (100, 'x y', 'b1', 'c1', 'a b'), + (200, 'c d', 'b2', 'c2', 'a b'), + (300, 'e f', 'b3', 'c3', 'a b'); +} + +do_execsql_test 6.1 { + UPDATE ft1 SET b='b1.1', c='c1.1' WHERE rowid=100; +} +do_execsql_test 6.2 { + UPDATE ft1 SET b='b2.1' WHERE rowid=200; +} +do_execsql_test 6.3 { + UPDATE ft1 SET c='c3.1' WHERE rowid=300; +} + +do_execsql_test 6.4 { + SELECT rowid, a, b, c, d FROM ft1 +} { + 100 {} b1.1 c1.1 {} + 200 {} b2.1 c2 {} + 300 {} b3 c3.1 {} +} + +finish_test + diff --git a/ext/fts5/test/fts5update2.test b/ext/fts5/test/fts5update2.test new file mode 100644 index 000000000..d04af4800 --- /dev/null +++ b/ext/fts5/test/fts5update2.test @@ -0,0 +1,177 @@ +# 2024 Sep 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 script is testing the FTS5 module. +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5update2 + +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + + +#------------------------------------------------------------------------- +# Test that the various types of UPDATE statement are handled correctly +# by different table types. +# +foreach_detail_mode $testprefix { +foreach {tn cu} { + 1 0 + 2 1 +} { + reset_db + do_execsql_test 1.$tn.1 " + CREATE VIRTUAL TABLE ft1 USING fts5(a, b UNINDEXED, c UNINDEXED, d, + content='', + contentless_unindexed=$cu, + detail=%DETAIL% + ); + CREATE VIRTUAL TABLE ft2 USING fts5(a, b UNINDEXED, c UNINDEXED, d, + content='', + contentless_unindexed=$cu, contentless_delete=1, + detail=%DETAIL% + ); + " + + do_execsql_test 1.$tn.2 { + INSERT INTO ft1(rowid, a, b, c, d) VALUES(1, 'a1', 'b1', 'c1', 'd1'); + INSERT INTO ft1(rowid, a, b, c, d) VALUES(2, 'a2', 'b2', 'c2', 'd2'); + INSERT INTO ft1(rowid, a, b, c, d) VALUES(3, 'a3', 'b3', 'c3', 'd3'); + + INSERT INTO ft2(rowid, a, b, c, d) VALUES(1, 'a1', 'b1', 'c1', 'd1'); + INSERT INTO ft2(rowid, a, b, c, d) VALUES(2, 'a2', 'b2', 'c2', 'd2'); + INSERT INTO ft2(rowid, a, b, c, d) VALUES(3, 'a3', 'b3', 'c3', 'd3'); + } + + # It should be possible to update a subset of the UNINDEXED columns of + # a contentless table. Regardless of whether or not contentless_unindexed=1 + # or contentless_delete=1 is set. + do_execsql_test 1.$tn.3 { + UPDATE ft1 SET b=b||'.1'; + UPDATE ft2 SET b=b||'.1'; + } + do_execsql_test 1.$tn.4 { + UPDATE ft1 SET b=b||'.2', c=c||'.2'; + UPDATE ft2 SET b=b||'.2', c=c||'.2'; + } + + set res(0) { + 1 {} {} {} {} + 2 {} {} {} {} + 3 {} {} {} {} + } + set res(1) { + 1 {} b1.1.2 c1.2 {} + 2 {} b2.1.2 c2.2 {} + 3 {} b3.1.2 c3.2 {} + } + + do_execsql_test 1.$tn.5 { + SELECT rowid, * FROM ft2 + } $res($cu) + + do_execsql_test 1.6.1 { SELECT rowid FROM ft1('a2') } {2} + do_execsql_test 1.6.2 { SELECT rowid FROM ft2('a2') } {2} + + # It should be possible to update all indexed columns (but no other subset) + # if the contentless_delete=1 option is set, as it is for "ft2". + do_execsql_test 1.$tn.7 { + UPDATE ft2 SET a='a22', d='d22' WHERE rowid=2; + } + do_execsql_test 1.$tn.8 { SELECT rowid FROM ft2('a22 AND d22') } {2} + + do_execsql_test 1.$tn.9 { + UPDATE ft2 SET a='a33', d='d33', b='b3' WHERE rowid=3; + } + + set res(1) { + 1 {} b1.1.2 c1.2 {} + 2 {} b2.1.2 c2.2 {} + 3 {} b3 c3.2 {} + } + do_execsql_test 1.$tn.10 { + SELECT rowid, * FROM ft2 + } $res($cu) + + do_catchsql_test 1.$tn.11 { + UPDATE ft2 SET a='a11' WHERE rowid=1 + } {1 {cannot UPDATE a subset of columns on fts5 contentless-delete table: ft2}} + do_catchsql_test 1.$tn.12 { + UPDATE ft2 SET d='d11' WHERE rowid=1 + } {1 {cannot UPDATE a subset of columns on fts5 contentless-delete table: ft2}} + + # It is not possible to update the values of indexed columns if + # contentless_delete=1 is not set. + do_catchsql_test 1.$tn.13 { + UPDATE ft1 SET a='a11' WHERE rowid=1 + } {1 {cannot UPDATE contentless fts5 table: ft1}} + do_catchsql_test 1.$tn.14 { + UPDATE ft1 SET d='d11' WHERE rowid=1 + } {1 {cannot UPDATE contentless fts5 table: ft1}} + + # It should be possible to update the rowid if contentless_delete=1 is + # set and all indexed columns are updated. + do_execsql_test 1.$tn.15 { + UPDATE ft2 SET a='aXone', d='dXone', rowid=11 WHERE rowid=1 + } + + set res(0) { + 2 {} {} {} {} + 3 {} {} {} {} + 11 {} {} {} {} + } + set res(1) { + 2 {} b2.1.2 c2.2 {} + 3 {} b3 c3.2 {} + 11 {} b1.1.2 c1.2 {} + } + do_execsql_test 1.$tn.16 { + SELECT rowid, * FROM ft2 + } $res($cu) + + # Should not be possible to update the rowid of a contentless_delete=1 + # table if no indexed columns are updated. + do_catchsql_test 1.$tn.17 { + UPDATE ft2 SET rowid=12 WHERE rowid=11 + } {1 {cannot UPDATE a subset of columns on fts5 contentless-delete table: ft2}} + do_catchsql_test 1.$tn.18 { + UPDATE ft1 SET rowid=12 WHERE rowid=1 + } {1 {cannot UPDATE contentless fts5 table: ft1}} + + do_execsql_test 1.$tn.19 { + UPDATE ft2 SET a='aXtwo', d='dXtwo', c='newval', rowid=12 WHERE rowid=2 + } {} + + set res(0) { + 3 {} {} {} {} + 11 {} {} {} {} + 12 {} {} {} {} + } + set res(1) { + 3 {} b3 c3.2 {} + 11 {} b1.1.2 c1.2 {} + 12 {} b2.1.2 newval {} + } + do_execsql_test 1.$tn.20 { + SELECT rowid, * FROM ft2 + } $res($cu) + + do_execsql_test 1.$tn.21 { + SELECT rowid, * FROM ft2('aXtwo AND dXtwo') + } [lrange $res($cu) 10 end] + +}} ;# end of [foreach_detail_mode] loop + +finish_test diff --git a/ext/fts5/test/fts5version.test b/ext/fts5/test/fts5version.test index 79fd94e6b..a92c0dc9f 100644 --- a/ext/fts5/test/fts5version.test +++ b/ext/fts5/test/fts5version.test @@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5version -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/fts5/test/fts5vocab.test b/ext/fts5/test/fts5vocab.test index c457c5c21..b1644527e 100644 --- a/ext/fts5/test/fts5vocab.test +++ b/ext/fts5/test/fts5vocab.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5vocab -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return @@ -513,6 +513,7 @@ do_execsql_test 10.5 { INSERT INTO ft(a) VALUES('4 5 6'); } +unset -nocomplain x res do_test 10.6 { set res [list] db eval { SELECT rowid FROM ft('4') } x { diff --git a/ext/fts5/test/fts5vocab2.test b/ext/fts5/test/fts5vocab2.test index ecacc50da..7b3c3b0d6 100644 --- a/ext/fts5/test/fts5vocab2.test +++ b/ext/fts5/test/fts5vocab2.test @@ -15,7 +15,7 @@ source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5vocab2 -# If SQLITE_ENABLE_FTS5 is defined, omit this file. +# If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return diff --git a/ext/intck/test_intck.c b/ext/intck/test_intck.c index 84008fb07..d3a619b50 100644 --- a/ext/intck/test_intck.c +++ b/ext/intck/test_intck.c @@ -15,13 +15,7 @@ #include "sqlite3.h" #include "sqlite3intck.h" - -#if defined(INCLUDE_SQLITE_TCL_H) -# include "sqlite_tcl.h" -#else -# include "tcl.h" -#endif - +#include "tclsqlite.h" #include #include @@ -92,8 +86,9 @@ static int testIntckCmd( case 3: assert( 0==strcmp("error", aCmd[iIdx].zName) ); { const char *zErr = 0; + Tcl_Obj *pRes; rc = sqlite3_intck_error(p->intck, 0); - Tcl_Obj *pRes = Tcl_NewObj(); + pRes = Tcl_NewObj(); Tcl_ListObjAppendElement( interp, pRes, Tcl_NewStringObj(sqlite3ErrName(rc), -1) ); diff --git a/ext/jni/src/org/sqlite/jni/capi/SQLTester.java b/ext/jni/src/org/sqlite/jni/capi/SQLTester.java index 81d6106be..c68785e2c 100644 --- a/ext/jni/src/org/sqlite/jni/capi/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/capi/SQLTester.java @@ -851,11 +851,26 @@ class JsonBlockCommand extends TableResultCommand { //! --new command class NewDbCommand extends OpenDbCommand { public NewDbCommand(){ super(true); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + if(argv.length>1){ + Util.unlink(argv[1]); + } + super.process(t, ts, argv); + } + } -//! Placeholder dummy/no-op commands +//! Placeholder dummy/no-op/unimplemented commands class NoopCommand extends Command { + private boolean verbose = false; + public NoopCommand(boolean verbose){ + this.verbose = verbose; + } + public NoopCommand(){} public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + if( this.verbose ){ + t.outln("Skipping unhandled command: "+argv[0]); + } } } @@ -1021,6 +1036,7 @@ static Command getCommandByName(String name){ case "db": rv = new DbCommand(); break; case "glob": rv = new GlobCommand(); break; case "json": rv = new JsonCommand(); break; + case "jsonglob": rv = new NoopCommand(true); break; case "json-block": rv = new JsonBlockCommand(); break; case "new": rv = new NewDbCommand(); break; case "notglob": rv = new NotGlobCommand(); break; @@ -1030,6 +1046,7 @@ static Command getCommandByName(String name){ case "print": rv = new PrintCommand(); break; case "result": rv = new ResultCommand(); break; case "run": rv = new RunCommand(); break; + case "stmt-cache": rv = new NoopCommand(); break; case "tableresult": rv = new TableResultCommand(); break; case "testcase": rv = new TestCaseCommand(); break; case "verbosity": rv = new VerbosityCommand(); break; diff --git a/ext/jni/src/org/sqlite/jni/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/test-script-interpreter.md index a51d64d10..939f77e1b 100644 --- a/ext/jni/src/org/sqlite/jni/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/test-script-interpreter.md @@ -4,7 +4,7 @@ The purpose of the Test Script Interpreter is to read and interpret script files that contain SQL commands and desired results. The -interpreter will check results and report an discrepencies found. +interpreter will check results and report any discrepencies found. The test script files are ASCII text files. The filename always ends with ".test". Each script is evaluated independently; context does not carry @@ -87,6 +87,7 @@ Each command looks like an SQL comment. The command begins at the left margin (no leading space) and starts with exactly 2 minus signs ("-"). The command name consists of lowercase letters and maybe a "-" or two. Some commands have arguments. + The arguments are separated from the command name by one or more spaces. Commands have access to the input buffer and might reset the input buffer. @@ -159,9 +160,9 @@ the result buffer. This distinction does not matter for the --result command itself, but it is important for related commands like --glob and --notglob. Sometimes test cases will contains a bunch of SQL followed by multiple --glob and/or --notglob statements. All of the -globs should be evaluted agains the result buffer correct, but the SQL -should only be run once. This is accomplished by resetting the input -buffer but not the result buffer. +globs should be evaluated agains the result buffer, but the SQL should +only be run once. This is accomplished by resetting the input buffer +but not the result buffer. ### The --glob command @@ -187,7 +188,7 @@ The TEST-GLOB pattern is slightly different for a standard GLOB: ### The --notglob command The --notglob command works just like --glob except that it reports an -error if the GLOB does match, rather than if the GLOB does not matches. +error if the GLOB does match, rather than if the GLOB does not match. ### The --oom command diff --git a/ext/misc/completion.c b/ext/misc/completion.c index 987595a3d..54abc0ae1 100644 --- a/ext/misc/completion.c +++ b/ext/misc/completion.c @@ -251,7 +251,7 @@ static int completionNext(sqlite3_vtab_cursor *cur){ zSql = sqlite3_mprintf( "%z%s" "SELECT pti.name FROM \"%w\".sqlite_schema AS sm" - " JOIN pragma_table_info(sm.name,%Q) AS pti" + " JOIN pragma_table_xinfo(sm.name,%Q) AS pti" " WHERE sm.type='table'", zSql, zSep, zDb, zDb ); diff --git a/ext/misc/compress.c b/ext/misc/compress.c index 2e7a31636..6b034eb45 100644 --- a/ext/misc/compress.c +++ b/ext/misc/compress.c @@ -89,7 +89,7 @@ static void uncompressFunc( unsigned int nIn; unsigned long int nOut; int rc; - int i; + unsigned int i; pIn = sqlite3_value_blob(argv[0]); nIn = sqlite3_value_bytes(argv[0]); diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c index ca8090ed2..483ef0187 100644 --- a/ext/misc/fileio.c +++ b/ext/misc/fileio.c @@ -40,7 +40,7 @@ ** modification-time of the target file is set to this value before ** returning. ** -** If three or more arguments are passed to this function and an +** If five or more arguments are passed to this function and an ** error is encountered, an exception is raised. ** ** READFILE(FILE): @@ -110,6 +110,13 @@ SQLITE_EXTENSION_INIT1 #include #include +/* When used as part of the CLI, the sqlite3_stdio.h module will have +** been included before this one. In that case use the sqlite3_stdio.h +** #defines. If not, create our own for fopen(). +*/ +#ifndef _SQLITE3_STDIO_H_ +# define sqlite3_fopen fopen +#endif /* ** Structure of the fsdir() table-valued function @@ -142,7 +149,7 @@ static void readFileContents(sqlite3_context *ctx, const char *zName){ sqlite3 *db; int mxBlob; - in = fopen(zName, "rb"); + in = sqlite3_fopen(zName, "rb"); if( in==0 ){ /* File does not exist or is unreadable. Leave the result set to NULL. */ return; @@ -397,7 +404,7 @@ static int writeFile( sqlite3_int64 nWrite = 0; const char *z; int rc = 0; - FILE *out = fopen(zFile, "wb"); + FILE *out = sqlite3_fopen(zFile, "wb"); if( out==0 ) return 1; z = (const char*)sqlite3_value_blob(pData); if( z ){ diff --git a/ext/misc/percentile.c b/ext/misc/percentile.c index d83bc5b83..1c6191d42 100644 --- a/ext/misc/percentile.c +++ b/ext/misc/percentile.c @@ -11,7 +11,7 @@ ****************************************************************************** ** ** This file contains code to implement the percentile(Y,P) SQL function -** as described below: +** and similar as described below: ** ** (1) The percentile(Y,P) function is an aggregate function taking ** exactly two arguments. @@ -57,29 +57,108 @@ ** (12) The percentile(Y,P) is implemented as a single C99 source-code ** file that compiles into a shared-library or DLL that can be loaded ** into SQLite using the sqlite3_load_extension() interface. +** +** (13) A separate median(Y) function is the equivalent percentile(Y,50). +** +** (14) A separate percentile_cont(Y,P) function is equivalent to +** percentile(Y,P/100.0). In other words, the fraction value in +** the second argument is in the range of 0 to 1 instead of 0 to 100. +** +** (15) A separate percentile_disc(Y,P) function is like +** percentile_cont(Y,P) except that instead of returning the weighted +** average of the nearest two input values, it returns the next lower +** value. So the percentile_disc(Y,P) will always return a value +** that was one of the inputs. +** +** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and +** percentile_disc(Y,P) can be used as window functions. +** +** Differences from standard SQL: +** +** * The percentile_cont(X,P) function is equivalent to the following in +** standard SQL: +** +** (percentile_cont(P) WITHIN GROUP (ORDER BY X)) +** +** The SQLite syntax is much more compact. The standard SQL syntax +** is also supported if SQLite is compiled with the +** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option. +** +** * No median(X) function exists in the SQL standard. App developers +** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)". +** +** * No percentile(Y,P) function exists in the SQL standard. Instead of +** percential(Y,P), developers must write this: +** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that +** the fraction parameter to percentile() goes from 0 to 100 whereas +** the fraction parameter in SQL standard percentile_cont() goes from +** 0 to 1. +** +** Implementation notes as of 2024-08-31: +** +** * The regular aggregate-function versions of these routines work +** by accumulating all values in an array of doubles, then sorting +** that array using quicksort before computing the answer. Thus +** the runtime is O(NlogN) where N is the number of rows of input. +** +** * For the window-function versions of these routines, the array of +** inputs is sorted as soon as the first value is computed. Thereafter, +** the array is kept in sorted order using an insert-sort. This +** results in O(N*K) performance where K is the size of the window. +** One can imagine alternative implementations that give O(N*logN*logK) +** performance, but they require more complex logic and data structures. +** The developers have elected to keep the asymptotically slower +** algorithm for now, for simplicity, under the theory that window +** functions are seldom used and when they are, the window size K is +** often small. The developers might revisit that decision later, +** should the need arise. */ -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT1 +#if defined(SQLITE3_H) + /* no-op */ +#elif defined(SQLITE_STATIC_PERCENTILE) +# include "sqlite3.h" +#else +# include "sqlite3ext.h" + SQLITE_EXTENSION_INIT1 +#endif #include #include #include -/* The following object is the session context for a single percentile() -** function. We have to remember all input Y values until the very end. +/* The following object is the group context for a single percentile() +** aggregate. Remember all input Y values until the very end. ** Those values are accumulated in the Percentile.a[] array. */ typedef struct Percentile Percentile; struct Percentile { unsigned nAlloc; /* Number of slots allocated for a[] */ unsigned nUsed; /* Number of slots actually used in a[] */ - double rPct; /* 1.0 more than the value for P */ + char bSorted; /* True if a[] is already in sorted order */ + char bKeepSorted; /* True if advantageous to keep a[] sorted */ + char bPctValid; /* True if rPct is valid */ + double rPct; /* Fraction. 0.0 to 1.0 */ double *a; /* Array of Y values */ }; +/* Details of each function in the percentile family */ +typedef struct PercentileFunc PercentileFunc; +struct PercentileFunc { + const char *zName; /* Function name */ + char nArg; /* Number of arguments */ + char mxFrac; /* Maximum value of the "fraction" input */ + char bDiscrete; /* True for percentile_disc() */ +}; +static const PercentileFunc aPercentFunc[] = { + { "median", 1, 1, 0 }, + { "percentile", 2, 100, 0 }, + { "percentile_cont", 2, 1, 0 }, + { "percentile_disc", 2, 1, 1 }, +}; + /* ** Return TRUE if the input floating-point number is an infinity. */ -static int isInfinity(double r){ +static int percentIsInfinity(double r){ sqlite3_uint64 u; assert( sizeof(u)==sizeof(r) ); memcpy(&u, &r, sizeof(u)); @@ -87,13 +166,64 @@ static int isInfinity(double r){ } /* -** Return TRUE if two doubles differ by 0.001 or less +** Return TRUE if two doubles differ by 0.001 or less. */ -static int sameValue(double a, double b){ +static int percentSameValue(double a, double b){ a -= b; return a>=-0.001 && a<=0.001; } +/* +** Search p (which must have p->bSorted) looking for an entry with +** value y. Return the index of that entry. +** +** If bExact is true, return -1 if the entry is not found. +** +** If bExact is false, return the index at which a new entry with +** value y should be insert in order to keep the values in sorted +** order. The smallest return value in this case will be 0, and +** the largest return value will be p->nUsed. +*/ +static int percentBinarySearch(Percentile *p, double y, int bExact){ + int iFirst = 0; /* First element of search range */ + int iLast = p->nUsed - 1; /* Last element of search range */ + while( iLast>=iFirst ){ + int iMid = (iFirst+iLast)/2; + double x = p->a[iMid]; + if( xy ){ + iLast = iMid - 1; + }else{ + return iMid; + } + } + if( bExact ) return -1; + return iFirst; +} + +/* +** Generate an error for a percentile function. +** +** The error format string must have exactly one occurrance of "%%s()" +** (with two '%' characters). That substring will be replaced by the name +** of the function. +*/ +static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){ + PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); + char *zMsg1; + char *zMsg2; + va_list ap; + + va_start(ap, zFormat); + zMsg1 = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, pFunc->zName) : 0; + sqlite3_result_error(pCtx, zMsg2, -1); + sqlite3_free(zMsg1); + sqlite3_free(zMsg2); +} + /* ** The "step" function for percentile(Y,P) is called once for each ** input row. @@ -103,16 +233,24 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ double rPct; int eType; double y; - assert( argc==2 ); - - /* Requirement 3: P must be a number between 0 and 100 */ - eType = sqlite3_value_numeric_type(argv[1]); - rPct = sqlite3_value_double(argv[1]); - if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) - || rPct<0.0 || rPct>100.0 ){ - sqlite3_result_error(pCtx, "2nd argument to percentile() is not " - "a number between 0.0 and 100.0", -1); - return; + assert( argc==2 || argc==1 ); + + if( argc==1 ){ + /* Requirement 13: median(Y) is the same as percentile(Y,50). */ + rPct = 0.5; + }else{ + /* Requirement 3: P must be a number between 0 and 100 */ + PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); + eType = sqlite3_value_numeric_type(argv[1]); + rPct = sqlite3_value_double(argv[1])/(double)pFunc->mxFrac; + if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) + || rPct<0.0 || rPct>1.0 + ){ + percentError(pCtx, "the fraction argument to %%s()" + " is not between 0.0 and %.1f", + (double)pFunc->mxFrac); + return; + } } /* Allocate the session context. */ @@ -121,11 +259,12 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ /* Remember the P value. Throw an error if the P value is different ** from any prior row, per Requirement (2). */ - if( p->rPct==0.0 ){ - p->rPct = rPct+1.0; - }else if( !sameValue(p->rPct,rPct+1.0) ){ - sqlite3_result_error(pCtx, "2nd argument to percentile() is not the " - "same for all input rows", -1); + if( !p->bPctValid ){ + p->rPct = rPct; + p->bPctValid = 1; + }else if( !percentSameValue(p->rPct,rPct) ){ + percentError(pCtx, "the fraction argument to %%s()" + " is not the same for all input rows"); return; } @@ -136,15 +275,14 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ /* If not NULL, then Y must be numeric. Otherwise throw an error. ** Requirement 4 */ if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ - sqlite3_result_error(pCtx, "1st argument to percentile() is not " - "numeric", -1); + percentError(pCtx, "input to %%s() is not numeric"); return; } /* Throw an error if the Y value is infinity or NaN */ y = sqlite3_value_double(argv[0]); - if( isInfinity(y) ){ - sqlite3_result_error(pCtx, "Inf input to percentile()", -1); + if( percentIsInfinity(y) ){ + percentError(pCtx, "Inf input to %%s()"); return; } @@ -161,26 +299,143 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ p->nAlloc = n; p->a = a; } - p->a[p->nUsed++] = y; + if( p->nUsed==0 ){ + p->a[p->nUsed++] = y; + p->bSorted = 1; + }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){ + p->a[p->nUsed++] = y; + }else if( p->bKeepSorted ){ + int i; + i = percentBinarySearch(p, y, 0); + if( i<(int)p->nUsed ){ + memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0])); + } + p->a[i] = y; + p->nUsed++; + }else{ + p->a[p->nUsed++] = y; + p->bSorted = 0; + } } /* -** Compare to doubles for sorting using qsort() +** Interchange two doubles. */ -static int SQLITE_CDECL doubleCmp(const void *pA, const void *pB){ - double a = *(double*)pA; - double b = *(double*)pB; - if( a==b ) return 0; - if( a=2 ); + if( a[0]>a[n-1] ){ + SWAP_DOUBLE(a[0],a[n-1]) + } + if( n==2 ) return; + iGt = n-1; + i = n/2; + if( a[0]>a[i] ){ + SWAP_DOUBLE(a[0],a[i]) + }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) + } + if( n==3 ) return; + rPivot = a[i]; + iLt = i = 1; + do{ + if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) + iLt++; + i++; + }else if( a[i]>rPivot ){ + do{ + iGt--; + }while( iGt>i && a[iGt]>rPivot ); + SWAP_DOUBLE(a[i],a[iGt]) + }else{ + i++; + } + }while( i=2 ) percentSort(a, iLt); + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); + +/* Uncomment for testing */ +#if 0 + for(i=0; ibSorted==0 ){ + assert( p->nUsed>1 ); + percentSort(p->a, p->nUsed); + p->bSorted = 1; + } + p->bKeepSorted = 1; + + /* Find and remove the row */ + i = percentBinarySearch(p, y, 1); + if( i>=0 ){ + p->nUsed--; + if( i<(int)p->nUsed ){ + memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0])); + } + } +} + +/* +** Compute the final output of percentile(). Clean up all allocated +** memory if and only if bIsFinal is true. +*/ +static void percentCompute(sqlite3_context *pCtx, int bIsFinal){ + Percentile *p; + PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); unsigned i1, i2; double v1, v2; double ix, vx; @@ -188,21 +443,38 @@ static void percentFinal(sqlite3_context *pCtx){ if( p==0 ) return; if( p->a==0 ) return; if( p->nUsed ){ - qsort(p->a, p->nUsed, sizeof(double), doubleCmp); - ix = (p->rPct-1.0)*(p->nUsed-1)*0.01; + if( p->bSorted==0 ){ + assert( p->nUsed>1 ); + percentSort(p->a, p->nUsed); + p->bSorted = 1; + } + ix = p->rPct*(p->nUsed-1); i1 = (unsigned)ix; - i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; - v1 = p->a[i1]; - v2 = p->a[i2]; - vx = v1 + (v2-v1)*(ix-i1); + if( pFunc->bDiscrete ){ + vx = p->a[i1]; + }else{ + i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; + v1 = p->a[i1]; + v2 = p->a[i2]; + vx = v1 + (v2-v1)*(ix-i1); + } sqlite3_result_double(pCtx, vx); } - sqlite3_free(p->a); - memset(p, 0, sizeof(*p)); + if( bIsFinal ){ + sqlite3_free(p->a); + memset(p, 0, sizeof(*p)); + }else{ + p->bKeepSorted = 1; + } +} +static void percentFinal(sqlite3_context *pCtx){ + percentCompute(pCtx, 1); +} +static void percentValue(sqlite3_context *pCtx){ + percentCompute(pCtx, 0); } - -#ifdef _WIN32 +#if defined(_WIN32) && !defined(SQLITE3_H) && !defined(SQLITE_STATIC_PERCENTILE) __declspec(dllexport) #endif int sqlite3_percentile_init( @@ -211,10 +483,21 @@ int sqlite3_percentile_init( const sqlite3_api_routines *pApi ){ int rc = SQLITE_OK; + unsigned int i; +#if defined(SQLITE3_H) || defined(SQLITE_STATIC_PERCENTILE) + (void)pApi; /* Unused parameter */ +#else SQLITE_EXTENSION_INIT2(pApi); +#endif (void)pzErrMsg; /* Unused parameter */ - rc = sqlite3_create_function(db, "percentile", 2, - SQLITE_UTF8|SQLITE_INNOCUOUS, 0, - 0, percentStep, percentFinal); + for(i=0; i